@radishland/runtime 0.3.1 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/client/index.d.ts +3 -1
- package/client/index.js +180 -3
- package/package.json +1 -5
- package/client/boot.d.ts +0 -2
- package/client/boot.js +0 -169
package/README.md
CHANGED
package/client/index.d.ts
CHANGED
@@ -61,6 +61,8 @@ declare class HandlerRegistry extends HTMLElement implements AutonomousCustomEle
|
|
61
61
|
* Looks up an identifier on the instance
|
62
62
|
*/
|
63
63
|
lookup(identifier: string): any;
|
64
|
+
hydrateElement(element: Element): void;
|
65
|
+
hydrate(node?: Node): void;
|
64
66
|
connectedCallback(): void;
|
65
67
|
disconnectedCallback(): void;
|
66
68
|
}
|
@@ -102,4 +104,4 @@ declare const effect: (cb: EffectCallback, options?: EffectOptions) => Destructo
|
|
102
104
|
*/
|
103
105
|
declare const reactive: <T>(thing: T) => T;
|
104
106
|
|
105
|
-
export { HandlerRegistry, computed, effect, isState, reactive, signal };
|
107
|
+
export { type AutonomousCustomElement, HandlerRegistry, computed, effect, isState, reactive, signal };
|
package/client/index.js
CHANGED
@@ -230,7 +230,11 @@ var HandlerRegistry = class extends HTMLElement {
|
|
230
230
|
if (identifier in this && property in target) {
|
231
231
|
const ref = this.lookup(identifier);
|
232
232
|
this.effect(() => {
|
233
|
-
target[property]
|
233
|
+
if (isState(target[property])) {
|
234
|
+
target[property].value = ref.valueOf();
|
235
|
+
} else {
|
236
|
+
target[property] = ref.valueOf();
|
237
|
+
}
|
234
238
|
});
|
235
239
|
e.stopPropagation();
|
236
240
|
}
|
@@ -278,8 +282,163 @@ var HandlerRegistry = class extends HTMLElement {
|
|
278
282
|
}
|
279
283
|
}
|
280
284
|
}
|
285
|
+
hydrateElement(element) {
|
286
|
+
console.log(`hydrating element ${element.nodeName}`);
|
287
|
+
const attributes = [...element.attributes];
|
288
|
+
const attr = attributes.filter((a) => a.localName.startsWith("attr:"));
|
289
|
+
for (const attribute of attr) {
|
290
|
+
const [_, key] = attribute.localName.split(":");
|
291
|
+
if (!key) throw new Error("Missing <key> in attr:<key>");
|
292
|
+
const attrRequest = new CustomEvent("rad::attr", {
|
293
|
+
bubbles: true,
|
294
|
+
cancelable: true,
|
295
|
+
composed: true,
|
296
|
+
detail: {
|
297
|
+
attribute: key,
|
298
|
+
identifier: attribute.value || key,
|
299
|
+
target: element
|
300
|
+
}
|
301
|
+
});
|
302
|
+
element.dispatchEvent(attrRequest);
|
303
|
+
}
|
304
|
+
for (const property of Object.keys(bindingConfig)) {
|
305
|
+
if (element.hasAttribute(`bind:${property}`)) {
|
306
|
+
const identifier = element.getAttribute(`bind:${property}`)?.trim() || property;
|
307
|
+
const bindRequest = new CustomEvent("rad::bind", {
|
308
|
+
bubbles: true,
|
309
|
+
cancelable: true,
|
310
|
+
composed: true,
|
311
|
+
detail: {
|
312
|
+
property,
|
313
|
+
identifier,
|
314
|
+
target: element
|
315
|
+
}
|
316
|
+
});
|
317
|
+
element.dispatchEvent(bindRequest);
|
318
|
+
}
|
319
|
+
}
|
320
|
+
const bools = attributes.filter((a) => a.localName.startsWith("bool:"));
|
321
|
+
for (const bool of bools) {
|
322
|
+
const [_, key] = bool.localName.split(":");
|
323
|
+
if (!key) throw new Error("Missing <key> in bool:<key>");
|
324
|
+
element.dispatchEvent(
|
325
|
+
new CustomEvent("rad::bool", {
|
326
|
+
bubbles: true,
|
327
|
+
cancelable: true,
|
328
|
+
composed: true,
|
329
|
+
detail: {
|
330
|
+
attribute: key,
|
331
|
+
identifier: bool.value || key,
|
332
|
+
target: element
|
333
|
+
}
|
334
|
+
})
|
335
|
+
);
|
336
|
+
}
|
337
|
+
const classList = element.getAttribute("classlist");
|
338
|
+
if (classList) {
|
339
|
+
const classRequest = new CustomEvent("rad::classlist", {
|
340
|
+
bubbles: true,
|
341
|
+
cancelable: true,
|
342
|
+
composed: true,
|
343
|
+
detail: {
|
344
|
+
identifier: classList,
|
345
|
+
target: element
|
346
|
+
}
|
347
|
+
});
|
348
|
+
element.dispatchEvent(classRequest);
|
349
|
+
}
|
350
|
+
const html = element.getAttribute("html");
|
351
|
+
if (html) {
|
352
|
+
const htmlRequest = new CustomEvent("rad::html", {
|
353
|
+
bubbles: true,
|
354
|
+
cancelable: true,
|
355
|
+
composed: true,
|
356
|
+
detail: {
|
357
|
+
identifier: html,
|
358
|
+
target: element
|
359
|
+
}
|
360
|
+
});
|
361
|
+
element.dispatchEvent(htmlRequest);
|
362
|
+
}
|
363
|
+
const events = attributes.filter((a) => a.localName.startsWith("on:"));
|
364
|
+
for (const event of events) {
|
365
|
+
const [_, type2] = event.localName.split(":");
|
366
|
+
if (!type2) throw new Error("Missing <type> in on:<type>");
|
367
|
+
const onRequest = new CustomEvent("rad::on", {
|
368
|
+
bubbles: true,
|
369
|
+
cancelable: true,
|
370
|
+
composed: true,
|
371
|
+
detail: {
|
372
|
+
type: type2,
|
373
|
+
identifier: event.value || type2,
|
374
|
+
target: element
|
375
|
+
}
|
376
|
+
});
|
377
|
+
element.dispatchEvent(onRequest);
|
378
|
+
}
|
379
|
+
const props = attributes.filter((a) => a.localName.startsWith("prop:"));
|
380
|
+
for (const prop of props) {
|
381
|
+
const [_, key] = prop.localName.split(":");
|
382
|
+
if (!key) throw new Error("Missing <key> in prop:<key>");
|
383
|
+
const propRequest = new CustomEvent("rad::prop", {
|
384
|
+
bubbles: true,
|
385
|
+
cancelable: true,
|
386
|
+
composed: true,
|
387
|
+
detail: {
|
388
|
+
property: key,
|
389
|
+
identifier: prop.value || key,
|
390
|
+
target: element
|
391
|
+
}
|
392
|
+
});
|
393
|
+
element.dispatchEvent(propRequest);
|
394
|
+
}
|
395
|
+
const text = element.getAttribute("text");
|
396
|
+
if (text) {
|
397
|
+
const textRequest = new CustomEvent("rad::text", {
|
398
|
+
bubbles: true,
|
399
|
+
cancelable: true,
|
400
|
+
composed: true,
|
401
|
+
detail: {
|
402
|
+
identifier: text,
|
403
|
+
target: element
|
404
|
+
}
|
405
|
+
});
|
406
|
+
element.dispatchEvent(textRequest);
|
407
|
+
}
|
408
|
+
const hooks = attributes.filter((a) => a.localName.startsWith("use:"));
|
409
|
+
for (const hook of hooks) {
|
410
|
+
const [_, identifier] = hook.localName.split(":");
|
411
|
+
if (!identifier) throw new Error("Missing <id> in use:<id>");
|
412
|
+
const useRequest = new CustomEvent("rad::use", {
|
413
|
+
bubbles: true,
|
414
|
+
cancelable: true,
|
415
|
+
composed: true,
|
416
|
+
detail: {
|
417
|
+
identifier,
|
418
|
+
target: element
|
419
|
+
}
|
420
|
+
});
|
421
|
+
element.dispatchEvent(useRequest);
|
422
|
+
}
|
423
|
+
}
|
424
|
+
// TODO: tree aware hydration that filters out HandlerRegistries and parallelize
|
425
|
+
hydrate(node = this) {
|
426
|
+
if (node instanceof Element) {
|
427
|
+
this.hydrateElement(node);
|
428
|
+
if (node.shadowRoot && node.shadowRoot.mode === "open") {
|
429
|
+
console.log("entering shadow root");
|
430
|
+
this.hydrate(node.shadowRoot);
|
431
|
+
console.log("exiting shadow root");
|
432
|
+
}
|
433
|
+
let child = node.firstElementChild;
|
434
|
+
while (child) {
|
435
|
+
this.hydrate(child);
|
436
|
+
child = child.nextElementSibling;
|
437
|
+
}
|
438
|
+
}
|
439
|
+
}
|
281
440
|
connectedCallback() {
|
282
|
-
console.log(
|
441
|
+
console.log(`[connectedCallback]: ${this.tagName}`);
|
283
442
|
const { signal: signal2 } = this.abortController;
|
284
443
|
this.addEventListener("rad::attr", this.#handleAttr, { signal: signal2 });
|
285
444
|
this.addEventListener("rad::bind", this.#handleBind, { signal: signal2 });
|
@@ -298,6 +457,24 @@ var HandlerRegistry = class extends HTMLElement {
|
|
298
457
|
}
|
299
458
|
}
|
300
459
|
};
|
301
|
-
customElements
|
460
|
+
if (window && !customElements.get("handler-registry")) {
|
461
|
+
customElements?.define("handler-registry", HandlerRegistry);
|
462
|
+
}
|
463
|
+
customElements?.whenDefined("handler-registry").then(() => {
|
464
|
+
const tw = document.createTreeWalker(
|
465
|
+
document,
|
466
|
+
NodeFilter.SHOW_ELEMENT,
|
467
|
+
(node) => {
|
468
|
+
return node instanceof HandlerRegistry ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
|
469
|
+
}
|
470
|
+
);
|
471
|
+
let current = tw.currentNode;
|
472
|
+
while (current && !(current instanceof HandlerRegistry)) {
|
473
|
+
current = tw.nextNode();
|
474
|
+
}
|
475
|
+
if (current) {
|
476
|
+
current.hydrate();
|
477
|
+
}
|
478
|
+
});
|
302
479
|
|
303
480
|
export { HandlerRegistry, computed, effect, isState, reactive, signal };
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@radishland/runtime",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.4.1",
|
4
4
|
"type": "module",
|
5
5
|
"description": "The Radish runtime",
|
6
6
|
"author": "Frédéric Crozatier",
|
@@ -17,10 +17,6 @@
|
|
17
17
|
"import": "./client/index.js",
|
18
18
|
"types": "./client/index.d.ts"
|
19
19
|
},
|
20
|
-
"./boot": {
|
21
|
-
"import": "./client/boot.js",
|
22
|
-
"types": "./client/boot.d.ts"
|
23
|
-
},
|
24
20
|
"./utils": {
|
25
21
|
"import": "./client/utils.js",
|
26
22
|
"types": "./client/utils.d.ts"
|
package/client/boot.d.ts
DELETED
package/client/boot.js
DELETED
@@ -1,169 +0,0 @@
|
|
1
|
-
// src/utils.ts
|
2
|
-
var bindingConfig = {
|
3
|
-
"checked": {
|
4
|
-
type: ["boolean"],
|
5
|
-
event: "change"
|
6
|
-
},
|
7
|
-
"value": {
|
8
|
-
type: ["string", "number"],
|
9
|
-
event: "input"
|
10
|
-
}
|
11
|
-
};
|
12
|
-
|
13
|
-
// src/boot.ts
|
14
|
-
var hydrateElement = (element) => {
|
15
|
-
const attributes = [...element.attributes];
|
16
|
-
const attr = attributes.filter((a) => a.localName.startsWith("attr:"));
|
17
|
-
for (const attribute of attr) {
|
18
|
-
const [_, key] = attribute.localName.split(":");
|
19
|
-
if (!key) throw new Error("Missing <key> in attr:<key>");
|
20
|
-
const attrRequest = new CustomEvent("rad::attr", {
|
21
|
-
bubbles: true,
|
22
|
-
cancelable: true,
|
23
|
-
composed: true,
|
24
|
-
detail: {
|
25
|
-
attribute: key,
|
26
|
-
identifier: attribute.value || key,
|
27
|
-
target: element
|
28
|
-
}
|
29
|
-
});
|
30
|
-
element.dispatchEvent(attrRequest);
|
31
|
-
}
|
32
|
-
for (const property of Object.keys(bindingConfig)) {
|
33
|
-
if (element.hasAttribute(`bind:${property}`)) {
|
34
|
-
const identifier = element.getAttribute(`bind:${property}`)?.trim() || property;
|
35
|
-
const bindRequest = new CustomEvent("rad::bind", {
|
36
|
-
bubbles: true,
|
37
|
-
cancelable: true,
|
38
|
-
composed: true,
|
39
|
-
detail: {
|
40
|
-
property,
|
41
|
-
identifier,
|
42
|
-
target: element
|
43
|
-
}
|
44
|
-
});
|
45
|
-
element.dispatchEvent(bindRequest);
|
46
|
-
}
|
47
|
-
}
|
48
|
-
const bools = attributes.filter((a) => a.localName.startsWith("bool:"));
|
49
|
-
for (const bool of bools) {
|
50
|
-
const [_, key] = bool.localName.split(":");
|
51
|
-
if (!key) throw new Error("Missing <key> in bool:<key>");
|
52
|
-
element.dispatchEvent(
|
53
|
-
new CustomEvent("rad::bool", {
|
54
|
-
bubbles: true,
|
55
|
-
cancelable: true,
|
56
|
-
composed: true,
|
57
|
-
detail: {
|
58
|
-
attribute: key,
|
59
|
-
identifier: bool.value || key,
|
60
|
-
target: element
|
61
|
-
}
|
62
|
-
})
|
63
|
-
);
|
64
|
-
}
|
65
|
-
const classList = element.getAttribute("classlist");
|
66
|
-
if (classList) {
|
67
|
-
const classRequest = new CustomEvent("rad::classlist", {
|
68
|
-
bubbles: true,
|
69
|
-
cancelable: true,
|
70
|
-
composed: true,
|
71
|
-
detail: {
|
72
|
-
identifier: classList,
|
73
|
-
target: element
|
74
|
-
}
|
75
|
-
});
|
76
|
-
element.dispatchEvent(classRequest);
|
77
|
-
}
|
78
|
-
const html = element.getAttribute("html");
|
79
|
-
if (html) {
|
80
|
-
const htmlRequest = new CustomEvent("rad::html", {
|
81
|
-
bubbles: true,
|
82
|
-
cancelable: true,
|
83
|
-
composed: true,
|
84
|
-
detail: {
|
85
|
-
identifier: html,
|
86
|
-
target: element
|
87
|
-
}
|
88
|
-
});
|
89
|
-
element.dispatchEvent(htmlRequest);
|
90
|
-
}
|
91
|
-
const events = attributes.filter((a) => a.localName.startsWith("on:"));
|
92
|
-
for (const event of events) {
|
93
|
-
const [_, type] = event.localName.split(":");
|
94
|
-
if (!type) throw new Error("Missing <type> in on:<type>");
|
95
|
-
const onRequest = new CustomEvent("rad::on", {
|
96
|
-
bubbles: true,
|
97
|
-
cancelable: true,
|
98
|
-
composed: true,
|
99
|
-
detail: {
|
100
|
-
type,
|
101
|
-
identifier: event.value || type,
|
102
|
-
target: element
|
103
|
-
}
|
104
|
-
});
|
105
|
-
element.dispatchEvent(onRequest);
|
106
|
-
}
|
107
|
-
const props = attributes.filter((a) => a.localName.startsWith("prop:"));
|
108
|
-
for (const prop of props) {
|
109
|
-
const [_, key] = prop.localName.split(":");
|
110
|
-
if (!key) throw new Error("Missing <key> in prop:<key>");
|
111
|
-
const propRequest = new CustomEvent("rad::prop", {
|
112
|
-
bubbles: true,
|
113
|
-
cancelable: true,
|
114
|
-
composed: true,
|
115
|
-
detail: {
|
116
|
-
property: key,
|
117
|
-
identifier: prop.value || key,
|
118
|
-
target: element
|
119
|
-
}
|
120
|
-
});
|
121
|
-
element.dispatchEvent(propRequest);
|
122
|
-
}
|
123
|
-
const text = element.getAttribute("text");
|
124
|
-
if (text) {
|
125
|
-
const textRequest = new CustomEvent("rad::text", {
|
126
|
-
bubbles: true,
|
127
|
-
cancelable: true,
|
128
|
-
composed: true,
|
129
|
-
detail: {
|
130
|
-
identifier: text,
|
131
|
-
target: element
|
132
|
-
}
|
133
|
-
});
|
134
|
-
element.dispatchEvent(textRequest);
|
135
|
-
}
|
136
|
-
const hooks = attributes.filter((a) => a.localName.startsWith("use:"));
|
137
|
-
for (const hook of hooks) {
|
138
|
-
const [_, identifier] = hook.localName.split(":");
|
139
|
-
if (!identifier) throw new Error("Missing <id> in use:<id>");
|
140
|
-
const useRequest = new CustomEvent("rad::use", {
|
141
|
-
bubbles: true,
|
142
|
-
cancelable: true,
|
143
|
-
composed: true,
|
144
|
-
detail: {
|
145
|
-
identifier,
|
146
|
-
target: element
|
147
|
-
}
|
148
|
-
});
|
149
|
-
element.dispatchEvent(useRequest);
|
150
|
-
}
|
151
|
-
};
|
152
|
-
var hydrate = (root) => {
|
153
|
-
const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
|
154
|
-
let node = walker.currentNode;
|
155
|
-
do {
|
156
|
-
if (node instanceof Element) {
|
157
|
-
console.log(node.tagName);
|
158
|
-
hydrateElement(node);
|
159
|
-
if (node.shadowRoot) {
|
160
|
-
console.log("entering shadow root");
|
161
|
-
hydrate(node.shadowRoot);
|
162
|
-
console.log("exiting shadow root");
|
163
|
-
}
|
164
|
-
}
|
165
|
-
} while (node = walker.nextNode());
|
166
|
-
};
|
167
|
-
customElements?.whenDefined("handler-registry").then(() => {
|
168
|
-
hydrate(document.body);
|
169
|
-
});
|