@eifi1/ui-kit 0.15.5 → 0.16.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 (140) hide show
  1. package/README.md +17 -12
  2. package/dist/chart.d.ts +3 -3
  3. package/dist/components/amount-input.d.ts +10 -3
  4. package/dist/components/amount-input.js +165 -156
  5. package/dist/components/amount-input.js.map +1 -1
  6. package/dist/components/authed-image.d.ts +14 -3
  7. package/dist/components/authed-image.js +32 -23
  8. package/dist/components/authed-image.js.map +1 -1
  9. package/dist/components/button-group.d.ts +3 -3
  10. package/dist/components/calculator.d.ts +3 -3
  11. package/dist/components/copy-button.d.ts +3 -3
  12. package/dist/components/facing-pair.d.ts +3 -3
  13. package/dist/components/file-button.d.ts +3 -3
  14. package/dist/components/file-button.js +64 -31
  15. package/dist/components/file-button.js.map +1 -1
  16. package/dist/components/file-dropzone.d.ts +3 -3
  17. package/dist/components/form-actions.d.ts +4 -4
  18. package/dist/components/form-actions.js +18 -6
  19. package/dist/components/form-actions.js.map +1 -1
  20. package/dist/components/line-items.d.ts +53 -2
  21. package/dist/components/line-items.js +73 -7
  22. package/dist/components/line-items.js.map +1 -1
  23. package/dist/components/list.d.ts +99 -5
  24. package/dist/components/list.js +103 -38
  25. package/dist/components/list.js.map +1 -1
  26. package/dist/components/loading-state.d.ts +19 -3
  27. package/dist/components/loading-state.js +14 -3
  28. package/dist/components/loading-state.js.map +1 -1
  29. package/dist/components/money-field.d.ts +3 -3
  30. package/dist/components/number-field.d.ts +3 -3
  31. package/dist/components/number-input.d.ts +3 -3
  32. package/dist/components/number-input.js +4 -2
  33. package/dist/components/number-input.js.map +1 -1
  34. package/dist/components/numpad-sheet.d.ts +3 -3
  35. package/dist/components/passkeys-setting.d.ts +30 -10
  36. package/dist/components/passkeys-setting.js +19 -11
  37. package/dist/components/passkeys-setting.js.map +1 -1
  38. package/dist/components/progress-bar.d.ts +12 -3
  39. package/dist/components/progress-bar.js +21 -11
  40. package/dist/components/progress-bar.js.map +1 -1
  41. package/dist/components/series-chart.d.ts +3 -3
  42. package/dist/components/settings-fields.d.ts +3 -3
  43. package/dist/components/signed-amount.d.ts +28 -4
  44. package/dist/components/signed-amount.js +17 -8
  45. package/dist/components/signed-amount.js.map +1 -1
  46. package/dist/components/stat-tile.d.ts +31 -2
  47. package/dist/components/stat-tile.js +37 -6
  48. package/dist/components/stat-tile.js.map +1 -1
  49. package/dist/components/status-dot.d.ts +4 -1
  50. package/dist/components/status-dot.js +20 -1
  51. package/dist/components/status-dot.js.map +1 -1
  52. package/dist/components/swatch-picker.d.ts +3 -1
  53. package/dist/components/swatch-picker.js.map +1 -1
  54. package/dist/components/table.d.ts +18 -13
  55. package/dist/components/table.js +20 -5
  56. package/dist/components/table.js.map +1 -1
  57. package/dist/components/text-link.d.ts +3 -3
  58. package/dist/components/time-input.d.ts +3 -3
  59. package/dist/components/toggle-legend.d.ts +12 -1
  60. package/dist/components/toggle-legend.js +6 -2
  61. package/dist/components/toggle-legend.js.map +1 -1
  62. package/dist/components/ui.d.ts +3 -3
  63. package/dist/components/ui.js +149 -45
  64. package/dist/components/ui.js.map +1 -1
  65. package/dist/feedback/feedback-attachment.d.ts +1 -1
  66. package/dist/feedback/feedback-attachment.js +24 -11
  67. package/dist/feedback/feedback-attachment.js.map +1 -1
  68. package/dist/feedback/feedback-dialog.d.ts +1 -1
  69. package/dist/feedback/feedback-dialog.js +6 -5
  70. package/dist/feedback/feedback-dialog.js.map +1 -1
  71. package/dist/feedback/feedback-inbox.d.ts +1 -1
  72. package/dist/feedback/feedback-thread.d.ts +1 -1
  73. package/dist/{feedback-attachment-BYAHJMOr.d.ts → feedback-attachment-WFiXJ8We.d.ts} +21 -8
  74. package/dist/feedback.d.ts +1 -1
  75. package/dist/hooks/use-file-drop.d.ts +3 -3
  76. package/dist/i18n/defaults.d.ts +3 -3
  77. package/dist/i18n/kit-labels.d.ts +2 -2
  78. package/dist/i18n/locales/de-CH-informal.d.ts +3 -3
  79. package/dist/i18n/locales/de-CH.d.ts +3 -3
  80. package/dist/i18n/locales/de-informal.d.ts +3 -3
  81. package/dist/i18n/locales/de.d.ts +3 -3
  82. package/dist/i18n/locales/de.js +1 -1
  83. package/dist/i18n/locales/de.js.map +1 -1
  84. package/dist/i18n/locales/es.d.ts +3 -3
  85. package/dist/i18n/locales/es.js +1 -1
  86. package/dist/i18n/locales/es.js.map +1 -1
  87. package/dist/i18n/locales/fr.d.ts +3 -3
  88. package/dist/i18n/locales/fr.js +1 -1
  89. package/dist/i18n/locales/fr.js.map +1 -1
  90. package/dist/i18n/locales/hu.d.ts +3 -3
  91. package/dist/i18n/locales/hu.js +1 -1
  92. package/dist/i18n/locales/hu.js.map +1 -1
  93. package/dist/i18n/locales/it.d.ts +3 -3
  94. package/dist/i18n/locales/it.js +1 -1
  95. package/dist/i18n/locales/it.js.map +1 -1
  96. package/dist/i18n/locales/zh.d.ts +3 -3
  97. package/dist/i18n/locales/zh.js +1 -1
  98. package/dist/i18n/locales/zh.js.map +1 -1
  99. package/dist/index.d.ts +10 -10
  100. package/dist/{kit-labels-BD2F7sOz.d.ts → kit-labels-DE--LmCk.d.ts} +153 -12
  101. package/dist/lib/format.d.ts +18 -3
  102. package/dist/lib/format.js +2 -1
  103. package/dist/lib/format.js.map +1 -1
  104. package/dist/rhf/fields.d.ts +3 -3
  105. package/dist/rhf/form.d.ts +3 -3
  106. package/dist/rhf/line-items.js.map +1 -1
  107. package/dist/rhf.d.ts +3 -3
  108. package/dist/shell/app-shell.d.ts +3 -3
  109. package/dist/shell/top-bar-brand.d.ts +3 -3
  110. package/dist/shell.d.ts +3 -3
  111. package/dist/wizard/stepper-nav.d.ts +3 -3
  112. package/dist/wizard.d.ts +3 -3
  113. package/package.json +1 -1
  114. package/src/components/amount-input.tsx +39 -15
  115. package/src/components/authed-image.tsx +20 -2
  116. package/src/components/file-button.tsx +77 -27
  117. package/src/components/form-actions.tsx +86 -8
  118. package/src/components/line-items.tsx +131 -14
  119. package/src/components/list.tsx +240 -50
  120. package/src/components/loading-state.tsx +35 -4
  121. package/src/components/number-input.tsx +18 -2
  122. package/src/components/passkeys-setting.tsx +45 -18
  123. package/src/components/progress-bar.tsx +46 -21
  124. package/src/components/signed-amount.tsx +47 -8
  125. package/src/components/stat-tile.tsx +76 -6
  126. package/src/components/status-dot.tsx +31 -0
  127. package/src/components/swatch-picker.tsx +3 -1
  128. package/src/components/table.tsx +42 -15
  129. package/src/components/toggle-legend.tsx +21 -4
  130. package/src/components/ui.tsx +273 -63
  131. package/src/feedback/feedback-attachment.tsx +45 -14
  132. package/src/feedback/feedback-dialog.tsx +31 -16
  133. package/src/i18n/locales/de.ts +1 -1
  134. package/src/i18n/locales/es.ts +1 -1
  135. package/src/i18n/locales/fr.ts +1 -1
  136. package/src/i18n/locales/hu.ts +1 -1
  137. package/src/i18n/locales/it.ts +1 -1
  138. package/src/i18n/locales/zh.ts +1 -1
  139. package/src/lib/format.ts +24 -3
  140. package/src/rhf/line-items.tsx +6 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/table.tsx"],"sourcesContent":["import { Children, createContext, useContext, useId, useLayoutEffect, useMemo, useRef, useState } from \"react\";\nimport type { ComponentPropsWithoutRef, ReactNode } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { THIN_SCROLLBAR_CLASS, useScrollOverflow } from \"./scroll-area\";\n\n/** `none` (0.10.0): no cell padding at all — keksdose's VAT summary, a table set\n * inside a card's own padding whose columns are spaced by hand (`pl-2` on exactly the\n * columns that need it). Type stays at the body size, as `comfortable`. */\nexport type TableDensity = \"comfortable\" | \"compact\" | \"none\";\n/** CSS `table-layout`. `auto` (the browser default) sizes columns to their content;\n * `fixed` takes widths from the first row and ignores the rest — keksdose's VAT\n * summary switches to it while editing so two inputs stop bidding against the\n * figures for room. */\nexport type TableLayout = \"auto\" | \"fixed\";\nexport type TableAlign = \"start\" | \"center\" | \"end\";\n/**\n * Vertical alignment of a cell's content (0.11.0). The defaults stay as they were — a\n * head cell sits on the `bottom` (a wrapped heading ends on the line above the\n * figures), a body cell at the `top` (a wrapped note starts level with its row). A\n * table whose cells are INPUTS wants `middle`: keksdose's VAT summary\n * (invoices/vat-summary.tsx) sets fields beside derived read-out figures in one row,\n * and a figure at the top of its cell sat above the middle of the field next to it —\n * so it writes `<tr className=\"align-middle\">` over raw `<th>`/`<td>`, because the\n * kit's cells each stated `align-top` and a class on the row could not reach them.\n */\nexport type TableVAlign = \"top\" | \"middle\" | \"bottom\";\n/** The head cell's type size: `xs` (the head's default) or `sm` (the body's). */\nexport type TableHeaderCellSize = \"xs\" | \"sm\";\n/** The header cell's weight. Default `medium`. */\nexport type TableHeaderCellWeight = \"normal\" | \"medium\" | \"semibold\";\n\n/**\n * What a {@link TableRow} is in a statement-style table (0.12.0): an ordinary `row`, a\n * `group` heading (\"Current assets\", a tinted full-width label row), a `subtotal` under\n * a group, or the `total` of the whole table. kastlan's balance sheet, income statement\n * and journal entry each spelled these as `className=\"font-medium\"`, `\"font-bold\n * border-t-2\"` and `bg-muted/50` on the cell — three pages, three sets of classes for\n * the same four kinds of line.\n */\nexport type TableRowVariant = \"row\" | \"group\" | \"subtotal\" | \"total\";\n\ninterface TableContextValue {\n density: TableDensity;\n zebra: boolean;\n hover: boolean;\n rowDividers: boolean;\n captionId: string;\n registerCaption: () => () => void;\n}\n\nconst TableContext = createContext<TableContextValue>({\n density: \"comfortable\",\n zebra: false,\n hover: false,\n rowDividers: true,\n captionId: \"\",\n registerCaption: () => () => {},\n});\n\ntype TableSection = \"head\" | \"body\" | \"foot\";\n\n/** Which section a row sits in — zebra and hover apply to body rows only. `undefined`\n * outside the kit's section parts: a raw `<thead>` / `<tbody>`, which\n * {@link useSection} then reads off the DOM. */\nconst SectionContext = createContext<TableSection | undefined>(undefined);\n\nconst SECTION_OF_TAG: Record<string, TableSection> = { THEAD: \"head\", TBODY: \"body\", TFOOT: \"foot\" };\n\n/**\n * The section a row or cell sits in: the kit part's context where there is one, else the\n * nearest `<thead>` / `<tbody>` / `<tfoot>` in the DOM.\n *\n * keksdose's VAT summary writes a raw `<thead>` round kit cells in places, and a\n * `TableHeaderCell` there took itself for a BODY cell — `scope=\"row\"`, body type — with\n * nothing on the page to say why. React cannot see an ancestor's tag, so the fallback\n * reads it after mount; a layout effect, so the corrected cell is what gets painted.\n */\nfunction useSection<E extends HTMLElement>(): [TableSection, ((el: E | null) => void) | undefined] {\n const fromContext = useContext(SectionContext);\n const [node, setNode] = useState<E | null>(null);\n const [detected, setDetected] = useState<TableSection | undefined>(undefined);\n useLayoutEffect(() => {\n if (fromContext !== undefined || !node) return;\n const section = node.closest(\"thead, tbody, tfoot\");\n const found = section ? SECTION_OF_TAG[section.tagName] : undefined;\n // eslint-disable-next-line react-hooks/set-state-in-effect -- read off the DOM, which only exists after mount\n if (found !== detected) setDetected(found);\n }, [fromContext, node, detected]);\n // No ref at all under a kit section: holding the node would cost every row and cell\n // a second render on mount for an answer the context already gave.\n return [fromContext ?? detected ?? \"body\", fromContext === undefined ? setNode : undefined];\n}\n\n/** A {@link TableRow}'s `valign`, which its cells take unless they set their own. It\n * cannot be left to CSS inheritance: every cell states its own default alignment,\n * and a class on the cell beats one on the row. */\nconst RowVAlignContext = createContext<TableVAlign | undefined>(undefined);\n\nconst VALIGN: Record<TableVAlign, string> = {\n top: \"align-top\",\n middle: \"align-middle\",\n bottom: \"align-bottom\",\n};\n\nconst HEADER_SIZE: Record<TableHeaderCellSize, string> = {\n xs: \"text-xs\",\n sm: \"text-sm\",\n};\n\nconst HEADER_WEIGHT: Record<TableHeaderCellWeight, string> = {\n normal: \"font-normal\",\n medium: \"font-medium\",\n semibold: \"font-semibold\",\n};\n\nconst CELL_PAD: Record<TableDensity, string> = {\n comfortable: \"px-3 py-2\",\n compact: \"px-2 py-1\",\n none: \"p-0\",\n};\n\nconst ALIGN: Record<TableAlign, string> = {\n start: \"text-start\",\n center: \"text-center\",\n end: \"text-end\",\n};\n\n/**\n * The alignment a figure column wants: end-aligned (so the units line up under each\n * other in either reading direction) with tabular digits (so they line up at all).\n * Exported for a cell the kit did not render — a DataTable column, a `<td>` of the\n * app's own.\n */\nexport const NUMERIC_CELL_CLASS = \"text-end tabular-nums\";\n\nexport interface TableProps extends ComponentPropsWithoutRef<\"table\"> {\n /** `compact` for a dense detail view (a ledger, a spec sheet). */\n density?: TableDensity;\n /** Tint every other body row. */\n zebra?: boolean;\n /** Tint the body row under the pointer. Off by default: a static table whose rows\n * light up promises a click that does nothing. */\n hover?: boolean;\n /** Classes for the overflow wrapper around the `<table>` (a max-height, a border). */\n wrapperClassName?: string;\n /** See {@link TableLayout}. Unset leaves the browser's `auto` and adds no class. */\n layout?: TableLayout;\n /**\n * A rounded border round the whole table (0.12.0) — for a table standing on the page\n * or in a dialog rather than inside a card. kastlan wraps six of them in\n * `<div className=\"rounded-md border\">` by hand (payment allocation, the new-budget\n * lines, the meter readings, the deposit transactions, the unit values editor, the\n * maintenance detail), and a hand-made frame outside the scroll wrapper does not clip\n * the rows' tint to its corners.\n */\n framed?: boolean;\n /**\n * The rule between body rows. Default true. `false` for a small table whose rows are\n * spaced by their own content — keksdose's VAT summary, a block of three quiet\n * columns inside a card — which otherwise had to drop to raw `<tr>`s to lose it. One\n * row can still say otherwise with {@link TableRowProps.bordered}.\n */\n rowDividers?: boolean;\n /**\n * `\"phone\"`: below `sm` each body row becomes a block — its first cell the row's\n * title, every further cell under its column's header as a small label — and the\n * head row is visually hidden (still read by screen readers). For tables of PROSE,\n * not figures (lenkbank L5: a PID-terms table of three sentence columns wrapped into\n * two-word slivers at 390px and ran its third column off the screen). A table of\n * numbers should keep scrolling sideways, which is why this is opt-in. The labels\n * are the head row's cell texts, read from the DOM; a body cell spanning several\n * columns gets none.\n */\n stack?: \"phone\";\n}\n\n/**\n * Below `sm` on a `stack=\"phone\"` table: rows as blocks, the head visually hidden,\n * the first cell as a title, the others labelled by `data-label` (set by the table).\n */\nconst STACK_PHONE_CLASSES = cn(\n \"max-sm:block max-sm:[&_tbody]:block max-sm:[&_tfoot]:block\",\n \"max-sm:[&_thead]:sr-only\",\n \"max-sm:[&_tbody_tr]:block max-sm:[&_tbody_tr]:py-2 max-sm:[&_tfoot_tr]:block max-sm:[&_tfoot_tr]:py-2\",\n \"max-sm:[&_tbody_td]:block max-sm:[&_tbody_td]:px-0 max-sm:[&_tbody_td]:py-0.5 max-sm:[&_tbody_td]:text-start\",\n \"max-sm:[&_tfoot_td]:block max-sm:[&_tfoot_td]:px-0 max-sm:[&_tfoot_td]:py-0.5\",\n \"max-sm:[&_tbody_td:first-child]:font-medium\",\n \"max-sm:[&_td[data-label]]:before:block max-sm:[&_td[data-label]]:before:text-[11px] max-sm:[&_td[data-label]]:before:font-medium max-sm:[&_td[data-label]]:before:text-[var(--text-muted)] max-sm:[&_td[data-label]]:before:content-[attr(data-label)]\",\n);\n\n/**\n * A plain, static HTML table in the kit's tokens — for the detail views that need\n * rows and columns and none of {@link DataTable}'s sorting, filtering or paging.\n *\n * kastlan renders about twenty of these on shadcn's Table parts; the part names here\n * are the same idea (`TableHead` is the `<thead>`, `TableHeaderCell` the `<th>`), so\n * the move is a rename rather than a rewrite.\n *\n * The table sits in an overflow-x wrapper, because a table cannot shrink below its\n * content and on a phone it would otherwise widen the page. While it overflows that\n * wrapper becomes a tab stop and a `role=\"region\"` named by the `TableCaption` (or the\n * table's own `aria-label`), so a keyboard user can scroll it and a reader can say\n * what it is — the same contract as {@link ScrollArea}.\n *\n * Alignment is logical throughout (`text-start` / `text-end`), so a right-to-left\n * page gets its first column on the right without a class changing.\n */\nexport function Table({\n density = \"comfortable\",\n zebra = false,\n hover = false,\n wrapperClassName,\n layout,\n framed = false,\n rowDividers = true,\n stack,\n className,\n \"aria-label\": ariaLabel,\n ...rest\n}: TableProps) {\n const captionId = useId();\n const [captions, setCaptions] = useState(0);\n const wrapper = useRef<HTMLDivElement | null>(null);\n const overflowing = useScrollOverflow(wrapper);\n const tableRef = useRef<HTMLTableElement | null>(null);\n\n // `stack=\"phone\"`: label every body cell with its column's header text, and keep the\n // table's roles explicit — `display: block` on table parts drops their table\n // semantics in some engines. Re-run on any change to the rows, which the caller\n // renders and this component never sees.\n useLayoutEffect(() => {\n const table = tableRef.current;\n if (!stack || !table) return;\n const label = () => {\n const heads = Array.from(table.tHead?.rows[0]?.cells ?? []).map((cell) => cell.textContent?.trim() ?? \"\");\n table.setAttribute(\"role\", \"table\");\n for (const row of Array.from(table.rows)) {\n row.setAttribute(\"role\", \"row\");\n let column = 0;\n for (const cell of Array.from(row.cells)) {\n const span = cell.colSpan || 1;\n if (cell.tagName === \"TH\") cell.setAttribute(\"role\", cell.closest(\"thead\") ? \"columnheader\" : \"rowheader\");\n else cell.setAttribute(\"role\", \"cell\");\n const text = span === 1 && column > 0 ? heads[column] : undefined;\n if (cell.tagName === \"TD\" && text) cell.setAttribute(\"data-label\", text);\n else cell.removeAttribute(\"data-label\");\n column += span;\n }\n }\n };\n label();\n const observer = typeof MutationObserver === \"undefined\" ? null : new MutationObserver(label);\n observer?.observe(table, { childList: true, subtree: true, characterData: true });\n return () => observer?.disconnect();\n }, [stack]);\n\n const ctx = useMemo<TableContextValue>(\n () => ({\n density,\n zebra,\n hover,\n rowDividers,\n captionId,\n registerCaption: () => {\n setCaptions((n) => n + 1);\n return () => setCaptions((n) => n - 1);\n },\n }),\n [density, zebra, hover, rowDividers, captionId],\n );\n\n const regionName = captions > 0 ? { \"aria-labelledby\": captionId } : ariaLabel ? { \"aria-label\": ariaLabel } : null;\n\n return (\n <TableContext.Provider value={ctx}>\n <div\n ref={wrapper}\n // Only a region when it scrolls AND has a name: a landmark for every short\n // table on a page would bury the real ones.\n role={overflowing && regionName ? \"region\" : undefined}\n {...(overflowing ? regionName : null)}\n tabIndex={overflowing ? 0 : undefined}\n data-overflowing={overflowing || undefined}\n data-clips=\"\"\n data-framed={framed || undefined}\n className={cn(\n // `relative`: the containing block for an `sr-only` caption.\n \"relative w-full overflow-x-auto\",\n // On the scroll wrapper itself, so its clipping keeps the rows inside the\n // rounded corners.\n framed && \"rounded-md border border-[var(--border)]\",\n THIN_SCROLLBAR_CLASS,\n \"focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[var(--brand)]\",\n wrapperClassName,\n )}\n >\n <table\n {...rest}\n ref={tableRef}\n aria-label={ariaLabel}\n className={cn(\n \"w-full caption-bottom border-collapse text-sm text-[var(--text-primary)]\",\n density === \"compact\" && \"text-xs\",\n layout === \"fixed\" && \"table-fixed\",\n layout === \"auto\" && \"table-auto\",\n stack === \"phone\" && STACK_PHONE_CLASSES,\n className,\n )}\n />\n </div>\n </TableContext.Provider>\n );\n}\n\nexport interface TableHeadProps extends ComponentPropsWithoutRef<\"thead\"> {\n /** The rule under the head. Default true; `false` for a quiet header over a small\n * table (keksdose's VAT summary wrote `className=\"border-b-0\"` to lose it). */\n bordered?: boolean;\n}\n\nexport function TableHead({ bordered = true, className, ...rest }: TableHeadProps) {\n return (\n <SectionContext.Provider value=\"head\">\n <thead {...rest} className={cn(bordered && \"border-b border-[var(--border)]\", className)} />\n </SectionContext.Provider>\n );\n}\n\nexport interface TableBodyProps extends ComponentPropsWithoutRef<\"tbody\"> {\n /**\n * Shown as ONE full-width row (a {@link TableEmpty}) when the body has no rows —\n * `children` that render nothing: an empty `.map`, a `false`, `null`. kastlan hand-\n * writes that row with a counted `colSpan` in unit-values-editor, the meeting agenda\n * and invitations tabs, the journal-entry and invoice detail pages, and a column\n * added later leaves every one of those counts one short.\n */\n empty?: ReactNode;\n}\n\nexport function TableBody({ className, empty, children, ...rest }: TableBodyProps) {\n // `toArray` drops null/undefined/booleans and flattens arrays, so `[[], false]` —\n // an empty map beside a conditional row that is off — is no rows.\n const hasRows = Children.toArray(children).length > 0;\n return (\n <SectionContext.Provider value=\"body\">\n <tbody {...rest} className={cn(\"[&>tr:last-child]:border-b-0\", className)}>\n {hasRows || empty === undefined || empty === null || empty === false ? (\n children\n ) : (\n <TableEmpty>{empty}</TableEmpty>\n )}\n </tbody>\n </SectionContext.Provider>\n );\n}\n\nexport interface TableEmptyProps extends Omit<ComponentPropsWithoutRef<\"tr\">, \"children\"> {\n children: ReactNode;\n /** Columns to span. Default: MEASURED — the widest row of the table it renders in,\n * counted after mount, so the row stays full width when a column is added. */\n colSpan?: number;\n /** Classes for the `<td>`. */\n cellClassName?: string;\n}\n\n/**\n * The \"nothing here\" row: one cell across every column, centred, muted. What\n * {@link TableBodyProps.empty} renders, and usable on its own for a body whose rows\n * are not its direct children.\n */\nexport function TableEmpty({ children, colSpan, cellClassName, className, ...rest }: TableEmptyProps) {\n const { density } = useContext(TableContext);\n const cell = useRef<HTMLTableCellElement | null>(null);\n const [measured, setMeasured] = useState(1);\n // Every render, deliberately: a column can appear in the head without anything this\n // row depends on changing. It cannot loop — setting the same count is a bail-out.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n useLayoutEffect(() => {\n if (colSpan !== undefined) return;\n const row = cell.current?.parentElement;\n const table = cell.current?.closest(\"table\");\n if (!row || !table) return;\n let widest = 1;\n for (const r of Array.from(table.rows)) {\n if (r === row) continue;\n let n = 0;\n for (const c of Array.from(r.cells)) n += c.colSpan || 1;\n widest = Math.max(widest, n);\n }\n setMeasured(widest);\n });\n return (\n <tr {...rest} data-table-empty=\"\" className={className}>\n <td\n ref={cell}\n colSpan={colSpan ?? measured}\n className={cn(\n // `none` keeps the message off the edges regardless: it is text, not a figure.\n density === \"none\" ? \"py-2\" : CELL_PAD[density],\n \"text-center text-[var(--text-muted)]\",\n cellClassName,\n )}\n >\n {children}\n </td>\n </tr>\n );\n}\n\nexport type TableFootProps = ComponentPropsWithoutRef<\"tfoot\">;\n\n/** The totals row. Not in the brief, but every ledger view has one and a `<tfoot>`\n * is what tells a reader it is a total rather than one more row. */\nexport function TableFoot({ className, ...rest }: TableFootProps) {\n return (\n <SectionContext.Provider value=\"foot\">\n <tfoot {...rest} className={cn(\"border-t border-[var(--border-strong)] font-medium\", className)} />\n </SectionContext.Provider>\n );\n}\n\nconst ROW_VARIANT: Record<TableRowVariant, string | false> = {\n row: false,\n group: \"bg-[var(--bg-surface-2)] font-semibold\",\n subtotal: \"font-medium\",\n // A 2px rule over the 1px divider the row above draws on its bottom: collapsed\n // borders meet on one line, the wider wins, and the heavier rule is what says \"sum\".\n total: \"border-t-2 border-t-[var(--border-strong)] font-semibold\",\n};\n\n/** The deprecated HTML `valign` attribute is replaced by a class-backed one. */\nexport interface TableRowProps extends ComponentPropsWithoutRef<\"tr\"> {\n /** Vertical alignment for every cell of the row that does not set its own. See\n * {@link TableVAlign}. Left out, each cell keeps its default. */\n valign?: TableVAlign;\n /** See {@link TableRowVariant}. Default `row`. Zebra and hover tint ordinary rows\n * only: a group heading or a total is not one more record. */\n variant?: TableRowVariant;\n /** The rule under this body row. Default: the table's `rowDividers`. */\n bordered?: boolean;\n}\n\nexport function TableRow({ valign, variant = \"row\", bordered, className, ...rest }: TableRowProps) {\n const { zebra, hover, rowDividers } = useContext(TableContext);\n const [section, ref] = useSection<HTMLTableRowElement>();\n const body = section === \"body\";\n const record = body && variant === \"row\";\n const row = (\n <tr\n ref={ref}\n {...rest}\n data-variant={variant === \"row\" ? undefined : variant}\n className={cn(\n body && (bordered ?? rowDividers) && \"border-b border-[var(--border)]\",\n record && zebra && \"even:bg-[var(--bg-surface-2)]\",\n record && hover && \"transition-colors hover:bg-[var(--bg-hover)]\",\n ROW_VARIANT[variant],\n // On the row as well, for a raw `<td>` of the caller's, which inherits it.\n valign && VALIGN[valign],\n className,\n )}\n />\n );\n return valign ? <RowVAlignContext.Provider value={valign}>{row}</RowVAlignContext.Provider> : row;\n}\n\ninterface CellAlignProps {\n /** Shorthand for a figure column: end-aligned, tabular digits. */\n numeric?: boolean;\n /** Logical alignment. Default `start` (`end` when `numeric`). */\n align?: TableAlign;\n /** Vertical alignment. Default: the row's `valign`, else `bottom` for a head cell and\n * `top` for a body cell. See {@link TableVAlign}. */\n valign?: TableVAlign;\n}\n\n/** The deprecated HTML `align` and `valign` attributes are replaced by class-backed ones. */\nexport interface TableHeaderCellProps extends Omit<ComponentPropsWithoutRef<\"th\">, \"align\" | \"valign\">, CellAlignProps {\n /**\n * Type size. Left out: `xs` in the head, the table's own size in the body — as\n * before. keksdose's VAT summary heads its columns at the body size and normal weight\n * (vat-summary.tsx:158–168, `font-normal` on each `<th>`), a quiet header over a\n * small table of figures, and could not say so without overriding classes.\n */\n size?: TableHeaderCellSize;\n /** Default `medium`. `normal` for the quiet header above; see {@link size}. */\n weight?: TableHeaderCellWeight;\n}\n\n/**\n * A `<th>`. `scope` defaults to `col` in the head and `row` in the body — a header\n * cell in a body row is the row's label (\"Net rent\" in a key-value table), and a\n * reader needs the scope to read it with each cell beside it.\n */\nexport function TableHeaderCell({\n numeric = false,\n align,\n valign,\n size,\n weight = \"medium\",\n scope,\n className,\n ...rest\n}: TableHeaderCellProps) {\n const { density } = useContext(TableContext);\n const [section, ref] = useSection<HTMLTableCellElement>();\n const rowVAlign = useContext(RowVAlignContext);\n const head = section === \"head\";\n const textSize = size ?? (head ? \"xs\" : undefined);\n return (\n <th\n ref={ref}\n {...rest}\n scope={scope ?? (section === \"head\" ? \"col\" : \"row\")}\n className={cn(\n CELL_PAD[density],\n ALIGN[align ?? (numeric ? \"end\" : \"start\")],\n numeric && \"tabular-nums\",\n VALIGN[valign ?? rowVAlign ?? (head ? \"bottom\" : \"top\")],\n HEADER_WEIGHT[weight],\n head ? \"text-[var(--text-muted)]\" : \"text-[var(--text-secondary)]\",\n textSize && HEADER_SIZE[textSize],\n className,\n )}\n />\n );\n}\n\nexport interface TableCellProps extends Omit<ComponentPropsWithoutRef<\"td\">, \"align\" | \"valign\">, CellAlignProps {}\n\nexport function TableCell({ numeric = false, align, valign, className, ...rest }: TableCellProps) {\n const { density } = useContext(TableContext);\n const rowVAlign = useContext(RowVAlignContext);\n return (\n <td\n {...rest}\n className={cn(\n CELL_PAD[density],\n VALIGN[valign ?? rowVAlign ?? \"top\"],\n ALIGN[align ?? (numeric ? \"end\" : \"start\")],\n numeric && \"tabular-nums whitespace-nowrap\",\n className,\n )}\n />\n );\n}\n\nexport type TableCaptionProps = ComponentPropsWithoutRef<\"caption\">;\n\n/**\n * The table's title. Beneath the table by default (`caption-bottom`, the shadcn\n * convention kastlan's views already follow); `className=\"caption-top\"` moves it.\n * `className=\"sr-only\"` keeps it for readers only — the wrapper is `relative`, so\n * the hidden caption stays inside it.\n *\n * It also names the scroll region when the table overflows: it registers itself\n * with the {@link Table} so the wrapper points at an id that exists, and never at\n * one that does not.\n */\nexport function TableCaption({ className, id, ...rest }: TableCaptionProps) {\n const { captionId, registerCaption } = useContext(TableContext);\n useLayoutEffect(() => (id ? undefined : registerCaption()), [id, registerCaption]);\n return (\n <caption\n {...rest}\n id={id ?? captionId}\n className={cn(\"mt-2 text-start text-sm text-[var(--text-muted)]\", className)}\n />\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/components/table.tsx"],"sourcesContent":["import { Children, createContext, useCallback, useContext, useId, useLayoutEffect, useMemo, useRef, useState } from \"react\";\nimport type { ComponentPropsWithRef, ReactNode, Ref } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { assignRef } from \"./choice-parts\";\nimport { THIN_SCROLLBAR_CLASS, useScrollOverflow } from \"./scroll-area\";\n\n/** `none` (0.10.0): no cell padding at all — keksdose's VAT summary, a table set\n * inside a card's own padding whose columns are spaced by hand (`pl-2` on exactly the\n * columns that need it). Type stays at the body size, as `comfortable`. */\nexport type TableDensity = \"comfortable\" | \"compact\" | \"none\";\n/** CSS `table-layout`. `auto` (the browser default) sizes columns to their content;\n * `fixed` takes widths from the first row and ignores the rest — keksdose's VAT\n * summary switches to it while editing so two inputs stop bidding against the\n * figures for room. */\nexport type TableLayout = \"auto\" | \"fixed\";\nexport type TableAlign = \"start\" | \"center\" | \"end\";\n/**\n * Vertical alignment of a cell's content (0.11.0). The defaults stay as they were — a\n * head cell sits on the `bottom` (a wrapped heading ends on the line above the\n * figures), a body cell at the `top` (a wrapped note starts level with its row). A\n * table whose cells are INPUTS wants `middle`: keksdose's VAT summary\n * (invoices/vat-summary.tsx) sets fields beside derived read-out figures in one row,\n * and a figure at the top of its cell sat above the middle of the field next to it —\n * so it writes `<tr className=\"align-middle\">` over raw `<th>`/`<td>`, because the\n * kit's cells each stated `align-top` and a class on the row could not reach them.\n */\nexport type TableVAlign = \"top\" | \"middle\" | \"bottom\";\n/** The head cell's type size: `xs` (the head's default) or `sm` (the body's). */\nexport type TableHeaderCellSize = \"xs\" | \"sm\";\n/** The header cell's weight. Default `medium`. */\nexport type TableHeaderCellWeight = \"normal\" | \"medium\" | \"semibold\";\n\n/**\n * What a {@link TableRow} is in a statement-style table (0.12.0): an ordinary `row`, a\n * `group` heading (\"Current assets\", a tinted full-width label row), a `subtotal` under\n * a group, or the `total` of the whole table. kastlan's balance sheet, income statement\n * and journal entry each spelled these as `className=\"font-medium\"`, `\"font-bold\n * border-t-2\"` and `bg-muted/50` on the cell — three pages, three sets of classes for\n * the same four kinds of line.\n */\nexport type TableRowVariant = \"row\" | \"group\" | \"subtotal\" | \"total\";\n\ninterface TableContextValue {\n density: TableDensity;\n zebra: boolean;\n hover: boolean;\n rowDividers: boolean;\n captionId: string;\n registerCaption: () => () => void;\n}\n\nconst TableContext = createContext<TableContextValue>({\n density: \"comfortable\",\n zebra: false,\n hover: false,\n rowDividers: true,\n captionId: \"\",\n registerCaption: () => () => {},\n});\n\ntype TableSection = \"head\" | \"body\" | \"foot\";\n\n/** Which section a row sits in — zebra and hover apply to body rows only. `undefined`\n * outside the kit's section parts: a raw `<thead>` / `<tbody>`, which\n * {@link useSection} then reads off the DOM. */\nconst SectionContext = createContext<TableSection | undefined>(undefined);\n\nconst SECTION_OF_TAG: Record<string, TableSection> = { THEAD: \"head\", TBODY: \"body\", TFOOT: \"foot\" };\n\n/**\n * The section a row or cell sits in: the kit part's context where there is one, else the\n * nearest `<thead>` / `<tbody>` / `<tfoot>` in the DOM.\n *\n * keksdose's VAT summary writes a raw `<thead>` round kit cells in places, and a\n * `TableHeaderCell` there took itself for a BODY cell — `scope=\"row\"`, body type — with\n * nothing on the page to say why. React cannot see an ancestor's tag, so the fallback\n * reads it after mount; a layout effect, so the corrected cell is what gets painted.\n */\nfunction useSection<E extends HTMLElement>(): [TableSection, ((el: E | null) => void) | undefined] {\n const fromContext = useContext(SectionContext);\n const [node, setNode] = useState<E | null>(null);\n const [detected, setDetected] = useState<TableSection | undefined>(undefined);\n useLayoutEffect(() => {\n if (fromContext !== undefined || !node) return;\n const section = node.closest(\"thead, tbody, tfoot\");\n const found = section ? SECTION_OF_TAG[section.tagName] : undefined;\n // eslint-disable-next-line react-hooks/set-state-in-effect -- read off the DOM, which only exists after mount\n if (found !== detected) setDetected(found);\n }, [fromContext, node, detected]);\n // No ref at all under a kit section: holding the node would cost every row and cell\n // a second render on mount for an answer the context already gave.\n return [fromContext ?? detected ?? \"body\", fromContext === undefined ? setNode : undefined];\n}\n\n/**\n * One callback ref for the caller's `ref` and the part's own (0.16.0). Every part takes\n * a `ref` to its DOM element — React 19 hands it to a function component as a prop —\n * and the parts that hold the node themselves (the table, a row or a head cell reading\n * its section off the DOM) must still get it when the caller passes one too.\n */\nfunction useMergedRef<E>(own: ((el: E | null) => void) | { current: E | null } | undefined, ref: Ref<E> | undefined) {\n return useCallback(\n (node: E | null) => {\n assignRef(own as Ref<E> | undefined, node);\n assignRef(ref, node);\n },\n [own, ref],\n );\n}\n\n/** A {@link TableRow}'s `valign`, which its cells take unless they set their own. It\n * cannot be left to CSS inheritance: every cell states its own default alignment,\n * and a class on the cell beats one on the row. */\nconst RowVAlignContext = createContext<TableVAlign | undefined>(undefined);\n\nconst VALIGN: Record<TableVAlign, string> = {\n top: \"align-top\",\n middle: \"align-middle\",\n bottom: \"align-bottom\",\n};\n\nconst HEADER_SIZE: Record<TableHeaderCellSize, string> = {\n xs: \"text-xs\",\n sm: \"text-sm\",\n};\n\nconst HEADER_WEIGHT: Record<TableHeaderCellWeight, string> = {\n normal: \"font-normal\",\n medium: \"font-medium\",\n semibold: \"font-semibold\",\n};\n\nconst CELL_PAD: Record<TableDensity, string> = {\n comfortable: \"px-3 py-2\",\n compact: \"px-2 py-1\",\n none: \"p-0\",\n};\n\nconst ALIGN: Record<TableAlign, string> = {\n start: \"text-start\",\n center: \"text-center\",\n end: \"text-end\",\n};\n\n/**\n * The alignment a figure column wants: end-aligned (so the units line up under each\n * other in either reading direction) with tabular digits (so they line up at all).\n * Exported for a cell the kit did not render — a DataTable column, a `<td>` of the\n * app's own.\n */\nexport const NUMERIC_CELL_CLASS = \"text-end tabular-nums\";\n\n/**\n * Every part's props are `ComponentPropsWithRef` (0.16.0), so `ref` reaches the DOM\n * element: keksdose scrolled a budget row into view through `useId` + `getElementById`\n * because `<TableRow ref>` did not type-check.\n */\nexport interface TableProps extends ComponentPropsWithRef<\"table\"> {\n /** `compact` for a dense detail view (a ledger, a spec sheet). */\n density?: TableDensity;\n /** Tint every other body row. */\n zebra?: boolean;\n /** Tint the body row under the pointer. Off by default: a static table whose rows\n * light up promises a click that does nothing. */\n hover?: boolean;\n /** Classes for the overflow wrapper around the `<table>` (a max-height, a border). */\n wrapperClassName?: string;\n /** See {@link TableLayout}. Unset leaves the browser's `auto` and adds no class. */\n layout?: TableLayout;\n /**\n * A rounded border round the whole table (0.12.0) — for a table standing on the page\n * or in a dialog rather than inside a card. kastlan wraps six of them in\n * `<div className=\"rounded-md border\">` by hand (payment allocation, the new-budget\n * lines, the meter readings, the deposit transactions, the unit values editor, the\n * maintenance detail), and a hand-made frame outside the scroll wrapper does not clip\n * the rows' tint to its corners.\n */\n framed?: boolean;\n /**\n * The rule between body rows. Default true. `false` for a small table whose rows are\n * spaced by their own content — keksdose's VAT summary, a block of three quiet\n * columns inside a card — which otherwise had to drop to raw `<tr>`s to lose it. One\n * row can still say otherwise with {@link TableRowProps.bordered}.\n */\n rowDividers?: boolean;\n /**\n * `\"phone\"`: below `sm` each body row becomes a block — its first cell the row's\n * title, every further cell under its column's header as a small label — and the\n * head row is visually hidden (still read by screen readers). For tables of PROSE,\n * not figures (lenkbank L5: a PID-terms table of three sentence columns wrapped into\n * two-word slivers at 390px and ran its third column off the screen). A table of\n * numbers should keep scrolling sideways, which is why this is opt-in. The labels\n * are the head row's cell texts, read from the DOM; a body cell spanning several\n * columns gets none.\n */\n stack?: \"phone\";\n}\n\n/**\n * Below `sm` on a `stack=\"phone\"` table: rows as blocks, the head visually hidden,\n * the first cell as a title, the others labelled by `data-label` (set by the table).\n */\nconst STACK_PHONE_CLASSES = cn(\n \"max-sm:block max-sm:[&_tbody]:block max-sm:[&_tfoot]:block\",\n \"max-sm:[&_thead]:sr-only\",\n \"max-sm:[&_tbody_tr]:block max-sm:[&_tbody_tr]:py-2 max-sm:[&_tfoot_tr]:block max-sm:[&_tfoot_tr]:py-2\",\n \"max-sm:[&_tbody_td]:block max-sm:[&_tbody_td]:px-0 max-sm:[&_tbody_td]:py-0.5 max-sm:[&_tbody_td]:text-start\",\n \"max-sm:[&_tfoot_td]:block max-sm:[&_tfoot_td]:px-0 max-sm:[&_tfoot_td]:py-0.5\",\n \"max-sm:[&_tbody_td:first-child]:font-medium\",\n \"max-sm:[&_td[data-label]]:before:block max-sm:[&_td[data-label]]:before:text-[11px] max-sm:[&_td[data-label]]:before:font-medium max-sm:[&_td[data-label]]:before:text-[var(--text-muted)] max-sm:[&_td[data-label]]:before:content-[attr(data-label)]\",\n);\n\n/**\n * A plain, static HTML table in the kit's tokens — for the detail views that need\n * rows and columns and none of {@link DataTable}'s sorting, filtering or paging.\n *\n * kastlan renders about twenty of these on shadcn's Table parts; the part names here\n * are the same idea (`TableHead` is the `<thead>`, `TableHeaderCell` the `<th>`), so\n * the move is a rename rather than a rewrite.\n *\n * The table sits in an overflow-x wrapper, because a table cannot shrink below its\n * content and on a phone it would otherwise widen the page. While it overflows that\n * wrapper becomes a tab stop and a `role=\"region\"` named by the `TableCaption` (or the\n * table's own `aria-label`), so a keyboard user can scroll it and a reader can say\n * what it is — the same contract as {@link ScrollArea}.\n *\n * Alignment is logical throughout (`text-start` / `text-end`), so a right-to-left\n * page gets its first column on the right without a class changing.\n */\nexport function Table({\n density = \"comfortable\",\n zebra = false,\n hover = false,\n wrapperClassName,\n layout,\n framed = false,\n rowDividers = true,\n stack,\n className,\n \"aria-label\": ariaLabel,\n ref,\n ...rest\n}: TableProps) {\n const captionId = useId();\n const [captions, setCaptions] = useState(0);\n const wrapper = useRef<HTMLDivElement | null>(null);\n const overflowing = useScrollOverflow(wrapper);\n const tableRef = useRef<HTMLTableElement | null>(null);\n const setTableRef = useMergedRef(tableRef, ref);\n\n // `stack=\"phone\"`: label every body cell with its column's header text, and keep the\n // table's roles explicit — `display: block` on table parts drops their table\n // semantics in some engines. Re-run on any change to the rows, which the caller\n // renders and this component never sees.\n useLayoutEffect(() => {\n const table = tableRef.current;\n if (!stack || !table) return;\n const label = () => {\n const heads = Array.from(table.tHead?.rows[0]?.cells ?? []).map((cell) => cell.textContent?.trim() ?? \"\");\n table.setAttribute(\"role\", \"table\");\n for (const row of Array.from(table.rows)) {\n row.setAttribute(\"role\", \"row\");\n let column = 0;\n for (const cell of Array.from(row.cells)) {\n const span = cell.colSpan || 1;\n if (cell.tagName === \"TH\") cell.setAttribute(\"role\", cell.closest(\"thead\") ? \"columnheader\" : \"rowheader\");\n else cell.setAttribute(\"role\", \"cell\");\n const text = span === 1 && column > 0 ? heads[column] : undefined;\n if (cell.tagName === \"TD\" && text) cell.setAttribute(\"data-label\", text);\n else cell.removeAttribute(\"data-label\");\n column += span;\n }\n }\n };\n label();\n const observer = typeof MutationObserver === \"undefined\" ? null : new MutationObserver(label);\n observer?.observe(table, { childList: true, subtree: true, characterData: true });\n return () => observer?.disconnect();\n }, [stack]);\n\n const ctx = useMemo<TableContextValue>(\n () => ({\n density,\n zebra,\n hover,\n rowDividers,\n captionId,\n registerCaption: () => {\n setCaptions((n) => n + 1);\n return () => setCaptions((n) => n - 1);\n },\n }),\n [density, zebra, hover, rowDividers, captionId],\n );\n\n const regionName = captions > 0 ? { \"aria-labelledby\": captionId } : ariaLabel ? { \"aria-label\": ariaLabel } : null;\n\n return (\n <TableContext.Provider value={ctx}>\n <div\n ref={wrapper}\n // Only a region when it scrolls AND has a name: a landmark for every short\n // table on a page would bury the real ones.\n role={overflowing && regionName ? \"region\" : undefined}\n {...(overflowing ? regionName : null)}\n tabIndex={overflowing ? 0 : undefined}\n data-overflowing={overflowing || undefined}\n data-clips=\"\"\n data-framed={framed || undefined}\n className={cn(\n // `relative`: the containing block for an `sr-only` caption.\n \"relative w-full overflow-x-auto\",\n // On the scroll wrapper itself, so its clipping keeps the rows inside the\n // rounded corners.\n framed && \"rounded-md border border-[var(--border)]\",\n THIN_SCROLLBAR_CLASS,\n \"focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[var(--brand)]\",\n wrapperClassName,\n )}\n >\n <table\n {...rest}\n ref={setTableRef}\n aria-label={ariaLabel}\n className={cn(\n \"w-full caption-bottom border-collapse text-sm text-[var(--text-primary)]\",\n density === \"compact\" && \"text-xs\",\n layout === \"fixed\" && \"table-fixed\",\n layout === \"auto\" && \"table-auto\",\n stack === \"phone\" && STACK_PHONE_CLASSES,\n className,\n )}\n />\n </div>\n </TableContext.Provider>\n );\n}\n\nexport interface TableHeadProps extends ComponentPropsWithRef<\"thead\"> {\n /** The rule under the head. Default true; `false` for a quiet header over a small\n * table (keksdose's VAT summary wrote `className=\"border-b-0\"` to lose it). */\n bordered?: boolean;\n}\n\nexport function TableHead({ bordered = true, className, ...rest }: TableHeadProps) {\n return (\n <SectionContext.Provider value=\"head\">\n <thead {...rest} className={cn(bordered && \"border-b border-[var(--border)]\", className)} />\n </SectionContext.Provider>\n );\n}\n\nexport interface TableBodyProps extends ComponentPropsWithRef<\"tbody\"> {\n /**\n * Shown as ONE full-width row (a {@link TableEmpty}) when the body has no rows —\n * `children` that render nothing: an empty `.map`, a `false`, `null`. kastlan hand-\n * writes that row with a counted `colSpan` in unit-values-editor, the meeting agenda\n * and invitations tabs, the journal-entry and invoice detail pages, and a column\n * added later leaves every one of those counts one short.\n */\n empty?: ReactNode;\n}\n\nexport function TableBody({ className, empty, children, ...rest }: TableBodyProps) {\n // `toArray` drops null/undefined/booleans and flattens arrays, so `[[], false]` —\n // an empty map beside a conditional row that is off — is no rows.\n const hasRows = Children.toArray(children).length > 0;\n return (\n <SectionContext.Provider value=\"body\">\n <tbody {...rest} className={cn(\"[&>tr:last-child]:border-b-0\", className)}>\n {hasRows || empty === undefined || empty === null || empty === false ? (\n children\n ) : (\n <TableEmpty>{empty}</TableEmpty>\n )}\n </tbody>\n </SectionContext.Provider>\n );\n}\n\nexport interface TableEmptyProps extends Omit<ComponentPropsWithRef<\"tr\">, \"children\"> {\n children: ReactNode;\n /** Columns to span. Default: MEASURED — the widest row of the table it renders in,\n * counted after mount, so the row stays full width when a column is added. */\n colSpan?: number;\n /** Classes for the `<td>`. */\n cellClassName?: string;\n}\n\n/**\n * The \"nothing here\" row: one cell across every column, centred, muted. What\n * {@link TableBodyProps.empty} renders, and usable on its own for a body whose rows\n * are not its direct children.\n */\nexport function TableEmpty({ children, colSpan, cellClassName, className, ...rest }: TableEmptyProps) {\n const { density } = useContext(TableContext);\n const cell = useRef<HTMLTableCellElement | null>(null);\n const [measured, setMeasured] = useState(1);\n // Every render, deliberately: a column can appear in the head without anything this\n // row depends on changing. It cannot loop — setting the same count is a bail-out.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n useLayoutEffect(() => {\n if (colSpan !== undefined) return;\n const row = cell.current?.parentElement;\n const table = cell.current?.closest(\"table\");\n if (!row || !table) return;\n let widest = 1;\n for (const r of Array.from(table.rows)) {\n if (r === row) continue;\n let n = 0;\n for (const c of Array.from(r.cells)) n += c.colSpan || 1;\n widest = Math.max(widest, n);\n }\n setMeasured(widest);\n });\n return (\n <tr {...rest} data-table-empty=\"\" className={className}>\n <td\n ref={cell}\n colSpan={colSpan ?? measured}\n className={cn(\n // `none` keeps the message off the edges regardless: it is text, not a figure.\n density === \"none\" ? \"py-2\" : CELL_PAD[density],\n \"text-center text-[var(--text-muted)]\",\n cellClassName,\n )}\n >\n {children}\n </td>\n </tr>\n );\n}\n\nexport type TableFootProps = ComponentPropsWithRef<\"tfoot\">;\n\n/** The totals row. Not in the brief, but every ledger view has one and a `<tfoot>`\n * is what tells a reader it is a total rather than one more row. */\nexport function TableFoot({ className, ...rest }: TableFootProps) {\n return (\n <SectionContext.Provider value=\"foot\">\n <tfoot {...rest} className={cn(\"border-t border-[var(--border-strong)] font-medium\", className)} />\n </SectionContext.Provider>\n );\n}\n\nconst ROW_VARIANT: Record<TableRowVariant, string | false> = {\n row: false,\n group: \"bg-[var(--bg-surface-2)] font-semibold\",\n subtotal: \"font-medium\",\n // A 2px rule over the 1px divider the row above draws on its bottom: collapsed\n // borders meet on one line, the wider wins, and the heavier rule is what says \"sum\".\n total: \"border-t-2 border-t-[var(--border-strong)] font-semibold\",\n};\n\n/** The deprecated HTML `valign` attribute is replaced by a class-backed one. */\nexport interface TableRowProps extends ComponentPropsWithRef<\"tr\"> {\n /** Vertical alignment for every cell of the row that does not set its own. See\n * {@link TableVAlign}. Left out, each cell keeps its default. */\n valign?: TableVAlign;\n /** See {@link TableRowVariant}. Default `row`. Zebra and hover tint ordinary rows\n * only: a group heading or a total is not one more record. */\n variant?: TableRowVariant;\n /** The rule under this body row. Default: the table's `rowDividers`. */\n bordered?: boolean;\n}\n\nexport function TableRow({ valign, variant = \"row\", bordered, className, ref: refProp, ...rest }: TableRowProps) {\n const { zebra, hover, rowDividers } = useContext(TableContext);\n const [section, sectionRef] = useSection<HTMLTableRowElement>();\n const ref = useMergedRef(sectionRef, refProp);\n const body = section === \"body\";\n const record = body && variant === \"row\";\n const row = (\n <tr\n ref={ref}\n {...rest}\n data-variant={variant === \"row\" ? undefined : variant}\n className={cn(\n body && (bordered ?? rowDividers) && \"border-b border-[var(--border)]\",\n record && zebra && \"even:bg-[var(--bg-surface-2)]\",\n record && hover && \"transition-colors hover:bg-[var(--bg-hover)]\",\n ROW_VARIANT[variant],\n // On the row as well, for a raw `<td>` of the caller's, which inherits it.\n valign && VALIGN[valign],\n className,\n )}\n />\n );\n return valign ? <RowVAlignContext.Provider value={valign}>{row}</RowVAlignContext.Provider> : row;\n}\n\ninterface CellAlignProps {\n /** Shorthand for a figure column: end-aligned, tabular digits. */\n numeric?: boolean;\n /** Logical alignment. Default `start` (`end` when `numeric`). */\n align?: TableAlign;\n /** Vertical alignment. Default: the row's `valign`, else `bottom` for a head cell and\n * `top` for a body cell. See {@link TableVAlign}. */\n valign?: TableVAlign;\n}\n\n/** The deprecated HTML `align` and `valign` attributes are replaced by class-backed ones. */\nexport interface TableHeaderCellProps extends Omit<ComponentPropsWithRef<\"th\">, \"align\" | \"valign\">, CellAlignProps {\n /**\n * Type size. Left out: `xs` in the head, the table's own size in the body — as\n * before. keksdose's VAT summary heads its columns at the body size and normal weight\n * (vat-summary.tsx:158–168, `font-normal` on each `<th>`), a quiet header over a\n * small table of figures, and could not say so without overriding classes.\n */\n size?: TableHeaderCellSize;\n /** Default `medium`. `normal` for the quiet header above; see {@link size}. */\n weight?: TableHeaderCellWeight;\n}\n\n/**\n * A `<th>`. `scope` defaults to `col` in the head and `row` in the body — a header\n * cell in a body row is the row's label (\"Net rent\" in a key-value table), and a\n * reader needs the scope to read it with each cell beside it.\n */\nexport function TableHeaderCell({\n numeric = false,\n align,\n valign,\n size,\n weight = \"medium\",\n scope,\n className,\n ref: refProp,\n ...rest\n}: TableHeaderCellProps) {\n const { density } = useContext(TableContext);\n const [section, sectionRef] = useSection<HTMLTableCellElement>();\n const ref = useMergedRef(sectionRef, refProp);\n const rowVAlign = useContext(RowVAlignContext);\n const head = section === \"head\";\n const textSize = size ?? (head ? \"xs\" : undefined);\n return (\n <th\n ref={ref}\n {...rest}\n scope={scope ?? (section === \"head\" ? \"col\" : \"row\")}\n className={cn(\n CELL_PAD[density],\n ALIGN[align ?? (numeric ? \"end\" : \"start\")],\n numeric && \"tabular-nums\",\n VALIGN[valign ?? rowVAlign ?? (head ? \"bottom\" : \"top\")],\n HEADER_WEIGHT[weight],\n head ? \"text-[var(--text-muted)]\" : \"text-[var(--text-secondary)]\",\n textSize && HEADER_SIZE[textSize],\n className,\n )}\n />\n );\n}\n\nexport interface TableCellProps extends Omit<ComponentPropsWithRef<\"td\">, \"align\" | \"valign\">, CellAlignProps {}\n\nexport function TableCell({ numeric = false, align, valign, className, ...rest }: TableCellProps) {\n const { density } = useContext(TableContext);\n const rowVAlign = useContext(RowVAlignContext);\n return (\n <td\n {...rest}\n className={cn(\n CELL_PAD[density],\n VALIGN[valign ?? rowVAlign ?? \"top\"],\n ALIGN[align ?? (numeric ? \"end\" : \"start\")],\n numeric && \"tabular-nums whitespace-nowrap\",\n className,\n )}\n />\n );\n}\n\nexport type TableCaptionProps = ComponentPropsWithRef<\"caption\">;\n\n/**\n * The table's title. Beneath the table by default (`caption-bottom`, the shadcn\n * convention kastlan's views already follow); `className=\"caption-top\"` moves it.\n * `className=\"sr-only\"` keeps it for readers only — the wrapper is `relative`, so\n * the hidden caption stays inside it.\n *\n * It also names the scroll region when the table overflows: it registers itself\n * with the {@link Table} so the wrapper points at an id that exists, and never at\n * one that does not.\n */\nexport function TableCaption({ className, id, ...rest }: TableCaptionProps) {\n const { captionId, registerCaption } = useContext(TableContext);\n useLayoutEffect(() => (id ? undefined : registerCaption()), [id, registerCaption]);\n return (\n <caption\n {...rest}\n id={id ?? captionId}\n className={cn(\"mt-2 text-start text-sm text-[var(--text-muted)]\", className)}\n />\n );\n}\n"],"mappings":"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","names":[]}
@@ -1,7 +1,7 @@
1
1
  import * as react from 'react';
2
2
  import { ReactElement, ComponentPropsWithoutRef, ReactNode, Ref, MouseEvent, KeyboardEvent } from 'react';
3
3
  import { LucideIcon } from 'lucide-react';
4
- import { aA as KitLinkComponent } from '../kit-labels-BD2F7sOz.js';
4
+ import { aD as KitLinkComponent } from '../kit-labels-DE--LmCk.js';
5
5
  import './data-table-labels.js';
6
6
  import './mini-calendar.js';
7
7
  import './calendar-heatmap.js';
@@ -32,7 +32,7 @@ import './dialog-frame.js';
32
32
  import './modal.js';
33
33
  import '../hooks/use-search-param-state.js';
34
34
  import './measured-grid.js';
35
- import '../feedback-attachment-BYAHJMOr.js';
35
+ import '../feedback-attachment-WFiXJ8We.js';
36
36
  import '../feedback/feedback-thread.js';
37
37
  import '../feedback/feedback-inbox.js';
38
38
  import './account-settings-labels.js';
@@ -40,12 +40,12 @@ import './confirm-dialog.js';
40
40
  import './floating-panel.js';
41
41
  import './bulk-action-bar.js';
42
42
  import './list.js';
43
+ import './signed-amount.js';
43
44
  import './breadcrumbs.js';
44
45
  import './toast.js';
45
46
  import './description-list.js';
46
47
  import './line-items.js';
47
48
  import './progress-bar.js';
48
- import './signed-amount.js';
49
49
  import './error-boundary.js';
50
50
  import './authed-image.js';
51
51
  import '../hooks/use-authed-src.js';
@@ -1,5 +1,5 @@
1
1
  import * as react from 'react';
2
- import { az as InputProps } from '../kit-labels-BD2F7sOz.js';
2
+ import { aC as InputProps } from '../kit-labels-DE--LmCk.js';
3
3
  import './data-table-labels.js';
4
4
  import './mini-calendar.js';
5
5
  import './calendar-heatmap.js';
@@ -31,7 +31,7 @@ import './dialog-frame.js';
31
31
  import './modal.js';
32
32
  import '../hooks/use-search-param-state.js';
33
33
  import './measured-grid.js';
34
- import '../feedback-attachment-BYAHJMOr.js';
34
+ import '../feedback-attachment-WFiXJ8We.js';
35
35
  import '../feedback/feedback-thread.js';
36
36
  import '../feedback/feedback-inbox.js';
37
37
  import './account-settings-labels.js';
@@ -39,12 +39,12 @@ import './confirm-dialog.js';
39
39
  import './floating-panel.js';
40
40
  import './bulk-action-bar.js';
41
41
  import './list.js';
42
+ import './signed-amount.js';
42
43
  import './breadcrumbs.js';
43
44
  import './toast.js';
44
45
  import './description-list.js';
45
46
  import './line-items.js';
46
47
  import './progress-bar.js';
47
- import './signed-amount.js';
48
48
  import './error-boundary.js';
49
49
  import './authed-image.js';
50
50
  import '../hooks/use-authed-src.js';
@@ -120,6 +120,17 @@ interface StaticLegendProps {
120
120
  orientation?: "horizontal" | "vertical";
121
121
  /** The list's accessible name. Default: the `seriesChart.legend` label. */
122
122
  "aria-label"?: string;
123
+ /**
124
+ * `ul` (default): a list — "list, 4 items", walked with the list keys. Its entries are
125
+ * `listitem`s, so a host test that counts `getAllByRole("listitem")` on a page with
126
+ * the legend on it counts the legend too: scope it (`within(...)`) or use `div`.
127
+ *
128
+ * `div`: a named `role="group"` of plain entries, for a legend set inside something
129
+ * that is already a list or a table row, where a list in a list reads as nesting that
130
+ * is not there, or where the entries are too few for "list, 2 items" to help. The
131
+ * entries are read in order either way; only the list framing goes.
132
+ */
133
+ as?: "ul" | "div";
123
134
  className?: string;
124
135
  /** Per-instance strings over `<UiKitProvider labels={{ seriesChart }}>`. */
125
136
  labels?: Partial<SeriesChartLabels>;
@@ -139,7 +150,7 @@ interface StaticLegendProps {
139
150
  * Unlike `ToggleLegend` it draws a single entry: a key with one line still says what
140
151
  * the one mark on the chart means.
141
152
  */
142
- declare function StaticLegend({ entries, orientation, "aria-label": ariaLabel, className, labels: labelsProp, }: StaticLegendProps): react.JSX.Element | null;
153
+ declare function StaticLegend({ entries, orientation, "aria-label": ariaLabel, as, className, labels: labelsProp, }: StaticLegendProps): react.JSX.Element | null;
143
154
  /**
144
155
  * The column a vertical legend stands in: beside the charts, on their horizontal
145
156
  * centre line. `h-full` + `justify-center`, so it centres in a grid cell or a flex row
@@ -70,14 +70,18 @@ function StaticLegend({
70
70
  entries,
71
71
  orientation = "horizontal",
72
72
  "aria-label": ariaLabel,
73
+ as = "ul",
73
74
  className,
74
75
  labels: labelsProp
75
76
  }) {
76
77
  const labels = useKitLabels("seriesChart", DEFAULT_SERIES_CHART_LABELS, labelsProp);
77
78
  if (entries.length === 0) return null;
79
+ const List = as;
80
+ const Item = as === "ul" ? "li" : "div";
78
81
  return /* @__PURE__ */ jsx(
79
- "ul",
82
+ List,
80
83
  {
84
+ role: as === "div" ? "group" : void 0,
81
85
  "aria-label": ariaLabel ?? labels.legend,
82
86
  className: cn(
83
87
  "m-0 flex list-none gap-x-3 gap-y-1 p-0",
@@ -85,7 +89,7 @@ function StaticLegend({
85
89
  className
86
90
  ),
87
91
  children: entries.map((entry) => /* @__PURE__ */ jsxs(
88
- "li",
92
+ Item,
89
93
  {
90
94
  className: cn(
91
95
  "flex items-center gap-1.5 text-[11px] text-[var(--text-secondary)]",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/toggle-legend.tsx"],"sourcesContent":["// A legend whose entries are SWITCHES — and the stroke table it shares with\n// `SeriesChart`. Lifted out of lenkbank's measurement plots, where ten legends over\n// thirteen charts took channels on and off the picture.\n//\n// No recharts in this module: a legend of switches is plain buttons, and it is as\n// useful under a chart the consumer drew by hand as under `SeriesChart`.\nimport type { ReactNode } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { useKitLabels } from \"../i18n/kit-labels\";\nimport { DEFAULT_SERIES_CHART_LABELS, type SeriesChartLabels } from \"./series-chart-labels\";\n\n/**\n * The strokes a reader can tell apart, in the order they are handed out.\n *\n * For the charts where the colour is already spoken for: a comparison draws each\n * measurement in its own colour, so a chart carrying several CHANNELS of each has\n * nothing left but the stroke — and \"solid versus dashed\" only gets you two.\n *\n * **Five, and the fifth is the last one.** They are the draughtsman's line types, in\n * the draughtsman's order — continuous, dashed, dotted, dash-dot, dash-dot-dot — the\n * one family of five a century of drawings has already proved legible at a hair's\n * width. A sixth is not distinguishable from one of these at two pixels wide, so a\n * chart needing one is a chart that should be split.\n *\n * Kept here, beside the legend's own mark, and read by `SeriesChart` from here — so\n * the key under a chart and the lines on it cannot disagree.\n */\nexport const STROKE_PATTERNS: readonly (string | undefined)[] = [\n undefined,\n \"5 4\",\n \"1 3\",\n \"9 3 2 3\",\n \"9 3 2 3 2 3\",\n];\n\n/** The dash array for the nth pattern, wrapping past the fifth. */\nexport function strokeDash(order: number): string | undefined {\n const n = STROKE_PATTERNS.length;\n return STROKE_PATTERNS[((Math.trunc(order) % n) + n) % n];\n}\n\n/** Which of the patterns a `step` series is drawn in.\n *\n * A whole-number channel that jumps rather than travels is drawn dashed, and it has\n * to be one of {@link STROKE_PATTERNS} rather than a dash array written out at the\n * call site: a legend entry says its pattern by index, so a step line drawn in\n * anything else would be a legend promising a solid line for a dashed one. */\nexport const STEP_DASH = 1;\n\n/**\n * One key switched, as a new hidden set.\n *\n * Every caller writes `onToggle={(key) => setHidden(toggleHidden(hidden, key))}`. A\n * new set rather than a mutated one, because the state is read during render and a\n * mutation of something a component already holds is invisible to React.\n */\nexport function toggleHidden(hidden: ReadonlySet<string>, key: string): ReadonlySet<string> {\n const next = new Set(hidden);\n if (!next.delete(key)) next.add(key);\n return next;\n}\n\nexport interface LegendEntry {\n key: string;\n label: ReactNode;\n /** Any CSS colour — `paletteFor(i)` for the kit's own ramp. */\n color: string;\n /**\n * What the entry's mark looks like. `swatch` — a filled square, the default — says\n * WHICH MEASUREMENT. `stroke` says WHICH QUANTITY, for the charts that carry both at\n * once and tell them apart by colour and by dash. `dot` is a round mark, for a\n * MARKER on the chart (a point, an event) rather than a series.\n */\n marker?: \"swatch\" | \"stroke\" | \"dot\";\n /** Which of {@link STROKE_PATTERNS}, when the marker is a stroke — the same index\n * `SeriesChartSeries.dash` takes, so a legend cannot promise a dot-dash the plot\n * draws dashed. Or the same custom `stroke-dasharray` string the series draws with\n * (keksdose's `\"4 3\"`). */\n dash?: number | string;\n /**\n * A mark of the caller's own, drawn in place of {@link marker} — for a key whose\n * mark is not a colour at all (a fading bar for \"open-ended\", a hairline for\n * \"today\"). Decorative: it is wrapped `aria-hidden`, and the label is the name.\n */\n icon?: ReactNode;\n /**\n * `\"end\"`: push this entry to the legend's inline END — the far right in LTR, the far\n * left in RTL — with everything before it kept at the start. kastlan's lease gantt\n * (feedback #49) keys its bars at the start and its \"today\" hairline at the end, over\n * the time axis whose end \"today\" is nearest, and wrote it as\n * `className=\"[&>li:last-child]:ms-auto\"` — a selector that breaks the moment the\n * entries are reordered and that reaches into the legend's markup.\n *\n * An auto inline-start margin, so it is logical (RTL mirrors it) and it takes only the\n * row's LEFTOVER space: on a narrow screen where the row wraps, the entry wraps with it\n * and sits at the end of its own line rather than overflowing. Give it to the LAST\n * entry — auto margins push everything after them too, so an end entry in the middle\n * drags the rest over with it (which is also how to end-align a GROUP: mark its first).\n * In a vertical legend it sits at the column's inline end. Honoured by both\n * {@link StaticLegend} and {@link ToggleLegend}, which share this entry type.\n */\n align?: \"start\" | \"end\";\n}\n\nexport interface ToggleLegendProps {\n entries: LegendEntry[];\n /** The keys currently off the chart. */\n hidden: ReadonlySet<string>;\n onToggle: (key: string) => void;\n /** `vertical` is for a legend standing BESIDE the charts rather than under them —\n * what a row of charts sharing one legend wants, since under two charts there is\n * no \"under\", and putting it under one says it belongs to that one. */\n orientation?: \"horizontal\" | \"vertical\";\n /**\n * Draw a legend of ONE entry rather than nothing.\n *\n * A single line's legend is usually noise. It is not where the legend is the only\n * SWITCH that line has — a series that starts hidden and has no entry to bring it\n * back is gone for good.\n */\n showSingle?: boolean;\n className?: string;\n /** Per-instance strings over `<UiKitProvider labels={{ seriesChart }}>`. */\n labels?: Partial<SeriesChartLabels>;\n}\n\n/**\n * A legend whose entries are switches.\n *\n * `ChartLegendContent` with `onItemClick`/`activeKey` is the other kind: clicking an\n * entry HIGHLIGHTS it and dims the rest, which is right when every series belongs on\n * the chart and one of them is momentarily interesting. This is for the opposite —\n * three coordinates on one axis where y runs to 800 and z barely moves, and the only\n * way to see z is to take y off the chart entirely. Any number may be off at once.\n *\n * A hidden entry keeps its mark and its place. Removing it would reflow the legend on\n * every click and lose the one thing it is for: saying what COULD be shown.\n *\n * Each entry is a real `<button>` with `aria-pressed` — pressed means \"on the chart\" —\n * so Tab reaches every switch and Space/Enter flips it.\n */\nexport function ToggleLegend({\n entries,\n hidden,\n onToggle,\n orientation = \"horizontal\",\n showSingle = false,\n className,\n labels: labelsProp,\n}: ToggleLegendProps) {\n const labels = useKitLabels(\"seriesChart\", DEFAULT_SERIES_CHART_LABELS, labelsProp);\n if (entries.length < (showSingle ? 1 : 2)) return null;\n return (\n <div\n role=\"group\"\n aria-label={labels.legend}\n className={cn(\n \"flex gap-x-3 gap-y-1\",\n orientation === \"vertical\"\n ? \"flex-col items-start justify-center\"\n : \"mt-2 flex-wrap items-center\",\n className,\n )}\n >\n {entries.map((entry) => {\n const off = hidden.has(entry.key);\n return (\n <button\n key={entry.key}\n type=\"button\"\n // Pressed rather than a checkbox: it is a control over what the chart\n // draws, and \"not pressed\" is the same sentence the dimmed mark says.\n aria-pressed={!off}\n onClick={() => onToggle(entry.key)}\n className={cn(\n \"flex items-center gap-1.5 rounded text-start text-[11px] text-[var(--text-secondary)] transition-opacity hover:opacity-80\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--brand)]\",\n off && \"opacity-35\",\n entry.align === \"end\" && \"ms-auto\",\n )}\n >\n <LegendMark entry={entry} off={off} />\n {entry.label}\n </button>\n );\n })}\n </div>\n );\n}\n\nexport interface StaticLegendProps {\n entries: LegendEntry[];\n /** See {@link ToggleLegendProps.orientation}. */\n orientation?: \"horizontal\" | \"vertical\";\n /** The list's accessible name. Default: the `seriesChart.legend` label. */\n \"aria-label\"?: string;\n className?: string;\n /** Per-instance strings over `<UiKitProvider labels={{ seriesChart }}>`. */\n labels?: Partial<SeriesChartLabels>;\n}\n\n/**\n * A legend that is a KEY, not a control: the same entries and marks as\n * {@link ToggleLegend} — swatch, stroke (with the chart's own dash), dot, or a mark of\n * the caller's own — but nothing to press.\n *\n * A separate component rather than `ToggleLegend` without `onToggle`, because the two\n * are different things to a screen reader: a group of switches announces a pressed\n * state on every entry, a key is a LIST, read as \"list, 4 items\" and walked with the\n * list keys, and costs no tab stops. The apps drew this by hand under the charts that\n * say what a colour or a dash means without letting you switch it (kastlan's lease\n * timeline, keksdose's cash buffer).\n *\n * Unlike `ToggleLegend` it draws a single entry: a key with one line still says what\n * the one mark on the chart means.\n */\nexport function StaticLegend({\n entries,\n orientation = \"horizontal\",\n \"aria-label\": ariaLabel,\n className,\n labels: labelsProp,\n}: StaticLegendProps) {\n const labels = useKitLabels(\"seriesChart\", DEFAULT_SERIES_CHART_LABELS, labelsProp);\n if (entries.length === 0) return null;\n return (\n <ul\n aria-label={ariaLabel ?? labels.legend}\n className={cn(\n \"m-0 flex list-none gap-x-3 gap-y-1 p-0\",\n orientation === \"vertical\"\n ? \"flex-col items-start justify-center\"\n : \"mt-2 flex-wrap items-center\",\n className,\n )}\n >\n {entries.map((entry) => (\n <li\n key={entry.key}\n className={cn(\n \"flex items-center gap-1.5 text-[11px] text-[var(--text-secondary)]\",\n entry.align === \"end\" && \"ms-auto\",\n )}\n >\n <LegendMark entry={entry} off={false} />\n {entry.label}\n </li>\n ))}\n </ul>\n );\n}\n\n/**\n * The column a vertical legend stands in: beside the charts, on their horizontal\n * centre line. `h-full` + `justify-center`, so it centres in a grid cell or a flex row\n * that stretches it — and stacks whatever it is given, which is what a panel showing\n * two legends at once needs. The width is the caller's: how wide a legend column is\n * is a fact about that screen's layout.\n */\nexport function LegendColumn({ children, className }: { children: ReactNode; className?: string }) {\n return (\n <div className={cn(\"flex h-full flex-col justify-center gap-3\", className)}>{children}</div>\n );\n}\n\n/**\n * A legend and what it is a legend OF, as a headed column.\n *\n * For legends that are several vocabularies at once — which measurement and which\n * channel — so the entries are read down a group and the groups across. Wrapped rows\n * would put the second half of one group on a line under the first half of the next.\n */\nexport function LegendGroup({ title, children }: { title: ReactNode; children: ReactNode }) {\n return (\n <div className=\"flex min-w-0 flex-col items-start gap-1\">\n <span className=\"text-[10px] font-semibold uppercase tracking-wide text-[var(--text-muted)]\">\n {title}\n </span>\n <div className=\"flex flex-col items-start gap-y-0.5\">{children}</div>\n </div>\n );\n}\n\n/** The entry's mark: a square for a measurement, a stroke for a quantity. A hidden one\n * keeps its outline — a mark that vanished would leave a line of text with nothing in\n * front of it.\n *\n * A stroke is an SVG line with the chart's own `strokeDasharray`, not a CSS gradient\n * approximating it: a gradient can say \"solid or dashed\" and nothing more.\n *\n * A hidden stroke is NOT faded here: the button around it already is (`opacity-35`),\n * and the line used to take its own 0.35 on top — 0.35 × 0.35, about 12 %, a mark\n * gone rather than dimmed, while the square beside it read at the full 35 %. */\nfunction LegendMark({ entry, off }: { entry: LegendEntry; off: boolean }) {\n if (entry.icon != null) {\n return (\n <span aria-hidden className=\"inline-flex shrink-0 items-center\">\n {entry.icon}\n </span>\n );\n }\n if (entry.marker === \"dot\") {\n return (\n <span\n aria-hidden\n className=\"size-2 shrink-0 rounded-full\"\n style={{\n backgroundColor: off ? \"transparent\" : entry.color,\n boxShadow: `inset 0 0 0 1.5px ${entry.color}`,\n }}\n />\n );\n }\n if (entry.marker === \"stroke\") {\n return (\n <svg aria-hidden viewBox=\"0 0 20 2\" className=\"h-0.5 w-5 shrink-0 overflow-visible\">\n <line\n x1={0}\n y1={1}\n x2={20}\n y2={1}\n stroke={entry.color}\n strokeWidth={2}\n strokeDasharray={typeof entry.dash === \"string\" ? entry.dash : strokeDash(entry.dash ?? 0)}\n />\n </svg>\n );\n }\n return (\n <span\n aria-hidden\n className=\"h-2.5 w-2.5 shrink-0 rounded-[3px]\"\n style={{\n backgroundColor: off ? \"transparent\" : entry.color,\n boxShadow: `inset 0 0 0 1.5px ${entry.color}`,\n }}\n />\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/components/toggle-legend.tsx"],"sourcesContent":["// A legend whose entries are SWITCHES — and the stroke table it shares with\n// `SeriesChart`. Lifted out of lenkbank's measurement plots, where ten legends over\n// thirteen charts took channels on and off the picture.\n//\n// No recharts in this module: a legend of switches is plain buttons, and it is as\n// useful under a chart the consumer drew by hand as under `SeriesChart`.\nimport type { ReactNode } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { useKitLabels } from \"../i18n/kit-labels\";\nimport { DEFAULT_SERIES_CHART_LABELS, type SeriesChartLabels } from \"./series-chart-labels\";\n\n/**\n * The strokes a reader can tell apart, in the order they are handed out.\n *\n * For the charts where the colour is already spoken for: a comparison draws each\n * measurement in its own colour, so a chart carrying several CHANNELS of each has\n * nothing left but the stroke — and \"solid versus dashed\" only gets you two.\n *\n * **Five, and the fifth is the last one.** They are the draughtsman's line types, in\n * the draughtsman's order — continuous, dashed, dotted, dash-dot, dash-dot-dot — the\n * one family of five a century of drawings has already proved legible at a hair's\n * width. A sixth is not distinguishable from one of these at two pixels wide, so a\n * chart needing one is a chart that should be split.\n *\n * Kept here, beside the legend's own mark, and read by `SeriesChart` from here — so\n * the key under a chart and the lines on it cannot disagree.\n */\nexport const STROKE_PATTERNS: readonly (string | undefined)[] = [\n undefined,\n \"5 4\",\n \"1 3\",\n \"9 3 2 3\",\n \"9 3 2 3 2 3\",\n];\n\n/** The dash array for the nth pattern, wrapping past the fifth. */\nexport function strokeDash(order: number): string | undefined {\n const n = STROKE_PATTERNS.length;\n return STROKE_PATTERNS[((Math.trunc(order) % n) + n) % n];\n}\n\n/** Which of the patterns a `step` series is drawn in.\n *\n * A whole-number channel that jumps rather than travels is drawn dashed, and it has\n * to be one of {@link STROKE_PATTERNS} rather than a dash array written out at the\n * call site: a legend entry says its pattern by index, so a step line drawn in\n * anything else would be a legend promising a solid line for a dashed one. */\nexport const STEP_DASH = 1;\n\n/**\n * One key switched, as a new hidden set.\n *\n * Every caller writes `onToggle={(key) => setHidden(toggleHidden(hidden, key))}`. A\n * new set rather than a mutated one, because the state is read during render and a\n * mutation of something a component already holds is invisible to React.\n */\nexport function toggleHidden(hidden: ReadonlySet<string>, key: string): ReadonlySet<string> {\n const next = new Set(hidden);\n if (!next.delete(key)) next.add(key);\n return next;\n}\n\nexport interface LegendEntry {\n key: string;\n label: ReactNode;\n /** Any CSS colour — `paletteFor(i)` for the kit's own ramp. */\n color: string;\n /**\n * What the entry's mark looks like. `swatch` — a filled square, the default — says\n * WHICH MEASUREMENT. `stroke` says WHICH QUANTITY, for the charts that carry both at\n * once and tell them apart by colour and by dash. `dot` is a round mark, for a\n * MARKER on the chart (a point, an event) rather than a series.\n */\n marker?: \"swatch\" | \"stroke\" | \"dot\";\n /** Which of {@link STROKE_PATTERNS}, when the marker is a stroke — the same index\n * `SeriesChartSeries.dash` takes, so a legend cannot promise a dot-dash the plot\n * draws dashed. Or the same custom `stroke-dasharray` string the series draws with\n * (keksdose's `\"4 3\"`). */\n dash?: number | string;\n /**\n * A mark of the caller's own, drawn in place of {@link marker} — for a key whose\n * mark is not a colour at all (a fading bar for \"open-ended\", a hairline for\n * \"today\"). Decorative: it is wrapped `aria-hidden`, and the label is the name.\n */\n icon?: ReactNode;\n /**\n * `\"end\"`: push this entry to the legend's inline END — the far right in LTR, the far\n * left in RTL — with everything before it kept at the start. kastlan's lease gantt\n * (feedback #49) keys its bars at the start and its \"today\" hairline at the end, over\n * the time axis whose end \"today\" is nearest, and wrote it as\n * `className=\"[&>li:last-child]:ms-auto\"` — a selector that breaks the moment the\n * entries are reordered and that reaches into the legend's markup.\n *\n * An auto inline-start margin, so it is logical (RTL mirrors it) and it takes only the\n * row's LEFTOVER space: on a narrow screen where the row wraps, the entry wraps with it\n * and sits at the end of its own line rather than overflowing. Give it to the LAST\n * entry — auto margins push everything after them too, so an end entry in the middle\n * drags the rest over with it (which is also how to end-align a GROUP: mark its first).\n * In a vertical legend it sits at the column's inline end. Honoured by both\n * {@link StaticLegend} and {@link ToggleLegend}, which share this entry type.\n */\n align?: \"start\" | \"end\";\n}\n\nexport interface ToggleLegendProps {\n entries: LegendEntry[];\n /** The keys currently off the chart. */\n hidden: ReadonlySet<string>;\n onToggle: (key: string) => void;\n /** `vertical` is for a legend standing BESIDE the charts rather than under them —\n * what a row of charts sharing one legend wants, since under two charts there is\n * no \"under\", and putting it under one says it belongs to that one. */\n orientation?: \"horizontal\" | \"vertical\";\n /**\n * Draw a legend of ONE entry rather than nothing.\n *\n * A single line's legend is usually noise. It is not where the legend is the only\n * SWITCH that line has — a series that starts hidden and has no entry to bring it\n * back is gone for good.\n */\n showSingle?: boolean;\n className?: string;\n /** Per-instance strings over `<UiKitProvider labels={{ seriesChart }}>`. */\n labels?: Partial<SeriesChartLabels>;\n}\n\n/**\n * A legend whose entries are switches.\n *\n * `ChartLegendContent` with `onItemClick`/`activeKey` is the other kind: clicking an\n * entry HIGHLIGHTS it and dims the rest, which is right when every series belongs on\n * the chart and one of them is momentarily interesting. This is for the opposite —\n * three coordinates on one axis where y runs to 800 and z barely moves, and the only\n * way to see z is to take y off the chart entirely. Any number may be off at once.\n *\n * A hidden entry keeps its mark and its place. Removing it would reflow the legend on\n * every click and lose the one thing it is for: saying what COULD be shown.\n *\n * Each entry is a real `<button>` with `aria-pressed` — pressed means \"on the chart\" —\n * so Tab reaches every switch and Space/Enter flips it.\n */\nexport function ToggleLegend({\n entries,\n hidden,\n onToggle,\n orientation = \"horizontal\",\n showSingle = false,\n className,\n labels: labelsProp,\n}: ToggleLegendProps) {\n const labels = useKitLabels(\"seriesChart\", DEFAULT_SERIES_CHART_LABELS, labelsProp);\n if (entries.length < (showSingle ? 1 : 2)) return null;\n return (\n <div\n role=\"group\"\n aria-label={labels.legend}\n className={cn(\n \"flex gap-x-3 gap-y-1\",\n orientation === \"vertical\"\n ? \"flex-col items-start justify-center\"\n : \"mt-2 flex-wrap items-center\",\n className,\n )}\n >\n {entries.map((entry) => {\n const off = hidden.has(entry.key);\n return (\n <button\n key={entry.key}\n type=\"button\"\n // Pressed rather than a checkbox: it is a control over what the chart\n // draws, and \"not pressed\" is the same sentence the dimmed mark says.\n aria-pressed={!off}\n onClick={() => onToggle(entry.key)}\n className={cn(\n \"flex items-center gap-1.5 rounded text-start text-[11px] text-[var(--text-secondary)] transition-opacity hover:opacity-80\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--brand)]\",\n off && \"opacity-35\",\n entry.align === \"end\" && \"ms-auto\",\n )}\n >\n <LegendMark entry={entry} off={off} />\n {entry.label}\n </button>\n );\n })}\n </div>\n );\n}\n\nexport interface StaticLegendProps {\n entries: LegendEntry[];\n /** See {@link ToggleLegendProps.orientation}. */\n orientation?: \"horizontal\" | \"vertical\";\n /** The list's accessible name. Default: the `seriesChart.legend` label. */\n \"aria-label\"?: string;\n /**\n * `ul` (default): a list — \"list, 4 items\", walked with the list keys. Its entries are\n * `listitem`s, so a host test that counts `getAllByRole(\"listitem\")` on a page with\n * the legend on it counts the legend too: scope it (`within(...)`) or use `div`.\n *\n * `div`: a named `role=\"group\"` of plain entries, for a legend set inside something\n * that is already a list or a table row, where a list in a list reads as nesting that\n * is not there, or where the entries are too few for \"list, 2 items\" to help. The\n * entries are read in order either way; only the list framing goes.\n */\n as?: \"ul\" | \"div\";\n className?: string;\n /** Per-instance strings over `<UiKitProvider labels={{ seriesChart }}>`. */\n labels?: Partial<SeriesChartLabels>;\n}\n\n/**\n * A legend that is a KEY, not a control: the same entries and marks as\n * {@link ToggleLegend} — swatch, stroke (with the chart's own dash), dot, or a mark of\n * the caller's own — but nothing to press.\n *\n * A separate component rather than `ToggleLegend` without `onToggle`, because the two\n * are different things to a screen reader: a group of switches announces a pressed\n * state on every entry, a key is a LIST, read as \"list, 4 items\" and walked with the\n * list keys, and costs no tab stops. The apps drew this by hand under the charts that\n * say what a colour or a dash means without letting you switch it (kastlan's lease\n * timeline, keksdose's cash buffer).\n *\n * Unlike `ToggleLegend` it draws a single entry: a key with one line still says what\n * the one mark on the chart means.\n */\nexport function StaticLegend({\n entries,\n orientation = \"horizontal\",\n \"aria-label\": ariaLabel,\n as = \"ul\",\n className,\n labels: labelsProp,\n}: StaticLegendProps) {\n const labels = useKitLabels(\"seriesChart\", DEFAULT_SERIES_CHART_LABELS, labelsProp);\n if (entries.length === 0) return null;\n const List = as;\n const Item = as === \"ul\" ? \"li\" : \"div\";\n return (\n <List\n // A `div` takes `group`: a name on a role-less div is one ARIA forbids and\n // screen readers drop, and the name is what says these marks are a key.\n role={as === \"div\" ? \"group\" : undefined}\n aria-label={ariaLabel ?? labels.legend}\n className={cn(\n \"m-0 flex list-none gap-x-3 gap-y-1 p-0\",\n orientation === \"vertical\"\n ? \"flex-col items-start justify-center\"\n : \"mt-2 flex-wrap items-center\",\n className,\n )}\n >\n {entries.map((entry) => (\n <Item\n key={entry.key}\n className={cn(\n \"flex items-center gap-1.5 text-[11px] text-[var(--text-secondary)]\",\n entry.align === \"end\" && \"ms-auto\",\n )}\n >\n <LegendMark entry={entry} off={false} />\n {entry.label}\n </Item>\n ))}\n </List>\n );\n}\n\n/**\n * The column a vertical legend stands in: beside the charts, on their horizontal\n * centre line. `h-full` + `justify-center`, so it centres in a grid cell or a flex row\n * that stretches it — and stacks whatever it is given, which is what a panel showing\n * two legends at once needs. The width is the caller's: how wide a legend column is\n * is a fact about that screen's layout.\n */\nexport function LegendColumn({ children, className }: { children: ReactNode; className?: string }) {\n return (\n <div className={cn(\"flex h-full flex-col justify-center gap-3\", className)}>{children}</div>\n );\n}\n\n/**\n * A legend and what it is a legend OF, as a headed column.\n *\n * For legends that are several vocabularies at once — which measurement and which\n * channel — so the entries are read down a group and the groups across. Wrapped rows\n * would put the second half of one group on a line under the first half of the next.\n */\nexport function LegendGroup({ title, children }: { title: ReactNode; children: ReactNode }) {\n return (\n <div className=\"flex min-w-0 flex-col items-start gap-1\">\n <span className=\"text-[10px] font-semibold uppercase tracking-wide text-[var(--text-muted)]\">\n {title}\n </span>\n <div className=\"flex flex-col items-start gap-y-0.5\">{children}</div>\n </div>\n );\n}\n\n/** The entry's mark: a square for a measurement, a stroke for a quantity. A hidden one\n * keeps its outline — a mark that vanished would leave a line of text with nothing in\n * front of it.\n *\n * A stroke is an SVG line with the chart's own `strokeDasharray`, not a CSS gradient\n * approximating it: a gradient can say \"solid or dashed\" and nothing more.\n *\n * A hidden stroke is NOT faded here: the button around it already is (`opacity-35`),\n * and the line used to take its own 0.35 on top — 0.35 × 0.35, about 12 %, a mark\n * gone rather than dimmed, while the square beside it read at the full 35 %. */\nfunction LegendMark({ entry, off }: { entry: LegendEntry; off: boolean }) {\n if (entry.icon != null) {\n return (\n <span aria-hidden className=\"inline-flex shrink-0 items-center\">\n {entry.icon}\n </span>\n );\n }\n if (entry.marker === \"dot\") {\n return (\n <span\n aria-hidden\n className=\"size-2 shrink-0 rounded-full\"\n style={{\n backgroundColor: off ? \"transparent\" : entry.color,\n boxShadow: `inset 0 0 0 1.5px ${entry.color}`,\n }}\n />\n );\n }\n if (entry.marker === \"stroke\") {\n return (\n <svg aria-hidden viewBox=\"0 0 20 2\" className=\"h-0.5 w-5 shrink-0 overflow-visible\">\n <line\n x1={0}\n y1={1}\n x2={20}\n y2={1}\n stroke={entry.color}\n strokeWidth={2}\n strokeDasharray={typeof entry.dash === \"string\" ? entry.dash : strokeDash(entry.dash ?? 0)}\n />\n </svg>\n );\n }\n return (\n <span\n aria-hidden\n className=\"h-2.5 w-2.5 shrink-0 rounded-[3px]\"\n style={{\n backgroundColor: off ? \"transparent\" : entry.color,\n boxShadow: `inset 0 0 0 1.5px ${entry.color}`,\n }}\n />\n );\n}\n"],"mappings":"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","names":[]}
@@ -1,6 +1,6 @@
1
1
  import 'react';
2
2
  import './tooltip.js';
3
- export { B as Button, a as ButtonClassesOptions, b as ButtonLinkProps, c as ButtonProps, d as ButtonSize, e as ButtonTone, f as ButtonVariant, g as Card, h as CardAction, i as CardActionProps, j as CardContent, k as CardContentProps, l as CardDescription, m as CardDescriptionProps, n as CardFooter, o as CardFooterProps, p as CardHeader, q as CardHeaderProps, r as CardProps, s as CardTitle, t as CardTitleProps, u as CardTone, Q as DEFAULT_PASSWORD_REVEAL_LABELS, T as DEFAULT_TABS_LABELS, W as EmptyState, X as EmptyStateProps, Y as FIELD_BASE, Z as FIELD_DISPLAY, _ as FIELD_FLOATING_PAD, $ as FIELD_INVALID, a0 as FIELD_TRIGGER, a1 as FIELD_WRITABLE_LOOK, a2 as FLOATING_INPUT_CLASS, a3 as FLOATING_LABEL_CLASS, a4 as FLOATING_LABEL_STATIC, a5 as FieldChevron, a6 as FieldChevronProps, a7 as FieldHint, a8 as FieldHintProps, a9 as FieldLabel, aa as FieldLabelProps, ai as FloatingField, aj as FloatingFieldProps, aq as IconButton, ar as IconButtonGlyphSize, as as IconButtonLinkProps, at as IconButtonProps, au as IconButtonSize, av as IconButtonTone, aw as IconButtonToneColor, ax as IconButtonVariant, ay as Input, az as InputProps, aC as Label, aE as LabelProps, aG as PHONE_QUERY, aH as PasswordRevealLabels, aJ as Select, aK as SelectProps, aL as Spinner, aM as SpinnerProps, aO as TabItem, aP as Tabs, aQ as TabsLabels, aR as TabsProps, aS as Textarea, aT as TextareaProps, a$ as buttonClasses, b3 as resolvePasswordRevealLabels } from '../kit-labels-BD2F7sOz.js';
3
+ export { B as Button, a as ButtonClassesOptions, b as ButtonLinkProps, c as ButtonProps, d as ButtonSize, e as ButtonTone, f as ButtonVariant, g as Card, h as CardAction, i as CardActionProps, j as CardContent, k as CardContentProps, l as CardDescription, m as CardDescriptionProps, n as CardFooter, o as CardFooterProps, p as CardHeader, q as CardHeaderProps, r as CardProps, s as CardTitle, t as CardTitleLevel, u as CardTitleProps, v as CardTone, R as DEFAULT_PASSWORD_REVEAL_LABELS, U as DEFAULT_TABS_LABELS, X as EmptyState, Y as EmptyStateProps, Z as FIELD_BASE, _ as FIELD_DISPLAY, $ as FIELD_FLOATING_PAD, a0 as FIELD_INVALID, a1 as FIELD_TRIGGER, a2 as FIELD_WRITABLE_LOOK, a3 as FLOATING_INPUT_CLASS, a4 as FLOATING_LABEL_CLASS, a5 as FLOATING_LABEL_STATIC, a6 as FieldChevron, a7 as FieldChevronProps, a8 as FieldHint, a9 as FieldHintProps, aa as FieldLabel, ab as FieldLabelProps, aj as FloatingField, ak as FloatingFieldProps, at as IconButton, au as IconButtonGlyphSize, av as IconButtonLinkProps, aw as IconButtonProps, ax as IconButtonSize, ay as IconButtonTone, az as IconButtonToneColor, aA as IconButtonVariant, aB as Input, aC as InputProps, aF as Label, aH as LabelProps, aJ as PHONE_QUERY, aK as PasswordRevealLabels, aM as Select, aN as SelectProps, aO as Spinner, aP as SpinnerProps, aR as TabItem, aS as Tabs, aT as TabsLabels, aU as TabsProps, aV as Textarea, aW as TextareaProps, b2 as buttonClasses, b6 as resolvePasswordRevealLabels } from '../kit-labels-DE--LmCk.js';
4
4
  import '../hooks/use-anchored-rect.js';
5
5
  import '../lib/clipping.js';
6
6
  import './data-table-labels.js';
@@ -31,7 +31,7 @@ import './dialog-frame.js';
31
31
  import './modal.js';
32
32
  import '../hooks/use-search-param-state.js';
33
33
  import './measured-grid.js';
34
- import '../feedback-attachment-BYAHJMOr.js';
34
+ import '../feedback-attachment-WFiXJ8We.js';
35
35
  import '../feedback/feedback-thread.js';
36
36
  import '../feedback/feedback-inbox.js';
37
37
  import './account-settings-labels.js';
@@ -39,12 +39,12 @@ import './confirm-dialog.js';
39
39
  import './floating-panel.js';
40
40
  import './bulk-action-bar.js';
41
41
  import './list.js';
42
+ import './signed-amount.js';
42
43
  import './breadcrumbs.js';
43
44
  import './toast.js';
44
45
  import './description-list.js';
45
46
  import './line-items.js';
46
47
  import './progress-bar.js';
47
- import './signed-amount.js';
48
48
  import './error-boundary.js';
49
49
  import './authed-image.js';
50
50
  import '../hooks/use-authed-src.js';