@byline/ui 1.4.0 → 1.6.1

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: "xs",
17
- id: id ?? fieldPath,
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.4.0",
6
+ "version": "1.6.1",
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.4.0",
69
- "@byline/client": "1.5.0",
70
- "@byline/core": "1.5.0"
68
+ "@byline/admin": "1.6.1",
69
+ "@byline/client": "1.6.1",
70
+ "@byline/core": "1.6.1"
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="xs"
41
- id={id ?? fieldPath}
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
- <PathWidget
688
- useAsPath={useAsPath}
689
- collectionPath={collectionPath ?? ''}
690
- defaultLocale={defaultLocale}
691
- mode={mode}
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 }