@joanroucoux/cairn-ui 0.7.1 → 0.8.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.
Files changed (111) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/fesm2022/joanroucoux-cairn-ui-action-bar.mjs +54 -0
  3. package/fesm2022/joanroucoux-cairn-ui-action-bar.mjs.map +1 -0
  4. package/fesm2022/joanroucoux-cairn-ui-alert.mjs +129 -0
  5. package/fesm2022/joanroucoux-cairn-ui-alert.mjs.map +1 -0
  6. package/fesm2022/joanroucoux-cairn-ui-amount.mjs +114 -0
  7. package/fesm2022/joanroucoux-cairn-ui-amount.mjs.map +1 -0
  8. package/fesm2022/joanroucoux-cairn-ui-async.mjs +282 -0
  9. package/fesm2022/joanroucoux-cairn-ui-async.mjs.map +1 -0
  10. package/fesm2022/joanroucoux-cairn-ui-avatar.mjs +71 -0
  11. package/fesm2022/joanroucoux-cairn-ui-avatar.mjs.map +1 -0
  12. package/fesm2022/joanroucoux-cairn-ui-back-link.mjs +79 -0
  13. package/fesm2022/joanroucoux-cairn-ui-back-link.mjs.map +1 -0
  14. package/fesm2022/joanroucoux-cairn-ui-badge.mjs +48 -0
  15. package/fesm2022/joanroucoux-cairn-ui-badge.mjs.map +1 -0
  16. package/fesm2022/joanroucoux-cairn-ui-button.mjs +128 -0
  17. package/fesm2022/joanroucoux-cairn-ui-button.mjs.map +1 -0
  18. package/fesm2022/joanroucoux-cairn-ui-card.mjs +146 -0
  19. package/fesm2022/joanroucoux-cairn-ui-card.mjs.map +1 -0
  20. package/fesm2022/joanroucoux-cairn-ui-choice-chips.mjs +172 -0
  21. package/fesm2022/joanroucoux-cairn-ui-choice-chips.mjs.map +1 -0
  22. package/fesm2022/joanroucoux-cairn-ui-control.mjs +18 -0
  23. package/fesm2022/joanroucoux-cairn-ui-control.mjs.map +1 -0
  24. package/fesm2022/joanroucoux-cairn-ui-delta.mjs +103 -0
  25. package/fesm2022/joanroucoux-cairn-ui-delta.mjs.map +1 -0
  26. package/fesm2022/joanroucoux-cairn-ui-dialog.mjs +626 -0
  27. package/fesm2022/joanroucoux-cairn-ui-dialog.mjs.map +1 -0
  28. package/fesm2022/joanroucoux-cairn-ui-donut.mjs +347 -0
  29. package/fesm2022/joanroucoux-cairn-ui-donut.mjs.map +1 -0
  30. package/fesm2022/joanroucoux-cairn-ui-empty.mjs +47 -0
  31. package/fesm2022/joanroucoux-cairn-ui-empty.mjs.map +1 -0
  32. package/fesm2022/joanroucoux-cairn-ui-external-link.mjs +65 -0
  33. package/fesm2022/joanroucoux-cairn-ui-external-link.mjs.map +1 -0
  34. package/fesm2022/joanroucoux-cairn-ui-fact.mjs +99 -0
  35. package/fesm2022/joanroucoux-cairn-ui-fact.mjs.map +1 -0
  36. package/fesm2022/joanroucoux-cairn-ui-field.mjs +232 -0
  37. package/fesm2022/joanroucoux-cairn-ui-field.mjs.map +1 -0
  38. package/fesm2022/joanroucoux-cairn-ui-filter-chips.mjs +89 -0
  39. package/fesm2022/joanroucoux-cairn-ui-filter-chips.mjs.map +1 -0
  40. package/fesm2022/joanroucoux-cairn-ui-input.mjs +90 -0
  41. package/fesm2022/joanroucoux-cairn-ui-input.mjs.map +1 -0
  42. package/fesm2022/joanroucoux-cairn-ui-line-chart.mjs +844 -0
  43. package/fesm2022/joanroucoux-cairn-ui-line-chart.mjs.map +1 -0
  44. package/fesm2022/joanroucoux-cairn-ui-menu.mjs +241 -0
  45. package/fesm2022/joanroucoux-cairn-ui-menu.mjs.map +1 -0
  46. package/fesm2022/joanroucoux-cairn-ui-meter.mjs +86 -0
  47. package/fesm2022/joanroucoux-cairn-ui-meter.mjs.map +1 -0
  48. package/fesm2022/joanroucoux-cairn-ui-motion.mjs +415 -0
  49. package/fesm2022/joanroucoux-cairn-ui-motion.mjs.map +1 -0
  50. package/fesm2022/joanroucoux-cairn-ui-nav-item.mjs +45 -0
  51. package/fesm2022/joanroucoux-cairn-ui-nav-item.mjs.map +1 -0
  52. package/fesm2022/joanroucoux-cairn-ui-row.mjs +164 -0
  53. package/fesm2022/joanroucoux-cairn-ui-row.mjs.map +1 -0
  54. package/fesm2022/joanroucoux-cairn-ui-segmented.mjs +165 -0
  55. package/fesm2022/joanroucoux-cairn-ui-segmented.mjs.map +1 -0
  56. package/fesm2022/joanroucoux-cairn-ui-select.mjs +69 -0
  57. package/fesm2022/joanroucoux-cairn-ui-select.mjs.map +1 -0
  58. package/fesm2022/joanroucoux-cairn-ui-skeleton.mjs +91 -0
  59. package/fesm2022/joanroucoux-cairn-ui-skeleton.mjs.map +1 -0
  60. package/fesm2022/joanroucoux-cairn-ui-stale-link.mjs +43 -0
  61. package/fesm2022/joanroucoux-cairn-ui-stale-link.mjs.map +1 -0
  62. package/fesm2022/joanroucoux-cairn-ui-stat.mjs +96 -0
  63. package/fesm2022/joanroucoux-cairn-ui-stat.mjs.map +1 -0
  64. package/fesm2022/joanroucoux-cairn-ui-switch.mjs +37 -0
  65. package/fesm2022/joanroucoux-cairn-ui-switch.mjs.map +1 -0
  66. package/fesm2022/joanroucoux-cairn-ui-tab-bar.mjs +75 -0
  67. package/fesm2022/joanroucoux-cairn-ui-tab-bar.mjs.map +1 -0
  68. package/fesm2022/joanroucoux-cairn-ui-table.mjs +321 -0
  69. package/fesm2022/joanroucoux-cairn-ui-table.mjs.map +1 -0
  70. package/fesm2022/joanroucoux-cairn-ui-toast.mjs +153 -0
  71. package/fesm2022/joanroucoux-cairn-ui-toast.mjs.map +1 -0
  72. package/fesm2022/joanroucoux-cairn-ui.mjs +0 -5386
  73. package/fesm2022/joanroucoux-cairn-ui.mjs.map +1 -1
  74. package/package.json +141 -1
  75. package/styles/motion.css +9 -0
  76. package/types/joanroucoux-cairn-ui-action-bar.d.ts +22 -0
  77. package/types/joanroucoux-cairn-ui-alert.d.ts +23 -0
  78. package/types/joanroucoux-cairn-ui-amount.d.ts +45 -0
  79. package/types/joanroucoux-cairn-ui-async.d.ts +58 -0
  80. package/types/joanroucoux-cairn-ui-avatar.d.ts +35 -0
  81. package/types/joanroucoux-cairn-ui-back-link.d.ts +22 -0
  82. package/types/joanroucoux-cairn-ui-badge.d.ts +23 -0
  83. package/types/joanroucoux-cairn-ui-button.d.ts +29 -0
  84. package/types/joanroucoux-cairn-ui-card.d.ts +36 -0
  85. package/types/joanroucoux-cairn-ui-choice-chips.d.ts +46 -0
  86. package/types/joanroucoux-cairn-ui-control.d.ts +27 -0
  87. package/types/joanroucoux-cairn-ui-delta.d.ts +38 -0
  88. package/types/joanroucoux-cairn-ui-dialog.d.ts +72 -0
  89. package/types/joanroucoux-cairn-ui-donut.d.ts +59 -0
  90. package/types/joanroucoux-cairn-ui-empty.d.ts +19 -0
  91. package/types/joanroucoux-cairn-ui-external-link.d.ts +15 -0
  92. package/types/joanroucoux-cairn-ui-fact.d.ts +44 -0
  93. package/types/joanroucoux-cairn-ui-field.d.ts +84 -0
  94. package/types/joanroucoux-cairn-ui-filter-chips.d.ts +28 -0
  95. package/types/joanroucoux-cairn-ui-input.d.ts +51 -0
  96. package/types/joanroucoux-cairn-ui-line-chart.d.ts +114 -0
  97. package/types/joanroucoux-cairn-ui-menu.d.ts +66 -0
  98. package/types/joanroucoux-cairn-ui-meter.d.ts +30 -0
  99. package/types/joanroucoux-cairn-ui-motion.d.ts +124 -0
  100. package/types/joanroucoux-cairn-ui-nav-item.d.ts +21 -0
  101. package/types/joanroucoux-cairn-ui-row.d.ts +78 -0
  102. package/types/joanroucoux-cairn-ui-segmented.d.ts +36 -0
  103. package/types/joanroucoux-cairn-ui-select.d.ts +23 -0
  104. package/types/joanroucoux-cairn-ui-skeleton.d.ts +26 -0
  105. package/types/joanroucoux-cairn-ui-stale-link.d.ts +24 -0
  106. package/types/joanroucoux-cairn-ui-stat.d.ts +32 -0
  107. package/types/joanroucoux-cairn-ui-switch.d.ts +17 -0
  108. package/types/joanroucoux-cairn-ui-tab-bar.d.ts +31 -0
  109. package/types/joanroucoux-cairn-ui-table.d.ts +139 -0
  110. package/types/joanroucoux-cairn-ui-toast.d.ts +51 -0
  111. package/types/joanroucoux-cairn-ui.d.ts +1 -1407
package/CHANGELOG.md CHANGED
@@ -1,5 +1,101 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.8.0
4
+
5
+ ### Breaking
6
+
7
+ - The package root `@joanroucoux/cairn-ui` no longer exports anything. Each component folder is now its own
8
+ entry point, imported as `@joanroucoux/cairn-ui/<folder>`. A consumer's bundler splits chunks per module: a
9
+ single bundle, or a root barrel re-exporting the entries, put every component in the initial chunk. Moving to
10
+ subpaths lets each lazy route pull in only the components it uses.
11
+ - Migration: replace `import { UiButton } from '@joanroucoux/cairn-ui'` with
12
+ `import { UiButton } from '@joanroucoux/cairn-ui/button'`. The stylesheets under `@joanroucoux/cairn-ui/styles/*`
13
+ are unchanged.
14
+
15
+ ### Added
16
+
17
+ - `@joanroucoux/cairn-ui/control` (`UI_CONTROL`, `UiControl`, `UiControlError`) and, from
18
+ `@joanroucoux/cairn-ui/input`, `CONTROL_BASE_CLASSES`, `CONTROL_SIZE_CLASSES` and `CONTROL_SURFACE_CLASSES` are now
19
+ exported, because `select`, `choice-chips` and `field` import them across entry points. Only `UiControlError` was
20
+ public before.
21
+
22
+ ### Subpaths
23
+
24
+ Every symbol that was exported from the root now lives under exactly one subpath:
25
+
26
+ - `@joanroucoux/cairn-ui/action-bar`: `UiActionBar`
27
+ - `@joanroucoux/cairn-ui/alert`: `ALERT_VARIANTS`, `UiAlert`, `AlertVariant`
28
+ - `@joanroucoux/cairn-ui/amount`: `AMOUNT_MASKINGS`, `AMOUNT_NUMERICS`, `UI_AMOUNT_MASKED`, `UiAmount`, `AmountMasking`, `AmountNumeric`, `AmountFormatOptions`, `formatAmount`
29
+ - `@joanroucoux/cairn-ui/async`: `ASYNC_ALIGNS`, `ASYNC_STATES`, `ASYNC_VARIANTS`, `UiAsync`, `AsyncAlign`, `AsyncState`, `AsyncVariant`, `delayedState`
30
+ - `@joanroucoux/cairn-ui/avatar`: `AVATAR_SIZES`, `UiAvatar`, `UiAvatarLink`, `AvatarSize`
31
+ - `@joanroucoux/cairn-ui/back-link`: `BACK_LINK_SIZES`, `UiBackLink`, `BackLinkSize`
32
+ - `@joanroucoux/cairn-ui/badge`: `BADGE_SIZES`, `BADGE_VARIANTS`, `UiBadge`, `BadgeSize`, `BadgeVariant`
33
+ - `@joanroucoux/cairn-ui/button`: `BUTTON_SIZES`, `BUTTON_VARIANTS`, `UiButton`, `ButtonSize`, `ButtonVariant`
34
+ - `@joanroucoux/cairn-ui/card`: `CARD_PADDINGS`, `CARD_SURFACES`, `CARD_VARIANTS`, `UiCard`, `CardPadding`, `CardSurface`, `CardVariant`
35
+ - `@joanroucoux/cairn-ui/choice-chips`: `ChoiceChipOption`, `UiChoiceChips`
36
+ - `@joanroucoux/cairn-ui/control`: `UiControlError`, `UI_CONTROL`, `UiControl`
37
+ - `@joanroucoux/cairn-ui/delta`: `DELTA_EMPHASES`, `DELTA_SIZES`, `DELTA_WEIGHTS`, `UiDelta`, `DeltaEmphasis`, `DeltaSize`, `DeltaWeight`
38
+ - `@joanroucoux/cairn-ui/dialog`: `DIALOG_CLOSE_REASONS`, `DIALOG_LAYOUTS`, `DIALOG_WIDTHS`, `UiDialog`, `DialogCloseReason`, `DialogLayout`, `DialogWidth`
39
+ - `@joanroucoux/cairn-ui/donut`: `DonutSlice`, `UiDonut`
40
+ - `@joanroucoux/cairn-ui/empty`: `UiEmpty`
41
+ - `@joanroucoux/cairn-ui/external-link`: `UiExternalLink`
42
+ - `@joanroucoux/cairn-ui/fact`: `FACT_SIZES`, `FACT_SUB_TONES`, `UiFact`, `UiFacts`, `FactSize`, `FactSubTone`
43
+ - `@joanroucoux/cairn-ui/field`: `UiField`, `UiFieldLeading`, `UiFieldTrailing`
44
+ - `@joanroucoux/cairn-ui/filter-chips`: `FilterChipOption`, `UiFilterChips`
45
+ - `@joanroucoux/cairn-ui/input`: `CONTROL_SIZES`, `CONTROL_SURFACES`, `UiInput`, `UiTextarea`, `ControlSize`, `ControlSurface`, `CONTROL_BASE_CLASSES`, `CONTROL_SIZE_CLASSES`, `CONTROL_SURFACE_CLASSES`
46
+ - `@joanroucoux/cairn-ui/line-chart`: `AxisTicks`, `ChartPoint`, `TooltipSize`, `UiLineChart`
47
+ - `@joanroucoux/cairn-ui/menu`: `UiMenu`, `UiMenuItem`, `UiMenuTrigger`
48
+ - `@joanroucoux/cairn-ui/meter`: `METER_SIZES`, `METER_TONES`, `UiMeter`, `MeterSize`, `MeterTone`
49
+ - `@joanroucoux/cairn-ui/motion`: `UiFlipItem`, `UiFlipList`, `UiHighlight`, `injectReducedMotion`, `holdTransitionsUntilRendered`
50
+ - `@joanroucoux/cairn-ui/nav-item`: `UiNavItem`
51
+ - `@joanroucoux/cairn-ui/row`: `ROW_GAPS`, `ROW_PADDINGS`, `ROW_SIZES`, `UiListRow`, `UiRow`, `UiRowItem`, `UiRowTile`, `RowGap`, `RowPadding`, `RowSize`
52
+ - `@joanroucoux/cairn-ui/segmented`: `SEGMENTED_SIZES`, `UiSegmented`, `SegmentedOption`, `SegmentedSize`
53
+ - `@joanroucoux/cairn-ui/select`: `UiSelect`
54
+ - `@joanroucoux/cairn-ui/skeleton`: `SKELETON_SHAPES`, `UiSkeleton`, `SkeletonShape`
55
+ - `@joanroucoux/cairn-ui/stale-link`: `STALE_LINK_SIZES`, `UiStaleLink`, `StaleLinkSize`
56
+ - `@joanroucoux/cairn-ui/stat`: `STAT_SIZES`, `UiStat`, `StatSize`
57
+ - `@joanroucoux/cairn-ui/switch`: `UiSwitch`
58
+ - `@joanroucoux/cairn-ui/tab-bar`: `UiTab`, `UiTabBar`
59
+ - `@joanroucoux/cairn-ui/table`: `CELL_BREAKPOINTS`, `GROUP_SIZES`, `SUB_TONES`, `TABLE_ROWS`, `UiCellSub`, `UiGroup`, `UiGroupCell`, `UiRowAction`, `UiRowLink`, `UiTable`, `UiTd`, `UiTh`, `UiTr`, `CellBreakpoint`, `GroupSize`, `SubTone`, `TableRow`
60
+ - `@joanroucoux/cairn-ui/toast`: `UiToaster`, `UiToasts`, `Toast`
61
+
62
+ ## 0.7.2
63
+
64
+ ### Added
65
+
66
+ - `uiStaleLink` `size` input: `caption` (default, 12/17) or `label` (14/20), both at weight 500, for the dashboard
67
+ total's "1 cours en retard ›". The hit area stays 44px high, 40px with a mouse. `STALE_LINK_SIZES` and
68
+ `StaleLinkSize` are exported.
69
+ - `ui-line-chart` `rangeKey` input (`string | null`, default `null`): when bound, a new series interpolates and
70
+ fades "Départ" and the dashed line only if it comes with a new key. A new series under the same key (a reload
71
+ after a buy) is drawn at once, with no fade. The key may change before its series arrives; the interpolation
72
+ runs when the series does. Unbound, every new series interpolates, as before.
73
+ - `holdTransitionsUntilRendered()`: called in a constructor, it holds the transitions of the host and of everything
74
+ inside it until two frames after its first render, through `data-ui-settling` and the matching rule of
75
+ `styles/motion.css`. For a consumer element whose bound classes carry a transition.
76
+
77
+ ### Changed
78
+
79
+ - `ui-button` (and the `ui-async` retry button) no longer fades its opacity when it becomes disabled, busy or
80
+ enabled, and a `ui-segmented` option switches its label colour at once: MOUVEMENT.md animates neither. The press
81
+ scale and the hover fill keep their transitions.
82
+ - `uiAvatarLink` draws its current-page halo as a background image, so it appears at once when the route changes;
83
+ the hover glow keeps its 180 ms fade.
84
+ - `ui-nav-item` switches its active fill (now a background image) and its text colour at once on navigation, as
85
+ MOUVEMENT.md §4.1 asks of the sidebar; only the hover fill keeps its 180 ms fade, and the press scale stays.
86
+ `ui-tab` already switched at once (scale transition only); a story now checks it.
87
+
88
+ ### Fixed
89
+
90
+ - Nothing transitions on page open any more. A route's elements are created a tick before their classes are bound,
91
+ and a layout read in between (the router's scroll to the top, a view transition) turned the bound fill, colour
92
+ or switch track into a 180 ms transition. `ui-button`, `ui-async`, `ui-back-link`, `ui-nav-item`, `ui-row`,
93
+ `uiSwitch`, `ui-segmented` and `uiTable` now hold their transitions until rendered. Consumers already import
94
+ `styles/motion.css`, which carries the rule.
95
+ - `ui-menu` items stay on one line, left aligned (`whitespace-nowrap`, `text-left`). The menu places itself from
96
+ its layout size instead of its box scaled by the entry animation, and frees its right and bottom edges, so a
97
+ menu opened beside the right edge of a phone no longer narrows and wraps a long label.
98
+
3
99
  ## 0.7.1
4
100
 
5
101
  ### Added
@@ -0,0 +1,54 @@
1
+ import { DOCUMENT } from '@angular/common';
2
+ import * as i0 from '@angular/core';
3
+ import { inject, ElementRef, DestroyRef, afterNextRender, Component } from '@angular/core';
4
+
5
+ const HEIGHT_PROPERTY = '--action-bar-height';
6
+ /**
7
+ * Bar of actions fixed above the tab bar on iPhone: the primary actions of a detail screen, within
8
+ * thumb reach. It sits `--tab-bar-height` (52px plus the safe area) above the bottom edge, so it clears `ui-tab-bar`, and
9
+ * it is hidden from `64rem`, where the same actions live in the page itself. Its children share the
10
+ * width equally. While it is on the page it publishes its height as `--action-bar-height` on the
11
+ * root element, which `ui-toaster` adds to its offset so a confirmation never covers the actions.
12
+ *
13
+ * @example
14
+ * <ui-action-bar>
15
+ * <button ui-button variant="outline" size="tall">Sell</button>
16
+ * <button ui-button size="tall">Buy</button>
17
+ * </ui-action-bar>
18
+ */
19
+ class UiActionBar {
20
+ constructor() {
21
+ const host = inject(ElementRef).nativeElement;
22
+ const root = inject(DOCUMENT).documentElement;
23
+ const destroyRef = inject(DestroyRef);
24
+ const publish = () => root.style.setProperty(HEIGHT_PROPERTY, `${host.offsetHeight}px`);
25
+ afterNextRender(() => {
26
+ publish();
27
+ if (typeof ResizeObserver !== 'undefined') {
28
+ const observer = new ResizeObserver(publish);
29
+ observer.observe(host);
30
+ destroyRef.onDestroy(() => observer.disconnect());
31
+ }
32
+ });
33
+ destroyRef.onDestroy(() => root.style.removeProperty(HEIGHT_PROPERTY));
34
+ }
35
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiActionBar, deps: [], target: i0.ɵɵFactoryTarget.Component });
36
+ 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 });
37
+ }
38
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiActionBar, decorators: [{
39
+ type: Component,
40
+ args: [{
41
+ selector: 'ui-action-bar',
42
+ template: '<ng-content />',
43
+ host: {
44
+ 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',
45
+ },
46
+ }]
47
+ }], ctorParameters: () => [] });
48
+
49
+ /**
50
+ * Generated bundle index. Do not edit.
51
+ */
52
+
53
+ export { UiActionBar };
54
+ //# sourceMappingURL=joanroucoux-cairn-ui-action-bar.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"joanroucoux-cairn-ui-action-bar.mjs","sources":["../../../projects/ui/action-bar/action-bar.ts","../../../projects/ui/action-bar/joanroucoux-cairn-ui-action-bar.ts"],"sourcesContent":["import { DOCUMENT } from '@angular/common';\nimport { Component, DestroyRef, ElementRef, afterNextRender, inject } from '@angular/core';\n\nconst HEIGHT_PROPERTY = '--action-bar-height';\n\n/**\n * Bar of actions fixed above the tab bar on iPhone: the primary actions of a detail screen, within\n * thumb reach. It sits `--tab-bar-height` (52px plus the safe area) above the bottom edge, so it clears `ui-tab-bar`, and\n * it is hidden from `64rem`, where the same actions live in the page itself. Its children share the\n * width equally. While it is on the page it publishes its height as `--action-bar-height` on the\n * root element, which `ui-toaster` adds to its offset so a confirmation never covers the actions.\n *\n * @example\n * <ui-action-bar>\n * <button ui-button variant=\"outline\" size=\"tall\">Sell</button>\n * <button ui-button size=\"tall\">Buy</button>\n * </ui-action-bar>\n */\n@Component({\n selector: 'ui-action-bar',\n template: '<ng-content />',\n host: {\n class:\n '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',\n },\n})\nexport class UiActionBar {\n constructor() {\n const host = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n const root = inject(DOCUMENT).documentElement;\n const destroyRef = inject(DestroyRef);\n const publish = (): void => root.style.setProperty(HEIGHT_PROPERTY, `${host.offsetHeight}px`);\n\n afterNextRender(() => {\n publish();\n\n if (typeof ResizeObserver !== 'undefined') {\n const observer = new ResizeObserver(publish);\n observer.observe(host);\n destroyRef.onDestroy(() => observer.disconnect());\n }\n });\n\n destroyRef.onDestroy(() => root.style.removeProperty(HEIGHT_PROPERTY));\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;AAGA,MAAM,eAAe,GAAG,qBAAqB;AAE7C;;;;;;;;;;;;AAYG;MASU,WAAW,CAAA;AACtB,IAAA,WAAA,GAAA;QACE,MAAM,IAAI,GAAG,MAAM,CAA0B,UAAU,CAAC,CAAC,aAAa;QACtE,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,eAAe;AAC7C,QAAA,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC;AACrC,QAAA,MAAM,OAAO,GAAG,MAAY,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,eAAe,EAAE,CAAA,EAAG,IAAI,CAAC,YAAY,CAAA,EAAA,CAAI,CAAC;QAE7F,eAAe,CAAC,MAAK;AACnB,YAAA,OAAO,EAAE;AAET,YAAA,IAAI,OAAO,cAAc,KAAK,WAAW,EAAE;AACzC,gBAAA,MAAM,QAAQ,GAAG,IAAI,cAAc,CAAC,OAAO,CAAC;AAC5C,gBAAA,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC;gBACtB,UAAU,CAAC,SAAS,CAAC,MAAM,QAAQ,CAAC,UAAU,EAAE,CAAC;YACnD;AACF,QAAA,CAAC,CAAC;AAEF,QAAA,UAAU,CAAC,SAAS,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,eAAe,CAAC,CAAC;IACxE;uGAlBW,WAAW,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAX,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,WAAW,wTANZ,gBAAgB,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA;;2FAMf,WAAW,EAAA,UAAA,EAAA,CAAA;kBARvB,SAAS;AAAC,YAAA,IAAA,EAAA,CAAA;AACT,oBAAA,QAAQ,EAAE,eAAe;AACzB,oBAAA,QAAQ,EAAE,gBAAgB;AAC1B,oBAAA,IAAI,EAAE;AACJ,wBAAA,KAAK,EACH,mNAAmN;AACtN,qBAAA;AACF,iBAAA;;;ACzBD;;AAEG;;;;"}
@@ -0,0 +1,129 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input, booleanAttribute, computed, Component } from '@angular/core';
3
+
4
+ const ALERT_VARIANTS = ['error', 'warning'];
5
+ const BASE_CLASSES = 'flex items-start gap-2.5 text-left text-label';
6
+ const VARIANT_CLASSES = {
7
+ error: 'rounded-control bg-(--card) px-3.5 py-3 shadow-[inset_0_0_0_1px_var(--border)]',
8
+ warning: 'text-(--foreground)',
9
+ };
10
+ /**
11
+ * Inline message explaining a failure or a consequence: an icon, an optional heading and a text.
12
+ * It fades in when it appears; set `[fadeIn]="false"` on an alert that is part of the page as it
13
+ * opens (an error state after a load), which shows without motion.
14
+ *
15
+ * @example
16
+ * <ui-alert heading="Sign-in failed">Try again.</ui-alert>
17
+ */
18
+ class UiAlert {
19
+ variant = input('error', /* @ts-ignore */
20
+ ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
21
+ heading = input(/* @ts-ignore */
22
+ ...(ngDevMode ? [undefined, { debugName: "heading" }] : /* istanbul ignore next */ []));
23
+ fadeIn = input(true, { ...(ngDevMode ? { debugName: "fadeIn" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
24
+ classes = computed(() => `${BASE_CLASSES} ${VARIANT_CLASSES[this.variant()]}${this.fadeIn() ? ' ui-enter-fade' : ''}`, /* @ts-ignore */
25
+ ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
26
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiAlert, deps: [], target: i0.ɵɵFactoryTarget.Component });
27
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: UiAlert, isStandalone: true, selector: "ui-alert", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired: false, transformFunction: null }, fadeIn: { classPropertyName: "fadeIn", publicName: "fadeIn", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "animate.leave": "ui-leave-fade" }, properties: { "class": "classes()", "attr.role": "variant() === 'warning' ? 'status' : 'alert'" } }, ngImport: i0, template: `
28
+ @if (variant() === 'warning') {
29
+ <svg
30
+ aria-hidden="true"
31
+ class="mt-px block flex-none stroke-(--negative)"
32
+ fill="none"
33
+ height="18"
34
+ stroke-linecap="round"
35
+ stroke-linejoin="round"
36
+ stroke-width="1.75"
37
+ viewBox="0 0 24 24"
38
+ width="18"
39
+ >
40
+ <path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" />
41
+ <path d="M12 9v4" />
42
+ <path d="M12 17h.01" />
43
+ </svg>
44
+ } @else {
45
+ <svg
46
+ aria-hidden="true"
47
+ class="block flex-none stroke-(--negative)"
48
+ fill="none"
49
+ height="18"
50
+ stroke-linecap="round"
51
+ stroke-linejoin="round"
52
+ stroke-width="1.75"
53
+ viewBox="0 0 24 24"
54
+ width="18"
55
+ [class.mt-px]="heading()"
56
+ >
57
+ <circle cx="12" cy="12" r="10" />
58
+ <line x1="12" x2="12" y1="8" y2="12" />
59
+ <line x1="12" x2="12.01" y1="16" y2="16" />
60
+ </svg>
61
+ }
62
+ <div class="flex flex-col gap-0.5">
63
+ @if (heading()) {
64
+ <span class="font-medium">{{ heading() }}</span>
65
+ }
66
+ <span class="text-pretty" [class.text-(--muted-foreground)]="heading()"><ng-content /></span>
67
+ </div>
68
+ `, isInline: true });
69
+ }
70
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiAlert, decorators: [{
71
+ type: Component,
72
+ args: [{
73
+ selector: 'ui-alert',
74
+ template: `
75
+ @if (variant() === 'warning') {
76
+ <svg
77
+ aria-hidden="true"
78
+ class="mt-px block flex-none stroke-(--negative)"
79
+ fill="none"
80
+ height="18"
81
+ stroke-linecap="round"
82
+ stroke-linejoin="round"
83
+ stroke-width="1.75"
84
+ viewBox="0 0 24 24"
85
+ width="18"
86
+ >
87
+ <path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" />
88
+ <path d="M12 9v4" />
89
+ <path d="M12 17h.01" />
90
+ </svg>
91
+ } @else {
92
+ <svg
93
+ aria-hidden="true"
94
+ class="block flex-none stroke-(--negative)"
95
+ fill="none"
96
+ height="18"
97
+ stroke-linecap="round"
98
+ stroke-linejoin="round"
99
+ stroke-width="1.75"
100
+ viewBox="0 0 24 24"
101
+ width="18"
102
+ [class.mt-px]="heading()"
103
+ >
104
+ <circle cx="12" cy="12" r="10" />
105
+ <line x1="12" x2="12" y1="8" y2="12" />
106
+ <line x1="12" x2="12.01" y1="16" y2="16" />
107
+ </svg>
108
+ }
109
+ <div class="flex flex-col gap-0.5">
110
+ @if (heading()) {
111
+ <span class="font-medium">{{ heading() }}</span>
112
+ }
113
+ <span class="text-pretty" [class.text-(--muted-foreground)]="heading()"><ng-content /></span>
114
+ </div>
115
+ `,
116
+ host: {
117
+ 'animate.leave': 'ui-leave-fade',
118
+ '[class]': 'classes()',
119
+ '[attr.role]': "variant() === 'warning' ? 'status' : 'alert'",
120
+ },
121
+ }]
122
+ }], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], heading: [{ type: i0.Input, args: [{ isSignal: true, alias: "heading", required: false }] }], fadeIn: [{ type: i0.Input, args: [{ isSignal: true, alias: "fadeIn", required: false }] }] } });
123
+
124
+ /**
125
+ * Generated bundle index. Do not edit.
126
+ */
127
+
128
+ export { ALERT_VARIANTS, UiAlert };
129
+ //# sourceMappingURL=joanroucoux-cairn-ui-alert.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"joanroucoux-cairn-ui-alert.mjs","sources":["../../../projects/ui/alert/alert.ts","../../../projects/ui/alert/joanroucoux-cairn-ui-alert.ts"],"sourcesContent":["import { Component, booleanAttribute, computed, input } from '@angular/core';\n\nexport const ALERT_VARIANTS = ['error', 'warning'] as const;\nexport type AlertVariant = (typeof ALERT_VARIANTS)[number];\n\nconst BASE_CLASSES = 'flex items-start gap-2.5 text-left text-label';\n\nconst VARIANT_CLASSES: Record<AlertVariant, string> = {\n error: 'rounded-control bg-(--card) px-3.5 py-3 shadow-[inset_0_0_0_1px_var(--border)]',\n warning: 'text-(--foreground)',\n};\n\n/**\n * Inline message explaining a failure or a consequence: an icon, an optional heading and a text.\n * It fades in when it appears; set `[fadeIn]=\"false\"` on an alert that is part of the page as it\n * opens (an error state after a load), which shows without motion.\n *\n * @example\n * <ui-alert heading=\"Sign-in failed\">Try again.</ui-alert>\n */\n@Component({\n selector: 'ui-alert',\n template: `\n @if (variant() === 'warning') {\n <svg\n aria-hidden=\"true\"\n class=\"mt-px block flex-none stroke-(--negative)\"\n fill=\"none\"\n height=\"18\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.75\"\n viewBox=\"0 0 24 24\"\n width=\"18\"\n >\n <path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\" />\n <path d=\"M12 9v4\" />\n <path d=\"M12 17h.01\" />\n </svg>\n } @else {\n <svg\n aria-hidden=\"true\"\n class=\"block flex-none stroke-(--negative)\"\n fill=\"none\"\n height=\"18\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.75\"\n viewBox=\"0 0 24 24\"\n width=\"18\"\n [class.mt-px]=\"heading()\"\n >\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <line x1=\"12\" x2=\"12\" y1=\"8\" y2=\"12\" />\n <line x1=\"12\" x2=\"12.01\" y1=\"16\" y2=\"16\" />\n </svg>\n }\n <div class=\"flex flex-col gap-0.5\">\n @if (heading()) {\n <span class=\"font-medium\">{{ heading() }}</span>\n }\n <span class=\"text-pretty\" [class.text-(--muted-foreground)]=\"heading()\"><ng-content /></span>\n </div>\n `,\n host: {\n 'animate.leave': 'ui-leave-fade',\n '[class]': 'classes()',\n '[attr.role]': \"variant() === 'warning' ? 'status' : 'alert'\",\n },\n})\nexport class UiAlert {\n readonly variant = input<AlertVariant>('error');\n readonly heading = input<string>();\n readonly fadeIn = input(true, { transform: booleanAttribute });\n\n protected readonly classes = computed(\n () => `${BASE_CLASSES} ${VARIANT_CLASSES[this.variant()]}${this.fadeIn() ? ' ui-enter-fade' : ''}`,\n );\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;MAEa,cAAc,GAAG,CAAC,OAAO,EAAE,SAAS;AAGjD,MAAM,YAAY,GAAG,+CAA+C;AAEpE,MAAM,eAAe,GAAiC;AACpD,IAAA,KAAK,EAAE,gFAAgF;AACvF,IAAA,OAAO,EAAE,qBAAqB;CAC/B;AAED;;;;;;;AAOG;MAmDU,OAAO,CAAA;IACT,OAAO,GAAG,KAAK,CAAe,OAAO;gFAAC;AACtC,IAAA,OAAO,GAAG,KAAK;2FAAU;IACzB,MAAM,GAAG,KAAK,CAAC,IAAI,8EAAI,SAAS,EAAE,gBAAgB,EAAA,CAAG;AAE3C,IAAA,OAAO,GAAG,QAAQ,CACnC,MAAM,CAAA,EAAG,YAAY,CAAA,CAAA,EAAI,eAAe,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAA,EAAG,IAAI,CAAC,MAAM,EAAE,GAAG,gBAAgB,GAAG,EAAE,CAAA,CAAE;gFACnG;uGAPU,OAAO,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAP,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,OAAO,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,UAAA,EAAA,MAAA,EAAA,EAAA,OAAA,EAAA,EAAA,iBAAA,EAAA,SAAA,EAAA,UAAA,EAAA,SAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,OAAA,EAAA,EAAA,iBAAA,EAAA,SAAA,EAAA,UAAA,EAAA,SAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,MAAA,EAAA,EAAA,iBAAA,EAAA,QAAA,EAAA,UAAA,EAAA,QAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,IAAA,EAAA,EAAA,UAAA,EAAA,EAAA,eAAA,EAAA,eAAA,EAAA,EAAA,UAAA,EAAA,EAAA,OAAA,EAAA,WAAA,EAAA,WAAA,EAAA,8CAAA,EAAA,EAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EAhDR;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCT,EAAA,CAAA,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA;;2FAOU,OAAO,EAAA,UAAA,EAAA,CAAA;kBAlDnB,SAAS;AAAC,YAAA,IAAA,EAAA,CAAA;AACT,oBAAA,QAAQ,EAAE,UAAU;AACpB,oBAAA,QAAQ,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCT,EAAA,CAAA;AACD,oBAAA,IAAI,EAAE;AACJ,wBAAA,eAAe,EAAE,eAAe;AAChC,wBAAA,SAAS,EAAE,WAAW;AACtB,wBAAA,aAAa,EAAE,8CAA8C;AAC9D,qBAAA;AACF,iBAAA;;;ACrED;;AAEG;;;;"}
@@ -0,0 +1,114 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, signal, input, inject, LOCALE_ID, booleanAttribute, computed, Component } from '@angular/core';
3
+
4
+ const MINUS = '−';
5
+ const EM_DASH = '—';
6
+ const BULLETS = '••••';
7
+ /** Formats a number or an amount for the locale, with a missing value as an em dash and masking behind four bullets. */
8
+ function formatAmount(value, options, masked = false) {
9
+ if (value === null || value === undefined || Number.isNaN(value)) {
10
+ return EM_DASH;
11
+ }
12
+ const digits = options.fractionDigits ?? 2;
13
+ const format = new Intl.NumberFormat(options.locale, {
14
+ ...(options.currency ? { style: 'currency', currency: options.currency } : {}),
15
+ minimumFractionDigits: digits,
16
+ maximumFractionDigits: digits,
17
+ });
18
+ const magnitude = Math.abs(value);
19
+ const parts = format.formatToParts(magnitude);
20
+ if (masked) {
21
+ let replaced = false;
22
+ return parts
23
+ .map((part) => {
24
+ if (['integer', 'group', 'decimal', 'fraction'].includes(part.type)) {
25
+ if (replaced) {
26
+ return '';
27
+ }
28
+ replaced = true;
29
+ return BULLETS;
30
+ }
31
+ return part.value;
32
+ })
33
+ .join('');
34
+ }
35
+ const text = parts.map((part) => part.value).join('');
36
+ const isZero = Number(format.format(magnitude).replace(/[^\d]/g, '')) === 0;
37
+ if (!options.signed || isZero) {
38
+ return value < 0 && !isZero ? `${MINUS}${text}` : text;
39
+ }
40
+ return `${value > 0 ? '+' : MINUS}${text}`;
41
+ }
42
+
43
+ /** App-wide "amounts are hidden" state. Defaults to never masked; the app overrides it. */
44
+ const UI_AMOUNT_MASKED = new InjectionToken('UI_AMOUNT_MASKED', {
45
+ providedIn: 'root',
46
+ factory: () => signal(false),
47
+ });
48
+ /** Available figure styles. `AmountNumeric` is derived from this tuple. */
49
+ const AMOUNT_NUMERICS = ['tabular', 'proportional'];
50
+ /** Available masking behaviors. `AmountMasking` is derived from this tuple. */
51
+ const AMOUNT_MASKINGS = ['dots', 'hide'];
52
+ /**
53
+ * A number or an amount of money, formatted for the locale and masked when the app hides amounts.
54
+ *
55
+ * @example
56
+ * <ui-amount [value]="total" currency="EUR" numeric="proportional" />
57
+ * <ui-amount [value]="dayChange" currency="EUR" signed whenMasked="hide" />
58
+ */
59
+ class UiAmount {
60
+ value = input(/* @ts-ignore */
61
+ ...(ngDevMode ? [undefined, { debugName: "value" }] : /* istanbul ignore next */ []));
62
+ currency = input(/* @ts-ignore */
63
+ ...(ngDevMode ? [undefined, { debugName: "currency" }] : /* istanbul ignore next */ []));
64
+ locale = input(inject(LOCALE_ID), /* @ts-ignore */
65
+ ...(ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []));
66
+ signed = input(false, { ...(ngDevMode ? { debugName: "signed" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
67
+ fractionDigits = input(2, /* @ts-ignore */
68
+ ...(ngDevMode ? [{ debugName: "fractionDigits" }] : /* istanbul ignore next */ []));
69
+ numeric = input('tabular', /* @ts-ignore */
70
+ ...(ngDevMode ? [{ debugName: "numeric" }] : /* istanbul ignore next */ []));
71
+ whenMasked = input('dots', /* @ts-ignore */
72
+ ...(ngDevMode ? [{ debugName: "whenMasked" }] : /* istanbul ignore next */ []));
73
+ #masked = inject(UI_AMOUNT_MASKED);
74
+ hidden = computed(() => this.#masked() && this.whenMasked() === 'hide', /* @ts-ignore */
75
+ ...(ngDevMode ? [{ debugName: "hidden" }] : /* istanbul ignore next */ []));
76
+ text = computed(() => formatAmount(this.value(), {
77
+ locale: this.locale(),
78
+ currency: this.currency(),
79
+ signed: this.signed(),
80
+ fractionDigits: this.fractionDigits(),
81
+ }, this.#masked()), /* @ts-ignore */
82
+ ...(ngDevMode ? [{ debugName: "text" }] : /* istanbul ignore next */ []));
83
+ classes = computed(() => {
84
+ const missing = this.value() === null || this.value() === undefined;
85
+ return [
86
+ 'whitespace-nowrap',
87
+ this.numeric() === 'tabular' ? 'tabular-nums' : '',
88
+ missing ? 'text-(--subtle-foreground)' : '',
89
+ ]
90
+ .filter(Boolean)
91
+ .join(' ');
92
+ }, /* @ts-ignore */
93
+ ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
94
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiAmount, deps: [], target: i0.ɵɵFactoryTarget.Component });
95
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.7", type: UiAmount, isStandalone: true, selector: "ui-amount", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, currency: { classPropertyName: "currency", publicName: "currency", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, signed: { classPropertyName: "signed", publicName: "signed", isSignal: true, isRequired: false, transformFunction: null }, fractionDigits: { classPropertyName: "fractionDigits", publicName: "fractionDigits", isSignal: true, isRequired: false, transformFunction: null }, numeric: { classPropertyName: "numeric", publicName: "numeric", isSignal: true, isRequired: false, transformFunction: null }, whenMasked: { classPropertyName: "whenMasked", publicName: "whenMasked", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "classes()", "attr.hidden": "hidden() || null" } }, ngImport: i0, template: '{{ text() }}', isInline: true });
96
+ }
97
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiAmount, decorators: [{
98
+ type: Component,
99
+ args: [{
100
+ selector: 'ui-amount',
101
+ template: '{{ text() }}',
102
+ host: {
103
+ '[class]': 'classes()',
104
+ '[attr.hidden]': 'hidden() || null',
105
+ },
106
+ }]
107
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], currency: [{ type: i0.Input, args: [{ isSignal: true, alias: "currency", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], signed: [{ type: i0.Input, args: [{ isSignal: true, alias: "signed", required: false }] }], fractionDigits: [{ type: i0.Input, args: [{ isSignal: true, alias: "fractionDigits", required: false }] }], numeric: [{ type: i0.Input, args: [{ isSignal: true, alias: "numeric", required: false }] }], whenMasked: [{ type: i0.Input, args: [{ isSignal: true, alias: "whenMasked", required: false }] }] } });
108
+
109
+ /**
110
+ * Generated bundle index. Do not edit.
111
+ */
112
+
113
+ export { AMOUNT_MASKINGS, AMOUNT_NUMERICS, UI_AMOUNT_MASKED, UiAmount, formatAmount };
114
+ //# sourceMappingURL=joanroucoux-cairn-ui-amount.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"joanroucoux-cairn-ui-amount.mjs","sources":["../../../projects/ui/amount/format-amount.ts","../../../projects/ui/amount/amount.ts","../../../projects/ui/amount/joanroucoux-cairn-ui-amount.ts"],"sourcesContent":["const MINUS = '−';\nconst EM_DASH = '—';\nconst BULLETS = '••••';\n\nexport type AmountFormatOptions = {\n locale: string;\n currency?: string;\n signed?: boolean;\n fractionDigits?: number;\n};\n\n/** Formats a number or an amount for the locale, with a missing value as an em dash and masking behind four bullets. */\nexport function formatAmount(value: number | null | undefined, options: AmountFormatOptions, masked = false): string {\n if (value === null || value === undefined || Number.isNaN(value)) {\n return EM_DASH;\n }\n const digits = options.fractionDigits ?? 2;\n const format = new Intl.NumberFormat(options.locale, {\n ...(options.currency ? { style: 'currency', currency: options.currency } : {}),\n minimumFractionDigits: digits,\n maximumFractionDigits: digits,\n });\n const magnitude = Math.abs(value);\n const parts = format.formatToParts(magnitude);\n\n if (masked) {\n let replaced = false;\n return parts\n .map((part) => {\n if (['integer', 'group', 'decimal', 'fraction'].includes(part.type)) {\n if (replaced) {\n return '';\n }\n replaced = true;\n return BULLETS;\n }\n return part.value;\n })\n .join('');\n }\n\n const text = parts.map((part) => part.value).join('');\n const isZero = Number(format.format(magnitude).replace(/[^\\d]/g, '')) === 0;\n if (!options.signed || isZero) {\n return value < 0 && !isZero ? `${MINUS}${text}` : text;\n }\n return `${value > 0 ? '+' : MINUS}${text}`;\n}\n","import {\n Component,\n InjectionToken,\n LOCALE_ID,\n type Signal,\n booleanAttribute,\n computed,\n inject,\n input,\n signal,\n} from '@angular/core';\n\nimport { formatAmount } from './format-amount';\n\n/** App-wide \"amounts are hidden\" state. Defaults to never masked; the app overrides it. */\nexport const UI_AMOUNT_MASKED = new InjectionToken<Signal<boolean>>('UI_AMOUNT_MASKED', {\n providedIn: 'root',\n factory: () => signal(false),\n});\n\n/** Available figure styles. `AmountNumeric` is derived from this tuple. */\nexport const AMOUNT_NUMERICS = ['tabular', 'proportional'] as const;\nexport type AmountNumeric = (typeof AMOUNT_NUMERICS)[number];\n\n/** Available masking behaviors. `AmountMasking` is derived from this tuple. */\nexport const AMOUNT_MASKINGS = ['dots', 'hide'] as const;\nexport type AmountMasking = (typeof AMOUNT_MASKINGS)[number];\n\n/**\n * A number or an amount of money, formatted for the locale and masked when the app hides amounts.\n *\n * @example\n * <ui-amount [value]=\"total\" currency=\"EUR\" numeric=\"proportional\" />\n * <ui-amount [value]=\"dayChange\" currency=\"EUR\" signed whenMasked=\"hide\" />\n */\n@Component({\n selector: 'ui-amount',\n template: '{{ text() }}',\n host: {\n '[class]': 'classes()',\n '[attr.hidden]': 'hidden() || null',\n },\n})\nexport class UiAmount {\n readonly value = input<number | null | undefined>();\n readonly currency = input<string>();\n readonly locale = input<string>(inject(LOCALE_ID));\n readonly signed = input(false, { transform: booleanAttribute });\n readonly fractionDigits = input(2);\n readonly numeric = input<AmountNumeric>('tabular');\n readonly whenMasked = input<AmountMasking>('dots');\n\n readonly #masked = inject(UI_AMOUNT_MASKED);\n\n protected readonly hidden = computed(() => this.#masked() && this.whenMasked() === 'hide');\n\n protected readonly text = computed(() =>\n formatAmount(\n this.value(),\n {\n locale: this.locale(),\n currency: this.currency(),\n signed: this.signed(),\n fractionDigits: this.fractionDigits(),\n },\n this.#masked(),\n ),\n );\n\n protected readonly classes = computed(() => {\n const missing = this.value() === null || this.value() === undefined;\n\n return [\n 'whitespace-nowrap',\n this.numeric() === 'tabular' ? 'tabular-nums' : '',\n missing ? 'text-(--subtle-foreground)' : '',\n ]\n .filter(Boolean)\n .join(' ');\n });\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}