@hypen-space/web 0.4.35 → 0.4.37

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 (139) hide show
  1. package/dist/canvas/accessibility.js +174 -174
  2. package/dist/canvas/accessibility.js.map +1 -10
  3. package/dist/canvas/events.js +264 -360
  4. package/dist/canvas/events.js.map +1 -11
  5. package/dist/canvas/index.js +11 -47
  6. package/dist/canvas/index.js.map +1 -9
  7. package/dist/canvas/input.js +178 -289
  8. package/dist/canvas/input.js.map +1 -11
  9. package/dist/canvas/layout.js +349 -547
  10. package/dist/canvas/layout.js.map +1 -12
  11. package/dist/canvas/paint.js +1122 -1146
  12. package/dist/canvas/paint.js.map +1 -12
  13. package/dist/canvas/renderer.js +352 -2259
  14. package/dist/canvas/renderer.js.map +1 -17
  15. package/dist/canvas/text.js +129 -264
  16. package/dist/canvas/text.js.map +1 -11
  17. package/dist/canvas/types.js +7 -2
  18. package/dist/canvas/types.js.map +1 -9
  19. package/dist/canvas/utils.js +158 -161
  20. package/dist/canvas/utils.js.map +1 -10
  21. package/dist/client.js +19 -45
  22. package/dist/client.js.map +1 -9
  23. package/dist/dom/applicators/advanced-layout.js +105 -138
  24. package/dist/dom/applicators/advanced-layout.js.map +1 -10
  25. package/dist/dom/applicators/background.js +36 -76
  26. package/dist/dom/applicators/background.js.map +1 -10
  27. package/dist/dom/applicators/border.js +82 -106
  28. package/dist/dom/applicators/border.js.map +1 -10
  29. package/dist/dom/applicators/color.js +17 -58
  30. package/dist/dom/applicators/color.js.map +1 -10
  31. package/dist/dom/applicators/display.js +39 -79
  32. package/dist/dom/applicators/display.js.map +1 -10
  33. package/dist/dom/applicators/effects.js +121 -147
  34. package/dist/dom/applicators/effects.js.map +1 -10
  35. package/dist/dom/applicators/events.js +413 -450
  36. package/dist/dom/applicators/events.js.map +1 -11
  37. package/dist/dom/applicators/font.js +119 -140
  38. package/dist/dom/applicators/font.js.map +1 -10
  39. package/dist/dom/applicators/index.js +386 -1770
  40. package/dist/dom/applicators/index.js.map +1 -26
  41. package/dist/dom/applicators/layout.js +143 -149
  42. package/dist/dom/applicators/layout.js.map +1 -10
  43. package/dist/dom/applicators/margin.js +64 -105
  44. package/dist/dom/applicators/margin.js.map +1 -10
  45. package/dist/dom/applicators/padding.js +64 -105
  46. package/dist/dom/applicators/padding.js.map +1 -10
  47. package/dist/dom/applicators/size.js +169 -172
  48. package/dist/dom/applicators/size.js.map +1 -10
  49. package/dist/dom/applicators/transform.js +73 -114
  50. package/dist/dom/applicators/transform.js.map +1 -10
  51. package/dist/dom/applicators/transition.js +47 -88
  52. package/dist/dom/applicators/transition.js.map +1 -10
  53. package/dist/dom/applicators/types.js +7 -2
  54. package/dist/dom/applicators/types.js.map +1 -9
  55. package/dist/dom/applicators/typography.js +68 -109
  56. package/dist/dom/applicators/typography.js.map +1 -10
  57. package/dist/dom/canvas/index.js +49 -83
  58. package/dist/dom/canvas/index.js.map +1 -10
  59. package/dist/dom/components/app.js +16 -63
  60. package/dist/dom/components/app.js.map +1 -10
  61. package/dist/dom/components/audio.js +31 -76
  62. package/dist/dom/components/audio.js.map +1 -10
  63. package/dist/dom/components/avatar.js +41 -87
  64. package/dist/dom/components/avatar.js.map +1 -10
  65. package/dist/dom/components/badge.js +35 -83
  66. package/dist/dom/components/badge.js.map +1 -10
  67. package/dist/dom/components/button.js +26 -69
  68. package/dist/dom/components/button.js.map +1 -10
  69. package/dist/dom/components/card.js +13 -63
  70. package/dist/dom/components/card.js.map +1 -10
  71. package/dist/dom/components/center.js +21 -65
  72. package/dist/dom/components/center.js.map +1 -10
  73. package/dist/dom/components/checkbox.js +37 -82
  74. package/dist/dom/components/checkbox.js.map +1 -10
  75. package/dist/dom/components/column.js +16 -62
  76. package/dist/dom/components/column.js.map +1 -10
  77. package/dist/dom/components/container.js +11 -59
  78. package/dist/dom/components/container.js.map +1 -10
  79. package/dist/dom/components/divider.js +27 -73
  80. package/dist/dom/components/divider.js.map +1 -10
  81. package/dist/dom/components/grid.js +41 -80
  82. package/dist/dom/components/grid.js.map +1 -10
  83. package/dist/dom/components/heading.js +28 -74
  84. package/dist/dom/components/heading.js.map +1 -10
  85. package/dist/dom/components/hypenapp.js +291 -288
  86. package/dist/dom/components/hypenapp.js.map +1 -10
  87. package/dist/dom/components/image.js +18 -67
  88. package/dist/dom/components/image.js.map +1 -10
  89. package/dist/dom/components/index.js +109 -1427
  90. package/dist/dom/components/index.js.map +1 -42
  91. package/dist/dom/components/input.js +19 -69
  92. package/dist/dom/components/input.js.map +1 -10
  93. package/dist/dom/components/link.js +23 -70
  94. package/dist/dom/components/link.js.map +1 -10
  95. package/dist/dom/components/list.js +26 -72
  96. package/dist/dom/components/list.js.map +1 -10
  97. package/dist/dom/components/paragraph.js +14 -63
  98. package/dist/dom/components/paragraph.js.map +1 -10
  99. package/dist/dom/components/progressbar.js +38 -85
  100. package/dist/dom/components/progressbar.js.map +1 -10
  101. package/dist/dom/components/route.js +27 -73
  102. package/dist/dom/components/route.js.map +1 -10
  103. package/dist/dom/components/router.js +15 -62
  104. package/dist/dom/components/router.js.map +1 -10
  105. package/dist/dom/components/row.js +25 -70
  106. package/dist/dom/components/row.js.map +1 -10
  107. package/dist/dom/components/select.js +42 -85
  108. package/dist/dom/components/select.js.map +1 -10
  109. package/dist/dom/components/slider.js +31 -76
  110. package/dist/dom/components/slider.js.map +1 -10
  111. package/dist/dom/components/spacer.js +10 -60
  112. package/dist/dom/components/spacer.js.map +1 -10
  113. package/dist/dom/components/spinner.js +41 -88
  114. package/dist/dom/components/spinner.js.map +1 -10
  115. package/dist/dom/components/stack.js +19 -67
  116. package/dist/dom/components/stack.js.map +1 -10
  117. package/dist/dom/components/switch.js +50 -93
  118. package/dist/dom/components/switch.js.map +1 -10
  119. package/dist/dom/components/text.js +24 -69
  120. package/dist/dom/components/text.js.map +1 -10
  121. package/dist/dom/components/textarea.js +35 -79
  122. package/dist/dom/components/textarea.js.map +1 -10
  123. package/dist/dom/components/video.js +35 -79
  124. package/dist/dom/components/video.js.map +1 -10
  125. package/dist/dom/debug.js +159 -160
  126. package/dist/dom/debug.js.map +1 -10
  127. package/dist/dom/element-data.js +181 -143
  128. package/dist/dom/element-data.js.map +1 -10
  129. package/dist/dom/events.js +133 -142
  130. package/dist/dom/events.js.map +1 -10
  131. package/dist/dom/index.js +11 -48
  132. package/dist/dom/index.js.map +1 -9
  133. package/dist/dom/renderer.js +311 -3596
  134. package/dist/dom/renderer.js.map +1 -62
  135. package/dist/hypen.js +385 -3880
  136. package/dist/hypen.js.map +1 -63
  137. package/dist/index.js +36 -53
  138. package/dist/index.js.map +1 -9
  139. package/package.json +2 -2
package/dist/hypen.js CHANGED
@@ -1,3909 +1,414 @@
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" }
1
+ /**
2
+ * Hypen - High-Level API for Web Applications
3
+ *
4
+ * Simple API for rendering Hypen applications (like ReactDOM.render)
5
+ */
6
+ import { Engine } from "@hypen-space/core/engine/browser";
7
+ import { HypenModuleInstance } from "@hypen-space/core/app";
8
+ import { HypenRouter } from "@hypen-space/core/router";
9
+ import { HypenGlobalContext } from "@hypen-space/core/context";
10
+ import { componentLoader } from "@hypen-space/core/loader";
11
+ import { Router, Route, Link } from "@hypen-space/core/components";
12
+ import { frameworkLoggers, setDebugMode } from "@hypen-space/core/logger";
13
+ import { DOMRenderer } from "./dom/renderer.js";
14
+ const log = frameworkLoggers.hypen;
15
+ export class Hypen {
16
+ engine = null;
17
+ renderer = null;
18
+ moduleInstance = null;
19
+ container = null;
20
+ config;
21
+ router;
22
+ globalContext;
23
+ moduleInstances = new Map();
24
+ constructor(config = {}) {
25
+ this.config = {
26
+ componentsDir: "./src/components",
27
+ debug: false,
28
+ ...config,
801
29
  };
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
- });
1024
- 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);
30
+ // Enable debug mode if configured
31
+ if (this.config.debug) {
32
+ setDebugMode(true);
1074
33
  }
1075
- break;
1076
- }
1077
- case "setText": {
1078
- const el = nodes.get(patch.id);
1079
- if (el) {
1080
- el.textContent = patch.text;
34
+ // Initialize router and global context
35
+ this.router = new HypenRouter();
36
+ this.globalContext = new HypenGlobalContext();
37
+ // Register built-in components
38
+ componentLoader.register("Router", Router, "");
39
+ componentLoader.register("Route", Route, "");
40
+ componentLoader.register("Link", Link, "");
41
+ // Store hypen engine instance in global context for built-in component access
42
+ this.globalContext.__hypenEngine = this;
43
+ }
44
+ /**
45
+ * Initialize the Hypen runtime
46
+ * Must be called before render()
47
+ */
48
+ async init() {
49
+ log.debug("Initializing...");
50
+ // Initialize engine
51
+ this.engine = new Engine();
52
+ await this.engine.init({
53
+ wasmUrl: this.config.wasmUrl,
54
+ jsUrl: this.config.jsUrl,
55
+ });
56
+ log.debug("Engine initialized");
57
+ }
58
+ /**
59
+ * Load all components from the components directory
60
+ */
61
+ async loadComponents(componentsDir) {
62
+ const dir = componentsDir || this.config.componentsDir;
63
+ log.debug(`Loading components from ${dir}...`);
64
+ await componentLoader.loadFromComponentsDir(dir);
65
+ log.debug(`Loaded ${componentLoader.getNames().length} components`);
66
+ }
67
+ /**
68
+ * Render a component to a DOM container
69
+ *
70
+ * @param componentName - Name of the component to render (e.g., "HomePage")
71
+ * @param containerSelector - CSS selector or HTMLElement for the mount point
72
+ */
73
+ async render(componentName, containerSelector) {
74
+ if (!this.engine) {
75
+ throw new Error("[Hypen] Engine not initialized. Call init() first.");
1081
76
  }
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);
77
+ // Get the container element
78
+ if (typeof containerSelector === "string") {
79
+ const element = document.querySelector(containerSelector);
80
+ if (!element) {
81
+ throw new Error(`[Hypen] Container not found: ${containerSelector}`);
1099
82
  }
1100
- } else if (!parent.contains(child)) {
1101
- parent.appendChild(child);
1102
- }
83
+ this.container = element;
1103
84
  }
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
- }
85
+ else {
86
+ this.container = containerSelector;
1120
87
  }
1121
- break;
1122
- }
1123
- case "remove": {
1124
- const el = nodes.get(patch.id);
1125
- if (el && el.parentNode) {
1126
- el.parentNode.removeChild(el);
88
+ // Get the component definition
89
+ const component = componentLoader.get(componentName);
90
+ if (!component) {
91
+ throw new Error(`[Hypen] Component "${componentName}" not found. Available: ${componentLoader.getNames().join(", ")}`);
1127
92
  }
1128
- nodes.delete(patch.id);
1129
- break;
1130
- }
1131
- }
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"]);
1182
- }
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;
93
+ log.debug(`Rendering ${componentName} to`, this.container);
94
+ // Create renderer with debug config
95
+ this.renderer = new DOMRenderer(this.container, this.engine, {
96
+ enabled: this.config.debugHeatmap || false,
97
+ showHeatmap: this.config.debugHeatmap || false,
98
+ heatmapIncrement: this.config.heatmapIncrement || 5,
99
+ fadeOutDuration: this.config.heatmapFadeOut || 2000,
100
+ maxOpacity: 0.8,
1276
101
  });
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;
1301
- }
1302
- }
102
+ // Set render callback
103
+ this.engine.setRenderCallback((patches) => {
104
+ log.debug(`Applying ${patches.length} patches`);
105
+ this.renderer.applyPatches(patches);
106
+ });
107
+ // Set context on renderer for component composition
108
+ this.renderer.setContext(this.router, this.globalContext);
109
+ // Extract module ID from component name or .id() applicator
110
+ const moduleId = this.extractModuleId(componentName, component.template);
111
+ // Create module instance with router and global context
112
+ this.moduleInstance = new HypenModuleInstance(this.engine, component.module, this.router, this.globalContext);
113
+ // Register module in global context
114
+ this.globalContext.registerModule(moduleId, this.moduleInstance);
115
+ this.moduleInstances.set(moduleId, this.moduleInstance);
116
+ // Connect module state changes to renderer
117
+ this.moduleInstance.onStateChange(() => {
118
+ const mergedState = this.getMergedState();
119
+ log.debug(`State changed, merged state:`, mergedState);
120
+ this.renderer.updateState(mergedState);
121
+ });
122
+ // Set up component resolver for dynamic component composition
123
+ this.setupComponentResolver();
124
+ // Create module instances for ALL components that have state
125
+ this.createNestedModuleInstances();
126
+ // Render the UI template
127
+ this.engine.renderSource(component.template);
128
+ // Update renderer with initial state
129
+ this.renderer.updateState(this.getMergedState());
130
+ log.debug(`${componentName} rendered successfully`);
131
+ }
132
+ /**
133
+ * Unmount and cleanup
134
+ */
135
+ async unmount() {
136
+ log.debug("Unmounting...");
137
+ if (this.moduleInstance) {
138
+ await this.moduleInstance.destroy();
139
+ this.moduleInstance = null;
1303
140
  }
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
- }
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: {} };
2101
- }
2102
- let actionName = value.substring(1);
2103
- if (actionName.startsWith("actions.")) {
2104
- actionName = actionName.substring(8);
2105
- }
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);
141
+ if (this.container) {
142
+ this.container.innerHTML = "";
143
+ this.container = null;
2118
144
  }
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;
2125
- }
2126
- } else {
2127
- payload[key] = val;
2128
- }
145
+ this.renderer = null;
146
+ log.debug("Unmounted");
147
+ }
148
+ /**
149
+ * Get the current module instance
150
+ */
151
+ getModuleInstance() {
152
+ return this.moduleInstance;
153
+ }
154
+ /**
155
+ * Get the current state
156
+ */
157
+ getState() {
158
+ return this.moduleInstance?.getState() ?? null;
159
+ }
160
+ /**
161
+ * Get the router instance
162
+ */
163
+ getRouter() {
164
+ return this.router;
165
+ }
166
+ /**
167
+ * Get the global context
168
+ */
169
+ getGlobalContext() {
170
+ return this.globalContext;
171
+ }
172
+ /**
173
+ * Enable or disable debug heatmap mode
174
+ */
175
+ setDebugHeatmap(enabled) {
176
+ if (this.renderer) {
177
+ this.renderer.setDebugConfig({ enabled, showHeatmap: enabled });
178
+ log.debug(`Debug heatmap ${enabled ? "enabled" : "disabled"}`);
2129
179
  }
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
180
  }
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);
181
+ /**
182
+ * Reset debug tracking for all elements
183
+ */
184
+ resetDebugTracking() {
185
+ if (this.renderer) {
186
+ this.renderer.resetDebugTracking();
187
+ log.debug("Debug tracking reset");
2199
188
  }
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
189
  }
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);
190
+ /**
191
+ * Get debug statistics
192
+ */
193
+ getDebugStats() {
194
+ return this.renderer?.getDebugStats() || null;
195
+ }
196
+ /**
197
+ * Render a lazy route component into a specific route element
198
+ * This is called by the Router when a route becomes active
199
+ */
200
+ async renderLazyRoute(routePath, componentName, routeElement) {
201
+ if (!this.engine || !this.renderer) {
202
+ throw new Error("Engine not initialized");
2284
203
  }
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
- });
204
+ // Get the component from the loader
205
+ const component = componentLoader.get(componentName);
206
+ if (!component) {
207
+ throw new Error(`Component ${componentName} not found`);
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
+ // Get the component template
210
+ const template = component.template;
211
+ if (!template) {
212
+ throw new Error(`Component ${componentName} has no template`);
2401
213
  }
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
- });
214
+ // Create module instance for this component if it has state
215
+ if (component.module && !this.moduleInstances.has(componentName)) {
216
+ const moduleId = this.extractModuleId(componentName, template);
217
+ const moduleInstance = new HypenModuleInstance(this.engine, component.module, this.router, this.globalContext);
218
+ this.globalContext.registerModule(moduleId, moduleInstance);
219
+ this.moduleInstances.set(componentName, moduleInstance);
220
+ // Listen to state changes
221
+ moduleInstance.onStateChange(() => {
222
+ const mergedState = this.getMergedState();
223
+ log.debug(`Lazy component ${componentName} state changed:`, mergedState);
224
+ this.renderer.updateState(mergedState);
225
+ });
2412
226
  }
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);
227
+ // Wait a tick for module instance to initialize and fetch data
228
+ await new Promise((resolve) => setTimeout(resolve, 50));
229
+ // Get the route element's node ID from the renderer
230
+ const routeNodeId = routeElement.dataset.hypenId;
231
+ if (!routeNodeId) {
232
+ throw new Error(`Route element is missing data-hypen-id attribute`);
3044
233
  }
3045
- } else {
3046
- this.apply(element, baseName, args);
3047
- }
234
+ // Get the current merged state
235
+ const mergedState = this.getMergedState();
236
+ // Render into the Route element
237
+ this.engine.renderInto(template, routeNodeId, mergedState);
238
+ // Ensure freshly created text nodes are interpolated with current state
239
+ this.renderer.updateState(mergedState);
3048
240
  }
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);
241
+ /**
242
+ * Extract module ID from component name or .id() applicator
243
+ */
244
+ extractModuleId(componentName, template) {
245
+ // Look for .id("CustomName") or .id('CustomName') in the template
246
+ const idMatch = template.match(/\.id\(["']([^"']+)["']\)/);
247
+ const matchedId = idMatch?.[1];
248
+ if (matchedId) {
249
+ return matchedId;
3066
250
  }
3067
- element.classList.add(className);
3068
- }
3069
- return;
3070
- }
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);
251
+ // Default to component name
252
+ return componentName;
253
+ }
254
+ /**
255
+ * Create module instances for all components that have state
256
+ */
257
+ createNestedModuleInstances() {
258
+ // Create built-in Router module instance
259
+ if (Router && !this.moduleInstances.has("Router")) {
260
+ const routerInstance = new HypenModuleInstance(this.engine, Router, this.router, this.globalContext);
261
+ this.globalContext.registerModule("Router", routerInstance);
262
+ this.moduleInstances.set("Router", routerInstance);
263
+ routerInstance.onStateChange(() => {
264
+ const mergedState = this.getMergedState();
265
+ log.debug("Router state changed:", mergedState);
266
+ this.renderer.updateState(mergedState);
267
+ });
3084
268
  }
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";
269
+ // Get all registered components
270
+ const componentNames = componentLoader.getNames();
271
+ for (const name of componentNames) {
272
+ // Skip if already created
273
+ if (this.moduleInstances.has(name)) {
274
+ continue;
275
+ }
276
+ const comp = componentLoader.get(name);
277
+ if (!comp || !comp.module) {
278
+ continue;
279
+ }
280
+ // Create module instance
281
+ log.debug(`Creating nested module instance for: ${name}`);
282
+ const moduleInstance = new HypenModuleInstance(this.engine, comp.module, this.router, this.globalContext);
283
+ this.globalContext.registerModule(name, moduleInstance);
284
+ this.moduleInstances.set(name, moduleInstance);
285
+ // Connect state changes to renderer
286
+ moduleInstance.onStateChange(() => {
287
+ const mergedState = this.getMergedState();
288
+ log.debug(`Nested component ${name} state changed:`, mergedState);
289
+ this.renderer.updateState(mergedState);
290
+ });
3323
291
  }
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
- }
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
292
  }
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");
293
+ /**
294
+ * Get merged state from all module instances
295
+ */
296
+ getMergedState() {
297
+ const merged = {};
298
+ // Include main module state
299
+ if (this.moduleInstance) {
300
+ const mainState = this.moduleInstance.getState();
301
+ Object.assign(merged, mainState);
3429
302
  }
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
-
3616
- // src/hypen.ts
3617
- import { Engine } from "@hypen-space/core/engine/browser";
3618
- import { HypenModuleInstance } from "@hypen-space/core/app";
3619
- import { HypenRouter } from "@hypen-space/core/router";
3620
- import { HypenGlobalContext } from "@hypen-space/core/context";
3621
- import { componentLoader } from "@hypen-space/core/loader";
3622
- import { Router, Route, Link } from "@hypen-space/core/components";
3623
- import { frameworkLoggers as frameworkLoggers6, setDebugMode } from "@hypen-space/core/logger";
3624
- var log6 = frameworkLoggers6.hypen;
3625
-
3626
- class Hypen {
3627
- engine = null;
3628
- renderer = null;
3629
- moduleInstance = null;
3630
- container = null;
3631
- config;
3632
- router;
3633
- globalContext;
3634
- moduleInstances = new Map;
3635
- constructor(config = {}) {
3636
- this.config = {
3637
- componentsDir: "./src/components",
3638
- debug: false,
3639
- ...config
3640
- };
3641
- if (this.config.debug) {
3642
- setDebugMode(true);
3643
- }
3644
- this.router = new HypenRouter;
3645
- this.globalContext = new HypenGlobalContext;
3646
- componentLoader.register("Router", Router, "");
3647
- componentLoader.register("Route", Route, "");
3648
- componentLoader.register("Link", Link, "");
3649
- this.globalContext.__hypenEngine = this;
3650
- }
3651
- async init() {
3652
- log6.debug("Initializing...");
3653
- this.engine = new Engine;
3654
- await this.engine.init({
3655
- wasmUrl: this.config.wasmUrl,
3656
- jsUrl: this.config.jsUrl
3657
- });
3658
- log6.debug("Engine initialized");
3659
- }
3660
- async loadComponents(componentsDir) {
3661
- const dir = componentsDir || this.config.componentsDir;
3662
- log6.debug(`Loading components from ${dir}...`);
3663
- await componentLoader.loadFromComponentsDir(dir);
3664
- log6.debug(`Loaded ${componentLoader.getNames().length} components`);
3665
- }
3666
- async render(componentName, containerSelector) {
3667
- if (!this.engine) {
3668
- throw new Error("[Hypen] Engine not initialized. Call init() first.");
3669
- }
3670
- if (typeof containerSelector === "string") {
3671
- const element = document.querySelector(containerSelector);
3672
- if (!element) {
3673
- throw new Error(`[Hypen] Container not found: ${containerSelector}`);
3674
- }
3675
- this.container = element;
3676
- } else {
3677
- this.container = containerSelector;
3678
- }
3679
- const component = componentLoader.get(componentName);
3680
- if (!component) {
3681
- throw new Error(`[Hypen] Component "${componentName}" not found. Available: ${componentLoader.getNames().join(", ")}`);
3682
- }
3683
- log6.debug(`Rendering ${componentName} to`, this.container);
3684
- this.renderer = new DOMRenderer(this.container, this.engine, {
3685
- enabled: this.config.debugHeatmap || false,
3686
- showHeatmap: this.config.debugHeatmap || false,
3687
- heatmapIncrement: this.config.heatmapIncrement || 5,
3688
- fadeOutDuration: this.config.heatmapFadeOut || 2000,
3689
- maxOpacity: 0.8
3690
- });
3691
- this.engine.setRenderCallback((patches) => {
3692
- log6.debug(`Applying ${patches.length} patches`);
3693
- this.renderer.applyPatches(patches);
3694
- });
3695
- this.renderer.setContext(this.router, this.globalContext);
3696
- const moduleId = this.extractModuleId(componentName, component.template);
3697
- this.moduleInstance = new HypenModuleInstance(this.engine, component.module, this.router, this.globalContext);
3698
- this.globalContext.registerModule(moduleId, this.moduleInstance);
3699
- this.moduleInstances.set(moduleId, this.moduleInstance);
3700
- this.moduleInstance.onStateChange(() => {
3701
- const mergedState = this.getMergedState();
3702
- log6.debug(`State changed, merged state:`, mergedState);
3703
- this.renderer.updateState(mergedState);
3704
- });
3705
- this.setupComponentResolver();
3706
- this.createNestedModuleInstances();
3707
- this.engine.renderSource(component.template);
3708
- this.renderer.updateState(this.getMergedState());
3709
- log6.debug(`${componentName} rendered successfully`);
3710
- }
3711
- async unmount() {
3712
- log6.debug("Unmounting...");
3713
- if (this.moduleInstance) {
3714
- await this.moduleInstance.destroy();
3715
- this.moduleInstance = null;
3716
- }
3717
- if (this.container) {
3718
- this.container.innerHTML = "";
3719
- this.container = null;
3720
- }
3721
- this.renderer = null;
3722
- log6.debug("Unmounted");
3723
- }
3724
- getModuleInstance() {
3725
- return this.moduleInstance;
3726
- }
3727
- getState() {
3728
- return this.moduleInstance?.getState() ?? null;
3729
- }
3730
- getRouter() {
3731
- return this.router;
3732
- }
3733
- getGlobalContext() {
3734
- return this.globalContext;
3735
- }
3736
- setDebugHeatmap(enabled) {
3737
- if (this.renderer) {
3738
- this.renderer.setDebugConfig({ enabled, showHeatmap: enabled });
3739
- log6.debug(`Debug heatmap ${enabled ? "enabled" : "disabled"}`);
3740
- }
3741
- }
3742
- resetDebugTracking() {
3743
- if (this.renderer) {
3744
- this.renderer.resetDebugTracking();
3745
- log6.debug("Debug tracking reset");
3746
- }
3747
- }
3748
- getDebugStats() {
3749
- return this.renderer?.getDebugStats() || null;
3750
- }
3751
- async renderLazyRoute(routePath, componentName, routeElement) {
3752
- if (!this.engine || !this.renderer) {
3753
- throw new Error("Engine not initialized");
3754
- }
3755
- const component = componentLoader.get(componentName);
3756
- if (!component) {
3757
- throw new Error(`Component ${componentName} not found`);
3758
- }
3759
- const template = component.template;
3760
- if (!template) {
3761
- throw new Error(`Component ${componentName} has no template`);
3762
- }
3763
- if (component.module && !this.moduleInstances.has(componentName)) {
3764
- const moduleId = this.extractModuleId(componentName, template);
3765
- const moduleInstance = new HypenModuleInstance(this.engine, component.module, this.router, this.globalContext);
3766
- this.globalContext.registerModule(moduleId, moduleInstance);
3767
- this.moduleInstances.set(componentName, moduleInstance);
3768
- moduleInstance.onStateChange(() => {
3769
- const mergedState2 = this.getMergedState();
3770
- log6.debug(`Lazy component ${componentName} state changed:`, mergedState2);
3771
- this.renderer.updateState(mergedState2);
3772
- });
3773
- }
3774
- await new Promise((resolve) => setTimeout(resolve, 50));
3775
- const routeNodeId = routeElement.dataset.hypenId;
3776
- if (!routeNodeId) {
3777
- throw new Error(`Route element is missing data-hypen-id attribute`);
3778
- }
3779
- const mergedState = this.getMergedState();
3780
- this.engine.renderInto(template, routeNodeId, mergedState);
3781
- this.renderer.updateState(mergedState);
3782
- }
3783
- extractModuleId(componentName, template) {
3784
- const idMatch = template.match(/\.id\(["']([^"']+)["']\)/);
3785
- const matchedId = idMatch?.[1];
3786
- if (matchedId) {
3787
- return matchedId;
3788
- }
3789
- return componentName;
3790
- }
3791
- createNestedModuleInstances() {
3792
- if (Router && !this.moduleInstances.has("Router")) {
3793
- const routerInstance = new HypenModuleInstance(this.engine, Router, this.router, this.globalContext);
3794
- this.globalContext.registerModule("Router", routerInstance);
3795
- this.moduleInstances.set("Router", routerInstance);
3796
- routerInstance.onStateChange(() => {
3797
- const mergedState = this.getMergedState();
3798
- log6.debug("Router state changed:", mergedState);
3799
- this.renderer.updateState(mergedState);
3800
- });
3801
- }
3802
- const componentNames = componentLoader.getNames();
3803
- for (const name of componentNames) {
3804
- if (this.moduleInstances.has(name)) {
3805
- continue;
3806
- }
3807
- const comp = componentLoader.get(name);
3808
- if (!comp || !comp.module) {
3809
- continue;
3810
- }
3811
- log6.debug(`Creating nested module instance for: ${name}`);
3812
- const moduleInstance = new HypenModuleInstance(this.engine, comp.module, this.router, this.globalContext);
3813
- this.globalContext.registerModule(name, moduleInstance);
3814
- this.moduleInstances.set(name, moduleInstance);
3815
- moduleInstance.onStateChange(() => {
3816
- const mergedState = this.getMergedState();
3817
- log6.debug(`Nested component ${name} state changed:`, mergedState);
3818
- this.renderer.updateState(mergedState);
3819
- });
3820
- }
3821
- }
3822
- getMergedState() {
3823
- const merged = {};
3824
- if (this.moduleInstance) {
3825
- const mainState = this.moduleInstance.getState();
3826
- Object.assign(merged, mainState);
3827
- }
3828
- for (const [name, instance] of this.moduleInstances.entries()) {
3829
- const nestedState = instance.getState();
3830
- Object.assign(merged, nestedState);
303
+ // Include all nested component states
304
+ for (const [name, instance] of this.moduleInstances.entries()) {
305
+ const nestedState = instance.getState();
306
+ Object.assign(merged, nestedState);
307
+ }
308
+ return merged;
309
+ }
310
+ /**
311
+ * Set up component resolver for the engine
312
+ */
313
+ setupComponentResolver() {
314
+ if (!this.engine)
315
+ return;
316
+ // List of built-in DOM elements that should NOT be resolved
317
+ const builtInElements = new Set([
318
+ "Column",
319
+ "Row",
320
+ "Text",
321
+ "Button",
322
+ "Image",
323
+ "Input",
324
+ "Container",
325
+ "Box",
326
+ "Center",
327
+ "List",
328
+ "Canvas",
329
+ "Spacer",
330
+ "Divider",
331
+ "ScrollView",
332
+ ]);
333
+ this.engine.setComponentResolver((componentName, contextPath) => {
334
+ // Don't try to resolve built-in DOM elements
335
+ if (builtInElements.has(componentName)) {
336
+ return null;
337
+ }
338
+ log.debug(`Resolving component: ${componentName} (context: ${contextPath})`);
339
+ // Check if component is registered
340
+ const componentDef = componentLoader.get(componentName);
341
+ if (!componentDef) {
342
+ log.debug(`Component not found: ${componentName}`);
343
+ return null;
344
+ }
345
+ // Router is passthrough, Route is lazy
346
+ const isPassthrough = componentName === "Router";
347
+ const isLazy = componentName === "Route";
348
+ const resolved = {
349
+ source: componentDef.template,
350
+ path: componentDef.path || componentName,
351
+ passthrough: isPassthrough,
352
+ lazy: isLazy,
353
+ };
354
+ const flags = [];
355
+ if (isPassthrough)
356
+ flags.push("passthrough");
357
+ if (isLazy)
358
+ flags.push("lazy");
359
+ const flagStr = flags.length > 0 ? ` (${flags.join(", ")})` : "";
360
+ log.debug(`Resolved ${componentName} -> ${resolved.path}${flagStr}`);
361
+ return resolved;
362
+ });
3831
363
  }
3832
- return merged;
3833
- }
3834
- setupComponentResolver() {
3835
- if (!this.engine)
3836
- return;
3837
- const builtInElements = new Set([
3838
- "Column",
3839
- "Row",
3840
- "Text",
3841
- "Button",
3842
- "Image",
3843
- "Input",
3844
- "Container",
3845
- "Box",
3846
- "Center",
3847
- "List",
3848
- "Canvas",
3849
- "Spacer",
3850
- "Divider",
3851
- "ScrollView"
3852
- ]);
3853
- this.engine.setComponentResolver((componentName, contextPath) => {
3854
- if (builtInElements.has(componentName)) {
3855
- return null;
3856
- }
3857
- log6.debug(`Resolving component: ${componentName} (context: ${contextPath})`);
3858
- const componentDef = componentLoader.get(componentName);
3859
- if (!componentDef) {
3860
- log6.debug(`Component not found: ${componentName}`);
3861
- return null;
3862
- }
3863
- const isPassthrough = componentName === "Router";
3864
- const isLazy = componentName === "Route";
3865
- const resolved = {
3866
- source: componentDef.template,
3867
- path: componentDef.path || componentName,
3868
- passthrough: isPassthrough,
3869
- lazy: isLazy
3870
- };
3871
- const flags = [];
3872
- if (isPassthrough)
3873
- flags.push("passthrough");
3874
- if (isLazy)
3875
- flags.push("lazy");
3876
- const flagStr = flags.length > 0 ? ` (${flags.join(", ")})` : "";
3877
- log6.debug(`Resolved ${componentName} -> ${resolved.path}${flagStr}`);
3878
- return resolved;
3879
- });
3880
- }
3881
- }
3882
- async function render(componentName, containerSelector, config) {
3883
- const hypen = new Hypen(config);
3884
- await hypen.init();
3885
- await hypen.loadComponents();
3886
- await hypen.render(componentName, containerSelector);
3887
- return hypen;
3888
364
  }
3889
- async function renderWithComponents(components, componentName, containerSelector, config) {
3890
- const hypen = new Hypen(config);
3891
- await hypen.init();
3892
- for (const [name, { module, template }] of Object.entries(components)) {
3893
- let processedTemplate = template;
3894
- const moduleMatch = template.match(/^\s*module\s+\w+\s*\{([\s\S]*)\}\s*$/);
3895
- if (moduleMatch && moduleMatch[1]) {
3896
- processedTemplate = moduleMatch[1].trim();
365
+ /**
366
+ * Quick render function (like ReactDOM.render)
367
+ *
368
+ * @example
369
+ * ```typescript
370
+ * import { render } from "@hypen-space/web/client";
371
+ *
372
+ * await render("HomePage", "#app");
373
+ * ```
374
+ */
375
+ export async function render(componentName, containerSelector, config) {
376
+ const hypen = new Hypen(config);
377
+ await hypen.init();
378
+ await hypen.loadComponents();
379
+ await hypen.render(componentName, containerSelector);
380
+ return hypen;
381
+ }
382
+ /**
383
+ * Render with explicit component loading
384
+ *
385
+ * @example
386
+ * ```typescript
387
+ * import { renderWithComponents } from "@hypen-space/web/client";
388
+ * import HomePage from "./components/HomePage/component";
389
+ * import homePageTemplate from "./components/HomePage/component.hypen";
390
+ *
391
+ * await renderWithComponents(
392
+ * { HomePage: { module: HomePage, template: homePageTemplate } },
393
+ * "HomePage",
394
+ * "#app"
395
+ * );
396
+ * ```
397
+ */
398
+ export async function renderWithComponents(components, componentName, containerSelector, config) {
399
+ const hypen = new Hypen(config);
400
+ await hypen.init();
401
+ // Register components manually
402
+ for (const [name, { module, template }] of Object.entries(components)) {
403
+ let processedTemplate = template;
404
+ // If template starts with "module ComponentName {", extract just the children
405
+ const moduleMatch = template.match(/^\s*module\s+\w+\s*\{([\s\S]*)\}\s*$/);
406
+ if (moduleMatch && moduleMatch[1]) {
407
+ processedTemplate = moduleMatch[1].trim();
408
+ }
409
+ componentLoader.register(name, module, processedTemplate);
3897
410
  }
3898
- componentLoader.register(name, module, processedTemplate);
3899
- }
3900
- await hypen.render(componentName, containerSelector);
3901
- return hypen;
411
+ await hypen.render(componentName, containerSelector);
412
+ return hypen;
3902
413
  }
3903
- export {
3904
- renderWithComponents,
3905
- render,
3906
- Hypen
3907
- };
3908
-
3909
- //# debugId=3DCF8179C4B1004C64756E2164756E21
414
+ //# sourceMappingURL=hypen.js.map