@joanroucoux/cairn-ui 0.7.2 → 0.8.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.
Files changed (111) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/README.md +67 -0
  3. package/fesm2022/joanroucoux-cairn-ui-action-bar.mjs +54 -0
  4. package/fesm2022/joanroucoux-cairn-ui-action-bar.mjs.map +1 -0
  5. package/fesm2022/joanroucoux-cairn-ui-alert.mjs +129 -0
  6. package/fesm2022/joanroucoux-cairn-ui-alert.mjs.map +1 -0
  7. package/fesm2022/joanroucoux-cairn-ui-amount.mjs +114 -0
  8. package/fesm2022/joanroucoux-cairn-ui-amount.mjs.map +1 -0
  9. package/fesm2022/joanroucoux-cairn-ui-async.mjs +282 -0
  10. package/fesm2022/joanroucoux-cairn-ui-async.mjs.map +1 -0
  11. package/fesm2022/joanroucoux-cairn-ui-avatar.mjs +71 -0
  12. package/fesm2022/joanroucoux-cairn-ui-avatar.mjs.map +1 -0
  13. package/fesm2022/joanroucoux-cairn-ui-back-link.mjs +79 -0
  14. package/fesm2022/joanroucoux-cairn-ui-back-link.mjs.map +1 -0
  15. package/fesm2022/joanroucoux-cairn-ui-badge.mjs +48 -0
  16. package/fesm2022/joanroucoux-cairn-ui-badge.mjs.map +1 -0
  17. package/fesm2022/joanroucoux-cairn-ui-button.mjs +128 -0
  18. package/fesm2022/joanroucoux-cairn-ui-button.mjs.map +1 -0
  19. package/fesm2022/joanroucoux-cairn-ui-card.mjs +146 -0
  20. package/fesm2022/joanroucoux-cairn-ui-card.mjs.map +1 -0
  21. package/fesm2022/joanroucoux-cairn-ui-choice-chips.mjs +172 -0
  22. package/fesm2022/joanroucoux-cairn-ui-choice-chips.mjs.map +1 -0
  23. package/fesm2022/joanroucoux-cairn-ui-control.mjs +18 -0
  24. package/fesm2022/joanroucoux-cairn-ui-control.mjs.map +1 -0
  25. package/fesm2022/joanroucoux-cairn-ui-delta.mjs +103 -0
  26. package/fesm2022/joanroucoux-cairn-ui-delta.mjs.map +1 -0
  27. package/fesm2022/joanroucoux-cairn-ui-dialog.mjs +626 -0
  28. package/fesm2022/joanroucoux-cairn-ui-dialog.mjs.map +1 -0
  29. package/fesm2022/joanroucoux-cairn-ui-donut.mjs +347 -0
  30. package/fesm2022/joanroucoux-cairn-ui-donut.mjs.map +1 -0
  31. package/fesm2022/joanroucoux-cairn-ui-empty.mjs +47 -0
  32. package/fesm2022/joanroucoux-cairn-ui-empty.mjs.map +1 -0
  33. package/fesm2022/joanroucoux-cairn-ui-external-link.mjs +65 -0
  34. package/fesm2022/joanroucoux-cairn-ui-external-link.mjs.map +1 -0
  35. package/fesm2022/joanroucoux-cairn-ui-fact.mjs +99 -0
  36. package/fesm2022/joanroucoux-cairn-ui-fact.mjs.map +1 -0
  37. package/fesm2022/joanroucoux-cairn-ui-field.mjs +232 -0
  38. package/fesm2022/joanroucoux-cairn-ui-field.mjs.map +1 -0
  39. package/fesm2022/joanroucoux-cairn-ui-filter-chips.mjs +89 -0
  40. package/fesm2022/joanroucoux-cairn-ui-filter-chips.mjs.map +1 -0
  41. package/fesm2022/joanroucoux-cairn-ui-input.mjs +90 -0
  42. package/fesm2022/joanroucoux-cairn-ui-input.mjs.map +1 -0
  43. package/fesm2022/joanroucoux-cairn-ui-line-chart.mjs +844 -0
  44. package/fesm2022/joanroucoux-cairn-ui-line-chart.mjs.map +1 -0
  45. package/fesm2022/joanroucoux-cairn-ui-menu.mjs +241 -0
  46. package/fesm2022/joanroucoux-cairn-ui-menu.mjs.map +1 -0
  47. package/fesm2022/joanroucoux-cairn-ui-meter.mjs +86 -0
  48. package/fesm2022/joanroucoux-cairn-ui-meter.mjs.map +1 -0
  49. package/fesm2022/joanroucoux-cairn-ui-motion.mjs +415 -0
  50. package/fesm2022/joanroucoux-cairn-ui-motion.mjs.map +1 -0
  51. package/fesm2022/joanroucoux-cairn-ui-nav-item.mjs +45 -0
  52. package/fesm2022/joanroucoux-cairn-ui-nav-item.mjs.map +1 -0
  53. package/fesm2022/joanroucoux-cairn-ui-row.mjs +164 -0
  54. package/fesm2022/joanroucoux-cairn-ui-row.mjs.map +1 -0
  55. package/fesm2022/joanroucoux-cairn-ui-segmented.mjs +165 -0
  56. package/fesm2022/joanroucoux-cairn-ui-segmented.mjs.map +1 -0
  57. package/fesm2022/joanroucoux-cairn-ui-select.mjs +69 -0
  58. package/fesm2022/joanroucoux-cairn-ui-select.mjs.map +1 -0
  59. package/fesm2022/joanroucoux-cairn-ui-skeleton.mjs +91 -0
  60. package/fesm2022/joanroucoux-cairn-ui-skeleton.mjs.map +1 -0
  61. package/fesm2022/joanroucoux-cairn-ui-stale-link.mjs +43 -0
  62. package/fesm2022/joanroucoux-cairn-ui-stale-link.mjs.map +1 -0
  63. package/fesm2022/joanroucoux-cairn-ui-stat.mjs +96 -0
  64. package/fesm2022/joanroucoux-cairn-ui-stat.mjs.map +1 -0
  65. package/fesm2022/joanroucoux-cairn-ui-switch.mjs +37 -0
  66. package/fesm2022/joanroucoux-cairn-ui-switch.mjs.map +1 -0
  67. package/fesm2022/joanroucoux-cairn-ui-tab-bar.mjs +75 -0
  68. package/fesm2022/joanroucoux-cairn-ui-tab-bar.mjs.map +1 -0
  69. package/fesm2022/joanroucoux-cairn-ui-table.mjs +321 -0
  70. package/fesm2022/joanroucoux-cairn-ui-table.mjs.map +1 -0
  71. package/fesm2022/joanroucoux-cairn-ui-toast.mjs +153 -0
  72. package/fesm2022/joanroucoux-cairn-ui-toast.mjs.map +1 -0
  73. package/fesm2022/joanroucoux-cairn-ui.mjs +0 -5462
  74. package/fesm2022/joanroucoux-cairn-ui.mjs.map +1 -1
  75. package/package.json +141 -1
  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 -1440
@@ -0,0 +1,282 @@
1
+ import * as i0 from '@angular/core';
2
+ import { signal, inject, DestroyRef, effect, untracked, input, booleanAttribute, output, computed, Component } from '@angular/core';
3
+ import { holdTransitionsUntilRendered } from '@joanroucoux/cairn-ui/motion';
4
+
5
+ const SKELETON_DELAY_MS = 150;
6
+ const SKELETON_MIN_MS = 400;
7
+ /**
8
+ * Turns the live state of a call into the state to draw: `loading` only appears once the call has
9
+ * lasted 150 ms and, once shown, stays at least 400 ms, so a fast call never flashes a skeleton and a
10
+ * slow one never blinks. Until then the previous state stays on screen (a reload keeps its content);
11
+ * `null` means nothing to draw yet, for a first load still under 150 ms. Call it in an injection context.
12
+ *
13
+ * @example
14
+ * protected readonly shown = delayedState(this.state);
15
+ */
16
+ function delayedState(source) {
17
+ const displayed = signal(null, /* @ts-ignore */
18
+ ...(ngDevMode ? [{ debugName: "displayed" }] : /* istanbul ignore next */ []));
19
+ let timer;
20
+ let shownAt = 0;
21
+ const clear = () => {
22
+ clearTimeout(timer);
23
+ timer = undefined;
24
+ };
25
+ inject(DestroyRef).onDestroy(clear);
26
+ effect(() => {
27
+ const state = source();
28
+ untracked(() => {
29
+ if (state === 'loading') {
30
+ if (displayed() === 'loading') {
31
+ clear();
32
+ }
33
+ else {
34
+ timer = setTimeout(() => {
35
+ timer = undefined;
36
+ shownAt = Date.now();
37
+ displayed.set('loading');
38
+ }, SKELETON_DELAY_MS);
39
+ }
40
+ return;
41
+ }
42
+ clear();
43
+ const remaining = shownAt + SKELETON_MIN_MS - Date.now();
44
+ if (displayed() === 'loading' && remaining > 0) {
45
+ timer = setTimeout(() => {
46
+ timer = undefined;
47
+ displayed.set(untracked(source));
48
+ }, remaining);
49
+ }
50
+ else {
51
+ displayed.set(state);
52
+ }
53
+ });
54
+ });
55
+ return displayed.asReadonly();
56
+ }
57
+
58
+ /** Available async block states. `AsyncState` is derived from this tuple. */
59
+ const ASYNC_STATES = ['loading', 'error', 'empty', 'ready'];
60
+ /** Looks of the error block. `AsyncVariant` is derived from this tuple. */
61
+ const ASYNC_VARIANTS = ['elevated', 'plain', 'emphasis', 'inline'];
62
+ /** Alignments of the error block; `auto` is centred from 64rem. `AsyncAlign` is derived from this tuple. */
63
+ const ASYNC_ALIGNS = ['start', 'center', 'auto'];
64
+ const RETRY_BASE_CLASSES = 'relative inline-flex items-center justify-center rounded-control font-medium whitespace-nowrap cursor-pointer select-none touch-manipulation transition-[scale,background-color] [transition-duration:var(--duration-press),var(--duration-fast)] ease-out active:scale-(--press-scale) focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--ring) text-label';
65
+ const RETRY_OUTLINE_CLASSES = 'bg-(--card) text-(--foreground) shadow-[inset_0_0_0_1px_var(--border)] hover:bg-(--glow)';
66
+ const RETRY_PRIMARY_CLASSES = 'bg-(--primary) text-(--primary-foreground) hover:bg-(--primary-to)';
67
+ const RETRY_BLOCK_CLASSES = 'min-h-(--row-min) gap-2 pl-3 pr-4';
68
+ const RETRY_INLINE_CLASSES = 'h-11 pointer-fine:h-8 flex-none gap-1.5 pl-2.5 pr-3';
69
+ const ELEVATED_SURFACE_CLASSES = 'rounded-control bg-(--elevated)';
70
+ const CARD_SURFACE_CLASSES = 'rounded-container bg-(--card) shadow-[inset_0_0_0_1px_var(--border)]';
71
+ const BLOCK_SPECS = {
72
+ elevated: {
73
+ surface: ELEVATED_SURFACE_CLASSES,
74
+ gap: 'gap-1',
75
+ padding: { start: 'p-4', center: 'px-4 py-10', auto: 'p-4 lg:py-10' },
76
+ title: 'text-body font-medium',
77
+ message: { start: 'text-label', center: 'text-label', auto: 'text-label' },
78
+ retryMargin: { start: 'mt-2', center: 'mt-2', auto: 'mt-2' },
79
+ retry: RETRY_OUTLINE_CLASSES,
80
+ },
81
+ plain: {
82
+ surface: '',
83
+ gap: 'gap-1',
84
+ padding: { start: 'px-4 py-5', center: 'px-4 py-12', auto: 'px-4 py-5 lg:py-12' },
85
+ title: 'text-body leading-[normal] font-medium',
86
+ message: {
87
+ start: 'text-label leading-[normal]',
88
+ center: 'text-label leading-[normal]',
89
+ auto: 'text-label leading-[normal]',
90
+ },
91
+ retryMargin: { start: 'mt-2', center: 'mt-2', auto: 'mt-2' },
92
+ retry: RETRY_OUTLINE_CLASSES,
93
+ },
94
+ emphasis: {
95
+ surface: '',
96
+ gap: 'gap-3',
97
+ padding: { start: 'px-4 py-6', center: 'px-4 py-14', auto: 'px-4 py-6 lg:py-14' },
98
+ title: 'text-title font-semibold',
99
+ message: {
100
+ start: 'text-label',
101
+ center: 'text-body leading-[normal]',
102
+ auto: 'text-label lg:text-body lg:leading-[normal]',
103
+ },
104
+ retryMargin: { start: 'mt-1', center: 'mt-2', auto: 'mt-1 lg:mt-2' },
105
+ retry: RETRY_PRIMARY_CLASSES,
106
+ },
107
+ };
108
+ const ALIGN_CLASSES = {
109
+ start: 'items-start',
110
+ center: 'items-center text-center',
111
+ auto: 'items-start lg:items-center lg:text-center',
112
+ };
113
+ const INLINE_BOX_CLASSES = 'flex-row items-center justify-between gap-3 pt-1.5 pr-1.5 pb-2 pl-2.5';
114
+ /**
115
+ * The three states of a block that depends on a service call, next to the ready content.
116
+ *
117
+ * @example
118
+ * <ui-async [state]="totalState()" errorTitle="Total could not be loaded" errorMessage="The server did not answer." retryLabel="Retry" (retry)="reload()">
119
+ * <span asyncLoading><ui-skeleton shape="figure" /></span>
120
+ * <span asyncEmpty>Add a holding to see your total.</span>
121
+ * <span>{{ total() }}</span>
122
+ * </ui-async>
123
+ */
124
+ class UiAsync {
125
+ state = input.required(/* @ts-ignore */
126
+ ...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
127
+ errorTitle = input(/* @ts-ignore */
128
+ ...(ngDevMode ? [undefined, { debugName: "errorTitle" }] : /* istanbul ignore next */ []));
129
+ errorMessage = input(/* @ts-ignore */
130
+ ...(ngDevMode ? [undefined, { debugName: "errorMessage" }] : /* istanbul ignore next */ []));
131
+ retryLabel = input(/* @ts-ignore */
132
+ ...(ngDevMode ? [undefined, { debugName: "retryLabel" }] : /* istanbul ignore next */ []));
133
+ variant = input('elevated', /* @ts-ignore */
134
+ ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
135
+ align = input('start', /* @ts-ignore */
136
+ ...(ngDevMode ? [{ debugName: "align" }] : /* istanbul ignore next */ []));
137
+ card = input(false, { ...(ngDevMode ? { debugName: "card" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
138
+ fill = input(false, { ...(ngDevMode ? { debugName: "fill" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
139
+ retry = output();
140
+ shown = delayedState(this.state);
141
+ filling = computed(() => this.fill() && this.shown() === 'error', /* @ts-ignore */
142
+ ...(ngDevMode ? [{ debugName: "filling" }] : /* istanbul ignore next */ []));
143
+ iconSize = computed(() => (this.variant() === 'inline' ? 16 : 18), /* @ts-ignore */
144
+ ...(ngDevMode ? [{ debugName: "iconSize" }] : /* istanbul ignore next */ []));
145
+ #spec = computed(() => {
146
+ const variant = this.variant();
147
+ return variant === 'inline' ? null : BLOCK_SPECS[variant];
148
+ }, /* @ts-ignore */
149
+ ...(ngDevMode ? [{ debugName: "#spec" }] : /* istanbul ignore next */ []));
150
+ boxClasses = computed(() => {
151
+ const spec = this.#spec();
152
+ const fill = this.fill() ? ' flex-1 justify-center' : '';
153
+ if (spec === null) {
154
+ return `flex ${INLINE_BOX_CLASSES}${this.card() ? ` ${CARD_SURFACE_CLASSES}` : ''}${this.fill() ? ' flex-1' : ''}`;
155
+ }
156
+ const surface = this.card() ? CARD_SURFACE_CLASSES : spec.surface;
157
+ return `flex flex-col ${surface} ${spec.gap} ${ALIGN_CLASSES[this.align()]} ${spec.padding[this.align()]}${fill}`;
158
+ }, /* @ts-ignore */
159
+ ...(ngDevMode ? [{ debugName: "boxClasses" }] : /* istanbul ignore next */ []));
160
+ titleClasses = computed(() => this.#spec().title, /* @ts-ignore */
161
+ ...(ngDevMode ? [{ debugName: "titleClasses" }] : /* istanbul ignore next */ []));
162
+ messageClasses = computed(() => this.#spec().message[this.align()], /* @ts-ignore */
163
+ ...(ngDevMode ? [{ debugName: "messageClasses" }] : /* istanbul ignore next */ []));
164
+ retryButtonClasses = computed(() => {
165
+ const spec = this.#spec();
166
+ if (spec === null) {
167
+ return `${RETRY_BASE_CLASSES} ${RETRY_OUTLINE_CLASSES} ${RETRY_INLINE_CLASSES}`;
168
+ }
169
+ return `${RETRY_BASE_CLASSES} ${spec.retry} ${RETRY_BLOCK_CLASSES} ${spec.retryMargin[this.align()]}`;
170
+ }, /* @ts-ignore */
171
+ ...(ngDevMode ? [{ debugName: "retryButtonClasses" }] : /* istanbul ignore next */ []));
172
+ constructor() {
173
+ holdTransitionsUntilRendered();
174
+ }
175
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiAsync, deps: [], target: i0.ɵɵFactoryTarget.Component });
176
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: UiAsync, isStandalone: true, selector: "ui-async", inputs: { state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: true, transformFunction: null }, errorTitle: { classPropertyName: "errorTitle", publicName: "errorTitle", isSignal: true, isRequired: false, transformFunction: null }, errorMessage: { classPropertyName: "errorMessage", publicName: "errorMessage", isSignal: true, isRequired: false, transformFunction: null }, retryLabel: { classPropertyName: "retryLabel", publicName: "retryLabel", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null }, card: { classPropertyName: "card", publicName: "card", isSignal: true, isRequired: false, transformFunction: null }, fill: { classPropertyName: "fill", publicName: "fill", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { retry: "retry" }, host: { properties: { "attr.aria-busy": "state() === 'loading' || null", "class.flex": "filling()", "class.flex-col": "filling()" } }, ngImport: i0, template: `
177
+ @switch (shown()) {
178
+ @case (null) {}
179
+ @case ('loading') {
180
+ <ng-content select="[asyncLoading]" />
181
+ }
182
+ @case ('error') {
183
+ <div aria-live="polite" role="alert" [class]="boxClasses()">
184
+ @if (variant() === 'inline') {
185
+ <span class="text-label text-(--muted-foreground)">{{ errorMessage() }}</span>
186
+ } @else {
187
+ @if (variant() === 'emphasis') {
188
+ <h2 [class]="titleClasses()">{{ errorTitle() }}</h2>
189
+ } @else {
190
+ <span [class]="titleClasses()">{{ errorTitle() }}</span>
191
+ }
192
+ <span class="text-(--muted-foreground)" [class]="messageClasses()">{{ errorMessage() }}</span>
193
+ }
194
+ <button type="button" [class]="retryButtonClasses()" (click)="retry.emit()">
195
+ <svg
196
+ aria-hidden="true"
197
+ class="block flex-none fill-none stroke-current"
198
+ stroke-linecap="round"
199
+ stroke-linejoin="round"
200
+ stroke-width="1.75"
201
+ viewBox="0 0 24 24"
202
+ [attr.height]="iconSize()"
203
+ [attr.width]="iconSize()"
204
+ >
205
+ <path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
206
+ <path d="M21 3v5h-5" />
207
+ </svg>
208
+ {{ retryLabel() }}
209
+ </button>
210
+ </div>
211
+ }
212
+ @case ('empty') {
213
+ <ng-content select="[asyncEmpty]" />
214
+ }
215
+ @default {
216
+ <ng-content />
217
+ }
218
+ }
219
+ `, isInline: true });
220
+ }
221
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiAsync, decorators: [{
222
+ type: Component,
223
+ args: [{
224
+ selector: 'ui-async',
225
+ template: `
226
+ @switch (shown()) {
227
+ @case (null) {}
228
+ @case ('loading') {
229
+ <ng-content select="[asyncLoading]" />
230
+ }
231
+ @case ('error') {
232
+ <div aria-live="polite" role="alert" [class]="boxClasses()">
233
+ @if (variant() === 'inline') {
234
+ <span class="text-label text-(--muted-foreground)">{{ errorMessage() }}</span>
235
+ } @else {
236
+ @if (variant() === 'emphasis') {
237
+ <h2 [class]="titleClasses()">{{ errorTitle() }}</h2>
238
+ } @else {
239
+ <span [class]="titleClasses()">{{ errorTitle() }}</span>
240
+ }
241
+ <span class="text-(--muted-foreground)" [class]="messageClasses()">{{ errorMessage() }}</span>
242
+ }
243
+ <button type="button" [class]="retryButtonClasses()" (click)="retry.emit()">
244
+ <svg
245
+ aria-hidden="true"
246
+ class="block flex-none fill-none stroke-current"
247
+ stroke-linecap="round"
248
+ stroke-linejoin="round"
249
+ stroke-width="1.75"
250
+ viewBox="0 0 24 24"
251
+ [attr.height]="iconSize()"
252
+ [attr.width]="iconSize()"
253
+ >
254
+ <path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
255
+ <path d="M21 3v5h-5" />
256
+ </svg>
257
+ {{ retryLabel() }}
258
+ </button>
259
+ </div>
260
+ }
261
+ @case ('empty') {
262
+ <ng-content select="[asyncEmpty]" />
263
+ }
264
+ @default {
265
+ <ng-content />
266
+ }
267
+ }
268
+ `,
269
+ host: {
270
+ '[attr.aria-busy]': "state() === 'loading' || null",
271
+ '[class.flex]': 'filling()',
272
+ '[class.flex-col]': 'filling()',
273
+ },
274
+ }]
275
+ }], ctorParameters: () => [], propDecorators: { state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: true }] }], errorTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorTitle", required: false }] }], errorMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMessage", required: false }] }], retryLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "retryLabel", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], align: [{ type: i0.Input, args: [{ isSignal: true, alias: "align", required: false }] }], card: [{ type: i0.Input, args: [{ isSignal: true, alias: "card", required: false }] }], fill: [{ type: i0.Input, args: [{ isSignal: true, alias: "fill", required: false }] }], retry: [{ type: i0.Output, args: ["retry"] }] } });
276
+
277
+ /**
278
+ * Generated bundle index. Do not edit.
279
+ */
280
+
281
+ export { ASYNC_ALIGNS, ASYNC_STATES, ASYNC_VARIANTS, UiAsync, delayedState };
282
+ //# sourceMappingURL=joanroucoux-cairn-ui-async.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"joanroucoux-cairn-ui-async.mjs","sources":["../../../projects/ui/async/delayed-state.ts","../../../projects/ui/async/async.ts","../../../projects/ui/async/joanroucoux-cairn-ui-async.ts"],"sourcesContent":["import { DestroyRef, type Signal, effect, inject, signal, untracked } from '@angular/core';\n\nimport type { AsyncState } from './async';\n\nexport const SKELETON_DELAY_MS = 150;\nexport const SKELETON_MIN_MS = 400;\n\n/**\n * Turns the live state of a call into the state to draw: `loading` only appears once the call has\n * lasted 150 ms and, once shown, stays at least 400 ms, so a fast call never flashes a skeleton and a\n * slow one never blinks. Until then the previous state stays on screen (a reload keeps its content);\n * `null` means nothing to draw yet, for a first load still under 150 ms. Call it in an injection context.\n *\n * @example\n * protected readonly shown = delayedState(this.state);\n */\nexport function delayedState(source: Signal<AsyncState>): Signal<AsyncState | null> {\n const displayed = signal<AsyncState | null>(null);\n let timer: ReturnType<typeof setTimeout> | undefined;\n let shownAt = 0;\n\n const clear = (): void => {\n clearTimeout(timer);\n timer = undefined;\n };\n\n inject(DestroyRef).onDestroy(clear);\n\n effect(() => {\n const state = source();\n\n untracked(() => {\n if (state === 'loading') {\n if (displayed() === 'loading') {\n clear();\n } else {\n timer = setTimeout(() => {\n timer = undefined;\n shownAt = Date.now();\n displayed.set('loading');\n }, SKELETON_DELAY_MS);\n }\n\n return;\n }\n\n clear();\n\n const remaining = shownAt + SKELETON_MIN_MS - Date.now();\n\n if (displayed() === 'loading' && remaining > 0) {\n timer = setTimeout(() => {\n timer = undefined;\n displayed.set(untracked(source));\n }, remaining);\n } else {\n displayed.set(state);\n }\n });\n });\n\n return displayed.asReadonly();\n}\n","import { Component, booleanAttribute, computed, input, output } from '@angular/core';\n\nimport { holdTransitionsUntilRendered } from '@joanroucoux/cairn-ui/motion';\n\nimport { delayedState } from './delayed-state';\n\n/** Available async block states. `AsyncState` is derived from this tuple. */\nexport const ASYNC_STATES = ['loading', 'error', 'empty', 'ready'] as const;\nexport type AsyncState = (typeof ASYNC_STATES)[number];\n\n/** Looks of the error block. `AsyncVariant` is derived from this tuple. */\nexport const ASYNC_VARIANTS = ['elevated', 'plain', 'emphasis', 'inline'] as const;\nexport type AsyncVariant = (typeof ASYNC_VARIANTS)[number];\n\n/** Alignments of the error block; `auto` is centred from 64rem. `AsyncAlign` is derived from this tuple. */\nexport const ASYNC_ALIGNS = ['start', 'center', 'auto'] as const;\nexport type AsyncAlign = (typeof ASYNC_ALIGNS)[number];\n\nconst RETRY_BASE_CLASSES =\n 'relative inline-flex items-center justify-center rounded-control font-medium whitespace-nowrap cursor-pointer select-none touch-manipulation transition-[scale,background-color] [transition-duration:var(--duration-press),var(--duration-fast)] ease-out active:scale-(--press-scale) focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--ring) text-label';\n\nconst RETRY_OUTLINE_CLASSES =\n 'bg-(--card) text-(--foreground) shadow-[inset_0_0_0_1px_var(--border)] hover:bg-(--glow)';\n\nconst RETRY_PRIMARY_CLASSES = 'bg-(--primary) text-(--primary-foreground) hover:bg-(--primary-to)';\n\nconst RETRY_BLOCK_CLASSES = 'min-h-(--row-min) gap-2 pl-3 pr-4';\n\nconst RETRY_INLINE_CLASSES = 'h-11 pointer-fine:h-8 flex-none gap-1.5 pl-2.5 pr-3';\n\nconst ELEVATED_SURFACE_CLASSES = 'rounded-control bg-(--elevated)';\n\nconst CARD_SURFACE_CLASSES = 'rounded-container bg-(--card) shadow-[inset_0_0_0_1px_var(--border)]';\n\ntype BlockSpec = {\n surface: string;\n gap: string;\n padding: Record<AsyncAlign, string>;\n title: string;\n message: Record<AsyncAlign, string>;\n retryMargin: Record<AsyncAlign, string>;\n retry: string;\n};\n\nconst BLOCK_SPECS: Record<Exclude<AsyncVariant, 'inline'>, BlockSpec> = {\n elevated: {\n surface: ELEVATED_SURFACE_CLASSES,\n gap: 'gap-1',\n padding: { start: 'p-4', center: 'px-4 py-10', auto: 'p-4 lg:py-10' },\n title: 'text-body font-medium',\n message: { start: 'text-label', center: 'text-label', auto: 'text-label' },\n retryMargin: { start: 'mt-2', center: 'mt-2', auto: 'mt-2' },\n retry: RETRY_OUTLINE_CLASSES,\n },\n plain: {\n surface: '',\n gap: 'gap-1',\n padding: { start: 'px-4 py-5', center: 'px-4 py-12', auto: 'px-4 py-5 lg:py-12' },\n title: 'text-body leading-[normal] font-medium',\n message: {\n start: 'text-label leading-[normal]',\n center: 'text-label leading-[normal]',\n auto: 'text-label leading-[normal]',\n },\n retryMargin: { start: 'mt-2', center: 'mt-2', auto: 'mt-2' },\n retry: RETRY_OUTLINE_CLASSES,\n },\n emphasis: {\n surface: '',\n gap: 'gap-3',\n padding: { start: 'px-4 py-6', center: 'px-4 py-14', auto: 'px-4 py-6 lg:py-14' },\n title: 'text-title font-semibold',\n message: {\n start: 'text-label',\n center: 'text-body leading-[normal]',\n auto: 'text-label lg:text-body lg:leading-[normal]',\n },\n retryMargin: { start: 'mt-1', center: 'mt-2', auto: 'mt-1 lg:mt-2' },\n retry: RETRY_PRIMARY_CLASSES,\n },\n};\n\nconst ALIGN_CLASSES: Record<AsyncAlign, string> = {\n start: 'items-start',\n center: 'items-center text-center',\n auto: 'items-start lg:items-center lg:text-center',\n};\n\nconst INLINE_BOX_CLASSES = 'flex-row items-center justify-between gap-3 pt-1.5 pr-1.5 pb-2 pl-2.5';\n\n/**\n * The three states of a block that depends on a service call, next to the ready content.\n *\n * @example\n * <ui-async [state]=\"totalState()\" errorTitle=\"Total could not be loaded\" errorMessage=\"The server did not answer.\" retryLabel=\"Retry\" (retry)=\"reload()\">\n * <span asyncLoading><ui-skeleton shape=\"figure\" /></span>\n * <span asyncEmpty>Add a holding to see your total.</span>\n * <span>{{ total() }}</span>\n * </ui-async>\n */\n@Component({\n selector: 'ui-async',\n template: `\n @switch (shown()) {\n @case (null) {}\n @case ('loading') {\n <ng-content select=\"[asyncLoading]\" />\n }\n @case ('error') {\n <div aria-live=\"polite\" role=\"alert\" [class]=\"boxClasses()\">\n @if (variant() === 'inline') {\n <span class=\"text-label text-(--muted-foreground)\">{{ errorMessage() }}</span>\n } @else {\n @if (variant() === 'emphasis') {\n <h2 [class]=\"titleClasses()\">{{ errorTitle() }}</h2>\n } @else {\n <span [class]=\"titleClasses()\">{{ errorTitle() }}</span>\n }\n <span class=\"text-(--muted-foreground)\" [class]=\"messageClasses()\">{{ errorMessage() }}</span>\n }\n <button type=\"button\" [class]=\"retryButtonClasses()\" (click)=\"retry.emit()\">\n <svg\n aria-hidden=\"true\"\n class=\"block flex-none fill-none stroke-current\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.75\"\n viewBox=\"0 0 24 24\"\n [attr.height]=\"iconSize()\"\n [attr.width]=\"iconSize()\"\n >\n <path d=\"M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8\" />\n <path d=\"M21 3v5h-5\" />\n </svg>\n {{ retryLabel() }}\n </button>\n </div>\n }\n @case ('empty') {\n <ng-content select=\"[asyncEmpty]\" />\n }\n @default {\n <ng-content />\n }\n }\n `,\n host: {\n '[attr.aria-busy]': \"state() === 'loading' || null\",\n '[class.flex]': 'filling()',\n '[class.flex-col]': 'filling()',\n },\n})\nexport class UiAsync {\n readonly state = input.required<AsyncState>();\n readonly errorTitle = input<string>();\n readonly errorMessage = input<string>();\n readonly retryLabel = input<string>();\n readonly variant = input<AsyncVariant>('elevated');\n readonly align = input<AsyncAlign>('start');\n readonly card = input(false, { transform: booleanAttribute });\n readonly fill = input(false, { transform: booleanAttribute });\n readonly retry = output<void>();\n\n protected readonly shown = delayedState(this.state);\n\n protected readonly filling = computed(() => this.fill() && this.shown() === 'error');\n\n protected readonly iconSize = computed(() => (this.variant() === 'inline' ? 16 : 18));\n\n readonly #spec = computed(() => {\n const variant = this.variant();\n\n return variant === 'inline' ? null : BLOCK_SPECS[variant];\n });\n\n protected readonly boxClasses = computed(() => {\n const spec = this.#spec();\n const fill = this.fill() ? ' flex-1 justify-center' : '';\n\n if (spec === null) {\n return `flex ${INLINE_BOX_CLASSES}${this.card() ? ` ${CARD_SURFACE_CLASSES}` : ''}${this.fill() ? ' flex-1' : ''}`;\n }\n\n const surface = this.card() ? CARD_SURFACE_CLASSES : spec.surface;\n\n return `flex flex-col ${surface} ${spec.gap} ${ALIGN_CLASSES[this.align()]} ${spec.padding[this.align()]}${fill}`;\n });\n\n protected readonly titleClasses = computed(() => this.#spec()!.title);\n\n protected readonly messageClasses = computed(() => this.#spec()!.message[this.align()]);\n\n protected readonly retryButtonClasses = computed(() => {\n const spec = this.#spec();\n\n if (spec === null) {\n return `${RETRY_BASE_CLASSES} ${RETRY_OUTLINE_CLASSES} ${RETRY_INLINE_CLASSES}`;\n }\n\n return `${RETRY_BASE_CLASSES} ${spec.retry} ${RETRY_BLOCK_CLASSES} ${spec.retryMargin[this.align()]}`;\n });\n\n constructor() {\n holdTransitionsUntilRendered();\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input, computed, Component, Directive } from '@angular/core';
3
+
4
+ /** Available avatar sizes. `AvatarSize` is derived from this tuple. */
5
+ const AVATAR_SIZES = ['sm', 'md', 'lg', 'auto'];
6
+ const BASE_CLASSES = 'inline-flex items-center justify-center rounded-pill bg-(--muted) font-medium tabular-nums';
7
+ const SIZE_CLASSES = {
8
+ sm: 'size-8 text-caption',
9
+ md: 'size-[34px] text-label',
10
+ lg: 'size-14 text-title',
11
+ auto: 'size-[34px] text-label lg:size-8 lg:text-caption',
12
+ };
13
+ /**
14
+ * Initials in a disc, standing in for a person. Single-user application: there is exactly one.
15
+ *
16
+ * @example
17
+ * <ui-avatar initials="JR" label="My account" />
18
+ */
19
+ class UiAvatar {
20
+ initials = input.required(/* @ts-ignore */
21
+ ...(ngDevMode ? [{ debugName: "initials" }] : /* istanbul ignore next */ []));
22
+ label = input.required(/* @ts-ignore */
23
+ ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
24
+ size = input('md', /* @ts-ignore */
25
+ ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
26
+ classes = computed(() => `${BASE_CLASSES} ${SIZE_CLASSES[this.size()]}`, /* @ts-ignore */
27
+ ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
28
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiAvatar, deps: [], target: i0.ɵɵFactoryTarget.Component });
29
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.7", type: UiAvatar, isStandalone: true, selector: "ui-avatar", inputs: { initials: { classPropertyName: "initials", publicName: "initials", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "img" }, properties: { "attr.aria-label": "label()", "class": "classes()" } }, ngImport: i0, template: '<span aria-hidden="true">{{ initials() }}</span>', isInline: true });
30
+ }
31
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiAvatar, decorators: [{
32
+ type: Component,
33
+ args: [{
34
+ selector: 'ui-avatar',
35
+ template: '<span aria-hidden="true">{{ initials() }}</span>',
36
+ host: {
37
+ role: 'img',
38
+ '[attr.aria-label]': 'label()',
39
+ '[class]': 'classes()',
40
+ },
41
+ }]
42
+ }], propDecorators: { initials: [{ type: i0.Input, args: [{ isSignal: true, alias: "initials", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }] } });
43
+ /**
44
+ * Round hit area around a `ui-avatar` that links somewhere: 44px on touch, 40px from 64rem, with a
45
+ * glow on hover and the press scale. On the current page (`aria-current="page"`, which `routerLinkActive`
46
+ * sets through `ariaCurrentWhenActive`) it holds a soft halo, drawn at once when the route changes. The avatar
47
+ * itself keeps its own size.
48
+ *
49
+ * @example
50
+ * <a uiAvatarLink routerLink="/profile"><ui-avatar initials="JR" label="Profile" size="auto" /></a>
51
+ */
52
+ class UiAvatarLink {
53
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiAvatarLink, deps: [], target: i0.ɵɵFactoryTarget.Directive });
54
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.7", type: UiAvatarLink, isStandalone: true, selector: "a[uiAvatarLink], button[uiAvatarLink]", host: { classAttribute: "rounded-pill grid size-11 lg:size-10 place-items-center cursor-pointer select-none touch-manipulation transition-[scale,background-color] [transition-duration:var(--duration-press),var(--duration-fast)] ease-out hover:bg-(--glow) aria-[current=page]:bg-transparent aria-[current=page]:bg-[image:linear-gradient(var(--soft),var(--soft))] active:scale-(--press-scale) focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--ring)" }, ngImport: i0 });
55
+ }
56
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiAvatarLink, decorators: [{
57
+ type: Directive,
58
+ args: [{
59
+ selector: 'a[uiAvatarLink], button[uiAvatarLink]',
60
+ host: {
61
+ class: 'rounded-pill grid size-11 lg:size-10 place-items-center cursor-pointer select-none touch-manipulation transition-[scale,background-color] [transition-duration:var(--duration-press),var(--duration-fast)] ease-out hover:bg-(--glow) aria-[current=page]:bg-transparent aria-[current=page]:bg-[image:linear-gradient(var(--soft),var(--soft))] active:scale-(--press-scale) focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--ring)',
62
+ },
63
+ }]
64
+ }] });
65
+
66
+ /**
67
+ * Generated bundle index. Do not edit.
68
+ */
69
+
70
+ export { AVATAR_SIZES, UiAvatar, UiAvatarLink };
71
+ //# sourceMappingURL=joanroucoux-cairn-ui-avatar.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"joanroucoux-cairn-ui-avatar.mjs","sources":["../../../projects/ui/avatar/avatar.ts","../../../projects/ui/avatar/joanroucoux-cairn-ui-avatar.ts"],"sourcesContent":["import { Component, Directive, computed, input } from '@angular/core';\n\n/** Available avatar sizes. `AvatarSize` is derived from this tuple. */\nexport const AVATAR_SIZES = ['sm', 'md', 'lg', 'auto'] as const;\nexport type AvatarSize = (typeof AVATAR_SIZES)[number];\n\nconst BASE_CLASSES = 'inline-flex items-center justify-center rounded-pill bg-(--muted) font-medium tabular-nums';\n\nconst SIZE_CLASSES: Record<AvatarSize, string> = {\n sm: 'size-8 text-caption',\n md: 'size-[34px] text-label',\n lg: 'size-14 text-title',\n auto: 'size-[34px] text-label lg:size-8 lg:text-caption',\n};\n\n/**\n * Initials in a disc, standing in for a person. Single-user application: there is exactly one.\n *\n * @example\n * <ui-avatar initials=\"JR\" label=\"My account\" />\n */\n@Component({\n selector: 'ui-avatar',\n template: '<span aria-hidden=\"true\">{{ initials() }}</span>',\n host: {\n role: 'img',\n '[attr.aria-label]': 'label()',\n '[class]': 'classes()',\n },\n})\nexport class UiAvatar {\n readonly initials = input.required<string>();\n readonly label = input.required<string>();\n readonly size = input<AvatarSize>('md');\n\n protected readonly classes = computed(() => `${BASE_CLASSES} ${SIZE_CLASSES[this.size()]}`);\n}\n\n/**\n * Round hit area around a `ui-avatar` that links somewhere: 44px on touch, 40px from 64rem, with a\n * glow on hover and the press scale. On the current page (`aria-current=\"page\"`, which `routerLinkActive`\n * sets through `ariaCurrentWhenActive`) it holds a soft halo, drawn at once when the route changes. The avatar\n * itself keeps its own size.\n *\n * @example\n * <a uiAvatarLink routerLink=\"/profile\"><ui-avatar initials=\"JR\" label=\"Profile\" size=\"auto\" /></a>\n */\n@Directive({\n selector: 'a[uiAvatarLink], button[uiAvatarLink]',\n host: {\n class:\n 'rounded-pill grid size-11 lg:size-10 place-items-center cursor-pointer select-none touch-manipulation transition-[scale,background-color] [transition-duration:var(--duration-press),var(--duration-fast)] ease-out hover:bg-(--glow) aria-[current=page]:bg-transparent aria-[current=page]:bg-[image:linear-gradient(var(--soft),var(--soft))] active:scale-(--press-scale) focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--ring)',\n },\n})\nexport class UiAvatarLink {}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;AAEA;AACO,MAAM,YAAY,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM;AAGrD,MAAM,YAAY,GAAG,4FAA4F;AAEjH,MAAM,YAAY,GAA+B;AAC/C,IAAA,EAAE,EAAE,qBAAqB;AACzB,IAAA,EAAE,EAAE,wBAAwB;AAC5B,IAAA,EAAE,EAAE,oBAAoB;AACxB,IAAA,IAAI,EAAE,kDAAkD;CACzD;AAED;;;;;AAKG;MAUU,QAAQ,CAAA;IACV,QAAQ,GAAG,KAAK,CAAC,QAAQ;iFAAU;IACnC,KAAK,GAAG,KAAK,CAAC,QAAQ;8EAAU;IAChC,IAAI,GAAG,KAAK,CAAa,IAAI;6EAAC;AAEpB,IAAA,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAA,EAAG,YAAY,CAAA,CAAA,EAAI,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAA,CAAE;gFAAC;uGALhF,QAAQ,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAR,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,QAAQ,0iBAPT,kDAAkD,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA;;2FAOjD,QAAQ,EAAA,UAAA,EAAA,CAAA;kBATpB,SAAS;AAAC,YAAA,IAAA,EAAA,CAAA;AACT,oBAAA,QAAQ,EAAE,WAAW;AACrB,oBAAA,QAAQ,EAAE,kDAAkD;AAC5D,oBAAA,IAAI,EAAE;AACJ,wBAAA,IAAI,EAAE,KAAK;AACX,wBAAA,mBAAmB,EAAE,SAAS;AAC9B,wBAAA,SAAS,EAAE,WAAW;AACvB,qBAAA;AACF,iBAAA;;AASD;;;;;;;;AAQG;MAQU,YAAY,CAAA;uGAAZ,YAAY,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;2FAAZ,YAAY,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,uCAAA,EAAA,IAAA,EAAA,EAAA,cAAA,EAAA,qcAAA,EAAA,EAAA,QAAA,EAAA,EAAA,EAAA,CAAA;;2FAAZ,YAAY,EAAA,UAAA,EAAA,CAAA;kBAPxB,SAAS;AAAC,YAAA,IAAA,EAAA,CAAA;AACT,oBAAA,QAAQ,EAAE,uCAAuC;AACjD,oBAAA,IAAI,EAAE;AACJ,wBAAA,KAAK,EACH,qcAAqc;AACxc,qBAAA;AACF,iBAAA;;;ACrDD;;AAEG;;;;"}
@@ -0,0 +1,79 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input, computed, Component } from '@angular/core';
3
+ import { holdTransitionsUntilRendered } from '@joanroucoux/cairn-ui/motion';
4
+
5
+ /** Available back link sizes. `BackLinkSize` is derived from this tuple. */
6
+ const BACK_LINK_SIZES = ['md', 'sm', 'header'];
7
+ const BASE_CLASSES = 'inline-flex items-center gap-0.5 rounded-control text-body font-medium whitespace-nowrap cursor-pointer select-none touch-manipulation transition-[scale,background-color,color] [transition-duration:var(--duration-press),var(--duration-fast),var(--duration-fast)] ease-out active:scale-(--press-scale) focus-visible:outline-2 focus-visible:outline-(--ring)';
8
+ const SIZE_CLASSES = {
9
+ md: 'h-11 pr-3 pl-1 text-(--foreground) focus-visible:outline-offset-2',
10
+ sm: 'h-9 pr-2 pl-0.5 text-(--muted-foreground) hover:bg-(--glow) hover:text-(--foreground) focus-visible:outline-offset-2',
11
+ header: 'h-11 pr-3 pl-1 text-(--foreground) focus-visible:-outline-offset-2',
12
+ };
13
+ const CHEVRON = { md: 22, sm: 22, header: 24 };
14
+ /**
15
+ * Link back to the parent screen: a chevron and the name of the destination.
16
+ *
17
+ * @example
18
+ * <a ui-back-link routerLink="/accounts">Accounts</a>
19
+ */
20
+ class UiBackLink {
21
+ size = input('md', /* @ts-ignore */
22
+ ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
23
+ chevron = computed(() => CHEVRON[this.size()], /* @ts-ignore */
24
+ ...(ngDevMode ? [{ debugName: "chevron" }] : /* istanbul ignore next */ []));
25
+ classes = computed(() => `${BASE_CLASSES} ${SIZE_CLASSES[this.size()]}`, /* @ts-ignore */
26
+ ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
27
+ constructor() {
28
+ holdTransitionsUntilRendered();
29
+ }
30
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiBackLink, deps: [], target: i0.ɵɵFactoryTarget.Component });
31
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.7", type: UiBackLink, isStandalone: true, selector: "a[ui-back-link]", inputs: { size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "classes()" } }, ngImport: i0, template: `
32
+ <svg
33
+ aria-hidden="true"
34
+ fill="none"
35
+ stroke="currentColor"
36
+ stroke-linecap="round"
37
+ stroke-linejoin="round"
38
+ stroke-width="1.75"
39
+ viewBox="0 0 24 24"
40
+ [attr.height]="chevron()"
41
+ [attr.width]="chevron()"
42
+ >
43
+ <path d="m15 18-6-6 6-6" />
44
+ </svg>
45
+ <ng-content />
46
+ `, isInline: true });
47
+ }
48
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiBackLink, decorators: [{
49
+ type: Component,
50
+ args: [{
51
+ selector: 'a[ui-back-link]',
52
+ template: `
53
+ <svg
54
+ aria-hidden="true"
55
+ fill="none"
56
+ stroke="currentColor"
57
+ stroke-linecap="round"
58
+ stroke-linejoin="round"
59
+ stroke-width="1.75"
60
+ viewBox="0 0 24 24"
61
+ [attr.height]="chevron()"
62
+ [attr.width]="chevron()"
63
+ >
64
+ <path d="m15 18-6-6 6-6" />
65
+ </svg>
66
+ <ng-content />
67
+ `,
68
+ host: {
69
+ '[class]': 'classes()',
70
+ },
71
+ }]
72
+ }], ctorParameters: () => [], propDecorators: { size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }] } });
73
+
74
+ /**
75
+ * Generated bundle index. Do not edit.
76
+ */
77
+
78
+ export { BACK_LINK_SIZES, UiBackLink };
79
+ //# sourceMappingURL=joanroucoux-cairn-ui-back-link.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"joanroucoux-cairn-ui-back-link.mjs","sources":["../../../projects/ui/back-link/back-link.ts","../../../projects/ui/back-link/joanroucoux-cairn-ui-back-link.ts"],"sourcesContent":["import { Component, computed, input } from '@angular/core';\n\nimport { holdTransitionsUntilRendered } from '@joanroucoux/cairn-ui/motion';\n\n/** Available back link sizes. `BackLinkSize` is derived from this tuple. */\nexport const BACK_LINK_SIZES = ['md', 'sm', 'header'] as const;\nexport type BackLinkSize = (typeof BACK_LINK_SIZES)[number];\n\nconst BASE_CLASSES =\n 'inline-flex items-center gap-0.5 rounded-control text-body font-medium whitespace-nowrap cursor-pointer select-none touch-manipulation transition-[scale,background-color,color] [transition-duration:var(--duration-press),var(--duration-fast),var(--duration-fast)] ease-out active:scale-(--press-scale) focus-visible:outline-2 focus-visible:outline-(--ring)';\n\nconst SIZE_CLASSES: Record<BackLinkSize, string> = {\n md: 'h-11 pr-3 pl-1 text-(--foreground) focus-visible:outline-offset-2',\n sm: 'h-9 pr-2 pl-0.5 text-(--muted-foreground) hover:bg-(--glow) hover:text-(--foreground) focus-visible:outline-offset-2',\n header: 'h-11 pr-3 pl-1 text-(--foreground) focus-visible:-outline-offset-2',\n};\n\nconst CHEVRON: Record<BackLinkSize, number> = { md: 22, sm: 22, header: 24 };\n\n/**\n * Link back to the parent screen: a chevron and the name of the destination.\n *\n * @example\n * <a ui-back-link routerLink=\"/accounts\">Accounts</a>\n */\n@Component({\n selector: 'a[ui-back-link]',\n template: `\n <svg\n aria-hidden=\"true\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.75\"\n viewBox=\"0 0 24 24\"\n [attr.height]=\"chevron()\"\n [attr.width]=\"chevron()\"\n >\n <path d=\"m15 18-6-6 6-6\" />\n </svg>\n <ng-content />\n `,\n host: {\n '[class]': 'classes()',\n },\n})\nexport class UiBackLink {\n readonly size = input<BackLinkSize>('md');\n\n protected readonly chevron = computed(() => CHEVRON[this.size()]);\n protected readonly classes = computed(() => `${BASE_CLASSES} ${SIZE_CLASSES[this.size()]}`);\n\n constructor() {\n holdTransitionsUntilRendered();\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;AAIA;AACO,MAAM,eAAe,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ;AAGpD,MAAM,YAAY,GAChB,qWAAqW;AAEvW,MAAM,YAAY,GAAiC;AACjD,IAAA,EAAE,EAAE,mEAAmE;AACvE,IAAA,EAAE,EAAE,sHAAsH;AAC1H,IAAA,MAAM,EAAE,oEAAoE;CAC7E;AAED,MAAM,OAAO,GAAiC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE;AAE5E;;;;;AAKG;MAuBU,UAAU,CAAA;IACZ,IAAI,GAAG,KAAK,CAAe,IAAI;6EAAC;AAEtB,IAAA,OAAO,GAAG,QAAQ,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;gFAAC;AAC9C,IAAA,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAA,EAAG,YAAY,CAAA,CAAA,EAAI,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAA,CAAE;gFAAC;AAE3F,IAAA,WAAA,GAAA;AACE,QAAA,4BAA4B,EAAE;IAChC;uGARW,UAAU,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAV,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAU,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,iBAAA,EAAA,MAAA,EAAA,EAAA,IAAA,EAAA,EAAA,iBAAA,EAAA,MAAA,EAAA,UAAA,EAAA,MAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,IAAA,EAAA,EAAA,UAAA,EAAA,EAAA,OAAA,EAAA,WAAA,EAAA,EAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EApBX;;;;;;;;;;;;;;;AAeT,EAAA,CAAA,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA;;2FAKU,UAAU,EAAA,UAAA,EAAA,CAAA;kBAtBtB,SAAS;AAAC,YAAA,IAAA,EAAA,CAAA;AACT,oBAAA,QAAQ,EAAE,iBAAiB;AAC3B,oBAAA,QAAQ,EAAE;;;;;;;;;;;;;;;AAeT,EAAA,CAAA;AACD,oBAAA,IAAI,EAAE;AACJ,wBAAA,SAAS,EAAE,WAAW;AACvB,qBAAA;AACF,iBAAA;;;AC9CD;;AAEG;;;;"}
@@ -0,0 +1,48 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input, computed, Component } from '@angular/core';
3
+
4
+ /** Available badge variants. `BadgeVariant` is derived from this tuple. */
5
+ const BADGE_VARIANTS = ['neutral', 'outline'];
6
+ const BADGE_SIZES = ['md', 'sm'];
7
+ const BASE_CLASSES = 'inline-flex items-center rounded-pill text-caption font-medium';
8
+ const SIZE_CLASSES = {
9
+ md: 'h-6 px-2.5',
10
+ sm: 'h-[22px] px-2',
11
+ };
12
+ const VARIANT_CLASSES = {
13
+ neutral: 'bg-(--muted) text-(--foreground)',
14
+ outline: 'shadow-[inset_0_0_0_1px_var(--border)] text-(--foreground)',
15
+ };
16
+ /**
17
+ * Small status descriptor for counts, states or labels, such as an instrument's asset class.
18
+ *
19
+ * @example
20
+ * <ui-badge>ETF</ui-badge>
21
+ */
22
+ class UiBadge {
23
+ variant = input('neutral', /* @ts-ignore */
24
+ ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
25
+ size = input('md', /* @ts-ignore */
26
+ ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
27
+ classes = computed(() => `${BASE_CLASSES} ${SIZE_CLASSES[this.size()]} ${VARIANT_CLASSES[this.variant()]}`, /* @ts-ignore */
28
+ ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
29
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiBadge, deps: [], target: i0.ɵɵFactoryTarget.Component });
30
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.7", type: UiBadge, isStandalone: true, selector: "ui-badge", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "classes()" } }, ngImport: i0, template: '<ng-content />', isInline: true });
31
+ }
32
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiBadge, decorators: [{
33
+ type: Component,
34
+ args: [{
35
+ selector: 'ui-badge',
36
+ template: '<ng-content />',
37
+ host: {
38
+ '[class]': 'classes()',
39
+ },
40
+ }]
41
+ }], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }] } });
42
+
43
+ /**
44
+ * Generated bundle index. Do not edit.
45
+ */
46
+
47
+ export { BADGE_SIZES, BADGE_VARIANTS, UiBadge };
48
+ //# sourceMappingURL=joanroucoux-cairn-ui-badge.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"joanroucoux-cairn-ui-badge.mjs","sources":["../../../projects/ui/badge/badge.ts","../../../projects/ui/badge/joanroucoux-cairn-ui-badge.ts"],"sourcesContent":["import { Component, computed, input } from '@angular/core';\n\n/** Available badge variants. `BadgeVariant` is derived from this tuple. */\nexport const BADGE_VARIANTS = ['neutral', 'outline'] as const;\nexport type BadgeVariant = (typeof BADGE_VARIANTS)[number];\n\nexport const BADGE_SIZES = ['md', 'sm'] as const;\nexport type BadgeSize = (typeof BADGE_SIZES)[number];\n\nconst BASE_CLASSES = 'inline-flex items-center rounded-pill text-caption font-medium';\n\nconst SIZE_CLASSES: Record<BadgeSize, string> = {\n md: 'h-6 px-2.5',\n sm: 'h-[22px] px-2',\n};\n\nconst VARIANT_CLASSES: Record<BadgeVariant, string> = {\n neutral: 'bg-(--muted) text-(--foreground)',\n outline: 'shadow-[inset_0_0_0_1px_var(--border)] text-(--foreground)',\n};\n\n/**\n * Small status descriptor for counts, states or labels, such as an instrument's asset class.\n *\n * @example\n * <ui-badge>ETF</ui-badge>\n */\n@Component({\n selector: 'ui-badge',\n template: '<ng-content />',\n host: {\n '[class]': 'classes()',\n },\n})\nexport class UiBadge {\n readonly variant = input<BadgeVariant>('neutral');\n readonly size = input<BadgeSize>('md');\n\n protected readonly classes = computed(\n () => `${BASE_CLASSES} ${SIZE_CLASSES[this.size()]} ${VARIANT_CLASSES[this.variant()]}`,\n );\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;AAEA;MACa,cAAc,GAAG,CAAC,SAAS,EAAE,SAAS;MAGtC,WAAW,GAAG,CAAC,IAAI,EAAE,IAAI;AAGtC,MAAM,YAAY,GAAG,gEAAgE;AAErF,MAAM,YAAY,GAA8B;AAC9C,IAAA,EAAE,EAAE,YAAY;AAChB,IAAA,EAAE,EAAE,eAAe;CACpB;AAED,MAAM,eAAe,GAAiC;AACpD,IAAA,OAAO,EAAE,kCAAkC;AAC3C,IAAA,OAAO,EAAE,4DAA4D;CACtE;AAED;;;;;AAKG;MAQU,OAAO,CAAA;IACT,OAAO,GAAG,KAAK,CAAe,SAAS;gFAAC;IACxC,IAAI,GAAG,KAAK,CAAY,IAAI;6EAAC;IAEnB,OAAO,GAAG,QAAQ,CACnC,MAAM,CAAA,EAAG,YAAY,CAAA,CAAA,EAAI,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAA,CAAA,EAAI,eAAe,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAA,CAAE;gFACxF;uGANU,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,mXALR,gBAAgB,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA;;2FAKf,OAAO,EAAA,UAAA,EAAA,CAAA;kBAPnB,SAAS;AAAC,YAAA,IAAA,EAAA,CAAA;AACT,oBAAA,QAAQ,EAAE,UAAU;AACpB,oBAAA,QAAQ,EAAE,gBAAgB;AAC1B,oBAAA,IAAI,EAAE;AACJ,wBAAA,SAAS,EAAE,WAAW;AACvB,qBAAA;AACF,iBAAA;;;ACjCD;;AAEG;;;;"}