@byline/admin 3.12.0 → 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.
- package/dist/forms/form-context.js +30 -60
- package/dist/forms/form-modals.d.ts +30 -0
- package/dist/forms/form-modals.js +189 -0
- package/dist/forms/form-renderer.js +22 -363
- package/dist/forms/form-status-display.d.ts +21 -0
- package/dist/forms/form-status-display.js +94 -0
- package/dist/forms/status-transitions.d.ts +23 -0
- package/dist/forms/status-transitions.js +37 -0
- package/dist/forms/status-transitions.test.node.d.ts +8 -0
- package/dist/forms/use-form-layout.d.ts +35 -0
- package/dist/forms/use-form-layout.js +77 -0
- package/dist/forms/use-form-layout.test.node.d.ts +8 -0
- package/dist/forms/use-tracked-slot.d.ts +45 -0
- package/dist/forms/use-tracked-slot.js +46 -0
- package/package.json +5 -5
- package/src/forms/form-context.tsx +36 -92
- package/src/forms/form-modals.tsx +186 -0
- package/src/forms/form-renderer.tsx +24 -387
- package/src/forms/form-status-display.tsx +108 -0
- package/src/forms/status-transitions.test.node.ts +87 -0
- package/src/forms/status-transitions.ts +86 -0
- package/src/forms/use-form-layout.test.node.ts +82 -0
- package/src/forms/use-form-layout.ts +134 -0
- package/src/forms/use-tracked-slot.ts +101 -0
|
@@ -3,6 +3,7 @@ import { jsx } from "react/jsx-runtime";
|
|
|
3
3
|
import { createContext, useCallback, useContext, useEffect, useRef, useState } from "react";
|
|
4
4
|
import { normalizeHooks } from "@byline/core";
|
|
5
5
|
import { get, set as external_nested_path_js_set } from "./nested-path.js";
|
|
6
|
+
import { useTrackedSlot } from "./use-tracked-slot.js";
|
|
6
7
|
const sameLocaleSet = (a, b)=>{
|
|
7
8
|
if (a.length !== b.length) return false;
|
|
8
9
|
const sa = [
|
|
@@ -33,16 +34,6 @@ const FormProvider = ({ children, initialData = {} })=>{
|
|
|
33
34
|
const fieldListeners = useRef(new Map());
|
|
34
35
|
const errorListeners = useRef(new Set());
|
|
35
36
|
const metaListeners = useRef(new Set());
|
|
36
|
-
const systemPathRef = useRef('string' == typeof initialData?.path && initialData.path.length > 0 ? initialData.path : null);
|
|
37
|
-
const initialSystemPath = useRef(systemPathRef.current);
|
|
38
|
-
const systemPathListeners = useRef(new Set());
|
|
39
|
-
const systemAvailableLocalesRef = useRef(Array.isArray(initialData?.availableLocales) ? [
|
|
40
|
-
...initialData.availableLocales
|
|
41
|
-
] : []);
|
|
42
|
-
const initialSystemAvailableLocales = useRef([
|
|
43
|
-
...systemAvailableLocalesRef.current
|
|
44
|
-
]);
|
|
45
|
-
const systemAvailableLocalesListeners = useRef(new Set());
|
|
46
37
|
const subscribeField = useCallback((name, listener)=>{
|
|
47
38
|
if (!fieldListeners.current.has(name)) fieldListeners.current.set(name, new Set());
|
|
48
39
|
fieldListeners.current.get(name)?.add(listener);
|
|
@@ -82,6 +73,24 @@ const FormProvider = ({ children, initialData = {} })=>{
|
|
|
82
73
|
listener();
|
|
83
74
|
});
|
|
84
75
|
}, []);
|
|
76
|
+
const pathSlot = useTrackedSlot({
|
|
77
|
+
initial: 'string' == typeof initialData?.path && initialData.path.length > 0 ? initialData.path : null,
|
|
78
|
+
dirtyKey: SYSTEM_PATH_DIRTY_KEY,
|
|
79
|
+
dirtyFields,
|
|
80
|
+
notifyMeta: notifyMetaListeners
|
|
81
|
+
});
|
|
82
|
+
const availableLocalesSlot = useTrackedSlot({
|
|
83
|
+
initial: Array.isArray(initialData?.availableLocales) ? [
|
|
84
|
+
...initialData.availableLocales
|
|
85
|
+
] : [],
|
|
86
|
+
dirtyKey: SYSTEM_AVAILABLE_LOCALES_DIRTY_KEY,
|
|
87
|
+
dirtyFields,
|
|
88
|
+
notifyMeta: notifyMetaListeners,
|
|
89
|
+
isEqual: sameLocaleSet,
|
|
90
|
+
clone: (value)=>[
|
|
91
|
+
...value
|
|
92
|
+
]
|
|
93
|
+
});
|
|
85
94
|
const updateFieldStoreInternal = useCallback((name, value)=>{
|
|
86
95
|
const newFieldValues = {
|
|
87
96
|
...fieldValues.current
|
|
@@ -152,13 +161,13 @@ const FormProvider = ({ children, initialData = {} })=>{
|
|
|
152
161
|
const resetHasChanges = useCallback(()=>{
|
|
153
162
|
dirtyFields.current.clear();
|
|
154
163
|
patchesRef.current = [];
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
...systemAvailableLocalesRef.current
|
|
158
|
-
];
|
|
164
|
+
pathSlot.commitInitial();
|
|
165
|
+
availableLocalesSlot.commitInitial();
|
|
159
166
|
notifyMetaListeners();
|
|
160
167
|
}, [
|
|
161
|
-
notifyMetaListeners
|
|
168
|
+
notifyMetaListeners,
|
|
169
|
+
pathSlot.commitInitial,
|
|
170
|
+
availableLocalesSlot.commitInitial
|
|
162
171
|
]);
|
|
163
172
|
const isDirty = useCallback((fieldName)=>dirtyFields.current.has(fieldName), []);
|
|
164
173
|
const getDirtyBreakdown = useCallback(()=>{
|
|
@@ -179,45 +188,6 @@ const FormProvider = ({ children, initialData = {} })=>{
|
|
|
179
188
|
availableLocalesDirty
|
|
180
189
|
};
|
|
181
190
|
}, []);
|
|
182
|
-
const getSystemPath = useCallback(()=>systemPathRef.current, []);
|
|
183
|
-
const setSystemPath = useCallback((value)=>{
|
|
184
|
-
systemPathRef.current = value;
|
|
185
|
-
if (value !== initialSystemPath.current) dirtyFields.current.add(SYSTEM_PATH_DIRTY_KEY);
|
|
186
|
-
else dirtyFields.current.delete(SYSTEM_PATH_DIRTY_KEY);
|
|
187
|
-
systemPathListeners.current.forEach((listener)=>{
|
|
188
|
-
listener(value);
|
|
189
|
-
});
|
|
190
|
-
notifyMetaListeners();
|
|
191
|
-
}, [
|
|
192
|
-
notifyMetaListeners
|
|
193
|
-
]);
|
|
194
|
-
const subscribeSystemPath = useCallback((listener)=>{
|
|
195
|
-
systemPathListeners.current.add(listener);
|
|
196
|
-
return ()=>{
|
|
197
|
-
systemPathListeners.current.delete(listener);
|
|
198
|
-
};
|
|
199
|
-
}, []);
|
|
200
|
-
const getSystemAvailableLocales = useCallback(()=>systemAvailableLocalesRef.current, []);
|
|
201
|
-
const setSystemAvailableLocales = useCallback((value)=>{
|
|
202
|
-
const next = [
|
|
203
|
-
...value
|
|
204
|
-
];
|
|
205
|
-
systemAvailableLocalesRef.current = next;
|
|
206
|
-
if (sameLocaleSet(next, initialSystemAvailableLocales.current)) dirtyFields.current.delete(SYSTEM_AVAILABLE_LOCALES_DIRTY_KEY);
|
|
207
|
-
else dirtyFields.current.add(SYSTEM_AVAILABLE_LOCALES_DIRTY_KEY);
|
|
208
|
-
systemAvailableLocalesListeners.current.forEach((listener)=>{
|
|
209
|
-
listener(next);
|
|
210
|
-
});
|
|
211
|
-
notifyMetaListeners();
|
|
212
|
-
}, [
|
|
213
|
-
notifyMetaListeners
|
|
214
|
-
]);
|
|
215
|
-
const subscribeSystemAvailableLocales = useCallback((listener)=>{
|
|
216
|
-
systemAvailableLocalesListeners.current.add(listener);
|
|
217
|
-
return ()=>{
|
|
218
|
-
systemAvailableLocalesListeners.current.delete(listener);
|
|
219
|
-
};
|
|
220
|
-
}, []);
|
|
221
191
|
const addPendingUpload = useCallback((fieldPath, upload)=>{
|
|
222
192
|
const existing = pendingUploadsRef.current.get(fieldPath);
|
|
223
193
|
if (existing) URL.revokeObjectURL(existing.previewUrl);
|
|
@@ -438,12 +408,12 @@ const FormProvider = ({ children, initialData = {} })=>{
|
|
|
438
408
|
setFieldUploading,
|
|
439
409
|
getIsFieldUploading,
|
|
440
410
|
subscribeFieldUploading,
|
|
441
|
-
getSystemPath,
|
|
442
|
-
setSystemPath,
|
|
443
|
-
subscribeSystemPath,
|
|
444
|
-
getSystemAvailableLocales,
|
|
445
|
-
setSystemAvailableLocales,
|
|
446
|
-
subscribeSystemAvailableLocales
|
|
411
|
+
getSystemPath: pathSlot.get,
|
|
412
|
+
setSystemPath: pathSlot.set,
|
|
413
|
+
subscribeSystemPath: pathSlot.subscribe,
|
|
414
|
+
getSystemAvailableLocales: availableLocalesSlot.get,
|
|
415
|
+
setSystemAvailableLocales: availableLocalesSlot.set,
|
|
416
|
+
subscribeSystemAvailableLocales: availableLocalesSlot.subscribe
|
|
447
417
|
},
|
|
448
418
|
children: children
|
|
449
419
|
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shown when the editor triggers a guarded action (status change, duplicate,
|
|
3
|
+
* copy-to-locale) while the form is dirty. Those actions operate on the saved
|
|
4
|
+
* version, so the editor must save first.
|
|
5
|
+
*/
|
|
6
|
+
export declare const UnsavedChangesModal: ({ onClose }: {
|
|
7
|
+
onClose: () => void;
|
|
8
|
+
}) => import("react").JSX.Element;
|
|
9
|
+
/**
|
|
10
|
+
* Confirms an immediate, non-versioned write of the document-grain system
|
|
11
|
+
* fields (path / advertised locales) — which does NOT reset workflow status.
|
|
12
|
+
* When content is also dirty, the copy reassures that content edits still
|
|
13
|
+
* follow the normal revision + publish workflow. See docs/I18N.md.
|
|
14
|
+
*/
|
|
15
|
+
export declare const SystemFieldsConfirmModal: ({ contentDirty, pathDirty, availableLocalesDirty, onCancel, onConfirm, }: {
|
|
16
|
+
contentDirty: boolean;
|
|
17
|
+
pathDirty: boolean;
|
|
18
|
+
availableLocalesDirty: boolean;
|
|
19
|
+
onCancel: () => void;
|
|
20
|
+
onConfirm: () => void;
|
|
21
|
+
}) => import("react").JSX.Element;
|
|
22
|
+
/**
|
|
23
|
+
* Blocks router navigation / browser unload while the form is dirty. Driven by
|
|
24
|
+
* the navigation-guard adapter's `isBlocked` state; `onStay` keeps the editor
|
|
25
|
+
* on the page, `onProceed` discards and continues.
|
|
26
|
+
*/
|
|
27
|
+
export declare const NavigationGuardModal: ({ onStay, onProceed, }: {
|
|
28
|
+
onStay: () => void;
|
|
29
|
+
onProceed: () => void;
|
|
30
|
+
}) => import("react").JSX.Element;
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useTranslation } from "@byline/i18n/react";
|
|
4
|
+
import { Button, CloseIcon, IconButton, Modal } from "@byline/ui/react";
|
|
5
|
+
import classnames from "classnames";
|
|
6
|
+
import form_renderer_module from "./form-renderer.module.js";
|
|
7
|
+
const UnsavedChangesModal = ({ onClose })=>{
|
|
8
|
+
const { t } = useTranslation('byline-admin');
|
|
9
|
+
return /*#__PURE__*/ jsx(Modal, {
|
|
10
|
+
isOpen: true,
|
|
11
|
+
closeOnOverlayClick: true,
|
|
12
|
+
onDismiss: onClose,
|
|
13
|
+
children: /*#__PURE__*/ jsxs(Modal.Container, {
|
|
14
|
+
style: {
|
|
15
|
+
maxWidth: '460px'
|
|
16
|
+
},
|
|
17
|
+
children: [
|
|
18
|
+
/*#__PURE__*/ jsx(Modal.Header, {
|
|
19
|
+
className: classnames('byline-form-guard-modal-head', form_renderer_module["guard-modal-head"]),
|
|
20
|
+
children: /*#__PURE__*/ jsx("h3", {
|
|
21
|
+
className: classnames('byline-form-guard-modal-title', form_renderer_module["guard-modal-title"]),
|
|
22
|
+
children: t('forms.unsavedChanges.title')
|
|
23
|
+
})
|
|
24
|
+
}),
|
|
25
|
+
/*#__PURE__*/ jsx(Modal.Content, {
|
|
26
|
+
children: /*#__PURE__*/ jsx("p", {
|
|
27
|
+
className: classnames('byline-form-guard-modal-text', form_renderer_module["guard-modal-text"]),
|
|
28
|
+
children: t('forms.unsavedChanges.message')
|
|
29
|
+
})
|
|
30
|
+
}),
|
|
31
|
+
/*#__PURE__*/ jsx(Modal.Actions, {
|
|
32
|
+
children: /*#__PURE__*/ jsx(Button, {
|
|
33
|
+
size: "sm",
|
|
34
|
+
style: {
|
|
35
|
+
minWidth: '60px'
|
|
36
|
+
},
|
|
37
|
+
intent: "primary",
|
|
38
|
+
type: "button",
|
|
39
|
+
onClick: onClose,
|
|
40
|
+
children: t('forms.unsavedChanges.okButton')
|
|
41
|
+
})
|
|
42
|
+
})
|
|
43
|
+
]
|
|
44
|
+
})
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
const SystemFieldsConfirmModal = ({ contentDirty, pathDirty, availableLocalesDirty, onCancel, onConfirm })=>{
|
|
48
|
+
const { t } = useTranslation('byline-admin');
|
|
49
|
+
return /*#__PURE__*/ jsx(Modal, {
|
|
50
|
+
isOpen: true,
|
|
51
|
+
closeOnOverlayClick: true,
|
|
52
|
+
onDismiss: onCancel,
|
|
53
|
+
children: /*#__PURE__*/ jsxs(Modal.Container, {
|
|
54
|
+
style: {
|
|
55
|
+
maxWidth: '520px'
|
|
56
|
+
},
|
|
57
|
+
children: [
|
|
58
|
+
/*#__PURE__*/ jsxs(Modal.Header, {
|
|
59
|
+
className: classnames('byline-form-guard-modal-head', form_renderer_module["guard-modal-head"]),
|
|
60
|
+
children: [
|
|
61
|
+
/*#__PURE__*/ jsx("h3", {
|
|
62
|
+
className: classnames('byline-form-guard-modal-title', form_renderer_module["guard-modal-title"]),
|
|
63
|
+
children: contentDirty ? t('forms.systemFieldsConfirm.bothTitle') : t('forms.systemFieldsConfirm.title')
|
|
64
|
+
}),
|
|
65
|
+
/*#__PURE__*/ jsx(IconButton, {
|
|
66
|
+
"aria-label": t('common.actions.close'),
|
|
67
|
+
size: "xs",
|
|
68
|
+
onClick: onCancel,
|
|
69
|
+
children: /*#__PURE__*/ jsx(CloseIcon, {
|
|
70
|
+
width: "16px",
|
|
71
|
+
height: "16px",
|
|
72
|
+
svgClassName: "white-icon"
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
]
|
|
76
|
+
}),
|
|
77
|
+
/*#__PURE__*/ jsxs(Modal.Content, {
|
|
78
|
+
className: "prose",
|
|
79
|
+
children: [
|
|
80
|
+
contentDirty && /*#__PURE__*/ jsx("p", {
|
|
81
|
+
className: classnames('byline-form-system-fields-content-note', 'm-0 mt-2'),
|
|
82
|
+
children: t('forms.systemFieldsConfirm.contentNote')
|
|
83
|
+
}),
|
|
84
|
+
/*#__PURE__*/ jsx("p", {
|
|
85
|
+
className: "m-0 mt-2",
|
|
86
|
+
style: contentDirty ? {
|
|
87
|
+
marginTop: 'var(--spacing-8)',
|
|
88
|
+
paddingTop: 'var(--spacing-12)',
|
|
89
|
+
borderTop: '1px solid var(--border-color)'
|
|
90
|
+
} : void 0,
|
|
91
|
+
children: t('forms.systemFieldsConfirm.intro')
|
|
92
|
+
}),
|
|
93
|
+
/*#__PURE__*/ jsxs("ul", {
|
|
94
|
+
className: classnames('byline-form-system-fields-list', form_renderer_module["guard-modal-text"], 'm-0'),
|
|
95
|
+
children: [
|
|
96
|
+
pathDirty && /*#__PURE__*/ jsx("li", {
|
|
97
|
+
children: t('forms.systemFieldsConfirm.bulletPath')
|
|
98
|
+
}),
|
|
99
|
+
availableLocalesDirty && /*#__PURE__*/ jsx("li", {
|
|
100
|
+
children: t('forms.systemFieldsConfirm.bulletLocales')
|
|
101
|
+
})
|
|
102
|
+
]
|
|
103
|
+
}),
|
|
104
|
+
/*#__PURE__*/ jsx("p", {
|
|
105
|
+
className: classnames('byline-form-system-fields-effect', form_renderer_module["guard-modal-text"]),
|
|
106
|
+
style: {
|
|
107
|
+
marginTop: 'var(--spacing-4)',
|
|
108
|
+
marginBottom: 0,
|
|
109
|
+
color: 'var(--text-subtle)'
|
|
110
|
+
},
|
|
111
|
+
children: t('forms.systemFieldsConfirm.effectLine')
|
|
112
|
+
})
|
|
113
|
+
]
|
|
114
|
+
}),
|
|
115
|
+
/*#__PURE__*/ jsxs(Modal.Actions, {
|
|
116
|
+
children: [
|
|
117
|
+
/*#__PURE__*/ jsx(Button, {
|
|
118
|
+
size: "sm",
|
|
119
|
+
style: {
|
|
120
|
+
minWidth: '80px'
|
|
121
|
+
},
|
|
122
|
+
intent: "noeffect",
|
|
123
|
+
type: "button",
|
|
124
|
+
onClick: onCancel,
|
|
125
|
+
children: t('common.actions.cancel')
|
|
126
|
+
}),
|
|
127
|
+
/*#__PURE__*/ jsx(Button, {
|
|
128
|
+
size: "sm",
|
|
129
|
+
style: {
|
|
130
|
+
minWidth: '80px'
|
|
131
|
+
},
|
|
132
|
+
intent: "primary",
|
|
133
|
+
type: "button",
|
|
134
|
+
onClick: onConfirm,
|
|
135
|
+
children: t('forms.systemFieldsConfirm.confirmButton')
|
|
136
|
+
})
|
|
137
|
+
]
|
|
138
|
+
})
|
|
139
|
+
]
|
|
140
|
+
})
|
|
141
|
+
});
|
|
142
|
+
};
|
|
143
|
+
const NavigationGuardModal = ({ onStay, onProceed })=>{
|
|
144
|
+
const { t } = useTranslation('byline-admin');
|
|
145
|
+
return /*#__PURE__*/ jsx(Modal, {
|
|
146
|
+
isOpen: true,
|
|
147
|
+
closeOnOverlayClick: false,
|
|
148
|
+
onDismiss: onStay,
|
|
149
|
+
children: /*#__PURE__*/ jsxs(Modal.Container, {
|
|
150
|
+
style: {
|
|
151
|
+
maxWidth: '460px'
|
|
152
|
+
},
|
|
153
|
+
children: [
|
|
154
|
+
/*#__PURE__*/ jsx(Modal.Header, {
|
|
155
|
+
className: classnames('byline-form-guard-modal-head', form_renderer_module["guard-modal-head"]),
|
|
156
|
+
children: /*#__PURE__*/ jsx("h3", {
|
|
157
|
+
className: classnames('byline-form-guard-modal-title', form_renderer_module["guard-modal-title"]),
|
|
158
|
+
children: t('forms.navigationGuard.title')
|
|
159
|
+
})
|
|
160
|
+
}),
|
|
161
|
+
/*#__PURE__*/ jsx(Modal.Content, {
|
|
162
|
+
children: /*#__PURE__*/ jsx("p", {
|
|
163
|
+
className: classnames('byline-form-guard-modal-text', form_renderer_module["guard-modal-text"]),
|
|
164
|
+
children: t('forms.navigationGuard.message')
|
|
165
|
+
})
|
|
166
|
+
}),
|
|
167
|
+
/*#__PURE__*/ jsxs(Modal.Actions, {
|
|
168
|
+
children: [
|
|
169
|
+
/*#__PURE__*/ jsx(Button, {
|
|
170
|
+
size: "sm",
|
|
171
|
+
intent: "noeffect",
|
|
172
|
+
type: "button",
|
|
173
|
+
onClick: onStay,
|
|
174
|
+
children: t('forms.navigationGuard.stayButton')
|
|
175
|
+
}),
|
|
176
|
+
/*#__PURE__*/ jsx(Button, {
|
|
177
|
+
size: "sm",
|
|
178
|
+
intent: "danger",
|
|
179
|
+
type: "button",
|
|
180
|
+
onClick: onProceed,
|
|
181
|
+
children: t('forms.navigationGuard.leaveButton')
|
|
182
|
+
})
|
|
183
|
+
]
|
|
184
|
+
})
|
|
185
|
+
]
|
|
186
|
+
})
|
|
187
|
+
});
|
|
188
|
+
};
|
|
189
|
+
export { NavigationGuardModal, SystemFieldsConfirmModal, UnsavedChangesModal };
|