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,483 @@
1
+ /**
2
+ * Client-Side Router
3
+ * ElementDrawing Framework - Route definition, matching, navigation,
4
+ * History API, hash fallback, guards, lazy loading, nested routes,
5
+ * named routes, transitions, and query string parsing.
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ // ─── Route Definition ─────────────────────────────────────────────────────────
11
+
12
+ /**
13
+ * Create a route configuration.
14
+ * @param {Object} config
15
+ * @param {string} config.path - URL path pattern
16
+ * @param {Function|string} config.component - Component to render
17
+ * @param {string} [config.name] - Named route
18
+ * @param {Object} [config.meta] - Route metadata
19
+ * @param {Function} [config.redirect] - Redirect function or path
20
+ * @param {Function} [config.beforeEnter] - Navigation guard
21
+ * @param {Array} [config.children] - Child routes
22
+ * @param {Object} [config.props] - Props to pass to component
23
+ * @returns {Object} Route configuration
24
+ */
25
+ function createRoute(config) {
26
+ return {
27
+ path: config.path,
28
+ component: config.component,
29
+ name: config.name || null,
30
+ meta: config.meta || {},
31
+ redirect: config.redirect || null,
32
+ beforeEnter: config.beforeEnter || null,
33
+ children: (config.children || []).map((child) => {
34
+ // Inherit parent path prefix
35
+ if (config.path && config.path !== '/' && child.path && !child.path.startsWith('/')) {
36
+ child.path = config.path.replace(/\/$/, '') + '/' + child.path;
37
+ }
38
+ return createRoute(child);
39
+ }),
40
+ props: config.props || {},
41
+ _regex: null,
42
+ _paramNames: [],
43
+ };
44
+ }
45
+
46
+ // ─── Path Matching ────────────────────────────────────────────────────────────
47
+
48
+ /**
49
+ * Compile a path pattern into a regex.
50
+ * Supports :param, :param?, *wildcard
51
+ * @param {string} path
52
+ * @returns {{ regex: RegExp, paramNames: string[] }}
53
+ */
54
+ function compilePath(path) {
55
+ const paramNames = [];
56
+ let regexStr = '^' + path
57
+ .replace(/\//g, '\\/')
58
+ .replace(/:([^/]+)/g, (_, paramName) => {
59
+ if (paramName.endsWith('?')) {
60
+ paramNames.push(paramName.slice(0, -1));
61
+ return '(?:([^/]+))?';
62
+ }
63
+ paramNames.push(paramName);
64
+ return '([^/]+)';
65
+ })
66
+ .replace(/\*([^/]*)/, '(?:.*)') +
67
+ '(?:\\?.*)?$';
68
+
69
+ return { regex: new RegExp(regexStr), paramNames };
70
+ }
71
+
72
+ /**
73
+ * Match a path against a route pattern.
74
+ * @param {string} pattern - Route pattern (e.g., '/users/:id')
75
+ * @param {string} path - Actual path to match
76
+ * @param {boolean} [exact=true] - Require exact match
77
+ * @returns {Object|null} Match result with params, or null
78
+ */
79
+ function matchPath(pattern, path, exact) {
80
+ exact = exact !== false;
81
+ const { regex, paramNames } = compilePath(pattern);
82
+ const match = path.match(regex);
83
+
84
+ if (!match) return null;
85
+
86
+ const params = {};
87
+ paramNames.forEach((name, i) => {
88
+ params[name] = match[i + 1] !== undefined ? decodeURIComponent(match[i + 1]) : undefined;
89
+ });
90
+
91
+ // Check if remaining path exists for non-exact match
92
+ const matchedLength = match[0].length;
93
+ if (exact && matchedLength < path.length) {
94
+ // Check for query string
95
+ if (path[matchedLength] !== '?') return null;
96
+ }
97
+
98
+ return {
99
+ path: match[0].replace(/\?.*$/, ''),
100
+ params,
101
+ isExact: matchedLength === path.length || path[matchedLength] === '?',
102
+ };
103
+ }
104
+
105
+ /**
106
+ * Match a path against all registered routes.
107
+ * @param {string} path
108
+ * @param {Array} routes
109
+ * @returns {Object|null} Matched route with params
110
+ */
111
+ function matchRoute(path, routes) {
112
+ for (let i = 0; i < routes.length; i++) {
113
+ const route = routes[i];
114
+ const match = matchPath(route.path, path, !route.children || route.children.length === 0);
115
+
116
+ if (match) {
117
+ const result = { route, match };
118
+
119
+ // Check children for nested matching
120
+ if (route.children && route.children.length > 0) {
121
+ const remainingPath = path.slice(match.path.length) || '/';
122
+ const childMatch = matchRoute(remainingPath, route.children);
123
+ if (childMatch) {
124
+ result.child = childMatch;
125
+ Object.assign(result.match.params, childMatch.match.params);
126
+ }
127
+ }
128
+
129
+ return result;
130
+ }
131
+ }
132
+ return null;
133
+ }
134
+
135
+ // ─── Query String Parsing ─────────────────────────────────────────────────────
136
+
137
+ /**
138
+ * Parse a query string into an object.
139
+ * @param {string} qs
140
+ * @returns {Object}
141
+ */
142
+ function parseQuery(qs) {
143
+ if (!qs) return {};
144
+ qs = qs.replace(/^\?/, '');
145
+ const params = {};
146
+ qs.split('&').forEach((pair) => {
147
+ const [key, val] = pair.split('=');
148
+ if (!key) return;
149
+ const decoded = decodeURIComponent(val || '');
150
+ if (params[decodeURIComponent(key)]) {
151
+ if (!Array.isArray(params[decodeURIComponent(key)])) {
152
+ params[decodeURIComponent(key)] = [params[decodeURIComponent(key)]];
153
+ }
154
+ params[decodeURIComponent(key)].push(decoded);
155
+ } else {
156
+ params[decodeURIComponent(key)] = decoded;
157
+ }
158
+ });
159
+ return params;
160
+ }
161
+
162
+ /**
163
+ * Stringify an object into a query string.
164
+ * @param {Object} params
165
+ * @returns {string}
166
+ */
167
+ function stringifyQuery(params) {
168
+ if (!params) return '';
169
+ return Object.keys(params)
170
+ .filter((k) => params[k] !== undefined && params[k] !== null)
171
+ .map((k) => encodeURIComponent(k) + '=' + encodeURIComponent(params[k]))
172
+ .join('&');
173
+ }
174
+
175
+ // ─── Router Class ─────────────────────────────────────────────────────────────
176
+
177
+ function Router(options) {
178
+ options = options || {};
179
+ this.routes = [];
180
+ this.currentRoute = null;
181
+ this.currentParams = {};
182
+ this.currentQuery = {};
183
+ this.beforeEachGuards = [];
184
+ this.afterEachGuards = [];
185
+ this._listeners = [];
186
+ this._mode = options.mode || 'history'; // 'history' | 'hash'
187
+ this._base = options.base || '';
188
+ this._transition = options.transition || null;
189
+ this._namedRoutes = {};
190
+
191
+ // Register routes
192
+ if (options.routes) {
193
+ options.routes.forEach((r) => this.addRoute(r));
194
+ }
195
+
196
+ // Bind popstate
197
+ this._onPopState = this._onPopState.bind(this);
198
+ }
199
+
200
+ /**
201
+ * Add a route to the router.
202
+ * @param {Object} routeConfig
203
+ */
204
+ Router.prototype.addRoute = function (routeConfig) {
205
+ const route = createRoute(routeConfig);
206
+ this.routes.push(route);
207
+ if (route.name) {
208
+ this._namedRoutes[route.name] = route;
209
+ }
210
+ return this;
211
+ };
212
+
213
+ /**
214
+ * Remove a route by path.
215
+ * @param {string} path
216
+ */
217
+ Router.prototype.removeRoute = function (path) {
218
+ const idx = this.routes.findIndex((r) => r.path === path);
219
+ if (idx !== -1) this.routes.splice(idx, 1);
220
+ return this;
221
+ };
222
+
223
+ // ─── Navigation ───────────────────────────────────────────────────────────────
224
+
225
+ /**
226
+ * Navigate to a path.
227
+ * @param {string} path
228
+ * @param {Object} [query]
229
+ * @returns {Promise<boolean>}
230
+ */
231
+ Router.prototype.push = function (path, query) {
232
+ return this._navigate(path, query, 'push');
233
+ };
234
+
235
+ /**
236
+ * Replace current history entry.
237
+ * @param {string} path
238
+ * @param {Object} [query]
239
+ * @returns {Promise<boolean>}
240
+ */
241
+ Router.prototype.replace = function (path, query) {
242
+ return this._navigate(path, query, 'replace');
243
+ };
244
+
245
+ /**
246
+ * Navigate by history offset.
247
+ * @param {number} offset
248
+ */
249
+ Router.prototype.go = function (offset) {
250
+ window.history.go(offset);
251
+ };
252
+
253
+ /**
254
+ * Go back one entry.
255
+ */
256
+ Router.prototype.back = function () { this.go(-1); };
257
+
258
+ /**
259
+ * Go forward one entry.
260
+ */
261
+ Router.prototype.forward = function () { this.go(1); };
262
+
263
+ /**
264
+ * Navigate to a named route.
265
+ * @param {string} name
266
+ * @param {Object} [params]
267
+ * @param {Object} [query]
268
+ * @returns {Promise<boolean>}
269
+ */
270
+ Router.prototype.pushByName = function (name, params, query) {
271
+ const route = this._namedRoutes[name];
272
+ if (!route) return Promise.reject(new Error('Route not found: ' + name));
273
+ const path = buildPath(route.path, params);
274
+ return this.push(path, query);
275
+ };
276
+
277
+ // ─── Internal Navigation ──────────────────────────────────────────────────────
278
+
279
+ Router.prototype._navigate = async function (path, query, action) {
280
+ const fullPath = this._base + path + (query ? '?' + stringifyQuery(query) : '');
281
+
282
+ // Run beforeEach guards
283
+ const toRoute = matchRoute(path, this.routes);
284
+ const fromRoute = this.currentRoute;
285
+
286
+ for (const guard of this.beforeEachGuards) {
287
+ const result = await guard(toRoute, fromRoute);
288
+ if (result === false) return false;
289
+ if (typeof result === 'string') {
290
+ return this.push(result);
291
+ }
292
+ if (typeof result === 'object' && result.path) {
293
+ return this.push(result.path, result.query);
294
+ }
295
+ }
296
+
297
+ // Run route-level beforeEnter guard
298
+ if (toRoute && toRoute.route && toRoute.route.beforeEnter) {
299
+ const result = await toRoute.route.beforeEnter(toRoute, fromRoute);
300
+ if (result === false) return false;
301
+ if (typeof result === 'string') return this.push(result);
302
+ }
303
+
304
+ // Update history
305
+ if (this._mode === 'history') {
306
+ if (action === 'push') {
307
+ window.history.pushState({ path: fullPath }, '', fullPath);
308
+ } else {
309
+ window.history.replaceState({ path: fullPath }, '', fullPath);
310
+ }
311
+ } else {
312
+ window.location.hash = path;
313
+ }
314
+
315
+ // Update current state
316
+ const parsedPath = this._parseFullPath(fullPath);
317
+ this.currentRoute = toRoute;
318
+ this.currentParams = toRoute ? toRoute.match.params : {};
319
+ this.currentQuery = parsedPath.query;
320
+
321
+ // Run afterEach guards
322
+ for (const guard of this.afterEachGuards) {
323
+ guard(toRoute, fromRoute);
324
+ }
325
+
326
+ // Notify listeners
327
+ this._notifyListeners(toRoute, fromRoute);
328
+
329
+ return true;
330
+ };
331
+
332
+ Router.prototype._parseFullPath = function (fullPath) {
333
+ const [path, qs] = fullPath.split('?');
334
+ return { path, query: parseQuery(qs) };
335
+ };
336
+
337
+ Router.prototype._onPopState = function () {
338
+ const path = this._mode === 'history'
339
+ ? window.location.pathname.slice(this._base.length) || '/'
340
+ : window.location.hash.slice(1) || '/';
341
+ this.replace(path);
342
+ };
343
+
344
+ // ─── Route Guards ─────────────────────────────────────────────────────────────
345
+
346
+ /**
347
+ * Register a beforeEach guard.
348
+ * @param {Function} guard - (to, from) => boolean | string | { path, query }
349
+ * @returns {Function} Remove guard function
350
+ */
351
+ Router.prototype.beforeEach = function (guard) {
352
+ this.beforeEachGuards.push(guard);
353
+ return () => {
354
+ const idx = this.beforeEachGuards.indexOf(guard);
355
+ if (idx !== -1) this.beforeEachGuards.splice(idx, 1);
356
+ };
357
+ };
358
+
359
+ /**
360
+ * Register an afterEach guard.
361
+ * @param {Function} guard - (to, from) => void
362
+ * @returns {Function} Remove guard function
363
+ */
364
+ Router.prototype.afterEach = function (guard) {
365
+ this.afterEachGuards.push(guard);
366
+ return () => {
367
+ const idx = this.afterEachGuards.indexOf(guard);
368
+ if (idx !== -1) this.afterEachGuards.splice(idx, 1);
369
+ };
370
+ };
371
+
372
+ // ─── Lazy Loading ─────────────────────────────────────────────────────────────
373
+
374
+ /**
375
+ * Create a lazy-loaded route component.
376
+ * @param {Function} loader - () => Promise<Component>
377
+ * @returns {Function} Lazy component
378
+ */
379
+ Router.prototype.lazy = function (loader) {
380
+ let cachedComponent = null;
381
+ return function LazyComponent(props) {
382
+ if (cachedComponent) return cachedComponent(props);
383
+ return loader().then((module) => {
384
+ cachedComponent = module.default || module;
385
+ return cachedComponent(props);
386
+ });
387
+ };
388
+ };
389
+
390
+ // ─── Active Route Detection ───────────────────────────────────────────────────
391
+
392
+ /**
393
+ * Check if a path is currently active.
394
+ * @param {string} path
395
+ * @param {boolean} [exact=false]
396
+ * @returns {boolean}
397
+ */
398
+ Router.prototype.isActive = function (path, exact) {
399
+ if (!this.currentRoute) return false;
400
+ const currentPath = this.currentRoute.match ? this.currentRoute.match.path : '';
401
+ if (exact) return currentPath === path;
402
+ return currentPath.startsWith(path);
403
+ };
404
+
405
+ // ─── Listener Management ──────────────────────────────────────────────────────
406
+
407
+ Router.prototype.onRouteChange = function (callback) {
408
+ this._listeners.push(callback);
409
+ return () => {
410
+ const idx = this._listeners.indexOf(callback);
411
+ if (idx !== -1) this._listeners.splice(idx, 1);
412
+ };
413
+ };
414
+
415
+ Router.prototype._notifyListeners = function (to, from) {
416
+ this._listeners.forEach((cb) => {
417
+ try { cb(to, from); } catch (e) { console.error('[Router] Listener error:', e); }
418
+ });
419
+ };
420
+
421
+ // ─── Start/Stop ───────────────────────────────────────────────────────────────
422
+
423
+ /**
424
+ * Start listening for navigation events.
425
+ */
426
+ Router.prototype.start = function () {
427
+ if (this._mode === 'history') {
428
+ window.addEventListener('popstate', this._onPopState);
429
+ // Navigate to current URL
430
+ const path = window.location.pathname.slice(this._base.length) || '/';
431
+ this.replace(path);
432
+ } else {
433
+ window.addEventListener('hashchange', this._onPopState);
434
+ const hash = window.location.hash.slice(1) || '/';
435
+ this.replace(hash);
436
+ }
437
+ };
438
+
439
+ /**
440
+ * Stop the router.
441
+ */
442
+ Router.prototype.stop = function () {
443
+ if (this._mode === 'history') {
444
+ window.removeEventListener('popstate', this._onPopState);
445
+ } else {
446
+ window.removeEventListener('hashchange', this._onPopState);
447
+ }
448
+ };
449
+
450
+ // ─── Helper: Build path from pattern and params ──────────────────────────────
451
+
452
+ function buildPath(pattern, params) {
453
+ params = params || {};
454
+ return pattern.replace(/:([^/?]+)/g, (_, name) => {
455
+ if (name.endsWith('?')) name = name.slice(0, -1);
456
+ return params[name] !== undefined ? encodeURIComponent(params[name]) : '';
457
+ }).replace(/\/+/g, '/').replace(/\/$/, '') || '/';
458
+ }
459
+
460
+ // ─── Factory ──────────────────────────────────────────────────────────────────
461
+
462
+ /**
463
+ * Create a new router instance.
464
+ * @param {Object} [options]
465
+ * @returns {Router}
466
+ */
467
+ function createRouter(options) {
468
+ return new Router(options);
469
+ }
470
+
471
+ // ─── Exports ──────────────────────────────────────────────────────────────────
472
+
473
+ module.exports = {
474
+ Router,
475
+ createRouter,
476
+ createRoute,
477
+ matchPath,
478
+ matchRoute,
479
+ compilePath,
480
+ parseQuery,
481
+ stringifyQuery,
482
+ buildPath,
483
+ };