@joanroucoux/cairn-ui 0.6.5 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@joanroucoux/cairn-ui",
3
- "version": "0.6.5",
3
+ "version": "0.7.1",
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
 
@@ -3,9 +3,10 @@ import { InjectionToken, Signal, ElementRef } from '@angular/core';
3
3
 
4
4
  /**
5
5
  * Bar of actions fixed above the tab bar on iPhone: the primary actions of a detail screen, within
6
- * thumb reach. It sits 52px plus the safe area above the bottom edge, so it clears `ui-tab-bar`, and
6
+ * thumb reach. It sits `--tab-bar-height` (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,9 +358,15 @@ 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
  *
367
+ * `busy` keeps it open while its action runs: Escape, a click on the backdrop and a drag of the sheet do
368
+ * nothing and the cross is disabled. The owner disables its own buttons and still closes it through `open`.
369
+ *
347
370
  * @example
348
371
  * <ui-dialog heading="Buy" description="Ferrari" closeLabel="Close" width="528px" [open]="buying()" (dismissed)="buying.set(false)">
349
372
  * <p>Body</p>
@@ -356,11 +379,13 @@ declare class UiDialog {
356
379
  readonly heading: _angular_core.InputSignal<string>;
357
380
  readonly description: _angular_core.InputSignal<string | undefined>;
358
381
  readonly closeLabel: _angular_core.InputSignal<string | undefined>;
359
- readonly layout: _angular_core.InputSignal<"list" | "trade" | "form" | "confirm">;
382
+ readonly layout: _angular_core.InputSignal<"list" | "form" | "trade" | "confirm">;
360
383
  readonly truncateDescription: _angular_core.InputSignalWithTransform<boolean, unknown>;
361
384
  readonly width: _angular_core.InputSignal<"md" | "lg" | (string & {})>;
362
385
  readonly open: _angular_core.InputSignalWithTransform<boolean, unknown>;
386
+ readonly busy: _angular_core.InputSignalWithTransform<boolean, unknown>;
363
387
  readonly dismissed: _angular_core.OutputEmitterRef<void>;
388
+ readonly closed: _angular_core.OutputEmitterRef<"drag" | "escape" | "backdrop" | "cross" | "programmatic">;
364
389
  protected readonly headingId: string;
365
390
  protected readonly descriptionId: string;
366
391
  protected readonly bodyId: string;
@@ -375,11 +400,14 @@ declare class UiDialog {
375
400
  protected readonly footerClasses: _angular_core.Signal<string>;
376
401
  protected readonly classes: _angular_core.Signal<string>;
377
402
  constructor();
403
+ protected close(reason: DialogCloseReason): void;
404
+ protected onCancel(event: Event): void;
405
+ protected onKeydown(event: KeyboardEvent): void;
378
406
  protected onPointerDown(event: MouseEvent): void;
379
407
  protected onClick(event: MouseEvent): void;
380
408
  protected onNativeClose(): void;
381
409
  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>;
410
+ 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; }; "busy": { "alias": "busy"; "required": false; "isSignal": true; }; }, { "dismissed": "dismissed"; "closed": "closed"; }, never, ["*", "[dialogActions]"], true, never>;
383
411
  }
384
412
 
385
413
  type DonutSlice = {
@@ -423,7 +451,7 @@ declare class UiDonut {
423
451
  protected readonly radius = 78;
424
452
  protected readonly ringWidth = 30;
425
453
  protected strokeColor(slice: RankedSlice): string;
426
- protected strokeWidth(slice: RankedSlice): number;
454
+ protected sliceClasses(slice: RankedSlice): string;
427
455
  protected dotClasses(slice: RankedSlice): string;
428
456
  protected rowClasses(slice: RankedSlice): string;
429
457
  protected subLabel(slice: RankedSlice): string;
@@ -437,6 +465,22 @@ declare class UiDonut {
437
465
  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
466
  }
439
467
 
468
+ /**
469
+ * Centred empty state: a title, a hint and an action slot, such as a catalogue search that matches
470
+ * nothing. The padding is 24px, 48px from 64rem.
471
+ *
472
+ * @example
473
+ * <ui-empty heading="Aucun instrument ne correspond à « zzz »" hint="La recherche porte sur le nom et l'ISIN.">
474
+ * <button ui-button variant="outline" size="md" type="button">Ajouter une ligne</button>
475
+ * </ui-empty>
476
+ */
477
+ declare class UiEmpty {
478
+ readonly heading: _angular_core.InputSignal<string>;
479
+ readonly hint: _angular_core.InputSignal<string>;
480
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<UiEmpty, never>;
481
+ 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>;
482
+ }
483
+
440
484
  /**
441
485
  * Link to another site: the label followed by an external-link icon. A 44px target on touch,
442
486
  * an underline on hover with a mouse.
@@ -668,6 +712,8 @@ type TooltipSize = (typeof TOOLTIP_SIZES)[number];
668
712
  type AxisTicks = 3 | 5 | 'auto';
669
713
 
670
714
  type PlotRef = ElementRef<SVGSVGElement>;
715
+ type LineRef = ElementRef<SVGPathElement>;
716
+ type EndRef = ElementRef<SVGCircleElement>;
671
717
  /**
672
718
  * Value over time: a monotone curve with a dashed line at the starting value and a tooltip.
673
719
  *
@@ -697,8 +743,10 @@ declare class UiLineChart {
697
743
  readonly axisGap: _angular_core.InputSignal<number | "auto">;
698
744
  readonly axisTicks: _angular_core.InputSignalWithTransform<AxisTicks, string | 3 | 5>;
699
745
  protected readonly svgRef: _angular_core.Signal<PlotRef>;
746
+ protected readonly lineRef: _angular_core.Signal<LineRef | undefined>;
747
+ protected readonly endRef: _angular_core.Signal<EndRef | undefined>;
700
748
  protected readonly activeIndex: _angular_core.WritableSignal<number | null>;
701
- protected readonly animatedLine: _angular_core.WritableSignal<string | null>;
749
+ protected readonly held: _angular_core.WritableSignal<ChartGeometry | null>;
702
750
  protected readonly measuredSize: _angular_core.WritableSignal<{
703
751
  width: number;
704
752
  height: number;
@@ -713,7 +761,8 @@ declare class UiLineChart {
713
761
  delta: string;
714
762
  }>;
715
763
  protected readonly geometry: _angular_core.Signal<ChartGeometry | null>;
716
- protected readonly displayLine: _angular_core.Signal<string>;
764
+ protected readonly shown: _angular_core.Signal<ChartGeometry | null>;
765
+ protected readonly startFade: _angular_core.Signal<"opacity-0 duration-(--duration-exit)" | "duration-(--duration-fast)">;
717
766
  protected readonly ticks: _angular_core.Signal<PlottedPoint[]>;
718
767
  protected readonly tickTexts: _angular_core.Signal<(TickLabel | null)[]>;
719
768
  protected readonly active: _angular_core.Signal<PlottedPoint | null>;
@@ -815,7 +864,7 @@ declare class UiMeter {
815
864
  readonly value: _angular_core.InputSignal<number>;
816
865
  readonly label: _angular_core.InputSignal<string>;
817
866
  readonly valueText: _angular_core.InputSignal<string>;
818
- readonly tone: _angular_core.InputSignal<2 | 4 | 3 | 1 | 5 | 6>;
867
+ readonly tone: _angular_core.InputSignal<2 | 4 | 1 | 3 | 6 | 5>;
819
868
  readonly size: _angular_core.InputSignal<"sm" | "md">;
820
869
  protected readonly trackClasses: _angular_core.Signal<string>;
821
870
  protected readonly percent: _angular_core.Signal<number>;
@@ -824,6 +873,111 @@ declare class UiMeter {
824
873
  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>;
825
874
  }
826
875
 
876
+ /**
877
+ * Slides the items of a list into place when one is removed or inserted (FLIP): it measures every item
878
+ * before and after the change, from the top of the list itself, then animates the shift in `translateY`
879
+ * over `--duration-base` `--ease-out`. Pair it with `animate.leave="ui-leave-fade"` on the item so it fades
880
+ * out first. Nothing moves under `prefers-reduced-motion`: the list closes up at once.
881
+ *
882
+ * By default the items are the direct children of the list. Mark them with `uiFlipItem` instead when they
883
+ * sit deeper, for instance a page of several groups: put `uiFlipList` on the element around every group and
884
+ * `uiFlipItem` on each group heading and row, so a removal also slides the groups below. An item may sit
885
+ * inside another item (a row inside a card): it then moves by its own share and its card carries the rest.
886
+ * Only an item added or removed starts a slide; a change inside an item does not. Positions leave out the
887
+ * translate of any animation running inside the list (a slide in progress, an enter fade-up).
888
+ *
889
+ * @example
890
+ * <ul uiFlipList>
891
+ * @for (key of keys(); track key.id) {
892
+ * <li uiListRow animate.leave="ui-leave-fade">...</li>
893
+ * }
894
+ * </ul>
895
+ *
896
+ * <table uiTable uiFlipList>
897
+ * @for (group of groups(); track group.id) {
898
+ * <tbody>
899
+ * <tr uiTr group uiFlipItem>...</tr>
900
+ * @for (row of group.rows; track row.id) {
901
+ * <tr uiTr uiFlipItem animate.leave="ui-leave-fade">...</tr>
902
+ * }
903
+ * </tbody>
904
+ * }
905
+ * </table>
906
+ */
907
+ declare class UiFlipList {
908
+ private readonly host;
909
+ private readonly reduced;
910
+ private readonly mutations;
911
+ private readonly resizes;
912
+ private readonly running;
913
+ private readonly watched;
914
+ private readonly items;
915
+ private tops;
916
+ private shifts;
917
+ constructor();
918
+ private tracked;
919
+ private measure;
920
+ private onChange;
921
+ private parentItem;
922
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<UiFlipList, never>;
923
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<UiFlipList, "[uiFlipList]", never, {}, {}, never, never, true, never>;
924
+ }
925
+ /**
926
+ * Marks an element as an item of the nearest `uiFlipList`, at any depth: a group heading, a row inside a
927
+ * group, a card. Once one item is marked, the list follows its marked items only.
928
+ *
929
+ * @example
930
+ * <tr uiTr uiFlipItem animate.leave="ui-leave-fade">...</tr>
931
+ */
932
+ declare class UiFlipItem {
933
+ constructor();
934
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<UiFlipItem, never>;
935
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<UiFlipItem, "[uiFlipItem]", never, {}, {}, never, never, true, never>;
936
+ }
937
+
938
+ /**
939
+ * Draws the eye to an element that just changed: scrolls it into view when it is off screen, holds the
940
+ * `--soft` fill for 200 ms, then fades back to the element's own background over `--duration-highlight`.
941
+ * Each new non-null token plays once, so pass a fresh value per event (an object or a counter), not a
942
+ * boolean. A non-null token already present at first render plays on arrival. On a `tr` it paints the
943
+ * cells, on a `td[ui-group-cell]` the band, anywhere else the host. Under `prefers-reduced-motion` the
944
+ * scroll is instant and the fade lasts 600 ms.
945
+ *
946
+ * It starts on the next frame and, when the element is still playing a finite animation of its own (an
947
+ * `animate.enter` fade on a new row), once that animation has finished, waiting no longer than
948
+ * `--duration-base`: the element fades in, then lights up. Both can sit on the same element.
949
+ *
950
+ * Give the element `scroll-margin-top` and `scroll-margin-bottom` equal to what is pinned above and
951
+ * below it (header, tab bar, action bar): a row under them counts as off screen and is scrolled out.
952
+ * For an arrival on a page opened at the element, scroll to it at once first (`scrollIntoView` in
953
+ * `afterNextRender`) and set the token afterwards: a token present at first render scrolls smoothly
954
+ * from where the page is.
955
+ *
956
+ * @example
957
+ * <a ui-row [uiHighlight]="changed() === holding.id ? token() : null" routerLink="/holdings/1">...</a>
958
+ */
959
+ declare class UiHighlight {
960
+ readonly uiHighlight: _angular_core.InputSignal<unknown>;
961
+ private readonly host;
962
+ private readonly reduced;
963
+ private cancel;
964
+ constructor();
965
+ private play;
966
+ private isVisible;
967
+ private flash;
968
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<UiHighlight, never>;
969
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<UiHighlight, "[uiHighlight]", never, { "uiHighlight": { "alias": "uiHighlight"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
970
+ }
971
+
972
+ /**
973
+ * A signal that follows the user's `prefers-reduced-motion` preference live.
974
+ * Call it in an injection context; the listener is removed with that context.
975
+ *
976
+ * @example
977
+ * protected readonly reduced = injectReducedMotion();
978
+ */
979
+ declare function injectReducedMotion(): Signal<boolean>;
980
+
827
981
  /**
828
982
  * One destination of the sidebar navigation (Portfolio, Holdings, Allocation, Accounts).
829
983
  *
@@ -847,7 +1001,7 @@ type RowPadding = (typeof ROW_PADDINGS)[number];
847
1001
  declare const ROW_GAPS: readonly ["default", "sm"];
848
1002
  type RowGap = (typeof ROW_GAPS)[number];
849
1003
  /**
850
- * Clickable row of a list: a holding, an envelope, an account.
1004
+ * Clickable row of a list: a holding, an account, a search result.
851
1005
  *
852
1006
  * @example
853
1007
  * <a ui-row [selected]="holding.id === openId()" [routerLink]="['/holdings', holding.id]">
@@ -856,12 +1010,15 @@ type RowGap = (typeof ROW_GAPS)[number];
856
1010
  */
857
1011
  declare class UiRow {
858
1012
  readonly selected: _angular_core.InputSignalWithTransform<boolean, unknown>;
1013
+ readonly busy: _angular_core.InputSignalWithTransform<boolean, unknown>;
1014
+ readonly unavailable: _angular_core.InputSignalWithTransform<boolean, unknown>;
859
1015
  readonly size: _angular_core.InputSignal<"md" | "lg" | "xl" | "card" | "dense">;
860
1016
  readonly padding: _angular_core.InputSignal<"sm" | "md" | "none">;
861
1017
  readonly gap: _angular_core.InputSignal<"sm" | "default">;
862
1018
  protected readonly classes: _angular_core.Signal<string>;
1019
+ constructor();
863
1020
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<UiRow, never>;
864
- 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>;
1021
+ 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>;
865
1022
  }
866
1023
  /**
867
1024
  * Lays out a `ui-row` with a trailing action beside it, such as a "more" icon button: the row takes
@@ -982,6 +1139,21 @@ declare class UiSkeleton {
982
1139
  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>;
983
1140
  }
984
1141
 
1142
+ /**
1143
+ * Link in the stale tone, for a caption about data that is late or left out of a total, such as
1144
+ * "1 ligne sans cours, non comptée". Caption size, weight 500, underlined on hover, with an optional
1145
+ * trailing chevron.
1146
+ *
1147
+ * @example
1148
+ * <a uiStaleLink chevron routerLink="/holdings">1 cours en retard</a>
1149
+ */
1150
+ declare class UiStaleLink {
1151
+ readonly chevron: _angular_core.InputSignalWithTransform<boolean, unknown>;
1152
+ protected readonly classes: _angular_core.Signal<"relative 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) before:absolute before:inset-x-0 before:top-1/2 before:h-11 pointer-fine:before:h-10 before:-translate-y-1/2 before:content-['']" | "relative 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) before:absolute before:inset-x-0 before:top-1/2 before:h-11 pointer-fine:before:h-10 before:-translate-y-1/2 before:content-[''] 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)]">;
1153
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<UiStaleLink, never>;
1154
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<UiStaleLink, "a[uiStaleLink]", never, { "chevron": { "alias": "chevron"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
1155
+ }
1156
+
985
1157
  /** Available stat sizes. `StatSize` is derived from this tuple. */
986
1158
  declare const STAT_SIZES: readonly ["hero", "tile"];
987
1159
  type StatSize = (typeof STAT_SIZES)[number];
@@ -1018,13 +1190,15 @@ declare class UiStat {
1018
1190
  * <input type="checkbox" uiSwitch [checked]="hidden()" (change)="toggle()" aria-label="Hide amounts" />
1019
1191
  */
1020
1192
  declare class UiSwitch {
1021
- 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";
1193
+ 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";
1022
1194
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<UiSwitch, never>;
1023
1195
  static ɵdir: _angular_core.ɵɵDirectiveDeclaration<UiSwitch, "input[type=checkbox][uiSwitch]", never, {}, {}, never, never, true, never>;
1024
1196
  }
1025
1197
 
1026
1198
  /**
1027
- * Bottom navigation bar for iPhone: 52px of tabs plus the home indicator's safe area.
1199
+ * Bottom navigation bar for iPhone: 52px of tabs plus the home indicator's safe area. While it is on the
1200
+ * page it publishes that height as `--tab-bar-height` on the root element, which `ui-action-bar` and
1201
+ * `ui-toaster` sit above; use it rather than restating 52px for anything that must clear the bar.
1028
1202
  *
1029
1203
  * @example
1030
1204
  * <nav ui-tab-bar>
@@ -1032,6 +1206,7 @@ declare class UiSwitch {
1032
1206
  * </nav>
1033
1207
  */
1034
1208
  declare class UiTabBar {
1209
+ constructor();
1035
1210
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<UiTabBar, never>;
1036
1211
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<UiTabBar, "nav[ui-tab-bar]", never, {}, {}, never, ["*"], true, never>;
1037
1212
  }
@@ -1048,6 +1223,53 @@ declare class UiTab {
1048
1223
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<UiTab, "a[ui-tab]", never, { "active": { "alias": "active"; "required": false; "isSignal": true; }; }, {}, never, ["[tabIcon]", "*"], true, never>;
1049
1224
  }
1050
1225
 
1226
+ type Toast = {
1227
+ readonly id: number;
1228
+ readonly text: string;
1229
+ };
1230
+ /**
1231
+ * One-slot confirmation message queue read by `ui-toaster`.
1232
+ *
1233
+ * @example
1234
+ * inject(UiToasts).show('Achat enregistré');
1235
+ */
1236
+ declare class UiToasts {
1237
+ private readonly current;
1238
+ private sequence;
1239
+ readonly toast: _angular_core.Signal<Toast | null>;
1240
+ show(message: string): void;
1241
+ dismiss(): void;
1242
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<UiToasts, never>;
1243
+ static ɵprov: _angular_core.ɵɵInjectableDeclaration<any>;
1244
+ }
1245
+
1246
+ /**
1247
+ * Confirmation message shown by `UiToasts.show`: one at a time, no action, gone after `--toast-duration`.
1248
+ * Place it once in the app shell.
1249
+ *
1250
+ * @example
1251
+ * <ui-toaster />
1252
+ */
1253
+ declare class UiToaster {
1254
+ protected readonly toasts: UiToasts;
1255
+ 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";
1256
+ private readonly host;
1257
+ private readonly hovered;
1258
+ private readonly focused;
1259
+ private lastId;
1260
+ private remaining;
1261
+ private startedAt;
1262
+ private timer;
1263
+ constructor();
1264
+ protected setHovered(value: boolean): void;
1265
+ protected setFocused(value: boolean): void;
1266
+ private resume;
1267
+ private pause;
1268
+ private stop;
1269
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<UiToaster, never>;
1270
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<UiToaster, "ui-toaster", never, {}, {}, never, never, true, never>;
1271
+ }
1272
+
1051
1273
  /** Breakpoints a secondary column can be held back until. */
1052
1274
  declare const CELL_BREAKPOINTS: readonly ["md", "lg"];
1053
1275
  type CellBreakpoint = (typeof CELL_BREAKPOINTS)[number];
@@ -1182,5 +1404,5 @@ declare class UiCellSub {
1182
1404
  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>;
1183
1405
  }
1184
1406
 
1185
- 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 };
1186
- 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 };
1407
+ 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, UiFlipItem, UiFlipList, UiGroup, UiGroupCell, UiHighlight, UiInput, UiLineChart, UiListRow, UiMenu, UiMenuItem, UiMenuTrigger, UiMeter, UiNavItem, UiRow, UiRowAction, UiRowItem, UiRowLink, UiRowTile, UiSegmented, UiSelect, UiSkeleton, UiStaleLink, UiStat, UiSwitch, UiTab, UiTabBar, UiTable, UiTd, UiTextarea, UiTh, UiToaster, UiToasts, UiTr, delayedState, formatAmount, injectReducedMotion };
1408
+ 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 };