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,687 @@
1
+ /**
2
+ * reconcile - Reconciliation Algorithm
3
+ * ElementDrawing Framework - Diffs old and new virtual DOM trees to determine
4
+ * the minimal set of DOM operations needed. Includes key-based child
5
+ * reconciliation, fiber-based scheduling hints, prop/style/event diffing,
6
+ * memo optimization, and time-slicing support.
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const { REACT_FRAGMENT_TYPE, isValidElement } = require('./createElement');
12
+
13
+ // ─── Constants ────────────────────────────────────────────────────────────────
14
+
15
+ const INSERT = 'INSERT';
16
+ const MOVE = 'MOVE';
17
+ const REMOVE = 'REMOVE';
18
+ const UPDATE = 'UPDATE';
19
+ const REPLACE = 'REPLACE';
20
+ const SKIP = 'SKIP';
21
+ const REORDER = 'REORDER';
22
+
23
+ // ─── Reconciliation Priority ──────────────────────────────────────────────────
24
+
25
+ const RECONCILE_PRIORITY = {
26
+ IMMEDIATE: 0,
27
+ NORMAL: 1,
28
+ LOW: 2,
29
+ IDLE: 3,
30
+ };
31
+
32
+ // ─── Diff Result Types ────────────────────────────────────────────────────────
33
+
34
+ /**
35
+ * Create a diff operation.
36
+ */
37
+ function createDiffOp(type, payload) {
38
+ return { type, payload, _isDiffOp: true };
39
+ }
40
+
41
+ // ─── Fiber Node Creation ──────────────────────────────────────────────────────
42
+
43
+ /**
44
+ * Create a lightweight fiber node for reconciliation tracking.
45
+ * @param {Object|null} vnode - Virtual DOM element
46
+ * @param {Object|null} parentFiber - Parent fiber
47
+ * @returns {Object} Fiber node
48
+ */
49
+ function createFiber(vnode, parentFiber) {
50
+ return {
51
+ vnode,
52
+ parent: parentFiber,
53
+ child: null,
54
+ sibling: null,
55
+ alternate: null,
56
+ effectTag: null,
57
+ deps: null,
58
+ index: 0,
59
+ };
60
+ }
61
+
62
+ // ─── Tree Diffing ─────────────────────────────────────────────────────────────
63
+
64
+ /**
65
+ * Reconcile an old virtual tree with a new one, producing a list of changes.
66
+ *
67
+ * @param {Object|null} oldVNode - Previous virtual DOM tree
68
+ * @param {Object|null} newVNode - New virtual DOM tree
69
+ * @param {Object} [options] - Reconciliation options
70
+ * @param {number} [options.priority] - Reconciliation priority
71
+ * @param {number} [options.timeoutMs] - Time-slicing timeout
72
+ * @returns {Object} Reconciliation result with operations list
73
+ */
74
+ function reconcile(oldVNode, newVNode, options = {}) {
75
+ const operations = [];
76
+ const context = {
77
+ operations,
78
+ parentDOM: null,
79
+ oldParentVNode: oldVNode,
80
+ newParentVNode: newVNode,
81
+ priority: options.priority || RECONCILE_PRIORITY.NORMAL,
82
+ startTime: Date.now(),
83
+ timeoutMs: options.timeoutMs || Infinity,
84
+ aborted: false,
85
+ stats: {
86
+ nodesCompared: 0,
87
+ operationsGenerated: 0,
88
+ timeElapsed: 0,
89
+ },
90
+ };
91
+
92
+ reconcileElement(oldVNode, newVNode, operations, context);
93
+
94
+ // Update stats
95
+ context.stats.timeElapsed = Date.now() - context.startTime;
96
+ context.stats.operationsGenerated = operations.length;
97
+
98
+ return { operations, context };
99
+ }
100
+
101
+ /**
102
+ * Check if time-slicing should yield.
103
+ * @param {Object} context
104
+ * @returns {boolean}
105
+ */
106
+ function shouldYield(context) {
107
+ if (context.aborted) return true;
108
+ if (context.timeoutMs === Infinity) return false;
109
+ return Date.now() - context.startTime > context.timeoutMs;
110
+ }
111
+
112
+ /**
113
+ * Reconcile children of two vnodes.
114
+ * @param {Object|null} oldVNode
115
+ * @param {Object|null} newVNode
116
+ * @param {Array} operations
117
+ * @param {Object} context
118
+ */
119
+ function reconcileChildren(oldVNode, newVNode, operations, context) {
120
+ const oldChildren = normalizeChildren(oldVNode);
121
+ const newChildren = normalizeChildren(newVNode);
122
+
123
+ // Key-based reconciliation for efficient child reordering
124
+ const oldKeyMap = buildKeyMap(oldChildren);
125
+ const newKeyMap = buildKeyMap(newChildren);
126
+
127
+ // ─── Phase 1: Match keyed children using Longest Increasing Subsequence ──
128
+
129
+ const matchedPairs = [];
130
+ const removeIndices = new Set();
131
+ const insertNodes = [];
132
+
133
+ // Build matching table
134
+ let oldIndex = 0;
135
+ let newIndex = 0;
136
+
137
+ while (oldIndex < oldChildren.length || newIndex < newChildren.length) {
138
+ if (shouldYield(context)) {
139
+ context.aborted = true;
140
+ return;
141
+ }
142
+
143
+ const oldChild = oldChildren[oldIndex];
144
+ const newChild = newChildren[newIndex];
145
+
146
+ if (oldIndex >= oldChildren.length) {
147
+ // No more old children - insert remaining new children
148
+ insertNodes.push({ child: newChild, beforeIndex: newIndex });
149
+ matchedPairs.push({ old: null, new: newChild, oldIndex: -1, newIndex });
150
+ newIndex++;
151
+ continue;
152
+ }
153
+
154
+ if (newIndex >= newChildren.length) {
155
+ // No more new children - remove remaining old children
156
+ removeIndices.add(oldIndex);
157
+ matchedPairs.push({ old: oldChild, new: null, oldIndex, newIndex: -1 });
158
+ oldIndex++;
159
+ continue;
160
+ }
161
+
162
+ if (oldChild.key !== null && newChild.key !== null) {
163
+ if (oldChild.key === newChild.key) {
164
+ // Same key - reconcile in place
165
+ matchedPairs.push({ old: oldChild, new: newChild, oldIndex, newIndex });
166
+ oldIndex++;
167
+ newIndex++;
168
+ } else {
169
+ // Keys don't match
170
+ if (newKeyMap.has(oldChild.key) && newKeyMap.get(oldChild.key).index >= newIndex) {
171
+ insertNodes.push({ child: newChild, beforeIndex: newIndex });
172
+ newIndex++;
173
+ } else {
174
+ removeIndices.add(oldIndex);
175
+ matchedPairs.push({ old: oldChild, new: null, oldIndex, newIndex: -1 });
176
+ oldIndex++;
177
+ }
178
+ }
179
+ } else {
180
+ // Positional matching for unkeyed children
181
+ matchedPairs.push({ old: oldChild, new: newChild, oldIndex, newIndex });
182
+ oldIndex++;
183
+ newIndex++;
184
+ }
185
+ }
186
+
187
+ // ─── Phase 2: Detect reordering using LIS ──────────────────────────────
188
+
189
+ const keyedPairs = matchedPairs.filter(
190
+ (p) => p.old !== null && p.new !== null && p.new.key !== null
191
+ );
192
+
193
+ if (keyedPairs.length > 0) {
194
+ const newIndices = keyedPairs.map((p) => p.newIndex);
195
+ const lis = longestIncreasingSubsequence(newIndices);
196
+
197
+ // Mark pairs that are part of LIS as stable, others need MOVE operations
198
+ const lisSet = new Set(lis.map((i) => keyedPairs[i].newIndex));
199
+
200
+ matchedPairs.forEach((pair) => {
201
+ if (pair.old !== null && pair.new !== null && pair.new.key !== null) {
202
+ if (!lisSet.has(pair.newIndex)) {
203
+ pair.needsMove = true;
204
+ }
205
+ }
206
+ });
207
+ }
208
+
209
+ // ─── Phase 3: Generate operations ──────────────────────────────────────
210
+
211
+ matchedPairs.forEach((pair) => {
212
+ context.stats.nodesCompared++;
213
+
214
+ if (pair.old === null && pair.new !== null) {
215
+ // Insert new child
216
+ operations.push(createDiffOp(INSERT, {
217
+ vnode: pair.new,
218
+ parentVNode: newVNode,
219
+ index: pair.newIndex,
220
+ }));
221
+ } else if (pair.old !== null && pair.new === null) {
222
+ // Remove old child
223
+ operations.push(createDiffOp(REMOVE, {
224
+ vnode: pair.old,
225
+ parentVNode: oldVNode,
226
+ index: pair.oldIndex,
227
+ }));
228
+ } else if (pair.old !== null && pair.new !== null) {
229
+ if (pair.needsMove) {
230
+ operations.push(createDiffOp(MOVE, {
231
+ vnode: pair.new,
232
+ oldVNode: pair.old,
233
+ fromIndex: pair.oldIndex,
234
+ toIndex: pair.newIndex,
235
+ }));
236
+ }
237
+ // Reconcile the pair
238
+ reconcileElement(pair.old, pair.new, operations, context);
239
+ }
240
+ });
241
+ }
242
+
243
+ /**
244
+ * Reconcile two elements.
245
+ * @param {Object} oldVNode
246
+ * @param {Object} newVNode
247
+ * @param {Array} operations
248
+ * @param {Object} context
249
+ */
250
+ function reconcileElement(oldVNode, newVNode, operations, context) {
251
+ if (shouldYield(context)) {
252
+ context.aborted = true;
253
+ return;
254
+ }
255
+
256
+ // Handle null cases
257
+ if (oldVNode === null && newVNode === null) return;
258
+
259
+ if (oldVNode === null) {
260
+ operations.push(createDiffOp(INSERT, { vnode: newVNode }));
261
+ return;
262
+ }
263
+
264
+ if (newVNode === null) {
265
+ operations.push(createDiffOp(REMOVE, { vnode: oldVNode }));
266
+ return;
267
+ }
268
+
269
+ context.stats.nodesCompared++;
270
+
271
+ // ─── Type Comparison ────────────────────────────────────────────────────
272
+
273
+ if (!isSameType(oldVNode, newVNode)) {
274
+ operations.push(createDiffOp(REPLACE, {
275
+ oldVNode,
276
+ newVNode,
277
+ }));
278
+ return;
279
+ }
280
+
281
+ // ─── shouldComponentUpdate optimization ────────────────────────────────
282
+
283
+ if (oldVNode.type && typeof oldVNode.type === 'function' && oldVNode.type._shouldComponentUpdate) {
284
+ if (!oldVNode.type._shouldComponentUpdate(oldVNode.props, newVNode.props)) {
285
+ operations.push(createDiffOp(SKIP, { vnode: newVNode }));
286
+ return;
287
+ }
288
+ }
289
+
290
+ // ─── Memo component comparison ────────────────────────────────────────
291
+
292
+ if (oldVNode.type && oldVNode.type._isMemo && oldVNode.type._compare) {
293
+ if (oldVNode.type._compare(oldVNode.props, newVNode.props)) {
294
+ operations.push(createDiffOp(SKIP, { vnode: newVNode }));
295
+ return;
296
+ }
297
+ }
298
+
299
+ // ─── Pure component comparison ────────────────────────────────────────
300
+
301
+ if (oldVNode.type && oldVNode.type._isPureComponent) {
302
+ if (shallowEqual(oldVNode.props, newVNode.props)) {
303
+ operations.push(createDiffOp(SKIP, { vnode: newVNode }));
304
+ return;
305
+ }
306
+ }
307
+
308
+ // ─── Text node comparison ─────────────────────────────────────────────
309
+
310
+ if (oldVNode.type === '#text' && newVNode.type === '#text') {
311
+ if (oldVNode.props.nodeValue !== newVNode.props.nodeValue) {
312
+ operations.push(createDiffOp(UPDATE, {
313
+ vnode: newVNode,
314
+ changes: [{ type: 'set', key: 'nodeValue', value: newVNode.props.nodeValue }],
315
+ changeType: 'text',
316
+ }));
317
+ }
318
+ return;
319
+ }
320
+
321
+ // ─── Diff Props ────────────────────────────────────────────────────────
322
+
323
+ const propChanges = diffProps(oldVNode.props, newVNode.props);
324
+ if (propChanges.length > 0) {
325
+ operations.push(createDiffOp(UPDATE, {
326
+ vnode: newVNode,
327
+ changes: propChanges,
328
+ changeType: 'props',
329
+ }));
330
+ }
331
+
332
+ // ─── Diff Style ────────────────────────────────────────────────────────
333
+
334
+ const styleChanges = diffStyle(
335
+ oldVNode.props ? oldVNode.props.style : null,
336
+ newVNode.props ? newVNode.props.style : null
337
+ );
338
+ if (styleChanges.length > 0) {
339
+ operations.push(createDiffOp(UPDATE, {
340
+ vnode: newVNode,
341
+ changes: styleChanges,
342
+ changeType: 'style',
343
+ }));
344
+ }
345
+
346
+ // ─── Diff Event Handlers ────────────────────────────────────────────────
347
+
348
+ const eventChanges = diffEventHandlers(
349
+ oldVNode.props || {},
350
+ newVNode.props || {}
351
+ );
352
+ if (eventChanges.length > 0) {
353
+ operations.push(createDiffOp(UPDATE, {
354
+ vnode: newVNode,
355
+ changes: eventChanges,
356
+ changeType: 'events',
357
+ }));
358
+ }
359
+
360
+ // ─── Diff Ref ──────────────────────────────────────────────────────────
361
+
362
+ const refChange = diffRef(oldVNode.ref, newVNode.ref);
363
+ if (refChange) {
364
+ operations.push(createDiffOp(UPDATE, {
365
+ vnode: newVNode,
366
+ changes: [refChange],
367
+ changeType: 'ref',
368
+ }));
369
+ }
370
+
371
+ // ─── Diff dangerouslySetInnerHTML ──────────────────────────────────────
372
+
373
+ const htmlChange = diffDangerouslySetInnerHTML(
374
+ oldVNode.props ? oldVNode.props.dangerouslySetInnerHTML : null,
375
+ newVNode.props ? newVNode.props.dangerouslySetInnerHTML : null
376
+ );
377
+ if (htmlChange) {
378
+ operations.push(createDiffOp(UPDATE, {
379
+ vnode: newVNode,
380
+ changes: [htmlChange],
381
+ changeType: 'innerHTML',
382
+ }));
383
+ }
384
+
385
+ // ─── Recurse into Children ──────────────────────────────────────────────
386
+
387
+ if (oldVNode.type !== '#text' && newVNode.type !== '#text') {
388
+ reconcileChildren(oldVNode, newVNode, operations, context);
389
+ }
390
+ }
391
+
392
+ // ─── Key-Based Reconciliation Helpers ─────────────────────────────────────────
393
+
394
+ /**
395
+ * Build a map of keys to child indices.
396
+ */
397
+ function buildKeyMap(children) {
398
+ const map = new Map();
399
+ children.forEach((child, index) => {
400
+ if (child && child.key !== null) {
401
+ map.set(child.key, { child, index });
402
+ }
403
+ });
404
+ return map;
405
+ }
406
+
407
+ /**
408
+ * Normalize children from a vnode into a flat array.
409
+ */
410
+ function normalizeChildren(vnode) {
411
+ if (!vnode) return [];
412
+ if (!vnode.props || !vnode.props.children) return [];
413
+
414
+ const children = vnode.props.children;
415
+ if (Array.isArray(children)) {
416
+ return children.filter((c) => c !== null && c !== undefined && typeof c !== 'boolean');
417
+ }
418
+ if (children !== null && children !== undefined && typeof children !== 'boolean') {
419
+ return [children];
420
+ }
421
+ return [];
422
+ }
423
+
424
+ /**
425
+ * Check if two vnodes are the same type.
426
+ */
427
+ function isSameType(oldVNode, newVNode) {
428
+ if (!isValidElement(oldVNode) || !isValidElement(newVNode)) {
429
+ return false;
430
+ }
431
+
432
+ if (oldVNode.type === newVNode.type) return true;
433
+
434
+ if (oldVNode.type === REACT_FRAGMENT_TYPE && newVNode.type === REACT_FRAGMENT_TYPE) {
435
+ return true;
436
+ }
437
+
438
+ return false;
439
+ }
440
+
441
+ /**
442
+ * Compute the Longest Increasing Subsequence of an array.
443
+ * Used to minimize DOM moves during child reconciliation.
444
+ * @param {Array<number>} arr
445
+ * @returns {Array<number>} Indices of LIS elements
446
+ */
447
+ function longestIncreasingSubsequence(arr) {
448
+ if (arr.length === 0) return [];
449
+
450
+ const dp = new Array(arr.length).fill(1);
451
+ const prev = new Array(arr.length).fill(-1);
452
+
453
+ for (let i = 1; i < arr.length; i++) {
454
+ for (let j = 0; j < i; j++) {
455
+ if (arr[j] < arr[i] && dp[j] + 1 > dp[i]) {
456
+ dp[i] = dp[j] + 1;
457
+ prev[i] = j;
458
+ }
459
+ }
460
+ }
461
+
462
+ // Find the index of the maximum value in dp
463
+ let maxLen = 0;
464
+ let maxIdx = 0;
465
+ for (let i = 0; i < dp.length; i++) {
466
+ if (dp[i] > maxLen) {
467
+ maxLen = dp[i];
468
+ maxIdx = i;
469
+ }
470
+ }
471
+
472
+ // Backtrack to find the LIS
473
+ const result = [];
474
+ let idx = maxIdx;
475
+ while (idx !== -1) {
476
+ result.unshift(idx);
477
+ idx = prev[idx];
478
+ }
479
+
480
+ return result;
481
+ }
482
+
483
+ // ─── Prop Diffing ─────────────────────────────────────────────────────────────
484
+
485
+ /**
486
+ * Diff props between old and new vnodes.
487
+ */
488
+ function diffProps(oldProps, newProps) {
489
+ const changes = [];
490
+ if (!oldProps) oldProps = {};
491
+ if (!newProps) newProps = {};
492
+
493
+ for (const key in newProps) {
494
+ if (!newProps.hasOwnProperty(key)) continue;
495
+ if (key === 'children' || key === 'style') continue;
496
+ if (key.startsWith('on')) continue;
497
+ if (key === 'dangerouslySetInnerHTML') continue;
498
+
499
+ if (!oldProps.hasOwnProperty(key)) {
500
+ changes.push({ type: 'set', key, value: newProps[key] });
501
+ } else if (!Object.is(oldProps[key], newProps[key])) {
502
+ changes.push({ type: 'set', key, value: newProps[key] });
503
+ }
504
+ }
505
+
506
+ for (const key in oldProps) {
507
+ if (!oldProps.hasOwnProperty(key)) continue;
508
+ if (key === 'children' || key === 'style') continue;
509
+ if (key.startsWith('on')) continue;
510
+ if (key === 'dangerouslySetInnerHTML') continue;
511
+
512
+ if (!newProps.hasOwnProperty(key)) {
513
+ changes.push({ type: 'remove', key });
514
+ }
515
+ }
516
+
517
+ return changes;
518
+ }
519
+
520
+ // ─── Style Diffing ────────────────────────────────────────────────────────────
521
+
522
+ /**
523
+ * Diff style objects.
524
+ */
525
+ function diffStyle(oldStyle, newStyle) {
526
+ const changes = [];
527
+
528
+ if (oldStyle === newStyle) return changes;
529
+
530
+ if (typeof oldStyle === 'string' && typeof newStyle === 'string') {
531
+ if (oldStyle !== newStyle) {
532
+ changes.push({ type: 'replace', value: newStyle });
533
+ }
534
+ return changes;
535
+ }
536
+
537
+ const oldObj = typeof oldStyle === 'object' ? oldStyle : {};
538
+ const newObj = typeof newStyle === 'object' ? newStyle : {};
539
+
540
+ for (const prop in newObj) {
541
+ if (!newObj.hasOwnProperty(prop)) continue;
542
+
543
+ if (!oldObj.hasOwnProperty(prop)) {
544
+ changes.push({ type: 'set', key: prop, value: newObj[prop] });
545
+ } else if (!Object.is(oldObj[prop], newObj[prop])) {
546
+ changes.push({ type: 'set', key: prop, value: newObj[prop] });
547
+ }
548
+ }
549
+
550
+ for (const prop in oldObj) {
551
+ if (!oldObj.hasOwnProperty(prop)) continue;
552
+ if (!newObj.hasOwnProperty(prop)) {
553
+ changes.push({ type: 'remove', key: prop });
554
+ }
555
+ }
556
+
557
+ return changes;
558
+ }
559
+
560
+ // ─── Event Handler Diffing ────────────────────────────────────────────────────
561
+
562
+ function diffEventHandlers(oldProps, newProps) {
563
+ const changes = [];
564
+
565
+ for (const key in newProps) {
566
+ if (!newProps.hasOwnProperty(key)) continue;
567
+ if (!key.startsWith('on')) continue;
568
+
569
+ if (!oldProps.hasOwnProperty(key) || oldProps[key] !== newProps[key]) {
570
+ changes.push({ type: 'set', key, value: newProps[key] });
571
+ }
572
+ }
573
+
574
+ for (const key in oldProps) {
575
+ if (!oldProps.hasOwnProperty(key)) continue;
576
+ if (!key.startsWith('on')) continue;
577
+
578
+ if (!newProps.hasOwnProperty(key)) {
579
+ changes.push({ type: 'remove', key });
580
+ }
581
+ }
582
+
583
+ return changes;
584
+ }
585
+
586
+ // ─── Ref Diffing ──────────────────────────────────────────────────────────────
587
+
588
+ function diffRef(oldRef, newRef) {
589
+ if (oldRef === newRef) return null;
590
+
591
+ if (oldRef && !newRef) {
592
+ return { type: 'remove', oldRef };
593
+ }
594
+
595
+ if (!oldRef && newRef) {
596
+ return { type: 'set', newRef };
597
+ }
598
+
599
+ if (oldRef !== newRef) {
600
+ return { type: 'replace', oldRef, newRef };
601
+ }
602
+
603
+ return null;
604
+ }
605
+
606
+ // ─── dangerouslySetInnerHTML Diffing ──────────────────────────────────────────
607
+
608
+ function diffDangerouslySetInnerHTML(oldHtml, newHtml) {
609
+ if (oldHtml === newHtml) return null;
610
+ if (!oldHtml && !newHtml) return null;
611
+
612
+ const oldStr = oldHtml ? oldHtml.__html : '';
613
+ const newStr = newHtml ? newHtml.__html : '';
614
+
615
+ if (oldStr === newStr) return null;
616
+
617
+ return { type: 'set', key: 'dangerouslySetInnerHTML', value: newHtml };
618
+ }
619
+
620
+ // ─── ShouldComponentUpdate Optimization ────────────────────────────────────────
621
+
622
+ /**
623
+ * Create a memo component.
624
+ */
625
+ function memo(component, areEqual) {
626
+ const memoComponent = function MemoComponent(props) {
627
+ return component(props);
628
+ };
629
+
630
+ memoComponent._isMemo = true;
631
+ memoComponent._compare = areEqual || shallowEqual;
632
+ memoComponent.displayName = 'Memo(' + (component.displayName || component.name || 'Anonymous') + ')';
633
+
634
+ return memoComponent;
635
+ }
636
+
637
+ /**
638
+ * Shallow equality comparison.
639
+ */
640
+ function shallowEqual(objA, objB) {
641
+ if (Object.is(objA, objB)) return true;
642
+
643
+ if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) {
644
+ return false;
645
+ }
646
+
647
+ const keysA = Object.keys(objA);
648
+ const keysB = Object.keys(objB);
649
+
650
+ if (keysA.length !== keysB.length) return false;
651
+
652
+ for (let i = 0; i < keysA.length; i++) {
653
+ if (!Object.is(objA[keysA[i]], objB[keysA[i]])) return false;
654
+ }
655
+
656
+ return true;
657
+ }
658
+
659
+ // ─── Exports ──────────────────────────────────────────────────────────────────
660
+
661
+ module.exports = {
662
+ reconcile,
663
+ reconcileChildren,
664
+ reconcileElement,
665
+ diffProps,
666
+ diffStyle,
667
+ diffEventHandlers,
668
+ diffRef,
669
+ diffDangerouslySetInnerHTML,
670
+ isSameType,
671
+ buildKeyMap,
672
+ normalizeChildren,
673
+ memo,
674
+ shallowEqual,
675
+ createFiber,
676
+ createDiffOp,
677
+ longestIncreasingSubsequence,
678
+ shouldYield,
679
+ INSERT,
680
+ MOVE,
681
+ REMOVE,
682
+ UPDATE,
683
+ REPLACE,
684
+ SKIP,
685
+ REORDER,
686
+ RECONCILE_PRIORITY,
687
+ };