@esportsplus/template 0.20.4 → 0.22.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.
- package/build/attributes.d.ts +5 -1
- package/build/attributes.js +22 -22
- package/build/constants.d.ts +3 -2
- package/build/constants.js +3 -2
- package/build/event/index.js +4 -0
- package/build/event/onresize.d.ts +3 -0
- package/build/event/onresize.js +26 -0
- package/build/html/index.d.ts +2 -2
- package/build/html/index.js +2 -2
- package/build/html/parser.js +73 -13
- package/build/types.d.ts +6 -6
- package/package.json +1 -1
- package/src/attributes.ts +29 -29
- package/src/constants.ts +6 -5
- package/src/event/index.ts +5 -0
- package/src/event/onresize.ts +37 -0
- package/src/html/index.ts +4 -4
- package/src/html/parser.ts +93 -14
- package/src/types.ts +6 -6
package/build/attributes.d.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
import { STATE_HYDRATING, STATE_NONE, STATE_WAITING } from './constants.js';
|
|
1
2
|
import { Attributes, Element } from './types.js';
|
|
3
|
+
type State = typeof STATE_HYDRATING | typeof STATE_NONE | typeof STATE_WAITING;
|
|
4
|
+
declare const set: (element: Element, name: string, value: unknown, state?: State) => void;
|
|
2
5
|
declare const spread: (element: Element, value: Attributes | Attributes[]) => void;
|
|
3
6
|
declare const _default: {
|
|
7
|
+
set: (element: Element, name: string, value: unknown, state?: State) => void;
|
|
4
8
|
spread: (element: Element, value: Attributes | Attributes[]) => void;
|
|
5
9
|
};
|
|
6
10
|
export default _default;
|
|
7
|
-
export { spread };
|
|
11
|
+
export { set, spread };
|
package/build/attributes.js
CHANGED
|
@@ -102,7 +102,24 @@ function schedule(ctx, element, name, state, value) {
|
|
|
102
102
|
scheduled = true;
|
|
103
103
|
raf.add(task);
|
|
104
104
|
}
|
|
105
|
-
function
|
|
105
|
+
function task() {
|
|
106
|
+
let context, n = queue.length;
|
|
107
|
+
while ((context = queue.next()) && n--) {
|
|
108
|
+
let { element, updates } = context;
|
|
109
|
+
for (let name in updates) {
|
|
110
|
+
apply(element, name, updates[name]);
|
|
111
|
+
}
|
|
112
|
+
context.updates = {};
|
|
113
|
+
context.updating = false;
|
|
114
|
+
}
|
|
115
|
+
if (queue.length) {
|
|
116
|
+
raf.add(task);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
scheduled = false;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const set = (element, name, value, state = STATE_HYDRATING) => {
|
|
106
123
|
let fn = name === 'class' || name === 'style' ? list : property, type = typeof value;
|
|
107
124
|
if (type === 'function') {
|
|
108
125
|
if (name.startsWith('on')) {
|
|
@@ -144,24 +161,7 @@ function set(element, name, value, state) {
|
|
|
144
161
|
return;
|
|
145
162
|
}
|
|
146
163
|
fn(null, element, null, name, value, state);
|
|
147
|
-
}
|
|
148
|
-
function task() {
|
|
149
|
-
let context, n = queue.length;
|
|
150
|
-
while ((context = queue.next()) && n--) {
|
|
151
|
-
let { element, updates } = context;
|
|
152
|
-
for (let name in updates) {
|
|
153
|
-
apply(element, name, updates[name]);
|
|
154
|
-
}
|
|
155
|
-
context.updates = {};
|
|
156
|
-
context.updating = false;
|
|
157
|
-
}
|
|
158
|
-
if (queue.length) {
|
|
159
|
-
raf.add(task);
|
|
160
|
-
}
|
|
161
|
-
else {
|
|
162
|
-
scheduled = false;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
164
|
+
};
|
|
165
165
|
const spread = function (element, value) {
|
|
166
166
|
if (isObject(value)) {
|
|
167
167
|
for (let name in value) {
|
|
@@ -169,7 +169,7 @@ const spread = function (element, value) {
|
|
|
169
169
|
if (v == null || v === false || v === '') {
|
|
170
170
|
continue;
|
|
171
171
|
}
|
|
172
|
-
set(element, name, v
|
|
172
|
+
set(element, name, v);
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
175
|
else if (isArray(value)) {
|
|
@@ -178,5 +178,5 @@ const spread = function (element, value) {
|
|
|
178
178
|
}
|
|
179
179
|
}
|
|
180
180
|
};
|
|
181
|
-
export default { spread };
|
|
182
|
-
export { spread };
|
|
181
|
+
export default { set, spread };
|
|
182
|
+
export { set, spread };
|
package/build/constants.d.ts
CHANGED
|
@@ -6,13 +6,14 @@ declare const NODE_SLOT = 4;
|
|
|
6
6
|
declare const NODE_VOID = 5;
|
|
7
7
|
declare const NODE_WHITELIST: Record<string, number>;
|
|
8
8
|
declare const REGEX_EMPTY_TEXT_NODES: RegExp;
|
|
9
|
+
declare const REGEX_EVENTS: RegExp;
|
|
10
|
+
declare const REGEX_SLOT_ATTRIBUTES: RegExp;
|
|
9
11
|
declare const REGEX_SLOT_NODES: RegExp;
|
|
10
12
|
declare const RENDERABLE: unique symbol;
|
|
11
13
|
declare const RENDERABLE_HTML_REACTIVE_ARRAY = 1;
|
|
12
14
|
declare const SLOT_HTML = "<!--$-->";
|
|
13
15
|
declare const SLOT_MARKER = "{{$}}";
|
|
14
|
-
declare const SLOT_MARKER_LENGTH: number;
|
|
15
16
|
declare const STATE_HYDRATING = 0;
|
|
16
17
|
declare const STATE_NONE = 1;
|
|
17
18
|
declare const STATE_WAITING = 2;
|
|
18
|
-
export { CLEANUP, EMPTY_FRAGMENT, NODE_CLOSING, NODE_ELEMENT, NODE_SLOT, NODE_VOID, NODE_WHITELIST, REGEX_EMPTY_TEXT_NODES, REGEX_SLOT_NODES, RENDERABLE, RENDERABLE_HTML_REACTIVE_ARRAY, SLOT_HTML, SLOT_MARKER,
|
|
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 };
|
package/build/constants.js
CHANGED
|
@@ -27,13 +27,14 @@ const NODE_WHITELIST = {
|
|
|
27
27
|
'wbr': NODE_VOID
|
|
28
28
|
};
|
|
29
29
|
const REGEX_EMPTY_TEXT_NODES = /(>|})\s+(<|{)/g;
|
|
30
|
+
const REGEX_EVENTS = /(?:\s*on[\w-:]+\s*=(?:\s*["'][^"']*["'])*)/g;
|
|
31
|
+
const REGEX_SLOT_ATTRIBUTES = /<[\w-]+([^><]*{{\$}}[^><]*)>/g;
|
|
30
32
|
const REGEX_SLOT_NODES = /<([\w-]+|[\/!])(?:([^><]*{{\$}}[^><]*)|(?:[^><]*))?>|{{\$}}/g;
|
|
31
33
|
const RENDERABLE = Symbol();
|
|
32
34
|
const RENDERABLE_HTML_REACTIVE_ARRAY = 1;
|
|
33
35
|
const SLOT_HTML = '<!--$-->';
|
|
34
36
|
const SLOT_MARKER = '{{$}}';
|
|
35
|
-
const SLOT_MARKER_LENGTH = SLOT_MARKER.length;
|
|
36
37
|
const STATE_HYDRATING = 0;
|
|
37
38
|
const STATE_NONE = 1;
|
|
38
39
|
const STATE_WAITING = 2;
|
|
39
|
-
export { CLEANUP, EMPTY_FRAGMENT, NODE_CLOSING, NODE_ELEMENT, NODE_SLOT, NODE_VOID, NODE_WHITELIST, REGEX_EMPTY_TEXT_NODES, REGEX_SLOT_NODES, RENDERABLE, RENDERABLE_HTML_REACTIVE_ARRAY, SLOT_HTML, SLOT_MARKER,
|
|
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 };
|
package/build/event/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { addEventListener } from '../utilities/element.js';
|
|
|
4
4
|
import { parentElement } from '../utilities/node.js';
|
|
5
5
|
import { ondisconnect } from '../slot/cleanup.js';
|
|
6
6
|
import onconnect from './onconnect.js';
|
|
7
|
+
import onresize from './onresize.js';
|
|
7
8
|
import ontick from './ontick.js';
|
|
8
9
|
let capture = new Set(['onblur', 'onfocus', 'onscroll']), controllers = new Map(), keys = {}, passive = new Set([
|
|
9
10
|
'onmousedown', 'onmouseenter', 'onmouseleave', 'onmousemove', 'onmouseout', 'onmouseover', 'onmouseup', 'onmousewheel',
|
|
@@ -65,6 +66,9 @@ export default (element, event, listener) => {
|
|
|
65
66
|
case 'ondisconnect':
|
|
66
67
|
ondisconnect(element, () => listener(element));
|
|
67
68
|
return;
|
|
69
|
+
case 'onresize':
|
|
70
|
+
onresize(element, listener);
|
|
71
|
+
return;
|
|
68
72
|
case 'ontick':
|
|
69
73
|
ontick(element, listener);
|
|
70
74
|
return;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { onCleanup } from '@esportsplus/reactivity';
|
|
2
|
+
let listeners = new Map(), registered = false;
|
|
3
|
+
function onresize() {
|
|
4
|
+
for (let [element, fn] of listeners) {
|
|
5
|
+
if (element.isConnected) {
|
|
6
|
+
fn(element);
|
|
7
|
+
}
|
|
8
|
+
else {
|
|
9
|
+
listeners.delete(element);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
if (listeners.size === 0) {
|
|
13
|
+
window.removeEventListener('resize', onresize);
|
|
14
|
+
registered = false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export default (element, listener) => {
|
|
18
|
+
listeners.set(element, listener);
|
|
19
|
+
onCleanup(() => {
|
|
20
|
+
listeners.delete(element);
|
|
21
|
+
});
|
|
22
|
+
if (!registered) {
|
|
23
|
+
window.addEventListener('resize', onresize);
|
|
24
|
+
registered = true;
|
|
25
|
+
}
|
|
26
|
+
};
|
package/build/html/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ReactiveArray } from '@esportsplus/reactivity';
|
|
2
|
-
import { Attributes, Renderable, RenderableReactive } from '../types.js';
|
|
3
|
-
type Values<T> = Attributes<any> | Renderable<T>;
|
|
2
|
+
import { Attribute, Attributes, Renderable, RenderableReactive } from '../types.js';
|
|
3
|
+
type Values<T> = Attribute | Attributes<any> | Renderable<T>;
|
|
4
4
|
declare const html: {
|
|
5
5
|
<T>(literals: TemplateStringsArray, ...values: (Values<T> | Values<T>[])[]): Node;
|
|
6
6
|
reactive<T>(array: ReactiveArray<T>, template: RenderableReactive<T>["template"]): RenderableReactive<T>;
|
package/build/html/index.js
CHANGED
|
@@ -6,14 +6,14 @@ const html = (literals, ...values) => {
|
|
|
6
6
|
if (slots !== null) {
|
|
7
7
|
let node, nodePath;
|
|
8
8
|
for (let i = slots.length - 1; i >= 0; i--) {
|
|
9
|
-
let { fn, path
|
|
9
|
+
let { fn, path } = slots[i];
|
|
10
10
|
if (nodePath !== path) {
|
|
11
11
|
node = clone;
|
|
12
12
|
for (let i = 0, n = path.length; i < n; i++) {
|
|
13
13
|
node = path[i].call(node);
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
|
-
fn(node, values[
|
|
16
|
+
fn(node, values[i]);
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
return clone;
|
package/build/html/parser.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { NODE_CLOSING, NODE_ELEMENT, NODE_SLOT, NODE_VOID, NODE_WHITELIST, REGEX_EMPTY_TEXT_NODES, REGEX_SLOT_NODES, SLOT_HTML, SLOT_MARKER
|
|
1
|
+
import { NODE_CLOSING, NODE_ELEMENT, NODE_SLOT, NODE_VOID, NODE_WHITELIST, REGEX_EMPTY_TEXT_NODES, REGEX_EVENTS, REGEX_SLOT_ATTRIBUTES, REGEX_SLOT_NODES, SLOT_HTML, SLOT_MARKER } from '../constants.js';
|
|
2
2
|
import { firstElementChild, nextElementSibling } from '../utilities/element.js';
|
|
3
3
|
import { firstChild, nextSibling } from '../utilities/node.js';
|
|
4
|
-
import { spread } from '../attributes.js';
|
|
5
4
|
import { fragment } from '../utilities/fragment.js';
|
|
5
|
+
import a from '../attributes.js';
|
|
6
6
|
import s from '../slot/index.js';
|
|
7
7
|
let cache = new WeakMap();
|
|
8
8
|
function build(literals) {
|
|
@@ -10,13 +10,67 @@ function build(literals) {
|
|
|
10
10
|
if (n === 0) {
|
|
11
11
|
return set(literals, literals[0]);
|
|
12
12
|
}
|
|
13
|
-
let buffer = '', html = literals.join(SLOT_MARKER)
|
|
13
|
+
let attributes = {}, buffer = '', events = false, html = literals.join(SLOT_MARKER)
|
|
14
14
|
.replace(REGEX_EMPTY_TEXT_NODES, '$1$2')
|
|
15
15
|
.trim(), index = 0, level = 0, levels = [{
|
|
16
16
|
children: 0,
|
|
17
17
|
elements: 0,
|
|
18
18
|
path: []
|
|
19
19
|
}], parsed = html.split(SLOT_MARKER), slot = 0, slots = [];
|
|
20
|
+
{
|
|
21
|
+
let attribute = '', buffer = '', char = '', quote = '';
|
|
22
|
+
for (let match of html.matchAll(REGEX_SLOT_ATTRIBUTES)) {
|
|
23
|
+
let found = match[1], metadata = attributes[found];
|
|
24
|
+
if (metadata) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
metadata = attributes[found] = [];
|
|
28
|
+
for (let i = 0, n = found.length; i < n; i++) {
|
|
29
|
+
char = found[i];
|
|
30
|
+
if (char === ' ') {
|
|
31
|
+
buffer = '';
|
|
32
|
+
}
|
|
33
|
+
else if (char === '=') {
|
|
34
|
+
attribute = buffer;
|
|
35
|
+
buffer = '';
|
|
36
|
+
}
|
|
37
|
+
else if (char === '"' || char === "'") {
|
|
38
|
+
if (!attribute) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
else if (!quote) {
|
|
42
|
+
quote = char;
|
|
43
|
+
}
|
|
44
|
+
else if (quote === char) {
|
|
45
|
+
attribute = '';
|
|
46
|
+
buffer = '';
|
|
47
|
+
quote = '';
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else if (char === '{' && char !== buffer) {
|
|
51
|
+
buffer = char;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
buffer += char;
|
|
55
|
+
if (buffer === SLOT_MARKER) {
|
|
56
|
+
buffer = '';
|
|
57
|
+
if (attribute) {
|
|
58
|
+
metadata.push(attribute);
|
|
59
|
+
if (!quote) {
|
|
60
|
+
attribute = '';
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
metadata.push(null);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
else if (buffer === 'on') {
|
|
68
|
+
events = true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
20
74
|
for (let match of html.matchAll(REGEX_SLOT_NODES)) {
|
|
21
75
|
let parent = levels[level], type = match[1] === undefined ? NODE_SLOT : (NODE_WHITELIST[match[1].toLowerCase()] || NODE_ELEMENT);
|
|
22
76
|
if ((match.index || 1) - 1 > index) {
|
|
@@ -27,15 +81,18 @@ function build(literals) {
|
|
|
27
81
|
? methods(parent.elements, parent.path, firstElementChild, nextElementSibling)
|
|
28
82
|
: methods(parent.children, [], firstChild, nextSibling);
|
|
29
83
|
if (attr) {
|
|
30
|
-
let
|
|
31
|
-
|
|
84
|
+
let metadata = attributes[attr];
|
|
85
|
+
if (!metadata) {
|
|
86
|
+
throw new Error(`Template: attribute metadata could not be found for '${attr}'`);
|
|
87
|
+
}
|
|
88
|
+
for (let i = 0, n = metadata.length; i < n; i++) {
|
|
89
|
+
let name = metadata[i];
|
|
32
90
|
slots.push({
|
|
33
|
-
fn: spread,
|
|
34
|
-
|
|
35
|
-
|
|
91
|
+
fn: name === null ? a.spread : a.set,
|
|
92
|
+
name,
|
|
93
|
+
path
|
|
36
94
|
});
|
|
37
95
|
buffer += parsed[slot++];
|
|
38
|
-
i = attr.indexOf(SLOT_MARKER, i + SLOT_MARKER_LENGTH);
|
|
39
96
|
}
|
|
40
97
|
}
|
|
41
98
|
if (type === NODE_ELEMENT) {
|
|
@@ -48,14 +105,14 @@ function build(literals) {
|
|
|
48
105
|
parent.elements++;
|
|
49
106
|
}
|
|
50
107
|
else if (type === NODE_SLOT) {
|
|
51
|
-
buffer += parsed[slot] + SLOT_HTML;
|
|
108
|
+
buffer += parsed[slot++] + SLOT_HTML;
|
|
52
109
|
slots.push({
|
|
53
110
|
fn: s,
|
|
54
|
-
|
|
55
|
-
|
|
111
|
+
name: null,
|
|
112
|
+
path: methods(parent.children, parent.path, firstChild, nextSibling)
|
|
56
113
|
});
|
|
57
114
|
}
|
|
58
|
-
if (
|
|
115
|
+
if (n === slot) {
|
|
59
116
|
buffer += parsed[slot];
|
|
60
117
|
break;
|
|
61
118
|
}
|
|
@@ -67,6 +124,9 @@ function build(literals) {
|
|
|
67
124
|
}
|
|
68
125
|
index = (match.index || 0) + match[0].length;
|
|
69
126
|
}
|
|
127
|
+
if (events) {
|
|
128
|
+
buffer = buffer.replace(REGEX_EVENTS, '');
|
|
129
|
+
}
|
|
70
130
|
return set(literals, buffer, slots);
|
|
71
131
|
}
|
|
72
132
|
function methods(children, copy, first, next) {
|
package/build/types.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { firstChild } from './utilities/node.js';
|
|
|
4
4
|
import attributes from './attributes.js';
|
|
5
5
|
import slot from './slot/index.js';
|
|
6
6
|
import html from './html/index.js';
|
|
7
|
-
type Attribute = Effect<Primitive | Primitive[]> | Primitive;
|
|
7
|
+
type Attribute = Effect<Primitive | Primitive[]> | (<T>(...args: T[]) => void) | Primitive;
|
|
8
8
|
type Attributes<T extends HTMLElement = Element> = {
|
|
9
9
|
[key: `aria-${string}`]: string | number | boolean | undefined;
|
|
10
10
|
[key: `data-${string}`]: string | undefined;
|
|
@@ -12,10 +12,10 @@ type Attributes<T extends HTMLElement = Element> = {
|
|
|
12
12
|
onconnect?: (element: T) => void;
|
|
13
13
|
ondisconnect?: (element: T) => void;
|
|
14
14
|
onrender?: (element: T) => void;
|
|
15
|
-
ontick?: (element: T) => void;
|
|
15
|
+
ontick?: (dispose: VoidFunction, element: T) => void;
|
|
16
16
|
style?: Attribute | Attribute[];
|
|
17
17
|
} & {
|
|
18
|
-
[K in keyof GlobalEventHandlersEventMap as `on${string & K}`]?: (this:
|
|
18
|
+
[K in keyof GlobalEventHandlersEventMap as `on${string & K}`]?: (this: T, event: GlobalEventHandlersEventMap[K]) => void;
|
|
19
19
|
} & Record<PropertyKey, unknown>;
|
|
20
20
|
type Effect<T> = () => T extends [] ? Renderable<T>[] : Renderable<T>;
|
|
21
21
|
type Element = HTMLElement & Attributes<any>;
|
|
@@ -35,9 +35,9 @@ type Template = {
|
|
|
35
35
|
html: string;
|
|
36
36
|
literals: TemplateStringsArray;
|
|
37
37
|
slots: {
|
|
38
|
-
fn: typeof attributes.spread | typeof slot;
|
|
38
|
+
fn: typeof attributes.set | typeof attributes.spread | typeof slot;
|
|
39
|
+
name: string | null;
|
|
39
40
|
path: typeof firstChild[];
|
|
40
|
-
slot: number;
|
|
41
41
|
}[] | null;
|
|
42
42
|
};
|
|
43
|
-
export type { Attributes, Effect, Element, Renderable, RenderableReactive, SlotGroup, Template };
|
|
43
|
+
export type { Attribute, Attributes, Effect, Element, Renderable, RenderableReactive, SlotGroup, Template };
|
package/package.json
CHANGED
package/src/attributes.ts
CHANGED
|
@@ -169,7 +169,31 @@ function schedule(ctx: Context | null, element: Element, name: string, state: St
|
|
|
169
169
|
raf.add(task);
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
-
function
|
|
172
|
+
function task() {
|
|
173
|
+
let context,
|
|
174
|
+
n = queue.length;
|
|
175
|
+
|
|
176
|
+
while ((context = queue.next()) && n--) {
|
|
177
|
+
let { element, updates } = context;
|
|
178
|
+
|
|
179
|
+
for (let name in updates) {
|
|
180
|
+
apply(element, name, updates[name]);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
context.updates = {};
|
|
184
|
+
context.updating = false;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (queue.length) {
|
|
188
|
+
raf.add(task);
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
scheduled = false;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
const set = (element: Element, name: string, value: unknown, state: State = STATE_HYDRATING) => {
|
|
173
197
|
let fn = name === 'class' || name === 'style' ? list : property,
|
|
174
198
|
type = typeof value;
|
|
175
199
|
|
|
@@ -233,31 +257,7 @@ function set(element: Element, name: string, value: unknown, state: State) {
|
|
|
233
257
|
}
|
|
234
258
|
|
|
235
259
|
fn(null, element, null, name, value, state);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
function task() {
|
|
239
|
-
let context,
|
|
240
|
-
n = queue.length;
|
|
241
|
-
|
|
242
|
-
while ((context = queue.next()) && n--) {
|
|
243
|
-
let { element, updates } = context;
|
|
244
|
-
|
|
245
|
-
for (let name in updates) {
|
|
246
|
-
apply(element, name, updates[name]);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
context.updates = {};
|
|
250
|
-
context.updating = false;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
if (queue.length) {
|
|
254
|
-
raf.add(task);
|
|
255
|
-
}
|
|
256
|
-
else {
|
|
257
|
-
scheduled = false;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
260
|
+
};
|
|
261
261
|
|
|
262
262
|
const spread = function (element: Element, value: Attributes | Attributes[]) {
|
|
263
263
|
if (isObject(value)) {
|
|
@@ -268,7 +268,7 @@ const spread = function (element: Element, value: Attributes | Attributes[]) {
|
|
|
268
268
|
continue;
|
|
269
269
|
}
|
|
270
270
|
|
|
271
|
-
set(element, name, v
|
|
271
|
+
set(element, name, v);
|
|
272
272
|
}
|
|
273
273
|
}
|
|
274
274
|
else if (isArray(value)) {
|
|
@@ -279,5 +279,5 @@ const spread = function (element: Element, value: Attributes | Attributes[]) {
|
|
|
279
279
|
};
|
|
280
280
|
|
|
281
281
|
|
|
282
|
-
export default { spread };
|
|
283
|
-
export { spread };
|
|
282
|
+
export default { set, spread };
|
|
283
|
+
export { set, spread };
|
package/src/constants.ts
CHANGED
|
@@ -42,6 +42,10 @@ const NODE_WHITELIST: Record<string, number> = {
|
|
|
42
42
|
|
|
43
43
|
const REGEX_EMPTY_TEXT_NODES = /(>|})\s+(<|{)/g;
|
|
44
44
|
|
|
45
|
+
const REGEX_EVENTS = /(?:\s*on[\w-:]+\s*=(?:\s*["'][^"']*["'])*)/g;
|
|
46
|
+
|
|
47
|
+
const REGEX_SLOT_ATTRIBUTES = /<[\w-]+([^><]*{{\$}}[^><]*)>/g;
|
|
48
|
+
|
|
45
49
|
const REGEX_SLOT_NODES = /<([\w-]+|[\/!])(?:([^><]*{{\$}}[^><]*)|(?:[^><]*))?>|{{\$}}/g;
|
|
46
50
|
|
|
47
51
|
|
|
@@ -54,8 +58,6 @@ const SLOT_HTML = '<!--$-->';
|
|
|
54
58
|
|
|
55
59
|
const SLOT_MARKER = '{{$}}';
|
|
56
60
|
|
|
57
|
-
const SLOT_MARKER_LENGTH = SLOT_MARKER.length;
|
|
58
|
-
|
|
59
61
|
|
|
60
62
|
const STATE_HYDRATING = 0;
|
|
61
63
|
|
|
@@ -68,8 +70,7 @@ export {
|
|
|
68
70
|
CLEANUP,
|
|
69
71
|
EMPTY_FRAGMENT,
|
|
70
72
|
NODE_CLOSING, NODE_ELEMENT, NODE_SLOT, NODE_VOID, NODE_WHITELIST,
|
|
71
|
-
REGEX_EMPTY_TEXT_NODES, REGEX_SLOT_NODES,
|
|
73
|
+
REGEX_EMPTY_TEXT_NODES, REGEX_EVENTS, REGEX_SLOT_ATTRIBUTES, REGEX_SLOT_NODES,
|
|
72
74
|
RENDERABLE, RENDERABLE_HTML_REACTIVE_ARRAY,
|
|
73
|
-
SLOT_HTML, SLOT_MARKER,
|
|
74
|
-
STATE_HYDRATING, STATE_NONE, STATE_WAITING
|
|
75
|
+
SLOT_HTML, SLOT_MARKER, STATE_HYDRATING, STATE_NONE, STATE_WAITING
|
|
75
76
|
};
|
package/src/event/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { addEventListener } from '~/utilities/element';
|
|
|
5
5
|
import { parentElement } from '~/utilities/node';
|
|
6
6
|
import { ondisconnect } from '~/slot/cleanup';
|
|
7
7
|
import onconnect from './onconnect';
|
|
8
|
+
import onresize from './onresize';
|
|
8
9
|
import ontick from './ontick';
|
|
9
10
|
|
|
10
11
|
|
|
@@ -97,6 +98,10 @@ export default (element: Element, event: `on${string}`, listener: Function): voi
|
|
|
97
98
|
ondisconnect(element, () => listener(element));
|
|
98
99
|
return;
|
|
99
100
|
|
|
101
|
+
case 'onresize':
|
|
102
|
+
onresize(element, listener);
|
|
103
|
+
return;
|
|
104
|
+
|
|
100
105
|
case 'ontick':
|
|
101
106
|
ontick(element, listener);
|
|
102
107
|
return;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { onCleanup } from '@esportsplus/reactivity';
|
|
2
|
+
import { Element } from '~/types';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
let listeners = new Map<Element, Function>(),
|
|
6
|
+
registered = false;
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
function onresize() {
|
|
10
|
+
for (let [element, fn] of listeners) {
|
|
11
|
+
if (element.isConnected) {
|
|
12
|
+
fn(element);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
listeners.delete(element);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (listeners.size === 0) {
|
|
20
|
+
window.removeEventListener('resize', onresize);
|
|
21
|
+
registered = false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
export default (element: Element, listener: Function) => {
|
|
27
|
+
listeners.set(element, listener);
|
|
28
|
+
|
|
29
|
+
onCleanup(() => {
|
|
30
|
+
listeners.delete(element);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
if (!registered) {
|
|
34
|
+
window.addEventListener('resize', onresize);
|
|
35
|
+
registered = true;
|
|
36
|
+
}
|
|
37
|
+
};
|
package/src/html/index.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { ReactiveArray } from '@esportsplus/reactivity';
|
|
2
2
|
import { RENDERABLE, RENDERABLE_HTML_REACTIVE_ARRAY } from '~/constants';
|
|
3
|
-
import { Attributes, Renderable, RenderableReactive } from '~/types';
|
|
3
|
+
import { Attribute, Attributes, Renderable, RenderableReactive } from '~/types';
|
|
4
4
|
import { cloneNode } from '~/utilities/node';
|
|
5
5
|
import parser from './parser';
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
type Values<T> = Attributes<any> | Renderable<T>;
|
|
8
|
+
type Values<T> = Attribute | Attributes<any> | Renderable<T>;
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
const html = <T>(literals: TemplateStringsArray, ...values: (Values<T> | Values<T>[])[]) => {
|
|
@@ -16,7 +16,7 @@ const html = <T>(literals: TemplateStringsArray, ...values: (Values<T> | Values<
|
|
|
16
16
|
let node, nodePath;
|
|
17
17
|
|
|
18
18
|
for (let i = slots.length - 1; i >= 0; i--) {
|
|
19
|
-
let { fn, path
|
|
19
|
+
let { fn, path } = slots[i];
|
|
20
20
|
|
|
21
21
|
if (nodePath !== path) {
|
|
22
22
|
node = clone;
|
|
@@ -27,7 +27,7 @@ const html = <T>(literals: TemplateStringsArray, ...values: (Values<T> | Values<
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
// @ts-ignore
|
|
30
|
-
fn(node, values[
|
|
30
|
+
fn(node, values[i]);
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
|
package/src/html/parser.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
|
-
NODE_CLOSING, NODE_ELEMENT, NODE_SLOT, NODE_VOID, NODE_WHITELIST,
|
|
3
|
-
|
|
2
|
+
NODE_CLOSING, NODE_ELEMENT, NODE_SLOT, NODE_VOID, NODE_WHITELIST,
|
|
3
|
+
REGEX_EMPTY_TEXT_NODES, REGEX_EVENTS, REGEX_SLOT_ATTRIBUTES, REGEX_SLOT_NODES,
|
|
4
|
+
SLOT_HTML, SLOT_MARKER
|
|
4
5
|
} from '~/constants';
|
|
5
6
|
import { Template } from '~/types';
|
|
6
7
|
import { firstElementChild, nextElementSibling } from '~/utilities/element';
|
|
7
8
|
import { firstChild, nextSibling } from '~/utilities/node';
|
|
8
|
-
import { spread } from '~/attributes';
|
|
9
9
|
import { fragment } from '~/utilities/fragment';
|
|
10
|
+
import a from '~/attributes';
|
|
10
11
|
import s from '~/slot';
|
|
11
12
|
|
|
12
13
|
|
|
@@ -20,7 +21,9 @@ function build(literals: TemplateStringsArray) {
|
|
|
20
21
|
return set(literals, literals[0]);
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
let
|
|
24
|
+
let attributes: Record<string, (null | string)[]> = {},
|
|
25
|
+
buffer = '',
|
|
26
|
+
events = false,
|
|
24
27
|
html = literals.join(SLOT_MARKER)
|
|
25
28
|
.replace(REGEX_EMPTY_TEXT_NODES, '$1$2')
|
|
26
29
|
.trim(),
|
|
@@ -35,6 +38,73 @@ function build(literals: TemplateStringsArray) {
|
|
|
35
38
|
slot = 0,
|
|
36
39
|
slots: Template['slots'] = [];
|
|
37
40
|
|
|
41
|
+
{
|
|
42
|
+
let attribute = '',
|
|
43
|
+
buffer = '',
|
|
44
|
+
char = '',
|
|
45
|
+
quote = '';
|
|
46
|
+
|
|
47
|
+
for (let match of html.matchAll(REGEX_SLOT_ATTRIBUTES)) {
|
|
48
|
+
let found = match[1],
|
|
49
|
+
metadata = attributes[found];
|
|
50
|
+
|
|
51
|
+
if (metadata) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
metadata = attributes[found] = [];
|
|
56
|
+
|
|
57
|
+
for (let i = 0, n = found.length; i < n; i++) {
|
|
58
|
+
char = found[i];
|
|
59
|
+
|
|
60
|
+
if (char === ' ') {
|
|
61
|
+
buffer = '';
|
|
62
|
+
}
|
|
63
|
+
else if (char === '=') {
|
|
64
|
+
attribute = buffer;
|
|
65
|
+
buffer = '';
|
|
66
|
+
}
|
|
67
|
+
else if (char === '"' || char === "'") {
|
|
68
|
+
if (!attribute) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
else if (!quote) {
|
|
72
|
+
quote = char;
|
|
73
|
+
}
|
|
74
|
+
else if (quote === char) {
|
|
75
|
+
attribute = '';
|
|
76
|
+
buffer = '';
|
|
77
|
+
quote = '';
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
else if (char === '{' && char !== buffer) {
|
|
81
|
+
buffer = char;
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
buffer += char;
|
|
85
|
+
|
|
86
|
+
if (buffer === SLOT_MARKER) {
|
|
87
|
+
buffer = '';
|
|
88
|
+
|
|
89
|
+
if (attribute) {
|
|
90
|
+
metadata.push(attribute);
|
|
91
|
+
|
|
92
|
+
if (!quote) {
|
|
93
|
+
attribute = '';
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
metadata.push(null);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else if (buffer === 'on') {
|
|
101
|
+
events = true;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
38
108
|
for (let match of html.matchAll(REGEX_SLOT_NODES)) {
|
|
39
109
|
let parent = levels[level],
|
|
40
110
|
type = match[1] === undefined ? NODE_SLOT : (NODE_WHITELIST[match[1].toLowerCase()] || NODE_ELEMENT);
|
|
@@ -51,17 +121,22 @@ function build(literals: TemplateStringsArray) {
|
|
|
51
121
|
: methods(parent.children, [], firstChild, nextSibling);
|
|
52
122
|
|
|
53
123
|
if (attr) {
|
|
54
|
-
let
|
|
124
|
+
let metadata = attributes[attr];
|
|
125
|
+
|
|
126
|
+
if (!metadata) {
|
|
127
|
+
throw new Error(`Template: attribute metadata could not be found for '${attr}'`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
for (let i = 0, n = metadata.length; i < n; i++) {
|
|
131
|
+
let name = metadata[i];
|
|
55
132
|
|
|
56
|
-
while (i !== -1) {
|
|
57
133
|
slots.push({
|
|
58
|
-
fn: spread,
|
|
59
|
-
|
|
60
|
-
|
|
134
|
+
fn: name === null ? a.spread : a.set,
|
|
135
|
+
name,
|
|
136
|
+
path
|
|
61
137
|
});
|
|
62
138
|
|
|
63
139
|
buffer += parsed[slot++];
|
|
64
|
-
i = attr.indexOf(SLOT_MARKER, i + SLOT_MARKER_LENGTH);
|
|
65
140
|
}
|
|
66
141
|
}
|
|
67
142
|
|
|
@@ -76,15 +151,15 @@ function build(literals: TemplateStringsArray) {
|
|
|
76
151
|
parent.elements++;
|
|
77
152
|
}
|
|
78
153
|
else if (type === NODE_SLOT) {
|
|
79
|
-
buffer += parsed[slot] + SLOT_HTML;
|
|
154
|
+
buffer += parsed[slot++] + SLOT_HTML;
|
|
80
155
|
slots.push({
|
|
81
156
|
fn: s,
|
|
82
|
-
|
|
83
|
-
|
|
157
|
+
name: null,
|
|
158
|
+
path: methods(parent.children, parent.path, firstChild, nextSibling)
|
|
84
159
|
});
|
|
85
160
|
}
|
|
86
161
|
|
|
87
|
-
if (
|
|
162
|
+
if (n === slot) {
|
|
88
163
|
buffer += parsed[slot];
|
|
89
164
|
break;
|
|
90
165
|
}
|
|
@@ -99,6 +174,10 @@ function build(literals: TemplateStringsArray) {
|
|
|
99
174
|
index = (match.index || 0) + match[0].length;
|
|
100
175
|
}
|
|
101
176
|
|
|
177
|
+
if (events) {
|
|
178
|
+
buffer = buffer.replace(REGEX_EVENTS, '');
|
|
179
|
+
}
|
|
180
|
+
|
|
102
181
|
return set(literals, buffer, slots);
|
|
103
182
|
}
|
|
104
183
|
|
package/src/types.ts
CHANGED
|
@@ -6,7 +6,7 @@ import slot from './slot';
|
|
|
6
6
|
import html from './html';
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
type Attribute = Effect<Primitive | Primitive[]> | Primitive;
|
|
9
|
+
type Attribute = Effect<Primitive | Primitive[]> | (<T>(...args: T[]) => void) | Primitive;
|
|
10
10
|
|
|
11
11
|
type Attributes<T extends HTMLElement = Element> = {
|
|
12
12
|
[key: `aria-${string}`]: string | number | boolean | undefined;
|
|
@@ -15,10 +15,10 @@ type Attributes<T extends HTMLElement = Element> = {
|
|
|
15
15
|
onconnect?: (element: T) => void;
|
|
16
16
|
ondisconnect?: (element: T) => void;
|
|
17
17
|
onrender?: (element: T) => void;
|
|
18
|
-
ontick?: (element: T) => void;
|
|
18
|
+
ontick?: (dispose: VoidFunction, element: T) => void;
|
|
19
19
|
style?: Attribute | Attribute[];
|
|
20
20
|
} & {
|
|
21
|
-
[K in keyof GlobalEventHandlersEventMap as `on${string & K}`]?: (this:
|
|
21
|
+
[K in keyof GlobalEventHandlersEventMap as `on${string & K}`]?: (this: T, event: GlobalEventHandlersEventMap[K]) => void;
|
|
22
22
|
} & Record<PropertyKey, unknown>;
|
|
23
23
|
|
|
24
24
|
type Effect<T> = () => T extends [] ? Renderable<T>[] : Renderable<T>;
|
|
@@ -51,15 +51,15 @@ type Template = {
|
|
|
51
51
|
html: string;
|
|
52
52
|
literals: TemplateStringsArray;
|
|
53
53
|
slots: {
|
|
54
|
-
fn: typeof attributes.spread | typeof slot;
|
|
54
|
+
fn: typeof attributes.set | typeof attributes.spread | typeof slot;
|
|
55
|
+
name: string | null;
|
|
55
56
|
path: typeof firstChild[];
|
|
56
|
-
slot: number;
|
|
57
57
|
}[] | null;
|
|
58
58
|
};
|
|
59
59
|
|
|
60
60
|
|
|
61
61
|
export type {
|
|
62
|
-
Attributes,
|
|
62
|
+
Attribute, Attributes,
|
|
63
63
|
Effect, Element,
|
|
64
64
|
Renderable, RenderableReactive,
|
|
65
65
|
SlotGroup,
|