@ai-matrx/design-system 0.21.9 → 0.21.11

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 (55) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/data-table/copy-helpers.cjs.map +1 -1
  3. package/dist/data-table/copy-helpers.d.cts +1 -1
  4. package/dist/data-table/copy-helpers.d.ts +1 -1
  5. package/dist/data-table/copy-helpers.js.map +1 -1
  6. package/dist/data-table/data-row-inspector.cjs.map +1 -1
  7. package/dist/data-table/data-row-inspector.js.map +1 -1
  8. package/dist/data-table/data-row-window.cjs.map +1 -1
  9. package/dist/data-table/data-row-window.js.map +1 -1
  10. package/dist/data-table/filter-engine.cjs +1 -1
  11. package/dist/data-table/filter-engine.cjs.map +1 -1
  12. package/dist/data-table/filter-engine.d.cts +1 -1
  13. package/dist/data-table/filter-engine.d.ts +1 -1
  14. package/dist/data-table/filter-engine.js +1 -1
  15. package/dist/data-table/filter-engine.js.map +1 -1
  16. package/dist/data-table/host.d.cts +1 -1
  17. package/dist/data-table/host.d.ts +1 -1
  18. package/dist/data-table/index.cjs +190 -55
  19. package/dist/data-table/index.cjs.map +1 -1
  20. package/dist/data-table/index.d.cts +2 -2
  21. package/dist/data-table/index.d.ts +2 -2
  22. package/dist/data-table/index.js +190 -55
  23. package/dist/data-table/index.js.map +1 -1
  24. package/dist/data-table/infer-filter.cjs +16 -6
  25. package/dist/data-table/infer-filter.cjs.map +1 -1
  26. package/dist/data-table/infer-filter.d.cts +1 -1
  27. package/dist/data-table/infer-filter.d.ts +1 -1
  28. package/dist/data-table/infer-filter.js +16 -6
  29. package/dist/data-table/infer-filter.js.map +1 -1
  30. package/dist/data-table/layered-filters.d.cts +1 -1
  31. package/dist/data-table/layered-filters.d.ts +1 -1
  32. package/dist/data-table/pagination.cjs +28 -14
  33. package/dist/data-table/pagination.cjs.map +1 -1
  34. package/dist/data-table/pagination.d.cts +7 -1
  35. package/dist/data-table/pagination.d.ts +7 -1
  36. package/dist/data-table/pagination.js +28 -14
  37. package/dist/data-table/pagination.js.map +1 -1
  38. package/dist/data-table/query-control.cjs +16 -6
  39. package/dist/data-table/query-control.cjs.map +1 -1
  40. package/dist/data-table/query-control.d.cts +1 -1
  41. package/dist/data-table/query-control.d.ts +1 -1
  42. package/dist/data-table/query-control.js +16 -6
  43. package/dist/data-table/query-control.js.map +1 -1
  44. package/dist/data-table/types.cjs.map +1 -1
  45. package/dist/data-table/types.d.cts +1 -1
  46. package/dist/data-table/types.d.ts +1 -1
  47. package/dist/data-table/url-state.d.cts +1 -1
  48. package/dist/data-table/url-state.d.ts +1 -1
  49. package/dist/data-table/xlsx.cjs.map +1 -1
  50. package/dist/data-table/xlsx.d.cts +1 -1
  51. package/dist/data-table/xlsx.d.ts +1 -1
  52. package/dist/data-table/xlsx.js.map +1 -1
  53. package/dist/{types-CqA8mpjE.d.cts → types-9Ii-b9EJ.d.cts} +37 -1
  54. package/dist/{types-CuTdr8mW.d.ts → types-wy1EEaeG.d.ts} +37 -1
  55. package/package.json +1 -1
@@ -1991,6 +1991,7 @@ function PageNavigationControl({
1991
1991
  }
1992
1992
  function PaginationAction({
1993
1993
  HostAction,
1994
+ compact = false,
1994
1995
  active = false,
1995
1996
  disabledHint,
1996
1997
  visibilityClassName = "inline-flex",
@@ -2019,6 +2020,7 @@ function PaginationAction({
2019
2020
  onClick: props.onClick,
2020
2021
  className: cn(
2021
2022
  PAGINATION_ACTION_CLASS,
2023
+ compact && "h-8 w-8 min-h-8 min-w-8",
2022
2024
  active && PAGINATION_ACTIVE_CLASS,
2023
2025
  fallbackClassName
2024
2026
  ),
@@ -2032,6 +2034,8 @@ var PAGINATION_ACTION_CLASS = "h-11 w-11 min-h-11 min-w-11 rounded-md border bor
2032
2034
  var PAGINATION_ACTIVE_CLASS = "border-primary bg-primary text-primary-foreground hover:bg-primary/90";
2033
2035
  function GenericTablePagination({
2034
2036
  totalItems,
2037
+ unknownTotal,
2038
+ isFetching = false,
2035
2039
  itemsPerPage,
2036
2040
  currentPage,
2037
2041
  onPageChange,
@@ -2072,11 +2076,12 @@ function GenericTablePagination({
2072
2076
  (_, i) => startPage + i
2073
2077
  );
2074
2078
  };
2075
- const isFirstPageDisabled = currentPage <= 1;
2076
- const isLastPageDisabled = currentPage >= totalPages;
2079
+ const isFirstPageDisabled = isFetching || currentPage <= 1;
2080
+ const isLastPageDisabled = isFetching || (unknownTotal ? !unknownTotal.hasNextPage : currentPage >= totalPages);
2077
2081
  const startItem = totalItems === 0 ? 0 : progressive ? 1 : (currentPage - 1) * effectiveItemsPerPage + 1;
2078
2082
  const endItem = Math.min(progressive?.shownCount ?? currentPage * effectiveItemsPerPage, totalItems);
2079
2083
  const defaultLabelFormat = (start, end, total) => {
2084
+ if (unknownTotal) return `${unknownTotal.pageRowCount.toLocaleString()} on page ${currentPage.toLocaleString()}`;
2080
2085
  if (progressive) return progressive.loadedCount !== void 0 && progressive.loadedCount !== total ? `${end} shown / ${total} matching \xB7 ${progressive.loadedCount} loaded` : `${end} shown / ${total} loaded`;
2081
2086
  return compact ? `${start}-${end} of ${total}` : `Showing ${start} to ${end} of ${total} entries`;
2082
2087
  };
@@ -2101,6 +2106,7 @@ function GenericTablePagination({
2101
2106
  children: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
2102
2107
  Select,
2103
2108
  {
2109
+ disabled: isFetching,
2104
2110
  value: itemsPerPage.toString(),
2105
2111
  onValueChange: (value) => onItemsPerPageChange(parseInt(value)),
2106
2112
  children: [
@@ -2117,7 +2123,7 @@ function GenericTablePagination({
2117
2123
  ),
2118
2124
  /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(SelectContent, { children: [
2119
2125
  availablePageSizes.map((size) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(SelectItem, { value: size.toString(), children: size }, size)),
2120
- showAllOption ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(SelectItem, { value: allValue.toString(), children: allOptionLabel }) : null
2126
+ showAllOption && !unknownTotal ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(SelectItem, { value: allValue.toString(), children: allOptionLabel }) : null
2121
2127
  ] })
2122
2128
  ]
2123
2129
  }
@@ -2129,7 +2135,7 @@ function GenericTablePagination({
2129
2135
  {
2130
2136
  className: cn("flex min-w-0 justify-center", infoContainerClassName),
2131
2137
  children: showPageInfo && !hideEntriesInfo && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
2132
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "truncate text-xs tabular-nums text-muted-foreground sm:hidden", children: progressive ? `${endItem}/${totalItems} ${progressive.loadedCount !== void 0 && progressive.loadedCount !== totalItems ? "matching" : "loaded"}` : `${startItem}-${endItem} / ${totalItems}` }),
2138
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "truncate text-xs tabular-nums text-muted-foreground sm:hidden", children: labelFormat ? formatLabel(startItem, endItem, totalItems) : unknownTotal ? defaultLabelFormat(startItem, endItem, totalItems) : progressive ? `${endItem}/${totalItems} ${progressive.loadedCount !== void 0 && progressive.loadedCount !== totalItems ? "matching" : "loaded"}` : `${startItem}-${endItem} / ${totalItems}` }),
2133
2139
  /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
2134
2140
  "span",
2135
2141
  {
@@ -2154,7 +2160,7 @@ function GenericTablePagination({
2154
2160
  }
2155
2161
  ) }),
2156
2162
  /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(TooltipContent, { children: "Show more already-loaded rows" })
2157
- ] }) : null : showPageControls && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
2163
+ ] }) : null : showPageControls && (unknownTotal ? currentPage > 1 || unknownTotal.hasNextPage : totalPages > 1) && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
2158
2164
  "div",
2159
2165
  {
2160
2166
  className: cn(
@@ -2167,8 +2173,9 @@ function GenericTablePagination({
2167
2173
  PaginationAction,
2168
2174
  {
2169
2175
  HostAction: host.ToolbarAction,
2176
+ compact,
2170
2177
  ariaLabel: "First page",
2171
- disabledHint: "Already on the first page",
2178
+ disabledHint: isFetching ? "Loading source page" : "Already on the first page",
2172
2179
  visibilityClassName: "hidden sm:inline-flex",
2173
2180
  fallbackClassName: navButtonClassName,
2174
2181
  onClick: () => onPageChange(1),
@@ -2180,41 +2187,46 @@ function GenericTablePagination({
2180
2187
  PaginationAction,
2181
2188
  {
2182
2189
  HostAction: host.ToolbarAction,
2190
+ compact,
2183
2191
  ariaLabel: "Previous page",
2184
- disabledHint: "Already on the first page",
2192
+ disabledHint: isFetching ? "Loading source page" : "Already on the first page",
2185
2193
  fallbackClassName: navButtonClassName,
2186
2194
  onClick: () => onPageChange(currentPage - 1),
2187
2195
  disabled: isFirstPageDisabled,
2188
2196
  children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ChevronLeft, { className: "h-5 w-5" })
2189
2197
  }
2190
2198
  ),
2191
- getPageNumbers(3).map((page) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
2199
+ !unknownTotal && getPageNumbers(3).map((page) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
2192
2200
  PaginationAction,
2193
2201
  {
2194
2202
  HostAction: host.ToolbarAction,
2203
+ compact,
2195
2204
  ariaLabel: `Page ${page}`,
2196
2205
  active: currentPage === page,
2197
- disabledHint: `Already on page ${page}`,
2206
+ disabledHint: isFetching ? "Loading source page" : `Already on page ${page}`,
2198
2207
  visibilityClassName: cn(
2199
2208
  "sm:hidden",
2200
2209
  page === currentPage ? "inline-flex" : "hidden min-[360px]:inline-flex"
2201
2210
  ),
2202
2211
  fallbackClassName: page === currentPage ? pageActiveButtonClassName : pageButtonClassName,
2203
2212
  onClick: () => onPageChange(page),
2213
+ disabled: isFetching,
2204
2214
  children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { children: page })
2205
2215
  },
2206
2216
  `mobile-${page}`
2207
2217
  )),
2208
- getPageNumbers().map((page) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
2218
+ !unknownTotal && getPageNumbers().map((page) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
2209
2219
  PaginationAction,
2210
2220
  {
2211
2221
  HostAction: host.ToolbarAction,
2222
+ compact,
2212
2223
  ariaLabel: `Page ${page}`,
2213
2224
  active: currentPage === page,
2214
- disabledHint: `Already on page ${page}`,
2225
+ disabledHint: isFetching ? "Loading source page" : `Already on page ${page}`,
2215
2226
  visibilityClassName: "hidden sm:inline-flex",
2216
2227
  fallbackClassName: currentPage === page ? pageActiveButtonClassName : pageButtonClassName,
2217
2228
  onClick: () => onPageChange(page),
2229
+ disabled: isFetching,
2218
2230
  children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { children: page })
2219
2231
  },
2220
2232
  `desktop-${page}`
@@ -2223,20 +2235,22 @@ function GenericTablePagination({
2223
2235
  PaginationAction,
2224
2236
  {
2225
2237
  HostAction: host.ToolbarAction,
2238
+ compact,
2226
2239
  ariaLabel: "Next page",
2227
- disabledHint: "Already on the last page",
2240
+ disabledHint: isFetching ? "Loading source page" : "Already on the last page",
2228
2241
  fallbackClassName: navButtonClassName,
2229
2242
  onClick: () => onPageChange(currentPage + 1),
2230
2243
  disabled: isLastPageDisabled,
2231
2244
  children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ChevronRight, { className: "h-5 w-5" })
2232
2245
  }
2233
2246
  ),
2234
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
2247
+ !unknownTotal && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
2235
2248
  PaginationAction,
2236
2249
  {
2237
2250
  HostAction: host.ToolbarAction,
2251
+ compact,
2238
2252
  ariaLabel: "Last page",
2239
- disabledHint: "Already on the last page",
2253
+ disabledHint: isFetching ? "Loading source page" : "Already on the last page",
2240
2254
  visibilityClassName: "hidden sm:inline-flex",
2241
2255
  fallbackClassName: navButtonClassName,
2242
2256
  onClick: () => onPageChange(totalPages),
@@ -5117,7 +5131,7 @@ function passesColumnFilter(value, filter) {
5117
5131
  if (selected.length === 0) return true;
5118
5132
  const values = selectCellValues(value);
5119
5133
  return selected.some(
5120
- (v) => v === SELECT_NULL_VALUE ? value == null : v === SELECT_EMPTY_VALUE ? values.length === 0 : v === SELECT_NOT_EMPTY_VALUE ? values.length > 0 : values.includes(v)
5134
+ (v) => v === SELECT_NULL_VALUE ? value == null : v === SELECT_EMPTY_VALUE ? values.length === 0 : v === SELECT_NOT_EMPTY_VALUE ? values.length > 0 : v === "" ? value === "" || Array.isArray(value) && value.includes("") : values.includes(v)
5121
5135
  );
5122
5136
  }
5123
5137
  case "boolean": {
@@ -5885,6 +5899,7 @@ function ColumnHeaderCell({
5885
5899
  onFilterChange,
5886
5900
  selectOptions = [],
5887
5901
  selectSingle = false,
5902
+ selectAllowCustom = false,
5888
5903
  align = "left",
5889
5904
  className,
5890
5905
  compact = false,
@@ -5920,9 +5935,13 @@ function ColumnHeaderCell({
5920
5935
  const filterActive = isColumnFilterActive(filterValue);
5921
5936
  const deferredFilter = filterApplyLabel !== void 0 || filterShowCancel || filterFooterHint !== void 0;
5922
5937
  const [filterOpen, setFilterOpen] = (0, import_react13.useState)(false);
5938
+ const [pendingNegation, setPendingNegation] = (0, import_react13.useState)(false);
5923
5939
  const [draftFilter, setDraftFilter] = (0, import_react13.useState)(filterValue);
5924
5940
  (0, import_react13.useEffect)(() => {
5925
- if (!filterOpen) setDraftFilter(filterValue);
5941
+ if (!filterOpen) {
5942
+ setDraftFilter(filterValue);
5943
+ setPendingNegation(filterValue?.negated ?? false);
5944
+ }
5926
5945
  }, [filterOpen, filterValue]);
5927
5946
  const applyFilter = () => {
5928
5947
  onFilterChange(draftFilter);
@@ -5934,6 +5953,7 @@ function ColumnHeaderCell({
5934
5953
  };
5935
5954
  const visibleFilter = deferredFilter ? draftFilter : filterValue;
5936
5955
  const setNegated = (negated) => {
5956
+ setPendingNegation(negated);
5937
5957
  if (!visibleFilter) return;
5938
5958
  const next = { ...visibleFilter, ...negated ? { negated: true } : {} };
5939
5959
  if (!negated) delete next.negated;
@@ -6010,12 +6030,13 @@ function ColumnHeaderCell({
6010
6030
  kind: filterKind,
6011
6031
  value: visibleFilter,
6012
6032
  onChange: (next) => {
6013
- const preservingNegation = next && visibleFilter?.negated ? { ...next, negated: true } : next;
6033
+ const preservingNegation = next && (visibleFilter?.negated ?? pendingNegation) ? { ...next, negated: true } : next;
6014
6034
  if (deferredFilter) setDraftFilter(preservingNegation);
6015
6035
  else onFilterChange(preservingNegation);
6016
6036
  },
6017
6037
  selectOptions,
6018
6038
  selectSingle,
6039
+ selectAllowCustom,
6019
6040
  ...markerLabels ? {
6020
6041
  booleanLabels: {
6021
6042
  all: "All",
@@ -6026,18 +6047,18 @@ function ColumnHeaderCell({
6026
6047
  allowNullModes
6027
6048
  }
6028
6049
  ) : null,
6029
- filterKind && visibleFilter && allowNegation ? /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "mt-2 border-t border-border pt-2", children: [
6050
+ filterKind && allowNegation ? /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "mt-2 border-t border-border pt-2", children: [
6030
6051
  /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("p", { className: "mb-1 text-[11px] font-medium uppercase tracking-wider text-muted-foreground", children: "Match mode" }),
6031
6052
  /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "grid grid-cols-2 gap-1 rounded-md border p-0.5", children: [
6032
6053
  /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
6033
6054
  "button",
6034
6055
  {
6035
6056
  type: "button",
6036
- "aria-pressed": !visibleFilter.negated,
6057
+ "aria-pressed": !(visibleFilter?.negated ?? pendingNegation),
6037
6058
  onClick: () => setNegated(false),
6038
6059
  className: cn(
6039
6060
  "rounded px-2 py-1.5 text-xs transition-colors",
6040
- !visibleFilter.negated ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-muted"
6061
+ !(visibleFilter?.negated ?? pendingNegation) ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-muted"
6041
6062
  ),
6042
6063
  children: "Include"
6043
6064
  }
@@ -6046,11 +6067,11 @@ function ColumnHeaderCell({
6046
6067
  "button",
6047
6068
  {
6048
6069
  type: "button",
6049
- "aria-pressed": visibleFilter.negated === true,
6070
+ "aria-pressed": visibleFilter?.negated ?? pendingNegation,
6050
6071
  onClick: () => setNegated(true),
6051
6072
  className: cn(
6052
6073
  "rounded px-2 py-1.5 text-xs transition-colors",
6053
- visibleFilter.negated ? "bg-destructive text-destructive-foreground" : "text-muted-foreground hover:bg-muted"
6074
+ visibleFilter?.negated ?? pendingNegation ? "bg-destructive text-destructive-foreground" : "text-muted-foreground hover:bg-muted"
6054
6075
  ),
6055
6076
  children: "Exclude"
6056
6077
  }
@@ -6238,6 +6259,7 @@ function FilterBody({
6238
6259
  onChange,
6239
6260
  selectOptions,
6240
6261
  selectSingle = false,
6262
+ selectAllowCustom = false,
6241
6263
  allowNullModes = false,
6242
6264
  booleanLabels
6243
6265
  }) {
@@ -6301,15 +6323,29 @@ function FilterBody({
6301
6323
  }
6302
6324
  if (kind === "select") {
6303
6325
  const selected = value?.kind === "select" ? value.values ?? (value.value && value.value !== "__all__" ? [value.value] : []) : [];
6304
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
6305
- SearchableSelectFilter,
6306
- {
6307
- selected,
6308
- options: selectOptions,
6309
- onChange,
6310
- single: selectSingle
6311
- }
6312
- );
6326
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "space-y-2", children: [
6327
+ allowNullModes && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "flex flex-wrap gap-1", "aria-label": "Blank checks", children: [["empty", "(empty)"], ["not_empty", "(not empty)"], ["null", "(null)"], ["not_null", "(not null)"]].map(([mode, label]) => /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
6328
+ "button",
6329
+ {
6330
+ type: "button",
6331
+ "aria-pressed": value?.kind === "text" && value.mode === mode,
6332
+ className: "rounded border px-1.5 py-0.5 text-[10px] hover:bg-muted",
6333
+ onClick: () => onChange({ kind: "text", value: "", mode }),
6334
+ children: label
6335
+ },
6336
+ mode
6337
+ )) }),
6338
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
6339
+ SearchableSelectFilter,
6340
+ {
6341
+ selected,
6342
+ options: selectOptions,
6343
+ onChange,
6344
+ single: selectSingle,
6345
+ allowCustom: selectAllowCustom
6346
+ }
6347
+ )
6348
+ ] });
6313
6349
  }
6314
6350
  if (kind === "boolean") {
6315
6351
  const current = value?.kind === "boolean" ? value.value : "__all__";
@@ -6341,7 +6377,8 @@ function SearchableSelectFilter({
6341
6377
  selected,
6342
6378
  options,
6343
6379
  onChange,
6344
- single = false
6380
+ single = false,
6381
+ allowCustom = false
6345
6382
  }) {
6346
6383
  const [query, setQuery] = (0, import_react13.useState)("");
6347
6384
  const filtered = (0, import_react13.useMemo)(() => {
@@ -6354,7 +6391,7 @@ function SearchableSelectFilter({
6354
6391
  const toggle = (v) => {
6355
6392
  if (single) {
6356
6393
  onChange(
6357
- selected.length === 1 && selected[0] === v ? void 0 : { kind: "select", value: v }
6394
+ selected.length === 1 && selected[0] === v ? void 0 : { kind: "select", value: v, ...v === "" ? { values: [v] } : {} }
6358
6395
  );
6359
6396
  return;
6360
6397
  }
@@ -6391,6 +6428,11 @@ function SearchableSelectFilter({
6391
6428
  /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(CommandList, { className: "max-h-48", children: [
6392
6429
  /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(CommandEmpty, { className: "py-3 text-xs text-muted-foreground", children: "No matches" }),
6393
6430
  /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(CommandGroup, { children: [
6431
+ allowCustom && query.trim() && !options.some((option) => option.value === query.trim()) && /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(CommandItem, { value: `custom:${query.trim()}`, onSelect: () => toggle(query.trim()), className: "text-xs", children: [
6432
+ "Use \u201C",
6433
+ query.trim(),
6434
+ "\u201D"
6435
+ ] }),
6394
6436
  /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
6395
6437
  CommandItem,
6396
6438
  {
@@ -8457,10 +8499,13 @@ function collectSelectOptions(col, rows) {
8457
8499
  const seen = /* @__PURE__ */ new Set();
8458
8500
  const options = [];
8459
8501
  let hasEmpty = false;
8502
+ let hasEmptyCollection = false;
8460
8503
  for (const row of rows) {
8461
- const values = selectCellValues(getCellValue(row, col));
8504
+ const cell = getCellValue(row, col);
8505
+ const values = selectCellValues(cell);
8462
8506
  if (values.length === 0) {
8463
8507
  hasEmpty = true;
8508
+ if (Array.isArray(cell)) hasEmptyCollection = true;
8464
8509
  continue;
8465
8510
  }
8466
8511
  for (const value of values) {
@@ -8469,12 +8514,18 @@ function collectSelectOptions(col, rows) {
8469
8514
  options.push({ value, label: value });
8470
8515
  }
8471
8516
  }
8472
- const base = col.filterOptions?.length ? [...col.filterOptions] : options.sort((a, b) => a.label.localeCompare(b.label));
8517
+ const base = col.filterOptions?.length ? col.filterOptions.map((option) => ({
8518
+ ...option,
8519
+ label: option.label.trim() ? option.label : option.value === "" ? "(empty string)" : option.value.trim() ? option.value : "(whitespace)"
8520
+ })) : options.sort((a, b) => a.label.localeCompare(b.label));
8473
8521
  if (hasEmpty) {
8474
- base.unshift(
8475
- { value: SELECT_EMPTY_VALUE, label: "(empty)" },
8476
- { value: SELECT_NOT_EMPTY_VALUE, label: "(not empty)" }
8477
- );
8522
+ const emptyOption = !hasEmptyCollection && base.some((option) => option.value === "") ? { value: SELECT_NULL_VALUE, label: "(null)" } : { value: SELECT_EMPTY_VALUE, label: "(empty)" };
8523
+ const additions = [];
8524
+ if (!base.some((option) => option.value === emptyOption.value)) additions.push(emptyOption);
8525
+ if (!base.some((option) => option.value === SELECT_NOT_EMPTY_VALUE)) {
8526
+ additions.push({ value: SELECT_NOT_EMPTY_VALUE, label: "(not empty)" });
8527
+ }
8528
+ base.unshift(...additions);
8478
8529
  }
8479
8530
  return base;
8480
8531
  }
@@ -8685,6 +8736,24 @@ function MatrxDataTableWithUrlState({
8685
8736
  );
8686
8737
  }
8687
8738
  var COLUMN_REORDER_ACTIVATION_DISTANCE = 6;
8739
+ var ROW_INTERACTIVE_DESCENDANT_SELECTOR = [
8740
+ "a",
8741
+ "button",
8742
+ "input",
8743
+ "select",
8744
+ "textarea",
8745
+ '[role="button"]',
8746
+ '[role="combobox"]',
8747
+ '[contenteditable="true"]',
8748
+ "[data-matrx-cell-control]"
8749
+ ].join(", ");
8750
+ function rowEventTargetsInteractiveDescendant(target) {
8751
+ const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
8752
+ return element?.closest(ROW_INTERACTIVE_DESCENDANT_SELECTOR) !== null;
8753
+ }
8754
+ function opensRowHref(event) {
8755
+ return event.metaKey || event.ctrlKey || event.shiftKey || event.button === 1;
8756
+ }
8688
8757
  function TableContextPassthrough({ children }) {
8689
8758
  return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_jsx_runtime41.Fragment, { children });
8690
8759
  }
@@ -8712,6 +8781,7 @@ function MatrxDataTableCore({
8712
8781
  hidePagination = false,
8713
8782
  filterUi,
8714
8783
  detail,
8784
+ expandedDetail,
8715
8785
  window: windowConfig,
8716
8786
  copy,
8717
8787
  contextMenu,
@@ -8739,6 +8809,7 @@ function MatrxDataTableCore({
8739
8809
  tableClassName,
8740
8810
  mobileCards,
8741
8811
  mobileCardsBreakpoint = "sm",
8812
+ getRowHref,
8742
8813
  onRowOpen,
8743
8814
  density
8744
8815
  }) {
@@ -9217,7 +9288,12 @@ function MatrxDataTableCore({
9217
9288
  0,
9218
9289
  ...visibleActionStrips.map((element) => Math.ceil(element.scrollWidth))
9219
9290
  );
9220
- const actionsWidth = actionContentWidth || headerActionsWidth;
9291
+ const actionsHeader = table?.querySelector("th[data-matrx-table-actions-column]");
9292
+ const actionsLabel = actionsHeader?.querySelector("[data-matrx-table-actions-label]");
9293
+ const headerPadding = actionsHeader ? getComputedStyle(actionsHeader) : void 0;
9294
+ const measuredLabelWidth = actionsLabel?.getBoundingClientRect().width ?? 0;
9295
+ const labelWidth = measuredLabelWidth > 0 ? Math.ceil(measuredLabelWidth + (Number.parseFloat(headerPadding?.paddingLeft ?? "0") || 0) + (Number.parseFloat(headerPadding?.paddingRight ?? "0") || 0)) : 0;
9296
+ const actionsWidth = Math.max(actionContentWidth, labelWidth) || headerActionsWidth;
9221
9297
  return {
9222
9298
  ...headerTotal > 0 ? { tableWidth: Math.ceil(headerTotal) } : {},
9223
9299
  ...selectionWidth === void 0 ? {} : { selectionWidth },
@@ -9573,7 +9649,20 @@ function MatrxDataTableCore({
9573
9649
  setWindowRowId(null);
9574
9650
  setWindowRowSnapshot(null);
9575
9651
  };
9652
+ const canExpandRow = (row) => Boolean(expandedDetail && (expandedDetail.canExpand?.(row) ?? true));
9653
+ const isRowExpanded = (row) => canExpandRow(row) && expandedDetail?.expandedId === getRowId(row);
9654
+ const toggleExpanded = (row) => {
9655
+ if (!expandedDetail || !canExpandRow(row)) return;
9656
+ const id = getRowId(row);
9657
+ expandedDetail.onExpandedIdChange(
9658
+ expandedDetail.expandedId === id ? null : id
9659
+ );
9660
+ };
9576
9661
  const openRow = (row) => {
9662
+ if (canExpandRow(row)) {
9663
+ toggleExpanded(row);
9664
+ return;
9665
+ }
9577
9666
  if (opensWindowOnRowClick) {
9578
9667
  openWindow(row);
9579
9668
  return;
@@ -9677,7 +9766,11 @@ function MatrxDataTableCore({
9677
9766
  } : {},
9678
9767
  ...editEnabled && visibleColumns.some(
9679
9768
  (column) => column.editable && (column.editableIf?.(row) ?? true)
9680
- ) ? { beginEdit: () => beginRowEdit(row) } : {}
9769
+ ) ? { beginEdit: () => beginRowEdit(row) } : {},
9770
+ ...canExpandRow(row) ? {
9771
+ isExpanded: isRowExpanded(row),
9772
+ toggleExpanded: () => toggleExpanded(row)
9773
+ } : {}
9681
9774
  };
9682
9775
  };
9683
9776
  const hierarchyRows = hierarchy?.rows ?? data;
@@ -10583,6 +10676,7 @@ function MatrxDataTableCore({
10583
10676
  ] }) }) : paginated.map((row, index) => {
10584
10677
  const id = getRowId(row);
10585
10678
  const displayRow = applyRowEdits(row, edits[id]);
10679
+ const controls = buildRecordControls(displayRow);
10586
10680
  const selectable = selection?.isRowSelectable?.(row) ?? Boolean(selection);
10587
10681
  return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { "data-row-id": id, className: "contents", children: mobileCards(displayRow, index, {
10588
10682
  renderCell: (columnKey) => {
@@ -10617,10 +10711,15 @@ function MatrxDataTableCore({
10617
10711
  ...copy === void 0 ? { defaultColumns: declaredVisibleCopyColumns } : {}
10618
10712
  }
10619
10713
  ) : null,
10620
- renderRowActions(displayRow, buildRecordControls(displayRow))
10714
+ renderRowActions(displayRow, controls)
10621
10715
  ]
10622
10716
  }
10623
- ) : null
10717
+ ) : null,
10718
+ ...canExpandRow(row) ? {
10719
+ isExpanded: isRowExpanded(row),
10720
+ toggleExpanded: () => toggleExpanded(row)
10721
+ } : {},
10722
+ expandedDetail: expandedDetail && isRowExpanded(row) ? expandedDetail.render(displayRow, controls) : null
10624
10723
  }) }, id);
10625
10724
  }),
10626
10725
  appendQuery !== null && isFetching && !isLoading ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
@@ -10810,6 +10909,7 @@ function MatrxDataTableCore({
10810
10909
  onFilterChange: (next) => setColumnFilter(id, next),
10811
10910
  selectOptions: meta?.options,
10812
10911
  selectSingle: col.filterSingle,
10912
+ selectAllowCustom: col.filterAllowCustom,
10813
10913
  filterApplyLabel: filterUi?.applyLabel,
10814
10914
  filterShowCancel: filterUi?.showCancel,
10815
10915
  filterFooterHint: filterUi?.footerHint,
@@ -10846,9 +10946,9 @@ function MatrxDataTableCore({
10846
10946
  "th",
10847
10947
  {
10848
10948
  "data-matrx-table-actions-column": true,
10849
- className: "h-9 w-px px-2 text-right align-middle text-xs font-semibold uppercase tracking-wide text-muted-foreground",
10949
+ className: "h-9 w-px whitespace-nowrap px-2 text-right align-middle text-xs font-semibold uppercase tracking-wide text-muted-foreground",
10850
10950
  style: fixedUtilityWidth(fixedTableGeometry?.actionsWidth),
10851
- children: "Actions"
10951
+ children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { "data-matrx-table-actions-label": true, className: "inline-block whitespace-nowrap", children: "Actions" })
10852
10952
  }
10853
10953
  )
10854
10954
  ] })
@@ -10887,6 +10987,7 @@ function MatrxDataTableCore({
10887
10987
  const isSelected = selectedId === id;
10888
10988
  const rowEdits = edits[id];
10889
10989
  const displayRow = applyRowEdits(row, rowEdits);
10990
+ const controls = buildRecordControls(displayRow);
10890
10991
  const isChecked = selectedIdSet.has(id);
10891
10992
  const rowNode = /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
10892
10993
  "tr",
@@ -10894,15 +10995,28 @@ function MatrxDataTableCore({
10894
10995
  "data-row-id": id,
10895
10996
  "data-state": isSelected ? "selected" : void 0,
10896
10997
  onClick: (e) => {
10897
- if (e.target.closest('a, button, input, select, textarea, [role="button"], [role="combobox"], [contenteditable="true"]')) return;
10998
+ if (rowEventTargetsInteractiveDescendant(e.target)) return;
10999
+ if (opensRowHref(e)) {
11000
+ const href = getRowHref?.(row);
11001
+ if (href) window.open(href, "_blank", "noopener");
11002
+ return;
11003
+ }
10898
11004
  openRow(row);
10899
11005
  },
11006
+ onAuxClick: (e) => {
11007
+ if (e.button !== 1 || rowEventTargetsInteractiveDescendant(e.target)) return;
11008
+ const href = getRowHref?.(row);
11009
+ if (href) {
11010
+ e.preventDefault();
11011
+ window.open(href, "_blank", "noopener");
11012
+ }
11013
+ },
10900
11014
  className: cn(
10901
11015
  // Keep the mobile row opaque while the complete row scrolls
10902
11016
  // horizontally. The translucent zebra/hover tints remain
10903
11017
  // desktop-only so densely layered mobile content stays crisp.
10904
11018
  "border-b border-border/60 bg-card transition-colors",
10905
- (detailEnabled || Boolean(onRowOpen)) && "cursor-pointer sm:hover:bg-muted/50",
11019
+ (detailEnabled || Boolean(onRowOpen) || canExpandRow(row)) && "cursor-pointer sm:hover:bg-muted/50",
10906
11020
  isSelected && "bg-muted",
10907
11021
  isChecked && "bg-primary/5",
10908
11022
  highlightedIndex === index && "bg-muted ring-1 ring-inset ring-primary/60",
@@ -11018,7 +11132,7 @@ function MatrxDataTableCore({
11018
11132
  ...copy === void 0 ? { defaultColumns: declaredVisibleCopyColumns } : {}
11019
11133
  }
11020
11134
  ) : null,
11021
- renderRowActions(displayRow, buildRecordControls(displayRow)),
11135
+ renderRowActions(displayRow, controls),
11022
11136
  windowEnabled ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(Tooltip, { children: [
11023
11137
  /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
11024
11138
  Button,
@@ -11049,15 +11163,36 @@ function MatrxDataTableCore({
11049
11163
  id
11050
11164
  );
11051
11165
  const wrappedRow = rowWrapper ? rowWrapper(row, rowNode) : rowNode;
11052
- return hierarchy ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
11053
- HierarchyDroppableRow,
11166
+ const inlineDetail = expandedDetail && isRowExpanded(row) ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
11167
+ "tr",
11054
11168
  {
11055
- id,
11056
- disabled: !canDropOn(row),
11057
- children: wrappedRow
11169
+ "data-matrx-table-expanded-detail": id,
11170
+ className: cn("bg-muted/20", expandedDetail.className),
11171
+ children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
11172
+ "td",
11173
+ {
11174
+ colSpan: visibleColumns.length + leadingCols + (showActionsCol ? 1 : 0),
11175
+ className: "p-4",
11176
+ children: expandedDetail.render(displayRow, controls)
11177
+ }
11178
+ )
11058
11179
  },
11059
- id
11060
- ) : /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_react20.Fragment, { children: wrappedRow }, id);
11180
+ `${id}:expanded-detail`
11181
+ ) : null;
11182
+ return hierarchy ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(import_react20.Fragment, { children: [
11183
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
11184
+ HierarchyDroppableRow,
11185
+ {
11186
+ id,
11187
+ disabled: !canDropOn(row),
11188
+ children: wrappedRow
11189
+ }
11190
+ ),
11191
+ inlineDetail
11192
+ ] }, id) : /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(import_react20.Fragment, { children: [
11193
+ wrappedRow,
11194
+ inlineDetail
11195
+ ] }, id);
11061
11196
  }),
11062
11197
  appendQuery !== null && isFetching && !isLoading ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("tr", { "data-matrx-table-append-loading-row": true, children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
11063
11198
  "td",