create-nativecore 0.1.1 → 0.2.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 (175) hide show
  1. package/README.md +6 -14
  2. package/bin/index.mjs +402 -431
  3. package/package.json +3 -2
  4. package/template/.env.example +28 -0
  5. package/template/.htmlhintrc +14 -0
  6. package/template/api/data/dashboard.json +11 -0
  7. package/template/api/data/users.json +18 -0
  8. package/template/api/mockApi.js +161 -0
  9. package/template/assets/icon.svg +13 -0
  10. package/template/assets/logo.svg +25 -0
  11. package/template/eslint.config.js +94 -0
  12. package/template/index.html +137 -0
  13. package/template/manifest.json +19 -0
  14. package/template/public/.well-known/security.txt +9 -0
  15. package/template/public/_headers +24 -0
  16. package/template/public/_redirects +14 -0
  17. package/template/public/assets/icon.svg +13 -0
  18. package/template/public/assets/logo.svg +25 -0
  19. package/template/public/manifest.json +19 -0
  20. package/template/public/robots.txt +13 -0
  21. package/template/public/sitemap.xml +27 -0
  22. package/template/scripts/build-for-bots.mjs +121 -0
  23. package/template/scripts/convert-to-ts.mjs +106 -0
  24. package/template/scripts/fix-encoding.mjs +38 -0
  25. package/template/scripts/fix-svg-paths.mjs +32 -0
  26. package/template/scripts/generate-cf-router.mjs +52 -0
  27. package/template/scripts/inject-dev-tools.mjs +41 -0
  28. package/template/scripts/inject-version.mjs +65 -0
  29. package/template/scripts/make-component.mjs +445 -0
  30. package/template/scripts/make-component.mjs.backup +432 -0
  31. package/template/scripts/make-controller.mjs +119 -0
  32. package/template/scripts/make-core-component.mjs +303 -0
  33. package/template/scripts/make-view.mjs +346 -0
  34. package/template/scripts/minify.mjs +71 -0
  35. package/template/scripts/prepare-static-assets.mjs +141 -0
  36. package/template/scripts/prompt-bot-build.mjs +223 -0
  37. package/template/scripts/remove-component.mjs +170 -0
  38. package/template/scripts/remove-core-component.mjs +156 -0
  39. package/template/scripts/remove-dev.mjs +13 -0
  40. package/template/scripts/remove-view.mjs +200 -0
  41. package/template/scripts/strip-dev-blocks.mjs +30 -0
  42. package/template/scripts/watch-compile.mjs +69 -0
  43. package/template/server.js +1066 -0
  44. package/template/src/app.ts +115 -0
  45. package/template/src/components/appRegistry.ts +8 -0
  46. package/template/src/components/core/app-footer.ts +27 -0
  47. package/template/src/components/core/app-header.ts +175 -0
  48. package/template/src/components/core/app-sidebar.ts +238 -0
  49. package/template/src/components/core/loading-spinner.ts +25 -0
  50. package/template/src/components/core/nc-a.ts +313 -0
  51. package/template/src/components/core/nc-accordion.ts +186 -0
  52. package/template/src/components/core/nc-alert.ts +153 -0
  53. package/template/src/components/core/nc-animation.ts +1150 -0
  54. package/template/src/components/core/nc-autocomplete.ts +271 -0
  55. package/template/src/components/core/nc-avatar-group.ts +113 -0
  56. package/template/src/components/core/nc-avatar.ts +148 -0
  57. package/template/src/components/core/nc-badge.ts +86 -0
  58. package/template/src/components/core/nc-bottom-nav.ts +214 -0
  59. package/template/src/components/core/nc-breadcrumb.ts +96 -0
  60. package/template/src/components/core/nc-button.ts +307 -0
  61. package/template/src/components/core/nc-card.ts +160 -0
  62. package/template/src/components/core/nc-checkbox.ts +282 -0
  63. package/template/src/components/core/nc-chip.ts +115 -0
  64. package/template/src/components/core/nc-code.ts +314 -0
  65. package/template/src/components/core/nc-collapsible.ts +154 -0
  66. package/template/src/components/core/nc-color-picker.ts +268 -0
  67. package/template/src/components/core/nc-copy-button.ts +119 -0
  68. package/template/src/components/core/nc-date-picker.ts +443 -0
  69. package/template/src/components/core/nc-div.ts +280 -0
  70. package/template/src/components/core/nc-divider.ts +81 -0
  71. package/template/src/components/core/nc-drawer.ts +230 -0
  72. package/template/src/components/core/nc-dropdown.ts +178 -0
  73. package/template/src/components/core/nc-empty-state.ts +134 -0
  74. package/template/src/components/core/nc-file-upload.ts +354 -0
  75. package/template/src/components/core/nc-form.ts +312 -0
  76. package/template/src/components/core/nc-image.ts +184 -0
  77. package/template/src/components/core/nc-input.ts +383 -0
  78. package/template/src/components/core/nc-kbd.ts +48 -0
  79. package/template/src/components/core/nc-menu-item.ts +193 -0
  80. package/template/src/components/core/nc-menu.ts +376 -0
  81. package/template/src/components/core/nc-modal.ts +238 -0
  82. package/template/src/components/core/nc-nav-item.ts +151 -0
  83. package/template/src/components/core/nc-number-input.ts +350 -0
  84. package/template/src/components/core/nc-otp-input.ts +235 -0
  85. package/template/src/components/core/nc-pagination.ts +178 -0
  86. package/template/src/components/core/nc-popover.ts +260 -0
  87. package/template/src/components/core/nc-progress-circular.ts +119 -0
  88. package/template/src/components/core/nc-progress.ts +134 -0
  89. package/template/src/components/core/nc-radio.ts +235 -0
  90. package/template/src/components/core/nc-rating.ts +266 -0
  91. package/template/src/components/core/nc-rich-text.ts +283 -0
  92. package/template/src/components/core/nc-scroll-top.ts +116 -0
  93. package/template/src/components/core/nc-select.ts +452 -0
  94. package/template/src/components/core/nc-skeleton.ts +107 -0
  95. package/template/src/components/core/nc-slider.ts +285 -0
  96. package/template/src/components/core/nc-snackbar.ts +230 -0
  97. package/template/src/components/core/nc-splash.ts +343 -0
  98. package/template/src/components/core/nc-stepper.ts +247 -0
  99. package/template/src/components/core/nc-switch.ts +281 -0
  100. package/template/src/components/core/nc-tab-item.ts +138 -0
  101. package/template/src/components/core/nc-table.ts +279 -0
  102. package/template/src/components/core/nc-tabs.ts +554 -0
  103. package/template/src/components/core/nc-tag-input.ts +279 -0
  104. package/template/src/components/core/nc-textarea.ts +216 -0
  105. package/template/src/components/core/nc-time-picker.ts +438 -0
  106. package/template/src/components/core/nc-timeline.ts +186 -0
  107. package/template/src/components/core/nc-tooltip.ts +143 -0
  108. package/template/src/components/frameworkRegistry.ts +68 -0
  109. package/template/src/components/preloadRegistry.ts +28 -0
  110. package/template/src/components/registry.ts +8 -0
  111. package/template/src/components/ui/dashboard-signal-lab.ts +284 -0
  112. package/template/src/constants/apiEndpoints.ts +27 -0
  113. package/template/src/constants/errorMessages.ts +23 -0
  114. package/template/src/constants/index.ts +8 -0
  115. package/template/src/constants/routePaths.ts +15 -0
  116. package/template/src/constants/storageKeys.ts +18 -0
  117. package/template/src/controllers/dashboard.controller.ts +200 -0
  118. package/template/src/controllers/home.controller.ts +21 -0
  119. package/template/src/controllers/index.ts +11 -0
  120. package/template/src/controllers/login.controller.ts +131 -0
  121. package/template/src/core/component.ts +354 -0
  122. package/template/src/core/errorHandler.ts +85 -0
  123. package/template/src/core/gpu-animation.ts +604 -0
  124. package/template/src/core/http.ts +173 -0
  125. package/template/src/core/lazyComponents.ts +90 -0
  126. package/template/src/core/router.ts +642 -0
  127. package/template/src/core/signals.ts +146 -0
  128. package/template/src/core/state.ts +248 -0
  129. package/template/src/dev/component-editor.ts +1363 -0
  130. package/template/src/dev/component-overlay.ts +278 -0
  131. package/template/src/dev/context-menu.ts +223 -0
  132. package/template/src/dev/denc-tools.ts +250 -0
  133. package/template/src/dev/hmr.ts +189 -0
  134. package/template/src/dev/nfbs.code-workspace +27 -0
  135. package/template/src/dev/outline-panel.ts +1247 -0
  136. package/template/src/middleware/auth.middleware.ts +23 -0
  137. package/template/src/routes/routes.ts +38 -0
  138. package/template/src/services/api.service.ts +394 -0
  139. package/template/src/services/auth.service.ts +176 -0
  140. package/template/src/services/index.ts +8 -0
  141. package/template/src/services/logger.service.ts +74 -0
  142. package/template/src/services/storage.service.ts +88 -0
  143. package/template/src/stores/appStore.ts +57 -0
  144. package/template/src/stores/uiStore.ts +36 -0
  145. package/template/src/styles/core-variables.css +219 -0
  146. package/template/src/styles/core.css +710 -0
  147. package/template/src/styles/main.css +3164 -0
  148. package/template/src/styles/variables.css +152 -0
  149. package/template/src/types/global.d.ts +47 -0
  150. package/template/src/utils/cacheBuster.ts +20 -0
  151. package/template/src/utils/dom.ts +149 -0
  152. package/template/src/utils/events.ts +203 -0
  153. package/template/src/utils/form.ts +176 -0
  154. package/template/src/utils/formatters.ts +169 -0
  155. package/template/src/utils/helpers.ts +195 -0
  156. package/template/src/utils/markdown.ts +307 -0
  157. package/template/src/utils/sidebar.ts +96 -0
  158. package/template/src/utils/smoothScroll.ts +85 -0
  159. package/template/src/utils/templates.ts +23 -0
  160. package/template/src/utils/validation.ts +73 -0
  161. package/template/src/views/protected/dashboard.html +293 -0
  162. package/template/src/views/public/home.html +150 -0
  163. package/template/src/views/public/login.html +102 -0
  164. package/template/tests/unit/component.test.ts +87 -0
  165. package/template/tests/unit/computed.test.ts +79 -0
  166. package/template/tests/unit/form.test.ts +68 -0
  167. package/template/tests/unit/formatters.test.ts +49 -0
  168. package/template/tests/unit/lazy-components.test.ts +59 -0
  169. package/template/tests/unit/markdown.test.ts +62 -0
  170. package/template/tests/unit/router.test.ts +112 -0
  171. package/template/tests/unit/signals.test.ts +54 -0
  172. package/template/tests/unit/validation.test.ts +50 -0
  173. package/template/tsconfig.build.json +21 -0
  174. package/template/tsconfig.json +51 -0
  175. package/template/vitest.config.ts +36 -0
@@ -0,0 +1,152 @@
1
+ /**
2
+ * CSS Variables - Design System
3
+ *
4
+ * ⚠️ DEVELOPER CUSTOMIZATION ZONE ⚠️
5
+ *
6
+ * This file is for YOUR custom variables and theme overrides.
7
+ * Feel free to add, modify, or remove any variables here.
8
+ *
9
+ * Framework variables (prefixed with --nc-) are in core-variables.css.
10
+ * DO NOT use the --nc- prefix here to avoid conflicts.
11
+ *
12
+ * Optional: Override framework theme colors by setting --nc-theme-* variables:
13
+ * --nc-theme-primary: #your-color;
14
+ * --nc-theme-primary-light: #your-light-color;
15
+ * --nc-theme-primary-dark: #your-dark-color;
16
+ * --nc-theme-secondary: #your-secondary-color;
17
+ * --nc-theme-accent: #your-accent-color;
18
+ */
19
+ :root {
20
+ /* Brand Colors - Professional Emerald & Slate */
21
+ --color-primary: #10b981;
22
+ --color-primary-light: #34d399;
23
+ --color-primary-white: #ffffff;
24
+ --color-primary-dark: #059669;
25
+ --color-secondary: #3b82f6;
26
+ --color-accent: #0f172a;
27
+
28
+ /* Gradients - Subtle and Modern */
29
+ --gradient-primary: linear-gradient(135deg, #10b981 0%, #059669 100%);
30
+ --gradient-secondary: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
31
+ --gradient-accent: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
32
+ --gradient-success: linear-gradient(135deg, #10b981 0%, #059669 100%);
33
+ --gradient-subtle: linear-gradient(180deg, rgba(255, 255, 255, 0) 0%, rgba(0, 0, 0, 0.02) 100%);
34
+ --gradient-hero: linear-gradient(135deg, rgba(16, 185, 129, 0.1) 0%, rgba(5, 150, 105, 0.05) 100%);
35
+
36
+ /* Metallic Gradients - Removed for cleaner look */
37
+ --gradient-gold: linear-gradient(135deg, #10b981 0%, #059669 100%);
38
+ --gradient-silver: linear-gradient(135deg, #64748b 0%, #475569 100%);
39
+ --gradient-platinum: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
40
+ --gradient-bronze: linear-gradient(135deg, #10b981 0%, #059669 100%);
41
+
42
+ /* Neutral Colors - Tailwind Slate Palette */
43
+ --color-white: #ffffff;
44
+ --color-black: #000000;
45
+ --color-gray-50: #f8fafc;
46
+ --color-gray-100: #f1f5f9;
47
+ --color-gray-200: #e2e8f0;
48
+ --color-gray-300: #cbd5e1;
49
+ --color-gray-400: #94a3b8;
50
+ --color-gray-500: #64748b;
51
+ --color-gray-600: #475569;
52
+ --color-gray-700: #334155;
53
+ --color-gray-800: #1e293b;
54
+ --color-gray-900: #0f172a;
55
+
56
+ /* Semantic Colors */
57
+ --color-success: #10b981;
58
+ --color-warning: #f59e0b;
59
+ --color-error: #ef4444;
60
+ --color-info: #3b82f6;
61
+
62
+ /* Text Colors */
63
+ --color-text: #0f172a;
64
+ --color-text-secondary: #64748b;
65
+ --color-text-muted: #94a3b8;
66
+
67
+ /* Background Colors */
68
+ --color-bg: #ffffff;
69
+ --color-bg-secondary: #f8fafc;
70
+ --color-bg-tertiary: #f1f5f9;
71
+ --color-border: #e2e8f0;
72
+
73
+ /* Short aliases for component use */
74
+ --primary: var(--color-primary);
75
+ --primary-dark: var(--color-primary-dark);
76
+ --secondary: var(--color-secondary);
77
+ --secondary-dark: #2563eb;
78
+ --success: var(--color-success);
79
+ --danger: var(--color-error);
80
+ --text: var(--color-text);
81
+ --text-secondary: var(--color-text-secondary);
82
+ --bg: var(--color-bg);
83
+ --bg-secondary: var(--color-bg-secondary);
84
+ --border: var(--color-border);
85
+
86
+ /* Shadows - Subtle and Modern */
87
+ --shadow-xs: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
88
+ --shadow-sm: 0 1px 3px 0 rgba(0, 0, 0, 0.08), 0 1px 2px 0 rgba(0, 0, 0, 0.04);
89
+ --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.08), 0 2px 4px -1px rgba(0, 0, 0, 0.04);
90
+ --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.08), 0 4px 6px -2px rgba(0, 0, 0, 0.04);
91
+ --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.08), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
92
+ --shadow-2xl: 0 25px 50px -12px rgba(0, 0, 0, 0.15);
93
+ --shadow-inner: inset 0 2px 4px 0 rgba(0, 0, 0, 0.06);
94
+
95
+ /* Colored Shadows - For hover effects */
96
+ --shadow-primary: 0 4px 12px rgba(16, 185, 129, 0.25);
97
+ --shadow-secondary: 0 4px 12px rgba(59, 130, 246, 0.25);
98
+ --shadow-success: 0 4px 12px rgba(16, 185, 129, 0.25);
99
+
100
+ /* Typography */
101
+ --font-family: 'Geist', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
102
+ --font-family-mono: 'Geist Mono', 'SF Mono', Monaco, 'Cascadia Code', Consolas, monospace;
103
+
104
+ --font-size-xs: 0.75rem; /* 12px */
105
+ --font-size-sm: 0.875rem; /* 14px */
106
+ --font-size-base: 1rem; /* 16px */
107
+ --font-size-lg: 1.125rem; /* 18px */
108
+ --font-size-xl: 1.25rem; /* 20px */
109
+ --font-size-2xl: 1.5rem; /* 24px */
110
+ --font-size-3xl: 1.875rem; /* 30px */
111
+ --font-size-4xl: 2.25rem; /* 36px */
112
+
113
+ --font-weight-normal: 400;
114
+ --font-weight-medium: 500;
115
+ --font-weight-semibold: 600;
116
+ --font-weight-bold: 700;
117
+
118
+ /* Spacing */
119
+ --spacing-xs: 0.25rem; /* 4px */
120
+ --spacing-sm: 0.5rem; /* 8px */
121
+ --spacing-md: 1rem; /* 16px */
122
+ --spacing-lg: 1.5rem; /* 24px */
123
+ --spacing-xl: 2rem; /* 32px */
124
+ --spacing-2xl: 3rem; /* 48px */
125
+ --spacing-3xl: 4rem; /* 64px */
126
+
127
+ /* Border Radius */
128
+ --radius-sm: 0.25rem; /* 4px */
129
+ --radius-md: 0.5rem; /* 8px */
130
+ --radius-lg: 0.75rem; /* 12px */
131
+ --radius-xl: 1rem; /* 16px */
132
+ --radius-2xl: 1.5rem; /* 24px */
133
+ --radius-full: 9999px;
134
+
135
+ /* Transitions - Smooth animations */
136
+ --transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
137
+ --transition-base: 250ms cubic-bezier(0.4, 0, 0.2, 1);
138
+ --transition-slow: 350ms cubic-bezier(0.4, 0, 0.2, 1);
139
+
140
+ /* Layout */
141
+ --container-max-width: 100%;
142
+ --header-height: 70px;
143
+
144
+ /* Z-index Scale */
145
+ --z-dropdown: 1000;
146
+ --z-sticky: 1020;
147
+ --z-fixed: 1030;
148
+ --z-modal-backdrop: 1040;
149
+ --z-modal: 1050;
150
+ --z-popover: 1060;
151
+ --z-tooltip: 1070;
152
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Global Type Declarations
3
+ */
4
+
5
+ interface Toast {
6
+ success(message: string): void;
7
+ error(message: string): void;
8
+ warning(message: string): void;
9
+ info(message: string): void;
10
+ }
11
+
12
+ // ── Custom Element Registry ──────────────────────────────────────────────────
13
+ // Registering nc-* elements here gives TypeScript IntelliSense in .ts files:
14
+ // document.querySelector('nc-button') → typed as NcButton
15
+ // document.createElement('nc-tabs') → typed as NcTabs
16
+
17
+ import type { NcA } from '../components/core/nc-a.js';
18
+ import type { NcButton } from '../components/core/nc-button.js';
19
+ import type { NcCard } from '../components/core/nc-card.js';
20
+ import type { NcTabs } from '../components/core/nc-tabs.js';
21
+ import type { NcTabItem } from '../components/core/nc-tab-item.js';
22
+ import type { NcMenu } from '../components/core/nc-menu.js';
23
+ import type { NcMenuItem } from '../components/core/nc-menu-item.js';
24
+
25
+ declare global {
26
+ interface Window {
27
+ Toast: Toast;
28
+ router: unknown;
29
+ __NATIVECORE_DEV__?: boolean;
30
+ dom: {
31
+ query: (selector: string) => Element | null;
32
+ queryAll: (selector: string) => NodeListOf<Element>;
33
+ $: (selector: string) => Element | null;
34
+ $$: (selector: string) => NodeListOf<Element>;
35
+ };
36
+ }
37
+
38
+ interface HTMLElementTagNameMap {
39
+ 'nc-a': NcA;
40
+ 'nc-button': NcButton;
41
+ 'nc-card': NcCard;
42
+ 'nc-tabs': NcTabs;
43
+ 'nc-tab-item': NcTabItem;
44
+ 'nc-menu': NcMenu;
45
+ 'nc-menu-item': NcMenuItem;
46
+ }
47
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Cache Busting Utility
3
+ */
4
+
5
+ const isDevelopment = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1';
6
+
7
+ export const cacheVersion = isDevelopment
8
+ ? Date.now()
9
+ : '1.0.0-20260414231644';
10
+
11
+ export function bustCache(url: string): string {
12
+ const separator = url.includes('?') ? '&' : '?';
13
+ return `${url}${separator}v=${cacheVersion}`;
14
+ }
15
+
16
+ export async function importWithBust(modulePath: string): Promise<any> {
17
+ return import(bustCache(modulePath));
18
+ }
19
+
20
+ export default { cacheVersion, bustCache, importWithBust };
@@ -0,0 +1,149 @@
1
+ /**
2
+ * DOM Utility Functions
3
+ * Shorthand helpers for common DOM operations
4
+ */
5
+
6
+ export const dom = {
7
+ /**
8
+ * Query single element (shorthand for document.querySelector)
9
+ */
10
+ query: <T extends Element = Element>(selector: string): T | null =>
11
+ document.querySelector<T>(selector),
12
+
13
+ /**
14
+ * Query multiple elements (shorthand for document.querySelectorAll)
15
+ */
16
+ queryAll: <T extends Element = Element>(selector: string): NodeListOf<T> =>
17
+ document.querySelectorAll<T>(selector),
18
+
19
+ /**
20
+ * Alias for query (similar to jQuery)
21
+ */
22
+ $: <T extends Element = Element>(selector: string): T | null =>
23
+ document.querySelector<T>(selector),
24
+
25
+ /**
26
+ * Alias for queryAll (similar to jQuery)
27
+ */
28
+ $$: <T extends Element = Element>(selector: string): NodeListOf<T> =>
29
+ document.querySelectorAll<T>(selector),
30
+
31
+ /**
32
+ * Query within a specific parent element (scoped query)
33
+ * @example dom.within(shadowRoot, '.btn')
34
+ * @example dom.within('#sidebar', 'a.active')
35
+ */
36
+ within: <T extends Element = Element>(
37
+ parent: Element | ShadowRoot | string,
38
+ selector: string
39
+ ): T | null => {
40
+ const el = typeof parent === 'string' ? document.querySelector(parent) : parent;
41
+ return el ? el.querySelector<T>(selector) : null;
42
+ },
43
+
44
+ /**
45
+ * Query all within a specific parent element (scoped query)
46
+ */
47
+ withinAll: <T extends Element = Element>(
48
+ parent: Element | ShadowRoot | string,
49
+ selector: string
50
+ ): NodeListOf<T> | T[] => {
51
+ const el = typeof parent === 'string' ? document.querySelector(parent) : parent;
52
+ return el ? el.querySelectorAll<T>(selector) : ([] as T[]);
53
+ },
54
+
55
+ /**
56
+ * Create an element with optional attributes and children
57
+ * @example dom.create('button', { class: 'btn', type: 'button' }, 'Click me')
58
+ * @example dom.create('div', { id: 'wrapper' }, childEl1, childEl2)
59
+ */
60
+ create: <K extends keyof HTMLElementTagNameMap>(
61
+ tag: K,
62
+ attrs?: Record<string, string> | null,
63
+ ...children: Array<string | Node>
64
+ ): HTMLElementTagNameMap[K] => {
65
+ const el = document.createElement(tag);
66
+ if (attrs) {
67
+ for (const [key, val] of Object.entries(attrs)) {
68
+ el.setAttribute(key, val);
69
+ }
70
+ }
71
+ for (const child of children) {
72
+ if (typeof child === 'string') {
73
+ el.appendChild(document.createTextNode(child));
74
+ } else {
75
+ el.appendChild(child);
76
+ }
77
+ }
78
+ return el;
79
+ },
80
+
81
+ /**
82
+ * Add one or more CSS classes to an element
83
+ */
84
+ addClass: (el: Element | string | null, ...classes: string[]): void => {
85
+ const target = typeof el === 'string' ? document.querySelector(el) : el;
86
+ if (target) target.classList.add(...classes);
87
+ },
88
+
89
+ /**
90
+ * Remove one or more CSS classes from an element
91
+ */
92
+ removeClass: (el: Element | string | null, ...classes: string[]): void => {
93
+ const target = typeof el === 'string' ? document.querySelector(el) : el;
94
+ if (target) target.classList.remove(...classes);
95
+ },
96
+
97
+ /**
98
+ * Toggle a CSS class on an element
99
+ */
100
+ toggleClass: (el: Element | string | null, cls: string, force?: boolean): void => {
101
+ const target = typeof el === 'string' ? document.querySelector(el) : el;
102
+ if (target) target.classList.toggle(cls, force);
103
+ },
104
+
105
+ /**
106
+ * Show an element (removes display:none inline style)
107
+ */
108
+ show: (el: Element | string | null): void => {
109
+ const target = typeof el === 'string' ? document.querySelector(el) : el;
110
+ if (target) (target as HTMLElement).style.removeProperty('display');
111
+ },
112
+
113
+ /**
114
+ * Hide an element (sets display:none inline style)
115
+ */
116
+ hide: (el: Element | string | null): void => {
117
+ const target = typeof el === 'string' ? document.querySelector(el) : el;
118
+ if (target) (target as HTMLElement).style.display = 'none';
119
+ },
120
+
121
+ /**
122
+ * Add event listener — returns an unsubscribe function for cleanup
123
+ * @example const off = dom.listen('#btn', 'click', handler); off(); // removes listener
124
+ */
125
+ listen: (
126
+ selectorOrElement: string | Element | null,
127
+ eventName: string,
128
+ handler: (event: any) => void,
129
+ options?: boolean | AddEventListenerOptions
130
+ ): () => void => {
131
+ const element = typeof selectorOrElement === 'string'
132
+ ? document.querySelector(selectorOrElement)
133
+ : selectorOrElement;
134
+
135
+ if (element) {
136
+ element.addEventListener(eventName, handler as EventListener, options);
137
+ return () => element.removeEventListener(eventName, handler as EventListener, options);
138
+ }
139
+
140
+ return () => {};
141
+ }
142
+ };
143
+
144
+ // Expose to window for console debugging
145
+ if (typeof window !== 'undefined') {
146
+ (window as any).dom = dom;
147
+ }
148
+
149
+ export default dom;
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Event handling utilities for controllers
3
+ * Works with any component and any event type
4
+ */
5
+
6
+ /**
7
+ * Generic event listener with cleanup
8
+ *
9
+ * Usage:
10
+ * on('#myBtn', 'click', handleClick);
11
+ * on('#myBtn', 'nc-click', handleClick);
12
+ * on('.input', 'input', handleInput);
13
+ */
14
+ export function on<T = Event>(
15
+ selector: string,
16
+ eventName: string,
17
+ handler: (event: T) => void
18
+ ): () => void {
19
+ const elements = document.querySelectorAll(selector);
20
+
21
+ elements.forEach(el => {
22
+ el.addEventListener(eventName, handler as EventListener);
23
+ });
24
+
25
+ // Return cleanup function
26
+ return () => {
27
+ elements.forEach(el => {
28
+ el.removeEventListener(eventName, handler as EventListener);
29
+ });
30
+ };
31
+ }
32
+
33
+ /**
34
+ * Batch event binding for cleaner controller syntax
35
+ *
36
+ * Usage:
37
+ * const cleanup = bindEvents({
38
+ * 'click': {
39
+ * '#submitBtn': handleSubmit,
40
+ * '#cancelBtn': handleCancel
41
+ * },
42
+ * 'nc-click': {
43
+ * '.action-btn': handleAction
44
+ * },
45
+ * 'input': {
46
+ * '#searchInput': handleSearch
47
+ * }
48
+ * });
49
+ */
50
+ export function bindEvents(
51
+ bindings: Record<string, Record<string, (event: any) => void>>
52
+ ): () => void {
53
+ const cleanups: Array<() => void> = [];
54
+
55
+ for (const [eventName, handlers] of Object.entries(bindings)) {
56
+ for (const [selector, handler] of Object.entries(handlers)) {
57
+ cleanups.push(on(selector, eventName, handler));
58
+ }
59
+ }
60
+
61
+ // Return cleanup function that removes all listeners
62
+ return () => {
63
+ cleanups.forEach(cleanup => cleanup());
64
+ };
65
+ }
66
+
67
+ /**
68
+ * Shorthand for common events
69
+ */
70
+ export const onClick = (selector: string, handler: (event: Event) => void) =>
71
+ on(selector, 'click', handler);
72
+
73
+ export const onChange = (selector: string, handler: (event: Event) => void) =>
74
+ on(selector, 'change', handler);
75
+
76
+ export const onInput = (selector: string, handler: (event: Event) => void) =>
77
+ on(selector, 'input', handler);
78
+
79
+ export const onSubmit = (selector: string, handler: (event: Event) => void) =>
80
+ on(selector, 'submit', handler);
81
+
82
+ /**
83
+ * Delegate event to parent container (for dynamic elements)
84
+ *
85
+ * Usage:
86
+ * delegate('#container', 'click', '.dynamic-btn', handleClick);
87
+ */
88
+ export function delegate<T = Event>(
89
+ containerSelector: string,
90
+ eventName: string,
91
+ targetSelector: string,
92
+ handler: (event: T, target: Element) => void
93
+ ): () => void {
94
+ const container = document.querySelector(containerSelector);
95
+ if (!container) return () => {};
96
+
97
+ const delegateHandler = (event: Event) => {
98
+ const target = (event.target as Element).closest(targetSelector);
99
+ if (target && container.contains(target)) {
100
+ handler(event as T, target);
101
+ }
102
+ };
103
+
104
+ container.addEventListener(eventName, delegateHandler);
105
+
106
+ return () => {
107
+ container.removeEventListener(eventName, delegateHandler);
108
+ };
109
+ }
110
+
111
+ /**
112
+ * Event scope tracker - automatically collects all listeners for cleanup
113
+ * Perfect for controllers that need comprehensive cleanup
114
+ *
115
+ * Usage:
116
+ * export function myController() {
117
+ * const events = trackEvents();
118
+ *
119
+ * events.on('#btn1', 'click', handler1);
120
+ * events.on('#btn2', 'click', handler2);
121
+ * events.onClick('#btn3', handler3);
122
+ *
123
+ * return events.cleanup;
124
+ * }
125
+ */
126
+ export function trackEvents() {
127
+ const cleanupFunctions: Array<() => void> = [];
128
+
129
+ return {
130
+ on<T = Event>(selector: string, eventName: string, handler: (event: T) => void): void {
131
+ cleanupFunctions.push(on(selector, eventName, handler));
132
+ },
133
+
134
+ onClick(selector: string, handler: (event: Event) => void): void {
135
+ cleanupFunctions.push(onClick(selector, handler));
136
+ },
137
+
138
+ onChange(selector: string, handler: (event: Event) => void): void {
139
+ cleanupFunctions.push(onChange(selector, handler));
140
+ },
141
+
142
+ onInput(selector: string, handler: (event: Event) => void): void {
143
+ cleanupFunctions.push(onInput(selector, handler));
144
+ },
145
+
146
+ onSubmit(selector: string, handler: (event: Event) => void): void {
147
+ cleanupFunctions.push(onSubmit(selector, handler));
148
+ },
149
+
150
+ delegate<T = Event>(
151
+ containerSelector: string,
152
+ eventName: string,
153
+ targetSelector: string,
154
+ handler: (event: T, target: Element) => void
155
+ ): void {
156
+ cleanupFunctions.push(delegate(containerSelector, eventName, targetSelector, handler));
157
+ },
158
+
159
+ cleanup(): void {
160
+ cleanupFunctions.forEach(cleanup => cleanup());
161
+ cleanupFunctions.length = 0; // Clear array
162
+ }
163
+ };
164
+ }
165
+
166
+ /**
167
+ * Subscription scope tracker - automatically collects state watcher unsubscribes for cleanup
168
+ * The reactive analog to trackEvents() — use this alongside it in controllers and components
169
+ *
170
+ * Usage:
171
+ * export async function myController() {
172
+ * const subs = trackSubscriptions();
173
+ * const events = trackEvents();
174
+ *
175
+ * subs.watch(store.user.watch(user => renderUser(user)));
176
+ * subs.watch(store.isLoading.watch(loading => toggleSpinner(loading)));
177
+ * events.onClick('#btn', handleClick);
178
+ *
179
+ * return () => {
180
+ * subs.cleanup();
181
+ * events.cleanup();
182
+ * };
183
+ * }
184
+ */
185
+ export function trackSubscriptions() {
186
+ const unsubscribers: Array<() => void> = [];
187
+
188
+ return {
189
+ /**
190
+ * Register a state watcher unsubscribe function
191
+ * Pass the return value of any .watch() call directly
192
+ * @example subs.watch(myState.watch(val => doSomething(val)));
193
+ */
194
+ watch(unsubscribe: () => void): void {
195
+ unsubscribers.push(unsubscribe);
196
+ },
197
+
198
+ cleanup(): void {
199
+ unsubscribers.forEach(fn => fn());
200
+ unsubscribers.length = 0;
201
+ }
202
+ };
203
+ }