@byline/admin 3.12.1 → 3.12.2

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,145 +1,25 @@
1
1
  "use client";
2
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
4
4
  import { useTranslation } from "@byline/i18n/react";
5
- import { Alert, Button, CloseIcon, ComboButton, IconButton, Modal } from "@byline/ui/react";
5
+ import { Alert, Button, ComboButton } from "@byline/ui/react";
6
6
  import classnames from "classnames";
7
7
  import { FieldRenderer } from "../fields/field-renderer.js";
8
8
  import { useBylineFieldServices } from "../fields/field-services-context.js";
9
- import { LocalDateTime } from "../fields/local-date-time.js";
10
9
  import { AdminGroup } from "../presentation/group.js";
11
10
  import { AdminRow } from "../presentation/row.js";
12
11
  import { AdminTabs } from "../presentation/tabs.js";
13
12
  import { AvailableLocalesWidget } from "./available-locales-widget.js";
14
13
  import { DocumentActions } from "./document-actions.js";
15
14
  import { FormProvider, useFieldValue, useFormContext } from "./form-context.js";
15
+ import { NavigationGuardModal, SystemFieldsConfirmModal, UnsavedChangesModal } from "./form-modals.js";
16
16
  import form_renderer_module from "./form-renderer.module.js";
17
+ import { FormStatusDisplay } from "./form-status-display.js";
17
18
  import { useNavigationGuardAdapter } from "./navigation-guard.js";
18
19
  import { PathWidget } from "./path-widget.js";
20
+ import { computeStatusTransitions } from "./status-transitions.js";
19
21
  import { executeUploadsWithProgress } from "./upload-executor.js";
20
- const FormStatusDisplay = ({ initialData, workflowStatuses, publishedVersion, onUnpublish })=>{
21
- const { t } = useTranslation('byline-admin');
22
- const statusCode = initialData?.status;
23
- const statusLabel = workflowStatuses?.find((s)=>s.name === statusCode)?.label ?? statusCode;
24
- const showStatusCell = (workflowStatuses?.length ?? 0) > 1;
25
- return /*#__PURE__*/ jsxs("div", {
26
- className: classnames('byline-form-status', form_renderer_module.status),
27
- children: [
28
- /*#__PURE__*/ jsxs("div", {
29
- className: classnames('byline-form-status-meta', form_renderer_module["status-meta"]),
30
- children: [
31
- showStatusCell && /*#__PURE__*/ jsxs("div", {
32
- className: classnames('byline-form-status-cell', form_renderer_module["status-cell"]),
33
- children: [
34
- /*#__PURE__*/ jsx("span", {
35
- className: classnames('byline-form-status-muted', form_renderer_module["status-muted"]),
36
- children: t('forms.status.label')
37
- }),
38
- /*#__PURE__*/ jsx("span", {
39
- className: classnames('byline-form-status-trunc', form_renderer_module["status-trunc"]),
40
- children: statusLabel
41
- })
42
- ]
43
- }),
44
- initialData?.updatedAt != null && /*#__PURE__*/ jsxs("div", {
45
- className: classnames('byline-form-status-cell', form_renderer_module["status-cell"]),
46
- children: [
47
- /*#__PURE__*/ jsx("span", {
48
- className: classnames('byline-form-status-muted', form_renderer_module["status-muted"]),
49
- children: t('forms.status.lastModified')
50
- }),
51
- /*#__PURE__*/ jsx("span", {
52
- className: classnames('byline-form-status-trunc', form_renderer_module["status-trunc"]),
53
- children: /*#__PURE__*/ jsx(LocalDateTime, {
54
- value: initialData.updatedAt
55
- })
56
- })
57
- ]
58
- }),
59
- initialData?.createdAt != null && /*#__PURE__*/ jsxs("div", {
60
- className: classnames('byline-form-status-cell', form_renderer_module["status-cell"]),
61
- children: [
62
- /*#__PURE__*/ jsx("span", {
63
- className: classnames('byline-form-status-muted', form_renderer_module["status-muted"]),
64
- children: t('forms.status.created')
65
- }),
66
- /*#__PURE__*/ jsx("span", {
67
- className: classnames('byline-form-status-trunc', form_renderer_module["status-trunc"]),
68
- children: /*#__PURE__*/ jsx(LocalDateTime, {
69
- value: initialData.createdAt
70
- })
71
- })
72
- ]
73
- })
74
- ]
75
- }),
76
- null != publishedVersion && /*#__PURE__*/ jsxs("div", {
77
- className: classnames('byline-form-status-published', form_renderer_module["status-published"]),
78
- children: [
79
- /*#__PURE__*/ jsxs("span", {
80
- className: classnames('byline-form-status-muted', form_renderer_module["status-muted"]),
81
- children: [
82
- t('forms.status.publishedLive'),
83
- ' ',
84
- publishedVersion.updatedAt ? /*#__PURE__*/ jsx("span", {
85
- children: t('forms.status.publishedOn', {
86
- date: new Date(publishedVersion.updatedAt)
87
- })
88
- }) : ''
89
- ]
90
- }),
91
- onUnpublish && /*#__PURE__*/ jsxs(Fragment, {
92
- children: [
93
- ' ',
94
- /*#__PURE__*/ jsx("button", {
95
- type: "button",
96
- onClick: onUnpublish,
97
- className: classnames('byline-form-status-unpublish', form_renderer_module["status-unpublish"]),
98
- children: t('common.actions.unpublish')
99
- })
100
- ]
101
- })
102
- ]
103
- })
104
- ]
105
- });
106
- };
107
- function computeStatusTransitions(currentStatus, workflowStatuses, nextStatus) {
108
- if (!workflowStatuses || 0 === workflowStatuses.length || !currentStatus) return {
109
- primaryStatus: nextStatus,
110
- secondaryStatuses: [],
111
- isTerminal: false
112
- };
113
- if (workflowStatuses.length <= 1) return {
114
- primaryStatus: void 0,
115
- secondaryStatuses: [],
116
- isTerminal: false
117
- };
118
- const currentIndex = workflowStatuses.findIndex((s)=>s.name === currentStatus);
119
- if (-1 === currentIndex) return {
120
- primaryStatus: nextStatus,
121
- secondaryStatuses: [],
122
- isTerminal: false
123
- };
124
- const isAtEnd = currentIndex === workflowStatuses.length - 1;
125
- const isAtStart = 0 === currentIndex;
126
- const availableTargets = [];
127
- if (!isAtStart && workflowStatuses[0]) availableTargets.push(workflowStatuses[0]);
128
- const prev = workflowStatuses[currentIndex - 1];
129
- if (currentIndex > 1 && prev) availableTargets.push(prev);
130
- const next = workflowStatuses[currentIndex + 1];
131
- if (!isAtEnd && next) availableTargets.push(next);
132
- if (isAtEnd) return {
133
- primaryStatus: workflowStatuses[currentIndex],
134
- secondaryStatuses: availableTargets,
135
- isTerminal: true
136
- };
137
- return {
138
- primaryStatus: nextStatus,
139
- secondaryStatuses: availableTargets.filter((s)=>s.name !== nextStatus?.name),
140
- isTerminal: false
141
- };
142
- }
22
+ import { useFormLayout } from "./use-form-layout.js";
143
23
  const FormContent = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpublish, onDelete, onDuplicate, onCopyToLocale, onDeleteLocale, contentLocales, nextStatus, workflowStatuses, publishedVersion, initialData, adminConfig, useAsTitle, useAsPath, advertiseLocales, headingLabel, headerSlot, collectionPath, initialLocale, onLocaleChange, defaultLocale = 'en', useNavigationGuard: useNavigationGuardProp, restoreWarnings, _activeTabBySet, _onTabChange })=>{
144
24
  const { getFieldValues, runFieldHooks, validateForm, errors: initialErrors, hasChanges: hasChangesFn, resetHasChanges, getPatches, getDirtyBreakdown, getSystemPath, getSystemAvailableLocales, subscribeErrors, subscribeMeta, setFieldValue, setFieldError, getPendingUploads, clearPendingUploads, setFieldUploading } = useFormContext();
145
25
  const { t } = useTranslation('byline-admin');
@@ -156,69 +36,7 @@ const FormContent = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpub
156
36
  }, [
157
37
  initialLocale
158
38
  ]);
159
- const fieldByName = useMemo(()=>{
160
- const map = new Map();
161
- for (const field of fields)if ('name' in field) map.set(field.name, field);
162
- return map;
163
- }, [
164
- fields
165
- ]);
166
- const tabSetByName = useMemo(()=>{
167
- const map = new Map();
168
- for (const set of adminConfig?.tabSets ?? [])map.set(set.name, set);
169
- return map;
170
- }, [
171
- adminConfig
172
- ]);
173
- const rowByName = useMemo(()=>{
174
- const map = new Map();
175
- for (const row of adminConfig?.rows ?? [])map.set(row.name, row);
176
- return map;
177
- }, [
178
- adminConfig
179
- ]);
180
- const groupByName = useMemo(()=>{
181
- const map = new Map();
182
- for (const group of adminConfig?.groups ?? [])map.set(group.name, group);
183
- return map;
184
- }, [
185
- adminConfig
186
- ]);
187
- const layout = useMemo(()=>{
188
- if (adminConfig?.layout) return adminConfig.layout;
189
- return {
190
- main: fields.filter((f)=>'name' in f).map((f)=>f.name)
191
- };
192
- }, [
193
- adminConfig,
194
- fields
195
- ]);
196
- const fieldToTabPath = useMemo(()=>{
197
- const map = new Map();
198
- const visit = (names, tabSetName, tabName, seen)=>{
199
- for (const name of names)if (fieldByName.has(name)) map.set(name, {
200
- tabSetName,
201
- tabName
202
- });
203
- else if (seen.has(name)) ;
204
- else if (rowByName.has(name)) {
205
- const row = rowByName.get(name);
206
- const next = new Set(seen).add(name);
207
- visit(row.fields, tabSetName, tabName, next);
208
- } else if (groupByName.has(name)) {
209
- const group = groupByName.get(name);
210
- const next = new Set(seen).add(name);
211
- visit(group.fields, tabSetName, tabName, next);
212
- }
213
- };
214
- for (const set of adminConfig?.tabSets ?? [])for (const tab of set.tabs)visit(tab.fields, set.name, tab.name, new Set());
215
- return map;
216
- }, [
217
- adminConfig,
218
- fieldByName,
219
- rowByName,
220
- groupByName
221
- ]);
39
+ const { fieldByName, tabSetByName, rowByName, groupByName, layout, fieldToTabPath } = useFormLayout(adminConfig, fields);
222
40
  const tabSets = adminConfig?.tabSets ?? [];
223
41
  const initialActiveTabBySet = useMemo(()=>{
224
42
  const result = {};
@@ -537,182 +355,23 @@ const FormContent = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpub
537
355
  })
538
356
  ]
539
357
  }),
540
- showUnsavedModal && /*#__PURE__*/ jsx(Modal, {
541
- isOpen: true,
542
- closeOnOverlayClick: true,
543
- onDismiss: ()=>setShowUnsavedModal(false),
544
- children: /*#__PURE__*/ jsxs(Modal.Container, {
545
- style: {
546
- maxWidth: '460px'
547
- },
548
- children: [
549
- /*#__PURE__*/ jsx(Modal.Header, {
550
- className: classnames('byline-form-guard-modal-head', form_renderer_module["guard-modal-head"]),
551
- children: /*#__PURE__*/ jsx("h3", {
552
- className: classnames('byline-form-guard-modal-title', form_renderer_module["guard-modal-title"]),
553
- children: t('forms.unsavedChanges.title')
554
- })
555
- }),
556
- /*#__PURE__*/ jsx(Modal.Content, {
557
- children: /*#__PURE__*/ jsx("p", {
558
- className: classnames('byline-form-guard-modal-text', form_renderer_module["guard-modal-text"]),
559
- children: t('forms.unsavedChanges.message')
560
- })
561
- }),
562
- /*#__PURE__*/ jsx(Modal.Actions, {
563
- children: /*#__PURE__*/ jsx(Button, {
564
- size: "sm",
565
- style: {
566
- minWidth: '60px'
567
- },
568
- intent: "primary",
569
- type: "button",
570
- onClick: ()=>setShowUnsavedModal(false),
571
- children: t('forms.unsavedChanges.okButton')
572
- })
573
- })
574
- ]
575
- })
358
+ showUnsavedModal && /*#__PURE__*/ jsx(UnsavedChangesModal, {
359
+ onClose: ()=>setShowUnsavedModal(false)
576
360
  }),
577
- null != pendingSystemFieldsSubmit && /*#__PURE__*/ jsx(Modal, {
578
- isOpen: true,
579
- closeOnOverlayClick: true,
580
- onDismiss: ()=>setPendingSystemFieldsSubmit(null),
581
- children: /*#__PURE__*/ jsxs(Modal.Container, {
582
- style: {
583
- maxWidth: '520px'
584
- },
585
- children: [
586
- /*#__PURE__*/ jsxs(Modal.Header, {
587
- className: classnames('byline-form-guard-modal-head', form_renderer_module["guard-modal-head"]),
588
- children: [
589
- /*#__PURE__*/ jsx("h3", {
590
- className: classnames('byline-form-guard-modal-title', form_renderer_module["guard-modal-title"]),
591
- children: pendingSystemFieldsSubmit.contentDirty ? t('forms.systemFieldsConfirm.bothTitle') : t('forms.systemFieldsConfirm.title')
592
- }),
593
- /*#__PURE__*/ jsx(IconButton, {
594
- "aria-label": t('common.actions.close'),
595
- size: "xs",
596
- onClick: ()=>setPendingSystemFieldsSubmit(null),
597
- children: /*#__PURE__*/ jsx(CloseIcon, {
598
- width: "16px",
599
- height: "16px",
600
- svgClassName: "white-icon"
601
- })
602
- })
603
- ]
604
- }),
605
- /*#__PURE__*/ jsxs(Modal.Content, {
606
- className: "prose",
607
- children: [
608
- pendingSystemFieldsSubmit.contentDirty && /*#__PURE__*/ jsx("p", {
609
- className: classnames('byline-form-system-fields-content-note', 'm-0 mt-2'),
610
- children: t('forms.systemFieldsConfirm.contentNote')
611
- }),
612
- /*#__PURE__*/ jsx("p", {
613
- className: "m-0 mt-2",
614
- style: pendingSystemFieldsSubmit.contentDirty ? {
615
- marginTop: 'var(--spacing-8)',
616
- paddingTop: 'var(--spacing-12)',
617
- borderTop: '1px solid var(--border-color)'
618
- } : void 0,
619
- children: t('forms.systemFieldsConfirm.intro')
620
- }),
621
- /*#__PURE__*/ jsxs("ul", {
622
- className: classnames('byline-form-system-fields-list', form_renderer_module["guard-modal-text"], 'm-0'),
623
- children: [
624
- pendingSystemFieldsSubmit.pathDirty && /*#__PURE__*/ jsx("li", {
625
- children: t('forms.systemFieldsConfirm.bulletPath')
626
- }),
627
- pendingSystemFieldsSubmit.availableLocalesDirty && /*#__PURE__*/ jsx("li", {
628
- children: t('forms.systemFieldsConfirm.bulletLocales')
629
- })
630
- ]
631
- }),
632
- /*#__PURE__*/ jsx("p", {
633
- className: classnames('byline-form-system-fields-effect', form_renderer_module["guard-modal-text"]),
634
- style: {
635
- marginTop: 'var(--spacing-4)',
636
- marginBottom: 0,
637
- color: 'var(--text-subtle)'
638
- },
639
- children: t('forms.systemFieldsConfirm.effectLine')
640
- })
641
- ]
642
- }),
643
- /*#__PURE__*/ jsxs(Modal.Actions, {
644
- children: [
645
- /*#__PURE__*/ jsx(Button, {
646
- size: "sm",
647
- style: {
648
- minWidth: '80px'
649
- },
650
- intent: "noeffect",
651
- type: "button",
652
- onClick: ()=>setPendingSystemFieldsSubmit(null),
653
- children: t('common.actions.cancel')
654
- }),
655
- /*#__PURE__*/ jsx(Button, {
656
- size: "sm",
657
- style: {
658
- minWidth: '80px'
659
- },
660
- intent: "primary",
661
- type: "button",
662
- onClick: ()=>{
663
- const payload = pendingSystemFieldsSubmit;
664
- setPendingSystemFieldsSubmit(null);
665
- submitPayload(payload);
666
- },
667
- children: t('forms.systemFieldsConfirm.confirmButton')
668
- })
669
- ]
670
- })
671
- ]
672
- })
361
+ null != pendingSystemFieldsSubmit && /*#__PURE__*/ jsx(SystemFieldsConfirmModal, {
362
+ contentDirty: pendingSystemFieldsSubmit.contentDirty,
363
+ pathDirty: pendingSystemFieldsSubmit.pathDirty,
364
+ availableLocalesDirty: pendingSystemFieldsSubmit.availableLocalesDirty,
365
+ onCancel: ()=>setPendingSystemFieldsSubmit(null),
366
+ onConfirm: ()=>{
367
+ const payload = pendingSystemFieldsSubmit;
368
+ setPendingSystemFieldsSubmit(null);
369
+ submitPayload(payload);
370
+ }
673
371
  }),
674
- guard.isBlocked && /*#__PURE__*/ jsx(Modal, {
675
- isOpen: true,
676
- closeOnOverlayClick: false,
677
- onDismiss: guard.stay,
678
- children: /*#__PURE__*/ jsxs(Modal.Container, {
679
- style: {
680
- maxWidth: '460px'
681
- },
682
- children: [
683
- /*#__PURE__*/ jsx(Modal.Header, {
684
- className: classnames('byline-form-guard-modal-head', form_renderer_module["guard-modal-head"]),
685
- children: /*#__PURE__*/ jsx("h3", {
686
- className: classnames('byline-form-guard-modal-title', form_renderer_module["guard-modal-title"]),
687
- children: t('forms.navigationGuard.title')
688
- })
689
- }),
690
- /*#__PURE__*/ jsx(Modal.Content, {
691
- children: /*#__PURE__*/ jsx("p", {
692
- className: classnames('byline-form-guard-modal-text', form_renderer_module["guard-modal-text"]),
693
- children: t('forms.navigationGuard.message')
694
- })
695
- }),
696
- /*#__PURE__*/ jsxs(Modal.Actions, {
697
- children: [
698
- /*#__PURE__*/ jsx(Button, {
699
- size: "sm",
700
- intent: "noeffect",
701
- type: "button",
702
- onClick: guard.stay,
703
- children: t('forms.navigationGuard.stayButton')
704
- }),
705
- /*#__PURE__*/ jsx(Button, {
706
- size: "sm",
707
- intent: "danger",
708
- type: "button",
709
- onClick: guard.proceed,
710
- children: t('forms.navigationGuard.leaveButton')
711
- })
712
- ]
713
- })
714
- ]
715
- })
372
+ guard.isBlocked && /*#__PURE__*/ jsx(NavigationGuardModal, {
373
+ onStay: guard.stay,
374
+ onProceed: guard.proceed
716
375
  })
717
376
  ]
718
377
  });
@@ -0,0 +1,21 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+ import type { WorkflowStatus } from '@byline/core';
9
+ import type { PublishedVersionInfo } from './form-renderer';
10
+ /**
11
+ * The status / metadata strip at the top of a document form — current
12
+ * workflow status (suppressed for single-status workflows), last-modified and
13
+ * created timestamps, and a "published live" indicator with an inline
14
+ * Unpublish action when a previously-published version is still live.
15
+ */
16
+ export declare const FormStatusDisplay: ({ initialData, workflowStatuses, publishedVersion, onUnpublish, }: {
17
+ initialData?: Record<string, any>;
18
+ workflowStatuses?: WorkflowStatus[];
19
+ publishedVersion?: PublishedVersionInfo | null;
20
+ onUnpublish?: () => Promise<void>;
21
+ }) => import("react").JSX.Element;
@@ -0,0 +1,94 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { useTranslation } from "@byline/i18n/react";
4
+ import classnames from "classnames";
5
+ import { LocalDateTime } from "../fields/local-date-time.js";
6
+ import form_renderer_module from "./form-renderer.module.js";
7
+ const FormStatusDisplay = ({ initialData, workflowStatuses, publishedVersion, onUnpublish })=>{
8
+ const { t } = useTranslation('byline-admin');
9
+ const statusCode = initialData?.status;
10
+ const statusLabel = workflowStatuses?.find((s)=>s.name === statusCode)?.label ?? statusCode;
11
+ const showStatusCell = (workflowStatuses?.length ?? 0) > 1;
12
+ return /*#__PURE__*/ jsxs("div", {
13
+ className: classnames('byline-form-status', form_renderer_module.status),
14
+ children: [
15
+ /*#__PURE__*/ jsxs("div", {
16
+ className: classnames('byline-form-status-meta', form_renderer_module["status-meta"]),
17
+ children: [
18
+ showStatusCell && /*#__PURE__*/ jsxs("div", {
19
+ className: classnames('byline-form-status-cell', form_renderer_module["status-cell"]),
20
+ children: [
21
+ /*#__PURE__*/ jsx("span", {
22
+ className: classnames('byline-form-status-muted', form_renderer_module["status-muted"]),
23
+ children: t('forms.status.label')
24
+ }),
25
+ /*#__PURE__*/ jsx("span", {
26
+ className: classnames('byline-form-status-trunc', form_renderer_module["status-trunc"]),
27
+ children: statusLabel
28
+ })
29
+ ]
30
+ }),
31
+ initialData?.updatedAt != null && /*#__PURE__*/ jsxs("div", {
32
+ className: classnames('byline-form-status-cell', form_renderer_module["status-cell"]),
33
+ children: [
34
+ /*#__PURE__*/ jsx("span", {
35
+ className: classnames('byline-form-status-muted', form_renderer_module["status-muted"]),
36
+ children: t('forms.status.lastModified')
37
+ }),
38
+ /*#__PURE__*/ jsx("span", {
39
+ className: classnames('byline-form-status-trunc', form_renderer_module["status-trunc"]),
40
+ children: /*#__PURE__*/ jsx(LocalDateTime, {
41
+ value: initialData.updatedAt
42
+ })
43
+ })
44
+ ]
45
+ }),
46
+ initialData?.createdAt != null && /*#__PURE__*/ jsxs("div", {
47
+ className: classnames('byline-form-status-cell', form_renderer_module["status-cell"]),
48
+ children: [
49
+ /*#__PURE__*/ jsx("span", {
50
+ className: classnames('byline-form-status-muted', form_renderer_module["status-muted"]),
51
+ children: t('forms.status.created')
52
+ }),
53
+ /*#__PURE__*/ jsx("span", {
54
+ className: classnames('byline-form-status-trunc', form_renderer_module["status-trunc"]),
55
+ children: /*#__PURE__*/ jsx(LocalDateTime, {
56
+ value: initialData.createdAt
57
+ })
58
+ })
59
+ ]
60
+ })
61
+ ]
62
+ }),
63
+ null != publishedVersion && /*#__PURE__*/ jsxs("div", {
64
+ className: classnames('byline-form-status-published', form_renderer_module["status-published"]),
65
+ children: [
66
+ /*#__PURE__*/ jsxs("span", {
67
+ className: classnames('byline-form-status-muted', form_renderer_module["status-muted"]),
68
+ children: [
69
+ t('forms.status.publishedLive'),
70
+ ' ',
71
+ publishedVersion.updatedAt ? /*#__PURE__*/ jsx("span", {
72
+ children: t('forms.status.publishedOn', {
73
+ date: new Date(publishedVersion.updatedAt)
74
+ })
75
+ }) : ''
76
+ ]
77
+ }),
78
+ onUnpublish && /*#__PURE__*/ jsxs(Fragment, {
79
+ children: [
80
+ ' ',
81
+ /*#__PURE__*/ jsx("button", {
82
+ type: "button",
83
+ onClick: onUnpublish,
84
+ className: classnames('byline-form-status-unpublish', form_renderer_module["status-unpublish"]),
85
+ children: t('common.actions.unpublish')
86
+ })
87
+ ]
88
+ })
89
+ ]
90
+ })
91
+ ]
92
+ });
93
+ };
94
+ export { FormStatusDisplay };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+ import type { WorkflowStatus } from '@byline/core';
9
+ export interface StatusTransitions {
10
+ primaryStatus: WorkflowStatus | undefined;
11
+ secondaryStatuses: WorkflowStatus[];
12
+ isTerminal: boolean;
13
+ }
14
+ /**
15
+ * Compute the primary and secondary status transitions for the ComboButton.
16
+ * - Primary: the main action (forward step), or the current status itself
17
+ * when the document has reached the final workflow step (terminal state).
18
+ * - Secondary: other available transitions to show as dropdown options.
19
+ * - isTerminal: true when the document is at the final workflow status —
20
+ * the primary button renders as a non-actionable indicator and all
21
+ * back-steps move into the dropdown.
22
+ */
23
+ export declare function computeStatusTransitions(currentStatus: string | undefined, workflowStatuses: WorkflowStatus[] | undefined, nextStatus: WorkflowStatus | undefined): StatusTransitions;
@@ -0,0 +1,37 @@
1
+ function computeStatusTransitions(currentStatus, workflowStatuses, nextStatus) {
2
+ if (!workflowStatuses || 0 === workflowStatuses.length || !currentStatus) return {
3
+ primaryStatus: nextStatus,
4
+ secondaryStatuses: [],
5
+ isTerminal: false
6
+ };
7
+ if (workflowStatuses.length <= 1) return {
8
+ primaryStatus: void 0,
9
+ secondaryStatuses: [],
10
+ isTerminal: false
11
+ };
12
+ const currentIndex = workflowStatuses.findIndex((s)=>s.name === currentStatus);
13
+ if (-1 === currentIndex) return {
14
+ primaryStatus: nextStatus,
15
+ secondaryStatuses: [],
16
+ isTerminal: false
17
+ };
18
+ const isAtEnd = currentIndex === workflowStatuses.length - 1;
19
+ const isAtStart = 0 === currentIndex;
20
+ const availableTargets = [];
21
+ if (!isAtStart && workflowStatuses[0]) availableTargets.push(workflowStatuses[0]);
22
+ const prev = workflowStatuses[currentIndex - 1];
23
+ if (currentIndex > 1 && prev) availableTargets.push(prev);
24
+ const next = workflowStatuses[currentIndex + 1];
25
+ if (!isAtEnd && next) availableTargets.push(next);
26
+ if (isAtEnd) return {
27
+ primaryStatus: workflowStatuses[currentIndex],
28
+ secondaryStatuses: availableTargets,
29
+ isTerminal: true
30
+ };
31
+ return {
32
+ primaryStatus: nextStatus,
33
+ secondaryStatuses: availableTargets.filter((s)=>s.name !== nextStatus?.name),
34
+ isTerminal: false
35
+ };
36
+ }
37
+ export { computeStatusTransitions };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+ export {};
@@ -0,0 +1,35 @@
1
+ import type { CollectionAdminConfig, Field, GroupDefinition, LayoutDefinition, RowDefinition, TabSetDefinition } from '@byline/core';
2
+ /** Which tab set + tab a schema field is placed under. */
3
+ export interface TabPath {
4
+ tabSetName: string;
5
+ tabName: string;
6
+ }
7
+ /**
8
+ * Derived lookup tables that drive the form's layout walk and per-tab error
9
+ * badges. All are pure derivations of `adminConfig` + `fields`; the startup
10
+ * validator guarantees every reachable name resolves and every schema field is
11
+ * placed at most once, so the consuming render-time lookups stay unguarded.
12
+ */
13
+ export interface FormLayout {
14
+ /** Schema field name → field definition. */
15
+ fieldByName: Map<string, Field>;
16
+ tabSetByName: Map<string, TabSetDefinition>;
17
+ rowByName: Map<string, RowDefinition>;
18
+ groupByName: Map<string, GroupDefinition>;
19
+ /** Region placement; synthesised to `{ main: <all fields> }` when omitted. */
20
+ layout: LayoutDefinition;
21
+ /** Reverse index: schema field name → which tab set + tab it lives in. */
22
+ fieldToTabPath: Map<string, TabPath>;
23
+ }
24
+ /**
25
+ * Build the reverse index from schema field name to its enclosing tab set +
26
+ * tab. Fields not under any tab set (e.g. raw-field placement directly in
27
+ * `layout.main`) are absent from the map. Rows and groups are recursed into;
28
+ * the `seen` set guards against a config that references a row/group cycle.
29
+ */
30
+ export declare function buildFieldToTabPath(adminConfig: CollectionAdminConfig | undefined, fieldByName: Map<string, Field>, rowByName: Map<string, RowDefinition>, groupByName: Map<string, GroupDefinition>): Map<string, TabPath>;
31
+ /**
32
+ * Memoise the layout primitives + lookup tables the form renderer walks.
33
+ * Rebuilt only when `adminConfig` / `fields` change.
34
+ */
35
+ export declare function useFormLayout(adminConfig: CollectionAdminConfig | undefined, fields: Field[]): FormLayout;