@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
@@ -1,7 +1,7 @@
1
1
  import { effect } from '@esportsplus/reactivity';
2
2
  import { firstChild, lastChild, nodeValue } from '../utilities/node.js';
3
- import { raf } from '../utilities/queue.js';
4
3
  import { remove } from './cleanup.js';
4
+ import raf from '../utilities/raf.js';
5
5
  import text from '../utilities/text.js';
6
6
  import render from './render.js';
7
7
  function read(value) {
@@ -17,17 +17,20 @@ class EffectSlot {
17
17
  anchor;
18
18
  disposer;
19
19
  group = null;
20
+ scheduled = false;
20
21
  textnode = null;
21
22
  constructor(anchor, fn) {
22
- let dispose = fn.length ? () => this.dispose() : undefined;
23
+ let dispose = fn.length ? () => this.dispose() : undefined, value;
23
24
  this.anchor = anchor;
24
25
  this.disposer = effect(() => {
25
- let value = read(fn(dispose));
26
+ value = read(fn(dispose));
26
27
  if (!this.disposer) {
27
28
  this.update(value);
28
29
  }
29
- else {
30
- raf.add(() => {
30
+ else if (!this.scheduled) {
31
+ this.scheduled = true;
32
+ raf(() => {
33
+ this.scheduled = false;
31
34
  this.update(value);
32
35
  });
33
36
  }
@@ -53,14 +56,17 @@ class EffectSlot {
53
56
  this.group = null;
54
57
  }
55
58
  if (typeof value !== 'object') {
59
+ if (typeof value !== 'string') {
60
+ value = String(value);
61
+ }
56
62
  if (textnode) {
57
- nodeValue.call(textnode, String(value));
63
+ nodeValue.call(textnode, value);
58
64
  if (!textnode.isConnected) {
59
65
  anchor.after(textnode);
60
66
  }
61
67
  }
62
68
  else {
63
- anchor.after(this.textnode = text(String(value)));
69
+ anchor.after(this.textnode = text(value));
64
70
  }
65
71
  }
66
72
  else {
@@ -78,6 +84,4 @@ class EffectSlot {
78
84
  }
79
85
  }
80
86
  }
81
- export default (anchor, fn) => {
82
- new EffectSlot(anchor, fn);
83
- };
87
+ export { EffectSlot };
@@ -1,8 +1,8 @@
1
- import effect from './effect.js';
1
+ import { EffectSlot } from './effect.js';
2
2
  import render from './render.js';
3
3
  export default (anchor, value) => {
4
4
  if (typeof value === 'function') {
5
- effect(anchor, value);
5
+ new EffectSlot(anchor, value);
6
6
  }
7
7
  else {
8
8
  anchor.after(render(anchor, value));
@@ -1,9 +1,8 @@
1
1
  import { isArray } from '@esportsplus/utilities';
2
- import { EMPTY_FRAGMENT, RENDERABLE } from '../constants.js';
2
+ import { ARRAY_SLOT, EMPTY_FRAGMENT } from '../constants.js';
3
3
  import { cloneNode, lastChild } from '../utilities/node.js';
4
4
  import { append } from '../utilities/fragment.js';
5
5
  import text from '../utilities/text.js';
6
- import array from './array.js';
7
6
  export default function render(anchor, value) {
8
7
  if (value == null || value === false || value === '') {
9
8
  return EMPTY_FRAGMENT;
@@ -11,15 +10,24 @@ export default function render(anchor, value) {
11
10
  if (typeof value !== 'object') {
12
11
  return text(value);
13
12
  }
14
- if (RENDERABLE in value) {
15
- return array(anchor, value);
13
+ if (value[ARRAY_SLOT] === true) {
14
+ return value.fragment;
16
15
  }
17
- if ('nodeType' in value) {
16
+ if (value.nodeType !== undefined) {
18
17
  return value;
19
18
  }
19
+ let n = value.length;
20
+ if (typeof n === 'number') {
21
+ if (n === 0) {
22
+ return EMPTY_FRAGMENT;
23
+ }
24
+ else if (n === 1) {
25
+ return render(anchor, value[0]);
26
+ }
27
+ }
20
28
  if (isArray(value)) {
21
29
  let fragment = cloneNode.call(EMPTY_FRAGMENT);
22
- for (let i = 0, n = value.length; i < n; i++) {
30
+ for (let i = 0; i < n; i++) {
23
31
  append.call(fragment, render(anchor, value[i]));
24
32
  anchor = lastChild.call(fragment);
25
33
  }
@@ -27,7 +35,7 @@ export default function render(anchor, value) {
27
35
  }
28
36
  if (value instanceof NodeList) {
29
37
  let fragment = cloneNode.call(EMPTY_FRAGMENT);
30
- for (let i = 0, n = value.length; i < n; i++) {
38
+ for (let i = 0; i < n; i++) {
31
39
  append.call(fragment, value[i]);
32
40
  }
33
41
  return fragment;
package/build/types.d.ts CHANGED
@@ -1,9 +1,7 @@
1
- import { ReactiveArray } from '@esportsplus/reactivity';
2
- import { RENDERABLE, RENDERABLE_HTML_REACTIVE_ARRAY } from './constants.js';
3
1
  import { firstChild } from './utilities/node.js';
2
+ import { ArraySlot } from './slot/array.js';
4
3
  import attributes from './attributes.js';
5
4
  import slot from './slot/index.js';
6
- import html from './html/index.js';
7
5
  type Attribute = Effect<Primitive | Primitive[]> | ((...args: any[]) => void) | Primitive;
8
6
  type Attributes<T extends HTMLElement = Element> = {
9
7
  class?: Attribute | Attribute[];
@@ -20,12 +18,7 @@ type Attributes<T extends HTMLElement = Element> = {
20
18
  type Effect<T> = () => T extends [] ? Renderable<T>[] : Renderable<T>;
21
19
  type Element = HTMLElement & Attributes<any>;
22
20
  type Primitive = bigint | boolean | null | number | string | undefined;
23
- type Renderable<T> = DocumentFragment | Effect<T> | Node | NodeList | Primitive | RenderableReactive<T> | Renderable<T>[];
24
- type RenderableReactive<T> = Readonly<{
25
- [RENDERABLE]: typeof RENDERABLE_HTML_REACTIVE_ARRAY;
26
- array: ReactiveArray<T>;
27
- template: (value: T, i: number) => ReturnType<typeof html>;
28
- }>;
21
+ type Renderable<T> = DocumentFragment | ArraySlot<T> | Effect<T> | Node | NodeList | Primitive | Renderable<T>[];
29
22
  type SlotGroup = {
30
23
  head: Element;
31
24
  tail: Element;
@@ -40,4 +33,4 @@ type Template = {
40
33
  path: typeof firstChild[];
41
34
  }[] | null;
42
35
  };
43
- export type { Attribute, Attributes, Effect, Element, Renderable, RenderableReactive, SlotGroup, Template };
36
+ export type { Attribute, Attributes, Effect, Element, Renderable, SlotGroup, Template };
package/build/types.js CHANGED
@@ -1 +1 @@
1
- import { RENDERABLE } from './constants.js';
1
+ export {};
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,4 @@
1
+ import { SLOT_HTML } from '../constants.js';
2
+ import { fragment } from './fragment.js';
3
+ import { firstChild } from './node.js';
4
+ export default firstChild.call(fragment(SLOT_HTML));
@@ -0,0 +1,2 @@
1
+ declare const _default: typeof requestAnimationFrame;
2
+ export default _default;
@@ -0,0 +1 @@
1
+ export default globalThis?.requestAnimationFrame;
package/package.json CHANGED
@@ -1,9 +1,8 @@
1
1
  {
2
2
  "author": "ICJR",
3
3
  "dependencies": {
4
- "@esportsplus/queue": "^0.1.0",
5
- "@esportsplus/reactivity": "^0.18.1",
6
- "@esportsplus/tasks": "^0.4.0",
4
+ "@esportsplus/queue": "^0.2.0",
5
+ "@esportsplus/reactivity": "^0.22.0",
7
6
  "@esportsplus/utilities": "^0.25.0"
8
7
  },
9
8
  "devDependencies": {
@@ -12,9 +11,13 @@
12
11
  "main": "./build/index.js",
13
12
  "name": "@esportsplus/template",
14
13
  "private": false,
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://github.com/esportsplus/template"
17
+ },
15
18
  "type": "module",
16
19
  "types": "./build/index.d.ts",
17
- "version": "0.26.6",
20
+ "version": "0.28.1",
18
21
  "scripts": {
19
22
  "build": "tsc && tsc-alias",
20
23
  "-": "-"
package/src/attributes.ts CHANGED
@@ -3,8 +3,8 @@ import { isArray, isObject } from '@esportsplus/utilities';
3
3
  import { STATE_HYDRATING, STATE_NONE, STATE_WAITING } from './constants';
4
4
  import { Attributes, Element } from './types';
5
5
  import { className, removeAttribute, setAttribute } from './utilities/element';
6
- import { raf } from './utilities/queue';
7
6
  import q from '@esportsplus/queue';
7
+ import raf from './utilities/raf';
8
8
  import event from './event';
9
9
 
10
10
 
@@ -37,7 +37,7 @@ function apply(element: Element, name: string, value: unknown) {
37
37
  else if (name === 'class') {
38
38
  className.call(element, value as string);
39
39
  }
40
- else if (name === 'style' || name.startsWith('data-') || 'ownerSVGElement' in element) {
40
+ else if (name === 'style' || (name[0] === 'd' && name.startsWith('data-')) || element['ownerSVGElement']) {
41
41
  setAttribute.call(element, name, value as string);
42
42
  }
43
43
  else {
@@ -79,7 +79,7 @@ function list(
79
79
  }
80
80
  }
81
81
  else {
82
- let hot: Attributes = {};
82
+ let hot: Record<PropertyKey, true> = {};
83
83
 
84
84
  if (value && typeof value === 'string') {
85
85
  let part: string,
@@ -93,15 +93,15 @@ function list(
93
93
  }
94
94
 
95
95
  dynamic.add(part);
96
- hot[part] = null;
96
+ hot[part] = true;
97
97
  }
98
98
  }
99
99
 
100
- let cold = store[id] as Attributes | undefined;
100
+ let cold = store[id] as Record<PropertyKey, true> | undefined;
101
101
 
102
102
  if (cold !== undefined) {
103
103
  for (let part in cold) {
104
- if (part in hot) {
104
+ if (hot[part] === true) {
105
105
  continue;
106
106
  }
107
107
 
@@ -170,7 +170,7 @@ function schedule(ctx: Context | null, element: Element, name: string, state: St
170
170
  }
171
171
 
172
172
  scheduled = true;
173
- raf.add(task);
173
+ raf(task);
174
174
  }
175
175
 
176
176
  function task() {
@@ -189,7 +189,7 @@ function task() {
189
189
  }
190
190
 
191
191
  if (queue.length) {
192
- raf.add(task);
192
+ raf(task);
193
193
  }
194
194
  else {
195
195
  scheduled = false;
@@ -202,7 +202,7 @@ const set = (element: Element, name: string, value: unknown) => {
202
202
  state: State = STATE_HYDRATING;
203
203
 
204
204
  if (typeof value === 'function') {
205
- if (name.startsWith('on')) {
205
+ if (name[0] === 'o' && name[1] === 'n') {
206
206
  return event(element, name as `on${string}`, value as Function);
207
207
  }
208
208
 
package/src/constants.ts CHANGED
@@ -1,7 +1,10 @@
1
1
  import { fragment } from './utilities/fragment';
2
2
 
3
3
 
4
- const CLEANUP = Symbol();
4
+ const ARRAY_SLOT = Symbol('template.array.slot');
5
+
6
+
7
+ const CLEANUP = Symbol('template.cleanup');
5
8
 
6
9
 
7
10
  const EMPTY_FRAGMENT = fragment('');
@@ -49,11 +52,6 @@ const REGEX_SLOT_ATTRIBUTES = /<[\w-]+([^><]*{{\$}}[^><]*)>/g;
49
52
  const REGEX_SLOT_NODES = /<([\w-]+|[\/!])(?:([^><]*{{\$}}[^><]*)|(?:[^><]*))?>|{{\$}}/g;
50
53
 
51
54
 
52
- const RENDERABLE = Symbol();
53
-
54
- const RENDERABLE_HTML_REACTIVE_ARRAY = 1;
55
-
56
-
57
55
  const SLOT_HTML = '<!--$-->';
58
56
 
59
57
  const SLOT_MARKER = '{{$}}';
@@ -67,10 +65,10 @@ const STATE_WAITING = 2;
67
65
 
68
66
 
69
67
  export {
68
+ ARRAY_SLOT,
70
69
  CLEANUP,
71
70
  EMPTY_FRAGMENT,
72
71
  NODE_CLOSING, NODE_ELEMENT, NODE_SLOT, NODE_VOID, NODE_WHITELIST,
73
72
  REGEX_EMPTY_TEXT_NODES, REGEX_EVENTS, REGEX_SLOT_ATTRIBUTES, REGEX_SLOT_NODES,
74
- RENDERABLE, RENDERABLE_HTML_REACTIVE_ARRAY,
75
- SLOT_HTML, SLOT_MARKER, STATE_HYDRATING, STATE_NONE, STATE_WAITING
73
+ SLOT_HTML, STATE_HYDRATING, SLOT_MARKER, STATE_NONE, STATE_WAITING
76
74
  };
@@ -16,9 +16,11 @@ let capture = new Set<`on${string}`>(['onblur', 'onfocus', 'onscroll']),
16
16
  >(),
17
17
  keys: Record<string, symbol> = {},
18
18
  passive = new Set<`on${string}`>([
19
+ 'onanimationend', 'onanimationiteration', 'onanimationstart',
19
20
  'onmousedown', 'onmouseenter', 'onmouseleave', 'onmousemove', 'onmouseout', 'onmouseover', 'onmouseup', 'onmousewheel',
21
+ 'onpointerenter', 'onpointerleave', 'onpointermove', 'onpointerout', 'onpointerover',
20
22
  'onscroll',
21
- 'ontouchcancel', 'ontouchend', 'ontouchleave', 'ontouchmove', 'ontouchstart',
23
+ 'ontouchcancel', 'ontouchend', 'ontouchleave', 'ontouchmove', 'ontouchstart', 'ontransitionend',
22
24
  'onwheel'
23
25
  ]);
24
26
 
@@ -62,10 +64,13 @@ function register(element: Element, event: `on${string}`) {
62
64
  let key = keys[event] = Symbol();
63
65
 
64
66
  addEventListener.call(window.document, event.slice(2), (e) => {
65
- let node = e.target as Element | null;
67
+ let fn,
68
+ node = e.target as Element | null;
66
69
 
67
70
  while (node) {
68
- if (key in node) {
71
+ fn = node[key];
72
+
73
+ if (typeof fn === 'function') {
69
74
  defineProperty(e, 'currentTarget', {
70
75
  configurable: true,
71
76
  get() {
@@ -73,7 +78,7 @@ function register(element: Element, event: `on${string}`) {
73
78
  }
74
79
  });
75
80
 
76
- return (node[key] as Function).call(node, e);
81
+ return fn.call(node, e);
77
82
  }
78
83
 
79
84
  node = parentElement.call(node);
@@ -1,5 +1,5 @@
1
1
  import { STATE_HYDRATING, STATE_NONE } from '~/constants';
2
- import { raf } from '~/utilities/queue';
2
+ import raf from '~/utilities/raf';
3
3
 
4
4
 
5
5
  let tasks = Object.assign(new Set<VoidFunction>(), { running: false });
@@ -15,7 +15,7 @@ function tick() {
15
15
  task();
16
16
  }
17
17
 
18
- raf.add(tick);
18
+ raf(tick);
19
19
  }
20
20
 
21
21
 
@@ -24,7 +24,7 @@ const add = (task: VoidFunction) => {
24
24
 
25
25
  if (!tasks.running) {
26
26
  tasks.running = true;
27
- raf.add(tick);
27
+ raf(tick);
28
28
  }
29
29
  };
30
30
 
package/src/html/index.ts CHANGED
@@ -1,13 +1,13 @@
1
1
  import { ReactiveArray } from '@esportsplus/reactivity';
2
- import { RENDERABLE, RENDERABLE_HTML_REACTIVE_ARRAY } from '~/constants';
3
- import { Attribute, Attributes, Renderable, RenderableReactive } from '~/types';
2
+ import { Attribute, Attributes, Renderable } from '~/types';
4
3
  import { cloneNode } from '~/utilities/node';
4
+ import { ArraySlot } from '~/slot/array';
5
5
  import parser from './parser';
6
6
  import attributes from '~/attributes';
7
7
  import slot from '~/slot';
8
8
 
9
9
 
10
- type Values<T> = Attribute | Attributes<any> | Renderable<T>;
10
+ type Values<T> = Attribute | Attributes<any> | ArraySlot<T> | Renderable<T>;
11
11
 
12
12
 
13
13
  const html = <T>(literals: TemplateStringsArray, ...values: (Values<T> | Values<T>[])[]) => {
@@ -40,12 +40,8 @@ const html = <T>(literals: TemplateStringsArray, ...values: (Values<T> | Values<
40
40
  return clone;
41
41
  };
42
42
 
43
- html.reactive = <T>(array: ReactiveArray<T>, template: RenderableReactive<T>['template']): RenderableReactive<T> => {
44
- return {
45
- [RENDERABLE]: RENDERABLE_HTML_REACTIVE_ARRAY,
46
- array,
47
- template
48
- };
43
+ html.reactive = <T>(arr: ReactiveArray<T>, template: (value: T) => ReturnType<typeof html>) => {
44
+ return new ArraySlot(arr, template);
49
45
  };
50
46
 
51
47
 
@@ -42,9 +42,12 @@ function build(literals: TemplateStringsArray) {
42
42
  let attribute = '',
43
43
  buffer = '',
44
44
  char = '',
45
+ match: RegExpExecArray | null,
45
46
  quote = '';
46
47
 
47
- for (let match of html.matchAll(REGEX_SLOT_ATTRIBUTES)) {
48
+ REGEX_SLOT_ATTRIBUTES.lastIndex = 0;
49
+
50
+ while (match = REGEX_SLOT_ATTRIBUTES.exec(html)) {
48
51
  let found = match[1],
49
52
  metadata = attributes[found];
50
53
 
@@ -105,7 +108,12 @@ function build(literals: TemplateStringsArray) {
105
108
  }
106
109
  }
107
110
 
108
- for (let match of html.matchAll(REGEX_SLOT_NODES)) {
111
+ REGEX_SLOT_NODES.lastIndex = 0;
112
+
113
+ {
114
+ let match: RegExpExecArray | null;
115
+
116
+ while (match = REGEX_SLOT_NODES.exec(html)) {
109
117
  let parent = levels[level],
110
118
  type = match[1] === undefined ? NODE_SLOT : (NODE_WHITELIST[match[1].toLowerCase()] || NODE_ELEMENT);
111
119
 
@@ -171,7 +179,8 @@ function build(literals: TemplateStringsArray) {
171
179
  parent.children++;
172
180
  }
173
181
 
174
- index = (match.index || 0) + match[0].length;
182
+ index = (match.index || 0) + match[0].length;
183
+ }
175
184
  }
176
185
 
177
186
  if (events) {
@@ -182,12 +191,17 @@ function build(literals: TemplateStringsArray) {
182
191
  }
183
192
 
184
193
  function methods(children: number, copy: (typeof firstChild)[], first: (typeof firstChild), next: (typeof firstChild)) {
185
- let methods = copy.slice();
194
+ let length = copy.length,
195
+ methods: (typeof firstChild)[] = new Array(length + 1 + children);
196
+
197
+ for (let i = 0, n = length; i < n; i++) {
198
+ methods[i] = copy[i];
199
+ }
186
200
 
187
- methods.push(first);
201
+ methods[length] = first;
188
202
 
189
- for (let start = 0; start < children; start++) {
190
- methods.push(next);
203
+ for (let i = 0, n = children; i < n; i++) {
204
+ methods[length + 1 + i] = next;
191
205
  }
192
206
 
193
207
  return methods;
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { default as html } from './html';
2
2
  export { default as render } from './render';
3
3
  export { default as svg } from './svg';
4
+ export type { ArraySlot } from './slot/array';
4
5
  export type { Attributes, Element, Renderable } from './types';
package/src/render.ts CHANGED
@@ -1,17 +1,14 @@
1
- import { SLOT_HTML } from './constants';
2
1
  import { Renderable } from './types';
3
- import { fragment } from './utilities/fragment';
4
- import { firstChild, nodeValue } from './utilities/node';
2
+ import { nodeValue } from './utilities/node';
3
+ import marker from './utilities/marker';
5
4
  import slot from './slot';
6
5
 
7
6
 
8
- let anchor,
9
- marker = firstChild.call( fragment(SLOT_HTML) );
10
-
11
-
12
7
  export default <T>(parent: HTMLElement, renderable: Renderable<T>) => {
8
+ let anchor = marker.cloneNode();
9
+
13
10
  nodeValue.call(parent, '');
14
- parent.append(anchor = marker.cloneNode());
11
+ parent.append(anchor);
15
12
 
16
13
  slot(anchor, renderable);
17
14
  };