@symbo.ls/connect 3.4.0 → 3.4.2
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.
- package/Artboard.png +0 -0
- package/SESSION_LOG.md +190 -0
- package/dist/manifest.json +1 -1
- package/dist/panel.css +737 -171
- package/dist/panel.html +49 -13
- package/dist/panel.js +1466 -157
- package/package.json +3 -3
- package/static/panel.css +737 -171
- package/static/panel.html +49 -13
- package/static/panel.js +1466 -157
package/dist/panel.js
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
let redoStack = []
|
|
22
22
|
let platformProjectData = null // project data from platform API
|
|
23
23
|
let platformProjectId = null
|
|
24
|
+
let cachedDesignSystem = null // cached DS for autocomplete suggestions
|
|
24
25
|
|
|
25
26
|
// ============================================================
|
|
26
27
|
// IndexedDB
|
|
@@ -1264,13 +1265,70 @@
|
|
|
1264
1265
|
|
|
1265
1266
|
function updateSyncButton () {
|
|
1266
1267
|
const btn = document.getElementById('btn-sync')
|
|
1268
|
+
const dropdown = document.getElementById('btn-sync-dropdown')
|
|
1267
1269
|
if (!btn) return
|
|
1268
1270
|
const count = pendingSyncOps.length
|
|
1269
1271
|
if (count > 0) {
|
|
1270
1272
|
btn.style.display = ''
|
|
1271
1273
|
btn.textContent = 'Sync (' + count + ')'
|
|
1274
|
+
if (dropdown) dropdown.style.display = ''
|
|
1272
1275
|
} else {
|
|
1273
1276
|
btn.style.display = 'none'
|
|
1277
|
+
if (dropdown) dropdown.style.display = 'none'
|
|
1278
|
+
const panel = document.getElementById('sync-changes-panel')
|
|
1279
|
+
if (panel) panel.style.display = 'none'
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
function toggleSyncChangesPanel () {
|
|
1284
|
+
const panel = document.getElementById('sync-changes-panel')
|
|
1285
|
+
if (!panel) return
|
|
1286
|
+
const visible = panel.style.display !== 'none'
|
|
1287
|
+
if (visible) {
|
|
1288
|
+
panel.style.display = 'none'
|
|
1289
|
+
return
|
|
1290
|
+
}
|
|
1291
|
+
panel.style.display = ''
|
|
1292
|
+
renderSyncChangesList()
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
function renderSyncChangesList () {
|
|
1296
|
+
const list = document.getElementById('sync-changes-list')
|
|
1297
|
+
if (!list) return
|
|
1298
|
+
list.innerHTML = ''
|
|
1299
|
+
|
|
1300
|
+
if (pendingSyncOps.length === 0) {
|
|
1301
|
+
list.innerHTML = '<div class="empty-message">No pending changes</div>'
|
|
1302
|
+
return
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
for (let i = 0; i < pendingSyncOps.length; i++) {
|
|
1306
|
+
const op = pendingSyncOps[i]
|
|
1307
|
+
const row = document.createElement('div')
|
|
1308
|
+
row.className = 'sync-change-row'
|
|
1309
|
+
|
|
1310
|
+
const path = document.createElement('span')
|
|
1311
|
+
path.className = 'sync-change-path'
|
|
1312
|
+
path.textContent = (op.elementPath || '') + '.' + op.key
|
|
1313
|
+
|
|
1314
|
+
const val = document.createElement('span')
|
|
1315
|
+
val.className = 'sync-change-val'
|
|
1316
|
+
val.textContent = typeof op.value === 'string' ? op.value : JSON.stringify(op.value)
|
|
1317
|
+
|
|
1318
|
+
const removeBtn = document.createElement('button')
|
|
1319
|
+
removeBtn.className = 'sync-change-remove'
|
|
1320
|
+
removeBtn.textContent = '\u00d7'
|
|
1321
|
+
removeBtn.title = 'Remove this change'
|
|
1322
|
+
removeBtn.addEventListener('click', () => {
|
|
1323
|
+
pendingSyncOps.splice(i, 1)
|
|
1324
|
+
updateSyncButton()
|
|
1325
|
+
renderSyncChangesList()
|
|
1326
|
+
})
|
|
1327
|
+
|
|
1328
|
+
row.appendChild(path)
|
|
1329
|
+
row.appendChild(val)
|
|
1330
|
+
row.appendChild(removeBtn)
|
|
1331
|
+
list.appendChild(row)
|
|
1274
1332
|
}
|
|
1275
1333
|
}
|
|
1276
1334
|
|
|
@@ -1701,6 +1759,12 @@
|
|
|
1701
1759
|
}
|
|
1702
1760
|
|
|
1703
1761
|
setStatus('Tree loaded')
|
|
1762
|
+
// Pre-cache design system for autocomplete
|
|
1763
|
+
if (!cachedDesignSystem) {
|
|
1764
|
+
fetchRuntimeDesignSystem().then(ds => {
|
|
1765
|
+
if (ds && Object.keys(ds).length > 0) cachedDesignSystem = ds
|
|
1766
|
+
})
|
|
1767
|
+
}
|
|
1704
1768
|
} else {
|
|
1705
1769
|
setStatus('No DOMQL root found')
|
|
1706
1770
|
}
|
|
@@ -2124,7 +2188,7 @@
|
|
|
2124
2188
|
|
|
2125
2189
|
// --- Original view ---
|
|
2126
2190
|
if (!hasOriginal) {
|
|
2127
|
-
originalPanel.innerHTML = '<div class="empty-message">No original definition props found
|
|
2191
|
+
originalPanel.innerHTML = '<div class="empty-message">No original definition props found. Connect a local folder or platform to recognize original props.</div>'
|
|
2128
2192
|
} else {
|
|
2129
2193
|
for (const [key, val] of Object.entries(selectedInfo.originalProps)) {
|
|
2130
2194
|
const isFunc = key in funcProps
|
|
@@ -2205,6 +2269,36 @@
|
|
|
2205
2269
|
row.dataset.propKey = key
|
|
2206
2270
|
row.dataset.propType = type
|
|
2207
2271
|
|
|
2272
|
+
// Checkbox to disable/enable prop (like CSS DevTools)
|
|
2273
|
+
const checkbox = document.createElement('input')
|
|
2274
|
+
checkbox.type = 'checkbox'
|
|
2275
|
+
checkbox.checked = true
|
|
2276
|
+
checkbox.className = 'prop-checkbox'
|
|
2277
|
+
checkbox.title = 'Toggle property'
|
|
2278
|
+
checkbox.addEventListener('change', async () => {
|
|
2279
|
+
row.classList.toggle('prop-disabled', !checkbox.checked)
|
|
2280
|
+
if (checkbox.checked) {
|
|
2281
|
+
// Re-enable: set value back
|
|
2282
|
+
const expr = type === 'state'
|
|
2283
|
+
? 'JSON.stringify(window.__DOMQL_INSPECTOR__.updateState(' +
|
|
2284
|
+
JSON.stringify(selectedPath) + ',' + JSON.stringify(key) + ',' + JSON.stringify(val) + '))'
|
|
2285
|
+
: 'JSON.stringify(window.__DOMQL_INSPECTOR__.updateProp(' +
|
|
2286
|
+
JSON.stringify(selectedPath) + ',' + JSON.stringify(key) + ',' + JSON.stringify(val) + '))'
|
|
2287
|
+
await pageEval(expr).catch(() => {})
|
|
2288
|
+
setStatus('Enabled ' + key)
|
|
2289
|
+
} else {
|
|
2290
|
+
// Disable: set to undefined/remove
|
|
2291
|
+
const expr = type === 'state'
|
|
2292
|
+
? 'JSON.stringify(window.__DOMQL_INSPECTOR__.updateState(' +
|
|
2293
|
+
JSON.stringify(selectedPath) + ',' + JSON.stringify(key) + ',undefined))'
|
|
2294
|
+
: 'JSON.stringify(window.__DOMQL_INSPECTOR__.updateProp(' +
|
|
2295
|
+
JSON.stringify(selectedPath) + ',' + JSON.stringify(key) + ',undefined))'
|
|
2296
|
+
await pageEval(expr).catch(() => {})
|
|
2297
|
+
setStatus('Disabled ' + key)
|
|
2298
|
+
}
|
|
2299
|
+
})
|
|
2300
|
+
row.appendChild(checkbox)
|
|
2301
|
+
|
|
2208
2302
|
const keyEl = document.createElement('span')
|
|
2209
2303
|
keyEl.className = 'prop-key'
|
|
2210
2304
|
keyEl.textContent = key
|
|
@@ -2240,6 +2334,56 @@
|
|
|
2240
2334
|
valEl.addEventListener('click', () => startEditing(valEl, key, editableVal, type, componentName))
|
|
2241
2335
|
}
|
|
2242
2336
|
|
|
2337
|
+
// Number arrows (like CSS DevTools)
|
|
2338
|
+
const isNum = typeof val === 'number' || (typeof val === 'string' && /^-?\d+(\.\d+)?(px|em|rem|%|vh|vw|ms|s)?$/.test(val))
|
|
2339
|
+
if (isNum && !isComplex(val)) {
|
|
2340
|
+
const arrows = document.createElement('span')
|
|
2341
|
+
arrows.className = 'prop-num-arrows'
|
|
2342
|
+
const upBtn = document.createElement('button')
|
|
2343
|
+
upBtn.className = 'prop-num-arrow up'
|
|
2344
|
+
upBtn.textContent = '\u25B2'
|
|
2345
|
+
upBtn.title = 'Increment'
|
|
2346
|
+
const downBtn = document.createElement('button')
|
|
2347
|
+
downBtn.className = 'prop-num-arrow down'
|
|
2348
|
+
downBtn.textContent = '\u25BC'
|
|
2349
|
+
downBtn.title = 'Decrement'
|
|
2350
|
+
|
|
2351
|
+
const nudge = async (delta) => {
|
|
2352
|
+
const current = typeof val === 'number' ? val : parseFloat(val)
|
|
2353
|
+
const unit = typeof val === 'string' ? val.replace(/^-?\d+(\.\d+)?/, '') : ''
|
|
2354
|
+
const newNum = current + delta
|
|
2355
|
+
const newVal = unit ? String(newNum) + unit : newNum
|
|
2356
|
+
val = newVal
|
|
2357
|
+
valEl.innerHTML = ''
|
|
2358
|
+
valEl.appendChild(renderValue(newVal))
|
|
2359
|
+
// Reattach click
|
|
2360
|
+
if (!isComplex(newVal)) {
|
|
2361
|
+
valEl.addEventListener('click', () => startEditing(valEl, key, newVal, type, componentName))
|
|
2362
|
+
}
|
|
2363
|
+
const expr = type === 'state'
|
|
2364
|
+
? 'JSON.stringify(window.__DOMQL_INSPECTOR__.updateState(' +
|
|
2365
|
+
JSON.stringify(selectedPath) + ',' + JSON.stringify(key) + ',' + JSON.stringify(newVal) + '))'
|
|
2366
|
+
: 'JSON.stringify(window.__DOMQL_INSPECTOR__.updateProp(' +
|
|
2367
|
+
JSON.stringify(selectedPath) + ',' + JSON.stringify(key) + ',' + JSON.stringify(newVal) + '))'
|
|
2368
|
+
await pageEval(expr).catch(() => {})
|
|
2369
|
+
}
|
|
2370
|
+
upBtn.addEventListener('click', (e) => { e.stopPropagation(); nudge(1) })
|
|
2371
|
+
downBtn.addEventListener('click', (e) => { e.stopPropagation(); nudge(-1) })
|
|
2372
|
+
arrows.appendChild(upBtn)
|
|
2373
|
+
arrows.appendChild(downBtn)
|
|
2374
|
+
|
|
2375
|
+
const semiEl = document.createElement('span')
|
|
2376
|
+
semiEl.className = 'prop-semi'
|
|
2377
|
+
semiEl.textContent = ';'
|
|
2378
|
+
|
|
2379
|
+
row.appendChild(keyEl)
|
|
2380
|
+
row.appendChild(colonEl)
|
|
2381
|
+
row.appendChild(valEl)
|
|
2382
|
+
row.appendChild(arrows)
|
|
2383
|
+
row.appendChild(semiEl)
|
|
2384
|
+
return row
|
|
2385
|
+
}
|
|
2386
|
+
|
|
2243
2387
|
const semiEl = document.createElement('span')
|
|
2244
2388
|
semiEl.className = 'prop-semi'
|
|
2245
2389
|
semiEl.textContent = ';'
|
|
@@ -2251,6 +2395,49 @@
|
|
|
2251
2395
|
return row
|
|
2252
2396
|
}
|
|
2253
2397
|
|
|
2398
|
+
// All known prop keys for autocomplete
|
|
2399
|
+
const KNOWN_PROP_KEYS = [
|
|
2400
|
+
// Layout
|
|
2401
|
+
'display', 'position', 'overflow', 'overflowX', 'overflowY', 'visibility',
|
|
2402
|
+
'flex', 'flexDirection', 'flexWrap', 'flexGrow', 'flexShrink', 'flexBasis',
|
|
2403
|
+
'alignItems', 'alignContent', 'alignSelf', 'justifyContent', 'justifyItems', 'justifySelf',
|
|
2404
|
+
'flow', 'wrap', 'order',
|
|
2405
|
+
// Spacing
|
|
2406
|
+
'padding', 'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft',
|
|
2407
|
+
'paddingInline', 'paddingBlock',
|
|
2408
|
+
'margin', 'marginTop', 'marginRight', 'marginBottom', 'marginLeft',
|
|
2409
|
+
'marginInline', 'marginBlock',
|
|
2410
|
+
'gap', 'rowGap', 'columnGap',
|
|
2411
|
+
// Size
|
|
2412
|
+
'width', 'height', 'minWidth', 'maxWidth', 'minHeight', 'maxHeight', 'boxSize',
|
|
2413
|
+
// Position
|
|
2414
|
+
'top', 'right', 'bottom', 'left', 'inset', 'zIndex',
|
|
2415
|
+
// Color
|
|
2416
|
+
'color', 'background', 'backgroundColor', 'borderColor', 'fill', 'stroke',
|
|
2417
|
+
'opacity',
|
|
2418
|
+
// Typography
|
|
2419
|
+
'fontSize', 'fontFamily', 'fontWeight', 'fontStyle', 'lineHeight', 'letterSpacing',
|
|
2420
|
+
'textAlign', 'textDecoration', 'textTransform', 'textOverflow', 'whiteSpace', 'wordBreak',
|
|
2421
|
+
// Border
|
|
2422
|
+
'border', 'borderWidth', 'borderStyle', 'borderRadius', 'round',
|
|
2423
|
+
'borderTop', 'borderRight', 'borderBottom', 'borderLeft',
|
|
2424
|
+
// Effects
|
|
2425
|
+
'boxShadow', 'transition', 'transform', 'animation', 'cursor', 'pointerEvents',
|
|
2426
|
+
'userSelect', 'outline', 'outlineColor', 'outlineWidth', 'outlineOffset',
|
|
2427
|
+
// Background
|
|
2428
|
+
'backgroundImage', 'backgroundSize', 'backgroundRepeat', 'backgroundPosition', 'backgroundClip',
|
|
2429
|
+
// Grid
|
|
2430
|
+
'gridTemplate', 'gridTemplateColumns', 'gridTemplateRows', 'gridColumn', 'gridRow',
|
|
2431
|
+
'gridArea', 'gridGap',
|
|
2432
|
+
// DOMQL specific
|
|
2433
|
+
'tag', 'text', 'html', 'theme', 'scope', 'if', 'content',
|
|
2434
|
+
'src', 'href', 'placeholder', 'type', 'value', 'title', 'alt',
|
|
2435
|
+
'childExtend', 'childProps', 'extend', 'props',
|
|
2436
|
+
// Other
|
|
2437
|
+
'objectFit', 'objectPosition', 'resize', 'appearance', 'boxSizing',
|
|
2438
|
+
'willChange', 'verticalAlign', 'mixBlendMode', 'backdropFilter', 'filter'
|
|
2439
|
+
]
|
|
2440
|
+
|
|
2254
2441
|
function createAddButton (type) {
|
|
2255
2442
|
const wrap = document.createElement('div')
|
|
2256
2443
|
wrap.className = 'prop-add-row'
|
|
@@ -2261,7 +2448,7 @@
|
|
|
2261
2448
|
// Replace button with inline key:value editor
|
|
2262
2449
|
wrap.innerHTML = ''
|
|
2263
2450
|
const row = document.createElement('div')
|
|
2264
|
-
row.className = 'prop-row'
|
|
2451
|
+
row.className = 'prop-row prop-add-inline'
|
|
2265
2452
|
|
|
2266
2453
|
const keyInput = document.createElement('input')
|
|
2267
2454
|
keyInput.className = 'prop-edit-input'
|
|
@@ -2287,7 +2474,112 @@
|
|
|
2287
2474
|
row.appendChild(semiEl)
|
|
2288
2475
|
wrap.appendChild(row)
|
|
2289
2476
|
|
|
2477
|
+
// Key autocomplete dropdown
|
|
2478
|
+
const keyDropdown = document.createElement('div')
|
|
2479
|
+
keyDropdown.className = 'prop-dropdown'
|
|
2480
|
+
keyDropdown.style.display = 'none'
|
|
2481
|
+
row.appendChild(keyDropdown)
|
|
2482
|
+
|
|
2483
|
+
// Value autocomplete dropdown
|
|
2484
|
+
const valDropdown = document.createElement('div')
|
|
2485
|
+
valDropdown.className = 'prop-dropdown'
|
|
2486
|
+
valDropdown.style.display = 'none'
|
|
2487
|
+
row.appendChild(valDropdown)
|
|
2488
|
+
|
|
2489
|
+
let keyFiltered = []
|
|
2490
|
+
let keyActiveIdx = -1
|
|
2491
|
+
let valFiltered = []
|
|
2492
|
+
let valActiveIdx = -1
|
|
2493
|
+
let activeDropdown = null
|
|
2494
|
+
|
|
2495
|
+
function showKeyDropdown () {
|
|
2496
|
+
activeDropdown = 'key'
|
|
2497
|
+
valDropdown.style.display = 'none'
|
|
2498
|
+
const text = keyInput.value.toLowerCase()
|
|
2499
|
+
// Exclude props that already exist
|
|
2500
|
+
const existing = selectedInfo ? Object.keys(selectedInfo.props || {}) : []
|
|
2501
|
+
keyFiltered = KNOWN_PROP_KEYS
|
|
2502
|
+
.filter(k => !existing.includes(k) && (text ? k.toLowerCase().includes(text) : true))
|
|
2503
|
+
.slice(0, 20)
|
|
2504
|
+
.map(k => ({ label: k }))
|
|
2505
|
+
keyActiveIdx = -1
|
|
2506
|
+
renderKeyDD()
|
|
2507
|
+
}
|
|
2508
|
+
|
|
2509
|
+
function renderKeyDD () {
|
|
2510
|
+
keyDropdown.innerHTML = ''
|
|
2511
|
+
if (keyFiltered.length === 0) { keyDropdown.style.display = 'none'; return }
|
|
2512
|
+
keyDropdown.style.display = ''
|
|
2513
|
+
for (let i = 0; i < keyFiltered.length; i++) {
|
|
2514
|
+
const item = document.createElement('div')
|
|
2515
|
+
item.className = 'prop-dropdown-item' + (i === keyActiveIdx ? ' active' : '')
|
|
2516
|
+
item.textContent = keyFiltered[i].label
|
|
2517
|
+
item.addEventListener('mousedown', (e) => {
|
|
2518
|
+
e.preventDefault()
|
|
2519
|
+
keyInput.value = keyFiltered[i].label
|
|
2520
|
+
keyDropdown.style.display = 'none'
|
|
2521
|
+
valInput.focus()
|
|
2522
|
+
showValDropdown()
|
|
2523
|
+
})
|
|
2524
|
+
keyDropdown.appendChild(item)
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
|
|
2528
|
+
function showValDropdown () {
|
|
2529
|
+
activeDropdown = 'val'
|
|
2530
|
+
keyDropdown.style.display = 'none'
|
|
2531
|
+
const propName = keyInput.value.trim()
|
|
2532
|
+
const allSuggestions = getSuggestionsForProp(propName) || []
|
|
2533
|
+
const text = valInput.value.toLowerCase()
|
|
2534
|
+
valFiltered = text
|
|
2535
|
+
? allSuggestions.filter(s => s.label.toLowerCase().includes(text)).slice(0, 20)
|
|
2536
|
+
: allSuggestions.slice(0, 20)
|
|
2537
|
+
valActiveIdx = -1
|
|
2538
|
+
renderValDD()
|
|
2539
|
+
}
|
|
2540
|
+
|
|
2541
|
+
function renderValDD () {
|
|
2542
|
+
valDropdown.innerHTML = ''
|
|
2543
|
+
if (valFiltered.length === 0) { valDropdown.style.display = 'none'; return }
|
|
2544
|
+
valDropdown.style.display = ''
|
|
2545
|
+
for (let i = 0; i < valFiltered.length; i++) {
|
|
2546
|
+
const s = valFiltered[i]
|
|
2547
|
+
const item = document.createElement('div')
|
|
2548
|
+
item.className = 'prop-dropdown-item' + (i === valActiveIdx ? ' active' : '')
|
|
2549
|
+
if (s.hex !== undefined) {
|
|
2550
|
+
const sw = document.createElement('span')
|
|
2551
|
+
sw.className = 'dd-swatch'
|
|
2552
|
+
sw.style.background = s.hex || '#555'
|
|
2553
|
+
item.appendChild(sw)
|
|
2554
|
+
}
|
|
2555
|
+
const label = document.createElement('span')
|
|
2556
|
+
label.textContent = s.label
|
|
2557
|
+
item.appendChild(label)
|
|
2558
|
+
if (s.hint) {
|
|
2559
|
+
const hint = document.createElement('span')
|
|
2560
|
+
hint.className = 'dd-hint'
|
|
2561
|
+
hint.textContent = s.hint
|
|
2562
|
+
item.appendChild(hint)
|
|
2563
|
+
}
|
|
2564
|
+
item.addEventListener('mousedown', (e) => {
|
|
2565
|
+
e.preventDefault()
|
|
2566
|
+
valInput.value = s.label
|
|
2567
|
+
valDropdown.style.display = 'none'
|
|
2568
|
+
})
|
|
2569
|
+
valDropdown.appendChild(item)
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
|
|
2573
|
+
keyInput.addEventListener('input', showKeyDropdown)
|
|
2574
|
+
keyInput.addEventListener('focus', showKeyDropdown)
|
|
2575
|
+
keyInput.addEventListener('blur', () => setTimeout(() => { keyDropdown.style.display = 'none' }, 150))
|
|
2576
|
+
|
|
2577
|
+
valInput.addEventListener('input', showValDropdown)
|
|
2578
|
+
valInput.addEventListener('focus', showValDropdown)
|
|
2579
|
+
valInput.addEventListener('blur', () => setTimeout(() => { valDropdown.style.display = 'none' }, 150))
|
|
2580
|
+
|
|
2290
2581
|
keyInput.focus()
|
|
2582
|
+
showKeyDropdown()
|
|
2291
2583
|
|
|
2292
2584
|
const commit = async () => {
|
|
2293
2585
|
const key = keyInput.value.trim()
|
|
@@ -2316,13 +2608,31 @@
|
|
|
2316
2608
|
}
|
|
2317
2609
|
|
|
2318
2610
|
valInput.addEventListener('keydown', (e) => {
|
|
2611
|
+
if (activeDropdown === 'val' && valFiltered.length > 0) {
|
|
2612
|
+
if (e.key === 'ArrowDown') { e.preventDefault(); valActiveIdx = Math.min(valActiveIdx + 1, valFiltered.length - 1); renderValDD(); return }
|
|
2613
|
+
if (e.key === 'ArrowUp') { e.preventDefault(); valActiveIdx = Math.max(valActiveIdx - 1, 0); renderValDD(); return }
|
|
2614
|
+
if (e.key === 'Enter' && valActiveIdx >= 0) { e.preventDefault(); valInput.value = valFiltered[valActiveIdx].label; valDropdown.style.display = 'none'; commit(); return }
|
|
2615
|
+
}
|
|
2319
2616
|
if (e.key === 'Enter') commit()
|
|
2320
2617
|
if (e.key === 'Escape') renderDetail()
|
|
2321
2618
|
})
|
|
2322
2619
|
keyInput.addEventListener('keydown', (e) => {
|
|
2620
|
+
if (activeDropdown === 'key' && keyFiltered.length > 0) {
|
|
2621
|
+
if (e.key === 'ArrowDown') { e.preventDefault(); keyActiveIdx = Math.min(keyActiveIdx + 1, keyFiltered.length - 1); renderKeyDD(); return }
|
|
2622
|
+
if (e.key === 'ArrowUp') { e.preventDefault(); keyActiveIdx = Math.max(keyActiveIdx - 1, 0); renderKeyDD(); return }
|
|
2623
|
+
if ((e.key === 'Enter' || e.key === 'Tab') && keyActiveIdx >= 0) {
|
|
2624
|
+
e.preventDefault()
|
|
2625
|
+
keyInput.value = keyFiltered[keyActiveIdx].label
|
|
2626
|
+
keyDropdown.style.display = 'none'
|
|
2627
|
+
valInput.focus()
|
|
2628
|
+
showValDropdown()
|
|
2629
|
+
return
|
|
2630
|
+
}
|
|
2631
|
+
}
|
|
2323
2632
|
if (e.key === 'Enter' || e.key === 'Tab') {
|
|
2324
2633
|
e.preventDefault()
|
|
2325
2634
|
valInput.focus()
|
|
2635
|
+
showValDropdown()
|
|
2326
2636
|
}
|
|
2327
2637
|
if (e.key === 'Escape') renderDetail()
|
|
2328
2638
|
})
|
|
@@ -2647,23 +2957,15 @@
|
|
|
2647
2957
|
// ============================================================
|
|
2648
2958
|
// Design System tab
|
|
2649
2959
|
// ============================================================
|
|
2650
|
-
async function
|
|
2651
|
-
const container = document.getElementById('design-system-container')
|
|
2652
|
-
container.innerHTML = ''
|
|
2653
|
-
|
|
2654
|
-
// Try runtime — check multiple paths for designSystem
|
|
2655
|
-
let ds = null
|
|
2960
|
+
async function fetchRuntimeDesignSystem () {
|
|
2656
2961
|
try {
|
|
2657
2962
|
const raw = await pageEval(`(function(){
|
|
2658
2963
|
var I = window.__DOMQL_INSPECTOR__;
|
|
2659
2964
|
if (!I) return 'null';
|
|
2660
|
-
// Try from root context
|
|
2661
2965
|
var ds = I.getDesignSystem();
|
|
2662
2966
|
if (ds) return JSON.stringify(ds);
|
|
2663
|
-
// Try from any element that has context
|
|
2664
2967
|
var root = I.findRoot();
|
|
2665
2968
|
if (root) {
|
|
2666
|
-
// Walk to find any child with context.designSystem
|
|
2667
2969
|
function findDS(el, depth) {
|
|
2668
2970
|
if (depth > 4 || !el) return null;
|
|
2669
2971
|
if (el.context && el.context.designSystem) return el;
|
|
@@ -2684,39 +2986,96 @@
|
|
|
2684
2986
|
}
|
|
2685
2987
|
return 'null';
|
|
2686
2988
|
})()`)
|
|
2687
|
-
if (raw && raw !== 'null')
|
|
2989
|
+
if (raw && raw !== 'null') return JSON.parse(raw)
|
|
2688
2990
|
} catch (e) { /* ignore */ }
|
|
2991
|
+
return null
|
|
2992
|
+
}
|
|
2993
|
+
|
|
2994
|
+
async function renderDesignSystem () {
|
|
2995
|
+
const container = document.getElementById('design-system-container')
|
|
2996
|
+
container.innerHTML = ''
|
|
2997
|
+
|
|
2998
|
+
// Build sub-tabs: Original / Computed
|
|
2999
|
+
const subtabs = document.createElement('div')
|
|
3000
|
+
subtabs.id = 'ds-subtabs'
|
|
3001
|
+
subtabs.innerHTML = '<button class="props-subtab active" data-dsview="original">Original</button>' +
|
|
3002
|
+
'<button class="props-subtab" data-dsview="computed">Computed</button>'
|
|
3003
|
+
|
|
3004
|
+
const originalPanel = document.createElement('div')
|
|
3005
|
+
originalPanel.id = 'ds-original'
|
|
3006
|
+
originalPanel.className = 'props-subpanel active'
|
|
3007
|
+
|
|
3008
|
+
const computedPanel = document.createElement('div')
|
|
3009
|
+
computedPanel.id = 'ds-computed'
|
|
3010
|
+
computedPanel.className = 'props-subpanel'
|
|
3011
|
+
|
|
3012
|
+
container.appendChild(subtabs)
|
|
3013
|
+
container.appendChild(originalPanel)
|
|
3014
|
+
container.appendChild(computedPanel)
|
|
3015
|
+
|
|
3016
|
+
// Wire sub-tab switching
|
|
3017
|
+
subtabs.querySelectorAll('.props-subtab').forEach(btn => {
|
|
3018
|
+
btn.addEventListener('click', () => {
|
|
3019
|
+
subtabs.querySelectorAll('.props-subtab').forEach(b => b.classList.remove('active'))
|
|
3020
|
+
btn.classList.add('active')
|
|
3021
|
+
originalPanel.classList.toggle('active', btn.dataset.dsview === 'original')
|
|
3022
|
+
computedPanel.classList.toggle('active', btn.dataset.dsview === 'computed')
|
|
3023
|
+
})
|
|
3024
|
+
})
|
|
2689
3025
|
|
|
2690
|
-
//
|
|
2691
|
-
|
|
2692
|
-
|
|
3026
|
+
// --- Original (from source files) ---
|
|
3027
|
+
const originalDS = buildDesignSystemFromFiles()
|
|
3028
|
+
if (originalDS && Object.keys(originalDS).length > 0) {
|
|
3029
|
+
cachedDesignSystem = originalDS
|
|
3030
|
+
renderDesignSystemContent(originalPanel, originalDS, true)
|
|
3031
|
+
} else {
|
|
3032
|
+
originalPanel.innerHTML = '<div class="empty-message">No source files found. Connect a local folder or platform to view original design system.</div>'
|
|
2693
3033
|
}
|
|
2694
3034
|
|
|
2695
|
-
|
|
3035
|
+
// --- Computed (from runtime) ---
|
|
3036
|
+
const runtimeDS = await fetchRuntimeDesignSystem()
|
|
3037
|
+
if (runtimeDS && Object.keys(runtimeDS).length > 0) {
|
|
3038
|
+
if (!cachedDesignSystem) cachedDesignSystem = runtimeDS
|
|
3039
|
+
renderDesignSystemContent(computedPanel, runtimeDS, false)
|
|
3040
|
+
} else {
|
|
2696
3041
|
if (!renderDesignSystem._retried) {
|
|
2697
3042
|
renderDesignSystem._retried = true
|
|
2698
|
-
|
|
2699
|
-
setTimeout(() =>
|
|
3043
|
+
computedPanel.innerHTML = '<div class="empty-message">Loading...</div>'
|
|
3044
|
+
setTimeout(async () => {
|
|
3045
|
+
const ds = await fetchRuntimeDesignSystem()
|
|
3046
|
+
renderDesignSystem._retried = false
|
|
3047
|
+
if (ds && Object.keys(ds).length > 0) {
|
|
3048
|
+
if (!cachedDesignSystem) cachedDesignSystem = ds
|
|
3049
|
+
computedPanel.innerHTML = ''
|
|
3050
|
+
renderDesignSystemContent(computedPanel, ds, false)
|
|
3051
|
+
} else {
|
|
3052
|
+
computedPanel.innerHTML = '<div class="empty-message">Design system is empty at runtime</div>'
|
|
3053
|
+
}
|
|
3054
|
+
}, 2000)
|
|
2700
3055
|
} else {
|
|
2701
3056
|
renderDesignSystem._retried = false
|
|
2702
|
-
|
|
3057
|
+
computedPanel.innerHTML = '<div class="empty-message">Design system is empty at runtime</div>'
|
|
2703
3058
|
}
|
|
2704
|
-
return
|
|
2705
3059
|
}
|
|
2706
|
-
renderDesignSystem._retried = false
|
|
2707
3060
|
|
|
2708
|
-
|
|
3061
|
+
// If no original but we have computed, default to computed tab
|
|
3062
|
+
if ((!originalDS || Object.keys(originalDS).length === 0) && runtimeDS && Object.keys(runtimeDS).length > 0) {
|
|
3063
|
+
subtabs.querySelectorAll('.props-subtab').forEach(b => b.classList.remove('active'))
|
|
3064
|
+
subtabs.querySelector('[data-dsview="computed"]').classList.add('active')
|
|
3065
|
+
originalPanel.classList.remove('active')
|
|
3066
|
+
computedPanel.classList.add('active')
|
|
3067
|
+
}
|
|
2709
3068
|
}
|
|
2710
3069
|
|
|
2711
|
-
function renderDesignSystemContent (container, ds) {
|
|
3070
|
+
function renderDesignSystemContent (container, ds, isOriginal) {
|
|
2712
3071
|
|
|
2713
3072
|
// Render known categories in order, then the rest
|
|
2714
|
-
const categoryOrder = ['color', 'colors', 'theme', 'themes', 'spacing', 'space', '
|
|
3073
|
+
const categoryOrder = ['color', 'COLOR', 'colors', 'gradient', 'GRADIENT', 'theme', 'THEME', 'themes', 'font', 'FONT', 'font_family', 'FONT_FAMILY', 'typography', 'TYPOGRAPHY', 'spacing', 'SPACING', 'space', 'timing', 'TIMING', 'class', 'CLASS', 'grid', 'GRID', 'icons', 'ICONS', 'shape', 'SHAPE', 'reset', 'RESET', 'animation', 'ANIMATION', 'media', 'MEDIA', 'cases', 'CASES', 'fontSize', 'fontFamily', 'fontWeight', 'lineHeight', 'letterSpacing', 'borderRadius', 'shadow', 'breakpoints', 'opacity', 'transition']
|
|
2715
3074
|
const rendered = {}
|
|
2716
3075
|
|
|
2717
3076
|
for (const cat of categoryOrder) {
|
|
2718
3077
|
if (ds[cat] !== undefined) {
|
|
2719
|
-
renderDSCategory(container, cat, ds[cat], [cat])
|
|
3078
|
+
renderDSCategory(container, cat, ds[cat], [cat], isOriginal)
|
|
2720
3079
|
rendered[cat] = true
|
|
2721
3080
|
}
|
|
2722
3081
|
}
|
|
@@ -2726,36 +3085,59 @@
|
|
|
2726
3085
|
if (rendered[key]) continue
|
|
2727
3086
|
if (key.startsWith('__') || typeof ds[key] === 'function') continue
|
|
2728
3087
|
if (ds[key] && ds[key].__type === 'function') continue
|
|
2729
|
-
renderDSCategory(container, key, ds[key], [key])
|
|
3088
|
+
renderDSCategory(container, key, ds[key], [key], isOriginal)
|
|
2730
3089
|
}
|
|
2731
3090
|
}
|
|
2732
3091
|
|
|
2733
3092
|
function buildDesignSystemFromFiles () {
|
|
2734
3093
|
const ds = {}
|
|
3094
|
+
const dsFiles = []
|
|
3095
|
+
|
|
3096
|
+
// Collect all DS-related files
|
|
2735
3097
|
for (const [path, content] of Object.entries(fileCache)) {
|
|
2736
3098
|
if (!content) continue
|
|
2737
|
-
|
|
2738
|
-
const isDS = /designSystem/i.test(path) || /design.system/i.test(path)
|
|
3099
|
+
const isDS = /designSystem/i.test(path) || /design.system/i.test(path) || /design-system/i.test(path)
|
|
2739
3100
|
if (!isDS) continue
|
|
3101
|
+
dsFiles.push({ path, content })
|
|
3102
|
+
}
|
|
3103
|
+
|
|
3104
|
+
if (dsFiles.length === 0) return ds
|
|
2740
3105
|
|
|
2741
|
-
|
|
3106
|
+
// Sort: index files last (they aggregate), category files first
|
|
3107
|
+
dsFiles.sort((a, b) => {
|
|
3108
|
+
const aName = a.path.split('/').pop().replace(/\.(js|jsx|ts|tsx|json)$/i, '').toLowerCase()
|
|
3109
|
+
const bName = b.path.split('/').pop().replace(/\.(js|jsx|ts|tsx|json)$/i, '').toLowerCase()
|
|
3110
|
+
const aIsIndex = aName === 'index' || aName === 'designsystem'
|
|
3111
|
+
const bIsIndex = bName === 'index' || bName === 'designsystem'
|
|
3112
|
+
if (aIsIndex && !bIsIndex) return 1
|
|
3113
|
+
if (!aIsIndex && bIsIndex) return -1
|
|
3114
|
+
return 0
|
|
3115
|
+
})
|
|
3116
|
+
|
|
3117
|
+
for (const { path, content } of dsFiles) {
|
|
2742
3118
|
const name = path.split('/').pop().replace(/\.(js|jsx|ts|tsx|json)$/i, '')
|
|
3119
|
+
const nameLower = name.toLowerCase()
|
|
2743
3120
|
try {
|
|
2744
3121
|
if (path.endsWith('.json')) {
|
|
2745
3122
|
const parsed = JSON.parse(content)
|
|
2746
|
-
if (
|
|
2747
|
-
|
|
3123
|
+
if (nameLower === 'index' || nameLower === 'designsystem') {
|
|
3124
|
+
// Index only adds keys not already present from individual files
|
|
3125
|
+
for (const [k, v] of Object.entries(parsed)) {
|
|
3126
|
+
if (!ds[k] && !ds[k.toLowerCase()]) ds[k] = v
|
|
3127
|
+
}
|
|
2748
3128
|
} else {
|
|
2749
3129
|
ds[name] = parsed
|
|
2750
3130
|
}
|
|
2751
3131
|
} else {
|
|
2752
3132
|
// Try to extract object literals from export default or module.exports
|
|
2753
|
-
const match = content.match(/(?:export\s+default|module\.exports\s*=)\s*(\{[\s\S]
|
|
3133
|
+
const match = content.match(/(?:export\s+default|module\.exports\s*=)\s*(\{[\s\S]*?\})\s*;?\s*$/m)
|
|
2754
3134
|
if (match) {
|
|
2755
3135
|
try {
|
|
2756
3136
|
const obj = (new Function('return ' + match[1]))()
|
|
2757
|
-
if (
|
|
2758
|
-
Object.
|
|
3137
|
+
if (nameLower === 'index' || nameLower === 'designsystem') {
|
|
3138
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
3139
|
+
if (!ds[k] && !ds[k.toLowerCase()]) ds[k] = v
|
|
3140
|
+
}
|
|
2759
3141
|
} else {
|
|
2760
3142
|
ds[name] = obj
|
|
2761
3143
|
}
|
|
@@ -2767,7 +3149,7 @@
|
|
|
2767
3149
|
return ds
|
|
2768
3150
|
}
|
|
2769
3151
|
|
|
2770
|
-
function renderDSCategory (container, name, value, path) {
|
|
3152
|
+
function renderDSCategory (container, name, value, path, isOriginal) {
|
|
2771
3153
|
const section = document.createElement('div')
|
|
2772
3154
|
section.className = 'ds-category'
|
|
2773
3155
|
|
|
@@ -2797,7 +3179,7 @@
|
|
|
2797
3179
|
body.className = 'ds-category-body'
|
|
2798
3180
|
body.style.display = 'none'
|
|
2799
3181
|
|
|
2800
|
-
renderDSValue(body, value, path, 0)
|
|
3182
|
+
renderDSValue(body, value, path, 0, isOriginal)
|
|
2801
3183
|
|
|
2802
3184
|
section.appendChild(body)
|
|
2803
3185
|
container.appendChild(section)
|
|
@@ -2811,7 +3193,7 @@
|
|
|
2811
3193
|
})
|
|
2812
3194
|
}
|
|
2813
3195
|
|
|
2814
|
-
function renderDSValue (container, value, path, depth) {
|
|
3196
|
+
function renderDSValue (container, value, path, depth, isOriginal) {
|
|
2815
3197
|
if (depth > 6) return
|
|
2816
3198
|
if (value === null || value === undefined) return
|
|
2817
3199
|
if (value && value.__type === 'function') {
|
|
@@ -2860,7 +3242,7 @@
|
|
|
2860
3242
|
|
|
2861
3243
|
const subBody = document.createElement('div')
|
|
2862
3244
|
subBody.style.display = 'none'
|
|
2863
|
-
renderDSValue(subBody, val, [...path, key], depth + 1)
|
|
3245
|
+
renderDSValue(subBody, val, [...path, key], depth + 1, isOriginal)
|
|
2864
3246
|
container.appendChild(subBody)
|
|
2865
3247
|
|
|
2866
3248
|
let subExpanded = false
|
|
@@ -2898,19 +3280,23 @@
|
|
|
2898
3280
|
row.appendChild(colon)
|
|
2899
3281
|
row.appendChild(valSpan)
|
|
2900
3282
|
|
|
2901
|
-
// Make editable on click
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
3283
|
+
// Make editable on click — only for original, computed is read-only
|
|
3284
|
+
if (isOriginal) {
|
|
3285
|
+
row.style.cursor = 'pointer'
|
|
3286
|
+
row.addEventListener('click', (e) => {
|
|
3287
|
+
e.stopPropagation()
|
|
3288
|
+
editDSValue(row, path, key, val, valSpan, isOriginal)
|
|
3289
|
+
})
|
|
3290
|
+
} else {
|
|
3291
|
+
row.classList.add('ds-readonly')
|
|
3292
|
+
}
|
|
2907
3293
|
|
|
2908
3294
|
container.appendChild(row)
|
|
2909
3295
|
}
|
|
2910
3296
|
}
|
|
2911
3297
|
}
|
|
2912
3298
|
|
|
2913
|
-
function editDSValue (row, path, key, currentVal, valSpan) {
|
|
3299
|
+
function editDSValue (row, path, key, currentVal, valSpan, isOriginal) {
|
|
2914
3300
|
if (row.querySelector('input')) return // already editing
|
|
2915
3301
|
|
|
2916
3302
|
const input = document.createElement('input')
|
|
@@ -2919,10 +3305,85 @@
|
|
|
2919
3305
|
input.value = typeof currentVal === 'string' ? currentVal : JSON.stringify(currentVal)
|
|
2920
3306
|
input.style.width = '120px'
|
|
2921
3307
|
|
|
3308
|
+
// Build autocomplete for DS values
|
|
3309
|
+
const dsSuggestions = getDSSuggestionsForPath(path, key)
|
|
3310
|
+
let dropdown = null
|
|
3311
|
+
let filtered = []
|
|
3312
|
+
let activeIndex = -1
|
|
3313
|
+
|
|
2922
3314
|
valSpan.style.display = 'none'
|
|
2923
3315
|
row.appendChild(input)
|
|
3316
|
+
|
|
3317
|
+
if (dsSuggestions.length > 0) {
|
|
3318
|
+
dropdown = document.createElement('div')
|
|
3319
|
+
dropdown.className = 'prop-dropdown'
|
|
3320
|
+
row.appendChild(dropdown)
|
|
3321
|
+
}
|
|
3322
|
+
|
|
3323
|
+
function filterSuggestions (text) {
|
|
3324
|
+
const lower = (text || '').toLowerCase()
|
|
3325
|
+
filtered = lower
|
|
3326
|
+
? dsSuggestions.filter(s => s.label.toLowerCase().includes(lower))
|
|
3327
|
+
: dsSuggestions.slice()
|
|
3328
|
+
if (filtered.length > 30) filtered = filtered.slice(0, 30)
|
|
3329
|
+
activeIndex = -1
|
|
3330
|
+
renderDDItems()
|
|
3331
|
+
}
|
|
3332
|
+
|
|
3333
|
+
function renderDDItems () {
|
|
3334
|
+
if (!dropdown) return
|
|
3335
|
+
dropdown.innerHTML = ''
|
|
3336
|
+
if (filtered.length === 0) { dropdown.style.display = 'none'; return }
|
|
3337
|
+
dropdown.style.display = ''
|
|
3338
|
+
for (let i = 0; i < filtered.length; i++) {
|
|
3339
|
+
const s = filtered[i]
|
|
3340
|
+
const item = document.createElement('div')
|
|
3341
|
+
item.className = 'prop-dropdown-item' + (i === activeIndex ? ' active' : '')
|
|
3342
|
+
if (s.hex !== undefined) {
|
|
3343
|
+
const sw = document.createElement('span')
|
|
3344
|
+
sw.className = 'dd-swatch'
|
|
3345
|
+
sw.style.background = s.hex || 'repeating-conic-gradient(#555 0% 25%, transparent 0% 50%) 50%/8px 8px'
|
|
3346
|
+
item.appendChild(sw)
|
|
3347
|
+
}
|
|
3348
|
+
const label = document.createElement('span')
|
|
3349
|
+
label.className = 'dd-label'
|
|
3350
|
+
label.textContent = s.label
|
|
3351
|
+
item.appendChild(label)
|
|
3352
|
+
if (s.hint) {
|
|
3353
|
+
const hint = document.createElement('span')
|
|
3354
|
+
hint.className = 'dd-hint'
|
|
3355
|
+
hint.textContent = s.hint
|
|
3356
|
+
item.appendChild(hint)
|
|
3357
|
+
}
|
|
3358
|
+
item.addEventListener('mousedown', (e) => {
|
|
3359
|
+
e.preventDefault()
|
|
3360
|
+
input.value = s.label
|
|
3361
|
+
commit()
|
|
3362
|
+
})
|
|
3363
|
+
dropdown.appendChild(item)
|
|
3364
|
+
}
|
|
3365
|
+
}
|
|
3366
|
+
|
|
3367
|
+
input.addEventListener('input', () => filterSuggestions(input.value))
|
|
3368
|
+
input.addEventListener('keydown', (e) => {
|
|
3369
|
+
if (dropdown && filtered.length > 0) {
|
|
3370
|
+
if (e.key === 'ArrowDown') { e.preventDefault(); activeIndex = Math.min(activeIndex + 1, filtered.length - 1); renderDDItems() }
|
|
3371
|
+
else if (e.key === 'ArrowUp') { e.preventDefault(); activeIndex = Math.max(activeIndex - 1, 0); renderDDItems() }
|
|
3372
|
+
else if (e.key === 'Enter' && activeIndex >= 0) { e.preventDefault(); input.value = filtered[activeIndex].label; commit(); return }
|
|
3373
|
+
}
|
|
3374
|
+
if (e.key === 'Enter') { e.preventDefault(); commit() }
|
|
3375
|
+
if (e.key === 'Escape') { cleanup() }
|
|
3376
|
+
})
|
|
3377
|
+
|
|
2924
3378
|
input.focus()
|
|
2925
3379
|
input.select()
|
|
3380
|
+
if (dsSuggestions.length > 0) filterSuggestions(input.value)
|
|
3381
|
+
|
|
3382
|
+
function cleanup () {
|
|
3383
|
+
if (dropdown) dropdown.remove()
|
|
3384
|
+
input.remove()
|
|
3385
|
+
valSpan.style.display = ''
|
|
3386
|
+
}
|
|
2926
3387
|
|
|
2927
3388
|
const commit = async () => {
|
|
2928
3389
|
let newVal = input.value.trim()
|
|
@@ -2932,32 +3393,126 @@
|
|
|
2932
3393
|
else if (newVal === 'null') newVal = null
|
|
2933
3394
|
else if (!isNaN(Number(newVal)) && newVal !== '') newVal = Number(newVal)
|
|
2934
3395
|
|
|
2935
|
-
// Update via pageEval on root.context.designSystem
|
|
2936
3396
|
const fullPath = [...path, key]
|
|
3397
|
+
|
|
3398
|
+
if (isOriginal) {
|
|
3399
|
+
// Update source file via updateDesignSystem pattern
|
|
3400
|
+
// Path: e.g. ['color', 'primary'] -> 'COLOR.primary'
|
|
3401
|
+
const dsKey = fullPath.length > 1
|
|
3402
|
+
? fullPath[0].toUpperCase() + '.' + fullPath.slice(1).join('.')
|
|
3403
|
+
: fullPath[0]
|
|
3404
|
+
if (connectionMode === 'local') {
|
|
3405
|
+
// Find and update the source file
|
|
3406
|
+
const category = fullPath[0]
|
|
3407
|
+
const dsFilePath = findDSSourceFile(category)
|
|
3408
|
+
if (dsFilePath) {
|
|
3409
|
+
await updateDSSourceFile(dsFilePath, fullPath.slice(1), newVal)
|
|
3410
|
+
setStatus('Updated ' + dsKey + ' in source')
|
|
3411
|
+
} else {
|
|
3412
|
+
setStatus('Source file not found for ' + category)
|
|
3413
|
+
}
|
|
3414
|
+
} else {
|
|
3415
|
+
setStatus('Source editing requires local folder connection')
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
3418
|
+
|
|
3419
|
+
// Always update runtime too
|
|
2937
3420
|
const pathStr = fullPath.map(p => '["' + p.replace(/"/g, '\\"') + '"]').join('')
|
|
2938
3421
|
const valExpr = typeof newVal === 'string' ? '"' + newVal.replace(/"/g, '\\"') + '"' : String(newVal)
|
|
2939
|
-
|
|
2940
3422
|
try {
|
|
2941
3423
|
await pageEval(`(function(){
|
|
2942
3424
|
var root = window.__DOMQL_INSPECTOR__.findRoot();
|
|
2943
3425
|
if (!root || !root.context || !root.context.designSystem) return;
|
|
2944
3426
|
root.context.designSystem${pathStr} = ${valExpr};
|
|
2945
3427
|
})()`)
|
|
2946
|
-
setStatus('Updated ' + key)
|
|
3428
|
+
if (!isOriginal) setStatus('Updated ' + key + ' (runtime)')
|
|
2947
3429
|
} catch (e) {
|
|
2948
|
-
setStatus('Error: ' + e.message)
|
|
3430
|
+
if (!isOriginal) setStatus('Error: ' + e.message)
|
|
2949
3431
|
}
|
|
2950
3432
|
|
|
2951
|
-
|
|
3433
|
+
// Update cached DS
|
|
3434
|
+
if (cachedDesignSystem) {
|
|
3435
|
+
let obj = cachedDesignSystem
|
|
3436
|
+
for (let i = 0; i < fullPath.length - 1; i++) {
|
|
3437
|
+
if (!obj[fullPath[i]]) obj[fullPath[i]] = {}
|
|
3438
|
+
obj = obj[fullPath[i]]
|
|
3439
|
+
}
|
|
3440
|
+
obj[fullPath[fullPath.length - 1]] = newVal
|
|
3441
|
+
}
|
|
3442
|
+
|
|
3443
|
+
cleanup()
|
|
2952
3444
|
valSpan.textContent = String(newVal ?? '')
|
|
2953
|
-
valSpan.style.display = ''
|
|
2954
3445
|
}
|
|
2955
3446
|
|
|
2956
|
-
input.addEventListener('
|
|
2957
|
-
if (
|
|
2958
|
-
if (e.key === 'Escape') { input.remove(); valSpan.style.display = '' }
|
|
3447
|
+
input.addEventListener('blur', () => {
|
|
3448
|
+
setTimeout(() => { if (document.activeElement !== input) commit() }, 150)
|
|
2959
3449
|
})
|
|
2960
|
-
|
|
3450
|
+
}
|
|
3451
|
+
|
|
3452
|
+
// Find the source file in fileCache for a DS category
|
|
3453
|
+
function findDSSourceFile (category) {
|
|
3454
|
+
const lc = category.toLowerCase()
|
|
3455
|
+
for (const path of Object.keys(fileCache)) {
|
|
3456
|
+
if (!/designSystem/i.test(path)) continue
|
|
3457
|
+
const filename = path.split('/').pop().replace(/\.(js|jsx|ts|tsx|json)$/i, '').toLowerCase()
|
|
3458
|
+
if (filename === lc || filename === category) return path
|
|
3459
|
+
}
|
|
3460
|
+
return null
|
|
3461
|
+
}
|
|
3462
|
+
|
|
3463
|
+
// Update a value in a DS source file (basic string replacement)
|
|
3464
|
+
async function updateDSSourceFile (filePath, keyPath, newVal) {
|
|
3465
|
+
const content = fileCache[filePath]
|
|
3466
|
+
if (!content) return
|
|
3467
|
+
|
|
3468
|
+
const key = keyPath[keyPath.length - 1]
|
|
3469
|
+
const valStr = typeof newVal === 'string' ? "'" + newVal.replace(/'/g, "\\'") + "'" : String(newVal)
|
|
3470
|
+
|
|
3471
|
+
// Try to find and replace the key-value pair
|
|
3472
|
+
const patterns = [
|
|
3473
|
+
new RegExp("(\\b" + escapeRegExp(key) + "\\s*:\\s*)(['\"][^'\"]*['\"]|[\\d.]+|true|false|null)", 'm'),
|
|
3474
|
+
new RegExp("(['\"]" + escapeRegExp(key) + "['\"]\\s*:\\s*)(['\"][^'\"]*['\"]|[\\d.]+|true|false|null)", 'm')
|
|
3475
|
+
]
|
|
3476
|
+
|
|
3477
|
+
let updated = content
|
|
3478
|
+
let replaced = false
|
|
3479
|
+
for (const pat of patterns) {
|
|
3480
|
+
if (pat.test(updated)) {
|
|
3481
|
+
updated = updated.replace(pat, '$1' + valStr)
|
|
3482
|
+
replaced = true
|
|
3483
|
+
break
|
|
3484
|
+
}
|
|
3485
|
+
}
|
|
3486
|
+
|
|
3487
|
+
if (replaced) {
|
|
3488
|
+
fileCache[filePath] = updated
|
|
3489
|
+
// Save to IndexedDB
|
|
3490
|
+
try {
|
|
3491
|
+
const db = await openDB()
|
|
3492
|
+
const tx = db.transaction(FILES_STORE, 'readwrite')
|
|
3493
|
+
tx.objectStore(FILES_STORE).put(fileCache, folderName)
|
|
3494
|
+
} catch (e) { /* ignore */ }
|
|
3495
|
+
}
|
|
3496
|
+
}
|
|
3497
|
+
|
|
3498
|
+
function escapeRegExp (s) {
|
|
3499
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
3500
|
+
}
|
|
3501
|
+
|
|
3502
|
+
// Get autocomplete suggestions based on DS path context
|
|
3503
|
+
function getDSSuggestionsForPath (path, key) {
|
|
3504
|
+
if (!cachedDesignSystem) return []
|
|
3505
|
+
const category = (path[0] || '').toLowerCase()
|
|
3506
|
+
|
|
3507
|
+
// For color categories, suggest existing color values
|
|
3508
|
+
if (category === 'color' || category === 'colors') {
|
|
3509
|
+
return extractDSColorSuggestions()
|
|
3510
|
+
}
|
|
3511
|
+
// For theme categories, suggest theme names
|
|
3512
|
+
if (category === 'theme' || category === 'themes') {
|
|
3513
|
+
return extractDSKeys('theme').concat(extractDSKeys('THEME')).map(k => ({ label: k }))
|
|
3514
|
+
}
|
|
3515
|
+
return []
|
|
2961
3516
|
}
|
|
2962
3517
|
|
|
2963
3518
|
function renderChildrenTab () {
|
|
@@ -3034,13 +3589,44 @@
|
|
|
3034
3589
|
const item = document.createElement('div')
|
|
3035
3590
|
item.className = 'method-item'
|
|
3036
3591
|
|
|
3592
|
+
const nameRow = document.createElement('div')
|
|
3593
|
+
nameRow.className = 'method-name-row'
|
|
3594
|
+
|
|
3037
3595
|
const name = document.createElement('span')
|
|
3038
3596
|
name.className = 'method-name'
|
|
3039
3597
|
name.textContent = method + '()'
|
|
3040
3598
|
|
|
3041
|
-
const
|
|
3042
|
-
|
|
3043
|
-
|
|
3599
|
+
const toggleBtn = document.createElement('button')
|
|
3600
|
+
toggleBtn.className = 'method-toggle'
|
|
3601
|
+
toggleBtn.textContent = '\u25B6'
|
|
3602
|
+
toggleBtn.title = 'Show input'
|
|
3603
|
+
|
|
3604
|
+
nameRow.appendChild(name)
|
|
3605
|
+
nameRow.appendChild(toggleBtn)
|
|
3606
|
+
item.appendChild(nameRow)
|
|
3607
|
+
|
|
3608
|
+
// Collapsible input area
|
|
3609
|
+
const inputArea = document.createElement('div')
|
|
3610
|
+
inputArea.className = 'method-input-area'
|
|
3611
|
+
inputArea.style.display = 'none'
|
|
3612
|
+
|
|
3613
|
+
const argsTextarea = document.createElement('textarea')
|
|
3614
|
+
argsTextarea.className = 'method-args-textarea'
|
|
3615
|
+
argsTextarea.placeholder = '// JSON or object literal\n{\n "key": "value"\n}'
|
|
3616
|
+
argsTextarea.rows = 6
|
|
3617
|
+
argsTextarea.spellcheck = false
|
|
3618
|
+
argsTextarea.addEventListener('keydown', (e) => {
|
|
3619
|
+
if (e.key === 'Tab') {
|
|
3620
|
+
e.preventDefault()
|
|
3621
|
+
const start = argsTextarea.selectionStart
|
|
3622
|
+
const end = argsTextarea.selectionEnd
|
|
3623
|
+
argsTextarea.value = argsTextarea.value.substring(0, start) + ' ' + argsTextarea.value.substring(end)
|
|
3624
|
+
argsTextarea.selectionStart = argsTextarea.selectionEnd = start + 2
|
|
3625
|
+
}
|
|
3626
|
+
})
|
|
3627
|
+
|
|
3628
|
+
const btnRow = document.createElement('div')
|
|
3629
|
+
btnRow.className = 'method-btn-row'
|
|
3044
3630
|
|
|
3045
3631
|
const btn = document.createElement('button')
|
|
3046
3632
|
btn.className = 'method-btn'
|
|
@@ -3049,15 +3635,43 @@
|
|
|
3049
3635
|
const result = document.createElement('span')
|
|
3050
3636
|
result.className = 'method-result'
|
|
3051
3637
|
|
|
3638
|
+
btnRow.appendChild(btn)
|
|
3639
|
+
btnRow.appendChild(result)
|
|
3640
|
+
inputArea.appendChild(argsTextarea)
|
|
3641
|
+
inputArea.appendChild(btnRow)
|
|
3642
|
+
item.appendChild(inputArea)
|
|
3643
|
+
|
|
3644
|
+
let expanded = false
|
|
3645
|
+
toggleBtn.addEventListener('click', () => {
|
|
3646
|
+
expanded = !expanded
|
|
3647
|
+
toggleBtn.textContent = expanded ? '\u25BC' : '\u25B6'
|
|
3648
|
+
inputArea.style.display = expanded ? 'block' : 'none'
|
|
3649
|
+
})
|
|
3650
|
+
|
|
3651
|
+
// Also toggle on name click
|
|
3652
|
+
name.style.cursor = 'pointer'
|
|
3653
|
+
name.addEventListener('click', () => {
|
|
3654
|
+
expanded = !expanded
|
|
3655
|
+
toggleBtn.textContent = expanded ? '\u25BC' : '\u25B6'
|
|
3656
|
+
inputArea.style.display = expanded ? 'block' : 'none'
|
|
3657
|
+
})
|
|
3658
|
+
|
|
3052
3659
|
btn.addEventListener('click', async () => {
|
|
3053
3660
|
let args = []
|
|
3054
|
-
|
|
3661
|
+
const raw = argsTextarea.value.trim()
|
|
3662
|
+
if (raw) {
|
|
3055
3663
|
try {
|
|
3056
|
-
|
|
3664
|
+
// Try parsing as JSON array first, then as single arg
|
|
3665
|
+
if (raw.startsWith('[')) args = JSON.parse(raw)
|
|
3666
|
+
else args = [JSON.parse(raw)]
|
|
3057
3667
|
} catch (e) {
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3668
|
+
// Try wrapping in array
|
|
3669
|
+
try { args = JSON.parse('[' + raw + ']') }
|
|
3670
|
+
catch (e2) {
|
|
3671
|
+
result.textContent = 'Invalid JSON/object'
|
|
3672
|
+
result.style.color = 'var(--error-color)'
|
|
3673
|
+
return
|
|
3674
|
+
}
|
|
3061
3675
|
}
|
|
3062
3676
|
}
|
|
3063
3677
|
|
|
@@ -3075,14 +3689,14 @@
|
|
|
3075
3689
|
JSON.stringify(args) + '))'
|
|
3076
3690
|
}
|
|
3077
3691
|
|
|
3078
|
-
const
|
|
3079
|
-
const res = JSON.parse(
|
|
3692
|
+
const rawRes = await pageEval(expr)
|
|
3693
|
+
const res = JSON.parse(rawRes)
|
|
3080
3694
|
if (res.error) {
|
|
3081
3695
|
result.textContent = 'Error: ' + res.error
|
|
3082
3696
|
result.style.color = 'var(--error-color)'
|
|
3083
3697
|
} else {
|
|
3084
3698
|
result.textContent = res.result !== undefined ? JSON.stringify(res.result).slice(0, 100) : 'OK'
|
|
3085
|
-
result.style.color = 'var(--
|
|
3699
|
+
result.style.color = 'var(--type-color)'
|
|
3086
3700
|
setTimeout(() => selectElement(selectedPath), 200)
|
|
3087
3701
|
}
|
|
3088
3702
|
} catch (e) {
|
|
@@ -3091,10 +3705,6 @@
|
|
|
3091
3705
|
}
|
|
3092
3706
|
})
|
|
3093
3707
|
|
|
3094
|
-
item.appendChild(name)
|
|
3095
|
-
item.appendChild(argsInput)
|
|
3096
|
-
item.appendChild(btn)
|
|
3097
|
-
item.appendChild(result)
|
|
3098
3708
|
return item
|
|
3099
3709
|
}
|
|
3100
3710
|
|
|
@@ -3163,89 +3773,135 @@
|
|
|
3163
3773
|
const editor = document.createElement('div')
|
|
3164
3774
|
editor.className = 'obj-editor'
|
|
3165
3775
|
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3776
|
+
// Recursively build editor for an object/array at any nesting depth
|
|
3777
|
+
function buildLevel (container, obj, depth, setParentValue) {
|
|
3778
|
+
container.innerHTML = ''
|
|
3779
|
+
const isArr = Array.isArray(obj)
|
|
3780
|
+
const entries = isArr
|
|
3781
|
+
? obj.map((v, i) => [String(i), v])
|
|
3782
|
+
: Object.entries(obj)
|
|
3171
3783
|
|
|
3172
3784
|
for (const [k, v] of entries) {
|
|
3173
3785
|
const row = document.createElement('div')
|
|
3174
3786
|
row.className = 'obj-editor-row'
|
|
3787
|
+
if (depth > 0) row.style.paddingLeft = (depth * 12) + 'px'
|
|
3175
3788
|
|
|
3176
|
-
|
|
3789
|
+
// Key label
|
|
3790
|
+
if (!isArr) {
|
|
3177
3791
|
const keyInput = document.createElement('input')
|
|
3178
3792
|
keyInput.className = 'prop-edit-input obj-key-input'
|
|
3179
3793
|
keyInput.value = k
|
|
3180
3794
|
keyInput.readOnly = true
|
|
3181
3795
|
keyInput.title = k
|
|
3182
3796
|
row.appendChild(keyInput)
|
|
3183
|
-
|
|
3184
|
-
const colon = document.createElement('span')
|
|
3185
|
-
colon.className = 'prop-colon'
|
|
3186
|
-
colon.textContent = ': '
|
|
3187
|
-
row.appendChild(colon)
|
|
3188
3797
|
} else {
|
|
3189
3798
|
const idx = document.createElement('span')
|
|
3190
3799
|
idx.className = 'prop-key'
|
|
3191
3800
|
idx.textContent = k
|
|
3192
3801
|
idx.style.minWidth = '20px'
|
|
3193
3802
|
row.appendChild(idx)
|
|
3194
|
-
|
|
3195
|
-
const colon = document.createElement('span')
|
|
3196
|
-
colon.className = 'prop-colon'
|
|
3197
|
-
colon.textContent = ': '
|
|
3198
|
-
row.appendChild(colon)
|
|
3199
3803
|
}
|
|
3200
3804
|
|
|
3201
|
-
const
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
: typeof v === 'object' ? JSON.stringify(v)
|
|
3206
|
-
: String(v)
|
|
3207
|
-
valInput.dataset.origType = typeof v === 'string' ? 'string'
|
|
3208
|
-
: v === null ? 'null'
|
|
3209
|
-
: typeof v === 'object' ? 'json'
|
|
3210
|
-
: typeof v
|
|
3211
|
-
valInput.addEventListener('change', () => {
|
|
3212
|
-
const parsed = parsePreservingType(valInput.value, valInput.dataset.origType)
|
|
3213
|
-
if (isArray) workingCopy[parseInt(k)] = parsed
|
|
3214
|
-
else workingCopy[k] = parsed
|
|
3215
|
-
})
|
|
3216
|
-
row.appendChild(valInput)
|
|
3217
|
-
|
|
3218
|
-
// Delete button
|
|
3219
|
-
const delBtn = document.createElement('button')
|
|
3220
|
-
delBtn.className = 'obj-editor-del'
|
|
3221
|
-
delBtn.textContent = '\u00d7'
|
|
3222
|
-
delBtn.title = 'Remove'
|
|
3223
|
-
delBtn.addEventListener('click', (e) => {
|
|
3224
|
-
e.stopPropagation()
|
|
3225
|
-
if (isArray) workingCopy.splice(parseInt(k), 1)
|
|
3226
|
-
else delete workingCopy[k]
|
|
3227
|
-
rebuild()
|
|
3228
|
-
})
|
|
3229
|
-
row.appendChild(delBtn)
|
|
3805
|
+
const colon = document.createElement('span')
|
|
3806
|
+
colon.className = 'prop-colon'
|
|
3807
|
+
colon.textContent = ': '
|
|
3808
|
+
row.appendChild(colon)
|
|
3230
3809
|
|
|
3231
|
-
|
|
3810
|
+
const isNested = v !== null && typeof v === 'object'
|
|
3811
|
+
|
|
3812
|
+
if (isNested) {
|
|
3813
|
+
// Nested object/array — show toggle + nested editor
|
|
3814
|
+
const nestedIsArr = Array.isArray(v)
|
|
3815
|
+
const preview = document.createElement('span')
|
|
3816
|
+
preview.className = 'obj-nested-preview'
|
|
3817
|
+
preview.textContent = nestedIsArr ? '[' + v.length + ']' : '{' + Object.keys(v).length + '}'
|
|
3818
|
+
preview.style.cursor = 'pointer'
|
|
3819
|
+
preview.style.color = 'var(--text-dim)'
|
|
3820
|
+
preview.style.fontSize = '10px'
|
|
3821
|
+
row.appendChild(preview)
|
|
3822
|
+
|
|
3823
|
+
// Delete button
|
|
3824
|
+
const delBtn = document.createElement('button')
|
|
3825
|
+
delBtn.className = 'obj-editor-del'
|
|
3826
|
+
delBtn.textContent = '\u00d7'
|
|
3827
|
+
delBtn.title = 'Remove'
|
|
3828
|
+
delBtn.addEventListener('click', (e) => {
|
|
3829
|
+
e.stopPropagation()
|
|
3830
|
+
if (isArr) obj.splice(parseInt(k), 1)
|
|
3831
|
+
else delete obj[k]
|
|
3832
|
+
if (setParentValue) setParentValue(obj)
|
|
3833
|
+
buildLevel(container, obj, depth, setParentValue)
|
|
3834
|
+
})
|
|
3835
|
+
row.appendChild(delBtn)
|
|
3836
|
+
container.appendChild(row)
|
|
3837
|
+
|
|
3838
|
+
// Nested container (collapsed by default for depth > 0)
|
|
3839
|
+
const nestedContainer = document.createElement('div')
|
|
3840
|
+
nestedContainer.className = 'obj-editor-nested'
|
|
3841
|
+
let nestedExpanded = depth === 0
|
|
3842
|
+
if (!nestedExpanded) nestedContainer.style.display = 'none'
|
|
3843
|
+
else buildLevel(nestedContainer, v, depth + 1, (newVal) => {
|
|
3844
|
+
if (isArr) obj[parseInt(k)] = newVal
|
|
3845
|
+
else obj[k] = newVal
|
|
3846
|
+
})
|
|
3847
|
+
|
|
3848
|
+
preview.addEventListener('click', () => {
|
|
3849
|
+
nestedExpanded = !nestedExpanded
|
|
3850
|
+
nestedContainer.style.display = nestedExpanded ? 'block' : 'none'
|
|
3851
|
+
if (nestedExpanded && nestedContainer.innerHTML === '') {
|
|
3852
|
+
buildLevel(nestedContainer, v, depth + 1, (newVal) => {
|
|
3853
|
+
if (isArr) obj[parseInt(k)] = newVal
|
|
3854
|
+
else obj[k] = newVal
|
|
3855
|
+
})
|
|
3856
|
+
}
|
|
3857
|
+
})
|
|
3858
|
+
container.appendChild(nestedContainer)
|
|
3859
|
+
} else {
|
|
3860
|
+
// Primitive value — editable input
|
|
3861
|
+
const valInput = document.createElement('input')
|
|
3862
|
+
valInput.className = 'prop-edit-input'
|
|
3863
|
+
valInput.value = v === null ? 'null' : String(v)
|
|
3864
|
+
valInput.dataset.origType = v === null ? 'null' : typeof v
|
|
3865
|
+
valInput.addEventListener('change', () => {
|
|
3866
|
+
const parsed = parsePreservingType(valInput.value, valInput.dataset.origType)
|
|
3867
|
+
if (isArr) obj[parseInt(k)] = parsed
|
|
3868
|
+
else obj[k] = parsed
|
|
3869
|
+
})
|
|
3870
|
+
row.appendChild(valInput)
|
|
3871
|
+
|
|
3872
|
+
// Delete button
|
|
3873
|
+
const delBtn = document.createElement('button')
|
|
3874
|
+
delBtn.className = 'obj-editor-del'
|
|
3875
|
+
delBtn.textContent = '\u00d7'
|
|
3876
|
+
delBtn.title = 'Remove'
|
|
3877
|
+
delBtn.addEventListener('click', (e) => {
|
|
3878
|
+
e.stopPropagation()
|
|
3879
|
+
if (isArr) obj.splice(parseInt(k), 1)
|
|
3880
|
+
else delete obj[k]
|
|
3881
|
+
if (setParentValue) setParentValue(obj)
|
|
3882
|
+
buildLevel(container, obj, depth, setParentValue)
|
|
3883
|
+
})
|
|
3884
|
+
row.appendChild(delBtn)
|
|
3885
|
+
container.appendChild(row)
|
|
3886
|
+
}
|
|
3232
3887
|
}
|
|
3233
3888
|
|
|
3234
3889
|
// Add entry button
|
|
3235
3890
|
const addRow = document.createElement('div')
|
|
3236
3891
|
addRow.className = 'obj-editor-row obj-editor-add'
|
|
3892
|
+
if (depth > 0) addRow.style.paddingLeft = (depth * 12) + 'px'
|
|
3237
3893
|
const addBtn = document.createElement('button')
|
|
3238
3894
|
addBtn.className = 'prop-add-btn'
|
|
3239
|
-
addBtn.textContent =
|
|
3895
|
+
addBtn.textContent = isArr ? '+ Add item' : '+ Add key'
|
|
3240
3896
|
addBtn.style.fontSize = '10px'
|
|
3241
3897
|
addBtn.style.padding = '2px 6px'
|
|
3242
3898
|
addBtn.addEventListener('click', (e) => {
|
|
3243
3899
|
e.stopPropagation()
|
|
3244
|
-
if (
|
|
3900
|
+
if (isArr) { obj.push(''); buildLevel(container, obj, depth, setParentValue) }
|
|
3245
3901
|
else {
|
|
3246
|
-
// Inline key input instead of prompt()
|
|
3247
3902
|
const keyRow = document.createElement('div')
|
|
3248
3903
|
keyRow.className = 'obj-editor-row'
|
|
3904
|
+
if (depth > 0) keyRow.style.paddingLeft = (depth * 12) + 'px'
|
|
3249
3905
|
const keyInput = document.createElement('input')
|
|
3250
3906
|
keyInput.className = 'prop-edit-input'
|
|
3251
3907
|
keyInput.placeholder = 'key name'
|
|
@@ -3253,25 +3909,27 @@
|
|
|
3253
3909
|
keyRow.appendChild(keyInput)
|
|
3254
3910
|
addRow.before(keyRow)
|
|
3255
3911
|
keyInput.focus()
|
|
3912
|
+
const finishAdd = () => {
|
|
3913
|
+
const newKey = keyInput.value.trim()
|
|
3914
|
+
if (newKey && !(newKey in obj)) { obj[newKey] = ''; buildLevel(container, obj, depth, setParentValue) }
|
|
3915
|
+
else keyRow.remove()
|
|
3916
|
+
}
|
|
3256
3917
|
keyInput.addEventListener('keydown', (ev) => {
|
|
3257
|
-
if (ev.key === 'Enter')
|
|
3258
|
-
const k = keyInput.value.trim()
|
|
3259
|
-
if (k && !(k in workingCopy)) { workingCopy[k] = ''; rebuild() }
|
|
3260
|
-
else keyRow.remove()
|
|
3261
|
-
}
|
|
3918
|
+
if (ev.key === 'Enter') finishAdd()
|
|
3262
3919
|
if (ev.key === 'Escape') keyRow.remove()
|
|
3263
3920
|
})
|
|
3264
|
-
keyInput.addEventListener('blur',
|
|
3265
|
-
const k = keyInput.value.trim()
|
|
3266
|
-
if (k && !(k in workingCopy)) { workingCopy[k] = ''; rebuild() }
|
|
3267
|
-
else keyRow.remove()
|
|
3268
|
-
})
|
|
3921
|
+
keyInput.addEventListener('blur', finishAdd)
|
|
3269
3922
|
}
|
|
3270
3923
|
})
|
|
3271
3924
|
addRow.appendChild(addBtn)
|
|
3272
|
-
|
|
3925
|
+
container.appendChild(addRow)
|
|
3926
|
+
}
|
|
3273
3927
|
|
|
3274
|
-
|
|
3928
|
+
function rebuild () {
|
|
3929
|
+
editor.innerHTML = ''
|
|
3930
|
+
buildLevel(editor, workingCopy, 0, null)
|
|
3931
|
+
|
|
3932
|
+
// Save / Cancel buttons (always at bottom of top-level editor)
|
|
3275
3933
|
const actions = document.createElement('div')
|
|
3276
3934
|
actions.className = 'obj-editor-actions'
|
|
3277
3935
|
const saveBtn = document.createElement('button')
|
|
@@ -3306,24 +3964,7 @@
|
|
|
3306
3964
|
}
|
|
3307
3965
|
|
|
3308
3966
|
async function commitObj () {
|
|
3309
|
-
//
|
|
3310
|
-
const inputs = editor.querySelectorAll('.obj-editor-row:not(.obj-editor-add)')
|
|
3311
|
-
if (isArray) {
|
|
3312
|
-
workingCopy = []
|
|
3313
|
-
inputs.forEach(row => {
|
|
3314
|
-
const inp = row.querySelector('.prop-edit-input')
|
|
3315
|
-
if (inp) workingCopy.push(parsePreservingType(inp.value, inp.dataset.origType))
|
|
3316
|
-
})
|
|
3317
|
-
} else {
|
|
3318
|
-
workingCopy = {}
|
|
3319
|
-
inputs.forEach(row => {
|
|
3320
|
-
const keyInp = row.querySelector('.obj-key-input')
|
|
3321
|
-
const valInp = row.querySelectorAll('.prop-edit-input')[row.querySelector('.obj-key-input') ? 1 : 0]
|
|
3322
|
-
if (keyInp && valInp) {
|
|
3323
|
-
workingCopy[keyInp.value] = parsePreservingType(valInp.value, valInp.dataset.origType)
|
|
3324
|
-
}
|
|
3325
|
-
})
|
|
3326
|
-
}
|
|
3967
|
+
// workingCopy is already updated in-place by input change handlers and recursive buildLevel
|
|
3327
3968
|
|
|
3328
3969
|
el.classList.remove('editing')
|
|
3329
3970
|
el.innerHTML = ''
|
|
@@ -3476,6 +4117,25 @@
|
|
|
3476
4117
|
let newValue = input.value
|
|
3477
4118
|
try { newValue = JSON.parse(newValue) } catch (e) {}
|
|
3478
4119
|
|
|
4120
|
+
// Skip update if value hasn't changed
|
|
4121
|
+
const unchanged = newValue === currentValue ||
|
|
4122
|
+
(typeof newValue === 'string' && typeof currentValue === 'string' && newValue === currentValue) ||
|
|
4123
|
+
JSON.stringify(newValue) === JSON.stringify(currentValue)
|
|
4124
|
+
|
|
4125
|
+
if (unchanged) {
|
|
4126
|
+
el.innerHTML = ''
|
|
4127
|
+
el.appendChild(renderValue(currentValue))
|
|
4128
|
+
if (opts && opts.tabNext) {
|
|
4129
|
+
setTimeout(() => {
|
|
4130
|
+
const allVals = Array.from(document.querySelectorAll('.prop-value.editable'))
|
|
4131
|
+
const idx = allVals.indexOf(el)
|
|
4132
|
+
const next = allVals[idx + (opts.tabBack ? -1 : 1)]
|
|
4133
|
+
if (next) next.click()
|
|
4134
|
+
}, 0)
|
|
4135
|
+
}
|
|
4136
|
+
return
|
|
4137
|
+
}
|
|
4138
|
+
|
|
3479
4139
|
el.innerHTML = ''
|
|
3480
4140
|
el.appendChild(renderValue(newValue))
|
|
3481
4141
|
|
|
@@ -4492,11 +5152,91 @@ Do NOT include any explanation, only valid JSON.`
|
|
|
4492
5152
|
'hr', 'br', 'strong', 'em', 'b', 'i', 'u', 'small', 'mark'
|
|
4493
5153
|
]
|
|
4494
5154
|
|
|
5155
|
+
// Extract DS token suggestions from cached design system
|
|
5156
|
+
function extractDSColorSuggestions () {
|
|
5157
|
+
if (!cachedDesignSystem) return []
|
|
5158
|
+
const colors = cachedDesignSystem.color || cachedDesignSystem.COLOR || cachedDesignSystem.colors || {}
|
|
5159
|
+
const items = []
|
|
5160
|
+
for (const [k, v] of Object.entries(colors)) {
|
|
5161
|
+
if (k.startsWith('__')) continue
|
|
5162
|
+
if (typeof v === 'string') {
|
|
5163
|
+
items.push({ label: k, hex: /^(#|rgb|hsl)/.test(v) ? v : '', hint: v })
|
|
5164
|
+
}
|
|
5165
|
+
}
|
|
5166
|
+
return items
|
|
5167
|
+
}
|
|
5168
|
+
|
|
5169
|
+
function extractDSThemeSuggestions () {
|
|
5170
|
+
if (!cachedDesignSystem) return []
|
|
5171
|
+
const themes = cachedDesignSystem.theme || cachedDesignSystem.THEME || cachedDesignSystem.themes || {}
|
|
5172
|
+
const items = []
|
|
5173
|
+
for (const k of Object.keys(themes)) {
|
|
5174
|
+
if (k.startsWith('__')) continue
|
|
5175
|
+
items.push({ label: k, hint: 'theme' })
|
|
5176
|
+
}
|
|
5177
|
+
return items
|
|
5178
|
+
}
|
|
5179
|
+
|
|
5180
|
+
function extractDSKeys (category) {
|
|
5181
|
+
if (!cachedDesignSystem) return []
|
|
5182
|
+
const obj = cachedDesignSystem[category]
|
|
5183
|
+
if (!obj || typeof obj !== 'object') return []
|
|
5184
|
+
return Object.keys(obj).filter(k => !k.startsWith('__'))
|
|
5185
|
+
}
|
|
5186
|
+
|
|
5187
|
+
function extractDSSpacingSuggestions () {
|
|
5188
|
+
if (!cachedDesignSystem) return []
|
|
5189
|
+
const sp = cachedDesignSystem.spacing || cachedDesignSystem.SPACING || {}
|
|
5190
|
+
if (sp.base && sp.ratio) {
|
|
5191
|
+
// Generate tokens from base/ratio
|
|
5192
|
+
return generateTokens(sp.base, sp.ratio, 'px', -4, 7)
|
|
5193
|
+
}
|
|
5194
|
+
// Otherwise extract raw keys
|
|
5195
|
+
const items = []
|
|
5196
|
+
for (const [k, v] of Object.entries(sp)) {
|
|
5197
|
+
if (k.startsWith('__')) continue
|
|
5198
|
+
items.push({ label: k, hint: typeof v === 'number' ? v + 'px' : String(v) })
|
|
5199
|
+
}
|
|
5200
|
+
return items
|
|
5201
|
+
}
|
|
5202
|
+
|
|
5203
|
+
function extractDSFontSuggestions () {
|
|
5204
|
+
if (!cachedDesignSystem) return []
|
|
5205
|
+
const typo = cachedDesignSystem.typography || cachedDesignSystem.TYPOGRAPHY || {}
|
|
5206
|
+
if (typo.base && typo.ratio) {
|
|
5207
|
+
return generateTokens(typo.base, typo.ratio, 'px', -3, 7)
|
|
5208
|
+
}
|
|
5209
|
+
const font = cachedDesignSystem.font || cachedDesignSystem.FONT || {}
|
|
5210
|
+
const items = []
|
|
5211
|
+
for (const [k, v] of Object.entries(font)) {
|
|
5212
|
+
if (k.startsWith('__')) continue
|
|
5213
|
+
items.push({ label: k, hint: String(v) })
|
|
5214
|
+
}
|
|
5215
|
+
return items
|
|
5216
|
+
}
|
|
5217
|
+
|
|
5218
|
+
function extractDSGradientSuggestions () {
|
|
5219
|
+
if (!cachedDesignSystem) return []
|
|
5220
|
+
const gr = cachedDesignSystem.gradient || cachedDesignSystem.GRADIENT || {}
|
|
5221
|
+
return Object.keys(gr).filter(k => !k.startsWith('__')).map(k => ({ label: k, hint: 'gradient' }))
|
|
5222
|
+
}
|
|
5223
|
+
|
|
4495
5224
|
function getSuggestionsForProp (propName) {
|
|
4496
5225
|
// CSS enum values (display, position, flexDirection, etc.)
|
|
4497
5226
|
if (CSS_ENUMS[propName]) return CSS_ENUMS[propName].map(v => ({ label: v }))
|
|
4498
|
-
|
|
5227
|
+
|
|
5228
|
+
// Theme tokens — prefer DS tokens, fall back to defaults
|
|
4499
5229
|
if (propName === 'theme') {
|
|
5230
|
+
const dsThemes = extractDSThemeSuggestions()
|
|
5231
|
+
if (dsThemes.length > 0) {
|
|
5232
|
+
// Add modifiers to DS themes
|
|
5233
|
+
for (const t of dsThemes.slice()) {
|
|
5234
|
+
for (const mod of THEME_MODIFIERS) {
|
|
5235
|
+
dsThemes.push({ label: t.label + ' ' + mod, hint: 'modifier' })
|
|
5236
|
+
}
|
|
5237
|
+
}
|
|
5238
|
+
return dsThemes
|
|
5239
|
+
}
|
|
4500
5240
|
const items = THEME_TOKENS.map(v => ({ label: v, hint: 'theme' }))
|
|
4501
5241
|
for (const t of ['primary', 'secondary', 'card', 'dialog', 'label']) {
|
|
4502
5242
|
for (const mod of THEME_MODIFIERS) {
|
|
@@ -4505,18 +5245,57 @@ Do NOT include any explanation, only valid JSON.`
|
|
|
4505
5245
|
}
|
|
4506
5246
|
return items
|
|
4507
5247
|
}
|
|
4508
|
-
|
|
5248
|
+
|
|
5249
|
+
// Color properties — prefer DS color tokens
|
|
4509
5250
|
if (COLOR_PROPS.has(propName)) {
|
|
5251
|
+
const dsColors = extractDSColorSuggestions()
|
|
5252
|
+
if (dsColors.length > 0) {
|
|
5253
|
+
// Merge DS colors with gradients
|
|
5254
|
+
const dsGradients = extractDSGradientSuggestions()
|
|
5255
|
+
return [...dsColors, ...dsGradients, ...COLOR_TOKENS]
|
|
5256
|
+
}
|
|
4510
5257
|
const items = COLOR_TOKENS.slice()
|
|
4511
5258
|
for (const g of GRADIENT_TOKENS) items.push({ label: g, hint: 'gradient' })
|
|
4512
5259
|
return items
|
|
4513
5260
|
}
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
5261
|
+
|
|
5262
|
+
// Spacing — prefer DS spacing tokens
|
|
5263
|
+
if (SPACING_PROPS.has(propName)) {
|
|
5264
|
+
const dsSpacing = extractDSSpacingSuggestions()
|
|
5265
|
+
return dsSpacing.length > 0 ? dsSpacing : SPACING_TOKENS
|
|
5266
|
+
}
|
|
5267
|
+
|
|
5268
|
+
// Font size — prefer DS typography tokens
|
|
5269
|
+
if (FONT_SIZE_PROPS.has(propName)) {
|
|
5270
|
+
const dsFonts = extractDSFontSuggestions()
|
|
5271
|
+
return dsFonts.length > 0 ? dsFonts : FONT_SIZE_TOKENS
|
|
5272
|
+
}
|
|
5273
|
+
|
|
4518
5274
|
// Timing tokens
|
|
4519
|
-
if (propName === 'transition' || propName === 'transitionDuration' || propName === 'animationDuration')
|
|
5275
|
+
if (propName === 'transition' || propName === 'transitionDuration' || propName === 'animationDuration') {
|
|
5276
|
+
const dsTiming = cachedDesignSystem && (cachedDesignSystem.timing || cachedDesignSystem.TIMING)
|
|
5277
|
+
if (dsTiming && typeof dsTiming === 'object') {
|
|
5278
|
+
const items = []
|
|
5279
|
+
for (const [k, v] of Object.entries(dsTiming)) {
|
|
5280
|
+
if (!k.startsWith('__')) items.push({ label: k, hint: String(v) })
|
|
5281
|
+
}
|
|
5282
|
+
if (items.length > 0) return items
|
|
5283
|
+
}
|
|
5284
|
+
return TIMING_TOKENS
|
|
5285
|
+
}
|
|
5286
|
+
|
|
5287
|
+
// Font family — from DS
|
|
5288
|
+
if (propName === 'fontFamily') {
|
|
5289
|
+
const ff = cachedDesignSystem && (cachedDesignSystem.font_family || cachedDesignSystem.FONT_FAMILY)
|
|
5290
|
+
if (ff && typeof ff === 'object') {
|
|
5291
|
+
const items = []
|
|
5292
|
+
for (const [k, v] of Object.entries(ff)) {
|
|
5293
|
+
if (!k.startsWith('__')) items.push({ label: k, hint: typeof v === 'string' ? v : '' })
|
|
5294
|
+
}
|
|
5295
|
+
if (items.length > 0) return items
|
|
5296
|
+
}
|
|
5297
|
+
}
|
|
5298
|
+
|
|
4520
5299
|
// HTML tags
|
|
4521
5300
|
if (propName === 'tag') return HTML_TAGS.map(v => ({ label: v }))
|
|
4522
5301
|
return null
|
|
@@ -4541,6 +5320,8 @@ Do NOT include any explanation, only valid JSON.`
|
|
|
4541
5320
|
p.classList.toggle('active', p.id === 'mode-' + mode)
|
|
4542
5321
|
})
|
|
4543
5322
|
if (mode === 'chat') updateChatContextLabel()
|
|
5323
|
+
if (mode === 'gallery') renderGallery()
|
|
5324
|
+
if (mode === 'content') renderContent()
|
|
4544
5325
|
}
|
|
4545
5326
|
|
|
4546
5327
|
// ============================================================
|
|
@@ -4738,10 +5519,64 @@ Do NOT include any explanation, only valid JSON.`
|
|
|
4738
5519
|
initElementsSync()
|
|
4739
5520
|
initAutoRefresh()
|
|
4740
5521
|
|
|
4741
|
-
// Mode tabs (Editor / Chat)
|
|
5522
|
+
// Mode tabs (Editor / Chat / Gallery / Content)
|
|
4742
5523
|
document.querySelectorAll('.mode-tab').forEach(tab => {
|
|
4743
5524
|
tab.addEventListener('click', () => switchMode(tab.dataset.mode))
|
|
4744
5525
|
})
|
|
5526
|
+
|
|
5527
|
+
// Gallery controls
|
|
5528
|
+
const gallerySearch = document.getElementById('gallery-search')
|
|
5529
|
+
if (gallerySearch) {
|
|
5530
|
+
gallerySearch.addEventListener('input', () => {
|
|
5531
|
+
const q = gallerySearch.value.toLowerCase()
|
|
5532
|
+
document.querySelectorAll('.gallery-card').forEach(card => {
|
|
5533
|
+
const name = card.querySelector('.gallery-card-name')?.textContent.toLowerCase() || ''
|
|
5534
|
+
card.style.display = name.includes(q) ? '' : 'none'
|
|
5535
|
+
})
|
|
5536
|
+
})
|
|
5537
|
+
}
|
|
5538
|
+
// Content controls
|
|
5539
|
+
const contentLang = document.getElementById('content-language')
|
|
5540
|
+
if (contentLang) {
|
|
5541
|
+
contentLang.addEventListener('change', () => {
|
|
5542
|
+
activeContentLang = contentLang.value
|
|
5543
|
+
renderContent()
|
|
5544
|
+
})
|
|
5545
|
+
}
|
|
5546
|
+
const addLangBtn = document.getElementById('content-add-lang')
|
|
5547
|
+
if (addLangBtn) {
|
|
5548
|
+
addLangBtn.addEventListener('click', () => {
|
|
5549
|
+
const code = window.prompt ? null : '' // prompt doesn't work in DevTools
|
|
5550
|
+
// Use inline input instead
|
|
5551
|
+
const toolbar = document.getElementById('content-toolbar')
|
|
5552
|
+
const inp = document.createElement('input')
|
|
5553
|
+
inp.className = 'content-lang-input'
|
|
5554
|
+
inp.placeholder = 'lang code (e.g. fr)'
|
|
5555
|
+
inp.style.width = '80px'
|
|
5556
|
+
toolbar.appendChild(inp)
|
|
5557
|
+
inp.focus()
|
|
5558
|
+
const add = () => {
|
|
5559
|
+
const code = inp.value.trim().toLowerCase()
|
|
5560
|
+
if (code && !contentLanguages.includes(code)) {
|
|
5561
|
+
contentLanguages.push(code)
|
|
5562
|
+
contentData[code] = {}
|
|
5563
|
+
const opt = document.createElement('option')
|
|
5564
|
+
opt.value = code
|
|
5565
|
+
opt.textContent = code.toUpperCase()
|
|
5566
|
+
contentLang.appendChild(opt)
|
|
5567
|
+
contentLang.value = code
|
|
5568
|
+
activeContentLang = code
|
|
5569
|
+
renderContent()
|
|
5570
|
+
}
|
|
5571
|
+
inp.remove()
|
|
5572
|
+
}
|
|
5573
|
+
inp.addEventListener('keydown', (e) => {
|
|
5574
|
+
if (e.key === 'Enter') add()
|
|
5575
|
+
if (e.key === 'Escape') inp.remove()
|
|
5576
|
+
})
|
|
5577
|
+
inp.addEventListener('blur', add)
|
|
5578
|
+
})
|
|
5579
|
+
}
|
|
4745
5580
|
document.getElementById('btn-app-disconnect').addEventListener('click', disconnect)
|
|
4746
5581
|
|
|
4747
5582
|
// Tree pane tabs (Active Nodes / State Tree / Design System)
|
|
@@ -4809,6 +5644,10 @@ Do NOT include any explanation, only valid JSON.`
|
|
|
4809
5644
|
document.getElementById('btn-refresh').addEventListener('click', loadTree)
|
|
4810
5645
|
document.getElementById('btn-inspect').addEventListener('click', inspectSelected)
|
|
4811
5646
|
document.getElementById('btn-sync').addEventListener('click', syncChanges)
|
|
5647
|
+
document.getElementById('btn-sync-dropdown').addEventListener('click', toggleSyncChangesPanel)
|
|
5648
|
+
document.getElementById('sync-changes-close').addEventListener('click', () => {
|
|
5649
|
+
document.getElementById('sync-changes-panel').style.display = 'none'
|
|
5650
|
+
})
|
|
4812
5651
|
document.getElementById('btn-undo').addEventListener('click', undo)
|
|
4813
5652
|
document.getElementById('btn-redo').addEventListener('click', redo)
|
|
4814
5653
|
|
|
@@ -4923,6 +5762,474 @@ Do NOT include any explanation, only valid JSON.`
|
|
|
4923
5762
|
if (connectionMode) loadTree()
|
|
4924
5763
|
}
|
|
4925
5764
|
|
|
5765
|
+
// ============================================================
|
|
5766
|
+
// Gallery mode — display all components
|
|
5767
|
+
// ============================================================
|
|
5768
|
+
async function renderGallery () {
|
|
5769
|
+
const container = document.getElementById('gallery-container')
|
|
5770
|
+
const pagesRow = document.getElementById('gallery-pages')
|
|
5771
|
+
if (!container) return
|
|
5772
|
+
container.innerHTML = '<div class="empty-message">Loading...</div>'
|
|
5773
|
+
if (pagesRow) pagesRow.innerHTML = ''
|
|
5774
|
+
|
|
5775
|
+
// Get components from element.context.components and pages from element.context.pages
|
|
5776
|
+
let result = { components: [], pages: [], debug: '' }
|
|
5777
|
+
try {
|
|
5778
|
+
const raw = await pageEval(`(function(){
|
|
5779
|
+
var I = window.__DOMQL_INSPECTOR__;
|
|
5780
|
+
if (!I) return JSON.stringify({components:[],pages:[],debug:'no inspector'});
|
|
5781
|
+
var el = I.findRoot();
|
|
5782
|
+
if (!el) return JSON.stringify({components:[],pages:[],debug:'no root'});
|
|
5783
|
+
|
|
5784
|
+
// Find context - may be on root or on a child element
|
|
5785
|
+
var ctx = el.context;
|
|
5786
|
+
if (!ctx) {
|
|
5787
|
+
// Walk children to find an element with context
|
|
5788
|
+
function findCtx(e, d) {
|
|
5789
|
+
if (d > 4 || !e) return null;
|
|
5790
|
+
if (e.context) return e.context;
|
|
5791
|
+
for (var k in e) {
|
|
5792
|
+
if (k === 'parent' || k === 'node' || k === 'state' || k.startsWith('__')) continue;
|
|
5793
|
+
if (e[k] && typeof e[k] === 'object' && e[k].node) {
|
|
5794
|
+
var found = findCtx(e[k], d + 1);
|
|
5795
|
+
if (found) return found;
|
|
5796
|
+
}
|
|
5797
|
+
}
|
|
5798
|
+
return null;
|
|
5799
|
+
}
|
|
5800
|
+
ctx = findCtx(el, 0);
|
|
5801
|
+
}
|
|
5802
|
+
if (!ctx) return JSON.stringify({components:[],pages:[],debug:'no context on root or children. root key=' + (el.key||'?') + ' has keys: ' + Object.keys(el).slice(0,15).join(',')});
|
|
5803
|
+
|
|
5804
|
+
var comps = [];
|
|
5805
|
+
var pages = [];
|
|
5806
|
+
|
|
5807
|
+
// Count component usage in the live tree
|
|
5808
|
+
var usage = {};
|
|
5809
|
+
function countUsage(e, d) {
|
|
5810
|
+
if (d > 20 || !e || !e.node) return;
|
|
5811
|
+
var cname = (e.__ref && e.__ref.__name) || e.component || '';
|
|
5812
|
+
if (cname) usage[cname] = (usage[cname] || 0) + 1;
|
|
5813
|
+
// Also count by key if it's CapitalCase
|
|
5814
|
+
if (e.key && /^[A-Z]/.test(e.key) && e.key !== cname) {
|
|
5815
|
+
usage[e.key] = (usage[e.key] || 0) + 1;
|
|
5816
|
+
}
|
|
5817
|
+
for (var ck in e) {
|
|
5818
|
+
if (ck === 'parent' || ck === 'node' || ck === 'context' || ck === 'state' || ck.startsWith('__')) continue;
|
|
5819
|
+
if (e[ck] && typeof e[ck] === 'object' && e[ck].node) countUsage(e[ck], d + 1);
|
|
5820
|
+
}
|
|
5821
|
+
}
|
|
5822
|
+
countUsage(el, 0);
|
|
5823
|
+
|
|
5824
|
+
// Extract context.components (CapitalCase keys)
|
|
5825
|
+
var src = ctx.components || {};
|
|
5826
|
+
for (var k in src) {
|
|
5827
|
+
if (k[0] === k[0].toUpperCase() && k[0] !== '_' && /^[A-Z]/.test(k)) {
|
|
5828
|
+
comps.push({ name: k, count: usage[k] || 0 });
|
|
5829
|
+
}
|
|
5830
|
+
}
|
|
5831
|
+
|
|
5832
|
+
// Extract context.pages
|
|
5833
|
+
var pgs = ctx.pages || {};
|
|
5834
|
+
for (var k in pgs) {
|
|
5835
|
+
if (k.startsWith('__')) continue;
|
|
5836
|
+
try {
|
|
5837
|
+
pages.push({ name: k, path: pgs[k] && pgs[k].path ? pgs[k].path : '/' + k });
|
|
5838
|
+
} catch(e) {}
|
|
5839
|
+
}
|
|
5840
|
+
|
|
5841
|
+
var debug = 'ctx keys: ' + Object.keys(ctx).slice(0,20).join(',') + '; components keys: ' + Object.keys(src).slice(0,10).join(',');
|
|
5842
|
+
return JSON.stringify({ components: comps, pages: pages, debug: debug });
|
|
5843
|
+
})()`)
|
|
5844
|
+
if (raw) result = JSON.parse(raw)
|
|
5845
|
+
} catch (e) { result.debug = 'parse error: ' + e.message }
|
|
5846
|
+
|
|
5847
|
+
// Render pages row
|
|
5848
|
+
if (pagesRow && result.pages.length > 0) {
|
|
5849
|
+
const label = document.createElement('span')
|
|
5850
|
+
label.className = 'gallery-section-label'
|
|
5851
|
+
label.textContent = 'Pages'
|
|
5852
|
+
pagesRow.appendChild(label)
|
|
5853
|
+
|
|
5854
|
+
for (const page of result.pages) {
|
|
5855
|
+
const chip = document.createElement('button')
|
|
5856
|
+
chip.className = 'gallery-page-chip'
|
|
5857
|
+
chip.textContent = page.name
|
|
5858
|
+
chip.title = page.path || ''
|
|
5859
|
+
chip.addEventListener('click', () => {
|
|
5860
|
+
// Navigate to page in the inspected tab
|
|
5861
|
+
const path = page.path.startsWith('/') ? page.path : '/' + page.path
|
|
5862
|
+
pageEval('window.location.pathname = ' + JSON.stringify(path)).catch(() => {})
|
|
5863
|
+
})
|
|
5864
|
+
pagesRow.appendChild(chip)
|
|
5865
|
+
}
|
|
5866
|
+
}
|
|
5867
|
+
|
|
5868
|
+
// Render components grid
|
|
5869
|
+
container.innerHTML = ''
|
|
5870
|
+
|
|
5871
|
+
if (result.components.length === 0) {
|
|
5872
|
+
container.innerHTML = '<div class="empty-message">No components found' + (result.debug ? '<br><small style="opacity:0.5">' + result.debug + '</small>' : '') + '</div>'
|
|
5873
|
+
return
|
|
5874
|
+
}
|
|
5875
|
+
|
|
5876
|
+
for (const comp of result.components) {
|
|
5877
|
+
const card = document.createElement('div')
|
|
5878
|
+
card.className = 'gallery-card'
|
|
5879
|
+
|
|
5880
|
+
const name = document.createElement('div')
|
|
5881
|
+
name.className = 'gallery-card-name'
|
|
5882
|
+
name.textContent = comp.name
|
|
5883
|
+
|
|
5884
|
+
if (comp.count > 0) {
|
|
5885
|
+
const count = document.createElement('span')
|
|
5886
|
+
count.className = 'gallery-card-count'
|
|
5887
|
+
count.textContent = comp.count
|
|
5888
|
+
count.title = 'Used ' + comp.count + ' time' + (comp.count === 1 ? '' : 's')
|
|
5889
|
+
name.appendChild(count)
|
|
5890
|
+
}
|
|
5891
|
+
|
|
5892
|
+
card.appendChild(name)
|
|
5893
|
+
|
|
5894
|
+
card.addEventListener('click', () => {
|
|
5895
|
+
// Try to find this component in the tree and select it
|
|
5896
|
+
switchMode('editor')
|
|
5897
|
+
if (treeData) {
|
|
5898
|
+
const path = findComponentPath(comp.name)
|
|
5899
|
+
if (path) {
|
|
5900
|
+
selectElement(path)
|
|
5901
|
+
highlightElement(path)
|
|
5902
|
+
expandAndSelectTreePath(path)
|
|
5903
|
+
}
|
|
5904
|
+
}
|
|
5905
|
+
})
|
|
5906
|
+
|
|
5907
|
+
container.appendChild(card)
|
|
5908
|
+
}
|
|
5909
|
+
}
|
|
5910
|
+
|
|
5911
|
+
// Find the tree path of a component by its name (walks tree looking for matching __ref.__name or key)
|
|
5912
|
+
function findComponentPath (name) {
|
|
5913
|
+
if (!treeData) return null
|
|
5914
|
+
function walk (node, path) {
|
|
5915
|
+
if (!node || !node.children) return null
|
|
5916
|
+
for (const child of node.children) {
|
|
5917
|
+
const childPath = path ? path + '.' + child.key : child.key
|
|
5918
|
+
if (child.key === name || child.component === name) return childPath
|
|
5919
|
+
const found = walk(child, childPath)
|
|
5920
|
+
if (found) return found
|
|
5921
|
+
}
|
|
5922
|
+
return null
|
|
5923
|
+
}
|
|
5924
|
+
return walk(treeData, '')
|
|
5925
|
+
}
|
|
5926
|
+
|
|
5927
|
+
// ============================================================
|
|
5928
|
+
// Content mode — CMS-like environment with languages
|
|
5929
|
+
// ============================================================
|
|
5930
|
+
let contentData = {} // { en: { key: value }, fr: { key: value } }
|
|
5931
|
+
let contentLanguages = ['en']
|
|
5932
|
+
let activeContentLang = 'en'
|
|
5933
|
+
|
|
5934
|
+
let contentSelectedKey = null // currently selected sidebar key
|
|
5935
|
+
|
|
5936
|
+
async function renderContent () {
|
|
5937
|
+
const sidebar = document.getElementById('content-sidebar')
|
|
5938
|
+
const main = document.getElementById('content-main')
|
|
5939
|
+
if (!sidebar || !main) return
|
|
5940
|
+
sidebar.innerHTML = '<div class="empty-message">Loading...</div>'
|
|
5941
|
+
main.innerHTML = ''
|
|
5942
|
+
|
|
5943
|
+
// Extract root state (same data source as the State Tree tab)
|
|
5944
|
+
let rootState = {}
|
|
5945
|
+
let contentDebug = ''
|
|
5946
|
+
try {
|
|
5947
|
+
const raw = await pageEval(`(function(){
|
|
5948
|
+
var I = window.__DOMQL_INSPECTOR__;
|
|
5949
|
+
if (!I) return JSON.stringify({data:{},debug:'no inspector'});
|
|
5950
|
+
var el = I.findRoot();
|
|
5951
|
+
if (!el) return JSON.stringify({data:{},debug:'no root'});
|
|
5952
|
+
|
|
5953
|
+
// Find state - may be on root or on a child element
|
|
5954
|
+
var st = el.state;
|
|
5955
|
+
if (!st || typeof st !== 'object' || !Object.keys(st).some(function(k){ return k !== 'parent' && k !== 'update' && !k.startsWith('__') && typeof st[k] !== 'function'; })) {
|
|
5956
|
+
// Walk children to find element with meaningful state
|
|
5957
|
+
function findState(e, d) {
|
|
5958
|
+
if (d > 4 || !e) return null;
|
|
5959
|
+
for (var k in e) {
|
|
5960
|
+
if (k === 'parent' || k === 'node' || k === 'context' || k.startsWith('__')) continue;
|
|
5961
|
+
if (e[k] && typeof e[k] === 'object' && e[k].node) {
|
|
5962
|
+
if (e[k].state && typeof e[k].state === 'object') {
|
|
5963
|
+
var hasData = Object.keys(e[k].state).some(function(sk){ return sk !== 'parent' && sk !== 'update' && !sk.startsWith('__') && typeof e[k].state[sk] !== 'function'; });
|
|
5964
|
+
if (hasData) return e[k].state;
|
|
5965
|
+
}
|
|
5966
|
+
var found = findState(e[k], d + 1);
|
|
5967
|
+
if (found) return found;
|
|
5968
|
+
}
|
|
5969
|
+
}
|
|
5970
|
+
return null;
|
|
5971
|
+
}
|
|
5972
|
+
var childState = findState(el, 0);
|
|
5973
|
+
if (childState) st = childState;
|
|
5974
|
+
}
|
|
5975
|
+
if (!st) return JSON.stringify({data:{},debug:'no state found on root or children'});
|
|
5976
|
+
var SKIP = {parent:1,root:1,update:1,set:1,reset:1,replace:1,toggle:1,
|
|
5977
|
+
remove:1,add:1,apply:1,setByPath:1,parse:1,clean:1,destroy:1,
|
|
5978
|
+
create:1,quietUpdate:1,__element:1,__depends:1,__ref:1};
|
|
5979
|
+
var result = {};
|
|
5980
|
+
var stKeys = [];
|
|
5981
|
+
var skipped = [];
|
|
5982
|
+
for (var k in st) {
|
|
5983
|
+
if (SKIP[k] || k.startsWith('__') || typeof st[k] === 'function') continue;
|
|
5984
|
+
stKeys.push(k);
|
|
5985
|
+
try {
|
|
5986
|
+
var v = st[k];
|
|
5987
|
+
var t = typeof v;
|
|
5988
|
+
if (v === null || t === 'string' || t === 'number' || t === 'boolean') {
|
|
5989
|
+
result[k] = { type: 'primitive', value: v };
|
|
5990
|
+
} else if (Array.isArray(v)) {
|
|
5991
|
+
result[k] = { type: 'array', length: v.length, value: JSON.parse(JSON.stringify(v.slice(0, 50))) };
|
|
5992
|
+
} else if (t === 'object') {
|
|
5993
|
+
if (v.node) { skipped.push(k + '(node)'); continue; }
|
|
5994
|
+
if (v.parent === st) { skipped.push(k + '(childState)'); continue; }
|
|
5995
|
+
var keys = Object.keys(v).filter(function(kk){ return !kk.startsWith('__') && typeof v[kk] !== 'function'; });
|
|
5996
|
+
var obj = {};
|
|
5997
|
+
keys.slice(0, 100).forEach(function(kk){ try { obj[kk] = JSON.parse(JSON.stringify(v[kk])); } catch(e){} });
|
|
5998
|
+
result[k] = { type: 'object', keys: keys.length, value: obj };
|
|
5999
|
+
}
|
|
6000
|
+
} catch(e) { skipped.push(k + '(err:' + e.message + ')'); }
|
|
6001
|
+
}
|
|
6002
|
+
return JSON.stringify({data:result, debug:'keys: ' + stKeys.join(',') + '; skipped: ' + skipped.join(',') + '; resultKeys: ' + Object.keys(result).join(',')});
|
|
6003
|
+
})()`)
|
|
6004
|
+
if (raw) {
|
|
6005
|
+
const parsed = JSON.parse(raw)
|
|
6006
|
+
rootState = parsed.data || {}
|
|
6007
|
+
contentDebug = parsed.debug || ''
|
|
6008
|
+
}
|
|
6009
|
+
} catch (e) { contentDebug = 'parse error: ' + e.message }
|
|
6010
|
+
|
|
6011
|
+
sidebar.innerHTML = ''
|
|
6012
|
+
|
|
6013
|
+
// Sidebar: show keys that have objects or arrays as values
|
|
6014
|
+
const sidebarKeys = []
|
|
6015
|
+
const primitiveKeys = []
|
|
6016
|
+
for (const [key, info] of Object.entries(rootState)) {
|
|
6017
|
+
if (info.type === 'object' || info.type === 'array') {
|
|
6018
|
+
sidebarKeys.push(key)
|
|
6019
|
+
} else {
|
|
6020
|
+
primitiveKeys.push(key)
|
|
6021
|
+
}
|
|
6022
|
+
}
|
|
6023
|
+
|
|
6024
|
+
if (sidebarKeys.length === 0 && primitiveKeys.length === 0) {
|
|
6025
|
+
sidebar.innerHTML = '<div class="empty-message">No root state data' + (contentDebug ? '<br><small style="opacity:0.5">' + contentDebug + '</small>' : '') + '</div>'
|
|
6026
|
+
main.innerHTML = ''
|
|
6027
|
+
return
|
|
6028
|
+
}
|
|
6029
|
+
|
|
6030
|
+
// Temporary debug
|
|
6031
|
+
if (contentDebug) {
|
|
6032
|
+
const dbg = document.createElement('div')
|
|
6033
|
+
dbg.style.cssText = 'font-size:9px;color:var(--text-dim);padding:4px 8px;opacity:0.5'
|
|
6034
|
+
dbg.textContent = contentDebug
|
|
6035
|
+
sidebar.appendChild(dbg)
|
|
6036
|
+
}
|
|
6037
|
+
|
|
6038
|
+
// Render sidebar items
|
|
6039
|
+
for (const key of sidebarKeys) {
|
|
6040
|
+
const info = rootState[key]
|
|
6041
|
+
const item = document.createElement('div')
|
|
6042
|
+
item.className = 'content-sidebar-item'
|
|
6043
|
+
if (contentSelectedKey === key) item.classList.add('active')
|
|
6044
|
+
|
|
6045
|
+
const label = document.createElement('span')
|
|
6046
|
+
label.className = 'content-sidebar-label'
|
|
6047
|
+
label.textContent = key
|
|
6048
|
+
|
|
6049
|
+
const badge = document.createElement('span')
|
|
6050
|
+
badge.className = 'content-sidebar-badge'
|
|
6051
|
+
badge.textContent = info.type === 'array' ? '[' + info.length + ']' : '{' + info.keys + '}'
|
|
6052
|
+
|
|
6053
|
+
item.appendChild(label)
|
|
6054
|
+
item.appendChild(badge)
|
|
6055
|
+
item.addEventListener('click', () => {
|
|
6056
|
+
contentSelectedKey = key
|
|
6057
|
+
renderContentMain(main, key, rootState[key])
|
|
6058
|
+
// Update active
|
|
6059
|
+
sidebar.querySelectorAll('.content-sidebar-item').forEach(i => i.classList.remove('active'))
|
|
6060
|
+
item.classList.add('active')
|
|
6061
|
+
})
|
|
6062
|
+
sidebar.appendChild(item)
|
|
6063
|
+
}
|
|
6064
|
+
|
|
6065
|
+
// Auto-select first sidebar item, or show primitives
|
|
6066
|
+
if (contentSelectedKey && rootState[contentSelectedKey]) {
|
|
6067
|
+
renderContentMain(main, contentSelectedKey, rootState[contentSelectedKey])
|
|
6068
|
+
} else if (sidebarKeys.length > 0) {
|
|
6069
|
+
contentSelectedKey = sidebarKeys[0]
|
|
6070
|
+
sidebar.querySelector('.content-sidebar-item')?.classList.add('active')
|
|
6071
|
+
renderContentMain(main, sidebarKeys[0], rootState[sidebarKeys[0]])
|
|
6072
|
+
} else {
|
|
6073
|
+
renderContentPrimitives(main, primitiveKeys, rootState)
|
|
6074
|
+
}
|
|
6075
|
+
|
|
6076
|
+
// Show primitives section at bottom of main if there are any
|
|
6077
|
+
if (primitiveKeys.length > 0 && sidebarKeys.length > 0) {
|
|
6078
|
+
const primSection = document.createElement('div')
|
|
6079
|
+
primSection.className = 'content-primitives-section'
|
|
6080
|
+
const primHeader = document.createElement('div')
|
|
6081
|
+
primHeader.className = 'section-header'
|
|
6082
|
+
primHeader.textContent = 'Root Values'
|
|
6083
|
+
primSection.appendChild(primHeader)
|
|
6084
|
+
renderContentPrimitives(primSection, primitiveKeys, rootState)
|
|
6085
|
+
main.appendChild(primSection)
|
|
6086
|
+
}
|
|
6087
|
+
}
|
|
6088
|
+
|
|
6089
|
+
function renderContentMain (container, key, info) {
|
|
6090
|
+
// Clear previous content (keep primitives section if any)
|
|
6091
|
+
const primSection = container.querySelector('.content-primitives-section')
|
|
6092
|
+
container.innerHTML = ''
|
|
6093
|
+
|
|
6094
|
+
const header = document.createElement('div')
|
|
6095
|
+
header.className = 'content-main-header'
|
|
6096
|
+
header.textContent = key + (info.type === 'array' ? ' [' + info.length + ' items]' : '')
|
|
6097
|
+
container.appendChild(header)
|
|
6098
|
+
|
|
6099
|
+
const data = info.value
|
|
6100
|
+
if (info.type === 'array') {
|
|
6101
|
+
for (let i = 0; i < data.length; i++) {
|
|
6102
|
+
const item = data[i]
|
|
6103
|
+
if (item && typeof item === 'object') {
|
|
6104
|
+
renderContentObject(container, key, i, item)
|
|
6105
|
+
} else {
|
|
6106
|
+
renderContentField(container, key + '[' + i + ']', String(i), item, key, i)
|
|
6107
|
+
}
|
|
6108
|
+
}
|
|
6109
|
+
} else if (info.type === 'object') {
|
|
6110
|
+
for (const [subKey, val] of Object.entries(data)) {
|
|
6111
|
+
if (val && typeof val === 'object' && !Array.isArray(val)) {
|
|
6112
|
+
renderContentObject(container, key, subKey, val)
|
|
6113
|
+
} else {
|
|
6114
|
+
renderContentField(container, key + '.' + subKey, subKey, val, key, subKey)
|
|
6115
|
+
}
|
|
6116
|
+
}
|
|
6117
|
+
}
|
|
6118
|
+
|
|
6119
|
+
if (primSection) container.appendChild(primSection)
|
|
6120
|
+
}
|
|
6121
|
+
|
|
6122
|
+
function renderContentObject (container, stateKey, index, obj) {
|
|
6123
|
+
const card = document.createElement('div')
|
|
6124
|
+
card.className = 'content-object-card'
|
|
6125
|
+
|
|
6126
|
+
const cardHeader = document.createElement('div')
|
|
6127
|
+
cardHeader.className = 'content-object-header'
|
|
6128
|
+
cardHeader.textContent = typeof index === 'number' ? '#' + index : index
|
|
6129
|
+
card.appendChild(cardHeader)
|
|
6130
|
+
|
|
6131
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
6132
|
+
if (k.startsWith('__') || typeof v === 'function') continue
|
|
6133
|
+
const row = document.createElement('div')
|
|
6134
|
+
row.className = 'content-field'
|
|
6135
|
+
|
|
6136
|
+
const label = document.createElement('label')
|
|
6137
|
+
label.className = 'content-field-label'
|
|
6138
|
+
label.textContent = k
|
|
6139
|
+
|
|
6140
|
+
const input = document.createElement(typeof v === 'string' && v.length > 60 ? 'textarea' : 'input')
|
|
6141
|
+
input.className = 'content-input'
|
|
6142
|
+
input.value = v === null ? '' : typeof v === 'object' ? JSON.stringify(v) : String(v)
|
|
6143
|
+
if (input.tagName === 'TEXTAREA') { input.rows = 2 }
|
|
6144
|
+
|
|
6145
|
+
input.addEventListener('change', async () => {
|
|
6146
|
+
let newVal = input.value
|
|
6147
|
+
if (newVal === 'true') newVal = true
|
|
6148
|
+
else if (newVal === 'false') newVal = false
|
|
6149
|
+
else if (newVal === 'null') newVal = null
|
|
6150
|
+
else if (!isNaN(Number(newVal)) && newVal !== '') newVal = Number(newVal)
|
|
6151
|
+
try {
|
|
6152
|
+
await pageEval('(function(){ var root = window.__DOMQL_INSPECTOR__.findRoot(); if(!root || !root.state) return; var st = root.state; ' +
|
|
6153
|
+
'var target = st[' + JSON.stringify(stateKey) + ']; if(!target) return; ' +
|
|
6154
|
+
(typeof index === 'number'
|
|
6155
|
+
? 'target = target[' + index + ']; '
|
|
6156
|
+
: 'target = target[' + JSON.stringify(index) + ']; ') +
|
|
6157
|
+
'if(target) target[' + JSON.stringify(k) + '] = ' + JSON.stringify(newVal) + '; if(st.update) st.update(); })()')
|
|
6158
|
+
setStatus('Updated ' + k)
|
|
6159
|
+
} catch (e) { setStatus('Error: ' + (e.message || e)) }
|
|
6160
|
+
})
|
|
6161
|
+
|
|
6162
|
+
row.appendChild(label)
|
|
6163
|
+
row.appendChild(input)
|
|
6164
|
+
card.appendChild(row)
|
|
6165
|
+
}
|
|
6166
|
+
|
|
6167
|
+
container.appendChild(card)
|
|
6168
|
+
}
|
|
6169
|
+
|
|
6170
|
+
function renderContentField (container, fullPath, label, value, stateKey, subKey) {
|
|
6171
|
+
const row = document.createElement('div')
|
|
6172
|
+
row.className = 'content-field'
|
|
6173
|
+
|
|
6174
|
+
const labelEl = document.createElement('label')
|
|
6175
|
+
labelEl.className = 'content-field-label'
|
|
6176
|
+
labelEl.textContent = label
|
|
6177
|
+
|
|
6178
|
+
const input = document.createElement('input')
|
|
6179
|
+
input.className = 'content-input'
|
|
6180
|
+
input.value = value === null ? '' : typeof value === 'object' ? JSON.stringify(value) : String(value)
|
|
6181
|
+
|
|
6182
|
+
input.addEventListener('change', async () => {
|
|
6183
|
+
let newVal = input.value
|
|
6184
|
+
if (newVal === 'true') newVal = true
|
|
6185
|
+
else if (newVal === 'false') newVal = false
|
|
6186
|
+
else if (newVal === 'null') newVal = null
|
|
6187
|
+
else if (!isNaN(Number(newVal)) && newVal !== '') newVal = Number(newVal)
|
|
6188
|
+
try {
|
|
6189
|
+
await pageEval('(function(){ var root = window.__DOMQL_INSPECTOR__.findRoot(); if(root && root.state) { root.state.' + stateKey +
|
|
6190
|
+
(typeof subKey === 'number' ? '[' + subKey + ']' : '["' + subKey + '"]') +
|
|
6191
|
+
' = ' + JSON.stringify(newVal) + '; if(root.state.update) root.state.update(); } })()')
|
|
6192
|
+
setStatus('Updated ' + label)
|
|
6193
|
+
} catch (e) { setStatus('Error: ' + (e.message || e)) }
|
|
6194
|
+
})
|
|
6195
|
+
|
|
6196
|
+
row.appendChild(labelEl)
|
|
6197
|
+
row.appendChild(input)
|
|
6198
|
+
container.appendChild(row)
|
|
6199
|
+
}
|
|
6200
|
+
|
|
6201
|
+
function renderContentPrimitives (container, keys, rootState) {
|
|
6202
|
+
for (const key of keys) {
|
|
6203
|
+
const info = rootState[key]
|
|
6204
|
+
const row = document.createElement('div')
|
|
6205
|
+
row.className = 'content-field'
|
|
6206
|
+
|
|
6207
|
+
const label = document.createElement('label')
|
|
6208
|
+
label.className = 'content-field-label'
|
|
6209
|
+
label.textContent = key
|
|
6210
|
+
|
|
6211
|
+
const input = document.createElement('input')
|
|
6212
|
+
input.className = 'content-input'
|
|
6213
|
+
input.value = info.value === null ? '' : String(info.value)
|
|
6214
|
+
|
|
6215
|
+
input.addEventListener('change', async () => {
|
|
6216
|
+
let newVal = input.value
|
|
6217
|
+
if (newVal === 'true') newVal = true
|
|
6218
|
+
else if (newVal === 'false') newVal = false
|
|
6219
|
+
else if (newVal === 'null') newVal = null
|
|
6220
|
+
else if (!isNaN(Number(newVal)) && newVal !== '') newVal = Number(newVal)
|
|
6221
|
+
try {
|
|
6222
|
+
await pageEval('(function(){ var root = window.__DOMQL_INSPECTOR__.findRoot(); if(root && root.state) { root.state["' + key + '"] = ' + JSON.stringify(newVal) + '; if(root.state.update) root.state.update(); } })()')
|
|
6223
|
+
setStatus('Updated ' + key)
|
|
6224
|
+
} catch (e) { setStatus('Error: ' + (e.message || e)) }
|
|
6225
|
+
})
|
|
6226
|
+
|
|
6227
|
+
row.appendChild(label)
|
|
6228
|
+
row.appendChild(input)
|
|
6229
|
+
container.appendChild(row)
|
|
6230
|
+
}
|
|
6231
|
+
}
|
|
6232
|
+
|
|
4926
6233
|
// Auto-refresh: listen for page navigation and poll for DOM changes
|
|
4927
6234
|
function initAutoRefresh () {
|
|
4928
6235
|
// Refresh tree when user navigates to a new page
|
|
@@ -4942,6 +6249,8 @@ Do NOT include any explanation, only valid JSON.`
|
|
|
4942
6249
|
let lastTreeHash = ''
|
|
4943
6250
|
setInterval(async () => {
|
|
4944
6251
|
if (!connectionMode) return
|
|
6252
|
+
// Skip polling while an editor is active to avoid re-rendering mid-edit
|
|
6253
|
+
if (document.querySelector('.prop-value.editing') || document.querySelector('.obj-editor') || document.querySelector('.prop-add-inline')) return
|
|
4945
6254
|
try {
|
|
4946
6255
|
const ready = await pageEval('typeof window.__DOMQL_INSPECTOR__ !== "undefined"')
|
|
4947
6256
|
if (!ready) return
|