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.
Files changed (175) hide show
  1. package/README.md +10 -18
  2. package/bin/index.mjs +407 -489
  3. package/package.json +4 -3
  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,176 @@
1
+ import { computed, useState } from '@core/state.js';
2
+ import type { ComputedState, State } from '@core/state.js';
3
+ import { validateForm } from '@utils/validation.js';
4
+
5
+ type Validator = (value: any) => boolean;
6
+ type FieldStates<T extends Record<string, any>> = { [K in keyof T]: State<T[K]> };
7
+ type FieldFlags<T extends Record<string, any>> = { [K in keyof T]: State<boolean> };
8
+ type ValidationRules<T extends Record<string, any>> = Partial<Record<keyof T, Validator[]>>;
9
+
10
+ export interface UseFormOptions<T extends Record<string, any>> {
11
+ initialValues: T;
12
+ rules?: ValidationRules<T>;
13
+ }
14
+
15
+ export interface UseFormResult<T extends Record<string, any>> {
16
+ fields: FieldStates<T>;
17
+ touched: FieldFlags<T>;
18
+ dirty: FieldFlags<T>;
19
+ errors: ComputedState<Record<string, string>>;
20
+ isDirty: ComputedState<boolean>;
21
+ isValid: ComputedState<boolean>;
22
+ getValues(): T;
23
+ reset(values?: Partial<T>): void;
24
+ markAsPristine(): void;
25
+ bindField<K extends keyof T>(fieldName: K, target: string | HTMLElement): () => void;
26
+ submit(handler: (values: T) => void | Promise<void>): (event?: Event) => Promise<boolean>;
27
+ }
28
+
29
+ export function useForm<T extends Record<string, any>>(options: UseFormOptions<T>): UseFormResult<T> {
30
+ const initialSnapshot = { ...options.initialValues };
31
+ const fields = {} as FieldStates<T>;
32
+ const touched = {} as FieldFlags<T>;
33
+ const dirty = {} as FieldFlags<T>;
34
+
35
+ for (const [key, value] of Object.entries(options.initialValues)) {
36
+ const typedKey = key as keyof T;
37
+ fields[typedKey] = useState(value as T[keyof T]);
38
+ touched[typedKey] = useState(false);
39
+ dirty[typedKey] = useState(false);
40
+ }
41
+
42
+ const errors = computed(() => validateForm(getValues(), options.rules ?? {}));
43
+ const isDirty = computed(() => Object.values(dirty).some(flag => flag.value));
44
+ const isValid = computed(() => Object.keys(errors.value).length === 0);
45
+
46
+ return {
47
+ fields,
48
+ touched,
49
+ dirty,
50
+ errors,
51
+ isDirty,
52
+ isValid,
53
+ getValues,
54
+ reset,
55
+ markAsPristine,
56
+ bindField,
57
+ submit
58
+ };
59
+
60
+ function getValues(): T {
61
+ const values = {} as T;
62
+
63
+ for (const key of Object.keys(fields) as Array<keyof T>) {
64
+ values[key] = fields[key].value;
65
+ }
66
+
67
+ return values;
68
+ }
69
+
70
+ function reset(values: Partial<T> = {}): void {
71
+ for (const key of Object.keys(fields) as Array<keyof T>) {
72
+ const nextValue = key in values ? values[key] : initialSnapshot[key];
73
+ fields[key].value = nextValue as T[keyof T];
74
+ touched[key].value = false;
75
+ dirty[key].value = false;
76
+ }
77
+ }
78
+
79
+ function markAsPristine(): void {
80
+ for (const key of Object.keys(fields) as Array<keyof T>) {
81
+ touched[key].value = false;
82
+ dirty[key].value = false;
83
+ }
84
+ }
85
+
86
+ function bindField<K extends keyof T>(fieldName: K, target: string | HTMLElement): () => void {
87
+ const element = resolveFieldElement(target);
88
+ if (!element) return () => {};
89
+
90
+ syncElementFromState(fieldName, element);
91
+
92
+ const inputHandler = () => {
93
+ fields[fieldName].value = readElementValue(element) as T[K];
94
+ touched[fieldName].value = true;
95
+ dirty[fieldName].value = !Object.is(fields[fieldName].value, initialSnapshot[fieldName]);
96
+ };
97
+
98
+ const blurHandler = () => {
99
+ touched[fieldName].value = true;
100
+ };
101
+
102
+ const eventName = getBindingEventName(element);
103
+ element.addEventListener(eventName, inputHandler);
104
+ element.addEventListener('blur', blurHandler);
105
+
106
+ return () => {
107
+ element.removeEventListener(eventName, inputHandler);
108
+ element.removeEventListener('blur', blurHandler);
109
+ };
110
+ }
111
+
112
+ function submit(handler: (values: T) => void | Promise<void>): (event?: Event) => Promise<boolean> {
113
+ return async (event?: Event) => {
114
+ event?.preventDefault();
115
+
116
+ for (const key of Object.keys(touched) as Array<keyof T>) {
117
+ touched[key].value = true;
118
+ }
119
+
120
+ if (!isValid.value) {
121
+ return false;
122
+ }
123
+
124
+ await handler(getValues());
125
+ markAsPristine();
126
+ return true;
127
+ };
128
+ }
129
+
130
+ function syncElementFromState<K extends keyof T>(fieldName: K, element: FormControlElement): void {
131
+ const value = fields[fieldName].value;
132
+
133
+ if (element instanceof HTMLInputElement && (element.type === 'checkbox' || element.type === 'radio')) {
134
+ element.checked = Boolean(value);
135
+ return;
136
+ }
137
+
138
+ element.value = value == null ? '' : String(value);
139
+ }
140
+ }
141
+
142
+ type FormControlElement = HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;
143
+
144
+ function resolveFieldElement(target: string | HTMLElement): FormControlElement | null {
145
+ const element = typeof target === 'string' ? document.querySelector<HTMLElement>(target) : target;
146
+
147
+ if (
148
+ element instanceof HTMLInputElement ||
149
+ element instanceof HTMLTextAreaElement ||
150
+ element instanceof HTMLSelectElement
151
+ ) {
152
+ return element;
153
+ }
154
+
155
+ return null;
156
+ }
157
+
158
+ function getBindingEventName(element: FormControlElement): 'input' | 'change' {
159
+ if (element instanceof HTMLSelectElement) {
160
+ return 'change';
161
+ }
162
+
163
+ if (element instanceof HTMLInputElement && (element.type === 'checkbox' || element.type === 'radio')) {
164
+ return 'change';
165
+ }
166
+
167
+ return 'input';
168
+ }
169
+
170
+ function readElementValue(element: FormControlElement): string | boolean {
171
+ if (element instanceof HTMLInputElement && (element.type === 'checkbox' || element.type === 'radio')) {
172
+ return element.checked;
173
+ }
174
+
175
+ return element.value;
176
+ }
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Formatter Utilities
3
+ * Common formatting functions for display
4
+ */
5
+
6
+ /**
7
+ * Format currency
8
+ * @param {number} amount - Amount to format
9
+ * @param {string} currency - Currency code (default: USD)
10
+ * @returns {string} Formatted currency
11
+ */
12
+ export function formatCurrency(amount, currency = 'USD') {
13
+ return new Intl.NumberFormat('en-US', {
14
+ style: 'currency',
15
+ currency,
16
+ }).format(amount);
17
+ }
18
+
19
+ /**
20
+ * Format date
21
+ * @param {Date|string} date - Date to format
22
+ * @param {string} format - Format style (short, medium, long, full)
23
+ * @returns {string} Formatted date
24
+ */
25
+ export function formatDate(date, format = 'medium') {
26
+ const d = new Date(date);
27
+ const options = {
28
+ short: { month: 'numeric', day: 'numeric', year: '2-digit' },
29
+ medium: { month: 'short', day: 'numeric', year: 'numeric' },
30
+ long: { month: 'long', day: 'numeric', year: 'numeric' },
31
+ full: { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' },
32
+ };
33
+
34
+ return new Intl.DateTimeFormat('en-US', options[format] || options.medium).format(d);
35
+ }
36
+
37
+ /**
38
+ * Format time
39
+ * @param {Date|string} date - Date to format
40
+ * @returns {string} Formatted time
41
+ */
42
+ export function formatTime(date) {
43
+ const d = new Date(date);
44
+ return new Intl.DateTimeFormat('en-US', {
45
+ hour: 'numeric',
46
+ minute: '2-digit',
47
+ hour12: true,
48
+ }).format(d);
49
+ }
50
+
51
+ /**
52
+ * Format date and time
53
+ * @param {Date|string} date - Date to format
54
+ * @returns {string} Formatted date and time
55
+ */
56
+ export function formatDateTime(date) {
57
+ return `${formatDate(date)} at ${formatTime(date)}`;
58
+ }
59
+
60
+ /**
61
+ * Format relative time (e.g., "2 hours ago")
62
+ * @param {Date|string} date - Date to format
63
+ * @returns {string} Relative time string
64
+ */
65
+ export function formatRelativeTime(date: Date | string): string {
66
+ const d = new Date(date);
67
+ const now = new Date();
68
+ const seconds = Math.floor((now.getTime() - d.getTime()) / 1000);
69
+
70
+ const intervals = {
71
+ year: 31536000,
72
+ month: 2592000,
73
+ week: 604800,
74
+ day: 86400,
75
+ hour: 3600,
76
+ minute: 60,
77
+ second: 1,
78
+ };
79
+
80
+ for (const [unit, secondsInUnit] of Object.entries(intervals)) {
81
+ const interval = Math.floor(seconds / secondsInUnit);
82
+ if (interval >= 1) {
83
+ return interval === 1 ? `1 ${unit} ago` : `${interval} ${unit}s ago`;
84
+ }
85
+ }
86
+
87
+ return 'just now';
88
+ }
89
+
90
+ /**
91
+ * Format number with thousands separator
92
+ * @param {number} num - Number to format
93
+ * @returns {string} Formatted number
94
+ */
95
+ export function formatNumber(num) {
96
+ return new Intl.NumberFormat('en-US').format(num);
97
+ }
98
+
99
+ /**
100
+ * Format file size
101
+ * @param {number} bytes - Size in bytes
102
+ * @returns {string} Formatted size
103
+ */
104
+ export function formatFileSize(bytes) {
105
+ if (bytes === 0) return '0 Bytes';
106
+
107
+ const k = 1024;
108
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
109
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
110
+
111
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
112
+ }
113
+
114
+ /**
115
+ * Format percentage
116
+ * @param {number} value - Value to format (0-1 or 0-100)
117
+ * @param {number} decimals - Decimal places
118
+ * @returns {string} Formatted percentage
119
+ */
120
+ export function formatPercentage(value, decimals = 0) {
121
+ const percent = value <= 1 ? value * 100 : value;
122
+ return `${percent.toFixed(decimals)}%`;
123
+ }
124
+
125
+ /**
126
+ * Truncate text
127
+ * @param {string} text - Text to truncate
128
+ * @param {number} maxLength - Maximum length
129
+ * @param {string} suffix - Suffix to add (default: '...')
130
+ * @returns {string} Truncated text
131
+ */
132
+ export function truncate(text, maxLength, suffix = '...') {
133
+ if (!text || text.length <= maxLength) return text;
134
+ return text.substring(0, maxLength - suffix.length) + suffix;
135
+ }
136
+
137
+ /**
138
+ * Capitalize first letter
139
+ * @param {string} str - String to capitalize
140
+ * @returns {string} Capitalized string
141
+ */
142
+ export function capitalize(str) {
143
+ if (!str) return '';
144
+ return str.charAt(0).toUpperCase() + str.slice(1);
145
+ }
146
+
147
+ /**
148
+ * Title case
149
+ * @param {string} str - String to convert
150
+ * @returns {string} Title cased string
151
+ */
152
+ export function titleCase(str) {
153
+ if (!str) return '';
154
+ return str.toLowerCase().split(' ').map(capitalize).join(' ');
155
+ }
156
+
157
+ export default {
158
+ formatCurrency,
159
+ formatDate,
160
+ formatTime,
161
+ formatDateTime,
162
+ formatRelativeTime,
163
+ formatNumber,
164
+ formatFileSize,
165
+ formatPercentage,
166
+ truncate,
167
+ capitalize,
168
+ titleCase,
169
+ };
@@ -0,0 +1,195 @@
1
+ /**
2
+ * Utility Helper Functions
3
+ * Reusable utility functions for common tasks
4
+ */
5
+
6
+ /**
7
+ * Debounce function calls
8
+ * @param {Function} func - Function to debounce
9
+ * @param {number} wait - Wait time in milliseconds
10
+ */
11
+ export function debounce(func, wait = 300) {
12
+ let timeout;
13
+ return function executedFunction(...args) {
14
+ const later = () => {
15
+ clearTimeout(timeout);
16
+ func(...args);
17
+ };
18
+ clearTimeout(timeout);
19
+ timeout = setTimeout(later, wait);
20
+ };
21
+ }
22
+
23
+ /**
24
+ * Throttle function calls
25
+ * @param {Function} func - Function to throttle
26
+ * @param {number} limit - Time limit in milliseconds
27
+ */
28
+ export function throttle(func, limit = 300) {
29
+ let inThrottle;
30
+ return function executedFunction(...args) {
31
+ if (!inThrottle) {
32
+ func(...args);
33
+ inThrottle = true;
34
+ setTimeout(() => inThrottle = false, limit);
35
+ }
36
+ };
37
+ }
38
+
39
+ /**
40
+ * Format date to readable string
41
+ * @param {Date|string} date - Date to format
42
+ * @param {object} options - Intl.DateTimeFormat options
43
+ */
44
+ export function formatDate(date: Date | string, options: Intl.DateTimeFormatOptions = {}): string {
45
+ const defaultOptions: Intl.DateTimeFormatOptions = {
46
+ year: 'numeric' as const,
47
+ month: 'short' as const,
48
+ day: 'numeric' as const,
49
+ };
50
+ return new Intl.DateTimeFormat('en-US', { ...defaultOptions, ...options })
51
+ .format(new Date(date));
52
+ }
53
+
54
+ /**
55
+ * Format number with thousands separator
56
+ * @param {number} num - Number to format
57
+ */
58
+ export function formatNumber(num) {
59
+ return new Intl.NumberFormat('en-US').format(num);
60
+ }
61
+
62
+ /**
63
+ * Format currency
64
+ * @param {number} amount - Amount to format
65
+ * @param {string} currency - Currency code (default: USD)
66
+ */
67
+ export function formatCurrency(amount, currency = 'USD') {
68
+ return new Intl.NumberFormat('en-US', {
69
+ style: 'currency',
70
+ currency,
71
+ }).format(amount);
72
+ }
73
+
74
+ /**
75
+ * Truncate string to max length
76
+ * @param {string} str - String to truncate
77
+ * @param {number} maxLength - Maximum length
78
+ */
79
+ export function truncate(str, maxLength = 50) {
80
+ if (str.length <= maxLength) return str;
81
+ return str.slice(0, maxLength - 3) + '...';
82
+ }
83
+
84
+ /**
85
+ * Generate unique ID
86
+ */
87
+ export function generateId() {
88
+ return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
89
+ }
90
+
91
+ /**
92
+ * Deep clone object
93
+ * @param {object} obj - Object to clone
94
+ */
95
+ export function deepClone(obj) {
96
+ return JSON.parse(JSON.stringify(obj));
97
+ }
98
+
99
+ /**
100
+ * Check if object is empty
101
+ * @param {object} obj - Object to check
102
+ */
103
+ export function isEmpty(obj) {
104
+ return Object.keys(obj).length === 0;
105
+ }
106
+
107
+ /**
108
+ * Sleep/delay function
109
+ * @param {number} ms - Milliseconds to sleep
110
+ */
111
+ export function sleep(ms) {
112
+ return new Promise(resolve => setTimeout(resolve, ms));
113
+ }
114
+
115
+ /**
116
+ * Sanitize HTML string to prevent XSS
117
+ * @param {string} str - String to sanitize
118
+ */
119
+ export function sanitizeHTML(str) {
120
+ const temp = document.createElement('div');
121
+ temp.textContent = str;
122
+ return temp.innerHTML;
123
+ }
124
+
125
+ /**
126
+ * Parse query string to object
127
+ * @param {string} queryString - Query string (with or without ?)
128
+ */
129
+ export function parseQueryString(queryString) {
130
+ const params = new URLSearchParams(queryString);
131
+ const result = {};
132
+ for (const [key, value] of params) {
133
+ result[key] = value;
134
+ }
135
+ return result;
136
+ }
137
+
138
+ /**
139
+ * Build query string from object
140
+ * @param {object} params - Parameters object
141
+ */
142
+ export function buildQueryString(params) {
143
+ return new URLSearchParams(params).toString();
144
+ }
145
+
146
+ /**
147
+ * Validate email format
148
+ * @param {string} email - Email to validate
149
+ */
150
+ export function isValidEmail(email) {
151
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
152
+ return emailRegex.test(email);
153
+ }
154
+
155
+ /**
156
+ * Copy text to clipboard
157
+ * @param {string} text - Text to copy
158
+ */
159
+ export async function copyToClipboard(text) {
160
+ try {
161
+ await navigator.clipboard.writeText(text);
162
+ return true;
163
+ } catch (err) {
164
+ console.error('Failed to copy:', err);
165
+ return false;
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Check if element is in viewport
171
+ * @param {HTMLElement} element - Element to check
172
+ */
173
+ export function isInViewport(element) {
174
+ const rect = element.getBoundingClientRect();
175
+ return (
176
+ rect.top >= 0 &&
177
+ rect.left >= 0 &&
178
+ rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
179
+ rect.right <= (window.innerWidth || document.documentElement.clientWidth)
180
+ );
181
+ }
182
+
183
+ /**
184
+ * Scroll element into view smoothly
185
+ * @param {HTMLElement|string} elementOrSelector - Element or selector
186
+ */
187
+ export function scrollToElement(elementOrSelector) {
188
+ const element = typeof elementOrSelector === 'string'
189
+ ? document.querySelector(elementOrSelector)
190
+ : elementOrSelector;
191
+
192
+ if (element) {
193
+ element.scrollIntoView({ behavior: 'smooth', block: 'start' });
194
+ }
195
+ }