@godxjp/ui 31.5.0 → 31.6.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 (62) hide show
  1. package/agent/START-HERE.md +5 -5
  2. package/agent/components/BranchScopePicker.json +5 -0
  3. package/agent/components/Cascader.json +1 -1
  4. package/agent/components/DataTable.json +2 -2
  5. package/agent/components/InfiniteQueryState.json +6 -1
  6. package/agent/components/RangeTimeline.json +6 -0
  7. package/agent/components/RecordPicker.json +10 -0
  8. package/agent/components/Transfer.json +5 -0
  9. package/agent/components/Tree.json +21 -4
  10. package/agent/components/TreeSelect.json +1 -1
  11. package/agent/components.json +57 -9
  12. package/agent/index.json +5 -5
  13. package/agent/llms.txt +6 -6
  14. package/agent/tokens.json +6 -0
  15. package/dist/components/data-display/data-table.d.ts +3 -0
  16. package/dist/components/data-display/data-table.js +415 -370
  17. package/dist/components/data-display/range-timeline.d.ts +20 -0
  18. package/dist/components/data-display/range-timeline.js +217 -197
  19. package/dist/components/data-display/tree.js +179 -23
  20. package/dist/components/data-entry/branch-scope-picker.d.ts +1 -0
  21. package/dist/components/data-entry/branch-scope-picker.js +2 -1
  22. package/dist/components/data-entry/cascader.js +4 -5
  23. package/dist/components/data-entry/record-picker.d.ts +2 -0
  24. package/dist/components/data-entry/record-picker.js +22 -3
  25. package/dist/components/data-entry/transfer.d.ts +1 -1
  26. package/dist/components/data-entry/transfer.js +5 -2
  27. package/dist/components/data-entry/tree-select.js +14 -5
  28. package/dist/components/query/infinite-query-state.d.ts +1 -1
  29. package/dist/components/query/infinite-query-state.js +2 -1
  30. package/dist/contracts/measurement.json +1 -1
  31. package/dist/i18n/messages/en.json +16 -2
  32. package/dist/i18n/messages/ja.json +7 -2
  33. package/dist/i18n/messages/vi.json +7 -2
  34. package/dist/lib/hooks.d.ts +8 -4
  35. package/dist/lib/hooks.js +5 -2
  36. package/dist/lib/tree.d.ts +23 -0
  37. package/dist/lib/tree.js +29 -0
  38. package/dist/props/components/data-display.prop.d.ts +21 -2
  39. package/dist/props/components/data-entry.prop.d.ts +31 -4
  40. package/dist/props/components/query.prop.d.ts +8 -1
  41. package/dist/props/registry.d.ts +24 -6
  42. package/dist/props/registry.js +41 -4
  43. package/dist/props/vocabulary/data.prop.d.ts +32 -2
  44. package/dist/props/vocabulary/index.d.ts +1 -1
  45. package/dist/styles/data-display-layout.css +52 -0
  46. package/dist/styles/layers.json +1 -1
  47. package/dist/styles/table-layout.css +16 -1
  48. package/dist/tokens/components/tree.css +2 -0
  49. package/docs/data-display/data-table/examples/server-paged.tsx +23 -0
  50. package/docs/data-display/data-table/index.md +2 -0
  51. package/docs/data-display/timeline.tsx +33 -0
  52. package/docs/data-display/tree.tsx +154 -0
  53. package/docs/data-entry/branch-scope-picker.tsx +6 -1
  54. package/docs/data-entry/record-picker.tsx +1 -0
  55. package/docs/data-entry/transfer.tsx +1 -0
  56. package/docs/i18n/messages/en.json +19 -0
  57. package/docs/i18n/messages/ja.json +18 -0
  58. package/docs/i18n/messages/vi.json +18 -0
  59. package/docs/query/infinite-query-state.tsx +5 -1
  60. package/docs/roadmap/tree-components.md +2 -1
  61. package/package.json +2 -2
  62. package/scripts/ui-audit.mjs +15 -5
@@ -336,7 +336,15 @@ function DataTable({
336
336
  onFilterChange?.(merged);
337
337
  };
338
338
  const rowSelectionKeys = rowSelection?.selectedRowKeys;
339
- const rowSelectionState = rowSelectionKeys !== void 0 ? Object.fromEntries(rowSelectionKeys.map((key) => [key, true])) : controlledSelected !== void 0 ? selectionFromSet(controlledSelected) : internalSelection;
339
+ const ownSelectionState = rowSelectionKeys !== void 0 ? Object.fromEntries(rowSelectionKeys.map((key) => [key, true])) : controlledSelected !== void 0 ? selectionFromSet(controlledSelected) : internalSelection;
340
+ const isRowLocked = (row) => !!rowSelection?.getCheckboxProps?.(row).disabled;
341
+ const matching = rowSelection?.matching;
342
+ const rowSelectionState = matching?.selected ? {
343
+ ...ownSelectionState,
344
+ ...Object.fromEntries(
345
+ data.filter((row) => !isRowLocked(row)).map((row) => [getRowId(row), true])
346
+ )
347
+ } : ownSelectionState;
340
348
  const onSortingChange = (updater) => {
341
349
  const next = typeof updater === "function" ? updater(sortingState) : updater;
342
350
  if (columnSortOrders.length > 0) {
@@ -373,6 +381,9 @@ function DataTable({
373
381
  }
374
382
  }
375
383
  const keys = Object.keys(next).filter((key) => next[key]);
384
+ if (matching?.selected && data.some((row) => !isRowLocked(row) && !next[getRowId(row)])) {
385
+ matching.onSelectedChange(false);
386
+ }
376
387
  if (rowSelectionKeys === void 0 && controlledSelected === void 0) {
377
388
  setInternalSelection(next);
378
389
  }
@@ -418,7 +429,9 @@ function DataTable({
418
429
  // rows unsliced. Page count still derives from `rowCount`/the pre-paginated model either way.
419
430
  manualPagination: manualPagination || serverPaged || !paginationEngaged,
420
431
  rowCount: paginationConfig?.total ?? rowCount,
421
- enableRowSelection: selectionEnabled,
432
+ // A function only when rows CAN be locked, so a table without `getCheckboxProps` is untouched.
433
+ // TanStack's page toggles and header tri-state then skip a locked row in both directions.
434
+ enableRowSelection: selectionEnabled && rowSelection?.getCheckboxProps ? (row) => !isRowLocked(row.original) : selectionEnabled,
422
435
  state: {
423
436
  sorting: sortingState,
424
437
  columnFilters,
@@ -586,41 +599,49 @@ DataTable.SelectAll = function DataTableSelectAll() {
586
599
  {
587
600
  checked: allSelected ? true : someSelected ? "indeterminate" : false,
588
601
  onCheckedChange: (v) => table.toggleAllPageRowsSelected(!!v),
589
- "aria-label": t("dataTable.selectAll")
602
+ "aria-label": rowSelection?.selectAllLabel ?? // With `matching` on, the table is server-paged by declaration: this box selects the PAGE.
603
+ t(rowSelection?.matching ? "dataTable.selectPage" : "dataTable.selectAll")
590
604
  }
591
605
  );
592
606
  if (rowSelection?.columnTitle !== void 0) return /* @__PURE__ */ jsx(Fragment, { children: rowSelection.columnTitle });
593
607
  if (!rowSelection?.selections) return box;
594
608
  const pageKeys = table.getRowModel().rows.map((row) => getRowId(row.original));
595
609
  const selectedKeys = table.getSelectedRowModel().rows.map((row) => getRowId(row.original));
596
- const builtIn = [
597
- {
610
+ const builtIn = {
611
+ SELECT_ALL: {
598
612
  key: "all",
599
613
  text: t("dataTable.selectAll"),
600
614
  onSelect: () => table.toggleAllPageRowsSelected(true)
601
615
  },
602
- {
616
+ SELECT_INVERT: {
603
617
  key: "invert",
604
618
  text: t("dataTable.selectInvert"),
605
619
  onSelect: () => {
606
620
  table.setRowSelection(
607
621
  Object.fromEntries(
608
- table.getRowModel().rows.filter((row) => !row.getIsSelected()).map((row) => [getRowId(row.original), true])
622
+ table.getRowModel().rows.filter(
623
+ (row) => (
624
+ // A locked row keeps its state; every other row flips.
625
+ row.getCanSelect() ? !row.getIsSelected() : row.getIsSelected()
626
+ )
627
+ ).map((row) => [getRowId(row.original), true])
609
628
  )
610
629
  );
611
630
  }
612
631
  },
613
- {
632
+ SELECT_NONE: {
614
633
  key: "none",
615
634
  text: t("dataTable.selectNone"),
616
635
  onSelect: () => table.toggleAllPageRowsSelected(false)
617
636
  }
618
- ];
619
- const entries = rowSelection.selections === true ? builtIn : rowSelection.selections.map((entry) => ({
620
- key: entry.key,
621
- text: entry.text,
622
- onSelect: () => entry.onSelect(pageKeys.length > 0 ? pageKeys : selectedKeys)
623
- }));
637
+ };
638
+ const entries = rowSelection.selections === true ? [builtIn.SELECT_ALL, builtIn.SELECT_INVERT, builtIn.SELECT_NONE] : rowSelection.selections.map(
639
+ (entry) => typeof entry === "string" ? builtIn[entry] : {
640
+ key: entry.key,
641
+ text: entry.text,
642
+ onSelect: () => entry.onSelect(pageKeys.length > 0 ? pageKeys : selectedKeys)
643
+ }
644
+ );
624
645
  return /* @__PURE__ */ jsxs("span", { className: "ui-data-table-selection-menu", children: [
625
646
  box,
626
647
  /* @__PURE__ */ jsxs(DropdownMenu, { children: [
@@ -639,6 +660,9 @@ DataTable.SelectAll = function DataTableSelectAll() {
639
660
  ] });
640
661
  };
641
662
  DataTable.SelectAll.displayName = "DataTable.SelectAll";
663
+ DataTable.SELECTION_ALL = "SELECT_ALL";
664
+ DataTable.SELECTION_INVERT = "SELECT_INVERT";
665
+ DataTable.SELECTION_NONE = "SELECT_NONE";
642
666
  DataTable.BulkActions = function DataTableBulkActions({
643
667
  count,
644
668
  children,
@@ -915,6 +939,13 @@ DataTable.Content = function DataTableContent() {
915
939
  };
916
940
  const dataRows = table.getRowModel().rows;
917
941
  const rowCount = dataRows.length;
942
+ const matching = rowSelection?.type === "radio" ? void 0 : rowSelection?.matching;
943
+ const matchingOffered = !!matching && !matching.selected && rowCount > 0 && matching.total > rowCount && table.getIsAllPageRowsSelected();
944
+ const matchingShown = !!matching && (matching.selected || matchingOffered);
945
+ const clearMatching = () => {
946
+ if (dataRows.some((row) => row.getCanSelect())) table.toggleAllPageRowsSelected(false);
947
+ else matching?.onSelectedChange(false);
948
+ };
918
949
  const scrollStyle = {};
919
950
  const scrollVars = scrollStyle;
920
951
  if (scroll?.x !== void 0) scrollVars["--table-scroll-inline-size"] = cssLength(scroll.x);
@@ -936,7 +967,7 @@ DataTable.Content = function DataTableContent() {
936
967
  "data-scroll-y": scroll?.y !== void 0 ? "" : void 0,
937
968
  "aria-busy": loading,
938
969
  ...scrolls ? { role: "group", "aria-label": scrollRegionLabel(label, t), tabIndex: 0 } : {},
939
- children: /* @__PURE__ */ jsx(
970
+ children: /* @__PURE__ */ jsxs(
940
971
  "div",
941
972
  {
942
973
  className: "ui-data-table-surface",
@@ -944,386 +975,400 @@ DataTable.Content = function DataTableContent() {
944
975
  "data-table-layout": fixedTableLayout ? "fixed" : void 0,
945
976
  "data-striped": striped ? "" : void 0,
946
977
  "data-hoverable": hoverable ? "" : void 0,
947
- children: /* @__PURE__ */ jsxs(
948
- Table,
949
- {
950
- scrollable: preset !== "default",
951
- label,
952
- preset,
953
- collapseBelow,
954
- bordered,
955
- striped,
956
- children: [
957
- /* @__PURE__ */ jsx(
958
- TableHeader,
959
- {
960
- className: cn("bg-secondary", stickyHeader && "ui-data-table-sticky-header"),
961
- children: /* @__PURE__ */ jsxs(TableRow, { children: [
962
- expandColumnShown && /* @__PURE__ */ jsx(
963
- TableHead,
964
- {
965
- className: cn(
966
- "ui-data-table-expand-column",
967
- leadingFixed === "start" && "ui-data-table-pin-start"
968
- ),
969
- ...fixedCellProps("__expand", leadingFixed),
970
- style: leadingFixedStyle("__expand"),
971
- children: /* @__PURE__ */ jsx("span", { className: "sr-only", children: expandable?.columnTitle ?? t("dataTable.expandColumn") })
972
- }
973
- ),
974
- selectable && /* @__PURE__ */ jsx(
975
- TableHead,
976
- {
977
- className: cn(
978
- "ui-data-table-select-column",
979
- leadingFixed === "start" && "ui-data-table-pin-start"
980
- ),
981
- ...fixedCellProps("__select", leadingFixed),
982
- style: leadingFixedStyle("__select"),
983
- children: /* @__PURE__ */ jsx(DataTable.SelectAll, {})
984
- }
985
- ),
986
- visibleColumns.map((col) => {
987
- const isSortable = columnIsSortable(col);
988
- const activeDirection = directionOf(sortingState, col.key);
989
- const sortIndicator = isSortable ? activeDirection ? activeDirection === "asc" ? /* @__PURE__ */ jsx(ArrowUp, { className: "ui-data-table-sort-icon", "aria-hidden": "true" }) : /* @__PURE__ */ jsx(ArrowDown, { className: "ui-data-table-sort-icon", "aria-hidden": "true" }) : /* @__PURE__ */ jsx(
990
- ChevronsUpDown,
991
- {
992
- className: "ui-data-table-sort-icon text-muted-foreground",
993
- "aria-hidden": "true"
994
- }
995
- ) : null;
996
- const headerEmpty = isEmptyHeader(col.header);
997
- const headerContent = headerEmpty && col.ariaLabel ? /* @__PURE__ */ jsx("span", { className: "sr-only", children: col.ariaLabel }) : col.header;
998
- const label2 = /* @__PURE__ */ jsxs("span", { className: "ui-data-table-sort-label", children: [
999
- headerContent,
1000
- sortIndicator
1001
- ] });
1002
- const tooltipOn = col.showSorterTooltip ?? showSorterTooltip;
1003
- const nextDirection = nextSortDirection(
1004
- activeDirection,
1005
- columnSortDirections(col, sortDirections)
1006
- );
1007
- const sortHint = nextDirection === "asc" ? t("dataTable.sortAscending") : nextDirection === "desc" ? t("dataTable.sortDescending") : t("dataTable.sortCancel");
1008
- const sortButton = /* @__PURE__ */ jsx(
1009
- "button",
978
+ children: [
979
+ matching ? /* @__PURE__ */ jsxs("div", { className: "ui-data-table-matching", "data-active": matchingShown ? "" : void 0, children: [
980
+ /* @__PURE__ */ jsx("span", { role: "status", children: matchingShown ? matching.selected ? t("dataTable.matchingSelected", { count: matching.total }) : t("dataTable.matchingPage", { count: rowCount }) : null }),
981
+ matchingShown ? /* @__PURE__ */ jsx(
982
+ Button,
983
+ {
984
+ variant: "link",
985
+ size: "sm",
986
+ onClick: matching.selected ? clearMatching : () => matching.onSelectedChange(true),
987
+ children: matching.selected ? t("dataTable.selectNone") : t("dataTable.matchingSelectAll", { count: matching.total })
988
+ }
989
+ ) : null
990
+ ] }) : null,
991
+ /* @__PURE__ */ jsxs(
992
+ Table,
993
+ {
994
+ scrollable: preset !== "default",
995
+ label,
996
+ preset,
997
+ collapseBelow,
998
+ bordered,
999
+ striped,
1000
+ children: [
1001
+ /* @__PURE__ */ jsx(
1002
+ TableHeader,
1003
+ {
1004
+ className: cn("bg-secondary", stickyHeader && "ui-data-table-sticky-header"),
1005
+ children: /* @__PURE__ */ jsxs(TableRow, { children: [
1006
+ expandColumnShown && /* @__PURE__ */ jsx(
1007
+ TableHead,
1010
1008
  {
1011
- type: "button",
1012
- className: "ui-data-table-sort-button ui-focus-ring",
1013
- onClick: () => {
1014
- onHeaderClick(col);
1015
- },
1016
- children: label2
1009
+ className: cn(
1010
+ "ui-data-table-expand-column",
1011
+ leadingFixed === "start" && "ui-data-table-pin-start"
1012
+ ),
1013
+ ...fixedCellProps("__expand", leadingFixed),
1014
+ style: leadingFixedStyle("__expand"),
1015
+ children: /* @__PURE__ */ jsx("span", { className: "sr-only", children: expandable?.columnTitle ?? t("dataTable.expandColumn") })
1017
1016
  }
1018
- );
1019
- return /* @__PURE__ */ jsxs(
1017
+ ),
1018
+ selectable && /* @__PURE__ */ jsx(
1020
1019
  TableHead,
1021
1020
  {
1022
- priority: col.priority,
1023
- "data-empty": headerEmpty || void 0,
1024
- "data-align": col.headerAlign ?? col.align,
1025
- "aria-sort": isSortable ? activeDirection ? activeDirection === "asc" ? "ascending" : "descending" : "none" : void 0,
1026
- ...fixedCellProps(col.key, fixedEdge(col)),
1027
- ...columnHideBelowProps(col),
1028
- style: columnCellStyle(col),
1029
1021
  className: cn(
1030
- columnCellClass(col),
1031
- // `headerAlign` when the heading differs from the rows,
1032
- // `align` otherwise. A table wanting centred headings
1033
- // over start-aligned text could not say so before, and
1034
- // consumers reached for `[&_th_button]:justify-center`.
1035
- (col.headerAlign ?? col.align) === "right" && "text-end",
1036
- (col.headerAlign ?? col.align) === "center" && "text-center",
1037
- isSortable && "select-none"
1022
+ "ui-data-table-select-column",
1023
+ leadingFixed === "start" && "ui-data-table-pin-start"
1038
1024
  ),
1039
- children: [
1040
- isSortable ? tooltipOn ? /* @__PURE__ */ jsxs(Tooltip, { children: [
1041
- /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: sortButton }),
1042
- /* @__PURE__ */ jsx(TooltipContent, { children: sortHint })
1043
- ] }) : sortButton : label2,
1044
- col.filters ? /* @__PURE__ */ jsx(ColumnFilterMenu, { column: col }) : null
1045
- ]
1046
- },
1047
- col.key
1048
- );
1049
- })
1050
- ] })
1051
- }
1052
- ),
1053
- /* @__PURE__ */ jsx(TableBody, { children: loading ? (
1054
- // Shaped skeleton rows rendered INSIDE the real table grid so they
1055
- // share its borders + column widths — no second framed container
1056
- // (which double-borders when the table sits in a Card). Count is
1057
- // bounded to the previous page so the height barely shifts.
1058
- Array.from({ length: Math.min(Math.max(rowCount, 6), 10) }).map((_, i) => /* @__PURE__ */ jsxs(TableRow, { className: cn(rowPadding, "hover:bg-transparent"), children: [
1059
- expandColumnShown && /* @__PURE__ */ jsx(TableCell, { className: cellPadding }),
1060
- selectable && /* @__PURE__ */ jsx(TableCell, { className: cellPadding, children: /* @__PURE__ */ jsx("div", { className: "ui-skeleton-block ui-data-table-skeleton-check" }) }),
1061
- visibleColumns.map((col, j) => /* @__PURE__ */ jsx(
1025
+ ...fixedCellProps("__select", leadingFixed),
1026
+ style: leadingFixedStyle("__select"),
1027
+ children: /* @__PURE__ */ jsx(DataTable.SelectAll, {})
1028
+ }
1029
+ ),
1030
+ visibleColumns.map((col) => {
1031
+ const isSortable = columnIsSortable(col);
1032
+ const activeDirection = directionOf(sortingState, col.key);
1033
+ const sortIndicator = isSortable ? activeDirection ? activeDirection === "asc" ? /* @__PURE__ */ jsx(ArrowUp, { className: "ui-data-table-sort-icon", "aria-hidden": "true" }) : /* @__PURE__ */ jsx(ArrowDown, { className: "ui-data-table-sort-icon", "aria-hidden": "true" }) : /* @__PURE__ */ jsx(
1034
+ ChevronsUpDown,
1035
+ {
1036
+ className: "ui-data-table-sort-icon text-muted-foreground",
1037
+ "aria-hidden": "true"
1038
+ }
1039
+ ) : null;
1040
+ const headerEmpty = isEmptyHeader(col.header);
1041
+ const headerContent = headerEmpty && col.ariaLabel ? /* @__PURE__ */ jsx("span", { className: "sr-only", children: col.ariaLabel }) : col.header;
1042
+ const label2 = /* @__PURE__ */ jsxs("span", { className: "ui-data-table-sort-label", children: [
1043
+ headerContent,
1044
+ sortIndicator
1045
+ ] });
1046
+ const tooltipOn = col.showSorterTooltip ?? showSorterTooltip;
1047
+ const nextDirection = nextSortDirection(
1048
+ activeDirection,
1049
+ columnSortDirections(col, sortDirections)
1050
+ );
1051
+ const sortHint = nextDirection === "asc" ? t("dataTable.sortAscending") : nextDirection === "desc" ? t("dataTable.sortDescending") : t("dataTable.sortCancel");
1052
+ const sortButton = /* @__PURE__ */ jsx(
1053
+ "button",
1054
+ {
1055
+ type: "button",
1056
+ className: "ui-data-table-sort-button ui-focus-ring",
1057
+ onClick: () => {
1058
+ onHeaderClick(col);
1059
+ },
1060
+ children: label2
1061
+ }
1062
+ );
1063
+ return /* @__PURE__ */ jsxs(
1064
+ TableHead,
1065
+ {
1066
+ priority: col.priority,
1067
+ "data-empty": headerEmpty || void 0,
1068
+ "data-align": col.headerAlign ?? col.align,
1069
+ "aria-sort": isSortable ? activeDirection ? activeDirection === "asc" ? "ascending" : "descending" : "none" : void 0,
1070
+ ...fixedCellProps(col.key, fixedEdge(col)),
1071
+ ...columnHideBelowProps(col),
1072
+ style: columnCellStyle(col),
1073
+ className: cn(
1074
+ columnCellClass(col),
1075
+ // `headerAlign` when the heading differs from the rows,
1076
+ // `align` otherwise. A table wanting centred headings
1077
+ // over start-aligned text could not say so before, and
1078
+ // consumers reached for `[&_th_button]:justify-center`.
1079
+ (col.headerAlign ?? col.align) === "right" && "text-end",
1080
+ (col.headerAlign ?? col.align) === "center" && "text-center",
1081
+ isSortable && "select-none"
1082
+ ),
1083
+ children: [
1084
+ isSortable ? tooltipOn ? /* @__PURE__ */ jsxs(Tooltip, { children: [
1085
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: sortButton }),
1086
+ /* @__PURE__ */ jsx(TooltipContent, { children: sortHint })
1087
+ ] }) : sortButton : label2,
1088
+ col.filters ? /* @__PURE__ */ jsx(ColumnFilterMenu, { column: col }) : null
1089
+ ]
1090
+ },
1091
+ col.key
1092
+ );
1093
+ })
1094
+ ] })
1095
+ }
1096
+ ),
1097
+ /* @__PURE__ */ jsx(TableBody, { children: loading ? (
1098
+ // Shaped skeleton rows rendered INSIDE the real table grid so they
1099
+ // share its borders + column widths — no second framed container
1100
+ // (which double-borders when the table sits in a Card). Count is
1101
+ // bounded to the previous page so the height barely shifts.
1102
+ Array.from({ length: Math.min(Math.max(rowCount, 6), 10) }).map((_, i) => /* @__PURE__ */ jsxs(TableRow, { className: cn(rowPadding, "hover:bg-transparent"), children: [
1103
+ expandColumnShown && /* @__PURE__ */ jsx(TableCell, { className: cellPadding }),
1104
+ selectable && /* @__PURE__ */ jsx(TableCell, { className: cellPadding, children: /* @__PURE__ */ jsx("div", { className: "ui-skeleton-block ui-data-table-skeleton-check" }) }),
1105
+ visibleColumns.map((col, j) => /* @__PURE__ */ jsx(
1106
+ TableCell,
1107
+ {
1108
+ priority: col.priority,
1109
+ "data-align": col.align,
1110
+ ...columnHideBelowProps(col),
1111
+ style: columnCellStyle(col),
1112
+ className: cn(cellPadding, columnCellClass(col)),
1113
+ children: /* @__PURE__ */ jsx(
1114
+ "div",
1115
+ {
1116
+ className: cn(
1117
+ "ui-skeleton-block ui-data-table-skeleton-line",
1118
+ j === 0 ? "w-1/2" : "w-3/4",
1119
+ col.align === "right" && "ms-auto",
1120
+ col.align === "center" && "mx-auto"
1121
+ )
1122
+ }
1123
+ )
1124
+ },
1125
+ col.key
1126
+ ))
1127
+ ] }, `skeleton-${i}`))
1128
+ ) : isDenied ? (
1129
+ // repeating a 403 cannot succeed. `warning` (not destructive) keeps it a policy
1130
+ // statement rather than a system fault. Announced politely: a permission boundary
1131
+ // is expected information, not an interruption.
1132
+ /* @__PURE__ */ jsx(TableRow, { className: "hover:bg-transparent", children: /* @__PURE__ */ jsx(
1062
1133
  TableCell,
1063
1134
  {
1064
- priority: col.priority,
1065
- "data-align": col.align,
1066
- ...columnHideBelowProps(col),
1067
- style: columnCellStyle(col),
1068
- className: cn(cellPadding, columnCellClass(col)),
1069
- children: /* @__PURE__ */ jsx(
1070
- "div",
1135
+ colSpan: emptyColSpan,
1136
+ className: "ui-data-table-empty",
1137
+ "aria-live": "polite",
1138
+ children: denied === true ? /* @__PURE__ */ jsx(
1139
+ EmptyState,
1071
1140
  {
1072
- className: cn(
1073
- "ui-skeleton-block ui-data-table-skeleton-line",
1074
- j === 0 ? "w-1/2" : "w-3/4",
1075
- col.align === "right" && "ms-auto",
1076
- col.align === "center" && "mx-auto"
1077
- )
1141
+ icon: ShieldAlert,
1142
+ tone: "warning",
1143
+ title: t("dataTable.denied"),
1144
+ description: t("dataTable.deniedDescription"),
1145
+ titleAs: "p"
1078
1146
  }
1079
- )
1080
- },
1081
- col.key
1082
- ))
1083
- ] }, `skeleton-${i}`))
1084
- ) : isDenied ? (
1085
- // repeating a 403 cannot succeed. `warning` (not destructive) keeps it a policy
1086
- // statement rather than a system fault. Announced politely: a permission boundary
1087
- // is expected information, not an interruption.
1088
- /* @__PURE__ */ jsx(TableRow, { className: "hover:bg-transparent", children: /* @__PURE__ */ jsx(
1147
+ ) : denied
1148
+ }
1149
+ ) })
1150
+ ) : isError ? (
1151
+ // Announced assertively (`role="alert"`) because the
1152
+ // user's request did not complete — unlike empty/denied, which are states of the data
1153
+ // itself. The role sits on an inner wrapper, never on the <td>: overriding a cell's
1154
+ // role would strip it out of the table's grid semantics for AT.
1155
+ /* @__PURE__ */ jsx(TableRow, { className: "hover:bg-transparent", children: /* @__PURE__ */ jsx(TableCell, { colSpan: emptyColSpan, className: "ui-data-table-empty", children: /* @__PURE__ */ jsx("div", { role: "alert", children: error === true ? /* @__PURE__ */ jsx(
1156
+ EmptyState,
1157
+ {
1158
+ icon: AlertCircle,
1159
+ tone: "destructive",
1160
+ title: t("dataTable.error"),
1161
+ description: t("dataTable.errorDescription"),
1162
+ titleAs: "p",
1163
+ action: onRetry ? /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: onRetry, children: [
1164
+ /* @__PURE__ */ jsx(RefreshCw, { "aria-hidden": "true" }),
1165
+ t("common.retry")
1166
+ ] }) : void 0
1167
+ }
1168
+ ) : error }) }) })
1169
+ ) : rowCount === 0 ? /* @__PURE__ */ jsx(TableRow, { className: "hover:bg-transparent", children: /* @__PURE__ */ jsx(
1089
1170
  TableCell,
1090
1171
  {
1091
1172
  colSpan: emptyColSpan,
1092
1173
  className: "ui-data-table-empty",
1093
1174
  "aria-live": "polite",
1094
- children: denied === true ? /* @__PURE__ */ jsx(
1095
- EmptyState,
1096
- {
1097
- icon: ShieldAlert,
1098
- tone: "warning",
1099
- title: t("dataTable.denied"),
1100
- description: t("dataTable.deniedDescription"),
1101
- titleAs: "p"
1102
- }
1103
- ) : denied
1175
+ children: empty ?? /* @__PURE__ */ jsx(EmptyState, { title: t("dataTable.empty"), titleAs: "p" })
1104
1176
  }
1105
- ) })
1106
- ) : isError ? (
1107
- // Announced assertively (`role="alert"`) because the
1108
- // user's request did not complete — unlike empty/denied, which are states of the data
1109
- // itself. The role sits on an inner wrapper, never on the <td>: overriding a cell's
1110
- // role would strip it out of the table's grid semantics for AT.
1111
- /* @__PURE__ */ jsx(TableRow, { className: "hover:bg-transparent", children: /* @__PURE__ */ jsx(TableCell, { colSpan: emptyColSpan, className: "ui-data-table-empty", children: /* @__PURE__ */ jsx("div", { role: "alert", children: error === true ? /* @__PURE__ */ jsx(
1112
- EmptyState,
1113
- {
1114
- icon: AlertCircle,
1115
- tone: "destructive",
1116
- title: t("dataTable.error"),
1117
- description: t("dataTable.errorDescription"),
1118
- titleAs: "p",
1119
- action: onRetry ? /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: onRetry, children: [
1120
- /* @__PURE__ */ jsx(RefreshCw, { "aria-hidden": "true" }),
1121
- t("common.retry")
1122
- ] }) : void 0
1123
- }
1124
- ) : error }) }) })
1125
- ) : rowCount === 0 ? /* @__PURE__ */ jsx(TableRow, { className: "hover:bg-transparent", children: /* @__PURE__ */ jsx(
1126
- TableCell,
1127
- {
1128
- colSpan: emptyColSpan,
1129
- className: "ui-data-table-empty",
1130
- "aria-live": "polite",
1131
- children: empty ?? /* @__PURE__ */ jsx(EmptyState, { title: t("dataTable.empty"), titleAs: "p" })
1132
- }
1133
- ) }) : dataRows.map((row, rowIndex) => {
1134
- const original = row.original;
1135
- const rowKey = getRowId(original);
1136
- const isSelected = row.getIsSelected();
1137
- const checkboxProps = rowSelection?.getCheckboxProps?.(original) ?? {};
1138
- const labelValue = selectLabelColumn ? original[selectLabelColumn.key] : void 0;
1139
- const rowLabel = getRowLabel?.(original) ?? (typeof labelValue === "string" && labelValue.trim() !== "" || typeof labelValue === "number" ? String(labelValue) : row.id);
1140
- const selectRowLabel = checkboxProps["aria-label"] ?? t("dataTable.selectRow", { id: rowLabel });
1141
- const canExpand = expandColumnShown && (expandable?.rowExpandable?.(original) ?? true);
1142
- const isExpanded = canExpand && expandedKeys.includes(rowKey);
1143
- const rowProps = onRow?.(original, rowIndex) ?? {};
1144
- const isInteractiveTarget = (target) => !!target.closest(
1145
- "button, a, input, select, textarea, [role=menuitem], [role=checkbox], [role=radio]"
1146
- );
1147
- const expandByClick = canExpand && expandable?.expandRowByClick;
1148
- return /* @__PURE__ */ jsxs(React.Fragment, { children: [
1149
- /* @__PURE__ */ jsxs(
1150
- TableRow,
1151
- {
1152
- ...rowProps,
1153
- "data-state": isSelected ? "selected" : void 0,
1154
- "data-tone": rowTone?.(original),
1155
- tabIndex: onRowClick ? 0 : rowProps.tabIndex,
1156
- onClick: (e) => {
1157
- rowProps.onClick?.(e);
1158
- const target = e.target;
1159
- if (isInteractiveTarget(target)) return;
1160
- if (expandByClick) toggleExpanded(rowKey);
1161
- onRowClick?.(original);
1162
- },
1163
- onKeyDown: onRowClick ? (e) => {
1164
- rowProps.onKeyDown?.(e);
1165
- if (e.key !== "Enter" && e.key !== " ") return;
1166
- if (e.target !== e.currentTarget) return;
1167
- e.preventDefault();
1168
- onRowClick?.(original);
1169
- } : rowProps.onKeyDown,
1170
- className: cn(
1171
- rowPadding,
1172
- // Hover highlight when rows are clickable OR explicitly hoverable… The
1173
- // --accent step TableRow itself uses, not --muted/50: that sat BELOW the
1174
- // zebra stripe (--muted/0.8, gh#700), so hovering a striped row lightened it.
1175
- // Same token-driven formula as TableRow's own hover fill (gh#894) — a bare
1176
- // `/70` here would silently outrank a theme's retuned default at equal
1177
- // specificity, and `color-mix` (not `hsl(x / var(y, 70%))`) because a `/`
1178
- // inside a Tailwind arbitrary property is read as ITS OWN opacity suffix.
1179
- (onRowClick || hoverable) && "hover:[background-color:color-mix(in_oklab,hsl(var(--accent))_var(--table-row-hover-background-alpha,70%),transparent)]",
1180
- // …but the affordance (cursor + focus mark) only when clickable.
1181
- //
1182
- // `ui-focus-ring` = the single focus source (styles/focus-ring.css). It
1183
- // replaces `focus-visible:ring-ring focus-visible:ring-2
1184
- // focus-visible:ring-inset`, which was a hand-rolled second ring: it read no
1185
- // `--focus-ring-*` knob, so a service could not retune it, and — being a
1186
- // utility — it painted regardless of the `--focus-outline` switch, which is
1187
- // exactly the hole the switch exists to close.
1188
- onRowClick && "ui-focus-ring cursor-pointer",
1189
- isSelected && "bg-muted/30",
1190
- rowProps.className,
1191
- rowClassName?.(original)
1192
- ),
1193
- children: [
1194
- expandColumnShown && /* @__PURE__ */ jsx(
1195
- TableCell,
1196
- {
1197
- className: cn(
1198
- cellPadding,
1199
- leadingFixed === "start" && "ui-data-table-pin-start"
1200
- ),
1201
- ...fixedCellProps("__expand", leadingFixed),
1202
- style: leadingFixedStyle("__expand"),
1203
- children: canExpand ? /* @__PURE__ */ jsx(
1204
- Button,
1205
- {
1206
- variant: "ghost",
1207
- size: "icon-xs",
1208
- "aria-expanded": isExpanded,
1209
- "aria-label": isExpanded ? t("dataTable.collapseRow") : t("dataTable.expandRow"),
1210
- className: "ui-data-table-expand-trigger",
1211
- onClick: (e) => {
1212
- e.stopPropagation();
1213
- toggleExpanded(rowKey);
1214
- },
1215
- children: isExpanded ? /* @__PURE__ */ jsx(
1216
- ChevronDown,
1217
- {
1218
- className: "ui-data-table-sort-icon",
1219
- "aria-hidden": "true"
1220
- }
1221
- ) : /* @__PURE__ */ jsx(
1222
- ChevronRight,
1223
- {
1224
- className: "ui-data-table-sort-icon",
1225
- "aria-hidden": "true"
1226
- }
1227
- )
1228
- }
1229
- ) : null
1230
- }
1177
+ ) }) : dataRows.map((row, rowIndex) => {
1178
+ const original = row.original;
1179
+ const rowKey = getRowId(original);
1180
+ const isSelected = row.getIsSelected();
1181
+ const checkboxProps = rowSelection?.getCheckboxProps?.(original) ?? {};
1182
+ const labelValue = selectLabelColumn ? original[selectLabelColumn.key] : void 0;
1183
+ const rowLabel = getRowLabel?.(original) ?? (typeof labelValue === "string" && labelValue.trim() !== "" || typeof labelValue === "number" ? String(labelValue) : row.id);
1184
+ const selectRowLabel = checkboxProps["aria-label"] ?? t("dataTable.selectRow", { id: rowLabel });
1185
+ const canExpand = expandColumnShown && (expandable?.rowExpandable?.(original) ?? true);
1186
+ const isExpanded = canExpand && expandedKeys.includes(rowKey);
1187
+ const rowProps = onRow?.(original, rowIndex) ?? {};
1188
+ const isInteractiveTarget = (target) => !!target.closest(
1189
+ "button, a, input, select, textarea, [role=menuitem], [role=checkbox], [role=radio]"
1190
+ );
1191
+ const expandByClick = canExpand && expandable?.expandRowByClick;
1192
+ return /* @__PURE__ */ jsxs(React.Fragment, { children: [
1193
+ /* @__PURE__ */ jsxs(
1194
+ TableRow,
1195
+ {
1196
+ ...rowProps,
1197
+ "data-state": isSelected ? "selected" : void 0,
1198
+ "data-tone": rowTone?.(original),
1199
+ tabIndex: onRowClick ? 0 : rowProps.tabIndex,
1200
+ onClick: (e) => {
1201
+ rowProps.onClick?.(e);
1202
+ const target = e.target;
1203
+ if (isInteractiveTarget(target)) return;
1204
+ if (expandByClick) toggleExpanded(rowKey);
1205
+ onRowClick?.(original);
1206
+ },
1207
+ onKeyDown: onRowClick ? (e) => {
1208
+ rowProps.onKeyDown?.(e);
1209
+ if (e.key !== "Enter" && e.key !== " ") return;
1210
+ if (e.target !== e.currentTarget) return;
1211
+ e.preventDefault();
1212
+ onRowClick?.(original);
1213
+ } : rowProps.onKeyDown,
1214
+ className: cn(
1215
+ rowPadding,
1216
+ // Hover highlight when rows are clickable OR explicitly hoverable… The
1217
+ // --accent step TableRow itself uses, not --muted/50: that sat BELOW the
1218
+ // zebra stripe (--muted/0.8, gh#700), so hovering a striped row lightened it.
1219
+ // Same token-driven formula as TableRow's own hover fill (gh#894) — a bare
1220
+ // `/70` here would silently outrank a theme's retuned default at equal
1221
+ // specificity, and `color-mix` (not `hsl(x / var(y, 70%))`) because a `/`
1222
+ // inside a Tailwind arbitrary property is read as ITS OWN opacity suffix.
1223
+ (onRowClick || hoverable) && "hover:[background-color:color-mix(in_oklab,hsl(var(--accent))_var(--table-row-hover-background-alpha,70%),transparent)]",
1224
+ // …but the affordance (cursor + focus mark) only when clickable.
1225
+ //
1226
+ // `ui-focus-ring` = the single focus source (styles/focus-ring.css). It
1227
+ // replaces `focus-visible:ring-ring focus-visible:ring-2
1228
+ // focus-visible:ring-inset`, which was a hand-rolled second ring: it read no
1229
+ // `--focus-ring-*` knob, so a service could not retune it, and — being a
1230
+ // utility — it painted regardless of the `--focus-outline` switch, which is
1231
+ // exactly the hole the switch exists to close.
1232
+ onRowClick && "ui-focus-ring cursor-pointer",
1233
+ isSelected && "bg-muted/30",
1234
+ rowProps.className,
1235
+ rowClassName?.(original)
1231
1236
  ),
1232
- selectable && /* @__PURE__ */ jsx(
1233
- TableCell,
1234
- {
1235
- className: cn(
1236
- cellPadding,
1237
- leadingFixed === "start" && "ui-data-table-pin-start"
1238
- ),
1239
- ...fixedCellProps("__select", leadingFixed),
1240
- style: leadingFixedStyle("__select"),
1241
- children: rowSelection?.type === "radio" ? (
1242
- // One choice per table, so each cell is its own single-item group:
1243
- // Radix requires a Root for an Item, and a Root cannot legally span
1244
- // <tr> boundaries without rewriting the table's own semantics.
1245
- /* @__PURE__ */ jsx(
1246
- RadioGroupRoot,
1237
+ children: [
1238
+ expandColumnShown && /* @__PURE__ */ jsx(
1239
+ TableCell,
1240
+ {
1241
+ className: cn(
1242
+ cellPadding,
1243
+ leadingFixed === "start" && "ui-data-table-pin-start"
1244
+ ),
1245
+ ...fixedCellProps("__expand", leadingFixed),
1246
+ style: leadingFixedStyle("__expand"),
1247
+ children: canExpand ? /* @__PURE__ */ jsx(
1248
+ Button,
1247
1249
  {
1248
- value: isSelected ? rowKey : "",
1249
- disabled: checkboxProps.disabled,
1250
- onValueChange: () => {
1251
- row.toggleSelected(true);
1250
+ variant: "ghost",
1251
+ size: "icon-xs",
1252
+ "aria-expanded": isExpanded,
1253
+ "aria-label": isExpanded ? t("dataTable.collapseRow") : t("dataTable.expandRow"),
1254
+ className: "ui-data-table-expand-trigger",
1255
+ onClick: (e) => {
1256
+ e.stopPropagation();
1257
+ toggleExpanded(rowKey);
1252
1258
  },
1253
- children: /* @__PURE__ */ jsx(
1254
- RadioItem,
1259
+ children: isExpanded ? /* @__PURE__ */ jsx(
1260
+ ChevronDown,
1255
1261
  {
1256
- value: rowKey,
1257
- "aria-label": selectRowLabel,
1258
- onClick: (e) => {
1259
- e.stopPropagation();
1260
- }
1262
+ className: "ui-data-table-sort-icon",
1263
+ "aria-hidden": "true"
1264
+ }
1265
+ ) : /* @__PURE__ */ jsx(
1266
+ ChevronRight,
1267
+ {
1268
+ className: "ui-data-table-sort-icon",
1269
+ "aria-hidden": "true"
1261
1270
  }
1262
1271
  )
1263
1272
  }
1264
- )
1265
- ) : /* @__PURE__ */ jsx(
1266
- Checkbox,
1267
- {
1268
- checked: isSelected,
1269
- disabled: checkboxProps.disabled,
1270
- onCheckedChange: (v) => {
1271
- row.toggleSelected(!!v);
1272
- },
1273
- "aria-label": selectRowLabel,
1274
- onClick: (e) => {
1275
- e.stopPropagation();
1276
- }
1277
- }
1278
- )
1279
- }
1280
- ),
1281
- visibleColumns.map((col) => {
1282
- const rendered = col.render ? col.render(original) : (() => {
1283
- const v = original[col.key];
1284
- const text = v == null || !(typeof v === "string" || typeof v === "number") ? "\u2014" : String(v);
1285
- return /* @__PURE__ */ jsx("span", { "data-slot": "table-cell-text", children: text });
1286
- })();
1287
- const rawValue = original[col.key];
1288
- const title = col.ellipsis && !col.render && (typeof rawValue === "string" || typeof rawValue === "number") ? String(rawValue) : void 0;
1289
- return /* @__PURE__ */ jsx(
1273
+ ) : null
1274
+ }
1275
+ ),
1276
+ selectable && /* @__PURE__ */ jsx(
1290
1277
  TableCell,
1291
1278
  {
1292
- priority: col.priority,
1293
- "data-align": col.align,
1294
- title,
1295
- label: stacked ? stackedCellLabel(col) : void 0,
1296
- ...fixedCellProps(col.key, fixedEdge(col)),
1297
- ...columnHideBelowProps(col),
1298
- style: columnCellStyle(col),
1299
- flush: col.flush,
1300
- className: cn(!col.flush && cellPadding, columnCellClass(col)),
1301
- children: rendered
1302
- },
1303
- col.key
1304
- );
1305
- })
1306
- ]
1307
- }
1308
- ),
1309
- isExpanded && expandable?.expandedRowRender ? /* @__PURE__ */ jsx(
1310
- TableRow,
1311
- {
1312
- className: "ui-data-table-expanded-row hover:bg-transparent",
1313
- "data-expanded-row": "",
1314
- children: /* @__PURE__ */ jsx(TableCell, { colSpan: emptyColSpan, flush: true, children: expandable.expandedRowRender(original, rowIndex, true) })
1315
- }
1316
- ) : null
1317
- ] }, row.id);
1318
- }) }),
1319
- summary ? (
1320
- // A real <tfoot>: the totals row belongs to the table's own grid, so it keeps the
1321
- // column widths, the header association and the screen-reader row navigation.
1322
- /* @__PURE__ */ jsx("tfoot", { "data-slot": "table-footer", className: "ui-data-table-summary", children: summary(dataRows.map((row) => row.original)) })
1323
- ) : null
1324
- ]
1325
- }
1326
- )
1279
+ className: cn(
1280
+ cellPadding,
1281
+ leadingFixed === "start" && "ui-data-table-pin-start"
1282
+ ),
1283
+ ...fixedCellProps("__select", leadingFixed),
1284
+ style: leadingFixedStyle("__select"),
1285
+ children: rowSelection?.type === "radio" ? (
1286
+ // One choice per table, so each cell is its own single-item group:
1287
+ // Radix requires a Root for an Item, and a Root cannot legally span
1288
+ // <tr> boundaries without rewriting the table's own semantics.
1289
+ /* @__PURE__ */ jsx(
1290
+ RadioGroupRoot,
1291
+ {
1292
+ value: isSelected ? rowKey : "",
1293
+ disabled: checkboxProps.disabled,
1294
+ onValueChange: () => {
1295
+ row.toggleSelected(true);
1296
+ },
1297
+ children: /* @__PURE__ */ jsx(
1298
+ RadioItem,
1299
+ {
1300
+ value: rowKey,
1301
+ "aria-label": selectRowLabel,
1302
+ onClick: (e) => {
1303
+ e.stopPropagation();
1304
+ }
1305
+ }
1306
+ )
1307
+ }
1308
+ )
1309
+ ) : /* @__PURE__ */ jsx(
1310
+ Checkbox,
1311
+ {
1312
+ checked: isSelected,
1313
+ disabled: checkboxProps.disabled,
1314
+ onCheckedChange: (v) => {
1315
+ row.toggleSelected(!!v);
1316
+ },
1317
+ "aria-label": selectRowLabel,
1318
+ onClick: (e) => {
1319
+ e.stopPropagation();
1320
+ }
1321
+ }
1322
+ )
1323
+ }
1324
+ ),
1325
+ visibleColumns.map((col) => {
1326
+ const rendered = col.render ? col.render(original) : (() => {
1327
+ const v = original[col.key];
1328
+ const text = v == null || !(typeof v === "string" || typeof v === "number") ? "\u2014" : String(v);
1329
+ return /* @__PURE__ */ jsx("span", { "data-slot": "table-cell-text", children: text });
1330
+ })();
1331
+ const rawValue = original[col.key];
1332
+ const title = col.ellipsis && !col.render && (typeof rawValue === "string" || typeof rawValue === "number") ? String(rawValue) : void 0;
1333
+ return /* @__PURE__ */ jsx(
1334
+ TableCell,
1335
+ {
1336
+ priority: col.priority,
1337
+ "data-align": col.align,
1338
+ title,
1339
+ label: stacked ? stackedCellLabel(col) : void 0,
1340
+ ...fixedCellProps(col.key, fixedEdge(col)),
1341
+ ...columnHideBelowProps(col),
1342
+ style: columnCellStyle(col),
1343
+ flush: col.flush,
1344
+ className: cn(!col.flush && cellPadding, columnCellClass(col)),
1345
+ children: rendered
1346
+ },
1347
+ col.key
1348
+ );
1349
+ })
1350
+ ]
1351
+ }
1352
+ ),
1353
+ isExpanded && expandable?.expandedRowRender ? /* @__PURE__ */ jsx(
1354
+ TableRow,
1355
+ {
1356
+ className: "ui-data-table-expanded-row hover:bg-transparent",
1357
+ "data-expanded-row": "",
1358
+ children: /* @__PURE__ */ jsx(TableCell, { colSpan: emptyColSpan, flush: true, children: expandable.expandedRowRender(original, rowIndex, true) })
1359
+ }
1360
+ ) : null
1361
+ ] }, row.id);
1362
+ }) }),
1363
+ summary ? (
1364
+ // A real <tfoot>: the totals row belongs to the table's own grid, so it keeps the
1365
+ // column widths, the header association and the screen-reader row navigation.
1366
+ /* @__PURE__ */ jsx("tfoot", { "data-slot": "table-footer", className: "ui-data-table-summary", children: summary(dataRows.map((row) => row.original)) })
1367
+ ) : null
1368
+ ]
1369
+ }
1370
+ )
1371
+ ]
1327
1372
  }
1328
1373
  )
1329
1374
  }