@rogieking/figui3 6.10.0 → 6.12.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/fig-lab.css CHANGED
@@ -302,13 +302,15 @@ fig-field-slider {
302
302
  background-color: transparent;
303
303
  box-shadow: none !important;
304
304
  position: absolute;
305
- right: var(--spacer-1);
305
+ right: 2px;
306
+ top:2px;
307
+ bottom:2px;
306
308
  width: fit-content;
307
309
  min-width: fit-content;
308
310
  max-width: max-content;
309
311
  flex: 0 0 auto;
310
312
  border-left: 0;
311
- border-radius: var(--radius-small);
313
+ border-radius: var(--radius-medium);
312
314
 
313
315
  &:hover,
314
316
  &:has(input:focus) {
@@ -321,7 +323,7 @@ fig-field-slider {
321
323
  input {
322
324
  field-sizing: content;
323
325
  font-variant-numeric: tabular-nums;
324
- border-radius: var(--radius-small);
326
+ border-radius: var(--radius-medium);
325
327
  width: auto;
326
328
  flex: 0 0 auto;
327
329
  text-align: right;
@@ -636,6 +638,32 @@ fig-input-oscillator {
636
638
  }
637
639
  }
638
640
 
641
+ .fig-input-oscillator-frequency-handle {
642
+ cursor: ew-resize;
643
+
644
+ fig-handle,
645
+ fig-handle:hover,
646
+ fig-handle .fig-handle-hit-area,
647
+ fig-handle:hover .fig-handle-hit-area,
648
+ fig-handle:is(:active, .active, .dragging),
649
+ fig-handle:is(:active, .active, .dragging) .fig-handle-hit-area {
650
+ cursor: ew-resize !important;
651
+ }
652
+ }
653
+
654
+ .fig-input-oscillator-amplitude-handle {
655
+ cursor: ns-resize;
656
+
657
+ fig-handle,
658
+ fig-handle:hover,
659
+ fig-handle .fig-handle-hit-area,
660
+ fig-handle:hover .fig-handle-hit-area,
661
+ fig-handle:is(:active, .active, .dragging),
662
+ fig-handle:is(:active, .active, .dragging) .fig-handle-hit-area {
663
+ cursor: ns-resize !important;
664
+ }
665
+ }
666
+
639
667
  .fig-input-oscillator-waves {
640
668
  display: flex;
641
669
  flex-direction: column;
@@ -673,3 +701,51 @@ fig-input-oscillator {
673
701
  }
674
702
  }
675
703
 
704
+ /* Reorder */
705
+ fig-reorder {
706
+ display: contents;
707
+ }
708
+
709
+ fig-reorder:not([handle]):is(:not([disabled]), [disabled="false"]) > [data-reorder-item] {
710
+ cursor: grab;
711
+ }
712
+
713
+ fig-reorder:not([handle]):is(:not([disabled]), [disabled="false"]) > [data-reorder-item] > fig-header,
714
+ fig-reorder:not([handle]):is(:not([disabled]), [disabled="false"]) > [data-reorder-item] > fig-header *,
715
+ fig-reorder:not([handle]):is(:not([disabled]), [disabled="false"]) > [data-reorder-item] > label {
716
+ cursor: grab;
717
+ }
718
+
719
+ fig-reorder > .dragging {
720
+ background-color: var(--figma-color-bg-selected);
721
+ }
722
+
723
+ [data-reorder-handle] {
724
+ cursor: grab;
725
+ }
726
+
727
+ fig-reorder[disabled]:not([disabled="false"]) [data-reorder-handle] {
728
+ cursor: inherit;
729
+ }
730
+
731
+ body.fig-reorder-dragging,
732
+ body.fig-reorder-dragging * {
733
+ user-select: none !important;
734
+ -webkit-user-select: none !important;
735
+ cursor: grabbing !important;
736
+ }
737
+
738
+ .fig-reorder-indicator {
739
+ position: fixed;
740
+ pointer-events: none;
741
+ z-index: 1000000;
742
+ background: var(--figma-color-border-strong);
743
+ }
744
+
745
+ .fig-reorder-indicator[data-axis="vertical"] {
746
+ height: 2px;
747
+ }
748
+
749
+ .fig-reorder-indicator[data-axis="horizontal"] {
750
+ width: 2px;
751
+ }
package/fig-lab.js CHANGED
@@ -8,6 +8,10 @@
8
8
  * <script src="fig-lab.js"></script>
9
9
  */
10
10
 
11
+ function figLabBooleanAttribute(element, name) {
12
+ return element.hasAttribute(name) && element.getAttribute(name) !== "false";
13
+ }
14
+
11
15
  /* Field + Slider wrapper */
12
16
  class FigFieldSlider extends HTMLElement {
13
17
  #field = null;
@@ -331,7 +335,7 @@ class FigFieldSlider extends HTMLElement {
331
335
  }
332
336
 
333
337
  #handleElasticPointerDown(event) {
334
- if (event.button !== 0 || this.hasAttribute("disabled")) return;
338
+ if (event.button !== 0 || figLabBooleanAttribute(this, "disabled")) return;
335
339
  if (this.getAttribute("elastic") === "false") return;
336
340
  if (event.target?.closest?.("fig-input-number")) return;
337
341
  const rangeInput =
@@ -397,7 +401,7 @@ class FigFieldSlider extends HTMLElement {
397
401
  }
398
402
 
399
403
  #handleContextMenu(event) {
400
- if (this.hasAttribute("disabled")) return;
404
+ if (figLabBooleanAttribute(this, "disabled")) return;
401
405
  event.preventDefault();
402
406
  event.stopPropagation();
403
407
  event.stopImmediatePropagation();
@@ -872,7 +876,7 @@ class FigCanvasControl extends HTMLElement {
872
876
  this.#radiusTooltip = null;
873
877
  this.#angleTooltip = null;
874
878
 
875
- const disabled = this.hasAttribute("disabled");
879
+ const disabled = figLabBooleanAttribute(this, "disabled");
876
880
  const type = this.#type;
877
881
  const tooltips = this.#tooltipsEnabled;
878
882
 
@@ -945,7 +949,7 @@ class FigCanvasControl extends HTMLElement {
945
949
  }
946
950
 
947
951
  #activateMoveCursor(e) {
948
- if (this.hasAttribute("disabled")) return;
952
+ if (figLabBooleanAttribute(this, "disabled")) return;
949
953
  if (e?.button !== undefined && e.button !== 0) return;
950
954
  if (e?.isPrimary === false) return;
951
955
 
@@ -1190,7 +1194,7 @@ class FigCanvasControl extends HTMLElement {
1190
1194
 
1191
1195
  #setupLineDrag(hitLine) {
1192
1196
  hitLine.addEventListener("pointerdown", (e) => {
1193
- if (this.hasAttribute("disabled")) return;
1197
+ if (figLabBooleanAttribute(this, "disabled")) return;
1194
1198
  e.preventDefault();
1195
1199
  e.stopPropagation();
1196
1200
  const container = this.#container;
@@ -1705,7 +1709,7 @@ class FigCanvasControl extends HTMLElement {
1705
1709
  circle.style.cursor = this.#resizeCursorSvg(deg);
1706
1710
  });
1707
1711
  const onDown = (e) => {
1708
- if (this.hasAttribute("disabled")) return;
1712
+ if (figLabBooleanAttribute(this, "disabled")) return;
1709
1713
  e.preventDefault();
1710
1714
  e.stopPropagation();
1711
1715
  this.#isRadiusDragging = true;
@@ -1830,6 +1834,7 @@ class FigInputOscillator extends HTMLElement {
1830
1834
  #precision = 2;
1831
1835
  #drawWidth = 240;
1832
1836
  #drawHeight = 120;
1837
+ #valueRange = { min: -1, max: 1 };
1833
1838
  #isDragging = null;
1834
1839
  #svg = null;
1835
1840
  #path = null;
@@ -1935,7 +1940,9 @@ class FigInputOscillator extends HTMLElement {
1935
1940
 
1936
1941
  #readInteger(name, fallback) {
1937
1942
  const value = Number.parseInt(this.getAttribute(name) || "", 10);
1938
- return Number.isFinite(value) ? value : fallback;
1943
+ return Number.isFinite(value)
1944
+ ? Math.max(0, Math.min(100, value))
1945
+ : fallback;
1939
1946
  }
1940
1947
 
1941
1948
  #readBooleanAttribute(name, defaultValue = false) {
@@ -2098,8 +2105,8 @@ class FigInputOscillator extends HTMLElement {
2098
2105
  <line class="fig-input-oscillator-baseline"></line>
2099
2106
  <path class="fig-input-oscillator-path"></path>
2100
2107
  <circle class="fig-input-oscillator-playhead"></circle>
2101
- <foreignObject class="fig-input-oscillator-handle fig-input-oscillator-amplitude-handle" data-handle="amplitude" width="20" height="20"><div class="fig-input-oscillator-handle-inner"><fig-tooltip text="Amplitude"><fig-handle type="canvas" size="small" aria-label="Oscillator amplitude handle"${disabled}></fig-handle></fig-tooltip></div></foreignObject>
2102
- <foreignObject class="fig-input-oscillator-handle fig-input-oscillator-frequency-handle" data-handle="frequency" width="20" height="20"><div class="fig-input-oscillator-handle-inner"><fig-tooltip text="Frequency"><fig-handle type="canvas" size="small" aria-label="Oscillator frequency handle"${disabled}></fig-handle></fig-tooltip></div></foreignObject>
2108
+ <foreignObject class="fig-input-oscillator-handle fig-input-oscillator-amplitude-handle" data-handle="amplitude" width="20" height="20"><div class="fig-input-oscillator-handle-inner"><fig-tooltip text="Amplitude"><fig-handle size="small" aria-label="Oscillator amplitude handle"${disabled}></fig-handle></fig-tooltip></div></foreignObject>
2109
+ <foreignObject class="fig-input-oscillator-handle fig-input-oscillator-frequency-handle" data-handle="frequency" width="20" height="20"><div class="fig-input-oscillator-handle-inner"><fig-tooltip text="Frequency"><fig-handle size="small" aria-label="Oscillator frequency handle"${disabled}></fig-handle></fig-tooltip></div></foreignObject>
2103
2110
  </svg>
2104
2111
  </div>
2105
2112
  ${this.#isEditEnabled() ? this.#getWaveControlsHTML(disabled) : ""}`;
@@ -2231,12 +2238,12 @@ class FigInputOscillator extends HTMLElement {
2231
2238
  }
2232
2239
 
2233
2240
  #toY(value) {
2234
- const { min, max } = this.#getValueRange();
2241
+ const { min, max } = this.#valueRange;
2235
2242
  return this.#drawHeight - ((value - min) / (max - min)) * this.#drawHeight;
2236
2243
  }
2237
2244
 
2238
2245
  #fromY(y) {
2239
- const { min, max } = this.#getValueRange();
2246
+ const { min, max } = this.#valueRange;
2240
2247
  return min + (1 - y / this.#drawHeight) * (max - min);
2241
2248
  }
2242
2249
 
@@ -2251,6 +2258,7 @@ class FigInputOscillator extends HTMLElement {
2251
2258
  #updateWaveform() {
2252
2259
  if (!this.#svg || !this.#path) return;
2253
2260
  this.#syncViewportSize();
2261
+ this.#valueRange = this.#getValueRange();
2254
2262
 
2255
2263
  if (this.#bounds) {
2256
2264
  this.#bounds.setAttribute("width", this.#drawWidth);
@@ -2589,6 +2597,13 @@ class FigInputOscillator extends HTMLElement {
2589
2597
 
2590
2598
  #startDrag(event, type) {
2591
2599
  this.#isDragging = type;
2600
+ this.#svg?.classList.add("dragging");
2601
+ const dragCursor =
2602
+ type === "frequency" ? "ew-resize" : type === "amplitude" ? "ns-resize" : "";
2603
+ const prevBodyCursor = document.body.style.cursor;
2604
+ if (dragCursor) {
2605
+ document.body.style.cursor = dragCursor;
2606
+ }
2592
2607
 
2593
2608
  const onMove = (moveEvent) => {
2594
2609
  if (!this.#isDragging) return;
@@ -2623,6 +2638,10 @@ class FigInputOscillator extends HTMLElement {
2623
2638
 
2624
2639
  const onUp = () => {
2625
2640
  this.#isDragging = null;
2641
+ this.#svg?.classList.remove("dragging");
2642
+ if (dragCursor) {
2643
+ document.body.style.cursor = prevBodyCursor;
2644
+ }
2626
2645
  document.removeEventListener("pointermove", onMove);
2627
2646
  document.removeEventListener("pointerup", onUp);
2628
2647
  this.#emit("change");
@@ -3221,3 +3240,432 @@ class FigInputAngle extends HTMLElement {
3221
3240
  }
3222
3241
  customElements.define("fig-input-angle", FigInputAngle);
3223
3242
 
3243
+ /* Reorder wrapper */
3244
+ class FigReorder extends HTMLElement {
3245
+ static observedAttributes = ["axis", "handle", "disabled"];
3246
+
3247
+ static #DRAG_THRESHOLD = 6;
3248
+
3249
+ static #INTERACTIVE_SELECTORS = [
3250
+ "input",
3251
+ "button",
3252
+ "select",
3253
+ "textarea",
3254
+ "a[href]",
3255
+ "label",
3256
+ "summary",
3257
+ "fig-button",
3258
+ "fig-dropdown",
3259
+ "fig-slider",
3260
+ "fig-input-number",
3261
+ "fig-input-text",
3262
+ "fig-field-slider",
3263
+ "fig-checkbox",
3264
+ "fig-switch",
3265
+ "fig-combo-input",
3266
+ "fig-segmented-control",
3267
+ "fig-input-color",
3268
+ "fig-input-fill",
3269
+ '[contenteditable="true"]',
3270
+ ];
3271
+
3272
+ #childObserver = null;
3273
+ #bindings = new Map();
3274
+ #drag = null;
3275
+ #indicator = null;
3276
+
3277
+ connectedCallback() {
3278
+ this.style.display = "contents";
3279
+ this.#syncChildren();
3280
+ this.#childObserver = new MutationObserver(() => this.#syncChildren());
3281
+ this.#childObserver.observe(this, { childList: true });
3282
+ }
3283
+
3284
+ disconnectedCallback() {
3285
+ this.#childObserver?.disconnect();
3286
+ this.#childObserver = null;
3287
+ this.#unbindAll();
3288
+ this.#cancelDrag();
3289
+ this.#removeIndicator();
3290
+ }
3291
+
3292
+ attributeChangedCallback() {
3293
+ if (this.isConnected) this.#syncChildren();
3294
+ }
3295
+
3296
+ get #disabled() {
3297
+ return (
3298
+ this.hasAttribute("disabled") && this.getAttribute("disabled") !== "false"
3299
+ );
3300
+ }
3301
+
3302
+ get #axis() {
3303
+ const value = (this.getAttribute("axis") || "vertical").trim().toLowerCase();
3304
+ return value === "horizontal" ? "horizontal" : "vertical";
3305
+ }
3306
+
3307
+ get #handleSelector() {
3308
+ return (this.getAttribute("handle") || "").trim();
3309
+ }
3310
+
3311
+ #getElementChildren() {
3312
+ return [...this.children].filter((node) => node.nodeType === Node.ELEMENT_NODE);
3313
+ }
3314
+
3315
+ #syncChildren() {
3316
+ const children = this.#getElementChildren();
3317
+ const childSet = new Set(children);
3318
+
3319
+ for (const [child, binding] of this.#bindings) {
3320
+ if (!childSet.has(child)) {
3321
+ binding.target.removeEventListener(
3322
+ "pointerdown",
3323
+ binding.onPointerDown,
3324
+ true,
3325
+ );
3326
+ this.#bindings.delete(child);
3327
+ }
3328
+ }
3329
+
3330
+ this.#clearHandleMarks(children);
3331
+ this.#clearReorderItemMarks(children);
3332
+
3333
+ if (this.#disabled || children.length < 2) {
3334
+ this.#unbindAll();
3335
+ return;
3336
+ }
3337
+
3338
+ for (const child of children) {
3339
+ const target = this.#getDragTarget(child);
3340
+ const existing = this.#bindings.get(child);
3341
+ if (existing && existing.target === target) continue;
3342
+ if (existing) {
3343
+ existing.target.removeEventListener(
3344
+ "pointerdown",
3345
+ existing.onPointerDown,
3346
+ true,
3347
+ );
3348
+ this.#bindings.delete(child);
3349
+ }
3350
+ if (!target) continue;
3351
+ this.#bindChild(child, target);
3352
+ }
3353
+
3354
+ this.#markHandles(children);
3355
+ this.#markReorderItems(children);
3356
+ }
3357
+
3358
+ #clearReorderItemMarks(children) {
3359
+ for (const child of children) {
3360
+ child.removeAttribute("data-reorder-item");
3361
+ }
3362
+ }
3363
+
3364
+ #markReorderItems(children) {
3365
+ for (const child of children) {
3366
+ child.setAttribute("data-reorder-item", "");
3367
+ }
3368
+ }
3369
+
3370
+ #clearHandleMarks(children) {
3371
+ for (const child of children) {
3372
+ child
3373
+ .querySelectorAll("[data-reorder-handle]")
3374
+ .forEach((node) => node.removeAttribute("data-reorder-handle"));
3375
+ }
3376
+ }
3377
+
3378
+ #markHandles(children) {
3379
+ if (!this.#handleSelector) return;
3380
+ for (const child of children) {
3381
+ const handle = child.querySelector(this.#handleSelector);
3382
+ if (handle) handle.setAttribute("data-reorder-handle", "");
3383
+ }
3384
+ }
3385
+
3386
+ #getDragTarget(child) {
3387
+ if (this.#handleSelector) {
3388
+ return child.querySelector(this.#handleSelector);
3389
+ }
3390
+ return child;
3391
+ }
3392
+
3393
+ #bindChild(child, target) {
3394
+ const onPointerDown = (event) => {
3395
+ if (this.#disabled || this.#getElementChildren().length < 2) return;
3396
+ if (event.button !== 0) return;
3397
+ if (
3398
+ !this.#handleSelector &&
3399
+ this.#isInteractiveTarget(event.target, child)
3400
+ ) {
3401
+ return;
3402
+ }
3403
+ this.#startPendingDrag(event, child, target);
3404
+ };
3405
+
3406
+ target.addEventListener("pointerdown", onPointerDown, true);
3407
+ this.#bindings.set(child, { target, onPointerDown });
3408
+ }
3409
+
3410
+ #isInteractiveTarget(target, child) {
3411
+ let node = target;
3412
+ while (node && node !== child) {
3413
+ if (node instanceof Element) {
3414
+ for (const selector of FigReorder.#INTERACTIVE_SELECTORS) {
3415
+ if (node.matches(selector)) return true;
3416
+ }
3417
+ }
3418
+ node = node.parentElement;
3419
+ }
3420
+ return false;
3421
+ }
3422
+
3423
+ static #DRAGGING_BODY_CLASS = "fig-reorder-dragging";
3424
+
3425
+ static #setDocumentDragging(active) {
3426
+ document.body.classList.toggle(FigReorder.#DRAGGING_BODY_CLASS, active);
3427
+ }
3428
+
3429
+ #startPendingDrag(event, item, target) {
3430
+ this.#cancelDrag();
3431
+ FigReorder.#setDocumentDragging(true);
3432
+
3433
+ const state = {
3434
+ item,
3435
+ target,
3436
+ pointerId: event.pointerId,
3437
+ oldIndex: this.#getElementChildren().indexOf(item),
3438
+ targetIndex: this.#getElementChildren().indexOf(item),
3439
+ startX: event.clientX,
3440
+ startY: event.clientY,
3441
+ active: false,
3442
+ onMove: null,
3443
+ onUp: null,
3444
+ onKeyDown: null,
3445
+ };
3446
+
3447
+ state.onMove = (moveEvent) => {
3448
+ if (moveEvent.pointerId !== state.pointerId) return;
3449
+
3450
+ const dx = moveEvent.clientX - state.startX;
3451
+ const dy = moveEvent.clientY - state.startY;
3452
+
3453
+ if (!state.active) {
3454
+ if (dx * dx + dy * dy < FigReorder.#DRAG_THRESHOLD * FigReorder.#DRAG_THRESHOLD) {
3455
+ return;
3456
+ }
3457
+ state.active = true;
3458
+ event.preventDefault();
3459
+ event.stopPropagation();
3460
+ item.classList.add("dragging");
3461
+ try {
3462
+ target.setPointerCapture(state.pointerId);
3463
+ } catch {}
3464
+ }
3465
+
3466
+ moveEvent.preventDefault();
3467
+ const pointer =
3468
+ this.#axis === "horizontal" ? moveEvent.clientX : moveEvent.clientY;
3469
+ const index = this.#getInsertIndex(pointer);
3470
+ state.targetIndex = index;
3471
+ this.#updateIndicator(index, item);
3472
+ };
3473
+
3474
+ state.onKeyDown = (keyEvent) => {
3475
+ if (keyEvent.key !== "Escape" || !state.active) return;
3476
+ keyEvent.preventDefault();
3477
+ this.#finishDrag(state, true);
3478
+ };
3479
+
3480
+ state.onUp = (upEvent) => {
3481
+ if (upEvent.pointerId !== state.pointerId) return;
3482
+ this.#finishDrag(state, upEvent.type === "pointercancel");
3483
+ };
3484
+
3485
+ this.#drag = state;
3486
+ window.addEventListener("pointermove", state.onMove);
3487
+ window.addEventListener("pointerup", state.onUp);
3488
+ window.addEventListener("pointercancel", state.onUp);
3489
+ window.addEventListener("keydown", state.onKeyDown);
3490
+ }
3491
+
3492
+ #getInsertIndex(pointer) {
3493
+ const items = this.#getElementChildren();
3494
+ const horizontal = this.#axis === "horizontal";
3495
+
3496
+ for (let i = 0; i < items.length; i++) {
3497
+ const rect = items[i].getBoundingClientRect();
3498
+ const midpoint = horizontal
3499
+ ? rect.left + rect.width / 2
3500
+ : rect.top + rect.height / 2;
3501
+ if (pointer < midpoint) return i;
3502
+ }
3503
+
3504
+ return items.length;
3505
+ }
3506
+
3507
+ #shouldShowIndicator(item, index) {
3508
+ const items = this.#getElementChildren();
3509
+ const currentIndex = items.indexOf(item);
3510
+ if (currentIndex === -1) return false;
3511
+
3512
+ const clamped = Math.max(0, Math.min(index, items.length));
3513
+
3514
+ if (index === currentIndex) return false;
3515
+
3516
+ // Hide only the redundant bottom line when the dragged item is already last.
3517
+ if (clamped >= items.length && currentIndex === items.length - 1) {
3518
+ return false;
3519
+ }
3520
+
3521
+ return true;
3522
+ }
3523
+
3524
+ #getReorderBounds(items) {
3525
+ if (!items.length) return null;
3526
+
3527
+ let left = Infinity;
3528
+ let right = -Infinity;
3529
+ let top = Infinity;
3530
+ let bottom = -Infinity;
3531
+
3532
+ for (const item of items) {
3533
+ const rect = item.getBoundingClientRect();
3534
+ left = Math.min(left, rect.left);
3535
+ right = Math.max(right, rect.right);
3536
+ top = Math.min(top, rect.top);
3537
+ bottom = Math.max(bottom, rect.bottom);
3538
+ }
3539
+
3540
+ return {
3541
+ left,
3542
+ top,
3543
+ width: right - left,
3544
+ height: bottom - top,
3545
+ };
3546
+ }
3547
+
3548
+ #ensureIndicator() {
3549
+ if (this.#indicator) return this.#indicator;
3550
+
3551
+ const indicator = document.createElement("div");
3552
+ indicator.className = "fig-reorder-indicator";
3553
+ indicator.setAttribute("data-axis", this.#axis);
3554
+ document.body.appendChild(indicator);
3555
+ this.#indicator = indicator;
3556
+ return indicator;
3557
+ }
3558
+
3559
+ #updateIndicator(index, item) {
3560
+ const items = this.#getElementChildren();
3561
+ if (!this.#shouldShowIndicator(item, index)) {
3562
+ this.#removeIndicator();
3563
+ return;
3564
+ }
3565
+
3566
+ const bounds = this.#getReorderBounds(items);
3567
+ if (!bounds) {
3568
+ this.#removeIndicator();
3569
+ return;
3570
+ }
3571
+
3572
+ const indicator = this.#ensureIndicator();
3573
+ indicator.setAttribute("data-axis", this.#axis);
3574
+
3575
+ if (this.#axis === "horizontal") {
3576
+ let x;
3577
+ if (index <= 0) {
3578
+ x = items[0].getBoundingClientRect().left;
3579
+ } else if (index >= items.length) {
3580
+ x = items[items.length - 1].getBoundingClientRect().right;
3581
+ } else {
3582
+ x = items[index].getBoundingClientRect().left;
3583
+ }
3584
+
3585
+ indicator.style.left = `${x - 1}px`;
3586
+ indicator.style.top = `${bounds.top}px`;
3587
+ indicator.style.width = "2px";
3588
+ indicator.style.height = `${bounds.height}px`;
3589
+ return;
3590
+ }
3591
+
3592
+ let y;
3593
+ if (index <= 0) {
3594
+ y = items[0].getBoundingClientRect().top;
3595
+ } else if (index >= items.length) {
3596
+ y = items[items.length - 1].getBoundingClientRect().bottom;
3597
+ } else {
3598
+ y = items[index].getBoundingClientRect().top;
3599
+ }
3600
+
3601
+ indicator.style.left = `${bounds.left}px`;
3602
+ indicator.style.top = `${y - 1}px`;
3603
+ indicator.style.width = `${bounds.width}px`;
3604
+ indicator.style.height = "2px";
3605
+ }
3606
+
3607
+ #removeIndicator() {
3608
+ this.#indicator?.remove();
3609
+ this.#indicator = null;
3610
+ }
3611
+
3612
+ #moveItemToIndex(item, index) {
3613
+ const items = this.#getElementChildren();
3614
+ const clamped = Math.max(0, Math.min(index, items.length));
3615
+
3616
+ if (clamped >= items.length) {
3617
+ if (items[items.length - 1] !== item) this.appendChild(item);
3618
+ return;
3619
+ }
3620
+
3621
+ const ref = items[clamped];
3622
+ if (ref !== item) this.insertBefore(item, ref);
3623
+ }
3624
+
3625
+ #finishDrag(state, revert) {
3626
+ const { item, oldIndex, active, onMove, onUp, onKeyDown } = state;
3627
+
3628
+ window.removeEventListener("pointermove", onMove);
3629
+ window.removeEventListener("pointerup", onUp);
3630
+ window.removeEventListener("pointercancel", onUp);
3631
+ window.removeEventListener("keydown", onKeyDown);
3632
+
3633
+ if (active) {
3634
+ if (!revert) {
3635
+ this.#moveItemToIndex(item, state.targetIndex);
3636
+ const newIndex = this.#getElementChildren().indexOf(item);
3637
+ if (newIndex !== -1 && newIndex !== oldIndex) {
3638
+ this.dispatchEvent(
3639
+ new CustomEvent("reorder", {
3640
+ bubbles: true,
3641
+ detail: { oldIndex, newIndex, item },
3642
+ }),
3643
+ );
3644
+ }
3645
+ }
3646
+ }
3647
+
3648
+ item.classList.remove("dragging");
3649
+ FigReorder.#setDocumentDragging(false);
3650
+ this.#removeIndicator();
3651
+ if (this.#drag === state) this.#drag = null;
3652
+ }
3653
+
3654
+ #unbindAll() {
3655
+ for (const [, binding] of this.#bindings) {
3656
+ binding.target.removeEventListener(
3657
+ "pointerdown",
3658
+ binding.onPointerDown,
3659
+ true,
3660
+ );
3661
+ }
3662
+ this.#bindings.clear();
3663
+ }
3664
+
3665
+ #cancelDrag() {
3666
+ if (!this.#drag) return;
3667
+ this.#finishDrag(this.#drag, true);
3668
+ }
3669
+ }
3670
+
3671
+ customElements.define("fig-reorder", FigReorder);