@joewinke/jatui 0.40.0 → 0.41.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/CLAUDE.md +29 -0
  2. package/THIRD_PARTY_NOTICES.md +15 -0
  3. package/dist/components/AnimatedNumber.svelte +60 -1
  4. package/dist/components/AnimatedNumber.svelte.d.ts +7 -0
  5. package/dist/components/SegmentedControl.svelte +263 -0
  6. package/dist/components/SegmentedControl.svelte.d.ts +48 -0
  7. package/dist/components/settings/SettingsRow.svelte +177 -0
  8. package/dist/components/settings/SettingsRow.svelte.d.ts +32 -0
  9. package/dist/components/settings/SettingsSection.svelte +139 -0
  10. package/dist/components/settings/SettingsSection.svelte.d.ts +21 -0
  11. package/dist/components/stats/ActivityHeatmap.svelte +412 -0
  12. package/dist/components/stats/ActivityHeatmap.svelte.d.ts +72 -0
  13. package/dist/components/stats/BarList.svelte +262 -0
  14. package/dist/components/stats/BarList.svelte.d.ts +57 -0
  15. package/dist/components/stats/ChartCard.svelte +230 -0
  16. package/dist/components/stats/ChartCard.svelte.d.ts +63 -0
  17. package/dist/components/stats/StatCard.svelte +387 -0
  18. package/dist/components/stats/StatCard.svelte.d.ts +37 -0
  19. package/dist/components/stats/StatGroup.svelte +67 -0
  20. package/dist/components/stats/StatGroup.svelte.d.ts +14 -0
  21. package/dist/components/stats/heatmap.d.ts +76 -0
  22. package/dist/components/stats/heatmap.js +151 -0
  23. package/dist/components/tree/TreeView.svelte +680 -0
  24. package/dist/components/tree/TreeView.svelte.d.ts +123 -0
  25. package/dist/components/tree/flattenTree.d.ts +57 -0
  26. package/dist/components/tree/flattenTree.js +91 -0
  27. package/dist/components/tree/treeKeys.d.ts +46 -0
  28. package/dist/components/tree/treeKeys.js +80 -0
  29. package/dist/data/grid/cells.d.ts +40 -0
  30. package/dist/data/grid/cells.js +89 -0
  31. package/dist/data/grid/clipboard.d.ts +46 -0
  32. package/dist/data/grid/clipboard.js +131 -0
  33. package/dist/data/grid/edits.d.ts +148 -0
  34. package/dist/data/grid/edits.js +222 -0
  35. package/dist/data/grid/index.d.ts +12 -0
  36. package/dist/data/grid/index.js +12 -0
  37. package/dist/index.d.ts +22 -0
  38. package/dist/index.js +21 -0
  39. package/dist/showcase/Globe.svelte +1 -1
  40. package/package.json +5 -1
@@ -0,0 +1,262 @@
1
+ <!--
2
+ BarList: ranked horizontal bars, "top pages / top callers" style
3
+ (jatui-6kitr). Own design, built from the public idea only (no BoardUI Pro
4
+ source). Plain HTML: each row is a list item with its label and value as
5
+ text; the bar is a decorative token-tinted fill sized against the largest
6
+ value. Long lists fold behind "Show N more".
7
+ -->
8
+ <script lang="ts" module>
9
+ export interface BarListItem {
10
+ /** Stable key; defaults to the label. */
11
+ id?: string;
12
+ label: string;
13
+ value: number;
14
+ href?: string;
15
+ /** Secondary text after the label (domain, count of sub-items…). */
16
+ hint?: string;
17
+ tone?: 'primary' | 'secondary' | 'accent' | 'success' | 'info' | 'warning' | 'error' | 'neutral';
18
+ }
19
+ </script>
20
+
21
+ <script lang="ts" generics="I extends BarListItem = BarListItem">
22
+ import type { Snippet } from 'svelte';
23
+
24
+ interface Props {
25
+ items: readonly I[];
26
+ /** 'desc' (default) ranks by value; 'none' keeps your order. */
27
+ sort?: 'desc' | 'asc' | 'none';
28
+ /** Rows shown before "Show more". Default: all. */
29
+ limit?: number;
30
+ format?: Intl.NumberFormatOptions;
31
+ /** Override the value text, e.g. "1.2k (34%)". */
32
+ formatValue?: (item: I, total: number) => string;
33
+ /** Column captions above the list. */
34
+ labelHeader?: string;
35
+ valueHeader?: string;
36
+ tone?: BarListItem['tone'];
37
+ size?: 'sm' | 'md';
38
+ /** Makes rows buttons (unless they have an href). */
39
+ onSelect?: (item: I) => void;
40
+ /** Custom label content (icon, avatar…). */
41
+ label?: Snippet<[I]>;
42
+ ariaLabel?: string;
43
+ emptyText?: string;
44
+ locale?: string;
45
+ class?: string;
46
+ }
47
+
48
+ let {
49
+ items,
50
+ sort = 'desc',
51
+ limit,
52
+ format,
53
+ formatValue,
54
+ labelHeader,
55
+ valueHeader,
56
+ tone = 'primary',
57
+ size = 'md',
58
+ onSelect,
59
+ label,
60
+ ariaLabel,
61
+ emptyText = 'No data',
62
+ locale,
63
+ class: className = ''
64
+ }: Props = $props();
65
+
66
+ let expanded = $state(false);
67
+ const uid = $props.id();
68
+
69
+ const ranked = $derived.by(() => {
70
+ const list = items.slice();
71
+ if (sort === 'desc') list.sort((a, b) => b.value - a.value);
72
+ else if (sort === 'asc') list.sort((a, b) => a.value - b.value);
73
+ return list;
74
+ });
75
+ const max = $derived(ranked.reduce((m, it) => Math.max(m, it.value), 0));
76
+ const total = $derived(ranked.reduce((s, it) => s + (Number.isFinite(it.value) ? it.value : 0), 0));
77
+ const visible = $derived(limit != null && !expanded ? ranked.slice(0, limit) : ranked);
78
+ const hidden = $derived(ranked.length - visible.length);
79
+ const nf = $derived(new Intl.NumberFormat(locale, format));
80
+ const valueText = (it: I) => (formatValue ? formatValue(it, total) : nf.format(it.value));
81
+ const pct = (v: number) => (max > 0 ? Math.max(0, Math.min(100, (v / max) * 100)) : 0);
82
+ </script>
83
+
84
+ {#snippet rowContent(it: I)}
85
+ <span class="bl-bar tone-{it.tone ?? tone}" style:--pct="{pct(it.value)}%" aria-hidden="true"></span>
86
+ <span class="bl-label">
87
+ {#if label}{@render label(it)}{:else}<span class="bl-text">{it.label}</span>{/if}
88
+ {#if it.hint}<span class="bl-hint">{it.hint}</span>{/if}
89
+ </span>
90
+ <span class="bl-value">{valueText(it)}</span>
91
+ {/snippet}
92
+
93
+ <div class="jatui-bar-list bl-{size} {className}">
94
+ {#if labelHeader || valueHeader}
95
+ <div class="bl-headers" aria-hidden="true">
96
+ <span>{labelHeader ?? ''}</span>
97
+ <span>{valueHeader ?? ''}</span>
98
+ </div>
99
+ {/if}
100
+ {#if ranked.length === 0}
101
+ <p class="bl-empty">{emptyText}</p>
102
+ {:else}
103
+ <ul class="bl-list" aria-label={ariaLabel} id="{uid}-list">
104
+ {#each visible as it, i (it.id ?? it.label + '\u0000' + i)}
105
+ <li>
106
+ {#if it.href}
107
+ <a class="bl-row is-interactive" href={it.href}>{@render rowContent(it)}</a>
108
+ {:else if onSelect}
109
+ <button type="button" class="bl-row is-interactive" onclick={() => onSelect(it)}>
110
+ {@render rowContent(it)}
111
+ </button>
112
+ {:else}
113
+ <div class="bl-row">{@render rowContent(it)}</div>
114
+ {/if}
115
+ </li>
116
+ {/each}
117
+ </ul>
118
+ {#if limit != null && ranked.length > limit}
119
+ <button
120
+ type="button"
121
+ class="bl-more"
122
+ aria-expanded={expanded}
123
+ aria-controls="{uid}-list"
124
+ onclick={() => (expanded = !expanded)}
125
+ >
126
+ {expanded ? 'Show less' : `Show ${hidden} more`}
127
+ </button>
128
+ {/if}
129
+ {/if}
130
+ </div>
131
+
132
+ <style>
133
+ .jatui-bar-list {
134
+ --bl-row-h: 2rem;
135
+ display: flex;
136
+ flex-direction: column;
137
+ gap: var(--jatui-space-sm, 4px);
138
+ min-width: 0;
139
+ font-size: 0.875rem;
140
+ color: var(--color-base-content);
141
+ }
142
+ .bl-sm {
143
+ --bl-row-h: 1.625rem;
144
+ font-size: 0.8125rem;
145
+ }
146
+ .bl-headers {
147
+ display: flex;
148
+ justify-content: space-between;
149
+ padding: 0 var(--jatui-space-base, 8px);
150
+ font-size: 0.75rem;
151
+ font-weight: 500;
152
+ color: color-mix(in oklch, var(--color-base-content) 55%, transparent);
153
+ }
154
+ .bl-list {
155
+ list-style: none;
156
+ margin: 0;
157
+ padding: 0;
158
+ display: flex;
159
+ flex-direction: column;
160
+ gap: var(--jatui-space-xs, 2px);
161
+ }
162
+ .bl-row {
163
+ position: relative;
164
+ display: flex;
165
+ align-items: center;
166
+ gap: var(--jatui-space-lg, 12px);
167
+ width: 100%;
168
+ min-height: var(--bl-row-h);
169
+ padding: 0 var(--jatui-space-base, 8px);
170
+ border: 0;
171
+ border-radius: var(--jatui-radius-field, 0.25rem);
172
+ background: transparent;
173
+ color: inherit;
174
+ font: inherit;
175
+ text-align: start;
176
+ text-decoration: none;
177
+ isolation: isolate;
178
+ }
179
+ .bl-row.is-interactive {
180
+ cursor: pointer;
181
+ }
182
+ .bl-row.is-interactive:hover .bl-bar {
183
+ background: color-mix(in oklch, var(--bl-tone) 26%, transparent);
184
+ }
185
+ .bl-row.is-interactive:focus-visible {
186
+ outline: 2px solid var(--color-primary);
187
+ outline-offset: 1px;
188
+ }
189
+ .bl-bar {
190
+ --bl-tone: var(--color-primary);
191
+ position: absolute;
192
+ inset-block: 0;
193
+ inset-inline-start: 0;
194
+ z-index: -1;
195
+ width: var(--pct);
196
+ min-width: 2px;
197
+ border-radius: var(--jatui-radius-field, 0.25rem);
198
+ background: color-mix(in oklch, var(--bl-tone) 16%, transparent);
199
+ transition:
200
+ width var(--motion-move, 250ms) var(--motion-glide, cubic-bezier(0.22, 1, 0.36, 1)),
201
+ background-color var(--motion-fast, 150ms) var(--motion-standard, ease);
202
+ }
203
+ .bl-bar.tone-secondary { --bl-tone: var(--color-secondary); }
204
+ .bl-bar.tone-accent { --bl-tone: var(--color-accent); }
205
+ .bl-bar.tone-success { --bl-tone: var(--color-success); }
206
+ .bl-bar.tone-info { --bl-tone: var(--color-info); }
207
+ .bl-bar.tone-warning { --bl-tone: var(--color-warning); }
208
+ .bl-bar.tone-error { --bl-tone: var(--color-error); }
209
+ .bl-bar.tone-neutral { --bl-tone: var(--color-base-content); }
210
+ .bl-label {
211
+ display: inline-flex;
212
+ align-items: center;
213
+ gap: var(--jatui-space-md, 6px);
214
+ flex: 1 1 auto;
215
+ min-width: 0;
216
+ }
217
+ .bl-text {
218
+ overflow: hidden;
219
+ text-overflow: ellipsis;
220
+ white-space: nowrap;
221
+ }
222
+ .bl-hint {
223
+ flex: none;
224
+ font-size: 0.75rem;
225
+ color: color-mix(in oklch, var(--color-base-content) 50%, transparent);
226
+ }
227
+ .bl-value {
228
+ flex: none;
229
+ font-variant-numeric: tabular-nums;
230
+ font-weight: 500;
231
+ }
232
+ .bl-more {
233
+ align-self: flex-start;
234
+ margin-top: var(--jatui-space-xs, 2px);
235
+ padding: var(--jatui-space-xs, 2px) var(--jatui-space-base, 8px);
236
+ border: 0;
237
+ border-radius: var(--jatui-radius-field, 0.25rem);
238
+ background: transparent;
239
+ color: var(--color-primary);
240
+ font: inherit;
241
+ font-size: 0.8125rem;
242
+ cursor: pointer;
243
+ }
244
+ .bl-more:hover {
245
+ background: color-mix(in oklch, var(--color-primary) 10%, transparent);
246
+ }
247
+ .bl-more:focus-visible {
248
+ outline: 2px solid var(--color-primary);
249
+ }
250
+ .bl-empty {
251
+ margin: 0;
252
+ padding: var(--jatui-space-lg, 12px);
253
+ text-align: center;
254
+ color: color-mix(in oklch, var(--color-base-content) 55%, transparent);
255
+ }
256
+
257
+ @media (prefers-reduced-motion: reduce) {
258
+ .bl-bar {
259
+ transition: none;
260
+ }
261
+ }
262
+ </style>
@@ -0,0 +1,57 @@
1
+ export interface BarListItem {
2
+ /** Stable key; defaults to the label. */
3
+ id?: string;
4
+ label: string;
5
+ value: number;
6
+ href?: string;
7
+ /** Secondary text after the label (domain, count of sub-items…). */
8
+ hint?: string;
9
+ tone?: 'primary' | 'secondary' | 'accent' | 'success' | 'info' | 'warning' | 'error' | 'neutral';
10
+ }
11
+ import type { Snippet } from 'svelte';
12
+ declare function $$render<I extends BarListItem = BarListItem>(): {
13
+ props: {
14
+ items: readonly I[];
15
+ /** 'desc' (default) ranks by value; 'none' keeps your order. */
16
+ sort?: "desc" | "asc" | "none";
17
+ /** Rows shown before "Show more". Default: all. */
18
+ limit?: number;
19
+ format?: Intl.NumberFormatOptions;
20
+ /** Override the value text, e.g. "1.2k (34%)". */
21
+ formatValue?: (item: I, total: number) => string;
22
+ /** Column captions above the list. */
23
+ labelHeader?: string;
24
+ valueHeader?: string;
25
+ tone?: BarListItem["tone"];
26
+ size?: "sm" | "md";
27
+ /** Makes rows buttons (unless they have an href). */
28
+ onSelect?: (item: I) => void;
29
+ /** Custom label content (icon, avatar…). */
30
+ label?: Snippet<[I]>;
31
+ ariaLabel?: string;
32
+ emptyText?: string;
33
+ locale?: string;
34
+ class?: string;
35
+ };
36
+ exports: {};
37
+ bindings: "";
38
+ slots: {};
39
+ events: {};
40
+ };
41
+ declare class __sveltets_Render<I extends BarListItem = BarListItem> {
42
+ props(): ReturnType<typeof $$render<I>>['props'];
43
+ events(): ReturnType<typeof $$render<I>>['events'];
44
+ slots(): ReturnType<typeof $$render<I>>['slots'];
45
+ bindings(): "";
46
+ exports(): {};
47
+ }
48
+ interface $$IsomorphicComponent {
49
+ new <I extends BarListItem = BarListItem>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<I>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<I>['props']>, ReturnType<__sveltets_Render<I>['events']>, ReturnType<__sveltets_Render<I>['slots']>> & {
50
+ $$bindings?: ReturnType<__sveltets_Render<I>['bindings']>;
51
+ } & ReturnType<__sveltets_Render<I>['exports']>;
52
+ <I extends BarListItem = BarListItem>(internal: unknown, props: ReturnType<__sveltets_Render<I>['props']> & {}): ReturnType<__sveltets_Render<I>['exports']>;
53
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
54
+ }
55
+ declare const BarList: $$IsomorphicComponent;
56
+ type BarList<I extends BarListItem = BarListItem> = InstanceType<typeof BarList<I>>;
57
+ export default BarList;
@@ -0,0 +1,230 @@
1
+ <!--
2
+ ChartCard: the dashboard "headline + period switch + chart" card
3
+ (jatui-6kitr). A layout contract, not a chart: it draws NO chart and pulls
4
+ in no chart library. Your chart (layerchart, d3, Sparkline, BurndownChart,
5
+ plain SVG) goes in the `chart` snippet and calls `hover(point)` as the
6
+ pointer moves; the headline then counts to the hovered value and the
7
+ caption shows the point's label. `hover(null)` returns to the total.
8
+ -->
9
+ <script lang="ts" module>
10
+ export interface ChartCardPoint {
11
+ value: number;
12
+ /** Shown in place of the caption while hovering, e.g. "Tue, Nov 3". */
13
+ label?: string;
14
+ /** Optional delta for the hovered point. */
15
+ delta?: number | string | null;
16
+ }
17
+
18
+ export interface ChartCardContext<P extends string = string> {
19
+ period: P | undefined;
20
+ hover: (point: ChartCardPoint | null) => void;
21
+ /** The point being hovered, or null. */
22
+ hovered: ChartCardPoint | null;
23
+ }
24
+ </script>
25
+
26
+ <script lang="ts" generics="P extends string = string">
27
+ import type { Snippet } from 'svelte';
28
+ import AnimatedNumber from '../AnimatedNumber.svelte';
29
+ import SegmentedControl, { type SegmentedOption } from '../SegmentedControl.svelte';
30
+
31
+ interface Props {
32
+ label: string;
33
+ /** Headline value when nothing is hovered. */
34
+ value: number | null | undefined;
35
+ format?: Intl.NumberFormatOptions;
36
+ /** A number is a fraction (0.08 → "+8%"). */
37
+ delta?: number | string | null;
38
+ deltaFormat?: Intl.NumberFormatOptions;
39
+ invertDelta?: boolean;
40
+ caption?: string;
41
+ /** Period switch options; omit for none. */
42
+ periods?: readonly (SegmentedOption<P> | P)[];
43
+ period?: P;
44
+ onPeriodChange?: (period: P) => void;
45
+ /** Chart area height (CSS). Default 10rem. */
46
+ chartHeight?: string;
47
+ loading?: boolean;
48
+ chart: Snippet<[ChartCardContext<P>]>;
49
+ /** Extra header controls, right of the period switch. */
50
+ actions?: Snippet;
51
+ /** Content under the chart (legend, footnote). */
52
+ footer?: Snippet;
53
+ locale?: string;
54
+ class?: string;
55
+ }
56
+
57
+ let {
58
+ label,
59
+ value,
60
+ format,
61
+ delta = null,
62
+ deltaFormat = { style: 'percent', maximumFractionDigits: 1, signDisplay: 'exceptZero' },
63
+ invertDelta = false,
64
+ caption,
65
+ periods,
66
+ period = $bindable(),
67
+ onPeriodChange,
68
+ chartHeight = '10rem',
69
+ loading = false,
70
+ chart,
71
+ actions,
72
+ footer,
73
+ locale,
74
+ class: className = ''
75
+ }: Props = $props();
76
+
77
+ const uid = $props.id();
78
+ let hovered = $state<ChartCardPoint | null>(null);
79
+
80
+ function hover(point: ChartCardPoint | null) {
81
+ hovered = point;
82
+ }
83
+
84
+ const shownValue = $derived(hovered ? hovered.value : value);
85
+ const shownDelta = $derived(hovered && hovered.delta !== undefined ? hovered.delta : hovered ? null : delta);
86
+ const deltaText = $derived(
87
+ shownDelta == null
88
+ ? ''
89
+ : typeof shownDelta === 'number'
90
+ ? new Intl.NumberFormat(locale, deltaFormat).format(shownDelta)
91
+ : shownDelta
92
+ );
93
+ const deltaSign = $derived.by(() => {
94
+ if (shownDelta == null) return 0;
95
+ const n = typeof shownDelta === 'number' ? shownDelta : parseFloat(shownDelta.replace(/[^\d.+-]/g, ''));
96
+ return Number.isFinite(n) ? Math.sign(n) : 0;
97
+ });
98
+ const deltaTone = $derived(deltaSign === 0 ? 'neutral' : (deltaSign > 0) !== invertDelta ? 'success' : 'error');
99
+ const ctx = $derived<ChartCardContext<P>>({ period, hover, hovered });
100
+ </script>
101
+
102
+ <section class="jatui-chart-card {className}" aria-labelledby="{uid}-label" aria-busy={loading || undefined}>
103
+ <header class="cc-head">
104
+ <div class="cc-headline">
105
+ <h3 class="cc-label" id="{uid}-label">{label}</h3>
106
+ <div class="cc-value-row">
107
+ <span class="cc-value">
108
+ {#if loading}
109
+ <span class="cc-skeleton" aria-hidden="true"></span><span class="sr-only">Loading</span>
110
+ {:else if shownValue == null}
111
+ —
112
+ {:else}
113
+ <AnimatedNumber value={shownValue} format={format ?? {}} mode="tween" />
114
+ {/if}
115
+ </span>
116
+ {#if deltaText && !loading}
117
+ <span class="cc-delta tone-{deltaTone}">{deltaText}</span>
118
+ {/if}
119
+ </div>
120
+ {#if hovered?.label || caption}
121
+ <p class="cc-caption">{hovered?.label ?? caption}</p>
122
+ {/if}
123
+ </div>
124
+ {#if periods?.length || actions}
125
+ <div class="cc-controls">
126
+ {#if periods?.length}
127
+ <SegmentedControl
128
+ options={periods}
129
+ bind:value={period}
130
+ size="xs"
131
+ ariaLabel="{label} period"
132
+ onChange={(p) => onPeriodChange?.(p)}
133
+ />
134
+ {/if}
135
+ {#if actions}{@render actions()}{/if}
136
+ </div>
137
+ {/if}
138
+ </header>
139
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
140
+ <div class="cc-chart" style:height={chartHeight} onpointerleave={() => (hovered = null)}>
141
+ {@render chart(ctx)}
142
+ </div>
143
+ {#if footer}
144
+ <div class="cc-footer">{@render footer()}</div>
145
+ {/if}
146
+ </section>
147
+
148
+ <style>
149
+ .jatui-chart-card {
150
+ display: flex;
151
+ flex-direction: column;
152
+ gap: var(--jatui-space-lg, 12px);
153
+ min-width: 0;
154
+ padding: var(--jatui-space-xl, 16px);
155
+ border: var(--jatui-border-w, 1px) solid color-mix(in oklch, var(--color-base-content) 10%, transparent);
156
+ border-radius: var(--jatui-radius-box, 0.5rem);
157
+ background: var(--color-base-100);
158
+ color: var(--color-base-content);
159
+ }
160
+ .cc-head {
161
+ display: flex;
162
+ flex-wrap: wrap;
163
+ align-items: flex-start;
164
+ justify-content: space-between;
165
+ gap: var(--jatui-space-lg, 12px);
166
+ }
167
+ .cc-headline {
168
+ display: flex;
169
+ flex-direction: column;
170
+ gap: var(--jatui-space-sm, 4px);
171
+ min-width: 0;
172
+ }
173
+ .cc-label {
174
+ margin: 0;
175
+ font-size: 0.8125rem;
176
+ font-weight: 500;
177
+ color: color-mix(in oklch, var(--color-base-content) 65%, transparent);
178
+ }
179
+ .cc-value-row {
180
+ display: flex;
181
+ align-items: center;
182
+ gap: var(--jatui-space-base, 8px);
183
+ }
184
+ .cc-value {
185
+ font-size: 1.75rem;
186
+ line-height: 1.15;
187
+ font-weight: 600;
188
+ letter-spacing: -0.01em;
189
+ font-variant-numeric: tabular-nums;
190
+ }
191
+ .cc-skeleton {
192
+ display: inline-block;
193
+ width: 7rem;
194
+ height: 1.75rem;
195
+ border-radius: var(--jatui-radius-field, 0.25rem);
196
+ background: color-mix(in oklch, var(--color-base-content) 10%, transparent);
197
+ }
198
+ .cc-delta {
199
+ --cc-d: var(--color-base-content);
200
+ padding: 1px var(--jatui-space-md, 6px);
201
+ border-radius: var(--jatui-radius-selector, 0.375rem);
202
+ background: color-mix(in oklch, var(--cc-d) 12%, transparent);
203
+ color: color-mix(in oklch, var(--cc-d) 85%, var(--color-base-content));
204
+ font-size: 0.75rem;
205
+ font-weight: 600;
206
+ font-variant-numeric: tabular-nums;
207
+ }
208
+ .cc-delta.tone-success { --cc-d: var(--color-success); }
209
+ .cc-delta.tone-error { --cc-d: var(--color-error); }
210
+ .cc-delta.tone-neutral { color: color-mix(in oklch, var(--color-base-content) 65%, transparent); }
211
+ .cc-caption {
212
+ margin: 0;
213
+ min-height: 1.2em;
214
+ font-size: 0.75rem;
215
+ color: color-mix(in oklch, var(--color-base-content) 55%, transparent);
216
+ }
217
+ .cc-controls {
218
+ display: flex;
219
+ align-items: center;
220
+ gap: var(--jatui-space-base, 8px);
221
+ }
222
+ .cc-chart {
223
+ position: relative;
224
+ min-width: 0;
225
+ }
226
+ .cc-footer {
227
+ font-size: 0.75rem;
228
+ color: color-mix(in oklch, var(--color-base-content) 60%, transparent);
229
+ }
230
+ </style>
@@ -0,0 +1,63 @@
1
+ export interface ChartCardPoint {
2
+ value: number;
3
+ /** Shown in place of the caption while hovering, e.g. "Tue, Nov 3". */
4
+ label?: string;
5
+ /** Optional delta for the hovered point. */
6
+ delta?: number | string | null;
7
+ }
8
+ export interface ChartCardContext<P extends string = string> {
9
+ period: P | undefined;
10
+ hover: (point: ChartCardPoint | null) => void;
11
+ /** The point being hovered, or null. */
12
+ hovered: ChartCardPoint | null;
13
+ }
14
+ import type { Snippet } from 'svelte';
15
+ import { type SegmentedOption } from '../SegmentedControl.svelte';
16
+ declare function $$render<P extends string = string>(): {
17
+ props: {
18
+ label: string;
19
+ /** Headline value when nothing is hovered. */
20
+ value: number | null | undefined;
21
+ format?: Intl.NumberFormatOptions;
22
+ /** A number is a fraction (0.08 → "+8%"). */
23
+ delta?: number | string | null;
24
+ deltaFormat?: Intl.NumberFormatOptions;
25
+ invertDelta?: boolean;
26
+ caption?: string;
27
+ /** Period switch options; omit for none. */
28
+ periods?: readonly (SegmentedOption<P> | P)[];
29
+ period?: P;
30
+ onPeriodChange?: (period: P) => void;
31
+ /** Chart area height (CSS). Default 10rem. */
32
+ chartHeight?: string;
33
+ loading?: boolean;
34
+ chart: Snippet<[ChartCardContext<P>]>;
35
+ /** Extra header controls, right of the period switch. */
36
+ actions?: Snippet;
37
+ /** Content under the chart (legend, footnote). */
38
+ footer?: Snippet;
39
+ locale?: string;
40
+ class?: string;
41
+ };
42
+ exports: {};
43
+ bindings: "period";
44
+ slots: {};
45
+ events: {};
46
+ };
47
+ declare class __sveltets_Render<P extends string = string> {
48
+ props(): ReturnType<typeof $$render<P>>['props'];
49
+ events(): ReturnType<typeof $$render<P>>['events'];
50
+ slots(): ReturnType<typeof $$render<P>>['slots'];
51
+ bindings(): "period";
52
+ exports(): {};
53
+ }
54
+ interface $$IsomorphicComponent {
55
+ new <P extends string = string>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<P>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<P>['props']>, ReturnType<__sveltets_Render<P>['events']>, ReturnType<__sveltets_Render<P>['slots']>> & {
56
+ $$bindings?: ReturnType<__sveltets_Render<P>['bindings']>;
57
+ } & ReturnType<__sveltets_Render<P>['exports']>;
58
+ <P extends string = string>(internal: unknown, props: ReturnType<__sveltets_Render<P>['props']> & {}): ReturnType<__sveltets_Render<P>['exports']>;
59
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
60
+ }
61
+ declare const ChartCard: $$IsomorphicComponent;
62
+ type ChartCard<P extends string = string> = InstanceType<typeof ChartCard<P>>;
63
+ export default ChartCard;