@byline/admin 2.5.2 → 2.6.0

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 (260) hide show
  1. package/dist/fields/array/array-field.d.ts +14 -0
  2. package/dist/fields/array/array-field.js +177 -0
  3. package/dist/fields/array/array-field.module.js +11 -0
  4. package/dist/fields/array/array-field_module.css +32 -0
  5. package/dist/fields/blocks/blocks-field.d.ts +13 -0
  6. package/dist/fields/blocks/blocks-field.js +245 -0
  7. package/dist/fields/blocks/blocks-field.module.js +26 -0
  8. package/dist/fields/blocks/blocks-field_module.css +107 -0
  9. package/dist/fields/checkbox/checkbox-field.d.ts +16 -0
  10. package/dist/fields/checkbox/checkbox-field.js +28 -0
  11. package/dist/fields/checkbox/checkbox-field.module.js +6 -0
  12. package/dist/fields/checkbox/checkbox-field_module.css +4 -0
  13. package/dist/fields/column-formatter.d.ts +20 -0
  14. package/dist/fields/column-formatter.js +15 -0
  15. package/dist/fields/date-time-formatter.d.ts +16 -0
  16. package/dist/fields/date-time-formatter.js +8 -0
  17. package/dist/fields/datetime/datetime-field.d.ts +16 -0
  18. package/dist/fields/datetime/datetime-field.js +37 -0
  19. package/dist/fields/datetime/datetime-field.module.js +5 -0
  20. package/dist/fields/datetime/datetime-field_module.css +4 -0
  21. package/dist/fields/draggable-context-menu.d.ts +6 -0
  22. package/dist/fields/draggable-context-menu.js +85 -0
  23. package/dist/fields/draggable-context-menu.module.js +15 -0
  24. package/dist/fields/draggable-context-menu_module.css +91 -0
  25. package/dist/fields/field-helpers.d.ts +26 -0
  26. package/dist/fields/field-helpers.js +50 -0
  27. package/dist/fields/field-renderer.d.ts +37 -0
  28. package/dist/fields/field-renderer.js +206 -0
  29. package/dist/fields/field-renderer.module.js +8 -0
  30. package/dist/fields/field-renderer_module.css +11 -0
  31. package/dist/fields/field-services-context.d.ts +16 -0
  32. package/dist/fields/field-services-context.js +13 -0
  33. package/dist/fields/field-services-types.d.ts +63 -0
  34. package/dist/fields/field-services-types.js +1 -0
  35. package/dist/fields/file/file-field.d.ts +19 -0
  36. package/dist/fields/file/file-field.js +225 -0
  37. package/dist/fields/file/file-field.module.js +18 -0
  38. package/dist/fields/file/file-field_module.css +131 -0
  39. package/dist/fields/file/file-upload-field.d.ts +21 -0
  40. package/dist/fields/file/file-upload-field.js +130 -0
  41. package/dist/fields/file/file-upload-field.module.js +15 -0
  42. package/dist/fields/file/file-upload-field_module.css +74 -0
  43. package/dist/fields/group/group-field.d.ts +15 -0
  44. package/dist/fields/group/group-field.js +59 -0
  45. package/dist/fields/group/group-field.module.js +9 -0
  46. package/dist/fields/group/group-field_module.css +27 -0
  47. package/dist/fields/image/image-field.d.ts +19 -0
  48. package/dist/fields/image/image-field.js +241 -0
  49. package/dist/fields/image/image-field.module.js +22 -0
  50. package/dist/fields/image/image-field_module.css +121 -0
  51. package/dist/fields/image/image-upload-field.d.ts +21 -0
  52. package/dist/fields/image/image-upload-field.js +190 -0
  53. package/dist/fields/image/image-upload-field.module.js +19 -0
  54. package/dist/fields/image/image-upload-field_module.css +92 -0
  55. package/dist/fields/local-date-time.d.ts +27 -0
  56. package/dist/fields/local-date-time.js +49 -0
  57. package/dist/fields/locale-badge.d.ts +18 -0
  58. package/dist/fields/locale-badge.js +10 -0
  59. package/dist/fields/locale-badge.module.js +5 -0
  60. package/dist/fields/locale-badge_module.css +27 -0
  61. package/dist/fields/numerical/numerical-field.d.ts +18 -0
  62. package/dist/fields/numerical/numerical-field.js +74 -0
  63. package/dist/fields/relation/relation-display.d.ts +40 -0
  64. package/dist/fields/relation/relation-display.js +58 -0
  65. package/dist/fields/relation/relation-display.module.js +9 -0
  66. package/dist/fields/relation/relation-display_module.css +21 -0
  67. package/dist/fields/relation/relation-field.d.ts +18 -0
  68. package/dist/fields/relation/relation-field.js +138 -0
  69. package/dist/fields/relation/relation-field.module.js +13 -0
  70. package/dist/fields/relation/relation-field_module.css +62 -0
  71. package/dist/fields/relation/relation-picker.d.ts +49 -0
  72. package/dist/fields/relation/relation-picker.js +236 -0
  73. package/dist/fields/relation/relation-picker.module.js +26 -0
  74. package/dist/fields/relation/relation-picker_module.css +124 -0
  75. package/dist/fields/relation/relation-summary.d.ts +31 -0
  76. package/dist/fields/relation/relation-summary.js +50 -0
  77. package/dist/fields/relation/relation-summary.module.js +11 -0
  78. package/dist/fields/relation/relation-summary_module.css +37 -0
  79. package/dist/fields/select/select-field.d.ts +16 -0
  80. package/dist/fields/select/select-field.js +50 -0
  81. package/dist/fields/select/select-field.module.js +5 -0
  82. package/dist/fields/select/select-field_module.css +4 -0
  83. package/dist/fields/sortable-item.d.ts +15 -0
  84. package/dist/fields/sortable-item.js +81 -0
  85. package/dist/fields/sortable-item.module.js +22 -0
  86. package/dist/fields/sortable-item_module.css +124 -0
  87. package/dist/fields/text/text-field.d.ts +20 -0
  88. package/dist/fields/text/text-field.js +104 -0
  89. package/dist/fields/text/text-field.module.js +6 -0
  90. package/dist/fields/text/text-field_module.css +5 -0
  91. package/dist/fields/text-area/text-area-field.d.ts +20 -0
  92. package/dist/fields/text-area/text-area-field.js +105 -0
  93. package/dist/fields/text-area/text-area-field.module.js +6 -0
  94. package/dist/fields/text-area/text-area-field_module.css +5 -0
  95. package/dist/fields/use-field-change-handler.d.ts +23 -0
  96. package/dist/fields/use-field-change-handler.js +52 -0
  97. package/dist/forms/document-actions.d.ts +48 -0
  98. package/dist/forms/document-actions.js +475 -0
  99. package/dist/forms/document-actions.module.js +34 -0
  100. package/dist/forms/document-actions_module.css +118 -0
  101. package/dist/forms/form-context.d.ts +89 -0
  102. package/dist/forms/form-context.js +466 -0
  103. package/dist/forms/form-renderer.d.ts +98 -0
  104. package/dist/forms/form-renderer.js +597 -0
  105. package/dist/forms/form-renderer.module.js +46 -0
  106. package/dist/forms/form-renderer_module.css +245 -0
  107. package/dist/forms/navigation-guard.d.ts +54 -0
  108. package/dist/forms/navigation-guard.js +22 -0
  109. package/dist/forms/path-widget.d.ts +36 -0
  110. package/dist/forms/path-widget.js +116 -0
  111. package/dist/forms/path-widget.module.js +8 -0
  112. package/dist/forms/path-widget_module.css +29 -0
  113. package/dist/forms/upload-executor.d.ts +57 -0
  114. package/dist/forms/upload-executor.js +94 -0
  115. package/dist/lib/translate-validation-error.d.ts +36 -0
  116. package/dist/lib/translate-validation-error.js +11 -0
  117. package/dist/modules/admin-account/commands.d.ts +2 -1
  118. package/dist/modules/admin-account/commands.js +13 -2
  119. package/dist/modules/admin-account/components/change-password.js +45 -36
  120. package/dist/modules/admin-account/components/container.js +185 -134
  121. package/dist/modules/admin-account/components/preferences.d.ts +8 -0
  122. package/dist/modules/admin-account/components/preferences.js +152 -0
  123. package/dist/modules/admin-account/components/preferences.module.js +11 -0
  124. package/dist/modules/admin-account/components/preferences_module.css +41 -0
  125. package/dist/modules/admin-account/components/update.js +50 -31
  126. package/dist/modules/admin-account/index.d.ts +3 -3
  127. package/dist/modules/admin-account/index.js +2 -2
  128. package/dist/modules/admin-account/schemas.d.ts +4 -0
  129. package/dist/modules/admin-account/schemas.js +4 -1
  130. package/dist/modules/admin-account/service.d.ts +1 -0
  131. package/dist/modules/admin-account/service.js +8 -0
  132. package/dist/modules/admin-permissions/components/inspector.js +31 -41
  133. package/dist/modules/admin-roles/components/create.js +43 -26
  134. package/dist/modules/admin-roles/components/permissions.js +26 -35
  135. package/dist/modules/admin-roles/components/update.js +26 -16
  136. package/dist/modules/admin-users/components/create.js +60 -40
  137. package/dist/modules/admin-users/components/roles.js +9 -15
  138. package/dist/modules/admin-users/components/set-password.js +30 -31
  139. package/dist/modules/admin-users/components/update.js +58 -39
  140. package/dist/modules/admin-users/dto.js +1 -0
  141. package/dist/modules/admin-users/repository.d.ts +17 -0
  142. package/dist/modules/admin-users/schemas.d.ts +4 -0
  143. package/dist/modules/admin-users/schemas.js +6 -2
  144. package/dist/modules/auth/components/sign-in-form.js +10 -8
  145. package/dist/presentation/group.d.ts +27 -0
  146. package/dist/presentation/group.js +14 -0
  147. package/dist/presentation/group.module.js +6 -0
  148. package/dist/presentation/group_module.css +19 -0
  149. package/dist/presentation/row.d.ts +25 -0
  150. package/dist/presentation/row.js +8 -0
  151. package/dist/presentation/row.module.js +5 -0
  152. package/dist/presentation/row_module.css +18 -0
  153. package/dist/presentation/tabs.d.ts +25 -0
  154. package/dist/presentation/tabs.js +39 -0
  155. package/dist/presentation/tabs.module.js +10 -0
  156. package/dist/presentation/tabs_module.css +68 -0
  157. package/dist/react.d.ts +66 -0
  158. package/dist/react.js +36 -0
  159. package/dist/services/admin-services-types.d.ts +16 -0
  160. package/dist/widgets/diff-viewer/diff-modal.d.ts +22 -0
  161. package/dist/widgets/diff-viewer/diff-modal.js +149 -0
  162. package/dist/widgets/diff-viewer/diff-modal.module.js +14 -0
  163. package/dist/widgets/diff-viewer/diff-modal_module.css +56 -0
  164. package/dist/widgets/status-badge/status-badge.d.ts +25 -0
  165. package/dist/widgets/status-badge/status-badge.js +37 -0
  166. package/dist/widgets/status-badge/status-badge.module.js +7 -0
  167. package/dist/widgets/status-badge/status-badge_module.css +20 -0
  168. package/package.json +14 -4
  169. package/src/fields/array/array-field.module.css +48 -0
  170. package/src/fields/array/array-field.tsx +267 -0
  171. package/src/fields/blocks/blocks-field.module.css +148 -0
  172. package/src/fields/blocks/blocks-field.tsx +323 -0
  173. package/src/fields/checkbox/checkbox-field.module.css +4 -0
  174. package/src/fields/checkbox/checkbox-field.tsx +54 -0
  175. package/src/fields/column-formatter.tsx +31 -0
  176. package/src/fields/date-time-formatter.tsx +22 -0
  177. package/src/fields/datetime/datetime-field.module.css +13 -0
  178. package/src/fields/datetime/datetime-field.tsx +54 -0
  179. package/src/fields/draggable-context-menu.module.css +127 -0
  180. package/src/fields/draggable-context-menu.tsx +87 -0
  181. package/src/fields/field-helpers.ts +69 -0
  182. package/src/fields/field-renderer.module.css +22 -0
  183. package/src/fields/field-renderer.tsx +288 -0
  184. package/src/fields/field-services-context.tsx +35 -0
  185. package/src/fields/field-services-types.ts +68 -0
  186. package/src/fields/file/file-field.module.css +153 -0
  187. package/src/fields/file/file-field.tsx +286 -0
  188. package/src/fields/file/file-upload-field.module.css +101 -0
  189. package/src/fields/file/file-upload-field.tsx +187 -0
  190. package/src/fields/group/group-field.module.css +43 -0
  191. package/src/fields/group/group-field.tsx +84 -0
  192. package/src/fields/image/image-field.module.css +155 -0
  193. package/src/fields/image/image-field.tsx +306 -0
  194. package/src/fields/image/image-upload-field.module.css +123 -0
  195. package/src/fields/image/image-upload-field.tsx +276 -0
  196. package/src/fields/local-date-time.tsx +88 -0
  197. package/src/fields/locale-badge.module.css +37 -0
  198. package/src/fields/locale-badge.tsx +32 -0
  199. package/src/fields/numerical/numerical-field.tsx +114 -0
  200. package/src/fields/relation/relation-display.module.css +36 -0
  201. package/src/fields/relation/relation-display.tsx +130 -0
  202. package/src/fields/relation/relation-field.module.css +83 -0
  203. package/src/fields/relation/relation-field.tsx +211 -0
  204. package/src/fields/relation/relation-picker.module.css +168 -0
  205. package/src/fields/relation/relation-picker.tsx +326 -0
  206. package/src/fields/relation/relation-summary.module.css +55 -0
  207. package/src/fields/relation/relation-summary.tsx +123 -0
  208. package/src/fields/select/select-field.module.css +13 -0
  209. package/src/fields/select/select-field.tsx +61 -0
  210. package/src/fields/sortable-item.module.css +167 -0
  211. package/src/fields/sortable-item.tsx +106 -0
  212. package/src/fields/text/text-field.module.css +13 -0
  213. package/src/fields/text/text-field.tsx +146 -0
  214. package/src/fields/text-area/text-area-field.module.css +13 -0
  215. package/src/fields/text-area/text-area-field.tsx +147 -0
  216. package/src/fields/use-field-change-handler.ts +112 -0
  217. package/src/forms/document-actions.module.css +160 -0
  218. package/src/forms/document-actions.tsx +482 -0
  219. package/src/forms/form-context.tsx +704 -0
  220. package/src/forms/form-renderer.module.css +321 -0
  221. package/src/forms/form-renderer.tsx +891 -0
  222. package/src/forms/navigation-guard.tsx +98 -0
  223. package/src/forms/path-widget.module.css +41 -0
  224. package/src/forms/path-widget.test.tsx +217 -0
  225. package/src/forms/path-widget.tsx +183 -0
  226. package/src/forms/upload-executor.ts +192 -0
  227. package/src/lib/translate-validation-error.ts +56 -0
  228. package/src/modules/admin-account/commands.ts +13 -0
  229. package/src/modules/admin-account/components/change-password.tsx +46 -31
  230. package/src/modules/admin-account/components/container.tsx +83 -38
  231. package/src/modules/admin-account/components/preferences.module.css +60 -0
  232. package/src/modules/admin-account/components/preferences.tsx +203 -0
  233. package/src/modules/admin-account/components/update.tsx +53 -27
  234. package/src/modules/admin-account/index.ts +3 -0
  235. package/src/modules/admin-account/schemas.ts +13 -0
  236. package/src/modules/admin-account/service.ts +12 -0
  237. package/src/modules/admin-permissions/components/inspector.tsx +22 -14
  238. package/src/modules/admin-roles/components/create.tsx +51 -23
  239. package/src/modules/admin-roles/components/permissions.tsx +25 -21
  240. package/src/modules/admin-roles/components/update.tsx +37 -19
  241. package/src/modules/admin-users/components/create.tsx +63 -34
  242. package/src/modules/admin-users/components/roles.tsx +9 -8
  243. package/src/modules/admin-users/components/set-password.tsx +34 -28
  244. package/src/modules/admin-users/components/update.tsx +58 -36
  245. package/src/modules/admin-users/dto.ts +1 -0
  246. package/src/modules/admin-users/repository.ts +17 -0
  247. package/src/modules/admin-users/schemas.ts +12 -0
  248. package/src/modules/auth/components/sign-in-form.tsx +14 -8
  249. package/src/presentation/group.module.css +41 -0
  250. package/src/presentation/group.tsx +40 -0
  251. package/src/presentation/row.module.css +32 -0
  252. package/src/presentation/row.tsx +33 -0
  253. package/src/presentation/tabs.module.css +107 -0
  254. package/src/presentation/tabs.tsx +84 -0
  255. package/src/react.ts +84 -0
  256. package/src/services/admin-services-types.ts +18 -0
  257. package/src/widgets/diff-viewer/diff-modal.module.css +79 -0
  258. package/src/widgets/diff-viewer/diff-modal.tsx +186 -0
  259. package/src/widgets/status-badge/status-badge.module.css +31 -0
  260. package/src/widgets/status-badge/status-badge.tsx +71 -0
@@ -0,0 +1,147 @@
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
+
9
+ import { useCallback } from 'react'
10
+
11
+ import type { Field, FieldComponentSlots, TextAreaField as FieldType } from '@byline/core'
12
+ import { Label, TextArea } from '@byline/ui/react'
13
+ import cx from 'classnames'
14
+
15
+ import { useFieldError, useFieldValue } from '../../forms/form-context'
16
+ import { LocaleBadge } from '../locale-badge'
17
+ import styles from './text-area-field.module.css'
18
+
19
+ export const TextAreaField = ({
20
+ field,
21
+ value,
22
+ defaultValue,
23
+ onChange,
24
+ id,
25
+ path,
26
+ locale,
27
+ components,
28
+ }: {
29
+ field: FieldType
30
+ value?: string
31
+ defaultValue?: string
32
+ onChange?: (value: string) => void
33
+ id?: string
34
+ path?: string
35
+ /** When provided, renders a LocaleBadge next to the field label. */
36
+ locale?: string
37
+ /** Optional UI component slot overrides from the admin config. */
38
+ components?: FieldComponentSlots
39
+ }) => {
40
+ const fieldPath = path ?? field.name
41
+ const fieldError = useFieldError(fieldPath)
42
+ const fieldValue = useFieldValue<string | undefined>(fieldPath)
43
+ const incomingValue = value ?? fieldValue ?? defaultValue ?? ''
44
+ const htmlId = id ?? fieldPath
45
+
46
+ const handleChange = useCallback(
47
+ (value: string) => {
48
+ if (onChange) {
49
+ onChange(value)
50
+ }
51
+ },
52
+ [onChange]
53
+ )
54
+
55
+ // Custom component slots (from admin config)
56
+ const slots = components
57
+ const CustomLabel = slots?.Label
58
+ const CustomHelpText = slots?.HelpText
59
+ const CustomField = slots?.Field
60
+ const BeforeField = slots?.beforeField
61
+ const AfterField = slots?.afterField
62
+
63
+ // Shared props available to every slot component
64
+ const slotBaseProps = {
65
+ field: field as Field,
66
+ path: fieldPath,
67
+ value: incomingValue,
68
+ error: fieldError,
69
+ id: htmlId,
70
+ }
71
+
72
+ // When a locale is active, render a custom Label+badge and suppress the
73
+ // TextArea's own label so the locale indicator appears in the label row.
74
+ const showBadge = !!locale && !!field.label
75
+
76
+ // Determine whether the label is handled externally (by a custom slot or
77
+ // the locale badge row) so TextArea doesn't render its own.
78
+ const hasCustomLabel = !!CustomLabel
79
+ const suppressInputLabel = showBadge || hasCustomLabel
80
+ const suppressInputHelpText = !!CustomHelpText
81
+
82
+ const labelRowClass = cx('byline-field-text-area-label-row', styles['label-row'])
83
+
84
+ // ── Label rendering ──────────────────────────────────────────
85
+ const renderLabel = () => {
86
+ if (hasCustomLabel) {
87
+ return (
88
+ <div className={labelRowClass}>
89
+ <CustomLabel {...slotBaseProps} label={field.label} required={!field.optional} />
90
+ {showBadge && <LocaleBadge locale={locale!} />}
91
+ </div>
92
+ )
93
+ }
94
+ if (showBadge) {
95
+ return (
96
+ <div className={labelRowClass}>
97
+ <Label
98
+ id={`${htmlId}-label`}
99
+ htmlFor={htmlId}
100
+ label={field.label!}
101
+ required={!field.optional}
102
+ />
103
+ <LocaleBadge locale={locale!} />
104
+ </div>
105
+ )
106
+ }
107
+ return null
108
+ }
109
+
110
+ // ── Field input rendering ────────────────────────────────────
111
+ const renderInput = () => {
112
+ if (CustomField) {
113
+ return (
114
+ <CustomField
115
+ {...slotBaseProps}
116
+ onChange={handleChange}
117
+ defaultValue={defaultValue}
118
+ placeholder={field.placeholder}
119
+ />
120
+ )
121
+ }
122
+ return (
123
+ <TextArea
124
+ id={htmlId}
125
+ name={field.name}
126
+ label={suppressInputLabel ? undefined : field.label}
127
+ required={!field.optional}
128
+ helpText={suppressInputHelpText ? undefined : field.helpText}
129
+ value={incomingValue}
130
+ onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => handleChange(e.target.value)}
131
+ error={fieldError != null}
132
+ errorText={fieldError}
133
+ rows={4}
134
+ />
135
+ )
136
+ }
137
+
138
+ return (
139
+ <div className={`byline-field-text-area ${field.name}`}>
140
+ {renderLabel()}
141
+ {BeforeField && <BeforeField {...slotBaseProps} />}
142
+ {renderInput()}
143
+ {AfterField && <AfterField {...slotBaseProps} />}
144
+ {CustomHelpText && <CustomHelpText {...slotBaseProps} helpText={field.helpText} />}
145
+ </div>
146
+ )
147
+ }
@@ -0,0 +1,112 @@
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
+
9
+ import { useCallback } from 'react'
10
+
11
+ import type { Field, FieldBeforeChangeResult, FieldHookContext } from '@byline/core'
12
+ import { normalizeHooks } from '@byline/core'
13
+
14
+ import { useFormContext } from '../forms/form-context'
15
+
16
+ /**
17
+ * Returns a change handler for the given field that runs through the
18
+ * field-hook pipeline before committing the value:
19
+ *
20
+ * 1. `clearFieldError(path)`
21
+ * 2. `field.hooks.beforeValidate(ctx)` — advisory: may set an error on
22
+ * the field but the value is **always** committed (user can keep typing)
23
+ * 3. `field.hooks.beforeChange(ctx)` — may return `{ value }` to replace
24
+ * or `{ error }` to block the change entirely
25
+ * 4. `setFieldValue(path, finalValue)`
26
+ *
27
+ * When the field has no hooks the function is a zero-overhead pass-through
28
+ * to `setFieldValue` (no promises, no extra allocations).
29
+ */
30
+ export function useFieldChangeHandler(field: Field, path: string) {
31
+ const { setFieldValue, getFieldValue, getFieldValues, setFieldError, clearFieldError } =
32
+ useFormContext()
33
+
34
+ return useCallback(
35
+ (value: any) => {
36
+ const hooks = field.hooks
37
+
38
+ // ── fast path: no hooks defined ────────────────────────────
39
+ const validateFns = normalizeHooks(hooks?.beforeValidate)
40
+ const changeFns = normalizeHooks(hooks?.beforeChange)
41
+
42
+ if (validateFns.length === 0 && changeFns.length === 0) {
43
+ setFieldValue(path, value)
44
+ return
45
+ }
46
+
47
+ // ── slow path: run async hook pipeline ─────────────────────
48
+ const previousValue = getFieldValue(path)
49
+ const ctx: FieldHookContext = {
50
+ value,
51
+ previousValue,
52
+ data: getFieldValues(),
53
+ path,
54
+ field,
55
+ operation: 'change',
56
+ }
57
+
58
+ clearFieldError(path)
59
+
60
+ // When there are only advisory beforeValidate hooks (no beforeChange that can
61
+ // block or transform the value), commit synchronously so that controlled inputs
62
+ // (e.g. <input value={...}>) receive the updated value before React's next
63
+ // render. Deferring even one microtask tick (via await) causes React to
64
+ // reconcile the stale value prop against the DOM and reset cursor position.
65
+ if (changeFns.length === 0) {
66
+ setFieldValue(path, value)
67
+ }
68
+
69
+ void (async () => {
70
+ try {
71
+ // 1. beforeValidate (advisory — value is always committed)
72
+ let advisoryError: string | undefined
73
+ for (const fn of validateFns) {
74
+ const result = (await fn(ctx)) as FieldBeforeChangeResult | undefined
75
+ if (result?.error) {
76
+ advisoryError = result.error
77
+ }
78
+ if (result?.value !== undefined) {
79
+ ctx.value = result.value
80
+ }
81
+ }
82
+
83
+ // 2. beforeChange
84
+ for (const fn of changeFns) {
85
+ const result = (await fn(ctx)) as FieldBeforeChangeResult | undefined
86
+ if (result?.error) {
87
+ setFieldError(path, result.error)
88
+ return // block the change
89
+ }
90
+ if (result?.value !== undefined) {
91
+ ctx.value = result.value
92
+ }
93
+ }
94
+
95
+ // 3. commit the (possibly transformed) value and surface any advisory error.
96
+ // If there were no beforeChange hooks we already committed synchronously above;
97
+ // this call is a no-op if the value hasn't been altered by a hook.
98
+ setFieldValue(path, ctx.value)
99
+ if (advisoryError) {
100
+ setFieldError(path, advisoryError)
101
+ }
102
+ } catch (err) {
103
+ // Surface unexpected hook errors as field errors rather than crashing
104
+ const message = err instanceof Error ? err.message : 'Unexpected hook error'
105
+ setFieldError(path, message)
106
+ }
107
+ })()
108
+ },
109
+ // field reference is stable per render cycle; path is derived from props
110
+ [field, path, setFieldValue, getFieldValue, getFieldValues, setFieldError, clearFieldError]
111
+ )
112
+ }
@@ -0,0 +1,160 @@
1
+ /**
2
+ * DocumentActions — overflow menu (Unpublish / Delete) on the form chrome.
3
+ *
4
+ * Override handles:
5
+ * .byline-form-actions-icon — the rotated ellipsis trigger icon
6
+ * .byline-form-actions-menu — dropdown content panel
7
+ * .byline-form-actions-item — a row inside the dropdown
8
+ * .byline-form-actions-item-icon — fixed-width icon column
9
+ * .byline-form-actions-item-text — label column
10
+ * .byline-form-actions-delete — destructive label colour
11
+ * .byline-form-actions-modal-head — modal header layout
12
+ * .byline-form-actions-modal-title — modal heading text
13
+ * .byline-form-actions-sr-only — visually-hidden autofocus shim
14
+ */
15
+
16
+ .icon,
17
+ :global(.byline-form-actions-icon) {
18
+ transform: rotate(90deg);
19
+ color: var(--primary-500);
20
+ }
21
+
22
+ .menu,
23
+ :global(.byline-form-actions-menu) {
24
+ min-width: 140px;
25
+ }
26
+
27
+ .item,
28
+ :global(.byline-form-actions-item) {
29
+ display: flex;
30
+ align-items: center;
31
+ margin-left: var(--spacing-8);
32
+ }
33
+
34
+ .item-icon,
35
+ :global(.byline-form-actions-item-icon) {
36
+ display: inline-block;
37
+ width: 28px;
38
+ }
39
+
40
+ .item-text,
41
+ :global(.byline-form-actions-item-text) {
42
+ display: inline-block;
43
+ width: 100%;
44
+ flex: 1;
45
+ font-size: var(--font-size-sm);
46
+ text-align: left;
47
+ }
48
+
49
+ .delete,
50
+ :global(.byline-form-actions-delete) {
51
+ flex: 1;
52
+ width: 100%;
53
+ border: none;
54
+ background: none;
55
+ padding: 0;
56
+ color: var(--red-600);
57
+ line-height: 1;
58
+ text-align: left;
59
+ cursor: pointer;
60
+ }
61
+
62
+ .modal-head,
63
+ :global(.byline-form-actions-modal-head) {
64
+ padding-top: 1rem;
65
+ margin-bottom: var(--spacing-8);
66
+ }
67
+
68
+ .modal-title,
69
+ :global(.byline-form-actions-modal-title) {
70
+ margin: 0 0 var(--spacing-8) 0;
71
+ font-size: var(--font-size-2xl);
72
+ }
73
+
74
+ .sr-only,
75
+ :global(.byline-form-actions-sr-only) {
76
+ position: absolute;
77
+ width: 1px;
78
+ height: 1px;
79
+ padding: 0;
80
+ margin: -1px;
81
+ overflow: hidden;
82
+ clip: rect(0, 0, 0, 0);
83
+ white-space: nowrap;
84
+ border: 0;
85
+ }
86
+
87
+ .list,
88
+ :global(.byline-form-actions-list) {
89
+ margin: var(--spacing-8) 0;
90
+ padding-left: 1.25rem;
91
+ font-size: var(--font-size-sm);
92
+ }
93
+
94
+ .preview,
95
+ :global(.byline-form-actions-preview) {
96
+ margin-top: var(--spacing-16);
97
+ padding: var(--spacing-8) var(--spacing-12);
98
+ background: var(--surface-2, rgba(0, 0, 0, 0.03));
99
+ border: 1px solid var(--border-subtle, rgba(0, 0, 0, 0.08));
100
+ border-radius: var(--radius-md, 4px);
101
+ font-size: var(--font-size-sm);
102
+ }
103
+
104
+ .preview-label,
105
+ :global(.byline-form-actions-preview-label) {
106
+ font-weight: 500;
107
+ margin-bottom: var(--spacing-4);
108
+ opacity: 0.75;
109
+ }
110
+
111
+ .preview-row,
112
+ :global(.byline-form-actions-preview-row) {
113
+ display: flex;
114
+ align-items: center;
115
+ gap: var(--spacing-8);
116
+ flex-wrap: wrap;
117
+ }
118
+
119
+ .preview-before,
120
+ :global(.byline-form-actions-preview-before) {
121
+ font-family: var(--font-mono, monospace);
122
+ opacity: 0.7;
123
+ }
124
+
125
+ .preview-arrow,
126
+ :global(.byline-form-actions-preview-arrow) {
127
+ opacity: 0.5;
128
+ }
129
+
130
+ .preview-after,
131
+ :global(.byline-form-actions-preview-after) {
132
+ font-family: var(--font-mono, monospace);
133
+ font-weight: 500;
134
+ }
135
+
136
+ .copy-row,
137
+ :global(.byline-form-actions-copy-row) {
138
+ display: flex;
139
+ align-items: center;
140
+ flex-wrap: wrap;
141
+ }
142
+
143
+ .copy-label,
144
+ :global(.byline-form-actions-copy-label) {
145
+ /* Layout-only handle; spacing is set inline alongside the field. */
146
+ }
147
+
148
+ .copy-source,
149
+ :global(.byline-form-actions-copy-source) {
150
+ font-family: var(--font-mono, monospace);
151
+ }
152
+
153
+ /* ─── Dark theme variants ───────────────────────────────────── */
154
+
155
+ :is([data-theme="dark"], :global(.dark)) {
156
+ .delete,
157
+ :global(.byline-form-actions-delete) {
158
+ color: var(--red-400);
159
+ }
160
+ }