@radishland/runtime 0.1.2 → 0.2.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/client/boot.js CHANGED
@@ -2,147 +2,170 @@
2
2
  var spaces_sep_by_comma = /\s*,\s*/;
3
3
  var bindingConfig = {
4
4
  "checked": {
5
- element: ["input"],
6
5
  type: ["boolean"],
7
6
  event: "change"
8
7
  },
9
8
  "value": {
10
- element: ["input", "select", "textarea"],
11
9
  type: ["string", "number"],
12
10
  event: "input"
13
11
  }
14
12
  };
15
13
 
16
14
  // src/boot.ts
17
- var bindingsQueryString = Object.keys(bindingConfig).map(
18
- (property) => `[\\@bind\\:${property}]`
19
- ).join(",");
20
- setTimeout(() => {
21
- customElements?.whenDefined("handler-registry").then(() => {
22
- document.querySelectorAll(
23
- `[\\@on],[\\@use],[\\@attr],[\\@attr\\|client],[\\@prop],${bindingsQueryString},[\\@text],[\\@html]`
24
- ).forEach(
25
- (entry) => {
26
- const events = entry.getAttribute("@on")?.trim()?.split(spaces_sep_by_comma);
27
- if (events) {
28
- for (const event of events) {
29
- const [type, handler] = event.split(":");
30
- const onRequest = new CustomEvent("@on-request", {
31
- bubbles: true,
32
- cancelable: true,
33
- composed: true,
34
- detail: {
35
- type,
36
- handler: handler || type
37
- }
38
- });
39
- entry.dispatchEvent(onRequest);
40
- }
41
- }
42
- const hooks = entry.getAttribute("@use")?.trim()?.split(spaces_sep_by_comma);
43
- if (hooks) {
44
- for (const hook of hooks) {
45
- const useRequest = new CustomEvent("@use-request", {
46
- bubbles: true,
47
- cancelable: true,
48
- composed: true,
49
- detail: {
50
- hook
51
- }
52
- });
53
- entry.dispatchEvent(useRequest);
54
- }
15
+ var hydrateElement = (element) => {
16
+ const attributes = ["@attr", "@attr|client"].map((item) => element?.getAttribute(item)).filter((attr) => attr !== null && attr !== void 0).flatMap((attr) => attr.trim().split(spaces_sep_by_comma));
17
+ for (const attribute of attributes) {
18
+ const [key, value] = attribute.split(":");
19
+ const attrRequest = new CustomEvent("@attr-request", {
20
+ bubbles: true,
21
+ cancelable: true,
22
+ composed: true,
23
+ detail: {
24
+ attribute: key,
25
+ identifier: value || key,
26
+ target: element
27
+ }
28
+ });
29
+ element.dispatchEvent(attrRequest);
30
+ }
31
+ for (const property of Object.keys(bindingConfig)) {
32
+ if (element.hasAttribute(`@bind:${property}`)) {
33
+ const identifier = element.getAttribute(`@bind:${property}`)?.trim() || property;
34
+ const bindRequest = new CustomEvent("@bind-request", {
35
+ bubbles: true,
36
+ cancelable: true,
37
+ composed: true,
38
+ detail: {
39
+ property,
40
+ identifier,
41
+ target: element
55
42
  }
56
- const props = entry.getAttribute("@prop")?.trim().split(spaces_sep_by_comma);
57
- if (props) {
58
- for (const prop of props) {
59
- const [key, value] = prop.split(":");
60
- const propRequest = new CustomEvent("@prop-request", {
61
- bubbles: true,
62
- cancelable: true,
63
- composed: true,
64
- detail: {
65
- property: key,
66
- identifier: value || key
67
- }
68
- });
69
- entry.dispatchEvent(propRequest);
43
+ });
44
+ element.dispatchEvent(bindRequest);
45
+ }
46
+ }
47
+ const booleanAttributes = element.getAttribute("@bool")?.trim().split(spaces_sep_by_comma);
48
+ if (booleanAttributes) {
49
+ for (const bool of booleanAttributes) {
50
+ const [key, value] = bool.split(":");
51
+ element.dispatchEvent(
52
+ new CustomEvent("@bool-request", {
53
+ bubbles: true,
54
+ cancelable: true,
55
+ composed: true,
56
+ detail: {
57
+ attribute: key,
58
+ identifier: value || key,
59
+ target: element
70
60
  }
61
+ })
62
+ );
63
+ }
64
+ }
65
+ const classList = element.getAttribute("@class");
66
+ if (classList) {
67
+ const classRequest = new CustomEvent("@class-request", {
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("@html-request", {
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 = element.getAttribute("@on")?.trim().split(spaces_sep_by_comma);
92
+ if (events) {
93
+ for (const event of events) {
94
+ const [type, handler] = event.split(":");
95
+ const onRequest = new CustomEvent("@on-request", {
96
+ bubbles: true,
97
+ cancelable: true,
98
+ composed: true,
99
+ detail: {
100
+ type,
101
+ identifier: handler || type,
102
+ target: element
71
103
  }
72
- const text = entry.hasAttribute("@text");
73
- if (text) {
74
- const identifier = entry.getAttribute("@text") || "text";
75
- const textRequest = new CustomEvent("@text-request", {
76
- bubbles: true,
77
- cancelable: true,
78
- composed: true,
79
- detail: {
80
- identifier
81
- }
82
- });
83
- entry.dispatchEvent(textRequest);
84
- }
85
- const html = entry.hasAttribute("@html");
86
- if (html) {
87
- const identifier = entry.getAttribute("@html") || "html";
88
- const htmlRequest = new CustomEvent("@html-request", {
89
- bubbles: true,
90
- cancelable: true,
91
- composed: true,
92
- detail: {
93
- identifier
94
- }
95
- });
96
- entry.dispatchEvent(htmlRequest);
97
- }
98
- const classList = entry.hasAttribute("@class");
99
- if (classList) {
100
- const identifier = entry.getAttribute("@class") || "class";
101
- const classRequest = new CustomEvent("@class-request", {
102
- bubbles: true,
103
- cancelable: true,
104
- composed: true,
105
- detail: {
106
- identifier
107
- }
108
- });
109
- entry.dispatchEvent(classRequest);
110
- }
111
- const attributes = [
112
- ...entry.getAttribute("@attr")?.trim().split(spaces_sep_by_comma) ?? [],
113
- ...entry.getAttribute("@attr|client")?.trim().split(spaces_sep_by_comma) ?? []
114
- ];
115
- if (attributes.length > 0) {
116
- for (const attribute of attributes) {
117
- const [key, value] = attribute.split(":");
118
- const attrRequest = new CustomEvent("@attr-request", {
119
- bubbles: true,
120
- cancelable: true,
121
- composed: true,
122
- detail: {
123
- attribute: key,
124
- identifier: value || key
125
- }
126
- });
127
- entry.dispatchEvent(attrRequest);
128
- }
104
+ });
105
+ element.dispatchEvent(onRequest);
106
+ }
107
+ }
108
+ const props = element.getAttribute("@prop")?.trim().split(spaces_sep_by_comma);
109
+ if (props) {
110
+ for (const prop of props) {
111
+ const [key, value] = prop.split(":");
112
+ const propRequest = new CustomEvent("@prop-request", {
113
+ bubbles: true,
114
+ cancelable: true,
115
+ composed: true,
116
+ detail: {
117
+ property: key,
118
+ identifier: value || key,
119
+ target: element
129
120
  }
130
- for (const property of Object.keys(bindingConfig)) {
131
- if (entry.hasAttribute(`@bind:${property}`)) {
132
- const identifier = entry.getAttribute(`@bind:${property}`)?.trim() || property;
133
- const bindRequest = new CustomEvent("@bind-request", {
134
- bubbles: true,
135
- cancelable: true,
136
- composed: true,
137
- detail: {
138
- property,
139
- identifier
140
- }
141
- });
142
- entry.dispatchEvent(bindRequest);
143
- }
121
+ });
122
+ element.dispatchEvent(propRequest);
123
+ }
124
+ }
125
+ const text = element.getAttribute("@text");
126
+ if (text) {
127
+ const textRequest = new CustomEvent("@text-request", {
128
+ bubbles: true,
129
+ cancelable: true,
130
+ composed: true,
131
+ detail: {
132
+ identifier: text,
133
+ target: element
134
+ }
135
+ });
136
+ element.dispatchEvent(textRequest);
137
+ }
138
+ const hooks = element.getAttribute("@use")?.trim().split(spaces_sep_by_comma);
139
+ if (hooks) {
140
+ for (const hook of hooks) {
141
+ const useRequest = new CustomEvent("@use-request", {
142
+ bubbles: true,
143
+ cancelable: true,
144
+ composed: true,
145
+ detail: {
146
+ identifier: hook,
147
+ target: element
144
148
  }
149
+ });
150
+ element.dispatchEvent(useRequest);
151
+ }
152
+ }
153
+ };
154
+ var hydrate = (root) => {
155
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
156
+ let node = walker.currentNode;
157
+ do {
158
+ if (node instanceof Element) {
159
+ console.log(node.tagName);
160
+ hydrateElement(node);
161
+ if (node.shadowRoot) {
162
+ console.log("entering shadow root");
163
+ hydrate(node.shadowRoot);
164
+ console.log("exiting shadow root");
145
165
  }
146
- );
147
- });
148
- }, 100);
166
+ }
167
+ } while (node = walker.nextNode());
168
+ };
169
+ customElements?.whenDefined("handler-registry").then(() => {
170
+ hydrate(document.body);
171
+ });
package/client/index.js CHANGED
@@ -12,11 +12,9 @@ function type(value) {
12
12
  if (!["object", "function"].includes(baseType)) {
13
13
  return baseType;
14
14
  }
15
- if (typeof value === "object" && Symbol.toStringTag in value) {
16
- const tag = value[Symbol.toStringTag];
17
- if (typeof tag === "string") {
18
- return tag;
19
- }
15
+ const tag = value[Symbol.toStringTag];
16
+ if (typeof tag === "string") {
17
+ return tag;
20
18
  }
21
19
  if (baseType === "function" && Function.prototype.toString.call(value).startsWith("class")) {
22
20
  return "class";
@@ -79,12 +77,10 @@ var booleanAttributes = [
79
77
  ];
80
78
  var bindingConfig = {
81
79
  "checked": {
82
- element: ["input"],
83
80
  type: ["boolean"],
84
81
  event: "change"
85
82
  },
86
83
  "value": {
87
- element: ["input", "select", "textarea"],
88
84
  type: ["string", "number"],
89
85
  event: "input"
90
86
  }
@@ -139,10 +135,9 @@ var HandlerRegistry = class extends HTMLElement {
139
135
  *
140
136
  * The `abort` method of the controller is called in the `disconnectedCallback` method. It allows to cleanup event handlers and other abortable operations
141
137
  */
142
- abortController;
138
+ abortController = new AbortController();
143
139
  constructor() {
144
140
  super();
145
- this.abortController = new AbortController();
146
141
  }
147
142
  /**
148
143
  * Creates an effect that is automatically cleaned up when the component is disconnected
@@ -150,7 +145,7 @@ var HandlerRegistry = class extends HTMLElement {
150
145
  * An optional AbortSignal can be provided to abort the effect prematurely
151
146
  */
152
147
  effect(callback) {
153
- effect(callback, { signal: this.abortController.signal });
148
+ effect(callback, this.abortController);
154
149
  }
155
150
  /**
156
151
  * Looks up an identifier on the instance
@@ -158,19 +153,46 @@ var HandlerRegistry = class extends HTMLElement {
158
153
  lookup(identifier) {
159
154
  return this[identifier];
160
155
  }
156
+ #handleAttr(e) {
157
+ if (e instanceof CustomEvent) {
158
+ const { identifier, attribute, target } = e.detail;
159
+ if (identifier in this && target instanceof HTMLElement && attribute in target) {
160
+ const ref = this.lookup(identifier);
161
+ this.effect(() => {
162
+ if (booleanAttributes.includes(attribute)) {
163
+ target.toggleAttribute(attribute, ref.valueOf());
164
+ } else {
165
+ target.setAttribute(attribute, `${ref}`);
166
+ }
167
+ });
168
+ e.stopPropagation();
169
+ }
170
+ }
171
+ }
172
+ #handleBool(e) {
173
+ if (e instanceof CustomEvent) {
174
+ const { identifier, attribute, target } = e.detail;
175
+ if (identifier in this && target instanceof HTMLElement) {
176
+ const ref = this.lookup(identifier);
177
+ this.effect(() => {
178
+ target.toggleAttribute(attribute, ref.valueOf());
179
+ });
180
+ e.stopPropagation();
181
+ }
182
+ }
183
+ }
161
184
  #handleOn(e) {
162
185
  if (e instanceof CustomEvent) {
163
- const { handler, type: type2 } = e.detail;
164
- if (handler in this && typeof this.lookup(handler) === "function") {
165
- e.target?.addEventListener(type2, this.lookup(handler).bind(this));
186
+ const { identifier, type: type2, target } = e.detail;
187
+ if (identifier in this && typeof this.lookup(identifier) === "function") {
188
+ target.addEventListener(type2, this.lookup(identifier).bind(this));
166
189
  e.stopPropagation();
167
190
  }
168
191
  }
169
192
  }
170
193
  #handleClass(e) {
171
- const target = e.target;
172
- if (e instanceof CustomEvent && target) {
173
- const { identifier } = e.detail;
194
+ if (e instanceof CustomEvent) {
195
+ const { identifier, target } = e.detail;
174
196
  if (identifier in this) {
175
197
  this.effect(() => {
176
198
  const classList = this.lookup(identifier)?.valueOf();
@@ -192,9 +214,9 @@ var HandlerRegistry = class extends HTMLElement {
192
214
  }
193
215
  #handleUse(e) {
194
216
  if (e instanceof CustomEvent) {
195
- const { hook } = e.detail;
196
- if (hook in this && typeof this.lookup(hook) === "function") {
197
- const cleanup = this.lookup(hook).bind(this)(e.target);
217
+ const { identifier, target } = e.detail;
218
+ if (identifier in this && typeof this.lookup(identifier) === "function") {
219
+ const cleanup = this.lookup(identifier).bind(this)(target);
198
220
  if (typeof cleanup === "function") {
199
221
  this.#cleanup.push(cleanup);
200
222
  }
@@ -202,28 +224,10 @@ var HandlerRegistry = class extends HTMLElement {
202
224
  }
203
225
  }
204
226
  }
205
- #handleAttr(e) {
206
- if (e instanceof CustomEvent) {
207
- const { identifier, attribute } = e.detail;
208
- const target = e.target;
209
- if (identifier in this && target instanceof HTMLElement && attribute in target) {
210
- const ref = this.lookup(identifier);
211
- this.effect(() => {
212
- if (booleanAttributes.includes(attribute)) {
213
- ref.valueOf() ? target.setAttribute(attribute, "") : target.removeAttribute(attribute);
214
- } else {
215
- target.setAttribute(attribute, `${ref}`);
216
- }
217
- });
218
- e.stopPropagation();
219
- }
220
- }
221
- }
222
227
  #handleProp(e) {
223
228
  if (e instanceof CustomEvent) {
224
- const { identifier, property } = e.detail;
225
- const target = e.target;
226
- if (identifier in this && target && property in target) {
229
+ const { identifier, property, target } = e.detail;
230
+ if (identifier in this && property in target) {
227
231
  const ref = this.lookup(identifier);
228
232
  this.effect(() => {
229
233
  target[property] = ref.valueOf();
@@ -234,8 +238,7 @@ var HandlerRegistry = class extends HTMLElement {
234
238
  }
235
239
  #handleText(e) {
236
240
  if (e instanceof CustomEvent) {
237
- const target = e.target;
238
- const { identifier } = e.detail;
241
+ const { identifier, target } = e.detail;
239
242
  if (identifier in this && target instanceof HTMLElement) {
240
243
  const ref = this.lookup(identifier);
241
244
  this.effect(() => {
@@ -247,8 +250,7 @@ var HandlerRegistry = class extends HTMLElement {
247
250
  }
248
251
  #handleHTML(e) {
249
252
  if (e instanceof CustomEvent) {
250
- const { identifier } = e.detail;
251
- const target = e.target;
253
+ const { identifier, target } = e.detail;
252
254
  if (identifier in this && target instanceof HTMLElement) {
253
255
  const ref = this.lookup(identifier);
254
256
  this.effect(() => {
@@ -260,8 +262,7 @@ var HandlerRegistry = class extends HTMLElement {
260
262
  }
261
263
  #handleBind(e) {
262
264
  if (e instanceof CustomEvent) {
263
- const { identifier, property } = e.detail;
264
- const target = e.target;
265
+ const { identifier, property, target } = e.detail;
265
266
  if (identifier in this && target instanceof HTMLElement && property in target) {
266
267
  const state = this.lookup(identifier);
267
268
  if (isState(state)) {
@@ -278,8 +279,10 @@ var HandlerRegistry = class extends HTMLElement {
278
279
  }
279
280
  }
280
281
  connectedCallback() {
282
+ console.log(`${this.tagName} connected`);
281
283
  const { signal: signal2 } = this.abortController;
282
284
  this.addEventListener("@attr-request", this.#handleAttr, { signal: signal2 });
285
+ this.addEventListener("@bool-request", this.#handleBool, { signal: signal2 });
283
286
  this.addEventListener("@class-request", this.#handleClass, { signal: signal2 });
284
287
  this.addEventListener("@on-request", this.#handleOn, { signal: signal2 });
285
288
  this.addEventListener("@use-request", this.#handleUse, { signal: signal2 });
package/client/utils.d.ts CHANGED
@@ -1,14 +1,6 @@
1
1
  declare const spaces_sep_by_comma: RegExp;
2
- /**
3
- * Idempotent string conversion to kebab-case
4
- */
5
- declare const toKebabCase: (str: string) => string;
6
- /**
7
- * Idempotent string conversion to PascalCase
8
- */
9
- declare const toPascalCase: (str: string) => string;
10
2
  type LooseAutocomplete<T extends string> = T | Omit<string, T>;
11
- type Types = LooseAutocomplete<"null" | "undefined" | "boolean" | "number" | "bigint" | "string" | "symbol" | "function" | "class" | "array" | "date" | "error" | "regexp" | "object">;
3
+ type Types = LooseAutocomplete<"null" | "undefined" | "boolean" | "number" | "bigint" | "string" | "symbol" | "function" | "AsyncFunction" | "class" | "array" | "date" | "error" | "regexp" | "object">;
12
4
  /**
13
5
  * A more reliable `typeof` function
14
6
  */
@@ -16,15 +8,13 @@ declare function type(value: unknown): Types;
16
8
  declare const booleanAttributes: string[];
17
9
  declare const bindingConfig: {
18
10
  checked: {
19
- element: string[];
20
11
  type: string[];
21
12
  event: string;
22
13
  };
23
14
  value: {
24
- element: string[];
25
15
  type: string[];
26
16
  event: string;
27
17
  };
28
18
  };
29
19
 
30
- export { bindingConfig, booleanAttributes, spaces_sep_by_comma, toKebabCase, toPascalCase, type };
20
+ export { bindingConfig, booleanAttributes, spaces_sep_by_comma, type };
package/client/utils.js CHANGED
@@ -1,36 +1,5 @@
1
1
  // src/utils.ts
2
- var is_upper = /[A-Z]/;
3
2
  var spaces_sep_by_comma = /\s*,\s*/;
4
- var toKebabCase = (str) => {
5
- let kebab = "";
6
- for (let index = 0; index < str.length; index++) {
7
- const char = str[index];
8
- if (index !== 0 && is_upper.test(char)) {
9
- kebab += `-${char.toLowerCase()}`;
10
- } else {
11
- kebab += char.toLowerCase();
12
- }
13
- }
14
- return kebab;
15
- };
16
- var toPascalCase = (str) => {
17
- let pascal = "";
18
- let toUpper = true;
19
- for (let index = 0; index < str.length; index++) {
20
- const char = str[index];
21
- if (char === "-") {
22
- toUpper = true;
23
- continue;
24
- }
25
- if (toUpper) {
26
- pascal += char.toUpperCase();
27
- toUpper = false;
28
- } else {
29
- pascal += char.toLowerCase();
30
- }
31
- }
32
- return pascal;
33
- };
34
3
  function type(value) {
35
4
  if (value === null) {
36
5
  return "null";
@@ -42,11 +11,9 @@ function type(value) {
42
11
  if (!["object", "function"].includes(baseType)) {
43
12
  return baseType;
44
13
  }
45
- if (typeof value === "object" && Symbol.toStringTag in value) {
46
- const tag = value[Symbol.toStringTag];
47
- if (typeof tag === "string") {
48
- return tag;
49
- }
14
+ const tag = value[Symbol.toStringTag];
15
+ if (typeof tag === "string") {
16
+ return tag;
50
17
  }
51
18
  if (baseType === "function" && Function.prototype.toString.call(value).startsWith("class")) {
52
19
  return "class";
@@ -109,15 +76,13 @@ var booleanAttributes = [
109
76
  ];
110
77
  var bindingConfig = {
111
78
  "checked": {
112
- element: ["input"],
113
79
  type: ["boolean"],
114
80
  event: "change"
115
81
  },
116
82
  "value": {
117
- element: ["input", "select", "textarea"],
118
83
  type: ["string", "number"],
119
84
  event: "input"
120
85
  }
121
86
  };
122
87
 
123
- export { bindingConfig, booleanAttributes, spaces_sep_by_comma, toKebabCase, toPascalCase, type };
88
+ export { bindingConfig, booleanAttributes, spaces_sep_by_comma, type };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@radishland/runtime",
3
- "version": "0.1.2",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "description": "The Radish runtime",
6
6
  "author": "Frédéric Crozatier",
@@ -12,10 +12,6 @@
12
12
  "url": "https://github.com/radishland/radish/issues"
13
13
  },
14
14
  "license": "MIT",
15
- "scripts": {
16
- "build": "tsup",
17
- "prepublishOnly": "pnpm build"
18
- },
19
15
  "exports": {
20
16
  ".": {
21
17
  "import": "./client/index.js",
@@ -46,5 +42,8 @@
46
42
  "devDependencies": {
47
43
  "tsup": "^8.4.0",
48
44
  "typescript": "^5.7.3"
45
+ },
46
+ "scripts": {
47
+ "build": "tsup"
49
48
  }
50
- }
49
+ }