@hypen-space/web 0.4.36 → 0.4.38

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.
Files changed (149) hide show
  1. package/README.md +7 -6
  2. package/dist/canvas/accessibility.js +174 -174
  3. package/dist/canvas/accessibility.js.map +1 -10
  4. package/dist/canvas/events.js +264 -360
  5. package/dist/canvas/events.js.map +1 -11
  6. package/dist/canvas/index.js +11 -47
  7. package/dist/canvas/index.js.map +1 -9
  8. package/dist/canvas/input.js +178 -289
  9. package/dist/canvas/input.js.map +1 -11
  10. package/dist/canvas/layout.js +349 -547
  11. package/dist/canvas/layout.js.map +1 -12
  12. package/dist/canvas/paint.js +1122 -1146
  13. package/dist/canvas/paint.js.map +1 -12
  14. package/dist/canvas/renderer.js +352 -2259
  15. package/dist/canvas/renderer.js.map +1 -17
  16. package/dist/canvas/text.js +129 -264
  17. package/dist/canvas/text.js.map +1 -11
  18. package/dist/canvas/types.js +7 -2
  19. package/dist/canvas/types.js.map +1 -9
  20. package/dist/canvas/utils.js +158 -161
  21. package/dist/canvas/utils.js.map +1 -10
  22. package/dist/dom/applicators/advanced-layout.js +105 -138
  23. package/dist/dom/applicators/advanced-layout.js.map +1 -10
  24. package/dist/dom/applicators/background.js +36 -76
  25. package/dist/dom/applicators/background.js.map +1 -10
  26. package/dist/dom/applicators/border.js +82 -106
  27. package/dist/dom/applicators/border.js.map +1 -10
  28. package/dist/dom/applicators/color.js +17 -58
  29. package/dist/dom/applicators/color.js.map +1 -10
  30. package/dist/dom/applicators/display.js +39 -79
  31. package/dist/dom/applicators/display.js.map +1 -10
  32. package/dist/dom/applicators/effects.js +121 -147
  33. package/dist/dom/applicators/effects.js.map +1 -10
  34. package/dist/dom/applicators/events.js +413 -450
  35. package/dist/dom/applicators/events.js.map +1 -11
  36. package/dist/dom/applicators/font.js +119 -140
  37. package/dist/dom/applicators/font.js.map +1 -10
  38. package/dist/dom/applicators/index.js +386 -1770
  39. package/dist/dom/applicators/index.js.map +1 -26
  40. package/dist/dom/applicators/layout.js +143 -149
  41. package/dist/dom/applicators/layout.js.map +1 -10
  42. package/dist/dom/applicators/margin.js +64 -105
  43. package/dist/dom/applicators/margin.js.map +1 -10
  44. package/dist/dom/applicators/padding.js +64 -105
  45. package/dist/dom/applicators/padding.js.map +1 -10
  46. package/dist/dom/applicators/size.js +169 -172
  47. package/dist/dom/applicators/size.js.map +1 -10
  48. package/dist/dom/applicators/transform.js +73 -114
  49. package/dist/dom/applicators/transform.js.map +1 -10
  50. package/dist/dom/applicators/transition.js +47 -88
  51. package/dist/dom/applicators/transition.js.map +1 -10
  52. package/dist/dom/applicators/types.js +7 -2
  53. package/dist/dom/applicators/types.js.map +1 -9
  54. package/dist/dom/applicators/typography.js +68 -109
  55. package/dist/dom/applicators/typography.js.map +1 -10
  56. package/dist/dom/canvas/index.js +49 -83
  57. package/dist/dom/canvas/index.js.map +1 -10
  58. package/dist/dom/components/app.js +16 -63
  59. package/dist/dom/components/app.js.map +1 -10
  60. package/dist/dom/components/audio.js +31 -76
  61. package/dist/dom/components/audio.js.map +1 -10
  62. package/dist/dom/components/avatar.js +41 -87
  63. package/dist/dom/components/avatar.js.map +1 -10
  64. package/dist/dom/components/badge.js +35 -83
  65. package/dist/dom/components/badge.js.map +1 -10
  66. package/dist/dom/components/button.js +26 -69
  67. package/dist/dom/components/button.js.map +1 -10
  68. package/dist/dom/components/card.js +13 -63
  69. package/dist/dom/components/card.js.map +1 -10
  70. package/dist/dom/components/center.js +21 -65
  71. package/dist/dom/components/center.js.map +1 -10
  72. package/dist/dom/components/checkbox.js +37 -82
  73. package/dist/dom/components/checkbox.js.map +1 -10
  74. package/dist/dom/components/column.js +16 -62
  75. package/dist/dom/components/column.js.map +1 -10
  76. package/dist/dom/components/container.js +11 -59
  77. package/dist/dom/components/container.js.map +1 -10
  78. package/dist/dom/components/divider.js +27 -73
  79. package/dist/dom/components/divider.js.map +1 -10
  80. package/dist/dom/components/grid.js +41 -80
  81. package/dist/dom/components/grid.js.map +1 -10
  82. package/dist/dom/components/heading.js +28 -74
  83. package/dist/dom/components/heading.js.map +1 -10
  84. package/dist/dom/components/hypenapp.js +291 -288
  85. package/dist/dom/components/hypenapp.js.map +1 -10
  86. package/dist/dom/components/image.js +18 -67
  87. package/dist/dom/components/image.js.map +1 -10
  88. package/dist/dom/components/index.js +109 -1427
  89. package/dist/dom/components/index.js.map +1 -42
  90. package/dist/dom/components/input.js +19 -69
  91. package/dist/dom/components/input.js.map +1 -10
  92. package/dist/dom/components/link.js +23 -70
  93. package/dist/dom/components/link.js.map +1 -10
  94. package/dist/dom/components/list.js +26 -72
  95. package/dist/dom/components/list.js.map +1 -10
  96. package/dist/dom/components/paragraph.js +14 -63
  97. package/dist/dom/components/paragraph.js.map +1 -10
  98. package/dist/dom/components/progressbar.js +38 -85
  99. package/dist/dom/components/progressbar.js.map +1 -10
  100. package/dist/dom/components/route.js +27 -73
  101. package/dist/dom/components/route.js.map +1 -10
  102. package/dist/dom/components/router.js +15 -62
  103. package/dist/dom/components/router.js.map +1 -10
  104. package/dist/dom/components/row.js +25 -70
  105. package/dist/dom/components/row.js.map +1 -10
  106. package/dist/dom/components/select.js +42 -85
  107. package/dist/dom/components/select.js.map +1 -10
  108. package/dist/dom/components/slider.js +31 -76
  109. package/dist/dom/components/slider.js.map +1 -10
  110. package/dist/dom/components/spacer.js +10 -60
  111. package/dist/dom/components/spacer.js.map +1 -10
  112. package/dist/dom/components/spinner.js +41 -88
  113. package/dist/dom/components/spinner.js.map +1 -10
  114. package/dist/dom/components/stack.js +19 -67
  115. package/dist/dom/components/stack.js.map +1 -10
  116. package/dist/dom/components/switch.js +50 -93
  117. package/dist/dom/components/switch.js.map +1 -10
  118. package/dist/dom/components/text.js +24 -69
  119. package/dist/dom/components/text.js.map +1 -10
  120. package/dist/dom/components/textarea.js +35 -79
  121. package/dist/dom/components/textarea.js.map +1 -10
  122. package/dist/dom/components/video.js +35 -79
  123. package/dist/dom/components/video.js.map +1 -10
  124. package/dist/dom/debug.js +159 -160
  125. package/dist/dom/debug.js.map +1 -10
  126. package/dist/dom/element-data.js +181 -143
  127. package/dist/dom/element-data.js.map +1 -10
  128. package/dist/dom/events.js +133 -142
  129. package/dist/dom/events.js.map +1 -10
  130. package/dist/dom/index.js +11 -48
  131. package/dist/dom/index.js.map +1 -9
  132. package/dist/dom/renderer.js +311 -3596
  133. package/dist/dom/renderer.js.map +1 -62
  134. package/dist/index.d.ts +2 -2
  135. package/dist/index.js +36 -53
  136. package/dist/index.js.map +1 -9
  137. package/package.json +2 -16
  138. package/src/canvas/QUICKSTART.md +4 -3
  139. package/src/canvas/README.md +3 -2
  140. package/src/dom/README.md +4 -3
  141. package/src/index.ts +2 -2
  142. package/dist/client.d.ts +0 -19
  143. package/dist/client.js +0 -45
  144. package/dist/client.js.map +0 -9
  145. package/dist/hypen.d.ts +0 -140
  146. package/dist/hypen.js +0 -3909
  147. package/dist/hypen.js.map +0 -63
  148. package/src/client.ts +0 -20
  149. package/src/hypen.ts +0 -556
@@ -1,3619 +1,334 @@
1
- var __defProp = Object.defineProperty;
2
- var __getOwnPropNames = Object.getOwnPropertyNames;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __hasOwnProp = Object.prototype.hasOwnProperty;
5
- function __accessProp(key) {
6
- return this[key];
7
- }
8
- var __toCommonJS = (from) => {
9
- var entry = (__moduleCache ??= new WeakMap).get(from), desc;
10
- if (entry)
11
- return entry;
12
- entry = __defProp({}, "__esModule", { value: true });
13
- if (from && typeof from === "object" || typeof from === "function") {
14
- for (var key of __getOwnPropNames(from))
15
- if (!__hasOwnProp.call(entry, key))
16
- __defProp(entry, key, {
17
- get: __accessProp.bind(from, key),
18
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
19
- });
20
- }
21
- __moduleCache.set(from, entry);
22
- return entry;
23
- };
24
- var __moduleCache;
25
- var __returnValue = (v) => v;
26
- function __exportSetter(name, newValue) {
27
- this[name] = __returnValue.bind(null, newValue);
28
- }
29
- var __export = (target, all) => {
30
- for (var name in all)
31
- __defProp(target, name, {
32
- get: all[name],
33
- enumerable: true,
34
- configurable: true,
35
- set: __exportSetter.bind(all, name)
36
- });
37
- };
38
- var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
39
-
40
- // src/dom/components/column.ts
41
- var exports_column = {};
42
- __export(exports_column, {
43
- columnHandler: () => columnHandler
44
- });
45
- var columnHandler;
46
- var init_column = __esm(() => {
47
- columnHandler = {
48
- create() {
49
- const el = document.createElement("div");
50
- el.style.display = "flex";
51
- el.style.flexDirection = "column";
52
- el.style.alignItems = "flex-start";
53
- el.dataset.hypenType = "column";
54
- return el;
55
- }
56
- };
57
- });
58
-
59
- // src/dom/components/row.ts
60
- var exports_row = {};
61
- __export(exports_row, {
62
- rowHandler: () => rowHandler
63
- });
64
- function ensureRowStyles() {
65
- if (rowStylesInjected)
66
- return;
67
- rowStylesInjected = true;
68
- const style = document.createElement("style");
69
- style.id = "hypen-row-styles";
70
- style.textContent = `
71
- /* Row expands to fill width when it has children with flex/weight */
72
- /* This matches iOS/Android behavior where weighted children cause parent to expand */
73
- [data-hypen-type="row"]:has(> [data-hypen-flex]) {
74
- width: 100%;
75
- }
76
- `;
77
- document.head.appendChild(style);
78
- }
79
- var rowStylesInjected = false, rowHandler;
80
- var init_row = __esm(() => {
81
- rowHandler = {
82
- create() {
83
- ensureRowStyles();
84
- const el = document.createElement("div");
85
- el.style.display = "flex";
86
- el.style.flexDirection = "row";
87
- el.style.alignItems = "flex-start";
88
- el.dataset.hypenType = "row";
89
- return el;
90
- }
91
- };
92
- });
93
-
94
- // src/dom/components/text.ts
95
- var exports_text = {};
96
- __export(exports_text, {
97
- textHandler: () => textHandler
98
- });
99
- var textHandler;
100
- var init_text = __esm(() => {
101
- textHandler = {
102
- create() {
103
- const el = document.createElement("span");
104
- el.style.display = "inline-block";
105
- el.style.lineHeight = "1";
106
- el.style.verticalAlign = "top";
107
- el.style.margin = "0";
108
- el.style.padding = "0";
109
- el.dataset.hypenType = "text";
110
- return el;
111
- },
112
- applyProps(el, props) {
113
- const text = props["0"] || props.text;
114
- if (text !== undefined) {
115
- el.dataset.textTemplate = String(text);
116
- el.textContent = String(text);
117
- }
118
- }
119
- };
120
- });
121
-
122
- // src/dom/components/image.ts
123
- var exports_image = {};
124
- __export(exports_image, {
125
- imageHandler: () => imageHandler
126
- });
127
- var imageHandler;
128
- var init_image = __esm(() => {
129
- imageHandler = {
130
- create() {
131
- const el = document.createElement("img");
132
- el.dataset.hypenType = "image";
133
- return el;
134
- },
135
- applyProps(el, props) {
136
- const img = el;
137
- const src = props["0"] || props.url || props.src;
138
- if (src !== undefined) {
139
- img.src = String(src);
140
- }
141
- if (props.alt !== undefined) {
142
- img.alt = String(props.alt);
143
- }
144
- }
145
- };
146
- });
147
-
148
- // src/dom/components/button.ts
149
- var exports_button = {};
150
- __export(exports_button, {
151
- buttonHandler: () => buttonHandler
152
- });
153
- var buttonHandler;
154
- var init_button = __esm(() => {
155
- buttonHandler = {
156
- create() {
157
- const el = document.createElement("button");
158
- el.style.border = "none";
159
- el.style.background = "none";
160
- el.style.padding = "0";
161
- el.style.margin = "0";
162
- el.style.font = "inherit";
163
- el.style.color = "inherit";
164
- el.style.cursor = "pointer";
165
- el.style.display = "flex";
166
- el.style.flexDirection = "column";
167
- el.style.alignItems = "flex-start";
168
- el.dataset.hypenType = "button";
169
- return el;
170
- }
171
- };
172
- });
173
-
174
- // src/dom/components/container.ts
175
- var exports_container = {};
176
- __export(exports_container, {
177
- containerHandler: () => containerHandler
178
- });
179
- var containerHandler;
180
- var init_container = __esm(() => {
181
- containerHandler = {
182
- create() {
183
- const el = document.createElement("div");
184
- el.dataset.hypenType = "container";
185
- return el;
186
- }
187
- };
188
- });
189
-
190
- // src/dom/components/center.ts
191
- var exports_center = {};
192
- __export(exports_center, {
193
- centerHandler: () => centerHandler
194
- });
195
- var centerHandler;
196
- var init_center = __esm(() => {
197
- centerHandler = {
198
- create() {
199
- const el = document.createElement("div");
200
- el.style.display = "flex";
201
- el.style.alignItems = "center";
202
- el.style.justifyContent = "center";
203
- el.style.width = "100%";
204
- el.style.height = "100%";
205
- el.style.alignSelf = "stretch";
206
- el.dataset.hypenType = "center";
207
- return el;
208
- }
209
- };
210
- });
211
-
212
- // src/dom/components/list.ts
213
- var exports_list = {};
214
- __export(exports_list, {
215
- listHandler: () => listHandler
216
- });
217
- var listHandler;
218
- var init_list = __esm(() => {
219
- listHandler = {
220
- create() {
221
- const el = document.createElement("div");
222
- el.style.display = "flex";
223
- el.style.flexDirection = "column";
224
- el.style.alignItems = "flex-start";
225
- el.style.overflow = "auto";
226
- el.dataset.hypenType = "list";
227
- return el;
228
- },
229
- applyProps(el, props) {
230
- const direction = props.direction || props["1"] || "vertical";
231
- if (direction === "vertical") {
232
- el.style.flexDirection = "column";
233
- } else {
234
- el.style.flexDirection = "row";
235
- }
236
- if (props.gap !== undefined) {
237
- el.style.gap = typeof props.gap === "number" ? `${props.gap}px` : String(props.gap);
238
- }
239
- }
240
- };
241
- });
242
-
243
- // src/dom/components/input.ts
244
- var exports_input = {};
245
- __export(exports_input, {
246
- inputHandler: () => inputHandler
247
- });
248
- var inputHandler;
249
- var init_input = __esm(() => {
250
- inputHandler = {
251
- create() {
252
- const el = document.createElement("input");
253
- el.dataset.hypenType = "input";
254
- return el;
255
- },
256
- applyProps(el, props) {
257
- const input = el;
258
- if (props.type !== undefined) {
259
- input.type = String(props.type);
260
- }
261
- if (props.placeholder !== undefined) {
262
- input.placeholder = String(props.placeholder);
263
- }
264
- if (props.value !== undefined) {
265
- input.value = String(props.value);
266
- }
267
- }
268
- };
269
- });
270
-
271
- // src/dom/components/link.ts
272
- var exports_link = {};
273
- __export(exports_link, {
274
- linkHandler: () => linkHandler
275
- });
276
- var linkHandler;
277
- var init_link = __esm(() => {
278
- linkHandler = {
279
- create() {
280
- const el = document.createElement("a");
281
- el.dataset.hypenType = "link";
282
- return el;
283
- },
284
- applyProps(el, props) {
285
- const anchor = el;
286
- const href = props["0"] || props.href;
287
- if (href !== undefined) {
288
- anchor.href = String(href);
289
- }
290
- if (props.target !== undefined) {
291
- anchor.target = String(props.target);
292
- }
293
- if (props.rel !== undefined) {
294
- anchor.rel = String(props.rel);
295
- }
296
- }
297
- };
298
- });
299
-
300
- // src/dom/components/textarea.ts
301
- var exports_textarea = {};
302
- __export(exports_textarea, {
303
- textareaHandler: () => textareaHandler
304
- });
305
- var textareaHandler;
306
- var init_textarea = __esm(() => {
307
- textareaHandler = {
308
- create() {
309
- const el = document.createElement("textarea");
310
- el.dataset.hypenType = "textarea";
311
- return el;
312
- },
313
- applyProps(el, props) {
314
- const textarea = el;
315
- const value = props.value ?? props["0"];
316
- if (value !== undefined) {
317
- textarea.value = String(value);
318
- }
319
- if (props.placeholder !== undefined) {
320
- textarea.placeholder = String(props.placeholder);
321
- }
322
- if (props.rows !== undefined) {
323
- textarea.rows = Number(props.rows);
324
- }
325
- if (props.cols !== undefined) {
326
- textarea.cols = Number(props.cols);
327
- }
328
- if (props.disabled !== undefined) {
329
- textarea.disabled = Boolean(props.disabled);
330
- }
331
- if (props.readonly !== undefined) {
332
- textarea.readOnly = Boolean(props.readonly);
333
- }
334
- }
335
- };
336
- });
337
-
338
- // src/dom/components/checkbox.ts
339
- var exports_checkbox = {};
340
- __export(exports_checkbox, {
341
- checkboxHandler: () => checkboxHandler
342
- });
343
- var checkboxHandler;
344
- var init_checkbox = __esm(() => {
345
- checkboxHandler = {
346
- create() {
347
- const wrapper = document.createElement("label");
348
- wrapper.dataset.hypenType = "checkbox";
349
- wrapper.style.display = "inline-flex";
350
- wrapper.style.alignItems = "center";
351
- wrapper.style.gap = "8px";
352
- wrapper.style.cursor = "pointer";
353
- const input = document.createElement("input");
354
- input.type = "checkbox";
355
- input.dataset.hypenCheckbox = "true";
356
- wrapper.appendChild(input);
357
- return wrapper;
358
- },
359
- applyProps(el, props) {
360
- const input = el.querySelector('input[type="checkbox"]');
361
- if (!input)
362
- return;
363
- if (props.checked !== undefined) {
364
- input.checked = Boolean(props.checked);
365
- }
366
- if (props.disabled !== undefined) {
367
- input.disabled = Boolean(props.disabled);
368
- }
369
- const label = props["0"] || props.label;
370
- if (label !== undefined) {
371
- const textNodes = Array.from(el.childNodes).filter((node) => node.nodeType === Node.TEXT_NODE);
372
- textNodes.forEach((node) => node.remove());
373
- el.appendChild(document.createTextNode(String(label)));
374
- }
375
- }
376
- };
377
- });
378
-
379
- // src/dom/components/select.ts
380
- var exports_select = {};
381
- __export(exports_select, {
382
- selectHandler: () => selectHandler
383
- });
384
- var selectHandler;
385
- var init_select = __esm(() => {
386
- selectHandler = {
387
- create() {
388
- const el = document.createElement("select");
389
- el.dataset.hypenType = "select";
390
- return el;
391
- },
392
- applyProps(el, props) {
393
- const select = el;
394
- if (props.value !== undefined) {
395
- select.value = String(props.value);
396
- }
397
- if (props.disabled !== undefined) {
398
- select.disabled = Boolean(props.disabled);
399
- }
400
- if (props.multiple !== undefined) {
401
- select.multiple = Boolean(props.multiple);
402
- }
403
- if (props.options && Array.isArray(props.options)) {
404
- select.innerHTML = "";
405
- props.options.forEach((opt) => {
406
- const option = document.createElement("option");
407
- if (typeof opt === "string") {
408
- option.value = opt;
409
- option.textContent = opt;
410
- } else if (typeof opt === "object") {
411
- option.value = String(opt.value ?? opt.label ?? "");
412
- option.textContent = String(opt.label ?? opt.value ?? "");
413
- if (opt.disabled)
414
- option.disabled = true;
415
- }
416
- select.appendChild(option);
417
- });
418
- }
419
- }
420
- };
421
- });
422
-
423
- // src/dom/components/spacer.ts
424
- var exports_spacer = {};
425
- __export(exports_spacer, {
426
- spacerHandler: () => spacerHandler
427
- });
428
- var spacerHandler;
429
- var init_spacer = __esm(() => {
430
- spacerHandler = {
431
- create() {
432
- const el = document.createElement("div");
433
- el.style.flex = "1";
434
- el.dataset.hypenType = "spacer";
435
- return el;
436
- }
437
- };
438
- });
439
-
440
- // src/dom/components/stack.ts
441
- var exports_stack = {};
442
- __export(exports_stack, {
443
- stackHandler: () => stackHandler
444
- });
445
- function ensureStackStyles() {
446
- if (stackStylesInjected)
447
- return;
448
- stackStylesInjected = true;
449
- const style = document.createElement("style");
450
- style.id = "hypen-stack-styles";
451
- style.textContent = `
452
- [data-hypen-type="stack"] {
453
- position: relative;
454
- display: grid;
455
- grid-template-areas: "stack";
456
- /* Default alignment: top-left (matching iOS/Android ZStack default) */
457
- justify-items: start;
458
- align-items: start;
459
- /* Ensure Stack participates properly in flex layouts (Row/Column) */
460
- min-width: 0;
461
- min-height: 0;
462
- }
463
- [data-hypen-type="stack"] > * {
464
- grid-area: stack;
465
- /* Don't set justify-self/align-self here - let parent's justify-items/align-items control */
466
- }
467
- `;
468
- document.head.appendChild(style);
469
- }
470
- var stackStylesInjected = false, stackHandler;
471
- var init_stack = __esm(() => {
472
- stackHandler = {
473
- create() {
474
- ensureStackStyles();
475
- const el = document.createElement("div");
476
- el.dataset.hypenType = "stack";
477
- return el;
478
- }
479
- };
480
- });
481
-
482
- // src/dom/components/divider.ts
483
- var exports_divider = {};
484
- __export(exports_divider, {
485
- dividerHandler: () => dividerHandler
486
- });
487
- var dividerHandler;
488
- var init_divider = __esm(() => {
489
- dividerHandler = {
490
- create() {
491
- const el = document.createElement("hr");
492
- el.dataset.hypenType = "divider";
493
- el.style.border = "none";
494
- el.style.borderTop = "1px solid #e0e0e0";
495
- el.style.margin = "0";
496
- return el;
497
- },
498
- applyProps(el, props) {
499
- if (props.thickness !== undefined) {
500
- const thickness = typeof props.thickness === "number" ? `${props.thickness}px` : String(props.thickness);
501
- el.style.borderTopWidth = thickness;
502
- }
503
- if (props.orientation === "vertical") {
504
- el.style.borderTop = "none";
505
- el.style.borderLeft = "1px solid #e0e0e0";
506
- el.style.height = "100%";
507
- el.style.width = "0";
508
- el.style.display = "inline-block";
509
- }
510
- }
511
- };
512
- });
513
-
514
- // src/dom/components/grid.ts
515
- var exports_grid = {};
516
- __export(exports_grid, {
517
- gridHandler: () => gridHandler
518
- });
519
- function ensureGridStyles() {
520
- if (gridStylesInjected)
521
- return;
522
- gridStylesInjected = true;
523
- const style = document.createElement("style");
524
- style.id = "hypen-grid-styles";
525
- style.textContent = `
526
- /* Grid children stretch to fill cells by default (matches Android behavior) */
527
- [data-hypen-type="grid"] > * {
528
- justify-self: stretch;
529
- align-self: stretch;
530
- }
531
- `;
532
- document.head.appendChild(style);
533
- }
534
- var gridStylesInjected = false, gridHandler;
535
- var init_grid = __esm(() => {
536
- gridHandler = {
537
- create() {
538
- ensureGridStyles();
539
- const el = document.createElement("div");
540
- el.style.display = "grid";
541
- el.dataset.hypenType = "grid";
542
- return el;
543
- },
544
- applyProps(el, props) {
545
- if (props.columns !== undefined) {
546
- const columns = typeof props.columns === "number" ? `repeat(${props.columns}, 1fr)` : String(props.columns);
547
- el.style.gridTemplateColumns = columns;
548
- }
549
- if (props.rows !== undefined) {
550
- const rows = typeof props.rows === "number" ? `repeat(${props.rows}, 1fr)` : String(props.rows);
551
- el.style.gridTemplateRows = rows;
552
- }
553
- if (props.gap !== undefined) {
554
- const gap = typeof props.gap === "number" ? `${props.gap}px` : String(props.gap);
555
- el.style.gap = gap;
556
- }
557
- }
558
- };
559
- });
560
-
561
- // src/dom/components/card.ts
562
- var exports_card = {};
563
- __export(exports_card, {
564
- cardHandler: () => cardHandler
565
- });
566
- var cardHandler;
567
- var init_card = __esm(() => {
568
- cardHandler = {
569
- create() {
570
- const el = document.createElement("div");
571
- el.dataset.hypenType = "card";
572
- el.style.backgroundColor = "#ffffff";
573
- el.style.borderRadius = "8px";
574
- el.style.boxShadow = "0 2px 4px rgba(0, 0, 0, 0.1)";
575
- el.style.padding = "16px";
576
- return el;
577
- }
578
- };
579
- });
580
-
581
- // src/dom/components/heading.ts
582
- var exports_heading = {};
583
- __export(exports_heading, {
584
- headingHandler: () => headingHandler
585
- });
586
- var headingHandler;
587
- var init_heading = __esm(() => {
588
- headingHandler = {
589
- create() {
590
- const el = document.createElement("h2");
591
- el.dataset.hypenType = "heading";
592
- return el;
593
- },
594
- applyProps(el, props) {
595
- if (props.level !== undefined) {
596
- const level = Math.max(1, Math.min(6, Number(props.level)));
597
- const newEl = document.createElement(`h${level}`);
598
- newEl.dataset.hypenType = "heading";
599
- newEl.innerHTML = el.innerHTML;
600
- Array.from(el.attributes).forEach((attr) => {
601
- newEl.setAttribute(attr.name, attr.value);
602
- });
603
- if (el.parentNode) {
604
- el.parentNode.replaceChild(newEl, el);
605
- }
606
- }
607
- const text = props["0"] || props.text;
608
- if (text !== undefined) {
609
- el.textContent = String(text);
610
- }
611
- }
612
- };
613
- });
614
-
615
- // src/dom/components/switch.ts
616
- var exports_switch = {};
617
- __export(exports_switch, {
618
- switchHandler: () => switchHandler
619
- });
620
- var switchHandler;
621
- var init_switch = __esm(() => {
622
- switchHandler = {
623
- create() {
624
- const wrapper = document.createElement("label");
625
- wrapper.dataset.hypenType = "switch";
626
- wrapper.style.display = "inline-flex";
627
- wrapper.style.alignItems = "center";
628
- wrapper.style.gap = "8px";
629
- wrapper.style.cursor = "pointer";
630
- const input = document.createElement("input");
631
- input.type = "checkbox";
632
- input.dataset.hypenSwitch = "true";
633
- input.style.appearance = "none";
634
- input.style.width = "44px";
635
- input.style.height = "24px";
636
- input.style.backgroundColor = "#ccc";
637
- input.style.borderRadius = "12px";
638
- input.style.position = "relative";
639
- input.style.cursor = "pointer";
640
- input.style.transition = "background-color 0.2s";
641
- const style = document.createElement("style");
642
- style.textContent = `
643
- input[data-hypen-switch="true"]::before {
644
- content: "";
645
- position: absolute;
646
- width: 20px;
647
- height: 20px;
648
- background-color: white;
649
- border-radius: 50%;
650
- top: 2px;
651
- left: 2px;
652
- transition: transform 0.2s;
653
- }
654
- input[data-hypen-switch="true"]:checked {
655
- background-color: #4CAF50;
656
- }
657
- input[data-hypen-switch="true"]:checked::before {
658
- transform: translateX(20px);
659
- }
660
- `;
661
- wrapper.appendChild(style);
662
- wrapper.appendChild(input);
663
- return wrapper;
664
- },
665
- applyProps(el, props) {
666
- const input = el.querySelector('input[type="checkbox"]');
667
- if (!input)
668
- return;
669
- if (props.on !== undefined) {
670
- input.checked = Boolean(props.on);
671
- }
672
- if (props.disabled !== undefined) {
673
- input.disabled = Boolean(props.disabled);
674
- }
675
- const label = props["0"] || props.label;
676
- if (label !== undefined) {
677
- const textNodes = Array.from(el.childNodes).filter((node) => node.nodeType === Node.TEXT_NODE);
678
- textNodes.forEach((node) => node.remove());
679
- el.appendChild(document.createTextNode(String(label)));
680
- }
681
- }
682
- };
683
- });
684
-
685
- // src/dom/components/slider.ts
686
- var exports_slider = {};
687
- __export(exports_slider, {
688
- sliderHandler: () => sliderHandler
689
- });
690
- var sliderHandler;
691
- var init_slider = __esm(() => {
692
- sliderHandler = {
693
- create() {
694
- const el = document.createElement("input");
695
- el.type = "range";
696
- el.dataset.hypenType = "slider";
697
- return el;
698
- },
699
- applyProps(el, props) {
700
- const input = el;
701
- if (props.value !== undefined) {
702
- input.value = String(props.value);
703
- }
704
- if (props.min !== undefined) {
705
- input.min = String(props.min);
706
- }
707
- if (props.max !== undefined) {
708
- input.max = String(props.max);
709
- }
710
- if (props.step !== undefined) {
711
- input.step = String(props.step);
712
- }
713
- if (props.disabled !== undefined) {
714
- input.disabled = Boolean(props.disabled);
715
- }
716
- }
717
- };
718
- });
719
-
720
- // src/dom/components/spinner.ts
721
- var exports_spinner = {};
722
- __export(exports_spinner, {
723
- spinnerHandler: () => spinnerHandler
724
- });
725
- var spinnerHandler;
726
- var init_spinner = __esm(() => {
727
- spinnerHandler = {
728
- create() {
729
- const wrapper = document.createElement("div");
730
- wrapper.dataset.hypenType = "spinner";
731
- wrapper.style.display = "inline-block";
732
- const spinner = document.createElement("div");
733
- spinner.style.width = "40px";
734
- spinner.style.height = "40px";
735
- spinner.style.border = "4px solid #f3f3f3";
736
- spinner.style.borderTop = "4px solid #3498db";
737
- spinner.style.borderRadius = "50%";
738
- spinner.style.animation = "spin 1s linear infinite";
739
- const style = document.createElement("style");
740
- style.textContent = `
741
- @keyframes spin {
742
- 0% { transform: rotate(0deg); }
743
- 100% { transform: rotate(360deg); }
744
- }
745
- `;
746
- wrapper.appendChild(style);
747
- wrapper.appendChild(spinner);
748
- return wrapper;
749
- },
750
- applyProps(el, props) {
751
- const spinner = el.querySelector("div:not(style)");
752
- if (!spinner)
753
- return;
754
- if (props.size !== undefined) {
755
- const size = String(props.size);
756
- const sizeMap = {
757
- small: "24px",
758
- medium: "40px",
759
- large: "60px"
760
- };
761
- const actualSize = sizeMap[size] || size;
762
- spinner.style.width = actualSize;
763
- spinner.style.height = actualSize;
764
- }
765
- if (props.color !== undefined) {
766
- spinner.style.borderTopColor = String(props.color);
767
- }
768
- }
769
- };
770
- });
771
-
772
- // src/dom/components/badge.ts
773
- var exports_badge = {};
774
- __export(exports_badge, {
775
- badgeHandler: () => badgeHandler
776
- });
777
- var badgeHandler;
778
- var init_badge = __esm(() => {
779
- badgeHandler = {
780
- create() {
781
- const el = document.createElement("span");
782
- el.dataset.hypenType = "badge";
783
- el.style.display = "inline-block";
784
- el.style.padding = "4px 8px";
785
- el.style.borderRadius = "4px";
786
- el.style.fontSize = "12px";
787
- el.style.fontWeight = "600";
788
- el.style.backgroundColor = "#e0e0e0";
789
- el.style.color = "#333";
790
- return el;
791
- },
792
- applyProps(el, props) {
793
- if (props.theme !== undefined) {
794
- const theme = String(props.theme);
795
- const themeColors = {
796
- success: { bg: "#4CAF50", color: "#fff" },
797
- error: { bg: "#f44336", color: "#fff" },
798
- warning: { bg: "#ff9800", color: "#fff" },
799
- info: { bg: "#2196F3", color: "#fff" },
800
- default: { bg: "#e0e0e0", color: "#333" }
801
- };
802
- const colors = themeColors[theme] || themeColors.default;
803
- el.style.backgroundColor = colors.bg;
804
- el.style.color = colors.color;
805
- }
806
- const text = props["0"] || props.text;
807
- if (text !== undefined) {
808
- el.textContent = String(text);
809
- }
810
- }
811
- };
812
- });
813
-
814
- // src/dom/components/avatar.ts
815
- var exports_avatar = {};
816
- __export(exports_avatar, {
817
- avatarHandler: () => avatarHandler
818
- });
819
- var avatarHandler;
820
- var init_avatar = __esm(() => {
821
- avatarHandler = {
822
- create() {
823
- const el = document.createElement("div");
824
- el.dataset.hypenType = "avatar";
825
- el.style.display = "inline-flex";
826
- el.style.alignItems = "center";
827
- el.style.justifyContent = "center";
828
- el.style.width = "40px";
829
- el.style.height = "40px";
830
- el.style.borderRadius = "50%";
831
- el.style.backgroundColor = "#9e9e9e";
832
- el.style.color = "#fff";
833
- el.style.fontSize = "16px";
834
- el.style.fontWeight = "600";
835
- el.style.overflow = "hidden";
836
- return el;
837
- },
838
- applyProps(el, props) {
839
- const src = props["src.0"] || props["0"] || props.src || props.source;
840
- if (src !== undefined) {
841
- const img = document.createElement("img");
842
- img.src = String(src);
843
- img.style.width = "100%";
844
- img.style.height = "100%";
845
- img.style.objectFit = "cover";
846
- el.innerHTML = "";
847
- el.appendChild(img);
848
- } else if (props.initials !== undefined) {
849
- el.textContent = String(props.initials).toUpperCase();
850
- }
851
- if (props.size !== undefined) {
852
- const size = typeof props.size === "number" ? `${props.size}px` : String(props.size);
853
- el.style.width = size;
854
- el.style.height = size;
855
- }
856
- }
857
- };
858
- });
859
-
860
- // src/dom/components/progressbar.ts
861
- var exports_progressbar = {};
862
- __export(exports_progressbar, {
863
- progressBarHandler: () => progressBarHandler
864
- });
865
- var progressBarHandler;
866
- var init_progressbar = __esm(() => {
867
- progressBarHandler = {
868
- create() {
869
- const wrapper = document.createElement("div");
870
- wrapper.dataset.hypenType = "progressbar";
871
- wrapper.style.width = "100%";
872
- wrapper.style.height = "8px";
873
- wrapper.style.backgroundColor = "#e0e0e0";
874
- wrapper.style.borderRadius = "4px";
875
- wrapper.style.overflow = "hidden";
876
- const bar = document.createElement("div");
877
- bar.dataset.hypenBar = "true";
878
- bar.style.height = "100%";
879
- bar.style.backgroundColor = "#2196F3";
880
- bar.style.transition = "width 0.3s ease";
881
- bar.style.width = "0%";
882
- wrapper.appendChild(bar);
883
- return wrapper;
884
- },
885
- applyProps(el, props) {
886
- const bar = el.querySelector('[data-hypen-bar="true"]');
887
- if (!bar)
888
- return;
889
- const value = Number(props.value || 0);
890
- const max = Number(props.max || 100);
891
- const percentage = Math.min(100, Math.max(0, value / max * 100));
892
- bar.style.width = `${percentage}%`;
893
- if (props.color !== undefined) {
894
- bar.style.backgroundColor = String(props.color);
895
- }
896
- if (props.height !== undefined) {
897
- const height = typeof props.height === "number" ? `${props.height}px` : String(props.height);
898
- el.style.height = height;
899
- }
900
- }
901
- };
902
- });
903
-
904
- // src/dom/components/video.ts
905
- var exports_video = {};
906
- __export(exports_video, {
907
- videoHandler: () => videoHandler
908
- });
909
- var videoHandler;
910
- var init_video = __esm(() => {
911
- videoHandler = {
912
- create() {
913
- const el = document.createElement("video");
914
- el.dataset.hypenType = "video";
915
- return el;
916
- },
917
- applyProps(el, props) {
918
- const video = el;
919
- const src = props["0"] || props.src;
920
- if (src !== undefined) {
921
- video.src = String(src);
922
- }
923
- if (props.controls !== undefined) {
924
- video.controls = Boolean(props.controls);
925
- }
926
- if (props.autoplay !== undefined) {
927
- video.autoplay = Boolean(props.autoplay);
928
- }
929
- if (props.loop !== undefined) {
930
- video.loop = Boolean(props.loop);
931
- }
932
- if (props.muted !== undefined) {
933
- video.muted = Boolean(props.muted);
934
- }
935
- if (props.poster !== undefined) {
936
- video.poster = String(props.poster);
937
- }
938
- }
939
- };
940
- });
941
-
942
- // src/dom/components/audio.ts
943
- var exports_audio = {};
944
- __export(exports_audio, {
945
- audioHandler: () => audioHandler
946
- });
947
- var audioHandler;
948
- var init_audio = __esm(() => {
949
- audioHandler = {
950
- create() {
951
- const el = document.createElement("audio");
952
- el.dataset.hypenType = "audio";
953
- return el;
954
- },
955
- applyProps(el, props) {
956
- const audio = el;
957
- const src = props["0"] || props.src;
958
- if (src !== undefined) {
959
- audio.src = String(src);
960
- }
961
- if (props.controls !== undefined) {
962
- audio.controls = Boolean(props.controls);
963
- }
964
- if (props.autoplay !== undefined) {
965
- audio.autoplay = Boolean(props.autoplay);
966
- }
967
- if (props.loop !== undefined) {
968
- audio.loop = Boolean(props.loop);
969
- }
970
- if (props.muted !== undefined) {
971
- audio.muted = Boolean(props.muted);
972
- }
973
- }
974
- };
975
- });
976
-
977
- // src/dom/components/paragraph.ts
978
- var exports_paragraph = {};
979
- __export(exports_paragraph, {
980
- paragraphHandler: () => paragraphHandler
981
- });
982
- var paragraphHandler;
983
- var init_paragraph = __esm(() => {
984
- paragraphHandler = {
985
- create() {
986
- const el = document.createElement("p");
987
- el.dataset.hypenType = "paragraph";
988
- return el;
989
- },
990
- applyProps(el, props) {
991
- const text = props["0"] || props.text;
992
- if (text !== undefined) {
993
- el.textContent = String(text);
994
- }
995
- }
996
- };
997
- });
998
-
999
- // src/dom/components/router.ts
1000
- var exports_router = {};
1001
- __export(exports_router, {
1002
- routerHandler: () => routerHandler
1003
- });
1004
- var routerHandler;
1005
- var init_router = __esm(() => {
1006
- routerHandler = {
1007
- create() {
1008
- const el = document.createElement("div");
1009
- el.style.display = "flex";
1010
- el.style.flexDirection = "column";
1011
- el.style.width = "100%";
1012
- el.dataset.hypenType = "router";
1013
- return el;
1014
- },
1015
- applyProps(el, props) {}
1016
- };
1017
- });
1018
-
1019
- // src/dom/components/route.ts
1020
- var exports_route = {};
1021
- __export(exports_route, {
1022
- routeHandler: () => routeHandler
1023
- });
1
+ /**
2
+ * DOM Renderer
3
+ *
4
+ * Renders Hypen patches to the DOM
5
+ */
1024
6
  import { frameworkLoggers } from "@hypen-space/core/logger";
1025
- var log, routeHandler;
1026
- var init_route = __esm(() => {
1027
- log = frameworkLoggers.router;
1028
- routeHandler = {
1029
- create() {
1030
- const el = document.createElement("div");
1031
- el.style.display = "flex";
1032
- el.style.flexDirection = "column";
1033
- el.style.width = "100%";
1034
- el.dataset.hypenType = "route";
1035
- el.dataset.routeRendered = "false";
1036
- return el;
1037
- },
1038
- applyProps(el, props) {
1039
- const path = props.path || props["0"] || "/";
1040
- el.dataset.routePath = String(path);
1041
- const isLazy = props.__lazy === true;
1042
- el.dataset.routeLazy = String(isLazy);
1043
- const componentName = props.component || props.__lazy_child;
1044
- if (componentName) {
1045
- el.dataset.routeComponent = String(componentName);
1046
- }
1047
- log.debug(`Route created: path="${path}", lazy=${isLazy}, component="${el.dataset.routeComponent || "none"}"`);
1048
- }
1049
- };
1050
- });
1051
-
1052
- // src/dom/components/hypenapp.ts
1053
- var exports_hypenapp = {};
1054
- __export(exports_hypenapp, {
1055
- hypenAppHandler: () => hypenAppHandler,
1056
- disconnectHypenApp: () => disconnectHypenApp
1057
- });
1058
- import { RemoteEngine } from "@hypen-space/core/remote/client";
1059
- import { frameworkLoggers as frameworkLoggers2 } from "@hypen-space/core/logger";
1060
- function applyPatches(container, nodes, patches, engine, onRoot) {
1061
- for (const patch of patches) {
1062
- switch (patch.type) {
1063
- case "create": {
1064
- const el = createElement(patch.elementType, patch.props || {});
1065
- el.dataset.hypenId = patch.id;
1066
- el.__hypenEngine = engine;
1067
- nodes.set(patch.id, el);
1068
- break;
1069
- }
1070
- case "setProp": {
1071
- const el = nodes.get(patch.id);
1072
- if (el) {
1073
- applyProp(el, patch.name, patch.value);
7
+ const log = frameworkLoggers.renderer;
8
+ import { ComponentRegistry } from "./components/index.js";
9
+ import { ApplicatorRegistry } from "./applicators/index.js";
10
+ import { canvasHandler, canvasApplicators } from "./canvas/index.js";
11
+ import { RerenderTracker, defaultDebugConfig } from "./debug.js";
12
+ import { setEngine, disposeHypenElement } from "./element-data.js";
13
+ export class DOMRenderer {
14
+ container;
15
+ nodes = new Map();
16
+ rootId = null;
17
+ components;
18
+ applicators;
19
+ engine;
20
+ currentState = {};
21
+ router = null;
22
+ globalContext = null;
23
+ componentInstances = new Map();
24
+ debugTracker;
25
+ constructor(container, engine, debugConfig) {
26
+ this.container = container;
27
+ this.engine = engine;
28
+ this.components = new ComponentRegistry();
29
+ this.applicators = new ApplicatorRegistry();
30
+ this.debugTracker = new RerenderTracker({ ...defaultDebugConfig, ...debugConfig });
31
+ // Register canvas component and applicators
32
+ this.components.register("canvas", canvasHandler);
33
+ for (const [name, handler] of Object.entries(canvasApplicators)) {
34
+ this.applicators.register(name, handler);
1074
35
  }
1075
- break;
1076
- }
1077
- case "setText": {
1078
- const el = nodes.get(patch.id);
1079
- if (el) {
1080
- el.textContent = patch.text;
1081
- }
1082
- break;
1083
- }
1084
- case "insert": {
1085
- const parentId = patch.parentId;
1086
- const parent = parentId === "root" ? container : nodes.get(parentId);
1087
- const child = nodes.get(patch.id);
1088
- const beforeId = patch.beforeId;
1089
- if (parent && child) {
1090
- if (parentId === "root") {
1091
- onRoot(patch.id);
1092
- }
1093
- if (beforeId) {
1094
- const before = nodes.get(beforeId);
1095
- if (before && before.parentNode === parent) {
1096
- parent.insertBefore(child, before);
1097
- } else if (!parent.contains(child)) {
1098
- parent.appendChild(child);
1099
- }
1100
- } else if (!parent.contains(child)) {
1101
- parent.appendChild(child);
1102
- }
1103
- }
1104
- break;
1105
- }
1106
- case "move": {
1107
- const parentId = patch.parentId;
1108
- const parent = parentId === "root" ? container : nodes.get(parentId);
1109
- const child = nodes.get(patch.id);
1110
- const beforeId = patch.beforeId;
1111
- if (parent && child) {
1112
- if (beforeId) {
1113
- const before = nodes.get(beforeId);
1114
- if (before && before.parentNode === parent) {
1115
- parent.insertBefore(child, before);
1116
- }
1117
- } else {
1118
- parent.appendChild(child);
1119
- }
1120
- }
1121
- break;
1122
- }
1123
- case "remove": {
1124
- const el = nodes.get(patch.id);
1125
- if (el && el.parentNode) {
1126
- el.parentNode.removeChild(el);
1127
- }
1128
- nodes.delete(patch.id);
1129
- break;
1130
- }
1131
36
  }
1132
- }
1133
- }
1134
- function createElement(type, props) {
1135
- const normalizedType = type.toLowerCase();
1136
- const tagMap = {
1137
- column: "div",
1138
- row: "div",
1139
- text: "span",
1140
- button: "button",
1141
- input: "input",
1142
- image: "img",
1143
- container: "div",
1144
- box: "div",
1145
- center: "div",
1146
- list: "div",
1147
- spacer: "div",
1148
- stack: "div",
1149
- divider: "hr",
1150
- grid: "div",
1151
- card: "div",
1152
- heading: "h2",
1153
- link: "a",
1154
- textarea: "textarea",
1155
- checkbox: "input",
1156
- select: "select",
1157
- slider: "input",
1158
- switch: "input",
1159
- spinner: "div",
1160
- badge: "span",
1161
- avatar: "img",
1162
- progressbar: "div",
1163
- video: "video",
1164
- audio: "audio"
1165
- };
1166
- const tag = tagMap[normalizedType] || "div";
1167
- const el = document.createElement(tag);
1168
- el.dataset.hypenType = normalizedType;
1169
- if (normalizedType === "column") {
1170
- el.style.display = "flex";
1171
- el.style.flexDirection = "column";
1172
- } else if (normalizedType === "row") {
1173
- el.style.display = "flex";
1174
- el.style.flexDirection = "row";
1175
- } else if (normalizedType === "center") {
1176
- el.style.display = "flex";
1177
- el.style.alignItems = "center";
1178
- el.style.justifyContent = "center";
1179
- } else if (normalizedType === "text") {
1180
- if (props["0"]) {
1181
- el.textContent = String(props["0"]);
37
+ /**
38
+ * Set router and global context for component composition
39
+ */
40
+ setContext(router, globalContext) {
41
+ this.router = router;
42
+ this.globalContext = globalContext;
43
+ }
44
+ /**
45
+ * Apply a batch of patches to the DOM
46
+ */
47
+ applyPatches(patches) {
48
+ for (const patch of patches) {
49
+ this.applyPatch(patch);
50
+ }
1182
51
  }
1183
- } else if (normalizedType === "button") {
1184
- el.style.cursor = "pointer";
1185
- } else if (normalizedType === "checkbox" || normalizedType === "switch") {
1186
- el.type = "checkbox";
1187
- } else if (normalizedType === "slider") {
1188
- el.type = "range";
1189
- }
1190
- return el;
1191
- }
1192
- function applyProp(el, name, value) {
1193
- if (name === "0" || name === "text") {
1194
- el.textContent = String(value);
1195
- return;
1196
- }
1197
- const styleProps = {
1198
- padding: "padding",
1199
- margin: "margin",
1200
- backgroundColor: "backgroundColor",
1201
- background: "background",
1202
- color: "color",
1203
- fontSize: "fontSize",
1204
- fontWeight: "fontWeight",
1205
- width: "width",
1206
- height: "height",
1207
- minWidth: "minWidth",
1208
- minHeight: "minHeight",
1209
- maxWidth: "maxWidth",
1210
- maxHeight: "maxHeight",
1211
- borderRadius: "borderRadius",
1212
- border: "border",
1213
- gap: "gap",
1214
- flex: "flex",
1215
- opacity: "opacity",
1216
- overflow: "overflow"
1217
- };
1218
- if (styleProps[name]) {
1219
- const cssValue = typeof value === "number" ? `${value}px` : String(value);
1220
- el.style[styleProps[name]] = cssValue;
1221
- return;
1222
- }
1223
- if (name === "onClick" || name === "onclick") {
1224
- el.onclick = () => {
1225
- const engine = el.__hypenEngine;
1226
- if (engine && typeof value === "string" && value.startsWith("@actions.")) {
1227
- const action = value.replace("@actions.", "");
1228
- engine.dispatchAction(action);
1229
- }
1230
- };
1231
- return;
1232
- }
1233
- el.setAttribute(name, String(value));
1234
- }
1235
- function disconnectHypenApp(element) {
1236
- const instance = activeInstances.get(element);
1237
- if (instance) {
1238
- instance.engine.disconnect();
1239
- activeInstances.delete(element);
1240
- }
1241
- }
1242
- var log2, activeInstances, hypenAppHandler;
1243
- var init_hypenapp = __esm(() => {
1244
- log2 = frameworkLoggers2.remote;
1245
- activeInstances = new WeakMap;
1246
- hypenAppHandler = {
1247
- create() {
1248
- const el = document.createElement("div");
1249
- el.dataset.hypenType = "hypenapp";
1250
- el.style.display = "contents";
1251
- return el;
1252
- },
1253
- applyProps(element, props) {
1254
- const url = props["0"] || props.url;
1255
- if (!url || typeof url !== "string") {
1256
- log2.error("HypenApp: URL is required");
1257
- element.innerHTML = '<div style="color: red;">HypenApp: URL required</div>';
1258
- return;
1259
- }
1260
- const existing = activeInstances.get(element);
1261
- if (existing) {
1262
- return;
1263
- }
1264
- const engine = new RemoteEngine(url, {
1265
- autoReconnect: props.autoReconnect ?? true,
1266
- reconnectInterval: props.reconnectInterval ?? 3000,
1267
- maxReconnectAttempts: props.maxReconnectAttempts ?? 10
1268
- });
1269
- const nodes = new Map;
1270
- let rootId = null;
1271
- activeInstances.set(element, { engine, nodes });
1272
- engine.onPatches((patches) => {
1273
- applyPatches(element, nodes, patches, engine, (id) => {
1274
- if (!rootId)
1275
- rootId = id;
1276
- });
1277
- });
1278
- element.innerHTML = '<div class="hypen-app-loading">Connecting...</div>';
1279
- engine.connect().then(() => {
1280
- element.innerHTML = "";
1281
- log2.debug(`HypenApp connected to ${url}`);
1282
- }).catch((error) => {
1283
- element.innerHTML = `<div style="color: red;">HypenApp: Connection failed - ${error.message}</div>`;
1284
- log2.error("HypenApp connection failed:", error);
1285
- });
1286
- engine.onDisconnect(() => {
1287
- log2.debug("HypenApp disconnected");
1288
- });
1289
- engine.onError((error) => {
1290
- log2.error("HypenApp error:", error);
1291
- });
1292
- const observer = new MutationObserver((mutations) => {
1293
- for (const mutation of mutations) {
1294
- for (const removedNode of mutation.removedNodes) {
1295
- if (removedNode === element || removedNode.contains?.(element)) {
1296
- engine.disconnect();
1297
- activeInstances.delete(element);
1298
- observer.disconnect();
1299
- log2.debug("HypenApp cleaned up");
1300
- return;
52
+ /**
53
+ * Update state and interpolate text content
54
+ */
55
+ updateState(state) {
56
+ log.debug("Updating state:", state);
57
+ this.currentState = state;
58
+ this.interpolateAllText();
59
+ }
60
+ /**
61
+ * Merge component state into current state and re-interpolate
62
+ */
63
+ mergeComponentState(componentState) {
64
+ this.currentState = { ...this.currentState, ...componentState };
65
+ log.debug("Merged state:", this.currentState);
66
+ this.interpolateAllText();
67
+ }
68
+ /**
69
+ * Interpolate state values in all text elements
70
+ */
71
+ interpolateAllText() {
72
+ for (const [id, element] of this.nodes.entries()) {
73
+ if (element.dataset.hypenType === "text" && element.dataset.textTemplate) {
74
+ const template = element.dataset.textTemplate;
75
+ const interpolated = this.interpolateText(template, this.currentState);
76
+ // Track re-render if text actually changed
77
+ const currentText = element.textContent;
78
+ if (currentText !== interpolated) {
79
+ this.debugTracker.trackRerender(id, element, "interpolate");
80
+ }
81
+ element.textContent = interpolated;
1301
82
  }
1302
- }
1303
83
  }
1304
- });
1305
- if (element.parentNode) {
1306
- observer.observe(element.parentNode, { childList: true, subtree: true });
1307
- }
1308
- }
1309
- };
1310
- });
1311
-
1312
- // src/dom/components/app.ts
1313
- var exports_app = {};
1314
- __export(exports_app, {
1315
- appHandler: () => appHandler
1316
- });
1317
- var appHandler;
1318
- var init_app = __esm(() => {
1319
- appHandler = {
1320
- create() {
1321
- const el = document.createElement("div");
1322
- el.style.display = "flex";
1323
- el.style.flexDirection = "column";
1324
- el.style.minHeight = "100vh";
1325
- el.style.width = "100%";
1326
- el.dataset.hypenType = "app";
1327
- return el;
1328
- }
1329
- };
1330
- });
1331
-
1332
- // src/dom/components/index.ts
1333
- class ComponentRegistry {
1334
- handlers = new Map;
1335
- constructor() {
1336
- this.registerDefaults();
1337
- }
1338
- register(type, handler) {
1339
- this.handlers.set(type.toLowerCase(), handler);
1340
- }
1341
- get(type) {
1342
- if (!type)
1343
- return;
1344
- return this.handlers.get(type.toLowerCase());
1345
- }
1346
- createElement(type, props = {}) {
1347
- if (!type)
1348
- return null;
1349
- const handler = this.get(type);
1350
- if (!handler)
1351
- return null;
1352
- const element = handler.create();
1353
- if (handler.applyProps) {
1354
- handler.applyProps(element, props);
1355
- }
1356
- return element;
1357
- }
1358
- registerDefaults() {
1359
- const { columnHandler: columnHandler2 } = (init_column(), __toCommonJS(exports_column));
1360
- const { rowHandler: rowHandler2 } = (init_row(), __toCommonJS(exports_row));
1361
- const { textHandler: textHandler2 } = (init_text(), __toCommonJS(exports_text));
1362
- const { imageHandler: imageHandler2 } = (init_image(), __toCommonJS(exports_image));
1363
- const { buttonHandler: buttonHandler2 } = (init_button(), __toCommonJS(exports_button));
1364
- const { containerHandler: containerHandler2 } = (init_container(), __toCommonJS(exports_container));
1365
- const { centerHandler: centerHandler2 } = (init_center(), __toCommonJS(exports_center));
1366
- const { listHandler: listHandler2 } = (init_list(), __toCommonJS(exports_list));
1367
- const { inputHandler: inputHandler2 } = (init_input(), __toCommonJS(exports_input));
1368
- const { linkHandler: linkHandler2 } = (init_link(), __toCommonJS(exports_link));
1369
- const { textareaHandler: textareaHandler2 } = (init_textarea(), __toCommonJS(exports_textarea));
1370
- const { checkboxHandler: checkboxHandler2 } = (init_checkbox(), __toCommonJS(exports_checkbox));
1371
- const { selectHandler: selectHandler2 } = (init_select(), __toCommonJS(exports_select));
1372
- const { spacerHandler: spacerHandler2 } = (init_spacer(), __toCommonJS(exports_spacer));
1373
- const { stackHandler: stackHandler2 } = (init_stack(), __toCommonJS(exports_stack));
1374
- const { dividerHandler: dividerHandler2 } = (init_divider(), __toCommonJS(exports_divider));
1375
- const { gridHandler: gridHandler2 } = (init_grid(), __toCommonJS(exports_grid));
1376
- const { cardHandler: cardHandler2 } = (init_card(), __toCommonJS(exports_card));
1377
- const { headingHandler: headingHandler2 } = (init_heading(), __toCommonJS(exports_heading));
1378
- const { switchHandler: switchHandler2 } = (init_switch(), __toCommonJS(exports_switch));
1379
- const { sliderHandler: sliderHandler2 } = (init_slider(), __toCommonJS(exports_slider));
1380
- const { spinnerHandler: spinnerHandler2 } = (init_spinner(), __toCommonJS(exports_spinner));
1381
- const { badgeHandler: badgeHandler2 } = (init_badge(), __toCommonJS(exports_badge));
1382
- const { avatarHandler: avatarHandler2 } = (init_avatar(), __toCommonJS(exports_avatar));
1383
- const { progressBarHandler: progressBarHandler2 } = (init_progressbar(), __toCommonJS(exports_progressbar));
1384
- const { videoHandler: videoHandler2 } = (init_video(), __toCommonJS(exports_video));
1385
- const { audioHandler: audioHandler2 } = (init_audio(), __toCommonJS(exports_audio));
1386
- const { paragraphHandler: paragraphHandler2 } = (init_paragraph(), __toCommonJS(exports_paragraph));
1387
- const { routerHandler: routerHandler2 } = (init_router(), __toCommonJS(exports_router));
1388
- const { routeHandler: routeHandler2 } = (init_route(), __toCommonJS(exports_route));
1389
- const { hypenAppHandler: hypenAppHandler2 } = (init_hypenapp(), __toCommonJS(exports_hypenapp));
1390
- const { appHandler: appHandler2 } = (init_app(), __toCommonJS(exports_app));
1391
- this.register("app", appHandler2);
1392
- this.register("column", columnHandler2);
1393
- this.register("row", rowHandler2);
1394
- this.register("text", textHandler2);
1395
- this.register("image", imageHandler2);
1396
- this.register("button", buttonHandler2);
1397
- this.register("container", containerHandler2);
1398
- this.register("box", containerHandler2);
1399
- this.register("center", centerHandler2);
1400
- this.register("list", listHandler2);
1401
- this.register("input", inputHandler2);
1402
- this.register("link", linkHandler2);
1403
- this.register("textarea", textareaHandler2);
1404
- this.register("checkbox", checkboxHandler2);
1405
- this.register("select", selectHandler2);
1406
- this.register("spacer", spacerHandler2);
1407
- this.register("stack", stackHandler2);
1408
- this.register("divider", dividerHandler2);
1409
- this.register("grid", gridHandler2);
1410
- this.register("card", cardHandler2);
1411
- this.register("heading", headingHandler2);
1412
- this.register("switch", switchHandler2);
1413
- this.register("slider", sliderHandler2);
1414
- this.register("spinner", spinnerHandler2);
1415
- this.register("badge", badgeHandler2);
1416
- this.register("avatar", avatarHandler2);
1417
- this.register("progressbar", progressBarHandler2);
1418
- this.register("video", videoHandler2);
1419
- this.register("audio", audioHandler2);
1420
- this.register("paragraph", paragraphHandler2);
1421
- this.register("router", routerHandler2);
1422
- this.register("route", routeHandler2);
1423
- this.register("hypenapp", hypenAppHandler2);
1424
- }
1425
- }
1426
-
1427
- // src/dom/applicators/padding.ts
1428
- var getNumericValue = (value) => {
1429
- if (typeof value === "number")
1430
- return value;
1431
- if (typeof value === "object" && value["0"] !== undefined)
1432
- return Number(value["0"]);
1433
- if (typeof value === "string")
1434
- return parseFloat(value);
1435
- return null;
1436
- };
1437
- var paddingHandler = (el, value) => {
1438
- if (typeof value === "number") {
1439
- el.style.padding = `${value}px`;
1440
- } else if (typeof value === "object") {
1441
- if (value["0"] !== undefined && Object.keys(value).length === 1) {
1442
- el.style.padding = `${value["0"]}px`;
1443
- } else {
1444
- if (value.left !== undefined)
1445
- el.style.paddingLeft = `${value.left}px`;
1446
- if (value.right !== undefined)
1447
- el.style.paddingRight = `${value.right}px`;
1448
- if (value.top !== undefined)
1449
- el.style.paddingTop = `${value.top}px`;
1450
- if (value.bottom !== undefined)
1451
- el.style.paddingBottom = `${value.bottom}px`;
1452
- }
1453
- } else {
1454
- el.style.padding = String(value);
1455
- }
1456
- };
1457
- var paddingTopHandler = (el, value) => {
1458
- const v = getNumericValue(value);
1459
- if (v !== null)
1460
- el.style.paddingTop = `${v}px`;
1461
- };
1462
- var paddingBottomHandler = (el, value) => {
1463
- const v = getNumericValue(value);
1464
- if (v !== null)
1465
- el.style.paddingBottom = `${v}px`;
1466
- };
1467
- var paddingLeftHandler = (el, value) => {
1468
- const v = getNumericValue(value);
1469
- if (v !== null)
1470
- el.style.paddingLeft = `${v}px`;
1471
- };
1472
- var paddingRightHandler = (el, value) => {
1473
- const v = getNumericValue(value);
1474
- if (v !== null)
1475
- el.style.paddingRight = `${v}px`;
1476
- };
1477
- var paddingHorizontalHandler = (el, value) => {
1478
- const v = getNumericValue(value);
1479
- if (v !== null) {
1480
- el.style.paddingLeft = `${v}px`;
1481
- el.style.paddingRight = `${v}px`;
1482
- }
1483
- };
1484
- var paddingVerticalHandler = (el, value) => {
1485
- const v = getNumericValue(value);
1486
- if (v !== null) {
1487
- el.style.paddingTop = `${v}px`;
1488
- el.style.paddingBottom = `${v}px`;
1489
- }
1490
- };
1491
-
1492
- // src/dom/applicators/margin.ts
1493
- var getNumericValue2 = (value) => {
1494
- if (typeof value === "number")
1495
- return value;
1496
- if (typeof value === "object" && value["0"] !== undefined)
1497
- return Number(value["0"]);
1498
- if (typeof value === "string")
1499
- return parseFloat(value);
1500
- return null;
1501
- };
1502
- var marginHandler = (el, value) => {
1503
- if (typeof value === "number") {
1504
- el.style.margin = `${value}px`;
1505
- } else if (typeof value === "object") {
1506
- if (value["0"] !== undefined && Object.keys(value).length === 1) {
1507
- el.style.margin = `${value["0"]}px`;
1508
- } else {
1509
- if (value.left !== undefined)
1510
- el.style.marginLeft = `${value.left}px`;
1511
- if (value.right !== undefined)
1512
- el.style.marginRight = `${value.right}px`;
1513
- if (value.top !== undefined)
1514
- el.style.marginTop = `${value.top}px`;
1515
- if (value.bottom !== undefined)
1516
- el.style.marginBottom = `${value.bottom}px`;
1517
- }
1518
- } else {
1519
- el.style.margin = String(value);
1520
- }
1521
- };
1522
- var marginTopHandler = (el, value) => {
1523
- const v = getNumericValue2(value);
1524
- if (v !== null)
1525
- el.style.marginTop = `${v}px`;
1526
- };
1527
- var marginBottomHandler = (el, value) => {
1528
- const v = getNumericValue2(value);
1529
- if (v !== null)
1530
- el.style.marginBottom = `${v}px`;
1531
- };
1532
- var marginLeftHandler = (el, value) => {
1533
- const v = getNumericValue2(value);
1534
- if (v !== null)
1535
- el.style.marginLeft = `${v}px`;
1536
- };
1537
- var marginRightHandler = (el, value) => {
1538
- const v = getNumericValue2(value);
1539
- if (v !== null)
1540
- el.style.marginRight = `${v}px`;
1541
- };
1542
- var marginHorizontalHandler = (el, value) => {
1543
- const v = getNumericValue2(value);
1544
- if (v !== null) {
1545
- el.style.marginLeft = `${v}px`;
1546
- el.style.marginRight = `${v}px`;
1547
- }
1548
- };
1549
- var marginVerticalHandler = (el, value) => {
1550
- const v = getNumericValue2(value);
1551
- if (v !== null) {
1552
- el.style.marginTop = `${v}px`;
1553
- el.style.marginBottom = `${v}px`;
1554
- }
1555
- };
1556
-
1557
- // src/dom/applicators/color.ts
1558
- var colorHandlers = {
1559
- color: (el, value) => {
1560
- el.style.color = String(value);
1561
- },
1562
- backgroundColor: (el, value) => {
1563
- el.style.backgroundColor = String(value);
1564
- },
1565
- borderColor: (el, value) => {
1566
- el.style.borderColor = String(value);
1567
- },
1568
- opacity: (el, value) => {
1569
- el.style.opacity = String(value);
1570
- }
1571
- };
1572
-
1573
- // src/dom/applicators/border.ts
1574
- var borderHandlers = {
1575
- border: (el, value) => {
1576
- if (typeof value === "number") {
1577
- el.style.borderWidth = `${value}px`;
1578
- el.style.borderStyle = "solid";
1579
- } else if (typeof value === "object" && value !== null) {
1580
- const obj = value;
1581
- if (obj.width !== undefined) {
1582
- el.style.borderWidth = typeof obj.width === "number" ? `${obj.width}px` : String(obj.width);
1583
- }
1584
- if (obj.color !== undefined) {
1585
- el.style.borderColor = String(obj.color);
1586
- }
1587
- if (obj.style !== undefined) {
1588
- el.style.borderStyle = String(obj.style);
1589
- } else {
1590
- el.style.borderStyle = "solid";
1591
- }
1592
- if (obj.radius !== undefined) {
1593
- el.style.borderRadius = typeof obj.radius === "number" ? `${obj.radius}px` : String(obj.radius);
1594
- }
1595
- } else if (typeof value === "string") {
1596
- el.style.border = value;
1597
- }
1598
- },
1599
- borderWidth: (el, value) => {
1600
- el.style.borderWidth = typeof value === "number" ? `${value}px` : String(value);
1601
- },
1602
- borderStyle: (el, value) => {
1603
- el.style.borderStyle = String(value);
1604
- },
1605
- borderRadius: (el, value) => {
1606
- if (typeof value === "number") {
1607
- el.style.borderRadius = `${value}px`;
1608
- } else if (typeof value === "object" && value !== null) {
1609
- const obj = value;
1610
- const topLeft = obj.topLeft ?? obj.topStart ?? 0;
1611
- const topRight = obj.topRight ?? obj.topEnd ?? 0;
1612
- const bottomRight = obj.bottomRight ?? obj.bottomEnd ?? 0;
1613
- const bottomLeft = obj.bottomLeft ?? obj.bottomStart ?? 0;
1614
- const formatValue = (v) => typeof v === "number" ? `${v}px` : String(v);
1615
- el.style.borderRadius = `${formatValue(topLeft)} ${formatValue(topRight)} ${formatValue(bottomRight)} ${formatValue(bottomLeft)}`;
1616
- } else {
1617
- el.style.borderRadius = String(value);
1618
- }
1619
- },
1620
- cornerRadius: (el, value) => {
1621
- if (typeof value === "number") {
1622
- el.style.borderRadius = `${value}px`;
1623
- } else if (typeof value === "object" && value !== null) {
1624
- const obj = value;
1625
- const topLeft = obj.topLeft ?? obj.topStart ?? 0;
1626
- const topRight = obj.topRight ?? obj.topEnd ?? 0;
1627
- const bottomRight = obj.bottomRight ?? obj.bottomEnd ?? 0;
1628
- const bottomLeft = obj.bottomLeft ?? obj.bottomStart ?? 0;
1629
- const formatValue = (v) => typeof v === "number" ? `${v}px` : String(v);
1630
- el.style.borderRadius = `${formatValue(topLeft)} ${formatValue(topRight)} ${formatValue(bottomRight)} ${formatValue(bottomLeft)}`;
1631
- } else {
1632
- el.style.borderRadius = String(value);
1633
- }
1634
- }
1635
- };
1636
-
1637
- // src/dom/applicators/size.ts
1638
- function parseSizeValue(value) {
1639
- if (value === null || value === undefined)
1640
- return null;
1641
- if (typeof value === "number") {
1642
- return `${value}px`;
1643
- }
1644
- const str = String(value).trim().toLowerCase();
1645
- switch (str) {
1646
- case "fill":
1647
- case "match_parent":
1648
- return "100%";
1649
- case "wrap":
1650
- case "wrap_content":
1651
- case "auto":
1652
- return "auto";
1653
- case "infinity":
1654
- case "inf":
1655
- case "max":
1656
- return "100%";
1657
- }
1658
- const match = str.match(/^(-?[\d.]+)\s*(px|dp|pt|%|vw|vh|vmin|vmax|em|rem)?$/);
1659
- if (!match) {
1660
- return str;
1661
- }
1662
- const num = parseFloat(match[1]);
1663
- const unit = match[2] || "px";
1664
- switch (unit) {
1665
- case "px":
1666
- return `${num}px`;
1667
- case "dp":
1668
- case "pt":
1669
- return `${num}px`;
1670
- case "%":
1671
- return `${num}%`;
1672
- case "vw":
1673
- return `${num}vw`;
1674
- case "vh":
1675
- return `${num}vh`;
1676
- case "vmin":
1677
- return `${num}vmin`;
1678
- case "vmax":
1679
- return `${num}vmax`;
1680
- case "em":
1681
- return `${num}em`;
1682
- case "rem":
1683
- return `${num}rem`;
1684
- default:
1685
- return `${num}px`;
1686
- }
1687
- }
1688
- var sizeHandlers = {
1689
- width: (el, value) => {
1690
- const size = parseSizeValue(value);
1691
- if (size)
1692
- el.style.width = size;
1693
- },
1694
- height: (el, value) => {
1695
- const size = parseSizeValue(value);
1696
- if (size)
1697
- el.style.height = size;
1698
- },
1699
- minWidth: (el, value) => {
1700
- const size = parseSizeValue(value);
1701
- if (size)
1702
- el.style.minWidth = size;
1703
- },
1704
- minHeight: (el, value) => {
1705
- const size = parseSizeValue(value);
1706
- if (size)
1707
- el.style.minHeight = size;
1708
- },
1709
- maxWidth: (el, value) => {
1710
- const size = parseSizeValue(value);
1711
- if (size)
1712
- el.style.maxWidth = size;
1713
- },
1714
- maxHeight: (el, value) => {
1715
- const size = parseSizeValue(value);
1716
- if (size)
1717
- el.style.maxHeight = size;
1718
- },
1719
- size: (el, value) => {
1720
- if (typeof value === "object" && value !== null) {
1721
- const obj = value;
1722
- if (obj.width !== undefined) {
1723
- const w = parseSizeValue(obj.width);
1724
- if (w)
1725
- el.style.width = w;
1726
- }
1727
- if (obj.height !== undefined) {
1728
- const h = parseSizeValue(obj.height);
1729
- if (h)
1730
- el.style.height = h;
1731
- }
1732
- } else {
1733
- const size = parseSizeValue(value);
1734
- if (size) {
1735
- el.style.width = size;
1736
- el.style.height = size;
1737
- }
1738
- }
1739
- },
1740
- fillMaxWidth: (el, value) => {
1741
- if (value === false)
1742
- return;
1743
- const fraction = typeof value === "number" ? value : 1;
1744
- if (fraction === 1) {
1745
- el.style.alignSelf = "stretch";
1746
- el.style.width = "100%";
1747
- el.style.minWidth = "0";
1748
- } else {
1749
- el.style.width = `${fraction * 100}%`;
1750
- }
1751
- el.style.justifySelf = "stretch";
1752
- },
1753
- fillMaxHeight: (el, value) => {
1754
- if (value === false)
1755
- return;
1756
- const fraction = typeof value === "number" ? value : 1;
1757
- el.style.height = `${fraction * 100}%`;
1758
- },
1759
- fillMaxSize: (el, value) => {
1760
- if (value === false)
1761
- return;
1762
- const fraction = typeof value === "number" ? value : 1;
1763
- el.style.width = `${fraction * 100}%`;
1764
- el.style.height = `${fraction * 100}%`;
1765
- el.style.alignSelf = "stretch";
1766
- el.style.minWidth = "0";
1767
- }
1768
- };
1769
-
1770
- // src/dom/applicators/font.ts
1771
- var loadedGoogleFonts = new Set;
1772
- var systemFontKeywords = new Set([
1773
- "default",
1774
- "system",
1775
- "system-ui",
1776
- "inherit",
1777
- "initial",
1778
- "unset",
1779
- "serif",
1780
- "sans-serif",
1781
- "monospace",
1782
- "cursive",
1783
- "fantasy",
1784
- "-apple-system",
1785
- "BlinkMacSystemFont",
1786
- "Segoe UI",
1787
- "Arial",
1788
- "Helvetica",
1789
- "Times New Roman",
1790
- "Georgia",
1791
- "Courier New",
1792
- "Verdana",
1793
- "Tahoma"
1794
- ]);
1795
- function isSystemFont(fontName) {
1796
- const normalized = fontName.toLowerCase().trim();
1797
- return systemFontKeywords.has(normalized) || normalized.startsWith("-") || normalized.startsWith("ui-");
1798
- }
1799
- function loadGoogleFont(fontName) {
1800
- const normalized = fontName.trim();
1801
- if (loadedGoogleFonts.has(normalized) || isSystemFont(normalized)) {
1802
- return;
1803
- }
1804
- loadedGoogleFonts.add(normalized);
1805
- const link = document.createElement("link");
1806
- link.rel = "stylesheet";
1807
- link.href = `https://fonts.googleapis.com/css2?family=${encodeURIComponent(normalized)}:wght@100;200;300;400;500;600;700;800;900&display=swap`;
1808
- document.head.appendChild(link);
1809
- }
1810
- function processFontFamily(value) {
1811
- const fonts = value.split(",").map((f) => f.trim().replace(/["']/g, ""));
1812
- for (const font of fonts) {
1813
- if (!isSystemFont(font)) {
1814
- loadGoogleFont(font);
1815
- }
1816
- }
1817
- return fonts.map((f) => {
1818
- if (f.includes(" ") && !f.startsWith('"') && !f.startsWith("'")) {
1819
- return `"${f}"`;
1820
- }
1821
- return f;
1822
- }).join(", ");
1823
- }
1824
- var fontHandlers = {
1825
- fontSize: (el, value) => {
1826
- el.style.fontSize = typeof value === "number" ? `${value}px` : String(value);
1827
- },
1828
- fontWeight: (el, value) => {
1829
- el.style.fontWeight = String(value);
1830
- },
1831
- fontFamily: (el, value) => {
1832
- const fontValue = String(value);
1833
- el.style.fontFamily = processFontFamily(fontValue);
1834
- },
1835
- textAlign: (el, value) => {
1836
- el.style.textAlign = String(value);
1837
- },
1838
- lineHeight: (el, value) => {
1839
- el.style.lineHeight = String(value);
1840
- },
1841
- fontStyle: (el, value) => {
1842
- el.style.fontStyle = String(value);
1843
- },
1844
- textTransform: (el, value) => {
1845
- el.style.textTransform = String(value);
1846
- }
1847
- };
1848
- var GoogleFonts = {
1849
- preload: loadGoogleFont,
1850
- isLoaded: (fontName) => loadedGoogleFonts.has(fontName.trim()),
1851
- getLoadedFonts: () => Array.from(loadedGoogleFonts),
1852
- popular: [
1853
- "Roboto",
1854
- "Open Sans",
1855
- "Lato",
1856
- "Montserrat",
1857
- "Poppins",
1858
- "Inter",
1859
- "Nunito",
1860
- "Playfair Display",
1861
- "Merriweather",
1862
- "Source Code Pro",
1863
- "Fira Code",
1864
- "JetBrains Mono",
1865
- "Raleway",
1866
- "Ubuntu",
1867
- "Oswald",
1868
- "Quicksand",
1869
- "Work Sans",
1870
- "Rubik",
1871
- "Karla",
1872
- "DM Sans"
1873
- ]
1874
- };
1875
-
1876
- // src/dom/applicators/layout.ts
1877
- function mapAlignmentValue(value) {
1878
- const v = String(value).toLowerCase();
1879
- switch (v) {
1880
- case "top":
1881
- case "start":
1882
- case "leading":
1883
- case "left":
1884
- return "flex-start";
1885
- case "bottom":
1886
- case "end":
1887
- case "trailing":
1888
- case "right":
1889
- return "flex-end";
1890
- case "center":
1891
- return "center";
1892
- case "spacebetween":
1893
- case "space-between":
1894
- return "space-between";
1895
- case "spacearound":
1896
- case "space-around":
1897
- return "space-around";
1898
- case "spaceevenly":
1899
- case "space-evenly":
1900
- return "space-evenly";
1901
- default:
1902
- return v;
1903
- }
1904
- }
1905
- var layoutHandlers = {
1906
- verticalAlignment: (el, value) => {
1907
- const val = mapAlignmentValue(String(value));
1908
- const display = el.style.display || getComputedStyle(el).display;
1909
- const flexDirection = el.style.flexDirection || getComputedStyle(el).flexDirection;
1910
- if (display === "grid") {
1911
- el.style.alignItems = val;
1912
- } else if (flexDirection === "column" || flexDirection === "column-reverse") {
1913
- el.style.justifyContent = val;
1914
- } else {
1915
- el.style.alignItems = val;
1916
- }
1917
- },
1918
- horizontalAlignment: (el, value) => {
1919
- const val = mapAlignmentValue(String(value));
1920
- const display = el.style.display || getComputedStyle(el).display;
1921
- const flexDirection = el.style.flexDirection || getComputedStyle(el).flexDirection;
1922
- if (display === "grid") {
1923
- el.style.justifyItems = val;
1924
- } else if (flexDirection === "column" || flexDirection === "column-reverse") {
1925
- el.style.alignItems = val;
1926
- } else if (flexDirection === "row" || flexDirection === "row-reverse") {
1927
- el.style.justifyContent = val;
1928
- if (val !== "flex-start" && el.style.overflow !== "auto" && el.style.overflowX !== "auto") {
1929
- el.style.width = "100%";
1930
- }
1931
- } else {
1932
- el.style.justifyContent = val;
1933
- }
1934
- },
1935
- horizontalAlign: (el, value) => {
1936
- el.style.justifyContent = mapAlignmentValue(String(value));
1937
- },
1938
- verticalAlign: (el, value) => {
1939
- el.style.alignItems = mapAlignmentValue(String(value));
1940
- },
1941
- gap: (el, value) => {
1942
- el.style.gap = typeof value === "number" ? `${value}px` : String(value);
1943
- },
1944
- weight: (el, value) => {
1945
- el.style.flex = String(value);
1946
- el.dataset.hypenFlex = "true";
1947
- },
1948
- flex: (el, value) => {
1949
- el.style.flex = String(value);
1950
- el.dataset.hypenFlex = "true";
1951
- },
1952
- flexGrow: (el, value) => {
1953
- el.style.flexGrow = String(value);
1954
- },
1955
- flexShrink: (el, value) => {
1956
- el.style.flexShrink = String(value);
1957
- },
1958
- cursor: (el, value) => {
1959
- el.style.cursor = String(value);
1960
- },
1961
- overflow: (el, value) => {
1962
- el.style.overflow = String(value);
1963
- },
1964
- scrollable: (el, value) => {
1965
- if (value === true || value === "true") {
1966
- el.style.overflow = "auto";
1967
- } else if (value === false || value === "false") {
1968
- el.style.overflow = "hidden";
1969
- } else if (value === "vertical") {
1970
- el.style.overflowX = "hidden";
1971
- el.style.overflowY = "auto";
1972
- } else if (value === "horizontal") {
1973
- el.style.overflowX = "auto";
1974
- el.style.overflowY = "hidden";
1975
- } else if (value === "both") {
1976
- el.style.overflow = "auto";
1977
- } else {
1978
- el.style.overflow = String(value);
1979
- }
1980
- }
1981
- };
1982
-
1983
- // src/dom/element-data.ts
1984
- import { getElementDisposables } from "@hypen-space/core/disposable";
1985
- var elementDataMap = new WeakMap;
1986
- function getHypenData(element) {
1987
- let data = elementDataMap.get(element);
1988
- if (!data) {
1989
- data = {};
1990
- elementDataMap.set(element, data);
1991
- }
1992
- return data;
1993
- }
1994
- function hasHypenData(element) {
1995
- return elementDataMap.has(element);
1996
- }
1997
- function clearHypenData(element) {
1998
- elementDataMap.delete(element);
1999
- }
2000
- function getEngine(element) {
2001
- return getHypenData(element).engine;
2002
- }
2003
- function setEngine(element, engine) {
2004
- getHypenData(element).engine = engine;
2005
- }
2006
- function findEngine(element) {
2007
- let current = element;
2008
- while (current) {
2009
- const engine = getEngine(current);
2010
- if (engine)
2011
- return engine;
2012
- current = current.parentElement;
2013
- }
2014
- return;
2015
- }
2016
- function getRegisteredEvents(element) {
2017
- const data = getHypenData(element);
2018
- if (!data.registeredEvents) {
2019
- data.registeredEvents = new Set;
2020
- }
2021
- return data.registeredEvents;
2022
- }
2023
- function isEventRegistered(element, eventKey) {
2024
- return getRegisteredEvents(element).has(eventKey);
2025
- }
2026
- function registerEvent(element, eventKey) {
2027
- getRegisteredEvents(element).add(eventKey);
2028
- }
2029
- function unregisterEvent(element, eventKey) {
2030
- getRegisteredEvents(element).delete(eventKey);
2031
- }
2032
- function getKeyTarget(element) {
2033
- return getHypenData(element).keyTarget;
2034
- }
2035
- function setKeyTarget(element, key) {
2036
- getHypenData(element).keyTarget = key;
2037
- }
2038
- function getMeta(element, key) {
2039
- return getHypenData(element).meta?.[key];
2040
- }
2041
- function setMeta(element, key, value) {
2042
- const data = getHypenData(element);
2043
- if (!data.meta) {
2044
- data.meta = {};
2045
- }
2046
- data.meta[key] = value;
2047
- }
2048
- function disposeHypenElement(element) {
2049
- try {
2050
- const disposables = getElementDisposables(element);
2051
- disposables.dispose();
2052
- } catch {}
2053
- clearHypenData(element);
2054
- }
2055
- var HYPEN_ENGINE_SYMBOL = Symbol.for("hypen.engine");
2056
- var REGISTERED_EVENTS_SYMBOL = Symbol.for("hypen.registeredEvents");
2057
- var KEY_TARGET_SYMBOL = Symbol.for("hypen.keyTarget");
2058
- function getLegacyEngine(element) {
2059
- const engine = getEngine(element);
2060
- if (engine)
2061
- return engine;
2062
- return element[HYPEN_ENGINE_SYMBOL] ?? element.__hypenEngine;
2063
- }
2064
- function setLegacyEngine(element, engine) {
2065
- setEngine(element, engine);
2066
- element.__hypenEngine = engine;
2067
- }
2068
-
2069
- // src/dom/applicators/events.ts
2070
- import {
2071
- getElementDisposables as getElementDisposables2,
2072
- disposableListener,
2073
- disposableTimeout
2074
- } from "@hypen-space/core/disposable";
2075
- import { frameworkLoggers as frameworkLoggers3 } from "@hypen-space/core/logger";
2076
- var log3 = frameworkLoggers3.events;
2077
- function toPlainObject(value) {
2078
- if (value instanceof Map) {
2079
- const obj = {};
2080
- for (const [key, val] of value.entries()) {
2081
- obj[key] = toPlainObject(val);
2082
- }
2083
- return obj;
2084
- }
2085
- if (Array.isArray(value)) {
2086
- return value.map((item) => toPlainObject(item));
2087
- }
2088
- if (value && typeof value === "object") {
2089
- const obj = {};
2090
- for (const [key, val] of Object.entries(value)) {
2091
- obj[key] = toPlainObject(val);
2092
84
  }
2093
- return obj;
2094
- }
2095
- return value;
2096
- }
2097
- function extractActionDetails(value) {
2098
- if (typeof value === "string") {
2099
- if (!value.startsWith("@")) {
2100
- return { actionName: null, payload: {} };
85
+ /**
86
+ * Interpolate state values in text template
87
+ */
88
+ interpolateText(template, state) {
89
+ return template.replace(/\$\{([^}]+)\}/g, (match, path) => {
90
+ try {
91
+ const value = path.split('.').reduce((obj, key) => {
92
+ if (key === 'state')
93
+ return state;
94
+ return obj?.[key];
95
+ }, state);
96
+ return value !== undefined ? String(value) : match;
97
+ }
98
+ catch {
99
+ return match;
100
+ }
101
+ });
2101
102
  }
2102
- let actionName = value.substring(1);
2103
- if (actionName.startsWith("actions.")) {
2104
- actionName = actionName.substring(8);
103
+ /**
104
+ * Apply a single patch
105
+ *
106
+ * Handles both camelCase (direct WASM) and snake_case (WebSocket JSON) field names.
107
+ */
108
+ applyPatch(patch) {
109
+ // Normalize: accept both camelCase and snake_case field names.
110
+ // WASM serde outputs camelCase, but patches forwarded via JSON over WebSocket
111
+ // may arrive with snake_case depending on the serialization path.
112
+ const p = patch;
113
+ const id = patch.id ?? p.id;
114
+ const elementType = patch.elementType ?? p.element_type;
115
+ const parentId = patch.parentId ?? p.parent_id;
116
+ const beforeId = patch.beforeId ?? p.before_id;
117
+ const eventName = patch.eventName ?? p.event_name;
118
+ switch (patch.type) {
119
+ case "create":
120
+ this.onCreate(id, elementType ?? "container", patch.props || {});
121
+ break;
122
+ case "setProp":
123
+ this.onSetProp(id, patch.name, patch.value);
124
+ break;
125
+ case "removeProp":
126
+ this.onRemoveProp(id, patch.name);
127
+ break;
128
+ case "setText":
129
+ this.onSetText(id, patch.text);
130
+ break;
131
+ case "insert":
132
+ this.onInsert(parentId, id, beforeId);
133
+ break;
134
+ case "move":
135
+ this.onMove(parentId, id, beforeId);
136
+ break;
137
+ case "remove":
138
+ this.onRemove(id);
139
+ break;
140
+ }
2105
141
  }
2106
- return { actionName, payload: {} };
2107
- }
2108
- if (value && typeof value === "object") {
2109
- const plain = toPlainObject(value);
2110
- const payload = {};
2111
- let actionName = null;
2112
- if (plain && typeof plain === "object") {
2113
- const actionValue = plain["0"];
2114
- if (typeof actionValue === "string" && actionValue.startsWith("@")) {
2115
- actionName = actionValue.substring(1);
2116
- if (actionName.startsWith("actions.")) {
2117
- actionName = actionName.substring(8);
142
+ /**
143
+ * Create a new element
144
+ */
145
+ onCreate(id, elementType, props) {
146
+ const propsObj = props instanceof Map ? Object.fromEntries(props) : props;
147
+ let element = this.components.createElement(elementType, propsObj);
148
+ if (!element) {
149
+ // For unknown component types, create a transparent container (div).
150
+ // This handles module wrappers (like "App") and custom components
151
+ // that aren't registered but should act as layout containers.
152
+ const fallback = document.createElement("div");
153
+ fallback.dataset.hypenType = elementType.toLowerCase();
154
+ fallback.style.display = "contents"; // Make container transparent in layout
155
+ element = fallback;
156
+ log.debug(`Unknown component "${elementType}" - using transparent container`);
2118
157
  }
2119
- }
2120
- for (const [key, val] of Object.entries(plain)) {
2121
- if (key !== "0") {
2122
- if (/^\d+$/.test(key) && val && typeof val === "object" && !Array.isArray(val)) {
2123
- for (const [innerKey, innerVal] of Object.entries(val)) {
2124
- payload[innerKey] = innerVal;
158
+ element.dataset.hypenType = elementType.toLowerCase();
159
+ element.dataset.hypenId = id;
160
+ setEngine(element, this.engine);
161
+ this.applicators.applyAll(element, propsObj);
162
+ this.nodes.set(id, element);
163
+ this.debugTracker.trackRerender(id, element, `create:${elementType}`);
164
+ if (!this.rootId) {
165
+ this.rootId = id;
166
+ if (!this.container.contains(element)) {
167
+ this.container.appendChild(element);
2125
168
  }
2126
- } else {
2127
- payload[key] = val;
2128
- }
2129
- }
2130
- }
2131
- }
2132
- return { actionName, payload };
2133
- }
2134
- return { actionName: null, payload: {} };
2135
- }
2136
- function extractEventData(event, element) {
2137
- const data = {
2138
- type: event.type,
2139
- timestamp: Date.now()
2140
- };
2141
- if (event instanceof MouseEvent) {
2142
- data.clientX = event.clientX;
2143
- data.clientY = event.clientY;
2144
- data.button = event.button;
2145
- }
2146
- if (event instanceof KeyboardEvent) {
2147
- data.key = event.key;
2148
- data.code = event.code;
2149
- data.ctrlKey = event.ctrlKey;
2150
- data.shiftKey = event.shiftKey;
2151
- data.altKey = event.altKey;
2152
- data.metaKey = event.metaKey;
2153
- }
2154
- if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
2155
- data.value = element.value;
2156
- }
2157
- if (element instanceof HTMLSelectElement) {
2158
- data.value = element.value;
2159
- data.selectedIndex = element.selectedIndex;
2160
- }
2161
- if (event.type === "submit" && element instanceof HTMLFormElement) {
2162
- data.formData = new FormData(element);
2163
- }
2164
- return data;
2165
- }
2166
- function createEventHandler(eventType, options = {}) {
2167
- return (element, value) => {
2168
- const { actionName, payload: customPayload } = extractActionDetails(value);
2169
- if (!actionName) {
2170
- log3.warn(`${eventType} requires an action reference starting with @, got:`, value);
2171
- return;
2172
- }
2173
- const disposables = getElementDisposables2(element);
2174
- const eventKey = `${eventType}:${actionName}`;
2175
- if (getRegisteredEvents(element).has(eventKey)) {
2176
- return;
2177
- }
2178
- registerEvent(element, eventKey);
2179
- let throttleTimer = null;
2180
- const listener = (event) => {
2181
- if (options.throttleMs && throttleTimer) {
2182
- return;
2183
- }
2184
- if (options.throttleMs) {
2185
- throttleTimer = disposableTimeout(() => {
2186
- throttleTimer = null;
2187
- }, options.throttleMs);
2188
- }
2189
- if (options.preventDefault) {
2190
- event.preventDefault();
2191
- }
2192
- const payload = Object.keys(customPayload).length > 0 ? { ...customPayload } : options.extractPayload ? options.extractPayload(event, element) : extractEventData(event, element);
2193
- const engine = getEngine(element);
2194
- if (engine) {
2195
- try {
2196
- engine.dispatchAction(actionName, payload);
2197
- } catch (err) {
2198
- log3.error(`Error dispatching action "${actionName}":`, err);
2199
169
  }
2200
- }
2201
- };
2202
- disposables.add(disposableListener(element, eventType, listener, {
2203
- passive: options.passive
2204
- }));
2205
- disposables.addCallback(() => {
2206
- unregisterEvent(element, eventKey);
2207
- if (throttleTimer) {
2208
- throttleTimer.dispose();
2209
- }
2210
- });
2211
- };
2212
- }
2213
- function createKeyHandler(defaultKey = "Enter") {
2214
- return (element, value) => {
2215
- const { actionName, payload: customPayload } = extractActionDetails(value);
2216
- if (!actionName) {
2217
- log3.warn(`onKey requires an action reference starting with @, got:`, value);
2218
- return;
2219
170
  }
2220
- const disposables = getElementDisposables2(element);
2221
- const eventKey = `keydown:${actionName}:${defaultKey}`;
2222
- if (getRegisteredEvents(element).has(eventKey)) {
2223
- return;
2224
- }
2225
- registerEvent(element, eventKey);
2226
- const targetKey = getKeyTarget(element) || defaultKey;
2227
- const keyToMatch = targetKey.toLowerCase() === "return" ? "Enter" : targetKey;
2228
- const listener = (event) => {
2229
- const keyEvent = event;
2230
- if (keyEvent.key !== keyToMatch) {
2231
- return;
2232
- }
2233
- event.preventDefault();
2234
- const target = event.target;
2235
- const payload = Object.keys(customPayload).length > 0 ? { ...customPayload } : {
2236
- type: event.type,
2237
- timestamp: Date.now(),
2238
- key: keyEvent.key,
2239
- code: keyEvent.code,
2240
- value: target.value,
2241
- input: target.value,
2242
- ctrlKey: keyEvent.ctrlKey,
2243
- shiftKey: keyEvent.shiftKey,
2244
- altKey: keyEvent.altKey,
2245
- metaKey: keyEvent.metaKey
2246
- };
2247
- const engine = getEngine(element);
2248
- if (engine) {
2249
- engine.dispatchAction(actionName, payload);
2250
- }
2251
- };
2252
- disposables.add(disposableListener(element, "keydown", listener));
2253
- disposables.addCallback(() => {
2254
- unregisterEvent(element, eventKey);
2255
- });
2256
- };
2257
- }
2258
- function createLongClickHandler(thresholdMs = 500) {
2259
- return (element, value) => {
2260
- const { actionName, payload: customPayload } = extractActionDetails(value);
2261
- if (!actionName) {
2262
- log3.warn(`onLongClick requires an action reference starting with @, got:`, value);
2263
- return;
2264
- }
2265
- const disposables = getElementDisposables2(element);
2266
- const eventKey = `longclick:${actionName}`;
2267
- if (getRegisteredEvents(element).has(eventKey)) {
2268
- return;
2269
- }
2270
- registerEvent(element, eventKey);
2271
- let longClickTimer = null;
2272
- const downListener = (event) => {
2273
- const pointerEvent = event;
2274
- longClickTimer = disposableTimeout(() => {
2275
- const payload = Object.keys(customPayload).length > 0 ? { ...customPayload } : {
2276
- type: "longclick",
2277
- timestamp: Date.now(),
2278
- clientX: pointerEvent.clientX,
2279
- clientY: pointerEvent.clientY
2280
- };
2281
- const engine = getEngine(element);
2282
- if (engine) {
2283
- engine.dispatchAction(actionName, payload);
2284
- }
2285
- longClickTimer = null;
2286
- }, thresholdMs);
2287
- };
2288
- const cancelListener = () => {
2289
- if (longClickTimer) {
2290
- longClickTimer.dispose();
2291
- longClickTimer = null;
2292
- }
2293
- };
2294
- disposables.add(disposableListener(element, "pointerdown", downListener));
2295
- disposables.add(disposableListener(element, "pointerup", cancelListener));
2296
- disposables.add(disposableListener(element, "pointerleave", cancelListener));
2297
- disposables.addCallback(() => {
2298
- unregisterEvent(element, eventKey);
2299
- cancelListener();
2300
- });
2301
- };
2302
- }
2303
- var inputPayload = (event, element) => {
2304
- const target = element;
2305
- return {
2306
- type: event.type,
2307
- timestamp: Date.now(),
2308
- value: target.value,
2309
- input: target.value
2310
- };
2311
- };
2312
- var scrollPayload = (_event, element) => {
2313
- const scrollTop = element.scrollTop;
2314
- const scrollHeight = element.scrollHeight;
2315
- const clientHeight = element.clientHeight;
2316
- const scrollPercentage = scrollHeight - clientHeight > 0 ? scrollTop / (scrollHeight - clientHeight) * 100 : 0;
2317
- const nearBottom = scrollHeight - scrollTop - clientHeight < 100 || scrollPercentage > 90;
2318
- return {
2319
- type: "scroll",
2320
- timestamp: Date.now(),
2321
- scrollTop,
2322
- scrollLeft: element.scrollLeft,
2323
- scrollHeight,
2324
- scrollWidth: element.scrollWidth,
2325
- clientHeight,
2326
- clientWidth: element.clientWidth,
2327
- scrollPercentage: Math.round(scrollPercentage),
2328
- nearBottom,
2329
- atBottom: scrollHeight - scrollTop === clientHeight,
2330
- atTop: scrollTop === 0
2331
- };
2332
- };
2333
- var focusPayload = (event, element) => ({
2334
- type: event.type,
2335
- timestamp: Date.now(),
2336
- value: element.value ?? undefined
2337
- });
2338
- var mousePayload = (event, _element) => {
2339
- const mouseEvent = event;
2340
- return {
2341
- type: event.type,
2342
- timestamp: Date.now(),
2343
- clientX: mouseEvent.clientX,
2344
- clientY: mouseEvent.clientY
2345
- };
2346
- };
2347
- var eventHandlers = {
2348
- onClick: createEventHandler("click"),
2349
- onPress: createEventHandler("click"),
2350
- onChange: createEventHandler("change"),
2351
- onSubmit: createEventHandler("submit", { preventDefault: true }),
2352
- onInput: createEventHandler("input", { extractPayload: inputPayload }),
2353
- onKey: createKeyHandler("Enter"),
2354
- "onKey.key": (element, value) => {
2355
- setKeyTarget(element, String(value));
2356
- },
2357
- "onKey.action": createKeyHandler("Enter"),
2358
- onScroll: createEventHandler("scroll", {
2359
- throttleMs: 100,
2360
- passive: true,
2361
- extractPayload: scrollPayload
2362
- }),
2363
- onLongClick: createLongClickHandler(500),
2364
- onLongPress: createLongClickHandler(500),
2365
- onFocus: createEventHandler("focus", { extractPayload: focusPayload }),
2366
- onBlur: createEventHandler("blur", { extractPayload: focusPayload }),
2367
- onMouseEnter: createEventHandler("mouseenter", { extractPayload: mousePayload }),
2368
- onMouseLeave: createEventHandler("mouseleave", { extractPayload: mousePayload }),
2369
- bind: (element, value) => {
2370
- const bindPath = typeof value === "string" ? value : null;
2371
- if (!bindPath)
2372
- return;
2373
- const disposables = getElementDisposables2(element);
2374
- const eventKey = `bind:${bindPath}`;
2375
- if (getRegisteredEvents(element).has(eventKey))
2376
- return;
2377
- registerEvent(element, eventKey);
2378
- const hypenType = element.dataset?.hypenType;
2379
- if (hypenType === "checkbox" || hypenType === "switch") {
2380
- const input = element.querySelector('input[type="checkbox"]');
2381
- if (!input)
2382
- return;
2383
- const listener = () => {
2384
- const engine = getEngine(element);
2385
- if (engine) {
2386
- engine.dispatchAction("__hypen_bind", {
2387
- path: bindPath,
2388
- value: input.checked
2389
- });
171
+ /**
172
+ * Set a property on an element
173
+ */
174
+ onSetProp(id, name, value) {
175
+ const element = this.nodes.get(id);
176
+ if (!element)
177
+ return;
178
+ this.debugTracker.trackRerender(id, element, `setProp:${name}`);
179
+ if (name === "0" || name === "text") {
180
+ const elementType = element.dataset.hypenType;
181
+ if (elementType === "input") {
182
+ const inputEl = element;
183
+ inputEl.value = String(value);
184
+ log.debug(`Updated input value: "${value}"`);
185
+ return;
186
+ }
187
+ const nextText = String(value);
188
+ element.textContent = nextText;
189
+ // Preserve the original template when it contains state interpolation.
190
+ // Engine patches may send interpolated strings; if we overwrite the template,
191
+ // future state updates won't be able to re-interpolate.
192
+ const currentTemplate = element.dataset.textTemplate;
193
+ const nextLooksLikeTemplate = nextText.includes("${");
194
+ const currentLooksLikeTemplate = typeof currentTemplate === "string" && currentTemplate.includes("${");
195
+ if (nextLooksLikeTemplate) {
196
+ element.dataset.textTemplate = nextText;
197
+ }
198
+ else if (currentTemplate === undefined) {
199
+ // No template stored yet; treat this as the template.
200
+ element.dataset.textTemplate = nextText;
201
+ }
202
+ else if (!currentLooksLikeTemplate) {
203
+ // If current template isn't a template, keep it in sync.
204
+ element.dataset.textTemplate = nextText;
205
+ }
206
+ log.debug(`Updated text content: "${value}"`);
207
+ return;
2390
208
  }
2391
- };
2392
- disposables.add(disposableListener(input, "change", listener, { passive: true }));
2393
- } else if (element instanceof HTMLSelectElement) {
2394
- const listener = () => {
2395
- const engine = getEngine(element);
2396
- if (engine) {
2397
- engine.dispatchAction("__hypen_bind", {
2398
- path: bindPath,
2399
- value: element.value
2400
- });
209
+ this.applicators.apply(element, name, value);
210
+ }
211
+ /**
212
+ * Remove a property from an element
213
+ */
214
+ onRemoveProp(id, name) {
215
+ const element = this.nodes.get(id);
216
+ if (!element)
217
+ return;
218
+ this.debugTracker.trackRerender(id, element, `removeProp:${name}`);
219
+ this.applicators.apply(element, name, undefined);
220
+ }
221
+ /**
222
+ * Set text content
223
+ */
224
+ onSetText(id, text) {
225
+ const element = this.nodes.get(id);
226
+ if (!element)
227
+ return;
228
+ this.debugTracker.trackRerender(id, element, "setText");
229
+ element.textContent = text;
230
+ }
231
+ /**
232
+ * Insert an element into the tree
233
+ */
234
+ onInsert(parentId, id, beforeId) {
235
+ const parent = parentId === "root" ? this.container : this.nodes.get(parentId);
236
+ const child = this.nodes.get(id);
237
+ log.debug(`Inserting ${id} into ${parentId}`, {
238
+ parent: parent ? `${parent.tagName}#${parent.id || 'no-id'}` : 'null',
239
+ child: child ? `${child.tagName}#${child.id || 'no-id'}` : 'null',
240
+ childText: child?.textContent?.substring(0, 20)
241
+ });
242
+ if (!parent || !child)
243
+ return;
244
+ if (parentId === "root") {
245
+ this.rootId = id;
2401
246
  }
2402
- };
2403
- disposables.add(disposableListener(element, "change", listener, { passive: true }));
2404
- } else if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
2405
- const listener = () => {
2406
- const engine = getEngine(element);
2407
- if (engine) {
2408
- engine.dispatchAction("__hypen_bind", {
2409
- path: bindPath,
2410
- value: element.value
2411
- });
247
+ if (beforeId) {
248
+ const before = this.nodes.get(beforeId);
249
+ if (before && before.parentNode === parent) {
250
+ parent.insertBefore(child, before);
251
+ }
252
+ else if (!parent.contains(child)) {
253
+ parent.appendChild(child);
254
+ }
2412
255
  }
2413
- };
2414
- disposables.add(disposableListener(element, "input", listener, { passive: true }));
2415
- } else {
2416
- log3.warn(`.bind() is not supported on element type "${element.dataset?.hypenType || element.tagName}". ` + `Supported types: input, textarea, checkbox, switch, select.`);
2417
- unregisterEvent(element, eventKey);
2418
- return;
2419
- }
2420
- disposables.addCallback(() => unregisterEvent(element, eventKey));
2421
- }
2422
- };
2423
-
2424
- // src/dom/applicators/typography.ts
2425
- var typographyHandlers = {
2426
- textAlign: (el, value) => {
2427
- el.style.textAlign = String(value);
2428
- },
2429
- textTransform: (el, value) => {
2430
- el.style.textTransform = String(value);
2431
- },
2432
- textDecoration: (el, value) => {
2433
- el.style.textDecoration = String(value);
2434
- },
2435
- textDecorationColor: (el, value) => {
2436
- el.style.textDecorationColor = String(value);
2437
- },
2438
- textDecorationStyle: (el, value) => {
2439
- el.style.textDecorationStyle = String(value);
2440
- },
2441
- textDecorationThickness: (el, value) => {
2442
- el.style.textDecorationThickness = typeof value === "number" ? `${value}px` : String(value);
2443
- },
2444
- letterSpacing: (el, value) => {
2445
- el.style.letterSpacing = typeof value === "number" ? `${value}px` : String(value);
2446
- },
2447
- wordSpacing: (el, value) => {
2448
- el.style.wordSpacing = typeof value === "number" ? `${value}px` : String(value);
2449
- },
2450
- lineHeight: (el, value) => {
2451
- el.style.lineHeight = String(value);
2452
- },
2453
- textIndent: (el, value) => {
2454
- el.style.textIndent = typeof value === "number" ? `${value}px` : String(value);
2455
- },
2456
- textOverflow: (el, value) => {
2457
- el.style.textOverflow = String(value);
2458
- },
2459
- whiteSpace: (el, value) => {
2460
- el.style.whiteSpace = String(value);
2461
- },
2462
- wordBreak: (el, value) => {
2463
- el.style.wordBreak = String(value);
2464
- },
2465
- verticalAlign: (el, value) => {
2466
- el.style.verticalAlign = String(value);
2467
- },
2468
- fontVariant: (el, value) => {
2469
- el.style.fontVariant = String(value);
2470
- },
2471
- fontStretch: (el, value) => {
2472
- el.style.fontStretch = String(value);
2473
- },
2474
- fontStyle: (el, value) => {
2475
- el.style.fontStyle = String(value);
2476
- },
2477
- writingMode: (el, value) => {
2478
- el.style.writingMode = String(value);
2479
- },
2480
- maxLines: (el, value) => {
2481
- const lines = typeof value === "number" ? value : parseInt(String(value), 10);
2482
- if (!isNaN(lines) && lines > 0) {
2483
- el.style.display = "-webkit-box";
2484
- el.style.setProperty("-webkit-line-clamp", String(lines));
2485
- el.style.setProperty("-webkit-box-orient", "vertical");
2486
- el.style.overflow = "hidden";
2487
- }
2488
- }
2489
- };
2490
-
2491
- // src/dom/applicators/transform.ts
2492
- var transformHandlers = {
2493
- transform: (el, value) => {
2494
- el.style.transform = String(value);
2495
- },
2496
- transformOrigin: (el, value) => {
2497
- el.style.transformOrigin = String(value);
2498
- },
2499
- translateX: (el, value) => {
2500
- const current = el.style.transform || "";
2501
- const val = typeof value === "number" ? `${value}px` : String(value);
2502
- el.style.transform = current ? `${current} translateX(${val})` : `translateX(${val})`;
2503
- },
2504
- translateY: (el, value) => {
2505
- const current = el.style.transform || "";
2506
- const val = typeof value === "number" ? `${value}px` : String(value);
2507
- el.style.transform = current ? `${current} translateY(${val})` : `translateY(${val})`;
2508
- },
2509
- translateZ: (el, value) => {
2510
- const current = el.style.transform || "";
2511
- const val = typeof value === "number" ? `${value}px` : String(value);
2512
- el.style.transform = current ? `${current} translateZ(${val})` : `translateZ(${val})`;
2513
- },
2514
- rotate: (el, value) => {
2515
- const current = el.style.transform || "";
2516
- const val = String(value);
2517
- el.style.transform = current ? `${current} rotate(${val})` : `rotate(${val})`;
2518
- },
2519
- rotateX: (el, value) => {
2520
- const current = el.style.transform || "";
2521
- const val = String(value);
2522
- el.style.transform = current ? `${current} rotateX(${val})` : `rotateX(${val})`;
2523
- },
2524
- rotateY: (el, value) => {
2525
- const current = el.style.transform || "";
2526
- const val = String(value);
2527
- el.style.transform = current ? `${current} rotateY(${val})` : `rotateY(${val})`;
2528
- },
2529
- rotateZ: (el, value) => {
2530
- const current = el.style.transform || "";
2531
- const val = String(value);
2532
- el.style.transform = current ? `${current} rotateZ(${val})` : `rotateZ(${val})`;
2533
- },
2534
- scale: (el, value) => {
2535
- const current = el.style.transform || "";
2536
- el.style.transform = current ? `${current} scale(${value})` : `scale(${value})`;
2537
- },
2538
- scaleX: (el, value) => {
2539
- const current = el.style.transform || "";
2540
- el.style.transform = current ? `${current} scaleX(${value})` : `scaleX(${value})`;
2541
- },
2542
- scaleY: (el, value) => {
2543
- const current = el.style.transform || "";
2544
- el.style.transform = current ? `${current} scaleY(${value})` : `scaleY(${value})`;
2545
- },
2546
- skew: (el, value) => {
2547
- const current = el.style.transform || "";
2548
- el.style.transform = current ? `${current} skew(${value})` : `skew(${value})`;
2549
- },
2550
- skewX: (el, value) => {
2551
- const current = el.style.transform || "";
2552
- el.style.transform = current ? `${current} skewX(${value})` : `skewX(${value})`;
2553
- },
2554
- skewY: (el, value) => {
2555
- const current = el.style.transform || "";
2556
- el.style.transform = current ? `${current} skewY(${value})` : `skewY(${value})`;
2557
- },
2558
- perspective: (el, value) => {
2559
- el.style.perspective = typeof value === "number" ? `${value}px` : String(value);
2560
- }
2561
- };
2562
-
2563
- // src/dom/applicators/effects.ts
2564
- var effectsHandlers = {
2565
- boxShadow: (el, value) => {
2566
- if (typeof value === "string") {
2567
- el.style.boxShadow = value;
2568
- } else if (typeof value === "object" && value !== null) {
2569
- const obj = value;
2570
- const x = typeof obj.x === "number" ? `${obj.x}px` : obj.x ?? obj.offsetX ?? "0px";
2571
- const y = typeof obj.y === "number" ? `${obj.y}px` : obj.y ?? obj.offsetY ?? "0px";
2572
- const blur = typeof obj.blur === "number" ? `${obj.blur}px` : obj.blur ?? obj.radius ?? "0px";
2573
- const spread = typeof obj.spread === "number" ? `${obj.spread}px` : obj.spread ?? "0px";
2574
- const color = obj.color ?? "rgba(0,0,0,0.2)";
2575
- const inset = obj.inset ? "inset " : "";
2576
- el.style.boxShadow = `${inset}${x} ${y} ${blur} ${spread} ${color}`;
2577
- } else if (typeof value === "number") {
2578
- el.style.boxShadow = `0 ${value}px ${value * 2}px rgba(0,0,0,0.2)`;
2579
- }
2580
- },
2581
- shadow: (el, value) => {
2582
- if (typeof value === "object" && value !== null) {
2583
- const obj = value;
2584
- const x = typeof obj.x === "number" ? `${obj.x}px` : obj.x ?? obj.offsetX ?? "0px";
2585
- const y = typeof obj.y === "number" ? `${obj.y}px` : obj.y ?? obj.offsetY ?? "0px";
2586
- const blur = typeof obj.blur === "number" ? `${obj.blur}px` : obj.blur ?? obj.radius ?? "4px";
2587
- const color = obj.color ?? "rgba(0,0,0,0.2)";
2588
- el.style.boxShadow = `${x} ${y} ${blur} ${color}`;
2589
- } else if (typeof value === "number") {
2590
- el.style.boxShadow = `0 ${value}px ${value * 2}px rgba(0,0,0,0.2)`;
2591
- } else {
2592
- el.style.boxShadow = String(value);
2593
- }
2594
- },
2595
- elevation: (el, value) => {
2596
- const level = typeof value === "number" ? value : parseInt(String(value), 10);
2597
- if (!isNaN(level) && level >= 0) {
2598
- const y = level * 0.5;
2599
- const blur = level * 1.5;
2600
- const opacity = Math.min(0.1 + level * 0.02, 0.4);
2601
- el.style.boxShadow = `0 ${y}px ${blur}px rgba(0,0,0,${opacity})`;
2602
- }
2603
- },
2604
- textShadow: (el, value) => {
2605
- el.style.textShadow = String(value);
2606
- },
2607
- filter: (el, value) => {
2608
- el.style.filter = String(value);
2609
- },
2610
- backdropFilter: (el, value) => {
2611
- el.style.backdropFilter = String(value);
2612
- },
2613
- blur: (el, value) => {
2614
- const val = typeof value === "number" ? `${value}px` : String(value);
2615
- const current = el.style.filter || "";
2616
- el.style.filter = current ? `${current} blur(${val})` : `blur(${val})`;
2617
- },
2618
- brightness: (el, value) => {
2619
- const current = el.style.filter || "";
2620
- el.style.filter = current ? `${current} brightness(${value})` : `brightness(${value})`;
2621
- },
2622
- contrast: (el, value) => {
2623
- const current = el.style.filter || "";
2624
- el.style.filter = current ? `${current} contrast(${value})` : `contrast(${value})`;
2625
- },
2626
- grayscale: (el, value) => {
2627
- const current = el.style.filter || "";
2628
- el.style.filter = current ? `${current} grayscale(${value})` : `grayscale(${value})`;
2629
- },
2630
- hueRotate: (el, value) => {
2631
- const val = String(value);
2632
- const current = el.style.filter || "";
2633
- el.style.filter = current ? `${current} hue-rotate(${val})` : `hue-rotate(${val})`;
2634
- },
2635
- invert: (el, value) => {
2636
- const current = el.style.filter || "";
2637
- el.style.filter = current ? `${current} invert(${value})` : `invert(${value})`;
2638
- },
2639
- saturate: (el, value) => {
2640
- const current = el.style.filter || "";
2641
- el.style.filter = current ? `${current} saturate(${value})` : `saturate(${value})`;
2642
- },
2643
- sepia: (el, value) => {
2644
- const current = el.style.filter || "";
2645
- el.style.filter = current ? `${current} sepia(${value})` : `sepia(${value})`;
2646
- },
2647
- dropShadow: (el, value) => {
2648
- const current = el.style.filter || "";
2649
- el.style.filter = current ? `${current} drop-shadow(${value})` : `drop-shadow(${value})`;
2650
- },
2651
- mixBlendMode: (el, value) => {
2652
- el.style.mixBlendMode = String(value);
2653
- },
2654
- backgroundBlendMode: (el, value) => {
2655
- el.style.backgroundBlendMode = String(value);
2656
- },
2657
- clipPath: (el, value) => {
2658
- el.style.clipPath = String(value);
2659
- },
2660
- mask: (el, value) => {
2661
- el.style.mask = String(value);
2662
- },
2663
- maskImage: (el, value) => {
2664
- el.style.maskImage = String(value);
2665
- }
2666
- };
2667
-
2668
- // src/dom/applicators/advanced-layout.ts
2669
- var advancedLayoutHandlers = {
2670
- flexDirection: (el, value) => {
2671
- el.style.flexDirection = String(value);
2672
- },
2673
- flexWrap: (el, value) => {
2674
- el.style.flexWrap = String(value);
2675
- },
2676
- flexBasis: (el, value) => {
2677
- el.style.flexBasis = typeof value === "number" ? `${value}px` : String(value);
2678
- },
2679
- alignContent: (el, value) => {
2680
- el.style.alignContent = String(value);
2681
- },
2682
- order: (el, value) => {
2683
- el.style.order = String(value);
2684
- },
2685
- gridTemplateColumns: (el, value) => {
2686
- el.style.gridTemplateColumns = String(value);
2687
- },
2688
- gridTemplateRows: (el, value) => {
2689
- el.style.gridTemplateRows = String(value);
2690
- },
2691
- gridColumns: (el, value) => {
2692
- if (typeof value === "number") {
2693
- el.style.gridTemplateColumns = `repeat(${value}, 1fr)`;
2694
- } else {
2695
- el.style.gridTemplateColumns = String(value);
2696
- }
2697
- },
2698
- gridRows: (el, value) => {
2699
- if (typeof value === "number") {
2700
- el.style.gridTemplateRows = `repeat(${value}, 1fr)`;
2701
- } else {
2702
- el.style.gridTemplateRows = String(value);
2703
- }
2704
- },
2705
- gridTemplateAreas: (el, value) => {
2706
- el.style.gridTemplateAreas = String(value);
2707
- },
2708
- gridColumn: (el, value) => {
2709
- el.style.gridColumn = String(value);
2710
- },
2711
- gridRow: (el, value) => {
2712
- el.style.gridRow = String(value);
2713
- },
2714
- gridArea: (el, value) => {
2715
- el.style.gridArea = String(value);
2716
- },
2717
- gridAutoFlow: (el, value) => {
2718
- el.style.gridAutoFlow = String(value);
2719
- },
2720
- gridAutoColumns: (el, value) => {
2721
- el.style.gridAutoColumns = String(value);
2722
- },
2723
- gridAutoRows: (el, value) => {
2724
- el.style.gridAutoRows = String(value);
2725
- },
2726
- rowGap: (el, value) => {
2727
- el.style.rowGap = typeof value === "number" ? `${value}px` : String(value);
2728
- },
2729
- columnGap: (el, value) => {
2730
- el.style.columnGap = typeof value === "number" ? `${value}px` : String(value);
2731
- },
2732
- placeItems: (el, value) => {
2733
- el.style.placeItems = String(value);
2734
- },
2735
- placeContent: (el, value) => {
2736
- el.style.placeContent = String(value);
2737
- },
2738
- placeSelf: (el, value) => {
2739
- el.style.placeSelf = String(value);
2740
- },
2741
- position: (el, value) => {
2742
- el.style.position = String(value);
2743
- },
2744
- top: (el, value) => {
2745
- el.style.top = typeof value === "number" ? `${value}px` : String(value);
2746
- },
2747
- right: (el, value) => {
2748
- el.style.right = typeof value === "number" ? `${value}px` : String(value);
2749
- },
2750
- bottom: (el, value) => {
2751
- el.style.bottom = typeof value === "number" ? `${value}px` : String(value);
2752
- },
2753
- left: (el, value) => {
2754
- el.style.left = typeof value === "number" ? `${value}px` : String(value);
2755
- },
2756
- inset: (el, value) => {
2757
- el.style.inset = typeof value === "number" ? `${value}px` : String(value);
2758
- },
2759
- zIndex: (el, value) => {
2760
- el.style.zIndex = String(value);
2761
- }
2762
- };
2763
-
2764
- // src/dom/applicators/background.ts
2765
- var backgroundHandlers = {
2766
- backgroundImage: (el, value) => {
2767
- el.style.backgroundImage = String(value);
2768
- },
2769
- backgroundSize: (el, value) => {
2770
- el.style.backgroundSize = String(value);
2771
- },
2772
- backgroundPosition: (el, value) => {
2773
- el.style.backgroundPosition = String(value);
2774
- },
2775
- backgroundRepeat: (el, value) => {
2776
- el.style.backgroundRepeat = String(value);
2777
- },
2778
- backgroundAttachment: (el, value) => {
2779
- el.style.backgroundAttachment = String(value);
2780
- },
2781
- backgroundClip: (el, value) => {
2782
- el.style.backgroundClip = String(value);
2783
- },
2784
- backgroundOrigin: (el, value) => {
2785
- el.style.backgroundOrigin = String(value);
2786
- },
2787
- linearGradient: (el, value) => {
2788
- el.style.backgroundImage = `linear-gradient(${value})`;
2789
- },
2790
- radialGradient: (el, value) => {
2791
- el.style.backgroundImage = `radial-gradient(${value})`;
2792
- },
2793
- conicGradient: (el, value) => {
2794
- el.style.backgroundImage = `conic-gradient(${value})`;
2795
- }
2796
- };
2797
-
2798
- // src/dom/applicators/display.ts
2799
- var displayHandlers = {
2800
- display: (el, value) => {
2801
- el.style.display = String(value);
2802
- },
2803
- visibility: (el, value) => {
2804
- el.style.visibility = String(value);
2805
- },
2806
- overflowX: (el, value) => {
2807
- el.style.overflowX = String(value);
2808
- },
2809
- overflowY: (el, value) => {
2810
- el.style.overflowY = String(value);
2811
- },
2812
- pointerEvents: (el, value) => {
2813
- el.style.pointerEvents = String(value);
2814
- },
2815
- userSelect: (el, value) => {
2816
- el.style.userSelect = String(value);
2817
- },
2818
- resize: (el, value) => {
2819
- el.style.resize = String(value);
2820
- },
2821
- boxSizing: (el, value) => {
2822
- el.style.boxSizing = String(value);
2823
- },
2824
- aspectRatio: (el, value) => {
2825
- el.style.aspectRatio = String(value);
2826
- },
2827
- objectFit: (el, value) => {
2828
- el.style.objectFit = String(value);
2829
- },
2830
- objectPosition: (el, value) => {
2831
- el.style.objectPosition = String(value);
2832
- }
2833
- };
2834
-
2835
- // src/dom/applicators/transition.ts
2836
- var transitionHandlers = {
2837
- transition: (el, value) => {
2838
- el.style.transition = String(value);
2839
- },
2840
- transitionProperty: (el, value) => {
2841
- el.style.transitionProperty = String(value);
2842
- },
2843
- transitionDuration: (el, value) => {
2844
- el.style.transitionDuration = String(value);
2845
- },
2846
- transitionTimingFunction: (el, value) => {
2847
- el.style.transitionTimingFunction = String(value);
2848
- },
2849
- transitionDelay: (el, value) => {
2850
- el.style.transitionDelay = String(value);
2851
- },
2852
- animation: (el, value) => {
2853
- el.style.animation = String(value);
2854
- },
2855
- animationName: (el, value) => {
2856
- el.style.animationName = String(value);
2857
- },
2858
- animationDuration: (el, value) => {
2859
- el.style.animationDuration = String(value);
2860
- },
2861
- animationTimingFunction: (el, value) => {
2862
- el.style.animationTimingFunction = String(value);
2863
- },
2864
- animationDelay: (el, value) => {
2865
- el.style.animationDelay = String(value);
2866
- },
2867
- animationIterationCount: (el, value) => {
2868
- el.style.animationIterationCount = String(value);
2869
- },
2870
- animationDirection: (el, value) => {
2871
- el.style.animationDirection = String(value);
2872
- },
2873
- animationFillMode: (el, value) => {
2874
- el.style.animationFillMode = String(value);
2875
- },
2876
- animationPlayState: (el, value) => {
2877
- el.style.animationPlayState = String(value);
2878
- }
2879
- };
2880
-
2881
- // src/dom/applicators/index.ts
2882
- var BREAKPOINTS = {
2883
- sm: "640px",
2884
- md: "768px",
2885
- lg: "1024px",
2886
- xl: "1280px",
2887
- "2xl": "1536px"
2888
- };
2889
- var variantStyleSheet = null;
2890
- var insertedRules = new Set;
2891
- function getVariantStyleSheet() {
2892
- if (!variantStyleSheet) {
2893
- const style = document.createElement("style");
2894
- style.id = "hypen-variants";
2895
- document.head.appendChild(style);
2896
- variantStyleSheet = style.sheet;
2897
- }
2898
- return variantStyleSheet;
2899
- }
2900
- function hashValue(value) {
2901
- return String(value).replace(/[^a-zA-Z0-9]/g, "").slice(0, 8);
2902
- }
2903
-
2904
- class ApplicatorRegistry {
2905
- handlers = new Map;
2906
- elementState = new WeakMap;
2907
- constructor() {
2908
- this.registerDefaults();
2909
- }
2910
- register(name, handler) {
2911
- this.handlers.set(name, handler);
2912
- }
2913
- apply(element, name, value) {
2914
- const { handlerName, argKey, aggregate, fallbackName } = this.parseApplicatorName(name);
2915
- const handler = this.handlers.get(handlerName);
2916
- const state = this.getElementState(element);
2917
- const previous = state.get(handlerName);
2918
- if (aggregate && argKey !== null) {
2919
- const merged = this.mergeAggregateState(previous, argKey, this.normalizeValue(value));
2920
- state.set(handlerName, merged);
2921
- if (handler) {
2922
- handler(element, merged);
2923
- } else {
2924
- this.setStyleProperty(element, fallbackName, value);
2925
- }
2926
- return;
2927
- }
2928
- if (handler) {
2929
- if (this.isEventApplicator(handlerName)) {
2930
- const normalizedValue = this.normalizeEventValue(previous, value);
2931
- state.set(handlerName, normalizedValue);
2932
- handler(element, normalizedValue);
2933
- } else {
2934
- state.set(handlerName, value);
2935
- handler(element, value);
2936
- }
2937
- } else {
2938
- this.setStyleProperty(element, handlerName, value);
2939
- }
2940
- }
2941
- parseApplicatorName(name) {
2942
- const dotIndex = name.indexOf(".");
2943
- if (dotIndex === -1) {
2944
- return { handlerName: name, argKey: null, aggregate: false, fallbackName: name };
2945
- }
2946
- const baseName = name.substring(0, dotIndex);
2947
- const argKey = name.substring(dotIndex + 1);
2948
- if (this.handlers.has(baseName) && this.isEventApplicator(baseName)) {
2949
- return { handlerName: baseName, argKey, aggregate: true, fallbackName: name };
2950
- }
2951
- if (/^\d+$/.test(argKey)) {
2952
- return { handlerName: baseName, argKey: null, aggregate: false, fallbackName: baseName };
2953
- }
2954
- return { handlerName: name, argKey: null, aggregate: false, fallbackName: name };
2955
- }
2956
- getElementState(element) {
2957
- let state = this.elementState.get(element);
2958
- if (!state) {
2959
- state = new Map;
2960
- this.elementState.set(element, state);
2961
- }
2962
- return state;
2963
- }
2964
- mergeAggregateState(previous, argKey, value) {
2965
- const base = this.cloneAggregateState(previous);
2966
- if (value === undefined) {
2967
- delete base[argKey];
2968
- } else {
2969
- base[argKey] = value;
2970
- }
2971
- return base;
2972
- }
2973
- cloneAggregateState(previous) {
2974
- if (previous && typeof previous === "object" && !Array.isArray(previous)) {
2975
- return { ...previous };
2976
- }
2977
- if (typeof previous === "string") {
2978
- return { "0": previous };
2979
- }
2980
- return {};
2981
- }
2982
- normalizeEventValue(previous, value) {
2983
- const normalizedInput = this.normalizeValue(value);
2984
- const base = this.cloneAggregateState(previous);
2985
- if (normalizedInput && typeof normalizedInput === "object" && !Array.isArray(normalizedInput)) {
2986
- const next = { ...base, ...normalizedInput };
2987
- if (!Object.prototype.hasOwnProperty.call(next, "0") && base["0"] !== undefined) {
2988
- next["0"] = base["0"];
2989
- }
2990
- return next;
2991
- }
2992
- if (normalizedInput !== undefined) {
2993
- base["0"] = normalizedInput;
2994
- }
2995
- return base;
2996
- }
2997
- normalizeValue(value) {
2998
- if (value instanceof Map) {
2999
- const obj = {};
3000
- for (const [key, val] of value.entries()) {
3001
- obj[key] = this.normalizeValue(val);
3002
- }
3003
- return obj;
3004
- }
3005
- if (Array.isArray(value)) {
3006
- return value.map((item) => this.normalizeValue(item));
3007
- }
3008
- if (value && typeof value === "object") {
3009
- const obj = {};
3010
- for (const [key, val] of Object.entries(value)) {
3011
- obj[key] = this.normalizeValue(val);
3012
- }
3013
- return obj;
3014
- }
3015
- return value;
3016
- }
3017
- isEventApplicator(name) {
3018
- return /^on[A-Z]/.test(name);
3019
- }
3020
- applyAll(element, applicators) {
3021
- const grouped = new Map;
3022
- for (const [name, value] of Object.entries(applicators)) {
3023
- const dotIndex = name.indexOf(".");
3024
- const baseName = dotIndex !== -1 ? name.substring(0, dotIndex) : name;
3025
- const argKey = dotIndex !== -1 ? name.substring(dotIndex + 1) : null;
3026
- if (!grouped.has(baseName)) {
3027
- grouped.set(baseName, {});
3028
- }
3029
- const args = grouped.get(baseName);
3030
- if (argKey !== null) {
3031
- args[argKey] = value;
3032
- } else {
3033
- args["__value"] = value;
3034
- }
3035
- }
3036
- for (const [baseName, args] of grouped.entries()) {
3037
- if (Object.keys(args).length === 1) {
3038
- if ("__value" in args) {
3039
- this.apply(element, baseName, args["__value"]);
3040
- } else if ("0" in args) {
3041
- this.apply(element, baseName, args["0"]);
3042
- } else {
3043
- this.apply(element, baseName, args);
256
+ else {
257
+ if (!parent.contains(child)) {
258
+ parent.appendChild(child);
259
+ }
3044
260
  }
3045
- } else {
3046
- this.apply(element, baseName, args);
3047
- }
3048
261
  }
3049
- }
3050
- setStyleProperty(element, name, value) {
3051
- const atIndex = name.indexOf("@");
3052
- const colonIndex = name.indexOf(":");
3053
- if (atIndex !== -1) {
3054
- const prop = name.slice(0, atIndex);
3055
- const breakpoint = name.slice(atIndex + 1);
3056
- const minWidth = BREAKPOINTS[breakpoint];
3057
- if (minWidth) {
3058
- const cssName2 = this.toKebabCase(prop);
3059
- const cssValue = this.formatCssValue(cssName2, value);
3060
- const className = `hypen-${cssName2.replace(/[^a-zA-Z0-9-]/g, "")}-${breakpoint}-${hashValue(value)}`;
3061
- const ruleKey = `${className}:${cssValue}`;
3062
- if (!insertedRules.has(ruleKey)) {
3063
- const sheet = getVariantStyleSheet();
3064
- sheet.insertRule(`@media (min-width: ${minWidth}) { .${className} { ${cssName2}: ${cssValue}; } }`, sheet.cssRules.length);
3065
- insertedRules.add(ruleKey);
3066
- }
3067
- element.classList.add(className);
3068
- }
3069
- return;
262
+ /**
263
+ * Move an element within the tree
264
+ */
265
+ onMove(parentId, id, beforeId) {
266
+ this.onInsert(parentId, id, beforeId);
3070
267
  }
3071
- if (colonIndex !== -1) {
3072
- const prop = name.slice(0, colonIndex);
3073
- const state = name.slice(colonIndex + 1);
3074
- const validStates = ["hover", "focus", "active", "disabled", "focus-visible", "focus-within"];
3075
- if (validStates.includes(state)) {
3076
- const cssName2 = this.toKebabCase(prop);
3077
- const cssValue = this.formatCssValue(cssName2, value);
3078
- const className = `hypen-${cssName2.replace(/[^a-zA-Z0-9-]/g, "")}-${state}-${hashValue(value)}`;
3079
- const ruleKey = `${className}:${cssValue}`;
3080
- if (!insertedRules.has(ruleKey)) {
3081
- const sheet = getVariantStyleSheet();
3082
- sheet.insertRule(`.${className}:${state} { ${cssName2}: ${cssValue}; }`, sheet.cssRules.length);
3083
- insertedRules.add(ruleKey);
268
+ /**
269
+ * Remove an element from the tree
270
+ */
271
+ onRemove(id) {
272
+ const element = this.nodes.get(id);
273
+ if (!element)
274
+ return;
275
+ // Dispose event listeners and other resources before removing from DOM
276
+ disposeHypenElement(element);
277
+ if (element.parentNode) {
278
+ element.parentNode.removeChild(element);
3084
279
  }
3085
- element.classList.add(className);
3086
- }
3087
- return;
3088
- }
3089
- const cssName = this.toKebabCase(name);
3090
- element.style.setProperty(cssName, this.formatCssValue(cssName, value));
3091
- }
3092
- toKebabCase(name) {
3093
- return name.replace(/([A-Z])/g, "-$1").toLowerCase();
3094
- }
3095
- formatCssValue(cssName, value) {
3096
- if (typeof value === "number" && this.needsUnit(cssName)) {
3097
- return `${value}px`;
3098
- }
3099
- return String(value);
3100
- }
3101
- needsUnit(prop) {
3102
- const unitless = [
3103
- "opacity",
3104
- "z-index",
3105
- "font-weight",
3106
- "line-height",
3107
- "flex",
3108
- "flex-grow",
3109
- "flex-shrink",
3110
- "order"
3111
- ];
3112
- return !unitless.includes(prop);
3113
- }
3114
- registerDefaults() {
3115
- this.register("padding", paddingHandler);
3116
- this.register("paddingTop", paddingTopHandler);
3117
- this.register("paddingBottom", paddingBottomHandler);
3118
- this.register("paddingLeft", paddingLeftHandler);
3119
- this.register("paddingRight", paddingRightHandler);
3120
- this.register("paddingHorizontal", paddingHorizontalHandler);
3121
- this.register("paddingVertical", paddingVerticalHandler);
3122
- this.register("margin", marginHandler);
3123
- this.register("marginTop", marginTopHandler);
3124
- this.register("marginBottom", marginBottomHandler);
3125
- this.register("marginLeft", marginLeftHandler);
3126
- this.register("marginRight", marginRightHandler);
3127
- this.register("marginHorizontal", marginHorizontalHandler);
3128
- this.register("marginVertical", marginVerticalHandler);
3129
- for (const [name, handler] of Object.entries(colorHandlers)) {
3130
- this.register(name, handler);
3131
- }
3132
- for (const [name, handler] of Object.entries(borderHandlers)) {
3133
- this.register(name, handler);
3134
- }
3135
- for (const [name, handler] of Object.entries(sizeHandlers)) {
3136
- this.register(name, handler);
3137
- }
3138
- for (const [name, handler] of Object.entries(fontHandlers)) {
3139
- this.register(name, handler);
3140
- }
3141
- for (const [name, handler] of Object.entries(layoutHandlers)) {
3142
- this.register(name, handler);
3143
- }
3144
- for (const [name, handler] of Object.entries(eventHandlers)) {
3145
- this.register(name, handler);
3146
- }
3147
- for (const [name, handler] of Object.entries(typographyHandlers)) {
3148
- this.register(name, handler);
3149
- }
3150
- for (const [name, handler] of Object.entries(transformHandlers)) {
3151
- this.register(name, handler);
3152
- }
3153
- for (const [name, handler] of Object.entries(effectsHandlers)) {
3154
- this.register(name, handler);
3155
- }
3156
- for (const [name, handler] of Object.entries(advancedLayoutHandlers)) {
3157
- this.register(name, handler);
3158
- }
3159
- for (const [name, handler] of Object.entries(backgroundHandlers)) {
3160
- this.register(name, handler);
3161
- }
3162
- for (const [name, handler] of Object.entries(displayHandlers)) {
3163
- this.register(name, handler);
3164
- }
3165
- for (const [name, handler] of Object.entries(transitionHandlers)) {
3166
- this.register(name, handler);
3167
- }
3168
- }
3169
- }
3170
-
3171
- // src/dom/canvas/index.ts
3172
- var canvasHandler = {
3173
- create() {
3174
- const el = document.createElement("canvas");
3175
- el.dataset.hypenType = "canvas";
3176
- return el;
3177
- },
3178
- applyProps(el, props) {
3179
- const canvas = el;
3180
- if (props.width !== undefined) {
3181
- canvas.width = Number(props.width);
3182
- }
3183
- if (props.height !== undefined) {
3184
- canvas.height = Number(props.height);
3185
- }
3186
- }
3187
- };
3188
- var canvasApplicators = {
3189
- fillStyle: (el, value) => {
3190
- const canvas = el;
3191
- const ctx = canvas.getContext("2d");
3192
- if (ctx) {
3193
- ctx.fillStyle = String(value);
3194
- }
3195
- },
3196
- strokeStyle: (el, value) => {
3197
- const canvas = el;
3198
- const ctx = canvas.getContext("2d");
3199
- if (ctx) {
3200
- ctx.strokeStyle = String(value);
3201
- }
3202
- },
3203
- lineWidth: (el, value) => {
3204
- const canvas = el;
3205
- const ctx = canvas.getContext("2d");
3206
- if (ctx) {
3207
- ctx.lineWidth = Number(value);
3208
- }
3209
- }
3210
- };
3211
-
3212
- // src/dom/debug.ts
3213
- import { frameworkLoggers as frameworkLoggers4 } from "@hypen-space/core/logger";
3214
- var log4 = frameworkLoggers4.debug;
3215
- var defaultDebugConfig = {
3216
- enabled: false,
3217
- showHeatmap: true,
3218
- heatmapIncrement: 5,
3219
- maxOpacity: 0.8,
3220
- fadeOutDuration: 2000
3221
- };
3222
-
3223
- class RerenderTracker {
3224
- renderCounts = new Map;
3225
- overlays = new Map;
3226
- config;
3227
- constructor(config = defaultDebugConfig) {
3228
- this.config = config;
3229
- }
3230
- setConfig(config) {
3231
- this.config = { ...this.config, ...config };
3232
- if (!this.config.enabled) {
3233
- this.cleanup();
3234
- }
3235
- }
3236
- trackRerender(id, element, patchType) {
3237
- if (!this.config.enabled || !this.config.showHeatmap) {
3238
- return;
3239
- }
3240
- log4.debug(`Tracking re-render: ${id} - ${patchType}`);
3241
- const currentCount = this.renderCounts.get(id) || 0;
3242
- const newCount = currentCount + 1;
3243
- this.renderCounts.set(id, newCount);
3244
- this.updateHeatmap(id, element, newCount, patchType);
3245
- }
3246
- updateHeatmap(id, element, renderCount, patchType) {
3247
- const opacity = Math.min(renderCount * this.config.heatmapIncrement / 100, this.config.maxOpacity);
3248
- log4.debug(`Updating heatmap for ${id}, count: ${renderCount}, opacity: ${opacity}`);
3249
- const isInline = window.getComputedStyle(element).display.includes("inline");
3250
- if (isInline || element.tagName === "SPAN") {
3251
- if (!element.dataset.hypenDebugOriginalBg) {
3252
- element.dataset.hypenDebugOriginalBg = element.style.backgroundColor || "";
3253
- element.dataset.hypenDebugOriginalOutline = element.style.outline || "";
3254
- element.dataset.hypenDebugOriginalPosition = element.style.position || "";
3255
- }
3256
- element.style.backgroundColor = `rgba(255, 0, 0, ${Math.max(opacity, 0.15)})`;
3257
- element.style.outline = `2px solid rgba(255, 0, 0, ${Math.max(opacity + 0.2, 0.3)})`;
3258
- element.style.outlineOffset = "2px";
3259
- element.style.position = "relative";
3260
- element.setAttribute("data-hypen-renders", `${renderCount}× ${patchType}`);
3261
- if (!document.getElementById("hypen-debug-styles")) {
3262
- const style = document.createElement("style");
3263
- style.id = "hypen-debug-styles";
3264
- style.textContent = `
3265
- [data-hypen-renders]::before {
3266
- content: attr(data-hypen-renders);
3267
- position: absolute;
3268
- top: -18px;
3269
- left: 0;
3270
- background: rgba(255, 0, 0, 0.9);
3271
- color: white;
3272
- padding: 2px 6px;
3273
- font-size: 10px;
3274
- font-family: 'Courier New', monospace;
3275
- font-weight: bold;
3276
- border-radius: 3px;
3277
- z-index: 999999;
3278
- pointer-events: none;
3279
- white-space: nowrap;
3280
- text-shadow: none;
3281
- }
3282
- `;
3283
- document.head.appendChild(style);
3284
- }
3285
- this.overlays.set(id, element);
3286
- if (this.config.fadeOutDuration > 0) {
3287
- setTimeout(() => {
3288
- const originalBg = element.dataset.hypenDebugOriginalBg || "";
3289
- const originalOutline = element.dataset.hypenDebugOriginalOutline || "";
3290
- element.style.backgroundColor = originalBg;
3291
- element.style.outline = originalOutline;
3292
- element.style.opacity = "1";
3293
- }, this.config.fadeOutDuration);
3294
- }
3295
- } else {
3296
- let overlay = this.overlays.get(id);
3297
- if (!overlay) {
3298
- overlay = document.createElement("div");
3299
- overlay.className = "hypen-debug-overlay";
3300
- overlay.style.cssText = `
3301
- position: absolute;
3302
- top: 0;
3303
- left: 0;
3304
- right: 0;
3305
- bottom: 0;
3306
- pointer-events: none;
3307
- z-index: 999999 !important;
3308
- transition: opacity ${this.config.fadeOutDuration}ms ease-out;
3309
- border: 2px solid rgba(255, 0, 0, 0.7) !important;
3310
- box-sizing: border-box;
3311
- font-size: 11px;
3312
- color: white;
3313
- text-shadow: 0 0 3px black, 0 0 5px black;
3314
- padding: 4px;
3315
- font-family: 'Courier New', monospace;
3316
- font-weight: bold;
3317
- display: block !important;
3318
- visibility: visible !important;
3319
- `;
3320
- const currentPosition = window.getComputedStyle(element).position;
3321
- if (currentPosition === "static") {
3322
- element.style.position = "relative";
280
+ this.nodes.delete(id);
281
+ if (this.rootId === id) {
282
+ this.rootId = null;
3323
283
  }
3324
- element.appendChild(overlay);
3325
- this.overlays.set(id, overlay);
3326
- }
3327
- overlay.style.backgroundColor = `rgba(255, 0, 0, ${Math.max(opacity, 0.15)})`;
3328
- overlay.style.opacity = "1";
3329
- overlay.textContent = `${renderCount}× ${patchType}`;
3330
- if (this.config.fadeOutDuration > 0) {
3331
- setTimeout(() => {
3332
- if (overlay) {
3333
- overlay.style.opacity = "0.2";
3334
- }
3335
- }, this.config.fadeOutDuration);
3336
- }
3337
- }
3338
- }
3339
- reset(id) {
3340
- this.renderCounts.delete(id);
3341
- const overlay = this.overlays.get(id);
3342
- if (overlay) {
3343
- overlay.remove();
3344
- this.overlays.delete(id);
3345
284
  }
3346
- }
3347
- resetAll() {
3348
- this.renderCounts.clear();
3349
- for (const overlay of this.overlays.values()) {
3350
- overlay.remove();
3351
- }
3352
- this.overlays.clear();
3353
- }
3354
- getRenderCount(id) {
3355
- return this.renderCounts.get(id) || 0;
3356
- }
3357
- cleanup() {
3358
- for (const overlay of this.overlays.values()) {
3359
- overlay.remove();
3360
- }
3361
- this.overlays.clear();
3362
- }
3363
- getStats() {
3364
- const totalRerenders = Array.from(this.renderCounts.values()).reduce((sum, count) => sum + count, 0);
3365
- const elementCount = this.renderCounts.size;
3366
- const avgRerenders = elementCount > 0 ? totalRerenders / elementCount : 0;
3367
- return {
3368
- totalRerenders,
3369
- elementCount,
3370
- avgRerenders: Math.round(avgRerenders * 100) / 100
3371
- };
3372
- }
3373
- }
3374
-
3375
- // src/dom/renderer.ts
3376
- import { frameworkLoggers as frameworkLoggers5 } from "@hypen-space/core/logger";
3377
- var log5 = frameworkLoggers5.renderer;
3378
-
3379
- class DOMRenderer {
3380
- container;
3381
- nodes = new Map;
3382
- rootId = null;
3383
- components;
3384
- applicators;
3385
- engine;
3386
- currentState = {};
3387
- router = null;
3388
- globalContext = null;
3389
- componentInstances = new Map;
3390
- debugTracker;
3391
- constructor(container, engine, debugConfig) {
3392
- this.container = container;
3393
- this.engine = engine;
3394
- this.components = new ComponentRegistry;
3395
- this.applicators = new ApplicatorRegistry;
3396
- this.debugTracker = new RerenderTracker({ ...defaultDebugConfig, ...debugConfig });
3397
- this.components.register("canvas", canvasHandler);
3398
- for (const [name, handler] of Object.entries(canvasApplicators)) {
3399
- this.applicators.register(name, handler);
3400
- }
3401
- }
3402
- setContext(router, globalContext) {
3403
- this.router = router;
3404
- this.globalContext = globalContext;
3405
- }
3406
- applyPatches(patches) {
3407
- for (const patch of patches) {
3408
- this.applyPatch(patch);
3409
- }
3410
- }
3411
- updateState(state) {
3412
- log5.debug("Updating state:", state);
3413
- this.currentState = state;
3414
- this.interpolateAllText();
3415
- }
3416
- mergeComponentState(componentState) {
3417
- this.currentState = { ...this.currentState, ...componentState };
3418
- log5.debug("Merged state:", this.currentState);
3419
- this.interpolateAllText();
3420
- }
3421
- interpolateAllText() {
3422
- for (const [id, element] of this.nodes.entries()) {
3423
- if (element.dataset.hypenType === "text" && element.dataset.textTemplate) {
3424
- const template = element.dataset.textTemplate;
3425
- const interpolated = this.interpolateText(template, this.currentState);
3426
- const currentText = element.textContent;
3427
- if (currentText !== interpolated) {
3428
- this.debugTracker.trackRerender(id, element, "interpolate");
285
+ /**
286
+ * Get an element by ID
287
+ */
288
+ getNode(id) {
289
+ return this.nodes.get(id);
290
+ }
291
+ /**
292
+ * Clear all nodes
293
+ */
294
+ clear() {
295
+ // Dispose all element resources before clearing
296
+ for (const element of this.nodes.values()) {
297
+ disposeHypenElement(element);
3429
298
  }
3430
- element.textContent = interpolated;
3431
- }
3432
- }
3433
- }
3434
- interpolateText(template, state) {
3435
- return template.replace(/\$\{([^}]+)\}/g, (match, path) => {
3436
- try {
3437
- const value = path.split(".").reduce((obj, key) => {
3438
- if (key === "state")
3439
- return state;
3440
- return obj?.[key];
3441
- }, state);
3442
- return value !== undefined ? String(value) : match;
3443
- } catch {
3444
- return match;
3445
- }
3446
- });
3447
- }
3448
- applyPatch(patch) {
3449
- const p = patch;
3450
- const id = patch.id ?? p.id;
3451
- const elementType = patch.elementType ?? p.element_type;
3452
- const parentId = patch.parentId ?? p.parent_id;
3453
- const beforeId = patch.beforeId ?? p.before_id;
3454
- const eventName = patch.eventName ?? p.event_name;
3455
- switch (patch.type) {
3456
- case "create":
3457
- this.onCreate(id, elementType ?? "container", patch.props || {});
3458
- break;
3459
- case "setProp":
3460
- this.onSetProp(id, patch.name, patch.value);
3461
- break;
3462
- case "removeProp":
3463
- this.onRemoveProp(id, patch.name);
3464
- break;
3465
- case "setText":
3466
- this.onSetText(id, patch.text);
3467
- break;
3468
- case "insert":
3469
- this.onInsert(parentId, id, beforeId);
3470
- break;
3471
- case "move":
3472
- this.onMove(parentId, id, beforeId);
3473
- break;
3474
- case "remove":
3475
- this.onRemove(id);
3476
- break;
3477
- }
3478
- }
3479
- onCreate(id, elementType, props) {
3480
- const propsObj = props instanceof Map ? Object.fromEntries(props) : props;
3481
- let element = this.components.createElement(elementType, propsObj);
3482
- if (!element) {
3483
- const fallback = document.createElement("div");
3484
- fallback.dataset.hypenType = elementType.toLowerCase();
3485
- fallback.style.display = "contents";
3486
- element = fallback;
3487
- log5.debug(`Unknown component "${elementType}" - using transparent container`);
3488
- }
3489
- element.dataset.hypenType = elementType.toLowerCase();
3490
- element.dataset.hypenId = id;
3491
- setEngine(element, this.engine);
3492
- this.applicators.applyAll(element, propsObj);
3493
- this.nodes.set(id, element);
3494
- this.debugTracker.trackRerender(id, element, `create:${elementType}`);
3495
- if (!this.rootId) {
3496
- this.rootId = id;
3497
- if (!this.container.contains(element)) {
3498
- this.container.appendChild(element);
3499
- }
3500
- }
3501
- }
3502
- onSetProp(id, name, value) {
3503
- const element = this.nodes.get(id);
3504
- if (!element)
3505
- return;
3506
- this.debugTracker.trackRerender(id, element, `setProp:${name}`);
3507
- if (name === "0" || name === "text") {
3508
- const elementType = element.dataset.hypenType;
3509
- if (elementType === "input") {
3510
- const inputEl = element;
3511
- inputEl.value = String(value);
3512
- log5.debug(`Updated input value: "${value}"`);
3513
- return;
3514
- }
3515
- const nextText = String(value);
3516
- element.textContent = nextText;
3517
- const currentTemplate = element.dataset.textTemplate;
3518
- const nextLooksLikeTemplate = nextText.includes("${");
3519
- const currentLooksLikeTemplate = typeof currentTemplate === "string" && currentTemplate.includes("${");
3520
- if (nextLooksLikeTemplate) {
3521
- element.dataset.textTemplate = nextText;
3522
- } else if (currentTemplate === undefined) {
3523
- element.dataset.textTemplate = nextText;
3524
- } else if (!currentLooksLikeTemplate) {
3525
- element.dataset.textTemplate = nextText;
3526
- }
3527
- log5.debug(`Updated text content: "${value}"`);
3528
- return;
3529
- }
3530
- this.applicators.apply(element, name, value);
3531
- }
3532
- onRemoveProp(id, name) {
3533
- const element = this.nodes.get(id);
3534
- if (!element)
3535
- return;
3536
- this.debugTracker.trackRerender(id, element, `removeProp:${name}`);
3537
- this.applicators.apply(element, name, undefined);
3538
- }
3539
- onSetText(id, text) {
3540
- const element = this.nodes.get(id);
3541
- if (!element)
3542
- return;
3543
- this.debugTracker.trackRerender(id, element, "setText");
3544
- element.textContent = text;
3545
- }
3546
- onInsert(parentId, id, beforeId) {
3547
- const parent = parentId === "root" ? this.container : this.nodes.get(parentId);
3548
- const child = this.nodes.get(id);
3549
- log5.debug(`Inserting ${id} into ${parentId}`, {
3550
- parent: parent ? `${parent.tagName}#${parent.id || "no-id"}` : "null",
3551
- child: child ? `${child.tagName}#${child.id || "no-id"}` : "null",
3552
- childText: child?.textContent?.substring(0, 20)
3553
- });
3554
- if (!parent || !child)
3555
- return;
3556
- if (parentId === "root") {
3557
- this.rootId = id;
3558
- }
3559
- if (beforeId) {
3560
- const before = this.nodes.get(beforeId);
3561
- if (before && before.parentNode === parent) {
3562
- parent.insertBefore(child, before);
3563
- } else if (!parent.contains(child)) {
3564
- parent.appendChild(child);
3565
- }
3566
- } else {
3567
- if (!parent.contains(child)) {
3568
- parent.appendChild(child);
3569
- }
3570
- }
3571
- }
3572
- onMove(parentId, id, beforeId) {
3573
- this.onInsert(parentId, id, beforeId);
3574
- }
3575
- onRemove(id) {
3576
- const element = this.nodes.get(id);
3577
- if (!element)
3578
- return;
3579
- disposeHypenElement(element);
3580
- if (element.parentNode) {
3581
- element.parentNode.removeChild(element);
3582
- }
3583
- this.nodes.delete(id);
3584
- if (this.rootId === id) {
3585
- this.rootId = null;
3586
- }
3587
- }
3588
- getNode(id) {
3589
- return this.nodes.get(id);
3590
- }
3591
- clear() {
3592
- for (const element of this.nodes.values()) {
3593
- disposeHypenElement(element);
3594
- }
3595
- this.container.innerHTML = "";
3596
- this.nodes.clear();
3597
- this.rootId = null;
3598
- }
3599
- getComponentRegistry() {
3600
- return this.components;
3601
- }
3602
- getApplicatorRegistry() {
3603
- return this.applicators;
3604
- }
3605
- setDebugConfig(config) {
3606
- this.debugTracker.setConfig(config);
3607
- }
3608
- resetDebugTracking() {
3609
- this.debugTracker.resetAll();
3610
- }
3611
- getDebugStats() {
3612
- return this.debugTracker.getStats();
3613
- }
3614
- }
3615
- export {
3616
- DOMRenderer
3617
- };
3618
-
3619
- //# debugId=7281F3826585C79464756E2164756E21
299
+ this.container.innerHTML = "";
300
+ this.nodes.clear();
301
+ this.rootId = null;
302
+ }
303
+ /**
304
+ * Get the component registry (for registering custom components)
305
+ */
306
+ getComponentRegistry() {
307
+ return this.components;
308
+ }
309
+ /**
310
+ * Get the applicator registry (for registering custom applicators)
311
+ */
312
+ getApplicatorRegistry() {
313
+ return this.applicators;
314
+ }
315
+ /**
316
+ * Enable or configure debug mode
317
+ */
318
+ setDebugConfig(config) {
319
+ this.debugTracker.setConfig(config);
320
+ }
321
+ /**
322
+ * Reset debug tracking for all elements
323
+ */
324
+ resetDebugTracking() {
325
+ this.debugTracker.resetAll();
326
+ }
327
+ /**
328
+ * Get debug statistics
329
+ */
330
+ getDebugStats() {
331
+ return this.debugTracker.getStats();
332
+ }
333
+ }
334
+ //# sourceMappingURL=renderer.js.map