create-nativecore 0.1.0 → 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.
- package/README.md +10 -18
- package/bin/index.mjs +407 -489
- package/package.json +4 -3
- 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 +642 -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,432 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Component Generator Script
|
|
5
|
+
* Like Laravel Artisan for vanilla JS components
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* node scripts/make-component.mjs counter
|
|
9
|
+
* npm run make:component counter
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import fs from 'fs';
|
|
13
|
+
import path from 'path';
|
|
14
|
+
import { fileURLToPath } from 'url';
|
|
15
|
+
import readline from 'readline';
|
|
16
|
+
|
|
17
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
18
|
+
const __dirname = path.dirname(__filename);
|
|
19
|
+
|
|
20
|
+
// Get component name from command line
|
|
21
|
+
const componentName = process.argv[2];
|
|
22
|
+
|
|
23
|
+
if (!componentName) {
|
|
24
|
+
console.error('❌ Error: Component name is required');
|
|
25
|
+
console.log('\nUsage:');
|
|
26
|
+
console.log(' npm run make:component <name>');
|
|
27
|
+
console.log('\nExample:');
|
|
28
|
+
console.log(' npm run make:component my-card');
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Validate component name (kebab-case with required hyphen)
|
|
33
|
+
if (!/^[a-z][a-z0-9]*(-[a-z0-9]+)+$/.test(componentName)) {
|
|
34
|
+
console.error('❌ Error: Component name must be in kebab-case with at least one hyphen');
|
|
35
|
+
console.error(' Custom elements require a hyphen to avoid conflicts with native HTML elements');
|
|
36
|
+
console.error('\n✅ Valid examples:');
|
|
37
|
+
console.error(' - my-card');
|
|
38
|
+
console.error(' - user-profile');
|
|
39
|
+
console.error(' - sample-component');
|
|
40
|
+
console.error('\n❌ Invalid examples:');
|
|
41
|
+
console.error(' - card (no hyphen)');
|
|
42
|
+
console.error(' - MyCard (not kebab-case)');
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Generate class name (PascalCase)
|
|
47
|
+
const className = componentName
|
|
48
|
+
.split('-')
|
|
49
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
50
|
+
.join('');
|
|
51
|
+
|
|
52
|
+
// Component file path (in components/ui folder by default)
|
|
53
|
+
const componentsDir = path.resolve(__dirname, '..', 'src', 'components');
|
|
54
|
+
const uiDir = path.join(componentsDir, 'ui');
|
|
55
|
+
const componentFile = path.join(uiDir, `${componentName}.ts`);
|
|
56
|
+
const registryFile = path.join(componentsDir, 'registry.ts');
|
|
57
|
+
|
|
58
|
+
// Ensure ui directory exists
|
|
59
|
+
if (!fs.existsSync(uiDir)) {
|
|
60
|
+
fs.mkdirSync(uiDir, { recursive: true });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Check if component already exists
|
|
64
|
+
if (fs.existsSync(componentFile)) {
|
|
65
|
+
console.error(`❌ Error: Component "${componentName}.ts" already exists`);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Template files
|
|
70
|
+
const jsTemplate = `/**
|
|
71
|
+
* ${className} Component
|
|
72
|
+
* Generated on ${new Date().toLocaleDateString()}
|
|
73
|
+
*
|
|
74
|
+
* REGISTRATION:
|
|
75
|
+
* This component is automatically registered in src/components/registry.ts
|
|
76
|
+
* It can be used anywhere after the component registry loads:
|
|
77
|
+
*
|
|
78
|
+
* Usage in HTML/Templates:
|
|
79
|
+
* <${componentName}></${componentName}>
|
|
80
|
+
*
|
|
81
|
+
* Usage in other components:
|
|
82
|
+
* <${componentName}></${componentName}>
|
|
83
|
+
*
|
|
84
|
+
* Usage in controllers:
|
|
85
|
+
* const el = document.createElement('${componentName}');
|
|
86
|
+
* document.body.appendChild(el);
|
|
87
|
+
*
|
|
88
|
+
* PERFORMANCE TIPS:
|
|
89
|
+
* - By default, this component is lazy-loaded (loads on first use)
|
|
90
|
+
* - For critical layout components (header, sidebar, footer, layout):
|
|
91
|
+
* Preload by adding to src/components/preloadRegistry.ts:
|
|
92
|
+
* import './ui/${componentName}.js';
|
|
93
|
+
* - For small, reusable components (buttons, cards, modals):
|
|
94
|
+
* Keep lazy loading (better initial load time)
|
|
95
|
+
* - The registry prevents duplicate loading automatically
|
|
96
|
+
*/
|
|
97
|
+
import { Component, defineComponent } from '../../core/component.js';
|
|
98
|
+
import { useState, computed, type State, type ComputedState } from '../../core/state.js';
|
|
99
|
+
import { store } from '../../stores/appStore.js';
|
|
100
|
+
import { html } from '../../utils/templates.js';
|
|
101
|
+
|
|
102
|
+
export class ${className} extends Component {
|
|
103
|
+
// Enable Shadow DOM for CSS encapsulation and slot support
|
|
104
|
+
// Uncomment to use scoped styles and <slot> elements
|
|
105
|
+
// static useShadowDOM = true;
|
|
106
|
+
|
|
107
|
+
// ========== Local State ==========
|
|
108
|
+
// count: State<number>;
|
|
109
|
+
// name: State<string>;
|
|
110
|
+
|
|
111
|
+
// ========== Computed Values ==========
|
|
112
|
+
// doubleCount: ComputedState<number>;
|
|
113
|
+
// greeting: ComputedState<string>;
|
|
114
|
+
|
|
115
|
+
// ========== Global Store ==========
|
|
116
|
+
// sharedData: State<number>;
|
|
117
|
+
|
|
118
|
+
constructor() {
|
|
119
|
+
super();
|
|
120
|
+
|
|
121
|
+
// ========== Local State ==========
|
|
122
|
+
// Example reactive state - remove if not needed
|
|
123
|
+
// this.count = useState(0);
|
|
124
|
+
// this.name = useState('');
|
|
125
|
+
|
|
126
|
+
// ========== Computed Values ==========
|
|
127
|
+
// Automatically derived from other state
|
|
128
|
+
// this.doubleCount = computed(() => this.count.value * 2);
|
|
129
|
+
// this.greeting = computed(() => \`Hello, \${this.name.value}!\`);
|
|
130
|
+
|
|
131
|
+
// ========== Global Store ==========
|
|
132
|
+
// Use global store if needed
|
|
133
|
+
// this.sharedData = store.count;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
template() {
|
|
137
|
+
// TIP: Define CSS variant classes (e.g., .primary, .secondary) below
|
|
138
|
+
// The dev tools will automatically detect them and show in a dropdown
|
|
139
|
+
// This lets you quickly change variants without editing code
|
|
140
|
+
|
|
141
|
+
return html\`
|
|
142
|
+
<style>
|
|
143
|
+
/* :host targets the custom element itself (<${componentName}>) */
|
|
144
|
+
/* Use it to set defaults like display mode, sizing, etc. */
|
|
145
|
+
/* Note: External CSS can override :host styles */
|
|
146
|
+
/* :host {
|
|
147
|
+
display: block;
|
|
148
|
+
width: 100%;
|
|
149
|
+
} */
|
|
150
|
+
|
|
151
|
+
/* :host with attributes - conditional styling */
|
|
152
|
+
/* :host([disabled]) {
|
|
153
|
+
opacity: 0.5;
|
|
154
|
+
pointer-events: none;
|
|
155
|
+
} */
|
|
156
|
+
|
|
157
|
+
/* Styles below only affect content inside shadow DOM */
|
|
158
|
+
/* External CSS cannot reach these (fully encapsulated) */
|
|
159
|
+
.${componentName} {
|
|
160
|
+
padding: var(--spacing-lg);
|
|
161
|
+
background: white;
|
|
162
|
+
border-radius: var(--radius-lg);
|
|
163
|
+
box-shadow: var(--shadow-md);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.${componentName} h3 {
|
|
167
|
+
margin-top: 0;
|
|
168
|
+
color: var(--primary);
|
|
169
|
+
}
|
|
170
|
+
</style>
|
|
171
|
+
|
|
172
|
+
<div class="${componentName}">
|
|
173
|
+
<h3>${className}</h3>
|
|
174
|
+
<p>Component content goes here</p>
|
|
175
|
+
|
|
176
|
+
<!-- State watcher example - uncomment to use -->
|
|
177
|
+
<!-- <p id="example-element"></p> -->
|
|
178
|
+
|
|
179
|
+
<!-- Uncomment when Shadow DOM is enabled -->
|
|
180
|
+
<!-- <slot></slot> -->
|
|
181
|
+
</div>
|
|
182
|
+
\`;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
onMount() {
|
|
186
|
+
// ========== Event Delegation Pattern ==========
|
|
187
|
+
// Use event delegation for reliable event handling
|
|
188
|
+
// This works even if elements are re-rendered
|
|
189
|
+
|
|
190
|
+
// Use shadowRoot if Shadow DOM is enabled, otherwise use this
|
|
191
|
+
const root = this.shadowRoot || this;
|
|
192
|
+
|
|
193
|
+
// ========== Event Delegation Example ==========
|
|
194
|
+
// Uncomment to use event delegation pattern
|
|
195
|
+
// root.addEventListener('click', (e) => {
|
|
196
|
+
// const target = e.target as HTMLElement;
|
|
197
|
+
//
|
|
198
|
+
// // Handle button clicks
|
|
199
|
+
// if (target.matches('.example-button')) {
|
|
200
|
+
// this.handleButtonClick(e);
|
|
201
|
+
// }
|
|
202
|
+
//
|
|
203
|
+
// // Handle link clicks
|
|
204
|
+
// if (target.matches('.example-link')) {
|
|
205
|
+
// e.preventDefault();
|
|
206
|
+
// this.handleLinkClick(e);
|
|
207
|
+
// }
|
|
208
|
+
// });
|
|
209
|
+
|
|
210
|
+
// ========== State Watchers ==========
|
|
211
|
+
// Watch state changes (like React useEffect)
|
|
212
|
+
// this.count.watch(value => {
|
|
213
|
+
// this.$('#example-element').textContent = value;
|
|
214
|
+
// });
|
|
215
|
+
|
|
216
|
+
// ========== Other Event Listeners ==========
|
|
217
|
+
// For events that need to be on specific elements (focus, blur, etc.)
|
|
218
|
+
// const input = this.$('#example-input');
|
|
219
|
+
// if (input) {
|
|
220
|
+
// input.addEventListener('focus', () => console.warn('focused'));
|
|
221
|
+
// }
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// ========== Event Handlers ==========
|
|
225
|
+
// Uncomment and customize as needed
|
|
226
|
+
// handleButtonClick(e) {
|
|
227
|
+
// console.warn('Button clicked', e.target);
|
|
228
|
+
//
|
|
229
|
+
// // Emit custom event to parent components
|
|
230
|
+
// // this.emitEvent('button-click', { data: 'example' });
|
|
231
|
+
//
|
|
232
|
+
// // Emit with custom options
|
|
233
|
+
// // this.emitEvent('custom-event', { value: 123 }, {
|
|
234
|
+
// // bubbles: true,
|
|
235
|
+
// // composed: true,
|
|
236
|
+
// // cancelable: true
|
|
237
|
+
// // });
|
|
238
|
+
//
|
|
239
|
+
// // Parent components OR page controllers can listen like this:
|
|
240
|
+
// //
|
|
241
|
+
// // In HTML:
|
|
242
|
+
// // <${componentName}></${componentName}>
|
|
243
|
+
// //
|
|
244
|
+
// // In page controller (e.g., home.controller.ts):
|
|
245
|
+
// // import { dom } from '@utils/dom.js';
|
|
246
|
+
// //
|
|
247
|
+
// // export function homeController() {
|
|
248
|
+
// // // Option 1: Using dom.listen() shorthand (recommended)
|
|
249
|
+
// // dom.listen('${componentName}', 'button-click', (e) => {
|
|
250
|
+
// // console.log('Received event:', e.detail); // { data: 'example' }
|
|
251
|
+
// // });
|
|
252
|
+
// //
|
|
253
|
+
// // // Option 2: Traditional way
|
|
254
|
+
// // document.querySelector('${componentName}').addEventListener('button-click', (e) => {
|
|
255
|
+
// // console.log('Received event:', e.detail);
|
|
256
|
+
// // });
|
|
257
|
+
// // }
|
|
258
|
+
// //
|
|
259
|
+
// // Or in another parent component's onMount():
|
|
260
|
+
// // this.shadowRoot.querySelector('${componentName}').addEventListener('button-click', (e) => {
|
|
261
|
+
// // console.log('Child emitted:', e.detail);
|
|
262
|
+
// // });
|
|
263
|
+
// }
|
|
264
|
+
//
|
|
265
|
+
// handleLinkClick(e) {
|
|
266
|
+
// console.warn('Link clicked', e.target);
|
|
267
|
+
//
|
|
268
|
+
// // Example: Emit event with navigation data
|
|
269
|
+
// // this.emitEvent('navigate', { href: e.target.href });
|
|
270
|
+
//
|
|
271
|
+
// // Parent can listen and handle navigation:
|
|
272
|
+
// // import { dom } from '@utils/dom.js';
|
|
273
|
+
// // dom.listen('${componentName}', 'navigate', (e) => {
|
|
274
|
+
// // window.router.navigate(e.detail.href);
|
|
275
|
+
// // });
|
|
276
|
+
// }
|
|
277
|
+
|
|
278
|
+
// Emit custom event to parent components
|
|
279
|
+
// this.emitEvent('button-click', { data: 'example' });
|
|
280
|
+
|
|
281
|
+
// Emit with custom options
|
|
282
|
+
// this.emitEvent('custom-event', { value: 123 }, {
|
|
283
|
+
// bubbles: true,
|
|
284
|
+
// composed: true,
|
|
285
|
+
// cancelable: true
|
|
286
|
+
// });
|
|
287
|
+
|
|
288
|
+
// Parent components OR page controllers can listen like this:
|
|
289
|
+
//
|
|
290
|
+
// In HTML:
|
|
291
|
+
// <${componentName}></${componentName}>
|
|
292
|
+
//
|
|
293
|
+
// In page controller (e.g., home.controller.js):
|
|
294
|
+
// import { dom } from '../utils/dom.js';
|
|
295
|
+
//
|
|
296
|
+
// export function homeController() {
|
|
297
|
+
|
|
298
|
+
onUnmount() {
|
|
299
|
+
// Cleanup if needed
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
defineComponent('${componentName}', ${className});
|
|
304
|
+
`;
|
|
305
|
+
|
|
306
|
+
const readmeTemplate = `# ${className}
|
|
307
|
+
|
|
308
|
+
Custom element: \`<${componentName}>\`
|
|
309
|
+
|
|
310
|
+
## Usage
|
|
311
|
+
|
|
312
|
+
\`\`\`html
|
|
313
|
+
<${componentName}></${componentName}>
|
|
314
|
+
\`\`\`
|
|
315
|
+
|
|
316
|
+
## Properties
|
|
317
|
+
|
|
318
|
+
| Property | Type | Default | Description |
|
|
319
|
+
|----------|------|---------|-------------|
|
|
320
|
+
| - | - | - | - |
|
|
321
|
+
|
|
322
|
+
## Events
|
|
323
|
+
|
|
324
|
+
| Event | Description |
|
|
325
|
+
|-------|-------------|
|
|
326
|
+
| - | - |
|
|
327
|
+
|
|
328
|
+
## CSS Variables
|
|
329
|
+
|
|
330
|
+
| Variable | Description |
|
|
331
|
+
|----------|-------------|
|
|
332
|
+
| - | - |
|
|
333
|
+
|
|
334
|
+
## Example
|
|
335
|
+
|
|
336
|
+
\`\`\`html
|
|
337
|
+
<${componentName}></${componentName}>
|
|
338
|
+
\`\`\`
|
|
339
|
+
`;
|
|
340
|
+
|
|
341
|
+
// Write component file
|
|
342
|
+
fs.writeFileSync(componentFile, jsTemplate.trim());
|
|
343
|
+
|
|
344
|
+
// Prompt for prefetch
|
|
345
|
+
const rl = readline.createInterface({
|
|
346
|
+
input: process.stdin,
|
|
347
|
+
output: process.stdout
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
console.log('\n💡 Performance optimization:');
|
|
351
|
+
console.log(' All components are lazy-loaded by default (loads on first use).');
|
|
352
|
+
console.log(' For critical layout components (header, sidebar, footer), prefetching improves performance.\n');
|
|
353
|
+
|
|
354
|
+
rl.question('Would you like to prefetch this component? (y/N): ', (answer) => {
|
|
355
|
+
const shouldPreload = answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes';
|
|
356
|
+
|
|
357
|
+
if (shouldPreload) {
|
|
358
|
+
// Add import to preloadRegistry.ts
|
|
359
|
+
const preloadFile = path.resolve(__dirname, '..', 'src', 'components', 'preloadRegistry.ts');
|
|
360
|
+
|
|
361
|
+
if (fs.existsSync(preloadFile)) {
|
|
362
|
+
let preloadContent = fs.readFileSync(preloadFile, 'utf-8');
|
|
363
|
+
const importStatement = `import './ui/${componentName}.js';`;
|
|
364
|
+
|
|
365
|
+
// Check if not already imported
|
|
366
|
+
if (!preloadContent.includes(importStatement)) {
|
|
367
|
+
// Find the last import line and add after it
|
|
368
|
+
const lines = preloadContent.split('\n');
|
|
369
|
+
const lastImportIndex = lines.findLastIndex(line =>
|
|
370
|
+
line.trim().startsWith("import './") && line.endsWith(".js';")
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
if (lastImportIndex !== -1) {
|
|
374
|
+
// Add after last import
|
|
375
|
+
lines.splice(lastImportIndex + 1, 0, importStatement);
|
|
376
|
+
} else {
|
|
377
|
+
// Add at the end of the file
|
|
378
|
+
lines.push(importStatement);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
preloadContent = lines.join('\n');
|
|
382
|
+
fs.writeFileSync(preloadFile, preloadContent);
|
|
383
|
+
console.log('✅ Component will be preloaded (added to preloadRegistry.ts)\n');
|
|
384
|
+
}
|
|
385
|
+
} else {
|
|
386
|
+
console.warn('⚠️ preloadRegistry.ts not found - component will still be lazy-loaded\n');
|
|
387
|
+
}
|
|
388
|
+
} else {
|
|
389
|
+
console.log('✅ Component will be lazy-loaded (default behavior)\n');
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Add to registry.ts for lazy loading registration
|
|
393
|
+
if (fs.existsSync(registryFile)) {
|
|
394
|
+
let registryContent = fs.readFileSync(registryFile, 'utf-8');
|
|
395
|
+
const registrationStatement = `componentRegistry.register('${componentName}', './ui/${componentName}.js');`;
|
|
396
|
+
|
|
397
|
+
// Check if already registered
|
|
398
|
+
if (!registryContent.includes(registrationStatement)) {
|
|
399
|
+
// Find the last registration line and add after it
|
|
400
|
+
const lines = registryContent.split('\n');
|
|
401
|
+
const lastRegisterIndex = lines.findLastIndex(line => line.includes('componentRegistry.register'));
|
|
402
|
+
|
|
403
|
+
if (lastRegisterIndex !== -1) {
|
|
404
|
+
// Add after the last registration
|
|
405
|
+
lines.splice(lastRegisterIndex + 1, 0, registrationStatement);
|
|
406
|
+
registryContent = lines.join('\n');
|
|
407
|
+
} else {
|
|
408
|
+
// Add at the end if no registrations found
|
|
409
|
+
registryContent = registryContent.trimEnd() + '\n' + registrationStatement + '\n';
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
fs.writeFileSync(registryFile, registryContent);
|
|
413
|
+
console.log('✅ Component registered in component registry\n');
|
|
414
|
+
}
|
|
415
|
+
} else {
|
|
416
|
+
console.log('⚠️ Note: src/components/registry.ts not found. Manual registration required.\n');
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Success message
|
|
420
|
+
console.log(`📁 Location: src/components/ui/${componentName}.ts`);
|
|
421
|
+
console.log(`🔗 Registered in: src/components/registry.ts`);
|
|
422
|
+
if (shouldPreload) {
|
|
423
|
+
console.log(`⚡ Preloaded in: src/components/preloadRegistry.ts`);
|
|
424
|
+
}
|
|
425
|
+
console.log('\n📝 Next steps:');
|
|
426
|
+
console.log(` 1. Edit src/components/ui/${componentName}.ts`);
|
|
427
|
+
console.log(` 2. Use in your HTML: <${componentName}></${componentName}>`);
|
|
428
|
+
console.log('\n🎨 Component class:', className);
|
|
429
|
+
console.log('🏷️ Custom element:', `<${componentName}>`);
|
|
430
|
+
|
|
431
|
+
rl.close();
|
|
432
|
+
});
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import readline from 'readline';
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = path.dirname(__filename);
|
|
10
|
+
|
|
11
|
+
const rl = readline.createInterface({
|
|
12
|
+
input: process.stdin,
|
|
13
|
+
output: process.stdout
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const question = (query) => new Promise((resolve) => rl.question(query, resolve));
|
|
17
|
+
|
|
18
|
+
// Get controller name from command line or prompt
|
|
19
|
+
const controllerArg = process.argv[2];
|
|
20
|
+
|
|
21
|
+
async function main() {
|
|
22
|
+
console.log('NativeCore Controller Generator\n');
|
|
23
|
+
|
|
24
|
+
// Get controller name
|
|
25
|
+
let controllerName = controllerArg;
|
|
26
|
+
if (!controllerName) {
|
|
27
|
+
controllerName = await question('Controller name (e.g., "user-profile"): ');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!controllerName) {
|
|
31
|
+
console.error('Error: Controller name is required');
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Convert to kebab-case and camelCase
|
|
36
|
+
const kebabName = controllerName.toLowerCase().replace(/\s+/g, '-');
|
|
37
|
+
const camelName = kebabName.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
|
|
38
|
+
const titleName = kebabName.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
|
|
39
|
+
|
|
40
|
+
// Paths
|
|
41
|
+
const controllersDir = path.resolve(__dirname, '..', 'src', 'controllers');
|
|
42
|
+
const controllerFile = path.join(controllersDir, `${kebabName}.controller.ts`);
|
|
43
|
+
const indexFile = path.join(controllersDir, 'index.ts');
|
|
44
|
+
|
|
45
|
+
// Check if controller already exists
|
|
46
|
+
if (fs.existsSync(controllerFile)) {
|
|
47
|
+
console.error(`Error: Controller "${kebabName}.controller.ts" already exists`);
|
|
48
|
+
rl.close();
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Generate standardized controller template
|
|
53
|
+
const controllerTemplate = `/**
|
|
54
|
+
* ${titleName} Controller
|
|
55
|
+
* Handles dynamic behavior for the ${titleName.toLowerCase()} functionality
|
|
56
|
+
*/
|
|
57
|
+
import { trackEvents, trackSubscriptions } from '@utils/events.js';
|
|
58
|
+
|
|
59
|
+
export async function ${camelName}Controller(params: Record<string, string> = {}): Promise<() => void> {
|
|
60
|
+
const events = trackEvents();
|
|
61
|
+
const subs = trackSubscriptions();
|
|
62
|
+
|
|
63
|
+
// DOM references
|
|
64
|
+
// const element = document.getElementById('${kebabName}') as HTMLElement;
|
|
65
|
+
|
|
66
|
+
// Load data, then render and attach events
|
|
67
|
+
// const data = await api.get('/endpoint');
|
|
68
|
+
// element.innerHTML = \`...\`;
|
|
69
|
+
|
|
70
|
+
// Event listeners — all auto-tracked for cleanup
|
|
71
|
+
// events.onClick('#my-button', handleClick);
|
|
72
|
+
// events.delegate('#list', 'click', '.item', handleItemClick);
|
|
73
|
+
|
|
74
|
+
// State watchers — all auto-unsubscribed on cleanup
|
|
75
|
+
// subs.watch(store.isLoading.watch(loading => toggleSpinner(loading)));
|
|
76
|
+
|
|
77
|
+
// Return cleanup function — called automatically on route change
|
|
78
|
+
return () => {
|
|
79
|
+
events.cleanup();
|
|
80
|
+
subs.cleanup();
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
`;
|
|
84
|
+
|
|
85
|
+
// Create controller file
|
|
86
|
+
fs.writeFileSync(controllerFile, controllerTemplate);
|
|
87
|
+
console.log(`Created controller: src/controllers/${kebabName}.controller.ts`);
|
|
88
|
+
|
|
89
|
+
// Update index.ts
|
|
90
|
+
if (fs.existsSync(indexFile)) {
|
|
91
|
+
let indexContent = fs.readFileSync(indexFile, 'utf-8');
|
|
92
|
+
const exportLine = `export { ${camelName}Controller } from './${kebabName}.controller.js';\n`;
|
|
93
|
+
|
|
94
|
+
// Check if already exported
|
|
95
|
+
if (!indexContent.includes(`${camelName}Controller`)) {
|
|
96
|
+
indexContent += exportLine;
|
|
97
|
+
fs.writeFileSync(indexFile, indexContent);
|
|
98
|
+
console.log(`Added export to controllers/index.ts`);
|
|
99
|
+
}
|
|
100
|
+
} else {
|
|
101
|
+
// Create index file if it doesn't exist
|
|
102
|
+
const indexContent = `export { ${camelName}Controller } from './${kebabName}.controller.js';\n`;
|
|
103
|
+
fs.writeFileSync(indexFile, indexContent);
|
|
104
|
+
console.log(`Created controllers/index.ts`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
console.log('\nController created successfully!');
|
|
108
|
+
console.log(`\nNext steps:`);
|
|
109
|
+
console.log(`1. Register in routes: lazyController('${camelName}Controller', '../controllers/${kebabName}.controller.js')`);
|
|
110
|
+
console.log(`2. Add your logic to: src/controllers/${kebabName}.controller.ts`);
|
|
111
|
+
|
|
112
|
+
rl.close();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
main().catch(err => {
|
|
116
|
+
console.error('Error:', err.message);
|
|
117
|
+
rl.close();
|
|
118
|
+
process.exit(1);
|
|
119
|
+
});
|