@byline/ui 1.3.1 → 1.6.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.
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import classnames from "classnames";
|
|
3
3
|
import { useFieldError, useFieldValue, useIsDirty } from "../../forms/form-context.js";
|
|
4
|
-
import { ErrorText, Select } from "../../uikit.js";
|
|
4
|
+
import { ErrorText, Label, Select } from "../../uikit.js";
|
|
5
5
|
import select_field_module from "./select-field.module.js";
|
|
6
6
|
const SelectField = ({ field, value, defaultValue, onChange, id, path })=>{
|
|
7
7
|
const fieldPath = path ?? field.name;
|
|
@@ -9,16 +9,24 @@ const SelectField = ({ field, value, defaultValue, onChange, id, path })=>{
|
|
|
9
9
|
const isDirty = useIsDirty(fieldPath);
|
|
10
10
|
const fieldValue = useFieldValue(fieldPath);
|
|
11
11
|
const incomingValue = value ?? fieldValue ?? defaultValue ?? '';
|
|
12
|
+
const htmlId = id ?? fieldPath;
|
|
12
13
|
return /*#__PURE__*/ jsxs("div", {
|
|
13
14
|
className: `byline-field-select ${field.name}`,
|
|
14
15
|
children: [
|
|
16
|
+
field.label && /*#__PURE__*/ jsx(Label, {
|
|
17
|
+
id: htmlId,
|
|
18
|
+
htmlFor: htmlId,
|
|
19
|
+
label: field.label,
|
|
20
|
+
required: !field.optional
|
|
21
|
+
}),
|
|
15
22
|
/*#__PURE__*/ jsx(Select, {
|
|
16
|
-
size: "
|
|
17
|
-
id:
|
|
23
|
+
size: "sm",
|
|
24
|
+
id: htmlId,
|
|
18
25
|
name: field.name,
|
|
19
26
|
placeholder: "Select an option",
|
|
20
27
|
required: !field.optional,
|
|
21
28
|
value: incomingValue,
|
|
29
|
+
ariaLabel: field.label,
|
|
22
30
|
helpText: field.helpText,
|
|
23
31
|
items: field.options.map((opt)=>({
|
|
24
32
|
value: opt.value,
|
|
@@ -49,6 +49,14 @@ export interface FormRendererProps {
|
|
|
49
49
|
initialLocale?: string;
|
|
50
50
|
/** Called when the user picks a different content locale. */
|
|
51
51
|
onLocaleChange?: (locale: string) => void;
|
|
52
|
+
/**
|
|
53
|
+
* Schema-mismatch warnings produced by a "best-effort" reconstruction
|
|
54
|
+
* of the document (`findById({ lenient: true })`). When present, the
|
|
55
|
+
* form renders an inline Alert telling the editor that fields from a
|
|
56
|
+
* previous schema have been dropped — saving the form will overwrite
|
|
57
|
+
* them with the new shape.
|
|
58
|
+
*/
|
|
59
|
+
restoreWarnings?: string[];
|
|
52
60
|
/**
|
|
53
61
|
* Default content locale used when no `initialLocale` is supplied and as the
|
|
54
62
|
* fallback inside `PathWidget`. Hosts typically pass their app-wide
|
|
@@ -62,4 +70,4 @@ export interface FormRendererProps {
|
|
|
62
70
|
*/
|
|
63
71
|
useNavigationGuard?: UseNavigationGuard;
|
|
64
72
|
}
|
|
65
|
-
export declare const FormRenderer: ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpublish, onDelete, nextStatus, workflowStatuses, publishedVersion, initialData, adminConfig, useAsTitle, useAsPath, headingLabel, headerSlot, collectionPath, initialLocale, onLocaleChange, defaultLocale, useNavigationGuard, }: FormRendererProps) => import("react").JSX.Element;
|
|
73
|
+
export declare const FormRenderer: ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpublish, onDelete, nextStatus, workflowStatuses, publishedVersion, initialData, adminConfig, useAsTitle, useAsPath, headingLabel, headerSlot, collectionPath, initialLocale, onLocaleChange, defaultLocale, useNavigationGuard, restoreWarnings, }: FormRendererProps) => import("react").JSX.Element;
|
|
@@ -8,7 +8,7 @@ import { AdminTabs } from "../admin/tabs.js";
|
|
|
8
8
|
import { FieldRenderer } from "../fields/field-renderer.js";
|
|
9
9
|
import { LocalDateTime } from "../fields/local-date-time.js";
|
|
10
10
|
import { useBylineFieldServices } from "../services/field-services-context.js";
|
|
11
|
-
import { Button, ComboButton, Modal } from "../uikit.js";
|
|
11
|
+
import { Alert, Button, ComboButton, Modal } from "../uikit.js";
|
|
12
12
|
import { DocumentActions } from "./document-actions.js";
|
|
13
13
|
import { FormProvider, useFieldValue, useFormContext } from "./form-context.js";
|
|
14
14
|
import form_renderer_module from "./form-renderer.module.js";
|
|
@@ -139,7 +139,7 @@ function computeStatusTransitions(currentStatus, workflowStatuses, nextStatus) {
|
|
|
139
139
|
secondaryStatuses
|
|
140
140
|
};
|
|
141
141
|
}
|
|
142
|
-
const FormContent = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpublish, onDelete, nextStatus, workflowStatuses, publishedVersion, initialData, adminConfig, useAsTitle, useAsPath, headingLabel, headerSlot, collectionPath, initialLocale, onLocaleChange, defaultLocale = 'en', useNavigationGuard: useNavigationGuardProp, _activeTabBySet, _onTabChange })=>{
|
|
142
|
+
const FormContent = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpublish, onDelete, nextStatus, workflowStatuses, publishedVersion, initialData, adminConfig, useAsTitle, useAsPath, headingLabel, headerSlot, collectionPath, initialLocale, onLocaleChange, defaultLocale = 'en', useNavigationGuard: useNavigationGuardProp, restoreWarnings, _activeTabBySet, _onTabChange })=>{
|
|
143
143
|
const { getFieldValues, runFieldHooks, validateForm, errors: initialErrors, hasChanges: hasChangesFn, resetHasChanges, getPatches, getSystemPath, subscribeErrors, subscribeMeta, setFieldValue, setFieldError, getPendingUploads, clearPendingUploads } = useFormContext();
|
|
144
144
|
const [errors, setErrors] = useState(initialErrors);
|
|
145
145
|
const [hasChanges, setHasChanges] = useState(hasChangesFn());
|
|
@@ -450,6 +450,29 @@ const FormContent = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpub
|
|
|
450
450
|
})
|
|
451
451
|
]
|
|
452
452
|
}),
|
|
453
|
+
restoreWarnings && restoreWarnings.length > 0 && /*#__PURE__*/ jsxs(Alert, {
|
|
454
|
+
className: "m-0 mt-4",
|
|
455
|
+
intent: "warning",
|
|
456
|
+
icon: true,
|
|
457
|
+
close: false,
|
|
458
|
+
title: "This document was loaded with a best-effort reconstruction",
|
|
459
|
+
children: [
|
|
460
|
+
/*#__PURE__*/ jsxs("p", {
|
|
461
|
+
children: [
|
|
462
|
+
"The collection schema has changed since this document was last saved, and",
|
|
463
|
+
' ',
|
|
464
|
+
1 === restoreWarnings.length ? '1 field could not be restored against the current shape.' : `${restoreWarnings.length} fields could not be restored against the current shape.`,
|
|
465
|
+
' ',
|
|
466
|
+
"The form below shows only the fields that match the new schema. Saving will overwrite the document with the new shape — any data that did not match will be lost. To preserve it, copy what you need before saving, or delete this document and recreate it. Errors:"
|
|
467
|
+
]
|
|
468
|
+
}),
|
|
469
|
+
/*#__PURE__*/ jsx("ul", {
|
|
470
|
+
children: restoreWarnings.map((w)=>/*#__PURE__*/ jsx("li", {
|
|
471
|
+
children: w
|
|
472
|
+
}, w))
|
|
473
|
+
})
|
|
474
|
+
]
|
|
475
|
+
}),
|
|
453
476
|
/*#__PURE__*/ jsxs("div", {
|
|
454
477
|
className: classnames('byline-form-layout', form_renderer_module.layout),
|
|
455
478
|
children: [
|
|
@@ -517,7 +540,7 @@ const FormContent = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpub
|
|
|
517
540
|
]
|
|
518
541
|
});
|
|
519
542
|
};
|
|
520
|
-
const FormRenderer = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpublish, onDelete, nextStatus, workflowStatuses, publishedVersion, initialData, adminConfig, useAsTitle, useAsPath, headingLabel, headerSlot, collectionPath, initialLocale, onLocaleChange, defaultLocale, useNavigationGuard })=>{
|
|
543
|
+
const FormRenderer = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpublish, onDelete, nextStatus, workflowStatuses, publishedVersion, initialData, adminConfig, useAsTitle, useAsPath, headingLabel, headerSlot, collectionPath, initialLocale, onLocaleChange, defaultLocale, useNavigationGuard, restoreWarnings })=>{
|
|
521
544
|
const savedTabsRef = useRef({});
|
|
522
545
|
return /*#__PURE__*/ jsx(FormProvider, {
|
|
523
546
|
initialData: initialData,
|
|
@@ -543,6 +566,7 @@ const FormRenderer = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpu
|
|
|
543
566
|
onLocaleChange: onLocaleChange,
|
|
544
567
|
defaultLocale: defaultLocale,
|
|
545
568
|
useNavigationGuard: useNavigationGuard,
|
|
569
|
+
restoreWarnings: restoreWarnings,
|
|
546
570
|
_activeTabBySet: savedTabsRef.current,
|
|
547
571
|
_onTabChange: (tabSetName, tabName)=>{
|
|
548
572
|
savedTabsRef.current = {
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"private": false,
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MPL-2.0",
|
|
6
|
-
"version": "1.
|
|
6
|
+
"version": "1.6.0",
|
|
7
7
|
"engines": {
|
|
8
8
|
"node": ">=20.9.0"
|
|
9
9
|
},
|
|
@@ -65,9 +65,9 @@
|
|
|
65
65
|
"react-diff-viewer-continued": "^4.2.2",
|
|
66
66
|
"zod": "^4.4.2",
|
|
67
67
|
"zod-form-data": "^3.0.1",
|
|
68
|
-
"@byline/admin": "1.
|
|
69
|
-
"@byline/
|
|
70
|
-
"@byline/
|
|
68
|
+
"@byline/admin": "1.6.0",
|
|
69
|
+
"@byline/client": "1.6.0",
|
|
70
|
+
"@byline/core": "1.6.0"
|
|
71
71
|
},
|
|
72
72
|
"peerDependencies": {
|
|
73
73
|
"react": "^19.0.0",
|
|
@@ -10,7 +10,7 @@ import type { SelectField as FieldType } from '@byline/core'
|
|
|
10
10
|
import cx from 'classnames'
|
|
11
11
|
|
|
12
12
|
import { useFieldError, useFieldValue, useIsDirty } from '../../forms/form-context'
|
|
13
|
-
import { ErrorText, Select } from '../../uikit.js'
|
|
13
|
+
import { ErrorText, Label, Select } from '../../uikit.js'
|
|
14
14
|
import styles from './select-field.module.css'
|
|
15
15
|
|
|
16
16
|
export const SelectField = ({
|
|
@@ -33,16 +33,21 @@ export const SelectField = ({
|
|
|
33
33
|
const isDirty = useIsDirty(fieldPath)
|
|
34
34
|
const fieldValue = useFieldValue<string | undefined>(fieldPath)
|
|
35
35
|
const incomingValue = value ?? fieldValue ?? defaultValue ?? ''
|
|
36
|
+
const htmlId = id ?? fieldPath
|
|
36
37
|
|
|
37
38
|
return (
|
|
38
39
|
<div className={`byline-field-select ${field.name}`}>
|
|
40
|
+
{field.label && (
|
|
41
|
+
<Label id={htmlId} htmlFor={htmlId} label={field.label} required={!field.optional} />
|
|
42
|
+
)}
|
|
39
43
|
<Select<string>
|
|
40
|
-
size="
|
|
41
|
-
id={
|
|
44
|
+
size="sm"
|
|
45
|
+
id={htmlId}
|
|
42
46
|
name={field.name}
|
|
43
47
|
placeholder="Select an option"
|
|
44
48
|
required={!field.optional}
|
|
45
49
|
value={incomingValue}
|
|
50
|
+
ariaLabel={field.label}
|
|
46
51
|
helpText={field.helpText}
|
|
47
52
|
items={field.options.map((opt) => ({ value: opt.value, label: opt.label }))}
|
|
48
53
|
onValueChange={(value) => {
|
|
@@ -26,7 +26,7 @@ import { AdminTabs } from '../admin/tabs'
|
|
|
26
26
|
import { FieldRenderer } from '../fields/field-renderer'
|
|
27
27
|
import { LocalDateTime } from '../fields/local-date-time'
|
|
28
28
|
import { useBylineFieldServices } from '../services/field-services-context'
|
|
29
|
-
import { Button, ComboButton, Modal } from '../uikit.js'
|
|
29
|
+
import { Alert, Button, ComboButton, Modal } from '../uikit.js'
|
|
30
30
|
import { DocumentActions } from './document-actions'
|
|
31
31
|
import { FormProvider, useFieldValue, useFormContext } from './form-context'
|
|
32
32
|
import styles from './form-renderer.module.css'
|
|
@@ -77,6 +77,14 @@ export interface FormRendererProps {
|
|
|
77
77
|
initialLocale?: string
|
|
78
78
|
/** Called when the user picks a different content locale. */
|
|
79
79
|
onLocaleChange?: (locale: string) => void
|
|
80
|
+
/**
|
|
81
|
+
* Schema-mismatch warnings produced by a "best-effort" reconstruction
|
|
82
|
+
* of the document (`findById({ lenient: true })`). When present, the
|
|
83
|
+
* form renders an inline Alert telling the editor that fields from a
|
|
84
|
+
* previous schema have been dropped — saving the form will overwrite
|
|
85
|
+
* them with the new shape.
|
|
86
|
+
*/
|
|
87
|
+
restoreWarnings?: string[]
|
|
80
88
|
/**
|
|
81
89
|
* Default content locale used when no `initialLocale` is supplied and as the
|
|
82
90
|
* fallback inside `PathWidget`. Hosts typically pass their app-wide
|
|
@@ -260,6 +268,7 @@ const FormContent = ({
|
|
|
260
268
|
onLocaleChange,
|
|
261
269
|
defaultLocale = 'en',
|
|
262
270
|
useNavigationGuard: useNavigationGuardProp,
|
|
271
|
+
restoreWarnings,
|
|
263
272
|
_activeTabBySet,
|
|
264
273
|
_onTabChange,
|
|
265
274
|
}: FormRendererProps & {
|
|
@@ -677,6 +686,30 @@ const FormContent = ({
|
|
|
677
686
|
/>
|
|
678
687
|
</div>
|
|
679
688
|
</div>
|
|
689
|
+
{restoreWarnings && restoreWarnings.length > 0 && (
|
|
690
|
+
<Alert
|
|
691
|
+
className="m-0 mt-4"
|
|
692
|
+
intent="warning"
|
|
693
|
+
icon={true}
|
|
694
|
+
close={false}
|
|
695
|
+
title="This document was loaded with a best-effort reconstruction"
|
|
696
|
+
>
|
|
697
|
+
<p>
|
|
698
|
+
The collection schema has changed since this document was last saved, and{' '}
|
|
699
|
+
{restoreWarnings.length === 1
|
|
700
|
+
? '1 field could not be restored against the current shape.'
|
|
701
|
+
: `${restoreWarnings.length} fields could not be restored against the current shape.`}{' '}
|
|
702
|
+
The form below shows only the fields that match the new schema. Saving will overwrite
|
|
703
|
+
the document with the new shape — any data that did not match will be lost. To preserve
|
|
704
|
+
it, copy what you need before saving, or delete this document and recreate it. Errors:
|
|
705
|
+
</p>
|
|
706
|
+
<ul>
|
|
707
|
+
{restoreWarnings.map((w) => (
|
|
708
|
+
<li key={w}>{w}</li>
|
|
709
|
+
))}
|
|
710
|
+
</ul>
|
|
711
|
+
</Alert>
|
|
712
|
+
)}
|
|
680
713
|
<div className={cx('byline-form-layout', styles.layout)}>
|
|
681
714
|
<div className={cx('byline-form-content', styles.content)}>
|
|
682
715
|
{layout.main.map((name) => renderItem(name))}
|
|
@@ -684,13 +717,13 @@ const FormContent = ({
|
|
|
684
717
|
<div className={cx('byline-form-sidebar', styles.sidebar)}>
|
|
685
718
|
{(useAsPath ||
|
|
686
719
|
(typeof initialData?.path === 'string' && initialData.path.length > 0)) && (
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
720
|
+
<PathWidget
|
|
721
|
+
useAsPath={useAsPath}
|
|
722
|
+
collectionPath={collectionPath ?? ''}
|
|
723
|
+
defaultLocale={defaultLocale}
|
|
724
|
+
mode={mode}
|
|
725
|
+
/>
|
|
726
|
+
)}
|
|
694
727
|
{(layout.sidebar ?? []).map((name) => renderItem(name))}
|
|
695
728
|
</div>
|
|
696
729
|
</div>
|
|
@@ -746,6 +779,7 @@ export const FormRenderer = ({
|
|
|
746
779
|
onLocaleChange,
|
|
747
780
|
defaultLocale,
|
|
748
781
|
useNavigationGuard,
|
|
782
|
+
restoreWarnings,
|
|
749
783
|
}: FormRendererProps) => {
|
|
750
784
|
// Persists per-tab-set active tab across locale-change remounts of FormContent.
|
|
751
785
|
// useRef so mutations never trigger a re-render of FormRenderer itself.
|
|
@@ -778,6 +812,7 @@ export const FormRenderer = ({
|
|
|
778
812
|
onLocaleChange={onLocaleChange}
|
|
779
813
|
defaultLocale={defaultLocale}
|
|
780
814
|
useNavigationGuard={useNavigationGuard}
|
|
815
|
+
restoreWarnings={restoreWarnings}
|
|
781
816
|
_activeTabBySet={savedTabsRef.current}
|
|
782
817
|
_onTabChange={(tabSetName, tabName) => {
|
|
783
818
|
savedTabsRef.current = { ...savedTabsRef.current, [tabSetName]: tabName }
|