@esportsplus/template 0.26.6 → 0.28.1

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 (44) hide show
  1. package/.github/workflows/publish.yml +2 -2
  2. package/build/attributes.js +7 -7
  3. package/build/constants.d.ts +2 -3
  4. package/build/constants.js +3 -4
  5. package/build/event/index.js +7 -4
  6. package/build/event/ontick.js +3 -3
  7. package/build/html/index.d.ts +4 -3
  8. package/build/html/index.js +3 -7
  9. package/build/html/parser.js +60 -52
  10. package/build/index.d.ts +1 -0
  11. package/build/render.js +4 -5
  12. package/build/slot/array.d.ts +25 -3
  13. package/build/slot/array.js +123 -48
  14. package/build/slot/cleanup.js +2 -2
  15. package/build/slot/effect.d.ts +12 -3
  16. package/build/slot/effect.js +14 -10
  17. package/build/slot/index.js +2 -2
  18. package/build/slot/render.js +15 -7
  19. package/build/types.d.ts +3 -10
  20. package/build/types.js +1 -1
  21. package/build/utilities/marker.d.ts +2 -0
  22. package/build/utilities/marker.js +4 -0
  23. package/build/utilities/raf.d.ts +2 -0
  24. package/build/utilities/raf.js +1 -0
  25. package/package.json +7 -4
  26. package/src/attributes.ts +9 -9
  27. package/src/constants.ts +6 -8
  28. package/src/event/index.ts +9 -4
  29. package/src/event/ontick.ts +3 -3
  30. package/src/html/index.ts +5 -9
  31. package/src/html/parser.ts +21 -7
  32. package/src/index.ts +1 -0
  33. package/src/render.ts +5 -8
  34. package/src/slot/array.ts +172 -65
  35. package/src/slot/cleanup.ts +2 -2
  36. package/src/slot/effect.ts +17 -10
  37. package/src/slot/index.ts +2 -2
  38. package/src/slot/render.ts +20 -9
  39. package/src/types.ts +3 -11
  40. package/src/utilities/marker.ts +6 -0
  41. package/src/utilities/raf.ts +1 -0
  42. package/build/utilities/queue.d.ts +0 -2
  43. package/build/utilities/queue.js +0 -3
  44. package/src/utilities/queue.ts +0 -7
@@ -12,5 +12,5 @@ on:
12
12
  jobs:
13
13
  publish:
14
14
  secrets:
15
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN_PUBLISHING }}
16
- uses: esportsplus/workflows/.github/workflows/publish.yml@main
15
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
16
+ uses: esportsplus/workflows/.github/workflows/publish.yml@main
@@ -2,8 +2,8 @@ import { effect } from '@esportsplus/reactivity';
2
2
  import { isArray, isObject } from '@esportsplus/utilities';
3
3
  import { STATE_HYDRATING, STATE_NONE, STATE_WAITING } from './constants.js';
4
4
  import { className, removeAttribute, setAttribute } from './utilities/element.js';
5
- import { raf } from './utilities/queue.js';
6
5
  import q from '@esportsplus/queue';
6
+ import raf from './utilities/raf.js';
7
7
  import event from './event/index.js';
8
8
  const STORE = Symbol();
9
9
  let delimiters = {
@@ -17,7 +17,7 @@ function apply(element, name, value) {
17
17
  else if (name === 'class') {
18
18
  className.call(element, value);
19
19
  }
20
- else if (name === 'style' || name.startsWith('data-') || 'ownerSVGElement' in element) {
20
+ else if (name === 'style' || (name[0] === 'd' && name.startsWith('data-')) || element['ownerSVGElement']) {
21
21
  setAttribute.call(element, name, value);
22
22
  }
23
23
  else {
@@ -52,13 +52,13 @@ function list(ctx, element, id, name, state, value) {
52
52
  continue;
53
53
  }
54
54
  dynamic.add(part);
55
- hot[part] = null;
55
+ hot[part] = true;
56
56
  }
57
57
  }
58
58
  let cold = store[id];
59
59
  if (cold !== undefined) {
60
60
  for (let part in cold) {
61
- if (part in hot) {
61
+ if (hot[part] === true) {
62
62
  continue;
63
63
  }
64
64
  dynamic.delete(part);
@@ -106,7 +106,7 @@ function schedule(ctx, element, name, state, value) {
106
106
  return;
107
107
  }
108
108
  scheduled = true;
109
- raf.add(task);
109
+ raf(task);
110
110
  }
111
111
  function task() {
112
112
  let context, n = queue.length;
@@ -119,7 +119,7 @@ function task() {
119
119
  context.updating = false;
120
120
  }
121
121
  if (queue.length) {
122
- raf.add(task);
122
+ raf(task);
123
123
  }
124
124
  else {
125
125
  scheduled = false;
@@ -128,7 +128,7 @@ function task() {
128
128
  const set = (element, name, value) => {
129
129
  let fn = name === 'class' || name === 'style' ? list : property, state = STATE_HYDRATING;
130
130
  if (typeof value === 'function') {
131
- if (name.startsWith('on')) {
131
+ if (name[0] === 'o' && name[1] === 'n') {
132
132
  return event(element, name, value);
133
133
  }
134
134
  let ctx = context(element);
@@ -1,3 +1,4 @@
1
+ declare const ARRAY_SLOT: unique symbol;
1
2
  declare const CLEANUP: unique symbol;
2
3
  declare const EMPTY_FRAGMENT: DocumentFragment;
3
4
  declare const NODE_CLOSING = 1;
@@ -9,11 +10,9 @@ declare const REGEX_EMPTY_TEXT_NODES: RegExp;
9
10
  declare const REGEX_EVENTS: RegExp;
10
11
  declare const REGEX_SLOT_ATTRIBUTES: RegExp;
11
12
  declare const REGEX_SLOT_NODES: RegExp;
12
- declare const RENDERABLE: unique symbol;
13
- declare const RENDERABLE_HTML_REACTIVE_ARRAY = 1;
14
13
  declare const SLOT_HTML = "<!--$-->";
15
14
  declare const SLOT_MARKER = "{{$}}";
16
15
  declare const STATE_HYDRATING = 0;
17
16
  declare const STATE_NONE = 1;
18
17
  declare const STATE_WAITING = 2;
19
- export { CLEANUP, EMPTY_FRAGMENT, NODE_CLOSING, NODE_ELEMENT, NODE_SLOT, NODE_VOID, NODE_WHITELIST, REGEX_EMPTY_TEXT_NODES, REGEX_EVENTS, REGEX_SLOT_ATTRIBUTES, REGEX_SLOT_NODES, RENDERABLE, RENDERABLE_HTML_REACTIVE_ARRAY, SLOT_HTML, SLOT_MARKER, STATE_HYDRATING, STATE_NONE, STATE_WAITING };
18
+ export { ARRAY_SLOT, CLEANUP, EMPTY_FRAGMENT, NODE_CLOSING, NODE_ELEMENT, NODE_SLOT, NODE_VOID, NODE_WHITELIST, REGEX_EMPTY_TEXT_NODES, REGEX_EVENTS, REGEX_SLOT_ATTRIBUTES, REGEX_SLOT_NODES, SLOT_HTML, STATE_HYDRATING, SLOT_MARKER, STATE_NONE, STATE_WAITING };
@@ -1,5 +1,6 @@
1
1
  import { fragment } from './utilities/fragment.js';
2
- const CLEANUP = Symbol();
2
+ const ARRAY_SLOT = Symbol('template.array.slot');
3
+ const CLEANUP = Symbol('template.cleanup');
3
4
  const EMPTY_FRAGMENT = fragment('');
4
5
  const NODE_CLOSING = 1;
5
6
  const NODE_COMMENT = 2;
@@ -30,11 +31,9 @@ const REGEX_EMPTY_TEXT_NODES = /(>|}|\s)\s+(<|{|\s)/g;
30
31
  const REGEX_EVENTS = /(?:\s*on[\w-:]+\s*=(?:\s*["'][^"']*["'])*)/g;
31
32
  const REGEX_SLOT_ATTRIBUTES = /<[\w-]+([^><]*{{\$}}[^><]*)>/g;
32
33
  const REGEX_SLOT_NODES = /<([\w-]+|[\/!])(?:([^><]*{{\$}}[^><]*)|(?:[^><]*))?>|{{\$}}/g;
33
- const RENDERABLE = Symbol();
34
- const RENDERABLE_HTML_REACTIVE_ARRAY = 1;
35
34
  const SLOT_HTML = '<!--$-->';
36
35
  const SLOT_MARKER = '{{$}}';
37
36
  const STATE_HYDRATING = 0;
38
37
  const STATE_NONE = 1;
39
38
  const STATE_WAITING = 2;
40
- export { CLEANUP, EMPTY_FRAGMENT, NODE_CLOSING, NODE_ELEMENT, NODE_SLOT, NODE_VOID, NODE_WHITELIST, REGEX_EMPTY_TEXT_NODES, REGEX_EVENTS, REGEX_SLOT_ATTRIBUTES, REGEX_SLOT_NODES, RENDERABLE, RENDERABLE_HTML_REACTIVE_ARRAY, SLOT_HTML, SLOT_MARKER, STATE_HYDRATING, STATE_NONE, STATE_WAITING };
39
+ export { ARRAY_SLOT, CLEANUP, EMPTY_FRAGMENT, NODE_CLOSING, NODE_ELEMENT, NODE_SLOT, NODE_VOID, NODE_WHITELIST, REGEX_EMPTY_TEXT_NODES, REGEX_EVENTS, REGEX_SLOT_ATTRIBUTES, REGEX_SLOT_NODES, SLOT_HTML, STATE_HYDRATING, SLOT_MARKER, STATE_NONE, STATE_WAITING };
@@ -7,9 +7,11 @@ import onconnect from './onconnect.js';
7
7
  import onresize from './onresize.js';
8
8
  import ontick from './ontick.js';
9
9
  let capture = new Set(['onblur', 'onfocus', 'onscroll']), controllers = new Map(), keys = {}, passive = new Set([
10
+ 'onanimationend', 'onanimationiteration', 'onanimationstart',
10
11
  'onmousedown', 'onmouseenter', 'onmouseleave', 'onmousemove', 'onmouseout', 'onmouseover', 'onmouseup', 'onmousewheel',
12
+ 'onpointerenter', 'onpointerleave', 'onpointermove', 'onpointerout', 'onpointerover',
11
13
  'onscroll',
12
- 'ontouchcancel', 'ontouchend', 'ontouchleave', 'ontouchmove', 'ontouchstart',
14
+ 'ontouchcancel', 'ontouchend', 'ontouchleave', 'ontouchmove', 'ontouchstart', 'ontransitionend',
13
15
  'onwheel'
14
16
  ]);
15
17
  ['onmousemove', 'onmousewheel', 'onscroll', 'ontouchend', 'ontouchmove', 'ontouchstart', 'onwheel'].map(event => {
@@ -38,16 +40,17 @@ function register(element, event) {
38
40
  }
39
41
  let key = keys[event] = Symbol();
40
42
  addEventListener.call(window.document, event.slice(2), (e) => {
41
- let node = e.target;
43
+ let fn, node = e.target;
42
44
  while (node) {
43
- if (key in node) {
45
+ fn = node[key];
46
+ if (typeof fn === 'function') {
44
47
  defineProperty(e, 'currentTarget', {
45
48
  configurable: true,
46
49
  get() {
47
50
  return node || window.document;
48
51
  }
49
52
  });
50
- return node[key].call(node, e);
53
+ return fn.call(node, e);
51
54
  }
52
55
  node = parentElement.call(node);
53
56
  }
@@ -1,5 +1,5 @@
1
1
  import { STATE_HYDRATING, STATE_NONE } from '../constants.js';
2
- import { raf } from '../utilities/queue.js';
2
+ import raf from '../utilities/raf.js';
3
3
  let tasks = Object.assign(new Set(), { running: false });
4
4
  function tick() {
5
5
  if (tasks.size === 0) {
@@ -9,13 +9,13 @@ function tick() {
9
9
  for (let task of tasks) {
10
10
  task();
11
11
  }
12
- raf.add(tick);
12
+ raf(tick);
13
13
  }
14
14
  const add = (task) => {
15
15
  tasks.add(task);
16
16
  if (!tasks.running) {
17
17
  tasks.running = true;
18
- raf.add(tick);
18
+ raf(tick);
19
19
  }
20
20
  };
21
21
  const remove = (task) => {
@@ -1,8 +1,9 @@
1
1
  import { ReactiveArray } from '@esportsplus/reactivity';
2
- import { Attribute, Attributes, Renderable, RenderableReactive } from '../types.js';
3
- type Values<T> = Attribute | Attributes<any> | Renderable<T>;
2
+ import { Attribute, Attributes, Renderable } from '../types.js';
3
+ import { ArraySlot } from '../slot/array.js';
4
+ type Values<T> = Attribute | Attributes<any> | ArraySlot<T> | Renderable<T>;
4
5
  declare const html: {
5
6
  <T>(literals: TemplateStringsArray, ...values: (Values<T> | Values<T>[])[]): Node;
6
- reactive<T>(array: ReactiveArray<T>, template: RenderableReactive<T>["template"]): RenderableReactive<T>;
7
+ reactive<T>(arr: ReactiveArray<T>, template: (value: T) => ReturnType<typeof html>): ArraySlot<T>;
7
8
  };
8
9
  export default html;
@@ -1,5 +1,5 @@
1
- import { RENDERABLE, RENDERABLE_HTML_REACTIVE_ARRAY } from '../constants.js';
2
1
  import { cloneNode } from '../utilities/node.js';
2
+ import { ArraySlot } from '../slot/array.js';
3
3
  import parser from './parser.js';
4
4
  const html = (literals, ...values) => {
5
5
  let { fragment, slots } = parser.parse(literals), clone = cloneNode.call(fragment, true);
@@ -23,11 +23,7 @@ const html = (literals, ...values) => {
23
23
  }
24
24
  return clone;
25
25
  };
26
- html.reactive = (array, template) => {
27
- return {
28
- [RENDERABLE]: RENDERABLE_HTML_REACTIVE_ARRAY,
29
- array,
30
- template
31
- };
26
+ html.reactive = (arr, template) => {
27
+ return new ArraySlot(arr, template);
32
28
  };
33
29
  export default html;
@@ -18,8 +18,9 @@ function build(literals) {
18
18
  path: []
19
19
  }], parsed = html.split(SLOT_MARKER), slot = 0, slots = [];
20
20
  {
21
- let attribute = '', buffer = '', char = '', quote = '';
22
- for (let match of html.matchAll(REGEX_SLOT_ATTRIBUTES)) {
21
+ let attribute = '', buffer = '', char = '', match, quote = '';
22
+ REGEX_SLOT_ATTRIBUTES.lastIndex = 0;
23
+ while (match = REGEX_SLOT_ATTRIBUTES.exec(html)) {
23
24
  let found = match[1], metadata = attributes[found];
24
25
  if (metadata) {
25
26
  continue;
@@ -71,58 +72,62 @@ function build(literals) {
71
72
  }
72
73
  }
73
74
  }
74
- for (let match of html.matchAll(REGEX_SLOT_NODES)) {
75
- let parent = levels[level], type = match[1] === undefined ? NODE_SLOT : (NODE_WHITELIST[match[1].toLowerCase()] || NODE_ELEMENT);
76
- if ((match.index || 1) - 1 > index) {
77
- parent.children++;
78
- }
79
- if (type === NODE_ELEMENT || type === NODE_VOID) {
80
- let attr = match[2], path = parent.path.length
81
- ? methods(parent.elements, parent.path, firstElementChild, nextElementSibling)
82
- : methods(parent.children, [], firstChild, nextSibling);
83
- if (attr) {
84
- let metadata = attributes[attr];
85
- if (!metadata) {
86
- throw new Error(`Template: attribute metadata could not be found for '${attr}'`);
75
+ REGEX_SLOT_NODES.lastIndex = 0;
76
+ {
77
+ let match;
78
+ while (match = REGEX_SLOT_NODES.exec(html)) {
79
+ let parent = levels[level], type = match[1] === undefined ? NODE_SLOT : (NODE_WHITELIST[match[1].toLowerCase()] || NODE_ELEMENT);
80
+ if ((match.index || 1) - 1 > index) {
81
+ parent.children++;
82
+ }
83
+ if (type === NODE_ELEMENT || type === NODE_VOID) {
84
+ let attr = match[2], path = parent.path.length
85
+ ? methods(parent.elements, parent.path, firstElementChild, nextElementSibling)
86
+ : methods(parent.children, [], firstChild, nextSibling);
87
+ if (attr) {
88
+ let metadata = attributes[attr];
89
+ if (!metadata) {
90
+ throw new Error(`Template: attribute metadata could not be found for '${attr}'`);
91
+ }
92
+ for (let i = 0, n = metadata.length; i < n; i++) {
93
+ let name = metadata[i];
94
+ slots.push({
95
+ fn: name === null ? a.spread : a.set,
96
+ name,
97
+ path
98
+ });
99
+ buffer += parsed[slot++];
100
+ }
87
101
  }
88
- for (let i = 0, n = metadata.length; i < n; i++) {
89
- let name = metadata[i];
90
- slots.push({
91
- fn: name === null ? a.spread : a.set,
92
- name,
102
+ if (type === NODE_ELEMENT) {
103
+ levels[++level] = {
104
+ children: 0,
105
+ elements: 0,
93
106
  path
94
- });
95
- buffer += parsed[slot++];
107
+ };
96
108
  }
109
+ parent.elements++;
97
110
  }
98
- if (type === NODE_ELEMENT) {
99
- levels[++level] = {
100
- children: 0,
101
- elements: 0,
102
- path
103
- };
111
+ else if (type === NODE_SLOT) {
112
+ buffer += parsed[slot++] + SLOT_HTML;
113
+ slots.push({
114
+ fn: s,
115
+ name: null,
116
+ path: methods(parent.children, parent.path, firstChild, nextSibling)
117
+ });
104
118
  }
105
- parent.elements++;
106
- }
107
- else if (type === NODE_SLOT) {
108
- buffer += parsed[slot++] + SLOT_HTML;
109
- slots.push({
110
- fn: s,
111
- name: null,
112
- path: methods(parent.children, parent.path, firstChild, nextSibling)
113
- });
114
- }
115
- if (n === slot) {
116
- buffer += parsed[slot];
117
- break;
118
- }
119
- if (type === NODE_CLOSING) {
120
- level--;
121
- }
122
- else {
123
- parent.children++;
119
+ if (n === slot) {
120
+ buffer += parsed[slot];
121
+ break;
122
+ }
123
+ if (type === NODE_CLOSING) {
124
+ level--;
125
+ }
126
+ else {
127
+ parent.children++;
128
+ }
129
+ index = (match.index || 0) + match[0].length;
124
130
  }
125
- index = (match.index || 0) + match[0].length;
126
131
  }
127
132
  if (events) {
128
133
  buffer = buffer.replace(REGEX_EVENTS, '');
@@ -130,10 +135,13 @@ function build(literals) {
130
135
  return set(literals, buffer, slots);
131
136
  }
132
137
  function methods(children, copy, first, next) {
133
- let methods = copy.slice();
134
- methods.push(first);
135
- for (let start = 0; start < children; start++) {
136
- methods.push(next);
138
+ let length = copy.length, methods = new Array(length + 1 + children);
139
+ for (let i = 0, n = length; i < n; i++) {
140
+ methods[i] = copy[i];
141
+ }
142
+ methods[length] = first;
143
+ for (let i = 0, n = children; i < n; i++) {
144
+ methods[length + 1 + i] = next;
137
145
  }
138
146
  return methods;
139
147
  }
package/build/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { default as html } from './html/index.js';
2
2
  export { default as render } from './render.js';
3
3
  export { default as svg } from './svg.js';
4
+ export type { ArraySlot } from './slot/array.js';
4
5
  export type { Attributes, Element, Renderable } from './types.js';
package/build/render.js CHANGED
@@ -1,10 +1,9 @@
1
- import { SLOT_HTML } from './constants.js';
2
- import { fragment } from './utilities/fragment.js';
3
- import { firstChild, nodeValue } from './utilities/node.js';
1
+ import { nodeValue } from './utilities/node.js';
2
+ import marker from './utilities/marker.js';
4
3
  import slot from './slot/index.js';
5
- let anchor, marker = firstChild.call(fragment(SLOT_HTML));
6
4
  export default (parent, renderable) => {
5
+ let anchor = marker.cloneNode();
7
6
  nodeValue.call(parent, '');
8
- parent.append(anchor = marker.cloneNode());
7
+ parent.append(anchor);
9
8
  slot(anchor, renderable);
10
9
  };
@@ -1,3 +1,25 @@
1
- import { RenderableReactive } from '../types.js';
2
- declare const _default: <T>(anchor: Element, renderable: RenderableReactive<T>) => Node;
3
- export default _default;
1
+ import { ReactiveArray } from '@esportsplus/reactivity';
2
+ import html from '../html/index.js';
3
+ declare class ArraySlot<T> {
4
+ private array;
5
+ private queue;
6
+ private marker;
7
+ private nodes;
8
+ private scheduled;
9
+ private signal;
10
+ private template;
11
+ readonly fragment: Node;
12
+ constructor(array: ReactiveArray<T>, template: ((value: T) => ReturnType<typeof html>));
13
+ private anchor;
14
+ private clear;
15
+ private pop;
16
+ private push;
17
+ private schedule;
18
+ private shift;
19
+ private sort;
20
+ private splice;
21
+ private sync;
22
+ private unshift;
23
+ get length(): number;
24
+ }
25
+ export { ArraySlot };
@@ -1,22 +1,28 @@
1
- import { root } from '@esportsplus/reactivity';
2
- import { EMPTY_FRAGMENT } from '../constants.js';
1
+ import { read, root, set, signal } from '@esportsplus/reactivity';
2
+ import { ARRAY_SLOT, EMPTY_FRAGMENT } from '../constants.js';
3
3
  import { append } from '../utilities/fragment.js';
4
- import { cloneNode, firstChild, lastChild } from '../utilities/node.js';
4
+ import { cloneNode, firstChild, lastChild, nextSibling } from '../utilities/node.js';
5
5
  import { ondisconnect, remove } from './cleanup.js';
6
+ import marker from '../utilities/marker.js';
7
+ import raf from '../utilities/raf.js';
6
8
  class ArraySlot {
7
9
  array;
8
- fragment;
10
+ queue = [];
9
11
  marker;
10
12
  nodes = [];
13
+ scheduled = false;
14
+ signal;
11
15
  template;
12
- constructor(anchor, array, template) {
13
- let fragment = this.fragment = cloneNode.call(EMPTY_FRAGMENT);
16
+ fragment;
17
+ constructor(array, template) {
14
18
  this.array = array;
15
- this.marker = anchor;
16
- this.template = function (data, i) {
19
+ let fragment = this.fragment = cloneNode.call(EMPTY_FRAGMENT);
20
+ this.marker = marker.cloneNode();
21
+ this.signal = signal(array.length);
22
+ this.template = function (data) {
17
23
  let dispose, frag = root((d) => {
18
24
  dispose = d;
19
- return template(data, i);
25
+ return template(data);
20
26
  }), group = {
21
27
  head: firstChild.call(frag),
22
28
  tail: lastChild.call(frag)
@@ -25,39 +31,41 @@ class ArraySlot {
25
31
  ondisconnect(group.head, dispose);
26
32
  return group;
27
33
  };
28
- array.on('clear', () => this.clear());
29
- array.on('reverse', () => {
30
- root(() => this.render());
34
+ append.call(fragment, this.marker);
35
+ if (array.length) {
36
+ root(() => {
37
+ this.nodes = array.map(this.template);
38
+ });
39
+ }
40
+ array.on('clear', () => {
41
+ this.queue.length = 0;
42
+ this.schedule({ op: 'clear' });
43
+ });
44
+ array.on('concat', ({ items }) => {
45
+ this.schedule({ items, op: 'concat' });
46
+ });
47
+ array.on('pop', () => {
48
+ this.schedule({ op: 'pop' });
31
49
  });
32
- array.on('pop', () => this.pop());
33
50
  array.on('push', ({ items }) => {
34
- root(() => this.push(items));
51
+ this.schedule({ items, op: 'push' });
35
52
  });
36
- array.on('shift', () => this.shift());
37
- array.on('sort', () => {
38
- root(() => this.render());
53
+ array.on('reverse', () => {
54
+ this.schedule({ op: 'reverse' });
55
+ });
56
+ array.on('shift', () => {
57
+ this.schedule({ op: 'shift' });
58
+ });
59
+ array.on('sort', ({ order }) => {
60
+ this.schedule({ op: 'sort', order });
39
61
  });
40
62
  array.on('splice', ({ deleteCount, items, start }) => {
41
- root(() => this.splice(start, deleteCount, ...items));
63
+ this.schedule({ deleteCount, items, op: 'splice', start });
42
64
  });
43
65
  array.on('unshift', ({ items }) => {
44
- root(() => this.unshift(items));
66
+ this.schedule({ items, op: 'unshift' });
45
67
  });
46
68
  }
47
- get length() {
48
- return this.nodes.length;
49
- }
50
- set length(n) {
51
- if (n >= this.nodes.length) {
52
- return;
53
- }
54
- else if (n === 0) {
55
- this.clear();
56
- }
57
- else {
58
- this.splice(n);
59
- }
60
- }
61
69
  anchor(index = this.nodes.length - 1) {
62
70
  let node = this.nodes[index];
63
71
  if (node) {
@@ -79,12 +87,53 @@ class ArraySlot {
79
87
  this.nodes.push(...items.map(this.template));
80
88
  anchor.after(this.fragment);
81
89
  }
82
- render() {
83
- if (this.nodes.length) {
84
- remove(...this.nodes.splice(0));
90
+ schedule(op) {
91
+ this.queue.push(op);
92
+ if (this.scheduled) {
93
+ return;
85
94
  }
86
- this.nodes = this.array.map(this.template);
87
- this.marker.after(this.fragment);
95
+ this.scheduled = true;
96
+ raf(() => {
97
+ let queue = this.queue;
98
+ this.queue.length = 0;
99
+ root(() => {
100
+ for (let i = 0, n = queue.length; i < n; i++) {
101
+ let op = queue[i];
102
+ switch (op.op) {
103
+ case 'clear':
104
+ this.clear();
105
+ break;
106
+ case 'concat':
107
+ this.push(op.items);
108
+ break;
109
+ case 'pop':
110
+ this.pop();
111
+ break;
112
+ case 'push':
113
+ this.push(op.items);
114
+ break;
115
+ case 'reverse':
116
+ this.nodes.reverse();
117
+ this.sync();
118
+ break;
119
+ case 'shift':
120
+ this.shift();
121
+ break;
122
+ case 'sort':
123
+ this.sort(op.order);
124
+ break;
125
+ case 'splice':
126
+ this.splice(op.start, op.deleteCount, op.items);
127
+ break;
128
+ case 'unshift':
129
+ this.unshift(op.items);
130
+ break;
131
+ }
132
+ }
133
+ });
134
+ set(this.signal, this.nodes.length);
135
+ this.scheduled = false;
136
+ });
88
137
  }
89
138
  shift() {
90
139
  let group = this.nodes.shift();
@@ -92,7 +141,22 @@ class ArraySlot {
92
141
  remove(group);
93
142
  }
94
143
  }
95
- splice(start, stop = this.nodes.length, ...items) {
144
+ sort(order) {
145
+ let nodes = this.nodes, n = nodes.length;
146
+ if (n !== order.length) {
147
+ remove(...nodes.splice(0));
148
+ this.nodes = this.array.map(this.template);
149
+ this.marker.after(this.fragment);
150
+ return;
151
+ }
152
+ let sorted = new Array(n);
153
+ for (let i = 0; i < n; i++) {
154
+ sorted[i] = nodes[order[i]];
155
+ }
156
+ this.nodes = sorted;
157
+ this.sync();
158
+ }
159
+ splice(start, stop = this.nodes.length, items) {
96
160
  if (!items.length) {
97
161
  remove(...this.nodes.splice(start, stop));
98
162
  return;
@@ -100,17 +164,28 @@ class ArraySlot {
100
164
  remove(...this.nodes.splice(start, stop, ...items.map(this.template)));
101
165
  this.anchor(start - 1).after(this.fragment);
102
166
  }
167
+ sync() {
168
+ let nodes = this.nodes, n = nodes.length;
169
+ if (!n) {
170
+ return;
171
+ }
172
+ for (let i = 0; i < n; i++) {
173
+ let group = nodes[i], next, node = group.head;
174
+ while (node) {
175
+ next = node === group.tail ? null : nextSibling.call(node);
176
+ append.call(this.fragment, node);
177
+ node = next;
178
+ }
179
+ }
180
+ this.marker.after(this.fragment);
181
+ }
103
182
  unshift(items) {
104
183
  this.nodes.unshift(...items.map(this.template));
105
184
  this.marker.after(this.fragment);
106
185
  }
107
- }
108
- export default (anchor, renderable) => {
109
- let array = renderable.array, slot = new ArraySlot(anchor, array, renderable.template);
110
- if (array.length) {
111
- root(() => {
112
- slot.nodes = array.map(slot.template);
113
- });
186
+ get length() {
187
+ return read(this.signal);
114
188
  }
115
- return slot.fragment;
116
- };
189
+ }
190
+ Object.defineProperty(ArraySlot.prototype, ARRAY_SLOT, { value: true });
191
+ export { ArraySlot };
@@ -7,8 +7,8 @@ const remove = (...groups) => {
7
7
  for (let i = 0, n = groups.length; i < n; i++) {
8
8
  let fns, fn, group = groups[i], head = group.head, next, tail = group.tail || head;
9
9
  while (tail) {
10
- if (CLEANUP in tail) {
11
- fns = tail[CLEANUP];
10
+ fns = tail[CLEANUP];
11
+ if (fns !== undefined) {
12
12
  while (fn = fns.pop()) {
13
13
  fn();
14
14
  }
@@ -1,3 +1,12 @@
1
- import { Element, Renderable } from '../types.js';
2
- declare const _default: (anchor: Element, fn: (dispose?: VoidFunction) => Renderable<any>) => void;
3
- export default _default;
1
+ import { Element, Renderable, SlotGroup } from '../types.js';
2
+ declare class EffectSlot {
3
+ anchor: Element;
4
+ disposer: VoidFunction;
5
+ group: SlotGroup | null;
6
+ scheduled: boolean;
7
+ textnode: Node | null;
8
+ constructor(anchor: Element, fn: (dispose?: VoidFunction) => Renderable<any>);
9
+ dispose(): void;
10
+ update(value: unknown): void;
11
+ }
12
+ export { EffectSlot };