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> {
|
||||
readonly dom: H;
|
||||
private static_classes = new Set<string>();
|
||||
constructor(tagname: string, options?: $ElementOptions) {
|
||||
super();
|
||||
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') */
|
||||
class(): DOMTokenList;
|
||||
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. */
|
||||
addClass(...name: (string | undefined)[]): this {return $.fluent(this, arguments, () => this, () => {this.dom.classList.add(...name.detype())})}
|
||||
/**Remove class name from dom */
|
||||
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. */
|
||||
css(): CSSStyleDeclaration
|
||||
css(style: Partial<CSSStyleDeclaration>): this;
|
||||
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): this;
|
||||
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";
|
||||
|
||||
export abstract class $Node<N extends Node = Node> {
|
||||
readonly parent?: $Container;
|
||||
abstract readonly dom: N;
|
||||
readonly $hidden: boolean = false;
|
||||
readonly __hidden: boolean = false;
|
||||
private domEvents: {[key: string]: Map<Function, Function>} = {};
|
||||
|
||||
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(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 instanceof $State) { (this as Mutable<$Node>).$hidden = hide.value; hide.use(this, 'hide')}
|
||||
else (this as Mutable<$Node>).$hidden = hide;
|
||||
if (hide instanceof $State) { (this as Mutable<$Node>).__hidden = hide.value; hide.use(this, 'hide')}
|
||||
else (this as Mutable<$Node>).__hidden = hide;
|
||||
this.parent?.children.render();
|
||||
return this;
|
||||
})}
|
||||
@ -60,8 +60,11 @@ export abstract class $Node<N extends Node = Node> {
|
||||
}
|
||||
|
||||
self(callback: ($node: this) => void) { callback(this); return this; }
|
||||
|
||||
inDOM() { return document.contains(this.dom); }
|
||||
isElement() {
|
||||
if (this instanceof $Element) return this;
|
||||
else return undefined;
|
||||
}
|
||||
|
||||
static from(element: HTMLElement | Text): $Node {
|
||||
if (element.$) return element.$;
|
||||
|
@ -52,13 +52,13 @@ export class $NodeManager {
|
||||
while (nodeList.length || domList.length) { // while nodeList or domList has item
|
||||
const [node, dom] = [nodeList.at(0), domList.at(0)];
|
||||
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) {
|
||||
if (!dom.$.$hidden) { this.#dom.insertBefore(dom, node); appendedNodeList.push(dom) }
|
||||
if (!dom.$.__hidden) { this.#dom.insertBefore(dom, node); appendedNodeList.push(dom) }
|
||||
domList.shift();
|
||||
}
|
||||
else {
|
||||
if (dom.$.$hidden) this.#dom.removeChild(dom);
|
||||
if (dom.$.__hidden) this.#dom.removeChild(dom);
|
||||
domList.shift(); nodeList.shift();
|
||||
}
|
||||
}
|
||||
|
@ -28,17 +28,19 @@ export interface RouteRecord extends $EventMethod<RouteRecordEventMap> {};
|
||||
export class RouteRecord {
|
||||
id: string;
|
||||
readonly content?: $Node;
|
||||
events = new $EventManager<RouteRecordEventMap>().register('open')
|
||||
events = new $EventManager<RouteRecordEventMap>().register('open', 'load')
|
||||
constructor(id: string) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
params: PathParamResolver<Path>,
|
||||
record: RouteRecord,
|
||||
loaded: () => void;
|
||||
}
|
@ -9,7 +9,7 @@ export class Router {
|
||||
recordMap = new Map<string, RouteRecord>();
|
||||
view: $Container;
|
||||
index: number = 0;
|
||||
events = new $EventManager<RouterEventMap>().register('pathchange', 'notfound');
|
||||
events = new $EventManager<RouterEventMap>().register('pathchange', 'notfound', 'load');
|
||||
basePath: string;
|
||||
constructor(basePath: string, view: $Container) {
|
||||
this.basePath = basePath;
|
||||
@ -26,7 +26,7 @@ export class Router {
|
||||
/**Start listen to the path change */
|
||||
listen() {
|
||||
if (!history.state || 'index' in history.state === false) {
|
||||
const routeData: RouteData = {index: this.index}
|
||||
const routeData: RouteData = {index: this.index, data: {}}
|
||||
history.replaceState(routeData, '')
|
||||
} else {
|
||||
this.index = history.state.index
|
||||
@ -39,10 +39,11 @@ export class Router {
|
||||
}
|
||||
|
||||
/**Open path */
|
||||
open(path: string) {
|
||||
open(path: string | undefined) {
|
||||
if (path === undefined) return;
|
||||
if (path === location.href) return this;
|
||||
this.index += 1;
|
||||
const routeData: RouteData = { index: this.index };
|
||||
const routeData: RouteData = { index: this.index, data: {} };
|
||||
history.pushState(routeData, '', path);
|
||||
$.routers.forEach(router => router.resolvePath())
|
||||
this.events.fire('pathchange', path, 'Forward');
|
||||
@ -59,6 +60,12 @@ export class Router {
|
||||
return this;
|
||||
}
|
||||
|
||||
setStateData(key: string, value: any) {
|
||||
if (history.state.data === undefined) history.state.data = {};
|
||||
history.state.data[key] = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
private popstate = (() => {
|
||||
// Forward
|
||||
if (history.state.index > this.index) { }
|
||||
@ -77,8 +84,7 @@ export class Router {
|
||||
const record = this.recordMap.get(pathId);
|
||||
if (record) {
|
||||
found = true;
|
||||
if (record.content && this.view.contains(record.content)) return true;
|
||||
this.view.content(record.content);
|
||||
if (record.content && !this.view.contains(record.content)) this.view.content(record.content);
|
||||
record.events.fire('open', path, record);
|
||||
return true;
|
||||
}
|
||||
@ -86,7 +92,14 @@ export class Router {
|
||||
}
|
||||
const create = (pathId: string, route: Route<any>, data: any) => {
|
||||
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);
|
||||
(record as Mutable<RouteRecord>).content = content;
|
||||
this.recordMap.set(pathId, record);
|
||||
@ -134,10 +147,11 @@ export class Router {
|
||||
}
|
||||
interface RouterEventMap {
|
||||
pathchange: [path: string, navigation: 'Back' | 'Forward'];
|
||||
notfound: [path: string]
|
||||
notfound: [path: string];
|
||||
load: [path: string];
|
||||
}
|
||||
|
||||
type RouteData = {
|
||||
index: number;
|
||||
data?: any;
|
||||
data: {[key: string]: any};
|
||||
}
|
Loading…
Reference in New Issue
Block a user