slicejs-web-framework 3.3.7 → 3.4.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.
@@ -1,1498 +1,1506 @@
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>`
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
+ ${(() => {
368
+ if (config.default === undefined) return '';
369
+ try { return `<div class="default-value">Default: ${JSON.stringify(config.default)}</div>`; }
370
+ catch { return `<div class="default-value">Default: ${String(config.default)}</div>`; }
371
+ })()}
372
+ `;
373
+
374
+ return propWrapper;
375
+ }
376
+
377
+ /**
378
+ * Build input HTML for a prop type.
379
+ * @param {string} prop
380
+ * @param {any} value
381
+ * @param {string} type
382
+ * @param {Object} [config]
383
+ * @returns {string}
384
+ */
385
+ createInputForType(prop, value, type, config = {}) {
386
+ const serializedValue = this.serializeValue(value);
387
+
388
+ if (type === 'boolean') {
389
+ return `
390
+ <div class="input-group">
391
+ <input type="checkbox"
392
+ class="prop-control debugger-input"
393
+ data-prop="${prop}"
394
+ ${value ? 'checked' : ''}
395
+ data-debugger-input="true">
396
+ <span class="checkbox-label">${value ? 'true' : 'false'}</span>
397
+ </div>
398
+ `;
399
+ } else if (type === 'number') {
400
+ return `
401
+ <div class="input-group">
402
+ <input type="number"
403
+ class="prop-control debugger-input"
404
+ data-prop="${prop}"
405
+ value="${serializedValue}"
406
+ step="any"
407
+ placeholder="Enter number..."
408
+ data-debugger-input="true">
409
+ </div>
410
+ `;
411
+ } else if (type === 'object' || type === 'array' || type === 'function') {
412
+ return `
413
+ <div class="input-group">
414
+ <input type="text"
415
+ class="prop-control debugger-input"
416
+ data-prop="${prop}"
417
+ value="${serializedValue}"
418
+ readonly
419
+ title="Click edit button to modify"
420
+ data-debugger-input="true">
421
+ <button class="edit-btn" onclick="slice.debugger.openAdvancedEditor('${prop}', '${type}')">✏️</button>
422
+ </div>
423
+ `;
424
+ } else {
425
+ return `
426
+ <div class="input-group">
427
+ <input type="text"
428
+ class="prop-control debugger-input"
429
+ data-prop="${prop}"
430
+ value="${serializedValue}"
431
+ placeholder="Enter value..."
432
+ data-debugger-input="true">
433
+ </div>
434
+ `;
435
+ }
436
+ }
437
+
438
+ /**
439
+ * Apply a single prop change from an input.
440
+ * @param {HTMLInputElement} inputElement
441
+ * @returns {void}
442
+ */
443
+ applyPropertyChange(inputElement) {
444
+ const prop = inputElement.dataset.prop;
445
+ if (!prop) return;
446
+
447
+ let newValue;
448
+
449
+ if (inputElement.type === 'checkbox') {
450
+ newValue = inputElement.checked;
451
+ const label = inputElement.parentNode.querySelector('.checkbox-label');
452
+ if (label) {
453
+ label.textContent = newValue ? 'true' : 'false';
454
+ }
455
+ } else if (inputElement.type === 'number') {
456
+ newValue = Number(inputElement.value);
457
+ } else {
458
+ newValue = inputElement.value;
459
+
460
+ // Convert string values
461
+ if (newValue === 'true') newValue = true;
462
+ if (newValue === 'false') newValue = false;
463
+ if (!isNaN(newValue) && newValue !== '' && newValue !== null) newValue = Number(newValue);
464
+ }
465
+
466
+ const oldValue = this.currentComponent[prop];
467
+
468
+ this.currentComponent[prop] = newValue;
469
+ slice.logger.logInfo('Debugger', `Updated ${prop}: ${oldValue} → ${newValue}`);
470
+
471
+ this.showVisualFeedback(inputElement);
472
+ }
473
+
474
+ /**
475
+ * Show temporary highlight on updated input.
476
+ * @param {HTMLElement} inputElement
477
+ * @returns {void}
478
+ */
479
+ showVisualFeedback(inputElement) {
480
+ const originalBorder = inputElement.style.borderColor;
481
+ const originalBoxShadow = inputElement.style.boxShadow;
482
+
483
+ inputElement.style.borderColor = '#4CAF50';
484
+ inputElement.style.boxShadow = '0 0 0 2px rgba(76, 175, 80, 0.3)';
485
+
486
+ setTimeout(() => {
487
+ inputElement.style.borderColor = originalBorder;
488
+ inputElement.style.boxShadow = originalBoxShadow;
489
+ }, 1500);
490
+ }
491
+
492
+ /**
493
+ * Apply all editable prop changes in the panel.
494
+ * @returns {void}
495
+ */
496
+ applyAllChanges() {
497
+ const inputs = this.querySelectorAll('.prop-control:not([readonly])');
498
+ let changeCount = 0;
499
+
500
+ inputs.forEach(input => {
501
+ if (!input.readOnly) {
502
+ this.applyPropertyChange(input);
503
+ changeCount++;
504
+ }
505
+ });
506
+
507
+ slice.logger.logInfo('Debugger', `Applied ${changeCount} property changes`);
508
+ this.showApplyFeedback(changeCount);
509
+ }
510
+
511
+ /**
512
+ * Show apply feedback in button.
513
+ * @param {number} changeCount
514
+ * @returns {void}
515
+ */
516
+ showApplyFeedback(changeCount) {
517
+ const applyBtn = this.querySelector('#apply-changes');
518
+ if (!applyBtn) return;
519
+
520
+ const originalText = applyBtn.textContent;
521
+
522
+ if (changeCount > 0) {
523
+ applyBtn.textContent = `✅ Applied ${changeCount} changes!`;
524
+ applyBtn.style.background = '#4CAF50';
525
+ } else {
526
+ applyBtn.textContent = '✅ No changes to apply';
527
+ applyBtn.style.background = '#9E9E9E';
528
+ }
529
+
530
+ setTimeout(() => {
531
+ applyBtn.textContent = originalText;
532
+ applyBtn.style.background = '';
533
+ }, 2000);
534
+ }
535
+
536
+ /**
537
+ * Open advanced editor for objects/functions.
538
+ * @param {string} prop
539
+ * @param {string} type
540
+ * @returns {void}
541
+ */
542
+ openAdvancedEditor(prop, type) {
543
+ this.currentEditingProp = prop;
544
+ this.currentEditingType = this.normalizeEditorType(type) || 'json';
545
+
546
+ const value = this.currentComponent[prop];
547
+
548
+ this.modalTitle.textContent = `Edit ${prop} (${this.currentEditingType})`;
549
+
550
+ this.querySelectorAll('.type-btn').forEach(btn => {
551
+ btn.classList.toggle('active', btn.dataset.type === this.currentEditingType);
552
+ });
553
+
554
+ if (this.currentEditingType === 'function') {
555
+ if (typeof value === 'function') {
556
+ this.propertyEditor.value = value.toString();
557
+ } else {
558
+ this.propertyEditor.value = 'function() {\n // Your code here\n}';
559
+ }
560
+ } else {
561
+ try {
562
+ this.propertyEditor.value = JSON.stringify(value, null, 2);
563
+ } catch (error) {
564
+ slice.logger?.warn?.('Debugger', 'Error serializing property value for editor', error);
565
+ this.propertyEditor.value = `/* Error: ${error.message} */`;
566
+ }
567
+ }
568
+
569
+ this.editorModal.classList.add('active');
570
+ this.propertyEditor.focus();
571
+ }
572
+
573
+ /**
574
+ * Validate editor contents for current type.
575
+ * @returns {void}
576
+ */
577
+ validateEditor() {
578
+ const value = this.propertyEditor.value.trim();
579
+ const type = this.normalizeEditorType(this.currentEditingType) || 'json';
580
+
581
+ try {
582
+ if (type === 'function') {
583
+ new Function('return ' + value)();
584
+ } else {
585
+ JSON.parse(value);
586
+ }
587
+
588
+ this.validationMessage.textContent = '✅ Valid syntax';
589
+ this.validationMessage.style.color = '#4CAF50';
590
+ this.querySelector('#modal-save').disabled = false;
591
+ } catch (error) {
592
+ this.validationMessage.textContent = `❌ ${error.message}`;
593
+ this.validationMessage.style.color = '#F44336';
594
+ this.querySelector('#modal-save').disabled = true;
595
+ slice.logger?.warn?.('Debugger', 'Validation error in editor', error);
596
+ }
597
+ }
598
+
599
+ /**
600
+ * Save editor value to component.
601
+ * @returns {void}
602
+ */
603
+ savePropertyValue() {
604
+ const value = this.propertyEditor.value.trim();
605
+ const type = this.normalizeEditorType(this.currentEditingType) || 'json';
606
+
607
+ try {
608
+ let newValue;
609
+
610
+ if (type === 'function') {
611
+ newValue = new Function('return ' + value)();
612
+ } else {
613
+ newValue = JSON.parse(value);
614
+ }
615
+
616
+ this.currentComponent[this.currentEditingProp] = newValue;
617
+ this.closeModal();
618
+
619
+ slice.logger.logInfo('Debugger', `Updated ${this.currentEditingProp} via advanced editor`);
620
+
621
+ const input = this.querySelector(`[data-prop="${this.currentEditingProp}"]`);
622
+ if (input) {
623
+ input.value = this.serializeValue(newValue);
624
+ this.showVisualFeedback(input);
625
+ }
626
+
627
+ } catch (error) {
628
+ this.validationMessage.textContent = `❌ ${error.message}`;
629
+ this.validationMessage.style.color = '#F44336';
630
+ slice.logger?.error?.('Debugger', `Failed to save ${this.currentEditingProp}`, error);
631
+ }
632
+ }
633
+
634
+ /**
635
+ * Close the advanced editor modal.
636
+ * @returns {void}
637
+ */
638
+ closeModal() {
639
+ this.editorModal.classList.remove('active');
640
+ this.currentEditingProp = null;
641
+ this.currentEditingType = null;
642
+ this.validationMessage.textContent = '';
643
+ }
644
+
645
+ /**
646
+ * Reset props to defaults (if defined in static props).
647
+ * @returns {void}
648
+ */
649
+ resetValues() {
650
+ const ComponentClass = this.currentComponent.constructor;
651
+ const configuredProps = ComponentClass.props || {};
652
+ let resetCount = 0;
653
+
654
+ Object.entries(configuredProps).forEach(([prop, config]) => {
655
+ if (config.default !== undefined) {
656
+ this.currentComponent[prop] = config.default;
657
+
658
+ const input = this.querySelector(`[data-prop="${prop}"]`);
659
+ if (input && !input.readOnly) {
660
+ if (input.type === 'checkbox') {
661
+ input.checked = config.default;
662
+ const label = input.parentNode.querySelector('.checkbox-label');
663
+ if (label) {
664
+ label.textContent = config.default ? 'true' : 'false';
665
+ }
666
+ } else {
667
+ input.value = this.serializeValue(config.default);
668
+ }
669
+ resetCount++;
670
+ }
671
+ }
672
+ });
673
+
674
+ slice.logger.logInfo('Debugger', 'Reset values to defaults');
675
+ this.showResetFeedback(resetCount);
676
+ }
677
+
678
+ /**
679
+ * Show reset feedback in button.
680
+ * @param {number} resetCount
681
+ * @returns {void}
682
+ */
683
+ showResetFeedback(resetCount) {
684
+ const resetBtn = this.querySelector('#reset-values');
685
+ if (!resetBtn) return;
686
+
687
+ const originalText = resetBtn.textContent;
688
+
689
+ resetBtn.textContent = `🔄 Reset ${resetCount} values!`;
690
+ resetBtn.style.background = '#FF9800';
691
+
692
+ setTimeout(() => {
693
+ resetBtn.textContent = originalText;
694
+ resetBtn.style.background = '';
695
+ }, 1500);
696
+ }
697
+
698
+ /**
699
+ * Render info tab content.
700
+ * @returns {void}
701
+ */
702
+ updateInfoTab() {
703
+ const infoContainer = this.querySelector('.info-list');
704
+ if (!infoContainer) return;
705
+
706
+ const component = this.currentComponent;
707
+
708
+ const info = [
709
+ { label: 'Component Type', value: component.constructor.name },
710
+ { label: 'Slice ID', value: component.sliceId || 'Not assigned' },
711
+ { label: 'Tag Name', value: component.tagName },
712
+ { label: 'Connected', value: component.isConnected ? 'Yes' : 'No' },
713
+ { label: 'Props Count', value: Object.keys(this.componentProps).length },
714
+ { label: 'Children', value: component.children.length }
715
+ ];
716
+
717
+ infoContainer.innerHTML = info.map(item => `
718
+ <div class="info-item">
719
+ <span class="info-label">${item.label}</span>
720
+ <span class="info-value">${item.value}</span>
721
+ </div>
722
+ `).join('');
723
+ }
724
+
725
+ /**
726
+ * Get a simple value type label.
727
+ * @param {any} value
728
+ * @returns {string}
729
+ */
730
+ getValueType(value) {
731
+ if (value === null) return 'null';
732
+ if (value === undefined) return 'undefined';
733
+ if (Array.isArray(value)) return 'array';
734
+ return typeof value;
735
+ }
736
+
737
+ /**
738
+ * Serialize a value for input display.
739
+ * @param {any} value
740
+ * @returns {string}
741
+ */
742
+ serializeValue(value) {
743
+ if (value === null || value === undefined) {
744
+ return '';
745
+ }
746
+
747
+ if (typeof value === 'object' || typeof value === 'function') {
748
+ try {
749
+ return JSON.stringify(value);
750
+ } catch {
751
+ return String(value);
752
+ }
753
+ }
754
+
755
+ return String(value);
756
+ }
757
+
758
+ /**
759
+ * Resolve which props to show in the debugger.
760
+ * @param {HTMLElement} component
761
+ * @returns {string[]}
762
+ */
763
+ getComponentPropsForDebugger(component) {
764
+ const ComponentClass = component.constructor;
765
+
766
+ if (ComponentClass.props) {
767
+ return Object.keys(ComponentClass.props);
768
+ }
769
+
770
+ if (component.debuggerProps && Array.isArray(component.debuggerProps)) {
771
+ return component.debuggerProps;
772
+ }
773
+
774
+ return this.detectUsedProps(component);
775
+ }
776
+
777
+ /**
778
+ * Detect props from backing fields on the component.
779
+ * @param {HTMLElement} component
780
+ * @returns {string[]}
781
+ */
782
+ detectUsedProps(component) {
783
+ const usedProps = [];
784
+
785
+ Object.getOwnPropertyNames(component).forEach(key => {
786
+ if (key.startsWith('_') && key !== '_isActive') {
787
+ const propName = key.substring(1);
788
+ usedProps.push(propName);
789
+ }
790
+ });
791
+
792
+ return usedProps;
793
+ }
794
+
795
+ /**
796
+ * Hide the debugger UI.
797
+ * @returns {void}
798
+ */
799
+ hide() {
800
+ this.debuggerContainer.classList.remove('active');
801
+ this.closeModal();
802
+ }
803
+ }
804
+
805
+ customElements.define('slice-debugger', Debugger);
806
+
807
+ function productionOnlyCSS(){
808
+ return `
809
+ /* ============================================================
810
+ Slice Instruments — Component Inspector
811
+ All selectors are scoped under the <slice-debugger> tag so the
812
+ panel never clashes with (or leaks into) app styles. Tokens live
813
+ on the tag so both #debugger-container and the sibling
814
+ #editor-modal inherit them. Every --si-* token reads the matching
815
+ framework theme variable from :root, falling back to the original
816
+ hardcoded value if absent — so debuggers always match the app theme.
817
+ ============================================================ */
818
+ slice-debugger {
819
+ --si-accent: var(--primary-color, #6ee7ff);
820
+ --si-accent-rgb: var(--primary-color-rgb, 110, 231, 255);
821
+ --si-surface: var(--primary-background-color, rgba(17, 19, 28, 0.88));
822
+ --si-raised: var(--secondary-background-color, rgba(255, 255, 255, 0.035));
823
+ --si-raised-2: var(--tertiary-background-color, rgba(255, 255, 255, 0.06));
824
+ --si-inset: var(--primary-color-shade, rgba(0, 0, 0, 0.28));
825
+ --si-border: var(--medium-color, rgba(255, 255, 255, 0.09));
826
+ --si-text: var(--font-primary-color, #e8eaf2);
827
+ --si-dim: var(--font-secondary-color, #888fa6);
828
+ --si-danger: var(--danger-color, #ff6b6b);
829
+ --si-success: var(--success-color, #46d39a);
830
+ --si-mono: ui-monospace, 'SF Mono', 'JetBrains Mono', 'Cascadia Code', Menlo, Consolas, monospace;
831
+ --si-sans: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
832
+ }
833
+
834
+ slice-debugger *,
835
+ slice-debugger *::before,
836
+ slice-debugger *::after { box-sizing: border-box; }
837
+
838
+ slice-debugger #debugger-container {
839
+ font-family: var(--si-sans);
840
+ display: none;
841
+ position: fixed;
842
+ top: 20px;
843
+ right: 20px;
844
+ width: 430px;
845
+ height: 85vh;
846
+ max-height: 85vh;
847
+ background: var(--si-surface);
848
+ border: 1px solid var(--si-border);
849
+ border-radius: 16px;
850
+ box-shadow:
851
+ 0 30px 70px -16px rgba(0, 0, 0, 0.6),
852
+ 0 0 0 1px rgba(0, 0, 0, 0.2),
853
+ 0 0 46px -20px rgba(var(--si-accent-rgb), 0.6);
854
+ color: var(--si-text);
855
+ z-index: 10000;
856
+ overflow: hidden;
857
+ -webkit-backdrop-filter: blur(24px) saturate(1.3);
858
+ backdrop-filter: blur(24px) saturate(1.3);
859
+ }
860
+
861
+ slice-debugger #debugger-container.active {
862
+ display: flex;
863
+ flex-direction: column;
864
+ animation: si-inspector-in 0.28s cubic-bezier(0.16, 1, 0.3, 1);
865
+ }
866
+
867
+ @keyframes si-inspector-in {
868
+ from { opacity: 0; transform: translateY(12px) scale(0.985); }
869
+ to { opacity: 1; transform: translateY(0) scale(1); }
870
+ }
871
+
872
+ slice-debugger #debugger-container::before {
873
+ content: '';
874
+ position: absolute;
875
+ left: 0; top: 0; bottom: 0;
876
+ width: 2px;
877
+ background: linear-gradient(180deg, var(--si-accent), transparent 60%);
878
+ opacity: 0.9;
879
+ pointer-events: none;
880
+ z-index: 2;
881
+ }
882
+
883
+ slice-debugger .debugger-header {
884
+ background:
885
+ radial-gradient(140% 160% at 0% 0%, rgba(var(--si-accent-rgb), 0.14), transparent 55%),
886
+ var(--si-raised);
887
+ color: var(--si-text);
888
+ padding: 13px 15px;
889
+ border-bottom: 1px solid var(--si-border);
890
+ user-select: none;
891
+ cursor: grab;
892
+ }
893
+ slice-debugger .debugger-header:active { cursor: grabbing; }
894
+
895
+ slice-debugger .header-content {
896
+ display: flex;
897
+ justify-content: space-between;
898
+ align-items: center;
899
+ }
900
+
901
+ slice-debugger .component-info {
902
+ display: flex;
903
+ align-items: center;
904
+ gap: 11px;
905
+ min-width: 0;
906
+ }
907
+
908
+ slice-debugger .component-icon {
909
+ font-size: 17px;
910
+ width: 30px; height: 30px;
911
+ display: flex; align-items: center; justify-content: center;
912
+ border-radius: 9px;
913
+ background: rgba(var(--si-accent-rgb), 0.12);
914
+ border: 1px solid rgba(var(--si-accent-rgb), 0.28);
915
+ color: var(--si-accent);
916
+ flex-shrink: 0;
917
+ }
918
+
919
+ slice-debugger .component-name {
920
+ font-size: 13px;
921
+ font-weight: 600;
922
+ font-family: var(--si-mono);
923
+ color: var(--si-text);
924
+ margin-bottom: 2px;
925
+ overflow: hidden;
926
+ text-overflow: ellipsis;
927
+ white-space: nowrap;
928
+ }
929
+
930
+ slice-debugger .component-id {
931
+ font-size: 10.5px;
932
+ font-family: var(--si-mono);
933
+ color: var(--si-dim);
934
+ letter-spacing: 0.02em;
935
+ }
936
+
937
+ slice-debugger .header-actions { display: flex; gap: 6px; }
938
+
939
+ slice-debugger .minimize-btn,
940
+ slice-debugger #close-debugger {
941
+ background: var(--si-raised);
942
+ border: 1px solid var(--si-border);
943
+ color: var(--si-dim);
944
+ width: 28px;
945
+ height: 28px;
946
+ border-radius: 8px;
947
+ cursor: pointer;
948
+ display: flex;
949
+ align-items: center;
950
+ justify-content: center;
951
+ font-size: 15px;
952
+ font-weight: 500;
953
+ line-height: 1;
954
+ transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
955
+ }
956
+ slice-debugger .minimize-btn:hover,
957
+ slice-debugger #close-debugger:hover {
958
+ color: var(--si-text);
959
+ background: var(--si-raised-2);
960
+ border-color: rgba(var(--si-accent-rgb), 0.5);
961
+ }
962
+ slice-debugger #close-debugger:hover { color: var(--si-danger); border-color: rgba(255, 107, 107, 0.5); }
963
+ slice-debugger .minimize-btn:active,
964
+ slice-debugger #close-debugger:active { transform: scale(0.92); }
965
+
966
+ slice-debugger .debugger-content {
967
+ flex: 1;
968
+ display: flex;
969
+ flex-direction: column;
970
+ overflow: hidden;
971
+ }
972
+
973
+ slice-debugger .tabs-container { border-bottom: 1px solid var(--si-border); }
974
+
975
+ slice-debugger .tab-nav {
976
+ display: flex;
977
+ background: var(--si-inset);
978
+ padding: 0 6px;
979
+ gap: 2px;
980
+ }
981
+
982
+ slice-debugger .tab-btn {
983
+ flex: 1;
984
+ padding: 11px 14px;
985
+ border: none;
986
+ background: transparent;
987
+ color: var(--si-dim);
988
+ font-family: var(--si-mono);
989
+ font-size: 11px;
990
+ letter-spacing: 0.08em;
991
+ text-transform: uppercase;
992
+ cursor: pointer;
993
+ transition: color 0.18s ease, border-color 0.18s ease;
994
+ border-bottom: 2px solid transparent;
995
+ }
996
+ slice-debugger .tab-btn:hover { color: var(--si-text); }
997
+ slice-debugger .tab-btn.active {
998
+ color: var(--si-accent);
999
+ border-bottom-color: var(--si-accent);
1000
+ }
1001
+
1002
+ slice-debugger .tab-content {
1003
+ flex: 1;
1004
+ overflow: hidden;
1005
+ height: calc(85vh - 104px);
1006
+ }
1007
+
1008
+ slice-debugger .tab-pane {
1009
+ display: none;
1010
+ height: 100%;
1011
+ overflow-y: auto;
1012
+ overflow-x: hidden;
1013
+ padding: 16px;
1014
+ }
1015
+ slice-debugger .tab-pane.active { display: block; }
1016
+
1017
+ slice-debugger .tab-pane::-webkit-scrollbar { width: 8px; }
1018
+ slice-debugger .tab-pane::-webkit-scrollbar-track { background: transparent; }
1019
+ slice-debugger .tab-pane::-webkit-scrollbar-thumb {
1020
+ background: var(--si-raised-2);
1021
+ border-radius: 8px;
1022
+ border: 2px solid transparent;
1023
+ background-clip: padding-box;
1024
+ }
1025
+ slice-debugger .tab-pane::-webkit-scrollbar-thumb:hover { background: rgba(var(--si-accent-rgb), 0.4); background-clip: padding-box; }
1026
+
1027
+ slice-debugger .props-container {
1028
+ display: flex;
1029
+ flex-direction: column;
1030
+ gap: 12px;
1031
+ margin-bottom: 16px;
1032
+ }
1033
+
1034
+ slice-debugger .props-actions {
1035
+ border-top: 1px solid var(--si-border);
1036
+ padding-top: 16px;
1037
+ margin-top: 8px;
1038
+ }
1039
+
1040
+ slice-debugger .actions-note {
1041
+ margin-top: 12px;
1042
+ padding: 9px 12px;
1043
+ background: var(--si-raised);
1044
+ border-radius: 8px;
1045
+ border: 1px solid var(--si-border);
1046
+ }
1047
+ slice-debugger .actions-note small {
1048
+ color: var(--si-dim);
1049
+ font-size: 11px;
1050
+ display: flex;
1051
+ align-items: center;
1052
+ gap: 6px;
1053
+ }
1054
+
1055
+ slice-debugger .props-section {
1056
+ background: var(--si-raised);
1057
+ border: 1px solid var(--si-border);
1058
+ border-radius: 12px;
1059
+ padding: 13px;
1060
+ }
1061
+
1062
+ slice-debugger .section-title {
1063
+ font-size: 10.5px;
1064
+ font-weight: 600;
1065
+ letter-spacing: 0.1em;
1066
+ text-transform: uppercase;
1067
+ color: var(--si-dim);
1068
+ margin-bottom: 12px;
1069
+ display: flex;
1070
+ align-items: center;
1071
+ gap: 6px;
1072
+ }
1073
+
1074
+ slice-debugger .prop-item {
1075
+ display: flex;
1076
+ flex-direction: column;
1077
+ gap: 6px;
1078
+ padding: 12px;
1079
+ background: var(--si-inset);
1080
+ border: 1px solid var(--si-border);
1081
+ border-left: 2px solid transparent;
1082
+ border-radius: 10px;
1083
+ margin-bottom: 8px;
1084
+ transition: border-color 0.18s ease, background 0.18s ease;
1085
+ }
1086
+ slice-debugger .prop-item:hover {
1087
+ border-left-color: var(--si-accent);
1088
+ background: rgba(0, 0, 0, 0.34);
1089
+ }
1090
+
1091
+ slice-debugger .prop-header {
1092
+ display: flex;
1093
+ justify-content: space-between;
1094
+ align-items: center;
1095
+ gap: 8px;
1096
+ }
1097
+
1098
+ slice-debugger .prop-name {
1099
+ font-size: 12.5px;
1100
+ font-weight: 600;
1101
+ font-family: var(--si-mono);
1102
+ color: var(--si-text);
1103
+ }
1104
+ slice-debugger .prop-name.required::after {
1105
+ content: " *";
1106
+ color: var(--si-danger);
1107
+ }
1108
+
1109
+ slice-debugger .prop-meta {
1110
+ display: flex;
1111
+ align-items: center;
1112
+ gap: 8px;
1113
+ }
1114
+
1115
+ slice-debugger .prop-type {
1116
+ font-size: 10px;
1117
+ padding: 2px 7px;
1118
+ background: rgba(var(--si-accent-rgb), 0.12);
1119
+ color: var(--si-accent);
1120
+ border: 1px solid rgba(var(--si-accent-rgb), 0.28);
1121
+ border-radius: 999px;
1122
+ font-family: var(--si-mono);
1123
+ font-weight: 500;
1124
+ letter-spacing: 0.02em;
1125
+ }
1126
+
1127
+ slice-debugger .prop-status { font-size: 14px; line-height: 1; }
1128
+ slice-debugger .status-used { color: var(--si-success); }
1129
+ slice-debugger .status-missing { color: var(--si-danger); }
1130
+ slice-debugger .status-optional { color: var(--si-dim); }
1131
+
1132
+ slice-debugger .prop-input { margin-top: 6px; }
1133
+ slice-debugger .input-group { position: relative; }
1134
+
1135
+ slice-debugger .prop-control {
1136
+ width: 100%;
1137
+ padding: 9px 34px 9px 11px;
1138
+ border: 1px solid var(--si-border);
1139
+ border-radius: 8px;
1140
+ background: rgba(0, 0, 0, 0.35);
1141
+ color: var(--si-text);
1142
+ font-size: 12.5px;
1143
+ font-family: var(--si-mono);
1144
+ transition: border-color 0.15s ease, box-shadow 0.15s ease;
1145
+ }
1146
+ slice-debugger .prop-control::placeholder { color: var(--si-dim); }
1147
+ slice-debugger .prop-control:focus {
1148
+ outline: none;
1149
+ border-color: rgba(var(--si-accent-rgb), 0.6);
1150
+ box-shadow: 0 0 0 3px rgba(var(--si-accent-rgb), 0.13);
1151
+ }
1152
+ slice-debugger .prop-control:disabled { opacity: 0.5; cursor: not-allowed; }
1153
+ slice-debugger .prop-control[readonly] { background: var(--si-raised); cursor: pointer; }
1154
+ slice-debugger .prop-control[readonly]:focus { border-color: rgba(var(--si-accent-rgb), 0.6); }
1155
+ slice-debugger .prop-control[type="checkbox"] {
1156
+ width: 18px;
1157
+ height: 18px;
1158
+ padding: 0;
1159
+ margin: 0;
1160
+ cursor: pointer;
1161
+ accent-color: var(--si-accent);
1162
+ }
1163
+
1164
+ slice-debugger .edit-btn {
1165
+ position: absolute;
1166
+ right: 5px;
1167
+ top: 50%;
1168
+ transform: translateY(-50%);
1169
+ background: rgba(var(--si-accent-rgb), 0.14);
1170
+ border: 1px solid rgba(var(--si-accent-rgb), 0.3);
1171
+ color: var(--si-accent);
1172
+ width: 25px;
1173
+ height: 25px;
1174
+ border-radius: 6px;
1175
+ cursor: pointer;
1176
+ font-size: 12px;
1177
+ display: flex;
1178
+ align-items: center;
1179
+ justify-content: center;
1180
+ transition: background 0.15s ease, transform 0.15s ease;
1181
+ }
1182
+ slice-debugger .edit-btn:hover { background: rgba(var(--si-accent-rgb), 0.26); }
1183
+ slice-debugger .edit-btn:active { transform: translateY(-50%) scale(0.9); }
1184
+
1185
+ slice-debugger .default-value {
1186
+ font-size: 10.5px;
1187
+ color: var(--si-dim);
1188
+ font-family: var(--si-mono);
1189
+ margin-top: 2px;
1190
+ }
1191
+
1192
+ slice-debugger .info-list { display: flex; flex-direction: column; gap: 8px; }
1193
+
1194
+ slice-debugger .info-item {
1195
+ display: flex;
1196
+ justify-content: space-between;
1197
+ align-items: center;
1198
+ gap: 12px;
1199
+ padding: 12px 13px;
1200
+ background: var(--si-raised);
1201
+ border-radius: 10px;
1202
+ border: 1px solid var(--si-border);
1203
+ }
1204
+
1205
+ slice-debugger .info-label {
1206
+ font-weight: 500;
1207
+ color: var(--si-dim);
1208
+ font-size: 11px;
1209
+ letter-spacing: 0.04em;
1210
+ text-transform: uppercase;
1211
+ }
1212
+
1213
+ slice-debugger .info-value {
1214
+ color: var(--si-text);
1215
+ font-family: var(--si-mono);
1216
+ font-size: 12px;
1217
+ text-align: right;
1218
+ overflow: hidden;
1219
+ text-overflow: ellipsis;
1220
+ white-space: nowrap;
1221
+ }
1222
+
1223
+ slice-debugger .actions-container { display: flex; flex-direction: column; gap: 16px; }
1224
+ slice-debugger .action-buttons { display: flex; flex-direction: column; gap: 8px; }
1225
+
1226
+ slice-debugger .action-btn {
1227
+ padding: 12px 16px;
1228
+ border: 1px solid transparent;
1229
+ border-radius: 9px;
1230
+ font-size: 12.5px;
1231
+ font-weight: 600;
1232
+ cursor: pointer;
1233
+ transition: background 0.15s ease, border-color 0.15s ease, transform 0.1s ease;
1234
+ display: flex;
1235
+ align-items: center;
1236
+ justify-content: center;
1237
+ gap: 8px;
1238
+ }
1239
+ slice-debugger .action-btn:active { transform: scale(0.99); }
1240
+ slice-debugger .action-btn.primary { background: var(--si-accent); color: #0b1020; }
1241
+ slice-debugger .action-btn.primary:hover { filter: brightness(1.08); }
1242
+ slice-debugger .action-btn.secondary {
1243
+ background: rgba(var(--si-accent-rgb), 0.12);
1244
+ color: var(--si-accent);
1245
+ border-color: rgba(var(--si-accent-rgb), 0.3);
1246
+ }
1247
+ slice-debugger .action-btn.secondary:hover { background: rgba(var(--si-accent-rgb), 0.2); }
1248
+ slice-debugger .action-btn.tertiary {
1249
+ background: var(--si-raised);
1250
+ color: var(--si-text);
1251
+ border-color: var(--si-border);
1252
+ }
1253
+ slice-debugger .action-btn.tertiary:hover { background: var(--si-raised-2); }
1254
+
1255
+ slice-debugger .editor-modal {
1256
+ display: none;
1257
+ position: fixed;
1258
+ top: 0;
1259
+ left: 0;
1260
+ width: 100%;
1261
+ height: 100%;
1262
+ background: rgba(6, 8, 14, 0.62);
1263
+ z-index: 20000;
1264
+ -webkit-backdrop-filter: blur(6px);
1265
+ backdrop-filter: blur(6px);
1266
+ }
1267
+ slice-debugger .editor-modal.active {
1268
+ display: flex;
1269
+ align-items: center;
1270
+ justify-content: center;
1271
+ animation: si-inspector-in 0.2s ease;
1272
+ }
1273
+
1274
+ slice-debugger .modal-content {
1275
+ background: var(--si-surface);
1276
+ border-radius: 16px;
1277
+ width: 90%;
1278
+ max-width: 620px;
1279
+ max-height: 80%;
1280
+ display: flex;
1281
+ flex-direction: column;
1282
+ box-shadow: 0 30px 70px -16px rgba(0, 0, 0, 0.7);
1283
+ border: 1px solid var(--si-border);
1284
+ -webkit-backdrop-filter: blur(24px);
1285
+ backdrop-filter: blur(24px);
1286
+ }
1287
+
1288
+ slice-debugger .modal-header {
1289
+ padding: 15px 20px;
1290
+ background: var(--si-raised);
1291
+ border-radius: 16px 16px 0 0;
1292
+ border-bottom: 1px solid var(--si-border);
1293
+ display: flex;
1294
+ justify-content: space-between;
1295
+ align-items: center;
1296
+ }
1297
+ slice-debugger .modal-header h3 {
1298
+ margin: 0;
1299
+ font-size: 13px;
1300
+ font-weight: 600;
1301
+ font-family: var(--si-mono);
1302
+ letter-spacing: 0.04em;
1303
+ color: var(--si-text);
1304
+ }
1305
+
1306
+ slice-debugger .modal-close {
1307
+ background: var(--si-raised);
1308
+ border: 1px solid var(--si-border);
1309
+ font-size: 16px;
1310
+ color: var(--si-dim);
1311
+ cursor: pointer;
1312
+ width: 30px;
1313
+ height: 30px;
1314
+ border-radius: 8px;
1315
+ display: flex;
1316
+ align-items: center;
1317
+ justify-content: center;
1318
+ transition: color 0.15s ease, background 0.15s ease;
1319
+ }
1320
+ slice-debugger .modal-close:hover { color: var(--si-danger); background: var(--si-raised-2); }
1321
+
1322
+ slice-debugger .modal-body {
1323
+ flex: 1;
1324
+ padding: 20px;
1325
+ display: flex;
1326
+ flex-direction: column;
1327
+ gap: 16px;
1328
+ overflow: hidden;
1329
+ }
1330
+
1331
+ slice-debugger .editor-type-selector {
1332
+ display: flex;
1333
+ gap: 4px;
1334
+ background: var(--si-inset);
1335
+ padding: 4px;
1336
+ border-radius: 9px;
1337
+ border: 1px solid var(--si-border);
1338
+ }
1339
+
1340
+ slice-debugger .type-btn {
1341
+ flex: 1;
1342
+ padding: 8px 12px;
1343
+ border: none;
1344
+ background: transparent;
1345
+ color: var(--si-dim);
1346
+ font-size: 11px;
1347
+ font-family: var(--si-mono);
1348
+ font-weight: 500;
1349
+ cursor: pointer;
1350
+ border-radius: 6px;
1351
+ transition: color 0.15s ease, background 0.15s ease;
1352
+ }
1353
+ slice-debugger .type-btn.active {
1354
+ background: rgba(var(--si-accent-rgb), 0.16);
1355
+ color: var(--si-accent);
1356
+ }
1357
+
1358
+ slice-debugger .editor-container {
1359
+ flex: 1;
1360
+ position: relative;
1361
+ min-height: 200px;
1362
+ }
1363
+
1364
+ slice-debugger #property-editor {
1365
+ width: 100%;
1366
+ height: 100%;
1367
+ border: 1px solid var(--si-border);
1368
+ border-radius: 10px;
1369
+ padding: 14px;
1370
+ background: rgba(0, 0, 0, 0.4);
1371
+ color: var(--si-text);
1372
+ font-family: var(--si-mono);
1373
+ font-size: 12.5px;
1374
+ line-height: 1.6;
1375
+ resize: none;
1376
+ outline: none;
1377
+ min-height: 200px;
1378
+ tab-size: 2;
1379
+ }
1380
+ slice-debugger #property-editor:focus {
1381
+ border-color: rgba(var(--si-accent-rgb), 0.6);
1382
+ box-shadow: 0 0 0 3px rgba(var(--si-accent-rgb), 0.13);
1383
+ }
1384
+
1385
+ slice-debugger .validation-message {
1386
+ font-size: 11.5px;
1387
+ color: var(--si-danger);
1388
+ min-height: 18px;
1389
+ display: flex;
1390
+ align-items: center;
1391
+ gap: 6px;
1392
+ font-family: var(--si-mono);
1393
+ }
1394
+
1395
+ slice-debugger .modal-actions {
1396
+ padding: 15px 20px;
1397
+ background: var(--si-raised);
1398
+ border-radius: 0 0 16px 16px;
1399
+ border-top: 1px solid var(--si-border);
1400
+ display: flex;
1401
+ gap: 12px;
1402
+ justify-content: flex-end;
1403
+ }
1404
+
1405
+ slice-debugger .modal-btn {
1406
+ padding: 10px 20px;
1407
+ border: 1px solid transparent;
1408
+ border-radius: 8px;
1409
+ font-size: 12.5px;
1410
+ font-weight: 600;
1411
+ cursor: pointer;
1412
+ transition: background 0.15s ease, border-color 0.15s ease;
1413
+ }
1414
+ slice-debugger .modal-btn.cancel {
1415
+ background: var(--si-raised);
1416
+ color: var(--si-text);
1417
+ border-color: var(--si-border);
1418
+ }
1419
+ slice-debugger .modal-btn.cancel:hover { background: var(--si-raised-2); }
1420
+ slice-debugger .modal-btn.save { background: var(--si-success); color: #06231a; }
1421
+ slice-debugger .modal-btn.save:hover { filter: brightness(1.08); }
1422
+ slice-debugger .modal-btn.save:disabled { opacity: 0.45; cursor: not-allowed; filter: none; }
1423
+
1424
+ @media (prefers-reduced-motion: reduce) {
1425
+ slice-debugger #debugger-container.active,
1426
+ slice-debugger .editor-modal.active { animation: none; }
1427
+ }
1428
+ `
1429
+ }
1430
+
1431
+ function productionOnlyHtml(){
1432
+ return `
1433
+ <div id="debugger-container">
1434
+ <div class="debugger-header">
1435
+ <div class="header-content">
1436
+ <div class="component-info">
1437
+ <div class="component-icon">🔍</div>
1438
+ <div class="component-details">
1439
+ <div class="component-name">Component Inspector</div>
1440
+ <div class="component-id">Ready to debug</div>
1441
+ </div>
1442
+ </div>
1443
+ <div class="header-actions">
1444
+ <button class="minimize-btn" title="Minimize">−</button>
1445
+ <button id="close-debugger" title="Close">×</button>
1446
+ </div>
1447
+ </div>
1448
+ </div>
1449
+
1450
+ <div class="debugger-content">
1451
+ <div class="tabs-container">
1452
+ <div class="tab-nav">
1453
+ <button class="tab-btn active" data-tab="props">📋 Props</button>
1454
+ <button class="tab-btn" data-tab="info">ℹ️ Info</button>
1455
+ </div>
1456
+ </div>
1457
+
1458
+ <div class="tab-content">
1459
+ <div class="tab-pane active" id="props-tab">
1460
+ <div class="props-container"></div>
1461
+ <div class="props-actions">
1462
+ <div class="action-buttons">
1463
+ <button class="action-btn primary" id="apply-changes">✅ Apply Changes</button>
1464
+ <button class="action-btn secondary" id="reset-values">🔄 Reset Values</button>
1465
+ </div>
1466
+ <div class="actions-note">
1467
+ <small>💡 Press Enter on any input to apply changes automatically</small>
1468
+ </div>
1469
+ </div>
1470
+ </div>
1471
+
1472
+ <div class="tab-pane" id="info-tab">
1473
+ <div class="info-container">
1474
+ <div class="info-list"></div>
1475
+ </div>
1476
+ </div>
1477
+ </div>
1478
+ </div>
1479
+
1480
+ <!-- Modal para editar objetos/funciones -->
1481
+ <div class="editor-modal" id="editor-modal">
1482
+ <div class="modal-content">
1483
+ <div class="modal-header">
1484
+ <h3 id="modal-title">Edit Property</h3>
1485
+ <button class="modal-close" id="modal-close">×</button>
1486
+ </div>
1487
+ <div class="modal-body">
1488
+ <div class="editor-type-selector">
1489
+ <button class="type-btn active" data-type="json">📋 JSON</button>
1490
+ <button class="type-btn" data-type="function">⚡ Function</button>
1491
+ </div>
1492
+ <div class="editor-container">
1493
+ <textarea id="property-editor" spellcheck="false"></textarea>
1494
+ </div>
1495
+ <div class="editor-footer">
1496
+ <div class="validation-message"></div>
1497
+ </div>
1498
+ </div>
1499
+ <div class="modal-actions">
1500
+ <button class="modal-btn cancel" id="modal-cancel">Cancel</button>
1501
+ <button class="modal-btn save" id="modal-save">Save Changes</button>
1502
+ </div>
1503
+ </div>
1504
+ </div>
1505
+ </div>`
1498
1506
  }