@byline/admin 6.2.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.
@@ -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, tabPanelId, tabTriggerId } 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,161 +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
- const idBase = `tabset:${set.name}`;
288
- return /*#__PURE__*/ jsxs("div", {
289
- className: clsx('byline-form-tabset', form_renderer_module.tabset),
290
- children: [
291
- visibleTabs.length > 0 && /*#__PURE__*/ jsx(AdminTabs, {
292
- idBase: idBase,
293
- tabs: visibleTabs,
294
- activeTab: resolvedActive,
295
- onChange: (tabName)=>handleTabChange(set.name, tabName),
296
- errorCounts: tabErrorCountsBySet[set.name],
297
- className: clsx('byline-form-tabset-tabs', form_renderer_module["tabset-tabs"])
298
- }),
299
- activeTab && /*#__PURE__*/ jsx("div", {
300
- role: "tabpanel",
301
- id: tabPanelId(idBase, activeTab.name),
302
- "aria-labelledby": tabTriggerId(idBase, activeTab.name),
303
- className: clsx('byline-form-tabset-fields', form_renderer_module["tabset-fields"]),
304
- children: activeTab.fields.map((name)=>renderItem(name))
305
- })
306
- ]
307
- }, idBase);
153
+ if (mutationBlockedRef.current) return;
154
+ submission.submit();
308
155
  };
309
156
  const busyAnnouncement = isUploading ? t('forms.actions.uploading') : isSubmitting ? t('forms.actions.saving') : '';
310
157
  return /*#__PURE__*/ jsxs(Fragment, {
@@ -325,248 +172,84 @@ const FormContent = ({ mode, mutationIssue, mutationsBlocked = false, observedRe
325
172
  className: clsx('byline-form', form_renderer_module.form),
326
173
  inert: isBusy ? true : void 0,
327
174
  children: [
328
- /*#__PURE__*/ jsxs("div", {
329
- className: clsx('byline-form-heading-row', form_renderer_module["heading-row"]),
330
- children: [
331
- /*#__PURE__*/ jsx("h1", {
332
- className: clsx('byline-form-heading', form_renderer_module.heading),
333
- children: computedHeading
334
- }),
335
- headerSlot
336
- ]
337
- }),
338
- /*#__PURE__*/ jsxs("div", {
339
- className: clsx('byline-form-status-bar', form_renderer_module["status-bar"]),
340
- children: [
341
- /*#__PURE__*/ jsx("div", {
342
- className: clsx('byline-form-status-details', form_renderer_module["status-details"]),
343
- children: /*#__PURE__*/ jsx(FormStatusDisplay, {
344
- disabled: mutationsBlocked || discarding,
345
- initialData: initialData,
346
- workflowStatuses: workflowStatuses,
347
- publishedVersion: publishedVersion,
348
- onUnpublish: onUnpublish,
349
- afterStatusCells: /*#__PURE__*/ jsx(ScheduledPublicationCell, {
350
- state: scheduling.state,
351
- timeZone: scheduling.timeZone
352
- })
353
- })
354
- }),
355
- /*#__PURE__*/ jsxs("div", {
356
- className: clsx('byline-form-actions', form_renderer_module.actions),
357
- children: [
358
- /*#__PURE__*/ jsx(Button, {
359
- className: clsx('byline-form-actions-button', form_renderer_module["actions-button"]),
360
- size: "sm",
361
- intent: "noeffect",
362
- type: "button",
363
- onClick: handleCancel,
364
- children: false === hasChanges ? t('common.actions.close') : t('common.actions.cancel')
365
- }),
366
- /*#__PURE__*/ jsx(Button, {
367
- className: clsx('byline-form-actions-button', form_renderer_module["actions-button"]),
368
- size: "sm",
369
- type: "submit",
370
- disabled: mutationsBlocked || discarding || false === hasChanges || isUploading || isSubmitting,
371
- "aria-label": isSubmitting ? t('common.actions.save') : void 0,
372
- children: isUploading ? t('forms.actions.uploading') : /*#__PURE__*/ jsxs("span", {
373
- className: clsx('byline-form-save-content', form_renderer_module["save-content"]),
374
- children: [
375
- /*#__PURE__*/ jsx("span", {
376
- className: clsx('byline-form-save-label', form_renderer_module["save-label"], isSubmitting && form_renderer_module["save-label-hidden"]),
377
- children: t('common.actions.save')
378
- }),
379
- isSubmitting ? /*#__PURE__*/ jsx("span", {
380
- className: clsx('byline-form-save-loader', form_renderer_module["save-loader"]),
381
- children: /*#__PURE__*/ jsx(LoaderEllipsis, {
382
- size: 28,
383
- "aria-hidden": "true"
384
- })
385
- }) : null
386
- ]
387
- })
388
- }),
389
- primaryStatus && onStatusChange && /*#__PURE__*/ jsx("div", {
390
- className: clsx('byline-form-actions-status-wrap', form_renderer_module["actions-status-wrap"]),
391
- children: /*#__PURE__*/ jsx(ComboButton, {
392
- buttonClassName: clsx('byline-form-actions-combo-button', form_renderer_module["actions-combo-button"]),
393
- triggerClassName: clsx('byline-form-actions-combo-trigger', form_renderer_module["actions-combo-trigger"]),
394
- options: secondaryStatuses.map((s)=>({
395
- label: isTerminal ? t('forms.actions.revertTo', {
396
- label: s.label ?? s.name
397
- }) : s.verb ?? s.label ?? s.name,
398
- value: s.name
399
- })),
400
- sideOffset: 5,
401
- size: "sm",
402
- type: "button",
403
- intent: isTerminal ? 'info' : 'success',
404
- disabled: mutationsBlocked || discarding || statusBusy,
405
- onOptionSelect: async (value)=>{
406
- if (mutationBlockedRef.current) return;
407
- if (hasChanges) return void setShowUnsavedModal(true);
408
- setStatusBusy(true);
409
- try {
410
- await onStatusChange(value);
411
- } catch (error) {
412
- onMutationError?.(error);
413
- } finally{
414
- setStatusBusy(false);
415
- }
416
- },
417
- onButtonClick: isTerminal ? void 0 : async ()=>{
418
- if (mutationBlockedRef.current) return;
419
- if (hasChanges) return void setShowUnsavedModal(true);
420
- setStatusBusy(true);
421
- try {
422
- await onStatusChange(primaryStatus.name);
423
- } catch (error) {
424
- onMutationError?.(error);
425
- } finally{
426
- setStatusBusy(false);
427
- }
428
- },
429
- children: statusBusy ? '...' : isTerminal ? primaryStatus.label ?? primaryStatus.name : primaryStatus.verb ?? primaryStatus.label ?? primaryStatus.name
430
- })
431
- }),
432
- /*#__PURE__*/ jsx(DocumentActions, {
433
- disabled: mutationsBlocked || discarding,
434
- publishedVersion: publishedVersion,
435
- onUnpublish: onUnpublish,
436
- onDelete: onDelete,
437
- onDuplicate: onDuplicate,
438
- sourceTitle: null != useAsTitle && null != initialData ? initialData[useAsTitle] : null,
439
- onCopyToLocale: onCopyToLocale,
440
- sourceLocale: contentLocale,
441
- contentLocales: contentLocales,
442
- hasUnsavedChanges: hasChanges,
443
- onUnsavedChanges: ()=>setShowUnsavedModal(true),
444
- onDeleteLocale: onDeleteLocale,
445
- defaultLocale: defaultLocale,
446
- availableLocales: initialData?._availableVersionLocales,
447
- scheduledPublicationState: scheduling.state,
448
- onSchedulePublication: scheduling.openSchedule,
449
- onConfirmScheduledPublication: scheduling.confirm,
450
- onCancelScheduledPublication: scheduling.cancel
451
- })
452
- ]
453
- })
454
- ]
455
- }),
456
- (mutationIssue || scheduledPublicationsNeedReconfirmation) && /*#__PURE__*/ jsxs("div", {
457
- ref: warningRef,
458
- tabIndex: -1,
459
- role: "alert",
460
- "aria-live": "assertive",
461
- className: clsx('byline-document-concurrency', form_renderer_module.concurrency),
462
- children: [
463
- mutationIssue && /*#__PURE__*/ jsxs(Alert, {
464
- intent: "warning",
465
- icon: true,
466
- close: false,
467
- title: t(`documentConcurrency.${mutationIssue}Title`),
468
- children: [
469
- /*#__PURE__*/ jsx("p", {
470
- children: t(`documentConcurrency.${mutationIssue}`)
471
- }),
472
- 'committed' !== mutationIssue && /*#__PURE__*/ jsx(Button, {
473
- type: "button",
474
- disabled: discarding,
475
- onClick: ()=>{
476
- setReloadFailed(false);
477
- setDiscarding(true);
478
- },
479
- children: t('documentConcurrency.reloadAction')
480
- }),
481
- reloadFailed && /*#__PURE__*/ jsx("p", {
482
- children: t('documentConcurrency.reloadFailed')
483
- })
484
- ]
485
- }),
486
- scheduledPublicationsNeedReconfirmation && /*#__PURE__*/ jsxs(Alert, {
487
- intent: "warning",
488
- icon: true,
489
- close: false,
490
- title: t('documentConcurrency.schedulesTitle'),
491
- children: [
492
- /*#__PURE__*/ jsx("p", {
493
- children: t('documentConcurrency.schedules')
494
- }),
495
- scheduledPublicationsHref && /*#__PURE__*/ jsx("a", {
496
- href: scheduledPublicationsHref,
497
- children: t('documentConcurrency.reviewSchedules')
498
- })
499
- ]
500
- })
501
- ]
175
+ /*#__PURE__*/ jsx(FormHeadingRow, {
176
+ heading: computedHeading,
177
+ headerSlot: headerSlot
502
178
  }),
503
- /*#__PURE__*/ jsx(ScheduledPublicationNotice, {
504
- state: scheduling.state,
505
- timeZone: scheduling.timeZone,
506
- busy: mutationsBlocked || discarding || scheduling.busy,
507
- onConfirm: scheduling.confirm,
508
- onReschedule: scheduling.openSchedule,
509
- 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
510
209
  }),
511
- scheduling.modal,
512
- restoreWarnings && restoreWarnings.length > 0 && /*#__PURE__*/ jsxs(Alert, {
513
- className: "m-0 mt-4",
514
- intent: "warning",
515
- icon: true,
516
- close: false,
517
- title: t('forms.restoreWarnings.title'),
518
- children: [
519
- /*#__PURE__*/ jsx("p", {
520
- children: t('forms.restoreWarnings.body', {
521
- count: restoreWarnings.length
522
- })
523
- }),
524
- /*#__PURE__*/ jsx("ul", {
525
- children: restoreWarnings.map((w)=>/*#__PURE__*/ jsx("li", {
526
- children: w
527
- }, w))
528
- })
529
- ]
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
530
224
  }),
531
- /*#__PURE__*/ jsxs("div", {
532
- className: clsx('byline-form-layout', form_renderer_module.layout),
533
- children: [
534
- /*#__PURE__*/ jsx("div", {
535
- className: clsx('byline-form-content', form_renderer_module.content),
536
- children: layout.main.map((name)=>renderItem(name))
537
- }),
538
- /*#__PURE__*/ jsxs("div", {
539
- className: clsx('byline-form-sidebar', form_renderer_module.sidebar),
540
- children: [
541
- showPath && (useAsPath || adminConfig?.lockPath === true || 'string' == typeof initialData?.path && initialData.path.length > 0) && /*#__PURE__*/ jsx(PathWidget, {
542
- disabled: mutationsBlocked || discarding,
543
- useAsPath: useAsPath,
544
- collectionPath: collectionPath ?? '',
545
- defaultLocale: defaultLocale,
546
- activeLocale: contentLocale,
547
- mode: mode,
548
- slugifier: pathSlugifier,
549
- sourceLocked: pathSourceLocked,
550
- lockPath: adminConfig?.lockPath
551
- }),
552
- tree && 'edit' === mode && 'string' == typeof initialData?.id && /*#__PURE__*/ jsx(TreePlacementWidget, {
553
- disabled: mutationsBlocked || discarding,
554
- onMutationError: onMutationError,
555
- onCommitted: onTreeMutationCommitted,
556
- expectedRevision: observedRevision ?? initialData.revision,
557
- collectionPath: collectionPath ?? '',
558
- documentId: initialData.id,
559
- useAsTitle: useAsTitle
560
- }),
561
- advertiseLocales && /*#__PURE__*/ jsx(AvailableLocalesWidget, {
562
- disabled: mutationsBlocked || discarding,
563
- contentLocales: contentLocales ?? [],
564
- availableVersionLocales: initialData?._availableVersionLocales ?? []
565
- }),
566
- (layout.sidebar ?? []).map((name)=>renderItem(name))
567
- ]
568
- })
569
- ]
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
+ })
570
253
  }),
571
254
  showUnsavedModal && /*#__PURE__*/ jsx(UnsavedChangesModal, {
572
255
  onClose: ()=>setShowUnsavedModal(false)
@@ -575,11 +258,9 @@ const FormContent = ({ mode, mutationIssue, mutationsBlocked = false, observedRe
575
258
  contentDirty: pendingSystemFieldsSubmit.contentDirty,
576
259
  pathDirty: pendingSystemFieldsSubmit.pathDirty,
577
260
  availableLocalesDirty: pendingSystemFieldsSubmit.availableLocalesDirty,
578
- onCancel: ()=>setPendingSystemFieldsSubmit(null),
261
+ onCancel: submission.cancelSystemFields,
579
262
  onConfirm: ()=>{
580
- const payload = pendingSystemFieldsSubmit;
581
- setPendingSystemFieldsSubmit(null);
582
- submitPayload(payload);
263
+ submission.confirmSystemFields();
583
264
  }
584
265
  }),
585
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;