add: $Node.isElement()
add: Router/Route event - load enhance: RouteData.data add: Router.setStateData() fix: route record open event not fire rename: $Node.$hidden -> $Node.__hidden add: $Element.static_classes, .stateClass(), .addStaticClass, .removeStaticClass, .attribute()
This commit is contained in:
parent
658df2d8e6
commit
1489b1dba5
@ -7,6 +7,7 @@ export interface $ElementOptions {
|
|||||||
|
|
||||||
export class $Element<H extends HTMLElement = HTMLElement> extends $Node<H> {
|
export class $Element<H extends HTMLElement = HTMLElement> extends $Node<H> {
|
||||||
readonly dom: H;
|
readonly dom: H;
|
||||||
|
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 = document.createElement(tagname) as H;
|
||||||
@ -28,17 +29,38 @@ export class $Element<H extends HTMLElement = HTMLElement> extends $Node<H> {
|
|||||||
/**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;
|
||||||
class(...name: (string | undefined)[]): this;
|
class(...name: (string | undefined)[]): this;
|
||||||
class(...name: (string | undefined)[]): this | DOMTokenList {return $.fluent(this, arguments, () => this.dom.classList, () => {this.dom.className = ''; this.dom.classList.add(...name.detype())})}
|
class(...name: (string | undefined)[]): this | DOMTokenList {return $.fluent(this, arguments, () => this.dom.classList, () => {this.dom.classList.forEach(n => this.static_classes.has(n) ?? this.dom.classList.remove(n)); this.dom.classList.add(...name.detype())})}
|
||||||
/**Add class name to dom. */
|
/**Add class name to dom. */
|
||||||
addClass(...name: (string | undefined)[]): this {return $.fluent(this, arguments, () => this, () => {this.dom.classList.add(...name.detype())})}
|
addClass(...name: (string | undefined)[]): this {return $.fluent(this, arguments, () => this, () => {this.dom.classList.add(...name.detype())})}
|
||||||
/**Remove class name from dom */
|
/**Remove class name from dom */
|
||||||
removeClass(...name: (string | undefined)[]): this {return $.fluent(this, arguments, () => this, () => {this.dom.classList.remove(...name.detype())})}
|
removeClass(...name: (string | undefined)[]): this {return $.fluent(this, arguments, () => this, () => {this.dom.classList.remove(...name.detype())})}
|
||||||
|
|
||||||
|
staticClass(): Set<string>;
|
||||||
|
staticClass(...name: (string | undefined)[]): this;
|
||||||
|
staticClass(...name: (string | undefined)[]) {return $.fluent(this, arguments, () => this.static_classes, () => {this.removeClass(...this.static_classes); this.static_classes.clear(); this.addStaticClass(...name);})}
|
||||||
|
addStaticClass(...name: (string | undefined)[]) {return $.fluent(this, arguments, () => this, () => {name.detype().forEach(n => this.static_classes.add(n)); this.addClass(...name)})}
|
||||||
|
removeStaticClass(...name: (string | undefined)[]) {return $.fluent(this, arguments, () => this, () => {name.detype().forEach(n => this.static_classes.delete(n)); this.removeClass(...name)})}
|
||||||
|
|
||||||
/**Modify css of element. */
|
/**Modify css of element. */
|
||||||
css(): CSSStyleDeclaration
|
css(): CSSStyleDeclaration
|
||||||
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)})}
|
||||||
|
|
||||||
|
attribute(qualifiedName: string | undefined): string | null;
|
||||||
|
attribute(qualifiedName: string | undefined, value?: string | number | boolean): this;
|
||||||
|
attribute(qualifiedName: string | undefined, value?: string | number | boolean): this | string | null {
|
||||||
|
if (!arguments.length) return null;
|
||||||
|
if (arguments.length === 1) {
|
||||||
|
if (qualifiedName === undefined) return null;
|
||||||
|
return this.dom.getAttribute(qualifiedName);
|
||||||
|
}
|
||||||
|
if (arguments.length === 2) {
|
||||||
|
if (qualifiedName && value) this.dom.setAttribute(qualifiedName, `${value}`);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
autocapitalize(): Autocapitalize;
|
autocapitalize(): Autocapitalize;
|
||||||
autocapitalize(autocapitalize?: Autocapitalize): this;
|
autocapitalize(autocapitalize?: Autocapitalize): this;
|
||||||
autocapitalize(autocapitalize?: Autocapitalize) { return $.fluent(this, arguments, () => this.dom.autocapitalize, () => $.set(this.dom, 'autocapitalize', autocapitalize))}
|
autocapitalize(autocapitalize?: Autocapitalize) { return $.fluent(this, arguments, () => this.dom.autocapitalize, () => $.set(this.dom, 'autocapitalize', autocapitalize))}
|
||||||
|
15
lib/$Node.ts
15
lib/$Node.ts
@ -1,10 +1,10 @@
|
|||||||
import { $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;
|
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) => void, options?: AddEventListenerOptions | boolean) {
|
||||||
@ -32,10 +32,10 @@ export abstract class $Node<N extends Node = Node> {
|
|||||||
|
|
||||||
hide(): boolean;
|
hide(): boolean;
|
||||||
hide(hide?: boolean | $State<boolean>): this;
|
hide(hide?: boolean | $State<boolean>): this;
|
||||||
hide(hide?: boolean | $State<boolean>) { return $.fluent(this, arguments, () => this.$hidden, () => {
|
hide(hide?: boolean | $State<boolean>) { return $.fluent(this, arguments, () => this.__hidden, () => {
|
||||||
if (hide === undefined) return;
|
if (hide === undefined) return;
|
||||||
if (hide instanceof $State) { (this as Mutable<$Node>).$hidden = hide.value; hide.use(this, 'hide')}
|
if (hide instanceof $State) { (this as Mutable<$Node>).__hidden = hide.value; hide.use(this, 'hide')}
|
||||||
else (this as Mutable<$Node>).$hidden = hide;
|
else (this as Mutable<$Node>).__hidden = hide;
|
||||||
this.parent?.children.render();
|
this.parent?.children.render();
|
||||||
return this;
|
return this;
|
||||||
})}
|
})}
|
||||||
@ -60,8 +60,11 @@ 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() {
|
||||||
|
if (this instanceof $Element) return this;
|
||||||
|
else return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
static from(element: HTMLElement | Text): $Node {
|
static from(element: HTMLElement | Text): $Node {
|
||||||
if (element.$) return element.$;
|
if (element.$) return element.$;
|
||||||
|
@ -52,13 +52,13 @@ export class $NodeManager {
|
|||||||
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,17 +28,19 @@ export interface RouteRecord extends $EventMethod<RouteRecordEventMap> {};
|
|||||||
export class RouteRecord {
|
export class RouteRecord {
|
||||||
id: string;
|
id: string;
|
||||||
readonly content?: $Node;
|
readonly content?: $Node;
|
||||||
events = new $EventManager<RouteRecordEventMap>().register('open')
|
events = new $EventManager<RouteRecordEventMap>().register('open', 'load')
|
||||||
constructor(id: string) {
|
constructor(id: string) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RouteRecordEventMap {
|
export interface RouteRecordEventMap {
|
||||||
'open': [path: string, record: RouteRecord]
|
'open': [path: string, record: RouteRecord];
|
||||||
|
'load': [path: string, record: RouteRecord];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RouteRequest<Path extends PathResolverFn | string> {
|
export interface RouteRequest<Path extends PathResolverFn | string> {
|
||||||
params: PathParamResolver<Path>,
|
params: PathParamResolver<Path>,
|
||||||
record: RouteRecord,
|
record: RouteRecord,
|
||||||
|
loaded: () => void;
|
||||||
}
|
}
|
@ -9,7 +9,7 @@ export class Router {
|
|||||||
recordMap = new Map<string, RouteRecord>();
|
recordMap = new Map<string, RouteRecord>();
|
||||||
view: $Container;
|
view: $Container;
|
||||||
index: number = 0;
|
index: number = 0;
|
||||||
events = new $EventManager<RouterEventMap>().register('pathchange', 'notfound');
|
events = new $EventManager<RouterEventMap>().register('pathchange', 'notfound', 'load');
|
||||||
basePath: string;
|
basePath: string;
|
||||||
constructor(basePath: string, view: $Container) {
|
constructor(basePath: string, view: $Container) {
|
||||||
this.basePath = basePath;
|
this.basePath = basePath;
|
||||||
@ -26,7 +26,7 @@ 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}
|
const routeData: RouteData = {index: this.index, data: {}}
|
||||||
history.replaceState(routeData, '')
|
history.replaceState(routeData, '')
|
||||||
} else {
|
} else {
|
||||||
this.index = history.state.index
|
this.index = history.state.index
|
||||||
@ -39,10 +39,11 @@ export class Router {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**Open path */
|
/**Open path */
|
||||||
open(path: string) {
|
open(path: string | undefined) {
|
||||||
|
if (path === undefined) return;
|
||||||
if (path === location.href) return this;
|
if (path === location.href) return this;
|
||||||
this.index += 1;
|
this.index += 1;
|
||||||
const routeData: RouteData = { index: this.index };
|
const routeData: RouteData = { index: this.index, data: {} };
|
||||||
history.pushState(routeData, '', path);
|
history.pushState(routeData, '', path);
|
||||||
$.routers.forEach(router => router.resolvePath())
|
$.routers.forEach(router => router.resolvePath())
|
||||||
this.events.fire('pathchange', path, 'Forward');
|
this.events.fire('pathchange', path, 'Forward');
|
||||||
@ -59,6 +60,12 @@ export class Router {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setStateData(key: string, value: any) {
|
||||||
|
if (history.state.data === undefined) history.state.data = {};
|
||||||
|
history.state.data[key] = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
private popstate = (() => {
|
private popstate = (() => {
|
||||||
// Forward
|
// Forward
|
||||||
if (history.state.index > this.index) { }
|
if (history.state.index > this.index) { }
|
||||||
@ -77,8 +84,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)) return true;
|
if (record.content && !this.view.contains(record.content)) this.view.content(record.content);
|
||||||
this.view.content(record.content);
|
|
||||||
record.events.fire('open', path, record);
|
record.events.fire('open', path, record);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -86,7 +92,14 @@ export class Router {
|
|||||||
}
|
}
|
||||||
const create = (pathId: string, route: Route<any>, data: any) => {
|
const create = (pathId: string, route: Route<any>, data: any) => {
|
||||||
const record = new RouteRecord(pathId);
|
const record = new RouteRecord(pathId);
|
||||||
let content = route.builder({params: data, record: record});
|
let content = route.builder({
|
||||||
|
params: data,
|
||||||
|
record: record,
|
||||||
|
loaded: () => {
|
||||||
|
record.events.fire('load', pathId, record);
|
||||||
|
this.events.fire('load', pathId);
|
||||||
|
}
|
||||||
|
});
|
||||||
if (typeof content === 'string') content = new $Text(content);
|
if (typeof content === 'string') content = new $Text(content);
|
||||||
(record as Mutable<RouteRecord>).content = content;
|
(record as Mutable<RouteRecord>).content = content;
|
||||||
this.recordMap.set(pathId, record);
|
this.recordMap.set(pathId, record);
|
||||||
@ -134,10 +147,11 @@ export class Router {
|
|||||||
}
|
}
|
||||||
interface RouterEventMap {
|
interface RouterEventMap {
|
||||||
pathchange: [path: string, navigation: 'Back' | 'Forward'];
|
pathchange: [path: string, navigation: 'Back' | 'Forward'];
|
||||||
notfound: [path: string]
|
notfound: [path: string];
|
||||||
|
load: [path: string];
|
||||||
}
|
}
|
||||||
|
|
||||||
type RouteData = {
|
type RouteData = {
|
||||||
index: number;
|
index: number;
|
||||||
data?: any;
|
data: {[key: string]: any};
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user