one 1.10.0 → 1.10.1
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/devtools/devtools.mjs +14 -1
- package/devtools/source-inspector.mjs +669 -10
- package/dist/cjs/router/interceptRoutes.cjs +15 -1
- package/dist/cjs/router/interceptRoutes.js +14 -1
- package/dist/cjs/router/interceptRoutes.js.map +1 -1
- package/dist/cjs/router/interceptRoutes.native.js +15 -1
- package/dist/cjs/router/interceptRoutes.native.js.map +1 -1
- package/dist/cjs/vite/plugins/devtoolsPlugin.cjs +18 -5
- package/dist/cjs/vite/plugins/devtoolsPlugin.js +13 -4
- package/dist/cjs/vite/plugins/devtoolsPlugin.js.map +2 -2
- package/dist/cjs/vite/plugins/devtoolsPlugin.native.js +20 -6
- package/dist/cjs/vite/plugins/devtoolsPlugin.native.js.map +1 -1
- package/dist/esm/router/interceptRoutes.js +14 -1
- package/dist/esm/router/interceptRoutes.js.map +1 -1
- package/dist/esm/router/interceptRoutes.mjs +15 -1
- package/dist/esm/router/interceptRoutes.mjs.map +1 -1
- package/dist/esm/router/interceptRoutes.native.js +15 -1
- package/dist/esm/router/interceptRoutes.native.js.map +1 -1
- package/dist/esm/vite/plugins/devtoolsPlugin.js +3 -1
- package/dist/esm/vite/plugins/devtoolsPlugin.js.map +1 -1
- package/dist/esm/vite/plugins/devtoolsPlugin.mjs +3 -1
- package/dist/esm/vite/plugins/devtoolsPlugin.mjs.map +1 -1
- package/dist/esm/vite/plugins/devtoolsPlugin.native.js +5 -2
- package/dist/esm/vite/plugins/devtoolsPlugin.native.js.map +1 -1
- package/package.json +9 -9
- package/src/router/interceptRoutes.ts +34 -5
- package/src/vite/plugins/devtoolsPlugin.ts +3 -1
- package/types/router/interceptRoutes.d.ts.map +1 -1
- package/types/vite/plugins/devtoolsPlugin.d.ts.map +1 -1
|
@@ -11,11 +11,18 @@
|
|
|
11
11
|
let shadow = null
|
|
12
12
|
let overlay = null
|
|
13
13
|
let tag = null
|
|
14
|
+
let pickerDialog = null
|
|
14
15
|
let holdTimer = null
|
|
15
16
|
const holdDelay = 800
|
|
16
17
|
let otherKeyPressed = false
|
|
17
18
|
const mousePos = { x: 0, y: 0 }
|
|
18
19
|
let removalObserver = null
|
|
20
|
+
let currentElementChain = []
|
|
21
|
+
let recording = false
|
|
22
|
+
let recordEvents = []
|
|
23
|
+
let recordStartTime = 0
|
|
24
|
+
let recordFrameCount = 0
|
|
25
|
+
const recordThrottle = 3
|
|
19
26
|
|
|
20
27
|
// set up HMR listener for cursor position from vscode
|
|
21
28
|
const cursorHot = createHotContext('/__one_cursor_hmr')
|
|
@@ -62,16 +69,650 @@
|
|
|
62
69
|
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
|
63
70
|
display: none;
|
|
64
71
|
}
|
|
72
|
+
dialog { border: none; padding: 0; background: transparent; max-width: none; max-height: none; overflow: visible; }
|
|
73
|
+
dialog::backdrop { background: rgba(0,0,0,0.01); }
|
|
74
|
+
.picker-dialog { position: fixed; margin: 0; z-index: 2147483647; }
|
|
75
|
+
.picker { width: 320px; max-width: calc(100vw - 20px); background: #161616; border-radius: 8px; box-shadow: 0 8px 32px rgba(0,0,0,0.5); display: flex; flex-direction: column; overflow: hidden; font: 12px system-ui, sans-serif; color: #ccc; }
|
|
76
|
+
.picker-header { display: flex; align-items: center; justify-content: flex-end; padding: 6px 10px; background: #1a1a1a; gap: 8px; border-bottom: 1px solid #252525; }
|
|
77
|
+
.picker-close { background: none; border: none; color: #666; cursor: pointer; padding: 2px 4px; font-size: 14px; line-height: 1; }
|
|
78
|
+
.picker-close:hover { color: #fff; }
|
|
79
|
+
.picker-shortcut { font-size: 11px; color: #888; padding: 4px 8px; cursor: pointer; border-radius: 4px; margin-left: auto; }
|
|
80
|
+
.picker-shortcut:hover { color: #fff; background: #252525; }
|
|
81
|
+
.picker-actions { display: flex; background: #1a1a1a; border-top: 1px solid #252525; border-bottom: 1px solid #252525; }
|
|
82
|
+
.picker-btn { flex: 1; background: none; border: none; border-right: 1px solid #252525; color: #888; padding: 8px; font: 12px system-ui, sans-serif; cursor: pointer; transition: color 0.1s; }
|
|
83
|
+
.picker-btn:last-child { border-right: none; }
|
|
84
|
+
.picker-btn:hover { color: #fff; }
|
|
85
|
+
.picker-btn.copied { color: #4ade80; }
|
|
86
|
+
.picker-btn { position: relative; }
|
|
87
|
+
.rec-tooltip { display: none; position: absolute; bottom: 100%; left: 50%; transform: translateX(-50%); background: #222; border-radius: 6px; box-shadow: 0 4px 16px rgba(0,0,0,0.4); padding: 6px 10px; margin-bottom: 4px; font: 11px system-ui, sans-serif; color: #888; white-space: nowrap; pointer-events: none; }
|
|
88
|
+
.picker-btn:hover .rec-tooltip { display: block; }
|
|
89
|
+
.picker-btn .rec-dot { display: inline-block; width: 8px; height: 8px; background: #f55; border-radius: 50%; }
|
|
90
|
+
.toast { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(20,20,20,0.95); border-radius: 12px; padding: 24px 32px; text-align: center; z-index: 2147483647; box-shadow: 0 8px 32px rgba(0,0,0,0.5); pointer-events: none; font-family: system-ui, sans-serif; }
|
|
91
|
+
.toast-big { font-size: 48px; font-weight: 600; color: #f55; }
|
|
92
|
+
.toast-big.success { color: #4ade80; }
|
|
93
|
+
.toast-hint { font-size: 12px; color: #888; margin-top: 12px; }
|
|
94
|
+
.picker-list { max-height: 240px; overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin; scrollbar-color: #333 transparent; }
|
|
95
|
+
.picker-list::-webkit-scrollbar { width: 4px; }
|
|
96
|
+
.picker-list::-webkit-scrollbar-track { background: transparent; }
|
|
97
|
+
.picker-list::-webkit-scrollbar-thumb { background: #333; border-radius: 2px; }
|
|
98
|
+
.picker-item { display: flex; align-items: center; padding: 8px 10px; color: #999; cursor: pointer; transition: background 0.08s; border-bottom: 1px solid #1e1e1e; gap: 8px; }
|
|
99
|
+
.picker-item:last-child { border-bottom: none; }
|
|
100
|
+
.picker-item:hover { background: #252525; color: #fff; }
|
|
101
|
+
.picker-item-name { font-weight: 500; color: #ddd; min-width: 60px; }
|
|
102
|
+
.picker-item-file { flex: 1; font-size: 10px; color: #555; font-family: monospace; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; direction: rtl; text-align: left; }
|
|
65
103
|
</style>
|
|
66
104
|
<div class="overlay"></div>
|
|
67
105
|
<div class="tag"></div>
|
|
106
|
+
<dialog class="picker-dialog" id="picker-dialog">
|
|
107
|
+
<div class="picker" id="picker">
|
|
108
|
+
<div class="picker-header">
|
|
109
|
+
<span class="picker-shortcut">\u2325space</span>
|
|
110
|
+
<button class="picker-close" id="picker-close">\u00d7</button>
|
|
111
|
+
</div>
|
|
112
|
+
<div class="picker-actions" id="picker-actions">
|
|
113
|
+
<button class="picker-btn" data-action="open">Open</button>
|
|
114
|
+
<button class="picker-btn" data-action="copy">Copy</button>
|
|
115
|
+
<button class="picker-btn" data-action="record" id="record-btn"><span class="rec-dot"></span><span class="rec-tooltip">Tap Option to stop</span></button>
|
|
116
|
+
</div>
|
|
117
|
+
<div class="picker-list" id="picker-list"></div>
|
|
118
|
+
</div>
|
|
119
|
+
</dialog>
|
|
68
120
|
`
|
|
69
121
|
document.body.appendChild(host)
|
|
70
122
|
overlay = shadow.querySelector('.overlay')
|
|
71
123
|
tag = shadow.querySelector('.tag')
|
|
124
|
+
pickerDialog = shadow.getElementById('picker-dialog')
|
|
125
|
+
setupPicker()
|
|
72
126
|
setupRemovalObserver()
|
|
73
127
|
}
|
|
74
128
|
|
|
129
|
+
function setupPicker() {
|
|
130
|
+
const closeBtn = shadow.getElementById('picker-close')
|
|
131
|
+
const shortcut = shadow.querySelector('.picker-shortcut')
|
|
132
|
+
const actions = shadow.getElementById('picker-actions')
|
|
133
|
+
const list = shadow.getElementById('picker-list')
|
|
134
|
+
|
|
135
|
+
closeBtn.addEventListener('click', () => {
|
|
136
|
+
pickerDialog.close()
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
// click shortcut to open devtools menu
|
|
140
|
+
shortcut.addEventListener('click', () => {
|
|
141
|
+
pickerDialog.close()
|
|
142
|
+
window.dispatchEvent(new CustomEvent('one-open-devtools'))
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
// click backdrop to close
|
|
146
|
+
pickerDialog.addEventListener('click', (e) => {
|
|
147
|
+
if (e.target === pickerDialog) pickerDialog.close()
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
actions.addEventListener('click', (e) => {
|
|
151
|
+
const btn = e.target.closest('.picker-btn')
|
|
152
|
+
if (!btn) return
|
|
153
|
+
const action = btn.dataset.action
|
|
154
|
+
|
|
155
|
+
if (action === 'open') {
|
|
156
|
+
if (!currentElementChain.length) return
|
|
157
|
+
fetch(
|
|
158
|
+
'/__one/open-source?source=' +
|
|
159
|
+
encodeURIComponent(currentElementChain[0].source)
|
|
160
|
+
)
|
|
161
|
+
pickerDialog.close()
|
|
162
|
+
} else if (action === 'copy') {
|
|
163
|
+
if (!currentElementChain.length) return
|
|
164
|
+
navigator.clipboard.writeText(buildSnapshot())
|
|
165
|
+
flashCopied(btn)
|
|
166
|
+
} else if (action === 'record') {
|
|
167
|
+
if (recording) {
|
|
168
|
+
stopRecording()
|
|
169
|
+
} else {
|
|
170
|
+
startRecording()
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
list.addEventListener('click', (e) => {
|
|
176
|
+
const item = e.target.closest('.picker-item')
|
|
177
|
+
if (!item) return
|
|
178
|
+
const source = item.dataset.source
|
|
179
|
+
if (source) {
|
|
180
|
+
fetch('/__one/open-source?source=' + encodeURIComponent(source))
|
|
181
|
+
pickerDialog.close()
|
|
182
|
+
}
|
|
183
|
+
})
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function flashCopied(btn) {
|
|
187
|
+
btn.classList.add('copied')
|
|
188
|
+
const orig = btn.textContent
|
|
189
|
+
btn.textContent = 'Copied!'
|
|
190
|
+
setTimeout(() => {
|
|
191
|
+
btn.classList.remove('copied')
|
|
192
|
+
btn.textContent = orig
|
|
193
|
+
}, 1000)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function generateSelector(el) {
|
|
197
|
+
const parts = []
|
|
198
|
+
let current = el
|
|
199
|
+
while (current && current !== document.body) {
|
|
200
|
+
if (current.id) {
|
|
201
|
+
parts.unshift('#' + current.id)
|
|
202
|
+
break
|
|
203
|
+
}
|
|
204
|
+
let selector = current.tagName.toLowerCase()
|
|
205
|
+
if (current.className && typeof current.className === 'string') {
|
|
206
|
+
const classes = current.className.trim().split(/\s+/).slice(0, 2)
|
|
207
|
+
if (classes.length && classes[0]) {
|
|
208
|
+
selector += '.' + classes.join('.')
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
// add nth-child if needed for specificity
|
|
212
|
+
const parent = current.parentElement
|
|
213
|
+
if (parent) {
|
|
214
|
+
const siblings = Array.from(parent.children).filter(
|
|
215
|
+
(c) => c.tagName === current.tagName
|
|
216
|
+
)
|
|
217
|
+
if (siblings.length > 1) {
|
|
218
|
+
const idx = siblings.indexOf(current) + 1
|
|
219
|
+
selector += ':nth-child(' + idx + ')'
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
parts.unshift(selector)
|
|
223
|
+
current = current.parentElement
|
|
224
|
+
}
|
|
225
|
+
return parts.join(' > ')
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// recording functionality
|
|
229
|
+
let recordHandlers = {}
|
|
230
|
+
|
|
231
|
+
function getRecordSelector(el) {
|
|
232
|
+
if (!el || el === document.body || el === document.documentElement) return null
|
|
233
|
+
let current = el
|
|
234
|
+
let path = []
|
|
235
|
+
let anchor = null
|
|
236
|
+
|
|
237
|
+
while (current && current !== document.body) {
|
|
238
|
+
const id = current.id
|
|
239
|
+
const testId = current.getAttribute('data-testid')
|
|
240
|
+
const oneSource = current.getAttribute('data-one-source')
|
|
241
|
+
|
|
242
|
+
if (id || testId || oneSource) {
|
|
243
|
+
anchor = { id, testId, oneSource }
|
|
244
|
+
break
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
let seg = current.tagName.toLowerCase()
|
|
248
|
+
if (current.className && typeof current.className === 'string') {
|
|
249
|
+
const cls = current.className
|
|
250
|
+
.split(/\s+/)
|
|
251
|
+
.filter((c) => c && !c.startsWith('_'))[0]
|
|
252
|
+
if (cls) seg += '.' + cls
|
|
253
|
+
}
|
|
254
|
+
path.unshift(seg)
|
|
255
|
+
current = current.parentElement
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const pathStr = path.slice(-3).join(' > ')
|
|
259
|
+
|
|
260
|
+
if (anchor) {
|
|
261
|
+
const ref = anchor.id
|
|
262
|
+
? `#${anchor.id}`
|
|
263
|
+
: anchor.testId
|
|
264
|
+
? `[data-testid="${anchor.testId}"]`
|
|
265
|
+
: `[data-one-source="${anchor.oneSource}"]`
|
|
266
|
+
return pathStr ? `${ref} > ${pathStr}` : ref
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return pathStr || el.tagName.toLowerCase()
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function getRecordElementInfo(el) {
|
|
273
|
+
if (!el) return null
|
|
274
|
+
const oneSource = el.getAttribute?.('data-one-source')
|
|
275
|
+
return {
|
|
276
|
+
selector: getRecordSelector(el),
|
|
277
|
+
tag: el.tagName?.toLowerCase(),
|
|
278
|
+
id: el.id || undefined,
|
|
279
|
+
testId: el.getAttribute?.('data-testid') || undefined,
|
|
280
|
+
source: oneSource || undefined,
|
|
281
|
+
text: el.textContent?.slice(0, 50).trim() || undefined,
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function recordTs() {
|
|
286
|
+
return Date.now() - recordStartTime
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function getMods(e) {
|
|
290
|
+
const m = []
|
|
291
|
+
if (e.ctrlKey) m.push('ctrl')
|
|
292
|
+
if (e.altKey) m.push('alt')
|
|
293
|
+
if (e.shiftKey) m.push('shift')
|
|
294
|
+
if (e.metaKey) m.push('cmd')
|
|
295
|
+
return m.length ? m : undefined
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function showToast(big, hint, duration, isSuccess) {
|
|
299
|
+
if (!host) createHost()
|
|
300
|
+
const toast = document.createElement('div')
|
|
301
|
+
toast.className = 'toast'
|
|
302
|
+
toast.innerHTML =
|
|
303
|
+
'<div class="toast-big' +
|
|
304
|
+
(isSuccess ? ' success' : '') +
|
|
305
|
+
'">' +
|
|
306
|
+
big +
|
|
307
|
+
'</div>' +
|
|
308
|
+
(hint ? '<div class="toast-hint">' + hint + '</div>' : '')
|
|
309
|
+
shadow.appendChild(toast)
|
|
310
|
+
if (duration) setTimeout(() => toast.remove(), duration)
|
|
311
|
+
return toast
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function startRecording() {
|
|
315
|
+
// exit inspection mode and close dialog
|
|
316
|
+
deactivate()
|
|
317
|
+
pickerDialog?.close()
|
|
318
|
+
|
|
319
|
+
recording = true
|
|
320
|
+
recordEvents = []
|
|
321
|
+
recordFrameCount = 0
|
|
322
|
+
recordStartTime = Date.now()
|
|
323
|
+
|
|
324
|
+
recordEvents.push({
|
|
325
|
+
t: 0,
|
|
326
|
+
type: 'start',
|
|
327
|
+
window: {
|
|
328
|
+
w: window.innerWidth,
|
|
329
|
+
h: window.innerHeight,
|
|
330
|
+
scrollX: window.scrollX,
|
|
331
|
+
scrollY: window.scrollY,
|
|
332
|
+
},
|
|
333
|
+
url: location.href,
|
|
334
|
+
title: document.title,
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
recordHandlers = {
|
|
338
|
+
mousemove: (e) => {
|
|
339
|
+
recordFrameCount++
|
|
340
|
+
if (recordFrameCount % recordThrottle !== 0) return
|
|
341
|
+
recordEvents.push({ t: recordTs(), type: 'move', x: e.clientX, y: e.clientY })
|
|
342
|
+
},
|
|
343
|
+
click: (e) => {
|
|
344
|
+
recordEvents.push({
|
|
345
|
+
t: recordTs(),
|
|
346
|
+
type: 'click',
|
|
347
|
+
x: e.clientX,
|
|
348
|
+
y: e.clientY,
|
|
349
|
+
btn: e.button,
|
|
350
|
+
el: getRecordElementInfo(e.target),
|
|
351
|
+
})
|
|
352
|
+
},
|
|
353
|
+
dblclick: (e) => {
|
|
354
|
+
recordEvents.push({
|
|
355
|
+
t: recordTs(),
|
|
356
|
+
type: 'dblclick',
|
|
357
|
+
x: e.clientX,
|
|
358
|
+
y: e.clientY,
|
|
359
|
+
el: getRecordElementInfo(e.target),
|
|
360
|
+
})
|
|
361
|
+
},
|
|
362
|
+
contextmenu: (e) => {
|
|
363
|
+
recordEvents.push({
|
|
364
|
+
t: recordTs(),
|
|
365
|
+
type: 'contextmenu',
|
|
366
|
+
x: e.clientX,
|
|
367
|
+
y: e.clientY,
|
|
368
|
+
el: getRecordElementInfo(e.target),
|
|
369
|
+
})
|
|
370
|
+
},
|
|
371
|
+
mousedown: (e) => {
|
|
372
|
+
recordEvents.push({
|
|
373
|
+
t: recordTs(),
|
|
374
|
+
type: 'mousedown',
|
|
375
|
+
x: e.clientX,
|
|
376
|
+
y: e.clientY,
|
|
377
|
+
btn: e.button,
|
|
378
|
+
el: getRecordElementInfo(e.target),
|
|
379
|
+
})
|
|
380
|
+
},
|
|
381
|
+
mouseup: (e) => {
|
|
382
|
+
recordEvents.push({
|
|
383
|
+
t: recordTs(),
|
|
384
|
+
type: 'mouseup',
|
|
385
|
+
x: e.clientX,
|
|
386
|
+
y: e.clientY,
|
|
387
|
+
btn: e.button,
|
|
388
|
+
})
|
|
389
|
+
},
|
|
390
|
+
keydown: (e) => {
|
|
391
|
+
if (e.key === 'Alt' || e.key === 'Meta') return
|
|
392
|
+
recordEvents.push({
|
|
393
|
+
t: recordTs(),
|
|
394
|
+
type: 'keydown',
|
|
395
|
+
key: e.key,
|
|
396
|
+
code: e.code,
|
|
397
|
+
mods: getMods(e),
|
|
398
|
+
el: getRecordElementInfo(document.activeElement),
|
|
399
|
+
})
|
|
400
|
+
},
|
|
401
|
+
keyup: (e) => {
|
|
402
|
+
if (e.key === 'Alt' || e.key === 'Meta') return
|
|
403
|
+
recordEvents.push({ t: recordTs(), type: 'keyup', key: e.key, code: e.code })
|
|
404
|
+
},
|
|
405
|
+
input: (e) => {
|
|
406
|
+
const val = e.target.value
|
|
407
|
+
recordEvents.push({
|
|
408
|
+
t: recordTs(),
|
|
409
|
+
type: 'input',
|
|
410
|
+
val: val?.slice?.(-20),
|
|
411
|
+
el: getRecordElementInfo(e.target),
|
|
412
|
+
})
|
|
413
|
+
},
|
|
414
|
+
change: (e) => {
|
|
415
|
+
recordEvents.push({
|
|
416
|
+
t: recordTs(),
|
|
417
|
+
type: 'change',
|
|
418
|
+
el: getRecordElementInfo(e.target),
|
|
419
|
+
})
|
|
420
|
+
},
|
|
421
|
+
focus: (e) => {
|
|
422
|
+
recordEvents.push({
|
|
423
|
+
t: recordTs(),
|
|
424
|
+
type: 'focus',
|
|
425
|
+
el: getRecordElementInfo(e.target),
|
|
426
|
+
})
|
|
427
|
+
},
|
|
428
|
+
blur: (e) => {
|
|
429
|
+
recordEvents.push({
|
|
430
|
+
t: recordTs(),
|
|
431
|
+
type: 'blur',
|
|
432
|
+
el: getRecordElementInfo(e.target),
|
|
433
|
+
})
|
|
434
|
+
},
|
|
435
|
+
scroll: () => {
|
|
436
|
+
recordEvents.push({
|
|
437
|
+
t: recordTs(),
|
|
438
|
+
type: 'scroll',
|
|
439
|
+
scrollX: window.scrollX,
|
|
440
|
+
scrollY: window.scrollY,
|
|
441
|
+
})
|
|
442
|
+
},
|
|
443
|
+
wheel: (e) => {
|
|
444
|
+
recordEvents.push({
|
|
445
|
+
t: recordTs(),
|
|
446
|
+
type: 'wheel',
|
|
447
|
+
x: e.clientX,
|
|
448
|
+
y: e.clientY,
|
|
449
|
+
dx: Math.round(e.deltaX),
|
|
450
|
+
dy: Math.round(e.deltaY),
|
|
451
|
+
})
|
|
452
|
+
},
|
|
453
|
+
resize: () => {
|
|
454
|
+
recordEvents.push({
|
|
455
|
+
t: recordTs(),
|
|
456
|
+
type: 'resize',
|
|
457
|
+
w: window.innerWidth,
|
|
458
|
+
h: window.innerHeight,
|
|
459
|
+
})
|
|
460
|
+
},
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
Object.entries(recordHandlers).forEach(([evt, fn]) => {
|
|
464
|
+
window.addEventListener(evt, fn, { capture: true, passive: true })
|
|
465
|
+
})
|
|
466
|
+
|
|
467
|
+
showToast('\u25cf', 'Tap Option to stop and copy', 800)
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function buildContextLines() {
|
|
471
|
+
const lines = []
|
|
472
|
+
const projectRoot = window.__oneProjectRoot || ''
|
|
473
|
+
lines.push('# url: ' + location.href)
|
|
474
|
+
if (projectRoot) lines.push('# project: ' + projectRoot)
|
|
475
|
+
lines.push('# route: ' + location.pathname)
|
|
476
|
+
lines.push('# title: ' + document.title)
|
|
477
|
+
lines.push(
|
|
478
|
+
'# window: ' +
|
|
479
|
+
window.innerWidth +
|
|
480
|
+
'x' +
|
|
481
|
+
window.innerHeight +
|
|
482
|
+
' dpr:' +
|
|
483
|
+
devicePixelRatio
|
|
484
|
+
)
|
|
485
|
+
lines.push('# scroll: ' + window.scrollX + ',' + window.scrollY)
|
|
486
|
+
lines.push('# mouse: ' + mousePos.x + ',' + mousePos.y)
|
|
487
|
+
// color scheme
|
|
488
|
+
const dark = window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
489
|
+
lines.push('# theme: ' + (dark ? 'dark' : 'light'))
|
|
490
|
+
|
|
491
|
+
if (currentElementChain.length) {
|
|
492
|
+
const topEl = currentElementChain[0]
|
|
493
|
+
const el = topEl.element
|
|
494
|
+
const names = currentElementChain
|
|
495
|
+
.slice(0, 6)
|
|
496
|
+
.map((e) => e.name)
|
|
497
|
+
.join(' < ')
|
|
498
|
+
const cssSelector = generateSelector(el)
|
|
499
|
+
const recordSelector = getRecordSelector(el)
|
|
500
|
+
if (names) lines.push('# components: ' + names)
|
|
501
|
+
if (cssSelector) lines.push('# selector: ' + cssSelector)
|
|
502
|
+
if (recordSelector) lines.push('# test-selector: ' + recordSelector)
|
|
503
|
+
// bounding box
|
|
504
|
+
const rect = el.getBoundingClientRect()
|
|
505
|
+
lines.push(
|
|
506
|
+
'# rect: ' +
|
|
507
|
+
Math.round(rect.x) +
|
|
508
|
+
',' +
|
|
509
|
+
Math.round(rect.y) +
|
|
510
|
+
' ' +
|
|
511
|
+
Math.round(rect.width) +
|
|
512
|
+
'x' +
|
|
513
|
+
Math.round(rect.height)
|
|
514
|
+
)
|
|
515
|
+
// text content (innerText excludes scripts/hidden elements)
|
|
516
|
+
const text = (el.innerText || '').trim().replace(/\s+/g, ' ').slice(0, 80)
|
|
517
|
+
if (text) lines.push('# text: ' + text)
|
|
518
|
+
// accessibility
|
|
519
|
+
const role = el.getAttribute('role')
|
|
520
|
+
const ariaLabel = el.getAttribute('aria-label')
|
|
521
|
+
const testId = el.getAttribute('data-testid')
|
|
522
|
+
const a11y = [
|
|
523
|
+
role && 'role=' + role,
|
|
524
|
+
ariaLabel && 'label=' + ariaLabel,
|
|
525
|
+
testId && 'testid=' + testId,
|
|
526
|
+
]
|
|
527
|
+
.filter(Boolean)
|
|
528
|
+
.join(' ')
|
|
529
|
+
if (a11y) lines.push('# a11y: ' + a11y)
|
|
530
|
+
// key computed styles
|
|
531
|
+
const cs = window.getComputedStyle(el)
|
|
532
|
+
const styles = ['display:' + cs.display, 'position:' + cs.position]
|
|
533
|
+
if (cs.overflow !== 'visible') styles.push('overflow:' + cs.overflow)
|
|
534
|
+
if (cs.zIndex !== 'auto') styles.push('z:' + cs.zIndex)
|
|
535
|
+
lines.push('# style: ' + styles.join(' '))
|
|
536
|
+
// source chain
|
|
537
|
+
for (const e of currentElementChain) {
|
|
538
|
+
lines.push('# source: ' + e.source)
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
return lines
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function buildSnapshot() {
|
|
545
|
+
const lines = ['# Snapshot ' + new Date().toISOString()]
|
|
546
|
+
lines.push(...buildContextLines())
|
|
547
|
+
return lines.join('\n')
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function stopRecording() {
|
|
551
|
+
if (!recording) return
|
|
552
|
+
recording = false
|
|
553
|
+
|
|
554
|
+
Object.entries(recordHandlers).forEach(([evt, fn]) => {
|
|
555
|
+
window.removeEventListener(evt, fn, { capture: true })
|
|
556
|
+
})
|
|
557
|
+
recordHandlers = {}
|
|
558
|
+
|
|
559
|
+
// capture final state
|
|
560
|
+
const finalEl = document.elementFromPoint(mousePos.x, mousePos.y)
|
|
561
|
+
const finalInfo = getRecordElementInfo(finalEl)
|
|
562
|
+
|
|
563
|
+
const duration = recordTs()
|
|
564
|
+
|
|
565
|
+
// build compact line-based format
|
|
566
|
+
const lines = ['# Recording ' + new Date().toISOString()]
|
|
567
|
+
lines.push(...buildContextLines())
|
|
568
|
+
lines.push('# duration: ' + duration + 'ms events: ' + recordEvents.length)
|
|
569
|
+
lines.push('')
|
|
570
|
+
|
|
571
|
+
for (const e of recordEvents) {
|
|
572
|
+
if (e.type === 'start') continue
|
|
573
|
+
let line = e.t + ' ' + e.type
|
|
574
|
+
if (e.x !== undefined) line += ' ' + e.x + ',' + e.y
|
|
575
|
+
if (e.btn !== undefined) line += ' btn:' + e.btn
|
|
576
|
+
if (e.dx !== undefined) line += ' d:' + e.dx + ',' + e.dy
|
|
577
|
+
if (e.w !== undefined) line += ' ' + e.w + 'x' + e.h
|
|
578
|
+
if (e.key) line += ' ' + e.key + (e.code !== e.key ? '(' + e.code + ')' : '')
|
|
579
|
+
if (e.mods) line += ' +' + e.mods.join('+')
|
|
580
|
+
if (e.val !== undefined) line += ' val:"' + e.val + '"'
|
|
581
|
+
if (e.scrollX !== undefined) line += ' ' + e.scrollX + ',' + e.scrollY
|
|
582
|
+
if (e.el) {
|
|
583
|
+
if (e.el.selector) line += ' [' + e.el.selector + ']'
|
|
584
|
+
if (e.el.source) line += ' @' + e.el.source.split('/').pop()
|
|
585
|
+
if (e.el.text && e.type !== 'input') line += ' "' + e.el.text.slice(0, 30) + '"'
|
|
586
|
+
}
|
|
587
|
+
lines.push(line)
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
if (finalInfo) {
|
|
591
|
+
lines.push('')
|
|
592
|
+
lines.push('# final hover: ' + (finalInfo.selector || 'none'))
|
|
593
|
+
if (finalInfo.source) lines.push('# final source: ' + finalInfo.source)
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
const output = lines.join('\n')
|
|
597
|
+
navigator.clipboard
|
|
598
|
+
.writeText(output)
|
|
599
|
+
.then(() => {
|
|
600
|
+
showToast('✓', 'Copied to clipboard', 1200, true)
|
|
601
|
+
})
|
|
602
|
+
.catch(() => {
|
|
603
|
+
console.log('[Source Inspector] Recording:\n' + output)
|
|
604
|
+
})
|
|
605
|
+
|
|
606
|
+
recordEvents = []
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function getElementChain(x, y) {
|
|
610
|
+
const allElements = document.elementsFromPoint(x, y)
|
|
611
|
+
const chain = []
|
|
612
|
+
for (const element of allElements) {
|
|
613
|
+
if (element.hasAttribute('data-one-source')) {
|
|
614
|
+
chain.push({
|
|
615
|
+
element,
|
|
616
|
+
source: element.getAttribute('data-one-source'),
|
|
617
|
+
name: getComponentName(element.getAttribute('data-one-source')),
|
|
618
|
+
})
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
return chain
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function getComponentName(source) {
|
|
625
|
+
const parts = source.split(':')
|
|
626
|
+
const filePath = parts.slice(0, -2).join(':')
|
|
627
|
+
const fileName = filePath.split('/').pop()
|
|
628
|
+
// extract component name from file name
|
|
629
|
+
return fileName.replace(/\.(tsx?|jsx?)$/, '')
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
function showPicker(x, y, chain) {
|
|
633
|
+
if (!host) createHost()
|
|
634
|
+
currentElementChain = chain
|
|
635
|
+
|
|
636
|
+
// build list items
|
|
637
|
+
const listEl = shadow.getElementById('picker-list')
|
|
638
|
+
listEl.innerHTML = chain
|
|
639
|
+
.map((el) => {
|
|
640
|
+
const parts = el.source.split(':')
|
|
641
|
+
const filePath = parts.slice(0, -2).join(':')
|
|
642
|
+
const line = parts[parts.length - 2]
|
|
643
|
+
// show just filename:line
|
|
644
|
+
const shortFile = filePath.split('/').pop() + ':' + line
|
|
645
|
+
return (
|
|
646
|
+
'<div class="picker-item" data-source="' +
|
|
647
|
+
escapeHtml(el.source) +
|
|
648
|
+
'">' +
|
|
649
|
+
'<span class="picker-item-name">' +
|
|
650
|
+
escapeHtml(el.name) +
|
|
651
|
+
'</span>' +
|
|
652
|
+
'<span class="picker-item-file">' +
|
|
653
|
+
escapeHtml(shortFile) +
|
|
654
|
+
'</span>' +
|
|
655
|
+
'</div>'
|
|
656
|
+
)
|
|
657
|
+
})
|
|
658
|
+
.join('')
|
|
659
|
+
|
|
660
|
+
// position picker near click, smart about viewport edges
|
|
661
|
+
const pickerWidth = 320
|
|
662
|
+
const pickerHeight = Math.min(300, 80 + chain.length * 36)
|
|
663
|
+
const pad = 10
|
|
664
|
+
|
|
665
|
+
let left = x + 10
|
|
666
|
+
let top = y + 10
|
|
667
|
+
let flippedUp = false
|
|
668
|
+
|
|
669
|
+
// flip to left if too close to right edge
|
|
670
|
+
if (left + pickerWidth > window.innerWidth - pad) {
|
|
671
|
+
left = x - pickerWidth - 10
|
|
672
|
+
}
|
|
673
|
+
// flip up if too close to bottom
|
|
674
|
+
if (top + pickerHeight > window.innerHeight - pad) {
|
|
675
|
+
top = y - pickerHeight - 10
|
|
676
|
+
flippedUp = true
|
|
677
|
+
}
|
|
678
|
+
// clamp to viewport
|
|
679
|
+
left = Math.max(pad, Math.min(left, window.innerWidth - pickerWidth - pad))
|
|
680
|
+
top = Math.max(pad, Math.min(top, window.innerHeight - pickerHeight - pad))
|
|
681
|
+
|
|
682
|
+
// reorder elements so actions row is closest to mouse
|
|
683
|
+
const picker = shadow.getElementById('picker')
|
|
684
|
+
const header = picker.querySelector('.picker-header')
|
|
685
|
+
const actions = picker.querySelector('.picker-actions')
|
|
686
|
+
const list = picker.querySelector('.picker-list')
|
|
687
|
+
|
|
688
|
+
if (flippedUp) {
|
|
689
|
+
// picker is above click, put actions at bottom (closest to mouse)
|
|
690
|
+
picker.style.flexDirection = 'column'
|
|
691
|
+
header.style.order = '0'
|
|
692
|
+
list.style.order = '1'
|
|
693
|
+
actions.style.order = '2'
|
|
694
|
+
} else {
|
|
695
|
+
// picker is below click, put actions at top (closest to mouse)
|
|
696
|
+
picker.style.flexDirection = 'column'
|
|
697
|
+
header.style.order = '1'
|
|
698
|
+
list.style.order = '2'
|
|
699
|
+
actions.style.order = '0'
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
pickerDialog.style.left = left + 'px'
|
|
703
|
+
pickerDialog.style.top = top + 'px'
|
|
704
|
+
pickerDialog.showModal()
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
function escapeHtml(str) {
|
|
708
|
+
if (!str) return ''
|
|
709
|
+
return String(str)
|
|
710
|
+
.replace(/&/g, '&')
|
|
711
|
+
.replace(/</g, '<')
|
|
712
|
+
.replace(/>/g, '>')
|
|
713
|
+
.replace(/"/g, '"')
|
|
714
|
+
}
|
|
715
|
+
|
|
75
716
|
function setupRemovalObserver() {
|
|
76
717
|
if (removalObserver) return
|
|
77
718
|
removalObserver = new MutationObserver((mutations) => {
|
|
@@ -83,6 +724,7 @@
|
|
|
83
724
|
shadow = null
|
|
84
725
|
overlay = null
|
|
85
726
|
tag = null
|
|
727
|
+
pickerDialog = null
|
|
86
728
|
return
|
|
87
729
|
}
|
|
88
730
|
}
|
|
@@ -194,6 +836,11 @@
|
|
|
194
836
|
activate,
|
|
195
837
|
deactivate,
|
|
196
838
|
isActive: () => active,
|
|
839
|
+
closePicker: () => pickerDialog?.close(),
|
|
840
|
+
startRecording: () => {
|
|
841
|
+
currentElementChain = [] // no element context
|
|
842
|
+
startRecording()
|
|
843
|
+
},
|
|
197
844
|
}
|
|
198
845
|
|
|
199
846
|
function deactivate() {
|
|
@@ -232,6 +879,11 @@
|
|
|
232
879
|
window.addEventListener('keyup', (e) => {
|
|
233
880
|
if (e.defaultPrevented) return
|
|
234
881
|
if (e.key === 'Alt') {
|
|
882
|
+
// if recording, stop on option release
|
|
883
|
+
if (recording) {
|
|
884
|
+
stopRecording()
|
|
885
|
+
return
|
|
886
|
+
}
|
|
235
887
|
deactivate()
|
|
236
888
|
} else {
|
|
237
889
|
// reset when other keys are released (check if no modifiers held)
|
|
@@ -262,19 +914,26 @@
|
|
|
262
914
|
'click',
|
|
263
915
|
(e) => {
|
|
264
916
|
if (!active) return
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
}
|
|
917
|
+
const chain = getElementChain(e.clientX, e.clientY)
|
|
918
|
+
if (chain.length) {
|
|
919
|
+
e.preventDefault()
|
|
920
|
+
e.stopImmediatePropagation()
|
|
921
|
+
deactivate()
|
|
922
|
+
showPicker(e.clientX, e.clientY, chain)
|
|
272
923
|
}
|
|
273
|
-
|
|
924
|
+
},
|
|
925
|
+
true
|
|
926
|
+
)
|
|
927
|
+
|
|
928
|
+
// close picker on escape, prevent bubbling
|
|
929
|
+
document.addEventListener(
|
|
930
|
+
'keydown',
|
|
931
|
+
(e) => {
|
|
932
|
+
if (e.key === 'Escape' && pickerDialog?.open) {
|
|
274
933
|
e.preventDefault()
|
|
275
934
|
e.stopPropagation()
|
|
276
|
-
|
|
277
|
-
|
|
935
|
+
e.stopImmediatePropagation()
|
|
936
|
+
pickerDialog.close()
|
|
278
937
|
}
|
|
279
938
|
},
|
|
280
939
|
true
|