@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
package/README.md CHANGED
@@ -23,8 +23,7 @@ Add the admin module to `modules.ts`:
23
23
 
24
24
  ```ts
25
25
  // questpie.config.ts
26
- import { runtimeConfig } from "questpie";
27
-
26
+ import { runtimeConfig } from "questpie/app";
28
27
  export default runtimeConfig({
29
28
  app: { url: process.env.APP_URL! },
30
29
  db: { url: process.env.DATABASE_URL! },
@@ -34,8 +33,7 @@ export default runtimeConfig({
34
33
 
35
34
  ```ts
36
35
  // modules.ts
37
- import { adminModule } from "@questpie/admin/server";
38
-
36
+ import { adminModule } from "@questpie/admin/modules/admin";
39
37
  export default [adminModule] as const;
40
38
  ```
41
39
 
@@ -477,8 +475,8 @@ import {
477
475
  } from "@questpie/admin/client";
478
476
 
479
477
  // Server (admin module + server factories/config)
480
- import { adminModule, auditModule } from "@questpie/admin/server";
481
-
478
+ import { adminModule } from "@questpie/admin/modules/admin";
479
+ import { auditModule } from "@questpie/admin/modules/audit";
482
480
  // Styles
483
481
  import "@questpie/admin/client/styles/index.css";
484
482
  ```
@@ -1,6 +1,6 @@
1
1
  import { BlockContent } from "./types.mjs";
2
2
  import * as React from "react";
3
- import * as react_jsx_runtime19 from "react/jsx-runtime";
3
+ import * as react_jsx_runtime20 from "react/jsx-runtime";
4
4
 
5
5
  //#region src/client/blocks/block-renderer.d.ts
6
6
 
@@ -50,6 +50,6 @@ declare function BlockRenderer({
50
50
  onBlockClick,
51
51
  onBlockInsert,
52
52
  className
53
- }: BlockRendererProps): react_jsx_runtime19.JSX.Element | null;
53
+ }: BlockRendererProps): react_jsx_runtime20.JSX.Element | null;
54
54
  //#endregion
55
55
  export { BlockRenderer, BlockRendererProps };
@@ -1,10 +1,10 @@
1
- import { SimpleMessages } from "../i18n/simple.mjs";
2
1
  import { MaybeLazyComponent } from "./types/common.mjs";
2
+ import { ViewDefinition } from "./view/view.mjs";
3
3
  import { FieldDefinition } from "./field/field.mjs";
4
4
  import { PageDefinition } from "./page/page.mjs";
5
- import { LocaleConfig } from "./types/ui-config.mjs";
6
- import { ViewDefinition } from "./view/view.mjs";
7
5
  import { WidgetDefinition } from "./widget/widget.mjs";
6
+ import { SimpleMessages } from "../i18n/simple.mjs";
7
+ import { LocaleConfig } from "./types/ui-config.mjs";
8
8
 
9
9
  //#region src/client/builder/admin-types.d.ts
10
10
 
@@ -1,8 +1,8 @@
1
1
  import { I18nText } from "../../i18n/types.mjs";
2
2
  import { IconComponent, MaybeLazyComponent } from "./common.mjs";
3
- import { FieldInstance } from "../field/field.mjs";
4
3
  import { ComponentReference } from "../../../server/augmentation/common.mjs";
5
4
  import "../../../server/augmentation.mjs";
5
+ import { FieldInstance } from "../field/field.mjs";
6
6
 
7
7
  //#region src/client/builder/types/action-types.d.ts
8
8
 
@@ -1,7 +1,7 @@
1
1
  import "../../i18n/types.mjs";
2
2
  import { MaybeLazyComponent } from "./common.mjs";
3
- import "../field/field.mjs";
4
3
  import "../../../server/augmentation.mjs";
4
+ import "../field/field.mjs";
5
5
  import "../admin.mjs";
6
6
  import { ActionsConfig } from "./action-types.mjs";
7
7
 
@@ -60,6 +60,51 @@ interface ColumnConfigObject<TFieldNames extends string = string> {
60
60
  */
61
61
  align?: "left" | "center" | "right";
62
62
  }
63
+ interface ListViewLayoutConfig<TFieldNames extends string = string> {
64
+ density?: "compact" | "comfortable";
65
+ titleField?: TFieldNames;
66
+ subtitleField?: TFieldNames;
67
+ leadingFields?: TFieldNames[];
68
+ badgeFields?: TFieldNames[];
69
+ metaFields?: TFieldNames[];
70
+ }
71
+ type ListViewOutlineLevel<TFieldNames extends string = string> = {
72
+ kind: "field";
73
+ field: TFieldNames;
74
+ labelField?: TFieldNames;
75
+ order?: "asc" | "desc" | string[];
76
+ } | {
77
+ kind: "relation-field";
78
+ relation: TFieldNames;
79
+ field?: string;
80
+ labelField?: string;
81
+ order?: "asc" | "desc" | string[];
82
+ } | {
83
+ kind: "edge";
84
+ collection: string;
85
+ parentField: string;
86
+ childField: string;
87
+ where?: Record<string, unknown>;
88
+ groupByEdgeField?: string;
89
+ repeat?: boolean | {
90
+ maxDepth?: number;
91
+ };
92
+ } | {
93
+ kind: "path";
94
+ field: TFieldNames;
95
+ separator?: string;
96
+ syntheticFolders?: boolean;
97
+ repeat?: boolean | {
98
+ maxDepth?: number;
99
+ };
100
+ };
101
+ interface ListViewOutlineConfig<TFieldNames extends string = string> {
102
+ levels: ListViewOutlineLevel<TFieldNames>[];
103
+ defaultExpanded?: boolean | "roots";
104
+ maxDepth?: number;
105
+ showCounts?: boolean;
106
+ preserveMatchingBranches?: boolean;
107
+ }
63
108
  /**
64
109
  * List view configuration
65
110
  */
@@ -91,11 +136,15 @@ interface ListViewConfig<TFieldNames extends string = string> {
91
136
  * Enable search
92
137
  * @default true
93
138
  */
94
- searchable?: boolean;
139
+ searchable?: boolean | TFieldNames[];
95
140
  /**
96
141
  * Searchable fields (defaults to all text-like fields)
97
142
  */
98
143
  searchFields?: TFieldNames[];
144
+ /**
145
+ * Filterable fields exposed by schema-driven list views.
146
+ */
147
+ filterable?: TFieldNames[];
99
148
  /**
100
149
  * Enable row selection
101
150
  * @default false
@@ -131,6 +180,14 @@ interface ListViewConfig<TFieldNames extends string = string> {
131
180
  defaultCollapsed?: boolean;
132
181
  showCounts?: boolean;
133
182
  };
183
+ /**
184
+ * Dense list row layout hints used by list-like renderers.
185
+ */
186
+ layout?: ListViewLayoutConfig<TFieldNames>;
187
+ /**
188
+ * Renderer-agnostic multi-level outline/grouping configuration.
189
+ */
190
+ outline?: ListViewOutlineConfig<TFieldNames>;
134
191
  /**
135
192
  * Actions configuration for list view
136
193
  */
@@ -13,6 +13,17 @@ import { jsx } from "react/jsx-runtime";
13
13
  *
14
14
  * Provides selector-driven state and actions for the block editor.
15
15
  */
16
+ function areSetsEqual(left, right) {
17
+ if (left === right) return true;
18
+ if (left.size !== right.size) return false;
19
+ for (const value of left) if (!right.has(value)) return false;
20
+ return true;
21
+ }
22
+ function areInsertPositionsEqual(left, right) {
23
+ if (left === right) return true;
24
+ if (!left || !right) return left === right;
25
+ return left.parentId === right.parentId && left.index === right.index;
26
+ }
16
27
  function BlockEditorProvider({ value, onChange, blocks, allowedBlocks, locale = "en", children }) {
17
28
  const onChangeRef = React.useRef(onChange);
18
29
  const previewFocusState = useFocusOptional()?.state;
@@ -173,6 +184,7 @@ function BlockEditorProvider({ value, onChange, blocks, allowedBlocks, locale =
173
184
  for (const blockId of blockPath) expandedBlockIds.add(blockId);
174
185
  }
175
186
  if (previewFocusState.type === "block-insert") {
187
+ if (state.selectedBlockId === targetBlockId && state.isLibraryOpen === true && areInsertPositionsEqual(state.insertPosition, previewFocusState.position) && areSetsEqual(state.expandedBlockIds, expandedBlockIds)) return;
176
188
  store.setState({
177
189
  selectedBlockId: targetBlockId,
178
190
  isLibraryOpen: true,
@@ -181,6 +193,7 @@ function BlockEditorProvider({ value, onChange, blocks, allowedBlocks, locale =
181
193
  });
182
194
  return;
183
195
  }
196
+ if (state.selectedBlockId === previewFocusState.blockId && state.isLibraryOpen === false && state.insertPosition === null && areSetsEqual(state.expandedBlockIds, expandedBlockIds)) return;
184
197
  store.setState({
185
198
  selectedBlockId: previewFocusState.blockId,
186
199
  isLibraryOpen: false,
@@ -4,11 +4,11 @@ import { Button } from "../ui/button.mjs";
4
4
  import { Input } from "../ui/input.mjs";
5
5
  import { Select, SelectContent, SelectItem, SelectTrigger } from "../ui/select.mjs";
6
6
  import { Textarea } from "../ui/textarea.mjs";
7
- import { LocaleBadge } from "./locale-badge.mjs";
7
+ import { FieldCountAccessory, FieldWrapper } from "./field-wrapper.mjs";
8
8
  import { ObjectArrayField } from "./object-array-field.mjs";
9
9
  import { Icon } from "@iconify/react";
10
10
  import * as React from "react";
11
- import { jsx, jsxs } from "react/jsx-runtime";
11
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
12
12
  import { useFieldArray, useFormContext, useWatch } from "react-hook-form";
13
13
 
14
14
  //#region src/client/components/fields/array-field.tsx
@@ -70,13 +70,34 @@ function ArrayItemInput({ name, index, itemType, itemValue, placeholder, disable
70
70
  ...form.register(itemName, itemType === "number" ? { valueAsNumber: true } : void 0)
71
71
  });
72
72
  }
73
- function ArrayField({ name, value, label, description, placeholder, required, disabled, readOnly, error, localized, locale, itemType = "text", options, orderable = false, minItems, maxItems, item, mode, layout, columns, itemLabel }) {
73
+ function ArrayField(props) {
74
+ if (props.item) return /* @__PURE__ */ jsx(ObjectArrayField, {
75
+ name: props.name,
76
+ label: props.label,
77
+ description: props.description,
78
+ placeholder: props.placeholder,
79
+ required: props.required,
80
+ disabled: props.disabled,
81
+ readOnly: props.readOnly,
82
+ error: props.error,
83
+ localized: props.localized,
84
+ locale: props.locale,
85
+ item: props.item,
86
+ mode: props.mode,
87
+ layout: props.layout,
88
+ columns: props.columns,
89
+ itemLabel: props.itemLabel,
90
+ orderable: props.orderable,
91
+ minItems: props.minItems,
92
+ maxItems: props.maxItems
93
+ });
94
+ return /* @__PURE__ */ jsx(PrimitiveArrayField, { ...props });
95
+ }
96
+ function PrimitiveArrayField({ name, value, label, description, placeholder, required, disabled, readOnly, error, localized, locale, itemType = "text", options, orderable = false, minItems, maxItems }) {
74
97
  const { t } = useTranslation();
75
98
  const resolveText = useResolveText();
76
99
  const resolvedPlaceholder = placeholder ? resolveText(placeholder) : void 0;
77
- const resolvedDescription = description ? resolveText(description) : void 0;
78
- const resolvedLabel = label ? resolveText(label) : void 0;
79
- const fallbackLabel = resolvedLabel || "item";
100
+ const fallbackLabel = (label ? resolveText(label) : void 0) || "item";
80
101
  const emptyLabel = t("array.empty", { name: fallbackLabel });
81
102
  const addLabel = t("array.addItem", { name: fallbackLabel });
82
103
  const { control } = useFormContext();
@@ -95,24 +116,6 @@ function ArrayField({ name, value, label, description, placeholder, required, di
95
116
  if (itemType === "select") return options?.[0]?.value ?? "";
96
117
  return "";
97
118
  }, [itemType, options]);
98
- if (item) return /* @__PURE__ */ jsx(ObjectArrayField, {
99
- name,
100
- label,
101
- description,
102
- placeholder,
103
- required,
104
- disabled,
105
- localized,
106
- locale,
107
- item,
108
- mode,
109
- layout,
110
- columns,
111
- itemLabel,
112
- orderable,
113
- minItems,
114
- maxItems
115
- });
116
119
  const handleAdd = () => {
117
120
  if (disabled || readOnly || !canAddMore) return;
118
121
  append(createEmptyItem());
@@ -125,95 +128,80 @@ function ArrayField({ name, value, label, description, placeholder, required, di
125
128
  if (to < 0 || to >= fields.length) return;
126
129
  move(from, to);
127
130
  };
128
- return /* @__PURE__ */ jsxs("div", {
129
- className: "qa-array-field space-y-2",
130
- children: [
131
- label && /* @__PURE__ */ jsxs("div", {
132
- className: "flex items-center gap-2",
133
- children: [/* @__PURE__ */ jsxs("label", {
134
- htmlFor: name,
135
- className: "text-sm font-medium",
131
+ return /* @__PURE__ */ jsx(FieldWrapper, {
132
+ name,
133
+ label,
134
+ description,
135
+ required,
136
+ disabled,
137
+ readOnly,
138
+ error,
139
+ localized,
140
+ locale,
141
+ labelAccessory: /* @__PURE__ */ jsx(FieldCountAccessory, {
142
+ count: fields.length,
143
+ max: maxItems
144
+ }),
145
+ children: /* @__PURE__ */ jsxs("div", {
146
+ className: "qa-array-field space-y-3",
147
+ children: [fields.length === 0 ? /* @__PURE__ */ jsx("div", {
148
+ className: "panel-surface border-border-subtle border-dashed px-3 py-3",
149
+ children: /* @__PURE__ */ jsx("p", {
150
+ className: "text-muted-foreground text-sm text-pretty",
151
+ children: resolvedPlaceholder || emptyLabel
152
+ })
153
+ }) : fields.map((field, index) => {
154
+ const canMoveUp = orderable && index > 0;
155
+ const canMoveDown = orderable && index < fields.length - 1;
156
+ return /* @__PURE__ */ jsxs("div", {
157
+ className: "panel-surface flex min-w-0 items-start gap-2 p-2",
136
158
  children: [
137
- resolvedLabel,
138
- required && /* @__PURE__ */ jsx("span", {
139
- className: "text-destructive",
140
- children: "*"
159
+ /* @__PURE__ */ jsxs("span", {
160
+ className: "text-muted-foreground mt-2 min-w-6 shrink-0 text-xs tabular-nums",
161
+ children: ["#", index + 1]
141
162
  }),
142
- maxItems && /* @__PURE__ */ jsxs("span", {
143
- className: "text-muted-foreground ml-2 text-xs tabular-nums",
144
- children: [
145
- "(",
146
- fields.length,
147
- "/",
148
- maxItems,
149
- ")"
150
- ]
151
- })
152
- ]
153
- }), localized && /* @__PURE__ */ jsx(LocaleBadge, { locale: locale || "i18n" })]
154
- }),
155
- resolvedDescription && /* @__PURE__ */ jsx("p", {
156
- className: "text-muted-foreground text-sm text-pretty",
157
- children: resolvedDescription
158
- }),
159
- /* @__PURE__ */ jsx("div", {
160
- className: "space-y-2",
161
- children: fields.length === 0 ? /* @__PURE__ */ jsx("div", {
162
- className: "py-2",
163
- children: /* @__PURE__ */ jsx("p", {
164
- className: "text-muted-foreground text-sm text-pretty",
165
- children: resolvedPlaceholder || emptyLabel
166
- })
167
- }) : fields.map((field, index) => {
168
- const canMoveUp = orderable && index > 0;
169
- const canMoveDown = orderable && index < fields.length - 1;
170
- return /* @__PURE__ */ jsxs("div", {
171
- className: "flex items-start gap-2",
172
- children: [
173
- /* @__PURE__ */ jsx("div", {
174
- className: "flex-1",
175
- children: /* @__PURE__ */ jsx(ArrayItemInput, {
176
- name,
177
- index,
178
- itemType,
179
- itemValue: values?.[index],
180
- placeholder: resolvedPlaceholder,
181
- disabled,
182
- readOnly,
183
- options
163
+ /* @__PURE__ */ jsx("div", {
164
+ className: "min-w-0 flex-1",
165
+ children: /* @__PURE__ */ jsx(ArrayItemInput, {
166
+ name,
167
+ index,
168
+ itemType,
169
+ itemValue: values?.[index],
170
+ placeholder: resolvedPlaceholder,
171
+ disabled,
172
+ readOnly,
173
+ options
174
+ })
175
+ }),
176
+ !readOnly && (orderable || canRemove) && /* @__PURE__ */ jsxs("div", {
177
+ className: "flex shrink-0 items-center gap-1",
178
+ children: [orderable && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Button, {
179
+ type: "button",
180
+ variant: "ghost",
181
+ size: "icon-sm",
182
+ className: "relative after:absolute after:-inset-1",
183
+ onClick: () => handleMove(index, index - 1),
184
+ disabled: !canMoveUp || disabled,
185
+ title: t("field.moveUp"),
186
+ "aria-label": t("field.moveUp"),
187
+ children: /* @__PURE__ */ jsx(Icon, {
188
+ icon: "ph:caret-up",
189
+ className: "size-3.5"
190
+ })
191
+ }), /* @__PURE__ */ jsx(Button, {
192
+ type: "button",
193
+ variant: "ghost",
194
+ size: "icon-sm",
195
+ className: "relative after:absolute after:-inset-1",
196
+ onClick: () => handleMove(index, index + 1),
197
+ disabled: !canMoveDown || disabled,
198
+ title: t("field.moveDown"),
199
+ "aria-label": t("field.moveDown"),
200
+ children: /* @__PURE__ */ jsx(Icon, {
201
+ icon: "ph:caret-down",
202
+ className: "size-3.5"
184
203
  })
185
- }),
186
- orderable && !readOnly && /* @__PURE__ */ jsxs("div", {
187
- className: "flex flex-col gap-1",
188
- children: [/* @__PURE__ */ jsx(Button, {
189
- type: "button",
190
- variant: "ghost",
191
- size: "icon-sm",
192
- className: "relative after:absolute after:-inset-1",
193
- onClick: () => handleMove(index, index - 1),
194
- disabled: !canMoveUp || disabled,
195
- title: t("field.moveUp"),
196
- "aria-label": t("field.moveUp"),
197
- children: /* @__PURE__ */ jsx(Icon, {
198
- icon: "ph:caret-up",
199
- className: "size-3.5"
200
- })
201
- }), /* @__PURE__ */ jsx(Button, {
202
- type: "button",
203
- variant: "ghost",
204
- size: "icon-sm",
205
- className: "relative after:absolute after:-inset-1",
206
- onClick: () => handleMove(index, index + 1),
207
- disabled: !canMoveDown || disabled,
208
- title: t("field.moveDown"),
209
- "aria-label": t("field.moveDown"),
210
- children: /* @__PURE__ */ jsx(Icon, {
211
- icon: "ph:caret-down",
212
- className: "size-3.5"
213
- })
214
- })]
215
- }),
216
- !readOnly && canRemove && /* @__PURE__ */ jsx(Button, {
204
+ })] }), canRemove && /* @__PURE__ */ jsx(Button, {
217
205
  type: "button",
218
206
  variant: "ghost",
219
207
  size: "icon-sm",
@@ -226,12 +214,11 @@ function ArrayField({ name, value, label, description, placeholder, required, di
226
214
  icon: "ph:trash",
227
215
  className: "size-3.5"
228
216
  })
229
- })
230
- ]
231
- }, field.id);
232
- })
233
- }),
234
- !readOnly && canAddMore && /* @__PURE__ */ jsxs(Button, {
217
+ })]
218
+ })
219
+ ]
220
+ }, field.id);
221
+ }), !readOnly && canAddMore && /* @__PURE__ */ jsxs(Button, {
235
222
  type: "button",
236
223
  variant: "outline",
237
224
  onClick: handleAdd,
@@ -240,12 +227,8 @@ function ArrayField({ name, value, label, description, placeholder, required, di
240
227
  icon: "ph:plus",
241
228
  className: "h-4 w-4"
242
229
  }), addLabel]
243
- }),
244
- error && /* @__PURE__ */ jsx("p", {
245
- className: "text-destructive text-sm text-pretty",
246
- children: error
247
- })
248
- ]
230
+ })]
231
+ })
249
232
  });
250
233
  }
251
234
 
@@ -1,6 +1,6 @@
1
1
  import { cn } from "../../lib/utils.mjs";
2
- import { AssetThumbnail } from "../../views/collection/cells/shared/asset-thumbnail.mjs";
3
2
  import { FieldWrapper } from "./field-wrapper.mjs";
3
+ import { AssetThumbnail } from "../../views/collection/cells/shared/asset-thumbnail.mjs";
4
4
  import "react";
5
5
  import { jsx } from "react/jsx-runtime";
6
6
  import { useFormContext, useWatch } from "react-hook-form";
@@ -1,9 +1,9 @@
1
1
  "use client";
2
2
 
3
3
  import { useTranslation } from "../../../i18n/hooks.mjs";
4
+ import { FieldWrapper } from "../field-wrapper.mjs";
4
5
  import { EMPTY_BLOCK_CONTENT, isBlockContent } from "../../../blocks/types.mjs";
5
6
  import { useAdminConfig } from "../../../hooks/use-admin-config.mjs";
6
- import { FieldWrapper } from "../field-wrapper.mjs";
7
7
  import { countBlocks } from "../../blocks/utils/tree-utils.mjs";
8
8
  import { BlockEditorLayout } from "../../blocks/block-editor-layout.mjs";
9
9
  import { BlockEditorProvider } from "../../blocks/block-editor-provider.mjs";
@@ -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 { CheckboxInput } from "../primitives/checkbox-input.mjs";
5
5
  import { ToggleInput } from "../primitives/toggle-input.mjs";
6
6
  import { jsx } from "react/jsx-runtime";
@@ -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 { DateInput } from "../primitives/date-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 { DateTimeInput } from "../primitives/date-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 { TextInput } from "../primitives/text-input.mjs";
5
5
  import { jsx } from "react/jsx-runtime";
6
6
  import { Controller } from "react-hook-form";
@@ -6,12 +6,38 @@ import { Field, FieldContent, FieldDescription, FieldError, FieldLabel } from ".
6
6
  import { jsx, jsxs } from "react/jsx-runtime";
7
7
 
8
8
  //#region src/client/components/fields/field-wrapper.tsx
9
- function FieldWrapper({ name, label, description, required, disabled, readOnly, error, localized, locale, children, fieldPath }) {
10
- const resolveText = useResolveText();
9
+ function getLocaleOptions(contentLocales, resolvedLocale) {
10
+ if (contentLocales?.locales?.length) return contentLocales.locales;
11
+ if (resolvedLocale) return [{ code: resolvedLocale }];
12
+ return [];
13
+ }
14
+ function FieldCountAccessory({ count, max }) {
15
+ if (!max) return null;
16
+ return /* @__PURE__ */ jsxs("span", {
17
+ className: "text-muted-foreground text-xs tabular-nums",
18
+ children: [
19
+ "(",
20
+ count,
21
+ "/",
22
+ max,
23
+ ")"
24
+ ]
25
+ });
26
+ }
27
+ function FieldLocaleIndicator({ localized, locale }) {
11
28
  const { locale: scopedLocale } = useScopedLocale();
12
29
  const contentLocales = useSafeContentLocales();
13
30
  const resolvedLocale = locale ?? scopedLocale;
14
- const localeOptions = contentLocales?.locales?.length ? contentLocales.locales : resolvedLocale ? [{ code: resolvedLocale }] : [];
31
+ const localeOptions = getLocaleOptions(contentLocales, resolvedLocale);
32
+ if (!localized) return null;
33
+ return /* @__PURE__ */ jsx(LocaleSwitcher, {
34
+ locales: localeOptions,
35
+ value: resolvedLocale,
36
+ showFlag: false
37
+ });
38
+ }
39
+ function FieldWrapper({ name, label, description, labelAccessory, required, disabled, readOnly, error, localized, locale, children, fieldPath }) {
40
+ const resolveText = useResolveText();
15
41
  const resolvedLabel = label ? resolveText(label) : void 0;
16
42
  const resolvedDescription = description ? resolveText(description) : void 0;
17
43
  return /* @__PURE__ */ jsx(Field, {
@@ -25,17 +51,20 @@ function FieldWrapper({ name, label, description, required, disabled, readOnly,
25
51
  resolvedLabel && /* @__PURE__ */ jsxs(FieldLabel, {
26
52
  htmlFor: name,
27
53
  className: "flex items-center gap-2",
28
- children: [/* @__PURE__ */ jsxs("span", {
29
- className: "flex items-center gap-1",
30
- children: [resolvedLabel, required && /* @__PURE__ */ jsx("span", {
31
- className: "text-destructive",
32
- children: "*"
33
- })]
34
- }), localized && /* @__PURE__ */ jsx(LocaleSwitcher, {
35
- locales: localeOptions,
36
- value: resolvedLocale,
37
- showFlag: false
38
- })]
54
+ children: [
55
+ /* @__PURE__ */ jsxs("span", {
56
+ className: "flex items-center gap-1",
57
+ children: [resolvedLabel, required && /* @__PURE__ */ jsx("span", {
58
+ className: "text-destructive",
59
+ children: "*"
60
+ })]
61
+ }),
62
+ labelAccessory,
63
+ /* @__PURE__ */ jsx(FieldLocaleIndicator, {
64
+ localized,
65
+ locale
66
+ })
67
+ ]
39
68
  }),
40
69
  /* @__PURE__ */ jsx(FieldContent, { children }),
41
70
  resolvedDescription && /* @__PURE__ */ jsx(FieldDescription, { children: resolvedDescription }),
@@ -46,4 +75,4 @@ function FieldWrapper({ name, label, description, required, disabled, readOnly,
46
75
  }
47
76
 
48
77
  //#endregion
49
- export { FieldWrapper };
78
+ export { FieldCountAccessory, FieldLocaleIndicator, FieldWrapper };
@@ -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 { NumberInput } from "../primitives/number-input.mjs";
5
5
  import { jsx } from "react/jsx-runtime";
6
6
  import { Controller } from "react-hook-form";