@marktiderman/genesis-ui 2.4.0 → 2.5.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.
@@ -186,6 +186,8 @@ function useResourcePage(props) {
186
186
  header: col.header ?? titleCase(col.key),
187
187
  sortable: col.sortable ?? true,
188
188
  hideBelow: col.hideBelow,
189
+ editable: col.editable,
190
+ accessor: col.accessor,
189
191
  render: col.render ? (item, value) => col.render(item, value, {
190
192
  update: async () => {
191
193
  throw new Error(
@@ -288,5 +290,5 @@ function useResourcePage(props) {
288
290
  }
289
291
 
290
292
  export { titleCase, useResourcePage };
291
- //# sourceMappingURL=chunk-GDFNAHI4.js.map
292
- //# sourceMappingURL=chunk-GDFNAHI4.js.map
293
+ //# sourceMappingURL=chunk-IXGAEECT.js.map
294
+ //# sourceMappingURL=chunk-IXGAEECT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/hooks/use-resource-page.ts"],"names":[],"mappings":"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","file":"chunk-IXGAEECT.js","sourcesContent":["\"use client\";\n\nimport { useState, useMemo, useCallback, useEffect, useRef } from \"react\";\nimport type { ReactNode } from \"react\";\nimport type { DataTableColumn } from \"../components/data/DataTable\";\nimport type { DataFilterConfig } from \"../components/data/DataFilters\";\nimport type { ViewMode } from \"../components/patterns/view-toggle\";\nimport type { Identifier, FilterParam, SortParam } from \"@marktiderman/genesis-core/provider\";\nimport { useViewPreference } from \"./use-view-preference\";\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/** Actions available inside column render functions. */\nexport interface ResourceActions<T> {\n update: (id: Identifier, data: Partial<T>) => Promise<T>;\n remove: (id: Identifier) => Promise<void>;\n refetch: () => void;\n}\n\n/** Column definition — full config form. */\nexport interface ResourceColumnDef<T> {\n key: string;\n header?: string;\n sortable?: boolean;\n hideBelow?: \"sm\" | \"md\" | \"lg\" | \"xl\";\n render?: (\n item: T,\n value: unknown,\n actions: ResourceActions<T>,\n ) => ReactNode;\n /** In-place cell editing, passed straight through to `DataTable`. */\n editable?: DataTableColumn<T>[\"editable\"];\n /**\n * The cell's value, when it isn't `item[key]`. Passed straight through to\n * `DataTable`, which also starts an edit from `String(accessor(item))`.\n */\n accessor?: (item: T) => unknown;\n}\n\nexport interface UseResourcePageProps<T> {\n /** Resource name — used for view-preference storage key. */\n resource?: string;\n /** The data to display (pre-fetched or from useResource). */\n data: T[];\n /** Total count of records (before client-side filtering). */\n total: number;\n /** Column definitions. Strings are expanded to `{ key, header, sortable }`. Omit to auto-generate from data keys. */\n columns?: Array<string | ResourceColumnDef<T>>;\n /** Status filter config. String = field name (options auto-derived). Object = explicit field + options. */\n statusFilter?: string | { field: string; options?: string[] };\n /** Fields to search across. Defaults to all string-valued fields in data. */\n searchFields?: string[];\n /** Initial filter params applied on mount. */\n initialFilters?: FilterParam[];\n /** Callback when filters change. */\n onFiltersChange?: (filters: FilterParam[]) => void;\n /** Default view mode. */\n defaultView?: ViewMode;\n /** Detail interaction mode. */\n detail?: \"panel\" | \"modal\" | \"route\" | \"none\";\n /** Field used for the detail title. Defaults to first column key. */\n titleField?: string;\n /** Field used for the detail subtitle. Defaults to second column key. */\n subtitleField?: string;\n /**\n * Server-controlled mode. The `data` handed in has ALREADY been searched,\n * sorted and paginated by the server, so this hook must not do any of it a\n * second time — `data` is returned untouched and `total` is the server's\n * `total` rather than the length of a client-filtered slice.\n *\n * Search / sort / status state is still tracked (the filter UI stays\n * controlled and `onFiltersChange` still fires) — only the client-side\n * transform of `data` is skipped. Defaults to `false`, so every existing\n * caller keeps its current client-side behavior.\n */\n controlled?: boolean;\n}\n\nexport interface UseResourcePageReturn<T> {\n /** Filtered and sorted data for display. */\n data: T[];\n /** Total count (of the filtered set; the server's `total` when `controlled`). */\n total: number;\n /** Resolved column definitions. */\n columns: ResourceColumnDef<T>[];\n /** Columns mapped to DataTable format. */\n tableColumns: DataTableColumn<T>[];\n /** Ready-to-spread filter config for DataFilters. */\n filterConfig: {\n search: string;\n onSearchChange: (value: string) => void;\n sort: string;\n onSortChange: (sort: string) => void;\n sortOptions: Array<{ value: string; label: string }>;\n statusChips?: {\n options: string[];\n selected: string[];\n onChange: (values: string[]) => void;\n };\n };\n viewMode: ViewMode;\n setViewMode: (mode: ViewMode) => void;\n search: string;\n setSearch: (value: string) => void;\n sort: SortParam | null;\n setSort: (sort: SortParam | null) => void;\n statusValue: string[];\n setStatusValue: (values: string[]) => void;\n selectedItem: T | null;\n setSelectedItem: (item: T | null) => void;\n detailOpen: boolean;\n setDetailOpen: (open: boolean) => void;\n handleRowClick: (item: T) => void;\n titleField: string;\n subtitleField: string;\n statusOptions: string[];\n hasActiveFilters: boolean;\n clearFilters: () => void;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Convert a key string to title case.\n * \"created_at\" → \"Created At\"\n * \"firstName\" → \"First Name\"\n */\nexport function titleCase(str: string): string {\n // Split on underscores, hyphens, and camelCase boundaries\n const words = str\n .replace(/([a-z])([A-Z])/g, \"$1 $2\") // camelCase → camel Case\n .replace(/[_-]/g, \" \") // snake_case / kebab-case → spaces\n .split(/\\s+/)\n .filter(Boolean);\n\n return words\n .map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())\n .join(\" \");\n}\n\n/**\n * Resolve column shorthand into full ResourceColumnDef objects.\n * - undefined + data present → auto-generate from Object.keys(data[0]), skip \"id\"\n * - undefined + empty data → empty array\n * - string items → { key, header: titleCase(key), sortable: true }\n * - object items → defaults applied\n */\nfunction resolveColumns<T>(\n columns: Array<string | ResourceColumnDef<T>> | undefined,\n data: T[],\n): ResourceColumnDef<T>[] {\n if (columns === undefined) {\n if (data.length === 0) return [];\n const keys = Object.keys(data[0] as Record<string, unknown>).filter(\n (k) => k !== \"id\",\n );\n return keys.map((key) => ({\n key,\n header: titleCase(key),\n sortable: true,\n }));\n }\n\n return columns.map((col) => {\n if (typeof col === \"string\") {\n return { key: col, header: titleCase(col), sortable: true };\n }\n return {\n ...col,\n header: col.header ?? titleCase(col.key),\n sortable: col.sortable ?? true,\n };\n });\n}\n\n/**\n * Derive the list of string fields in a data set (for default search).\n */\nfunction deriveStringFields<T>(data: T[]): string[] {\n if (data.length === 0) return [];\n const first = data[0] as Record<string, unknown>;\n return Object.keys(first).filter((k) => typeof first[k] === \"string\");\n}\n\n/**\n * Derive unique status options from a data array.\n */\nfunction deriveStatusOptions<T>(data: T[], field: string): string[] {\n const values = new Set<string>();\n for (const item of data) {\n const val = (item as Record<string, unknown>)[field];\n if (val != null) values.add(String(val));\n }\n return Array.from(values).sort();\n}\n\n/**\n * Client-side text search across multiple fields.\n */\nfunction matchesSearch<T>(\n item: T,\n query: string,\n fields: string[],\n): boolean {\n if (!query) return true;\n const lower = query.toLowerCase();\n const record = item as Record<string, unknown>;\n return fields.some((f) => {\n const val = record[f];\n return val != null && String(val).toLowerCase().includes(lower);\n });\n}\n\n/**\n * Client-side sort comparator.\n */\nfunction compareItems<T>(a: T, b: T, sort: SortParam): number {\n const ra = a as Record<string, unknown>;\n const rb = b as Record<string, unknown>;\n const va = ra[sort.field];\n const vb = rb[sort.field];\n const sa = va == null ? \"\" : String(va);\n const sb = vb == null ? \"\" : String(vb);\n const cmp = sa.localeCompare(sb, undefined, {\n numeric: true,\n sensitivity: \"base\",\n });\n return sort.order === \"asc\" ? cmp : -cmp;\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\nexport function useResourcePage<T = Record<string, unknown>>(\n props: UseResourcePageProps<T>,\n): UseResourcePageReturn<T> {\n const {\n resource,\n data: rawData,\n total: rawTotal,\n columns: columnsProp,\n statusFilter: statusFilterProp,\n searchFields: searchFieldsProp,\n defaultView = \"table\",\n detail = \"panel\",\n titleField: titleFieldProp,\n subtitleField: subtitleFieldProp,\n initialFilters,\n onFiltersChange,\n controlled = false,\n } = props;\n\n // -- View mode --\n const [viewMode, setViewMode] = useViewPreference(\n resource ?? \"resource-page\",\n defaultView,\n );\n\n // -- Derive initial values from initialFilters --\n const initialSearch = useMemo(() => {\n if (!initialFilters) return \"\";\n const searchFilter = initialFilters.find(\n (f) => f.operator === \"contains\" && f.field === \"__search__\",\n );\n return searchFilter && \"value\" in searchFilter ? String(searchFilter.value ?? \"\") : \"\";\n }, []); // eslint-disable-line react-hooks/exhaustive-deps -- only on mount\n\n const initialSort = useMemo(() => {\n if (!initialFilters) return null;\n const sortFilter = initialFilters.find(\n (f) => f.field === \"__sort__\",\n );\n if (!sortFilter || !(\"value\" in sortFilter) || typeof sortFilter.value !== \"string\") return null;\n const [field, order] = sortFilter.value.split(\":\");\n if (!field || (order !== \"asc\" && order !== \"desc\")) return null;\n return { field, order } as SortParam;\n }, []); // eslint-disable-line react-hooks/exhaustive-deps -- only on mount\n\n const initialStatus = useMemo(() => {\n if (!initialFilters) return [];\n const statusFilter = initialFilters.find(\n (f) => f.operator === \"in\" && f.field === \"__status__\",\n );\n if (!statusFilter || statusFilter.operator !== \"in\") return [];\n return statusFilter.value.map((v) => String(v));\n }, []); // eslint-disable-line react-hooks/exhaustive-deps -- only on mount\n\n // -- Search --\n const [search, setSearch] = useState(initialSearch);\n\n // -- Sort --\n const [sort, setSort] = useState<SortParam | null>(initialSort);\n\n // -- Status filter --\n const [statusValue, setStatusValue] = useState<string[]>(initialStatus);\n\n // -- Detail panel --\n const [selectedItem, setSelectedItem] = useState<T | null>(null);\n const [detailOpen, setDetailOpen] = useState(false);\n\n // -- Resolve columns --\n const resolvedColumns = useMemo(\n () => resolveColumns(columnsProp, rawData),\n [columnsProp, rawData],\n );\n\n // -- Title / subtitle fields --\n const titleField =\n titleFieldProp ?? resolvedColumns[0]?.key ?? \"name\";\n const subtitleField =\n subtitleFieldProp ?? resolvedColumns[1]?.key ?? \"\";\n\n // -- Status filter config --\n const statusFieldName = useMemo(() => {\n if (!statusFilterProp) return null;\n return typeof statusFilterProp === \"string\"\n ? statusFilterProp\n : statusFilterProp.field;\n }, [statusFilterProp]);\n\n const statusOptions = useMemo(() => {\n if (!statusFilterProp) return [];\n if (typeof statusFilterProp === \"object\" && statusFilterProp.options) {\n return statusFilterProp.options;\n }\n if (!statusFieldName) return [];\n return deriveStatusOptions(rawData, statusFieldName);\n }, [statusFilterProp, statusFieldName, rawData]);\n\n // -- Search fields --\n const searchFields = useMemo(\n () => searchFieldsProp ?? deriveStringFields(rawData),\n [searchFieldsProp, rawData],\n );\n\n // -- Client-side filtering + sorting --\n // Skipped entirely in controlled mode: the server already applied the\n // search, the sort and the page window, so re-running them here would\n // filter the current page against a query the server has already honoured\n // (and re-sort a slice of rows that is not the full result set).\n const filteredData = useMemo(() => {\n if (controlled) return rawData;\n\n let result = rawData;\n\n // Text search\n if (search) {\n result = result.filter((item) =>\n matchesSearch(item, search, searchFields),\n );\n }\n\n // Status filter\n if (statusFieldName && statusValue.length > 0) {\n result = result.filter((item) => {\n const val = (item as Record<string, unknown>)[statusFieldName];\n return val != null && statusValue.includes(String(val));\n });\n }\n\n // Sort\n if (sort) {\n result = [...result].sort((a, b) => compareItems(a, b, sort));\n }\n\n return result;\n }, [controlled, rawData, search, searchFields, statusFieldName, statusValue, sort]);\n\n // -- Sort options (built from resolved columns) --\n const sortOptions = useMemo(() => {\n const opts: Array<{ value: string; label: string }> = [];\n for (const col of resolvedColumns) {\n if (col.sortable !== false) {\n opts.push({\n value: `${col.key}:asc`,\n label: `${col.header ?? titleCase(col.key)} (A-Z)`,\n });\n opts.push({\n value: `${col.key}:desc`,\n label: `${col.header ?? titleCase(col.key)} (Z-A)`,\n });\n }\n }\n return opts;\n }, [resolvedColumns]);\n\n // -- Sort string for DataFilters select --\n const sortString = sort ? `${sort.field}:${sort.order}` : \"\";\n\n const handleSortChange = useCallback((value: string) => {\n if (!value) {\n setSort(null);\n return;\n }\n const [field, order] = value.split(\":\");\n setSort({ field, order: order as \"asc\" | \"desc\" });\n }, []);\n\n // -- Map to DataTableColumn --\n const tableColumns: DataTableColumn<T>[] = useMemo(\n () =>\n resolvedColumns.map((col) => ({\n key: col.key,\n header: col.header ?? titleCase(col.key),\n sortable: col.sortable ?? true,\n hideBelow: col.hideBelow,\n editable: col.editable,\n accessor: col.accessor,\n render: col.render\n ? (item: T, value: unknown) =>\n col.render!(item, value, {\n update: async () => {\n throw new Error(\n \"ResourceActions.update not available in server-data mode\",\n );\n },\n remove: async () => {\n throw new Error(\n \"ResourceActions.remove not available in server-data mode\",\n );\n },\n refetch: () => {\n /* no-op in server-data mode */\n },\n })\n : undefined,\n })),\n [resolvedColumns],\n );\n\n // -- Active filters check --\n const hasActiveFilters =\n search !== \"\" || statusValue.length > 0 || sort !== null;\n\n const clearFilters = useCallback(() => {\n setSearch(\"\");\n setStatusValue([]);\n setSort(null);\n }, []);\n\n // -- Notify parent of filter changes --\n const onFiltersChangeRef = useRef(onFiltersChange);\n onFiltersChangeRef.current = onFiltersChange;\n\n useEffect(() => {\n if (!onFiltersChangeRef.current) return;\n const filters: FilterParam[] = [];\n if (search) {\n filters.push({ field: \"__search__\", operator: \"contains\", value: search });\n }\n if (sort) {\n filters.push({ field: \"__sort__\", operator: \"eq\", value: `${sort.field}:${sort.order}` });\n }\n if (statusValue.length > 0) {\n filters.push({ field: \"__status__\", operator: \"in\", value: statusValue });\n }\n onFiltersChangeRef.current(filters);\n }, [search, sort, statusValue]);\n\n // -- Row click --\n const handleRowClick = useCallback(\n (item: T) => {\n if (detail === \"none\") return;\n setSelectedItem(item);\n setDetailOpen(true);\n },\n [detail],\n );\n\n // -- Filter config (ready to spread into DataFilters) --\n const filterConfig = useMemo(\n () => ({\n search,\n onSearchChange: setSearch,\n sort: sortString,\n onSortChange: handleSortChange,\n sortOptions,\n ...(statusFieldName && statusOptions.length > 0\n ? {\n statusChips: {\n options: statusOptions,\n selected: statusValue,\n onChange: setStatusValue,\n },\n }\n : {}),\n }),\n [\n search,\n sortString,\n handleSortChange,\n sortOptions,\n statusFieldName,\n statusOptions,\n statusValue,\n ],\n );\n\n return {\n data: filteredData,\n // Controlled mode: `filteredData` is one server page, so its length is\n // not the record count — the server's total is.\n total: controlled ? rawTotal : filteredData.length,\n columns: resolvedColumns,\n tableColumns,\n filterConfig,\n viewMode,\n setViewMode,\n search,\n setSearch,\n sort,\n setSort,\n statusValue,\n setStatusValue,\n selectedItem,\n setSelectedItem,\n detailOpen,\n setDetailOpen,\n handleRowClick,\n titleField,\n subtitleField,\n statusOptions,\n hasActiveFilters,\n clearFilters,\n };\n}\n"]}
@@ -6,8 +6,8 @@ import { V as ViewMode } from '../../view-toggle-CahQ87PW.js';
6
6
  export { a as ViewToggle } from '../../view-toggle-CahQ87PW.js';
7
7
  import { LucideIcon } from 'lucide-react';
8
8
  import { Density } from '../../hooks/use-view-settings.js';
9
- import { R as ResourceActions, a as ResourceColumnDef, D as DataTable } from '../../use-resource-page-DTQS011Q.js';
10
- export { c as DataTableColumn, d as DataTableProps, L as LinkCell, T as ThumbnailCell } from '../../use-resource-page-DTQS011Q.js';
9
+ import { R as ResourceActions, a as ResourceColumnDef, D as DataTable } from '../../use-resource-page-Dd2VUv30.js';
10
+ export { c as DataTableColumn, d as DataTableProps, L as LinkCell, T as ThumbnailCell } from '../../use-resource-page-Dd2VUv30.js';
11
11
  export { EmptyState } from '../patterns/empty-state.js';
12
12
  export { V as ViewSettings } from '../../view-settings-kHKCHMjj.js';
13
13
  import { L as LinkComponent, N as NavigateFn } from '../../navigation-DBwcZ6vb.js';
@@ -512,6 +512,14 @@ interface ResourcePageProps<T extends Record<string, unknown> = Record<string, u
512
512
  subtitle?: string;
513
513
  icon?: LucideIcon;
514
514
  columns?: Array<string | ResourceColumnDef<T>>;
515
+ /**
516
+ * A row's identity, for React keys and bulk selection. Defaults to
517
+ * `String(item.id)`. Supply it for rows keyed some other way — a composite
518
+ * key (`encodeRecordKey` from `@marktiderman/genesis-core/schema`), or a
519
+ * table whose key column isn't called `id`. Detail `route` mode still
520
+ * navigates by `item.id`.
521
+ */
522
+ getKey?: (item: T) => string;
515
523
  /** How row-click detail is shown. Default "panel". */
516
524
  detail?: "panel" | "modal" | "route" | "none";
517
525
  detailWidth?: "sm" | "md" | "lg";
@@ -27,7 +27,7 @@ import '../../chunk-F47U5KNW.js';
27
27
  import { DetailPage } from '../../chunk-AFZ76Q26.js';
28
28
  import '../../chunk-D2LIXA2Y.js';
29
29
  import '../../chunk-AVFZ4P4B.js';
30
- import { titleCase, useResourcePage } from '../../chunk-GDFNAHI4.js';
30
+ import { titleCase, useResourcePage } from '../../chunk-IXGAEECT.js';
31
31
  export { useSavedViews } from '../../chunk-GCQFVMPI.js';
32
32
  import '../../chunk-FTTNGGLA.js';
33
33
  import { useViewSettings } from '../../chunk-OZ7ZAYCS.js';
@@ -61,7 +61,7 @@ function DataPageShell({
61
61
  emptyMessage,
62
62
  emptyAction,
63
63
  hasError = false,
64
- errorMessage,
64
+ errorMessage: errorMessage2,
65
65
  onRetry,
66
66
  count,
67
67
  totalCount,
@@ -148,7 +148,7 @@ function DataPageShell({
148
148
  ) : hasError ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-16 text-center", children: [
149
149
  /* @__PURE__ */ jsx(AlertTriangle, { className: "h-10 w-10 text-destructive mb-3" }),
150
150
  /* @__PURE__ */ jsx("p", { className: "mb-1 text-sm font-medium text-foreground", children: "Something went wrong" }),
151
- /* @__PURE__ */ jsx("p", { className: "mb-4 text-sm text-muted-foreground", children: errorMessage || `Failed to load ${entity}s. Please try again.` }),
151
+ /* @__PURE__ */ jsx("p", { className: "mb-4 text-sm text-muted-foreground", children: errorMessage2 || `Failed to load ${entity}s. Please try again.` }),
152
152
  onRetry && /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: onRetry, children: [
153
153
  /* @__PURE__ */ jsx(RefreshCw, { className: "mr-1 h-4 w-4" }),
154
154
  " Retry"
@@ -639,6 +639,9 @@ function getCellClasses(col) {
639
639
  const hide = col.hideBelow ? col.hideBelow === "sm" ? "hidden sm:table-cell" : col.hideBelow === "md" ? "hidden md:table-cell" : col.hideBelow === "lg" ? "hidden lg:table-cell" : "hidden xl:table-cell" : "";
640
640
  return cn(col.width, hide);
641
641
  }
642
+ function errorMessage(error) {
643
+ return error instanceof Error ? error.message : String(error);
644
+ }
642
645
  function EditableCell({
643
646
  item,
644
647
  column,
@@ -646,13 +649,49 @@ function EditableCell({
646
649
  }) {
647
650
  const [editing, setEditing] = useState(false);
648
651
  const [draft, setDraft] = useState(String(value ?? ""));
652
+ const [saving, setSaving] = useState(false);
653
+ const [error, setError] = useState(null);
654
+ const inFlight = useRef(false);
655
+ const failedDraft = useRef(null);
649
656
  const editable = column.editable;
657
+ const close = useCallback(() => {
658
+ setEditing(false);
659
+ setError(null);
660
+ failedDraft.current = null;
661
+ }, []);
650
662
  const commit = useCallback(
651
663
  (v) => {
652
- editable.onSave(item, v);
653
- setEditing(false);
664
+ if (inFlight.current) return;
665
+ let result;
666
+ try {
667
+ result = editable.onSave(item, v);
668
+ } catch (e) {
669
+ setError(errorMessage(e));
670
+ failedDraft.current = v;
671
+ return;
672
+ }
673
+ if (!result || typeof result.then !== "function") {
674
+ close();
675
+ return;
676
+ }
677
+ inFlight.current = true;
678
+ setSaving(true);
679
+ setError(null);
680
+ result.then(
681
+ () => {
682
+ inFlight.current = false;
683
+ setSaving(false);
684
+ close();
685
+ },
686
+ (e) => {
687
+ inFlight.current = false;
688
+ setSaving(false);
689
+ setError(errorMessage(e));
690
+ failedDraft.current = v;
691
+ }
692
+ );
654
693
  },
655
- [editable, item]
694
+ [editable, item, close]
656
695
  );
657
696
  if (!editing) {
658
697
  return /* @__PURE__ */ jsx(
@@ -662,32 +701,80 @@ function EditableCell({
662
701
  onClick: (e) => {
663
702
  e.stopPropagation();
664
703
  setDraft(String(value ?? ""));
704
+ setError(null);
705
+ failedDraft.current = null;
665
706
  setEditing(true);
666
707
  },
667
708
  children: column.render ? column.render(item, value) : String(value ?? "\u2014")
668
709
  }
669
710
  );
670
711
  }
712
+ const feedback = error ? /* @__PURE__ */ jsx("p", { role: "alert", className: "mt-1 text-[11px] leading-snug text-destructive", children: error }) : null;
671
713
  if (editable.type === "select" && editable.options) {
672
- return /* @__PURE__ */ jsx("div", { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsxs(Select, { defaultValue: draft, onValueChange: (v) => commit(v), children: [
673
- /* @__PURE__ */ jsx(SelectTrigger, { className: "h-7 text-xs w-28", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
674
- /* @__PURE__ */ jsx(SelectContent, { children: editable.options.map((opt) => /* @__PURE__ */ jsx(SelectItem, { value: opt, children: opt }, opt)) })
675
- ] }) });
676
- }
677
- return /* @__PURE__ */ jsx("div", { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx(
678
- Input,
679
- {
680
- autoFocus: true,
681
- className: "h-7 text-xs w-full",
682
- value: draft,
683
- onChange: (e) => setDraft(e.target.value),
684
- onBlur: () => commit(draft),
685
- onKeyDown: (e) => {
686
- if (e.key === "Enter") commit(draft);
687
- if (e.key === "Escape") setEditing(false);
714
+ return /* @__PURE__ */ jsxs(
715
+ "div",
716
+ {
717
+ onClick: (e) => e.stopPropagation(),
718
+ onKeyDown: (e) => {
719
+ if (e.key === "Escape" && !inFlight.current) close();
720
+ },
721
+ "aria-busy": saving || void 0,
722
+ children: [
723
+ /* @__PURE__ */ jsxs(
724
+ Select,
725
+ {
726
+ value: draft,
727
+ disabled: saving,
728
+ onValueChange: (v) => {
729
+ setDraft(v);
730
+ commit(v);
731
+ },
732
+ children: [
733
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "h-7 text-xs w-28", "aria-invalid": error ? true : void 0, children: /* @__PURE__ */ jsx(SelectValue, {}) }),
734
+ /* @__PURE__ */ jsx(SelectContent, { children: editable.options.map((opt) => /* @__PURE__ */ jsx(SelectItem, { value: opt, children: opt }, opt)) })
735
+ ]
736
+ }
737
+ ),
738
+ feedback,
739
+ error && /* @__PURE__ */ jsx(
740
+ "button",
741
+ {
742
+ type: "button",
743
+ className: "mt-0.5 text-[11px] text-muted-foreground underline-offset-2 hover:underline",
744
+ onClick: close,
745
+ children: "Cancel"
746
+ }
747
+ )
748
+ ]
688
749
  }
689
- }
690
- ) });
750
+ );
751
+ }
752
+ return /* @__PURE__ */ jsxs("div", { onClick: (e) => e.stopPropagation(), "aria-busy": saving || void 0, children: [
753
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
754
+ /* @__PURE__ */ jsx(
755
+ Input,
756
+ {
757
+ autoFocus: true,
758
+ className: cn("h-7 text-xs w-full", saving && "pr-7 opacity-70"),
759
+ value: draft,
760
+ readOnly: saving,
761
+ "aria-invalid": error ? true : void 0,
762
+ onChange: (e) => setDraft(e.target.value),
763
+ onBlur: () => {
764
+ if (inFlight.current) return;
765
+ if (failedDraft.current === draft) close();
766
+ else commit(draft);
767
+ },
768
+ onKeyDown: (e) => {
769
+ if (e.key === "Enter") commit(draft);
770
+ if (e.key === "Escape" && !inFlight.current) close();
771
+ }
772
+ }
773
+ ),
774
+ saving && /* @__PURE__ */ jsx(Spinner, { className: "absolute right-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2", "aria-label": "Saving" })
775
+ ] }),
776
+ feedback
777
+ ] });
691
778
  }
692
779
  function ResizeHandle({
693
780
  onResize,
@@ -2036,15 +2123,15 @@ function ResourceFormFieldRenderer({
2036
2123
  name: field.key,
2037
2124
  control,
2038
2125
  render: ({ field: f, fieldState }) => {
2039
- const errorMessage = fieldState.error?.message;
2126
+ const errorMessage2 = fieldState.error?.message;
2040
2127
  const ariaProps = {
2041
- "aria-invalid": !!errorMessage || void 0,
2042
- "aria-describedby": errorMessage ? `${field.key}-error` : field.description ? `${field.key}-desc` : void 0,
2128
+ "aria-invalid": !!errorMessage2 || void 0,
2129
+ "aria-describedby": errorMessage2 ? `${field.key}-error` : field.description ? `${field.key}-desc` : void 0,
2043
2130
  "aria-required": field.required || void 0
2044
2131
  };
2045
2132
  if (fieldType === "switch") {
2046
2133
  return /* @__PURE__ */ jsxs(FormField, { className: "flex flex-row items-center justify-between rounded-lg border p-4", children: [
2047
- /* @__PURE__ */ jsx("div", { className: "space-y-0.5", children: /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage, children: label }) }),
2134
+ /* @__PURE__ */ jsx("div", { className: "space-y-0.5", children: /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage2, children: label }) }),
2048
2135
  /* @__PURE__ */ jsx(
2049
2136
  Switch,
2050
2137
  {
@@ -2068,14 +2155,14 @@ function ResourceFormFieldRenderer({
2068
2155
  }
2069
2156
  ),
2070
2157
  /* @__PURE__ */ jsxs("div", { className: "space-y-1 leading-none", children: [
2071
- /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage, children: label }),
2072
- /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage })
2158
+ /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage2, children: label }),
2159
+ /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage2 })
2073
2160
  ] })
2074
2161
  ] });
2075
2162
  }
2076
2163
  if (fieldType === "select" && field.options) {
2077
2164
  return /* @__PURE__ */ jsxs(FormField, { children: [
2078
- /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage, required: field.required, children: label }),
2165
+ /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage2, required: field.required, children: label }),
2079
2166
  /* @__PURE__ */ jsxs(
2080
2167
  Select,
2081
2168
  {
@@ -2095,12 +2182,12 @@ function ResourceFormFieldRenderer({
2095
2182
  }
2096
2183
  ),
2097
2184
  field.description && /* @__PURE__ */ jsx(FormDescription, { id: `${field.key}-desc`, children: field.description }),
2098
- /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage })
2185
+ /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage2 })
2099
2186
  ] });
2100
2187
  }
2101
2188
  if (fieldType === "relation" && field.relation) {
2102
2189
  return /* @__PURE__ */ jsxs(FormField, { children: [
2103
- /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage, required: field.required, children: label }),
2190
+ /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage2, required: field.required, children: label }),
2104
2191
  /* @__PURE__ */ jsx(
2105
2192
  RelationField,
2106
2193
  {
@@ -2111,12 +2198,12 @@ function ResourceFormFieldRenderer({
2111
2198
  }
2112
2199
  ),
2113
2200
  field.description && /* @__PURE__ */ jsx(FormDescription, { id: `${field.key}-desc`, children: field.description }),
2114
- /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage })
2201
+ /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage2 })
2115
2202
  ] });
2116
2203
  }
2117
2204
  if (fieldType === "file" || fieldType === "image") {
2118
2205
  return /* @__PURE__ */ jsxs(FormField, { children: [
2119
- /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage, required: field.required, children: label }),
2206
+ /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage2, required: field.required, children: label }),
2120
2207
  /* @__PURE__ */ jsx(
2121
2208
  FileUploadField,
2122
2209
  {
@@ -2127,12 +2214,12 @@ function ResourceFormFieldRenderer({
2127
2214
  }
2128
2215
  ),
2129
2216
  field.description && /* @__PURE__ */ jsx(FormDescription, { id: `${field.key}-desc`, children: field.description }),
2130
- /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage })
2217
+ /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage2 })
2131
2218
  ] });
2132
2219
  }
2133
2220
  if (fieldType === "textarea") {
2134
2221
  return /* @__PURE__ */ jsxs(FormField, { children: [
2135
- /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage, required: field.required, children: label }),
2222
+ /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage2, required: field.required, children: label }),
2136
2223
  /* @__PURE__ */ jsx(
2137
2224
  Textarea,
2138
2225
  {
@@ -2146,12 +2233,12 @@ function ResourceFormFieldRenderer({
2146
2233
  }
2147
2234
  ),
2148
2235
  field.description && /* @__PURE__ */ jsx(FormDescription, { id: `${field.key}-desc`, children: field.description }),
2149
- /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage })
2236
+ /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage2 })
2150
2237
  ] });
2151
2238
  }
2152
2239
  if (fieldType === "number") {
2153
2240
  return /* @__PURE__ */ jsxs(FormField, { children: [
2154
- /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage, required: field.required, children: label }),
2241
+ /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage2, required: field.required, children: label }),
2155
2242
  /* @__PURE__ */ jsx(
2156
2243
  Input,
2157
2244
  {
@@ -2170,11 +2257,11 @@ function ResourceFormFieldRenderer({
2170
2257
  }
2171
2258
  ),
2172
2259
  field.description && /* @__PURE__ */ jsx(FormDescription, { id: `${field.key}-desc`, children: field.description }),
2173
- /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage })
2260
+ /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage2 })
2174
2261
  ] });
2175
2262
  }
2176
2263
  return /* @__PURE__ */ jsxs(FormField, { children: [
2177
- /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage, required: field.required, children: label }),
2264
+ /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage2, required: field.required, children: label }),
2178
2265
  /* @__PURE__ */ jsx(
2179
2266
  Input,
2180
2267
  {
@@ -2188,7 +2275,7 @@ function ResourceFormFieldRenderer({
2188
2275
  }
2189
2276
  ),
2190
2277
  field.description && /* @__PURE__ */ jsx(FormDescription, { id: `${field.key}-desc`, children: field.description }),
2191
- /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage })
2278
+ /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage2 })
2192
2279
  ] });
2193
2280
  }
2194
2281
  }
@@ -2475,6 +2562,7 @@ function ResourcePage(props) {
2475
2562
  subtitle,
2476
2563
  icon,
2477
2564
  columns: columnsProp,
2565
+ getKey: getKeyProp,
2478
2566
  detail = "panel",
2479
2567
  detailWidth = "md",
2480
2568
  titleField: titleFieldProp,
@@ -2495,7 +2583,7 @@ function ResourcePage(props) {
2495
2583
  onSearchChange,
2496
2584
  loading: loadingProp,
2497
2585
  error: errorProp,
2498
- errorMessage,
2586
+ errorMessage: errorMessage2,
2499
2587
  onRetry: onRetryProp,
2500
2588
  savedViews,
2501
2589
  onLoadView,
@@ -2592,6 +2680,10 @@ function ResourcePage(props) {
2592
2680
  resourceHook.remove,
2593
2681
  resourceHook.list
2594
2682
  ]);
2683
+ const keyOf = useCallback(
2684
+ (item) => getKeyProp ? getKeyProp(item) : String(item.id ?? ""),
2685
+ [getKeyProp]
2686
+ );
2595
2687
  const [selectedIds, setSelectedIds] = useState(/* @__PURE__ */ new Set());
2596
2688
  const handleToggleSelect = useCallback((id) => {
2597
2689
  setSelectedIds((prev) => {
@@ -2604,13 +2696,9 @@ function ResourcePage(props) {
2604
2696
  const handleToggleAll = useCallback(() => {
2605
2697
  setSelectedIds((prev) => {
2606
2698
  if (prev.size === page.data.length) return /* @__PURE__ */ new Set();
2607
- return new Set(
2608
- page.data.map(
2609
- (item) => String(item.id ?? "")
2610
- )
2611
- );
2699
+ return new Set(page.data.map(keyOf));
2612
2700
  });
2613
- }, [page.data]);
2701
+ }, [page.data, keyOf]);
2614
2702
  const handleClearSelection = useCallback(() => {
2615
2703
  setSelectedIds(/* @__PURE__ */ new Set());
2616
2704
  }, []);
@@ -2646,6 +2734,8 @@ function ResourcePage(props) {
2646
2734
  header: col.header ?? col.key,
2647
2735
  sortable: col.sortable ?? true,
2648
2736
  hideBelow: col.hideBelow,
2737
+ editable: col.editable,
2738
+ accessor: col.accessor,
2649
2739
  render: col.render ? (item, value) => col.render(item, value, actions) : void 0
2650
2740
  })) : page.tableColumns;
2651
2741
  if (!isServerControlled) return base;
@@ -2765,9 +2855,7 @@ function ResourcePage(props) {
2765
2855
  onToggleSelect: hasBulkSelect ? (index) => {
2766
2856
  const item = page.data[index];
2767
2857
  if (item) {
2768
- handleToggleSelect(
2769
- String(item.id ?? "")
2770
- );
2858
+ handleToggleSelect(keyOf(item));
2771
2859
  }
2772
2860
  } : void 0,
2773
2861
  // Supplied only while there is an open detail to close — the hook
@@ -2881,7 +2969,7 @@ function ResourcePage(props) {
2881
2969
  isLoading,
2882
2970
  hasError,
2883
2971
  isEmpty,
2884
- errorMessage,
2972
+ errorMessage: errorMessage2,
2885
2973
  onRetry: isClientMode ? () => resourceHook.list.refetch() : onRetryProp,
2886
2974
  viewMode: page.viewMode,
2887
2975
  onViewModeChange: page.setViewMode,
@@ -2946,7 +3034,7 @@ function ResourcePage(props) {
2946
3034
  items: page.data,
2947
3035
  viewMode: page.viewMode,
2948
3036
  gridCols: gridColsProp,
2949
- getKey: (item) => String(item.id ?? ""),
3037
+ getKey: keyOf,
2950
3038
  focusedIndex: keyboardNav.focusedIndex,
2951
3039
  keyboardContainerRef: keyboardNav.containerRef,
2952
3040
  renderTable: (items) => renderTableProp ? renderTableProp(items) : renderSlot(
@@ -2955,7 +3043,7 @@ function ResourcePage(props) {
2955
3043
  {
2956
3044
  items,
2957
3045
  columns: tableColumns,
2958
- getKey: (item) => String(item.id ?? ""),
3046
+ getKey: keyOf,
2959
3047
  onRowClick: detail !== "none" ? handleRowClick : void 0,
2960
3048
  selectable: !!bulkActions && bulkActions.length > 0,
2961
3049
  selected: selectedIds,