@byline/admin 4.1.0 → 4.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/fields/array/array-field.d.ts +12 -2
- package/dist/fields/array/array-field.js +46 -20
- package/dist/fields/array/array-field.module.js +5 -1
- package/dist/fields/array/array-field_module.css +23 -0
- package/dist/fields/blocks/blocks-field.js +34 -12
- package/dist/fields/blocks/blocks-field.module.js +2 -0
- package/dist/fields/blocks/blocks-field_module.css +37 -0
- package/dist/fields/code/code-editor.d.ts +20 -0
- package/dist/fields/code/code-editor.js +239 -0
- package/dist/fields/code/code-field.d.ts +21 -0
- package/dist/fields/code/code-field.js +124 -0
- package/dist/fields/code/code-field.module.js +7 -0
- package/dist/fields/code/code-field_module.css +58 -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-helpers.js +1 -0
- package/dist/fields/field-renderer.d.ts +11 -2
- package/dist/fields/field-renderer.js +21 -4
- package/dist/fields/group/group-field.d.ts +23 -2
- package/dist/fields/group/group-field.js +7 -3
- package/dist/fields/relation/relation-picker.js +8 -2
- package/dist/fields/select/select-field.js +4 -2
- package/dist/fields/select/select-field.module.js +1 -0
- package/dist/fields/select/select-field_module.css +4 -0
- package/dist/fields/sortable-item.d.ts +6 -0
- package/dist/fields/sortable-item.js +44 -25
- package/dist/fields/text/text-field_module.css +1 -0
- package/dist/fields/text-area/text-area-field_module.css +1 -0
- package/dist/forms/form-context.js +1 -1
- package/dist/forms/form-renderer.d.ts +1 -1
- package/dist/forms/form-renderer.js +3 -1
- package/dist/forms/tree-placement-widget.d.ts +1 -1
- package/package.json +23 -10
- package/src/fields/array/array-field.module.css +30 -0
- package/src/fields/array/array-field.tsx +74 -20
- package/src/fields/blocks/blocks-field.module.css +53 -0
- package/src/fields/blocks/blocks-field.tsx +38 -1
- package/src/fields/code/code-editor.tsx +246 -0
- package/src/fields/code/code-field.module.css +84 -0
- package/src/fields/code/code-field.tsx +191 -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-helpers.ts +1 -0
- package/src/fields/field-renderer.tsx +33 -2
- package/src/fields/field-services-types.ts +1 -1
- package/src/fields/group/group-field.tsx +29 -2
- package/src/fields/relation/relation-picker.tsx +11 -0
- package/src/fields/select/select-field.module.css +5 -0
- package/src/fields/select/select-field.tsx +9 -2
- package/src/fields/sortable-item.tsx +115 -34
- package/src/fields/text/text-field.module.css +1 -0
- package/src/fields/text-area/text-area-field.module.css +1 -0
- package/src/forms/form-context.tsx +9 -1
- package/src/forms/form-renderer.tsx +3 -1
- package/src/forms/tree-placement-widget.tsx +1 -1
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import react, { Suspense, useCallback } from "react";
|
|
3
|
+
import { ErrorText, HelpText, Label } from "@byline/ui/react";
|
|
4
|
+
import classnames from "classnames";
|
|
5
|
+
import { useFieldError, useFieldValue } from "../../forms/form-context.js";
|
|
6
|
+
import { LocaleBadge } from "../locale-badge.js";
|
|
7
|
+
import code_field_module from "./code-field.module.js";
|
|
8
|
+
const CodeEditor = /*#__PURE__*/ react.lazy(()=>import("./code-editor.js"));
|
|
9
|
+
const siblingFieldPath = (fieldPath, siblingName)=>{
|
|
10
|
+
const lastDot = fieldPath.lastIndexOf('.');
|
|
11
|
+
return -1 === lastDot ? siblingName : `${fieldPath.slice(0, lastDot + 1)}${siblingName}`;
|
|
12
|
+
};
|
|
13
|
+
const CodeField = ({ field, value, defaultValue, onChange, id, path, locale, components })=>{
|
|
14
|
+
const fieldPath = path ?? field.name;
|
|
15
|
+
const fieldError = useFieldError(fieldPath);
|
|
16
|
+
const fieldValue = useFieldValue(fieldPath);
|
|
17
|
+
const incomingValue = value ?? fieldValue ?? defaultValue ?? '';
|
|
18
|
+
const htmlId = id ?? fieldPath;
|
|
19
|
+
const siblingPath = field.languageField ? siblingFieldPath(fieldPath, field.languageField) : fieldPath;
|
|
20
|
+
const siblingLanguage = useFieldValue(siblingPath);
|
|
21
|
+
const effectiveLanguage = (field.languageField ? siblingLanguage : void 0) || field.language;
|
|
22
|
+
const handleChange = useCallback((value)=>{
|
|
23
|
+
if (onChange) onChange(value);
|
|
24
|
+
}, [
|
|
25
|
+
onChange
|
|
26
|
+
]);
|
|
27
|
+
const slots = components;
|
|
28
|
+
const CustomLabel = slots?.Label;
|
|
29
|
+
const CustomHelpText = slots?.HelpText;
|
|
30
|
+
const CustomField = slots?.Field;
|
|
31
|
+
const BeforeField = slots?.beforeField;
|
|
32
|
+
const AfterField = slots?.afterField;
|
|
33
|
+
const slotBaseProps = {
|
|
34
|
+
field: field,
|
|
35
|
+
path: fieldPath,
|
|
36
|
+
value: incomingValue,
|
|
37
|
+
error: fieldError,
|
|
38
|
+
id: htmlId
|
|
39
|
+
};
|
|
40
|
+
const showBadge = !!locale && !!field.label;
|
|
41
|
+
const hasCustomLabel = !!CustomLabel;
|
|
42
|
+
const labelRowClass = classnames('byline-field-code-label-row', code_field_module["label-row"]);
|
|
43
|
+
const renderLabel = ()=>{
|
|
44
|
+
if (hasCustomLabel) return /*#__PURE__*/ jsxs("div", {
|
|
45
|
+
className: labelRowClass,
|
|
46
|
+
children: [
|
|
47
|
+
/*#__PURE__*/ jsx(CustomLabel, {
|
|
48
|
+
...slotBaseProps,
|
|
49
|
+
label: field.label,
|
|
50
|
+
required: !field.optional
|
|
51
|
+
}),
|
|
52
|
+
showBadge && /*#__PURE__*/ jsx(LocaleBadge, {
|
|
53
|
+
locale: locale
|
|
54
|
+
})
|
|
55
|
+
]
|
|
56
|
+
});
|
|
57
|
+
if (field.label) return /*#__PURE__*/ jsxs("div", {
|
|
58
|
+
className: labelRowClass,
|
|
59
|
+
children: [
|
|
60
|
+
/*#__PURE__*/ jsx(Label, {
|
|
61
|
+
id: `${htmlId}-label`,
|
|
62
|
+
htmlFor: htmlId,
|
|
63
|
+
label: field.label,
|
|
64
|
+
required: !field.optional
|
|
65
|
+
}),
|
|
66
|
+
showBadge && /*#__PURE__*/ jsx(LocaleBadge, {
|
|
67
|
+
locale: locale
|
|
68
|
+
})
|
|
69
|
+
]
|
|
70
|
+
});
|
|
71
|
+
return null;
|
|
72
|
+
};
|
|
73
|
+
const renderInput = ()=>{
|
|
74
|
+
if (CustomField) return /*#__PURE__*/ jsx(CustomField, {
|
|
75
|
+
...slotBaseProps,
|
|
76
|
+
onChange: handleChange,
|
|
77
|
+
defaultValue: defaultValue,
|
|
78
|
+
placeholder: field.placeholder
|
|
79
|
+
});
|
|
80
|
+
return /*#__PURE__*/ jsx(Suspense, {
|
|
81
|
+
fallback: /*#__PURE__*/ jsx("textarea", {
|
|
82
|
+
className: classnames('byline-field-code-loading', code_field_module.loading),
|
|
83
|
+
readOnly: true,
|
|
84
|
+
value: incomingValue,
|
|
85
|
+
rows: 6,
|
|
86
|
+
"aria-label": field.label
|
|
87
|
+
}),
|
|
88
|
+
children: /*#__PURE__*/ jsx(CodeEditor, {
|
|
89
|
+
id: htmlId,
|
|
90
|
+
value: incomingValue,
|
|
91
|
+
language: effectiveLanguage,
|
|
92
|
+
onChange: handleChange,
|
|
93
|
+
readOnly: true === field.readOnly,
|
|
94
|
+
ariaInvalid: null != fieldError,
|
|
95
|
+
ariaDescribedBy: null != fieldError ? `error-for-${htmlId}` : field.helpText ? `help-for-${htmlId}` : void 0
|
|
96
|
+
})
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
return /*#__PURE__*/ jsxs("div", {
|
|
100
|
+
className: `byline-field-code ${field.name}`,
|
|
101
|
+
children: [
|
|
102
|
+
renderLabel(),
|
|
103
|
+
BeforeField && /*#__PURE__*/ jsx(BeforeField, {
|
|
104
|
+
...slotBaseProps
|
|
105
|
+
}),
|
|
106
|
+
renderInput(),
|
|
107
|
+
AfterField && /*#__PURE__*/ jsx(AfterField, {
|
|
108
|
+
...slotBaseProps
|
|
109
|
+
}),
|
|
110
|
+
null != fieldError && /*#__PURE__*/ jsx(ErrorText, {
|
|
111
|
+
id: `error-for-${htmlId}`,
|
|
112
|
+
text: fieldError
|
|
113
|
+
}),
|
|
114
|
+
CustomHelpText ? /*#__PURE__*/ jsx(CustomHelpText, {
|
|
115
|
+
...slotBaseProps,
|
|
116
|
+
helpText: field.helpText
|
|
117
|
+
}) : null == fieldError && field.helpText && /*#__PURE__*/ jsx(HelpText, {
|
|
118
|
+
id: `help-for-${htmlId}`,
|
|
119
|
+
text: field.helpText
|
|
120
|
+
})
|
|
121
|
+
]
|
|
122
|
+
});
|
|
123
|
+
};
|
|
124
|
+
export { CodeField };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
:is(.label-row-bNilI8, .byline-field-code-label-row) {
|
|
2
|
+
align-items: center;
|
|
3
|
+
margin-bottom: .25rem;
|
|
4
|
+
display: flex;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.byline-field-code {
|
|
8
|
+
--byline-code-bg: var(--canvas-50, #fafafa);
|
|
9
|
+
--byline-code-fg: var(--canvas-950, #1c1c1e);
|
|
10
|
+
--byline-code-border: var(--canvas-300, #d6d6d6);
|
|
11
|
+
--byline-code-gutter-bg: var(--canvas-100, #f2f2f2);
|
|
12
|
+
--byline-code-gutter-fg: var(--canvas-500, #8b8b90);
|
|
13
|
+
--byline-code-active-line: #0000000a;
|
|
14
|
+
--byline-code-selection: #3b82f62e;
|
|
15
|
+
--byline-code-caret: var(--canvas-950, #1c1c1e);
|
|
16
|
+
--byline-code-focus-ring: #3b82f699;
|
|
17
|
+
--byline-code-keyword: #7c3aed;
|
|
18
|
+
--byline-code-string: #15803d;
|
|
19
|
+
--byline-code-comment: #767680;
|
|
20
|
+
--byline-code-number: #c2410c;
|
|
21
|
+
--byline-code-function: #1d4ed8;
|
|
22
|
+
--byline-code-type: #0f766e;
|
|
23
|
+
--byline-code-property: #a16207;
|
|
24
|
+
--byline-code-operator: #52525b;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
:is([data-theme="dark"], .dark) .byline-field-code {
|
|
28
|
+
--byline-code-bg: var(--canvas-900, #1b1b1f);
|
|
29
|
+
--byline-code-fg: var(--canvas-100, #e6e6ea);
|
|
30
|
+
--byline-code-border: var(--canvas-700, #3f3f46);
|
|
31
|
+
--byline-code-gutter-bg: var(--canvas-800, #232327);
|
|
32
|
+
--byline-code-gutter-fg: var(--canvas-500, #77777f);
|
|
33
|
+
--byline-code-active-line: #ffffff0d;
|
|
34
|
+
--byline-code-selection: #60a5fa47;
|
|
35
|
+
--byline-code-caret: var(--canvas-100, #e6e6ea);
|
|
36
|
+
--byline-code-focus-ring: #60a5fa8c;
|
|
37
|
+
--byline-code-keyword: #c4b5fd;
|
|
38
|
+
--byline-code-string: #86efac;
|
|
39
|
+
--byline-code-comment: #8e8e96;
|
|
40
|
+
--byline-code-number: #fdba74;
|
|
41
|
+
--byline-code-function: #93c5fd;
|
|
42
|
+
--byline-code-type: #5eead4;
|
|
43
|
+
--byline-code-property: #fde047;
|
|
44
|
+
--byline-code-operator: #a1a1aa;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
:is(.loading-sejBT6, .byline-field-code-loading) {
|
|
48
|
+
resize: none;
|
|
49
|
+
width: 100%;
|
|
50
|
+
font-family: var(--byline-code-font, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
|
|
51
|
+
font-size: var(--byline-code-font-size, 13px);
|
|
52
|
+
color: var(--byline-code-fg);
|
|
53
|
+
background-color: var(--byline-code-bg);
|
|
54
|
+
border: 1px solid var(--byline-code-border);
|
|
55
|
+
border-radius: var(--byline-code-radius, 4px);
|
|
56
|
+
padding: .5rem;
|
|
57
|
+
}
|
|
58
|
+
|
|
@@ -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,7 +5,7 @@
|
|
|
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;
|
|
@@ -32,6 +32,15 @@ interface FieldRendererProps {
|
|
|
32
32
|
* Ignored when `field.type !== 'richText'`.
|
|
33
33
|
*/
|
|
34
34
|
editor?: RichTextEditorComponent;
|
|
35
|
+
/**
|
|
36
|
+
* Admin overrides for this field's *descendants*, keyed by dotted,
|
|
37
|
+
* index-free schema paths relative to this field ('answer',
|
|
38
|
+
* 'filesGroup.publicationFile'). Only meaningful when `field` is a
|
|
39
|
+
* structural `group` / `array` — the widget slices the map per child
|
|
40
|
+
* (see `sliceFieldAdmin`). `components` / `editor` above stay the
|
|
41
|
+
* overrides for this field itself.
|
|
42
|
+
*/
|
|
43
|
+
fieldAdmin?: Record<string, FieldAdminConfig>;
|
|
35
44
|
}
|
|
36
|
-
export declare const FieldRenderer: ({ field, defaultValue: initialDefault, basePath, disableSorting, hideLabel, collectionPath, contentLocale, components, editor, }: FieldRendererProps) => import("react").JSX.Element | null;
|
|
45
|
+
export declare const FieldRenderer: ({ field, defaultValue: initialDefault, basePath, disableSorting, hideLabel, collectionPath, contentLocale, components, editor, fieldAdmin, }: FieldRendererProps) => import("react").JSX.Element | null;
|
|
37
46
|
export {};
|
|
@@ -5,6 +5,7 @@ import { useFormContext } from "../forms/form-context.js";
|
|
|
5
5
|
import { ArrayField } from "./array/array-field.js";
|
|
6
6
|
import { BlocksField } from "./blocks/blocks-field.js";
|
|
7
7
|
import { CheckboxField } from "./checkbox/checkbox-field.js";
|
|
8
|
+
import { CodeField } from "./code/code-field.js";
|
|
8
9
|
import { DateTimeField } from "./datetime/datetime-field.js";
|
|
9
10
|
import field_renderer_module from "./field-renderer.module.js";
|
|
10
11
|
import { FileField } from "./file/file-field.js";
|
|
@@ -19,7 +20,7 @@ import { TextField } from "./text/text-field.js";
|
|
|
19
20
|
import { TextAreaField } from "./text-area/text-area-field.js";
|
|
20
21
|
import { useFieldChangeHandler } from "./use-field-change-handler.js";
|
|
21
22
|
import { useFieldCondition } from "./use-field-condition.js";
|
|
22
|
-
const FieldRenderer = ({ field, defaultValue: initialDefault, basePath, disableSorting, hideLabel, collectionPath, contentLocale, components, editor })=>{
|
|
23
|
+
const FieldRenderer = ({ field, defaultValue: initialDefault, basePath, disableSorting, hideLabel, collectionPath, contentLocale, components, editor, fieldAdmin })=>{
|
|
23
24
|
const path = basePath ? `${basePath}.${field.name}` : field.name;
|
|
24
25
|
const htmlId = path.replace(/[[\].]/g, '-');
|
|
25
26
|
const handleChange = useFieldChangeHandler(field, path);
|
|
@@ -60,6 +61,19 @@ const FieldRenderer = ({ field, defaultValue: initialDefault, basePath, disableS
|
|
|
60
61
|
locale: isLocalised ? contentLocale : void 0,
|
|
61
62
|
components: components
|
|
62
63
|
});
|
|
64
|
+
case 'code':
|
|
65
|
+
return /*#__PURE__*/ jsx(CodeField, {
|
|
66
|
+
field: hideLabel ? {
|
|
67
|
+
...field,
|
|
68
|
+
label: void 0
|
|
69
|
+
} : field,
|
|
70
|
+
defaultValue: defaultValue,
|
|
71
|
+
onChange: handleChange,
|
|
72
|
+
path: path,
|
|
73
|
+
id: htmlId,
|
|
74
|
+
locale: isLocalised ? contentLocale : void 0,
|
|
75
|
+
components: components
|
|
76
|
+
});
|
|
63
77
|
case 'checkbox':
|
|
64
78
|
return /*#__PURE__*/ jsx(CheckboxField, {
|
|
65
79
|
field: hideLabel ? {
|
|
@@ -189,8 +203,10 @@ const FieldRenderer = ({ field, defaultValue: initialDefault, basePath, disableS
|
|
|
189
203
|
} : field,
|
|
190
204
|
defaultValue: defaultValue,
|
|
191
205
|
path: path,
|
|
206
|
+
disableSorting: disableSorting,
|
|
192
207
|
collectionPath: collectionPath,
|
|
193
|
-
contentLocale: contentLocale
|
|
208
|
+
contentLocale: contentLocale,
|
|
209
|
+
fieldAdmin: fieldAdmin
|
|
194
210
|
});
|
|
195
211
|
case 'blocks':
|
|
196
212
|
if (!field.blocks) return null;
|
|
@@ -208,13 +224,14 @@ const FieldRenderer = ({ field, defaultValue: initialDefault, basePath, disableS
|
|
|
208
224
|
path: path,
|
|
209
225
|
disableSorting: disableSorting,
|
|
210
226
|
collectionPath: collectionPath,
|
|
211
|
-
contentLocale: contentLocale
|
|
227
|
+
contentLocale: contentLocale,
|
|
228
|
+
fieldAdmin: fieldAdmin
|
|
212
229
|
});
|
|
213
230
|
default:
|
|
214
231
|
return null;
|
|
215
232
|
}
|
|
216
233
|
};
|
|
217
|
-
const selfBadge = 'text' === field.type || 'textArea' === field.type || 'richText' === field.type;
|
|
234
|
+
const selfBadge = 'text' === field.type || 'textArea' === field.type || 'code' === field.type || 'richText' === field.type;
|
|
218
235
|
if (badge && !selfBadge) return /*#__PURE__*/ jsxs("div", {
|
|
219
236
|
className: classnames('byline-field-localized-wrap', field_renderer_module["localized-wrap"]),
|
|
220
237
|
children: [
|
|
@@ -5,11 +5,21 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Copyright (c) Infonomic Company Limited
|
|
7
7
|
*/
|
|
8
|
-
import type { GroupField as GroupFieldType } from '@byline/core';
|
|
8
|
+
import type { FieldAdminConfig, GroupField as GroupFieldType } from '@byline/core';
|
|
9
9
|
interface GroupFieldProps {
|
|
10
10
|
field: GroupFieldType;
|
|
11
11
|
defaultValue: any;
|
|
12
12
|
path: string;
|
|
13
|
+
/**
|
|
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.
|
|
21
|
+
*/
|
|
22
|
+
disableSorting?: boolean;
|
|
13
23
|
/**
|
|
14
24
|
* Collection path forwarded to upload-capable child fields (`file` / `image`),
|
|
15
25
|
* which need it to reach the `/upload` endpoint. Without it those fields fall
|
|
@@ -22,6 +32,17 @@ interface GroupFieldProps {
|
|
|
22
32
|
* locale badge.
|
|
23
33
|
*/
|
|
24
34
|
contentLocale?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Per-child-field admin overrides (`components` slots, richtext `editor`),
|
|
37
|
+
* keyed by dotted, index-free schema paths relative to this group
|
|
38
|
+
* ('caption', 'faq.answer'). Threaded by `BlocksField` from the site-wide
|
|
39
|
+
* `ClientConfig.blockAdmin` registry (block children render through a
|
|
40
|
+
* synthesized group) and by `FieldRenderer` for plain schema groups, whose
|
|
41
|
+
* map arrives pre-sliced from the collection admin config. Exact-name
|
|
42
|
+
* entries apply to the child itself; deeper entries are re-sliced and
|
|
43
|
+
* threaded on (see `sliceFieldAdmin`).
|
|
44
|
+
*/
|
|
45
|
+
fieldAdmin?: Record<string, FieldAdminConfig>;
|
|
25
46
|
}
|
|
26
|
-
export declare const GroupField: ({ field, defaultValue, path, collectionPath, contentLocale, }: GroupFieldProps) => import("react").JSX.Element;
|
|
47
|
+
export declare const GroupField: ({ field, defaultValue, path, disableSorting, collectionPath, contentLocale, fieldAdmin, }: GroupFieldProps) => import("react").JSX.Element;
|
|
27
48
|
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, collectionPath, contentLocale })=>{
|
|
10
|
+
const GroupField = ({ field, defaultValue, path, disableSorting = true, collectionPath, 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,9 +47,12 @@ const GroupField = ({ field, defaultValue, path, collectionPath, contentLocale }
|
|
|
46
47
|
field: innerField,
|
|
47
48
|
defaultValue: groupData[innerField.name],
|
|
48
49
|
basePath: path,
|
|
49
|
-
disableSorting:
|
|
50
|
+
disableSorting: disableSorting,
|
|
50
51
|
collectionPath: collectionPath,
|
|
51
|
-
contentLocale: contentLocale
|
|
52
|
+
contentLocale: contentLocale,
|
|
53
|
+
components: fieldAdmin?.[innerField.name]?.components,
|
|
54
|
+
editor: fieldAdmin?.[innerField.name]?.editor,
|
|
55
|
+
fieldAdmin: sliceFieldAdmin(fieldAdmin, innerField.name)
|
|
52
56
|
}, innerField.name))
|
|
53
57
|
}),
|
|
54
58
|
fieldError && /*#__PURE__*/ jsx(ErrorText, {
|
|
@@ -39,13 +39,18 @@ const RelationPicker = ({ targetCollectionPath, targetDefinition, displayField,
|
|
|
39
39
|
const selectFields = resolveSelectFields(targetDefinition, displayField, pickerColumns, extraSelectFields);
|
|
40
40
|
setLoading(true);
|
|
41
41
|
setError(null);
|
|
42
|
+
const itemViewSort = targetAdminConfig?.itemViewSort;
|
|
42
43
|
getCollectionDocuments({
|
|
43
44
|
collection: targetCollectionPath,
|
|
44
45
|
params: {
|
|
45
46
|
page,
|
|
46
47
|
page_size: PAGE_SIZE,
|
|
47
48
|
query: query.length > 0 ? query : void 0,
|
|
48
|
-
fields: selectFields
|
|
49
|
+
fields: selectFields,
|
|
50
|
+
...null != itemViewSort ? {
|
|
51
|
+
order: String(itemViewSort.field),
|
|
52
|
+
desc: 'desc' === itemViewSort.direction
|
|
53
|
+
} : {}
|
|
49
54
|
}
|
|
50
55
|
}).then((response)=>{
|
|
51
56
|
if (cancelled) return;
|
|
@@ -71,7 +76,8 @@ const RelationPicker = ({ targetCollectionPath, targetDefinition, displayField,
|
|
|
71
76
|
targetDefinition,
|
|
72
77
|
pickerColumns,
|
|
73
78
|
getCollectionDocuments,
|
|
74
|
-
t
|
|
79
|
+
t,
|
|
80
|
+
targetAdminConfig?.itemViewSort
|
|
75
81
|
]);
|
|
76
82
|
const resolvedDisplayField = displayField ?? targetDefinition?.useAsTitle ?? resolveFallbackDisplayField(targetDefinition) ?? null;
|
|
77
83
|
const handleSelect = useCallback(()=>{
|
|
@@ -17,10 +17,12 @@ const SelectField = ({ field, value, defaultValue, onChange, id, path })=>{
|
|
|
17
17
|
id: htmlId,
|
|
18
18
|
htmlFor: htmlId,
|
|
19
19
|
label: field.label,
|
|
20
|
-
required: !field.optional
|
|
20
|
+
required: !field.optional,
|
|
21
|
+
className: classnames('byline-field-select-label', select_field_module.label)
|
|
21
22
|
}),
|
|
22
23
|
/*#__PURE__*/ jsx(Select, {
|
|
23
|
-
size: "
|
|
24
|
+
size: "xs",
|
|
25
|
+
variant: "outlined",
|
|
24
26
|
id: htmlId,
|
|
25
27
|
name: field.name,
|
|
26
28
|
placeholder: "Select an option",
|
|
@@ -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 };
|
|
@@ -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();
|
|
@@ -104,7 +104,7 @@ export interface FormRendererProps {
|
|
|
104
104
|
* Opts the document-tree placement widget into the sidebar (above the
|
|
105
105
|
* available-locales widget). Sourced from `CollectionDefinition.tree` by the
|
|
106
106
|
* caller. Renders only in edit mode (placement needs a persisted document)
|
|
107
|
-
* and only when the host wires the tree services. See docs/04-collections/
|
|
107
|
+
* and only when the host wires the tree services. See docs/04-collections/04-document-trees.md.
|
|
108
108
|
*/
|
|
109
109
|
tree?: boolean;
|
|
110
110
|
headingLabel?: string;
|
|
@@ -5,6 +5,7 @@ import { getClientConfig } from "@byline/core";
|
|
|
5
5
|
import { useTranslation } from "@byline/i18n/react";
|
|
6
6
|
import { Alert, Button, ComboButton } from "@byline/ui/react";
|
|
7
7
|
import classnames from "classnames";
|
|
8
|
+
import { sliceFieldAdmin } from "../fields/field-admin.js";
|
|
8
9
|
import { FieldRenderer } from "../fields/field-renderer.js";
|
|
9
10
|
import { useBylineFieldServices } from "../fields/field-services-context.js";
|
|
10
11
|
import { AdminGroup } from "../presentation/group.js";
|
|
@@ -185,7 +186,8 @@ const FormContent = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpub
|
|
|
185
186
|
collectionPath: collectionPath,
|
|
186
187
|
contentLocale: contentLocale,
|
|
187
188
|
components: adminConfig?.fields?.[field.name]?.components,
|
|
188
|
-
editor: adminConfig?.fields?.[field.name]?.editor
|
|
189
|
+
editor: adminConfig?.fields?.[field.name]?.editor,
|
|
190
|
+
fieldAdmin: sliceFieldAdmin(adminConfig?.fields, field.name)
|
|
189
191
|
}, field.name);
|
|
190
192
|
};
|
|
191
193
|
const renderItem = (name)=>{
|
|
@@ -8,7 +8,7 @@ export interface TreePlacementWidgetProps {
|
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
10
10
|
* Sidebar widget for placing the current document within its collection's
|
|
11
|
-
* single-parent document tree (the `tree: true` primitive — docs/04-collections/
|
|
11
|
+
* single-parent document tree (the `tree: true` primitive — docs/04-collections/04-document-trees.md).
|
|
12
12
|
*
|
|
13
13
|
* The tree is document-grain and **unversioned**, so changes here write
|
|
14
14
|
* immediately (independent of the form's content save). The editor picks a
|