@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 CHANGED
@@ -1,3 +1,3 @@
1
1
  # Radish runtime
2
2
 
3
- This package contains the runtime of the Radish WebComponents framework
3
+ This package contains the runtime of the Radish WebComponents framework
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] = ref.valueOf();
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(`${this.tagName} connected`);
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?.define("handler-registry", HandlerRegistry);
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.1",
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
@@ -1,2 +0,0 @@
1
-
2
- export { }
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
- });