@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.
@@ -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
- initialSystemPath.current = systemPathRef.current;
156
- initialSystemAvailableLocales.current = [
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 };