amateras 0.3.0 → 0.4.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.
Files changed (42) hide show
  1. package/README.md +6 -4
  2. package/ext/css/README.md +19 -0
  3. package/ext/css/src/index.ts +347 -331
  4. package/ext/css/src/lib/colorAssign.ts +1 -1
  5. package/ext/css/src/structure/$CSSContainerRule.ts +13 -0
  6. package/ext/css/src/structure/$CSSRule.ts +1 -1
  7. package/ext/css/src/structure/$CSSStyleRule.ts +0 -7
  8. package/ext/css/src/structure/$CSSVariable.ts +3 -3
  9. package/ext/html/html.ts +1 -13
  10. package/ext/i18n/README.md +53 -0
  11. package/ext/i18n/package.json +10 -0
  12. package/ext/i18n/src/index.ts +54 -0
  13. package/ext/i18n/src/node/I18nText.ts +35 -0
  14. package/ext/i18n/src/structure/I18n.ts +40 -0
  15. package/ext/i18n/src/structure/I18nDictionary.ts +31 -0
  16. package/ext/markdown/index.ts +123 -0
  17. package/ext/router/index.ts +8 -1
  18. package/ext/router/node/Page.ts +1 -0
  19. package/ext/router/node/Route.ts +2 -1
  20. package/ext/router/node/Router.ts +33 -22
  21. package/ext/ssr/index.ts +4 -2
  22. package/ext/ui/lib/VirtualScroll.ts +24 -0
  23. package/ext/ui/node/Accordian.ts +97 -0
  24. package/ext/ui/node/Tabs.ts +114 -0
  25. package/ext/ui/node/Toast.ts +16 -0
  26. package/ext/ui/node/Waterfall.ts +73 -0
  27. package/ext/ui/package.json +11 -0
  28. package/package.json +4 -7
  29. package/src/core.ts +21 -8
  30. package/src/lib/assign.ts +8 -9
  31. package/src/lib/assignHelper.ts +1 -1
  32. package/src/lib/chain.ts +3 -0
  33. package/src/lib/debounce.ts +7 -0
  34. package/src/lib/env.ts +2 -0
  35. package/src/lib/native.ts +22 -35
  36. package/src/lib/randomId.ts +1 -1
  37. package/src/lib/sleep.ts +1 -1
  38. package/src/node/$Element.ts +182 -20
  39. package/src/node/$HTMLElement.ts +24 -0
  40. package/src/node/$Node.ts +75 -52
  41. package/src/node/$Virtual.ts +58 -0
  42. package/src/{node/node.ts → node.ts} +2 -5
@@ -1,6 +1,7 @@
1
1
  import { Signal } from "#structure/Signal";
2
2
  import { $Node } from "#node/$Node";
3
- import { _Array_from, _document, _instanceof, _Object_assign, _Object_entries, _Object_fromEntries, isFunction, isString, isUndefined } from "#lib/native";
3
+ import { _Array_from, _instanceof, _Object_assign, _Object_entries, _Object_fromEntries, isNull, isString, isUndefined } from "#lib/native";
4
+ import { _document } from "#lib/env";
4
5
 
5
6
  export class $Element<Ele extends Element = Element, EvMap = ElementEventMap> extends $Node {
6
7
  declare node: Ele
@@ -12,12 +13,12 @@ export class $Element<Ele extends Element = Element, EvMap = ElementEventMap> ex
12
13
 
13
14
  attr(): {[key: string]: string};
14
15
  attr(key: string): string | null;
15
- attr(obj: {[key: string]: string | number | boolean | Signal<any>}): this;
16
- attr(resolver?: {[key: string]: string | number | boolean | Signal<any>} | string) {
16
+ attr(obj: {[key: string]: string | number | boolean | Signal<any> | null}): this;
17
+ attr(resolver?: {[key: string]: string | number | boolean | Signal<any> | null} | string) {
17
18
  if (!arguments.length) return _Object_fromEntries(_Array_from(this.attributes).map(attr => [attr.name, attr.value]));
18
19
  if (isString(resolver)) return this.getAttribute(resolver);
19
20
  if (resolver) for (let [key, value] of _Object_entries(resolver)) {
20
- const set = (value: any) => !isUndefined(value) && this.setAttribute(key, `${value}`)
21
+ const set = (value: any) => !isUndefined(value) && isNull(value) ? this.removeAttribute(key) : this.setAttribute(key, `${value}`)
21
22
  if (_instanceof(value, Signal)) value = value.subscribe(set).value();
22
23
  set(value);
23
24
  }
@@ -39,28 +40,18 @@ export class $Element<Ele extends Element = Element, EvMap = ElementEventMap> ex
39
40
  return this;
40
41
  }
41
42
 
42
- use(callback: ($ele: this) => void) {
43
- callback(this);
44
- return this;
45
- }
46
-
47
43
  on<K extends keyof EvMap, Ev extends EvMap[K]>(type: K, listener: $EventListener<this, Ev> | $EventListenerObject<this, Ev>, options?: boolean | AddEventListenerOptions) {
48
- this.node.addEventListener(type as string, listener as any, options);
44
+ this.addEventListener(type as string, listener as any, options);
49
45
  return this;
50
46
  }
51
47
 
52
48
  off<K extends keyof EvMap, Ev extends EvMap[K]>(type: K, listener: $EventListener<this, Ev> | $EventListenerObject<this, Ev>, options?: boolean | EventListenerOptions) {
53
- this.node.removeEventListener(type as string, listener as any, options);
49
+ this.removeEventListener(type as string, listener as any, options);
54
50
  return this;
55
51
  }
56
52
 
57
53
  once<K extends keyof EvMap, Ev extends EvMap[K]>(type: K, listener: $EventListener<this, Ev> | $EventListenerObject<this, Ev>, options?: boolean | AddEventListenerOptions) {
58
- const handler = (event: $Event<any>) => {
59
- this.off(type, handler, options);
60
- isFunction(listener) ? listener(event) : listener.handleEvent(event);
61
- }
62
- this.on(type, handler, options);
63
- return this;
54
+ return this.on(type, listener, {once: true})
64
55
  }
65
56
 
66
57
  toString() {
@@ -68,14 +59,15 @@ export class $Element<Ele extends Element = Element, EvMap = ElementEventMap> ex
68
59
  }
69
60
  }
70
61
 
71
- export type EventMap = {[key: string]: Event}
72
- export type $Event<E extends $Element, Ev = any> = Ev & {target: {$: E}};
62
+ export type $Event<E extends $Element, Ev = any> = Ev & {currentTarget: {$: E}};
73
63
  export type $EventListener<E extends $Element, Ev> = (event: $Event<E, Ev>) => void;
74
64
  export type $EventListenerObject<E extends $Element, Ev> = { handleEvent(object: $Event<E, Ev>): void; }
75
65
 
76
66
  function createNode(nodeName: string) {
67
+ return !_document
77
68
  //@ts-expect-error
78
- return !_document ? new Node(nodeName) as unknown as Node & ChildNode : _document.createElement(nodeName);
69
+ ? new Node(nodeName) as unknown as Node & ChildNode
70
+ : _document.createElement(nodeName);
79
71
  }
80
72
 
81
73
  export interface $Element<Ele extends Element, EvMap> {
@@ -107,6 +99,20 @@ export interface $Element<Ele extends Element, EvMap> {
107
99
  readonly shadowRoot: ShadowRoot | null;
108
100
  /** {@link Element.tagName} */
109
101
  readonly tagName: string;
102
+ /** {@link Element.nextElementSibling} */
103
+ readonly nextElementSibling: Element | null;
104
+ /** {@link Element.previousElementSibling} */
105
+ readonly previousElementSibling: Element | null;
106
+ /** {@link Element.childElementCount} */
107
+ readonly childElementCount: number;
108
+ /** {@link Element.children} */
109
+ readonly children: HTMLCollection;
110
+ /** {@link Element.firstElementChild} */
111
+ readonly firstElementChild: Element | null;
112
+ /** {@link Element.lastElementChild} */
113
+ readonly lastElementChild: Element | null;
114
+ /** {@link Element.assignedSlot} */
115
+ readonly assignedSlot: HTMLSlotElement | null;
110
116
 
111
117
  /** {@link Element.attachShadow} */
112
118
  attachShadow(init: ShadowRootInit): ShadowRoot;
@@ -200,6 +206,24 @@ export interface $Element<Ele extends Element, EvMap> {
200
206
  setPointerCapture(pointerId: number): this;
201
207
  /** {@link Element.toggleAttribute} */
202
208
  toggleAttribute(qualifiedName: string, force?: boolean): boolean;
209
+ /** {@link Element.animate} */
210
+ animate(keyframes: Keyframe[] | PropertyIndexedKeyframes | null, options?: number | KeyframeAnimationOptions): Animation;
211
+ /** {@link Element.getAnimations} */
212
+ getAnimations(options?: GetAnimationsOptions): Animation[];
213
+ /** {@link Element.append} */
214
+ append(...nodes: (Node | string)[]): this;
215
+ /** {@link Element.prepend} */
216
+ prepend(...nodes: (Node | string)[]): this;
217
+ /** {@link Element.querySelector} */
218
+ querySelector<K extends keyof HTMLElementTagNameMap>(selectors: K): HTMLElementTagNameMap[K] | null;
219
+ querySelector<K extends keyof SVGElementTagNameMap>(selectors: K): SVGElementTagNameMap[K] | null;
220
+ querySelector<K extends keyof MathMLElementTagNameMap>(selectors: K): MathMLElementTagNameMap[K] | null;
221
+ /** {@link Element.querySelectorAll} */
222
+ querySelectorAll<K extends keyof HTMLElementTagNameMap>(selectors: K): NodeListOf<HTMLElementTagNameMap[K]>;
223
+ querySelectorAll<K extends keyof SVGElementTagNameMap>(selectors: K): NodeListOf<SVGElementTagNameMap[K]>;
224
+ querySelectorAll<K extends keyof MathMLElementTagNameMap>(selectors: K): NodeListOf<MathMLElementTagNameMap[K]>;
225
+ /** {@link Element.replaceChildren} */
226
+ replaceChildren(...nodes: (Node | string)[]): this;
203
227
 
204
228
  /** {@link Element.classList} */
205
229
  classList(): DOMTokenList;
@@ -228,6 +252,144 @@ export interface $Element<Ele extends Element, EvMap> {
228
252
  /** {@link Element.slot} */
229
253
  slot(): string;
230
254
  slot(slot: $Parameter<string>): this;
255
+
256
+ // ARIAMixin
257
+ /** {@link ARIAMixin.ariaAtomic} */
258
+ ariaAtomic(): string | null;
259
+ ariaAtomic(ariaAtomic: $Parameter<string | null>): this;
260
+ /** {@link ARIAMixin.ariaAutoComplete} */
261
+ ariaAutoComplete(): string | null;
262
+ ariaAutoComplete(ariaAutoComplete: $Parameter<string | null>): this;
263
+ /** {@link ARIAMixin.ariaBrailleLabel} */
264
+ ariaBrailleLabel(): string | null;
265
+ ariaBrailleLabel(ariaBrailleLabel: $Parameter<string | null>): this;
266
+ /** {@link ARIAMixin.ariaBrailleRoleDescription} */
267
+ ariaBrailleRoleDescription(): string | null;
268
+ ariaBrailleRoleDescription(ariaBrailleRoleDescription: $Parameter<string | null>): this;
269
+ /** {@link ARIAMixin.ariaBusy} */
270
+ ariaBusy(): string | null;
271
+ ariaBusy(ariaBusy: $Parameter<string | null>): this;
272
+ /** {@link ARIAMixin.ariaChecked} */
273
+ ariaChecked(): string | null;
274
+ ariaChecked(ariaChecked: $Parameter<string | null>): this;
275
+ /** {@link ARIAMixin.ariaColCount} */
276
+ ariaColCount(): string | null;
277
+ ariaColCount(ariaColCount: $Parameter<string | null>): this;
278
+ /** {@link ARIAMixin.ariaColIndex} */
279
+ ariaColIndex(): string | null;
280
+ ariaColIndex(ariaColIndex: $Parameter<string | null>): this;
281
+ /** {@link ARIAMixin.ariaColIndexText} */
282
+ ariaColIndexText(): string | null;
283
+ ariaColIndexText(ariaColIndexText: $Parameter<string | null>): this;
284
+ /** {@link ARIAMixin.ariaColSpan} */
285
+ ariaColSpan(): string | null;
286
+ ariaColSpan(ariaColSpan: $Parameter<string | null>): this;
287
+ /** {@link ARIAMixin.ariaCurrent} */
288
+ ariaCurrent(): string | null;
289
+ ariaCurrent(ariaCurrent: $Parameter<string | null>): this;
290
+ /** {@link ARIAMixin.ariaDescription} */
291
+ ariaDescription(): string | null;
292
+ ariaDescription(ariaDescription: $Parameter<string | null>): this;
293
+ /** {@link ARIAMixin.ariaDisabled} */
294
+ ariaDisabled(): string | null;
295
+ ariaDisabled(ariaDisabled: $Parameter<string | null>): this;
296
+ /** {@link ARIAMixin.ariaExpanded} */
297
+ ariaExpanded(): string | null;
298
+ ariaExpanded(ariaExpanded: $Parameter<string | null>): this;
299
+ /** {@link ARIAMixin.ariaHasPopup} */
300
+ ariaHasPopup(): string | null;
301
+ ariaHasPopup(ariaHasPopup: $Parameter<string | null>): this;
302
+ /** {@link ARIAMixin.ariaHidden} */
303
+ ariaHidden(): string | null;
304
+ ariaHidden(ariaHidden: $Parameter<string | null>): this;
305
+ /** {@link ARIAMixin.ariaInvalid} */
306
+ ariaInvalid(): string | null;
307
+ ariaInvalid(ariaInvalid: $Parameter<string | null>): this;
308
+ /** {@link ARIAMixin.ariaKeyShortcuts} */
309
+ ariaKeyShortcuts(): string | null;
310
+ ariaKeyShortcuts(ariaKeyShortcuts: $Parameter<string | null>): this;
311
+ /** {@link ARIAMixin.ariaLabel} */
312
+ ariaLabel(): string | null;
313
+ ariaLabel(ariaLabel: $Parameter<string | null>): this;
314
+ /** {@link ARIAMixin.ariaLevel} */
315
+ ariaLevel(): string | null;
316
+ ariaLevel(ariaLevel: $Parameter<string | null>): this;
317
+ /** {@link ARIAMixin.ariaLive} */
318
+ ariaLive(): string | null;
319
+ ariaLive(ariaLive: $Parameter<string | null>): this;
320
+ /** {@link ARIAMixin.ariaModal} */
321
+ ariaModal(): string | null;
322
+ ariaModal(ariaModal: $Parameter<string | null>): this;
323
+ /** {@link ARIAMixin.ariaMultiLine} */
324
+ ariaMultiLine(): string | null;
325
+ ariaMultiLine(ariaMultiLine: $Parameter<string | null>): this;
326
+ /** {@link ARIAMixin.ariaMultiSelectable} */
327
+ ariaMultiSelectable(): string | null;
328
+ ariaMultiSelectable(ariaMultiSelectable: $Parameter<string | null>): this;
329
+ /** {@link ARIAMixin.ariaOrientation} */
330
+ ariaOrientation(): string | null;
331
+ ariaOrientation(ariaOrientation: $Parameter<string | null>): this;
332
+ /** {@link ARIAMixin.ariaPlaceholder} */
333
+ ariaPlaceholder(): string | null;
334
+ ariaPlaceholder(ariaPlaceholder: $Parameter<string | null>): this;
335
+ /** {@link ARIAMixin.ariaPosInSet} */
336
+ ariaPosInSet(): string | null;
337
+ ariaPosInSet(ariaPosInSet: $Parameter<string | null>): this;
338
+ /** {@link ARIAMixin.ariaPressed} */
339
+ ariaPressed(): string | null;
340
+ ariaPressed(ariaPressed: $Parameter<string | null>): this;
341
+ /** {@link ARIAMixin.ariaReadOnly} */
342
+ ariaReadOnly(): string | null;
343
+ ariaReadOnly(ariaReadOnly: $Parameter<string | null>): this;
344
+ /** {@link ARIAMixin.ariaRelevant} */
345
+ ariaRelevant(): string | null;
346
+ ariaRelevant(ariaRelevant: $Parameter<string | null>): this;
347
+ /** {@link ARIAMixin.ariaRequired} */
348
+ ariaRequired(): string | null;
349
+ ariaRequired(ariaRequired: $Parameter<string | null>): this;
350
+ /** {@link ARIAMixin.ariaRoleDescription} */
351
+ ariaRoleDescription(): string | null;
352
+ ariaRoleDescription(ariaRoleDescription: $Parameter<string | null>): this;
353
+ /** {@link ARIAMixin.ariaRowCount} */
354
+ ariaRowCount(): string | null;
355
+ ariaRowCount(ariaRowCount: $Parameter<string | null>): this;
356
+ /** {@link ARIAMixin.ariaRowIndex} */
357
+ ariaRowIndex(): string | null;
358
+ ariaRowIndex(ariaRowIndex: $Parameter<string | null>): this;
359
+ /** {@link ARIAMixin.ariaRowIndexText} */
360
+ ariaRowIndexText(): string | null;
361
+ ariaRowIndexText(ariaRowIndexText: $Parameter<string | null>): this;
362
+ /** {@link ARIAMixin.ariaRowSpan} */
363
+ ariaRowSpan(): string | null;
364
+ ariaRowSpan(ariaRowSpan: $Parameter<string | null>): this;
365
+ /** {@link ARIAMixin.ariaSelected} */
366
+ ariaSelected(): string | null;
367
+ ariaSelected(ariaSelected: $Parameter<string | null>): this;
368
+ /** {@link ARIAMixin.ariaSetSize} */
369
+ ariaSetSize(): string | null;
370
+ ariaSetSize(ariaSetSize: $Parameter<string | null>): this;
371
+ /** {@link ARIAMixin.ariaSort} */
372
+ ariaSort(): string | null;
373
+ ariaSort(ariaSort: $Parameter<string | null>): this;
374
+ /** {@link ARIAMixin.ariaValueMax} */
375
+ ariaValueMax(): string | null;
376
+ ariaValueMax(ariaValueMax: $Parameter<string | null>): this;
377
+ /** {@link ARIAMixin.ariaValueMin} */
378
+ ariaValueMin(): string | null;
379
+ ariaValueMin(ariaValueMin: $Parameter<string | null>): this;
380
+ /** {@link ARIAMixin.ariaValueNow} */
381
+ ariaValueNow(): string | null;
382
+ ariaValueNow(ariaValueNow: $Parameter<string | null>): this;
383
+ /** {@link ARIAMixin.ariaValueText} */
384
+ ariaValueText(): string | null;
385
+ ariaValueText(ariaValueText: $Parameter<string | null>): this;
386
+ /** {@link ARIAMixin.role} */
387
+ role(): string | null;
388
+ role(role: $Parameter<string | null>): this;
389
+ addEventListener<K extends keyof EvMap, Ev extends EvMap[K]>(type: K, listener: $EventListener<this, Ev> | $EventListenerObject<this, Ev>, options?: boolean | AddEventListenerOptions): void;
390
+ addEventListener(type: string, listener: $EventListener<this, Event> | $EventListenerObject<this, Event>, options?: boolean | AddEventListenerOptions): void;
391
+ removeEventListener<K extends keyof EvMap, Ev extends EvMap[K]>(type: K, listener: $EventListener<this, Ev> | $EventListenerObject<this, Ev>, options?: boolean | EventListenerOptions): void;
392
+ removeEventListener(type: string, listener: $EventListener<this, Event> | $EventListenerObject<this, Event>, options?: boolean | EventListenerOptions): void;
231
393
 
232
394
 
233
395
  on(type: string, listener: $EventListener<this, Event> | $EventListenerObject<this, Event>, options?: boolean | AddEventListenerOptions): this;
@@ -1,11 +1,24 @@
1
+ import { _Object_entries, forEach } from "#lib/native";
1
2
  import { $Element } from "#node/$Element";
2
3
 
3
4
  export class $HTMLElement<Ele extends HTMLElement = HTMLElement, EvMap = HTMLElementEventMap> extends $Element<Ele, EvMap> {
4
5
  constructor(resolver: string | Ele) {
5
6
  super(resolver);
6
7
  }
8
+
9
+ style(): CSSStyleDeclaration;
10
+ style(style: Partial<CSSStyleDeclarationOptions> | undefined): this
11
+ style(style?: Partial<CSSStyleDeclarationOptions> | undefined) {
12
+ let _style = this.node.style
13
+ if (!arguments.length) return _style
14
+ if (!style) return this;
15
+ forEach(_Object_entries(style), ([key, value]) => _style[key as any] = value ?? '')
16
+ return this;
17
+ }
7
18
  }
8
19
 
20
+ type CSSStyleDeclarationOptions = Omit<CSSStyleDeclaration, 'parentRule' | 'length' | 'getPropertyPriority' | 'getPropertyValue' | 'item' | 'removeProperty' | 'setProperty'>;
21
+
9
22
  export interface $HTMLElement<Ele extends HTMLElement = HTMLElement, EvMap = HTMLElementEventMap> extends $Element<Ele, EvMap> {
10
23
  /** {@link HTMLElement.accessKeyLabel} */
11
24
  readonly accessKeyLabel: string;
@@ -19,6 +32,8 @@ export interface $HTMLElement<Ele extends HTMLElement = HTMLElement, EvMap = HTM
19
32
  readonly offsetTop: number;
20
33
  /** {@link HTMLElement.offsetWidth} */
21
34
  readonly offsetWidth: number;
35
+ /** {@link HTMLElement.isContentEditable} */
36
+ readonly isContentEditable: boolean;
22
37
 
23
38
  /** {@link HTMLElement.attachInternals} */
24
39
  attachInternals(): ElementInternals;
@@ -73,4 +88,13 @@ export interface $HTMLElement<Ele extends HTMLElement = HTMLElement, EvMap = HTM
73
88
  /** {@link HTMLElement.writingSuggestions} */
74
89
  writingSuggestions(): string;
75
90
  writingSuggestions(writingSuggestions: $Parameter<string>): this;
91
+ /** {@link HTMLElement.contentEditable} */
92
+ contentEditable(): string;
93
+ contentEditable(contentEditable: $Parameter<string>): this;
94
+ /** {@link HTMLElement.enterKeyHint} */
95
+ enterKeyHint(): string;
96
+ enterKeyHint(enterKeyHint: $Parameter<string>): this;
97
+ /** {@link HTMLElement.inputMode} */
98
+ inputMode(): string;
99
+ inputMode(inputMode: $Parameter<string>): this;
76
100
  }
package/src/node/$Node.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { _Array_from, _document, _instanceof, _JSON_stringify, forEach, isFunction, isNull, isObject, isUndefined } from "#lib/native";
1
+ import { _document } from "#lib/env";
2
+ import { _Array_from, _instanceof, _JSON_stringify, forEach, isFunction, isNull, isObject, isUndefined } from "#lib/native";
2
3
  import { Signal } from "#structure/Signal";
3
4
 
4
5
  export class $Node {
@@ -10,84 +11,100 @@ export class $Node {
10
11
  }
11
12
 
12
13
  content(children: $NodeContentResolver<this>) {
14
+ if (isUndefined(children)) return this;
13
15
  forEach(_Array_from(this.childNodes), node => node.remove());
14
16
  return this.insert(children);
15
17
  }
16
18
 
17
19
  insert(resolver: $NodeContentResolver<this>, position = -1) {
18
- // insert node helper function for depend position
19
- const appendChild = (children: OrArray<$Node | undefined | null>) => {
20
- // get child node at position
21
- const positionChild = _Array_from(this.childNodes).filter(node => node.nodeType !== node.TEXT_NODE).at(position);
22
- forEach($.toArray(children), child => {
23
- if (!child) return;
24
- if (_instanceof(child, Array)) this.insert(child);
25
- else if (!positionChild) this.appendChild(child.node);
26
- else this.insertBefore(child.node, position < 0 ? positionChild.nextSibling : positionChild);
27
- })
28
- }
29
20
  // process nodes
30
- for (const child of $.toArray(resolver)) !isUndefined(child) && appendChild(processContent(this, child))
21
+ forEach($.toArray(resolver), resolve_child => forEach($Node.process(this, resolve_child), $node => $Node.append(this, $node, position)));
31
22
  return this;
32
23
  }
33
24
 
34
- await<T>(promise: Promise<T>, callback: ($node: this, result: T) => void): this {
35
- return promise.then(result => callback(this, result)), this;
25
+ await<T>(promise: OrPromise<T>, callback: ($node: this, result: T) => void): this {
26
+ if (_instanceof(promise, Promise)) return promise.then(result => callback(this, result)), this;
27
+ else return callback(this, promise), this;
36
28
  }
37
29
 
38
30
  replace($node: $NodeContentResolver<$Node>) {
39
31
  if (!$node) return this;
40
32
  this.replaceWith(
41
- ...$.toArray(processContent(this, $node)).filter($node => $node).map($node => $node?.node) as Node[]
33
+ ...$.toArray($Node.process(this, $node)).filter($node => $node).map($node => $node?.node) as Node[]
42
34
  )
43
35
  return this;
44
36
  }
45
37
 
38
+ inDOM() {
39
+ return _document.contains(this.node);
40
+ }
41
+
46
42
  toString() {
47
43
  return this.textContent();
48
44
  }
49
- }
50
45
 
51
- function processContent<T extends $Node>($node: T, content: $NodeContentResolver<any>): OrArray<$Node | undefined | null> {
52
- if (isUndefined(content)) return;
53
- if (isNull(content)) return content;
54
- // is $Element
55
- if (_instanceof(content, $Node)) return content;
56
- // is Promise
57
- if (_instanceof(content, Promise)) return $('async').await(content, ($async, $child) => $async.replace($child as any));
58
- // is SignalFunction or ContentHandler
59
- if (isFunction(content)) {
60
- const signal = (content as any).signal;
61
- if (_instanceof(signal, Signal)) {
62
- const resolver = (content as $.SignalFunction<any>)();
63
- if (_instanceof(resolver, $Node)) {
64
- // handler signal $Node result
65
- let node = resolver;
66
- const set = (value: any) => {
67
- node.replace(value);
68
- node = value;
46
+ mounted($parent: $Node) {
47
+ return this;
48
+ }
49
+
50
+ use<F extends ($ele: this, ...args: any) => void>(callback: F, ...args: F extends ($ele: this, ...args: infer P) => void ? P : never) {
51
+ callback(this, ...args);
52
+ return this;
53
+ }
54
+
55
+ is<T extends (abstract new (...args: any[]) => $Node)>(instance: T): InstanceType<T> | null {
56
+ return _instanceof(this, instance) ? this : null;
57
+ }
58
+
59
+ static process<T extends $Node>($node: T, content: $NodeContentResolver<any>): Array<$Node | undefined | null> {
60
+ if (isUndefined(content) || isNull(content) || _instanceof(content, $Node)) return [content];
61
+ // is Promise
62
+ if (_instanceof(content, Promise)) return [$('async').await(content, ($async, $child) => $async.replace($child as any))];
63
+ // is SignalFunction or ContentHandler
64
+ if (isFunction(content)) {
65
+ const signal = (content as any).signal;
66
+ if (_instanceof(signal, Signal)) {
67
+ const resolver = (content as $.SignalFunction<any>)();
68
+ if (_instanceof(resolver, $Node)) {
69
+ // handler signal $Node result
70
+ let node = resolver;
71
+ const set = (value: any) => {
72
+ node.replace(value);
73
+ node = value;
74
+ }
75
+ signal.subscribe(set);
76
+ return [resolver];
77
+ } else {
78
+ // handler signal other type result
79
+ const $text = _document ? new $Text() : $('signal').attr({ type: typeof signal.value() });
80
+ const set = (value: any) => $text.textContent(isObject(value) ? _JSON_stringify(value) : value);
81
+ if (_instanceof($text, $Text)) $text.signals.add(signal);
82
+ signal.subscribe(set);
83
+ set(resolver);
84
+ return [$text];
69
85
  }
70
- signal.subscribe(set);
71
- return resolver;
72
86
  } else {
73
- // handler signal other type result
74
- const $text = _document ? new $Text() : $('signal').attr({ type: typeof signal.value() });
75
- const set = (value: any) => $text.textContent(isObject(value) ? _JSON_stringify(value) : value);
76
- if (_instanceof($text, $Text)) $text.signals.add(signal);
77
- signal.subscribe(set);
78
- set(resolver);
79
- return $text;
87
+ const _content = content($node) as $NodeContentResolver<$Node>;
88
+ if (_instanceof(_content, Promise)) return this.process($node, _content as any);
89
+ else return $.toArray(_content).map(content => this.process($node, content)).flat();
80
90
  }
81
- } else {
82
- const _content = content($node) as $NodeContentResolver<$Node>;
83
- if (_instanceof(_content, Promise)) return processContent($node, _content as any);
84
- else return $.toArray(_content).map(content => processContent($node, content) as $Node);
85
91
  }
92
+ // is nested array
93
+ if (_instanceof(content, Array)) return content.map(c => this.process($node, c)).flat();
94
+ // is string | number | boolean
95
+ return [new $Text(`${content}`)];
96
+ }
97
+
98
+ /** */
99
+ static append($node: $Node, child: $Node | undefined | null, position: number) {
100
+ // insert each child, child may be an array
101
+ if (!child) return;
102
+ // get child node at position
103
+ let positionChild = _Array_from($node.childNodes).at(position);
104
+ if (!positionChild) $node.appendChild(child.node);
105
+ else $node.insertBefore(child.node, position < 0 ? positionChild.nextSibling : positionChild);
106
+ child.mounted($node);
86
107
  }
87
- // is nested array
88
- if (_instanceof(content, Array)) return content.map(c => processContent($node, c) as $Node)
89
- // is string | number | boolean
90
- return new $Text(`${content}`);
91
108
  }
92
109
 
93
110
  export class $Text extends $Node {
@@ -163,6 +180,12 @@ export interface $Node {
163
180
  remove(): this;
164
181
  /** {@link Node.replaceChild} */
165
182
  replaceWith(...nodes: (Node | string)[]): this;
183
+ /** {@link EventTarget.addEventListener} */
184
+ addEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: AddEventListenerOptions | boolean): void;
185
+ /** {@link EventTarget.removeEventListener} */
186
+ removeEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: EventListenerOptions | boolean): void;
187
+ /** {@link EventTarget.dispatchEvent} */
188
+ dispatchEvent(event: Event): boolean;
166
189
 
167
190
  /** {@link Node.nodeValue} */
168
191
  nodeValue(nodeValue: $Parameter<string | null>): this;
@@ -0,0 +1,58 @@
1
+ import { forEach, _Array_from, isUndefined, _instanceof } from "#lib/native";
2
+ import { $HTMLElement } from "#node/$HTMLElement";
3
+ import { $Node, type $NodeContentResolver } from "#node/$Node";
4
+
5
+ export class $Virtual<Ele extends HTMLElement = HTMLElement, EvMap = HTMLElementEventMap> extends $HTMLElement<Ele, EvMap> {
6
+ nodes = new Set<$Node>;
7
+ hiddenNodes = new Set<$Node>;
8
+ constructor(resolver: string | Ele) {
9
+ super(resolver);
10
+ }
11
+
12
+ content(children: $NodeContentResolver<this>) {
13
+ this.nodes.clear();
14
+ forEach(_Array_from(this.childNodes), node => node.remove());
15
+ return this.insert(children);
16
+ }
17
+
18
+ insert(resolver: $NodeContentResolver<this>, position = -1) {
19
+ // process nodes
20
+ forEach($.toArray(resolver), resolve_child => {
21
+ forEach($Node.process(this, resolve_child), $node => $Virtual.append(this, $node, position))
22
+ });
23
+ this.render();
24
+ return this;
25
+ }
26
+
27
+ hide($node?: $Node | null) {
28
+ if (!$node || !this.nodes.has($node)) return this;
29
+ this.hiddenNodes.add($node);
30
+ return this;
31
+ }
32
+
33
+ show($node?: $Node | null) {
34
+ if (!$node) return this;
35
+ this.hiddenNodes.delete($node);
36
+ return this;
37
+ }
38
+
39
+ render() {
40
+ // remove hidden node
41
+ forEach(_Array_from(this.childNodes), node => this.hiddenNodes.has($(node)) && node.remove());
42
+ // add visible node with position
43
+ forEach(_Array_from(this.nodes), ($node, i) => {
44
+ if (this.hiddenNodes.has($node)) return;
45
+ if (_Array_from(this.childNodes).at(i) !== $node.node) $Node.append(this, $node, i);
46
+ })
47
+ return this;
48
+ }
49
+
50
+ static append($node: $Virtual, child: $Node | undefined | null, position: number) {
51
+ if (!child) return;
52
+ const childList = _Array_from($node.nodes);
53
+ let $positionChild = childList.at(position);
54
+ if (!$positionChild) childList.push(child);
55
+ else childList.splice(position >= 0 ? position : childList.length + 1 + position, 0, child);
56
+ $node.nodes = new Set(childList);
57
+ }
58
+ }
@@ -1,12 +1,9 @@
1
1
  import { $HTMLElement } from '#node/$HTMLElement';
2
2
  import { assignHelper } from '#lib/assignHelper';
3
3
  import { $Element } from '#node/$Element';
4
- import { $Node, $Text } from './$Node';
5
-
6
- export * from './$Node';
7
- export * from './$Element';
8
- export * from './$HTMLElement';
4
+ import { $Node, $Text } from '#node/$Node';
9
5
 
6
+ assignHelper(EventTarget, $Node);
10
7
  assignHelper(Node, $Node);
11
8
  assignHelper(Text, $Text);
12
9
  assignHelper(Element, $Element);