@marktiderman/genesis-core 1.0.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.
- package/dist/chunk-65SXNHWE.js +3 -0
- package/dist/chunk-65SXNHWE.js.map +1 -0
- package/dist/chunk-6V2TIN3G.js +202 -0
- package/dist/chunk-6V2TIN3G.js.map +1 -0
- package/dist/chunk-7KOLYF27.js +50 -0
- package/dist/chunk-7KOLYF27.js.map +1 -0
- package/dist/chunk-EL7YGOTY.js +3 -0
- package/dist/chunk-EL7YGOTY.js.map +1 -0
- package/dist/chunk-F4FOTIJI.js +357 -0
- package/dist/chunk-F4FOTIJI.js.map +1 -0
- package/dist/chunk-LQEICXJS.js +122 -0
- package/dist/chunk-LQEICXJS.js.map +1 -0
- package/dist/chunk-PVT6FHAO.js +21 -0
- package/dist/chunk-PVT6FHAO.js.map +1 -0
- package/dist/chunk-RYEXWQFD.js +9 -0
- package/dist/chunk-RYEXWQFD.js.map +1 -0
- package/dist/chunk-XCX2TRBL.js +3 -0
- package/dist/chunk-XCX2TRBL.js.map +1 -0
- package/dist/chunk-ZU72FMDR.js +137 -0
- package/dist/chunk-ZU72FMDR.js.map +1 -0
- package/dist/chunk-ZYJUEJ4Z.js +59 -0
- package/dist/chunk-ZYJUEJ4Z.js.map +1 -0
- package/dist/form-types.d.ts +50 -0
- package/dist/form-types.js +3 -0
- package/dist/form-types.js.map +1 -0
- package/dist/hooks/index.d.ts +8 -0
- package/dist/hooks/index.js +10 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/use-one.d.ts +18 -0
- package/dist/hooks/use-one.js +5 -0
- package/dist/hooks/use-one.js.map +1 -0
- package/dist/hooks/use-resource-form.d.ts +38 -0
- package/dist/hooks/use-resource-form.js +7 -0
- package/dist/hooks/use-resource-form.js.map +1 -0
- package/dist/hooks/use-resource.d.ts +46 -0
- package/dist/hooks/use-resource.js +5 -0
- package/dist/hooks/use-resource.js.map +1 -0
- package/dist/hooks/use-wizard.d.ts +25 -0
- package/dist/hooks/use-wizard.js +3 -0
- package/dist/hooks/use-wizard.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/provider/index.d.ts +78 -0
- package/dist/provider/index.js +5 -0
- package/dist/provider/index.js.map +1 -0
- package/dist/storage/index.d.ts +10 -0
- package/dist/storage/index.js +4 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/types-BzcZrTjl.d.ts +119 -0
- package/dist/types-DKcxHXxq.d.ts +17 -0
- package/dist/utils.d.ts +8 -0
- package/dist/utils.js +3 -0
- package/dist/utils.js.map +1 -0
- package/package.json +67 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"chunk-65SXNHWE.js"}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { createLocalStorageAdapter } from './chunk-ZYJUEJ4Z.js';
|
|
2
|
+
import { createContext, useContext, useMemo } from 'react';
|
|
3
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
4
|
+
import { jsx } from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
// src/provider/mock-provider.ts
|
|
7
|
+
function createMockProvider(datasets, options = {}) {
|
|
8
|
+
const { delay = 0 } = options;
|
|
9
|
+
const store = JSON.parse(JSON.stringify(datasets));
|
|
10
|
+
let nextId = 1;
|
|
11
|
+
function getTable(resource) {
|
|
12
|
+
if (!store[resource]) {
|
|
13
|
+
store[resource] = [];
|
|
14
|
+
}
|
|
15
|
+
return store[resource];
|
|
16
|
+
}
|
|
17
|
+
async function withDelay(value) {
|
|
18
|
+
if (delay > 0) {
|
|
19
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
function matchesFilter(row, filter) {
|
|
24
|
+
const val = row[filter.field];
|
|
25
|
+
switch (filter.operator) {
|
|
26
|
+
case "eq":
|
|
27
|
+
return val === filter.value;
|
|
28
|
+
case "neq":
|
|
29
|
+
return val !== filter.value;
|
|
30
|
+
case "gt":
|
|
31
|
+
return val > filter.value;
|
|
32
|
+
case "lt":
|
|
33
|
+
return val < filter.value;
|
|
34
|
+
case "gte":
|
|
35
|
+
return val >= filter.value;
|
|
36
|
+
case "lte":
|
|
37
|
+
return val <= filter.value;
|
|
38
|
+
case "contains":
|
|
39
|
+
return typeof val === "string" && val.toLowerCase().includes(String(filter.value).toLowerCase());
|
|
40
|
+
case "in":
|
|
41
|
+
return filter.value.includes(val);
|
|
42
|
+
case "is_null":
|
|
43
|
+
return val === null || val === void 0;
|
|
44
|
+
case "is_not_null":
|
|
45
|
+
return val !== null && val !== void 0;
|
|
46
|
+
case "between": {
|
|
47
|
+
const [lo, hi] = filter.value;
|
|
48
|
+
return val >= lo && val <= hi;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function matchesSearch(row, search) {
|
|
53
|
+
const q = search.query.toLowerCase();
|
|
54
|
+
return search.fields.some((field) => {
|
|
55
|
+
const val = row[field];
|
|
56
|
+
return typeof val === "string" && val.toLowerCase().includes(q);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
function compareValues(a, b) {
|
|
60
|
+
if (typeof a === "string" && typeof b === "string") {
|
|
61
|
+
return a.localeCompare(b, void 0, { numeric: true });
|
|
62
|
+
}
|
|
63
|
+
if (typeof a === "number" && typeof b === "number") {
|
|
64
|
+
return a - b;
|
|
65
|
+
}
|
|
66
|
+
return String(a ?? "").localeCompare(String(b ?? ""), void 0, {
|
|
67
|
+
numeric: true
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
function applyListParams(table, params) {
|
|
71
|
+
let rows = [...table];
|
|
72
|
+
if (params.filters) {
|
|
73
|
+
for (const f of params.filters) {
|
|
74
|
+
rows = rows.filter((r) => matchesFilter(r, f));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (params.search && params.search.query) {
|
|
78
|
+
rows = rows.filter((r) => matchesSearch(r, params.search));
|
|
79
|
+
}
|
|
80
|
+
const total = rows.length;
|
|
81
|
+
if (params.sort) {
|
|
82
|
+
const { field, order } = params.sort;
|
|
83
|
+
rows.sort((a, b) => {
|
|
84
|
+
const cmp = compareValues(a[field], b[field]);
|
|
85
|
+
return order === "desc" ? -cmp : cmp;
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
if (params.pagination) {
|
|
89
|
+
const { page, perPage } = params.pagination;
|
|
90
|
+
const start = (page - 1) * perPage;
|
|
91
|
+
rows = rows.slice(start, start + perPage);
|
|
92
|
+
}
|
|
93
|
+
return { data: rows, total };
|
|
94
|
+
}
|
|
95
|
+
const provider = {
|
|
96
|
+
async getList(_resource, params) {
|
|
97
|
+
const table = getTable(_resource);
|
|
98
|
+
const result = applyListParams(table, params);
|
|
99
|
+
return withDelay(result);
|
|
100
|
+
},
|
|
101
|
+
async getOne(_resource, params) {
|
|
102
|
+
const table = getTable(_resource);
|
|
103
|
+
const row = table.find((r) => String(r.id) === String(params.id));
|
|
104
|
+
if (!row) {
|
|
105
|
+
throw new Error(
|
|
106
|
+
`[MockProvider] Record not found: ${_resource}#${params.id}`
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
return withDelay({ data: JSON.parse(JSON.stringify(row)) });
|
|
110
|
+
},
|
|
111
|
+
async getMany(_resource, params) {
|
|
112
|
+
const table = getTable(_resource);
|
|
113
|
+
const idSet = new Set(params.ids.map(String));
|
|
114
|
+
const rows = table.filter((r) => idSet.has(String(r.id)));
|
|
115
|
+
return withDelay({ data: JSON.parse(JSON.stringify(rows)) });
|
|
116
|
+
},
|
|
117
|
+
async create(_resource, params) {
|
|
118
|
+
const table = getTable(_resource);
|
|
119
|
+
const id = String(nextId++);
|
|
120
|
+
const row = { id, ...params.data };
|
|
121
|
+
table.push(row);
|
|
122
|
+
return withDelay({ data: JSON.parse(JSON.stringify(row)) });
|
|
123
|
+
},
|
|
124
|
+
async update(_resource, params) {
|
|
125
|
+
const table = getTable(_resource);
|
|
126
|
+
const idx = table.findIndex((r) => String(r.id) === String(params.id));
|
|
127
|
+
if (idx === -1) {
|
|
128
|
+
throw new Error(
|
|
129
|
+
`[MockProvider] Record not found: ${_resource}#${params.id}`
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
table[idx] = { ...table[idx], ...params.data };
|
|
133
|
+
return withDelay({ data: JSON.parse(JSON.stringify(table[idx])) });
|
|
134
|
+
},
|
|
135
|
+
async deleteOne(_resource, params) {
|
|
136
|
+
const table = getTable(_resource);
|
|
137
|
+
const idx = table.findIndex((r) => String(r.id) === String(params.id));
|
|
138
|
+
if (idx === -1) {
|
|
139
|
+
throw new Error(
|
|
140
|
+
`[MockProvider] Record not found: ${_resource}#${params.id}`
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
table.splice(idx, 1);
|
|
144
|
+
return withDelay({ data: { id: params.id } });
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
return provider;
|
|
148
|
+
}
|
|
149
|
+
var DataProviderContext = createContext(null);
|
|
150
|
+
var StorageContext = createContext(createLocalStorageAdapter());
|
|
151
|
+
function DataProviderRoot({ provider, children }) {
|
|
152
|
+
return /* @__PURE__ */ jsx(DataProviderContext.Provider, { value: provider, children });
|
|
153
|
+
}
|
|
154
|
+
function useDataProvider() {
|
|
155
|
+
const ctx = useContext(DataProviderContext);
|
|
156
|
+
if (!ctx) {
|
|
157
|
+
throw new Error(
|
|
158
|
+
"useDataProvider must be used inside <DataProviderRoot> or <GenesisProvider>. Wrap your app (or the relevant subtree) in one of these providers."
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
return ctx;
|
|
162
|
+
}
|
|
163
|
+
function useStorage() {
|
|
164
|
+
return useContext(StorageContext);
|
|
165
|
+
}
|
|
166
|
+
var defaultStorage = createLocalStorageAdapter();
|
|
167
|
+
function GenesisProvider({
|
|
168
|
+
children,
|
|
169
|
+
provider,
|
|
170
|
+
mock,
|
|
171
|
+
storage
|
|
172
|
+
}) {
|
|
173
|
+
if (provider && mock) {
|
|
174
|
+
if (process.env.NODE_ENV !== "production") {
|
|
175
|
+
console.warn("[GenesisProvider] Both `provider` and `mock` were passed. `provider` takes precedence. This is likely a mistake.");
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const resolvedProvider = useMemo(() => {
|
|
179
|
+
if (provider) return provider;
|
|
180
|
+
if (mock) return createMockProvider(mock.datasets, mock.options);
|
|
181
|
+
throw new Error(
|
|
182
|
+
"<GenesisProvider> requires either a `provider` or `mock` prop."
|
|
183
|
+
);
|
|
184
|
+
}, [provider, mock]);
|
|
185
|
+
const queryClient = useMemo(
|
|
186
|
+
() => new QueryClient({
|
|
187
|
+
defaultOptions: {
|
|
188
|
+
queries: {
|
|
189
|
+
staleTime: 5 * 60 * 1e3
|
|
190
|
+
// 5 minutes
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}),
|
|
194
|
+
[]
|
|
195
|
+
);
|
|
196
|
+
const resolvedStorage = storage ?? defaultStorage;
|
|
197
|
+
return /* @__PURE__ */ jsx(QueryClientProvider, { client: queryClient, children: /* @__PURE__ */ jsx(StorageContext.Provider, { value: resolvedStorage, children: /* @__PURE__ */ jsx(DataProviderRoot, { provider: resolvedProvider, children }) }) });
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export { DataProviderRoot, GenesisProvider, createMockProvider, useDataProvider, useStorage };
|
|
201
|
+
//# sourceMappingURL=chunk-6V2TIN3G.js.map
|
|
202
|
+
//# sourceMappingURL=chunk-6V2TIN3G.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/provider/mock-provider.ts","../src/provider/context.tsx"],"names":[],"mappings":"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","file":"chunk-6V2TIN3G.js","sourcesContent":["import type {\n DataProvider,\n ListParams,\n FilterParam,\n} from \"./types\";\n\n// ---------------------------------------------------------------------------\n// Mock DataProvider — operates on in-memory arrays, deep-cloned on creation\n// ---------------------------------------------------------------------------\n\nexport interface MockProviderOptions {\n /** Simulated latency in ms (default 0). */\n delay?: number;\n}\n\ntype Row = Record<string, unknown>;\n\nexport function createMockProvider(\n datasets: Record<string, Row[]>,\n options: MockProviderOptions = {},\n): DataProvider {\n const { delay = 0 } = options;\n\n // Deep clone so mutations don't affect the original data.\n const store: Record<string, Row[]> = JSON.parse(JSON.stringify(datasets));\n\n // Instance-scoped ID counter (NOT module-level).\n let nextId = 1;\n\n // ---------------------------------------------------------------------------\n // Helpers\n // ---------------------------------------------------------------------------\n\n function getTable(resource: string): Row[] {\n if (!store[resource]) {\n store[resource] = [];\n }\n return store[resource];\n }\n\n async function withDelay<T>(value: T): Promise<T> {\n if (delay > 0) {\n await new Promise((r) => setTimeout(r, delay));\n }\n return value;\n }\n\n function matchesFilter(row: Row, filter: FilterParam): boolean {\n const val = row[filter.field];\n\n switch (filter.operator) {\n case \"eq\":\n return val === filter.value;\n case \"neq\":\n return val !== filter.value;\n case \"gt\":\n return (val as number) > (filter.value as number);\n case \"lt\":\n return (val as number) < (filter.value as number);\n case \"gte\":\n return (val as number) >= (filter.value as number);\n case \"lte\":\n return (val as number) <= (filter.value as number);\n case \"contains\":\n return typeof val === \"string\" &&\n val.toLowerCase().includes(String(filter.value).toLowerCase());\n case \"in\":\n return filter.value.includes(val as string | number | boolean);\n case \"is_null\":\n return val === null || val === undefined;\n case \"is_not_null\":\n return val !== null && val !== undefined;\n case \"between\": {\n const [lo, hi] = filter.value;\n return (val as number) >= (lo as number) && (val as number) <= (hi as number);\n }\n }\n }\n\n function matchesSearch(\n row: Row,\n search: { query: string; fields: string[] },\n ): boolean {\n const q = search.query.toLowerCase();\n return search.fields.some((field) => {\n const val = row[field];\n return typeof val === \"string\" && val.toLowerCase().includes(q);\n });\n }\n\n function compareValues(a: unknown, b: unknown): number {\n if (typeof a === \"string\" && typeof b === \"string\") {\n return a.localeCompare(b, undefined, { numeric: true });\n }\n if (typeof a === \"number\" && typeof b === \"number\") {\n return a - b;\n }\n // Fallback: coerce to string.\n return String(a ?? \"\").localeCompare(String(b ?? \"\"), undefined, {\n numeric: true,\n });\n }\n\n function applyListParams(table: Row[], params: ListParams): { data: Row[]; total: number } {\n let rows = [...table];\n\n // Filters (AND).\n if (params.filters) {\n for (const f of params.filters) {\n rows = rows.filter((r) => matchesFilter(r, f));\n }\n }\n\n // Search (OR across fields, AND with filters).\n if (params.search && params.search.query) {\n rows = rows.filter((r) => matchesSearch(r, params.search!));\n }\n\n const total = rows.length;\n\n // Sort.\n if (params.sort) {\n const { field, order } = params.sort;\n rows.sort((a, b) => {\n const cmp = compareValues(a[field], b[field]);\n return order === \"desc\" ? -cmp : cmp;\n });\n }\n\n // Pagination (offset-based).\n if (params.pagination) {\n const { page, perPage } = params.pagination;\n const start = (page - 1) * perPage;\n rows = rows.slice(start, start + perPage);\n }\n\n return { data: rows, total };\n }\n\n // ---------------------------------------------------------------------------\n // DataProvider implementation\n // ---------------------------------------------------------------------------\n\n const provider: DataProvider = {\n async getList(_resource, params) {\n const table = getTable(_resource);\n const result = applyListParams(table, params);\n return withDelay(result) as never;\n },\n\n async getOne(_resource, params) {\n const table = getTable(_resource);\n const row = table.find((r) => String(r.id) === String(params.id));\n if (!row) {\n throw new Error(\n `[MockProvider] Record not found: ${_resource}#${params.id}`,\n );\n }\n return withDelay({ data: JSON.parse(JSON.stringify(row)) }) as never;\n },\n\n async getMany(_resource, params) {\n const table = getTable(_resource);\n const idSet = new Set(params.ids.map(String));\n const rows = table.filter((r) => idSet.has(String(r.id)));\n return withDelay({ data: JSON.parse(JSON.stringify(rows)) }) as never;\n },\n\n async create(_resource, params) {\n const table = getTable(_resource);\n const id = String(nextId++);\n const row = { id, ...params.data } as Row;\n table.push(row);\n return withDelay({ data: JSON.parse(JSON.stringify(row)) }) as never;\n },\n\n async update(_resource, params) {\n const table = getTable(_resource);\n const idx = table.findIndex((r) => String(r.id) === String(params.id));\n if (idx === -1) {\n throw new Error(\n `[MockProvider] Record not found: ${_resource}#${params.id}`,\n );\n }\n table[idx] = { ...table[idx], ...params.data };\n return withDelay({ data: JSON.parse(JSON.stringify(table[idx])) }) as never;\n },\n\n async deleteOne(_resource, params) {\n const table = getTable(_resource);\n const idx = table.findIndex((r) => String(r.id) === String(params.id));\n if (idx === -1) {\n throw new Error(\n `[MockProvider] Record not found: ${_resource}#${params.id}`,\n );\n }\n table.splice(idx, 1);\n return withDelay({ data: { id: params.id } });\n },\n };\n\n return provider;\n}\n","import {\n createContext,\n useContext,\n useMemo,\n type ReactNode,\n} from \"react\";\nimport { QueryClient, QueryClientProvider } from \"@tanstack/react-query\";\nimport type { DataProvider } from \"./types\";\nimport { createMockProvider, type MockProviderOptions } from \"./mock-provider\";\nimport type { StorageAdapter } from \"../storage/types\";\nimport { createLocalStorageAdapter } from \"../storage/adapters\";\n\n// ---------------------------------------------------------------------------\n// Context\n// ---------------------------------------------------------------------------\n\nconst DataProviderContext = createContext<DataProvider | null>(null);\nconst StorageContext = createContext<StorageAdapter>(createLocalStorageAdapter());\n\n// ---------------------------------------------------------------------------\n// DataProviderRoot — thin context wrapper\n// ---------------------------------------------------------------------------\n\nexport interface DataProviderRootProps {\n provider: DataProvider;\n children: ReactNode;\n}\n\nexport function DataProviderRoot({ provider, children }: DataProviderRootProps) {\n return (\n <DataProviderContext.Provider value={provider}>\n {children}\n </DataProviderContext.Provider>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\nexport function useDataProvider(): DataProvider {\n const ctx = useContext(DataProviderContext);\n if (!ctx) {\n throw new Error(\n \"useDataProvider must be used inside <DataProviderRoot> or <GenesisProvider>. \" +\n \"Wrap your app (or the relevant subtree) in one of these providers.\",\n );\n }\n return ctx;\n}\n\nexport function useStorage(): StorageAdapter {\n return useContext(StorageContext);\n}\n\n// ---------------------------------------------------------------------------\n// GenesisProvider — convenience wrapper\n// ---------------------------------------------------------------------------\n\ntype Row = Record<string, unknown>;\n\nexport type GenesisProviderProps =\n | {\n children: ReactNode;\n /** Pass a ready-made DataProvider. Mutually exclusive with `mock`. */\n provider: DataProvider;\n mock?: never;\n /** Platform-specific storage adapter. Defaults to localStorage (SSR-safe). */\n storage?: StorageAdapter;\n }\n | {\n children: ReactNode;\n provider?: never;\n /** Pass mock datasets. Mutually exclusive with `provider`. */\n mock: {\n datasets: Record<string, Row[]>;\n options?: MockProviderOptions;\n };\n /** Platform-specific storage adapter. Defaults to localStorage (SSR-safe). */\n storage?: StorageAdapter;\n };\n\nconst defaultStorage = createLocalStorageAdapter();\n\nexport function GenesisProvider({\n children,\n provider,\n mock,\n storage,\n}: GenesisProviderProps) {\n if (provider && mock) {\n if (process.env.NODE_ENV !== \"production\") {\n console.warn(\"[GenesisProvider] Both `provider` and `mock` were passed. `provider` takes precedence. This is likely a mistake.\");\n }\n }\n\n // Build provider from mock datasets when no explicit provider is given.\n const resolvedProvider = useMemo(() => {\n if (provider) return provider;\n if (mock) return createMockProvider(mock.datasets, mock.options);\n throw new Error(\n \"<GenesisProvider> requires either a `provider` or `mock` prop.\",\n );\n }, [provider, mock]);\n\n // Internal QueryClient with sensible defaults.\n const queryClient = useMemo(\n () =>\n new QueryClient({\n defaultOptions: {\n queries: {\n staleTime: 5 * 60 * 1000, // 5 minutes\n },\n },\n }),\n [],\n );\n\n const resolvedStorage = storage ?? defaultStorage;\n\n return (\n <QueryClientProvider client={queryClient}>\n <StorageContext.Provider value={resolvedStorage}>\n <DataProviderRoot provider={resolvedProvider}>\n {children}\n </DataProviderRoot>\n </StorageContext.Provider>\n </QueryClientProvider>\n );\n}\n"]}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { useState, useCallback, useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
// src/hooks/use-wizard.ts
|
|
4
|
+
function useWizard(steps, form) {
|
|
5
|
+
const [currentStep, setCurrentStep] = useState(0);
|
|
6
|
+
const totalSteps = steps.length;
|
|
7
|
+
const emptyStep = { id: "empty", title: "No steps defined", fields: [] };
|
|
8
|
+
const currentStepDef = steps[currentStep] ?? steps[0] ?? emptyStep;
|
|
9
|
+
const isFirst = currentStep === 0;
|
|
10
|
+
const isLast = currentStep === totalSteps - 1;
|
|
11
|
+
const progress = totalSteps > 1 ? (currentStep + 1) / totalSteps : 1;
|
|
12
|
+
const next = useCallback(async () => {
|
|
13
|
+
const currentFields = steps[currentStep]?.fields ?? [];
|
|
14
|
+
const valid = await form.trigger(currentFields);
|
|
15
|
+
if (valid && currentStep < steps.length - 1) {
|
|
16
|
+
setCurrentStep((s) => s + 1);
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
return valid;
|
|
20
|
+
}, [currentStep, steps, form]);
|
|
21
|
+
const back = useCallback(() => {
|
|
22
|
+
setCurrentStep((s) => s > 0 ? s - 1 : s);
|
|
23
|
+
}, []);
|
|
24
|
+
const goTo = useCallback(
|
|
25
|
+
(step) => {
|
|
26
|
+
if (step >= 0 && step < totalSteps) {
|
|
27
|
+
setCurrentStep(step);
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
[totalSteps]
|
|
31
|
+
);
|
|
32
|
+
return useMemo(
|
|
33
|
+
() => ({
|
|
34
|
+
currentStep,
|
|
35
|
+
currentStepDef,
|
|
36
|
+
totalSteps,
|
|
37
|
+
progress,
|
|
38
|
+
isFirst,
|
|
39
|
+
isLast,
|
|
40
|
+
next,
|
|
41
|
+
back,
|
|
42
|
+
goTo
|
|
43
|
+
}),
|
|
44
|
+
[currentStep, currentStepDef, totalSteps, progress, isFirst, isLast, next, back, goTo]
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export { useWizard };
|
|
49
|
+
//# sourceMappingURL=chunk-7KOLYF27.js.map
|
|
50
|
+
//# sourceMappingURL=chunk-7KOLYF27.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/hooks/use-wizard.ts"],"names":[],"mappings":";;;AAiCO,SAAS,SAAA,CACd,OACA,IAAA,EACiB;AACjB,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAI,SAAS,CAAC,CAAA;AAEhD,EAAA,MAAM,aAAa,KAAA,CAAM,MAAA;AACzB,EAAA,MAAM,SAAA,GAAwB,EAAE,EAAA,EAAI,OAAA,EAAS,OAAO,kBAAA,EAAoB,MAAA,EAAQ,EAAC,EAAE;AACnF,EAAA,MAAM,iBAAiB,KAAA,CAAM,WAAW,CAAA,IAAK,KAAA,CAAM,CAAC,CAAA,IAAK,SAAA;AACzD,EAAA,MAAM,UAAU,WAAA,KAAgB,CAAA;AAChC,EAAA,MAAM,MAAA,GAAS,gBAAgB,UAAA,GAAa,CAAA;AAC5C,EAAA,MAAM,QAAA,GAAW,UAAA,GAAa,CAAA,GAAA,CAAK,WAAA,GAAc,KAAK,UAAA,GAAa,CAAA;AAEnE,EAAA,MAAM,IAAA,GAAO,YAAY,YAAY;AACnC,IAAA,MAAM,aAAA,GAAgB,KAAA,CAAM,WAAW,CAAA,EAAG,UAAU,EAAC;AACrD,IAAA,MAAM,KAAA,GAAQ,MAAM,IAAA,CAAK,OAAA,CAAQ,aAAmD,CAAA;AACpF,IAAA,IAAI,KAAA,IAAS,WAAA,GAAc,KAAA,CAAM,MAAA,GAAS,CAAA,EAAG;AAC3C,MAAA,cAAA,CAAe,CAAC,CAAA,KAAM,CAAA,GAAI,CAAC,CAAA;AAC3B,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,OAAO,KAAA;AAAA,EAET,CAAA,EAAG,CAAC,WAAA,EAAa,KAAA,EAAO,IAAI,CAAC,CAAA;AAE7B,EAAA,MAAM,IAAA,GAAO,YAAY,MAAM;AAC7B,IAAA,cAAA,CAAe,CAAC,CAAA,KAAO,CAAA,GAAI,CAAA,GAAI,CAAA,GAAI,IAAI,CAAE,CAAA;AAAA,EAC3C,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,IAAA,GAAO,WAAA;AAAA,IACX,CAAC,IAAA,KAAiB;AAChB,MAAA,IAAI,IAAA,IAAQ,CAAA,IAAK,IAAA,GAAO,UAAA,EAAY;AAClC,QAAA,cAAA,CAAe,IAAI,CAAA;AAAA,MACrB;AAAA,IACF,CAAA;AAAA,IACA,CAAC,UAAU;AAAA,GACb;AAEA,EAAA,OAAO,OAAA;AAAA,IACL,OAAO;AAAA,MACL,WAAA;AAAA,MACA,cAAA;AAAA,MACA,UAAA;AAAA,MACA,QAAA;AAAA,MACA,OAAA;AAAA,MACA,MAAA;AAAA,MACA,IAAA;AAAA,MACA,IAAA;AAAA,MACA;AAAA,KACF,CAAA;AAAA,IACA,CAAC,aAAa,cAAA,EAAgB,UAAA,EAAY,UAAU,OAAA,EAAS,MAAA,EAAQ,IAAA,EAAM,IAAA,EAAM,IAAI;AAAA,GACvF;AACF","file":"chunk-7KOLYF27.js","sourcesContent":["import { useState, useCallback, useMemo } from \"react\";\nimport type { UseFormReturn } from \"react-hook-form\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface WizardStep {\n id: string;\n title: string;\n description?: string;\n /** Field keys belonging to this step. */\n fields: string[];\n}\n\nexport interface UseWizardReturn {\n currentStep: number;\n currentStepDef: WizardStep;\n totalSteps: number;\n /** Progress from 0 to 1. */\n progress: number;\n isFirst: boolean;\n isLast: boolean;\n /** Validate current step fields, advance if valid. Returns true if valid. */\n next: () => Promise<boolean>;\n back: () => void;\n goTo: (step: number) => void;\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\nexport function useWizard(\n steps: WizardStep[],\n form: UseFormReturn<Record<string, unknown>>,\n): UseWizardReturn {\n const [currentStep, setCurrentStep] = useState(0);\n\n const totalSteps = steps.length;\n const emptyStep: WizardStep = { id: \"empty\", title: \"No steps defined\", fields: [] };\n const currentStepDef = steps[currentStep] ?? steps[0] ?? emptyStep;\n const isFirst = currentStep === 0;\n const isLast = currentStep === totalSteps - 1;\n const progress = totalSteps > 1 ? (currentStep + 1) / totalSteps : 1;\n\n const next = useCallback(async () => {\n const currentFields = steps[currentStep]?.fields ?? [];\n const valid = await form.trigger(currentFields as Parameters<typeof form.trigger>[0]);\n if (valid && currentStep < steps.length - 1) {\n setCurrentStep((s) => s + 1);\n return true;\n }\n return valid;\n // currentStep is needed here (not just in the setter) for the guard check and field lookup\n }, [currentStep, steps, form]);\n\n const back = useCallback(() => {\n setCurrentStep((s) => (s > 0 ? s - 1 : s));\n }, []);\n\n const goTo = useCallback(\n (step: number) => {\n if (step >= 0 && step < totalSteps) {\n setCurrentStep(step);\n }\n },\n [totalSteps],\n );\n\n return useMemo(\n () => ({\n currentStep,\n currentStepDef,\n totalSteps,\n progress,\n isFirst,\n isLast,\n next,\n back,\n goTo,\n }),\n [currentStep, currentStepDef, totalSteps, progress, isFirst, isLast, next, back, goTo],\n );\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"chunk-EL7YGOTY.js"}
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import { titleCase } from './chunk-RYEXWQFD.js';
|
|
2
|
+
import { useOne } from './chunk-PVT6FHAO.js';
|
|
3
|
+
import { useDataProvider, useStorage } from './chunk-6V2TIN3G.js';
|
|
4
|
+
import { useMemo, useEffect, useCallback } from 'react';
|
|
5
|
+
import { useForm } from 'react-hook-form';
|
|
6
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { useQueryClient } from '@tanstack/react-query';
|
|
9
|
+
|
|
10
|
+
function inferFieldType(value) {
|
|
11
|
+
if (typeof value === "boolean") return "switch";
|
|
12
|
+
if (typeof value === "number") return "number";
|
|
13
|
+
if (typeof value === "string") {
|
|
14
|
+
if (/^\d{4}-\d{2}-\d{2}/.test(value)) return "date";
|
|
15
|
+
if (value.includes("@") && value.includes(".")) return "email";
|
|
16
|
+
if (/^https?:\/\//.test(value)) return "url";
|
|
17
|
+
if (value.length > 100) return "textarea";
|
|
18
|
+
}
|
|
19
|
+
return "text";
|
|
20
|
+
}
|
|
21
|
+
function inferSelectFromData(key, allData) {
|
|
22
|
+
if (!allData || allData.length === 0) return null;
|
|
23
|
+
const uniqueValues = /* @__PURE__ */ new Set();
|
|
24
|
+
for (const row of allData) {
|
|
25
|
+
const v = row[key];
|
|
26
|
+
if (typeof v === "string" && v.length > 0) uniqueValues.add(v);
|
|
27
|
+
}
|
|
28
|
+
if (uniqueValues.size > 0 && uniqueValues.size <= 10) {
|
|
29
|
+
return {
|
|
30
|
+
type: "select",
|
|
31
|
+
options: Array.from(uniqueValues).sort().map((v) => ({ value: v, label: v }))
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
function defaultPlaceholder(key, type) {
|
|
37
|
+
switch (type) {
|
|
38
|
+
case "email":
|
|
39
|
+
return "name@example.com";
|
|
40
|
+
case "url":
|
|
41
|
+
return "https://...";
|
|
42
|
+
case "number":
|
|
43
|
+
return "0";
|
|
44
|
+
case "date":
|
|
45
|
+
return "YYYY-MM-DD";
|
|
46
|
+
case "textarea":
|
|
47
|
+
return `Enter ${titleCase(key).toLowerCase()}...`;
|
|
48
|
+
default:
|
|
49
|
+
return `Enter ${titleCase(key).toLowerCase()}`;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function resolveFields(fields, data, allData) {
|
|
53
|
+
if (fields && fields.length > 0) {
|
|
54
|
+
return fields.map((f) => {
|
|
55
|
+
const inferred = f.type ?? (data ? inferFieldType(data[f.key]) : "text");
|
|
56
|
+
return {
|
|
57
|
+
...f,
|
|
58
|
+
label: f.label ?? titleCase(f.key),
|
|
59
|
+
type: inferred,
|
|
60
|
+
placeholder: f.placeholder ?? defaultPlaceholder(f.key, inferred),
|
|
61
|
+
required: f.required ?? (inferred !== "switch" && inferred !== "checkbox" && inferred !== "hidden")
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
if (!data) return [];
|
|
66
|
+
return Object.keys(data).filter((key) => key !== "id" && key !== "created_at" && key !== "updated_at").map((key) => {
|
|
67
|
+
if (typeof data[key] === "string") {
|
|
68
|
+
const selectInfo = inferSelectFromData(key, allData);
|
|
69
|
+
if (selectInfo) {
|
|
70
|
+
return {
|
|
71
|
+
key,
|
|
72
|
+
label: titleCase(key),
|
|
73
|
+
type: selectInfo.type,
|
|
74
|
+
options: selectInfo.options,
|
|
75
|
+
placeholder: `Select ${titleCase(key).toLowerCase()}...`,
|
|
76
|
+
required: true
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const inferred = inferFieldType(data[key]);
|
|
81
|
+
return {
|
|
82
|
+
key,
|
|
83
|
+
label: titleCase(key),
|
|
84
|
+
type: inferred,
|
|
85
|
+
placeholder: defaultPlaceholder(key, inferred),
|
|
86
|
+
required: inferred !== "switch" && inferred !== "checkbox" && inferred !== "hidden"
|
|
87
|
+
};
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
function buildSchema(fields) {
|
|
91
|
+
const shape = {};
|
|
92
|
+
for (const field of fields) {
|
|
93
|
+
const fieldType = field.type ?? "text";
|
|
94
|
+
if (field.hidden || fieldType === "hidden") continue;
|
|
95
|
+
if (fieldType === "file" || fieldType === "image") {
|
|
96
|
+
shape[field.key] = field.required !== false ? z.unknown().refine((v) => v != null, { message: `${field.label ?? titleCase(field.key)} is required` }) : z.unknown().optional();
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
switch (fieldType) {
|
|
100
|
+
case "number": {
|
|
101
|
+
let schema = z.coerce.number({ message: "Must be a number" });
|
|
102
|
+
if (field.min != null) schema = schema.min(field.min);
|
|
103
|
+
if (field.max != null) schema = schema.max(field.max);
|
|
104
|
+
shape[field.key] = field.required !== false ? schema : schema.optional();
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
case "array": {
|
|
108
|
+
if (field.arrayFields) {
|
|
109
|
+
const itemShape = {};
|
|
110
|
+
for (const sub of field.arrayFields) {
|
|
111
|
+
const subType = sub.type ?? "text";
|
|
112
|
+
if (subType === "number") {
|
|
113
|
+
let s = z.coerce.number({ message: "Must be a number" });
|
|
114
|
+
if (sub.min != null) s = s.min(sub.min);
|
|
115
|
+
if (sub.max != null) s = s.max(sub.max);
|
|
116
|
+
itemShape[sub.key] = sub.required !== false ? s : s.optional();
|
|
117
|
+
} else if (subType === "switch" || subType === "checkbox") {
|
|
118
|
+
itemShape[sub.key] = z.boolean().optional();
|
|
119
|
+
} else {
|
|
120
|
+
let s = z.string();
|
|
121
|
+
if (sub.required !== false) {
|
|
122
|
+
s = s.min(1, `${sub.label ?? titleCase(sub.key)} is required`);
|
|
123
|
+
}
|
|
124
|
+
if (subType === "email") s = s.email("Invalid email address");
|
|
125
|
+
if (subType === "url") s = s.url("Invalid URL");
|
|
126
|
+
itemShape[sub.key] = sub.required === false ? s.optional().or(z.literal("")) : s;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
let arraySchema = z.array(z.object(itemShape));
|
|
130
|
+
if (field.arrayMin != null) arraySchema = arraySchema.min(field.arrayMin, `Minimum ${field.arrayMin} items required`);
|
|
131
|
+
if (field.arrayMax != null) arraySchema = arraySchema.max(field.arrayMax, `Maximum ${field.arrayMax} items allowed`);
|
|
132
|
+
shape[field.key] = arraySchema;
|
|
133
|
+
}
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
case "switch":
|
|
137
|
+
case "checkbox": {
|
|
138
|
+
shape[field.key] = z.boolean().optional();
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
default: {
|
|
142
|
+
let schema = z.string();
|
|
143
|
+
if (field.required !== false) {
|
|
144
|
+
schema = schema.min(1, `${field.label ?? titleCase(field.key)} is required`);
|
|
145
|
+
}
|
|
146
|
+
if (fieldType === "email") {
|
|
147
|
+
schema = schema.email("Invalid email address");
|
|
148
|
+
}
|
|
149
|
+
if (fieldType === "url") {
|
|
150
|
+
schema = schema.url("Invalid URL");
|
|
151
|
+
}
|
|
152
|
+
shape[field.key] = field.required === false ? schema.optional().or(z.literal("")) : schema;
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return z.object(shape).passthrough();
|
|
158
|
+
}
|
|
159
|
+
function buildArrayItemDefaults(arrayFields) {
|
|
160
|
+
const defaults = {};
|
|
161
|
+
for (const sub of arrayFields) {
|
|
162
|
+
const subType = sub.type ?? "text";
|
|
163
|
+
if (subType === "number") defaults[sub.key] = void 0;
|
|
164
|
+
else if (subType === "switch" || subType === "checkbox") defaults[sub.key] = false;
|
|
165
|
+
else defaults[sub.key] = sub.defaultValue ?? "";
|
|
166
|
+
}
|
|
167
|
+
return defaults;
|
|
168
|
+
}
|
|
169
|
+
function buildDefaults(fields, data, action, prefillFromUrl) {
|
|
170
|
+
const defaults = {};
|
|
171
|
+
for (const field of fields) {
|
|
172
|
+
if (data && field.key in data) {
|
|
173
|
+
defaults[field.key] = data[field.key];
|
|
174
|
+
} else if (field.defaultValue !== void 0) {
|
|
175
|
+
defaults[field.key] = field.defaultValue;
|
|
176
|
+
} else {
|
|
177
|
+
const fieldType = field.type ?? "text";
|
|
178
|
+
switch (fieldType) {
|
|
179
|
+
case "array":
|
|
180
|
+
defaults[field.key] = field.arrayMin ? Array.from(
|
|
181
|
+
{ length: field.arrayMin },
|
|
182
|
+
() => buildArrayItemDefaults(field.arrayFields ?? [])
|
|
183
|
+
) : [];
|
|
184
|
+
break;
|
|
185
|
+
case "number":
|
|
186
|
+
defaults[field.key] = void 0;
|
|
187
|
+
break;
|
|
188
|
+
case "switch":
|
|
189
|
+
case "checkbox":
|
|
190
|
+
defaults[field.key] = false;
|
|
191
|
+
break;
|
|
192
|
+
default:
|
|
193
|
+
defaults[field.key] = "";
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (typeof window !== "undefined" && typeof window.location !== "undefined" && typeof window.location.search === "string" && action === "create" && prefillFromUrl !== false) {
|
|
199
|
+
const params = new URLSearchParams(window.location.search);
|
|
200
|
+
for (const field of fields) {
|
|
201
|
+
const val = params.get(field.key);
|
|
202
|
+
if (val != null) {
|
|
203
|
+
defaults[field.key] = field.type === "number" ? Number(val) : val;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return defaults;
|
|
208
|
+
}
|
|
209
|
+
function useResourceForm(options) {
|
|
210
|
+
const {
|
|
211
|
+
resource,
|
|
212
|
+
action,
|
|
213
|
+
id,
|
|
214
|
+
initialData,
|
|
215
|
+
fields: fieldsProp,
|
|
216
|
+
listData,
|
|
217
|
+
onSuccess,
|
|
218
|
+
onError,
|
|
219
|
+
prefillFromUrl = true,
|
|
220
|
+
validationMode,
|
|
221
|
+
autosave
|
|
222
|
+
} = options;
|
|
223
|
+
const autosaveEnabled = !!autosave;
|
|
224
|
+
const debounceMs = typeof autosave === "object" ? autosave.debounceMs ?? 1e3 : 1e3;
|
|
225
|
+
const provider = useDataProvider();
|
|
226
|
+
const storage = useStorage();
|
|
227
|
+
const queryClient = useQueryClient();
|
|
228
|
+
const needsFetch = action === "edit" && !initialData && !!id;
|
|
229
|
+
const fetchResult = useOne(
|
|
230
|
+
resource,
|
|
231
|
+
needsFetch ? id : void 0,
|
|
232
|
+
{ enabled: needsFetch }
|
|
233
|
+
);
|
|
234
|
+
const fetchedData = fetchResult.data ?? void 0;
|
|
235
|
+
const effectiveData = initialData ?? fetchedData;
|
|
236
|
+
const isLoading = needsFetch && fetchResult.isLoading;
|
|
237
|
+
const resolvedFields = useMemo(
|
|
238
|
+
() => resolveFields(fieldsProp, effectiveData, listData),
|
|
239
|
+
[fieldsProp, effectiveData, listData]
|
|
240
|
+
);
|
|
241
|
+
const schema = useMemo(
|
|
242
|
+
() => buildSchema(resolvedFields),
|
|
243
|
+
[resolvedFields]
|
|
244
|
+
);
|
|
245
|
+
const defaultValues = useMemo(() => {
|
|
246
|
+
const defaults = buildDefaults(resolvedFields, effectiveData, action, prefillFromUrl);
|
|
247
|
+
if (autosaveEnabled && action === "create") {
|
|
248
|
+
const key = `genesis-draft:${resource}:new`;
|
|
249
|
+
const saved = storage.getItem(key);
|
|
250
|
+
if (saved) {
|
|
251
|
+
try {
|
|
252
|
+
const draft = JSON.parse(saved);
|
|
253
|
+
if (draft && typeof draft === "object" && !Array.isArray(draft)) {
|
|
254
|
+
const validKeys = new Set(resolvedFields.map((f) => f.key));
|
|
255
|
+
for (const k of Object.keys(draft)) {
|
|
256
|
+
if (validKeys.has(k)) {
|
|
257
|
+
defaults[k] = draft[k];
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
} else {
|
|
261
|
+
console.warn(`[useResourceForm] Draft for "${resource}" has unexpected shape, removing`);
|
|
262
|
+
storage.removeItem(key);
|
|
263
|
+
}
|
|
264
|
+
} catch {
|
|
265
|
+
console.warn(`[useResourceForm] Corrupt draft for "${resource}", removing`);
|
|
266
|
+
storage.removeItem(key);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return defaults;
|
|
271
|
+
}, [resolvedFields, effectiveData, action, prefillFromUrl, autosaveEnabled, resource, storage]);
|
|
272
|
+
const resolver = useMemo(
|
|
273
|
+
() => zodResolver(schema),
|
|
274
|
+
[schema]
|
|
275
|
+
);
|
|
276
|
+
const form = useForm({
|
|
277
|
+
resolver,
|
|
278
|
+
defaultValues,
|
|
279
|
+
values: action === "edit" ? defaultValues : void 0,
|
|
280
|
+
mode: validationMode ?? "onBlur",
|
|
281
|
+
reValidateMode: "onChange",
|
|
282
|
+
shouldUnregister: true
|
|
283
|
+
});
|
|
284
|
+
useEffect(() => {
|
|
285
|
+
if (!autosaveEnabled || action !== "create") return;
|
|
286
|
+
const key = `genesis-draft:${resource}:new`;
|
|
287
|
+
let timer;
|
|
288
|
+
const subscription = form.watch((values) => {
|
|
289
|
+
clearTimeout(timer);
|
|
290
|
+
timer = setTimeout(() => {
|
|
291
|
+
try {
|
|
292
|
+
storage.setItem(key, JSON.stringify(values));
|
|
293
|
+
} catch (err) {
|
|
294
|
+
console.warn(`[useResourceForm] Autosave failed for "${resource}":`, err instanceof Error ? err.message : String(err));
|
|
295
|
+
}
|
|
296
|
+
}, debounceMs);
|
|
297
|
+
});
|
|
298
|
+
return () => {
|
|
299
|
+
clearTimeout(timer);
|
|
300
|
+
subscription.unsubscribe();
|
|
301
|
+
};
|
|
302
|
+
}, [autosaveEnabled, resource, action, form, debounceMs, storage]);
|
|
303
|
+
const handleSubmit = useCallback(
|
|
304
|
+
async (formData) => {
|
|
305
|
+
try {
|
|
306
|
+
let result;
|
|
307
|
+
if (action === "create") {
|
|
308
|
+
const res = await provider.create(resource, { data: formData });
|
|
309
|
+
result = res.data;
|
|
310
|
+
} else {
|
|
311
|
+
const recordId = id ?? String(effectiveData?.id ?? "");
|
|
312
|
+
if (!recordId) {
|
|
313
|
+
throw new Error("Cannot update record: no ID available. Provide an id prop or ensure the record has an id field.");
|
|
314
|
+
}
|
|
315
|
+
const res = await provider.update(resource, { id: recordId, data: formData });
|
|
316
|
+
result = res.data;
|
|
317
|
+
}
|
|
318
|
+
if (autosaveEnabled && action === "create") {
|
|
319
|
+
try {
|
|
320
|
+
storage.removeItem(`genesis-draft:${resource}:new`);
|
|
321
|
+
} catch (err) {
|
|
322
|
+
console.warn(`[useResourceForm] Draft cleanup failed for "${resource}":`, err instanceof Error ? err.message : String(err));
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
await queryClient.invalidateQueries({ queryKey: [resource] });
|
|
326
|
+
onSuccess?.(result);
|
|
327
|
+
} catch (err) {
|
|
328
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
329
|
+
form.setError("root", { message: error.message });
|
|
330
|
+
if (onError) {
|
|
331
|
+
onError(error);
|
|
332
|
+
} else {
|
|
333
|
+
console.error(`[useResourceForm] ${action} failed for "${resource}":`, error);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
// form is stable (from useForm) but included for correctness since we call form.setError
|
|
338
|
+
[action, provider, resource, id, effectiveData, queryClient, onSuccess, onError, autosaveEnabled, form, storage]
|
|
339
|
+
);
|
|
340
|
+
const onSubmit = useCallback(
|
|
341
|
+
(e) => form.handleSubmit(handleSubmit)(e),
|
|
342
|
+
[form, handleSubmit]
|
|
343
|
+
);
|
|
344
|
+
const isSaving = form.formState.isSubmitting;
|
|
345
|
+
return {
|
|
346
|
+
form,
|
|
347
|
+
resolvedFields,
|
|
348
|
+
onSubmit,
|
|
349
|
+
handleSave: handleSubmit,
|
|
350
|
+
isSaving,
|
|
351
|
+
isLoading
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export { buildArrayItemDefaults, useResourceForm };
|
|
356
|
+
//# sourceMappingURL=chunk-F4FOTIJI.js.map
|
|
357
|
+
//# sourceMappingURL=chunk-F4FOTIJI.js.map
|