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,903 @@
1
+ /**
2
+ * commit - Commit Phase Operations
3
+ * ElementDrawing Framework - Applies reconciliation results to the DOM with
4
+ * insertions, deletions, updates, move operations, effect execution,
5
+ * ref management, error handling, snapshots, batch commit, and
6
+ * priority-based commit scheduling.
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const { INSERT, MOVE, REMOVE, UPDATE, REPLACE, SKIP, REORDER } = require('./reconcile');
12
+
13
+ // ─── Commit Priority ──────────────────────────────────────────────────────────
14
+
15
+ const COMMIT_PRIORITY = {
16
+ IMMEDIATE: 0,
17
+ NORMAL: 1,
18
+ LOW: 2,
19
+ };
20
+
21
+ // ─── Internal State ───────────────────────────────────────────────────────────
22
+
23
+ let isCommitting = false;
24
+ const commitQueue = [];
25
+ const pendingLayoutEffects = [];
26
+ const pendingPassiveEffects = [];
27
+ const pendingInsertionEffects = [];
28
+ const pendingRefCleanups = [];
29
+ const pendingRefAssignments = [];
30
+ const errorRecoveryStack = [];
31
+ let snapshotBeforeMutations = null;
32
+ let currentCommitPriority = COMMIT_PRIORITY.NORMAL;
33
+
34
+ // ─── Commit Statistics ────────────────────────────────────────────────────────
35
+
36
+ let commitStats = {
37
+ totalCommits: 0,
38
+ totalOperations: 0,
39
+ totalInsertions: 0,
40
+ totalDeletions: 0,
41
+ totalUpdates: 0,
42
+ totalReplacements: 0,
43
+ totalMoves: 0,
44
+ lastCommitTime: 0,
45
+ lastCommitDuration: 0,
46
+ };
47
+
48
+ /**
49
+ * Get commit statistics.
50
+ * @returns {Object}
51
+ */
52
+ function getCommitStats() {
53
+ return Object.assign({}, commitStats);
54
+ }
55
+
56
+ /**
57
+ * Reset commit statistics.
58
+ */
59
+ function resetCommitStats() {
60
+ commitStats = {
61
+ totalCommits: 0,
62
+ totalOperations: 0,
63
+ totalInsertions: 0,
64
+ totalDeletions: 0,
65
+ totalUpdates: 0,
66
+ totalReplacements: 0,
67
+ totalMoves: 0,
68
+ lastCommitTime: 0,
69
+ lastCommitDuration: 0,
70
+ };
71
+ }
72
+
73
+ // ─── Commit Phase Entry ───────────────────────────────────────────────────────
74
+
75
+ /**
76
+ * Commit reconciliation results to the DOM.
77
+ * Processes all diff operations in the correct order:
78
+ * 1. Snapshot before mutations
79
+ * 2. DOM deletions
80
+ * 3. DOM insertions
81
+ * 4. DOM updates
82
+ * 5. DOM moves
83
+ * 6. DOM replacements
84
+ * 7. Layout effects
85
+ * 8. Ref cleanups and assignments
86
+ * 9. Passive effects
87
+ *
88
+ * @param {Array} operations - Diff operations from reconciliation
89
+ * @param {HTMLElement} container - Root DOM container
90
+ * @param {Object} [options] - Commit options
91
+ * @param {boolean} [options.batchCommit=false]
92
+ * @param {Function} [options.onComplete]
93
+ * @param {number} [options.priority]
94
+ * @returns {boolean} True if commit succeeded
95
+ */
96
+ function commitWork(operations, container, options = {}) {
97
+ if (isCommitting && !options.batchCommit) {
98
+ commitQueue.push({ operations, container, options });
99
+ return false;
100
+ }
101
+
102
+ const commitStart = Date.now();
103
+ isCommitting = true;
104
+
105
+ try {
106
+ // ─── Phase 1: Snapshot before mutations ──────────────────────────────
107
+ if (operations.some((op) => op.type === REMOVE || op.type === REPLACE)) {
108
+ snapshotBeforeMutations = captureSnapshot(container);
109
+ }
110
+
111
+ // ─── Phase 2: Process deletions ──────────────────────────────────────
112
+ const deletions = operations.filter((op) => op.type === REMOVE);
113
+ processDeletions(deletions, container);
114
+ commitStats.totalDeletions += deletions.length;
115
+
116
+ // ─── Phase 3: Process insertions ─────────────────────────────────────
117
+ const insertions = operations.filter((op) => op.type === INSERT);
118
+ processInsertions(insertions, container);
119
+ commitStats.totalInsertions += insertions.length;
120
+
121
+ // ─── Phase 4: Process updates ────────────────────────────────────────
122
+ const updates = operations.filter((op) => op.type === UPDATE);
123
+ processUpdates(updates, container);
124
+ commitStats.totalUpdates += updates.length;
125
+
126
+ // ─── Phase 5: Process moves ──────────────────────────────────────────
127
+ const moves = operations.filter((op) => op.type === MOVE);
128
+ processMoves(moves, container);
129
+ commitStats.totalMoves += moves.length;
130
+
131
+ // ─── Phase 6: Process replacements ───────────────────────────────────
132
+ const replacements = operations.filter((op) => op.type === REPLACE);
133
+ processReplacements(replacements, container);
134
+ commitStats.totalReplacements += replacements.length;
135
+
136
+ // ─── Phase 7: Execute insertion effects ──────────────────────────────
137
+ flushPendingInsertionEffects();
138
+
139
+ // ─── Phase 8: Execute layout effects ─────────────────────────────────
140
+ flushPendingLayoutEffects();
141
+
142
+ // ─── Phase 9: Ref cleanups and assignments ───────────────────────────
143
+ processRefCleanups();
144
+ processRefAssignments();
145
+
146
+ // ─── Phase 10: Schedule passive effects ──────────────────────────────
147
+ schedulePassiveEffects();
148
+
149
+ // ─── Update stats ────────────────────────────────────────────────────
150
+ commitStats.totalCommits++;
151
+ commitStats.totalOperations += operations.length;
152
+ commitStats.lastCommitTime = commitStart;
153
+ commitStats.lastCommitDuration = Date.now() - commitStart;
154
+
155
+ // ─── Phase 11: Notify completion ──────────────────────────────────────
156
+ if (typeof options.onComplete === 'function') {
157
+ options.onComplete();
158
+ }
159
+
160
+ return true;
161
+ } catch (error) {
162
+ console.error('[commit] Commit phase error:', error);
163
+ handleErrorDuringCommit(error, container);
164
+ return false;
165
+ } finally {
166
+ isCommitting = false;
167
+
168
+ // Process queued commits
169
+ if (commitQueue.length > 0) {
170
+ const next = commitQueue.shift();
171
+ commitWork(next.operations, next.container, next.options);
172
+ }
173
+ }
174
+ }
175
+
176
+ // ─── DOM Insertions ───────────────────────────────────────────────────────────
177
+
178
+ /**
179
+ * Process INSERT operations from the diff.
180
+ */
181
+ function processInsertions(operations, container) {
182
+ operations.forEach((op) => {
183
+ if (op.type !== INSERT) return;
184
+
185
+ const { vnode, parentVNode, index } = op.payload;
186
+ if (!vnode) return;
187
+
188
+ try {
189
+ const domNode = createDOMNodeFromVNode(vnode);
190
+ if (!domNode) return;
191
+
192
+ // Find the parent DOM node
193
+ const parentDOMNode = findParentDOMNode(container, parentVNode);
194
+ if (!parentDOMNode) {
195
+ container.appendChild(domNode);
196
+ return;
197
+ }
198
+
199
+ // Insert at the correct position
200
+ if (index !== undefined && index !== null) {
201
+ const referenceNode = parentDOMNode.childNodes[index];
202
+ if (referenceNode) {
203
+ parentDOMNode.insertBefore(domNode, referenceNode);
204
+ } else {
205
+ parentDOMNode.appendChild(domNode);
206
+ }
207
+ } else {
208
+ parentDOMNode.appendChild(domNode);
209
+ }
210
+
211
+ // Queue layout effects for the inserted node
212
+ queueLayoutEffects(vnode, 'mount');
213
+
214
+ // Queue ref assignments
215
+ if (vnode.ref) {
216
+ pendingRefAssignments.push({ ref: vnode.ref, domNode });
217
+ }
218
+
219
+ // Recursively insert children
220
+ if (vnode.props && vnode.props.children) {
221
+ insertChildren(vnode, domNode);
222
+ }
223
+ } catch (error) {
224
+ console.error('[commit] Insertion error:', error);
225
+ handleErrorDuringCommit(error, container);
226
+ }
227
+ });
228
+ }
229
+
230
+ /**
231
+ * Recursively insert children of a vnode.
232
+ */
233
+ function insertChildren(vnode, parentDOMNode) {
234
+ if (!vnode.props || !vnode.props.children) return;
235
+
236
+ const children = Array.isArray(vnode.props.children)
237
+ ? vnode.props.children
238
+ : [vnode.props.children];
239
+
240
+ children.forEach((child) => {
241
+ if (!child) return;
242
+
243
+ const childDOMNode = createDOMNodeFromVNode(child);
244
+ if (childDOMNode) {
245
+ parentDOMNode.appendChild(childDOMNode);
246
+
247
+ if (child.ref) {
248
+ pendingRefAssignments.push({ ref: child.ref, domNode: childDOMNode });
249
+ }
250
+
251
+ insertChildren(child, childDOMNode);
252
+ }
253
+ });
254
+ }
255
+
256
+ // ─── DOM Deletions ────────────────────────────────────────────────────────────
257
+
258
+ /**
259
+ * Process REMOVE operations from the diff.
260
+ */
261
+ function processDeletions(operations, container) {
262
+ operations.forEach((op) => {
263
+ if (op.type !== REMOVE) return;
264
+
265
+ const { vnode, parentVNode } = op.payload;
266
+ if (!vnode) return;
267
+
268
+ try {
269
+ const domNode = findDOMNodeForVNode(container, vnode);
270
+ if (!domNode) return;
271
+
272
+ // Queue ref cleanups before removal
273
+ if (vnode.ref) {
274
+ pendingRefCleanups.push({ ref: vnode.ref });
275
+ }
276
+
277
+ // Queue unmount layout effects
278
+ queueLayoutEffects(vnode, 'unmount');
279
+
280
+ // Queue unmount passive effects
281
+ queuePassiveEffects(vnode, 'unmount');
282
+
283
+ // Recursively clean up children
284
+ cleanupChildVNodes(vnode);
285
+
286
+ // Remove the DOM node
287
+ if (domNode.parentNode) {
288
+ domNode.parentNode.removeChild(domNode);
289
+ }
290
+
291
+ // Clean up stored vnode reference
292
+ if (domNode.__ed_vnode) {
293
+ delete domNode.__ed_vnode;
294
+ }
295
+ } catch (error) {
296
+ console.error('[commit] Deletion error:', error);
297
+ handleErrorDuringCommit(error, container);
298
+ }
299
+ });
300
+ }
301
+
302
+ /**
303
+ * Recursively clean up child vnodes during deletion.
304
+ */
305
+ function cleanupChildVNodes(vnode) {
306
+ if (!vnode.props || !vnode.props.children) return;
307
+
308
+ const children = Array.isArray(vnode.props.children)
309
+ ? vnode.props.children
310
+ : [vnode.props.children];
311
+
312
+ children.forEach((child) => {
313
+ if (!child || typeof child !== 'object') return;
314
+
315
+ if (child.ref) {
316
+ pendingRefCleanups.push({ ref: child.ref });
317
+ }
318
+
319
+ queueLayoutEffects(child, 'unmount');
320
+ queuePassiveEffects(child, 'unmount');
321
+ cleanupChildVNodes(child);
322
+ });
323
+ }
324
+
325
+ // ─── DOM Updates ──────────────────────────────────────────────────────────────
326
+
327
+ /**
328
+ * Process UPDATE operations from the diff.
329
+ */
330
+ function processUpdates(operations, container) {
331
+ operations.forEach((op) => {
332
+ if (op.type !== UPDATE) return;
333
+
334
+ const { vnode, changes, changeType } = op.payload;
335
+ if (!vnode) return;
336
+
337
+ try {
338
+ const domNode = findDOMNodeForVNode(container, vnode);
339
+ if (!domNode) return;
340
+
341
+ switch (changeType) {
342
+ case 'props':
343
+ applyPropChanges(domNode, changes);
344
+ break;
345
+ case 'style':
346
+ applyStyleChanges(domNode, changes);
347
+ break;
348
+ case 'events':
349
+ applyEventChanges(domNode, changes);
350
+ break;
351
+ case 'ref':
352
+ applyRefChanges(domNode, changes);
353
+ break;
354
+ case 'text':
355
+ applyTextChanges(domNode, changes);
356
+ break;
357
+ case 'innerHTML':
358
+ applyInnerHTMLChanges(domNode, changes);
359
+ break;
360
+ }
361
+
362
+ // Queue update layout effects
363
+ queueLayoutEffects(vnode, 'update');
364
+ } catch (error) {
365
+ console.error('[commit] Update error:', error);
366
+ handleErrorDuringCommit(error, container);
367
+ }
368
+ });
369
+ }
370
+
371
+ /**
372
+ * Apply prop changes to a DOM node.
373
+ */
374
+ function applyPropChanges(domNode, changes) {
375
+ changes.forEach((change) => {
376
+ switch (change.type) {
377
+ case 'set':
378
+ if (change.key === 'className') {
379
+ domNode.className = change.value || '';
380
+ } else if (change.key === 'dangerouslySetInnerHTML') {
381
+ if (change.value && change.value.__html) {
382
+ domNode.innerHTML = change.value.__html;
383
+ }
384
+ } else if (change.key === 'value') {
385
+ domNode.value = change.value;
386
+ } else if (change.key === 'checked') {
387
+ domNode.checked = change.value;
388
+ } else if (typeof change.value === 'boolean') {
389
+ if (change.value) {
390
+ domNode.setAttribute(change.key, '');
391
+ } else {
392
+ domNode.removeAttribute(change.key);
393
+ }
394
+ } else {
395
+ domNode.setAttribute(change.key, change.value);
396
+ }
397
+ break;
398
+
399
+ case 'remove':
400
+ if (change.key === 'className') {
401
+ domNode.className = '';
402
+ } else if (change.key === 'value') {
403
+ domNode.value = '';
404
+ } else if (change.key === 'checked') {
405
+ domNode.checked = false;
406
+ } else if (change.key === 'dangerouslySetInnerHTML') {
407
+ domNode.innerHTML = '';
408
+ } else {
409
+ domNode.removeAttribute(change.key);
410
+ }
411
+ break;
412
+ }
413
+ });
414
+ }
415
+
416
+ /**
417
+ * Apply style changes to a DOM node.
418
+ */
419
+ function applyStyleChanges(domNode, changes) {
420
+ changes.forEach((change) => {
421
+ switch (change.type) {
422
+ case 'replace':
423
+ domNode.style.cssText = change.value;
424
+ break;
425
+ case 'set':
426
+ domNode.style.setProperty(change.key, change.value);
427
+ break;
428
+ case 'remove':
429
+ domNode.style.removeProperty(change.key);
430
+ break;
431
+ }
432
+ });
433
+ }
434
+
435
+ /**
436
+ * Apply event handler changes.
437
+ */
438
+ function applyEventChanges(domNode, changes) {
439
+ changes.forEach((change) => {
440
+ const eventType = change.key.slice(2).toLowerCase();
441
+ const isCapture = change.key.endsWith('Capture');
442
+ const handlerKey = isCapture ? '__ed_capture_' + eventType : '__ed_handler_' + eventType;
443
+
444
+ switch (change.type) {
445
+ case 'set':
446
+ if (domNode[handlerKey]) {
447
+ domNode.removeEventListener(eventType, domNode[handlerKey], isCapture);
448
+ }
449
+ if (typeof change.value === 'function') {
450
+ domNode[handlerKey] = change.value;
451
+ domNode.addEventListener(eventType, change.value, isCapture);
452
+ }
453
+ break;
454
+
455
+ case 'remove':
456
+ if (domNode[handlerKey]) {
457
+ domNode.removeEventListener(eventType, domNode[handlerKey], isCapture);
458
+ domNode[handlerKey] = null;
459
+ }
460
+ break;
461
+ }
462
+ });
463
+ }
464
+
465
+ /**
466
+ * Apply ref changes.
467
+ */
468
+ function applyRefChanges(domNode, changes) {
469
+ changes.forEach((change) => {
470
+ if (change.oldRef) {
471
+ if (typeof change.oldRef === 'function') {
472
+ change.oldRef(null);
473
+ } else if (typeof change.oldRef === 'object') {
474
+ change.oldRef.current = null;
475
+ }
476
+ }
477
+
478
+ if (change.newRef) {
479
+ if (typeof change.newRef === 'function') {
480
+ change.newRef(domNode);
481
+ } else if (typeof change.newRef === 'object') {
482
+ change.newRef.current = domNode;
483
+ }
484
+ }
485
+ });
486
+ }
487
+
488
+ /**
489
+ * Apply text content changes.
490
+ */
491
+ function applyTextChanges(domNode, changes) {
492
+ changes.forEach((change) => {
493
+ if (change.key === 'nodeValue' && domNode.nodeType === 3) {
494
+ domNode.textContent = change.value;
495
+ }
496
+ });
497
+ }
498
+
499
+ /**
500
+ * Apply innerHTML changes.
501
+ */
502
+ function applyInnerHTMLChanges(domNode, changes) {
503
+ changes.forEach((change) => {
504
+ if (change.type === 'set' && change.value) {
505
+ domNode.innerHTML = change.value.__html || '';
506
+ } else if (change.type === 'remove') {
507
+ domNode.innerHTML = '';
508
+ }
509
+ });
510
+ }
511
+
512
+ // ─── DOM Moves ────────────────────────────────────────────────────────────────
513
+
514
+ /**
515
+ * Process MOVE operations (child reordering).
516
+ */
517
+ function processMoves(operations, container) {
518
+ operations.forEach((op) => {
519
+ if (op.type !== MOVE) return;
520
+
521
+ const { vnode, fromIndex, toIndex } = op.payload;
522
+ if (!vnode) return;
523
+
524
+ try {
525
+ const domNode = findDOMNodeForVNode(container, vnode);
526
+ if (!domNode || !domNode.parentNode) return;
527
+
528
+ const parent = domNode.parentNode;
529
+
530
+ // Remove from old position
531
+ parent.removeChild(domNode);
532
+
533
+ // Insert at new position
534
+ const referenceNode = parent.childNodes[toIndex];
535
+ if (referenceNode) {
536
+ parent.insertBefore(domNode, referenceNode);
537
+ } else {
538
+ parent.appendChild(domNode);
539
+ }
540
+
541
+ // Queue update effects
542
+ queueLayoutEffects(vnode, 'update');
543
+ } catch (error) {
544
+ console.error('[commit] Move error:', error);
545
+ handleErrorDuringCommit(error, container);
546
+ }
547
+ });
548
+ }
549
+
550
+ // ─── DOM Replacements ─────────────────────────────────────────────────────────
551
+
552
+ /**
553
+ * Process REPLACE operations from the diff.
554
+ */
555
+ function processReplacements(operations, container) {
556
+ operations.forEach((op) => {
557
+ if (op.type !== REPLACE) return;
558
+
559
+ const { oldVNode, newVNode } = op.payload;
560
+ if (!oldVNode || !newVNode) return;
561
+
562
+ try {
563
+ const oldDOMNode = findDOMNodeForVNode(container, oldVNode);
564
+ if (!oldDOMNode) return;
565
+
566
+ // Clean up old ref
567
+ if (oldVNode.ref) {
568
+ pendingRefCleanups.push({ ref: oldVNode.ref });
569
+ }
570
+
571
+ // Queue unmount effects for old node
572
+ queueLayoutEffects(oldVNode, 'unmount');
573
+ queuePassiveEffects(oldVNode, 'unmount');
574
+ cleanupChildVNodes(oldVNode);
575
+
576
+ // Create new DOM node
577
+ const newDOMNode = createDOMNodeFromVNode(newVNode);
578
+ if (!newDOMNode) return;
579
+
580
+ // Replace in DOM
581
+ if (oldDOMNode.parentNode) {
582
+ oldDOMNode.parentNode.replaceChild(newDOMNode, oldDOMNode);
583
+ }
584
+
585
+ // Queue mount effects for new node
586
+ queueLayoutEffects(newVNode, 'mount');
587
+ queuePassiveEffects(newVNode, 'mount');
588
+
589
+ // Assign new ref
590
+ if (newVNode.ref) {
591
+ pendingRefAssignments.push({ ref: newVNode.ref, domNode: newDOMNode });
592
+ }
593
+ } catch (error) {
594
+ console.error('[commit] Replacement error:', error);
595
+ handleErrorDuringCommit(error, container);
596
+ }
597
+ });
598
+ }
599
+
600
+ // ─── Effect Execution ─────────────────────────────────────────────────────────
601
+
602
+ /**
603
+ * Queue a layout effect for execution.
604
+ */
605
+ function queueLayoutEffects(vnode, phase) {
606
+ if (!vnode || !vnode._hooks) return;
607
+
608
+ vnode._hooks.forEach((hook) => {
609
+ if (hook && (hook.type === 'layout-effect' || hook.type === 'insertion-effect')) {
610
+ pendingLayoutEffects.push({ hook, phase });
611
+ }
612
+ });
613
+ }
614
+
615
+ /**
616
+ * Queue a passive effect for execution.
617
+ */
618
+ function queuePassiveEffects(vnode, phase) {
619
+ if (!vnode || !vnode._hooks) return;
620
+
621
+ vnode._hooks.forEach((hook) => {
622
+ if (hook && hook.type === 'passive-effect') {
623
+ pendingPassiveEffects.push({ hook, phase });
624
+ }
625
+ });
626
+ }
627
+
628
+ /**
629
+ * Flush pending insertion effects (synchronous, before layout).
630
+ */
631
+ function flushPendingInsertionEffects() {
632
+ while (pendingInsertionEffects.length > 0) {
633
+ const { hook, phase } = pendingInsertionEffects.shift();
634
+ try {
635
+ if (phase === 'unmount' && typeof hook.cleanup === 'function') {
636
+ hook.cleanup();
637
+ hook.cleanup = null;
638
+ } else if (hook._shouldRun !== false) {
639
+ const cleanup = hook.create();
640
+ if (typeof cleanup === 'function') {
641
+ hook.cleanup = cleanup;
642
+ }
643
+ }
644
+ } catch (error) {
645
+ console.error('[commit] Insertion effect error:', error);
646
+ }
647
+ }
648
+ }
649
+
650
+ /**
651
+ * Flush pending layout effects (synchronous).
652
+ */
653
+ function flushPendingLayoutEffects() {
654
+ while (pendingLayoutEffects.length > 0) {
655
+ const { hook, phase } = pendingLayoutEffects.shift();
656
+
657
+ try {
658
+ if (phase === 'unmount' && typeof hook.cleanup === 'function') {
659
+ hook.cleanup();
660
+ hook.cleanup = null;
661
+ } else if ((phase === 'mount' || phase === 'update') && hook._shouldRun !== false) {
662
+ const cleanup = hook.create();
663
+ if (typeof cleanup === 'function') {
664
+ hook.cleanup = cleanup;
665
+ }
666
+ }
667
+ } catch (error) {
668
+ console.error('[commit] Layout effect error:', error);
669
+ }
670
+ }
671
+ }
672
+
673
+ /**
674
+ * Schedule passive effects for async execution after paint.
675
+ */
676
+ function schedulePassiveEffects() {
677
+ if (pendingPassiveEffects.length === 0) return;
678
+
679
+ const effects = pendingPassiveEffects.splice(0);
680
+ requestAnimationFrame(() => {
681
+ setTimeout(() => {
682
+ effects.forEach(({ hook, phase }) => {
683
+ try {
684
+ if (phase === 'unmount' && typeof hook.cleanup === 'function') {
685
+ hook.cleanup();
686
+ hook.cleanup = null;
687
+ } else if ((phase === 'mount' || phase === 'update') && hook._shouldRun !== false) {
688
+ const cleanup = hook.create();
689
+ if (typeof cleanup === 'function') {
690
+ hook.cleanup = cleanup;
691
+ }
692
+ }
693
+ } catch (error) {
694
+ console.error('[commit] Passive effect error:', error);
695
+ }
696
+ });
697
+ }, 0);
698
+ });
699
+ }
700
+
701
+ // ─── Ref Management ───────────────────────────────────────────────────────────
702
+
703
+ function processRefCleanups() {
704
+ while (pendingRefCleanups.length > 0) {
705
+ const { ref } = pendingRefCleanups.shift();
706
+ try {
707
+ if (typeof ref === 'function') {
708
+ ref(null);
709
+ } else if (typeof ref === 'object' && ref !== null) {
710
+ ref.current = null;
711
+ }
712
+ } catch (error) {
713
+ console.error('[commit] Ref cleanup error:', error);
714
+ }
715
+ }
716
+ }
717
+
718
+ function processRefAssignments() {
719
+ while (pendingRefAssignments.length > 0) {
720
+ const { ref, domNode } = pendingRefAssignments.shift();
721
+ try {
722
+ if (typeof ref === 'function') {
723
+ ref(domNode);
724
+ } else if (typeof ref === 'object' && ref !== null) {
725
+ ref.current = domNode;
726
+ }
727
+ } catch (error) {
728
+ console.error('[commit] Ref assignment error:', error);
729
+ }
730
+ }
731
+ }
732
+
733
+ // ─── Error Handling ───────────────────────────────────────────────────────────
734
+
735
+ function handleErrorDuringCommit(error, container) {
736
+ errorRecoveryStack.push({ error, container, timestamp: Date.now() });
737
+
738
+ if (container && container.__ed_errorBoundary) {
739
+ const boundary = container.__ed_errorBoundary;
740
+ if (typeof boundary._handleError === 'function') {
741
+ try {
742
+ boundary._handleError(error);
743
+ } catch (recoveryError) {
744
+ console.error('[commit] Error boundary also failed:', recoveryError);
745
+ }
746
+ }
747
+ }
748
+ }
749
+
750
+ // ─── Snapshot ─────────────────────────────────────────────────────────────────
751
+
752
+ function captureSnapshot(container) {
753
+ if (!container) return null;
754
+
755
+ try {
756
+ return {
757
+ scrollTop: container.scrollTop,
758
+ scrollLeft: container.scrollLeft,
759
+ scrollHeight: container.scrollHeight,
760
+ scrollWidth: container.scrollWidth,
761
+ clientHeight: container.clientHeight,
762
+ clientWidth: container.clientWidth,
763
+ timestamp: Date.now(),
764
+ };
765
+ } catch (error) {
766
+ return null;
767
+ }
768
+ }
769
+
770
+ function getSnapshot() {
771
+ return snapshotBeforeMutations;
772
+ }
773
+
774
+ // ─── Batch Commit Operations ──────────────────────────────────────────────────
775
+
776
+ function batchCommitWork(callback) {
777
+ isCommitting = true;
778
+ const batchedOperations = [];
779
+
780
+ try {
781
+ const originalCommitWork = commitWork;
782
+ const collectOps = (ops, container, opts) => {
783
+ batchedOperations.push({ ops, container, opts });
784
+ };
785
+
786
+ callback(collectOps);
787
+
788
+ batchedOperations.forEach(({ ops, container, opts }) => {
789
+ originalCommitWork(ops, container, opts);
790
+ });
791
+ } finally {
792
+ isCommitting = false;
793
+ }
794
+ }
795
+
796
+ // ─── Utility Functions ────────────────────────────────────────────────────────
797
+
798
+ function findParentDOMNode(container, parentVNode) {
799
+ if (!parentVNode) return container;
800
+
801
+ if (container.__ed_vnode === parentVNode) return container;
802
+
803
+ const walker = document.createTreeWalker(container, NodeFilter.SHOW_ELEMENT);
804
+ let node;
805
+ while ((node = walker.nextNode())) {
806
+ if (node.__ed_vnode === parentVNode) return node;
807
+ }
808
+
809
+ return container;
810
+ }
811
+
812
+ function findDOMNodeForVNode(container, vnode) {
813
+ if (!vnode || !container) return null;
814
+
815
+ // Fast path: text node lookup
816
+ if (vnode.type === '#text') {
817
+ const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT);
818
+ let node;
819
+ while ((node = walker.nextNode())) {
820
+ if (node.__ed_vnode === vnode) return node;
821
+ }
822
+ return null;
823
+ }
824
+
825
+ const walker = document.createTreeWalker(container, NodeFilter.SHOW_ELEMENT);
826
+ let node;
827
+ while ((node = walker.nextNode())) {
828
+ if (node.__ed_vnode === vnode) return node;
829
+ }
830
+
831
+ return null;
832
+ }
833
+
834
+ function createDOMNodeFromVNode(vnode) {
835
+ if (!vnode) return null;
836
+
837
+ if (typeof vnode === 'string' || typeof vnode === 'number') {
838
+ return document.createTextNode(String(vnode));
839
+ }
840
+
841
+ if (vnode.type === '#text') {
842
+ return document.createTextNode(vnode.props.nodeValue || '');
843
+ }
844
+
845
+ if (typeof vnode.type === 'string') {
846
+ const domNode = document.createElement(vnode.type);
847
+
848
+ if (vnode.props) {
849
+ Object.keys(vnode.props).forEach((key) => {
850
+ if (key === 'children') return;
851
+ if (key === 'dangerouslySetInnerHTML') return;
852
+
853
+ const value = vnode.props[key];
854
+ if (key === 'className') {
855
+ domNode.className = value || '';
856
+ } else if (key === 'style' && typeof value === 'object') {
857
+ Object.assign(domNode.style, value);
858
+ } else if (key === 'style' && typeof value === 'string') {
859
+ domNode.style.cssText = value;
860
+ } else if (value === true) {
861
+ domNode.setAttribute(key, '');
862
+ } else if (value !== false && value !== null && value !== undefined) {
863
+ domNode.setAttribute(key, value);
864
+ }
865
+ });
866
+
867
+ if (vnode.props.dangerouslySetInnerHTML) {
868
+ domNode.innerHTML = vnode.props.dangerouslySetInnerHTML.__html || '';
869
+ }
870
+ }
871
+
872
+ domNode.__ed_vnode = vnode;
873
+ return domNode;
874
+ }
875
+
876
+ return null;
877
+ }
878
+
879
+ // ─── Exports ──────────────────────────────────────────────────────────────────
880
+
881
+ module.exports = {
882
+ commitWork,
883
+ batchCommitWork,
884
+ processInsertions,
885
+ processDeletions,
886
+ processUpdates,
887
+ processMoves,
888
+ processReplacements,
889
+ flushPendingLayoutEffects,
890
+ flushPendingInsertionEffects,
891
+ schedulePassiveEffects,
892
+ processRefCleanups,
893
+ processRefAssignments,
894
+ handleErrorDuringCommit,
895
+ captureSnapshot,
896
+ getSnapshot,
897
+ findDOMNodeForVNode,
898
+ findParentDOMNode,
899
+ createDOMNodeFromVNode,
900
+ getCommitStats,
901
+ resetCommitStats,
902
+ COMMIT_PRIORITY,
903
+ };