@joanroucoux/cairn-ui 0.7.0 → 0.7.2

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.
@@ -4,10 +4,10 @@ import { inject, ElementRef, DestroyRef, afterNextRender, Component, input, bool
4
4
  import { scaleLinear } from 'd3-scale';
5
5
  import { curveMonotoneX, line } from 'd3-shape';
6
6
 
7
- const HEIGHT_PROPERTY = '--action-bar-height';
7
+ const HEIGHT_PROPERTY$1 = '--action-bar-height';
8
8
  /**
9
9
  * Bar of actions fixed above the tab bar on iPhone: the primary actions of a detail screen, within
10
- * thumb reach. It sits 52px plus the safe area above the bottom edge, so it clears `ui-tab-bar`, and
10
+ * thumb reach. It sits `--tab-bar-height` (52px plus the safe area) above the bottom edge, so it clears `ui-tab-bar`, and
11
11
  * it is hidden from `64rem`, where the same actions live in the page itself. Its children share the
12
12
  * width equally. While it is on the page it publishes its height as `--action-bar-height` on the
13
13
  * root element, which `ui-toaster` adds to its offset so a confirmation never covers the actions.
@@ -23,7 +23,7 @@ class UiActionBar {
23
23
  const host = inject(ElementRef).nativeElement;
24
24
  const root = inject(DOCUMENT).documentElement;
25
25
  const destroyRef = inject(DestroyRef);
26
- const publish = () => root.style.setProperty(HEIGHT_PROPERTY, `${host.offsetHeight}px`);
26
+ const publish = () => root.style.setProperty(HEIGHT_PROPERTY$1, `${host.offsetHeight}px`);
27
27
  afterNextRender(() => {
28
28
  publish();
29
29
  if (typeof ResizeObserver !== 'undefined') {
@@ -32,10 +32,10 @@ class UiActionBar {
32
32
  destroyRef.onDestroy(() => observer.disconnect());
33
33
  }
34
34
  });
35
- destroyRef.onDestroy(() => root.style.removeProperty(HEIGHT_PROPERTY));
35
+ destroyRef.onDestroy(() => root.style.removeProperty(HEIGHT_PROPERTY$1));
36
36
  }
37
37
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiActionBar, deps: [], target: i0.ɵɵFactoryTarget.Component });
38
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: UiActionBar, isStandalone: true, selector: "ui-action-bar", host: { classAttribute: "fixed inset-x-0 bottom-[calc(52px+env(safe-area-inset-bottom))] z-40 grid grid-flow-col auto-cols-fr gap-2 bg-(--background) px-(--gutter) py-3 shadow-[0_-1px_0_var(--hairline)] lg:hidden" }, ngImport: i0, template: '<ng-content />', isInline: true });
38
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: UiActionBar, isStandalone: true, selector: "ui-action-bar", host: { classAttribute: "fixed inset-x-0 bottom-[var(--tab-bar-height,calc(52px+env(safe-area-inset-bottom)))] z-40 grid grid-flow-col auto-cols-fr gap-2 bg-(--background) px-(--gutter) py-3 shadow-[0_-1px_0_var(--hairline)] lg:hidden" }, ngImport: i0, template: '<ng-content />', isInline: true });
39
39
  }
40
40
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiActionBar, decorators: [{
41
41
  type: Component,
@@ -43,7 +43,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
43
43
  selector: 'ui-action-bar',
44
44
  template: '<ng-content />',
45
45
  host: {
46
- class: 'fixed inset-x-0 bottom-[calc(52px+env(safe-area-inset-bottom))] z-40 grid grid-flow-col auto-cols-fr gap-2 bg-(--background) px-(--gutter) py-3 shadow-[0_-1px_0_var(--hairline)] lg:hidden',
46
+ class: 'fixed inset-x-0 bottom-[var(--tab-bar-height,calc(52px+env(safe-area-inset-bottom)))] z-40 grid grid-flow-col auto-cols-fr gap-2 bg-(--background) px-(--gutter) py-3 shadow-[0_-1px_0_var(--hairline)] lg:hidden',
47
47
  },
48
48
  }]
49
49
  }], ctorParameters: () => [] });
@@ -273,6 +273,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
273
273
  }]
274
274
  }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], currency: [{ type: i0.Input, args: [{ isSignal: true, alias: "currency", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], signed: [{ type: i0.Input, args: [{ isSignal: true, alias: "signed", required: false }] }], fractionDigits: [{ type: i0.Input, args: [{ isSignal: true, alias: "fractionDigits", required: false }] }], numeric: [{ type: i0.Input, args: [{ isSignal: true, alias: "numeric", required: false }] }], whenMasked: [{ type: i0.Input, args: [{ isSignal: true, alias: "whenMasked", required: false }] }] } });
275
275
 
276
+ /**
277
+ * Holds the transitions of the host and of everything inside it until two frames after its first
278
+ * render, through the `data-ui-settling` attribute and the matching rule of `styles/motion.css`.
279
+ * A route's elements are created a tick before their classes are bound; a layout read in between
280
+ * would otherwise turn the bound fill, colour or switch track into a transition on page open. Call it
281
+ * in an injection context. A host whose view is never refreshed (a detached change detector) keeps the
282
+ * attribute, and with it no transition.
283
+ *
284
+ * @example
285
+ * constructor() {
286
+ * holdTransitionsUntilRendered();
287
+ * }
288
+ */
289
+ function holdTransitionsUntilRendered() {
290
+ const host = inject(ElementRef).nativeElement;
291
+ host.setAttribute('data-ui-settling', '');
292
+ afterNextRender({
293
+ read: () => {
294
+ requestAnimationFrame(() => requestAnimationFrame(() => host.removeAttribute('data-ui-settling')));
295
+ },
296
+ });
297
+ }
298
+
276
299
  const SKELETON_DELAY_MS = 150;
277
300
  const SKELETON_MIN_MS = 400;
278
301
  /**
@@ -332,7 +355,7 @@ const ASYNC_STATES = ['loading', 'error', 'empty', 'ready'];
332
355
  const ASYNC_VARIANTS = ['elevated', 'plain', 'emphasis', 'inline'];
333
356
  /** Alignments of the error block; `auto` is centred from 64rem. `AsyncAlign` is derived from this tuple. */
334
357
  const ASYNC_ALIGNS = ['start', 'center', 'auto'];
335
- const RETRY_BASE_CLASSES = 'relative inline-flex items-center justify-center rounded-control font-medium whitespace-nowrap cursor-pointer select-none touch-manipulation transition-[scale,background-color,opacity] [transition-duration:var(--duration-press),var(--duration-fast),var(--duration-fast)] ease-out active:scale-(--press-scale) focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--ring) text-label';
358
+ const RETRY_BASE_CLASSES = 'relative inline-flex items-center justify-center rounded-control font-medium whitespace-nowrap cursor-pointer select-none touch-manipulation transition-[scale,background-color] [transition-duration:var(--duration-press),var(--duration-fast)] ease-out active:scale-(--press-scale) focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--ring) text-label';
336
359
  const RETRY_OUTLINE_CLASSES = 'bg-(--card) text-(--foreground) shadow-[inset_0_0_0_1px_var(--border)] hover:bg-(--glow)';
337
360
  const RETRY_PRIMARY_CLASSES = 'bg-(--primary) text-(--primary-foreground) hover:bg-(--primary-to)';
338
361
  const RETRY_BLOCK_CLASSES = 'min-h-(--row-min) gap-2 pl-3 pr-4';
@@ -440,6 +463,9 @@ class UiAsync {
440
463
  return `${RETRY_BASE_CLASSES} ${spec.retry} ${RETRY_BLOCK_CLASSES} ${spec.retryMargin[this.align()]}`;
441
464
  }, /* @ts-ignore */
442
465
  ...(ngDevMode ? [{ debugName: "retryButtonClasses" }] : /* istanbul ignore next */ []));
466
+ constructor() {
467
+ holdTransitionsUntilRendered();
468
+ }
443
469
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiAsync, deps: [], target: i0.ɵɵFactoryTarget.Component });
444
470
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: UiAsync, isStandalone: true, selector: "ui-async", inputs: { state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: true, transformFunction: null }, errorTitle: { classPropertyName: "errorTitle", publicName: "errorTitle", isSignal: true, isRequired: false, transformFunction: null }, errorMessage: { classPropertyName: "errorMessage", publicName: "errorMessage", isSignal: true, isRequired: false, transformFunction: null }, retryLabel: { classPropertyName: "retryLabel", publicName: "retryLabel", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null }, card: { classPropertyName: "card", publicName: "card", isSignal: true, isRequired: false, transformFunction: null }, fill: { classPropertyName: "fill", publicName: "fill", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { retry: "retry" }, host: { properties: { "attr.aria-busy": "state() === 'loading' || null", "class.flex": "filling()", "class.flex-col": "filling()" } }, ngImport: i0, template: `
445
471
  @switch (shown()) {
@@ -540,12 +566,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
540
566
  '[class.flex-col]': 'filling()',
541
567
  },
542
568
  }]
543
- }], propDecorators: { state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: true }] }], errorTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorTitle", required: false }] }], errorMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMessage", required: false }] }], retryLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "retryLabel", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], align: [{ type: i0.Input, args: [{ isSignal: true, alias: "align", required: false }] }], card: [{ type: i0.Input, args: [{ isSignal: true, alias: "card", required: false }] }], fill: [{ type: i0.Input, args: [{ isSignal: true, alias: "fill", required: false }] }], retry: [{ type: i0.Output, args: ["retry"] }] } });
569
+ }], ctorParameters: () => [], propDecorators: { state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: true }] }], errorTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorTitle", required: false }] }], errorMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMessage", required: false }] }], retryLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "retryLabel", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], align: [{ type: i0.Input, args: [{ isSignal: true, alias: "align", required: false }] }], card: [{ type: i0.Input, args: [{ isSignal: true, alias: "card", required: false }] }], fill: [{ type: i0.Input, args: [{ isSignal: true, alias: "fill", required: false }] }], retry: [{ type: i0.Output, args: ["retry"] }] } });
544
570
 
545
571
  /** Available avatar sizes. `AvatarSize` is derived from this tuple. */
546
572
  const AVATAR_SIZES = ['sm', 'md', 'lg', 'auto'];
547
573
  const BASE_CLASSES$8 = 'inline-flex items-center justify-center rounded-pill bg-(--muted) font-medium tabular-nums';
548
- const SIZE_CLASSES$7 = {
574
+ const SIZE_CLASSES$8 = {
549
575
  sm: 'size-8 text-caption',
550
576
  md: 'size-[34px] text-label',
551
577
  lg: 'size-14 text-title',
@@ -564,7 +590,7 @@ class UiAvatar {
564
590
  ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
565
591
  size = input('md', /* @ts-ignore */
566
592
  ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
567
- classes = computed(() => `${BASE_CLASSES$8} ${SIZE_CLASSES$7[this.size()]}`, /* @ts-ignore */
593
+ classes = computed(() => `${BASE_CLASSES$8} ${SIZE_CLASSES$8[this.size()]}`, /* @ts-ignore */
568
594
  ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
569
595
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiAvatar, deps: [], target: i0.ɵɵFactoryTarget.Component });
570
596
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.7", type: UiAvatar, isStandalone: true, selector: "ui-avatar", inputs: { initials: { classPropertyName: "initials", publicName: "initials", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "img" }, properties: { "attr.aria-label": "label()", "class": "classes()" } }, ngImport: i0, template: '<span aria-hidden="true">{{ initials() }}</span>', isInline: true });
@@ -584,21 +610,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
584
610
  /**
585
611
  * Round hit area around a `ui-avatar` that links somewhere: 44px on touch, 40px from 64rem, with a
586
612
  * glow on hover and the press scale. On the current page (`aria-current="page"`, which `routerLinkActive`
587
- * sets through `ariaCurrentWhenActive`) it holds a soft halo. The avatar itself keeps its own size.
613
+ * sets through `ariaCurrentWhenActive`) it holds a soft halo, drawn at once when the route changes. The avatar
614
+ * itself keeps its own size.
588
615
  *
589
616
  * @example
590
617
  * <a uiAvatarLink routerLink="/profile"><ui-avatar initials="JR" label="Profile" size="auto" /></a>
591
618
  */
592
619
  class UiAvatarLink {
593
620
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiAvatarLink, deps: [], target: i0.ɵɵFactoryTarget.Directive });
594
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.7", type: UiAvatarLink, isStandalone: true, selector: "a[uiAvatarLink], button[uiAvatarLink]", host: { classAttribute: "rounded-pill grid size-11 lg:size-10 place-items-center cursor-pointer select-none touch-manipulation transition-[scale,background-color] [transition-duration:var(--duration-press),var(--duration-fast)] ease-out hover:bg-(--glow) aria-[current=page]:bg-(--soft) active:scale-(--press-scale) focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--ring)" }, ngImport: i0 });
621
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.7", type: UiAvatarLink, isStandalone: true, selector: "a[uiAvatarLink], button[uiAvatarLink]", host: { classAttribute: "rounded-pill grid size-11 lg:size-10 place-items-center cursor-pointer select-none touch-manipulation transition-[scale,background-color] [transition-duration:var(--duration-press),var(--duration-fast)] ease-out hover:bg-(--glow) aria-[current=page]:bg-transparent aria-[current=page]:bg-[image:linear-gradient(var(--soft),var(--soft))] active:scale-(--press-scale) focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--ring)" }, ngImport: i0 });
595
622
  }
596
623
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiAvatarLink, decorators: [{
597
624
  type: Directive,
598
625
  args: [{
599
626
  selector: 'a[uiAvatarLink], button[uiAvatarLink]',
600
627
  host: {
601
- class: 'rounded-pill grid size-11 lg:size-10 place-items-center cursor-pointer select-none touch-manipulation transition-[scale,background-color] [transition-duration:var(--duration-press),var(--duration-fast)] ease-out hover:bg-(--glow) aria-[current=page]:bg-(--soft) active:scale-(--press-scale) focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--ring)',
628
+ class: 'rounded-pill grid size-11 lg:size-10 place-items-center cursor-pointer select-none touch-manipulation transition-[scale,background-color] [transition-duration:var(--duration-press),var(--duration-fast)] ease-out hover:bg-(--glow) aria-[current=page]:bg-transparent aria-[current=page]:bg-[image:linear-gradient(var(--soft),var(--soft))] active:scale-(--press-scale) focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--ring)',
602
629
  },
603
630
  }]
604
631
  }] });
@@ -606,7 +633,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
606
633
  /** Available back link sizes. `BackLinkSize` is derived from this tuple. */
607
634
  const BACK_LINK_SIZES = ['md', 'sm', 'header'];
608
635
  const BASE_CLASSES$7 = 'inline-flex items-center gap-0.5 rounded-control text-body font-medium whitespace-nowrap cursor-pointer select-none touch-manipulation transition-[scale,background-color,color] [transition-duration:var(--duration-press),var(--duration-fast),var(--duration-fast)] ease-out active:scale-(--press-scale) focus-visible:outline-2 focus-visible:outline-(--ring)';
609
- const SIZE_CLASSES$6 = {
636
+ const SIZE_CLASSES$7 = {
610
637
  md: 'h-11 pr-3 pl-1 text-(--foreground) focus-visible:outline-offset-2',
611
638
  sm: 'h-9 pr-2 pl-0.5 text-(--muted-foreground) hover:bg-(--glow) hover:text-(--foreground) focus-visible:outline-offset-2',
612
639
  header: 'h-11 pr-3 pl-1 text-(--foreground) focus-visible:-outline-offset-2',
@@ -623,8 +650,11 @@ class UiBackLink {
623
650
  ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
624
651
  chevron = computed(() => CHEVRON[this.size()], /* @ts-ignore */
625
652
  ...(ngDevMode ? [{ debugName: "chevron" }] : /* istanbul ignore next */ []));
626
- classes = computed(() => `${BASE_CLASSES$7} ${SIZE_CLASSES$6[this.size()]}`, /* @ts-ignore */
653
+ classes = computed(() => `${BASE_CLASSES$7} ${SIZE_CLASSES$7[this.size()]}`, /* @ts-ignore */
627
654
  ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
655
+ constructor() {
656
+ holdTransitionsUntilRendered();
657
+ }
628
658
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiBackLink, deps: [], target: i0.ɵɵFactoryTarget.Component });
629
659
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.7", type: UiBackLink, isStandalone: true, selector: "a[ui-back-link]", inputs: { size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "classes()" } }, ngImport: i0, template: `
630
660
  <svg
@@ -667,13 +697,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
667
697
  '[class]': 'classes()',
668
698
  },
669
699
  }]
670
- }], propDecorators: { size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }] } });
700
+ }], ctorParameters: () => [], propDecorators: { size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }] } });
671
701
 
672
702
  /** Available badge variants. `BadgeVariant` is derived from this tuple. */
673
703
  const BADGE_VARIANTS = ['neutral', 'outline'];
674
704
  const BADGE_SIZES = ['md', 'sm'];
675
705
  const BASE_CLASSES$6 = 'inline-flex items-center rounded-pill text-caption font-medium';
676
- const SIZE_CLASSES$5 = {
706
+ const SIZE_CLASSES$6 = {
677
707
  md: 'h-6 px-2.5',
678
708
  sm: 'h-[22px] px-2',
679
709
  };
@@ -692,7 +722,7 @@ class UiBadge {
692
722
  ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
693
723
  size = input('md', /* @ts-ignore */
694
724
  ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
695
- classes = computed(() => `${BASE_CLASSES$6} ${SIZE_CLASSES$5[this.size()]} ${VARIANT_CLASSES$2[this.variant()]}`, /* @ts-ignore */
725
+ classes = computed(() => `${BASE_CLASSES$6} ${SIZE_CLASSES$6[this.size()]} ${VARIANT_CLASSES$2[this.variant()]}`, /* @ts-ignore */
696
726
  ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
697
727
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiBadge, deps: [], target: i0.ɵɵFactoryTarget.Component });
698
728
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.7", type: UiBadge, isStandalone: true, selector: "ui-badge", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "classes()" } }, ngImport: i0, template: '<ng-content />', isInline: true });
@@ -735,7 +765,7 @@ const BUTTON_SIZES = [
735
765
  'icon',
736
766
  'icon-sm',
737
767
  ];
738
- const BASE_CLASSES$5 = 'relative inline-flex items-center justify-center rounded-control font-medium whitespace-nowrap cursor-pointer select-none touch-manipulation transition-[scale,background-color,opacity] [transition-duration:var(--duration-press),var(--duration-fast),var(--duration-fast)] ease-out active:scale-(--press-scale) focus-visible:outline-2 focus-visible:outline-(--ring) disabled:pointer-events-none disabled:opacity-40 aria-disabled:not-aria-busy:opacity-40';
768
+ const BASE_CLASSES$5 = 'relative inline-flex items-center justify-center rounded-control font-medium whitespace-nowrap cursor-pointer select-none touch-manipulation transition-[scale,background-color] [transition-duration:var(--duration-press),var(--duration-fast)] ease-out active:scale-(--press-scale) focus-visible:outline-2 focus-visible:outline-(--ring) disabled:pointer-events-none disabled:opacity-40 aria-disabled:not-aria-busy:opacity-40';
739
769
  const RING_OUTSIDE = 'focus-visible:outline-offset-2';
740
770
  const RING_INSIDE = 'focus-visible:-outline-offset-2';
741
771
  const OUTLINE = 'bg-(--card) shadow-[inset_0_0_0_1px_var(--border)] hover:bg-(--glow) active:bg-(--soft)';
@@ -750,7 +780,7 @@ const VARIANT_CLASSES$1 = {
750
780
  'quiet-destructive': `${RING_INSIDE} bg-transparent text-(--muted-foreground) hover:bg-(--glow) hover:text-(--negative) active:bg-(--soft)`,
751
781
  'outline-destructive': `${RING_OUTSIDE} ${OUTLINE} text-(--negative)`,
752
782
  };
753
- const SIZE_CLASSES$4 = {
783
+ const SIZE_CLASSES$5 = {
754
784
  compact: 'h-8 lg:h-7 gap-1.5 px-2.5 lg:px-2 text-label before:absolute before:-inset-x-1 before:-inset-y-1.5 lg:before:hidden',
755
785
  slim: 'h-11 lg:h-8 gap-1.5 px-2.5 text-label',
756
786
  xs: 'h-8 gap-1.5 px-2.5 text-label',
@@ -780,9 +810,10 @@ class UiButton {
780
810
  busy = input(false, { ...(ngDevMode ? { debugName: "busy" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
781
811
  inactive = computed(() => this.loading() || this.busy(), /* @ts-ignore */
782
812
  ...(ngDevMode ? [{ debugName: "inactive" }] : /* istanbul ignore next */ []));
783
- classes = computed(() => `${BASE_CLASSES$5} ${VARIANT_CLASSES$1[this.variant()]} ${SIZE_CLASSES$4[this.size()]}${this.inactive() ? ' pointer-events-none opacity-70' : ''}`, /* @ts-ignore */
813
+ classes = computed(() => `${BASE_CLASSES$5} ${VARIANT_CLASSES$1[this.variant()]} ${SIZE_CLASSES$5[this.size()]}${this.inactive() ? ' pointer-events-none opacity-70' : ''}`, /* @ts-ignore */
784
814
  ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
785
815
  constructor() {
816
+ holdTransitionsUntilRendered();
786
817
  const host = inject(ElementRef).nativeElement;
787
818
  const guard = (event) => {
788
819
  if (this.inactive()) {
@@ -1220,7 +1251,7 @@ const DELTA_SIZES = ['label', 'inherit'];
1220
1251
  /** Font weights. `DeltaWeight` is derived from this tuple. */
1221
1252
  const DELTA_WEIGHTS = ['medium', 'regular'];
1222
1253
  const BASE_CLASSES$3 = 'inline-flex items-center gap-1 tabular-nums';
1223
- const SIZE_CLASSES$3 = {
1254
+ const SIZE_CLASSES$4 = {
1224
1255
  label: 'text-label',
1225
1256
  inherit: '',
1226
1257
  };
@@ -1276,7 +1307,7 @@ class UiDelta {
1276
1307
  classes = computed(() => {
1277
1308
  const sign = this.sign();
1278
1309
  const background = this.emphasis() === 'pill' ? `${PILL_CLASSES$1} ${PILL_BACKGROUND_CLASS}` : 'bg-transparent';
1279
- return `${BASE_CLASSES$3} ${SIZE_CLASSES$3[this.size()]} ${WEIGHT_CLASSES[this.weight()]} ${TEXT_CLASSES[sign]} ${background}`;
1310
+ return `${BASE_CLASSES$3} ${SIZE_CLASSES$4[this.size()]} ${WEIGHT_CLASSES[this.weight()]} ${TEXT_CLASSES[sign]} ${background}`;
1280
1311
  }, /* @ts-ignore */
1281
1312
  ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
1282
1313
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiDelta, deps: [], target: i0.ɵɵFactoryTarget.Component });
@@ -1441,11 +1472,13 @@ class SheetDrag {
1441
1472
  #panel;
1442
1473
  #grips;
1443
1474
  #dismiss;
1475
+ #enabled;
1444
1476
  #gesture = null;
1445
- constructor(panel, grips, dismiss) {
1477
+ constructor(panel, grips, dismiss, enabled) {
1446
1478
  this.#panel = panel;
1447
1479
  this.#grips = grips;
1448
1480
  this.#dismiss = dismiss;
1481
+ this.#enabled = enabled;
1449
1482
  for (const grip of grips) {
1450
1483
  grip.addEventListener('pointerdown', this.#onDown);
1451
1484
  grip.addEventListener('pointermove', this.#onMove);
@@ -1468,7 +1501,7 @@ class SheetDrag {
1468
1501
  }
1469
1502
  #onDown = (event) => {
1470
1503
  const grip = event.currentTarget;
1471
- if (event.button !== 0 || !isSheet() || event.target.closest(CONTROLS)) {
1504
+ if (event.button !== 0 || !this.#enabled() || !isSheet() || event.target.closest(CONTROLS)) {
1472
1505
  return;
1473
1506
  }
1474
1507
  grip.setPointerCapture(event.pointerId);
@@ -1602,6 +1635,9 @@ const nextId$3 = (() => {
1602
1635
  /**
1603
1636
  * Modal dialog on the native <dialog>: a bottom sheet under `64rem`, centered above it.
1604
1637
  *
1638
+ * `busy` keeps it open while its action runs: Escape, a click on the backdrop and a drag of the sheet do
1639
+ * nothing and the cross is disabled. The owner disables its own buttons and still closes it through `open`.
1640
+ *
1605
1641
  * @example
1606
1642
  * <ui-dialog heading="Buy" description="Ferrari" closeLabel="Close" width="528px" [open]="buying()" (dismissed)="buying.set(false)">
1607
1643
  * <p>Body</p>
@@ -1622,6 +1658,7 @@ class UiDialog {
1622
1658
  width = input('lg', /* @ts-ignore */
1623
1659
  ...(ngDevMode ? [{ debugName: "width" }] : /* istanbul ignore next */ []));
1624
1660
  open = input(false, { ...(ngDevMode ? { debugName: "open" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1661
+ busy = input(false, { ...(ngDevMode ? { debugName: "busy" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1625
1662
  dismissed = output();
1626
1663
  closed = output();
1627
1664
  headingId = nextId$3();
@@ -1647,7 +1684,7 @@ class UiDialog {
1647
1684
  ...(ngDevMode ? [{ debugName: "headerClasses" }] : /* istanbul ignore next */ []));
1648
1685
  titleClasses = computed(() => this.spec().title, /* @ts-ignore */
1649
1686
  ...(ngDevMode ? [{ debugName: "titleClasses" }] : /* istanbul ignore next */ []));
1650
- crossClasses = computed(() => `rounded-pill lg:rounded-control grid size-11 flex-none cursor-pointer place-items-center text-(--muted-foreground) transition-[scale,background-color,color] [transition-duration:var(--duration-press),var(--duration-fast),var(--duration-fast)] ease-out outline-none focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-(--ring) active:scale-(--press-scale) active:bg-(--soft) lg:size-9 lg:hover:bg-(--glow) lg:hover:text-(--foreground) ${this.spec().cross}`, /* @ts-ignore */
1687
+ crossClasses = computed(() => `rounded-pill lg:rounded-control grid size-11 flex-none cursor-pointer place-items-center text-(--muted-foreground) transition-[scale,background-color,color] [transition-duration:var(--duration-press),var(--duration-fast),var(--duration-fast)] ease-out outline-none focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-(--ring) active:scale-(--press-scale) active:bg-(--soft) disabled:pointer-events-none disabled:opacity-40 lg:size-9 lg:hover:bg-(--glow) lg:hover:text-(--foreground) ${this.spec().cross}`, /* @ts-ignore */
1651
1688
  ...(ngDevMode ? [{ debugName: "crossClasses" }] : /* istanbul ignore next */ []));
1652
1689
  iconClasses = computed(() => this.spec().icon, /* @ts-ignore */
1653
1690
  ...(ngDevMode ? [{ debugName: "iconClasses" }] : /* istanbul ignore next */ []));
@@ -1661,7 +1698,7 @@ class UiDialog {
1661
1698
  afterNextRender(() => {
1662
1699
  const host = this.#host.nativeElement;
1663
1700
  const grips = ['[data-dialog-handle]', '[data-dialog-header]'].map((grip) => host.querySelector(grip));
1664
- this.#drag = new SheetDrag(this.#dialog, grips, () => this.close('drag'));
1701
+ this.#drag = new SheetDrag(this.#dialog, grips, () => this.close('drag'), () => !this.busy());
1665
1702
  destroyRef.onDestroy(() => {
1666
1703
  this.#drag?.destroy();
1667
1704
  this.#cancelExit();
@@ -1703,13 +1740,20 @@ class UiDialog {
1703
1740
  return;
1704
1741
  }
1705
1742
  event.preventDefault();
1706
- this.close('escape');
1743
+ if (!this.busy()) {
1744
+ this.close('escape');
1745
+ }
1746
+ }
1747
+ onKeydown(event) {
1748
+ if (event.key === 'Escape' && this.busy()) {
1749
+ event.preventDefault();
1750
+ }
1707
1751
  }
1708
1752
  onPointerDown(event) {
1709
1753
  this.#pressedOnBackdrop = event.target === this.#dialog && this.#outside(event);
1710
1754
  }
1711
1755
  onClick(event) {
1712
- if (this.#pressedOnBackdrop && event.target === this.#dialog && this.#outside(event)) {
1756
+ if (this.#pressedOnBackdrop && !this.busy() && event.target === this.#dialog && this.#outside(event)) {
1713
1757
  this.close('backdrop');
1714
1758
  }
1715
1759
  this.#pressedOnBackdrop = false;
@@ -1722,11 +1766,16 @@ class UiDialog {
1722
1766
  return (event.clientX < rect.left || event.clientX > rect.right || event.clientY < rect.top || event.clientY > rect.bottom);
1723
1767
  }
1724
1768
  onNativeClose() {
1725
- const reason = this.#reason ?? 'escape';
1769
+ const asked = this.#reason;
1726
1770
  this.#reason = null;
1727
1771
  if (this.#dialog.open) {
1728
1772
  return;
1729
1773
  }
1774
+ if (asked === null && this.busy()) {
1775
+ this.#dialog.showModal();
1776
+ return;
1777
+ }
1778
+ const reason = asked ?? 'escape';
1730
1779
  if (reason !== 'programmatic') {
1731
1780
  this.dismissed.emit();
1732
1781
  }
@@ -1741,9 +1790,10 @@ class UiDialog {
1741
1790
  });
1742
1791
  }
1743
1792
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
1744
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: UiDialog, isStandalone: true, selector: "ui-dialog", inputs: { heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired: true, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null }, closeLabel: { classPropertyName: "closeLabel", publicName: "closeLabel", isSignal: true, isRequired: false, transformFunction: null }, layout: { classPropertyName: "layout", publicName: "layout", isSignal: true, isRequired: false, transformFunction: null }, truncateDescription: { classPropertyName: "truncateDescription", publicName: "truncateDescription", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { dismissed: "dismissed", closed: "closed" }, host: { listeners: { "pointerdown": "onPointerDown($event)", "click": "onClick($event)" } }, ngImport: i0, template: `
1793
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: UiDialog, isStandalone: true, selector: "ui-dialog", inputs: { heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired: true, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null }, closeLabel: { classPropertyName: "closeLabel", publicName: "closeLabel", isSignal: true, isRequired: false, transformFunction: null }, layout: { classPropertyName: "layout", publicName: "layout", isSignal: true, isRequired: false, transformFunction: null }, truncateDescription: { classPropertyName: "truncateDescription", publicName: "truncateDescription", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, busy: { classPropertyName: "busy", publicName: "busy", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { dismissed: "dismissed", closed: "closed" }, host: { listeners: { "pointerdown": "onPointerDown($event)", "click": "onClick($event)", "keydown": "onKeydown($event)" } }, ngImport: i0, template: `
1745
1794
  <dialog
1746
1795
  #dlg
1796
+ [attr.aria-busy]="busy() || null"
1747
1797
  [attr.aria-describedby]="describedBy()"
1748
1798
  [attr.aria-labelledby]="headingId"
1749
1799
  [attr.role]="layout() === 'confirm' ? 'alertdialog' : 'dialog'"
@@ -1777,7 +1827,13 @@ class UiDialog {
1777
1827
  </div>
1778
1828
 
1779
1829
  @if (closeLabel()) {
1780
- <button type="button" [attr.aria-label]="closeLabel()" [class]="crossClasses()" (click)="close('cross')">
1830
+ <button
1831
+ type="button"
1832
+ [attr.aria-label]="closeLabel()"
1833
+ [class]="crossClasses()"
1834
+ [disabled]="busy()"
1835
+ (click)="close('cross')"
1836
+ >
1781
1837
  <svg
1782
1838
  aria-hidden="true"
1783
1839
  class="block size-[22px] fill-none stroke-current stroke-[1.75]"
@@ -1815,9 +1871,14 @@ class UiDialog {
1815
1871
  }
1816
1872
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiDialog, decorators: [{
1817
1873
  type: Component,
1818
- args: [{ selector: 'ui-dialog', host: { '(pointerdown)': 'onPointerDown($event)', '(click)': 'onClick($event)' }, template: `
1874
+ args: [{ selector: 'ui-dialog', host: {
1875
+ '(pointerdown)': 'onPointerDown($event)',
1876
+ '(click)': 'onClick($event)',
1877
+ '(keydown)': 'onKeydown($event)',
1878
+ }, template: `
1819
1879
  <dialog
1820
1880
  #dlg
1881
+ [attr.aria-busy]="busy() || null"
1821
1882
  [attr.aria-describedby]="describedBy()"
1822
1883
  [attr.aria-labelledby]="headingId"
1823
1884
  [attr.role]="layout() === 'confirm' ? 'alertdialog' : 'dialog'"
@@ -1851,7 +1912,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
1851
1912
  </div>
1852
1913
 
1853
1914
  @if (closeLabel()) {
1854
- <button type="button" [attr.aria-label]="closeLabel()" [class]="crossClasses()" (click)="close('cross')">
1915
+ <button
1916
+ type="button"
1917
+ [attr.aria-label]="closeLabel()"
1918
+ [class]="crossClasses()"
1919
+ [disabled]="busy()"
1920
+ (click)="close('cross')"
1921
+ >
1855
1922
  <svg
1856
1923
  aria-hidden="true"
1857
1924
  class="block size-[22px] fill-none stroke-current stroke-[1.75]"
@@ -1886,7 +1953,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
1886
1953
  </div>
1887
1954
  </dialog>
1888
1955
  `, styles: ["dialog{--dialog-ease: var(--ease-sheet);transition:transform var(--duration-base) var(--dialog-ease),overlay var(--duration-base) allow-discrete,display var(--duration-base) allow-discrete;transform:translateY(var(--drag-y, 0px))}dialog:not([open]){transform:translateY(100%);transition-duration:var(--duration-exit)}@starting-style{dialog[open]{transform:translateY(100%)}}dialog[data-dragging]{transition:none}@media(prefers-reduced-motion:reduce){dialog{transition:opacity var(--duration-base) var(--dialog-ease),overlay var(--duration-base) allow-discrete,display var(--duration-base) allow-discrete;opacity:1}dialog:not([open]){opacity:0;transform:translateY(var(--drag-y, 0px))}@starting-style{dialog[open]{opacity:0;transform:translateY(0)}}}@media(min-width:64rem){dialog{--dialog-ease: var(--ease-out);transition:opacity var(--duration-base) var(--dialog-ease),transform var(--duration-base) var(--dialog-ease),overlay var(--duration-base) allow-discrete,display var(--duration-base) allow-discrete;opacity:1;transform:scale(1)}dialog:not([open]){opacity:0;transform:scale(var(--enter-scale))}@starting-style{dialog[open]{opacity:0;transform:scale(var(--enter-scale))}}}dialog::backdrop{transition:opacity var(--duration-base) var(--ease-out);opacity:1}dialog:not([open])::backdrop{opacity:0;transition-duration:var(--duration-exit)}@starting-style{dialog[open]::backdrop{opacity:0}}\n"] }]
1889
- }], ctorParameters: () => [], propDecorators: { heading: [{ type: i0.Input, args: [{ isSignal: true, alias: "heading", required: true }] }], description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }], closeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeLabel", required: false }] }], layout: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout", required: false }] }], truncateDescription: [{ type: i0.Input, args: [{ isSignal: true, alias: "truncateDescription", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }], dismissed: [{ type: i0.Output, args: ["dismissed"] }], closed: [{ type: i0.Output, args: ["closed"] }] } });
1956
+ }], ctorParameters: () => [], propDecorators: { heading: [{ type: i0.Input, args: [{ isSignal: true, alias: "heading", required: true }] }], description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }], closeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeLabel", required: false }] }], layout: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout", required: false }] }], truncateDescription: [{ type: i0.Input, args: [{ isSignal: true, alias: "truncateDescription", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }], busy: [{ type: i0.Input, args: [{ isSignal: true, alias: "busy", required: false }] }], dismissed: [{ type: i0.Output, args: ["dismissed"] }], closed: [{ type: i0.Output, args: ["closed"] }] } });
1890
1957
 
1891
1958
  const RADIUS = 78;
1892
1959
  const RING_WIDTH = 30;
@@ -2323,7 +2390,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
2323
2390
  const FACT_SIZES = ['md', 'sm', 'auto', 'recap'];
2324
2391
  /** Colour of the line under a fact value. `stale` flags an out-of-date figure. */
2325
2392
  const FACT_SUB_TONES = ['subtle', 'stale'];
2326
- const SIZE_CLASSES$2 = {
2393
+ const SIZE_CLASSES$3 = {
2327
2394
  md: { row: 'gap-4 py-3 [&:not(:first-child)]:shadow-[inset_0_1px_0_var(--hairline)]', value: 'text-body' },
2328
2395
  sm: { row: 'gap-4 py-2.5', value: 'text-label' },
2329
2396
  auto: {
@@ -2375,9 +2442,9 @@ class UiFact {
2375
2442
  ...(ngDevMode ? [{ debugName: "subTone" }] : /* istanbul ignore next */ []));
2376
2443
  size = input('md', /* @ts-ignore */
2377
2444
  ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
2378
- classes = computed(() => `flex items-baseline justify-between ${SIZE_CLASSES$2[this.size()].row}`, /* @ts-ignore */
2445
+ classes = computed(() => `flex items-baseline justify-between ${SIZE_CLASSES$3[this.size()].row}`, /* @ts-ignore */
2379
2446
  ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
2380
- valueClasses = computed(() => `${SIZE_CLASSES$2[this.size()].value} tabular-nums`, /* @ts-ignore */
2447
+ valueClasses = computed(() => `${SIZE_CLASSES$3[this.size()].value} tabular-nums`, /* @ts-ignore */
2381
2448
  ...(ngDevMode ? [{ debugName: "valueClasses" }] : /* istanbul ignore next */ []));
2382
2449
  subClasses = computed(() => `text-caption ${SUB_TONE_CLASSES$1[this.subTone()]}`, /* @ts-ignore */
2383
2450
  ...(ngDevMode ? [{ debugName: "subClasses" }] : /* istanbul ignore next */ []));
@@ -3031,6 +3098,7 @@ const animateShape = (from, to, duration, draw, done) => {
3031
3098
  class ShapeMotion {
3032
3099
  #view;
3033
3100
  #points = null;
3101
+ #rangeKey = null;
3034
3102
  #target = null;
3035
3103
  #held = null;
3036
3104
  #drawn = null;
@@ -3040,7 +3108,11 @@ class ShapeMotion {
3040
3108
  }
3041
3109
  follow(points, geometry, options) {
3042
3110
  const newSeries = !sameSeries(points, this.#points);
3111
+ const newRange = options.rangeKey === null || options.rangeKey !== this.#rangeKey;
3043
3112
  this.#points = points;
3113
+ if (newSeries) {
3114
+ this.#rangeKey = options.rangeKey;
3115
+ }
3044
3116
  if (this.#stop && !newSeries && !options.reduced && geometry?.line === this.#target?.line) {
3045
3117
  this.#target = geometry;
3046
3118
  return;
@@ -3049,7 +3121,13 @@ class ShapeMotion {
3049
3121
  const held = this.#held ?? this.#target;
3050
3122
  this.#target = geometry;
3051
3123
  this.stop();
3052
- if (!newSeries || !geometry || !from || from.length < 2 || geometry.points.length < 2 || options.reduced) {
3124
+ if (!newSeries ||
3125
+ !newRange ||
3126
+ !geometry ||
3127
+ !from ||
3128
+ from.length < 2 ||
3129
+ geometry.points.length < 2 ||
3130
+ options.reduced) {
3053
3131
  this.#settle(geometry);
3054
3132
  return;
3055
3133
  }
@@ -3104,6 +3182,10 @@ const wideViewport = () => {
3104
3182
  * only the first, middle and last ticks count; with `auto` every tick counts from `sm` up and the
3105
3183
  * three core ticks below it. Tick positions are unchanged.
3106
3184
  *
3185
+ * A new series interpolates from the drawn curve, fading the start label and the dashed line. With
3186
+ * `rangeKey` bound, only a series that comes with a new key does so: a new series under the same key
3187
+ * (a reload after a buy) is drawn at once, with no fade. The key may change before its series arrives.
3188
+ *
3107
3189
  * @example
3108
3190
  * <ui-line-chart [points]="points" label="Net worth over one month" startLabel="Since" [valueFormat]="formatEur" />
3109
3191
  */
@@ -3135,6 +3217,8 @@ class UiLineChart {
3135
3217
  axisGap = input(12, /* @ts-ignore */
3136
3218
  ...(ngDevMode ? [{ debugName: "axisGap" }] : /* istanbul ignore next */ []));
3137
3219
  axisTicks = input('auto', { ...(ngDevMode ? { debugName: "axisTicks" } : /* istanbul ignore next */ {}), transform: parseAxisTicks });
3220
+ rangeKey = input(null, /* @ts-ignore */
3221
+ ...(ngDevMode ? [{ debugName: "rangeKey" }] : /* istanbul ignore next */ []));
3138
3222
  #destroyRef = inject(DestroyRef);
3139
3223
  #host = inject(ElementRef);
3140
3224
  #wide = wideViewport();
@@ -3214,6 +3298,7 @@ class UiLineChart {
3214
3298
  const reduced = this.#reducedMotion();
3215
3299
  untracked(() => this.#motion.follow(points, geometry, {
3216
3300
  reduced,
3301
+ rangeKey: this.rangeKey(),
3217
3302
  duration: () => durationFrom(getComputedStyle(this.#host.nativeElement).getPropertyValue('--duration-base')),
3218
3303
  }));
3219
3304
  });
@@ -3253,7 +3338,7 @@ class UiLineChart {
3253
3338
  this.activeIndex.set(navigation.index);
3254
3339
  }
3255
3340
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiLineChart, deps: [], target: i0.ɵɵFactoryTarget.Component });
3256
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: UiLineChart, isStandalone: true, selector: "ui-line-chart", inputs: { points: { classPropertyName: "points", publicName: "points", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, valueFormat: { classPropertyName: "valueFormat", publicName: "valueFormat", isSignal: true, isRequired: false, transformFunction: null }, tooltipFormat: { classPropertyName: "tooltipFormat", publicName: "tooltipFormat", isSignal: true, isRequired: false, transformFunction: null }, deltaFormat: { classPropertyName: "deltaFormat", publicName: "deltaFormat", isSignal: true, isRequired: false, transformFunction: null }, timeFormat: { classPropertyName: "timeFormat", publicName: "timeFormat", isSignal: true, isRequired: false, transformFunction: null }, axisFormat: { classPropertyName: "axisFormat", publicName: "axisFormat", isSignal: true, isRequired: false, transformFunction: null }, startLabel: { classPropertyName: "startLabel", publicName: "startLabel", isSignal: true, isRequired: false, transformFunction: null }, timeColumnLabel: { classPropertyName: "timeColumnLabel", publicName: "timeColumnLabel", isSignal: true, isRequired: false, transformFunction: null }, valueColumnLabel: { classPropertyName: "valueColumnLabel", publicName: "valueColumnLabel", isSignal: true, isRequired: false, transformFunction: null }, sparkline: { classPropertyName: "sparkline", publicName: "sparkline", isSignal: true, isRequired: false, transformFunction: null }, tooltip: { classPropertyName: "tooltip", publicName: "tooltip", isSignal: true, isRequired: false, transformFunction: null }, tooltipDelta: { classPropertyName: "tooltipDelta", publicName: "tooltipDelta", isSignal: true, isRequired: false, transformFunction: null }, axisGap: { classPropertyName: "axisGap", publicName: "axisGap", isSignal: true, isRequired: false, transformFunction: null }, axisTicks: { classPropertyName: "axisTicks", publicName: "axisTicks", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "block h-full" }, viewQueries: [{ propertyName: "svgRef", first: true, predicate: ["svgRef"], descendants: true, isSignal: true }, { propertyName: "lineRef", first: true, predicate: ["lineRef"], descendants: true, isSignal: true }, { propertyName: "endRef", first: true, predicate: ["endRef"], descendants: true, isSignal: true }], ngImport: i0, template: `
3341
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: UiLineChart, isStandalone: true, selector: "ui-line-chart", inputs: { points: { classPropertyName: "points", publicName: "points", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, valueFormat: { classPropertyName: "valueFormat", publicName: "valueFormat", isSignal: true, isRequired: false, transformFunction: null }, tooltipFormat: { classPropertyName: "tooltipFormat", publicName: "tooltipFormat", isSignal: true, isRequired: false, transformFunction: null }, deltaFormat: { classPropertyName: "deltaFormat", publicName: "deltaFormat", isSignal: true, isRequired: false, transformFunction: null }, timeFormat: { classPropertyName: "timeFormat", publicName: "timeFormat", isSignal: true, isRequired: false, transformFunction: null }, axisFormat: { classPropertyName: "axisFormat", publicName: "axisFormat", isSignal: true, isRequired: false, transformFunction: null }, startLabel: { classPropertyName: "startLabel", publicName: "startLabel", isSignal: true, isRequired: false, transformFunction: null }, timeColumnLabel: { classPropertyName: "timeColumnLabel", publicName: "timeColumnLabel", isSignal: true, isRequired: false, transformFunction: null }, valueColumnLabel: { classPropertyName: "valueColumnLabel", publicName: "valueColumnLabel", isSignal: true, isRequired: false, transformFunction: null }, sparkline: { classPropertyName: "sparkline", publicName: "sparkline", isSignal: true, isRequired: false, transformFunction: null }, tooltip: { classPropertyName: "tooltip", publicName: "tooltip", isSignal: true, isRequired: false, transformFunction: null }, tooltipDelta: { classPropertyName: "tooltipDelta", publicName: "tooltipDelta", isSignal: true, isRequired: false, transformFunction: null }, axisGap: { classPropertyName: "axisGap", publicName: "axisGap", isSignal: true, isRequired: false, transformFunction: null }, axisTicks: { classPropertyName: "axisTicks", publicName: "axisTicks", isSignal: true, isRequired: false, transformFunction: null }, rangeKey: { classPropertyName: "rangeKey", publicName: "rangeKey", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "block h-full" }, viewQueries: [{ propertyName: "svgRef", first: true, predicate: ["svgRef"], descendants: true, isSignal: true }, { propertyName: "lineRef", first: true, predicate: ["lineRef"], descendants: true, isSignal: true }, { propertyName: "endRef", first: true, predicate: ["endRef"], descendants: true, isSignal: true }], ngImport: i0, template: `
3257
3342
  <div class="relative h-full">
3258
3343
  <svg
3259
3344
  #svgRef
@@ -3544,12 +3629,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
3544
3629
  class: 'block h-full',
3545
3630
  },
3546
3631
  }]
3547
- }], ctorParameters: () => [], propDecorators: { points: [{ type: i0.Input, args: [{ isSignal: true, alias: "points", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], valueFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueFormat", required: false }] }], tooltipFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "tooltipFormat", required: false }] }], deltaFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "deltaFormat", required: false }] }], timeFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "timeFormat", required: false }] }], axisFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "axisFormat", required: false }] }], startLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "startLabel", required: false }] }], timeColumnLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "timeColumnLabel", required: false }] }], valueColumnLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueColumnLabel", required: false }] }], sparkline: [{ type: i0.Input, args: [{ isSignal: true, alias: "sparkline", required: false }] }], tooltip: [{ type: i0.Input, args: [{ isSignal: true, alias: "tooltip", required: false }] }], tooltipDelta: [{ type: i0.Input, args: [{ isSignal: true, alias: "tooltipDelta", required: false }] }], axisGap: [{ type: i0.Input, args: [{ isSignal: true, alias: "axisGap", required: false }] }], axisTicks: [{ type: i0.Input, args: [{ isSignal: true, alias: "axisTicks", required: false }] }], svgRef: [{ type: i0.ViewChild, args: ['svgRef', { isSignal: true }] }], lineRef: [{ type: i0.ViewChild, args: ['lineRef', { isSignal: true }] }], endRef: [{ type: i0.ViewChild, args: ['endRef', { isSignal: true }] }] } });
3632
+ }], ctorParameters: () => [], propDecorators: { points: [{ type: i0.Input, args: [{ isSignal: true, alias: "points", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], valueFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueFormat", required: false }] }], tooltipFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "tooltipFormat", required: false }] }], deltaFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "deltaFormat", required: false }] }], timeFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "timeFormat", required: false }] }], axisFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "axisFormat", required: false }] }], startLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "startLabel", required: false }] }], timeColumnLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "timeColumnLabel", required: false }] }], valueColumnLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueColumnLabel", required: false }] }], sparkline: [{ type: i0.Input, args: [{ isSignal: true, alias: "sparkline", required: false }] }], tooltip: [{ type: i0.Input, args: [{ isSignal: true, alias: "tooltip", required: false }] }], tooltipDelta: [{ type: i0.Input, args: [{ isSignal: true, alias: "tooltipDelta", required: false }] }], axisGap: [{ type: i0.Input, args: [{ isSignal: true, alias: "axisGap", required: false }] }], axisTicks: [{ type: i0.Input, args: [{ isSignal: true, alias: "axisTicks", required: false }] }], rangeKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "rangeKey", required: false }] }], svgRef: [{ type: i0.ViewChild, args: ['svgRef', { isSignal: true }] }], lineRef: [{ type: i0.ViewChild, args: ['lineRef', { isSignal: true }] }], endRef: [{ type: i0.ViewChild, args: ['endRef', { isSignal: true }] }] } });
3548
3633
 
3549
3634
  const MENU_CLASSES = 'm-0 min-w-44 p-1 rounded-container bg-(--elevated) shadow-[0_8px_24px_rgb(0_0_0/0.16),inset_0_0_0_1px_var(--border)] text-(--foreground) transition-[opacity,scale,overlay,display] transition-discrete duration-(--duration-fast) not-open:duration-[120ms] ease-out starting:open:opacity-0 starting:open:scale-(--enter-scale) opacity-0 scale-(--enter-scale) open:opacity-100 open:scale-100';
3550
3635
  const SHEET_CLASSES = 'max-lg:fixed max-lg:inset-x-4 max-lg:top-auto max-lg:w-auto max-lg:bottom-[calc(76px+env(safe-area-inset-bottom))] max-lg:min-w-0 max-lg:p-1.5 max-lg:bg-(--card) max-lg:shadow-[0_12px_32px_rgb(0_0_0/0.2),inset_0_0_0_1px_var(--border)] max-lg:backdrop:bg-[rgb(0_0_0/0.36)] max-lg:transition-[opacity,translate,overlay,display] max-lg:scale-100 max-lg:translate-y-4 max-lg:open:translate-y-0 max-lg:starting:open:scale-100 max-lg:starting:open:translate-y-4 max-lg:motion-reduce:translate-y-0 max-lg:motion-reduce:starting:open:translate-y-0';
3551
- const SHEET_ITEM_CLASSES = 'flex w-full items-center gap-3 lg:pointer-fine:gap-2.5 min-h-12 lg:min-h-11 lg:pointer-fine:min-h-9 px-3 lg:pointer-fine:px-2.5 rounded-[calc(var(--radius-container)-6px)] lg:rounded-[calc(var(--radius-container)-4px)] text-body lg:pointer-fine:text-label hover:bg-(--glow) focus-visible:bg-(--glow) active:bg-(--soft) transition-[background-color] duration-(--duration-fast) ease-out outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-(--ring) [:where(&>svg)]:size-5 [:where(&>svg)]:flex-none lg:[:where(&>svg)]:size-[18px] lg:pointer-fine:[:where(&>svg)]:size-4';
3552
- const ITEM_CLASSES = 'flex w-full items-center gap-3 pointer-fine:gap-2.5 min-h-11 pointer-fine:min-h-9 px-3 pointer-fine:px-2.5 rounded-[calc(var(--radius-container)-4px)] text-body pointer-fine:text-label hover:bg-(--glow) focus-visible:bg-(--glow) active:bg-(--soft) transition-[background-color] duration-(--duration-fast) ease-out outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-(--ring) [:where(&>svg)]:size-[18px] [:where(&>svg)]:flex-none pointer-fine:[:where(&>svg)]:size-4';
3636
+ const SHEET_ITEM_CLASSES = 'flex w-full items-center gap-3 lg:pointer-fine:gap-2.5 min-h-12 lg:min-h-11 lg:pointer-fine:min-h-9 px-3 lg:pointer-fine:px-2.5 rounded-[calc(var(--radius-container)-6px)] lg:rounded-[calc(var(--radius-container)-4px)] text-body lg:pointer-fine:text-label text-left whitespace-nowrap hover:bg-(--glow) focus-visible:bg-(--glow) active:bg-(--soft) transition-[background-color] duration-(--duration-fast) ease-out outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-(--ring) [:where(&>svg)]:size-5 [:where(&>svg)]:flex-none lg:[:where(&>svg)]:size-[18px] lg:pointer-fine:[:where(&>svg)]:size-4';
3637
+ const ITEM_CLASSES = 'flex w-full items-center gap-3 pointer-fine:gap-2.5 min-h-11 pointer-fine:min-h-9 px-3 pointer-fine:px-2.5 rounded-[calc(var(--radius-container)-4px)] text-body pointer-fine:text-label text-left whitespace-nowrap hover:bg-(--glow) focus-visible:bg-(--glow) active:bg-(--soft) transition-[background-color] duration-(--duration-fast) ease-out outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-(--ring) [:where(&>svg)]:size-[18px] [:where(&>svg)]:flex-none pointer-fine:[:where(&>svg)]:size-4';
3553
3638
  const nextId$1 = (() => {
3554
3639
  let count = 0;
3555
3640
  return () => `ui-menu-${++count}`;
@@ -3636,6 +3721,8 @@ class UiMenu {
3636
3721
  this.#host.style.position = '';
3637
3722
  this.#host.style.top = '';
3638
3723
  this.#host.style.left = '';
3724
+ this.#host.style.right = '';
3725
+ this.#host.style.bottom = '';
3639
3726
  }
3640
3727
  else {
3641
3728
  this.#position(trigger);
@@ -3695,13 +3782,15 @@ class UiMenu {
3695
3782
  #position(trigger) {
3696
3783
  const margin = 8;
3697
3784
  const triggerRect = trigger.getBoundingClientRect();
3698
- const menuRect = this.#host.getBoundingClientRect();
3785
+ const menuRect = { width: this.#host.offsetWidth, height: this.#host.offsetHeight };
3699
3786
  const fitsBelow = window.innerHeight - triggerRect.bottom >= menuRect.height + margin;
3700
3787
  const top = fitsBelow ? triggerRect.bottom + 4 : Math.max(margin, triggerRect.top - menuRect.height - 4);
3701
3788
  const left = Math.min(Math.max(triggerRect.right - menuRect.width, margin), window.innerWidth - menuRect.width - margin);
3702
3789
  this.#host.style.position = 'fixed';
3703
3790
  this.#host.style.top = `${Math.min(top, window.innerHeight - menuRect.height - margin)}px`;
3704
3791
  this.#host.style.left = `${left}px`;
3792
+ this.#host.style.right = 'auto';
3793
+ this.#host.style.bottom = 'auto';
3705
3794
  this.#host.style.transformOrigin = fitsBelow ? 'top right' : 'bottom right';
3706
3795
  }
3707
3796
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiMenu, deps: [], target: i0.ɵɵFactoryTarget.Component });
@@ -3851,6 +3940,40 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
3851
3940
  }]
3852
3941
  }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], valueText: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueText", required: true }] }], tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }] } });
3853
3942
 
3943
+ class FlipItems {
3944
+ items = new Set();
3945
+ destroyed = new Set();
3946
+ used = false;
3947
+ get marked() {
3948
+ return this.used;
3949
+ }
3950
+ add(item) {
3951
+ this.used = true;
3952
+ this.items.add(item);
3953
+ }
3954
+ remove(item) {
3955
+ this.destroyed.add(item);
3956
+ }
3957
+ inside(host) {
3958
+ const inside = [];
3959
+ for (const item of this.items) {
3960
+ if (host.contains(item)) {
3961
+ inside.push(item);
3962
+ }
3963
+ else if (this.destroyed.has(item)) {
3964
+ this.items.delete(item);
3965
+ this.destroyed.delete(item);
3966
+ }
3967
+ }
3968
+ return inside;
3969
+ }
3970
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FlipItems, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3971
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FlipItems });
3972
+ }
3973
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FlipItems, decorators: [{
3974
+ type: Injectable
3975
+ }] });
3976
+
3854
3977
  function readDuration$1(element, name, fallback) {
3855
3978
  const raw = getComputedStyle(element).getPropertyValue(name).trim();
3856
3979
  const value = parseFloat(raw);
@@ -3863,31 +3986,57 @@ function readEasing(element, name, fallback) {
3863
3986
  return getComputedStyle(element).getPropertyValue(name).trim() || fallback;
3864
3987
  }
3865
3988
 
3989
+ function translateY(element) {
3990
+ const matrix = /^matrix\((.+)\)$/.exec(getComputedStyle(element).transform);
3991
+ return matrix ? parseFloat(matrix[1].split(',')[5]) : 0;
3992
+ }
3866
3993
  /**
3867
- * Slides the remaining children of a list into place when one is removed or inserted (FLIP): it
3868
- * measures every child before and after the change, then animates the shift in `translateY` over
3869
- * `--duration-base` `--ease-out`. Pair it with `animate.leave="ui-leave-fade"` on the item so it fades
3994
+ * Slides the items of a list into place when one is removed or inserted (FLIP): it measures every item
3995
+ * before and after the change, from the top of the list itself, then animates the shift in `translateY`
3996
+ * over `--duration-base` `--ease-out`. Pair it with `animate.leave="ui-leave-fade"` on the item so it fades
3870
3997
  * out first. Nothing moves under `prefers-reduced-motion`: the list closes up at once.
3871
3998
  *
3999
+ * By default the items are the direct children of the list. Mark them with `uiFlipItem` instead when they
4000
+ * sit deeper, for instance a page of several groups: put `uiFlipList` on the element around every group and
4001
+ * `uiFlipItem` on each group heading and row, so a removal also slides the groups below. An item may sit
4002
+ * inside another item (a row inside a card): it then moves by its own share and its card carries the rest.
4003
+ * Only an item added or removed starts a slide; a change inside an item does not. Positions leave out the
4004
+ * translate of any animation running inside the list (a slide in progress, an enter fade-up).
4005
+ *
3872
4006
  * @example
3873
4007
  * <ul uiFlipList>
3874
4008
  * @for (key of keys(); track key.id) {
3875
4009
  * <li uiListRow animate.leave="ui-leave-fade">...</li>
3876
4010
  * }
3877
4011
  * </ul>
4012
+ *
4013
+ * <table uiTable uiFlipList>
4014
+ * @for (group of groups(); track group.id) {
4015
+ * <tbody>
4016
+ * <tr uiTr group uiFlipItem>...</tr>
4017
+ * @for (row of group.rows; track row.id) {
4018
+ * <tr uiTr uiFlipItem animate.leave="ui-leave-fade">...</tr>
4019
+ * }
4020
+ * </tbody>
4021
+ * }
4022
+ * </table>
3878
4023
  */
3879
4024
  class UiFlipList {
3880
4025
  host = inject(ElementRef).nativeElement;
3881
4026
  reduced = injectReducedMotion();
3882
4027
  mutations = new MutationObserver(() => this.onChange());
3883
- resizes = new ResizeObserver(() => this.measure());
4028
+ resizes = new ResizeObserver(() => {
4029
+ this.tops = this.measure();
4030
+ });
3884
4031
  running = new Map();
3885
4032
  watched = new Set();
4033
+ items = inject(FlipItems, { self: true });
3886
4034
  tops = new Map();
4035
+ shifts = new Map();
3887
4036
  constructor() {
3888
4037
  afterNextRender(() => {
3889
- this.measure();
3890
- this.mutations.observe(this.host, { childList: true });
4038
+ this.tops = this.measure();
4039
+ this.mutations.observe(this.host, { childList: true, subtree: true });
3891
4040
  this.resizes.observe(this.host);
3892
4041
  });
3893
4042
  inject(DestroyRef).onDestroy(() => {
@@ -3895,57 +4044,118 @@ class UiFlipList {
3895
4044
  this.resizes.disconnect();
3896
4045
  });
3897
4046
  }
4047
+ tracked() {
4048
+ if (!this.items.marked) {
4049
+ return Array.from(this.host.children).filter((child) => child instanceof HTMLElement);
4050
+ }
4051
+ return this.items.inside(this.host);
4052
+ }
3898
4053
  measure() {
3899
- const children = Array.from(this.host.children).filter((child) => child instanceof HTMLElement);
3900
- this.tops = new Map(children.map((child) => [child, child.offsetTop]));
3901
- for (const child of children) {
3902
- if (!this.watched.has(child)) {
3903
- this.watched.add(child);
3904
- this.resizes.observe(child);
4054
+ const elements = this.tracked();
4055
+ const origin = this.host.getBoundingClientRect().top - this.host.scrollTop;
4056
+ const tops = new Map(elements.map((element) => [element, element.getBoundingClientRect().top - origin]));
4057
+ this.shifts = new Map();
4058
+ for (const animation of this.host.getAnimations({ subtree: true })) {
4059
+ const target = animation.effect.target;
4060
+ if (target !== this.host) {
4061
+ this.shifts.set(target, translateY(target));
3905
4062
  }
3906
4063
  }
3907
- for (const child of this.watched) {
3908
- if (!this.tops.has(child)) {
3909
- this.watched.delete(child);
3910
- this.running.delete(child);
3911
- this.resizes.unobserve(child);
4064
+ for (const [moving, shift] of this.shifts) {
4065
+ for (const [element, top] of tops) {
4066
+ if (moving.contains(element)) {
4067
+ tops.set(element, top - shift);
4068
+ }
3912
4069
  }
3913
4070
  }
3914
- }
3915
- interruptedOffset(child) {
3916
- const running = this.running.get(child);
3917
- if (!running) {
3918
- return 0;
4071
+ for (const element of elements) {
4072
+ if (!this.watched.has(element)) {
4073
+ this.watched.add(element);
4074
+ this.resizes.observe(element);
4075
+ }
3919
4076
  }
3920
- const shown = child.getBoundingClientRect().top;
3921
- running.cancel();
3922
- this.running.delete(child);
3923
- return shown - child.getBoundingClientRect().top;
4077
+ for (const element of this.watched) {
4078
+ if (!tops.has(element)) {
4079
+ this.watched.delete(element);
4080
+ this.running.delete(element);
4081
+ this.resizes.unobserve(element);
4082
+ }
4083
+ }
4084
+ return tops;
3924
4085
  }
3925
4086
  onChange() {
3926
4087
  const before = this.tops;
3927
- this.measure();
3928
- if (this.reduced()) {
4088
+ const after = this.measure();
4089
+ this.tops = after;
4090
+ const changed = after.size !== before.size || Array.from(after.keys()).some((element) => !before.has(element));
4091
+ if (this.reduced() || !changed) {
3929
4092
  return;
3930
4093
  }
3931
4094
  const duration = readDuration$1(this.host, '--duration-base', 260);
3932
4095
  const easing = readEasing(this.host, '--ease-out', 'ease-out');
3933
- for (const [child, top] of this.tops) {
3934
- const previous = before.get(child);
3935
- if (previous === undefined || previous === top) {
4096
+ const moved = (element) => (before.get(element) ?? 0) - (after.get(element) ?? 0);
4097
+ for (const [element] of after) {
4098
+ if (!before.has(element)) {
4099
+ continue;
4100
+ }
4101
+ const own = moved(element) - moved(this.parentItem(element, before, after));
4102
+ if (Math.abs(own) < 0.5) {
3936
4103
  continue;
3937
4104
  }
3938
- const from = previous - top + this.interruptedOffset(child);
3939
- this.running.set(child, child.animate([{ transform: `translateY(${from}px)` }, { transform: 'translateY(0)' }], { duration, easing }));
4105
+ const from = own + (this.shifts.get(element) ?? 0);
4106
+ this.running.get(element)?.cancel();
4107
+ const animation = element.animate([{ transform: `translateY(${from}px)` }, { transform: 'translateY(0)' }], {
4108
+ duration,
4109
+ easing,
4110
+ });
4111
+ animation.onfinish = () => {
4112
+ if (this.running.get(element) === animation) {
4113
+ this.running.delete(element);
4114
+ }
4115
+ };
4116
+ this.running.set(element, animation);
4117
+ }
4118
+ }
4119
+ parentItem(element, before, after) {
4120
+ let node = element.parentElement;
4121
+ while (node !== this.host && !(before.has(node) && after.has(node))) {
4122
+ node = node.parentElement;
3940
4123
  }
4124
+ return node;
3941
4125
  }
3942
4126
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiFlipList, deps: [], target: i0.ɵɵFactoryTarget.Directive });
3943
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.7", type: UiFlipList, isStandalone: true, selector: "[uiFlipList]", ngImport: i0 });
4127
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.7", type: UiFlipList, isStandalone: true, selector: "[uiFlipList]", providers: [FlipItems], ngImport: i0 });
3944
4128
  }
3945
4129
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiFlipList, decorators: [{
3946
4130
  type: Directive,
3947
4131
  args: [{
3948
4132
  selector: '[uiFlipList]',
4133
+ providers: [FlipItems],
4134
+ }]
4135
+ }], ctorParameters: () => [] });
4136
+ /**
4137
+ * Marks an element as an item of the nearest `uiFlipList`, at any depth: a group heading, a row inside a
4138
+ * group, a card. Once one item is marked, the list follows its marked items only.
4139
+ *
4140
+ * @example
4141
+ * <tr uiTr uiFlipItem animate.leave="ui-leave-fade">...</tr>
4142
+ */
4143
+ class UiFlipItem {
4144
+ constructor() {
4145
+ const items = inject(FlipItems, { optional: true });
4146
+ if (items) {
4147
+ const element = inject(ElementRef).nativeElement;
4148
+ items.add(element);
4149
+ inject(DestroyRef).onDestroy(() => items.remove(element));
4150
+ }
4151
+ }
4152
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiFlipItem, deps: [], target: i0.ɵɵFactoryTarget.Directive });
4153
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.7", type: UiFlipItem, isStandalone: true, selector: "[uiFlipItem]", ngImport: i0 });
4154
+ }
4155
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiFlipItem, decorators: [{
4156
+ type: Directive,
4157
+ args: [{
4158
+ selector: '[uiFlipItem]',
3949
4159
  }]
3950
4160
  }], ctorParameters: () => [] });
3951
4161
 
@@ -3969,6 +4179,10 @@ function paintedElements(host) {
3969
4179
  * cells, on a `td[ui-group-cell]` the band, anywhere else the host. Under `prefers-reduced-motion` the
3970
4180
  * scroll is instant and the fade lasts 600 ms.
3971
4181
  *
4182
+ * It starts on the next frame and, when the element is still playing a finite animation of its own (an
4183
+ * `animate.enter` fade on a new row), once that animation has finished, waiting no longer than
4184
+ * `--duration-base`: the element fades in, then lights up. Both can sit on the same element.
4185
+ *
3972
4186
  * Give the element `scroll-margin-top` and `scroll-margin-bottom` equal to what is pinned above and
3973
4187
  * below it (header, tab bar, action bar): a row under them counts as off screen and is scrolled out.
3974
4188
  * For an arrival on a page opened at the element, scroll to it at once first (`scrollIntoView` in
@@ -4045,15 +4259,38 @@ class UiHighlight {
4045
4259
  return rect.top >= top && rect.bottom <= window.innerHeight - bottom;
4046
4260
  }
4047
4261
  flash() {
4048
- const fade = readDuration$1(this.host, '--duration-highlight', 1200);
4049
- const easing = readEasing(this.host, '--ease-out', 'ease-out');
4050
- const total = HOLD_MS + fade;
4051
- const animations = paintedElements(this.host).map((element) => element.animate([
4052
- { backgroundColor: 'var(--soft)', offset: 0 },
4053
- { backgroundColor: 'var(--soft)', offset: HOLD_MS / total, easing },
4054
- { offset: 1 },
4055
- ], { duration: total }));
4056
- return () => animations.forEach((animation) => animation.cancel());
4262
+ const painted = paintedElements(this.host);
4263
+ let animations = [];
4264
+ let timer;
4265
+ const paint = () => {
4266
+ const fade = readDuration$1(this.host, '--duration-highlight', 1200);
4267
+ const easing = readEasing(this.host, '--ease-out', 'ease-out');
4268
+ const total = HOLD_MS + fade;
4269
+ animations = painted.map((element) => element.animate([
4270
+ { backgroundColor: 'var(--soft)', offset: 0 },
4271
+ { backgroundColor: 'var(--soft)', offset: HOLD_MS / total, easing },
4272
+ { offset: 1 },
4273
+ ], { duration: total }));
4274
+ };
4275
+ const frame = requestAnimationFrame(() => {
4276
+ const remaining = Array.from(new Set([this.host, ...painted]))
4277
+ .flatMap((element) => element.getAnimations())
4278
+ .map((animation) => {
4279
+ const timing = animation.effect.getComputedTiming();
4280
+ return Number(timing.endTime) - Number(timing.localTime ?? 0);
4281
+ })
4282
+ .filter((left) => Number.isFinite(left));
4283
+ if (remaining.length === 0) {
4284
+ paint();
4285
+ return;
4286
+ }
4287
+ timer = setTimeout(paint, Math.min(Math.max(...remaining), readDuration$1(this.host, '--duration-base', 260)));
4288
+ });
4289
+ return () => {
4290
+ cancelAnimationFrame(frame);
4291
+ clearTimeout(timer);
4292
+ animations.forEach((animation) => animation.cancel());
4293
+ };
4057
4294
  }
4058
4295
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiHighlight, deps: [], target: i0.ɵɵFactoryTarget.Directive });
4059
4296
  static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.7", type: UiHighlight, isStandalone: true, selector: "[uiHighlight]", inputs: { uiHighlight: { classPropertyName: "uiHighlight", publicName: "uiHighlight", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
@@ -4065,11 +4302,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
4065
4302
  }]
4066
4303
  }], ctorParameters: () => [], propDecorators: { uiHighlight: [{ type: i0.Input, args: [{ isSignal: true, alias: "uiHighlight", required: false }] }] } });
4067
4304
 
4068
- const BASE_CLASSES$2 = 'flex items-center gap-3 h-10 px-3 rounded-control text-label leading-normal font-medium transition-[scale,background-color,color] [transition-duration:var(--duration-press),var(--duration-fast),var(--duration-fast)] ease-out active:scale-(--press-scale) hover:bg-(--glow) hover:text-(--foreground) focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-(--ring)';
4069
- const ACTIVE_CLASSES = 'bg-(--soft) text-(--foreground)';
4305
+ const BASE_CLASSES$2 = 'flex items-center gap-3 h-10 px-3 rounded-control text-label leading-normal font-medium transition-[scale,background-color] [transition-duration:var(--duration-press),var(--duration-fast)] ease-out active:scale-(--press-scale) hover:bg-(--glow) hover:text-(--foreground) focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-(--ring)';
4306
+ const ACTIVE_CLASSES = 'bg-[image:linear-gradient(var(--soft),var(--soft))] text-(--foreground)';
4070
4307
  const IDLE_CLASSES = 'text-(--muted-foreground) active:bg-(--soft)';
4071
4308
  /**
4072
- * One destination of the sidebar navigation (Portfolio, Holdings, Allocation, Accounts).
4309
+ * One destination of the sidebar navigation (Portfolio, Holdings, Allocation, Accounts). The active
4310
+ * fill and text colour switch at once on navigation; only the hover fill fades.
4073
4311
  *
4074
4312
  * @example
4075
4313
  * <a ui-nav-item [active]="url === '/'" routerLink="/">
@@ -4081,6 +4319,9 @@ class UiNavItem {
4081
4319
  active = input(false, { ...(ngDevMode ? { debugName: "active" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4082
4320
  classes = computed(() => `${BASE_CLASSES$2} ${this.active() ? ACTIVE_CLASSES : IDLE_CLASSES}`, /* @ts-ignore */
4083
4321
  ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
4322
+ constructor() {
4323
+ holdTransitionsUntilRendered();
4324
+ }
4084
4325
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiNavItem, deps: [], target: i0.ɵɵFactoryTarget.Component });
4085
4326
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.7", type: UiNavItem, isStandalone: true, selector: "a[ui-nav-item]", inputs: { active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "classes()", "attr.aria-current": "active() ? 'page' : null" } }, ngImport: i0, template: '<ng-content select="[navIcon]" /><ng-content />', isInline: true });
4086
4327
  }
@@ -4094,12 +4335,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
4094
4335
  '[attr.aria-current]': "active() ? 'page' : null",
4095
4336
  },
4096
4337
  }]
4097
- }], propDecorators: { active: [{ type: i0.Input, args: [{ isSignal: true, alias: "active", required: false }] }] } });
4338
+ }], ctorParameters: () => [], propDecorators: { active: [{ type: i0.Input, args: [{ isSignal: true, alias: "active", required: false }] }] } });
4098
4339
 
4099
4340
  const ROW_SIZES = ['md', 'lg', 'xl', 'card', 'dense'];
4100
4341
  const ROW_PADDINGS = ['md', 'sm', 'none'];
4101
4342
  const ROW_GAPS = ['default', 'sm'];
4102
- const SIZE_CLASSES$1 = {
4343
+ const SIZE_CLASSES$2 = {
4103
4344
  md: 'min-h-14',
4104
4345
  lg: 'min-h-15',
4105
4346
  xl: 'min-h-18 pointer-fine:min-h-17',
@@ -4130,9 +4371,10 @@ class UiRow {
4130
4371
  ...(ngDevMode ? [{ debugName: "padding" }] : /* istanbul ignore next */ []));
4131
4372
  gap = input('default', /* @ts-ignore */
4132
4373
  ...(ngDevMode ? [{ debugName: "gap" }] : /* istanbul ignore next */ []));
4133
- classes = computed(() => `${BASE_CLASSES$1} ${SIZE_CLASSES$1[this.size()]} ${this.gap() === 'sm' ? 'gap-2' : SIZE_GAPS[this.size()]} ${PADDING_CLASSES[this.padding()]}${this.unavailable() ? ` ${UNAVAILABLE_CLASSES}` : ` ${INTERACTIVE_CLASSES}`}${this.selected() ? ` ${SELECTED_CLASSES$1}` : ''}${this.busy() ? ' pointer-events-none' : ''}`, /* @ts-ignore */
4374
+ classes = computed(() => `${BASE_CLASSES$1} ${SIZE_CLASSES$2[this.size()]} ${this.gap() === 'sm' ? 'gap-2' : SIZE_GAPS[this.size()]} ${PADDING_CLASSES[this.padding()]}${this.unavailable() ? ` ${UNAVAILABLE_CLASSES}` : ` ${INTERACTIVE_CLASSES}`}${this.selected() ? ` ${SELECTED_CLASSES$1}` : ''}${this.busy() ? ' pointer-events-none' : ''}`, /* @ts-ignore */
4134
4375
  ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
4135
4376
  constructor() {
4377
+ holdTransitionsUntilRendered();
4136
4378
  const host = inject(ElementRef).nativeElement;
4137
4379
  const guard = (event) => {
4138
4380
  if (this.busy() || this.unavailable()) {
@@ -4253,9 +4495,9 @@ const nextId = (() => {
4253
4495
  let count = 0;
4254
4496
  return () => `ui-segmented-${++count}`;
4255
4497
  })();
4256
- const OPTION_CLASSES = "relative z-10 flex-1 cursor-pointer px-3 text-label font-medium transition-[scale,color] duration-(--duration-press) ease-out after:absolute after:inset-x-0 after:top-1/2 after:h-(--row-min) after:-translate-y-1/2 after:content-[''] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-(--ring) active:scale-(--press-scale)";
4498
+ const OPTION_CLASSES = "relative z-10 flex-1 cursor-pointer px-3 text-label font-medium transition-[scale] duration-(--duration-press) ease-out after:absolute after:inset-x-0 after:top-1/2 after:h-(--row-min) after:-translate-y-1/2 after:content-[''] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-(--ring) active:scale-(--press-scale)";
4257
4499
  const SEGMENTED_SIZES = ['md', 'sm'];
4258
- const SIZE_CLASSES = {
4500
+ const SIZE_CLASSES$1 = {
4259
4501
  md: 'h-10 pointer-fine:h-8',
4260
4502
  sm: 'h-10 pointer-fine:h-[30px]',
4261
4503
  };
@@ -4286,8 +4528,11 @@ class UiSegmented {
4286
4528
  return index === -1 ? 0 : index + 1;
4287
4529
  }, /* @ts-ignore */
4288
4530
  ...(ngDevMode ? [{ debugName: "thumbIndex" }] : /* istanbul ignore next */ []));
4531
+ constructor() {
4532
+ holdTransitionsUntilRendered();
4533
+ }
4289
4534
  optionClasses(optionValue) {
4290
- return `${OPTION_CLASSES} ${SIZE_CLASSES[this.size()]} ${optionValue === this.value() ? SELECTED_CLASSES : UNSELECTED_CLASSES}`;
4535
+ return `${OPTION_CLASSES} ${SIZE_CLASSES$1[this.size()]} ${optionValue === this.value() ? SELECTED_CLASSES : UNSELECTED_CLASSES}`;
4291
4536
  }
4292
4537
  tabIndexFor(optionValue) {
4293
4538
  const opts = this.options();
@@ -4399,7 +4644,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
4399
4644
  </div>
4400
4645
  `,
4401
4646
  }]
4402
- }], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], showLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "showLabel", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }, { type: i0.Output, args: ["valueChange"] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], radios: [{ type: i0.ViewChildren, args: ['radio', { isSignal: true }] }] } });
4647
+ }], ctorParameters: () => [], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], showLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "showLabel", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }, { type: i0.Output, args: ["valueChange"] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], radios: [{ type: i0.ViewChildren, args: ['radio', { isSignal: true }] }] } });
4403
4648
 
4404
4649
  const SELECT_CLASSES = 'cursor-pointer appearance-none pr-10 bg-(image:--chevron-down) bg-no-repeat bg-[position:right_0.75rem_center] bg-[size:1.125rem]';
4405
4650
  // Spelled out prefix included: Tailwind scans for literal strings, so a prefix applied in code
@@ -4541,22 +4786,31 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
4541
4786
  }]
4542
4787
  }], propDecorators: { shape: [{ type: i0.Input, args: [{ isSignal: true, alias: "shape", required: false }] }], lines: [{ type: i0.Input, args: [{ isSignal: true, alias: "lines", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }] } });
4543
4788
 
4544
- const BASE_CLASSES = 'inline-flex items-center gap-0.5 rounded-[4px] text-caption font-medium text-(--stale) hover:underline underline-offset-3 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--ring)';
4789
+ /** Available stale link sizes. `StaleLinkSize` is derived from this tuple. */
4790
+ const STALE_LINK_SIZES = ['caption', 'label'];
4791
+ const BASE_CLASSES = "relative inline-flex items-center gap-0.5 rounded-[4px] font-medium text-(--stale) hover:underline underline-offset-3 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--ring) before:absolute before:inset-x-0 before:top-1/2 before:h-11 pointer-fine:before:h-10 before:-translate-y-1/2 before:content-['']";
4792
+ const SIZE_CLASSES = {
4793
+ caption: 'text-caption',
4794
+ label: 'text-label',
4795
+ };
4545
4796
  const CHEVRON_CLASSES = "after:size-4 after:flex-none after:bg-current after:content-[''] after:mask-center after:mask-no-repeat after:mask-[url(data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2024%2024%27%20fill%3D%27none%27%20stroke%3D%27black%27%20stroke-width%3D%271.75%27%20stroke-linecap%3D%27round%27%20stroke-linejoin%3D%27round%27%3E%3Cpath%20d%3D%27m9%2018%206-6-6-6%27%2F%3E%3C%2Fsvg%3E)]";
4546
4797
  /**
4547
4798
  * Link in the stale tone, for a caption about data that is late or left out of a total, such as
4548
- * "1 ligne sans cours, non comptée". Caption size, weight 500, underlined on hover, with an optional
4549
- * trailing chevron.
4799
+ * "1 ligne sans cours, non comptée". Weight 500, underlined on hover, with an optional trailing
4800
+ * chevron. `size` is `caption` (12/17, the default) or `label` (14/20, the dashboard total's
4801
+ * "1 cours en retard").
4550
4802
  *
4551
4803
  * @example
4552
- * <a uiStaleLink chevron routerLink="/holdings">1 cours en retard</a>
4804
+ * <a uiStaleLink chevron size="label" routerLink="/holdings">1 cours en retard</a>
4553
4805
  */
4554
4806
  class UiStaleLink {
4555
4807
  chevron = input(false, { ...(ngDevMode ? { debugName: "chevron" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4556
- classes = computed(() => (this.chevron() ? `${BASE_CLASSES} ${CHEVRON_CLASSES}` : BASE_CLASSES), /* @ts-ignore */
4808
+ size = input('caption', /* @ts-ignore */
4809
+ ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
4810
+ classes = computed(() => `${BASE_CLASSES} ${SIZE_CLASSES[this.size()]}${this.chevron() ? ` ${CHEVRON_CLASSES}` : ''}`, /* @ts-ignore */
4557
4811
  ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
4558
4812
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiStaleLink, deps: [], target: i0.ɵɵFactoryTarget.Directive });
4559
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.7", type: UiStaleLink, isStandalone: true, selector: "a[uiStaleLink]", inputs: { chevron: { classPropertyName: "chevron", publicName: "chevron", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "classes()" } }, ngImport: i0 });
4813
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.7", type: UiStaleLink, isStandalone: true, selector: "a[uiStaleLink]", inputs: { chevron: { classPropertyName: "chevron", publicName: "chevron", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "classes()" } }, ngImport: i0 });
4560
4814
  }
4561
4815
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiStaleLink, decorators: [{
4562
4816
  type: Directive,
@@ -4564,7 +4818,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
4564
4818
  selector: 'a[uiStaleLink]',
4565
4819
  host: { '[class]': 'classes()' },
4566
4820
  }]
4567
- }], propDecorators: { chevron: [{ type: i0.Input, args: [{ isSignal: true, alias: "chevron", required: false }] }] } });
4821
+ }], propDecorators: { chevron: [{ type: i0.Input, args: [{ isSignal: true, alias: "chevron", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }] } });
4568
4822
 
4569
4823
  /** Available stat sizes. `StatSize` is derived from this tuple. */
4570
4824
  const STAT_SIZES = ['hero', 'tile'];
@@ -4663,6 +4917,9 @@ const CLASSES = "relative shrink-0 w-[51px] h-[31px] pointer-fine:w-10 pointer-f
4663
4917
  */
4664
4918
  class UiSwitch {
4665
4919
  classes = CLASSES;
4920
+ constructor() {
4921
+ holdTransitionsUntilRendered();
4922
+ }
4666
4923
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiSwitch, deps: [], target: i0.ɵɵFactoryTarget.Directive });
4667
4924
  static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.7", type: UiSwitch, isStandalone: true, selector: "input[type=checkbox][uiSwitch]", host: { attributes: { "role": "switch" }, properties: { "class": "classes" } }, ngImport: i0 });
4668
4925
  }
@@ -4675,13 +4932,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
4675
4932
  '[class]': 'classes',
4676
4933
  },
4677
4934
  }]
4678
- }] });
4935
+ }], ctorParameters: () => [] });
4679
4936
 
4937
+ const HEIGHT_PROPERTY = '--tab-bar-height';
4938
+ const HEIGHT = 'calc(52px + env(safe-area-inset-bottom))';
4680
4939
  const TAB_CLASSES = 'flex h-[52px] flex-col items-center justify-center gap-[3px] touch-manipulation select-none transition-transform duration-(--duration-press) ease-out active:scale-(--press-scale) focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-(--ring)';
4681
4940
  const INACTIVE_TAB_CLASSES = 'font-medium text-(--muted-foreground)';
4682
4941
  const ACTIVE_TAB_CLASSES = 'font-semibold text-(--foreground)';
4683
4942
  /**
4684
- * Bottom navigation bar for iPhone: 52px of tabs plus the home indicator's safe area.
4943
+ * Bottom navigation bar for iPhone: 52px of tabs plus the home indicator's safe area. While it is on the
4944
+ * page it publishes that height as `--tab-bar-height` on the root element, which `ui-action-bar` and
4945
+ * `ui-toaster` sit above; use it rather than restating 52px for anything that must clear the bar.
4685
4946
  *
4686
4947
  * @example
4687
4948
  * <nav ui-tab-bar>
@@ -4689,6 +4950,11 @@ const ACTIVE_TAB_CLASSES = 'font-semibold text-(--foreground)';
4689
4950
  * </nav>
4690
4951
  */
4691
4952
  class UiTabBar {
4953
+ constructor() {
4954
+ const root = inject(DOCUMENT).documentElement;
4955
+ root.style.setProperty(HEIGHT_PROPERTY, HEIGHT);
4956
+ inject(DestroyRef).onDestroy(() => root.style.removeProperty(HEIGHT_PROPERTY));
4957
+ }
4692
4958
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiTabBar, deps: [], target: i0.ɵɵFactoryTarget.Component });
4693
4959
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: UiTabBar, isStandalone: true, selector: "nav[ui-tab-bar]", host: { classAttribute: "grid grid-flow-col auto-cols-fr items-stretch bg-(--background)/90 backdrop-blur [@media(prefers-reduced-transparency:reduce)]:bg-(--background) shadow-[inset_0_1px_0_var(--hairline)] pb-[env(safe-area-inset-bottom)]" }, ngImport: i0, template: '<ng-content />', isInline: true });
4694
4960
  }
@@ -4701,7 +4967,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
4701
4967
  class: 'grid grid-flow-col auto-cols-fr items-stretch bg-(--background)/90 backdrop-blur [@media(prefers-reduced-transparency:reduce)]:bg-(--background) shadow-[inset_0_1px_0_var(--hairline)] pb-[env(safe-area-inset-bottom)]',
4702
4968
  },
4703
4969
  }]
4704
- }] });
4970
+ }], ctorParameters: () => [] });
4705
4971
  /**
4706
4972
  * One destination of `ui-tab-bar`.
4707
4973
  *
@@ -4837,7 +5103,7 @@ class UiToaster {
4837
5103
  this.lastId = 0;
4838
5104
  }
4839
5105
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiToaster, deps: [], target: i0.ɵɵFactoryTarget.Component });
4840
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: UiToaster, isStandalone: true, selector: "ui-toaster", host: { attributes: { "role": "status" }, classAttribute: "pointer-events-none fixed inset-x-0 z-50 flex justify-center bottom-[calc(52px+env(safe-area-inset-bottom)+var(--action-bar-height,0px)+8px)] lg:inset-x-auto lg:right-6 lg:bottom-6" }, ngImport: i0, template: `
5106
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: UiToaster, isStandalone: true, selector: "ui-toaster", host: { attributes: { "role": "status" }, classAttribute: "pointer-events-none fixed inset-x-0 z-50 flex justify-center bottom-[calc(var(--tab-bar-height,calc(52px+env(safe-area-inset-bottom)))+var(--action-bar-height,0px)+8px)] lg:inset-x-auto lg:right-6 lg:bottom-6" }, ngImport: i0, template: `
4841
5107
  @if (toasts.toast(); as toast) {
4842
5108
  <div
4843
5109
  animate.leave="ui-leave-fade"
@@ -4872,7 +5138,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
4872
5138
  `,
4873
5139
  host: {
4874
5140
  role: 'status',
4875
- class: 'pointer-events-none fixed inset-x-0 z-50 flex justify-center bottom-[calc(52px+env(safe-area-inset-bottom)+var(--action-bar-height,0px)+8px)] lg:inset-x-auto lg:right-6 lg:bottom-6',
5141
+ class: 'pointer-events-none fixed inset-x-0 z-50 flex justify-center bottom-[calc(var(--tab-bar-height,calc(52px+env(safe-area-inset-bottom)))+var(--action-bar-height,0px)+8px)] lg:inset-x-auto lg:right-6 lg:bottom-6',
4876
5142
  },
4877
5143
  }]
4878
5144
  }], ctorParameters: () => [] });
@@ -4915,6 +5181,9 @@ class UiTable {
4915
5181
  this.spaced() ? '[--table-head-gap:0.25rem]' : '[--table-head-gap:0px]',
4916
5182
  ].join(' '), /* @ts-ignore */
4917
5183
  ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
5184
+ constructor() {
5185
+ holdTransitionsUntilRendered();
5186
+ }
4918
5187
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiTable, deps: [], target: i0.ɵɵFactoryTarget.Directive });
4919
5188
  static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.7", type: UiTable, isStandalone: true, selector: "table[uiTable]", inputs: { row: { classPropertyName: "row", publicName: "row", isSignal: true, isRequired: false, transformFunction: null }, rule: { classPropertyName: "rule", publicName: "rule", isSignal: true, isRequired: false, transformFunction: null }, spaced: { classPropertyName: "spaced", publicName: "spaced", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "classes()" } }, ngImport: i0 });
4920
5189
  }
@@ -4926,7 +5195,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
4926
5195
  '[class]': 'classes()',
4927
5196
  },
4928
5197
  }]
4929
- }], propDecorators: { row: [{ type: i0.Input, args: [{ isSignal: true, alias: "row", required: false }] }], rule: [{ type: i0.Input, args: [{ isSignal: true, alias: "rule", required: false }] }], spaced: [{ type: i0.Input, args: [{ isSignal: true, alias: "spaced", required: false }] }] } });
5198
+ }], ctorParameters: () => [], propDecorators: { row: [{ type: i0.Input, args: [{ isSignal: true, alias: "row", required: false }] }], rule: [{ type: i0.Input, args: [{ isSignal: true, alias: "rule", required: false }] }], spaced: [{ type: i0.Input, args: [{ isSignal: true, alias: "spaced", required: false }] }] } });
4930
5199
  /** Body of one group of rows: leaves a 4px tail after its last row. */
4931
5200
  class UiGroup {
4932
5201
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiGroup, deps: [], target: i0.ɵɵFactoryTarget.Directive });
@@ -5193,5 +5462,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
5193
5462
  * Generated bundle index. Do not edit.
5194
5463
  */
5195
5464
 
5196
- export { ALERT_VARIANTS, AMOUNT_MASKINGS, AMOUNT_NUMERICS, ASYNC_ALIGNS, ASYNC_STATES, ASYNC_VARIANTS, AVATAR_SIZES, BACK_LINK_SIZES, BADGE_SIZES, BADGE_VARIANTS, BUTTON_SIZES, BUTTON_VARIANTS, CARD_PADDINGS, CARD_SURFACES, CARD_VARIANTS, CELL_BREAKPOINTS, CONTROL_SIZES, CONTROL_SURFACES, DELTA_EMPHASES, DELTA_SIZES, DELTA_WEIGHTS, DIALOG_CLOSE_REASONS, DIALOG_LAYOUTS, DIALOG_WIDTHS, FACT_SIZES, FACT_SUB_TONES, GROUP_SIZES, METER_SIZES, METER_TONES, ROW_GAPS, ROW_PADDINGS, ROW_SIZES, SEGMENTED_SIZES, SKELETON_SHAPES, STAT_SIZES, SUB_TONES, TABLE_ROWS, UI_AMOUNT_MASKED, UiActionBar, UiAlert, UiAmount, UiAsync, UiAvatar, UiAvatarLink, UiBackLink, UiBadge, UiButton, UiCard, UiCellSub, UiChoiceChips, UiDelta, UiDialog, UiDonut, UiEmpty, UiExternalLink, UiFact, UiFacts, UiField, UiFieldLeading, UiFieldTrailing, UiFilterChips, UiFlipList, UiGroup, UiGroupCell, UiHighlight, UiInput, UiLineChart, UiListRow, UiMenu, UiMenuItem, UiMenuTrigger, UiMeter, UiNavItem, UiRow, UiRowAction, UiRowItem, UiRowLink, UiRowTile, UiSegmented, UiSelect, UiSkeleton, UiStaleLink, UiStat, UiSwitch, UiTab, UiTabBar, UiTable, UiTd, UiTextarea, UiTh, UiToaster, UiToasts, UiTr, delayedState, formatAmount, injectReducedMotion };
5465
+ export { ALERT_VARIANTS, AMOUNT_MASKINGS, AMOUNT_NUMERICS, ASYNC_ALIGNS, ASYNC_STATES, ASYNC_VARIANTS, AVATAR_SIZES, BACK_LINK_SIZES, BADGE_SIZES, BADGE_VARIANTS, BUTTON_SIZES, BUTTON_VARIANTS, CARD_PADDINGS, CARD_SURFACES, CARD_VARIANTS, CELL_BREAKPOINTS, CONTROL_SIZES, CONTROL_SURFACES, DELTA_EMPHASES, DELTA_SIZES, DELTA_WEIGHTS, DIALOG_CLOSE_REASONS, DIALOG_LAYOUTS, DIALOG_WIDTHS, FACT_SIZES, FACT_SUB_TONES, GROUP_SIZES, METER_SIZES, METER_TONES, ROW_GAPS, ROW_PADDINGS, ROW_SIZES, SEGMENTED_SIZES, SKELETON_SHAPES, STALE_LINK_SIZES, STAT_SIZES, SUB_TONES, TABLE_ROWS, UI_AMOUNT_MASKED, UiActionBar, UiAlert, UiAmount, UiAsync, UiAvatar, UiAvatarLink, UiBackLink, UiBadge, UiButton, UiCard, UiCellSub, UiChoiceChips, UiDelta, UiDialog, UiDonut, UiEmpty, UiExternalLink, UiFact, UiFacts, UiField, UiFieldLeading, UiFieldTrailing, UiFilterChips, UiFlipItem, UiFlipList, UiGroup, UiGroupCell, UiHighlight, UiInput, UiLineChart, UiListRow, UiMenu, UiMenuItem, UiMenuTrigger, UiMeter, UiNavItem, UiRow, UiRowAction, UiRowItem, UiRowLink, UiRowTile, UiSegmented, UiSelect, UiSkeleton, UiStaleLink, UiStat, UiSwitch, UiTab, UiTabBar, UiTable, UiTd, UiTextarea, UiTh, UiToaster, UiToasts, UiTr, delayedState, formatAmount, holdTransitionsUntilRendered, injectReducedMotion };
5197
5466
  //# sourceMappingURL=joanroucoux-cairn-ui.mjs.map