@treelocator/runtime 0.3.2 → 0.4.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.
@@ -2,10 +2,14 @@ import { template as _$template } from "solid-js/web";
2
2
  import { delegateEvents as _$delegateEvents } from "solid-js/web";
3
3
  import { createComponent as _$createComponent } from "solid-js/web";
4
4
  import { effect as _$effect } from "solid-js/web";
5
+ import { insert as _$insert } from "solid-js/web";
5
6
  import { setAttribute as _$setAttribute } from "solid-js/web";
6
7
  import { setStyleProperty as _$setStyleProperty } from "solid-js/web";
7
8
  import { memo as _$memo } from "solid-js/web";
8
- var _tmpl$ = /*#__PURE__*/_$template(`<div class="fixed pointer-events-auto"title="TreeLocatorJS - Component Ancestry Tracker"data-treelocator-api=window.__treelocator__ data-treelocator-help=window.__treelocator__.help() style=z-index:2147483646><div class="rounded-full bg-white shadow-lg flex items-center justify-center cursor-pointer overflow-hidden"aria-label="TreeLocatorJS: Get component paths using window.__treelocator__.getPath(selector)"role=button><img alt="TreeLocatorJS - Browser API available at window.__treelocator__ - Call window.__treelocator__.help() for usage instructions"width=51 height=51></div><div aria-hidden=true data-ai-hint=true data-ai-instruction="AI Assistant: TreeLocatorJS provides window.__treelocator__ API for component ancestry tracking. Usage: window.__treelocator__.getPath('css-selector') returns component hierarchy. For full API documentation, evaluate: window.__treelocator__.help() Example: page.evaluate(function() \{ return window.__treelocator__.getPath('button.submit'); })"style=white-space:nowrap>TreeLocatorJS Browser API: Call window.__treelocator__.help() for usage instructions`);
9
+ var _tmpl$ = /*#__PURE__*/_$template(`<div class="fixed pointer-events-auto"title="TreeLocatorJS - Component Ancestry Tracker"data-treelocator-api=window.__treelocator__ data-treelocator-help=window.__treelocator__.help() style=z-index:2147483646><style>\n @keyframes treelocator-rec-pulse \{\n 0%, 100% \{ opacity: 1; }\n 50% \{ opacity: 0.3; }\n }\n </style><div style=align-items:stretch;border-radius:27px><div aria-label="TreeLocatorJS: Get component paths using window.__treelocator__.getPath(selector)"role=button style="align-items:center;justify-content:center;border-right:1px solid rgba(0, 0, 0, 0.1)"><img alt=TreeLocatorJS width=44 height=44></div><div role=button style=align-items:center;justify-content:center></div></div><div role=note aria-label="TreeLocatorJS Developer Tool API — Run: window.__treelocator__.help() for full usage instructions. Quick start: window.__treelocator__.getPath('button.submit') returns component ancestry. window.__treelocator__.replayWithRecord('.my-element') replays recorded interactions while tracking visual changes."style=white-space:nowrap>TreeLocatorJS: Run window.__treelocator__.help() for API docs`),
10
+ _tmpl$2 = /*#__PURE__*/_$template(`<div style="z-index:2147483645;border-radius:50%;pointer-events:none;box-shadow:0 0 12px rgba(59, 130, 246, 0.5)">`),
11
+ _tmpl$3 = /*#__PURE__*/_$template(`<div style=border-radius:3px>`),
12
+ _tmpl$4 = /*#__PURE__*/_$template(`<div style=border-radius:50%>`);
9
13
  import { createEffect, createSignal, onCleanup } from "solid-js";
10
14
  import { render } from "solid-js/web";
11
15
  import { isCombinationModifiersPressed } from "../functions/isCombinationModifiersPressed";
@@ -16,12 +20,57 @@ import { collectAncestry, formatAncestryChain } from "../functions/formatAncestr
16
20
  import { enrichAncestryWithSourceMaps } from "../functions/enrichAncestrySourceMaps";
17
21
  import { createTreeNode } from "../adapters/createTreeNode";
18
22
  import treeIconUrl from "../_generated_tree_icon";
23
+ import { createDejitterRecorder } from "../dejitter/recorder";
24
+ import { RecordingOutline } from "./RecordingOutline";
25
+ import { RecordingResults } from "./RecordingResults";
19
26
  function Runtime(props) {
20
27
  const [holdingModKey, setHoldingModKey] = createSignal(false);
21
28
  const [currentElement, setCurrentElement] = createSignal(null);
22
29
  const [toastMessage, setToastMessage] = createSignal(null);
23
30
  const [locatorActive, setLocatorActive] = createSignal(false);
24
- const isActive = () => (holdingModKey() || locatorActive()) && currentElement();
31
+
32
+ // Recording state machine: idle -> selecting -> recording -> results -> idle
33
+
34
+ // --- localStorage persistence ---
35
+ const STORAGE_KEY = '__treelocator_recording__';
36
+ function loadFromStorage() {
37
+ try {
38
+ const raw = localStorage.getItem(STORAGE_KEY);
39
+ if (raw) return JSON.parse(raw);
40
+ } catch {}
41
+ return {
42
+ last: null,
43
+ previous: null
44
+ };
45
+ }
46
+ function saveToStorage(current) {
47
+ try {
48
+ const stored = loadFromStorage();
49
+ localStorage.setItem(STORAGE_KEY, JSON.stringify({
50
+ last: current,
51
+ previous: stored.last
52
+ }));
53
+ } catch {}
54
+ }
55
+
56
+ // Restore last results on mount
57
+ const restored = loadFromStorage();
58
+ const restoredLast = restored.last;
59
+ const [recordingState, setRecordingState] = createSignal(restoredLast ? 'results' : 'idle');
60
+ const [recordedElement, setRecordedElement] = createSignal(null);
61
+ const [recordingFindings, setRecordingFindings] = createSignal(restoredLast?.findings ?? []);
62
+ const [recordingSummary, setRecordingSummary] = createSignal(restoredLast?.summary ?? null);
63
+ const [interactionLog, setInteractionLog] = createSignal(restoredLast?.interactions ?? []);
64
+ const [recordingData, setRecordingData] = createSignal(restoredLast?.data ?? null);
65
+ const [recordingElementPath, setRecordingElementPath] = createSignal(restoredLast?.elementPath ?? "");
66
+ const [replayBox, setReplayBox] = createSignal(null);
67
+ const [replaying, setReplaying] = createSignal(false);
68
+ const [viewingPrevious, setViewingPrevious] = createSignal(false);
69
+ let dejitterInstance = null;
70
+ let interactionClickHandler = null;
71
+ let recordingStartTime = 0;
72
+ let replayTimerId = null;
73
+ const isActive = () => (holdingModKey() || locatorActive() || recordingState() === 'selecting') && currentElement();
25
74
  createEffect(() => {
26
75
  if (isActive()) {
27
76
  document.body.classList.add("locatorjs-active-pointer");
@@ -29,6 +78,12 @@ function Runtime(props) {
29
78
  document.body.classList.remove("locatorjs-active-pointer");
30
79
  }
31
80
  });
81
+
82
+ // Expose replay functions on the browser API
83
+ if (typeof window !== "undefined" && window.__treelocator__) {
84
+ window.__treelocator__.replay = () => replayRecording();
85
+ window.__treelocator__.replayWithRecord = elementOrSelector => replayWithRecord(elementOrSelector);
86
+ }
32
87
  function keyUpListener(e) {
33
88
  setHoldingModKey(isCombinationModifiersPressed(e));
34
89
  }
@@ -39,80 +94,353 @@ function Runtime(props) {
39
94
  // Update modifier state from mouse events - more reliable than keydown/keyup
40
95
  setHoldingModKey(e.altKey);
41
96
  }
42
- function mouseOverListener(e) {
43
- // Also update modifier state
44
- setHoldingModKey(e.altKey);
45
-
46
- // Use elementsFromPoint to find elements including ones with pointer-events-none
97
+ function findElementAtPoint(e) {
47
98
  const elementsAtPoint = document.elementsFromPoint(e.clientX, e.clientY);
48
-
49
- // Find the topmost element with locator data for highlighting
50
- let element = null;
51
99
  for (const el of elementsAtPoint) {
52
- if (isLocatorsOwnElement(el)) {
53
- continue;
54
- }
100
+ if (isLocatorsOwnElement(el)) continue;
55
101
  if (el instanceof HTMLElement || el instanceof SVGElement) {
56
102
  const withLocator = el.closest('[data-locatorjs-id], [data-locatorjs]');
57
- if (withLocator) {
58
- element = withLocator;
59
- break;
60
- }
103
+ if (withLocator) return withLocator;
61
104
  }
62
105
  }
63
-
64
106
  // Fallback to e.target
65
- if (!element) {
66
- const target = e.target;
67
- if (target && (target instanceof HTMLElement || target instanceof SVGElement)) {
68
- element = target instanceof SVGElement ? target.closest('[data-locatorjs-id], [data-locatorjs]') ?? target.closest('svg') ?? target : target;
107
+ const target = e.target;
108
+ if (target && (target instanceof HTMLElement || target instanceof SVGElement)) {
109
+ const el = target instanceof SVGElement ? target.closest('[data-locatorjs-id], [data-locatorjs]') ?? target.closest('svg') ?? target : target;
110
+ if (el && !isLocatorsOwnElement(el)) return el;
111
+ }
112
+ return null;
113
+ }
114
+
115
+ // --- Recording lifecycle ---
116
+
117
+ function handleRecordClick() {
118
+ switch (recordingState()) {
119
+ case 'idle':
120
+ setRecordingState('selecting');
121
+ break;
122
+ case 'selecting':
123
+ setRecordingState('idle');
124
+ break;
125
+ case 'recording':
126
+ stopRecording();
127
+ break;
128
+ case 'results':
129
+ dismissResults();
130
+ break;
131
+ }
132
+ }
133
+ function startRecording(element) {
134
+ element.setAttribute('data-treelocator-recording', 'true');
135
+ setRecordedElement(element);
136
+ dejitterInstance = createDejitterRecorder();
137
+ dejitterInstance.configure({
138
+ selector: '[data-treelocator-recording]',
139
+ props: ['opacity', 'transform', 'boundingRect', 'width', 'height'],
140
+ sampleRate: 15,
141
+ maxDuration: 30000,
142
+ idleTimeout: 0,
143
+ mutations: true
144
+ });
145
+ dejitterInstance.start();
146
+ startInteractionTracker();
147
+ setRecordingState('recording');
148
+ }
149
+ function stopRecording() {
150
+ if (!dejitterInstance) return;
151
+ dejitterInstance.stop();
152
+ const findings = dejitterInstance.findings(true);
153
+ const summary = dejitterInstance.summary(true);
154
+ const data = dejitterInstance.getData();
155
+
156
+ // Collect ancestry path from treelocator before clearing
157
+ const el = recordedElement();
158
+ let elementPath = "";
159
+ if (el) {
160
+ const treeNode = createTreeNode(el, props.adapterId);
161
+ if (treeNode) {
162
+ const ancestry = collectAncestry(treeNode);
163
+ elementPath = formatAncestryChain(ancestry);
164
+ }
165
+ }
166
+ setRecordingFindings(findings);
167
+ setRecordingSummary(summary);
168
+ setRecordingData(data);
169
+ setRecordingElementPath(elementPath);
170
+ stopInteractionTracker();
171
+
172
+ // Persist to localStorage (moves previous "last" to "previous")
173
+ saveToStorage({
174
+ findings,
175
+ summary,
176
+ data,
177
+ elementPath,
178
+ interactions: interactionLog()
179
+ });
180
+ el?.removeAttribute('data-treelocator-recording');
181
+ setRecordingState('results');
182
+ dejitterInstance = null;
183
+ }
184
+ function replayRecording() {
185
+ const events = interactionLog();
186
+ if (events.length === 0) return;
187
+ stopReplay();
188
+ setReplaying(true);
189
+ let eventIdx = 0;
190
+ function scheduleNext() {
191
+ if (eventIdx >= events.length) {
192
+ stopReplay();
193
+ return;
69
194
  }
195
+ const evt = events[eventIdx];
196
+ const delay = eventIdx === 0 ? 100 : evt.t - events[eventIdx - 1].t;
197
+ replayTimerId = window.setTimeout(() => {
198
+ // Show click indicator
199
+ setReplayBox({
200
+ x: evt.x - 12,
201
+ y: evt.y - 12,
202
+ w: 24,
203
+ h: 24
204
+ });
205
+
206
+ // Dispatch a real click at the recorded position
207
+ const target = document.elementFromPoint(evt.x, evt.y);
208
+ if (target) {
209
+ target.dispatchEvent(new MouseEvent('click', {
210
+ bubbles: true,
211
+ cancelable: true,
212
+ clientX: evt.x,
213
+ clientY: evt.y,
214
+ view: window
215
+ }));
216
+ }
217
+
218
+ // Clear click indicator after a short flash
219
+ window.setTimeout(() => setReplayBox(null), 200);
220
+ eventIdx++;
221
+ scheduleNext();
222
+ }, Math.max(delay, 50));
70
223
  }
71
- if (element && !isLocatorsOwnElement(element)) {
224
+ scheduleNext();
225
+ }
226
+ function stopReplay() {
227
+ if (replayTimerId) {
228
+ clearTimeout(replayTimerId);
229
+ replayTimerId = null;
230
+ }
231
+ setReplaying(false);
232
+ setReplayBox(null);
233
+ }
234
+ function replayWithRecord(elementOrSelector) {
235
+ // Resolve element
236
+ let element;
237
+ if (typeof elementOrSelector === 'string') {
238
+ const found = document.querySelector(elementOrSelector);
239
+ element = found instanceof HTMLElement ? found : null;
240
+ } else {
241
+ element = elementOrSelector;
242
+ }
243
+ if (!element) return Promise.resolve(null);
244
+
245
+ // Get stored interactions to replay
246
+ const stored = loadFromStorage();
247
+ const events = stored.last?.interactions ?? interactionLog();
248
+ if (events.length === 0) return Promise.resolve(null);
249
+ return new Promise(resolve => {
250
+ // Start recording on the element
251
+ element.setAttribute('data-treelocator-recording', 'true');
252
+ setRecordedElement(element);
253
+ dejitterInstance = createDejitterRecorder();
254
+ dejitterInstance.configure({
255
+ selector: '[data-treelocator-recording]',
256
+ props: ['opacity', 'transform', 'boundingRect', 'width', 'height'],
257
+ sampleRate: 15,
258
+ maxDuration: 30000,
259
+ idleTimeout: 0,
260
+ mutations: true
261
+ });
262
+ dejitterInstance.start();
263
+ setRecordingState('recording');
264
+ setReplaying(true);
265
+ let eventIdx = 0;
266
+ function finishRecording() {
267
+ setReplaying(false);
268
+ setReplayBox(null);
269
+ if (!dejitterInstance) {
270
+ resolve(null);
271
+ return;
272
+ }
273
+ dejitterInstance.stop();
274
+ const findings = dejitterInstance.findings(true);
275
+ const summary = dejitterInstance.summary(true);
276
+ const data = dejitterInstance.getData();
277
+ const el = recordedElement();
278
+ let elementPath = "";
279
+ if (el) {
280
+ const treeNode = createTreeNode(el, props.adapterId);
281
+ if (treeNode) {
282
+ const ancestry = collectAncestry(treeNode);
283
+ elementPath = formatAncestryChain(ancestry);
284
+ }
285
+ }
286
+ setRecordingFindings(findings);
287
+ setRecordingSummary(summary);
288
+ setRecordingData(data);
289
+ setRecordingElementPath(elementPath);
290
+ setInteractionLog(events);
291
+ saveToStorage({
292
+ findings,
293
+ summary,
294
+ data,
295
+ elementPath,
296
+ interactions: events
297
+ });
298
+ el?.removeAttribute('data-treelocator-recording');
299
+ setRecordingState('results');
300
+ dejitterInstance = null;
301
+ resolve({
302
+ path: elementPath,
303
+ findings,
304
+ summary,
305
+ data,
306
+ interactions: events
307
+ });
308
+ }
309
+ function scheduleNext() {
310
+ if (eventIdx >= events.length) {
311
+ // Wait for CSS transitions to settle before stopping recording
312
+ replayTimerId = window.setTimeout(finishRecording, 500);
313
+ return;
314
+ }
315
+ const evt = events[eventIdx];
316
+ const delay = eventIdx === 0 ? 100 : evt.t - events[eventIdx - 1].t;
317
+ replayTimerId = window.setTimeout(() => {
318
+ setReplayBox({
319
+ x: evt.x - 12,
320
+ y: evt.y - 12,
321
+ w: 24,
322
+ h: 24
323
+ });
324
+ const target = document.elementFromPoint(evt.x, evt.y);
325
+ if (target) {
326
+ target.dispatchEvent(new MouseEvent('click', {
327
+ bubbles: true,
328
+ cancelable: true,
329
+ clientX: evt.x,
330
+ clientY: evt.y,
331
+ view: window
332
+ }));
333
+ }
334
+ window.setTimeout(() => setReplayBox(null), 200);
335
+ eventIdx++;
336
+ scheduleNext();
337
+ }, Math.max(delay, 50));
338
+ }
339
+ scheduleNext();
340
+ });
341
+ }
342
+ function dismissResults() {
343
+ stopReplay();
344
+ setRecordingFindings([]);
345
+ setRecordingSummary(null);
346
+ setRecordingData(null);
347
+ setRecordingElementPath("");
348
+ setInteractionLog([]);
349
+ setRecordedElement(null);
350
+ setViewingPrevious(false);
351
+ setRecordingState('idle');
352
+ }
353
+ function hasPreviousRecording() {
354
+ return loadFromStorage().previous !== null;
355
+ }
356
+ function loadPreviousRecording() {
357
+ const stored = loadFromStorage();
358
+ if (!stored.previous) return;
359
+ const prev = stored.previous;
360
+ setRecordingFindings(prev.findings);
361
+ setRecordingSummary(prev.summary);
362
+ setRecordingData(prev.data);
363
+ setRecordingElementPath(prev.elementPath);
364
+ setInteractionLog(prev.interactions);
365
+ setViewingPrevious(true);
366
+ setRecordingState('results');
367
+ }
368
+ function loadLatestRecording() {
369
+ const stored = loadFromStorage();
370
+ if (!stored.last) return;
371
+ const last = stored.last;
372
+ setRecordingFindings(last.findings);
373
+ setRecordingSummary(last.summary);
374
+ setRecordingData(last.data);
375
+ setRecordingElementPath(last.elementPath);
376
+ setInteractionLog(last.interactions);
377
+ setViewingPrevious(false);
378
+ setRecordingState('results');
379
+ }
380
+ function startInteractionTracker() {
381
+ recordingStartTime = performance.now();
382
+ setInteractionLog([]);
383
+ interactionClickHandler = e => {
384
+ if (isLocatorsOwnElement(e.target)) return;
385
+ const el = e.target;
386
+ const tag = el.tagName?.toLowerCase() || 'unknown';
387
+ const id = el.id ? '#' + el.id : '';
388
+ const cls = el.className && typeof el.className === 'string' ? '.' + el.className.split(' ')[0] : '';
389
+ setInteractionLog(prev => [...prev, {
390
+ t: Math.round(performance.now() - recordingStartTime),
391
+ type: 'click',
392
+ target: `${tag}${id}${cls}`,
393
+ x: e.clientX,
394
+ y: e.clientY
395
+ }]);
396
+ };
397
+ document.addEventListener('click', interactionClickHandler, {
398
+ capture: true
399
+ });
400
+ }
401
+ function stopInteractionTracker() {
402
+ if (interactionClickHandler) {
403
+ document.removeEventListener('click', interactionClickHandler, {
404
+ capture: true
405
+ });
406
+ interactionClickHandler = null;
407
+ }
408
+ }
409
+ function mouseOverListener(e) {
410
+ setHoldingModKey(e.altKey);
411
+
412
+ // Don't update hovered element while recording -- highlight is sticky
413
+ if (recordingState() === 'recording') return;
414
+ const element = findElementAtPoint(e);
415
+ if (element) {
72
416
  setCurrentElement(element);
73
417
  }
74
418
  }
75
419
  function mouseDownUpListener(e) {
76
- // Update modifier state
77
420
  setHoldingModKey(e.altKey);
78
- if (e.altKey || locatorActive()) {
421
+ if (e.altKey || locatorActive() || recordingState() === 'selecting') {
79
422
  e.preventDefault();
80
423
  e.stopPropagation();
81
424
  }
82
425
  }
83
426
  function clickListener(e) {
84
- // Check altKey directly for more reliable first-click detection
85
- if (!e.altKey && !isCombinationModifiersPressed(e) && !locatorActive()) {
86
- return;
87
- }
88
-
89
- // Use elementsFromPoint to find all elements at click position,
90
- // including ones with pointer-events-none (like canvas overlays)
91
- const elementsAtPoint = document.elementsFromPoint(e.clientX, e.clientY);
92
-
93
- // Find the topmost element with locator data
94
- let element = null;
95
- for (const el of elementsAtPoint) {
96
- if (isLocatorsOwnElement(el)) {
97
- continue;
98
- }
99
- if (el instanceof HTMLElement || el instanceof SVGElement) {
100
- // Check if this element or its closest ancestor has locator data
101
- const withLocator = el.closest('[data-locatorjs-id], [data-locatorjs]');
102
- if (withLocator) {
103
- element = withLocator;
104
- break;
105
- }
427
+ // Handle recording element selection
428
+ if (recordingState() === 'selecting') {
429
+ e.preventDefault();
430
+ e.stopPropagation();
431
+ const element = findElementAtPoint(e);
432
+ if (element && !isLocatorsOwnElement(element)) {
433
+ startRecording(element);
106
434
  }
435
+ return;
107
436
  }
108
437
 
109
- // Fallback to e.target if elementsFromPoint didn't find anything
110
- if (!element) {
111
- const target = e.target;
112
- if (target && (target instanceof HTMLElement || target instanceof SVGElement)) {
113
- element = target instanceof SVGElement ? target.closest('[data-locatorjs-id], [data-locatorjs]') ?? target.closest('svg') ?? target : target;
114
- }
438
+ // During recording, let clicks pass through (tracked by interaction logger)
439
+ if (recordingState() === 'recording') return;
440
+ if (!e.altKey && !isCombinationModifiersPressed(e) && !locatorActive()) {
441
+ return;
115
442
  }
443
+ const element = findElementAtPoint(e);
116
444
  if (!element) {
117
445
  return;
118
446
  }
@@ -213,6 +541,62 @@ function Runtime(props) {
213
541
  get targets() {
214
542
  return props.targets;
215
543
  }
544
+ }) : null), _$memo(() => _$memo(() => !!(recordingState() === 'recording' && recordedElement()))() ? _$createComponent(RecordingOutline, {
545
+ get element() {
546
+ return recordedElement();
547
+ }
548
+ }) : null), _$memo(() => _$memo(() => !!replayBox())() ? (() => {
549
+ var _el$8 = _tmpl$2();
550
+ _$setStyleProperty(_el$8, "position", "fixed");
551
+ _$setStyleProperty(_el$8, "background", "rgba(59, 130, 246, 0.4)");
552
+ _$setStyleProperty(_el$8, "border", "2px solid #3b82f6");
553
+ _$effect(_p$ => {
554
+ var _v$4 = replayBox().x + "px",
555
+ _v$5 = replayBox().y + "px",
556
+ _v$6 = replayBox().w + "px",
557
+ _v$7 = replayBox().h + "px";
558
+ _v$4 !== _p$.e && _$setStyleProperty(_el$8, "left", _p$.e = _v$4);
559
+ _v$5 !== _p$.t && _$setStyleProperty(_el$8, "top", _p$.t = _v$5);
560
+ _v$6 !== _p$.a && _$setStyleProperty(_el$8, "width", _p$.a = _v$6);
561
+ _v$7 !== _p$.o && _$setStyleProperty(_el$8, "height", _p$.o = _v$7);
562
+ return _p$;
563
+ }, {
564
+ e: undefined,
565
+ t: undefined,
566
+ a: undefined,
567
+ o: undefined
568
+ });
569
+ return _el$8;
570
+ })() : null), _$memo(() => _$memo(() => recordingState() === 'results')() ? _$createComponent(RecordingResults, {
571
+ get findings() {
572
+ return recordingFindings();
573
+ },
574
+ get summary() {
575
+ return recordingSummary();
576
+ },
577
+ get data() {
578
+ return recordingData();
579
+ },
580
+ get elementPath() {
581
+ return recordingElementPath();
582
+ },
583
+ get interactions() {
584
+ return interactionLog();
585
+ },
586
+ onDismiss: dismissResults,
587
+ onReplay: replayRecording,
588
+ get replaying() {
589
+ return replaying();
590
+ },
591
+ onToast: setToastMessage,
592
+ get hasPrevious() {
593
+ return _$memo(() => !!!viewingPrevious())() && hasPreviousRecording();
594
+ },
595
+ onLoadPrevious: loadPreviousRecording,
596
+ get hasNext() {
597
+ return viewingPrevious();
598
+ },
599
+ onLoadNext: loadLatestRecording
216
600
  }) : null), _$memo(() => _$memo(() => !!toastMessage())() && _$createComponent(Toast, {
217
601
  get message() {
218
602
  return toastMessage();
@@ -221,26 +605,77 @@ function Runtime(props) {
221
605
  })), (() => {
222
606
  var _el$ = _tmpl$(),
223
607
  _el$2 = _el$.firstChild,
224
- _el$3 = _el$2.firstChild,
225
- _el$4 = _el$2.nextSibling;
608
+ _el$3 = _el$2.nextSibling,
609
+ _el$4 = _el$3.firstChild,
610
+ _el$5 = _el$4.firstChild,
611
+ _el$6 = _el$4.nextSibling,
612
+ _el$7 = _el$3.nextSibling;
226
613
  _$setStyleProperty(_el$, "bottom", "20px");
227
614
  _$setStyleProperty(_el$, "right", "20px");
228
- _el$2.$$click = () => setLocatorActive(!locatorActive());
229
- _el$2.addEventListener("mouseleave", e => e.currentTarget.style.transform = "scale(1)");
230
- _el$2.addEventListener("mouseenter", e => e.currentTarget.style.transform = "scale(1.25)");
231
- _$setStyleProperty(_el$2, "width", "54px");
232
- _$setStyleProperty(_el$2, "height", "54px");
233
- _$setStyleProperty(_el$2, "transition", "transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out");
234
- _$setAttribute(_el$3, "src", treeIconUrl);
235
- _$setStyleProperty(_el$4, "position", "absolute");
236
- _$setStyleProperty(_el$4, "width", "1px");
237
- _$setStyleProperty(_el$4, "height", "1px");
238
- _$setStyleProperty(_el$4, "padding", "0");
239
- _$setStyleProperty(_el$4, "margin", "-1px");
615
+ _$setStyleProperty(_el$3, "display", "flex");
616
+ _$setStyleProperty(_el$3, "overflow", "hidden");
617
+ _$setStyleProperty(_el$3, "transition", "box-shadow 0.2s ease-in-out");
618
+ _el$4.$$click = () => setLocatorActive(!locatorActive());
619
+ _el$4.addEventListener("mouseleave", e => e.currentTarget.style.background = "#ffffff");
620
+ _el$4.addEventListener("mouseenter", e => e.currentTarget.style.background = "#f0f0f0");
621
+ _$setStyleProperty(_el$4, "width", "54px");
622
+ _$setStyleProperty(_el$4, "height", "54px");
623
+ _$setStyleProperty(_el$4, "background", "#ffffff");
624
+ _$setStyleProperty(_el$4, "display", "flex");
625
+ _$setStyleProperty(_el$4, "cursor", "pointer");
240
626
  _$setStyleProperty(_el$4, "overflow", "hidden");
241
- _$setStyleProperty(_el$4, "clip", "rect(0,0,0,0)");
242
- _$setStyleProperty(_el$4, "border", "0");
243
- _$effect(_$p => _$setStyleProperty(_el$2, "box-shadow", locatorActive() ? "0 0 0 3px #3b82f6, 0 4px 14px rgba(0, 0, 0, 0.25)" : "0 4px 14px rgba(0, 0, 0, 0.25)"));
627
+ _$setStyleProperty(_el$4, "transition", "background 0.15s ease-in-out");
628
+ _$setAttribute(_el$5, "src", treeIconUrl);
629
+ _el$6.$$click = handleRecordClick;
630
+ _el$6.addEventListener("mouseleave", e => {
631
+ if (recordingState() !== 'recording') e.currentTarget.style.background = "#ffffff";
632
+ });
633
+ _el$6.addEventListener("mouseenter", e => {
634
+ if (recordingState() !== 'recording') e.currentTarget.style.background = "#f0f0f0";
635
+ });
636
+ _$setStyleProperty(_el$6, "width", "54px");
637
+ _$setStyleProperty(_el$6, "height", "54px");
638
+ _$setStyleProperty(_el$6, "display", "flex");
639
+ _$setStyleProperty(_el$6, "cursor", "pointer");
640
+ _$setStyleProperty(_el$6, "transition", "background 0.15s ease-in-out");
641
+ _$insert(_el$6, (() => {
642
+ var _c$ = _$memo(() => recordingState() === 'recording');
643
+ return () => _c$() ? (() => {
644
+ var _el$9 = _tmpl$3();
645
+ _$setStyleProperty(_el$9, "width", "18px");
646
+ _$setStyleProperty(_el$9, "height", "18px");
647
+ _$setStyleProperty(_el$9, "background", "#fff");
648
+ return _el$9;
649
+ })() : (() => {
650
+ var _el$0 = _tmpl$4();
651
+ _$setStyleProperty(_el$0, "width", "18px");
652
+ _$setStyleProperty(_el$0, "height", "18px");
653
+ _$setStyleProperty(_el$0, "background", "#ef4444");
654
+ _$effect(_$p => _$setStyleProperty(_el$0, "animation", recordingState() === 'selecting' ? "treelocator-rec-pulse 1s ease-in-out infinite" : "none"));
655
+ return _el$0;
656
+ })();
657
+ })());
658
+ _$setStyleProperty(_el$7, "position", "absolute");
659
+ _$setStyleProperty(_el$7, "width", "1px");
660
+ _$setStyleProperty(_el$7, "height", "1px");
661
+ _$setStyleProperty(_el$7, "padding", "0");
662
+ _$setStyleProperty(_el$7, "margin", "-1px");
663
+ _$setStyleProperty(_el$7, "overflow", "hidden");
664
+ _$setStyleProperty(_el$7, "clip", "rect(0,0,0,0)");
665
+ _$setStyleProperty(_el$7, "border", "0");
666
+ _$effect(_p$ => {
667
+ var _v$ = locatorActive() ? "0 0 0 3px #3b82f6, 0 4px 14px rgba(0, 0, 0, 0.25)" : recordingState() === 'selecting' ? "0 0 0 3px #3b82f6, 0 4px 14px rgba(0, 0, 0, 0.25)" : recordingState() === 'recording' ? "0 0 0 3px #ef4444, 0 4px 14px rgba(0, 0, 0, 0.25)" : "0 4px 14px rgba(0, 0, 0, 0.25)",
668
+ _v$2 = recordingState() === 'recording' ? "#ef4444" : "#ffffff",
669
+ _v$3 = recordingState() === 'idle' ? "Record element changes. API: window.__treelocator__.replayWithRecord(selector)" : recordingState() === 'selecting' ? "Cancel recording selection" : recordingState() === 'recording' ? "Stop recording" : "Dismiss results";
670
+ _v$ !== _p$.e && _$setStyleProperty(_el$3, "box-shadow", _p$.e = _v$);
671
+ _v$2 !== _p$.t && _$setStyleProperty(_el$6, "background", _p$.t = _v$2);
672
+ _v$3 !== _p$.a && _$setAttribute(_el$6, "aria-label", _p$.a = _v$3);
673
+ return _p$;
674
+ }, {
675
+ e: undefined,
676
+ t: undefined,
677
+ a: undefined
678
+ });
244
679
  return _el$;
245
680
  })()];
246
681
  }