@kyralabs/m3e-motion 1.3.0 → 1.5.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.js CHANGED
@@ -379,16 +379,17 @@ var SpringAnimatable = class {
379
379
  }
380
380
  /**
381
381
  * Animates to `target` from the value and velocity at `now` (ms); a null spec (e.g. a spatial
382
- * spring under reduced motion) jumps there.
382
+ * spring under reduced motion) jumps there. `velocity` (units per second) overrides the current
383
+ * velocity, e.g. after the value was driven by something else (a drag, a tween).
383
384
  */
384
- animateTo(target, spec, now) {
385
+ animateTo(target, spec, now, velocity) {
385
386
  this.sample(now);
386
387
  if (spec === null) {
387
388
  this.snapTo(target);
388
389
  return;
389
390
  }
390
391
  this.#from = this.#value;
391
- this.#velocity0 = this.#velocity;
392
+ this.#velocity0 = velocity ?? this.#velocity;
392
393
  this.#target = target;
393
394
  this.#start = now;
394
395
  this.#spec = spec;
@@ -437,6 +438,226 @@ function menuNavigationTarget(key, count, current) {
437
438
  }
438
439
  }
439
440
 
441
+ // src/toolbar-navigation.ts
442
+ function toolbarNavigationTarget(key, count, current, orientation = "horizontal", rtl = false) {
443
+ if (count === 0) return null;
444
+ let step;
445
+ switch (key) {
446
+ case "Home":
447
+ return 0;
448
+ case "End":
449
+ return count - 1;
450
+ case "ArrowRight":
451
+ case "ArrowLeft":
452
+ if (orientation !== "horizontal") return null;
453
+ step = key === "ArrowRight" !== rtl ? 1 : -1;
454
+ break;
455
+ case "ArrowDown":
456
+ case "ArrowUp":
457
+ if (orientation !== "vertical") return null;
458
+ step = key === "ArrowDown" ? 1 : -1;
459
+ break;
460
+ default:
461
+ return null;
462
+ }
463
+ if (current < 0) return step > 0 ? 0 : count - 1;
464
+ return Math.min(Math.max(current + step, 0), count - 1);
465
+ }
466
+
467
+ // src/cubic-bezier.ts
468
+ var bezier = (p1, p2, t) => 3 * (1 - t) * (1 - t) * t * p1 + 3 * (1 - t) * t * t * p2 + t * t * t;
469
+ var cubicBezier = (a, b, c, d) => (fraction) => {
470
+ if (!(fraction > 0 && fraction < 1)) return fraction;
471
+ let lo = 0;
472
+ let hi = 1;
473
+ for (let i = 0; i < 30; i++) {
474
+ const mid = (lo + hi) / 2;
475
+ if (bezier(a, c, mid) < fraction) lo = mid;
476
+ else hi = mid;
477
+ }
478
+ return bezier(b, d, (lo + hi) / 2);
479
+ };
480
+ function parseCubicBezier(css) {
481
+ const values = /cubic-bezier\(([^)]*)\)/.exec(css)?.[1]?.split(",").map(Number);
482
+ return values?.length === 4 && values.every(Number.isFinite) ? cubicBezier(...values) : null;
483
+ }
484
+
485
+ // src/app-bar-scroll.ts
486
+ function appBarScrollState(height) {
487
+ return { heightOffsetLimit: -height, heightOffset: 0, contentOffset: 0, atStart: true };
488
+ }
489
+ function withHeightOffset(state, offset) {
490
+ return { ...state, heightOffset: Math.min(Math.max(offset, state.heightOffsetLimit), 0) };
491
+ }
492
+ function withHeight(state, height) {
493
+ return withHeightOffset({ ...state, heightOffsetLimit: -height }, state.heightOffset);
494
+ }
495
+ function collapsedFraction(state) {
496
+ return state.heightOffsetLimit !== 0 ? state.heightOffset / state.heightOffsetLimit + 0 : 0;
497
+ }
498
+ function overlappedFraction(state) {
499
+ if (!state.atStart && state.contentOffset === 0) return 1;
500
+ if (state.heightOffsetLimit === 0) return 0;
501
+ const limit = state.heightOffsetLimit;
502
+ const clamped = Math.min(Math.max(limit + Math.abs(state.contentOffset), limit), 0);
503
+ return 1 - clamped / limit;
504
+ }
505
+ function isScrolledUnder(state) {
506
+ return overlappedFraction(state) > 0.01;
507
+ }
508
+ function scrollAppBar(state, behavior, scrollTop, delta) {
509
+ const next = { ...state, contentOffset: -scrollTop, atStart: scrollTop <= 0 };
510
+ if (behavior === "pinned") return next;
511
+ if (behavior === "enter-always" || delta < 0) {
512
+ return withHeightOffset(next, state.heightOffset + delta);
513
+ }
514
+ const below = (top) => Math.max(0, -state.heightOffset - top);
515
+ return withHeightOffset(next, state.heightOffset + below(scrollTop) - below(scrollTop + delta));
516
+ }
517
+ function settleTarget(state) {
518
+ const fraction = collapsedFraction(state);
519
+ if (fraction < 0.01 || fraction === 1) return null;
520
+ if (!(state.heightOffset < 0 && state.heightOffset > state.heightOffsetLimit)) return null;
521
+ return fraction < 0.5 ? 0 : state.heightOffsetLimit;
522
+ }
523
+ var TOP_TITLE_ALPHA_EASING = [0.8, 0, 0.8, 0.15];
524
+ var FAST_OUT_LINEAR_IN_EASING = [0.4, 0, 1, 1];
525
+ var topTitleAlpha = /* @__PURE__ */ cubicBezier(...TOP_TITLE_ALPHA_EASING);
526
+ var fastOutLinearIn = /* @__PURE__ */ cubicBezier(...FAST_OUT_LINEAR_IN_EASING);
527
+ function twoRowsFrame(fraction) {
528
+ return {
529
+ collapsedTitleAlpha: topTitleAlpha(fraction),
530
+ expandedTitleAlpha: 1 - fraction,
531
+ containerColorFraction: fastOutLinearIn(fraction),
532
+ collapsedTitleHidden: fraction < 0.5
533
+ };
534
+ }
535
+
536
+ // src/floating-toolbar-scroll.ts
537
+ function floatingToolbarScrollState(offsetLimit = -Number.MAX_VALUE, offset = 0, contentOffset = 0) {
538
+ return withToolbarOffset({ offsetLimit, offset: 0, contentOffset }, offset);
539
+ }
540
+ function withToolbarOffset(state, offset) {
541
+ return { ...state, offset: Math.min(Math.max(offset, state.offsetLimit), 0) };
542
+ }
543
+ function withToolbarOffsetLimit(state, distance) {
544
+ return withToolbarOffset({ ...state, offsetLimit: -Math.max(distance, 0) }, state.offset);
545
+ }
546
+ function scrollFloatingToolbar(state, delta) {
547
+ return withToolbarOffset(
548
+ { ...state, contentOffset: state.contentOffset + delta },
549
+ state.offset + delta
550
+ );
551
+ }
552
+ function toolbarCollapsedFraction(state) {
553
+ return state.offsetLimit !== 0 ? state.offset / state.offsetLimit + 0 : 0;
554
+ }
555
+ function toolbarSettleTarget(state) {
556
+ return settleTarget({
557
+ heightOffsetLimit: state.offsetLimit,
558
+ heightOffset: state.offset,
559
+ contentOffset: state.contentOffset});
560
+ }
561
+ function toolbarExpansionScroll(expanded, collapseThreshold, expandThreshold, contentOffset = 0) {
562
+ return {
563
+ contentOffset,
564
+ threshold: expanded ? contentOffset - collapseThreshold : contentOffset + expandThreshold
565
+ };
566
+ }
567
+ function scrollToolbarExpansion(state, delta, collapseThreshold, expandThreshold) {
568
+ const contentOffset = state.contentOffset + delta;
569
+ if (delta < 0 && contentOffset <= state.threshold) {
570
+ return {
571
+ state: { contentOffset, threshold: contentOffset + expandThreshold },
572
+ change: "collapse"
573
+ };
574
+ }
575
+ if (delta > 0 && contentOffset >= state.threshold) {
576
+ return {
577
+ state: { contentOffset, threshold: contentOffset - collapseThreshold },
578
+ change: "expand"
579
+ };
580
+ }
581
+ return { state: { contentOffset, threshold: state.threshold }, change: null };
582
+ }
583
+
584
+ // src/progress-indicator.ts
585
+ var line = (t, [delay, duration], easing) => t <= delay ? 0 : t >= delay + duration ? 1 : easing((t - delay) / duration);
586
+ function linearIndeterminateFractions(elapsedMillis, timing) {
587
+ const t = Math.max(0, elapsedMillis) % timing.durationMillis;
588
+ return [
589
+ line(t, timing.firstTail, timing.easing),
590
+ line(t, timing.firstHead, timing.easing),
591
+ line(t, timing.secondTail, timing.easing),
592
+ line(t, timing.secondHead, timing.easing)
593
+ ];
594
+ }
595
+ function circularIndeterminateFrame(elapsedMillis, timing) {
596
+ const d = timing.durationMillis;
597
+ const t = Math.max(0, elapsedMillis) % d;
598
+ const steps = Math.round(d / timing.additionalDelayMillis);
599
+ const step = timing.additionalRotation / steps;
600
+ const index = Math.floor(t / timing.additionalDelayMillis);
601
+ const within = t - index * timing.additionalDelayMillis;
602
+ const additional = step * index + step * Math.min(1, within / timing.additionalDurationMillis);
603
+ const half = d / 2;
604
+ const range = timing.maxProgress - timing.minProgress;
605
+ const sweep = t <= half ? timing.minProgress + range * (t / half) : timing.maxProgress - range * timing.easing((t - half) / half);
606
+ return { rotation: timing.rotation * t / d + additional, sweep };
607
+ }
608
+ function waveTravelMillis(wavelength, waveSpeed, minMillis, vertexCount = 1) {
609
+ if (!(waveSpeed > 0 && wavelength > 0)) return Number.POSITIVE_INFINITY;
610
+ return Math.max(minMillis, Math.round(wavelength / waveSpeed * 1e3 * vertexCount));
611
+ }
612
+ var waveOffset = (elapsedMillis, travelMillis) => Number.isFinite(travelMillis) ? Math.max(0, elapsedMillis) % travelMillis / travelMillis : 0;
613
+ var indicatorAmplitude = (progress, minProgress, maxProgress) => progress <= minProgress || progress >= maxProgress ? 0 : 1;
614
+ var AmplitudeAnimation = class {
615
+ #from;
616
+ #to;
617
+ #start = 0;
618
+ #tween = null;
619
+ #value;
620
+ constructor(initial) {
621
+ this.#from = initial;
622
+ this.#to = initial;
623
+ this.#value = initial;
624
+ }
625
+ get value() {
626
+ return this.#value;
627
+ }
628
+ get target() {
629
+ return this.#to;
630
+ }
631
+ get isRunning() {
632
+ return this.#tween !== null;
633
+ }
634
+ /** Advances to `now` (ms). */
635
+ sample(now) {
636
+ const tween = this.#tween;
637
+ if (tween === null) return this.#value;
638
+ const fraction = tween.durationMillis > 0 ? (now - this.#start) / tween.durationMillis : 1;
639
+ if (fraction >= 1) {
640
+ this.#tween = null;
641
+ this.#value = this.#to;
642
+ } else {
643
+ this.#value = this.#from + (this.#to - this.#from) * tween.easing(Math.max(0, fraction));
644
+ }
645
+ return this.#value;
646
+ }
647
+ /** Requests `target` at `now`; returns true when a tween started. */
648
+ update(target, now, increase, decrease) {
649
+ this.sample(now);
650
+ if (this.#tween !== null || target === this.#to) return false;
651
+ const tween = this.#value < target ? increase : decrease;
652
+ this.#from = this.#value;
653
+ this.#to = target;
654
+ this.#start = now;
655
+ this.#tween = tween;
656
+ if (tween.durationMillis <= 0) this.sample(now);
657
+ return true;
658
+ }
659
+ };
660
+
440
661
  // src/loading-indicator.ts
441
662
  var FULL_ROTATION = 360;
442
663
  var QUARTER_ROTATION = FULL_ROTATION / 4;
@@ -572,38 +793,197 @@ function menuPosition(input) {
572
793
  };
573
794
  }
574
795
 
575
- // src/app-bar-scroll.ts
576
- function appBarScrollState(height) {
577
- return { heightOffsetLimit: -height, heightOffset: 0, contentOffset: 0, atStart: true };
578
- }
579
- function withHeightOffset(state, offset) {
580
- return { ...state, heightOffset: Math.min(Math.max(offset, state.heightOffsetLimit), 0) };
581
- }
582
- function withHeight(state, height) {
583
- return withHeightOffset({ ...state, heightOffsetLimit: -height }, state.heightOffset);
584
- }
585
- function collapsedFraction(state) {
586
- return state.heightOffsetLimit !== 0 ? state.heightOffset / state.heightOffsetLimit + 0 : 0;
587
- }
588
- function overlappedFraction(state) {
589
- if (!state.atStart && state.contentOffset === 0) return 1;
590
- if (state.heightOffsetLimit === 0) return 0;
591
- const limit = state.heightOffsetLimit;
592
- const clamped = Math.min(Math.max(limit + Math.abs(state.contentOffset), limit), 0);
593
- return 1 - clamped / limit;
594
- }
595
- function isScrolledUnder(state) {
596
- return overlappedFraction(state) > 0.01;
597
- }
598
- function scrollAppBar(state, behavior, scrollTop, delta) {
599
- const next = { ...state, contentOffset: -scrollTop, atStart: scrollTop <= 0 };
600
- return behavior === "enter-always" ? withHeightOffset(next, state.heightOffset + delta) : next;
796
+ // src/search-bar.ts
797
+ var SEARCH_BAR_EXPANDED_THRESHOLD = 0.02;
798
+ var SEARCH_DROPDOWN_VISIBLE_PROGRESS = 0.1;
799
+ var TweenAnimatable = class {
800
+ #value;
801
+ #from;
802
+ #target;
803
+ #start = 0;
804
+ #spec = null;
805
+ constructor(value) {
806
+ this.#value = value;
807
+ this.#from = value;
808
+ this.#target = value;
809
+ }
810
+ get value() {
811
+ return this.#value;
812
+ }
813
+ get target() {
814
+ return this.#target;
815
+ }
816
+ get isRunning() {
817
+ return this.#spec !== null;
818
+ }
819
+ snapTo(value) {
820
+ this.#spec = null;
821
+ this.#value = value;
822
+ this.#from = value;
823
+ this.#target = value;
824
+ }
825
+ animateTo(target, spec, now) {
826
+ this.sample(now);
827
+ if (spec === null || spec.delay + spec.duration <= 0) {
828
+ this.snapTo(target);
829
+ return;
830
+ }
831
+ this.#from = this.#value;
832
+ this.#target = target;
833
+ this.#start = now;
834
+ this.#spec = spec;
835
+ }
836
+ sample(now) {
837
+ const spec = this.#spec;
838
+ if (spec === null) return this.#value;
839
+ const elapsed = now - this.#start - spec.delay;
840
+ if (elapsed >= spec.duration) {
841
+ this.snapTo(this.#target);
842
+ return this.#value;
843
+ }
844
+ const fraction = elapsed <= 0 ? 0 : spec.easing(elapsed / spec.duration);
845
+ this.#value = this.#from + (this.#target - this.#from) * fraction;
846
+ return this.#value;
847
+ }
848
+ };
849
+ var SearchBarAnimation = class {
850
+ #progress;
851
+ #content;
852
+ #slide = new SpringAnimatable(0);
853
+ #specs = null;
854
+ #dropdownShown = false;
855
+ #dropdownVisible = false;
856
+ constructor(expanded = false) {
857
+ this.#progress = new SpringAnimatable(expanded ? 1 : 0);
858
+ this.#content = new TweenAnimatable(expanded ? 1 : 0);
859
+ this.#dropdownShown = expanded;
860
+ this.#dropdownVisible = expanded;
861
+ }
862
+ /** SearchBarState.targetValue. */
863
+ get isTargetExpanded() {
864
+ return this.#progress.target === 1;
865
+ }
866
+ /** SearchBarState.currentValue: expanded until the value drops to the 0.02 tolerance. */
867
+ get isExpanded() {
868
+ return this.#progress.value > SEARCH_BAR_EXPANDED_THRESHOLD;
869
+ }
870
+ get isRunning() {
871
+ return this.#progress.isRunning || this.#content.isRunning || this.#slide.isRunning;
872
+ }
873
+ /** animateToExpanded() at `now` (ms). */
874
+ expand(specs, now) {
875
+ this.#specs = specs;
876
+ this.#progress.animateTo(1, specs.expand, now);
877
+ this.#content.animateTo(1, specs.contentFadeIn, now);
878
+ }
879
+ /** animateToCollapsed() at `now` (ms). */
880
+ collapse(specs, now) {
881
+ this.#specs = specs;
882
+ this.#content.animateTo(0, specs.contentFadeOut, now);
883
+ this.#progress.animateTo(0, specs.collapse, now);
884
+ }
885
+ /** Jumps to collapsed (0) or expanded (1), stopping every animation. */
886
+ snapTo(expanded) {
887
+ this.#progress.snapTo(expanded ? 1 : 0);
888
+ this.#content.snapTo(expanded ? 1 : 0);
889
+ this.#slide.snapTo(0);
890
+ this.#dropdownShown = expanded;
891
+ this.#dropdownVisible = expanded;
892
+ }
893
+ /**
894
+ * Advances to `now`. `dropdownHeight` (px, the drop-down's full height) enables the with-gap
895
+ * slide: AnimatedVisibility(visible = progress > 0.1 && target == Expanded) slides in from minus
896
+ * half the height on the expand spring and out to it on the collapse spring.
897
+ */
898
+ sample(now, dropdownHeight) {
899
+ const value = this.#progress.sample(now);
900
+ const progress = Math.min(Math.max(value, 0), 1);
901
+ const contentProgress = Math.min(Math.max(this.#content.sample(now), 0), 1);
902
+ if (dropdownHeight !== void 0) {
903
+ const hidden = Math.round(-dropdownHeight / 2);
904
+ const visible = progress > SEARCH_DROPDOWN_VISIBLE_PROGRESS && this.isTargetExpanded;
905
+ if (visible !== this.#dropdownVisible) {
906
+ this.#dropdownVisible = visible;
907
+ if (visible) {
908
+ if (!this.#dropdownShown) this.#slide.snapTo(hidden);
909
+ this.#dropdownShown = true;
910
+ this.#slide.animateTo(0, this.#specs?.expand ?? null, now);
911
+ } else if (this.#dropdownShown) {
912
+ this.#slide.animateTo(hidden, this.#specs?.collapse ?? null, now);
913
+ }
914
+ }
915
+ this.#slide.sample(now);
916
+ if (!visible && this.#dropdownShown && !this.#slide.isRunning) this.#dropdownShown = false;
917
+ }
918
+ return {
919
+ value,
920
+ progress,
921
+ contentProgress,
922
+ slide: this.#slide.value,
923
+ dropdownShown: this.#dropdownShown
924
+ };
925
+ }
926
+ };
927
+ var lerp = (a, b, t) => a + (b - a) * t;
928
+ function searchViewFrame(input) {
929
+ const { variant, collapsed: c, window: w, insetTop, metrics: m } = input;
930
+ const { value, progress: p } = input.sample;
931
+ const none = { radius: 0, alpha: 1, minHeight: 0, maxHeight: 0 };
932
+ if (variant === "docked" || variant === "docked-with-gap") {
933
+ const ratio = variant === "docked" ? m.dockedMaxHeightRatio : m.withGapMaxHeightRatio;
934
+ const tableMax = w.height * ratio;
935
+ const maxHeight = lerp(c.height, tableMax, Math.max(value, 0));
936
+ const minHeight = Math.min(Math.min(m.dockedMinHeight, tableMax), maxHeight);
937
+ if (variant === "docked") {
938
+ return {
939
+ surface: { ...c, radius: 0, alpha: 1, minHeight, maxHeight },
940
+ field: { x: c.x, y: c.y, width: c.width },
941
+ results: { x: c.x, y: c.y + c.height, width: c.width, maxHeight: maxHeight - c.height }
942
+ };
943
+ }
944
+ return {
945
+ surface: { x: 0, y: 0, width: w.width, height: w.height, ...none, alpha: p },
946
+ field: { x: c.x, y: c.y, width: c.width },
947
+ results: {
948
+ x: c.x,
949
+ y: c.y + c.height + m.dropdownGap,
950
+ width: c.width,
951
+ maxHeight: Math.max(0, maxHeight - c.height - m.dropdownGap)
952
+ }
953
+ };
954
+ }
955
+ const contained = variant === "full-screen-contained";
956
+ const width = contained ? w.width : lerp(c.width, w.width, p);
957
+ const height = contained ? w.height : lerp(c.height, w.height, p);
958
+ const offsetX = contained ? 0 : lerp(c.x, 0, p);
959
+ const padding = contained ? m.containedHorizontalPadding : 0;
960
+ const fieldWidth = lerp(c.width, width - 2 * padding, value);
961
+ const centerX2 = lerp(c.x + c.width / 2, offsetX + width / 2, value);
962
+ const topPadding = insetTop + (contained ? m.containedTopPadding : m.verticalPadding);
963
+ const animatedTopPadding = topPadding * Math.min(p, 1);
964
+ const bottomPadding = contained ? m.verticalPadding : m.verticalPadding * p;
965
+ const offsetY = lerp(c.y, 0, p);
966
+ const fieldY = offsetY + animatedTopPadding;
967
+ return {
968
+ surface: contained ? { x: 0, y: 0, width, height, ...none, alpha: p } : { x: offsetX, y: offsetY, width, height, ...none, radius: m.cornerRadius * (1 - p) },
969
+ field: { x: Math.round(centerX2 - fieldWidth / 2), y: fieldY, width: fieldWidth },
970
+ results: {
971
+ x: offsetX,
972
+ y: fieldY + c.height + bottomPadding,
973
+ width,
974
+ maxHeight: Math.max(0, height - (c.height + animatedTopPadding + bottomPadding))
975
+ }
976
+ };
601
977
  }
602
- function settleTarget(state) {
603
- const fraction = collapsedFraction(state);
604
- if (fraction < 0.01 || fraction === 1) return null;
605
- if (!(state.heightOffset < 0 && state.heightOffset > state.heightOffsetLimit)) return null;
606
- return fraction < 0.5 ? 0 : state.heightOffsetLimit;
978
+ function searchNavigationTarget(key, enabled, active) {
979
+ const indices = enabled.flatMap((on, i) => on ? [i] : []);
980
+ const first = indices[0];
981
+ const last = indices.at(-1);
982
+ if (first === void 0 || last === void 0) return null;
983
+ const at2 = indices.indexOf(active);
984
+ if (key === "ArrowDown") return at2 === -1 ? first : indices[at2 + 1] ?? last;
985
+ if (key === "ArrowUp") return at2 === -1 ? last : at2 === 0 ? -1 : indices[at2 - 1] ?? first;
986
+ return null;
607
987
  }
608
988
 
609
989
  // src/button-group.ts
@@ -652,6 +1032,1231 @@ function buttonGroupWidths(items, pressed, expandedRatio, visibleCount = items.l
652
1032
  return widths.slice(0, last);
653
1033
  }
654
1034
 
655
- export { DEFAULT_DISPLACEMENT_THRESHOLD, SpringAnimatable, SpringSimulation, animatedInt, appBarScrollState, buttonGroupVisibleCount, buttonGroupWidths, collapsedFraction, dampingCoefficient, dampingRatioFromCoefficient, estimateAnimationDurationMillis, estimateAnimationDurationMillisDouble, fabMenuItemVisible, indeterminateFrame, initialIndeterminateFrame, isScrolledUnder, menuNavigationTarget, menuPosition, menuTransformOrigin, morphSpringDurationMillis, overlappedFraction, scrollAppBar, settleTarget, springDurationMillis, springToLinear, springValue, springVelocity, withHeight, withHeightOffset };
1035
+ // src/window-size-class.ts
1036
+ var WINDOW_WIDTH_BREAKPOINTS = /* @__PURE__ */ Object.freeze({
1037
+ compact: 0,
1038
+ medium: 600,
1039
+ expanded: 840,
1040
+ large: 1200,
1041
+ "extra-large": 1600
1042
+ });
1043
+ var WINDOW_HEIGHT_BREAKPOINTS = /* @__PURE__ */ Object.freeze({ compact: 0, medium: 480, expanded: 900 });
1044
+ var WIDTH_CLASSES = [
1045
+ "compact",
1046
+ "medium",
1047
+ "expanded",
1048
+ "large",
1049
+ "extra-large"
1050
+ ];
1051
+ var HEIGHT_CLASSES = ["compact", "medium", "expanded"];
1052
+ function bucket(size, compact, classes, bounds) {
1053
+ let match = compact;
1054
+ for (const c of classes) if (size >= bounds[c]) match = c;
1055
+ return match;
1056
+ }
1057
+ function windowWidthSizeClass(widthPx, options = {}) {
1058
+ const classes = options.largeAndExtraLargeWidth === false ? WIDTH_CLASSES.slice(0, 3) : WIDTH_CLASSES;
1059
+ return bucket(widthPx, "compact", classes, WINDOW_WIDTH_BREAKPOINTS);
1060
+ }
1061
+ function windowHeightSizeClass(heightPx) {
1062
+ return bucket(heightPx, "compact", HEIGHT_CLASSES, WINDOW_HEIGHT_BREAKPOINTS);
1063
+ }
1064
+ function windowSizeClass(size, options = {}) {
1065
+ const widthClass = windowWidthSizeClass(size.width, options);
1066
+ const heightClass = windowHeightSizeClass(size.height);
1067
+ return {
1068
+ widthClass,
1069
+ heightClass,
1070
+ minWidth: WINDOW_WIDTH_BREAKPOINTS[widthClass],
1071
+ minHeight: WINDOW_HEIGHT_BREAKPOINTS[heightClass]
1072
+ };
1073
+ }
1074
+ function sameWindowSizeClass(a, b) {
1075
+ return a.minWidth === b.minWidth && a.minHeight === b.minHeight;
1076
+ }
1077
+ var widthBound = (b) => typeof b === "number" ? b : WINDOW_WIDTH_BREAKPOINTS[b];
1078
+ var heightBound = (b) => typeof b === "number" ? b : WINDOW_HEIGHT_BREAKPOINTS[b];
1079
+ function isWidthAtLeastBreakpoint(sizeClass, breakpoint) {
1080
+ return sizeClass.minWidth >= widthBound(breakpoint);
1081
+ }
1082
+ function isHeightAtLeastBreakpoint(sizeClass, breakpoint) {
1083
+ return sizeClass.minHeight >= heightBound(breakpoint);
1084
+ }
1085
+ function isAtLeastBreakpoint(sizeClass, widthBreakpoint, heightBreakpoint) {
1086
+ return isWidthAtLeastBreakpoint(sizeClass, widthBreakpoint) && isHeightAtLeastBreakpoint(sizeClass, heightBreakpoint);
1087
+ }
1088
+ var PARTITION_SPACER = 24;
1089
+ var DEFAULT_PREFERRED_WIDTH = 360;
1090
+ var DEFAULT_PREFERRED_WIDTH_XL = 412;
1091
+ var DEFAULT_PREFERRED_HEIGHT = 420;
1092
+ function paneScaffoldDirective(sizeClass, options = {}) {
1093
+ let maxHorizontalPartitions;
1094
+ let horizontalPartitionSpacerSize;
1095
+ let defaultPanePreferredWidth;
1096
+ switch (sizeClass.minWidth) {
1097
+ case WINDOW_WIDTH_BREAKPOINTS.compact:
1098
+ case WINDOW_WIDTH_BREAKPOINTS.medium:
1099
+ maxHorizontalPartitions = 1;
1100
+ horizontalPartitionSpacerSize = 0;
1101
+ defaultPanePreferredWidth = DEFAULT_PREFERRED_WIDTH;
1102
+ break;
1103
+ case WINDOW_WIDTH_BREAKPOINTS.expanded:
1104
+ maxHorizontalPartitions = 2;
1105
+ horizontalPartitionSpacerSize = PARTITION_SPACER;
1106
+ defaultPanePreferredWidth = DEFAULT_PREFERRED_WIDTH;
1107
+ break;
1108
+ default:
1109
+ maxHorizontalPartitions = 3;
1110
+ horizontalPartitionSpacerSize = PARTITION_SPACER;
1111
+ defaultPanePreferredWidth = DEFAULT_PREFERRED_WIDTH_XL;
1112
+ }
1113
+ const stacked = options.isTabletop === true || maxHorizontalPartitions === 1 && sizeClass.minHeight === WINDOW_HEIGHT_BREAKPOINTS.expanded;
1114
+ return {
1115
+ maxHorizontalPartitions,
1116
+ horizontalPartitionSpacerSize,
1117
+ maxVerticalPartitions: stacked ? 2 : 1,
1118
+ verticalPartitionSpacerSize: stacked ? PARTITION_SPACER : 0,
1119
+ defaultPanePreferredWidth,
1120
+ defaultPanePreferredHeight: DEFAULT_PREFERRED_HEIGHT
1121
+ };
1122
+ }
1123
+ var NAVIGATION_SUITE_TYPES = [
1124
+ "short-navigation-bar-compact",
1125
+ "short-navigation-bar-medium",
1126
+ "wide-navigation-rail-collapsed",
1127
+ "wide-navigation-rail-expanded",
1128
+ "navigation-drawer",
1129
+ "none"
1130
+ ];
1131
+ function isNavigationBarType(type) {
1132
+ return type === "short-navigation-bar-compact" || type === "short-navigation-bar-medium";
1133
+ }
1134
+ var NAVIGATION_SUITE_EXPANDED_FROM = "large";
1135
+ function navigationSuiteType(sizeClass, options = {}) {
1136
+ if (sizeClass.minWidth === WINDOW_WIDTH_BREAKPOINTS.compact)
1137
+ return "short-navigation-bar-compact";
1138
+ if (options.isTabletop === true || sizeClass.minHeight === WINDOW_HEIGHT_BREAKPOINTS.compact) {
1139
+ return "short-navigation-bar-medium";
1140
+ }
1141
+ const { expandedFrom } = options;
1142
+ if (expandedFrom !== void 0 && expandedFrom !== null && isWidthAtLeastBreakpoint(sizeClass, expandedFrom)) {
1143
+ return options.expandedType ?? "wide-navigation-rail-expanded";
1144
+ }
1145
+ return "wide-navigation-rail-collapsed";
1146
+ }
1147
+
1148
+ // src/navigation-suite.ts
1149
+ var HIDDEN = 0;
1150
+ var VISIBLE = 1;
1151
+ var NavigationSuiteScaffoldAnimation = class {
1152
+ #state;
1153
+ #progress;
1154
+ #spec = null;
1155
+ /** `hidden`: rememberNavigationSuiteScaffoldState(initialValue = Hidden); rendered at rest. */
1156
+ constructor(hidden = false) {
1157
+ const value = hidden ? HIDDEN : VISIBLE;
1158
+ this.#state = new SpringAnimatable(value);
1159
+ this.#progress = new SpringAnimatable(value);
1160
+ }
1161
+ /** NavigationSuiteScaffoldState.targetValue == Hidden. */
1162
+ get targetHidden() {
1163
+ return this.#state.target !== VISIBLE;
1164
+ }
1165
+ /** NavigationSuiteScaffoldState.currentValue == Hidden (anything but exactly Visible). */
1166
+ get currentHidden() {
1167
+ return this.#state.value !== VISIBLE;
1168
+ }
1169
+ /** NavigationSuiteScaffoldState.isAnimating (the state's spring, not the layout's). */
1170
+ get isAnimating() {
1171
+ return this.#state.isRunning;
1172
+ }
1173
+ /** NavigationSuiteScaffoldState.hide(). */
1174
+ hide(spec, now) {
1175
+ this.#animate(HIDDEN, spec, now);
1176
+ }
1177
+ /** NavigationSuiteScaffoldState.show(). */
1178
+ show(spec, now) {
1179
+ this.#animate(VISIBLE, spec, now);
1180
+ }
1181
+ /** NavigationSuiteScaffoldState.toggle(): towards the opposite of the target. */
1182
+ toggle(spec, now) {
1183
+ this.#animate(this.targetHidden ? VISIBLE : HIDDEN, spec, now);
1184
+ }
1185
+ /** NavigationSuiteScaffoldState.snapTo(): the state jumps; the layout animates from `now`. */
1186
+ snapTo(hidden, spec, now) {
1187
+ this.#spec = spec;
1188
+ this.#state.snapTo(hidden ? HIDDEN : VISIBLE);
1189
+ this.sample(now);
1190
+ }
1191
+ #animate(target, spec, now) {
1192
+ this.#spec = spec;
1193
+ this.#state.animateTo(target, spec, now);
1194
+ this.sample(now);
1195
+ }
1196
+ /** Advances both springs to `now`; the layout retargets when currentValue changes. */
1197
+ sample(now) {
1198
+ this.#state.sample(now);
1199
+ const target = this.currentHidden ? HIDDEN : VISIBLE;
1200
+ if (this.#progress.target !== target) this.#progress.animateTo(target, this.#spec, now);
1201
+ const progress = this.#progress.sample(now);
1202
+ return { progress, running: this.#state.isRunning || this.#progress.isRunning };
1203
+ }
1204
+ };
1205
+ function navigationSuiteOffset(size, progress) {
1206
+ return size - Math.trunc(size * progress);
1207
+ }
1208
+
1209
+ // src/wide-navigation-rail.ts
1210
+ var MODAL_RAIL_EXPANDED_PROGRESS = 0.3;
1211
+ var to = (expanded) => expanded ? 1 : 0;
1212
+ var WideNavigationRailAnimation = class {
1213
+ modal;
1214
+ hideOnCollapse;
1215
+ #expanded;
1216
+ #item;
1217
+ #width;
1218
+ #layout;
1219
+ #position;
1220
+ #scrim = new SpringAnimatable(0);
1221
+ #slide;
1222
+ #specs = null;
1223
+ #open = false;
1224
+ #modalExpanded = false;
1225
+ /** hideOnCollapse: positionProgress waits for the slide out (animateToDismiss). */
1226
+ #closeAfterSlide = false;
1227
+ constructor(options) {
1228
+ this.modal = Boolean(options.modal);
1229
+ this.hideOnCollapse = this.modal && Boolean(options.hideOnCollapse);
1230
+ this.#expanded = options.expanded;
1231
+ const v = to(options.expanded);
1232
+ const laidOut = this.hideOnCollapse ? 1 : this.modal ? options.expanded ? 1 : 0 : v;
1233
+ this.#item = new SpringAnimatable(this.hideOnCollapse ? 1 : v);
1234
+ this.#width = new SpringAnimatable(laidOut, options.widthThreshold ?? 0.01 / 264);
1235
+ this.#layout = new SpringAnimatable(laidOut, options.layoutThreshold ?? 0.01 / 16);
1236
+ this.#position = new SpringAnimatable(this.modal ? v : 0);
1237
+ this.#slide = new SpringAnimatable(v, 0.01 / 360);
1238
+ if (this.modal && options.expanded) {
1239
+ this.#open = true;
1240
+ this.#modalExpanded = true;
1241
+ this.#scrim.snapTo(1);
1242
+ }
1243
+ }
1244
+ /** WideNavigationRailState.targetValue. */
1245
+ get expanded() {
1246
+ return this.#expanded;
1247
+ }
1248
+ get isRunning() {
1249
+ return this.#item.isRunning || this.#width.isRunning || this.#layout.isRunning || this.#position.isRunning || this.#scrim.isRunning || this.#slide.isRunning || this.#closeAfterSlide;
1250
+ }
1251
+ /** expand() / collapse() at `now` (ms). */
1252
+ setExpanded(expanded, specs, now) {
1253
+ this.sample(now);
1254
+ this.#specs = specs;
1255
+ if (expanded === this.#expanded) return;
1256
+ this.#expanded = expanded;
1257
+ const v = to(expanded);
1258
+ if (!this.modal) {
1259
+ this.#item.animateTo(v, specs.iconPosition, now);
1260
+ this.#width.animateTo(v, specs.width, now);
1261
+ this.#layout.animateTo(v, specs.width, now);
1262
+ return;
1263
+ }
1264
+ if (this.hideOnCollapse) {
1265
+ this.#slide.animateTo(v, specs.slide, now);
1266
+ this.#scrim.animateTo(v, specs.scrim, now);
1267
+ if (expanded) {
1268
+ this.#closeAfterSlide = false;
1269
+ this.#position.animateTo(1, specs.position, now);
1270
+ } else {
1271
+ this.#closeAfterSlide = true;
1272
+ }
1273
+ this.sample(now);
1274
+ return;
1275
+ }
1276
+ this.#item.animateTo(v, specs.iconPosition, now);
1277
+ this.#position.animateTo(v, specs.position, now);
1278
+ this.sample(now);
1279
+ }
1280
+ /** WideNavigationRailState.snapTo. */
1281
+ snapTo(expanded) {
1282
+ this.#expanded = expanded;
1283
+ const v = to(expanded);
1284
+ this.#closeAfterSlide = false;
1285
+ if (!this.modal) {
1286
+ for (const a of [this.#item, this.#width, this.#layout]) a.snapTo(v);
1287
+ return;
1288
+ }
1289
+ this.#position.snapTo(v);
1290
+ this.#slide.snapTo(v);
1291
+ this.#scrim.snapTo(v);
1292
+ this.#open = expanded;
1293
+ this.#modalExpanded = expanded;
1294
+ if (!this.hideOnCollapse) {
1295
+ for (const a of [this.#item, this.#width, this.#layout]) a.snapTo(v);
1296
+ }
1297
+ }
1298
+ sample(now) {
1299
+ const item = this.#item.sample(now);
1300
+ if (this.modal) this.#sampleModal(now);
1301
+ const width = this.#width.sample(now);
1302
+ const layout = this.#layout.sample(now);
1303
+ return {
1304
+ item: Math.max(item, 0),
1305
+ // iconPositionProgress.coerceAtLeast(0f) (NavigationItem.kt L544)
1306
+ width,
1307
+ layout,
1308
+ open: this.#open,
1309
+ railExpanded: !this.modal ? this.#expanded : this.#open && (this.hideOnCollapse || this.#modalExpanded),
1310
+ scrim: Math.min(Math.max(this.#scrim.value, 0), 1),
1311
+ slide: this.#slide.value,
1312
+ running: this.isRunning
1313
+ };
1314
+ }
1315
+ #sampleModal(now) {
1316
+ const specs = this.#specs;
1317
+ if (this.hideOnCollapse) {
1318
+ this.#slide.sample(now);
1319
+ this.#scrim.sample(now);
1320
+ if (this.#closeAfterSlide && !this.#slide.isRunning) {
1321
+ this.#closeAfterSlide = false;
1322
+ this.#position.animateTo(0, specs?.position ?? null, now);
1323
+ }
1324
+ this.#open = this.#position.sample(now) > 0;
1325
+ return;
1326
+ }
1327
+ const position = this.#position.sample(now);
1328
+ const open = position > 0;
1329
+ const modalExpanded = position >= MODAL_RAIL_EXPANDED_PROGRESS;
1330
+ if (!open) {
1331
+ this.#width.snapTo(0);
1332
+ this.#layout.snapTo(0);
1333
+ this.#scrim.snapTo(0);
1334
+ } else if (!this.#open) {
1335
+ const v = to(modalExpanded);
1336
+ this.#width.snapTo(v);
1337
+ this.#layout.snapTo(v);
1338
+ this.#scrim.snapTo(v);
1339
+ } else if (modalExpanded !== this.#modalExpanded) {
1340
+ const v = to(modalExpanded);
1341
+ this.#width.animateTo(v, specs?.modalWidth ?? null, now);
1342
+ this.#layout.animateTo(v, specs?.width ?? null, now);
1343
+ this.#scrim.animateTo(v, specs?.scrim ?? null, now);
1344
+ }
1345
+ this.#scrim.sample(now);
1346
+ this.#open = open;
1347
+ this.#modalExpanded = open && modalExpanded;
1348
+ }
1349
+ };
1350
+ var lerp2 = (a, b, t) => a + (b - a) * t;
1351
+ function wideNavigationRailItemWidth(content, progress, m, maxWidth) {
1352
+ const pad = m.itemHorizontalPadding;
1353
+ const indicatorPadding = lerp2(m.collapsedIndicatorPadding, m.expandedIndicatorPadding, progress);
1354
+ if (content === null)
1355
+ return Math.min(m.iconSize + 2 * m.collapsedIndicatorPadding + 2 * pad, maxWidth);
1356
+ const gapAndLabel = content - m.iconSize - 2 * m.expandedIndicatorPadding;
1357
+ const label = gapAndLabel - m.iconLabelSpace;
1358
+ const widthTop = Math.min(Math.max(label, m.iconSize + 2 * pad + 2 * indicatorPadding), maxWidth);
1359
+ const ripple = lerp2(m.iconSize, m.iconSize + gapAndLabel, progress) + 2 * indicatorPadding;
1360
+ const widthStart = Math.min(ripple + pad, maxWidth);
1361
+ return widthTop + (widthStart - widthTop) * progress;
1362
+ }
1363
+ function wideNavigationRailWidth(input) {
1364
+ const m = input.metrics;
1365
+ const max = Math.min(input.maxWidth ?? m.expandedMaxWidth, m.expandedMaxWidth);
1366
+ const minWidth = Math.min(lerp2(m.collapsedWidth, m.expandedMinWidth, input.widthProgress), max);
1367
+ const fullRange = lerp2(m.collapsedWidth, m.expandedMaxWidth, input.widthProgress);
1368
+ const expandedMin = Math.min(m.expandedMinWidth, max);
1369
+ if (input.expanded) {
1370
+ const itemMax = max - m.itemHorizontalPadding;
1371
+ let widestItem = 0;
1372
+ for (const content of input.items) {
1373
+ const w = wideNavigationRailItemWidth(content, input.itemProgress, m, itemMax);
1374
+ widestItem = Math.max(widestItem, w + m.itemHorizontalPadding);
1375
+ }
1376
+ const widest = Math.max(widestItem, input.header);
1377
+ if (widest > minWidth && widest > expandedMin) {
1378
+ const constrain = Math.min(Math.max(widest, expandedMin), max);
1379
+ return { width: Math.min(fullRange, constrain), wide: true };
1380
+ }
1381
+ return { width: minWidth, wide: input.wide };
1382
+ }
1383
+ if (input.wide) {
1384
+ const upper = Math.max(input.previousWidth, minWidth);
1385
+ return { width: Math.min(Math.max(fullRange, minWidth), upper), wide: true };
1386
+ }
1387
+ return { width: minWidth, wide: false };
1388
+ }
1389
+
1390
+ // src/navigation-drawer.ts
1391
+ var DRAWER_POSITIONAL_THRESHOLD = 0.5;
1392
+ var DRAWER_VELOCITY_THRESHOLD = 400;
1393
+ var DRAWER_SETTLE_MILLIS = 256;
1394
+ var DECAY_FRICTION = -4.2;
1395
+ var DECAY_VELOCITY_THRESHOLD = 0.1;
1396
+ function exponentialDecayTarget(value, velocity) {
1397
+ if (Math.abs(velocity) <= DECAY_VELOCITY_THRESHOLD) return value;
1398
+ const duration = Math.log(Math.abs(DECAY_VELOCITY_THRESHOLD / velocity)) / DECAY_FRICTION * 1e3;
1399
+ return value - velocity / DECAY_FRICTION + velocity / DECAY_FRICTION * Math.exp(DECAY_FRICTION * duration / 1e3);
1400
+ }
1401
+ function drawerSettlesOpen(offset, velocity, width, velocityThreshold = DRAWER_VELOCITY_THRESHOLD) {
1402
+ const closed = -width;
1403
+ if (velocity === 0) return Math.abs(offset) <= Math.abs(offset - closed);
1404
+ const forward = velocity > 0;
1405
+ if (Math.abs(velocity) >= Math.abs(velocityThreshold)) return forward;
1406
+ const threshold = Math.abs(width * DRAWER_POSITIONAL_THRESHOLD);
1407
+ const fromStart = Math.abs((forward ? closed : 0) - offset);
1408
+ return fromStart >= threshold ? forward : !forward;
1409
+ }
1410
+ var DrawerAnimation = class {
1411
+ #width;
1412
+ #open;
1413
+ #settled;
1414
+ #mode = "rest";
1415
+ #spring;
1416
+ #offset;
1417
+ #velocity = 0;
1418
+ // Tween / decay state.
1419
+ #from = 0;
1420
+ #to = 0;
1421
+ #v0 = 0;
1422
+ #start = 0;
1423
+ #duration = 0;
1424
+ #easing = (t) => t;
1425
+ /** @param open initial value (at rest); @param width the sheet width in px. */
1426
+ constructor(open, width) {
1427
+ this.#width = Math.max(0, width);
1428
+ this.#open = open;
1429
+ this.#settled = open;
1430
+ this.#offset = open ? 0 : -this.#width;
1431
+ this.#spring = new SpringAnimatable(this.#offset);
1432
+ }
1433
+ /** DrawerState.targetValue === Open. */
1434
+ get isOpen() {
1435
+ return this.#open;
1436
+ }
1437
+ /** DrawerState.currentValue === Open: the last anchor it settled on. */
1438
+ get isSettledOpen() {
1439
+ return this.#settled;
1440
+ }
1441
+ get isDragging() {
1442
+ return this.#mode === "drag";
1443
+ }
1444
+ get width() {
1445
+ return this.#width;
1446
+ }
1447
+ /** Units: px per second, positive towards open (AnchoredDraggableState.lastVelocity). */
1448
+ get velocity() {
1449
+ return this.#velocity;
1450
+ }
1451
+ /** New anchors (the sheet was measured): at rest the offset follows its anchor. */
1452
+ setWidth(width) {
1453
+ const w = Math.max(0, width);
1454
+ if (w === this.#width) return;
1455
+ const previous = this.#width;
1456
+ this.#width = w;
1457
+ if (this.#mode === "rest") {
1458
+ this.#offset = this.#open ? 0 : -w;
1459
+ this.#spring.snapTo(this.#offset);
1460
+ } else if (this.#mode !== "drag" && !this.#open && this.#to === -previous) {
1461
+ this.#to = -w;
1462
+ }
1463
+ }
1464
+ /** Jumps (DrawerState.snapTo). */
1465
+ snapTo(open) {
1466
+ this.#mode = "rest";
1467
+ this.#open = open;
1468
+ this.#settled = open;
1469
+ this.#offset = open ? 0 : -this.#width;
1470
+ this.#velocity = 0;
1471
+ this.#spring.snapTo(this.#offset);
1472
+ }
1473
+ /** DrawerState.open() / close() at `now` (ms): springs from the current offset and velocity. */
1474
+ animateTo(open, specs, now) {
1475
+ this.sample(now);
1476
+ if (this.#mode === "drag") this.#mode = "rest";
1477
+ this.#open = open;
1478
+ const target = open ? 0 : -this.#width;
1479
+ const spec = open ? specs.open : specs.close;
1480
+ if (spec === null || this.#offset === target && this.#velocity === 0) {
1481
+ this.snapTo(open);
1482
+ return;
1483
+ }
1484
+ const velocity = this.#velocity;
1485
+ this.#spring.snapTo(this.#offset);
1486
+ this.#spring.animateTo(target, spec, now, velocity);
1487
+ if (this.#spring.isRunning) this.#mode = "spring";
1488
+ else this.snapTo(open);
1489
+ }
1490
+ /** Grabs the sheet (pointer down past the slop): stops any animation where it is. */
1491
+ startDrag(now) {
1492
+ this.sample(now);
1493
+ this.#mode = "drag";
1494
+ this.#velocity = 0;
1495
+ }
1496
+ /** Moves the offset by `delta` px (positive towards open), coerced between the anchors. */
1497
+ dragBy(delta) {
1498
+ if (this.#mode !== "drag") return;
1499
+ this.#offset = Math.min(0, Math.max(-this.#width, this.#offset + delta));
1500
+ }
1501
+ /**
1502
+ * Ends a drag with the pointer `velocity` (px/s, positive towards open): picks the target
1503
+ * (computeTarget) and settles on it (animateToWithDecay). Returns whether it settles open.
1504
+ * `velocityThreshold`: DrawerVelocityThreshold in px/s.
1505
+ */
1506
+ release(velocity, specs, now, velocityThreshold = DRAWER_VELOCITY_THRESHOLD, confirm = () => true) {
1507
+ if (this.#mode !== "drag") return this.#open;
1508
+ const proposed = drawerSettlesOpen(this.#offset, velocity, this.#width, velocityThreshold);
1509
+ const open = proposed === this.#settled || confirm(proposed) ? proposed : this.#settled;
1510
+ this.#open = open;
1511
+ const target = open ? 0 : -this.#width;
1512
+ const prev = this.#offset;
1513
+ this.#start = now;
1514
+ this.#from = prev;
1515
+ this.#to = target;
1516
+ this.#v0 = velocity;
1517
+ this.#velocity = velocity;
1518
+ if (prev === target) {
1519
+ this.snapTo(open);
1520
+ return open;
1521
+ }
1522
+ const towards = velocity * (target - prev) > 0;
1523
+ if (towards) {
1524
+ const projected = exponentialDecayTarget(prev, velocity);
1525
+ if (velocity > 0 ? projected >= target : projected <= target) {
1526
+ this.#mode = "decay";
1527
+ this.#duration = 1e3 * Math.log(DECAY_VELOCITY_THRESHOLD / Math.abs(velocity)) / DECAY_FRICTION;
1528
+ return open;
1529
+ }
1530
+ }
1531
+ if (specs.settle === null || specs.settle.duration <= 0) {
1532
+ this.snapTo(open);
1533
+ return open;
1534
+ }
1535
+ this.#mode = "tween";
1536
+ this.#duration = specs.settle.duration;
1537
+ this.#easing = specs.settle.easing;
1538
+ return open;
1539
+ }
1540
+ /** Advances to `now` (ms). */
1541
+ sample(now) {
1542
+ switch (this.#mode) {
1543
+ case "spring": {
1544
+ this.#offset = this.#spring.sample(now);
1545
+ this.#velocity = this.#spring.velocity;
1546
+ if (!this.#spring.isRunning) this.#settle();
1547
+ break;
1548
+ }
1549
+ case "tween": {
1550
+ const elapsed = now - this.#start;
1551
+ if (elapsed >= this.#duration) {
1552
+ this.#settle();
1553
+ break;
1554
+ }
1555
+ const f = (t) => {
1556
+ const p = Math.min(1, Math.max(0, t / this.#duration));
1557
+ return this.#from + (this.#to - this.#from) * this.#easing(p);
1558
+ };
1559
+ const value = f(elapsed);
1560
+ this.#velocity = (f(elapsed + 1) - value) * 1e3;
1561
+ this.#offset = value;
1562
+ break;
1563
+ }
1564
+ case "decay": {
1565
+ const t = (now - this.#start) / 1e3;
1566
+ const value = this.#from - this.#v0 / DECAY_FRICTION + this.#v0 / DECAY_FRICTION * Math.exp(DECAY_FRICTION * t);
1567
+ const crossed = this.#v0 > 0 ? value >= this.#to : value <= this.#to;
1568
+ if (crossed || now - this.#start >= this.#duration) {
1569
+ this.#settle();
1570
+ break;
1571
+ }
1572
+ this.#offset = value;
1573
+ this.#velocity = this.#v0 * Math.exp(DECAY_FRICTION * t);
1574
+ break;
1575
+ }
1576
+ }
1577
+ const w = this.#width;
1578
+ const fraction = w <= 0 ? this.#open ? 1 : 0 : Math.min(1, Math.max(0, (this.#offset + w) / w));
1579
+ return {
1580
+ offset: this.#offset,
1581
+ fraction,
1582
+ running: this.#mode !== "rest",
1583
+ visible: fraction !== 0
1584
+ };
1585
+ }
1586
+ #settle() {
1587
+ this.#mode = "rest";
1588
+ this.#offset = this.#open ? 0 : -this.#width;
1589
+ this.#velocity = 0;
1590
+ this.#settled = this.#open;
1591
+ this.#spring.snapTo(this.#offset);
1592
+ }
1593
+ };
1594
+
1595
+ // src/tabs.ts
1596
+ var TAB_INDICATOR_MIN_WIDTH = 24;
1597
+ var THRESHOLD = 0.01;
1598
+ function tabPositions(tabs, start = 0, minIndicatorWidth = TAB_INDICATOR_MIN_WIDTH) {
1599
+ let left = start;
1600
+ return tabs.map(({ width, contentWidth }) => {
1601
+ const position = { left, width, contentWidth: Math.max(contentWidth, minIndicatorWidth) };
1602
+ left += width;
1603
+ return position;
1604
+ });
1605
+ }
1606
+ function tabIndicatorTarget(position, variant) {
1607
+ return {
1608
+ offset: position.left,
1609
+ width: variant === "primary" ? position.contentWidth : position.width
1610
+ };
1611
+ }
1612
+ var TabIndicatorAnimation = class {
1613
+ #offset;
1614
+ #width;
1615
+ /** Starts at rest on the first layout's target (Compose: initialOffset / initialWidth). */
1616
+ constructor(offset, width) {
1617
+ this.#offset = new SpringAnimatable(offset, THRESHOLD);
1618
+ this.#width = new SpringAnimatable(width, THRESHOLD);
1619
+ }
1620
+ get offset() {
1621
+ return this.#offset;
1622
+ }
1623
+ get width() {
1624
+ return this.#width;
1625
+ }
1626
+ get running() {
1627
+ return this.#offset.isRunning || this.#width.isRunning;
1628
+ }
1629
+ /**
1630
+ * Retargets each Animatable whose target changed, from its value and velocity at `now`; a null
1631
+ * spec (reduced motion) jumps.
1632
+ */
1633
+ animateTo(offset, width, spec, now) {
1634
+ if (width !== this.#width.target) this.#width.animateTo(width, spec, now);
1635
+ if (offset !== this.#offset.target) this.#offset.animateTo(offset, spec, now);
1636
+ }
1637
+ snapTo(offset, width) {
1638
+ this.#offset.snapTo(offset);
1639
+ this.#width.snapTo(width);
1640
+ }
1641
+ sample(now) {
1642
+ const offset = this.#offset.sample(now);
1643
+ const width = this.#width.sample(now);
1644
+ return { offset, width, running: this.running };
1645
+ }
1646
+ };
1647
+ var tabIndicatorShift = (sample, selected) => sample.offset - selected.left;
1648
+ function scrollableTabOffset(tab, scrollWidth, visibleWidth) {
1649
+ const centered = tab.left - (visibleWidth / 2 - tab.width / 2);
1650
+ const available = Math.max(scrollWidth - visibleWidth, 0);
1651
+ return Math.min(Math.max(centered, 0), available);
1652
+ }
1653
+ var TabScrollAnimation = class {
1654
+ value;
1655
+ constructor(position) {
1656
+ this.value = new SpringAnimatable(position, THRESHOLD);
1657
+ }
1658
+ /** Starts a new scroll from `from` (the scroll state's value), velocity 0. */
1659
+ animateTo(from, target, spec, now) {
1660
+ this.value.snapTo(from);
1661
+ this.value.animateTo(target, spec, now, 0);
1662
+ }
1663
+ };
1664
+
1665
+ // src/three-pane-scaffold.ts
1666
+ var THREE_PANE_SCAFFOLD_ROLES = [
1667
+ "primary",
1668
+ "secondary",
1669
+ "tertiary"
1670
+ ];
1671
+ var PRIORITY = {
1672
+ primary: 10,
1673
+ secondary: 5,
1674
+ tertiary: 1
1675
+ };
1676
+ var LIST_DETAIL_ADAPT_STRATEGIES = {
1677
+ primary: "hide",
1678
+ secondary: "hide",
1679
+ tertiary: "hide"
1680
+ };
1681
+ var SUPPORTING_PANE_ADAPT_STRATEGIES = {
1682
+ primary: "hide",
1683
+ secondary: { reflowUnder: "primary" },
1684
+ tertiary: "hide"
1685
+ };
1686
+ var ListDetailPaneScaffoldRole = /* @__PURE__ */ Object.freeze({
1687
+ List: "secondary",
1688
+ Detail: "primary",
1689
+ Extra: "tertiary"
1690
+ });
1691
+ var SupportingPaneScaffoldRole = /* @__PURE__ */ Object.freeze({
1692
+ Main: "primary",
1693
+ Supporting: "secondary",
1694
+ Extra: "tertiary"
1695
+ });
1696
+ var LIST_DETAIL_PANE_ORDER = [
1697
+ "secondary",
1698
+ "primary",
1699
+ "tertiary"
1700
+ ];
1701
+ var SUPPORTING_PANE_ORDER = [
1702
+ "primary",
1703
+ "secondary",
1704
+ "tertiary"
1705
+ ];
1706
+ function sameThreePaneScaffoldValue(a, b) {
1707
+ return a.primary === b.primary && a.secondary === b.secondary && a.tertiary === b.tertiary;
1708
+ }
1709
+ function calculateThreePaneScaffoldValue(maxHorizontalPartitions, adaptStrategies, destinationHistory, maxVerticalPartitions = 1) {
1710
+ let expandedCount = 0;
1711
+ const values = {};
1712
+ let checkReflowedPane = maxHorizontalPartitions === 1 && maxVerticalPartitions > 1 && THREE_PANE_SCAFFOLD_ROLES.some((r) => adaptStrategies[r] !== "hide");
1713
+ const order = [...destinationHistory.map((d) => d.pane).reverse(), ...THREE_PANE_SCAFFOLD_ROLES];
1714
+ for (const pane of order) {
1715
+ const hasAvailablePartition = expandedCount < maxHorizontalPartitions;
1716
+ if (!hasAvailablePartition && !checkReflowedPane) break;
1717
+ if (values[pane] !== void 0) continue;
1718
+ let reflowedPane = null;
1719
+ let anchorPane = pane;
1720
+ let anchorValue;
1721
+ const strategy = adaptStrategies[pane];
1722
+ if (checkReflowedPane && strategy !== "hide") {
1723
+ reflowedPane = pane;
1724
+ anchorPane = strategy.reflowUnder;
1725
+ anchorValue = values[anchorPane];
1726
+ }
1727
+ if (anchorValue === void 0) {
1728
+ if (!hasAvailablePartition) continue;
1729
+ values[anchorPane] = "expanded";
1730
+ expandedCount++;
1731
+ } else if (anchorValue !== "expanded") {
1732
+ continue;
1733
+ }
1734
+ if (reflowedPane !== null) {
1735
+ values[reflowedPane] = "reflowed";
1736
+ checkReflowedPane = false;
1737
+ }
1738
+ }
1739
+ return {
1740
+ primary: values.primary ?? "hidden",
1741
+ secondary: values.secondary ?? "hidden",
1742
+ tertiary: values.tertiary ?? "hidden",
1743
+ currentDestination: destinationHistory[destinationHistory.length - 1]?.pane ?? null
1744
+ };
1745
+ }
1746
+ function expandedPanes(value, order) {
1747
+ return order.filter((r) => value[r] === "expanded");
1748
+ }
1749
+ var RECOMMENDED_MAX_PANES = /* @__PURE__ */ Object.freeze({
1750
+ compact: 1,
1751
+ medium: 1,
1752
+ expanded: 2,
1753
+ large: 2,
1754
+ "extra-large": 3
1755
+ });
1756
+ function threePaneScaffoldDirective(sizeClass, options = {}) {
1757
+ const directive = paneScaffoldDirective(sizeClass, options);
1758
+ if (options.paneRule === "compose") return directive;
1759
+ return {
1760
+ ...directive,
1761
+ maxHorizontalPartitions: Math.min(
1762
+ directive.maxHorizontalPartitions,
1763
+ RECOMMENDED_MAX_PANES[sizeClass.widthClass]
1764
+ )
1765
+ };
1766
+ }
1767
+ var LIST_DETAIL_INITIAL_HISTORY = [
1768
+ { pane: "secondary" }
1769
+ ];
1770
+ var SUPPORTING_PANE_INITIAL_HISTORY = [
1771
+ { pane: "primary" }
1772
+ ];
1773
+ var ThreePaneScaffoldNavigator = class {
1774
+ #history;
1775
+ #directive;
1776
+ #strategies;
1777
+ #historyAware;
1778
+ #value;
1779
+ #listeners = /* @__PURE__ */ new Set();
1780
+ constructor(options) {
1781
+ this.#history = [...options.initialDestinationHistory];
1782
+ this.#directive = options.scaffoldDirective;
1783
+ this.#strategies = options.adaptStrategies;
1784
+ this.#historyAware = options.isDestinationHistoryAware ?? true;
1785
+ this.#value = this.#calculate(this.#history.length - 1);
1786
+ }
1787
+ get scaffoldDirective() {
1788
+ return this.#directive;
1789
+ }
1790
+ set scaffoldDirective(directive) {
1791
+ this.#directive = directive;
1792
+ this.#recalculate();
1793
+ }
1794
+ get adaptStrategies() {
1795
+ return this.#strategies;
1796
+ }
1797
+ set adaptStrategies(strategies) {
1798
+ this.#strategies = strategies;
1799
+ this.#recalculate();
1800
+ }
1801
+ get isDestinationHistoryAware() {
1802
+ return this.#historyAware;
1803
+ }
1804
+ set isDestinationHistoryAware(aware) {
1805
+ this.#historyAware = aware;
1806
+ this.#recalculate();
1807
+ }
1808
+ /** The destination history, oldest first. */
1809
+ get destinationHistory() {
1810
+ return this.#history;
1811
+ }
1812
+ get currentDestination() {
1813
+ return this.#history[this.#history.length - 1] ?? null;
1814
+ }
1815
+ get scaffoldValue() {
1816
+ return this.#value;
1817
+ }
1818
+ /** Calls `listener` after every change of the history or the value; returns the unsubscriber. */
1819
+ subscribe(listener) {
1820
+ this.#listeners.add(listener);
1821
+ return () => {
1822
+ this.#listeners.delete(listener);
1823
+ };
1824
+ }
1825
+ peekPreviousScaffoldValue(behavior = "pop-until-scaffold-value-change") {
1826
+ const index = this.#previousIndex(behavior);
1827
+ return index === -1 ? this.#value : this.#calculate(index);
1828
+ }
1829
+ navigateTo(pane, contentKey) {
1830
+ this.#history.push({ pane, contentKey });
1831
+ this.#recalculate(true);
1832
+ }
1833
+ canNavigateBack(behavior = "pop-until-scaffold-value-change") {
1834
+ return this.#previousIndex(behavior) >= 0;
1835
+ }
1836
+ /** Returns true when there was a destination to go back to (else the history is cleared). */
1837
+ navigateBack(behavior = "pop-until-scaffold-value-change") {
1838
+ const previous = this.#previousIndex(behavior);
1839
+ if (previous < 0) {
1840
+ this.#history = [];
1841
+ this.#recalculate(true);
1842
+ return false;
1843
+ }
1844
+ this.#history.length = previous + 1;
1845
+ this.#recalculate(true);
1846
+ return true;
1847
+ }
1848
+ #recalculate(force = false) {
1849
+ const next = this.#calculate(this.#history.length - 1);
1850
+ const changed = force || !sameThreePaneScaffoldValue(next, this.#value);
1851
+ this.#value = next;
1852
+ if (changed) for (const listener of [...this.#listeners]) listener();
1853
+ }
1854
+ /** getPreviousDestinationIndex (L430-466). */
1855
+ #previousIndex(behavior) {
1856
+ const history = this.#history;
1857
+ if (history.length <= 1) return -1;
1858
+ const last = history.length - 1;
1859
+ const current = history[last];
1860
+ for (let i = last - 1; i >= 0; i--) {
1861
+ const item = history[i];
1862
+ if (!item) continue;
1863
+ switch (behavior) {
1864
+ case "pop-latest":
1865
+ return i;
1866
+ case "pop-until-scaffold-value-change":
1867
+ if (!sameThreePaneScaffoldValue(this.#calculate(i), this.#value)) return i;
1868
+ break;
1869
+ case "pop-until-current-destination-change":
1870
+ if (item.pane !== current?.pane) return i;
1871
+ break;
1872
+ case "pop-until-content-change":
1873
+ if (!Object.is(item.contentKey, current?.contentKey)) return i;
1874
+ if (!sameThreePaneScaffoldValue(this.#calculate(i), this.#value)) return i;
1875
+ break;
1876
+ }
1877
+ }
1878
+ return -1;
1879
+ }
1880
+ /** calculateScaffoldValue (L468-490). */
1881
+ #calculate(index) {
1882
+ const { maxHorizontalPartitions, maxVerticalPartitions } = this.#directive;
1883
+ const history = index === -1 ? [] : this.#historyAware ? this.#history.slice(0, index + 1) : this.#history.slice(index, index + 1);
1884
+ return calculateThreePaneScaffoldValue(
1885
+ maxHorizontalPartitions,
1886
+ this.#strategies,
1887
+ history,
1888
+ maxVerticalPartitions
1889
+ );
1890
+ }
1891
+ };
1892
+ function threePaneScaffoldLayout(input) {
1893
+ const { width: W, height: H, directive, value, order } = input;
1894
+ const present = input.present ?? THREE_PANE_SCAFFOLD_ROLES;
1895
+ const make = (role) => ({
1896
+ role,
1897
+ width: input.preferredWidths?.[role] ?? directive.defaultPanePreferredWidth,
1898
+ height: input.preferredHeights?.[role] ?? directive.defaultPanePreferredHeight,
1899
+ bounds: null
1900
+ });
1901
+ const select = (pick2) => order.filter((r) => present.includes(r) && pick2(value[r])).map(make);
1902
+ const expanded = select((v) => v === "expanded");
1903
+ const reflowed = select((v) => v === "reflowed");
1904
+ const spacer = directive.horizontalPartitionSpacerSize;
1905
+ const vSpacer = directive.verticalPartitionSpacerSize;
1906
+ const outer = { x: 0, y: 0, width: W, height: H };
1907
+ const right = (r) => r.x + r.width;
1908
+ const between = (left, rightEdge) => ({
1909
+ x: left,
1910
+ y: 0,
1911
+ width: rightEdge - left,
1912
+ height: H
1913
+ });
1914
+ const inPartition = (partition, pane) => {
1915
+ const flowed = reflowed[0];
1916
+ const strategy = flowed ? input.adaptStrategies[flowed.role] : "hide";
1917
+ if (flowed && strategy !== "hide" && strategy.reflowUnder === pane.role) {
1918
+ const available = partition.height - vSpacer;
1919
+ pane.height = Math.max(available - flowed.height, Math.trunc(available / 2));
1920
+ flowed.height = available - pane.height;
1921
+ flowed.bounds = {
1922
+ x: partition.x,
1923
+ y: partition.y + pane.height + vSpacer,
1924
+ width: partition.width,
1925
+ height: partition.height - pane.height - vSpacer
1926
+ };
1927
+ } else {
1928
+ pane.height = partition.height;
1929
+ }
1930
+ pane.bounds = { ...partition, height: pane.height };
1931
+ };
1932
+ const partitions = (bounds, panes2) => {
1933
+ if (panes2.length === 0) return;
1934
+ const allocatable = bounds.width - (panes2.length - 1) * spacer;
1935
+ const total = panes2.reduce((sum, p) => sum + p.width, 0);
1936
+ if (allocatable > total) {
1937
+ const top = panes2.reduce((a, b) => PRIORITY[b.role] > PRIORITY[a.role] ? b : a);
1938
+ top.width += allocatable - total;
1939
+ } else if (allocatable < total) {
1940
+ const scale = Math.fround(allocatable / total);
1941
+ for (const p of panes2) p.width = Math.trunc(Math.fround(p.width * scale));
1942
+ }
1943
+ let x = bounds.x;
1944
+ for (const p of panes2) {
1945
+ inPartition({ x, y: bounds.y, width: p.width, height: bounds.height }, p);
1946
+ x += p.width + spacer;
1947
+ }
1948
+ };
1949
+ const expansion = input.expansion;
1950
+ const first = expanded[0];
1951
+ const second = expanded[1];
1952
+ const dragging = expansion?.draggingOffset;
1953
+ const resting = expansion?.firstPaneWidth;
1954
+ if (expanded.length === 2 && first && second && (dragging !== void 0 || resting !== void 0)) {
1955
+ const half = Math.trunc(spacer / 2);
1956
+ if (dragging !== void 0) {
1957
+ if (dragging <= half)
1958
+ inPartition({ ...outer, x: dragging * 2, width: W - dragging * 2 }, second);
1959
+ else if (dragging >= W - half) inPartition({ ...outer, width: dragging * 2 - W }, first);
1960
+ else {
1961
+ inPartition(between(0, dragging - half), first);
1962
+ inPartition(between(dragging + half, W), second);
1963
+ }
1964
+ } else if (resting !== void 0) {
1965
+ const firstWidth = Math.max(0, Math.min(resting, W));
1966
+ if (firstWidth === 0) inPartition(outer, second);
1967
+ else if (firstWidth >= W - spacer) inPartition(outer, first);
1968
+ else {
1969
+ inPartition(between(0, firstWidth), first);
1970
+ inPartition(between(firstWidth + spacer, W), second);
1971
+ }
1972
+ }
1973
+ } else {
1974
+ partitions(outer, expanded);
1975
+ }
1976
+ let handleOffset = null;
1977
+ if (expanded.length === 2 && first && second) {
1978
+ if (dragging !== void 0) handleOffset = dragging;
1979
+ else if (first.bounds && second.bounds)
1980
+ handleOffset = Math.trunc((right(first.bounds) + second.bounds.x) / 2);
1981
+ else if (first.bounds) handleOffset = right(first.bounds);
1982
+ else if (second.bounds) handleOffset = 0;
1983
+ }
1984
+ const panes = {};
1985
+ for (const p of [...expanded, ...reflowed]) {
1986
+ panes[p.role] = p.bounds ?? { x: 0, y: 0, width: 0, height: 0 };
1987
+ }
1988
+ return { panes, handleOffset };
1989
+ }
1990
+ var motionType = (motion) => motion === "none" ? "hidden" : motion === "animate-bounds" ? "shown" : motion.startsWith("enter") ? "entering" : "exiting";
1991
+ function typeOf(previous, current) {
1992
+ const was = previous !== "hidden";
1993
+ const is = current !== "hidden";
1994
+ return was && is ? "shown" : was ? "exiting" : is ? "entering" : "hidden";
1995
+ }
1996
+ function calculatePaneMotion(previous, current, order) {
1997
+ const n = order.length;
1998
+ const types = order.map((r) => typeOf(previous[r], current[r]));
1999
+ const motions = order.map(() => "none");
2000
+ let firstShown = n;
2001
+ let firstEntering = n;
2002
+ let lastShown = -1;
2003
+ let lastEntering = -1;
2004
+ types.forEach((t, i) => {
2005
+ if (t === "shown") {
2006
+ firstShown = Math.min(firstShown, i);
2007
+ lastShown = Math.max(lastShown, i);
2008
+ motions[i] = "animate-bounds";
2009
+ } else if (t === "entering") {
2010
+ firstEntering = Math.min(firstEntering, i);
2011
+ lastEntering = Math.max(lastEntering, i);
2012
+ }
2013
+ });
2014
+ let exitRight = false;
2015
+ let exitLeft = false;
2016
+ let firstExitRight = n;
2017
+ let lastExitLeft = -1;
2018
+ types.forEach((t, i) => {
2019
+ if (t !== "exiting") return;
2020
+ const shownLeft = firstShown < i;
2021
+ const enteringLeft = firstEntering < i;
2022
+ const shownRight = lastShown > i;
2023
+ const enteringRight = lastEntering > i;
2024
+ const toRight = () => {
2025
+ exitRight = true;
2026
+ firstExitRight = Math.min(firstExitRight, i);
2027
+ motions[i] = "exit-to-right";
2028
+ };
2029
+ const toLeft = () => {
2030
+ exitLeft = true;
2031
+ lastExitLeft = Math.max(lastExitLeft, i);
2032
+ motions[i] = "exit-to-left";
2033
+ };
2034
+ if (!shownRight && !enteringRight) toRight();
2035
+ else if (!shownLeft && !enteringLeft) toLeft();
2036
+ else if (!shownRight) toRight();
2037
+ else if (!shownLeft) toLeft();
2038
+ else motions[i] = "exit-with-shrink";
2039
+ });
2040
+ types.forEach((t, i) => {
2041
+ if (t !== "entering") return;
2042
+ const freeRight = !(lastShown > i) && !(lastExitLeft > i);
2043
+ const freeLeft = !(firstShown < i) && !(firstExitRight < i);
2044
+ motions[i] = freeRight && !exitRight ? "enter-from-right" : freeLeft && !exitLeft ? "enter-from-left" : freeRight ? "enter-from-right-delayed" : freeLeft ? "enter-from-left-delayed" : "enter-with-expand";
2045
+ });
2046
+ const out = { primary: "none", secondary: "none", tertiary: "none" };
2047
+ order.forEach((r, i) => {
2048
+ out[r] = motions[i] ?? "none";
2049
+ });
2050
+ return out;
2051
+ }
2052
+ var PANE_MOTION_DELAYED_RATIO = 0.1;
2053
+ function paneTransitions(motions, order, origin, target, scaffoldWidth) {
2054
+ const zero = { x: 0, y: 0, width: 0, height: 0 };
2055
+ const t = (r) => target[r] ?? origin[r] ?? zero;
2056
+ const o = (r) => origin[r] ?? target[r] ?? zero;
2057
+ const type = (r) => motionType(motions[r]);
2058
+ const shown = (r, other) => type(r) === "shown" || type(r) === other;
2059
+ const reversed = [...order].reverse();
2060
+ const slideInFromLeft = () => {
2061
+ let previous = null;
2062
+ for (const r of reversed) {
2063
+ const m = motions[r];
2064
+ if (m === "enter-from-left" || m === "enter-from-left-delayed")
2065
+ return -(previous ? t(previous).x : t(r).x + t(r).width);
2066
+ if (shown(r, "entering")) previous = r;
2067
+ }
2068
+ return 0;
2069
+ };
2070
+ const slideInFromRight = () => {
2071
+ let previous = null;
2072
+ for (const r of order) {
2073
+ const m = motions[r];
2074
+ if (m === "enter-from-right" || m === "enter-from-right-delayed")
2075
+ return scaffoldWidth - (previous ? t(previous).x + t(previous).width : t(r).x);
2076
+ if (shown(r, "entering")) previous = r;
2077
+ }
2078
+ return 0;
2079
+ };
2080
+ const slideOutToLeft = () => {
2081
+ let previous = null;
2082
+ for (const r of reversed) {
2083
+ if (motions[r] === "exit-to-left") return -(previous ? o(previous).x : o(r).x + o(r).width);
2084
+ if (shown(r, "exiting")) previous = r;
2085
+ }
2086
+ return 0;
2087
+ };
2088
+ const slideOutToRight = () => {
2089
+ let previous = null;
2090
+ for (const r of order) {
2091
+ if (motions[r] === "exit-to-right")
2092
+ return scaffoldWidth - (previous ? o(previous).x + o(previous).width : o(r).x);
2093
+ if (shown(r, "exiting")) previous = r;
2094
+ }
2095
+ return 0;
2096
+ };
2097
+ const leftEdge = (role, other, rect) => {
2098
+ let edge = 0;
2099
+ for (const r of order) {
2100
+ if (r === role) return edge;
2101
+ if (shown(r, other)) edge = rect(r).x + rect(r).width;
2102
+ }
2103
+ return edge;
2104
+ };
2105
+ const out = {};
2106
+ for (const r of order) {
2107
+ const motion = motions[r];
2108
+ const to2 = t(r);
2109
+ const from = o(r);
2110
+ const shift = (rect, dx) => ({ ...rect, x: rect.x + dx });
2111
+ let transition = null;
2112
+ switch (motion) {
2113
+ case "animate-bounds":
2114
+ transition = { motion, from, to: to2, delayed: false };
2115
+ break;
2116
+ case "enter-from-left":
2117
+ case "enter-from-left-delayed":
2118
+ transition = {
2119
+ motion,
2120
+ from: shift(to2, slideInFromLeft()),
2121
+ to: to2,
2122
+ delayed: motion.endsWith("delayed")
2123
+ };
2124
+ break;
2125
+ case "enter-from-right":
2126
+ case "enter-from-right-delayed":
2127
+ transition = {
2128
+ motion,
2129
+ from: shift(to2, slideInFromRight()),
2130
+ to: to2,
2131
+ delayed: motion.endsWith("delayed")
2132
+ };
2133
+ break;
2134
+ case "enter-with-expand":
2135
+ transition = {
2136
+ motion,
2137
+ from: { ...to2, x: leftEdge(r, "exiting", o), width: 0 },
2138
+ to: to2,
2139
+ delayed: false
2140
+ };
2141
+ break;
2142
+ case "exit-to-left":
2143
+ transition = { motion, from, to: shift(from, slideOutToLeft()), delayed: false };
2144
+ break;
2145
+ case "exit-to-right":
2146
+ transition = { motion, from, to: shift(from, slideOutToRight()), delayed: false };
2147
+ break;
2148
+ case "exit-with-shrink":
2149
+ transition = {
2150
+ motion,
2151
+ from,
2152
+ to: { ...from, x: leftEdge(r, "entering", t), width: 0 },
2153
+ delayed: false
2154
+ };
2155
+ break;
2156
+ }
2157
+ if (transition) out[r] = transition;
2158
+ }
2159
+ return out;
2160
+ }
2161
+ var PANE_EXPANSION_SAMPLE_ANCHORS = [
2162
+ { proportion: 0 },
2163
+ { offsetFromStart: 240 },
2164
+ { proportion: 0.5 },
2165
+ { offsetFromEnd: 240 },
2166
+ { proportion: 1 }
2167
+ ];
2168
+ var PANE_EXPANSION_ANCHORING_VELOCITY_THRESHOLD = 200;
2169
+ function paneExpansionAnchorPosition(anchor, width, rtl = false) {
2170
+ const offset = "proportion" in anchor ? Math.min(Math.max(Math.round(width * anchor.proportion), 0), width) : "offsetFromStart" in anchor ? Math.round(anchor.offsetFromStart) : width - Math.round(anchor.offsetFromEnd);
2171
+ return rtl ? width - offset : offset;
2172
+ }
2173
+ function closestPaneExpansionAnchor(anchors, width, position, velocity, rtl = false) {
2174
+ let best = -1;
2175
+ let bestScore = Infinity;
2176
+ const candidates2 = [];
2177
+ anchors.forEach((anchor, index) => {
2178
+ const at2 = paneExpansionAnchorPosition(anchor, width, rtl);
2179
+ let score;
2180
+ if (velocity >= PANE_EXPANSION_ANCHORING_VELOCITY_THRESHOLD) {
2181
+ const delta = at2 - position;
2182
+ score = delta < 0 ? width - delta : delta;
2183
+ } else if (velocity <= -PANE_EXPANSION_ANCHORING_VELOCITY_THRESHOLD) {
2184
+ const delta = position - at2;
2185
+ score = delta < 0 ? width - delta : delta;
2186
+ } else score = Math.abs(position - at2);
2187
+ candidates2.push({ at: at2, index, score });
2188
+ });
2189
+ candidates2.sort((a, b) => a.at - b.at || a.index - b.index);
2190
+ for (const c of candidates2) {
2191
+ if (c.score < bestScore) {
2192
+ best = c.index;
2193
+ bestScore = c.score;
2194
+ }
2195
+ }
2196
+ return best;
2197
+ }
2198
+ function nextPaneExpansionAnchor(anchors, width, position, rtl = false) {
2199
+ const sorted = anchors.map((anchor, index) => ({ at: paneExpansionAnchorPosition(anchor, width, rtl), index })).sort((a, b) => a.at - b.at || a.index - b.index);
2200
+ return (sorted.find((a) => position < a.at) ?? sorted[0])?.index ?? -1;
2201
+ }
2202
+ var PANE_MOTION_VISIBILITY_THRESHOLD = 1;
2203
+ var KEYS = ["x", "y", "width", "height"];
2204
+ var ThreePaneScaffoldAnimation = class {
2205
+ #panes = /* @__PURE__ */ new Map();
2206
+ start(transitions, spec, now) {
2207
+ const next = /* @__PURE__ */ new Map();
2208
+ for (const role of THREE_PANE_SCAFFOLD_ROLES) {
2209
+ const transition = transitions[role];
2210
+ if (!transition) continue;
2211
+ const { from, to: to2 } = transition;
2212
+ const previous = this.#panes.get(role);
2213
+ if (previous) for (const k of KEYS) previous.springs[k].sample(now);
2214
+ const springs = previous?.springs ?? Object.fromEntries(
2215
+ KEYS.map((k) => [k, new SpringAnimatable(from[k], PANE_MOTION_VISIBILITY_THRESHOLD)])
2216
+ );
2217
+ const pane = { springs, to: to2, spec, delayUntil: null };
2218
+ if (transition.delayed && spec !== null && !previous) {
2219
+ const duration = Math.max(
2220
+ ...KEYS.map(
2221
+ (k) => springDurationMillis(spec, from[k], to2[k], 0, PANE_MOTION_VISIBILITY_THRESHOLD)
2222
+ )
2223
+ );
2224
+ pane.delayUntil = now + Math.trunc(duration * PANE_MOTION_DELAYED_RATIO);
2225
+ } else {
2226
+ for (const k of KEYS) springs[k].animateTo(to2[k], spec, now);
2227
+ }
2228
+ next.set(role, pane);
2229
+ }
2230
+ this.#panes = next;
2231
+ }
2232
+ /** Jumps every pane to its target. */
2233
+ finish() {
2234
+ for (const pane of this.#panes.values()) {
2235
+ pane.delayUntil = null;
2236
+ for (const k of KEYS) pane.springs[k].snapTo(pane.to[k]);
2237
+ }
2238
+ }
2239
+ sample(now) {
2240
+ const panes = {};
2241
+ let running = false;
2242
+ for (const [role, pane] of this.#panes) {
2243
+ const s = pane.springs;
2244
+ if (pane.delayUntil !== null && now >= pane.delayUntil) {
2245
+ for (const k of KEYS) s[k].animateTo(pane.to[k], pane.spec, pane.delayUntil);
2246
+ pane.delayUntil = null;
2247
+ }
2248
+ panes[role] = {
2249
+ x: s.x.sample(now),
2250
+ y: s.y.sample(now),
2251
+ width: Math.max(0, s.width.sample(now)),
2252
+ height: Math.max(0, s.height.sample(now))
2253
+ };
2254
+ if (pane.delayUntil !== null || KEYS.some((k) => s[k].isRunning)) running = true;
2255
+ }
2256
+ return { panes, running };
2257
+ }
2258
+ };
2259
+
2260
+ export { AmplitudeAnimation, DEFAULT_DISPLACEMENT_THRESHOLD, DRAWER_POSITIONAL_THRESHOLD, DRAWER_SETTLE_MILLIS, DRAWER_VELOCITY_THRESHOLD, DrawerAnimation, FAST_OUT_LINEAR_IN_EASING, LIST_DETAIL_ADAPT_STRATEGIES, LIST_DETAIL_INITIAL_HISTORY, LIST_DETAIL_PANE_ORDER, ListDetailPaneScaffoldRole, MODAL_RAIL_EXPANDED_PROGRESS, NAVIGATION_SUITE_EXPANDED_FROM, NAVIGATION_SUITE_TYPES, NavigationSuiteScaffoldAnimation, PANE_EXPANSION_ANCHORING_VELOCITY_THRESHOLD, PANE_EXPANSION_SAMPLE_ANCHORS, PANE_MOTION_DELAYED_RATIO, PANE_MOTION_VISIBILITY_THRESHOLD, RECOMMENDED_MAX_PANES, SEARCH_BAR_EXPANDED_THRESHOLD, SEARCH_DROPDOWN_VISIBLE_PROGRESS, SUPPORTING_PANE_ADAPT_STRATEGIES, SUPPORTING_PANE_INITIAL_HISTORY, SUPPORTING_PANE_ORDER, SearchBarAnimation, SpringAnimatable, SpringSimulation, SupportingPaneScaffoldRole, TAB_INDICATOR_MIN_WIDTH, THREE_PANE_SCAFFOLD_ROLES, TOP_TITLE_ALPHA_EASING, TabIndicatorAnimation, TabScrollAnimation, ThreePaneScaffoldAnimation, ThreePaneScaffoldNavigator, TweenAnimatable, WINDOW_HEIGHT_BREAKPOINTS, WINDOW_WIDTH_BREAKPOINTS, WideNavigationRailAnimation, animatedInt, appBarScrollState, buttonGroupVisibleCount, buttonGroupWidths, calculatePaneMotion, calculateThreePaneScaffoldValue, circularIndeterminateFrame, closestPaneExpansionAnchor, collapsedFraction, cubicBezier, dampingCoefficient, dampingRatioFromCoefficient, drawerSettlesOpen, estimateAnimationDurationMillis, estimateAnimationDurationMillisDouble, expandedPanes, exponentialDecayTarget, fabMenuItemVisible, floatingToolbarScrollState, indeterminateFrame, indicatorAmplitude, initialIndeterminateFrame, isAtLeastBreakpoint, isHeightAtLeastBreakpoint, isNavigationBarType, isScrolledUnder, isWidthAtLeastBreakpoint, linearIndeterminateFractions, menuNavigationTarget, menuPosition, menuTransformOrigin, morphSpringDurationMillis, navigationSuiteOffset, navigationSuiteType, nextPaneExpansionAnchor, overlappedFraction, paneExpansionAnchorPosition, paneScaffoldDirective, paneTransitions, parseCubicBezier, sameThreePaneScaffoldValue, sameWindowSizeClass, scrollAppBar, scrollFloatingToolbar, scrollToolbarExpansion, scrollableTabOffset, searchNavigationTarget, searchViewFrame, settleTarget, springDurationMillis, springToLinear, springValue, springVelocity, tabIndicatorShift, tabIndicatorTarget, tabPositions, threePaneScaffoldDirective, threePaneScaffoldLayout, toolbarCollapsedFraction, toolbarExpansionScroll, toolbarNavigationTarget, toolbarSettleTarget, twoRowsFrame, waveOffset, waveTravelMillis, wideNavigationRailItemWidth, wideNavigationRailWidth, windowHeightSizeClass, windowSizeClass, windowWidthSizeClass, withHeight, withHeightOffset, withToolbarOffset, withToolbarOffsetLimit };
656
2261
  //# sourceMappingURL=index.js.map
657
2262
  //# sourceMappingURL=index.js.map