@joanroucoux/cairn-ui 0.6.4 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,14 +1,16 @@
1
+ import { DOCUMENT, NgTemplateOutlet } from '@angular/common';
1
2
  import * as i0 from '@angular/core';
2
- import { Component, input, computed, InjectionToken, signal, inject, LOCALE_ID, booleanAttribute, output, Directive, ElementRef, DestroyRef, forwardRef, model, viewChildren, afterNextRender, afterRenderEffect, contentChild, viewChild, effect, numberAttribute, contentChildren } from '@angular/core';
3
- import { NgTemplateOutlet } from '@angular/common';
3
+ import { inject, ElementRef, DestroyRef, afterNextRender, Component, input, booleanAttribute, computed, InjectionToken, signal, LOCALE_ID, effect, untracked, output, Directive, forwardRef, model, viewChildren, afterRenderEffect, contentChild, viewChild, numberAttribute, contentChildren, Injectable } from '@angular/core';
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
8
  /**
8
9
  * Bar of actions fixed above the tab bar on iPhone: the primary actions of a detail screen, within
9
10
  * thumb reach. It sits 52px plus the safe area above the bottom edge, so it clears `ui-tab-bar`, and
10
11
  * it is hidden from `64rem`, where the same actions live in the page itself. Its children share the
11
- * width equally.
12
+ * width equally. While it is on the page it publishes its height as `--action-bar-height` on the
13
+ * root element, which `ui-toaster` adds to its offset so a confirmation never covers the actions.
12
14
  *
13
15
  * @example
14
16
  * <ui-action-bar>
@@ -17,6 +19,21 @@ import { curveMonotoneX, line } from 'd3-shape';
17
19
  * </ui-action-bar>
18
20
  */
19
21
  class UiActionBar {
22
+ constructor() {
23
+ const host = inject(ElementRef).nativeElement;
24
+ const root = inject(DOCUMENT).documentElement;
25
+ const destroyRef = inject(DestroyRef);
26
+ const publish = () => root.style.setProperty(HEIGHT_PROPERTY, `${host.offsetHeight}px`);
27
+ afterNextRender(() => {
28
+ publish();
29
+ if (typeof ResizeObserver !== 'undefined') {
30
+ const observer = new ResizeObserver(publish);
31
+ observer.observe(host);
32
+ destroyRef.onDestroy(() => observer.disconnect());
33
+ }
34
+ });
35
+ destroyRef.onDestroy(() => root.style.removeProperty(HEIGHT_PROPERTY));
36
+ }
20
37
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiActionBar, deps: [], target: i0.ɵɵFactoryTarget.Component });
21
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 });
22
39
  }
@@ -29,16 +46,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
29
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',
30
47
  },
31
48
  }]
32
- }] });
49
+ }], ctorParameters: () => [] });
33
50
 
34
51
  const ALERT_VARIANTS = ['error', 'warning'];
35
- const BASE_CLASSES$8 = 'flex items-start gap-2.5 text-left text-label';
52
+ const BASE_CLASSES$9 = 'flex items-start gap-2.5 text-left text-label';
36
53
  const VARIANT_CLASSES$3 = {
37
54
  error: 'rounded-control bg-(--card) px-3.5 py-3 shadow-[inset_0_0_0_1px_var(--border)]',
38
55
  warning: 'text-(--foreground)',
39
56
  };
40
57
  /**
41
58
  * Inline message explaining a failure or a consequence: an icon, an optional heading and a text.
59
+ * It fades in when it appears; set `[fadeIn]="false"` on an alert that is part of the page as it
60
+ * opens (an error state after a load), which shows without motion.
42
61
  *
43
62
  * @example
44
63
  * <ui-alert heading="Sign-in failed">Try again.</ui-alert>
@@ -48,10 +67,11 @@ class UiAlert {
48
67
  ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
49
68
  heading = input(/* @ts-ignore */
50
69
  ...(ngDevMode ? [undefined, { debugName: "heading" }] : /* istanbul ignore next */ []));
51
- classes = computed(() => `${BASE_CLASSES$8} ${VARIANT_CLASSES$3[this.variant()]}`, /* @ts-ignore */
70
+ fadeIn = input(true, { ...(ngDevMode ? { debugName: "fadeIn" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
71
+ classes = computed(() => `${BASE_CLASSES$9} ${VARIANT_CLASSES$3[this.variant()]}${this.fadeIn() ? ' ui-enter-fade' : ''}`, /* @ts-ignore */
52
72
  ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
53
73
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiAlert, deps: [], target: i0.ɵɵFactoryTarget.Component });
54
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: UiAlert, isStandalone: true, selector: "ui-alert", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "classes()", "attr.role": "variant() === 'warning' ? 'status' : 'alert'" } }, ngImport: i0, template: `
74
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: UiAlert, isStandalone: true, selector: "ui-alert", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired: false, transformFunction: null }, fadeIn: { classPropertyName: "fadeIn", publicName: "fadeIn", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "animate.leave": "ui-leave-fade" }, properties: { "class": "classes()", "attr.role": "variant() === 'warning' ? 'status' : 'alert'" } }, ngImport: i0, template: `
55
75
  @if (variant() === 'warning') {
56
76
  <svg
57
77
  aria-hidden="true"
@@ -141,11 +161,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
141
161
  </div>
142
162
  `,
143
163
  host: {
164
+ 'animate.leave': 'ui-leave-fade',
144
165
  '[class]': 'classes()',
145
166
  '[attr.role]': "variant() === 'warning' ? 'status' : 'alert'",
146
167
  },
147
168
  }]
148
- }], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], heading: [{ type: i0.Input, args: [{ isSignal: true, alias: "heading", required: false }] }] } });
169
+ }], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], heading: [{ type: i0.Input, args: [{ isSignal: true, alias: "heading", required: false }] }], fadeIn: [{ type: i0.Input, args: [{ isSignal: true, alias: "fadeIn", required: false }] }] } });
149
170
 
150
171
  const MINUS = '−';
151
172
  const EM_DASH = '—';
@@ -252,13 +273,66 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
252
273
  }]
253
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 }] }] } });
254
275
 
276
+ const SKELETON_DELAY_MS = 150;
277
+ const SKELETON_MIN_MS = 400;
278
+ /**
279
+ * Turns the live state of a call into the state to draw: `loading` only appears once the call has
280
+ * lasted 150 ms and, once shown, stays at least 400 ms, so a fast call never flashes a skeleton and a
281
+ * slow one never blinks. Until then the previous state stays on screen (a reload keeps its content);
282
+ * `null` means nothing to draw yet, for a first load still under 150 ms. Call it in an injection context.
283
+ *
284
+ * @example
285
+ * protected readonly shown = delayedState(this.state);
286
+ */
287
+ function delayedState(source) {
288
+ const displayed = signal(null, /* @ts-ignore */
289
+ ...(ngDevMode ? [{ debugName: "displayed" }] : /* istanbul ignore next */ []));
290
+ let timer;
291
+ let shownAt = 0;
292
+ const clear = () => {
293
+ clearTimeout(timer);
294
+ timer = undefined;
295
+ };
296
+ inject(DestroyRef).onDestroy(clear);
297
+ effect(() => {
298
+ const state = source();
299
+ untracked(() => {
300
+ if (state === 'loading') {
301
+ if (displayed() === 'loading') {
302
+ clear();
303
+ }
304
+ else {
305
+ timer = setTimeout(() => {
306
+ timer = undefined;
307
+ shownAt = Date.now();
308
+ displayed.set('loading');
309
+ }, SKELETON_DELAY_MS);
310
+ }
311
+ return;
312
+ }
313
+ clear();
314
+ const remaining = shownAt + SKELETON_MIN_MS - Date.now();
315
+ if (displayed() === 'loading' && remaining > 0) {
316
+ timer = setTimeout(() => {
317
+ timer = undefined;
318
+ displayed.set(untracked(source));
319
+ }, remaining);
320
+ }
321
+ else {
322
+ displayed.set(state);
323
+ }
324
+ });
325
+ });
326
+ return displayed.asReadonly();
327
+ }
328
+
255
329
  /** Available async block states. `AsyncState` is derived from this tuple. */
256
330
  const ASYNC_STATES = ['loading', 'error', 'empty', 'ready'];
257
331
  /** Looks of the error block. `AsyncVariant` is derived from this tuple. */
258
332
  const ASYNC_VARIANTS = ['elevated', 'plain', 'emphasis', 'inline'];
259
333
  /** Alignments of the error block; `auto` is centred from 64rem. `AsyncAlign` is derived from this tuple. */
260
334
  const ASYNC_ALIGNS = ['start', 'center', 'auto'];
261
- const RETRY_BASE_CLASSES = 'relative inline-flex items-center justify-center rounded-control font-medium whitespace-nowrap cursor-pointer select-none touch-manipulation transition-[transform,background-color,opacity] duration-(--duration-press) ease-out active:scale-(--press-scale) focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--ring) text-label';
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';
262
336
  const RETRY_OUTLINE_CLASSES = 'bg-(--card) text-(--foreground) shadow-[inset_0_0_0_1px_var(--border)] hover:bg-(--glow)';
263
337
  const RETRY_PRIMARY_CLASSES = 'bg-(--primary) text-(--primary-foreground) hover:bg-(--primary-to)';
264
338
  const RETRY_BLOCK_CLASSES = 'min-h-(--row-min) gap-2 pl-3 pr-4';
@@ -334,7 +408,8 @@ class UiAsync {
334
408
  card = input(false, { ...(ngDevMode ? { debugName: "card" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
335
409
  fill = input(false, { ...(ngDevMode ? { debugName: "fill" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
336
410
  retry = output();
337
- filling = computed(() => this.fill() && this.state() === 'error', /* @ts-ignore */
411
+ shown = delayedState(this.state);
412
+ filling = computed(() => this.fill() && this.shown() === 'error', /* @ts-ignore */
338
413
  ...(ngDevMode ? [{ debugName: "filling" }] : /* istanbul ignore next */ []));
339
414
  iconSize = computed(() => (this.variant() === 'inline' ? 16 : 18), /* @ts-ignore */
340
415
  ...(ngDevMode ? [{ debugName: "iconSize" }] : /* istanbul ignore next */ []));
@@ -353,9 +428,9 @@ class UiAsync {
353
428
  return `flex flex-col ${surface} ${spec.gap} ${ALIGN_CLASSES[this.align()]} ${spec.padding[this.align()]}${fill}`;
354
429
  }, /* @ts-ignore */
355
430
  ...(ngDevMode ? [{ debugName: "boxClasses" }] : /* istanbul ignore next */ []));
356
- titleClasses = computed(() => this.#spec()?.title ?? '', /* @ts-ignore */
431
+ titleClasses = computed(() => this.#spec().title, /* @ts-ignore */
357
432
  ...(ngDevMode ? [{ debugName: "titleClasses" }] : /* istanbul ignore next */ []));
358
- messageClasses = computed(() => this.#spec()?.message[this.align()] ?? '', /* @ts-ignore */
433
+ messageClasses = computed(() => this.#spec().message[this.align()], /* @ts-ignore */
359
434
  ...(ngDevMode ? [{ debugName: "messageClasses" }] : /* istanbul ignore next */ []));
360
435
  retryButtonClasses = computed(() => {
361
436
  const spec = this.#spec();
@@ -367,7 +442,8 @@ class UiAsync {
367
442
  ...(ngDevMode ? [{ debugName: "retryButtonClasses" }] : /* istanbul ignore next */ []));
368
443
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiAsync, deps: [], target: i0.ɵɵFactoryTarget.Component });
369
444
  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: `
370
- @switch (state()) {
445
+ @switch (shown()) {
446
+ @case (null) {}
371
447
  @case ('loading') {
372
448
  <ng-content select="[asyncLoading]" />
373
449
  }
@@ -415,7 +491,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
415
491
  args: [{
416
492
  selector: 'ui-async',
417
493
  template: `
418
- @switch (state()) {
494
+ @switch (shown()) {
495
+ @case (null) {}
419
496
  @case ('loading') {
420
497
  <ng-content select="[asyncLoading]" />
421
498
  }
@@ -467,7 +544,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
467
544
 
468
545
  /** Available avatar sizes. `AvatarSize` is derived from this tuple. */
469
546
  const AVATAR_SIZES = ['sm', 'md', 'lg', 'auto'];
470
- const BASE_CLASSES$7 = 'inline-flex items-center justify-center rounded-pill bg-(--muted) font-medium tabular-nums';
547
+ const BASE_CLASSES$8 = 'inline-flex items-center justify-center rounded-pill bg-(--muted) font-medium tabular-nums';
471
548
  const SIZE_CLASSES$7 = {
472
549
  sm: 'size-8 text-caption',
473
550
  md: 'size-[34px] text-label',
@@ -487,7 +564,7 @@ class UiAvatar {
487
564
  ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
488
565
  size = input('md', /* @ts-ignore */
489
566
  ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
490
- classes = computed(() => `${BASE_CLASSES$7} ${SIZE_CLASSES$7[this.size()]}`, /* @ts-ignore */
567
+ classes = computed(() => `${BASE_CLASSES$8} ${SIZE_CLASSES$7[this.size()]}`, /* @ts-ignore */
491
568
  ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
492
569
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiAvatar, deps: [], target: i0.ɵɵFactoryTarget.Component });
493
570
  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 });
@@ -514,21 +591,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
514
591
  */
515
592
  class UiAvatarLink {
516
593
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiAvatarLink, deps: [], target: i0.ɵɵFactoryTarget.Directive });
517
- 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-[transform,background-color] duration-(--duration-press) 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 });
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 });
518
595
  }
519
596
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiAvatarLink, decorators: [{
520
597
  type: Directive,
521
598
  args: [{
522
599
  selector: 'a[uiAvatarLink], button[uiAvatarLink]',
523
600
  host: {
524
- class: 'rounded-pill grid size-11 lg:size-10 place-items-center cursor-pointer select-none touch-manipulation transition-[transform,background-color] duration-(--duration-press) 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)',
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)',
525
602
  },
526
603
  }]
527
604
  }] });
528
605
 
529
606
  /** Available back link sizes. `BackLinkSize` is derived from this tuple. */
530
607
  const BACK_LINK_SIZES = ['md', 'sm', 'header'];
531
- const BASE_CLASSES$6 = 'inline-flex items-center gap-0.5 rounded-control text-body font-medium whitespace-nowrap cursor-pointer select-none touch-manipulation transition-[transform,background-color,color] duration-(--duration-press) ease-out active:scale-(--press-scale) focus-visible:outline-2 focus-visible:outline-(--ring)';
608
+ 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)';
532
609
  const SIZE_CLASSES$6 = {
533
610
  md: 'h-11 pr-3 pl-1 text-(--foreground) focus-visible:outline-offset-2',
534
611
  sm: 'h-9 pr-2 pl-0.5 text-(--muted-foreground) hover:bg-(--glow) hover:text-(--foreground) focus-visible:outline-offset-2',
@@ -546,7 +623,7 @@ class UiBackLink {
546
623
  ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
547
624
  chevron = computed(() => CHEVRON[this.size()], /* @ts-ignore */
548
625
  ...(ngDevMode ? [{ debugName: "chevron" }] : /* istanbul ignore next */ []));
549
- classes = computed(() => `${BASE_CLASSES$6} ${SIZE_CLASSES$6[this.size()]}`, /* @ts-ignore */
626
+ classes = computed(() => `${BASE_CLASSES$7} ${SIZE_CLASSES$6[this.size()]}`, /* @ts-ignore */
550
627
  ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
551
628
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiBackLink, deps: [], target: i0.ɵɵFactoryTarget.Component });
552
629
  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: `
@@ -595,7 +672,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
595
672
  /** Available badge variants. `BadgeVariant` is derived from this tuple. */
596
673
  const BADGE_VARIANTS = ['neutral', 'outline'];
597
674
  const BADGE_SIZES = ['md', 'sm'];
598
- const BASE_CLASSES$5 = 'inline-flex items-center rounded-pill text-caption font-medium';
675
+ const BASE_CLASSES$6 = 'inline-flex items-center rounded-pill text-caption font-medium';
599
676
  const SIZE_CLASSES$5 = {
600
677
  md: 'h-6 px-2.5',
601
678
  sm: 'h-[22px] px-2',
@@ -615,7 +692,7 @@ class UiBadge {
615
692
  ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
616
693
  size = input('md', /* @ts-ignore */
617
694
  ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
618
- classes = computed(() => `${BASE_CLASSES$5} ${SIZE_CLASSES$5[this.size()]} ${VARIANT_CLASSES$2[this.variant()]}`, /* @ts-ignore */
695
+ classes = computed(() => `${BASE_CLASSES$6} ${SIZE_CLASSES$5[this.size()]} ${VARIANT_CLASSES$2[this.variant()]}`, /* @ts-ignore */
619
696
  ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
620
697
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiBadge, deps: [], target: i0.ɵɵFactoryTarget.Component });
621
698
  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 });
@@ -658,7 +735,7 @@ const BUTTON_SIZES = [
658
735
  'icon',
659
736
  'icon-sm',
660
737
  ];
661
- const BASE_CLASSES$4 = 'relative inline-flex items-center justify-center rounded-control font-medium whitespace-nowrap cursor-pointer select-none touch-manipulation transition-[transform,background-color,opacity] duration-(--duration-press) 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';
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';
662
739
  const RING_OUTSIDE = 'focus-visible:outline-offset-2';
663
740
  const RING_INSIDE = 'focus-visible:-outline-offset-2';
664
741
  const OUTLINE = 'bg-(--card) shadow-[inset_0_0_0_1px_var(--border)] hover:bg-(--glow) active:bg-(--soft)';
@@ -678,10 +755,10 @@ const SIZE_CLASSES$4 = {
678
755
  slim: 'h-11 lg:h-8 gap-1.5 px-2.5 text-label',
679
756
  xs: 'h-8 gap-1.5 px-2.5 text-label',
680
757
  sm: 'h-9 gap-2 px-3 text-label',
681
- md: 'min-h-(--row-min) gap-2 px-4 text-label has-[>svg:first-child]:pl-3',
758
+ md: 'min-h-(--row-min) gap-2 px-4 text-label has-[>span>svg:first-child]:pl-3',
682
759
  lg: 'h-11 gap-2 px-[18px] text-label',
683
760
  xl: 'h-[50px] gap-2 px-6 text-body',
684
- xxl: 'h-[52px] lg:h-11 gap-2.5 px-6 lg:px-5 text-body lg:has-[>svg:first-child]:pl-4',
761
+ xxl: 'h-[52px] lg:h-11 gap-2.5 px-6 lg:px-5 text-body lg:has-[>span>svg:first-child]:pl-4',
685
762
  tall: 'h-12 lg:h-10 gap-2 px-4 text-body',
686
763
  block: 'h-[50px] lg:h-11 gap-2 px-4 text-body',
687
764
  icon: 'size-(--row-min) gap-2 p-0',
@@ -703,7 +780,7 @@ class UiButton {
703
780
  busy = input(false, { ...(ngDevMode ? { debugName: "busy" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
704
781
  inactive = computed(() => this.loading() || this.busy(), /* @ts-ignore */
705
782
  ...(ngDevMode ? [{ debugName: "inactive" }] : /* istanbul ignore next */ []));
706
- classes = computed(() => `${BASE_CLASSES$4} ${VARIANT_CLASSES$1[this.variant()]} ${SIZE_CLASSES$4[this.size()]}${this.inactive() ? ' pointer-events-none opacity-70' : ''}`, /* @ts-ignore */
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 */
707
784
  ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
708
785
  constructor() {
709
786
  const host = inject(ElementRef).nativeElement;
@@ -721,10 +798,10 @@ class UiButton {
721
798
  @if (loading()) {
722
799
  <span
723
800
  aria-hidden="true"
724
- class="rounded-pill size-4 border-2 border-current border-r-transparent motion-safe:animate-spin"
801
+ class="rounded-pill animate-cairn-spin absolute inset-0 m-auto size-4 border-2 border-current border-r-transparent"
725
802
  ></span>
726
803
  }
727
- <ng-content />
804
+ <span class="contents" [class.text-transparent]="loading()"><ng-content /></span>
728
805
  `, isInline: true });
729
806
  }
730
807
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiButton, decorators: [{
@@ -735,10 +812,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
735
812
  @if (loading()) {
736
813
  <span
737
814
  aria-hidden="true"
738
- class="rounded-pill size-4 border-2 border-current border-r-transparent motion-safe:animate-spin"
815
+ class="rounded-pill animate-cairn-spin absolute inset-0 m-auto size-4 border-2 border-current border-r-transparent"
739
816
  ></span>
740
817
  }
741
- <ng-content />
818
+ <span class="contents" [class.text-transparent]="loading()"><ng-content /></span>
742
819
  `,
743
820
  host: {
744
821
  '[class]': 'classes()',
@@ -756,7 +833,7 @@ const CARD_PADDINGS = ['none', 'xs', 'sm', 'md', 'list', 'rows', 'recap', 'panel
756
833
  const CARD_SURFACES = ['always', 'lg', 'max-lg'];
757
834
  /** Border of the card surface; `auto` follows the variant. `CardBorder` is derived from this tuple. */
758
835
  const CARD_BORDERS = ['auto', 'none', 'border', 'hairline'];
759
- const BASE_CLASSES$3 = 'block';
836
+ const BASE_CLASSES$4 = 'block';
760
837
  const VARIANT_CLASSES = {
761
838
  default: {
762
839
  always: 'rounded-container bg-(--card) text-(--card-foreground)',
@@ -861,7 +938,7 @@ class UiCard {
861
938
  const surface = this.surface();
862
939
  const border = this.border();
863
940
  return [
864
- BASE_CLASSES$3,
941
+ BASE_CLASSES$4,
865
942
  VARIANT_CLASSES[variant][surface],
866
943
  BORDER_CLASSES[border === 'auto' ? AUTO_BORDERS[variant] : border][surface],
867
944
  PADDING_CLASSES$1[this.padding()][surface],
@@ -897,7 +974,7 @@ const UI_CONTROL = new InjectionToken('UI_CONTROL');
897
974
 
898
975
  /** Available control sizes. `md` is the 44/40px touch target; `lg` and `xl` are the 48px sheet and sign-in fields. */
899
976
  const CONTROL_SIZES = ['sm', 'md', 'lg', 'xl'];
900
- const CONTROL_BASE_CLASSES = 'w-full rounded-control px-3 text-body text-(--foreground) shadow-[inset_0_0_0_1px_var(--border)] transition-shadow duration-(--duration-fast) ease-out placeholder:text-(--muted-foreground) focus-visible:shadow-[inset_0_0_0_2px_var(--ring)] aria-invalid:shadow-[inset_0_0_0_2px_var(--negative)] disabled:cursor-not-allowed disabled:opacity-50';
977
+ const CONTROL_BASE_CLASSES = 'w-full rounded-control px-3 text-body text-(--foreground) shadow-[inset_0_0_0_1px_var(--border)] placeholder:text-(--muted-foreground) focus-visible:shadow-[inset_0_0_0_2px_var(--ring)] aria-invalid:shadow-[inset_0_0_0_2px_var(--negative)] disabled:cursor-not-allowed disabled:opacity-50';
901
978
  const CONTROL_SURFACES = ['background', 'card'];
902
979
  const CONTROL_SURFACE_CLASSES = {
903
980
  background: 'bg-(--background)',
@@ -962,7 +1039,7 @@ class UiTextarea {
962
1039
  touched = input(false, /* @ts-ignore */
963
1040
  ...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
964
1041
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiTextarea, deps: [], target: i0.ɵɵFactoryTarget.Directive });
965
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.7", type: UiTextarea, isStandalone: true, selector: "textarea[uiTextarea]", inputs: { errors: { classPropertyName: "errors", publicName: "errors", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "w-full rounded-control px-3 text-body text-(--foreground) shadow-[inset_0_0_0_1px_var(--border)] transition-shadow duration-(--duration-fast) ease-out placeholder:text-(--muted-foreground) focus-visible:shadow-[inset_0_0_0_2px_var(--ring)] aria-invalid:shadow-[inset_0_0_0_2px_var(--negative)] disabled:cursor-not-allowed disabled:opacity-50 bg-(--background) min-h-24 py-2" }, providers: [{ provide: UI_CONTROL, useExisting: forwardRef(() => UiTextarea) }], ngImport: i0 });
1042
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.7", type: UiTextarea, isStandalone: true, selector: "textarea[uiTextarea]", inputs: { errors: { classPropertyName: "errors", publicName: "errors", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "w-full rounded-control px-3 text-body text-(--foreground) shadow-[inset_0_0_0_1px_var(--border)] placeholder:text-(--muted-foreground) focus-visible:shadow-[inset_0_0_0_2px_var(--ring)] aria-invalid:shadow-[inset_0_0_0_2px_var(--negative)] disabled:cursor-not-allowed disabled:opacity-50 bg-(--background) min-h-24 py-2" }, providers: [{ provide: UI_CONTROL, useExisting: forwardRef(() => UiTextarea) }], ngImport: i0 });
966
1043
  }
967
1044
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiTextarea, decorators: [{
968
1045
  type: Directive,
@@ -1142,7 +1219,7 @@ const DELTA_EMPHASES = ['text', 'pill'];
1142
1219
  const DELTA_SIZES = ['label', 'inherit'];
1143
1220
  /** Font weights. `DeltaWeight` is derived from this tuple. */
1144
1221
  const DELTA_WEIGHTS = ['medium', 'regular'];
1145
- const BASE_CLASSES$2 = 'inline-flex items-center gap-1 tabular-nums';
1222
+ const BASE_CLASSES$3 = 'inline-flex items-center gap-1 tabular-nums';
1146
1223
  const SIZE_CLASSES$3 = {
1147
1224
  label: 'text-label',
1148
1225
  inherit: '',
@@ -1199,7 +1276,7 @@ class UiDelta {
1199
1276
  classes = computed(() => {
1200
1277
  const sign = this.sign();
1201
1278
  const background = this.emphasis() === 'pill' ? `${PILL_CLASSES$1} ${PILL_BACKGROUND_CLASS}` : 'bg-transparent';
1202
- return `${BASE_CLASSES$2} ${SIZE_CLASSES$3[this.size()]} ${WEIGHT_CLASSES[this.weight()]} ${TEXT_CLASSES[sign]} ${background}`;
1279
+ return `${BASE_CLASSES$3} ${SIZE_CLASSES$3[this.size()]} ${WEIGHT_CLASSES[this.weight()]} ${TEXT_CLASSES[sign]} ${background}`;
1203
1280
  }, /* @ts-ignore */
1204
1281
  ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
1205
1282
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiDelta, deps: [], target: i0.ɵɵFactoryTarget.Component });
@@ -1230,6 +1307,242 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
1230
1307
  }]
1231
1308
  }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], unknownLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "unknownLabel", required: true }] }], emphasis: [{ type: i0.Input, args: [{ isSignal: true, alias: "emphasis", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], weight: [{ type: i0.Input, args: [{ isSignal: true, alias: "weight", required: false }] }] } });
1232
1309
 
1310
+ const GRACE = 50;
1311
+ function afterExit(element, done) {
1312
+ const style = getComputedStyle(element);
1313
+ const wait = longest(style.transitionDuration) + longest(style.transitionDelay);
1314
+ if (wait === 0) {
1315
+ done();
1316
+ return () => undefined;
1317
+ }
1318
+ const onEnd = (event) => {
1319
+ if (event.target === element && !event.pseudoElement) {
1320
+ cancel();
1321
+ done();
1322
+ }
1323
+ };
1324
+ const timer = setTimeout(() => {
1325
+ cancel();
1326
+ done();
1327
+ }, wait + GRACE);
1328
+ const cancel = () => {
1329
+ clearTimeout(timer);
1330
+ element.removeEventListener('transitionend', onEnd);
1331
+ };
1332
+ element.addEventListener('transitionend', onEnd);
1333
+ return cancel;
1334
+ }
1335
+ function longest(list) {
1336
+ return Math.max(0, ...list.split(',').map(toMs));
1337
+ }
1338
+ function toMs(value) {
1339
+ const amount = Number.parseFloat(value);
1340
+ if (Number.isNaN(amount)) {
1341
+ return 0;
1342
+ }
1343
+ return value.trim().endsWith('ms') ? amount : amount * 1000;
1344
+ }
1345
+
1346
+ const DIALOG_STYLES = `
1347
+ dialog {
1348
+ --dialog-ease: var(--ease-sheet);
1349
+ transition:
1350
+ transform var(--duration-base) var(--dialog-ease),
1351
+ overlay var(--duration-base) allow-discrete,
1352
+ display var(--duration-base) allow-discrete;
1353
+ transform: translateY(var(--drag-y, 0px));
1354
+ }
1355
+
1356
+ dialog:not([open]) {
1357
+ transform: translateY(100%);
1358
+ transition-duration: var(--duration-exit);
1359
+ }
1360
+
1361
+ @starting-style {
1362
+ dialog[open] {
1363
+ transform: translateY(100%);
1364
+ }
1365
+ }
1366
+
1367
+ dialog[data-dragging] {
1368
+ transition: none;
1369
+ }
1370
+
1371
+ @media (prefers-reduced-motion: reduce) {
1372
+ dialog {
1373
+ transition:
1374
+ opacity var(--duration-base) var(--dialog-ease),
1375
+ overlay var(--duration-base) allow-discrete,
1376
+ display var(--duration-base) allow-discrete;
1377
+ opacity: 1;
1378
+ }
1379
+
1380
+ dialog:not([open]) {
1381
+ opacity: 0;
1382
+ transform: translateY(var(--drag-y, 0px));
1383
+ }
1384
+
1385
+ @starting-style {
1386
+ dialog[open] {
1387
+ opacity: 0;
1388
+ transform: translateY(0);
1389
+ }
1390
+ }
1391
+ }
1392
+
1393
+ @media (min-width: 64rem) {
1394
+ dialog {
1395
+ --dialog-ease: var(--ease-out);
1396
+ transition:
1397
+ opacity var(--duration-base) var(--dialog-ease),
1398
+ transform var(--duration-base) var(--dialog-ease),
1399
+ overlay var(--duration-base) allow-discrete,
1400
+ display var(--duration-base) allow-discrete;
1401
+ opacity: 1;
1402
+ transform: scale(1);
1403
+ }
1404
+
1405
+ dialog:not([open]) {
1406
+ opacity: 0;
1407
+ transform: scale(var(--enter-scale));
1408
+ }
1409
+
1410
+ @starting-style {
1411
+ dialog[open] {
1412
+ opacity: 0;
1413
+ transform: scale(var(--enter-scale));
1414
+ }
1415
+ }
1416
+ }
1417
+
1418
+ dialog::backdrop {
1419
+ transition: opacity var(--duration-base) var(--ease-out);
1420
+ opacity: 1;
1421
+ }
1422
+
1423
+ dialog:not([open])::backdrop {
1424
+ opacity: 0;
1425
+ transition-duration: var(--duration-exit);
1426
+ }
1427
+
1428
+ @starting-style {
1429
+ dialog[open]::backdrop {
1430
+ opacity: 0;
1431
+ }
1432
+ }
1433
+ `;
1434
+
1435
+ const DISMISS_RATIO = 0.3;
1436
+ const DISMISS_SPEED = 0.5;
1437
+ const SPEED_WINDOW = 100;
1438
+ const SLOP = 6;
1439
+ const CONTROLS = 'button, a, input, select, textarea, [role="button"]';
1440
+ class SheetDrag {
1441
+ #panel;
1442
+ #grips;
1443
+ #dismiss;
1444
+ #gesture = null;
1445
+ constructor(panel, grips, dismiss) {
1446
+ this.#panel = panel;
1447
+ this.#grips = grips;
1448
+ this.#dismiss = dismiss;
1449
+ for (const grip of grips) {
1450
+ grip.addEventListener('pointerdown', this.#onDown);
1451
+ grip.addEventListener('pointermove', this.#onMove);
1452
+ grip.addEventListener('pointerup', this.#onUp);
1453
+ grip.addEventListener('pointercancel', this.#onCancel);
1454
+ }
1455
+ }
1456
+ reset() {
1457
+ this.#gesture = null;
1458
+ this.#panel.removeAttribute('data-dragging');
1459
+ this.#panel.style.removeProperty('--drag-y');
1460
+ }
1461
+ destroy() {
1462
+ for (const grip of this.#grips) {
1463
+ grip.removeEventListener('pointerdown', this.#onDown);
1464
+ grip.removeEventListener('pointermove', this.#onMove);
1465
+ grip.removeEventListener('pointerup', this.#onUp);
1466
+ grip.removeEventListener('pointercancel', this.#onCancel);
1467
+ }
1468
+ }
1469
+ #onDown = (event) => {
1470
+ const grip = event.currentTarget;
1471
+ if (event.button !== 0 || !isSheet() || event.target.closest(CONTROLS)) {
1472
+ return;
1473
+ }
1474
+ grip.setPointerCapture(event.pointerId);
1475
+ this.#gesture = {
1476
+ id: event.pointerId,
1477
+ startX: event.clientX,
1478
+ startY: event.clientY,
1479
+ dragging: false,
1480
+ height: 0,
1481
+ offset: 0,
1482
+ samples: [{ y: event.clientY, at: event.timeStamp }],
1483
+ };
1484
+ };
1485
+ #onMove = (event) => {
1486
+ const gesture = this.#current(event);
1487
+ if (!gesture) {
1488
+ return;
1489
+ }
1490
+ const dx = event.clientX - gesture.startX;
1491
+ const dy = event.clientY - gesture.startY;
1492
+ if (!gesture.dragging) {
1493
+ if (Math.max(Math.abs(dx), Math.abs(dy)) < SLOP) {
1494
+ return;
1495
+ }
1496
+ if (Math.abs(dx) > Math.abs(dy)) {
1497
+ this.#gesture = null;
1498
+ return;
1499
+ }
1500
+ gesture.dragging = true;
1501
+ gesture.height = this.#panel.getBoundingClientRect().height;
1502
+ this.#panel.setAttribute('data-dragging', '');
1503
+ }
1504
+ gesture.offset = Math.max(0, dy);
1505
+ gesture.samples.push({ y: event.clientY, at: event.timeStamp });
1506
+ this.#panel.style.setProperty('--drag-y', `${gesture.offset}px`);
1507
+ };
1508
+ #onUp = (event) => {
1509
+ const gesture = this.#current(event);
1510
+ if (!gesture) {
1511
+ return;
1512
+ }
1513
+ this.#gesture = null;
1514
+ if (!gesture.dragging) {
1515
+ return;
1516
+ }
1517
+ this.#panel.removeAttribute('data-dragging');
1518
+ const release = { y: event.clientY, at: event.timeStamp };
1519
+ if (gesture.offset > gesture.height * DISMISS_RATIO || releaseSpeed(gesture.samples, release) > DISMISS_SPEED) {
1520
+ this.#dismiss();
1521
+ }
1522
+ else {
1523
+ this.reset();
1524
+ }
1525
+ };
1526
+ #onCancel = (event) => {
1527
+ if (this.#current(event)) {
1528
+ this.#gesture = null;
1529
+ this.#panel.removeAttribute('data-dragging');
1530
+ this.reset();
1531
+ }
1532
+ };
1533
+ #current(event) {
1534
+ return this.#gesture?.id === event.pointerId ? this.#gesture : null;
1535
+ }
1536
+ }
1537
+ function isSheet() {
1538
+ return typeof matchMedia === 'function' && !matchMedia('(min-width: 64rem)').matches;
1539
+ }
1540
+ function releaseSpeed(samples, last) {
1541
+ const first = samples.find((sample) => sample.at >= last.at - SPEED_WINDOW) ?? last;
1542
+ const elapsed = last.at - first.at;
1543
+ return elapsed > 0 ? (last.y - first.y) / elapsed : 0;
1544
+ }
1545
+
1233
1546
  /** Named dialog widths; any other CSS length is accepted as is. `DialogWidth` is derived from this tuple. */
1234
1547
  const DIALOG_WIDTHS = ['md', 'lg'];
1235
1548
  const WIDTH_VALUES = {
@@ -1280,6 +1593,8 @@ const LAYOUTS = {
1280
1593
  footer: 'max-lg:pb-[calc(8px+env(safe-area-inset-bottom))] lg:pb-6',
1281
1594
  },
1282
1595
  };
1596
+ /** What started a close, as `closed` reports it. `DialogCloseReason` is derived from this tuple. */
1597
+ const DIALOG_CLOSE_REASONS = ['escape', 'backdrop', 'cross', 'drag', 'programmatic'];
1283
1598
  const nextId$3 = (() => {
1284
1599
  let count = 0;
1285
1600
  return () => `ui-dialog-${++count}`;
@@ -1308,6 +1623,7 @@ class UiDialog {
1308
1623
  ...(ngDevMode ? [{ debugName: "width" }] : /* istanbul ignore next */ []));
1309
1624
  open = input(false, { ...(ngDevMode ? { debugName: "open" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1310
1625
  dismissed = output();
1626
+ closed = output();
1311
1627
  headingId = nextId$3();
1312
1628
  descriptionId = `${this.headingId}-description`;
1313
1629
  bodyId = `${this.headingId}-body`;
@@ -1327,11 +1643,11 @@ class UiDialog {
1327
1643
  ...(ngDevMode ? [{ debugName: "widthValue" }] : /* istanbul ignore next */ []));
1328
1644
  spec = computed(() => LAYOUTS[this.layout()], /* @ts-ignore */
1329
1645
  ...(ngDevMode ? [{ debugName: "spec" }] : /* istanbul ignore next */ []));
1330
- headerClasses = computed(() => `flex gap-2 pl-4 lg:gap-3 lg:pl-6 ${this.spec().header} ${this.closeLabel() ? 'pr-2 lg:pr-4' : 'pr-4 lg:pr-6'}`, /* @ts-ignore */
1646
+ headerClasses = computed(() => `flex gap-2 pl-4 max-lg:touch-none lg:gap-3 lg:pl-6 ${this.spec().header} ${this.closeLabel() ? 'pr-2 lg:pr-4' : 'pr-4 lg:pr-6'}`, /* @ts-ignore */
1331
1647
  ...(ngDevMode ? [{ debugName: "headerClasses" }] : /* istanbul ignore next */ []));
1332
1648
  titleClasses = computed(() => this.spec().title, /* @ts-ignore */
1333
1649
  ...(ngDevMode ? [{ debugName: "titleClasses" }] : /* istanbul ignore next */ []));
1334
- crossClasses = computed(() => `rounded-pill lg:rounded-control grid size-11 flex-none cursor-pointer place-items-center text-(--muted-foreground) transition-[transform,background-color,color] duration-(--duration-press) 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 */
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 */
1335
1651
  ...(ngDevMode ? [{ debugName: "crossClasses" }] : /* istanbul ignore next */ []));
1336
1652
  iconClasses = computed(() => this.spec().icon, /* @ts-ignore */
1337
1653
  ...(ngDevMode ? [{ debugName: "iconClasses" }] : /* istanbul ignore next */ []));
@@ -1343,6 +1659,13 @@ class UiDialog {
1343
1659
  constructor() {
1344
1660
  const destroyRef = inject(DestroyRef);
1345
1661
  afterNextRender(() => {
1662
+ const host = this.#host.nativeElement;
1663
+ const grips = ['[data-dialog-handle]', '[data-dialog-header]'].map((grip) => host.querySelector(grip));
1664
+ this.#drag = new SheetDrag(this.#dialog, grips, () => this.close('drag'));
1665
+ destroyRef.onDestroy(() => {
1666
+ this.#drag?.destroy();
1667
+ this.#cancelExit();
1668
+ });
1346
1669
  if (typeof ResizeObserver === 'undefined') {
1347
1670
  return;
1348
1671
  }
@@ -1356,20 +1679,38 @@ class UiDialog {
1356
1679
  // viewChild.required can't target a #private field (NG1053); safe because the template has exactly one <dialog>.
1357
1680
  const dialog = this.#host.nativeElement.querySelector('dialog');
1358
1681
  if (this.open() && !dialog.open) {
1682
+ this.#cancelExit();
1683
+ this.#drag?.reset();
1359
1684
  dialog.showModal();
1360
1685
  }
1361
1686
  else if (!this.open() && dialog.open) {
1362
- dialog.close();
1687
+ this.close('programmatic');
1363
1688
  }
1364
1689
  });
1365
1690
  }
1366
1691
  #pressedOnBackdrop = false;
1692
+ #drag = null;
1693
+ #reason = null;
1694
+ #cancelExit = () => undefined;
1695
+ close(reason) {
1696
+ if (this.#dialog.open) {
1697
+ this.#reason = reason;
1698
+ this.#dialog.close();
1699
+ }
1700
+ }
1701
+ onCancel(event) {
1702
+ if (event.target !== event.currentTarget) {
1703
+ return;
1704
+ }
1705
+ event.preventDefault();
1706
+ this.close('escape');
1707
+ }
1367
1708
  onPointerDown(event) {
1368
1709
  this.#pressedOnBackdrop = event.target === this.#dialog && this.#outside(event);
1369
1710
  }
1370
1711
  onClick(event) {
1371
1712
  if (this.#pressedOnBackdrop && event.target === this.#dialog && this.#outside(event)) {
1372
- this.#dialog.close();
1713
+ this.close('backdrop');
1373
1714
  }
1374
1715
  this.#pressedOnBackdrop = false;
1375
1716
  }
@@ -1381,13 +1722,26 @@ class UiDialog {
1381
1722
  return (event.clientX < rect.left || event.clientX > rect.right || event.clientY < rect.top || event.clientY > rect.bottom);
1382
1723
  }
1383
1724
  onNativeClose() {
1384
- // The owner already knows about a close it asked for; only a close it did not ask for is news.
1385
- if (this.open()) {
1725
+ const reason = this.#reason ?? 'escape';
1726
+ this.#reason = null;
1727
+ if (this.#dialog.open) {
1728
+ return;
1729
+ }
1730
+ if (reason !== 'programmatic') {
1386
1731
  this.dismissed.emit();
1387
1732
  }
1733
+ this.#awaitExit(reason);
1734
+ }
1735
+ #awaitExit(reason) {
1736
+ this.#cancelExit();
1737
+ this.#cancelExit = afterExit(this.#dialog, () => {
1738
+ this.#cancelExit = () => undefined;
1739
+ this.#drag?.reset();
1740
+ this.closed.emit(reason);
1741
+ });
1388
1742
  }
1389
1743
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
1390
- 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" }, host: { listeners: { "pointerdown": "onPointerDown($event)", "click": "onClick($event)" } }, ngImport: i0, template: `
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: `
1391
1745
  <dialog
1392
1746
  #dlg
1393
1747
  [attr.aria-describedby]="describedBy()"
@@ -1395,9 +1749,15 @@ class UiDialog {
1395
1749
  [attr.role]="layout() === 'confirm' ? 'alertdialog' : 'dialog'"
1396
1750
  [class]="classes()"
1397
1751
  [style.--dialog-width]="widthValue()"
1752
+ (cancel)="onCancel($event)"
1398
1753
  (close)="onNativeClose()"
1399
1754
  >
1400
- <div aria-hidden="true" class="flex h-5 justify-center lg:hidden" data-dialog-handle [class]="spec().handle">
1755
+ <div
1756
+ aria-hidden="true"
1757
+ class="flex h-5 touch-none justify-center lg:hidden"
1758
+ data-dialog-handle
1759
+ [class]="spec().handle"
1760
+ >
1401
1761
  <span class="rounded-pill h-[5px] w-9 bg-(--border)"></span>
1402
1762
  </div>
1403
1763
 
@@ -1417,7 +1777,7 @@ class UiDialog {
1417
1777
  </div>
1418
1778
 
1419
1779
  @if (closeLabel()) {
1420
- <button type="button" [attr.aria-label]="closeLabel()" [class]="crossClasses()" (click)="dlg.close()">
1780
+ <button type="button" [attr.aria-label]="closeLabel()" [class]="crossClasses()" (click)="close('cross')">
1421
1781
  <svg
1422
1782
  aria-hidden="true"
1423
1783
  class="block size-[22px] fill-none stroke-current stroke-[1.75]"
@@ -1451,7 +1811,7 @@ class UiDialog {
1451
1811
  <ng-content select="[dialogActions]" />
1452
1812
  </div>
1453
1813
  </dialog>
1454
- `, isInline: true, styles: ["dialog{--dialog-ease: var(--ease-sheet);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:translateY(0)}dialog:not([open]){opacity:0;transform:translateY(100%);transition-duration:var(--duration-exit)}@starting-style{dialog[open]{opacity:0;transform:translateY(100%)}}@media(min-width:64rem){dialog{--dialog-ease: var(--ease-out);transform:scale(1)}dialog:not([open]){transform:scale(var(--enter-scale))}@starting-style{dialog[open]{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"] });
1814
+ `, isInline: true, 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"] });
1455
1815
  }
1456
1816
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiDialog, decorators: [{
1457
1817
  type: Component,
@@ -1463,9 +1823,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
1463
1823
  [attr.role]="layout() === 'confirm' ? 'alertdialog' : 'dialog'"
1464
1824
  [class]="classes()"
1465
1825
  [style.--dialog-width]="widthValue()"
1826
+ (cancel)="onCancel($event)"
1466
1827
  (close)="onNativeClose()"
1467
1828
  >
1468
- <div aria-hidden="true" class="flex h-5 justify-center lg:hidden" data-dialog-handle [class]="spec().handle">
1829
+ <div
1830
+ aria-hidden="true"
1831
+ class="flex h-5 touch-none justify-center lg:hidden"
1832
+ data-dialog-handle
1833
+ [class]="spec().handle"
1834
+ >
1469
1835
  <span class="rounded-pill h-[5px] w-9 bg-(--border)"></span>
1470
1836
  </div>
1471
1837
 
@@ -1485,7 +1851,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
1485
1851
  </div>
1486
1852
 
1487
1853
  @if (closeLabel()) {
1488
- <button type="button" [attr.aria-label]="closeLabel()" [class]="crossClasses()" (click)="dlg.close()">
1854
+ <button type="button" [attr.aria-label]="closeLabel()" [class]="crossClasses()" (click)="close('cross')">
1489
1855
  <svg
1490
1856
  aria-hidden="true"
1491
1857
  class="block size-[22px] fill-none stroke-current stroke-[1.75]"
@@ -1519,12 +1885,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
1519
1885
  <ng-content select="[dialogActions]" />
1520
1886
  </div>
1521
1887
  </dialog>
1522
- `, styles: ["dialog{--dialog-ease: var(--ease-sheet);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:translateY(0)}dialog:not([open]){opacity:0;transform:translateY(100%);transition-duration:var(--duration-exit)}@starting-style{dialog[open]{opacity:0;transform:translateY(100%)}}@media(min-width:64rem){dialog{--dialog-ease: var(--ease-out);transform:scale(1)}dialog:not([open]){transform:scale(var(--enter-scale))}@starting-style{dialog[open]{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"] }]
1523
- }], 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"] }] } });
1888
+ `, 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"] }] } });
1524
1890
 
1525
1891
  const RADIUS = 78;
1526
1892
  const RING_WIDTH = 30;
1527
- const ACTIVE_RING_WIDTH = 38;
1528
1893
  const CIRCUMFERENCE = 2 * Math.PI * RADIUS;
1529
1894
  const GAP = 2;
1530
1895
  function positionSlices(slices) {
@@ -1571,7 +1936,8 @@ function rankSlices(slices, othersLabel) {
1571
1936
 
1572
1937
  const identityValue$1 = (value) => `${value}`;
1573
1938
  const identityShare = (share) => `${Math.round(share * 100)}%`;
1574
- const ROW_CLASSES$1 = 'flex w-full items-center gap-3 min-h-14 px-2 py-1 [font-variant-numeric:var(--numeric)] rounded-control text-left transition-colors duration-(--duration-fast) ease-out hover:bg-(--glow) active:bg-(--soft) focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-(--ring)';
1939
+ const ROW_CLASSES$1 = 'flex w-full items-center gap-3 min-h-14 px-2 py-1 [font-variant-numeric:var(--numeric)] rounded-control text-left transition-[scale,background-color] [transition-duration:var(--duration-press),var(--duration-fast)] ease-out hover:bg-(--glow) active:bg-(--soft) active:scale-(--press-scale) focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-(--ring)';
1940
+ const SLICE_CLASSES = 'cursor-pointer origin-center [transform-box:view-box] transition-[scale,opacity] duration-(--duration-fast) ease-out';
1575
1941
  const RAMP_DOT_CLASSES = {
1576
1942
  1: 'bg-(--ramp-1)',
1577
1943
  2: 'bg-(--ramp-2)',
@@ -1625,8 +1991,12 @@ class UiDonut {
1625
1991
  strokeColor(slice) {
1626
1992
  return `var(--ramp-${slice.ramp})`;
1627
1993
  }
1628
- strokeWidth(slice) {
1629
- return this.isActive(slice) ? ACTIVE_RING_WIDTH : RING_WIDTH;
1994
+ sliceClasses(slice) {
1995
+ const id = this.activeId();
1996
+ if (id === null) {
1997
+ return SLICE_CLASSES;
1998
+ }
1999
+ return `${SLICE_CLASSES} ${id === slice.id ? 'scale-104 motion-reduce:scale-100' : 'opacity-50'}`;
1630
2000
  }
1631
2001
  dotClasses(slice) {
1632
2002
  return `size-3 flex-none rounded-pill shadow-[inset_0_0_0_1px_var(--border)] ${RAMP_DOT_CLASSES[slice.ramp]}`;
@@ -1676,7 +2046,6 @@ class UiDonut {
1676
2046
  />
1677
2047
  @for (slice of positioned(); track slice.id) {
1678
2048
  <circle
1679
- class="cursor-pointer transition-[stroke-width] duration-(--duration-fast) ease-out"
1680
2049
  cx="100"
1681
2050
  cy="100"
1682
2051
  data-slice
@@ -1684,8 +2053,9 @@ class UiDonut {
1684
2053
  [attr.r]="radius"
1685
2054
  [attr.stroke-dasharray]="slice.dash"
1686
2055
  [attr.stroke-dashoffset]="slice.offset"
2056
+ [attr.stroke-width]="ringWidth"
2057
+ [class]="sliceClasses(slice)"
1687
2058
  [style.stroke]="strokeColor(slice)"
1688
- [style.stroke-width.px]="strokeWidth(slice)"
1689
2059
  (click)="select(slice)"
1690
2060
  (pointerenter)="highlight(slice.id)"
1691
2061
  (pointerleave)="clearHighlight(slice.id)"
@@ -1771,7 +2141,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
1771
2141
  />
1772
2142
  @for (slice of positioned(); track slice.id) {
1773
2143
  <circle
1774
- class="cursor-pointer transition-[stroke-width] duration-(--duration-fast) ease-out"
1775
2144
  cx="100"
1776
2145
  cy="100"
1777
2146
  data-slice
@@ -1779,8 +2148,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
1779
2148
  [attr.r]="radius"
1780
2149
  [attr.stroke-dasharray]="slice.dash"
1781
2150
  [attr.stroke-dashoffset]="slice.offset"
2151
+ [attr.stroke-width]="ringWidth"
2152
+ [class]="sliceClasses(slice)"
1782
2153
  [style.stroke]="strokeColor(slice)"
1783
- [style.stroke-width.px]="strokeWidth(slice)"
1784
2154
  (click)="select(slice)"
1785
2155
  (pointerenter)="highlight(slice.id)"
1786
2156
  (pointerleave)="clearHighlight(slice.id)"
@@ -1855,6 +2225,44 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
1855
2225
  }]
1856
2226
  }], propDecorators: { slices: [{ type: i0.Input, args: [{ isSignal: true, alias: "slices", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], othersLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "othersLabel", required: true }] }], valueFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueFormat", required: false }] }], shareFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "shareFormat", required: false }] }], legendHref: [{ type: i0.Input, args: [{ isSignal: true, alias: "legendHref", required: false }] }], sliceSelect: [{ type: i0.Output, args: ["sliceSelect"] }] } });
1857
2227
 
2228
+ /**
2229
+ * Centred empty state: a title, a hint and an action slot, such as a catalogue search that matches
2230
+ * nothing. The padding is 24px, 48px from 64rem.
2231
+ *
2232
+ * @example
2233
+ * <ui-empty heading="Aucun instrument ne correspond à « zzz »" hint="La recherche porte sur le nom et l'ISIN.">
2234
+ * <button ui-button variant="outline" size="md" type="button">Ajouter une ligne</button>
2235
+ * </ui-empty>
2236
+ */
2237
+ class UiEmpty {
2238
+ heading = input.required(/* @ts-ignore */
2239
+ ...(ngDevMode ? [{ debugName: "heading" }] : /* istanbul ignore next */ []));
2240
+ hint = input('', /* @ts-ignore */
2241
+ ...(ngDevMode ? [{ debugName: "hint" }] : /* istanbul ignore next */ []));
2242
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiEmpty, deps: [], target: i0.ɵɵFactoryTarget.Component });
2243
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: UiEmpty, isStandalone: true, selector: "ui-empty", inputs: { heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired: true, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "flex flex-col items-center gap-1.5 px-2 py-6 text-center lg:py-12" }, ngImport: i0, template: `
2244
+ <span class="text-body font-medium text-pretty">{{ heading() }}</span>
2245
+ @if (hint()) {
2246
+ <span class="text-label text-(--muted-foreground)">{{ hint() }}</span>
2247
+ }
2248
+ <div class="mt-2 empty:hidden"><ng-content /></div>
2249
+ `, isInline: true });
2250
+ }
2251
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiEmpty, decorators: [{
2252
+ type: Component,
2253
+ args: [{
2254
+ selector: 'ui-empty',
2255
+ template: `
2256
+ <span class="text-body font-medium text-pretty">{{ heading() }}</span>
2257
+ @if (hint()) {
2258
+ <span class="text-label text-(--muted-foreground)">{{ hint() }}</span>
2259
+ }
2260
+ <div class="mt-2 empty:hidden"><ng-content /></div>
2261
+ `,
2262
+ host: { class: 'flex flex-col items-center gap-1.5 px-2 py-6 text-center lg:py-12' },
2263
+ }]
2264
+ }], propDecorators: { heading: [{ type: i0.Input, args: [{ isSignal: true, alias: "heading", required: true }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }] } });
2265
+
1858
2266
  /**
1859
2267
  * Link to another site: the label followed by an external-link icon. A 44px target on touch,
1860
2268
  * an underline on hover with a mouse.
@@ -2045,14 +2453,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
2045
2453
  */
2046
2454
  class UiFieldTrailing {
2047
2455
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiFieldTrailing, deps: [], target: i0.ɵɵFactoryTarget.Directive });
2048
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.7", type: UiFieldTrailing, isStandalone: true, selector: "[uiFieldTrailing]", host: { classAttribute: "absolute right-0 grid h-full aspect-square place-items-center rounded-control cursor-pointer text-(--muted-foreground) hover:text-(--foreground) focus-visible:outline-2 focus-visible:-outline-offset-4 focus-visible:outline-(--ring)" }, ngImport: i0 });
2456
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.7", type: UiFieldTrailing, isStandalone: true, selector: "[uiFieldTrailing]", host: { classAttribute: "absolute right-0 grid h-full aspect-square place-items-center rounded-control cursor-pointer text-(--muted-foreground) transition-[color] duration-(--duration-fast) ease-out hover:text-(--foreground) focus-visible:outline-2 focus-visible:-outline-offset-4 focus-visible:outline-(--ring)" }, ngImport: i0 });
2049
2457
  }
2050
2458
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiFieldTrailing, decorators: [{
2051
2459
  type: Directive,
2052
2460
  args: [{
2053
2461
  selector: '[uiFieldTrailing]',
2054
2462
  host: {
2055
- class: 'absolute right-0 grid h-full aspect-square place-items-center rounded-control cursor-pointer text-(--muted-foreground) hover:text-(--foreground) focus-visible:outline-2 focus-visible:-outline-offset-4 focus-visible:outline-(--ring)',
2463
+ class: 'absolute right-0 grid h-full aspect-square place-items-center rounded-control cursor-pointer text-(--muted-foreground) transition-[color] duration-(--duration-fast) ease-out hover:text-(--foreground) focus-visible:outline-2 focus-visible:-outline-offset-4 focus-visible:outline-(--ring)',
2056
2464
  },
2057
2465
  }]
2058
2466
  }] });
@@ -2179,7 +2587,9 @@ class UiField {
2179
2587
  }
2180
2588
 
2181
2589
  @if (message()) {
2182
- <p class="text-label font-medium text-(--negative)" role="alert" [id]="errorId">{{ message() }}</p>
2590
+ <p animate.enter="ui-enter-fade" class="text-label font-medium text-(--negative)" role="alert" [id]="errorId">
2591
+ {{ message() }}
2592
+ </p>
2183
2593
  }
2184
2594
  </div>
2185
2595
  `, isInline: true });
@@ -2212,7 +2622,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
2212
2622
  }
2213
2623
 
2214
2624
  @if (message()) {
2215
- <p class="text-label font-medium text-(--negative)" role="alert" [id]="errorId">{{ message() }}</p>
2625
+ <p animate.enter="ui-enter-fade" class="text-label font-medium text-(--negative)" role="alert" [id]="errorId">
2626
+ {{ message() }}
2627
+ </p>
2216
2628
  }
2217
2629
  </div>
2218
2630
  `,
@@ -2299,6 +2711,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
2299
2711
  }]
2300
2712
  }], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }, { type: i0.Output, args: ["valueChange"] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: true }] }] } });
2301
2713
 
2714
+ const QUERY = '(prefers-reduced-motion: reduce)';
2715
+ /**
2716
+ * A signal that follows the user's `prefers-reduced-motion` preference live.
2717
+ * Call it in an injection context; the listener is removed with that context.
2718
+ *
2719
+ * @example
2720
+ * protected readonly reduced = injectReducedMotion();
2721
+ */
2722
+ function injectReducedMotion() {
2723
+ if (typeof matchMedia === 'undefined') {
2724
+ return signal(false).asReadonly();
2725
+ }
2726
+ const query = matchMedia(QUERY);
2727
+ const reduced = signal(query.matches, /* @ts-ignore */
2728
+ ...(ngDevMode ? [{ debugName: "reduced" }] : /* istanbul ignore next */ []));
2729
+ const listener = (event) => reduced.set(event.matches);
2730
+ query.addEventListener('change', listener);
2731
+ inject(DestroyRef).onDestroy(() => query.removeEventListener('change', listener));
2732
+ return reduced.asReadonly();
2733
+ }
2734
+
2302
2735
  const axisTicks = (points, maxTicks = 5) => {
2303
2736
  if (points.length <= maxTicks) {
2304
2737
  return [...points];
@@ -2318,6 +2751,33 @@ const isCoreTick = (index, count) => {
2318
2751
  const middle = Math.floor((count - 1) / 2);
2319
2752
  return index === 0 || index === count - 1 || index === middle;
2320
2753
  };
2754
+ const firstOfEachRun = (labels, included) => {
2755
+ let previous = null;
2756
+ return labels.map((text, index) => {
2757
+ if (!included(index)) {
2758
+ return false;
2759
+ }
2760
+ const distinct = text !== previous;
2761
+ previous = text;
2762
+ return distinct;
2763
+ });
2764
+ };
2765
+ const tickLabels = (ticks, format, mode) => {
2766
+ const labels = ticks.map((tick) => format(tick.t));
2767
+ const count = labels.length;
2768
+ const core = (index) => isCoreTick(index, count);
2769
+ const everyTick = firstOfEachRun(labels, () => true);
2770
+ const coreTicks = firstOfEachRun(labels, core);
2771
+ return labels.map((text, index) => {
2772
+ const wide = mode === 3 ? coreTicks[index] : everyTick[index];
2773
+ const narrow = coreTicks[index];
2774
+ if (!wide && !narrow) {
2775
+ return null;
2776
+ }
2777
+ const classes = mode === 'auto' ? `${narrow ? '' : 'max-sm:hidden'} ${wide ? '' : 'sm:hidden'}`.trim() : wide ? '' : 'hidden';
2778
+ return { text, classes };
2779
+ });
2780
+ };
2321
2781
 
2322
2782
  const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
2323
2783
  const nearestPointIndex = (points, x) => {
@@ -2414,6 +2874,23 @@ const buildGeometry = (points, width, height, padding = {}) => {
2414
2874
  };
2415
2875
  };
2416
2876
 
2877
+ const observeSize = (element, destroyRef, measured) => {
2878
+ if (typeof ResizeObserver === 'undefined') {
2879
+ return;
2880
+ }
2881
+ const { clientWidth: width, clientHeight: height } = element;
2882
+ if (width > 0 && height > 0) {
2883
+ measured({ width, height });
2884
+ }
2885
+ const observer = new ResizeObserver(([entry]) => {
2886
+ if (entry) {
2887
+ measured({ width: entry.contentRect.width, height: entry.contentRect.height });
2888
+ }
2889
+ });
2890
+ observer.observe(element);
2891
+ destroyRef.onDestroy(() => observer.disconnect());
2892
+ };
2893
+
2417
2894
  const LINE_GAP = 6;
2418
2895
  const COLLISION_MARGIN = 6;
2419
2896
  const EDGE_TOLERANCE = 3;
@@ -2462,7 +2939,15 @@ const placeStartLabel = (points, startY, box, label) => {
2462
2939
  const TRANSITION_SAMPLES = 120;
2463
2940
  const TRANSITION_DURATION = 260;
2464
2941
  const easeOutQuint = (t) => 1 - (1 - t) ** 5;
2465
- const prefersReducedMotion = () => typeof matchMedia !== 'undefined' && matchMedia('(prefers-reduced-motion: reduce)').matches;
2942
+ const durationFrom = (value) => {
2943
+ const match = /^\s*([\d.]+)(ms|s)\s*$/.exec(value);
2944
+ const duration = match ? Number(match[1]) * (match[2] === 's' ? 1000 : 1) : Number.NaN;
2945
+ return Number.isFinite(duration) ? duration : TRANSITION_DURATION;
2946
+ };
2947
+ const sameSeries = (a, b) => a === b ||
2948
+ (b !== null &&
2949
+ a.length === b.length &&
2950
+ a.every((point, index) => point.t === b[index].t && point.v === b[index].v));
2466
2951
  const monotoneTangents = (xs, ys) => {
2467
2952
  const n = xs.length;
2468
2953
  const slopes = [];
@@ -2518,15 +3003,85 @@ const sampleMonotone = (points, sampleXs) => {
2518
3003
  return (2 * f3 - 3 * f2 + 1) * y0 + (f3 - 2 * f2 + f) * m0 * dx + (-2 * f3 + 3 * f2) * y1 + (f3 - f2) * m1 * dx;
2519
3004
  });
2520
3005
  };
2521
- const buildPolyline = (xs, ys) => xs.map((x, index) => `${index === 0 ? 'M' : 'L'}${x.toFixed(2)},${ys[index].toFixed(2)}`).join(' ');
2522
- const interpolateLine = (from, to, progress) => {
2523
- const left = to.points[0].x;
2524
- const right = to.points.at(-1).x;
2525
- const samples = Array.from({ length: TRANSITION_SAMPLES }, (_, index) => left + ((right - left) * index) / (TRANSITION_SAMPLES - 1));
2526
- const fromYs = sampleMonotone(from.points, samples);
2527
- const toYs = sampleMonotone(to.points, samples);
2528
- return buildPolyline(samples, fromYs.map((fromY, index) => fromY + (toYs[index] - fromY) * progress));
3006
+ const polyline = (xs, ys) => xs.map((x, index) => `${index === 0 ? 'M' : 'L'}${x.toFixed(2)},${ys[index].toFixed(2)}`).join(' ');
3007
+ const shapeTransition = (from, to) => {
3008
+ const left = to[0].x;
3009
+ const right = to.at(-1).x;
3010
+ const xs = Array.from({ length: TRANSITION_SAMPLES }, (_, index) => left + ((right - left) * index) / (TRANSITION_SAMPLES - 1));
3011
+ return { xs, from: sampleMonotone(from, xs), to: sampleMonotone(to, xs) };
2529
3012
  };
3013
+ const shapeAt = (transition, progress) => transition.from.map((fromY, index) => fromY + (transition.to[index] - fromY) * progress);
3014
+ const animateShape = (from, to, duration, draw, done) => {
3015
+ const transition = shapeTransition(from, to);
3016
+ const start = performance.now();
3017
+ let frame = 0;
3018
+ const step = (now) => {
3019
+ const t = Math.min(1, Math.max(0, (now - start) / duration));
3020
+ if (t === 1) {
3021
+ done();
3022
+ return;
3023
+ }
3024
+ const ys = shapeAt(transition, easeOutQuint(t));
3025
+ draw(transition.xs.map((x, index) => ({ x, y: ys[index] })), polyline(transition.xs, ys));
3026
+ frame = requestAnimationFrame(step);
3027
+ };
3028
+ frame = requestAnimationFrame(step);
3029
+ return () => cancelAnimationFrame(frame);
3030
+ };
3031
+ class ShapeMotion {
3032
+ #view;
3033
+ #points = null;
3034
+ #target = null;
3035
+ #held = null;
3036
+ #drawn = null;
3037
+ #stop = null;
3038
+ constructor(view) {
3039
+ this.#view = view;
3040
+ }
3041
+ follow(points, geometry, options) {
3042
+ const newSeries = !sameSeries(points, this.#points);
3043
+ this.#points = points;
3044
+ if (this.#stop && !newSeries && !options.reduced && geometry?.line === this.#target?.line) {
3045
+ this.#target = geometry;
3046
+ return;
3047
+ }
3048
+ const from = this.#drawn;
3049
+ const held = this.#held ?? this.#target;
3050
+ this.#target = geometry;
3051
+ this.stop();
3052
+ if (!newSeries || !geometry || !from || from.length < 2 || geometry.points.length < 2 || options.reduced) {
3053
+ this.#settle(geometry);
3054
+ return;
3055
+ }
3056
+ const duration = options.duration();
3057
+ if (duration <= 0) {
3058
+ this.#settle(geometry);
3059
+ return;
3060
+ }
3061
+ this.#hold(held);
3062
+ this.#stop = animateShape(from, geometry.points, duration, (shape, line) => this.#draw(shape, line, shape.at(-1)), () => this.#settle(geometry));
3063
+ }
3064
+ stop() {
3065
+ this.#stop?.();
3066
+ this.#stop = null;
3067
+ }
3068
+ #hold(geometry) {
3069
+ this.#held = geometry;
3070
+ this.#view.hold(geometry);
3071
+ }
3072
+ #settle(geometry) {
3073
+ this.#stop = null;
3074
+ this.#hold(null);
3075
+ this.#drawn = null;
3076
+ if (geometry) {
3077
+ this.#draw(geometry.points, geometry.line, geometry.end);
3078
+ }
3079
+ }
3080
+ #draw(shape, line, end) {
3081
+ this.#drawn = shape;
3082
+ this.#view.draw(line, end);
3083
+ }
3084
+ }
2530
3085
 
2531
3086
  const WIDE_QUERY = '(min-width: 64rem)';
2532
3087
  const wideViewport = () => {
@@ -2544,6 +3099,11 @@ const wideViewport = () => {
2544
3099
  /**
2545
3100
  * Value over time: a monotone curve with a dashed line at the starting value and a tooltip.
2546
3101
  *
3102
+ * Axis labels are never repeated: a tick whose `axisFormat` text equals the previous drawn label is
3103
+ * not rendered. The first tick always is, so only the last edge label can drop. With `axisTicks="3"`
3104
+ * only the first, middle and last ticks count; with `auto` every tick counts from `sm` up and the
3105
+ * three core ticks below it. Tick positions are unchanged.
3106
+ *
2547
3107
  * @example
2548
3108
  * <ui-line-chart [points]="points" label="Net worth over one month" startLabel="Since" [valueFormat]="formatEur" />
2549
3109
  */
@@ -2576,17 +3136,30 @@ class UiLineChart {
2576
3136
  ...(ngDevMode ? [{ debugName: "axisGap" }] : /* istanbul ignore next */ []));
2577
3137
  axisTicks = input('auto', { ...(ngDevMode ? { debugName: "axisTicks" } : /* istanbul ignore next */ {}), transform: parseAxisTicks });
2578
3138
  #destroyRef = inject(DestroyRef);
3139
+ #host = inject(ElementRef);
2579
3140
  #wide = wideViewport();
2580
- #frame = null;
2581
- #hasRenderedOnce = false;
2582
- #previousGeometry = null;
2583
- isCoreTick = isCoreTick;
3141
+ #reducedMotion = injectReducedMotion();
3142
+ #motion = new ShapeMotion({
3143
+ hold: (geometry) => {
3144
+ this.held.set(geometry);
3145
+ this.activeIndex.update((index) => (geometry ? null : index));
3146
+ },
3147
+ draw: (line, end) => {
3148
+ this.lineRef()?.nativeElement.setAttribute('d', line);
3149
+ this.endRef()?.nativeElement.setAttribute('cx', String(end.x));
3150
+ this.endRef()?.nativeElement.setAttribute('cy', String(end.y));
3151
+ },
3152
+ });
2584
3153
  svgRef = viewChild.required('svgRef', /* @ts-ignore */
2585
3154
  ...(ngDevMode ? [{ debugName: "svgRef" }] : /* istanbul ignore next */ []));
3155
+ lineRef = viewChild('lineRef', /* @ts-ignore */
3156
+ ...(ngDevMode ? [{ debugName: "lineRef" }] : /* istanbul ignore next */ []));
3157
+ endRef = viewChild('endRef', /* @ts-ignore */
3158
+ ...(ngDevMode ? [{ debugName: "endRef" }] : /* istanbul ignore next */ []));
2586
3159
  activeIndex = signal(null, /* @ts-ignore */
2587
3160
  ...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
2588
- animatedLine = signal(null, /* @ts-ignore */
2589
- ...(ngDevMode ? [{ debugName: "animatedLine" }] : /* istanbul ignore next */ []));
3161
+ held = signal(null, /* @ts-ignore */
3162
+ ...(ngDevMode ? [{ debugName: "held" }] : /* istanbul ignore next */ []));
2590
3163
  measuredSize = signal(null, /* @ts-ignore */
2591
3164
  ...(ngDevMode ? [{ debugName: "measuredSize" }] : /* istanbul ignore next */ []));
2592
3165
  effectiveWidth = computed(() => this.measuredSize()?.width ?? DEFAULT_WIDTH, /* @ts-ignore */
@@ -2604,19 +3177,23 @@ class UiLineChart {
2604
3177
  return buildGeometry(this.points(), this.effectiveWidth(), plotHeight, { x: 0, top: padding, bottom: padding });
2605
3178
  }, /* @ts-ignore */
2606
3179
  ...(ngDevMode ? [{ debugName: "geometry" }] : /* istanbul ignore next */ []));
2607
- displayLine = computed(() => this.animatedLine() ?? this.geometry().line, /* @ts-ignore */
2608
- ...(ngDevMode ? [{ debugName: "displayLine" }] : /* istanbul ignore next */ []));
3180
+ shown = computed(() => this.held() ?? this.geometry(), /* @ts-ignore */
3181
+ ...(ngDevMode ? [{ debugName: "shown" }] : /* istanbul ignore next */ []));
3182
+ startFade = computed(() => this.held() ? 'opacity-0 duration-(--duration-exit)' : 'duration-(--duration-fast)', /* @ts-ignore */
3183
+ ...(ngDevMode ? [{ debugName: "startFade" }] : /* istanbul ignore next */ []));
2609
3184
  ticks = computed(() => axisTicks(this.geometry().points), /* @ts-ignore */
2610
3185
  ...(ngDevMode ? [{ debugName: "ticks" }] : /* istanbul ignore next */ []));
3186
+ tickTexts = computed(() => tickLabels(this.ticks(), this.axisFormat(), this.axisTicks()), /* @ts-ignore */
3187
+ ...(ngDevMode ? [{ debugName: "tickTexts" }] : /* istanbul ignore next */ []));
2611
3188
  active = computed(() => {
2612
3189
  const geometry = this.geometry();
2613
3190
  const index = this.activeIndex();
2614
- return geometry && index !== null ? geometry.points[index] : null;
3191
+ return geometry && index !== null && !this.held() ? geometry.points[index] : null;
2615
3192
  }, /* @ts-ignore */
2616
3193
  ...(ngDevMode ? [{ debugName: "active" }] : /* istanbul ignore next */ []));
2617
3194
  startLabelInfo = computed(() => {
2618
3195
  const text = this.startLabel();
2619
- const geometry = this.geometry();
3196
+ const geometry = this.shown();
2620
3197
  if (!text || !geometry) {
2621
3198
  return null;
2622
3199
  }
@@ -2626,63 +3203,21 @@ class UiLineChart {
2626
3203
  return { label, placement: { ...placement, y: startLabelBaseline(placement.y) } };
2627
3204
  }, /* @ts-ignore */
2628
3205
  ...(ngDevMode ? [{ debugName: "startLabelInfo" }] : /* istanbul ignore next */ []));
2629
- #renderRef = afterNextRender(() => this.#observeSize());
3206
+ #renderRef = afterNextRender(() => {
3207
+ this.#destroyRef.onDestroy(() => this.#renderRef.destroy());
3208
+ observeSize(this.svgRef().nativeElement, this.#destroyRef, (size) => this.measuredSize.set(size));
3209
+ });
2630
3210
  constructor() {
2631
3211
  effect(() => {
3212
+ const points = this.points();
2632
3213
  const geometry = this.geometry();
2633
- const previous = this.#previousGeometry;
2634
- if (previous === geometry) {
2635
- return;
2636
- }
2637
- this.#previousGeometry = geometry;
2638
- this.#cancelAnimation();
2639
- if (!this.#hasRenderedOnce) {
2640
- this.#hasRenderedOnce = true;
2641
- return;
2642
- }
2643
- if (!previous || !geometry) {
2644
- this.animatedLine.set(null);
2645
- return;
2646
- }
2647
- if (prefersReducedMotion()) {
2648
- this.animatedLine.set(null);
2649
- return;
2650
- }
2651
- this.#animate(previous, geometry);
2652
- });
2653
- this.#destroyRef.onDestroy(() => this.#cancelAnimation());
2654
- }
2655
- #observeSize() {
2656
- this.#destroyRef.onDestroy(() => this.#renderRef.destroy());
2657
- if (typeof ResizeObserver === 'undefined') {
2658
- return;
2659
- }
2660
- const element = this.svgRef().nativeElement;
2661
- const observer = new ResizeObserver(([entry]) => {
2662
- if (entry) {
2663
- const { width, height } = entry.contentRect;
2664
- this.measuredSize.set({ width, height });
2665
- }
3214
+ const reduced = this.#reducedMotion();
3215
+ untracked(() => this.#motion.follow(points, geometry, {
3216
+ reduced,
3217
+ duration: () => durationFrom(getComputedStyle(this.#host.nativeElement).getPropertyValue('--duration-base')),
3218
+ }));
2666
3219
  });
2667
- observer.observe(element);
2668
- this.#destroyRef.onDestroy(() => observer.disconnect());
2669
- }
2670
- #cancelAnimation() {
2671
- cancelAnimationFrame(this.#frame ?? 0);
2672
- this.#frame = null;
2673
- }
2674
- #animate(from, to) {
2675
- if (from.points.length < 2 || to.points.length < 2) {
2676
- this.animatedLine.set(null);
2677
- return;
2678
- }
2679
- const startTime = performance.now();
2680
- const step = (now) => {
2681
- const t = Math.min(1, (now - startTime) / TRANSITION_DURATION);
2682
- this.animatedLine.set(t < 1 ? interpolateLine(from, to, easeOutQuint(t)) : null);
2683
- this.#frame = t < 1 ? requestAnimationFrame(step) : null;
2684
- };
2685
- this.#frame = requestAnimationFrame(step);
3220
+ this.#destroyRef.onDestroy(() => this.#motion.stop());
2686
3221
  }
2687
3222
  deltaFor(point) {
2688
3223
  return point.v - this.points()[0].v;
@@ -2718,7 +3253,7 @@ class UiLineChart {
2718
3253
  this.activeIndex.set(navigation.index);
2719
3254
  }
2720
3255
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiLineChart, deps: [], target: i0.ɵɵFactoryTarget.Component });
2721
- 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 }], ngImport: i0, template: `
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: `
2722
3257
  <div class="relative h-full">
2723
3258
  <svg
2724
3259
  #svgRef
@@ -2733,69 +3268,67 @@ class UiLineChart {
2733
3268
  (pointerleave)="onPointerLeave()"
2734
3269
  (pointermove)="onPointerMove($event)"
2735
3270
  >
2736
- @if (geometry(); as geometry) {
3271
+ @if (shown(); as shape) {
2737
3272
  <path
3273
+ #lineRef
2738
3274
  class="stroke-(--foreground)"
2739
3275
  data-chart-line
2740
3276
  fill="none"
2741
3277
  stroke-linecap="round"
2742
3278
  stroke-linejoin="round"
2743
- [attr.d]="displayLine()"
3279
+ [attr.d]="shape.line"
2744
3280
  [attr.stroke-width]="sparkline() ? 1.5 : 2"
2745
3281
  />
2746
3282
 
2747
3283
  @if (!sparkline()) {
2748
3284
  <line
2749
- class="stroke-(--border)"
3285
+ class="stroke-(--border) transition-opacity ease-(--ease-out)"
2750
3286
  data-chart-start-line
2751
3287
  stroke-dasharray="3 4"
2752
3288
  stroke-width="1"
2753
3289
  x1="0"
2754
3290
  [attr.x2]="effectiveWidth()"
2755
- [attr.y1]="geometry.start.y"
2756
- [attr.y2]="geometry.start.y"
3291
+ [attr.y1]="shape.start.y"
3292
+ [attr.y2]="shape.start.y"
3293
+ [class]="startFade()"
2757
3294
  />
2758
3295
 
2759
3296
  @if (startLabelInfo(); as info) {
2760
3297
  <text
2761
- class="text-caption fill-(--subtle-foreground) tabular-nums"
3298
+ class="text-caption fill-(--subtle-foreground) tabular-nums transition-opacity ease-(--ease-out)"
2762
3299
  data-chart-start-label
2763
3300
  [attr.text-anchor]="info.placement.anchor"
2764
3301
  [attr.x]="info.placement.x"
2765
3302
  [attr.y]="info.placement.y"
3303
+ [class]="startFade()"
2766
3304
  >
2767
3305
  {{ info.label }}
2768
3306
  </text>
2769
3307
  }
2770
3308
 
2771
3309
  <circle
3310
+ #endRef
2772
3311
  class="fill-(--foreground) stroke-(--card) [paint-order:stroke]"
2773
3312
  data-chart-end
2774
3313
  r="4"
2775
3314
  stroke-width="6"
2776
- [attr.cx]="geometry.end.x"
2777
- [attr.cy]="geometry.end.y"
3315
+ [attr.cx]="shape.end.x"
3316
+ [attr.cy]="shape.end.y"
2778
3317
  />
2779
3318
 
2780
- @for (
2781
- tick of ticks();
2782
- track tick.t;
2783
- let index = $index;
2784
- let count = $count;
2785
- let first = $first;
2786
- let last = $last
2787
- ) {
2788
- <text
2789
- class="text-caption fill-(--subtle-foreground) tabular-nums"
2790
- data-chart-axis-tick
2791
- [attr.text-anchor]="first ? 'start' : last ? 'end' : 'middle'"
2792
- [attr.x]="tick.x"
2793
- [attr.y]="effectiveHeight() - 4"
2794
- [class.hidden]="axisTicks() === 3 && !isCoreTick(index, count)"
2795
- [class.max-sm:hidden]="axisTicks() === 'auto' && !isCoreTick(index, count)"
2796
- >
2797
- {{ axisFormat()(tick.t) }}
2798
- </text>
3319
+ @for (tick of ticks(); track tick.t; let index = $index; let first = $first; let last = $last) {
3320
+ @if (tickTexts()[index]; as label) {
3321
+ <text
3322
+ class="text-caption fill-(--subtle-foreground) tabular-nums"
3323
+ data-chart-axis-tick
3324
+ [attr.text-anchor]="first ? 'start' : last ? 'end' : 'middle'"
3325
+ [attr.x]="tick.x"
3326
+ [attr.y]="effectiveHeight() - 4"
3327
+ [class]="label.classes"
3328
+ >
3329
+ {{ label.text }}
3330
+ </text>
3331
+ }
2799
3332
  }
2800
3333
 
2801
3334
  @if (active(); as point) {
@@ -2881,69 +3414,67 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
2881
3414
  (pointerleave)="onPointerLeave()"
2882
3415
  (pointermove)="onPointerMove($event)"
2883
3416
  >
2884
- @if (geometry(); as geometry) {
3417
+ @if (shown(); as shape) {
2885
3418
  <path
3419
+ #lineRef
2886
3420
  class="stroke-(--foreground)"
2887
3421
  data-chart-line
2888
3422
  fill="none"
2889
3423
  stroke-linecap="round"
2890
3424
  stroke-linejoin="round"
2891
- [attr.d]="displayLine()"
3425
+ [attr.d]="shape.line"
2892
3426
  [attr.stroke-width]="sparkline() ? 1.5 : 2"
2893
3427
  />
2894
3428
 
2895
3429
  @if (!sparkline()) {
2896
3430
  <line
2897
- class="stroke-(--border)"
3431
+ class="stroke-(--border) transition-opacity ease-(--ease-out)"
2898
3432
  data-chart-start-line
2899
3433
  stroke-dasharray="3 4"
2900
3434
  stroke-width="1"
2901
3435
  x1="0"
2902
3436
  [attr.x2]="effectiveWidth()"
2903
- [attr.y1]="geometry.start.y"
2904
- [attr.y2]="geometry.start.y"
3437
+ [attr.y1]="shape.start.y"
3438
+ [attr.y2]="shape.start.y"
3439
+ [class]="startFade()"
2905
3440
  />
2906
3441
 
2907
3442
  @if (startLabelInfo(); as info) {
2908
3443
  <text
2909
- class="text-caption fill-(--subtle-foreground) tabular-nums"
3444
+ class="text-caption fill-(--subtle-foreground) tabular-nums transition-opacity ease-(--ease-out)"
2910
3445
  data-chart-start-label
2911
3446
  [attr.text-anchor]="info.placement.anchor"
2912
3447
  [attr.x]="info.placement.x"
2913
3448
  [attr.y]="info.placement.y"
3449
+ [class]="startFade()"
2914
3450
  >
2915
3451
  {{ info.label }}
2916
3452
  </text>
2917
3453
  }
2918
3454
 
2919
3455
  <circle
3456
+ #endRef
2920
3457
  class="fill-(--foreground) stroke-(--card) [paint-order:stroke]"
2921
3458
  data-chart-end
2922
3459
  r="4"
2923
3460
  stroke-width="6"
2924
- [attr.cx]="geometry.end.x"
2925
- [attr.cy]="geometry.end.y"
3461
+ [attr.cx]="shape.end.x"
3462
+ [attr.cy]="shape.end.y"
2926
3463
  />
2927
3464
 
2928
- @for (
2929
- tick of ticks();
2930
- track tick.t;
2931
- let index = $index;
2932
- let count = $count;
2933
- let first = $first;
2934
- let last = $last
2935
- ) {
2936
- <text
2937
- class="text-caption fill-(--subtle-foreground) tabular-nums"
2938
- data-chart-axis-tick
2939
- [attr.text-anchor]="first ? 'start' : last ? 'end' : 'middle'"
2940
- [attr.x]="tick.x"
2941
- [attr.y]="effectiveHeight() - 4"
2942
- [class.hidden]="axisTicks() === 3 && !isCoreTick(index, count)"
2943
- [class.max-sm:hidden]="axisTicks() === 'auto' && !isCoreTick(index, count)"
2944
- >
2945
- {{ axisFormat()(tick.t) }}
2946
- </text>
3465
+ @for (tick of ticks(); track tick.t; let index = $index; let first = $first; let last = $last) {
3466
+ @if (tickTexts()[index]; as label) {
3467
+ <text
3468
+ class="text-caption fill-(--subtle-foreground) tabular-nums"
3469
+ data-chart-axis-tick
3470
+ [attr.text-anchor]="first ? 'start' : last ? 'end' : 'middle'"
3471
+ [attr.x]="tick.x"
3472
+ [attr.y]="effectiveHeight() - 4"
3473
+ [class]="label.classes"
3474
+ >
3475
+ {{ label.text }}
3476
+ </text>
3477
+ }
2947
3478
  }
2948
3479
 
2949
3480
  @if (active(); as point) {
@@ -3013,12 +3544,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
3013
3544
  class: 'block h-full',
3014
3545
  },
3015
3546
  }]
3016
- }], 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 }] }] } });
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 }] }] } });
3017
3548
 
3018
- 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,transform,overlay,display] transition-discrete duration-(--duration-fast) ease-out starting:open:opacity-0 starting:open:scale-(--enter-scale) opacity-0 scale-(--enter-scale) open:opacity-100 open:scale-100';
3019
- 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';
3020
- 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) 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';
3021
- 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) 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';
3549
+ 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
+ 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';
3022
3553
  const nextId$1 = (() => {
3023
3554
  let count = 0;
3024
3555
  return () => `ui-menu-${++count}`;
@@ -3320,7 +3851,221 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
3320
3851
  }]
3321
3852
  }], 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 }] }] } });
3322
3853
 
3323
- const BASE_CLASSES$1 = 'flex items-center gap-3 h-10 px-3 rounded-control text-label leading-normal font-medium transition-colors duration-(--duration-press) hover:bg-(--glow) hover:text-(--foreground) focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-(--ring)';
3854
+ function readDuration$1(element, name, fallback) {
3855
+ const raw = getComputedStyle(element).getPropertyValue(name).trim();
3856
+ const value = parseFloat(raw);
3857
+ if (Number.isNaN(value)) {
3858
+ return fallback;
3859
+ }
3860
+ return raw.endsWith('ms') ? value : value * 1000;
3861
+ }
3862
+ function readEasing(element, name, fallback) {
3863
+ return getComputedStyle(element).getPropertyValue(name).trim() || fallback;
3864
+ }
3865
+
3866
+ /**
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
3870
+ * out first. Nothing moves under `prefers-reduced-motion`: the list closes up at once.
3871
+ *
3872
+ * @example
3873
+ * <ul uiFlipList>
3874
+ * @for (key of keys(); track key.id) {
3875
+ * <li uiListRow animate.leave="ui-leave-fade">...</li>
3876
+ * }
3877
+ * </ul>
3878
+ */
3879
+ class UiFlipList {
3880
+ host = inject(ElementRef).nativeElement;
3881
+ reduced = injectReducedMotion();
3882
+ mutations = new MutationObserver(() => this.onChange());
3883
+ resizes = new ResizeObserver(() => this.measure());
3884
+ running = new Map();
3885
+ watched = new Set();
3886
+ tops = new Map();
3887
+ constructor() {
3888
+ afterNextRender(() => {
3889
+ this.measure();
3890
+ this.mutations.observe(this.host, { childList: true });
3891
+ this.resizes.observe(this.host);
3892
+ });
3893
+ inject(DestroyRef).onDestroy(() => {
3894
+ this.mutations.disconnect();
3895
+ this.resizes.disconnect();
3896
+ });
3897
+ }
3898
+ 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);
3905
+ }
3906
+ }
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);
3912
+ }
3913
+ }
3914
+ }
3915
+ interruptedOffset(child) {
3916
+ const running = this.running.get(child);
3917
+ if (!running) {
3918
+ return 0;
3919
+ }
3920
+ const shown = child.getBoundingClientRect().top;
3921
+ running.cancel();
3922
+ this.running.delete(child);
3923
+ return shown - child.getBoundingClientRect().top;
3924
+ }
3925
+ onChange() {
3926
+ const before = this.tops;
3927
+ this.measure();
3928
+ if (this.reduced()) {
3929
+ return;
3930
+ }
3931
+ const duration = readDuration$1(this.host, '--duration-base', 260);
3932
+ 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) {
3936
+ continue;
3937
+ }
3938
+ const from = previous - top + this.interruptedOffset(child);
3939
+ this.running.set(child, child.animate([{ transform: `translateY(${from}px)` }, { transform: 'translateY(0)' }], { duration, easing }));
3940
+ }
3941
+ }
3942
+ 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 });
3944
+ }
3945
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiFlipList, decorators: [{
3946
+ type: Directive,
3947
+ args: [{
3948
+ selector: '[uiFlipList]',
3949
+ }]
3950
+ }], ctorParameters: () => [] });
3951
+
3952
+ const HOLD_MS = 200;
3953
+ const SCROLL_SETTLE_MS = 200;
3954
+ const SCROLL_START_MS = 300;
3955
+ function paintedElements(host) {
3956
+ if (host.tagName === 'TR') {
3957
+ return Array.from(host.children).filter((cell) => cell instanceof HTMLElement);
3958
+ }
3959
+ if (host.hasAttribute('ui-group-cell') && host.firstElementChild instanceof HTMLElement) {
3960
+ return [host.firstElementChild];
3961
+ }
3962
+ return [host];
3963
+ }
3964
+ /**
3965
+ * Draws the eye to an element that just changed: scrolls it into view when it is off screen, holds the
3966
+ * `--soft` fill for 200 ms, then fades back to the element's own background over `--duration-highlight`.
3967
+ * Each new non-null token plays once, so pass a fresh value per event (an object or a counter), not a
3968
+ * boolean. A non-null token already present at first render plays on arrival. On a `tr` it paints the
3969
+ * cells, on a `td[ui-group-cell]` the band, anywhere else the host. Under `prefers-reduced-motion` the
3970
+ * scroll is instant and the fade lasts 600 ms.
3971
+ *
3972
+ * Give the element `scroll-margin-top` and `scroll-margin-bottom` equal to what is pinned above and
3973
+ * below it (header, tab bar, action bar): a row under them counts as off screen and is scrolled out.
3974
+ * For an arrival on a page opened at the element, scroll to it at once first (`scrollIntoView` in
3975
+ * `afterNextRender`) and set the token afterwards: a token present at first render scrolls smoothly
3976
+ * from where the page is.
3977
+ *
3978
+ * @example
3979
+ * <a ui-row [uiHighlight]="changed() === holding.id ? token() : null" routerLink="/holdings/1">...</a>
3980
+ */
3981
+ class UiHighlight {
3982
+ uiHighlight = input(null, /* @ts-ignore */
3983
+ ...(ngDevMode ? [{ debugName: "uiHighlight" }] : /* istanbul ignore next */ []));
3984
+ host = inject(ElementRef).nativeElement;
3985
+ reduced = injectReducedMotion();
3986
+ cancel = null;
3987
+ constructor() {
3988
+ effect(() => {
3989
+ const token = this.uiHighlight();
3990
+ if (token === null || token === undefined) {
3991
+ return;
3992
+ }
3993
+ untracked(() => this.play());
3994
+ });
3995
+ inject(DestroyRef).onDestroy(() => this.cancel?.());
3996
+ }
3997
+ play() {
3998
+ this.cancel?.();
3999
+ if (this.isVisible()) {
4000
+ this.cancel = this.flash();
4001
+ return;
4002
+ }
4003
+ const reduced = this.reduced();
4004
+ this.host.scrollIntoView({ behavior: reduced ? 'auto' : 'smooth', block: 'center' });
4005
+ if (reduced) {
4006
+ this.cancel = this.flash();
4007
+ return;
4008
+ }
4009
+ let stopFlash = null;
4010
+ let timer;
4011
+ const moves = (event) => event.target === document || (event.target instanceof Node && event.target.contains(this.host));
4012
+ const start = () => {
4013
+ stopListening();
4014
+ stopFlash = this.flash();
4015
+ };
4016
+ const onScrollEnd = (event) => {
4017
+ if (moves(event)) {
4018
+ start();
4019
+ }
4020
+ };
4021
+ const onScroll = (event) => {
4022
+ if (moves(event)) {
4023
+ clearTimeout(timer);
4024
+ timer = setTimeout(start, SCROLL_SETTLE_MS);
4025
+ }
4026
+ };
4027
+ const stopListening = () => {
4028
+ clearTimeout(timer);
4029
+ window.removeEventListener('scrollend', onScrollEnd, true);
4030
+ window.removeEventListener('scroll', onScroll, true);
4031
+ };
4032
+ timer = setTimeout(start, SCROLL_START_MS);
4033
+ window.addEventListener('scrollend', onScrollEnd, true);
4034
+ window.addEventListener('scroll', onScroll, true);
4035
+ this.cancel = () => {
4036
+ stopListening();
4037
+ stopFlash?.();
4038
+ };
4039
+ }
4040
+ isVisible() {
4041
+ const rect = this.host.getBoundingClientRect();
4042
+ const style = getComputedStyle(this.host);
4043
+ const top = parseFloat(style.scrollMarginTop) || 0;
4044
+ const bottom = parseFloat(style.scrollMarginBottom) || 0;
4045
+ return rect.top >= top && rect.bottom <= window.innerHeight - bottom;
4046
+ }
4047
+ 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());
4057
+ }
4058
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiHighlight, deps: [], target: i0.ɵɵFactoryTarget.Directive });
4059
+ 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 });
4060
+ }
4061
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiHighlight, decorators: [{
4062
+ type: Directive,
4063
+ args: [{
4064
+ selector: '[uiHighlight]',
4065
+ }]
4066
+ }], ctorParameters: () => [], propDecorators: { uiHighlight: [{ type: i0.Input, args: [{ isSignal: true, alias: "uiHighlight", required: false }] }] } });
4067
+
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)';
3324
4069
  const ACTIVE_CLASSES = 'bg-(--soft) text-(--foreground)';
3325
4070
  const IDLE_CLASSES = 'text-(--muted-foreground) active:bg-(--soft)';
3326
4071
  /**
@@ -3334,7 +4079,7 @@ const IDLE_CLASSES = 'text-(--muted-foreground) active:bg-(--soft)';
3334
4079
  */
3335
4080
  class UiNavItem {
3336
4081
  active = input(false, { ...(ngDevMode ? { debugName: "active" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
3337
- classes = computed(() => `${BASE_CLASSES$1} ${this.active() ? ACTIVE_CLASSES : IDLE_CLASSES}`, /* @ts-ignore */
4082
+ classes = computed(() => `${BASE_CLASSES$2} ${this.active() ? ACTIVE_CLASSES : IDLE_CLASSES}`, /* @ts-ignore */
3338
4083
  ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
3339
4084
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiNavItem, deps: [], target: i0.ɵɵFactoryTarget.Component });
3340
4085
  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 });
@@ -3363,10 +4108,12 @@ const SIZE_CLASSES$1 = {
3363
4108
  };
3364
4109
  const SIZE_GAPS = { md: 'gap-2.5', lg: 'gap-2.5', xl: 'gap-3', card: 'gap-2', dense: 'gap-3' };
3365
4110
  const PADDING_CLASSES = { md: 'px-2.5', sm: 'px-2', none: 'px-0' };
3366
- const BASE_CLASSES = 'flex w-full items-center py-1.5 rounded-control text-left select-none touch-manipulation transition-colors duration-(--duration-press) hover:bg-(--glow) active:bg-(--soft) focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-(--ring)';
4111
+ const BASE_CLASSES$1 = 'flex w-full items-center py-1.5 rounded-control text-left select-none touch-manipulation focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-(--ring)';
4112
+ const INTERACTIVE_CLASSES = 'transition-[scale,background-color] [transition-duration:var(--duration-press),var(--duration-fast)] ease-out hover:bg-(--glow) active:bg-(--soft) active:scale-(--press-scale)';
4113
+ const UNAVAILABLE_CLASSES = 'opacity-50 cursor-not-allowed';
3367
4114
  const SELECTED_CLASSES$1 = 'bg-(--soft)';
3368
4115
  /**
3369
- * Clickable row of a list: a holding, an envelope, an account.
4116
+ * Clickable row of a list: a holding, an account, a search result.
3370
4117
  *
3371
4118
  * @example
3372
4119
  * <a ui-row [selected]="holding.id === openId()" [routerLink]="['/holdings', holding.id]">
@@ -3375,28 +4122,59 @@ const SELECTED_CLASSES$1 = 'bg-(--soft)';
3375
4122
  */
3376
4123
  class UiRow {
3377
4124
  selected = input(false, { ...(ngDevMode ? { debugName: "selected" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4125
+ busy = input(false, { ...(ngDevMode ? { debugName: "busy" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4126
+ unavailable = input(false, { ...(ngDevMode ? { debugName: "unavailable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
3378
4127
  size = input('md', /* @ts-ignore */
3379
4128
  ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
3380
4129
  padding = input('md', /* @ts-ignore */
3381
4130
  ...(ngDevMode ? [{ debugName: "padding" }] : /* istanbul ignore next */ []));
3382
4131
  gap = input('default', /* @ts-ignore */
3383
4132
  ...(ngDevMode ? [{ debugName: "gap" }] : /* istanbul ignore next */ []));
3384
- classes = computed(() => `${BASE_CLASSES} ${SIZE_CLASSES$1[this.size()]} ${this.gap() === 'sm' ? 'gap-2' : SIZE_GAPS[this.size()]} ${PADDING_CLASSES[this.padding()]}${this.selected() ? ` ${SELECTED_CLASSES$1}` : ''}`, /* @ts-ignore */
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 */
3385
4134
  ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
4135
+ constructor() {
4136
+ const host = inject(ElementRef).nativeElement;
4137
+ const guard = (event) => {
4138
+ if (this.busy() || this.unavailable()) {
4139
+ event.preventDefault();
4140
+ event.stopImmediatePropagation();
4141
+ }
4142
+ };
4143
+ host.addEventListener('click', guard, { capture: true });
4144
+ inject(DestroyRef).onDestroy(() => host.removeEventListener('click', guard, { capture: true }));
4145
+ }
3386
4146
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiRow, deps: [], target: i0.ɵɵFactoryTarget.Component });
3387
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.7", type: UiRow, isStandalone: true, selector: "a[ui-row], button[ui-row]", inputs: { selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, padding: { classPropertyName: "padding", publicName: "padding", isSignal: true, isRequired: false, transformFunction: null }, gap: { classPropertyName: "gap", publicName: "gap", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "classes()", "attr.aria-current": "selected() ? 'true' : null" } }, ngImport: i0, template: '<ng-content />', isInline: true });
4147
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: UiRow, isStandalone: true, selector: "a[ui-row], button[ui-row]", inputs: { selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, busy: { classPropertyName: "busy", publicName: "busy", isSignal: true, isRequired: false, transformFunction: null }, unavailable: { classPropertyName: "unavailable", publicName: "unavailable", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, padding: { classPropertyName: "padding", publicName: "padding", isSignal: true, isRequired: false, transformFunction: null }, gap: { classPropertyName: "gap", publicName: "gap", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "classes()", "attr.aria-busy": "busy() || null", "attr.aria-disabled": "busy() || unavailable() ? 'true' : null", "attr.aria-current": "selected() ? 'true' : null" } }, ngImport: i0, template: `
4148
+ <ng-content />
4149
+ @if (busy()) {
4150
+ <span
4151
+ aria-hidden="true"
4152
+ class="rounded-pill animate-cairn-spin size-4 flex-none border-2 border-current border-r-transparent"
4153
+ ></span>
4154
+ }
4155
+ `, isInline: true });
3388
4156
  }
3389
4157
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiRow, decorators: [{
3390
4158
  type: Component,
3391
4159
  args: [{
3392
4160
  selector: 'a[ui-row], button[ui-row]',
3393
- template: '<ng-content />',
4161
+ template: `
4162
+ <ng-content />
4163
+ @if (busy()) {
4164
+ <span
4165
+ aria-hidden="true"
4166
+ class="rounded-pill animate-cairn-spin size-4 flex-none border-2 border-current border-r-transparent"
4167
+ ></span>
4168
+ }
4169
+ `,
3394
4170
  host: {
3395
4171
  '[class]': 'classes()',
4172
+ '[attr.aria-busy]': 'busy() || null',
4173
+ '[attr.aria-disabled]': "busy() || unavailable() ? 'true' : null",
3396
4174
  '[attr.aria-current]': "selected() ? 'true' : null",
3397
4175
  },
3398
4176
  }]
3399
- }], propDecorators: { selected: [{ type: i0.Input, args: [{ isSignal: true, alias: "selected", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], padding: [{ type: i0.Input, args: [{ isSignal: true, alias: "padding", required: false }] }], gap: [{ type: i0.Input, args: [{ isSignal: true, alias: "gap", required: false }] }] } });
4177
+ }], ctorParameters: () => [], propDecorators: { selected: [{ type: i0.Input, args: [{ isSignal: true, alias: "selected", required: false }] }], busy: [{ type: i0.Input, args: [{ isSignal: true, alias: "busy", required: false }] }], unavailable: [{ type: i0.Input, args: [{ isSignal: true, alias: "unavailable", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], padding: [{ type: i0.Input, args: [{ isSignal: true, alias: "padding", required: false }] }], gap: [{ type: i0.Input, args: [{ isSignal: true, alias: "gap", required: false }] }] } });
3400
4178
  /**
3401
4179
  * Lays out a `ui-row` with a trailing action beside it, such as a "more" icon button: the row takes
3402
4180
  * the room the action leaves and the action keeps its own size.
@@ -3475,7 +4253,7 @@ const nextId = (() => {
3475
4253
  let count = 0;
3476
4254
  return () => `ui-segmented-${++count}`;
3477
4255
  })();
3478
- const OPTION_CLASSES = "relative z-10 flex-1 cursor-pointer px-3 text-label font-medium transition-colors 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)";
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)";
3479
4257
  const SEGMENTED_SIZES = ['md', 'sm'];
3480
4258
  const SIZE_CLASSES = {
3481
4259
  md: 'h-10 pointer-fine:h-8',
@@ -3710,11 +4488,13 @@ class UiSkeleton {
3710
4488
  }, /* @ts-ignore */
3711
4489
  ...(ngDevMode ? [{ debugName: "bars" }] : /* istanbul ignore next */ []));
3712
4490
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiSkeleton, deps: [], target: i0.ɵɵFactoryTarget.Component });
3713
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: UiSkeleton, isStandalone: true, selector: "ui-skeleton", inputs: { shape: { classPropertyName: "shape", publicName: "shape", isSignal: true, isRequired: false, transformFunction: null }, lines: { classPropertyName: "lines", publicName: "lines", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "aria-hidden": "true" }, classAttribute: "flex flex-col gap-2" }, ngImport: i0, template: "\n @switch (shape()) {\n @case ('figure') {\n <span class=\"block rounded-control bg-(--muted) h-10 w-65\" [style.animation]=\"animation\"></span>\n }\n @case ('row') {\n <span class=\"flex h-14 items-center justify-between gap-4\">\n <span class=\"flex min-w-0 flex-1 flex-col gap-1.5\">\n <span class=\"block rounded-control bg-(--muted) h-4 w-3/5\" [style.animation]=\"animation\"></span>\n <span class=\"block rounded-control bg-(--muted) h-3 w-20\" [style.animation]=\"animation\"></span>\n </span>\n <span class=\"block rounded-control bg-(--muted) h-4 w-[90px] flex-none\" [style.animation]=\"animation\"></span>\n </span>\n }\n @case ('chart') {\n <span class=\"rounded-control min-h-50 w-full flex-1 bg-(--muted)\" [style.animation]=\"animation\"></span>\n }\n @case ('dot') {\n <span class=\"rounded-pill size-3 bg-(--muted)\" [style.animation]=\"animation\"></span>\n }\n @case ('ring') {\n <span\n class=\"rounded-pill shadow-[inset_0_0_0_30px_var(--muted)]\"\n [style.animation]=\"animation\"\n [style.height.px]=\"size()\"\n [style.width.px]=\"size()\"\n ></span>\n }\n @default {\n @for (line of bars(); track $index) {\n <span\n class=\"block rounded-control bg-(--muted)\"\n [style.animation]=\"animation\"\n [style.height.px]=\"height()\"\n [style.width]=\"line\"\n ></span>\n }\n }\n }\n ", isInline: true, styles: ["@keyframes cairn-pulse{0%{opacity:1}to{opacity:.45}}\n"] });
4491
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: UiSkeleton, isStandalone: true, selector: "ui-skeleton", inputs: { shape: { classPropertyName: "shape", publicName: "shape", isSignal: true, isRequired: false, transformFunction: null }, lines: { classPropertyName: "lines", publicName: "lines", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "aria-hidden": "true" }, classAttribute: "flex flex-col gap-2" }, ngImport: i0, template: "\n @switch (shape()) {\n @case ('figure') {\n <span class=\"block rounded-control bg-(--muted) h-10 w-65\" [style.animation]=\"animation\"></span>\n }\n @case ('row') {\n <span class=\"flex h-14 items-center justify-between gap-4\">\n <span class=\"flex min-w-0 flex-1 flex-col gap-1.5\">\n <span class=\"block rounded-control bg-(--muted) h-4 w-3/5\" [style.animation]=\"animation\"></span>\n <span class=\"block rounded-control bg-(--muted) h-3 w-20\" [style.animation]=\"animation\"></span>\n </span>\n <span class=\"block rounded-control bg-(--muted) h-4 w-[90px] flex-none\" [style.animation]=\"animation\"></span>\n </span>\n }\n @case ('chart') {\n <span class=\"rounded-control min-h-50 w-full flex-1 bg-(--muted)\" [style.animation]=\"animation\"></span>\n }\n @case ('dot') {\n <span class=\"rounded-pill size-3 bg-(--muted)\" [style.animation]=\"animation\"></span>\n }\n @case ('ring') {\n <span\n class=\"rounded-pill shadow-[inset_0_0_0_30px_var(--muted)]\"\n [style.animation]=\"animation\"\n [style.height.px]=\"size()\"\n [style.width.px]=\"size()\"\n ></span>\n }\n @default {\n @for (line of bars(); track $index) {\n <span\n class=\"block rounded-control bg-(--muted)\"\n [style.animation]=\"animation\"\n [style.height.px]=\"height()\"\n [style.width]=\"line\"\n ></span>\n }\n }\n }\n ", isInline: true });
3714
4492
  }
3715
4493
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiSkeleton, decorators: [{
3716
4494
  type: Component,
3717
- args: [{ selector: 'ui-skeleton', template: `
4495
+ args: [{
4496
+ selector: 'ui-skeleton',
4497
+ template: `
3718
4498
  @switch (shape()) {
3719
4499
  @case ('figure') {
3720
4500
  <span class="${BAR_CLASSES} h-10 w-65" [style.animation]="animation"></span>
@@ -3753,12 +4533,39 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
3753
4533
  }
3754
4534
  }
3755
4535
  }
3756
- `, host: {
4536
+ `,
4537
+ host: {
3757
4538
  'aria-hidden': 'true',
3758
4539
  class: 'flex flex-col gap-2',
3759
- }, styles: ["@keyframes cairn-pulse{0%{opacity:1}to{opacity:.45}}\n"] }]
4540
+ },
4541
+ }]
3760
4542
  }], 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 }] }] } });
3761
4543
 
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)';
4545
+ 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
+ /**
4547
+ * 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.
4550
+ *
4551
+ * @example
4552
+ * <a uiStaleLink chevron routerLink="/holdings">1 cours en retard</a>
4553
+ */
4554
+ class UiStaleLink {
4555
+ chevron = input(false, { ...(ngDevMode ? { debugName: "chevron" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4556
+ classes = computed(() => (this.chevron() ? `${BASE_CLASSES} ${CHEVRON_CLASSES}` : BASE_CLASSES), /* @ts-ignore */
4557
+ ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
4558
+ 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 });
4560
+ }
4561
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiStaleLink, decorators: [{
4562
+ type: Directive,
4563
+ args: [{
4564
+ selector: 'a[uiStaleLink]',
4565
+ host: { '[class]': 'classes()' },
4566
+ }]
4567
+ }], propDecorators: { chevron: [{ type: i0.Input, args: [{ isSignal: true, alias: "chevron", required: false }] }] } });
4568
+
3762
4569
  /** Available stat sizes. `StatSize` is derived from this tuple. */
3763
4570
  const STAT_SIZES = ['hero', 'tile'];
3764
4571
  const VALUE_CLASSES = {
@@ -3846,7 +4653,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
3846
4653
  }]
3847
4654
  }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }] } });
3848
4655
 
3849
- const CLASSES = "relative shrink-0 w-[51px] h-[31px] pointer-fine:w-10 pointer-fine:h-6 appearance-none cursor-pointer rounded-pill bg-(--muted) checked:bg-(--primary) transition-colors duration-(--duration-fast) ease-out disabled:pointer-events-none disabled:opacity-40 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--ring) before:absolute before:inset-x-0 before:top-1/2 before:-translate-y-1/2 before:h-(--row-min) before:content-[''] after:absolute after:top-0.5 after:left-0.5 after:size-[27px] pointer-fine:after:size-5 after:rounded-pill after:bg-(--card) after:shadow-[0_1px_3px_rgb(0_0_0/0.2)] after:transition-transform after:duration-(--duration-fast) after:ease-out after:content-[''] checked:after:translate-x-[20px] pointer-fine:checked:after:translate-x-4";
4656
+ const CLASSES = "relative shrink-0 w-[51px] h-[31px] pointer-fine:w-10 pointer-fine:h-6 appearance-none cursor-pointer rounded-pill bg-(--muted) checked:bg-(--primary) transition-[background-color] duration-(--duration-fast) ease-out disabled:pointer-events-none disabled:opacity-40 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--ring) before:absolute before:inset-x-0 before:top-1/2 before:-translate-y-1/2 before:h-(--row-min) before:content-[''] after:absolute after:top-0.5 after:left-0.5 after:size-[27px] pointer-fine:after:size-5 after:rounded-pill after:bg-(--card) after:shadow-[0_1px_3px_rgb(0_0_0/0.2)] after:transition-transform after:duration-(--duration-fast) after:ease-out after:content-[''] checked:after:translate-x-[20px] pointer-fine:checked:after:translate-x-4";
3850
4657
  /**
3851
4658
  * Native checkbox styled as an iOS-style switch, exposed to assistive technology as `role="switch"`.
3852
4659
  * 51 by 31 on touch, 40 by 24 with a mouse.
@@ -3870,7 +4677,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
3870
4677
  }]
3871
4678
  }] });
3872
4679
 
3873
- const TAB_CLASSES = 'flex h-[52px] flex-col items-center justify-center gap-[3px] touch-manipulation select-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-(--ring)';
4680
+ 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)';
3874
4681
  const INACTIVE_TAB_CLASSES = 'font-medium text-(--muted-foreground)';
3875
4682
  const ACTIVE_TAB_CLASSES = 'font-semibold text-(--foreground)';
3876
4683
  /**
@@ -3926,6 +4733,150 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
3926
4733
  }]
3927
4734
  }], propDecorators: { active: [{ type: i0.Input, args: [{ isSignal: true, alias: "active", required: false }] }] } });
3928
4735
 
4736
+ /**
4737
+ * One-slot confirmation message queue read by `ui-toaster`.
4738
+ *
4739
+ * @example
4740
+ * inject(UiToasts).show('Achat enregistré');
4741
+ */
4742
+ class UiToasts {
4743
+ current = signal(null, /* @ts-ignore */
4744
+ ...(ngDevMode ? [{ debugName: "current" }] : /* istanbul ignore next */ []));
4745
+ sequence = 0;
4746
+ toast = this.current.asReadonly();
4747
+ show(message) {
4748
+ this.current.set({ id: ++this.sequence, text: message });
4749
+ }
4750
+ dismiss() {
4751
+ this.current.set(null);
4752
+ }
4753
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiToasts, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4754
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiToasts, providedIn: 'root' });
4755
+ }
4756
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiToasts, decorators: [{
4757
+ type: Injectable,
4758
+ args: [{ providedIn: 'root' }]
4759
+ }] });
4760
+
4761
+ const DEFAULT_DURATION = 5000;
4762
+ const TOAST_CLASSES = 'pointer-events-auto max-w-[min(28rem,calc(100vw-2rem))] rounded-container bg-(--elevated) px-4 py-3 text-label text-(--foreground) shadow-[0_8px_24px_rgb(0_0_0/0.16),inset_0_0_0_1px_var(--border)] transition-[opacity,translate] duration-(--duration-base) ease-out starting:opacity-0 starting:translate-y-2 motion-reduce:starting:translate-y-0';
4763
+ const readDuration = (host) => {
4764
+ const raw = getComputedStyle(host).getPropertyValue('--toast-duration').trim();
4765
+ const value = parseFloat(raw);
4766
+ if (Number.isNaN(value)) {
4767
+ return DEFAULT_DURATION;
4768
+ }
4769
+ return raw.endsWith('ms') ? value : value * 1000;
4770
+ };
4771
+ /**
4772
+ * Confirmation message shown by `UiToasts.show`: one at a time, no action, gone after `--toast-duration`.
4773
+ * Place it once in the app shell.
4774
+ *
4775
+ * @example
4776
+ * <ui-toaster />
4777
+ */
4778
+ class UiToaster {
4779
+ toasts = inject(UiToasts);
4780
+ toastClasses = TOAST_CLASSES;
4781
+ host = inject(ElementRef).nativeElement;
4782
+ hovered = signal(false, /* @ts-ignore */
4783
+ ...(ngDevMode ? [{ debugName: "hovered" }] : /* istanbul ignore next */ []));
4784
+ focused = signal(false, /* @ts-ignore */
4785
+ ...(ngDevMode ? [{ debugName: "focused" }] : /* istanbul ignore next */ []));
4786
+ lastId = 0;
4787
+ remaining = DEFAULT_DURATION;
4788
+ startedAt = 0;
4789
+ timer;
4790
+ constructor() {
4791
+ effect(() => {
4792
+ const toast = this.toasts.toast();
4793
+ const paused = this.hovered() || this.focused();
4794
+ untracked(() => {
4795
+ if (!toast) {
4796
+ this.stop();
4797
+ this.hovered.set(false);
4798
+ this.focused.set(false);
4799
+ return;
4800
+ }
4801
+ this.host.querySelectorAll('.ui-leave-fade').forEach((leaving) => leaving.remove());
4802
+ if (paused) {
4803
+ this.pause();
4804
+ }
4805
+ else {
4806
+ this.resume(toast.id);
4807
+ }
4808
+ });
4809
+ });
4810
+ inject(DestroyRef).onDestroy(() => this.stop());
4811
+ }
4812
+ setHovered(value) {
4813
+ this.hovered.set(value);
4814
+ }
4815
+ setFocused(value) {
4816
+ this.focused.set(value);
4817
+ }
4818
+ resume(id) {
4819
+ if (id !== this.lastId) {
4820
+ this.lastId = id;
4821
+ this.remaining = readDuration(this.host);
4822
+ }
4823
+ clearTimeout(this.timer);
4824
+ this.startedAt = Date.now();
4825
+ this.timer = setTimeout(() => this.toasts.dismiss(), this.remaining);
4826
+ }
4827
+ pause() {
4828
+ if (this.timer !== undefined) {
4829
+ this.remaining -= Date.now() - this.startedAt;
4830
+ clearTimeout(this.timer);
4831
+ this.timer = undefined;
4832
+ }
4833
+ }
4834
+ stop() {
4835
+ clearTimeout(this.timer);
4836
+ this.timer = undefined;
4837
+ this.lastId = 0;
4838
+ }
4839
+ 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: `
4841
+ @if (toasts.toast(); as toast) {
4842
+ <div
4843
+ animate.leave="ui-leave-fade"
4844
+ [class]="toastClasses"
4845
+ (focusin)="setFocused(true)"
4846
+ (focusout)="setFocused(false)"
4847
+ (mouseenter)="setHovered(true)"
4848
+ (mouseleave)="setHovered(false)"
4849
+ >
4850
+ {{ toast.text }}
4851
+ </div>
4852
+ }
4853
+ `, isInline: true });
4854
+ }
4855
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiToaster, decorators: [{
4856
+ type: Component,
4857
+ args: [{
4858
+ selector: 'ui-toaster',
4859
+ template: `
4860
+ @if (toasts.toast(); as toast) {
4861
+ <div
4862
+ animate.leave="ui-leave-fade"
4863
+ [class]="toastClasses"
4864
+ (focusin)="setFocused(true)"
4865
+ (focusout)="setFocused(false)"
4866
+ (mouseenter)="setHovered(true)"
4867
+ (mouseleave)="setHovered(false)"
4868
+ >
4869
+ {{ toast.text }}
4870
+ </div>
4871
+ }
4872
+ `,
4873
+ host: {
4874
+ 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',
4876
+ },
4877
+ }]
4878
+ }], ctorParameters: () => [] });
4879
+
3929
4880
  /** Breakpoints a secondary column can be held back until. */
3930
4881
  const CELL_BREAKPOINTS = ['md', 'lg'];
3931
4882
  /** Body row heights. */
@@ -4009,8 +4960,10 @@ class UiTr {
4009
4960
  return [
4010
4961
  'relative [&>td:first-child]:rounded-l-control [&>td:last-child]:rounded-r-control',
4011
4962
  this.selected() ? '[&>td]:bg-(--soft)' : '',
4012
- 'hover:[&>td]:bg-(--glow)',
4013
- this.interactive() ? 'active:[&>td]:bg-(--soft)' : '',
4963
+ 'hover:[&>td]:bg-(--glow) [&>td]:transition-colors [&>td]:duration-(--duration-fast) [&>td]:ease-out',
4964
+ this.interactive()
4965
+ ? 'active:[&>td]:bg-(--soft) transition-transform duration-(--duration-press) ease-out active:scale-(--press-scale)'
4966
+ : '',
4014
4967
  ]
4015
4968
  .filter(Boolean)
4016
4969
  .join(' ');
@@ -4240,5 +5193,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
4240
5193
  * Generated bundle index. Do not edit.
4241
5194
  */
4242
5195
 
4243
- 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_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, UiExternalLink, UiFact, UiFacts, UiField, UiFieldLeading, UiFieldTrailing, UiFilterChips, UiGroup, UiGroupCell, UiInput, UiLineChart, UiListRow, UiMenu, UiMenuItem, UiMenuTrigger, UiMeter, UiNavItem, UiRow, UiRowAction, UiRowItem, UiRowLink, UiRowTile, UiSegmented, UiSelect, UiSkeleton, UiStat, UiSwitch, UiTab, UiTabBar, UiTable, UiTd, UiTextarea, UiTh, UiTr, formatAmount };
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 };
4244
5197
  //# sourceMappingURL=joanroucoux-cairn-ui.mjs.map