lib-e2e-cypress-for-dummys-ts 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -302,6 +302,38 @@ declare function selectTestsForExport(tests: TestWithDetails[], mode: ExportMode
302
302
  */
303
303
  declare function isLocalHost(hostname: string): boolean;
304
304
 
305
+ /**
306
+ * Geometry helpers for the draggable recording widget (spec 007).
307
+ * Pure functions — no DOM — so the drag/clamp/expansion logic is unit-testable
308
+ * without real layout (jsdom returns 0-sized rects).
309
+ */
310
+ type ExpandDirection = 'up-left' | 'up-right' | 'down-left' | 'down-right';
311
+ interface Point {
312
+ x: number;
313
+ y: number;
314
+ }
315
+ /** Toggle radius (44/2) + small gap, so the toggle never touches the edge. */
316
+ declare const TOGGLE_MARGIN = 30;
317
+ /** Pixels the pointer must travel before a press becomes a drag (vs a click). */
318
+ declare const DRAG_THRESHOLD = 5;
319
+ /**
320
+ * Toggle-centre offset inside the 190×190 widget box, per expansion direction.
321
+ * The toggle sits at the box corner nearest the screen edge so the arc always
322
+ * expands toward the viewport interior (and stays inside the box / on screen).
323
+ */
324
+ declare function boxOffsetForDirection(dir: ExpandDirection): Point;
325
+ /** Clamps the toggle-centre so the toggle button stays fully on screen. */
326
+ declare function clampTogglePosition(x: number, y: number, vw: number, vh: number, margin?: number): Point;
327
+ /**
328
+ * Chooses the radial expansion direction so the menu opens toward the centre of
329
+ * the viewport (top half → expand down; left half → expand right).
330
+ */
331
+ declare function resolveExpandDirection(x: number, y: number, vw: number, vh: number): ExpandDirection;
332
+ /** Default toggle-centre (bottom-right corner), matching the original CSS anchor. */
333
+ declare function defaultTogglePosition(vw: number, vh: number): Point;
334
+ /** Box top-left for a given (clamped) toggle-centre and direction. */
335
+ declare function boxTopLeftFor(toggleCentre: Point, dir: ExpandDirection): Point;
336
+
305
337
  declare class SelectorPickerElement extends HTMLElement {
306
338
  targetElement: HTMLElement | null;
307
339
  recording: RecordingService;
@@ -427,6 +459,7 @@ declare class ConfigurationElement extends HTMLElement {
427
459
  onAdvancedHttpConfigChange(checked: boolean): void;
428
460
  onStartHiddenChange(checked: boolean): Promise<void>;
429
461
  onResumeTtlChange(minutes: number): Promise<void>;
462
+ onResetWidgetPosition(): void;
430
463
  onSmartSelectorChange(enabled: boolean): Promise<void>;
431
464
  onSelectorStrategyChange(strategy: SelectorStrategy): Promise<void>;
432
465
  changeFolder(): Promise<void>;
@@ -549,6 +582,13 @@ declare class LibE2eRecorderElement extends HTMLElement {
549
582
  private sessionUnsub?;
550
583
  private sessionSaveTimer?;
551
584
  private controlFirstTimeData;
585
+ private togglePos;
586
+ private expandDir;
587
+ private dragState?;
588
+ private suppressNextToggleClick;
589
+ private widgetPointerMove?;
590
+ private widgetPointerUp?;
591
+ private widgetResize?;
552
592
  private _previsualizerRef;
553
593
  private httpMonitor?;
554
594
  private smartSelectorEnabled;
@@ -595,6 +635,15 @@ declare class LibE2eRecorderElement extends HTMLElement {
595
635
  resumeSession(): void;
596
636
  /** Discard the persisted live session (breadcrumb + DB record). */
597
637
  discardSession(): void;
638
+ /** Loads a previously saved widget position and applies it. */
639
+ private initWidgetPosition;
640
+ /** Positions the `.widget` box from the (clamped) toggle centre and orients the arc. */
641
+ private applyWidgetPosition;
642
+ private beginWidgetDrag;
643
+ private onWidgetPointerMove;
644
+ private onWidgetPointerUp;
645
+ /** Resets the widget to its default corner and clears the saved position. */
646
+ resetWidgetPosition(): void;
598
647
  toggle(): void;
599
648
  togglePause(): void;
600
649
  setLanguage(lang?: string): void;
@@ -623,6 +672,6 @@ declare class LibE2eRecorderElement extends HTMLElement {
623
672
  private render;
624
673
  }
625
674
 
626
- declare const VERSION = "0.5.0";
675
+ declare const VERSION = "0.6.0";
627
676
 
628
- export { ACTIVE_SESSION_BREADCRUMB_KEY, type ActiveSessionState, AdvancedTestEditorElement, AdvancedTestTransformationService, ConfigurationElement, type DBSchema, type DBStore, type DBStoreIndex, DB_SCHEMA, DB_STORE_NAMES, DEFAULT_RESUME_TTL_MINUTES, type DirectoryNode, type ExportMode, type ExportSelectionOptions, type FileNode, FilePreviewElement, HttpMonitor, INPUT_TYPES, type InputType, LIB_E2E_CYPRESS_FOR_DUMMYS_SWAL2_STYLES, LOCALE_BY_LANG, type Lang, LibE2eRecorderElement, PersistenceService, RESUME_TTL_CONFIG_KEY, RecordingService, SCROLLBAR_STYLES, SUPPORTED_LANGS, SaveTestElement, SelectorPickerElement, type SelectorStrategy, Subject, type TestDetail, TestEditorElement, TestPrevisualizerElement, type TestWithDetails, TransformationService, TranslationService, VERSION, advancedTestTransformationService, generateAlias, injectStyles, isLang, isLocalHost, localeForLang, makeModalResizable, makeSwalDraggable, makeSwalDraggableByContentId, persistenceService, selectTestsForExport, setSwal2DataCyAttribute, showToast, transformationService, translationService };
677
+ export { ACTIVE_SESSION_BREADCRUMB_KEY, type ActiveSessionState, AdvancedTestEditorElement, AdvancedTestTransformationService, ConfigurationElement, type DBSchema, type DBStore, type DBStoreIndex, DB_SCHEMA, DB_STORE_NAMES, DEFAULT_RESUME_TTL_MINUTES, DRAG_THRESHOLD, type DirectoryNode, type ExpandDirection, type ExportMode, type ExportSelectionOptions, type FileNode, FilePreviewElement, HttpMonitor, INPUT_TYPES, type InputType, LIB_E2E_CYPRESS_FOR_DUMMYS_SWAL2_STYLES, LOCALE_BY_LANG, type Lang, LibE2eRecorderElement, PersistenceService, type Point, RESUME_TTL_CONFIG_KEY, RecordingService, SCROLLBAR_STYLES, SUPPORTED_LANGS, SaveTestElement, SelectorPickerElement, type SelectorStrategy, Subject, TOGGLE_MARGIN, type TestDetail, TestEditorElement, TestPrevisualizerElement, type TestWithDetails, TransformationService, TranslationService, VERSION, advancedTestTransformationService, boxOffsetForDirection, boxTopLeftFor, clampTogglePosition, defaultTogglePosition, generateAlias, injectStyles, isLang, isLocalHost, localeForLang, makeModalResizable, makeSwalDraggable, makeSwalDraggableByContentId, persistenceService, resolveExpandDirection, selectTestsForExport, setSwal2DataCyAttribute, showToast, transformationService, translationService };
package/dist/index.d.ts CHANGED
@@ -302,6 +302,38 @@ declare function selectTestsForExport(tests: TestWithDetails[], mode: ExportMode
302
302
  */
303
303
  declare function isLocalHost(hostname: string): boolean;
304
304
 
305
+ /**
306
+ * Geometry helpers for the draggable recording widget (spec 007).
307
+ * Pure functions — no DOM — so the drag/clamp/expansion logic is unit-testable
308
+ * without real layout (jsdom returns 0-sized rects).
309
+ */
310
+ type ExpandDirection = 'up-left' | 'up-right' | 'down-left' | 'down-right';
311
+ interface Point {
312
+ x: number;
313
+ y: number;
314
+ }
315
+ /** Toggle radius (44/2) + small gap, so the toggle never touches the edge. */
316
+ declare const TOGGLE_MARGIN = 30;
317
+ /** Pixels the pointer must travel before a press becomes a drag (vs a click). */
318
+ declare const DRAG_THRESHOLD = 5;
319
+ /**
320
+ * Toggle-centre offset inside the 190×190 widget box, per expansion direction.
321
+ * The toggle sits at the box corner nearest the screen edge so the arc always
322
+ * expands toward the viewport interior (and stays inside the box / on screen).
323
+ */
324
+ declare function boxOffsetForDirection(dir: ExpandDirection): Point;
325
+ /** Clamps the toggle-centre so the toggle button stays fully on screen. */
326
+ declare function clampTogglePosition(x: number, y: number, vw: number, vh: number, margin?: number): Point;
327
+ /**
328
+ * Chooses the radial expansion direction so the menu opens toward the centre of
329
+ * the viewport (top half → expand down; left half → expand right).
330
+ */
331
+ declare function resolveExpandDirection(x: number, y: number, vw: number, vh: number): ExpandDirection;
332
+ /** Default toggle-centre (bottom-right corner), matching the original CSS anchor. */
333
+ declare function defaultTogglePosition(vw: number, vh: number): Point;
334
+ /** Box top-left for a given (clamped) toggle-centre and direction. */
335
+ declare function boxTopLeftFor(toggleCentre: Point, dir: ExpandDirection): Point;
336
+
305
337
  declare class SelectorPickerElement extends HTMLElement {
306
338
  targetElement: HTMLElement | null;
307
339
  recording: RecordingService;
@@ -427,6 +459,7 @@ declare class ConfigurationElement extends HTMLElement {
427
459
  onAdvancedHttpConfigChange(checked: boolean): void;
428
460
  onStartHiddenChange(checked: boolean): Promise<void>;
429
461
  onResumeTtlChange(minutes: number): Promise<void>;
462
+ onResetWidgetPosition(): void;
430
463
  onSmartSelectorChange(enabled: boolean): Promise<void>;
431
464
  onSelectorStrategyChange(strategy: SelectorStrategy): Promise<void>;
432
465
  changeFolder(): Promise<void>;
@@ -549,6 +582,13 @@ declare class LibE2eRecorderElement extends HTMLElement {
549
582
  private sessionUnsub?;
550
583
  private sessionSaveTimer?;
551
584
  private controlFirstTimeData;
585
+ private togglePos;
586
+ private expandDir;
587
+ private dragState?;
588
+ private suppressNextToggleClick;
589
+ private widgetPointerMove?;
590
+ private widgetPointerUp?;
591
+ private widgetResize?;
552
592
  private _previsualizerRef;
553
593
  private httpMonitor?;
554
594
  private smartSelectorEnabled;
@@ -595,6 +635,15 @@ declare class LibE2eRecorderElement extends HTMLElement {
595
635
  resumeSession(): void;
596
636
  /** Discard the persisted live session (breadcrumb + DB record). */
597
637
  discardSession(): void;
638
+ /** Loads a previously saved widget position and applies it. */
639
+ private initWidgetPosition;
640
+ /** Positions the `.widget` box from the (clamped) toggle centre and orients the arc. */
641
+ private applyWidgetPosition;
642
+ private beginWidgetDrag;
643
+ private onWidgetPointerMove;
644
+ private onWidgetPointerUp;
645
+ /** Resets the widget to its default corner and clears the saved position. */
646
+ resetWidgetPosition(): void;
598
647
  toggle(): void;
599
648
  togglePause(): void;
600
649
  setLanguage(lang?: string): void;
@@ -623,6 +672,6 @@ declare class LibE2eRecorderElement extends HTMLElement {
623
672
  private render;
624
673
  }
625
674
 
626
- declare const VERSION = "0.5.0";
675
+ declare const VERSION = "0.6.0";
627
676
 
628
- export { ACTIVE_SESSION_BREADCRUMB_KEY, type ActiveSessionState, AdvancedTestEditorElement, AdvancedTestTransformationService, ConfigurationElement, type DBSchema, type DBStore, type DBStoreIndex, DB_SCHEMA, DB_STORE_NAMES, DEFAULT_RESUME_TTL_MINUTES, type DirectoryNode, type ExportMode, type ExportSelectionOptions, type FileNode, FilePreviewElement, HttpMonitor, INPUT_TYPES, type InputType, LIB_E2E_CYPRESS_FOR_DUMMYS_SWAL2_STYLES, LOCALE_BY_LANG, type Lang, LibE2eRecorderElement, PersistenceService, RESUME_TTL_CONFIG_KEY, RecordingService, SCROLLBAR_STYLES, SUPPORTED_LANGS, SaveTestElement, SelectorPickerElement, type SelectorStrategy, Subject, type TestDetail, TestEditorElement, TestPrevisualizerElement, type TestWithDetails, TransformationService, TranslationService, VERSION, advancedTestTransformationService, generateAlias, injectStyles, isLang, isLocalHost, localeForLang, makeModalResizable, makeSwalDraggable, makeSwalDraggableByContentId, persistenceService, selectTestsForExport, setSwal2DataCyAttribute, showToast, transformationService, translationService };
677
+ export { ACTIVE_SESSION_BREADCRUMB_KEY, type ActiveSessionState, AdvancedTestEditorElement, AdvancedTestTransformationService, ConfigurationElement, type DBSchema, type DBStore, type DBStoreIndex, DB_SCHEMA, DB_STORE_NAMES, DEFAULT_RESUME_TTL_MINUTES, DRAG_THRESHOLD, type DirectoryNode, type ExpandDirection, type ExportMode, type ExportSelectionOptions, type FileNode, FilePreviewElement, HttpMonitor, INPUT_TYPES, type InputType, LIB_E2E_CYPRESS_FOR_DUMMYS_SWAL2_STYLES, LOCALE_BY_LANG, type Lang, LibE2eRecorderElement, PersistenceService, type Point, RESUME_TTL_CONFIG_KEY, RecordingService, SCROLLBAR_STYLES, SUPPORTED_LANGS, SaveTestElement, SelectorPickerElement, type SelectorStrategy, Subject, TOGGLE_MARGIN, type TestDetail, TestEditorElement, TestPrevisualizerElement, type TestWithDetails, TransformationService, TranslationService, VERSION, advancedTestTransformationService, boxOffsetForDirection, boxTopLeftFor, clampTogglePosition, defaultTogglePosition, generateAlias, injectStyles, isLang, isLocalHost, localeForLang, makeModalResizable, makeSwalDraggable, makeSwalDraggableByContentId, persistenceService, resolveExpandDirection, selectTestsForExport, setSwal2DataCyAttribute, showToast, transformationService, translationService };
package/dist/index.js CHANGED
@@ -204,7 +204,11 @@ var I18N_ES = {
204
204
  START_HIDDEN_SUB: "El widget arranca invisible. Usa Ctrl+Shift+E para mostrarlo u ocultarlo.",
205
205
  RESUME_TTL_SECTION: "\u23F1 Continuidad de grabaci\xF3n",
206
206
  RESUME_TTL_LABEL: "Reanudar sin preguntar (minutos)",
207
- RESUME_TTL_HINT: "Si recargas o cambias de proyecto, una grabaci\xF3n activa se reanuda sin preguntar dentro de este margen. Pasado ese tiempo, te preguntar\xE1 si continuar o descartar."
207
+ RESUME_TTL_HINT: "Si recargas o cambias de proyecto, una grabaci\xF3n activa se reanuda sin preguntar dentro de este margen. Pasado ese tiempo, te preguntar\xE1 si continuar o descartar.",
208
+ WIDGET_POSITION_SECTION: "\u{1F9F2} Posici\xF3n del widget",
209
+ WIDGET_POSITION_HINT: "Arrastra el bot\xF3n de grabar para moverlo donde no estorbe. Se recuerda su posici\xF3n.",
210
+ WIDGET_POSITION_RESET_BTN: "\u21BA Restablecer posici\xF3n",
211
+ WIDGET_POSITION_RESET_DONE: "\u2713 Posici\xF3n restablecida"
208
212
  },
209
213
  SELECTOR_PICKER: {
210
214
  TITLE: "Selecciona un elemento del DOM",
@@ -400,7 +404,11 @@ var I18N_EN = {
400
404
  START_HIDDEN_SUB: "The widget starts invisible. Use Ctrl+Shift+E to show or hide it.",
401
405
  RESUME_TTL_SECTION: "\u23F1 Recording continuity",
402
406
  RESUME_TTL_LABEL: "Resume without asking (minutes)",
403
- RESUME_TTL_HINT: "If you reload or switch projects, an active recording resumes without asking within this window. After that, you will be asked to continue or discard."
407
+ RESUME_TTL_HINT: "If you reload or switch projects, an active recording resumes without asking within this window. After that, you will be asked to continue or discard.",
408
+ WIDGET_POSITION_SECTION: "\u{1F9F2} Widget position",
409
+ WIDGET_POSITION_HINT: "Drag the record button to move it out of the way. Its position is remembered.",
410
+ WIDGET_POSITION_RESET_BTN: "\u21BA Reset position",
411
+ WIDGET_POSITION_RESET_DONE: "\u2713 Position reset"
404
412
  },
405
413
  SELECTOR_PICKER: {
406
414
  TITLE: "Select a DOM element",
@@ -596,7 +604,11 @@ var I18N_FR = {
596
604
  START_HIDDEN_SUB: "Le widget d\xE9marre invisible. Utilisez Ctrl+Shift+E pour l'afficher ou le masquer.",
597
605
  RESUME_TTL_SECTION: "\u23F1 Continuit\xE9 d'enregistrement",
598
606
  RESUME_TTL_LABEL: "Reprendre sans demander (minutes)",
599
- RESUME_TTL_HINT: "Si vous rechargez ou changez de projet, un enregistrement actif reprend sans demander dans ce d\xE9lai. Au-del\xE0, il vous demandera de continuer ou d'ignorer."
607
+ RESUME_TTL_HINT: "Si vous rechargez ou changez de projet, un enregistrement actif reprend sans demander dans ce d\xE9lai. Au-del\xE0, il vous demandera de continuer ou d'ignorer.",
608
+ WIDGET_POSITION_SECTION: "\u{1F9F2} Position du widget",
609
+ WIDGET_POSITION_HINT: "Fais glisser le bouton d'enregistrement pour le d\xE9placer. Sa position est m\xE9moris\xE9e.",
610
+ WIDGET_POSITION_RESET_BTN: "\u21BA R\xE9initialiser la position",
611
+ WIDGET_POSITION_RESET_DONE: "\u2713 Position r\xE9initialis\xE9e"
600
612
  },
601
613
  SELECTOR_PICKER: {
602
614
  TITLE: "S\xE9lectionnez un \xE9l\xE9ment du DOM",
@@ -792,7 +804,11 @@ var I18N_IT = {
792
804
  START_HIDDEN_SUB: "Il widget si avvia invisibile. Usa Ctrl+Shift+E per mostrarlo o nasconderlo.",
793
805
  RESUME_TTL_SECTION: "\u23F1 Continuit\xE0 di registrazione",
794
806
  RESUME_TTL_LABEL: "Riprendi senza chiedere (minuti)",
795
- RESUME_TTL_HINT: "Se ricarichi o cambi progetto, una registrazione attiva riprende senza chiedere entro questo intervallo. Trascorso, ti chieder\xE0 se continuare o annullare."
807
+ RESUME_TTL_HINT: "Se ricarichi o cambi progetto, una registrazione attiva riprende senza chiedere entro questo intervallo. Trascorso, ti chieder\xE0 se continuare o annullare.",
808
+ WIDGET_POSITION_SECTION: "\u{1F9F2} Posizione del widget",
809
+ WIDGET_POSITION_HINT: "Trascina il pulsante di registrazione per spostarlo. La posizione viene ricordata.",
810
+ WIDGET_POSITION_RESET_BTN: "\u21BA Reimposta posizione",
811
+ WIDGET_POSITION_RESET_DONE: "\u2713 Posizione reimpostata"
796
812
  },
797
813
  SELECTOR_PICKER: {
798
814
  TITLE: "Seleziona un elemento DOM",
@@ -988,7 +1004,11 @@ var I18N_DE = {
988
1004
  START_HIDDEN_SUB: "Das Widget startet unsichtbar. Verwenden Sie Ctrl+Shift+E zum Ein-/Ausblenden.",
989
1005
  RESUME_TTL_SECTION: "\u23F1 Aufzeichnungskontinuit\xE4t",
990
1006
  RESUME_TTL_LABEL: "Ohne Nachfrage fortsetzen (Minuten)",
991
- RESUME_TTL_HINT: "Wenn Sie neu laden oder das Projekt wechseln, wird eine aktive Aufzeichnung innerhalb dieses Zeitfensters ohne Nachfrage fortgesetzt. Danach werden Sie gefragt, ob fortsetzen oder verwerfen."
1007
+ RESUME_TTL_HINT: "Wenn Sie neu laden oder das Projekt wechseln, wird eine aktive Aufzeichnung innerhalb dieses Zeitfensters ohne Nachfrage fortgesetzt. Danach werden Sie gefragt, ob fortsetzen oder verwerfen.",
1008
+ WIDGET_POSITION_SECTION: "\u{1F9F2} Widget-Position",
1009
+ WIDGET_POSITION_HINT: "Ziehe den Aufnahme-Button, um ihn aus dem Weg zu schieben. Die Position wird gespeichert.",
1010
+ WIDGET_POSITION_RESET_BTN: "\u21BA Position zur\xFCcksetzen",
1011
+ WIDGET_POSITION_RESET_DONE: "\u2713 Position zur\xFCckgesetzt"
992
1012
  },
993
1013
  SELECTOR_PICKER: {
994
1014
  TITLE: "DOM-Element ausw\xE4hlen",
@@ -2258,6 +2278,37 @@ function isLocalHost(hostname) {
2258
2278
  return h === "localhost" || h.endsWith(".localhost") || h === "127.0.0.1" || h === "::1" || h === "0.0.0.0";
2259
2279
  }
2260
2280
 
2281
+ // src/utils/widget-position.utils.ts
2282
+ var TOGGLE_MARGIN = 30;
2283
+ var DRAG_THRESHOLD = 5;
2284
+ var DEFAULT_EDGE_OFFSET = 46;
2285
+ function boxOffsetForDirection(dir) {
2286
+ return {
2287
+ x: dir.endsWith("left") ? 144 : 46,
2288
+ y: dir.startsWith("up") ? 144 : 46
2289
+ };
2290
+ }
2291
+ function clampTogglePosition(x, y, vw, vh, margin = TOGGLE_MARGIN) {
2292
+ const maxX = Math.max(margin, vw - margin);
2293
+ const maxY = Math.max(margin, vh - margin);
2294
+ return {
2295
+ x: Math.min(Math.max(x, margin), maxX),
2296
+ y: Math.min(Math.max(y, margin), maxY)
2297
+ };
2298
+ }
2299
+ function resolveExpandDirection(x, y, vw, vh) {
2300
+ const vertical = y < vh / 2 ? "down" : "up";
2301
+ const horizontal = x < vw / 2 ? "right" : "left";
2302
+ return `${vertical}-${horizontal}`;
2303
+ }
2304
+ function defaultTogglePosition(vw, vh) {
2305
+ return { x: vw - DEFAULT_EDGE_OFFSET, y: vh - DEFAULT_EDGE_OFFSET };
2306
+ }
2307
+ function boxTopLeftFor(toggleCentre, dir) {
2308
+ const off = boxOffsetForDirection(dir);
2309
+ return { x: toggleCentre.x - off.x, y: toggleCentre.y - off.y };
2310
+ }
2311
+
2261
2312
  // src/components/test-previsualizer/test-previsualizer.styles.ts
2262
2313
  var TEST_PREVISUALIZER_STYLES = `
2263
2314
  :host { display: flex; flex-direction: column; flex: 1; min-height: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; color: #e6edf3; overflow: hidden; }
@@ -3358,6 +3409,15 @@ function renderConfiguration(state, t) {
3358
3409
  </label>
3359
3410
  </div>
3360
3411
 
3412
+ <!-- Widget position (draggable widget) -->
3413
+ <div class="card">
3414
+ <div class="card-hd">${t("CONFIG.WIDGET_POSITION_SECTION")}</div>
3415
+ <div class="check-sub" style="margin-bottom:10px">${t("CONFIG.WIDGET_POSITION_HINT")}</div>
3416
+ <div class="btn-row">
3417
+ <button id="btn-reset-position">${t("CONFIG.WIDGET_POSITION_RESET_BTN")}</button>
3418
+ </div>
3419
+ </div>
3420
+
3361
3421
  <!-- Recording continuity (cross-app resume TTL) -->
3362
3422
  <div class="card">
3363
3423
  <div class="card-hd">${t("CONFIG.RESUME_TTL_SECTION")}</div>
@@ -3498,6 +3558,10 @@ var ConfigurationElement = class extends HTMLElement {
3498
3558
  await this.persistence.setConfig({ [RESUME_TTL_CONFIG_KEY]: safe });
3499
3559
  this.render();
3500
3560
  }
3561
+ onResetWidgetPosition() {
3562
+ this.dispatchEvent(new CustomEvent("resetwidgetposition", { bubbles: true, composed: true }));
3563
+ showToast(this.t("CONFIG.WIDGET_POSITION_RESET_DONE"));
3564
+ }
3501
3565
  async onSmartSelectorChange(enabled) {
3502
3566
  this.smartSelectorEnabled = enabled;
3503
3567
  await this.persistence.setConfig({ smartSelectorEnabled: enabled ? "true" : "false" });
@@ -3633,6 +3697,7 @@ var ConfigurationElement = class extends HTMLElement {
3633
3697
  "change",
3634
3698
  (e) => this.onResumeTtlChange(Number(e.target.value))
3635
3699
  );
3700
+ this.shadow.getElementById("btn-reset-position")?.addEventListener("click", () => this.onResetWidgetPosition());
3636
3701
  this.shadow.getElementById("selector-strategy").addEventListener(
3637
3702
  "change",
3638
3703
  (e) => this.onSelectorStrategyChange(e.target.value)
@@ -4595,15 +4660,37 @@ if (!customElements.get("file-preview")) {
4595
4660
  import Swal from "sweetalert2";
4596
4661
 
4597
4662
  // src/components/lib-e2e-recorder/lib-e2e-recorder.styles.ts
4663
+ var DIRECTIONS = ["up-left", "up-right", "down-left", "down-right"];
4664
+ function directionBlocks() {
4665
+ return DIRECTIONS.map((dir) => {
4666
+ const sx = dir.endsWith("left") ? -1 : 1;
4667
+ const sy = dir.startsWith("up") ? -1 : 1;
4668
+ const tv = dir.startsWith("up") ? "bottom" : "top";
4669
+ const th = dir.endsWith("left") ? "right" : "left";
4670
+ const ov = tv === "bottom" ? "top" : "bottom";
4671
+ const oh = th === "right" ? "left" : "right";
4672
+ const sel = `.widget[data-expand="${dir}"]`;
4673
+ const labelGeneral = `${sel} .btn-action::after { ${oh}: calc(100% + 9px); ${th}: auto; top: 50%; bottom: auto; transform: translateY(-50%); }`;
4674
+ const labelEnds = `${sel} .btn-action[data-n="1"]::after, ${sel} .btn-action[data-n="4"]::after { ${ov}: calc(100% + 9px); ${tv}: auto; left: 50%; right: auto; transform: translateX(-50%); }`;
4675
+ return `
4676
+ ${sel} { --sx: ${sx}; --sy: ${sy}; }
4677
+ ${sel} .btn-toggle { ${tv}: 24px; ${th}: 24px; ${ov}: auto; ${oh}: auto; }
4678
+ ${sel} .btn-pause { ${tv}: 78px; ${th}: 24px; ${ov}: auto; ${oh}: auto; }
4679
+ ${sel} .btn-action { ${tv}: 28px; ${th}: 28px; ${ov}: auto; ${oh}: auto; }
4680
+ ${labelGeneral}
4681
+ ${labelEnds}
4682
+ `;
4683
+ }).join("\n");
4684
+ }
4598
4685
  function getRecorderStyles(rec, paused) {
4599
4686
  return `
4600
4687
  :host { all: initial; }
4601
4688
  *, *::before, *::after { box-sizing: border-box; }
4602
4689
 
4603
4690
  /*
4604
- * Invisible 190\xD7190 hit area anchored at bottom-right.
4605
- * Keeps :hover alive while the cursor travels from the
4606
- * toggle to any of the radial action buttons.
4691
+ * Invisible 190\xD7190 hit area. Position (left/top) is set from JS so the
4692
+ * widget can be dragged; --sx/--sy + data-expand orient the radial menu so
4693
+ * it always expands toward the viewport interior. Defaults to bottom-right.
4607
4694
  */
4608
4695
  .widget {
4609
4696
  position: fixed;
@@ -4612,6 +4699,8 @@ function getRecorderStyles(rec, paused) {
4612
4699
  width: 190px;
4613
4700
  height: 190px;
4614
4701
  z-index: 2147483647;
4702
+ --sx: -1;
4703
+ --sy: -1;
4615
4704
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
4616
4705
  }
4617
4706
 
@@ -4624,7 +4713,8 @@ function getRecorderStyles(rec, paused) {
4624
4713
  height: 44px;
4625
4714
  border-radius: 50%;
4626
4715
  border: none;
4627
- cursor: pointer;
4716
+ cursor: grab;
4717
+ touch-action: none;
4628
4718
  font-size: 19px;
4629
4719
  background: ${rec ? "linear-gradient(135deg,#f85149 0%,#da3633 100%)" : "linear-gradient(135deg,#2f81f7 0%,#1f6feb 100%)"};
4630
4720
  color: #fff;
@@ -4637,7 +4727,7 @@ function getRecorderStyles(rec, paused) {
4637
4727
  ${rec ? "animation: toggle-pulse 2s ease-in-out infinite;" : ""}
4638
4728
  }
4639
4729
  .btn-toggle:hover { transform: scale(1.1); }
4640
- .btn-toggle:active { transform: scale(0.93); }
4730
+ .btn-toggle:active { transform: scale(0.93); cursor: grabbing; }
4641
4731
 
4642
4732
  @keyframes toggle-pulse {
4643
4733
  0%,100% { box-shadow: 0 4px 20px rgba(248,81,73,.55),0 0 0 4px rgba(248,81,73,.13); }
@@ -4668,11 +4758,6 @@ function getRecorderStyles(rec, paused) {
4668
4758
  .btn-pause:active { transform: scale(0.93); }
4669
4759
 
4670
4760
  /* \u2500\u2500 Action buttons \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
4671
- /*
4672
- * All three start centered on the toggle button
4673
- * (toggle center = bottom:46px right:46px from widget edge;
4674
- * button half = 18px \u2192 bottom:28px right:28px).
4675
- */
4676
4761
  .btn-action {
4677
4762
  position: absolute;
4678
4763
  bottom: 28px;
@@ -4694,12 +4779,11 @@ function getRecorderStyles(rec, paused) {
4694
4779
  opacity: 0;
4695
4780
  transform: scale(0.35);
4696
4781
  pointer-events: none;
4697
- /* Collapse: fast, no spring */
4698
4782
  transition: opacity .15s, transform .18s ease-in,
4699
4783
  background .15s, color .12s, box-shadow .15s;
4700
4784
  }
4701
4785
 
4702
- /* Label to the left of each button */
4786
+ /* Label chip */
4703
4787
  .btn-action::after {
4704
4788
  content: attr(data-label);
4705
4789
  position: absolute;
@@ -4719,14 +4803,6 @@ function getRecorderStyles(rec, paused) {
4719
4803
  border: 1px solid rgba(48,54,61,.8);
4720
4804
  box-shadow: 0 2px 8px rgba(0,0,0,.35);
4721
4805
  }
4722
- /* Button 1 (top) \u2014 label above instead of left to avoid overlap with btn 2 */
4723
- .btn-action[data-n="1"]::after {
4724
- right: auto;
4725
- left: 50%;
4726
- top: auto;
4727
- bottom: calc(100% + 9px);
4728
- transform: translateX(-50%);
4729
- }
4730
4806
  .btn-action:hover::after { opacity: 1; }
4731
4807
  .btn-action:hover { background: #21262d; color: #e6edf3; }
4732
4808
  .btn-action:active { background: #30363d !important; }
@@ -4739,31 +4815,26 @@ function getRecorderStyles(rec, paused) {
4739
4815
  background .15s, color .12s, box-shadow .15s;
4740
4816
  }
4741
4817
 
4742
- /* Arc positions \u2014 4 buttons, 30\xB0 spacing, radius 90px */
4743
- .widget:hover .btn-action[data-n="1"] { /* 0\xB0 \u2014 straight up */
4744
- transform: translateY(-90px) scale(1);
4818
+ /* Arc positions \u2014 signs come from --sx/--sy (data-expand) */
4819
+ .widget:hover .btn-action[data-n="1"] { /* vertical extreme */
4820
+ transform: translateY(calc(var(--sy) * 90px)) scale(1);
4745
4821
  transition-delay: .03s;
4746
4822
  }
4747
- .widget:hover .btn-action[data-n="2"] { /* 30\xB0 \u2014 upper-left */
4748
- transform: translate(-45px,-78px) scale(1);
4823
+ .widget:hover .btn-action[data-n="2"] { /* diagonal near-vertical */
4824
+ transform: translate(calc(var(--sx) * 45px), calc(var(--sy) * 78px)) scale(1);
4749
4825
  transition-delay: .07s;
4750
4826
  }
4751
- .widget:hover .btn-action[data-n="3"] { /* 60\xB0 \u2014 left-upper */
4752
- transform: translate(-78px,-45px) scale(1);
4827
+ .widget:hover .btn-action[data-n="3"] { /* diagonal near-horizontal */
4828
+ transform: translate(calc(var(--sx) * 78px), calc(var(--sy) * 45px)) scale(1);
4753
4829
  transition-delay: .11s;
4754
4830
  }
4755
- .widget:hover .btn-action[data-n="4"] { /* 90\xB0 \u2014 straight left */
4756
- transform: translateX(-90px) scale(1);
4831
+ .widget:hover .btn-action[data-n="4"] { /* horizontal extreme */
4832
+ transform: translateX(calc(var(--sx) * 90px)) scale(1);
4757
4833
  transition-delay: .15s;
4758
4834
  }
4759
- /* Button 4 (pure left) \u2014 label above to avoid going off-screen */
4760
- .btn-action[data-n="4"]::after {
4761
- right: auto;
4762
- left: 50%;
4763
- top: auto;
4764
- bottom: calc(100% + 9px);
4765
- transform: translateX(-50%);
4766
- }
4835
+
4836
+ /* Per-direction anchors + label sides */
4837
+ ${directionBlocks()}
4767
4838
 
4768
4839
  /* \u2500\u2500 REC / PAUSED badge \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
4769
4840
  .rec-badge {
@@ -4830,6 +4901,14 @@ var LibE2eRecorderElement = class extends HTMLElement {
4830
4901
  sessionUnsub;
4831
4902
  sessionSaveTimer;
4832
4903
  controlFirstTimeData = true;
4904
+ // ── draggable widget (spec 007) ──
4905
+ togglePos = null;
4906
+ expandDir = "up-left";
4907
+ dragState;
4908
+ suppressNextToggleClick = false;
4909
+ widgetPointerMove;
4910
+ widgetPointerUp;
4911
+ widgetResize;
4833
4912
  _previsualizerRef = null;
4834
4913
  httpMonitor;
4835
4914
  smartSelectorEnabled = true;
@@ -4873,8 +4952,15 @@ var LibE2eRecorderElement = class extends HTMLElement {
4873
4952
  this.render();
4874
4953
  this.initVisibility();
4875
4954
  this.initSessionContinuity();
4955
+ this.initWidgetPosition();
4876
4956
  this.keydownHandler = (e) => this.handleKeyboardEvent(e);
4877
4957
  window.addEventListener("keydown", this.keydownHandler);
4958
+ this.widgetPointerMove = (e) => this.onWidgetPointerMove(e);
4959
+ this.widgetPointerUp = () => this.onWidgetPointerUp();
4960
+ this.widgetResize = () => this.applyWidgetPosition();
4961
+ window.addEventListener("pointermove", this.widgetPointerMove);
4962
+ window.addEventListener("pointerup", this.widgetPointerUp);
4963
+ window.addEventListener("resize", this.widgetResize);
4878
4964
  }
4879
4965
  disconnectedCallback() {
4880
4966
  if (this._isDisabled) return;
@@ -4887,6 +4973,9 @@ var LibE2eRecorderElement = class extends HTMLElement {
4887
4973
  this.selectorNotFoundUnsub?.();
4888
4974
  this.langUnsub?.();
4889
4975
  this.sessionUnsub?.();
4976
+ if (this.widgetPointerMove) window.removeEventListener("pointermove", this.widgetPointerMove);
4977
+ if (this.widgetPointerUp) window.removeEventListener("pointerup", this.widgetPointerUp);
4978
+ if (this.widgetResize) window.removeEventListener("resize", this.widgetResize);
4890
4979
  this.httpMonitor?.uninstall();
4891
4980
  this.recording.destroy();
4892
4981
  }
@@ -5073,6 +5162,63 @@ var LibE2eRecorderElement = class extends HTMLElement {
5073
5162
  discardSession() {
5074
5163
  this.clearSessionPersistence();
5075
5164
  }
5165
+ // ── draggable widget (spec 007) ────────────────────────────────────────────
5166
+ /** Loads a previously saved widget position and applies it. */
5167
+ async initWidgetPosition() {
5168
+ const config = await this.persistence.getGeneralConfig();
5169
+ const pos = config?.["widgetPosition"];
5170
+ if (pos && typeof pos.x === "number" && typeof pos.y === "number") {
5171
+ this.togglePos = { x: pos.x, y: pos.y };
5172
+ }
5173
+ this.applyWidgetPosition();
5174
+ }
5175
+ /** Positions the `.widget` box from the (clamped) toggle centre and orients the arc. */
5176
+ applyWidgetPosition() {
5177
+ const widget = this.shadow.querySelector(".widget");
5178
+ if (!widget) return;
5179
+ const vw = window.innerWidth;
5180
+ const vh = window.innerHeight;
5181
+ const centre = this.togglePos ?? defaultTogglePosition(vw, vh);
5182
+ const clamped = clampTogglePosition(centre.x, centre.y, vw, vh);
5183
+ this.expandDir = resolveExpandDirection(clamped.x, clamped.y, vw, vh);
5184
+ const topLeft = boxTopLeftFor(clamped, this.expandDir);
5185
+ widget.style.left = `${topLeft.x}px`;
5186
+ widget.style.top = `${topLeft.y}px`;
5187
+ widget.style.right = "auto";
5188
+ widget.style.bottom = "auto";
5189
+ widget.setAttribute("data-expand", this.expandDir);
5190
+ }
5191
+ beginWidgetDrag(e) {
5192
+ const origin = this.togglePos ?? defaultTogglePosition(window.innerWidth, window.innerHeight);
5193
+ this.dragState = { startX: e.clientX, startY: e.clientY, origX: origin.x, origY: origin.y, moved: false };
5194
+ }
5195
+ onWidgetPointerMove(e) {
5196
+ if (!this.dragState) return;
5197
+ const dx = e.clientX - this.dragState.startX;
5198
+ const dy = e.clientY - this.dragState.startY;
5199
+ if (!this.dragState.moved && Math.hypot(dx, dy) < DRAG_THRESHOLD) return;
5200
+ this.dragState.moved = true;
5201
+ this.togglePos = { x: this.dragState.origX + dx, y: this.dragState.origY + dy };
5202
+ this.applyWidgetPosition();
5203
+ }
5204
+ onWidgetPointerUp() {
5205
+ if (!this.dragState) return;
5206
+ const moved = this.dragState.moved;
5207
+ this.dragState = void 0;
5208
+ if (!moved || !this.togglePos) return;
5209
+ this.suppressNextToggleClick = true;
5210
+ const clamped = clampTogglePosition(this.togglePos.x, this.togglePos.y, window.innerWidth, window.innerHeight);
5211
+ this.togglePos = clamped;
5212
+ this.persistence.setConfig({ widgetPosition: clamped }).catch(() => {
5213
+ });
5214
+ }
5215
+ /** Resets the widget to its default corner and clears the saved position. */
5216
+ resetWidgetPosition() {
5217
+ this.togglePos = null;
5218
+ this.persistence.setConfig({ widgetPosition: null }).catch(() => {
5219
+ });
5220
+ this.applyWidgetPosition();
5221
+ }
5076
5222
  toggle() {
5077
5223
  this.recording.toggleRecording();
5078
5224
  }
@@ -5389,6 +5535,7 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
5389
5535
  this.isVisible = !e.detail;
5390
5536
  this.style.display = this.isVisible ? "" : "none";
5391
5537
  });
5538
+ child.addEventListener("resetwidgetposition", () => this.resetWidgetPosition());
5392
5539
  },
5393
5540
  willClose: () => {
5394
5541
  this.isSettingsDialogOpen = false;
@@ -5542,12 +5689,21 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
5542
5689
  const paused = this.isPaused;
5543
5690
  this.style.display = this.isVisible ? "" : "none";
5544
5691
  this.shadow.innerHTML = `<style>${getRecorderStyles(rec, paused)}</style>${renderRecorderWidget(rec, paused, this.translation.translate.bind(this.translation))}`;
5545
- this.shadow.querySelector('[data-action="toggle"]')?.addEventListener("click", () => this.toggle());
5692
+ const toggleBtn = this.shadow.querySelector('[data-action="toggle"]');
5693
+ toggleBtn?.addEventListener("click", () => {
5694
+ if (this.suppressNextToggleClick) {
5695
+ this.suppressNextToggleClick = false;
5696
+ return;
5697
+ }
5698
+ this.toggle();
5699
+ });
5700
+ toggleBtn?.addEventListener("pointerdown", (e) => this.beginWidgetDrag(e));
5546
5701
  this.shadow.querySelector('[data-action="pause"]')?.addEventListener("click", () => this.togglePause());
5547
5702
  this.shadow.querySelector('[data-action="tests"]')?.addEventListener("click", () => this.showSavedTestsDialog());
5548
5703
  this.shadow.querySelector('[data-action="commands"]')?.addEventListener("click", () => this.showCommandsDialog());
5549
5704
  this.shadow.querySelector('[data-action="config"]')?.addEventListener("click", () => this.showSettingsDialog());
5550
5705
  this.shadow.querySelector('[data-action="browse"]')?.addEventListener("click", () => this.showAdvancedEditorDialog());
5706
+ this.applyWidgetPosition();
5551
5707
  }
5552
5708
  };
5553
5709
  if (!customElements.get("lib-e2e-recorder")) {
@@ -5555,7 +5711,7 @@ if (!customElements.get("lib-e2e-recorder")) {
5555
5711
  }
5556
5712
 
5557
5713
  // src/index.ts
5558
- var VERSION = "0.5.0";
5714
+ var VERSION = "0.6.0";
5559
5715
  export {
5560
5716
  ACTIVE_SESSION_BREADCRUMB_KEY,
5561
5717
  AdvancedTestEditorElement,
@@ -5564,6 +5720,7 @@ export {
5564
5720
  DB_SCHEMA,
5565
5721
  DB_STORE_NAMES,
5566
5722
  DEFAULT_RESUME_TTL_MINUTES,
5723
+ DRAG_THRESHOLD,
5567
5724
  FilePreviewElement,
5568
5725
  HttpMonitor,
5569
5726
  INPUT_TYPES,
@@ -5578,12 +5735,17 @@ export {
5578
5735
  SaveTestElement,
5579
5736
  SelectorPickerElement,
5580
5737
  Subject,
5738
+ TOGGLE_MARGIN,
5581
5739
  TestEditorElement,
5582
5740
  TestPrevisualizerElement,
5583
5741
  TransformationService,
5584
5742
  TranslationService,
5585
5743
  VERSION,
5586
5744
  advancedTestTransformationService,
5745
+ boxOffsetForDirection,
5746
+ boxTopLeftFor,
5747
+ clampTogglePosition,
5748
+ defaultTogglePosition,
5587
5749
  generateAlias,
5588
5750
  injectStyles,
5589
5751
  isLang,
@@ -5593,6 +5755,7 @@ export {
5593
5755
  makeSwalDraggable,
5594
5756
  makeSwalDraggableByContentId,
5595
5757
  persistenceService,
5758
+ resolveExpandDirection,
5596
5759
  selectTestsForExport,
5597
5760
  setSwal2DataCyAttribute,
5598
5761
  showToast,