@eifi1/ui-kit 0.14.2 → 0.15.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 (118) hide show
  1. package/dist/chart.d.ts +2 -1
  2. package/dist/components/alert-banner.js +5 -2
  3. package/dist/components/alert-banner.js.map +1 -1
  4. package/dist/components/amount-input.d.ts +1 -1
  5. package/dist/components/autocomplete.js +9 -2
  6. package/dist/components/autocomplete.js.map +1 -1
  7. package/dist/components/bulk-action-bar.js +6 -3
  8. package/dist/components/bulk-action-bar.js.map +1 -1
  9. package/dist/components/button-group.d.ts +1 -1
  10. package/dist/components/calculator.d.ts +1 -1
  11. package/dist/components/calendar-heatmap.js +5 -5
  12. package/dist/components/calendar-heatmap.js.map +1 -1
  13. package/dist/components/copy-button.d.ts +1 -1
  14. package/dist/components/data-table-cells.d.ts +1 -1
  15. package/dist/components/data-table-filter-popover.d.ts +1 -1
  16. package/dist/components/data-table-filter-popover.js +3 -3
  17. package/dist/components/data-table-filter-popover.js.map +1 -1
  18. package/dist/components/data-table-filters.d.ts +1 -1
  19. package/dist/components/data-table.d.ts +1 -1
  20. package/dist/components/data-table.js +12 -4
  21. package/dist/components/data-table.js.map +1 -1
  22. package/dist/components/facing-pair.d.ts +1 -0
  23. package/dist/components/field.js +4 -1
  24. package/dist/components/field.js.map +1 -1
  25. package/dist/components/file-button.d.ts +1 -1
  26. package/dist/components/file-dropzone.d.ts +1 -1
  27. package/dist/components/form-actions.d.ts +1 -1
  28. package/dist/components/image-grid.d.ts +8 -1
  29. package/dist/components/image-grid.js +16 -3
  30. package/dist/components/image-grid.js.map +1 -1
  31. package/dist/components/measured-grid.js +2 -2
  32. package/dist/components/measured-grid.js.map +1 -1
  33. package/dist/components/money-field.d.ts +1 -1
  34. package/dist/components/multi-select.js +3 -0
  35. package/dist/components/multi-select.js.map +1 -1
  36. package/dist/components/number-field.d.ts +1 -1
  37. package/dist/components/number-input.d.ts +5 -2
  38. package/dist/components/number-input.js +11 -3
  39. package/dist/components/number-input.js.map +1 -1
  40. package/dist/components/numpad-sheet.d.ts +1 -1
  41. package/dist/components/series-chart-budget.d.ts +60 -0
  42. package/dist/components/series-chart-budget.js +56 -0
  43. package/dist/components/series-chart-budget.js.map +1 -0
  44. package/dist/components/series-chart.d.ts +54 -2
  45. package/dist/components/series-chart.js +104 -20
  46. package/dist/components/series-chart.js.map +1 -1
  47. package/dist/components/settings-fields.d.ts +1 -1
  48. package/dist/components/slider.js +6 -2
  49. package/dist/components/slider.js.map +1 -1
  50. package/dist/components/stat-tile.js +3 -3
  51. package/dist/components/stat-tile.js.map +1 -1
  52. package/dist/components/text-link.d.ts +1 -1
  53. package/dist/components/time-input.d.ts +1 -1
  54. package/dist/components/treemap.d.ts +3 -2
  55. package/dist/components/treemap.js +14 -3
  56. package/dist/components/treemap.js.map +1 -1
  57. package/dist/components/ui.d.ts +1 -1
  58. package/dist/components/ui.js +44 -10
  59. package/dist/components/ui.js.map +1 -1
  60. package/dist/components/use-table-state.d.ts +1 -1
  61. package/dist/{data-table-Drun8Fk9.d.ts → data-table-CfVXtCeG.d.ts} +4 -3
  62. package/dist/data-table.d.ts +1 -1
  63. package/dist/feedback/feedback-thread.js +31 -12
  64. package/dist/feedback/feedback-thread.js.map +1 -1
  65. package/dist/hooks/use-file-drop.d.ts +1 -1
  66. package/dist/i18n/defaults.d.ts +1 -1
  67. package/dist/i18n/kit-labels.d.ts +1 -1
  68. package/dist/i18n/locales/de-CH-informal.d.ts +1 -1
  69. package/dist/i18n/locales/de-CH.d.ts +1 -1
  70. package/dist/i18n/locales/de-informal.d.ts +1 -1
  71. package/dist/i18n/locales/de.d.ts +1 -1
  72. package/dist/i18n/locales/es.d.ts +1 -1
  73. package/dist/i18n/locales/fr.d.ts +1 -1
  74. package/dist/i18n/locales/hu.d.ts +1 -1
  75. package/dist/i18n/locales/it.d.ts +1 -1
  76. package/dist/i18n/locales/zh.d.ts +1 -1
  77. package/dist/index.d.ts +5 -4
  78. package/dist/{kit-labels-Mz8zZHIF.d.ts → kit-labels-BHfRBbHs.d.ts} +5 -1
  79. package/dist/lib/strip-fade.d.ts +31 -0
  80. package/dist/lib/strip-fade.js +54 -0
  81. package/dist/lib/strip-fade.js.map +1 -0
  82. package/dist/rhf/fields.d.ts +1 -1
  83. package/dist/rhf/form.d.ts +1 -1
  84. package/dist/rhf/form.js +1 -1
  85. package/dist/rhf/form.js.map +1 -1
  86. package/dist/rhf.d.ts +1 -1
  87. package/dist/shell/app-shell.d.ts +11 -2
  88. package/dist/shell/app-shell.js +20 -4
  89. package/dist/shell/app-shell.js.map +1 -1
  90. package/dist/shell/top-bar-brand.d.ts +1 -1
  91. package/dist/shell.d.ts +1 -1
  92. package/dist/wizard/stepper-nav.d.ts +1 -1
  93. package/dist/wizard/stepper-nav.js +14 -2
  94. package/dist/wizard/stepper-nav.js.map +1 -1
  95. package/dist/wizard.d.ts +1 -1
  96. package/package.json +1 -1
  97. package/src/components/alert-banner.tsx +10 -2
  98. package/src/components/autocomplete.tsx +10 -1
  99. package/src/components/bulk-action-bar.tsx +12 -3
  100. package/src/components/calendar-heatmap.tsx +10 -5
  101. package/src/components/data-table-filter-popover.tsx +6 -3
  102. package/src/components/data-table.tsx +21 -7
  103. package/src/components/field.tsx +4 -1
  104. package/src/components/image-grid.tsx +30 -2
  105. package/src/components/measured-grid.tsx +3 -2
  106. package/src/components/multi-select.tsx +3 -0
  107. package/src/components/number-input.tsx +23 -4
  108. package/src/components/series-chart-budget.ts +121 -0
  109. package/src/components/series-chart.tsx +201 -15
  110. package/src/components/slider.tsx +6 -2
  111. package/src/components/stat-tile.tsx +8 -3
  112. package/src/components/treemap.tsx +30 -5
  113. package/src/components/ui.tsx +91 -11
  114. package/src/feedback/feedback-thread.tsx +36 -13
  115. package/src/lib/strip-fade.ts +82 -0
  116. package/src/rhf/form.tsx +3 -1
  117. package/src/shell/app-shell.tsx +34 -7
  118. package/src/wizard/stepper-nav.tsx +23 -2
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/calendar-heatmap.tsx"],"sourcesContent":["import { useEffect, useLayoutEffect, useMemo, useRef, useState } from \"react\";\nimport type { ComponentPropsWithoutRef, CSSProperties, KeyboardEvent, ReactNode } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { dirOf, horizontalStep } from \"../lib/direction\";\nimport { addMonthsClamped, localeWeekStart, parseIsoDate, toLocalIso } from \"../lib/dates\";\nimport { useKitLabels, useKitLocale, useKitWeekStart } from \"../i18n/kit-labels\";\nimport { lerpHex, textOn } from \"../theme/chart-palette\";\nimport { parseHex } from \"../theme/color\";\nimport { Tooltip } from \"./tooltip\";\nimport type { WeekDay } from \"./mini-calendar\";\n\n/**\n * Every string the heatmap speaks — the `calendarHeatmap` namespace of\n * `<UiKitProvider labels>`, overridable per instance through `labels`. Dates and\n * values arrive already formatted (in `locale`, and through `formatValue`), so the\n * functions only arrange them.\n */\nexport interface CalendarHeatmapLabels {\n /** Accessible name of the grid. */\n grid: string;\n /**\n * One day: its accessible name, and the tooltip unless `tooltip` is given. `date`\n * is the whole date in `locale` (\"Monday, 14 September 2026\"), `value` the day's\n * figure through `formatValue`.\n */\n day: (date: string, value: string) => string;\n /** The two ends of the legend's scale. */\n less: string;\n more: string;\n /** `maxDays` cut the window: how many of its earliest days are not drawn. */\n truncated: (count: number) => string;\n}\n\n/** English defaults, exported as the `calendarHeatmap` namespace of `UiKitLabels`. */\nexport const DEFAULT_CALENDAR_HEATMAP_LABELS: CalendarHeatmapLabels = {\n grid: \"Daily values\",\n day: (date, value) => `${date}: ${value}`,\n less: \"Less\",\n more: \"More\",\n truncated: (count) =>\n `Showing the most recent days; ${count} earlier ${count === 1 ? \"day is\" : \"days are\"} not shown.`,\n};\n\n/** One day's figure. Several entries for one date are added up. */\nexport interface CalendarHeatmapDatum {\n /** \"YYYY-MM-DD\". */\n date: string;\n value: number;\n}\n\n/** What `tooltip` is told about the day under the pointer. */\nexport interface CalendarHeatmapDay {\n iso: string;\n date: Date;\n /** The day's figure; `0` when `data` has no entry for it (see `hasData`). */\n value: number;\n hasData: boolean;\n /** `0` = empty … `levels` = the busiest. */\n level: number;\n /** The whole date in `locale`. */\n formattedDate: string;\n /** `value` through `formatValue`. */\n formattedValue: string;\n}\n\n/**\n * `onSelect` is omitted from the `<div>` attributes for the reason `MiniCalendar` gives:\n * the DOM event of that name is a text-selection event, and this one answers with a date.\n */\nexport interface CalendarHeatmapProps extends Omit<ComponentPropsWithoutRef<\"div\">, \"onSelect\"> {\n data: readonly CalendarHeatmapDatum[];\n /** The window, inclusive, as \"YYYY-MM-DD\". */\n from: string;\n to: string;\n /**\n * `\"weeks\"` (default): the contribution-graph shape — one COLUMN per week, the\n * weekdays down the side, month names along the top; a year fits in ~53 small\n * squares and scrolls sideways where it does not. `\"month\"`: a 7-column calendar\n * with day numbers in large square cells, for a single month on a phone (switch on\n * `useMediaQuery` and narrow `from`/`to` to the month).\n */\n layout?: \"weeks\" | \"month\";\n /** The day shown as selected (\"YYYY-MM-DD\"). */\n selected?: string;\n /** Makes each day a button: a click (or Enter/Space) selects it — a drill-down into\n * that day's rows, a day panel. Without it the days are focusable but inert. */\n onSelect?: (iso: string) => void;\n /** How a value is written in the tooltip and the day's name. Default: `Intl.NumberFormat`\n * in `locale`. Pass a currency formatter for money. */\n formatValue?: (value: number) => string;\n /** Tooltip content for a day. Default: `labels.day(date, value)`. */\n tooltip?: (day: CalendarHeatmapDay) => ReactNode;\n /**\n * Tag each tooltip `data-private`, so the host's demo-mode rule blurs it (the\n * attribute `Tooltip redact` and `StatTile sensitive` carry). ON by default: a heatmap\n * is almost always the user's own figures, and a missing tag is silent — the grid\n * looks anonymous and the bubble spells the amount out on hover.\n */\n sensitive?: boolean;\n /** The value that reaches the top of the scale. Default: the largest value ON SCREEN,\n * so a peak outside the window does not wash the drawn days out to empty. */\n max?: number;\n /** How many non-empty intensity steps. Default 4. */\n levels?: number;\n /** The scale's colour, as any CSS colour. Default `var(--brand)`; each step is this\n * mixed into `--bg-surface`, so the ramp follows the theme. */\n color?: string;\n /**\n * A CONTINUOUS scale from `colorFrom` (the smallest value) to `colorTo` (the busiest\n * day), instead of `levels` steps of `color` — keksdose's palette-store ramp,\n * `seqLow` → `seqHigh`. Both are needed; `color` is then unused. Two `#rrggbb`\n * colours are interpolated in JS, so the fill is a concrete hex and the day number's\n * ink (in `\"month\"`) is MEASURED against it; anything else (a `var()`) is mixed with\n * `color-mix()`. `levels` still sets `data-level` and the legend's swatch count.\n */\n colorFrom?: string;\n colorTo?: string;\n /**\n * A fill of the caller's own for a day with a value: handed the value and the\n * scale's `max`, returns any CSS colour. Wins over `color` and `colorFrom`/`colorTo`.\n * Also paints the legend, at `max × level / levels`.\n */\n fill?: (value: number, max: number) => string;\n /** The fill of a day with no value (or a value of 0). Default: a wash of the text\n * colour over the surface, which reads as a cell on both themes. */\n emptyColor?: string;\n /**\n * Where the window ENDS — the day `maxDays` counts back from, and the last one drawn:\n *\n * - `\"to\"` (default): on `to`.\n * - `\"today\"`: on today.\n * - `\"latest\"`: on the last day that can carry a figure — today, or a later day that\n * actually has a value. A window running into the future (a report's \"all\" preset\n * ends a year ahead) then spends its cells on the days a person is looking for,\n * not on a year of empty ones.\n * - a `\"YYYY-MM-DD\"`: on that day.\n *\n * Always kept inside `from`…`to`.\n */\n anchor?: \"to\" | \"today\" | \"latest\" | (string & {});\n /**\n * The most days to draw. A longer window keeps its LATEST `maxDays` and says how many\n * it left out (`labels.truncated`) rather than silently ending. Default: no cap.\n */\n maxDays?: number;\n /** Draw the Less … More legend. Default `true`. */\n legend?: boolean;\n /** BCP 47 tag for the dates, month and weekday names and numbers. */\n locale?: string;\n /** `0` = Sunday … `6` = Saturday. Default: the provider's, else the locale's. */\n weekStartsOn?: WeekDay;\n labels?: Partial<CalendarHeatmapLabels>;\n className?: string;\n}\n\nconst DAY_NAME_FORMAT: Intl.DateTimeFormatOptions = {\n weekday: \"long\",\n day: \"numeric\",\n month: \"long\",\n year: \"numeric\",\n};\n\n/** The empty day: a wash of the text colour, like `--bg-hover`, so it reads as a cell\n * on both themes without claiming any value. */\nconst EMPTY_FILL = \"color-mix(in oklab, var(--text-primary) 8%, var(--bg-surface))\";\n\nconst weekdayIndex = (d: Date, weekStart: WeekDay) => (d.getDay() - weekStart + 7) % 7;\n\n/** Where the window ends for an {@link CalendarHeatmapProps.anchor}, kept in `[from, to]`.\n * `latestData` is the latest ISO date carrying a value, or `\"\"`. */\nexport function heatmapWindowEnd(\n from: string,\n to: string,\n anchor: string,\n latestData: string,\n today: string = toLocalIso(new Date()),\n): string {\n let end: string;\n if (anchor === \"today\") end = today;\n else if (anchor === \"latest\") end = latestData > today ? latestData : today;\n else if (anchor === \"to\" || !parseIsoDate(anchor)) end = to;\n else end = anchor;\n // ISO dates sort as strings, so the clamp is plain comparison.\n if (end > to) end = to;\n if (end < from) end = from;\n return end;\n}\n\n/** The step a value falls on: 0 for nothing, else 1…levels by its share of `max`. */\nexport function heatmapLevel(value: number, max: number, levels: number): number {\n if (!(value > 0) || !(max > 0) || levels < 1) return 0;\n return Math.min(levels, Math.max(1, Math.ceil((value / max) * levels)));\n}\n\n/** How much of `color` a step mixes into the surface: 20% for the first step, 100% for\n * the last — the floor keeps the smallest value visibly apart from an empty day. */\nfunction levelPercent(level: number, levels: number): number {\n if (levels <= 1) return 100;\n return Math.round(20 + (80 * (level - 1)) / (levels - 1));\n}\n\nfunction levelFill(level: number, levels: number, color: string): string {\n if (level === 0) return EMPTY_FILL;\n return `color-mix(in oklab, ${color} ${levelPercent(level, levels)}%, var(--bg-surface))`;\n}\n\n/**\n * Days as a grid of shaded squares — how much happened on each, at a glance\n * (a contribution graph, a spending calendar).\n *\n * **Every day is a cell you can reach.** The grid is a `role=\"grid\"` with ONE tab\n * stop (the roving tabindex, as in `MiniCalendar`); the arrows move by the grid's own\n * geometry — in `\"weeks\"` ↑/↓ are the previous/next day and ←/→ a week, in `\"month\"`\n * the other way round, mirrored in RTL — PageUp/PageDown move a month and Home/End go\n * to the window's first/last day. Each cell is named with its whole date AND its value,\n * because the colour is not something a screen reader can say, and carries\n * `data-day=\"YYYY-MM-DD\"` as its identity for tests and for the host. Note that in\n * `\"weeks\"` the cells are in DOM order by WEEKDAY ROW (all Mondays, then all\n * Tuesdays…), not by date — query by `data-day`, not by position.\n *\n * The tooltip is portalled (a year of squares always sits in a sideways scroller) and\n * `data-private` unless `sensitive={false}`. Cell size in `\"weeks\"` is the CSS variable\n * `--heatmap-cell` (default `0.75rem`): `style={{ \"--heatmap-cell\": \"1rem\" }}`.\n */\nexport function CalendarHeatmap({\n data,\n from,\n to,\n layout = \"weeks\",\n selected,\n onSelect,\n formatValue: formatValueProp,\n tooltip,\n sensitive = true,\n max: maxProp,\n levels = 4,\n color = \"var(--brand)\",\n colorFrom,\n colorTo,\n fill: fillProp,\n emptyColor,\n anchor = \"to\",\n maxDays,\n legend = true,\n locale: localeProp,\n weekStartsOn,\n labels: labelsProp,\n className,\n \"aria-label\": ariaLabel,\n ...rest\n}: CalendarHeatmapProps) {\n const labels = useKitLabels(\"calendarHeatmap\", DEFAULT_CALENDAR_HEATMAP_LABELS, labelsProp);\n const locale = useKitLocale(localeProp);\n const providerWeekStart = useKitWeekStart();\n const weekStart: WeekDay = weekStartsOn ?? providerWeekStart ?? localeWeekStart(locale);\n const numberFormat = useMemo(() => new Intl.NumberFormat(locale), [locale]);\n const formatValue = formatValueProp ?? ((v: number) => numberFormat.format(v));\n\n const values = useMemo(() => {\n const m = new Map<string, number>();\n for (const p of data) m.set(p.date, (m.get(p.date) ?? 0) + p.value);\n return m;\n }, [data]);\n\n // The window, trimmed from the FRONT: the days a person looks for are the recent\n // ones, and a cap that kept the oldest would end the calendar before today.\n const latestData = useMemo(() => {\n let latest = \"\";\n for (const [iso, v] of values) if (v !== 0 && iso > latest) latest = iso;\n return latest;\n }, [values]);\n const windowEnd = heatmapWindowEnd(from, to, anchor, latestData);\n\n const { days, dropped } = useMemo(() => {\n const start = parseIsoDate(from);\n const end = parseIsoDate(windowEnd);\n const list: Date[] = [];\n if (!start || !end || end < start) return { days: list, dropped: 0 };\n const cursor = new Date(end);\n const cap = maxDays && maxDays > 0 ? maxDays : Infinity;\n while (cursor >= start && list.length < cap) {\n list.unshift(new Date(cursor));\n cursor.setDate(cursor.getDate() - 1);\n }\n const first = list[0];\n const trimmed = first ? Math.round((first.getTime() - start.getTime()) / 86_400_000) : 0;\n return { days: list, dropped: Math.max(0, trimmed) };\n }, [from, windowEnd, maxDays]);\n\n const firstIso = days.length ? toLocalIso(days[0]) : \"\";\n const lastIso = days.length ? toLocalIso(days[days.length - 1]) : \"\";\n\n const max = useMemo(() => {\n if (maxProp !== undefined) return maxProp;\n let m = 0;\n for (const d of days) m = Math.max(m, values.get(toLocalIso(d)) ?? 0);\n return m;\n }, [maxProp, days, values]);\n\n const clampIso = (iso: string) => (iso < firstIso ? firstIso : iso > lastIso ? lastIso : iso);\n\n // The roving tab stop: the selected day, else the latest one.\n const [activeState, setActiveIso] = useState(() => selected || lastIso);\n const [lastSelected, setLastSelected] = useState(selected);\n if (lastSelected !== selected) {\n setLastSelected(selected);\n if (selected) setActiveIso(selected);\n }\n const activeIso = days.length ? clampIso(activeState || lastIso) : \"\";\n\n const gridRef = useRef<HTMLDivElement>(null);\n const scrollerRef = useRef<HTMLDivElement>(null);\n const pendingFocus = useRef<string | null>(null);\n useEffect(() => {\n const iso = pendingFocus.current;\n if (!iso) return;\n pendingFocus.current = null;\n gridRef.current?.querySelector<HTMLElement>(`[data-day=\"${iso}\"]`)?.focus();\n });\n\n // Open the sideways scroller on the latest weeks, not the oldest. In RTL the end is\n // on the left, where browsers count scrollLeft down from 0.\n //\n // And KEEP it there while the box changes size — a sidebar or a contents rail that\n // appears after mount narrows the scroller, which leaves scrollLeft where it was and\n // so shows older weeks than it opened on. The pin holds only while the view sits at\n // the end: a user who scrolls back through the year unpins it, and a resize then\n // leaves their position alone. (Scrolling back to the end pins it again.)\n useLayoutEffect(() => {\n const el = scrollerRef.current;\n if (!el || layout !== \"weeks\") return;\n const toEnd = () => {\n el.scrollLeft = dirOf(el) === \"rtl\" ? -el.scrollWidth : el.scrollWidth;\n };\n toEnd();\n if (typeof ResizeObserver === \"undefined\") return;\n // |scrollLeft| covers both directions: 0 → max in LTR, 0 → -max in RTL.\n let pinned = true;\n const onScroll = () => {\n pinned = Math.abs(el.scrollLeft) >= el.scrollWidth - el.clientWidth - 1;\n };\n const ro = new ResizeObserver(() => {\n if (pinned) toEnd();\n });\n el.addEventListener(\"scroll\", onScroll, { passive: true });\n ro.observe(el);\n return () => {\n ro.disconnect();\n el.removeEventListener(\"scroll\", onScroll);\n };\n }, [layout, firstIso, lastIso]);\n\n const formatDay = (d: Date) => d.toLocaleDateString(locale, DAY_NAME_FORMAT);\n\n const weekdayLabels = useMemo(() => {\n const ref = new Date(2024, 0, 7 + weekStart); // 7 January 2024 was a Sunday\n return Array.from({ length: 7 }, (_, i) => {\n const d = new Date(ref);\n d.setDate(ref.getDate() + i);\n return {\n short: d.toLocaleDateString(locale, { weekday: \"short\" }),\n long: d.toLocaleDateString(locale, { weekday: \"long\" }),\n };\n });\n }, [locale, weekStart]);\n\n const lead = days.length ? weekdayIndex(days[0], weekStart) : 0;\n const weekCount = Math.ceil((lead + days.length) / 7);\n /** The day at position `i` of the week-aligned sequence (blanks before the first). */\n const dayAt = (i: number): Date | null => (i >= lead && i - lead < days.length ? days[i - lead] : null);\n\n const move = (e: KeyboardEvent<HTMLElement>) => {\n const current = parseIsoDate(activeIso);\n if (!current) return;\n const next = new Date(current);\n const step = horizontalStep(e.key, e.currentTarget);\n // In \"weeks\" a row is a weekday, so sideways is a week and up/down is a day.\n const across = layout === \"weeks\" ? 7 : 1;\n const down = layout === \"weeks\" ? 1 : 7;\n switch (step ? \"horizontal\" : e.key) {\n case \"horizontal\":\n next.setDate(next.getDate() + step * across);\n break;\n case \"ArrowUp\":\n next.setDate(next.getDate() - down);\n break;\n case \"ArrowDown\":\n next.setDate(next.getDate() + down);\n break;\n case \"PageUp\":\n next.setTime(addMonthsClamped(next, -1).getTime());\n break;\n case \"PageDown\":\n next.setTime(addMonthsClamped(next, 1).getTime());\n break;\n case \"Home\":\n next.setTime(days[0].getTime());\n break;\n case \"End\":\n next.setTime(days[days.length - 1].getTime());\n break;\n default:\n return;\n }\n e.preventDefault();\n const iso = clampIso(toLocalIso(next));\n if (iso === activeIso) return;\n pendingFocus.current = iso;\n setActiveIso(iso);\n };\n\n const legendLevels = Array.from({ length: levels + 1 }, (_, l) => l);\n const empty = emptyColor ?? EMPTY_FILL;\n const continuous = colorFrom !== undefined && colorTo !== undefined;\n const hexRamp = continuous && parseHex(colorFrom) != null && parseHex(colorTo) != null;\n\n /** A day's fill, and the ink its number is set in (`\"month\"`). */\n const paint = (value: number, level: number): { fill: string; ink: string } => {\n if (level === 0) return { fill: empty, ink: \"var(--text-primary)\" };\n if (fillProp || continuous) {\n const t = max > 0 ? Math.min(1, Math.max(0, value / max)) : 1;\n const fill = fillProp\n ? fillProp(value, max)\n : hexRamp\n ? lerpHex(colorFrom!, colorTo!, t)\n : `color-mix(in oklab, ${colorTo} ${Math.round(t * 100)}%, ${colorFrom})`;\n // Measured where the fill is a colour; a mix of references cannot be measured,\n // and the page's own text colour is the safe answer there.\n const measured = textOn(fill);\n return { fill, ink: measured === \"currentColor\" ? \"var(--text-primary)\" : measured };\n }\n // Light text once the fill is the stronger half of the mix — `--bg-surface` is the\n // colour the scale is mixed INTO, so it is the one that contrasts with its far end\n // on either theme.\n const strong = levelPercent(level, levels) >= 45;\n return {\n fill: levelFill(level, levels, color),\n ink: strong ? \"var(--bg-surface)\" : \"var(--text-primary)\",\n };\n };\n // The legend's swatches: a value that lands on each step (the top one on `max`).\n const legendFill = (l: number) => paint(((max > 0 ? max : 1) * l) / levels, l).fill;\n\n const cell = (d: Date, wrapClass: string, cellClass: string, withNumber: boolean) => {\n const iso = toLocalIso(d);\n const raw = values.get(iso);\n const value = raw ?? 0;\n const level = heatmapLevel(value, max, levels);\n const { fill, ink } = paint(value, level);\n const formattedDate = formatDay(d);\n const formattedValue = formatValue(value);\n const name = labels.day(formattedDate, formattedValue);\n const label = tooltip\n ? tooltip({ iso, date: d, value, hasData: raw !== undefined, level, formattedDate, formattedValue })\n : name;\n const isSelected = selected === iso;\n const shared = {\n role: \"gridcell\",\n \"data-day\": iso,\n \"data-level\": level,\n tabIndex: iso === activeIso ? 0 : -1,\n \"aria-label\": name,\n \"aria-selected\": onSelect ? isSelected : undefined,\n onKeyDown: move,\n onFocus: () => {\n if (iso !== activeIso) setActiveIso(iso);\n },\n style: { background: fill, color: ink },\n className: cn(\n cellClass,\n isSelected && \"ring-2 ring-[var(--text-primary)]\",\n onSelect && \"cursor-pointer hover:ring-1 hover:ring-[var(--brand)]\",\n ),\n } as const;\n const inner = withNumber ? (\n <span aria-hidden className=\"text-[11px] leading-none tabular-nums\">\n {numberFormat.format(d.getDate())}\n </span>\n ) : null;\n return (\n <Tooltip key={iso} label={label} portal redact={sensitive} role=\"none\" className={wrapClass}>\n {onSelect ? (\n <button type=\"button\" {...shared} onClick={() => onSelect(iso)}>\n {inner}\n </button>\n ) : (\n <div {...shared}>{inner}</div>\n )}\n </Tooltip>\n );\n };\n\n const gridName = ariaLabel ?? labels.grid;\n let body: ReactNode;\n if (layout === \"weeks\") {\n const columns = { gridTemplateColumns: `var(--heatmap-label, 2rem) repeat(${weekCount}, var(--heatmap-cell, 0.75rem))` } as CSSProperties;\n const cellSize = \"size-[var(--heatmap-cell,0.75rem)]\";\n // A month's name over the first column that holds its 1st — or over the first\n // column, when the window opens far enough before the next month for it to fit.\n const monthLabels = Array.from({ length: weekCount }, (_, w) => {\n for (let r = 0; r < 7; r++) {\n const d = dayAt(w * 7 + r);\n if (d && (d.getDate() === 1 || (w === 0 && r === lead && d.getDate() <= 14))) {\n return d.toLocaleDateString(locale, { month: \"short\" });\n }\n }\n return \"\";\n });\n body = (\n <div ref={scrollerRef} className=\"overflow-x-auto pb-1\">\n <div ref={gridRef} role=\"grid\" aria-label={gridName} className=\"inline-grid gap-0.5\">\n {/* Decoration: every cell's name already carries its month. */}\n <div aria-hidden className=\"grid gap-0.5 text-[10px] leading-3 text-[var(--text-muted)]\" style={columns}>\n <span />\n {monthLabels.map((m, w) => (\n <span key={w} className=\"overflow-visible whitespace-nowrap\">\n {m}\n </span>\n ))}\n </div>\n {Array.from({ length: 7 }, (_, r) => (\n <div key={r} role=\"row\" className=\"grid gap-0.5\" style={columns}>\n <div\n role=\"rowheader\"\n aria-label={weekdayLabels[r].long}\n className=\"overflow-hidden pe-1 text-[10px] leading-3 text-[var(--text-muted)]\"\n >\n {/* Every other weekday, as a contribution graph does: seven labels\n at this size are a smear. The name is there for all seven. */}\n {r % 2 === 1 ? weekdayLabels[r].short : \"\"}\n </div>\n {Array.from({ length: weekCount }, (_, w) => {\n const d = dayAt(w * 7 + r);\n return d ? (\n cell(d, cellSize, cn(cellSize, \"block rounded-sm\"), false)\n ) : (\n <div key={`pad-${w}`} role=\"gridcell\" className={cellSize} />\n );\n })}\n </div>\n ))}\n </div>\n </div>\n );\n } else {\n const rows = Array.from({ length: weekCount }, (_, w) => w);\n body = (\n <div ref={gridRef} role=\"grid\" aria-label={gridName} className=\"grid gap-1\">\n <div role=\"row\" className=\"grid grid-cols-7 gap-1\">\n {weekdayLabels.map((w, i) => (\n <div\n key={i}\n role=\"columnheader\"\n aria-label={w.long}\n className=\"pb-0.5 text-center text-[10px] uppercase tracking-wide text-[var(--text-muted)]\"\n >\n {w.short}\n </div>\n ))}\n </div>\n {rows.map((w) => (\n <div key={w} role=\"row\" className=\"grid grid-cols-7 gap-1\">\n {Array.from({ length: 7 }, (_, c) => {\n const d = dayAt(w * 7 + c);\n return d ? (\n cell(d, \"aspect-square w-full\", \"flex h-full w-full items-center justify-center rounded\", true)\n ) : (\n <div key={`pad-${c}`} role=\"gridcell\" className=\"aspect-square\" />\n );\n })}\n </div>\n ))}\n </div>\n );\n }\n\n return (\n <div {...rest} className={cn(\"min-w-0\", className)}>\n {dropped > 0 && (\n <div className=\"mb-2 text-xs text-[var(--text-muted)]\">{labels.truncated(dropped)}</div>\n )}\n {body}\n {legend && (\n <div aria-hidden className=\"mt-2 flex items-center justify-end gap-1 text-[10px] text-[var(--text-muted)]\">\n <span className=\"me-0.5\">{labels.less}</span>\n {legendLevels.map((l) => (\n <span\n key={l}\n data-legend-level={l}\n className=\"size-2.5 rounded-sm\"\n style={{ background: legendFill(l) }}\n />\n ))}\n <span className=\"ms-0.5\">{labels.more}</span>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":["_"]}
1
+ {"version":3,"sources":["../../src/components/calendar-heatmap.tsx"],"sourcesContent":["import { useEffect, useLayoutEffect, useMemo, useRef, useState } from \"react\";\nimport type { ComponentPropsWithoutRef, CSSProperties, KeyboardEvent, ReactNode } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { dirOf, horizontalStep } from \"../lib/direction\";\nimport { addMonthsClamped, localeWeekStart, parseIsoDate, toLocalIso } from \"../lib/dates\";\nimport { useKitLabels, useKitLocale, useKitWeekStart } from \"../i18n/kit-labels\";\nimport { lerpHex, textOn } from \"../theme/chart-palette\";\nimport { parseHex } from \"../theme/color\";\nimport { Tooltip } from \"./tooltip\";\nimport type { WeekDay } from \"./mini-calendar\";\n\n/**\n * Every string the heatmap speaks — the `calendarHeatmap` namespace of\n * `<UiKitProvider labels>`, overridable per instance through `labels`. Dates and\n * values arrive already formatted (in `locale`, and through `formatValue`), so the\n * functions only arrange them.\n */\nexport interface CalendarHeatmapLabels {\n /** Accessible name of the grid. */\n grid: string;\n /**\n * One day: its accessible name, and the tooltip unless `tooltip` is given. `date`\n * is the whole date in `locale` (\"Monday, 14 September 2026\"), `value` the day's\n * figure through `formatValue`.\n */\n day: (date: string, value: string) => string;\n /** The two ends of the legend's scale. */\n less: string;\n more: string;\n /** `maxDays` cut the window: how many of its earliest days are not drawn. */\n truncated: (count: number) => string;\n}\n\n/** English defaults, exported as the `calendarHeatmap` namespace of `UiKitLabels`. */\nexport const DEFAULT_CALENDAR_HEATMAP_LABELS: CalendarHeatmapLabels = {\n grid: \"Daily values\",\n day: (date, value) => `${date}: ${value}`,\n less: \"Less\",\n more: \"More\",\n truncated: (count) =>\n `Showing the most recent days; ${count} earlier ${count === 1 ? \"day is\" : \"days are\"} not shown.`,\n};\n\n/** One day's figure. Several entries for one date are added up. */\nexport interface CalendarHeatmapDatum {\n /** \"YYYY-MM-DD\". */\n date: string;\n value: number;\n}\n\n/** What `tooltip` is told about the day under the pointer. */\nexport interface CalendarHeatmapDay {\n iso: string;\n date: Date;\n /** The day's figure; `0` when `data` has no entry for it (see `hasData`). */\n value: number;\n hasData: boolean;\n /** `0` = empty … `levels` = the busiest. */\n level: number;\n /** The whole date in `locale`. */\n formattedDate: string;\n /** `value` through `formatValue`. */\n formattedValue: string;\n}\n\n/**\n * `onSelect` is omitted from the `<div>` attributes for the reason `MiniCalendar` gives:\n * the DOM event of that name is a text-selection event, and this one answers with a date.\n */\nexport interface CalendarHeatmapProps extends Omit<ComponentPropsWithoutRef<\"div\">, \"onSelect\"> {\n data: readonly CalendarHeatmapDatum[];\n /** The window, inclusive, as \"YYYY-MM-DD\". */\n from: string;\n to: string;\n /**\n * `\"weeks\"` (default): the contribution-graph shape — one COLUMN per week, the\n * weekdays down the side, month names along the top; a year fits in ~53 small\n * squares and scrolls sideways where it does not. `\"month\"`: a 7-column calendar\n * with day numbers in large square cells, for a single month on a phone (switch on\n * `useMediaQuery` and narrow `from`/`to` to the month).\n */\n layout?: \"weeks\" | \"month\";\n /** The day shown as selected (\"YYYY-MM-DD\"). */\n selected?: string;\n /** Makes each day a button: a click (or Enter/Space) selects it — a drill-down into\n * that day's rows, a day panel. Without it the days are focusable but inert. */\n onSelect?: (iso: string) => void;\n /** How a value is written in the tooltip and the day's name. Default: `Intl.NumberFormat`\n * in `locale`. Pass a currency formatter for money. */\n formatValue?: (value: number) => string;\n /** Tooltip content for a day. Default: `labels.day(date, value)`. */\n tooltip?: (day: CalendarHeatmapDay) => ReactNode;\n /**\n * Tag each tooltip `data-private`, so the host's demo-mode rule blurs it (the\n * attribute `Tooltip redact` and `StatTile sensitive` carry). ON by default: a heatmap\n * is almost always the user's own figures, and a missing tag is silent — the grid\n * looks anonymous and the bubble spells the amount out on hover.\n */\n sensitive?: boolean;\n /** The value that reaches the top of the scale. Default: the largest value ON SCREEN,\n * so a peak outside the window does not wash the drawn days out to empty. */\n max?: number;\n /** How many non-empty intensity steps. Default 4. */\n levels?: number;\n /** The scale's colour, as any CSS colour. Default `var(--brand)`; each step is this\n * mixed into `--bg-surface`, so the ramp follows the theme. */\n color?: string;\n /**\n * A CONTINUOUS scale from `colorFrom` (the smallest value) to `colorTo` (the busiest\n * day), instead of `levels` steps of `color` — keksdose's palette-store ramp,\n * `seqLow` → `seqHigh`. Both are needed; `color` is then unused. Two `#rrggbb`\n * colours are interpolated in JS, so the fill is a concrete hex and the day number's\n * ink (in `\"month\"`) is MEASURED against it; anything else (a `var()`) is mixed with\n * `color-mix()`. `levels` still sets `data-level` and the legend's swatch count.\n */\n colorFrom?: string;\n colorTo?: string;\n /**\n * A fill of the caller's own for a day with a value: handed the value and the\n * scale's `max`, returns any CSS colour. Wins over `color` and `colorFrom`/`colorTo`.\n * Also paints the legend, at `max × level / levels`.\n */\n fill?: (value: number, max: number) => string;\n /** The fill of a day with no value (or a value of 0). Default: a wash of the text\n * colour over the surface, which reads as a cell on both themes. */\n emptyColor?: string;\n /**\n * Where the window ENDS — the day `maxDays` counts back from, and the last one drawn:\n *\n * - `\"to\"` (default): on `to`.\n * - `\"today\"`: on today.\n * - `\"latest\"`: on the last day that can carry a figure — today, or a later day that\n * actually has a value. A window running into the future (a report's \"all\" preset\n * ends a year ahead) then spends its cells on the days a person is looking for,\n * not on a year of empty ones.\n * - a `\"YYYY-MM-DD\"`: on that day.\n *\n * Always kept inside `from`…`to`.\n */\n anchor?: \"to\" | \"today\" | \"latest\" | (string & {});\n /**\n * The most days to draw. A longer window keeps its LATEST `maxDays` and says how many\n * it left out (`labels.truncated`) rather than silently ending. Default: no cap.\n */\n maxDays?: number;\n /** Draw the Less … More legend. Default `true`. */\n legend?: boolean;\n /** BCP 47 tag for the dates, month and weekday names and numbers. */\n locale?: string;\n /** `0` = Sunday … `6` = Saturday. Default: the provider's, else the locale's. */\n weekStartsOn?: WeekDay;\n labels?: Partial<CalendarHeatmapLabels>;\n className?: string;\n}\n\nconst DAY_NAME_FORMAT: Intl.DateTimeFormatOptions = {\n weekday: \"long\",\n day: \"numeric\",\n month: \"long\",\n year: \"numeric\",\n};\n\n/** The empty day: a wash of the text colour, like `--bg-hover`, so it reads as a cell\n * on both themes without claiming any value. */\nconst EMPTY_FILL = \"color-mix(in oklab, var(--text-primary) 8%, var(--bg-surface))\";\n\nconst weekdayIndex = (d: Date, weekStart: WeekDay) => (d.getDay() - weekStart + 7) % 7;\n\n/** Where the window ends for an {@link CalendarHeatmapProps.anchor}, kept in `[from, to]`.\n * `latestData` is the latest ISO date carrying a value, or `\"\"`. */\nexport function heatmapWindowEnd(\n from: string,\n to: string,\n anchor: string,\n latestData: string,\n today: string = toLocalIso(new Date()),\n): string {\n let end: string;\n if (anchor === \"today\") end = today;\n else if (anchor === \"latest\") end = latestData > today ? latestData : today;\n else if (anchor === \"to\" || !parseIsoDate(anchor)) end = to;\n else end = anchor;\n // ISO dates sort as strings, so the clamp is plain comparison.\n if (end > to) end = to;\n if (end < from) end = from;\n return end;\n}\n\n/** The step a value falls on: 0 for nothing, else 1…levels by its share of `max`. */\nexport function heatmapLevel(value: number, max: number, levels: number): number {\n if (!(value > 0) || !(max > 0) || levels < 1) return 0;\n return Math.min(levels, Math.max(1, Math.ceil((value / max) * levels)));\n}\n\n/** How much of `color` a step mixes into the surface: 20% for the first step, 100% for\n * the last — the floor keeps the smallest value visibly apart from an empty day. */\nfunction levelPercent(level: number, levels: number): number {\n if (levels <= 1) return 100;\n return Math.round(20 + (80 * (level - 1)) / (levels - 1));\n}\n\nfunction levelFill(level: number, levels: number, color: string): string {\n if (level === 0) return EMPTY_FILL;\n return `color-mix(in oklab, ${color} ${levelPercent(level, levels)}%, var(--bg-surface))`;\n}\n\n/**\n * Days as a grid of shaded squares — how much happened on each, at a glance\n * (a contribution graph, a spending calendar).\n *\n * **Every day is a cell you can reach.** The grid is a `role=\"grid\"` with ONE tab\n * stop (the roving tabindex, as in `MiniCalendar`); the arrows move by the grid's own\n * geometry — in `\"weeks\"` ↑/↓ are the previous/next day and ←/→ a week, in `\"month\"`\n * the other way round, mirrored in RTL — PageUp/PageDown move a month and Home/End go\n * to the window's first/last day. Each cell is named with its whole date AND its value,\n * because the colour is not something a screen reader can say, and carries\n * `data-day=\"YYYY-MM-DD\"` as its identity for tests and for the host. Note that in\n * `\"weeks\"` the cells are in DOM order by WEEKDAY ROW (all Mondays, then all\n * Tuesdays…), not by date — query by `data-day`, not by position.\n *\n * The tooltip is portalled (a year of squares always sits in a sideways scroller) and\n * `data-private` unless `sensitive={false}`. Cell size in `\"weeks\"` is the CSS variable\n * `--heatmap-cell` (default `0.75rem`): `style={{ \"--heatmap-cell\": \"1rem\" }}`.\n */\nexport function CalendarHeatmap({\n data,\n from,\n to,\n layout = \"weeks\",\n selected,\n onSelect,\n formatValue: formatValueProp,\n tooltip,\n sensitive = true,\n max: maxProp,\n levels = 4,\n color = \"var(--brand)\",\n colorFrom,\n colorTo,\n fill: fillProp,\n emptyColor,\n anchor = \"to\",\n maxDays,\n legend = true,\n locale: localeProp,\n weekStartsOn,\n labels: labelsProp,\n className,\n \"aria-label\": ariaLabel,\n ...rest\n}: CalendarHeatmapProps) {\n const labels = useKitLabels(\"calendarHeatmap\", DEFAULT_CALENDAR_HEATMAP_LABELS, labelsProp);\n const locale = useKitLocale(localeProp);\n const providerWeekStart = useKitWeekStart();\n const weekStart: WeekDay = weekStartsOn ?? providerWeekStart ?? localeWeekStart(locale);\n const numberFormat = useMemo(() => new Intl.NumberFormat(locale), [locale]);\n const formatValue = formatValueProp ?? ((v: number) => numberFormat.format(v));\n\n const values = useMemo(() => {\n const m = new Map<string, number>();\n for (const p of data) m.set(p.date, (m.get(p.date) ?? 0) + p.value);\n return m;\n }, [data]);\n\n // The window, trimmed from the FRONT: the days a person looks for are the recent\n // ones, and a cap that kept the oldest would end the calendar before today.\n const latestData = useMemo(() => {\n let latest = \"\";\n for (const [iso, v] of values) if (v !== 0 && iso > latest) latest = iso;\n return latest;\n }, [values]);\n const windowEnd = heatmapWindowEnd(from, to, anchor, latestData);\n\n const { days, dropped } = useMemo(() => {\n const start = parseIsoDate(from);\n const end = parseIsoDate(windowEnd);\n const list: Date[] = [];\n if (!start || !end || end < start) return { days: list, dropped: 0 };\n const cursor = new Date(end);\n const cap = maxDays && maxDays > 0 ? maxDays : Infinity;\n while (cursor >= start && list.length < cap) {\n list.unshift(new Date(cursor));\n cursor.setDate(cursor.getDate() - 1);\n }\n const first = list[0];\n const trimmed = first ? Math.round((first.getTime() - start.getTime()) / 86_400_000) : 0;\n return { days: list, dropped: Math.max(0, trimmed) };\n }, [from, windowEnd, maxDays]);\n\n const firstIso = days.length ? toLocalIso(days[0]) : \"\";\n const lastIso = days.length ? toLocalIso(days[days.length - 1]) : \"\";\n\n const max = useMemo(() => {\n if (maxProp !== undefined) return maxProp;\n let m = 0;\n for (const d of days) m = Math.max(m, values.get(toLocalIso(d)) ?? 0);\n return m;\n }, [maxProp, days, values]);\n\n const clampIso = (iso: string) => (iso < firstIso ? firstIso : iso > lastIso ? lastIso : iso);\n\n // The roving tab stop: the selected day, else the latest one.\n const [activeState, setActiveIso] = useState(() => selected || lastIso);\n const [lastSelected, setLastSelected] = useState(selected);\n if (lastSelected !== selected) {\n setLastSelected(selected);\n if (selected) setActiveIso(selected);\n }\n const activeIso = days.length ? clampIso(activeState || lastIso) : \"\";\n\n const gridRef = useRef<HTMLDivElement>(null);\n const scrollerRef = useRef<HTMLDivElement>(null);\n const pendingFocus = useRef<string | null>(null);\n useEffect(() => {\n const iso = pendingFocus.current;\n if (!iso) return;\n pendingFocus.current = null;\n gridRef.current?.querySelector<HTMLElement>(`[data-day=\"${iso}\"]`)?.focus();\n });\n\n // Open the sideways scroller on the latest weeks, not the oldest. In RTL the end is\n // on the left, where browsers count scrollLeft down from 0.\n //\n // And KEEP it there while the box changes size — a sidebar or a contents rail that\n // appears after mount narrows the scroller, which leaves scrollLeft where it was and\n // so shows older weeks than it opened on. The pin holds only while the view sits at\n // the end: a user who scrolls back through the year unpins it, and a resize then\n // leaves their position alone. (Scrolling back to the end pins it again.)\n useLayoutEffect(() => {\n const el = scrollerRef.current;\n if (!el || layout !== \"weeks\") return;\n const toEnd = () => {\n el.scrollLeft = dirOf(el) === \"rtl\" ? -el.scrollWidth : el.scrollWidth;\n };\n toEnd();\n if (typeof ResizeObserver === \"undefined\") return;\n // |scrollLeft| covers both directions: 0 → max in LTR, 0 → -max in RTL.\n let pinned = true;\n const onScroll = () => {\n pinned = Math.abs(el.scrollLeft) >= el.scrollWidth - el.clientWidth - 1;\n };\n const ro = new ResizeObserver(() => {\n if (pinned) toEnd();\n });\n el.addEventListener(\"scroll\", onScroll, { passive: true });\n ro.observe(el);\n return () => {\n ro.disconnect();\n el.removeEventListener(\"scroll\", onScroll);\n };\n }, [layout, firstIso, lastIso]);\n\n const formatDay = (d: Date) => d.toLocaleDateString(locale, DAY_NAME_FORMAT);\n\n const weekdayLabels = useMemo(() => {\n const ref = new Date(2024, 0, 7 + weekStart); // 7 January 2024 was a Sunday\n const week = Array.from({ length: 7 }, (_, i) => {\n const d = new Date(ref);\n d.setDate(ref.getDate() + i);\n return d;\n });\n const name = (d: Date, weekday: \"short\" | \"narrow\" | \"long\") => d.toLocaleDateString(locale, { weekday });\n // The label column is 2rem at 10px type, room for about four characters. Most\n // locales' \"short\" is three or four (\"Thu\", \"jeu.\", \"czw.\"), but some have no\n // abbreviation at all — Arabic's short Thursday IS \"الخميس\", which overran the box\n // and was cut. Such a locale takes the \"narrow\" form (\"خ\") for ALL seven, so the\n // column never mixes two forms; the full name is on the aria-label either way.\n const tooLong = week.some((d) => [...name(d, \"short\")].length > 4);\n return week.map((d) => ({ short: name(d, tooLong ? \"narrow\" : \"short\"), long: name(d, \"long\") }));\n }, [locale, weekStart]);\n\n const lead = days.length ? weekdayIndex(days[0], weekStart) : 0;\n const weekCount = Math.ceil((lead + days.length) / 7);\n /** The day at position `i` of the week-aligned sequence (blanks before the first). */\n const dayAt = (i: number): Date | null => (i >= lead && i - lead < days.length ? days[i - lead] : null);\n\n const move = (e: KeyboardEvent<HTMLElement>) => {\n const current = parseIsoDate(activeIso);\n if (!current) return;\n const next = new Date(current);\n const step = horizontalStep(e.key, e.currentTarget);\n // In \"weeks\" a row is a weekday, so sideways is a week and up/down is a day.\n const across = layout === \"weeks\" ? 7 : 1;\n const down = layout === \"weeks\" ? 1 : 7;\n switch (step ? \"horizontal\" : e.key) {\n case \"horizontal\":\n next.setDate(next.getDate() + step * across);\n break;\n case \"ArrowUp\":\n next.setDate(next.getDate() - down);\n break;\n case \"ArrowDown\":\n next.setDate(next.getDate() + down);\n break;\n case \"PageUp\":\n next.setTime(addMonthsClamped(next, -1).getTime());\n break;\n case \"PageDown\":\n next.setTime(addMonthsClamped(next, 1).getTime());\n break;\n case \"Home\":\n next.setTime(days[0].getTime());\n break;\n case \"End\":\n next.setTime(days[days.length - 1].getTime());\n break;\n default:\n return;\n }\n e.preventDefault();\n const iso = clampIso(toLocalIso(next));\n if (iso === activeIso) return;\n pendingFocus.current = iso;\n setActiveIso(iso);\n };\n\n const legendLevels = Array.from({ length: levels + 1 }, (_, l) => l);\n const empty = emptyColor ?? EMPTY_FILL;\n const continuous = colorFrom !== undefined && colorTo !== undefined;\n const hexRamp = continuous && parseHex(colorFrom) != null && parseHex(colorTo) != null;\n\n /** A day's fill, and the ink its number is set in (`\"month\"`). */\n const paint = (value: number, level: number): { fill: string; ink: string } => {\n if (level === 0) return { fill: empty, ink: \"var(--text-primary)\" };\n if (fillProp || continuous) {\n const t = max > 0 ? Math.min(1, Math.max(0, value / max)) : 1;\n const fill = fillProp\n ? fillProp(value, max)\n : hexRamp\n ? lerpHex(colorFrom!, colorTo!, t)\n : `color-mix(in oklab, ${colorTo} ${Math.round(t * 100)}%, ${colorFrom})`;\n // Measured where the fill is a colour; a mix of references cannot be measured,\n // and the page's own text colour is the safe answer there.\n const measured = textOn(fill);\n return { fill, ink: measured === \"currentColor\" ? \"var(--text-primary)\" : measured };\n }\n // Light text once the fill is the stronger half of the mix — `--bg-surface` is the\n // colour the scale is mixed INTO, so it is the one that contrasts with its far end\n // on either theme.\n const strong = levelPercent(level, levels) >= 45;\n return {\n fill: levelFill(level, levels, color),\n ink: strong ? \"var(--bg-surface)\" : \"var(--text-primary)\",\n };\n };\n // The legend's swatches: a value that lands on each step (the top one on `max`).\n const legendFill = (l: number) => paint(((max > 0 ? max : 1) * l) / levels, l).fill;\n\n const cell = (d: Date, wrapClass: string, cellClass: string, withNumber: boolean) => {\n const iso = toLocalIso(d);\n const raw = values.get(iso);\n const value = raw ?? 0;\n const level = heatmapLevel(value, max, levels);\n const { fill, ink } = paint(value, level);\n const formattedDate = formatDay(d);\n const formattedValue = formatValue(value);\n const name = labels.day(formattedDate, formattedValue);\n const label = tooltip\n ? tooltip({ iso, date: d, value, hasData: raw !== undefined, level, formattedDate, formattedValue })\n : name;\n const isSelected = selected === iso;\n const shared = {\n role: \"gridcell\",\n \"data-day\": iso,\n \"data-level\": level,\n tabIndex: iso === activeIso ? 0 : -1,\n \"aria-label\": name,\n \"aria-selected\": onSelect ? isSelected : undefined,\n onKeyDown: move,\n onFocus: () => {\n if (iso !== activeIso) setActiveIso(iso);\n },\n style: { background: fill, color: ink },\n className: cn(\n cellClass,\n isSelected && \"ring-2 ring-[var(--text-primary)]\",\n onSelect && \"cursor-pointer hover:ring-1 hover:ring-[var(--brand)]\",\n ),\n } as const;\n const inner = withNumber ? (\n <span aria-hidden className=\"text-[11px] leading-none tabular-nums\">\n {numberFormat.format(d.getDate())}\n </span>\n ) : null;\n return (\n <Tooltip key={iso} label={label} portal redact={sensitive} role=\"none\" className={wrapClass}>\n {onSelect ? (\n <button type=\"button\" {...shared} onClick={() => onSelect(iso)}>\n {inner}\n </button>\n ) : (\n <div {...shared}>{inner}</div>\n )}\n </Tooltip>\n );\n };\n\n const gridName = ariaLabel ?? labels.grid;\n let body: ReactNode;\n if (layout === \"weeks\") {\n const columns = { gridTemplateColumns: `var(--heatmap-label, 2rem) repeat(${weekCount}, var(--heatmap-cell, 0.75rem))` } as CSSProperties;\n const cellSize = \"size-[var(--heatmap-cell,0.75rem)]\";\n // A month's name over the first column that holds its 1st — or over the first\n // column, when the window opens far enough before the next month for it to fit.\n const monthLabels = Array.from({ length: weekCount }, (_, w) => {\n for (let r = 0; r < 7; r++) {\n const d = dayAt(w * 7 + r);\n if (d && (d.getDate() === 1 || (w === 0 && r === lead && d.getDate() <= 14))) {\n return d.toLocaleDateString(locale, { month: \"short\" });\n }\n }\n return \"\";\n });\n body = (\n <div ref={scrollerRef} className=\"overflow-x-auto pb-1\">\n <div ref={gridRef} role=\"grid\" aria-label={gridName} className=\"inline-grid gap-0.5\">\n {/* Decoration: every cell's name already carries its month. */}\n <div aria-hidden className=\"grid gap-0.5 text-[10px] leading-3 text-[var(--text-muted)]\" style={columns}>\n <span />\n {monthLabels.map((m, w) => (\n <span key={w} className=\"overflow-visible whitespace-nowrap\">\n {m}\n </span>\n ))}\n </div>\n {Array.from({ length: 7 }, (_, r) => (\n <div key={r} role=\"row\" className=\"grid gap-0.5\" style={columns}>\n <div\n role=\"rowheader\"\n aria-label={weekdayLabels[r].long}\n className=\"overflow-hidden pe-1 text-[10px] leading-3 text-[var(--text-muted)]\"\n >\n {/* Every other weekday, as a contribution graph does: seven labels\n at this size are a smear. The name is there for all seven. */}\n {r % 2 === 1 ? weekdayLabels[r].short : \"\"}\n </div>\n {Array.from({ length: weekCount }, (_, w) => {\n const d = dayAt(w * 7 + r);\n return d ? (\n cell(d, cellSize, cn(cellSize, \"block rounded-sm\"), false)\n ) : (\n <div key={`pad-${w}`} role=\"gridcell\" className={cellSize} />\n );\n })}\n </div>\n ))}\n </div>\n </div>\n );\n } else {\n const rows = Array.from({ length: weekCount }, (_, w) => w);\n body = (\n <div ref={gridRef} role=\"grid\" aria-label={gridName} className=\"grid gap-1\">\n <div role=\"row\" className=\"grid grid-cols-7 gap-1\">\n {weekdayLabels.map((w, i) => (\n <div\n key={i}\n role=\"columnheader\"\n aria-label={w.long}\n className=\"pb-0.5 text-center text-[10px] uppercase tracking-wide text-[var(--text-muted)]\"\n >\n {w.short}\n </div>\n ))}\n </div>\n {rows.map((w) => (\n <div key={w} role=\"row\" className=\"grid grid-cols-7 gap-1\">\n {Array.from({ length: 7 }, (_, c) => {\n const d = dayAt(w * 7 + c);\n return d ? (\n cell(d, \"aspect-square w-full\", \"flex h-full w-full items-center justify-center rounded\", true)\n ) : (\n <div key={`pad-${c}`} role=\"gridcell\" className=\"aspect-square\" />\n );\n })}\n </div>\n ))}\n </div>\n );\n }\n\n return (\n <div {...rest} className={cn(\"min-w-0\", className)}>\n {dropped > 0 && (\n <div className=\"mb-2 text-xs text-[var(--text-muted)]\">{labels.truncated(dropped)}</div>\n )}\n {body}\n {legend && (\n <div aria-hidden className=\"mt-2 flex items-center justify-end gap-1 text-[10px] text-[var(--text-muted)]\">\n <span className=\"me-0.5\">{labels.less}</span>\n {legendLevels.map((l) => (\n <span\n key={l}\n data-legend-level={l}\n className=\"size-2.5 rounded-sm\"\n style={{ background: legendFill(l) }}\n />\n ))}\n <span className=\"ms-0.5\">{labels.more}</span>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":["_"]}
@@ -1,5 +1,5 @@
1
1
  import 'react';
2
- export { x as CopyButton, y as CopyButtonLabels, z as CopyButtonProps, I as DEFAULT_COPY_BUTTON_LABELS } from '../kit-labels-Mz8zZHIF.js';
2
+ export { x as CopyButton, y as CopyButtonLabels, z as CopyButtonProps, I as DEFAULT_COPY_BUTTON_LABELS } from '../kit-labels-BHfRBbHs.js';
3
3
  import './tooltip.js';
4
4
  import './data-table-labels.js';
5
5
  import './mini-calendar.js';
@@ -1,7 +1,7 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
3
  import { DataTableLabels } from './data-table-labels.js';
4
- import { b as DataTableColumn } from '../data-table-Drun8Fk9.js';
4
+ import { b as DataTableColumn } from '../data-table-CfVXtCeG.js';
5
5
  import './data-table-sort.js';
6
6
  import './swipeable-row.js';
7
7
 
@@ -1,5 +1,5 @@
1
1
  import * as react from 'react';
2
- import { b as DataTableColumn, f as FilterValue } from '../data-table-Drun8Fk9.js';
2
+ import { b as DataTableColumn, f as FilterValue } from '../data-table-CfVXtCeG.js';
3
3
  import { DataTableLabels } from './data-table-labels.js';
4
4
  import './data-table-sort.js';
5
5
  import './swipeable-row.js';
@@ -111,8 +111,8 @@ function FilterPopover({
111
111
  const set = (from, to) => onChange({ type: "date", from, to });
112
112
  const presets = dateRangePresets();
113
113
  const isActivePreset = (p) => v2.from === p.from && v2.to === p.to;
114
- return /* @__PURE__ */ jsxs("div", { className: "flex gap-3", children: [
115
- /* @__PURE__ */ jsx("ul", { className: "flex w-28 shrink-0 flex-col gap-0.5 text-xs", children: presets.map((p) => {
114
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 sm:flex-row", children: [
115
+ /* @__PURE__ */ jsx("ul", { className: "flex flex-wrap gap-0.5 text-xs sm:w-28 sm:shrink-0 sm:flex-col sm:flex-nowrap", children: presets.map((p) => {
116
116
  const active = isActivePreset(p);
117
117
  return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
118
118
  "button",
@@ -120,7 +120,7 @@ function FilterPopover({
120
120
  type: "button",
121
121
  onClick: () => set(p.from, p.to),
122
122
  className: cn(
123
- "block w-full rounded px-2 py-1 text-left transition-colors",
123
+ "block rounded px-2 py-1 text-start transition-colors sm:w-full",
124
124
  active ? "bg-[var(--brand-bg)] text-[var(--brand-muted)]" : "text-[var(--text-secondary)] hover:bg-[var(--bg-hover)]"
125
125
  ),
126
126
  children: labels.presets[p.key] ?? p.key
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/data-table-filter-popover.tsx"],"sourcesContent":["import { useCallback } from \"react\";\nimport { X } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { MiniCalendar } from \"./mini-calendar\";\nimport { dateRangePresets } from \"../lib/dates\";\nimport { resolveFilter } from \"./data-table-filters\";\nimport type { FilterValue } from \"./data-table-filters\";\nimport type { DataTableColumn } from \"./data-table\";\nimport { resolveDataTableLabels, type DataTableLabels } from \"./data-table-labels\";\nimport { useKitLabelOverrides, useKitLocale } from \"../i18n/kit-labels\";\n\ninterface FilterPopoverProps<T> {\n column: DataTableColumn<T>;\n state: FilterValue;\n onChange: (next: FilterValue) => void;\n onClear: () => void;\n selectOptions: { value: string; label: string }[];\n /** BCP-47 locale for the date picker / labels. Falls back to the provider's. */\n locale?: string;\n /** Already resolved by {@link DataTable}. Standalone (it is exported), omit it and\n * the `dataTable` namespace of `<UiKitProvider labels>` is used. */\n labels?: DataTableLabels;\n /**\n * Focus the text filter's input on mount. True, because that is right for the one\n * place this component normally lives: a Popover opened by an explicit press on\n * \"Filter\", which exists in order to be typed into and which the user has just asked\n * for. It was hard-coded, which made it right there and wrong everywhere else — a\n * browser scrolls whatever takes focus into view, so embedding this control in an\n * ordinary page (the showcase documents it on one) landed the reader 25,000px down a\n * page they had not scrolled. An embedder that is not a popover passes `false`.\n */\n autoFocus?: boolean;\n}\n\n/**\n * The contents of a column's filter Popover. Renders the right control set for\n * the column's filter type — text search, select checklist, date range (presets\n * + {@link MiniCalendar} + from/to inputs), or numeric min/max with an abs\n * toggle — driving the shared {@link FilterValue} state.\n */\nexport function FilterPopover<T>({\n column,\n state,\n onChange,\n onClear,\n selectOptions,\n locale: localeProp,\n labels: labelsProp,\n autoFocus = true,\n}: FilterPopoverProps<T>) {\n // Both hooks run before the early return below, unconditionally.\n const overrides = useKitLabelOverrides(\"dataTable\");\n const labels = labelsProp ?? resolveDataTableLabels(overrides);\n // No `\"en\"` fallback any more: that pinned the calendar to English in a table that\n // was never handed a locale, under a provider that had one.\n const locale = useKitLocale(localeProp);\n // What `autoFocus` did, done by hand: a callback ref runs in the same commit phase\n // React's own autofocus does, once when the input mounts (it is stable across\n // renders, so a re-render does not re-take focus).\n const focusOnMount = useCallback(\n (el: HTMLInputElement | null) => {\n if (el && autoFocus) el.focus();\n },\n [autoFocus],\n );\n const filter = resolveFilter(column);\n if (!filter) return null;\n\n // `focus:outline-none` with only a 1px border tint to replace it is the same finding\n // the audit records against the numpad's keys: on a panel of four identical fields a\n // border going from --border to --border-strong is not a location. The ring is, and it\n // is the package's brand focus colour, as on every field in ui.tsx.\n const inputBase =\n \"block w-full rounded border border-[var(--border)] bg-[var(--bg-surface)] px-2 py-1.5 text-sm text-[var(--text-secondary)] placeholder:text-[var(--text-placeholder)] focus:border-[var(--border-strong)] focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--brand)]\";\n const buttonBase =\n \"rounded border border-[var(--border)] bg-[var(--bg-surface)] px-2 py-1 text-xs text-[var(--text-secondary)] hover:bg-[var(--bg-hover)]\";\n\n const clearRow = (\n <div className=\"flex justify-end pt-1\">\n <button type=\"button\" onClick={onClear} className={cn(buttonBase, \"flex items-center gap-1\")}>\n <X className=\"size-3\" /> {labels.clearFilter}\n </button>\n </div>\n );\n\n if (filter.type === \"text\") {\n const v = state.type === \"text\" ? state : { type: \"text\" as const, q: \"\" };\n return (\n <div className=\"space-y-2\">\n <input\n type=\"text\"\n // A prop now, defaulting to what the popover needs; see `autoFocus` on the\n // props above. Focused by `focusOnMount` rather than the `autoFocus`\n // attribute, because here taking focus is the default, not an opt-in.\n ref={focusOnMount}\n value={v.q}\n onChange={(e) => onChange({ type: \"text\", q: e.target.value })}\n placeholder={labels.filterPlaceholder}\n className={inputBase}\n />\n {clearRow}\n </div>\n );\n }\n\n if (filter.type === \"select\") {\n const v = state.type === \"select\" ? state : { type: \"select\" as const, values: [] };\n const toggle = (val: string) => {\n const set = new Set(v.values);\n if (set.has(val)) set.delete(val);\n else set.add(val);\n onChange({ type: \"select\", values: Array.from(set) });\n };\n return (\n <div className=\"space-y-2\">\n <div className=\"flex items-center justify-between text-xs text-[var(--text-muted)]\">\n <span>{labels.selectFilter}</span>\n <div className=\"flex gap-1\">\n <button\n type=\"button\"\n onClick={() => onChange({ type: \"select\", values: selectOptions.map((o) => o.value) })}\n className={buttonBase}\n >\n {labels.selectAll}\n </button>\n <button\n type=\"button\"\n onClick={() => onChange({ type: \"select\", values: [] })}\n className={buttonBase}\n >\n {labels.selectNone}\n </button>\n </div>\n </div>\n <ul className=\"max-h-56 overflow-y-auto rounded border border-[var(--border)]\">\n {selectOptions.map((o) => {\n const checked = v.values.includes(o.value);\n return (\n <li key={o.value}>\n <label className=\"flex cursor-pointer items-center gap-2 px-2 py-1.5 text-sm hover:bg-[var(--bg-hover)]\">\n <input\n type=\"checkbox\"\n checked={checked}\n onChange={() => toggle(o.value)}\n className=\"size-3.5\"\n />\n <span className=\"truncate\">{o.label}</span>\n </label>\n </li>\n );\n })}\n {selectOptions.length === 0 && (\n <li className=\"px-2 py-2 text-sm text-[var(--text-muted)]\">—</li>\n )}\n </ul>\n {clearRow}\n </div>\n );\n }\n\n if (filter.type === \"date\") {\n const v = state.type === \"date\" ? state : { type: \"date\" as const, from: \"\", to: \"\" };\n const set = (from: string, to: string) => onChange({ type: \"date\", from, to });\n const presets = dateRangePresets();\n const isActivePreset = (p: { from: string; to: string }) => v.from === p.from && v.to === p.to;\n return (\n <div className=\"flex gap-3\">\n <ul className=\"flex w-28 shrink-0 flex-col gap-0.5 text-xs\">\n {presets.map((p) => {\n const active = isActivePreset(p);\n return (\n <li key={p.key}>\n <button\n type=\"button\"\n onClick={() => set(p.from, p.to)}\n className={cn(\n \"block w-full rounded px-2 py-1 text-left transition-colors\",\n active\n ? \"bg-[var(--brand-bg)] text-[var(--brand-muted)]\"\n : \"text-[var(--text-secondary)] hover:bg-[var(--bg-hover)]\",\n )}\n >\n {labels.presets[p.key] ?? p.key}\n </button>\n </li>\n );\n })}\n </ul>\n <div className=\"min-w-0 flex-1 space-y-2\">\n {/* No `labels` here: the calendar reads `miniCalendar` from the provider\n itself, which is the only way a calendar nested this deep was ever going\n to be translated — the table has no prop to forward them through. */}\n <MiniCalendar from={v.from} to={v.to} locale={locale} onSelect={set} />\n <div className=\"grid grid-cols-2 gap-2\">\n <label className=\"text-xs text-[var(--text-muted)]\">\n {labels.dateFrom}\n <input\n type=\"date\"\n value={v.from}\n onChange={(e) => set(e.target.value, v.to)}\n className={cn(\"mt-0.5\", inputBase)}\n />\n </label>\n <label className=\"text-xs text-[var(--text-muted)]\">\n {labels.dateTo}\n <input\n type=\"date\"\n value={v.to}\n onChange={(e) => set(v.from, e.target.value)}\n className={cn(\"mt-0.5\", inputBase)}\n />\n </label>\n </div>\n {clearRow}\n </div>\n </div>\n );\n }\n\n // number\n const v = state.type === \"number\" ? state : { type: \"number\" as const, min: \"\", max: \"\", abs: false };\n return (\n <div className=\"space-y-2\">\n <div className=\"grid grid-cols-2 gap-2\">\n <label className=\"text-xs text-[var(--text-muted)]\">\n {labels.numberMin} (≥)\n <input\n type=\"number\"\n inputMode=\"decimal\"\n value={v.min}\n onChange={(e) => onChange({ type: \"number\", min: e.target.value, max: v.max, abs: v.abs })}\n onWheel={(e) => e.currentTarget.blur()}\n className={cn(\"mt-0.5\", inputBase)}\n />\n </label>\n <label className=\"text-xs text-[var(--text-muted)]\">\n {labels.numberMax} (≤)\n <input\n type=\"number\"\n inputMode=\"decimal\"\n value={v.max}\n onChange={(e) => onChange({ type: \"number\", min: v.min, max: e.target.value, abs: v.abs })}\n onWheel={(e) => e.currentTarget.blur()}\n className={cn(\"mt-0.5\", inputBase)}\n />\n </label>\n </div>\n <label className=\"flex items-center gap-2 text-sm text-[var(--text-secondary)]\">\n <input\n type=\"checkbox\"\n checked={v.abs}\n onChange={(e) => onChange({ type: \"number\", min: v.min, max: v.max, abs: e.target.checked })}\n className=\"size-3.5\"\n />\n {labels.numberAbs}\n </label>\n {clearRow}\n </div>\n );\n}\n"],"mappings":"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","names":["v"]}
1
+ {"version":3,"sources":["../../src/components/data-table-filter-popover.tsx"],"sourcesContent":["import { useCallback } from \"react\";\nimport { X } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { MiniCalendar } from \"./mini-calendar\";\nimport { dateRangePresets } from \"../lib/dates\";\nimport { resolveFilter } from \"./data-table-filters\";\nimport type { FilterValue } from \"./data-table-filters\";\nimport type { DataTableColumn } from \"./data-table\";\nimport { resolveDataTableLabels, type DataTableLabels } from \"./data-table-labels\";\nimport { useKitLabelOverrides, useKitLocale } from \"../i18n/kit-labels\";\n\ninterface FilterPopoverProps<T> {\n column: DataTableColumn<T>;\n state: FilterValue;\n onChange: (next: FilterValue) => void;\n onClear: () => void;\n selectOptions: { value: string; label: string }[];\n /** BCP-47 locale for the date picker / labels. Falls back to the provider's. */\n locale?: string;\n /** Already resolved by {@link DataTable}. Standalone (it is exported), omit it and\n * the `dataTable` namespace of `<UiKitProvider labels>` is used. */\n labels?: DataTableLabels;\n /**\n * Focus the text filter's input on mount. True, because that is right for the one\n * place this component normally lives: a Popover opened by an explicit press on\n * \"Filter\", which exists in order to be typed into and which the user has just asked\n * for. It was hard-coded, which made it right there and wrong everywhere else — a\n * browser scrolls whatever takes focus into view, so embedding this control in an\n * ordinary page (the showcase documents it on one) landed the reader 25,000px down a\n * page they had not scrolled. An embedder that is not a popover passes `false`.\n */\n autoFocus?: boolean;\n}\n\n/**\n * The contents of a column's filter Popover. Renders the right control set for\n * the column's filter type — text search, select checklist, date range (presets\n * + {@link MiniCalendar} + from/to inputs), or numeric min/max with an abs\n * toggle — driving the shared {@link FilterValue} state.\n */\nexport function FilterPopover<T>({\n column,\n state,\n onChange,\n onClear,\n selectOptions,\n locale: localeProp,\n labels: labelsProp,\n autoFocus = true,\n}: FilterPopoverProps<T>) {\n // Both hooks run before the early return below, unconditionally.\n const overrides = useKitLabelOverrides(\"dataTable\");\n const labels = labelsProp ?? resolveDataTableLabels(overrides);\n // No `\"en\"` fallback any more: that pinned the calendar to English in a table that\n // was never handed a locale, under a provider that had one.\n const locale = useKitLocale(localeProp);\n // What `autoFocus` did, done by hand: a callback ref runs in the same commit phase\n // React's own autofocus does, once when the input mounts (it is stable across\n // renders, so a re-render does not re-take focus).\n const focusOnMount = useCallback(\n (el: HTMLInputElement | null) => {\n if (el && autoFocus) el.focus();\n },\n [autoFocus],\n );\n const filter = resolveFilter(column);\n if (!filter) return null;\n\n // `focus:outline-none` with only a 1px border tint to replace it is the same finding\n // the audit records against the numpad's keys: on a panel of four identical fields a\n // border going from --border to --border-strong is not a location. The ring is, and it\n // is the package's brand focus colour, as on every field in ui.tsx.\n const inputBase =\n \"block w-full rounded border border-[var(--border)] bg-[var(--bg-surface)] px-2 py-1.5 text-sm text-[var(--text-secondary)] placeholder:text-[var(--text-placeholder)] focus:border-[var(--border-strong)] focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--brand)]\";\n const buttonBase =\n \"rounded border border-[var(--border)] bg-[var(--bg-surface)] px-2 py-1 text-xs text-[var(--text-secondary)] hover:bg-[var(--bg-hover)]\";\n\n const clearRow = (\n <div className=\"flex justify-end pt-1\">\n <button type=\"button\" onClick={onClear} className={cn(buttonBase, \"flex items-center gap-1\")}>\n <X className=\"size-3\" /> {labels.clearFilter}\n </button>\n </div>\n );\n\n if (filter.type === \"text\") {\n const v = state.type === \"text\" ? state : { type: \"text\" as const, q: \"\" };\n return (\n <div className=\"space-y-2\">\n <input\n type=\"text\"\n // A prop now, defaulting to what the popover needs; see `autoFocus` on the\n // props above. Focused by `focusOnMount` rather than the `autoFocus`\n // attribute, because here taking focus is the default, not an opt-in.\n ref={focusOnMount}\n value={v.q}\n onChange={(e) => onChange({ type: \"text\", q: e.target.value })}\n placeholder={labels.filterPlaceholder}\n className={inputBase}\n />\n {clearRow}\n </div>\n );\n }\n\n if (filter.type === \"select\") {\n const v = state.type === \"select\" ? state : { type: \"select\" as const, values: [] };\n const toggle = (val: string) => {\n const set = new Set(v.values);\n if (set.has(val)) set.delete(val);\n else set.add(val);\n onChange({ type: \"select\", values: Array.from(set) });\n };\n return (\n <div className=\"space-y-2\">\n <div className=\"flex items-center justify-between text-xs text-[var(--text-muted)]\">\n <span>{labels.selectFilter}</span>\n <div className=\"flex gap-1\">\n <button\n type=\"button\"\n onClick={() => onChange({ type: \"select\", values: selectOptions.map((o) => o.value) })}\n className={buttonBase}\n >\n {labels.selectAll}\n </button>\n <button\n type=\"button\"\n onClick={() => onChange({ type: \"select\", values: [] })}\n className={buttonBase}\n >\n {labels.selectNone}\n </button>\n </div>\n </div>\n <ul className=\"max-h-56 overflow-y-auto rounded border border-[var(--border)]\">\n {selectOptions.map((o) => {\n const checked = v.values.includes(o.value);\n return (\n <li key={o.value}>\n <label className=\"flex cursor-pointer items-center gap-2 px-2 py-1.5 text-sm hover:bg-[var(--bg-hover)]\">\n <input\n type=\"checkbox\"\n checked={checked}\n onChange={() => toggle(o.value)}\n className=\"size-3.5\"\n />\n <span className=\"truncate\">{o.label}</span>\n </label>\n </li>\n );\n })}\n {selectOptions.length === 0 && (\n <li className=\"px-2 py-2 text-sm text-[var(--text-muted)]\">—</li>\n )}\n </ul>\n {clearRow}\n </div>\n );\n }\n\n if (filter.type === \"date\") {\n const v = state.type === \"date\" ? state : { type: \"date\" as const, from: \"\", to: \"\" };\n const set = (from: string, to: string) => onChange({ type: \"date\", from, to });\n const presets = dateRangePresets();\n const isActivePreset = (p: { from: string; to: string }) => v.from === p.from && v.to === p.to;\n // Side by side, the 7rem preset column leaves a phone's calendar and its two\n // native date inputs ~180px, which clips \"mm/dd/yyyy\" mid-placeholder. Below `sm`\n // the presets wrap as a row above the calendar, which then gets the panel's width.\n return (\n <div className=\"flex flex-col gap-3 sm:flex-row\">\n <ul className=\"flex flex-wrap gap-0.5 text-xs sm:w-28 sm:shrink-0 sm:flex-col sm:flex-nowrap\">\n {presets.map((p) => {\n const active = isActivePreset(p);\n return (\n <li key={p.key}>\n <button\n type=\"button\"\n onClick={() => set(p.from, p.to)}\n className={cn(\n \"block rounded px-2 py-1 text-start transition-colors sm:w-full\",\n active\n ? \"bg-[var(--brand-bg)] text-[var(--brand-muted)]\"\n : \"text-[var(--text-secondary)] hover:bg-[var(--bg-hover)]\",\n )}\n >\n {labels.presets[p.key] ?? p.key}\n </button>\n </li>\n );\n })}\n </ul>\n <div className=\"min-w-0 flex-1 space-y-2\">\n {/* No `labels` here: the calendar reads `miniCalendar` from the provider\n itself, which is the only way a calendar nested this deep was ever going\n to be translated — the table has no prop to forward them through. */}\n <MiniCalendar from={v.from} to={v.to} locale={locale} onSelect={set} />\n <div className=\"grid grid-cols-2 gap-2\">\n <label className=\"text-xs text-[var(--text-muted)]\">\n {labels.dateFrom}\n <input\n type=\"date\"\n value={v.from}\n onChange={(e) => set(e.target.value, v.to)}\n className={cn(\"mt-0.5\", inputBase)}\n />\n </label>\n <label className=\"text-xs text-[var(--text-muted)]\">\n {labels.dateTo}\n <input\n type=\"date\"\n value={v.to}\n onChange={(e) => set(v.from, e.target.value)}\n className={cn(\"mt-0.5\", inputBase)}\n />\n </label>\n </div>\n {clearRow}\n </div>\n </div>\n );\n }\n\n // number\n const v = state.type === \"number\" ? state : { type: \"number\" as const, min: \"\", max: \"\", abs: false };\n return (\n <div className=\"space-y-2\">\n <div className=\"grid grid-cols-2 gap-2\">\n <label className=\"text-xs text-[var(--text-muted)]\">\n {labels.numberMin} (≥)\n <input\n type=\"number\"\n inputMode=\"decimal\"\n value={v.min}\n onChange={(e) => onChange({ type: \"number\", min: e.target.value, max: v.max, abs: v.abs })}\n onWheel={(e) => e.currentTarget.blur()}\n className={cn(\"mt-0.5\", inputBase)}\n />\n </label>\n <label className=\"text-xs text-[var(--text-muted)]\">\n {labels.numberMax} (≤)\n <input\n type=\"number\"\n inputMode=\"decimal\"\n value={v.max}\n onChange={(e) => onChange({ type: \"number\", min: v.min, max: e.target.value, abs: v.abs })}\n onWheel={(e) => e.currentTarget.blur()}\n className={cn(\"mt-0.5\", inputBase)}\n />\n </label>\n </div>\n <label className=\"flex items-center gap-2 text-sm text-[var(--text-secondary)]\">\n <input\n type=\"checkbox\"\n checked={v.abs}\n onChange={(e) => onChange({ type: \"number\", min: v.min, max: v.max, abs: e.target.checked })}\n className=\"size-3.5\"\n />\n {labels.numberAbs}\n </label>\n {clearRow}\n </div>\n );\n}\n"],"mappings":"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","names":["v"]}
@@ -1,4 +1,4 @@
1
- export { C as ColumnFilter, f as FilterValue, j as dateFilter, k as decodeFilterValue, l as decodeFilterValueOfType, m as defaultFilterState, n as encodeFilterValue, o as filterHref, p as isFilterActive, q as numberFilter, s as resolveFilter, t as rowMatches, u as selectFilter, v as textFilter } from '../data-table-Drun8Fk9.js';
1
+ export { C as ColumnFilter, f as FilterValue, j as dateFilter, k as decodeFilterValue, l as decodeFilterValueOfType, m as defaultFilterState, n as encodeFilterValue, o as filterHref, p as isFilterActive, q as numberFilter, s as resolveFilter, t as rowMatches, u as selectFilter, v as textFilter } from '../data-table-CfVXtCeG.js';
2
2
  import 'react';
3
3
  import './data-table-sort.js';
4
4
  import './swipeable-row.js';
@@ -1,5 +1,5 @@
1
1
  import 'react';
2
- export { D as DataTable, x as DataTableCellProps, a as DataTableChrome, b as DataTableColumn, c as DataTableDensity, y as DataTableHeadProps, d as DataTableProps, e as DataTableRowAction, F as FilterState, M as MobileSwipeActions, S as ServerPagination } from '../data-table-Drun8Fk9.js';
2
+ export { D as DataTable, x as DataTableCellProps, a as DataTableChrome, b as DataTableColumn, c as DataTableDensity, y as DataTableHeadProps, d as DataTableProps, e as DataTableRowAction, F as FilterState, M as MobileSwipeActions, S as ServerPagination } from '../data-table-CfVXtCeG.js';
3
3
  export { SortState } from './data-table-sort.js';
4
4
  import './swipeable-row.js';
5
5
  import './data-table-labels.js';
@@ -707,7 +707,10 @@ function DataTable({
707
707
  className: cn(
708
708
  "group/table",
709
709
  frame ? "overflow-clip" : "min-w-0",
710
- fillHeight && "md:flex md:flex-1 md:flex-col md:min-h-0",
710
+ // Both layouts, not just the table: the phone card list overflowing a bounded
711
+ // pane drew itself over whatever followed it, and in a viewport-locked shell
712
+ // the rows below the fold could not be reached at all.
713
+ fillHeight && "flex flex-1 flex-col min-h-0",
711
714
  className
712
715
  ),
713
716
  children: [
@@ -723,7 +726,7 @@ function DataTable({
723
726
  children: toolbar
724
727
  }
725
728
  ),
726
- !isMdUp && /* @__PURE__ */ jsxs("div", { ref: mobileRootRef, children: [
729
+ !isMdUp && /* @__PURE__ */ jsxs("div", { ref: mobileRootRef, className: cn(fillHeight && "flex min-h-0 flex-1 flex-col"), children: [
727
730
  (!isServer || onFiltersChange) && /* @__PURE__ */ jsx(
728
731
  MobileFilters,
729
732
  {
@@ -744,6 +747,11 @@ function DataTable({
744
747
  {
745
748
  className: cn(
746
749
  "divide-y divide-[var(--border)]",
750
+ // Under `fillHeight` the list is the scroller, as the table body is on a
751
+ // wide screen: the filter button stays above it and a server pager below.
752
+ // The endless-scroll sentinel still fires in here — an
753
+ // IntersectionObserver on the viewport sees through the clipping ancestor.
754
+ fillHeight && "min-h-0 flex-1 overflow-y-auto overscroll-contain",
747
755
  // Same busy treatment as the desktop body; see `isLoading` below.
748
756
  isLoading && mobileSlice.length > 0 && "opacity-60 transition-opacity"
749
757
  ),
@@ -768,7 +776,7 @@ function DataTable({
768
776
  {
769
777
  ref: loadMoreRef,
770
778
  className: "px-4 py-4 text-center text-xs text-[var(--text-placeholder)]",
771
- children: labels.loading
779
+ children: /* @__PURE__ */ jsx("bdi", { children: labels.loading })
772
780
  }
773
781
  )
774
782
  ]
@@ -1222,7 +1230,7 @@ function PlainRoot(props) {
1222
1230
  function LoadingText({ label }) {
1223
1231
  return /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-2", children: [
1224
1232
  /* @__PURE__ */ jsx(Spinner, { label: null, className: "size-4" }),
1225
- label
1233
+ /* @__PURE__ */ jsx("bdi", { children: label })
1226
1234
  ] });
1227
1235
  }
1228
1236
  function MobileFilters({