@joanroucoux/cairn-ui 0.8.1 → 0.9.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 (125) hide show
  1. package/CHANGELOG.md +105 -0
  2. package/README.md +2 -0
  3. package/fesm2022/joanroucoux-cairn-ui-action-bar.mjs +57 -53
  4. package/fesm2022/joanroucoux-cairn-ui-action-bar.mjs.map +1 -1
  5. package/fesm2022/joanroucoux-cairn-ui-alert.mjs +155 -47
  6. package/fesm2022/joanroucoux-cairn-ui-alert.mjs.map +1 -1
  7. package/fesm2022/joanroucoux-cairn-ui-amount.mjs +220 -110
  8. package/fesm2022/joanroucoux-cairn-ui-amount.mjs.map +1 -1
  9. package/fesm2022/joanroucoux-cairn-ui-async.mjs +392 -216
  10. package/fesm2022/joanroucoux-cairn-ui-async.mjs.map +1 -1
  11. package/fesm2022/joanroucoux-cairn-ui-avatar.mjs +145 -68
  12. package/fesm2022/joanroucoux-cairn-ui-avatar.mjs.map +1 -1
  13. package/fesm2022/joanroucoux-cairn-ui-back-link.mjs +87 -47
  14. package/fesm2022/joanroucoux-cairn-ui-back-link.mjs.map +1 -1
  15. package/fesm2022/joanroucoux-cairn-ui-badge.mjs +83 -43
  16. package/fesm2022/joanroucoux-cairn-ui-badge.mjs.map +1 -1
  17. package/fesm2022/joanroucoux-cairn-ui-button.mjs +184 -104
  18. package/fesm2022/joanroucoux-cairn-ui-button.mjs.map +1 -1
  19. package/fesm2022/joanroucoux-cairn-ui-card.mjs +232 -137
  20. package/fesm2022/joanroucoux-cairn-ui-card.mjs.map +1 -1
  21. package/fesm2022/joanroucoux-cairn-ui-choice-chips.mjs +298 -102
  22. package/fesm2022/joanroucoux-cairn-ui-choice-chips.mjs.map +1 -1
  23. package/fesm2022/joanroucoux-cairn-ui-control.mjs +4 -17
  24. package/fesm2022/joanroucoux-cairn-ui-control.mjs.map +1 -1
  25. package/fesm2022/joanroucoux-cairn-ui-delta.mjs +150 -83
  26. package/fesm2022/joanroucoux-cairn-ui-delta.mjs.map +1 -1
  27. package/fesm2022/joanroucoux-cairn-ui-dialog.mjs +508 -471
  28. package/fesm2022/joanroucoux-cairn-ui-dialog.mjs.map +1 -1
  29. package/fesm2022/joanroucoux-cairn-ui-donut.mjs +255 -157
  30. package/fesm2022/joanroucoux-cairn-ui-donut.mjs.map +1 -1
  31. package/fesm2022/joanroucoux-cairn-ui-drawer.mjs +303 -0
  32. package/fesm2022/joanroucoux-cairn-ui-drawer.mjs.map +1 -0
  33. package/fesm2022/joanroucoux-cairn-ui-empty.mjs +76 -35
  34. package/fesm2022/joanroucoux-cairn-ui-empty.mjs.map +1 -1
  35. package/fesm2022/joanroucoux-cairn-ui-external-link.mjs +40 -31
  36. package/fesm2022/joanroucoux-cairn-ui-external-link.mjs.map +1 -1
  37. package/fesm2022/joanroucoux-cairn-ui-fact.mjs +188 -79
  38. package/fesm2022/joanroucoux-cairn-ui-fact.mjs.map +1 -1
  39. package/fesm2022/joanroucoux-cairn-ui-field.mjs +309 -173
  40. package/fesm2022/joanroucoux-cairn-ui-field.mjs.map +1 -1
  41. package/fesm2022/joanroucoux-cairn-ui-filter-chips.mjs +165 -87
  42. package/fesm2022/joanroucoux-cairn-ui-filter-chips.mjs.map +1 -1
  43. package/fesm2022/joanroucoux-cairn-ui-group-header.mjs +269 -0
  44. package/fesm2022/joanroucoux-cairn-ui-group-header.mjs.map +1 -0
  45. package/fesm2022/joanroucoux-cairn-ui-input.mjs +205 -85
  46. package/fesm2022/joanroucoux-cairn-ui-input.mjs.map +1 -1
  47. package/fesm2022/joanroucoux-cairn-ui-line-chart.mjs +838 -512
  48. package/fesm2022/joanroucoux-cairn-ui-line-chart.mjs.map +1 -1
  49. package/fesm2022/joanroucoux-cairn-ui-menu.mjs +401 -225
  50. package/fesm2022/joanroucoux-cairn-ui-menu.mjs.map +1 -1
  51. package/fesm2022/joanroucoux-cairn-ui-meter.mjs +143 -57
  52. package/fesm2022/joanroucoux-cairn-ui-meter.mjs.map +1 -1
  53. package/fesm2022/joanroucoux-cairn-ui-motion.mjs +367 -399
  54. package/fesm2022/joanroucoux-cairn-ui-motion.mjs.map +1 -1
  55. package/fesm2022/joanroucoux-cairn-ui-nav-item.mjs +73 -44
  56. package/fesm2022/joanroucoux-cairn-ui-nav-item.mjs.map +1 -1
  57. package/fesm2022/joanroucoux-cairn-ui-result-group.mjs +191 -0
  58. package/fesm2022/joanroucoux-cairn-ui-result-group.mjs.map +1 -0
  59. package/fesm2022/joanroucoux-cairn-ui-row.mjs +376 -146
  60. package/fesm2022/joanroucoux-cairn-ui-row.mjs.map +1 -1
  61. package/fesm2022/joanroucoux-cairn-ui-segmented.mjs +183 -81
  62. package/fesm2022/joanroucoux-cairn-ui-segmented.mjs.map +1 -1
  63. package/fesm2022/joanroucoux-cairn-ui-select-pill.mjs +233 -0
  64. package/fesm2022/joanroucoux-cairn-ui-select-pill.mjs.map +1 -0
  65. package/fesm2022/joanroucoux-cairn-ui-select.mjs +121 -67
  66. package/fesm2022/joanroucoux-cairn-ui-select.mjs.map +1 -1
  67. package/fesm2022/joanroucoux-cairn-ui-skeleton.mjs +129 -51
  68. package/fesm2022/joanroucoux-cairn-ui-skeleton.mjs.map +1 -1
  69. package/fesm2022/joanroucoux-cairn-ui-stale-link.mjs +80 -39
  70. package/fesm2022/joanroucoux-cairn-ui-stale-link.mjs.map +1 -1
  71. package/fesm2022/joanroucoux-cairn-ui-stat.mjs +85 -55
  72. package/fesm2022/joanroucoux-cairn-ui-stat.mjs.map +1 -1
  73. package/fesm2022/joanroucoux-cairn-ui-switch.mjs +48 -35
  74. package/fesm2022/joanroucoux-cairn-ui-switch.mjs.map +1 -1
  75. package/fesm2022/joanroucoux-cairn-ui-tab-bar.mjs +113 -69
  76. package/fesm2022/joanroucoux-cairn-ui-tab-bar.mjs.map +1 -1
  77. package/fesm2022/joanroucoux-cairn-ui-table.mjs +1036 -310
  78. package/fesm2022/joanroucoux-cairn-ui-table.mjs.map +1 -1
  79. package/fesm2022/joanroucoux-cairn-ui-toast.mjs +240 -127
  80. package/fesm2022/joanroucoux-cairn-ui-toast.mjs.map +1 -1
  81. package/fesm2022/joanroucoux-cairn-ui.mjs +0 -4
  82. package/package.json +17 -1
  83. package/styles/motion.css +1 -13
  84. package/styles/tokens.css +35 -64
  85. package/types/joanroucoux-cairn-ui-action-bar.d.ts +9 -14
  86. package/types/joanroucoux-cairn-ui-alert.d.ts +29 -15
  87. package/types/joanroucoux-cairn-ui-amount.d.ts +64 -32
  88. package/types/joanroucoux-cairn-ui-async.d.ts +85 -38
  89. package/types/joanroucoux-cairn-ui-avatar.d.ts +34 -22
  90. package/types/joanroucoux-cairn-ui-back-link.d.ts +20 -15
  91. package/types/joanroucoux-cairn-ui-badge.d.ts +22 -14
  92. package/types/joanroucoux-cairn-ui-button.d.ts +36 -18
  93. package/types/joanroucoux-cairn-ui-card.d.ts +48 -23
  94. package/types/joanroucoux-cairn-ui-choice-chips.d.ts +91 -35
  95. package/types/joanroucoux-cairn-ui-control.d.ts +9 -18
  96. package/types/joanroucoux-cairn-ui-delta.d.ts +46 -24
  97. package/types/joanroucoux-cairn-ui-dialog.d.ts +124 -57
  98. package/types/joanroucoux-cairn-ui-donut.d.ts +83 -46
  99. package/types/joanroucoux-cairn-ui-drawer.d.ts +73 -0
  100. package/types/joanroucoux-cairn-ui-empty.d.ts +20 -13
  101. package/types/joanroucoux-cairn-ui-external-link.d.ts +6 -10
  102. package/types/joanroucoux-cairn-ui-fact.d.ts +49 -25
  103. package/types/joanroucoux-cairn-ui-field.d.ts +72 -49
  104. package/types/joanroucoux-cairn-ui-filter-chips.d.ts +35 -20
  105. package/types/joanroucoux-cairn-ui-group-header.d.ts +58 -0
  106. package/types/joanroucoux-cairn-ui-input.d.ts +59 -37
  107. package/types/joanroucoux-cairn-ui-line-chart.d.ts +199 -89
  108. package/types/joanroucoux-cairn-ui-menu.d.ts +81 -44
  109. package/types/joanroucoux-cairn-ui-meter.d.ts +44 -22
  110. package/types/joanroucoux-cairn-ui-motion.d.ts +61 -75
  111. package/types/joanroucoux-cairn-ui-nav-item.d.ts +15 -13
  112. package/types/joanroucoux-cairn-ui-result-group.d.ts +52 -0
  113. package/types/joanroucoux-cairn-ui-row.d.ts +79 -37
  114. package/types/joanroucoux-cairn-ui-segmented.d.ts +51 -27
  115. package/types/joanroucoux-cairn-ui-select-pill.d.ts +52 -0
  116. package/types/joanroucoux-cairn-ui-select.d.ts +28 -18
  117. package/types/joanroucoux-cairn-ui-skeleton.d.ts +35 -18
  118. package/types/joanroucoux-cairn-ui-stale-link.d.ts +23 -16
  119. package/types/joanroucoux-cairn-ui-stat.d.ts +26 -21
  120. package/types/joanroucoux-cairn-ui-switch.d.ts +7 -11
  121. package/types/joanroucoux-cairn-ui-tab-bar.d.ts +20 -17
  122. package/types/joanroucoux-cairn-ui-table.d.ts +251 -89
  123. package/types/joanroucoux-cairn-ui-toast.d.ts +46 -36
  124. package/types/joanroucoux-cairn-ui.d.ts +1 -2
  125. package/fesm2022/joanroucoux-cairn-ui.mjs.map +0 -1
@@ -1,415 +1,383 @@
1
- import * as i0 from '@angular/core';
2
- import { Injectable, signal, inject, DestroyRef, ElementRef, afterNextRender, Directive, input, effect, untracked } from '@angular/core';
3
-
4
- class FlipItems {
5
- items = new Set();
6
- destroyed = new Set();
7
- used = false;
8
- get marked() {
9
- return this.used;
10
- }
11
- add(item) {
12
- this.used = true;
13
- this.items.add(item);
14
- }
15
- remove(item) {
16
- this.destroyed.add(item);
17
- }
18
- inside(host) {
19
- const inside = [];
20
- for (const item of this.items) {
21
- if (host.contains(item)) {
22
- inside.push(item);
23
- }
24
- else if (this.destroyed.has(item)) {
25
- this.items.delete(item);
26
- this.destroyed.delete(item);
27
- }
28
- }
29
- return inside;
30
- }
31
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FlipItems, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
32
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FlipItems });
33
- }
34
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FlipItems, decorators: [{
35
- type: Injectable
36
- }] });
37
-
1
+ import * as i0 from "@angular/core";
2
+ import { DestroyRef, Directive, ElementRef, Injectable, afterNextRender, effect, inject, input, signal, untracked } from "@angular/core";
3
+ var FlipItems = class FlipItems {
4
+ items = /* @__PURE__ */ new Set();
5
+ destroyed = /* @__PURE__ */ new Set();
6
+ used = false;
7
+ get marked() {
8
+ return this.used;
9
+ }
10
+ add(item) {
11
+ this.used = true;
12
+ this.items.add(item);
13
+ }
14
+ remove(item) {
15
+ this.destroyed.add(item);
16
+ }
17
+ inside(host) {
18
+ const inside = [];
19
+ for (const item of this.items) if (host.contains(item)) inside.push(item);
20
+ else if (this.destroyed.has(item)) {
21
+ this.items.delete(item);
22
+ this.destroyed.delete(item);
23
+ }
24
+ return inside;
25
+ }
26
+ static ɵfac = i0.ɵɵngDeclareFactory({
27
+ minVersion: "12.0.0",
28
+ version: "22.2.0",
29
+ ngImport: i0,
30
+ type: FlipItems,
31
+ deps: [],
32
+ target: i0.ɵɵFactoryTarget.Injectable
33
+ });
34
+ static ɵprov = i0.ɵɵngDeclareInjectable({
35
+ minVersion: "12.0.0",
36
+ version: "22.2.0",
37
+ ngImport: i0,
38
+ type: FlipItems
39
+ });
40
+ };
41
+ i0.ɵɵngDeclareClassMetadata({
42
+ minVersion: "12.0.0",
43
+ version: "22.2.0",
44
+ ngImport: i0,
45
+ type: FlipItems,
46
+ decorators: [{ type: Injectable }]
47
+ });
38
48
  function readDuration(element, name, fallback) {
39
- const raw = getComputedStyle(element).getPropertyValue(name).trim();
40
- const value = parseFloat(raw);
41
- if (Number.isNaN(value)) {
42
- return fallback;
43
- }
44
- return raw.endsWith('ms') ? value : value * 1000;
49
+ const raw = getComputedStyle(element).getPropertyValue(name).trim();
50
+ const value = parseFloat(raw);
51
+ if (Number.isNaN(value)) return fallback;
52
+ return raw.endsWith("ms") ? value : value * 1e3;
45
53
  }
46
54
  function readEasing(element, name, fallback) {
47
- return getComputedStyle(element).getPropertyValue(name).trim() || fallback;
55
+ return getComputedStyle(element).getPropertyValue(name).trim() || fallback;
48
56
  }
49
-
50
- const QUERY = '(prefers-reduced-motion: reduce)';
51
- /**
52
- * A signal that follows the user's `prefers-reduced-motion` preference live.
53
- * Call it in an injection context; the listener is removed with that context.
54
- *
55
- * @example
56
- * protected readonly reduced = injectReducedMotion();
57
- */
57
+ const QUERY = "(prefers-reduced-motion: reduce)";
58
58
  function injectReducedMotion() {
59
- if (typeof matchMedia === 'undefined') {
60
- return signal(false).asReadonly();
61
- }
62
- const query = matchMedia(QUERY);
63
- const reduced = signal(query.matches, /* @ts-ignore */
64
- ...(ngDevMode ? [{ debugName: "reduced" }] : /* istanbul ignore next */ []));
65
- const listener = (event) => reduced.set(event.matches);
66
- query.addEventListener('change', listener);
67
- inject(DestroyRef).onDestroy(() => query.removeEventListener('change', listener));
68
- return reduced.asReadonly();
59
+ if (typeof matchMedia === "undefined") return signal(false).asReadonly();
60
+ const query = matchMedia(QUERY);
61
+ const reduced = signal(query.matches, ...ngDevMode ? [{ debugName: "reduced" }] : /* istanbul ignore next */ []);
62
+ const listener = (event) => reduced.set(event.matches);
63
+ query.addEventListener("change", listener);
64
+ inject(DestroyRef).onDestroy(() => query.removeEventListener("change", listener));
65
+ return reduced.asReadonly();
69
66
  }
70
-
71
67
  function translateY(element) {
72
- const matrix = /^matrix\((.+)\)$/.exec(getComputedStyle(element).transform);
73
- return matrix ? parseFloat(matrix[1].split(',')[5]) : 0;
74
- }
75
- /**
76
- * Slides the items of a list into place when one is removed or inserted (FLIP): it measures every item
77
- * before and after the change, from the top of the list itself, then animates the shift in `translateY`
78
- * over `--duration-base` `--ease-out`. Pair it with `animate.leave="ui-leave-fade"` on the item so it fades
79
- * out first. Nothing moves under `prefers-reduced-motion`: the list closes up at once.
80
- *
81
- * By default the items are the direct children of the list. Mark them with `uiFlipItem` instead when they
82
- * sit deeper, for instance a page of several groups: put `uiFlipList` on the element around every group and
83
- * `uiFlipItem` on each group heading and row, so a removal also slides the groups below. An item may sit
84
- * inside another item (a row inside a card): it then moves by its own share and its card carries the rest.
85
- * Only an item added or removed starts a slide; a change inside an item does not. Positions leave out the
86
- * translate of any animation running inside the list (a slide in progress, an enter fade-up).
87
- *
88
- * @example
89
- * <ul uiFlipList>
90
- * @for (key of keys(); track key.id) {
91
- * <li uiListRow animate.leave="ui-leave-fade">...</li>
92
- * }
93
- * </ul>
94
- *
95
- * <table uiTable uiFlipList>
96
- * @for (group of groups(); track group.id) {
97
- * <tbody>
98
- * <tr uiTr group uiFlipItem>...</tr>
99
- * @for (row of group.rows; track row.id) {
100
- * <tr uiTr uiFlipItem animate.leave="ui-leave-fade">...</tr>
101
- * }
102
- * </tbody>
103
- * }
104
- * </table>
105
- */
106
- class UiFlipList {
107
- host = inject(ElementRef).nativeElement;
108
- reduced = injectReducedMotion();
109
- mutations = new MutationObserver(() => this.onChange());
110
- resizes = new ResizeObserver(() => {
111
- this.tops = this.measure();
112
- });
113
- running = new Map();
114
- watched = new Set();
115
- items = inject(FlipItems, { self: true });
116
- tops = new Map();
117
- shifts = new Map();
118
- constructor() {
119
- afterNextRender(() => {
120
- this.tops = this.measure();
121
- this.mutations.observe(this.host, { childList: true, subtree: true });
122
- this.resizes.observe(this.host);
123
- });
124
- inject(DestroyRef).onDestroy(() => {
125
- this.mutations.disconnect();
126
- this.resizes.disconnect();
127
- });
128
- }
129
- tracked() {
130
- if (!this.items.marked) {
131
- return Array.from(this.host.children).filter((child) => child instanceof HTMLElement);
132
- }
133
- return this.items.inside(this.host);
134
- }
135
- measure() {
136
- const elements = this.tracked();
137
- const origin = this.host.getBoundingClientRect().top - this.host.scrollTop;
138
- const tops = new Map(elements.map((element) => [element, element.getBoundingClientRect().top - origin]));
139
- this.shifts = new Map();
140
- for (const animation of this.host.getAnimations({ subtree: true })) {
141
- const target = animation.effect.target;
142
- if (target !== this.host) {
143
- this.shifts.set(target, translateY(target));
144
- }
145
- }
146
- for (const [moving, shift] of this.shifts) {
147
- for (const [element, top] of tops) {
148
- if (moving.contains(element)) {
149
- tops.set(element, top - shift);
150
- }
151
- }
152
- }
153
- for (const element of elements) {
154
- if (!this.watched.has(element)) {
155
- this.watched.add(element);
156
- this.resizes.observe(element);
157
- }
158
- }
159
- for (const element of this.watched) {
160
- if (!tops.has(element)) {
161
- this.watched.delete(element);
162
- this.running.delete(element);
163
- this.resizes.unobserve(element);
164
- }
165
- }
166
- return tops;
167
- }
168
- onChange() {
169
- const before = this.tops;
170
- const after = this.measure();
171
- this.tops = after;
172
- const changed = after.size !== before.size || Array.from(after.keys()).some((element) => !before.has(element));
173
- if (this.reduced() || !changed) {
174
- return;
175
- }
176
- const duration = readDuration(this.host, '--duration-base', 260);
177
- const easing = readEasing(this.host, '--ease-out', 'ease-out');
178
- const moved = (element) => (before.get(element) ?? 0) - (after.get(element) ?? 0);
179
- for (const [element] of after) {
180
- if (!before.has(element)) {
181
- continue;
182
- }
183
- const own = moved(element) - moved(this.parentItem(element, before, after));
184
- if (Math.abs(own) < 0.5) {
185
- continue;
186
- }
187
- const from = own + (this.shifts.get(element) ?? 0);
188
- this.running.get(element)?.cancel();
189
- const animation = element.animate([{ transform: `translateY(${from}px)` }, { transform: 'translateY(0)' }], {
190
- duration,
191
- easing,
192
- });
193
- animation.onfinish = () => {
194
- if (this.running.get(element) === animation) {
195
- this.running.delete(element);
196
- }
197
- };
198
- this.running.set(element, animation);
199
- }
200
- }
201
- parentItem(element, before, after) {
202
- let node = element.parentElement;
203
- while (node !== this.host && !(before.has(node) && after.has(node))) {
204
- node = node.parentElement;
205
- }
206
- return node;
207
- }
208
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiFlipList, deps: [], target: i0.ɵɵFactoryTarget.Directive });
209
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.7", type: UiFlipList, isStandalone: true, selector: "[uiFlipList]", providers: [FlipItems], ngImport: i0 });
210
- }
211
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiFlipList, decorators: [{
212
- type: Directive,
213
- args: [{
214
- selector: '[uiFlipList]',
215
- providers: [FlipItems],
216
- }]
217
- }], ctorParameters: () => [] });
218
- /**
219
- * Marks an element as an item of the nearest `uiFlipList`, at any depth: a group heading, a row inside a
220
- * group, a card. Once one item is marked, the list follows its marked items only.
221
- *
222
- * @example
223
- * <tr uiTr uiFlipItem animate.leave="ui-leave-fade">...</tr>
224
- */
225
- class UiFlipItem {
226
- constructor() {
227
- const items = inject(FlipItems, { optional: true });
228
- if (items) {
229
- const element = inject(ElementRef).nativeElement;
230
- items.add(element);
231
- inject(DestroyRef).onDestroy(() => items.remove(element));
232
- }
233
- }
234
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiFlipItem, deps: [], target: i0.ɵɵFactoryTarget.Directive });
235
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.7", type: UiFlipItem, isStandalone: true, selector: "[uiFlipItem]", ngImport: i0 });
68
+ const matrix = /^matrix\((.+)\)$/.exec(getComputedStyle(element).transform);
69
+ return matrix ? parseFloat(matrix[1].split(",")[5]) : 0;
236
70
  }
237
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiFlipItem, decorators: [{
238
- type: Directive,
239
- args: [{
240
- selector: '[uiFlipItem]',
241
- }]
242
- }], ctorParameters: () => [] });
243
-
71
+ var UiFlipList = class UiFlipList {
72
+ host = inject(ElementRef).nativeElement;
73
+ reduced = injectReducedMotion();
74
+ mutations = new MutationObserver(() => this.onChange());
75
+ resizes = new ResizeObserver(() => {
76
+ this.tops = this.measure();
77
+ });
78
+ running = /* @__PURE__ */ new Map();
79
+ watched = /* @__PURE__ */ new Set();
80
+ items = inject(FlipItems, { self: true });
81
+ tops = /* @__PURE__ */ new Map();
82
+ shifts = /* @__PURE__ */ new Map();
83
+ constructor() {
84
+ afterNextRender(() => {
85
+ this.tops = this.measure();
86
+ this.mutations.observe(this.host, {
87
+ childList: true,
88
+ subtree: true
89
+ });
90
+ this.resizes.observe(this.host);
91
+ });
92
+ inject(DestroyRef).onDestroy(() => {
93
+ this.mutations.disconnect();
94
+ this.resizes.disconnect();
95
+ });
96
+ }
97
+ tracked() {
98
+ if (!this.items.marked) return Array.from(this.host.children).filter((child) => child instanceof HTMLElement);
99
+ return this.items.inside(this.host);
100
+ }
101
+ measure() {
102
+ const elements = this.tracked();
103
+ const origin = this.host.getBoundingClientRect().top - this.host.scrollTop;
104
+ const tops = new Map(elements.map((element) => [element, element.getBoundingClientRect().top - origin]));
105
+ this.shifts = /* @__PURE__ */ new Map();
106
+ for (const animation of this.host.getAnimations({ subtree: true })) {
107
+ const target = animation.effect.target;
108
+ if (target !== this.host) this.shifts.set(target, translateY(target));
109
+ }
110
+ for (const [moving, shift] of this.shifts) for (const [element, top] of tops) if (moving.contains(element)) tops.set(element, top - shift);
111
+ for (const element of elements) if (!this.watched.has(element)) {
112
+ this.watched.add(element);
113
+ this.resizes.observe(element);
114
+ }
115
+ for (const element of this.watched) if (!tops.has(element)) {
116
+ this.watched.delete(element);
117
+ this.running.delete(element);
118
+ this.resizes.unobserve(element);
119
+ }
120
+ return tops;
121
+ }
122
+ onChange() {
123
+ const before = this.tops;
124
+ const after = this.measure();
125
+ this.tops = after;
126
+ const changed = after.size !== before.size || Array.from(after.keys()).some((element) => !before.has(element));
127
+ if (this.reduced() || !changed) return;
128
+ const duration = readDuration(this.host, "--duration-base", 260);
129
+ const easing = readEasing(this.host, "--ease-out", "ease-out");
130
+ const moved = (element) => (before.get(element) ?? 0) - (after.get(element) ?? 0);
131
+ for (const [element] of after) {
132
+ if (!before.has(element)) continue;
133
+ const own = moved(element) - moved(this.parentItem(element, before, after));
134
+ if (Math.abs(own) < .5) continue;
135
+ const from = own + (this.shifts.get(element) ?? 0);
136
+ this.running.get(element)?.cancel();
137
+ const animation = element.animate([{ transform: `translateY(${from}px)` }, { transform: "translateY(0)" }], {
138
+ duration,
139
+ easing
140
+ });
141
+ animation.onfinish = () => {
142
+ if (this.running.get(element) === animation) this.running.delete(element);
143
+ };
144
+ this.running.set(element, animation);
145
+ }
146
+ }
147
+ parentItem(element, before, after) {
148
+ let node = element.parentElement;
149
+ while (node !== this.host && !(before.has(node) && after.has(node))) node = node.parentElement;
150
+ return node;
151
+ }
152
+ static ɵfac = i0.ɵɵngDeclareFactory({
153
+ minVersion: "12.0.0",
154
+ version: "22.2.0",
155
+ ngImport: i0,
156
+ type: UiFlipList,
157
+ deps: [],
158
+ target: i0.ɵɵFactoryTarget.Directive
159
+ });
160
+ static ɵdir = i0.ɵɵngDeclareDirective({
161
+ minVersion: "14.0.0",
162
+ version: "22.2.0",
163
+ type: UiFlipList,
164
+ isStandalone: true,
165
+ selector: "[uiFlipList]",
166
+ providers: [FlipItems],
167
+ ngImport: i0
168
+ });
169
+ };
170
+ i0.ɵɵngDeclareClassMetadata({
171
+ minVersion: "12.0.0",
172
+ version: "22.2.0",
173
+ ngImport: i0,
174
+ type: UiFlipList,
175
+ decorators: [{
176
+ type: Directive,
177
+ args: [{
178
+ selector: "[uiFlipList]",
179
+ providers: [FlipItems]
180
+ }]
181
+ }],
182
+ ctorParameters: () => []
183
+ });
184
+ var UiFlipItem = class UiFlipItem {
185
+ constructor() {
186
+ const items = inject(FlipItems, { optional: true });
187
+ if (items) {
188
+ const element = inject(ElementRef).nativeElement;
189
+ items.add(element);
190
+ inject(DestroyRef).onDestroy(() => items.remove(element));
191
+ }
192
+ }
193
+ static ɵfac = i0.ɵɵngDeclareFactory({
194
+ minVersion: "12.0.0",
195
+ version: "22.2.0",
196
+ ngImport: i0,
197
+ type: UiFlipItem,
198
+ deps: [],
199
+ target: i0.ɵɵFactoryTarget.Directive
200
+ });
201
+ static ɵdir = i0.ɵɵngDeclareDirective({
202
+ minVersion: "14.0.0",
203
+ version: "22.2.0",
204
+ type: UiFlipItem,
205
+ isStandalone: true,
206
+ selector: "[uiFlipItem]",
207
+ ngImport: i0
208
+ });
209
+ };
210
+ i0.ɵɵngDeclareClassMetadata({
211
+ minVersion: "12.0.0",
212
+ version: "22.2.0",
213
+ ngImport: i0,
214
+ type: UiFlipItem,
215
+ decorators: [{
216
+ type: Directive,
217
+ args: [{ selector: "[uiFlipItem]" }]
218
+ }],
219
+ ctorParameters: () => []
220
+ });
244
221
  const HOLD_MS = 200;
245
222
  const SCROLL_SETTLE_MS = 200;
246
223
  const SCROLL_START_MS = 300;
247
224
  function paintedElements(host) {
248
- if (host.tagName === 'TR') {
249
- return Array.from(host.children).filter((cell) => cell instanceof HTMLElement);
250
- }
251
- if (host.hasAttribute('ui-group-cell') && host.firstElementChild instanceof HTMLElement) {
252
- return [host.firstElementChild];
253
- }
254
- return [host];
255
- }
256
- /**
257
- * Draws the eye to an element that just changed: scrolls it into view when it is off screen, holds the
258
- * `--soft` fill for 200 ms, then fades back to the element's own background over `--duration-highlight`.
259
- * Each new non-null token plays once, so pass a fresh value per event (an object or a counter), not a
260
- * boolean. A non-null token already present at first render plays on arrival. On a `tr` it paints the
261
- * cells, on a `td[ui-group-cell]` the band, anywhere else the host. Under `prefers-reduced-motion` the
262
- * scroll is instant and the fade lasts 600 ms.
263
- *
264
- * It starts on the next frame and, when the element is still playing a finite animation of its own (an
265
- * `animate.enter` fade on a new row), once that animation has finished, waiting no longer than
266
- * `--duration-base`: the element fades in, then lights up. Both can sit on the same element.
267
- *
268
- * Give the element `scroll-margin-top` and `scroll-margin-bottom` equal to what is pinned above and
269
- * below it (header, tab bar, action bar): a row under them counts as off screen and is scrolled out.
270
- * For an arrival on a page opened at the element, scroll to it at once first (`scrollIntoView` in
271
- * `afterNextRender`) and set the token afterwards: a token present at first render scrolls smoothly
272
- * from where the page is.
273
- *
274
- * @example
275
- * <a ui-row [uiHighlight]="changed() === holding.id ? token() : null" routerLink="/holdings/1">...</a>
276
- */
277
- class UiHighlight {
278
- uiHighlight = input(null, /* @ts-ignore */
279
- ...(ngDevMode ? [{ debugName: "uiHighlight" }] : /* istanbul ignore next */ []));
280
- host = inject(ElementRef).nativeElement;
281
- reduced = injectReducedMotion();
282
- cancel = null;
283
- constructor() {
284
- effect(() => {
285
- const token = this.uiHighlight();
286
- if (token === null || token === undefined) {
287
- return;
288
- }
289
- untracked(() => this.play());
290
- });
291
- inject(DestroyRef).onDestroy(() => this.cancel?.());
292
- }
293
- play() {
294
- this.cancel?.();
295
- if (this.isVisible()) {
296
- this.cancel = this.flash();
297
- return;
298
- }
299
- const reduced = this.reduced();
300
- this.host.scrollIntoView({ behavior: reduced ? 'auto' : 'smooth', block: 'center' });
301
- if (reduced) {
302
- this.cancel = this.flash();
303
- return;
304
- }
305
- let stopFlash = null;
306
- let timer;
307
- const moves = (event) => event.target === document || (event.target instanceof Node && event.target.contains(this.host));
308
- const start = () => {
309
- stopListening();
310
- stopFlash = this.flash();
311
- };
312
- const onScrollEnd = (event) => {
313
- if (moves(event)) {
314
- start();
315
- }
316
- };
317
- const onScroll = (event) => {
318
- if (moves(event)) {
319
- clearTimeout(timer);
320
- timer = setTimeout(start, SCROLL_SETTLE_MS);
321
- }
322
- };
323
- const stopListening = () => {
324
- clearTimeout(timer);
325
- window.removeEventListener('scrollend', onScrollEnd, true);
326
- window.removeEventListener('scroll', onScroll, true);
327
- };
328
- timer = setTimeout(start, SCROLL_START_MS);
329
- window.addEventListener('scrollend', onScrollEnd, true);
330
- window.addEventListener('scroll', onScroll, true);
331
- this.cancel = () => {
332
- stopListening();
333
- stopFlash?.();
334
- };
335
- }
336
- isVisible() {
337
- const rect = this.host.getBoundingClientRect();
338
- const style = getComputedStyle(this.host);
339
- const top = parseFloat(style.scrollMarginTop) || 0;
340
- const bottom = parseFloat(style.scrollMarginBottom) || 0;
341
- return rect.top >= top && rect.bottom <= window.innerHeight - bottom;
342
- }
343
- flash() {
344
- const painted = paintedElements(this.host);
345
- let animations = [];
346
- let timer;
347
- const paint = () => {
348
- const fade = readDuration(this.host, '--duration-highlight', 1200);
349
- const easing = readEasing(this.host, '--ease-out', 'ease-out');
350
- const total = HOLD_MS + fade;
351
- animations = painted.map((element) => element.animate([
352
- { backgroundColor: 'var(--soft)', offset: 0 },
353
- { backgroundColor: 'var(--soft)', offset: HOLD_MS / total, easing },
354
- { offset: 1 },
355
- ], { duration: total }));
356
- };
357
- const frame = requestAnimationFrame(() => {
358
- const remaining = Array.from(new Set([this.host, ...painted]))
359
- .flatMap((element) => element.getAnimations())
360
- .map((animation) => {
361
- const timing = animation.effect.getComputedTiming();
362
- return Number(timing.endTime) - Number(timing.localTime ?? 0);
363
- })
364
- .filter((left) => Number.isFinite(left));
365
- if (remaining.length === 0) {
366
- paint();
367
- return;
368
- }
369
- timer = setTimeout(paint, Math.min(Math.max(...remaining), readDuration(this.host, '--duration-base', 260)));
370
- });
371
- return () => {
372
- cancelAnimationFrame(frame);
373
- clearTimeout(timer);
374
- animations.forEach((animation) => animation.cancel());
375
- };
376
- }
377
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiHighlight, deps: [], target: i0.ɵɵFactoryTarget.Directive });
378
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.7", type: UiHighlight, isStandalone: true, selector: "[uiHighlight]", inputs: { uiHighlight: { classPropertyName: "uiHighlight", publicName: "uiHighlight", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
225
+ if (host.tagName === "TR") return Array.from(host.children).filter((cell) => cell instanceof HTMLElement);
226
+ if (host.hasAttribute("ui-group-cell") && host.firstElementChild instanceof HTMLElement) return [host.firstElementChild];
227
+ return [host];
379
228
  }
380
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiHighlight, decorators: [{
381
- type: Directive,
382
- args: [{
383
- selector: '[uiHighlight]',
384
- }]
385
- }], ctorParameters: () => [], propDecorators: { uiHighlight: [{ type: i0.Input, args: [{ isSignal: true, alias: "uiHighlight", required: false }] }] } });
386
-
387
- /**
388
- * Holds the transitions of the host and of everything inside it until two frames after its first
389
- * render, through the `data-ui-settling` attribute and the matching rule of `styles/motion.css`.
390
- * A route's elements are created a tick before their classes are bound; a layout read in between
391
- * would otherwise turn the bound fill, colour or switch track into a transition on page open. Call it
392
- * in an injection context. A host whose view is never refreshed (a detached change detector) keeps the
393
- * attribute, and with it no transition.
394
- *
395
- * @example
396
- * constructor() {
397
- * holdTransitionsUntilRendered();
398
- * }
399
- */
229
+ var UiHighlight = class UiHighlight {
230
+ uiHighlight = input(null, ...ngDevMode ? [{ debugName: "uiHighlight" }] : /* istanbul ignore next */ []);
231
+ host = inject(ElementRef).nativeElement;
232
+ reduced = injectReducedMotion();
233
+ cancel = null;
234
+ constructor() {
235
+ effect(() => {
236
+ const token = this.uiHighlight();
237
+ if (token === null || token === void 0) return;
238
+ untracked(() => this.play());
239
+ });
240
+ inject(DestroyRef).onDestroy(() => this.cancel?.());
241
+ }
242
+ play() {
243
+ this.cancel?.();
244
+ if (this.isVisible()) {
245
+ this.cancel = this.flash();
246
+ return;
247
+ }
248
+ const reduced = this.reduced();
249
+ this.host.scrollIntoView({
250
+ behavior: reduced ? "auto" : "smooth",
251
+ block: "center"
252
+ });
253
+ if (reduced) {
254
+ this.cancel = this.flash();
255
+ return;
256
+ }
257
+ let stopFlash = null;
258
+ let timer;
259
+ const moves = (event) => event.target === document || event.target instanceof Node && event.target.contains(this.host);
260
+ const start = () => {
261
+ stopListening();
262
+ stopFlash = this.flash();
263
+ };
264
+ const onScrollEnd = (event) => {
265
+ if (moves(event)) start();
266
+ };
267
+ const onScroll = (event) => {
268
+ if (moves(event)) {
269
+ clearTimeout(timer);
270
+ timer = setTimeout(start, SCROLL_SETTLE_MS);
271
+ }
272
+ };
273
+ const stopListening = () => {
274
+ clearTimeout(timer);
275
+ window.removeEventListener("scrollend", onScrollEnd, true);
276
+ window.removeEventListener("scroll", onScroll, true);
277
+ };
278
+ timer = setTimeout(start, SCROLL_START_MS);
279
+ window.addEventListener("scrollend", onScrollEnd, true);
280
+ window.addEventListener("scroll", onScroll, true);
281
+ this.cancel = () => {
282
+ stopListening();
283
+ stopFlash?.();
284
+ };
285
+ }
286
+ isVisible() {
287
+ const rect = this.host.getBoundingClientRect();
288
+ const style = getComputedStyle(this.host);
289
+ const top = parseFloat(style.scrollMarginTop) || 0;
290
+ const bottom = parseFloat(style.scrollMarginBottom) || 0;
291
+ return rect.top >= top && rect.bottom <= window.innerHeight - bottom;
292
+ }
293
+ flash() {
294
+ const painted = paintedElements(this.host);
295
+ let animations = [];
296
+ let timer;
297
+ const paint = () => {
298
+ const fade = readDuration(this.host, "--duration-highlight", 1200);
299
+ const easing = readEasing(this.host, "--ease-out", "ease-out");
300
+ const total = HOLD_MS + fade;
301
+ animations = painted.map((element) => element.animate([
302
+ {
303
+ backgroundColor: "var(--soft)",
304
+ offset: 0
305
+ },
306
+ {
307
+ backgroundColor: "var(--soft)",
308
+ offset: HOLD_MS / total,
309
+ easing
310
+ },
311
+ { offset: 1 }
312
+ ], { duration: total }));
313
+ };
314
+ const frame = requestAnimationFrame(() => {
315
+ const remaining = Array.from(/* @__PURE__ */ new Set([this.host, ...painted])).flatMap((element) => element.getAnimations()).map((animation) => {
316
+ const timing = animation.effect.getComputedTiming();
317
+ return Number(timing.endTime) - Number(timing.localTime ?? 0);
318
+ }).filter((left) => Number.isFinite(left));
319
+ if (remaining.length === 0) {
320
+ paint();
321
+ return;
322
+ }
323
+ timer = setTimeout(paint, Math.min(Math.max(...remaining), readDuration(this.host, "--duration-base", 260)));
324
+ });
325
+ return () => {
326
+ cancelAnimationFrame(frame);
327
+ clearTimeout(timer);
328
+ animations.forEach((animation) => animation.cancel());
329
+ };
330
+ }
331
+ static ɵfac = i0.ɵɵngDeclareFactory({
332
+ minVersion: "12.0.0",
333
+ version: "22.2.0",
334
+ ngImport: i0,
335
+ type: UiHighlight,
336
+ deps: [],
337
+ target: i0.ɵɵFactoryTarget.Directive
338
+ });
339
+ static ɵdir = i0.ɵɵngDeclareDirective({
340
+ minVersion: "17.1.0",
341
+ version: "22.2.0",
342
+ type: UiHighlight,
343
+ isStandalone: true,
344
+ selector: "[uiHighlight]",
345
+ inputs: { uiHighlight: {
346
+ classPropertyName: "uiHighlight",
347
+ publicName: "uiHighlight",
348
+ isSignal: true,
349
+ isRequired: false,
350
+ transformFunction: null
351
+ } },
352
+ ngImport: i0
353
+ });
354
+ };
355
+ i0.ɵɵngDeclareClassMetadata({
356
+ minVersion: "12.0.0",
357
+ version: "22.2.0",
358
+ ngImport: i0,
359
+ type: UiHighlight,
360
+ decorators: [{
361
+ type: Directive,
362
+ args: [{ selector: "[uiHighlight]" }]
363
+ }],
364
+ ctorParameters: () => [],
365
+ propDecorators: { uiHighlight: [{
366
+ type: i0.Input,
367
+ args: [{
368
+ isSignal: true,
369
+ alias: "uiHighlight",
370
+ required: false
371
+ }]
372
+ }] }
373
+ });
400
374
  function holdTransitionsUntilRendered() {
401
- const host = inject(ElementRef).nativeElement;
402
- host.setAttribute('data-ui-settling', '');
403
- afterNextRender({
404
- read: () => {
405
- requestAnimationFrame(() => requestAnimationFrame(() => host.removeAttribute('data-ui-settling')));
406
- },
407
- });
375
+ const host = inject(ElementRef).nativeElement;
376
+ host.setAttribute("data-ui-settling", "");
377
+ afterNextRender({ read: () => {
378
+ requestAnimationFrame(() => requestAnimationFrame(() => host.removeAttribute("data-ui-settling")));
379
+ } });
408
380
  }
409
-
410
- /**
411
- * Generated bundle index. Do not edit.
412
- */
413
-
414
381
  export { UiFlipItem, UiFlipList, UiHighlight, holdTransitionsUntilRendered, injectReducedMotion };
415
- //# sourceMappingURL=joanroucoux-cairn-ui-motion.mjs.map
382
+
383
+ //# sourceMappingURL=joanroucoux-cairn-ui-motion.mjs.map