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,508 @@
1
+ /**
2
+ * useLayoutEffect Hook Implementation
3
+ * ElementDrawing Framework - Synchronous effect after DOM mutations,
4
+ * executed during the commit phase before the browser paints.
5
+ * Includes priority-based ordering, isomorphic layout effects,
6
+ * mutation observation, resize observation, and batch processing.
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ // ─── Internal State ───────────────────────────────────────────────────────────
12
+
13
+ let currentlyRenderingComponent = null;
14
+ let hookIndex = 0;
15
+ const layoutEffectQueue = [];
16
+ let isFlushingLayoutEffects = false;
17
+
18
+ // ─── Layout Effect Priority ───────────────────────────────────────────────────
19
+
20
+ /**
21
+ * Priority levels for layout effects.
22
+ * Lower number = higher priority = executes first.
23
+ */
24
+ const LAYOUT_PRIORITY = {
25
+ HIGHEST: 0, // Insertion effects, critical DOM measurements
26
+ HIGH: 1, // Layout calculations
27
+ NORMAL: 2, // Standard layout effects
28
+ LOW: 3, // Non-critical layout adjustments
29
+ LOWEST: 4, // Post-layout cleanup
30
+ };
31
+
32
+ // ─── Dependency Comparison ────────────────────────────────────────────────────
33
+
34
+ /**
35
+ * Compare two dependency arrays for shallow equality.
36
+ * @param {Array|null} prevDeps
37
+ * @param {Array|null} nextDeps
38
+ * @returns {boolean}
39
+ */
40
+ function areDepsEqual(prevDeps, nextDeps) {
41
+ if (prevDeps === null || nextDeps === null) return false;
42
+ if (prevDeps.length !== nextDeps.length) return false;
43
+ for (let i = 0; i < prevDeps.length; i++) {
44
+ if (Object.is(prevDeps[i], nextDeps[i])) continue;
45
+ return false;
46
+ }
47
+ return true;
48
+ }
49
+
50
+ /**
51
+ * Validate dependency array.
52
+ * @param {*} deps
53
+ */
54
+ function validateDeps(deps) {
55
+ if (deps !== undefined && !Array.isArray(deps)) {
56
+ throw new Error(
57
+ '[useLayoutEffect] Dependencies must be an array or undefined. ' +
58
+ `Received: ${typeof deps}`
59
+ );
60
+ }
61
+ }
62
+
63
+ // ─── Effect Execution ─────────────────────────────────────────────────────────
64
+
65
+ /**
66
+ * Execute a layout effect, capturing its cleanup function.
67
+ * @param {Object} effect - Effect object
68
+ */
69
+ function executeLayoutEffect(effect) {
70
+ try {
71
+ const cleanup = effect.create();
72
+ if (cleanup !== undefined && typeof cleanup !== 'function') {
73
+ console.warn(
74
+ '[useLayoutEffect] Effect cleanup must be a function or undefined. ' +
75
+ `Received: ${typeof cleanup}`
76
+ );
77
+ effect.cleanup = null;
78
+ } else {
79
+ effect.cleanup = cleanup;
80
+ }
81
+ } catch (error) {
82
+ console.error('[useLayoutEffect] Effect execution error:', error);
83
+ effect.cleanup = null;
84
+ if (effect.component && effect.component._handleError) {
85
+ effect.component._handleError(error);
86
+ }
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Run cleanup for a layout effect.
92
+ * @param {Object} effect
93
+ */
94
+ function runLayoutCleanup(effect) {
95
+ if (typeof effect.cleanup === 'function') {
96
+ try {
97
+ effect.cleanup();
98
+ } catch (error) {
99
+ console.error('[useLayoutEffect] Cleanup error:', error);
100
+ if (effect.component && effect.component._handleError) {
101
+ effect.component._handleError(error);
102
+ }
103
+ }
104
+ effect.cleanup = null;
105
+ }
106
+ }
107
+
108
+ // ─── Layout Effect Queue Management ───────────────────────────────────────────
109
+
110
+ /**
111
+ * Flush all queued layout effects synchronously.
112
+ * Called during the commit phase, before the browser paints.
113
+ * Effects are sorted by priority before execution.
114
+ */
115
+ function flushLayoutEffects() {
116
+ if (isFlushingLayoutEffects) return;
117
+ isFlushingLayoutEffects = true;
118
+
119
+ try {
120
+ // Sort by priority (lower = higher priority = executes first)
121
+ const sortedEffects = layoutEffectQueue.slice().sort((a, b) => {
122
+ return (a._priority || LAYOUT_PRIORITY.NORMAL) - (b._priority || LAYOUT_PRIORITY.NORMAL);
123
+ });
124
+
125
+ // Clear the queue before execution to prevent double-execution
126
+ layoutEffectQueue.length = 0;
127
+
128
+ while (sortedEffects.length > 0) {
129
+ const effect = sortedEffects.shift();
130
+
131
+ // Run cleanup from previous render
132
+ runLayoutCleanup(effect);
133
+
134
+ // Execute the new layout effect
135
+ executeLayoutEffect(effect);
136
+
137
+ // Track execution time for profiling
138
+ effect._executedAt = Date.now();
139
+ }
140
+ } finally {
141
+ isFlushingLayoutEffects = false;
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Check if there are pending layout effects.
147
+ * @returns {boolean}
148
+ */
149
+ function hasPendingLayoutEffects() {
150
+ return layoutEffectQueue.length > 0;
151
+ }
152
+
153
+ /**
154
+ * Get the count of pending layout effects.
155
+ * @returns {number}
156
+ */
157
+ function getPendingLayoutEffectCount() {
158
+ return layoutEffectQueue.length;
159
+ }
160
+
161
+ /**
162
+ * Get pending layout effects grouped by priority.
163
+ * @returns {Object}
164
+ */
165
+ function getPendingLayoutEffectsByPriority() {
166
+ const grouped = {};
167
+ layoutEffectQueue.forEach((effect) => {
168
+ const priority = effect._priority || LAYOUT_PRIORITY.NORMAL;
169
+ if (!grouped[priority]) grouped[priority] = 0;
170
+ grouped[priority]++;
171
+ });
172
+ return grouped;
173
+ }
174
+
175
+ // ─── Hook Context Management ──────────────────────────────────────────────────
176
+
177
+ function setCurrentComponent(component) {
178
+ currentlyRenderingComponent = component;
179
+ hookIndex = 0;
180
+ }
181
+
182
+ function resetHookIndex() {
183
+ hookIndex = 0;
184
+ currentlyRenderingComponent = null;
185
+ }
186
+
187
+ // ─── useLayoutEffect Hook ─────────────────────────────────────────────────────
188
+
189
+ /**
190
+ * useLayoutEffect - Synchronous effect that runs after DOM mutations
191
+ * but before the browser paints. Use for:
192
+ * - DOM measurements (getBoundingClientRect, etc.)
193
+ * - Synchronous DOM mutations that must happen before paint
194
+ * - Preventing visual flickering
195
+ *
196
+ * @param {Function} create - Effect callback; may return a cleanup function
197
+ * @param {Array} [deps] - Dependency array; effect re-runs when deps change
198
+ * @returns {void}
199
+ *
200
+ * @example
201
+ * useLayoutEffect(() => {
202
+ * const height = ref.current.getBoundingClientRect().height;
203
+ * if (height > maxHeight) {
204
+ * ref.current.style.overflow = 'auto';
205
+ * ref.current.style.maxHeight = maxHeight + 'px';
206
+ * }
207
+ * }, [maxHeight]);
208
+ *
209
+ * @example
210
+ * // Auto-resize textarea
211
+ * useLayoutEffect(() => {
212
+ * const textarea = ref.current;
213
+ * textarea.style.height = 'auto';
214
+ * textarea.style.height = textarea.scrollHeight + 'px';
215
+ * }, [value]);
216
+ */
217
+ function useLayoutEffect(create, deps) {
218
+ const component = currentlyRenderingComponent;
219
+ if (!component) {
220
+ throw new Error(
221
+ '[useLayoutEffect] Must be called within a component render phase'
222
+ );
223
+ }
224
+
225
+ if (typeof create !== 'function') {
226
+ throw new Error(
227
+ '[useLayoutEffect] First argument must be a function. ' +
228
+ `Received: ${typeof create}`
229
+ );
230
+ }
231
+
232
+ validateDeps(deps);
233
+
234
+ // Initialize hooks array
235
+ if (!component._hooks) {
236
+ component._hooks = [];
237
+ }
238
+
239
+ const currentHookIndex = hookIndex;
240
+ const resolvedDeps = deps !== undefined ? deps : null;
241
+ let hook;
242
+
243
+ if (component._hooks[currentHookIndex]) {
244
+ // Re-render: check if dependencies changed
245
+ hook = component._hooks[currentHookIndex];
246
+
247
+ const prevDeps = hook.deps;
248
+ const depsChanged = !areDepsEqual(prevDeps, resolvedDeps);
249
+
250
+ if (depsChanged) {
251
+ hook.deps = resolvedDeps;
252
+ hook.create = create;
253
+ hook._shouldRun = true;
254
+ } else {
255
+ hook._shouldRun = false;
256
+ }
257
+ } else {
258
+ // First render - always run
259
+ hook = {
260
+ type: 'layout-effect',
261
+ create,
262
+ cleanup: null,
263
+ deps: resolvedDeps,
264
+ component,
265
+ _shouldRun: true,
266
+ _priority: LAYOUT_PRIORITY.NORMAL,
267
+ _isMounted: false,
268
+ _executedAt: null,
269
+ };
270
+
271
+ component._hooks[currentHookIndex] = hook;
272
+ }
273
+
274
+ // Queue the effect for synchronous execution during commit phase
275
+ if (hook._shouldRun) {
276
+ layoutEffectQueue.push(hook);
277
+ }
278
+
279
+ hookIndex++;
280
+ }
281
+
282
+ // ─── Priority Layout Effect ───────────────────────────────────────────────────
283
+
284
+ /**
285
+ * Create a layout effect with a priority level.
286
+ * Higher priority effects execute first during the commit phase.
287
+ *
288
+ * @param {number} priority - Priority level (use LAYOUT_PRIORITY constants)
289
+ * @param {Function} create - Effect callback
290
+ * @param {Array} [deps] - Dependency array
291
+ */
292
+ function usePriorityLayoutEffect(priority, create, deps) {
293
+ const component = currentlyRenderingComponent;
294
+ if (!component) {
295
+ throw new Error(
296
+ '[usePriorityLayoutEffect] Must be called within a component render phase'
297
+ );
298
+ }
299
+
300
+ if (typeof create !== 'function') {
301
+ throw new Error(
302
+ '[usePriorityLayoutEffect] Create must be a function. ' +
303
+ `Received: ${typeof create}`
304
+ );
305
+ }
306
+
307
+ validateDeps(deps);
308
+
309
+ if (!component._hooks) {
310
+ component._hooks = [];
311
+ }
312
+
313
+ const currentHookIndex = hookIndex;
314
+ const resolvedDeps = deps !== undefined ? deps : null;
315
+ let hook;
316
+
317
+ if (component._hooks[currentHookIndex]) {
318
+ hook = component._hooks[currentHookIndex];
319
+ const prevDeps = hook.deps;
320
+ const depsChanged = !areDepsEqual(prevDeps, resolvedDeps);
321
+
322
+ if (depsChanged) {
323
+ hook.deps = resolvedDeps;
324
+ hook.create = create;
325
+ hook._priority = priority;
326
+ hook._shouldRun = true;
327
+ } else {
328
+ hook._shouldRun = false;
329
+ }
330
+ } else {
331
+ hook = {
332
+ type: 'layout-effect',
333
+ create,
334
+ cleanup: null,
335
+ deps: resolvedDeps,
336
+ component,
337
+ _shouldRun: true,
338
+ _priority: priority,
339
+ _isMounted: false,
340
+ _executedAt: null,
341
+ };
342
+
343
+ component._hooks[currentHookIndex] = hook;
344
+ }
345
+
346
+ if (hook._shouldRun) {
347
+ layoutEffectQueue.push(hook);
348
+ }
349
+
350
+ hookIndex++;
351
+ }
352
+
353
+ // ─── Isomorphic Layout Effect ─────────────────────────────────────────────────
354
+
355
+ /**
356
+ * useIsomorphicLayoutEffect - Uses useLayoutEffect on the client and useEffect
357
+ * on the server to avoid SSR warnings.
358
+ *
359
+ * @param {Function} create - Effect callback
360
+ * @param {Array} [deps] - Dependency array
361
+ */
362
+ const useIsomorphicLayoutEffect = typeof window !== 'undefined'
363
+ ? useLayoutEffect
364
+ : function useLayoutEffectNoop(create, deps) {
365
+ // On server, fall back to no-op to avoid warnings
366
+ };
367
+
368
+ // ─── Mutation Observer Effect ─────────────────────────────────────────────────
369
+
370
+ /**
371
+ * useMutationObserver - Observe DOM mutations on a ref's element.
372
+ *
373
+ * @param {Object} ref - Ref to the target element
374
+ * @param {Object} options - MutationObserver options
375
+ * @param {Function} callback - Called with mutation records
376
+ *
377
+ * @example
378
+ * const divRef = useRef(null);
379
+ * useMutationObserver(divRef, { childList: true, subtree: true }, (mutations) => {
380
+ * console.log('DOM changed:', mutations);
381
+ * });
382
+ */
383
+ function useMutationObserver(ref, options, callback) {
384
+ const component = currentlyRenderingComponent;
385
+ if (!component) {
386
+ throw new Error('[useMutationObserver] Must be called within a component render phase');
387
+ }
388
+
389
+ useLayoutEffect(() => {
390
+ const node = ref.current;
391
+ if (!node || typeof MutationObserver === 'undefined') return;
392
+
393
+ const observer = new MutationObserver((mutations) => {
394
+ try {
395
+ callback(mutations);
396
+ } catch (error) {
397
+ console.error('[useMutationObserver] Callback error:', error);
398
+ }
399
+ });
400
+
401
+ observer.observe(node, options);
402
+
403
+ return () => {
404
+ observer.disconnect();
405
+ };
406
+ }, [ref.current]);
407
+ }
408
+
409
+ // ─── Resize Observer Effect ───────────────────────────────────────────────────
410
+
411
+ /**
412
+ * useResizeObserver - Observe element size changes.
413
+ *
414
+ * @param {Object} ref - Ref to the target element
415
+ * @param {Function} callback - Called with ResizeObserver entries
416
+ *
417
+ * @example
418
+ * const divRef = useRef(null);
419
+ * useResizeObserver(divRef, (entries) => {
420
+ * const { width, height } = entries[0].contentRect;
421
+ * setSize({ width, height });
422
+ * });
423
+ */
424
+ function useResizeObserver(ref, callback) {
425
+ const component = currentlyRenderingComponent;
426
+ if (!component) {
427
+ throw new Error('[useResizeObserver] Must be called within a component render phase');
428
+ }
429
+
430
+ useLayoutEffect(() => {
431
+ const node = ref.current;
432
+ if (!node || typeof ResizeObserver === 'undefined') return;
433
+
434
+ const observer = new ResizeObserver((entries) => {
435
+ try {
436
+ callback(entries);
437
+ } catch (error) {
438
+ console.error('[useResizeObserver] Callback error:', error);
439
+ }
440
+ });
441
+
442
+ observer.observe(node);
443
+
444
+ return () => {
445
+ observer.disconnect();
446
+ };
447
+ }, [ref.current]);
448
+ }
449
+
450
+ // ─── Auto-focus Effect ────────────────────────────────────────────────────────
451
+
452
+ /**
453
+ * useAutoFocus - Auto-focus an element on mount.
454
+ *
455
+ * @param {Object} ref - Ref to the element to focus
456
+ * @param {boolean} [shouldFocus=true] - Whether to auto-focus
457
+ */
458
+ function useAutoFocus(ref, shouldFocus) {
459
+ useLayoutEffect(() => {
460
+ if (shouldFocus === false) return;
461
+ const node = ref.current;
462
+ if (node && typeof node.focus === 'function') {
463
+ node.focus();
464
+ }
465
+ }, []);
466
+ }
467
+
468
+ // ─── Cleanup ──────────────────────────────────────────────────────────────────
469
+
470
+ /**
471
+ * Clean up layout effects when a component unmounts.
472
+ * @param {Object} component
473
+ */
474
+ function cleanupLayoutEffects(component) {
475
+ if (!component._hooks) return;
476
+
477
+ component._hooks.forEach((hook) => {
478
+ if (hook && hook.type === 'layout-effect' && typeof hook.cleanup === 'function') {
479
+ runLayoutCleanup(hook);
480
+ }
481
+ });
482
+
483
+ // Remove from queue
484
+ for (let i = layoutEffectQueue.length - 1; i >= 0; i--) {
485
+ if (layoutEffectQueue[i].component === component) {
486
+ layoutEffectQueue.splice(i, 1);
487
+ }
488
+ }
489
+ }
490
+
491
+ // ─── Exports ──────────────────────────────────────────────────────────────────
492
+
493
+ module.exports = {
494
+ useLayoutEffect,
495
+ useIsomorphicLayoutEffect,
496
+ usePriorityLayoutEffect,
497
+ useMutationObserver,
498
+ useResizeObserver,
499
+ useAutoFocus,
500
+ setCurrentComponent,
501
+ resetHookIndex,
502
+ flushLayoutEffects,
503
+ hasPendingLayoutEffects,
504
+ getPendingLayoutEffectCount,
505
+ getPendingLayoutEffectsByPriority,
506
+ cleanupLayoutEffects,
507
+ LAYOUT_PRIORITY,
508
+ };