@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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@joanroucoux/cairn-ui",
3
- "version": "0.6.4",
3
+ "version": "0.7.0",
4
4
  "description": "Cairn UI — a monochrome, accessible Angular design system.",
5
5
  "keywords": [
6
6
  "angular",
@@ -0,0 +1,83 @@
1
+ @keyframes cairn-spin {
2
+ to {
3
+ transform: rotate(1turn);
4
+ }
5
+ }
6
+
7
+ @keyframes cairn-pulse {
8
+ from {
9
+ opacity: 1;
10
+ }
11
+ to {
12
+ opacity: 0.45;
13
+ }
14
+ }
15
+
16
+ @keyframes cairn-fade-in {
17
+ from {
18
+ opacity: 0;
19
+ }
20
+ }
21
+
22
+ @keyframes cairn-fade-up-in {
23
+ from {
24
+ opacity: 0;
25
+ transform: translateY(4px);
26
+ }
27
+ }
28
+
29
+ @keyframes cairn-panel-in {
30
+ from {
31
+ opacity: 0;
32
+ transform: translateX(8px);
33
+ }
34
+ }
35
+
36
+ @keyframes cairn-fade-out {
37
+ to {
38
+ opacity: 0;
39
+ }
40
+ }
41
+
42
+ .ui-enter-fade {
43
+ animation: cairn-fade-in var(--duration-fast) var(--ease-out);
44
+ }
45
+
46
+ .ui-enter-fade-up {
47
+ animation: cairn-fade-up-in var(--duration-fast) var(--ease-out);
48
+ }
49
+
50
+ .ui-enter-panel {
51
+ animation: cairn-panel-in var(--duration-base) var(--ease-out);
52
+ }
53
+
54
+ .ui-leave-fade {
55
+ animation: cairn-fade-out var(--duration-exit) var(--ease-out) forwards;
56
+ }
57
+
58
+ ::view-transition-old(root),
59
+ ::view-transition-new(root) {
60
+ animation-duration: var(--duration-base);
61
+ animation-timing-function: var(--ease-out);
62
+ }
63
+
64
+ @view-transition {
65
+ navigation: auto;
66
+ }
67
+
68
+ [data-theme-switching] * {
69
+ transition: none !important;
70
+ }
71
+
72
+ @media (prefers-reduced-motion: reduce) {
73
+ .ui-enter-fade-up,
74
+ .ui-enter-panel {
75
+ animation-name: cairn-fade-in;
76
+ }
77
+
78
+ ::view-transition-group(*),
79
+ ::view-transition-old(root),
80
+ ::view-transition-new(root) {
81
+ animation: none;
82
+ }
83
+ }
package/styles/theme.css CHANGED
@@ -28,4 +28,10 @@
28
28
 
29
29
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
30
30
  --ease-sheet: cubic-bezier(0.32, 0.72, 0, 1);
31
+
32
+ --default-transition-timing-function: var(--ease-out);
33
+ --default-transition-duration: var(--duration-fast);
34
+
35
+ --animate-cairn-spin: cairn-spin var(--duration-spin) linear infinite;
36
+ --animate-cairn-pulse: cairn-pulse var(--pulse-duration) var(--ease-out) infinite alternate;
31
37
  }
package/styles/tokens.css CHANGED
@@ -101,6 +101,9 @@
101
101
  --press-scale: 0.97;
102
102
  --enter-scale: 0.95; /* dialogs and menus start at scale(0.95) and opacity 0, never scale(0) */
103
103
  --pulse-duration: 1600ms; /* skeleton opacity pulse */
104
+ --duration-spin: 800ms; /* button spinner, one turn */
105
+ --duration-highlight: 1200ms; /* a freshly edited row fading back to its background */
106
+ --toast-duration: 5000ms; /* how long a confirmation stays */
104
107
 
105
108
  /* Browser theme color (meta theme-color, iOS status bar) follows the top of the page:
106
109
  light #f7f8f8, dark #0a0b0b - the --background values. */
@@ -137,6 +140,8 @@
137
140
  --enter-scale: 1; /* slides and scales become short fades */
138
141
  --duration-base: 150ms;
139
142
  --pulse-duration: 0ms;
143
+ --duration-highlight: 600ms;
144
+ --duration-spin: 1600ms; /* the spinner still says work is in progress, only slower */
140
145
  }
141
146
  }
142
147
 
@@ -5,7 +5,8 @@ import { InjectionToken, Signal, ElementRef } from '@angular/core';
5
5
  * Bar of actions fixed above the tab bar on iPhone: the primary actions of a detail screen, within
6
6
  * thumb reach. It sits 52px plus the safe area above the bottom edge, so it clears `ui-tab-bar`, and
7
7
  * it is hidden from `64rem`, where the same actions live in the page itself. Its children share the
8
- * width equally.
8
+ * width equally. While it is on the page it publishes its height as `--action-bar-height` on the
9
+ * root element, which `ui-toaster` adds to its offset so a confirmation never covers the actions.
9
10
  *
10
11
  * @example
11
12
  * <ui-action-bar>
@@ -14,6 +15,7 @@ import { InjectionToken, Signal, ElementRef } from '@angular/core';
14
15
  * </ui-action-bar>
15
16
  */
16
17
  declare class UiActionBar {
18
+ constructor();
17
19
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<UiActionBar, never>;
18
20
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<UiActionBar, "ui-action-bar", never, {}, {}, never, ["*"], true, never>;
19
21
  }
@@ -22,6 +24,8 @@ declare const ALERT_VARIANTS: readonly ["error", "warning"];
22
24
  type AlertVariant = (typeof ALERT_VARIANTS)[number];
23
25
  /**
24
26
  * Inline message explaining a failure or a consequence: an icon, an optional heading and a text.
27
+ * It fades in when it appears; set `[fadeIn]="false"` on an alert that is part of the page as it
28
+ * opens (an error state after a load), which shows without motion.
25
29
  *
26
30
  * @example
27
31
  * <ui-alert heading="Sign-in failed">Try again.</ui-alert>
@@ -29,9 +33,10 @@ type AlertVariant = (typeof ALERT_VARIANTS)[number];
29
33
  declare class UiAlert {
30
34
  readonly variant: _angular_core.InputSignal<"error" | "warning">;
31
35
  readonly heading: _angular_core.InputSignal<string | undefined>;
36
+ readonly fadeIn: _angular_core.InputSignalWithTransform<boolean, unknown>;
32
37
  protected readonly classes: _angular_core.Signal<string>;
33
38
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<UiAlert, never>;
34
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<UiAlert, "ui-alert", never, { "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "heading": { "alias": "heading"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
39
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<UiAlert, "ui-alert", never, { "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "heading": { "alias": "heading"; "required": false; "isSignal": true; }; "fadeIn": { "alias": "fadeIn"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
35
40
  }
36
41
 
37
42
  /** App-wide "amounts are hidden" state. Defaults to never masked; the app overrides it. */
@@ -104,6 +109,7 @@ declare class UiAsync {
104
109
  readonly card: _angular_core.InputSignalWithTransform<boolean, unknown>;
105
110
  readonly fill: _angular_core.InputSignalWithTransform<boolean, unknown>;
106
111
  readonly retry: _angular_core.OutputEmitterRef<void>;
112
+ protected readonly shown: _angular_core.Signal<"error" | "loading" | "empty" | "ready" | null>;
107
113
  protected readonly filling: _angular_core.Signal<boolean>;
108
114
  protected readonly iconSize: _angular_core.Signal<16 | 18>;
109
115
  protected readonly boxClasses: _angular_core.Signal<string>;
@@ -114,6 +120,17 @@ declare class UiAsync {
114
120
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<UiAsync, "ui-async", never, { "state": { "alias": "state"; "required": true; "isSignal": true; }; "errorTitle": { "alias": "errorTitle"; "required": false; "isSignal": true; }; "errorMessage": { "alias": "errorMessage"; "required": false; "isSignal": true; }; "retryLabel": { "alias": "retryLabel"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "align": { "alias": "align"; "required": false; "isSignal": true; }; "card": { "alias": "card"; "required": false; "isSignal": true; }; "fill": { "alias": "fill"; "required": false; "isSignal": true; }; }, { "retry": "retry"; }, never, ["[asyncLoading]", "[asyncEmpty]", "*"], true, never>;
115
121
  }
116
122
 
123
+ /**
124
+ * Turns the live state of a call into the state to draw: `loading` only appears once the call has
125
+ * lasted 150 ms and, once shown, stays at least 400 ms, so a fast call never flashes a skeleton and a
126
+ * slow one never blinks. Until then the previous state stays on screen (a reload keeps its content);
127
+ * `null` means nothing to draw yet, for a first load still under 150 ms. Call it in an injection context.
128
+ *
129
+ * @example
130
+ * protected readonly shown = delayedState(this.state);
131
+ */
132
+ declare function delayedState(source: Signal<AsyncState>): Signal<AsyncState | null>;
133
+
117
134
  /** Available avatar sizes. `AvatarSize` is derived from this tuple. */
118
135
  declare const AVATAR_SIZES: readonly ["sm", "md", "lg", "auto"];
119
136
  type AvatarSize = (typeof AVATAR_SIZES)[number];
@@ -341,6 +358,9 @@ type LayoutClasses = {
341
358
  body: string;
342
359
  footer: string;
343
360
  };
361
+ /** What started a close, as `closed` reports it. `DialogCloseReason` is derived from this tuple. */
362
+ declare const DIALOG_CLOSE_REASONS: readonly ["escape", "backdrop", "cross", "drag", "programmatic"];
363
+ type DialogCloseReason = (typeof DIALOG_CLOSE_REASONS)[number];
344
364
  /**
345
365
  * Modal dialog on the native <dialog>: a bottom sheet under `64rem`, centered above it.
346
366
  *
@@ -356,11 +376,12 @@ declare class UiDialog {
356
376
  readonly heading: _angular_core.InputSignal<string>;
357
377
  readonly description: _angular_core.InputSignal<string | undefined>;
358
378
  readonly closeLabel: _angular_core.InputSignal<string | undefined>;
359
- readonly layout: _angular_core.InputSignal<"list" | "trade" | "form" | "confirm">;
379
+ readonly layout: _angular_core.InputSignal<"list" | "form" | "trade" | "confirm">;
360
380
  readonly truncateDescription: _angular_core.InputSignalWithTransform<boolean, unknown>;
361
381
  readonly width: _angular_core.InputSignal<"md" | "lg" | (string & {})>;
362
382
  readonly open: _angular_core.InputSignalWithTransform<boolean, unknown>;
363
383
  readonly dismissed: _angular_core.OutputEmitterRef<void>;
384
+ readonly closed: _angular_core.OutputEmitterRef<"drag" | "escape" | "backdrop" | "cross" | "programmatic">;
364
385
  protected readonly headingId: string;
365
386
  protected readonly descriptionId: string;
366
387
  protected readonly bodyId: string;
@@ -375,11 +396,13 @@ declare class UiDialog {
375
396
  protected readonly footerClasses: _angular_core.Signal<string>;
376
397
  protected readonly classes: _angular_core.Signal<string>;
377
398
  constructor();
399
+ protected close(reason: DialogCloseReason): void;
400
+ protected onCancel(event: Event): void;
378
401
  protected onPointerDown(event: MouseEvent): void;
379
402
  protected onClick(event: MouseEvent): void;
380
403
  protected onNativeClose(): void;
381
404
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<UiDialog, never>;
382
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<UiDialog, "ui-dialog", never, { "heading": { "alias": "heading"; "required": true; "isSignal": true; }; "description": { "alias": "description"; "required": false; "isSignal": true; }; "closeLabel": { "alias": "closeLabel"; "required": false; "isSignal": true; }; "layout": { "alias": "layout"; "required": false; "isSignal": true; }; "truncateDescription": { "alias": "truncateDescription"; "required": false; "isSignal": true; }; "width": { "alias": "width"; "required": false; "isSignal": true; }; "open": { "alias": "open"; "required": false; "isSignal": true; }; }, { "dismissed": "dismissed"; }, never, ["*", "[dialogActions]"], true, never>;
405
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<UiDialog, "ui-dialog", never, { "heading": { "alias": "heading"; "required": true; "isSignal": true; }; "description": { "alias": "description"; "required": false; "isSignal": true; }; "closeLabel": { "alias": "closeLabel"; "required": false; "isSignal": true; }; "layout": { "alias": "layout"; "required": false; "isSignal": true; }; "truncateDescription": { "alias": "truncateDescription"; "required": false; "isSignal": true; }; "width": { "alias": "width"; "required": false; "isSignal": true; }; "open": { "alias": "open"; "required": false; "isSignal": true; }; }, { "dismissed": "dismissed"; "closed": "closed"; }, never, ["*", "[dialogActions]"], true, never>;
383
406
  }
384
407
 
385
408
  type DonutSlice = {
@@ -423,7 +446,7 @@ declare class UiDonut {
423
446
  protected readonly radius = 78;
424
447
  protected readonly ringWidth = 30;
425
448
  protected strokeColor(slice: RankedSlice): string;
426
- protected strokeWidth(slice: RankedSlice): number;
449
+ protected sliceClasses(slice: RankedSlice): string;
427
450
  protected dotClasses(slice: RankedSlice): string;
428
451
  protected rowClasses(slice: RankedSlice): string;
429
452
  protected subLabel(slice: RankedSlice): string;
@@ -437,6 +460,22 @@ declare class UiDonut {
437
460
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<UiDonut, "ui-donut", never, { "slices": { "alias": "slices"; "required": true; "isSignal": true; }; "label": { "alias": "label"; "required": true; "isSignal": true; }; "othersLabel": { "alias": "othersLabel"; "required": true; "isSignal": true; }; "valueFormat": { "alias": "valueFormat"; "required": false; "isSignal": true; }; "shareFormat": { "alias": "shareFormat"; "required": false; "isSignal": true; }; "legendHref": { "alias": "legendHref"; "required": false; "isSignal": true; }; }, { "sliceSelect": "sliceSelect"; }, never, never, true, never>;
438
461
  }
439
462
 
463
+ /**
464
+ * Centred empty state: a title, a hint and an action slot, such as a catalogue search that matches
465
+ * nothing. The padding is 24px, 48px from 64rem.
466
+ *
467
+ * @example
468
+ * <ui-empty heading="Aucun instrument ne correspond à « zzz »" hint="La recherche porte sur le nom et l'ISIN.">
469
+ * <button ui-button variant="outline" size="md" type="button">Ajouter une ligne</button>
470
+ * </ui-empty>
471
+ */
472
+ declare class UiEmpty {
473
+ readonly heading: _angular_core.InputSignal<string>;
474
+ readonly hint: _angular_core.InputSignal<string>;
475
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<UiEmpty, never>;
476
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<UiEmpty, "ui-empty", never, { "heading": { "alias": "heading"; "required": true; "isSignal": true; }; "hint": { "alias": "hint"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
477
+ }
478
+
440
479
  /**
441
480
  * Link to another site: the label followed by an external-link icon. A 44px target on touch,
442
481
  * an underline on hover with a mouse.
@@ -636,10 +675,6 @@ declare class UiTextarea implements UiControl {
636
675
  static ɵdir: _angular_core.ɵɵDirectiveDeclaration<UiTextarea, "textarea[uiTextarea]", never, { "errors": { "alias": "errors"; "required": false; "isSignal": true; }; "touched": { "alias": "touched"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
637
676
  }
638
677
 
639
- declare const TOOLTIP_SIZES: readonly ["compact", "large"];
640
- type TooltipSize = (typeof TOOLTIP_SIZES)[number];
641
- type AxisTicks = 3 | 5 | 'auto';
642
-
643
678
  type ChartPoint = {
644
679
  t: number;
645
680
  v: number;
@@ -662,10 +697,26 @@ type ChartGeometry = {
662
697
  plotTop: number;
663
698
  };
664
699
 
700
+ type TickLabel = {
701
+ text: string;
702
+ classes: string;
703
+ };
704
+
705
+ declare const TOOLTIP_SIZES: readonly ["compact", "large"];
706
+ type TooltipSize = (typeof TOOLTIP_SIZES)[number];
707
+ type AxisTicks = 3 | 5 | 'auto';
708
+
665
709
  type PlotRef = ElementRef<SVGSVGElement>;
710
+ type LineRef = ElementRef<SVGPathElement>;
711
+ type EndRef = ElementRef<SVGCircleElement>;
666
712
  /**
667
713
  * Value over time: a monotone curve with a dashed line at the starting value and a tooltip.
668
714
  *
715
+ * Axis labels are never repeated: a tick whose `axisFormat` text equals the previous drawn label is
716
+ * not rendered. The first tick always is, so only the last edge label can drop. With `axisTicks="3"`
717
+ * only the first, middle and last ticks count; with `auto` every tick counts from `sm` up and the
718
+ * three core ticks below it. Tick positions are unchanged.
719
+ *
669
720
  * @example
670
721
  * <ui-line-chart [points]="points" label="Net worth over one month" startLabel="Since" [valueFormat]="formatEur" />
671
722
  */
@@ -686,10 +737,11 @@ declare class UiLineChart {
686
737
  readonly tooltipDelta: _angular_core.InputSignalWithTransform<boolean, unknown>;
687
738
  readonly axisGap: _angular_core.InputSignal<number | "auto">;
688
739
  readonly axisTicks: _angular_core.InputSignalWithTransform<AxisTicks, string | 3 | 5>;
689
- protected readonly isCoreTick: (index: number, count: number) => boolean;
690
740
  protected readonly svgRef: _angular_core.Signal<PlotRef>;
741
+ protected readonly lineRef: _angular_core.Signal<LineRef | undefined>;
742
+ protected readonly endRef: _angular_core.Signal<EndRef | undefined>;
691
743
  protected readonly activeIndex: _angular_core.WritableSignal<number | null>;
692
- protected readonly animatedLine: _angular_core.WritableSignal<string | null>;
744
+ protected readonly held: _angular_core.WritableSignal<ChartGeometry | null>;
693
745
  protected readonly measuredSize: _angular_core.WritableSignal<{
694
746
  width: number;
695
747
  height: number;
@@ -704,8 +756,10 @@ declare class UiLineChart {
704
756
  delta: string;
705
757
  }>;
706
758
  protected readonly geometry: _angular_core.Signal<ChartGeometry | null>;
707
- protected readonly displayLine: _angular_core.Signal<string>;
759
+ protected readonly shown: _angular_core.Signal<ChartGeometry | null>;
760
+ protected readonly startFade: _angular_core.Signal<"opacity-0 duration-(--duration-exit)" | "duration-(--duration-fast)">;
708
761
  protected readonly ticks: _angular_core.Signal<PlottedPoint[]>;
762
+ protected readonly tickTexts: _angular_core.Signal<(TickLabel | null)[]>;
709
763
  protected readonly active: _angular_core.Signal<PlottedPoint | null>;
710
764
  protected readonly startLabelInfo: _angular_core.Signal<{
711
765
  label: string;
@@ -805,7 +859,7 @@ declare class UiMeter {
805
859
  readonly value: _angular_core.InputSignal<number>;
806
860
  readonly label: _angular_core.InputSignal<string>;
807
861
  readonly valueText: _angular_core.InputSignal<string>;
808
- readonly tone: _angular_core.InputSignal<2 | 4 | 3 | 1 | 5 | 6>;
862
+ readonly tone: _angular_core.InputSignal<2 | 4 | 1 | 3 | 6 | 5>;
809
863
  readonly size: _angular_core.InputSignal<"sm" | "md">;
810
864
  protected readonly trackClasses: _angular_core.Signal<string>;
811
865
  protected readonly percent: _angular_core.Signal<number>;
@@ -814,6 +868,74 @@ declare class UiMeter {
814
868
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<UiMeter, "ui-meter", never, { "value": { "alias": "value"; "required": true; "isSignal": true; }; "label": { "alias": "label"; "required": true; "isSignal": true; }; "valueText": { "alias": "valueText"; "required": true; "isSignal": true; }; "tone": { "alias": "tone"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
815
869
  }
816
870
 
871
+ /**
872
+ * Slides the remaining children of a list into place when one is removed or inserted (FLIP): it
873
+ * measures every child before and after the change, then animates the shift in `translateY` over
874
+ * `--duration-base` `--ease-out`. Pair it with `animate.leave="ui-leave-fade"` on the item so it fades
875
+ * out first. Nothing moves under `prefers-reduced-motion`: the list closes up at once.
876
+ *
877
+ * @example
878
+ * <ul uiFlipList>
879
+ * @for (key of keys(); track key.id) {
880
+ * <li uiListRow animate.leave="ui-leave-fade">...</li>
881
+ * }
882
+ * </ul>
883
+ */
884
+ declare class UiFlipList {
885
+ private readonly host;
886
+ private readonly reduced;
887
+ private readonly mutations;
888
+ private readonly resizes;
889
+ private readonly running;
890
+ private readonly watched;
891
+ private tops;
892
+ constructor();
893
+ private measure;
894
+ private interruptedOffset;
895
+ private onChange;
896
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<UiFlipList, never>;
897
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<UiFlipList, "[uiFlipList]", never, {}, {}, never, never, true, never>;
898
+ }
899
+
900
+ /**
901
+ * Draws the eye to an element that just changed: scrolls it into view when it is off screen, holds the
902
+ * `--soft` fill for 200 ms, then fades back to the element's own background over `--duration-highlight`.
903
+ * Each new non-null token plays once, so pass a fresh value per event (an object or a counter), not a
904
+ * boolean. A non-null token already present at first render plays on arrival. On a `tr` it paints the
905
+ * cells, on a `td[ui-group-cell]` the band, anywhere else the host. Under `prefers-reduced-motion` the
906
+ * scroll is instant and the fade lasts 600 ms.
907
+ *
908
+ * Give the element `scroll-margin-top` and `scroll-margin-bottom` equal to what is pinned above and
909
+ * below it (header, tab bar, action bar): a row under them counts as off screen and is scrolled out.
910
+ * For an arrival on a page opened at the element, scroll to it at once first (`scrollIntoView` in
911
+ * `afterNextRender`) and set the token afterwards: a token present at first render scrolls smoothly
912
+ * from where the page is.
913
+ *
914
+ * @example
915
+ * <a ui-row [uiHighlight]="changed() === holding.id ? token() : null" routerLink="/holdings/1">...</a>
916
+ */
917
+ declare class UiHighlight {
918
+ readonly uiHighlight: _angular_core.InputSignal<unknown>;
919
+ private readonly host;
920
+ private readonly reduced;
921
+ private cancel;
922
+ constructor();
923
+ private play;
924
+ private isVisible;
925
+ private flash;
926
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<UiHighlight, never>;
927
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<UiHighlight, "[uiHighlight]", never, { "uiHighlight": { "alias": "uiHighlight"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
928
+ }
929
+
930
+ /**
931
+ * A signal that follows the user's `prefers-reduced-motion` preference live.
932
+ * Call it in an injection context; the listener is removed with that context.
933
+ *
934
+ * @example
935
+ * protected readonly reduced = injectReducedMotion();
936
+ */
937
+ declare function injectReducedMotion(): Signal<boolean>;
938
+
817
939
  /**
818
940
  * One destination of the sidebar navigation (Portfolio, Holdings, Allocation, Accounts).
819
941
  *
@@ -837,7 +959,7 @@ type RowPadding = (typeof ROW_PADDINGS)[number];
837
959
  declare const ROW_GAPS: readonly ["default", "sm"];
838
960
  type RowGap = (typeof ROW_GAPS)[number];
839
961
  /**
840
- * Clickable row of a list: a holding, an envelope, an account.
962
+ * Clickable row of a list: a holding, an account, a search result.
841
963
  *
842
964
  * @example
843
965
  * <a ui-row [selected]="holding.id === openId()" [routerLink]="['/holdings', holding.id]">
@@ -846,12 +968,15 @@ type RowGap = (typeof ROW_GAPS)[number];
846
968
  */
847
969
  declare class UiRow {
848
970
  readonly selected: _angular_core.InputSignalWithTransform<boolean, unknown>;
971
+ readonly busy: _angular_core.InputSignalWithTransform<boolean, unknown>;
972
+ readonly unavailable: _angular_core.InputSignalWithTransform<boolean, unknown>;
849
973
  readonly size: _angular_core.InputSignal<"md" | "lg" | "xl" | "card" | "dense">;
850
974
  readonly padding: _angular_core.InputSignal<"sm" | "md" | "none">;
851
975
  readonly gap: _angular_core.InputSignal<"sm" | "default">;
852
976
  protected readonly classes: _angular_core.Signal<string>;
977
+ constructor();
853
978
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<UiRow, never>;
854
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<UiRow, "a[ui-row], button[ui-row]", never, { "selected": { "alias": "selected"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "padding": { "alias": "padding"; "required": false; "isSignal": true; }; "gap": { "alias": "gap"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
979
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<UiRow, "a[ui-row], button[ui-row]", never, { "selected": { "alias": "selected"; "required": false; "isSignal": true; }; "busy": { "alias": "busy"; "required": false; "isSignal": true; }; "unavailable": { "alias": "unavailable"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "padding": { "alias": "padding"; "required": false; "isSignal": true; }; "gap": { "alias": "gap"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
855
980
  }
856
981
  /**
857
982
  * Lays out a `ui-row` with a trailing action beside it, such as a "more" icon button: the row takes
@@ -972,6 +1097,21 @@ declare class UiSkeleton {
972
1097
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<UiSkeleton, "ui-skeleton", never, { "shape": { "alias": "shape"; "required": false; "isSignal": true; }; "lines": { "alias": "lines"; "required": false; "isSignal": true; }; "height": { "alias": "height"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
973
1098
  }
974
1099
 
1100
+ /**
1101
+ * Link in the stale tone, for a caption about data that is late or left out of a total, such as
1102
+ * "1 ligne sans cours, non comptée". Caption size, weight 500, underlined on hover, with an optional
1103
+ * trailing chevron.
1104
+ *
1105
+ * @example
1106
+ * <a uiStaleLink chevron routerLink="/holdings">1 cours en retard</a>
1107
+ */
1108
+ declare class UiStaleLink {
1109
+ readonly chevron: _angular_core.InputSignalWithTransform<boolean, unknown>;
1110
+ protected readonly classes: _angular_core.Signal<"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)" | "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) 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)]">;
1111
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<UiStaleLink, never>;
1112
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<UiStaleLink, "a[uiStaleLink]", never, { "chevron": { "alias": "chevron"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
1113
+ }
1114
+
975
1115
  /** Available stat sizes. `StatSize` is derived from this tuple. */
976
1116
  declare const STAT_SIZES: readonly ["hero", "tile"];
977
1117
  type StatSize = (typeof STAT_SIZES)[number];
@@ -1008,7 +1148,7 @@ declare class UiStat {
1008
1148
  * <input type="checkbox" uiSwitch [checked]="hidden()" (change)="toggle()" aria-label="Hide amounts" />
1009
1149
  */
1010
1150
  declare class UiSwitch {
1011
- protected readonly 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";
1151
+ protected readonly 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";
1012
1152
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<UiSwitch, never>;
1013
1153
  static ɵdir: _angular_core.ɵɵDirectiveDeclaration<UiSwitch, "input[type=checkbox][uiSwitch]", never, {}, {}, never, never, true, never>;
1014
1154
  }
@@ -1038,6 +1178,53 @@ declare class UiTab {
1038
1178
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<UiTab, "a[ui-tab]", never, { "active": { "alias": "active"; "required": false; "isSignal": true; }; }, {}, never, ["[tabIcon]", "*"], true, never>;
1039
1179
  }
1040
1180
 
1181
+ type Toast = {
1182
+ readonly id: number;
1183
+ readonly text: string;
1184
+ };
1185
+ /**
1186
+ * One-slot confirmation message queue read by `ui-toaster`.
1187
+ *
1188
+ * @example
1189
+ * inject(UiToasts).show('Achat enregistré');
1190
+ */
1191
+ declare class UiToasts {
1192
+ private readonly current;
1193
+ private sequence;
1194
+ readonly toast: _angular_core.Signal<Toast | null>;
1195
+ show(message: string): void;
1196
+ dismiss(): void;
1197
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<UiToasts, never>;
1198
+ static ɵprov: _angular_core.ɵɵInjectableDeclaration<any>;
1199
+ }
1200
+
1201
+ /**
1202
+ * Confirmation message shown by `UiToasts.show`: one at a time, no action, gone after `--toast-duration`.
1203
+ * Place it once in the app shell.
1204
+ *
1205
+ * @example
1206
+ * <ui-toaster />
1207
+ */
1208
+ declare class UiToaster {
1209
+ protected readonly toasts: UiToasts;
1210
+ protected readonly toastClasses = "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";
1211
+ private readonly host;
1212
+ private readonly hovered;
1213
+ private readonly focused;
1214
+ private lastId;
1215
+ private remaining;
1216
+ private startedAt;
1217
+ private timer;
1218
+ constructor();
1219
+ protected setHovered(value: boolean): void;
1220
+ protected setFocused(value: boolean): void;
1221
+ private resume;
1222
+ private pause;
1223
+ private stop;
1224
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<UiToaster, never>;
1225
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<UiToaster, "ui-toaster", never, {}, {}, never, never, true, never>;
1226
+ }
1227
+
1041
1228
  /** Breakpoints a secondary column can be held back until. */
1042
1229
  declare const CELL_BREAKPOINTS: readonly ["md", "lg"];
1043
1230
  type CellBreakpoint = (typeof CELL_BREAKPOINTS)[number];
@@ -1172,5 +1359,5 @@ declare class UiCellSub {
1172
1359
  static ɵdir: _angular_core.ɵɵDirectiveDeclaration<UiCellSub, "[uiCellSub]", never, { "narrow": { "alias": "narrow"; "required": false; "isSignal": true; }; "tone": { "alias": "tone"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
1173
1360
  }
1174
1361
 
1175
- 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 };
1176
- export type { AlertVariant, AmountFormatOptions, AmountMasking, AmountNumeric, AsyncAlign, AsyncState, AsyncVariant, AvatarSize, AxisTicks, BackLinkSize, BadgeSize, BadgeVariant, ButtonSize, ButtonVariant, CardPadding, CardSurface, CardVariant, CellBreakpoint, ChartPoint, ChoiceChipOption, ControlSize, ControlSurface, DeltaEmphasis, DeltaSize, DeltaWeight, DialogLayout, DialogWidth, DonutSlice, FactSize, FactSubTone, FilterChipOption, GroupSize, MeterSize, MeterTone, RowGap, RowPadding, RowSize, SegmentedOption, SegmentedSize, SkeletonShape, StatSize, SubTone, TableRow, TooltipSize, UiControlError };
1362
+ 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 };
1363
+ export type { AlertVariant, AmountFormatOptions, AmountMasking, AmountNumeric, AsyncAlign, AsyncState, AsyncVariant, AvatarSize, AxisTicks, BackLinkSize, BadgeSize, BadgeVariant, ButtonSize, ButtonVariant, CardPadding, CardSurface, CardVariant, CellBreakpoint, ChartPoint, ChoiceChipOption, ControlSize, ControlSurface, DeltaEmphasis, DeltaSize, DeltaWeight, DialogCloseReason, DialogLayout, DialogWidth, DonutSlice, FactSize, FactSubTone, FilterChipOption, GroupSize, MeterSize, MeterTone, RowGap, RowPadding, RowSize, SegmentedOption, SegmentedSize, SkeletonShape, StatSize, SubTone, TableRow, Toast, TooltipSize, UiControlError };