amateras 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,104 @@
1
+ import { _Array_from, _instanceof, _Object_fromEntries } from "#lib/native";
2
+ import { Page } from "./Page";
3
+ import { BaseRouteNode, Route } from "./Route";
4
+
5
+ const _location = location;
6
+ const {origin} = _location;
7
+ const _history = history;
8
+ const toURL = (path: string | URL) =>
9
+ _instanceof(path, URL) ? path : new URL(path.startsWith(origin) ? path : origin + path);
10
+ const [PUSH, REPLACE] = [1, 2] as const;
11
+ const historyHandler = (path: string | URL | Nullish, mode: 1 | 2) => {
12
+ if (!path) return;
13
+ const url = toURL(path);
14
+ if (url.origin !== origin) return this;
15
+ history[mode === PUSH ? 'pushState' : 'replaceState']({}, '' , url)
16
+ Router.routers.forEach(router => router.routes.size && router.resolve(path));
17
+ }
18
+ export class Router extends BaseRouteNode<''> {
19
+ static pageRouters = new Map<Page, Router>();
20
+ static routers = new Set<Router>();
21
+ pageMap = new Map<string, Page>();
22
+ constructor(page?: Page) {
23
+ super('', () => [], 'router')
24
+ Router.routers.add(this);
25
+ if (page) Router.pageRouters.set(page, this);
26
+ }
27
+
28
+ static open(path: string | URL | Nullish) {
29
+ historyHandler(path, PUSH);
30
+ return this;
31
+ }
32
+
33
+ static back() {
34
+ _history.back();
35
+ return this;
36
+ }
37
+
38
+ static forward() {
39
+ _history.forward();
40
+ return this;
41
+ }
42
+
43
+ static replace(path: string | URL | Nullish) {
44
+ historyHandler(path, REPLACE);
45
+ return this;
46
+ }
47
+
48
+ async resolve(path: string | URL) {
49
+ const {pathname, searchParams, hash} = toURL(path);
50
+ const routeData = { params: {} as {[key: string]: string}, query: _Object_fromEntries(searchParams) }
51
+ const split = (p: string) => p.replaceAll(/\/+/g, '/').split('/').map(path => `/${path}`);
52
+
53
+ function determineRoute(parentRoute: BaseRouteNode<any>, path: string, hash: string | undefined): [route: Route | null, pathId: string][] {
54
+ const targetPathSplit = split(path);
55
+ hash && targetPathSplit.push(hash);
56
+ if (!parentRoute.routes.size) return [];
57
+ routeLoop: for (const route of _Array_from(parentRoute.routes.values()).sort((a, b) => b.path.length - a.path.length)) {
58
+ const routePathSplit = split(route.path);
59
+ let pathId = '';
60
+ splitLoop: for (let i = 0; i < routePathSplit.length; i++) {
61
+ const pass = () => pathId += targetSnippet;
62
+ const [routeSnippet, targetSnippet] = [routePathSplit[i], targetPathSplit[i]];
63
+ if (!routeSnippet || !targetSnippet) continue routeLoop;
64
+ // process params in path
65
+ if (routeSnippet.includes(':')) {
66
+ if (targetSnippet === '/') continue routeLoop;
67
+ const [prefix, paramName] = routeSnippet.split(':') as [string, string];
68
+ if (!targetSnippet.startsWith(prefix)) continue routeLoop;
69
+ routeData.params[paramName] = targetSnippet.replace(`${prefix}`, '');
70
+ pass();
71
+ continue splitLoop;
72
+ }
73
+ if (routeSnippet === '/' && route.routes.size) continue splitLoop;
74
+ if (routeSnippet !== targetSnippet) continue routeLoop;
75
+ pass()
76
+ }
77
+ return [[route, pathId], ...determineRoute(route, path, hash)];
78
+ }
79
+ return [[null, parentRoute.path + '$$NOT_FOUND$$']];
80
+ }
81
+ // analytics
82
+ const targetRoutes = determineRoute(this, pathname + '/', hash);
83
+ // build pages
84
+ let prevPage: null | Page = null, prevRoute: BaseRouteNode<any> = this;
85
+ for (const [route, pathId] of targetRoutes) {
86
+ const page = await (this.pageMap.get(pathId) ?? (route ? route.build(routeData) : prevRoute.routes.get('404')?.build(routeData) ?? new Route('404', () => null).build()));
87
+ const _document = document;
88
+ _document && (_document.title = page.pageTitle() ?? _document.title);
89
+ this.pageMap.set(pathId, page);
90
+ if (prevPage) Router.pageRouters.get(prevPage)?.content(page);
91
+ else this.content(page);
92
+ prevPage = page;
93
+ if (route) prevRoute = route;
94
+ }
95
+ return this;
96
+ }
97
+
98
+ listen() {
99
+ const resolve = () => this.resolve(_location.href)
100
+ addEventListener('popstate', resolve);
101
+ resolve();
102
+ return this;
103
+ }
104
+ }
@@ -0,0 +1,8 @@
1
+ import { $Anchor } from "#html/$Anchor";
2
+
3
+ export class RouterAnchor extends $Anchor {
4
+ constructor() {
5
+ super();
6
+ this.on('click', e => { e.preventDefault(); $.open(this.href()) })
7
+ }
8
+ }
package/ext/ssr/env.ts ADDED
@@ -0,0 +1,61 @@
1
+ import { NODE } from "esm-env";
2
+ import { _Array_from, _Object_assign, _Object_entries, isNull } from "../../src/lib/native";
3
+ import type { $Node } from "../../src/node/$Node";
4
+
5
+ if (NODE) {
6
+ //@ts-expect-error
7
+ global.window = undefined;
8
+ //@ts-expect-error
9
+ global.document = undefined;
10
+ //@ts-expect-error
11
+ global.Node = class Node {
12
+ nodeName: string;
13
+ attributes = {};
14
+ childNodes = new Set<Node>();
15
+ parent?: Node;
16
+ $!: $Node;
17
+ constructor(nodeName: string) {
18
+ this.nodeName = nodeName.toUpperCase();
19
+ }
20
+ get textContent() { return _Array_from(this.childNodes.values()).map(node => node.textContent).join() }
21
+ set textContent(content: string | null) {
22
+ this.childNodes.clear();
23
+ content && this.childNodes.add(new Text(content) as unknown as Node);
24
+ }
25
+ appendChild(node: Node) {
26
+ if (this.childNodes.has(node)) this.childNodes.delete(node);
27
+ this.childNodes.add(node);
28
+ node.parent = this;
29
+ }
30
+ addEventListener(type: string, cb: () => void) { }
31
+ setAttribute(key: string, value: string) { _Object_assign(this.attributes, {[key]: value}) }
32
+ remove() {
33
+ this.parent?.removeChild(this);
34
+ }
35
+ removeChild(node: Node) {
36
+ this.childNodes.delete(node);
37
+ }
38
+
39
+ get outerHTML() {
40
+ const attr = _Object_entries(this.attributes).map(([key, value]) => `${key}="${value}"`).join(' ');
41
+ const tagName = this.nodeName.toLowerCase();
42
+ const IS_VOID_ELEMENT = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr'].includes(tagName);
43
+ return `<${tagName}${attr ? ` ${attr}` : ''}>${_Array_from(this.childNodes).map(node => `${node.$}`).join('')}${IS_VOID_ELEMENT && !this.childNodes.size ? '' : `</${tagName}>`}`;
44
+ }
45
+ }
46
+ //@ts-expect-error
47
+ global.Element = class Element {}
48
+ //@ts-expect-error
49
+ global.HTMLElement = class HTMLElement {}
50
+ //@ts-expect-error
51
+ global.Text = class Text {
52
+ textContent: string;
53
+ $: this;
54
+ constructor(textContent: string) {
55
+ this.textContent = textContent;
56
+ this.$ = this;
57
+ }
58
+
59
+ toString() { return this.textContent }
60
+ }
61
+ }
@@ -0,0 +1,47 @@
1
+ import './env';
2
+ import 'amateras';
3
+ import { _Array_from, _instanceof, _Object_assign, _Object_defineProperty } from "amateras/lib/native";
4
+ import { $Element, $Node, $Text } from "amateras/node";
5
+ import { BROWSER, NODE } from 'esm-env';
6
+
7
+ declare module 'amateras/core' {
8
+ export namespace $ {
9
+ export function mount(id: string, $node: $Element): void;
10
+ export function serverSide(cb: () => any): void;
11
+ }
12
+ }
13
+
14
+ export function onserver<T>(cb: () => T): T | '' {
15
+ if (NODE) return cb();
16
+ return '';
17
+ }
18
+
19
+ export function onclient<T>(cb: () => T): T | undefined {
20
+ if (BROWSER) return cb();
21
+ return;
22
+ }
23
+
24
+ _Object_assign($, {
25
+ mount(id: string, $node: $Element) {
26
+ if (!BROWSER) return;
27
+ const node = document.querySelector(`#${id}`);
28
+ if (!node) throw 'Target node of mounting not found';
29
+ getData(node, $node);
30
+ node.replaceWith($node.node);
31
+
32
+ function getData(node: Node, $node: $Node) {
33
+ if (node.nodeName === 'SIGNAL' && _instanceof(node, Element) && _instanceof($node, $Text)) {
34
+ const type = $(node).attr()['type'];
35
+ return $node.signals.forEach(signal => signal.value(type === 'number' ? Number(node.textContent) : type === 'boolean' ? node.textContent == 'true' ? true : false : node.textContent));
36
+ }
37
+ if (_instanceof(node, Text)) return $node.textContent(node.textContent);
38
+ if (_instanceof(node, Element) && _instanceof($node, $Element)) $node.attr($(node).attr());
39
+ const arr = _Array_from($node.childNodes);
40
+ node.childNodes.forEach((_node, i) => {
41
+ const targetChildNode = arr.at(i);
42
+ if (!targetChildNode) throw 'Target DOM tree not matched';
43
+ getData(_node, targetChildNode.$)
44
+ })
45
+ }
46
+ }
47
+ })
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "@amateras/ssr",
3
+ "peerDependencies": {
4
+ "amateras": "../../",
5
+ "esm-env": "^1.2.2"
6
+ },
7
+ "imports": {
8
+ "#structure/*": "./src/structure/*.ts"
9
+ }
10
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amateras",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Amateras is a DOM Utility library.",
5
5
  "module": "index.ts",
6
6
  "type": "module",
@@ -20,8 +20,7 @@
20
20
  "#lib/*": "./src/lib/*.ts",
21
21
  "#structure/*": "./src/structure/*.ts",
22
22
  "#html": "./ext/html/html.ts",
23
- "#css": "./ext/css/css.ts",
24
- "#css/*": "./ext/css/*.ts"
23
+ "#html/*": "./ext/html/node/*.ts"
25
24
  },
26
25
  "exports": {
27
26
  ".": "./src/index.ts",
@@ -31,7 +30,10 @@
31
30
  "./lib/*": "./src/lib/*.ts",
32
31
  "./structure/*": "./src/structure/*.ts",
33
32
  "./html": "./ext/html/html.ts",
34
- "./css": "./ext/css/src/index.ts"
33
+ "./html/*": "./ext/html/node/*.ts",
34
+ "./css": "./ext/css/src/index.ts",
35
+ "./router": "./ext/router/index.ts",
36
+ "./ssr": "./ext/ssr/index.ts"
35
37
  },
36
38
  "workspaces": [
37
39
  "./ext/*"
package/src/core.ts CHANGED
@@ -1,22 +1,26 @@
1
1
  import './global';
2
2
  import { Signal } from "#structure/Signal";
3
- import { $Element } from "#node/$Element";
3
+ import { $Element, type $Event } from "#node/$Element";
4
4
  import { $Node, type $NodeContentTypes } from '#node/$Node';
5
5
  import '#node/node';
6
- import { _instanceof, _Object_defineProperty, _Object_entries, isFunction, isObject, isString, isUndefined } from '#lib/native';
6
+ import { _instanceof, isString, isFunction, _Object_assign, isObject, isNull, _Object_entries, _Object_defineProperty } from '#lib/native';
7
+ import type { $HTMLElement } from '#node/$HTMLElement';
7
8
 
8
- const tagNameMap: {[key: string]: Constructor<$Node>} = {}
9
- export function $<K extends (...args: any[]) => $Node>(fn: K, ...args: Parameters<K>): ReturnType<K>;
10
- export function $<K extends $NodeContentTypes | undefined | void, F extends () => K, P extends Parameters<F>>(fn: F, ...args: any[]): K;
11
- export function $<K extends $Node, T extends Constructor<K>, P extends ConstructorParameters<T>>(construct: T, ...args: P): K;
9
+ const nodeNameMap: {[key: string]: Constructor<$Node>} = {}
10
+
11
+ export function $<F extends (...args: any[]) => $Node>(fn: F, ...args: Parameters<F>): ReturnType<F>;
12
+ export function $<C extends $NodeContentTypes | undefined | void, F extends () => C, P extends Parameters<F>>(fn: F, ...args: any[]): C;
13
+ export function $<N extends $Node, T extends Constructor<N>, P extends ConstructorParameters<T>>(construct: T, ...args: P): N;
14
+ export function $<N extends $Node>($node: N, ...args: any[]): N;
15
+ export function $<E extends Element>(element: E, ...args: any[]): $Element<E>;
12
16
  export function $<K extends TemplateStringsArray>(string: K, ...values: any[]): $NodeContentTypes[];
13
- export function $<K extends $Node>($node: K): K;
14
- export function $<K extends Element>(element: K): $Element<K>;
15
- export function $<K extends keyof HTMLElementTagNameMap>(tagname: K): $Element<HTMLElementTagNameMap[K]>
16
- export function $(tagname: string): $Element<HTMLElement>
17
- export function $(resolver: string | HTMLElement | $Node | Function | TemplateStringsArray, ...args: any[]) {
17
+ export function $<K extends keyof HTMLElementTagNameMap>(tagname: K): $HTMLElement<HTMLElementTagNameMap[K]>;
18
+ export function $<H extends HTMLElement>(tagname: $Event<H>): $HTMLElement<H>;
19
+ export function $<E extends Element>(tagname: $Event<E>): $Element<E>;
20
+ export function $(tagname: string): $HTMLElement<HTMLElement>
21
+ export function $(resolver: string | Element | $Node | Function | TemplateStringsArray | Event, ...args: any[]) {
18
22
  if (_instanceof(resolver, $Node)) return resolver;
19
- if (isString(resolver) && tagNameMap[resolver]) return new tagNameMap[resolver]();
23
+ if (isString(resolver) && nodeNameMap[resolver]) return new nodeNameMap[resolver](...args);
20
24
  if (isFunction(resolver))
21
25
  if (resolver.prototype?.constructor) return resolver.prototype.constructor(...args);
22
26
  else return resolver(...args);
@@ -25,22 +29,24 @@ export function $(resolver: string | HTMLElement | $Node | Function | TemplateSt
25
29
  return resolver.map(str => [str ?? undefined, iterate.next().value]).flat().filter(item => item);
26
30
  }
27
31
  if (_instanceof(resolver, Node) && _instanceof(resolver.$, $Node)) return resolver.$;
32
+ if (_instanceof(resolver, Event)) return $(resolver.currentTarget as Element)
28
33
  return new $Element(resolver);
29
34
  }
30
35
 
31
36
  export namespace $ {
37
+ export const stylesheet = new CSSStyleSheet();
38
+ document?.adoptedStyleSheets.push(stylesheet);
32
39
  type SignalProcess<T> = T extends Array<any> ? {} : T extends object ? { [key in keyof T as `${string & key}$`]: SignalFunction<T[key]> } : {};
33
- export type SignalFunction<T> = ({(value: ((newValue: T) => T) | T): SignalFunction<T>, (): T}) & { signal: Signal<T> } & SignalProcess<T>;
40
+ export type SignalFunction<T> = {signal: Signal<T>, set: (newValue: T | ((oldValue: T) => T)) => SignalFunction<T>} & (() => T) & SignalProcess<T>;
34
41
  export function signal<T>(value: T): SignalFunction<T>
35
42
  export function signal<T>(value: T) {
36
43
  const signal = new Signal<T>(value);
37
- const signalFn = function (newValue?: T) {
38
- if (!arguments.length) return signal.value();
39
- if (!isUndefined(newValue)) signal.value(newValue);
40
- return signalFn;
41
- }
42
- _Object_defineProperty(signalFn, 'signal', { value: signal });
43
- if (isObject(value)) {
44
+ const signalFn = function () { return signal.value(); }
45
+ _Object_assign(signalFn, {
46
+ signal,
47
+ set(newValue: T) { return signal.value(newValue), signalFn; }
48
+ })
49
+ if (isObject(value) && !isNull(value)) {
44
50
  for (const [key, val] of _Object_entries(value)) {
45
51
  const val$ = $.signal(val);
46
52
  val$.signal.subscribe(newValue => { value[key as keyof typeof value] = newValue; signal.emit() });
@@ -55,12 +61,12 @@ export namespace $ {
55
61
  let subscribed = false;
56
62
  const signalFn: SignalFunction<any> = $.signal(null);
57
63
  function computeFn() {
58
- if (!subscribed) return signalFn(subscribe())();
59
- else return signalFn(process())();
64
+ if (!subscribed) return signalFn.set(subscribe())();
65
+ else return signalFn.set(process())();
60
66
  }
61
67
  function subscribe () {
62
68
  const signalHandler = (signal: Signal<any>) => {
63
- signal.subscribe(() => signalFn(process()))
69
+ signal.subscribe(() => signalFn.set(process()))
64
70
  }
65
71
  Signal.listeners.add(signalHandler);
66
72
  const result = process();
@@ -72,14 +78,21 @@ export namespace $ {
72
78
  return computeFn as ComputeFunction<T>
73
79
  }
74
80
 
75
- export function registerTagName(tagname: string, $node: Constructor<$Node>) {
76
- tagNameMap[tagname] = $node;
81
+ export function assign(resolver: [nodeName: string, $node: Constructor<$Node>][]): void;
82
+ export function assign(nodeName: string, $node: Constructor<$Node>): void;
83
+ export function assign(resolver: string | [nodeName: string, $node: Constructor<$Node>][], $node?: Constructor<$Node>) {
84
+ if (isString(resolver)) $node && (nodeNameMap[resolver] = $node);
85
+ else resolver.forEach(([nodeName, $node]) => nodeNameMap[nodeName] = $node);
77
86
  return $;
78
87
  }
79
88
 
80
89
  export function orArrayResolver<T>(item: OrArray<T>): T[] {
81
90
  return _instanceof(item, Array) ? item : [item];
82
91
  }
92
+
93
+ export function span(content: string) {
94
+ return $('span').content(content);
95
+ }
83
96
  }
84
97
  export type $ = typeof $;
85
98
  globalThis.$ = $;
package/src/global.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { $Element } from '#node/$Element';
2
2
  import type { $Node } from '#node/$Node';
3
3
  import * as core from '#core';
4
+ import type { Signal } from '#structure/Signal';
4
5
 
5
6
  declare global {
6
7
  export import $ = core.$;
@@ -10,6 +11,7 @@ declare global {
10
11
  type OrPromise<T> = T | Promise<T>;
11
12
  type OrNullish<T> = T | Nullish;
12
13
  type Constructor<T> = { new (...args: any[]): T }
14
+ type $Parameter<T> = T | undefined | Signal<T> | Signal<T | undefined>
13
15
  type Prettify<T> = {
14
16
  [K in keyof T]: T[K];
15
17
  } & {};
@@ -2,18 +2,16 @@ import type { $Node } from "#node/$Node";
2
2
  import { assign } from "./assign";
3
3
  import { _Object_entries, _Object_getOwnPropertyDescriptors } from "./native";
4
4
 
5
- export function assignHelper(targets: [object: Constructor<Node>, target: Constructor<$Node>, tagname?: string][]) {
6
- for (const [object, target, tagname] of targets) {
7
- const {set, get, fn} = { set: [] as string[], get: [] as string[], fn: [] as string[] }
8
- // assign native object properties to target
9
- for (const [prop, value] of _Object_entries(_Object_getOwnPropertyDescriptors(object.prototype))) {
10
- if (prop in target.prototype) continue;
11
- if (value.get && !value.set) get.push(prop);
12
- else if (value.value) fn.push(prop);
13
- else if (value.get && value.set) set.push(prop);
14
- }
15
- assign(target, {set, get, fn})
16
- // register tagname
17
- if (tagname) $.registerTagName(tagname, target)
5
+ export function assignHelper(object: Constructor<Node>, target: Constructor<$Node>, tagname?: string) {
6
+ const {set, get, fn} = { set: [] as string[], get: [] as string[], fn: [] as string[] }
7
+ // assign native object properties to target
8
+ for (const [prop, value] of _Object_entries(_Object_getOwnPropertyDescriptors(object.prototype))) {
9
+ if (prop in target.prototype) continue;
10
+ if (value.get && !value.set) get.push(prop);
11
+ else if (value.value) fn.push(prop);
12
+ else if (value.get && value.set) set.push(prop);
18
13
  }
14
+ assign(target, {set, get, fn})
15
+ // register tagname
16
+ if (tagname) $.assign(tagname, target)
19
17
  }
@@ -1,22 +1,23 @@
1
1
  import { Signal } from "#structure/Signal";
2
2
  import { $Node } from "#node/$Node";
3
- import { _Array_from, _instanceof, _Object_entries, _Object_fromEntries, isUndefined } from "#lib/native";
3
+ import { _Array_from, _instanceof, _Object_assign, _Object_entries, _Object_fromEntries, isFunction, isString, isUndefined } from "#lib/native";
4
4
 
5
5
  export class $Element<Ele extends Element = Element> extends $Node {
6
- listeners = new Map<Function, (event: Event) => void>;
7
6
  declare node: Ele
8
7
  constructor(resolver: Ele | string) {
9
- super(_instanceof(resolver, Element) ? resolver : document.createElement(resolver) as unknown as Ele)
8
+ super(_instanceof(resolver, Element) ? resolver : createNode(resolver) as unknown as Ele)
10
9
  //@ts-expect-error
11
10
  this.node.$ = this;
12
11
  }
13
12
 
14
13
  attr(): {[key: string]: string};
14
+ attr(key: string): string | null;
15
15
  attr(obj: {[key: string]: string | number | boolean | Signal<any>}): this;
16
- attr(obj?: {[key: string]: string | number | boolean | Signal<any>}) {
16
+ attr(resolver?: {[key: string]: string | number | boolean | Signal<any>} | string) {
17
17
  if (!arguments.length) return _Object_fromEntries(_Array_from(this.node.attributes).map(attr => [attr.name, attr.value]));
18
- if (obj) for (let [key, value] of _Object_entries(obj)) {
19
- const set = (value: any) => isUndefined(value) && this.node.setAttribute(key, `${value}`)
18
+ if (isString(resolver)) return this.node.getAttribute(resolver);
19
+ if (resolver) for (let [key, value] of _Object_entries(resolver)) {
20
+ const set = (value: any) => !isUndefined(value) && this.node.setAttribute(key, `${value}`)
20
21
  if (_instanceof(value, Signal)) value = value.subscribe(set).value();
21
22
  set(value);
22
23
  }
@@ -43,18 +44,94 @@ export class $Element<Ele extends Element = Element> extends $Node {
43
44
  return this;
44
45
  }
45
46
 
46
- on<K extends keyof HTMLElementEventMap>(type: K, listener: ($node: this, event: Event) => void, options?: boolean | AddEventListenerOptions) {
47
- const handler = (event: Event) => listener(this, event);
48
- this.listeners.set(listener, handler);
49
- this.node.addEventListener(type, handler, options);
47
+ on<K extends keyof HTMLElementEventMap>(type: K, listener: $EventListener<Ele> | $EventListenerObject<Ele>, options?: boolean | AddEventListenerOptions) {
48
+ this.node.addEventListener(type, listener as EventListener, options);
50
49
  return this;
51
50
  }
52
51
 
53
- off<K extends keyof HTMLElementEventMap>(type: K, listener: ($node: this, event: Event) => void, options?: boolean | EventListenerOptions) {
54
- const handler = this.listeners.get(listener);
55
- if (handler) this.node.removeEventListener(type, handler, options);
56
- this.listeners.delete(listener);
52
+ off<K extends keyof HTMLElementEventMap>(type: K, listener: $EventListener<Ele> | $EventListenerObject<Ele>, options?: boolean | EventListenerOptions) {
53
+ this.node.removeEventListener(type, listener as EventListener, options);
57
54
  return this;
58
55
  }
59
- once() {}
56
+
57
+ once<K extends keyof HTMLElementEventMap>(type: K, listener: $EventListener<Ele> | $EventListenerObject<Ele>, options?: boolean | AddEventListenerOptions) {
58
+ const handler = (event: Event) => {
59
+ this.off(type, handler, options);
60
+ isFunction(listener) ? listener(event as any) : listener.handleEvent(event as any);
61
+ }
62
+ this.on(type, handler, options);
63
+ return this;
64
+ }
65
+
66
+ toString() {
67
+ return this.node.outerHTML;
68
+ }
69
+ }
70
+
71
+ export type $Event<E extends Element> = Event & {target: E};
72
+ export type $EventListener<E extends Element> = (event: $Event<E>) => void;
73
+ export type $EventListenerObject<E extends Element> = { handleEvent(object: $Event<E>): void; }
74
+
75
+ function createNode(nodeName: string) {
76
+ //@ts-expect-error
77
+ return !document ? new Node(nodeName) as unknown as Node & ChildNode : document.createElement(nodeName);
78
+ }
79
+
80
+ export interface $Element<Ele extends Element> {
81
+ /** {@link Element.attributes} */
82
+ readonly attributes: NamedNodeMap;
83
+ /** {@link Element.clientHeight} */
84
+ readonly clientHeight: number;
85
+ /** {@link Element.clientLeft} */
86
+ readonly clientLeft: number;
87
+ /** {@link Element.clientTop} */
88
+ readonly clientTop: number;
89
+ /** {@link Element.clientWidth} */
90
+ readonly clientWidth: number;
91
+ /** {@link Element.currentCSSZoom} */
92
+ readonly currentCSSZoom: number;
93
+ /** {@link Element.localName} */
94
+ readonly localName: string;
95
+ /** {@link Element.namespaceURI} */
96
+ readonly namespaceURI: string | null;
97
+ /** {@link Element.prefix} */
98
+ readonly prefix: string | null;
99
+ /** {@link Element.ownerDocument} */
100
+ readonly ownerDocument: Document;
101
+ /** {@link Element.scrollHeight} */
102
+ readonly scrollHeight: number;
103
+ /** {@link Element.scrollWidth} */
104
+ readonly scrollWidth: number;
105
+ /** {@link Element.shadowRoot} */
106
+ readonly shadowRoot: ShadowRoot | null;
107
+ /** {@link Element.tagName} */
108
+ readonly tagName: string;
109
+
110
+ /** {@link Element.classList} */
111
+ classList(): DOMTokenList;
112
+ classList(value: $Parameter<string>): this;
113
+ /** {@link Element.className} */
114
+ className(): string;
115
+ className(value: $Parameter<string>): this;
116
+ /** {@link Element.id} */
117
+ id(): string;
118
+ id(id: $Parameter<string>): this;
119
+ /** {@link Element.innerHTML} */
120
+ innerHTML(): string;
121
+ innerHTML(innerHTML: $Parameter<string>): this;
122
+ /** {@link Element.outerHTML} */
123
+ outerHTML(): string;
124
+ outerHTML(outerHTML: $Parameter<string>): this;
125
+ /** {@link Element.part} */
126
+ part(): DOMTokenList;
127
+ part(part: $Parameter<string>): this;
128
+ /** {@link Element.scrollLeft} */
129
+ scrollLeft(): number;
130
+ scrollLeft(scrollLeft: $Parameter<number>): this;
131
+ /** {@link Element.scrollTop} */
132
+ scrollTop(): number;
133
+ scrollTop(scrollTop: $Parameter<number>): this;
134
+ /** {@link Element.slot} */
135
+ slot(): string;
136
+ slot(slot: $Parameter<string>): this;
60
137
  }