diff --git a/$index.ts b/$index.ts index 0d972c1..844621b 100644 --- a/$index.ts +++ b/$index.ts @@ -1,4 +1,4 @@ -import { $State, $StateArgument, $StateOption } from "./index"; +import { $EventManager, $State, $StateArgument, $StateOption } from "./index"; import { $Node } from "./lib/node/$Node" import { $Document } from "./lib/node/$Document" import { $Anchor } from "./lib/node/$Anchor"; @@ -11,7 +11,6 @@ import { $Label } from "./lib/node/$Label"; import { $Image } from "./lib/node/$Image"; import { $Canvas } from "./lib/node/$Canvas"; import { $Dialog } from "./lib/node/$Dialog"; -import { $View } from "./lib/node/$View"; import { $Select } from "./lib/node/$Select"; import { $Option } from "./lib/node/$Option"; import { $OptGroup } from "./lib/node/$OptGroup"; @@ -56,8 +55,9 @@ export function $(resolver: any) { } export namespace $ { export let anchorHandler: null | (($a: $Anchor, e: Event) => void) = null; - export let anchorPreventDefault: boolean = false; export const TagNameElementMap = { + 'html': $Container, + 'head': $Container, 'document': $Document, 'body': $Container, 'a': $Anchor, @@ -84,7 +84,6 @@ export namespace $ { 'img': $Image, 'dialog': $Dialog, 'canvas': $Canvas, - 'view': $View, 'select': $Select, 'option': $Option, 'optgroup': $OptGroup, @@ -192,9 +191,7 @@ export namespace $ { }) } - export function rem(amount: number = 1) { - return parseInt(getComputedStyle(document.documentElement).fontSize) * amount - } + export function rem(amount: number = 1) { return parseInt(getComputedStyle(document.documentElement).fontSize) * amount } export function html(html: string) { const body = new DOMParser().parseFromString(html, 'text/html').body; @@ -240,6 +237,10 @@ export namespace $ { Object.assign($.TagNameElementMap, {[string]: node}); return $.TagNameElementMap; } + + export function events(...eventname: N[]) { return new $EventManager<{[keys in N]: any[]}>().register(...eventname) } + + export function call(fn: () => T): T { return fn() } } type BuildNodeFunction = (...args: any[]) => $Node; type BuilderSelfFunction = (self: K) => void; diff --git a/index.ts b/index.ts index ddc9752..6ff3bea 100644 --- a/index.ts +++ b/index.ts @@ -1,7 +1,7 @@ declare global { var $: import('./$index').$; interface Array { - detype(...types: F[]): Array> + detype(...types: F[]): Array> } type OrMatrix = T | OrMatrix[]; type OrArray = T | T[]; @@ -23,11 +23,11 @@ declare global { $: import('./lib/node/$Node').$Node; } } -Array.prototype.detype = function (this: O[], ...types: T[]) { +Array.prototype.detype = function (this: O[], ...types: T[]) { return this.filter(item => { if (!types.length) return item !== undefined; else for (const type of types) if (typeof item !== typeof type) return true; else return false; - }) as Exclude[]; + }) as Exclude[]; } export * from "./$index"; export * from "./lib/node/$Node"; @@ -40,7 +40,6 @@ export * from "./lib/node/$Button"; export * from "./lib/node/$Form"; export * from "./lib/$EventManager"; export * from "./lib/$State"; -export * from "./lib/node/$View"; export * from "./lib/node/$Select"; export * from "./lib/node/$Option"; export * from "./lib/node/$OptGroup"; diff --git a/lib/$EventManager.ts b/lib/$EventManager.ts index 35cc005..df54dcb 100644 --- a/lib/$EventManager.ts +++ b/lib/$EventManager.ts @@ -8,7 +8,7 @@ export abstract class $EventMethod { once(type: K, callback: (...args: EM[K]) => any) { this.events.once(type, callback); return this } } export class $EventManager { - private eventMap = new Map(); + eventMap = new Map(); register(...names: string[]) { names.forEach(name => { const event = new $Event(name); diff --git a/lib/node/$Anchor.ts b/lib/node/$Anchor.ts index 87bf5fe..9160bef 100644 --- a/lib/node/$Anchor.ts +++ b/lib/node/$Anchor.ts @@ -7,8 +7,10 @@ export class $Anchor extends $Container { super('a', options); // Link Handler event this.dom.addEventListener('click', e => { - if ($.anchorPreventDefault) e.preventDefault(); - if ($.anchorHandler && !!this.href()) $.anchorHandler(this, e) + if ($.anchorHandler && !!this.href()) { + e.preventDefault(); + $.anchorHandler(this, e); + } }) } /**Set URL of anchor element. */ diff --git a/lib/node/$Async.ts b/lib/node/$Async.ts index 5ef5358..c39c9d1 100644 --- a/lib/node/$Async.ts +++ b/lib/node/$Async.ts @@ -1,5 +1,7 @@ -import { $Container, $ContainerOptions } from "./$Container"; +import { $State } from "../$State"; +import { $Container, $ContainerContentType, $ContainerOptions } from "./$Container"; import { $Node } from "./$Node"; +import { $Text } from "./$Text"; export interface $AsyncNodeOptions extends $ContainerOptions {} export class $Async extends $Container { #loaded: boolean = false; @@ -7,14 +9,22 @@ export class $Async extends $Container { super('async', options) } - await($node: Promise) { - $node.then($node => this._loaded($node)); + await($node: Promise | (($self: this) => Promise)) { + if ($node instanceof Function) $node(this).then($node => this._loaded($node)); + else $node.then($node => this._loaded($node)); return this as $Async } - protected _loaded($node: $Node) { + protected _loaded($node: $ContainerContentType) { this.#loaded = true; - this.replace($node) + if (typeof $node === 'string') this.replace(new $Text($node)); + else if ($node instanceof $State) { + const ele = new $Text($node.toString()); + $node.use(ele, 'content'); + this.replace(ele); + } + else if ($node === null || $node === undefined) this.replace(new $Text(String($node))); + else this.replace($node) this.dom.dispatchEvent(new Event('load')) } diff --git a/lib/node/$Container.ts b/lib/node/$Container.ts index ac4ba9b..f8cb1bc 100644 --- a/lib/node/$Container.ts +++ b/lib/node/$Container.ts @@ -22,22 +22,29 @@ export class $Container extends $HTMLElemen private __position_cursor = 0; /**Insert element to this element */ - insert(children: $ContainerContentBuilder, position = -1): this { return $.fluent(this, arguments, () => this, () => { - if (children instanceof Function) children = children(this); + insert(children: $ContainerContentBuilder, position = -1): this { return $.fluent(this, arguments, () => this, async () => { + if (children instanceof Function) children = await children(this); // resolve function children = $.orArrayResolve(children); + // Set position cursor depend negative or positive number, position will count from last index when position is negative. this.__position_cursor = position < 0 ? this.children.array.length + position : position; for (const child of children) { - if (child === undefined || child === null) continue; - if (child instanceof Array) this.insert(child, this.__position_cursor); - else if (typeof child === 'string') this.children.add(new $Text(child), position); + if (child === undefined || child === null) continue; // skip + if (child instanceof Array) this.insert(child, this.__position_cursor); // insert element group at this position + else if (typeof child === 'string') this.children.add(new $Text(child), position); // turn string into $Text element else if (child instanceof $State) { - const ele = new $Text(child.toString()); - child.use(ele, 'content'); + const ele = new $Text(child.toString()); // turn $State object into $Text element + child.use(ele, 'content'); // bind $Text elelment and function name to $State this.children.add(ele, position); - } else this.children.add(child, position); - this.__position_cursor += 1; + } + else if (child instanceof Promise) { + const $Async = (await import('./$Async')).$Async; // import $Async avoid extends error + const ele = new $Async().await(child) // using $Async.await resolve promise element + this.children.add(ele, position); // insert $Async element at this position, leave a position for promised element + } + else this.children.add(child, position); // insert $Node element directly + this.__position_cursor += 1; // increase position count } - this.children.render(); + this.children.render(); // start to render dom tree })} /**Remove all children elemetn from this element */ @@ -64,5 +71,6 @@ export class $Container extends $HTMLElemen scrollLeft(scrollLeft?: $StateArgument | undefined) { return $.fluent(this, arguments, () => this.dom.scrollLeft, () => $.set(this.dom, 'scrollLeft', scrollLeft as any))} } -export type $ContainerContentBuilder

= OrMatrix<$ContainerContentType> | (($node: P) => OrMatrix<$ContainerContentType>) +export type $ContainerContentBuilder

= $ContainerContentGroup | (($node: P) => OrPromise<$ContainerContentGroup>) +export type $ContainerContentGroup = OrMatrix> export type $ContainerContentType = $Node | string | undefined | $State | null \ No newline at end of file diff --git a/lib/node/$Element.ts b/lib/node/$Element.ts index 6026ef6..9045482 100644 --- a/lib/node/$Element.ts +++ b/lib/node/$Element.ts @@ -4,6 +4,7 @@ export interface $ElementOptions { id?: string; class?: string[]; dom?: HTMLElement | SVGElement; + tagname?: string; } export class $Element extends $Node { @@ -19,7 +20,7 @@ export class $Element extends private createDom(tagname: string, options?: $ElementOptions) { if (options?.dom) return options.dom; if (tagname === 'svg') return document.createElementNS("http://www.w3.org/2000/svg", "svg"); - return document.createElement(tagname); + return document.createElement(options?.tagname ?? tagname); } @@ -85,8 +86,8 @@ export class $Element extends animate(keyframes: Keyframe[] | PropertyIndexedKeyframes | null, options?: number | KeyframeAnimationOptions, callback?: (animation: Animation) => void) { const animation = this.dom.animate(keyframes, options); - if (callback) callback(animation); - return this; + if (callback) animation.onfinish = () => callback(animation); + return animation; } getAnimations(options?: GetAnimationsOptions) { return this.dom.getAnimations(options) } diff --git a/lib/node/$Input.ts b/lib/node/$Input.ts index ae11610..73aeb86 100644 --- a/lib/node/$Input.ts +++ b/lib/node/$Input.ts @@ -1,10 +1,10 @@ -import { $Element, $ElementOptions } from "./$Element"; import { $State, $StateArgument } from "../$State"; import { $HTMLElementAPIFilter, $HTMLElementAPIs } from "../$ElementTemplate"; import { $Util } from "../$Util"; +import { $HTMLElement, $HTMLElementOptions } from "./$HTMLElement"; -export interface $InputOptions extends $ElementOptions {} -export class $Input extends $Element { +export interface $InputOptions extends $HTMLElementOptions {} +export class $Input extends $HTMLElement { constructor(options?: $InputOptions) { super('input', options); } @@ -117,7 +117,6 @@ export class $Input extends $Element {} $Util.mixin($Input, $HTMLElementAPIs.create('checkValidity', 'reportValidity', 'autocomplete', 'name', 'form', 'required', 'validationMessage', 'validity', 'willValidate', 'formAction', 'formEnctype', 'formMethod', 'formNoValidate', 'formTarget')) - export class $NumberInput extends $Input { constructor(options?: $InputOptions) { super(options) @@ -169,7 +168,7 @@ export class $FileInput extends $Input { } static from($input: $Input) { - return $.mixin($Input, this) as $CheckInput; + return $.mixin($Input, this) as $FileInput; } multiple(): boolean; @@ -181,4 +180,5 @@ export class $FileInput extends $Input { accept(...filetype: string[]) { return $.fluent(this, arguments, () => this.dom.accept.split(','), () => this.dom.accept = filetype.toString() )} } -export type $InputType = T extends 'number' ? $NumberInput : T extends 'radio' | 'checkbox' ? $CheckInput : T extends 'file' ? $FileInput : $Input; \ No newline at end of file +export type $InputType = T extends 'number' ? $NumberInput : T extends 'radio' | 'checkbox' ? $CheckInput : T extends 'file' ? $FileInput : $Input; +$Util.mixin($Input, [$NumberInput, $CheckInput, $FileInput]) \ No newline at end of file diff --git a/lib/node/$Node.ts b/lib/node/$Node.ts index 7591440..b9968ca 100644 --- a/lib/node/$Node.ts +++ b/lib/node/$Node.ts @@ -65,4 +65,8 @@ export abstract class $Node { if (this instanceof $Element) return true; else return false; } + get element(): $Element | null { + if (this instanceof $Element) return this; + else return null; + } } \ No newline at end of file diff --git a/lib/node/$Select.ts b/lib/node/$Select.ts index 0d2b442..dcafc8e 100644 --- a/lib/node/$Select.ts +++ b/lib/node/$Select.ts @@ -1,7 +1,7 @@ import { $Container, $ContainerOptions } from "./$Container"; import { $OptGroup } from "./$OptGroup"; import { $Option } from "./$Option"; -import { $StateArgument } from "../$State"; +import { $State, $StateArgument } from "../$State"; import { $HTMLElementAPIFilter, $HTMLElementAPIs } from "../$ElementTemplate"; import { $Util } from "../$Util"; @@ -31,7 +31,13 @@ export class $Select extends $Container { value(): string; value(value?: $StateArgument | undefined): this; - value(value?: $StateArgument | undefined) { return $.fluent(this, arguments, () => this.dom.value, () => $.set(this.dom, 'value', value))} + value(value?: $StateArgument | undefined) { return $.fluent(this, arguments, () => this.dom.value, () => $.set(this.dom, 'value', value as $State | string, (value$) => { + this.on('input', () => { + if (value$.attributes.has(this.dom) === false) return; + if (typeof value$.value === 'string') (value$ as $State).set(`${this.value()}`) + if (typeof value$.value === 'number') (value$ as unknown as $State).set(Number(this.value())) + }) + }))} get labels() { return Array.from(this.dom.labels ?? []).map(label => $(label)) } } diff --git a/lib/node/$View.ts b/lib/node/$View.ts deleted file mode 100644 index 75ae1d6..0000000 --- a/lib/node/$View.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { $Container, $ContainerOptions } from "./$Container"; -import { $EventManager } from "../$EventManager"; -import { $Node } from "./$Node"; - -export interface $ViewOptions extends $ContainerOptions {} -export class $View extends $Container { - protected view_cache = new Map(); - event = new $EventManager<$ViewEventMap>().register('switch') - content_id: string | null = null; - constructor(options?: $ViewOptions) { - super('view', options); - } - - setView(id: string, $node: $Node) { - this.view_cache.set(id, $node); - return this; - } - - deleteView(id: string) { - this.view_cache.delete(id); - return this; - } - - deleteAllView() { - this.view_cache.clear(); - return this; - } - - switchView(id: string) { - const target_content = this.view_cache.get(id); - if (target_content === undefined) return this; - this.content(target_content); - this.content_id = id; - this.event.fire('switch', id); - return this; - } -} - -export interface $ViewEventMap { - 'switch': [id: string] -} \ No newline at end of file diff --git a/package.json b/package.json index 7f68cda..55207f9 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "elexis", "description": "Build Web in Native JavaScript Syntax", - "version": "0.2.4", + "version": "0.2.5", "author": { "name": "defaultkavy", "email": "defaultkavy@gmail.com",