@rogieking/figui3 3.8.2 → 3.9.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 +3 -0
- package/components.css +73 -1
- package/dist/fig.js +51 -46
- package/fig.js +125 -2
- package/package.json +1 -1
package/fig.js
CHANGED
|
@@ -2442,18 +2442,25 @@ customElements.define("fig-segment", FigSegment);
|
|
|
2442
2442
|
/**
|
|
2443
2443
|
* A custom segmented control container element.
|
|
2444
2444
|
* @attr {string} name - Identifier for the segmented control group
|
|
2445
|
+
* @attr {string} value - Selected segment value
|
|
2446
|
+
* @attr {boolean} animated - Enables animated selection indicator
|
|
2447
|
+
* @attr {"equal"|"auto"} sizing - Segment sizing mode
|
|
2445
2448
|
*/
|
|
2446
2449
|
class FigSegmentedControl extends HTMLElement {
|
|
2447
2450
|
#selectedSegment = null;
|
|
2448
2451
|
#boundHandleClick = this.handleClick.bind(this);
|
|
2449
2452
|
#mutationObserver = null;
|
|
2453
|
+
#resizeObserver = null;
|
|
2454
|
+
#indicatorFrame = 0;
|
|
2455
|
+
#indicatorSyncInstant = false;
|
|
2456
|
+
#hasRenderedIndicator = false;
|
|
2450
2457
|
|
|
2451
2458
|
constructor() {
|
|
2452
2459
|
super();
|
|
2453
2460
|
}
|
|
2454
2461
|
|
|
2455
2462
|
static get observedAttributes() {
|
|
2456
|
-
return ["disabled", "value"];
|
|
2463
|
+
return ["disabled", "value", "animated", "sizing"];
|
|
2457
2464
|
}
|
|
2458
2465
|
|
|
2459
2466
|
connectedCallback() {
|
|
@@ -2464,10 +2471,13 @@ class FigSegmentedControl extends HTMLElement {
|
|
|
2464
2471
|
this.getAttribute("disabled") !== "false",
|
|
2465
2472
|
);
|
|
2466
2473
|
this.#startSegmentObserver();
|
|
2474
|
+
this.#startResizeObserver();
|
|
2467
2475
|
|
|
2468
2476
|
// Defer initial selection so child segments are available.
|
|
2469
2477
|
requestAnimationFrame(() => {
|
|
2470
2478
|
this.#syncSelectionFromAttributes({ enforceFallback: true });
|
|
2479
|
+
this.#refreshResizeObserverTargets();
|
|
2480
|
+
this.#queueIndicatorSync({ forceInstant: true });
|
|
2471
2481
|
});
|
|
2472
2482
|
}
|
|
2473
2483
|
|
|
@@ -2475,6 +2485,14 @@ class FigSegmentedControl extends HTMLElement {
|
|
|
2475
2485
|
this.removeEventListener("click", this.#boundHandleClick);
|
|
2476
2486
|
this.#mutationObserver?.disconnect();
|
|
2477
2487
|
this.#mutationObserver = null;
|
|
2488
|
+
this.#resizeObserver?.disconnect();
|
|
2489
|
+
this.#resizeObserver = null;
|
|
2490
|
+
if (this.#indicatorFrame) {
|
|
2491
|
+
cancelAnimationFrame(this.#indicatorFrame);
|
|
2492
|
+
this.#indicatorFrame = 0;
|
|
2493
|
+
}
|
|
2494
|
+
this.#indicatorSyncInstant = false;
|
|
2495
|
+
this.#hasRenderedIndicator = false;
|
|
2478
2496
|
}
|
|
2479
2497
|
|
|
2480
2498
|
get selectedSegment() {
|
|
@@ -2492,7 +2510,9 @@ class FigSegmentedControl extends HTMLElement {
|
|
|
2492
2510
|
seg.removeAttribute("selected");
|
|
2493
2511
|
}
|
|
2494
2512
|
}
|
|
2495
|
-
this.#selectedSegment =
|
|
2513
|
+
this.#selectedSegment =
|
|
2514
|
+
segment instanceof HTMLElement && this.contains(segment) ? segment : null;
|
|
2515
|
+
this.#queueIndicatorSync();
|
|
2496
2516
|
}
|
|
2497
2517
|
|
|
2498
2518
|
get value() {
|
|
@@ -2559,10 +2579,98 @@ class FigSegmentedControl extends HTMLElement {
|
|
|
2559
2579
|
return false;
|
|
2560
2580
|
}
|
|
2561
2581
|
|
|
2582
|
+
#isAnimatedEnabled() {
|
|
2583
|
+
const rawAnimated = this.getAttribute("animated");
|
|
2584
|
+
if (rawAnimated === null) return false;
|
|
2585
|
+
if (rawAnimated === "") return true;
|
|
2586
|
+
return rawAnimated.trim().toLowerCase() === "true";
|
|
2587
|
+
}
|
|
2588
|
+
|
|
2589
|
+
#queueIndicatorSync({ forceInstant = false } = {}) {
|
|
2590
|
+
this.#indicatorSyncInstant = this.#indicatorSyncInstant || forceInstant;
|
|
2591
|
+
if (this.#indicatorFrame) return;
|
|
2592
|
+
|
|
2593
|
+
this.#indicatorFrame = requestAnimationFrame(() => {
|
|
2594
|
+
this.#indicatorFrame = 0;
|
|
2595
|
+
const nextForceInstant = this.#indicatorSyncInstant;
|
|
2596
|
+
this.#indicatorSyncInstant = false;
|
|
2597
|
+
this.#syncIndicator({ forceInstant: nextForceInstant });
|
|
2598
|
+
});
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
#syncIndicator({ forceInstant = false } = {}) {
|
|
2602
|
+
const isDisabled =
|
|
2603
|
+
this.hasAttribute("disabled") && this.getAttribute("disabled") !== "false";
|
|
2604
|
+
const isAnimated = this.#isAnimatedEnabled();
|
|
2605
|
+
const activeSegment =
|
|
2606
|
+
this.#selectedSegment && this.contains(this.#selectedSegment)
|
|
2607
|
+
? this.#selectedSegment
|
|
2608
|
+
: this.#getFirstSelectedSegment();
|
|
2609
|
+
|
|
2610
|
+
if (isDisabled || !isAnimated) {
|
|
2611
|
+
this.style.setProperty("--seg-indicator-opacity", "0");
|
|
2612
|
+
this.style.setProperty("--seg-indicator-transition-duration", "0ms");
|
|
2613
|
+
this.removeAttribute("data-indicator-ready");
|
|
2614
|
+
if (!isAnimated || isDisabled) {
|
|
2615
|
+
this.#hasRenderedIndicator = false;
|
|
2616
|
+
}
|
|
2617
|
+
return;
|
|
2618
|
+
}
|
|
2619
|
+
|
|
2620
|
+
if (!activeSegment) {
|
|
2621
|
+
// During transient mutation/paint windows, keep the previous indicator
|
|
2622
|
+
// state to avoid flicker while the next selected segment resolves.
|
|
2623
|
+
if (this.#hasRenderedIndicator) return;
|
|
2624
|
+
this.style.setProperty("--seg-indicator-opacity", "0");
|
|
2625
|
+
this.style.setProperty("--seg-indicator-transition-duration", "0ms");
|
|
2626
|
+
this.removeAttribute("data-indicator-ready");
|
|
2627
|
+
return;
|
|
2628
|
+
}
|
|
2629
|
+
|
|
2630
|
+
const hostRect = this.getBoundingClientRect();
|
|
2631
|
+
const segmentRect = activeSegment.getBoundingClientRect();
|
|
2632
|
+
if (hostRect.width <= 0 || segmentRect.width <= 0) {
|
|
2633
|
+
if (this.#hasRenderedIndicator) return;
|
|
2634
|
+
this.style.setProperty("--seg-indicator-opacity", "0");
|
|
2635
|
+
this.style.setProperty("--seg-indicator-transition-duration", "0ms");
|
|
2636
|
+
this.removeAttribute("data-indicator-ready");
|
|
2637
|
+
return;
|
|
2638
|
+
}
|
|
2639
|
+
|
|
2640
|
+
const x = Math.max(0, segmentRect.left - hostRect.left);
|
|
2641
|
+
this.style.setProperty("--seg-indicator-x", `${x}px`);
|
|
2642
|
+
this.style.setProperty("--seg-indicator-w", `${segmentRect.width}px`);
|
|
2643
|
+
this.style.setProperty("--seg-indicator-opacity", "1");
|
|
2644
|
+
this.style.setProperty(
|
|
2645
|
+
"--seg-indicator-transition-duration",
|
|
2646
|
+
!this.#hasRenderedIndicator || forceInstant ? "0ms" : "150ms",
|
|
2647
|
+
);
|
|
2648
|
+
this.setAttribute("data-indicator-ready", "true");
|
|
2649
|
+
this.#hasRenderedIndicator = true;
|
|
2650
|
+
}
|
|
2651
|
+
|
|
2652
|
+
#startResizeObserver() {
|
|
2653
|
+
this.#resizeObserver?.disconnect();
|
|
2654
|
+
this.#resizeObserver = new ResizeObserver(() => {
|
|
2655
|
+
this.#queueIndicatorSync();
|
|
2656
|
+
});
|
|
2657
|
+
this.#refreshResizeObserverTargets();
|
|
2658
|
+
}
|
|
2659
|
+
|
|
2660
|
+
#refreshResizeObserverTargets() {
|
|
2661
|
+
if (!this.#resizeObserver) return;
|
|
2662
|
+
this.#resizeObserver.disconnect();
|
|
2663
|
+
this.#resizeObserver.observe(this);
|
|
2664
|
+
this.querySelectorAll("fig-segment").forEach((segment) => {
|
|
2665
|
+
this.#resizeObserver?.observe(segment);
|
|
2666
|
+
});
|
|
2667
|
+
}
|
|
2668
|
+
|
|
2562
2669
|
#syncSelectionFromAttributes({ enforceFallback = false } = {}) {
|
|
2563
2670
|
const segments = this.querySelectorAll("fig-segment");
|
|
2564
2671
|
if (segments.length === 0) {
|
|
2565
2672
|
this.#selectedSegment = null;
|
|
2673
|
+
this.#queueIndicatorSync({ forceInstant: true });
|
|
2566
2674
|
return;
|
|
2567
2675
|
}
|
|
2568
2676
|
|
|
@@ -2623,6 +2731,7 @@ class FigSegmentedControl extends HTMLElement {
|
|
|
2623
2731
|
this.hasAttribute("disabled") &&
|
|
2624
2732
|
this.getAttribute("disabled") !== "false",
|
|
2625
2733
|
);
|
|
2734
|
+
this.#refreshResizeObserverTargets();
|
|
2626
2735
|
this.#syncSelectionFromAttributes({ enforceFallback: true });
|
|
2627
2736
|
}
|
|
2628
2737
|
});
|
|
@@ -2682,11 +2791,25 @@ class FigSegmentedControl extends HTMLElement {
|
|
|
2682
2791
|
|
|
2683
2792
|
if (name === "disabled") {
|
|
2684
2793
|
this.#applyDisabled(newValue !== null && newValue !== "false");
|
|
2794
|
+
this.#queueIndicatorSync({ forceInstant: true });
|
|
2685
2795
|
return;
|
|
2686
2796
|
}
|
|
2687
2797
|
|
|
2688
2798
|
if (name === "value") {
|
|
2689
2799
|
this.#syncSelectionFromAttributes({ enforceFallback: false });
|
|
2800
|
+
return;
|
|
2801
|
+
}
|
|
2802
|
+
|
|
2803
|
+
if (name === "animated") {
|
|
2804
|
+
if (!this.#isAnimatedEnabled()) {
|
|
2805
|
+
this.#hasRenderedIndicator = false;
|
|
2806
|
+
}
|
|
2807
|
+
this.#queueIndicatorSync({ forceInstant: true });
|
|
2808
|
+
return;
|
|
2809
|
+
}
|
|
2810
|
+
|
|
2811
|
+
if (name === "sizing") {
|
|
2812
|
+
this.#queueIndicatorSync({ forceInstant: true });
|
|
2690
2813
|
}
|
|
2691
2814
|
}
|
|
2692
2815
|
}
|
package/package.json
CHANGED