v0.0.7
change: Router.open(), .replace() .back(), .events change to static add: $.open(), $.replace(), $.back() add: $.html() convert html string to $Element update: $State support output format update: $Util.from() convert children node to $Element change: $Node.parent change to getter change: using $StateArgument<T> new: $SVGElement, $HTMLElement new: $AsyncNode add: $Image.load()
This commit is contained in:
parent
4b3c32c88d
commit
b3f28dbf86
116
$index.ts
116
$index.ts
@ -1,41 +1,47 @@
|
|||||||
import { $Node, $State } from "./index";
|
import { $Node, $State, $StateOption } from "./index";
|
||||||
import { $Anchor } from "./lib/$Anchor";
|
import { $Anchor } from "./lib/node/$Anchor";
|
||||||
import { $Button } from "./lib/$Button";
|
import { $Button } from "./lib/node/$Button";
|
||||||
import { $Form } from "./lib/$Form";
|
import { $Form } from "./lib/node/$Form";
|
||||||
import { $Input } from "./lib/$Input";
|
import { $Input } from "./lib/node/$Input";
|
||||||
import { $Container } from "./lib/$Container";
|
import { $Container } from "./lib/node/$Container";
|
||||||
import { $Element } from "./lib/$Element";
|
import { $Element } from "./lib/node/$Element";
|
||||||
import { $Label } from "./lib/$Label";
|
import { $Label } from "./lib/node/$Label";
|
||||||
import { Router } from "./lib/Router/Router";
|
import { Router } from "./lib/router/Router";
|
||||||
import { $Image } from "./lib/$Image";
|
import { $Image } from "./lib/node/$Image";
|
||||||
import { $Canvas } from "./lib/$Canvas";
|
import { $Canvas } from "./lib/node/$Canvas";
|
||||||
import { $Dialog } from "./lib/$Dialog";
|
import { $Dialog } from "./lib/node/$Dialog";
|
||||||
import { $View } from "./lib/$View";
|
import { $View } from "./lib/node/$View";
|
||||||
import { $Select } from "./lib/$Select";
|
import { $Select } from "./lib/node/$Select";
|
||||||
import { $Option } from "./lib/$Option";
|
import { $Option } from "./lib/node/$Option";
|
||||||
import { $OptGroup } from "./lib/$OptGroup";
|
import { $OptGroup } from "./lib/node/$OptGroup";
|
||||||
import { $Textarea } from "./lib/$Textarea";
|
import { $Textarea } from "./lib/node/$Textarea";
|
||||||
|
import { $Util } from "./lib/$Util";
|
||||||
|
import { $HTMLElement } from "./lib/node/$HTMLElement";
|
||||||
|
import { $AsyncNode } from "./lib/node/$AsyncNode";
|
||||||
|
|
||||||
export type $ = typeof $;
|
export type $ = typeof $;
|
||||||
export function $<E extends $Element = $Element>(query: `::${string}`): E[];
|
export function $<E extends $Element = $Element>(query: `::${string}`): E[];
|
||||||
export function $<E extends $Element = $Element>(query: `:${string}`): E | null;
|
export function $<E extends $Element = $Element>(query: `:${string}`): E | null;
|
||||||
export function $(element: null): null;
|
export function $(element: null): null;
|
||||||
export function $<K extends keyof $.TagNameTypeMap>(resolver: K): $.TagNameTypeMap[K];
|
export function $<K extends keyof $.TagNameTypeMap>(resolver: K): $.TagNameTypeMap[K];
|
||||||
export function $<K extends string>(resolver: K): $Container;
|
export function $<K extends string>(resolver: K): $Container;
|
||||||
export function $<H extends HTMLElement>(htmlElement: H): $.HTMLElementTo$ElementMap<H>;
|
export function $<H extends HTMLElement>(htmlElement: H): $.$HTMLElementMap<H>;
|
||||||
export function $<H extends Element>(element: H): $Element;
|
export function $<H extends Element>(element: H): $Element;
|
||||||
|
export function $<N extends $Node>(node: N): N;
|
||||||
export function $<H extends EventTarget>(element: H): $Element;
|
export function $<H extends EventTarget>(element: H): $Element;
|
||||||
export function $(element: null | HTMLElement | EventTarget): $Element | null;
|
export function $(element: null | HTMLElement | EventTarget): $Element | null;
|
||||||
export function $(element: undefined): undefined;
|
export function $(element: undefined): undefined;
|
||||||
export function $(resolver: any) {
|
export function $(resolver: any) {
|
||||||
if (typeof resolver === 'undefined') return resolver;
|
if (typeof resolver === 'undefined') return resolver;
|
||||||
if (resolver === null) return resolver;
|
if (resolver === null) return resolver;
|
||||||
|
if (resolver instanceof $Node) return resolver;
|
||||||
if (typeof resolver === 'string') {
|
if (typeof resolver === 'string') {
|
||||||
if (resolver.startsWith('::')) return Array.from(document.querySelectorAll(resolver.replace(/^::/, ''))).map(dom => $(dom));
|
if (resolver.startsWith('::')) return Array.from(document.querySelectorAll(resolver.replace(/^::/, ''))).map(dom => $(dom));
|
||||||
else if (resolver.startsWith(':')) return $(document.querySelector(resolver.replace(/^:/, '')));
|
else if (resolver.startsWith(':')) return $(document.querySelector(resolver.replace(/^:/, '')));
|
||||||
else if (resolver in $.TagNameElementMap) {
|
else if (resolver in $.TagNameElementMap) {
|
||||||
const instance = $.TagNameElementMap[resolver as keyof typeof $.TagNameElementMap]
|
const instance = $.TagNameElementMap[resolver as keyof typeof $.TagNameElementMap]
|
||||||
switch (instance) {
|
switch (instance) {
|
||||||
case $Element: return new $Element(resolver);
|
case $HTMLElement: return new $HTMLElement(resolver);
|
||||||
case $Anchor: return new $Anchor();
|
case $Anchor: return new $Anchor();
|
||||||
case $Container: return new $Container(resolver);
|
case $Container: return new $Container(resolver);
|
||||||
case $Input: return new $Input();
|
case $Input: return new $Input();
|
||||||
@ -50,20 +56,22 @@ export function $(resolver: any) {
|
|||||||
case $Option: return new $Option();
|
case $Option: return new $Option();
|
||||||
case $OptGroup: return new $OptGroup();
|
case $OptGroup: return new $OptGroup();
|
||||||
case $Textarea: return new $Textarea();
|
case $Textarea: return new $Textarea();
|
||||||
|
case $AsyncNode: return new $AsyncNode();
|
||||||
}
|
}
|
||||||
} else return new $Container(resolver);
|
} else return new $Container(resolver);
|
||||||
}
|
}
|
||||||
if (resolver instanceof HTMLElement || resolver instanceof Text) {
|
if (resolver instanceof HTMLElement || resolver instanceof Text || resolver instanceof SVGElement) {
|
||||||
if (resolver.$) return resolver.$;
|
if (resolver.$) return resolver.$;
|
||||||
else return $Node.from(resolver);
|
else return $Util.from(resolver);
|
||||||
}
|
}
|
||||||
throw '$: NOT SUPPORT TARGET ELEMENT TYPE'
|
throw `$: NOT SUPPORT TARGET ELEMENT TYPE ('${resolver}')`
|
||||||
}
|
}
|
||||||
export namespace $ {
|
export namespace $ {
|
||||||
export let anchorHandler: null | ((url: string, e: Event) => void) = null;
|
export let anchorHandler: null | (($a: $Anchor, e: Event) => void) = null;
|
||||||
export let anchorPreventDefault: boolean = false;
|
export let anchorPreventDefault: boolean = false;
|
||||||
export const routers = new Set<Router>;
|
export const routers = new Set<Router>;
|
||||||
export const TagNameElementMap = {
|
export const TagNameElementMap = {
|
||||||
|
'body': $Container,
|
||||||
'a': $Anchor,
|
'a': $Anchor,
|
||||||
'p': $Container,
|
'p': $Container,
|
||||||
'pre': $Container,
|
'pre': $Container,
|
||||||
@ -92,7 +100,8 @@ export namespace $ {
|
|||||||
'select': $Select,
|
'select': $Select,
|
||||||
'option': $Option,
|
'option': $Option,
|
||||||
'optgroup': $OptGroup,
|
'optgroup': $OptGroup,
|
||||||
'textarea': $Textarea
|
'textarea': $Textarea,
|
||||||
|
'async': $AsyncNode,
|
||||||
}
|
}
|
||||||
export type TagNameTypeMap = {
|
export type TagNameTypeMap = {
|
||||||
[key in keyof typeof $.TagNameElementMap]: InstanceType<typeof $.TagNameElementMap[key]>;
|
[key in keyof typeof $.TagNameElementMap]: InstanceType<typeof $.TagNameElementMap[key]>;
|
||||||
@ -100,7 +109,7 @@ export namespace $ {
|
|||||||
export type ContainerTypeTagName = Exclude<keyof TagNameTypeMap, 'input'>;
|
export type ContainerTypeTagName = Exclude<keyof TagNameTypeMap, 'input'>;
|
||||||
export type SelfTypeTagName = 'input';
|
export type SelfTypeTagName = 'input';
|
||||||
|
|
||||||
export type HTMLElementTo$ElementMap<H extends HTMLElement> =
|
export type $HTMLElementMap<H extends HTMLElement> =
|
||||||
H extends HTMLLabelElement ? $Label
|
H extends HTMLLabelElement ? $Label
|
||||||
: H extends HTMLInputElement ? $Input
|
: H extends HTMLInputElement ? $Input
|
||||||
: H extends HTMLAnchorElement ? $Anchor
|
: H extends HTMLAnchorElement ? $Anchor
|
||||||
@ -116,6 +125,10 @@ export namespace $ {
|
|||||||
: H extends HTMLTextAreaElement ? $Textarea
|
: H extends HTMLTextAreaElement ? $Textarea
|
||||||
: $Container<H>;
|
: $Container<H>;
|
||||||
|
|
||||||
|
export function open(path: string | URL | undefined) { return Router.open(path) }
|
||||||
|
export function replace(path: string | URL | undefined) { return Router.replace(path) }
|
||||||
|
export function back() { return Router.back() }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A helper for fluent method design. Return the `instance` object when arguments length not equal 0. Otherwise, return the `value`.
|
* A helper for fluent method design. Return the `instance` object when arguments length not equal 0. Otherwise, return the `value`.
|
||||||
* @param instance The object to return when arguments length not equal 0.
|
* @param instance The object to return when arguments length not equal 0.
|
||||||
@ -135,41 +148,34 @@ export namespace $ {
|
|||||||
else return [multable];
|
else return [multable];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mixin(target: any, constructors: OrArray<any>) {
|
export function mixin(target: any, constructors: OrArray<any>) { return $Util.mixin(target, constructors) }
|
||||||
orArrayResolve(constructors).forEach(constructor => {
|
|
||||||
Object.getOwnPropertyNames(constructor.prototype).forEach(name => {
|
|
||||||
if (name === 'constructor') return;
|
|
||||||
Object.defineProperty(
|
|
||||||
target.prototype,
|
|
||||||
name,
|
|
||||||
Object.getOwnPropertyDescriptor(constructor.prototype, name) || Object.create(null)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
* A helper for $State.set() which apply value to target.
|
* A helper for undefined able value and $State.set() which apply value to target.
|
||||||
* @param object Target object.
|
* @param object Target object.
|
||||||
* @param key The key of target object.
|
* @param key The key of target object.
|
||||||
* @param value Value of target property or parameter of method(Using Tuple to apply parameter).
|
* @param value Value of target property or parameter of method(Using Tuple to apply parameter).
|
||||||
* @param methodKey Variant key name when apply value on $State.set()
|
* @param methodKey Variant key name when apply value on $State.set()
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function set<O, K extends keyof O>(object: O, key: K, value: O[K] extends (...args: any) => any ? (Parameters<O[K]> | $State<Parameters<O[K]>>) : O[K] | undefined | $State<O[K]>, methodKey?: string) {
|
export function set<O, K extends keyof O>(
|
||||||
if (value === undefined) return;
|
object: O, key: K,
|
||||||
if (value instanceof $State && object instanceof Node) {
|
value: O[K] extends (...args: any) => any
|
||||||
value.use(object.$, methodKey ?? key as any);
|
? (Parameters<O[K]> | $State<Parameters<O[K]> | undefined>)
|
||||||
const prop = object[key];
|
: (O[K] | undefined | $State<O[K] | undefined>),
|
||||||
if (prop instanceof Function) prop(value.value);
|
methodKey?: string) {
|
||||||
else object[key] = value.value;
|
if (value === undefined) return;
|
||||||
return;
|
if (value instanceof $State && object instanceof Node) {
|
||||||
}
|
value.use(object.$, methodKey ?? key as any);
|
||||||
object[key] = value as any;
|
if (object[key] instanceof Function) (object[key] as Function)(value)
|
||||||
|
else object[key] = value.value;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (object[key] instanceof Function) (object[key] as Function)(value);
|
||||||
|
else object[key] = value as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function state<T>(value: T) {
|
export function state<T>(value: T, options?: $StateOption<T>) {
|
||||||
return new $State<T>(value)
|
return new $State<T>(value, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function resize(object: Blob, size: number): Promise<string> {
|
export async function resize(object: Blob, size: number): Promise<string> {
|
||||||
@ -200,6 +206,11 @@ export namespace $ {
|
|||||||
return parseInt(getComputedStyle(document.documentElement).fontSize) * amount
|
return parseInt(getComputedStyle(document.documentElement).fontSize) * amount
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function html(html: string) {
|
||||||
|
const body = new DOMParser().parseFromString(html, 'text/html').body;
|
||||||
|
return Array.from(body.children).map(child => $(child))
|
||||||
|
}
|
||||||
|
|
||||||
/**Build multiple element in once. */
|
/**Build multiple element in once. */
|
||||||
export function builder<F extends BuildNodeFunction, R extends ReturnType<F>>(bulder: F, params: [...Parameters<F>][], callback?: BuilderSelfFunction<R>): R[]
|
export function builder<F extends BuildNodeFunction, R extends ReturnType<F>>(bulder: F, params: [...Parameters<F>][], callback?: BuilderSelfFunction<R>): R[]
|
||||||
export function builder<F extends BuildNodeFunction, R extends ReturnType<F>>(bulder: [F, ...Parameters<F>], size: number, callback?: BuilderSelfFunction<R>): R[]
|
export function builder<F extends BuildNodeFunction, R extends ReturnType<F>>(bulder: [F, ...Parameters<F>], size: number, callback?: BuilderSelfFunction<R>): R[]
|
||||||
@ -237,5 +248,4 @@ export namespace $ {
|
|||||||
}
|
}
|
||||||
type BuildNodeFunction = (...args: any[]) => $Node;
|
type BuildNodeFunction = (...args: any[]) => $Node;
|
||||||
type BuilderSelfFunction<K extends $Node> = (self: K) => void;
|
type BuilderSelfFunction<K extends $Node> = (self: K) => void;
|
||||||
globalThis.$ = $;
|
globalThis.$ = $;
|
||||||
|
|
32
index.ts
32
index.ts
@ -20,7 +20,7 @@ declare global {
|
|||||||
type ImageLoading = "eager" | "lazy";
|
type ImageLoading = "eager" | "lazy";
|
||||||
type ContructorType<T> = { new (...args: any[]): T }
|
type ContructorType<T> = { new (...args: any[]): T }
|
||||||
interface Node {
|
interface Node {
|
||||||
$: import('./lib/$Node').$Node;
|
$: import('./lib/node/$Node').$Node;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Array.prototype.detype = function <T extends undefined | null, O>(this: O[], ...types: T[]) {
|
Array.prototype.detype = function <T extends undefined | null, O>(this: O[], ...types: T[]) {
|
||||||
@ -30,20 +30,22 @@ Array.prototype.detype = function <T extends undefined | null, O>(this: O[], ...
|
|||||||
}) as Exclude<O, T>[];
|
}) as Exclude<O, T>[];
|
||||||
}
|
}
|
||||||
export * from "./$index";
|
export * from "./$index";
|
||||||
export * from "./lib/Router/Route";
|
export * from "./lib/router/Route";
|
||||||
export * from "./lib/Router/Router";
|
export * from "./lib/router/Router";
|
||||||
export * from "./lib/$Node";
|
export * from "./lib/node/$Node";
|
||||||
export * from "./lib/$Anchor";
|
export * from "./lib/node/$Anchor";
|
||||||
export * from "./lib/$Element";
|
export * from "./lib/node/$Element";
|
||||||
export * from "./lib/$NodeManager";
|
export * from "./lib/$NodeManager";
|
||||||
export * from "./lib/$Text";
|
export * from "./lib/node/$Text";
|
||||||
export * from "./lib/$Container";
|
export * from "./lib/node/$Container";
|
||||||
export * from "./lib/$Button";
|
export * from "./lib/node/$Button";
|
||||||
export * from "./lib/$Form";
|
export * from "./lib/node/$Form";
|
||||||
export * from "./lib/$EventManager";
|
export * from "./lib/$EventManager";
|
||||||
export * from "./lib/$State";
|
export * from "./lib/$State";
|
||||||
export * from "./lib/$View";
|
export * from "./lib/node/$View";
|
||||||
export * from "./lib/$Select";
|
export * from "./lib/node/$Select";
|
||||||
export * from "./lib/$Option";
|
export * from "./lib/node/$Option";
|
||||||
export * from "./lib/$OptGroup";
|
export * from "./lib/node/$OptGroup";
|
||||||
export * from "./lib/$Textarea";
|
export * from "./lib/node/$Textarea";
|
||||||
|
export * from "./lib/node/$Image";
|
||||||
|
export * from "./lib/node/$AsyncNode";
|
@ -1,11 +1,11 @@
|
|||||||
export abstract class $EventMethod<EM> {
|
export abstract class $EventMethod<EM> {
|
||||||
abstract events: $EventManager<EM>;
|
abstract events: $EventManager<EM>;
|
||||||
//@ts-expect-error
|
//@ts-expect-error
|
||||||
on<K extends keyof EM>(type: K, callback: (...args: EM[K]) => void) { this.events.on(type, callback); return this }
|
on<K extends keyof EM>(type: K, callback: (...args: EM[K]) => any) { this.events.on(type, callback); return this }
|
||||||
//@ts-expect-error
|
//@ts-expect-error
|
||||||
off<K extends keyof EM>(type: K, callback: (...args: EM[K]) => void) { this.events.off(type, callback); return this }
|
off<K extends keyof EM>(type: K, callback: (...args: EM[K]) => any) { this.events.off(type, callback); return this }
|
||||||
//@ts-expect-error
|
//@ts-expect-error
|
||||||
once<K extends keyof EM>(type: K, callback: (...args: EM[K]) => void) { this.events.once(type, callback); return this }
|
once<K extends keyof EM>(type: K, callback: (...args: EM[K]) => any) { this.events.once(type, callback); return this }
|
||||||
}
|
}
|
||||||
export class $EventManager<EM> {
|
export class $EventManager<EM> {
|
||||||
private eventMap = new Map<string, $Event>();
|
private eventMap = new Map<string, $Event>();
|
||||||
@ -25,17 +25,17 @@ export class $EventManager<EM> {
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
//@ts-expect-error
|
//@ts-expect-error
|
||||||
on<K extends keyof EM>(type: K, callback: (...args: EM[K]) => void) {
|
on<K extends keyof EM>(type: K, callback: (...args: EM[K]) => any) {
|
||||||
this.get(type).add(callback);
|
this.get(type).add(callback);
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
//@ts-expect-error
|
//@ts-expect-error
|
||||||
off<K extends keyof EM>(type: K, callback: (...args: EM[K]) => void) {
|
off<K extends keyof EM>(type: K, callback: (...args: EM[K]) => any) {
|
||||||
this.get(type).delete(callback);
|
this.get(type).delete(callback);
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
//@ts-expect-error
|
//@ts-expect-error
|
||||||
once<K extends keyof EM>(type: K, callback: (...args: EM[K]) => void) {
|
once<K extends keyof EM>(type: K, callback: (...args: EM[K]) => any) {
|
||||||
//@ts-expect-error
|
//@ts-expect-error
|
||||||
const onceFn = (...args: EM[K]) => {
|
const onceFn = (...args: EM[K]) => {
|
||||||
this.get(type).delete(onceFn);
|
this.get(type).delete(onceFn);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { $Container } from "./$Container";
|
import { $Container } from "./node/$Container";
|
||||||
import { $Node } from "./$Node";
|
import { $Node } from "./node/$Node";
|
||||||
import { $Text } from "./$Text";
|
import { $Text } from "./node/$Text";
|
||||||
|
|
||||||
export class $NodeManager {
|
export class $NodeManager {
|
||||||
#container: $Container;
|
#container: $Container;
|
||||||
@ -12,10 +12,8 @@ export class $NodeManager {
|
|||||||
add(element: $Node | string) {
|
add(element: $Node | string) {
|
||||||
if (typeof element === 'string') {
|
if (typeof element === 'string') {
|
||||||
const text = new $Text(element);
|
const text = new $Text(element);
|
||||||
(text as Mutable<$Text>).parent = this.#container;
|
|
||||||
this.elementList.add(text);
|
this.elementList.add(text);
|
||||||
} else {
|
} else {
|
||||||
(element as Mutable<$Node>).parent = this.#container;
|
|
||||||
this.elementList.add(element);
|
this.elementList.add(element);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -23,7 +21,6 @@ export class $NodeManager {
|
|||||||
remove(element: $Node) {
|
remove(element: $Node) {
|
||||||
if (!this.elementList.has(element)) return this;
|
if (!this.elementList.has(element)) return this;
|
||||||
this.elementList.delete(element);
|
this.elementList.delete(element);
|
||||||
(element as Mutable<$Node>).parent = undefined;
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,8 +36,6 @@ export class $NodeManager {
|
|||||||
})
|
})
|
||||||
this.elementList.clear();
|
this.elementList.clear();
|
||||||
array.forEach(node => this.elementList.add(node));
|
array.forEach(node => this.elementList.add(node));
|
||||||
(target as Mutable<$Node>).parent = undefined;
|
|
||||||
(replace as Mutable<$Node>).parent = this.#container;
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
import { $Container, $ContainerOptions } from "./$Container";
|
|
||||||
import { $State } from "./$State";
|
|
||||||
|
|
||||||
export interface $OptGroupOptions extends $ContainerOptions {}
|
|
||||||
export class $OptGroup extends $Container<HTMLOptGroupElement> {
|
|
||||||
constructor(options?: $OptGroupOptions) {
|
|
||||||
super('optgroup', options);
|
|
||||||
}
|
|
||||||
|
|
||||||
disabled(): boolean;
|
|
||||||
disabled(disabled: boolean | $State<boolean>): this;
|
|
||||||
disabled(disabled?: boolean | $State<boolean>) { return $.fluent(this, arguments, () => this.dom.disabled, () => $.set(this.dom, 'disabled', disabled))}
|
|
||||||
|
|
||||||
label(): string;
|
|
||||||
label(label: string | $State<string>): this;
|
|
||||||
label(label?: string | $State<string>) { return $.fluent(this, arguments, () => this.dom.label, () => $.set(this.dom, 'label', label))}
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
import { $Container, $ContainerOptions } from "./$Container";
|
|
||||||
import { $State } from "./$State";
|
|
||||||
|
|
||||||
export interface $OptionOptions extends $ContainerOptions {}
|
|
||||||
export class $Option extends $Container<HTMLOptionElement> {
|
|
||||||
constructor(options?: $OptionOptions) {
|
|
||||||
super('option', options);
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultSelected(): boolean;
|
|
||||||
defaultSelected(defaultSelected: boolean | $State<boolean>): this;
|
|
||||||
defaultSelected(defaultSelected?: boolean | $State<boolean>) { return $.fluent(this, arguments, () => this.dom.defaultSelected, () => $.set(this.dom, 'defaultSelected', defaultSelected))}
|
|
||||||
|
|
||||||
disabled(): boolean;
|
|
||||||
disabled(disabled: boolean | $State<boolean>): this;
|
|
||||||
disabled(disabled?: boolean | $State<boolean>) { return $.fluent(this, arguments, () => this.dom.disabled, () => $.set(this.dom, 'disabled', disabled))}
|
|
||||||
|
|
||||||
label(): string;
|
|
||||||
label(label: string | $State<string>): this;
|
|
||||||
label(label?: string | $State<string>) { return $.fluent(this, arguments, () => this.dom.label, () => $.set(this.dom, 'label', label))}
|
|
||||||
|
|
||||||
selected(): boolean;
|
|
||||||
selected(selected: boolean | $State<boolean>): this;
|
|
||||||
selected(selected?: boolean | $State<boolean>) { return $.fluent(this, arguments, () => this.dom.selected, () => $.set(this.dom, 'selected', selected))}
|
|
||||||
|
|
||||||
text(): string;
|
|
||||||
text(text: string | $State<string>): this;
|
|
||||||
text(text?: string | $State<string>) { return $.fluent(this, arguments, () => this.dom.text, () => $.set(this.dom, 'text', text))}
|
|
||||||
|
|
||||||
value(): string;
|
|
||||||
value(value: string | $State<string>): this;
|
|
||||||
value(value?: string | $State<string>) { return $.fluent(this, arguments, () => this.dom.value, () => $.set(this.dom, 'value', value))}
|
|
||||||
|
|
||||||
get form() { return this.dom.form ? $(this.dom.form) : null }
|
|
||||||
get index() { return this.dom.index }
|
|
||||||
|
|
||||||
}
|
|
@ -1,22 +1,33 @@
|
|||||||
import { $Node } from "./$Node";
|
import { $Node } from "./node/$Node";
|
||||||
|
|
||||||
|
export interface $StateOption<T> {
|
||||||
|
format: (value: T) => string;
|
||||||
|
}
|
||||||
export class $State<T> {
|
export class $State<T> {
|
||||||
readonly value: T;
|
readonly value: T;
|
||||||
readonly attributes = new Map<$Node, Set<string | number | symbol>>();
|
readonly attributes = new Map<$Node, Set<string | number | symbol>>();
|
||||||
constructor(value: T) {
|
options: Partial<$StateOption<T>> = {}
|
||||||
|
constructor(value: T, options?: $StateOption<T>) {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
|
if (options) this.options = options;
|
||||||
}
|
}
|
||||||
set(value: T) {
|
set(value: T) {
|
||||||
(this as Mutable<$State<T>>).value = value;
|
(this as Mutable<$State<T>>).value = value;
|
||||||
for (const [node, attrList] of this.attributes.entries()) {
|
for (const [node, attrList] of this.attributes.entries()) {
|
||||||
for (const attr of attrList) {
|
for (const attr of attrList) {
|
||||||
//@ts-expect-error
|
//@ts-expect-error
|
||||||
if (node[attr] instanceof Function) node[attr](value)
|
if (node[attr] instanceof Function) {
|
||||||
|
//@ts-expect-error
|
||||||
|
if (this.options.format) node[attr](this.options.format(value))
|
||||||
|
//@ts-expect-error
|
||||||
|
else node[attr](value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toString(): string {
|
toString(): string {
|
||||||
|
if (this.options.format) return this.options.format(this.value);
|
||||||
return `${this.value}`
|
return `${this.value}`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,4 +36,6 @@ export class $State<T> {
|
|||||||
if (attrList) attrList.add(attrName);
|
if (attrList) attrList.add(attrName);
|
||||||
else this.attributes.set($node, new Set<string | number | symbol>().add(attrName))
|
else this.attributes.set($node, new Set<string | number | symbol>().add(attrName))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type $StateArgument<T> = T | $State<T | undefined>;
|
30
lib/$Util.ts
30
lib/$Util.ts
@ -1,4 +1,8 @@
|
|||||||
import { $State } from "./$State";
|
import { $State } from "./$State";
|
||||||
|
import { $Container } from "./node/$Container";
|
||||||
|
import { $Node } from "./node/$Node";
|
||||||
|
import { $SVGElement } from "./node/$SVGElement";
|
||||||
|
import { $Text } from "./node/$Text";
|
||||||
|
|
||||||
export namespace $Util {
|
export namespace $Util {
|
||||||
export function fluent<T, A, V>(instance: T, args: IArguments, value: () => V, action: (...args: any[]) => void) {
|
export function fluent<T, A, V>(instance: T, args: IArguments, value: () => V, action: (...args: any[]) => void) {
|
||||||
@ -7,13 +11,13 @@ export namespace $Util {
|
|||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function multableResolve<T>(multable: OrArray<T>) {
|
export function orArrayResolve<T>(multable: OrArray<T>) {
|
||||||
if (multable instanceof Array) return multable;
|
if (multable instanceof Array) return multable;
|
||||||
else return [multable];
|
else return [multable];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mixin(target: any, constructors: OrArray<any>) {
|
export function mixin(target: any, constructors: OrArray<any>) {
|
||||||
multableResolve(constructors).forEach(constructor => {
|
orArrayResolve(constructors).forEach(constructor => {
|
||||||
Object.getOwnPropertyNames(constructor.prototype).forEach(name => {
|
Object.getOwnPropertyNames(constructor.prototype).forEach(name => {
|
||||||
if (name === 'constructor') return;
|
if (name === 'constructor') return;
|
||||||
Object.defineProperty(
|
Object.defineProperty(
|
||||||
@ -33,4 +37,26 @@ export namespace $Util {
|
|||||||
export function state<T>(value: T) {
|
export function state<T>(value: T) {
|
||||||
return new $State<T>(value)
|
return new $State<T>(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function from(element: HTMLElement | Text | Node): $Node {
|
||||||
|
if (element.$) return element.$;
|
||||||
|
if (element.nodeName.toLowerCase() === 'body') return new $Container('body', {dom: element as HTMLBodyElement});
|
||||||
|
else if (element instanceof HTMLElement) {
|
||||||
|
const instance = $.TagNameElementMap[element.tagName.toLowerCase() as keyof typeof $.TagNameElementMap];
|
||||||
|
const $node = instance === $Container ? new instance(element.tagName, {dom: element}) : new instance({dom: element} as any);
|
||||||
|
if ($node instanceof $Container) for (const childnode of Array.from($node.dom.childNodes)) {
|
||||||
|
$node.children.add($(childnode));
|
||||||
|
}
|
||||||
|
return $node as $Node;
|
||||||
|
}
|
||||||
|
else if (element instanceof Text) {
|
||||||
|
const node = new $Text(element.textContent ?? '') as Mutable<$Node>;
|
||||||
|
node.dom = element;
|
||||||
|
return node as $Node;
|
||||||
|
}
|
||||||
|
else if (element instanceof SVGElement) {
|
||||||
|
if (element.tagName.toLowerCase() === 'svg') {return new $SVGElement('svg', {dom: element}) };
|
||||||
|
}
|
||||||
|
throw `$NODE.FROM: NOT SUPPORT TARGET ELEMENT TYPE (${element.nodeName})`
|
||||||
|
}
|
||||||
}
|
}
|
@ -8,7 +8,7 @@ export class $Anchor extends $Container<HTMLAnchorElement> {
|
|||||||
// Link Handler event
|
// Link Handler event
|
||||||
this.dom.addEventListener('click', e => {
|
this.dom.addEventListener('click', e => {
|
||||||
if ($.anchorPreventDefault) e.preventDefault();
|
if ($.anchorPreventDefault) e.preventDefault();
|
||||||
if ($.anchorHandler && !!this.href()) $.anchorHandler(this.href(), e)
|
if ($.anchorHandler && !!this.href()) $.anchorHandler(this, e)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/**Set URL of anchor element. */
|
/**Set URL of anchor element. */
|
||||||
@ -16,9 +16,9 @@ export class $Anchor extends $Container<HTMLAnchorElement> {
|
|||||||
href(url: string | undefined): this;
|
href(url: string | undefined): this;
|
||||||
href(url?: string | undefined) { return $.fluent(this, arguments, () => this.dom.href, () => {if (url) this.dom.href = url}) }
|
href(url?: string | undefined) { return $.fluent(this, arguments, () => this.dom.href, () => {if (url) this.dom.href = url}) }
|
||||||
/**Link open with this window, new tab or other */
|
/**Link open with this window, new tab or other */
|
||||||
target(): string;
|
target(): $AnchorTarget | undefined;
|
||||||
target(target: $AnchorTarget | undefined): this;
|
target(target: $AnchorTarget | undefined): this;
|
||||||
target(target?: $AnchorTarget | undefined) { return $.fluent(this, arguments, () => this.dom.target, () => {if (target) this.dom.target = target}) }
|
target(target?: $AnchorTarget | undefined) { return $.fluent(this, arguments, () => (this.dom.target ?? undefined) as $AnchorTarget | undefined, () => {if (target) this.dom.target = target}) }
|
||||||
}
|
}
|
||||||
|
|
||||||
export type $AnchorTarget = '_blank' | '_self' | '_parent' | '_top';
|
export type $AnchorTarget = '_blank' | '_self' | '_parent' | '_top';
|
22
lib/node/$AsyncNode.ts
Normal file
22
lib/node/$AsyncNode.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { $Node } from "./$Node";
|
||||||
|
|
||||||
|
export class $AsyncNode<N extends $Node = $Node> extends $Node {
|
||||||
|
dom: Node = document.createElement('async');
|
||||||
|
loaded: boolean = false;
|
||||||
|
constructor($node?: Promise<N>) {
|
||||||
|
super()
|
||||||
|
this.dom.$ = this;
|
||||||
|
if ($node) $node.then($node => this._loaded($node));
|
||||||
|
}
|
||||||
|
|
||||||
|
await<T extends $Node = $Node>($node: Promise<T>) {
|
||||||
|
$node.then($node => this._loaded($node));
|
||||||
|
return this as $AsyncNode<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
protected _loaded($node: $Node) {
|
||||||
|
this.loaded = true;
|
||||||
|
this.replace($node)
|
||||||
|
this.dom.dispatchEvent(new Event('load'))
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import { $Container, $ContainerOptions } from "./$Container";
|
import { $Container, $ContainerOptions } from "./$Container";
|
||||||
import { $State } from "./$State";
|
import { $State, $StateArgument } from "../$State";
|
||||||
export interface $ButtonOptions extends $ContainerOptions {}
|
export interface $ButtonOptions extends $ContainerOptions {}
|
||||||
export class $Button extends $Container<HTMLButtonElement> {
|
export class $Button extends $Container<HTMLButtonElement> {
|
||||||
constructor(options?: $ButtonOptions) {
|
constructor(options?: $ButtonOptions) {
|
||||||
@ -7,8 +7,8 @@ export class $Button extends $Container<HTMLButtonElement> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
disabled(): boolean;
|
disabled(): boolean;
|
||||||
disabled(disabled: boolean | $State<boolean>): this;
|
disabled(disabled: $StateArgument<boolean>): this;
|
||||||
disabled(disabled?: boolean | $State<boolean>) { return $.fluent(this, arguments, () => this.dom.disabled, () => $.set(this.dom, 'disabled', disabled))}
|
disabled(disabled?: $StateArgument<boolean>) { return $.fluent(this, arguments, () => this.dom.disabled, () => $.set(this.dom, 'disabled', disabled))}
|
||||||
|
|
||||||
type(): ButtonType;
|
type(): ButtonType;
|
||||||
type(type: ButtonType): this;
|
type(type: ButtonType): this;
|
@ -1,12 +1,12 @@
|
|||||||
import { $Element, $ElementOptions } from "./$Element";
|
import { $Element, $ElementOptions } from "./$Element";
|
||||||
import { $NodeManager } from "./$NodeManager";
|
import { $NodeManager } from "../$NodeManager";
|
||||||
import { $Node } from "./$Node";
|
import { $Node } from "./$Node";
|
||||||
import { $State } from "./$State";
|
import { $State } from "../$State";
|
||||||
import { $Text } from "./$Text";
|
import { $Text } from "./$Text";
|
||||||
|
import { $HTMLElement, $HTMLElementOptions } from "./$HTMLElement";
|
||||||
|
|
||||||
export interface $ContainerOptions extends $ElementOptions {}
|
export interface $ContainerOptions extends $HTMLElementOptions {}
|
||||||
|
export class $Container<H extends HTMLElement = HTMLElement> extends $HTMLElement<H> {
|
||||||
export class $Container<H extends HTMLElement = HTMLElement> extends $Element<H> {
|
|
||||||
readonly children: $NodeManager = new $NodeManager(this);
|
readonly children: $NodeManager = new $NodeManager(this);
|
||||||
constructor(tagname: string, options?: $ContainerOptions) {
|
constructor(tagname: string, options?: $ContainerOptions) {
|
||||||
super(tagname, options)
|
super(tagname, options)
|
@ -3,18 +3,26 @@ import { $Node } from "./$Node";
|
|||||||
export interface $ElementOptions {
|
export interface $ElementOptions {
|
||||||
id?: string;
|
id?: string;
|
||||||
class?: string[];
|
class?: string[];
|
||||||
|
dom?: HTMLElement | SVGElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class $Element<H extends HTMLElement = HTMLElement> extends $Node<H> {
|
export class $Element<H extends HTMLElement | SVGElement = HTMLElement> extends $Node<H> {
|
||||||
readonly dom: H;
|
readonly dom: H;
|
||||||
private static_classes = new Set<string>();
|
private static_classes = new Set<string>();
|
||||||
constructor(tagname: string, options?: $ElementOptions) {
|
constructor(tagname: string, options?: $ElementOptions) {
|
||||||
super();
|
super();
|
||||||
this.dom = document.createElement(tagname) as H;
|
this.dom = this.createDom(tagname, options) as H;
|
||||||
this.dom.$ = this;
|
this.dom.$ = this;
|
||||||
this.setOptions(options);
|
this.setOptions(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
setOptions(options: $ElementOptions | undefined) {
|
setOptions(options: $ElementOptions | undefined) {
|
||||||
this.id(options?.id)
|
this.id(options?.id)
|
||||||
if (options && options.class) this.class(...options.class)
|
if (options && options.class) this.class(...options.class)
|
||||||
@ -24,7 +32,7 @@ export class $Element<H extends HTMLElement = HTMLElement> extends $Node<H> {
|
|||||||
/**Replace id of element. @example Element.id('customId');*/
|
/**Replace id of element. @example Element.id('customId');*/
|
||||||
id(): string;
|
id(): string;
|
||||||
id(name: string | undefined): this;
|
id(name: string | undefined): this;
|
||||||
id(name?: string | undefined): this | string {return $.fluent(this, arguments, () => this.dom.id, () => $.set(this.dom, 'id', name))}
|
id(name?: string | undefined): this | string {return $.fluent(this, arguments, () => this.dom.id, () => $.set(this.dom, 'id', name as any))}
|
||||||
|
|
||||||
/**Replace list of class name to element. @example Element.class('name1', 'name2') */
|
/**Replace list of class name to element. @example Element.class('name1', 'name2') */
|
||||||
class(): DOMTokenList;
|
class(): DOMTokenList;
|
||||||
@ -46,74 +54,32 @@ export class $Element<H extends HTMLElement = HTMLElement> extends $Node<H> {
|
|||||||
css(style: Partial<CSSStyleDeclaration>): this;
|
css(style: Partial<CSSStyleDeclaration>): this;
|
||||||
css(style?: Partial<CSSStyleDeclaration>) { return $.fluent(this, arguments, () => this.dom.style, () => {Object.assign(this.dom.style, style)})}
|
css(style?: Partial<CSSStyleDeclaration>) { return $.fluent(this, arguments, () => this.dom.style, () => {Object.assign(this.dom.style, style)})}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get or set attribute from this element.
|
||||||
|
* @param qualifiedName Attribute name
|
||||||
|
* @param value Attribute value. Set `null` will remove attribute.
|
||||||
|
*/
|
||||||
attribute(qualifiedName: string | undefined): string | null;
|
attribute(qualifiedName: string | undefined): string | null;
|
||||||
attribute(qualifiedName: string | undefined, value?: string | number | boolean): this;
|
attribute(qualifiedName: string | undefined, value?: string | number | boolean | null): this;
|
||||||
attribute(qualifiedName: string | undefined, value?: string | number | boolean): this | string | null {
|
attribute(qualifiedName: string | undefined, value?: string | number | boolean | null): this | string | null {
|
||||||
if (!arguments.length) return null;
|
if (!arguments.length) return null;
|
||||||
if (arguments.length === 1) {
|
if (arguments.length === 1) {
|
||||||
if (qualifiedName === undefined) return null;
|
if (qualifiedName === undefined) return null;
|
||||||
return this.dom.getAttribute(qualifiedName);
|
return this.dom.getAttribute(qualifiedName);
|
||||||
}
|
}
|
||||||
if (arguments.length === 2) {
|
if (arguments.length === 2) {
|
||||||
if (qualifiedName && value) this.dom.setAttribute(qualifiedName, `${value}`);
|
if (!qualifiedName) return this;
|
||||||
|
if (value === null) this.dom.removeAttribute(qualifiedName);
|
||||||
|
else if (value !== undefined) this.dom.setAttribute(qualifiedName, `${value}`);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
autocapitalize(): Autocapitalize;
|
|
||||||
autocapitalize(autocapitalize?: Autocapitalize): this;
|
|
||||||
autocapitalize(autocapitalize?: Autocapitalize) { return $.fluent(this, arguments, () => this.dom.autocapitalize, () => $.set(this.dom, 'autocapitalize', autocapitalize))}
|
|
||||||
|
|
||||||
dir(): TextDirection;
|
|
||||||
dir(dir?: TextDirection): this;
|
|
||||||
dir(dir?: TextDirection) { return $.fluent(this, arguments, () => this.dom.dir, () => $.set(this.dom, 'dir', dir))}
|
|
||||||
|
|
||||||
innerText(): string;
|
|
||||||
innerText(text?: string): this;
|
|
||||||
innerText(text?: string) { return $.fluent(this, arguments, () => this.dom.innerText, () => $.set(this.dom, 'innerText', text))}
|
|
||||||
|
|
||||||
title(): string;
|
|
||||||
title(title?: string): this;
|
|
||||||
title(title?: string) { return $.fluent(this, arguments, () => this.dom.title, () => $.set(this.dom, 'title', title))}
|
|
||||||
|
|
||||||
translate(): boolean;
|
|
||||||
translate(translate?: boolean): this;
|
|
||||||
translate(translate?: boolean) { return $.fluent(this, arguments, () => this.dom.translate, () => $.set(this.dom, 'translate', translate))}
|
|
||||||
|
|
||||||
popover(): string | null;
|
|
||||||
popover(popover?: string | null): this;
|
|
||||||
popover(popover?: string | null) { return $.fluent(this, arguments, () => this.dom.popover, () => $.set(this.dom, 'popover', popover))}
|
|
||||||
|
|
||||||
spellcheck(): boolean;
|
|
||||||
spellcheck(spellcheck?: boolean): this;
|
|
||||||
spellcheck(spellcheck?: boolean) { return $.fluent(this, arguments, () => this.dom.spellcheck, () => $.set(this.dom, 'spellcheck', spellcheck))}
|
|
||||||
|
|
||||||
inert(): boolean;
|
|
||||||
inert(inert?: boolean): this;
|
|
||||||
inert(inert?: boolean) { return $.fluent(this, arguments, () => this.dom.inert, () => $.set(this.dom, 'inert', inert))}
|
|
||||||
|
|
||||||
lang(): string;
|
|
||||||
lang(lang?: string): this;
|
|
||||||
lang(lang?: string) { return $.fluent(this, arguments, () => this.dom.lang, () => $.set(this.dom, 'lang', lang))}
|
|
||||||
|
|
||||||
draggable(): boolean;
|
|
||||||
draggable(draggable?: boolean): this;
|
|
||||||
draggable(draggable?: boolean) { return $.fluent(this, arguments, () => this.dom.draggable, () => $.set(this.dom, 'draggable', draggable))}
|
|
||||||
|
|
||||||
hidden(): boolean;
|
|
||||||
hidden(hidden?: boolean): this;
|
|
||||||
hidden(hidden?: boolean) { return $.fluent(this, arguments, () => this.dom.hidden, () => $.set(this.dom, 'hidden', hidden))}
|
|
||||||
|
|
||||||
tabIndex(): number;
|
tabIndex(): number;
|
||||||
tabIndex(tabIndex: number): this;
|
tabIndex(tabIndex: number): this;
|
||||||
tabIndex(tabIndex?: number) { return $.fluent(this, arguments, () => this.dom.tabIndex, () => $.set(this.dom, 'tabIndex', tabIndex))}
|
tabIndex(tabIndex?: number) { return $.fluent(this, arguments, () => this.dom.tabIndex, () => $.set(this.dom, 'tabIndex', tabIndex as any))}
|
||||||
|
|
||||||
click() { this.dom.click(); return this; }
|
|
||||||
attachInternals() { return this.dom.attachInternals(); }
|
|
||||||
hidePopover() { this.dom.hidePopover(); return this; }
|
|
||||||
showPopover() { this.dom.showPopover(); return this; }
|
|
||||||
togglePopover() { this.dom.togglePopover(); return this; }
|
|
||||||
focus() { this.dom.focus(); return this; }
|
focus() { this.dom.focus(); return this; }
|
||||||
blur() { this.dom.blur(); return this; }
|
blur() { this.dom.blur(); return this; }
|
||||||
|
|
||||||
@ -125,11 +91,5 @@ export class $Element<H extends HTMLElement = HTMLElement> extends $Node<H> {
|
|||||||
|
|
||||||
getAnimations(options?: GetAnimationsOptions) { return this.dom.getAnimations(options) }
|
getAnimations(options?: GetAnimationsOptions) { return this.dom.getAnimations(options) }
|
||||||
|
|
||||||
get accessKeyLabel() { return this.dom.accessKeyLabel }
|
|
||||||
get offsetHeight() { return this.dom.offsetHeight }
|
|
||||||
get offsetLeft() { return this.dom.offsetLeft }
|
|
||||||
get offsetParent() { return $(this.dom.offsetParent) }
|
|
||||||
get offsetTop() { return this.dom.offsetTop }
|
|
||||||
get offsetWidth() { return this.dom.offsetWidth }
|
|
||||||
get dataset() { return this.dom.dataset }
|
get dataset() { return this.dom.dataset }
|
||||||
}
|
}
|
@ -1,6 +1,4 @@
|
|||||||
import { $Container, $ContainerOptions } from "./$Container";
|
import { $Container, $ContainerOptions } from "./$Container";
|
||||||
import { $State } from "./$State";
|
|
||||||
import { $Util } from "./$Util";
|
|
||||||
export interface $FormOptions extends $ContainerOptions {}
|
export interface $FormOptions extends $ContainerOptions {}
|
||||||
export class $Form extends $Container<HTMLFormElement> {
|
export class $Form extends $Container<HTMLFormElement> {
|
||||||
constructor(options?: $FormOptions) {
|
constructor(options?: $FormOptions) {
|
65
lib/node/$HTMLElement.ts
Normal file
65
lib/node/$HTMLElement.ts
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import { $Element, $ElementOptions } from "./$Element";
|
||||||
|
|
||||||
|
export interface $HTMLElementOptions extends $ElementOptions {}
|
||||||
|
export class $HTMLElement<H extends HTMLElement = HTMLElement> extends $Element<H> {
|
||||||
|
constructor(tagname: string, options?: $HTMLElementOptions) {
|
||||||
|
super(tagname, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
autocapitalize(): Autocapitalize;
|
||||||
|
autocapitalize(autocapitalize?: Autocapitalize): this;
|
||||||
|
autocapitalize(autocapitalize?: Autocapitalize) { return $.fluent(this, arguments, () => this.dom.autocapitalize, () => $.set(this.dom, 'autocapitalize', autocapitalize as any))}
|
||||||
|
|
||||||
|
innerText(): string;
|
||||||
|
innerText(text?: string): this;
|
||||||
|
innerText(text?: string) { return $.fluent(this, arguments, () => this.dom.innerText, () => $.set(this.dom, 'innerText', text as any))}
|
||||||
|
|
||||||
|
title(): string;
|
||||||
|
title(title?: string): this;
|
||||||
|
title(title?: string) { return $.fluent(this, arguments, () => this.dom.title, () => $.set(this.dom, 'title', title as any))}
|
||||||
|
|
||||||
|
dir(): TextDirection;
|
||||||
|
dir(dir?: TextDirection): this;
|
||||||
|
dir(dir?: TextDirection) { return $.fluent(this, arguments, () => this.dom.dir, () => $.set(this.dom, 'dir', dir as any))}
|
||||||
|
|
||||||
|
translate(): boolean;
|
||||||
|
translate(translate?: boolean): this;
|
||||||
|
translate(translate?: boolean) { return $.fluent(this, arguments, () => this.dom.translate, () => $.set(this.dom, 'translate', translate as any))}
|
||||||
|
|
||||||
|
popover(): string | null;
|
||||||
|
popover(popover?: string | null): this;
|
||||||
|
popover(popover?: string | null) { return $.fluent(this, arguments, () => this.dom.popover, () => $.set(this.dom, 'popover', popover as any))}
|
||||||
|
|
||||||
|
spellcheck(): boolean;
|
||||||
|
spellcheck(spellcheck?: boolean): this;
|
||||||
|
spellcheck(spellcheck?: boolean) { return $.fluent(this, arguments, () => this.dom.spellcheck, () => $.set(this.dom, 'spellcheck', spellcheck as any))}
|
||||||
|
|
||||||
|
inert(): boolean;
|
||||||
|
inert(inert?: boolean): this;
|
||||||
|
inert(inert?: boolean) { return $.fluent(this, arguments, () => this.dom.inert, () => $.set(this.dom, 'inert', inert as any))}
|
||||||
|
|
||||||
|
lang(): string;
|
||||||
|
lang(lang?: string): this;
|
||||||
|
lang(lang?: string) { return $.fluent(this, arguments, () => this.dom.lang, () => $.set(this.dom, 'lang', lang as any))}
|
||||||
|
|
||||||
|
draggable(): boolean;
|
||||||
|
draggable(draggable?: boolean): this;
|
||||||
|
draggable(draggable?: boolean) { return $.fluent(this, arguments, () => this.dom.draggable, () => $.set(this.dom, 'draggable', draggable as any))}
|
||||||
|
|
||||||
|
hidden(): boolean;
|
||||||
|
hidden(hidden?: boolean): this;
|
||||||
|
hidden(hidden?: boolean) { return $.fluent(this, arguments, () => this.dom.hidden, () => $.set(this.dom, 'hidden', hidden as any))}
|
||||||
|
|
||||||
|
click() { this.dom.click(); return this; }
|
||||||
|
attachInternals() { return this.dom.attachInternals(); }
|
||||||
|
hidePopover() { this.dom.hidePopover(); return this; }
|
||||||
|
showPopover() { this.dom.showPopover(); return this; }
|
||||||
|
togglePopover() { this.dom.togglePopover(); return this; }
|
||||||
|
|
||||||
|
get accessKeyLabel() { return this.dom.accessKeyLabel }
|
||||||
|
get offsetHeight() { return this.dom.offsetHeight }
|
||||||
|
get offsetLeft() { return this.dom.offsetLeft }
|
||||||
|
get offsetParent() { return $(this.dom.offsetParent) }
|
||||||
|
get offsetTop() { return this.dom.offsetTop }
|
||||||
|
get offsetWidth() { return this.dom.offsetWidth }
|
||||||
|
}
|
@ -1,11 +1,19 @@
|
|||||||
import { $Element, $ElementOptions } from "./$Element";
|
import { $HTMLElement, $HTMLElementOptions } from "./$HTMLElement";
|
||||||
import { $State } from "./$State";
|
import { $State } from "../$State";
|
||||||
export interface $ImageOptions extends $ElementOptions {}
|
export interface $ImageOptions extends $HTMLElementOptions {}
|
||||||
export class $Image extends $Element<HTMLImageElement> {
|
export class $Image extends $HTMLElement<HTMLImageElement> {
|
||||||
constructor(options?: $ImageOptions) {
|
constructor(options?: $ImageOptions) {
|
||||||
super('img', options);
|
super('img', options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async load(src: string): Promise<$Image> {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const $img = this.once('load', () => {
|
||||||
|
resolve($img)
|
||||||
|
}).src(src)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**HTMLImageElement base property */
|
/**HTMLImageElement base property */
|
||||||
alt(): string;
|
alt(): string;
|
||||||
alt(alt: string): this;
|
alt(alt: string): this;
|
@ -1,5 +1,5 @@
|
|||||||
import { $Element, $ElementOptions } from "./$Element";
|
import { $Element, $ElementOptions } from "./$Element";
|
||||||
import { $State } from "./$State";
|
import { $State, $StateArgument } from "../$State";
|
||||||
|
|
||||||
export interface $InputOptions extends $ElementOptions {}
|
export interface $InputOptions extends $ElementOptions {}
|
||||||
export class $Input extends $Element<HTMLInputElement> {
|
export class $Input extends $Element<HTMLInputElement> {
|
||||||
@ -172,12 +172,12 @@ export class $Input extends $Element<HTMLInputElement> {
|
|||||||
formTarget(target?: string) { return $.fluent(this, arguments, () => this.dom.formTarget, () => $.set(this.dom, 'formTarget', target))}
|
formTarget(target?: string) { return $.fluent(this, arguments, () => this.dom.formTarget, () => $.set(this.dom, 'formTarget', target))}
|
||||||
|
|
||||||
name(): string;
|
name(): string;
|
||||||
name(name?: string | $State<string>): this;
|
name(name?: $StateArgument<string> | undefined): this;
|
||||||
name(name?: string | $State<string>) { return $.fluent(this, arguments, () => this.dom.name, () => $.set(this.dom, 'name', name))}
|
name(name?: $StateArgument<string> | undefined) { return $.fluent(this, arguments, () => this.dom.name, () => $.set(this.dom, 'name', name))}
|
||||||
|
|
||||||
value(): string;
|
value(): string;
|
||||||
value(value?: string | $State<string>): this;
|
value(value: $StateArgument<string> | undefined): this;
|
||||||
value(value?: string | $State<string>) { return $.fluent(this, arguments, () => this.dom.value, () => $.set(this.dom, 'value', value))}
|
value(value?: $StateArgument<string> | undefined) { return $.fluent(this, arguments, () => this.dom.value, () => $.set(this.dom, 'value', value))}
|
||||||
|
|
||||||
get form() { return this.dom.form ? $(this.dom.form) : null }
|
get form() { return this.dom.form ? $(this.dom.form) : null }
|
||||||
get labels() { return Array.from(this.dom.labels ?? []).map(label => $(label)) }
|
get labels() { return Array.from(this.dom.labels ?? []).map(label => $(label)) }
|
@ -1,13 +1,12 @@
|
|||||||
import { $Element, $State, $Text } from "../index";
|
import { $, $Element, $State, $Text } from "../../index";
|
||||||
import { $Container } from "./$Container";
|
import { $Container } from "./$Container";
|
||||||
|
|
||||||
export abstract class $Node<N extends Node = Node> {
|
export abstract class $Node<N extends Node = Node> {
|
||||||
readonly parent?: $Container;
|
|
||||||
abstract readonly dom: N;
|
abstract readonly dom: N;
|
||||||
readonly __hidden: boolean = false;
|
readonly __hidden: boolean = false;
|
||||||
private domEvents: {[key: string]: Map<Function, Function>} = {};
|
private domEvents: {[key: string]: Map<Function, Function>} = {};
|
||||||
|
|
||||||
on<K extends keyof HTMLElementEventMap>(type: K, callback: (event: HTMLElementEventMap[K], $node: this) => void, options?: AddEventListenerOptions | boolean) {
|
on<K extends keyof HTMLElementEventMap>(type: K, callback: (event: HTMLElementEventMap[K], $node: this) => any, options?: AddEventListenerOptions | boolean) {
|
||||||
if (!this.domEvents[type]) this.domEvents[type] = new Map()
|
if (!this.domEvents[type]) this.domEvents[type] = new Map()
|
||||||
const middleCallback = (e: Event) => callback(e as HTMLElementEventMap[K], this);
|
const middleCallback = (e: Event) => callback(e as HTMLElementEventMap[K], this);
|
||||||
this.domEvents[type].set(callback, middleCallback)
|
this.domEvents[type].set(callback, middleCallback)
|
||||||
@ -15,13 +14,13 @@ export abstract class $Node<N extends Node = Node> {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
off<K extends keyof HTMLElementEventMap>(type: K, callback: (event: HTMLElementEventMap[K], $node: this) => void, options?: AddEventListenerOptions | boolean) {
|
off<K extends keyof HTMLElementEventMap>(type: K, callback: (event: HTMLElementEventMap[K], $node: this) => any, options?: AddEventListenerOptions | boolean) {
|
||||||
const middleCallback = this.domEvents[type]?.get(callback);
|
const middleCallback = this.domEvents[type]?.get(callback);
|
||||||
if (middleCallback) this.dom.removeEventListener(type, middleCallback as EventListener, options)
|
if (middleCallback) this.dom.removeEventListener(type, middleCallback as EventListener, options)
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
once<K extends keyof HTMLElementEventMap>(type: K, callback: (event: HTMLElementEventMap[K], $node: this) => void, options?: AddEventListenerOptions | boolean) {
|
once<K extends keyof HTMLElementEventMap>(type: K, callback: (event: HTMLElementEventMap[K], $node: this) => any, options?: AddEventListenerOptions | boolean) {
|
||||||
const onceFn = (event: Event) => {
|
const onceFn = (event: Event) => {
|
||||||
this.dom.removeEventListener(type, onceFn, options)
|
this.dom.removeEventListener(type, onceFn, options)
|
||||||
callback(event as HTMLElementEventMap[K], this);
|
callback(event as HTMLElementEventMap[K], this);
|
||||||
@ -52,7 +51,7 @@ export abstract class $Node<N extends Node = Node> {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
contains(target: $Node | EventTarget | Node | null) {
|
contains(target: $Node | EventTarget | Node | null): boolean {
|
||||||
if (!target) return false;
|
if (!target) return false;
|
||||||
if (target instanceof $Node) return this.dom.contains(target.dom);
|
if (target instanceof $Node) return this.dom.contains(target.dom);
|
||||||
else if (target instanceof EventTarget) return this.dom.contains($(target).dom)
|
else if (target instanceof EventTarget) return this.dom.contains($(target).dom)
|
||||||
@ -61,25 +60,12 @@ export abstract class $Node<N extends Node = Node> {
|
|||||||
|
|
||||||
self(callback: ($node: this) => void) { callback(this); return this; }
|
self(callback: ($node: this) => void) { callback(this); return this; }
|
||||||
inDOM() { return document.contains(this.dom); }
|
inDOM() { return document.contains(this.dom); }
|
||||||
isElement() {
|
isElement(): $Element | undefined {
|
||||||
if (this instanceof $Element) return this;
|
if (this instanceof $Element) return this;
|
||||||
else return undefined;
|
else return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
static from(element: HTMLElement | Text): $Node {
|
get parent() {
|
||||||
if (element.$) return element.$;
|
return this.dom.parentElement?.$ as $Container | undefined;
|
||||||
else if (element instanceof HTMLElement) {
|
|
||||||
const node = $(element.tagName) as Mutable<$Node>;
|
|
||||||
node.dom = element;
|
|
||||||
if (element.parentElement) node.parent = $(element.parentElement) as $Container;
|
|
||||||
return node as $Node;
|
|
||||||
}
|
|
||||||
else if (element instanceof Text) {
|
|
||||||
const node = new $Text(element.textContent ?? '') as Mutable<$Node>;
|
|
||||||
node.dom = element;
|
|
||||||
if (element.parentElement) node.parent = $(element.parentElement) as $Container;
|
|
||||||
return node as $Node;
|
|
||||||
}
|
|
||||||
throw '$NODE.FROM: NOT SUPPORT TARGET ELEMENT TYPE'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
17
lib/node/$OptGroup.ts
Normal file
17
lib/node/$OptGroup.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { $Container, $ContainerOptions } from "./$Container";
|
||||||
|
import { $State, $StateArgument } from "../$State";
|
||||||
|
|
||||||
|
export interface $OptGroupOptions extends $ContainerOptions {}
|
||||||
|
export class $OptGroup extends $Container<HTMLOptGroupElement> {
|
||||||
|
constructor(options?: $OptGroupOptions) {
|
||||||
|
super('optgroup', options);
|
||||||
|
}
|
||||||
|
|
||||||
|
disabled(): boolean;
|
||||||
|
disabled(disabled: $StateArgument<boolean> | undefined): this;
|
||||||
|
disabled(disabled?: $StateArgument<boolean> | undefined) { return $.fluent(this, arguments, () => this.dom.disabled, () => $.set(this.dom, 'disabled', disabled))}
|
||||||
|
|
||||||
|
label(): string;
|
||||||
|
label(label: $StateArgument<string> | undefined): this;
|
||||||
|
label(label?: $StateArgument<string> | undefined) { return $.fluent(this, arguments, () => this.dom.label, () => $.set(this.dom, 'label', label))}
|
||||||
|
}
|
37
lib/node/$Option.ts
Normal file
37
lib/node/$Option.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { $Container, $ContainerOptions } from "./$Container";
|
||||||
|
import { $State, $StateArgument } from "../$State";
|
||||||
|
|
||||||
|
export interface $OptionOptions extends $ContainerOptions {}
|
||||||
|
export class $Option extends $Container<HTMLOptionElement> {
|
||||||
|
constructor(options?: $OptionOptions) {
|
||||||
|
super('option', options);
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultSelected(): boolean;
|
||||||
|
defaultSelected(defaultSelected: $StateArgument<boolean> | undefined): this;
|
||||||
|
defaultSelected(defaultSelected?: $StateArgument<boolean> | undefined) { return $.fluent(this, arguments, () => this.dom.defaultSelected, () => $.set(this.dom, 'defaultSelected', defaultSelected))}
|
||||||
|
|
||||||
|
disabled(): boolean;
|
||||||
|
disabled(disabled: $StateArgument<boolean> | undefined): this;
|
||||||
|
disabled(disabled?: $StateArgument<boolean> | undefined) { return $.fluent(this, arguments, () => this.dom.disabled, () => $.set(this.dom, 'disabled', disabled))}
|
||||||
|
|
||||||
|
label(): string;
|
||||||
|
label(label: $StateArgument<string> | undefined): this;
|
||||||
|
label(label?: $StateArgument<string> | undefined) { return $.fluent(this, arguments, () => this.dom.label, () => $.set(this.dom, 'label', label))}
|
||||||
|
|
||||||
|
selected(): boolean;
|
||||||
|
selected(selected: $StateArgument<boolean> | undefined): this;
|
||||||
|
selected(selected?: $StateArgument<boolean> | undefined) { return $.fluent(this, arguments, () => this.dom.selected, () => $.set(this.dom, 'selected', selected))}
|
||||||
|
|
||||||
|
text(): string;
|
||||||
|
text(text: $StateArgument<string> | undefined): this;
|
||||||
|
text(text?: $StateArgument<string> | undefined) { return $.fluent(this, arguments, () => this.dom.text, () => $.set(this.dom, 'text', text))}
|
||||||
|
|
||||||
|
value(): string;
|
||||||
|
value(value: $StateArgument<string> | undefined): this;
|
||||||
|
value(value?: $StateArgument<string> | undefined) { return $.fluent(this, arguments, () => this.dom.value, () => $.set(this.dom, 'value', value))}
|
||||||
|
|
||||||
|
get form() { return this.dom.form ? $(this.dom.form) : null }
|
||||||
|
get index() { return this.dom.index }
|
||||||
|
|
||||||
|
}
|
8
lib/node/$SVGElement.ts
Normal file
8
lib/node/$SVGElement.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { $Element, $ElementOptions } from "./$Element"
|
||||||
|
|
||||||
|
export interface $SVGOptions extends $ElementOptions {}
|
||||||
|
export class $SVGElement<S extends SVGElement> extends $Element<S> {
|
||||||
|
constructor(tagname: string, options?: $SVGOptions) {
|
||||||
|
super(tagname, options);
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,11 @@
|
|||||||
import { $Container, $ContainerOptions } from "./$Container";
|
import { $Container, $ContainerOptions } from "./$Container";
|
||||||
import { $OptGroup } from "./$OptGroup";
|
import { $OptGroup } from "./$OptGroup";
|
||||||
import { $Option } from "./$Option";
|
import { $Option } from "./$Option";
|
||||||
import { $State } from "./$State";
|
import { $State, $StateArgument } from "../$State";
|
||||||
|
|
||||||
export interface $SelectOptions extends $ContainerOptions {}
|
export interface $SelectOptions extends $ContainerOptions {}
|
||||||
export class $Select extends $Container<HTMLSelectElement> {
|
export class $Select extends $Container<HTMLSelectElement> {
|
||||||
constructor() {
|
constructor(options?: $SelectOptions) {
|
||||||
super('select')
|
super('select')
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -18,12 +18,12 @@ export class $Select extends $Container<HTMLSelectElement> {
|
|||||||
namedItem(name: string) { return $(this.dom.namedItem(name)) }
|
namedItem(name: string) { return $(this.dom.namedItem(name)) }
|
||||||
|
|
||||||
disabled(): boolean;
|
disabled(): boolean;
|
||||||
disabled(disabled: boolean | $State<boolean>): this;
|
disabled(disabled: $StateArgument<boolean> | undefined): this;
|
||||||
disabled(disabled?: boolean | $State<boolean>) { return $.fluent(this, arguments, () => this.dom.disabled, () => $.set(this.dom, 'disabled', disabled))}
|
disabled(disabled?: $StateArgument<boolean> | undefined) { return $.fluent(this, arguments, () => this.dom.disabled, () => $.set(this.dom, 'disabled', disabled))}
|
||||||
|
|
||||||
multiple(): boolean;
|
multiple(): boolean;
|
||||||
multiple(multiple: boolean | $State<boolean>): this;
|
multiple(multiple: $StateArgument<boolean> | undefined): this;
|
||||||
multiple(multiple?: boolean | $State<boolean>) { return $.fluent(this, arguments, () => this.dom.multiple, () => $.set(this.dom, 'multiple', multiple))}
|
multiple(multiple?: $StateArgument<boolean> | undefined) { return $.fluent(this, arguments, () => this.dom.multiple, () => $.set(this.dom, 'multiple', multiple))}
|
||||||
|
|
||||||
required(): boolean;
|
required(): boolean;
|
||||||
required(required: boolean): this;
|
required(required: boolean): this;
|
||||||
@ -40,12 +40,12 @@ export class $Select extends $Container<HTMLSelectElement> {
|
|||||||
get selectedOptions() { return Array.from(this.dom.selectedOptions).map($option => $($option)) }
|
get selectedOptions() { return Array.from(this.dom.selectedOptions).map($option => $($option)) }
|
||||||
|
|
||||||
name(): string;
|
name(): string;
|
||||||
name(name?: string | $State<string>): this;
|
name(name?: $StateArgument<string> | undefined): this;
|
||||||
name(name?: string | $State<string>) { return $.fluent(this, arguments, () => this.dom.name, () => $.set(this.dom, 'name', name))}
|
name(name?: $StateArgument<string> | undefined) { return $.fluent(this, arguments, () => this.dom.name, () => $.set(this.dom, 'name', name))}
|
||||||
|
|
||||||
value(): string;
|
value(): string;
|
||||||
value(value?: string | $State<string>): this;
|
value(value?: $StateArgument<string> | undefined): this;
|
||||||
value(value?: string | $State<string>) { return $.fluent(this, arguments, () => this.dom.value, () => $.set(this.dom, 'value', value))}
|
value(value?: $StateArgument<string> | undefined) { return $.fluent(this, arguments, () => this.dom.value, () => $.set(this.dom, 'value', value))}
|
||||||
|
|
||||||
get form() { return this.dom.form ? $(this.dom.form) : null }
|
get form() { return this.dom.form ? $(this.dom.form) : null }
|
||||||
get labels() { return Array.from(this.dom.labels ?? []).map(label => $(label)) }
|
get labels() { return Array.from(this.dom.labels ?? []).map(label => $(label)) }
|
@ -1,5 +1,5 @@
|
|||||||
import { $Container, $ContainerOptions } from "./$Container";
|
import { $Container, $ContainerOptions } from "./$Container";
|
||||||
import { $State } from "./$State";
|
import { $StateArgument } from "../$State";
|
||||||
|
|
||||||
export interface $TextareaOptions extends $ContainerOptions {}
|
export interface $TextareaOptions extends $ContainerOptions {}
|
||||||
export class $Textarea extends $Container<HTMLTextAreaElement> {
|
export class $Textarea extends $Container<HTMLTextAreaElement> {
|
||||||
@ -12,16 +12,16 @@ export class $Textarea extends $Container<HTMLTextAreaElement> {
|
|||||||
cols(cols?: number) { return $.fluent(this, arguments, () => this.dom.cols, () => $.set(this.dom, 'cols', cols))}
|
cols(cols?: number) { return $.fluent(this, arguments, () => this.dom.cols, () => $.set(this.dom, 'cols', cols))}
|
||||||
|
|
||||||
name(): string;
|
name(): string;
|
||||||
name(name?: string | $State<string>): this;
|
name(name?: $StateArgument<string> | undefined): this;
|
||||||
name(name?: string | $State<string>) { return $.fluent(this, arguments, () => this.dom.name, () => $.set(this.dom, 'name', name))}
|
name(name?: $StateArgument<string> | undefined) { return $.fluent(this, arguments, () => this.dom.name, () => $.set(this.dom, 'name', name))}
|
||||||
|
|
||||||
wrap(): string;
|
wrap(): string;
|
||||||
wrap(wrap?: string | $State<string>): this;
|
wrap(wrap?: $StateArgument<string> | undefined): this;
|
||||||
wrap(wrap?: string | $State<string>) { return $.fluent(this, arguments, () => this.dom.wrap, () => $.set(this.dom, 'wrap', wrap))}
|
wrap(wrap?: $StateArgument<string> | undefined) { return $.fluent(this, arguments, () => this.dom.wrap, () => $.set(this.dom, 'wrap', wrap))}
|
||||||
|
|
||||||
value(): string;
|
value(): string;
|
||||||
value(value?: string | $State<string>): this;
|
value(value?: $StateArgument<string> | undefined): this;
|
||||||
value(value?: string | $State<string>) { return $.fluent(this, arguments, () => this.dom.value, () => $.set(this.dom, 'value', value))}
|
value(value?: $StateArgument<string> | undefined) { return $.fluent(this, arguments, () => this.dom.value, () => $.set(this.dom, 'value', value))}
|
||||||
|
|
||||||
maxLength(): number;
|
maxLength(): number;
|
||||||
maxLength(maxLength: number): this;
|
maxLength(maxLength: number): this;
|
@ -1,5 +1,5 @@
|
|||||||
import { $Container, $ContainerOptions } from "./$Container";
|
import { $Container, $ContainerOptions } from "./$Container";
|
||||||
import { $EventManager } from "./$EventManager";
|
import { $EventManager } from "../$EventManager";
|
||||||
import { $Node } from "./$Node";
|
import { $Node } from "./$Node";
|
||||||
|
|
||||||
export interface $ViewOptions extends $ContainerOptions {}
|
export interface $ViewOptions extends $ContainerOptions {}
|
||||||
@ -28,7 +28,7 @@ export class $View extends $Container {
|
|||||||
|
|
||||||
switchView(id: string) {
|
switchView(id: string) {
|
||||||
const target_content = this.view_cache.get(id);
|
const target_content = this.view_cache.get(id);
|
||||||
if (target_content === undefined) throw '$View.switch(): target content is undefined';
|
if (target_content === undefined) return this;
|
||||||
this.content(target_content);
|
this.content(target_content);
|
||||||
this.content_id = id;
|
this.content_id = id;
|
||||||
this.event.fire('switch', id);
|
this.event.fire('switch', id);
|
@ -1,5 +1,6 @@
|
|||||||
import { $EventManager, $EventMethod } from "../$EventManager";
|
import { $EventManager, $EventMethod } from "../$EventManager";
|
||||||
import { $Node } from "../$Node";
|
import { $Node } from "../node/$Node";
|
||||||
|
import { $Util } from "../$Util";
|
||||||
export class Route<Path extends string | PathResolverFn> {
|
export class Route<Path extends string | PathResolverFn> {
|
||||||
path: string | PathResolverFn;
|
path: string | PathResolverFn;
|
||||||
builder: (req: RouteRequest<Path>) => RouteContent;
|
builder: (req: RouteRequest<Path>) => RouteContent;
|
||||||
@ -32,7 +33,7 @@ export class RouteRecord {
|
|||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$.mixin(RouteRecord, $EventMethod)
|
$Util.mixin(RouteRecord, $EventMethod)
|
||||||
export interface RouteRecordEventMap {
|
export interface RouteRecordEventMap {
|
||||||
'open': [{path: string, record: RouteRecord}];
|
'open': [{path: string, record: RouteRecord}];
|
||||||
'load': [{path: string, record: RouteRecord}];
|
'load': [{path: string, record: RouteRecord}];
|
@ -1,15 +1,18 @@
|
|||||||
import { $EventManager, $EventMethod } from "../$EventManager";
|
import { $EventManager, $EventMethod } from "../$EventManager";
|
||||||
import { $Text } from "../$Text";
|
import { $Text } from "../node/$Text";
|
||||||
import { $View } from "../$View";
|
import { $Util } from "../$Util";
|
||||||
|
import { $View } from "../node/$View";
|
||||||
import { PathResolverFn, Route, RouteRecord } from "./Route";
|
import { PathResolverFn, Route, RouteRecord } from "./Route";
|
||||||
export interface Router extends $EventMethod<RouterEventMap> {};
|
export interface Router extends $EventMethod<RouterEventMap> {};
|
||||||
export class Router {
|
export class Router {
|
||||||
routeMap = new Map<string | PathResolverFn, Route<any>>();
|
routeMap = new Map<string | PathResolverFn, Route<any>>();
|
||||||
recordMap = new Map<string, RouteRecord>();
|
recordMap = new Map<string, RouteRecord>();
|
||||||
$view: $View;
|
$view: $View;
|
||||||
index: number = 0;
|
static index: number = 0;
|
||||||
events = new $EventManager<RouterEventMap>().register('pathchange', 'notfound', 'load');
|
static events = new $EventManager<RouterGlobalEventMap>().register('pathchange', 'notfound', 'load');
|
||||||
|
events = new $EventManager<RouterEventMap>().register('notfound', 'load');
|
||||||
basePath: string;
|
basePath: string;
|
||||||
|
static currentPath: URL = new URL(location.href);
|
||||||
constructor(basePath: string, view?: $View) {
|
constructor(basePath: string, view?: $View) {
|
||||||
this.basePath = basePath;
|
this.basePath = basePath;
|
||||||
this.$view = view ?? new $View();
|
this.$view = view ?? new $View();
|
||||||
@ -25,39 +28,54 @@ export class Router {
|
|||||||
/**Start listen to the path change */
|
/**Start listen to the path change */
|
||||||
listen() {
|
listen() {
|
||||||
if (!history.state || 'index' in history.state === false) {
|
if (!history.state || 'index' in history.state === false) {
|
||||||
const routeData: RouteData = {index: this.index, data: {}}
|
const routeData: RouteData = {index: Router.index, data: {}}
|
||||||
history.replaceState(routeData, '')
|
history.replaceState(routeData, '')
|
||||||
} else {
|
} else {
|
||||||
this.index = history.state.index
|
Router.index = history.state.index
|
||||||
}
|
}
|
||||||
addEventListener('popstate', this.popstate)
|
addEventListener('popstate', this.popstate)
|
||||||
$.routers.add(this);
|
$.routers.add(this);
|
||||||
this.resolvePath();
|
this.resolvePath();
|
||||||
this.events.fire('pathchange', {path: location.href, navigation: 'Forward'});
|
Router.events.fire('pathchange', {prevURL: undefined, nextURL: Router.currentPath, navigation: 'Forward'});
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**Open path */
|
/**Open URL */
|
||||||
open(path: string | undefined) {
|
static open(url: string | URL | undefined) {
|
||||||
if (path === undefined) return;
|
if (url === undefined) return this;
|
||||||
if (path === location.pathname) return this;
|
url = new URL(url);
|
||||||
|
if (url.origin !== location.origin) return this;
|
||||||
|
if (url.href === location.href) return this;
|
||||||
|
const prevPath = Router.currentPath;
|
||||||
this.index += 1;
|
this.index += 1;
|
||||||
const routeData: RouteData = { index: this.index, data: {} };
|
const routeData: RouteData = { index: this.index, data: {} };
|
||||||
history.pushState(routeData, '', path);
|
history.pushState(routeData, '', url);
|
||||||
|
Router.currentPath = new URL(location.href);
|
||||||
$.routers.forEach(router => router.resolvePath())
|
$.routers.forEach(router => router.resolvePath())
|
||||||
this.events.fire('pathchange', {path, navigation: 'Forward'});
|
Router.events.fire('pathchange', {prevURL: prevPath, nextURL: Router.currentPath, navigation: 'Forward'});
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**Back to previous page */
|
/**Back to previous page */
|
||||||
back() { history.back(); return this }
|
static back() {
|
||||||
|
const prevPath = Router.currentPath;
|
||||||
|
history.back();
|
||||||
|
Router.currentPath = new URL(location.href);
|
||||||
|
Router.events.fire('pathchange', {prevURL: prevPath, nextURL: Router.currentPath, navigation: 'Back'});
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
replace(path: string | undefined) {
|
static replace(url: string | URL | undefined) {
|
||||||
if (path === undefined) return;
|
if (url === undefined) return this;
|
||||||
if (path === location.pathname) return this;
|
if (typeof url === 'string' && !url.startsWith(location.origin)) url = location.origin + url;
|
||||||
history.replaceState({index: this.index}, '', path)
|
url = new URL(url);
|
||||||
$.routers.forEach(router => router.resolvePath(path));
|
if (url.origin !== location.origin) return this;
|
||||||
this.events.fire('pathchange', {path, navigation: 'Forward'});
|
if (url.href === location.href) return this;
|
||||||
|
const prevPath = Router.currentPath;
|
||||||
|
history.replaceState({index: Router.index}, '', url)
|
||||||
|
Router.currentPath = new URL(location.href);
|
||||||
|
$.routers.forEach(router => router.resolvePath(url.pathname));
|
||||||
|
Router.events.fire('pathchange', {prevURL: prevPath, nextURL: Router.currentPath, navigation: 'Forward'});
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,12 +87,14 @@ export class Router {
|
|||||||
|
|
||||||
private popstate = (() => {
|
private popstate = (() => {
|
||||||
// Forward
|
// Forward
|
||||||
if (history.state.index > this.index) { }
|
if (history.state.index > Router.index) { }
|
||||||
// Back
|
// Back
|
||||||
else if (history.state.index < this.index) { }
|
else if (history.state.index < Router.index) { }
|
||||||
this.index = history.state.index;
|
const prevPath = Router.currentPath;
|
||||||
|
Router.index = history.state.index;
|
||||||
this.resolvePath();
|
this.resolvePath();
|
||||||
this.events.fire('pathchange', {path: location.pathname, navigation: 'Forward'});
|
Router.currentPath = new URL(location.href);
|
||||||
|
Router.events.fire('pathchange', {prevURL: prevPath, nextURL: Router.currentPath, navigation: 'Forward'});
|
||||||
}).bind(this)
|
}).bind(this)
|
||||||
|
|
||||||
private resolvePath(path = location.pathname) {
|
private resolvePath(path = location.pathname) {
|
||||||
@ -151,14 +171,21 @@ export class Router {
|
|||||||
if (!preventDefaultState) this.$view.clear();
|
if (!preventDefaultState) this.$view.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static on<K extends keyof RouterGlobalEventMap>(type: K, callback: (...args: RouterGlobalEventMap[K]) => any) { this.events.on(type, callback); return this }
|
||||||
|
static off<K extends keyof RouterGlobalEventMap>(type: K, callback: (...args: RouterGlobalEventMap[K]) => any) { this.events.off(type, callback); return this }
|
||||||
|
static once<K extends keyof RouterGlobalEventMap>(type: K, callback: (...args: RouterGlobalEventMap[K]) => any) { this.events.once(type, callback); return this }
|
||||||
}
|
}
|
||||||
$.mixin(Router, $EventMethod);
|
$Util.mixin(Router, $EventMethod);
|
||||||
interface RouterEventMap {
|
interface RouterEventMap {
|
||||||
pathchange: [{path: string, navigation: 'Back' | 'Forward'}];
|
notfound: [{path: string, preventDefault: () => any}];
|
||||||
notfound: [{path: string, preventDefault: () => void}];
|
|
||||||
load: [{path: string}];
|
load: [{path: string}];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface RouterGlobalEventMap {
|
||||||
|
pathchange: [{prevURL?: URL, nextURL: URL, navigation: 'Back' | 'Forward'}];
|
||||||
|
}
|
||||||
|
|
||||||
type RouteData = {
|
type RouteData = {
|
||||||
index: number;
|
index: number;
|
||||||
data: {[key: string]: any};
|
data: {[key: string]: any};
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "fluentx",
|
"name": "fluentx",
|
||||||
"description": "Fast, fluent, simple web builder",
|
"description": "Fast, fluent, simple web builder",
|
||||||
"version": "0.0.6",
|
"version": "0.0.7",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"module": "index.ts",
|
"module": "index.ts",
|
||||||
"author": {
|
"author": {
|
||||||
|
Loading…
Reference in New Issue
Block a user