v0.0.4
new: $View change: Router.view => $View new: $Select, $Option, $OptGroup, $Textarea, $View add: $ query selector add: $Container.clear() .$() .$all() change: $Element.options => .setOptions remove: EventMethod function
This commit is contained in:
parent
0b3ca308d6
commit
b51edda800
31
$index.ts
31
$index.ts
@ -10,7 +10,14 @@ import { Router } from "./lib/Router/Router";
|
|||||||
import { $Image } from "./lib/$Image";
|
import { $Image } from "./lib/$Image";
|
||||||
import { $Canvas } from "./lib/$Canvas";
|
import { $Canvas } from "./lib/$Canvas";
|
||||||
import { $Dialog } from "./lib/$Dialog";
|
import { $Dialog } from "./lib/$Dialog";
|
||||||
|
import { $View } from "./lib/$View";
|
||||||
|
import { $Select } from "./lib/$Select";
|
||||||
|
import { $Option } from "./lib/$Option";
|
||||||
|
import { $OptGroup } from "./lib/$OptGroup";
|
||||||
|
import { $Textarea } from "./lib/$Textarea";
|
||||||
export type $ = typeof $;
|
export type $ = typeof $;
|
||||||
|
export function $<E extends $Element = $Element>(query: `::${string}`): E[];
|
||||||
|
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;
|
||||||
@ -23,7 +30,9 @@ 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 (typeof resolver === 'string') {
|
if (typeof resolver === 'string') {
|
||||||
if (resolver in $.TagNameElementMap) {
|
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 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 $Element: return new $Element(resolver);
|
||||||
@ -36,6 +45,11 @@ export function $(resolver: any) {
|
|||||||
case $Image: return new $Image();
|
case $Image: return new $Image();
|
||||||
case $Canvas: return new $Canvas();
|
case $Canvas: return new $Canvas();
|
||||||
case $Dialog: return new $Dialog();
|
case $Dialog: return new $Dialog();
|
||||||
|
case $View: return new $View();
|
||||||
|
case $Select: return new $Select();
|
||||||
|
case $Option: return new $Option();
|
||||||
|
case $OptGroup: return new $OptGroup();
|
||||||
|
case $Textarea: return new $Textarea();
|
||||||
}
|
}
|
||||||
} else return new $Container(resolver);
|
} else return new $Container(resolver);
|
||||||
}
|
}
|
||||||
@ -45,7 +59,6 @@ export function $(resolver: any) {
|
|||||||
}
|
}
|
||||||
throw '$: NOT SUPPORT TARGET ELEMENT TYPE'
|
throw '$: NOT SUPPORT TARGET ELEMENT TYPE'
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace $ {
|
export namespace $ {
|
||||||
export let anchorHandler: null | ((url: string, e: Event) => void) = null;
|
export let anchorHandler: null | ((url: string, e: Event) => void) = null;
|
||||||
export let anchorPreventDefault: boolean = false;
|
export let anchorPreventDefault: boolean = false;
|
||||||
@ -74,7 +87,12 @@ export namespace $ {
|
|||||||
'form': $Form,
|
'form': $Form,
|
||||||
'img': $Image,
|
'img': $Image,
|
||||||
'dialog': $Dialog,
|
'dialog': $Dialog,
|
||||||
'canvas': $Canvas
|
'canvas': $Canvas,
|
||||||
|
'view': $View,
|
||||||
|
'select': $Select,
|
||||||
|
'option': $Option,
|
||||||
|
'optgroup': $OptGroup,
|
||||||
|
'textarea': $Textarea
|
||||||
}
|
}
|
||||||
export type TagNameTypeMap = {
|
export type TagNameTypeMap = {
|
||||||
[key in keyof typeof $.TagNameElementMap]: InstanceType<typeof $.TagNameElementMap[key]>;
|
[key in keyof typeof $.TagNameElementMap]: InstanceType<typeof $.TagNameElementMap[key]>;
|
||||||
@ -92,7 +110,11 @@ export namespace $ {
|
|||||||
: H extends HTMLFormElement ? $Form
|
: H extends HTMLFormElement ? $Form
|
||||||
: H extends HTMLCanvasElement ? $Canvas
|
: H extends HTMLCanvasElement ? $Canvas
|
||||||
: H extends HTMLDialogElement ? $Dialog
|
: H extends HTMLDialogElement ? $Dialog
|
||||||
: $Element<H>;
|
: H extends HTMLSelectElement ? $Select
|
||||||
|
: H extends HTMLOptionElement ? $Option
|
||||||
|
: H extends HTMLOptGroupElement ? $OptGroup
|
||||||
|
: H extends HTMLTextAreaElement ? $Textarea
|
||||||
|
: $Container<H>;
|
||||||
|
|
||||||
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) {
|
||||||
if (!args.length) return value();
|
if (!args.length) return value();
|
||||||
@ -199,3 +221,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.$ = $;
|
||||||
|
|
||||||
|
5
index.ts
5
index.ts
@ -42,3 +42,8 @@ export * from "./lib/$Button";
|
|||||||
export * from "./lib/$Form";
|
export * from "./lib/$Form";
|
||||||
export * from "./lib/$EventManager";
|
export * from "./lib/$EventManager";
|
||||||
export * from "./lib/$State";
|
export * from "./lib/$State";
|
||||||
|
export * from "./lib/$View";
|
||||||
|
export * from "./lib/$Select";
|
||||||
|
export * from "./lib/$Option";
|
||||||
|
export * from "./lib/$OptGroup";
|
||||||
|
export * from "./lib/$Textarea";
|
@ -35,6 +35,18 @@ export class $Container<H extends HTMLElement = HTMLElement> extends $Element<H>
|
|||||||
}
|
}
|
||||||
this.children.render();
|
this.children.render();
|
||||||
})}
|
})}
|
||||||
|
|
||||||
|
/**Remove all children elemetn from this element */
|
||||||
|
clear() {
|
||||||
|
this.children.removeAll();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
//**Query selector one of child element */
|
||||||
|
$<E extends $Element>(query: string) { return $(this.dom.querySelector(query)) as E | null }
|
||||||
|
|
||||||
|
//**Query selector of child elements */
|
||||||
|
$all<E extends $Element>(query: string) { return Array.from(this.dom.querySelectorAll(query)).map($dom => $($dom) as E) }
|
||||||
}
|
}
|
||||||
|
|
||||||
export type $ContainerContentBuilder<P extends $Container> = OrMatrix<$ContainerContentType> | (($node: P) => OrMatrix<$ContainerContentType>)
|
export type $ContainerContentBuilder<P extends $Container> = OrMatrix<$ContainerContentType> | (($node: P) => OrMatrix<$ContainerContentType>)
|
||||||
|
@ -12,10 +12,10 @@ export class $Element<H extends HTMLElement = HTMLElement> extends $Node<H> {
|
|||||||
super();
|
super();
|
||||||
this.dom = document.createElement(tagname) as H;
|
this.dom = document.createElement(tagname) as H;
|
||||||
this.dom.$ = this;
|
this.dom.$ = this;
|
||||||
this.options(options);
|
this.setOptions(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
options(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)
|
||||||
return this;
|
return this;
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
export function EventMethod<T>(target: T) {return $.mixin(target, $EventMethod)}
|
|
||||||
export abstract class $EventMethod<EM> {
|
export abstract class $EventMethod<EM> {
|
||||||
abstract events: $EventManager<EM>;
|
abstract events: $EventManager<EM>;
|
||||||
//@ts-expect-error
|
//@ts-expect-error
|
||||||
|
@ -34,13 +34,13 @@ export class $Input extends $Element<HTMLInputElement> {
|
|||||||
checked(boolean: boolean): this;
|
checked(boolean: boolean): this;
|
||||||
checked(boolean?: boolean) { return $.fluent(this, arguments, () => this.dom.checked, () => $.set(this.dom, 'checked', boolean))}
|
checked(boolean?: boolean) { return $.fluent(this, arguments, () => this.dom.checked, () => $.set(this.dom, 'checked', boolean))}
|
||||||
|
|
||||||
max(): string;
|
max(): number;
|
||||||
max(max: string): this;
|
max(max: number): this;
|
||||||
max(max?: string) { return $.fluent(this, arguments, () => this.dom.max, () => $.set(this.dom, 'max', max))}
|
max(max?: number) { return $.fluent(this, arguments, () => this.dom.max === '' ? null : parseInt(this.dom.min), () => $.set(this.dom, 'max', max?.toString()))}
|
||||||
|
|
||||||
min(): string;
|
min(): number;
|
||||||
min(min: string): this;
|
min(min: number): this;
|
||||||
min(min?: string) { return $.fluent(this, arguments, () => this.dom.min, () => $.set(this.dom, 'min', min))}
|
min(min?: number) { return $.fluent(this, arguments, () => this.dom.min === '' ? null : parseInt(this.dom.min), () => $.set(this.dom, 'min', min?.toString()))}
|
||||||
|
|
||||||
maxLength(): number;
|
maxLength(): number;
|
||||||
maxLength(maxLength: number): this;
|
maxLength(maxLength: number): this;
|
||||||
@ -110,9 +110,9 @@ export class $Input extends $Element<HTMLInputElement> {
|
|||||||
src(src: string): this;
|
src(src: string): this;
|
||||||
src(src?: string) { return $.fluent(this, arguments, () => this.dom.src, () => $.set(this.dom, 'src', src))}
|
src(src?: string) { return $.fluent(this, arguments, () => this.dom.src, () => $.set(this.dom, 'src', src))}
|
||||||
|
|
||||||
step(): string;
|
step(): number;
|
||||||
step(step: string): this;
|
step(step: number): this;
|
||||||
step(step?: string) { return $.fluent(this, arguments, () => this.dom.step, () => $.set(this.dom, 'step', step))}
|
step(step?: number) { return $.fluent(this, arguments, () => Number(this.dom.step), () => $.set(this.dom, 'step', step?.toString()))}
|
||||||
|
|
||||||
type(): InputType;
|
type(): InputType;
|
||||||
type(type: InputType): this;
|
type(type: InputType): this;
|
||||||
|
@ -4,11 +4,9 @@ import { $Text } from "./$Text";
|
|||||||
|
|
||||||
export class $NodeManager {
|
export class $NodeManager {
|
||||||
#container: $Container;
|
#container: $Container;
|
||||||
#dom: HTMLElement;
|
|
||||||
elementList = new Set<$Node>
|
elementList = new Set<$Node>
|
||||||
constructor(container: $Container) {
|
constructor(container: $Container) {
|
||||||
this.#container = container;
|
this.#container = container;
|
||||||
this.#dom = this.#container.dom
|
|
||||||
}
|
}
|
||||||
|
|
||||||
add(element: $Node | string) {
|
add(element: $Node | string) {
|
||||||
@ -47,23 +45,25 @@ export class $NodeManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const [domList, nodeList] = [this.array.map(node => node.dom), Array.from(this.#dom.childNodes)];
|
const [domList, nodeList] = [this.array.map(node => node.dom), Array.from(this.dom.childNodes)];
|
||||||
const appendedNodeList: Node[] = []; // appended node list
|
const appendedNodeList: Node[] = []; // appended node list
|
||||||
// Rearrange
|
// Rearrange
|
||||||
while (nodeList.length || domList.length) { // while nodeList or domList has item
|
while (nodeList.length || domList.length) { // while nodeList or domList has item
|
||||||
const [node, dom] = [nodeList.at(0), domList.at(0)];
|
const [node, dom] = [nodeList.at(0), domList.at(0)];
|
||||||
if (!dom) { if (node && !appendedNodeList.includes(node)) node.remove(); nodeList.shift()}
|
if (!dom) { if (node && !appendedNodeList.includes(node)) node.remove(); nodeList.shift()}
|
||||||
else if (!node) { if (!dom.$.__hidden) this.#dom.append(dom); domList.shift();}
|
else if (!node) { if (!dom.$.__hidden) this.dom.append(dom); domList.shift();}
|
||||||
else if (dom !== node) {
|
else if (dom !== node) {
|
||||||
if (!dom.$.__hidden) { this.#dom.insertBefore(dom, node); appendedNodeList.push(dom) }
|
if (!dom.$.__hidden) { this.dom.insertBefore(dom, node); appendedNodeList.push(dom) }
|
||||||
domList.shift();
|
domList.shift();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (dom.$.__hidden) this.#dom.removeChild(dom);
|
if (dom.$.__hidden) this.dom.removeChild(dom);
|
||||||
domList.shift(); nodeList.shift();
|
domList.shift(); nodeList.shift();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get array() {return [...this.elementList.values()]};
|
get array() {return [...this.elementList.values()]};
|
||||||
|
|
||||||
|
get dom() {return this.#container.dom}
|
||||||
}
|
}
|
17
lib/$OptGroup.ts
Normal file
17
lib/$OptGroup.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
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))}
|
||||||
|
}
|
37
lib/$Option.ts
Normal file
37
lib/$Option.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
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 }
|
||||||
|
|
||||||
|
}
|
47
lib/$Select.ts
Normal file
47
lib/$Select.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { $Container, $ContainerOptions } from "./$Container";
|
||||||
|
import { $FormElementMethod, FormElementMethod } from "./$Form";
|
||||||
|
import { $OptGroup } from "./$OptGroup";
|
||||||
|
import { $Option } from "./$Option";
|
||||||
|
import { $State } from "./$State";
|
||||||
|
|
||||||
|
export interface $SelectOptions extends $ContainerOptions {}
|
||||||
|
//@ts-expect-error
|
||||||
|
export interface $Select extends $FormElementMethod {}
|
||||||
|
@FormElementMethod
|
||||||
|
export class $Select extends $Container<HTMLSelectElement> {
|
||||||
|
constructor() {
|
||||||
|
super('select')
|
||||||
|
}
|
||||||
|
|
||||||
|
add(option: $SelectContentType | OrMatrix<$SelectContentType>) {
|
||||||
|
this.insert(option);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
item(index: number) { return $(this.dom.item(index)) }
|
||||||
|
namedItem(name: string) { return $(this.dom.namedItem(name)) }
|
||||||
|
|
||||||
|
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))}
|
||||||
|
|
||||||
|
multiple(): boolean;
|
||||||
|
multiple(multiple: boolean | $State<boolean>): this;
|
||||||
|
multiple(multiple?: boolean | $State<boolean>) { return $.fluent(this, arguments, () => this.dom.multiple, () => $.set(this.dom, 'multiple', multiple))}
|
||||||
|
|
||||||
|
required(): boolean;
|
||||||
|
required(required: boolean): this;
|
||||||
|
required(required?: boolean) { return $.fluent(this, arguments, () => this.dom.required, () => $.set(this.dom, 'required', required))}
|
||||||
|
|
||||||
|
autocomplete(): AutoFill;
|
||||||
|
autocomplete(autocomplete: AutoFill): this;
|
||||||
|
autocomplete(autocomplete?: AutoFill) { return $.fluent(this, arguments, () => this.dom.autocomplete, () => $.set(this.dom, 'autocomplete', autocomplete))}
|
||||||
|
|
||||||
|
get length() { return this.dom.length }
|
||||||
|
get size() { return this.dom.size }
|
||||||
|
get options() { return Array.from(this.dom.options).map($option => $($option)) }
|
||||||
|
get selectedIndex() { return this.dom.selectedIndex }
|
||||||
|
get selectedOptions() { return Array.from(this.dom.selectedOptions).map($option => $($option)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
export type $SelectContentType = $Option | $OptGroup | undefined;
|
100
lib/$Textarea.ts
Normal file
100
lib/$Textarea.ts
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import { $Container, $ContainerOptions } from "./$Container";
|
||||||
|
import { $State } from "./$State";
|
||||||
|
|
||||||
|
export interface $TextareaOptions extends $ContainerOptions {}
|
||||||
|
export class $Textarea extends $Container<HTMLTextAreaElement> {
|
||||||
|
constructor(options?: $TextareaOptions) {
|
||||||
|
super('textarea', options);
|
||||||
|
}
|
||||||
|
|
||||||
|
cols(): number;
|
||||||
|
cols(cols: number): this;
|
||||||
|
cols(cols?: number) { return $.fluent(this, arguments, () => this.dom.cols, () => $.set(this.dom, 'cols', cols))}
|
||||||
|
|
||||||
|
name(): string;
|
||||||
|
name(name?: string | $State<string>): this;
|
||||||
|
name(name?: string | $State<string>) { return $.fluent(this, arguments, () => this.dom.name, () => $.set(this.dom, 'name', name))}
|
||||||
|
|
||||||
|
wrap(): string;
|
||||||
|
wrap(wrap?: string | $State<string>): this;
|
||||||
|
wrap(wrap?: string | $State<string>) { return $.fluent(this, arguments, () => this.dom.wrap, () => $.set(this.dom, 'wrap', wrap))}
|
||||||
|
|
||||||
|
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))}
|
||||||
|
|
||||||
|
maxLength(): number;
|
||||||
|
maxLength(maxLength: number): this;
|
||||||
|
maxLength(maxLength?: number) { return $.fluent(this, arguments, () => this.dom.maxLength, () => $.set(this.dom, 'maxLength', maxLength))}
|
||||||
|
|
||||||
|
minLength(): number;
|
||||||
|
minLength(minLength: number): this;
|
||||||
|
minLength(minLength?: number) { return $.fluent(this, arguments, () => this.dom.minLength, () => $.set(this.dom, 'minLength', minLength))}
|
||||||
|
|
||||||
|
autocomplete(): AutoFill;
|
||||||
|
autocomplete(autocomplete: AutoFill): this;
|
||||||
|
autocomplete(autocomplete?: AutoFill) { return $.fluent(this, arguments, () => this.dom.autocomplete, () => $.set(this.dom, 'autocomplete', autocomplete))}
|
||||||
|
|
||||||
|
defaultValue(): string;
|
||||||
|
defaultValue(defaultValue: string): this;
|
||||||
|
defaultValue(defaultValue?: string) { return $.fluent(this, arguments, () => this.dom.defaultValue, () => $.set(this.dom, 'defaultValue', defaultValue))}
|
||||||
|
|
||||||
|
dirName(): string;
|
||||||
|
dirName(dirName: string): this;
|
||||||
|
dirName(dirName?: string) { return $.fluent(this, arguments, () => this.dom.dirName, () => $.set(this.dom, 'dirName', dirName))}
|
||||||
|
|
||||||
|
disabled(): boolean;
|
||||||
|
disabled(disabled: boolean): this;
|
||||||
|
disabled(disabled?: boolean) { return $.fluent(this, arguments, () => this.dom.disabled, () => $.set(this.dom, 'disabled', disabled))}
|
||||||
|
|
||||||
|
placeholder(): string;
|
||||||
|
placeholder(placeholder?: string): this;
|
||||||
|
placeholder(placeholder?: string) { return $.fluent(this, arguments, () => this.dom.placeholder, () => $.set(this.dom, 'placeholder', placeholder))}
|
||||||
|
|
||||||
|
readOnly(): boolean;
|
||||||
|
readOnly(readOnly: boolean): this;
|
||||||
|
readOnly(readOnly?: boolean) { return $.fluent(this, arguments, () => this.dom.readOnly, () => $.set(this.dom, 'readOnly', readOnly))}
|
||||||
|
|
||||||
|
required(): boolean;
|
||||||
|
required(required: boolean): this;
|
||||||
|
required(required?: boolean) { return $.fluent(this, arguments, () => this.dom.required, () => $.set(this.dom, 'required', required))}
|
||||||
|
|
||||||
|
selectionDirection(): SelectionDirection;
|
||||||
|
selectionDirection(selectionDirection: SelectionDirection): this;
|
||||||
|
selectionDirection(selectionDirection?: SelectionDirection) { return $.fluent(this, arguments, () => this.dom.selectionDirection, () => $.set(this.dom, 'selectionDirection', selectionDirection))}
|
||||||
|
|
||||||
|
selectionEnd(): number;
|
||||||
|
selectionEnd(selectionEnd: number): this;
|
||||||
|
selectionEnd(selectionEnd?: number) { return $.fluent(this, arguments, () => this.dom.selectionEnd, () => $.set(this.dom, 'selectionEnd', selectionEnd))}
|
||||||
|
|
||||||
|
selectionStart(): number;
|
||||||
|
selectionStart(selectionStart: number): this;
|
||||||
|
selectionStart(selectionStart?: number) { return $.fluent(this, arguments, () => this.dom.selectionStart, () => $.set(this.dom, 'selectionStart', selectionStart))}
|
||||||
|
|
||||||
|
type(): InputType;
|
||||||
|
type(type: InputType): this;
|
||||||
|
type(type?: InputType) { return $.fluent(this, arguments, () => this.dom.type, () => $.set(this.dom, 'type', type))}
|
||||||
|
|
||||||
|
inputMode(): InputMode;
|
||||||
|
inputMode(mode: InputMode): this;
|
||||||
|
inputMode(mode?: InputMode) { return $.fluent(this, arguments, () => this.dom.inputMode as InputMode, () => $.set(this.dom, 'inputMode', mode))}
|
||||||
|
|
||||||
|
select() { this.dom.select(); return this }
|
||||||
|
setCustomValidity(error: string) { this.dom.setCustomValidity(error); return this }
|
||||||
|
setRangeText(replacement: string): this;
|
||||||
|
setRangeText(replacement: string, start: number, end: number, selectionMode?: SelectionMode): this;
|
||||||
|
setRangeText(replacement: string, start?: number, end?: number, selectionMode?: SelectionMode) {
|
||||||
|
if (typeof start === 'number' && typeof end === 'number') this.dom.setRangeText(replacement, start, end, selectionMode)
|
||||||
|
this.dom.setRangeText(replacement);
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
setSelectionRange(start: number | null, end: number | null, direction?: SelectionDirection) { this.dom.setSelectionRange(start, end, direction); return this }
|
||||||
|
|
||||||
|
checkValidity() { return this.dom.checkValidity() }
|
||||||
|
reportValidity() { return this.dom.reportValidity() }
|
||||||
|
|
||||||
|
get validationMessage() { return this.dom.validationMessage }
|
||||||
|
get validity() { return this.dom.validity }
|
||||||
|
get form() { return this.dom.form ? $(this.dom.form) : null }
|
||||||
|
get labels() { return Array.from(this.dom.labels ?? []).map(label => $(label)) }
|
||||||
|
}
|
41
lib/$View.ts
Normal file
41
lib/$View.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
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<string, $Node>();
|
||||||
|
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) throw '$View.switch(): target content is undefined';
|
||||||
|
this.content(target_content);
|
||||||
|
this.content_id = id;
|
||||||
|
this.event.fire('switch', id);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface $ViewEventMap {
|
||||||
|
'switch': [id: string]
|
||||||
|
}
|
@ -1,11 +1,11 @@
|
|||||||
import { $EventManager, $EventMethod, EventMethod } from "../$EventManager";
|
import { $EventManager, $EventMethod } from "../$EventManager";
|
||||||
import { $Node } from "../$Node";
|
import { $Node } from "../$Node";
|
||||||
export class Route<Path extends string | PathResolverFn> {
|
export class Route<Path extends string | PathResolverFn> {
|
||||||
path: string | PathResolverFn;
|
path: string | PathResolverFn;
|
||||||
builder: (req: RouteRequest<Path>) => $Node | string;
|
builder: (req: RouteRequest<Path>) => RouteContent;
|
||||||
constructor(path: Path, builder: (req: RouteRequest<Path>) => $Node | string) {
|
constructor(path: Path, builder: ((req: RouteRequest<Path>) => RouteContent) | RouteContent) {
|
||||||
this.path = path;
|
this.path = path;
|
||||||
this.builder = builder;
|
this.builder = builder instanceof Function ? builder : (req: RouteRequest<Path>) => builder;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,7 +24,6 @@ type PathParamResolver<P extends PathResolverFn | string> = P extends PathResolv
|
|||||||
|
|
||||||
|
|
||||||
export interface RouteRecord extends $EventMethod<RouteRecordEventMap> {};
|
export interface RouteRecord extends $EventMethod<RouteRecordEventMap> {};
|
||||||
@EventMethod
|
|
||||||
export class RouteRecord {
|
export class RouteRecord {
|
||||||
id: string;
|
id: string;
|
||||||
readonly content?: $Node;
|
readonly content?: $Node;
|
||||||
@ -33,7 +32,7 @@ export class RouteRecord {
|
|||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$.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}];
|
||||||
@ -44,3 +43,5 @@ export interface RouteRequest<Path extends PathResolverFn | string> {
|
|||||||
record: RouteRecord,
|
record: RouteRecord,
|
||||||
loaded: () => void;
|
loaded: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type RouteContent = $Node | string | void;
|
@ -1,19 +1,18 @@
|
|||||||
import { $Container } from "../$Container";
|
import { $EventManager, $EventMethod } from "../$EventManager";
|
||||||
import { $EventManager, $EventMethod, EventMethod } from "../$EventManager";
|
|
||||||
import { $Text } from "../$Text";
|
import { $Text } from "../$Text";
|
||||||
|
import { $View } from "../$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> {};
|
||||||
@EventMethod
|
|
||||||
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: $Container;
|
$view: $View;
|
||||||
index: number = 0;
|
index: number = 0;
|
||||||
events = new $EventManager<RouterEventMap>().register('pathchange', 'notfound', 'load');
|
events = new $EventManager<RouterEventMap>().register('pathchange', 'notfound', 'load');
|
||||||
basePath: string;
|
basePath: string;
|
||||||
constructor(basePath: string, view: $Container) {
|
constructor(basePath: string, view?: $View) {
|
||||||
this.basePath = basePath;
|
this.basePath = basePath;
|
||||||
this.view = view
|
this.$view = view ?? new $View();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**Add route to Router. @example Router.addRoute(new Route('/', 'Hello World')) */
|
/**Add route to Router. @example Router.addRoute(new Route('/', 'Hello World')) */
|
||||||
@ -41,7 +40,7 @@ export class Router {
|
|||||||
/**Open path */
|
/**Open path */
|
||||||
open(path: string | undefined) {
|
open(path: string | undefined) {
|
||||||
if (path === undefined) return;
|
if (path === undefined) return;
|
||||||
if (path === location.href) return this;
|
if (path === location.pathname) return this;
|
||||||
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, '', path);
|
||||||
@ -53,7 +52,9 @@ export class Router {
|
|||||||
/**Back to previous page */
|
/**Back to previous page */
|
||||||
back() { history.back(); return this }
|
back() { history.back(); return this }
|
||||||
|
|
||||||
replace(path: string) {
|
replace(path: string | undefined) {
|
||||||
|
if (path === undefined) return;
|
||||||
|
if (path === location.pathname) return this;
|
||||||
history.replaceState({index: this.index}, '', path)
|
history.replaceState({index: this.index}, '', path)
|
||||||
$.routers.forEach(router => router.resolvePath(path));
|
$.routers.forEach(router => router.resolvePath(path));
|
||||||
this.events.fire('pathchange', {path, navigation: 'Forward'});
|
this.events.fire('pathchange', {path, navigation: 'Forward'});
|
||||||
@ -84,7 +85,7 @@ export class Router {
|
|||||||
const record = this.recordMap.get(pathId);
|
const record = this.recordMap.get(pathId);
|
||||||
if (record) {
|
if (record) {
|
||||||
found = true;
|
found = true;
|
||||||
if (record.content && !this.view.contains(record.content)) this.view.content(record.content);
|
if (record.content && !this.$view.contains(record.content)) this.$view.switchView(pathId);
|
||||||
record.events.fire('open', {path, record});
|
record.events.fire('open', {path, record});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -101,9 +102,10 @@ export class Router {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (typeof content === 'string') content = new $Text(content);
|
if (typeof content === 'string') content = new $Text(content);
|
||||||
|
if (content === undefined) return;
|
||||||
(record as Mutable<RouteRecord>).content = content;
|
(record as Mutable<RouteRecord>).content = content;
|
||||||
this.recordMap.set(pathId, record);
|
this.recordMap.set(pathId, record);
|
||||||
this.view.content(content);
|
this.$view.setView(pathId, content).switchView(pathId);
|
||||||
record.events.fire('open', {path, record});
|
record.events.fire('open', {path, record});
|
||||||
found = true;
|
found = true;
|
||||||
}
|
}
|
||||||
@ -146,10 +148,11 @@ export class Router {
|
|||||||
let preventDefaultState = false;
|
let preventDefaultState = false;
|
||||||
const preventDefault = () => preventDefaultState = true;
|
const preventDefault = () => preventDefaultState = true;
|
||||||
this.events.fire('notfound', {path, preventDefault});
|
this.events.fire('notfound', {path, preventDefault});
|
||||||
if (!preventDefaultState) this.view.children.removeAll();
|
if (!preventDefaultState) this.$view.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$.mixin(Router, $EventMethod);
|
||||||
interface RouterEventMap {
|
interface RouterEventMap {
|
||||||
pathchange: [{path: string, navigation: 'Back' | 'Forward'}];
|
pathchange: [{path: string, navigation: 'Back' | 'Forward'}];
|
||||||
notfound: [{path: string, preventDefault: () => void}];
|
notfound: [{path: string, preventDefault: () => void}];
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "fluentx",
|
"name": "fluentx",
|
||||||
"description": "Fast, fluent, simple web builder",
|
"description": "Fast, fluent, simple web builder",
|
||||||
"version": "0.0.3",
|
"version": "0.0.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"module": "index.ts",
|
"module": "index.ts",
|
||||||
"author": {
|
"author": {
|
||||||
|
Loading…
Reference in New Issue
Block a user