create-nativecore 0.1.1 → 0.2.1
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.
- package/README.md +6 -14
- package/bin/index.mjs +403 -431
- package/package.json +3 -2
- package/template/.env.example +28 -0
- package/template/.htmlhintrc +14 -0
- package/template/api/data/dashboard.json +11 -0
- package/template/api/data/users.json +18 -0
- package/template/api/mockApi.js +161 -0
- package/template/assets/icon.svg +13 -0
- package/template/assets/logo.svg +25 -0
- package/template/eslint.config.js +94 -0
- package/template/index.html +137 -0
- package/template/manifest.json +19 -0
- package/template/public/.well-known/security.txt +9 -0
- package/template/public/_headers +24 -0
- package/template/public/_redirects +14 -0
- package/template/public/assets/icon.svg +13 -0
- package/template/public/assets/logo.svg +25 -0
- package/template/public/manifest.json +19 -0
- package/template/public/robots.txt +13 -0
- package/template/public/sitemap.xml +27 -0
- package/template/scripts/build-for-bots.mjs +121 -0
- package/template/scripts/convert-to-ts.mjs +106 -0
- package/template/scripts/fix-encoding.mjs +38 -0
- package/template/scripts/fix-svg-paths.mjs +32 -0
- package/template/scripts/generate-cf-router.mjs +52 -0
- package/template/scripts/inject-dev-tools.mjs +41 -0
- package/template/scripts/inject-version.mjs +65 -0
- package/template/scripts/make-component.mjs +445 -0
- package/template/scripts/make-component.mjs.backup +432 -0
- package/template/scripts/make-controller.mjs +119 -0
- package/template/scripts/make-core-component.mjs +303 -0
- package/template/scripts/make-view.mjs +346 -0
- package/template/scripts/minify.mjs +71 -0
- package/template/scripts/prepare-static-assets.mjs +141 -0
- package/template/scripts/prompt-bot-build.mjs +223 -0
- package/template/scripts/remove-component.mjs +170 -0
- package/template/scripts/remove-core-component.mjs +156 -0
- package/template/scripts/remove-dev.mjs +13 -0
- package/template/scripts/remove-view.mjs +200 -0
- package/template/scripts/strip-dev-blocks.mjs +30 -0
- package/template/scripts/watch-compile.mjs +69 -0
- package/template/server.js +1066 -0
- package/template/src/app.ts +115 -0
- package/template/src/components/appRegistry.ts +8 -0
- package/template/src/components/core/app-footer.ts +27 -0
- package/template/src/components/core/app-header.ts +175 -0
- package/template/src/components/core/app-sidebar.ts +238 -0
- package/template/src/components/core/loading-spinner.ts +25 -0
- package/template/src/components/core/nc-a.ts +313 -0
- package/template/src/components/core/nc-accordion.ts +186 -0
- package/template/src/components/core/nc-alert.ts +153 -0
- package/template/src/components/core/nc-animation.ts +1150 -0
- package/template/src/components/core/nc-autocomplete.ts +271 -0
- package/template/src/components/core/nc-avatar-group.ts +113 -0
- package/template/src/components/core/nc-avatar.ts +148 -0
- package/template/src/components/core/nc-badge.ts +86 -0
- package/template/src/components/core/nc-bottom-nav.ts +214 -0
- package/template/src/components/core/nc-breadcrumb.ts +96 -0
- package/template/src/components/core/nc-button.ts +307 -0
- package/template/src/components/core/nc-card.ts +160 -0
- package/template/src/components/core/nc-checkbox.ts +282 -0
- package/template/src/components/core/nc-chip.ts +115 -0
- package/template/src/components/core/nc-code.ts +314 -0
- package/template/src/components/core/nc-collapsible.ts +154 -0
- package/template/src/components/core/nc-color-picker.ts +268 -0
- package/template/src/components/core/nc-copy-button.ts +119 -0
- package/template/src/components/core/nc-date-picker.ts +443 -0
- package/template/src/components/core/nc-div.ts +280 -0
- package/template/src/components/core/nc-divider.ts +81 -0
- package/template/src/components/core/nc-drawer.ts +230 -0
- package/template/src/components/core/nc-dropdown.ts +178 -0
- package/template/src/components/core/nc-empty-state.ts +134 -0
- package/template/src/components/core/nc-file-upload.ts +354 -0
- package/template/src/components/core/nc-form.ts +312 -0
- package/template/src/components/core/nc-image.ts +184 -0
- package/template/src/components/core/nc-input.ts +383 -0
- package/template/src/components/core/nc-kbd.ts +48 -0
- package/template/src/components/core/nc-menu-item.ts +193 -0
- package/template/src/components/core/nc-menu.ts +376 -0
- package/template/src/components/core/nc-modal.ts +238 -0
- package/template/src/components/core/nc-nav-item.ts +151 -0
- package/template/src/components/core/nc-number-input.ts +350 -0
- package/template/src/components/core/nc-otp-input.ts +235 -0
- package/template/src/components/core/nc-pagination.ts +178 -0
- package/template/src/components/core/nc-popover.ts +260 -0
- package/template/src/components/core/nc-progress-circular.ts +119 -0
- package/template/src/components/core/nc-progress.ts +134 -0
- package/template/src/components/core/nc-radio.ts +235 -0
- package/template/src/components/core/nc-rating.ts +266 -0
- package/template/src/components/core/nc-rich-text.ts +283 -0
- package/template/src/components/core/nc-scroll-top.ts +116 -0
- package/template/src/components/core/nc-select.ts +452 -0
- package/template/src/components/core/nc-skeleton.ts +107 -0
- package/template/src/components/core/nc-slider.ts +285 -0
- package/template/src/components/core/nc-snackbar.ts +230 -0
- package/template/src/components/core/nc-splash.ts +343 -0
- package/template/src/components/core/nc-stepper.ts +247 -0
- package/template/src/components/core/nc-switch.ts +281 -0
- package/template/src/components/core/nc-tab-item.ts +138 -0
- package/template/src/components/core/nc-table.ts +279 -0
- package/template/src/components/core/nc-tabs.ts +554 -0
- package/template/src/components/core/nc-tag-input.ts +279 -0
- package/template/src/components/core/nc-textarea.ts +216 -0
- package/template/src/components/core/nc-time-picker.ts +438 -0
- package/template/src/components/core/nc-timeline.ts +186 -0
- package/template/src/components/core/nc-tooltip.ts +143 -0
- package/template/src/components/frameworkRegistry.ts +68 -0
- package/template/src/components/preloadRegistry.ts +28 -0
- package/template/src/components/registry.ts +8 -0
- package/template/src/components/ui/dashboard-signal-lab.ts +284 -0
- package/template/src/constants/apiEndpoints.ts +27 -0
- package/template/src/constants/errorMessages.ts +23 -0
- package/template/src/constants/index.ts +8 -0
- package/template/src/constants/routePaths.ts +15 -0
- package/template/src/constants/storageKeys.ts +18 -0
- package/template/src/controllers/dashboard.controller.ts +200 -0
- package/template/src/controllers/home.controller.ts +21 -0
- package/template/src/controllers/index.ts +11 -0
- package/template/src/controllers/login.controller.ts +131 -0
- package/template/src/core/component.ts +354 -0
- package/template/src/core/errorHandler.ts +85 -0
- package/template/src/core/gpu-animation.ts +604 -0
- package/template/src/core/http.ts +173 -0
- package/template/src/core/lazyComponents.ts +90 -0
- package/template/src/core/router.ts +653 -0
- package/template/src/core/signals.ts +146 -0
- package/template/src/core/state.ts +248 -0
- package/template/src/dev/component-editor.ts +1363 -0
- package/template/src/dev/component-overlay.ts +278 -0
- package/template/src/dev/context-menu.ts +223 -0
- package/template/src/dev/denc-tools.ts +250 -0
- package/template/src/dev/hmr.ts +189 -0
- package/template/src/dev/nfbs.code-workspace +27 -0
- package/template/src/dev/outline-panel.ts +1247 -0
- package/template/src/middleware/auth.middleware.ts +23 -0
- package/template/src/routes/routes.ts +38 -0
- package/template/src/services/api.service.ts +394 -0
- package/template/src/services/auth.service.ts +176 -0
- package/template/src/services/index.ts +8 -0
- package/template/src/services/logger.service.ts +74 -0
- package/template/src/services/storage.service.ts +88 -0
- package/template/src/stores/appStore.ts +57 -0
- package/template/src/stores/uiStore.ts +36 -0
- package/template/src/styles/core-variables.css +219 -0
- package/template/src/styles/core.css +710 -0
- package/template/src/styles/main.css +3164 -0
- package/template/src/styles/variables.css +152 -0
- package/template/src/types/global.d.ts +47 -0
- package/template/src/utils/cacheBuster.ts +20 -0
- package/template/src/utils/dom.ts +149 -0
- package/template/src/utils/events.ts +203 -0
- package/template/src/utils/form.ts +176 -0
- package/template/src/utils/formatters.ts +169 -0
- package/template/src/utils/helpers.ts +195 -0
- package/template/src/utils/markdown.ts +307 -0
- package/template/src/utils/sidebar.ts +96 -0
- package/template/src/utils/smoothScroll.ts +85 -0
- package/template/src/utils/templates.ts +23 -0
- package/template/src/utils/validation.ts +73 -0
- package/template/src/views/protected/dashboard.html +293 -0
- package/template/src/views/public/home.html +150 -0
- package/template/src/views/public/login.html +102 -0
- package/template/tests/unit/component.test.ts +87 -0
- package/template/tests/unit/computed.test.ts +79 -0
- package/template/tests/unit/form.test.ts +68 -0
- package/template/tests/unit/formatters.test.ts +49 -0
- package/template/tests/unit/lazy-components.test.ts +59 -0
- package/template/tests/unit/markdown.test.ts +62 -0
- package/template/tests/unit/router.test.ts +112 -0
- package/template/tests/unit/signals.test.ts +54 -0
- package/template/tests/unit/validation.test.ts +50 -0
- package/template/tsconfig.build.json +21 -0
- package/template/tsconfig.json +51 -0
- 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
|
+
}
|