web-toolkit-x 0.0.1 → 0.71.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/AUTHORS +3 -0
- package/LICENSE +1 -1
- package/NOTICE +7 -0
- package/README.md +169 -6
- package/dist/release/global.d.ts +10 -0
- package/dist/release/index.d.ts +650 -0
- package/dist/release/index.d.ts.map +1 -0
- package/dist/release/index.js +2837 -0
- package/dist/release/index.js.map +1 -0
- package/dist/release/toolkit-0.71.0.js +2840 -0
- package/dist/release/toolkit-0.71.0.js.map +1 -0
- package/package.json +75 -18
- package/src/core/bragi.ts +122 -0
- package/src/core/browser.ts +555 -0
- package/src/core/child-nodes.ts +77 -0
- package/src/core/comment.ts +24 -0
- package/src/core/component.ts +190 -0
- package/src/core/created-roots.ts +38 -0
- package/src/core/custom-element.ts +191 -0
- package/src/core/description.ts +241 -0
- package/src/core/diff.ts +590 -0
- package/src/core/dispatcher.ts +221 -0
- package/src/core/dom.ts +124 -0
- package/src/core/draft.ts +609 -0
- package/src/core/lifecycle.ts +301 -0
- package/src/core/nodes.ts +13 -0
- package/src/core/patch.ts +412 -0
- package/src/core/plugins.ts +231 -0
- package/src/core/reconciler.ts +185 -0
- package/src/core/renderer.ts +75 -0
- package/src/core/runtime.ts +36 -0
- package/src/core/sandbox.ts +128 -0
- package/src/core/service.ts +39 -0
- package/src/core/template.ts +472 -0
- package/src/core/text.ts +24 -0
- package/src/core/toolkit.ts +220 -0
- package/src/core/utils.ts +148 -0
- package/src/core/virtual-dom.ts +118 -0
- package/src/core/virtual-element.ts +72 -0
- package/src/core/virtual-node.ts +87 -0
- package/src/core/web-component.ts +285 -0
- package/src/global.d.ts +10 -0
- package/src/index.ts +62 -0
- package/src/plugins/logger.ts +38 -0
- package/src/release.ts +3 -0
- package/index.js +0 -1
|
@@ -0,0 +1,2840 @@
|
|
|
1
|
+
(function() {
|
|
2
|
+
//#region src/core/virtual-node.ts
|
|
3
|
+
var VirtualNode = class {
|
|
4
|
+
constructor(description, parentNode = null, context = null) {
|
|
5
|
+
this.description = description;
|
|
6
|
+
this.key = description.key;
|
|
7
|
+
this.parentNode = parentNode;
|
|
8
|
+
this.context = context;
|
|
9
|
+
}
|
|
10
|
+
get parentElement() {
|
|
11
|
+
if (this.parentNode) return this.parentNode.isElement() ? this.parentNode : this.parentNode.parentElement;
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
get container() {
|
|
15
|
+
if (this.parentNode) return this.parentNode.container;
|
|
16
|
+
return this;
|
|
17
|
+
}
|
|
18
|
+
get rootNode() {
|
|
19
|
+
if (this.isRoot()) return this;
|
|
20
|
+
if (this.parentNode) return this.parentNode.rootNode;
|
|
21
|
+
throw new Error("Inconsistent virtual DOM tree detected!");
|
|
22
|
+
}
|
|
23
|
+
isRoot() {
|
|
24
|
+
return this.nodeType === "root";
|
|
25
|
+
}
|
|
26
|
+
isComponent() {
|
|
27
|
+
const { nodeType } = this;
|
|
28
|
+
return nodeType === "component" || nodeType === "root";
|
|
29
|
+
}
|
|
30
|
+
isElement() {
|
|
31
|
+
return this.nodeType === "element";
|
|
32
|
+
}
|
|
33
|
+
isComment() {
|
|
34
|
+
return this.nodeType === "comment";
|
|
35
|
+
}
|
|
36
|
+
isText() {
|
|
37
|
+
return this.nodeType === "text";
|
|
38
|
+
}
|
|
39
|
+
isCompatible(node) {
|
|
40
|
+
return node && this.nodeType === node.nodeType && this.key === node.key;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/core/comment.ts
|
|
45
|
+
var Comment = class Comment extends VirtualNode {
|
|
46
|
+
static NodeType = "comment";
|
|
47
|
+
constructor(description, parentNode) {
|
|
48
|
+
super(description, parentNode);
|
|
49
|
+
this.attachDOM();
|
|
50
|
+
}
|
|
51
|
+
get nodeType() {
|
|
52
|
+
return Comment.NodeType;
|
|
53
|
+
}
|
|
54
|
+
attachDOM() {
|
|
55
|
+
this.ref = document.createComment(` ${this.description.text} `);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
//#endregion
|
|
59
|
+
//#region src/core/description.ts
|
|
60
|
+
var Description = class {
|
|
61
|
+
get childrenAsTemplates() {
|
|
62
|
+
if (this.children) return this.children.map((child) => child.asTemplate);
|
|
63
|
+
}
|
|
64
|
+
isCompatible(description) {
|
|
65
|
+
return this.constructor === description.constructor;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
var ComponentDescription = class extends Description {
|
|
69
|
+
constructor(component) {
|
|
70
|
+
super();
|
|
71
|
+
this.component = component;
|
|
72
|
+
this.type = "component";
|
|
73
|
+
}
|
|
74
|
+
isCompatible(description) {
|
|
75
|
+
return super.isCompatible(description) && this.component === description.component;
|
|
76
|
+
}
|
|
77
|
+
get isRoot() {
|
|
78
|
+
return this.component.NodeType === "root";
|
|
79
|
+
}
|
|
80
|
+
get asTemplate() {
|
|
81
|
+
const template = [this.component];
|
|
82
|
+
if (this.props) template.push(this.props);
|
|
83
|
+
if (this.children) template.push(...this.children.map((child) => child.asTemplate));
|
|
84
|
+
return template;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
var ElementDescription = class extends Description {
|
|
88
|
+
constructor(name) {
|
|
89
|
+
super();
|
|
90
|
+
this.name = name;
|
|
91
|
+
this.type = "element";
|
|
92
|
+
}
|
|
93
|
+
isCompatible(description) {
|
|
94
|
+
return super.isCompatible(description) && this.name === description.name;
|
|
95
|
+
}
|
|
96
|
+
get asTemplate() {
|
|
97
|
+
const template = [this.name];
|
|
98
|
+
const props = {};
|
|
99
|
+
if (this.key) props.key = this.key;
|
|
100
|
+
if (this.class) props.class = this.class;
|
|
101
|
+
if (this.style) props.style = this.style;
|
|
102
|
+
if (this.attrs) Object.assign(props, this.attrs);
|
|
103
|
+
if (this.dataset) props.dataset = this.dataset;
|
|
104
|
+
if (this.listeners) Object.assign(props, this.listeners);
|
|
105
|
+
if (this.properties) props.properties = this.properties;
|
|
106
|
+
if (this.custom?.attrs) props.attrs = this.custom.attrs;
|
|
107
|
+
if (this.custom?.listeners) props.on = this.custom.listeners;
|
|
108
|
+
if (Object.keys(props).length) template.push(props);
|
|
109
|
+
if (this.children) template.push(...this.children.map((child) => child.asTemplate));
|
|
110
|
+
return template;
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
var CommentDescription = class extends Description {
|
|
114
|
+
constructor(text) {
|
|
115
|
+
super();
|
|
116
|
+
this.text = text;
|
|
117
|
+
this.type = "comment";
|
|
118
|
+
}
|
|
119
|
+
get asTemplate() {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
isCompatible(description) {
|
|
123
|
+
return super.isCompatible(description) && this.text === description.text;
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
var TextDescription = class extends Description {
|
|
127
|
+
constructor(text) {
|
|
128
|
+
super();
|
|
129
|
+
this.text = text;
|
|
130
|
+
this.type = "text";
|
|
131
|
+
}
|
|
132
|
+
get asTemplate() {
|
|
133
|
+
return this.text;
|
|
134
|
+
}
|
|
135
|
+
isCompatible(description) {
|
|
136
|
+
return super.isCompatible(description) && this.text === description.text;
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
Object.assign(Description, {
|
|
140
|
+
ElementDescription,
|
|
141
|
+
ComponentDescription,
|
|
142
|
+
CommentDescription,
|
|
143
|
+
TextDescription
|
|
144
|
+
});
|
|
145
|
+
//#endregion
|
|
146
|
+
//#region src/core/utils.ts
|
|
147
|
+
function invariant(condition, message) {
|
|
148
|
+
if (!condition) throw new Error(message);
|
|
149
|
+
}
|
|
150
|
+
const throttle = (fn, wait = 200, delayFirstEvent = false) => {
|
|
151
|
+
let lastTimestamp = 0;
|
|
152
|
+
let taskId = null;
|
|
153
|
+
let context;
|
|
154
|
+
let params;
|
|
155
|
+
return function throttled(...args) {
|
|
156
|
+
if (!taskId) {
|
|
157
|
+
const timestamp = Date.now();
|
|
158
|
+
const elapsed = timestamp - lastTimestamp;
|
|
159
|
+
const scheduleTask = (delay) => {
|
|
160
|
+
taskId = setTimeout(() => {
|
|
161
|
+
taskId = null;
|
|
162
|
+
lastTimestamp = Date.now();
|
|
163
|
+
return fn.call(context, ...params);
|
|
164
|
+
}, delay);
|
|
165
|
+
};
|
|
166
|
+
if (elapsed >= wait) {
|
|
167
|
+
lastTimestamp = timestamp;
|
|
168
|
+
if (!delayFirstEvent) return fn.call(this, ...args);
|
|
169
|
+
scheduleTask(wait);
|
|
170
|
+
} else scheduleTask(wait - elapsed);
|
|
171
|
+
}
|
|
172
|
+
context = this;
|
|
173
|
+
params = args;
|
|
174
|
+
};
|
|
175
|
+
};
|
|
176
|
+
const debounce = (fn, wait = 200, leading = false) => {
|
|
177
|
+
let taskId = null;
|
|
178
|
+
let isTrailing = false;
|
|
179
|
+
let context;
|
|
180
|
+
let params;
|
|
181
|
+
return function debounced(...args) {
|
|
182
|
+
const isFirstInvocation = !taskId;
|
|
183
|
+
if (taskId) clearTimeout(taskId);
|
|
184
|
+
isTrailing = !(isFirstInvocation && leading);
|
|
185
|
+
taskId = setTimeout(() => {
|
|
186
|
+
taskId = null;
|
|
187
|
+
if (isTrailing) fn.call(context, ...params);
|
|
188
|
+
}, wait);
|
|
189
|
+
context = this;
|
|
190
|
+
params = args;
|
|
191
|
+
if (!isTrailing) return fn.call(context, ...params);
|
|
192
|
+
};
|
|
193
|
+
};
|
|
194
|
+
const createUUID = () => {
|
|
195
|
+
const s4 = () => Math.floor((1 + Math.random()) * 65536).toString(16).substring(1);
|
|
196
|
+
return `${s4()}${s4()}-${s4()}-${s4()}-${s4()}-${s4()}${s4()}${s4()}`;
|
|
197
|
+
};
|
|
198
|
+
const lowerDash = (name) => name.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
199
|
+
const getAttributeName = (key) => {
|
|
200
|
+
if (key === "acceptCharset" || key === "httpEquiv") return lowerDash(key);
|
|
201
|
+
else if (key.startsWith("aria")) return `aria-${key.slice(4).toLowerCase()}`;
|
|
202
|
+
return key.toLowerCase();
|
|
203
|
+
};
|
|
204
|
+
const getEventName = (key) => key === "onDoubleClick" ? "dblclick" : key.slice(2).toLowerCase();
|
|
205
|
+
const postRender = (fn) => {
|
|
206
|
+
requestAnimationFrame(function() {
|
|
207
|
+
requestAnimationFrame(fn);
|
|
208
|
+
});
|
|
209
|
+
};
|
|
210
|
+
const isSameList = (a, b) => a.length === b.length && a.every((item, index) => item === b[index]);
|
|
211
|
+
const deepFreeze = (obj) => {
|
|
212
|
+
if (obj === null || typeof obj !== "object" || Object.isFrozen(obj)) return obj;
|
|
213
|
+
Object.freeze(obj);
|
|
214
|
+
for (const property of Object.getOwnPropertyNames(obj)) deepFreeze(obj[property]);
|
|
215
|
+
return obj;
|
|
216
|
+
};
|
|
217
|
+
const Utils = {
|
|
218
|
+
throttle,
|
|
219
|
+
debounce,
|
|
220
|
+
createUUID,
|
|
221
|
+
postRender
|
|
222
|
+
};
|
|
223
|
+
//#endregion
|
|
224
|
+
//#region src/core/runtime.ts
|
|
225
|
+
let provided = null;
|
|
226
|
+
const provideRuntime = (runtime) => {
|
|
227
|
+
provided = runtime;
|
|
228
|
+
};
|
|
229
|
+
const runtime = () => {
|
|
230
|
+
invariant(provided, "No Toolkit runtime provided!");
|
|
231
|
+
return provided;
|
|
232
|
+
};
|
|
233
|
+
//#endregion
|
|
234
|
+
//#region src/core/sandbox.ts
|
|
235
|
+
const get = (target, property) => Reflect.get(target, property);
|
|
236
|
+
const isFunction = (target, property) => typeof get(target, property) === "function";
|
|
237
|
+
const delegated = [
|
|
238
|
+
"commands",
|
|
239
|
+
"constructor",
|
|
240
|
+
"container",
|
|
241
|
+
"elementName"
|
|
242
|
+
];
|
|
243
|
+
const methods = ["connectTo"];
|
|
244
|
+
const pluginMethods = /* @__PURE__ */ new Set();
|
|
245
|
+
const createBoundListener = (listener, component, context) => {
|
|
246
|
+
const boundListener = listener.bind(context);
|
|
247
|
+
boundListener.source = listener;
|
|
248
|
+
boundListener.component = component;
|
|
249
|
+
return boundListener;
|
|
250
|
+
};
|
|
251
|
+
var Sandbox = class {
|
|
252
|
+
static registerPluginMethod(name) {
|
|
253
|
+
pluginMethods.add(name);
|
|
254
|
+
}
|
|
255
|
+
static clearPluginMethods() {
|
|
256
|
+
pluginMethods.clear();
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Creates the sandbox of a component, hiding the given properties
|
|
260
|
+
* of the Component class from the component methods.
|
|
261
|
+
*/
|
|
262
|
+
static create(component, hidden) {
|
|
263
|
+
const state = {};
|
|
264
|
+
const autobound = {};
|
|
265
|
+
return new Proxy(component, {
|
|
266
|
+
get: (target, property, receiver) => {
|
|
267
|
+
if (property === "props") return state.props || target.state || {};
|
|
268
|
+
if (property === "children") return state.children || [];
|
|
269
|
+
if (property === "host") return target.isRoot() ? target.shadow.host : null;
|
|
270
|
+
if (property === "ref") {
|
|
271
|
+
if (target.isRoot()) return target.content.ref;
|
|
272
|
+
return target.ref;
|
|
273
|
+
}
|
|
274
|
+
if (property === "$component") return component;
|
|
275
|
+
if (delegated.includes(property)) return get(target, property);
|
|
276
|
+
if (methods.includes(property) && isFunction(target, property)) return createBoundListener(get(target, property), target, target);
|
|
277
|
+
if (pluginMethods.has(property)) return get(target.rootNode, property);
|
|
278
|
+
if (hidden.includes(property)) return;
|
|
279
|
+
if (isFunction(autobound, property)) return autobound[property];
|
|
280
|
+
if (isFunction(target, property)) return autobound[property] = createBoundListener(get(target, property), target, receiver);
|
|
281
|
+
return get(target, property);
|
|
282
|
+
},
|
|
283
|
+
set: (target, property, value) => {
|
|
284
|
+
if (property === "props") {
|
|
285
|
+
state.props = value;
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
if (property === "children") {
|
|
289
|
+
state.children = value || [];
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
//#endregion
|
|
298
|
+
//#region src/core/browser.ts
|
|
299
|
+
const ATTRIBUTE_PROPERTIES = { for: "htmlFor" };
|
|
300
|
+
const UNREFLECTED_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
301
|
+
"ariaOwns",
|
|
302
|
+
"is",
|
|
303
|
+
"itemProp"
|
|
304
|
+
]);
|
|
305
|
+
const elementProperties = /* @__PURE__ */ new Map();
|
|
306
|
+
const getElementProperties = (tagName) => {
|
|
307
|
+
let properties = elementProperties.get(tagName);
|
|
308
|
+
if (!properties) {
|
|
309
|
+
properties = /* @__PURE__ */ new Set();
|
|
310
|
+
for (const name in document.createElement(tagName)) properties.add(name.toLowerCase());
|
|
311
|
+
elementProperties.set(tagName, properties);
|
|
312
|
+
}
|
|
313
|
+
return properties;
|
|
314
|
+
};
|
|
315
|
+
const EVENTS_WITHOUT_HANDLERS = /* @__PURE__ */ new Set([
|
|
316
|
+
"compositionstart",
|
|
317
|
+
"compositionupdate",
|
|
318
|
+
"compositionend",
|
|
319
|
+
"touchstart",
|
|
320
|
+
"touchmove",
|
|
321
|
+
"touchend",
|
|
322
|
+
"touchcancel"
|
|
323
|
+
]);
|
|
324
|
+
let supportedStyles = null;
|
|
325
|
+
const getSupportedStyles = () => {
|
|
326
|
+
if (!supportedStyles) {
|
|
327
|
+
supportedStyles = /* @__PURE__ */ new Set();
|
|
328
|
+
for (let object = document.documentElement.style; object && object !== CSSStyleDeclaration.prototype; object = Object.getPrototypeOf(object)) for (const name of Object.getOwnPropertyNames(object)) supportedStyles.add(name);
|
|
329
|
+
}
|
|
330
|
+
return supportedStyles;
|
|
331
|
+
};
|
|
332
|
+
const Browser = {
|
|
333
|
+
/**
|
|
334
|
+
* Checks if the browser supports the attribute on the element, as
|
|
335
|
+
* a property of the element in any letter case, e.g. tabIndex. Meant
|
|
336
|
+
* for debug mode, as the check creates an element of each tag name.
|
|
337
|
+
*/
|
|
338
|
+
isAttributeSupported(key, tagName) {
|
|
339
|
+
if (UNREFLECTED_ATTRIBUTES.has(key)) return true;
|
|
340
|
+
const properties = getElementProperties(tagName);
|
|
341
|
+
const name = (ATTRIBUTE_PROPERTIES[key] ?? key).toLowerCase();
|
|
342
|
+
return properties.has(name) || properties.has(`${name}element`) || properties.has(`${name}elements`);
|
|
343
|
+
},
|
|
344
|
+
/**
|
|
345
|
+
* Checks if the browser supports the event of the listener, e.g. onClick,
|
|
346
|
+
* on the element, as its handler property, e.g. onclick. Meant for debug
|
|
347
|
+
* mode, as the check creates an element of each tag name.
|
|
348
|
+
*/
|
|
349
|
+
isEventSupported(key, tagName) {
|
|
350
|
+
const event = getEventName(key);
|
|
351
|
+
return EVENTS_WITHOUT_HANDLERS.has(event) || getElementProperties(tagName).has(`on${event}`);
|
|
352
|
+
},
|
|
353
|
+
isStyleSupported(key) {
|
|
354
|
+
return key.startsWith("--") || getSupportedStyles().has(key);
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
//#endregion
|
|
358
|
+
//#region src/core/template.ts
|
|
359
|
+
const isDefined = (value) => value !== void 0 && value !== null;
|
|
360
|
+
const isFalsy = (template) => template === null || template === false;
|
|
361
|
+
const isNotEmpty = (object) => Boolean(Object.keys(object).length);
|
|
362
|
+
const Template = {
|
|
363
|
+
/**
|
|
364
|
+
* Creates a normalized Description of given template.
|
|
365
|
+
*/
|
|
366
|
+
describe(template) {
|
|
367
|
+
if (isFalsy(template)) return null;
|
|
368
|
+
if (Array.isArray(template) && template.length) {
|
|
369
|
+
let description;
|
|
370
|
+
for (const [item, type, index] of template.map((item, index) => [
|
|
371
|
+
item,
|
|
372
|
+
this.getItemType(item),
|
|
373
|
+
index
|
|
374
|
+
])) {
|
|
375
|
+
if (index === 0) {
|
|
376
|
+
switch (type) {
|
|
377
|
+
case "string":
|
|
378
|
+
description = new ElementDescription(item);
|
|
379
|
+
break;
|
|
380
|
+
case "component":
|
|
381
|
+
case "function":
|
|
382
|
+
description = new ComponentDescription(runtime().resolveComponentClass(item, type));
|
|
383
|
+
break;
|
|
384
|
+
default:
|
|
385
|
+
console.error("Invalid node type:", item, `(${type}) at index: ${index}, template:`, template);
|
|
386
|
+
throw new Error(`Invalid node type specified: ${type}`);
|
|
387
|
+
}
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
if (index === 1 && type === "props") {
|
|
391
|
+
if (description.type === "component") this.assignPropsToComponent(item, description);
|
|
392
|
+
else if (description.type === "element") this.assignPropsToElement(item, description);
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
if (isFalsy(item)) continue;
|
|
396
|
+
if (type === "string" || type === "number" || item === true) {
|
|
397
|
+
description.children = description.children || [];
|
|
398
|
+
description.children.push(new TextDescription(String(item)));
|
|
399
|
+
continue;
|
|
400
|
+
} else if (type === "node") {
|
|
401
|
+
description.children = description.children || [];
|
|
402
|
+
description.children.push(this.describe(item));
|
|
403
|
+
} else {
|
|
404
|
+
console.error("Invalid item", item, `at index: ${index}, template:`, template);
|
|
405
|
+
throw new Error(`Invalid item specified: ${type}`);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
if (runtime().isDebug()) deepFreeze(description);
|
|
409
|
+
return description;
|
|
410
|
+
}
|
|
411
|
+
console.error("Invalid template definition:", template);
|
|
412
|
+
throw new Error("Expecting array, null or false");
|
|
413
|
+
},
|
|
414
|
+
/**
|
|
415
|
+
* Returns a new props object supplemented by overridden values.
|
|
416
|
+
*/
|
|
417
|
+
normalizeProps(...overrides) {
|
|
418
|
+
const result = {};
|
|
419
|
+
for (const override of overrides) for (const [key, value] of Object.entries(override || {})) if (result[key] === void 0 && value !== void 0) result[key] = value;
|
|
420
|
+
return result;
|
|
421
|
+
},
|
|
422
|
+
/**
|
|
423
|
+
* Normalizes specified element props object and returns either
|
|
424
|
+
* a non-empty object containing only supported props or null.
|
|
425
|
+
*/
|
|
426
|
+
normalizeComponentProps(props, ComponentClass) {
|
|
427
|
+
return this.normalizeProps(props, ComponentClass.defaultProps || {});
|
|
428
|
+
},
|
|
429
|
+
assignPropsToComponent(object, description) {
|
|
430
|
+
const props = this.getComponentProps(object, description.component, description.isRoot);
|
|
431
|
+
if (props) {
|
|
432
|
+
description.props = props;
|
|
433
|
+
if (isDefined(props.key)) description.key = String(props.key);
|
|
434
|
+
if (props.attrs) {
|
|
435
|
+
const attrs = this.getCustomAttributes(props.attrs, true);
|
|
436
|
+
if (attrs) description.attrs = attrs;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
},
|
|
440
|
+
getComponentProps(object, ComponentClass, isRoot) {
|
|
441
|
+
const props = isRoot ? object : this.normalizeComponentProps(object, ComponentClass);
|
|
442
|
+
return isNotEmpty(props) ? props : null;
|
|
443
|
+
},
|
|
444
|
+
assignPropsToElement(props, description) {
|
|
445
|
+
for (const [key, value] of Object.entries(props)) if (key === "key") {
|
|
446
|
+
if (isDefined(value)) description.key = String(value);
|
|
447
|
+
} else if (key === "class") {
|
|
448
|
+
const className = this.getClassName(value);
|
|
449
|
+
if (className) description.class = className;
|
|
450
|
+
} else if (key === "style") {
|
|
451
|
+
const style = this.getStyle(value);
|
|
452
|
+
if (style) description.style = style;
|
|
453
|
+
} else if (key === "dataset") {
|
|
454
|
+
const dataset = this.getDataset(value);
|
|
455
|
+
if (dataset) description.dataset = dataset;
|
|
456
|
+
} else if (key === "properties") {
|
|
457
|
+
const properties = this.getProperties(value);
|
|
458
|
+
if (properties) description.properties = properties;
|
|
459
|
+
} else if (key === "attrs") {
|
|
460
|
+
const customAttrs = this.getCustomAttributes(value);
|
|
461
|
+
if (customAttrs) {
|
|
462
|
+
description.custom = description.custom || {};
|
|
463
|
+
description.custom.attrs = customAttrs;
|
|
464
|
+
}
|
|
465
|
+
} else if (key === "on") {
|
|
466
|
+
const customListeners = this.getCustomListeners(value);
|
|
467
|
+
if (customListeners) {
|
|
468
|
+
description.custom = description.custom || {};
|
|
469
|
+
description.custom.listeners = customListeners;
|
|
470
|
+
}
|
|
471
|
+
} else if (/^on[A-Z]/.test(key)) {
|
|
472
|
+
if (this.getListener(value, key)) {
|
|
473
|
+
description.listeners = description.listeners || {};
|
|
474
|
+
description.listeners[key] = value;
|
|
475
|
+
}
|
|
476
|
+
const element = description.name;
|
|
477
|
+
if (runtime().isDebug() && !element.includes("-") && !Browser.isEventSupported(key, element)) console.warn(`The "${key}" listener is not supported on "${element}" elements.`);
|
|
478
|
+
} else {
|
|
479
|
+
const attr = this.getAttributeValue(value);
|
|
480
|
+
if (isDefined(attr)) {
|
|
481
|
+
description.attrs = description.attrs || {};
|
|
482
|
+
description.attrs[key] = attr;
|
|
483
|
+
}
|
|
484
|
+
if (runtime().isDebug()) {
|
|
485
|
+
const element = description.name;
|
|
486
|
+
if (attr === void 0) console.warn(`Invalid undefined value for attribute "${key}"`, `on element "${element}".`);
|
|
487
|
+
if (!element.includes("-") && !Browser.isAttributeSupported(key, element)) console.warn(`The "${key}" attribute is not supported on "${element}" elements.`);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
},
|
|
491
|
+
/**
|
|
492
|
+
* Returns the type of item used in the array representing node template.
|
|
493
|
+
*/
|
|
494
|
+
getItemType(item) {
|
|
495
|
+
const type = typeof item;
|
|
496
|
+
switch (type) {
|
|
497
|
+
case "function":
|
|
498
|
+
if (item.NodeType === "component" || item.NodeType === "root") return "component";
|
|
499
|
+
return "function";
|
|
500
|
+
case "object":
|
|
501
|
+
if (item === null) return "null";
|
|
502
|
+
else if (Array.isArray(item)) return "node";
|
|
503
|
+
else if (item.constructor === Object) return "props";
|
|
504
|
+
return "unknown";
|
|
505
|
+
default: return type;
|
|
506
|
+
}
|
|
507
|
+
},
|
|
508
|
+
/**
|
|
509
|
+
* Resolves any object to a space separated string of class names.
|
|
510
|
+
*/
|
|
511
|
+
getClassName(value) {
|
|
512
|
+
if (!value) return "";
|
|
513
|
+
if (typeof value === "string") return value;
|
|
514
|
+
if (Array.isArray(value)) return value.map((item) => this.getClassName(item)).filter((className) => className).join(" ");
|
|
515
|
+
if (typeof value === "object") return Object.keys(value).filter((key) => key && value[key]).join(" ");
|
|
516
|
+
throw new Error(`Invalid value: ${JSON.stringify(value)}`);
|
|
517
|
+
},
|
|
518
|
+
/**
|
|
519
|
+
* Returns either a non-empty style object containing only understood
|
|
520
|
+
* styling rules or null.
|
|
521
|
+
*/
|
|
522
|
+
getStyle(object) {
|
|
523
|
+
runtime().assert(object.constructor === Object, "Style must be a plain object!");
|
|
524
|
+
const style = {};
|
|
525
|
+
for (const [name, value] of Object.entries(object)) {
|
|
526
|
+
if (runtime().isDebug() && !Browser.isStyleSupported(name)) console.warn(`Unsupported style property, key: ${name}, value:`, value);
|
|
527
|
+
const string = this.getStyleProperty(value, name);
|
|
528
|
+
if (isDefined(string)) style[name] = string;
|
|
529
|
+
}
|
|
530
|
+
return isNotEmpty(style) ? style : null;
|
|
531
|
+
},
|
|
532
|
+
getStyleProperty(value, name) {
|
|
533
|
+
if (typeof value === "string") return value || "''";
|
|
534
|
+
else if ([
|
|
535
|
+
true,
|
|
536
|
+
false,
|
|
537
|
+
null,
|
|
538
|
+
void 0
|
|
539
|
+
].includes(value)) return null;
|
|
540
|
+
else if (Array.isArray(value)) return value.join("");
|
|
541
|
+
else if (typeof value === "number") return String(value);
|
|
542
|
+
else if (typeof value === "object") {
|
|
543
|
+
if (name !== "filter" && name !== "transform") throw new Error(`Unknown function list: ${JSON.stringify(value)}`);
|
|
544
|
+
const functions = this.getFunctionList(value, name === "filter" ? lowerDash : void 0);
|
|
545
|
+
if (runtime().isDebug() && functions && !CSS.supports(name, functions)) console.warn(`Unsupported ${name} functions: ${functions}`);
|
|
546
|
+
return functions;
|
|
547
|
+
}
|
|
548
|
+
throw new Error(`Invalid style property value: ${JSON.stringify(value)}`);
|
|
549
|
+
},
|
|
550
|
+
/**
|
|
551
|
+
* Returns a multi-property string value.
|
|
552
|
+
*/
|
|
553
|
+
getFunctionList(object, getCSSName = (name) => name) {
|
|
554
|
+
const composite = {};
|
|
555
|
+
for (const [key, value] of Object.entries(object)) {
|
|
556
|
+
const stringValue = this.getAttributeValue(value, false);
|
|
557
|
+
if (isDefined(stringValue)) composite[getCSSName(key)] = stringValue;
|
|
558
|
+
}
|
|
559
|
+
return Object.entries(composite).map(([key, value]) => `${key}(${value})`).join(" ");
|
|
560
|
+
},
|
|
561
|
+
getListener(value, name) {
|
|
562
|
+
if (typeof value === "function") return value;
|
|
563
|
+
if (value === null || value === false || value === void 0) return null;
|
|
564
|
+
throw new Error(`Invalid listener specified for event: ${name}`);
|
|
565
|
+
},
|
|
566
|
+
/**
|
|
567
|
+
* Resolves given value to a string.
|
|
568
|
+
*/
|
|
569
|
+
getAttributeValue(value, allowEmpty = true) {
|
|
570
|
+
if (value === true || value === "") return allowEmpty ? "" : null;
|
|
571
|
+
else if (typeof value === "string") return value;
|
|
572
|
+
else if (value === null || value === false) return null;
|
|
573
|
+
else if (value === void 0) return;
|
|
574
|
+
else if (Array.isArray(value)) return value.join("");
|
|
575
|
+
else if ([
|
|
576
|
+
"object",
|
|
577
|
+
"function",
|
|
578
|
+
"symbol"
|
|
579
|
+
].includes(typeof value)) throw new Error(`Invalid attribute value: ${JSON.stringify(value)}!`);
|
|
580
|
+
return String(value);
|
|
581
|
+
},
|
|
582
|
+
/**
|
|
583
|
+
* Returns either a non-empty dataset object or null.
|
|
584
|
+
*/
|
|
585
|
+
getDataset(object) {
|
|
586
|
+
const dataset = {};
|
|
587
|
+
for (const key of Object.keys(object)) {
|
|
588
|
+
const value = this.getAttributeValue(object[key]);
|
|
589
|
+
if (isDefined(value)) dataset[key] = value;
|
|
590
|
+
}
|
|
591
|
+
return isNotEmpty(dataset) ? dataset : null;
|
|
592
|
+
},
|
|
593
|
+
/**
|
|
594
|
+
* Returns either a non-empty object containing properties set
|
|
595
|
+
* directly on a rendered DOM Element or null.
|
|
596
|
+
*/
|
|
597
|
+
getProperties(object) {
|
|
598
|
+
return isNotEmpty(object) ? object : null;
|
|
599
|
+
},
|
|
600
|
+
getCustomAttributes(object, forComponent) {
|
|
601
|
+
runtime().assert(object.constructor === Object, "Expecting object for custom attributes!");
|
|
602
|
+
const attrs = {};
|
|
603
|
+
for (const [key, value] of Object.entries(object)) {
|
|
604
|
+
const attr = this.getAttributeValue(value, true);
|
|
605
|
+
if (isDefined(attr)) {
|
|
606
|
+
const name = forComponent ? lowerDash(key) : key;
|
|
607
|
+
attrs[name] = attr;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
return isNotEmpty(attrs) ? attrs : null;
|
|
611
|
+
},
|
|
612
|
+
getCustomListeners(object) {
|
|
613
|
+
runtime().assert(object.constructor === Object, "Expecting object for custom listeners!");
|
|
614
|
+
const listeners = {};
|
|
615
|
+
for (const [key, value] of Object.entries(object)) {
|
|
616
|
+
const listener = this.getListener(value, key);
|
|
617
|
+
if (listener) listeners[key] = listener;
|
|
618
|
+
}
|
|
619
|
+
return isNotEmpty(listeners) ? listeners : null;
|
|
620
|
+
}
|
|
621
|
+
};
|
|
622
|
+
//#endregion
|
|
623
|
+
//#region src/core/component.ts
|
|
624
|
+
var Component = class Component extends VirtualNode {
|
|
625
|
+
static NodeType = "component";
|
|
626
|
+
static get displayName() {
|
|
627
|
+
return this.name;
|
|
628
|
+
}
|
|
629
|
+
constructor(description, parent, context, attachDOM = true) {
|
|
630
|
+
super(description, parent, context);
|
|
631
|
+
this.sandbox = Sandbox.create(this, COMPONENT_PROPERTIES);
|
|
632
|
+
this.cleanUpTasks = [];
|
|
633
|
+
this.isInitialized = attachDOM;
|
|
634
|
+
if (attachDOM) this.attachDOM();
|
|
635
|
+
this.content = null;
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Sets the component content.
|
|
639
|
+
*/
|
|
640
|
+
setContent(node) {
|
|
641
|
+
invariant(node.parentNode === this, "Specified node does not have a valid parent!");
|
|
642
|
+
this.content.parentNode = null;
|
|
643
|
+
node.parentNode = this;
|
|
644
|
+
this.content.ref.replaceWith(node.ref);
|
|
645
|
+
this.content = node;
|
|
646
|
+
}
|
|
647
|
+
hasOwnMethod(method) {
|
|
648
|
+
return this.constructor.prototype.hasOwnProperty(method);
|
|
649
|
+
}
|
|
650
|
+
connectTo(service, listeners) {
|
|
651
|
+
runtime().assert(typeof service.connect === "function", "Services have to define the connect() method");
|
|
652
|
+
const disconnect = service.connect(listeners);
|
|
653
|
+
runtime().assert(typeof disconnect === "function", "The result of the connect() method has to be a disconnect() method");
|
|
654
|
+
disconnect.service = service;
|
|
655
|
+
this.cleanUpTasks.push(disconnect);
|
|
656
|
+
}
|
|
657
|
+
get childElement() {
|
|
658
|
+
if (this.content) {
|
|
659
|
+
if (this.content.isElement() || this.content.isRoot()) return this.content;
|
|
660
|
+
if (this.content.isComponent()) return this.content.childElement;
|
|
661
|
+
}
|
|
662
|
+
return null;
|
|
663
|
+
}
|
|
664
|
+
get placeholder() {
|
|
665
|
+
if (this.content.isComment()) return this.content;
|
|
666
|
+
return this.content.placeholder || null;
|
|
667
|
+
}
|
|
668
|
+
render() {}
|
|
669
|
+
/**
|
|
670
|
+
* Calls the render method and transforms the returned template
|
|
671
|
+
* into the normalised description of the rendered node.
|
|
672
|
+
*/
|
|
673
|
+
renderDescription(props = {}, children = []) {
|
|
674
|
+
this.sandbox.props = props;
|
|
675
|
+
this.sandbox.children = children;
|
|
676
|
+
const template = this.render.call(this.sandbox);
|
|
677
|
+
if (template) return Template.describe(template);
|
|
678
|
+
const text = this.constructor.displayName;
|
|
679
|
+
return new CommentDescription(text);
|
|
680
|
+
}
|
|
681
|
+
get commands() {
|
|
682
|
+
return this.context ? this.context.commands : this.rootNode.commands;
|
|
683
|
+
}
|
|
684
|
+
get dispatcher() {
|
|
685
|
+
return this.context ? this.context.dispatcher : this.rootNode.dispatcher;
|
|
686
|
+
}
|
|
687
|
+
destroy() {
|
|
688
|
+
for (const cleanUpTask of this.cleanUpTasks) cleanUpTask();
|
|
689
|
+
}
|
|
690
|
+
get nodeType() {
|
|
691
|
+
return Component.NodeType;
|
|
692
|
+
}
|
|
693
|
+
get ref() {
|
|
694
|
+
return this.content.ref;
|
|
695
|
+
}
|
|
696
|
+
isCompatible(node) {
|
|
697
|
+
return super.isCompatible(node) && this.constructor === node.constructor;
|
|
698
|
+
}
|
|
699
|
+
attachDOM() {
|
|
700
|
+
if (this.content) this.content.attachDOM();
|
|
701
|
+
}
|
|
702
|
+
};
|
|
703
|
+
const COMPONENT_PROPERTIES = Object.getOwnPropertyNames(Component.prototype);
|
|
704
|
+
//#endregion
|
|
705
|
+
//#region src/core/text.ts
|
|
706
|
+
var Text = class Text extends VirtualNode {
|
|
707
|
+
static NodeType = "text";
|
|
708
|
+
constructor(description, parentNode) {
|
|
709
|
+
super(description, parentNode);
|
|
710
|
+
this.attachDOM();
|
|
711
|
+
}
|
|
712
|
+
get nodeType() {
|
|
713
|
+
return Text.NodeType;
|
|
714
|
+
}
|
|
715
|
+
attachDOM() {
|
|
716
|
+
this.ref = document.createTextNode(this.description.text);
|
|
717
|
+
}
|
|
718
|
+
};
|
|
719
|
+
//#endregion
|
|
720
|
+
//#region src/core/child-nodes.ts
|
|
721
|
+
const ChildNodes = {
|
|
722
|
+
set(parent, nodes) {
|
|
723
|
+
parent.childNodes = nodes;
|
|
724
|
+
for (const child of nodes) parent.ref.appendChild(child.ref);
|
|
725
|
+
},
|
|
726
|
+
insert(parent, child, index) {
|
|
727
|
+
const nodes = parent.childNodes ?? [];
|
|
728
|
+
if (index === void 0) index = nodes.length;
|
|
729
|
+
const nextChild = nodes[index];
|
|
730
|
+
nodes.splice(index, 0, child);
|
|
731
|
+
parent.childNodes = nodes;
|
|
732
|
+
parent.ref.insertBefore(child.ref, nextChild && nextChild.ref || null);
|
|
733
|
+
child.parentNode = parent;
|
|
734
|
+
},
|
|
735
|
+
replace(parent, child, node) {
|
|
736
|
+
const nodes = parent.childNodes ?? [];
|
|
737
|
+
const index = nodes.indexOf(child);
|
|
738
|
+
invariant(index >= 0, "Specified node is not a child of this element!");
|
|
739
|
+
nodes.splice(index, 1, node);
|
|
740
|
+
child.parentNode = null;
|
|
741
|
+
node.parentNode = parent;
|
|
742
|
+
child.ref.replaceWith(node.ref);
|
|
743
|
+
},
|
|
744
|
+
move(parent, child, from, to) {
|
|
745
|
+
const nodes = parent.childNodes ?? [];
|
|
746
|
+
invariant(nodes[from] === child, "Specified node is not a child of this element!");
|
|
747
|
+
nodes.splice(from, 1);
|
|
748
|
+
nodes.splice(to, 0, child);
|
|
749
|
+
const nextChild = nodes[to + 1];
|
|
750
|
+
parent.ref.insertBefore(child.ref, nextChild ? nextChild.ref : null);
|
|
751
|
+
},
|
|
752
|
+
remove(parent, child) {
|
|
753
|
+
const nodes = parent.childNodes ?? [];
|
|
754
|
+
const index = nodes.indexOf(child);
|
|
755
|
+
invariant(index >= 0, "Specified node is not a child of this element!");
|
|
756
|
+
nodes.splice(index, 1);
|
|
757
|
+
if (!nodes.length) parent.childNodes = void 0;
|
|
758
|
+
parent.ref.removeChild(child.ref);
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
//#endregion
|
|
762
|
+
//#region src/core/dom.ts
|
|
763
|
+
const DOM = {
|
|
764
|
+
setClassName(element, className) {
|
|
765
|
+
element.className = className;
|
|
766
|
+
},
|
|
767
|
+
setStyleProperty(element, name, value) {
|
|
768
|
+
if (name.startsWith("--")) element.style.setProperty(name, ` ${value}`);
|
|
769
|
+
else element.style[name] = value;
|
|
770
|
+
},
|
|
771
|
+
removeStyleProperty(element, name) {
|
|
772
|
+
if (name.startsWith("--")) element.style.removeProperty(name);
|
|
773
|
+
else element.style[name] = null;
|
|
774
|
+
},
|
|
775
|
+
setAttribute(element, name, value, isCustom) {
|
|
776
|
+
const attr = isCustom ? name : getAttributeName(name);
|
|
777
|
+
element.setAttribute(attr, value);
|
|
778
|
+
},
|
|
779
|
+
removeAttribute(element, name, isCustom) {
|
|
780
|
+
const attr = isCustom ? name : getAttributeName(name);
|
|
781
|
+
element.removeAttribute(attr);
|
|
782
|
+
},
|
|
783
|
+
setDataAttribute(element, name, value) {
|
|
784
|
+
element.dataset[name] = value;
|
|
785
|
+
},
|
|
786
|
+
removeDataAttribute(element, name) {
|
|
787
|
+
delete element.dataset[name];
|
|
788
|
+
},
|
|
789
|
+
addListener(element, name, listener, isCustom) {
|
|
790
|
+
const event = isCustom ? name : getEventName(name);
|
|
791
|
+
element.addEventListener(event, listener);
|
|
792
|
+
},
|
|
793
|
+
removeListener(element, name, listener, isCustom) {
|
|
794
|
+
const event = isCustom ? name : getEventName(name);
|
|
795
|
+
element.removeEventListener(event, listener);
|
|
796
|
+
},
|
|
797
|
+
setProperty(element, key, value) {
|
|
798
|
+
element[key] = value;
|
|
799
|
+
},
|
|
800
|
+
deleteProperty(element, key) {
|
|
801
|
+
delete element[key];
|
|
802
|
+
},
|
|
803
|
+
/**
|
|
804
|
+
* Creates a new DOM Element based on the specified description.
|
|
805
|
+
*/
|
|
806
|
+
createElement(description) {
|
|
807
|
+
const element = document.createElement(description.name);
|
|
808
|
+
if (description.class) this.setClassName(element, description.class);
|
|
809
|
+
for (const [name, value] of Object.entries(description.style ?? {})) this.setStyleProperty(element, name, value);
|
|
810
|
+
for (const [name, listener] of Object.entries(description.listeners ?? {})) this.addListener(element, name, listener, false);
|
|
811
|
+
for (const [name, value] of Object.entries(description.attrs ?? {})) this.setAttribute(element, name, value, false);
|
|
812
|
+
for (const [name, value] of Object.entries(description.dataset ?? {})) this.setDataAttribute(element, name, value);
|
|
813
|
+
for (const [key, value] of Object.entries(description.properties ?? {})) this.setProperty(element, key, value);
|
|
814
|
+
for (const [name, value] of Object.entries(description.custom?.attrs ?? {})) this.setAttribute(element, name, value, true);
|
|
815
|
+
for (const [name, listener] of Object.entries(description.custom?.listeners ?? {})) this.addListener(element, name, listener, true);
|
|
816
|
+
return element;
|
|
817
|
+
}
|
|
818
|
+
};
|
|
819
|
+
//#endregion
|
|
820
|
+
//#region src/core/virtual-element.ts
|
|
821
|
+
var VirtualElement = class VirtualElement extends VirtualNode {
|
|
822
|
+
static NodeType = "element";
|
|
823
|
+
constructor(description, parent, context) {
|
|
824
|
+
super(description, parent, context);
|
|
825
|
+
this.attachDOM();
|
|
826
|
+
}
|
|
827
|
+
get childNodes() {
|
|
828
|
+
return this.children;
|
|
829
|
+
}
|
|
830
|
+
set childNodes(nodes) {
|
|
831
|
+
if (nodes) this.children = nodes;
|
|
832
|
+
else delete this.children;
|
|
833
|
+
}
|
|
834
|
+
get nodeType() {
|
|
835
|
+
return VirtualElement.NodeType;
|
|
836
|
+
}
|
|
837
|
+
isCompatible(node) {
|
|
838
|
+
return super.isCompatible(node) && this.description.name === node.description.name;
|
|
839
|
+
}
|
|
840
|
+
setChildNodes(nodes) {
|
|
841
|
+
ChildNodes.set(this, nodes);
|
|
842
|
+
}
|
|
843
|
+
insertChild(child, index) {
|
|
844
|
+
ChildNodes.insert(this, child, index);
|
|
845
|
+
}
|
|
846
|
+
replaceChild(child, node) {
|
|
847
|
+
ChildNodes.replace(this, child, node);
|
|
848
|
+
}
|
|
849
|
+
moveChild(child, from, to) {
|
|
850
|
+
ChildNodes.move(this, child, from, to);
|
|
851
|
+
}
|
|
852
|
+
removeChild(child) {
|
|
853
|
+
ChildNodes.remove(this, child);
|
|
854
|
+
}
|
|
855
|
+
attachDOM() {
|
|
856
|
+
this.ref = DOM.createElement(this.description);
|
|
857
|
+
}
|
|
858
|
+
};
|
|
859
|
+
//#endregion
|
|
860
|
+
//#region src/core/created-roots.ts
|
|
861
|
+
let createdRoots = null;
|
|
862
|
+
const CreatedRoots = {
|
|
863
|
+
/**
|
|
864
|
+
* Calls the function, destroying the roots it creates when it throws.
|
|
865
|
+
* They are neither attached nor destroyed by the lifecycle then, but
|
|
866
|
+
* would stay tracked, with the plugins installed.
|
|
867
|
+
*/
|
|
868
|
+
destroyingRootsOnError(fn) {
|
|
869
|
+
const outerRoots = createdRoots;
|
|
870
|
+
const roots = [];
|
|
871
|
+
createdRoots = roots;
|
|
872
|
+
try {
|
|
873
|
+
const result = fn();
|
|
874
|
+
outerRoots?.push(...roots);
|
|
875
|
+
return result;
|
|
876
|
+
} catch (error) {
|
|
877
|
+
for (const root of roots.reverse()) root.destroy();
|
|
878
|
+
throw error;
|
|
879
|
+
} finally {
|
|
880
|
+
createdRoots = outerRoots;
|
|
881
|
+
}
|
|
882
|
+
},
|
|
883
|
+
/**
|
|
884
|
+
* Records a root being created, see destroyingRootsOnError().
|
|
885
|
+
*/
|
|
886
|
+
collectRoot(root) {
|
|
887
|
+
createdRoots?.push(root);
|
|
888
|
+
}
|
|
889
|
+
};
|
|
890
|
+
//#endregion
|
|
891
|
+
//#region src/core/patch.ts
|
|
892
|
+
const Type = {
|
|
893
|
+
INIT_ROOT_COMPONENT: "init-root-component",
|
|
894
|
+
UPDATE_NODE: "update-node",
|
|
895
|
+
UPDATE_ROOT: "update-root",
|
|
896
|
+
SET_ATTRIBUTE: "set-attribute",
|
|
897
|
+
REMOVE_ATTRIBUTE: "remove-attribute",
|
|
898
|
+
SET_DATA_ATTRIBUTE: "set-data-attribute",
|
|
899
|
+
REMOVE_DATA_ATTRIBUTE: "remove-data-attribute",
|
|
900
|
+
SET_STYLE_PROPERTY: "set-style-property",
|
|
901
|
+
REMOVE_STYLE_PROPERTY: "remove-style-property",
|
|
902
|
+
SET_CLASS_NAME: "set-class-name",
|
|
903
|
+
ADD_LISTENER: "add-listener",
|
|
904
|
+
REPLACE_LISTENER: "replace-listener",
|
|
905
|
+
REMOVE_LISTENER: "remove-listener",
|
|
906
|
+
SET_PROPERTY: "set-property",
|
|
907
|
+
DELETE_PROPERTY: "delete-property",
|
|
908
|
+
INSERT_CHILD: "insert-child",
|
|
909
|
+
REPLACE_CHILD: "replace-child",
|
|
910
|
+
MOVE_CHILD: "move-child",
|
|
911
|
+
REMOVE_CHILD: "remove-child",
|
|
912
|
+
SET_CONTENT: "set-content"
|
|
913
|
+
};
|
|
914
|
+
const Patch = {
|
|
915
|
+
Type,
|
|
916
|
+
/**
|
|
917
|
+
* Applies the change to the virtual and rendered DOM.
|
|
918
|
+
*/
|
|
919
|
+
apply(patch) {
|
|
920
|
+
switch (patch.type) {
|
|
921
|
+
case Type.INIT_ROOT_COMPONENT:
|
|
922
|
+
(patch.root.container ? patch.root.container : patch.root.shadow).appendChild(patch.root.content.ref);
|
|
923
|
+
return;
|
|
924
|
+
case Type.UPDATE_NODE:
|
|
925
|
+
patch.node.description = patch.description;
|
|
926
|
+
return;
|
|
927
|
+
case Type.UPDATE_ROOT:
|
|
928
|
+
patch.root.update(patch.description);
|
|
929
|
+
return;
|
|
930
|
+
case Type.SET_ATTRIBUTE: {
|
|
931
|
+
const { target, name, value, isCustom } = patch;
|
|
932
|
+
DOM.setAttribute(target.ref, name, value, isCustom);
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
case Type.REMOVE_ATTRIBUTE:
|
|
936
|
+
DOM.removeAttribute(patch.target.ref, patch.name, patch.isCustom);
|
|
937
|
+
return;
|
|
938
|
+
case Type.SET_DATA_ATTRIBUTE:
|
|
939
|
+
DOM.setDataAttribute(patch.target.ref, patch.name, patch.value);
|
|
940
|
+
return;
|
|
941
|
+
case Type.REMOVE_DATA_ATTRIBUTE:
|
|
942
|
+
DOM.removeDataAttribute(patch.target.ref, patch.name);
|
|
943
|
+
return;
|
|
944
|
+
case Type.SET_STYLE_PROPERTY:
|
|
945
|
+
DOM.setStyleProperty(patch.target.ref, patch.property, patch.value);
|
|
946
|
+
return;
|
|
947
|
+
case Type.REMOVE_STYLE_PROPERTY:
|
|
948
|
+
DOM.removeStyleProperty(patch.target.ref, patch.property);
|
|
949
|
+
return;
|
|
950
|
+
case Type.SET_CLASS_NAME:
|
|
951
|
+
DOM.setClassName(patch.target.ref, patch.className);
|
|
952
|
+
return;
|
|
953
|
+
case Type.ADD_LISTENER: {
|
|
954
|
+
const { target, name, listener, isCustom } = patch;
|
|
955
|
+
DOM.addListener(target.ref, name, listener, isCustom);
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
case Type.REPLACE_LISTENER: {
|
|
959
|
+
const { target, name, removed, added, isCustom } = patch;
|
|
960
|
+
DOM.removeListener(target.ref, name, removed, isCustom);
|
|
961
|
+
DOM.addListener(target.ref, name, added, isCustom);
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
case Type.REMOVE_LISTENER: {
|
|
965
|
+
const { target, name, listener, isCustom } = patch;
|
|
966
|
+
DOM.removeListener(target.ref, name, listener, isCustom);
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
case Type.SET_PROPERTY:
|
|
970
|
+
DOM.setProperty(patch.target.ref, patch.key, patch.value);
|
|
971
|
+
return;
|
|
972
|
+
case Type.DELETE_PROPERTY:
|
|
973
|
+
DOM.deleteProperty(patch.target.ref, patch.key);
|
|
974
|
+
return;
|
|
975
|
+
case Type.INSERT_CHILD:
|
|
976
|
+
patch.parent.insertChild(patch.node, patch.at);
|
|
977
|
+
return;
|
|
978
|
+
case Type.REPLACE_CHILD:
|
|
979
|
+
patch.parent.replaceChild(patch.child, patch.node);
|
|
980
|
+
return;
|
|
981
|
+
case Type.MOVE_CHILD:
|
|
982
|
+
patch.parent.moveChild(patch.child, patch.from, patch.to);
|
|
983
|
+
return;
|
|
984
|
+
case Type.REMOVE_CHILD:
|
|
985
|
+
patch.parent.removeChild(patch.child);
|
|
986
|
+
return;
|
|
987
|
+
case Type.SET_CONTENT:
|
|
988
|
+
patch.parent.setContent(patch.node);
|
|
989
|
+
return;
|
|
990
|
+
default: throw new Error(`Unsupported patch type: ${patch.type}`);
|
|
991
|
+
}
|
|
992
|
+
},
|
|
993
|
+
initRootComponent(root) {
|
|
994
|
+
return {
|
|
995
|
+
type: Type.INIT_ROOT_COMPONENT,
|
|
996
|
+
root
|
|
997
|
+
};
|
|
998
|
+
},
|
|
999
|
+
updateNode(node, description) {
|
|
1000
|
+
return {
|
|
1001
|
+
type: Type.UPDATE_NODE,
|
|
1002
|
+
node,
|
|
1003
|
+
prevDescription: node.description,
|
|
1004
|
+
description
|
|
1005
|
+
};
|
|
1006
|
+
},
|
|
1007
|
+
updateRoot(root, description) {
|
|
1008
|
+
return {
|
|
1009
|
+
type: Type.UPDATE_ROOT,
|
|
1010
|
+
root,
|
|
1011
|
+
description
|
|
1012
|
+
};
|
|
1013
|
+
},
|
|
1014
|
+
insertChild(node, at, parent) {
|
|
1015
|
+
return {
|
|
1016
|
+
type: Type.INSERT_CHILD,
|
|
1017
|
+
node,
|
|
1018
|
+
at,
|
|
1019
|
+
parent
|
|
1020
|
+
};
|
|
1021
|
+
},
|
|
1022
|
+
moveChild(child, from, to, parent) {
|
|
1023
|
+
return {
|
|
1024
|
+
type: Type.MOVE_CHILD,
|
|
1025
|
+
child,
|
|
1026
|
+
from,
|
|
1027
|
+
to,
|
|
1028
|
+
parent
|
|
1029
|
+
};
|
|
1030
|
+
},
|
|
1031
|
+
replaceChild(child, node, parent) {
|
|
1032
|
+
return {
|
|
1033
|
+
type: Type.REPLACE_CHILD,
|
|
1034
|
+
child,
|
|
1035
|
+
node,
|
|
1036
|
+
parent
|
|
1037
|
+
};
|
|
1038
|
+
},
|
|
1039
|
+
removeChild(child, at, parent) {
|
|
1040
|
+
return {
|
|
1041
|
+
type: Type.REMOVE_CHILD,
|
|
1042
|
+
child,
|
|
1043
|
+
at,
|
|
1044
|
+
parent
|
|
1045
|
+
};
|
|
1046
|
+
},
|
|
1047
|
+
setContent(node, parent) {
|
|
1048
|
+
return {
|
|
1049
|
+
type: Type.SET_CONTENT,
|
|
1050
|
+
node,
|
|
1051
|
+
child: parent.content,
|
|
1052
|
+
parent
|
|
1053
|
+
};
|
|
1054
|
+
},
|
|
1055
|
+
setAttribute(name, value, target, isCustom) {
|
|
1056
|
+
return {
|
|
1057
|
+
type: Type.SET_ATTRIBUTE,
|
|
1058
|
+
name,
|
|
1059
|
+
value,
|
|
1060
|
+
target,
|
|
1061
|
+
isCustom
|
|
1062
|
+
};
|
|
1063
|
+
},
|
|
1064
|
+
removeAttribute(name, target, isCustom) {
|
|
1065
|
+
return {
|
|
1066
|
+
type: Type.REMOVE_ATTRIBUTE,
|
|
1067
|
+
name,
|
|
1068
|
+
target,
|
|
1069
|
+
isCustom
|
|
1070
|
+
};
|
|
1071
|
+
},
|
|
1072
|
+
setDataAttribute(name, value, target) {
|
|
1073
|
+
return {
|
|
1074
|
+
type: Type.SET_DATA_ATTRIBUTE,
|
|
1075
|
+
name,
|
|
1076
|
+
value,
|
|
1077
|
+
target
|
|
1078
|
+
};
|
|
1079
|
+
},
|
|
1080
|
+
removeDataAttribute(name, target) {
|
|
1081
|
+
return {
|
|
1082
|
+
type: Type.REMOVE_DATA_ATTRIBUTE,
|
|
1083
|
+
name,
|
|
1084
|
+
target
|
|
1085
|
+
};
|
|
1086
|
+
},
|
|
1087
|
+
setStyleProperty(property, value, target) {
|
|
1088
|
+
return {
|
|
1089
|
+
type: Type.SET_STYLE_PROPERTY,
|
|
1090
|
+
property,
|
|
1091
|
+
value,
|
|
1092
|
+
target
|
|
1093
|
+
};
|
|
1094
|
+
},
|
|
1095
|
+
removeStyleProperty(property, target) {
|
|
1096
|
+
return {
|
|
1097
|
+
type: Type.REMOVE_STYLE_PROPERTY,
|
|
1098
|
+
property,
|
|
1099
|
+
target
|
|
1100
|
+
};
|
|
1101
|
+
},
|
|
1102
|
+
setClassName(className, target) {
|
|
1103
|
+
return {
|
|
1104
|
+
type: Type.SET_CLASS_NAME,
|
|
1105
|
+
className,
|
|
1106
|
+
target
|
|
1107
|
+
};
|
|
1108
|
+
},
|
|
1109
|
+
addListener(name, listener, target, isCustom) {
|
|
1110
|
+
return {
|
|
1111
|
+
type: Type.ADD_LISTENER,
|
|
1112
|
+
name,
|
|
1113
|
+
listener,
|
|
1114
|
+
target,
|
|
1115
|
+
isCustom
|
|
1116
|
+
};
|
|
1117
|
+
},
|
|
1118
|
+
replaceListener(name, removed, added, target, isCustom) {
|
|
1119
|
+
return {
|
|
1120
|
+
type: Type.REPLACE_LISTENER,
|
|
1121
|
+
name,
|
|
1122
|
+
removed,
|
|
1123
|
+
added,
|
|
1124
|
+
target,
|
|
1125
|
+
isCustom
|
|
1126
|
+
};
|
|
1127
|
+
},
|
|
1128
|
+
removeListener(name, listener, target, isCustom) {
|
|
1129
|
+
return {
|
|
1130
|
+
type: Type.REMOVE_LISTENER,
|
|
1131
|
+
name,
|
|
1132
|
+
listener,
|
|
1133
|
+
target,
|
|
1134
|
+
isCustom
|
|
1135
|
+
};
|
|
1136
|
+
},
|
|
1137
|
+
setProperty(key, value, target) {
|
|
1138
|
+
return {
|
|
1139
|
+
type: Type.SET_PROPERTY,
|
|
1140
|
+
key,
|
|
1141
|
+
value,
|
|
1142
|
+
target
|
|
1143
|
+
};
|
|
1144
|
+
},
|
|
1145
|
+
deleteProperty(key, target) {
|
|
1146
|
+
return {
|
|
1147
|
+
type: Type.DELETE_PROPERTY,
|
|
1148
|
+
key,
|
|
1149
|
+
target
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1152
|
+
};
|
|
1153
|
+
//#endregion
|
|
1154
|
+
//#region src/core/lifecycle.ts
|
|
1155
|
+
let hookErrors = null;
|
|
1156
|
+
const callHook = (hook) => {
|
|
1157
|
+
if (!hookErrors) return hook();
|
|
1158
|
+
try {
|
|
1159
|
+
hook();
|
|
1160
|
+
} catch (error) {
|
|
1161
|
+
hookErrors.push(error);
|
|
1162
|
+
}
|
|
1163
|
+
};
|
|
1164
|
+
const callIgnoringCommands = (component, hook) => {
|
|
1165
|
+
const { dispatcher } = component;
|
|
1166
|
+
const { mode } = dispatcher;
|
|
1167
|
+
dispatcher.ignoreIncoming();
|
|
1168
|
+
try {
|
|
1169
|
+
callHook(hook);
|
|
1170
|
+
} finally {
|
|
1171
|
+
dispatcher.mode = mode;
|
|
1172
|
+
}
|
|
1173
|
+
};
|
|
1174
|
+
const throwErrors = (errors, message) => {
|
|
1175
|
+
if (errors.length === 1) throw errors[0];
|
|
1176
|
+
if (errors.length > 1) throw new AggregateError(errors, message);
|
|
1177
|
+
};
|
|
1178
|
+
const Lifecycle = {
|
|
1179
|
+
/**
|
|
1180
|
+
* Calls the hooks in the callback, collecting the errors they throw in
|
|
1181
|
+
* the given list instead of stopping at the first one.
|
|
1182
|
+
*/
|
|
1183
|
+
collectingErrors(errors, callback) {
|
|
1184
|
+
const prevErrors = hookErrors;
|
|
1185
|
+
hookErrors = errors;
|
|
1186
|
+
try {
|
|
1187
|
+
callback();
|
|
1188
|
+
} finally {
|
|
1189
|
+
hookErrors = prevErrors;
|
|
1190
|
+
}
|
|
1191
|
+
},
|
|
1192
|
+
onComponentCreated(component) {
|
|
1193
|
+
if (component.hasOwnMethod("onCreated")) callHook(() => component.onCreated.call(component.sandbox));
|
|
1194
|
+
if (component.content) this.onNodeCreated(component.content);
|
|
1195
|
+
},
|
|
1196
|
+
onElementCreated(element) {
|
|
1197
|
+
if (element.childNodes) for (const child of element.childNodes) this.onNodeCreated(child);
|
|
1198
|
+
},
|
|
1199
|
+
onNodeCreated(node) {
|
|
1200
|
+
if (node.isElement()) return this.onElementCreated(node);
|
|
1201
|
+
else if (node.isComponent() && !node.isRoot()) return this.onComponentCreated(node);
|
|
1202
|
+
},
|
|
1203
|
+
onRootCreated(root) {
|
|
1204
|
+
if (root.hasOwnMethod("onCreated")) callHook(() => root.onCreated.call(root.sandbox));
|
|
1205
|
+
const { childNodes } = root;
|
|
1206
|
+
if (childNodes) for (const child of childNodes) this.onNodeCreated(child);
|
|
1207
|
+
},
|
|
1208
|
+
onComponentAttached(component) {
|
|
1209
|
+
if (component.content) this.onNodeAttached(component.content);
|
|
1210
|
+
if (component.hasOwnMethod("onAttached")) callHook(() => component.onAttached.call(component.sandbox));
|
|
1211
|
+
},
|
|
1212
|
+
onElementAttached(element) {
|
|
1213
|
+
if (element.childNodes) for (const child of element.childNodes) this.onNodeAttached(child);
|
|
1214
|
+
},
|
|
1215
|
+
onNodeAttached(node) {
|
|
1216
|
+
if (node.isElement()) return this.onElementAttached(node);
|
|
1217
|
+
else if (node.isComponent() && !node.isRoot()) return this.onComponentAttached(node);
|
|
1218
|
+
},
|
|
1219
|
+
onNodeReceivedDescription(node, description) {
|
|
1220
|
+
if (node.isComponent()) this.onComponentReceivedProps(node, description.props);
|
|
1221
|
+
},
|
|
1222
|
+
onNodeUpdated(node, prevDescription) {
|
|
1223
|
+
if (node.isComponent()) this.onComponentUpdated(node, prevDescription.props);
|
|
1224
|
+
},
|
|
1225
|
+
onRootAttached(root) {
|
|
1226
|
+
const { childNodes } = root;
|
|
1227
|
+
if (childNodes) for (const child of childNodes) this.onNodeAttached(child);
|
|
1228
|
+
if (root.hasOwnMethod("onAttached")) callHook(() => root.onAttached.call(root.sandbox));
|
|
1229
|
+
},
|
|
1230
|
+
onComponentReceivedProps(component, nextProps = {}) {
|
|
1231
|
+
if (component.hasOwnMethod("onPropsReceived")) callHook(() => component.onPropsReceived.call(component.sandbox, nextProps));
|
|
1232
|
+
},
|
|
1233
|
+
onComponentUpdated(component, prevProps = {}) {
|
|
1234
|
+
if (component.hasOwnMethod("onUpdated")) callHook(() => component.onUpdated.call(component.sandbox, prevProps));
|
|
1235
|
+
},
|
|
1236
|
+
onComponentDestroyed(component) {
|
|
1237
|
+
component.destroy();
|
|
1238
|
+
if (component.hasOwnMethod("onDestroyed")) callIgnoringCommands(component, () => component.onDestroyed.call(component.sandbox));
|
|
1239
|
+
if (component.content) this.onNodeDestroyed(component.content);
|
|
1240
|
+
if (component.isRoot()) this.onElementDestroyed(component);
|
|
1241
|
+
},
|
|
1242
|
+
onElementDestroyed(element) {
|
|
1243
|
+
if (element.childNodes) for (const child of element.childNodes) this.onNodeDestroyed(child);
|
|
1244
|
+
},
|
|
1245
|
+
onNodeDestroyed(node) {
|
|
1246
|
+
if (node.isElement()) return this.onElementDestroyed(node);
|
|
1247
|
+
else if (node.isComponent() && !node.isRoot()) return this.onComponentDestroyed(node);
|
|
1248
|
+
},
|
|
1249
|
+
onComponentDetached(component) {
|
|
1250
|
+
if (component.isRoot()) this.onElementDetached(component);
|
|
1251
|
+
if (component.content) this.onNodeDetached(component.content);
|
|
1252
|
+
if (component.hasOwnMethod("onDetached")) callIgnoringCommands(component, () => component.onDetached.call(component.sandbox));
|
|
1253
|
+
},
|
|
1254
|
+
onElementDetached(element) {
|
|
1255
|
+
if (element.childNodes) for (const child of element.childNodes) this.onNodeDetached(child);
|
|
1256
|
+
},
|
|
1257
|
+
onNodeDetached(node) {
|
|
1258
|
+
if (node.isElement()) {
|
|
1259
|
+
this.onElementDetached(node);
|
|
1260
|
+
node.parentNode = null;
|
|
1261
|
+
} else if (node.isComponent() && !node.isRoot()) {
|
|
1262
|
+
this.onComponentDetached(node);
|
|
1263
|
+
node.parentNode = null;
|
|
1264
|
+
}
|
|
1265
|
+
},
|
|
1266
|
+
beforeUpdate(patches) {
|
|
1267
|
+
for (const patch of patches) this.beforePatchApplied(patch);
|
|
1268
|
+
},
|
|
1269
|
+
beforePatchApplied(patch) {
|
|
1270
|
+
const Type = Patch.Type;
|
|
1271
|
+
switch (patch.type) {
|
|
1272
|
+
case Type.INIT_ROOT_COMPONENT:
|
|
1273
|
+
this.onRootCreated(patch.root);
|
|
1274
|
+
return;
|
|
1275
|
+
case Type.INSERT_CHILD:
|
|
1276
|
+
this.onNodeCreated(patch.node);
|
|
1277
|
+
return;
|
|
1278
|
+
case Type.REPLACE_CHILD:
|
|
1279
|
+
case Type.SET_CONTENT:
|
|
1280
|
+
this.onNodeDestroyed(patch.child);
|
|
1281
|
+
this.onNodeCreated(patch.node);
|
|
1282
|
+
return;
|
|
1283
|
+
case Type.REMOVE_CHILD:
|
|
1284
|
+
this.onNodeDestroyed(patch.child);
|
|
1285
|
+
return;
|
|
1286
|
+
case Type.UPDATE_NODE:
|
|
1287
|
+
this.onNodeReceivedDescription(patch.node, patch.description);
|
|
1288
|
+
return;
|
|
1289
|
+
}
|
|
1290
|
+
},
|
|
1291
|
+
afterUpdate(patches) {
|
|
1292
|
+
patches = [...patches].reverse();
|
|
1293
|
+
for (const patch of patches) this.afterPatchApplied(patch);
|
|
1294
|
+
},
|
|
1295
|
+
afterPatchApplied(patch) {
|
|
1296
|
+
const Type = Patch.Type;
|
|
1297
|
+
switch (patch.type) {
|
|
1298
|
+
case Type.INIT_ROOT_COMPONENT:
|
|
1299
|
+
this.onRootAttached(patch.root);
|
|
1300
|
+
return;
|
|
1301
|
+
case Type.INSERT_CHILD:
|
|
1302
|
+
this.onNodeAttached(patch.node);
|
|
1303
|
+
return;
|
|
1304
|
+
case Type.REPLACE_CHILD:
|
|
1305
|
+
case Type.SET_CONTENT:
|
|
1306
|
+
this.onNodeDetached(patch.child);
|
|
1307
|
+
this.onNodeAttached(patch.node);
|
|
1308
|
+
return;
|
|
1309
|
+
case Type.REMOVE_CHILD:
|
|
1310
|
+
this.onNodeDetached(patch.child);
|
|
1311
|
+
return;
|
|
1312
|
+
case Type.UPDATE_NODE:
|
|
1313
|
+
this.onNodeUpdated(patch.node, patch.prevDescription);
|
|
1314
|
+
return;
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
};
|
|
1318
|
+
//#endregion
|
|
1319
|
+
//#region src/core/plugins.ts
|
|
1320
|
+
const Permission = {
|
|
1321
|
+
LISTEN_FOR_UPDATES: "listen-for-updates",
|
|
1322
|
+
REGISTER_METHOD: "register-method",
|
|
1323
|
+
INJECT_STYLESHEETS: "inject-stylesheets"
|
|
1324
|
+
};
|
|
1325
|
+
var Plugin = class {
|
|
1326
|
+
constructor(manifest) {
|
|
1327
|
+
runtime().assert(typeof manifest.name === "string" && manifest.name.length, "Plugin name must be a non-empty string!");
|
|
1328
|
+
Object.assign(this, manifest);
|
|
1329
|
+
this.origin = manifest;
|
|
1330
|
+
if (this.permissions === void 0) this.permissions = [];
|
|
1331
|
+
else {
|
|
1332
|
+
runtime().assert(Array.isArray(this.permissions), "Plugin permissions must be an array");
|
|
1333
|
+
this.permissions = this.permissions.filter((permission) => Object.values(Permission).includes(permission));
|
|
1334
|
+
}
|
|
1335
|
+
const sandbox = this.createSandbox();
|
|
1336
|
+
if (typeof manifest.register === "function") this.register = () => manifest.register(sandbox);
|
|
1337
|
+
if (typeof manifest.install === "function") this.install = (root) => {
|
|
1338
|
+
const uninstall = manifest.install(root);
|
|
1339
|
+
runtime().assert(typeof uninstall === "function", "The plugin installation must return the uninstall function!");
|
|
1340
|
+
return uninstall;
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
isListener() {
|
|
1344
|
+
return this.permissions.includes(Permission.LISTEN_FOR_UPDATES);
|
|
1345
|
+
}
|
|
1346
|
+
isStylesheetProvider() {
|
|
1347
|
+
return this.permissions.includes(Permission.INJECT_STYLESHEETS);
|
|
1348
|
+
}
|
|
1349
|
+
createSandbox() {
|
|
1350
|
+
const sandbox = {};
|
|
1351
|
+
for (const permission of this.permissions) switch (permission) {
|
|
1352
|
+
case Permission.REGISTER_METHOD: sandbox.registerMethod = (name) => Sandbox.registerPluginMethod(name);
|
|
1353
|
+
}
|
|
1354
|
+
return sandbox;
|
|
1355
|
+
}
|
|
1356
|
+
};
|
|
1357
|
+
var Registry = class {
|
|
1358
|
+
constructor() {
|
|
1359
|
+
this.plugins = /* @__PURE__ */ new Map();
|
|
1360
|
+
this.cache = { listeners: [] };
|
|
1361
|
+
this[Symbol.iterator] = () => this.plugins.values()[Symbol.iterator]();
|
|
1362
|
+
}
|
|
1363
|
+
/**
|
|
1364
|
+
* Adds the plugin to the registry
|
|
1365
|
+
*/
|
|
1366
|
+
add(plugin) {
|
|
1367
|
+
runtime().assert(!this.isRegistered(plugin.name), `Plugin '${plugin.name}' is already registered!`);
|
|
1368
|
+
this.plugins.set(plugin.name, plugin);
|
|
1369
|
+
this.updateCache();
|
|
1370
|
+
}
|
|
1371
|
+
/**
|
|
1372
|
+
* Removes the plugin with specified name from the registry.
|
|
1373
|
+
*/
|
|
1374
|
+
remove(name) {
|
|
1375
|
+
this.plugins.delete(name);
|
|
1376
|
+
this.updateCache();
|
|
1377
|
+
}
|
|
1378
|
+
/**
|
|
1379
|
+
* Checks if plugin with specified name exists in the registry.
|
|
1380
|
+
*/
|
|
1381
|
+
isRegistered(name) {
|
|
1382
|
+
return this.plugins.has(name);
|
|
1383
|
+
}
|
|
1384
|
+
/**
|
|
1385
|
+
* Updates the cache.
|
|
1386
|
+
*/
|
|
1387
|
+
updateCache() {
|
|
1388
|
+
const plugins = [...this.plugins.values()];
|
|
1389
|
+
this.cache.listeners = plugins.filter((plugin) => plugin.isListener());
|
|
1390
|
+
}
|
|
1391
|
+
};
|
|
1392
|
+
var Plugins = class {
|
|
1393
|
+
static Plugin = Plugin;
|
|
1394
|
+
constructor(root) {
|
|
1395
|
+
this.root = root;
|
|
1396
|
+
this.registry = new Registry();
|
|
1397
|
+
this.uninstalls = /* @__PURE__ */ new Map();
|
|
1398
|
+
this[Symbol.iterator] = () => this.registry[Symbol.iterator]();
|
|
1399
|
+
}
|
|
1400
|
+
/**
|
|
1401
|
+
* Creates a Plugin instance from the manifest object and registers it.
|
|
1402
|
+
*/
|
|
1403
|
+
register(plugin) {
|
|
1404
|
+
if (!(plugin instanceof Plugin)) plugin = new Plugin(plugin);
|
|
1405
|
+
if (plugin.register) plugin.register();
|
|
1406
|
+
this.registry.add(plugin);
|
|
1407
|
+
}
|
|
1408
|
+
installAll() {
|
|
1409
|
+
for (const plugin of this.registry) this.install(plugin);
|
|
1410
|
+
}
|
|
1411
|
+
install(plugin) {
|
|
1412
|
+
if (this.root && plugin.install) {
|
|
1413
|
+
const uninstall = plugin.install(this.root);
|
|
1414
|
+
this.uninstalls.set(plugin.name, uninstall);
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
/**
|
|
1418
|
+
* Removes the plugin from the registry and invokes it's uninstall method
|
|
1419
|
+
* if present.
|
|
1420
|
+
*/
|
|
1421
|
+
uninstall(name) {
|
|
1422
|
+
const uninstall = this.uninstalls.get(name);
|
|
1423
|
+
this.uninstalls.delete(name);
|
|
1424
|
+
this.registry.remove(name);
|
|
1425
|
+
if (uninstall) uninstall();
|
|
1426
|
+
}
|
|
1427
|
+
/**
|
|
1428
|
+
* Uninstalls all the plugins from the registry.
|
|
1429
|
+
*/
|
|
1430
|
+
destroy() {
|
|
1431
|
+
for (const plugin of this.registry) this.uninstall(plugin.name);
|
|
1432
|
+
this.root = null;
|
|
1433
|
+
}
|
|
1434
|
+
/**
|
|
1435
|
+
* Invokes listener methods on registered listener plugins.
|
|
1436
|
+
*/
|
|
1437
|
+
notify(action, event) {
|
|
1438
|
+
switch (action) {
|
|
1439
|
+
case "before-update":
|
|
1440
|
+
for (const listener of this.registry.cache.listeners) listener.onBeforeUpdate(event);
|
|
1441
|
+
return;
|
|
1442
|
+
case "after-update":
|
|
1443
|
+
for (const listener of this.registry.cache.listeners) listener.onAfterUpdate(event);
|
|
1444
|
+
return;
|
|
1445
|
+
default: throw new Error(`Unknown action: ${action}`);
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
};
|
|
1449
|
+
//#endregion
|
|
1450
|
+
//#region src/core/custom-element.ts
|
|
1451
|
+
const cssImports = (paths) => paths.map((path) => `@import url(${path});`).join("\n");
|
|
1452
|
+
var ComponentElement = class extends HTMLElement {
|
|
1453
|
+
constructor(root) {
|
|
1454
|
+
super();
|
|
1455
|
+
this.$root = root;
|
|
1456
|
+
this.stylesheets = [];
|
|
1457
|
+
this.styleElement = null;
|
|
1458
|
+
this.stylesheetsLoaded = Promise.resolve();
|
|
1459
|
+
addPluginsAPI(this);
|
|
1460
|
+
root.shadow = this.attachShadow({ mode: "open" });
|
|
1461
|
+
const stylesheets = root.getStylesheets();
|
|
1462
|
+
const init = () => {
|
|
1463
|
+
root.init().catch((error) => root.markAsFailed(error));
|
|
1464
|
+
};
|
|
1465
|
+
if (stylesheets.length) this.loadStylesheets(stylesheets).then(init, (error) => root.markAsFailed(error));
|
|
1466
|
+
else init();
|
|
1467
|
+
}
|
|
1468
|
+
/**
|
|
1469
|
+
* Replaces the stylesheets imported in the shadow root, keeping the
|
|
1470
|
+
* previous ones until the new ones are loaded.
|
|
1471
|
+
*/
|
|
1472
|
+
loadStylesheets(stylesheets) {
|
|
1473
|
+
const previous = this.styleElement;
|
|
1474
|
+
this.stylesheets = stylesheets;
|
|
1475
|
+
let loaded = Promise.resolve();
|
|
1476
|
+
if (stylesheets.length) {
|
|
1477
|
+
const style = document.createElement("style");
|
|
1478
|
+
style.textContent = cssImports(stylesheets);
|
|
1479
|
+
loaded = new Promise((resolve, reject) => {
|
|
1480
|
+
style.onload = () => resolve();
|
|
1481
|
+
style.onerror = () => reject(/* @__PURE__ */ new Error(`Error loading stylesheets: ${stylesheets.join(", ")}`));
|
|
1482
|
+
});
|
|
1483
|
+
if (previous) previous.after(style);
|
|
1484
|
+
else this.shadowRoot.prepend(style);
|
|
1485
|
+
this.styleElement = style;
|
|
1486
|
+
} else this.styleElement = null;
|
|
1487
|
+
if (previous) Promise.allSettled([this.stylesheetsLoaded, loaded]).then(() => previous.remove());
|
|
1488
|
+
this.stylesheetsLoaded = loaded;
|
|
1489
|
+
return loaded;
|
|
1490
|
+
}
|
|
1491
|
+
get isComponentElement() {
|
|
1492
|
+
return true;
|
|
1493
|
+
}
|
|
1494
|
+
connectedCallback() {
|
|
1495
|
+
clearTimeout(this.pendingDestruction);
|
|
1496
|
+
}
|
|
1497
|
+
disconnectedCallback() {
|
|
1498
|
+
this.pendingDestruction = setTimeout(() => this.destroy(), 50);
|
|
1499
|
+
}
|
|
1500
|
+
destroy() {
|
|
1501
|
+
const root = this.$root;
|
|
1502
|
+
if (!root) return;
|
|
1503
|
+
const errors = [];
|
|
1504
|
+
Lifecycle.collectingErrors(errors, () => {
|
|
1505
|
+
Lifecycle.onComponentDestroyed(root);
|
|
1506
|
+
Lifecycle.onComponentDetached(root);
|
|
1507
|
+
});
|
|
1508
|
+
root.ref = null;
|
|
1509
|
+
this.$root = null;
|
|
1510
|
+
throwErrors(errors, "Errors destroying the component");
|
|
1511
|
+
}
|
|
1512
|
+
};
|
|
1513
|
+
const addPluginsAPI = (element) => {
|
|
1514
|
+
const { Plugin } = Plugins;
|
|
1515
|
+
element.install = (plugin, cascade = true) => {
|
|
1516
|
+
const installTo = (root) => {
|
|
1517
|
+
if (plugin instanceof Plugin) {
|
|
1518
|
+
root.plugins.registry.add(plugin);
|
|
1519
|
+
root.plugins.install(plugin);
|
|
1520
|
+
} else root.plugins.install(plugin);
|
|
1521
|
+
if (cascade) for (const subroot of root.subroots) installTo(subroot);
|
|
1522
|
+
};
|
|
1523
|
+
installTo(element.$root);
|
|
1524
|
+
};
|
|
1525
|
+
element.uninstall = (plugin, cascade = true) => {
|
|
1526
|
+
const name = typeof plugin === "string" ? plugin : plugin.name;
|
|
1527
|
+
const uninstallFrom = (root) => {
|
|
1528
|
+
root.plugins.uninstall(name);
|
|
1529
|
+
if (cascade) for (const subroot of root.subroots) uninstallFrom(subroot);
|
|
1530
|
+
};
|
|
1531
|
+
uninstallFrom(element.$root);
|
|
1532
|
+
};
|
|
1533
|
+
};
|
|
1534
|
+
/**
|
|
1535
|
+
* Loads the stylesheets of the root in its custom element when they
|
|
1536
|
+
* changed, e.g. with the plugins reconfigured.
|
|
1537
|
+
*/
|
|
1538
|
+
const updateStylesheets = (root) => {
|
|
1539
|
+
const element = root.ref;
|
|
1540
|
+
const stylesheets = root.getStylesheets();
|
|
1541
|
+
if (!isSameList(stylesheets, element.stylesheets)) element.loadStylesheets(stylesheets).catch((error) => console.error(error));
|
|
1542
|
+
};
|
|
1543
|
+
/**
|
|
1544
|
+
* Creates a new Custom Element instance assigned to specified Web Component.
|
|
1545
|
+
*/
|
|
1546
|
+
const createComponentElement = (root) => {
|
|
1547
|
+
const defineCustomElementClass = (RootClass) => {
|
|
1548
|
+
let ElementClass = customElements.get(RootClass.elementName);
|
|
1549
|
+
if (!ElementClass) {
|
|
1550
|
+
ElementClass = class RootElement extends ComponentElement {};
|
|
1551
|
+
customElements.define(RootClass.elementName, ElementClass);
|
|
1552
|
+
RootClass.prototype.elementClass = ElementClass;
|
|
1553
|
+
}
|
|
1554
|
+
return ElementClass;
|
|
1555
|
+
};
|
|
1556
|
+
const element = new (defineCustomElementClass(root.constructor))(root);
|
|
1557
|
+
for (const [name, value] of Object.entries(root.description.attrs ?? {})) DOM.setAttribute(element, name, value, true);
|
|
1558
|
+
return element;
|
|
1559
|
+
};
|
|
1560
|
+
//#endregion
|
|
1561
|
+
//#region src/core/draft.ts
|
|
1562
|
+
const drafts = /* @__PURE__ */ new WeakMap();
|
|
1563
|
+
const targets = /* @__PURE__ */ new WeakMap();
|
|
1564
|
+
const ARRAY_SEARCHES = /* @__PURE__ */ new Set([
|
|
1565
|
+
"includes",
|
|
1566
|
+
"indexOf",
|
|
1567
|
+
"lastIndexOf"
|
|
1568
|
+
]);
|
|
1569
|
+
const TYPED_ARRAY_MUTATORS = /* @__PURE__ */ new Set([
|
|
1570
|
+
"copyWithin",
|
|
1571
|
+
"fill",
|
|
1572
|
+
"reverse",
|
|
1573
|
+
"set",
|
|
1574
|
+
"sort",
|
|
1575
|
+
"subarray"
|
|
1576
|
+
]);
|
|
1577
|
+
const getKind = (value) => {
|
|
1578
|
+
if (typeof value !== "object" || value === null) return null;
|
|
1579
|
+
if (ArrayBuffer.isView(value)) return value instanceof DataView ? null : "typed-array";
|
|
1580
|
+
switch (Object.getPrototypeOf(value)) {
|
|
1581
|
+
case Object.prototype:
|
|
1582
|
+
case null: return "object";
|
|
1583
|
+
case Array.prototype: return "array";
|
|
1584
|
+
case Map.prototype: return "map";
|
|
1585
|
+
case Set.prototype: return "set";
|
|
1586
|
+
case Date.prototype: return "date";
|
|
1587
|
+
default: return null;
|
|
1588
|
+
}
|
|
1589
|
+
};
|
|
1590
|
+
const INVALID_VALUE = "The state can contain only primitives, functions, plain objects, arrays, maps, sets, dates and typed arrays";
|
|
1591
|
+
const checkValue = (value) => {
|
|
1592
|
+
const kind = getKind(value);
|
|
1593
|
+
invariant(kind || typeof value !== "object" || value === null, INVALID_VALUE);
|
|
1594
|
+
return kind;
|
|
1595
|
+
};
|
|
1596
|
+
const current = (draft) => draft.copy ?? draft.base;
|
|
1597
|
+
const createDraft = (base, kind, parent, scope) => {
|
|
1598
|
+
const target = kind === "array" ? [] : Object.create(Object.getPrototypeOf(base));
|
|
1599
|
+
const { proxy, revoke } = Proxy.revocable(target, handler);
|
|
1600
|
+
const draft = {
|
|
1601
|
+
kind,
|
|
1602
|
+
base,
|
|
1603
|
+
copy: null,
|
|
1604
|
+
parent,
|
|
1605
|
+
scope,
|
|
1606
|
+
modified: false,
|
|
1607
|
+
proxy,
|
|
1608
|
+
result: null,
|
|
1609
|
+
items: null
|
|
1610
|
+
};
|
|
1611
|
+
drafts.set(proxy, draft);
|
|
1612
|
+
targets.set(target, draft);
|
|
1613
|
+
scope.revokes.push(revoke);
|
|
1614
|
+
return proxy;
|
|
1615
|
+
};
|
|
1616
|
+
const prepareCopy = (draft) => {
|
|
1617
|
+
if (draft.copy) return;
|
|
1618
|
+
const { base } = draft;
|
|
1619
|
+
switch (draft.kind) {
|
|
1620
|
+
case "object":
|
|
1621
|
+
draft.copy = Object.assign(Object.create(Object.getPrototypeOf(base)), base);
|
|
1622
|
+
break;
|
|
1623
|
+
case "array":
|
|
1624
|
+
draft.copy = base.slice();
|
|
1625
|
+
break;
|
|
1626
|
+
case "map":
|
|
1627
|
+
draft.copy = new Map(base);
|
|
1628
|
+
break;
|
|
1629
|
+
case "set": {
|
|
1630
|
+
const copy = /* @__PURE__ */ new Set();
|
|
1631
|
+
draft.items = /* @__PURE__ */ new Map();
|
|
1632
|
+
for (const item of base) {
|
|
1633
|
+
const kind = checkValue(item);
|
|
1634
|
+
if (kind) {
|
|
1635
|
+
const itemDraft = createDraft(item, kind, draft, draft.scope);
|
|
1636
|
+
draft.items.set(item, itemDraft);
|
|
1637
|
+
copy.add(itemDraft);
|
|
1638
|
+
} else copy.add(item);
|
|
1639
|
+
}
|
|
1640
|
+
draft.copy = copy;
|
|
1641
|
+
break;
|
|
1642
|
+
}
|
|
1643
|
+
case "date":
|
|
1644
|
+
draft.copy = new Date(base.getTime());
|
|
1645
|
+
break;
|
|
1646
|
+
case "typed-array": draft.copy = base.slice();
|
|
1647
|
+
}
|
|
1648
|
+
};
|
|
1649
|
+
const markModified = (draft) => {
|
|
1650
|
+
while (draft && !draft.modified) {
|
|
1651
|
+
prepareCopy(draft);
|
|
1652
|
+
draft.modified = true;
|
|
1653
|
+
draft = draft.parent;
|
|
1654
|
+
}
|
|
1655
|
+
};
|
|
1656
|
+
const getValue = (draft, value, baseValue, store) => {
|
|
1657
|
+
if (drafts.has(value)) return value;
|
|
1658
|
+
const kind = checkValue(value);
|
|
1659
|
+
if (!kind || value !== baseValue) return value;
|
|
1660
|
+
prepareCopy(draft);
|
|
1661
|
+
const child = createDraft(value, kind, draft, draft.scope);
|
|
1662
|
+
store(draft.copy, child);
|
|
1663
|
+
return child;
|
|
1664
|
+
};
|
|
1665
|
+
const getOriginal = (value) => typeof value === "object" && value !== null && drafts.get(value)?.base || value;
|
|
1666
|
+
const getProperty = (draft, key) => {
|
|
1667
|
+
const source = current(draft);
|
|
1668
|
+
if (draft.kind === "array" && ARRAY_SEARCHES.has(key)) return (value, ...args) => {
|
|
1669
|
+
return current(draft).map(getOriginal)[key](getOriginal(value), ...args);
|
|
1670
|
+
};
|
|
1671
|
+
if (!Object.hasOwn(source, key)) return Reflect.get(source, key, draft.proxy);
|
|
1672
|
+
const base = draft.base;
|
|
1673
|
+
return getValue(draft, source[key], Object.hasOwn(base, key) ? base[key] : void 0, (copy, child) => copy[key] = child);
|
|
1674
|
+
};
|
|
1675
|
+
const getMapValue = (draft, key) => {
|
|
1676
|
+
const source = current(draft);
|
|
1677
|
+
return getValue(draft, source.get(key), draft.base.get(key), (copy, child) => copy.set(key, child));
|
|
1678
|
+
};
|
|
1679
|
+
const mapMethods = /* @__PURE__ */ new Map([
|
|
1680
|
+
["get", (draft) => (key) => getMapValue(draft, key)],
|
|
1681
|
+
["set", (draft) => (key, value) => {
|
|
1682
|
+
const source = current(draft);
|
|
1683
|
+
if (!source.has(key) || !Object.is(source.get(key), value)) {
|
|
1684
|
+
markModified(draft);
|
|
1685
|
+
draft.copy.set(key, value);
|
|
1686
|
+
}
|
|
1687
|
+
return draft.proxy;
|
|
1688
|
+
}],
|
|
1689
|
+
["delete", (draft) => (key) => {
|
|
1690
|
+
if (!current(draft).has(key)) return false;
|
|
1691
|
+
markModified(draft);
|
|
1692
|
+
return draft.copy.delete(key);
|
|
1693
|
+
}],
|
|
1694
|
+
["clear", (draft) => () => {
|
|
1695
|
+
if (current(draft).size) {
|
|
1696
|
+
markModified(draft);
|
|
1697
|
+
draft.copy.clear();
|
|
1698
|
+
}
|
|
1699
|
+
}],
|
|
1700
|
+
["forEach", (draft) => (callback, thisArg) => {
|
|
1701
|
+
for (const key of [...current(draft).keys()]) callback.call(thisArg, getMapValue(draft, key), key, draft.proxy);
|
|
1702
|
+
}],
|
|
1703
|
+
["keys", (draft) => () => [...current(draft).keys()].values()],
|
|
1704
|
+
["values", (draft) => function* () {
|
|
1705
|
+
for (const key of [...current(draft).keys()]) yield getMapValue(draft, key);
|
|
1706
|
+
}],
|
|
1707
|
+
["entries", (draft) => () => mapEntries(draft)],
|
|
1708
|
+
[Symbol.iterator, (draft) => () => mapEntries(draft)]
|
|
1709
|
+
]);
|
|
1710
|
+
function* mapEntries(draft) {
|
|
1711
|
+
for (const key of [...current(draft).keys()]) yield [key, getMapValue(draft, key)];
|
|
1712
|
+
}
|
|
1713
|
+
const hasSetItem = (draft, item) => {
|
|
1714
|
+
const source = current(draft);
|
|
1715
|
+
if (source.has(item)) return true;
|
|
1716
|
+
const itemDraft = draft.items?.get(item);
|
|
1717
|
+
return itemDraft !== void 0 && source.has(itemDraft);
|
|
1718
|
+
};
|
|
1719
|
+
const setMethods = /* @__PURE__ */ new Map([
|
|
1720
|
+
["has", (draft) => (item) => hasSetItem(draft, item)],
|
|
1721
|
+
["add", (draft) => (item) => {
|
|
1722
|
+
if (!hasSetItem(draft, item)) {
|
|
1723
|
+
markModified(draft);
|
|
1724
|
+
draft.copy.add(item);
|
|
1725
|
+
}
|
|
1726
|
+
return draft.proxy;
|
|
1727
|
+
}],
|
|
1728
|
+
["delete", (draft) => (item) => {
|
|
1729
|
+
if (!hasSetItem(draft, item)) return false;
|
|
1730
|
+
markModified(draft);
|
|
1731
|
+
const copy = draft.copy;
|
|
1732
|
+
return copy.delete(item) || copy.delete(draft.items.get(item));
|
|
1733
|
+
}],
|
|
1734
|
+
["clear", (draft) => () => {
|
|
1735
|
+
if (current(draft).size) {
|
|
1736
|
+
markModified(draft);
|
|
1737
|
+
draft.copy.clear();
|
|
1738
|
+
}
|
|
1739
|
+
}],
|
|
1740
|
+
["forEach", (draft) => (callback, thisArg) => {
|
|
1741
|
+
for (const item of setItems(draft)) callback.call(thisArg, item, item, draft.proxy);
|
|
1742
|
+
}],
|
|
1743
|
+
["keys", (draft) => () => setItems(draft).values()],
|
|
1744
|
+
["values", (draft) => () => setItems(draft).values()],
|
|
1745
|
+
[Symbol.iterator, (draft) => () => setItems(draft).values()],
|
|
1746
|
+
["entries", (draft) => () => setItems(draft).map((item) => [item, item]).values()]
|
|
1747
|
+
]);
|
|
1748
|
+
const setItems = (draft) => {
|
|
1749
|
+
prepareCopy(draft);
|
|
1750
|
+
return [...draft.copy];
|
|
1751
|
+
};
|
|
1752
|
+
const getSlotProperty = (draft, key) => {
|
|
1753
|
+
if (draft.kind === "map" || draft.kind === "set") {
|
|
1754
|
+
const method = (draft.kind === "map" ? mapMethods : setMethods).get(key);
|
|
1755
|
+
if (method) return method(draft);
|
|
1756
|
+
if (draft.kind === "set" && key !== "size") prepareCopy(draft);
|
|
1757
|
+
}
|
|
1758
|
+
if (draft.kind === "typed-array" && key === "buffer") {
|
|
1759
|
+
markModified(draft);
|
|
1760
|
+
return draft.copy.buffer;
|
|
1761
|
+
}
|
|
1762
|
+
const source = current(draft);
|
|
1763
|
+
if (draft.kind === "typed-array" && Object.hasOwn(source, key)) return source[key];
|
|
1764
|
+
const value = Reflect.get(source, key, source);
|
|
1765
|
+
if (typeof value !== "function" || key === "constructor") return value;
|
|
1766
|
+
const method = value;
|
|
1767
|
+
if (!(draft.kind === "date" ? typeof key === "string" && key.startsWith("set") : draft.kind === "typed-array" && TYPED_ARRAY_MUTATORS.has(key))) return method.bind(source);
|
|
1768
|
+
return (...args) => {
|
|
1769
|
+
prepareCopy(draft);
|
|
1770
|
+
const copy = draft.copy;
|
|
1771
|
+
const time = draft.kind === "date" ? copy.getTime() : null;
|
|
1772
|
+
const result = method.apply(copy, args);
|
|
1773
|
+
if (draft.kind !== "date" || !Object.is(time, copy.getTime())) markModified(draft);
|
|
1774
|
+
return result === copy ? draft.proxy : result;
|
|
1775
|
+
};
|
|
1776
|
+
};
|
|
1777
|
+
const handler = {
|
|
1778
|
+
get(target, key) {
|
|
1779
|
+
const draft = targets.get(target);
|
|
1780
|
+
return draft.kind === "object" || draft.kind === "array" ? getProperty(draft, key) : getSlotProperty(draft, key);
|
|
1781
|
+
},
|
|
1782
|
+
set(target, key, value) {
|
|
1783
|
+
const draft = targets.get(target);
|
|
1784
|
+
const source = current(draft);
|
|
1785
|
+
if (Object.hasOwn(source, key) && Object.is(source[key], value)) return true;
|
|
1786
|
+
markModified(draft);
|
|
1787
|
+
draft.copy[key] = value;
|
|
1788
|
+
return true;
|
|
1789
|
+
},
|
|
1790
|
+
deleteProperty(target, key) {
|
|
1791
|
+
const draft = targets.get(target);
|
|
1792
|
+
if (Object.hasOwn(current(draft), key)) {
|
|
1793
|
+
markModified(draft);
|
|
1794
|
+
delete draft.copy[key];
|
|
1795
|
+
}
|
|
1796
|
+
return true;
|
|
1797
|
+
},
|
|
1798
|
+
has(target, key) {
|
|
1799
|
+
return key in current(targets.get(target));
|
|
1800
|
+
},
|
|
1801
|
+
ownKeys(target) {
|
|
1802
|
+
return Reflect.ownKeys(current(targets.get(target)));
|
|
1803
|
+
},
|
|
1804
|
+
getOwnPropertyDescriptor(target, key) {
|
|
1805
|
+
const draft = targets.get(target);
|
|
1806
|
+
const source = current(draft);
|
|
1807
|
+
const descriptor = Reflect.getOwnPropertyDescriptor(source, key);
|
|
1808
|
+
if (!descriptor) return;
|
|
1809
|
+
const configurable = draft.kind !== "array" || key !== "length";
|
|
1810
|
+
return "value" in descriptor ? {
|
|
1811
|
+
...descriptor,
|
|
1812
|
+
value: source[key],
|
|
1813
|
+
writable: true,
|
|
1814
|
+
configurable
|
|
1815
|
+
} : {
|
|
1816
|
+
...descriptor,
|
|
1817
|
+
configurable
|
|
1818
|
+
};
|
|
1819
|
+
},
|
|
1820
|
+
defineProperty(target, key, descriptor) {
|
|
1821
|
+
const draft = targets.get(target);
|
|
1822
|
+
markModified(draft);
|
|
1823
|
+
return Reflect.defineProperty(draft.copy, key, descriptor);
|
|
1824
|
+
},
|
|
1825
|
+
setPrototypeOf() {
|
|
1826
|
+
return false;
|
|
1827
|
+
},
|
|
1828
|
+
preventExtensions() {
|
|
1829
|
+
return false;
|
|
1830
|
+
}
|
|
1831
|
+
};
|
|
1832
|
+
const finalize = (value, scope) => {
|
|
1833
|
+
if (typeof value !== "object" || value === null) return value;
|
|
1834
|
+
const draft = drafts.get(value);
|
|
1835
|
+
if (draft) return finalizeDraft(draft);
|
|
1836
|
+
const kind = checkValue(value);
|
|
1837
|
+
if (scope.visited.has(value) || Object.isFrozen(value)) return value;
|
|
1838
|
+
scope.visited.add(value);
|
|
1839
|
+
switch (kind) {
|
|
1840
|
+
case "object":
|
|
1841
|
+
case "array": {
|
|
1842
|
+
const entries = value;
|
|
1843
|
+
for (const key of Reflect.ownKeys(entries)) {
|
|
1844
|
+
const next = finalize(entries[key], scope);
|
|
1845
|
+
if (next !== entries[key]) entries[key] = next;
|
|
1846
|
+
}
|
|
1847
|
+
break;
|
|
1848
|
+
}
|
|
1849
|
+
case "map": {
|
|
1850
|
+
const map = value;
|
|
1851
|
+
for (const [key, item] of map) {
|
|
1852
|
+
const next = finalize(item, scope);
|
|
1853
|
+
if (next !== item) map.set(key, next);
|
|
1854
|
+
}
|
|
1855
|
+
break;
|
|
1856
|
+
}
|
|
1857
|
+
case "set": {
|
|
1858
|
+
const set = value;
|
|
1859
|
+
const items = [...set];
|
|
1860
|
+
const nextItems = items.map((item) => finalize(item, scope));
|
|
1861
|
+
if (nextItems.some((item, index) => item !== items[index])) {
|
|
1862
|
+
set.clear();
|
|
1863
|
+
nextItems.forEach((item) => set.add(item));
|
|
1864
|
+
}
|
|
1865
|
+
break;
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
return value;
|
|
1869
|
+
};
|
|
1870
|
+
const finalizeDraft = (draft) => {
|
|
1871
|
+
if (draft.result) return draft.result;
|
|
1872
|
+
if (!draft.modified) return draft.result = draft.base;
|
|
1873
|
+
const { base, copy, scope } = draft;
|
|
1874
|
+
draft.result = copy;
|
|
1875
|
+
switch (draft.kind) {
|
|
1876
|
+
case "object":
|
|
1877
|
+
case "array": {
|
|
1878
|
+
const entries = copy;
|
|
1879
|
+
for (const key of Reflect.ownKeys(entries)) {
|
|
1880
|
+
const value = entries[key];
|
|
1881
|
+
if (value !== base[key]) {
|
|
1882
|
+
const next = finalize(value, scope);
|
|
1883
|
+
if (next !== value) entries[key] = next;
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
break;
|
|
1887
|
+
}
|
|
1888
|
+
case "map": {
|
|
1889
|
+
const map = copy;
|
|
1890
|
+
for (const [key, value] of map) if (value !== base.get(key)) map.set(key, finalize(value, scope));
|
|
1891
|
+
break;
|
|
1892
|
+
}
|
|
1893
|
+
case "set": {
|
|
1894
|
+
const set = copy;
|
|
1895
|
+
const items = [...set];
|
|
1896
|
+
set.clear();
|
|
1897
|
+
items.forEach((item) => set.add(finalize(item, scope)));
|
|
1898
|
+
break;
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
return draft.result;
|
|
1902
|
+
};
|
|
1903
|
+
const produce = (base, recipe) => {
|
|
1904
|
+
const kind = getKind(base);
|
|
1905
|
+
invariant(kind, INVALID_VALUE);
|
|
1906
|
+
const scope = {
|
|
1907
|
+
revokes: [],
|
|
1908
|
+
visited: /* @__PURE__ */ new WeakSet()
|
|
1909
|
+
};
|
|
1910
|
+
const root = createDraft(base, kind, null, scope);
|
|
1911
|
+
try {
|
|
1912
|
+
recipe(root);
|
|
1913
|
+
return finalizeDraft(drafts.get(root));
|
|
1914
|
+
} finally {
|
|
1915
|
+
scope.revokes.forEach((revoke) => revoke());
|
|
1916
|
+
}
|
|
1917
|
+
};
|
|
1918
|
+
//#endregion
|
|
1919
|
+
//#region src/core/dispatcher.ts
|
|
1920
|
+
const Mode = {
|
|
1921
|
+
EXECUTE: Symbol("execute-commands"),
|
|
1922
|
+
IGNORE: Symbol("ignore-commands")
|
|
1923
|
+
};
|
|
1924
|
+
const MAX_FLUSH_CYCLES = 4;
|
|
1925
|
+
const coreAPI = {
|
|
1926
|
+
update(overrides) {
|
|
1927
|
+
return (state) => ({
|
|
1928
|
+
...state,
|
|
1929
|
+
...overrides
|
|
1930
|
+
});
|
|
1931
|
+
},
|
|
1932
|
+
replace(state) {
|
|
1933
|
+
return () => state;
|
|
1934
|
+
}
|
|
1935
|
+
};
|
|
1936
|
+
var Command = class {
|
|
1937
|
+
constructor(name, args, update) {
|
|
1938
|
+
this.name = name;
|
|
1939
|
+
this.args = args;
|
|
1940
|
+
this.update = update;
|
|
1941
|
+
}
|
|
1942
|
+
invoke(state) {
|
|
1943
|
+
return this.update(state);
|
|
1944
|
+
}
|
|
1945
|
+
};
|
|
1946
|
+
const createCommandsAPI = (...apis) => {
|
|
1947
|
+
const commandsAPI = {};
|
|
1948
|
+
for (const api of apis) {
|
|
1949
|
+
const overridden = Object.keys(api).find((key) => Object.hasOwn(coreAPI, key) || Object.hasOwn(commandsAPI, key));
|
|
1950
|
+
if (overridden) throw new Error(`The "${overridden}" command is already defined!`);
|
|
1951
|
+
Object.assign(commandsAPI, api);
|
|
1952
|
+
}
|
|
1953
|
+
return commandsAPI;
|
|
1954
|
+
};
|
|
1955
|
+
var Dispatcher = class {
|
|
1956
|
+
ignoreIncoming() {
|
|
1957
|
+
this.mode = Mode.IGNORE;
|
|
1958
|
+
}
|
|
1959
|
+
constructor(root) {
|
|
1960
|
+
this.mode = Mode.EXECUTE;
|
|
1961
|
+
this.queue = [];
|
|
1962
|
+
this.isFlushing = false;
|
|
1963
|
+
this.root = root;
|
|
1964
|
+
this.commands = {};
|
|
1965
|
+
const { commands = [] } = root.constructor;
|
|
1966
|
+
this.api = createCommandsAPI(...Array.isArray(commands) ? commands : [commands]);
|
|
1967
|
+
this.names = [...Object.keys(coreAPI), ...Object.keys(this.api)];
|
|
1968
|
+
for (const name of this.names) this.commands[name] = (...args) => this.issue(name, args);
|
|
1969
|
+
}
|
|
1970
|
+
createCommand(name, args) {
|
|
1971
|
+
const method = this.api[name];
|
|
1972
|
+
return new Command(name, args, method ? (state) => produce(state, (draft) => {
|
|
1973
|
+
const result = method.apply(draft, args);
|
|
1974
|
+
if (result instanceof Promise) {
|
|
1975
|
+
result.catch(() => {});
|
|
1976
|
+
throw new Error(`The "${name}" command must be synchronous!`);
|
|
1977
|
+
}
|
|
1978
|
+
}) : coreAPI[name](...args));
|
|
1979
|
+
}
|
|
1980
|
+
issue(name, args) {
|
|
1981
|
+
if (this.mode === Mode.IGNORE) return Promise.resolve(false);
|
|
1982
|
+
const command = this.createCommand(name, args);
|
|
1983
|
+
const done = new Promise((resolve, reject) => {
|
|
1984
|
+
command.resolve = resolve;
|
|
1985
|
+
command.reject = reject;
|
|
1986
|
+
});
|
|
1987
|
+
this.queue.push(command);
|
|
1988
|
+
if (this.queue.length === 1 && !this.isFlushing) queueMicrotask(() => this.flush());
|
|
1989
|
+
return done;
|
|
1990
|
+
}
|
|
1991
|
+
/**
|
|
1992
|
+
* Sets the state right away, when the root is rendered or receives
|
|
1993
|
+
* new props from its parent.
|
|
1994
|
+
*/
|
|
1995
|
+
setState(state) {
|
|
1996
|
+
if (this.mode === Mode.IGNORE) return;
|
|
1997
|
+
this.execute([this.createCommand("replace", [state])]);
|
|
1998
|
+
}
|
|
1999
|
+
/**
|
|
2000
|
+
* Updates the root with the state calculated by the commands in turn.
|
|
2001
|
+
*/
|
|
2002
|
+
execute(commands) {
|
|
2003
|
+
const prevState = this.root.state;
|
|
2004
|
+
const nextState = commands.reduce((state, command) => command.invoke(state), prevState);
|
|
2005
|
+
runtime().update(this.root, prevState, nextState, commands);
|
|
2006
|
+
}
|
|
2007
|
+
/**
|
|
2008
|
+
* Executes the queued commands, followed by the ones issued in lifecycle
|
|
2009
|
+
* methods in turn, until there are none left. When an update fails, its
|
|
2010
|
+
* commands reject, and the ones issued in its lifecycle methods are still
|
|
2011
|
+
* executed, as the update is completed.
|
|
2012
|
+
*/
|
|
2013
|
+
flush() {
|
|
2014
|
+
this.isFlushing = true;
|
|
2015
|
+
try {
|
|
2016
|
+
for (let cycle = 1; this.queue.length; cycle++) {
|
|
2017
|
+
const commands = this.queue.splice(0);
|
|
2018
|
+
if (this.mode === Mode.IGNORE) {
|
|
2019
|
+
commands.forEach((command) => command.resolve(false));
|
|
2020
|
+
continue;
|
|
2021
|
+
}
|
|
2022
|
+
try {
|
|
2023
|
+
if (cycle > MAX_FLUSH_CYCLES) throw new Error("Too many cycles updating state in lifecycle methods!");
|
|
2024
|
+
this.execute(commands);
|
|
2025
|
+
} catch (error) {
|
|
2026
|
+
commands.forEach((command) => command.reject(error));
|
|
2027
|
+
continue;
|
|
2028
|
+
}
|
|
2029
|
+
commands.forEach((command) => command.resolve(true));
|
|
2030
|
+
}
|
|
2031
|
+
} finally {
|
|
2032
|
+
this.isFlushing = false;
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
};
|
|
2036
|
+
//#endregion
|
|
2037
|
+
//#region src/core/web-component.ts
|
|
2038
|
+
const CONTAINER = Symbol("container");
|
|
2039
|
+
const CUSTOM_ELEMENT = Symbol("custom-element");
|
|
2040
|
+
const DISPATCHER = Symbol("dispatcher");
|
|
2041
|
+
var WebComponent = class WebComponent extends Component {
|
|
2042
|
+
static NodeType = "root";
|
|
2043
|
+
static styles = [];
|
|
2044
|
+
constructor(description, parent = null, context = null) {
|
|
2045
|
+
super(description, parent, context, false);
|
|
2046
|
+
CreatedRoots.collectRoot(this);
|
|
2047
|
+
this.subroots = /* @__PURE__ */ new Set();
|
|
2048
|
+
this.dispatcher = new Dispatcher(this);
|
|
2049
|
+
this.ready = new Promise((resolve, reject) => {
|
|
2050
|
+
this.markAsReady = resolve;
|
|
2051
|
+
this.markAsFailed = reject;
|
|
2052
|
+
});
|
|
2053
|
+
this.plugins = this.createPlugins();
|
|
2054
|
+
runtime().track(this);
|
|
2055
|
+
this.content = this.createPlaceholder();
|
|
2056
|
+
this.shadow = null;
|
|
2057
|
+
this.attachDOM();
|
|
2058
|
+
}
|
|
2059
|
+
attachDOM() {
|
|
2060
|
+
if (this.constructor.elementName) {
|
|
2061
|
+
this.ref = createComponentElement(this);
|
|
2062
|
+
this.plugins.installAll();
|
|
2063
|
+
} else {
|
|
2064
|
+
this.plugins.installAll();
|
|
2065
|
+
super.attachDOM();
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
createPlaceholder() {
|
|
2069
|
+
return new Comment(new CommentDescription(this.constructor.displayName));
|
|
2070
|
+
}
|
|
2071
|
+
/**
|
|
2072
|
+
* Triggers the initial rendering of the component in given container.
|
|
2073
|
+
*/
|
|
2074
|
+
async init() {
|
|
2075
|
+
const state = await this.getInitialState.call(this.sandbox, this.description.props || {});
|
|
2076
|
+
this.setState(state);
|
|
2077
|
+
if (this.pendingDescription) {
|
|
2078
|
+
const description = this.pendingDescription;
|
|
2079
|
+
delete this.pendingDescription;
|
|
2080
|
+
setTimeout(() => this.update(description));
|
|
2081
|
+
}
|
|
2082
|
+
this.isInitialized = true;
|
|
2083
|
+
this.markAsReady();
|
|
2084
|
+
}
|
|
2085
|
+
setState(state) {
|
|
2086
|
+
if (state.constructor !== Object) throw new Error("Web Component state must be a plain object!");
|
|
2087
|
+
this.dispatcher.setState(Template.normalizeComponentProps(state, this.constructor));
|
|
2088
|
+
}
|
|
2089
|
+
/**
|
|
2090
|
+
* Triggers the component update.
|
|
2091
|
+
*/
|
|
2092
|
+
update(description) {
|
|
2093
|
+
if (!this.isInitialized) {
|
|
2094
|
+
this.pendingDescription = description;
|
|
2095
|
+
return;
|
|
2096
|
+
}
|
|
2097
|
+
const state = this.getUpdatedState(description.props || {}, this.state || {});
|
|
2098
|
+
this.setState(state);
|
|
2099
|
+
}
|
|
2100
|
+
/**
|
|
2101
|
+
* The default implementation delegating the calculation of initial state
|
|
2102
|
+
* to the state manager.
|
|
2103
|
+
*/
|
|
2104
|
+
getInitialState(props) {
|
|
2105
|
+
return { ...props };
|
|
2106
|
+
}
|
|
2107
|
+
/**
|
|
2108
|
+
* The default implementation delegating the calculation of updated state
|
|
2109
|
+
* to the state manager.
|
|
2110
|
+
*/
|
|
2111
|
+
getUpdatedState(props, state) {
|
|
2112
|
+
return {
|
|
2113
|
+
...state,
|
|
2114
|
+
...props
|
|
2115
|
+
};
|
|
2116
|
+
}
|
|
2117
|
+
get dispatcher() {
|
|
2118
|
+
return this[DISPATCHER];
|
|
2119
|
+
}
|
|
2120
|
+
set dispatcher(dispatcher) {
|
|
2121
|
+
this[DISPATCHER] = dispatcher;
|
|
2122
|
+
}
|
|
2123
|
+
get commands() {
|
|
2124
|
+
return this.dispatcher.commands;
|
|
2125
|
+
}
|
|
2126
|
+
createPlugins() {
|
|
2127
|
+
const plugins = new Plugins(this);
|
|
2128
|
+
const inherited = this.parentNode ? this.parentNode.plugins : runtime().plugins;
|
|
2129
|
+
for (const plugin of inherited) plugins.register(plugin);
|
|
2130
|
+
return plugins;
|
|
2131
|
+
}
|
|
2132
|
+
async mount(container) {
|
|
2133
|
+
if (this.constructor.elementName) {
|
|
2134
|
+
container.appendChild(this.ref);
|
|
2135
|
+
await this.ready;
|
|
2136
|
+
} else {
|
|
2137
|
+
this.container = container;
|
|
2138
|
+
await this.init();
|
|
2139
|
+
}
|
|
2140
|
+
return this;
|
|
2141
|
+
}
|
|
2142
|
+
getStylesheets() {
|
|
2143
|
+
const stylesheets = [];
|
|
2144
|
+
const stylesheetProviders = [...this.plugins].filter((plugin) => plugin.isStylesheetProvider());
|
|
2145
|
+
for (const plugin of stylesheetProviders) {
|
|
2146
|
+
if (typeof plugin.getStylesheets !== "function") throw new Error(`Plugin '${plugin.name}' must provide the getStylesheets() method!`);
|
|
2147
|
+
stylesheets.push(...plugin.getStylesheets());
|
|
2148
|
+
}
|
|
2149
|
+
const RootClass = this.constructor;
|
|
2150
|
+
if (Array.isArray(RootClass.styles)) stylesheets.push(...RootClass.styles);
|
|
2151
|
+
return stylesheets;
|
|
2152
|
+
}
|
|
2153
|
+
get ref() {
|
|
2154
|
+
return this[CUSTOM_ELEMENT] || super.ref;
|
|
2155
|
+
}
|
|
2156
|
+
set ref(ref) {
|
|
2157
|
+
this[CUSTOM_ELEMENT] = ref;
|
|
2158
|
+
}
|
|
2159
|
+
set container(container) {
|
|
2160
|
+
this[CONTAINER] = container;
|
|
2161
|
+
}
|
|
2162
|
+
get container() {
|
|
2163
|
+
return this[CONTAINER];
|
|
2164
|
+
}
|
|
2165
|
+
get tracked() {
|
|
2166
|
+
const tracked = [];
|
|
2167
|
+
for (const root of this.subroots) tracked.push(root, ...root.tracked);
|
|
2168
|
+
return tracked;
|
|
2169
|
+
}
|
|
2170
|
+
destroy() {
|
|
2171
|
+
if (!this.plugins) return;
|
|
2172
|
+
super.destroy();
|
|
2173
|
+
this.stopTracking?.();
|
|
2174
|
+
this.dispatcher.ignoreIncoming();
|
|
2175
|
+
this.plugins.destroy();
|
|
2176
|
+
this.plugins = null;
|
|
2177
|
+
this.parentNode = null;
|
|
2178
|
+
}
|
|
2179
|
+
setChildNodes(nodes) {
|
|
2180
|
+
ChildNodes.set(this, nodes);
|
|
2181
|
+
}
|
|
2182
|
+
insertChild(child, index) {
|
|
2183
|
+
ChildNodes.insert(this, child, index);
|
|
2184
|
+
}
|
|
2185
|
+
replaceChild(child, node) {
|
|
2186
|
+
ChildNodes.replace(this, child, node);
|
|
2187
|
+
}
|
|
2188
|
+
moveChild(child, from, to) {
|
|
2189
|
+
ChildNodes.move(this, child, from, to);
|
|
2190
|
+
}
|
|
2191
|
+
removeChild(child) {
|
|
2192
|
+
ChildNodes.remove(this, child);
|
|
2193
|
+
}
|
|
2194
|
+
get nodeType() {
|
|
2195
|
+
return WebComponent.NodeType;
|
|
2196
|
+
}
|
|
2197
|
+
};
|
|
2198
|
+
//#endregion
|
|
2199
|
+
//#region src/core/service.ts
|
|
2200
|
+
var Service = class {
|
|
2201
|
+
static validate(listeners) {
|
|
2202
|
+
if (runtime().isDebug()) {
|
|
2203
|
+
const keys = Object.keys(listeners);
|
|
2204
|
+
runtime().assert(this.events instanceof Array, `Service "${this.name}" does not provide information about valid events, implement "static events = ['foo', 'bar']"`);
|
|
2205
|
+
runtime().assert(this.events.length > 0, `Service "${this.name}" returned an empty list of valid events, the "static events" list must contain at least one event name`);
|
|
2206
|
+
const unsupportedKeys = keys.filter((key) => !this.events.includes(key));
|
|
2207
|
+
for (const unsupportedKey of unsupportedKeys) runtime().warn(`Unsupported listener specified "${unsupportedKey}" when connecting to ${this.name}`);
|
|
2208
|
+
const supportedKeys = this.events.filter((event) => keys.includes(event));
|
|
2209
|
+
runtime().assert(supportedKeys.length > 0, `No valid listener specified when connecting to ${this.name}, use one of [${this.events.join(", ")}]`);
|
|
2210
|
+
for (const supportedKey of supportedKeys) runtime().assert(listeners[supportedKey] instanceof Function, `Specified listener "${supportedKey}" for ${this.name} is not a function`);
|
|
2211
|
+
}
|
|
2212
|
+
return this.events.filter((event) => listeners[event] instanceof Function);
|
|
2213
|
+
}
|
|
2214
|
+
};
|
|
2215
|
+
//#endregion
|
|
2216
|
+
//#region src/core/reconciler.ts
|
|
2217
|
+
const Name = {
|
|
2218
|
+
INSERT: Symbol("insert"),
|
|
2219
|
+
MOVE: Symbol("move"),
|
|
2220
|
+
REMOVE: Symbol("remove")
|
|
2221
|
+
};
|
|
2222
|
+
var Move = class Move {
|
|
2223
|
+
static Name = Name;
|
|
2224
|
+
constructor(name, item, props, make) {
|
|
2225
|
+
this.name = name;
|
|
2226
|
+
this.item = item;
|
|
2227
|
+
this.at = props.at;
|
|
2228
|
+
this.from = props.from;
|
|
2229
|
+
this.to = props.to;
|
|
2230
|
+
this.make = make;
|
|
2231
|
+
}
|
|
2232
|
+
static insert(item, at) {
|
|
2233
|
+
return new Move(Name.INSERT, item, { at }, (items) => {
|
|
2234
|
+
items.splice(at, 0, item);
|
|
2235
|
+
});
|
|
2236
|
+
}
|
|
2237
|
+
static move(item, from, to) {
|
|
2238
|
+
return new Move(Name.MOVE, item, {
|
|
2239
|
+
from,
|
|
2240
|
+
to
|
|
2241
|
+
}, (items) => {
|
|
2242
|
+
items.splice(from, 1);
|
|
2243
|
+
items.splice(to, 0, item);
|
|
2244
|
+
});
|
|
2245
|
+
}
|
|
2246
|
+
static remove(item, at) {
|
|
2247
|
+
return new Move(Name.REMOVE, item, { at }, (items) => {
|
|
2248
|
+
items.splice(at, 1);
|
|
2249
|
+
});
|
|
2250
|
+
}
|
|
2251
|
+
};
|
|
2252
|
+
const Reconciler = {
|
|
2253
|
+
comparator(a, b) {
|
|
2254
|
+
if (Object.is(a.key, b.key)) return 0;
|
|
2255
|
+
return a.key > b.key ? 1 : -1;
|
|
2256
|
+
},
|
|
2257
|
+
calculateMoves(source, target) {
|
|
2258
|
+
const moves = [];
|
|
2259
|
+
const createItem = function(key, index) {
|
|
2260
|
+
return {
|
|
2261
|
+
key,
|
|
2262
|
+
index
|
|
2263
|
+
};
|
|
2264
|
+
};
|
|
2265
|
+
const before = source.map(createItem).sort(this.comparator);
|
|
2266
|
+
const after = target.map(createItem).sort(this.comparator);
|
|
2267
|
+
let removed = [];
|
|
2268
|
+
let inserted = [];
|
|
2269
|
+
while (before.length || after.length) {
|
|
2270
|
+
if (!before.length) {
|
|
2271
|
+
inserted = inserted.concat(after);
|
|
2272
|
+
break;
|
|
2273
|
+
}
|
|
2274
|
+
if (!after.length) {
|
|
2275
|
+
removed = removed.concat(before);
|
|
2276
|
+
break;
|
|
2277
|
+
}
|
|
2278
|
+
const result = this.comparator(after[0], before[0]);
|
|
2279
|
+
if (result === 0) {
|
|
2280
|
+
before.shift();
|
|
2281
|
+
after.shift();
|
|
2282
|
+
} else if (result === 1) removed.push(before.shift());
|
|
2283
|
+
else inserted.push(after.shift());
|
|
2284
|
+
}
|
|
2285
|
+
const sortByIndex = function(foo, bar) {
|
|
2286
|
+
return foo.index - bar.index;
|
|
2287
|
+
};
|
|
2288
|
+
removed.sort(sortByIndex).reverse();
|
|
2289
|
+
inserted.sort(sortByIndex);
|
|
2290
|
+
const result = [...source];
|
|
2291
|
+
for (const item of removed) {
|
|
2292
|
+
const move = Move.remove(item.key, item.index);
|
|
2293
|
+
move.make(result);
|
|
2294
|
+
moves.push(move);
|
|
2295
|
+
}
|
|
2296
|
+
for (const item of inserted) {
|
|
2297
|
+
const move = Move.insert(item.key, item.index);
|
|
2298
|
+
move.make(result);
|
|
2299
|
+
moves.push(move);
|
|
2300
|
+
}
|
|
2301
|
+
if (result.length === target.length && result.every((key, index) => key === target[index])) {
|
|
2302
|
+
moves.result = result;
|
|
2303
|
+
return moves;
|
|
2304
|
+
}
|
|
2305
|
+
const calculateIndexChanges = (source, target, reversed = false) => {
|
|
2306
|
+
const moves = [];
|
|
2307
|
+
const moveItemIfNeeded = (index) => {
|
|
2308
|
+
const item = target[index];
|
|
2309
|
+
if (source[index] !== item) {
|
|
2310
|
+
const from = source.indexOf(item);
|
|
2311
|
+
const move = Move.move(item, from, index);
|
|
2312
|
+
move.make(source);
|
|
2313
|
+
moves.push(move);
|
|
2314
|
+
}
|
|
2315
|
+
};
|
|
2316
|
+
if (reversed) for (let i = target.length - 1; i >= 0; i--) moveItemIfNeeded(i);
|
|
2317
|
+
else for (let i = 0; i < target.length; i++) moveItemIfNeeded(i);
|
|
2318
|
+
moves.result = source;
|
|
2319
|
+
return moves;
|
|
2320
|
+
};
|
|
2321
|
+
const defaultMoves = calculateIndexChanges([...result], target);
|
|
2322
|
+
if (defaultMoves.length > 1) {
|
|
2323
|
+
const alternativeMoves = calculateIndexChanges([...result], target, true);
|
|
2324
|
+
if (alternativeMoves.length <= defaultMoves.length) {
|
|
2325
|
+
moves.push(...alternativeMoves);
|
|
2326
|
+
moves.result = alternativeMoves.result;
|
|
2327
|
+
return moves;
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2330
|
+
moves.push(...defaultMoves);
|
|
2331
|
+
moves.result = defaultMoves.result;
|
|
2332
|
+
return moves;
|
|
2333
|
+
},
|
|
2334
|
+
Move
|
|
2335
|
+
};
|
|
2336
|
+
//#endregion
|
|
2337
|
+
//#region src/core/virtual-dom.ts
|
|
2338
|
+
const VirtualDOM = {
|
|
2339
|
+
/**
|
|
2340
|
+
* Creates a new Virtual DOM structure from given description.
|
|
2341
|
+
*/
|
|
2342
|
+
createFromDescription(description, parent, context) {
|
|
2343
|
+
if (!description) return null;
|
|
2344
|
+
switch (description.type) {
|
|
2345
|
+
case "component": return this.createComponent(description, parent, context);
|
|
2346
|
+
case "element": {
|
|
2347
|
+
const element = new VirtualElement(description, parent, context);
|
|
2348
|
+
this.createChildNodes(element);
|
|
2349
|
+
return element;
|
|
2350
|
+
}
|
|
2351
|
+
case "comment": return new Comment(description, parent);
|
|
2352
|
+
case "text": return new Text(description, parent);
|
|
2353
|
+
default: throw new Error(`Unsupported node type: ${description.type}`);
|
|
2354
|
+
}
|
|
2355
|
+
},
|
|
2356
|
+
/**
|
|
2357
|
+
* Creates the child nodes of an element or a Web Component.
|
|
2358
|
+
*/
|
|
2359
|
+
createChildNodes(parent) {
|
|
2360
|
+
const { children } = parent.description;
|
|
2361
|
+
if (children) parent.setChildNodes(children.map((description) => this.createFromDescription(description, parent, parent.context)));
|
|
2362
|
+
},
|
|
2363
|
+
/**
|
|
2364
|
+
* Creates a new component instance from given description.
|
|
2365
|
+
*/
|
|
2366
|
+
createComponent(description, parent, context) {
|
|
2367
|
+
const ComponentClass = description.component;
|
|
2368
|
+
if (ComponentClass.prototype instanceof WebComponent) return this.createWebComponent(description, parent && parent.rootNode, context, true);
|
|
2369
|
+
const component = new ComponentClass(description, parent, context);
|
|
2370
|
+
const nodeDescription = component.renderDescription(description.props, description.childrenAsTemplates);
|
|
2371
|
+
component.content = this.createFromDescription(nodeDescription, component, context);
|
|
2372
|
+
return component;
|
|
2373
|
+
},
|
|
2374
|
+
/**
|
|
2375
|
+
* Creates a new Web Component instance from given description.
|
|
2376
|
+
*/
|
|
2377
|
+
createWebComponent(description, parent, context, requireCustomElement = false) {
|
|
2378
|
+
try {
|
|
2379
|
+
const ComponentClass = description.component;
|
|
2380
|
+
if (requireCustomElement && !ComponentClass.elementName) throw new Error(`Root component "${ComponentClass.displayName}" does not define custom element name!`);
|
|
2381
|
+
const root = new ComponentClass(description, parent, context);
|
|
2382
|
+
if (ComponentClass.elementName) this.createChildNodes(root);
|
|
2383
|
+
return root;
|
|
2384
|
+
} catch (error) {
|
|
2385
|
+
console.error("Error rendering root component:", description);
|
|
2386
|
+
throw error;
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2389
|
+
};
|
|
2390
|
+
//#endregion
|
|
2391
|
+
//#region src/core/diff.ts
|
|
2392
|
+
const compareKeys = (current, next, isEqual = (value, nextValue) => value === nextValue) => {
|
|
2393
|
+
const keys = Object.keys(current);
|
|
2394
|
+
const nextKeys = Object.keys(next);
|
|
2395
|
+
return {
|
|
2396
|
+
added: nextKeys.filter((key) => !keys.includes(key)),
|
|
2397
|
+
removed: keys.filter((key) => !nextKeys.includes(key)),
|
|
2398
|
+
changed: keys.filter((key) => nextKeys.includes(key) && !isEqual(current[key], next[key]))
|
|
2399
|
+
};
|
|
2400
|
+
};
|
|
2401
|
+
var Diff = class Diff {
|
|
2402
|
+
/**
|
|
2403
|
+
* Creates a new instance bound to a root component
|
|
2404
|
+
* with an empty list of patches.
|
|
2405
|
+
*/
|
|
2406
|
+
constructor(root, currentState, nextState) {
|
|
2407
|
+
this.root = root;
|
|
2408
|
+
this.patches = [];
|
|
2409
|
+
CreatedRoots.destroyingRootsOnError(() => this.calculate(currentState, nextState));
|
|
2410
|
+
}
|
|
2411
|
+
/**
|
|
2412
|
+
* Adds the patch to the underlying list.
|
|
2413
|
+
*/
|
|
2414
|
+
addPatch(patch) {
|
|
2415
|
+
return this.patches.push(patch);
|
|
2416
|
+
}
|
|
2417
|
+
/**
|
|
2418
|
+
* Applies all the patches onto the bound root node.
|
|
2419
|
+
*/
|
|
2420
|
+
apply() {
|
|
2421
|
+
if (this.patches.length) {
|
|
2422
|
+
const errors = [];
|
|
2423
|
+
Lifecycle.collectingErrors(errors, () => Lifecycle.beforeUpdate(this.patches));
|
|
2424
|
+
const failedRoots = /* @__PURE__ */ new Set();
|
|
2425
|
+
const applied = [];
|
|
2426
|
+
for (const patch of this.patches) {
|
|
2427
|
+
if (patch.type === Patch.Type.UPDATE_NODE && failedRoots.has(patch.node)) continue;
|
|
2428
|
+
try {
|
|
2429
|
+
Patch.apply(patch);
|
|
2430
|
+
} catch (error) {
|
|
2431
|
+
errors.push(error);
|
|
2432
|
+
if (patch.type === Patch.Type.UPDATE_ROOT) failedRoots.add(patch.root);
|
|
2433
|
+
continue;
|
|
2434
|
+
}
|
|
2435
|
+
applied.push(patch);
|
|
2436
|
+
}
|
|
2437
|
+
Lifecycle.collectingErrors(errors, () => Lifecycle.afterUpdate(applied));
|
|
2438
|
+
throwErrors(errors, "Errors applying the update");
|
|
2439
|
+
}
|
|
2440
|
+
return this.patches;
|
|
2441
|
+
}
|
|
2442
|
+
/**
|
|
2443
|
+
* Calculates and returns all patches needed for transformation
|
|
2444
|
+
* of the rendered DOM fragment from one state to another.
|
|
2445
|
+
*/
|
|
2446
|
+
calculate(currentState, nextState) {
|
|
2447
|
+
if (!currentState) this.addPatch(Patch.initRootComponent(this.root));
|
|
2448
|
+
if (Diff.deepEqual(currentState, nextState)) return;
|
|
2449
|
+
const template = [this.root.constructor, nextState];
|
|
2450
|
+
if (this.root.description.children) template.push(...this.root.description.children.map((child) => child.asTemplate));
|
|
2451
|
+
const description = Template.describe(template);
|
|
2452
|
+
if (!this.root.isInitialized || !Diff.deepEqual(this.root.description, description)) this.componentPatches(this.root, description);
|
|
2453
|
+
if (this.root.description.attrs || description.attrs) this.attributePatches(this.root.description.attrs, description.attrs, this.root, true);
|
|
2454
|
+
}
|
|
2455
|
+
/**
|
|
2456
|
+
* Renders the descendants with normalized props and children passed
|
|
2457
|
+
* from the parent component.
|
|
2458
|
+
*
|
|
2459
|
+
* Calculates the patches needed for transformation of a component
|
|
2460
|
+
* to match the given description.
|
|
2461
|
+
*/
|
|
2462
|
+
componentPatches(component, description) {
|
|
2463
|
+
const nodeDescription = component.renderDescription(description.props, description.childrenAsTemplates);
|
|
2464
|
+
this.componentContentPatches(nodeDescription, component);
|
|
2465
|
+
this.addPatch(Patch.updateNode(component, description));
|
|
2466
|
+
}
|
|
2467
|
+
componentContentPatches(description, parent) {
|
|
2468
|
+
const content = parent.content;
|
|
2469
|
+
if (!content && !description) return;
|
|
2470
|
+
if (!content || !description) throw new Error("Invalid component state!");
|
|
2471
|
+
if (content.description.isCompatible(description)) {
|
|
2472
|
+
this.childPatches(content, description);
|
|
2473
|
+
return;
|
|
2474
|
+
}
|
|
2475
|
+
const node = VirtualDOM.createFromDescription(description, parent, this.root);
|
|
2476
|
+
this.addPatch(Patch.setContent(node, parent));
|
|
2477
|
+
}
|
|
2478
|
+
/**
|
|
2479
|
+
* Calculates patches for transformation of specified child node
|
|
2480
|
+
* to match given description.
|
|
2481
|
+
*/
|
|
2482
|
+
childPatches(child, description) {
|
|
2483
|
+
if (Diff.deepEqual(child.description, description)) return;
|
|
2484
|
+
if (child.isComponent()) {
|
|
2485
|
+
if (child.isRoot()) {
|
|
2486
|
+
this.childrenPatches(child.childNodes, description.children, child);
|
|
2487
|
+
this.addPatch(Patch.updateRoot(child, description));
|
|
2488
|
+
this.addPatch(Patch.updateNode(child, description));
|
|
2489
|
+
return;
|
|
2490
|
+
}
|
|
2491
|
+
return this.componentPatches(child, description);
|
|
2492
|
+
}
|
|
2493
|
+
if (child.isElement()) return this.elementPatches(child, description);
|
|
2494
|
+
throw new Error(`Unsupported node type: ${child.nodeType}`);
|
|
2495
|
+
}
|
|
2496
|
+
/**
|
|
2497
|
+
* Calculates patches for transformation of an element to match given
|
|
2498
|
+
* description.
|
|
2499
|
+
*/
|
|
2500
|
+
elementPatches(element, description) {
|
|
2501
|
+
this.classNamePatches(element.description.class, description.class, element);
|
|
2502
|
+
this.stylePatches(element.description.style, description.style, element);
|
|
2503
|
+
this.attributePatches(element.description.attrs, description.attrs, element);
|
|
2504
|
+
this.listenerPatches(element.description.listeners, description.listeners, element);
|
|
2505
|
+
this.datasetPatches(element.description.dataset, description.dataset, element);
|
|
2506
|
+
this.propertiesPatches(element.description.properties, description.properties, element);
|
|
2507
|
+
if (element.description.custom || description.custom) {
|
|
2508
|
+
this.attributePatches(element.description.custom && element.description.custom.attrs, description.custom && description.custom.attrs, element, true);
|
|
2509
|
+
this.listenerPatches(element.description.custom && element.description.custom.listeners, description.custom && description.custom.listeners, element, true);
|
|
2510
|
+
}
|
|
2511
|
+
if (element.children || description.children) this.childrenPatches(element.children, description.children, element);
|
|
2512
|
+
this.addPatch(Patch.updateNode(element, description));
|
|
2513
|
+
}
|
|
2514
|
+
classNamePatches(current = "", next = "", target) {
|
|
2515
|
+
if (current !== next) this.addPatch(Patch.setClassName(next, target));
|
|
2516
|
+
}
|
|
2517
|
+
stylePatches(current = {}, next = {}, target) {
|
|
2518
|
+
const { added, removed, changed } = compareKeys(current, next);
|
|
2519
|
+
for (const prop of added) this.addPatch(Patch.setStyleProperty(prop, next[prop], target));
|
|
2520
|
+
for (const prop of removed) this.addPatch(Patch.removeStyleProperty(prop, target));
|
|
2521
|
+
for (const prop of changed) this.addPatch(Patch.setStyleProperty(prop, next[prop], target));
|
|
2522
|
+
}
|
|
2523
|
+
attributePatches(current = {}, next = {}, target, isCustom = false) {
|
|
2524
|
+
const { added, removed, changed } = compareKeys(current, next);
|
|
2525
|
+
for (const attr of added) this.addPatch(Patch.setAttribute(attr, next[attr], target, isCustom));
|
|
2526
|
+
for (const attr of removed) this.addPatch(Patch.removeAttribute(attr, target, isCustom));
|
|
2527
|
+
for (const attr of changed) this.addPatch(Patch.setAttribute(attr, next[attr], target, isCustom));
|
|
2528
|
+
}
|
|
2529
|
+
listenerPatches(current = {}, next = {}, target, isCustom = false) {
|
|
2530
|
+
const { added, removed, changed } = compareKeys(current, next, (listener, nextListener) => listener === nextListener || listener.source !== void 0 && listener.source === nextListener.source);
|
|
2531
|
+
for (const event of added) this.addPatch(Patch.addListener(event, next[event], target, isCustom));
|
|
2532
|
+
for (const event of removed) this.addPatch(Patch.removeListener(event, current[event], target, isCustom));
|
|
2533
|
+
for (const event of changed) this.addPatch(Patch.replaceListener(event, current[event], next[event], target, isCustom));
|
|
2534
|
+
}
|
|
2535
|
+
datasetPatches(current = {}, next = {}, target) {
|
|
2536
|
+
const { added, removed, changed } = compareKeys(current, next);
|
|
2537
|
+
for (const attr of added) this.addPatch(Patch.setDataAttribute(attr, next[attr], target));
|
|
2538
|
+
for (const attr of removed) this.addPatch(Patch.removeDataAttribute(attr, target));
|
|
2539
|
+
for (const attr of changed) this.addPatch(Patch.setDataAttribute(attr, next[attr], target));
|
|
2540
|
+
}
|
|
2541
|
+
propertiesPatches(current = {}, next = {}, target) {
|
|
2542
|
+
const { added, removed, changed } = compareKeys(current, next, (value, nextValue) => Diff.deepEqual(value, nextValue));
|
|
2543
|
+
for (const key of added) this.addPatch(Patch.setProperty(key, next[key], target));
|
|
2544
|
+
for (const key of removed) this.addPatch(Patch.deleteProperty(key, target));
|
|
2545
|
+
for (const key of changed) this.addPatch(Patch.setProperty(key, next[key], target));
|
|
2546
|
+
}
|
|
2547
|
+
childrenPatches(sourceNodes = [], targetDescriptions = [], parent) {
|
|
2548
|
+
const Move = Reconciler.Move;
|
|
2549
|
+
const created = /* @__PURE__ */ new Set();
|
|
2550
|
+
const createdNodesMap = /* @__PURE__ */ new Map();
|
|
2551
|
+
const createNode = (description, key) => {
|
|
2552
|
+
const node = VirtualDOM.createFromDescription(description, parent, this.root);
|
|
2553
|
+
created.add(node);
|
|
2554
|
+
createdNodesMap.set(key, node);
|
|
2555
|
+
return node;
|
|
2556
|
+
};
|
|
2557
|
+
const from = sourceNodes.map((node, index) => node.key || Diff.createKey(index));
|
|
2558
|
+
const to = targetDescriptions.map((description, index) => description.key || Diff.createKey(index));
|
|
2559
|
+
if (runtime().isDebug()) {
|
|
2560
|
+
const assertUniqueKeys = (keys) => {
|
|
2561
|
+
if (keys.length) {
|
|
2562
|
+
if ([...new Set(keys)].length !== keys.length) throw new Error(`Non-unique keys detected in: ${keys.join(", ")}`);
|
|
2563
|
+
}
|
|
2564
|
+
};
|
|
2565
|
+
assertUniqueKeys(from);
|
|
2566
|
+
assertUniqueKeys(to);
|
|
2567
|
+
}
|
|
2568
|
+
if (from.length === to.length && from.every((key, i) => key === to[i])) {
|
|
2569
|
+
for (let i = 0; i < sourceNodes.length; i++) this.elementChildPatches(sourceNodes[i], targetDescriptions[i], parent);
|
|
2570
|
+
return;
|
|
2571
|
+
}
|
|
2572
|
+
const mapByKey = (keys, items) => {
|
|
2573
|
+
const map = /* @__PURE__ */ new Map();
|
|
2574
|
+
keys.forEach((key, index) => {
|
|
2575
|
+
if (!map.has(key)) map.set(key, items[index]);
|
|
2576
|
+
});
|
|
2577
|
+
return map;
|
|
2578
|
+
};
|
|
2579
|
+
const sourceNodesMap = mapByKey(from, sourceNodes);
|
|
2580
|
+
const targetDescriptionsMap = mapByKey(to, targetDescriptions);
|
|
2581
|
+
const getNode = (key, isMove) => {
|
|
2582
|
+
const sourceNode = sourceNodesMap.get(key);
|
|
2583
|
+
if (sourceNode) return sourceNode;
|
|
2584
|
+
if (isMove) return createdNodesMap.get(key);
|
|
2585
|
+
return createNode(targetDescriptionsMap.get(key), key);
|
|
2586
|
+
};
|
|
2587
|
+
const moves = Reconciler.calculateMoves(from, to);
|
|
2588
|
+
const children = [...sourceNodes];
|
|
2589
|
+
for (const move of moves) {
|
|
2590
|
+
const node = getNode(move.item, move.name === Move.Name.MOVE);
|
|
2591
|
+
switch (move.name) {
|
|
2592
|
+
case Move.Name.REMOVE:
|
|
2593
|
+
this.addPatch(Patch.removeChild(node, move.at, parent));
|
|
2594
|
+
Move.remove(node, move.at).make(children);
|
|
2595
|
+
continue;
|
|
2596
|
+
case Move.Name.INSERT:
|
|
2597
|
+
this.addPatch(Patch.insertChild(node, move.at, parent));
|
|
2598
|
+
Move.insert(node, move.at).make(children);
|
|
2599
|
+
continue;
|
|
2600
|
+
case Move.Name.MOVE:
|
|
2601
|
+
this.addPatch(Patch.moveChild(node, move.from, move.to, parent));
|
|
2602
|
+
Move.move(node, move.from, move.to).make(children);
|
|
2603
|
+
continue;
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
for (let i = 0; i < children.length; i++) {
|
|
2607
|
+
const child = children[i];
|
|
2608
|
+
if (!created.has(child)) {
|
|
2609
|
+
const targetDescription = targetDescriptions[i];
|
|
2610
|
+
this.elementChildPatches(child, targetDescription, parent);
|
|
2611
|
+
}
|
|
2612
|
+
}
|
|
2613
|
+
}
|
|
2614
|
+
elementChildPatches(child, description, parent) {
|
|
2615
|
+
if (child.description.isCompatible(description)) this.childPatches(child, description);
|
|
2616
|
+
else {
|
|
2617
|
+
const node = VirtualDOM.createFromDescription(description, parent, this.root);
|
|
2618
|
+
this.addPatch(Patch.replaceChild(child, node, parent));
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2621
|
+
/**
|
|
2622
|
+
* Returns a normalized type of given item.
|
|
2623
|
+
*/
|
|
2624
|
+
static getType(item) {
|
|
2625
|
+
const type = typeof item;
|
|
2626
|
+
if (type !== "object") return type;
|
|
2627
|
+
if (item === null) return "null";
|
|
2628
|
+
if (Array.isArray(item)) return "array";
|
|
2629
|
+
return "object";
|
|
2630
|
+
}
|
|
2631
|
+
static createKey(index) {
|
|
2632
|
+
return String(index).padStart(8, "0");
|
|
2633
|
+
}
|
|
2634
|
+
static deepEqual(current, next) {
|
|
2635
|
+
if (Object.is(current, next)) return true;
|
|
2636
|
+
const type = this.getType(current);
|
|
2637
|
+
if (type !== this.getType(next)) return false;
|
|
2638
|
+
if (type === "array") {
|
|
2639
|
+
const currentArray = current;
|
|
2640
|
+
const nextArray = next;
|
|
2641
|
+
if (currentArray.length !== nextArray.length) return false;
|
|
2642
|
+
for (let i = 0; i < currentArray.length; i++) if (!this.deepEqual(currentArray[i], nextArray[i])) return false;
|
|
2643
|
+
return true;
|
|
2644
|
+
} else if (type === "object") {
|
|
2645
|
+
const currentObject = current;
|
|
2646
|
+
const nextObject = next;
|
|
2647
|
+
if (currentObject.constructor !== nextObject.constructor) return false;
|
|
2648
|
+
if (current instanceof Date) return Object.is(current.getTime(), next.getTime());
|
|
2649
|
+
if (current instanceof Map || current instanceof Set) return this.deepEqual([...current], [...next]);
|
|
2650
|
+
const keys = Object.keys(currentObject);
|
|
2651
|
+
if (keys.length !== Object.keys(nextObject).length) return false;
|
|
2652
|
+
for (const key of keys) if (!Object.prototype.propertyIsEnumerable.call(nextObject, key) || !this.deepEqual(currentObject[key], nextObject[key])) return false;
|
|
2653
|
+
return true;
|
|
2654
|
+
}
|
|
2655
|
+
return false;
|
|
2656
|
+
}
|
|
2657
|
+
};
|
|
2658
|
+
//#endregion
|
|
2659
|
+
//#region src/core/renderer.ts
|
|
2660
|
+
const Renderer = {
|
|
2661
|
+
/**
|
|
2662
|
+
* Calls the component render method and transforms the returned template
|
|
2663
|
+
* into the normalised description of the rendered node.
|
|
2664
|
+
*/
|
|
2665
|
+
render(component, props = {}, children = []) {
|
|
2666
|
+
return component.renderDescription(props, children);
|
|
2667
|
+
},
|
|
2668
|
+
/**
|
|
2669
|
+
* Updates the Web component and patches the DOM tree
|
|
2670
|
+
* to match the new component state.
|
|
2671
|
+
*/
|
|
2672
|
+
update(root, from, to, commands) {
|
|
2673
|
+
const update = {
|
|
2674
|
+
commands,
|
|
2675
|
+
root,
|
|
2676
|
+
state: {
|
|
2677
|
+
from,
|
|
2678
|
+
to
|
|
2679
|
+
}
|
|
2680
|
+
};
|
|
2681
|
+
this.onBeforeUpdate(update, root);
|
|
2682
|
+
const diff = new Diff(root, from, to);
|
|
2683
|
+
root.state = to;
|
|
2684
|
+
update.patches = diff.apply();
|
|
2685
|
+
this.onAfterUpdate(update, root);
|
|
2686
|
+
},
|
|
2687
|
+
/**
|
|
2688
|
+
* Notifies the observers about upcoming update.
|
|
2689
|
+
*/
|
|
2690
|
+
onBeforeUpdate(update, root) {
|
|
2691
|
+
root.plugins.notify("before-update", update);
|
|
2692
|
+
},
|
|
2693
|
+
/**
|
|
2694
|
+
* Notifies the observers about completed update.
|
|
2695
|
+
*/
|
|
2696
|
+
onAfterUpdate(update, root) {
|
|
2697
|
+
root.plugins.notify("after-update", update);
|
|
2698
|
+
}
|
|
2699
|
+
};
|
|
2700
|
+
//#endregion
|
|
2701
|
+
//#region src/core/toolkit.ts
|
|
2702
|
+
const roots = /* @__PURE__ */ new Set();
|
|
2703
|
+
const defaultSettings = Object.freeze({ debug: false });
|
|
2704
|
+
const pureComponentClassRegistry = /* @__PURE__ */ new Map();
|
|
2705
|
+
const createPlugins = (manifests = []) => {
|
|
2706
|
+
const plugins = new Plugins(null);
|
|
2707
|
+
for (const manifest of manifests) plugins.register(manifest);
|
|
2708
|
+
return plugins;
|
|
2709
|
+
};
|
|
2710
|
+
let plugins = createPlugins();
|
|
2711
|
+
/**
|
|
2712
|
+
* Returns a PureComponent class rendering the template
|
|
2713
|
+
* provided by the specified function.
|
|
2714
|
+
*/
|
|
2715
|
+
const resolvePureComponentClass = (fn) => {
|
|
2716
|
+
let ComponentClass = pureComponentClassRegistry.get(fn);
|
|
2717
|
+
if (ComponentClass) return ComponentClass;
|
|
2718
|
+
ComponentClass = class PureComponent extends Component {
|
|
2719
|
+
static renderer = fn;
|
|
2720
|
+
render() {
|
|
2721
|
+
return fn.call(this, this.props);
|
|
2722
|
+
}
|
|
2723
|
+
};
|
|
2724
|
+
pureComponentClassRegistry.set(fn, ComponentClass);
|
|
2725
|
+
return ComponentClass;
|
|
2726
|
+
};
|
|
2727
|
+
/**
|
|
2728
|
+
* Returns resolved Component class.
|
|
2729
|
+
*/
|
|
2730
|
+
const resolveComponentClass = (component, type) => {
|
|
2731
|
+
switch (type) {
|
|
2732
|
+
case "component": return component;
|
|
2733
|
+
case "function": return resolvePureComponentClass(component);
|
|
2734
|
+
default: throw new Error(`Unsupported component type: ${type}`);
|
|
2735
|
+
}
|
|
2736
|
+
};
|
|
2737
|
+
const track = (root) => {
|
|
2738
|
+
if (root.parentNode) {
|
|
2739
|
+
const parentRootNode = root.parentNode.rootNode;
|
|
2740
|
+
parentRootNode.subroots.add(root);
|
|
2741
|
+
root.stopTracking = () => {
|
|
2742
|
+
parentRootNode.subroots.delete(root);
|
|
2743
|
+
};
|
|
2744
|
+
} else {
|
|
2745
|
+
roots.add(root);
|
|
2746
|
+
root.stopTracking = () => {
|
|
2747
|
+
roots.delete(root);
|
|
2748
|
+
};
|
|
2749
|
+
}
|
|
2750
|
+
};
|
|
2751
|
+
var Toolkit = class {
|
|
2752
|
+
constructor() {
|
|
2753
|
+
this.settings = defaultSettings;
|
|
2754
|
+
this.assert = console.assert;
|
|
2755
|
+
}
|
|
2756
|
+
/**
|
|
2757
|
+
* Configures Toolkit with given options, keeping the current values
|
|
2758
|
+
* of the options not provided, by default no debug mode and no plugins.
|
|
2759
|
+
* Changed plugins are uninstalled from the created roots, and the new
|
|
2760
|
+
* ones installed in their place, with their stylesheets loaded.
|
|
2761
|
+
*/
|
|
2762
|
+
configure(options) {
|
|
2763
|
+
if (options.debug !== void 0) this.settings = Object.freeze({ debug: options.debug });
|
|
2764
|
+
const manifests = options.plugins;
|
|
2765
|
+
const previous = [...plugins];
|
|
2766
|
+
const origins = previous.map((plugin) => plugin.origin);
|
|
2767
|
+
if (!manifests || isSameList(origins, manifests)) return;
|
|
2768
|
+
Sandbox.clearPluginMethods();
|
|
2769
|
+
plugins = createPlugins(manifests);
|
|
2770
|
+
for (const root of this.tracked) {
|
|
2771
|
+
for (const plugin of previous) root.plugins.uninstall(plugin.name);
|
|
2772
|
+
for (const plugin of root.plugins) plugin.register?.();
|
|
2773
|
+
for (const plugin of plugins) {
|
|
2774
|
+
root.plugins.register(plugin);
|
|
2775
|
+
root.plugins.install(plugin);
|
|
2776
|
+
}
|
|
2777
|
+
if (root.shadow) updateStylesheets(root);
|
|
2778
|
+
}
|
|
2779
|
+
}
|
|
2780
|
+
/**
|
|
2781
|
+
* Resets Toolkit to a pristine state, with the default settings
|
|
2782
|
+
* and no plugins, forgetting the created roots.
|
|
2783
|
+
*/
|
|
2784
|
+
reset() {
|
|
2785
|
+
plugins.destroy();
|
|
2786
|
+
plugins = createPlugins();
|
|
2787
|
+
roots.clear();
|
|
2788
|
+
this.settings = defaultSettings;
|
|
2789
|
+
pureComponentClassRegistry.clear();
|
|
2790
|
+
Sandbox.clearPluginMethods();
|
|
2791
|
+
}
|
|
2792
|
+
get tracked() {
|
|
2793
|
+
const tracked = [];
|
|
2794
|
+
for (const root of roots) tracked.push(root, ...root.tracked);
|
|
2795
|
+
return tracked;
|
|
2796
|
+
}
|
|
2797
|
+
isDebug() {
|
|
2798
|
+
return this.settings.debug;
|
|
2799
|
+
}
|
|
2800
|
+
warn(...messages) {
|
|
2801
|
+
if (this.isDebug()) console.warn(...messages);
|
|
2802
|
+
}
|
|
2803
|
+
async createRoot(component, props = {}) {
|
|
2804
|
+
const description = Template.describe([component, props]);
|
|
2805
|
+
return CreatedRoots.destroyingRootsOnError(() => VirtualDOM.createWebComponent(description, null));
|
|
2806
|
+
}
|
|
2807
|
+
async render(component, container, props = {}) {
|
|
2808
|
+
return (await this.createRoot(component, props)).mount(container);
|
|
2809
|
+
}
|
|
2810
|
+
};
|
|
2811
|
+
const toolkit = new Toolkit();
|
|
2812
|
+
provideRuntime({
|
|
2813
|
+
get plugins() {
|
|
2814
|
+
return plugins;
|
|
2815
|
+
},
|
|
2816
|
+
assert: (condition, message) => toolkit.assert(condition, message),
|
|
2817
|
+
isDebug: () => toolkit.isDebug(),
|
|
2818
|
+
warn: (...messages) => toolkit.warn(...messages),
|
|
2819
|
+
track,
|
|
2820
|
+
resolveComponentClass,
|
|
2821
|
+
update: (root, from, to, commands) => Renderer.update(root, from, to, commands)
|
|
2822
|
+
});
|
|
2823
|
+
//#endregion
|
|
2824
|
+
//#region src/index.ts
|
|
2825
|
+
const classes = {
|
|
2826
|
+
Component,
|
|
2827
|
+
WebComponent,
|
|
2828
|
+
Root: WebComponent,
|
|
2829
|
+
Service,
|
|
2830
|
+
utils: Utils,
|
|
2831
|
+
noop: () => {}
|
|
2832
|
+
};
|
|
2833
|
+
Object.assign(Toolkit.prototype, classes);
|
|
2834
|
+
//#endregion
|
|
2835
|
+
//#region src/release.ts
|
|
2836
|
+
globalThis.toolkit = toolkit;
|
|
2837
|
+
//#endregion
|
|
2838
|
+
})();
|
|
2839
|
+
|
|
2840
|
+
//# sourceMappingURL=toolkit-0.71.0.js.map
|