@byline/ui 2.5.2 → 2.6.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 (218) hide show
  1. package/dist/components/shimmer/shimmer.d.ts +13 -1
  2. package/dist/components/shimmer/shimmer.js +29 -20
  3. package/dist/components/shimmer/shimmer_module.css +4 -4
  4. package/dist/dnd/draggable-sortable/demo/draggable-list-demo.js +1 -1
  5. package/dist/react.d.ts +18 -54
  6. package/dist/react.js +0 -35
  7. package/dist/styles/styles.css +3 -0
  8. package/dist/uikit.d.ts +1 -0
  9. package/dist/uikit.js +1 -0
  10. package/package.json +2 -8
  11. package/src/components/shimmer/shimmer.module.css +8 -4
  12. package/src/components/shimmer/shimmer.tsx +34 -9
  13. package/src/dnd/draggable-sortable/demo/draggable-list-demo.tsx +1 -1
  14. package/src/react.ts +20 -68
  15. package/src/styles/functional/surfaces.css +13 -1
  16. package/src/uikit.ts +1 -0
  17. package/dist/admin/group.d.ts +0 -27
  18. package/dist/admin/group.js +0 -14
  19. package/dist/admin/group.module.js +0 -6
  20. package/dist/admin/group_module.css +0 -19
  21. package/dist/admin/row.d.ts +0 -25
  22. package/dist/admin/row.js +0 -8
  23. package/dist/admin/row.module.js +0 -5
  24. package/dist/admin/row_module.css +0 -18
  25. package/dist/admin/tabs.d.ts +0 -25
  26. package/dist/admin/tabs.js +0 -35
  27. package/dist/admin/tabs.module.js +0 -10
  28. package/dist/admin/tabs_module.css +0 -68
  29. package/dist/fields/array/array-field.d.ts +0 -14
  30. package/dist/fields/array/array-field.js +0 -176
  31. package/dist/fields/array/array-field.module.js +0 -11
  32. package/dist/fields/array/array-field_module.css +0 -32
  33. package/dist/fields/blocks/blocks-field.d.ts +0 -13
  34. package/dist/fields/blocks/blocks-field.js +0 -244
  35. package/dist/fields/blocks/blocks-field.module.js +0 -26
  36. package/dist/fields/blocks/blocks-field_module.css +0 -107
  37. package/dist/fields/checkbox/checkbox-field.d.ts +0 -16
  38. package/dist/fields/checkbox/checkbox-field.js +0 -28
  39. package/dist/fields/checkbox/checkbox-field.module.js +0 -6
  40. package/dist/fields/checkbox/checkbox-field_module.css +0 -4
  41. package/dist/fields/column-formatter.d.ts +0 -20
  42. package/dist/fields/column-formatter.js +0 -15
  43. package/dist/fields/date-time-formatter.d.ts +0 -16
  44. package/dist/fields/date-time-formatter.js +0 -8
  45. package/dist/fields/datetime/datetime-field.d.ts +0 -16
  46. package/dist/fields/datetime/datetime-field.js +0 -37
  47. package/dist/fields/datetime/datetime-field.module.js +0 -5
  48. package/dist/fields/datetime/datetime-field_module.css +0 -4
  49. package/dist/fields/draggable-context-menu.d.ts +0 -6
  50. package/dist/fields/draggable-context-menu.js +0 -83
  51. package/dist/fields/draggable-context-menu.module.js +0 -15
  52. package/dist/fields/draggable-context-menu_module.css +0 -91
  53. package/dist/fields/field-helpers.d.ts +0 -26
  54. package/dist/fields/field-helpers.js +0 -50
  55. package/dist/fields/field-renderer.d.ts +0 -37
  56. package/dist/fields/field-renderer.js +0 -206
  57. package/dist/fields/field-renderer.module.js +0 -8
  58. package/dist/fields/field-renderer_module.css +0 -11
  59. package/dist/fields/file/file-field.d.ts +0 -19
  60. package/dist/fields/file/file-field.js +0 -226
  61. package/dist/fields/file/file-field.module.js +0 -18
  62. package/dist/fields/file/file-field_module.css +0 -131
  63. package/dist/fields/file/file-upload-field.d.ts +0 -21
  64. package/dist/fields/file/file-upload-field.js +0 -128
  65. package/dist/fields/file/file-upload-field.module.js +0 -15
  66. package/dist/fields/file/file-upload-field_module.css +0 -74
  67. package/dist/fields/group/group-field.d.ts +0 -15
  68. package/dist/fields/group/group-field.js +0 -59
  69. package/dist/fields/group/group-field.module.js +0 -9
  70. package/dist/fields/group/group-field_module.css +0 -27
  71. package/dist/fields/image/image-field.d.ts +0 -19
  72. package/dist/fields/image/image-field.js +0 -242
  73. package/dist/fields/image/image-field.module.js +0 -22
  74. package/dist/fields/image/image-field_module.css +0 -121
  75. package/dist/fields/image/image-upload-field.d.ts +0 -21
  76. package/dist/fields/image/image-upload-field.js +0 -187
  77. package/dist/fields/image/image-upload-field.module.js +0 -19
  78. package/dist/fields/image/image-upload-field_module.css +0 -92
  79. package/dist/fields/local-date-time.d.ts +0 -27
  80. package/dist/fields/local-date-time.js +0 -49
  81. package/dist/fields/locale-badge.d.ts +0 -18
  82. package/dist/fields/locale-badge.js +0 -10
  83. package/dist/fields/locale-badge.module.js +0 -5
  84. package/dist/fields/locale-badge_module.css +0 -27
  85. package/dist/fields/numerical/numerical-field.d.ts +0 -18
  86. package/dist/fields/numerical/numerical-field.js +0 -74
  87. package/dist/fields/relation/relation-display.d.ts +0 -40
  88. package/dist/fields/relation/relation-display.js +0 -58
  89. package/dist/fields/relation/relation-display.module.js +0 -9
  90. package/dist/fields/relation/relation-display_module.css +0 -21
  91. package/dist/fields/relation/relation-field.d.ts +0 -18
  92. package/dist/fields/relation/relation-field.js +0 -146
  93. package/dist/fields/relation/relation-field.module.js +0 -13
  94. package/dist/fields/relation/relation-field_module.css +0 -62
  95. package/dist/fields/relation/relation-picker.d.ts +0 -49
  96. package/dist/fields/relation/relation-picker.js +0 -233
  97. package/dist/fields/relation/relation-picker.module.js +0 -26
  98. package/dist/fields/relation/relation-picker_module.css +0 -124
  99. package/dist/fields/relation/relation-summary.d.ts +0 -31
  100. package/dist/fields/relation/relation-summary.js +0 -50
  101. package/dist/fields/relation/relation-summary.module.js +0 -11
  102. package/dist/fields/relation/relation-summary_module.css +0 -37
  103. package/dist/fields/select/select-field.d.ts +0 -16
  104. package/dist/fields/select/select-field.js +0 -50
  105. package/dist/fields/select/select-field.module.js +0 -5
  106. package/dist/fields/select/select-field_module.css +0 -4
  107. package/dist/fields/sortable-item.d.ts +0 -15
  108. package/dist/fields/sortable-item.js +0 -80
  109. package/dist/fields/sortable-item.module.js +0 -22
  110. package/dist/fields/sortable-item_module.css +0 -124
  111. package/dist/fields/text/text-field.d.ts +0 -20
  112. package/dist/fields/text/text-field.js +0 -104
  113. package/dist/fields/text/text-field.module.js +0 -6
  114. package/dist/fields/text/text-field_module.css +0 -5
  115. package/dist/fields/text-area/text-area-field.d.ts +0 -20
  116. package/dist/fields/text-area/text-area-field.js +0 -105
  117. package/dist/fields/text-area/text-area-field.module.js +0 -6
  118. package/dist/fields/text-area/text-area-field_module.css +0 -5
  119. package/dist/fields/use-field-change-handler.d.ts +0 -23
  120. package/dist/fields/use-field-change-handler.js +0 -52
  121. package/dist/forms/document-actions.d.ts +0 -48
  122. package/dist/forms/document-actions.js +0 -469
  123. package/dist/forms/document-actions.module.js +0 -34
  124. package/dist/forms/document-actions_module.css +0 -118
  125. package/dist/forms/form-context.d.ts +0 -89
  126. package/dist/forms/form-context.js +0 -466
  127. package/dist/forms/form-renderer.d.ts +0 -98
  128. package/dist/forms/form-renderer.js +0 -591
  129. package/dist/forms/form-renderer.module.js +0 -46
  130. package/dist/forms/form-renderer_module.css +0 -245
  131. package/dist/forms/navigation-guard.d.ts +0 -54
  132. package/dist/forms/navigation-guard.js +0 -22
  133. package/dist/forms/path-widget.d.ts +0 -36
  134. package/dist/forms/path-widget.js +0 -107
  135. package/dist/forms/path-widget.module.js +0 -8
  136. package/dist/forms/path-widget_module.css +0 -29
  137. package/dist/forms/upload-executor.d.ts +0 -57
  138. package/dist/forms/upload-executor.js +0 -92
  139. package/dist/services/field-services-context.d.ts +0 -16
  140. package/dist/services/field-services-context.js +0 -13
  141. package/dist/services/field-services-types.d.ts +0 -63
  142. package/dist/services/field-services-types.js +0 -1
  143. package/dist/widgets/diff-viewer/diff-modal.d.ts +0 -22
  144. package/dist/widgets/diff-viewer/diff-modal.js +0 -146
  145. package/dist/widgets/diff-viewer/diff-modal.module.js +0 -14
  146. package/dist/widgets/diff-viewer/diff-modal_module.css +0 -56
  147. package/dist/widgets/status-badge/status-badge.d.ts +0 -25
  148. package/dist/widgets/status-badge/status-badge.js +0 -35
  149. package/dist/widgets/status-badge/status-badge.module.js +0 -7
  150. package/dist/widgets/status-badge/status-badge_module.css +0 -20
  151. package/src/admin/group.module.css +0 -41
  152. package/src/admin/group.tsx +0 -40
  153. package/src/admin/row.module.css +0 -32
  154. package/src/admin/row.tsx +0 -33
  155. package/src/admin/tabs.module.css +0 -107
  156. package/src/admin/tabs.tsx +0 -82
  157. package/src/fields/array/array-field.module.css +0 -48
  158. package/src/fields/array/array-field.tsx +0 -266
  159. package/src/fields/blocks/blocks-field.module.css +0 -148
  160. package/src/fields/blocks/blocks-field.tsx +0 -312
  161. package/src/fields/checkbox/checkbox-field.module.css +0 -4
  162. package/src/fields/checkbox/checkbox-field.tsx +0 -54
  163. package/src/fields/column-formatter.tsx +0 -31
  164. package/src/fields/date-time-formatter.tsx +0 -22
  165. package/src/fields/datetime/datetime-field.module.css +0 -13
  166. package/src/fields/datetime/datetime-field.tsx +0 -54
  167. package/src/fields/draggable-context-menu.module.css +0 -127
  168. package/src/fields/draggable-context-menu.tsx +0 -85
  169. package/src/fields/field-helpers.ts +0 -69
  170. package/src/fields/field-renderer.module.css +0 -22
  171. package/src/fields/field-renderer.tsx +0 -288
  172. package/src/fields/file/file-field.module.css +0 -153
  173. package/src/fields/file/file-field.tsx +0 -271
  174. package/src/fields/file/file-upload-field.module.css +0 -101
  175. package/src/fields/file/file-upload-field.tsx +0 -183
  176. package/src/fields/group/group-field.module.css +0 -43
  177. package/src/fields/group/group-field.tsx +0 -84
  178. package/src/fields/image/image-field.module.css +0 -155
  179. package/src/fields/image/image-field.tsx +0 -291
  180. package/src/fields/image/image-upload-field.module.css +0 -123
  181. package/src/fields/image/image-upload-field.tsx +0 -270
  182. package/src/fields/local-date-time.tsx +0 -88
  183. package/src/fields/locale-badge.module.css +0 -37
  184. package/src/fields/locale-badge.tsx +0 -32
  185. package/src/fields/numerical/numerical-field.tsx +0 -114
  186. package/src/fields/relation/relation-display.module.css +0 -36
  187. package/src/fields/relation/relation-display.tsx +0 -130
  188. package/src/fields/relation/relation-field.module.css +0 -83
  189. package/src/fields/relation/relation-field.tsx +0 -206
  190. package/src/fields/relation/relation-picker.module.css +0 -168
  191. package/src/fields/relation/relation-picker.tsx +0 -325
  192. package/src/fields/relation/relation-summary.module.css +0 -55
  193. package/src/fields/relation/relation-summary.tsx +0 -123
  194. package/src/fields/select/select-field.module.css +0 -13
  195. package/src/fields/select/select-field.tsx +0 -61
  196. package/src/fields/sortable-item.module.css +0 -167
  197. package/src/fields/sortable-item.tsx +0 -101
  198. package/src/fields/text/text-field.module.css +0 -13
  199. package/src/fields/text/text-field.tsx +0 -146
  200. package/src/fields/text-area/text-area-field.module.css +0 -13
  201. package/src/fields/text-area/text-area-field.tsx +0 -147
  202. package/src/fields/use-field-change-handler.ts +0 -112
  203. package/src/forms/document-actions.module.css +0 -160
  204. package/src/forms/document-actions.tsx +0 -487
  205. package/src/forms/form-context.tsx +0 -704
  206. package/src/forms/form-renderer.module.css +0 -321
  207. package/src/forms/form-renderer.tsx +0 -888
  208. package/src/forms/navigation-guard.tsx +0 -98
  209. package/src/forms/path-widget.module.css +0 -41
  210. package/src/forms/path-widget.test.tsx +0 -217
  211. package/src/forms/path-widget.tsx +0 -181
  212. package/src/forms/upload-executor.ts +0 -190
  213. package/src/services/field-services-context.tsx +0 -35
  214. package/src/services/field-services-types.ts +0 -68
  215. package/src/widgets/diff-viewer/diff-modal.module.css +0 -79
  216. package/src/widgets/diff-viewer/diff-modal.tsx +0 -184
  217. package/src/widgets/status-badge/status-badge.module.css +0 -31
  218. package/src/widgets/status-badge/status-badge.tsx +0 -69
@@ -1,89 +0,0 @@
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 React from 'react';
9
- import type { Field } from '@byline/core';
10
- import type { DocumentPatch } from '@byline/core/patches';
11
- interface FormError {
12
- field: string;
13
- message: string;
14
- }
15
- /**
16
- * Represents a file that has been selected but not yet uploaded.
17
- * The file is held locally until form submission.
18
- */
19
- export interface PendingUpload {
20
- /** The actual File object to upload */
21
- file: File;
22
- /** Blob URL for local preview (must be revoked on cleanup) */
23
- previewUrl: string;
24
- /** The collection path for the upload endpoint */
25
- collectionPath: string;
26
- }
27
- type FieldListener = (value: any) => void;
28
- type ErrorsListener = (errors: FormError[]) => void;
29
- type MetaListener = () => void;
30
- type SystemPathListener = (value: string | null) => void;
31
- type FieldUploadingListener = (uploading: boolean) => void;
32
- interface FormContextType {
33
- setFieldValue: (name: string, value: any) => void;
34
- setFieldStore: (name: string, value: any) => void;
35
- getFieldValue: (name: string) => any;
36
- getFieldValues: () => Record<string, any>;
37
- getPatches: () => DocumentPatch[];
38
- appendPatch: (patch: DocumentPatch) => void;
39
- resetPatches: () => void;
40
- hasChanges: () => boolean;
41
- resetHasChanges: () => void;
42
- runFieldHooks: (fields: Field[]) => Promise<FormError[]>;
43
- validateForm: (fields: Field[]) => FormError[];
44
- errors: FormError[];
45
- getErrors: () => FormError[];
46
- clearErrors: () => void;
47
- setFieldError: (field: string, message: string) => void;
48
- clearFieldError: (field: string) => void;
49
- isDirty: (fieldName: string) => boolean;
50
- subscribeField: (name: string, listener: FieldListener) => () => void;
51
- subscribeErrors: (listener: ErrorsListener) => () => void;
52
- subscribeMeta: (listener: MetaListener) => () => void;
53
- addPendingUpload: (fieldPath: string, upload: PendingUpload) => void;
54
- removePendingUpload: (fieldPath: string) => void;
55
- getPendingUploads: () => Map<string, PendingUpload>;
56
- hasPendingUploads: () => boolean;
57
- clearPendingUploads: () => void;
58
- setFieldUploading: (fieldPath: string, uploading: boolean) => void;
59
- getIsFieldUploading: (fieldPath: string) => boolean;
60
- subscribeFieldUploading: (fieldPath: string, listener: FieldUploadingListener) => () => void;
61
- getSystemPath: () => string | null;
62
- setSystemPath: (value: string | null) => void;
63
- subscribeSystemPath: (listener: SystemPathListener) => () => void;
64
- }
65
- export declare const useFormContext: () => FormContextType;
66
- export declare const FormProvider: ({ children, initialData, }: {
67
- children: React.ReactNode;
68
- initialData?: Record<string, any>;
69
- }) => React.JSX.Element;
70
- /**
71
- * Subscribe to the system `path` slot edited by the path widget.
72
- * Returns the current value (or `null` when no override is set).
73
- */
74
- export declare const useSystemPath: () => string | null;
75
- export declare const useFormStore: () => FormContextType;
76
- export declare const useFieldError: (name: string) => string | undefined;
77
- export declare const useFormMeta: () => {
78
- hasChanges: boolean;
79
- };
80
- export declare const useIsDirty: (name: string) => boolean;
81
- export declare const useFieldValue: <T = any>(name: string) => T | undefined;
82
- /**
83
- * Subscribe to a single field's upload-in-flight state. Returns `true` while
84
- * the form orchestrator is actively transporting this field's pending upload
85
- * (between the `setFieldUploading(path, true)` and the matching `false`
86
- * emitted by the upload executor's progress callback).
87
- */
88
- export declare const useIsFieldUploading: (fieldPath: string) => boolean;
89
- export {};
@@ -1,466 +0,0 @@
1
- "use client";
2
- import { jsx } from "react/jsx-runtime";
3
- import { createContext, useCallback, useContext, useEffect, useRef, useState } from "react";
4
- import { normalizeHooks } from "@byline/core";
5
- import { get, set as external_lodash_es_set } from "lodash-es";
6
- const FormContext = /*#__PURE__*/ createContext(null);
7
- const useFormContext = ()=>{
8
- const context = useContext(FormContext);
9
- if (null == context) throw new Error('useFormContext must be used within a FormProvider');
10
- return context;
11
- };
12
- const FormProvider = ({ children, initialData = {} })=>{
13
- const fieldValues = useRef(JSON.parse(JSON.stringify(initialData?.fields ?? initialData)));
14
- const initialValues = useRef(initialData?.fields ?? initialData);
15
- const errorsRef = useRef([]);
16
- const dirtyFields = useRef(new Set());
17
- const patchesRef = useRef([]);
18
- const pendingUploadsRef = useRef(new Map());
19
- const uploadingFieldsRef = useRef(new Set());
20
- const uploadingListenersRef = useRef(new Map());
21
- const fieldListeners = useRef(new Map());
22
- const errorListeners = useRef(new Set());
23
- const metaListeners = useRef(new Set());
24
- const systemPathRef = useRef('string' == typeof initialData?.path && initialData.path.length > 0 ? initialData.path : null);
25
- const initialSystemPath = useRef(systemPathRef.current);
26
- const systemPathListeners = useRef(new Set());
27
- const subscribeField = useCallback((name, listener)=>{
28
- if (!fieldListeners.current.has(name)) fieldListeners.current.set(name, new Set());
29
- fieldListeners.current.get(name)?.add(listener);
30
- return ()=>{
31
- const listeners = fieldListeners.current.get(name);
32
- if (listeners) {
33
- listeners.delete(listener);
34
- if (0 === listeners.size) fieldListeners.current.delete(name);
35
- }
36
- };
37
- }, []);
38
- const subscribeErrors = useCallback((listener)=>{
39
- errorListeners.current.add(listener);
40
- return ()=>{
41
- errorListeners.current.delete(listener);
42
- };
43
- }, []);
44
- const subscribeMeta = useCallback((listener)=>{
45
- metaListeners.current.add(listener);
46
- return ()=>{
47
- metaListeners.current.delete(listener);
48
- };
49
- }, []);
50
- const notifyFieldListeners = useCallback((name, value)=>{
51
- const listeners = fieldListeners.current.get(name);
52
- if (listeners) listeners.forEach((listener)=>{
53
- listener(value);
54
- });
55
- }, []);
56
- const notifyErrorListeners = useCallback(()=>{
57
- errorListeners.current.forEach((listener)=>{
58
- listener(errorsRef.current);
59
- });
60
- }, []);
61
- const notifyMetaListeners = useCallback(()=>{
62
- metaListeners.current.forEach((listener)=>{
63
- listener();
64
- });
65
- }, []);
66
- const updateFieldStoreInternal = useCallback((name, value)=>{
67
- const newFieldValues = {
68
- ...fieldValues.current
69
- };
70
- external_lodash_es_set(newFieldValues, name, value);
71
- fieldValues.current = newFieldValues;
72
- dirtyFields.current.add(name);
73
- notifyFieldListeners(name, value);
74
- notifyMetaListeners();
75
- }, [
76
- notifyFieldListeners,
77
- notifyMetaListeners
78
- ]);
79
- const setFieldStore = useCallback((name, value)=>{
80
- updateFieldStoreInternal(name, value);
81
- }, [
82
- updateFieldStoreInternal
83
- ]);
84
- const setFieldValue = useCallback((name, value)=>{
85
- updateFieldStoreInternal(name, value);
86
- const patch = {
87
- kind: 'field.set',
88
- path: name,
89
- value
90
- };
91
- const lastPatch = patchesRef.current[patchesRef.current.length - 1];
92
- if (lastPatch && 'field.set' === lastPatch.kind && lastPatch.path === name) {
93
- const newPatches = [
94
- ...patchesRef.current
95
- ];
96
- newPatches[newPatches.length - 1] = patch;
97
- patchesRef.current = newPatches;
98
- } else patchesRef.current = [
99
- ...patchesRef.current,
100
- patch
101
- ];
102
- if (errorsRef.current.some((error)=>error.field === name)) {
103
- errorsRef.current = errorsRef.current.filter((error)=>error.field !== name);
104
- notifyErrorListeners();
105
- }
106
- }, [
107
- updateFieldStoreInternal,
108
- notifyErrorListeners
109
- ]);
110
- const getFieldValues = useCallback(()=>fieldValues.current, []);
111
- const getPatches = useCallback(()=>patchesRef.current, []);
112
- const appendPatch = useCallback((patch)=>{
113
- patchesRef.current = [
114
- ...patchesRef.current,
115
- patch
116
- ];
117
- dirtyFields.current.add('__patch__');
118
- notifyMetaListeners();
119
- if ('production' !== process.env.NODE_ENV) console.debug('FormContext.appendPatch', {
120
- patch,
121
- dirtyCount: dirtyFields.current.size
122
- });
123
- }, [
124
- notifyMetaListeners
125
- ]);
126
- const getFieldValue = useCallback((name)=>{
127
- const dirty = dirtyFields.current.has(name);
128
- const currentValue = get(fieldValues.current, name);
129
- if (void 0 !== currentValue) return currentValue;
130
- if (!dirty) return get(initialValues.current, name);
131
- }, []);
132
- const hasChanges = useCallback(()=>dirtyFields.current.size > 0, []);
133
- const resetHasChanges = useCallback(()=>{
134
- dirtyFields.current.clear();
135
- patchesRef.current = [];
136
- initialSystemPath.current = systemPathRef.current;
137
- notifyMetaListeners();
138
- }, [
139
- notifyMetaListeners
140
- ]);
141
- const isDirty = useCallback((fieldName)=>dirtyFields.current.has(fieldName), []);
142
- const getSystemPath = useCallback(()=>systemPathRef.current, []);
143
- const setSystemPath = useCallback((value)=>{
144
- systemPathRef.current = value;
145
- if (value !== initialSystemPath.current) dirtyFields.current.add('__systemPath__');
146
- else dirtyFields.current.delete('__systemPath__');
147
- systemPathListeners.current.forEach((listener)=>{
148
- listener(value);
149
- });
150
- notifyMetaListeners();
151
- }, [
152
- notifyMetaListeners
153
- ]);
154
- const subscribeSystemPath = useCallback((listener)=>{
155
- systemPathListeners.current.add(listener);
156
- return ()=>{
157
- systemPathListeners.current.delete(listener);
158
- };
159
- }, []);
160
- const addPendingUpload = useCallback((fieldPath, upload)=>{
161
- const existing = pendingUploadsRef.current.get(fieldPath);
162
- if (existing) URL.revokeObjectURL(existing.previewUrl);
163
- pendingUploadsRef.current.set(fieldPath, upload);
164
- dirtyFields.current.add(fieldPath);
165
- notifyMetaListeners();
166
- }, [
167
- notifyMetaListeners
168
- ]);
169
- const removePendingUpload = useCallback((fieldPath)=>{
170
- const existing = pendingUploadsRef.current.get(fieldPath);
171
- if (existing) {
172
- URL.revokeObjectURL(existing.previewUrl);
173
- pendingUploadsRef.current.delete(fieldPath);
174
- notifyMetaListeners();
175
- }
176
- }, [
177
- notifyMetaListeners
178
- ]);
179
- const getPendingUploads = useCallback(()=>new Map(pendingUploadsRef.current), []);
180
- const hasPendingUploads = useCallback(()=>pendingUploadsRef.current.size > 0, []);
181
- const clearPendingUploads = useCallback(()=>{
182
- for (const upload of pendingUploadsRef.current.values())URL.revokeObjectURL(upload.previewUrl);
183
- pendingUploadsRef.current.clear();
184
- }, []);
185
- const setFieldUploading = useCallback((fieldPath, uploading)=>{
186
- if (uploading) {
187
- if (uploadingFieldsRef.current.has(fieldPath)) return;
188
- uploadingFieldsRef.current.add(fieldPath);
189
- } else {
190
- if (!uploadingFieldsRef.current.has(fieldPath)) return;
191
- uploadingFieldsRef.current.delete(fieldPath);
192
- }
193
- uploadingListenersRef.current.get(fieldPath)?.forEach((listener)=>{
194
- listener(uploading);
195
- });
196
- }, []);
197
- const getIsFieldUploading = useCallback((fieldPath)=>uploadingFieldsRef.current.has(fieldPath), []);
198
- const subscribeFieldUploading = useCallback((fieldPath, listener)=>{
199
- let listeners = uploadingListenersRef.current.get(fieldPath);
200
- if (!listeners) {
201
- listeners = new Set();
202
- uploadingListenersRef.current.set(fieldPath, listeners);
203
- }
204
- listeners.add(listener);
205
- return ()=>{
206
- const set = uploadingListenersRef.current.get(fieldPath);
207
- if (set) {
208
- set.delete(listener);
209
- if (0 === set.size) uploadingListenersRef.current.delete(fieldPath);
210
- }
211
- };
212
- }, []);
213
- useEffect(()=>()=>{
214
- for (const upload of pendingUploadsRef.current.values())URL.revokeObjectURL(upload.previewUrl);
215
- }, []);
216
- const validateForm = useCallback((fields)=>{
217
- const formErrors = [];
218
- const data = getFieldValues();
219
- for (const field of fields){
220
- const value = getFieldValue(field.name);
221
- if (!field.optional && (null == value || '' === value)) formErrors.push({
222
- field: field.name,
223
- message: `${field.label} is required`
224
- });
225
- if (null != value && '' !== value) switch(field.type){
226
- case 'text':
227
- if ('string' != typeof value) formErrors.push({
228
- field: field.name,
229
- message: `${field.label} must be text`
230
- });
231
- break;
232
- case 'checkbox':
233
- if ('boolean' != typeof value) formErrors.push({
234
- field: field.name,
235
- message: `${field.label} must be true or false`
236
- });
237
- break;
238
- case 'select':
239
- if ('options' in field && field.options) {
240
- const validValues = field.options.map((opt)=>opt.value);
241
- if (!validValues.includes(value)) formErrors.push({
242
- field: field.name,
243
- message: `${field.label} must be one of: ${validValues.join(', ')}`
244
- });
245
- }
246
- break;
247
- case 'datetime':
248
- if (value instanceof Date === false && 'string' != typeof value) formErrors.push({
249
- field: field.name,
250
- message: `${field.label} must be a valid date`
251
- });
252
- break;
253
- }
254
- if (field.validate) {
255
- const error = field.validate(value, data);
256
- if (error) formErrors.push({
257
- field: field.name,
258
- message: error
259
- });
260
- }
261
- }
262
- errorsRef.current = formErrors;
263
- notifyErrorListeners();
264
- return formErrors;
265
- }, [
266
- getFieldValue,
267
- getFieldValues,
268
- notifyErrorListeners
269
- ]);
270
- const clearErrors = useCallback(()=>{
271
- errorsRef.current = [];
272
- notifyErrorListeners();
273
- }, [
274
- notifyErrorListeners
275
- ]);
276
- const setFieldError = useCallback((field, message)=>{
277
- const filtered = errorsRef.current.filter((e)=>e.field !== field);
278
- filtered.push({
279
- field,
280
- message
281
- });
282
- errorsRef.current = filtered;
283
- notifyErrorListeners();
284
- }, [
285
- notifyErrorListeners
286
- ]);
287
- const clearFieldError = useCallback((field)=>{
288
- if (errorsRef.current.some((e)=>e.field === field)) {
289
- errorsRef.current = errorsRef.current.filter((e)=>e.field !== field);
290
- notifyErrorListeners();
291
- }
292
- }, [
293
- notifyErrorListeners
294
- ]);
295
- const runFieldHooks = useCallback(async (fields)=>{
296
- const hookErrors = [];
297
- const data = {
298
- ...fieldValues.current
299
- };
300
- for (const field of fields){
301
- const fns = normalizeHooks(field.hooks?.beforeValidate);
302
- if (0 === fns.length) continue;
303
- const path = field.name;
304
- const value = getFieldValue(path);
305
- const ctx = {
306
- value,
307
- previousValue: value,
308
- data,
309
- path,
310
- field,
311
- operation: 'submit'
312
- };
313
- try {
314
- for (const fn of fns){
315
- const result = await fn(ctx);
316
- if (result?.error) hookErrors.push({
317
- field: path,
318
- message: result.error
319
- });
320
- if (result?.value !== void 0) {
321
- setFieldValue(path, result.value);
322
- ctx.value = result.value;
323
- data[path] = result.value;
324
- }
325
- }
326
- } catch (err) {
327
- const message = err instanceof Error ? err.message : 'Unexpected hook error';
328
- hookErrors.push({
329
- field: path,
330
- message
331
- });
332
- }
333
- }
334
- if (hookErrors.length > 0) {
335
- errorsRef.current = [
336
- ...errorsRef.current,
337
- ...hookErrors
338
- ];
339
- notifyErrorListeners();
340
- }
341
- return hookErrors;
342
- }, [
343
- getFieldValue,
344
- setFieldValue,
345
- notifyErrorListeners
346
- ]);
347
- return /*#__PURE__*/ jsx(FormContext.Provider, {
348
- value: {
349
- setFieldValue,
350
- setFieldStore,
351
- getFieldValue,
352
- getFieldValues,
353
- getPatches,
354
- appendPatch,
355
- resetPatches: ()=>{
356
- patchesRef.current = [];
357
- },
358
- hasChanges,
359
- resetHasChanges,
360
- runFieldHooks,
361
- validateForm,
362
- errors: errorsRef.current,
363
- getErrors: ()=>errorsRef.current,
364
- clearErrors,
365
- setFieldError,
366
- clearFieldError,
367
- isDirty,
368
- subscribeField,
369
- subscribeErrors,
370
- subscribeMeta,
371
- addPendingUpload,
372
- removePendingUpload,
373
- getPendingUploads,
374
- hasPendingUploads,
375
- clearPendingUploads,
376
- setFieldUploading,
377
- getIsFieldUploading,
378
- subscribeFieldUploading,
379
- getSystemPath,
380
- setSystemPath,
381
- subscribeSystemPath
382
- },
383
- children: children
384
- });
385
- };
386
- const useSystemPath = ()=>{
387
- const { getSystemPath, subscribeSystemPath } = useFormContext();
388
- const [value, setValue] = useState(()=>getSystemPath());
389
- useEffect(()=>subscribeSystemPath((next)=>setValue(next)), [
390
- subscribeSystemPath
391
- ]);
392
- return value;
393
- };
394
- const useFormStore = ()=>useFormContext();
395
- const useFieldError = (name)=>{
396
- const { getErrors, subscribeErrors } = useFormContext();
397
- const [error, setError] = useState(()=>getErrors().find((e)=>e.field === name)?.message);
398
- useEffect(()=>{
399
- const unsubscribe = subscribeErrors((currentErrors)=>{
400
- const fieldError = currentErrors.find((e)=>e.field === name);
401
- setError(fieldError?.message);
402
- });
403
- return unsubscribe;
404
- }, [
405
- subscribeErrors,
406
- name
407
- ]);
408
- return error;
409
- };
410
- const useFormMeta = ()=>{
411
- const { hasChanges, subscribeMeta } = useFormContext();
412
- const [hasChangesValue, setHasChangesValue] = useState(hasChanges());
413
- useEffect(()=>{
414
- const unsubscribe = subscribeMeta(()=>{
415
- setHasChangesValue(hasChanges());
416
- });
417
- return unsubscribe;
418
- }, [
419
- subscribeMeta,
420
- hasChanges
421
- ]);
422
- return {
423
- hasChanges: hasChangesValue
424
- };
425
- };
426
- const useIsDirty = (name)=>{
427
- const { isDirty, subscribeMeta } = useFormContext();
428
- const [dirty, setDirty] = useState(isDirty(name));
429
- useEffect(()=>{
430
- const unsubscribe = subscribeMeta(()=>{
431
- setDirty(isDirty(name));
432
- });
433
- return unsubscribe;
434
- }, [
435
- subscribeMeta,
436
- isDirty,
437
- name
438
- ]);
439
- return dirty;
440
- };
441
- const useFieldValue = (name)=>{
442
- const { getFieldValue, subscribeField } = useFormContext();
443
- const [value, setValue] = useState(()=>getFieldValue(name));
444
- useEffect(()=>{
445
- const unsubscribe = subscribeField(name, (nextValue)=>{
446
- setValue(nextValue);
447
- });
448
- return unsubscribe;
449
- }, [
450
- subscribeField,
451
- name
452
- ]);
453
- return value;
454
- };
455
- const useIsFieldUploading = (fieldPath)=>{
456
- const { getIsFieldUploading, subscribeFieldUploading } = useFormContext();
457
- const [uploading, setUploading] = useState(()=>getIsFieldUploading(fieldPath));
458
- useEffect(()=>subscribeFieldUploading(fieldPath, (next)=>{
459
- setUploading(next);
460
- }), [
461
- subscribeFieldUploading,
462
- fieldPath
463
- ]);
464
- return uploading;
465
- };
466
- export { FormProvider, useFieldError, useFieldValue, useFormContext, useFormMeta, useFormStore, useIsDirty, useIsFieldUploading, useSystemPath };
@@ -1,98 +0,0 @@
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 ReactNode } from 'react';
9
- import type { CollectionAdminConfig, Field, WorkflowStatus } from '@byline/core';
10
- import { type DocumentActionsLocaleOption } from './document-actions';
11
- import type { UseNavigationGuard } from './navigation-guard';
12
- /** Metadata about a previously published version that is still live. */
13
- export interface PublishedVersionInfo {
14
- id: string;
15
- versionId: string;
16
- status: string;
17
- createdAt: string | Date;
18
- updatedAt: string | Date;
19
- }
20
- /** Props shared by both the public FormRenderer and its internal FormContent component. */
21
- export interface FormRendererProps {
22
- mode: 'create' | 'edit';
23
- fields: Field[];
24
- onSubmit: (data: any) => void;
25
- onCancel: () => void;
26
- onStatusChange?: (nextStatus: string) => Promise<void>;
27
- onUnpublish?: () => Promise<void>;
28
- onDelete?: () => Promise<void>;
29
- /**
30
- * Called when the editor confirms the duplicate modal in
31
- * `DocumentActions`. Edit views provide a handler that invokes the
32
- * `duplicateCollectionDocument` server fn and navigates to the new doc.
33
- * When omitted, the Duplicate menu item is hidden.
34
- */
35
- onDuplicate?: () => Promise<void>;
36
- /**
37
- * Called when the editor confirms the Copy-to-Locale modal in
38
- * `DocumentActions`. Edit views provide a handler that invokes the
39
- * `copyDocumentToLocale` server fn and navigates to the target-locale
40
- * view. When omitted (or when fewer than two `contentLocales` are
41
- * configured), the Copy-to-Locale menu item is hidden.
42
- */
43
- onCopyToLocale?: (args: {
44
- targetLocale: string;
45
- overwrite: boolean;
46
- }) => Promise<void>;
47
- /**
48
- * All configured content locales (code + display label) — required for
49
- * the Copy-to-Locale modal's target Select. Threaded as an opaque list
50
- * through to `DocumentActions`.
51
- */
52
- contentLocales?: ReadonlyArray<DocumentActionsLocaleOption>;
53
- nextStatus?: WorkflowStatus;
54
- workflowStatuses?: WorkflowStatus[];
55
- publishedVersion?: PublishedVersionInfo | null;
56
- initialData?: Record<string, any>;
57
- adminConfig?: CollectionAdminConfig;
58
- /**
59
- * Name of the schema field to render as the live form heading.
60
- * Sourced from `CollectionDefinition.useAsTitle` by the caller.
61
- */
62
- useAsTitle?: string;
63
- /**
64
- * Name of the schema field that initialises the system path.
65
- * Sourced from `CollectionDefinition.useAsPath` by the caller. When
66
- * present the path widget renders in the sidebar.
67
- */
68
- useAsPath?: string;
69
- headingLabel?: string;
70
- headerSlot?: ReactNode;
71
- /** Collection path forwarded to upload-capable fields (e.g. `'media'`). */
72
- collectionPath?: string;
73
- /** The active content locale — initialised from the route query string. */
74
- initialLocale?: string;
75
- /** Called when the user picks a different content locale. */
76
- onLocaleChange?: (locale: string) => void;
77
- /**
78
- * Schema-mismatch warnings produced by a "best-effort" reconstruction
79
- * of the document (`findById({ lenient: true })`). When present, the
80
- * form renders an inline Alert telling the editor that fields from a
81
- * previous schema have been dropped — saving the form will overwrite
82
- * them with the new shape.
83
- */
84
- restoreWarnings?: string[];
85
- /**
86
- * Default content locale used when no `initialLocale` is supplied and as the
87
- * fallback inside `PathWidget`. Hosts typically pass their app-wide
88
- * `i18n.content.defaultLocale`. Defaults to `'en'`.
89
- */
90
- defaultLocale?: string;
91
- /**
92
- * Framework-specific navigation guard hook.
93
- * When provided, this overrides the adapter from `NavigationGuardProvider` context.
94
- * If neither is set, a no-op `beforeunload`-only guard is used.
95
- */
96
- useNavigationGuard?: UseNavigationGuard;
97
- }
98
- export declare const FormRenderer: ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpublish, onDelete, onDuplicate, onCopyToLocale, contentLocales, nextStatus, workflowStatuses, publishedVersion, initialData, adminConfig, useAsTitle, useAsPath, headingLabel, headerSlot, collectionPath, initialLocale, onLocaleChange, defaultLocale, useNavigationGuard, restoreWarnings, }: FormRendererProps) => import("react").JSX.Element;