@hjmds/react 1.19.0 → 1.21.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 (84) hide show
  1. package/dist/actions.d.ts +10 -0
  2. package/dist/actions.d.ts.map +1 -1
  3. package/dist/actions.js +9 -3
  4. package/dist/actions.js.map +1 -1
  5. package/dist/authoring-tools.d.ts +38 -0
  6. package/dist/authoring-tools.d.ts.map +1 -0
  7. package/dist/authoring-tools.js +65 -0
  8. package/dist/authoring-tools.js.map +1 -0
  9. package/dist/bottom-navigation.d.ts.map +1 -1
  10. package/dist/bottom-navigation.js +1 -1
  11. package/dist/bottom-navigation.js.map +1 -1
  12. package/dist/calendar.d.ts.map +1 -1
  13. package/dist/calendar.js +1 -0
  14. package/dist/calendar.js.map +1 -1
  15. package/dist/chart.d.ts +157 -0
  16. package/dist/chart.d.ts.map +1 -0
  17. package/dist/chart.js +467 -0
  18. package/dist/chart.js.map +1 -0
  19. package/dist/collection-editing.d.ts +66 -0
  20. package/dist/collection-editing.d.ts.map +1 -0
  21. package/dist/collection-editing.js +126 -0
  22. package/dist/collection-editing.js.map +1 -0
  23. package/dist/communication.d.ts +51 -0
  24. package/dist/communication.d.ts.map +1 -0
  25. package/dist/communication.js +71 -0
  26. package/dist/communication.js.map +1 -0
  27. package/dist/data-table.d.ts +21 -1
  28. package/dist/data-table.d.ts.map +1 -1
  29. package/dist/data-table.js +105 -10
  30. package/dist/data-table.js.map +1 -1
  31. package/dist/data-workflows.d.ts +67 -0
  32. package/dist/data-workflows.d.ts.map +1 -0
  33. package/dist/data-workflows.js +60 -0
  34. package/dist/data-workflows.js.map +1 -0
  35. package/dist/evidence.d.ts +1 -1
  36. package/dist/evidence.js +1 -1
  37. package/dist/evidence.js.map +1 -1
  38. package/dist/internal/use-composition-action.d.ts +17 -0
  39. package/dist/internal/use-composition-action.d.ts.map +1 -0
  40. package/dist/internal/use-composition-action.js +18 -0
  41. package/dist/internal/use-composition-action.js.map +1 -0
  42. package/dist/media-transport.d.ts +22 -0
  43. package/dist/media-transport.d.ts.map +1 -0
  44. package/dist/media-transport.js +50 -0
  45. package/dist/media-transport.js.map +1 -0
  46. package/dist/navigation.d.ts +3 -1
  47. package/dist/navigation.d.ts.map +1 -1
  48. package/dist/navigation.js +29 -5
  49. package/dist/navigation.js.map +1 -1
  50. package/dist/screen-flows.d.ts +1 -1
  51. package/dist/screen-flows.d.ts.map +1 -1
  52. package/dist/screen-flows.js +1 -1
  53. package/dist/screen-flows.js.map +1 -1
  54. package/dist/screens.d.ts +8 -2
  55. package/dist/screens.d.ts.map +1 -1
  56. package/dist/screens.js +11 -5
  57. package/dist/screens.js.map +1 -1
  58. package/dist/social-compositions.d.ts +52 -0
  59. package/dist/social-compositions.d.ts.map +1 -0
  60. package/dist/social-compositions.js +41 -0
  61. package/dist/social-compositions.js.map +1 -0
  62. package/dist/streaming-answer.d.ts +24 -0
  63. package/dist/streaming-answer.d.ts.map +1 -0
  64. package/dist/streaming-answer.js +57 -0
  65. package/dist/streaming-answer.js.map +1 -0
  66. package/dist/styles.css +149 -1
  67. package/dist/styles.layered.css +149 -1
  68. package/dist/table-family.d.ts +66 -0
  69. package/dist/table-family.d.ts.map +1 -0
  70. package/dist/table-family.js +75 -0
  71. package/dist/table-family.js.map +1 -0
  72. package/dist/task-controls.d.ts +47 -0
  73. package/dist/task-controls.d.ts.map +1 -0
  74. package/dist/task-controls.js +46 -0
  75. package/dist/task-controls.js.map +1 -0
  76. package/dist/toggle-feedback.d.ts +26 -0
  77. package/dist/toggle-feedback.d.ts.map +1 -0
  78. package/dist/toggle-feedback.js +47 -0
  79. package/dist/toggle-feedback.js.map +1 -0
  80. package/dist/transfer-task.d.ts +18 -0
  81. package/dist/transfer-task.d.ts.map +1 -0
  82. package/dist/transfer-task.js +56 -0
  83. package/dist/transfer-task.js.map +1 -0
  84. package/package.json +58 -3
@@ -0,0 +1,71 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useEffect, useRef, useState } from "react";
4
+ import { resolveSearchCommit } from "@hjmds/design-contracts/screen-patterns";
5
+ import { validateReplyTarget } from "@hjmds/design-contracts/communication";
6
+ import { ChatMessage, MessageComposer } from "./screens.js";
7
+ import { SearchScreen } from "./screen-flows.js";
8
+ import { Button } from "./actions.js";
9
+ import { Stack, Text } from "./layout.js";
10
+ import { useCompositionAction } from "./internal/use-composition-action.js";
11
+ export function ReplyMessage({ messageId, onReply, replyLabel, replyDisabled = false, swipeAction = "reply", ...message }) {
12
+ if (!messageId.trim())
13
+ throw new TypeError("Reply message requires the actual message ID");
14
+ return _jsx(ChatMessage, { ...message, timestampPresentation: swipeAction === "timestamp" ? "swipe" : "always", replyAction: { label: replyLabel, onPress: () => onReply(messageId), disabled: replyDisabled } });
15
+ }
16
+ export function ReplyComposer({ replyTarget, onReplyCancel, cancelReplyLabel, errorLabel, onSend, ...composer }) {
17
+ validateReplyTarget(replyTarget);
18
+ const action = useCompositionAction(composer.disabled);
19
+ return _jsxs(Stack, { gap: "sm", children: [_jsx(MessageComposer, { ...composer, pending: action.pending, ...(replyTarget ? { replyTo: { author: replyTarget.author, excerpt: replyTarget.excerpt, cancelLabel: cancelReplyLabel, onCancel: onReplyCancel } } : {}), onSend: value => { const id = replyTarget?.id ?? null; void action.run("send", () => onSend(value, { replyToId: id })); } }), action.failed ? _jsx(Text, { role: "alert", tone: "danger", style: { textAlign: "center" }, children: errorLabel }) : null] });
20
+ }
21
+ export function ExpandableSearch(props) {
22
+ const [snapshot, setSnapshot] = useState(null), [pending, setPending] = useState(false), [failed, setFailed] = useState(false), [committed, setCommitted] = useState("");
23
+ const latest = useRef(props);
24
+ latest.current = props;
25
+ const generation = useRef(0), controller = useRef(null);
26
+ const host = useRef(null), opener = useRef(null), restore = useRef(false);
27
+ useEffect(() => { if (!props.expanded) {
28
+ controller.current?.abort();
29
+ generation.current++;
30
+ setPending(false);
31
+ setCommitted("");
32
+ } }, [props.expanded]);
33
+ useEffect(() => () => { generation.current++; controller.current?.abort(); }, []);
34
+ useEffect(() => { if (props.expanded)
35
+ host.current?.querySelector("input")?.focus();
36
+ else if (restore.current) {
37
+ opener.current?.focus();
38
+ restore.current = false;
39
+ } }, [props.expanded]);
40
+ const search = (query, { signal }) => {
41
+ controller.current?.abort();
42
+ const token = ++generation.current, request = new AbortController();
43
+ controller.current = request;
44
+ const abort = () => request.abort();
45
+ signal.addEventListener("abort", abort, { once: true });
46
+ if (signal.aborted)
47
+ request.abort();
48
+ // Some adapters ignore abort; generation and current-query checks still prevent stale UI writes.
49
+ const valid = () => !request.signal.aborted && token === generation.current && latest.current.expanded && latest.current.query === query;
50
+ if (!query.trim()) {
51
+ setSnapshot(null);
52
+ setPending(false);
53
+ setFailed(false);
54
+ signal.removeEventListener("abort", abort);
55
+ return;
56
+ }
57
+ setPending(true);
58
+ setFailed(false);
59
+ const execute = latest.current.onSearch;
60
+ void Promise.resolve().then(() => request.signal.aborted ? null : execute(query, { signal: request.signal })).then(response => { if (valid() && response)
61
+ setSnapshot({ query, response }); }, () => { if (valid())
62
+ setFailed(true); }).finally(() => { signal.removeEventListener("abort", abort); if (valid())
63
+ setPending(false); });
64
+ };
65
+ const cancel = () => { controller.current?.abort(); generation.current++; setPending(false); restore.current = true; props.onExpandedChange(false); };
66
+ const commit = (query) => { const normalized = resolveSearchCommit(query); if (normalized === null)
67
+ return; setCommitted(normalized); props.onSubmit?.(normalized); };
68
+ const current = snapshot?.query === props.query ? snapshot.response : null;
69
+ return _jsx("div", { ref: host, children: _jsx(Stack, { gap: "sm", children: !props.expanded ? _jsx(Button, { ref: opener, disabled: props.disabled === true, onClick: () => props.onExpandedChange(true), children: props.labels.open }) : _jsxs(_Fragment, { children: [_jsx(Button, { tone: "ghost", onClick: cancel, children: props.labels.cancel }), _jsx(SearchScreen, { title: props.labels.title, titleVisibility: "hidden", queryLabel: props.labels.query, queryClearLabel: props.labels.clear, query: props.query, onQueryChange: props.onQueryChange, onSearch: search, ...(props.debounceMs === undefined ? {} : { debounceMs: props.debounceMs }), ...(props.recentQueries ? { recentQueries: props.recentQueries } : {}), committedQuery: committed, onSubmit: commit, searching: pending, searchingLabel: props.labels.searching, suggestions: { items: current?.suggestions ?? [], commitLabel: props.labels.commit, countLabel: props.labels.suggestionCount }, children: current ? props.renderResult(current.result) : null }), failed ? _jsx(Text, { role: "alert", tone: "danger", style: { textAlign: "center" }, children: props.labels.error }) : null] }) }) });
70
+ }
71
+ //# sourceMappingURL=communication.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"communication.js","sourceRoot":"","sources":["../src/communication.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\nimport { useEffect,useRef,useState,type ReactNode } from \"react\";\nimport { resolveSearchCommit } from \"@hjmds/design-contracts/screen-patterns\";\nimport { validateReplyTarget,type ReplyTarget,type SearchResponse } from \"@hjmds/design-contracts/communication\";\nimport { ChatMessage,MessageComposer,type ChatMessageProps,type MessageComposerProps } from \"./screens.js\";\nimport { SearchScreen,type SearchRecentQueries } from \"./screen-flows.js\";\nimport { Button } from \"./actions.js\";\nimport { Stack,Text } from \"./layout.js\";\nimport { useCompositionAction } from \"./internal/use-composition-action.js\";\n\nexport type ReplyMessageProps = Omit<ChatMessageProps,\"replyAction\"|\"timestampPresentation\"> & Readonly<{\n messageId: string; onReply: (messageId:string)=>void; replyLabel: string; replyDisabled?: boolean;\n /** A drag has one owner: reply, or time reveal. Accessible reply stays available in either mode. */\n swipeAction?: \"reply\"|\"timestamp\";\n}>;\nexport function ReplyMessage({messageId,onReply,replyLabel,replyDisabled=false,swipeAction=\"reply\",...message}:ReplyMessageProps){\n if(!messageId.trim())throw new TypeError(\"Reply message requires the actual message ID\");\n return <ChatMessage {...message} timestampPresentation={swipeAction===\"timestamp\"?\"swipe\":\"always\"} replyAction={{label:replyLabel,onPress:()=>onReply(messageId),disabled:replyDisabled}}/>;\n}\nexport type ReplyComposerProps=Omit<MessageComposerProps,\"onSend\"|\"replyTo\"|\"pending\"|\"error\"> & Readonly<{\n replyTarget: ReplyTarget|null; onReplyCancel:()=>void; cancelReplyLabel:string; errorLabel:string;\n /** Resolve only after the actual send acknowledgement. The controlled draft is never cleared here. */\n onSend:(value:string,context:Readonly<{replyToId:string|null}>)=>void|Promise<void>;\n}>;\nexport function ReplyComposer({replyTarget,onReplyCancel,cancelReplyLabel,errorLabel,onSend,...composer}:ReplyComposerProps){\n validateReplyTarget(replyTarget);const action=useCompositionAction(composer.disabled);\n return <Stack gap=\"sm\"><MessageComposer {...composer} pending={action.pending}\n {...(replyTarget?{replyTo:{author:replyTarget.author,excerpt:replyTarget.excerpt,cancelLabel:cancelReplyLabel,onCancel:onReplyCancel}}:{})}\n onSend={value=>{const id=replyTarget?.id??null;void action.run(\"send\",()=>onSend(value,{replyToId:id}));}}/>\n {action.failed?<Text role=\"alert\" tone=\"danger\" style={{textAlign:\"center\"}}>{errorLabel}</Text>:null}</Stack>;\n}\nexport type ExpandableSearchProps<T>=Readonly<{\n expanded:boolean;onExpandedChange:(expanded:boolean)=>void;query:string;onQueryChange:(query:string)=>void;\n onSearch:(query:string,context:Readonly<{signal:AbortSignal}>)=>Promise<SearchResponse<T>>;\n renderResult:(result:T)=>ReactNode;onSubmit?:(query:string)=>void;recentQueries?:SearchRecentQueries;debounceMs?:number;disabled?:boolean;\n labels:Readonly<{title:string;open:string;cancel:string;query:string;clear:string;searching:string;error:string;commit:(query:string)=>string;suggestionCount:(count:number)=>string}>;\n}>;\nexport function ExpandableSearch<T>(props:ExpandableSearchProps<T>){\n const [snapshot,setSnapshot]=useState<Readonly<{query:string;response:SearchResponse<T>}>|null>(null),[pending,setPending]=useState(false),[failed,setFailed]=useState(false),[committed,setCommitted]=useState(\"\");\n const latest=useRef(props);latest.current=props;const generation=useRef(0),controller=useRef<AbortController|null>(null);\n const host=useRef<HTMLDivElement>(null),opener=useRef<HTMLButtonElement>(null),restore=useRef(false);\n useEffect(()=>{if(!props.expanded){controller.current?.abort();generation.current++;setPending(false);setCommitted(\"\");}},[props.expanded]);\n useEffect(()=>()=>{generation.current++;controller.current?.abort();},[]);\n useEffect(()=>{if(props.expanded)host.current?.querySelector(\"input\")?.focus();else if(restore.current){opener.current?.focus();restore.current=false;}},[props.expanded]);\n const search=(query:string,{signal}:Readonly<{signal:AbortSignal}>)=>{\n controller.current?.abort();const token=++generation.current,request=new AbortController();controller.current=request;\n const abort=()=>request.abort();signal.addEventListener(\"abort\",abort,{once:true});if(signal.aborted)request.abort();\n // Some adapters ignore abort; generation and current-query checks still prevent stale UI writes.\n const valid=()=>!request.signal.aborted&&token===generation.current&&latest.current.expanded&&latest.current.query===query;\n if(!query.trim()){setSnapshot(null);setPending(false);setFailed(false);signal.removeEventListener(\"abort\",abort);return;}\n setPending(true);setFailed(false);\n const execute=latest.current.onSearch;\n void Promise.resolve().then(()=>request.signal.aborted?null:execute(query,{signal:request.signal})).then(response=>{if(valid()&&response)setSnapshot({query,response});},()=>{if(valid())setFailed(true);}).finally(()=>{signal.removeEventListener(\"abort\",abort);if(valid())setPending(false);});\n };\n const cancel=()=>{controller.current?.abort();generation.current++;setPending(false);restore.current=true;props.onExpandedChange(false);};\n const commit=(query:string)=>{const normalized=resolveSearchCommit(query);if(normalized===null)return;setCommitted(normalized);props.onSubmit?.(normalized);};\n const current=snapshot?.query===props.query?snapshot.response:null;\n return <div ref={host}><Stack gap=\"sm\">{!props.expanded?<Button ref={opener} disabled={props.disabled===true} onClick={()=>props.onExpandedChange(true)}>{props.labels.open}</Button>:<>\n <Button tone=\"ghost\" onClick={cancel}>{props.labels.cancel}</Button>\n <SearchScreen title={props.labels.title} titleVisibility=\"hidden\" queryLabel={props.labels.query} queryClearLabel={props.labels.clear} query={props.query} onQueryChange={props.onQueryChange} onSearch={search}\n {...(props.debounceMs===undefined?{}:{debounceMs:props.debounceMs})} {...(props.recentQueries?{recentQueries:props.recentQueries}:{})}\n committedQuery={committed} onSubmit={commit} searching={pending} searchingLabel={props.labels.searching}\n suggestions={{items:current?.suggestions??[],commitLabel:props.labels.commit,countLabel:props.labels.suggestionCount}}>\n {current?props.renderResult(current.result):null}\n </SearchScreen>{failed?<Text role=\"alert\" tone=\"danger\" style={{textAlign:\"center\"}}>{props.labels.error}</Text>:null}</>}</Stack></div>;\n}\n"]}
@@ -1,6 +1,7 @@
1
- import { type DataTableAsyncState, type DataTableColumnDescriptor, type DataTableDensity, type DataTableRowDescriptor, type DataTableSelection, type DataTableSortCycle, type DataTableSortState } from "@hjmds/design-contracts/components/data-table";
1
+ import { type DataTableAsyncState, type DataTableCellHeat, type DataTableCellTone, type DataTableColumnDescriptor, type DataTableColumnGroup, type DataTableDensity, type DataTableRowDescriptor, type DataTableRowGroup, type DataTableSelection, type DataTableSortCycle, type DataTableSortState } from "@hjmds/design-contracts/components/data-table";
2
2
  import { type ReactNode } from "react";
3
3
  import type { HjmCompositionStyleProp } from "./composition-style.js";
4
+ import { type TableHeatColors } from "./table-family.js";
4
5
  export type DataTableLabels = Readonly<{
5
6
  /** Accessible name for the table itself. */
6
7
  table: string;
@@ -23,6 +24,24 @@ export type DataTableProps<ColumnKey extends string = string, RowKey extends str
23
24
  density?: DataTableDensity;
24
25
  /** Composed beneath the table by the product — pagination, load more, totals. */
25
26
  footer?: ReactNode;
27
+ /**
28
+ * Optional summary row rendered as the table's `tfoot` (totals, averages). Returns the cell for
29
+ * each column; HJM never computes the aggregate (2026-10-14, stat tables).
30
+ */
31
+ summaryRow?: (columnId: ColumnKey) => ReactNode;
32
+ /** Header bands above leaf columns (타격 | 주루). */
33
+ columnGroups?: readonly DataTableColumnGroup<ColumnKey>[];
34
+ /** Labeled row sections (선발 / 교체); each renders as its own `tbody`. */
35
+ rowGroups?: readonly DataTableRowGroup<RowKey>[];
36
+ /** Per-cell emphasis decided by the product (AG Grid cellClassRules / Savant percentile heat). */
37
+ cellStyle?: (rowId: RowKey, columnId: ColumnKey) => Readonly<{
38
+ tone?: DataTableCellTone | null;
39
+ heat?: DataTableCellHeat | null;
40
+ }> | null;
41
+ /** Heat base colors (any CSS color); default high = danger red, low = info blue (Savant). */
42
+ heatColors?: TableHeatColors;
43
+ /** Bounds the scroll box height in px; the header stays fixed while rows scroll. */
44
+ maxBodyHeight?: number;
26
45
  className?: string;
27
46
  /** Canonical layout-only placement on the root wrapper. Controlled visual keys are excluded. */
28
47
  layoutStyle?: HjmCompositionStyleProp;
@@ -30,4 +49,5 @@ export type DataTableProps<ColumnKey extends string = string, RowKey extends str
30
49
  export declare const DataTable: <ColumnKey extends string = string, RowKey extends string = string>(props: DataTableProps<ColumnKey, RowKey> & {
31
50
  ref?: React.Ref<HTMLTableElement>;
32
51
  }) => React.ReactElement | null;
52
+ export { LineScore, MatrixTable, ComparisonTable, type LineScoreProps, type MatrixTableProps, type ComparisonTableProps, type TableHeatColors, } from "./table-family.js";
33
53
  //# sourceMappingURL=data-table.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"data-table.d.ts","sourceRoot":"","sources":["../src/data-table.tsx"],"names":[],"mappings":"AACA,OAAO,EAQL,KAAK,mBAAmB,EACxB,KAAK,yBAAyB,EAC9B,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACxB,MAAM,+CAA+C,CAAC;AACvD,OAAO,EAA4C,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAGjF,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAEtE,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IACrC,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,6DAA6D;IAC7D,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACrC,iFAAiF;IACjF,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,kBAAkB,CAAC,MAAM,CAAC,KAAK,MAAM,CAAC;CAC/E,CAAC,CAAC;AAEH,MAAM,MAAM,cAAc,CAAC,SAAS,SAAS,MAAM,GAAG,MAAM,EAAE,MAAM,SAAS,MAAM,GAAG,MAAM,IAAI,QAAQ,CAAC;IACvG,OAAO,EAAE,SAAS,yBAAyB,CAAC,SAAS,CAAC,EAAE,CAAC;IACzD,IAAI,EAAE,SAAS,sBAAsB,CAAC,MAAM,CAAC,EAAE,CAAC;IAChD,MAAM,EAAE,eAAe,CAAC;IACxB,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,KAAK,SAAS,CAAC;IAC9D,SAAS,CAAC,EAAE,kBAAkB,CAAC,MAAM,CAAC,CAAC;IACvC,SAAS,CAAC,EAAE,kBAAkB,CAAC,SAAS,CAAC,CAAC;IAC1C,SAAS,CAAC,EAAE,kBAAkB,CAAC;IAC/B,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,kBAAkB,CAAC,SAAS,CAAC,KAAK,IAAI,CAAC;IAC7D,UAAU,CAAC,EAAE,mBAAmB,CAAC;IACjC,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,iFAAiF;IACjF,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gGAAgG;IAChG,WAAW,CAAC,EAAE,uBAAuB,CAAC;CACvC,CAAC,CAAC;AAaH,eAAO,MAAM,SAAS,EAyJhB,CAAC,SAAS,SAAS,MAAM,GAAG,MAAM,EAAE,MAAM,SAAS,MAAM,GAAG,MAAM,EACtE,KAAK,EAAE,cAAc,CAAC,SAAS,EAAE,MAAM,CAAC,GAAG;IAAE,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAA;CAAE,KAC7E,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC"}
1
+ {"version":3,"file":"data-table.d.ts","sourceRoot":"","sources":["../src/data-table.tsx"],"names":[],"mappings":"AACA,OAAO,EAaL,KAAK,mBAAmB,EAExB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,yBAAyB,EAC9B,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACxB,MAAM,+CAA+C,CAAC;AACvD,OAAO,EAA8E,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAGnH,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACtE,OAAO,EAAqB,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAE5E,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IACrC,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,6DAA6D;IAC7D,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACrC,iFAAiF;IACjF,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,kBAAkB,CAAC,MAAM,CAAC,KAAK,MAAM,CAAC;CAC/E,CAAC,CAAC;AAEH,MAAM,MAAM,cAAc,CAAC,SAAS,SAAS,MAAM,GAAG,MAAM,EAAE,MAAM,SAAS,MAAM,GAAG,MAAM,IAAI,QAAQ,CAAC;IACvG,OAAO,EAAE,SAAS,yBAAyB,CAAC,SAAS,CAAC,EAAE,CAAC;IACzD,IAAI,EAAE,SAAS,sBAAsB,CAAC,MAAM,CAAC,EAAE,CAAC;IAChD,MAAM,EAAE,eAAe,CAAC;IACxB,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,KAAK,SAAS,CAAC;IAC9D,SAAS,CAAC,EAAE,kBAAkB,CAAC,MAAM,CAAC,CAAC;IACvC,SAAS,CAAC,EAAE,kBAAkB,CAAC,SAAS,CAAC,CAAC;IAC1C,SAAS,CAAC,EAAE,kBAAkB,CAAC;IAC/B,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,kBAAkB,CAAC,SAAS,CAAC,KAAK,IAAI,CAAC;IAC7D,UAAU,CAAC,EAAE,mBAAmB,CAAC;IACjC,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,iFAAiF;IACjF,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB;;;OAGG;IACH,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,SAAS,KAAK,SAAS,CAAC;IAChD,iDAAiD;IACjD,YAAY,CAAC,EAAE,SAAS,oBAAoB,CAAC,SAAS,CAAC,EAAE,CAAC;IAC1D,uEAAuE;IACvE,SAAS,CAAC,EAAE,SAAS,iBAAiB,CAAC,MAAM,CAAC,EAAE,CAAC;IACjD,kGAAkG;IAClG,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,KAAK,QAAQ,CAAC;QAAE,IAAI,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAAC;QAAC,IAAI,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAAA;KAAE,CAAC,GAAG,IAAI,CAAC;IAC1I,6FAA6F;IAC7F,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B,oFAAoF;IACpF,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gGAAgG;IAChG,WAAW,CAAC,EAAE,uBAAuB,CAAC;CACvC,CAAC,CAAC;AAwCH,eAAO,MAAM,SAAS,EA+PhB,CAAC,SAAS,SAAS,MAAM,GAAG,MAAM,EAAE,MAAM,SAAS,MAAM,GAAG,MAAM,EACtE,KAAK,EAAE,cAAc,CAAC,SAAS,EAAE,MAAM,CAAC,GAAG;IAAE,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAA;CAAE,KAC7E,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC;AAE/B,OAAO,EACL,SAAS,EACT,WAAW,EACX,eAAe,EACf,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,KAAK,eAAe,GACrB,MAAM,mBAAmB,CAAC"}
@@ -1,9 +1,10 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { TableSortButton } from "./table-sort-button.js";
3
- import { dataTableColumnDefaults, dataTableDefaults, getNextDataTableSortState, resolveDataTableSelectAllState, validateDataTableColumns, validateDataTableRows, validateDataTableSortState, } from "@hjmds/design-contracts/components/data-table";
4
- import { forwardRef, useState } from "react";
3
+ import { dataTableColumnDefaults, dataTableDefaults, dataTableFamilyRecipe, dataTableFirstSortDirection, getNextDataTableSortState, resolveDataTableBody, resolveDataTableHeaderBands, splitPinnedDataTableColumns, resolveDataTableSelectAllState, validateDataTableColumns, validateDataTableRows, validateDataTableSortState, } from "@hjmds/design-contracts/components/data-table";
4
+ import { forwardRef, useLayoutEffect, useMemo, useRef, useState } from "react";
5
5
  import { useHjmDensityDefault } from "./provider.js";
6
6
  import { classNames } from "./internal.js";
7
+ import { tableCellEmphasis } from "./table-family.js";
7
8
  function selectedRowKeysFrom(selection) {
8
9
  if (!selection || selection.mode === "none")
9
10
  return new Set();
@@ -13,7 +14,31 @@ function selectedRowKeysFrom(selection) {
13
14
  }
14
15
  return selection.selectedKeys ?? selection.defaultSelectedKeys ?? new Set();
15
16
  }
16
- export const DataTable = forwardRef(function DataTable({ columns, rows, labels, renderCell, selection, sortState = null, sortCycle = dataTableDefaults.sortCycle, onSortChange, asyncState = { status: "idle" }, density: densityProp, footer, className, layoutStyle, }, forwardedRef) {
17
+ /**
18
+ * Pinned columns and bounded heights need their own scroll box (sticky cells stick to it);
19
+ * other tables keep the original DOM.
20
+ */
21
+ function TableScroll({ scroll, maxBodyHeight, children }) {
22
+ if (!scroll && maxBodyHeight === undefined)
23
+ return _jsx(_Fragment, { children: children });
24
+ return (_jsx("div", { className: "hjm-data-table__scroll", "data-bounded": maxBodyHeight !== undefined || undefined, style: maxBodyHeight !== undefined ? { maxBlockSize: maxBodyHeight } : undefined, children: children }));
25
+ }
26
+ /** Splits the resolved body into `tbody` sections: loose rows first, then one per row group. */
27
+ function bodySections(items) {
28
+ const sections = [];
29
+ for (const item of items) {
30
+ if (item.kind === "group") {
31
+ sections.push({ group: { id: item.id, label: item.label }, rows: [] });
32
+ continue;
33
+ }
34
+ if (sections.length === 0)
35
+ sections.push({ group: null, rows: [] });
36
+ sections.at(-1).rows.push(item.id);
37
+ }
38
+ // An empty table still renders one (empty) tbody, as before row groups existed.
39
+ return sections.length > 0 ? sections : [{ group: null, rows: [] }];
40
+ }
41
+ export const DataTable = forwardRef(function DataTable({ columns, rows, labels, renderCell, selection, sortState = null, sortCycle = dataTableDefaults.sortCycle, onSortChange, asyncState = { status: "idle" }, density: densityProp, footer, summaryRow, columnGroups, rowGroups, cellStyle, heatColors, maxBodyHeight, className, layoutStyle, }, forwardedRef) {
17
42
  // Unconditional: `??` on the hook call would break the rules of hooks.
18
43
  const densityDefault = useHjmDensityDefault({ comfortable: "regular", compact: "compact" });
19
44
  const density = densityProp ?? densityDefault;
@@ -37,7 +62,51 @@ export const DataTable = forwardRef(function DataTable({ columns, rows, labels,
37
62
  if (!selectionControlled)
38
63
  setUncontrolledKeys(next);
39
64
  };
65
+ const hasSelector = selection !== undefined && selection.mode !== "none";
66
+ // Only an unbroken leading run is pinned (contract). Sticky offsets need the rendered widths of
67
+ // the cells before each pinned column, so they are measured rather than guessed from `width`.
68
+ const pinnedCount = splitPinnedDataTableColumns(columns).pinned.length;
69
+ const headerRowRef = useRef(null);
70
+ const [pinnedOffsets, setPinnedOffsets] = useState([]);
71
+ useLayoutEffect(() => {
72
+ const row = headerRowRef.current;
73
+ if (!row || pinnedCount === 0)
74
+ return;
75
+ const stickyCells = pinnedCount + (hasSelector ? 1 : 0);
76
+ const measure = () => {
77
+ const cells = [...row.cells].slice(0, stickyCells);
78
+ const offsets = [];
79
+ let sum = 0;
80
+ for (const cell of cells) {
81
+ offsets.push(sum);
82
+ sum += cell.getBoundingClientRect().width;
83
+ }
84
+ setPinnedOffsets((previous) => previous.length === offsets.length && previous.every((value, i) => value === offsets[i]) ? previous : offsets);
85
+ };
86
+ measure();
87
+ if (typeof ResizeObserver === "undefined")
88
+ return;
89
+ const observer = new ResizeObserver(measure);
90
+ for (const cell of [...row.cells].slice(0, stickyCells))
91
+ observer.observe(cell);
92
+ return () => observer.disconnect();
93
+ }, [pinnedCount, hasSelector, columns]);
94
+ // Sticky props for the selector cell (when pinning) and each pinned column, by visual position.
95
+ const stickyAt = (position, isLast) => {
96
+ if (pinnedCount === 0)
97
+ return {};
98
+ return {
99
+ className: isLast ? "hjm-data-table__pinned hjm-data-table__pinned-edge" : "hjm-data-table__pinned",
100
+ style: { insetInlineStart: pinnedOffsets[position] ?? 0 },
101
+ };
102
+ };
103
+ const selectorSticky = hasSelector && pinnedCount > 0 ? stickyAt(0, false) : {};
104
+ const columnSticky = (index) => index < pinnedCount ? stickyAt(index + (hasSelector ? 1 : 0), index === pinnedCount - 1) : {};
40
105
  const selectAllState = multiple ? resolveDataTableSelectAllState(rows, selectedKeys) : false;
106
+ const bands = useMemo(() => columnGroups && columnGroups.length > 0 ? resolveDataTableHeaderBands(columns, columnGroups) : null, [columns, columnGroups]);
107
+ const sections = useMemo(() => bodySections(resolveDataTableBody(rows, rowGroups ?? [])), [rows, rowGroups]);
108
+ const rowById = useMemo(() => new Map(rows.map((row) => [row.id, row])), [rows]);
109
+ const columnSpan = columns.length + (hasSelector ? 1 : 0);
41
110
  const toggleRow = (row) => {
42
111
  if (row.disabled || !selection || selection.mode === "none")
43
112
  return;
@@ -68,11 +137,37 @@ export const DataTable = forwardRef(function DataTable({ columns, rows, labels,
68
137
  commitSelection(next);
69
138
  selection.onSelectionChange?.(next);
70
139
  };
71
- return (_jsxs("div", { className: classNames("hjm-data-table", className), style: layoutStyle, "data-density": density, children: [asyncState.status === "idle" ? null : (_jsx("p", { className: "hjm-data-table__state", role: asyncState.status === "error" ? "alert" : "status", children: asyncState.message })), _jsxs("table", { ref: forwardedRef, className: "hjm-data-table__table", "aria-label": labels.table, "aria-busy": asyncState.status === "loading" || undefined, children: [_jsx("thead", { children: _jsxs("tr", { children: [selection && selection.mode !== "none" ? (_jsx("th", { scope: "col", className: "hjm-data-table__selector", children: multiple ? (_jsx("button", { type: "button", role: "checkbox", "aria-checked": selectAllState === "mixed" ? "mixed" : String(selectAllState === true), "aria-label": labels.selectAll, className: "hjm-data-table__check", onClick: toggleAll, children: selectAllState === true ? "✓" : selectAllState === "mixed" ? "–" : "" })) : null })) : null, columns.map((column) => {
72
- const sorted = sortState?.columnId === column.id ? sortState.direction : null;
73
- return (_jsx("th", { scope: "col", "aria-sort": sorted ?? undefined, style: { inlineSize: column.width, textAlign: column.align ?? dataTableColumnDefaults.align }, children: column.sortable ? (
74
- // A button inside the header, never the header itself.
75
- _jsx(TableSortButton, { header: column.header, direction: sorted, glyphs: { ascending: "▲", descending: "▼", none: "↕" }, className: "hjm-data-table__sort", accessibleName: labels.sortColumn(column.header, sortState), onSort: () => onSortChange?.(getNextDataTableSortState(sortState, column.id, sortCycle)) })) : column.header }, column.id));
76
- })] }) }), _jsx("tbody", { children: rows.map((row) => (_jsxs("tr", { "aria-selected": selection && selection.mode !== "none" ? selectedKeys.has(row.id) : undefined, "data-disabled": row.disabled || undefined, children: [selection && selection.mode !== "none" ? (_jsx("td", { className: "hjm-data-table__selector", children: _jsx("button", { type: "button", role: multiple ? "checkbox" : "radio", "aria-checked": selectedKeys.has(row.id), "aria-label": labels.selectRow(row.id), "aria-disabled": row.disabled || undefined, className: "hjm-data-table__check", onClick: () => toggleRow(row), children: selectedKeys.has(row.id) ? "✓" : "" }) })) : null, columns.map((column) => (_jsx("td", { style: { textAlign: column.align ?? dataTableColumnDefaults.align }, children: renderCell(row.id, column.id) }, column.id)))] }, row.id))) })] }), footer ? _jsx("div", { className: "hjm-data-table__footer", children: footer }) : null] }));
140
+ return (_jsxs("div", { className: classNames("hjm-data-table", className), style: layoutStyle, "data-density": density, children: [asyncState.status === "idle" ? null : (_jsx("p", { className: "hjm-data-table__state", role: asyncState.status === "error" ? "alert" : "status", children: asyncState.message })), _jsx(TableScroll, { scroll: pinnedCount > 0, maxBodyHeight: maxBodyHeight, children: _jsxs("table", { ref: forwardedRef, className: "hjm-data-table__table", "aria-label": labels.table, "aria-busy": asyncState.status === "loading" || undefined, children: [_jsxs("thead", { children: [bands ? (_jsxs("tr", { className: "hjm-data-table__bands", children: [hasSelector ? _jsx("td", { "aria-hidden": "true", className: classNames("hjm-data-table__selector", selectorSticky.className), style: selectorSticky.style }) : null, bands.map((band) => {
141
+ const first = columns.findIndex((column) => column.id === band.columnIds[0]);
142
+ // A band sticks only when every column under it is pinned; otherwise it scrolls with them.
143
+ const sticky = first + band.span <= pinnedCount ? columnSticky(first + band.span - 1) : {};
144
+ const stickyStyle = sticky.style ? { insetInlineStart: pinnedOffsets[first + (hasSelector ? 1 : 0)] ?? 0 } : undefined;
145
+ return band.header === null
146
+ ? _jsx("td", { "aria-hidden": "true", colSpan: band.span, className: sticky.className, style: stickyStyle }, band.id)
147
+ : _jsx("th", { scope: "colgroup", colSpan: band.span, className: classNames("hjm-data-table__band", sticky.className), style: stickyStyle, children: _jsx("span", { children: band.header }) }, band.id);
148
+ })] })) : null, _jsxs("tr", { ref: headerRowRef, children: [selection && selection.mode !== "none" ? (_jsx("th", { scope: "col", className: classNames("hjm-data-table__selector", selectorSticky.className), style: selectorSticky.style, children: multiple ? (_jsx("button", { type: "button", role: "checkbox", "aria-checked": selectAllState === "mixed" ? "mixed" : String(selectAllState === true), "aria-label": labels.selectAll, className: "hjm-data-table__check", onClick: toggleAll, children: selectAllState === true ? "✓" : selectAllState === "mixed" ? "–" : "" })) : null })) : null, columns.map((column, columnIndex) => {
149
+ const sorted = sortState?.columnId === column.id ? sortState.direction : null;
150
+ const sticky = columnSticky(columnIndex);
151
+ return (_jsx("th", { scope: "col", "aria-sort": sorted ?? undefined, className: sticky.className, style: { inlineSize: column.width, textAlign: column.align ?? dataTableColumnDefaults.align, ...sticky.style }, children: column.sortable ? (
152
+ // A button inside the header, never the header itself.
153
+ _jsx(TableSortButton, { header: column.header, direction: sorted, glyphs: { ascending: "▲", descending: "▼", none: "↕" }, className: "hjm-data-table__sort", accessibleName: labels.sortColumn(column.header, sortState),
154
+ // `sortDescFirst` columns (stat tables: highest first) start descending.
155
+ onSort: () => onSortChange?.(getNextDataTableSortState(sortState, column.id, sortCycle, dataTableFirstSortDirection(column))) })) : column.header }, column.id));
156
+ })] })] }), sections.map((section) => (_jsxs("tbody", { children: [section.group ? (_jsx("tr", { className: "hjm-data-table__group", children: _jsx("th", { scope: "rowgroup", colSpan: columnSpan, children: _jsx("span", { children: section.group.label }) }) })) : null, section.rows.map((rowId) => rowById.get(rowId)).map((row) => (_jsxs("tr", { "aria-selected": selection && selection.mode !== "none" ? selectedKeys.has(row.id) : undefined, "data-disabled": row.disabled || undefined, children: [selection && selection.mode !== "none" ? (_jsx("td", { className: classNames("hjm-data-table__selector", selectorSticky.className), style: selectorSticky.style, children: _jsx("button", { type: "button", role: multiple ? "checkbox" : "radio", "aria-checked": selectedKeys.has(row.id), "aria-label": labels.selectRow(row.id), "aria-disabled": row.disabled || undefined, className: "hjm-data-table__check", onClick: () => toggleRow(row), children: selectedKeys.has(row.id) ? "✓" : "" }) })) : null, columns.map((column, columnIndex) => {
157
+ const sticky = columnSticky(columnIndex);
158
+ const emphasis = tableCellEmphasis(cellStyle?.(row.id, column.id), heatColors);
159
+ // Sub-rows (TanStack `depth`) indent only the first column, which is the pinned one when pinning.
160
+ const depth = columnIndex === 0 ? row.depth ?? 0 : 0;
161
+ return (_jsx("td", { className: classNames(sticky.className, emphasis.heat && "hjm-table-family__heat"), "data-tone": emphasis.tone, "data-heat": emphasis.heat, "data-depth": depth > 0 ? depth : undefined, style: {
162
+ textAlign: column.align ?? dataTableColumnDefaults.align,
163
+ ...emphasis.style,
164
+ ...(depth > 0 ? { "--hjm-data-table-indent": `${depth * dataTableFamilyRecipe.depthIndent}px` } : {}),
165
+ ...sticky.style,
166
+ }, children: renderCell(row.id, column.id) }, column.id));
167
+ })] }, row.id)))] }, section.group?.id ?? "rows"))), summaryRow ? (_jsx("tfoot", { children: _jsxs("tr", { className: "hjm-data-table__summary", children: [hasSelector ? _jsx("td", { className: classNames("hjm-data-table__selector", selectorSticky.className), style: selectorSticky.style }) : null, columns.map((column, columnIndex) => {
168
+ const sticky = columnSticky(columnIndex);
169
+ return (_jsx("td", { className: sticky.className, style: { textAlign: column.align ?? dataTableColumnDefaults.align, ...sticky.style }, children: summaryRow(column.id) }, column.id));
170
+ })] }) })) : null] }) }), footer ? _jsx("div", { className: "hjm-data-table__footer", children: footer }) : null] }));
77
171
  });
172
+ export { LineScore, MatrixTable, ComparisonTable, } from "./table-family.js";
78
173
  //# sourceMappingURL=data-table.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"data-table.js","sourceRoot":"","sources":["../src/data-table.tsx"],"names":[],"mappings":"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","sourcesContent":["import { TableSortButton } from \"./table-sort-button.js\";\nimport {\n dataTableColumnDefaults,\n dataTableDefaults,\n getNextDataTableSortState,\n resolveDataTableSelectAllState,\n validateDataTableColumns,\n validateDataTableRows,\n validateDataTableSortState,\n type DataTableAsyncState,\n type DataTableColumnDescriptor,\n type DataTableDensity,\n type DataTableRowDescriptor,\n type DataTableSelection,\n type DataTableSortCycle,\n type DataTableSortState,\n} from \"@hjmds/design-contracts/components/data-table\";\nimport { forwardRef, useState, type CSSProperties, type ReactNode } from \"react\";\nimport { useHjmDensityDefault } from \"./provider.js\";\nimport { classNames } from \"./internal.js\";\nimport type { HjmCompositionStyleProp } from \"./composition-style.js\";\n\nexport type DataTableLabels = Readonly<{\n /** Accessible name for the table itself. */\n table: string;\n selectAll: string;\n /** Composes one row's selection control name from its id. */\n selectRow: (rowId: string) => string;\n /** Composes a sortable header's action name, including its current direction. */\n sortColumn: (header: string, direction: DataTableSortState<string>) => string;\n}>;\n\nexport type DataTableProps<ColumnKey extends string = string, RowKey extends string = string> = Readonly<{\n columns: readonly DataTableColumnDescriptor<ColumnKey>[];\n rows: readonly DataTableRowDescriptor<RowKey>[];\n labels: DataTableLabels;\n renderCell: (rowId: RowKey, columnId: ColumnKey) => ReactNode;\n selection?: DataTableSelection<RowKey>;\n sortState?: DataTableSortState<ColumnKey>;\n sortCycle?: DataTableSortCycle;\n onSortChange?: (next: DataTableSortState<ColumnKey>) => void;\n asyncState?: DataTableAsyncState;\n density?: DataTableDensity;\n /** Composed beneath the table by the product — pagination, load more, totals. */\n footer?: ReactNode;\n className?: string;\n /** Canonical layout-only placement on the root wrapper. Controlled visual keys are excluded. */\n layoutStyle?: HjmCompositionStyleProp;\n}>;\n\nfunction selectedRowKeysFrom<RowKey extends string>(\n selection: DataTableSelection<RowKey> | undefined,\n): ReadonlySet<RowKey> {\n if (!selection || selection.mode === \"none\") return new Set<RowKey>();\n if (selection.mode === \"single\") {\n const key = selection.selectedKey ?? selection.defaultSelectedKey ?? null;\n return key === null ? new Set<RowKey>() : new Set<RowKey>([key]);\n }\n return selection.selectedKeys ?? selection.defaultSelectedKeys ?? new Set<RowKey>();\n}\n\nexport const DataTable = forwardRef(function DataTable<\n ColumnKey extends string = string,\n RowKey extends string = string,\n>(\n {\n columns,\n rows,\n labels,\n renderCell,\n selection,\n sortState = null,\n sortCycle = dataTableDefaults.sortCycle,\n onSortChange,\n asyncState = { status: \"idle\" },\n density: densityProp,\n footer,\n className,\n layoutStyle,\n }: DataTableProps<ColumnKey, RowKey>,\n forwardedRef: React.Ref<HTMLTableElement>,\n) {\n // Unconditional: `??` on the hook call would break the rules of hooks.\n const densityDefault = useHjmDensityDefault({ comfortable: \"regular\", compact: \"compact\" });\n const density = densityProp ?? densityDefault;\n validateDataTableColumns(columns);\n validateDataTableRows(rows);\n validateDataTableSortState(sortState, columns);\n const multiple = selection?.mode === \"multiple\";\n const single = selection?.mode === \"single\";\n // `defaultSelectedKey(s)` used to be re-read on every render, so an\n // uncontrolled table reported the click to `onSelectionChange` but kept showing\n // the default (same defect and fix as Tree, 2026-10-06). The uncontrolled value\n // lives in state; a controlled `selectedKey(s)` (including `null`) still wins.\n const selectionControlled = single\n ? selection.selectedKey !== undefined\n : multiple ? selection.selectedKeys !== undefined : true;\n const [uncontrolledKeys, setUncontrolledKeys] = useState<ReadonlySet<RowKey>>(() => selectedRowKeysFrom(selection));\n const selectedKeys: ReadonlySet<RowKey> = selectionControlled\n ? selectedRowKeysFrom(selection)\n : uncontrolledKeys;\n const commitSelection = (next: ReadonlySet<RowKey>) => {\n if (!selectionControlled) setUncontrolledKeys(next);\n };\n const selectAllState = multiple ? resolveDataTableSelectAllState(rows, selectedKeys) : false;\n\n const toggleRow = (row: DataTableRowDescriptor<RowKey>) => {\n if (row.disabled || !selection || selection.mode === \"none\") return;\n if (selection.mode === \"single\") {\n const current = selectedKeys.has(row.id) ? row.id : null;\n const next = current === row.id && selection.disallowEmptySelection !== true ? null : row.id;\n commitSelection(next === null ? new Set<RowKey>() : new Set<RowKey>([next]));\n selection.onSelectionChange?.(next);\n return;\n }\n const next = new Set(selectedKeys);\n if (next.has(row.id)) next.delete(row.id); else next.add(row.id);\n commitSelection(next);\n selection.onSelectionChange?.(next);\n };\n const toggleAll = () => {\n if (!multiple) return;\n // Disabled rows stay out of both the denominator and the result, matching\n // the shared select-all accounting.\n const selectable = rows.filter((row) => !row.disabled);\n const next = selectAllState === true\n ? new Set([...selectedKeys].filter((id) => !selectable.some((row) => row.id === id)))\n : new Set([...selectedKeys, ...selectable.map((row) => row.id)]);\n commitSelection(next);\n selection.onSelectionChange?.(next);\n };\n\n return (\n <div className={classNames(\"hjm-data-table\", className)} style={layoutStyle} data-density={density}>\n {asyncState.status === \"idle\" ? null : (\n <p className=\"hjm-data-table__state\" role={asyncState.status === \"error\" ? \"alert\" : \"status\"}>{asyncState.message}</p>\n )}\n <table ref={forwardedRef} className=\"hjm-data-table__table\" aria-label={labels.table} aria-busy={asyncState.status === \"loading\" || undefined}>\n <thead>\n <tr>\n {selection && selection.mode !== \"none\" ? (\n <th scope=\"col\" className=\"hjm-data-table__selector\">\n {multiple ? (\n <button\n type=\"button\"\n role=\"checkbox\"\n aria-checked={selectAllState === \"mixed\" ? \"mixed\" : String(selectAllState === true) as \"true\" | \"false\"}\n aria-label={labels.selectAll}\n className=\"hjm-data-table__check\"\n onClick={toggleAll}\n >\n {selectAllState === true ? \"✓\" : selectAllState === \"mixed\" ? \"–\" : \"\"}\n </button>\n ) : null}\n </th>\n ) : null}\n {columns.map((column) => {\n const sorted = sortState?.columnId === column.id ? sortState.direction : null;\n return (\n <th\n key={column.id}\n scope=\"col\"\n // The direction passes straight through to aria-sort; the\n // contract already speaks that vocabulary.\n aria-sort={sorted ?? undefined}\n style={{ inlineSize: column.width, textAlign: column.align ?? dataTableColumnDefaults.align } as CSSProperties}\n >\n {column.sortable ? (\n // A button inside the header, never the header itself.\n <TableSortButton\n header={column.header}\n direction={sorted}\n glyphs={{ ascending: \"▲\", descending: \"▼\", none: \"↕\" }}\n className=\"hjm-data-table__sort\"\n accessibleName={labels.sortColumn(column.header, sortState as DataTableSortState<string>)}\n onSort={() => onSortChange?.(getNextDataTableSortState(sortState, column.id, sortCycle))}\n />\n ) : column.header}\n </th>\n );\n })}\n </tr>\n </thead>\n <tbody>\n {rows.map((row) => (\n <tr key={row.id} aria-selected={selection && selection.mode !== \"none\" ? selectedKeys.has(row.id) : undefined} data-disabled={row.disabled || undefined}>\n {selection && selection.mode !== \"none\" ? (\n <td className=\"hjm-data-table__selector\">\n <button\n type=\"button\"\n role={multiple ? \"checkbox\" : \"radio\"}\n aria-checked={selectedKeys.has(row.id)}\n aria-label={labels.selectRow(row.id)}\n aria-disabled={row.disabled || undefined}\n className=\"hjm-data-table__check\"\n onClick={() => toggleRow(row)}\n >\n {selectedKeys.has(row.id) ? \"✓\" : \"\"}\n </button>\n </td>\n ) : null}\n {columns.map((column) => (\n <td key={column.id} style={{ textAlign: column.align ?? dataTableColumnDefaults.align } as CSSProperties}>\n {renderCell(row.id, column.id)}\n </td>\n ))}\n </tr>\n ))}\n </tbody>\n </table>\n {/* Pagination or load-more is composed beneath the table, never owned by it. */}\n {footer ? <div className=\"hjm-data-table__footer\">{footer}</div> : null}\n </div>\n );\n}) as <ColumnKey extends string = string, RowKey extends string = string>(\n props: DataTableProps<ColumnKey, RowKey> & { ref?: React.Ref<HTMLTableElement> },\n) => React.ReactElement | null;\n"]}
1
+ {"version":3,"file":"data-table.js","sourceRoot":"","sources":["../src/data-table.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EACL,uBAAuB,EACvB,iBAAiB,EACjB,qBAAqB,EACrB,2BAA2B,EAC3B,yBAAyB,EACzB,oBAAoB,EACpB,2BAA2B,EAC3B,2BAA2B,EAC3B,8BAA8B,EAC9B,wBAAwB,EACxB,qBAAqB,EACrB,0BAA0B,GAa3B,MAAM,+CAA+C,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAsC,MAAM,OAAO,CAAC;AACnH,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAE3C,OAAO,EAAE,iBAAiB,EAAwB,MAAM,mBAAmB,CAAC;AA6C5E,SAAS,mBAAmB,CAC1B,SAAiD;IAEjD,IAAI,CAAC,SAAS,IAAI,SAAS,CAAC,IAAI,KAAK,MAAM;QAAE,OAAO,IAAI,GAAG,EAAU,CAAC;IACtE,IAAI,SAAS,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAChC,MAAM,GAAG,GAAG,SAAS,CAAC,WAAW,IAAI,SAAS,CAAC,kBAAkB,IAAI,IAAI,CAAC;QAC1E,OAAO,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,EAAU,CAAC,CAAC,CAAC,IAAI,GAAG,CAAS,CAAC,GAAG,CAAC,CAAC,CAAC;IACnE,CAAC;IACD,OAAO,SAAS,CAAC,YAAY,IAAI,SAAS,CAAC,mBAAmB,IAAI,IAAI,GAAG,EAAU,CAAC;AACtF,CAAC;AAED;;;GAGG;AACH,SAAS,WAAW,CAAC,EAAE,MAAM,EAAE,aAAa,EAAE,QAAQ,EAA0F;IAC9I,IAAI,CAAC,MAAM,IAAI,aAAa,KAAK,SAAS;QAAE,OAAO,4BAAG,QAAQ,GAAI,CAAC;IACnE,OAAO,CACL,cAAK,SAAS,EAAC,wBAAwB,kBAAe,aAAa,KAAK,SAAS,IAAI,SAAS,EAAE,KAAK,EAAE,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,SAAS,YAC7K,QAAQ,GACL,CACP,CAAC;AACJ,CAAC;AAID,gGAAgG;AAChG,SAAS,YAAY,CAAS,KAAoD;IAChF,MAAM,QAAQ,GAA0B,EAAE,CAAC;IAC3C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;YAAC,SAAS;QAAC,CAAC;QAChH,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;YAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;QACpE,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,CAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAY,CAAC,CAAC;IAChD,CAAC;IACD,gFAAgF;IAChF,OAAO,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;AACtE,CAAC;AAED,MAAM,CAAC,MAAM,SAAS,GAAG,UAAU,CAAC,SAAS,SAAS,CAIpD,EACE,OAAO,EACP,IAAI,EACJ,MAAM,EACN,UAAU,EACV,SAAS,EACT,SAAS,GAAG,IAAI,EAChB,SAAS,GAAG,iBAAiB,CAAC,SAAS,EACvC,YAAY,EACZ,UAAU,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,EAC/B,OAAO,EAAE,WAAW,EACpB,MAAM,EACN,UAAU,EACV,YAAY,EACZ,SAAS,EACT,SAAS,EACT,UAAU,EACV,aAAa,EACb,SAAS,EACT,WAAW,GACuB,EACpC,YAAyC;IAEzC,uEAAuE;IACvE,MAAM,cAAc,GAAG,oBAAoB,CAAC,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC;IAC5F,MAAM,OAAO,GAAG,WAAW,IAAI,cAAc,CAAC;IAC9C,wBAAwB,CAAC,OAAO,CAAC,CAAC;IAClC,qBAAqB,CAAC,IAAI,CAAC,CAAC;IAC5B,0BAA0B,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAC/C,MAAM,QAAQ,GAAG,SAAS,EAAE,IAAI,KAAK,UAAU,CAAC;IAChD,MAAM,MAAM,GAAG,SAAS,EAAE,IAAI,KAAK,QAAQ,CAAC;IAC5C,oEAAoE;IACpE,gFAAgF;IAChF,gFAAgF;IAChF,+EAA+E;IAC/E,MAAM,mBAAmB,GAAG,MAAM;QAChC,CAAC,CAAC,SAAS,CAAC,WAAW,KAAK,SAAS;QACrC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC;IAC3D,MAAM,CAAC,gBAAgB,EAAE,mBAAmB,CAAC,GAAG,QAAQ,CAAsB,GAAG,EAAE,CAAC,mBAAmB,CAAC,SAAS,CAAC,CAAC,CAAC;IACpH,MAAM,YAAY,GAAwB,mBAAmB;QAC3D,CAAC,CAAC,mBAAmB,CAAC,SAAS,CAAC;QAChC,CAAC,CAAC,gBAAgB,CAAC;IACrB,MAAM,eAAe,GAAG,CAAC,IAAyB,EAAE,EAAE;QACpD,IAAI,CAAC,mBAAmB;YAAE,mBAAmB,CAAC,IAAI,CAAC,CAAC;IACtD,CAAC,CAAC;IACF,MAAM,WAAW,GAAG,SAAS,KAAK,SAAS,IAAI,SAAS,CAAC,IAAI,KAAK,MAAM,CAAC;IACzE,gGAAgG;IAChG,8FAA8F;IAC9F,MAAM,WAAW,GAAG,2BAA2B,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC;IACvE,MAAM,YAAY,GAAG,MAAM,CAAsB,IAAI,CAAC,CAAC;IACvD,MAAM,CAAC,aAAa,EAAE,gBAAgB,CAAC,GAAG,QAAQ,CAAoB,EAAE,CAAC,CAAC;IAC1E,eAAe,CAAC,GAAG,EAAE;QACnB,MAAM,GAAG,GAAG,YAAY,CAAC,OAAO,CAAC;QACjC,IAAI,CAAC,GAAG,IAAI,WAAW,KAAK,CAAC;YAAE,OAAO;QACtC,MAAM,WAAW,GAAG,WAAW,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACxD,MAAM,OAAO,GAAG,GAAG,EAAE;YACnB,MAAM,KAAK,GAAG,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;YACnD,MAAM,OAAO,GAAa,EAAE,CAAC;YAC7B,IAAI,GAAG,GAAG,CAAC,CAAC;YACZ,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBAAC,GAAG,IAAI,IAAI,CAAC,qBAAqB,EAAE,CAAC,KAAK,CAAC;YAAC,CAAC;YAC3F,gBAAgB,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,KAAK,OAAO,CAAC,MAAM,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QAChJ,CAAC,CAAC;QACF,OAAO,EAAE,CAAC;QACV,IAAI,OAAO,cAAc,KAAK,WAAW;YAAE,OAAO;QAClD,MAAM,QAAQ,GAAG,IAAI,cAAc,CAAC,OAAO,CAAC,CAAC;QAC7C,KAAK,MAAM,IAAI,IAAI,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,WAAW,CAAC;YAAE,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAChF,OAAO,GAAG,EAAE,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC;IACrC,CAAC,EAAE,CAAC,WAAW,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC;IACxC,gGAAgG;IAChG,MAAM,QAAQ,GAAG,CAAC,QAAgB,EAAE,MAAe,EAA2D,EAAE;QAC9G,IAAI,WAAW,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QACjC,OAAO;YACL,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,oDAAoD,CAAC,CAAC,CAAC,wBAAwB;YACnG,KAAK,EAAE,EAAE,gBAAgB,EAAE,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE;SAC1D,CAAC;IACJ,CAAC,CAAC;IACF,MAAM,cAAc,GAAG,WAAW,IAAI,WAAW,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAChF,MAAM,YAAY,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,KAAK,GAAG,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,KAAK,WAAW,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACtI,MAAM,cAAc,GAAG,QAAQ,CAAC,CAAC,CAAC,8BAA8B,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IAC7F,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,YAAY,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,2BAA2B,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC;IAC1J,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,YAAY,CAAS,oBAAoB,CAAC,IAAI,EAAE,SAAS,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC;IACrH,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IACjF,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAE1D,MAAM,SAAS,GAAG,CAAC,GAAmC,EAAE,EAAE;QACxD,IAAI,GAAG,CAAC,QAAQ,IAAI,CAAC,SAAS,IAAI,SAAS,CAAC,IAAI,KAAK,MAAM;YAAE,OAAO;QACpE,IAAI,SAAS,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAChC,MAAM,OAAO,GAAG,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;YACzD,MAAM,IAAI,GAAG,OAAO,KAAK,GAAG,CAAC,EAAE,IAAI,SAAS,CAAC,sBAAsB,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;YAC7F,eAAe,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,EAAU,CAAC,CAAC,CAAC,IAAI,GAAG,CAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAC7E,SAAS,CAAC,iBAAiB,EAAE,CAAC,IAAI,CAAC,CAAC;YACpC,OAAO;QACT,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,YAAY,CAAC,CAAC;QACnC,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;;YAAM,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACjE,eAAe,CAAC,IAAI,CAAC,CAAC;QACtB,SAAS,CAAC,iBAAiB,EAAE,CAAC,IAAI,CAAC,CAAC;IACtC,CAAC,CAAC;IACF,MAAM,SAAS,GAAG,GAAG,EAAE;QACrB,IAAI,CAAC,QAAQ;YAAE,OAAO;QACtB,0EAA0E;QAC1E,oCAAoC;QACpC,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACvD,MAAM,IAAI,GAAG,cAAc,KAAK,IAAI;YAClC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;YACrF,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,YAAY,EAAE,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACnE,eAAe,CAAC,IAAI,CAAC,CAAC;QACtB,SAAS,CAAC,iBAAiB,EAAE,CAAC,IAAI,CAAC,CAAC;IACtC,CAAC,CAAC;IAEF,OAAO,CACL,eAAK,SAAS,EAAE,UAAU,CAAC,gBAAgB,EAAE,SAAS,CAAC,EAAE,KAAK,EAAE,WAAW,kBAAgB,OAAO,aAC/F,UAAU,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CACrC,YAAG,SAAS,EAAC,uBAAuB,EAAC,IAAI,EAAE,UAAU,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,YAAG,UAAU,CAAC,OAAO,GAAK,CACxH,EACD,KAAC,WAAW,IAAC,MAAM,EAAE,WAAW,GAAG,CAAC,EAAE,aAAa,EAAE,aAAa,YAClE,iBAAO,GAAG,EAAE,YAAY,EAAE,SAAS,EAAC,uBAAuB,gBAAa,MAAM,CAAC,KAAK,eAAa,UAAU,CAAC,MAAM,KAAK,SAAS,IAAI,SAAS,aAC3I,4BACG,KAAK,CAAC,CAAC,CAAC,CACP,cAAI,SAAS,EAAC,uBAAuB,aAClC,WAAW,CAAC,CAAC,CAAC,4BAAgB,MAAM,EAAC,SAAS,EAAE,UAAU,CAAC,0BAA0B,EAAE,cAAc,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,cAAc,CAAC,KAAK,GAAI,CAAC,CAAC,CAAC,IAAI,EACxJ,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;4CAClB,MAAM,KAAK,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,KAAK,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;4CAC7E,2FAA2F;4CAC3F,MAAM,MAAM,GAAG,KAAK,GAAG,IAAI,CAAC,IAAI,IAAI,WAAW,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,GAAG,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;4CAC3F,MAAM,WAAW,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,aAAa,CAAC,KAAK,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;4CACvH,OAAO,IAAI,CAAC,MAAM,KAAK,IAAI;gDACzB,CAAC,CAAC,4BAA8B,MAAM,EAAC,OAAO,EAAE,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,KAAK,EAAE,WAAW,IAA/F,IAAI,CAAC,EAAE,CAA4F;gDAC9G,CAAC,CAAC,aAAkB,KAAK,EAAC,UAAU,EAAC,OAAO,EAAE,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,UAAU,CAAC,sBAAsB,EAAE,MAAM,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,WAAW,YAAE,yBAAO,IAAI,CAAC,MAAM,GAAQ,IAA7J,IAAI,CAAC,EAAE,CAA2J,CAAC;wCAClL,CAAC,CAAC,IACC,CACN,CAAC,CAAC,CAAC,IAAI,EACR,cAAI,GAAG,EAAE,YAAY,aAClB,SAAS,IAAI,SAAS,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,CACxC,aAAI,KAAK,EAAC,KAAK,EAAC,SAAS,EAAE,UAAU,CAAC,0BAA0B,EAAE,cAAc,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,cAAc,CAAC,KAAK,YACrH,QAAQ,CAAC,CAAC,CAAC,CACV,iBACE,IAAI,EAAC,QAAQ,EACb,IAAI,EAAC,UAAU,kBACD,cAAc,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,cAAc,KAAK,IAAI,CAAqB,gBAC5F,MAAM,CAAC,SAAS,EAC5B,SAAS,EAAC,uBAAuB,EACjC,OAAO,EAAE,SAAS,YAEjB,cAAc,KAAK,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,cAAc,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAC/D,CACV,CAAC,CAAC,CAAC,IAAI,GACL,CACN,CAAC,CAAC,CAAC,IAAI,EACP,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,WAAW,EAAE,EAAE;4CACnC,MAAM,MAAM,GAAG,SAAS,EAAE,QAAQ,KAAK,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC;4CAC9E,MAAM,MAAM,GAAG,YAAY,CAAC,WAAW,CAAC,CAAC;4CACzC,OAAO,CACL,aAEE,KAAK,EAAC,KAAK,eAGA,MAAM,IAAI,SAAS,EAC9B,SAAS,EAAE,MAAM,CAAC,SAAS,EAC3B,KAAK,EAAE,EAAE,UAAU,EAAE,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,CAAC,KAAK,IAAI,uBAAuB,CAAC,KAAK,EAAE,GAAG,MAAM,CAAC,KAAK,EAAmB,YAE9H,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;gDACjB,uDAAuD;gDACvD,KAAC,eAAe,IACd,MAAM,EAAE,MAAM,CAAC,MAAM,EACrB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,EACtD,SAAS,EAAC,sBAAsB,EAChC,cAAc,EAAE,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,MAAM,EAAE,SAAuC,CAAC;oDACzF,yEAAyE;oDACzE,MAAM,EAAE,GAAG,EAAE,CAAC,YAAY,EAAE,CAAC,yBAAyB,CAAC,SAAS,EAAE,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,2BAA2B,CAAC,MAAM,CAAC,CAAC,CAAC,GAC7H,CACH,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,IAnBZ,MAAM,CAAC,EAAE,CAoBX,CACN,CAAC;wCACJ,CAAC,CAAC,IACC,IACC,EACP,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAC3B,4BACG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CACf,aAAI,SAAS,EAAC,uBAAuB,YACnC,aAAI,KAAK,EAAC,UAAU,EAAC,OAAO,EAAE,UAAU,YAAE,yBAAO,OAAO,CAAC,KAAK,CAAC,KAAK,GAAQ,GAAK,GAC9E,CACN,CAAC,CAAC,CAAC,IAAI,EACP,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAC7D,+BAAgC,SAAS,IAAI,SAAS,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,mBAAiB,GAAG,CAAC,QAAQ,IAAI,SAAS,aACpJ,SAAS,IAAI,SAAS,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,CACxC,aAAI,SAAS,EAAE,UAAU,CAAC,0BAA0B,EAAE,cAAc,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,cAAc,CAAC,KAAK,YAC1G,iBACE,IAAI,EAAC,QAAQ,EACb,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,OAAO,kBACvB,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,gBAC1B,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,mBACrB,GAAG,CAAC,QAAQ,IAAI,SAAS,EACxC,SAAS,EAAC,uBAAuB,EACjC,OAAO,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,YAE5B,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAC7B,GACN,CACN,CAAC,CAAC,CAAC,IAAI,EACP,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,WAAW,EAAE,EAAE;4CACnC,MAAM,MAAM,GAAG,YAAY,CAAC,WAAW,CAAC,CAAC;4CACzC,MAAM,QAAQ,GAAG,iBAAiB,CAAC,SAAS,EAAE,CAAC,GAAG,CAAC,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC,EAAE,UAAU,CAAC,CAAC;4CAC/E,kGAAkG;4CAClG,MAAM,KAAK,GAAG,WAAW,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;4CACrD,OAAO,CACL,aAEE,SAAS,EAAE,UAAU,CAAC,MAAM,CAAC,SAAS,EAAE,QAAQ,CAAC,IAAI,IAAI,wBAAwB,CAAC,eACvE,QAAQ,CAAC,IAAI,eACb,QAAQ,CAAC,IAAI,gBACZ,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,EACzC,KAAK,EAAE;oDACL,SAAS,EAAE,MAAM,CAAC,KAAK,IAAI,uBAAuB,CAAC,KAAK;oDACxD,GAAG,QAAQ,CAAC,KAAK;oDACjB,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,yBAAyB,EAAE,GAAG,KAAK,GAAG,qBAAqB,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oDACrG,GAAG,MAAM,CAAC,KAAK;iDACC,YAEjB,UAAU,CAAC,GAAG,CAAC,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC,IAZzB,MAAM,CAAC,EAAE,CAaX,CACN,CAAC;wCACJ,CAAC,CAAC,KAtCK,GAAG,CAAC,EAAE,CAuCV,CACN,CAAC,KA/CQ,OAAO,CAAC,KAAK,EAAE,EAAE,IAAI,MAAM,CAgD/B,CACP,CAAC,EACD,UAAU,CAAC,CAAC,CAAC,CACZ,0BACE,cAAI,SAAS,EAAC,yBAAyB,aACpC,WAAW,CAAC,CAAC,CAAC,aAAI,SAAS,EAAE,UAAU,CAAC,0BAA0B,EAAE,cAAc,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,cAAc,CAAC,KAAK,GAAI,CAAC,CAAC,CAAC,IAAI,EACrI,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,WAAW,EAAE,EAAE;wCACnC,MAAM,MAAM,GAAG,YAAY,CAAC,WAAW,CAAC,CAAC;wCACzC,OAAO,CACL,aAAoB,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,MAAM,CAAC,KAAK,IAAI,uBAAuB,CAAC,KAAK,EAAE,GAAG,MAAM,CAAC,KAAK,EAAmB,YACnJ,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC,IADf,MAAM,CAAC,EAAE,CAEb,CACN,CAAC;oCACJ,CAAC,CAAC,IACC,GACC,CACT,CAAC,CAAC,CAAC,IAAI,IACF,GACM,EAEb,MAAM,CAAC,CAAC,CAAC,cAAK,SAAS,EAAC,wBAAwB,YAAE,MAAM,GAAO,CAAC,CAAC,CAAC,IAAI,IACnE,CACP,CAAC;AACJ,CAAC,CAE6B,CAAC;AAE/B,OAAO,EACL,SAAS,EACT,WAAW,EACX,eAAe,GAKhB,MAAM,mBAAmB,CAAC","sourcesContent":["import { TableSortButton } from \"./table-sort-button.js\";\nimport {\n dataTableColumnDefaults,\n dataTableDefaults,\n dataTableFamilyRecipe,\n dataTableFirstSortDirection,\n getNextDataTableSortState,\n resolveDataTableBody,\n resolveDataTableHeaderBands,\n splitPinnedDataTableColumns,\n resolveDataTableSelectAllState,\n validateDataTableColumns,\n validateDataTableRows,\n validateDataTableSortState,\n type DataTableAsyncState,\n type DataTableBodyItem,\n type DataTableCellHeat,\n type DataTableCellTone,\n type DataTableColumnDescriptor,\n type DataTableColumnGroup,\n type DataTableDensity,\n type DataTableRowDescriptor,\n type DataTableRowGroup,\n type DataTableSelection,\n type DataTableSortCycle,\n type DataTableSortState,\n} from \"@hjmds/design-contracts/components/data-table\";\nimport { forwardRef, useLayoutEffect, useMemo, useRef, useState, type CSSProperties, type ReactNode } from \"react\";\nimport { useHjmDensityDefault } from \"./provider.js\";\nimport { classNames } from \"./internal.js\";\nimport type { HjmCompositionStyleProp } from \"./composition-style.js\";\nimport { tableCellEmphasis, type TableHeatColors } from \"./table-family.js\";\n\nexport type DataTableLabels = Readonly<{\n /** Accessible name for the table itself. */\n table: string;\n selectAll: string;\n /** Composes one row's selection control name from its id. */\n selectRow: (rowId: string) => string;\n /** Composes a sortable header's action name, including its current direction. */\n sortColumn: (header: string, direction: DataTableSortState<string>) => string;\n}>;\n\nexport type DataTableProps<ColumnKey extends string = string, RowKey extends string = string> = Readonly<{\n columns: readonly DataTableColumnDescriptor<ColumnKey>[];\n rows: readonly DataTableRowDescriptor<RowKey>[];\n labels: DataTableLabels;\n renderCell: (rowId: RowKey, columnId: ColumnKey) => ReactNode;\n selection?: DataTableSelection<RowKey>;\n sortState?: DataTableSortState<ColumnKey>;\n sortCycle?: DataTableSortCycle;\n onSortChange?: (next: DataTableSortState<ColumnKey>) => void;\n asyncState?: DataTableAsyncState;\n density?: DataTableDensity;\n /** Composed beneath the table by the product — pagination, load more, totals. */\n footer?: ReactNode;\n /**\n * Optional summary row rendered as the table's `tfoot` (totals, averages). Returns the cell for\n * each column; HJM never computes the aggregate (2026-10-14, stat tables).\n */\n summaryRow?: (columnId: ColumnKey) => ReactNode;\n /** Header bands above leaf columns (타격 | 주루). */\n columnGroups?: readonly DataTableColumnGroup<ColumnKey>[];\n /** Labeled row sections (선발 / 교체); each renders as its own `tbody`. */\n rowGroups?: readonly DataTableRowGroup<RowKey>[];\n /** Per-cell emphasis decided by the product (AG Grid cellClassRules / Savant percentile heat). */\n cellStyle?: (rowId: RowKey, columnId: ColumnKey) => Readonly<{ tone?: DataTableCellTone | null; heat?: DataTableCellHeat | null }> | null;\n /** Heat base colors (any CSS color); default high = danger red, low = info blue (Savant). */\n heatColors?: TableHeatColors;\n /** Bounds the scroll box height in px; the header stays fixed while rows scroll. */\n maxBodyHeight?: number;\n className?: string;\n /** Canonical layout-only placement on the root wrapper. Controlled visual keys are excluded. */\n layoutStyle?: HjmCompositionStyleProp;\n}>;\n\nfunction selectedRowKeysFrom<RowKey extends string>(\n selection: DataTableSelection<RowKey> | undefined,\n): ReadonlySet<RowKey> {\n if (!selection || selection.mode === \"none\") return new Set<RowKey>();\n if (selection.mode === \"single\") {\n const key = selection.selectedKey ?? selection.defaultSelectedKey ?? null;\n return key === null ? new Set<RowKey>() : new Set<RowKey>([key]);\n }\n return selection.selectedKeys ?? selection.defaultSelectedKeys ?? new Set<RowKey>();\n}\n\n/**\n * Pinned columns and bounded heights need their own scroll box (sticky cells stick to it);\n * other tables keep the original DOM.\n */\nfunction TableScroll({ scroll, maxBodyHeight, children }: Readonly<{ scroll: boolean; maxBodyHeight?: number | undefined; children: ReactNode }>) {\n if (!scroll && maxBodyHeight === undefined) return <>{children}</>;\n return (\n <div className=\"hjm-data-table__scroll\" data-bounded={maxBodyHeight !== undefined || undefined} style={maxBodyHeight !== undefined ? { maxBlockSize: maxBodyHeight } : undefined}>\n {children}\n </div>\n );\n}\n\ntype BodySection<RowKey> = { group: Readonly<{ id: string; label: string }> | null; rows: RowKey[] };\n\n/** Splits the resolved body into `tbody` sections: loose rows first, then one per row group. */\nfunction bodySections<RowKey>(items: readonly DataTableBodyItem<RowKey & string>[]): BodySection<RowKey>[] {\n const sections: BodySection<RowKey>[] = [];\n for (const item of items) {\n if (item.kind === \"group\") { sections.push({ group: { id: item.id, label: item.label }, rows: [] }); continue; }\n if (sections.length === 0) sections.push({ group: null, rows: [] });\n sections.at(-1)!.rows.push(item.id as RowKey);\n }\n // An empty table still renders one (empty) tbody, as before row groups existed.\n return sections.length > 0 ? sections : [{ group: null, rows: [] }];\n}\n\nexport const DataTable = forwardRef(function DataTable<\n ColumnKey extends string = string,\n RowKey extends string = string,\n>(\n {\n columns,\n rows,\n labels,\n renderCell,\n selection,\n sortState = null,\n sortCycle = dataTableDefaults.sortCycle,\n onSortChange,\n asyncState = { status: \"idle\" },\n density: densityProp,\n footer,\n summaryRow,\n columnGroups,\n rowGroups,\n cellStyle,\n heatColors,\n maxBodyHeight,\n className,\n layoutStyle,\n }: DataTableProps<ColumnKey, RowKey>,\n forwardedRef: React.Ref<HTMLTableElement>,\n) {\n // Unconditional: `??` on the hook call would break the rules of hooks.\n const densityDefault = useHjmDensityDefault({ comfortable: \"regular\", compact: \"compact\" });\n const density = densityProp ?? densityDefault;\n validateDataTableColumns(columns);\n validateDataTableRows(rows);\n validateDataTableSortState(sortState, columns);\n const multiple = selection?.mode === \"multiple\";\n const single = selection?.mode === \"single\";\n // `defaultSelectedKey(s)` used to be re-read on every render, so an\n // uncontrolled table reported the click to `onSelectionChange` but kept showing\n // the default (same defect and fix as Tree, 2026-10-06). The uncontrolled value\n // lives in state; a controlled `selectedKey(s)` (including `null`) still wins.\n const selectionControlled = single\n ? selection.selectedKey !== undefined\n : multiple ? selection.selectedKeys !== undefined : true;\n const [uncontrolledKeys, setUncontrolledKeys] = useState<ReadonlySet<RowKey>>(() => selectedRowKeysFrom(selection));\n const selectedKeys: ReadonlySet<RowKey> = selectionControlled\n ? selectedRowKeysFrom(selection)\n : uncontrolledKeys;\n const commitSelection = (next: ReadonlySet<RowKey>) => {\n if (!selectionControlled) setUncontrolledKeys(next);\n };\n const hasSelector = selection !== undefined && selection.mode !== \"none\";\n // Only an unbroken leading run is pinned (contract). Sticky offsets need the rendered widths of\n // the cells before each pinned column, so they are measured rather than guessed from `width`.\n const pinnedCount = splitPinnedDataTableColumns(columns).pinned.length;\n const headerRowRef = useRef<HTMLTableRowElement>(null);\n const [pinnedOffsets, setPinnedOffsets] = useState<readonly number[]>([]);\n useLayoutEffect(() => {\n const row = headerRowRef.current;\n if (!row || pinnedCount === 0) return;\n const stickyCells = pinnedCount + (hasSelector ? 1 : 0);\n const measure = () => {\n const cells = [...row.cells].slice(0, stickyCells);\n const offsets: number[] = [];\n let sum = 0;\n for (const cell of cells) { offsets.push(sum); sum += cell.getBoundingClientRect().width; }\n setPinnedOffsets((previous) => previous.length === offsets.length && previous.every((value, i) => value === offsets[i]) ? previous : offsets);\n };\n measure();\n if (typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(measure);\n for (const cell of [...row.cells].slice(0, stickyCells)) observer.observe(cell);\n return () => observer.disconnect();\n }, [pinnedCount, hasSelector, columns]);\n // Sticky props for the selector cell (when pinning) and each pinned column, by visual position.\n const stickyAt = (position: number, isLast: boolean): Readonly<{ className?: string; style?: CSSProperties }> => {\n if (pinnedCount === 0) return {};\n return {\n className: isLast ? \"hjm-data-table__pinned hjm-data-table__pinned-edge\" : \"hjm-data-table__pinned\",\n style: { insetInlineStart: pinnedOffsets[position] ?? 0 },\n };\n };\n const selectorSticky = hasSelector && pinnedCount > 0 ? stickyAt(0, false) : {};\n const columnSticky = (index: number) => index < pinnedCount ? stickyAt(index + (hasSelector ? 1 : 0), index === pinnedCount - 1) : {};\n const selectAllState = multiple ? resolveDataTableSelectAllState(rows, selectedKeys) : false;\n const bands = useMemo(() => columnGroups && columnGroups.length > 0 ? resolveDataTableHeaderBands(columns, columnGroups) : null, [columns, columnGroups]);\n const sections = useMemo(() => bodySections<RowKey>(resolveDataTableBody(rows, rowGroups ?? [])), [rows, rowGroups]);\n const rowById = useMemo(() => new Map(rows.map((row) => [row.id, row])), [rows]);\n const columnSpan = columns.length + (hasSelector ? 1 : 0);\n\n const toggleRow = (row: DataTableRowDescriptor<RowKey>) => {\n if (row.disabled || !selection || selection.mode === \"none\") return;\n if (selection.mode === \"single\") {\n const current = selectedKeys.has(row.id) ? row.id : null;\n const next = current === row.id && selection.disallowEmptySelection !== true ? null : row.id;\n commitSelection(next === null ? new Set<RowKey>() : new Set<RowKey>([next]));\n selection.onSelectionChange?.(next);\n return;\n }\n const next = new Set(selectedKeys);\n if (next.has(row.id)) next.delete(row.id); else next.add(row.id);\n commitSelection(next);\n selection.onSelectionChange?.(next);\n };\n const toggleAll = () => {\n if (!multiple) return;\n // Disabled rows stay out of both the denominator and the result, matching\n // the shared select-all accounting.\n const selectable = rows.filter((row) => !row.disabled);\n const next = selectAllState === true\n ? new Set([...selectedKeys].filter((id) => !selectable.some((row) => row.id === id)))\n : new Set([...selectedKeys, ...selectable.map((row) => row.id)]);\n commitSelection(next);\n selection.onSelectionChange?.(next);\n };\n\n return (\n <div className={classNames(\"hjm-data-table\", className)} style={layoutStyle} data-density={density}>\n {asyncState.status === \"idle\" ? null : (\n <p className=\"hjm-data-table__state\" role={asyncState.status === \"error\" ? \"alert\" : \"status\"}>{asyncState.message}</p>\n )}\n <TableScroll scroll={pinnedCount > 0} maxBodyHeight={maxBodyHeight}>\n <table ref={forwardedRef} className=\"hjm-data-table__table\" aria-label={labels.table} aria-busy={asyncState.status === \"loading\" || undefined}>\n <thead>\n {bands ? (\n <tr className=\"hjm-data-table__bands\">\n {hasSelector ? <td aria-hidden=\"true\" className={classNames(\"hjm-data-table__selector\", selectorSticky.className)} style={selectorSticky.style} /> : null}\n {bands.map((band) => {\n const first = columns.findIndex((column) => column.id === band.columnIds[0]);\n // A band sticks only when every column under it is pinned; otherwise it scrolls with them.\n const sticky = first + band.span <= pinnedCount ? columnSticky(first + band.span - 1) : {};\n const stickyStyle = sticky.style ? { insetInlineStart: pinnedOffsets[first + (hasSelector ? 1 : 0)] ?? 0 } : undefined;\n return band.header === null\n ? <td key={band.id} aria-hidden=\"true\" colSpan={band.span} className={sticky.className} style={stickyStyle} />\n : <th key={band.id} scope=\"colgroup\" colSpan={band.span} className={classNames(\"hjm-data-table__band\", sticky.className)} style={stickyStyle}><span>{band.header}</span></th>;\n })}\n </tr>\n ) : null}\n <tr ref={headerRowRef}>\n {selection && selection.mode !== \"none\" ? (\n <th scope=\"col\" className={classNames(\"hjm-data-table__selector\", selectorSticky.className)} style={selectorSticky.style}>\n {multiple ? (\n <button\n type=\"button\"\n role=\"checkbox\"\n aria-checked={selectAllState === \"mixed\" ? \"mixed\" : String(selectAllState === true) as \"true\" | \"false\"}\n aria-label={labels.selectAll}\n className=\"hjm-data-table__check\"\n onClick={toggleAll}\n >\n {selectAllState === true ? \"✓\" : selectAllState === \"mixed\" ? \"–\" : \"\"}\n </button>\n ) : null}\n </th>\n ) : null}\n {columns.map((column, columnIndex) => {\n const sorted = sortState?.columnId === column.id ? sortState.direction : null;\n const sticky = columnSticky(columnIndex);\n return (\n <th\n key={column.id}\n scope=\"col\"\n // The direction passes straight through to aria-sort; the\n // contract already speaks that vocabulary.\n aria-sort={sorted ?? undefined}\n className={sticky.className}\n style={{ inlineSize: column.width, textAlign: column.align ?? dataTableColumnDefaults.align, ...sticky.style } as CSSProperties}\n >\n {column.sortable ? (\n // A button inside the header, never the header itself.\n <TableSortButton\n header={column.header}\n direction={sorted}\n glyphs={{ ascending: \"▲\", descending: \"▼\", none: \"↕\" }}\n className=\"hjm-data-table__sort\"\n accessibleName={labels.sortColumn(column.header, sortState as DataTableSortState<string>)}\n // `sortDescFirst` columns (stat tables: highest first) start descending.\n onSort={() => onSortChange?.(getNextDataTableSortState(sortState, column.id, sortCycle, dataTableFirstSortDirection(column)))}\n />\n ) : column.header}\n </th>\n );\n })}\n </tr>\n </thead>\n {sections.map((section) => (\n <tbody key={section.group?.id ?? \"rows\"}>\n {section.group ? (\n <tr className=\"hjm-data-table__group\">\n <th scope=\"rowgroup\" colSpan={columnSpan}><span>{section.group.label}</span></th>\n </tr>\n ) : null}\n {section.rows.map((rowId) => rowById.get(rowId)!).map((row) => (\n <tr key={row.id} aria-selected={selection && selection.mode !== \"none\" ? selectedKeys.has(row.id) : undefined} data-disabled={row.disabled || undefined}>\n {selection && selection.mode !== \"none\" ? (\n <td className={classNames(\"hjm-data-table__selector\", selectorSticky.className)} style={selectorSticky.style}>\n <button\n type=\"button\"\n role={multiple ? \"checkbox\" : \"radio\"}\n aria-checked={selectedKeys.has(row.id)}\n aria-label={labels.selectRow(row.id)}\n aria-disabled={row.disabled || undefined}\n className=\"hjm-data-table__check\"\n onClick={() => toggleRow(row)}\n >\n {selectedKeys.has(row.id) ? \"✓\" : \"\"}\n </button>\n </td>\n ) : null}\n {columns.map((column, columnIndex) => {\n const sticky = columnSticky(columnIndex);\n const emphasis = tableCellEmphasis(cellStyle?.(row.id, column.id), heatColors);\n // Sub-rows (TanStack `depth`) indent only the first column, which is the pinned one when pinning.\n const depth = columnIndex === 0 ? row.depth ?? 0 : 0;\n return (\n <td\n key={column.id}\n className={classNames(sticky.className, emphasis.heat && \"hjm-table-family__heat\")}\n data-tone={emphasis.tone}\n data-heat={emphasis.heat}\n data-depth={depth > 0 ? depth : undefined}\n style={{\n textAlign: column.align ?? dataTableColumnDefaults.align,\n ...emphasis.style,\n ...(depth > 0 ? { \"--hjm-data-table-indent\": `${depth * dataTableFamilyRecipe.depthIndent}px` } : {}),\n ...sticky.style,\n } as CSSProperties}\n >\n {renderCell(row.id, column.id)}\n </td>\n );\n })}\n </tr>\n ))}\n </tbody>\n ))}\n {summaryRow ? (\n <tfoot>\n <tr className=\"hjm-data-table__summary\">\n {hasSelector ? <td className={classNames(\"hjm-data-table__selector\", selectorSticky.className)} style={selectorSticky.style} /> : null}\n {columns.map((column, columnIndex) => {\n const sticky = columnSticky(columnIndex);\n return (\n <td key={column.id} className={sticky.className} style={{ textAlign: column.align ?? dataTableColumnDefaults.align, ...sticky.style } as CSSProperties}>\n {summaryRow(column.id)}\n </td>\n );\n })}\n </tr>\n </tfoot>\n ) : null}\n </table>\n </TableScroll>\n {/* Pagination or load-more is composed beneath the table, never owned by it. */}\n {footer ? <div className=\"hjm-data-table__footer\">{footer}</div> : null}\n </div>\n );\n}) as <ColumnKey extends string = string, RowKey extends string = string>(\n props: DataTableProps<ColumnKey, RowKey> & { ref?: React.Ref<HTMLTableElement> },\n) => React.ReactElement | null;\n\nexport {\n LineScore,\n MatrixTable,\n ComparisonTable,\n type LineScoreProps,\n type MatrixTableProps,\n type ComparisonTableProps,\n type TableHeatColors,\n} from \"./table-family.js\";\n"]}
@@ -0,0 +1,67 @@
1
+ import { binHistogram, resolveFunnel, resolveImportMapping, resolveTarget, type Cohort, type CorrelationData, type FunnelStage, type HistogramBin, type ImportMapping, type WorkflowField } from "@hjmds/design-contracts/data-workflows";
2
+ export type ImportFieldMappingProps = Readonly<{
3
+ label: string;
4
+ sources: readonly WorkflowField[];
5
+ targets: readonly WorkflowField[];
6
+ mapping: ImportMapping;
7
+ onMappingChange: (mapping: ImportMapping) => void;
8
+ onImport: (mapping: ImportMapping) => void | Promise<void>;
9
+ importLabel: string;
10
+ skipLabel: string;
11
+ errorLabel: string;
12
+ disabled?: boolean;
13
+ validationLabel: (state: ReturnType<typeof resolveImportMapping>) => string;
14
+ previewRows?: readonly Readonly<{
15
+ id: string;
16
+ values: Readonly<Record<string, string>>;
17
+ }>[];
18
+ }>;
19
+ export declare function ImportFieldMapping(props: ImportFieldMappingProps): import("react").JSX.Element;
20
+ export type ConversionFunnelProps = Readonly<{
21
+ label: string;
22
+ stages: readonly FunnelStage[];
23
+ formatCount: (count: number) => string;
24
+ missingLabel: string;
25
+ detailLabel: (stage: ReturnType<typeof resolveFunnel>[number]) => string;
26
+ }>;
27
+ export declare function ConversionFunnel(props: ConversionFunnelProps): import("react").JSX.Element;
28
+ export type CohortRetentionTableProps = Readonly<{
29
+ label: string;
30
+ cohorts: readonly Cohort[];
31
+ periods: readonly string[];
32
+ cohortColumnLabel: string;
33
+ formatRate: (rate: number, retained: number, size: number) => string;
34
+ missingLabel: string;
35
+ }>;
36
+ export declare function CohortRetentionTable(props: CohortRetentionTableProps): import("react").JSX.Element;
37
+ export type CorrelationMatrixProps = Readonly<{
38
+ label: string;
39
+ data: CorrelationData;
40
+ variableColumnLabel: string;
41
+ formatValue: (value: number) => string;
42
+ missingLabel: string;
43
+ }>;
44
+ export declare function CorrelationMatrix(props: CorrelationMatrixProps): import("react").JSX.Element;
45
+ export type TargetSummaryProps = Readonly<{
46
+ label: string;
47
+ actual: number;
48
+ target: number;
49
+ formatActual: (actual: number) => string;
50
+ summaryLabel: (summary: ReturnType<typeof resolveTarget>) => string;
51
+ }>;
52
+ export declare function TargetSummary(props: TargetSummaryProps): import("react").JSX.Element;
53
+ export type HistogramExplorerProps = Readonly<{
54
+ label: string;
55
+ samples: readonly (number | null)[];
56
+ binCounts: readonly number[];
57
+ binCount: number;
58
+ onBinCountChange: (count: number) => void;
59
+ domain?: readonly [number, number];
60
+ binLabel: (bin: HistogramBin) => string;
61
+ binCountLabel: (count: number) => string;
62
+ formatCount: (count: number) => string;
63
+ missingLabel: string;
64
+ summaryLabel: (histogram: ReturnType<typeof binHistogram>) => string;
65
+ }>;
66
+ export declare function HistogramExplorer(props: HistogramExplorerProps): import("react").JSX.Element;
67
+ //# sourceMappingURL=data-workflows.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-workflows.d.ts","sourceRoot":"","sources":["../src/data-workflows.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useState } from "react";
4
+ import { binHistogram, resolveCohorts, resolveFunnel, resolveImportMapping, resolveTarget, validateCorrelation } from "@hjmds/design-contracts/data-workflows";
5
+ import { assertDistinctIds } from "@hjmds/design-contracts/collection-editing";
6
+ import { resolveDatavizSeriesColor } from "@hjmds/design-contracts/dataviz";
7
+ import { Button } from "./actions.js";
8
+ import { Select } from "./select.js";
9
+ import { Stack, Text } from "./layout.js";
10
+ import { DataTable } from "./data-table.js";
11
+ import { BarChart } from "./chart.js";
12
+ import { Statistic } from "./advanced-display.js";
13
+ import { Progress } from "./feedback.js";
14
+ import { useHjmTheme } from "./provider.js";
15
+ import { useCompositionAction } from "./internal/use-composition-action.js";
16
+ // These read-only tables expose neither sort controls nor row selection.
17
+ const labelsFor = (label) => ({ table: label, selectAll: label, selectRow: (id) => id, sortColumn: (header) => header });
18
+ export function ImportFieldMapping(props) {
19
+ const action = useCompositionAction(props.disabled), resolved = resolveImportMapping(props.sources, props.targets, props.mapping);
20
+ const locked = props.disabled === true || action.pending, rows = props.previewRows ?? [];
21
+ assertDistinctIds(rows);
22
+ const pairs = props.targets.flatMap(target => { const source = props.sources.find(source => props.mapping[source.id] === target.id); return source ? [{ source, target }] : []; });
23
+ return _jsxs(Stack, { gap: "md", children: [_jsx(Text, { emphasis: "strong", children: props.label }), props.sources.map(source => _jsx(Select, { label: source.label, placeholder: props.skipLabel, emptySelectionLabel: props.skipLabel, disabled: locked, items: [{ id: resolved.skipKey, label: props.skipLabel, textValue: props.skipLabel }, ...props.targets.map(target => ({ id: target.id, label: target.label, textValue: target.label }))], selectedKey: props.mapping[source.id] ?? resolved.skipKey, onSelectionChange: key => { if (!locked)
24
+ props.onMappingChange({ ...props.mapping, [source.id]: key === resolved.skipKey ? null : key }); } }, source.id)), !resolved.valid ? _jsx(Text, { role: "status", style: { textAlign: "center" }, children: props.validationLabel(resolved) }) : null, resolved.valid && rows.length ? _jsx(DataTable, { columns: pairs.map(pair => ({ id: pair.target.id, header: pair.target.label })), rows: rows.map(row => ({ id: row.id })), labels: labelsFor(props.label), renderCell: (id, column) => rows.find(row => row.id === id)?.values[pairs.find(pair => pair.target.id === column).source.id] ?? "" }) : null, _jsx(Button, { loading: action.pending, disabled: props.disabled === true || !resolved.valid, onClick: () => { if (resolved.valid)
25
+ void action.run("import", () => props.onImport(resolved.mapping)); }, children: props.importLabel }), action.failed ? _jsx(Text, { role: "alert", tone: "danger", style: { textAlign: "center" }, children: props.errorLabel }) : null] });
26
+ }
27
+ export function ConversionFunnel(props) {
28
+ const stages = resolveFunnel(props.stages), [chosen, setChosen] = useState(null);
29
+ const selected = Math.max(0, stages.findIndex(stage => stage.id === chosen));
30
+ return _jsxs(Stack, { gap: "md", children: [_jsx(Text, { emphasis: "strong", children: props.label }), stages.length ? _jsxs(_Fragment, { children: [_jsx(BarChart, { categories: stages.map(stage => stage.label), series: [{ id: "count", label: props.label, values: stages.map(stage => stage.count) }], accessibilityLabel: props.label, formatValue: props.formatCount, missingLabel: props.missingLabel, orientation: "horizontal", focusIndex: selected, onFocusChange: index => { if (index !== null && stages[index])
31
+ setChosen(stages[index].id); } }), _jsx(Stack, { axis: "inline", gap: "sm", wrap: true, children: stages.map(stage => _jsx(Button, { tone: "ghost", selected: stages[selected].id === stage.id, onClick: () => setChosen(stage.id), children: stage.label }, stage.id)) }), _jsx(Text, { role: "status", children: props.detailLabel(stages[selected]) })] }) : _jsx(Text, { style: { textAlign: "center" }, children: props.missingLabel })] });
32
+ }
33
+ export function CohortRetentionTable(props) {
34
+ const rows = resolveCohorts(props.cohorts, props.periods.length);
35
+ const color = useHjmTheme().environment.theme;
36
+ // MatrixTable intentionally suppresses its diagonal for head-to-head teams. Cohort data is rectangular.
37
+ return _jsx(DataTable, { columns: [{ id: "label", header: props.cohortColumnLabel, pinned: "start" }, ...props.periods.map((header, index) => ({ id: `p-${index}`, header }))], rows: rows.map(row => ({ id: row.id })), labels: labelsFor(props.label), heatColors: { high: resolveDatavizSeriesColor(color, 0), low: resolveDatavizSeriesColor(color, 1) }, cellStyle: (rowId, column) => { const rate = rows.find(row => row.id === rowId).rates[Number(column.slice(2))]; return typeof rate === "number" && rate > 0 ? { heat: { side: "high", strength: rate >= .7 ? 3 : rate >= .35 ? 2 : 1 } } : null; }, renderCell: (rowId, column) => { const row = rows.find(row => row.id === rowId); if (column === "label")
38
+ return row.label; const index = Number(column.slice(2)), rate = row.rates[index]; return rate === null || rate === undefined ? props.missingLabel : props.formatRate(rate, row.retained[index], row.size); } });
39
+ }
40
+ export function CorrelationMatrix(props) {
41
+ validateCorrelation(props.data);
42
+ const color = useHjmTheme().environment.theme;
43
+ const valueOf = (row, column) => props.data.values[props.data.variables.findIndex(variable => variable.id === row)][Number(column.slice(2))];
44
+ return _jsx(DataTable, { columns: [{ id: "label", header: props.variableColumnLabel, pinned: "start" }, ...props.data.variables.map((variable, index) => ({ id: `v-${index}`, header: variable.label }))], rows: props.data.variables.map(variable => ({ id: variable.id })), labels: labelsFor(props.label), heatColors: { high: resolveDatavizSeriesColor(color, 0), low: resolveDatavizSeriesColor(color, 1) }, cellStyle: (row, column) => { if (column === "label")
45
+ return null; const value = valueOf(row, column); return value === null || value === undefined || value === 0 ? null : { heat: { side: value > 0 ? "high" : "low", strength: Math.abs(value) >= .7 ? 3 : Math.abs(value) >= .35 ? 2 : 1 } }; }, renderCell: (row, column) => { if (column === "label")
46
+ return props.data.variables.find(variable => variable.id === row).label; const value = valueOf(row, column); return value === null || value === undefined ? props.missingLabel : props.formatValue(value); } });
47
+ }
48
+ export function TargetSummary(props) {
49
+ const summary = resolveTarget(props.actual, props.target), label = props.summaryLabel(summary);
50
+ return _jsxs(Stack, { gap: "sm", children: [_jsx(Statistic, { descriptor: { id: "target-summary", label: props.label, value: props.formatActual(summary.actual), hint: label } }), summary.progress === null ? null : _jsx(Progress, { label: props.label, value: summary.progress, max: 1, valueText: label })] });
51
+ }
52
+ export function HistogramExplorer(props) {
53
+ if (!props.binCounts.length || !props.binCounts.includes(props.binCount) || new Set(props.binCounts).size !== props.binCounts.length)
54
+ throw new RangeError("Histogram requires distinct choices including the selected bin count");
55
+ const variants = props.binCounts.map(count => binHistogram(props.samples, count, props.domain)), histogram = variants[props.binCounts.indexOf(props.binCount)];
56
+ // Hold the y domain across rebinning. Normalizing each set to its tallest bar changes the meaning.
57
+ const maximum = Math.max(...variants.map(variant => variant.maxCount));
58
+ return _jsxs(Stack, { gap: "md", children: [_jsx(Text, { emphasis: "strong", children: props.label }), _jsx(Stack, { axis: "inline", gap: "sm", wrap: true, children: props.binCounts.map(count => _jsx(Button, { tone: "secondary", selected: count === props.binCount, onClick: () => props.onBinCountChange(count), children: props.binCountLabel(count) }, count)) }), histogram.observed ? _jsx(BarChart, { categories: histogram.bins.map(props.binLabel), series: [{ id: "frequency", label: props.label, values: histogram.bins.map(bin => bin.count) }], accessibilityLabel: props.label, formatValue: props.formatCount, missingLabel: props.missingLabel, domain: [0, maximum], showValues: true }) : _jsx(Text, { style: { textAlign: "center" }, children: props.missingLabel }), _jsx(Text, { role: "status", children: props.summaryLabel(histogram) })] });
59
+ }
60
+ //# sourceMappingURL=data-workflows.js.map