slicejs-web-framework 3.2.3 → 3.3.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 (117) hide show
  1. package/.opencode/opencode.json +13 -13
  2. package/LICENSE +21 -21
  3. package/README.md +97 -174
  4. package/Slice/Components/Structural/ContextManager/ContextManager.js +369 -369
  5. package/Slice/Components/Structural/ContextManager/ContextManagerDebugger.js +420 -297
  6. package/Slice/Components/Structural/Controller/Controller.js +1131 -1131
  7. package/Slice/Components/Structural/Debugger/Debugger.html +72 -72
  8. package/Slice/Components/Structural/Debugger/Debugger.js +1497 -1547
  9. package/Slice/Components/Structural/EventManager/EventManager.js +338 -338
  10. package/Slice/Components/Structural/EventManager/EventManagerDebugger.js +476 -361
  11. package/Slice/Components/Structural/Logger/Log.js +10 -10
  12. package/Slice/Components/Structural/Logger/Logger.js +146 -146
  13. package/Slice/Components/Structural/Router/Router.js +752 -721
  14. package/Slice/Components/Structural/StylesManager/StylesManager.js +78 -78
  15. package/Slice/Components/Structural/StylesManager/ThemeManager/ThemeManager.js +84 -84
  16. package/Slice/Slice.js +542 -542
  17. package/Slice/tests/build-bundled-component-without-category.test.js +103 -103
  18. package/Slice/tests/build-js-only-visual-components.test.js +144 -144
  19. package/Slice/tests/bundle-v2-runtime-contract.test.js +728 -728
  20. package/Slice/tests/public-env-runtime-accessors.test.js +44 -44
  21. package/Slice/tests/router-loading-finally.test.js +68 -68
  22. package/api/index.js +286 -286
  23. package/api/middleware/securityMiddleware.js +252 -252
  24. package/api/tests/public-env-resolver.test.js +193 -193
  25. package/api/utils/publicEnvResolver.js +117 -117
  26. package/package.json +40 -38
  27. package/sliceConfig.schema.json +109 -109
  28. package/src/App/index.html +16 -22
  29. package/src/App/index.js +20 -23
  30. package/src/App/style.css +11 -40
  31. package/src/Components/AppComponents/AboutSection/AboutSection.css +9 -0
  32. package/src/Components/AppComponents/AboutSection/AboutSection.html +8 -0
  33. package/src/Components/AppComponents/AboutSection/AboutSection.js +12 -0
  34. package/src/Components/AppComponents/AppShell/AppShell.css +10 -0
  35. package/src/Components/AppComponents/AppShell/AppShell.html +4 -0
  36. package/src/Components/AppComponents/AppShell/AppShell.js +36 -0
  37. package/src/Components/AppComponents/HomeSection/HomeSection.css +20 -0
  38. package/src/Components/AppComponents/HomeSection/HomeSection.html +10 -0
  39. package/src/Components/AppComponents/HomeSection/HomeSection.js +19 -0
  40. package/src/Components/Service/FetchManager/FetchManager.js +133 -133
  41. package/src/Components/Service/IndexedDbManager/IndexedDbManager.js +141 -141
  42. package/src/Components/Service/LocalStorageManager/LocalStorageManager.js +45 -45
  43. package/src/Components/Visual/Button/Button.css +47 -47
  44. package/src/Components/Visual/Button/Button.html +5 -5
  45. package/src/Components/Visual/Button/Button.js +92 -92
  46. package/src/Components/Visual/Card/Card.css +68 -68
  47. package/src/Components/Visual/Card/Card.html +7 -7
  48. package/src/Components/Visual/Card/Card.js +107 -107
  49. package/src/Components/Visual/Checkbox/Checkbox.css +87 -87
  50. package/src/Components/Visual/Checkbox/Checkbox.html +8 -8
  51. package/src/Components/Visual/Checkbox/Checkbox.js +86 -86
  52. package/src/Components/Visual/CodeVisualizer/CodeVisualizer.css +129 -129
  53. package/src/Components/Visual/CodeVisualizer/CodeVisualizer.html +3 -3
  54. package/src/Components/Visual/CodeVisualizer/CodeVisualizer.js +262 -262
  55. package/src/Components/Visual/Details/Details.css +70 -70
  56. package/src/Components/Visual/Details/Details.html +9 -9
  57. package/src/Components/Visual/Details/Details.js +76 -76
  58. package/src/Components/Visual/DropDown/DropDown.css +60 -60
  59. package/src/Components/Visual/DropDown/DropDown.html +5 -5
  60. package/src/Components/Visual/DropDown/DropDown.js +63 -63
  61. package/src/Components/Visual/Grid/Grid.css +7 -7
  62. package/src/Components/Visual/Grid/Grid.html +1 -1
  63. package/src/Components/Visual/Grid/Grid.js +57 -57
  64. package/src/Components/Visual/Icon/Icon.css +510 -510
  65. package/src/Components/Visual/Icon/Icon.js +89 -89
  66. package/src/Components/Visual/Icon/slc.json +554 -554
  67. package/src/Components/Visual/Icon/slc.styl +507 -507
  68. package/src/Components/Visual/Icon/slc.svg +1485 -1485
  69. package/src/Components/Visual/Icon/slc.symbol.svg +1058 -1058
  70. package/src/Components/Visual/Input/Input.css +91 -91
  71. package/src/Components/Visual/Input/Input.html +4 -4
  72. package/src/Components/Visual/Input/Input.js +215 -215
  73. package/src/Components/Visual/Layout/Layout.js +49 -49
  74. package/src/Components/Visual/Link/Link.css +8 -8
  75. package/src/Components/Visual/Link/Link.html +1 -1
  76. package/src/Components/Visual/Link/Link.js +63 -63
  77. package/src/Components/Visual/Loading/Loading.css +56 -56
  78. package/src/Components/Visual/Loading/Loading.html +83 -83
  79. package/src/Components/Visual/Loading/Loading.js +38 -38
  80. package/src/Components/Visual/MultiRoute/MultiRoute.js +100 -93
  81. package/src/Components/Visual/Navbar/Navbar.css +115 -115
  82. package/src/Components/Visual/Navbar/Navbar.html +44 -44
  83. package/src/Components/Visual/Navbar/Navbar.js +141 -141
  84. package/src/Components/Visual/NotFound/NotFound.css +116 -116
  85. package/src/Components/Visual/NotFound/NotFound.html +23 -23
  86. package/src/Components/Visual/NotFound/NotFound.js +16 -16
  87. package/src/Components/Visual/Route/Route.js +93 -93
  88. package/src/Components/Visual/Select/Select.css +84 -84
  89. package/src/Components/Visual/Select/Select.html +8 -8
  90. package/src/Components/Visual/Select/Select.js +195 -195
  91. package/src/Components/Visual/Switch/Switch.css +76 -76
  92. package/src/Components/Visual/Switch/Switch.html +8 -8
  93. package/src/Components/Visual/Switch/Switch.js +102 -102
  94. package/src/Components/Visual/TreeItem/TreeItem.css +36 -36
  95. package/src/Components/Visual/TreeItem/TreeItem.html +1 -1
  96. package/src/Components/Visual/TreeItem/TreeItem.js +126 -126
  97. package/src/Components/Visual/TreeView/TreeView.css +8 -8
  98. package/src/Components/Visual/TreeView/TreeView.html +1 -1
  99. package/src/Components/Visual/TreeView/TreeView.js +48 -48
  100. package/src/Components/components.js +15 -27
  101. package/src/Styles/sliceStyles.css +34 -34
  102. package/src/Themes/Dark.css +42 -42
  103. package/src/Themes/Light.css +31 -31
  104. package/src/Themes/Slice.css +47 -47
  105. package/src/routes.js +9 -15
  106. package/src/sliceConfig.json +74 -73
  107. package/types/index.d.ts +207 -207
  108. package/Slice/Components/Structural/Debugger/Debugger.css +0 -620
  109. package/src/Components/AppComponents/HomePage/HomePage.css +0 -201
  110. package/src/Components/AppComponents/HomePage/HomePage.html +0 -37
  111. package/src/Components/AppComponents/HomePage/HomePage.js +0 -210
  112. package/src/Components/AppComponents/Playground/Playground.css +0 -12
  113. package/src/Components/AppComponents/Playground/Playground.html +0 -0
  114. package/src/Components/AppComponents/Playground/Playground.js +0 -111
  115. package/src/images/Slice.js-logo.png +0 -0
  116. package/src/images/im2/Slice.js-logo.png +0 -0
  117. package/src/testing.js +0 -888
@@ -1,1548 +1,1498 @@
1
- // ✅ VERSIÓN ANTI-INTERFERENCIA - Aislada del Router y con debugging
2
-
3
- /**
4
- * Runtime UI debugger for Slice components.
5
- */
6
- export default class Debugger extends HTMLElement {
7
- constructor() {
8
- super();
9
- this.toggleClick = slice.debuggerConfig.click;
10
- this.toggle = 'click';
11
- this.selectedComponentSliceId = null;
12
- this.isActive = false;
13
- this.activeTab = 'props';
14
- this.currentComponent = null;
15
- this.componentProps = {};
16
- this.currentEditingProp = null;
17
- this.currentEditingType = null;
18
-
19
- // ✅ Flag para prevenir interferencias externas
20
- this.isDebuggerInput = false;
21
- }
22
-
23
- /**
24
- * Load debugger UI and enable interactions.
25
- * @returns {Promise<boolean>}
26
- */
27
- async enableDebugMode() {
28
- //const html = await slice.controller.fetchText('Debugger', 'html', 'Structural');
29
- //const css = await slice.controller.fetchText('Debugger', 'css', 'Structural');
30
-
31
- const html = productionOnlyHtml();
32
- const css = productionOnlyCSS();
33
-
34
- this.innerHTML = html;
35
- slice.stylesManager.registerComponentStyles('Debugger', css);
36
-
37
- this.setupElements();
38
- this.setupEventListeners();
39
- this.makeDraggable();
40
-
41
- slice.logger.logInfo('Debugger', 'Advanced Debug mode enabled');
42
- return true;
43
- }
44
-
45
- /**
46
- * Cache UI elements.
47
- * @returns {void}
48
- */
49
- setupElements() {
50
- this.debuggerContainer = this.querySelector('#debugger-container');
51
- this.closeDebugger = this.querySelector('#close-debugger');
52
- this.propsContainer = this.querySelector('.props-container');
53
- this.infoContainer = this.querySelector('.info-list');
54
- this.editorModal = this.querySelector('#editor-modal');
55
- this.propertyEditor = this.querySelector('#property-editor');
56
- this.modalTitle = this.querySelector('#modal-title');
57
- this.validationMessage = this.querySelector('.validation-message');
58
-
59
- // Header elements
60
- this.componentName = this.querySelector('.component-name');
61
- this.componentId = this.querySelector('.component-id');
62
- }
63
-
64
- /**
65
- * Bind UI event listeners.
66
- * @returns {void}
67
- */
68
- setupEventListeners() {
69
- // Tab navigation
70
- this.querySelectorAll('.tab-btn').forEach(btn => {
71
- btn.addEventListener('click', (e) => {
72
- this.switchTab(e.target.dataset.tab);
73
- });
74
- });
75
-
76
- // Close and minimize
77
- this.closeDebugger.addEventListener('click', () => {
78
- this.hide();
79
- this.isActive = false;
80
- });
81
-
82
- // Modal events
83
- this.querySelector('#modal-close').addEventListener('click', () => {
84
- this.closeModal();
85
- });
86
-
87
- this.querySelector('#modal-cancel').addEventListener('click', () => {
88
- this.closeModal();
89
- });
90
-
91
- this.querySelector('#modal-save').addEventListener('click', () => {
92
- this.savePropertyValue();
93
- });
94
-
95
- // Editor type selector
96
- this.querySelectorAll('.type-btn').forEach(btn => {
97
- btn.addEventListener('click', (e) => {
98
- this.switchEditorType(e.target.dataset.type);
99
- });
100
- });
101
-
102
- // Action buttons
103
- this.querySelector('#apply-changes')?.addEventListener('click', (e) => {
104
- e.stopPropagation();
105
- this.applyAllChanges();
106
- });
107
-
108
- this.querySelector('#reset-values')?.addEventListener('click', (e) => {
109
- e.stopPropagation();
110
- this.resetValues();
111
- });
112
-
113
- // Property editor validation
114
- this.propertyEditor.addEventListener('input', () => {
115
- this.validateEditor();
116
- });
117
-
118
- // Modal backdrop click
119
- this.editorModal.addEventListener('click', (e) => {
120
- if (e.target === this.editorModal) {
121
- this.closeModal();
122
- }
123
- });
124
-
125
- // ✅ EVENTOS PRINCIPALES - Con protecciÃŗn anti-interferencia
126
- this.addEventListener('mousedown', (event) => {
127
- if (event.target.classList.contains('prop-control')) {
128
- this.isDebuggerInput = true;
129
- // Prevenir interferencias del Router u otros sistemas
130
- event.stopPropagation();
131
- }
132
- });
133
-
134
- this.addEventListener('focus', (event) => {
135
- if (event.target.classList.contains('prop-control')) {
136
- this.isDebuggerInput = true;
137
- event.stopPropagation();
138
- }
139
- }, true);
140
-
141
- this.addEventListener('blur', (event) => {
142
- if (event.target.classList.contains('prop-control')) {
143
- this.isDebuggerInput = false;
144
- }
145
- }, true);
146
-
147
- this.addEventListener('keypress', (event) => {
148
- if (event.key === 'Enter' && event.target.classList.contains('prop-control')) {
149
- event.preventDefault();
150
- event.stopPropagation();
151
- this.applyPropertyChange(event.target);
152
- }
153
- });
154
-
155
- this.addEventListener('change', (event) => {
156
- if (event.target.type === 'checkbox' && event.target.classList.contains('prop-control')) {
157
- event.stopPropagation();
158
- this.applyPropertyChange(event.target);
159
- }
160
- });
161
-
162
- // ✅ PROTECCIÓN GLOBAL: Prevenir que eventos externos interfieran
163
- this.addEventListener('click', (event) => {
164
- if (this.contains(event.target)) {
165
- event.stopPropagation();
166
- }
167
- });
168
-
169
- // ✅ Los eventos DOMNodeInserted/Removed estÃĄn deprecated,
170
- // pero la protecciÃŗn con stopPropagation() ya es suficiente
171
- }
172
-
173
- /**
174
- * Switch active tab.
175
- * @param {string} tabName
176
- * @returns {void}
177
- */
178
- switchTab(tabName) {
179
- this.activeTab = tabName;
180
-
181
- this.querySelectorAll('.tab-btn').forEach(btn => {
182
- btn.classList.toggle('active', btn.dataset.tab === tabName);
183
- });
184
-
185
- this.querySelectorAll('.tab-pane').forEach(pane => {
186
- pane.classList.toggle('active', pane.id === `${tabName}-tab`);
187
- });
188
- }
189
-
190
- /**
191
- * Switch editor type (json | function).
192
- * @param {string} type
193
- * @returns {void}
194
- */
195
- switchEditorType(type) {
196
- const normalized = this.normalizeEditorType(type);
197
- if (!normalized) {
198
- return;
199
- }
200
- this.querySelectorAll('.type-btn').forEach(btn => {
201
- btn.classList.toggle('active', btn.dataset.type === normalized);
202
- });
203
- this.currentEditingType = normalized;
204
- }
205
-
206
- /**
207
- * Normalize editor type to supported values.
208
- * @param {string} type
209
- * @returns {string|null}
210
- */
211
- normalizeEditorType(type) {
212
- if (type === 'json' || type === 'function') {
213
- return type;
214
- }
215
- return null;
216
- }
217
-
218
- /**
219
- * Attach debugger toggle handler to a component.
220
- * @param {HTMLElement} component
221
- * @returns {void}
222
- */
223
- attachDebugMode(component) {
224
- if (this.toggleClick === 'right') {
225
- this.toggle = 'contextmenu';
226
- } else {
227
- this.toggle = 'click';
228
- }
229
- component.addEventListener(this.toggle, (event) => this.handleDebugClick(event, component));
230
- }
231
-
232
- /**
233
- * Enable drag interaction for the debugger panel.
234
- * @returns {void}
235
- */
236
- makeDraggable() {
237
- let offset = { x: 0, y: 0 };
238
- let isDragging = false;
239
-
240
- const header = this.querySelector('.debugger-header');
241
-
242
- header.addEventListener('mousedown', (event) => {
243
- isDragging = true;
244
- offset.x = event.clientX - this.debuggerContainer.getBoundingClientRect().left;
245
- offset.y = event.clientY - this.debuggerContainer.getBoundingClientRect().top;
246
- header.style.cursor = 'grabbing';
247
- });
248
-
249
- document.addEventListener('mousemove', (event) => {
250
- if (isDragging) {
251
- const x = event.clientX - offset.x;
252
- const y = event.clientY - offset.y;
253
- this.debuggerContainer.style.left = `${x}px`;
254
- this.debuggerContainer.style.top = `${y}px`;
255
- this.debuggerContainer.style.right = 'auto';
256
- }
257
- });
258
-
259
- document.addEventListener('mouseup', () => {
260
- if (isDragging) {
261
- isDragging = false;
262
- header.style.cursor = 'grab';
263
- }
264
- });
265
- }
266
-
267
- /**
268
- * Handle toggle click and load component info.
269
- * @param {Event} event
270
- * @param {HTMLElement} component
271
- * @returns {void}
272
- */
273
- handleDebugClick(event, component) {
274
- event.preventDefault();
275
- event.stopPropagation();
276
-
277
- this.selectedComponentSliceId = component.sliceId;
278
- this.currentComponent = component;
279
- this.isActive = true;
280
-
281
- // Update header info
282
- this.componentName.textContent = component.constructor.name;
283
- this.componentId.textContent = `ID: ${component.sliceId}`;
284
-
285
- // Gather component data
286
- const realComponentProps = this.getComponentPropsForDebugger(component);
287
- this.componentProps = {};
288
-
289
- realComponentProps.forEach((attr) => {
290
- if (component[attr] === undefined) {
291
- this.componentProps[attr] = component[`_${attr}`];
292
- } else {
293
- this.componentProps[attr] = component[attr];
294
- }
295
- });
296
-
297
- // ✅ Crear UI sin interferencias
298
- this.updateDebuggerContent();
299
- this.debuggerContainer.classList.add('active');
300
- }
301
-
302
- updateDebuggerContent() {
303
- this.updatePropsTab();
304
- this.updateInfoTab();
305
- }
306
-
307
- /**
308
- * Render props tab content.
309
- * @returns {void}
310
- */
311
- updatePropsTab() {
312
- const propsContainer = this.querySelector('.props-container');
313
- if (!propsContainer) {
314
- return;
315
- }
316
-
317
- propsContainer.innerHTML = '';
318
-
319
- const realComponentProps = this.getComponentPropsForDebugger(this.currentComponent);
320
- const ComponentClass = this.currentComponent.constructor;
321
- const configuredProps = ComponentClass.props || {};
322
-
323
- realComponentProps.forEach(prop => {
324
- const propElement = this.createPropElement(prop, configuredProps[prop]);
325
- propsContainer.appendChild(propElement);
326
- });
327
- }
328
-
329
- /**
330
- * Build a prop row element.
331
- * @param {string} prop
332
- * @param {Object} [config]
333
- * @returns {HTMLElement}
334
- */
335
- createPropElement(prop, config = {}) {
336
- const propWrapper = document.createElement('div');
337
- propWrapper.className = 'prop-item';
338
- propWrapper.dataset.prop = prop;
339
-
340
- const currentValue = this.currentComponent[prop];
341
- const valueType = this.getValueType(currentValue);
342
-
343
- // Status based on usage
344
- let status, statusClass;
345
- if (currentValue !== undefined && currentValue !== null) {
346
- status = 'Used';
347
- statusClass = 'status-used';
348
- } else if (config.required) {
349
- status = 'Missing';
350
- statusClass = 'status-missing';
351
- } else {
352
- status = 'Optional';
353
- statusClass = 'status-optional';
354
- }
355
-
356
- propWrapper.innerHTML = `
357
- <div class="prop-header">
358
- <div class="prop-title">
359
- <strong>${prop}</strong>
360
- <span class="prop-type">${valueType}</span>
361
- </div>
362
- <div class="prop-status ${statusClass}">${status}</div>
363
- </div>
364
- <div class="prop-input">
365
- ${this.createInputForType(prop, currentValue, valueType, config)}
366
- </div>
367
- ${config.default !== undefined ? `<div class="default-value">Default: ${JSON.stringify(config.default)}</div>` : ''}
368
- `;
369
-
370
- return propWrapper;
371
- }
372
-
373
- /**
374
- * Build input HTML for a prop type.
375
- * @param {string} prop
376
- * @param {any} value
377
- * @param {string} type
378
- * @param {Object} [config]
379
- * @returns {string}
380
- */
381
- createInputForType(prop, value, type, config = {}) {
382
- const serializedValue = this.serializeValue(value);
383
-
384
- if (type === 'boolean') {
385
- return `
386
- <div class="input-group">
387
- <input type="checkbox"
388
- class="prop-control debugger-input"
389
- data-prop="${prop}"
390
- ${value ? 'checked' : ''}
391
- data-debugger-input="true">
392
- <span class="checkbox-label">${value ? 'true' : 'false'}</span>
393
- </div>
394
- `;
395
- } else if (type === 'number') {
396
- return `
397
- <div class="input-group">
398
- <input type="number"
399
- class="prop-control debugger-input"
400
- data-prop="${prop}"
401
- value="${serializedValue}"
402
- step="any"
403
- placeholder="Enter number..."
404
- data-debugger-input="true">
405
- </div>
406
- `;
407
- } else if (type === 'object' || type === 'array' || type === 'function') {
408
- return `
409
- <div class="input-group">
410
- <input type="text"
411
- class="prop-control debugger-input"
412
- data-prop="${prop}"
413
- value="${serializedValue}"
414
- readonly
415
- title="Click edit button to modify"
416
- data-debugger-input="true">
417
- <button class="edit-btn" onclick="slice.debugger.openAdvancedEditor('${prop}', '${type}')">âœī¸</button>
418
- </div>
419
- `;
420
- } else {
421
- return `
422
- <div class="input-group">
423
- <input type="text"
424
- class="prop-control debugger-input"
425
- data-prop="${prop}"
426
- value="${serializedValue}"
427
- placeholder="Enter value..."
428
- data-debugger-input="true">
429
- </div>
430
- `;
431
- }
432
- }
433
-
434
- /**
435
- * Apply a single prop change from an input.
436
- * @param {HTMLInputElement} inputElement
437
- * @returns {void}
438
- */
439
- applyPropertyChange(inputElement) {
440
- const prop = inputElement.dataset.prop;
441
- if (!prop) return;
442
-
443
- let newValue;
444
-
445
- if (inputElement.type === 'checkbox') {
446
- newValue = inputElement.checked;
447
- const label = inputElement.parentNode.querySelector('.checkbox-label');
448
- if (label) {
449
- label.textContent = newValue ? 'true' : 'false';
450
- }
451
- } else if (inputElement.type === 'number') {
452
- newValue = Number(inputElement.value);
453
- } else {
454
- newValue = inputElement.value;
455
-
456
- // Convert string values
457
- if (newValue === 'true') newValue = true;
458
- if (newValue === 'false') newValue = false;
459
- if (!isNaN(newValue) && newValue !== '' && newValue !== null) newValue = Number(newValue);
460
- }
461
-
462
- const oldValue = this.currentComponent[prop];
463
-
464
- this.currentComponent[prop] = newValue;
465
- slice.logger.logInfo('Debugger', `Updated ${prop}: ${oldValue} → ${newValue}`);
466
-
467
- this.showVisualFeedback(inputElement);
468
- }
469
-
470
- /**
471
- * Show temporary highlight on updated input.
472
- * @param {HTMLElement} inputElement
473
- * @returns {void}
474
- */
475
- showVisualFeedback(inputElement) {
476
- const originalBorder = inputElement.style.borderColor;
477
- const originalBoxShadow = inputElement.style.boxShadow;
478
-
479
- inputElement.style.borderColor = '#4CAF50';
480
- inputElement.style.boxShadow = '0 0 0 2px rgba(76, 175, 80, 0.3)';
481
-
482
- setTimeout(() => {
483
- inputElement.style.borderColor = originalBorder;
484
- inputElement.style.boxShadow = originalBoxShadow;
485
- }, 1500);
486
- }
487
-
488
- /**
489
- * Apply all editable prop changes in the panel.
490
- * @returns {void}
491
- */
492
- applyAllChanges() {
493
- const inputs = this.querySelectorAll('.prop-control:not([readonly])');
494
- let changeCount = 0;
495
-
496
- inputs.forEach(input => {
497
- if (!input.readOnly) {
498
- this.applyPropertyChange(input);
499
- changeCount++;
500
- }
501
- });
502
-
503
- slice.logger.logInfo('Debugger', `Applied ${changeCount} property changes`);
504
- this.showApplyFeedback(changeCount);
505
- }
506
-
507
- /**
508
- * Show apply feedback in button.
509
- * @param {number} changeCount
510
- * @returns {void}
511
- */
512
- showApplyFeedback(changeCount) {
513
- const applyBtn = this.querySelector('#apply-changes');
514
- if (!applyBtn) return;
515
-
516
- const originalText = applyBtn.textContent;
517
-
518
- if (changeCount > 0) {
519
- applyBtn.textContent = `✅ Applied ${changeCount} changes!`;
520
- applyBtn.style.background = '#4CAF50';
521
- } else {
522
- applyBtn.textContent = '✅ No changes to apply';
523
- applyBtn.style.background = '#9E9E9E';
524
- }
525
-
526
- setTimeout(() => {
527
- applyBtn.textContent = originalText;
528
- applyBtn.style.background = '';
529
- }, 2000);
530
- }
531
-
532
- /**
533
- * Open advanced editor for objects/functions.
534
- * @param {string} prop
535
- * @param {string} type
536
- * @returns {void}
537
- */
538
- openAdvancedEditor(prop, type) {
539
- this.currentEditingProp = prop;
540
- this.currentEditingType = this.normalizeEditorType(type) || 'json';
541
-
542
- const value = this.currentComponent[prop];
543
-
544
- this.modalTitle.textContent = `Edit ${prop} (${this.currentEditingType})`;
545
-
546
- this.querySelectorAll('.type-btn').forEach(btn => {
547
- btn.classList.toggle('active', btn.dataset.type === this.currentEditingType);
548
- });
549
-
550
- if (this.currentEditingType === 'function') {
551
- if (typeof value === 'function') {
552
- this.propertyEditor.value = value.toString();
553
- } else {
554
- this.propertyEditor.value = 'function() {\n // Your code here\n}';
555
- }
556
- } else {
557
- try {
558
- this.propertyEditor.value = JSON.stringify(value, null, 2);
559
- } catch (error) {
560
- this.propertyEditor.value = 'null';
561
- }
562
- }
563
-
564
- this.editorModal.classList.add('active');
565
- this.propertyEditor.focus();
566
- }
567
-
568
- /**
569
- * Validate editor contents for current type.
570
- * @returns {void}
571
- */
572
- validateEditor() {
573
- const value = this.propertyEditor.value.trim();
574
- const type = this.normalizeEditorType(this.currentEditingType) || 'json';
575
-
576
- try {
577
- if (type === 'function') {
578
- new Function('return ' + value)();
579
- } else {
580
- JSON.parse(value);
581
- }
582
-
583
- this.validationMessage.textContent = '✅ Valid syntax';
584
- this.validationMessage.style.color = '#4CAF50';
585
- this.querySelector('#modal-save').disabled = false;
586
- } catch (error) {
587
- this.validationMessage.textContent = `❌ ${error.message}`;
588
- this.validationMessage.style.color = '#F44336';
589
- this.querySelector('#modal-save').disabled = true;
590
- }
591
- }
592
-
593
- /**
594
- * Save editor value to component.
595
- * @returns {void}
596
- */
597
- savePropertyValue() {
598
- const value = this.propertyEditor.value.trim();
599
- const type = this.normalizeEditorType(this.currentEditingType) || 'json';
600
-
601
- try {
602
- let newValue;
603
-
604
- if (type === 'function') {
605
- newValue = new Function('return ' + value)();
606
- } else {
607
- newValue = JSON.parse(value);
608
- }
609
-
610
- this.currentComponent[this.currentEditingProp] = newValue;
611
- this.closeModal();
612
-
613
- slice.logger.logInfo('Debugger', `Updated ${this.currentEditingProp} via advanced editor`);
614
-
615
- const input = this.querySelector(`[data-prop="${this.currentEditingProp}"]`);
616
- if (input) {
617
- input.value = this.serializeValue(newValue);
618
- this.showVisualFeedback(input);
619
- }
620
-
621
- } catch (error) {
622
- this.validationMessage.textContent = `❌ ${error.message}`;
623
- this.validationMessage.style.color = '#F44336';
624
- }
625
- }
626
-
627
- /**
628
- * Close the advanced editor modal.
629
- * @returns {void}
630
- */
631
- closeModal() {
632
- this.editorModal.classList.remove('active');
633
- this.currentEditingProp = null;
634
- this.currentEditingType = null;
635
- this.validationMessage.textContent = '';
636
- }
637
-
638
- /**
639
- * Reset props to defaults (if defined in static props).
640
- * @returns {void}
641
- */
642
- resetValues() {
643
- const ComponentClass = this.currentComponent.constructor;
644
- const configuredProps = ComponentClass.props || {};
645
- let resetCount = 0;
646
-
647
- Object.entries(configuredProps).forEach(([prop, config]) => {
648
- if (config.default !== undefined) {
649
- this.currentComponent[prop] = config.default;
650
-
651
- const input = this.querySelector(`[data-prop="${prop}"]`);
652
- if (input && !input.readOnly) {
653
- if (input.type === 'checkbox') {
654
- input.checked = config.default;
655
- const label = input.parentNode.querySelector('.checkbox-label');
656
- if (label) {
657
- label.textContent = config.default ? 'true' : 'false';
658
- }
659
- } else {
660
- input.value = this.serializeValue(config.default);
661
- }
662
- resetCount++;
663
- }
664
- }
665
- });
666
-
667
- slice.logger.logInfo('Debugger', 'Reset values to defaults');
668
- this.showResetFeedback(resetCount);
669
- }
670
-
671
- /**
672
- * Show reset feedback in button.
673
- * @param {number} resetCount
674
- * @returns {void}
675
- */
676
- showResetFeedback(resetCount) {
677
- const resetBtn = this.querySelector('#reset-values');
678
- if (!resetBtn) return;
679
-
680
- const originalText = resetBtn.textContent;
681
-
682
- resetBtn.textContent = `🔄 Reset ${resetCount} values!`;
683
- resetBtn.style.background = '#FF9800';
684
-
685
- setTimeout(() => {
686
- resetBtn.textContent = originalText;
687
- resetBtn.style.background = '';
688
- }, 1500);
689
- }
690
-
691
- /**
692
- * Render info tab content.
693
- * @returns {void}
694
- */
695
- updateInfoTab() {
696
- const infoContainer = this.querySelector('.info-list');
697
- if (!infoContainer) return;
698
-
699
- const component = this.currentComponent;
700
-
701
- const info = [
702
- { label: 'Component Type', value: component.constructor.name },
703
- { label: 'Slice ID', value: component.sliceId || 'Not assigned' },
704
- { label: 'Tag Name', value: component.tagName },
705
- { label: 'Connected', value: component.isConnected ? 'Yes' : 'No' },
706
- { label: 'Props Count', value: Object.keys(this.componentProps).length },
707
- { label: 'Children', value: component.children.length }
708
- ];
709
-
710
- infoContainer.innerHTML = info.map(item => `
711
- <div class="info-item">
712
- <span class="info-label">${item.label}</span>
713
- <span class="info-value">${item.value}</span>
714
- </div>
715
- `).join('');
716
- }
717
-
718
- /**
719
- * Get a simple value type label.
720
- * @param {any} value
721
- * @returns {string}
722
- */
723
- getValueType(value) {
724
- if (value === null) return 'null';
725
- if (value === undefined) return 'undefined';
726
- if (Array.isArray(value)) return 'array';
727
- return typeof value;
728
- }
729
-
730
- /**
731
- * Serialize a value for input display.
732
- * @param {any} value
733
- * @returns {string}
734
- */
735
- serializeValue(value) {
736
- if (value === null || value === undefined) {
737
- return '';
738
- }
739
-
740
- if (typeof value === 'object' || typeof value === 'function') {
741
- try {
742
- return JSON.stringify(value);
743
- } catch {
744
- return String(value);
745
- }
746
- }
747
-
748
- return String(value);
749
- }
750
-
751
- /**
752
- * Resolve which props to show in the debugger.
753
- * @param {HTMLElement} component
754
- * @returns {string[]}
755
- */
756
- getComponentPropsForDebugger(component) {
757
- const ComponentClass = component.constructor;
758
-
759
- if (ComponentClass.props) {
760
- return Object.keys(ComponentClass.props);
761
- }
762
-
763
- if (component.debuggerProps && Array.isArray(component.debuggerProps)) {
764
- return component.debuggerProps;
765
- }
766
-
767
- return this.detectUsedProps(component);
768
- }
769
-
770
- /**
771
- * Detect props from backing fields on the component.
772
- * @param {HTMLElement} component
773
- * @returns {string[]}
774
- */
775
- detectUsedProps(component) {
776
- const usedProps = [];
777
-
778
- Object.getOwnPropertyNames(component).forEach(key => {
779
- if (key.startsWith('_') && key !== '_isActive') {
780
- const propName = key.substring(1);
781
- usedProps.push(propName);
782
- }
783
- });
784
-
785
- return usedProps;
786
- }
787
-
788
- /**
789
- * Hide the debugger UI.
790
- * @returns {void}
791
- */
792
- hide() {
793
- this.debuggerContainer.classList.remove('active');
794
- this.closeModal();
795
- }
796
- }
797
-
798
- customElements.define('slice-debugger', Debugger);
799
-
800
- function productionOnlyCSS(){
801
- return `
802
- #debugger-container {
803
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
804
- display: none;
805
- position: fixed;
806
- top: 20px;
807
- right: 20px;
808
- width: min(420px, calc(100vw - 40px));
809
- height: 85vh;
810
- max-height: 85vh;
811
- background: var(--primary-background-color);
812
- border: 1px solid var(--medium-color);
813
- border-radius: 12px;
814
- box-shadow: 0 20px 40px rgba(var(--primary-color-rgb), 0.15), 0 4px 12px rgba(0, 0, 0, 0.1);
815
- z-index: 10000;
816
- overflow: hidden;
817
- backdrop-filter: blur(10px);
818
- box-sizing: border-box;
819
- }
820
-
821
- #debugger-container.active {
822
- display: flex;
823
- flex-direction: column;
824
- }
825
-
826
- .debugger-header {
827
- background: linear-gradient(135deg, var(--primary-color), var(--primary-color-shade));
828
- color: var(--primary-color-contrast);
829
- padding: 12px 16px;
830
- border-radius: 12px 12px 0 0;
831
- user-select: none;
832
- cursor: grab;
833
- }
834
-
835
- .debugger-header:active {
836
- cursor: grabbing;
837
- }
838
-
839
- .header-content {
840
- display: flex;
841
- justify-content: space-between;
842
- align-items: center;
843
- }
844
-
845
- .component-info {
846
- display: flex;
847
- align-items: center;
848
- gap: 10px;
849
- }
850
-
851
- .component-icon {
852
- font-size: 20px;
853
- opacity: 0.9;
854
- }
855
-
856
- .component-name {
857
- font-size: 14px;
858
- font-weight: 600;
859
- margin-bottom: 2px;
860
- }
861
-
862
- .component-id {
863
- font-size: 11px;
864
- opacity: 0.8;
865
- }
866
-
867
- .header-actions {
868
- display: flex;
869
- gap: 8px;
870
- }
871
-
872
- .minimize-btn, #close-debugger {
873
- background: rgba(255, 255, 255, 0.2);
874
- border: none;
875
- color: var(--primary-color-contrast);
876
- width: 28px;
877
- height: 28px;
878
- border-radius: 6px;
879
- cursor: pointer;
880
- display: flex;
881
- align-items: center;
882
- justify-content: center;
883
- font-size: 16px;
884
- font-weight: bold;
885
- transition: background 0.2s ease;
886
- }
887
-
888
- .minimize-btn:hover, #close-debugger:hover {
889
- background: rgba(255, 255, 255, 0.3);
890
- }
891
-
892
- .debugger-content {
893
- flex: 1;
894
- display: flex;
895
- flex-direction: column;
896
- overflow: hidden;
897
- }
898
-
899
- .tabs-container {
900
- border-bottom: 1px solid var(--medium-color);
901
- }
902
-
903
- .tab-nav {
904
- display: flex;
905
- background: var(--tertiary-background-color);
906
- }
907
-
908
- .tab-btn {
909
- flex: 1;
910
- padding: 10px 14px;
911
- border: none;
912
- background: transparent;
913
- color: var(--font-secondary-color);
914
- font-size: 12px;
915
- font-weight: 500;
916
- cursor: pointer;
917
- transition: all 0.2s ease;
918
- border-bottom: 2px solid transparent;
919
- }
920
-
921
- .tab-btn:hover {
922
- background: var(--secondary-background-color);
923
- color: var(--font-primary-color);
924
- }
925
-
926
- .tab-btn.active {
927
- background: var(--primary-background-color);
928
- color: var(--primary-color);
929
- border-bottom-color: var(--primary-color);
930
- font-weight: 600;
931
- }
932
-
933
- .tab-content {
934
- flex: 1;
935
- overflow: hidden;
936
- min-height: 0;
937
- }
938
-
939
- .tab-pane {
940
- display: none;
941
- height: 100%;
942
- overflow-y: auto;
943
- overflow-x: hidden;
944
- padding: 16px;
945
- box-sizing: border-box;
946
- }
947
-
948
- .tab-pane.active {
949
- display: block;
950
- }
951
-
952
- .tab-pane::-webkit-scrollbar {
953
- width: 4px;
954
- }
955
-
956
- .tab-pane::-webkit-scrollbar-track {
957
- background: var(--tertiary-background-color);
958
- border-radius: 2px;
959
- }
960
-
961
- .tab-pane::-webkit-scrollbar-thumb {
962
- background: var(--medium-color);
963
- border-radius: 2px;
964
- }
965
-
966
- .tab-pane::-webkit-scrollbar-thumb:hover {
967
- background: var(--primary-color);
968
- }
969
-
970
- .props-container {
971
- display: flex;
972
- flex-direction: column;
973
- gap: 12px;
974
- margin-bottom: 16px;
975
- min-width: 0;
976
- overflow-x: hidden;
977
- }
978
-
979
- #debugger-container *,
980
- #debugger-container *::before,
981
- #debugger-container *::after {
982
- box-sizing: border-box;
983
- }
984
-
985
- .props-actions {
986
- border-top: 1px solid var(--medium-color);
987
- padding-top: 16px;
988
- margin-top: 8px;
989
- }
990
-
991
- .actions-note {
992
- margin-top: 12px;
993
- padding: 8px 12px;
994
- background: var(--tertiary-background-color);
995
- border-radius: 6px;
996
- border: 1px solid var(--medium-color);
997
- }
998
-
999
- .actions-note small {
1000
- color: var(--font-secondary-color);
1001
- font-size: 11px;
1002
- display: flex;
1003
- align-items: center;
1004
- gap: 6px;
1005
- }
1006
-
1007
- .props-section {
1008
- background: var(--tertiary-background-color);
1009
- border: 1px solid var(--medium-color);
1010
- border-radius: 8px;
1011
- padding: 12px;
1012
- }
1013
-
1014
- .section-title {
1015
- font-size: 12px;
1016
- font-weight: 600;
1017
- color: var(--font-primary-color);
1018
- margin-bottom: 12px;
1019
- display: flex;
1020
- align-items: center;
1021
- gap: 6px;
1022
- }
1023
-
1024
- .prop-item {
1025
- display: flex;
1026
- flex-direction: column;
1027
- gap: 6px;
1028
- padding: 12px;
1029
- background: var(--primary-background-color);
1030
- border: 1px solid var(--medium-color);
1031
- border-radius: 6px;
1032
- margin-bottom: 8px;
1033
- transition: border-color 0.2s ease;
1034
- min-width: 0;
1035
- overflow: hidden;
1036
- }
1037
-
1038
- .prop-item:hover {
1039
- border-color: var(--primary-color);
1040
- }
1041
-
1042
- .prop-header {
1043
- display: flex;
1044
- justify-content: space-between;
1045
- align-items: center;
1046
- gap: 8px;
1047
- min-width: 0;
1048
- }
1049
-
1050
- .prop-title {
1051
- display: flex;
1052
- align-items: center;
1053
- gap: 6px;
1054
- min-width: 0;
1055
- flex: 1;
1056
- }
1057
-
1058
- .prop-title strong {
1059
- min-width: 0;
1060
- overflow: hidden;
1061
- text-overflow: ellipsis;
1062
- white-space: nowrap;
1063
- }
1064
-
1065
- .prop-name {
1066
- font-size: 13px;
1067
- font-weight: 600;
1068
- color: var(--font-primary-color);
1069
- }
1070
-
1071
- .prop-name.required::after {
1072
- content: " *";
1073
- color: var(--danger-color);
1074
- }
1075
-
1076
- .prop-meta {
1077
- display: flex;
1078
- align-items: center;
1079
- gap: 8px;
1080
- }
1081
-
1082
- .prop-type {
1083
- font-size: 11px;
1084
- padding: 2px 6px;
1085
- background: var(--secondary-color);
1086
- color: var(--secondary-color-contrast);
1087
- border-radius: 4px;
1088
- font-family: monospace;
1089
- font-weight: 500;
1090
- flex-shrink: 0;
1091
- }
1092
-
1093
- .prop-status {
1094
- font-size: 12px;
1095
- font-weight: 500;
1096
- flex-shrink: 0;
1097
- }
1098
-
1099
- .status-used {
1100
- color: var(--success-color);
1101
- }
1102
-
1103
- .status-missing {
1104
- color: var(--danger-color);
1105
- }
1106
-
1107
- .status-optional {
1108
- color: var(--medium-color);
1109
- }
1110
-
1111
- .prop-input {
1112
- margin-top: 8px;
1113
- min-width: 0;
1114
- overflow: hidden;
1115
- }
1116
-
1117
- .input-group {
1118
- position: relative;
1119
- max-width: 100%;
1120
- min-width: 0;
1121
- overflow: hidden;
1122
- }
1123
-
1124
- .prop-control {
1125
- width: 100%;
1126
- max-width: 100%;
1127
- min-width: 0;
1128
- padding: 8px 32px 8px 10px;
1129
- border: 1px solid var(--medium-color);
1130
- border-radius: 6px;
1131
- background: var(--primary-background-color);
1132
- color: var(--font-primary-color);
1133
- font-size: 13px;
1134
- transition: border-color 0.2s ease, box-shadow 0.2s ease;
1135
- font-family: monospace;
1136
- box-sizing: border-box;
1137
- display: block;
1138
- }
1139
-
1140
- .prop-control:focus {
1141
- outline: none;
1142
- border-color: var(--primary-color);
1143
- box-shadow: 0 0 0 3px rgba(var(--primary-color-rgb), 0.1);
1144
- }
1145
-
1146
- .prop-control:disabled {
1147
- background: var(--disabled-color);
1148
- color: var(--font-secondary-color);
1149
- cursor: not-allowed;
1150
- }
1151
-
1152
- .prop-control[readonly] {
1153
- background: var(--tertiary-background-color);
1154
- cursor: pointer;
1155
- text-overflow: ellipsis;
1156
- overflow: hidden;
1157
- white-space: nowrap;
1158
- max-width: 100%;
1159
- }
1160
-
1161
- .prop-control[readonly]:focus {
1162
- border-color: var(--accent-color);
1163
- box-shadow: 0 0 0 3px rgba(var(--accent-color), 0.1);
1164
- }
1165
-
1166
- /* Estilos específicos para checkboxes */
1167
- .prop-control[type="checkbox"] {
1168
- width: auto;
1169
- padding: 0;
1170
- margin: 0;
1171
- cursor: pointer;
1172
- }
1173
-
1174
- .edit-btn {
1175
- position: absolute;
1176
- right: 4px;
1177
- top: 50%;
1178
- transform: translateY(-50%);
1179
- background: var(--accent-color);
1180
- border: none;
1181
- color: white;
1182
- width: 24px;
1183
- height: 24px;
1184
- border-radius: 4px;
1185
- cursor: pointer;
1186
- font-size: 12px;
1187
- display: flex;
1188
- align-items: center;
1189
- justify-content: center;
1190
- transition: background 0.2s ease;
1191
- }
1192
-
1193
- .edit-btn:hover {
1194
- background: var(--primary-color);
1195
- }
1196
-
1197
- .default-value {
1198
- font-size: 11px;
1199
- color: var(--font-secondary-color);
1200
- font-style: italic;
1201
- margin-top: 4px;
1202
- }
1203
-
1204
- .info-list {
1205
- display: flex;
1206
- flex-direction: column;
1207
- gap: 12px;
1208
- }
1209
-
1210
- .info-item {
1211
- display: flex;
1212
- justify-content: space-between;
1213
- padding: 12px;
1214
- background: var(--tertiary-background-color);
1215
- border-radius: 6px;
1216
- border: 1px solid var(--medium-color);
1217
- }
1218
-
1219
- .info-label {
1220
- font-weight: 600;
1221
- color: var(--font-primary-color);
1222
- font-size: 13px;
1223
- }
1224
-
1225
- .info-value {
1226
- color: var(--font-secondary-color);
1227
- font-family: monospace;
1228
- font-size: 12px;
1229
- }
1230
-
1231
- .actions-container {
1232
- display: flex;
1233
- flex-direction: column;
1234
- gap: 16px;
1235
- }
1236
-
1237
- .action-buttons {
1238
- display: flex;
1239
- flex-direction: column;
1240
- gap: 8px;
1241
- }
1242
-
1243
- .action-btn {
1244
- padding: 12px 16px;
1245
- border: none;
1246
- border-radius: 6px;
1247
- font-size: 13px;
1248
- font-weight: 500;
1249
- cursor: pointer;
1250
- transition: all 0.2s ease;
1251
- display: flex;
1252
- align-items: center;
1253
- justify-content: center;
1254
- gap: 8px;
1255
- }
1256
-
1257
- .action-btn.primary {
1258
- background: var(--primary-color);
1259
- color: var(--primary-color-contrast);
1260
- }
1261
-
1262
- .action-btn.primary:hover {
1263
- background: var(--primary-color-shade);
1264
- }
1265
-
1266
- .action-btn.secondary {
1267
- background: var(--secondary-color);
1268
- color: var(--secondary-color-contrast);
1269
- }
1270
-
1271
- .action-btn.secondary:hover {
1272
- opacity: 0.9;
1273
- }
1274
-
1275
- .action-btn.tertiary {
1276
- background: var(--tertiary-background-color);
1277
- color: var(--font-primary-color);
1278
- border: 1px solid var(--medium-color);
1279
- }
1280
-
1281
- .action-btn.tertiary:hover {
1282
- background: var(--secondary-background-color);
1283
- }
1284
-
1285
- /* Modal Styles */
1286
- .editor-modal {
1287
- display: none;
1288
- position: fixed;
1289
- top: 0;
1290
- left: 0;
1291
- width: 100%;
1292
- height: 100%;
1293
- background: rgba(0, 0, 0, 0.6);
1294
- z-index: 20000;
1295
- backdrop-filter: blur(4px);
1296
- }
1297
-
1298
- .editor-modal.active {
1299
- display: flex;
1300
- align-items: center;
1301
- justify-content: center;
1302
- }
1303
-
1304
- .modal-content {
1305
- background: var(--primary-background-color);
1306
- border-radius: 12px;
1307
- width: min(600px, 92vw);
1308
- max-width: 600px;
1309
- max-height: 80%;
1310
- display: flex;
1311
- flex-direction: column;
1312
- box-shadow: 0 25px 50px rgba(0, 0, 0, 0.25);
1313
- border: 1px solid var(--medium-color);
1314
- box-sizing: border-box;
1315
- }
1316
-
1317
- .modal-header {
1318
- padding: 16px 20px;
1319
- background: var(--tertiary-background-color);
1320
- border-radius: 12px 12px 0 0;
1321
- border-bottom: 1px solid var(--medium-color);
1322
- display: flex;
1323
- justify-content: space-between;
1324
- align-items: center;
1325
- }
1326
-
1327
- .modal-header h3 {
1328
- margin: 0;
1329
- font-size: 16px;
1330
- font-weight: 600;
1331
- color: var(--font-primary-color);
1332
- }
1333
-
1334
- .modal-close {
1335
- background: none;
1336
- border: none;
1337
- font-size: 20px;
1338
- color: var(--font-secondary-color);
1339
- cursor: pointer;
1340
- width: 32px;
1341
- height: 32px;
1342
- border-radius: 6px;
1343
- display: flex;
1344
- align-items: center;
1345
- justify-content: center;
1346
- transition: background 0.2s ease;
1347
- }
1348
-
1349
- .modal-close:hover {
1350
- background: var(--secondary-background-color);
1351
- }
1352
-
1353
- .modal-body {
1354
- flex: 1;
1355
- padding: 20px;
1356
- display: flex;
1357
- flex-direction: column;
1358
- gap: 16px;
1359
- overflow: hidden;
1360
- min-height: 0;
1361
- }
1362
-
1363
- .editor-type-selector {
1364
- display: flex;
1365
- gap: 4px;
1366
- background: var(--tertiary-background-color);
1367
- padding: 4px;
1368
- border-radius: 6px;
1369
- }
1370
-
1371
- .type-btn {
1372
- flex: 1;
1373
- padding: 8px 12px;
1374
- border: none;
1375
- background: transparent;
1376
- color: var(--font-secondary-color);
1377
- font-size: 12px;
1378
- font-weight: 500;
1379
- cursor: pointer;
1380
- border-radius: 4px;
1381
- transition: all 0.2s ease;
1382
- }
1383
-
1384
- .type-btn.active {
1385
- background: var(--primary-color);
1386
- color: var(--primary-color-contrast);
1387
- }
1388
-
1389
- .editor-container {
1390
- flex: 1;
1391
- position: relative;
1392
- min-height: 200px;
1393
- min-width: 0;
1394
- }
1395
-
1396
- #property-editor {
1397
- width: 100%;
1398
- height: 100%;
1399
- border: 1px solid var(--medium-color);
1400
- border-radius: 6px;
1401
- padding: 12px;
1402
- background: var(--primary-background-color);
1403
- color: var(--font-primary-color);
1404
- font-family: 'Monaco', 'Consolas', monospace;
1405
- font-size: 13px;
1406
- line-height: 1.5;
1407
- resize: none;
1408
- outline: none;
1409
- min-height: 200px;
1410
- box-sizing: border-box;
1411
- }
1412
-
1413
- #property-editor:focus {
1414
- border-color: var(--primary-color);
1415
- box-shadow: 0 0 0 3px rgba(var(--primary-color-rgb), 0.1);
1416
- }
1417
-
1418
- .validation-message {
1419
- font-size: 12px;
1420
- color: var(--danger-color);
1421
- min-height: 18px;
1422
- display: flex;
1423
- align-items: center;
1424
- gap: 6px;
1425
- }
1426
-
1427
- .modal-actions {
1428
- padding: 16px 20px;
1429
- background: var(--tertiary-background-color);
1430
- border-radius: 0 0 12px 12px;
1431
- border-top: 1px solid var(--medium-color);
1432
- display: flex;
1433
- gap: 12px;
1434
- justify-content: flex-end;
1435
- }
1436
-
1437
- .modal-btn {
1438
- padding: 10px 20px;
1439
- border: none;
1440
- border-radius: 6px;
1441
- font-size: 13px;
1442
- font-weight: 500;
1443
- cursor: pointer;
1444
- transition: all 0.2s ease;
1445
- }
1446
-
1447
- .modal-btn.cancel {
1448
- background: var(--tertiary-background-color);
1449
- color: var(--font-primary-color);
1450
- border: 1px solid var(--medium-color);
1451
- }
1452
-
1453
- .modal-btn.cancel:hover {
1454
- background: var(--secondary-background-color);
1455
- }
1456
-
1457
- .modal-btn.save {
1458
- background: var(--success-color);
1459
- color: var(--success-contrast);
1460
- }
1461
-
1462
- .modal-btn.save:hover {
1463
- opacity: 0.9;
1464
- }
1465
-
1466
- .modal-btn.save:disabled {
1467
- background: var(--disabled-color);
1468
- cursor: not-allowed;
1469
- }
1470
- `
1471
- }
1472
-
1473
- function productionOnlyHtml(){
1474
- return `
1475
- <div id="debugger-container">
1476
- <div class="debugger-header">
1477
- <div class="header-content">
1478
- <div class="component-info">
1479
- <div class="component-icon">🔍</div>
1480
- <div class="component-details">
1481
- <div class="component-name">Component Inspector</div>
1482
- <div class="component-id">Ready to debug</div>
1483
- </div>
1484
- </div>
1485
- <div class="header-actions">
1486
- <button class="minimize-btn" title="Minimize">−</button>
1487
- <button id="close-debugger" title="Close">×</button>
1488
- </div>
1489
- </div>
1490
- </div>
1491
-
1492
- <div class="debugger-content">
1493
- <div class="tabs-container">
1494
- <div class="tab-nav">
1495
- <button class="tab-btn active" data-tab="props">📋 Props</button>
1496
- <button class="tab-btn" data-tab="info">â„šī¸ Info</button>
1497
- </div>
1498
- </div>
1499
-
1500
- <div class="tab-content">
1501
- <div class="tab-pane active" id="props-tab">
1502
- <div class="props-container"></div>
1503
- <div class="props-actions">
1504
- <div class="action-buttons">
1505
- <button class="action-btn primary" id="apply-changes">✅ Apply Changes</button>
1506
- <button class="action-btn secondary" id="reset-values">🔄 Reset Values</button>
1507
- </div>
1508
- <div class="actions-note">
1509
- <small>💡 Press Enter on any input to apply changes automatically</small>
1510
- </div>
1511
- </div>
1512
- </div>
1513
-
1514
- <div class="tab-pane" id="info-tab">
1515
- <div class="info-container">
1516
- <div class="info-list"></div>
1517
- </div>
1518
- </div>
1519
- </div>
1520
- </div>
1521
-
1522
- <!-- Modal para editar objetos/funciones -->
1523
- <div class="editor-modal" id="editor-modal">
1524
- <div class="modal-content">
1525
- <div class="modal-header">
1526
- <h3 id="modal-title">Edit Property</h3>
1527
- <button class="modal-close" id="modal-close">×</button>
1528
- </div>
1529
- <div class="modal-body">
1530
- <div class="editor-type-selector">
1531
- <button class="type-btn active" data-type="json">📋 JSON</button>
1532
- <button class="type-btn" data-type="function">⚡ Function</button>
1533
- </div>
1534
- <div class="editor-container">
1535
- <textarea id="property-editor" spellcheck="false"></textarea>
1536
- </div>
1537
- <div class="editor-footer">
1538
- <div class="validation-message"></div>
1539
- </div>
1540
- </div>
1541
- <div class="modal-actions">
1542
- <button class="modal-btn cancel" id="modal-cancel">Cancel</button>
1543
- <button class="modal-btn save" id="modal-save">Save Changes</button>
1544
- </div>
1545
- </div>
1546
- </div>
1547
- </div>`
1
+ // ✅ VERSIÓN ANTI-INTERFERENCIA - Aislada del Router y con debugging
2
+
3
+ /**
4
+ * Runtime UI debugger for Slice components.
5
+ */
6
+ export default class Debugger extends HTMLElement {
7
+ constructor() {
8
+ super();
9
+ this.toggleClick = slice.debuggerConfig.click;
10
+ this.toggle = 'click';
11
+ this.selectedComponentSliceId = null;
12
+ this.isActive = false;
13
+ this.activeTab = 'props';
14
+ this.currentComponent = null;
15
+ this.componentProps = {};
16
+ this.currentEditingProp = null;
17
+ this.currentEditingType = null;
18
+
19
+ // ✅ Flag para prevenir interferencias externas
20
+ this.isDebuggerInput = false;
21
+ }
22
+
23
+ /**
24
+ * Load debugger UI and enable interactions.
25
+ * @returns {Promise<boolean>}
26
+ */
27
+ async enableDebugMode() {
28
+ //const html = await slice.controller.fetchText('Debugger', 'html', 'Structural');
29
+ //const css = await slice.controller.fetchText('Debugger', 'css', 'Structural');
30
+
31
+ const html = productionOnlyHtml();
32
+ const css = productionOnlyCSS();
33
+
34
+ this.innerHTML = html;
35
+ slice.stylesManager.registerComponentStyles('Debugger', css);
36
+
37
+ this.setupElements();
38
+ this.setupEventListeners();
39
+ this.makeDraggable();
40
+
41
+ slice.logger.logInfo('Debugger', 'Advanced Debug mode enabled');
42
+ return true;
43
+ }
44
+
45
+ /**
46
+ * Cache UI elements.
47
+ * @returns {void}
48
+ */
49
+ setupElements() {
50
+ this.debuggerContainer = this.querySelector('#debugger-container');
51
+ this.closeDebugger = this.querySelector('#close-debugger');
52
+ this.propsContainer = this.querySelector('.props-container');
53
+ this.infoContainer = this.querySelector('.info-list');
54
+ this.editorModal = this.querySelector('#editor-modal');
55
+ this.propertyEditor = this.querySelector('#property-editor');
56
+ this.modalTitle = this.querySelector('#modal-title');
57
+ this.validationMessage = this.querySelector('.validation-message');
58
+
59
+ // Header elements
60
+ this.componentName = this.querySelector('.component-name');
61
+ this.componentId = this.querySelector('.component-id');
62
+ }
63
+
64
+ /**
65
+ * Bind UI event listeners.
66
+ * @returns {void}
67
+ */
68
+ setupEventListeners() {
69
+ // Tab navigation
70
+ this.querySelectorAll('.tab-btn').forEach(btn => {
71
+ btn.addEventListener('click', (e) => {
72
+ this.switchTab(e.target.dataset.tab);
73
+ });
74
+ });
75
+
76
+ // Close and minimize
77
+ this.closeDebugger.addEventListener('click', () => {
78
+ this.hide();
79
+ this.isActive = false;
80
+ });
81
+
82
+ // Modal events
83
+ this.querySelector('#modal-close').addEventListener('click', () => {
84
+ this.closeModal();
85
+ });
86
+
87
+ this.querySelector('#modal-cancel').addEventListener('click', () => {
88
+ this.closeModal();
89
+ });
90
+
91
+ this.querySelector('#modal-save').addEventListener('click', () => {
92
+ this.savePropertyValue();
93
+ });
94
+
95
+ // Editor type selector
96
+ this.querySelectorAll('.type-btn').forEach(btn => {
97
+ btn.addEventListener('click', (e) => {
98
+ this.switchEditorType(e.target.dataset.type);
99
+ });
100
+ });
101
+
102
+ // Action buttons
103
+ this.querySelector('#apply-changes')?.addEventListener('click', (e) => {
104
+ e.stopPropagation();
105
+ this.applyAllChanges();
106
+ });
107
+
108
+ this.querySelector('#reset-values')?.addEventListener('click', (e) => {
109
+ e.stopPropagation();
110
+ this.resetValues();
111
+ });
112
+
113
+ // Property editor validation
114
+ this.propertyEditor.addEventListener('input', () => {
115
+ this.validateEditor();
116
+ });
117
+
118
+ // Modal backdrop click
119
+ this.editorModal.addEventListener('click', (e) => {
120
+ if (e.target === this.editorModal) {
121
+ this.closeModal();
122
+ }
123
+ });
124
+
125
+ // ✅ EVENTOS PRINCIPALES - Con protecciÃŗn anti-interferencia
126
+ this.addEventListener('mousedown', (event) => {
127
+ if (event.target.classList.contains('prop-control')) {
128
+ this.isDebuggerInput = true;
129
+ // Prevenir interferencias del Router u otros sistemas
130
+ event.stopPropagation();
131
+ }
132
+ });
133
+
134
+ this.addEventListener('focus', (event) => {
135
+ if (event.target.classList.contains('prop-control')) {
136
+ this.isDebuggerInput = true;
137
+ event.stopPropagation();
138
+ }
139
+ }, true);
140
+
141
+ this.addEventListener('blur', (event) => {
142
+ if (event.target.classList.contains('prop-control')) {
143
+ this.isDebuggerInput = false;
144
+ }
145
+ }, true);
146
+
147
+ this.addEventListener('keypress', (event) => {
148
+ if (event.key === 'Enter' && event.target.classList.contains('prop-control')) {
149
+ event.preventDefault();
150
+ event.stopPropagation();
151
+ this.applyPropertyChange(event.target);
152
+ }
153
+ });
154
+
155
+ this.addEventListener('change', (event) => {
156
+ if (event.target.type === 'checkbox' && event.target.classList.contains('prop-control')) {
157
+ event.stopPropagation();
158
+ this.applyPropertyChange(event.target);
159
+ }
160
+ });
161
+
162
+ // ✅ PROTECCIÓN GLOBAL: Prevenir que eventos externos interfieran
163
+ this.addEventListener('click', (event) => {
164
+ if (this.contains(event.target)) {
165
+ event.stopPropagation();
166
+ }
167
+ });
168
+
169
+ // ✅ Los eventos DOMNodeInserted/Removed estÃĄn deprecated,
170
+ // pero la protecciÃŗn con stopPropagation() ya es suficiente
171
+ }
172
+
173
+ /**
174
+ * Switch active tab.
175
+ * @param {string} tabName
176
+ * @returns {void}
177
+ */
178
+ switchTab(tabName) {
179
+ this.activeTab = tabName;
180
+
181
+ this.querySelectorAll('.tab-btn').forEach(btn => {
182
+ btn.classList.toggle('active', btn.dataset.tab === tabName);
183
+ });
184
+
185
+ this.querySelectorAll('.tab-pane').forEach(pane => {
186
+ pane.classList.toggle('active', pane.id === `${tabName}-tab`);
187
+ });
188
+ }
189
+
190
+ /**
191
+ * Switch editor type (json | function).
192
+ * @param {string} type
193
+ * @returns {void}
194
+ */
195
+ switchEditorType(type) {
196
+ const normalized = this.normalizeEditorType(type);
197
+ if (!normalized) {
198
+ return;
199
+ }
200
+ this.querySelectorAll('.type-btn').forEach(btn => {
201
+ btn.classList.toggle('active', btn.dataset.type === normalized);
202
+ });
203
+ this.currentEditingType = normalized;
204
+ }
205
+
206
+ /**
207
+ * Normalize editor type to supported values.
208
+ * @param {string} type
209
+ * @returns {string|null}
210
+ */
211
+ normalizeEditorType(type) {
212
+ if (type === 'json' || type === 'function') {
213
+ return type;
214
+ }
215
+ return null;
216
+ }
217
+
218
+ /**
219
+ * Attach debugger toggle handler to a component.
220
+ * @param {HTMLElement} component
221
+ * @returns {void}
222
+ */
223
+ attachDebugMode(component) {
224
+ if (this.toggleClick === 'right') {
225
+ this.toggle = 'contextmenu';
226
+ } else {
227
+ this.toggle = 'click';
228
+ }
229
+ component.addEventListener(this.toggle, (event) => this.handleDebugClick(event, component));
230
+ }
231
+
232
+ /**
233
+ * Enable drag interaction for the debugger panel.
234
+ * @returns {void}
235
+ */
236
+ makeDraggable() {
237
+ let offset = { x: 0, y: 0 };
238
+ let isDragging = false;
239
+
240
+ const header = this.querySelector('.debugger-header');
241
+
242
+ header.addEventListener('mousedown', (event) => {
243
+ isDragging = true;
244
+ offset.x = event.clientX - this.debuggerContainer.getBoundingClientRect().left;
245
+ offset.y = event.clientY - this.debuggerContainer.getBoundingClientRect().top;
246
+ header.style.cursor = 'grabbing';
247
+ });
248
+
249
+ document.addEventListener('mousemove', (event) => {
250
+ if (isDragging) {
251
+ const x = event.clientX - offset.x;
252
+ const y = event.clientY - offset.y;
253
+ this.debuggerContainer.style.left = `${x}px`;
254
+ this.debuggerContainer.style.top = `${y}px`;
255
+ this.debuggerContainer.style.right = 'auto';
256
+ }
257
+ });
258
+
259
+ document.addEventListener('mouseup', () => {
260
+ if (isDragging) {
261
+ isDragging = false;
262
+ header.style.cursor = 'grab';
263
+ }
264
+ });
265
+ }
266
+
267
+ /**
268
+ * Handle toggle click and load component info.
269
+ * @param {Event} event
270
+ * @param {HTMLElement} component
271
+ * @returns {void}
272
+ */
273
+ handleDebugClick(event, component) {
274
+ event.preventDefault();
275
+ event.stopPropagation();
276
+
277
+ this.selectedComponentSliceId = component.sliceId;
278
+ this.currentComponent = component;
279
+ this.isActive = true;
280
+
281
+ // Update header info
282
+ this.componentName.textContent = component.constructor.name;
283
+ this.componentId.textContent = `ID: ${component.sliceId}`;
284
+
285
+ // Gather component data
286
+ const realComponentProps = this.getComponentPropsForDebugger(component);
287
+ this.componentProps = {};
288
+
289
+ realComponentProps.forEach((attr) => {
290
+ if (component[attr] === undefined) {
291
+ this.componentProps[attr] = component[`_${attr}`];
292
+ } else {
293
+ this.componentProps[attr] = component[attr];
294
+ }
295
+ });
296
+
297
+ // ✅ Crear UI sin interferencias
298
+ this.updateDebuggerContent();
299
+ this.debuggerContainer.classList.add('active');
300
+ }
301
+
302
+ updateDebuggerContent() {
303
+ this.updatePropsTab();
304
+ this.updateInfoTab();
305
+ }
306
+
307
+ /**
308
+ * Render props tab content.
309
+ * @returns {void}
310
+ */
311
+ updatePropsTab() {
312
+ const propsContainer = this.querySelector('.props-container');
313
+ if (!propsContainer) {
314
+ return;
315
+ }
316
+
317
+ propsContainer.innerHTML = '';
318
+
319
+ const realComponentProps = this.getComponentPropsForDebugger(this.currentComponent);
320
+ const ComponentClass = this.currentComponent.constructor;
321
+ const configuredProps = ComponentClass.props || {};
322
+
323
+ realComponentProps.forEach(prop => {
324
+ const propElement = this.createPropElement(prop, configuredProps[prop]);
325
+ propsContainer.appendChild(propElement);
326
+ });
327
+ }
328
+
329
+ /**
330
+ * Build a prop row element.
331
+ * @param {string} prop
332
+ * @param {Object} [config]
333
+ * @returns {HTMLElement}
334
+ */
335
+ createPropElement(prop, config = {}) {
336
+ const propWrapper = document.createElement('div');
337
+ propWrapper.className = 'prop-item';
338
+ propWrapper.dataset.prop = prop;
339
+
340
+ const currentValue = this.currentComponent[prop];
341
+ const valueType = this.getValueType(currentValue);
342
+
343
+ // Status based on usage
344
+ let status, statusClass;
345
+ if (currentValue !== undefined && currentValue !== null) {
346
+ status = 'Used';
347
+ statusClass = 'status-used';
348
+ } else if (config.required) {
349
+ status = 'Missing';
350
+ statusClass = 'status-missing';
351
+ } else {
352
+ status = 'Optional';
353
+ statusClass = 'status-optional';
354
+ }
355
+
356
+ propWrapper.innerHTML = `
357
+ <div class="prop-header">
358
+ <div class="prop-title">
359
+ <strong>${prop}</strong>
360
+ <span class="prop-type">${valueType}</span>
361
+ </div>
362
+ <div class="prop-status ${statusClass}">${status}</div>
363
+ </div>
364
+ <div class="prop-input">
365
+ ${this.createInputForType(prop, currentValue, valueType, config)}
366
+ </div>
367
+ ${config.default !== undefined ? `<div class="default-value">Default: ${JSON.stringify(config.default)}</div>` : ''}
368
+ `;
369
+
370
+ return propWrapper;
371
+ }
372
+
373
+ /**
374
+ * Build input HTML for a prop type.
375
+ * @param {string} prop
376
+ * @param {any} value
377
+ * @param {string} type
378
+ * @param {Object} [config]
379
+ * @returns {string}
380
+ */
381
+ createInputForType(prop, value, type, config = {}) {
382
+ const serializedValue = this.serializeValue(value);
383
+
384
+ if (type === 'boolean') {
385
+ return `
386
+ <div class="input-group">
387
+ <input type="checkbox"
388
+ class="prop-control debugger-input"
389
+ data-prop="${prop}"
390
+ ${value ? 'checked' : ''}
391
+ data-debugger-input="true">
392
+ <span class="checkbox-label">${value ? 'true' : 'false'}</span>
393
+ </div>
394
+ `;
395
+ } else if (type === 'number') {
396
+ return `
397
+ <div class="input-group">
398
+ <input type="number"
399
+ class="prop-control debugger-input"
400
+ data-prop="${prop}"
401
+ value="${serializedValue}"
402
+ step="any"
403
+ placeholder="Enter number..."
404
+ data-debugger-input="true">
405
+ </div>
406
+ `;
407
+ } else if (type === 'object' || type === 'array' || type === 'function') {
408
+ return `
409
+ <div class="input-group">
410
+ <input type="text"
411
+ class="prop-control debugger-input"
412
+ data-prop="${prop}"
413
+ value="${serializedValue}"
414
+ readonly
415
+ title="Click edit button to modify"
416
+ data-debugger-input="true">
417
+ <button class="edit-btn" onclick="slice.debugger.openAdvancedEditor('${prop}', '${type}')">âœī¸</button>
418
+ </div>
419
+ `;
420
+ } else {
421
+ return `
422
+ <div class="input-group">
423
+ <input type="text"
424
+ class="prop-control debugger-input"
425
+ data-prop="${prop}"
426
+ value="${serializedValue}"
427
+ placeholder="Enter value..."
428
+ data-debugger-input="true">
429
+ </div>
430
+ `;
431
+ }
432
+ }
433
+
434
+ /**
435
+ * Apply a single prop change from an input.
436
+ * @param {HTMLInputElement} inputElement
437
+ * @returns {void}
438
+ */
439
+ applyPropertyChange(inputElement) {
440
+ const prop = inputElement.dataset.prop;
441
+ if (!prop) return;
442
+
443
+ let newValue;
444
+
445
+ if (inputElement.type === 'checkbox') {
446
+ newValue = inputElement.checked;
447
+ const label = inputElement.parentNode.querySelector('.checkbox-label');
448
+ if (label) {
449
+ label.textContent = newValue ? 'true' : 'false';
450
+ }
451
+ } else if (inputElement.type === 'number') {
452
+ newValue = Number(inputElement.value);
453
+ } else {
454
+ newValue = inputElement.value;
455
+
456
+ // Convert string values
457
+ if (newValue === 'true') newValue = true;
458
+ if (newValue === 'false') newValue = false;
459
+ if (!isNaN(newValue) && newValue !== '' && newValue !== null) newValue = Number(newValue);
460
+ }
461
+
462
+ const oldValue = this.currentComponent[prop];
463
+
464
+ this.currentComponent[prop] = newValue;
465
+ slice.logger.logInfo('Debugger', `Updated ${prop}: ${oldValue} → ${newValue}`);
466
+
467
+ this.showVisualFeedback(inputElement);
468
+ }
469
+
470
+ /**
471
+ * Show temporary highlight on updated input.
472
+ * @param {HTMLElement} inputElement
473
+ * @returns {void}
474
+ */
475
+ showVisualFeedback(inputElement) {
476
+ const originalBorder = inputElement.style.borderColor;
477
+ const originalBoxShadow = inputElement.style.boxShadow;
478
+
479
+ inputElement.style.borderColor = '#4CAF50';
480
+ inputElement.style.boxShadow = '0 0 0 2px rgba(76, 175, 80, 0.3)';
481
+
482
+ setTimeout(() => {
483
+ inputElement.style.borderColor = originalBorder;
484
+ inputElement.style.boxShadow = originalBoxShadow;
485
+ }, 1500);
486
+ }
487
+
488
+ /**
489
+ * Apply all editable prop changes in the panel.
490
+ * @returns {void}
491
+ */
492
+ applyAllChanges() {
493
+ const inputs = this.querySelectorAll('.prop-control:not([readonly])');
494
+ let changeCount = 0;
495
+
496
+ inputs.forEach(input => {
497
+ if (!input.readOnly) {
498
+ this.applyPropertyChange(input);
499
+ changeCount++;
500
+ }
501
+ });
502
+
503
+ slice.logger.logInfo('Debugger', `Applied ${changeCount} property changes`);
504
+ this.showApplyFeedback(changeCount);
505
+ }
506
+
507
+ /**
508
+ * Show apply feedback in button.
509
+ * @param {number} changeCount
510
+ * @returns {void}
511
+ */
512
+ showApplyFeedback(changeCount) {
513
+ const applyBtn = this.querySelector('#apply-changes');
514
+ if (!applyBtn) return;
515
+
516
+ const originalText = applyBtn.textContent;
517
+
518
+ if (changeCount > 0) {
519
+ applyBtn.textContent = `✅ Applied ${changeCount} changes!`;
520
+ applyBtn.style.background = '#4CAF50';
521
+ } else {
522
+ applyBtn.textContent = '✅ No changes to apply';
523
+ applyBtn.style.background = '#9E9E9E';
524
+ }
525
+
526
+ setTimeout(() => {
527
+ applyBtn.textContent = originalText;
528
+ applyBtn.style.background = '';
529
+ }, 2000);
530
+ }
531
+
532
+ /**
533
+ * Open advanced editor for objects/functions.
534
+ * @param {string} prop
535
+ * @param {string} type
536
+ * @returns {void}
537
+ */
538
+ openAdvancedEditor(prop, type) {
539
+ this.currentEditingProp = prop;
540
+ this.currentEditingType = this.normalizeEditorType(type) || 'json';
541
+
542
+ const value = this.currentComponent[prop];
543
+
544
+ this.modalTitle.textContent = `Edit ${prop} (${this.currentEditingType})`;
545
+
546
+ this.querySelectorAll('.type-btn').forEach(btn => {
547
+ btn.classList.toggle('active', btn.dataset.type === this.currentEditingType);
548
+ });
549
+
550
+ if (this.currentEditingType === 'function') {
551
+ if (typeof value === 'function') {
552
+ this.propertyEditor.value = value.toString();
553
+ } else {
554
+ this.propertyEditor.value = 'function() {\n // Your code here\n}';
555
+ }
556
+ } else {
557
+ try {
558
+ this.propertyEditor.value = JSON.stringify(value, null, 2);
559
+ } catch (error) {
560
+ this.propertyEditor.value = 'null';
561
+ }
562
+ }
563
+
564
+ this.editorModal.classList.add('active');
565
+ this.propertyEditor.focus();
566
+ }
567
+
568
+ /**
569
+ * Validate editor contents for current type.
570
+ * @returns {void}
571
+ */
572
+ validateEditor() {
573
+ const value = this.propertyEditor.value.trim();
574
+ const type = this.normalizeEditorType(this.currentEditingType) || 'json';
575
+
576
+ try {
577
+ if (type === 'function') {
578
+ new Function('return ' + value)();
579
+ } else {
580
+ JSON.parse(value);
581
+ }
582
+
583
+ this.validationMessage.textContent = '✅ Valid syntax';
584
+ this.validationMessage.style.color = '#4CAF50';
585
+ this.querySelector('#modal-save').disabled = false;
586
+ } catch (error) {
587
+ this.validationMessage.textContent = `❌ ${error.message}`;
588
+ this.validationMessage.style.color = '#F44336';
589
+ this.querySelector('#modal-save').disabled = true;
590
+ }
591
+ }
592
+
593
+ /**
594
+ * Save editor value to component.
595
+ * @returns {void}
596
+ */
597
+ savePropertyValue() {
598
+ const value = this.propertyEditor.value.trim();
599
+ const type = this.normalizeEditorType(this.currentEditingType) || 'json';
600
+
601
+ try {
602
+ let newValue;
603
+
604
+ if (type === 'function') {
605
+ newValue = new Function('return ' + value)();
606
+ } else {
607
+ newValue = JSON.parse(value);
608
+ }
609
+
610
+ this.currentComponent[this.currentEditingProp] = newValue;
611
+ this.closeModal();
612
+
613
+ slice.logger.logInfo('Debugger', `Updated ${this.currentEditingProp} via advanced editor`);
614
+
615
+ const input = this.querySelector(`[data-prop="${this.currentEditingProp}"]`);
616
+ if (input) {
617
+ input.value = this.serializeValue(newValue);
618
+ this.showVisualFeedback(input);
619
+ }
620
+
621
+ } catch (error) {
622
+ this.validationMessage.textContent = `❌ ${error.message}`;
623
+ this.validationMessage.style.color = '#F44336';
624
+ }
625
+ }
626
+
627
+ /**
628
+ * Close the advanced editor modal.
629
+ * @returns {void}
630
+ */
631
+ closeModal() {
632
+ this.editorModal.classList.remove('active');
633
+ this.currentEditingProp = null;
634
+ this.currentEditingType = null;
635
+ this.validationMessage.textContent = '';
636
+ }
637
+
638
+ /**
639
+ * Reset props to defaults (if defined in static props).
640
+ * @returns {void}
641
+ */
642
+ resetValues() {
643
+ const ComponentClass = this.currentComponent.constructor;
644
+ const configuredProps = ComponentClass.props || {};
645
+ let resetCount = 0;
646
+
647
+ Object.entries(configuredProps).forEach(([prop, config]) => {
648
+ if (config.default !== undefined) {
649
+ this.currentComponent[prop] = config.default;
650
+
651
+ const input = this.querySelector(`[data-prop="${prop}"]`);
652
+ if (input && !input.readOnly) {
653
+ if (input.type === 'checkbox') {
654
+ input.checked = config.default;
655
+ const label = input.parentNode.querySelector('.checkbox-label');
656
+ if (label) {
657
+ label.textContent = config.default ? 'true' : 'false';
658
+ }
659
+ } else {
660
+ input.value = this.serializeValue(config.default);
661
+ }
662
+ resetCount++;
663
+ }
664
+ }
665
+ });
666
+
667
+ slice.logger.logInfo('Debugger', 'Reset values to defaults');
668
+ this.showResetFeedback(resetCount);
669
+ }
670
+
671
+ /**
672
+ * Show reset feedback in button.
673
+ * @param {number} resetCount
674
+ * @returns {void}
675
+ */
676
+ showResetFeedback(resetCount) {
677
+ const resetBtn = this.querySelector('#reset-values');
678
+ if (!resetBtn) return;
679
+
680
+ const originalText = resetBtn.textContent;
681
+
682
+ resetBtn.textContent = `🔄 Reset ${resetCount} values!`;
683
+ resetBtn.style.background = '#FF9800';
684
+
685
+ setTimeout(() => {
686
+ resetBtn.textContent = originalText;
687
+ resetBtn.style.background = '';
688
+ }, 1500);
689
+ }
690
+
691
+ /**
692
+ * Render info tab content.
693
+ * @returns {void}
694
+ */
695
+ updateInfoTab() {
696
+ const infoContainer = this.querySelector('.info-list');
697
+ if (!infoContainer) return;
698
+
699
+ const component = this.currentComponent;
700
+
701
+ const info = [
702
+ { label: 'Component Type', value: component.constructor.name },
703
+ { label: 'Slice ID', value: component.sliceId || 'Not assigned' },
704
+ { label: 'Tag Name', value: component.tagName },
705
+ { label: 'Connected', value: component.isConnected ? 'Yes' : 'No' },
706
+ { label: 'Props Count', value: Object.keys(this.componentProps).length },
707
+ { label: 'Children', value: component.children.length }
708
+ ];
709
+
710
+ infoContainer.innerHTML = info.map(item => `
711
+ <div class="info-item">
712
+ <span class="info-label">${item.label}</span>
713
+ <span class="info-value">${item.value}</span>
714
+ </div>
715
+ `).join('');
716
+ }
717
+
718
+ /**
719
+ * Get a simple value type label.
720
+ * @param {any} value
721
+ * @returns {string}
722
+ */
723
+ getValueType(value) {
724
+ if (value === null) return 'null';
725
+ if (value === undefined) return 'undefined';
726
+ if (Array.isArray(value)) return 'array';
727
+ return typeof value;
728
+ }
729
+
730
+ /**
731
+ * Serialize a value for input display.
732
+ * @param {any} value
733
+ * @returns {string}
734
+ */
735
+ serializeValue(value) {
736
+ if (value === null || value === undefined) {
737
+ return '';
738
+ }
739
+
740
+ if (typeof value === 'object' || typeof value === 'function') {
741
+ try {
742
+ return JSON.stringify(value);
743
+ } catch {
744
+ return String(value);
745
+ }
746
+ }
747
+
748
+ return String(value);
749
+ }
750
+
751
+ /**
752
+ * Resolve which props to show in the debugger.
753
+ * @param {HTMLElement} component
754
+ * @returns {string[]}
755
+ */
756
+ getComponentPropsForDebugger(component) {
757
+ const ComponentClass = component.constructor;
758
+
759
+ if (ComponentClass.props) {
760
+ return Object.keys(ComponentClass.props);
761
+ }
762
+
763
+ if (component.debuggerProps && Array.isArray(component.debuggerProps)) {
764
+ return component.debuggerProps;
765
+ }
766
+
767
+ return this.detectUsedProps(component);
768
+ }
769
+
770
+ /**
771
+ * Detect props from backing fields on the component.
772
+ * @param {HTMLElement} component
773
+ * @returns {string[]}
774
+ */
775
+ detectUsedProps(component) {
776
+ const usedProps = [];
777
+
778
+ Object.getOwnPropertyNames(component).forEach(key => {
779
+ if (key.startsWith('_') && key !== '_isActive') {
780
+ const propName = key.substring(1);
781
+ usedProps.push(propName);
782
+ }
783
+ });
784
+
785
+ return usedProps;
786
+ }
787
+
788
+ /**
789
+ * Hide the debugger UI.
790
+ * @returns {void}
791
+ */
792
+ hide() {
793
+ this.debuggerContainer.classList.remove('active');
794
+ this.closeModal();
795
+ }
796
+ }
797
+
798
+ customElements.define('slice-debugger', Debugger);
799
+
800
+ function productionOnlyCSS(){
801
+ return `
802
+ /* ============================================================
803
+ Slice Instruments — Component Inspector
804
+ All selectors are scoped under the <slice-debugger> tag so the
805
+ panel never clashes with (or leaks into) app styles. Tokens live
806
+ on the tag so both #debugger-container and the sibling
807
+ #editor-modal inherit them. Chrome colors are static; only the
808
+ accent reads the theme (--primary-color) with a static fallback.
809
+ ============================================================ */
810
+ slice-debugger {
811
+ --si-accent: var(--primary-color, #6ee7ff);
812
+ --si-accent-rgb: var(--primary-color-rgb, 110, 231, 255);
813
+ --si-surface: rgba(17, 19, 28, 0.88);
814
+ --si-raised: rgba(255, 255, 255, 0.035);
815
+ --si-raised-2: rgba(255, 255, 255, 0.06);
816
+ --si-inset: rgba(0, 0, 0, 0.28);
817
+ --si-border: rgba(255, 255, 255, 0.09);
818
+ --si-text: #e8eaf2;
819
+ --si-dim: #888fa6;
820
+ --si-danger: #ff6b6b;
821
+ --si-success: #46d39a;
822
+ --si-mono: ui-monospace, 'SF Mono', 'JetBrains Mono', 'Cascadia Code', Menlo, Consolas, monospace;
823
+ --si-sans: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
824
+ }
825
+
826
+ slice-debugger *,
827
+ slice-debugger *::before,
828
+ slice-debugger *::after { box-sizing: border-box; }
829
+
830
+ slice-debugger #debugger-container {
831
+ font-family: var(--si-sans);
832
+ display: none;
833
+ position: fixed;
834
+ top: 20px;
835
+ right: 20px;
836
+ width: 430px;
837
+ height: 85vh;
838
+ max-height: 85vh;
839
+ background: var(--si-surface);
840
+ border: 1px solid var(--si-border);
841
+ border-radius: 16px;
842
+ box-shadow:
843
+ 0 30px 70px -16px rgba(0, 0, 0, 0.6),
844
+ 0 0 0 1px rgba(0, 0, 0, 0.2),
845
+ 0 0 46px -20px rgba(var(--si-accent-rgb), 0.6);
846
+ color: var(--si-text);
847
+ z-index: 10000;
848
+ overflow: hidden;
849
+ -webkit-backdrop-filter: blur(24px) saturate(1.3);
850
+ backdrop-filter: blur(24px) saturate(1.3);
851
+ }
852
+
853
+ slice-debugger #debugger-container.active {
854
+ display: flex;
855
+ flex-direction: column;
856
+ animation: si-inspector-in 0.28s cubic-bezier(0.16, 1, 0.3, 1);
857
+ }
858
+
859
+ @keyframes si-inspector-in {
860
+ from { opacity: 0; transform: translateY(12px) scale(0.985); }
861
+ to { opacity: 1; transform: translateY(0) scale(1); }
862
+ }
863
+
864
+ slice-debugger #debugger-container::before {
865
+ content: '';
866
+ position: absolute;
867
+ left: 0; top: 0; bottom: 0;
868
+ width: 2px;
869
+ background: linear-gradient(180deg, var(--si-accent), transparent 60%);
870
+ opacity: 0.9;
871
+ pointer-events: none;
872
+ z-index: 2;
873
+ }
874
+
875
+ slice-debugger .debugger-header {
876
+ background:
877
+ radial-gradient(140% 160% at 0% 0%, rgba(var(--si-accent-rgb), 0.14), transparent 55%),
878
+ var(--si-raised);
879
+ color: var(--si-text);
880
+ padding: 13px 15px;
881
+ border-bottom: 1px solid var(--si-border);
882
+ user-select: none;
883
+ cursor: grab;
884
+ }
885
+ slice-debugger .debugger-header:active { cursor: grabbing; }
886
+
887
+ slice-debugger .header-content {
888
+ display: flex;
889
+ justify-content: space-between;
890
+ align-items: center;
891
+ }
892
+
893
+ slice-debugger .component-info {
894
+ display: flex;
895
+ align-items: center;
896
+ gap: 11px;
897
+ min-width: 0;
898
+ }
899
+
900
+ slice-debugger .component-icon {
901
+ font-size: 17px;
902
+ width: 30px; height: 30px;
903
+ display: flex; align-items: center; justify-content: center;
904
+ border-radius: 9px;
905
+ background: rgba(var(--si-accent-rgb), 0.12);
906
+ border: 1px solid rgba(var(--si-accent-rgb), 0.28);
907
+ color: var(--si-accent);
908
+ flex-shrink: 0;
909
+ }
910
+
911
+ slice-debugger .component-name {
912
+ font-size: 13px;
913
+ font-weight: 600;
914
+ font-family: var(--si-mono);
915
+ color: var(--si-text);
916
+ margin-bottom: 2px;
917
+ overflow: hidden;
918
+ text-overflow: ellipsis;
919
+ white-space: nowrap;
920
+ }
921
+
922
+ slice-debugger .component-id {
923
+ font-size: 10.5px;
924
+ font-family: var(--si-mono);
925
+ color: var(--si-dim);
926
+ letter-spacing: 0.02em;
927
+ }
928
+
929
+ slice-debugger .header-actions { display: flex; gap: 6px; }
930
+
931
+ slice-debugger .minimize-btn,
932
+ slice-debugger #close-debugger {
933
+ background: var(--si-raised);
934
+ border: 1px solid var(--si-border);
935
+ color: var(--si-dim);
936
+ width: 28px;
937
+ height: 28px;
938
+ border-radius: 8px;
939
+ cursor: pointer;
940
+ display: flex;
941
+ align-items: center;
942
+ justify-content: center;
943
+ font-size: 15px;
944
+ font-weight: 500;
945
+ line-height: 1;
946
+ transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
947
+ }
948
+ slice-debugger .minimize-btn:hover,
949
+ slice-debugger #close-debugger:hover {
950
+ color: var(--si-text);
951
+ background: var(--si-raised-2);
952
+ border-color: rgba(var(--si-accent-rgb), 0.5);
953
+ }
954
+ slice-debugger #close-debugger:hover { color: var(--si-danger); border-color: rgba(255, 107, 107, 0.5); }
955
+ slice-debugger .minimize-btn:active,
956
+ slice-debugger #close-debugger:active { transform: scale(0.92); }
957
+
958
+ slice-debugger .debugger-content {
959
+ flex: 1;
960
+ display: flex;
961
+ flex-direction: column;
962
+ overflow: hidden;
963
+ }
964
+
965
+ slice-debugger .tabs-container { border-bottom: 1px solid var(--si-border); }
966
+
967
+ slice-debugger .tab-nav {
968
+ display: flex;
969
+ background: var(--si-inset);
970
+ padding: 0 6px;
971
+ gap: 2px;
972
+ }
973
+
974
+ slice-debugger .tab-btn {
975
+ flex: 1;
976
+ padding: 11px 14px;
977
+ border: none;
978
+ background: transparent;
979
+ color: var(--si-dim);
980
+ font-family: var(--si-mono);
981
+ font-size: 11px;
982
+ letter-spacing: 0.08em;
983
+ text-transform: uppercase;
984
+ cursor: pointer;
985
+ transition: color 0.18s ease, border-color 0.18s ease;
986
+ border-bottom: 2px solid transparent;
987
+ }
988
+ slice-debugger .tab-btn:hover { color: var(--si-text); }
989
+ slice-debugger .tab-btn.active {
990
+ color: var(--si-accent);
991
+ border-bottom-color: var(--si-accent);
992
+ }
993
+
994
+ slice-debugger .tab-content {
995
+ flex: 1;
996
+ overflow: hidden;
997
+ height: calc(85vh - 104px);
998
+ }
999
+
1000
+ slice-debugger .tab-pane {
1001
+ display: none;
1002
+ height: 100%;
1003
+ overflow-y: auto;
1004
+ overflow-x: hidden;
1005
+ padding: 16px;
1006
+ }
1007
+ slice-debugger .tab-pane.active { display: block; }
1008
+
1009
+ slice-debugger .tab-pane::-webkit-scrollbar { width: 8px; }
1010
+ slice-debugger .tab-pane::-webkit-scrollbar-track { background: transparent; }
1011
+ slice-debugger .tab-pane::-webkit-scrollbar-thumb {
1012
+ background: var(--si-raised-2);
1013
+ border-radius: 8px;
1014
+ border: 2px solid transparent;
1015
+ background-clip: padding-box;
1016
+ }
1017
+ slice-debugger .tab-pane::-webkit-scrollbar-thumb:hover { background: rgba(var(--si-accent-rgb), 0.4); background-clip: padding-box; }
1018
+
1019
+ slice-debugger .props-container {
1020
+ display: flex;
1021
+ flex-direction: column;
1022
+ gap: 12px;
1023
+ margin-bottom: 16px;
1024
+ }
1025
+
1026
+ slice-debugger .props-actions {
1027
+ border-top: 1px solid var(--si-border);
1028
+ padding-top: 16px;
1029
+ margin-top: 8px;
1030
+ }
1031
+
1032
+ slice-debugger .actions-note {
1033
+ margin-top: 12px;
1034
+ padding: 9px 12px;
1035
+ background: var(--si-raised);
1036
+ border-radius: 8px;
1037
+ border: 1px solid var(--si-border);
1038
+ }
1039
+ slice-debugger .actions-note small {
1040
+ color: var(--si-dim);
1041
+ font-size: 11px;
1042
+ display: flex;
1043
+ align-items: center;
1044
+ gap: 6px;
1045
+ }
1046
+
1047
+ slice-debugger .props-section {
1048
+ background: var(--si-raised);
1049
+ border: 1px solid var(--si-border);
1050
+ border-radius: 12px;
1051
+ padding: 13px;
1052
+ }
1053
+
1054
+ slice-debugger .section-title {
1055
+ font-size: 10.5px;
1056
+ font-weight: 600;
1057
+ letter-spacing: 0.1em;
1058
+ text-transform: uppercase;
1059
+ color: var(--si-dim);
1060
+ margin-bottom: 12px;
1061
+ display: flex;
1062
+ align-items: center;
1063
+ gap: 6px;
1064
+ }
1065
+
1066
+ slice-debugger .prop-item {
1067
+ display: flex;
1068
+ flex-direction: column;
1069
+ gap: 6px;
1070
+ padding: 12px;
1071
+ background: var(--si-inset);
1072
+ border: 1px solid var(--si-border);
1073
+ border-left: 2px solid transparent;
1074
+ border-radius: 10px;
1075
+ margin-bottom: 8px;
1076
+ transition: border-color 0.18s ease, background 0.18s ease;
1077
+ }
1078
+ slice-debugger .prop-item:hover {
1079
+ border-left-color: var(--si-accent);
1080
+ background: rgba(0, 0, 0, 0.34);
1081
+ }
1082
+
1083
+ slice-debugger .prop-header {
1084
+ display: flex;
1085
+ justify-content: space-between;
1086
+ align-items: center;
1087
+ gap: 8px;
1088
+ }
1089
+
1090
+ slice-debugger .prop-name {
1091
+ font-size: 12.5px;
1092
+ font-weight: 600;
1093
+ font-family: var(--si-mono);
1094
+ color: var(--si-text);
1095
+ }
1096
+ slice-debugger .prop-name.required::after {
1097
+ content: " *";
1098
+ color: var(--si-danger);
1099
+ }
1100
+
1101
+ slice-debugger .prop-meta {
1102
+ display: flex;
1103
+ align-items: center;
1104
+ gap: 8px;
1105
+ }
1106
+
1107
+ slice-debugger .prop-type {
1108
+ font-size: 10px;
1109
+ padding: 2px 7px;
1110
+ background: rgba(var(--si-accent-rgb), 0.12);
1111
+ color: var(--si-accent);
1112
+ border: 1px solid rgba(var(--si-accent-rgb), 0.28);
1113
+ border-radius: 999px;
1114
+ font-family: var(--si-mono);
1115
+ font-weight: 500;
1116
+ letter-spacing: 0.02em;
1117
+ }
1118
+
1119
+ slice-debugger .prop-status { font-size: 14px; line-height: 1; }
1120
+ slice-debugger .status-used { color: var(--si-success); }
1121
+ slice-debugger .status-missing { color: var(--si-danger); }
1122
+ slice-debugger .status-optional { color: var(--si-dim); }
1123
+
1124
+ slice-debugger .prop-input { margin-top: 6px; }
1125
+ slice-debugger .input-group { position: relative; }
1126
+
1127
+ slice-debugger .prop-control {
1128
+ width: 100%;
1129
+ padding: 9px 34px 9px 11px;
1130
+ border: 1px solid var(--si-border);
1131
+ border-radius: 8px;
1132
+ background: rgba(0, 0, 0, 0.35);
1133
+ color: var(--si-text);
1134
+ font-size: 12.5px;
1135
+ font-family: var(--si-mono);
1136
+ transition: border-color 0.15s ease, box-shadow 0.15s ease;
1137
+ }
1138
+ slice-debugger .prop-control::placeholder { color: var(--si-dim); }
1139
+ slice-debugger .prop-control:focus {
1140
+ outline: none;
1141
+ border-color: rgba(var(--si-accent-rgb), 0.6);
1142
+ box-shadow: 0 0 0 3px rgba(var(--si-accent-rgb), 0.13);
1143
+ }
1144
+ slice-debugger .prop-control:disabled { opacity: 0.5; cursor: not-allowed; }
1145
+ slice-debugger .prop-control[readonly] { background: var(--si-raised); cursor: pointer; }
1146
+ slice-debugger .prop-control[readonly]:focus { border-color: rgba(var(--si-accent-rgb), 0.6); }
1147
+ slice-debugger .prop-control[type="checkbox"] {
1148
+ width: 18px;
1149
+ height: 18px;
1150
+ padding: 0;
1151
+ margin: 0;
1152
+ cursor: pointer;
1153
+ accent-color: var(--si-accent);
1154
+ }
1155
+
1156
+ slice-debugger .edit-btn {
1157
+ position: absolute;
1158
+ right: 5px;
1159
+ top: 50%;
1160
+ transform: translateY(-50%);
1161
+ background: rgba(var(--si-accent-rgb), 0.14);
1162
+ border: 1px solid rgba(var(--si-accent-rgb), 0.3);
1163
+ color: var(--si-accent);
1164
+ width: 25px;
1165
+ height: 25px;
1166
+ border-radius: 6px;
1167
+ cursor: pointer;
1168
+ font-size: 12px;
1169
+ display: flex;
1170
+ align-items: center;
1171
+ justify-content: center;
1172
+ transition: background 0.15s ease, transform 0.15s ease;
1173
+ }
1174
+ slice-debugger .edit-btn:hover { background: rgba(var(--si-accent-rgb), 0.26); }
1175
+ slice-debugger .edit-btn:active { transform: translateY(-50%) scale(0.9); }
1176
+
1177
+ slice-debugger .default-value {
1178
+ font-size: 10.5px;
1179
+ color: var(--si-dim);
1180
+ font-family: var(--si-mono);
1181
+ margin-top: 2px;
1182
+ }
1183
+
1184
+ slice-debugger .info-list { display: flex; flex-direction: column; gap: 8px; }
1185
+
1186
+ slice-debugger .info-item {
1187
+ display: flex;
1188
+ justify-content: space-between;
1189
+ align-items: center;
1190
+ gap: 12px;
1191
+ padding: 12px 13px;
1192
+ background: var(--si-raised);
1193
+ border-radius: 10px;
1194
+ border: 1px solid var(--si-border);
1195
+ }
1196
+
1197
+ slice-debugger .info-label {
1198
+ font-weight: 500;
1199
+ color: var(--si-dim);
1200
+ font-size: 11px;
1201
+ letter-spacing: 0.04em;
1202
+ text-transform: uppercase;
1203
+ }
1204
+
1205
+ slice-debugger .info-value {
1206
+ color: var(--si-text);
1207
+ font-family: var(--si-mono);
1208
+ font-size: 12px;
1209
+ text-align: right;
1210
+ overflow: hidden;
1211
+ text-overflow: ellipsis;
1212
+ white-space: nowrap;
1213
+ }
1214
+
1215
+ slice-debugger .actions-container { display: flex; flex-direction: column; gap: 16px; }
1216
+ slice-debugger .action-buttons { display: flex; flex-direction: column; gap: 8px; }
1217
+
1218
+ slice-debugger .action-btn {
1219
+ padding: 12px 16px;
1220
+ border: 1px solid transparent;
1221
+ border-radius: 9px;
1222
+ font-size: 12.5px;
1223
+ font-weight: 600;
1224
+ cursor: pointer;
1225
+ transition: background 0.15s ease, border-color 0.15s ease, transform 0.1s ease;
1226
+ display: flex;
1227
+ align-items: center;
1228
+ justify-content: center;
1229
+ gap: 8px;
1230
+ }
1231
+ slice-debugger .action-btn:active { transform: scale(0.99); }
1232
+ slice-debugger .action-btn.primary { background: var(--si-accent); color: #0b1020; }
1233
+ slice-debugger .action-btn.primary:hover { filter: brightness(1.08); }
1234
+ slice-debugger .action-btn.secondary {
1235
+ background: rgba(var(--si-accent-rgb), 0.12);
1236
+ color: var(--si-accent);
1237
+ border-color: rgba(var(--si-accent-rgb), 0.3);
1238
+ }
1239
+ slice-debugger .action-btn.secondary:hover { background: rgba(var(--si-accent-rgb), 0.2); }
1240
+ slice-debugger .action-btn.tertiary {
1241
+ background: var(--si-raised);
1242
+ color: var(--si-text);
1243
+ border-color: var(--si-border);
1244
+ }
1245
+ slice-debugger .action-btn.tertiary:hover { background: var(--si-raised-2); }
1246
+
1247
+ slice-debugger .editor-modal {
1248
+ display: none;
1249
+ position: fixed;
1250
+ top: 0;
1251
+ left: 0;
1252
+ width: 100%;
1253
+ height: 100%;
1254
+ background: rgba(6, 8, 14, 0.62);
1255
+ z-index: 20000;
1256
+ -webkit-backdrop-filter: blur(6px);
1257
+ backdrop-filter: blur(6px);
1258
+ }
1259
+ slice-debugger .editor-modal.active {
1260
+ display: flex;
1261
+ align-items: center;
1262
+ justify-content: center;
1263
+ animation: si-inspector-in 0.2s ease;
1264
+ }
1265
+
1266
+ slice-debugger .modal-content {
1267
+ background: var(--si-surface);
1268
+ border-radius: 16px;
1269
+ width: 90%;
1270
+ max-width: 620px;
1271
+ max-height: 80%;
1272
+ display: flex;
1273
+ flex-direction: column;
1274
+ box-shadow: 0 30px 70px -16px rgba(0, 0, 0, 0.7);
1275
+ border: 1px solid var(--si-border);
1276
+ -webkit-backdrop-filter: blur(24px);
1277
+ backdrop-filter: blur(24px);
1278
+ }
1279
+
1280
+ slice-debugger .modal-header {
1281
+ padding: 15px 20px;
1282
+ background: var(--si-raised);
1283
+ border-radius: 16px 16px 0 0;
1284
+ border-bottom: 1px solid var(--si-border);
1285
+ display: flex;
1286
+ justify-content: space-between;
1287
+ align-items: center;
1288
+ }
1289
+ slice-debugger .modal-header h3 {
1290
+ margin: 0;
1291
+ font-size: 13px;
1292
+ font-weight: 600;
1293
+ font-family: var(--si-mono);
1294
+ letter-spacing: 0.04em;
1295
+ color: var(--si-text);
1296
+ }
1297
+
1298
+ slice-debugger .modal-close {
1299
+ background: var(--si-raised);
1300
+ border: 1px solid var(--si-border);
1301
+ font-size: 16px;
1302
+ color: var(--si-dim);
1303
+ cursor: pointer;
1304
+ width: 30px;
1305
+ height: 30px;
1306
+ border-radius: 8px;
1307
+ display: flex;
1308
+ align-items: center;
1309
+ justify-content: center;
1310
+ transition: color 0.15s ease, background 0.15s ease;
1311
+ }
1312
+ slice-debugger .modal-close:hover { color: var(--si-danger); background: var(--si-raised-2); }
1313
+
1314
+ slice-debugger .modal-body {
1315
+ flex: 1;
1316
+ padding: 20px;
1317
+ display: flex;
1318
+ flex-direction: column;
1319
+ gap: 16px;
1320
+ overflow: hidden;
1321
+ }
1322
+
1323
+ slice-debugger .editor-type-selector {
1324
+ display: flex;
1325
+ gap: 4px;
1326
+ background: var(--si-inset);
1327
+ padding: 4px;
1328
+ border-radius: 9px;
1329
+ border: 1px solid var(--si-border);
1330
+ }
1331
+
1332
+ slice-debugger .type-btn {
1333
+ flex: 1;
1334
+ padding: 8px 12px;
1335
+ border: none;
1336
+ background: transparent;
1337
+ color: var(--si-dim);
1338
+ font-size: 11px;
1339
+ font-family: var(--si-mono);
1340
+ font-weight: 500;
1341
+ cursor: pointer;
1342
+ border-radius: 6px;
1343
+ transition: color 0.15s ease, background 0.15s ease;
1344
+ }
1345
+ slice-debugger .type-btn.active {
1346
+ background: rgba(var(--si-accent-rgb), 0.16);
1347
+ color: var(--si-accent);
1348
+ }
1349
+
1350
+ slice-debugger .editor-container {
1351
+ flex: 1;
1352
+ position: relative;
1353
+ min-height: 200px;
1354
+ }
1355
+
1356
+ slice-debugger #property-editor {
1357
+ width: 100%;
1358
+ height: 100%;
1359
+ border: 1px solid var(--si-border);
1360
+ border-radius: 10px;
1361
+ padding: 14px;
1362
+ background: rgba(0, 0, 0, 0.4);
1363
+ color: var(--si-text);
1364
+ font-family: var(--si-mono);
1365
+ font-size: 12.5px;
1366
+ line-height: 1.6;
1367
+ resize: none;
1368
+ outline: none;
1369
+ min-height: 200px;
1370
+ tab-size: 2;
1371
+ }
1372
+ slice-debugger #property-editor:focus {
1373
+ border-color: rgba(var(--si-accent-rgb), 0.6);
1374
+ box-shadow: 0 0 0 3px rgba(var(--si-accent-rgb), 0.13);
1375
+ }
1376
+
1377
+ slice-debugger .validation-message {
1378
+ font-size: 11.5px;
1379
+ color: var(--si-danger);
1380
+ min-height: 18px;
1381
+ display: flex;
1382
+ align-items: center;
1383
+ gap: 6px;
1384
+ font-family: var(--si-mono);
1385
+ }
1386
+
1387
+ slice-debugger .modal-actions {
1388
+ padding: 15px 20px;
1389
+ background: var(--si-raised);
1390
+ border-radius: 0 0 16px 16px;
1391
+ border-top: 1px solid var(--si-border);
1392
+ display: flex;
1393
+ gap: 12px;
1394
+ justify-content: flex-end;
1395
+ }
1396
+
1397
+ slice-debugger .modal-btn {
1398
+ padding: 10px 20px;
1399
+ border: 1px solid transparent;
1400
+ border-radius: 8px;
1401
+ font-size: 12.5px;
1402
+ font-weight: 600;
1403
+ cursor: pointer;
1404
+ transition: background 0.15s ease, border-color 0.15s ease;
1405
+ }
1406
+ slice-debugger .modal-btn.cancel {
1407
+ background: var(--si-raised);
1408
+ color: var(--si-text);
1409
+ border-color: var(--si-border);
1410
+ }
1411
+ slice-debugger .modal-btn.cancel:hover { background: var(--si-raised-2); }
1412
+ slice-debugger .modal-btn.save { background: var(--si-success); color: #06231a; }
1413
+ slice-debugger .modal-btn.save:hover { filter: brightness(1.08); }
1414
+ slice-debugger .modal-btn.save:disabled { opacity: 0.45; cursor: not-allowed; filter: none; }
1415
+
1416
+ @media (prefers-reduced-motion: reduce) {
1417
+ slice-debugger #debugger-container.active,
1418
+ slice-debugger .editor-modal.active { animation: none; }
1419
+ }
1420
+ `
1421
+ }
1422
+
1423
+ function productionOnlyHtml(){
1424
+ return `
1425
+ <div id="debugger-container">
1426
+ <div class="debugger-header">
1427
+ <div class="header-content">
1428
+ <div class="component-info">
1429
+ <div class="component-icon">🔍</div>
1430
+ <div class="component-details">
1431
+ <div class="component-name">Component Inspector</div>
1432
+ <div class="component-id">Ready to debug</div>
1433
+ </div>
1434
+ </div>
1435
+ <div class="header-actions">
1436
+ <button class="minimize-btn" title="Minimize">−</button>
1437
+ <button id="close-debugger" title="Close">×</button>
1438
+ </div>
1439
+ </div>
1440
+ </div>
1441
+
1442
+ <div class="debugger-content">
1443
+ <div class="tabs-container">
1444
+ <div class="tab-nav">
1445
+ <button class="tab-btn active" data-tab="props">📋 Props</button>
1446
+ <button class="tab-btn" data-tab="info">â„šī¸ Info</button>
1447
+ </div>
1448
+ </div>
1449
+
1450
+ <div class="tab-content">
1451
+ <div class="tab-pane active" id="props-tab">
1452
+ <div class="props-container"></div>
1453
+ <div class="props-actions">
1454
+ <div class="action-buttons">
1455
+ <button class="action-btn primary" id="apply-changes">✅ Apply Changes</button>
1456
+ <button class="action-btn secondary" id="reset-values">🔄 Reset Values</button>
1457
+ </div>
1458
+ <div class="actions-note">
1459
+ <small>💡 Press Enter on any input to apply changes automatically</small>
1460
+ </div>
1461
+ </div>
1462
+ </div>
1463
+
1464
+ <div class="tab-pane" id="info-tab">
1465
+ <div class="info-container">
1466
+ <div class="info-list"></div>
1467
+ </div>
1468
+ </div>
1469
+ </div>
1470
+ </div>
1471
+
1472
+ <!-- Modal para editar objetos/funciones -->
1473
+ <div class="editor-modal" id="editor-modal">
1474
+ <div class="modal-content">
1475
+ <div class="modal-header">
1476
+ <h3 id="modal-title">Edit Property</h3>
1477
+ <button class="modal-close" id="modal-close">×</button>
1478
+ </div>
1479
+ <div class="modal-body">
1480
+ <div class="editor-type-selector">
1481
+ <button class="type-btn active" data-type="json">📋 JSON</button>
1482
+ <button class="type-btn" data-type="function">⚡ Function</button>
1483
+ </div>
1484
+ <div class="editor-container">
1485
+ <textarea id="property-editor" spellcheck="false"></textarea>
1486
+ </div>
1487
+ <div class="editor-footer">
1488
+ <div class="validation-message"></div>
1489
+ </div>
1490
+ </div>
1491
+ <div class="modal-actions">
1492
+ <button class="modal-btn cancel" id="modal-cancel">Cancel</button>
1493
+ <button class="modal-btn save" id="modal-save">Save Changes</button>
1494
+ </div>
1495
+ </div>
1496
+ </div>
1497
+ </div>`
1548
1498
  }