@joanroucoux/cairn-ui 0.7.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/fesm2022/joanroucoux-cairn-ui-action-bar.mjs +54 -0
  3. package/fesm2022/joanroucoux-cairn-ui-action-bar.mjs.map +1 -0
  4. package/fesm2022/joanroucoux-cairn-ui-alert.mjs +129 -0
  5. package/fesm2022/joanroucoux-cairn-ui-alert.mjs.map +1 -0
  6. package/fesm2022/joanroucoux-cairn-ui-amount.mjs +114 -0
  7. package/fesm2022/joanroucoux-cairn-ui-amount.mjs.map +1 -0
  8. package/fesm2022/joanroucoux-cairn-ui-async.mjs +282 -0
  9. package/fesm2022/joanroucoux-cairn-ui-async.mjs.map +1 -0
  10. package/fesm2022/joanroucoux-cairn-ui-avatar.mjs +71 -0
  11. package/fesm2022/joanroucoux-cairn-ui-avatar.mjs.map +1 -0
  12. package/fesm2022/joanroucoux-cairn-ui-back-link.mjs +79 -0
  13. package/fesm2022/joanroucoux-cairn-ui-back-link.mjs.map +1 -0
  14. package/fesm2022/joanroucoux-cairn-ui-badge.mjs +48 -0
  15. package/fesm2022/joanroucoux-cairn-ui-badge.mjs.map +1 -0
  16. package/fesm2022/joanroucoux-cairn-ui-button.mjs +128 -0
  17. package/fesm2022/joanroucoux-cairn-ui-button.mjs.map +1 -0
  18. package/fesm2022/joanroucoux-cairn-ui-card.mjs +146 -0
  19. package/fesm2022/joanroucoux-cairn-ui-card.mjs.map +1 -0
  20. package/fesm2022/joanroucoux-cairn-ui-choice-chips.mjs +172 -0
  21. package/fesm2022/joanroucoux-cairn-ui-choice-chips.mjs.map +1 -0
  22. package/fesm2022/joanroucoux-cairn-ui-control.mjs +18 -0
  23. package/fesm2022/joanroucoux-cairn-ui-control.mjs.map +1 -0
  24. package/fesm2022/joanroucoux-cairn-ui-delta.mjs +103 -0
  25. package/fesm2022/joanroucoux-cairn-ui-delta.mjs.map +1 -0
  26. package/fesm2022/joanroucoux-cairn-ui-dialog.mjs +626 -0
  27. package/fesm2022/joanroucoux-cairn-ui-dialog.mjs.map +1 -0
  28. package/fesm2022/joanroucoux-cairn-ui-donut.mjs +347 -0
  29. package/fesm2022/joanroucoux-cairn-ui-donut.mjs.map +1 -0
  30. package/fesm2022/joanroucoux-cairn-ui-empty.mjs +47 -0
  31. package/fesm2022/joanroucoux-cairn-ui-empty.mjs.map +1 -0
  32. package/fesm2022/joanroucoux-cairn-ui-external-link.mjs +65 -0
  33. package/fesm2022/joanroucoux-cairn-ui-external-link.mjs.map +1 -0
  34. package/fesm2022/joanroucoux-cairn-ui-fact.mjs +99 -0
  35. package/fesm2022/joanroucoux-cairn-ui-fact.mjs.map +1 -0
  36. package/fesm2022/joanroucoux-cairn-ui-field.mjs +232 -0
  37. package/fesm2022/joanroucoux-cairn-ui-field.mjs.map +1 -0
  38. package/fesm2022/joanroucoux-cairn-ui-filter-chips.mjs +89 -0
  39. package/fesm2022/joanroucoux-cairn-ui-filter-chips.mjs.map +1 -0
  40. package/fesm2022/joanroucoux-cairn-ui-input.mjs +90 -0
  41. package/fesm2022/joanroucoux-cairn-ui-input.mjs.map +1 -0
  42. package/fesm2022/joanroucoux-cairn-ui-line-chart.mjs +844 -0
  43. package/fesm2022/joanroucoux-cairn-ui-line-chart.mjs.map +1 -0
  44. package/fesm2022/joanroucoux-cairn-ui-menu.mjs +241 -0
  45. package/fesm2022/joanroucoux-cairn-ui-menu.mjs.map +1 -0
  46. package/fesm2022/joanroucoux-cairn-ui-meter.mjs +86 -0
  47. package/fesm2022/joanroucoux-cairn-ui-meter.mjs.map +1 -0
  48. package/fesm2022/joanroucoux-cairn-ui-motion.mjs +415 -0
  49. package/fesm2022/joanroucoux-cairn-ui-motion.mjs.map +1 -0
  50. package/fesm2022/joanroucoux-cairn-ui-nav-item.mjs +45 -0
  51. package/fesm2022/joanroucoux-cairn-ui-nav-item.mjs.map +1 -0
  52. package/fesm2022/joanroucoux-cairn-ui-row.mjs +164 -0
  53. package/fesm2022/joanroucoux-cairn-ui-row.mjs.map +1 -0
  54. package/fesm2022/joanroucoux-cairn-ui-segmented.mjs +165 -0
  55. package/fesm2022/joanroucoux-cairn-ui-segmented.mjs.map +1 -0
  56. package/fesm2022/joanroucoux-cairn-ui-select.mjs +69 -0
  57. package/fesm2022/joanroucoux-cairn-ui-select.mjs.map +1 -0
  58. package/fesm2022/joanroucoux-cairn-ui-skeleton.mjs +91 -0
  59. package/fesm2022/joanroucoux-cairn-ui-skeleton.mjs.map +1 -0
  60. package/fesm2022/joanroucoux-cairn-ui-stale-link.mjs +43 -0
  61. package/fesm2022/joanroucoux-cairn-ui-stale-link.mjs.map +1 -0
  62. package/fesm2022/joanroucoux-cairn-ui-stat.mjs +96 -0
  63. package/fesm2022/joanroucoux-cairn-ui-stat.mjs.map +1 -0
  64. package/fesm2022/joanroucoux-cairn-ui-switch.mjs +37 -0
  65. package/fesm2022/joanroucoux-cairn-ui-switch.mjs.map +1 -0
  66. package/fesm2022/joanroucoux-cairn-ui-tab-bar.mjs +75 -0
  67. package/fesm2022/joanroucoux-cairn-ui-tab-bar.mjs.map +1 -0
  68. package/fesm2022/joanroucoux-cairn-ui-table.mjs +321 -0
  69. package/fesm2022/joanroucoux-cairn-ui-table.mjs.map +1 -0
  70. package/fesm2022/joanroucoux-cairn-ui-toast.mjs +153 -0
  71. package/fesm2022/joanroucoux-cairn-ui-toast.mjs.map +1 -0
  72. package/fesm2022/joanroucoux-cairn-ui.mjs +0 -5386
  73. package/fesm2022/joanroucoux-cairn-ui.mjs.map +1 -1
  74. package/package.json +141 -1
  75. package/styles/motion.css +9 -0
  76. package/types/joanroucoux-cairn-ui-action-bar.d.ts +22 -0
  77. package/types/joanroucoux-cairn-ui-alert.d.ts +23 -0
  78. package/types/joanroucoux-cairn-ui-amount.d.ts +45 -0
  79. package/types/joanroucoux-cairn-ui-async.d.ts +58 -0
  80. package/types/joanroucoux-cairn-ui-avatar.d.ts +35 -0
  81. package/types/joanroucoux-cairn-ui-back-link.d.ts +22 -0
  82. package/types/joanroucoux-cairn-ui-badge.d.ts +23 -0
  83. package/types/joanroucoux-cairn-ui-button.d.ts +29 -0
  84. package/types/joanroucoux-cairn-ui-card.d.ts +36 -0
  85. package/types/joanroucoux-cairn-ui-choice-chips.d.ts +46 -0
  86. package/types/joanroucoux-cairn-ui-control.d.ts +27 -0
  87. package/types/joanroucoux-cairn-ui-delta.d.ts +38 -0
  88. package/types/joanroucoux-cairn-ui-dialog.d.ts +72 -0
  89. package/types/joanroucoux-cairn-ui-donut.d.ts +59 -0
  90. package/types/joanroucoux-cairn-ui-empty.d.ts +19 -0
  91. package/types/joanroucoux-cairn-ui-external-link.d.ts +15 -0
  92. package/types/joanroucoux-cairn-ui-fact.d.ts +44 -0
  93. package/types/joanroucoux-cairn-ui-field.d.ts +84 -0
  94. package/types/joanroucoux-cairn-ui-filter-chips.d.ts +28 -0
  95. package/types/joanroucoux-cairn-ui-input.d.ts +51 -0
  96. package/types/joanroucoux-cairn-ui-line-chart.d.ts +114 -0
  97. package/types/joanroucoux-cairn-ui-menu.d.ts +66 -0
  98. package/types/joanroucoux-cairn-ui-meter.d.ts +30 -0
  99. package/types/joanroucoux-cairn-ui-motion.d.ts +124 -0
  100. package/types/joanroucoux-cairn-ui-nav-item.d.ts +21 -0
  101. package/types/joanroucoux-cairn-ui-row.d.ts +78 -0
  102. package/types/joanroucoux-cairn-ui-segmented.d.ts +36 -0
  103. package/types/joanroucoux-cairn-ui-select.d.ts +23 -0
  104. package/types/joanroucoux-cairn-ui-skeleton.d.ts +26 -0
  105. package/types/joanroucoux-cairn-ui-stale-link.d.ts +24 -0
  106. package/types/joanroucoux-cairn-ui-stat.d.ts +32 -0
  107. package/types/joanroucoux-cairn-ui-switch.d.ts +17 -0
  108. package/types/joanroucoux-cairn-ui-tab-bar.d.ts +31 -0
  109. package/types/joanroucoux-cairn-ui-table.d.ts +139 -0
  110. package/types/joanroucoux-cairn-ui-toast.d.ts +51 -0
  111. package/types/joanroucoux-cairn-ui.d.ts +1 -1407
@@ -0,0 +1,164 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input, booleanAttribute, computed, inject, ElementRef, DestroyRef, Component, Directive } from '@angular/core';
3
+ import { holdTransitionsUntilRendered } from '@joanroucoux/cairn-ui/motion';
4
+
5
+ const ROW_SIZES = ['md', 'lg', 'xl', 'card', 'dense'];
6
+ const ROW_PADDINGS = ['md', 'sm', 'none'];
7
+ const ROW_GAPS = ['default', 'sm'];
8
+ const SIZE_CLASSES = {
9
+ md: 'min-h-14',
10
+ lg: 'min-h-15',
11
+ xl: 'min-h-18 pointer-fine:min-h-17',
12
+ card: 'min-h-17',
13
+ dense: 'min-h-14 lg:min-h-12',
14
+ };
15
+ const SIZE_GAPS = { md: 'gap-2.5', lg: 'gap-2.5', xl: 'gap-3', card: 'gap-2', dense: 'gap-3' };
16
+ const PADDING_CLASSES = { md: 'px-2.5', sm: 'px-2', none: 'px-0' };
17
+ const BASE_CLASSES = 'flex w-full items-center py-1.5 rounded-control text-left select-none touch-manipulation focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-(--ring)';
18
+ const INTERACTIVE_CLASSES = 'transition-[scale,background-color] [transition-duration:var(--duration-press),var(--duration-fast)] ease-out hover:bg-(--glow) active:bg-(--soft) active:scale-(--press-scale)';
19
+ const UNAVAILABLE_CLASSES = 'opacity-50 cursor-not-allowed';
20
+ const SELECTED_CLASSES = 'bg-(--soft)';
21
+ /**
22
+ * Clickable row of a list: a holding, an account, a search result.
23
+ *
24
+ * @example
25
+ * <a ui-row [selected]="holding.id === openId()" [routerLink]="['/holdings', holding.id]">
26
+ * {{ holding.name }}
27
+ * </a>
28
+ */
29
+ class UiRow {
30
+ selected = input(false, { ...(ngDevMode ? { debugName: "selected" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
31
+ busy = input(false, { ...(ngDevMode ? { debugName: "busy" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
32
+ unavailable = input(false, { ...(ngDevMode ? { debugName: "unavailable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
33
+ size = input('md', /* @ts-ignore */
34
+ ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
35
+ padding = input('md', /* @ts-ignore */
36
+ ...(ngDevMode ? [{ debugName: "padding" }] : /* istanbul ignore next */ []));
37
+ gap = input('default', /* @ts-ignore */
38
+ ...(ngDevMode ? [{ debugName: "gap" }] : /* istanbul ignore next */ []));
39
+ classes = computed(() => `${BASE_CLASSES} ${SIZE_CLASSES[this.size()]} ${this.gap() === 'sm' ? 'gap-2' : SIZE_GAPS[this.size()]} ${PADDING_CLASSES[this.padding()]}${this.unavailable() ? ` ${UNAVAILABLE_CLASSES}` : ` ${INTERACTIVE_CLASSES}`}${this.selected() ? ` ${SELECTED_CLASSES}` : ''}${this.busy() ? ' pointer-events-none' : ''}`, /* @ts-ignore */
40
+ ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
41
+ constructor() {
42
+ holdTransitionsUntilRendered();
43
+ const host = inject(ElementRef).nativeElement;
44
+ const guard = (event) => {
45
+ if (this.busy() || this.unavailable()) {
46
+ event.preventDefault();
47
+ event.stopImmediatePropagation();
48
+ }
49
+ };
50
+ host.addEventListener('click', guard, { capture: true });
51
+ inject(DestroyRef).onDestroy(() => host.removeEventListener('click', guard, { capture: true }));
52
+ }
53
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiRow, deps: [], target: i0.ɵɵFactoryTarget.Component });
54
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: UiRow, isStandalone: true, selector: "a[ui-row], button[ui-row]", inputs: { selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, busy: { classPropertyName: "busy", publicName: "busy", isSignal: true, isRequired: false, transformFunction: null }, unavailable: { classPropertyName: "unavailable", publicName: "unavailable", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, padding: { classPropertyName: "padding", publicName: "padding", isSignal: true, isRequired: false, transformFunction: null }, gap: { classPropertyName: "gap", publicName: "gap", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "classes()", "attr.aria-busy": "busy() || null", "attr.aria-disabled": "busy() || unavailable() ? 'true' : null", "attr.aria-current": "selected() ? 'true' : null" } }, ngImport: i0, template: `
55
+ <ng-content />
56
+ @if (busy()) {
57
+ <span
58
+ aria-hidden="true"
59
+ class="rounded-pill animate-cairn-spin size-4 flex-none border-2 border-current border-r-transparent"
60
+ ></span>
61
+ }
62
+ `, isInline: true });
63
+ }
64
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiRow, decorators: [{
65
+ type: Component,
66
+ args: [{
67
+ selector: 'a[ui-row], button[ui-row]',
68
+ template: `
69
+ <ng-content />
70
+ @if (busy()) {
71
+ <span
72
+ aria-hidden="true"
73
+ class="rounded-pill animate-cairn-spin size-4 flex-none border-2 border-current border-r-transparent"
74
+ ></span>
75
+ }
76
+ `,
77
+ host: {
78
+ '[class]': 'classes()',
79
+ '[attr.aria-busy]': 'busy() || null',
80
+ '[attr.aria-disabled]': "busy() || unavailable() ? 'true' : null",
81
+ '[attr.aria-current]': "selected() ? 'true' : null",
82
+ },
83
+ }]
84
+ }], ctorParameters: () => [], propDecorators: { selected: [{ type: i0.Input, args: [{ isSignal: true, alias: "selected", required: false }] }], busy: [{ type: i0.Input, args: [{ isSignal: true, alias: "busy", required: false }] }], unavailable: [{ type: i0.Input, args: [{ isSignal: true, alias: "unavailable", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], padding: [{ type: i0.Input, args: [{ isSignal: true, alias: "padding", required: false }] }], gap: [{ type: i0.Input, args: [{ isSignal: true, alias: "gap", required: false }] }] } });
85
+ /**
86
+ * Lays out a `ui-row` with a trailing action beside it, such as a "more" icon button: the row takes
87
+ * the room the action leaves and the action keeps its own size.
88
+ *
89
+ * @example
90
+ * <div uiRowItem>
91
+ * <a ui-row size="xl" padding="sm" routerLink="/accounts/1">...</a>
92
+ * <button ui-button variant="quiet" size="icon-sm" aria-label="Account actions">...</button>
93
+ * </div>
94
+ */
95
+ class UiRowItem {
96
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiRowItem, deps: [], target: i0.ɵɵFactoryTarget.Directive });
97
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.7", type: UiRowItem, isStandalone: true, selector: "[uiRowItem]", host: { classAttribute: "flex items-center gap-1 [&>[ui-row]]:w-auto [&>[ui-row]]:min-w-0 [&>[ui-row]]:flex-1 [&>:not([ui-row])]:flex-none" }, ngImport: i0 });
98
+ }
99
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiRowItem, decorators: [{
100
+ type: Directive,
101
+ args: [{
102
+ selector: '[uiRowItem]',
103
+ host: {
104
+ class: 'flex items-center gap-1 [&>[ui-row]]:w-auto [&>[ui-row]]:min-w-0 [&>[ui-row]]:flex-1 [&>:not([ui-row])]:flex-none',
105
+ },
106
+ }]
107
+ }] });
108
+ /**
109
+ * Non-interactive row of a list, separated from the next by a hairline: a passkey with its delete
110
+ * button, a device, a setting. It is a 72px row (68px from 64rem) with a 12px gap; put it on an
111
+ * `li` or a `div`. Use `ui-row` instead when the whole row is a link or an action.
112
+ *
113
+ * @example
114
+ * <ul>
115
+ * <li uiListRow>
116
+ * <span class="flex-1">MacBook Air</span>
117
+ * <button ui-button variant="quiet-destructive" size="icon-sm" aria-label="Delete MacBook Air">...</button>
118
+ * </li>
119
+ * </ul>
120
+ */
121
+ class UiListRow {
122
+ ruled = input(true, { ...(ngDevMode ? { debugName: "ruled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
123
+ classes = computed(() => this.ruled()
124
+ ? 'flex min-h-18 items-center gap-3 py-1.5 lg:min-h-17 shadow-[inset_0_-1px_0_var(--hairline)]'
125
+ : 'flex min-h-18 items-center gap-3 py-1.5 lg:min-h-17', /* @ts-ignore */
126
+ ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
127
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiListRow, deps: [], target: i0.ɵɵFactoryTarget.Directive });
128
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.7", type: UiListRow, isStandalone: true, selector: "[uiListRow]", inputs: { ruled: { classPropertyName: "ruled", publicName: "ruled", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "classes()" } }, ngImport: i0 });
129
+ }
130
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiListRow, decorators: [{
131
+ type: Directive,
132
+ args: [{
133
+ selector: '[uiListRow]',
134
+ host: { '[class]': 'classes()' },
135
+ }]
136
+ }], propDecorators: { ruled: [{ type: i0.Input, args: [{ isSignal: true, alias: "ruled", required: false }] }] } });
137
+ /**
138
+ * The 36px filled tile that leads a row: an icon on a muted square with the control radius. Put it on
139
+ * a `span` as the first child of a `ui-row` or a `uiListRow`.
140
+ *
141
+ * @example
142
+ * <a ui-row size="xl" padding="sm" href="/export">
143
+ * <span uiRowTile><svg lucideDownload [size]="18" /></span>
144
+ * Export
145
+ * </a>
146
+ */
147
+ class UiRowTile {
148
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiRowTile, deps: [], target: i0.ɵɵFactoryTarget.Directive });
149
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.7", type: UiRowTile, isStandalone: true, selector: "[uiRowTile]", host: { classAttribute: "rounded-control grid size-9 flex-none place-items-center bg-(--muted)" }, ngImport: i0 });
150
+ }
151
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiRowTile, decorators: [{
152
+ type: Directive,
153
+ args: [{
154
+ selector: '[uiRowTile]',
155
+ host: { class: 'rounded-control grid size-9 flex-none place-items-center bg-(--muted)' },
156
+ }]
157
+ }] });
158
+
159
+ /**
160
+ * Generated bundle index. Do not edit.
161
+ */
162
+
163
+ export { ROW_GAPS, ROW_PADDINGS, ROW_SIZES, UiListRow, UiRow, UiRowItem, UiRowTile };
164
+ //# sourceMappingURL=joanroucoux-cairn-ui-row.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"joanroucoux-cairn-ui-row.mjs","sources":["../../../projects/ui/row/row.ts","../../../projects/ui/row/joanroucoux-cairn-ui-row.ts"],"sourcesContent":["import { Component, DestroyRef, Directive, ElementRef, booleanAttribute, computed, inject, input } from '@angular/core';\n\nimport { holdTransitionsUntilRendered } from '@joanroucoux/cairn-ui/motion';\n\nexport const ROW_SIZES = ['md', 'lg', 'xl', 'card', 'dense'] as const;\nexport type RowSize = (typeof ROW_SIZES)[number];\n\nexport const ROW_PADDINGS = ['md', 'sm', 'none'] as const;\nexport type RowPadding = (typeof ROW_PADDINGS)[number];\n\nexport const ROW_GAPS = ['default', 'sm'] as const;\nexport type RowGap = (typeof ROW_GAPS)[number];\n\nconst SIZE_CLASSES: Record<RowSize, string> = {\n md: 'min-h-14',\n lg: 'min-h-15',\n xl: 'min-h-18 pointer-fine:min-h-17',\n card: 'min-h-17',\n dense: 'min-h-14 lg:min-h-12',\n};\n\nconst SIZE_GAPS: Record<RowSize, string> = { md: 'gap-2.5', lg: 'gap-2.5', xl: 'gap-3', card: 'gap-2', dense: 'gap-3' };\n\nconst PADDING_CLASSES: Record<RowPadding, string> = { md: 'px-2.5', sm: 'px-2', none: 'px-0' };\n\nconst BASE_CLASSES =\n 'flex w-full items-center py-1.5 rounded-control text-left select-none touch-manipulation focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-(--ring)';\n\nconst INTERACTIVE_CLASSES =\n 'transition-[scale,background-color] [transition-duration:var(--duration-press),var(--duration-fast)] ease-out hover:bg-(--glow) active:bg-(--soft) active:scale-(--press-scale)';\n\nconst UNAVAILABLE_CLASSES = 'opacity-50 cursor-not-allowed';\n\nconst SELECTED_CLASSES = 'bg-(--soft)';\n\n/**\n * Clickable row of a list: a holding, an account, a search result.\n *\n * @example\n * <a ui-row [selected]=\"holding.id === openId()\" [routerLink]=\"['/holdings', holding.id]\">\n * {{ holding.name }}\n * </a>\n */\n@Component({\n selector: 'a[ui-row], button[ui-row]',\n template: `\n <ng-content />\n @if (busy()) {\n <span\n aria-hidden=\"true\"\n class=\"rounded-pill animate-cairn-spin size-4 flex-none border-2 border-current border-r-transparent\"\n ></span>\n }\n `,\n host: {\n '[class]': 'classes()',\n '[attr.aria-busy]': 'busy() || null',\n '[attr.aria-disabled]': \"busy() || unavailable() ? 'true' : null\",\n '[attr.aria-current]': \"selected() ? 'true' : null\",\n },\n})\nexport class UiRow {\n readonly selected = input(false, { transform: booleanAttribute });\n readonly busy = input(false, { transform: booleanAttribute });\n readonly unavailable = input(false, { transform: booleanAttribute });\n readonly size = input<RowSize>('md');\n readonly padding = input<RowPadding>('md');\n readonly gap = input<RowGap>('default');\n\n protected readonly classes = computed(\n () =>\n `${BASE_CLASSES} ${SIZE_CLASSES[this.size()]} ${this.gap() === 'sm' ? 'gap-2' : SIZE_GAPS[this.size()]} ${PADDING_CLASSES[this.padding()]}${this.unavailable() ? ` ${UNAVAILABLE_CLASSES}` : ` ${INTERACTIVE_CLASSES}`}${this.selected() ? ` ${SELECTED_CLASSES}` : ''}${this.busy() ? ' pointer-events-none' : ''}`,\n );\n\n constructor() {\n holdTransitionsUntilRendered();\n const host = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n const guard = (event: Event): void => {\n if (this.busy() || this.unavailable()) {\n event.preventDefault();\n event.stopImmediatePropagation();\n }\n };\n\n host.addEventListener('click', guard, { capture: true });\n inject(DestroyRef).onDestroy(() => host.removeEventListener('click', guard, { capture: true }));\n }\n}\n\n/**\n * Lays out a `ui-row` with a trailing action beside it, such as a \"more\" icon button: the row takes\n * the room the action leaves and the action keeps its own size.\n *\n * @example\n * <div uiRowItem>\n * <a ui-row size=\"xl\" padding=\"sm\" routerLink=\"/accounts/1\">...</a>\n * <button ui-button variant=\"quiet\" size=\"icon-sm\" aria-label=\"Account actions\">...</button>\n * </div>\n */\n@Directive({\n selector: '[uiRowItem]',\n host: {\n class:\n 'flex items-center gap-1 [&>[ui-row]]:w-auto [&>[ui-row]]:min-w-0 [&>[ui-row]]:flex-1 [&>:not([ui-row])]:flex-none',\n },\n})\nexport class UiRowItem {}\n\n/**\n * Non-interactive row of a list, separated from the next by a hairline: a passkey with its delete\n * button, a device, a setting. It is a 72px row (68px from 64rem) with a 12px gap; put it on an\n * `li` or a `div`. Use `ui-row` instead when the whole row is a link or an action.\n *\n * @example\n * <ul>\n * <li uiListRow>\n * <span class=\"flex-1\">MacBook Air</span>\n * <button ui-button variant=\"quiet-destructive\" size=\"icon-sm\" aria-label=\"Delete MacBook Air\">...</button>\n * </li>\n * </ul>\n */\n@Directive({\n selector: '[uiListRow]',\n host: { '[class]': 'classes()' },\n})\nexport class UiListRow {\n readonly ruled = input(true, { transform: booleanAttribute });\n\n protected readonly classes = computed(() =>\n this.ruled()\n ? 'flex min-h-18 items-center gap-3 py-1.5 lg:min-h-17 shadow-[inset_0_-1px_0_var(--hairline)]'\n : 'flex min-h-18 items-center gap-3 py-1.5 lg:min-h-17',\n );\n}\n\n/**\n * The 36px filled tile that leads a row: an icon on a muted square with the control radius. Put it on\n * a `span` as the first child of a `ui-row` or a `uiListRow`.\n *\n * @example\n * <a ui-row size=\"xl\" padding=\"sm\" href=\"/export\">\n * <span uiRowTile><svg lucideDownload [size]=\"18\" /></span>\n * Export\n * </a>\n */\n@Directive({\n selector: '[uiRowTile]',\n host: { class: 'rounded-control grid size-9 flex-none place-items-center bg-(--muted)' },\n})\nexport class UiRowTile {}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,165 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input, booleanAttribute, model, viewChildren, computed, Component } from '@angular/core';
3
+ import { holdTransitionsUntilRendered } from '@joanroucoux/cairn-ui/motion';
4
+
5
+ const nextId = (() => {
6
+ let count = 0;
7
+ return () => `ui-segmented-${++count}`;
8
+ })();
9
+ const OPTION_CLASSES = "relative z-10 flex-1 cursor-pointer px-3 text-label font-medium transition-[scale] duration-(--duration-press) ease-out after:absolute after:inset-x-0 after:top-1/2 after:h-(--row-min) after:-translate-y-1/2 after:content-[''] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-(--ring) active:scale-(--press-scale)";
10
+ const SEGMENTED_SIZES = ['md', 'sm'];
11
+ const SIZE_CLASSES = {
12
+ md: 'h-10 pointer-fine:h-8',
13
+ sm: 'h-10 pointer-fine:h-[30px]',
14
+ };
15
+ const SELECTED_CLASSES = 'text-(--foreground)';
16
+ const UNSELECTED_CLASSES = 'text-(--muted-foreground) hover:text-(--foreground)';
17
+ /**
18
+ * Exclusive choice within a small, known set: a time range, a unit, a mode. The selected option's
19
+ * background is a single thumb that slides under it in `transform`, instead of being redrawn.
20
+ *
21
+ * @example
22
+ * <ui-segmented [options]="ranges" label="Time range" [(value)]="range" />
23
+ */
24
+ class UiSegmented {
25
+ options = input.required(/* @ts-ignore */
26
+ ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
27
+ label = input.required(/* @ts-ignore */
28
+ ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
29
+ showLabel = input(false, { ...(ngDevMode ? { debugName: "showLabel" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
30
+ labelId = nextId();
31
+ value = model.required(/* @ts-ignore */
32
+ ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
33
+ size = input('md', /* @ts-ignore */
34
+ ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
35
+ radios = viewChildren('radio', /* @ts-ignore */
36
+ ...(ngDevMode ? [{ debugName: "radios" }] : /* istanbul ignore next */ []));
37
+ thumbIndex = computed(() => {
38
+ const index = this.options().findIndex((option) => option.value === this.value());
39
+ return index === -1 ? 0 : index + 1;
40
+ }, /* @ts-ignore */
41
+ ...(ngDevMode ? [{ debugName: "thumbIndex" }] : /* istanbul ignore next */ []));
42
+ constructor() {
43
+ holdTransitionsUntilRendered();
44
+ }
45
+ optionClasses(optionValue) {
46
+ return `${OPTION_CLASSES} ${SIZE_CLASSES[this.size()]} ${optionValue === this.value() ? SELECTED_CLASSES : UNSELECTED_CLASSES}`;
47
+ }
48
+ tabIndexFor(optionValue) {
49
+ const opts = this.options();
50
+ const tabStopValue = opts.some((option) => option.value === this.value()) ? this.value() : opts[0]?.value;
51
+ return optionValue === tabStopValue ? 0 : -1;
52
+ }
53
+ select(optionValue) {
54
+ this.value.set(optionValue);
55
+ }
56
+ onKeydown(event) {
57
+ const delta = { ArrowRight: 1, ArrowDown: 1, ArrowLeft: -1, ArrowUp: -1 }[event.key];
58
+ if (delta === undefined) {
59
+ return;
60
+ }
61
+ event.preventDefault();
62
+ const opts = this.options();
63
+ const currentIndex = opts.findIndex((option) => option.value === this.value());
64
+ const nextIndex = (currentIndex + delta + opts.length) % opts.length;
65
+ const next = opts[nextIndex];
66
+ this.value.set(next.value);
67
+ this.radios()[nextIndex]?.nativeElement.focus();
68
+ }
69
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiSegmented, deps: [], target: i0.ɵɵFactoryTarget.Component });
70
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: UiSegmented, isStandalone: true, selector: "ui-segmented", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, showLabel: { classPropertyName: "showLabel", publicName: "showLabel", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, viewQueries: [{ propertyName: "radios", predicate: ["radio"], descendants: true, isSignal: true }], ngImport: i0, template: `
71
+ <div [class]="showLabel() ? 'flex flex-col gap-1.5' : ''">
72
+ @if (showLabel()) {
73
+ <span class="text-label leading-[17px] font-medium text-(--muted-foreground)" [id]="labelId">{{
74
+ label()
75
+ }}</span>
76
+ }
77
+ <div
78
+ class="rounded-control relative grid bg-(--muted) p-0.5"
79
+ role="radiogroup"
80
+ [attr.aria-label]="showLabel() ? null : label()"
81
+ [attr.aria-labelledby]="showLabel() ? labelId : null"
82
+ [style.grid-template-columns]="'repeat(' + options().length + ', 1fr)'"
83
+ >
84
+ @if (thumbIndex(); as index) {
85
+ <span
86
+ aria-hidden="true"
87
+ class="absolute top-0.5 bottom-0.5 left-0.5 rounded-[calc(var(--radius-control)-2px)] bg-(--card) shadow-[0_1px_2px_rgb(0_0_0/0.08),inset_0_0_0_1px_var(--border)] transition-transform duration-(--duration-fast) ease-out"
88
+ data-thumb
89
+ [style.transform]="'translateX(' + (index - 1) * 100 + '%)'"
90
+ [style.width]="'calc((100% - 4px) / ' + options().length + ')'"
91
+ ></span>
92
+ }
93
+
94
+ @for (option of options(); track option.value) {
95
+ <button
96
+ #radio
97
+ role="radio"
98
+ type="button"
99
+ [attr.aria-checked]="option.value === value()"
100
+ [attr.tabindex]="tabIndexFor(option.value)"
101
+ [class]="optionClasses(option.value)"
102
+ (click)="select(option.value)"
103
+ (keydown)="onKeydown($event)"
104
+ >
105
+ {{ option.label }}
106
+ </button>
107
+ }
108
+ </div>
109
+ </div>
110
+ `, isInline: true });
111
+ }
112
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiSegmented, decorators: [{
113
+ type: Component,
114
+ args: [{
115
+ selector: 'ui-segmented',
116
+ template: `
117
+ <div [class]="showLabel() ? 'flex flex-col gap-1.5' : ''">
118
+ @if (showLabel()) {
119
+ <span class="text-label leading-[17px] font-medium text-(--muted-foreground)" [id]="labelId">{{
120
+ label()
121
+ }}</span>
122
+ }
123
+ <div
124
+ class="rounded-control relative grid bg-(--muted) p-0.5"
125
+ role="radiogroup"
126
+ [attr.aria-label]="showLabel() ? null : label()"
127
+ [attr.aria-labelledby]="showLabel() ? labelId : null"
128
+ [style.grid-template-columns]="'repeat(' + options().length + ', 1fr)'"
129
+ >
130
+ @if (thumbIndex(); as index) {
131
+ <span
132
+ aria-hidden="true"
133
+ class="absolute top-0.5 bottom-0.5 left-0.5 rounded-[calc(var(--radius-control)-2px)] bg-(--card) shadow-[0_1px_2px_rgb(0_0_0/0.08),inset_0_0_0_1px_var(--border)] transition-transform duration-(--duration-fast) ease-out"
134
+ data-thumb
135
+ [style.transform]="'translateX(' + (index - 1) * 100 + '%)'"
136
+ [style.width]="'calc((100% - 4px) / ' + options().length + ')'"
137
+ ></span>
138
+ }
139
+
140
+ @for (option of options(); track option.value) {
141
+ <button
142
+ #radio
143
+ role="radio"
144
+ type="button"
145
+ [attr.aria-checked]="option.value === value()"
146
+ [attr.tabindex]="tabIndexFor(option.value)"
147
+ [class]="optionClasses(option.value)"
148
+ (click)="select(option.value)"
149
+ (keydown)="onKeydown($event)"
150
+ >
151
+ {{ option.label }}
152
+ </button>
153
+ }
154
+ </div>
155
+ </div>
156
+ `,
157
+ }]
158
+ }], ctorParameters: () => [], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], showLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "showLabel", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }, { type: i0.Output, args: ["valueChange"] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], radios: [{ type: i0.ViewChildren, args: ['radio', { isSignal: true }] }] } });
159
+
160
+ /**
161
+ * Generated bundle index. Do not edit.
162
+ */
163
+
164
+ export { SEGMENTED_SIZES, UiSegmented };
165
+ //# sourceMappingURL=joanroucoux-cairn-ui-segmented.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"joanroucoux-cairn-ui-segmented.mjs","sources":["../../../projects/ui/segmented/segmented.ts","../../../projects/ui/segmented/joanroucoux-cairn-ui-segmented.ts"],"sourcesContent":["import { Component, ElementRef, booleanAttribute, computed, input, model, viewChildren } from '@angular/core';\n\nimport { holdTransitionsUntilRendered } from '@joanroucoux/cairn-ui/motion';\n\nconst nextId = (() => {\n let count = 0;\n\n return () => `ui-segmented-${++count}`;\n})();\n\nexport type SegmentedOption = {\n value: string;\n label: string;\n};\n\nconst OPTION_CLASSES =\n \"relative z-10 flex-1 cursor-pointer px-3 text-label font-medium transition-[scale] duration-(--duration-press) ease-out after:absolute after:inset-x-0 after:top-1/2 after:h-(--row-min) after:-translate-y-1/2 after:content-[''] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-(--ring) active:scale-(--press-scale)\";\n\nexport const SEGMENTED_SIZES = ['md', 'sm'] as const;\nexport type SegmentedSize = (typeof SEGMENTED_SIZES)[number];\n\nconst SIZE_CLASSES: Record<SegmentedSize, string> = {\n md: 'h-10 pointer-fine:h-8',\n sm: 'h-10 pointer-fine:h-[30px]',\n};\n\nconst SELECTED_CLASSES = 'text-(--foreground)';\n\nconst UNSELECTED_CLASSES = 'text-(--muted-foreground) hover:text-(--foreground)';\n\n/**\n * Exclusive choice within a small, known set: a time range, a unit, a mode. The selected option's\n * background is a single thumb that slides under it in `transform`, instead of being redrawn.\n *\n * @example\n * <ui-segmented [options]=\"ranges\" label=\"Time range\" [(value)]=\"range\" />\n */\n@Component({\n selector: 'ui-segmented',\n template: `\n <div [class]=\"showLabel() ? 'flex flex-col gap-1.5' : ''\">\n @if (showLabel()) {\n <span class=\"text-label leading-[17px] font-medium text-(--muted-foreground)\" [id]=\"labelId\">{{\n label()\n }}</span>\n }\n <div\n class=\"rounded-control relative grid bg-(--muted) p-0.5\"\n role=\"radiogroup\"\n [attr.aria-label]=\"showLabel() ? null : label()\"\n [attr.aria-labelledby]=\"showLabel() ? labelId : null\"\n [style.grid-template-columns]=\"'repeat(' + options().length + ', 1fr)'\"\n >\n @if (thumbIndex(); as index) {\n <span\n aria-hidden=\"true\"\n class=\"absolute top-0.5 bottom-0.5 left-0.5 rounded-[calc(var(--radius-control)-2px)] bg-(--card) shadow-[0_1px_2px_rgb(0_0_0/0.08),inset_0_0_0_1px_var(--border)] transition-transform duration-(--duration-fast) ease-out\"\n data-thumb\n [style.transform]=\"'translateX(' + (index - 1) * 100 + '%)'\"\n [style.width]=\"'calc((100% - 4px) / ' + options().length + ')'\"\n ></span>\n }\n\n @for (option of options(); track option.value) {\n <button\n #radio\n role=\"radio\"\n type=\"button\"\n [attr.aria-checked]=\"option.value === value()\"\n [attr.tabindex]=\"tabIndexFor(option.value)\"\n [class]=\"optionClasses(option.value)\"\n (click)=\"select(option.value)\"\n (keydown)=\"onKeydown($event)\"\n >\n {{ option.label }}\n </button>\n }\n </div>\n </div>\n `,\n})\nexport class UiSegmented {\n readonly options = input.required<SegmentedOption[]>();\n readonly label = input.required<string>();\n readonly showLabel = input(false, { transform: booleanAttribute });\n\n protected readonly labelId = nextId();\n readonly value = model.required<string>();\n readonly size = input<SegmentedSize>('md');\n\n protected readonly radios = viewChildren<ElementRef<HTMLButtonElement>>('radio');\n\n protected readonly thumbIndex = computed(() => {\n const index = this.options().findIndex((option) => option.value === this.value());\n\n return index === -1 ? 0 : index + 1;\n });\n\n constructor() {\n holdTransitionsUntilRendered();\n }\n\n protected optionClasses(optionValue: string): string {\n return `${OPTION_CLASSES} ${SIZE_CLASSES[this.size()]} ${optionValue === this.value() ? SELECTED_CLASSES : UNSELECTED_CLASSES}`;\n }\n\n protected tabIndexFor(optionValue: string): number {\n const opts = this.options();\n const tabStopValue = opts.some((option) => option.value === this.value()) ? this.value() : opts[0]?.value;\n\n return optionValue === tabStopValue ? 0 : -1;\n }\n\n protected select(optionValue: string): void {\n this.value.set(optionValue);\n }\n\n protected onKeydown(event: KeyboardEvent): void {\n const delta = { ArrowRight: 1, ArrowDown: 1, ArrowLeft: -1, ArrowUp: -1 }[event.key];\n if (delta === undefined) {\n return;\n }\n event.preventDefault();\n\n const opts = this.options();\n const currentIndex = opts.findIndex((option) => option.value === this.value());\n const nextIndex = (currentIndex + delta + opts.length) % opts.length;\n const next = opts[nextIndex]!;\n\n this.value.set(next.value);\n this.radios()[nextIndex]?.nativeElement.focus();\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,69 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input, computed, forwardRef, Directive } from '@angular/core';
3
+ import { UI_CONTROL } from '@joanroucoux/cairn-ui/control';
4
+ import { CONTROL_BASE_CLASSES, CONTROL_SIZE_CLASSES } from '@joanroucoux/cairn-ui/input';
5
+
6
+ const SELECT_CLASSES = 'cursor-pointer appearance-none pr-10 bg-(image:--chevron-down) bg-no-repeat bg-[position:right_0.75rem_center] bg-[size:1.125rem]';
7
+ // Spelled out prefix included: Tailwind scans for literal strings, so a prefix applied in code
8
+ // emits nothing.
9
+ const PICKER_CLASSES = [
10
+ 'pointer-fine:supports-[appearance:base-select]:[appearance:base-select]',
11
+ 'pointer-fine:supports-[appearance:base-select]:items-center',
12
+ 'pointer-fine:supports-[appearance:base-select]:[&_option]:cursor-pointer',
13
+ 'pointer-fine:supports-[appearance:base-select]:[&_option]:rounded-control',
14
+ 'pointer-fine:supports-[appearance:base-select]:[&_option]:px-2.5',
15
+ 'pointer-fine:supports-[appearance:base-select]:[&_option]:py-2',
16
+ 'pointer-fine:supports-[appearance:base-select]:[&_option:hover]:bg-(--soft)',
17
+ 'pointer-fine:supports-[appearance:base-select]:[&_option:focus]:bg-(--soft)',
18
+ 'pointer-fine:supports-[appearance:base-select]:[&_option:checked]:font-medium',
19
+ '[&::picker-icon]:hidden',
20
+ '[&::picker(select)]:[appearance:base-select]',
21
+ '[&::picker(select)]:my-1.5',
22
+ '[&::picker(select)]:rounded-control',
23
+ '[&::picker(select)]:border',
24
+ '[&::picker(select)]:border-(--border)',
25
+ '[&::picker(select)]:bg-(--card)',
26
+ '[&::picker(select)]:[position-try-fallbacks:none]',
27
+ '[&::picker(select)]:overflow-y-auto',
28
+ '[&::picker(select)]:p-1',
29
+ '[&::picker(select)]:text-(--card-foreground)',
30
+ ].join(' ');
31
+ /**
32
+ * Styled native select, drop-down list included where the browser allows it. It stays a native
33
+ * `<select>`: keyboard, typeahead and semantics come from the platform, not from a rebuilt listbox.
34
+ *
35
+ * Declares the `errors` and `touched` inputs Angular's `[formField]` fills, so a `ui-field` around
36
+ * it can show the message without any binding here.
37
+ *
38
+ * @example
39
+ * <select uiSelect><option value="pea">PEA</option></select>
40
+ */
41
+ class UiSelect {
42
+ size = input('md', /* @ts-ignore */
43
+ ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
44
+ errors = input([], /* @ts-ignore */
45
+ ...(ngDevMode ? [{ debugName: "errors" }] : /* istanbul ignore next */ []));
46
+ touched = input(false, /* @ts-ignore */
47
+ ...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
48
+ classes = computed(() => `${CONTROL_BASE_CLASSES} bg-(--background) ${SELECT_CLASSES} ${PICKER_CLASSES} ${CONTROL_SIZE_CLASSES[this.size()]}`, /* @ts-ignore */
49
+ ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
50
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiSelect, deps: [], target: i0.ɵɵFactoryTarget.Directive });
51
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.7", type: UiSelect, isStandalone: true, selector: "select[uiSelect]", inputs: { size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, errors: { classPropertyName: "errors", publicName: "errors", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "classes()" } }, providers: [{ provide: UI_CONTROL, useExisting: forwardRef(() => UiSelect) }], ngImport: i0 });
52
+ }
53
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiSelect, decorators: [{
54
+ type: Directive,
55
+ args: [{
56
+ selector: 'select[uiSelect]',
57
+ host: {
58
+ '[class]': 'classes()',
59
+ },
60
+ providers: [{ provide: UI_CONTROL, useExisting: forwardRef(() => UiSelect) }],
61
+ }]
62
+ }], propDecorators: { size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], errors: [{ type: i0.Input, args: [{ isSignal: true, alias: "errors", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }] } });
63
+
64
+ /**
65
+ * Generated bundle index. Do not edit.
66
+ */
67
+
68
+ export { UiSelect };
69
+ //# sourceMappingURL=joanroucoux-cairn-ui-select.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"joanroucoux-cairn-ui-select.mjs","sources":["../../../projects/ui/select/select.ts","../../../projects/ui/select/joanroucoux-cairn-ui-select.ts"],"sourcesContent":["import { Directive, computed, forwardRef, input } from '@angular/core';\n\nimport { UI_CONTROL, type UiControl, type UiControlError } from '@joanroucoux/cairn-ui/control';\nimport { CONTROL_BASE_CLASSES, CONTROL_SIZE_CLASSES, type ControlSize } from '@joanroucoux/cairn-ui/input';\n\nconst SELECT_CLASSES =\n 'cursor-pointer appearance-none pr-10 bg-(image:--chevron-down) bg-no-repeat bg-[position:right_0.75rem_center] bg-[size:1.125rem]';\n\n// Spelled out prefix included: Tailwind scans for literal strings, so a prefix applied in code\n// emits nothing.\nconst PICKER_CLASSES = [\n 'pointer-fine:supports-[appearance:base-select]:[appearance:base-select]',\n 'pointer-fine:supports-[appearance:base-select]:items-center',\n 'pointer-fine:supports-[appearance:base-select]:[&_option]:cursor-pointer',\n 'pointer-fine:supports-[appearance:base-select]:[&_option]:rounded-control',\n 'pointer-fine:supports-[appearance:base-select]:[&_option]:px-2.5',\n 'pointer-fine:supports-[appearance:base-select]:[&_option]:py-2',\n 'pointer-fine:supports-[appearance:base-select]:[&_option:hover]:bg-(--soft)',\n 'pointer-fine:supports-[appearance:base-select]:[&_option:focus]:bg-(--soft)',\n 'pointer-fine:supports-[appearance:base-select]:[&_option:checked]:font-medium',\n '[&::picker-icon]:hidden',\n '[&::picker(select)]:[appearance:base-select]',\n '[&::picker(select)]:my-1.5',\n '[&::picker(select)]:rounded-control',\n '[&::picker(select)]:border',\n '[&::picker(select)]:border-(--border)',\n '[&::picker(select)]:bg-(--card)',\n '[&::picker(select)]:[position-try-fallbacks:none]',\n '[&::picker(select)]:overflow-y-auto',\n '[&::picker(select)]:p-1',\n '[&::picker(select)]:text-(--card-foreground)',\n].join(' ');\n\n/**\n * Styled native select, drop-down list included where the browser allows it. It stays a native\n * `<select>`: keyboard, typeahead and semantics come from the platform, not from a rebuilt listbox.\n *\n * Declares the `errors` and `touched` inputs Angular's `[formField]` fills, so a `ui-field` around\n * it can show the message without any binding here.\n *\n * @example\n * <select uiSelect><option value=\"pea\">PEA</option></select>\n */\n@Directive({\n selector: 'select[uiSelect]',\n host: {\n '[class]': 'classes()',\n },\n providers: [{ provide: UI_CONTROL, useExisting: forwardRef(() => UiSelect) }],\n})\nexport class UiSelect implements UiControl {\n readonly size = input<ControlSize>('md');\n readonly errors = input<readonly UiControlError[]>([]);\n readonly touched = input(false);\n\n protected readonly classes = computed(\n () =>\n `${CONTROL_BASE_CLASSES} bg-(--background) ${SELECT_CLASSES} ${PICKER_CLASSES} ${CONTROL_SIZE_CLASSES[this.size()]}`,\n );\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;AAKA,MAAM,cAAc,GAClB,mIAAmI;AAErI;AACA;AACA,MAAM,cAAc,GAAG;IACrB,yEAAyE;IACzE,6DAA6D;IAC7D,0EAA0E;IAC1E,2EAA2E;IAC3E,kEAAkE;IAClE,gEAAgE;IAChE,6EAA6E;IAC7E,6EAA6E;IAC7E,+EAA+E;IAC/E,yBAAyB;IACzB,8CAA8C;IAC9C,4BAA4B;IAC5B,qCAAqC;IACrC,4BAA4B;IAC5B,uCAAuC;IACvC,iCAAiC;IACjC,mDAAmD;IACnD,qCAAqC;IACrC,yBAAyB;IACzB,8CAA8C;AAC/C,CAAA,CAAC,IAAI,CAAC,GAAG,CAAC;AAEX;;;;;;;;;AASG;MAQU,QAAQ,CAAA;IACV,IAAI,GAAG,KAAK,CAAc,IAAI;6EAAC;IAC/B,MAAM,GAAG,KAAK,CAA4B,EAAE;+EAAC;IAC7C,OAAO,GAAG,KAAK,CAAC,KAAK;gFAAC;IAEZ,OAAO,GAAG,QAAQ,CACnC,MACE,CAAA,EAAG,oBAAoB,CAAA,mBAAA,EAAsB,cAAc,IAAI,cAAc,CAAA,CAAA,EAAI,oBAAoB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAA,CAAE;gFACvH;uGARU,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,yeAFR,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,CAAC,MAAM,QAAQ,CAAC,EAAE,CAAC,EAAA,QAAA,EAAA,EAAA,EAAA,CAAA;;2FAElE,QAAQ,EAAA,UAAA,EAAA,CAAA;kBAPpB,SAAS;AAAC,YAAA,IAAA,EAAA,CAAA;AACT,oBAAA,QAAQ,EAAE,kBAAkB;AAC5B,oBAAA,IAAI,EAAE;AACJ,wBAAA,SAAS,EAAE,WAAW;AACvB,qBAAA;AACD,oBAAA,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,CAAC,MAAK,QAAS,CAAC,EAAE,CAAC;AAC9E,iBAAA;;;ACjDD;;AAEG;;;;"}
@@ -0,0 +1,91 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input, computed, Component } from '@angular/core';
3
+
4
+ /** Available skeleton shapes. `SkeletonShape` is derived from this tuple. */
5
+ const SKELETON_SHAPES = ['text', 'figure', 'row', 'chart', 'ring', 'dot'];
6
+ const LAST_LINE_WIDTH = '62%';
7
+ const PULSE_ANIMATION = 'cairn-pulse var(--pulse-duration) var(--ease-out) infinite alternate';
8
+ const BAR_CLASSES = 'block rounded-control bg-(--muted)';
9
+ /**
10
+ * Decorative, pulsing placeholder for content that is still loading, at the shape of the content
11
+ * it stands in for.
12
+ *
13
+ * @example
14
+ * <ui-skeleton shape="figure" />
15
+ * <ui-skeleton [lines]="3" />
16
+ */
17
+ class UiSkeleton {
18
+ shape = input('text', /* @ts-ignore */
19
+ ...(ngDevMode ? [{ debugName: "shape" }] : /* istanbul ignore next */ []));
20
+ lines = input(1, /* @ts-ignore */
21
+ ...(ngDevMode ? [{ debugName: "lines" }] : /* istanbul ignore next */ []));
22
+ height = input(12, /* @ts-ignore */
23
+ ...(ngDevMode ? [{ debugName: "height" }] : /* istanbul ignore next */ []));
24
+ size = input(232, /* @ts-ignore */
25
+ ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
26
+ animation = PULSE_ANIMATION;
27
+ bars = computed(() => {
28
+ const count = Math.max(1, this.lines());
29
+ return Array.from({ length: count }, (_, index) => (index === count - 1 && count > 1 ? LAST_LINE_WIDTH : '100%'));
30
+ }, /* @ts-ignore */
31
+ ...(ngDevMode ? [{ debugName: "bars" }] : /* istanbul ignore next */ []));
32
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiSkeleton, deps: [], target: i0.ɵɵFactoryTarget.Component });
33
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: UiSkeleton, isStandalone: true, selector: "ui-skeleton", inputs: { shape: { classPropertyName: "shape", publicName: "shape", isSignal: true, isRequired: false, transformFunction: null }, lines: { classPropertyName: "lines", publicName: "lines", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "aria-hidden": "true" }, classAttribute: "flex flex-col gap-2" }, ngImport: i0, template: "\n @switch (shape()) {\n @case ('figure') {\n <span class=\"block rounded-control bg-(--muted) h-10 w-65\" [style.animation]=\"animation\"></span>\n }\n @case ('row') {\n <span class=\"flex h-14 items-center justify-between gap-4\">\n <span class=\"flex min-w-0 flex-1 flex-col gap-1.5\">\n <span class=\"block rounded-control bg-(--muted) h-4 w-3/5\" [style.animation]=\"animation\"></span>\n <span class=\"block rounded-control bg-(--muted) h-3 w-20\" [style.animation]=\"animation\"></span>\n </span>\n <span class=\"block rounded-control bg-(--muted) h-4 w-[90px] flex-none\" [style.animation]=\"animation\"></span>\n </span>\n }\n @case ('chart') {\n <span class=\"rounded-control min-h-50 w-full flex-1 bg-(--muted)\" [style.animation]=\"animation\"></span>\n }\n @case ('dot') {\n <span class=\"rounded-pill size-3 bg-(--muted)\" [style.animation]=\"animation\"></span>\n }\n @case ('ring') {\n <span\n class=\"rounded-pill shadow-[inset_0_0_0_30px_var(--muted)]\"\n [style.animation]=\"animation\"\n [style.height.px]=\"size()\"\n [style.width.px]=\"size()\"\n ></span>\n }\n @default {\n @for (line of bars(); track $index) {\n <span\n class=\"block rounded-control bg-(--muted)\"\n [style.animation]=\"animation\"\n [style.height.px]=\"height()\"\n [style.width]=\"line\"\n ></span>\n }\n }\n }\n ", isInline: true });
34
+ }
35
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiSkeleton, decorators: [{
36
+ type: Component,
37
+ args: [{
38
+ selector: 'ui-skeleton',
39
+ template: `
40
+ @switch (shape()) {
41
+ @case ('figure') {
42
+ <span class="${BAR_CLASSES} h-10 w-65" [style.animation]="animation"></span>
43
+ }
44
+ @case ('row') {
45
+ <span class="flex h-14 items-center justify-between gap-4">
46
+ <span class="flex min-w-0 flex-1 flex-col gap-1.5">
47
+ <span class="${BAR_CLASSES} h-4 w-3/5" [style.animation]="animation"></span>
48
+ <span class="${BAR_CLASSES} h-3 w-20" [style.animation]="animation"></span>
49
+ </span>
50
+ <span class="${BAR_CLASSES} h-4 w-[90px] flex-none" [style.animation]="animation"></span>
51
+ </span>
52
+ }
53
+ @case ('chart') {
54
+ <span class="rounded-control min-h-50 w-full flex-1 bg-(--muted)" [style.animation]="animation"></span>
55
+ }
56
+ @case ('dot') {
57
+ <span class="rounded-pill size-3 bg-(--muted)" [style.animation]="animation"></span>
58
+ }
59
+ @case ('ring') {
60
+ <span
61
+ class="rounded-pill shadow-[inset_0_0_0_30px_var(--muted)]"
62
+ [style.animation]="animation"
63
+ [style.height.px]="size()"
64
+ [style.width.px]="size()"
65
+ ></span>
66
+ }
67
+ @default {
68
+ @for (line of bars(); track $index) {
69
+ <span
70
+ class="${BAR_CLASSES}"
71
+ [style.animation]="animation"
72
+ [style.height.px]="height()"
73
+ [style.width]="line"
74
+ ></span>
75
+ }
76
+ }
77
+ }
78
+ `,
79
+ host: {
80
+ 'aria-hidden': 'true',
81
+ class: 'flex flex-col gap-2',
82
+ },
83
+ }]
84
+ }], propDecorators: { shape: [{ type: i0.Input, args: [{ isSignal: true, alias: "shape", required: false }] }], lines: [{ type: i0.Input, args: [{ isSignal: true, alias: "lines", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }] } });
85
+
86
+ /**
87
+ * Generated bundle index. Do not edit.
88
+ */
89
+
90
+ export { SKELETON_SHAPES, UiSkeleton };
91
+ //# sourceMappingURL=joanroucoux-cairn-ui-skeleton.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"joanroucoux-cairn-ui-skeleton.mjs","sources":["../../../projects/ui/skeleton/skeleton.ts","../../../projects/ui/skeleton/joanroucoux-cairn-ui-skeleton.ts"],"sourcesContent":["import { Component, computed, input } from '@angular/core';\n\n/** Available skeleton shapes. `SkeletonShape` is derived from this tuple. */\nexport const SKELETON_SHAPES = ['text', 'figure', 'row', 'chart', 'ring', 'dot'] as const;\nexport type SkeletonShape = (typeof SKELETON_SHAPES)[number];\n\nconst LAST_LINE_WIDTH = '62%';\n\nconst PULSE_ANIMATION = 'cairn-pulse var(--pulse-duration) var(--ease-out) infinite alternate';\n\nconst BAR_CLASSES = 'block rounded-control bg-(--muted)';\n\n/**\n * Decorative, pulsing placeholder for content that is still loading, at the shape of the content\n * it stands in for.\n *\n * @example\n * <ui-skeleton shape=\"figure\" />\n * <ui-skeleton [lines]=\"3\" />\n */\n@Component({\n selector: 'ui-skeleton',\n template: `\n @switch (shape()) {\n @case ('figure') {\n <span class=\"${BAR_CLASSES} h-10 w-65\" [style.animation]=\"animation\"></span>\n }\n @case ('row') {\n <span class=\"flex h-14 items-center justify-between gap-4\">\n <span class=\"flex min-w-0 flex-1 flex-col gap-1.5\">\n <span class=\"${BAR_CLASSES} h-4 w-3/5\" [style.animation]=\"animation\"></span>\n <span class=\"${BAR_CLASSES} h-3 w-20\" [style.animation]=\"animation\"></span>\n </span>\n <span class=\"${BAR_CLASSES} h-4 w-[90px] flex-none\" [style.animation]=\"animation\"></span>\n </span>\n }\n @case ('chart') {\n <span class=\"rounded-control min-h-50 w-full flex-1 bg-(--muted)\" [style.animation]=\"animation\"></span>\n }\n @case ('dot') {\n <span class=\"rounded-pill size-3 bg-(--muted)\" [style.animation]=\"animation\"></span>\n }\n @case ('ring') {\n <span\n class=\"rounded-pill shadow-[inset_0_0_0_30px_var(--muted)]\"\n [style.animation]=\"animation\"\n [style.height.px]=\"size()\"\n [style.width.px]=\"size()\"\n ></span>\n }\n @default {\n @for (line of bars(); track $index) {\n <span\n class=\"${BAR_CLASSES}\"\n [style.animation]=\"animation\"\n [style.height.px]=\"height()\"\n [style.width]=\"line\"\n ></span>\n }\n }\n }\n `,\n host: {\n 'aria-hidden': 'true',\n class: 'flex flex-col gap-2',\n },\n})\nexport class UiSkeleton {\n readonly shape = input<SkeletonShape>('text');\n readonly lines = input(1);\n readonly height = input(12);\n readonly size = input(232);\n\n protected readonly animation = PULSE_ANIMATION;\n\n protected readonly bars = computed(() => {\n const count = Math.max(1, this.lines());\n\n return Array.from({ length: count }, (_, index) => (index === count - 1 && count > 1 ? LAST_LINE_WIDTH : '100%'));\n });\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;AAEA;AACO,MAAM,eAAe,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK;AAG/E,MAAM,eAAe,GAAG,KAAK;AAE7B,MAAM,eAAe,GAAG,sEAAsE;AAE9F,MAAM,WAAW,GAAG,oCAAoC;AAExD;;;;;;;AAOG;MAgDU,UAAU,CAAA;IACZ,KAAK,GAAG,KAAK,CAAgB,MAAM;8EAAC;IACpC,KAAK,GAAG,KAAK,CAAC,CAAC;8EAAC;IAChB,MAAM,GAAG,KAAK,CAAC,EAAE;+EAAC;IAClB,IAAI,GAAG,KAAK,CAAC,GAAG;6EAAC;IAEP,SAAS,GAAG,eAAe;AAE3B,IAAA,IAAI,GAAG,QAAQ,CAAC,MAAK;AACtC,QAAA,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC;AAEvC,QAAA,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,MAAM,KAAK,KAAK,KAAK,GAAG,CAAC,IAAI,KAAK,GAAG,CAAC,GAAG,eAAe,GAAG,MAAM,CAAC,CAAC;IACnH,CAAC;6EAAC;uGAZS,UAAU,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;2FAAV,UAAU,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,aAAA,EAAA,MAAA,EAAA,EAAA,KAAA,EAAA,EAAA,iBAAA,EAAA,OAAA,EAAA,UAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,KAAA,EAAA,EAAA,iBAAA,EAAA,OAAA,EAAA,UAAA,EAAA,OAAA,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,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,aAAA,EAAA,MAAA,EAAA,EAAA,cAAA,EAAA,qBAAA,EAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EAAA,kjDAAA,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA;;2FAAV,UAAU,EAAA,UAAA,EAAA,CAAA;kBA/CtB,SAAS;AAAC,YAAA,IAAA,EAAA,CAAA;AACT,oBAAA,QAAQ,EAAE,aAAa;AACvB,oBAAA,QAAQ,EAAE;;;uBAGW,WAAW,CAAA;;;;;2BAKP,WAAW,CAAA;2BACX,WAAW,CAAA;;yBAEb,WAAW,CAAA;;;;;;;;;;;;;;;;;;;;qBAoBf,WAAW,CAAA;;;;;;;;AAQ7B,EAAA,CAAA;AACD,oBAAA,IAAI,EAAE;AACJ,wBAAA,aAAa,EAAE,MAAM;AACrB,wBAAA,KAAK,EAAE,qBAAqB;AAC7B,qBAAA;AACF,iBAAA;;;AClED;;AAEG;;;;"}
@@ -0,0 +1,43 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input, booleanAttribute, computed, Directive } from '@angular/core';
3
+
4
+ /** Available stale link sizes. `StaleLinkSize` is derived from this tuple. */
5
+ const STALE_LINK_SIZES = ['caption', 'label'];
6
+ const BASE_CLASSES = "relative inline-flex items-center gap-0.5 rounded-[4px] font-medium text-(--stale) hover:underline underline-offset-3 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--ring) before:absolute before:inset-x-0 before:top-1/2 before:h-11 pointer-fine:before:h-10 before:-translate-y-1/2 before:content-['']";
7
+ const SIZE_CLASSES = {
8
+ caption: 'text-caption',
9
+ label: 'text-label',
10
+ };
11
+ const CHEVRON_CLASSES = "after:size-4 after:flex-none after:bg-current after:content-[''] after:mask-center after:mask-no-repeat after:mask-[url(data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2024%2024%27%20fill%3D%27none%27%20stroke%3D%27black%27%20stroke-width%3D%271.75%27%20stroke-linecap%3D%27round%27%20stroke-linejoin%3D%27round%27%3E%3Cpath%20d%3D%27m9%2018%206-6-6-6%27%2F%3E%3C%2Fsvg%3E)]";
12
+ /**
13
+ * Link in the stale tone, for a caption about data that is late or left out of a total, such as
14
+ * "1 ligne sans cours, non comptée". Weight 500, underlined on hover, with an optional trailing
15
+ * chevron. `size` is `caption` (12/17, the default) or `label` (14/20, the dashboard total's
16
+ * "1 cours en retard").
17
+ *
18
+ * @example
19
+ * <a uiStaleLink chevron size="label" routerLink="/holdings">1 cours en retard</a>
20
+ */
21
+ class UiStaleLink {
22
+ chevron = input(false, { ...(ngDevMode ? { debugName: "chevron" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
23
+ size = input('caption', /* @ts-ignore */
24
+ ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
25
+ classes = computed(() => `${BASE_CLASSES} ${SIZE_CLASSES[this.size()]}${this.chevron() ? ` ${CHEVRON_CLASSES}` : ''}`, /* @ts-ignore */
26
+ ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
27
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiStaleLink, deps: [], target: i0.ɵɵFactoryTarget.Directive });
28
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.7", type: UiStaleLink, isStandalone: true, selector: "a[uiStaleLink]", inputs: { chevron: { classPropertyName: "chevron", publicName: "chevron", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "classes()" } }, ngImport: i0 });
29
+ }
30
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiStaleLink, decorators: [{
31
+ type: Directive,
32
+ args: [{
33
+ selector: 'a[uiStaleLink]',
34
+ host: { '[class]': 'classes()' },
35
+ }]
36
+ }], propDecorators: { chevron: [{ type: i0.Input, args: [{ isSignal: true, alias: "chevron", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }] } });
37
+
38
+ /**
39
+ * Generated bundle index. Do not edit.
40
+ */
41
+
42
+ export { STALE_LINK_SIZES, UiStaleLink };
43
+ //# sourceMappingURL=joanroucoux-cairn-ui-stale-link.mjs.map