@byline/ui 1.7.7 → 1.8.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.
@@ -487,6 +487,7 @@ const FormContent = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpub
487
487
  useAsPath: useAsPath,
488
488
  collectionPath: collectionPath ?? '',
489
489
  defaultLocale: defaultLocale,
490
+ activeLocale: contentLocale,
490
491
  mode: mode
491
492
  }),
492
493
  (layout.sidebar ?? []).map((name)=>renderItem(name))
@@ -5,6 +5,15 @@ export interface PathWidgetProps {
5
5
  collectionPath: string;
6
6
  /** Default content locale, forwarded to the slugifier as context. */
7
7
  defaultLocale: string;
8
+ /**
9
+ * The locale currently being edited in the form. When this differs
10
+ * from `defaultLocale` (i.e. the editor is editing a translation),
11
+ * the widget renders read-only — phase 1 paths are default-locale
12
+ * territory, and the lifecycle drops translation-locale path changes
13
+ * with a warn. Locking the input prevents the warn path being hit
14
+ * through the admin form and gives editors a clear cue.
15
+ */
16
+ activeLocale: string;
8
17
  /** `'create'` shows the live derived preview as placeholder text. */
9
18
  mode: 'create' | 'edit';
10
19
  }
@@ -22,4 +31,4 @@ export interface PathWidgetProps {
22
31
  * Stable override handles: `.byline-form-path`, `.byline-form-path-header`,
23
32
  * `.byline-form-path-regenerate`.
24
33
  */
25
- export declare const PathWidget: ({ useAsPath, collectionPath, defaultLocale, mode }: PathWidgetProps) => import("react").JSX.Element;
34
+ export declare const PathWidget: ({ useAsPath, collectionPath, defaultLocale, activeLocale, mode, }: PathWidgetProps) => import("react").JSX.Element;
@@ -11,10 +11,11 @@ function coerceToString(value) {
11
11
  if (value instanceof Date) return value.toISOString();
12
12
  return String(value);
13
13
  }
14
- const PathWidget = ({ useAsPath, collectionPath, defaultLocale, mode })=>{
14
+ const PathWidget = ({ useAsPath, collectionPath, defaultLocale, activeLocale, mode })=>{
15
15
  const { setSystemPath } = useFormContext();
16
16
  const systemPath = useSystemPath();
17
17
  const sourceValue = useFieldValue(useAsPath ?? '');
18
+ const isReadOnly = activeLocale !== defaultLocale;
18
19
  const livePreview = useMemo(()=>{
19
20
  if (!useAsPath) return '';
20
21
  const asString = coerceToString(sourceValue);
@@ -52,13 +53,16 @@ const PathWidget = ({ useAsPath, collectionPath, defaultLocale, mode })=>{
52
53
  defaultLocale,
53
54
  collectionPath
54
55
  ]);
55
- const hint = inputValue.length > 0 && formatted !== inputValue ? `Suggested: "${formatted}"` : void 0;
56
- const placeholder = 'create' === mode && livePreview.length > 0 ? `Will be saved as "${livePreview}"` : void 0;
56
+ const validationHint = inputValue.length > 0 && formatted !== inputValue ? `Suggested: "${formatted}"` : void 0;
57
+ const readOnlyHint = isReadOnly ? `Path is set in the default locale ("${defaultLocale}") and applies across translations.` : void 0;
58
+ const hint = readOnlyHint ?? validationHint;
59
+ const placeholder = !isReadOnly && 'create' === mode && livePreview.length > 0 ? `Will be saved as "${livePreview}"` : void 0;
57
60
  const srDescription = [
58
61
  'System-managed URL path for this document.',
59
62
  placeholder,
60
63
  hint
61
64
  ].filter(Boolean).join(' ');
65
+ const showRegenerate = !isReadOnly && useAsPath && livePreview.length > 0 && livePreview !== systemPath;
62
66
  return /*#__PURE__*/ jsxs("div", {
63
67
  className: "byline-form-path",
64
68
  children: [
@@ -70,7 +74,7 @@ const PathWidget = ({ useAsPath, collectionPath, defaultLocale, mode })=>{
70
74
  htmlFor: "system-path",
71
75
  label: "Path"
72
76
  }),
73
- useAsPath && livePreview.length > 0 && livePreview !== systemPath && /*#__PURE__*/ jsxs("button", {
77
+ showRegenerate && /*#__PURE__*/ jsxs("button", {
74
78
  type: "button",
75
79
  onClick: handleRegenerate,
76
80
  className: classnames('byline-form-path-regenerate', path_widget_module.regenerate),
@@ -89,6 +93,7 @@ const PathWidget = ({ useAsPath, collectionPath, defaultLocale, mode })=>{
89
93
  placeholder: placeholder,
90
94
  onChange: (e)=>handleChange(e.target.value),
91
95
  helpText: hint,
96
+ readOnly: isReadOnly,
92
97
  "aria-describedby": "system-path-description"
93
98
  }),
94
99
  /*#__PURE__*/ jsx("span", {
@@ -1595,3 +1595,7 @@
1595
1595
  visibility: visible;
1596
1596
  transform: matrix(1, 0, 0, 1, 0, 0);
1597
1597
  }
1598
+
1599
+ .byline-ui {
1600
+ isolation: isolate;
1601
+ }
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.7.7",
6
+ "version": "1.8.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.7.7",
69
- "@byline/core": "1.7.7",
70
- "@byline/client": "1.7.7"
68
+ "@byline/client": "1.8.1",
69
+ "@byline/core": "1.8.1",
70
+ "@byline/admin": "1.8.1"
71
71
  },
72
72
  "peerDependencies": {
73
73
  "react": "^19.0.0",
@@ -721,6 +721,7 @@ const FormContent = ({
721
721
  useAsPath={useAsPath}
722
722
  collectionPath={collectionPath ?? ''}
723
723
  defaultLocale={defaultLocale}
724
+ activeLocale={contentLocale}
724
725
  mode={mode}
725
726
  />
726
727
  )}
@@ -35,6 +35,15 @@ export interface PathWidgetProps {
35
35
  collectionPath: string
36
36
  /** Default content locale, forwarded to the slugifier as context. */
37
37
  defaultLocale: string
38
+ /**
39
+ * The locale currently being edited in the form. When this differs
40
+ * from `defaultLocale` (i.e. the editor is editing a translation),
41
+ * the widget renders read-only — phase 1 paths are default-locale
42
+ * territory, and the lifecycle drops translation-locale path changes
43
+ * with a warn. Locking the input prevents the warn path being hit
44
+ * through the admin form and gives editors a clear cue.
45
+ */
46
+ activeLocale: string
38
47
  /** `'create'` shows the live derived preview as placeholder text. */
39
48
  mode: 'create' | 'edit'
40
49
  }
@@ -53,11 +62,23 @@ export interface PathWidgetProps {
53
62
  * Stable override handles: `.byline-form-path`, `.byline-form-path-header`,
54
63
  * `.byline-form-path-regenerate`.
55
64
  */
56
- export const PathWidget = ({ useAsPath, collectionPath, defaultLocale, mode }: PathWidgetProps) => {
65
+ export const PathWidget = ({
66
+ useAsPath,
67
+ collectionPath,
68
+ defaultLocale,
69
+ activeLocale,
70
+ mode,
71
+ }: PathWidgetProps) => {
57
72
  const { setSystemPath } = useFormContext()
58
73
  const systemPath = useSystemPath()
59
74
  const sourceValue = useFieldValue<unknown>(useAsPath ?? '')
60
75
 
76
+ // Phase 1: paths are written/edited only under the default content
77
+ // locale. When editing a translation, the widget locks down — the
78
+ // input is read-only, the Regenerate action is suppressed, and a
79
+ // helpText line explains why.
80
+ const isReadOnly = activeLocale !== defaultLocale
81
+
61
82
  // Live preview — what the server would derive from the current source
62
83
  // field value if no override were set. Used as placeholder in create
63
84
  // mode and as the target of the "Regenerate" action.
@@ -93,26 +114,40 @@ export const PathWidget = ({ useAsPath, collectionPath, defaultLocale, mode }: P
93
114
  return slugify(inputValue, { locale: defaultLocale, collectionPath })
94
115
  }, [inputValue, defaultLocale, collectionPath])
95
116
 
96
- const hint =
117
+ const validationHint =
97
118
  inputValue.length > 0 && formatted !== inputValue ? `Suggested: "${formatted}"` : undefined
98
119
 
120
+ // When read-only, replace the live validation hint with a fixed
121
+ // explanatory line so editors understand why the field is locked.
122
+ const readOnlyHint = isReadOnly
123
+ ? `Path is set in the default locale ("${defaultLocale}") and applies across translations.`
124
+ : undefined
125
+
126
+ const hint = readOnlyHint ?? validationHint
127
+
99
128
  const placeholder =
100
- mode === 'create' && livePreview.length > 0 ? `Will be saved as "${livePreview}"` : undefined
129
+ !isReadOnly && mode === 'create' && livePreview.length > 0
130
+ ? `Will be saved as "${livePreview}"`
131
+ : undefined
101
132
 
102
133
  // Screen-reader description. The input's base purpose ("System-managed
103
134
  // URL path") plus whichever of the visible hints (placeholder preview
104
- // in create mode, "Suggested" validation hint) currently applies. The
105
- // visible helpText/placeholder cover sighted users; this element makes
106
- // the same information addressable via aria-describedby for AT.
135
+ // in create mode, "Suggested" validation hint, or read-only explainer)
136
+ // currently applies. The visible helpText/placeholder cover sighted
137
+ // users; this element makes the same information addressable via
138
+ // aria-describedby for AT.
107
139
  const srDescription = ['System-managed URL path for this document.', placeholder, hint]
108
140
  .filter(Boolean)
109
141
  .join(' ')
110
142
 
143
+ const showRegenerate =
144
+ !isReadOnly && useAsPath && livePreview.length > 0 && livePreview !== systemPath
145
+
111
146
  return (
112
147
  <div className="byline-form-path">
113
148
  <div className={cx('byline-form-path-header', styles.header)}>
114
149
  <Label id="system-path-label" htmlFor="system-path" label="Path" />
115
- {useAsPath && livePreview.length > 0 && livePreview !== systemPath && (
150
+ {showRegenerate && (
116
151
  <button
117
152
  type="button"
118
153
  onClick={handleRegenerate}
@@ -130,6 +165,7 @@ export const PathWidget = ({ useAsPath, collectionPath, defaultLocale, mode }: P
130
165
  placeholder={placeholder}
131
166
  onChange={(e) => handleChange(e.target.value)}
132
167
  helpText={hint}
168
+ readOnly={isReadOnly}
133
169
  aria-describedby="system-path-description"
134
170
  />
135
171
  <span
@@ -6,3 +6,7 @@
6
6
  @import "./functional/functional.css";
7
7
  @import "./theme/theme.css";
8
8
  @import "./components/components.css";
9
+
10
+ .byline-ui {
11
+ isolation: isolate;
12
+ }