@questpie/admin 3.2.7 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/dist/client/components/blocks/block-editor-provider.mjs +13 -0
  2. package/dist/client/components/fields/array-field.mjs +105 -122
  3. package/dist/client/components/fields/asset-preview-field.mjs +1 -1
  4. package/dist/client/components/fields/blocks-field/blocks-field.mjs +1 -1
  5. package/dist/client/components/fields/boolean-field.mjs +1 -1
  6. package/dist/client/components/fields/date-field.mjs +1 -1
  7. package/dist/client/components/fields/datetime-field.mjs +1 -1
  8. package/dist/client/components/fields/email-field.mjs +1 -1
  9. package/dist/client/components/fields/field-wrapper.mjs +44 -15
  10. package/dist/client/components/fields/number-field.mjs +1 -1
  11. package/dist/client/components/fields/object-array-field.mjs +179 -149
  12. package/dist/client/components/fields/object-field.mjs +96 -87
  13. package/dist/client/components/fields/relation-picker.mjs +1 -1
  14. package/dist/client/components/fields/relation-select.mjs +1 -1
  15. package/dist/client/components/fields/rich-text-editor/index.mjs +1 -1
  16. package/dist/client/components/fields/select-field.mjs +1 -1
  17. package/dist/client/components/fields/text-field.mjs +1 -1
  18. package/dist/client/components/fields/textarea-field.mjs +1 -1
  19. package/dist/client/components/fields/time-field.mjs +1 -1
  20. package/dist/client/components/fields/upload-field.mjs +1 -1
  21. package/dist/client/components/history-sidebar.mjs +10 -4
  22. package/dist/client/components/structured-diff.mjs +367 -0
  23. package/dist/client/components/ui/sidebar.mjs +1 -1
  24. package/dist/client/hooks/use-field-options.mjs +34 -15
  25. package/dist/client/hooks/use-transition-stage.mjs +2 -2
  26. package/dist/client/utils/auto-expand-fields.mjs +1 -1
  27. package/dist/client/views/collection/auto-form-fields.mjs +23 -19
  28. package/dist/client/views/collection/cells/complex-cells.mjs +1 -1
  29. package/dist/client/views/collection/columns/build-columns.mjs +1 -1
  30. package/dist/client/views/collection/columns/column-defaults.mjs +17 -4
  31. package/dist/client/views/collection/field-renderer.mjs +19 -7
  32. package/dist/client/views/collection/form-view.mjs +10 -6
  33. package/dist/client/views/collection/table-view.mjs +19 -13
  34. package/dist/client/views/globals/global-form-view.mjs +47 -27
  35. package/dist/client/views/layout/admin-sidebar.mjs +2 -2
  36. package/dist/client.mjs +1 -1
  37. package/dist/index.mjs +1 -1
  38. package/dist/server/i18n/messages/cs.mjs +8 -0
  39. package/dist/server/i18n/messages/de.mjs +8 -0
  40. package/dist/server/i18n/messages/en.mjs +8 -0
  41. package/dist/server/i18n/messages/es.mjs +8 -0
  42. package/dist/server/i18n/messages/fr.mjs +8 -0
  43. package/dist/server/i18n/messages/pl.mjs +8 -0
  44. package/dist/server/i18n/messages/pt.mjs +8 -0
  45. package/dist/server/i18n/messages/sk.mjs +8 -0
  46. package/dist/server/modules/admin/collections/account.d.mts +50 -50
  47. package/dist/server/modules/admin/collections/admin-locks.d.mts +54 -54
  48. package/dist/server/modules/admin/collections/admin-preferences.d.mts +39 -39
  49. package/dist/server/modules/admin/collections/admin-saved-views.d.mts +47 -47
  50. package/dist/server/modules/admin/collections/apikey.d.mts +68 -68
  51. package/dist/server/modules/admin/collections/assets.d.mts +39 -39
  52. package/dist/server/modules/admin/collections/session.d.mts +42 -42
  53. package/dist/server/modules/admin/collections/user.d.mts +63 -63
  54. package/dist/server/modules/admin/collections/verification.d.mts +36 -36
  55. package/dist/server/modules/admin/routes/admin-config.d.mts +2 -2
  56. package/dist/server/modules/admin/routes/execute-action.d.mts +9 -9
  57. package/dist/server/modules/admin/routes/locales.d.mts +2 -2
  58. package/dist/server/modules/admin/routes/preview.d.mts +24 -19
  59. package/dist/server/modules/admin/routes/preview.mjs +83 -62
  60. package/dist/server/modules/admin/routes/reactive.d.mts +9 -9
  61. package/dist/server/modules/admin/routes/setup.d.mts +7 -7
  62. package/dist/server/modules/admin/routes/translations.d.mts +4 -4
  63. package/dist/server/modules/admin/routes/widget-data.d.mts +5 -5
  64. package/dist/server/modules/admin-preferences/collections/saved-views.d.mts +41 -41
  65. package/dist/server/modules/audit/.generated/module.d.mts +6 -6
  66. package/dist/server/modules/audit/collections/audit-log.d.mts +80 -78
  67. package/dist/server/modules/audit/collections/audit-log.mjs +7 -2
  68. package/dist/server/modules/audit/config/localize-title.mjs +67 -0
  69. package/dist/server/modules/audit/index.d.mts +2 -1
  70. package/dist/server/modules/audit/jobs/audit-cleanup.d.mts +2 -2
  71. package/dist/server/modules/audit/log-audit-entry.d.mts +85 -0
  72. package/dist/server/modules/audit/log-audit-entry.mjs +125 -0
  73. package/dist/server/plugin.mjs +4 -4
  74. package/dist/server.d.mts +2 -1
  75. package/dist/server.mjs +2 -1
  76. package/dist/shared/preview-utils.d.mts +4 -4
  77. package/dist/shared/preview-utils.mjs +5 -7
  78. package/package.json +3 -3
  79. package/dist/client/hooks/use-audit-history.mjs +0 -38
@@ -1,7 +1,7 @@
1
1
  import { cn } from "../../lib/utils.mjs";
2
2
  import { isComponentReference, resolveIconElement } from "../component-renderer.mjs";
3
- import { useResolvedControl } from "./field-utils.mjs";
4
3
  import { FieldWrapper } from "./field-wrapper.mjs";
4
+ import { useResolvedControl } from "./field-utils.mjs";
5
5
  import { SelectSingle } from "../primitives/select-single.mjs";
6
6
  import { SelectMulti } from "../primitives/select-multi.mjs";
7
7
  import { useMemo } from "react";
@@ -1,6 +1,6 @@
1
1
  import { cn } from "../../lib/utils.mjs";
2
- import { useResolvedControl } from "./field-utils.mjs";
3
2
  import { FieldWrapper } from "./field-wrapper.mjs";
3
+ import { useResolvedControl } from "./field-utils.mjs";
4
4
  import { TextInput } from "../primitives/text-input.mjs";
5
5
  import { jsx } from "react/jsx-runtime";
6
6
  import { Controller } from "react-hook-form";
@@ -1,6 +1,6 @@
1
1
  import { cn } from "../../lib/utils.mjs";
2
- import { useResolvedControl } from "./field-utils.mjs";
3
2
  import { FieldWrapper } from "./field-wrapper.mjs";
3
+ import { useResolvedControl } from "./field-utils.mjs";
4
4
  import { TextareaInput } from "../primitives/textarea-input.mjs";
5
5
  import { jsx } from "react/jsx-runtime";
6
6
  import { Controller } from "react-hook-form";
@@ -1,6 +1,6 @@
1
1
  import { cn } from "../../lib/utils.mjs";
2
- import { useResolvedControl } from "./field-utils.mjs";
3
2
  import { FieldWrapper } from "./field-wrapper.mjs";
3
+ import { useResolvedControl } from "./field-utils.mjs";
4
4
  import { TimeInput } from "../primitives/time-input.mjs";
5
5
  import { jsx } from "react/jsx-runtime";
6
6
  import { Controller } from "react-hook-form";
@@ -1,8 +1,8 @@
1
1
  import { useResolveText, useTranslation } from "../../i18n/hooks.mjs";
2
2
  import { cn } from "../../lib/utils.mjs";
3
3
  import { selectClient, useAdminStore } from "../../runtime/provider.mjs";
4
- import { sanitizeFilename, useResolvedControl } from "./field-utils.mjs";
5
4
  import { FieldWrapper } from "./field-wrapper.mjs";
5
+ import { sanitizeFilename, useResolvedControl } from "./field-utils.mjs";
6
6
  import { useCollectionItem } from "../../hooks/use-collection.mjs";
7
7
  import { ResourceSheet } from "../sheets/resource-sheet.mjs";
8
8
  import { useUploadCollection } from "../../hooks/use-upload-collection.mjs";
@@ -2,11 +2,12 @@ import { useResolveText, useTranslation } from "../i18n/hooks.mjs";
2
2
  import { cn, formatLabel } from "../lib/utils.mjs";
3
3
  import { resolveOptionLabelForValue } from "./primitives/option-label.mjs";
4
4
  import { Button } from "./ui/button.mjs";
5
- import { Badge } from "./ui/badge.mjs";
6
- import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "./ui/sheet.mjs";
7
5
  import { Separator } from "./ui/separator.mjs";
6
+ import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "./ui/sheet.mjs";
7
+ import { Badge } from "./ui/badge.mjs";
8
8
  import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "./ui/accordion.mjs";
9
9
  import { Skeleton } from "./ui/skeleton.mjs";
10
+ import { StructuredFieldDiff, shouldUseStructuredDiff } from "./structured-diff.mjs";
10
11
  import { EmptyState } from "./ui/empty-state.mjs";
11
12
  import { ScrollFade } from "./ui/scroll-fade.mjs";
12
13
  import { Icon } from "@iconify/react";
@@ -157,7 +158,7 @@ function summarizeBlocks(value) {
157
158
  const blockNames = [];
158
159
  for (const item of value) {
159
160
  if (!isObjectRecord(item)) continue;
160
- const type = item.blockType ?? item.block ?? item.type ?? item._type ?? item.name;
161
+ const type = item.blockType ?? item.block ?? item.type ?? item["_type"] ?? item.name;
161
162
  if (typeof type === "string") blockNames.push(formatLabel(type));
162
163
  }
163
164
  if (blockNames.length === 0) return null;
@@ -345,7 +346,12 @@ function VersionDiffPanel({ previousVersion, changes, fields }) {
345
346
  })]
346
347
  }),
347
348
  /* @__PURE__ */ jsx(Separator, { className: "my-3" }),
348
- /* @__PURE__ */ jsxs("div", {
349
+ shouldUseStructuredDiff(change.type, change.from, change.to) ? /* @__PURE__ */ jsx(StructuredFieldDiff, {
350
+ type: change.type,
351
+ from: change.from,
352
+ to: change.to,
353
+ nestedFields: field?.metadata?.nestedFields
354
+ }) : /* @__PURE__ */ jsxs("div", {
349
355
  className: "grid gap-3 sm:grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)]",
350
356
  children: [
351
357
  /* @__PURE__ */ jsxs("div", {
@@ -0,0 +1,367 @@
1
+ import { useResolveText, useTranslation } from "../i18n/hooks.mjs";
2
+ import { cn, formatLabel } from "../lib/utils.mjs";
3
+ import { Badge } from "./ui/badge.mjs";
4
+ import { useAdminConfig } from "../hooks/use-admin-config.mjs";
5
+ import { Icon } from "@iconify/react";
6
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
+
8
+ //#region src/client/components/structured-diff.tsx
9
+ function isRecord(v) {
10
+ return typeof v === "object" && v !== null && !Array.isArray(v);
11
+ }
12
+ function isBlockDocument(value) {
13
+ return isRecord(value) && Array.isArray(value["_tree"]) && isRecord(value["_values"]);
14
+ }
15
+ function isEmpty(v) {
16
+ if (v === null || v === void 0 || v === "") return true;
17
+ if (Array.isArray(v)) return v.length === 0;
18
+ if (isRecord(v)) return Object.keys(v).length === 0;
19
+ return false;
20
+ }
21
+ function deepEqual(a, b) {
22
+ try {
23
+ return JSON.stringify(a) === JSON.stringify(b);
24
+ } catch {
25
+ return a === b;
26
+ }
27
+ }
28
+ function flattenTree(nodes) {
29
+ const result = /* @__PURE__ */ new Map();
30
+ function walk(list) {
31
+ for (const n of list) {
32
+ result.set(n.id, n.type);
33
+ if (n.children) walk(n.children);
34
+ }
35
+ }
36
+ walk(nodes);
37
+ return result;
38
+ }
39
+ function formatPrimitive(value) {
40
+ if (value === null || value === void 0) return "-";
41
+ if (typeof value === "string") return value.length > 120 ? `${value.slice(0, 120)}…` : value;
42
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
43
+ if (Array.isArray(value)) return `[${value.length}]`;
44
+ if (isRecord(value)) {
45
+ for (const key of [
46
+ "_title",
47
+ "title",
48
+ "name",
49
+ "label"
50
+ ]) if (typeof value[key] === "string") return value[key];
51
+ return `{${Object.keys(value).length}}`;
52
+ }
53
+ return String(value);
54
+ }
55
+ function summarizeItem(item, index) {
56
+ for (const key of [
57
+ "_title",
58
+ "title",
59
+ "name",
60
+ "label",
61
+ "email",
62
+ "filename"
63
+ ]) {
64
+ const val = item[key];
65
+ if (typeof val === "string" && val.trim()) return val.length > 60 ? `${val.slice(0, 60)}…` : val;
66
+ }
67
+ return `#${index}`;
68
+ }
69
+ function getChangeKind(from, to) {
70
+ if (isEmpty(from) && !isEmpty(to)) return "added";
71
+ if (!isEmpty(from) && isEmpty(to)) return "removed";
72
+ return "changed";
73
+ }
74
+ function getFieldMetadata(field) {
75
+ if (!field) return void 0;
76
+ return field.metadata ?? field;
77
+ }
78
+ function getFieldLabel(field, fallback, resolveText) {
79
+ const label = getFieldMetadata(field)?.label;
80
+ return label ? resolveText(label, fallback) : fallback;
81
+ }
82
+ function getArrayItemFields(nestedFields) {
83
+ return getFieldMetadata(nestedFields?.item)?.nestedFields ?? nestedFields;
84
+ }
85
+ function diffProperties(from, to, fields, resolveText) {
86
+ const allKeys = new Set([...Object.keys(from ?? {}), ...Object.keys(to ?? {})]);
87
+ const changes = [];
88
+ for (const key of allKeys) {
89
+ if (key.startsWith("_") || key === "id") continue;
90
+ const fromVal = from?.[key];
91
+ const toVal = to?.[key];
92
+ if (deepEqual(fromVal, toVal)) continue;
93
+ const label = getFieldLabel(fields?.[key], formatLabel(key), resolveText);
94
+ changes.push({
95
+ key,
96
+ label,
97
+ kind: getChangeKind(fromVal, toVal),
98
+ from: fromVal,
99
+ to: toVal
100
+ });
101
+ }
102
+ return changes;
103
+ }
104
+ function computeBlockChanges(from, to, blockSchemas, resolveText) {
105
+ const fromVal = isBlockDocument(from) ? from : null;
106
+ const toVal = isBlockDocument(to) ? to : null;
107
+ const fromTree = flattenTree(fromVal?.["_tree"] ?? []);
108
+ const toTree = flattenTree(toVal?.["_tree"] ?? []);
109
+ const fromValues = fromVal?.["_values"] ?? {};
110
+ const toValues = toVal?.["_values"] ?? {};
111
+ const changes = [];
112
+ const seen = /* @__PURE__ */ new Set();
113
+ for (const [id, type] of toTree) {
114
+ seen.add(id);
115
+ const schema = blockSchemas?.[type];
116
+ const label = schema?.admin?.label ? resolveText(schema.admin.label, formatLabel(type)) : formatLabel(type);
117
+ if (!fromTree.has(id)) changes.push({
118
+ id,
119
+ label,
120
+ kind: "added",
121
+ properties: diffProperties(void 0, toValues[id], schema?.fields, resolveText)
122
+ });
123
+ else {
124
+ const props = diffProperties(fromValues[id], toValues[id], schema?.fields, resolveText);
125
+ if (props.length > 0) changes.push({
126
+ id,
127
+ label,
128
+ kind: "changed",
129
+ properties: props
130
+ });
131
+ }
132
+ }
133
+ for (const [id, type] of fromTree) {
134
+ if (seen.has(id)) continue;
135
+ const schema = blockSchemas?.[type];
136
+ const label = schema?.admin?.label ? resolveText(schema.admin.label, formatLabel(type)) : formatLabel(type);
137
+ changes.push({
138
+ id,
139
+ label,
140
+ kind: "removed",
141
+ properties: diffProperties(fromValues[id], void 0, schema?.fields, resolveText)
142
+ });
143
+ }
144
+ return changes;
145
+ }
146
+ function computeArrayChanges(from, to, nestedFields, resolveText) {
147
+ const fromArr = Array.isArray(from) ? from : [];
148
+ const toArr = Array.isArray(to) ? to : [];
149
+ if (fromArr.some(itemHasStringId) || toArr.some(itemHasStringId)) return diffArrayById(fromArr, toArr, nestedFields, resolveText);
150
+ return diffArrayByIndex(fromArr, toArr, nestedFields, resolveText);
151
+ }
152
+ function itemHasStringId(item) {
153
+ return isRecord(item) && typeof item.id === "string";
154
+ }
155
+ function diffArrayById(from, to, nestedFields, resolveText) {
156
+ const fromMap = /* @__PURE__ */ new Map();
157
+ for (const item of from) if (isRecord(item) && typeof item.id === "string") fromMap.set(item.id, item);
158
+ const changes = [];
159
+ const seen = /* @__PURE__ */ new Set();
160
+ let idx = 0;
161
+ for (const item of to) {
162
+ idx++;
163
+ if (!isRecord(item) || typeof item.id !== "string") continue;
164
+ seen.add(item.id);
165
+ const label = summarizeItem(item, idx);
166
+ if (!fromMap.has(item.id)) changes.push({
167
+ id: item.id,
168
+ label,
169
+ kind: "added",
170
+ properties: diffProperties(void 0, item, nestedFields, resolveText)
171
+ });
172
+ else {
173
+ const props = diffProperties(fromMap.get(item.id), item, nestedFields, resolveText);
174
+ if (props.length > 0) changes.push({
175
+ id: item.id,
176
+ label,
177
+ kind: "changed",
178
+ properties: props
179
+ });
180
+ }
181
+ }
182
+ let fromIdx = 0;
183
+ for (const item of from) {
184
+ fromIdx++;
185
+ if (!isRecord(item) || typeof item.id !== "string") continue;
186
+ if (seen.has(item.id)) continue;
187
+ changes.push({
188
+ id: item.id,
189
+ label: summarizeItem(item, fromIdx),
190
+ kind: "removed",
191
+ properties: diffProperties(item, void 0, nestedFields, resolveText)
192
+ });
193
+ }
194
+ return changes;
195
+ }
196
+ function diffArrayByIndex(from, to, nestedFields, resolveText) {
197
+ const maxLen = Math.max(from.length, to.length);
198
+ const changes = [];
199
+ for (let i = 0; i < maxLen; i++) {
200
+ const fromItem = from[i];
201
+ const toItem = to[i];
202
+ if (deepEqual(fromItem, toItem)) continue;
203
+ const fromRec = isRecord(fromItem) ? fromItem : void 0;
204
+ const toRec = isRecord(toItem) ? toItem : void 0;
205
+ const label = `#${i + 1}`;
206
+ if (fromItem === void 0) changes.push({
207
+ id: String(i),
208
+ label,
209
+ kind: "added",
210
+ properties: toRec ? diffProperties(void 0, toRec, nestedFields, resolveText) : []
211
+ });
212
+ else if (toItem === void 0) changes.push({
213
+ id: String(i),
214
+ label,
215
+ kind: "removed",
216
+ properties: fromRec ? diffProperties(fromRec, void 0, nestedFields, resolveText) : []
217
+ });
218
+ else changes.push({
219
+ id: String(i),
220
+ label,
221
+ kind: "changed",
222
+ properties: fromRec && toRec ? diffProperties(fromRec, toRec, nestedFields, resolveText) : []
223
+ });
224
+ }
225
+ return changes;
226
+ }
227
+ function computeObjectChanges(from, to, nestedFields, resolveText) {
228
+ return diffProperties(isRecord(from) ? from : {}, isRecord(to) ? to : {}, nestedFields, resolveText);
229
+ }
230
+ const CHANGE_STYLES = {
231
+ added: {
232
+ icon: "ph:plus",
233
+ badge: "border-emerald-500/30 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300",
234
+ border: "border-l-emerald-500/50",
235
+ bg: "bg-emerald-500/5",
236
+ text: "text-emerald-600 dark:text-emerald-400",
237
+ labelKey: "history.changeAdded"
238
+ },
239
+ removed: {
240
+ icon: "ph:minus",
241
+ badge: "border-red-500/30 bg-red-500/10 text-red-700 dark:text-red-300",
242
+ border: "border-l-red-500/50",
243
+ bg: "bg-red-500/5",
244
+ text: "text-red-600 dark:text-red-400",
245
+ labelKey: "history.changeRemoved"
246
+ },
247
+ changed: {
248
+ icon: "ph:pencil-simple",
249
+ badge: "border-blue-500/30 bg-blue-500/10 text-blue-700 dark:text-blue-300",
250
+ border: "border-l-blue-500/50",
251
+ bg: "bg-blue-500/5",
252
+ text: "text-blue-600 dark:text-blue-400",
253
+ labelKey: "history.changeChanged"
254
+ }
255
+ };
256
+ const ADDED_VALUE_CLASS = "text-emerald-600 dark:text-emerald-400";
257
+ const REMOVED_VALUE_CLASS = "text-red-500/80 line-through dark:text-red-400/80";
258
+ function PropertyValueDelta({ change }) {
259
+ if (change.kind === "added") return /* @__PURE__ */ jsx("span", {
260
+ className: ADDED_VALUE_CLASS,
261
+ children: formatPrimitive(change.to)
262
+ });
263
+ if (change.kind === "removed") return /* @__PURE__ */ jsx("span", {
264
+ className: REMOVED_VALUE_CLASS,
265
+ children: formatPrimitive(change.from)
266
+ });
267
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
268
+ /* @__PURE__ */ jsx("span", {
269
+ className: REMOVED_VALUE_CLASS,
270
+ children: formatPrimitive(change.from)
271
+ }),
272
+ /* @__PURE__ */ jsx("span", {
273
+ className: "text-muted-foreground mx-1.5",
274
+ children: "→"
275
+ }),
276
+ /* @__PURE__ */ jsx("span", {
277
+ className: ADDED_VALUE_CLASS,
278
+ children: formatPrimitive(change.to)
279
+ })
280
+ ] });
281
+ }
282
+ function PropertyChangeRow({ change }) {
283
+ const style = CHANGE_STYLES[change.kind];
284
+ return /* @__PURE__ */ jsxs("div", {
285
+ className: "flex items-baseline gap-2 text-xs leading-relaxed",
286
+ children: [
287
+ /* @__PURE__ */ jsx(Icon, {
288
+ icon: style.icon,
289
+ className: cn("size-3 shrink-0", style.text)
290
+ }),
291
+ /* @__PURE__ */ jsxs("span", {
292
+ className: "text-muted-foreground shrink-0 font-medium",
293
+ children: [change.label, ":"]
294
+ }),
295
+ /* @__PURE__ */ jsx("span", {
296
+ className: "min-w-0 break-words",
297
+ children: /* @__PURE__ */ jsx(PropertyValueDelta, { change })
298
+ })
299
+ ]
300
+ });
301
+ }
302
+ function ItemChangeCard({ item }) {
303
+ const { t } = useTranslation();
304
+ const style = CHANGE_STYLES[item.kind];
305
+ return /* @__PURE__ */ jsxs("div", {
306
+ className: cn("rounded-sm border-l-2 py-2 pr-2 pl-3", style.border, style.bg),
307
+ children: [/* @__PURE__ */ jsxs("div", {
308
+ className: "flex items-center gap-2",
309
+ children: [/* @__PURE__ */ jsx("span", {
310
+ className: "text-foreground text-xs font-medium",
311
+ children: item.label
312
+ }), /* @__PURE__ */ jsxs(Badge, {
313
+ variant: "outline",
314
+ className: cn("text-[0.625rem]", style.badge),
315
+ children: [/* @__PURE__ */ jsx(Icon, {
316
+ icon: style.icon,
317
+ className: "size-2.5"
318
+ }), t(style.labelKey)]
319
+ })]
320
+ }), item.properties.length > 0 && /* @__PURE__ */ jsx("div", {
321
+ className: "mt-1.5 space-y-1 pl-1",
322
+ children: item.properties.map((prop) => /* @__PURE__ */ jsx(PropertyChangeRow, { change: prop }, prop.key))
323
+ })]
324
+ });
325
+ }
326
+ function NoChangesPlaceholder() {
327
+ const { t } = useTranslation();
328
+ return /* @__PURE__ */ jsx("div", {
329
+ className: "text-muted-foreground text-xs",
330
+ children: t("history.noFieldChanges")
331
+ });
332
+ }
333
+ function ItemChangeList({ items }) {
334
+ if (items.length === 0) return /* @__PURE__ */ jsx(NoChangesPlaceholder, {});
335
+ return /* @__PURE__ */ jsx("div", {
336
+ className: "space-y-2",
337
+ children: items.map((item) => /* @__PURE__ */ jsx(ItemChangeCard, { item }, item.id))
338
+ });
339
+ }
340
+ function StructuredFieldDiff({ type, from, to, nestedFields }) {
341
+ const resolveText = useResolveText();
342
+ const { data: adminConfig } = useAdminConfig();
343
+ if (type === "blocks") return /* @__PURE__ */ jsx(ItemChangeList, { items: computeBlockChanges(from, to, adminConfig?.blocks, resolveText) });
344
+ if (type === "array") return /* @__PURE__ */ jsx(ItemChangeList, { items: computeArrayChanges(from, to, getArrayItemFields(nestedFields), resolveText) });
345
+ if (type === "object") {
346
+ const props = computeObjectChanges(from, to, nestedFields, resolveText);
347
+ if (props.length === 0) return /* @__PURE__ */ jsx(NoChangesPlaceholder, {});
348
+ return /* @__PURE__ */ jsx("div", {
349
+ className: "space-y-1.5 py-1",
350
+ children: props.map((prop) => /* @__PURE__ */ jsx(PropertyChangeRow, { change: prop }, prop.key))
351
+ });
352
+ }
353
+ return null;
354
+ }
355
+ function shouldUseStructuredDiff(type, from, to) {
356
+ if (type === "blocks") return isBlockDocument(from) || isBlockDocument(to);
357
+ if (type === "object") return isRecord(from) || isRecord(to);
358
+ if (type === "array") {
359
+ const fromArr = Array.isArray(from) ? from : [];
360
+ const toArr = Array.isArray(to) ? to : [];
361
+ return fromArr.some(isRecord) || toArr.some(isRecord);
362
+ }
363
+ return false;
364
+ }
365
+
366
+ //#endregion
367
+ export { StructuredFieldDiff, shouldUseStructuredDiff };
@@ -2,8 +2,8 @@
2
2
 
3
3
  import { cn } from "../../lib/utils.mjs";
4
4
  import { Button } from "./button.mjs";
5
- import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "./sheet.mjs";
6
5
  import { Separator } from "./separator.mjs";
6
+ import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "./sheet.mjs";
7
7
  import { useIsMobile } from "../../hooks/use-media-query.mjs";
8
8
  import { shouldHandleAdminShortcut } from "../../utils/keyboard-shortcuts.mjs";
9
9
  import { Icon } from "@iconify/react";
@@ -15,30 +15,49 @@ function getSiblingData(values, fieldPath) {
15
15
  else return null;
16
16
  return typeof sibling === "object" ? sibling : null;
17
17
  }
18
- function getWatchedValues(allValues, siblingData, watchDeps) {
18
+ function getSiblingDepPath(fieldPath, dep) {
19
+ const siblingKey = dep.slice(9);
20
+ if (!siblingKey) return null;
21
+ const parts = fieldPath.split(".");
22
+ const numericIndex = parts.findIndex((p) => /^\d+$/.test(p));
23
+ if (numericIndex === -1) return null;
24
+ return [...parts.slice(0, numericIndex + 1), siblingKey].join(".");
25
+ }
26
+ function getWatchPathForDep(fieldPath, dep) {
27
+ if (dep.startsWith("$sibling.")) return getSiblingDepPath(fieldPath, dep);
28
+ if (dep.startsWith("$")) return null;
29
+ return dep;
30
+ }
31
+ function getWatchedValuesForDeps(watchEntries, watchedValues) {
19
32
  const result = {};
20
- for (const dep of watchDeps) if (dep.startsWith("$sibling.")) {
21
- const siblingKey = dep.slice(9);
22
- result[dep] = siblingData?.[siblingKey];
23
- } else if (!dep.startsWith("$")) result[dep] = allValues[dep];
33
+ if (watchEntries.length === 0) return result;
34
+ const values = Array.isArray(watchedValues) ? watchedValues : [watchedValues];
35
+ for (const [index, entry] of watchEntries.entries()) result[entry.dep] = values[index];
24
36
  return result;
25
37
  }
38
+ const EMPTY_WATCH_DEPS = [];
26
39
  function useFieldOptions({ collection, mode = "collection", field, optionsConfig, staticOptions, initialSearch = "", limit = 20, enabled = true }) {
27
40
  const client = useAdminStore((s) => s.client);
28
41
  const form = useFormContext();
29
42
  const queryClient = useQueryClient();
30
43
  const [search, setSearch] = React.useState(initialSearch);
31
44
  const debouncedSearch = useDebouncedValue(search, 300);
32
- const watchedValues = useWatch({ control: form.control });
33
- const formValues = React.useMemo(() => watchedValues ?? {}, [watchedValues]);
34
- const siblingData = React.useMemo(() => getSiblingData(formValues, field), [formValues, field]);
35
- const watchDeps = optionsConfig?.watch ?? [];
36
- const depValues = React.useMemo(() => getWatchedValues(formValues, siblingData, watchDeps), [
37
- formValues,
38
- siblingData,
39
- watchDeps
40
- ]);
41
- const depKey = JSON.stringify(depValues);
45
+ const watchDeps = optionsConfig?.watch ?? EMPTY_WATCH_DEPS;
46
+ const watchEntries = React.useMemo(() => watchDeps.map((dep) => {
47
+ const path = getWatchPathForDep(field, dep);
48
+ return path ? {
49
+ dep,
50
+ path
51
+ } : null;
52
+ }).filter((entry) => !!entry), [watchDeps, field]);
53
+ const watchPaths = React.useMemo(() => watchEntries.map((entry) => entry.path), [watchEntries]);
54
+ const watchedValues = useWatch({
55
+ control: form.control,
56
+ name: watchPaths,
57
+ disabled: watchPaths.length === 0
58
+ });
59
+ const depValues = React.useMemo(() => getWatchedValuesForDeps(watchEntries, watchedValues), [watchEntries, watchedValues]);
60
+ const depKey = React.useMemo(() => JSON.stringify(depValues), [depValues]);
42
61
  const filteredStaticOptions = React.useMemo(() => {
43
62
  if (optionsConfig || !staticOptions) return null;
44
63
  if (!debouncedSearch) return staticOptions;
@@ -1,4 +1,4 @@
1
- import { selectBasePath, useAdminStore } from "../runtime/provider.mjs";
1
+ import { selectClient, useAdminStore } from "../runtime/provider.mjs";
2
2
  import { useMutation, useQueryClient } from "@tanstack/react-query";
3
3
 
4
4
  //#region src/client/hooks/use-transition-stage.ts
@@ -40,7 +40,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
40
40
  */
41
41
  function useTransitionStage(resourceName, options) {
42
42
  const mode = options?.mode ?? "collection";
43
- const basePath = useAdminStore(selectBasePath);
43
+ const basePath = useAdminStore(selectClient).getBasePath?.() ?? "/api";
44
44
  const queryClient = useQueryClient();
45
45
  return useMutation({
46
46
  mutationFn: async (params) => {
@@ -44,7 +44,7 @@ function addRelationExpand(withFields, relationName, nestedRelation) {
44
44
  function autoExpandFields(config) {
45
45
  const withFields = {};
46
46
  if (config.list?.with) for (const rel of config.list.with) withFields[rel] = true;
47
- const columnFields = config.list?.columns ?? [];
47
+ const columnFields = config.visibleColumns?.length ? config.visibleColumns : config.list?.columns ?? [];
48
48
  const fieldsToCheck = [];
49
49
  if (columnFields.length > 0) for (const col of columnFields) {
50
50
  const fieldName = typeof col === "string" ? col : col.field;
@@ -385,20 +385,21 @@ function extractFieldNamesFromFieldItems(fieldItems) {
385
385
  * Supports recursive tabs/sections nesting and auto-generates
386
386
  * field list when no form config is defined.
387
387
  */
388
- function AutoFormFields({ app: _cms, collection, mode = "collection", config, registry, fieldResolver, fieldPrefix, allCollectionsConfig }) {
388
+ function AutoFormFields({ app: _cms, collection, mode = "collection", config, registry, fieldResolver, fieldPrefix, allCollectionsConfig, resolvedFields: providedFields, schema: providedSchema }) {
389
389
  const isActionForm = collection === "__action__";
390
+ const shouldFetchSchema = !providedFields || !providedSchema;
390
391
  const collectionResult = useCollectionFields(mode === "collection" ? collection : "", {
391
392
  fallbackFields: mode === "collection" ? config?.fields : void 0,
392
- schemaQueryOptions: { enabled: mode === "collection" && !isActionForm }
393
+ schemaQueryOptions: { enabled: mode === "collection" && !isActionForm && shouldFetchSchema }
393
394
  });
394
- const globalResult = useGlobalFields(mode === "global" ? collection : "", { schemaQueryOptions: { enabled: mode === "global" } });
395
+ const globalResult = useGlobalFields(mode === "global" ? collection : "", { schemaQueryOptions: { enabled: mode === "global" && shouldFetchSchema } });
395
396
  const { data: collectionMeta } = useCollectionMeta(collection, { enabled: mode === "collection" && !isActionForm });
396
397
  const { data: globalMeta } = useGlobalMeta(collection, { enabled: mode === "global" });
397
398
  const resolvedFields = mode === "global" ? {
398
- ...globalResult.fields,
399
+ ...providedFields ?? globalResult.fields,
399
400
  ...config?.fields
400
- } : collectionResult.fields;
401
- const schema = mode === "global" ? globalResult.schema : collectionResult.schema;
401
+ } : providedFields ?? collectionResult.fields;
402
+ const schema = providedSchema ?? (mode === "global" ? globalResult.schema : collectionResult.schema);
402
403
  const entityMeta = mode === "global" ? globalMeta : collectionMeta;
403
404
  const form = useFormContext();
404
405
  const resolveText = useResolveText();
@@ -476,20 +477,23 @@ function AutoFormFields({ app: _cms, collection, mode = "collection", config, re
476
477
  className: "qa-form-fields__main min-w-0 flex-1",
477
478
  children: mainContent
478
479
  }), /* @__PURE__ */ jsx("aside", {
479
- className: cn("qa-form-fields__sidebar", "w-full @max-2xl:pb-2 @2xl:max-w-[18rem] @2xl:pl-1"),
480
+ className: cn("qa-form-fields__sidebar", "w-full @max-2xl:pb-2 @2xl:sticky @2xl:top-4 @2xl:h-[calc(100svh-7rem)] @2xl:min-h-0 @2xl:max-w-[18rem] @2xl:shrink-0 @2xl:self-start @2xl:pl-1"),
480
481
  children: /* @__PURE__ */ jsx("div", {
481
- className: "bg-surface-low/45 space-y-4 rounded-md px-3 py-3 @2xl:sticky @2xl:top-4 @2xl:h-auto",
482
- children: /* @__PURE__ */ jsx(SidebarRenderer, {
483
- sidebar: formConfig.sidebar,
484
- fields,
485
- collection,
486
- mode,
487
- registry,
488
- fieldPrefix,
489
- allCollectionsConfig,
490
- entityMeta,
491
- formValues,
492
- resolveText
482
+ className: "bg-surface-low/45 flex min-h-0 flex-col rounded-md @2xl:h-full",
483
+ children: /* @__PURE__ */ jsx("div", {
484
+ className: "scrollbar-thin min-h-0 space-y-4 overflow-y-auto px-3 py-3 @2xl:flex-1",
485
+ children: /* @__PURE__ */ jsx(SidebarRenderer, {
486
+ sidebar: formConfig.sidebar,
487
+ fields,
488
+ collection,
489
+ mode,
490
+ registry,
491
+ fieldPrefix,
492
+ allCollectionsConfig,
493
+ entityMeta,
494
+ formValues,
495
+ resolveText
496
+ })
493
497
  })
494
498
  })
495
499
  })]
@@ -1,7 +1,7 @@
1
1
  import { useResolveText, useTranslation } from "../../../i18n/hooks.mjs";
2
2
  import { cn } from "../../../lib/utils.mjs";
3
- import { Badge } from "../../../components/ui/badge.mjs";
4
3
  import { isBlockContent } from "../../../blocks/types.mjs";
4
+ import { Badge } from "../../../components/ui/badge.mjs";
5
5
  import { Tooltip, TooltipContent, TooltipTrigger } from "../../../components/ui/tooltip.mjs";
6
6
  import { useAdminConfig } from "../../../hooks/use-admin-config.mjs";
7
7
  import { formatFieldLabel, formatPrimitiveValue, getFieldLabel, getItemLabel, summarizeValue } from "./shared/cell-helpers.mjs";
@@ -1,8 +1,8 @@
1
1
  import { useResolveText } from "../../../i18n/hooks.mjs";
2
2
  import { useSafeContentLocales } from "../../../runtime/content-locales-provider.mjs";
3
3
  import { useScopedLocale } from "../../../runtime/locale-scope.mjs";
4
- import { DefaultCell, TextCell } from "../cells/primitive-cells.mjs";
5
4
  import { LocaleSwitcher } from "../../../components/locale-switcher.mjs";
5
+ import { DefaultCell, TextCell } from "../cells/primitive-cells.mjs";
6
6
  import { normalizeColumnConfig } from "../../../builder/types/collection-types.mjs";
7
7
  import { computeDefaultColumns, formatHeader } from "./column-defaults.mjs";
8
8
  import { jsx, jsxs } from "react/jsx-runtime";