@questpie/admin 3.2.7 → 3.4.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 (135) hide show
  1. package/README.md +4 -6
  2. package/dist/client/blocks/block-renderer.d.mts +2 -2
  3. package/dist/client/builder/admin-types.d.mts +3 -3
  4. package/dist/client/builder/types/action-types.d.mts +1 -1
  5. package/dist/client/builder/types/collection-types.d.mts +59 -2
  6. package/dist/client/components/blocks/block-editor-provider.mjs +13 -0
  7. package/dist/client/components/fields/array-field.mjs +105 -122
  8. package/dist/client/components/fields/asset-preview-field.mjs +1 -1
  9. package/dist/client/components/fields/blocks-field/blocks-field.mjs +1 -1
  10. package/dist/client/components/fields/boolean-field.mjs +1 -1
  11. package/dist/client/components/fields/date-field.mjs +1 -1
  12. package/dist/client/components/fields/datetime-field.mjs +1 -1
  13. package/dist/client/components/fields/email-field.mjs +1 -1
  14. package/dist/client/components/fields/field-wrapper.mjs +44 -15
  15. package/dist/client/components/fields/number-field.mjs +1 -1
  16. package/dist/client/components/fields/object-array-field.mjs +179 -149
  17. package/dist/client/components/fields/object-field.mjs +96 -87
  18. package/dist/client/components/fields/relation-picker.mjs +1 -1
  19. package/dist/client/components/fields/relation-select.mjs +1 -1
  20. package/dist/client/components/fields/rich-text-editor/index.mjs +1 -1
  21. package/dist/client/components/fields/select-field.mjs +1 -1
  22. package/dist/client/components/fields/text-field.mjs +1 -1
  23. package/dist/client/components/fields/textarea-field.mjs +1 -1
  24. package/dist/client/components/fields/time-field.mjs +1 -1
  25. package/dist/client/components/fields/upload-field.mjs +1 -1
  26. package/dist/client/components/history-sidebar.mjs +10 -4
  27. package/dist/client/components/structured-diff.mjs +367 -0
  28. package/dist/client/components/ui/sidebar.mjs +1 -1
  29. package/dist/client/hooks/use-field-options.mjs +34 -15
  30. package/dist/client/hooks/use-transition-stage.mjs +2 -2
  31. package/dist/client/modules/admin.d.mts +3 -0
  32. package/dist/client/modules/admin.mjs +3 -0
  33. package/dist/client/preview/block-scope-context.d.mts +2 -2
  34. package/dist/client/preview/preview-banner.d.mts +2 -2
  35. package/dist/client/preview/preview-field.d.mts +4 -4
  36. package/dist/client/utils/auto-expand-fields.mjs +1 -1
  37. package/dist/client/views/collection/auto-form-fields.mjs +23 -19
  38. package/dist/client/views/collection/cells/complex-cells.mjs +1 -1
  39. package/dist/client/views/collection/columns/build-columns.mjs +1 -1
  40. package/dist/client/views/collection/columns/column-defaults.mjs +17 -4
  41. package/dist/client/views/collection/field-renderer.mjs +19 -7
  42. package/dist/client/views/collection/form-view.mjs +10 -6
  43. package/dist/client/views/collection/list-view.mjs +830 -0
  44. package/dist/client/views/collection/outline.mjs +363 -0
  45. package/dist/client/views/collection/table-view.mjs +25 -16
  46. package/dist/client/views/globals/global-form-view.mjs +47 -27
  47. package/dist/client/views/layout/admin-layout.d.mts +15 -1
  48. package/dist/client/views/layout/admin-layout.mjs +95 -31
  49. package/dist/client/views/layout/admin-sidebar.mjs +2 -2
  50. package/dist/client.d.mts +6 -6
  51. package/dist/client.mjs +1 -1
  52. package/dist/components/rich-text/rich-text-renderer.d.mts +2 -2
  53. package/dist/factories.d.mts +19 -0
  54. package/dist/factories.mjs +11 -0
  55. package/dist/fields.d.mts +4 -0
  56. package/dist/fields.mjs +5 -0
  57. package/dist/index.d.mts +6 -6
  58. package/dist/index.mjs +1 -1
  59. package/dist/modules/admin.d.mts +10 -0
  60. package/dist/modules/admin.mjs +9 -0
  61. package/dist/modules/audit.d.mts +5 -0
  62. package/dist/modules/audit.mjs +5 -0
  63. package/dist/server/augmentation/form-layout.d.mts +57 -2
  64. package/dist/server/augmentation/index.d.mts +3 -1
  65. package/dist/server/augmentation/shell.d.mts +48 -0
  66. package/dist/server/augmentation.d.mts +2 -1
  67. package/dist/server/codegen/admin-client-template.mjs +11 -4
  68. package/dist/server/fields/blocks.d.mts +9 -2
  69. package/dist/server/fields/blocks.mjs +1 -1
  70. package/dist/server/fields/index.d.mts +2 -2
  71. package/dist/server/fields/index.mjs +2 -2
  72. package/dist/server/fields/rich-text.d.mts +9 -2
  73. package/dist/server/fields/rich-text.mjs +1 -1
  74. package/dist/server/i18n/messages/cs.mjs +8 -0
  75. package/dist/server/i18n/messages/de.mjs +8 -0
  76. package/dist/server/i18n/messages/en.mjs +8 -0
  77. package/dist/server/i18n/messages/es.mjs +8 -0
  78. package/dist/server/i18n/messages/fr.mjs +8 -0
  79. package/dist/server/i18n/messages/pl.mjs +8 -0
  80. package/dist/server/i18n/messages/pt.mjs +8 -0
  81. package/dist/server/i18n/messages/sk.mjs +8 -0
  82. package/dist/server/modules/admin/.generated/module.d.mts +24 -19
  83. package/dist/server/modules/admin/.generated/module.mjs +5 -1
  84. package/dist/server/modules/admin/.generated/registries.d.mts +6 -4
  85. package/dist/server/modules/admin/client/.generated/module.d.mts +70 -70
  86. package/dist/server/modules/admin/client/.generated/module.mjs +3 -1
  87. package/dist/server/modules/admin/client/views/collection-form.d.mts +6 -0
  88. package/dist/server/modules/admin/client/views/collection-table.d.mts +6 -0
  89. package/dist/server/modules/admin/client/views/global-form.d.mts +6 -0
  90. package/dist/server/modules/admin/client/views/list-view.d.mts +6 -0
  91. package/dist/server/modules/admin/client/views/list-view.mjs +10 -0
  92. package/dist/server/modules/admin/collections/account.d.mts +50 -50
  93. package/dist/server/modules/admin/collections/admin-locks.d.mts +54 -54
  94. package/dist/server/modules/admin/collections/admin-preferences.d.mts +39 -39
  95. package/dist/server/modules/admin/collections/admin-saved-views.d.mts +47 -47
  96. package/dist/server/modules/admin/collections/apikey.d.mts +39 -39
  97. package/dist/server/modules/admin/collections/assets.d.mts +39 -39
  98. package/dist/server/modules/admin/collections/session.d.mts +42 -42
  99. package/dist/server/modules/admin/collections/user.d.mts +63 -63
  100. package/dist/server/modules/admin/collections/verification.d.mts +36 -36
  101. package/dist/server/modules/admin/dto/admin-config.dto.mjs +17 -0
  102. package/dist/server/modules/admin/index.d.mts +30 -31
  103. package/dist/server/modules/admin/routes/admin-config.d.mts +2 -17
  104. package/dist/server/modules/admin/routes/admin-config.mjs +21 -5
  105. package/dist/server/modules/admin/routes/execute-action.d.mts +9 -9
  106. package/dist/server/modules/admin/routes/execute-action.mjs +18 -12
  107. package/dist/server/modules/admin/routes/i18n-helpers.d.mts +4 -0
  108. package/dist/server/modules/admin/routes/locales.d.mts +2 -2
  109. package/dist/server/modules/admin/routes/preview.d.mts +24 -19
  110. package/dist/server/modules/admin/routes/preview.mjs +83 -62
  111. package/dist/server/modules/admin/routes/reactive.d.mts +9 -9
  112. package/dist/server/modules/admin/routes/route-helpers.mjs +36 -1
  113. package/dist/server/modules/admin/routes/setup.d.mts +7 -14
  114. package/dist/server/modules/admin/routes/setup.mjs +16 -3
  115. package/dist/server/modules/admin/routes/translations.d.mts +4 -4
  116. package/dist/server/modules/admin/routes/widget-data.d.mts +5 -5
  117. package/dist/server/modules/admin/views/list-view.d.mts +8 -0
  118. package/dist/server/modules/admin/views/list-view.mjs +7 -0
  119. package/dist/server/modules/admin-preferences/collections/saved-views.d.mts +41 -41
  120. package/dist/server/modules/audit/.generated/module.d.mts +6 -6
  121. package/dist/server/modules/audit/collections/audit-log.d.mts +87 -80
  122. package/dist/server/modules/audit/collections/audit-log.mjs +7 -2
  123. package/dist/server/modules/audit/config/localize-title.mjs +67 -0
  124. package/dist/server/modules/audit/index.d.mts +3 -2
  125. package/dist/server/modules/audit/jobs/audit-cleanup.d.mts +2 -2
  126. package/dist/server/modules/audit/log-audit-entry.d.mts +85 -0
  127. package/dist/server/modules/audit/log-audit-entry.mjs +125 -0
  128. package/dist/server/plugin.d.mts +1 -1
  129. package/dist/server/plugin.mjs +31 -31
  130. package/dist/server.d.mts +6 -4
  131. package/dist/server.mjs +9 -8
  132. package/dist/shared/preview-utils.d.mts +4 -4
  133. package/dist/shared/preview-utils.mjs +5 -7
  134. package/package.json +13 -3
  135. 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) => {
@@ -0,0 +1,3 @@
1
+ import { AdminModule, _module } from "../../server/modules/admin/client/.generated/module.mjs";
2
+ import "../../server/modules/admin/client/index.mjs";
3
+ export { type AdminModule as AdminClientModule, _module as adminClientModule };
@@ -0,0 +1,3 @@
1
+ import module_default from "../../server/modules/admin/client/.generated/module.mjs";
2
+
3
+ export { module_default as adminClientModule };
@@ -1,5 +1,5 @@
1
1
  import * as React from "react";
2
- import * as react_jsx_runtime25 from "react/jsx-runtime";
2
+ import * as react_jsx_runtime21 from "react/jsx-runtime";
3
3
 
4
4
  //#region src/client/preview/block-scope-context.d.ts
5
5
 
@@ -35,7 +35,7 @@ declare function BlockScopeProvider({
35
35
  blockId,
36
36
  basePath,
37
37
  children
38
- }: BlockScopeProviderProps): react_jsx_runtime25.JSX.Element;
38
+ }: BlockScopeProviderProps): react_jsx_runtime21.JSX.Element;
39
39
  /**
40
40
  * Get current block scope context.
41
41
  *
@@ -1,4 +1,4 @@
1
- import * as react_jsx_runtime24 from "react/jsx-runtime";
1
+ import * as react_jsx_runtime22 from "react/jsx-runtime";
2
2
 
3
3
  //#region src/client/preview/preview-banner.d.ts
4
4
 
@@ -40,6 +40,6 @@ declare function PreviewBanner({
40
40
  isPreviewMode,
41
41
  className,
42
42
  exitPreviewUrl
43
- }: PreviewBannerProps): react_jsx_runtime24.JSX.Element | null;
43
+ }: PreviewBannerProps): react_jsx_runtime22.JSX.Element | null;
44
44
  //#endregion
45
45
  export { PreviewBanner, PreviewBannerProps };
@@ -1,5 +1,5 @@
1
1
  import * as React from "react";
2
- import * as react_jsx_runtime21 from "react/jsx-runtime";
2
+ import * as react_jsx_runtime23 from "react/jsx-runtime";
3
3
 
4
4
  //#region src/client/preview/preview-field.d.ts
5
5
 
@@ -68,7 +68,7 @@ declare function PreviewProvider({
68
68
  }) => void;
69
69
  onFieldValueEdited?: (payload: PreviewFieldValueEditedPayload) => void;
70
70
  children: React.ReactNode;
71
- }): react_jsx_runtime21.JSX.Element;
71
+ }): react_jsx_runtime23.JSX.Element;
72
72
  /**
73
73
  * Hook to access preview context.
74
74
  */
@@ -107,7 +107,7 @@ declare function PreviewField({
107
107
  style,
108
108
  onClick,
109
109
  onValueCommit
110
- }: PreviewFieldProps): react_jsx_runtime21.JSX.Element;
110
+ }: PreviewFieldProps): react_jsx_runtime23.JSX.Element;
111
111
  /**
112
112
  * Standalone PreviewField that works without context.
113
113
  * Useful when you can't use PreviewProvider.
@@ -131,6 +131,6 @@ declare function StandalonePreviewField({
131
131
  blockId?: string;
132
132
  fieldType?: "regular" | "block" | "relation";
133
133
  }) => void;
134
- }): react_jsx_runtime21.JSX.Element;
134
+ }): react_jsx_runtime23.JSX.Element;
135
135
  //#endregion
136
136
  export { PreviewField, PreviewFieldProps, PreviewProvider, StandalonePreviewField, usePreviewContext };
@@ -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;