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,1247 @@
1
+ /**
2
+ * Outline Panel - DOM Tree View
3
+ *
4
+ * Shows a collapsible left-side panel with the full DOM tree
5
+ * of the page, including both custom components and regular elements.
6
+ */
7
+
8
+ export class OutlinePanel {
9
+ private onEdit: (element: HTMLElement, fromOutlineTree?: boolean) => void;
10
+ private panel: HTMLElement | null = null;
11
+ private tab: HTMLElement | null = null;
12
+ private isOpen: boolean = false;
13
+ private isVisible: boolean = true;
14
+ private mutationObserver: MutationObserver | null = null;
15
+ private refreshTimeout: number | null = null;
16
+
17
+ constructor(onEdit: (element: HTMLElement, fromOutlineTree?: boolean) => void) {
18
+ this.onEdit = onEdit;
19
+ this.injectStyles();
20
+ this.createPanel();
21
+ // Disable auto-refresh to prevent loops
22
+ // this.setupMutationObserver();
23
+
24
+ // Listen for current element from editor
25
+ document.addEventListener('nc-current-element-response', (e: Event) => {
26
+ const element = (e as CustomEvent).detail.element;
27
+ if (element && this.isOpen) {
28
+ this.expandToElement(element);
29
+ }
30
+ });
31
+ }
32
+
33
+ /**
34
+ * Inject panel styles
35
+ */
36
+ private injectStyles(): void {
37
+ const styleId = 'nativecore-outline-styles';
38
+ if (document.getElementById(styleId)) return;
39
+
40
+ const style = document.createElement('style');
41
+ style.id = styleId;
42
+ style.textContent = `
43
+ .nc-outline-panel {
44
+ position: fixed;
45
+ left: 0;
46
+ top: 0;
47
+ height: 100vh;
48
+ min-width: 280px;
49
+ max-width: 600px;
50
+ width: 280px;
51
+ background: #0f1520;
52
+ border-right: 1px solid #3a485b;
53
+ box-shadow: 2px 0 10px rgba(0,0,0,0.3);
54
+ z-index: 999998;
55
+ transition: transform 0.2s ease, width 0.3s ease;
56
+ display: flex;
57
+ flex-direction: column;
58
+ }
59
+
60
+ .nc-outline-panel.closed {
61
+ transform: translateX(-100%);
62
+ }
63
+
64
+ .nc-outline-tab {
65
+ position: fixed;
66
+ left: 0;
67
+ top: 50%;
68
+ transform: translateY(-50%);
69
+ width: 40px;
70
+ height: 140px;
71
+ background: #182130;
72
+ color: #e7eef7;
73
+ border: 1px solid #3a485b;
74
+ border-left: 0;
75
+ border-radius: 0 12px 12px 0;
76
+ box-shadow: 2px 2px 8px rgba(0,0,0,0.3);
77
+ writing-mode: vertical-rl;
78
+ text-orientation: mixed;
79
+ display: flex;
80
+ align-items: center;
81
+ justify-content: center;
82
+ font-weight: 700;
83
+ font-size: 13px;
84
+ letter-spacing: 2px;
85
+ cursor: pointer;
86
+ transition: all 0.2s ease;
87
+ z-index: 999999;
88
+ opacity: 1;
89
+ pointer-events: auto;
90
+ }
91
+
92
+ .nc-outline-tab.hidden {
93
+ opacity: 0;
94
+ pointer-events: none;
95
+ transform: translateY(-50%) translateX(-50px);
96
+ }
97
+
98
+ .nc-outline-tab:hover {
99
+ background: #1f2838;
100
+ transform: translateY(-50%) translateX(4px);
101
+ }
102
+
103
+ .nc-outline-content {
104
+ width: 100%;
105
+ height: 100%;
106
+ display: flex;
107
+ flex-direction: column;
108
+ font-family: 'Fira Code', 'Courier New', monospace;
109
+ font-size: 12px;
110
+ }
111
+
112
+ .nc-outline-header {
113
+ position: sticky;
114
+ top: 0;
115
+ z-index: 10;
116
+ background: #0f1520;
117
+ display: flex;
118
+ justify-content: space-between;
119
+ align-items: center;
120
+ padding: 20px 20px 12px 20px;
121
+ border-bottom: 1px solid #3a485b;
122
+ white-space: nowrap;
123
+ flex-shrink: 0;
124
+ }
125
+
126
+ .nc-outline-body {
127
+ flex: 1;
128
+ overflow-y: auto;
129
+ overflow-x: hidden;
130
+ padding: 10px 0;
131
+ scrollbar-width: thin;
132
+ scrollbar-color: #3a485b #0f1520;
133
+ }
134
+
135
+ .nc-outline-body::-webkit-scrollbar {
136
+ width: 8px;
137
+ }
138
+
139
+ .nc-outline-body::-webkit-scrollbar-track {
140
+ background: #0f1520;
141
+ }
142
+
143
+ .nc-outline-body::-webkit-scrollbar-thumb {
144
+ background: #3a485b;
145
+ border-radius: 4px;
146
+ transition: background 0.2s;
147
+ }
148
+
149
+ .nc-outline-body::-webkit-scrollbar-thumb:hover {
150
+ background: #4b5e75;
151
+ }
152
+
153
+ .nc-outline-title {
154
+ font-size: 14px;
155
+ font-weight: 700;
156
+ color: #e7eef7;
157
+ }
158
+
159
+ .nc-outline-close {
160
+ cursor: pointer;
161
+ padding: 4px 8px;
162
+ border-radius: 6px;
163
+ background: transparent;
164
+ color: #6c7a8a;
165
+ transition: all 0.15s;
166
+ font-size: 16px;
167
+ }
168
+
169
+ .nc-outline-close:hover {
170
+ background: #182130;
171
+ color: #e7eef7;
172
+ }
173
+
174
+ .nc-outline-tree {
175
+ padding: 0 10px;
176
+ width: max-content;
177
+ min-width: 100%;
178
+ }
179
+
180
+ .nc-tree-node {
181
+ font-size: 12px;
182
+ padding: 5px 8px;
183
+ border-radius: 6px;
184
+ cursor: pointer;
185
+ margin: 1px 0;
186
+ transition: background 0.15s;
187
+ user-select: none;
188
+ color: #c9d4e0;
189
+ display: flex;
190
+ align-items: center;
191
+ gap: 6px;
192
+ white-space: nowrap;
193
+ }
194
+
195
+ .nc-tree-node:hover {
196
+ background: #182130;
197
+ cursor: pointer;
198
+ }
199
+
200
+ .nc-tree-node.custom-component {
201
+ background: rgba(102,126,234,0.08);
202
+ }
203
+
204
+ .nc-tree-node.custom-component:hover {
205
+ background: rgba(102,126,234,0.15);
206
+ cursor: pointer;
207
+ }
208
+
209
+ @media (max-width: 768px) {
210
+ .nc-outline-panel,
211
+ .nc-outline-tab {
212
+ display: none !important;
213
+ }
214
+ }
215
+
216
+ .nc-tree-toggle {
217
+ width: 12px;
218
+ height: 12px;
219
+ display: inline-flex;
220
+ align-items: center;
221
+ justify-content: center;
222
+ color: #6c7a8a;
223
+ font-size: 10px;
224
+ flex-shrink: 0;
225
+ transition: transform 0.2s;
226
+ }
227
+
228
+ .nc-tree-toggle.collapsed {
229
+ transform: rotate(-90deg);
230
+ }
231
+
232
+ .nc-tree-toggle:hover {
233
+ color: #9ad1ff;
234
+ }
235
+
236
+ .nc-tree-content {
237
+ display: flex;
238
+ align-items: center;
239
+ gap: 4px;
240
+ flex: 1;
241
+ min-width: 0;
242
+ }
243
+
244
+ .nc-tree-children {
245
+ margin-left: 20px;
246
+ }
247
+
248
+ .nc-tree-children.hidden {
249
+ display: none;
250
+ }
251
+
252
+ .nc-tree-tag {
253
+ color: #9ad1ff;
254
+ font-weight: 600;
255
+ }
256
+
257
+ .nc-tree-tag.custom {
258
+ color: #b794f4;
259
+ }
260
+
261
+ .nc-tree-attr {
262
+ color: #c9d4e0;
263
+ opacity: 0.7;
264
+ font-size: 11px;
265
+ margin-left: 4px;
266
+ }
267
+
268
+ .nc-tree-gear {
269
+ margin-left: 8px;
270
+ width: 14px;
271
+ height: 14px;
272
+ fill: #5cc8ff;
273
+ cursor: pointer;
274
+ opacity: 0.5;
275
+ transition: opacity 0.2s;
276
+ vertical-align: middle;
277
+ display: inline-block;
278
+ }
279
+
280
+ .nc-tree-gear:hover {
281
+ opacity: 1;
282
+ }
283
+
284
+ /* Session Storage Section */
285
+ .nc-storage-section {
286
+ border-top: 1px solid rgba(255,255,255,0.1);
287
+ margin-top: 1rem;
288
+ }
289
+
290
+ .nc-storage-header {
291
+ padding: 12px 16px;
292
+ display: flex;
293
+ align-items: center;
294
+ gap: 8px;
295
+ cursor: pointer;
296
+ background: rgba(255,255,255,0.03);
297
+ transition: background 0.2s;
298
+ }
299
+
300
+ .nc-storage-header:hover {
301
+ background: rgba(255,255,255,0.06);
302
+ }
303
+
304
+ .nc-storage-toggle {
305
+ font-size: 10px;
306
+ color: #5cc8ff;
307
+ transition: transform 0.2s;
308
+ flex-shrink: 0;
309
+ }
310
+
311
+ .nc-storage-toggle.collapsed {
312
+ transform: rotate(-90deg);
313
+ }
314
+
315
+ .nc-storage-title {
316
+ color: #fff;
317
+ font-size: 11px;
318
+ font-weight: 600;
319
+ letter-spacing: 0.5px;
320
+ text-transform: uppercase;
321
+ }
322
+
323
+ .nc-storage-content {
324
+ padding: 8px 16px 16px 16px;
325
+ }
326
+
327
+ .nc-storage-content.hidden {
328
+ display: none;
329
+ }
330
+
331
+ .nc-storage-item {
332
+ display: flex;
333
+ align-items: center;
334
+ justify-content: space-between;
335
+ padding: 8px 12px;
336
+ margin-bottom: 4px;
337
+ background: rgba(255,255,255,0.05);
338
+ border-radius: 4px;
339
+ font-size: 11px;
340
+ }
341
+
342
+ .nc-storage-key {
343
+ color: #5cc8ff;
344
+ font-family: 'Fira Code', monospace;
345
+ flex: 1;
346
+ word-break: break-all;
347
+ }
348
+
349
+ .nc-storage-value {
350
+ color: #a0aec0;
351
+ margin-left: 8px;
352
+ flex-shrink: 0;
353
+ }
354
+
355
+ .nc-storage-delete {
356
+ margin-left: 8px;
357
+ padding: 2px 8px;
358
+ background: rgba(239, 68, 68, 0.2);
359
+ color: #ef4444;
360
+ border: none;
361
+ border-radius: 3px;
362
+ cursor: pointer;
363
+ font-size: 10px;
364
+ transition: background 0.2s;
365
+ flex-shrink: 0;
366
+ }
367
+
368
+ .nc-storage-delete:hover {
369
+ background: rgba(239, 68, 68, 0.4);
370
+ }
371
+
372
+ .nc-storage-empty {
373
+ color: #718096;
374
+ font-size: 11px;
375
+ font-style: italic;
376
+ padding: 12px;
377
+ }
378
+
379
+ .nc-storage-clear-all {
380
+ width: 100%;
381
+ padding: 8px;
382
+ margin-top: 8px;
383
+ background: rgba(239, 68, 68, 0.15);
384
+ color: #ef4444;
385
+ border: 1px solid rgba(239, 68, 68, 0.3);
386
+ border-radius: 4px;
387
+ cursor: pointer;
388
+ font-size: 11px;
389
+ transition: all 0.2s;
390
+ }
391
+
392
+ .nc-storage-clear-all:hover {
393
+ background: rgba(239, 68, 68, 0.25);
394
+ }
395
+
396
+ /* Cache Control Section */
397
+ .nc-cache-section {
398
+ border-top: 1px solid rgba(255,255,255,0.1);
399
+ }
400
+
401
+ .nc-cache-header {
402
+ padding: 12px 16px;
403
+ display: flex;
404
+ align-items: center;
405
+ gap: 8px;
406
+ cursor: pointer;
407
+ background: rgba(255,255,255,0.03);
408
+ transition: background 0.2s;
409
+ }
410
+
411
+ .nc-cache-header:hover {
412
+ background: rgba(255,255,255,0.06);
413
+ }
414
+
415
+ .nc-cache-toggle {
416
+ font-size: 10px;
417
+ color: #5cc8ff;
418
+ transition: transform 0.2s;
419
+ flex-shrink: 0;
420
+ }
421
+
422
+ .nc-cache-toggle.collapsed {
423
+ transform: rotate(-90deg);
424
+ }
425
+
426
+ .nc-cache-title {
427
+ color: #fff;
428
+ font-size: 11px;
429
+ font-weight: 600;
430
+ letter-spacing: 0.5px;
431
+ text-transform: uppercase;
432
+ }
433
+
434
+ .nc-cache-content {
435
+ padding: 8px 16px 16px 16px;
436
+ }
437
+
438
+ .nc-cache-content.hidden {
439
+ display: none;
440
+ }
441
+
442
+ .nc-cache-button {
443
+ width: 100%;
444
+ padding: 10px;
445
+ margin-bottom: 8px;
446
+ background: rgba(92, 200, 255, 0.15);
447
+ color: #5cc8ff;
448
+ border: 1px solid rgba(92, 200, 255, 0.3);
449
+ border-radius: 4px;
450
+ cursor: pointer;
451
+ font-size: 11px;
452
+ transition: all 0.2s;
453
+ display: flex;
454
+ align-items: center;
455
+ justify-content: center;
456
+ gap: 8px;
457
+ }
458
+
459
+ .nc-cache-button:hover {
460
+ background: rgba(92, 200, 255, 0.25);
461
+ }
462
+
463
+ .nc-cache-button.danger {
464
+ background: rgba(239, 68, 68, 0.15);
465
+ color: #ef4444;
466
+ border-color: rgba(239, 68, 68, 0.3);
467
+ }
468
+
469
+ .nc-cache-button.danger:hover {
470
+ background: rgba(239, 68, 68, 0.25);
471
+ }
472
+
473
+ .nc-cache-info {
474
+ color: #718096;
475
+ font-size: 10px;
476
+ padding: 8px 12px;
477
+ background: rgba(255,255,255,0.03);
478
+ border-radius: 4px;
479
+ margin-bottom: 8px;
480
+ }
481
+ `;
482
+ document.head.appendChild(style);
483
+ }
484
+
485
+ /**
486
+ * Create the panel
487
+ */
488
+ private createPanel(): void {
489
+ // Create tab
490
+ const tab = document.createElement('div');
491
+ tab.className = 'nc-outline-tab';
492
+ tab.textContent = 'OUTLINE';
493
+ tab.addEventListener('click', () => this.togglePanel());
494
+ document.body.appendChild(tab);
495
+ this.tab = tab;
496
+
497
+ // Create panel
498
+ const panel = document.createElement('div');
499
+ panel.className = 'nc-outline-panel closed';
500
+
501
+ const content = document.createElement('div');
502
+ content.className = 'nc-outline-content';
503
+
504
+ // Header
505
+ const header = document.createElement('div');
506
+ header.className = 'nc-outline-header';
507
+ header.innerHTML = `
508
+ <div class="nc-outline-title">Page Outline</div>
509
+ <div class="nc-outline-close">✕</div>
510
+ `;
511
+
512
+ header.querySelector('.nc-outline-close')?.addEventListener('click', () => this.togglePanel());
513
+
514
+ content.appendChild(header);
515
+
516
+ // Body with tree
517
+ const body = document.createElement('div');
518
+ body.className = 'nc-outline-body';
519
+
520
+ // Tree container
521
+ const tree = document.createElement('div');
522
+ tree.className = 'nc-outline-tree';
523
+ body.appendChild(tree);
524
+
525
+ // Cache Control Section
526
+ const cacheSection = this.createCacheSection();
527
+ body.appendChild(cacheSection);
528
+
529
+ // Session Storage Section
530
+ const sessionStorageSection = this.createStorageSection('session');
531
+ body.appendChild(sessionStorageSection);
532
+
533
+ // Local Storage Section
534
+ const localStorageSection = this.createStorageSection('local');
535
+ body.appendChild(localStorageSection);
536
+
537
+ content.appendChild(body);
538
+ panel.appendChild(content);
539
+ document.body.appendChild(panel);
540
+
541
+ this.panel = panel;
542
+ this.refreshTree();
543
+ this.applyVisibility();
544
+ }
545
+
546
+ public setVisible(visible: boolean): void {
547
+ this.isVisible = visible;
548
+
549
+ if (!visible && this.isOpen) {
550
+ this.isOpen = false;
551
+ this.panel?.classList.add('closed');
552
+ }
553
+
554
+ this.applyVisibility();
555
+ }
556
+
557
+ private applyVisibility(): void {
558
+ if (!this.panel || !this.tab) {
559
+ return;
560
+ }
561
+
562
+ if (!this.isVisible) {
563
+ this.panel.style.display = 'none';
564
+ this.tab.style.display = 'none';
565
+ return;
566
+ }
567
+
568
+ this.panel.style.display = '';
569
+ this.tab.style.display = this.isOpen ? 'none' : '';
570
+ }
571
+
572
+ /**
573
+ * Create cache control section
574
+ */
575
+ private createCacheSection(): HTMLElement {
576
+ const section = document.createElement('div');
577
+ section.className = 'nc-cache-section nc-storage-section';
578
+
579
+ // Header
580
+ const header = document.createElement('div');
581
+ header.className = 'nc-cache-header';
582
+ header.innerHTML = `
583
+ <span class="nc-cache-toggle collapsed">▼</span>
584
+ <span class="nc-cache-title">Cache Control</span>
585
+ `;
586
+
587
+ // Content
588
+ const content = document.createElement('div');
589
+ content.className = 'nc-cache-content hidden';
590
+ content.innerHTML = `
591
+ <div class="nc-cache-info">
592
+ Clear browser cache to see latest CSS/JS changes
593
+ </div>
594
+ `;
595
+
596
+ // Hard Refresh Button
597
+ const hardRefreshBtn = document.createElement('button');
598
+ hardRefreshBtn.className = 'nc-cache-button';
599
+ hardRefreshBtn.innerHTML = `
600
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
601
+ <path d="M21.5 2v6h-6M2.5 22v-6h6M2 11.5a10 10 0 0 1 18.8-4.3M22 12.5a10 10 0 0 1-18.8 4.2"/>
602
+ </svg>
603
+ Hard Refresh (Ctrl+Shift+R)
604
+ `;
605
+ hardRefreshBtn.addEventListener('click', () => {
606
+ location.reload();
607
+ });
608
+ content.appendChild(hardRefreshBtn);
609
+
610
+ // Clear Cache & Reload Button
611
+ const clearCacheBtn = document.createElement('button');
612
+ clearCacheBtn.className = 'nc-cache-button';
613
+ clearCacheBtn.innerHTML = `
614
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
615
+ <path d="M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
616
+ </svg>
617
+ Clear Cache & Reload
618
+ `;
619
+ clearCacheBtn.addEventListener('click', async () => {
620
+ if ('caches' in window) {
621
+ const cacheNames = await caches.keys();
622
+ await Promise.all(cacheNames.map(name => caches.delete(name)));
623
+ }
624
+ location.reload();
625
+ });
626
+ content.appendChild(clearCacheBtn);
627
+
628
+ // Clear All Data Button
629
+ const clearAllBtn = document.createElement('button');
630
+ clearAllBtn.className = 'nc-cache-button danger';
631
+ clearAllBtn.innerHTML = `
632
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
633
+ <path d="M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2m3 0v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6h14z"/>
634
+ <line x1="10" y1="11" x2="10" y2="17"/>
635
+ <line x1="14" y1="11" x2="14" y2="17"/>
636
+ </svg>
637
+ Clear All Data & Reload
638
+ `;
639
+ clearAllBtn.addEventListener('click', async () => {
640
+ if (confirm('Clear all cache, storage, and reload? This will reset everything.')) {
641
+ // Clear cache
642
+ if ('caches' in window) {
643
+ const cacheNames = await caches.keys();
644
+ await Promise.all(cacheNames.map(name => caches.delete(name)));
645
+ }
646
+ // Clear storage
647
+ sessionStorage.clear();
648
+ localStorage.clear();
649
+ // Reload
650
+ location.reload();
651
+ }
652
+ });
653
+ content.appendChild(clearAllBtn);
654
+
655
+ // Toggle functionality
656
+ header.addEventListener('click', () => {
657
+ const toggle = header.querySelector('.nc-cache-toggle');
658
+ if (content.classList.contains('hidden')) {
659
+ content.classList.remove('hidden');
660
+ toggle?.classList.remove('collapsed');
661
+ } else {
662
+ content.classList.add('hidden');
663
+ toggle?.classList.add('collapsed');
664
+ }
665
+ });
666
+
667
+ section.appendChild(header);
668
+ section.appendChild(content);
669
+
670
+ return section;
671
+ }
672
+
673
+ /**
674
+ * Create storage management section
675
+ */
676
+ private createStorageSection(type: 'session' | 'local'): HTMLElement {
677
+ const section = document.createElement('div');
678
+ section.className = 'nc-storage-section';
679
+
680
+ const storageObj = type === 'session' ? sessionStorage : localStorage;
681
+ const title = type === 'session' ? 'Session Storage' : 'Local Storage';
682
+ const contentId = type === 'session' ? 'storageContent' : 'localStorageContent';
683
+
684
+ // Header
685
+ const header = document.createElement('div');
686
+ header.className = 'nc-storage-header';
687
+ header.innerHTML = `
688
+ <span class="nc-storage-toggle collapsed">▼</span>
689
+ <span class="nc-storage-title">${title}</span>
690
+ `;
691
+
692
+ // Content
693
+ const content = document.createElement('div');
694
+ content.className = 'nc-storage-content hidden';
695
+ content.id = contentId;
696
+
697
+ this.refreshStorageContent(content, storageObj);
698
+
699
+ // Toggle functionality
700
+ header.addEventListener('click', () => {
701
+ const toggle = header.querySelector('.nc-storage-toggle');
702
+ if (content.classList.contains('hidden')) {
703
+ content.classList.remove('hidden');
704
+ toggle?.classList.remove('collapsed');
705
+ } else {
706
+ content.classList.add('hidden');
707
+ toggle?.classList.add('collapsed');
708
+ }
709
+ });
710
+
711
+ section.appendChild(header);
712
+ section.appendChild(content);
713
+
714
+ return section;
715
+ }
716
+
717
+ /**
718
+ * Refresh storage content
719
+ */
720
+ private refreshStorageContent(container: HTMLElement, storageObj: Storage): void {
721
+ container.innerHTML = '';
722
+
723
+ const items: Array<{key: string, value: string}> = [];
724
+ for (let i = 0; i < storageObj.length; i++) {
725
+ const key = storageObj.key(i);
726
+ if (key) {
727
+ items.push({
728
+ key,
729
+ value: storageObj.getItem(key) || ''
730
+ });
731
+ }
732
+ }
733
+
734
+ if (items.length === 0) {
735
+ container.innerHTML = '<div class="nc-storage-empty">No items in storage</div>';
736
+ return;
737
+ }
738
+
739
+ items.forEach(({key, value}) => {
740
+ const item = document.createElement('div');
741
+ item.className = 'nc-storage-item';
742
+
743
+ const keySpan = document.createElement('span');
744
+ keySpan.className = 'nc-storage-key';
745
+ keySpan.textContent = key;
746
+
747
+ const valueSpan = document.createElement('span');
748
+ valueSpan.className = 'nc-storage-value';
749
+ valueSpan.textContent = value.length > 20 ? value.substring(0, 20) + '...' : value;
750
+ valueSpan.title = value; // Show full value on hover
751
+
752
+ const deleteBtn = document.createElement('button');
753
+ deleteBtn.className = 'nc-storage-delete';
754
+ deleteBtn.textContent = 'Delete';
755
+ deleteBtn.addEventListener('click', () => {
756
+ storageObj.removeItem(key);
757
+ this.refreshStorageContent(container, storageObj);
758
+ });
759
+
760
+ item.appendChild(keySpan);
761
+ item.appendChild(valueSpan);
762
+ item.appendChild(deleteBtn);
763
+ container.appendChild(item);
764
+ });
765
+
766
+ // Add clear all button
767
+ if (items.length > 0) {
768
+ const clearAllBtn = document.createElement('button');
769
+ clearAllBtn.className = 'nc-storage-clear-all';
770
+ clearAllBtn.textContent = `Clear All (${items.length} items)`;
771
+ clearAllBtn.addEventListener('click', () => {
772
+ if (confirm('Clear all items from this storage?')) {
773
+ storageObj.clear();
774
+ this.refreshStorageContent(container, storageObj);
775
+ }
776
+ });
777
+ container.appendChild(clearAllBtn);
778
+ }
779
+ }
780
+
781
+ /**
782
+ * Toggle panel open/closed
783
+ */
784
+ private togglePanel(): void {
785
+ if (!this.isVisible) {
786
+ return;
787
+ }
788
+
789
+ this.isOpen = !this.isOpen;
790
+ const tab = this.tab;
791
+
792
+ if (this.isOpen) {
793
+ this.panel?.classList.remove('closed');
794
+ tab?.classList.add('hidden');
795
+ this.refreshTree();
796
+
797
+ // Refresh storage content when opening
798
+ const storageContent = this.panel?.querySelector('#storageContent');
799
+ if (storageContent) {
800
+ this.refreshStorageContent(storageContent as HTMLElement, sessionStorage);
801
+ }
802
+ const localStorageContent = this.panel?.querySelector('#localStorageContent');
803
+ if (localStorageContent) {
804
+ this.refreshStorageContent(localStorageContent as HTMLElement, localStorage);
805
+ }
806
+
807
+ // Request current element from editor and expand to it
808
+ setTimeout(() => {
809
+ const event = new CustomEvent('nc-request-current-element');
810
+ document.dispatchEvent(event);
811
+ }, 100); // Small delay to ensure tree is rendered
812
+ } else {
813
+ this.panel?.classList.add('closed');
814
+ tab?.classList.remove('hidden');
815
+ }
816
+
817
+ this.applyVisibility();
818
+ }
819
+
820
+ /**
821
+ * Refresh the tree view
822
+ */
823
+ private refreshTree(): void {
824
+ const tree = this.panel?.querySelector('.nc-outline-tree');
825
+ if (!tree) return;
826
+
827
+ tree.innerHTML = '';
828
+
829
+ // Build tree starting from body itself to show complete DOM
830
+ this.buildTree(document.body, tree as HTMLElement, -1);
831
+ }
832
+
833
+ /**
834
+ * Check if element is a dev tool element that should be excluded from tree
835
+ */
836
+ private isDevToolElement(element: Element): boolean {
837
+ const tagName = (element as HTMLElement).tagName?.toLowerCase();
838
+ const classList = (element as HTMLElement).classList;
839
+ const id = (element as HTMLElement).id?.toLowerCase();
840
+
841
+ // Check tag names
842
+ if (tagName?.startsWith('nc-dev') ||
843
+ tagName?.startsWith('nc-outline') ||
844
+ tagName?.startsWith('nc-editor') ||
845
+ tagName === 'hmr-indicator' ||
846
+ tagName === 'nativecore-denc-indicator') {
847
+ return true;
848
+ }
849
+
850
+ // Check class names
851
+ if (classList?.contains('nc-denc-overlay') ||
852
+ classList?.contains('nc-editor-overlay') ||
853
+ classList?.contains('nc-editor-modal') ||
854
+ classList?.contains('nc-context-menu') ||
855
+ classList?.contains('nc-component-overlay') ||
856
+ classList?.contains('nc-outline-panel') ||
857
+ classList?.contains('nc-outline-tab') ||
858
+ classList?.contains('hmr-indicator') ||
859
+ classList?.contains('nativecore-denc-indicator')) {
860
+ return true;
861
+ }
862
+
863
+ // Check IDs
864
+ if (id?.includes('nc-dev') ||
865
+ id?.includes('nc-editor') ||
866
+ id?.includes('nc-outline') ||
867
+ id?.includes('hmr-') ||
868
+ id?.includes('nativecore-dev') ||
869
+ id?.includes('denc-indicator')) {
870
+ return true;
871
+ }
872
+
873
+ // Check data attributes
874
+ if ((element as HTMLElement).hasAttribute('data-nc-dev') ||
875
+ (element as HTMLElement).hasAttribute('data-denc-tool')) {
876
+ return true;
877
+ }
878
+
879
+ return false;
880
+ }
881
+
882
+ /**
883
+ * Build tree recursively - collapsible nodes
884
+ */
885
+ private buildTree(element: Element, container: HTMLElement, level: number = 0): void {
886
+ // Skip dev tools elements
887
+ if (this.isDevToolElement(element)) {
888
+ return;
889
+ }
890
+
891
+ // Skip script, style, noscript, link, meta
892
+ const tagName = element.tagName.toLowerCase();
893
+ if (['script', 'style', 'noscript', 'link', 'meta'].includes(tagName)) {
894
+ return;
895
+ }
896
+
897
+ const isCustom = this.isCustomComponent(element as HTMLElement);
898
+ const hasChildren = Array.from(element.children).some(child => {
899
+ const childTag = child.tagName.toLowerCase();
900
+ return !['script', 'style', 'noscript', 'link', 'meta'].includes(childTag) &&
901
+ !this.isDevToolElement(child);
902
+ });
903
+
904
+ // Create node wrapper
905
+ const wrapper = document.createElement('div');
906
+
907
+ // Create node row
908
+ const nodeDiv = document.createElement('div');
909
+ nodeDiv.className = `nc-tree-node ${isCustom ? 'custom-component' : ''}`;
910
+ nodeDiv.style.paddingLeft = `${level * 14 + 8}px`;
911
+
912
+ // Toggle arrow for nodes with children
913
+ let toggle: HTMLSpanElement | null = null;
914
+ if (hasChildren) {
915
+ toggle = document.createElement('span');
916
+ toggle.className = 'nc-tree-toggle';
917
+ toggle.textContent = '▼';
918
+ nodeDiv.appendChild(toggle);
919
+ } else {
920
+ // Spacer for alignment
921
+ const spacer = document.createElement('span');
922
+ spacer.style.width = '12px';
923
+ spacer.style.display = 'inline-block';
924
+ spacer.style.flexShrink = '0';
925
+ nodeDiv.appendChild(spacer);
926
+ }
927
+
928
+ // Content wrapper
929
+ const content = document.createElement('div');
930
+ content.className = 'nc-tree-content';
931
+
932
+ // Build content: <tag> #id .class
933
+ const id = element.id ? `#${element.id}` : '';
934
+ const firstClass = element.classList.length > 0 ? `.${element.classList[0]}` : '';
935
+
936
+ // Only add gear icon for custom components
937
+ const gearHtml = isCustom ? `
938
+ <svg class="nc-tree-gear" viewBox="0 0 24 24">
939
+ <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"/>
940
+ </svg>
941
+ ` : '';
942
+
943
+ content.innerHTML = `
944
+ <span class="nc-tree-tag ${isCustom ? 'custom' : ''}">&lt;${tagName}&gt;</span>
945
+ <span class="nc-tree-attr">${id}${firstClass}</span>
946
+ ${gearHtml}
947
+ `;
948
+
949
+ // Gear icon click (only for custom components)
950
+ if (isCustom) {
951
+ const gear = content.querySelector('.nc-tree-gear');
952
+ gear?.addEventListener('click', (e) => {
953
+ e.stopPropagation();
954
+ this.onEdit(element as HTMLElement, true); // true = from outline tree
955
+ });
956
+ }
957
+
958
+ nodeDiv.appendChild(content);
959
+ wrapper.appendChild(nodeDiv);
960
+
961
+ // Children container
962
+ let childrenContainer: HTMLDivElement | null = null;
963
+ if (hasChildren) {
964
+ childrenContainer = document.createElement('div');
965
+ childrenContainer.className = 'nc-tree-children hidden';
966
+
967
+ // Build children recursively
968
+ Array.from(element.children).forEach(child => {
969
+ this.buildTree(child, childrenContainer!, level + 1);
970
+ });
971
+
972
+ wrapper.appendChild(childrenContainer);
973
+
974
+ // Start collapsed by default
975
+ if (toggle) {
976
+ toggle.classList.add('collapsed');
977
+
978
+ // Toggle functionality - clicking anywhere on the row expands/collapses
979
+ nodeDiv.addEventListener('click', (e) => {
980
+ // Don't toggle if clicking the gear icon
981
+ if ((e.target as HTMLElement).closest('.nc-tree-gear')) {
982
+ return;
983
+ }
984
+
985
+ e.stopPropagation();
986
+ const isCollapsed = toggle.classList.contains('collapsed');
987
+ if (isCollapsed) {
988
+ toggle.classList.remove('collapsed');
989
+ childrenContainer!.classList.remove('hidden');
990
+ // Expand panel width if needed for deep nesting
991
+ this.adjustPanelWidth();
992
+ } else {
993
+ toggle.classList.add('collapsed');
994
+ childrenContainer!.classList.add('hidden');
995
+ // Recalculate optimal width
996
+ this.adjustPanelWidth();
997
+ }
998
+ });
999
+ }
1000
+ }
1001
+
1002
+ // Node click to highlight on page (for nodes without children)
1003
+ if (!hasChildren) {
1004
+ content.addEventListener('click', (e) => {
1005
+ e.stopPropagation();
1006
+
1007
+ // If this is a regular element (not custom component), close the editor panel
1008
+ if (!isCustom) {
1009
+ // Dispatch event to close editor
1010
+ document.dispatchEvent(new CustomEvent('nc-close-editor'));
1011
+ }
1012
+
1013
+ // Highlight element on page
1014
+ (element as HTMLElement).style.outline = '2px solid #5cc8ff';
1015
+ (element as HTMLElement).style.outlineOffset = '2px';
1016
+ setTimeout(() => {
1017
+ (element as HTMLElement).style.outline = '';
1018
+ (element as HTMLElement).style.outlineOffset = '';
1019
+ }, 2000);
1020
+ });
1021
+ }
1022
+
1023
+ container.appendChild(wrapper);
1024
+ }
1025
+
1026
+ /**
1027
+ * Adjust panel width dynamically based on visible content
1028
+ */
1029
+ private adjustPanelWidth(): void {
1030
+ if (!this.panel) return;
1031
+
1032
+ // Find the maximum depth of visible (non-hidden) nodes
1033
+ const tree = this.panel.querySelector('.nc-outline-tree');
1034
+ if (!tree) return;
1035
+
1036
+ let maxDepth = 0;
1037
+ const visibleNodes = tree.querySelectorAll('.nc-tree-node');
1038
+
1039
+ visibleNodes.forEach(node => {
1040
+ const parent = node.parentElement;
1041
+ // Check if any ancestor has 'hidden' class
1042
+ let current: HTMLElement | null = parent as HTMLElement;
1043
+ let isVisible = true;
1044
+
1045
+ while (current && current !== tree) {
1046
+ if (current.classList?.contains('hidden')) {
1047
+ isVisible = false;
1048
+ break;
1049
+ }
1050
+ current = current.parentElement;
1051
+ }
1052
+
1053
+ if (isVisible) {
1054
+ const paddingLeft = parseInt((node as HTMLElement).style.paddingLeft || '0');
1055
+ maxDepth = Math.max(maxDepth, paddingLeft);
1056
+ }
1057
+ });
1058
+
1059
+ // Calculate optimal width: base + depth padding + buffer for gear icon
1060
+ const optimalWidth = Math.max(280, Math.min(600, 260 + maxDepth + 100));
1061
+ this.panel.style.width = `${optimalWidth}px`;
1062
+ }
1063
+
1064
+ /**
1065
+ * Check if element is a custom component
1066
+ */
1067
+ private isCustomComponent(element: HTMLElement): boolean {
1068
+ const tagName = element.tagName.toLowerCase();
1069
+ return tagName.includes('-') &&
1070
+ !tagName.startsWith('nc-dev') &&
1071
+ !tagName.startsWith('ion-') &&
1072
+ !tagName.startsWith('mat-');
1073
+ }
1074
+
1075
+ /**
1076
+ * Setup mutation observer to refresh tree on DOM changes
1077
+ */
1078
+ private setupMutationObserver(): void {
1079
+ this.mutationObserver = new MutationObserver((mutations) => {
1080
+ // Ignore mutations inside the outline panel itself
1081
+ const isOutlineChange = mutations.some(mutation => {
1082
+ const target = mutation.target as HTMLElement;
1083
+ return target.closest('.nc-outline-panel') ||
1084
+ target.classList?.contains('nc-outline-panel') ||
1085
+ target.classList?.contains('nc-outline-tab');
1086
+ });
1087
+
1088
+ if (isOutlineChange) return;
1089
+
1090
+ // Throttle refreshes
1091
+ if (this.isOpen && !this.refreshTimeout) {
1092
+ this.refreshTimeout = window.setTimeout(() => {
1093
+ this.refreshTree();
1094
+ this.refreshTimeout = null;
1095
+ }, 500);
1096
+ }
1097
+ });
1098
+
1099
+ this.mutationObserver.observe(document.body, {
1100
+ childList: true,
1101
+ subtree: true,
1102
+ attributes: true,
1103
+ attributeFilter: ['class', 'id']
1104
+ });
1105
+ }
1106
+
1107
+ /**
1108
+ * Expand tree to show a specific element
1109
+ */
1110
+ /**
1111
+ * Expand the outline tree to show a specific element
1112
+ * Public method to be called when gear icon is clicked
1113
+ */
1114
+ public expandToElement(targetElement: HTMLElement): void {
1115
+ console.log('[OutlinePanel] Expanding to element:', targetElement.tagName);
1116
+
1117
+ // Find all parent elements up to body
1118
+ const parents: Element[] = [];
1119
+ let current: Element | null = targetElement;
1120
+
1121
+ while (current && current !== document.body) {
1122
+ parents.unshift(current);
1123
+ current = current.parentElement;
1124
+ }
1125
+
1126
+ console.log('[OutlinePanel] Parents to expand:', parents.map(p => p.tagName));
1127
+
1128
+ // Build a path of indices to identify the exact element
1129
+ const pathIndices: number[] = [];
1130
+ let elem: Element | null = targetElement;
1131
+ while (elem && elem !== document.body) {
1132
+ const parent = elem.parentElement;
1133
+ if (parent) {
1134
+ const siblings = Array.from(parent.children).filter(child =>
1135
+ child.tagName === elem!.tagName
1136
+ );
1137
+ pathIndices.unshift(siblings.indexOf(elem));
1138
+ }
1139
+ elem = parent;
1140
+ }
1141
+
1142
+ console.log('[OutlinePanel] Path indices:', pathIndices);
1143
+
1144
+ // Expand each parent node in the tree
1145
+ parents.forEach((parent, _parentIndex) => {
1146
+ // Find the tree node for this element
1147
+ const treeNodes = this.panel?.querySelectorAll('.nc-tree-node');
1148
+ if (!treeNodes) {
1149
+ console.log('[OutlinePanel] No tree nodes found');
1150
+ return;
1151
+ }
1152
+
1153
+ let matchCount = 0;
1154
+ treeNodes.forEach(node => {
1155
+ const content = node.querySelector('.nc-tree-content');
1156
+ if (!content) return;
1157
+
1158
+ const tagSpan = content.querySelector('.nc-tree-tag');
1159
+ if (!tagSpan) return;
1160
+
1161
+ const tagName = tagSpan.textContent?.replace(/[<>]/g, '') || '';
1162
+ const nodeId = content.querySelector('.nc-tree-attr')?.textContent?.match(/#([\w-]+)/)?.[1];
1163
+ const nodeClass = content.querySelector('.nc-tree-attr')?.textContent?.match(/\.([\w-]+)/)?.[1];
1164
+
1165
+ // Check if this node matches the parent element
1166
+ const tagMatches = parent.tagName.toLowerCase() === tagName;
1167
+ const idMatches = !nodeId || !parent.id || parent.id === nodeId;
1168
+ const classMatches = !nodeClass || parent.classList.contains(nodeClass);
1169
+
1170
+ const matches = tagMatches && idMatches && classMatches;
1171
+
1172
+ if (matches) {
1173
+ matchCount++;
1174
+ console.log(`[OutlinePanel] Matched node for ${parent.tagName}:`, {tagName, nodeId, nodeClass});
1175
+
1176
+ // Expand this node's children
1177
+ const toggle = node.querySelector('.nc-tree-toggle');
1178
+ const wrapper = node.parentElement;
1179
+ const childrenContainer = wrapper?.querySelector('.nc-tree-children');
1180
+
1181
+ if (toggle && childrenContainer) {
1182
+ toggle.classList.remove('collapsed');
1183
+ childrenContainer.classList.remove('hidden');
1184
+ console.log('[OutlinePanel] Expanded node');
1185
+ }
1186
+
1187
+ // If this is the target element, highlight and scroll to it
1188
+ if (parent === targetElement) {
1189
+ console.log('[OutlinePanel] Highlighting target element');
1190
+ (node as HTMLElement).style.backgroundColor = 'rgba(92, 200, 255, 0.2)';
1191
+ node.scrollIntoView({ behavior: 'smooth', block: 'center' });
1192
+
1193
+ // Remove highlight after a moment
1194
+ setTimeout(() => {
1195
+ (node as HTMLElement).style.backgroundColor = '';
1196
+ }, 2000);
1197
+ }
1198
+ }
1199
+ });
1200
+
1201
+ if (matchCount === 0) {
1202
+ console.log(`[OutlinePanel] No matches found for ${parent.tagName}`);
1203
+ }
1204
+ });
1205
+
1206
+ // Adjust panel width after expanding to accommodate the new depth
1207
+ this.adjustPanelWidth();
1208
+ }
1209
+
1210
+ /**
1211
+ * Show the outline panel and expand to a specific element
1212
+ * Public method to be called when gear icon is clicked
1213
+ */
1214
+ public showAndExpandTo(element: HTMLElement): void {
1215
+ if (!this.isVisible) {
1216
+ return;
1217
+ }
1218
+
1219
+ // Open the panel if it's closed
1220
+ if (!this.isOpen) {
1221
+ this.togglePanel();
1222
+ // Wait longer for panel animation and tree rendering
1223
+ setTimeout(() => {
1224
+ this.expandToElement(element);
1225
+ }, 250);
1226
+ } else {
1227
+ // Panel is already open, refresh tree and expand
1228
+ this.refreshTree();
1229
+ setTimeout(() => {
1230
+ this.expandToElement(element);
1231
+ }, 100);
1232
+ }
1233
+ }
1234
+
1235
+ /**
1236
+ * Destroy the panel
1237
+ */
1238
+ destroy(): void {
1239
+ if (this.mutationObserver) {
1240
+ this.mutationObserver.disconnect();
1241
+ }
1242
+ this.panel?.remove();
1243
+ this.tab?.remove();
1244
+ this.tab = null;
1245
+ document.getElementById('nativecore-outline-styles')?.remove();
1246
+ }
1247
+ }