create-nativecore 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. package/README.md +6 -14
  2. package/bin/index.mjs +402 -431
  3. package/package.json +3 -2
  4. package/template/.env.example +28 -0
  5. package/template/.htmlhintrc +14 -0
  6. package/template/api/data/dashboard.json +11 -0
  7. package/template/api/data/users.json +18 -0
  8. package/template/api/mockApi.js +161 -0
  9. package/template/assets/icon.svg +13 -0
  10. package/template/assets/logo.svg +25 -0
  11. package/template/eslint.config.js +94 -0
  12. package/template/index.html +137 -0
  13. package/template/manifest.json +19 -0
  14. package/template/public/.well-known/security.txt +9 -0
  15. package/template/public/_headers +24 -0
  16. package/template/public/_redirects +14 -0
  17. package/template/public/assets/icon.svg +13 -0
  18. package/template/public/assets/logo.svg +25 -0
  19. package/template/public/manifest.json +19 -0
  20. package/template/public/robots.txt +13 -0
  21. package/template/public/sitemap.xml +27 -0
  22. package/template/scripts/build-for-bots.mjs +121 -0
  23. package/template/scripts/convert-to-ts.mjs +106 -0
  24. package/template/scripts/fix-encoding.mjs +38 -0
  25. package/template/scripts/fix-svg-paths.mjs +32 -0
  26. package/template/scripts/generate-cf-router.mjs +52 -0
  27. package/template/scripts/inject-dev-tools.mjs +41 -0
  28. package/template/scripts/inject-version.mjs +65 -0
  29. package/template/scripts/make-component.mjs +445 -0
  30. package/template/scripts/make-component.mjs.backup +432 -0
  31. package/template/scripts/make-controller.mjs +119 -0
  32. package/template/scripts/make-core-component.mjs +303 -0
  33. package/template/scripts/make-view.mjs +346 -0
  34. package/template/scripts/minify.mjs +71 -0
  35. package/template/scripts/prepare-static-assets.mjs +141 -0
  36. package/template/scripts/prompt-bot-build.mjs +223 -0
  37. package/template/scripts/remove-component.mjs +170 -0
  38. package/template/scripts/remove-core-component.mjs +156 -0
  39. package/template/scripts/remove-dev.mjs +13 -0
  40. package/template/scripts/remove-view.mjs +200 -0
  41. package/template/scripts/strip-dev-blocks.mjs +30 -0
  42. package/template/scripts/watch-compile.mjs +69 -0
  43. package/template/server.js +1066 -0
  44. package/template/src/app.ts +115 -0
  45. package/template/src/components/appRegistry.ts +8 -0
  46. package/template/src/components/core/app-footer.ts +27 -0
  47. package/template/src/components/core/app-header.ts +175 -0
  48. package/template/src/components/core/app-sidebar.ts +238 -0
  49. package/template/src/components/core/loading-spinner.ts +25 -0
  50. package/template/src/components/core/nc-a.ts +313 -0
  51. package/template/src/components/core/nc-accordion.ts +186 -0
  52. package/template/src/components/core/nc-alert.ts +153 -0
  53. package/template/src/components/core/nc-animation.ts +1150 -0
  54. package/template/src/components/core/nc-autocomplete.ts +271 -0
  55. package/template/src/components/core/nc-avatar-group.ts +113 -0
  56. package/template/src/components/core/nc-avatar.ts +148 -0
  57. package/template/src/components/core/nc-badge.ts +86 -0
  58. package/template/src/components/core/nc-bottom-nav.ts +214 -0
  59. package/template/src/components/core/nc-breadcrumb.ts +96 -0
  60. package/template/src/components/core/nc-button.ts +307 -0
  61. package/template/src/components/core/nc-card.ts +160 -0
  62. package/template/src/components/core/nc-checkbox.ts +282 -0
  63. package/template/src/components/core/nc-chip.ts +115 -0
  64. package/template/src/components/core/nc-code.ts +314 -0
  65. package/template/src/components/core/nc-collapsible.ts +154 -0
  66. package/template/src/components/core/nc-color-picker.ts +268 -0
  67. package/template/src/components/core/nc-copy-button.ts +119 -0
  68. package/template/src/components/core/nc-date-picker.ts +443 -0
  69. package/template/src/components/core/nc-div.ts +280 -0
  70. package/template/src/components/core/nc-divider.ts +81 -0
  71. package/template/src/components/core/nc-drawer.ts +230 -0
  72. package/template/src/components/core/nc-dropdown.ts +178 -0
  73. package/template/src/components/core/nc-empty-state.ts +134 -0
  74. package/template/src/components/core/nc-file-upload.ts +354 -0
  75. package/template/src/components/core/nc-form.ts +312 -0
  76. package/template/src/components/core/nc-image.ts +184 -0
  77. package/template/src/components/core/nc-input.ts +383 -0
  78. package/template/src/components/core/nc-kbd.ts +48 -0
  79. package/template/src/components/core/nc-menu-item.ts +193 -0
  80. package/template/src/components/core/nc-menu.ts +376 -0
  81. package/template/src/components/core/nc-modal.ts +238 -0
  82. package/template/src/components/core/nc-nav-item.ts +151 -0
  83. package/template/src/components/core/nc-number-input.ts +350 -0
  84. package/template/src/components/core/nc-otp-input.ts +235 -0
  85. package/template/src/components/core/nc-pagination.ts +178 -0
  86. package/template/src/components/core/nc-popover.ts +260 -0
  87. package/template/src/components/core/nc-progress-circular.ts +119 -0
  88. package/template/src/components/core/nc-progress.ts +134 -0
  89. package/template/src/components/core/nc-radio.ts +235 -0
  90. package/template/src/components/core/nc-rating.ts +266 -0
  91. package/template/src/components/core/nc-rich-text.ts +283 -0
  92. package/template/src/components/core/nc-scroll-top.ts +116 -0
  93. package/template/src/components/core/nc-select.ts +452 -0
  94. package/template/src/components/core/nc-skeleton.ts +107 -0
  95. package/template/src/components/core/nc-slider.ts +285 -0
  96. package/template/src/components/core/nc-snackbar.ts +230 -0
  97. package/template/src/components/core/nc-splash.ts +343 -0
  98. package/template/src/components/core/nc-stepper.ts +247 -0
  99. package/template/src/components/core/nc-switch.ts +281 -0
  100. package/template/src/components/core/nc-tab-item.ts +138 -0
  101. package/template/src/components/core/nc-table.ts +279 -0
  102. package/template/src/components/core/nc-tabs.ts +554 -0
  103. package/template/src/components/core/nc-tag-input.ts +279 -0
  104. package/template/src/components/core/nc-textarea.ts +216 -0
  105. package/template/src/components/core/nc-time-picker.ts +438 -0
  106. package/template/src/components/core/nc-timeline.ts +186 -0
  107. package/template/src/components/core/nc-tooltip.ts +143 -0
  108. package/template/src/components/frameworkRegistry.ts +68 -0
  109. package/template/src/components/preloadRegistry.ts +28 -0
  110. package/template/src/components/registry.ts +8 -0
  111. package/template/src/components/ui/dashboard-signal-lab.ts +284 -0
  112. package/template/src/constants/apiEndpoints.ts +27 -0
  113. package/template/src/constants/errorMessages.ts +23 -0
  114. package/template/src/constants/index.ts +8 -0
  115. package/template/src/constants/routePaths.ts +15 -0
  116. package/template/src/constants/storageKeys.ts +18 -0
  117. package/template/src/controllers/dashboard.controller.ts +200 -0
  118. package/template/src/controllers/home.controller.ts +21 -0
  119. package/template/src/controllers/index.ts +11 -0
  120. package/template/src/controllers/login.controller.ts +131 -0
  121. package/template/src/core/component.ts +354 -0
  122. package/template/src/core/errorHandler.ts +85 -0
  123. package/template/src/core/gpu-animation.ts +604 -0
  124. package/template/src/core/http.ts +173 -0
  125. package/template/src/core/lazyComponents.ts +90 -0
  126. package/template/src/core/router.ts +642 -0
  127. package/template/src/core/signals.ts +146 -0
  128. package/template/src/core/state.ts +248 -0
  129. package/template/src/dev/component-editor.ts +1363 -0
  130. package/template/src/dev/component-overlay.ts +278 -0
  131. package/template/src/dev/context-menu.ts +223 -0
  132. package/template/src/dev/denc-tools.ts +250 -0
  133. package/template/src/dev/hmr.ts +189 -0
  134. package/template/src/dev/nfbs.code-workspace +27 -0
  135. package/template/src/dev/outline-panel.ts +1247 -0
  136. package/template/src/middleware/auth.middleware.ts +23 -0
  137. package/template/src/routes/routes.ts +38 -0
  138. package/template/src/services/api.service.ts +394 -0
  139. package/template/src/services/auth.service.ts +176 -0
  140. package/template/src/services/index.ts +8 -0
  141. package/template/src/services/logger.service.ts +74 -0
  142. package/template/src/services/storage.service.ts +88 -0
  143. package/template/src/stores/appStore.ts +57 -0
  144. package/template/src/stores/uiStore.ts +36 -0
  145. package/template/src/styles/core-variables.css +219 -0
  146. package/template/src/styles/core.css +710 -0
  147. package/template/src/styles/main.css +3164 -0
  148. package/template/src/styles/variables.css +152 -0
  149. package/template/src/types/global.d.ts +47 -0
  150. package/template/src/utils/cacheBuster.ts +20 -0
  151. package/template/src/utils/dom.ts +149 -0
  152. package/template/src/utils/events.ts +203 -0
  153. package/template/src/utils/form.ts +176 -0
  154. package/template/src/utils/formatters.ts +169 -0
  155. package/template/src/utils/helpers.ts +195 -0
  156. package/template/src/utils/markdown.ts +307 -0
  157. package/template/src/utils/sidebar.ts +96 -0
  158. package/template/src/utils/smoothScroll.ts +85 -0
  159. package/template/src/utils/templates.ts +23 -0
  160. package/template/src/utils/validation.ts +73 -0
  161. package/template/src/views/protected/dashboard.html +293 -0
  162. package/template/src/views/public/home.html +150 -0
  163. package/template/src/views/public/login.html +102 -0
  164. package/template/tests/unit/component.test.ts +87 -0
  165. package/template/tests/unit/computed.test.ts +79 -0
  166. package/template/tests/unit/form.test.ts +68 -0
  167. package/template/tests/unit/formatters.test.ts +49 -0
  168. package/template/tests/unit/lazy-components.test.ts +59 -0
  169. package/template/tests/unit/markdown.test.ts +62 -0
  170. package/template/tests/unit/router.test.ts +112 -0
  171. package/template/tests/unit/signals.test.ts +54 -0
  172. package/template/tests/unit/validation.test.ts +50 -0
  173. package/template/tsconfig.build.json +21 -0
  174. package/template/tsconfig.json +51 -0
  175. package/template/vitest.config.ts +36 -0
@@ -0,0 +1,278 @@
1
+ /**
2
+ * Component Overlay System
3
+ *
4
+ * Shows a tiny persistent gear button on each custom component in dev mode.
5
+ * Visibility is controlled by the dev tools toggle pill.
6
+ */
7
+
8
+ export class ComponentOverlay {
9
+ private onEdit: (element: HTMLElement) => void;
10
+ private observedComponents: Set<HTMLElement> = new Set();
11
+ private componentButtons: Map<HTMLElement, HTMLButtonElement> = new Map();
12
+ private mutationObserver: MutationObserver | null = null;
13
+ private scrollListener: (() => void) | null = null;
14
+ private resizeListener: (() => void) | null = null;
15
+ private rescanInterval: number | null = null;
16
+ private updateFrame: number | null = null;
17
+ private visible = true;
18
+
19
+ constructor(onEdit: (element: HTMLElement) => void) {
20
+ this.onEdit = onEdit;
21
+ this.injectStyles();
22
+ this.setupObserver();
23
+ this.scanExistingComponents();
24
+
25
+ this.scrollListener = () => this.schedulePositionUpdate();
26
+ this.resizeListener = () => this.schedulePositionUpdate();
27
+ window.addEventListener('scroll', this.scrollListener, true);
28
+ window.addEventListener('resize', this.resizeListener);
29
+
30
+ this.rescanInterval = window.setInterval(() => this.scanExistingComponents(), 2000);
31
+ }
32
+
33
+ setVisible(visible: boolean): void {
34
+ this.visible = visible;
35
+ this.schedulePositionUpdate();
36
+ }
37
+
38
+ private injectStyles(): void {
39
+ const styleId = 'nativecore-overlay-styles';
40
+ if ((window.dom?.query?.(`#${styleId}`) || document.getElementById(styleId))) return;
41
+
42
+ const style = document.createElement('style');
43
+ style.id = styleId;
44
+ style.textContent = `
45
+ .nc-denc-control {
46
+ position: fixed !important;
47
+ width: 16px !important;
48
+ height: 16px !important;
49
+ display: flex !important;
50
+ align-items: center !important;
51
+ justify-content: center !important;
52
+ border: 1px solid rgba(15, 23, 42, 0.16) !important;
53
+ border-radius: 999px !important;
54
+ background: rgba(255, 255, 255, 0.96) !important;
55
+ color: #475569 !important;
56
+ box-shadow: 0 2px 6px rgba(15, 23, 42, 0.14) !important;
57
+ cursor: pointer !important;
58
+ opacity: 0.7 !important;
59
+ z-index: 999997 !important;
60
+ padding: 0 !important;
61
+ transition: opacity 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease !important;
62
+ }
63
+
64
+ .nc-denc-control:hover {
65
+ opacity: 1 !important;
66
+ transform: scale(1.08) !important;
67
+ box-shadow: 0 4px 10px rgba(15, 23, 42, 0.18) !important;
68
+ }
69
+
70
+ .nc-denc-control--hidden {
71
+ opacity: 0 !important;
72
+ pointer-events: none !important;
73
+ }
74
+
75
+ .nc-denc-control svg {
76
+ width: 9px !important;
77
+ height: 9px !important;
78
+ fill: currentColor !important;
79
+ }
80
+
81
+ .nc-denc-highlight {
82
+ outline: 2px dashed rgba(102, 126, 234, 0.5) !important;
83
+ outline-offset: 2px !important;
84
+ }
85
+ `;
86
+ document.head.appendChild(style);
87
+ }
88
+
89
+ private setupObserver(): void {
90
+ this.mutationObserver = new MutationObserver((mutations) => {
91
+ for (const mutation of mutations) {
92
+ mutation.addedNodes.forEach((node) => {
93
+ if (node instanceof HTMLElement) {
94
+ this.processTree(node);
95
+ }
96
+ });
97
+
98
+ mutation.removedNodes.forEach((node) => {
99
+ if (node instanceof HTMLElement) {
100
+ this.cleanupTree(node);
101
+ }
102
+ });
103
+ }
104
+
105
+ this.schedulePositionUpdate();
106
+ });
107
+
108
+ this.mutationObserver.observe(document.body, {
109
+ childList: true,
110
+ subtree: true,
111
+ });
112
+ }
113
+
114
+ private scanExistingComponents(): void {
115
+ const allElements = (window.dom?.queryAll?.('*') || document.querySelectorAll('*'));
116
+ allElements.forEach((element: Element) => {
117
+ if (element instanceof HTMLElement) {
118
+ this.processElement(element);
119
+ }
120
+ });
121
+
122
+ this.schedulePositionUpdate();
123
+ }
124
+
125
+ private processTree(root: HTMLElement): void {
126
+ this.processElement(root);
127
+ root.querySelectorAll('*').forEach((element) => {
128
+ if (element instanceof HTMLElement) {
129
+ this.processElement(element);
130
+ }
131
+ });
132
+ }
133
+
134
+ private cleanupTree(root: HTMLElement): void {
135
+ this.cleanupElement(root);
136
+ root.querySelectorAll('*').forEach((element) => {
137
+ if (element instanceof HTMLElement) {
138
+ this.cleanupElement(element);
139
+ }
140
+ });
141
+ }
142
+
143
+ private isCustomComponent(element: HTMLElement): boolean {
144
+ const tagName = element.tagName.toLowerCase();
145
+
146
+ if (!tagName.includes('-')) return false;
147
+ if (tagName.startsWith('nc-dev')) return false;
148
+ if (tagName.startsWith('ion-') || tagName.startsWith('mat-')) return false;
149
+ if (tagName.startsWith('nc-outline')) return false;
150
+ if (tagName === 'nativecore-denc-indicator') return false;
151
+
152
+ return customElements.get(tagName) !== undefined || element.shadowRoot !== null;
153
+ }
154
+
155
+ private processElement(element: HTMLElement): void {
156
+ if (!this.isCustomComponent(element)) return;
157
+ if (this.observedComponents.has(element)) return;
158
+
159
+ this.observedComponents.add(element);
160
+
161
+ const button = document.createElement('button');
162
+ button.type = 'button';
163
+ button.className = 'nc-denc-control';
164
+ button.title = `Edit <${element.tagName.toLowerCase()}>`;
165
+ button.setAttribute('aria-label', `Edit <${element.tagName.toLowerCase()}>`);
166
+ button.innerHTML = `
167
+ <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
168
+ <path d="M19.14 12.94c.04-.31.06-.63.06-.94 0-.31-.02-.63-.06-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.04.31-.06.63-.06.94s.02.63.06.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"/>
169
+ </svg>
170
+ `;
171
+
172
+ button.addEventListener('mouseenter', () => {
173
+ element.classList.add('nc-denc-highlight');
174
+ });
175
+
176
+ button.addEventListener('mouseleave', () => {
177
+ element.classList.remove('nc-denc-highlight');
178
+ });
179
+
180
+ button.addEventListener('click', (event) => {
181
+ event.preventDefault();
182
+ event.stopPropagation();
183
+ event.stopImmediatePropagation();
184
+ this.onEdit(element);
185
+ });
186
+
187
+ document.body.appendChild(button);
188
+ this.componentButtons.set(element, button);
189
+ this.updateControlPosition(element, button);
190
+ }
191
+
192
+ private updateControlPosition(element: HTMLElement, button: HTMLButtonElement): void {
193
+ if (!this.visible) {
194
+ button.classList.add('nc-denc-control--hidden');
195
+ return;
196
+ }
197
+
198
+ const rect = element.getBoundingClientRect();
199
+ const size = 16;
200
+ const inset = 4;
201
+ const inViewport = rect.width > 0 && rect.height > 0 && rect.bottom > 0 && rect.right > 0 && rect.top < window.innerHeight && rect.left < window.innerWidth;
202
+
203
+ if (!inViewport) {
204
+ button.classList.add('nc-denc-control--hidden');
205
+ return;
206
+ }
207
+
208
+ const top = Math.min(Math.max(rect.top + inset, 4), window.innerHeight - size - 4);
209
+ const left = Math.min(Math.max(rect.right - size - inset, 4), window.innerWidth - size - 4);
210
+
211
+ button.style.top = `${top}px`;
212
+ button.style.left = `${left}px`;
213
+ button.classList.remove('nc-denc-control--hidden');
214
+ }
215
+
216
+ private schedulePositionUpdate(): void {
217
+ if (this.updateFrame !== null) return;
218
+
219
+ this.updateFrame = window.requestAnimationFrame(() => {
220
+ this.updateFrame = null;
221
+ this.componentButtons.forEach((button, element) => {
222
+ if (!document.body.contains(element)) {
223
+ this.cleanupElement(element);
224
+ return;
225
+ }
226
+
227
+ this.updateControlPosition(element, button);
228
+ });
229
+ });
230
+ }
231
+
232
+ private cleanupElement(element: HTMLElement): void {
233
+ this.observedComponents.delete(element);
234
+ element.classList.remove('nc-denc-highlight');
235
+
236
+ const button = this.componentButtons.get(element);
237
+ if (button) {
238
+ button.remove();
239
+ this.componentButtons.delete(element);
240
+ }
241
+ }
242
+
243
+ destroy(): void {
244
+ if (this.mutationObserver) {
245
+ this.mutationObserver.disconnect();
246
+ this.mutationObserver = null;
247
+ }
248
+
249
+ if (this.scrollListener) {
250
+ window.removeEventListener('scroll', this.scrollListener, true);
251
+ this.scrollListener = null;
252
+ }
253
+
254
+ if (this.resizeListener) {
255
+ window.removeEventListener('resize', this.resizeListener);
256
+ this.resizeListener = null;
257
+ }
258
+
259
+ if (this.rescanInterval !== null) {
260
+ window.clearInterval(this.rescanInterval);
261
+ this.rescanInterval = null;
262
+ }
263
+
264
+ if (this.updateFrame !== null) {
265
+ window.cancelAnimationFrame(this.updateFrame);
266
+ this.updateFrame = null;
267
+ }
268
+
269
+ this.componentButtons.forEach((button, element) => {
270
+ element.classList.remove('nc-denc-highlight');
271
+ button.remove();
272
+ });
273
+
274
+ this.componentButtons.clear();
275
+ this.observedComponents.clear();
276
+ (window.dom?.query?.('#nativecore-overlay-styles') || document.getElementById('nativecore-overlay-styles'))?.remove();
277
+ }
278
+ }
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Context Menu System for Dev Tools
3
+ *
4
+ * Adds a right-click context menu to custom components
5
+ * with an option to open the dev tools sidebar.
6
+ */
7
+
8
+ export class ContextMenu {
9
+ private onEdit: (element: HTMLElement) => void;
10
+ private observedComponents: Set<HTMLElement> = new Set();
11
+ private mutationObserver: MutationObserver | null = null;
12
+ private activeMenu: HTMLElement | null = null;
13
+
14
+ constructor(onEdit: (element: HTMLElement) => void) {
15
+ this.onEdit = onEdit;
16
+ this.injectStyles();
17
+ this.setupObserver();
18
+ this.scanExistingComponents();
19
+
20
+ // Close menu when clicking elsewhere
21
+ document.addEventListener('click', () => this.hideMenu());
22
+ document.addEventListener('contextmenu', (e) => {
23
+ // If not on a custom component, let browser handle it
24
+ const target = e.target as HTMLElement;
25
+ if (!this.isCustomComponent(target)) {
26
+ this.hideMenu();
27
+ }
28
+ });
29
+
30
+ // Re-scan periodically for lazy-loaded components
31
+ setInterval(() => this.scanExistingComponents(), 2000);
32
+ }
33
+
34
+ /**
35
+ * Inject context menu styles
36
+ */
37
+ private injectStyles(): void {
38
+ const styleId = 'nativecore-context-menu-styles';
39
+ if (document.getElementById(styleId)) return;
40
+
41
+ const style = document.createElement('style');
42
+ style.id = styleId;
43
+ style.textContent = `
44
+ .nc-context-menu {
45
+ position: fixed;
46
+ background: white;
47
+ border: 1px solid #ccc;
48
+ border-radius: 4px;
49
+ box-shadow: 0 2px 10px rgba(0,0,0,0.2);
50
+ padding: 4px 0;
51
+ z-index: 999999;
52
+ min-width: 180px;
53
+ font-family: system-ui, -apple-system, sans-serif;
54
+ font-size: 14px;
55
+ }
56
+
57
+ .nc-context-menu-item {
58
+ padding: 8px 16px;
59
+ cursor: pointer;
60
+ display: flex;
61
+ align-items: center;
62
+ gap: 8px;
63
+ transition: background 0.15s ease;
64
+ }
65
+
66
+ .nc-context-menu-item:hover {
67
+ background: #f0f0f0;
68
+ }
69
+
70
+ .nc-context-menu-item.devtools {
71
+ border-top: 1px solid #e0e0e0;
72
+ color: #667eea;
73
+ font-weight: 600;
74
+ }
75
+
76
+ .nc-context-menu-item.devtools:hover {
77
+ background: #f5f3ff;
78
+ }
79
+
80
+ .nc-context-menu-icon {
81
+ width: 16px;
82
+ height: 16px;
83
+ fill: currentColor;
84
+ }
85
+ `;
86
+ document.head.appendChild(style);
87
+ }
88
+
89
+ /**
90
+ * Setup mutation observer
91
+ */
92
+ private setupObserver(): void {
93
+ this.mutationObserver = new MutationObserver((mutations) => {
94
+ mutations.forEach((mutation) => {
95
+ mutation.addedNodes.forEach((node) => {
96
+ if (node.nodeType === Node.ELEMENT_NODE) {
97
+ this.processElement(node as HTMLElement);
98
+ // Process children
99
+ (node as HTMLElement).querySelectorAll('*').forEach((child) => {
100
+ this.processElement(child as HTMLElement);
101
+ });
102
+ }
103
+ });
104
+ });
105
+ });
106
+
107
+ this.mutationObserver.observe(document.body, {
108
+ childList: true,
109
+ subtree: true
110
+ });
111
+ }
112
+
113
+ /**
114
+ * Scan for existing components
115
+ */
116
+ private scanExistingComponents(): void {
117
+ document.querySelectorAll('*').forEach((element) => {
118
+ this.processElement(element as HTMLElement);
119
+ });
120
+ }
121
+
122
+ /**
123
+ * Check if element is a custom component
124
+ */
125
+ private isCustomComponent(element: HTMLElement): boolean {
126
+ const tagName = element.tagName.toLowerCase();
127
+
128
+ // Must have a hyphen (Web Component requirement)
129
+ if (!tagName.includes('-')) return false;
130
+
131
+ // Skip dev tools elements
132
+ if (tagName.startsWith('nc-dev')) return false;
133
+
134
+ // Skip common third-party prefixes
135
+ if (tagName.startsWith('ion-') || tagName.startsWith('mat-')) return false;
136
+
137
+ // Check if it's a registered custom element OR has shadowRoot
138
+ return customElements.get(tagName) !== undefined || element.shadowRoot !== null;
139
+ }
140
+
141
+ /**
142
+ * Process element and add context menu
143
+ */
144
+ private processElement(element: HTMLElement): void {
145
+ if (!this.isCustomComponent(element)) return;
146
+ if (this.observedComponents.has(element)) return;
147
+
148
+ // Mark as observed
149
+ this.observedComponents.add(element);
150
+
151
+ // Add context menu listener
152
+ element.addEventListener('contextmenu', (e) => {
153
+ e.preventDefault();
154
+ e.stopPropagation();
155
+ this.showMenu(e.clientX, e.clientY, element);
156
+ });
157
+ }
158
+
159
+ /**
160
+ * Show context menu
161
+ */
162
+ private showMenu(x: number, y: number, element: HTMLElement): void {
163
+ this.hideMenu();
164
+
165
+ const menu = document.createElement('div');
166
+ menu.className = 'nc-context-menu';
167
+
168
+ // Dev Tools option
169
+ const devToolsItem = document.createElement('div');
170
+ devToolsItem.className = 'nc-context-menu-item devtools';
171
+ devToolsItem.innerHTML = `
172
+ <svg class="nc-context-menu-icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
173
+ <path d="M19.14 12.94c.04-.31.06-.63.06-.94 0-.31-.02-.63-.06-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.04.31-.06.63-.06.94s.02.63.06.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"/>
174
+ </svg>
175
+ Edit Component
176
+ `;
177
+ devToolsItem.addEventListener('click', (e) => {
178
+ e.stopPropagation();
179
+ this.hideMenu();
180
+ this.onEdit(element);
181
+ });
182
+
183
+ menu.appendChild(devToolsItem);
184
+
185
+ // Position menu
186
+ menu.style.left = `${x}px`;
187
+ menu.style.top = `${y}px`;
188
+
189
+ // Adjust if off-screen
190
+ document.body.appendChild(menu);
191
+ const rect = menu.getBoundingClientRect();
192
+ if (rect.right > window.innerWidth) {
193
+ menu.style.left = `${window.innerWidth - rect.width - 10}px`;
194
+ }
195
+ if (rect.bottom > window.innerHeight) {
196
+ menu.style.top = `${window.innerHeight - rect.height - 10}px`;
197
+ }
198
+
199
+ this.activeMenu = menu;
200
+ }
201
+
202
+ /**
203
+ * Hide context menu
204
+ */
205
+ private hideMenu(): void {
206
+ if (this.activeMenu) {
207
+ this.activeMenu.remove();
208
+ this.activeMenu = null;
209
+ }
210
+ }
211
+
212
+ /**
213
+ * Destroy the context menu system
214
+ */
215
+ destroy(): void {
216
+ if (this.mutationObserver) {
217
+ this.mutationObserver.disconnect();
218
+ }
219
+ this.hideMenu();
220
+ document.getElementById('nativecore-context-menu-styles')?.remove();
221
+ this.observedComponents.clear();
222
+ }
223
+ }