@questpie/admin 3.5.3 → 3.5.5

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 (141) hide show
  1. package/README.md +8 -0
  2. package/dist/client/blocks/block-renderer.d.mts +2 -2
  3. package/dist/client/builder/index.d.mts +1 -1
  4. package/dist/client/builder/types/collection-types.d.mts +80 -5
  5. package/dist/client/builder/types/common.d.mts +5 -0
  6. package/dist/client/builder/types/field-types.d.mts +41 -1
  7. package/dist/client/builder/view/view.d.mts +3 -2
  8. package/dist/client/components/admin-link.d.mts +2 -2
  9. package/dist/client/components/fields/boolean-field.mjs +2 -1
  10. package/dist/client/components/fields/date-field.mjs +2 -1
  11. package/dist/client/components/fields/datetime-field.mjs +2 -1
  12. package/dist/client/components/fields/email-field.mjs +2 -1
  13. package/dist/client/components/fields/field-utils.d.mts +11 -0
  14. package/dist/client/components/fields/field-utils.mjs +3 -1
  15. package/dist/client/components/fields/field-wrapper.mjs +3 -3
  16. package/dist/client/components/fields/number-field.mjs +2 -1
  17. package/dist/client/components/fields/object-field.mjs +2 -1
  18. package/dist/client/components/fields/relation/displays/types.mjs +3 -3
  19. package/dist/client/components/fields/rich-text-editor/extensions.mjs +2 -1
  20. package/dist/client/components/fields/rich-text-editor/image-popover.mjs +6 -2
  21. package/dist/client/components/fields/rich-text-editor/image-upload.mjs +2 -1
  22. package/dist/client/components/fields/rich-text-editor/index.d.mts +3 -2
  23. package/dist/client/components/fields/rich-text-editor/index.mjs +4 -3
  24. package/dist/client/components/fields/select-field.mjs +2 -1
  25. package/dist/client/components/fields/text-field.mjs +2 -1
  26. package/dist/client/components/fields/textarea-field.mjs +2 -1
  27. package/dist/client/components/fields/time-field.mjs +2 -1
  28. package/dist/client/components/layout/field-layout-renderer.mjs +4 -4
  29. package/dist/client/components/media/media-grid.mjs +2 -1
  30. package/dist/client/components/primitives/asset-preview.mjs +4 -2
  31. package/dist/client/components/primitives/dropzone.d.mts +100 -0
  32. package/dist/client/components/primitives/field-select-control.mjs +2 -1
  33. package/dist/client/components/ui/button.d.mts +23 -0
  34. package/dist/client/components/ui/button.mjs +2 -2
  35. package/dist/client/components/ui/dropdown-menu.d.mts +49 -0
  36. package/dist/client/components/ui/dropdown-menu.mjs +22 -1
  37. package/dist/client/components/ui/popover.mjs +1 -1
  38. package/dist/client/components/ui/search-input.d.mts +56 -0
  39. package/dist/client/components/ui/select.mjs +2 -2
  40. package/dist/client/components/ui/sheet.d.mts +40 -0
  41. package/dist/client/components/ui/table.d.mts +49 -0
  42. package/dist/client/components/ui/table.mjs +15 -1
  43. package/dist/client/components/ui/tooltip.d.mts +21 -0
  44. package/dist/client/contexts/focus-context.d.mts +2 -2
  45. package/dist/client/hooks/use-admin-config.mjs +20 -1
  46. package/dist/client/hooks/use-autosave.mjs +91 -0
  47. package/dist/client/hooks/use-collection.mjs +65 -23
  48. package/dist/client/hooks/use-upload.d.mts +40 -0
  49. package/dist/client/hooks/use-upload.mjs +4 -2
  50. package/dist/client/i18n/hooks.d.mts +20 -0
  51. package/dist/client/lib/utils.d.mts +6 -0
  52. package/dist/client/preview/block-scope-context.d.mts +2 -2
  53. package/dist/client/preview/preview-banner.d.mts +2 -2
  54. package/dist/client/preview/preview-field.d.mts +4 -4
  55. package/dist/client/runtime/provider.mjs +22 -3
  56. package/dist/client/scope/picker.d.mts +2 -2
  57. package/dist/client/scope/provider.d.mts +2 -2
  58. package/dist/client/styles/base.css +22 -18
  59. package/dist/client/utils/asset-url.mjs +27 -0
  60. package/dist/client/views/auth/accept-invite-form.d.mts +2 -2
  61. package/dist/client/views/auth/auth-layout.d.mts +3 -3
  62. package/dist/client/views/auth/forgot-password-form.d.mts +2 -2
  63. package/dist/client/views/auth/login-form.d.mts +2 -2
  64. package/dist/client/views/auth/reset-password-form.d.mts +2 -2
  65. package/dist/client/views/auth/setup-form.d.mts +2 -2
  66. package/dist/client/views/collection/auto-form-fields.mjs +4 -4
  67. package/dist/client/views/collection/cells/shared/asset-thumbnail.d.mts +7 -0
  68. package/dist/client/views/collection/cells/shared/asset-thumbnail.mjs +3 -2
  69. package/dist/client/views/collection/cells/shared/cell-helpers.mjs +3 -2
  70. package/dist/client/views/collection/cells/upload-cells.mjs +2 -1
  71. package/dist/client/views/collection/document-view.d.mts +30 -0
  72. package/dist/client/views/collection/document-view.mjs +377 -0
  73. package/dist/client/views/collection/field-context.mjs +3 -2
  74. package/dist/client/views/collection/field-renderer.mjs +2 -2
  75. package/dist/client/views/collection/form-view.mjs +14 -80
  76. package/dist/client/views/collection/list-view.mjs +19 -15
  77. package/dist/client/views/collection/table-view.mjs +1 -1
  78. package/dist/client/views/layout/admin-layout-provider.mjs +4 -3
  79. package/dist/client/views/layout/admin-layout.mjs +107 -20
  80. package/dist/client/views/layout/admin-router.mjs +19 -3
  81. package/dist/client/views/layout/admin-sidebar.mjs +50 -6
  82. package/dist/client/views/layout/admin-view-layout.d.mts +36 -0
  83. package/dist/client/views/pages/accept-invite-page.d.mts +2 -2
  84. package/dist/client/views/pages/dashboard-page.d.mts +2 -2
  85. package/dist/client/views/pages/forgot-password-page.d.mts +2 -2
  86. package/dist/client/views/pages/invite-page.d.mts +2 -2
  87. package/dist/client/views/pages/login-page.d.mts +2 -2
  88. package/dist/client/views/pages/reset-password-page.d.mts +2 -2
  89. package/dist/client/views/pages/setup-page.d.mts +2 -2
  90. package/dist/client.d.mts +17 -2
  91. package/dist/client.mjs +16 -1
  92. package/dist/components/rich-text/rich-text-renderer.d.mts +2 -2
  93. package/dist/factories.d.mts +2 -2
  94. package/dist/factories.mjs +2 -2
  95. package/dist/index.d.mts +17 -3
  96. package/dist/index.mjs +16 -1
  97. package/dist/server/augmentation/actions.d.mts +5 -0
  98. package/dist/server/augmentation/form-layout.d.mts +5 -0
  99. package/dist/server/augmentation/views.d.mts +4 -1
  100. package/dist/server/fields/blocks.mjs +4 -1
  101. package/dist/server/fields/reactive-runtime.mjs +3 -0
  102. package/dist/server/modules/admin/.generated/module.d.mts +1 -1
  103. package/dist/server/modules/admin/auth-helpers.mjs +7 -1
  104. package/dist/server/modules/admin/block/introspection.mjs +28 -4
  105. package/dist/server/modules/admin/block/prefetch.d.mts +11 -0
  106. package/dist/server/modules/admin/block/prefetch.mjs +108 -27
  107. package/dist/server/modules/admin/client/.generated/module.d.mts +68 -67
  108. package/dist/server/modules/admin/client/.generated/module.mjs +2 -0
  109. package/dist/server/modules/admin/client/views/collection-document.d.mts +6 -0
  110. package/dist/server/modules/admin/client/views/collection-document.mjs +10 -0
  111. package/dist/server/modules/admin/collections/account.d.mts +46 -46
  112. package/dist/server/modules/admin/collections/admin-locks.d.mts +57 -57
  113. package/dist/server/modules/admin/collections/admin-preferences.d.mts +42 -42
  114. package/dist/server/modules/admin/collections/admin-saved-views.d.mts +50 -50
  115. package/dist/server/modules/admin/collections/apikey.d.mts +79 -71
  116. package/dist/server/modules/admin/collections/assets.d.mts +42 -42
  117. package/dist/server/modules/admin/collections/session.d.mts +45 -45
  118. package/dist/server/modules/admin/collections/user.d.mts +66 -66
  119. package/dist/server/modules/admin/collections/verification.d.mts +39 -39
  120. package/dist/server/modules/admin/dto/admin-config.dto.mjs +34 -4
  121. package/dist/server/modules/admin/factories.mjs +4 -34
  122. package/dist/server/modules/admin/routes/admin-config.d.mts +3 -2
  123. package/dist/server/modules/admin/routes/admin-config.mjs +18 -2
  124. package/dist/server/modules/admin/routes/execute-action.d.mts +9 -9
  125. package/dist/server/modules/admin/routes/execute-action.mjs +10 -4
  126. package/dist/server/modules/admin/routes/locales.d.mts +2 -2
  127. package/dist/server/modules/admin/routes/locales.mjs +1 -1
  128. package/dist/server/modules/admin/routes/preview.d.mts +11 -11
  129. package/dist/server/modules/admin/routes/preview.mjs +6 -5
  130. package/dist/server/modules/admin/routes/reactive.d.mts +9 -9
  131. package/dist/server/modules/admin/routes/reactive.mjs +2 -2
  132. package/dist/server/modules/admin/routes/route-helpers.mjs +1 -1
  133. package/dist/server/modules/admin/routes/setup.d.mts +7 -7
  134. package/dist/server/modules/admin/routes/translations.d.mts +4 -4
  135. package/dist/server/modules/admin/routes/widget-data.d.mts +5 -5
  136. package/dist/server/modules/admin/routes/widget-data.mjs +1 -1
  137. package/dist/server/modules/admin-preferences/collections/saved-views.d.mts +27 -27
  138. package/dist/server/plugin.mjs +8 -3
  139. package/dist/server/proxy-factories.d.mts +8 -1
  140. package/dist/server/proxy-factories.mjs +33 -1
  141. package/package.json +4 -4
@@ -1,5 +1,6 @@
1
1
  import { useTranslation } from "../../../i18n/hooks.mjs";
2
2
  import { Badge } from "../../../components/ui/badge.mjs";
3
+ import { resolveAssetUrl } from "../../../utils/asset-url.mjs";
3
4
  import { AssetThumbnail } from "./shared/asset-thumbnail.mjs";
4
5
  import { useCollectionItem } from "../../../hooks/use-collection.mjs";
5
6
  import "react";
@@ -81,7 +82,7 @@ function UploadManyCell({ value }) {
81
82
  children: [/* @__PURE__ */ jsx("div", {
82
83
  className: "flex",
83
84
  children: imageAssets.map((asset, index) => /* @__PURE__ */ jsx("img", {
84
- src: asset.url,
85
+ src: resolveAssetUrl(asset.url) ?? "",
85
86
  alt: asset.filename || "Asset",
86
87
  className: `image-outline bg-background size-6 rounded object-cover${index > 0 ? " -ms-2" : ""}`
87
88
  }, asset.id || index))
@@ -0,0 +1,30 @@
1
+ import { ComponentRegistry, DocumentViewConfig } from "../../builder/types/field-types.mjs";
2
+ import { CollectionBuilderState } from "../../builder/types/collection-types.mjs";
3
+ import * as React from "react";
4
+
5
+ //#region src/client/views/collection/document-view.d.ts
6
+
7
+ interface DocumentViewProps {
8
+ collection: string;
9
+ id?: string;
10
+ config?: Partial<CollectionBuilderState> | Record<string, any>;
11
+ viewConfig?: DocumentViewConfig & Record<string, any>;
12
+ navigate: (path: string) => void;
13
+ basePath?: string;
14
+ defaultValues?: Record<string, any>;
15
+ registry?: ComponentRegistry;
16
+ allCollectionsConfig?: Record<string, any>;
17
+ title?: string;
18
+ }
19
+ declare function DocumentView({
20
+ collection,
21
+ id,
22
+ config,
23
+ viewConfig,
24
+ defaultValues: defaultValuesProp,
25
+ registry,
26
+ allCollectionsConfig,
27
+ title: titleProp
28
+ }: DocumentViewProps): React.ReactElement;
29
+ //#endregion
30
+ export { DocumentView };
@@ -0,0 +1,377 @@
1
+ import { useResolveText, useTranslation } from "../../i18n/hooks.mjs";
2
+ import { resolveIconElement } from "../../components/component-renderer.mjs";
3
+ import { Button } from "../../components/ui/button.mjs";
4
+ import { Separator } from "../../components/ui/separator.mjs";
5
+ import { Badge } from "../../components/ui/badge.mjs";
6
+ import { adminCollectionKey } from "../../hooks/query-access.mjs";
7
+ import { useCollectionFields } from "../../hooks/use-collection-fields.mjs";
8
+ import { FieldRenderer } from "./field-renderer.mjs";
9
+ import { EmptyState } from "../../components/ui/empty-state.mjs";
10
+ import { useCollectionValidation } from "../../hooks/use-collection-validation.mjs";
11
+ import { usePreferServerValidation } from "../../hooks/use-server-validation.mjs";
12
+ import { useAutosave } from "../../hooks/use-autosave.mjs";
13
+ import { useCollectionItem, useCollectionUpdate } from "../../hooks/use-collection.mjs";
14
+ import { AdminViewHeader, AdminViewLayout } from "../layout/admin-view-layout.mjs";
15
+ import { FormViewSkeleton } from "./view-skeletons.mjs";
16
+ import { Icon } from "@iconify/react";
17
+ import * as React from "react";
18
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
19
+ import { FormProvider, useForm, useFormContext, useFormState } from "react-hook-form";
20
+ import { toast } from "sonner";
21
+
22
+ //#region src/client/views/collection/document-view.tsx
23
+ /**
24
+ * Document View — Notion-style document page
25
+ *
26
+ * Renders a collection record as a centered page: an optional title, a compact
27
+ * block of inline-editable property rows, a divider, and a dominant long-form
28
+ * body field rendered with the rich-text editor.
29
+ *
30
+ * Drop-in with the form view: takes the same props (`CollectionFormViewProps`).
31
+ * Layout and which fields are body/title/properties are declared via
32
+ * `viewConfig.document` (the `DocumentViewConfig` contract) — no hardcoded field
33
+ * names. Save is configurable: `"autosave"` (debounced, shared `useAutosave`
34
+ * pipeline) or `"manual"` (a Save affordance with dirty tracking).
35
+ */
36
+ /**
37
+ * Presentation-only default icons per field type, used for property rows when a
38
+ * field carries no explicit icon. Falls back to a generic tag icon.
39
+ */
40
+ const FIELD_TYPE_ICONS = {
41
+ text: "ph:text-aa",
42
+ textarea: "ph:text-align-left",
43
+ richText: "ph:article",
44
+ number: "ph:hash",
45
+ boolean: "ph:toggle-left",
46
+ select: "ph:list-checks",
47
+ relation: "ph:link-simple",
48
+ date: "ph:calendar-blank",
49
+ datetime: "ph:calendar-blank",
50
+ upload: "ph:paperclip",
51
+ json: "ph:brackets-curly",
52
+ color: "ph:palette",
53
+ email: "ph:envelope-simple",
54
+ url: "ph:globe-simple"
55
+ };
56
+ const DEFAULT_PROPERTY_ICON = "ph:tag";
57
+ /**
58
+ * A single Notion-style property row: icon + label + the field's inline editor.
59
+ */
60
+ const DocumentPropertyRow = React.memo(function DocumentPropertyRow$1({ collection, fieldName, fieldDef, icon, label, registry, allCollectionsConfig }) {
61
+ return /* @__PURE__ */ jsxs("div", {
62
+ className: "qa-document-view__property hover:bg-muted/40 flex items-start gap-3 rounded-md px-2 py-1.5 transition-colors duration-150",
63
+ children: [/* @__PURE__ */ jsxs("div", {
64
+ className: "text-muted-foreground flex w-32 shrink-0 items-center gap-2 pt-2",
65
+ children: [/* @__PURE__ */ jsx("span", {
66
+ className: "flex size-3.5 shrink-0 items-center justify-center",
67
+ children: icon
68
+ }), /* @__PURE__ */ jsx("span", {
69
+ className: "truncate text-xs",
70
+ children: label
71
+ })]
72
+ }), /* @__PURE__ */ jsx("div", {
73
+ className: "qa-document-view__property-control min-w-0 flex-1",
74
+ children: /* @__PURE__ */ jsx(FieldRenderer, {
75
+ fieldName,
76
+ fieldDef,
77
+ collection,
78
+ registry,
79
+ allCollectionsConfig,
80
+ hideLabel: true
81
+ })
82
+ })]
83
+ });
84
+ });
85
+ const DocumentSaveButton = React.memo(function DocumentSaveButton$1({ isPending }) {
86
+ const { t } = useTranslation();
87
+ const { isDirty, isSubmitting } = useFormState();
88
+ const busy = isPending || isSubmitting;
89
+ return /* @__PURE__ */ jsx(Button, {
90
+ type: "submit",
91
+ size: "sm",
92
+ disabled: busy || !isDirty,
93
+ className: "gap-2",
94
+ children: busy ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Icon, {
95
+ icon: "ph:spinner-gap",
96
+ className: "size-4 animate-spin"
97
+ }), t("common.loading")] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Icon, {
98
+ icon: "ph:check",
99
+ width: 16,
100
+ height: 16
101
+ }), t("common.save")] })
102
+ });
103
+ });
104
+ const DocumentAutosaveIndicator = React.memo(function DocumentAutosaveIndicator$1({ isSaving, lastSaved }) {
105
+ const { t } = useTranslation();
106
+ const { isDirty } = useFormState();
107
+ const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
108
+ React.useEffect(() => {
109
+ if (!lastSaved) return;
110
+ const interval = setInterval(forceUpdate, 1e4);
111
+ return () => clearInterval(interval);
112
+ }, [lastSaved]);
113
+ const formatTimeAgo = (date) => {
114
+ const seconds = Math.floor((Date.now() - date.getTime()) / 1e3);
115
+ if (seconds < 10) return t("autosave.justNow");
116
+ if (seconds < 60) return t("autosave.secondsAgo", { count: seconds });
117
+ const minutes = Math.floor(seconds / 60);
118
+ if (minutes < 60) return t("autosave.minutesAgo", { count: minutes });
119
+ return t("autosave.hoursAgo", { count: Math.floor(minutes / 60) });
120
+ };
121
+ if (isSaving) return /* @__PURE__ */ jsxs(Badge, {
122
+ variant: "secondary",
123
+ className: "gap-1.5",
124
+ children: [/* @__PURE__ */ jsx(Icon, {
125
+ icon: "ph:spinner-gap",
126
+ className: "size-3 animate-spin"
127
+ }), t("autosave.saving")]
128
+ });
129
+ if (isDirty) return /* @__PURE__ */ jsxs(Badge, {
130
+ variant: "outline",
131
+ className: "gap-1.5",
132
+ children: [/* @__PURE__ */ jsx(Icon, {
133
+ icon: "ph:clock-counter-clockwise",
134
+ className: "size-3"
135
+ }), t("autosave.unsavedChanges")]
136
+ });
137
+ if (lastSaved) return /* @__PURE__ */ jsxs(Badge, {
138
+ variant: "secondary",
139
+ className: "text-muted-foreground gap-1.5",
140
+ children: [
141
+ /* @__PURE__ */ jsx(Icon, {
142
+ icon: "ph:check",
143
+ className: "size-3"
144
+ }),
145
+ t("autosave.saved"),
146
+ " ",
147
+ formatTimeAgo(lastSaved)
148
+ ]
149
+ });
150
+ return null;
151
+ });
152
+ /**
153
+ * Resolve the ordered list of property field names.
154
+ * - `"auto"` → every schema field except the body and title, in schema order.
155
+ * - `string[]` → the explicit list (filtered to fields that exist).
156
+ */
157
+ function resolvePropertyNames(properties, resolvedFields, body, title) {
158
+ const allNames = Object.keys(resolvedFields);
159
+ if (Array.isArray(properties)) return properties.filter((name) => allNames.includes(name));
160
+ return allNames.filter((name) => name !== body && name !== title);
161
+ }
162
+ const DocumentForm = React.memo(function DocumentForm$1({ collection, documentConfig, propertyNames, resolvedFields, schema, registry, allCollectionsConfig, title, saveMode, isSaving, lastSaved, isMutationPending }) {
163
+ const resolveText = useResolveText();
164
+ const form = useFormContext();
165
+ const propertyRows = React.useMemo(() => {
166
+ const schemaFields = schema?.fields ?? {};
167
+ return propertyNames.map((name) => {
168
+ const fieldDef = resolvedFields[name];
169
+ if (!fieldDef) return null;
170
+ const meta = schemaFields[name]?.metadata;
171
+ const fieldType = fieldDef.name;
172
+ return {
173
+ name,
174
+ fieldDef,
175
+ label: resolveText(meta?.label ?? name, name, form.getValues()),
176
+ icon: (meta?.icon ? resolveIconElement(meta.icon, { className: "size-3.5" }) : null) ?? /* @__PURE__ */ jsx(Icon, {
177
+ icon: FIELD_TYPE_ICONS[fieldType] ?? DEFAULT_PROPERTY_ICON,
178
+ className: "size-3.5"
179
+ })
180
+ };
181
+ }).filter(Boolean);
182
+ }, [
183
+ propertyNames,
184
+ resolvedFields,
185
+ schema?.fields,
186
+ resolveText,
187
+ form
188
+ ]);
189
+ const bodyFieldDef = resolvedFields[documentConfig.body];
190
+ return /* @__PURE__ */ jsxs("div", {
191
+ className: "qa-document-view mx-auto max-w-3xl px-6 py-8",
192
+ children: [
193
+ /* @__PURE__ */ jsx(AdminViewHeader, {
194
+ className: "mb-6",
195
+ title,
196
+ titleAccessory: saveMode === "autosave" ? /* @__PURE__ */ jsx(DocumentAutosaveIndicator, {
197
+ isSaving,
198
+ lastSaved
199
+ }) : /* @__PURE__ */ jsx(DocumentSaveButton, { isPending: isMutationPending })
200
+ }),
201
+ propertyRows.length > 0 && /* @__PURE__ */ jsx("div", {
202
+ className: "qa-document-view__properties mb-6 space-y-px",
203
+ children: propertyRows.map((row) => /* @__PURE__ */ jsx(DocumentPropertyRow, {
204
+ collection,
205
+ fieldName: row.name,
206
+ fieldDef: row.fieldDef,
207
+ icon: row.icon,
208
+ label: row.label,
209
+ registry,
210
+ allCollectionsConfig
211
+ }, row.name))
212
+ }),
213
+ /* @__PURE__ */ jsx(Separator, { className: "mb-8" }),
214
+ /* @__PURE__ */ jsx("div", {
215
+ className: "qa-document-view__body min-w-0",
216
+ children: bodyFieldDef ? /* @__PURE__ */ jsx(FieldRenderer, {
217
+ fieldName: documentConfig.body,
218
+ fieldDef: bodyFieldDef,
219
+ collection,
220
+ registry,
221
+ allCollectionsConfig,
222
+ hideLabel: true
223
+ }) : /* @__PURE__ */ jsx(EmptyState, {
224
+ iconName: "ph:warning",
225
+ title: `Document body field "${documentConfig.body}" not found.`
226
+ })
227
+ })
228
+ ]
229
+ });
230
+ });
231
+ /**
232
+ * The form-bearing inner component. The outer `DocumentView` gates on loading
233
+ * and mounts this with `initialValues` already populated, so `useForm` — and
234
+ * therefore the TipTap body editor (`content: value`) — initializes WITH the
235
+ * stored content on its very first render. This is the fix for the empty-body /
236
+ * spurious "unsaved changes" bug: we never mount the editor empty and then try
237
+ * to re-sync a value-prop change into ProseMirror.
238
+ *
239
+ * Remounted (via `key`) by the outer when the record identity changes, so each
240
+ * record gets a fresh form seeded with its own values.
241
+ */
242
+ function DocumentEditor({ collection, id, documentConfig, propertyNames, resolvedFields, schema, registry, allCollectionsConfig, resolver, updateMutation, initialValues, title, autosaveDebounce }) {
243
+ const { t } = useTranslation();
244
+ const saveMode = documentConfig.save === "manual" ? "manual" : "autosave";
245
+ const form = useForm({
246
+ defaultValues: initialValues,
247
+ resolver,
248
+ mode: "onBlur"
249
+ });
250
+ const formIsDirtyRef = React.useRef(false);
251
+ const formIsSubmittingRef = React.useRef(false);
252
+ const { isDirty: formIsDirty, isSubmitting: formIsSubmitting } = useFormState({ control: form.control });
253
+ React.useEffect(() => {
254
+ formIsDirtyRef.current = formIsDirty;
255
+ }, [formIsDirty]);
256
+ React.useEffect(() => {
257
+ formIsSubmittingRef.current = formIsSubmitting;
258
+ }, [formIsSubmitting]);
259
+ const [isSaving, setIsSaving] = React.useState(false);
260
+ const [lastSaved, setLastSaved] = React.useState(null);
261
+ useAutosave({
262
+ form,
263
+ id,
264
+ enabled: saveMode === "autosave",
265
+ debounce: autosaveDebounce,
266
+ isDirtyRef: formIsDirtyRef,
267
+ isSubmittingRef: formIsSubmittingRef,
268
+ updateMutation,
269
+ onSavingChange: setIsSaving,
270
+ onSaved: setLastSaved
271
+ });
272
+ const onManualSubmit = form.handleSubmit(async (data) => {
273
+ if (!id) return;
274
+ try {
275
+ const result = await updateMutation.mutateAsync({
276
+ id,
277
+ data
278
+ });
279
+ form.reset(result, { keepTouched: true });
280
+ setLastSaved(/* @__PURE__ */ new Date());
281
+ toast.success(t("toast.saveSuccess"));
282
+ } catch (error) {
283
+ toast.error(t("toast.saveFailed"), { description: error instanceof Error ? error.message : void 0 });
284
+ }
285
+ });
286
+ return /* @__PURE__ */ jsx(FormProvider, {
287
+ ...form,
288
+ children: /* @__PURE__ */ jsx("form", {
289
+ onSubmit: (e) => {
290
+ e.stopPropagation();
291
+ if (saveMode === "manual") onManualSubmit(e);
292
+ else e.preventDefault();
293
+ },
294
+ children: /* @__PURE__ */ jsx(DocumentForm, {
295
+ collection,
296
+ documentConfig,
297
+ propertyNames,
298
+ resolvedFields,
299
+ schema,
300
+ registry,
301
+ allCollectionsConfig,
302
+ title,
303
+ saveMode,
304
+ isSaving,
305
+ lastSaved,
306
+ isMutationPending: updateMutation.isPending
307
+ })
308
+ })
309
+ });
310
+ }
311
+ function DocumentView({ collection, id, config, viewConfig, defaultValues: defaultValuesProp, registry, allCollectionsConfig, title: titleProp }) {
312
+ const { t } = useTranslation();
313
+ const isEditMode = !!id;
314
+ const collectionKey = adminCollectionKey(collection);
315
+ const { fields: resolvedFields, schema, isLoading: isFieldsLoading } = useCollectionFields(collection, { fallbackFields: config?.fields });
316
+ const documentConfig = React.useMemo(() => viewConfig?.document ?? (schema?.admin?.form)?.document ?? config?.form?.document, [
317
+ viewConfig,
318
+ schema?.admin?.form,
319
+ config
320
+ ]);
321
+ const propertyNames = React.useMemo(() => documentConfig ? resolvePropertyNames(documentConfig.properties, resolvedFields, documentConfig.body, documentConfig.title) : [], [documentConfig, resolvedFields]);
322
+ const { data: item, isLoading, error: itemError } = useCollectionItem(collectionKey, id ?? "", { localeFallback: false }, { enabled: isEditMode });
323
+ const updateMutation = useCollectionUpdate(collectionKey);
324
+ const hasServerValidationSchema = !!schema?.validation?.update;
325
+ const clientResolver = useCollectionValidation(collection, { enabled: !isFieldsLoading && !hasServerValidationSchema });
326
+ const resolver = usePreferServerValidation(collection, {
327
+ mode: "update",
328
+ schema
329
+ }, clientResolver);
330
+ const autosaveDebounce = config?.autoSave?.debounce ?? 300;
331
+ if (!documentConfig) return /* @__PURE__ */ jsx(AdminViewLayout, {
332
+ contentClassName: "overflow-y-auto",
333
+ children: /* @__PURE__ */ jsx("div", {
334
+ className: "mx-auto max-w-3xl px-6 py-8",
335
+ children: /* @__PURE__ */ jsx(EmptyState, {
336
+ iconName: "ph:warning",
337
+ title: "No document configuration",
338
+ description: "This view has kind 'document' but no document config (body/properties). Configure it via view(..., { kind: 'document', document: { body, properties } })."
339
+ })
340
+ })
341
+ });
342
+ if (isEditMode && itemError) return /* @__PURE__ */ jsx(AdminViewLayout, {
343
+ contentClassName: "overflow-y-auto",
344
+ children: /* @__PURE__ */ jsx("div", {
345
+ className: "mx-auto max-w-3xl px-6 py-8",
346
+ children: /* @__PURE__ */ jsx(EmptyState, {
347
+ iconName: "ph:warning",
348
+ title: t("error.failedToLoad"),
349
+ description: itemError instanceof Error ? itemError.message : void 0
350
+ })
351
+ })
352
+ });
353
+ if (isEditMode && (isLoading || !item) || isFieldsLoading) return /* @__PURE__ */ jsx(FormViewSkeleton, {});
354
+ const initialValues = isEditMode ? item : defaultValuesProp ?? {};
355
+ const titleFieldValue = documentConfig.title ? initialValues[documentConfig.title] : void 0;
356
+ return /* @__PURE__ */ jsx(AdminViewLayout, {
357
+ contentClassName: "overflow-y-auto",
358
+ children: /* @__PURE__ */ jsx(DocumentEditor, {
359
+ collection,
360
+ id,
361
+ documentConfig,
362
+ propertyNames,
363
+ resolvedFields,
364
+ schema,
365
+ registry,
366
+ allCollectionsConfig,
367
+ resolver,
368
+ updateMutation,
369
+ initialValues,
370
+ title: (titleFieldValue && titleFieldValue.trim() ? titleFieldValue : void 0) ?? titleProp ?? initialValues.id ?? collection,
371
+ autosaveDebounce
372
+ }, id ?? "new")
373
+ });
374
+ }
375
+
376
+ //#endregion
377
+ export { DocumentView as default };
@@ -105,7 +105,7 @@ function getFieldContext({ fieldName, fieldDef, collection, form, fieldPrefix, l
105
105
  * Build props for a FieldInstance component (field.component).
106
106
  * Returns raw props with I18nText - resolve before passing to component.
107
107
  */
108
- function buildComponentProps(context) {
108
+ function buildComponentProps(context, overrides) {
109
109
  return {
110
110
  name: context.fullFieldName,
111
111
  value: context.fieldValue,
@@ -118,7 +118,8 @@ function buildComponentProps(context) {
118
118
  readOnly: context.isReadOnly,
119
119
  error: context.fieldError,
120
120
  localized: context.isLocalized,
121
- locale: context.locale
121
+ locale: context.locale,
122
+ hideLabel: overrides?.hideLabel
122
123
  };
123
124
  }
124
125
 
@@ -138,7 +138,7 @@ function renderEmbeddedField({ context, registry, allCollectionsConfig, componen
138
138
  * 2. FieldDefinition.field.component (registry-first approach)
139
139
  * 3. Error message if no component registered
140
140
  */
141
- function FieldRenderer({ fieldName, fieldDef, collection, mode = "collection", registry, fieldPrefix, allCollectionsConfig, renderEmbeddedFields, className, entityMeta: entityMetaProp, extraProps }) {
141
+ function FieldRenderer({ fieldName, fieldDef, collection, mode = "collection", registry, fieldPrefix, allCollectionsConfig, renderEmbeddedFields, className, entityMeta: entityMetaProp, extraProps, hideLabel }) {
142
142
  const form = useFormContext();
143
143
  const { locale } = useScopedLocale();
144
144
  const resolveText = useResolveText();
@@ -198,7 +198,7 @@ function FieldRenderer({ fieldName, fieldDef, collection, mode = "collection", r
198
198
  staticOptions: context.options
199
199
  });
200
200
  const resolvedOptions = hookOptions ?? context.options;
201
- const rawComponentProps = buildComponentProps(context);
201
+ const rawComponentProps = buildComponentProps(context, { hideLabel });
202
202
  const { props: resolvedFieldProps } = useReactiveProps({
203
203
  entity: collection,
204
204
  entityType: mode,
@@ -29,6 +29,7 @@ import { useCollectionValidation } from "../../hooks/use-collection-validation.m
29
29
  import { useSearchParamToggle } from "../../hooks/use-search-param-toggle.mjs";
30
30
  import { usePreferServerValidation } from "../../hooks/use-server-validation.mjs";
31
31
  import { useSidebarSearchParam } from "../../hooks/use-sidebar-search-param.mjs";
32
+ import { useAutosave } from "../../hooks/use-autosave.mjs";
32
33
  import { useCollectionCreate, useCollectionDelete, useCollectionItem, useCollectionRestore, useCollectionRevertVersion, useCollectionUpdate, useCollectionVersions } from "../../hooks/use-collection.mjs";
33
34
  import { getLockUser, useLock } from "../../hooks/use-locks.mjs";
34
35
  import { useServerActions } from "../../hooks/use-server-actions.mjs";
@@ -248,71 +249,6 @@ const PreviewPatchBridge = React.memo(function PreviewPatchBridge$1({ form, prev
248
249
  ]);
249
250
  return null;
250
251
  });
251
- const AutosaveManager = React.memo(function AutosaveManager$1({ form, formElementRef, isEditMode, id, enabled, debounce, isDirtyRef, isSubmittingRef, updateMutation, onPreviewRefresh, onPreviewCommit, onSavingChange, onSaved }) {
252
- const { t } = useTranslation();
253
- const timerRef = React.useRef(null);
254
- const runAutosave = React.useCallback(async () => {
255
- if (!id || !isDirtyRef.current || isSubmittingRef.current) return;
256
- try {
257
- onSavingChange(true);
258
- await form.handleSubmit(async (data) => {
259
- const result = await updateMutation.mutateAsync({
260
- id,
261
- data
262
- });
263
- form.reset(result, { keepTouched: true });
264
- onPreviewCommit?.(result);
265
- onPreviewRefresh?.();
266
- onSaved(/* @__PURE__ */ new Date());
267
- onSavingChange(false);
268
- }, () => {
269
- onSavingChange(false);
270
- })();
271
- } catch (error) {
272
- onSavingChange(false);
273
- console.error("Autosave failed:", error);
274
- toast.error(t("error.autosaveFailed"), { description: error instanceof Error ? error.message : void 0 });
275
- }
276
- }, [
277
- form,
278
- id,
279
- isDirtyRef,
280
- isSubmittingRef,
281
- onSaved,
282
- onSavingChange,
283
- onPreviewCommit,
284
- onPreviewRefresh,
285
- t,
286
- updateMutation
287
- ]);
288
- React.useEffect(() => {
289
- if (timerRef.current) clearTimeout(timerRef.current);
290
- if (!enabled || !isEditMode || !id) return;
291
- const target = formElementRef.current;
292
- if (!target) return;
293
- const scheduleAutosave = () => {
294
- if (timerRef.current) clearTimeout(timerRef.current);
295
- timerRef.current = setTimeout(() => {
296
- runAutosave();
297
- }, debounce);
298
- };
299
- target.addEventListener("input", scheduleAutosave, { capture: true });
300
- target.addEventListener("change", scheduleAutosave, { capture: true });
301
- return () => {
302
- target.removeEventListener("input", scheduleAutosave, { capture: true });
303
- target.removeEventListener("change", scheduleAutosave, { capture: true });
304
- if (timerRef.current) clearTimeout(timerRef.current);
305
- };
306
- }, [
307
- debounce,
308
- enabled,
309
- formElementRef,
310
- id,
311
- isEditMode,
312
- runAutosave
313
- ]);
314
- return null;
315
- });
316
252
  const AutosaveIndicator = React.memo(function AutosaveIndicator$1({ control, enabled, indicator, isEditMode, isSaving, lastSaved, formatTimeAgo, t }) {
317
253
  const { isDirty } = useFormState({ control });
318
254
  const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
@@ -603,6 +539,19 @@ function FormView({ collection, id, config, viewConfig, navigate, basePath = "/a
603
539
  preventNavigation: cfg.preventNavigation !== false
604
540
  };
605
541
  }, [config]);
542
+ useAutosave({
543
+ form,
544
+ id,
545
+ enabled: autoSaveConfig.enabled,
546
+ debounce: autoSaveConfig.debounce,
547
+ isDirtyRef: formIsDirtyRef,
548
+ isSubmittingRef: formIsSubmittingRef,
549
+ updateMutation,
550
+ onPreviewCommit: commitPreviewSnapshot,
551
+ onPreviewRefresh: triggerPreviewRefresh,
552
+ onSavingChange: setIsSaving,
553
+ onSaved: setLastSaved
554
+ });
606
555
  const { locale: contentLocale, setLocale: setContentLocale } = useScopedLocale();
607
556
  const localeOptions = useSafeContentLocales()?.locales ?? [];
608
557
  const prevLocaleRef = React.useRef(contentLocale);
@@ -1218,21 +1167,6 @@ function FormView({ collection, id, config, viewConfig, navigate, basePath = "/a
1218
1167
  onDirtyChange: handleFormDirtyChange,
1219
1168
  onSubmittingChange: handleFormSubmittingChange
1220
1169
  }),
1221
- /* @__PURE__ */ jsx(AutosaveManager, {
1222
- form,
1223
- formElementRef,
1224
- isEditMode,
1225
- id,
1226
- enabled: autoSaveConfig.enabled,
1227
- debounce: autoSaveConfig.debounce,
1228
- isDirtyRef: formIsDirtyRef,
1229
- isSubmittingRef: formIsSubmittingRef,
1230
- updateMutation,
1231
- onPreviewCommit: commitPreviewSnapshot,
1232
- onPreviewRefresh: triggerPreviewRefresh,
1233
- onSavingChange: setIsSaving,
1234
- onSaved: setLastSaved
1235
- }),
1236
1170
  /* @__PURE__ */ jsx(PreviewPatchBridge, {
1237
1171
  form,
1238
1172
  previewRef,