@joanroucoux/cairn-ui 0.7.0 → 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.
@@ -4,10 +4,10 @@ import { inject, ElementRef, DestroyRef, afterNextRender, Component, input, bool
4
4
  import { scaleLinear } from 'd3-scale';
5
5
  import { curveMonotoneX, line } from 'd3-shape';
6
6
 
7
- const HEIGHT_PROPERTY = '--action-bar-height';
7
+ const HEIGHT_PROPERTY$1 = '--action-bar-height';
8
8
  /**
9
9
  * Bar of actions fixed above the tab bar on iPhone: the primary actions of a detail screen, within
10
- * thumb reach. It sits 52px plus the safe area above the bottom edge, so it clears `ui-tab-bar`, and
10
+ * thumb reach. It sits `--tab-bar-height` (52px plus the safe area) above the bottom edge, so it clears `ui-tab-bar`, and
11
11
  * it is hidden from `64rem`, where the same actions live in the page itself. Its children share the
12
12
  * width equally. While it is on the page it publishes its height as `--action-bar-height` on the
13
13
  * root element, which `ui-toaster` adds to its offset so a confirmation never covers the actions.
@@ -23,7 +23,7 @@ class UiActionBar {
23
23
  const host = inject(ElementRef).nativeElement;
24
24
  const root = inject(DOCUMENT).documentElement;
25
25
  const destroyRef = inject(DestroyRef);
26
- const publish = () => root.style.setProperty(HEIGHT_PROPERTY, `${host.offsetHeight}px`);
26
+ const publish = () => root.style.setProperty(HEIGHT_PROPERTY$1, `${host.offsetHeight}px`);
27
27
  afterNextRender(() => {
28
28
  publish();
29
29
  if (typeof ResizeObserver !== 'undefined') {
@@ -32,10 +32,10 @@ class UiActionBar {
32
32
  destroyRef.onDestroy(() => observer.disconnect());
33
33
  }
34
34
  });
35
- destroyRef.onDestroy(() => root.style.removeProperty(HEIGHT_PROPERTY));
35
+ destroyRef.onDestroy(() => root.style.removeProperty(HEIGHT_PROPERTY$1));
36
36
  }
37
37
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiActionBar, deps: [], target: i0.ɵɵFactoryTarget.Component });
38
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: UiActionBar, isStandalone: true, selector: "ui-action-bar", host: { classAttribute: "fixed inset-x-0 bottom-[calc(52px+env(safe-area-inset-bottom))] z-40 grid grid-flow-col auto-cols-fr gap-2 bg-(--background) px-(--gutter) py-3 shadow-[0_-1px_0_var(--hairline)] lg:hidden" }, ngImport: i0, template: '<ng-content />', isInline: true });
38
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: UiActionBar, isStandalone: true, selector: "ui-action-bar", host: { classAttribute: "fixed inset-x-0 bottom-[var(--tab-bar-height,calc(52px+env(safe-area-inset-bottom)))] z-40 grid grid-flow-col auto-cols-fr gap-2 bg-(--background) px-(--gutter) py-3 shadow-[0_-1px_0_var(--hairline)] lg:hidden" }, ngImport: i0, template: '<ng-content />', isInline: true });
39
39
  }
40
40
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiActionBar, decorators: [{
41
41
  type: Component,
@@ -43,7 +43,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
43
43
  selector: 'ui-action-bar',
44
44
  template: '<ng-content />',
45
45
  host: {
46
- class: 'fixed inset-x-0 bottom-[calc(52px+env(safe-area-inset-bottom))] z-40 grid grid-flow-col auto-cols-fr gap-2 bg-(--background) px-(--gutter) py-3 shadow-[0_-1px_0_var(--hairline)] lg:hidden',
46
+ class: 'fixed inset-x-0 bottom-[var(--tab-bar-height,calc(52px+env(safe-area-inset-bottom)))] z-40 grid grid-flow-col auto-cols-fr gap-2 bg-(--background) px-(--gutter) py-3 shadow-[0_-1px_0_var(--hairline)] lg:hidden',
47
47
  },
48
48
  }]
49
49
  }], ctorParameters: () => [] });
@@ -1441,11 +1441,13 @@ class SheetDrag {
1441
1441
  #panel;
1442
1442
  #grips;
1443
1443
  #dismiss;
1444
+ #enabled;
1444
1445
  #gesture = null;
1445
- constructor(panel, grips, dismiss) {
1446
+ constructor(panel, grips, dismiss, enabled) {
1446
1447
  this.#panel = panel;
1447
1448
  this.#grips = grips;
1448
1449
  this.#dismiss = dismiss;
1450
+ this.#enabled = enabled;
1449
1451
  for (const grip of grips) {
1450
1452
  grip.addEventListener('pointerdown', this.#onDown);
1451
1453
  grip.addEventListener('pointermove', this.#onMove);
@@ -1468,7 +1470,7 @@ class SheetDrag {
1468
1470
  }
1469
1471
  #onDown = (event) => {
1470
1472
  const grip = event.currentTarget;
1471
- if (event.button !== 0 || !isSheet() || event.target.closest(CONTROLS)) {
1473
+ if (event.button !== 0 || !this.#enabled() || !isSheet() || event.target.closest(CONTROLS)) {
1472
1474
  return;
1473
1475
  }
1474
1476
  grip.setPointerCapture(event.pointerId);
@@ -1602,6 +1604,9 @@ const nextId$3 = (() => {
1602
1604
  /**
1603
1605
  * Modal dialog on the native <dialog>: a bottom sheet under `64rem`, centered above it.
1604
1606
  *
1607
+ * `busy` keeps it open while its action runs: Escape, a click on the backdrop and a drag of the sheet do
1608
+ * nothing and the cross is disabled. The owner disables its own buttons and still closes it through `open`.
1609
+ *
1605
1610
  * @example
1606
1611
  * <ui-dialog heading="Buy" description="Ferrari" closeLabel="Close" width="528px" [open]="buying()" (dismissed)="buying.set(false)">
1607
1612
  * <p>Body</p>
@@ -1622,6 +1627,7 @@ class UiDialog {
1622
1627
  width = input('lg', /* @ts-ignore */
1623
1628
  ...(ngDevMode ? [{ debugName: "width" }] : /* istanbul ignore next */ []));
1624
1629
  open = input(false, { ...(ngDevMode ? { debugName: "open" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1630
+ busy = input(false, { ...(ngDevMode ? { debugName: "busy" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1625
1631
  dismissed = output();
1626
1632
  closed = output();
1627
1633
  headingId = nextId$3();
@@ -1647,7 +1653,7 @@ class UiDialog {
1647
1653
  ...(ngDevMode ? [{ debugName: "headerClasses" }] : /* istanbul ignore next */ []));
1648
1654
  titleClasses = computed(() => this.spec().title, /* @ts-ignore */
1649
1655
  ...(ngDevMode ? [{ debugName: "titleClasses" }] : /* istanbul ignore next */ []));
1650
- crossClasses = computed(() => `rounded-pill lg:rounded-control grid size-11 flex-none cursor-pointer place-items-center text-(--muted-foreground) transition-[scale,background-color,color] [transition-duration:var(--duration-press),var(--duration-fast),var(--duration-fast)] ease-out outline-none focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-(--ring) active:scale-(--press-scale) active:bg-(--soft) lg:size-9 lg:hover:bg-(--glow) lg:hover:text-(--foreground) ${this.spec().cross}`, /* @ts-ignore */
1656
+ crossClasses = computed(() => `rounded-pill lg:rounded-control grid size-11 flex-none cursor-pointer place-items-center text-(--muted-foreground) transition-[scale,background-color,color] [transition-duration:var(--duration-press),var(--duration-fast),var(--duration-fast)] ease-out outline-none focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-(--ring) active:scale-(--press-scale) active:bg-(--soft) disabled:pointer-events-none disabled:opacity-40 lg:size-9 lg:hover:bg-(--glow) lg:hover:text-(--foreground) ${this.spec().cross}`, /* @ts-ignore */
1651
1657
  ...(ngDevMode ? [{ debugName: "crossClasses" }] : /* istanbul ignore next */ []));
1652
1658
  iconClasses = computed(() => this.spec().icon, /* @ts-ignore */
1653
1659
  ...(ngDevMode ? [{ debugName: "iconClasses" }] : /* istanbul ignore next */ []));
@@ -1661,7 +1667,7 @@ class UiDialog {
1661
1667
  afterNextRender(() => {
1662
1668
  const host = this.#host.nativeElement;
1663
1669
  const grips = ['[data-dialog-handle]', '[data-dialog-header]'].map((grip) => host.querySelector(grip));
1664
- this.#drag = new SheetDrag(this.#dialog, grips, () => this.close('drag'));
1670
+ this.#drag = new SheetDrag(this.#dialog, grips, () => this.close('drag'), () => !this.busy());
1665
1671
  destroyRef.onDestroy(() => {
1666
1672
  this.#drag?.destroy();
1667
1673
  this.#cancelExit();
@@ -1703,13 +1709,20 @@ class UiDialog {
1703
1709
  return;
1704
1710
  }
1705
1711
  event.preventDefault();
1706
- this.close('escape');
1712
+ if (!this.busy()) {
1713
+ this.close('escape');
1714
+ }
1715
+ }
1716
+ onKeydown(event) {
1717
+ if (event.key === 'Escape' && this.busy()) {
1718
+ event.preventDefault();
1719
+ }
1707
1720
  }
1708
1721
  onPointerDown(event) {
1709
1722
  this.#pressedOnBackdrop = event.target === this.#dialog && this.#outside(event);
1710
1723
  }
1711
1724
  onClick(event) {
1712
- if (this.#pressedOnBackdrop && event.target === this.#dialog && this.#outside(event)) {
1725
+ if (this.#pressedOnBackdrop && !this.busy() && event.target === this.#dialog && this.#outside(event)) {
1713
1726
  this.close('backdrop');
1714
1727
  }
1715
1728
  this.#pressedOnBackdrop = false;
@@ -1722,11 +1735,16 @@ class UiDialog {
1722
1735
  return (event.clientX < rect.left || event.clientX > rect.right || event.clientY < rect.top || event.clientY > rect.bottom);
1723
1736
  }
1724
1737
  onNativeClose() {
1725
- const reason = this.#reason ?? 'escape';
1738
+ const asked = this.#reason;
1726
1739
  this.#reason = null;
1727
1740
  if (this.#dialog.open) {
1728
1741
  return;
1729
1742
  }
1743
+ if (asked === null && this.busy()) {
1744
+ this.#dialog.showModal();
1745
+ return;
1746
+ }
1747
+ const reason = asked ?? 'escape';
1730
1748
  if (reason !== 'programmatic') {
1731
1749
  this.dismissed.emit();
1732
1750
  }
@@ -1741,9 +1759,10 @@ class UiDialog {
1741
1759
  });
1742
1760
  }
1743
1761
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
1744
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: UiDialog, isStandalone: true, selector: "ui-dialog", inputs: { heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired: true, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null }, closeLabel: { classPropertyName: "closeLabel", publicName: "closeLabel", isSignal: true, isRequired: false, transformFunction: null }, layout: { classPropertyName: "layout", publicName: "layout", isSignal: true, isRequired: false, transformFunction: null }, truncateDescription: { classPropertyName: "truncateDescription", publicName: "truncateDescription", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { dismissed: "dismissed", closed: "closed" }, host: { listeners: { "pointerdown": "onPointerDown($event)", "click": "onClick($event)" } }, ngImport: i0, template: `
1762
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: UiDialog, isStandalone: true, selector: "ui-dialog", inputs: { heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired: true, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null }, closeLabel: { classPropertyName: "closeLabel", publicName: "closeLabel", isSignal: true, isRequired: false, transformFunction: null }, layout: { classPropertyName: "layout", publicName: "layout", isSignal: true, isRequired: false, transformFunction: null }, truncateDescription: { classPropertyName: "truncateDescription", publicName: "truncateDescription", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, busy: { classPropertyName: "busy", publicName: "busy", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { dismissed: "dismissed", closed: "closed" }, host: { listeners: { "pointerdown": "onPointerDown($event)", "click": "onClick($event)", "keydown": "onKeydown($event)" } }, ngImport: i0, template: `
1745
1763
  <dialog
1746
1764
  #dlg
1765
+ [attr.aria-busy]="busy() || null"
1747
1766
  [attr.aria-describedby]="describedBy()"
1748
1767
  [attr.aria-labelledby]="headingId"
1749
1768
  [attr.role]="layout() === 'confirm' ? 'alertdialog' : 'dialog'"
@@ -1777,7 +1796,13 @@ class UiDialog {
1777
1796
  </div>
1778
1797
 
1779
1798
  @if (closeLabel()) {
1780
- <button type="button" [attr.aria-label]="closeLabel()" [class]="crossClasses()" (click)="close('cross')">
1799
+ <button
1800
+ type="button"
1801
+ [attr.aria-label]="closeLabel()"
1802
+ [class]="crossClasses()"
1803
+ [disabled]="busy()"
1804
+ (click)="close('cross')"
1805
+ >
1781
1806
  <svg
1782
1807
  aria-hidden="true"
1783
1808
  class="block size-[22px] fill-none stroke-current stroke-[1.75]"
@@ -1815,9 +1840,14 @@ class UiDialog {
1815
1840
  }
1816
1841
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiDialog, decorators: [{
1817
1842
  type: Component,
1818
- args: [{ selector: 'ui-dialog', host: { '(pointerdown)': 'onPointerDown($event)', '(click)': 'onClick($event)' }, template: `
1843
+ args: [{ selector: 'ui-dialog', host: {
1844
+ '(pointerdown)': 'onPointerDown($event)',
1845
+ '(click)': 'onClick($event)',
1846
+ '(keydown)': 'onKeydown($event)',
1847
+ }, template: `
1819
1848
  <dialog
1820
1849
  #dlg
1850
+ [attr.aria-busy]="busy() || null"
1821
1851
  [attr.aria-describedby]="describedBy()"
1822
1852
  [attr.aria-labelledby]="headingId"
1823
1853
  [attr.role]="layout() === 'confirm' ? 'alertdialog' : 'dialog'"
@@ -1851,7 +1881,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
1851
1881
  </div>
1852
1882
 
1853
1883
  @if (closeLabel()) {
1854
- <button type="button" [attr.aria-label]="closeLabel()" [class]="crossClasses()" (click)="close('cross')">
1884
+ <button
1885
+ type="button"
1886
+ [attr.aria-label]="closeLabel()"
1887
+ [class]="crossClasses()"
1888
+ [disabled]="busy()"
1889
+ (click)="close('cross')"
1890
+ >
1855
1891
  <svg
1856
1892
  aria-hidden="true"
1857
1893
  class="block size-[22px] fill-none stroke-current stroke-[1.75]"
@@ -1886,7 +1922,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
1886
1922
  </div>
1887
1923
  </dialog>
1888
1924
  `, 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"] }] } });
1925
+ }], ctorParameters: () => [], propDecorators: { heading: [{ type: i0.Input, args: [{ isSignal: true, alias: "heading", required: true }] }], description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }], closeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeLabel", required: false }] }], layout: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout", required: false }] }], truncateDescription: [{ type: i0.Input, args: [{ isSignal: true, alias: "truncateDescription", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }], busy: [{ type: i0.Input, args: [{ isSignal: true, alias: "busy", required: false }] }], dismissed: [{ type: i0.Output, args: ["dismissed"] }], closed: [{ type: i0.Output, args: ["closed"] }] } });
1890
1926
 
1891
1927
  const RADIUS = 78;
1892
1928
  const RING_WIDTH = 30;
@@ -3851,6 +3887,40 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
3851
3887
  }]
3852
3888
  }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], valueText: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueText", required: true }] }], tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }] } });
3853
3889
 
3890
+ class FlipItems {
3891
+ items = new Set();
3892
+ destroyed = new Set();
3893
+ used = false;
3894
+ get marked() {
3895
+ return this.used;
3896
+ }
3897
+ add(item) {
3898
+ this.used = true;
3899
+ this.items.add(item);
3900
+ }
3901
+ remove(item) {
3902
+ this.destroyed.add(item);
3903
+ }
3904
+ inside(host) {
3905
+ const inside = [];
3906
+ for (const item of this.items) {
3907
+ if (host.contains(item)) {
3908
+ inside.push(item);
3909
+ }
3910
+ else if (this.destroyed.has(item)) {
3911
+ this.items.delete(item);
3912
+ this.destroyed.delete(item);
3913
+ }
3914
+ }
3915
+ return inside;
3916
+ }
3917
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FlipItems, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3918
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FlipItems });
3919
+ }
3920
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FlipItems, decorators: [{
3921
+ type: Injectable
3922
+ }] });
3923
+
3854
3924
  function readDuration$1(element, name, fallback) {
3855
3925
  const raw = getComputedStyle(element).getPropertyValue(name).trim();
3856
3926
  const value = parseFloat(raw);
@@ -3863,31 +3933,57 @@ function readEasing(element, name, fallback) {
3863
3933
  return getComputedStyle(element).getPropertyValue(name).trim() || fallback;
3864
3934
  }
3865
3935
 
3936
+ function translateY(element) {
3937
+ const matrix = /^matrix\((.+)\)$/.exec(getComputedStyle(element).transform);
3938
+ return matrix ? parseFloat(matrix[1].split(',')[5]) : 0;
3939
+ }
3866
3940
  /**
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
3941
+ * Slides the items of a list into place when one is removed or inserted (FLIP): it measures every item
3942
+ * before and after the change, from the top of the list itself, then animates the shift in `translateY`
3943
+ * over `--duration-base` `--ease-out`. Pair it with `animate.leave="ui-leave-fade"` on the item so it fades
3870
3944
  * out first. Nothing moves under `prefers-reduced-motion`: the list closes up at once.
3871
3945
  *
3946
+ * By default the items are the direct children of the list. Mark them with `uiFlipItem` instead when they
3947
+ * sit deeper, for instance a page of several groups: put `uiFlipList` on the element around every group and
3948
+ * `uiFlipItem` on each group heading and row, so a removal also slides the groups below. An item may sit
3949
+ * inside another item (a row inside a card): it then moves by its own share and its card carries the rest.
3950
+ * Only an item added or removed starts a slide; a change inside an item does not. Positions leave out the
3951
+ * translate of any animation running inside the list (a slide in progress, an enter fade-up).
3952
+ *
3872
3953
  * @example
3873
3954
  * <ul uiFlipList>
3874
3955
  * @for (key of keys(); track key.id) {
3875
3956
  * <li uiListRow animate.leave="ui-leave-fade">...</li>
3876
3957
  * }
3877
3958
  * </ul>
3959
+ *
3960
+ * <table uiTable uiFlipList>
3961
+ * @for (group of groups(); track group.id) {
3962
+ * <tbody>
3963
+ * <tr uiTr group uiFlipItem>...</tr>
3964
+ * @for (row of group.rows; track row.id) {
3965
+ * <tr uiTr uiFlipItem animate.leave="ui-leave-fade">...</tr>
3966
+ * }
3967
+ * </tbody>
3968
+ * }
3969
+ * </table>
3878
3970
  */
3879
3971
  class UiFlipList {
3880
3972
  host = inject(ElementRef).nativeElement;
3881
3973
  reduced = injectReducedMotion();
3882
3974
  mutations = new MutationObserver(() => this.onChange());
3883
- resizes = new ResizeObserver(() => this.measure());
3975
+ resizes = new ResizeObserver(() => {
3976
+ this.tops = this.measure();
3977
+ });
3884
3978
  running = new Map();
3885
3979
  watched = new Set();
3980
+ items = inject(FlipItems, { self: true });
3886
3981
  tops = new Map();
3982
+ shifts = new Map();
3887
3983
  constructor() {
3888
3984
  afterNextRender(() => {
3889
- this.measure();
3890
- this.mutations.observe(this.host, { childList: true });
3985
+ this.tops = this.measure();
3986
+ this.mutations.observe(this.host, { childList: true, subtree: true });
3891
3987
  this.resizes.observe(this.host);
3892
3988
  });
3893
3989
  inject(DestroyRef).onDestroy(() => {
@@ -3895,57 +3991,118 @@ class UiFlipList {
3895
3991
  this.resizes.disconnect();
3896
3992
  });
3897
3993
  }
3994
+ tracked() {
3995
+ if (!this.items.marked) {
3996
+ return Array.from(this.host.children).filter((child) => child instanceof HTMLElement);
3997
+ }
3998
+ return this.items.inside(this.host);
3999
+ }
3898
4000
  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);
4001
+ const elements = this.tracked();
4002
+ const origin = this.host.getBoundingClientRect().top - this.host.scrollTop;
4003
+ const tops = new Map(elements.map((element) => [element, element.getBoundingClientRect().top - origin]));
4004
+ this.shifts = new Map();
4005
+ for (const animation of this.host.getAnimations({ subtree: true })) {
4006
+ const target = animation.effect.target;
4007
+ if (target !== this.host) {
4008
+ this.shifts.set(target, translateY(target));
3905
4009
  }
3906
4010
  }
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);
4011
+ for (const [moving, shift] of this.shifts) {
4012
+ for (const [element, top] of tops) {
4013
+ if (moving.contains(element)) {
4014
+ tops.set(element, top - shift);
4015
+ }
3912
4016
  }
3913
4017
  }
3914
- }
3915
- interruptedOffset(child) {
3916
- const running = this.running.get(child);
3917
- if (!running) {
3918
- return 0;
4018
+ for (const element of elements) {
4019
+ if (!this.watched.has(element)) {
4020
+ this.watched.add(element);
4021
+ this.resizes.observe(element);
4022
+ }
4023
+ }
4024
+ for (const element of this.watched) {
4025
+ if (!tops.has(element)) {
4026
+ this.watched.delete(element);
4027
+ this.running.delete(element);
4028
+ this.resizes.unobserve(element);
4029
+ }
3919
4030
  }
3920
- const shown = child.getBoundingClientRect().top;
3921
- running.cancel();
3922
- this.running.delete(child);
3923
- return shown - child.getBoundingClientRect().top;
4031
+ return tops;
3924
4032
  }
3925
4033
  onChange() {
3926
4034
  const before = this.tops;
3927
- this.measure();
3928
- if (this.reduced()) {
4035
+ const after = this.measure();
4036
+ this.tops = after;
4037
+ const changed = after.size !== before.size || Array.from(after.keys()).some((element) => !before.has(element));
4038
+ if (this.reduced() || !changed) {
3929
4039
  return;
3930
4040
  }
3931
4041
  const duration = readDuration$1(this.host, '--duration-base', 260);
3932
4042
  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) {
4043
+ const moved = (element) => (before.get(element) ?? 0) - (after.get(element) ?? 0);
4044
+ for (const [element] of after) {
4045
+ if (!before.has(element)) {
4046
+ continue;
4047
+ }
4048
+ const own = moved(element) - moved(this.parentItem(element, before, after));
4049
+ if (Math.abs(own) < 0.5) {
3936
4050
  continue;
3937
4051
  }
3938
- const from = previous - top + this.interruptedOffset(child);
3939
- this.running.set(child, child.animate([{ transform: `translateY(${from}px)` }, { transform: 'translateY(0)' }], { duration, easing }));
4052
+ const from = own + (this.shifts.get(element) ?? 0);
4053
+ this.running.get(element)?.cancel();
4054
+ const animation = element.animate([{ transform: `translateY(${from}px)` }, { transform: 'translateY(0)' }], {
4055
+ duration,
4056
+ easing,
4057
+ });
4058
+ animation.onfinish = () => {
4059
+ if (this.running.get(element) === animation) {
4060
+ this.running.delete(element);
4061
+ }
4062
+ };
4063
+ this.running.set(element, animation);
4064
+ }
4065
+ }
4066
+ parentItem(element, before, after) {
4067
+ let node = element.parentElement;
4068
+ while (node !== this.host && !(before.has(node) && after.has(node))) {
4069
+ node = node.parentElement;
3940
4070
  }
4071
+ return node;
3941
4072
  }
3942
4073
  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 });
4074
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.7", type: UiFlipList, isStandalone: true, selector: "[uiFlipList]", providers: [FlipItems], ngImport: i0 });
3944
4075
  }
3945
4076
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiFlipList, decorators: [{
3946
4077
  type: Directive,
3947
4078
  args: [{
3948
4079
  selector: '[uiFlipList]',
4080
+ providers: [FlipItems],
4081
+ }]
4082
+ }], ctorParameters: () => [] });
4083
+ /**
4084
+ * Marks an element as an item of the nearest `uiFlipList`, at any depth: a group heading, a row inside a
4085
+ * group, a card. Once one item is marked, the list follows its marked items only.
4086
+ *
4087
+ * @example
4088
+ * <tr uiTr uiFlipItem animate.leave="ui-leave-fade">...</tr>
4089
+ */
4090
+ class UiFlipItem {
4091
+ constructor() {
4092
+ const items = inject(FlipItems, { optional: true });
4093
+ if (items) {
4094
+ const element = inject(ElementRef).nativeElement;
4095
+ items.add(element);
4096
+ inject(DestroyRef).onDestroy(() => items.remove(element));
4097
+ }
4098
+ }
4099
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiFlipItem, deps: [], target: i0.ɵɵFactoryTarget.Directive });
4100
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.7", type: UiFlipItem, isStandalone: true, selector: "[uiFlipItem]", ngImport: i0 });
4101
+ }
4102
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiFlipItem, decorators: [{
4103
+ type: Directive,
4104
+ args: [{
4105
+ selector: '[uiFlipItem]',
3949
4106
  }]
3950
4107
  }], ctorParameters: () => [] });
3951
4108
 
@@ -3969,6 +4126,10 @@ function paintedElements(host) {
3969
4126
  * cells, on a `td[ui-group-cell]` the band, anywhere else the host. Under `prefers-reduced-motion` the
3970
4127
  * scroll is instant and the fade lasts 600 ms.
3971
4128
  *
4129
+ * It starts on the next frame and, when the element is still playing a finite animation of its own (an
4130
+ * `animate.enter` fade on a new row), once that animation has finished, waiting no longer than
4131
+ * `--duration-base`: the element fades in, then lights up. Both can sit on the same element.
4132
+ *
3972
4133
  * Give the element `scroll-margin-top` and `scroll-margin-bottom` equal to what is pinned above and
3973
4134
  * below it (header, tab bar, action bar): a row under them counts as off screen and is scrolled out.
3974
4135
  * For an arrival on a page opened at the element, scroll to it at once first (`scrollIntoView` in
@@ -4045,15 +4206,38 @@ class UiHighlight {
4045
4206
  return rect.top >= top && rect.bottom <= window.innerHeight - bottom;
4046
4207
  }
4047
4208
  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());
4209
+ const painted = paintedElements(this.host);
4210
+ let animations = [];
4211
+ let timer;
4212
+ const paint = () => {
4213
+ const fade = readDuration$1(this.host, '--duration-highlight', 1200);
4214
+ const easing = readEasing(this.host, '--ease-out', 'ease-out');
4215
+ const total = HOLD_MS + fade;
4216
+ animations = painted.map((element) => element.animate([
4217
+ { backgroundColor: 'var(--soft)', offset: 0 },
4218
+ { backgroundColor: 'var(--soft)', offset: HOLD_MS / total, easing },
4219
+ { offset: 1 },
4220
+ ], { duration: total }));
4221
+ };
4222
+ const frame = requestAnimationFrame(() => {
4223
+ const remaining = Array.from(new Set([this.host, ...painted]))
4224
+ .flatMap((element) => element.getAnimations())
4225
+ .map((animation) => {
4226
+ const timing = animation.effect.getComputedTiming();
4227
+ return Number(timing.endTime) - Number(timing.localTime ?? 0);
4228
+ })
4229
+ .filter((left) => Number.isFinite(left));
4230
+ if (remaining.length === 0) {
4231
+ paint();
4232
+ return;
4233
+ }
4234
+ timer = setTimeout(paint, Math.min(Math.max(...remaining), readDuration$1(this.host, '--duration-base', 260)));
4235
+ });
4236
+ return () => {
4237
+ cancelAnimationFrame(frame);
4238
+ clearTimeout(timer);
4239
+ animations.forEach((animation) => animation.cancel());
4240
+ };
4057
4241
  }
4058
4242
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiHighlight, deps: [], target: i0.ɵɵFactoryTarget.Directive });
4059
4243
  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 });
@@ -4541,7 +4725,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
4541
4725
  }]
4542
4726
  }], propDecorators: { shape: [{ type: i0.Input, args: [{ isSignal: true, alias: "shape", required: false }] }], lines: [{ type: i0.Input, args: [{ isSignal: true, alias: "lines", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }] } });
4543
4727
 
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)';
4728
+ const BASE_CLASSES = "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-['']";
4545
4729
  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
4730
  /**
4547
4731
  * Link in the stale tone, for a caption about data that is late or left out of a total, such as
@@ -4677,11 +4861,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
4677
4861
  }]
4678
4862
  }] });
4679
4863
 
4864
+ const HEIGHT_PROPERTY = '--tab-bar-height';
4865
+ const HEIGHT = 'calc(52px + env(safe-area-inset-bottom))';
4680
4866
  const TAB_CLASSES = 'flex h-[52px] flex-col items-center justify-center gap-[3px] touch-manipulation select-none transition-transform duration-(--duration-press) ease-out active:scale-(--press-scale) focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-(--ring)';
4681
4867
  const INACTIVE_TAB_CLASSES = 'font-medium text-(--muted-foreground)';
4682
4868
  const ACTIVE_TAB_CLASSES = 'font-semibold text-(--foreground)';
4683
4869
  /**
4684
- * Bottom navigation bar for iPhone: 52px of tabs plus the home indicator's safe area.
4870
+ * Bottom navigation bar for iPhone: 52px of tabs plus the home indicator's safe area. While it is on the
4871
+ * page it publishes that height as `--tab-bar-height` on the root element, which `ui-action-bar` and
4872
+ * `ui-toaster` sit above; use it rather than restating 52px for anything that must clear the bar.
4685
4873
  *
4686
4874
  * @example
4687
4875
  * <nav ui-tab-bar>
@@ -4689,6 +4877,11 @@ const ACTIVE_TAB_CLASSES = 'font-semibold text-(--foreground)';
4689
4877
  * </nav>
4690
4878
  */
4691
4879
  class UiTabBar {
4880
+ constructor() {
4881
+ const root = inject(DOCUMENT).documentElement;
4882
+ root.style.setProperty(HEIGHT_PROPERTY, HEIGHT);
4883
+ inject(DestroyRef).onDestroy(() => root.style.removeProperty(HEIGHT_PROPERTY));
4884
+ }
4692
4885
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiTabBar, deps: [], target: i0.ɵɵFactoryTarget.Component });
4693
4886
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: UiTabBar, isStandalone: true, selector: "nav[ui-tab-bar]", host: { classAttribute: "grid grid-flow-col auto-cols-fr items-stretch bg-(--background)/90 backdrop-blur [@media(prefers-reduced-transparency:reduce)]:bg-(--background) shadow-[inset_0_1px_0_var(--hairline)] pb-[env(safe-area-inset-bottom)]" }, ngImport: i0, template: '<ng-content />', isInline: true });
4694
4887
  }
@@ -4701,7 +4894,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
4701
4894
  class: 'grid grid-flow-col auto-cols-fr items-stretch bg-(--background)/90 backdrop-blur [@media(prefers-reduced-transparency:reduce)]:bg-(--background) shadow-[inset_0_1px_0_var(--hairline)] pb-[env(safe-area-inset-bottom)]',
4702
4895
  },
4703
4896
  }]
4704
- }] });
4897
+ }], ctorParameters: () => [] });
4705
4898
  /**
4706
4899
  * One destination of `ui-tab-bar`.
4707
4900
  *
@@ -4837,7 +5030,7 @@ class UiToaster {
4837
5030
  this.lastId = 0;
4838
5031
  }
4839
5032
  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: `
5033
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: UiToaster, isStandalone: true, selector: "ui-toaster", host: { attributes: { "role": "status" }, classAttribute: "pointer-events-none fixed inset-x-0 z-50 flex justify-center bottom-[calc(var(--tab-bar-height,calc(52px+env(safe-area-inset-bottom)))+var(--action-bar-height,0px)+8px)] lg:inset-x-auto lg:right-6 lg:bottom-6" }, ngImport: i0, template: `
4841
5034
  @if (toasts.toast(); as toast) {
4842
5035
  <div
4843
5036
  animate.leave="ui-leave-fade"
@@ -4872,7 +5065,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
4872
5065
  `,
4873
5066
  host: {
4874
5067
  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',
5068
+ class: 'pointer-events-none fixed inset-x-0 z-50 flex justify-center bottom-[calc(var(--tab-bar-height,calc(52px+env(safe-area-inset-bottom)))+var(--action-bar-height,0px)+8px)] lg:inset-x-auto lg:right-6 lg:bottom-6',
4876
5069
  },
4877
5070
  }]
4878
5071
  }], ctorParameters: () => [] });
@@ -5193,5 +5386,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
5193
5386
  * Generated bundle index. Do not edit.
5194
5387
  */
5195
5388
 
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 };
5389
+ 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 };
5197
5390
  //# sourceMappingURL=joanroucoux-cairn-ui.mjs.map