@joewinke/jatui 0.40.0 → 0.41.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 (39) hide show
  1. package/CLAUDE.md +29 -0
  2. package/THIRD_PARTY_NOTICES.md +15 -0
  3. package/dist/components/AnimatedNumber.svelte +42 -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/package.json +5 -1
@@ -0,0 +1,32 @@
1
+ export interface SettingsRowContext {
2
+ /** Id to put on the control (the label points at it). */
3
+ id: string;
4
+ labelId: string;
5
+ /** Undefined when there is no description. */
6
+ descriptionId: string | undefined;
7
+ }
8
+ import type { Snippet } from 'svelte';
9
+ interface Props {
10
+ label: string;
11
+ description?: string;
12
+ /** Id of the control the label is for. Default: generated, passed as `ctx.id`. */
13
+ for?: string;
14
+ /** 'inline' (default) puts the control beside the text; 'stacked' always below. */
15
+ layout?: 'inline' | 'stacked';
16
+ /** Unsaved-change dot. */
17
+ dirty?: boolean;
18
+ disabled?: boolean;
19
+ /** Validation / warning text under the description. */
20
+ error?: string;
21
+ /** Anchor id for deep links. */
22
+ id?: string;
23
+ control?: Snippet<[SettingsRowContext]>;
24
+ /** Extra content under the row (an expanded sub-form). */
25
+ children?: Snippet;
26
+ /** Custom label content (badge, icon). It becomes the accessible name, so include the text. */
27
+ labelContent?: Snippet;
28
+ class?: string;
29
+ }
30
+ declare const SettingsRow: import("svelte").Component<Props, {}, "">;
31
+ type SettingsRow = ReturnType<typeof SettingsRow>;
32
+ export default SettingsRow;
@@ -0,0 +1,139 @@
1
+ <!--
2
+ SettingsSection: a titled group of SettingsRows (jatui-6kitr). Rows inside
3
+ get hairline dividers. Give it an `id` + use it with SectionNav for long
4
+ settings pages; `dirty` shows the same amber dot SectionNav uses.
5
+ Row recipe after BoardUI's settings rows (MIT, © 2026 Mertcan Dundar
6
+ Esmergul); the modal shell was intentionally not taken.
7
+ -->
8
+ <script lang="ts">
9
+ import type { Snippet } from 'svelte';
10
+
11
+ interface Props {
12
+ title?: string;
13
+ description?: string;
14
+ /** Anchor id (SectionNav target). */
15
+ id?: string;
16
+ /** 'card' (default) frames the rows; 'plain' is flush. */
17
+ variant?: 'card' | 'plain';
18
+ headingLevel?: 2 | 3 | 4;
19
+ /** Unsaved-changes dot beside the title. */
20
+ dirty?: boolean;
21
+ /** Header controls (Reset, Save…). */
22
+ actions?: Snippet;
23
+ /** Footer under the rows (save bar, help link). */
24
+ footer?: Snippet;
25
+ children: Snippet;
26
+ class?: string;
27
+ }
28
+
29
+ let {
30
+ title,
31
+ description,
32
+ id,
33
+ variant = 'card',
34
+ headingLevel = 2,
35
+ dirty = false,
36
+ actions,
37
+ footer,
38
+ children,
39
+ class: className = ''
40
+ }: Props = $props();
41
+
42
+ const uid = $props.id();
43
+ </script>
44
+
45
+ <section
46
+ {id}
47
+ class="jatui-settings-section ss-{variant} {className}"
48
+ aria-labelledby={title ? `${uid}-title` : undefined}
49
+ aria-describedby={description ? `${uid}-desc` : undefined}
50
+ >
51
+ {#if title || description || actions}
52
+ <header class="ss-head">
53
+ <div class="ss-titles">
54
+ {#if title}
55
+ <svelte:element this={`h${headingLevel}`} class="ss-title" id="{uid}-title">
56
+ {title}
57
+ {#if dirty}
58
+ <span class="ss-dirty" aria-hidden="true"></span><span class="sr-only">(unsaved changes)</span>
59
+ {/if}
60
+ </svelte:element>
61
+ {/if}
62
+ {#if description}<p class="ss-desc" id="{uid}-desc">{description}</p>{/if}
63
+ </div>
64
+ {#if actions}<div class="ss-actions">{@render actions()}</div>{/if}
65
+ </header>
66
+ {/if}
67
+ <div class="ss-rows">
68
+ {@render children()}
69
+ </div>
70
+ {#if footer}<div class="ss-footer">{@render footer()}</div>{/if}
71
+ </section>
72
+
73
+ <style>
74
+ .jatui-settings-section {
75
+ display: flex;
76
+ flex-direction: column;
77
+ gap: var(--jatui-space-base, 8px);
78
+ min-width: 0;
79
+ scroll-margin-top: var(--jatui-space-xl, 16px);
80
+ }
81
+ .ss-head {
82
+ display: flex;
83
+ align-items: flex-end;
84
+ justify-content: space-between;
85
+ gap: var(--jatui-space-lg, 12px);
86
+ padding: 0 var(--jatui-space-xs, 2px);
87
+ }
88
+ .ss-titles {
89
+ min-width: 0;
90
+ }
91
+ .ss-title {
92
+ display: inline-flex;
93
+ align-items: center;
94
+ gap: var(--jatui-space-md, 6px);
95
+ margin: 0;
96
+ font-size: 0.9375rem;
97
+ font-weight: 600;
98
+ color: var(--color-base-content);
99
+ }
100
+ .ss-dirty {
101
+ width: 0.5rem;
102
+ height: 0.5rem;
103
+ border-radius: 9999px;
104
+ background: var(--color-warning);
105
+ }
106
+ .ss-desc {
107
+ margin: 2px 0 0;
108
+ font-size: 0.8125rem;
109
+ color: color-mix(in oklch, var(--color-base-content) 60%, transparent);
110
+ }
111
+ .ss-actions {
112
+ display: flex;
113
+ align-items: center;
114
+ gap: var(--jatui-space-base, 8px);
115
+ flex: none;
116
+ }
117
+ .ss-rows {
118
+ display: flex;
119
+ flex-direction: column;
120
+ container-type: inline-size;
121
+ }
122
+ .ss-card .ss-rows {
123
+ border: var(--jatui-border-w, 1px) solid color-mix(in oklch, var(--color-base-content) 10%, transparent);
124
+ border-radius: var(--jatui-radius-box, 0.5rem);
125
+ background: var(--color-base-100);
126
+ }
127
+ /* Hairline between rows. */
128
+ .ss-rows > :global(.jatui-settings-row + .jatui-settings-row) {
129
+ border-top: var(--jatui-border-w, 1px) solid color-mix(in oklch, var(--color-base-content) 8%, transparent);
130
+ }
131
+ .ss-card .ss-rows > :global(.jatui-settings-row) {
132
+ padding-inline: var(--jatui-space-xl, 16px);
133
+ }
134
+ .ss-footer {
135
+ display: flex;
136
+ justify-content: flex-end;
137
+ gap: var(--jatui-space-base, 8px);
138
+ }
139
+ </style>
@@ -0,0 +1,21 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ title?: string;
4
+ description?: string;
5
+ /** Anchor id (SectionNav target). */
6
+ id?: string;
7
+ /** 'card' (default) frames the rows; 'plain' is flush. */
8
+ variant?: 'card' | 'plain';
9
+ headingLevel?: 2 | 3 | 4;
10
+ /** Unsaved-changes dot beside the title. */
11
+ dirty?: boolean;
12
+ /** Header controls (Reset, Save…). */
13
+ actions?: Snippet;
14
+ /** Footer under the rows (save bar, help link). */
15
+ footer?: Snippet;
16
+ children: Snippet;
17
+ class?: string;
18
+ }
19
+ declare const SettingsSection: import("svelte").Component<Props, {}, "">;
20
+ type SettingsSection = ReturnType<typeof SettingsSection>;
21
+ export default SettingsSection;
@@ -0,0 +1,412 @@
1
+ <!--
2
+ ActivityHeatmap: GitHub-style "activity per day" calendar (jatui-6kitr).
3
+
4
+ Generalised from the IDE's StreakCalendar. Built from public behaviour only
5
+ (no BoardUI Pro source): 7 weekday rows × week columns, a token colour ramp
6
+ (color-mix from one theme colour into base-100, no hex), a single tooltip,
7
+ a legend and a summary line. One tab stop: arrows move by day/week inside
8
+ the grid (role="grid"), Enter/Space selects.
9
+ -->
10
+ <script lang="ts" generics="T = { date: string | Date; count: number }">
11
+ import { untrack } from 'svelte';
12
+ import { buildHeatmap, moveHeatFocus, parseDay, isoDay, type HeatDay } from './heatmap';
13
+
14
+ type Tone = 'primary' | 'secondary' | 'accent' | 'success' | 'info' | 'warning' | 'error';
15
+
16
+ interface Props {
17
+ /** One entry per day (several per day are summed). Default shape `{ date, count }`. */
18
+ data: readonly T[];
19
+ getDate?: (d: T) => string | Date;
20
+ getCount?: (d: T) => number;
21
+ /** Inclusive range. Default: the last 12 months ending `end`. */
22
+ start?: string | Date;
23
+ end?: string | Date;
24
+ /** Top row weekday: 0 Sunday (default), 1 Monday. */
25
+ weekStart?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
26
+ /** Unit noun for tooltips and the summary: 'call' or ['call', 'calls']. */
27
+ unit?: string | [string, string];
28
+ /** Custom tooltip / accessible label for a day. */
29
+ formatLabel?: (day: HeatDay) => string;
30
+ /** Theme colour of the ramp. Default 'primary'. */
31
+ tone?: Tone;
32
+ /** Non-zero colour steps. Default 4. */
33
+ levels?: number;
34
+ levelOf?: (count: number, max: number, levels: number) => number;
35
+ /** Smaller cells and labels. */
36
+ compact?: boolean;
37
+ showLegend?: boolean;
38
+ showWeekdays?: boolean;
39
+ showMonths?: boolean;
40
+ /** "1,204 calls in the last year". false hides; a string replaces it. */
41
+ summary?: boolean | string;
42
+ /** Selected day "YYYY-MM-DD" (bindable). */
43
+ selected?: string | null;
44
+ onSelectDay?: (date: string, count: number) => void;
45
+ locale?: string;
46
+ ariaLabel?: string;
47
+ class?: string;
48
+ }
49
+
50
+ let {
51
+ data,
52
+ getDate = (d: T) => (d as { date: string | Date }).date,
53
+ getCount = (d: T) => (d as { count: number }).count,
54
+ start,
55
+ end,
56
+ weekStart = 0,
57
+ unit = ['item', 'items'],
58
+ formatLabel,
59
+ tone = 'primary',
60
+ levels = 4,
61
+ levelOf,
62
+ compact = false,
63
+ showLegend = true,
64
+ showWeekdays = true,
65
+ showMonths = true,
66
+ summary = true,
67
+ selected = $bindable(null),
68
+ onSelectDay,
69
+ locale,
70
+ ariaLabel = 'Activity by day',
71
+ class: className = ''
72
+ }: Props = $props();
73
+
74
+ const layout = $derived(
75
+ buildHeatmap({ data, getDate, getCount, start, end, weekStart, levels, levelOf, locale })
76
+ );
77
+ const byDate = $derived(new Map(layout.days.map((d) => [d.date, d])));
78
+ const today = isoDay(new Date());
79
+
80
+ const [one, many] = $derived(Array.isArray(unit) ? unit : [unit, unit + 's']);
81
+ const nf = $derived(new Intl.NumberFormat(locale));
82
+ const df = $derived(
83
+ new Intl.DateTimeFormat(locale, { weekday: 'short', month: 'short', day: 'numeric', year: 'numeric' })
84
+ );
85
+ const countText = (n: number) => `${n === 0 ? 'No' : nf.format(n)} ${n === 1 ? one : many}`;
86
+ const labelFor = (d: HeatDay) =>
87
+ formatLabel ? formatLabel(d) : `${countText(d.count)} on ${df.format(parseDay(d.date))}`;
88
+
89
+ const weekdayNames = $derived.by(() => {
90
+ const f = new Intl.DateTimeFormat(locale, { weekday: 'short' });
91
+ // 2023-01-01 was a Sunday.
92
+ return Array.from({ length: 7 }, (_, r) => f.format(new Date(2023, 0, 1 + ((r + weekStart) % 7), 12)));
93
+ });
94
+
95
+ const summaryText = $derived.by(() => {
96
+ if (summary === false) return '';
97
+ if (typeof summary === 'string') return summary;
98
+ const days = layout.days.length;
99
+ const span = days >= 365 && days <= 366 ? 'in the last year' : `in ${nf.format(days)} days`;
100
+ return `${nf.format(layout.total)} ${layout.total === 1 ? one : many} ${span}`;
101
+ });
102
+
103
+ // Roving focus: selected day, else the last day in range.
104
+ let focusDate = $state<string | null>(untrack(() => selected));
105
+ const tabDate = $derived(
106
+ focusDate && byDate.has(focusDate) ? focusDate : selected && byDate.has(selected) ? selected : layout.end
107
+ );
108
+
109
+ let gridEl: HTMLDivElement | undefined = $state();
110
+ let scrollEl: HTMLDivElement | undefined = $state();
111
+ let tip = $state<{ day: HeatDay; x: number; y: number } | null>(null);
112
+ let wrapEl: HTMLDivElement | undefined = $state();
113
+
114
+ function showTip(day: HeatDay, cell: HTMLElement) {
115
+ if (!wrapEl) return;
116
+ const w = wrapEl.getBoundingClientRect();
117
+ const c = cell.getBoundingClientRect();
118
+ // Keep the centred tooltip inside the component.
119
+ const x = Math.min(Math.max(c.left - w.left + c.width / 2, 90), Math.max(90, w.width - 90));
120
+ tip = { day, x, y: c.top - w.top };
121
+ }
122
+
123
+ function cellEl(date: string): HTMLElement | null {
124
+ return gridEl?.querySelector<HTMLElement>(`[data-date="${date}"]`) ?? null;
125
+ }
126
+
127
+ function select(day: HeatDay) {
128
+ selected = day.date;
129
+ focusDate = day.date;
130
+ onSelectDay?.(day.date, day.count);
131
+ }
132
+
133
+ function onKeydown(e: KeyboardEvent) {
134
+ const t = e.target as HTMLElement;
135
+ const date = t.dataset.date;
136
+ if (!date) return;
137
+ if (e.key === 'Enter' || e.key === ' ') {
138
+ e.preventDefault();
139
+ const d = byDate.get(date);
140
+ if (d) select(d);
141
+ return;
142
+ }
143
+ if (e.key === 'Escape') {
144
+ tip = null;
145
+ return;
146
+ }
147
+ const next = moveHeatFocus(date, e.key, layout, e.ctrlKey || e.metaKey);
148
+ if (!next) return;
149
+ e.preventDefault();
150
+ focusDate = next;
151
+ queueMicrotask(() => {
152
+ const el = cellEl(next);
153
+ el?.focus();
154
+ el?.scrollIntoView?.({ block: 'nearest', inline: 'nearest' });
155
+ });
156
+ }
157
+
158
+ // Most recent weeks are the interesting ones: start scrolled to the end.
159
+ $effect(() => {
160
+ void layout.weeks;
161
+ if (scrollEl) scrollEl.scrollLeft = scrollEl.scrollWidth;
162
+ });
163
+ </script>
164
+
165
+ <div
166
+ class="jatui-heatmap tone-{tone} {className}"
167
+ class:is-compact={compact}
168
+ bind:this={wrapEl}
169
+ style:--weeks={layout.weeks}
170
+ >
171
+ <div class="hm-scroll" bind:this={scrollEl}>
172
+ <div class="hm-body">
173
+ {#if showMonths}
174
+ <div class="hm-months" aria-hidden="true">
175
+ {#each layout.months as m (m.col)}
176
+ <span style:--col={m.col}>{m.label}</span>
177
+ {/each}
178
+ </div>
179
+ {/if}
180
+ <div class="hm-main">
181
+ {#if showWeekdays}
182
+ <div class="hm-weekdays" aria-hidden="true">
183
+ {#each weekdayNames as name, r}
184
+ <span>{r % 2 === 1 ? name : ''}</span>
185
+ {/each}
186
+ </div>
187
+ {/if}
188
+ <!-- svelte-ignore a11y_interactive_supports_focus -->
189
+ <div
190
+ class="hm-grid"
191
+ role="grid"
192
+ aria-label={ariaLabel}
193
+ tabindex="-1"
194
+ bind:this={gridEl}
195
+ onkeydown={onKeydown}
196
+ onmouseleave={() => (tip = null)}
197
+ >
198
+ {#each layout.rows as row, r (r)}
199
+ <div class="hm-row" role="row" aria-label={weekdayNames[r]}>
200
+ {#each row as day, c (c)}
201
+ {#if day}
202
+ <!-- Keys are handled on the grid (roving focus). -->
203
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
204
+ <div
205
+ role="gridcell"
206
+ class="hm-cell lvl-{day.level}"
207
+ class:is-today={day.date === today}
208
+ class:is-selected={day.date === selected}
209
+ data-date={day.date}
210
+ tabindex={day.date === tabDate ? 0 : -1}
211
+ aria-label={labelFor(day)}
212
+ aria-selected={onSelectDay || selected != null ? day.date === selected : undefined}
213
+ onclick={() => select(day)}
214
+ onmouseenter={(e) => showTip(day, e.currentTarget as HTMLElement)}
215
+ onfocus={(e) => {
216
+ focusDate = day.date;
217
+ showTip(day, e.currentTarget as HTMLElement);
218
+ }}
219
+ onblur={() => (tip = null)}
220
+ ></div>
221
+ {:else}
222
+ <div class="hm-cell is-pad" role="presentation"></div>
223
+ {/if}
224
+ {/each}
225
+ </div>
226
+ {/each}
227
+ </div>
228
+ </div>
229
+ </div>
230
+ </div>
231
+
232
+ {#if summaryText || showLegend}
233
+ <div class="hm-foot">
234
+ {#if summaryText}<span class="hm-summary">{summaryText}</span>{/if}
235
+ {#if showLegend}
236
+ <span class="hm-legend" aria-hidden="true">
237
+ <span>Less</span>
238
+ {#each Array.from({ length: levels + 1 }, (_, i) => i) as l (l)}
239
+ <span class="hm-cell lvl-{l}" style:--l={l}></span>
240
+ {/each}
241
+ <span>More</span>
242
+ </span>
243
+ {/if}
244
+ </div>
245
+ {/if}
246
+
247
+ {#if tip}
248
+ <div class="hm-tip" role="presentation" style:left="{tip.x}px" style:top="{tip.y}px">
249
+ {labelFor(tip.day)}
250
+ </div>
251
+ {/if}
252
+ </div>
253
+
254
+ <style>
255
+ .jatui-heatmap {
256
+ --hm-cell: 0.75rem;
257
+ --hm-gap: 3px;
258
+ --hm-tone: var(--color-primary);
259
+ --hm-empty: color-mix(in oklch, var(--color-base-content) 8%, var(--color-base-100));
260
+ position: relative;
261
+ display: flex;
262
+ flex-direction: column;
263
+ gap: var(--jatui-space-base, 8px);
264
+ min-width: 0;
265
+ max-width: 100%;
266
+ font-size: 0.75rem;
267
+ color: color-mix(in oklch, var(--color-base-content) 60%, transparent);
268
+ }
269
+ .jatui-heatmap.is-compact {
270
+ --hm-cell: 0.625rem;
271
+ --hm-gap: 2px;
272
+ font-size: 0.6875rem;
273
+ }
274
+ .tone-secondary { --hm-tone: var(--color-secondary); }
275
+ .tone-accent { --hm-tone: var(--color-accent); }
276
+ .tone-success { --hm-tone: var(--color-success); }
277
+ .tone-info { --hm-tone: var(--color-info); }
278
+ .tone-warning { --hm-tone: var(--color-warning); }
279
+ .tone-error { --hm-tone: var(--color-error); }
280
+
281
+ .hm-scroll {
282
+ overflow-x: auto;
283
+ overscroll-behavior-x: contain;
284
+ /* room for the focus ring */
285
+ padding: 2px;
286
+ }
287
+ .hm-body {
288
+ display: inline-flex;
289
+ flex-direction: column;
290
+ gap: var(--jatui-space-sm, 4px);
291
+ }
292
+ .hm-months {
293
+ position: relative;
294
+ height: 1.1em;
295
+ margin-inline-start: var(--hm-label-w, 0px);
296
+ }
297
+ .jatui-heatmap:has(.hm-weekdays) .hm-months {
298
+ --hm-label-w: calc(2.25em + var(--hm-gap));
299
+ }
300
+ .hm-months span {
301
+ position: absolute;
302
+ inset-inline-start: calc(var(--col) * (var(--hm-cell) + var(--hm-gap)));
303
+ white-space: nowrap;
304
+ }
305
+ .hm-main {
306
+ display: flex;
307
+ gap: var(--hm-gap);
308
+ }
309
+ .hm-weekdays {
310
+ display: flex;
311
+ flex-direction: column;
312
+ gap: var(--hm-gap);
313
+ width: 2.25em;
314
+ }
315
+ .hm-weekdays span {
316
+ height: var(--hm-cell);
317
+ line-height: var(--hm-cell);
318
+ font-size: 0.9em;
319
+ }
320
+ .hm-grid {
321
+ display: flex;
322
+ flex-direction: column;
323
+ gap: var(--hm-gap);
324
+ outline: none;
325
+ }
326
+ .hm-row {
327
+ display: flex;
328
+ gap: var(--hm-gap);
329
+ }
330
+ .hm-cell {
331
+ flex: none;
332
+ width: var(--hm-cell);
333
+ height: var(--hm-cell);
334
+ border-radius: calc(var(--jatui-radius-field, 0.25rem) * 0.75);
335
+ background: var(--hm-empty);
336
+ outline: none;
337
+ }
338
+ .hm-cell.is-pad {
339
+ background: transparent;
340
+ }
341
+ .lvl-1 { background: color-mix(in oklch, var(--hm-tone) 28%, var(--color-base-100)); }
342
+ .lvl-2 { background: color-mix(in oklch, var(--hm-tone) 50%, var(--color-base-100)); }
343
+ .lvl-3 { background: color-mix(in oklch, var(--hm-tone) 74%, var(--color-base-100)); }
344
+ .lvl-4,
345
+ .lvl-5,
346
+ .lvl-6 { background: var(--hm-tone); }
347
+ .hm-grid .hm-cell[role='gridcell'] {
348
+ cursor: pointer;
349
+ transition: transform var(--motion-fast, 150ms) var(--motion-standard, ease);
350
+ }
351
+ .hm-grid .hm-cell[role='gridcell']:hover {
352
+ transform: scale(1.2);
353
+ }
354
+ .hm-cell.is-today {
355
+ box-shadow: inset 0 0 0 1px color-mix(in oklch, var(--color-base-content) 45%, transparent);
356
+ }
357
+ .hm-cell.is-selected {
358
+ box-shadow:
359
+ 0 0 0 1.5px var(--color-base-100),
360
+ 0 0 0 3px var(--color-base-content);
361
+ }
362
+ .hm-cell:focus-visible {
363
+ box-shadow:
364
+ 0 0 0 1.5px var(--color-base-100),
365
+ 0 0 0 3px var(--color-primary);
366
+ }
367
+
368
+ .hm-foot {
369
+ display: flex;
370
+ flex-wrap: wrap;
371
+ align-items: center;
372
+ justify-content: space-between;
373
+ gap: var(--jatui-space-base, 8px);
374
+ }
375
+ .hm-legend {
376
+ display: inline-flex;
377
+ align-items: center;
378
+ gap: var(--hm-gap);
379
+ margin-inline-start: auto;
380
+ }
381
+ .hm-legend > span:first-child {
382
+ margin-inline-end: var(--jatui-space-sm, 4px);
383
+ }
384
+ .hm-legend > span:last-child {
385
+ margin-inline-start: var(--jatui-space-sm, 4px);
386
+ }
387
+
388
+ .hm-tip {
389
+ position: absolute;
390
+ z-index: 10;
391
+ transform: translate(-50%, calc(-100% - 6px));
392
+ padding: var(--jatui-space-sm, 4px) var(--jatui-space-base, 8px);
393
+ border-radius: var(--jatui-radius-field, 0.25rem);
394
+ background: var(--color-neutral);
395
+ color: var(--color-neutral-content);
396
+ font-size: 0.75rem;
397
+ white-space: nowrap;
398
+ pointer-events: none;
399
+ }
400
+
401
+ @media (prefers-reduced-motion: reduce) {
402
+ .hm-grid .hm-cell[role='gridcell'] {
403
+ transition: none;
404
+ }
405
+ .hm-grid .hm-cell[role='gridcell']:hover {
406
+ transform: none;
407
+ }
408
+ }
409
+ :global([data-motion='calm']) .hm-grid .hm-cell[role='gridcell']:hover {
410
+ transform: none;
411
+ }
412
+ </style>
@@ -0,0 +1,72 @@
1
+ import { type HeatDay } from './heatmap';
2
+ declare function $$render<T = {
3
+ date: string | Date;
4
+ count: number;
5
+ }>(): {
6
+ props: {
7
+ /** One entry per day (several per day are summed). Default shape `{ date, count }`. */
8
+ data: readonly T[];
9
+ getDate?: (d: T) => string | Date;
10
+ getCount?: (d: T) => number;
11
+ /** Inclusive range. Default: the last 12 months ending `end`. */
12
+ start?: string | Date;
13
+ end?: string | Date;
14
+ /** Top row weekday: 0 Sunday (default), 1 Monday. */
15
+ weekStart?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
16
+ /** Unit noun for tooltips and the summary: 'call' or ['call', 'calls']. */
17
+ unit?: string | [string, string];
18
+ /** Custom tooltip / accessible label for a day. */
19
+ formatLabel?: (day: HeatDay) => string;
20
+ /** Theme colour of the ramp. Default 'primary'. */
21
+ tone?: "error" | "warning" | "info" | "success" | "primary" | "secondary" | "accent";
22
+ /** Non-zero colour steps. Default 4. */
23
+ levels?: number;
24
+ levelOf?: (count: number, max: number, levels: number) => number;
25
+ /** Smaller cells and labels. */
26
+ compact?: boolean;
27
+ showLegend?: boolean;
28
+ showWeekdays?: boolean;
29
+ showMonths?: boolean;
30
+ /** "1,204 calls in the last year". false hides; a string replaces it. */
31
+ summary?: boolean | string;
32
+ /** Selected day "YYYY-MM-DD" (bindable). */
33
+ selected?: string | null;
34
+ onSelectDay?: (date: string, count: number) => void;
35
+ locale?: string;
36
+ ariaLabel?: string;
37
+ class?: string;
38
+ };
39
+ exports: {};
40
+ bindings: "selected";
41
+ slots: {};
42
+ events: {};
43
+ };
44
+ declare class __sveltets_Render<T = {
45
+ date: string | Date;
46
+ count: number;
47
+ }> {
48
+ props(): ReturnType<typeof $$render<T>>['props'];
49
+ events(): ReturnType<typeof $$render<T>>['events'];
50
+ slots(): ReturnType<typeof $$render<T>>['slots'];
51
+ bindings(): "selected";
52
+ exports(): {};
53
+ }
54
+ interface $$IsomorphicComponent {
55
+ new <T = {
56
+ date: string | Date;
57
+ count: number;
58
+ }>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
59
+ $$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
60
+ } & ReturnType<__sveltets_Render<T>['exports']>;
61
+ <T = {
62
+ date: string | Date;
63
+ count: number;
64
+ }>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
65
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
66
+ }
67
+ declare const ActivityHeatmap: $$IsomorphicComponent;
68
+ type ActivityHeatmap<T = {
69
+ date: string | Date;
70
+ count: number;
71
+ }> = InstanceType<typeof ActivityHeatmap<T>>;
72
+ export default ActivityHeatmap;