@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,151 @@
1
+ /**
2
+ * ActivityHeatmap layout + keyboard math (jatui-6kitr). Pure and DOM-free.
3
+ *
4
+ * Days are local calendar dates, keyed "YYYY-MM-DD". The grid is 7 weekday
5
+ * ROWS × N week COLUMNS (GitHub contributions layout); `weekStart` picks the
6
+ * top row (0 = Sunday, 1 = Monday).
7
+ */
8
+ const pad = (n) => String(n).padStart(2, '0');
9
+ /** Local "YYYY-MM-DD". */
10
+ export function isoDay(d) {
11
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
12
+ }
13
+ /** Parse a day as LOCAL noon (DST-safe arithmetic). "YYYY-MM-DD" is never read as UTC. */
14
+ export function parseDay(input) {
15
+ if (input instanceof Date)
16
+ return new Date(input.getFullYear(), input.getMonth(), input.getDate(), 12);
17
+ const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(input);
18
+ if (m)
19
+ return new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]), 12);
20
+ const d = new Date(input);
21
+ return new Date(d.getFullYear(), d.getMonth(), d.getDate(), 12);
22
+ }
23
+ export function addDays(day, n) {
24
+ const d = parseDay(day);
25
+ d.setDate(d.getDate() + n);
26
+ return isoDay(d);
27
+ }
28
+ /** Whole days from a to b (b - a). */
29
+ export function daysBetween(a, b) {
30
+ return Math.round((parseDay(b).getTime() - parseDay(a).getTime()) / 86_400_000);
31
+ }
32
+ export function defaultLevelOf(count, max, levels) {
33
+ if (count <= 0 || max <= 0)
34
+ return 0;
35
+ return Math.min(levels, Math.max(1, Math.ceil((count / max) * levels)));
36
+ }
37
+ export function buildHeatmap(opts) {
38
+ const levels = Math.max(1, opts.levels ?? 4);
39
+ const weekStart = opts.weekStart ?? 0;
40
+ const levelOf = opts.levelOf ?? defaultLevelOf;
41
+ const end = isoDay(parseDay(opts.end ?? new Date()));
42
+ const start = opts.start != null ? isoDay(parseDay(opts.start)) : addDays(addDays(end, 1), -yearLength(end));
43
+ const counts = new Map();
44
+ for (const d of opts.data) {
45
+ const key = isoDay(parseDay(opts.getDate(d)));
46
+ const c = Number(opts.getCount(d)) || 0;
47
+ counts.set(key, (counts.get(key) ?? 0) + c);
48
+ }
49
+ const span = Math.max(0, daysBetween(start, end)) + 1;
50
+ const firstRow = (parseDay(start).getDay() - weekStart + 7) % 7;
51
+ const weeks = Math.ceil((firstRow + span) / 7);
52
+ const rows = Array.from({ length: 7 }, () => Array(weeks).fill(null));
53
+ const days = [];
54
+ let max = 0;
55
+ let total = 0;
56
+ let activeDays = 0;
57
+ const cursor = parseDay(start);
58
+ for (let i = 0; i < span; i++) {
59
+ const date = isoDay(cursor);
60
+ const count = counts.get(date) ?? 0;
61
+ if (count > max)
62
+ max = count;
63
+ total += count;
64
+ if (count > 0)
65
+ activeDays++;
66
+ const slot = firstRow + i;
67
+ const day = { date, count, level: 0, col: Math.floor(slot / 7), row: slot % 7 };
68
+ days.push(day);
69
+ rows[day.row][day.col] = day;
70
+ cursor.setDate(cursor.getDate() + 1);
71
+ }
72
+ for (const d of days)
73
+ d.level = Math.min(levels, Math.max(0, levelOf(d.count, max, levels)));
74
+ // Month labels: the first column whose first in-range day falls in a new month.
75
+ const fmt = new Intl.DateTimeFormat(opts.locale, { month: 'short' });
76
+ const gap = opts.monthGap ?? 3;
77
+ const months = [];
78
+ let lastMonth = -1;
79
+ let lastCol = -Infinity;
80
+ for (let col = 0; col < weeks; col++) {
81
+ let first = null;
82
+ for (let r = 0; r < 7 && !first; r++)
83
+ first = rows[r][col];
84
+ if (!first)
85
+ continue;
86
+ const d = parseDay(first.date);
87
+ if (d.getMonth() !== lastMonth) {
88
+ lastMonth = d.getMonth();
89
+ if (col - lastCol >= gap) {
90
+ months.push({ label: fmt.format(d), col });
91
+ lastCol = col;
92
+ }
93
+ }
94
+ }
95
+ return { days, rows, weeks, max, total, activeDays, months, start, end };
96
+ }
97
+ function yearLength(end) {
98
+ const e = parseDay(end);
99
+ const back = new Date(e.getFullYear() - 1, e.getMonth(), e.getDate(), 12);
100
+ return Math.round((e.getTime() - back.getTime()) / 86_400_000);
101
+ }
102
+ /**
103
+ * Keyboard move inside the heatmap grid. Returns the new focused day,
104
+ * clamped to [start, end], or null for keys it does not handle.
105
+ *
106
+ * ArrowUp/Down previous / next day (row)
107
+ * ArrowLeft/Right previous / next week (column)
108
+ * PageUp/PageDown four weeks back / forward
109
+ * Home / End first / last week, same weekday (row start / end)
110
+ * mod+Home/End first / last day in range
111
+ */
112
+ export function moveHeatFocus(day, key, range, mod = false) {
113
+ let next;
114
+ switch (key) {
115
+ case 'ArrowUp':
116
+ next = addDays(day, -1);
117
+ break;
118
+ case 'ArrowDown':
119
+ next = addDays(day, 1);
120
+ break;
121
+ case 'ArrowLeft':
122
+ next = addDays(day, -7);
123
+ break;
124
+ case 'ArrowRight':
125
+ next = addDays(day, 7);
126
+ break;
127
+ case 'PageUp':
128
+ next = addDays(day, -28);
129
+ break;
130
+ case 'PageDown':
131
+ next = addDays(day, 28);
132
+ break;
133
+ case 'Home':
134
+ if (mod)
135
+ return range.start;
136
+ next = addDays(day, -7 * Math.floor(daysBetween(range.start, day) / 7));
137
+ break;
138
+ case 'End':
139
+ if (mod)
140
+ return range.end;
141
+ next = addDays(day, 7 * Math.floor(daysBetween(day, range.end) / 7));
142
+ break;
143
+ default:
144
+ return null;
145
+ }
146
+ if (daysBetween(range.start, next) < 0)
147
+ return range.start;
148
+ if (daysBetween(next, range.end) < 0)
149
+ return range.end;
150
+ return next;
151
+ }