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,586 @@
1
+ /**
2
+ * useContext Hook Implementation
3
+ * ElementDrawing Framework - Context API with creation, provider/consumer,
4
+ * default values, subscription, nested contexts, selectors, observers,
5
+ * context versioning, and change notification optimization.
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ // ─── Internal State ───────────────────────────────────────────────────────────
11
+
12
+ let currentlyRenderingComponent = null;
13
+ let hookIndex = 0;
14
+ let contextIdCounter = 0;
15
+
16
+ // ─── Context Versioning ───────────────────────────────────────────────────────
17
+
18
+ /**
19
+ * Track context value versions to optimize change notifications.
20
+ * When a provider value is the same version, consumers can skip re-renders.
21
+ */
22
+ const contextVersions = new WeakMap();
23
+
24
+ function getContextVersion(context) {
25
+ return contextVersions.get(context) || 0;
26
+ }
27
+
28
+ function incrementContextVersion(context) {
29
+ const current = contextVersions.get(context) || 0;
30
+ contextVersions.set(context, current + 1);
31
+ return current + 1;
32
+ }
33
+
34
+ // ─── Context Creation ─────────────────────────────────────────────────────────
35
+
36
+ /**
37
+ * Create a new context with a default value.
38
+ *
39
+ * @param {*} defaultValue - The default value used when no Provider is found
40
+ * @returns {Object} Context object with Provider and Consumer
41
+ *
42
+ * @example
43
+ * const ThemeContext = createContext('light');
44
+ * const LocaleContext = createContext({ language: 'en', region: 'US' });
45
+ */
46
+ function createContext(defaultValue) {
47
+ const contextId = '__ED_CTX_' + (++contextIdCounter) + '__';
48
+
49
+ // Unique symbol for storing context value on providers
50
+ const valueKey = Symbol.for(contextId + '_value');
51
+ const subscribersKey = Symbol.for(contextId + '_subscribers');
52
+ const versionKey = Symbol.for(contextId + '_version');
53
+
54
+ // ─── Context Object ────────────────────────────────────────────────────
55
+
56
+ const context = {
57
+ _id: contextId,
58
+ _valueKey: valueKey,
59
+ _subscribersKey: subscribersKey,
60
+ _versionKey: versionKey,
61
+ _defaultValue: defaultValue,
62
+ _isContext: true,
63
+ _displayName: null,
64
+
65
+ /**
66
+ * Provider component that supplies context value to descendant consumers.
67
+ *
68
+ * @param {Object} props
69
+ * @param {*} props.value - The context value to provide
70
+ * @param {*} props.children - Child components
71
+ * @returns {Object} Virtual DOM node
72
+ */
73
+ Provider: null, // Assigned below
74
+
75
+ /**
76
+ * Consumer component that reads context value.
77
+ *
78
+ * @param {Object} props
79
+ * @param {Function} props.children - Render prop receiving context value
80
+ * @returns {*} Rendered result
81
+ */
82
+ Consumer: null, // Assigned below
83
+
84
+ /**
85
+ * Get the current context value by traversing the component tree upward.
86
+ * @param {Object} component - Starting component
87
+ * @returns {*} Context value or default value
88
+ */
89
+ _getValue(component) {
90
+ let current = component;
91
+ while (current) {
92
+ // Check if this component is a Provider for this context
93
+ if (current._contextProvider === contextId && current[valueKey] !== undefined) {
94
+ return current[valueKey];
95
+ }
96
+ current = current._parentComponent;
97
+ }
98
+ return defaultValue;
99
+ },
100
+
101
+ /**
102
+ * Get the version of the current context value.
103
+ * @param {Object} component - Starting component
104
+ * @returns {number} Version number
105
+ */
106
+ _getVersion(component) {
107
+ let current = component;
108
+ while (current) {
109
+ if (current._contextProvider === contextId) {
110
+ return current[versionKey] || 0;
111
+ }
112
+ current = current._parentComponent;
113
+ }
114
+ return 0;
115
+ },
116
+
117
+ /**
118
+ * Subscribe to context value changes.
119
+ * @param {Object} component - The subscribing component
120
+ * @param {Function} callback - Called with (newValue, oldValue)
121
+ * @returns {Function} Unsubscribe function
122
+ */
123
+ _subscribe(component, callback) {
124
+ let current = component;
125
+ while (current) {
126
+ if (current._contextProvider === contextId) {
127
+ if (!current[subscribersKey]) {
128
+ current[subscribersKey] = new Set();
129
+ }
130
+ const sub = { component, callback };
131
+ current[subscribersKey].add(sub);
132
+
133
+ return function unsubscribe() {
134
+ if (current[subscribersKey]) {
135
+ current[subscribersKey].delete(sub);
136
+ }
137
+ };
138
+ }
139
+ current = current._parentComponent;
140
+ }
141
+ return function noop() {};
142
+ },
143
+
144
+ /**
145
+ * Subscribe with a selector for optimized re-renders.
146
+ * @param {Object} component - The subscribing component
147
+ * @param {Function} selector - Selector function
148
+ * @param {Function} callback - Called when selected value changes
149
+ * @returns {Function} Unsubscribe function
150
+ */
151
+ _subscribeWithSelector(component, selector, callback) {
152
+ let lastSelectedValue = selector(context._getValue(component));
153
+
154
+ return context._subscribe(component, (newValue, oldValue) => {
155
+ const newSelectedValue = selector(newValue);
156
+ if (!Object.is(lastSelectedValue, newSelectedValue)) {
157
+ const prevSelected = lastSelectedValue;
158
+ lastSelectedValue = newSelectedValue;
159
+ callback(newSelectedValue, prevSelected);
160
+ }
161
+ });
162
+ },
163
+
164
+ /**
165
+ * Notify all subscribers when the Provider value changes.
166
+ * @param {Object} providerComponent - The Provider component
167
+ * @param {*} newValue - New context value
168
+ * @param {*} oldValue - Previous context value
169
+ */
170
+ _notifySubscribers(providerComponent, newValue, oldValue) {
171
+ const subs = providerComponent[subscribersKey];
172
+ if (!subs) return;
173
+
174
+ subs.forEach(({ component, callback }) => {
175
+ try {
176
+ callback(newValue, oldValue);
177
+ } catch (error) {
178
+ console.error('[useContext] Subscriber notification error:', error);
179
+ if (component._handleError) {
180
+ component._handleError(error);
181
+ }
182
+ }
183
+ });
184
+ },
185
+ };
186
+
187
+ // ─── Provider Implementation ────────────────────────────────────────────
188
+
189
+ /**
190
+ * Provider component wraps children and supplies a context value.
191
+ * When the value prop changes, all subscribed consumers re-render.
192
+ */
193
+ context.Provider = function Provider(props) {
194
+ const component = currentlyRenderingComponent;
195
+
196
+ // Mark this component as a provider for this context
197
+ if (component) {
198
+ component._contextProvider = contextId;
199
+
200
+ // Check if value changed
201
+ const prevValue = component[valueKey];
202
+ const newValue = props.value;
203
+
204
+ if (!Object.is(prevValue, newValue)) {
205
+ component[valueKey] = newValue;
206
+
207
+ // Increment version
208
+ const newVersion = (component[versionKey] || 0) + 1;
209
+ component[versionKey] = newVersion;
210
+ incrementContextVersion(context);
211
+
212
+ // Notify subscribers of value change
213
+ context._notifySubscribers(component, newValue, prevValue);
214
+ }
215
+
216
+ // Initialize subscribers set
217
+ if (!component[subscribersKey]) {
218
+ component[subscribersKey] = new Set();
219
+ }
220
+ }
221
+
222
+ return props.children;
223
+ };
224
+
225
+ context.Provider._context = context;
226
+ context.Provider.displayName = 'Context.Provider';
227
+
228
+ // ─── Consumer Implementation ────────────────────────────────────────────
229
+
230
+ /**
231
+ * Consumer component uses the render prop pattern to access context value.
232
+ */
233
+ context.Consumer = function Consumer(props) {
234
+ const component = currentlyRenderingComponent;
235
+ let value = defaultValue;
236
+
237
+ if (component) {
238
+ value = context._getValue(component);
239
+ }
240
+
241
+ if (typeof props.children === 'function') {
242
+ return props.children(value);
243
+ }
244
+
245
+ console.warn(
246
+ '[useContext] Consumer children should be a function. ' +
247
+ 'Received: ' + typeof props.children
248
+ );
249
+ return props.children;
250
+ };
251
+
252
+ context.Consumer._context = context;
253
+ context.Consumer.displayName = 'Context.Consumer';
254
+
255
+ return context;
256
+ }
257
+
258
+ // ─── useContext Hook ───────────────────────────────────────────────────────────
259
+
260
+ /**
261
+ * useContext - Read context value and subscribe to changes.
262
+ *
263
+ * @param {Object} context - Context object created by createContext
264
+ * @returns {*} Current context value
265
+ *
266
+ * @example
267
+ * const theme = useContext(ThemeContext);
268
+ * const { language } = useContext(LocaleContext);
269
+ */
270
+ function useContext(context) {
271
+ const component = currentlyRenderingComponent;
272
+ if (!component) {
273
+ throw new Error('[useContext] Must be called within a component render phase');
274
+ }
275
+
276
+ if (!context || !context._isContext) {
277
+ throw new Error(
278
+ '[useContext] Argument must be a context object created by createContext()'
279
+ );
280
+ }
281
+
282
+ // Initialize hooks array
283
+ if (!component._hooks) {
284
+ component._hooks = [];
285
+ }
286
+
287
+ const currentHookIndex = hookIndex;
288
+ let hook;
289
+
290
+ if (component._hooks[currentHookIndex]) {
291
+ // Re-render: reuse existing hook
292
+ hook = component._hooks[currentHookIndex];
293
+ } else {
294
+ // First render: create hook and subscribe to context
295
+ hook = {
296
+ type: 'context',
297
+ context,
298
+ _unsubscribe: null,
299
+ _lastValue: null,
300
+ _version: 0,
301
+ };
302
+
303
+ // Subscribe to context changes to trigger re-render
304
+ const unsubscribe = context._subscribe(component, (newValue, oldValue) => {
305
+ // Schedule re-render when context value changes
306
+ if (component._scheduleReRender) {
307
+ component._scheduleReRender();
308
+ }
309
+ });
310
+ hook._unsubscribe = unsubscribe;
311
+
312
+ component._hooks[currentHookIndex] = hook;
313
+ }
314
+
315
+ // Get the current context value
316
+ const value = context._getValue(component);
317
+ hook._lastValue = value;
318
+ hook._version = context._getVersion(component);
319
+
320
+ hookIndex++;
321
+ return value;
322
+ }
323
+
324
+ // ─── Context Selector Hook ────────────────────────────────────────────────────
325
+
326
+ /**
327
+ * useContextSelector - Read a specific slice of context using a selector.
328
+ * Only re-renders when the selected value changes, not on every context change.
329
+ *
330
+ * @param {Object} context - Context object created by createContext
331
+ * @param {Function} selector - Function that extracts a value from context
332
+ * @returns {*} Selected value from context
333
+ *
334
+ * @example
335
+ * const theme = useContextSelector(ThemeContext, ctx => ctx.mode);
336
+ * const language = useContextSelector(LocaleContext, ctx => ctx.language);
337
+ */
338
+ function useContextSelector(context, selector) {
339
+ const component = currentlyRenderingComponent;
340
+ if (!component) {
341
+ throw new Error('[useContextSelector] Must be called within a component render phase');
342
+ }
343
+
344
+ if (!context || !context._isContext) {
345
+ throw new Error('[useContextSelector] First argument must be a context object');
346
+ }
347
+
348
+ if (typeof selector !== 'function') {
349
+ throw new Error('[useContextSelector] Second argument must be a selector function');
350
+ }
351
+
352
+ // Initialize hooks array
353
+ if (!component._hooks) {
354
+ component._hooks = [];
355
+ }
356
+
357
+ const currentHookIndex = hookIndex;
358
+ let hook;
359
+
360
+ if (component._hooks[currentHookIndex]) {
361
+ hook = component._hooks[currentHookIndex];
362
+ hook.selector = selector;
363
+ } else {
364
+ hook = {
365
+ type: 'context-selector',
366
+ context,
367
+ selector,
368
+ _unsubscribe: null,
369
+ _lastSelectedValue: undefined,
370
+ };
371
+
372
+ // Subscribe with selector for optimized updates
373
+ const unsubscribe = context._subscribeWithSelector(
374
+ component,
375
+ selector,
376
+ (newSelected, oldSelected) => {
377
+ if (component._scheduleReRender) {
378
+ component._scheduleReRender();
379
+ }
380
+ }
381
+ );
382
+ hook._unsubscribe = unsubscribe;
383
+
384
+ component._hooks[currentHookIndex] = hook;
385
+ }
386
+
387
+ // Compute selected value
388
+ const fullValue = context._getValue(component);
389
+ const selectedValue = selector(fullValue);
390
+ hook._lastSelectedValue = selectedValue;
391
+
392
+ hookIndex++;
393
+ return selectedValue;
394
+ }
395
+
396
+ // ─── Context Observer Hook ────────────────────────────────────────────────────
397
+
398
+ /**
399
+ * useContextObserver - Observe context changes with a callback instead of
400
+ * triggering a re-render. Useful for logging, analytics, or side effects.
401
+ *
402
+ * @param {Object} context - Context object
403
+ * @param {Function} observer - Called with (newValue, oldValue) on changes
404
+ * @returns {*} Current context value
405
+ *
406
+ * @example
407
+ * const theme = useContextObserver(ThemeContext, (newTheme) => {
408
+ * analytics.track('theme_changed', { theme: newTheme });
409
+ * });
410
+ */
411
+ function useContextObserver(context, observer) {
412
+ const component = currentlyRenderingComponent;
413
+ if (!component) {
414
+ throw new Error('[useContextObserver] Must be called within a component render phase');
415
+ }
416
+
417
+ if (!context || !context._isContext) {
418
+ throw new Error('[useContextObserver] First argument must be a context object');
419
+ }
420
+
421
+ if (typeof observer !== 'function') {
422
+ throw new Error('[useContextObserver] Second argument must be an observer function');
423
+ }
424
+
425
+ if (!component._hooks) {
426
+ component._hooks = [];
427
+ }
428
+
429
+ const currentHookIndex = hookIndex;
430
+ let hook;
431
+
432
+ if (component._hooks[currentHookIndex]) {
433
+ hook = component._hooks[currentHookIndex];
434
+ hook.observer = observer;
435
+ } else {
436
+ hook = {
437
+ type: 'context-observer',
438
+ context,
439
+ observer,
440
+ _unsubscribe: null,
441
+ };
442
+
443
+ // Subscribe to context changes (observer doesn't trigger re-render)
444
+ const unsubscribe = context._subscribe(component, (newValue, oldValue) => {
445
+ try {
446
+ observer(newValue, oldValue);
447
+ } catch (error) {
448
+ console.error('[useContextObserver] Observer error:', error);
449
+ }
450
+ });
451
+ hook._unsubscribe = unsubscribe;
452
+
453
+ component._hooks[currentHookIndex] = hook;
454
+ }
455
+
456
+ const value = context._getValue(component);
457
+ hookIndex++;
458
+ return value;
459
+ }
460
+
461
+ // ─── Nested Context Support ───────────────────────────────────────────────────
462
+
463
+ /**
464
+ * Merge multiple contexts into a single object for convenience.
465
+ *
466
+ * @param {Object[]} contexts - Array of context objects
467
+ * @returns {Object} Object with current values from all contexts
468
+ *
469
+ * @example
470
+ * const { theme, locale } = useMergedContext([ThemeContext, LocaleContext]);
471
+ */
472
+ function useMergedContext(contexts) {
473
+ if (!Array.isArray(contexts)) {
474
+ throw new Error('[useMergedContext] Expected an array of context objects');
475
+ }
476
+
477
+ const values = {};
478
+ contexts.forEach((ctx) => {
479
+ const name = ctx._id.replace('__ED_CTX_', '').replace('__', '');
480
+ values[name] = useContext(ctx);
481
+ });
482
+ return values;
483
+ }
484
+
485
+ /**
486
+ * Create a composed context that derives its value from multiple parent contexts.
487
+ *
488
+ * @param {Object[]} contexts - Array of parent context objects
489
+ * @param {Function} compute - Function that receives parent values and returns composed value
490
+ * @returns {Object} New context object
491
+ *
492
+ * @example
493
+ * const ComposedCtx = composeContexts(
494
+ * [ThemeContext, LocaleContext],
495
+ * (theme, locale) => ({ theme, locale, isDark: theme === 'dark' })
496
+ * );
497
+ */
498
+ function composeContexts(contexts, compute) {
499
+ const defaultValues = contexts.map((ctx) => ctx._defaultValue);
500
+ const composedDefault = compute(...defaultValues);
501
+ return createContext(composedDefault);
502
+ }
503
+
504
+ // ─── Context Tree Utilities ───────────────────────────────────────────────────
505
+
506
+ /**
507
+ * Find the nearest Provider for a given context in the component tree.
508
+ * @param {Object} component - Starting component
509
+ * @param {Object} context - Context to search for
510
+ * @returns {Object|null} Provider component or null
511
+ */
512
+ function findNearestProvider(component, context) {
513
+ let current = component;
514
+ while (current) {
515
+ if (current._contextProvider === context._id) {
516
+ return current;
517
+ }
518
+ current = current._parentComponent;
519
+ }
520
+ return null;
521
+ }
522
+
523
+ /**
524
+ * Get all context values from the component tree.
525
+ * @param {Object} component - Starting component
526
+ * @returns {Object} Map of context IDs to values
527
+ */
528
+ function getAllContextValues(component) {
529
+ const values = {};
530
+ let current = component;
531
+ while (current) {
532
+ if (current._contextProvider && current[Symbol.for('__ED_CTX_' + current._contextProvider + '__value')]) {
533
+ values[current._contextProvider] = current[Symbol.for('__ED_CTX_' + current._contextProvider + '__value')];
534
+ }
535
+ current = current._parentComponent;
536
+ }
537
+ return values;
538
+ }
539
+
540
+ // ─── Hook Context Management ──────────────────────────────────────────────────
541
+
542
+ function setCurrentComponent(component) {
543
+ currentlyRenderingComponent = component;
544
+ hookIndex = 0;
545
+ }
546
+
547
+ function resetHookIndex() {
548
+ hookIndex = 0;
549
+ currentlyRenderingComponent = null;
550
+ }
551
+
552
+ // ─── Cleanup ──────────────────────────────────────────────────────────────────
553
+
554
+ /**
555
+ * Clean up context subscriptions when a component unmounts.
556
+ * @param {Object} component - The unmounting component
557
+ */
558
+ function cleanupContextSubscriptions(component) {
559
+ if (!component._hooks) return;
560
+
561
+ component._hooks.forEach((hook) => {
562
+ if (hook && (hook.type === 'context' || hook.type === 'context-selector' || hook.type === 'context-observer')) {
563
+ if (typeof hook._unsubscribe === 'function') {
564
+ hook._unsubscribe();
565
+ hook._unsubscribe = null;
566
+ }
567
+ }
568
+ });
569
+ }
570
+
571
+ // ─── Exports ──────────────────────────────────────────────────────────────────
572
+
573
+ module.exports = {
574
+ createContext,
575
+ useContext,
576
+ useContextSelector,
577
+ useContextObserver,
578
+ useMergedContext,
579
+ composeContexts,
580
+ setCurrentComponent,
581
+ resetHookIndex,
582
+ findNearestProvider,
583
+ getAllContextValues,
584
+ cleanupContextSubscriptions,
585
+ getContextVersion,
586
+ };