@esportsplus/template 0.16.1 → 0.17.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.
@@ -1,10 +1,18 @@
1
- import { RENDERABLE, RENDERABLE_REACTIVE, RENDERABLE_TEMPLATE } from '../constants.js';
1
+ import { RENDERABLE, RENDERABLE_HTML_FRAGMENT, RENDERABLE_HTML_REACTIVE_ARRAY } from '../constants.js';
2
2
  import hydrate from './hydrate.js';
3
+ import cache from './cache.js';
3
4
  const html = (literals, ...values) => {
4
- return { [RENDERABLE]: RENDERABLE_TEMPLATE, literals, template: null, values };
5
+ return {
6
+ [RENDERABLE]: RENDERABLE_HTML_FRAGMENT,
7
+ fragment: hydrate(cache.get(literals), values),
8
+ literals
9
+ };
5
10
  };
6
11
  html.reactive = (array, template) => {
7
- return { [RENDERABLE]: RENDERABLE_REACTIVE, literals: null, template, values: array };
12
+ return {
13
+ [RENDERABLE]: RENDERABLE_HTML_REACTIVE_ARRAY,
14
+ array,
15
+ template
16
+ };
8
17
  };
9
18
  export default html;
10
- export { hydrate };
package/build/render.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { Slot } from './slot.js';
2
1
  import { Renderable } from './types.js';
3
- declare const _default: (renderable: Renderable, parent: HTMLElement | Slot) => void | Slot;
2
+ declare const _default: (parent: HTMLElement, renderable: Renderable) => void;
4
3
  export default _default;
package/build/render.js CHANGED
@@ -1,13 +1,9 @@
1
- import { isInstanceOf } from '@esportsplus/utilities';
2
1
  import { SLOT_HTML } from './constants.js';
3
- import slot, { Slot } from './slot.js';
4
- import { firstChild, fragment, nodeValue, prepend } from './utilities.js';
5
- let marker = firstChild.call(fragment(SLOT_HTML)), node;
6
- export default (renderable, parent) => {
7
- if (isInstanceOf(parent, Slot)) {
8
- return parent.render(renderable);
9
- }
2
+ import { firstChild, fragment, nodeValue } from './utilities.js';
3
+ import slot from './slot/index.js';
4
+ let anchor, marker = firstChild.call(fragment(SLOT_HTML));
5
+ export default (parent, renderable) => {
10
6
  nodeValue.call(parent, '');
11
- prepend.call(parent, node = marker.cloneNode());
12
- return slot(node, renderable);
7
+ parent.append(anchor = marker.cloneNode());
8
+ return slot(anchor, renderable);
13
9
  };
@@ -0,0 +1,4 @@
1
+ import { SlotGroup } from '../types.js';
2
+ declare const ondisconnect: (element: Element, fn: VoidFunction) => void;
3
+ declare const remove: (groups: SlotGroup[]) => void;
4
+ export { ondisconnect, remove };
@@ -0,0 +1,51 @@
1
+ import queue from '@esportsplus/queue';
2
+ import { CLEANUP } from '../constants.js';
3
+ import { microtask, previousSibling } from '../utilities.js';
4
+ let cleanup = queue(64), scheduled = false;
5
+ function schedule() {
6
+ if (!cleanup.length || scheduled) {
7
+ return;
8
+ }
9
+ scheduled = true;
10
+ microtask.add(task);
11
+ }
12
+ function task() {
13
+ try {
14
+ let fns, fn, n = cleanup.length;
15
+ while ((fns = cleanup.next()) && n--) {
16
+ while (fn = fns.pop()) {
17
+ fn();
18
+ }
19
+ }
20
+ }
21
+ catch { }
22
+ if (cleanup.length) {
23
+ microtask.add(task);
24
+ }
25
+ else {
26
+ scheduled = false;
27
+ }
28
+ }
29
+ const ondisconnect = (element, fn) => {
30
+ (element[CLEANUP] ??= []).push(fn);
31
+ };
32
+ const remove = (groups) => {
33
+ let group, head, tail;
34
+ while (group = groups.pop()) {
35
+ head = group.head;
36
+ tail = group.tail || head;
37
+ for (let node = tail; node; node = previousSibling.call(node)) {
38
+ if (CLEANUP in node) {
39
+ cleanup.add(node[CLEANUP]);
40
+ }
41
+ node.remove();
42
+ if (head === node) {
43
+ break;
44
+ }
45
+ }
46
+ }
47
+ if (!scheduled && cleanup.length) {
48
+ schedule();
49
+ }
50
+ };
51
+ export { ondisconnect, remove };
@@ -0,0 +1,3 @@
1
+ import { Element } from '../types.js';
2
+ declare const _default: (anchor: Element, fn: Function) => void;
3
+ export default _default;
@@ -0,0 +1,51 @@
1
+ import { effect } from '@esportsplus/reactivity';
2
+ import { EMPTY_FRAGMENT, STATE_HYDRATING, STATE_NONE } from '../constants.js';
3
+ import { cloneNode, firstChild, lastChild, nodeValue, raf, text } from '../utilities.js';
4
+ import { ondisconnect } from '../slot/cleanup.js';
5
+ import { remove } from './cleanup.js';
6
+ import render from './render.js';
7
+ function update(anchor, fragment, value) {
8
+ let type = typeof value;
9
+ if (this.group) {
10
+ remove([this.group]);
11
+ this.group = undefined;
12
+ }
13
+ if (value == null || type !== 'object') {
14
+ let textnode = this.textnode;
15
+ if (textnode) {
16
+ nodeValue.call(textnode, String(value));
17
+ }
18
+ else {
19
+ textnode = this.textnode = text(String(value));
20
+ }
21
+ if (!textnode.isConnected) {
22
+ anchor.after(textnode);
23
+ }
24
+ }
25
+ else {
26
+ render(anchor, fragment, value);
27
+ this.group = {
28
+ head: firstChild.call(fragment),
29
+ tail: lastChild.call(fragment)
30
+ };
31
+ anchor.after(fragment);
32
+ }
33
+ }
34
+ export default (anchor, fn) => {
35
+ let context = {
36
+ group: undefined,
37
+ textnode: undefined
38
+ }, fragment = cloneNode.call(EMPTY_FRAGMENT), state = STATE_HYDRATING;
39
+ ondisconnect(anchor, effect(() => {
40
+ let value = fn();
41
+ if (state === STATE_HYDRATING) {
42
+ update.call(context, anchor, fragment, value);
43
+ state = STATE_NONE;
44
+ }
45
+ else if (state === STATE_NONE) {
46
+ raf.add(() => {
47
+ update.call(context, anchor, fragment, value);
48
+ });
49
+ }
50
+ }));
51
+ };
@@ -0,0 +1,3 @@
1
+ import { Element } from '../types.js';
2
+ declare const _default: (anchor: Element, value: unknown) => void;
3
+ export default _default;
@@ -0,0 +1,15 @@
1
+ import { EMPTY_FRAGMENT } from '../constants.js';
2
+ import { cloneNode } from '../utilities.js';
3
+ import effect from './effect.js';
4
+ import render from './render.js';
5
+ function slot(anchor, input) {
6
+ let fragment = cloneNode.call(EMPTY_FRAGMENT);
7
+ render(anchor, fragment, input);
8
+ anchor.after(fragment);
9
+ }
10
+ export default (anchor, value) => {
11
+ if (typeof value === 'function') {
12
+ return effect(anchor, value);
13
+ }
14
+ return slot(anchor, value);
15
+ };
@@ -0,0 +1,3 @@
1
+ import { RenderableReactive } from '../types.js';
2
+ declare const _default: (anchor: Element, renderable: RenderableReactive) => void;
3
+ export default _default;
@@ -0,0 +1,117 @@
1
+ import { root } from '@esportsplus/reactivity';
2
+ import { EMPTY_FRAGMENT } from '../constants.js';
3
+ import { append, cloneNode, firstChild, lastChild } from '../utilities.js';
4
+ import { remove } from './cleanup.js';
5
+ class ReactiveArraySlot {
6
+ array;
7
+ fragment = cloneNode.call(EMPTY_FRAGMENT);
8
+ marker;
9
+ nodes = [];
10
+ template;
11
+ constructor(anchor, array, template) {
12
+ let fragment = this.fragment;
13
+ this.array = array;
14
+ this.marker = anchor;
15
+ this.template = function (data, i) {
16
+ let frag = template.call(this, data, i).fragment, group = {
17
+ head: firstChild.call(frag),
18
+ tail: lastChild.call(frag)
19
+ };
20
+ append.call(fragment, frag);
21
+ return group;
22
+ };
23
+ let render = () => {
24
+ root(() => this.render());
25
+ };
26
+ array.on('clear', () => this.clear());
27
+ array.on('reverse', render);
28
+ array.on('pop', () => this.pop());
29
+ array.on('push', ({ items }) => {
30
+ root(() => this.push(items));
31
+ });
32
+ array.on('shift', () => this.shift());
33
+ array.on('sort', render);
34
+ array.on('splice', ({ deleteCount, items, start }) => {
35
+ root(() => this.splice(start, deleteCount, ...items));
36
+ });
37
+ array.on('unshift', ({ items }) => {
38
+ root(() => this.unshift(items));
39
+ });
40
+ if (array.length) {
41
+ render();
42
+ }
43
+ }
44
+ get length() {
45
+ return this.nodes.length;
46
+ }
47
+ set length(n) {
48
+ if (n >= this.nodes.length) {
49
+ return;
50
+ }
51
+ else if (n === 0) {
52
+ this.clear();
53
+ }
54
+ else {
55
+ this.splice(n);
56
+ }
57
+ }
58
+ anchor(index = this.nodes.length - 1) {
59
+ let node = this.nodes[index];
60
+ if (node) {
61
+ return node.tail || node.head;
62
+ }
63
+ return this.marker;
64
+ }
65
+ clear() {
66
+ remove(this.nodes);
67
+ }
68
+ pop() {
69
+ let group = this.nodes.pop();
70
+ if (group) {
71
+ remove([group]);
72
+ }
73
+ }
74
+ push(items) {
75
+ let anchor = this.anchor(), array = this.array;
76
+ for (let i = 0, n = items.length; i < n; i++) {
77
+ this.nodes.push(this.template.call(array, items[i], i));
78
+ }
79
+ anchor.after(this.fragment);
80
+ }
81
+ render() {
82
+ let nodes = this.nodes;
83
+ if (nodes.length) {
84
+ remove(nodes);
85
+ }
86
+ nodes = this.array.map(this.template);
87
+ this.marker.after(this.fragment);
88
+ }
89
+ shift() {
90
+ let group = this.nodes.shift();
91
+ if (group) {
92
+ remove([group]);
93
+ }
94
+ }
95
+ splice(start, stop = this.nodes.length, ...items) {
96
+ if (!items.length) {
97
+ return remove(this.nodes.splice(start, stop));
98
+ }
99
+ let array = this.array, n = items.length, nodes = new Array(n);
100
+ for (let i = 0; i < n; i++) {
101
+ nodes[i] = this.template.call(array, items[i], i);
102
+ }
103
+ remove(this.nodes.splice(start, stop, ...nodes));
104
+ this.anchor(start - 1).after(this.fragment);
105
+ }
106
+ unshift(items) {
107
+ let array = this.array, n = items.length, nodes = new Array(n);
108
+ for (let i = 0; i < n; i++) {
109
+ nodes[i] = this.template.call(array, items[i], i);
110
+ }
111
+ this.nodes.unshift(...nodes);
112
+ this.marker.after(this.fragment);
113
+ }
114
+ }
115
+ export default (anchor, renderable) => {
116
+ new ReactiveArraySlot(anchor, renderable.array, renderable.template);
117
+ };
@@ -0,0 +1,2 @@
1
+ import { Element, Fragment } from '../types.js';
2
+ export default function render(anchor: Element, fragment: Fragment, input: unknown): void;
@@ -0,0 +1,58 @@
1
+ import { isArray } from '@esportsplus/utilities';
2
+ import { RENDERABLE, RENDERABLE_ARRAY, RENDERABLE_FRAGMENT, RENDERABLE_HTML_FRAGMENT, RENDERABLE_HTML_REACTIVE_ARRAY, RENDERABLE_NODE, RENDERABLE_NODE_LIST, RENDERABLE_TEXT, RENDERABLE_VOID } from '../constants.js';
3
+ import { append, text } from '../utilities.js';
4
+ import reactive from './reactive.js';
5
+ function type(input) {
6
+ if (input === false || input == null || input === '') {
7
+ return RENDERABLE_VOID;
8
+ }
9
+ if (typeof input !== 'object') {
10
+ return RENDERABLE_TEXT;
11
+ }
12
+ if (RENDERABLE in input) {
13
+ return input[RENDERABLE];
14
+ }
15
+ if (isArray(input)) {
16
+ return RENDERABLE_ARRAY;
17
+ }
18
+ let nodeType = input.nodeType;
19
+ if (nodeType === 11) {
20
+ return RENDERABLE_FRAGMENT;
21
+ }
22
+ if (nodeType !== undefined) {
23
+ return RENDERABLE_NODE;
24
+ }
25
+ if (input instanceof NodeList) {
26
+ return RENDERABLE_NODE_LIST;
27
+ }
28
+ }
29
+ export default function render(anchor, fragment, input) {
30
+ let t = type(input);
31
+ switch (t) {
32
+ case RENDERABLE_VOID:
33
+ return;
34
+ case RENDERABLE_TEXT:
35
+ append.call(fragment, text(String(input)));
36
+ return;
37
+ case RENDERABLE_HTML_FRAGMENT:
38
+ append.call(fragment, input.fragment);
39
+ return;
40
+ case RENDERABLE_HTML_REACTIVE_ARRAY:
41
+ return reactive(anchor, input);
42
+ case RENDERABLE_ARRAY:
43
+ for (let i = 0, n = input.length; i < n; i++) {
44
+ render(anchor, fragment, input[i]);
45
+ }
46
+ return;
47
+ case RENDERABLE_FRAGMENT:
48
+ append.call(fragment, input);
49
+ return;
50
+ case RENDERABLE_NODE:
51
+ append.call(fragment, input);
52
+ return;
53
+ case RENDERABLE_NODE_LIST:
54
+ append.call(fragment, ...input);
55
+ return;
56
+ }
57
+ }
58
+ ;
package/build/svg.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import html from './html/index.js';
2
- import { RenderableTemplate } from './types.js';
3
2
  declare const svg: typeof html & {
4
- sprite: <T>(symbol: string) => RenderableTemplate<T>;
3
+ sprite: (symbol: string) => ReturnType<typeof html>;
5
4
  };
6
5
  export default svg;
package/build/types.d.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  import { ReactiveArray } from '@esportsplus/reactivity';
2
- import { RENDERABLE, RENDERABLE_REACTIVE, RENDERABLE_TEMPLATE } from './constants.js';
2
+ import { RENDERABLE, RENDERABLE_HTML_FRAGMENT, RENDERABLE_HTML_REACTIVE_ARRAY } from './constants.js';
3
3
  import { firstChild } from './utilities.js';
4
4
  import attributes from './attributes.js';
5
- import slot from './slot.js';
5
+ import slot from './slot/index.js';
6
+ import html from './html/index.js';
6
7
  type Attribute = Primitive | Effect<Primitive | Primitive[]>;
7
8
  type Attributes = {
8
9
  class?: Attribute | Attribute[];
@@ -17,25 +18,28 @@ type Attributes = {
17
18
  onrender?: (element: Element) => void;
18
19
  } & Record<PropertyKey, unknown>;
19
20
  type Effect<T> = () => EffectResponse<T>;
20
- type EffectResponse<T> = T extends [] ? EffectResponse<T[number]>[] : Primitive | Renderable<T>;
21
+ type EffectResponse<T> = T extends [] ? (Primitive | Renderable)[] : Primitive | Renderable;
21
22
  type Element = HTMLElement & Attributes & Record<PropertyKey, unknown>;
22
23
  type Elements = Element[];
23
- type Fragment = DocumentFragment | Node;
24
+ type Fragment = (DocumentFragment | Node) & Record<PropertyKey, unknown>;
24
25
  type Primitive = bigint | boolean | null | number | string | undefined;
25
- type Renderable<T = unknown> = RenderableReactive<T> | RenderableTemplate<T>;
26
- type RenderableReactive<T = unknown> = Readonly<{
27
- [RENDERABLE]: typeof RENDERABLE_REACTIVE;
28
- literals: null;
29
- template: (this: ThisParameterType<Parameters<ReactiveArray<T>['map']>[0]>, ...args: Parameters<Parameters<ReactiveArray<T>['map']>[0]>) => RenderableTemplate<T>;
30
- values: ReactiveArray<T>;
26
+ type Renderable = Fragment | RenderableReactive;
27
+ type RenderableReactive = Readonly<{
28
+ [RENDERABLE]: typeof RENDERABLE_HTML_REACTIVE_ARRAY;
29
+ array: ReactiveArray<unknown[]>;
30
+ template: (this: ReactiveArray<unknown[]>, ...args: Parameters<Parameters<ReactiveArray<unknown[]>['map']>[0]>) => ReturnType<typeof html>;
31
31
  }>;
32
- type RenderableTemplate<T = unknown> = {
33
- [RENDERABLE]: typeof RENDERABLE_TEMPLATE;
32
+ type RenderableTemplate = {
33
+ [RENDERABLE]: typeof RENDERABLE_HTML_FRAGMENT;
34
+ fragment: Fragment;
34
35
  literals: TemplateStringsArray;
35
- template: Template | null;
36
- values: (RenderableValue<T> | RenderableValue<T>[])[];
37
36
  };
38
- type RenderableValue<T = unknown> = Attributes | Readonly<Attributes> | Readonly<Attributes[]> | Effect<T> | Primitive | Renderable;
37
+ type RenderableValue<T = unknown> = Attributes | Readonly<Attributes> | Readonly<Attributes[]> | Effect<T> | Fragment | Primitive | RenderableReactive;
38
+ type RenderableValues = RenderableValue | RenderableValue[];
39
+ type SlotGroup = {
40
+ head: Element;
41
+ tail: Element;
42
+ };
39
43
  type Template = {
40
44
  fragment: DocumentFragment;
41
45
  html: string;
@@ -50,4 +54,4 @@ type Template = {
50
54
  slot: number;
51
55
  }[] | null;
52
56
  };
53
- export type { Attributes, Effect, Element, Elements, Fragment, Renderable, RenderableReactive, RenderableTemplate, RenderableValue, Template };
57
+ export type { Attributes, Effect, Element, Elements, Fragment, Renderable, RenderableReactive, RenderableTemplate, RenderableValue, RenderableValues, SlotGroup, Template };
@@ -12,17 +12,17 @@ declare const className: (v: any) => void;
12
12
  declare const innerHTML: (v: any) => void;
13
13
  declare const firstElementChild: () => any;
14
14
  declare const nextElementSibling: () => any;
15
- declare const prepend: (...nodes: (Node | string)[]) => void;
16
15
  declare const removeAttribute: (qualifiedName: string) => void;
17
16
  declare const setAttribute: (qualifiedName: string, value: string) => void;
18
17
  declare const cloneNode: (subtree?: boolean) => Node;
19
18
  declare const firstChild: () => any;
19
+ declare const lastChild: () => any;
20
20
  declare const nextSibling: () => any;
21
21
  declare const nodeValue: (v: any) => void;
22
22
  declare const parentElement: () => any;
23
- declare const parentNode: () => any;
23
+ declare const previousSibling: () => any;
24
24
  declare const fragment: (html: string) => DocumentFragment;
25
25
  declare const microtask: import("@esportsplus/tasks/build/factory").Scheduler;
26
26
  declare const raf: import("@esportsplus/tasks/build/factory").Scheduler;
27
27
  declare const text: (value: string) => E;
28
- export { addEventListener, append, className, cloneNode, firstChild, firstElementChild, fragment, innerHTML, microtask, nextElementSibling, nextSibling, nodeValue, parentElement, parentNode, prepend, raf, removeAttribute, removeEventListener, setAttribute, text };
28
+ export { addEventListener, append, className, cloneNode, firstChild, firstElementChild, fragment, innerHTML, lastChild, microtask, nextElementSibling, nextSibling, nodeValue, parentElement, previousSibling, raf, removeAttribute, removeEventListener, setAttribute, text };
@@ -1,24 +1,24 @@
1
1
  import { micro as m, raf as r } from '@esportsplus/tasks';
2
- let prototype, template = document.createElement('template'), t = document.createTextNode('');
2
+ let getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor, prototype, template = document.createElement('template'), t = document.createTextNode('');
3
3
  prototype = DocumentFragment.prototype;
4
4
  const append = prototype.append;
5
5
  prototype = Element.prototype;
6
6
  const addEventListener = prototype.addEventListener;
7
7
  const removeEventListener = prototype.removeEventListener;
8
- const className = Object.getOwnPropertyDescriptor(prototype, 'className').set;
9
- const innerHTML = Object.getOwnPropertyDescriptor(prototype, 'innerHTML').set;
10
- const firstElementChild = Object.getOwnPropertyDescriptor(prototype, 'firstElementChild').get;
11
- const nextElementSibling = Object.getOwnPropertyDescriptor(prototype, 'nextElementSibling').get;
12
- const prepend = prototype.prepend;
8
+ const className = getOwnPropertyDescriptor(prototype, 'className').set;
9
+ const innerHTML = getOwnPropertyDescriptor(prototype, 'innerHTML').set;
10
+ const firstElementChild = getOwnPropertyDescriptor(prototype, 'firstElementChild').get;
11
+ const nextElementSibling = getOwnPropertyDescriptor(prototype, 'nextElementSibling').get;
13
12
  const removeAttribute = prototype.removeAttribute;
14
13
  const setAttribute = prototype.setAttribute;
15
14
  prototype = Node.prototype;
16
15
  const cloneNode = prototype.cloneNode;
17
- const firstChild = Object.getOwnPropertyDescriptor(prototype, 'firstChild').get;
18
- const nextSibling = Object.getOwnPropertyDescriptor(prototype, 'nextSibling').get;
19
- const nodeValue = Object.getOwnPropertyDescriptor(prototype, 'nodeValue').set;
20
- const parentElement = Object.getOwnPropertyDescriptor(prototype, 'parentElement').get;
21
- const parentNode = Object.getOwnPropertyDescriptor(prototype, 'parentNode').get;
16
+ const firstChild = getOwnPropertyDescriptor(prototype, 'firstChild').get;
17
+ const lastChild = getOwnPropertyDescriptor(prototype, 'lastChild').get;
18
+ const nextSibling = getOwnPropertyDescriptor(prototype, 'nextSibling').get;
19
+ const nodeValue = getOwnPropertyDescriptor(prototype, 'nodeValue').set;
20
+ const parentElement = getOwnPropertyDescriptor(prototype, 'parentElement').get;
21
+ const previousSibling = getOwnPropertyDescriptor(prototype, 'previousSibling').get;
22
22
  const fragment = (html) => {
23
23
  innerHTML.call(template, html);
24
24
  let { content } = template;
@@ -34,4 +34,4 @@ const text = (value) => {
34
34
  }
35
35
  return element;
36
36
  };
37
- export { addEventListener, append, className, cloneNode, firstChild, firstElementChild, fragment, innerHTML, microtask, nextElementSibling, nextSibling, nodeValue, parentElement, parentNode, prepend, raf, removeAttribute, removeEventListener, setAttribute, text };
37
+ export { addEventListener, append, className, cloneNode, firstChild, firstElementChild, fragment, innerHTML, lastChild, microtask, nextElementSibling, nextSibling, nodeValue, parentElement, previousSibling, raf, removeAttribute, removeEventListener, setAttribute, text };
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "author": "ICJR",
3
3
  "dependencies": {
4
4
  "@esportsplus/queue": "^0.1.0",
5
- "@esportsplus/reactivity": "^0.13.2",
5
+ "@esportsplus/reactivity": "^0.15.0",
6
6
  "@esportsplus/tasks": "^0.2.1",
7
7
  "@esportsplus/utilities": "^0.22.1"
8
8
  },
@@ -14,7 +14,7 @@
14
14
  "private": false,
15
15
  "type": "module",
16
16
  "types": "./build/index.d.ts",
17
- "version": "0.16.1",
17
+ "version": "0.17.0",
18
18
  "scripts": {
19
19
  "build": "tsc && tsc-alias",
20
20
  "-": "-"
package/src/attributes.ts CHANGED
@@ -1,24 +1,18 @@
1
1
  import { effect } from '@esportsplus/reactivity';
2
- import { isArray, isFunction, isObject, isString } from '@esportsplus/utilities';
3
- import { ondisconnect } from './slot';
2
+ import { isArray, isObject, isString } from '@esportsplus/utilities';
3
+ import { ondisconnect } from './slot/cleanup';
4
+ import { STATE_HYDRATING, STATE_NONE, STATE_WAITING } from './constants';
4
5
  import { Attributes, Element } from './types';
5
6
  import { className, raf, removeAttribute, setAttribute } from './utilities';
6
7
  import q from '@esportsplus/queue';
7
8
  import event from './event';
8
9
 
9
10
 
10
- const EFFECT_KEY = Symbol();
11
+ const EFFECT = Symbol();
11
12
 
12
- const STORE_KEY = Symbol();
13
+ const STORE = Symbol();
13
14
 
14
- const UPDATES_KEY = Symbol();
15
-
16
-
17
- const STATE_HYDRATING = 0;
18
-
19
- const STATE_NONE = 1;
20
-
21
- const STATE_WAITING = 2;
15
+ const UPDATES = Symbol();
22
16
 
23
17
 
24
18
  type Context = {
@@ -64,19 +58,20 @@ function schedule() {
64
58
  }
65
59
 
66
60
  function set(context: Context, name: string, value: unknown, state: State) {
67
- if (isArray(value)) {
68
- for (let i = 0, n = value.length; i < n; i++) {
69
- set(context, name, value[i], state);
70
- }
61
+ if (value === false || value == null) {
62
+ value = '';
71
63
  }
72
- else if (isFunction(value)) {
64
+
65
+ let type = typeof value;
66
+
67
+ if (type === 'function') {
73
68
  if (name.startsWith('on')) {
74
- event(context.element, name as `on${string}`, value);
69
+ event(context.element, name as `on${string}`, value as Function);
75
70
  }
76
71
  else {
77
- context.store[EFFECT_KEY] ??= 0;
72
+ context.store[EFFECT] ??= 0;
78
73
 
79
- let id = (context.store[EFFECT_KEY] as number)++;
74
+ let id = (context.store[EFFECT] as number)++;
80
75
 
81
76
  ondisconnect(
82
77
  context.element,
@@ -107,6 +102,13 @@ function set(context: Context, name: string, value: unknown, state: State) {
107
102
  state = STATE_NONE;
108
103
  }
109
104
  }
105
+ else if (type === 'object') {
106
+ if (isArray(value)) {
107
+ for (let i = 0, n = value.length; i < n; i++) {
108
+ set(context, name, value[i], state);
109
+ }
110
+ }
111
+ }
110
112
  else {
111
113
  update(context, null, name, value, state);
112
114
  }
@@ -232,15 +234,20 @@ function update(
232
234
 
233
235
 
234
236
  const spread = function (element: Element, value: Attributes | Attributes[]) {
235
- let cache = (element[STORE_KEY] ??= { [UPDATES_KEY]: {} }) as Record<PropertyKey, unknown>,
237
+ let cache = (element[STORE] ??= { [UPDATES]: {} }) as Record<PropertyKey, unknown>,
236
238
  context = {
237
239
  element,
238
240
  store: cache,
239
- updates: cache[UPDATES_KEY] as Record<PropertyKey, unknown>,
241
+ updates: cache[UPDATES] as Record<PropertyKey, unknown>,
240
242
  updating: false
241
243
  };
242
244
 
243
- if (isArray(value)) {
245
+ if (isObject(value)) {
246
+ for (let name in value) {
247
+ set(context, name, value[name], STATE_HYDRATING);
248
+ }
249
+ }
250
+ else if (isArray(value)) {
244
251
  for (let i = 0, n = value.length; i < n; i++) {
245
252
  let v = value[i];
246
253
 
@@ -249,11 +256,6 @@ const spread = function (element: Element, value: Attributes | Attributes[]) {
249
256
  }
250
257
  }
251
258
  }
252
- else if (isObject(value)) {
253
- for (let name in value) {
254
- set(context, name, value[name], STATE_HYDRATING);
255
- }
256
- }
257
259
  };
258
260
 
259
261