elementdrawing 1.0.0

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 (78) hide show
  1. package/LICENSE +21 -0
  2. package/dist/elementdrawing.min.js +3 -0
  3. package/dist/elementdrawing.min.js.LICENSE.txt +8 -0
  4. package/dist/elementdrawing.min.js.map +1 -0
  5. package/dist/index.html +1 -0
  6. package/package.json +127 -0
  7. package/src/core/bridge.h +855 -0
  8. package/src/core/diff.c +900 -0
  9. package/src/core/element.c +1078 -0
  10. package/src/core/event.c +813 -0
  11. package/src/core/fiber.c +1027 -0
  12. package/src/core/hooks.c +919 -0
  13. package/src/core/renderer.c +963 -0
  14. package/src/core/scheduler.c +702 -0
  15. package/src/core/state.c +803 -0
  16. package/src/css/animations.css +779 -0
  17. package/src/css/base.css +615 -0
  18. package/src/css/components.css +1311 -0
  19. package/src/css/tailwind.css +370 -0
  20. package/src/css/themes.css +517 -0
  21. package/src/css/utilities.css +475 -0
  22. package/src/index.js +746 -0
  23. package/src/js/animation.js +655 -0
  24. package/src/js/dom.js +665 -0
  25. package/src/js/events.js +585 -0
  26. package/src/js/http.js +446 -0
  27. package/src/js/index.js +26 -0
  28. package/src/js/router.js +483 -0
  29. package/src/js/store.js +539 -0
  30. package/src/js/utils.js +593 -0
  31. package/src/js/validator.js +529 -0
  32. package/src/jsx/components/Accordion.jsx +210 -0
  33. package/src/jsx/components/Alert.jsx +169 -0
  34. package/src/jsx/components/Avatar.jsx +214 -0
  35. package/src/jsx/components/Badge.jsx +136 -0
  36. package/src/jsx/components/Breadcrumb.jsx +200 -0
  37. package/src/jsx/components/Button.jsx +188 -0
  38. package/src/jsx/components/Card.jsx +192 -0
  39. package/src/jsx/components/Carousel.jsx +278 -0
  40. package/src/jsx/components/Checkbox.jsx +215 -0
  41. package/src/jsx/components/Dialog.jsx +242 -0
  42. package/src/jsx/components/Drawer.jsx +190 -0
  43. package/src/jsx/components/Dropdown.jsx +268 -0
  44. package/src/jsx/components/Form.jsx +274 -0
  45. package/src/jsx/components/Input.jsx +285 -0
  46. package/src/jsx/components/Menu.jsx +276 -0
  47. package/src/jsx/components/Modal.jsx +274 -0
  48. package/src/jsx/components/Navbar.jsx +292 -0
  49. package/src/jsx/components/Pagination.jsx +268 -0
  50. package/src/jsx/components/Progress.jsx +252 -0
  51. package/src/jsx/components/Radio.jsx +208 -0
  52. package/src/jsx/components/Select.jsx +397 -0
  53. package/src/jsx/components/Sidebar.jsx +250 -0
  54. package/src/jsx/components/Slider.jsx +310 -0
  55. package/src/jsx/components/Spinner.jsx +198 -0
  56. package/src/jsx/components/Switch.jsx +201 -0
  57. package/src/jsx/components/Table.jsx +332 -0
  58. package/src/jsx/components/Tabs.jsx +227 -0
  59. package/src/jsx/components/Textarea.jsx +212 -0
  60. package/src/jsx/components/Toast.jsx +270 -0
  61. package/src/jsx/components/Tooltip.jsx +178 -0
  62. package/src/jsx/components/Typography.jsx +299 -0
  63. package/src/jsx/components/index.jsx +70 -0
  64. package/src/jsx/core/element.js +3 -0
  65. package/src/jsx/hooks/index.js +356 -0
  66. package/src/jsx/hooks/useCallback.js +472 -0
  67. package/src/jsx/hooks/useContext.js +586 -0
  68. package/src/jsx/hooks/useEffect.js +704 -0
  69. package/src/jsx/hooks/useLayoutEffect.js +508 -0
  70. package/src/jsx/hooks/useMemo.js +689 -0
  71. package/src/jsx/hooks/useReducer.js +729 -0
  72. package/src/jsx/hooks/useRef.js +542 -0
  73. package/src/jsx/hooks/useState.js +854 -0
  74. package/src/jsx/runtime/commit.js +903 -0
  75. package/src/jsx/runtime/createElement.js +860 -0
  76. package/src/jsx/runtime/index.js +356 -0
  77. package/src/jsx/runtime/reconcile.js +687 -0
  78. package/src/jsx/runtime/render.js +914 -0
@@ -0,0 +1,914 @@
1
+ /**
2
+ * render - DOM Rendering Engine
3
+ * ElementDrawing Framework - Converts virtual DOM to real DOM nodes with
4
+ * attribute setting, style application, event delegation, ref assignment,
5
+ * portal rendering, suspense, error boundaries, hydration, SVG/MathML
6
+ * namespace support, and concurrent rendering hints.
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const {
12
+ REACT_FRAGMENT_TYPE, REACT_PORTAL_TYPE, REACT_SUSPENSE_TYPE,
13
+ REACT_LAZY_TYPE, REACT_STRICT_MODE_TYPE, REACT_ERROR_BOUNDARY_TYPE,
14
+ isValidElement,
15
+ } = require('./createElement');
16
+
17
+ // ─── Constants ────────────────────────────────────────────────────────────────
18
+
19
+ const TEXT_NODE_TYPE = '#text';
20
+ const COMMENT_NODE_TYPE = '#comment';
21
+
22
+ const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
23
+ const MATH_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';
24
+
25
+ const VOID_ELEMENTS = new Set([
26
+ 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',
27
+ 'link', 'meta', 'param', 'source', 'track', 'wbr',
28
+ ]);
29
+
30
+ const SVG_TAGS = new Set([
31
+ 'svg', 'animate', 'animateMotion', 'animateTransform', 'circle', 'clipPath',
32
+ 'defs', 'desc', 'ellipse', 'feBlend', 'feColorMatrix', 'feComponentTransfer',
33
+ 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap',
34
+ 'feDistantLight', 'feDropShadow', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG',
35
+ 'feFuncR', 'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode', 'feMorphology',
36
+ 'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile',
37
+ 'feTurbulence', 'filter', 'foreignObject', 'g', 'image', 'line', 'linearGradient',
38
+ 'marker', 'mask', 'metadata', 'mpath', 'path', 'pattern', 'polygon', 'polyline',
39
+ 'radialGradient', 'rect', 'set', 'stop', 'switch', 'symbol', 'text', 'textPath',
40
+ 'tspan', 'use', 'view',
41
+ ]);
42
+
43
+ const BOOLEAN_ATTRIBUTES = new Set([
44
+ 'allowFullScreen', 'async', 'autofocus', 'autoPlay', 'checked', 'controls',
45
+ 'default', 'disabled', 'enableRemotePlayback', 'formNoValidate', 'hidden',
46
+ 'loop', 'multiple', 'muted', 'noModule', 'noValidate', 'open', 'playsInline',
47
+ 'readOnly', 'required', 'reversed', 'scoped', 'seamless', 'selected',
48
+ 'itemScope',
49
+ ]);
50
+
51
+ const PROPERTY_ATTRIBUTES = new Set([
52
+ 'value', 'checked', 'selected', 'innerHTML', 'textContent',
53
+ 'defaultValue', 'defaultChecked',
54
+ ]);
55
+
56
+ // Event delegation map
57
+ const eventDelegationMap = new Map();
58
+ let rootContainer = null;
59
+
60
+ // ─── DOM Creation ─────────────────────────────────────────────────────────────
61
+
62
+ /**
63
+ * Create a real DOM element from a virtual element type.
64
+ * @param {string} type
65
+ * @param {Object} props
66
+ * @param {string|null} namespace
67
+ * @returns {HTMLElement|SVGElement}
68
+ */
69
+ function createDOMElement(type, props, namespace) {
70
+ let domNode;
71
+
72
+ if (namespace === SVG_NAMESPACE || SVG_TAGS.has(type)) {
73
+ domNode = document.createElementNS(SVG_NAMESPACE, type);
74
+ } else if (namespace === MATH_NAMESPACE) {
75
+ domNode = document.createElementNS(MATH_NAMESPACE, type);
76
+ } else {
77
+ domNode = document.createElement(type);
78
+ }
79
+
80
+ return domNode;
81
+ }
82
+
83
+ /**
84
+ * Create a text node.
85
+ * @param {string} text
86
+ * @returns {Text}
87
+ */
88
+ function createTextNode(text) {
89
+ return document.createTextNode(text);
90
+ }
91
+
92
+ /**
93
+ * Create a comment node.
94
+ * @param {string} text
95
+ * @returns {Comment}
96
+ */
97
+ function createCommentNode(text) {
98
+ return document.createComment(text);
99
+ }
100
+
101
+ /**
102
+ * Create a document fragment.
103
+ * @returns {DocumentFragment}
104
+ */
105
+ function createDocumentFragment() {
106
+ return document.createDocumentFragment();
107
+ }
108
+
109
+ // ─── Attribute Setting ────────────────────────────────────────────────────────
110
+
111
+ /**
112
+ * Set an attribute on a DOM element with proper handling for different types.
113
+ * @param {HTMLElement} domNode
114
+ * @param {string} name
115
+ * @param {*} value
116
+ */
117
+ function setAttribute(domNode, name, value) {
118
+ if (value === null || value === undefined || value === false) {
119
+ removeAttribute(domNode, name);
120
+ return;
121
+ }
122
+
123
+ // Boolean attributes
124
+ if (BOOLEAN_ATTRIBUTES.has(name)) {
125
+ if (value === true) {
126
+ domNode.setAttribute(name.toLowerCase(), '');
127
+ domNode[name] = true;
128
+ } else {
129
+ removeAttribute(domNode, name);
130
+ domNode[name] = false;
131
+ }
132
+ return;
133
+ }
134
+
135
+ // Property attributes
136
+ if (PROPERTY_ATTRIBUTES.has(name)) {
137
+ domNode[name] = value;
138
+ return;
139
+ }
140
+
141
+ // className -> class
142
+ if (name === 'className') {
143
+ domNode.className = value;
144
+ return;
145
+ }
146
+
147
+ // htmlFor -> for
148
+ if (name === 'htmlFor') {
149
+ domNode.setAttribute('for', value);
150
+ return;
151
+ }
152
+
153
+ // tabIndex
154
+ if (name === 'tabIndex') {
155
+ domNode.tabIndex = value;
156
+ return;
157
+ }
158
+
159
+ // Style object
160
+ if (name === 'style') {
161
+ applyStyle(domNode, value);
162
+ return;
163
+ }
164
+
165
+ // Event handlers
166
+ if (name.startsWith('on')) {
167
+ setEventHandler(domNode, name, value);
168
+ return;
169
+ }
170
+
171
+ // Data attributes
172
+ if (name.startsWith('data-')) {
173
+ domNode.setAttribute(name, value);
174
+ return;
175
+ }
176
+
177
+ // ARIA attributes
178
+ if (name.startsWith('aria-')) {
179
+ domNode.setAttribute(name, value);
180
+ return;
181
+ }
182
+
183
+ // Custom element attributes
184
+ if (name.includes('-') && !(domNode instanceof SVGElement)) {
185
+ domNode.setAttribute(name, value);
186
+ return;
187
+ }
188
+
189
+ // Standard attributes
190
+ if (name in domNode) {
191
+ try {
192
+ domNode[name] = value;
193
+ } catch (e) {
194
+ domNode.setAttribute(name, value);
195
+ }
196
+ } else {
197
+ domNode.setAttribute(name, value);
198
+ }
199
+ }
200
+
201
+ /**
202
+ * Remove an attribute from a DOM element.
203
+ * @param {HTMLElement} domNode
204
+ * @param {string} name
205
+ */
206
+ function removeAttribute(domNode, name) {
207
+ if (name === 'className') {
208
+ domNode.className = '';
209
+ return;
210
+ }
211
+
212
+ if (name === 'style') {
213
+ domNode.removeAttribute('style');
214
+ domNode.style.cssText = '';
215
+ return;
216
+ }
217
+
218
+ if (name === 'htmlFor') {
219
+ domNode.removeAttribute('for');
220
+ return;
221
+ }
222
+
223
+ if (BOOLEAN_ATTRIBUTES.has(name)) {
224
+ domNode.removeAttribute(name.toLowerCase());
225
+ domNode[name] = false;
226
+ return;
227
+ }
228
+
229
+ if (PROPERTY_ATTRIBUTES.has(name)) {
230
+ if (name === 'value') domNode.value = '';
231
+ else if (name === 'checked') domNode.checked = false;
232
+ else if (name === 'selected') domNode.selected = false;
233
+ return;
234
+ }
235
+
236
+ if (name.startsWith('on')) {
237
+ removeEventHandler(domNode, name);
238
+ return;
239
+ }
240
+
241
+ try {
242
+ domNode.removeAttribute(name);
243
+ if (name in domNode) domNode[name] = '';
244
+ } catch (e) {
245
+ // Some attributes can't be removed
246
+ }
247
+ }
248
+
249
+ /**
250
+ * Set a property directly on a DOM element.
251
+ */
252
+ function setProperty(domNode, name, value) {
253
+ try {
254
+ domNode[name] = value;
255
+ } catch (e) {
256
+ console.warn('[render] Failed to set property "' + name + '":', e);
257
+ }
258
+ }
259
+
260
+ // ─── Style Application ────────────────────────────────────────────────────────
261
+
262
+ /**
263
+ * Apply inline styles to a DOM element.
264
+ * @param {HTMLElement} domNode
265
+ * @param {Object|string} style
266
+ */
267
+ function applyStyle(domNode, style) {
268
+ if (!style) {
269
+ domNode.removeAttribute('style');
270
+ return;
271
+ }
272
+
273
+ if (typeof style === 'string') {
274
+ domNode.style.cssText = style;
275
+ return;
276
+ }
277
+
278
+ if (typeof style === 'object') {
279
+ for (const prop in style) {
280
+ if (style.hasOwnProperty(prop)) {
281
+ const cssProp = prop.includes('-') ? prop : camelToKebab(prop);
282
+ domNode.style.setProperty(cssProp, style[prop]);
283
+ }
284
+ }
285
+ }
286
+ }
287
+
288
+ /**
289
+ * Convert camelCase to kebab-case.
290
+ */
291
+ function camelToKebab(str) {
292
+ return str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
293
+ }
294
+
295
+ /**
296
+ * Apply CSS classes to a DOM element.
297
+ */
298
+ function applyClassName(domNode, className) {
299
+ if (!className) {
300
+ domNode.removeAttribute('class');
301
+ return;
302
+ }
303
+ domNode.className = className;
304
+ }
305
+
306
+ // ─── Event Handling ───────────────────────────────────────────────────────────
307
+
308
+ /**
309
+ * Set an event handler on a DOM element.
310
+ */
311
+ function setEventHandler(domNode, name, handler) {
312
+ const eventType = name.slice(2).toLowerCase();
313
+ const normalizedType = normalizeEventType(eventType);
314
+
315
+ const handlerKey = '__ed_handler_' + normalizedType;
316
+ const captureHandlerKey = '__ed_capture_' + normalizedType;
317
+
318
+ const isCapture = name.endsWith('Capture');
319
+ const key = isCapture ? captureHandlerKey : handlerKey;
320
+
321
+ // Remove old handler if exists
322
+ if (domNode[key]) {
323
+ domNode.removeEventListener(normalizedType, domNode[key], isCapture);
324
+ }
325
+
326
+ // Set new handler
327
+ if (typeof handler === 'function') {
328
+ domNode[key] = handler;
329
+ domNode.addEventListener(normalizedType, handler, isCapture);
330
+ }
331
+ }
332
+
333
+ /**
334
+ * Remove an event handler from a DOM element.
335
+ */
336
+ function removeEventHandler(domNode, name) {
337
+ const eventType = name.slice(2).toLowerCase();
338
+ const normalizedType = normalizeEventType(eventType);
339
+ const isCapture = name.endsWith('Capture');
340
+ const key = isCapture ? '__ed_capture_' + normalizedType : '__ed_handler_' + normalizedType;
341
+
342
+ if (domNode[key]) {
343
+ domNode.removeEventListener(normalizedType, domNode[key], isCapture);
344
+ domNode[key] = null;
345
+ }
346
+ }
347
+
348
+ /**
349
+ * Normalize event type names.
350
+ */
351
+ function normalizeEventType(eventType) {
352
+ const map = {
353
+ 'doubleclick': 'dblclick',
354
+ 'change': 'change',
355
+ 'input': 'input',
356
+ 'submit': 'submit',
357
+ 'focusin': 'focusin',
358
+ 'focusout': 'focusout',
359
+ };
360
+ return map[eventType] || eventType;
361
+ }
362
+
363
+ // ─── Event Delegation ─────────────────────────────────────────────────────────
364
+
365
+ /**
366
+ * Set up event delegation on the root container.
367
+ * Captures events at the root and dispatches to the correct handler.
368
+ * @param {HTMLElement} container
369
+ */
370
+ function setupEventDelegation(container) {
371
+ const delegatedEvents = ['click', 'input', 'change', 'submit', 'keydown', 'keyup', 'focus', 'blur'];
372
+
373
+ delegatedEvents.forEach((eventType) => {
374
+ container.addEventListener(eventType, function delegatedHandler(event) {
375
+ let target = event.target;
376
+
377
+ // Walk up the DOM tree to find the handler
378
+ while (target && target !== container) {
379
+ const handlerKey = '__ed_handler_' + eventType;
380
+ if (target[handlerKey]) {
381
+ try {
382
+ target[handlerKey](event);
383
+ } catch (error) {
384
+ console.error('[render] Delegated event handler error:', error);
385
+ }
386
+ break;
387
+ }
388
+ target = target.parentElement;
389
+ }
390
+ });
391
+ });
392
+ }
393
+
394
+ // ─── Ref Assignment ───────────────────────────────────────────────────────────
395
+
396
+ /**
397
+ * Assign a DOM node to a ref.
398
+ */
399
+ function assignRef(ref, domNode) {
400
+ if (!ref) return;
401
+
402
+ if (typeof ref === 'function') {
403
+ try {
404
+ ref(domNode);
405
+ } catch (error) {
406
+ console.error('[render] Callback ref error:', error);
407
+ }
408
+ } else if (typeof ref === 'object' && ref._isRef) {
409
+ ref.current = domNode;
410
+ } else if (typeof ref === 'object' && ref !== null) {
411
+ ref.current = domNode;
412
+ }
413
+ }
414
+
415
+ /**
416
+ * Clean up a ref when an element is removed.
417
+ */
418
+ function detachRef(ref) {
419
+ if (!ref) return;
420
+
421
+ if (typeof ref === 'function') {
422
+ try {
423
+ ref(null);
424
+ } catch (error) {
425
+ console.error('[render] Detach ref error:', error);
426
+ }
427
+ } else if (typeof ref === 'object') {
428
+ ref.current = null;
429
+ }
430
+ }
431
+
432
+ // ─── Core Render Function ─────────────────────────────────────────────────────
433
+
434
+ /**
435
+ * Render a virtual DOM element into a real DOM container.
436
+ *
437
+ * @param {Object} vnode - Virtual DOM element
438
+ * @param {HTMLElement} container - Target DOM container
439
+ * @param {Function} [callback] - Called after render completes
440
+ * @returns {HTMLElement} The rendered DOM node
441
+ */
442
+ function render(vnode, container, callback) {
443
+ if (!container) {
444
+ throw new Error('[render] Target container is not a DOM element');
445
+ }
446
+
447
+ if (!(container instanceof HTMLElement) && !(container instanceof DocumentFragment)) {
448
+ throw new Error('[render] Target container must be an HTMLElement or DocumentFragment');
449
+ }
450
+
451
+ rootContainer = container;
452
+
453
+ // Set up event delegation on first render
454
+ if (!eventDelegationMap.has(container)) {
455
+ setupEventDelegation(container);
456
+ eventDelegationMap.set(container, true);
457
+ }
458
+
459
+ // Handle null/undefined/boolean vnodes
460
+ if (vnode === null || vnode === undefined || typeof vnode === 'boolean') {
461
+ while (container.firstChild) {
462
+ container.removeChild(container.firstChild);
463
+ }
464
+ if (typeof callback === 'function') callback();
465
+ return container;
466
+ }
467
+
468
+ // Render the vnode to DOM
469
+ const domNode = renderVNode(vnode, container, null);
470
+
471
+ // Clear container and insert new DOM
472
+ while (container.firstChild) {
473
+ container.removeChild(container.firstChild);
474
+ }
475
+ container.appendChild(domNode);
476
+
477
+ if (typeof callback === 'function') callback();
478
+
479
+ return domNode;
480
+ }
481
+
482
+ /**
483
+ * Render a virtual DOM node to a real DOM node.
484
+ * @param {Object} vnode
485
+ * @param {HTMLElement} container
486
+ * @param {string|null} namespace
487
+ * @returns {HTMLElement|Text}
488
+ */
489
+ function renderVNode(vnode, container, namespace) {
490
+ // Handle primitive values
491
+ if (typeof vnode === 'string' || typeof vnode === 'number') {
492
+ return createTextNode(String(vnode));
493
+ }
494
+
495
+ // Handle null/undefined/boolean
496
+ if (vnode === null || vnode === undefined || typeof vnode === 'boolean') {
497
+ return createCommentNode('empty');
498
+ }
499
+
500
+ // Handle arrays
501
+ if (Array.isArray(vnode)) {
502
+ const fragment = createDocumentFragment();
503
+ vnode.forEach((child) => {
504
+ const childNode = renderVNode(child, fragment, namespace);
505
+ if (childNode) fragment.appendChild(childNode);
506
+ });
507
+ return fragment;
508
+ }
509
+
510
+ // Handle invalid vnodes
511
+ if (typeof vnode !== 'object') {
512
+ console.warn('[render] Invalid vnode type:', typeof vnode);
513
+ return createCommentNode('invalid');
514
+ }
515
+
516
+ // Text node
517
+ if (vnode.type === TEXT_NODE_TYPE) {
518
+ return createTextNode(vnode.props.nodeValue || '');
519
+ }
520
+
521
+ // Fragment
522
+ if (vnode.type === REACT_FRAGMENT_TYPE) {
523
+ return renderFragment(vnode, container, namespace);
524
+ }
525
+
526
+ // Portal
527
+ if (vnode.$$typeof === Symbol.for('elementdrawing.portal')) {
528
+ return renderPortal(vnode, namespace);
529
+ }
530
+
531
+ // Suspense
532
+ if (vnode.type === REACT_SUSPENSE_TYPE) {
533
+ return renderSuspense(vnode, container, namespace);
534
+ }
535
+
536
+ // StrictMode
537
+ if (vnode.type === REACT_STRICT_MODE_TYPE) {
538
+ return renderStrictMode(vnode, container, namespace);
539
+ }
540
+
541
+ // Error Boundary
542
+ if (vnode.type === REACT_ERROR_BOUNDARY_TYPE) {
543
+ return renderErrorBoundaryVNode(vnode, container, namespace);
544
+ }
545
+
546
+ // Lazy component
547
+ if (vnode.$$typeof === Symbol.for('elementdrawing.lazy')) {
548
+ return renderLazyComponent(vnode, container, namespace);
549
+ }
550
+
551
+ // Function component
552
+ if (typeof vnode.type === 'function' && !vnode.type._isClassComponent) {
553
+ return renderFunctionComponent(vnode, container, namespace);
554
+ }
555
+
556
+ // Class component
557
+ if (typeof vnode.type === 'function' && vnode.type._isClassComponent) {
558
+ return renderClassComponent(vnode, container, namespace);
559
+ }
560
+
561
+ // Host element (HTML/SVG)
562
+ if (typeof vnode.type === 'string') {
563
+ return renderHostElement(vnode, container, namespace);
564
+ }
565
+
566
+ console.warn('[render] Unknown vnode type:', vnode.type);
567
+ return createCommentNode('unknown');
568
+ }
569
+
570
+ // ─── Special Renderers ────────────────────────────────────────────────────────
571
+
572
+ function renderFragment(vnode, container, namespace) {
573
+ const fragment = createDocumentFragment();
574
+ const children = flattenChildrenFromVNode(vnode);
575
+
576
+ children.forEach((child) => {
577
+ const childNode = renderVNode(child, fragment, namespace);
578
+ if (childNode) fragment.appendChild(childNode);
579
+ });
580
+
581
+ return fragment;
582
+ }
583
+
584
+ function renderPortal(vnode, namespace) {
585
+ const { children, containerInfo } = vnode;
586
+ if (!containerInfo) {
587
+ console.warn('[render] Portal requires a container element');
588
+ return createCommentNode('portal-no-container');
589
+ }
590
+
591
+ const childNodes = Array.isArray(children) ? children : [children];
592
+ childNodes.forEach((child) => {
593
+ const domNode = renderVNode(child, containerInfo, namespace);
594
+ if (domNode) containerInfo.appendChild(domNode);
595
+ });
596
+
597
+ return createCommentNode('portal');
598
+ }
599
+
600
+ function renderSuspense(vnode, container, namespace) {
601
+ try {
602
+ const children = flattenChildrenFromVNode(vnode);
603
+ const fragment = createDocumentFragment();
604
+
605
+ children.forEach((child) => {
606
+ const childNode = renderVNode(child, fragment, namespace);
607
+ if (childNode) fragment.appendChild(childNode);
608
+ });
609
+
610
+ return fragment;
611
+ } catch (error) {
612
+ // If a child suspends, render the fallback
613
+ if (vnode.props && vnode.props.fallback) {
614
+ return renderVNode(vnode.props.fallback, container, namespace);
615
+ }
616
+ return createCommentNode('suspense-error');
617
+ }
618
+ }
619
+
620
+ function renderStrictMode(vnode, container, namespace) {
621
+ // StrictMode renders children normally but enables double-invoke
622
+ const children = flattenChildrenFromVNode(vnode);
623
+ const fragment = createDocumentFragment();
624
+
625
+ children.forEach((child) => {
626
+ const childNode = renderVNode(child, fragment, namespace);
627
+ if (childNode) fragment.appendChild(childNode);
628
+ });
629
+
630
+ return fragment;
631
+ }
632
+
633
+ function renderErrorBoundaryVNode(vnode, container, namespace) {
634
+ try {
635
+ const children = flattenChildrenFromVNode(vnode);
636
+ const fragment = createDocumentFragment();
637
+
638
+ children.forEach((child) => {
639
+ const childNode = renderVNode(child, fragment, namespace);
640
+ if (childNode) fragment.appendChild(childNode);
641
+ });
642
+
643
+ return fragment;
644
+ } catch (error) {
645
+ if (vnode.props && vnode.props.fallback) {
646
+ return renderVNode(vnode.props.fallback, container, namespace);
647
+ }
648
+ if (vnode.props && typeof vnode.props.onError === 'function') {
649
+ vnode.props.onError(error);
650
+ }
651
+ return createCommentNode('error-boundary');
652
+ }
653
+ }
654
+
655
+ function renderLazyComponent(vnode, container, namespace) {
656
+ const payload = vnode._payload || vnode.type._payload;
657
+ if (!payload) return createCommentNode('lazy-no-payload');
658
+
659
+ if (payload._status === 0) {
660
+ // Resolved
661
+ const Component = payload._result;
662
+ return renderVNode(createElement(Component, vnode.props), container, namespace);
663
+ }
664
+
665
+ if (payload._status === 1) {
666
+ // Rejected
667
+ throw payload._result;
668
+ }
669
+
670
+ // Pending - trigger init
671
+ if (vnode.type._init) {
672
+ vnode.type._init(payload);
673
+ }
674
+
675
+ return createCommentNode('lazy-pending');
676
+ }
677
+
678
+ function renderFunctionComponent(vnode, container, namespace) {
679
+ try {
680
+ const { type, props } = vnode;
681
+ const renderedVNode = type(props);
682
+ return renderVNode(renderedVNode, container, namespace);
683
+ } catch (error) {
684
+ console.error('[render] Function component error:', error);
685
+ return renderErrorFallback(error, vnode);
686
+ }
687
+ }
688
+
689
+ function renderClassComponent(vnode, container, namespace) {
690
+ try {
691
+ const { type, props } = vnode;
692
+ const instance = new type(props);
693
+ instance._vnode = vnode;
694
+
695
+ if (vnode.ref) {
696
+ assignRef(vnode.ref, instance);
697
+ }
698
+
699
+ const renderedVNode = instance.render();
700
+ return renderVNode(renderedVNode, container, namespace);
701
+ } catch (error) {
702
+ console.error('[render] Class component error:', error);
703
+ return renderErrorFallback(error, vnode);
704
+ }
705
+ }
706
+
707
+ function renderHostElement(vnode, container, namespace) {
708
+ const { type, props, ref } = vnode;
709
+
710
+ // Determine namespace
711
+ let currentNamespace = namespace;
712
+ if (type === 'svg') {
713
+ currentNamespace = SVG_NAMESPACE;
714
+ } else if (type === 'math') {
715
+ currentNamespace = MATH_NAMESPACE;
716
+ }
717
+
718
+ // Create DOM element
719
+ const domNode = createDOMElement(type, props, currentNamespace);
720
+
721
+ // Apply attributes
722
+ for (const propName in props) {
723
+ if (!props.hasOwnProperty(propName)) continue;
724
+ if (propName === 'children') continue;
725
+ if (propName === 'dangerouslySetInnerHTML') continue;
726
+
727
+ setAttribute(domNode, propName, props[propName]);
728
+ }
729
+
730
+ // Handle dangerouslySetInnerHTML
731
+ if (props.dangerouslySetInnerHTML && props.dangerouslySetInnerHTML.__html) {
732
+ domNode.innerHTML = props.dangerouslySetInnerHTML.__html;
733
+ } else if (!VOID_ELEMENTS.has(type)) {
734
+ // Render children
735
+ const children = flattenChildrenFromVNode(vnode);
736
+ children.forEach((child) => {
737
+ const childNode = renderVNode(child, domNode, currentNamespace);
738
+ if (childNode) domNode.appendChild(childNode);
739
+ });
740
+ }
741
+
742
+ // Assign ref
743
+ if (ref) {
744
+ assignRef(ref, domNode);
745
+ }
746
+
747
+ // Store vnode reference on DOM node for reconciliation
748
+ domNode.__ed_vnode = vnode;
749
+
750
+ return domNode;
751
+ }
752
+
753
+ function renderErrorFallback(error, vnode) {
754
+ const fallback = vnode.props?.fallback;
755
+ if (fallback) {
756
+ return renderVNode(fallback, null, null);
757
+ }
758
+ return createCommentNode('error: ' + error.message);
759
+ }
760
+
761
+ // ─── Hydration Support ────────────────────────────────────────────────────────
762
+
763
+ /**
764
+ * Hydrate a virtual DOM tree onto existing DOM nodes.
765
+ *
766
+ * @param {Object} vnode
767
+ * @param {HTMLElement} container
768
+ * @param {Function} [callback]
769
+ * @returns {HTMLElement}
770
+ */
771
+ function hydrate(vnode, container, callback) {
772
+ if (!container) {
773
+ throw new Error('[hydrate] Target container is not a DOM element');
774
+ }
775
+
776
+ const domNode = hydrateVNode(vnode, container, container.firstChild, null);
777
+
778
+ if (typeof callback === 'function') callback();
779
+
780
+ return domNode;
781
+ }
782
+
783
+ /**
784
+ * Hydrate a single virtual DOM node.
785
+ */
786
+ function hydrateVNode(vnode, parent, existingNode, namespace) {
787
+ if (!existingNode) {
788
+ return renderVNode(vnode, parent, namespace);
789
+ }
790
+
791
+ // Handle text nodes
792
+ if (existingNode.nodeType === 3) { // Node.TEXT_NODE
793
+ if (typeof vnode === 'string' || typeof vnode === 'number') {
794
+ const text = String(vnode);
795
+ if (existingNode.textContent !== text) {
796
+ existingNode.textContent = text;
797
+ }
798
+ return existingNode;
799
+ }
800
+ const newNode = renderVNode(vnode, parent, namespace);
801
+ parent.replaceChild(newNode, existingNode);
802
+ return newNode;
803
+ }
804
+
805
+ // Handle element nodes
806
+ if (existingNode.nodeType === 1) { // Node.ELEMENT_NODE
807
+ if (typeof vnode === 'object' && vnode.type && typeof vnode.type === 'string' &&
808
+ vnode.type === existingNode.tagName.toLowerCase()) {
809
+ // Same tag - update attributes
810
+ for (const propName in vnode.props) {
811
+ if (propName === 'children') continue;
812
+ setAttribute(existingNode, propName, vnode.props[propName]);
813
+ }
814
+
815
+ // Hydrate children
816
+ const children = flattenChildrenFromVNode(vnode);
817
+ let existingChild = existingNode.firstChild;
818
+
819
+ children.forEach((childVNode) => {
820
+ existingChild = hydrateVNode(childVNode, existingNode, existingChild, namespace);
821
+ if (existingChild) existingChild = existingChild.nextSibling;
822
+ });
823
+
824
+ // Remove extra DOM children
825
+ while (existingChild) {
826
+ const next = existingChild.nextSibling;
827
+ existingNode.removeChild(existingChild);
828
+ existingChild = next;
829
+ }
830
+
831
+ // Store vnode reference
832
+ existingNode.__ed_vnode = vnode;
833
+
834
+ // Assign ref
835
+ if (vnode.ref) {
836
+ assignRef(vnode.ref, existingNode);
837
+ }
838
+
839
+ return existingNode;
840
+ }
841
+
842
+ // Different tag - replace
843
+ const newNode = renderVNode(vnode, parent, namespace);
844
+ parent.replaceChild(newNode, existingNode);
845
+ return newNode;
846
+ }
847
+
848
+ return renderVNode(vnode, parent, namespace);
849
+ }
850
+
851
+ // ─── Utility ──────────────────────────────────────────────────────────────────
852
+
853
+ function flattenChildrenFromVNode(vnode) {
854
+ if (!vnode || !vnode.props || !vnode.props.children) return [];
855
+ const children = vnode.props.children;
856
+ if (Array.isArray(children)) return children;
857
+ return [children];
858
+ }
859
+
860
+ /**
861
+ * Unmount a rendered tree from a container.
862
+ * @param {HTMLElement} container
863
+ */
864
+ function unmountContainer(container) {
865
+ if (!container) return;
866
+
867
+ // Detach refs and clean up
868
+ const walker = document.createTreeWalker(container, NodeFilter.SHOW_ELEMENT);
869
+ let node;
870
+ while ((node = walker.nextNode())) {
871
+ if (node.__ed_vnode && node.__ed_vnode.ref) {
872
+ detachRef(node.__ed_vnode.ref);
873
+ }
874
+ delete node.__ed_vnode;
875
+ }
876
+
877
+ // Remove all children
878
+ while (container.firstChild) {
879
+ container.removeChild(container.firstChild);
880
+ }
881
+
882
+ eventDelegationMap.delete(container);
883
+ }
884
+
885
+ // ─── Exports ──────────────────────────────────────────────────────────────────
886
+
887
+ module.exports = {
888
+ render,
889
+ hydrate,
890
+ unmountContainer,
891
+ createDOMElement,
892
+ createTextNode,
893
+ createCommentNode,
894
+ createDocumentFragment,
895
+ setAttribute,
896
+ removeAttribute,
897
+ setProperty,
898
+ applyStyle,
899
+ applyClassName,
900
+ setEventHandler,
901
+ removeEventHandler,
902
+ setupEventDelegation,
903
+ assignRef,
904
+ detachRef,
905
+ renderVNode,
906
+ hydrateVNode,
907
+ camelToKebab,
908
+ normalizeEventType,
909
+ VOID_ELEMENTS,
910
+ SVG_TAGS,
911
+ BOOLEAN_ATTRIBUTES,
912
+ SVG_NAMESPACE,
913
+ MATH_NAMESPACE,
914
+ };