@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
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import { ReactNode } from 'react';
3
- import { h as MatrxDataTableProps, i as TableViewSnapshot, c as MatrxDataTableRecordControls, j as ColumnMarker, M as MatrxColumnDef } from '../types-CqA8mpjE.cjs';
4
- export { A as AnyOfColumnSearch, k as CellEditType, l as CellEditsMap, f as ColumnFilterKind, a as ColumnFilterValue, C as ColumnFiltersState, m as MatrxDataTableContextMenuConfig, g as MatrxDataTableCopyConfig, d as MatrxDataTableDensity, n as MatrxDataTableDetailConfig, o as MatrxDataTableEditConfig, p as MatrxDataTableEmptyState, q as MatrxDataTableFilterUi, r as MatrxDataTableHierarchyConfig, s as MatrxDataTableHierarchyMove, t as MatrxDataTableMobileCardControls, u as MatrxDataTableQueryControl, e as MatrxDataTableQueryState, v as MatrxDataTableSelectionConfig, w as MatrxDataTableSourceProcessing, x as MatrxDataTableToolbar, y as MatrxDataTableUrlStateConfig, z as MatrxDataTableWindowConfig, B as MatrxTablePagination, D as SortDirection, S as SortState, b as TableSavedViewsProps, T as TableSearchMatchMode, E as TableViewQuery, F as TextFilterMode, G as ToolbarFacet } from '../types-CqA8mpjE.cjs';
3
+ import { h as MatrxDataTableProps, i as TableViewSnapshot, c as MatrxDataTableRecordControls, j as ColumnMarker, M as MatrxColumnDef } from '../types-9Ii-b9EJ.cjs';
4
+ export { A as AnyOfColumnSearch, k as CellEditType, l as CellEditsMap, f as ColumnFilterKind, a as ColumnFilterValue, C as ColumnFiltersState, m as MatrxDataTableContextMenuConfig, g as MatrxDataTableCopyConfig, d as MatrxDataTableDensity, n as MatrxDataTableDetailConfig, o as MatrxDataTableEditConfig, p as MatrxDataTableEmptyState, q as MatrxDataTableExpandedDetailConfig, r as MatrxDataTableFilterUi, s as MatrxDataTableHierarchyConfig, t as MatrxDataTableHierarchyMove, u as MatrxDataTableMobileCardControls, v as MatrxDataTableQueryControl, e as MatrxDataTableQueryState, w as MatrxDataTableSelectionConfig, x as MatrxDataTableSourceProcessing, y as MatrxDataTableToolbar, z as MatrxDataTableUrlStateConfig, B as MatrxDataTableWindowConfig, D as MatrxTablePagination, E as SortDirection, S as SortState, b as TableSavedViewsProps, T as TableSearchMatchMode, F as TableViewQuery, G as TextFilterMode, H as ToolbarFacet } from '../types-9Ii-b9EJ.cjs';
5
5
  export { MatrxDataTableHost, MatrxDataTableProvider, MatrxTableRowAlchemy, TableDoors, TableEntityRefProps, TableLinkProps, TablePeekProps, TableSidePanelProps, TableToolbarActionProps, TableWindowPanelProps, useMatrxDataTableHost, useMatrxTableRowAlchemy } from './host.cjs';
6
6
  import { CopyControlProps } from './copy-types.cjs';
7
7
  export { AgentPayloadInput } from './copy-types.cjs';
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import { ReactNode } from 'react';
3
- import { h as MatrxDataTableProps, i as TableViewSnapshot, c as MatrxDataTableRecordControls, j as ColumnMarker, M as MatrxColumnDef } from '../types-CuTdr8mW.js';
4
- export { A as AnyOfColumnSearch, k as CellEditType, l as CellEditsMap, f as ColumnFilterKind, a as ColumnFilterValue, C as ColumnFiltersState, m as MatrxDataTableContextMenuConfig, g as MatrxDataTableCopyConfig, d as MatrxDataTableDensity, n as MatrxDataTableDetailConfig, o as MatrxDataTableEditConfig, p as MatrxDataTableEmptyState, q as MatrxDataTableFilterUi, r as MatrxDataTableHierarchyConfig, s as MatrxDataTableHierarchyMove, t as MatrxDataTableMobileCardControls, u as MatrxDataTableQueryControl, e as MatrxDataTableQueryState, v as MatrxDataTableSelectionConfig, w as MatrxDataTableSourceProcessing, x as MatrxDataTableToolbar, y as MatrxDataTableUrlStateConfig, z as MatrxDataTableWindowConfig, B as MatrxTablePagination, D as SortDirection, S as SortState, b as TableSavedViewsProps, T as TableSearchMatchMode, E as TableViewQuery, F as TextFilterMode, G as ToolbarFacet } from '../types-CuTdr8mW.js';
3
+ import { h as MatrxDataTableProps, i as TableViewSnapshot, c as MatrxDataTableRecordControls, j as ColumnMarker, M as MatrxColumnDef } from '../types-wy1EEaeG.js';
4
+ export { A as AnyOfColumnSearch, k as CellEditType, l as CellEditsMap, f as ColumnFilterKind, a as ColumnFilterValue, C as ColumnFiltersState, m as MatrxDataTableContextMenuConfig, g as MatrxDataTableCopyConfig, d as MatrxDataTableDensity, n as MatrxDataTableDetailConfig, o as MatrxDataTableEditConfig, p as MatrxDataTableEmptyState, q as MatrxDataTableExpandedDetailConfig, r as MatrxDataTableFilterUi, s as MatrxDataTableHierarchyConfig, t as MatrxDataTableHierarchyMove, u as MatrxDataTableMobileCardControls, v as MatrxDataTableQueryControl, e as MatrxDataTableQueryState, w as MatrxDataTableSelectionConfig, x as MatrxDataTableSourceProcessing, y as MatrxDataTableToolbar, z as MatrxDataTableUrlStateConfig, B as MatrxDataTableWindowConfig, D as MatrxTablePagination, E as SortDirection, S as SortState, b as TableSavedViewsProps, T as TableSearchMatchMode, F as TableViewQuery, G as TextFilterMode, H as ToolbarFacet } from '../types-wy1EEaeG.js';
5
5
  export { MatrxDataTableHost, MatrxDataTableProvider, MatrxTableRowAlchemy, TableDoors, TableEntityRefProps, TableLinkProps, TablePeekProps, TableSidePanelProps, TableToolbarActionProps, TableWindowPanelProps, useMatrxDataTableHost, useMatrxTableRowAlchemy } from './host.js';
6
6
  import { CopyControlProps } from './copy-types.js';
7
7
  export { AgentPayloadInput } from './copy-types.js';
@@ -1951,6 +1951,7 @@ function PageNavigationControl({
1951
1951
  }
1952
1952
  function PaginationAction({
1953
1953
  HostAction,
1954
+ compact = false,
1954
1955
  active = false,
1955
1956
  disabledHint,
1956
1957
  visibilityClassName = "inline-flex",
@@ -1979,6 +1980,7 @@ function PaginationAction({
1979
1980
  onClick: props.onClick,
1980
1981
  className: cn(
1981
1982
  PAGINATION_ACTION_CLASS,
1983
+ compact && "h-8 w-8 min-h-8 min-w-8",
1982
1984
  active && PAGINATION_ACTIVE_CLASS,
1983
1985
  fallbackClassName
1984
1986
  ),
@@ -1992,6 +1994,8 @@ var PAGINATION_ACTION_CLASS = "h-11 w-11 min-h-11 min-w-11 rounded-md border bor
1992
1994
  var PAGINATION_ACTIVE_CLASS = "border-primary bg-primary text-primary-foreground hover:bg-primary/90";
1993
1995
  function GenericTablePagination({
1994
1996
  totalItems,
1997
+ unknownTotal,
1998
+ isFetching = false,
1995
1999
  itemsPerPage,
1996
2000
  currentPage,
1997
2001
  onPageChange,
@@ -2032,11 +2036,12 @@ function GenericTablePagination({
2032
2036
  (_, i) => startPage + i
2033
2037
  );
2034
2038
  };
2035
- const isFirstPageDisabled = currentPage <= 1;
2036
- const isLastPageDisabled = currentPage >= totalPages;
2039
+ const isFirstPageDisabled = isFetching || currentPage <= 1;
2040
+ const isLastPageDisabled = isFetching || (unknownTotal ? !unknownTotal.hasNextPage : currentPage >= totalPages);
2037
2041
  const startItem = totalItems === 0 ? 0 : progressive ? 1 : (currentPage - 1) * effectiveItemsPerPage + 1;
2038
2042
  const endItem = Math.min(progressive?.shownCount ?? currentPage * effectiveItemsPerPage, totalItems);
2039
2043
  const defaultLabelFormat = (start, end, total) => {
2044
+ if (unknownTotal) return `${unknownTotal.pageRowCount.toLocaleString()} on page ${currentPage.toLocaleString()}`;
2040
2045
  if (progressive) return progressive.loadedCount !== void 0 && progressive.loadedCount !== total ? `${end} shown / ${total} matching \xB7 ${progressive.loadedCount} loaded` : `${end} shown / ${total} loaded`;
2041
2046
  return compact ? `${start}-${end} of ${total}` : `Showing ${start} to ${end} of ${total} entries`;
2042
2047
  };
@@ -2061,6 +2066,7 @@ function GenericTablePagination({
2061
2066
  children: /* @__PURE__ */ jsxs13(
2062
2067
  Select,
2063
2068
  {
2069
+ disabled: isFetching,
2064
2070
  value: itemsPerPage.toString(),
2065
2071
  onValueChange: (value) => onItemsPerPageChange(parseInt(value)),
2066
2072
  children: [
@@ -2077,7 +2083,7 @@ function GenericTablePagination({
2077
2083
  ),
2078
2084
  /* @__PURE__ */ jsxs13(SelectContent, { children: [
2079
2085
  availablePageSizes.map((size) => /* @__PURE__ */ jsx20(SelectItem, { value: size.toString(), children: size }, size)),
2080
- showAllOption ? /* @__PURE__ */ jsx20(SelectItem, { value: allValue.toString(), children: allOptionLabel }) : null
2086
+ showAllOption && !unknownTotal ? /* @__PURE__ */ jsx20(SelectItem, { value: allValue.toString(), children: allOptionLabel }) : null
2081
2087
  ] })
2082
2088
  ]
2083
2089
  }
@@ -2089,7 +2095,7 @@ function GenericTablePagination({
2089
2095
  {
2090
2096
  className: cn("flex min-w-0 justify-center", infoContainerClassName),
2091
2097
  children: showPageInfo && !hideEntriesInfo && /* @__PURE__ */ jsxs13(Fragment, { children: [
2092
- /* @__PURE__ */ jsx20("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}` }),
2098
+ /* @__PURE__ */ jsx20("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}` }),
2093
2099
  /* @__PURE__ */ jsx20(
2094
2100
  "span",
2095
2101
  {
@@ -2114,7 +2120,7 @@ function GenericTablePagination({
2114
2120
  }
2115
2121
  ) }),
2116
2122
  /* @__PURE__ */ jsx20(TooltipContent, { children: "Show more already-loaded rows" })
2117
- ] }) : null : showPageControls && /* @__PURE__ */ jsxs13(
2123
+ ] }) : null : showPageControls && (unknownTotal ? currentPage > 1 || unknownTotal.hasNextPage : totalPages > 1) && /* @__PURE__ */ jsxs13(
2118
2124
  "div",
2119
2125
  {
2120
2126
  className: cn(
@@ -2127,8 +2133,9 @@ function GenericTablePagination({
2127
2133
  PaginationAction,
2128
2134
  {
2129
2135
  HostAction: host.ToolbarAction,
2136
+ compact,
2130
2137
  ariaLabel: "First page",
2131
- disabledHint: "Already on the first page",
2138
+ disabledHint: isFetching ? "Loading source page" : "Already on the first page",
2132
2139
  visibilityClassName: "hidden sm:inline-flex",
2133
2140
  fallbackClassName: navButtonClassName,
2134
2141
  onClick: () => onPageChange(1),
@@ -2140,41 +2147,46 @@ function GenericTablePagination({
2140
2147
  PaginationAction,
2141
2148
  {
2142
2149
  HostAction: host.ToolbarAction,
2150
+ compact,
2143
2151
  ariaLabel: "Previous page",
2144
- disabledHint: "Already on the first page",
2152
+ disabledHint: isFetching ? "Loading source page" : "Already on the first page",
2145
2153
  fallbackClassName: navButtonClassName,
2146
2154
  onClick: () => onPageChange(currentPage - 1),
2147
2155
  disabled: isFirstPageDisabled,
2148
2156
  children: /* @__PURE__ */ jsx20(ChevronLeft, { className: "h-5 w-5" })
2149
2157
  }
2150
2158
  ),
2151
- getPageNumbers(3).map((page) => /* @__PURE__ */ jsx20(
2159
+ !unknownTotal && getPageNumbers(3).map((page) => /* @__PURE__ */ jsx20(
2152
2160
  PaginationAction,
2153
2161
  {
2154
2162
  HostAction: host.ToolbarAction,
2163
+ compact,
2155
2164
  ariaLabel: `Page ${page}`,
2156
2165
  active: currentPage === page,
2157
- disabledHint: `Already on page ${page}`,
2166
+ disabledHint: isFetching ? "Loading source page" : `Already on page ${page}`,
2158
2167
  visibilityClassName: cn(
2159
2168
  "sm:hidden",
2160
2169
  page === currentPage ? "inline-flex" : "hidden min-[360px]:inline-flex"
2161
2170
  ),
2162
2171
  fallbackClassName: page === currentPage ? pageActiveButtonClassName : pageButtonClassName,
2163
2172
  onClick: () => onPageChange(page),
2173
+ disabled: isFetching,
2164
2174
  children: /* @__PURE__ */ jsx20("span", { children: page })
2165
2175
  },
2166
2176
  `mobile-${page}`
2167
2177
  )),
2168
- getPageNumbers().map((page) => /* @__PURE__ */ jsx20(
2178
+ !unknownTotal && getPageNumbers().map((page) => /* @__PURE__ */ jsx20(
2169
2179
  PaginationAction,
2170
2180
  {
2171
2181
  HostAction: host.ToolbarAction,
2182
+ compact,
2172
2183
  ariaLabel: `Page ${page}`,
2173
2184
  active: currentPage === page,
2174
- disabledHint: `Already on page ${page}`,
2185
+ disabledHint: isFetching ? "Loading source page" : `Already on page ${page}`,
2175
2186
  visibilityClassName: "hidden sm:inline-flex",
2176
2187
  fallbackClassName: currentPage === page ? pageActiveButtonClassName : pageButtonClassName,
2177
2188
  onClick: () => onPageChange(page),
2189
+ disabled: isFetching,
2178
2190
  children: /* @__PURE__ */ jsx20("span", { children: page })
2179
2191
  },
2180
2192
  `desktop-${page}`
@@ -2183,20 +2195,22 @@ function GenericTablePagination({
2183
2195
  PaginationAction,
2184
2196
  {
2185
2197
  HostAction: host.ToolbarAction,
2198
+ compact,
2186
2199
  ariaLabel: "Next page",
2187
- disabledHint: "Already on the last page",
2200
+ disabledHint: isFetching ? "Loading source page" : "Already on the last page",
2188
2201
  fallbackClassName: navButtonClassName,
2189
2202
  onClick: () => onPageChange(currentPage + 1),
2190
2203
  disabled: isLastPageDisabled,
2191
2204
  children: /* @__PURE__ */ jsx20(ChevronRight, { className: "h-5 w-5" })
2192
2205
  }
2193
2206
  ),
2194
- /* @__PURE__ */ jsx20(
2207
+ !unknownTotal && /* @__PURE__ */ jsx20(
2195
2208
  PaginationAction,
2196
2209
  {
2197
2210
  HostAction: host.ToolbarAction,
2211
+ compact,
2198
2212
  ariaLabel: "Last page",
2199
- disabledHint: "Already on the last page",
2213
+ disabledHint: isFetching ? "Loading source page" : "Already on the last page",
2200
2214
  visibilityClassName: "hidden sm:inline-flex",
2201
2215
  fallbackClassName: navButtonClassName,
2202
2216
  onClick: () => onPageChange(totalPages),
@@ -5113,7 +5127,7 @@ function passesColumnFilter(value, filter) {
5113
5127
  if (selected.length === 0) return true;
5114
5128
  const values = selectCellValues(value);
5115
5129
  return selected.some(
5116
- (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)
5130
+ (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)
5117
5131
  );
5118
5132
  }
5119
5133
  case "boolean": {
@@ -5893,6 +5907,7 @@ function ColumnHeaderCell({
5893
5907
  onFilterChange,
5894
5908
  selectOptions = [],
5895
5909
  selectSingle = false,
5910
+ selectAllowCustom = false,
5896
5911
  align = "left",
5897
5912
  className,
5898
5913
  compact = false,
@@ -5928,9 +5943,13 @@ function ColumnHeaderCell({
5928
5943
  const filterActive = isColumnFilterActive(filterValue);
5929
5944
  const deferredFilter = filterApplyLabel !== void 0 || filterShowCancel || filterFooterHint !== void 0;
5930
5945
  const [filterOpen, setFilterOpen] = useState14(false);
5946
+ const [pendingNegation, setPendingNegation] = useState14(false);
5931
5947
  const [draftFilter, setDraftFilter] = useState14(filterValue);
5932
5948
  useEffect9(() => {
5933
- if (!filterOpen) setDraftFilter(filterValue);
5949
+ if (!filterOpen) {
5950
+ setDraftFilter(filterValue);
5951
+ setPendingNegation(filterValue?.negated ?? false);
5952
+ }
5934
5953
  }, [filterOpen, filterValue]);
5935
5954
  const applyFilter = () => {
5936
5955
  onFilterChange(draftFilter);
@@ -5942,6 +5961,7 @@ function ColumnHeaderCell({
5942
5961
  };
5943
5962
  const visibleFilter = deferredFilter ? draftFilter : filterValue;
5944
5963
  const setNegated = (negated) => {
5964
+ setPendingNegation(negated);
5945
5965
  if (!visibleFilter) return;
5946
5966
  const next = { ...visibleFilter, ...negated ? { negated: true } : {} };
5947
5967
  if (!negated) delete next.negated;
@@ -6018,12 +6038,13 @@ function ColumnHeaderCell({
6018
6038
  kind: filterKind,
6019
6039
  value: visibleFilter,
6020
6040
  onChange: (next) => {
6021
- const preservingNegation = next && visibleFilter?.negated ? { ...next, negated: true } : next;
6041
+ const preservingNegation = next && (visibleFilter?.negated ?? pendingNegation) ? { ...next, negated: true } : next;
6022
6042
  if (deferredFilter) setDraftFilter(preservingNegation);
6023
6043
  else onFilterChange(preservingNegation);
6024
6044
  },
6025
6045
  selectOptions,
6026
6046
  selectSingle,
6047
+ selectAllowCustom,
6027
6048
  ...markerLabels ? {
6028
6049
  booleanLabels: {
6029
6050
  all: "All",
@@ -6034,18 +6055,18 @@ function ColumnHeaderCell({
6034
6055
  allowNullModes
6035
6056
  }
6036
6057
  ) : null,
6037
- filterKind && visibleFilter && allowNegation ? /* @__PURE__ */ jsxs22("div", { className: "mt-2 border-t border-border pt-2", children: [
6058
+ filterKind && allowNegation ? /* @__PURE__ */ jsxs22("div", { className: "mt-2 border-t border-border pt-2", children: [
6038
6059
  /* @__PURE__ */ jsx31("p", { className: "mb-1 text-[11px] font-medium uppercase tracking-wider text-muted-foreground", children: "Match mode" }),
6039
6060
  /* @__PURE__ */ jsxs22("div", { className: "grid grid-cols-2 gap-1 rounded-md border p-0.5", children: [
6040
6061
  /* @__PURE__ */ jsx31(
6041
6062
  "button",
6042
6063
  {
6043
6064
  type: "button",
6044
- "aria-pressed": !visibleFilter.negated,
6065
+ "aria-pressed": !(visibleFilter?.negated ?? pendingNegation),
6045
6066
  onClick: () => setNegated(false),
6046
6067
  className: cn(
6047
6068
  "rounded px-2 py-1.5 text-xs transition-colors",
6048
- !visibleFilter.negated ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-muted"
6069
+ !(visibleFilter?.negated ?? pendingNegation) ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-muted"
6049
6070
  ),
6050
6071
  children: "Include"
6051
6072
  }
@@ -6054,11 +6075,11 @@ function ColumnHeaderCell({
6054
6075
  "button",
6055
6076
  {
6056
6077
  type: "button",
6057
- "aria-pressed": visibleFilter.negated === true,
6078
+ "aria-pressed": visibleFilter?.negated ?? pendingNegation,
6058
6079
  onClick: () => setNegated(true),
6059
6080
  className: cn(
6060
6081
  "rounded px-2 py-1.5 text-xs transition-colors",
6061
- visibleFilter.negated ? "bg-destructive text-destructive-foreground" : "text-muted-foreground hover:bg-muted"
6082
+ visibleFilter?.negated ?? pendingNegation ? "bg-destructive text-destructive-foreground" : "text-muted-foreground hover:bg-muted"
6062
6083
  ),
6063
6084
  children: "Exclude"
6064
6085
  }
@@ -6246,6 +6267,7 @@ function FilterBody({
6246
6267
  onChange,
6247
6268
  selectOptions,
6248
6269
  selectSingle = false,
6270
+ selectAllowCustom = false,
6249
6271
  allowNullModes = false,
6250
6272
  booleanLabels
6251
6273
  }) {
@@ -6309,15 +6331,29 @@ function FilterBody({
6309
6331
  }
6310
6332
  if (kind === "select") {
6311
6333
  const selected = value?.kind === "select" ? value.values ?? (value.value && value.value !== "__all__" ? [value.value] : []) : [];
6312
- return /* @__PURE__ */ jsx31(
6313
- SearchableSelectFilter,
6314
- {
6315
- selected,
6316
- options: selectOptions,
6317
- onChange,
6318
- single: selectSingle
6319
- }
6320
- );
6334
+ return /* @__PURE__ */ jsxs22("div", { className: "space-y-2", children: [
6335
+ allowNullModes && /* @__PURE__ */ jsx31("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__ */ jsx31(
6336
+ "button",
6337
+ {
6338
+ type: "button",
6339
+ "aria-pressed": value?.kind === "text" && value.mode === mode,
6340
+ className: "rounded border px-1.5 py-0.5 text-[10px] hover:bg-muted",
6341
+ onClick: () => onChange({ kind: "text", value: "", mode }),
6342
+ children: label
6343
+ },
6344
+ mode
6345
+ )) }),
6346
+ /* @__PURE__ */ jsx31(
6347
+ SearchableSelectFilter,
6348
+ {
6349
+ selected,
6350
+ options: selectOptions,
6351
+ onChange,
6352
+ single: selectSingle,
6353
+ allowCustom: selectAllowCustom
6354
+ }
6355
+ )
6356
+ ] });
6321
6357
  }
6322
6358
  if (kind === "boolean") {
6323
6359
  const current = value?.kind === "boolean" ? value.value : "__all__";
@@ -6349,7 +6385,8 @@ function SearchableSelectFilter({
6349
6385
  selected,
6350
6386
  options,
6351
6387
  onChange,
6352
- single = false
6388
+ single = false,
6389
+ allowCustom = false
6353
6390
  }) {
6354
6391
  const [query, setQuery] = useState14("");
6355
6392
  const filtered = useMemo5(() => {
@@ -6362,7 +6399,7 @@ function SearchableSelectFilter({
6362
6399
  const toggle = (v) => {
6363
6400
  if (single) {
6364
6401
  onChange(
6365
- selected.length === 1 && selected[0] === v ? void 0 : { kind: "select", value: v }
6402
+ selected.length === 1 && selected[0] === v ? void 0 : { kind: "select", value: v, ...v === "" ? { values: [v] } : {} }
6366
6403
  );
6367
6404
  return;
6368
6405
  }
@@ -6399,6 +6436,11 @@ function SearchableSelectFilter({
6399
6436
  /* @__PURE__ */ jsxs22(CommandList, { className: "max-h-48", children: [
6400
6437
  /* @__PURE__ */ jsx31(CommandEmpty, { className: "py-3 text-xs text-muted-foreground", children: "No matches" }),
6401
6438
  /* @__PURE__ */ jsxs22(CommandGroup, { children: [
6439
+ allowCustom && query.trim() && !options.some((option) => option.value === query.trim()) && /* @__PURE__ */ jsxs22(CommandItem, { value: `custom:${query.trim()}`, onSelect: () => toggle(query.trim()), className: "text-xs", children: [
6440
+ "Use \u201C",
6441
+ query.trim(),
6442
+ "\u201D"
6443
+ ] }),
6402
6444
  /* @__PURE__ */ jsx31(
6403
6445
  CommandItem,
6404
6446
  {
@@ -8474,10 +8516,13 @@ function collectSelectOptions(col, rows) {
8474
8516
  const seen = /* @__PURE__ */ new Set();
8475
8517
  const options = [];
8476
8518
  let hasEmpty = false;
8519
+ let hasEmptyCollection = false;
8477
8520
  for (const row of rows) {
8478
- const values = selectCellValues(getCellValue(row, col));
8521
+ const cell = getCellValue(row, col);
8522
+ const values = selectCellValues(cell);
8479
8523
  if (values.length === 0) {
8480
8524
  hasEmpty = true;
8525
+ if (Array.isArray(cell)) hasEmptyCollection = true;
8481
8526
  continue;
8482
8527
  }
8483
8528
  for (const value of values) {
@@ -8486,12 +8531,18 @@ function collectSelectOptions(col, rows) {
8486
8531
  options.push({ value, label: value });
8487
8532
  }
8488
8533
  }
8489
- const base = col.filterOptions?.length ? [...col.filterOptions] : options.sort((a, b) => a.label.localeCompare(b.label));
8534
+ const base = col.filterOptions?.length ? col.filterOptions.map((option) => ({
8535
+ ...option,
8536
+ label: option.label.trim() ? option.label : option.value === "" ? "(empty string)" : option.value.trim() ? option.value : "(whitespace)"
8537
+ })) : options.sort((a, b) => a.label.localeCompare(b.label));
8490
8538
  if (hasEmpty) {
8491
- base.unshift(
8492
- { value: SELECT_EMPTY_VALUE, label: "(empty)" },
8493
- { value: SELECT_NOT_EMPTY_VALUE, label: "(not empty)" }
8494
- );
8539
+ const emptyOption = !hasEmptyCollection && base.some((option) => option.value === "") ? { value: SELECT_NULL_VALUE, label: "(null)" } : { value: SELECT_EMPTY_VALUE, label: "(empty)" };
8540
+ const additions = [];
8541
+ if (!base.some((option) => option.value === emptyOption.value)) additions.push(emptyOption);
8542
+ if (!base.some((option) => option.value === SELECT_NOT_EMPTY_VALUE)) {
8543
+ additions.push({ value: SELECT_NOT_EMPTY_VALUE, label: "(not empty)" });
8544
+ }
8545
+ base.unshift(...additions);
8495
8546
  }
8496
8547
  return base;
8497
8548
  }
@@ -8702,6 +8753,24 @@ function MatrxDataTableWithUrlState({
8702
8753
  );
8703
8754
  }
8704
8755
  var COLUMN_REORDER_ACTIVATION_DISTANCE = 6;
8756
+ var ROW_INTERACTIVE_DESCENDANT_SELECTOR = [
8757
+ "a",
8758
+ "button",
8759
+ "input",
8760
+ "select",
8761
+ "textarea",
8762
+ '[role="button"]',
8763
+ '[role="combobox"]',
8764
+ '[contenteditable="true"]',
8765
+ "[data-matrx-cell-control]"
8766
+ ].join(", ");
8767
+ function rowEventTargetsInteractiveDescendant(target) {
8768
+ const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
8769
+ return element?.closest(ROW_INTERACTIVE_DESCENDANT_SELECTOR) !== null;
8770
+ }
8771
+ function opensRowHref(event) {
8772
+ return event.metaKey || event.ctrlKey || event.shiftKey || event.button === 1;
8773
+ }
8705
8774
  function TableContextPassthrough({ children }) {
8706
8775
  return /* @__PURE__ */ jsx41(Fragment7, { children });
8707
8776
  }
@@ -8729,6 +8798,7 @@ function MatrxDataTableCore({
8729
8798
  hidePagination = false,
8730
8799
  filterUi,
8731
8800
  detail,
8801
+ expandedDetail,
8732
8802
  window: windowConfig,
8733
8803
  copy,
8734
8804
  contextMenu,
@@ -8756,6 +8826,7 @@ function MatrxDataTableCore({
8756
8826
  tableClassName,
8757
8827
  mobileCards,
8758
8828
  mobileCardsBreakpoint = "sm",
8829
+ getRowHref,
8759
8830
  onRowOpen,
8760
8831
  density
8761
8832
  }) {
@@ -9234,7 +9305,12 @@ function MatrxDataTableCore({
9234
9305
  0,
9235
9306
  ...visibleActionStrips.map((element) => Math.ceil(element.scrollWidth))
9236
9307
  );
9237
- const actionsWidth = actionContentWidth || headerActionsWidth;
9308
+ const actionsHeader = table?.querySelector("th[data-matrx-table-actions-column]");
9309
+ const actionsLabel = actionsHeader?.querySelector("[data-matrx-table-actions-label]");
9310
+ const headerPadding = actionsHeader ? getComputedStyle(actionsHeader) : void 0;
9311
+ const measuredLabelWidth = actionsLabel?.getBoundingClientRect().width ?? 0;
9312
+ const labelWidth = measuredLabelWidth > 0 ? Math.ceil(measuredLabelWidth + (Number.parseFloat(headerPadding?.paddingLeft ?? "0") || 0) + (Number.parseFloat(headerPadding?.paddingRight ?? "0") || 0)) : 0;
9313
+ const actionsWidth = Math.max(actionContentWidth, labelWidth) || headerActionsWidth;
9238
9314
  return {
9239
9315
  ...headerTotal > 0 ? { tableWidth: Math.ceil(headerTotal) } : {},
9240
9316
  ...selectionWidth === void 0 ? {} : { selectionWidth },
@@ -9590,7 +9666,20 @@ function MatrxDataTableCore({
9590
9666
  setWindowRowId(null);
9591
9667
  setWindowRowSnapshot(null);
9592
9668
  };
9669
+ const canExpandRow = (row) => Boolean(expandedDetail && (expandedDetail.canExpand?.(row) ?? true));
9670
+ const isRowExpanded = (row) => canExpandRow(row) && expandedDetail?.expandedId === getRowId(row);
9671
+ const toggleExpanded = (row) => {
9672
+ if (!expandedDetail || !canExpandRow(row)) return;
9673
+ const id = getRowId(row);
9674
+ expandedDetail.onExpandedIdChange(
9675
+ expandedDetail.expandedId === id ? null : id
9676
+ );
9677
+ };
9593
9678
  const openRow = (row) => {
9679
+ if (canExpandRow(row)) {
9680
+ toggleExpanded(row);
9681
+ return;
9682
+ }
9594
9683
  if (opensWindowOnRowClick) {
9595
9684
  openWindow(row);
9596
9685
  return;
@@ -9694,7 +9783,11 @@ function MatrxDataTableCore({
9694
9783
  } : {},
9695
9784
  ...editEnabled && visibleColumns.some(
9696
9785
  (column) => column.editable && (column.editableIf?.(row) ?? true)
9697
- ) ? { beginEdit: () => beginRowEdit(row) } : {}
9786
+ ) ? { beginEdit: () => beginRowEdit(row) } : {},
9787
+ ...canExpandRow(row) ? {
9788
+ isExpanded: isRowExpanded(row),
9789
+ toggleExpanded: () => toggleExpanded(row)
9790
+ } : {}
9698
9791
  };
9699
9792
  };
9700
9793
  const hierarchyRows = hierarchy?.rows ?? data;
@@ -10600,6 +10693,7 @@ function MatrxDataTableCore({
10600
10693
  ] }) }) : paginated.map((row, index) => {
10601
10694
  const id = getRowId(row);
10602
10695
  const displayRow = applyRowEdits(row, edits[id]);
10696
+ const controls = buildRecordControls(displayRow);
10603
10697
  const selectable = selection?.isRowSelectable?.(row) ?? Boolean(selection);
10604
10698
  return /* @__PURE__ */ jsx41("div", { "data-row-id": id, className: "contents", children: mobileCards(displayRow, index, {
10605
10699
  renderCell: (columnKey) => {
@@ -10634,10 +10728,15 @@ function MatrxDataTableCore({
10634
10728
  ...copy === void 0 ? { defaultColumns: declaredVisibleCopyColumns } : {}
10635
10729
  }
10636
10730
  ) : null,
10637
- renderRowActions(displayRow, buildRecordControls(displayRow))
10731
+ renderRowActions(displayRow, controls)
10638
10732
  ]
10639
10733
  }
10640
- ) : null
10734
+ ) : null,
10735
+ ...canExpandRow(row) ? {
10736
+ isExpanded: isRowExpanded(row),
10737
+ toggleExpanded: () => toggleExpanded(row)
10738
+ } : {},
10739
+ expandedDetail: expandedDetail && isRowExpanded(row) ? expandedDetail.render(displayRow, controls) : null
10641
10740
  }) }, id);
10642
10741
  }),
10643
10742
  appendQuery !== null && isFetching && !isLoading ? /* @__PURE__ */ jsxs31(
@@ -10827,6 +10926,7 @@ function MatrxDataTableCore({
10827
10926
  onFilterChange: (next) => setColumnFilter(id, next),
10828
10927
  selectOptions: meta?.options,
10829
10928
  selectSingle: col.filterSingle,
10929
+ selectAllowCustom: col.filterAllowCustom,
10830
10930
  filterApplyLabel: filterUi?.applyLabel,
10831
10931
  filterShowCancel: filterUi?.showCancel,
10832
10932
  filterFooterHint: filterUi?.footerHint,
@@ -10863,9 +10963,9 @@ function MatrxDataTableCore({
10863
10963
  "th",
10864
10964
  {
10865
10965
  "data-matrx-table-actions-column": true,
10866
- className: "h-9 w-px px-2 text-right align-middle text-xs font-semibold uppercase tracking-wide text-muted-foreground",
10966
+ className: "h-9 w-px whitespace-nowrap px-2 text-right align-middle text-xs font-semibold uppercase tracking-wide text-muted-foreground",
10867
10967
  style: fixedUtilityWidth(fixedTableGeometry?.actionsWidth),
10868
- children: "Actions"
10968
+ children: /* @__PURE__ */ jsx41("span", { "data-matrx-table-actions-label": true, className: "inline-block whitespace-nowrap", children: "Actions" })
10869
10969
  }
10870
10970
  )
10871
10971
  ] })
@@ -10904,6 +11004,7 @@ function MatrxDataTableCore({
10904
11004
  const isSelected = selectedId === id;
10905
11005
  const rowEdits = edits[id];
10906
11006
  const displayRow = applyRowEdits(row, rowEdits);
11007
+ const controls = buildRecordControls(displayRow);
10907
11008
  const isChecked = selectedIdSet.has(id);
10908
11009
  const rowNode = /* @__PURE__ */ jsxs31(
10909
11010
  "tr",
@@ -10911,15 +11012,28 @@ function MatrxDataTableCore({
10911
11012
  "data-row-id": id,
10912
11013
  "data-state": isSelected ? "selected" : void 0,
10913
11014
  onClick: (e) => {
10914
- if (e.target.closest('a, button, input, select, textarea, [role="button"], [role="combobox"], [contenteditable="true"]')) return;
11015
+ if (rowEventTargetsInteractiveDescendant(e.target)) return;
11016
+ if (opensRowHref(e)) {
11017
+ const href = getRowHref?.(row);
11018
+ if (href) window.open(href, "_blank", "noopener");
11019
+ return;
11020
+ }
10915
11021
  openRow(row);
10916
11022
  },
11023
+ onAuxClick: (e) => {
11024
+ if (e.button !== 1 || rowEventTargetsInteractiveDescendant(e.target)) return;
11025
+ const href = getRowHref?.(row);
11026
+ if (href) {
11027
+ e.preventDefault();
11028
+ window.open(href, "_blank", "noopener");
11029
+ }
11030
+ },
10917
11031
  className: cn(
10918
11032
  // Keep the mobile row opaque while the complete row scrolls
10919
11033
  // horizontally. The translucent zebra/hover tints remain
10920
11034
  // desktop-only so densely layered mobile content stays crisp.
10921
11035
  "border-b border-border/60 bg-card transition-colors",
10922
- (detailEnabled || Boolean(onRowOpen)) && "cursor-pointer sm:hover:bg-muted/50",
11036
+ (detailEnabled || Boolean(onRowOpen) || canExpandRow(row)) && "cursor-pointer sm:hover:bg-muted/50",
10923
11037
  isSelected && "bg-muted",
10924
11038
  isChecked && "bg-primary/5",
10925
11039
  highlightedIndex === index && "bg-muted ring-1 ring-inset ring-primary/60",
@@ -11035,7 +11149,7 @@ function MatrxDataTableCore({
11035
11149
  ...copy === void 0 ? { defaultColumns: declaredVisibleCopyColumns } : {}
11036
11150
  }
11037
11151
  ) : null,
11038
- renderRowActions(displayRow, buildRecordControls(displayRow)),
11152
+ renderRowActions(displayRow, controls),
11039
11153
  windowEnabled ? /* @__PURE__ */ jsxs31(Tooltip, { children: [
11040
11154
  /* @__PURE__ */ jsx41(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx41(
11041
11155
  Button,
@@ -11066,15 +11180,36 @@ function MatrxDataTableCore({
11066
11180
  id
11067
11181
  );
11068
11182
  const wrappedRow = rowWrapper ? rowWrapper(row, rowNode) : rowNode;
11069
- return hierarchy ? /* @__PURE__ */ jsx41(
11070
- HierarchyDroppableRow,
11183
+ const inlineDetail = expandedDetail && isRowExpanded(row) ? /* @__PURE__ */ jsx41(
11184
+ "tr",
11071
11185
  {
11072
- id,
11073
- disabled: !canDropOn(row),
11074
- children: wrappedRow
11186
+ "data-matrx-table-expanded-detail": id,
11187
+ className: cn("bg-muted/20", expandedDetail.className),
11188
+ children: /* @__PURE__ */ jsx41(
11189
+ "td",
11190
+ {
11191
+ colSpan: visibleColumns.length + leadingCols + (showActionsCol ? 1 : 0),
11192
+ className: "p-4",
11193
+ children: expandedDetail.render(displayRow, controls)
11194
+ }
11195
+ )
11075
11196
  },
11076
- id
11077
- ) : /* @__PURE__ */ jsx41(Fragment6, { children: wrappedRow }, id);
11197
+ `${id}:expanded-detail`
11198
+ ) : null;
11199
+ return hierarchy ? /* @__PURE__ */ jsxs31(Fragment6, { children: [
11200
+ /* @__PURE__ */ jsx41(
11201
+ HierarchyDroppableRow,
11202
+ {
11203
+ id,
11204
+ disabled: !canDropOn(row),
11205
+ children: wrappedRow
11206
+ }
11207
+ ),
11208
+ inlineDetail
11209
+ ] }, id) : /* @__PURE__ */ jsxs31(Fragment6, { children: [
11210
+ wrappedRow,
11211
+ inlineDetail
11212
+ ] }, id);
11078
11213
  }),
11079
11214
  appendQuery !== null && isFetching && !isLoading ? /* @__PURE__ */ jsx41("tr", { "data-matrx-table-append-loading-row": true, children: /* @__PURE__ */ jsx41(
11080
11215
  "td",