@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.
- package/CHANGELOG.md +96 -0
- package/fesm2022/joanroucoux-cairn-ui-action-bar.mjs +54 -0
- package/fesm2022/joanroucoux-cairn-ui-action-bar.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-alert.mjs +129 -0
- package/fesm2022/joanroucoux-cairn-ui-alert.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-amount.mjs +114 -0
- package/fesm2022/joanroucoux-cairn-ui-amount.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-async.mjs +282 -0
- package/fesm2022/joanroucoux-cairn-ui-async.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-avatar.mjs +71 -0
- package/fesm2022/joanroucoux-cairn-ui-avatar.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-back-link.mjs +79 -0
- package/fesm2022/joanroucoux-cairn-ui-back-link.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-badge.mjs +48 -0
- package/fesm2022/joanroucoux-cairn-ui-badge.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-button.mjs +128 -0
- package/fesm2022/joanroucoux-cairn-ui-button.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-card.mjs +146 -0
- package/fesm2022/joanroucoux-cairn-ui-card.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-choice-chips.mjs +172 -0
- package/fesm2022/joanroucoux-cairn-ui-choice-chips.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-control.mjs +18 -0
- package/fesm2022/joanroucoux-cairn-ui-control.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-delta.mjs +103 -0
- package/fesm2022/joanroucoux-cairn-ui-delta.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-dialog.mjs +626 -0
- package/fesm2022/joanroucoux-cairn-ui-dialog.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-donut.mjs +347 -0
- package/fesm2022/joanroucoux-cairn-ui-donut.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-empty.mjs +47 -0
- package/fesm2022/joanroucoux-cairn-ui-empty.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-external-link.mjs +65 -0
- package/fesm2022/joanroucoux-cairn-ui-external-link.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-fact.mjs +99 -0
- package/fesm2022/joanroucoux-cairn-ui-fact.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-field.mjs +232 -0
- package/fesm2022/joanroucoux-cairn-ui-field.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-filter-chips.mjs +89 -0
- package/fesm2022/joanroucoux-cairn-ui-filter-chips.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-input.mjs +90 -0
- package/fesm2022/joanroucoux-cairn-ui-input.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-line-chart.mjs +844 -0
- package/fesm2022/joanroucoux-cairn-ui-line-chart.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-menu.mjs +241 -0
- package/fesm2022/joanroucoux-cairn-ui-menu.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-meter.mjs +86 -0
- package/fesm2022/joanroucoux-cairn-ui-meter.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-motion.mjs +415 -0
- package/fesm2022/joanroucoux-cairn-ui-motion.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-nav-item.mjs +45 -0
- package/fesm2022/joanroucoux-cairn-ui-nav-item.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-row.mjs +164 -0
- package/fesm2022/joanroucoux-cairn-ui-row.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-segmented.mjs +165 -0
- package/fesm2022/joanroucoux-cairn-ui-segmented.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-select.mjs +69 -0
- package/fesm2022/joanroucoux-cairn-ui-select.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-skeleton.mjs +91 -0
- package/fesm2022/joanroucoux-cairn-ui-skeleton.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-stale-link.mjs +43 -0
- package/fesm2022/joanroucoux-cairn-ui-stale-link.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-stat.mjs +96 -0
- package/fesm2022/joanroucoux-cairn-ui-stat.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-switch.mjs +37 -0
- package/fesm2022/joanroucoux-cairn-ui-switch.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-tab-bar.mjs +75 -0
- package/fesm2022/joanroucoux-cairn-ui-tab-bar.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-table.mjs +321 -0
- package/fesm2022/joanroucoux-cairn-ui-table.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui-toast.mjs +153 -0
- package/fesm2022/joanroucoux-cairn-ui-toast.mjs.map +1 -0
- package/fesm2022/joanroucoux-cairn-ui.mjs +0 -5386
- package/fesm2022/joanroucoux-cairn-ui.mjs.map +1 -1
- package/package.json +141 -1
- package/styles/motion.css +9 -0
- package/types/joanroucoux-cairn-ui-action-bar.d.ts +22 -0
- package/types/joanroucoux-cairn-ui-alert.d.ts +23 -0
- package/types/joanroucoux-cairn-ui-amount.d.ts +45 -0
- package/types/joanroucoux-cairn-ui-async.d.ts +58 -0
- package/types/joanroucoux-cairn-ui-avatar.d.ts +35 -0
- package/types/joanroucoux-cairn-ui-back-link.d.ts +22 -0
- package/types/joanroucoux-cairn-ui-badge.d.ts +23 -0
- package/types/joanroucoux-cairn-ui-button.d.ts +29 -0
- package/types/joanroucoux-cairn-ui-card.d.ts +36 -0
- package/types/joanroucoux-cairn-ui-choice-chips.d.ts +46 -0
- package/types/joanroucoux-cairn-ui-control.d.ts +27 -0
- package/types/joanroucoux-cairn-ui-delta.d.ts +38 -0
- package/types/joanroucoux-cairn-ui-dialog.d.ts +72 -0
- package/types/joanroucoux-cairn-ui-donut.d.ts +59 -0
- package/types/joanroucoux-cairn-ui-empty.d.ts +19 -0
- package/types/joanroucoux-cairn-ui-external-link.d.ts +15 -0
- package/types/joanroucoux-cairn-ui-fact.d.ts +44 -0
- package/types/joanroucoux-cairn-ui-field.d.ts +84 -0
- package/types/joanroucoux-cairn-ui-filter-chips.d.ts +28 -0
- package/types/joanroucoux-cairn-ui-input.d.ts +51 -0
- package/types/joanroucoux-cairn-ui-line-chart.d.ts +114 -0
- package/types/joanroucoux-cairn-ui-menu.d.ts +66 -0
- package/types/joanroucoux-cairn-ui-meter.d.ts +30 -0
- package/types/joanroucoux-cairn-ui-motion.d.ts +124 -0
- package/types/joanroucoux-cairn-ui-nav-item.d.ts +21 -0
- package/types/joanroucoux-cairn-ui-row.d.ts +78 -0
- package/types/joanroucoux-cairn-ui-segmented.d.ts +36 -0
- package/types/joanroucoux-cairn-ui-select.d.ts +23 -0
- package/types/joanroucoux-cairn-ui-skeleton.d.ts +26 -0
- package/types/joanroucoux-cairn-ui-stale-link.d.ts +24 -0
- package/types/joanroucoux-cairn-ui-stat.d.ts +32 -0
- package/types/joanroucoux-cairn-ui-switch.d.ts +17 -0
- package/types/joanroucoux-cairn-ui-tab-bar.d.ts +31 -0
- package/types/joanroucoux-cairn-ui-table.d.ts +139 -0
- package/types/joanroucoux-cairn-ui-toast.d.ts +51 -0
- 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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"}
|