@byline/admin 4.2.0 → 4.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.
- package/dist/fields/array/array-field.d.ts +12 -9
- package/dist/fields/array/array-field.js +46 -34
- package/dist/fields/blocks/blocks-field.js +1 -0
- package/dist/fields/draggable-context-menu.js +2 -1
- package/dist/fields/field-admin.d.ts +15 -0
- package/dist/fields/field-admin.js +11 -0
- package/dist/fields/field-admin.test.node.d.ts +8 -0
- package/dist/fields/field-renderer.d.ts +11 -4
- package/dist/fields/field-renderer.js +8 -9
- package/dist/fields/file/file-field.d.ts +1 -3
- package/dist/fields/file/file-field.js +2 -2
- package/dist/fields/group/group-field.d.ts +16 -9
- package/dist/fields/group/group-field.js +5 -4
- package/dist/fields/image/image-field.d.ts +1 -3
- package/dist/fields/image/image-field.js +2 -2
- package/dist/fields/sortable-item.d.ts +6 -0
- package/dist/fields/sortable-item.js +44 -25
- package/dist/forms/form-context.d.ts +20 -1
- package/dist/forms/form-context.js +3 -2
- package/dist/forms/form-renderer.js +4 -2
- package/dist/forms/upload-executor.js +50 -29
- package/package.json +10 -10
- package/src/fields/array/array-field.tsx +74 -43
- package/src/fields/blocks/blocks-field.tsx +5 -0
- package/src/fields/draggable-context-menu.tsx +9 -1
- package/src/fields/field-admin.test.node.ts +49 -0
- package/src/fields/field-admin.ts +39 -0
- package/src/fields/field-renderer.tsx +14 -7
- package/src/fields/file/file-field.tsx +4 -4
- package/src/fields/group/group-field.tsx +19 -11
- package/src/fields/image/image-field.tsx +4 -4
- package/src/fields/sortable-item.tsx +115 -34
- package/src/forms/form-context.tsx +30 -1
- package/src/forms/form-renderer.tsx +3 -1
- package/src/forms/upload-executor.test.node.ts +187 -0
- package/src/forms/upload-executor.ts +90 -29
|
@@ -5,23 +5,26 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Copyright (c) Infonomic Company Limited
|
|
7
7
|
*/
|
|
8
|
-
import type { ArrayField as ArrayFieldType } from '@byline/core';
|
|
9
|
-
export declare const ArrayField: ({ field, defaultValue, path, disableSorting,
|
|
8
|
+
import type { ArrayField as ArrayFieldType, FieldAdminConfig } from '@byline/core';
|
|
9
|
+
export declare const ArrayField: ({ field, defaultValue, path, disableSorting, contentLocale, fieldAdmin, }: {
|
|
10
10
|
field: ArrayFieldType;
|
|
11
11
|
defaultValue: any;
|
|
12
12
|
path: string;
|
|
13
13
|
disableSorting?: boolean;
|
|
14
|
-
/**
|
|
15
|
-
* Collection path forwarded to upload-capable fields (`file` / `image`)
|
|
16
|
-
* nested inside an array item, which need it to reach the `/upload`
|
|
17
|
-
* endpoint. Without it those fields fall back to their empty placeholder
|
|
18
|
-
* and never render an upload widget.
|
|
19
|
-
*/
|
|
20
|
-
collectionPath?: string;
|
|
21
14
|
/**
|
|
22
15
|
* Active content locale, forwarded to each array item's fields so
|
|
23
16
|
* localized widgets nested inside an array (e.g. a `localized` richText)
|
|
24
17
|
* can render their locale badge.
|
|
25
18
|
*/
|
|
26
19
|
contentLocale?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Admin overrides for the array's child fields, keyed by dotted,
|
|
22
|
+
* index-free schema paths relative to this array ('answer',
|
|
23
|
+
* 'filesGroup.publicationFile'). Schema paths address declarations, so
|
|
24
|
+
* one entry applies to that field in every item. Arrives pre-sliced from
|
|
25
|
+
* the enclosing widget (`FieldRenderer` / `GroupField`); exact-name
|
|
26
|
+
* entries apply to the child, deeper entries are re-sliced and threaded
|
|
27
|
+
* on (see `sliceFieldAdmin`).
|
|
28
|
+
*/
|
|
29
|
+
fieldAdmin?: Record<string, FieldAdminConfig>;
|
|
27
30
|
}) => import("react").JSX.Element;
|
|
@@ -3,12 +3,13 @@ import { useEffect, useState } from "react";
|
|
|
3
3
|
import { useTranslation } from "@byline/i18n/react";
|
|
4
4
|
import { DraggableSortable, IconButton, PlusIcon, moveItem } from "@byline/ui/react";
|
|
5
5
|
import classnames from "classnames";
|
|
6
|
+
import { sliceFieldAdmin } from "../field-admin.js";
|
|
6
7
|
import { defaultScalarForField } from "../field-helpers.js";
|
|
7
8
|
import { FieldRenderer } from "../field-renderer.js";
|
|
8
|
-
import { SortableItem } from "../sortable-item.js";
|
|
9
|
+
import { SortableItem, StaticItem } from "../sortable-item.js";
|
|
9
10
|
import { useFormContext } from "../../forms/form-context.js";
|
|
10
11
|
import array_field_module from "./array-field.module.js";
|
|
11
|
-
const ArrayField = ({ field, defaultValue, path, disableSorting = false,
|
|
12
|
+
const ArrayField = ({ field, defaultValue, path, disableSorting = false, contentLocale, fieldAdmin })=>{
|
|
12
13
|
const { appendPatch, getFieldValue, getFieldValues, setFieldStore } = useFormContext();
|
|
13
14
|
const { t } = useTranslation('byline-admin');
|
|
14
15
|
const [items, setItems] = useState([]);
|
|
@@ -113,6 +114,7 @@ const ArrayField = ({ field, defaultValue, path, disableSorting = false, collect
|
|
|
113
114
|
const initial = item[childField.name];
|
|
114
115
|
if ('group' === childField.type && childField.fields && childField.fields.length > 0) {
|
|
115
116
|
const groupData = initial && 'object' == typeof initial ? initial : {};
|
|
117
|
+
const groupAdmin = sliceFieldAdmin(fieldAdmin, childField.name);
|
|
116
118
|
return /*#__PURE__*/ jsxs("div", {
|
|
117
119
|
className: classnames('byline-field-array-group-fields', array_field_module["group-fields"]),
|
|
118
120
|
children: [
|
|
@@ -125,8 +127,10 @@ const ArrayField = ({ field, defaultValue, path, disableSorting = false, collect
|
|
|
125
127
|
defaultValue: groupData[innerField.name],
|
|
126
128
|
basePath: `${arrayElementPath}.${childField.name}`,
|
|
127
129
|
disableSorting: true,
|
|
128
|
-
|
|
129
|
-
|
|
130
|
+
contentLocale: contentLocale,
|
|
131
|
+
components: groupAdmin?.[innerField.name]?.components,
|
|
132
|
+
editor: groupAdmin?.[innerField.name]?.editor,
|
|
133
|
+
fieldAdmin: sliceFieldAdmin(groupAdmin, innerField.name)
|
|
130
134
|
}, innerField.name))
|
|
131
135
|
]
|
|
132
136
|
}, childField.name);
|
|
@@ -136,13 +140,17 @@ const ArrayField = ({ field, defaultValue, path, disableSorting = false, collect
|
|
|
136
140
|
defaultValue: initial,
|
|
137
141
|
basePath: arrayElementPath,
|
|
138
142
|
disableSorting: true,
|
|
139
|
-
|
|
140
|
-
|
|
143
|
+
contentLocale: contentLocale,
|
|
144
|
+
components: fieldAdmin?.[childField.name]?.components,
|
|
145
|
+
editor: fieldAdmin?.[childField.name]?.editor,
|
|
146
|
+
fieldAdmin: sliceFieldAdmin(fieldAdmin, childField.name)
|
|
141
147
|
}, childField.name);
|
|
142
148
|
});
|
|
143
|
-
const
|
|
144
|
-
if (disableSorting) return /*#__PURE__*/ jsx(
|
|
145
|
-
|
|
149
|
+
const itemLabel = `${field.label ?? field.name} ${index + 1}`;
|
|
150
|
+
if (disableSorting) return /*#__PURE__*/ jsx(StaticItem, {
|
|
151
|
+
label: itemLabel,
|
|
152
|
+
onAddBelow: ()=>handleInsertBelow(index),
|
|
153
|
+
onRemove: ()=>handleRemoveItem(index),
|
|
146
154
|
children: /*#__PURE__*/ jsx("div", {
|
|
147
155
|
className: classnames('byline-field-array-group-fields', array_field_module["group-fields"]),
|
|
148
156
|
children: innerBody
|
|
@@ -150,7 +158,7 @@ const ArrayField = ({ field, defaultValue, path, disableSorting = false, collect
|
|
|
150
158
|
}, itemWrapper.id);
|
|
151
159
|
return /*#__PURE__*/ jsx(SortableItem, {
|
|
152
160
|
id: itemWrapper.id,
|
|
153
|
-
label:
|
|
161
|
+
label: itemLabel,
|
|
154
162
|
onAddBelow: ()=>handleInsertBelow(index),
|
|
155
163
|
onRemove: ()=>handleRemoveItem(index),
|
|
156
164
|
children: /*#__PURE__*/ jsx("div", {
|
|
@@ -159,43 +167,47 @@ const ArrayField = ({ field, defaultValue, path, disableSorting = false, collect
|
|
|
159
167
|
})
|
|
160
168
|
}, itemWrapper.id);
|
|
161
169
|
};
|
|
170
|
+
const addRow = /*#__PURE__*/ jsxs("div", {
|
|
171
|
+
className: classnames('byline-field-array-add-row', array_field_module["add-row"]),
|
|
172
|
+
children: [
|
|
173
|
+
/*#__PURE__*/ jsx(IconButton, {
|
|
174
|
+
onClick: ()=>{
|
|
175
|
+
handleAddItem();
|
|
176
|
+
},
|
|
177
|
+
"aria-label": t('fields.array.addItemAriaLabel'),
|
|
178
|
+
children: /*#__PURE__*/ jsx(PlusIcon, {})
|
|
179
|
+
}),
|
|
180
|
+
/*#__PURE__*/ jsx("button", {
|
|
181
|
+
type: "button",
|
|
182
|
+
tabIndex: -1,
|
|
183
|
+
onClick: ()=>{
|
|
184
|
+
handleAddItem();
|
|
185
|
+
},
|
|
186
|
+
className: classnames('byline-field-array-add-label', array_field_module["add-label"]),
|
|
187
|
+
children: t('fields.array.addItem')
|
|
188
|
+
})
|
|
189
|
+
]
|
|
190
|
+
});
|
|
162
191
|
return /*#__PURE__*/ jsxs("div", {
|
|
163
192
|
className: `byline-field-array ${field.name}`,
|
|
164
193
|
children: [
|
|
165
|
-
|
|
194
|
+
field.label && /*#__PURE__*/ jsx("h3", {
|
|
166
195
|
className: classnames('byline-field-array-title', array_field_module.title),
|
|
167
196
|
children: field.label
|
|
168
197
|
}),
|
|
169
|
-
disableSorting ? /*#__PURE__*/
|
|
198
|
+
disableSorting ? /*#__PURE__*/ jsxs("div", {
|
|
170
199
|
className: classnames('byline-field-array-stack', array_field_module.stack),
|
|
171
|
-
children:
|
|
200
|
+
children: [
|
|
201
|
+
items.map((item, index)=>renderItem(item, index)),
|
|
202
|
+
addRow
|
|
203
|
+
]
|
|
172
204
|
}) : /*#__PURE__*/ jsxs(DraggableSortable, {
|
|
173
205
|
ids: items.map((i)=>i.id),
|
|
174
206
|
onDragEnd: handleDragEnd,
|
|
175
207
|
className: classnames('byline-field-array-stack', array_field_module.stack),
|
|
176
208
|
children: [
|
|
177
209
|
items.map((item, index)=>renderItem(item, index)),
|
|
178
|
-
|
|
179
|
-
className: classnames('byline-field-array-add-row', array_field_module["add-row"]),
|
|
180
|
-
children: [
|
|
181
|
-
/*#__PURE__*/ jsx(IconButton, {
|
|
182
|
-
onClick: ()=>{
|
|
183
|
-
handleAddItem();
|
|
184
|
-
},
|
|
185
|
-
"aria-label": t('fields.array.addItemAriaLabel'),
|
|
186
|
-
children: /*#__PURE__*/ jsx(PlusIcon, {})
|
|
187
|
-
}),
|
|
188
|
-
/*#__PURE__*/ jsx("button", {
|
|
189
|
-
type: "button",
|
|
190
|
-
tabIndex: -1,
|
|
191
|
-
onClick: ()=>{
|
|
192
|
-
handleAddItem();
|
|
193
|
-
},
|
|
194
|
-
className: classnames('byline-field-array-add-label', array_field_module["add-label"]),
|
|
195
|
-
children: t('fields.array.addItem')
|
|
196
|
-
})
|
|
197
|
-
]
|
|
198
|
-
})
|
|
210
|
+
addRow
|
|
199
211
|
]
|
|
200
212
|
})
|
|
201
213
|
]
|
|
@@ -136,6 +136,7 @@ const BlocksField = ({ field, defaultValue, path, contentLocale })=>{
|
|
|
136
136
|
},
|
|
137
137
|
defaultValue: fieldData,
|
|
138
138
|
path: arrayElementPath,
|
|
139
|
+
disableSorting: false,
|
|
139
140
|
contentLocale: contentLocale,
|
|
140
141
|
fieldAdmin: blockAdminByType.get(item._type)?.fields
|
|
141
142
|
}, subField.blockType);
|
|
@@ -16,7 +16,8 @@ function DraggableContextMenu({ onAddBelow, onRemove }) {
|
|
|
16
16
|
/*#__PURE__*/ jsx(Dropdown.Trigger, {
|
|
17
17
|
render: /*#__PURE__*/ jsx(IconButton, {
|
|
18
18
|
variant: "text",
|
|
19
|
-
size: "sm"
|
|
19
|
+
size: "sm",
|
|
20
|
+
"aria-label": t('fields.draggableMenu.triggerAriaLabel')
|
|
20
21
|
}),
|
|
21
22
|
children: /*#__PURE__*/ jsx(EllipsisIcon, {
|
|
22
23
|
width: "16px",
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
import type { FieldAdminConfig } from '@byline/core';
|
|
9
|
+
/**
|
|
10
|
+
* Entries of `map` addressing descendants of `childName`, re-keyed with the
|
|
11
|
+
* `childName.` prefix stripped — the sub-map a structural child (group /
|
|
12
|
+
* array) threads to its own children. Returns `undefined` when `map` has no
|
|
13
|
+
* descendant entries for the child, so leaf widgets aren't handed empty maps.
|
|
14
|
+
*/
|
|
15
|
+
export declare function sliceFieldAdmin(map: Record<string, FieldAdminConfig> | undefined, childName: string): Record<string, FieldAdminConfig> | undefined;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
function sliceFieldAdmin(map, childName) {
|
|
2
|
+
if (null == map) return;
|
|
3
|
+
const prefix = `${childName}.`;
|
|
4
|
+
let sliced;
|
|
5
|
+
for (const [key, value] of Object.entries(map))if (key.startsWith(prefix)) {
|
|
6
|
+
sliced ??= {};
|
|
7
|
+
sliced[key.slice(prefix.length)] = value;
|
|
8
|
+
}
|
|
9
|
+
return sliced;
|
|
10
|
+
}
|
|
11
|
+
export { sliceFieldAdmin };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
@@ -5,15 +5,13 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Copyright (c) Infonomic Company Limited
|
|
7
7
|
*/
|
|
8
|
-
import type { Field, FieldComponentSlots, RichTextEditorComponent } from '@byline/core';
|
|
8
|
+
import type { Field, FieldAdminConfig, FieldComponentSlots, RichTextEditorComponent } from '@byline/core';
|
|
9
9
|
interface FieldRendererProps {
|
|
10
10
|
field: Field;
|
|
11
11
|
defaultValue?: any;
|
|
12
12
|
basePath?: string;
|
|
13
13
|
disableSorting?: boolean;
|
|
14
14
|
hideLabel?: boolean;
|
|
15
|
-
/** Collection path (e.g. `'media'`) forwarded to upload-capable fields. */
|
|
16
|
-
collectionPath?: string;
|
|
17
15
|
/**
|
|
18
16
|
* The active content locale (e.g. `'en'`, `'fr'`). When provided and
|
|
19
17
|
* `field.localized === true`, a small locale badge is shown so the editor
|
|
@@ -32,6 +30,15 @@ interface FieldRendererProps {
|
|
|
32
30
|
* Ignored when `field.type !== 'richText'`.
|
|
33
31
|
*/
|
|
34
32
|
editor?: RichTextEditorComponent;
|
|
33
|
+
/**
|
|
34
|
+
* Admin overrides for this field's *descendants*, keyed by dotted,
|
|
35
|
+
* index-free schema paths relative to this field ('answer',
|
|
36
|
+
* 'filesGroup.publicationFile'). Only meaningful when `field` is a
|
|
37
|
+
* structural `group` / `array` — the widget slices the map per child
|
|
38
|
+
* (see `sliceFieldAdmin`). `components` / `editor` above stay the
|
|
39
|
+
* overrides for this field itself.
|
|
40
|
+
*/
|
|
41
|
+
fieldAdmin?: Record<string, FieldAdminConfig>;
|
|
35
42
|
}
|
|
36
|
-
export declare const FieldRenderer: ({ field, defaultValue: initialDefault, basePath, disableSorting, hideLabel,
|
|
43
|
+
export declare const FieldRenderer: ({ field, defaultValue: initialDefault, basePath, disableSorting, hideLabel, contentLocale, components, editor, fieldAdmin, }: FieldRendererProps) => import("react").JSX.Element | null;
|
|
37
44
|
export {};
|
|
@@ -20,7 +20,7 @@ import { TextField } from "./text/text-field.js";
|
|
|
20
20
|
import { TextAreaField } from "./text-area/text-area-field.js";
|
|
21
21
|
import { useFieldChangeHandler } from "./use-field-change-handler.js";
|
|
22
22
|
import { useFieldCondition } from "./use-field-condition.js";
|
|
23
|
-
const FieldRenderer = ({ field, defaultValue: initialDefault, basePath, disableSorting, hideLabel,
|
|
23
|
+
const FieldRenderer = ({ field, defaultValue: initialDefault, basePath, disableSorting, hideLabel, contentLocale, components, editor, fieldAdmin })=>{
|
|
24
24
|
const path = basePath ? `${basePath}.${field.name}` : field.name;
|
|
25
25
|
const htmlId = path.replace(/[[\].]/g, '-');
|
|
26
26
|
const handleChange = useFieldChangeHandler(field, path);
|
|
@@ -161,8 +161,7 @@ const FieldRenderer = ({ field, defaultValue: initialDefault, basePath, disableS
|
|
|
161
161
|
} : field,
|
|
162
162
|
defaultValue: defaultValue,
|
|
163
163
|
onChange: handleChange,
|
|
164
|
-
path: path
|
|
165
|
-
collectionPath: collectionPath
|
|
164
|
+
path: path
|
|
166
165
|
});
|
|
167
166
|
case 'image':
|
|
168
167
|
return /*#__PURE__*/ jsx(ImageField, {
|
|
@@ -172,8 +171,7 @@ const FieldRenderer = ({ field, defaultValue: initialDefault, basePath, disableS
|
|
|
172
171
|
} : field,
|
|
173
172
|
defaultValue: defaultValue,
|
|
174
173
|
onChange: handleChange,
|
|
175
|
-
path: path
|
|
176
|
-
collectionPath: collectionPath
|
|
174
|
+
path: path
|
|
177
175
|
});
|
|
178
176
|
case 'relation':
|
|
179
177
|
if (field.hasMany) return /*#__PURE__*/ jsx(RelationManyField, {
|
|
@@ -203,8 +201,9 @@ const FieldRenderer = ({ field, defaultValue: initialDefault, basePath, disableS
|
|
|
203
201
|
} : field,
|
|
204
202
|
defaultValue: defaultValue,
|
|
205
203
|
path: path,
|
|
206
|
-
|
|
207
|
-
contentLocale: contentLocale
|
|
204
|
+
disableSorting: disableSorting,
|
|
205
|
+
contentLocale: contentLocale,
|
|
206
|
+
fieldAdmin: fieldAdmin
|
|
208
207
|
});
|
|
209
208
|
case 'blocks':
|
|
210
209
|
if (!field.blocks) return null;
|
|
@@ -221,8 +220,8 @@ const FieldRenderer = ({ field, defaultValue: initialDefault, basePath, disableS
|
|
|
221
220
|
defaultValue: defaultValue,
|
|
222
221
|
path: path,
|
|
223
222
|
disableSorting: disableSorting,
|
|
224
|
-
|
|
225
|
-
|
|
223
|
+
contentLocale: contentLocale,
|
|
224
|
+
fieldAdmin: fieldAdmin
|
|
226
225
|
});
|
|
227
226
|
default:
|
|
228
227
|
return null;
|
|
@@ -8,12 +8,10 @@
|
|
|
8
8
|
import { type FileField as FieldType, type StoredFileValue } from '@byline/core';
|
|
9
9
|
interface FileFieldProps {
|
|
10
10
|
field: FieldType;
|
|
11
|
-
/** Collection path required to call the /upload endpoint. */
|
|
12
|
-
collectionPath?: string;
|
|
13
11
|
value?: StoredFileValue | null;
|
|
14
12
|
defaultValue?: StoredFileValue | null;
|
|
15
13
|
onChange?: (value: StoredFileValue | null) => void;
|
|
16
14
|
path?: string;
|
|
17
15
|
}
|
|
18
|
-
export declare const FileField: ({ field,
|
|
16
|
+
export declare const FileField: ({ field, value, defaultValue, onChange: _onChange, path, }: FileFieldProps) => import("react").JSX.Element;
|
|
19
17
|
export {};
|
|
@@ -18,14 +18,14 @@ function triggerDownload(url, filename) {
|
|
|
18
18
|
a.click();
|
|
19
19
|
document.body.removeChild(a);
|
|
20
20
|
}
|
|
21
|
-
const FileField = ({ field,
|
|
21
|
+
const FileField = ({ field, value, defaultValue, onChange: _onChange, path })=>{
|
|
22
22
|
const fieldPath = path ?? field.name;
|
|
23
23
|
const { t } = useTranslation('byline-admin');
|
|
24
24
|
const fieldError = useFieldError(fieldPath);
|
|
25
25
|
const isDirty = useIsDirty(fieldPath);
|
|
26
26
|
const fieldValue = useFieldValue(fieldPath);
|
|
27
27
|
const isUploading = useIsFieldUploading(fieldPath);
|
|
28
|
-
const { removePendingUpload, documentId } = useFormContext();
|
|
28
|
+
const { removePendingUpload, documentId, collectionPath } = useFormContext();
|
|
29
29
|
const handleChange = useFieldChangeHandler(field, fieldPath);
|
|
30
30
|
const incomingValue = isDirty ? fieldValue ?? null : value ?? fieldValue ?? defaultValue ?? null;
|
|
31
31
|
const isPending = isPendingStoredFileValue(incomingValue);
|
|
@@ -11,11 +11,15 @@ interface GroupFieldProps {
|
|
|
11
11
|
defaultValue: any;
|
|
12
12
|
path: string;
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* Threaded to child fields — governs only the *drag* affordance of any
|
|
15
|
+
* `array` children (structural add/remove always renders; see ArrayField).
|
|
16
|
+
* Defaults to `true` (conservative): arrays inside plain schema groups
|
|
17
|
+
* stay drag-free. `BlocksField` passes `false` on its synthesized group so
|
|
18
|
+
* arrays directly inside blocks are fully sortable — safe because each
|
|
19
|
+
* `DraggableSortable` is an independent DndContext with grip-scoped
|
|
20
|
+
* listeners.
|
|
17
21
|
*/
|
|
18
|
-
|
|
22
|
+
disableSorting?: boolean;
|
|
19
23
|
/**
|
|
20
24
|
* Active content locale, forwarded to child fields so localized widgets
|
|
21
25
|
* nested inside the group (e.g. a `localized` richText) can render their
|
|
@@ -24,12 +28,15 @@ interface GroupFieldProps {
|
|
|
24
28
|
contentLocale?: string;
|
|
25
29
|
/**
|
|
26
30
|
* Per-child-field admin overrides (`components` slots, richtext `editor`),
|
|
27
|
-
* keyed by
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
+
* keyed by dotted, index-free schema paths relative to this group
|
|
32
|
+
* ('caption', 'faq.answer'). Threaded by `BlocksField` from the site-wide
|
|
33
|
+
* `ClientConfig.blockAdmin` registry (block children render through a
|
|
34
|
+
* synthesized group) and by `FieldRenderer` for plain schema groups, whose
|
|
35
|
+
* map arrives pre-sliced from the collection admin config. Exact-name
|
|
36
|
+
* entries apply to the child itself; deeper entries are re-sliced and
|
|
37
|
+
* threaded on (see `sliceFieldAdmin`).
|
|
31
38
|
*/
|
|
32
39
|
fieldAdmin?: Record<string, FieldAdminConfig>;
|
|
33
40
|
}
|
|
34
|
-
export declare const GroupField: ({ field, defaultValue, path,
|
|
41
|
+
export declare const GroupField: ({ field, defaultValue, path, disableSorting, contentLocale, fieldAdmin, }: GroupFieldProps) => import("react").JSX.Element;
|
|
35
42
|
export {};
|
|
@@ -2,11 +2,12 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useMemo } from "react";
|
|
3
3
|
import { ErrorText } from "@byline/ui/react";
|
|
4
4
|
import classnames from "classnames";
|
|
5
|
+
import { sliceFieldAdmin } from "../field-admin.js";
|
|
5
6
|
import { placeholderForField } from "../field-helpers.js";
|
|
6
7
|
import { FieldRenderer } from "../field-renderer.js";
|
|
7
8
|
import { useFieldError } from "../../forms/form-context.js";
|
|
8
9
|
import group_field_module from "./group-field.module.js";
|
|
9
|
-
const GroupField = ({ field, defaultValue, path,
|
|
10
|
+
const GroupField = ({ field, defaultValue, path, disableSorting = true, contentLocale, fieldAdmin })=>{
|
|
10
11
|
const fieldError = useFieldError(field.name);
|
|
11
12
|
const groupData = useMemo(()=>{
|
|
12
13
|
if (defaultValue && 'object' == typeof defaultValue && !Array.isArray(defaultValue)) return defaultValue;
|
|
@@ -46,11 +47,11 @@ const GroupField = ({ field, defaultValue, path, collectionPath, contentLocale,
|
|
|
46
47
|
field: innerField,
|
|
47
48
|
defaultValue: groupData[innerField.name],
|
|
48
49
|
basePath: path,
|
|
49
|
-
disableSorting:
|
|
50
|
-
collectionPath: collectionPath,
|
|
50
|
+
disableSorting: disableSorting,
|
|
51
51
|
contentLocale: contentLocale,
|
|
52
52
|
components: fieldAdmin?.[innerField.name]?.components,
|
|
53
|
-
editor: fieldAdmin?.[innerField.name]?.editor
|
|
53
|
+
editor: fieldAdmin?.[innerField.name]?.editor,
|
|
54
|
+
fieldAdmin: sliceFieldAdmin(fieldAdmin, innerField.name)
|
|
54
55
|
}, innerField.name))
|
|
55
56
|
}),
|
|
56
57
|
fieldError && /*#__PURE__*/ jsx(ErrorText, {
|
|
@@ -8,12 +8,10 @@
|
|
|
8
8
|
import { type ImageField as FieldType, type StoredFileValue } from '@byline/core';
|
|
9
9
|
interface ImageFieldProps {
|
|
10
10
|
field: FieldType;
|
|
11
|
-
/** Collection path required to call the /upload endpoint. */
|
|
12
|
-
collectionPath?: string;
|
|
13
11
|
value?: StoredFileValue | null;
|
|
14
12
|
defaultValue?: StoredFileValue | null;
|
|
15
13
|
onChange?: (value: StoredFileValue | null) => void;
|
|
16
14
|
path?: string;
|
|
17
15
|
}
|
|
18
|
-
export declare const ImageField: ({ field,
|
|
16
|
+
export declare const ImageField: ({ field, value, defaultValue, onChange: _onChange, path, }: ImageFieldProps) => import("react").JSX.Element;
|
|
19
17
|
export {};
|
|
@@ -8,13 +8,13 @@ import { useFieldError, useFieldValue, useFormContext, useIsDirty, useIsFieldUpl
|
|
|
8
8
|
import { useFieldChangeHandler } from "../use-field-change-handler.js";
|
|
9
9
|
import image_field_module from "./image-field.module.js";
|
|
10
10
|
import { ImageUploadField } from "./image-upload-field.js";
|
|
11
|
-
const ImageField = ({ field,
|
|
11
|
+
const ImageField = ({ field, value, defaultValue, onChange: _onChange, path })=>{
|
|
12
12
|
const fieldPath = path ?? field.name;
|
|
13
13
|
const fieldError = useFieldError(fieldPath);
|
|
14
14
|
const isDirty = useIsDirty(fieldPath);
|
|
15
15
|
const fieldValue = useFieldValue(fieldPath);
|
|
16
16
|
const isUploading = useIsFieldUploading(fieldPath);
|
|
17
|
-
const { removePendingUpload, documentId } = useFormContext();
|
|
17
|
+
const { removePendingUpload, documentId, collectionPath } = useFormContext();
|
|
18
18
|
const { t } = useTranslation('byline-admin');
|
|
19
19
|
const handleChange = useFieldChangeHandler(field, fieldPath);
|
|
20
20
|
const incomingValue = isDirty ? fieldValue ?? null : value ?? fieldValue ?? defaultValue ?? null;
|
|
@@ -13,3 +13,9 @@ export declare const SortableItem: ({ id, label, children, onAddBelow, onRemove,
|
|
|
13
13
|
onAddBelow?: () => void;
|
|
14
14
|
onRemove?: () => void;
|
|
15
15
|
}) => import("react").JSX.Element;
|
|
16
|
+
export declare const StaticItem: ({ label, children, onAddBelow, onRemove, }: {
|
|
17
|
+
label: ReactNode;
|
|
18
|
+
children: ReactNode;
|
|
19
|
+
onAddBelow?: () => void;
|
|
20
|
+
onRemove?: () => void;
|
|
21
|
+
}) => import("react").JSX.Element;
|
|
@@ -5,25 +5,13 @@ import { ChevronDownIcon, GripperVerticalIcon, useSortable } from "@byline/ui/re
|
|
|
5
5
|
import classnames from "classnames";
|
|
6
6
|
import { DraggableContextMenu } from "./draggable-context-menu.js";
|
|
7
7
|
import sortable_item_module from "./sortable-item.module.js";
|
|
8
|
-
const
|
|
8
|
+
const ItemFrame = ({ label, children, onAddBelow, onRemove, grip, rootRef, style, dragging = false, rootClassName })=>{
|
|
9
9
|
const { t } = useTranslation('byline-admin');
|
|
10
|
-
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
|
11
|
-
id,
|
|
12
|
-
transition: {
|
|
13
|
-
duration: 250,
|
|
14
|
-
easing: 'cubic-bezier(0, 0.2, 0.2, 1)'
|
|
15
|
-
}
|
|
16
|
-
});
|
|
17
10
|
const [collapsed, setCollapsed] = useState(false);
|
|
18
|
-
const style = {
|
|
19
|
-
transform: transform ? `translate3d(${transform.x}px, ${transform.y}px, 0)` : void 0,
|
|
20
|
-
transition,
|
|
21
|
-
zIndex: isDragging ? 10 : 'auto'
|
|
22
|
-
};
|
|
23
11
|
return /*#__PURE__*/ jsxs("div", {
|
|
24
|
-
ref:
|
|
12
|
+
ref: rootRef,
|
|
25
13
|
style: style,
|
|
26
|
-
className: classnames('byline-sortable', sortable_item_module.root,
|
|
14
|
+
className: classnames('byline-sortable', sortable_item_module.root, rootClassName, dragging && [
|
|
27
15
|
'byline-sortable-dragging',
|
|
28
16
|
sortable_item_module.dragging
|
|
29
17
|
], collapsed && [
|
|
@@ -37,15 +25,7 @@ const SortableItem = ({ id, label, children, onAddBelow, onRemove })=>{
|
|
|
37
25
|
sortable_item_module["header-expanded"]
|
|
38
26
|
]),
|
|
39
27
|
children: [
|
|
40
|
-
|
|
41
|
-
type: "button",
|
|
42
|
-
className: classnames('byline-sortable-grip', sortable_item_module.grip),
|
|
43
|
-
...attributes,
|
|
44
|
-
...listeners,
|
|
45
|
-
children: /*#__PURE__*/ jsx(GripperVerticalIcon, {
|
|
46
|
-
className: classnames('byline-sortable-grip-icon', sortable_item_module["grip-icon"])
|
|
47
|
-
})
|
|
48
|
-
}),
|
|
28
|
+
grip,
|
|
49
29
|
/*#__PURE__*/ jsx("div", {
|
|
50
30
|
className: classnames('byline-sortable-label', sortable_item_module.label),
|
|
51
31
|
children: label
|
|
@@ -78,4 +58,43 @@ const SortableItem = ({ id, label, children, onAddBelow, onRemove })=>{
|
|
|
78
58
|
]
|
|
79
59
|
});
|
|
80
60
|
};
|
|
81
|
-
|
|
61
|
+
const SortableItem = ({ id, label, children, onAddBelow, onRemove })=>{
|
|
62
|
+
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
|
63
|
+
id,
|
|
64
|
+
transition: {
|
|
65
|
+
duration: 250,
|
|
66
|
+
easing: 'cubic-bezier(0, 0.2, 0.2, 1)'
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
const style = {
|
|
70
|
+
transform: transform ? `translate3d(${transform.x}px, ${transform.y}px, 0)` : void 0,
|
|
71
|
+
transition,
|
|
72
|
+
zIndex: isDragging ? 10 : 'auto'
|
|
73
|
+
};
|
|
74
|
+
return /*#__PURE__*/ jsx(ItemFrame, {
|
|
75
|
+
label: label,
|
|
76
|
+
onAddBelow: onAddBelow,
|
|
77
|
+
onRemove: onRemove,
|
|
78
|
+
rootRef: setNodeRef,
|
|
79
|
+
style: style,
|
|
80
|
+
dragging: isDragging,
|
|
81
|
+
grip: /*#__PURE__*/ jsx("button", {
|
|
82
|
+
type: "button",
|
|
83
|
+
className: classnames('byline-sortable-grip', sortable_item_module.grip),
|
|
84
|
+
...attributes,
|
|
85
|
+
...listeners,
|
|
86
|
+
children: /*#__PURE__*/ jsx(GripperVerticalIcon, {
|
|
87
|
+
className: classnames('byline-sortable-grip-icon', sortable_item_module["grip-icon"])
|
|
88
|
+
})
|
|
89
|
+
}),
|
|
90
|
+
children: children
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
const StaticItem = ({ label, children, onAddBelow, onRemove })=>/*#__PURE__*/ jsx(ItemFrame, {
|
|
94
|
+
label: label,
|
|
95
|
+
onAddBelow: onAddBelow,
|
|
96
|
+
onRemove: onRemove,
|
|
97
|
+
rootClassName: "byline-sortable-static",
|
|
98
|
+
children: children
|
|
99
|
+
});
|
|
100
|
+
export { SortableItem, StaticItem };
|
|
@@ -55,6 +55,19 @@ interface FormContextType {
|
|
|
55
55
|
* `@byline/core`).
|
|
56
56
|
*/
|
|
57
57
|
documentId: string | null;
|
|
58
|
+
/**
|
|
59
|
+
* Path of the collection this form edits, `null` when the form is rendered
|
|
60
|
+
* without one. Upload widgets need it to address the upload endpoint.
|
|
61
|
+
*
|
|
62
|
+
* It lives here rather than being passed down because it is constant for
|
|
63
|
+
* the whole form: threading it as a prop meant every nesting-capable
|
|
64
|
+
* container (`array`, `group`, `blocks`) had to remember to forward it,
|
|
65
|
+
* and a container that forgot silently rendered upload fields read-only —
|
|
66
|
+
* no error, just a missing drop zone. That happened twice, in `array` /
|
|
67
|
+
* `group` and then in `blocks`. A value read from context cannot be
|
|
68
|
+
* dropped by a container that never carries it.
|
|
69
|
+
*/
|
|
70
|
+
collectionPath: string | null;
|
|
58
71
|
setFieldValue: (name: string, value: any) => void;
|
|
59
72
|
setFieldStore: (name: string, value: any) => void;
|
|
60
73
|
getFieldValue: (name: string) => any;
|
|
@@ -97,7 +110,7 @@ interface FormContextType {
|
|
|
97
110
|
subscribeSystemAvailableLocales: (listener: SystemAvailableLocalesListener) => () => void;
|
|
98
111
|
}
|
|
99
112
|
export declare const useFormContext: () => FormContextType;
|
|
100
|
-
export declare const FormProvider: ({ children, initialData, documentId, }: {
|
|
113
|
+
export declare const FormProvider: ({ children, initialData, documentId, collectionPath, }: {
|
|
101
114
|
children: React.ReactNode;
|
|
102
115
|
initialData?: Record<string, any>;
|
|
103
116
|
/**
|
|
@@ -105,6 +118,12 @@ export declare const FormProvider: ({ children, initialData, documentId, }: {
|
|
|
105
118
|
* the context for upload widgets honouring `upload.requireSavedDocument`.
|
|
106
119
|
*/
|
|
107
120
|
documentId?: string | null;
|
|
121
|
+
/**
|
|
122
|
+
* Path of the collection being edited. Exposed on the context so upload
|
|
123
|
+
* widgets can reach the upload endpoint from any nesting depth without
|
|
124
|
+
* every container forwarding it — see `FormContextType.collectionPath`.
|
|
125
|
+
*/
|
|
126
|
+
collectionPath?: string | null;
|
|
108
127
|
}) => React.JSX.Element;
|
|
109
128
|
/**
|
|
110
129
|
* Subscribe to the system `path` slot edited by the path widget.
|
|
@@ -22,7 +22,7 @@ const useFormContext = ()=>{
|
|
|
22
22
|
if (null == context) throw new Error('useFormContext must be used within a FormProvider');
|
|
23
23
|
return context;
|
|
24
24
|
};
|
|
25
|
-
const FormProvider = ({ children, initialData = {}, documentId = null })=>{
|
|
25
|
+
const FormProvider = ({ children, initialData = {}, documentId = null, collectionPath = null })=>{
|
|
26
26
|
const fieldValues = useRef(JSON.parse(JSON.stringify(initialData?.fields ?? initialData)));
|
|
27
27
|
const initialValues = useRef(initialData?.fields ?? initialData);
|
|
28
28
|
const errorsRef = useRef([]);
|
|
@@ -140,7 +140,7 @@ const FormProvider = ({ children, initialData = {}, documentId = null })=>{
|
|
|
140
140
|
const appendPatch = useCallback((patch)=>{
|
|
141
141
|
patchesRef.current = [
|
|
142
142
|
...patchesRef.current,
|
|
143
|
-
patch
|
|
143
|
+
structuredClone(patch)
|
|
144
144
|
];
|
|
145
145
|
dirtyFields.current.add('__patch__');
|
|
146
146
|
notifyMetaListeners();
|
|
@@ -377,6 +377,7 @@ const FormProvider = ({ children, initialData = {}, documentId = null })=>{
|
|
|
377
377
|
return /*#__PURE__*/ jsx(FormContext.Provider, {
|
|
378
378
|
value: {
|
|
379
379
|
documentId,
|
|
380
|
+
collectionPath,
|
|
380
381
|
setFieldValue,
|
|
381
382
|
setFieldStore,
|
|
382
383
|
getFieldValue,
|