@byline/admin 6.1.0 → 6.2.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.
Files changed (119) hide show
  1. package/dist/forms/form-layout-styles.test.node.d.ts +8 -0
  2. package/dist/forms/form-layout.d.ts +39 -0
  3. package/dist/forms/form-layout.js +100 -0
  4. package/dist/forms/form-page-chrome.d.ts +110 -0
  5. package/dist/forms/form-page-chrome.js +253 -0
  6. package/dist/forms/form-renderer.d.ts +2 -18
  7. package/dist/forms/form-renderer.js +104 -418
  8. package/dist/forms/form-renderer.module.js +2 -0
  9. package/dist/forms/form-renderer_module.css +13 -0
  10. package/dist/forms/use-form-submission.d.ts +74 -0
  11. package/dist/forms/use-form-submission.js +166 -0
  12. package/dist/forms/use-form-tabs.d.ts +29 -0
  13. package/dist/forms/use-form-tabs.js +45 -0
  14. package/dist/presentation/tab-strip-drag.d.ts +75 -0
  15. package/dist/presentation/tab-strip-drag.js +25 -0
  16. package/dist/presentation/tab-strip-drag.test.node.d.ts +8 -0
  17. package/dist/presentation/tab-strip-geometry.d.ts +75 -0
  18. package/dist/presentation/tab-strip-geometry.js +36 -0
  19. package/dist/presentation/tab-strip-geometry.test.node.d.ts +8 -0
  20. package/dist/presentation/tabs.d.ts +30 -7
  21. package/dist/presentation/tabs.js +260 -29
  22. package/dist/presentation/tabs.module.js +11 -1
  23. package/dist/presentation/tabs_module.css +166 -5
  24. package/package.json +7 -7
  25. package/src/forms/form-layout-styles.test.node.ts +32 -0
  26. package/src/forms/form-layout.test.tsx +276 -0
  27. package/src/forms/form-layout.tsx +198 -0
  28. package/src/forms/form-page-chrome.tsx +460 -0
  29. package/src/forms/form-renderer-submit.test.tsx +40 -0
  30. package/src/forms/form-renderer-tab-persistence.test.tsx +145 -0
  31. package/src/forms/form-renderer.module.css +18 -0
  32. package/src/forms/form-renderer.tsx +115 -593
  33. package/src/forms/use-form-submission.test.tsx +564 -0
  34. package/src/forms/use-form-submission.ts +293 -0
  35. package/src/forms/use-form-tabs.ts +89 -0
  36. package/src/presentation/tab-strip-drag.test.node.ts +143 -0
  37. package/src/presentation/tab-strip-drag.ts +114 -0
  38. package/src/presentation/tab-strip-geometry.test.node.ts +254 -0
  39. package/src/presentation/tab-strip-geometry.ts +158 -0
  40. package/src/presentation/tabs.module.css +209 -11
  41. package/src/presentation/tabs.test.tsx +481 -0
  42. package/src/presentation/tabs.tsx +369 -34
  43. package/dist/exports-parity.test.node.js +0 -37
  44. package/dist/fields/array/array-field.jsx +0 -209
  45. package/dist/fields/blocks/blocks-field.jsx +0 -248
  46. package/dist/fields/checkbox/checkbox-field.jsx +0 -24
  47. package/dist/fields/code/code-editor.jsx +0 -211
  48. package/dist/fields/code/code-field.jsx +0 -109
  49. package/dist/fields/column-formatter.jsx +0 -25
  50. package/dist/fields/date-time-formatter.jsx +0 -18
  51. package/dist/fields/datetime/datetime-field.jsx +0 -22
  52. package/dist/fields/draggable-context-menu.jsx +0 -50
  53. package/dist/fields/field-admin.test.node.js +0 -40
  54. package/dist/fields/field-renderer.jsx +0 -136
  55. package/dist/fields/field-services-context.jsx +0 -17
  56. package/dist/fields/file/file-field.jsx +0 -176
  57. package/dist/fields/file/file-upload-field.jsx +0 -89
  58. package/dist/fields/group/group-field.jsx +0 -46
  59. package/dist/fields/image/image-field.jsx +0 -183
  60. package/dist/fields/image/image-upload-field.jsx +0 -157
  61. package/dist/fields/local-date-time.jsx +0 -69
  62. package/dist/fields/locale-badge.jsx +0 -19
  63. package/dist/fields/numerical/numerical-field.jsx +0 -93
  64. package/dist/fields/relation/relation-column-formatter.jsx +0 -74
  65. package/dist/fields/relation/relation-display.jsx +0 -118
  66. package/dist/fields/relation/relation-field.jsx +0 -108
  67. package/dist/fields/relation/relation-many-field.jsx +0 -142
  68. package/dist/fields/relation/relation-picker.jsx +0 -240
  69. package/dist/fields/relation/relation-summary.jsx +0 -45
  70. package/dist/fields/select/select-field.jsx +0 -27
  71. package/dist/fields/sortable-item.jsx +0 -54
  72. package/dist/fields/text/text-field.jsx +0 -79
  73. package/dist/fields/text-area/text-area-field.jsx +0 -79
  74. package/dist/forms/available-locales-reconcile.test.node.js +0 -43
  75. package/dist/forms/available-locales-widget.jsx +0 -63
  76. package/dist/forms/document-actions.jsx +0 -486
  77. package/dist/forms/form-context.jsx +0 -598
  78. package/dist/forms/form-modals.jsx +0 -128
  79. package/dist/forms/form-renderer.jsx +0 -532
  80. package/dist/forms/form-status-display.jsx +0 -69
  81. package/dist/forms/navigation-guard.jsx +0 -68
  82. package/dist/forms/nested-path.test.node.js +0 -115
  83. package/dist/forms/path-widget.jsx +0 -125
  84. package/dist/forms/pending-uploads.test.node.js +0 -19
  85. package/dist/forms/repeating-items.test.node.js +0 -29
  86. package/dist/forms/scheduled-publication-control.jsx +0 -389
  87. package/dist/forms/scheduled-publication-state.test.node.js +0 -121
  88. package/dist/forms/scheduled-publication-time.test.node.js +0 -70
  89. package/dist/forms/status-transitions.test.node.js +0 -76
  90. package/dist/forms/tree-placement-widget.jsx +0 -160
  91. package/dist/forms/upload-executor.test.node.js +0 -368
  92. package/dist/forms/use-form-layout.test.node.js +0 -69
  93. package/dist/modules/admin-account/components/change-password.jsx +0 -169
  94. package/dist/modules/admin-account/components/container.jsx +0 -164
  95. package/dist/modules/admin-account/components/preferences.jsx +0 -148
  96. package/dist/modules/admin-account/components/theme-switch.jsx +0 -52
  97. package/dist/modules/admin-account/components/update.jsx +0 -186
  98. package/dist/modules/admin-permissions/components/inspector.jsx +0 -200
  99. package/dist/modules/admin-preferences/schemas.test.node.js +0 -48
  100. package/dist/modules/admin-roles/components/create.jsx +0 -155
  101. package/dist/modules/admin-roles/components/permissions.jsx +0 -239
  102. package/dist/modules/admin-roles/components/update.jsx +0 -148
  103. package/dist/modules/admin-users/components/create.jsx +0 -206
  104. package/dist/modules/admin-users/components/roles.jsx +0 -126
  105. package/dist/modules/admin-users/components/set-password.jsx +0 -133
  106. package/dist/modules/admin-users/components/update.jsx +0 -215
  107. package/dist/modules/analytics/components/dashboard.jsx +0 -208
  108. package/dist/modules/analytics/components/dashboard.test.node.js +0 -162
  109. package/dist/modules/analytics/components/timeseries.jsx +0 -193
  110. package/dist/modules/auth/components/sign-in-form.jsx +0 -89
  111. package/dist/modules/auth/safe-redirect.test.node.js +0 -40
  112. package/dist/modules/auth/sign-in-form-props.test.node.js +0 -7
  113. package/dist/presentation/group.jsx +0 -26
  114. package/dist/presentation/row.jsx +0 -23
  115. package/dist/presentation/tabs.jsx +0 -39
  116. package/dist/services/admin-services-context.jsx +0 -17
  117. package/dist/widgets/diff-viewer/diff-modal.jsx +0 -110
  118. package/dist/widgets/source-locale-badge/source-locale-badge.jsx +0 -28
  119. package/dist/widgets/status-badge/status-badge.jsx +0 -46
@@ -3,42 +3,26 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
4
4
  import { getAdminConfig } from "@byline/core";
5
5
  import { useTranslation } from "@byline/i18n/react";
6
- import { Alert, Button, ComboButton, LoaderEllipsis } from "@byline/ui/react";
7
6
  import clsx from "clsx";
8
- import { sliceFieldAdmin } from "../fields/field-admin.js";
9
- import { FieldRenderer } from "../fields/field-renderer.js";
10
7
  import { useBylineFieldServices } from "../fields/field-services-context.js";
11
- import { AdminGroup } from "../presentation/group.js";
12
- import { AdminRow } from "../presentation/row.js";
13
- import { AdminTabs } from "../presentation/tabs.js";
14
- import { AvailableLocalesWidget } from "./available-locales-widget.js";
15
- import { DocumentActions } from "./document-actions.js";
16
8
  import { FormProvider, useFieldValue, useFormContext } from "./form-context.js";
9
+ import { FormLayout } from "./form-layout.js";
17
10
  import { NavigationGuardModal, SystemFieldsConfirmModal, UnsavedChangesModal } from "./form-modals.js";
11
+ import { FormConcurrencyNotices, FormHeadingRow, FormSidebarWidgets, FormStatusBar } from "./form-page-chrome.js";
18
12
  import form_renderer_module from "./form-renderer.module.js";
19
- import { FormStatusDisplay } from "./form-status-display.js";
20
13
  import { useNavigationGuardAdapter } from "./navigation-guard.js";
21
- import { PathWidget } from "./path-widget.js";
22
- import { ScheduledPublicationCell, ScheduledPublicationNotice, useScheduledPublication } from "./scheduled-publication-control.js";
14
+ import { useScheduledPublication } from "./scheduled-publication-control.js";
23
15
  import { computeStatusTransitions } from "./status-transitions.js";
24
- import { TreePlacementWidget } from "./tree-placement-widget.js";
25
- import { executeUploadsWithProgress } from "./upload-executor.js";
26
- import { useFormLayout } from "./use-form-layout.js";
16
+ import { useFormSubmission } from "./use-form-submission.js";
27
17
  const FormContent = ({ mode, mutationIssue, mutationsBlocked = false, observedRevision, onMutationError, onTreeMutationCommitted, scheduledPublicationsNeedReconfirmation = false, scheduledPublicationsHref, onReloadDocument, fields, onSubmit, onCancel, onStatusChange, onUnpublish, scheduledPublication, onSchedulePublication, onConfirmScheduledPublication, onCancelScheduledPublication, onDelete, onDuplicate, onCopyToLocale, onDeleteLocale, contentLocales, nextStatus, workflowStatuses, publishedVersion, initialData, adminConfig, useAsTitle, useAsPath, showPath = true, heading, advertiseLocales, tree, headingLabel, headerSlot, collectionPath, initialLocale, onLocaleChange, defaultLocale = 'en', useNavigationGuard: useNavigationGuardProp, restoreWarnings, _activeTabBySet, _onTabChange })=>{
28
- const { getFieldValues, runFieldHooks, validateForm, errors: initialErrors, hasChanges: hasChangesFn, resetHasChanges, getPatches, getDirtyBreakdown, getSystemPath, getSystemAvailableLocales, subscribeErrors, subscribeMeta, setFieldValue, setFieldError, getPendingUploads, clearPendingUploads, setFieldUploading } = useFormContext();
18
+ const { hasChanges: hasChangesFn, subscribeMeta } = useFormContext();
29
19
  const { t } = useTranslation('byline-admin');
30
- const [errors, setErrors] = useState(initialErrors);
31
20
  const [hasChanges, setHasChanges] = useState(hasChangesFn());
32
21
  const [statusBusy, setStatusBusy] = useState(false);
33
- const [isUploading, setIsUploading] = useState(false);
34
- const submittingRef = useRef(false);
35
- const [isSubmitting, setIsSubmitting] = useState(false);
36
- const isBusy = isUploading || isSubmitting;
37
22
  const formRef = useRef(null);
38
23
  const focusBeforeBusyRef = useRef(null);
39
24
  const restoreFocusAfterBusyRef = useRef(false);
40
25
  const [showUnsavedModal, setShowUnsavedModal] = useState(false);
41
- const [pendingSystemFieldsSubmit, setPendingSystemFieldsSubmit] = useState(null);
42
26
  const [contentLocale, setContentLocale] = useState(initialLocale ?? defaultLocale);
43
27
  const scheduling = useScheduledPublication({
44
28
  disabled: mutationsBlocked,
@@ -64,7 +48,6 @@ const FormContent = ({ mode, mutationIssue, mutationsBlocked = false, observedRe
64
48
  }, [
65
49
  initialLocale
66
50
  ]);
67
- const { fieldByName, tabSetByName, rowByName, groupByName, layout, fieldToTabPath } = useFormLayout(adminConfig, fields);
68
51
  const tabSets = adminConfig?.tabSets ?? [];
69
52
  const initialActiveTabBySet = useMemo(()=>{
70
53
  const result = {};
@@ -88,7 +71,6 @@ const FormContent = ({ mode, mutationIssue, mutationsBlocked = false, observedRe
88
71
  }, [
89
72
  _onTabChange
90
73
  ]);
91
- const [formData, setFormData] = useState(()=>getFieldValues());
92
74
  const liveTitle = useFieldValue(useAsTitle ?? '');
93
75
  const computedHeading = heading || liveTitle || (headingLabel ? 'create' === mode ? t('forms.heading.createLabel', {
94
76
  label: headingLabel
@@ -120,17 +102,30 @@ const FormContent = ({ mode, mutationIssue, mutationsBlocked = false, observedRe
120
102
  ]);
121
103
  const currentStatus = initialData?.status;
122
104
  const { primaryStatus, secondaryStatuses, isTerminal } = computeStatusTransitions(currentStatus, workflowStatuses, nextStatus);
123
- useEffect(()=>subscribeErrors((newErrors)=>setErrors(newErrors)), [
124
- subscribeErrors
125
- ]);
126
105
  useEffect(()=>subscribeMeta(()=>setHasChanges(hasChangesFn())), [
127
106
  subscribeMeta,
128
107
  hasChangesFn
129
108
  ]);
130
- useEffect(()=>subscribeMeta(()=>setFormData(getFieldValues())), [
131
- subscribeMeta,
132
- getFieldValues
133
- ]);
109
+ const captureFocusBeforeBusy = useCallback(()=>{
110
+ if (null != focusBeforeBusyRef.current) return;
111
+ const activeElement = document.activeElement;
112
+ if (!(activeElement instanceof HTMLElement) || !formRef.current?.contains(activeElement)) return;
113
+ focusBeforeBusyRef.current = activeElement;
114
+ restoreFocusAfterBusyRef.current = true;
115
+ }, []);
116
+ const submission = useFormSubmission({
117
+ mode,
118
+ fields,
119
+ documentId: 'edit' === mode && 'string' == typeof initialData?.id ? initialData.id : void 0,
120
+ advertiseLocales,
121
+ onSubmit,
122
+ isBlocked: ()=>mutationBlockedRef.current,
123
+ onBeforeBusy: captureFocusBeforeBusy
124
+ });
125
+ const isUploading = 'uploading' === submission.phase.kind;
126
+ const isSubmitting = 'submitting' === submission.phase.kind;
127
+ const isBusy = submission.isBusy;
128
+ const pendingSystemFieldsSubmit = 'confirmingSystemFields' === submission.phase.kind ? submission.phase.payload : null;
134
129
  useEffect(()=>{
135
130
  if (isBusy || !restoreFocusAfterBusyRef.current) return;
136
131
  restoreFocusAfterBusyRef.current = false;
@@ -150,156 +145,13 @@ const FormContent = ({ mode, mutationIssue, mutationsBlocked = false, observedRe
150
145
  isBusy,
151
146
  mutationIssue
152
147
  ]);
153
- const captureFocusBeforeBusy = useCallback(()=>{
154
- if (null != focusBeforeBusyRef.current) return;
155
- const activeElement = document.activeElement;
156
- if (!(activeElement instanceof HTMLElement) || !formRef.current?.contains(activeElement)) return;
157
- focusBeforeBusyRef.current = activeElement;
158
- restoreFocusAfterBusyRef.current = true;
159
- }, []);
160
148
  const handleCancel = ()=>{
161
149
  if (onCancel && 'function' == typeof onCancel) onCancel();
162
150
  };
163
- const submitPayload = useCallback(async (payload)=>{
164
- if (mutationBlockedRef.current || 'function' != typeof onSubmit) return;
165
- if (submittingRef.current) return;
166
- submittingRef.current = true;
167
- captureFocusBeforeBusy();
168
- setIsSubmitting(true);
169
- try {
170
- await onSubmit(payload);
171
- resetHasChanges();
172
- } catch {} finally{
173
- submittingRef.current = false;
174
- setIsSubmitting(false);
175
- }
176
- }, [
177
- captureFocusBeforeBusy,
178
- onSubmit,
179
- resetHasChanges
180
- ]);
181
151
  const handleSubmit = (e)=>{
182
- if (mutationBlockedRef.current) return void e.preventDefault();
183
152
  e.preventDefault();
184
- (async ()=>{
185
- const hookErrors = await runFieldHooks(fields);
186
- const formErrors = validateForm(fields);
187
- const allErrors = [
188
- ...hookErrors,
189
- ...formErrors
190
- ];
191
- if (allErrors.length > 0) return void console.error('Form validation failed:', allErrors);
192
- if (mutationBlockedRef.current) return;
193
- const pendingUploads = getPendingUploads();
194
- if (pendingUploads.size > 0) {
195
- captureFocusBeforeBusy();
196
- setIsUploading(true);
197
- try {
198
- const uploadResult = await executeUploadsWithProgress(pendingUploads, uploadField, ({ fieldPath, status })=>{
199
- setFieldUploading(fieldPath, 'uploading' === status);
200
- }, {
201
- documentId: 'edit' === mode && 'string' == typeof initialData?.id ? initialData.id : void 0,
202
- fields,
203
- getFormValues: getFieldValues
204
- });
205
- if (!uploadResult.allSucceeded) {
206
- for (const [fieldPath, errorMessage] of uploadResult.errors.entries())setFieldError(fieldPath, t('forms.uploadFailedFieldError', {
207
- message: errorMessage
208
- }));
209
- console.error('One or more uploads failed:', uploadResult.errors);
210
- setIsUploading(false);
211
- return;
212
- }
213
- for (const [fieldPath, storedFile] of uploadResult.successful.entries())setFieldValue(fieldPath, storedFile);
214
- clearPendingUploads();
215
- } catch (err) {
216
- console.error('Upload execution error:', err);
217
- setIsUploading(false);
218
- return;
219
- }
220
- setIsUploading(false);
221
- }
222
- const data = getFieldValues();
223
- const patches = getPatches();
224
- const { contentDirty, pathDirty, availableLocalesDirty, reason } = getDirtyBreakdown();
225
- const systemPath = getSystemPath();
226
- const systemAvailableLocales = advertiseLocales ? getSystemAvailableLocales() : void 0;
227
- const payload = {
228
- data,
229
- patches,
230
- contentDirty,
231
- pathDirty,
232
- systemPath,
233
- availableLocalesDirty,
234
- systemAvailableLocales
235
- };
236
- if ('edit' === mode && ('direct-write' === reason || 'both' === reason)) return void setPendingSystemFieldsSubmit(payload);
237
- await submitPayload(payload);
238
- })();
239
- };
240
- const tabErrorCountsBySet = useMemo(()=>{
241
- const result = {};
242
- for (const err of errors){
243
- const path = fieldToTabPath.get(err.field);
244
- if (path) {
245
- result[path.tabSetName] ??= {};
246
- result[path.tabSetName][path.tabName] = (result[path.tabSetName]?.[path.tabName] ?? 0) + 1;
247
- }
248
- }
249
- return result;
250
- }, [
251
- errors,
252
- fieldToTabPath
253
- ]);
254
- const renderField = (fieldName)=>{
255
- const field = fieldByName.get(fieldName);
256
- if (!field) return null;
257
- return /*#__PURE__*/ jsx(FieldRenderer, {
258
- field: field,
259
- defaultValue: initialData?.fields?.[field.name],
260
- contentLocale: contentLocale,
261
- components: adminConfig?.fields?.[field.name]?.components,
262
- editor: adminConfig?.fields?.[field.name]?.editor,
263
- fieldAdmin: sliceFieldAdmin(adminConfig?.fields, field.name)
264
- }, field.name);
265
- };
266
- const renderItem = (name)=>{
267
- const tabSet = tabSetByName.get(name);
268
- if (tabSet) return renderTabSet(tabSet);
269
- const group = groupByName.get(name);
270
- if (group) return renderGroup(group);
271
- const row = rowByName.get(name);
272
- if (row) return renderRow(row);
273
- return renderField(name);
274
- };
275
- const renderRow = (row)=>/*#__PURE__*/ jsx(AdminRow, {
276
- children: row.fields.map((name)=>renderField(name))
277
- }, `row:${row.name}`);
278
- const renderGroup = (group)=>/*#__PURE__*/ jsx(AdminGroup, {
279
- label: group.label,
280
- children: group.fields.map((name)=>renderItem(name))
281
- }, `group:${group.name}`);
282
- const renderTabSet = (set)=>{
283
- const visibleTabs = set.tabs.filter((tab)=>!tab.condition || tab.condition(formData));
284
- const requested = activeTabBySet[set.name] ?? '';
285
- const resolvedActive = visibleTabs.length > 0 && !visibleTabs.some((t)=>t.name === requested) ? visibleTabs[0]?.name ?? requested : requested;
286
- const activeTab = visibleTabs.find((t)=>t.name === resolvedActive);
287
- return /*#__PURE__*/ jsxs("div", {
288
- className: clsx('byline-form-tabset', form_renderer_module.tabset),
289
- children: [
290
- visibleTabs.length > 0 && /*#__PURE__*/ jsx(AdminTabs, {
291
- tabs: visibleTabs,
292
- activeTab: resolvedActive,
293
- onChange: (tabName)=>handleTabChange(set.name, tabName),
294
- errorCounts: tabErrorCountsBySet[set.name],
295
- className: clsx('byline-form-tabset-tabs', form_renderer_module["tabset-tabs"])
296
- }),
297
- activeTab && /*#__PURE__*/ jsx("div", {
298
- className: clsx('byline-form-tabset-fields', form_renderer_module["tabset-fields"]),
299
- children: activeTab.fields.map((name)=>renderItem(name))
300
- })
301
- ]
302
- }, `tabset:${set.name}`);
153
+ if (mutationBlockedRef.current) return;
154
+ submission.submit();
303
155
  };
304
156
  const busyAnnouncement = isUploading ? t('forms.actions.uploading') : isSubmitting ? t('forms.actions.saving') : '';
305
157
  return /*#__PURE__*/ jsxs(Fragment, {
@@ -320,248 +172,84 @@ const FormContent = ({ mode, mutationIssue, mutationsBlocked = false, observedRe
320
172
  className: clsx('byline-form', form_renderer_module.form),
321
173
  inert: isBusy ? true : void 0,
322
174
  children: [
323
- /*#__PURE__*/ jsxs("div", {
324
- className: clsx('byline-form-heading-row', form_renderer_module["heading-row"]),
325
- children: [
326
- /*#__PURE__*/ jsx("h1", {
327
- className: clsx('byline-form-heading', form_renderer_module.heading),
328
- children: computedHeading
329
- }),
330
- headerSlot
331
- ]
332
- }),
333
- /*#__PURE__*/ jsxs("div", {
334
- className: clsx('byline-form-status-bar', form_renderer_module["status-bar"]),
335
- children: [
336
- /*#__PURE__*/ jsx("div", {
337
- className: clsx('byline-form-status-details', form_renderer_module["status-details"]),
338
- children: /*#__PURE__*/ jsx(FormStatusDisplay, {
339
- disabled: mutationsBlocked || discarding,
340
- initialData: initialData,
341
- workflowStatuses: workflowStatuses,
342
- publishedVersion: publishedVersion,
343
- onUnpublish: onUnpublish,
344
- afterStatusCells: /*#__PURE__*/ jsx(ScheduledPublicationCell, {
345
- state: scheduling.state,
346
- timeZone: scheduling.timeZone
347
- })
348
- })
349
- }),
350
- /*#__PURE__*/ jsxs("div", {
351
- className: clsx('byline-form-actions', form_renderer_module.actions),
352
- children: [
353
- /*#__PURE__*/ jsx(Button, {
354
- className: clsx('byline-form-actions-button', form_renderer_module["actions-button"]),
355
- size: "sm",
356
- intent: "noeffect",
357
- type: "button",
358
- onClick: handleCancel,
359
- children: false === hasChanges ? t('common.actions.close') : t('common.actions.cancel')
360
- }),
361
- /*#__PURE__*/ jsx(Button, {
362
- className: clsx('byline-form-actions-button', form_renderer_module["actions-button"]),
363
- size: "sm",
364
- type: "submit",
365
- disabled: mutationsBlocked || discarding || false === hasChanges || isUploading || isSubmitting,
366
- "aria-label": isSubmitting ? t('common.actions.save') : void 0,
367
- children: isUploading ? t('forms.actions.uploading') : /*#__PURE__*/ jsxs("span", {
368
- className: clsx('byline-form-save-content', form_renderer_module["save-content"]),
369
- children: [
370
- /*#__PURE__*/ jsx("span", {
371
- className: clsx('byline-form-save-label', form_renderer_module["save-label"], isSubmitting && form_renderer_module["save-label-hidden"]),
372
- children: t('common.actions.save')
373
- }),
374
- isSubmitting ? /*#__PURE__*/ jsx("span", {
375
- className: clsx('byline-form-save-loader', form_renderer_module["save-loader"]),
376
- children: /*#__PURE__*/ jsx(LoaderEllipsis, {
377
- size: 28,
378
- "aria-hidden": "true"
379
- })
380
- }) : null
381
- ]
382
- })
383
- }),
384
- primaryStatus && onStatusChange && /*#__PURE__*/ jsx("div", {
385
- className: clsx('byline-form-actions-status-wrap', form_renderer_module["actions-status-wrap"]),
386
- children: /*#__PURE__*/ jsx(ComboButton, {
387
- buttonClassName: clsx('byline-form-actions-combo-button', form_renderer_module["actions-combo-button"]),
388
- triggerClassName: clsx('byline-form-actions-combo-trigger', form_renderer_module["actions-combo-trigger"]),
389
- options: secondaryStatuses.map((s)=>({
390
- label: isTerminal ? t('forms.actions.revertTo', {
391
- label: s.label ?? s.name
392
- }) : s.verb ?? s.label ?? s.name,
393
- value: s.name
394
- })),
395
- sideOffset: 5,
396
- size: "sm",
397
- type: "button",
398
- intent: isTerminal ? 'info' : 'success',
399
- disabled: mutationsBlocked || discarding || statusBusy,
400
- onOptionSelect: async (value)=>{
401
- if (mutationBlockedRef.current) return;
402
- if (hasChanges) return void setShowUnsavedModal(true);
403
- setStatusBusy(true);
404
- try {
405
- await onStatusChange(value);
406
- } catch (error) {
407
- onMutationError?.(error);
408
- } finally{
409
- setStatusBusy(false);
410
- }
411
- },
412
- onButtonClick: isTerminal ? void 0 : async ()=>{
413
- if (mutationBlockedRef.current) return;
414
- if (hasChanges) return void setShowUnsavedModal(true);
415
- setStatusBusy(true);
416
- try {
417
- await onStatusChange(primaryStatus.name);
418
- } catch (error) {
419
- onMutationError?.(error);
420
- } finally{
421
- setStatusBusy(false);
422
- }
423
- },
424
- children: statusBusy ? '...' : isTerminal ? primaryStatus.label ?? primaryStatus.name : primaryStatus.verb ?? primaryStatus.label ?? primaryStatus.name
425
- })
426
- }),
427
- /*#__PURE__*/ jsx(DocumentActions, {
428
- disabled: mutationsBlocked || discarding,
429
- publishedVersion: publishedVersion,
430
- onUnpublish: onUnpublish,
431
- onDelete: onDelete,
432
- onDuplicate: onDuplicate,
433
- sourceTitle: null != useAsTitle && null != initialData ? initialData[useAsTitle] : null,
434
- onCopyToLocale: onCopyToLocale,
435
- sourceLocale: contentLocale,
436
- contentLocales: contentLocales,
437
- hasUnsavedChanges: hasChanges,
438
- onUnsavedChanges: ()=>setShowUnsavedModal(true),
439
- onDeleteLocale: onDeleteLocale,
440
- defaultLocale: defaultLocale,
441
- availableLocales: initialData?._availableVersionLocales,
442
- scheduledPublicationState: scheduling.state,
443
- onSchedulePublication: scheduling.openSchedule,
444
- onConfirmScheduledPublication: scheduling.confirm,
445
- onCancelScheduledPublication: scheduling.cancel
446
- })
447
- ]
448
- })
449
- ]
450
- }),
451
- (mutationIssue || scheduledPublicationsNeedReconfirmation) && /*#__PURE__*/ jsxs("div", {
452
- ref: warningRef,
453
- tabIndex: -1,
454
- role: "alert",
455
- "aria-live": "assertive",
456
- className: clsx('byline-document-concurrency', form_renderer_module.concurrency),
457
- children: [
458
- mutationIssue && /*#__PURE__*/ jsxs(Alert, {
459
- intent: "warning",
460
- icon: true,
461
- close: false,
462
- title: t(`documentConcurrency.${mutationIssue}Title`),
463
- children: [
464
- /*#__PURE__*/ jsx("p", {
465
- children: t(`documentConcurrency.${mutationIssue}`)
466
- }),
467
- 'committed' !== mutationIssue && /*#__PURE__*/ jsx(Button, {
468
- type: "button",
469
- disabled: discarding,
470
- onClick: ()=>{
471
- setReloadFailed(false);
472
- setDiscarding(true);
473
- },
474
- children: t('documentConcurrency.reloadAction')
475
- }),
476
- reloadFailed && /*#__PURE__*/ jsx("p", {
477
- children: t('documentConcurrency.reloadFailed')
478
- })
479
- ]
480
- }),
481
- scheduledPublicationsNeedReconfirmation && /*#__PURE__*/ jsxs(Alert, {
482
- intent: "warning",
483
- icon: true,
484
- close: false,
485
- title: t('documentConcurrency.schedulesTitle'),
486
- children: [
487
- /*#__PURE__*/ jsx("p", {
488
- children: t('documentConcurrency.schedules')
489
- }),
490
- scheduledPublicationsHref && /*#__PURE__*/ jsx("a", {
491
- href: scheduledPublicationsHref,
492
- children: t('documentConcurrency.reviewSchedules')
493
- })
494
- ]
495
- })
496
- ]
175
+ /*#__PURE__*/ jsx(FormHeadingRow, {
176
+ heading: computedHeading,
177
+ headerSlot: headerSlot
497
178
  }),
498
- /*#__PURE__*/ jsx(ScheduledPublicationNotice, {
499
- state: scheduling.state,
500
- timeZone: scheduling.timeZone,
501
- busy: mutationsBlocked || discarding || scheduling.busy,
502
- onConfirm: scheduling.confirm,
503
- onReschedule: scheduling.openSchedule,
504
- onCancel: scheduling.cancel
179
+ /*#__PURE__*/ jsx(FormStatusBar, {
180
+ disabled: mutationsBlocked || discarding,
181
+ initialData: initialData,
182
+ workflowStatuses: workflowStatuses,
183
+ publishedVersion: publishedVersion,
184
+ scheduling: scheduling,
185
+ hasChanges: hasChanges,
186
+ isUploading: isUploading,
187
+ isSubmitting: isSubmitting,
188
+ onCancel: handleCancel,
189
+ transitions: {
190
+ primaryStatus,
191
+ secondaryStatuses,
192
+ isTerminal
193
+ },
194
+ statusBusy: statusBusy,
195
+ onStatusBusyChange: setStatusBusy,
196
+ onStatusChange: onStatusChange,
197
+ onMutationError: onMutationError,
198
+ isBlocked: ()=>mutationBlockedRef.current,
199
+ onUnsavedChanges: ()=>setShowUnsavedModal(true),
200
+ onUnpublish: onUnpublish,
201
+ onDelete: onDelete,
202
+ onDuplicate: onDuplicate,
203
+ onCopyToLocale: onCopyToLocale,
204
+ onDeleteLocale: onDeleteLocale,
205
+ useAsTitle: useAsTitle,
206
+ contentLocale: contentLocale,
207
+ contentLocales: contentLocales,
208
+ defaultLocale: defaultLocale
505
209
  }),
506
- scheduling.modal,
507
- restoreWarnings && restoreWarnings.length > 0 && /*#__PURE__*/ jsxs(Alert, {
508
- className: "m-0 mt-4",
509
- intent: "warning",
510
- icon: true,
511
- close: false,
512
- title: t('forms.restoreWarnings.title'),
513
- children: [
514
- /*#__PURE__*/ jsx("p", {
515
- children: t('forms.restoreWarnings.body', {
516
- count: restoreWarnings.length
517
- })
518
- }),
519
- /*#__PURE__*/ jsx("ul", {
520
- children: restoreWarnings.map((w)=>/*#__PURE__*/ jsx("li", {
521
- children: w
522
- }, w))
523
- })
524
- ]
210
+ /*#__PURE__*/ jsx(FormConcurrencyNotices, {
211
+ disabled: mutationsBlocked || discarding,
212
+ mutationIssue: mutationIssue,
213
+ scheduledPublicationsNeedReconfirmation: scheduledPublicationsNeedReconfirmation,
214
+ scheduledPublicationsHref: scheduledPublicationsHref,
215
+ warningRef: warningRef,
216
+ discarding: discarding,
217
+ reloadFailed: reloadFailed,
218
+ onDiscardRequested: ()=>{
219
+ setReloadFailed(false);
220
+ setDiscarding(true);
221
+ },
222
+ scheduling: scheduling,
223
+ restoreWarnings: restoreWarnings
525
224
  }),
526
- /*#__PURE__*/ jsxs("div", {
527
- className: clsx('byline-form-layout', form_renderer_module.layout),
528
- children: [
529
- /*#__PURE__*/ jsx("div", {
530
- className: clsx('byline-form-content', form_renderer_module.content),
531
- children: layout.main.map((name)=>renderItem(name))
532
- }),
533
- /*#__PURE__*/ jsxs("div", {
534
- className: clsx('byline-form-sidebar', form_renderer_module.sidebar),
535
- children: [
536
- showPath && (useAsPath || adminConfig?.lockPath === true || 'string' == typeof initialData?.path && initialData.path.length > 0) && /*#__PURE__*/ jsx(PathWidget, {
537
- disabled: mutationsBlocked || discarding,
538
- useAsPath: useAsPath,
539
- collectionPath: collectionPath ?? '',
540
- defaultLocale: defaultLocale,
541
- activeLocale: contentLocale,
542
- mode: mode,
543
- slugifier: pathSlugifier,
544
- sourceLocked: pathSourceLocked,
545
- lockPath: adminConfig?.lockPath
546
- }),
547
- tree && 'edit' === mode && 'string' == typeof initialData?.id && /*#__PURE__*/ jsx(TreePlacementWidget, {
548
- disabled: mutationsBlocked || discarding,
549
- onMutationError: onMutationError,
550
- onCommitted: onTreeMutationCommitted,
551
- expectedRevision: observedRevision ?? initialData.revision,
552
- collectionPath: collectionPath ?? '',
553
- documentId: initialData.id,
554
- useAsTitle: useAsTitle
555
- }),
556
- advertiseLocales && /*#__PURE__*/ jsx(AvailableLocalesWidget, {
557
- disabled: mutationsBlocked || discarding,
558
- contentLocales: contentLocales ?? [],
559
- availableVersionLocales: initialData?._availableVersionLocales ?? []
560
- }),
561
- (layout.sidebar ?? []).map((name)=>renderItem(name))
562
- ]
563
- })
564
- ]
225
+ /*#__PURE__*/ jsx(FormLayout, {
226
+ fields: fields,
227
+ adminConfig: adminConfig,
228
+ initialData: initialData,
229
+ activeLocale: contentLocale,
230
+ collectionPath: collectionPath ?? void 0,
231
+ activeTabBySet: activeTabBySet,
232
+ onTabChange: handleTabChange,
233
+ sidebarSlot: /*#__PURE__*/ jsx(FormSidebarWidgets, {
234
+ disabled: mutationsBlocked || discarding,
235
+ mode: mode,
236
+ initialData: initialData,
237
+ collectionPath: collectionPath ?? '',
238
+ defaultLocale: defaultLocale,
239
+ contentLocale: contentLocale,
240
+ contentLocales: contentLocales,
241
+ showPath: showPath,
242
+ useAsPath: useAsPath,
243
+ lockPath: adminConfig?.lockPath,
244
+ pathSlugifier: pathSlugifier,
245
+ pathSourceLocked: pathSourceLocked,
246
+ tree: tree,
247
+ useAsTitle: useAsTitle,
248
+ advertiseLocales: advertiseLocales,
249
+ observedRevision: observedRevision,
250
+ onMutationError: onMutationError,
251
+ onTreeMutationCommitted: onTreeMutationCommitted
252
+ })
565
253
  }),
566
254
  showUnsavedModal && /*#__PURE__*/ jsx(UnsavedChangesModal, {
567
255
  onClose: ()=>setShowUnsavedModal(false)
@@ -570,11 +258,9 @@ const FormContent = ({ mode, mutationIssue, mutationsBlocked = false, observedRe
570
258
  contentDirty: pendingSystemFieldsSubmit.contentDirty,
571
259
  pathDirty: pendingSystemFieldsSubmit.pathDirty,
572
260
  availableLocalesDirty: pendingSystemFieldsSubmit.availableLocalesDirty,
573
- onCancel: ()=>setPendingSystemFieldsSubmit(null),
261
+ onCancel: submission.cancelSystemFields,
574
262
  onConfirm: ()=>{
575
- const payload = pendingSystemFieldsSubmit;
576
- setPendingSystemFieldsSubmit(null);
577
- submitPayload(payload);
263
+ submission.confirmSystemFields();
578
264
  }
579
265
  }),
580
266
  guard.isBlocked && /*#__PURE__*/ jsx(NavigationGuardModal, {
@@ -42,6 +42,8 @@ const form_renderer_module = {
42
42
  actionsComboTrigger: "actions-combo-trigger-bmXUzc",
43
43
  concurrency: "concurrency-UT6bX5",
44
44
  layout: "layout-WTbLYr",
45
+ "layout-stacked": "layout-stacked-yNPTOa",
46
+ layoutStacked: "layout-stacked-yNPTOa",
45
47
  content: "content-_P5cdJ",
46
48
  sidebar: "sidebar-WsxX88",
47
49
  tabset: "tabset-xfaMkP",
@@ -218,6 +218,19 @@
218
218
  }
219
219
  }
220
220
 
221
+ :is(.layout-stacked-yNPTOa, .byline-form-layout-stacked) {
222
+ gap: var(--spacing-12);
223
+ padding-top: var(--spacing-32);
224
+ flex-direction: column;
225
+ display: flex;
226
+ }
227
+
228
+ @media (min-width: 60rem) {
229
+ :is(.layout-stacked-yNPTOa, .byline-form-layout-stacked) {
230
+ gap: var(--spacing-32);
231
+ }
232
+ }
233
+
221
234
  :is(.content-_P5cdJ, .byline-form-content) {
222
235
  gap: var(--spacing-20);
223
236
  flex-direction: column;