@joanroucoux/cairn-ui 0.7.2 → 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 (110) hide show
  1. package/CHANGELOG.md +59 -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 -5462
  73. package/fesm2022/joanroucoux-cairn-ui.mjs.map +1 -1
  74. package/package.json +141 -1
  75. package/types/joanroucoux-cairn-ui-action-bar.d.ts +22 -0
  76. package/types/joanroucoux-cairn-ui-alert.d.ts +23 -0
  77. package/types/joanroucoux-cairn-ui-amount.d.ts +45 -0
  78. package/types/joanroucoux-cairn-ui-async.d.ts +58 -0
  79. package/types/joanroucoux-cairn-ui-avatar.d.ts +35 -0
  80. package/types/joanroucoux-cairn-ui-back-link.d.ts +22 -0
  81. package/types/joanroucoux-cairn-ui-badge.d.ts +23 -0
  82. package/types/joanroucoux-cairn-ui-button.d.ts +29 -0
  83. package/types/joanroucoux-cairn-ui-card.d.ts +36 -0
  84. package/types/joanroucoux-cairn-ui-choice-chips.d.ts +46 -0
  85. package/types/joanroucoux-cairn-ui-control.d.ts +27 -0
  86. package/types/joanroucoux-cairn-ui-delta.d.ts +38 -0
  87. package/types/joanroucoux-cairn-ui-dialog.d.ts +72 -0
  88. package/types/joanroucoux-cairn-ui-donut.d.ts +59 -0
  89. package/types/joanroucoux-cairn-ui-empty.d.ts +19 -0
  90. package/types/joanroucoux-cairn-ui-external-link.d.ts +15 -0
  91. package/types/joanroucoux-cairn-ui-fact.d.ts +44 -0
  92. package/types/joanroucoux-cairn-ui-field.d.ts +84 -0
  93. package/types/joanroucoux-cairn-ui-filter-chips.d.ts +28 -0
  94. package/types/joanroucoux-cairn-ui-input.d.ts +51 -0
  95. package/types/joanroucoux-cairn-ui-line-chart.d.ts +114 -0
  96. package/types/joanroucoux-cairn-ui-menu.d.ts +66 -0
  97. package/types/joanroucoux-cairn-ui-meter.d.ts +30 -0
  98. package/types/joanroucoux-cairn-ui-motion.d.ts +124 -0
  99. package/types/joanroucoux-cairn-ui-nav-item.d.ts +21 -0
  100. package/types/joanroucoux-cairn-ui-row.d.ts +78 -0
  101. package/types/joanroucoux-cairn-ui-segmented.d.ts +36 -0
  102. package/types/joanroucoux-cairn-ui-select.d.ts +23 -0
  103. package/types/joanroucoux-cairn-ui-skeleton.d.ts +26 -0
  104. package/types/joanroucoux-cairn-ui-stale-link.d.ts +24 -0
  105. package/types/joanroucoux-cairn-ui-stat.d.ts +32 -0
  106. package/types/joanroucoux-cairn-ui-switch.d.ts +17 -0
  107. package/types/joanroucoux-cairn-ui-tab-bar.d.ts +31 -0
  108. package/types/joanroucoux-cairn-ui-table.d.ts +139 -0
  109. package/types/joanroucoux-cairn-ui-toast.d.ts +51 -0
  110. package/types/joanroucoux-cairn-ui.d.ts +1 -1440
@@ -0,0 +1,844 @@
1
+ import * as i0 from '@angular/core';
2
+ import { signal, inject, DestroyRef, input, booleanAttribute, ElementRef, viewChild, computed, afterNextRender, effect, untracked, Component } from '@angular/core';
3
+ import { injectReducedMotion } from '@joanroucoux/cairn-ui/motion';
4
+ import { scaleLinear } from 'd3-scale';
5
+ import { curveMonotoneX, line } from 'd3-shape';
6
+
7
+ const axisTicks = (points, maxTicks = 5) => {
8
+ if (points.length <= maxTicks) {
9
+ return [...points];
10
+ }
11
+ const count = Math.max(3, Math.min(maxTicks, points.length));
12
+ const step = (points.length - 1) / (count - 1);
13
+ const indexes = new Set();
14
+ for (let tick = 0; tick < count; tick++) {
15
+ indexes.add(Math.round(tick * step));
16
+ }
17
+ return [...indexes].sort((a, b) => a - b).map((index) => points[index]);
18
+ };
19
+ const isCoreTick = (index, count) => {
20
+ if (count <= 3) {
21
+ return true;
22
+ }
23
+ const middle = Math.floor((count - 1) / 2);
24
+ return index === 0 || index === count - 1 || index === middle;
25
+ };
26
+ const firstOfEachRun = (labels, included) => {
27
+ let previous = null;
28
+ return labels.map((text, index) => {
29
+ if (!included(index)) {
30
+ return false;
31
+ }
32
+ const distinct = text !== previous;
33
+ previous = text;
34
+ return distinct;
35
+ });
36
+ };
37
+ const tickLabels = (ticks, format, mode) => {
38
+ const labels = ticks.map((tick) => format(tick.t));
39
+ const count = labels.length;
40
+ const core = (index) => isCoreTick(index, count);
41
+ const everyTick = firstOfEachRun(labels, () => true);
42
+ const coreTicks = firstOfEachRun(labels, core);
43
+ return labels.map((text, index) => {
44
+ const wide = mode === 3 ? coreTicks[index] : everyTick[index];
45
+ const narrow = coreTicks[index];
46
+ if (!wide && !narrow) {
47
+ return null;
48
+ }
49
+ const classes = mode === 'auto' ? `${narrow ? '' : 'max-sm:hidden'} ${wide ? '' : 'sm:hidden'}`.trim() : wide ? '' : 'hidden';
50
+ return { text, classes };
51
+ });
52
+ };
53
+
54
+ const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
55
+ const nearestPointIndex = (points, x) => {
56
+ let nearest = 0;
57
+ let nearestDistance = Infinity;
58
+ points.forEach((point, index) => {
59
+ const distance = Math.abs(point.x - x);
60
+ if (distance < nearestDistance) {
61
+ nearestDistance = distance;
62
+ nearest = index;
63
+ }
64
+ });
65
+ return nearest;
66
+ };
67
+ const navigateIndex = (key, current, last) => {
68
+ switch (key) {
69
+ case 'ArrowRight':
70
+ return { index: clamp((current ?? -1) + 1, 0, last), preventDefault: true };
71
+ case 'ArrowLeft':
72
+ return { index: clamp((current ?? last + 1) - 1, 0, last), preventDefault: true };
73
+ case 'Home':
74
+ return { index: 0, preventDefault: true };
75
+ case 'End':
76
+ return { index: last, preventDefault: true };
77
+ case 'Escape':
78
+ return { index: null, preventDefault: false };
79
+ default:
80
+ return null;
81
+ }
82
+ };
83
+
84
+ const START_LABEL_HEIGHT = 16;
85
+ const AXIS_ROW = 17;
86
+ const TOOLTIP_SIZES = ['compact', 'large'];
87
+ const TOOLTIP_CLASSES = {
88
+ compact: { box: '-top-3 px-2.5', value: 'text-label', delta: 'text-caption' },
89
+ large: { box: 'top-0 px-3', value: 'text-body', delta: 'text-label' },
90
+ };
91
+ const PLOT_PADDING_RATIO = 16 / 240;
92
+ const SPARKLINE_PADDING = 4;
93
+ const START_LABEL_BASELINE_OFFSET = 4;
94
+ const TOOLTIP_OFFSET = 14;
95
+ const TOOLTIP_FLIP_RATIO = 0.55;
96
+ const plotHeightFor = (height, sparkline, axisGap, wide) => sparkline ? height : Math.max(height - resolveGap(axisGap, wide) - AXIS_ROW, 1);
97
+ const plotPaddingFor = (plotHeight, sparkline) => sparkline ? SPARKLINE_PADDING : plotHeight * PLOT_PADDING_RATIO;
98
+ const startLabelBaseline = (boxBottom) => boxBottom - START_LABEL_BASELINE_OFFSET;
99
+ const tooltipPlacement = (ratio) => {
100
+ const flipped = ratio > TOOLTIP_FLIP_RATIO;
101
+ return `left: calc(${ratio * 100}% ${flipped ? '-' : '+'} ${TOOLTIP_OFFSET}px); transform: ${flipped ? 'translateX(-100%)' : 'none'}`;
102
+ };
103
+ const deltaTone = (delta) => delta > 0 ? 'text-(--positive)' : delta < 0 ? 'text-(--negative)' : 'text-(--muted-foreground)';
104
+ const DEFAULT_WIDTH = 640;
105
+ const DEFAULT_HEIGHT = 240;
106
+ const identityValue = (value) => `${value}`;
107
+ const identityDelta = (delta) => `${delta > 0 ? '+' : ''}${delta}`;
108
+ const identityTime = (time) => new Date(time).toISOString();
109
+ const parseAxisTicks = (value) => value === 'auto' ? 'auto' : Number(value);
110
+ const resolveGap = (gap, wide) => (gap === 'auto' ? (wide ? 16 : 12) : gap);
111
+ const resolveTooltip = (size, wide) => size === 'auto' ? (wide ? 'large' : 'compact') : size;
112
+ const tooltipClassesFor = (size, wide) => TOOLTIP_CLASSES[resolveTooltip(size, wide)];
113
+
114
+ const CURVE = curveMonotoneX;
115
+ const buildGeometry = (points, width, height, padding = {}) => {
116
+ if (points.length === 0) {
117
+ return null;
118
+ }
119
+ const { x: paddingX = 4, top: paddingTop = 4, bottom: paddingBottom = 4 } = padding;
120
+ const plotTop = paddingTop;
121
+ const plotBottom = height - paddingBottom;
122
+ const times = points.map((point) => point.t);
123
+ const values = points.map((point) => point.v);
124
+ const minValue = Math.min(...values);
125
+ const maxValue = Math.max(...values);
126
+ const x = scaleLinear()
127
+ .domain([Math.min(...times), Math.max(...times)])
128
+ .range([paddingX, width - paddingX]);
129
+ const y = minValue === maxValue
130
+ ? () => (plotTop + plotBottom) / 2
131
+ : scaleLinear().domain([minValue, maxValue]).range([plotBottom, plotTop]);
132
+ const toX = (point) => (points.length === 1 ? paddingX : x(point.t));
133
+ const lineGenerator = line()
134
+ .x(toX)
135
+ .y((point) => y(point.v))
136
+ .curve(CURVE);
137
+ const first = points[0];
138
+ const last = points.at(-1);
139
+ return {
140
+ line: lineGenerator(points),
141
+ end: { x: toX(last), y: y(last.v) },
142
+ start: { y: y(first.v) },
143
+ points: points.map((point) => ({ ...point, x: toX(point), y: y(point.v) })),
144
+ plotBottom,
145
+ plotTop,
146
+ };
147
+ };
148
+
149
+ const observeSize = (element, destroyRef, measured) => {
150
+ if (typeof ResizeObserver === 'undefined') {
151
+ return;
152
+ }
153
+ const { clientWidth: width, clientHeight: height } = element;
154
+ if (width > 0 && height > 0) {
155
+ measured({ width, height });
156
+ }
157
+ const observer = new ResizeObserver(([entry]) => {
158
+ if (entry) {
159
+ measured({ width: entry.contentRect.width, height: entry.contentRect.height });
160
+ }
161
+ });
162
+ observer.observe(element);
163
+ destroyRef.onDestroy(() => observer.disconnect());
164
+ };
165
+
166
+ const LINE_GAP = 6;
167
+ const COLLISION_MARGIN = 6;
168
+ const EDGE_TOLERANCE = 3;
169
+ const candidateSlots = (startY, box, label) => {
170
+ const aboveTop = startY - LINE_GAP - label.height;
171
+ const aboveBottom = startY - LINE_GAP;
172
+ const belowTop = startY + LINE_GAP;
173
+ const belowBottom = startY + LINE_GAP + label.height;
174
+ const slots = [
175
+ { anchor: 'end', x: box.width, y: aboveBottom, top: aboveTop, bottom: aboveBottom },
176
+ { anchor: 'end', x: box.width, y: belowBottom, top: belowTop, bottom: belowBottom },
177
+ { anchor: 'start', x: 0, y: aboveBottom, top: aboveTop, bottom: aboveBottom },
178
+ { anchor: 'start', x: 0, y: belowBottom, top: belowTop, bottom: belowBottom },
179
+ { anchor: 'end', x: box.width, y: label.height, top: 0, bottom: label.height },
180
+ { anchor: 'start', x: 0, y: label.height, top: 0, bottom: label.height },
181
+ { anchor: 'end', x: box.width, y: box.height, top: box.height - label.height, bottom: box.height },
182
+ { anchor: 'start', x: 0, y: box.height, top: box.height - label.height, bottom: box.height },
183
+ ];
184
+ return slots.filter((slot) => slot.top >= -EDGE_TOLERANCE && slot.bottom <= box.height + EDGE_TOLERANCE);
185
+ };
186
+ const hitsFor = (slot, label, points) => {
187
+ const left = slot.anchor === 'end' ? slot.x - label.width : slot.x;
188
+ const right = slot.anchor === 'end' ? slot.x : slot.x + label.width;
189
+ const top = slot.top - COLLISION_MARGIN;
190
+ const bottom = slot.bottom + COLLISION_MARGIN;
191
+ return points.filter((point) => point.x >= left && point.x <= right && point.y >= top && point.y <= bottom).length;
192
+ };
193
+ const placeStartLabel = (points, startY, box, label) => {
194
+ let best = null;
195
+ for (const slot of candidateSlots(startY, box, label)) {
196
+ const hits = hitsFor(slot, label, points);
197
+ if (!best || hits < best.hits) {
198
+ best = { slot, hits };
199
+ }
200
+ if (hits === 0) {
201
+ break;
202
+ }
203
+ }
204
+ if (!best) {
205
+ return { anchor: 'end', x: box.width, y: label.height };
206
+ }
207
+ const { anchor, x, y } = best.slot;
208
+ return { anchor, x, y };
209
+ };
210
+
211
+ const TRANSITION_SAMPLES = 120;
212
+ const TRANSITION_DURATION = 260;
213
+ const easeOutQuint = (t) => 1 - (1 - t) ** 5;
214
+ const durationFrom = (value) => {
215
+ const match = /^\s*([\d.]+)(ms|s)\s*$/.exec(value);
216
+ const duration = match ? Number(match[1]) * (match[2] === 's' ? 1000 : 1) : Number.NaN;
217
+ return Number.isFinite(duration) ? duration : TRANSITION_DURATION;
218
+ };
219
+ const sameSeries = (a, b) => a === b ||
220
+ (b !== null &&
221
+ a.length === b.length &&
222
+ a.every((point, index) => point.t === b[index].t && point.v === b[index].v));
223
+ const monotoneTangents = (xs, ys) => {
224
+ const n = xs.length;
225
+ const slopes = [];
226
+ for (let i = 0; i < n - 1; i++) {
227
+ const dx = xs[i + 1] - xs[i];
228
+ slopes.push(dx === 0 ? 0 : (ys[i + 1] - ys[i]) / dx);
229
+ }
230
+ const tangents = new Array(n).fill(0);
231
+ tangents[0] = slopes[0];
232
+ tangents[n - 1] = slopes[n - 2];
233
+ for (let i = 1; i < n - 1; i++) {
234
+ const a = slopes[i - 1];
235
+ const b = slopes[i];
236
+ tangents[i] = a * b <= 0 ? 0 : (a + b) / 2;
237
+ }
238
+ for (let i = 0; i < n - 1; i++) {
239
+ const slope = slopes[i];
240
+ if (slope === 0) {
241
+ tangents[i] = 0;
242
+ tangents[i + 1] = 0;
243
+ continue;
244
+ }
245
+ const a = tangents[i] / slope;
246
+ const b = tangents[i + 1] / slope;
247
+ const s = a * a + b * b;
248
+ if (s > 9) {
249
+ const t = 3 / Math.sqrt(s);
250
+ tangents[i] = t * a * slope;
251
+ tangents[i + 1] = t * b * slope;
252
+ }
253
+ }
254
+ return tangents;
255
+ };
256
+ const sampleMonotone = (points, sampleXs) => {
257
+ const xs = points.map((point) => point.x);
258
+ const ys = points.map((point) => point.y);
259
+ const tangents = monotoneTangents(xs, ys);
260
+ let segment = 0;
261
+ return sampleXs.map((x) => {
262
+ while (segment < xs.length - 2 && x > xs[segment + 1]) {
263
+ segment++;
264
+ }
265
+ const x0 = xs[segment];
266
+ const x1 = xs[segment + 1];
267
+ const dx = x1 - x0;
268
+ const f = dx === 0 ? 0 : (x - x0) / dx;
269
+ const f2 = f * f;
270
+ const f3 = f2 * f;
271
+ const y0 = ys[segment];
272
+ const y1 = ys[segment + 1];
273
+ const m0 = tangents[segment];
274
+ const m1 = tangents[segment + 1];
275
+ return (2 * f3 - 3 * f2 + 1) * y0 + (f3 - 2 * f2 + f) * m0 * dx + (-2 * f3 + 3 * f2) * y1 + (f3 - f2) * m1 * dx;
276
+ });
277
+ };
278
+ const polyline = (xs, ys) => xs.map((x, index) => `${index === 0 ? 'M' : 'L'}${x.toFixed(2)},${ys[index].toFixed(2)}`).join(' ');
279
+ const shapeTransition = (from, to) => {
280
+ const left = to[0].x;
281
+ const right = to.at(-1).x;
282
+ const xs = Array.from({ length: TRANSITION_SAMPLES }, (_, index) => left + ((right - left) * index) / (TRANSITION_SAMPLES - 1));
283
+ return { xs, from: sampleMonotone(from, xs), to: sampleMonotone(to, xs) };
284
+ };
285
+ const shapeAt = (transition, progress) => transition.from.map((fromY, index) => fromY + (transition.to[index] - fromY) * progress);
286
+ const animateShape = (from, to, duration, draw, done) => {
287
+ const transition = shapeTransition(from, to);
288
+ const start = performance.now();
289
+ let frame = 0;
290
+ const step = (now) => {
291
+ const t = Math.min(1, Math.max(0, (now - start) / duration));
292
+ if (t === 1) {
293
+ done();
294
+ return;
295
+ }
296
+ const ys = shapeAt(transition, easeOutQuint(t));
297
+ draw(transition.xs.map((x, index) => ({ x, y: ys[index] })), polyline(transition.xs, ys));
298
+ frame = requestAnimationFrame(step);
299
+ };
300
+ frame = requestAnimationFrame(step);
301
+ return () => cancelAnimationFrame(frame);
302
+ };
303
+ class ShapeMotion {
304
+ #view;
305
+ #points = null;
306
+ #rangeKey = null;
307
+ #target = null;
308
+ #held = null;
309
+ #drawn = null;
310
+ #stop = null;
311
+ constructor(view) {
312
+ this.#view = view;
313
+ }
314
+ follow(points, geometry, options) {
315
+ const newSeries = !sameSeries(points, this.#points);
316
+ const newRange = options.rangeKey === null || options.rangeKey !== this.#rangeKey;
317
+ this.#points = points;
318
+ if (newSeries) {
319
+ this.#rangeKey = options.rangeKey;
320
+ }
321
+ if (this.#stop && !newSeries && !options.reduced && geometry?.line === this.#target?.line) {
322
+ this.#target = geometry;
323
+ return;
324
+ }
325
+ const from = this.#drawn;
326
+ const held = this.#held ?? this.#target;
327
+ this.#target = geometry;
328
+ this.stop();
329
+ if (!newSeries ||
330
+ !newRange ||
331
+ !geometry ||
332
+ !from ||
333
+ from.length < 2 ||
334
+ geometry.points.length < 2 ||
335
+ options.reduced) {
336
+ this.#settle(geometry);
337
+ return;
338
+ }
339
+ const duration = options.duration();
340
+ if (duration <= 0) {
341
+ this.#settle(geometry);
342
+ return;
343
+ }
344
+ this.#hold(held);
345
+ this.#stop = animateShape(from, geometry.points, duration, (shape, line) => this.#draw(shape, line, shape.at(-1)), () => this.#settle(geometry));
346
+ }
347
+ stop() {
348
+ this.#stop?.();
349
+ this.#stop = null;
350
+ }
351
+ #hold(geometry) {
352
+ this.#held = geometry;
353
+ this.#view.hold(geometry);
354
+ }
355
+ #settle(geometry) {
356
+ this.#stop = null;
357
+ this.#hold(null);
358
+ this.#drawn = null;
359
+ if (geometry) {
360
+ this.#draw(geometry.points, geometry.line, geometry.end);
361
+ }
362
+ }
363
+ #draw(shape, line, end) {
364
+ this.#drawn = shape;
365
+ this.#view.draw(line, end);
366
+ }
367
+ }
368
+
369
+ const WIDE_QUERY = '(min-width: 64rem)';
370
+ const wideViewport = () => {
371
+ const query = typeof matchMedia === 'function' ? matchMedia(WIDE_QUERY) : null;
372
+ const wide = signal(query?.matches ?? false, /* @ts-ignore */
373
+ ...(ngDevMode ? [{ debugName: "wide" }] : /* istanbul ignore next */ []));
374
+ const onChange = (event) => wide.set(event.matches);
375
+ if (query) {
376
+ query.onchange = onChange;
377
+ inject(DestroyRef).onDestroy(() => (query.onchange = null));
378
+ }
379
+ return wide;
380
+ };
381
+
382
+ /**
383
+ * Value over time: a monotone curve with a dashed line at the starting value and a tooltip.
384
+ *
385
+ * Axis labels are never repeated: a tick whose `axisFormat` text equals the previous drawn label is
386
+ * not rendered. The first tick always is, so only the last edge label can drop. With `axisTicks="3"`
387
+ * only the first, middle and last ticks count; with `auto` every tick counts from `sm` up and the
388
+ * three core ticks below it. Tick positions are unchanged.
389
+ *
390
+ * A new series interpolates from the drawn curve, fading the start label and the dashed line. With
391
+ * `rangeKey` bound, only a series that comes with a new key does so: a new series under the same key
392
+ * (a reload after a buy) is drawn at once, with no fade. The key may change before its series arrives.
393
+ *
394
+ * @example
395
+ * <ui-line-chart [points]="points" label="Net worth over one month" startLabel="Since" [valueFormat]="formatEur" />
396
+ */
397
+ class UiLineChart {
398
+ points = input.required(/* @ts-ignore */
399
+ ...(ngDevMode ? [{ debugName: "points" }] : /* istanbul ignore next */ []));
400
+ label = input.required(/* @ts-ignore */
401
+ ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
402
+ valueFormat = input(identityValue, /* @ts-ignore */
403
+ ...(ngDevMode ? [{ debugName: "valueFormat" }] : /* istanbul ignore next */ []));
404
+ tooltipFormat = input(/* @ts-ignore */
405
+ ...(ngDevMode ? [undefined, { debugName: "tooltipFormat" }] : /* istanbul ignore next */ []));
406
+ deltaFormat = input(identityDelta, /* @ts-ignore */
407
+ ...(ngDevMode ? [{ debugName: "deltaFormat" }] : /* istanbul ignore next */ []));
408
+ timeFormat = input(identityTime, /* @ts-ignore */
409
+ ...(ngDevMode ? [{ debugName: "timeFormat" }] : /* istanbul ignore next */ []));
410
+ axisFormat = input(identityTime, /* @ts-ignore */
411
+ ...(ngDevMode ? [{ debugName: "axisFormat" }] : /* istanbul ignore next */ []));
412
+ startLabel = input('', /* @ts-ignore */
413
+ ...(ngDevMode ? [{ debugName: "startLabel" }] : /* istanbul ignore next */ []));
414
+ timeColumnLabel = input('Time', /* @ts-ignore */
415
+ ...(ngDevMode ? [{ debugName: "timeColumnLabel" }] : /* istanbul ignore next */ []));
416
+ valueColumnLabel = input('Value', /* @ts-ignore */
417
+ ...(ngDevMode ? [{ debugName: "valueColumnLabel" }] : /* istanbul ignore next */ []));
418
+ sparkline = input(false, { ...(ngDevMode ? { debugName: "sparkline" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
419
+ tooltip = input('compact', /* @ts-ignore */
420
+ ...(ngDevMode ? [{ debugName: "tooltip" }] : /* istanbul ignore next */ []));
421
+ tooltipDelta = input(true, { ...(ngDevMode ? { debugName: "tooltipDelta" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
422
+ axisGap = input(12, /* @ts-ignore */
423
+ ...(ngDevMode ? [{ debugName: "axisGap" }] : /* istanbul ignore next */ []));
424
+ axisTicks = input('auto', { ...(ngDevMode ? { debugName: "axisTicks" } : /* istanbul ignore next */ {}), transform: parseAxisTicks });
425
+ rangeKey = input(null, /* @ts-ignore */
426
+ ...(ngDevMode ? [{ debugName: "rangeKey" }] : /* istanbul ignore next */ []));
427
+ #destroyRef = inject(DestroyRef);
428
+ #host = inject(ElementRef);
429
+ #wide = wideViewport();
430
+ #reducedMotion = injectReducedMotion();
431
+ #motion = new ShapeMotion({
432
+ hold: (geometry) => {
433
+ this.held.set(geometry);
434
+ this.activeIndex.update((index) => (geometry ? null : index));
435
+ },
436
+ draw: (line, end) => {
437
+ this.lineRef()?.nativeElement.setAttribute('d', line);
438
+ this.endRef()?.nativeElement.setAttribute('cx', String(end.x));
439
+ this.endRef()?.nativeElement.setAttribute('cy', String(end.y));
440
+ },
441
+ });
442
+ svgRef = viewChild.required('svgRef', /* @ts-ignore */
443
+ ...(ngDevMode ? [{ debugName: "svgRef" }] : /* istanbul ignore next */ []));
444
+ lineRef = viewChild('lineRef', /* @ts-ignore */
445
+ ...(ngDevMode ? [{ debugName: "lineRef" }] : /* istanbul ignore next */ []));
446
+ endRef = viewChild('endRef', /* @ts-ignore */
447
+ ...(ngDevMode ? [{ debugName: "endRef" }] : /* istanbul ignore next */ []));
448
+ activeIndex = signal(null, /* @ts-ignore */
449
+ ...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
450
+ held = signal(null, /* @ts-ignore */
451
+ ...(ngDevMode ? [{ debugName: "held" }] : /* istanbul ignore next */ []));
452
+ measuredSize = signal(null, /* @ts-ignore */
453
+ ...(ngDevMode ? [{ debugName: "measuredSize" }] : /* istanbul ignore next */ []));
454
+ effectiveWidth = computed(() => this.measuredSize()?.width ?? DEFAULT_WIDTH, /* @ts-ignore */
455
+ ...(ngDevMode ? [{ debugName: "effectiveWidth" }] : /* istanbul ignore next */ []));
456
+ effectiveHeight = computed(() => this.measuredSize()?.height ?? DEFAULT_HEIGHT, /* @ts-ignore */
457
+ ...(ngDevMode ? [{ debugName: "effectiveHeight" }] : /* istanbul ignore next */ []));
458
+ plotHeight = computed(() => plotHeightFor(this.effectiveHeight(), this.sparkline(), this.axisGap(), this.#wide()), /* @ts-ignore */
459
+ ...(ngDevMode ? [{ debugName: "plotHeight" }] : /* istanbul ignore next */ []));
460
+ tooltipStyle = (point) => tooltipPlacement(point.x / this.effectiveWidth());
461
+ tooltipClasses = computed(() => tooltipClassesFor(this.tooltip(), this.#wide()), /* @ts-ignore */
462
+ ...(ngDevMode ? [{ debugName: "tooltipClasses" }] : /* istanbul ignore next */ []));
463
+ geometry = computed(() => {
464
+ const plotHeight = this.plotHeight();
465
+ const padding = plotPaddingFor(plotHeight, this.sparkline());
466
+ return buildGeometry(this.points(), this.effectiveWidth(), plotHeight, { x: 0, top: padding, bottom: padding });
467
+ }, /* @ts-ignore */
468
+ ...(ngDevMode ? [{ debugName: "geometry" }] : /* istanbul ignore next */ []));
469
+ shown = computed(() => this.held() ?? this.geometry(), /* @ts-ignore */
470
+ ...(ngDevMode ? [{ debugName: "shown" }] : /* istanbul ignore next */ []));
471
+ startFade = computed(() => this.held() ? 'opacity-0 duration-(--duration-exit)' : 'duration-(--duration-fast)', /* @ts-ignore */
472
+ ...(ngDevMode ? [{ debugName: "startFade" }] : /* istanbul ignore next */ []));
473
+ ticks = computed(() => axisTicks(this.geometry().points), /* @ts-ignore */
474
+ ...(ngDevMode ? [{ debugName: "ticks" }] : /* istanbul ignore next */ []));
475
+ tickTexts = computed(() => tickLabels(this.ticks(), this.axisFormat(), this.axisTicks()), /* @ts-ignore */
476
+ ...(ngDevMode ? [{ debugName: "tickTexts" }] : /* istanbul ignore next */ []));
477
+ active = computed(() => {
478
+ const geometry = this.geometry();
479
+ const index = this.activeIndex();
480
+ return geometry && index !== null && !this.held() ? geometry.points[index] : null;
481
+ }, /* @ts-ignore */
482
+ ...(ngDevMode ? [{ debugName: "active" }] : /* istanbul ignore next */ []));
483
+ startLabelInfo = computed(() => {
484
+ const text = this.startLabel();
485
+ const geometry = this.shown();
486
+ if (!text || !geometry) {
487
+ return null;
488
+ }
489
+ const label = `${text} ${this.valueFormat()(geometry.points[0].v)}`;
490
+ const size = { width: label.length * 0.56 * 12 + 8, height: START_LABEL_HEIGHT };
491
+ const placement = placeStartLabel(geometry.points, geometry.start.y, { width: this.effectiveWidth(), height: this.plotHeight() }, size);
492
+ return { label, placement: { ...placement, y: startLabelBaseline(placement.y) } };
493
+ }, /* @ts-ignore */
494
+ ...(ngDevMode ? [{ debugName: "startLabelInfo" }] : /* istanbul ignore next */ []));
495
+ #renderRef = afterNextRender(() => {
496
+ this.#destroyRef.onDestroy(() => this.#renderRef.destroy());
497
+ observeSize(this.svgRef().nativeElement, this.#destroyRef, (size) => this.measuredSize.set(size));
498
+ });
499
+ constructor() {
500
+ effect(() => {
501
+ const points = this.points();
502
+ const geometry = this.geometry();
503
+ const reduced = this.#reducedMotion();
504
+ untracked(() => this.#motion.follow(points, geometry, {
505
+ reduced,
506
+ rangeKey: this.rangeKey(),
507
+ duration: () => durationFrom(getComputedStyle(this.#host.nativeElement).getPropertyValue('--duration-base')),
508
+ }));
509
+ });
510
+ this.#destroyRef.onDestroy(() => this.#motion.stop());
511
+ }
512
+ deltaFor(point) {
513
+ return point.v - this.points()[0].v;
514
+ }
515
+ deltaClasses(point) {
516
+ return `${this.tooltipClasses().delta} ${deltaTone(this.deltaFor(point))}`;
517
+ }
518
+ onPointerMove(event) {
519
+ const geometry = this.geometry();
520
+ if (this.sparkline() || !geometry || geometry.points.length === 0) {
521
+ return;
522
+ }
523
+ const rect = this.svgRef().nativeElement.getBoundingClientRect();
524
+ const ratio = rect.width === 0 ? 1 : this.effectiveWidth() / rect.width;
525
+ const x = (event.clientX - rect.left) * ratio;
526
+ this.activeIndex.set(nearestPointIndex(geometry.points, x));
527
+ }
528
+ onPointerLeave() {
529
+ this.activeIndex.set(null);
530
+ }
531
+ onKeydown(event) {
532
+ const geometry = this.geometry();
533
+ if (this.sparkline() || !geometry || geometry.points.length === 0) {
534
+ return;
535
+ }
536
+ const navigation = navigateIndex(event.key, this.activeIndex(), geometry.points.length - 1);
537
+ if (!navigation) {
538
+ return;
539
+ }
540
+ if (navigation.preventDefault) {
541
+ event.preventDefault();
542
+ }
543
+ this.activeIndex.set(navigation.index);
544
+ }
545
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiLineChart, deps: [], target: i0.ɵɵFactoryTarget.Component });
546
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: UiLineChart, isStandalone: true, selector: "ui-line-chart", inputs: { points: { classPropertyName: "points", publicName: "points", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, valueFormat: { classPropertyName: "valueFormat", publicName: "valueFormat", isSignal: true, isRequired: false, transformFunction: null }, tooltipFormat: { classPropertyName: "tooltipFormat", publicName: "tooltipFormat", isSignal: true, isRequired: false, transformFunction: null }, deltaFormat: { classPropertyName: "deltaFormat", publicName: "deltaFormat", isSignal: true, isRequired: false, transformFunction: null }, timeFormat: { classPropertyName: "timeFormat", publicName: "timeFormat", isSignal: true, isRequired: false, transformFunction: null }, axisFormat: { classPropertyName: "axisFormat", publicName: "axisFormat", isSignal: true, isRequired: false, transformFunction: null }, startLabel: { classPropertyName: "startLabel", publicName: "startLabel", isSignal: true, isRequired: false, transformFunction: null }, timeColumnLabel: { classPropertyName: "timeColumnLabel", publicName: "timeColumnLabel", isSignal: true, isRequired: false, transformFunction: null }, valueColumnLabel: { classPropertyName: "valueColumnLabel", publicName: "valueColumnLabel", isSignal: true, isRequired: false, transformFunction: null }, sparkline: { classPropertyName: "sparkline", publicName: "sparkline", isSignal: true, isRequired: false, transformFunction: null }, tooltip: { classPropertyName: "tooltip", publicName: "tooltip", isSignal: true, isRequired: false, transformFunction: null }, tooltipDelta: { classPropertyName: "tooltipDelta", publicName: "tooltipDelta", isSignal: true, isRequired: false, transformFunction: null }, axisGap: { classPropertyName: "axisGap", publicName: "axisGap", isSignal: true, isRequired: false, transformFunction: null }, axisTicks: { classPropertyName: "axisTicks", publicName: "axisTicks", isSignal: true, isRequired: false, transformFunction: null }, rangeKey: { classPropertyName: "rangeKey", publicName: "rangeKey", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "block h-full" }, viewQueries: [{ propertyName: "svgRef", first: true, predicate: ["svgRef"], descendants: true, isSignal: true }, { propertyName: "lineRef", first: true, predicate: ["lineRef"], descendants: true, isSignal: true }, { propertyName: "endRef", first: true, predicate: ["endRef"], descendants: true, isSignal: true }], ngImport: i0, template: `
547
+ <div class="relative h-full">
548
+ <svg
549
+ #svgRef
550
+ class="block h-full w-full touch-pan-y overflow-visible"
551
+ role="img"
552
+ [attr.aria-label]="label()"
553
+ [attr.height]="effectiveHeight()"
554
+ [attr.tabindex]="sparkline() ? null : 0"
555
+ [attr.viewBox]="'0 0 ' + effectiveWidth() + ' ' + effectiveHeight()"
556
+ [attr.width]="effectiveWidth()"
557
+ (keydown)="onKeydown($event)"
558
+ (pointerleave)="onPointerLeave()"
559
+ (pointermove)="onPointerMove($event)"
560
+ >
561
+ @if (shown(); as shape) {
562
+ <path
563
+ #lineRef
564
+ class="stroke-(--foreground)"
565
+ data-chart-line
566
+ fill="none"
567
+ stroke-linecap="round"
568
+ stroke-linejoin="round"
569
+ [attr.d]="shape.line"
570
+ [attr.stroke-width]="sparkline() ? 1.5 : 2"
571
+ />
572
+
573
+ @if (!sparkline()) {
574
+ <line
575
+ class="stroke-(--border) transition-opacity ease-(--ease-out)"
576
+ data-chart-start-line
577
+ stroke-dasharray="3 4"
578
+ stroke-width="1"
579
+ x1="0"
580
+ [attr.x2]="effectiveWidth()"
581
+ [attr.y1]="shape.start.y"
582
+ [attr.y2]="shape.start.y"
583
+ [class]="startFade()"
584
+ />
585
+
586
+ @if (startLabelInfo(); as info) {
587
+ <text
588
+ class="text-caption fill-(--subtle-foreground) tabular-nums transition-opacity ease-(--ease-out)"
589
+ data-chart-start-label
590
+ [attr.text-anchor]="info.placement.anchor"
591
+ [attr.x]="info.placement.x"
592
+ [attr.y]="info.placement.y"
593
+ [class]="startFade()"
594
+ >
595
+ {{ info.label }}
596
+ </text>
597
+ }
598
+
599
+ <circle
600
+ #endRef
601
+ class="fill-(--foreground) stroke-(--card) [paint-order:stroke]"
602
+ data-chart-end
603
+ r="4"
604
+ stroke-width="6"
605
+ [attr.cx]="shape.end.x"
606
+ [attr.cy]="shape.end.y"
607
+ />
608
+
609
+ @for (tick of ticks(); track tick.t; let index = $index; let first = $first; let last = $last) {
610
+ @if (tickTexts()[index]; as label) {
611
+ <text
612
+ class="text-caption fill-(--subtle-foreground) tabular-nums"
613
+ data-chart-axis-tick
614
+ [attr.text-anchor]="first ? 'start' : last ? 'end' : 'middle'"
615
+ [attr.x]="tick.x"
616
+ [attr.y]="effectiveHeight() - 4"
617
+ [class]="label.classes"
618
+ >
619
+ {{ label.text }}
620
+ </text>
621
+ }
622
+ }
623
+
624
+ @if (active(); as point) {
625
+ <line
626
+ class="stroke-(--border)"
627
+ data-chart-crosshair
628
+ stroke-width="1"
629
+ y1="0"
630
+ [attr.x1]="point.x"
631
+ [attr.x2]="point.x"
632
+ [attr.y2]="plotHeight()"
633
+ />
634
+ <circle
635
+ class="fill-(--foreground) stroke-(--card) [paint-order:stroke]"
636
+ data-chart-active
637
+ r="5"
638
+ stroke-width="6"
639
+ [attr.cx]="point.x"
640
+ [attr.cy]="point.y"
641
+ />
642
+ }
643
+ }
644
+ }
645
+ </svg>
646
+
647
+ @if (active(); as point) {
648
+ <div
649
+ class="rounded-container pointer-events-none absolute bg-(--elevated) py-2 whitespace-nowrap [font-variant-numeric:var(--numeric)] shadow-[0_4px_16px_rgb(0_0_0/0.12),inset_0_0_0_1px_var(--border)]"
650
+ data-testid="chart-tooltip"
651
+ role="status"
652
+ [class]="tooltipClasses().box"
653
+ [style]="tooltipStyle(point)"
654
+ >
655
+ <p class="font-medium text-(--foreground)" [class]="tooltipClasses().value">
656
+ {{ tooltipFormat()?.(point) ?? valueFormat()(point.v) }}
657
+ </p>
658
+ @if (tooltipDelta()) {
659
+ <p data-chart-delta [class]="deltaClasses(point)">{{ deltaFormat()(deltaFor(point)) }}</p>
660
+ }
661
+ <p class="text-caption text-(--subtle-foreground)" data-chart-date>{{ timeFormat()(point.t) }}</p>
662
+ </div>
663
+ }
664
+ @if (points().length > 0) {
665
+ <div class="sr-only">
666
+ <table>
667
+ <caption [textContent]="label()"></caption>
668
+ <thead>
669
+ <tr>
670
+ <th scope="col">{{ timeColumnLabel() }}</th>
671
+ <th scope="col">{{ valueColumnLabel() }}</th>
672
+ </tr>
673
+ </thead>
674
+ <tbody>
675
+ @for (point of points(); track point.t) {
676
+ <tr>
677
+ <td>{{ timeFormat()(point.t) }}</td>
678
+ <td>{{ valueFormat()(point.v) }}</td>
679
+ </tr>
680
+ }
681
+ </tbody>
682
+ </table>
683
+ </div>
684
+ }
685
+ </div>
686
+ `, isInline: true });
687
+ }
688
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: UiLineChart, decorators: [{
689
+ type: Component,
690
+ args: [{
691
+ selector: 'ui-line-chart',
692
+ template: `
693
+ <div class="relative h-full">
694
+ <svg
695
+ #svgRef
696
+ class="block h-full w-full touch-pan-y overflow-visible"
697
+ role="img"
698
+ [attr.aria-label]="label()"
699
+ [attr.height]="effectiveHeight()"
700
+ [attr.tabindex]="sparkline() ? null : 0"
701
+ [attr.viewBox]="'0 0 ' + effectiveWidth() + ' ' + effectiveHeight()"
702
+ [attr.width]="effectiveWidth()"
703
+ (keydown)="onKeydown($event)"
704
+ (pointerleave)="onPointerLeave()"
705
+ (pointermove)="onPointerMove($event)"
706
+ >
707
+ @if (shown(); as shape) {
708
+ <path
709
+ #lineRef
710
+ class="stroke-(--foreground)"
711
+ data-chart-line
712
+ fill="none"
713
+ stroke-linecap="round"
714
+ stroke-linejoin="round"
715
+ [attr.d]="shape.line"
716
+ [attr.stroke-width]="sparkline() ? 1.5 : 2"
717
+ />
718
+
719
+ @if (!sparkline()) {
720
+ <line
721
+ class="stroke-(--border) transition-opacity ease-(--ease-out)"
722
+ data-chart-start-line
723
+ stroke-dasharray="3 4"
724
+ stroke-width="1"
725
+ x1="0"
726
+ [attr.x2]="effectiveWidth()"
727
+ [attr.y1]="shape.start.y"
728
+ [attr.y2]="shape.start.y"
729
+ [class]="startFade()"
730
+ />
731
+
732
+ @if (startLabelInfo(); as info) {
733
+ <text
734
+ class="text-caption fill-(--subtle-foreground) tabular-nums transition-opacity ease-(--ease-out)"
735
+ data-chart-start-label
736
+ [attr.text-anchor]="info.placement.anchor"
737
+ [attr.x]="info.placement.x"
738
+ [attr.y]="info.placement.y"
739
+ [class]="startFade()"
740
+ >
741
+ {{ info.label }}
742
+ </text>
743
+ }
744
+
745
+ <circle
746
+ #endRef
747
+ class="fill-(--foreground) stroke-(--card) [paint-order:stroke]"
748
+ data-chart-end
749
+ r="4"
750
+ stroke-width="6"
751
+ [attr.cx]="shape.end.x"
752
+ [attr.cy]="shape.end.y"
753
+ />
754
+
755
+ @for (tick of ticks(); track tick.t; let index = $index; let first = $first; let last = $last) {
756
+ @if (tickTexts()[index]; as label) {
757
+ <text
758
+ class="text-caption fill-(--subtle-foreground) tabular-nums"
759
+ data-chart-axis-tick
760
+ [attr.text-anchor]="first ? 'start' : last ? 'end' : 'middle'"
761
+ [attr.x]="tick.x"
762
+ [attr.y]="effectiveHeight() - 4"
763
+ [class]="label.classes"
764
+ >
765
+ {{ label.text }}
766
+ </text>
767
+ }
768
+ }
769
+
770
+ @if (active(); as point) {
771
+ <line
772
+ class="stroke-(--border)"
773
+ data-chart-crosshair
774
+ stroke-width="1"
775
+ y1="0"
776
+ [attr.x1]="point.x"
777
+ [attr.x2]="point.x"
778
+ [attr.y2]="plotHeight()"
779
+ />
780
+ <circle
781
+ class="fill-(--foreground) stroke-(--card) [paint-order:stroke]"
782
+ data-chart-active
783
+ r="5"
784
+ stroke-width="6"
785
+ [attr.cx]="point.x"
786
+ [attr.cy]="point.y"
787
+ />
788
+ }
789
+ }
790
+ }
791
+ </svg>
792
+
793
+ @if (active(); as point) {
794
+ <div
795
+ class="rounded-container pointer-events-none absolute bg-(--elevated) py-2 whitespace-nowrap [font-variant-numeric:var(--numeric)] shadow-[0_4px_16px_rgb(0_0_0/0.12),inset_0_0_0_1px_var(--border)]"
796
+ data-testid="chart-tooltip"
797
+ role="status"
798
+ [class]="tooltipClasses().box"
799
+ [style]="tooltipStyle(point)"
800
+ >
801
+ <p class="font-medium text-(--foreground)" [class]="tooltipClasses().value">
802
+ {{ tooltipFormat()?.(point) ?? valueFormat()(point.v) }}
803
+ </p>
804
+ @if (tooltipDelta()) {
805
+ <p data-chart-delta [class]="deltaClasses(point)">{{ deltaFormat()(deltaFor(point)) }}</p>
806
+ }
807
+ <p class="text-caption text-(--subtle-foreground)" data-chart-date>{{ timeFormat()(point.t) }}</p>
808
+ </div>
809
+ }
810
+ @if (points().length > 0) {
811
+ <div class="sr-only">
812
+ <table>
813
+ <caption [textContent]="label()"></caption>
814
+ <thead>
815
+ <tr>
816
+ <th scope="col">{{ timeColumnLabel() }}</th>
817
+ <th scope="col">{{ valueColumnLabel() }}</th>
818
+ </tr>
819
+ </thead>
820
+ <tbody>
821
+ @for (point of points(); track point.t) {
822
+ <tr>
823
+ <td>{{ timeFormat()(point.t) }}</td>
824
+ <td>{{ valueFormat()(point.v) }}</td>
825
+ </tr>
826
+ }
827
+ </tbody>
828
+ </table>
829
+ </div>
830
+ }
831
+ </div>
832
+ `,
833
+ host: {
834
+ class: 'block h-full',
835
+ },
836
+ }]
837
+ }], ctorParameters: () => [], propDecorators: { points: [{ type: i0.Input, args: [{ isSignal: true, alias: "points", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], valueFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueFormat", required: false }] }], tooltipFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "tooltipFormat", required: false }] }], deltaFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "deltaFormat", required: false }] }], timeFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "timeFormat", required: false }] }], axisFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "axisFormat", required: false }] }], startLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "startLabel", required: false }] }], timeColumnLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "timeColumnLabel", required: false }] }], valueColumnLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueColumnLabel", required: false }] }], sparkline: [{ type: i0.Input, args: [{ isSignal: true, alias: "sparkline", required: false }] }], tooltip: [{ type: i0.Input, args: [{ isSignal: true, alias: "tooltip", required: false }] }], tooltipDelta: [{ type: i0.Input, args: [{ isSignal: true, alias: "tooltipDelta", required: false }] }], axisGap: [{ type: i0.Input, args: [{ isSignal: true, alias: "axisGap", required: false }] }], axisTicks: [{ type: i0.Input, args: [{ isSignal: true, alias: "axisTicks", required: false }] }], rangeKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "rangeKey", required: false }] }], svgRef: [{ type: i0.ViewChild, args: ['svgRef', { isSignal: true }] }], lineRef: [{ type: i0.ViewChild, args: ['lineRef', { isSignal: true }] }], endRef: [{ type: i0.ViewChild, args: ['endRef', { isSignal: true }] }] } });
838
+
839
+ /**
840
+ * Generated bundle index. Do not edit.
841
+ */
842
+
843
+ export { UiLineChart };
844
+ //# sourceMappingURL=joanroucoux-cairn-ui-line-chart.mjs.map