lib-e2e-cypress-for-dummys-ts 0.5.0 → 0.6.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/README.md CHANGED
@@ -361,6 +361,23 @@ Stopping a recording (save **or** discard) ends the session, so a finished recor
361
361
 
362
362
  ---
363
363
 
364
+ ### Moving the widget (draggable)
365
+
366
+ If the floating widget covers a control you need, **drag the record button (⏺/⏹)**
367
+ to move the whole widget anywhere on screen. Press-and-move to drag; a plain click
368
+ still starts/stops recording (a ~5 px threshold tells the two apart).
369
+
370
+ The radial menu **expands toward the centre of the screen**, so the action buttons
371
+ stay fully visible wherever you drop it, and the position is **remembered** across
372
+ reloads (and app crossings). Reset it to the default corner from **⚙️ Config →
373
+ 🧲 Widget position → Reset position**, or programmatically:
374
+
375
+ ```typescript
376
+ recorder.resetWidgetPosition();
377
+ ```
378
+
379
+ ---
380
+
364
381
  ### Keyboard shortcuts
365
382
 
366
383
  | Shortcut | Action |
@@ -535,6 +552,9 @@ class LibE2eRecorderElement extends HTMLElement {
535
552
  hasActiveSession(): boolean; // is a recording session persisted/active?
536
553
  resumeSession(): void; // rehydrate the persisted session
537
554
  discardSession(): void; // drop the persisted session
555
+
556
+ // Draggable widget
557
+ resetWidgetPosition(): void; // move the widget back to its default corner
538
558
  }
539
559
  ```
540
560
 
package/dist/index.cjs CHANGED
@@ -467,6 +467,7 @@ __export(index_exports, {
467
467
  DB_SCHEMA: () => DB_SCHEMA,
468
468
  DB_STORE_NAMES: () => DB_STORE_NAMES,
469
469
  DEFAULT_RESUME_TTL_MINUTES: () => DEFAULT_RESUME_TTL_MINUTES,
470
+ DRAG_THRESHOLD: () => DRAG_THRESHOLD,
470
471
  FilePreviewElement: () => FilePreviewElement,
471
472
  HttpMonitor: () => HttpMonitor,
472
473
  INPUT_TYPES: () => INPUT_TYPES,
@@ -481,12 +482,17 @@ __export(index_exports, {
481
482
  SaveTestElement: () => SaveTestElement,
482
483
  SelectorPickerElement: () => SelectorPickerElement,
483
484
  Subject: () => Subject,
485
+ TOGGLE_MARGIN: () => TOGGLE_MARGIN,
484
486
  TestEditorElement: () => TestEditorElement,
485
487
  TestPrevisualizerElement: () => TestPrevisualizerElement,
486
488
  TransformationService: () => TransformationService,
487
489
  TranslationService: () => TranslationService,
488
490
  VERSION: () => VERSION,
489
491
  advancedTestTransformationService: () => advancedTestTransformationService,
492
+ boxOffsetForDirection: () => boxOffsetForDirection,
493
+ boxTopLeftFor: () => boxTopLeftFor,
494
+ clampTogglePosition: () => clampTogglePosition,
495
+ defaultTogglePosition: () => defaultTogglePosition,
490
496
  generateAlias: () => generateAlias,
491
497
  injectStyles: () => injectStyles,
492
498
  isLang: () => isLang,
@@ -496,6 +502,7 @@ __export(index_exports, {
496
502
  makeSwalDraggable: () => makeSwalDraggable,
497
503
  makeSwalDraggableByContentId: () => makeSwalDraggableByContentId,
498
504
  persistenceService: () => persistenceService,
505
+ resolveExpandDirection: () => resolveExpandDirection,
499
506
  selectTestsForExport: () => selectTestsForExport,
500
507
  setSwal2DataCyAttribute: () => setSwal2DataCyAttribute,
501
508
  showToast: () => showToast,
@@ -703,7 +710,11 @@ var I18N_ES = {
703
710
  START_HIDDEN_SUB: "El widget arranca invisible. Usa Ctrl+Shift+E para mostrarlo u ocultarlo.",
704
711
  RESUME_TTL_SECTION: "\u23F1 Continuidad de grabaci\xF3n",
705
712
  RESUME_TTL_LABEL: "Reanudar sin preguntar (minutos)",
706
- 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."
713
+ 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.",
714
+ WIDGET_POSITION_SECTION: "\u{1F9F2} Posici\xF3n del widget",
715
+ WIDGET_POSITION_HINT: "Arrastra el bot\xF3n de grabar para moverlo donde no estorbe. Se recuerda su posici\xF3n.",
716
+ WIDGET_POSITION_RESET_BTN: "\u21BA Restablecer posici\xF3n",
717
+ WIDGET_POSITION_RESET_DONE: "\u2713 Posici\xF3n restablecida"
707
718
  },
708
719
  SELECTOR_PICKER: {
709
720
  TITLE: "Selecciona un elemento del DOM",
@@ -899,7 +910,11 @@ var I18N_EN = {
899
910
  START_HIDDEN_SUB: "The widget starts invisible. Use Ctrl+Shift+E to show or hide it.",
900
911
  RESUME_TTL_SECTION: "\u23F1 Recording continuity",
901
912
  RESUME_TTL_LABEL: "Resume without asking (minutes)",
902
- 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."
913
+ 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.",
914
+ WIDGET_POSITION_SECTION: "\u{1F9F2} Widget position",
915
+ WIDGET_POSITION_HINT: "Drag the record button to move it out of the way. Its position is remembered.",
916
+ WIDGET_POSITION_RESET_BTN: "\u21BA Reset position",
917
+ WIDGET_POSITION_RESET_DONE: "\u2713 Position reset"
903
918
  },
904
919
  SELECTOR_PICKER: {
905
920
  TITLE: "Select a DOM element",
@@ -1095,7 +1110,11 @@ var I18N_FR = {
1095
1110
  START_HIDDEN_SUB: "Le widget d\xE9marre invisible. Utilisez Ctrl+Shift+E pour l'afficher ou le masquer.",
1096
1111
  RESUME_TTL_SECTION: "\u23F1 Continuit\xE9 d'enregistrement",
1097
1112
  RESUME_TTL_LABEL: "Reprendre sans demander (minutes)",
1098
- 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."
1113
+ 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.",
1114
+ WIDGET_POSITION_SECTION: "\u{1F9F2} Position du widget",
1115
+ WIDGET_POSITION_HINT: "Fais glisser le bouton d'enregistrement pour le d\xE9placer. Sa position est m\xE9moris\xE9e.",
1116
+ WIDGET_POSITION_RESET_BTN: "\u21BA R\xE9initialiser la position",
1117
+ WIDGET_POSITION_RESET_DONE: "\u2713 Position r\xE9initialis\xE9e"
1099
1118
  },
1100
1119
  SELECTOR_PICKER: {
1101
1120
  TITLE: "S\xE9lectionnez un \xE9l\xE9ment du DOM",
@@ -1291,7 +1310,11 @@ var I18N_IT = {
1291
1310
  START_HIDDEN_SUB: "Il widget si avvia invisibile. Usa Ctrl+Shift+E per mostrarlo o nasconderlo.",
1292
1311
  RESUME_TTL_SECTION: "\u23F1 Continuit\xE0 di registrazione",
1293
1312
  RESUME_TTL_LABEL: "Riprendi senza chiedere (minuti)",
1294
- 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."
1313
+ 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.",
1314
+ WIDGET_POSITION_SECTION: "\u{1F9F2} Posizione del widget",
1315
+ WIDGET_POSITION_HINT: "Trascina il pulsante di registrazione per spostarlo. La posizione viene ricordata.",
1316
+ WIDGET_POSITION_RESET_BTN: "\u21BA Reimposta posizione",
1317
+ WIDGET_POSITION_RESET_DONE: "\u2713 Posizione reimpostata"
1295
1318
  },
1296
1319
  SELECTOR_PICKER: {
1297
1320
  TITLE: "Seleziona un elemento DOM",
@@ -1487,7 +1510,11 @@ var I18N_DE = {
1487
1510
  START_HIDDEN_SUB: "Das Widget startet unsichtbar. Verwenden Sie Ctrl+Shift+E zum Ein-/Ausblenden.",
1488
1511
  RESUME_TTL_SECTION: "\u23F1 Aufzeichnungskontinuit\xE4t",
1489
1512
  RESUME_TTL_LABEL: "Ohne Nachfrage fortsetzen (Minuten)",
1490
- 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."
1513
+ 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.",
1514
+ WIDGET_POSITION_SECTION: "\u{1F9F2} Widget-Position",
1515
+ WIDGET_POSITION_HINT: "Ziehe den Aufnahme-Button, um ihn aus dem Weg zu schieben. Die Position wird gespeichert.",
1516
+ WIDGET_POSITION_RESET_BTN: "\u21BA Position zur\xFCcksetzen",
1517
+ WIDGET_POSITION_RESET_DONE: "\u2713 Position zur\xFCckgesetzt"
1491
1518
  },
1492
1519
  SELECTOR_PICKER: {
1493
1520
  TITLE: "DOM-Element ausw\xE4hlen",
@@ -1840,7 +1867,7 @@ var RecordingService = class {
1840
1867
  this.interceptors$.next([...ints.slice(0, index), ...ints.slice(index + 1)]);
1841
1868
  }
1842
1869
  registerInterceptor(method, url, alias) {
1843
- if (this.isPaused$.getValue()) return;
1870
+ if (!this.isRecording$.getValue() || this.isPaused$.getValue()) return;
1844
1871
  const command = `cy.intercept('${method}', '${this.urlToWildcard(url, method)}').as('${alias}')`;
1845
1872
  const current = this.interceptors$.getValue();
1846
1873
  if (!current.includes(command)) {
@@ -2758,6 +2785,37 @@ function isLocalHost(hostname) {
2758
2785
  return h === "localhost" || h.endsWith(".localhost") || h === "127.0.0.1" || h === "::1" || h === "0.0.0.0";
2759
2786
  }
2760
2787
 
2788
+ // src/utils/widget-position.utils.ts
2789
+ var TOGGLE_MARGIN = 30;
2790
+ var DRAG_THRESHOLD = 5;
2791
+ var DEFAULT_EDGE_OFFSET = 46;
2792
+ function boxOffsetForDirection(dir) {
2793
+ return {
2794
+ x: dir.endsWith("left") ? 144 : 46,
2795
+ y: dir.startsWith("up") ? 144 : 46
2796
+ };
2797
+ }
2798
+ function clampTogglePosition(x, y, vw, vh, margin = TOGGLE_MARGIN) {
2799
+ const maxX = Math.max(margin, vw - margin);
2800
+ const maxY = Math.max(margin, vh - margin);
2801
+ return {
2802
+ x: Math.min(Math.max(x, margin), maxX),
2803
+ y: Math.min(Math.max(y, margin), maxY)
2804
+ };
2805
+ }
2806
+ function resolveExpandDirection(x, y, vw, vh) {
2807
+ const vertical = y < vh / 2 ? "down" : "up";
2808
+ const horizontal = x < vw / 2 ? "right" : "left";
2809
+ return `${vertical}-${horizontal}`;
2810
+ }
2811
+ function defaultTogglePosition(vw, vh) {
2812
+ return { x: vw - DEFAULT_EDGE_OFFSET, y: vh - DEFAULT_EDGE_OFFSET };
2813
+ }
2814
+ function boxTopLeftFor(toggleCentre, dir) {
2815
+ const off = boxOffsetForDirection(dir);
2816
+ return { x: toggleCentre.x - off.x, y: toggleCentre.y - off.y };
2817
+ }
2818
+
2761
2819
  // src/index.ts
2762
2820
  init_selector_picker();
2763
2821
 
@@ -3868,6 +3926,15 @@ function renderConfiguration(state, t) {
3868
3926
  </label>
3869
3927
  </div>
3870
3928
 
3929
+ <!-- Widget position (draggable widget) -->
3930
+ <div class="card">
3931
+ <div class="card-hd">${t("CONFIG.WIDGET_POSITION_SECTION")}</div>
3932
+ <div class="check-sub" style="margin-bottom:10px">${t("CONFIG.WIDGET_POSITION_HINT")}</div>
3933
+ <div class="btn-row">
3934
+ <button id="btn-reset-position">${t("CONFIG.WIDGET_POSITION_RESET_BTN")}</button>
3935
+ </div>
3936
+ </div>
3937
+
3871
3938
  <!-- Recording continuity (cross-app resume TTL) -->
3872
3939
  <div class="card">
3873
3940
  <div class="card-hd">${t("CONFIG.RESUME_TTL_SECTION")}</div>
@@ -4008,6 +4075,10 @@ var ConfigurationElement = class extends HTMLElement {
4008
4075
  await this.persistence.setConfig({ [RESUME_TTL_CONFIG_KEY]: safe });
4009
4076
  this.render();
4010
4077
  }
4078
+ onResetWidgetPosition() {
4079
+ this.dispatchEvent(new CustomEvent("resetwidgetposition", { bubbles: true, composed: true }));
4080
+ showToast(this.t("CONFIG.WIDGET_POSITION_RESET_DONE"));
4081
+ }
4011
4082
  async onSmartSelectorChange(enabled) {
4012
4083
  this.smartSelectorEnabled = enabled;
4013
4084
  await this.persistence.setConfig({ smartSelectorEnabled: enabled ? "true" : "false" });
@@ -4143,6 +4214,7 @@ var ConfigurationElement = class extends HTMLElement {
4143
4214
  "change",
4144
4215
  (e) => this.onResumeTtlChange(Number(e.target.value))
4145
4216
  );
4217
+ this.shadow.getElementById("btn-reset-position")?.addEventListener("click", () => this.onResetWidgetPosition());
4146
4218
  this.shadow.getElementById("selector-strategy").addEventListener(
4147
4219
  "change",
4148
4220
  (e) => this.onSelectorStrategyChange(e.target.value)
@@ -5107,15 +5179,37 @@ if (!customElements.get("file-preview")) {
5107
5179
  var import_sweetalert2 = __toESM(require("sweetalert2"), 1);
5108
5180
 
5109
5181
  // src/components/lib-e2e-recorder/lib-e2e-recorder.styles.ts
5182
+ var DIRECTIONS = ["up-left", "up-right", "down-left", "down-right"];
5183
+ function directionBlocks() {
5184
+ return DIRECTIONS.map((dir) => {
5185
+ const sx = dir.endsWith("left") ? -1 : 1;
5186
+ const sy = dir.startsWith("up") ? -1 : 1;
5187
+ const tv = dir.startsWith("up") ? "bottom" : "top";
5188
+ const th = dir.endsWith("left") ? "right" : "left";
5189
+ const ov = tv === "bottom" ? "top" : "bottom";
5190
+ const oh = th === "right" ? "left" : "right";
5191
+ const sel = `.widget[data-expand="${dir}"]`;
5192
+ const labelGeneral = `${sel} .btn-action::after { ${oh}: calc(100% + 9px); ${th}: auto; top: 50%; bottom: auto; transform: translateY(-50%); }`;
5193
+ 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%); }`;
5194
+ return `
5195
+ ${sel} { --sx: ${sx}; --sy: ${sy}; }
5196
+ ${sel} .btn-toggle { ${tv}: 24px; ${th}: 24px; ${ov}: auto; ${oh}: auto; }
5197
+ ${sel} .btn-pause { ${tv}: 78px; ${th}: 24px; ${ov}: auto; ${oh}: auto; }
5198
+ ${sel} .btn-action { ${tv}: 28px; ${th}: 28px; ${ov}: auto; ${oh}: auto; }
5199
+ ${labelGeneral}
5200
+ ${labelEnds}
5201
+ `;
5202
+ }).join("\n");
5203
+ }
5110
5204
  function getRecorderStyles(rec, paused) {
5111
5205
  return `
5112
5206
  :host { all: initial; }
5113
5207
  *, *::before, *::after { box-sizing: border-box; }
5114
5208
 
5115
5209
  /*
5116
- * Invisible 190\xD7190 hit area anchored at bottom-right.
5117
- * Keeps :hover alive while the cursor travels from the
5118
- * toggle to any of the radial action buttons.
5210
+ * Invisible 190\xD7190 hit area. Position (left/top) is set from JS so the
5211
+ * widget can be dragged; --sx/--sy + data-expand orient the radial menu so
5212
+ * it always expands toward the viewport interior. Defaults to bottom-right.
5119
5213
  */
5120
5214
  .widget {
5121
5215
  position: fixed;
@@ -5124,6 +5218,8 @@ function getRecorderStyles(rec, paused) {
5124
5218
  width: 190px;
5125
5219
  height: 190px;
5126
5220
  z-index: 2147483647;
5221
+ --sx: -1;
5222
+ --sy: -1;
5127
5223
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
5128
5224
  }
5129
5225
 
@@ -5136,7 +5232,8 @@ function getRecorderStyles(rec, paused) {
5136
5232
  height: 44px;
5137
5233
  border-radius: 50%;
5138
5234
  border: none;
5139
- cursor: pointer;
5235
+ cursor: grab;
5236
+ touch-action: none;
5140
5237
  font-size: 19px;
5141
5238
  background: ${rec ? "linear-gradient(135deg,#f85149 0%,#da3633 100%)" : "linear-gradient(135deg,#2f81f7 0%,#1f6feb 100%)"};
5142
5239
  color: #fff;
@@ -5149,7 +5246,7 @@ function getRecorderStyles(rec, paused) {
5149
5246
  ${rec ? "animation: toggle-pulse 2s ease-in-out infinite;" : ""}
5150
5247
  }
5151
5248
  .btn-toggle:hover { transform: scale(1.1); }
5152
- .btn-toggle:active { transform: scale(0.93); }
5249
+ .btn-toggle:active { transform: scale(0.93); cursor: grabbing; }
5153
5250
 
5154
5251
  @keyframes toggle-pulse {
5155
5252
  0%,100% { box-shadow: 0 4px 20px rgba(248,81,73,.55),0 0 0 4px rgba(248,81,73,.13); }
@@ -5180,11 +5277,6 @@ function getRecorderStyles(rec, paused) {
5180
5277
  .btn-pause:active { transform: scale(0.93); }
5181
5278
 
5182
5279
  /* \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 */
5183
- /*
5184
- * All three start centered on the toggle button
5185
- * (toggle center = bottom:46px right:46px from widget edge;
5186
- * button half = 18px \u2192 bottom:28px right:28px).
5187
- */
5188
5280
  .btn-action {
5189
5281
  position: absolute;
5190
5282
  bottom: 28px;
@@ -5206,12 +5298,11 @@ function getRecorderStyles(rec, paused) {
5206
5298
  opacity: 0;
5207
5299
  transform: scale(0.35);
5208
5300
  pointer-events: none;
5209
- /* Collapse: fast, no spring */
5210
5301
  transition: opacity .15s, transform .18s ease-in,
5211
5302
  background .15s, color .12s, box-shadow .15s;
5212
5303
  }
5213
5304
 
5214
- /* Label to the left of each button */
5305
+ /* Label chip */
5215
5306
  .btn-action::after {
5216
5307
  content: attr(data-label);
5217
5308
  position: absolute;
@@ -5231,14 +5322,6 @@ function getRecorderStyles(rec, paused) {
5231
5322
  border: 1px solid rgba(48,54,61,.8);
5232
5323
  box-shadow: 0 2px 8px rgba(0,0,0,.35);
5233
5324
  }
5234
- /* Button 1 (top) \u2014 label above instead of left to avoid overlap with btn 2 */
5235
- .btn-action[data-n="1"]::after {
5236
- right: auto;
5237
- left: 50%;
5238
- top: auto;
5239
- bottom: calc(100% + 9px);
5240
- transform: translateX(-50%);
5241
- }
5242
5325
  .btn-action:hover::after { opacity: 1; }
5243
5326
  .btn-action:hover { background: #21262d; color: #e6edf3; }
5244
5327
  .btn-action:active { background: #30363d !important; }
@@ -5251,31 +5334,26 @@ function getRecorderStyles(rec, paused) {
5251
5334
  background .15s, color .12s, box-shadow .15s;
5252
5335
  }
5253
5336
 
5254
- /* Arc positions \u2014 4 buttons, 30\xB0 spacing, radius 90px */
5255
- .widget:hover .btn-action[data-n="1"] { /* 0\xB0 \u2014 straight up */
5256
- transform: translateY(-90px) scale(1);
5337
+ /* Arc positions \u2014 signs come from --sx/--sy (data-expand) */
5338
+ .widget:hover .btn-action[data-n="1"] { /* vertical extreme */
5339
+ transform: translateY(calc(var(--sy) * 90px)) scale(1);
5257
5340
  transition-delay: .03s;
5258
5341
  }
5259
- .widget:hover .btn-action[data-n="2"] { /* 30\xB0 \u2014 upper-left */
5260
- transform: translate(-45px,-78px) scale(1);
5342
+ .widget:hover .btn-action[data-n="2"] { /* diagonal near-vertical */
5343
+ transform: translate(calc(var(--sx) * 45px), calc(var(--sy) * 78px)) scale(1);
5261
5344
  transition-delay: .07s;
5262
5345
  }
5263
- .widget:hover .btn-action[data-n="3"] { /* 60\xB0 \u2014 left-upper */
5264
- transform: translate(-78px,-45px) scale(1);
5346
+ .widget:hover .btn-action[data-n="3"] { /* diagonal near-horizontal */
5347
+ transform: translate(calc(var(--sx) * 78px), calc(var(--sy) * 45px)) scale(1);
5265
5348
  transition-delay: .11s;
5266
5349
  }
5267
- .widget:hover .btn-action[data-n="4"] { /* 90\xB0 \u2014 straight left */
5268
- transform: translateX(-90px) scale(1);
5350
+ .widget:hover .btn-action[data-n="4"] { /* horizontal extreme */
5351
+ transform: translateX(calc(var(--sx) * 90px)) scale(1);
5269
5352
  transition-delay: .15s;
5270
5353
  }
5271
- /* Button 4 (pure left) \u2014 label above to avoid going off-screen */
5272
- .btn-action[data-n="4"]::after {
5273
- right: auto;
5274
- left: 50%;
5275
- top: auto;
5276
- bottom: calc(100% + 9px);
5277
- transform: translateX(-50%);
5278
- }
5354
+
5355
+ /* Per-direction anchors + label sides */
5356
+ ${directionBlocks()}
5279
5357
 
5280
5358
  /* \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 */
5281
5359
  .rec-badge {
@@ -5342,9 +5420,18 @@ var LibE2eRecorderElement = class extends HTMLElement {
5342
5420
  sessionUnsub;
5343
5421
  sessionSaveTimer;
5344
5422
  controlFirstTimeData = true;
5423
+ // ── draggable widget (spec 007) ──
5424
+ togglePos = null;
5425
+ expandDir = "up-left";
5426
+ dragState;
5427
+ suppressNextToggleClick = false;
5428
+ widgetPointerMove;
5429
+ widgetPointerUp;
5430
+ widgetResize;
5345
5431
  _previsualizerRef = null;
5346
5432
  httpMonitor;
5347
5433
  smartSelectorEnabled = true;
5434
+ _needsRecordingRebuild = false;
5348
5435
  recording;
5349
5436
  persistence;
5350
5437
  translation;
@@ -5370,7 +5457,10 @@ var LibE2eRecorderElement = class extends HTMLElement {
5370
5457
  if (!this.getAttribute("data-cy")) {
5371
5458
  this.setAttribute("data-cy", "lib-e2e-cypress-for-dummys");
5372
5459
  }
5373
- if (!this.recording) this.recording = new RecordingService();
5460
+ if (!this.recording || this._needsRecordingRebuild) {
5461
+ this.recording = new RecordingService();
5462
+ this._needsRecordingRebuild = false;
5463
+ }
5374
5464
  if (!this.persistence) this.persistence = new PersistenceService();
5375
5465
  if (!this.translation) this.translation = new TranslationService();
5376
5466
  this.httpMonitor = new HttpMonitor(this.recording);
@@ -5385,8 +5475,15 @@ var LibE2eRecorderElement = class extends HTMLElement {
5385
5475
  this.render();
5386
5476
  this.initVisibility();
5387
5477
  this.initSessionContinuity();
5478
+ this.initWidgetPosition();
5388
5479
  this.keydownHandler = (e) => this.handleKeyboardEvent(e);
5389
5480
  window.addEventListener("keydown", this.keydownHandler);
5481
+ this.widgetPointerMove = (e) => this.onWidgetPointerMove(e);
5482
+ this.widgetPointerUp = () => this.onWidgetPointerUp();
5483
+ this.widgetResize = () => this.applyWidgetPosition();
5484
+ window.addEventListener("pointermove", this.widgetPointerMove);
5485
+ window.addEventListener("pointerup", this.widgetPointerUp);
5486
+ window.addEventListener("resize", this.widgetResize);
5390
5487
  }
5391
5488
  disconnectedCallback() {
5392
5489
  if (this._isDisabled) return;
@@ -5399,8 +5496,12 @@ var LibE2eRecorderElement = class extends HTMLElement {
5399
5496
  this.selectorNotFoundUnsub?.();
5400
5497
  this.langUnsub?.();
5401
5498
  this.sessionUnsub?.();
5499
+ if (this.widgetPointerMove) window.removeEventListener("pointermove", this.widgetPointerMove);
5500
+ if (this.widgetPointerUp) window.removeEventListener("pointerup", this.widgetPointerUp);
5501
+ if (this.widgetResize) window.removeEventListener("resize", this.widgetResize);
5402
5502
  this.httpMonitor?.uninstall();
5403
5503
  this.recording.destroy();
5504
+ this._needsRecordingRebuild = true;
5404
5505
  }
5405
5506
  async initHttpConfig() {
5406
5507
  const config = await this.persistence.getConfig("extendedHttpCommands");
@@ -5585,6 +5686,64 @@ var LibE2eRecorderElement = class extends HTMLElement {
5585
5686
  discardSession() {
5586
5687
  this.clearSessionPersistence();
5587
5688
  }
5689
+ // ── draggable widget (spec 007) ────────────────────────────────────────────
5690
+ /** Loads a previously saved widget position and applies it. */
5691
+ async initWidgetPosition() {
5692
+ const config = await this.persistence.getGeneralConfig();
5693
+ const pos = config?.["widgetPosition"];
5694
+ if (pos && typeof pos.x === "number" && typeof pos.y === "number") {
5695
+ this.togglePos = { x: pos.x, y: pos.y };
5696
+ }
5697
+ this.applyWidgetPosition();
5698
+ }
5699
+ /** Positions the `.widget` box from the (clamped) toggle centre and orients the arc. */
5700
+ applyWidgetPosition() {
5701
+ const widget = this.shadow.querySelector(".widget");
5702
+ if (!widget) return;
5703
+ const vw = window.innerWidth;
5704
+ const vh = window.innerHeight;
5705
+ const centre = this.togglePos ?? defaultTogglePosition(vw, vh);
5706
+ const clamped = clampTogglePosition(centre.x, centre.y, vw, vh);
5707
+ this.expandDir = resolveExpandDirection(clamped.x, clamped.y, vw, vh);
5708
+ const topLeft = boxTopLeftFor(clamped, this.expandDir);
5709
+ widget.style.left = `${topLeft.x}px`;
5710
+ widget.style.top = `${topLeft.y}px`;
5711
+ widget.style.right = "auto";
5712
+ widget.style.bottom = "auto";
5713
+ widget.setAttribute("data-expand", this.expandDir);
5714
+ }
5715
+ beginWidgetDrag(e) {
5716
+ this.suppressNextToggleClick = false;
5717
+ const origin = this.togglePos ?? defaultTogglePosition(window.innerWidth, window.innerHeight);
5718
+ this.dragState = { startX: e.clientX, startY: e.clientY, origX: origin.x, origY: origin.y, moved: false };
5719
+ }
5720
+ onWidgetPointerMove(e) {
5721
+ if (!this.dragState) return;
5722
+ const dx = e.clientX - this.dragState.startX;
5723
+ const dy = e.clientY - this.dragState.startY;
5724
+ if (!this.dragState.moved && Math.hypot(dx, dy) < DRAG_THRESHOLD) return;
5725
+ this.dragState.moved = true;
5726
+ this.togglePos = { x: this.dragState.origX + dx, y: this.dragState.origY + dy };
5727
+ this.applyWidgetPosition();
5728
+ }
5729
+ onWidgetPointerUp() {
5730
+ if (!this.dragState) return;
5731
+ const moved = this.dragState.moved;
5732
+ this.dragState = void 0;
5733
+ if (!moved || !this.togglePos) return;
5734
+ this.suppressNextToggleClick = true;
5735
+ const clamped = clampTogglePosition(this.togglePos.x, this.togglePos.y, window.innerWidth, window.innerHeight);
5736
+ this.togglePos = clamped;
5737
+ this.persistence.setConfig({ widgetPosition: clamped }).catch(() => {
5738
+ });
5739
+ }
5740
+ /** Resets the widget to its default corner and clears the saved position. */
5741
+ resetWidgetPosition() {
5742
+ this.togglePos = null;
5743
+ this.persistence.setConfig({ widgetPosition: null }).catch(() => {
5744
+ });
5745
+ this.applyWidgetPosition();
5746
+ }
5588
5747
  toggle() {
5589
5748
  this.recording.toggleRecording();
5590
5749
  }
@@ -5802,7 +5961,9 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
5802
5961
  const type = document.getElementById("assert-type").value;
5803
5962
  const val = document.getElementById("assert-value").value.trim();
5804
5963
  if (!sel) return;
5805
- const cmd = NO_VALUE_ASSERTIONS.has(type) || !val ? `cy.get('${sel}').should('${type}')` : `cy.get('${sel}').should('${type}', '${val}')`;
5964
+ const s = escapeSingleQuotes(sel);
5965
+ const v = escapeSingleQuotes(val);
5966
+ const cmd = NO_VALUE_ASSERTIONS.has(type) || !val ? `cy.get('${s}').should('${type}')` : `cy.get('${s}').should('${type}', '${v}')`;
5806
5967
  this.recording.appendCommand(cmd);
5807
5968
  document.getElementById("assert-selector").value = "";
5808
5969
  document.getElementById("assert-value").value = "";
@@ -5901,6 +6062,7 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
5901
6062
  this.isVisible = !e.detail;
5902
6063
  this.style.display = this.isVisible ? "" : "none";
5903
6064
  });
6065
+ child.addEventListener("resetwidgetposition", () => this.resetWidgetPosition());
5904
6066
  },
5905
6067
  willClose: () => {
5906
6068
  this.isSettingsDialogOpen = false;
@@ -6054,12 +6216,21 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
6054
6216
  const paused = this.isPaused;
6055
6217
  this.style.display = this.isVisible ? "" : "none";
6056
6218
  this.shadow.innerHTML = `<style>${getRecorderStyles(rec, paused)}</style>${renderRecorderWidget(rec, paused, this.translation.translate.bind(this.translation))}`;
6057
- this.shadow.querySelector('[data-action="toggle"]')?.addEventListener("click", () => this.toggle());
6219
+ const toggleBtn = this.shadow.querySelector('[data-action="toggle"]');
6220
+ toggleBtn?.addEventListener("click", () => {
6221
+ if (this.suppressNextToggleClick) {
6222
+ this.suppressNextToggleClick = false;
6223
+ return;
6224
+ }
6225
+ this.toggle();
6226
+ });
6227
+ toggleBtn?.addEventListener("pointerdown", (e) => this.beginWidgetDrag(e));
6058
6228
  this.shadow.querySelector('[data-action="pause"]')?.addEventListener("click", () => this.togglePause());
6059
6229
  this.shadow.querySelector('[data-action="tests"]')?.addEventListener("click", () => this.showSavedTestsDialog());
6060
6230
  this.shadow.querySelector('[data-action="commands"]')?.addEventListener("click", () => this.showCommandsDialog());
6061
6231
  this.shadow.querySelector('[data-action="config"]')?.addEventListener("click", () => this.showSettingsDialog());
6062
6232
  this.shadow.querySelector('[data-action="browse"]')?.addEventListener("click", () => this.showAdvancedEditorDialog());
6233
+ this.applyWidgetPosition();
6063
6234
  }
6064
6235
  };
6065
6236
  if (!customElements.get("lib-e2e-recorder")) {
@@ -6067,7 +6238,7 @@ if (!customElements.get("lib-e2e-recorder")) {
6067
6238
  }
6068
6239
 
6069
6240
  // src/index.ts
6070
- var VERSION = "0.5.0";
6241
+ var VERSION = "0.6.1";
6071
6242
  // Annotate the CommonJS export names for ESM import in node:
6072
6243
  0 && (module.exports = {
6073
6244
  ACTIVE_SESSION_BREADCRUMB_KEY,
@@ -6077,6 +6248,7 @@ var VERSION = "0.5.0";
6077
6248
  DB_SCHEMA,
6078
6249
  DB_STORE_NAMES,
6079
6250
  DEFAULT_RESUME_TTL_MINUTES,
6251
+ DRAG_THRESHOLD,
6080
6252
  FilePreviewElement,
6081
6253
  HttpMonitor,
6082
6254
  INPUT_TYPES,
@@ -6091,12 +6263,17 @@ var VERSION = "0.5.0";
6091
6263
  SaveTestElement,
6092
6264
  SelectorPickerElement,
6093
6265
  Subject,
6266
+ TOGGLE_MARGIN,
6094
6267
  TestEditorElement,
6095
6268
  TestPrevisualizerElement,
6096
6269
  TransformationService,
6097
6270
  TranslationService,
6098
6271
  VERSION,
6099
6272
  advancedTestTransformationService,
6273
+ boxOffsetForDirection,
6274
+ boxTopLeftFor,
6275
+ clampTogglePosition,
6276
+ defaultTogglePosition,
6100
6277
  generateAlias,
6101
6278
  injectStyles,
6102
6279
  isLang,
@@ -6106,6 +6283,7 @@ var VERSION = "0.5.0";
6106
6283
  makeSwalDraggable,
6107
6284
  makeSwalDraggableByContentId,
6108
6285
  persistenceService,
6286
+ resolveExpandDirection,
6109
6287
  selectTestsForExport,
6110
6288
  setSwal2DataCyAttribute,
6111
6289
  showToast,