@esportsplus/template 0.15.20 → 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.
Files changed (45) hide show
  1. package/build/attributes.js +27 -23
  2. package/build/constants.d.ts +14 -3
  3. package/build/constants.js +15 -3
  4. package/build/event.js +7 -7
  5. package/build/html/cache.d.ts +2 -2
  6. package/build/html/cache.js +24 -16
  7. package/build/html/hydrate.d.ts +2 -6
  8. package/build/html/hydrate.js +29 -53
  9. package/build/html/index.d.ts +2106 -5
  10. package/build/html/index.js +12 -4
  11. package/build/render.d.ts +1 -2
  12. package/build/render.js +6 -10
  13. package/build/slot/cleanup.d.ts +4 -0
  14. package/build/slot/cleanup.js +51 -0
  15. package/build/slot/effect.d.ts +3 -0
  16. package/build/slot/effect.js +51 -0
  17. package/build/slot/index.d.ts +3 -0
  18. package/build/slot/index.js +15 -0
  19. package/build/slot/reactive.d.ts +3 -0
  20. package/build/slot/reactive.js +117 -0
  21. package/build/slot/render.d.ts +2 -0
  22. package/build/slot/render.js +58 -0
  23. package/build/svg.d.ts +1 -2
  24. package/build/types.d.ts +25 -21
  25. package/build/utilities.d.ts +3 -3
  26. package/build/utilities.js +12 -12
  27. package/package.json +2 -2
  28. package/src/attributes.ts +30 -28
  29. package/src/constants.ts +38 -4
  30. package/src/event.ts +8 -8
  31. package/src/html/cache.ts +28 -18
  32. package/src/html/hydrate.ts +38 -68
  33. package/src/html/index.ts +16 -8
  34. package/src/render.ts +8 -12
  35. package/src/slot/cleanup.ts +74 -0
  36. package/src/slot/effect.ts +73 -0
  37. package/src/slot/index.ts +23 -0
  38. package/src/slot/reactive.ts +167 -0
  39. package/src/slot/render.ts +81 -0
  40. package/src/svg.ts +1 -2
  41. package/src/types.ts +30 -20
  42. package/src/utilities.ts +15 -13
  43. package/build/slot.d.ts +0 -21
  44. package/build/slot.js +0 -200
  45. package/src/slot.ts +0 -287
package/src/constants.ts CHANGED
@@ -1,3 +1,12 @@
1
+ import { fragment } from './utilities';
2
+
3
+
4
+ const CLEANUP = Symbol();
5
+
6
+
7
+ const EMPTY_FRAGMENT = fragment('');
8
+
9
+
1
10
  const NODE_CLOSING = 1;
2
11
 
3
12
  const NODE_COMMENT = 2;
@@ -38,9 +47,21 @@ const REGEX_SLOT_NODES = /<([\w-]+|[\/!])(?:([^><]*{{\$}}[^><]*)|(?:[^><]*))?>|{
38
47
 
39
48
  const RENDERABLE = Symbol();
40
49
 
41
- const RENDERABLE_REACTIVE = Symbol();
50
+ const RENDERABLE_ARRAY = 0;
51
+
52
+ const RENDERABLE_FRAGMENT = 1;
53
+
54
+ const RENDERABLE_HTML_FRAGMENT = 2;
42
55
 
43
- const RENDERABLE_TEMPLATE = Symbol();
56
+ const RENDERABLE_HTML_REACTIVE_ARRAY = 3;
57
+
58
+ const RENDERABLE_NODE = 4;
59
+
60
+ const RENDERABLE_NODE_LIST = 5;
61
+
62
+ const RENDERABLE_TEXT = 6;
63
+
64
+ const RENDERABLE_VOID = 7;
44
65
 
45
66
 
46
67
  const SLOT_HTML = '<!--$-->';
@@ -50,9 +71,22 @@ const SLOT_MARKER = '{{$}}';
50
71
  const SLOT_MARKER_LENGTH = SLOT_MARKER.length;
51
72
 
52
73
 
74
+ const STATE_HYDRATING = 0;
75
+
76
+ const STATE_NONE = 1;
77
+
78
+ const STATE_WAITING = 2;
79
+
80
+
53
81
  export {
82
+ CLEANUP,
83
+ EMPTY_FRAGMENT,
54
84
  NODE_CLOSING, NODE_ELEMENT, NODE_SLOT, NODE_VOID, NODE_WHITELIST,
55
85
  REGEX_EMPTY_TEXT_NODES, REGEX_SLOT_NODES,
56
- RENDERABLE, RENDERABLE_REACTIVE, RENDERABLE_TEMPLATE,
57
- SLOT_HTML, SLOT_MARKER, SLOT_MARKER_LENGTH
86
+ RENDERABLE, RENDERABLE_ARRAY, RENDERABLE_FRAGMENT,
87
+ RENDERABLE_HTML_FRAGMENT, RENDERABLE_HTML_REACTIVE_ARRAY,
88
+ RENDERABLE_NODE, RENDERABLE_NODE_LIST, RENDERABLE_TEXT,
89
+ RENDERABLE_VOID,
90
+ SLOT_HTML, SLOT_MARKER, SLOT_MARKER_LENGTH,
91
+ STATE_HYDRATING, STATE_NONE, STATE_WAITING
58
92
  };
package/src/event.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { root } from '@esportsplus/reactivity';
2
2
  import { defineProperty } from '@esportsplus/utilities';
3
- import { ondisconnect } from './slot';
4
3
  import { Element } from './types';
5
4
  import { addEventListener, parentElement, raf } from './utilities';
5
+ import { ondisconnect } from './slot/cleanup';
6
6
 
7
7
 
8
8
  let capture = new Set<`on${string}`>(['onblur', 'onfocus', 'onscroll']),
@@ -91,15 +91,15 @@ export default (element: Element, event: `on${string}`, listener: Function): voi
91
91
  addEventListener.call(window.document, event.slice(2), (e) => {
92
92
  let node = e.target as Element | null;
93
93
 
94
- defineProperty(e, 'currentTarget', {
95
- configurable: true,
96
- get() {
97
- return node || window.document;
98
- }
99
- });
100
-
101
94
  while (node) {
102
95
  if (key in node) {
96
+ defineProperty(e, 'currentTarget', {
97
+ configurable: true,
98
+ get() {
99
+ return node || window.document;
100
+ }
101
+ });
102
+
103
103
  return (node[key] as Function).call(node, e);
104
104
  }
105
105
 
package/src/html/cache.ts CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  NODE_CLOSING, NODE_ELEMENT, NODE_SLOT, NODE_VOID, NODE_WHITELIST, REGEX_EMPTY_TEXT_NODES,
3
3
  REGEX_SLOT_NODES, SLOT_HTML, SLOT_MARKER, SLOT_MARKER_LENGTH
4
4
  } from '~/constants';
5
- import { RenderableTemplate, Template } from '~/types';
5
+ import { Template } from '~/types';
6
6
  import { firstChild, firstElementChild, fragment, nextElementSibling, nextSibling } from '~/utilities';
7
7
  import { spread } from '~/attributes';
8
8
  import s from '~/slot';
@@ -11,8 +11,10 @@ import s from '~/slot';
11
11
  let cache = new WeakMap<TemplateStringsArray, Template>();
12
12
 
13
13
 
14
- function build(literals: TemplateStringsArray, values: unknown[]) {
15
- if (values.length === 0) {
14
+ function build(literals: TemplateStringsArray) {
15
+ let n = literals.length - 1;
16
+
17
+ if (n === 0) {
16
18
  return set(literals, literals[0]);
17
19
  }
18
20
 
@@ -25,12 +27,11 @@ function build(literals: TemplateStringsArray, values: unknown[]) {
25
27
  levels = [{
26
28
  children: 0,
27
29
  elements: 0,
28
- path: [] as NonNullable<Template['slots']>[0]['path']
30
+ path: [] as NonNullable<Template['slots']>[number]['path']['parent']
29
31
  }],
30
32
  parsed = html.split(SLOT_MARKER),
31
33
  slot = 0,
32
- slots: Template['slots'] = [],
33
- total = values.length;
34
+ slots: Template['slots'] = [];
34
35
 
35
36
  for (let match of html.matchAll(REGEX_SLOT_NODES)) {
36
37
  let parent = levels[level],
@@ -42,17 +43,23 @@ function build(literals: TemplateStringsArray, values: unknown[]) {
42
43
  }
43
44
 
44
45
  if (type === NODE_ELEMENT || type === NODE_VOID) {
45
- let attr = match[2];
46
+ let attr = match[2],
47
+ path = parent.path.length
48
+ ? methods(parent.elements, parent.path, firstElementChild, nextElementSibling)
49
+ : methods(parent.children, [], firstChild, nextSibling);
46
50
 
47
51
  if (attr) {
48
52
  let i = attr.indexOf(SLOT_MARKER),
49
- path = methods(parent.children, parent.path, firstChild, nextSibling);
53
+ p = {
54
+ absolute: path,
55
+ parent: parent.path,
56
+ relative: path.slice(parent.path.length)
57
+ };
50
58
 
51
59
  while (i !== -1) {
52
60
  slots.push({
53
61
  fn: spread,
54
- parent: parent.path,
55
- path,
62
+ path: p,
56
63
  slot
57
64
  });
58
65
 
@@ -65,25 +72,28 @@ function build(literals: TemplateStringsArray, values: unknown[]) {
65
72
  levels[++level] = {
66
73
  children: 0,
67
74
  elements: 0,
68
- path: parent.path.length
69
- ? methods(parent.elements, parent.path, firstElementChild, nextElementSibling)
70
- : methods(parent.children, [], firstChild, nextSibling)
75
+ path
71
76
  };
72
77
  }
73
78
 
74
79
  parent.elements++;
75
80
  }
76
81
  else if (type === NODE_SLOT) {
82
+ let relative = methods(parent.children, [], firstChild, nextSibling);
83
+
77
84
  buffer += parsed[slot] + SLOT_HTML;
78
85
  slots.push({
79
86
  fn: s,
80
- parent: parent.path,
81
- path: methods(parent.children, [], firstChild, nextSibling),
87
+ path: {
88
+ absolute: [...parent.path, ...relative],
89
+ parent: parent.path,
90
+ relative
91
+ },
82
92
  slot: slot++
83
93
  });
84
94
  }
85
95
 
86
- if (slot === total) {
96
+ if (slot === n) {
87
97
  buffer += parsed[slot];
88
98
  break;
89
99
  }
@@ -127,8 +137,8 @@ function set(literals: TemplateStringsArray, html: string, slots: Template['slot
127
137
  }
128
138
 
129
139
 
130
- const get = <T>({ literals, values }: RenderableTemplate<T>) => {
131
- return cache.get(literals) || build(literals, values);
140
+ const get = (literals: TemplateStringsArray) => {
141
+ return cache.get(literals) || build(literals);
132
142
  };
133
143
 
134
144
 
@@ -1,83 +1,53 @@
1
- import { root } from '@esportsplus/reactivity';
2
- import { Element, Elements, HydrateResult, Renderable, RenderableReactive, RenderableTemplate, Template } from '~/types';
3
- import { Slot } from '~/slot';
4
- import { cloneNode, firstChild, nextSibling } from '~/utilities';
5
- import cache from './cache';
1
+ import { Fragment, RenderableValues, Template } from '~/types';
2
+ import { cloneNode } from '~/utilities';
6
3
 
7
4
 
8
- function reactive<T>(renderable: RenderableReactive<T>, slot: Slot) {
9
- let array = renderable.values,
10
- factory = renderable.template,
11
- refresh = () => {
12
- slot.render( root(() => array.map(template)) );
13
- },
14
- template = function(data, i) {
15
- let renderable = factory.call(this, data, i);
5
+ export default ({ fragment, slots }: Template, values: RenderableValues[]): Fragment => {
6
+ let clone = cloneNode.call(fragment, true);
16
7
 
17
- return hydrate<T>(renderable, cache.get(renderable));
18
- } as (this: typeof array, ...args: Parameters<Parameters<typeof array['map']>[0]>) => HydrateResult;
19
-
20
- array.on('pop', () => {
21
- slot.pop();
22
- });
23
- array.on('push', ({ items }) => {
24
- slot.push( ...root(() => array.map(template, array.length - items.length)) );
25
- });
26
- array.on('reverse', refresh);
27
- array.on('shift', () => {
28
- slot.shift();
29
- });
30
- array.on('sort', refresh);
31
- array.on('splice', ({ deleteCount: d, items: i, start: s }) => {
32
- slot.splice(s, d, ...root(() => array.map(template, s, i.length)));
33
- });
34
- array.on('unshift', ({ items }) => {
35
- slot.unshift( ...root(() => array.map(template, 0, items.length)) );
36
- });
37
-
38
- return array.map(template);
39
- }
8
+ if (slots === null) {
9
+ return clone as Fragment;
10
+ }
40
11
 
41
- function hydrate<T>(renderable: Renderable<T>, template: Template): HydrateResult {
42
- let elements: Elements = [],
43
- fragment = cloneNode.call(template.fragment, true),
44
- slots = template.slots;
12
+ let node,
13
+ nodePath,
14
+ parent,
15
+ parentPath;
45
16
 
46
- if (slots !== null) {
47
- let node,
48
- previous,
49
- values = renderable.values;
17
+ for (let i = 0, n = slots.length; i < n; i++) {
18
+ let { fn, path, slot } = slots[i],
19
+ pp = path.parent,
20
+ pr = path.relative;
50
21
 
51
- for (let i = slots.length - 1; i >= 0; i--) {
52
- let { fn, path, slot } = slots[i];
22
+ if (pp !== parentPath) {
23
+ if (pp === nodePath) {
24
+ parent = node;
25
+ parentPath = nodePath;
53
26
 
54
- if (path !== previous) {
55
- node = fragment;
56
- previous = path;
27
+ nodePath = undefined;
28
+ }
29
+ else {
30
+ parent = clone;
31
+ parentPath = pp;
57
32
 
58
- for (let o = 0, j = path.length; o < j; o++) {
59
- node = path[o].call(node as Element);
33
+ for (let i = 0, n = pp.length; i < n; i++) {
34
+ parent = pp[i].call(parent);
60
35
  }
61
36
  }
62
-
63
- // @ts-ignore
64
- fn(node, values[slot]);
65
37
  }
66
- }
67
38
 
68
- for (let element = firstChild.call(fragment as Element); element; element = nextSibling.call(element)) {
69
- elements.push(element);
70
- }
71
-
72
- return { elements, fragment };
73
- }
39
+ if (pr !== nodePath) {
40
+ node = parent;
41
+ nodePath = path.absolute;
74
42
 
43
+ for (let i = 0, n = pr.length; i < n; i++) {
44
+ node = pr[i].call(node);
45
+ }
46
+ }
75
47
 
76
- export default {
77
- reactive: <T>(renderable: RenderableReactive<T>, slot: Slot) => {
78
- return reactive(renderable, slot);
79
- },
80
- static: <T>(renderable: RenderableTemplate<T>) => {
81
- return hydrate(renderable, renderable.template || (renderable.template = cache.get(renderable)));
48
+ // @ts-ignore
49
+ fn(node, values[slot]);
82
50
  }
83
- };
51
+
52
+ return clone as Fragment;
53
+ }
package/src/html/index.ts CHANGED
@@ -1,17 +1,25 @@
1
1
  import { ReactiveArray } from '@esportsplus/reactivity';
2
- import { RENDERABLE, RENDERABLE_REACTIVE, RENDERABLE_TEMPLATE } from '~/constants';
3
- import { RenderableReactive, RenderableTemplate } from '~/types';
2
+ import { RENDERABLE, RENDERABLE_HTML_FRAGMENT, RENDERABLE_HTML_REACTIVE_ARRAY } from '~/constants';
3
+ import { RenderableReactive, RenderableTemplate, RenderableValues } from '~/types';
4
4
  import hydrate from './hydrate';
5
+ import cache from './cache';
5
6
 
6
7
 
7
- const html = <T>(literals: TemplateStringsArray, ...values: RenderableTemplate<T>['values']): RenderableTemplate<T> => {
8
- return { [RENDERABLE]: RENDERABLE_TEMPLATE, literals, template: null, values };
8
+ const html = (literals: TemplateStringsArray, ...values: RenderableValues[]): RenderableTemplate => {
9
+ return {
10
+ [RENDERABLE]: RENDERABLE_HTML_FRAGMENT,
11
+ fragment: hydrate(cache.get(literals), values),
12
+ literals
13
+ };
9
14
  };
10
15
 
11
- html.reactive = <T>(array: ReactiveArray<T>, template: RenderableReactive<T>['template']): RenderableReactive<T> => {
12
- return { [RENDERABLE]: RENDERABLE_REACTIVE, literals: null, template, values: array };
16
+ html.reactive = <T>(array: ReactiveArray<T[]>, template: RenderableReactive['template']) => {
17
+ return {
18
+ [RENDERABLE]: RENDERABLE_HTML_REACTIVE_ARRAY,
19
+ array,
20
+ template
21
+ };
13
22
  };
14
23
 
15
24
 
16
- export default html;
17
- export { hydrate };
25
+ export default html;
package/src/render.ts CHANGED
@@ -1,21 +1,17 @@
1
- import { isInstanceOf } from '@esportsplus/utilities';
2
1
  import { SLOT_HTML } from './constants';
3
- import slot, { Slot } from './slot';
4
2
  import { Renderable } from './types';
5
- import { firstChild, fragment, nodeValue, prepend } from './utilities';
3
+ import { firstChild, fragment, nodeValue } from './utilities';
4
+ import slot from './slot';
6
5
 
7
6
 
8
- let marker = firstChild.call( fragment(SLOT_HTML) ),
9
- node;
7
+ let anchor,
8
+ marker = firstChild.call( fragment(SLOT_HTML) );
10
9
 
11
10
 
12
- export default (renderable: Renderable, parent: HTMLElement | Slot) => {
13
- if (isInstanceOf(parent, Slot)) {
14
- return parent.render(renderable);
15
- }
16
-
11
+ export default (parent: HTMLElement, renderable: Renderable) => {
12
+ // parent.nodeValue = '';
17
13
  nodeValue.call(parent, '');
18
- prepend.call(parent, node = marker.cloneNode());
14
+ parent.append(anchor = marker.cloneNode());
19
15
 
20
- return slot(node, renderable);
16
+ return slot(anchor, renderable);
21
17
  };
@@ -0,0 +1,74 @@
1
+ import queue from '@esportsplus/queue';
2
+ import { CLEANUP } from '~/constants';
3
+ import { microtask, previousSibling } from '~/utilities';
4
+ import { SlotGroup } from '~/types';
5
+
6
+
7
+ let cleanup = queue<VoidFunction[]>(64),
8
+ scheduled = false;
9
+
10
+
11
+ function schedule() {
12
+ if (!cleanup.length || scheduled) {
13
+ return;
14
+ }
15
+
16
+ scheduled = true;
17
+ microtask.add(task);
18
+ }
19
+
20
+ function task() {
21
+ try {
22
+ let fns,
23
+ fn,
24
+ n = cleanup.length;
25
+
26
+ while ((fns = cleanup.next()) && n--) {
27
+ while (fn = fns.pop()) {
28
+ fn();
29
+ }
30
+ }
31
+ }
32
+ catch { }
33
+
34
+ if (cleanup.length) {
35
+ microtask.add(task);
36
+ }
37
+ else {
38
+ scheduled = false;
39
+ }
40
+ }
41
+
42
+
43
+ const ondisconnect = (element: Element, fn: VoidFunction) => {
44
+ ((element as any)[CLEANUP] ??= []).push(fn);
45
+ };
46
+
47
+ const remove = (groups: SlotGroup[]) => {
48
+ let group, head, tail;
49
+
50
+ while (group = groups.pop()) {
51
+ head = group.head;
52
+ tail = group.tail || head;
53
+
54
+ // for (let node: Element | null = tail; node; node = node.previousSibling as Element | null) {
55
+ for (let node = tail; node; node = previousSibling.call(node)) {
56
+ if (CLEANUP in node) {
57
+ cleanup.add( node[CLEANUP] as VoidFunction[] );
58
+ }
59
+
60
+ node.remove();
61
+
62
+ if (head === node) {
63
+ break;
64
+ }
65
+ }
66
+ }
67
+
68
+ if (!scheduled && cleanup.length) {
69
+ schedule();
70
+ }
71
+ };
72
+
73
+
74
+ export { ondisconnect, remove };
@@ -0,0 +1,73 @@
1
+ import { effect } from '@esportsplus/reactivity';
2
+ import { EMPTY_FRAGMENT, STATE_HYDRATING, STATE_NONE } from '~/constants';
3
+ import { Element, Fragment, SlotGroup } from '~/types';
4
+ import { cloneNode, firstChild, lastChild, nodeValue, raf, text } from '~/utilities'
5
+ import { ondisconnect } from '~/slot/cleanup';
6
+ import { remove } from './cleanup';
7
+ import render from './render';
8
+
9
+
10
+ function update(this: { group?: SlotGroup, textnode?: Element }, anchor: Element, fragment: Fragment, value: unknown) {
11
+ let type = typeof value;
12
+
13
+ if (this.group) {
14
+ remove([this.group]);
15
+ this.group = undefined;
16
+ }
17
+
18
+ if (value == null || type !== 'object') {
19
+ let textnode = this.textnode;
20
+
21
+ if (textnode) {
22
+ // textnode.nodeValue = String(value);
23
+ nodeValue.call(textnode, String(value));
24
+ }
25
+ else {
26
+ textnode = this.textnode = text( String(value) );
27
+ }
28
+
29
+ if (!textnode.isConnected) {
30
+ anchor.after(textnode);
31
+ }
32
+ }
33
+ else {
34
+ render(anchor, fragment, value);
35
+
36
+ this.group = {
37
+ // head: fragment.firstChild as Element,
38
+ // tail: fragment.lastChild as Element
39
+ head: firstChild.call(fragment),
40
+ tail: lastChild.call(fragment)
41
+ };
42
+
43
+ anchor.after(fragment);
44
+ }
45
+ }
46
+
47
+
48
+ export default (anchor: Element, fn: Function) => {
49
+ let context = {
50
+ group: undefined as SlotGroup | undefined,
51
+ textnode: undefined as Element | undefined
52
+ },
53
+ // fragment = EMPTY_FRAGMENT.cloneNode() as Fragment,
54
+ fragment = cloneNode.call(EMPTY_FRAGMENT) as Fragment,
55
+ state = STATE_HYDRATING;
56
+
57
+ ondisconnect(
58
+ anchor,
59
+ effect(() => {
60
+ let value = fn();
61
+
62
+ if (state === STATE_HYDRATING) {
63
+ update.call(context, anchor, fragment, value);
64
+ state = STATE_NONE;
65
+ }
66
+ else if (state === STATE_NONE) {
67
+ raf.add(() => {
68
+ update.call(context, anchor, fragment, value);
69
+ });
70
+ }
71
+ })
72
+ );
73
+ };
@@ -0,0 +1,23 @@
1
+ import { EMPTY_FRAGMENT } from '~/constants';
2
+ import { Element, Fragment } from '~/types';
3
+ import { cloneNode } from '~/utilities';
4
+ import effect from './effect';
5
+ import render from './render';
6
+
7
+
8
+ function slot(anchor: Element, input: unknown) {
9
+ let fragment = cloneNode.call(EMPTY_FRAGMENT) as Fragment;
10
+
11
+ render(anchor, fragment, input);
12
+
13
+ anchor.after(fragment);
14
+ }
15
+
16
+
17
+ export default (anchor: Element, value: unknown) => {
18
+ if (typeof value === 'function') {
19
+ return effect(anchor, value as Function);
20
+ }
21
+
22
+ return slot(anchor, value);
23
+ };