@byline/admin 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.
- package/dist/fields/array/array-field.d.ts +14 -0
- package/dist/fields/array/array-field.js +177 -0
- package/dist/fields/array/array-field.module.js +11 -0
- package/dist/fields/array/array-field_module.css +32 -0
- package/dist/fields/blocks/blocks-field.d.ts +13 -0
- package/dist/fields/blocks/blocks-field.js +245 -0
- package/dist/fields/blocks/blocks-field.module.js +26 -0
- package/dist/fields/blocks/blocks-field_module.css +107 -0
- package/dist/fields/checkbox/checkbox-field.d.ts +16 -0
- package/dist/fields/checkbox/checkbox-field.js +28 -0
- package/dist/fields/checkbox/checkbox-field.module.js +6 -0
- package/dist/fields/checkbox/checkbox-field_module.css +4 -0
- package/dist/fields/column-formatter.d.ts +20 -0
- package/dist/fields/column-formatter.js +15 -0
- package/dist/fields/date-time-formatter.d.ts +16 -0
- package/dist/fields/date-time-formatter.js +8 -0
- package/dist/fields/datetime/datetime-field.d.ts +16 -0
- package/dist/fields/datetime/datetime-field.js +37 -0
- package/dist/fields/datetime/datetime-field.module.js +5 -0
- package/dist/fields/datetime/datetime-field_module.css +4 -0
- package/dist/fields/draggable-context-menu.d.ts +6 -0
- package/dist/fields/draggable-context-menu.js +85 -0
- package/dist/fields/draggable-context-menu.module.js +15 -0
- package/dist/fields/draggable-context-menu_module.css +91 -0
- package/dist/fields/field-helpers.d.ts +26 -0
- package/dist/fields/field-helpers.js +50 -0
- package/dist/fields/field-renderer.d.ts +37 -0
- package/dist/fields/field-renderer.js +206 -0
- package/dist/fields/field-renderer.module.js +8 -0
- package/dist/fields/field-renderer_module.css +11 -0
- package/dist/fields/field-services-context.d.ts +16 -0
- package/dist/fields/field-services-context.js +13 -0
- package/dist/fields/field-services-types.d.ts +63 -0
- package/dist/fields/field-services-types.js +1 -0
- package/dist/fields/file/file-field.d.ts +19 -0
- package/dist/fields/file/file-field.js +225 -0
- package/dist/fields/file/file-field.module.js +18 -0
- package/dist/fields/file/file-field_module.css +131 -0
- package/dist/fields/file/file-upload-field.d.ts +21 -0
- package/dist/fields/file/file-upload-field.js +130 -0
- package/dist/fields/file/file-upload-field.module.js +15 -0
- package/dist/fields/file/file-upload-field_module.css +74 -0
- package/dist/fields/group/group-field.d.ts +15 -0
- package/dist/fields/group/group-field.js +59 -0
- package/dist/fields/group/group-field.module.js +9 -0
- package/dist/fields/group/group-field_module.css +27 -0
- package/dist/fields/image/image-field.d.ts +19 -0
- package/dist/fields/image/image-field.js +241 -0
- package/dist/fields/image/image-field.module.js +22 -0
- package/dist/fields/image/image-field_module.css +121 -0
- package/dist/fields/image/image-upload-field.d.ts +21 -0
- package/dist/fields/image/image-upload-field.js +190 -0
- package/dist/fields/image/image-upload-field.module.js +19 -0
- package/dist/fields/image/image-upload-field_module.css +92 -0
- package/dist/fields/local-date-time.d.ts +27 -0
- package/dist/fields/local-date-time.js +49 -0
- package/dist/fields/locale-badge.d.ts +18 -0
- package/dist/fields/locale-badge.js +10 -0
- package/dist/fields/locale-badge.module.js +5 -0
- package/dist/fields/locale-badge_module.css +27 -0
- package/dist/fields/numerical/numerical-field.d.ts +18 -0
- package/dist/fields/numerical/numerical-field.js +74 -0
- package/dist/fields/relation/relation-display.d.ts +40 -0
- package/dist/fields/relation/relation-display.js +61 -0
- package/dist/fields/relation/relation-display.module.js +9 -0
- package/dist/fields/relation/relation-display_module.css +21 -0
- package/dist/fields/relation/relation-field.d.ts +18 -0
- package/dist/fields/relation/relation-field.js +138 -0
- package/dist/fields/relation/relation-field.module.js +13 -0
- package/dist/fields/relation/relation-field_module.css +62 -0
- package/dist/fields/relation/relation-picker.d.ts +59 -0
- package/dist/fields/relation/relation-picker.js +237 -0
- package/dist/fields/relation/relation-picker.module.js +26 -0
- package/dist/fields/relation/relation-picker_module.css +124 -0
- package/dist/fields/relation/relation-summary.d.ts +31 -0
- package/dist/fields/relation/relation-summary.js +50 -0
- package/dist/fields/relation/relation-summary.module.js +11 -0
- package/dist/fields/relation/relation-summary_module.css +37 -0
- package/dist/fields/select/select-field.d.ts +16 -0
- package/dist/fields/select/select-field.js +50 -0
- package/dist/fields/select/select-field.module.js +5 -0
- package/dist/fields/select/select-field_module.css +4 -0
- package/dist/fields/sortable-item.d.ts +15 -0
- package/dist/fields/sortable-item.js +81 -0
- package/dist/fields/sortable-item.module.js +22 -0
- package/dist/fields/sortable-item_module.css +124 -0
- package/dist/fields/text/text-field.d.ts +20 -0
- package/dist/fields/text/text-field.js +104 -0
- package/dist/fields/text/text-field.module.js +6 -0
- package/dist/fields/text/text-field_module.css +5 -0
- package/dist/fields/text-area/text-area-field.d.ts +20 -0
- package/dist/fields/text-area/text-area-field.js +105 -0
- package/dist/fields/text-area/text-area-field.module.js +6 -0
- package/dist/fields/text-area/text-area-field_module.css +5 -0
- package/dist/fields/use-field-change-handler.d.ts +23 -0
- package/dist/fields/use-field-change-handler.js +52 -0
- package/dist/forms/document-actions.d.ts +48 -0
- package/dist/forms/document-actions.js +475 -0
- package/dist/forms/document-actions.module.js +34 -0
- package/dist/forms/document-actions_module.css +118 -0
- package/dist/forms/form-context.d.ts +89 -0
- package/dist/forms/form-context.js +466 -0
- package/dist/forms/form-renderer.d.ts +98 -0
- package/dist/forms/form-renderer.js +597 -0
- package/dist/forms/form-renderer.module.js +46 -0
- package/dist/forms/form-renderer_module.css +245 -0
- package/dist/forms/navigation-guard.d.ts +54 -0
- package/dist/forms/navigation-guard.js +22 -0
- package/dist/forms/path-widget.d.ts +36 -0
- package/dist/forms/path-widget.js +116 -0
- package/dist/forms/path-widget.module.js +8 -0
- package/dist/forms/path-widget_module.css +29 -0
- package/dist/forms/upload-executor.d.ts +57 -0
- package/dist/forms/upload-executor.js +94 -0
- package/dist/lib/translate-validation-error.d.ts +36 -0
- package/dist/lib/translate-validation-error.js +11 -0
- package/dist/modules/admin-account/commands.d.ts +2 -1
- package/dist/modules/admin-account/commands.js +13 -2
- package/dist/modules/admin-account/components/change-password.js +45 -36
- package/dist/modules/admin-account/components/container.js +185 -134
- package/dist/modules/admin-account/components/preferences.d.ts +8 -0
- package/dist/modules/admin-account/components/preferences.js +152 -0
- package/dist/modules/admin-account/components/preferences.module.js +11 -0
- package/dist/modules/admin-account/components/preferences_module.css +41 -0
- package/dist/modules/admin-account/components/update.js +50 -31
- package/dist/modules/admin-account/index.d.ts +3 -3
- package/dist/modules/admin-account/index.js +2 -2
- package/dist/modules/admin-account/schemas.d.ts +4 -0
- package/dist/modules/admin-account/schemas.js +4 -1
- package/dist/modules/admin-account/service.d.ts +1 -0
- package/dist/modules/admin-account/service.js +8 -0
- package/dist/modules/admin-permissions/components/inspector.js +31 -41
- package/dist/modules/admin-roles/components/create.js +43 -26
- package/dist/modules/admin-roles/components/permissions.js +26 -35
- package/dist/modules/admin-roles/components/update.js +26 -16
- package/dist/modules/admin-users/components/create.js +60 -40
- package/dist/modules/admin-users/components/roles.js +9 -15
- package/dist/modules/admin-users/components/set-password.js +30 -31
- package/dist/modules/admin-users/components/update.js +58 -39
- package/dist/modules/admin-users/dto.js +1 -0
- package/dist/modules/admin-users/repository.d.ts +17 -0
- package/dist/modules/admin-users/schemas.d.ts +4 -0
- package/dist/modules/admin-users/schemas.js +6 -2
- package/dist/modules/auth/components/sign-in-form.js +10 -8
- package/dist/presentation/group.d.ts +27 -0
- package/dist/presentation/group.js +14 -0
- package/dist/presentation/group.module.js +6 -0
- package/dist/presentation/group_module.css +19 -0
- package/dist/presentation/row.d.ts +25 -0
- package/dist/presentation/row.js +8 -0
- package/dist/presentation/row.module.js +5 -0
- package/dist/presentation/row_module.css +18 -0
- package/dist/presentation/tabs.d.ts +25 -0
- package/dist/presentation/tabs.js +39 -0
- package/dist/presentation/tabs.module.js +10 -0
- package/dist/presentation/tabs_module.css +68 -0
- package/dist/react.d.ts +66 -0
- package/dist/react.js +36 -0
- package/dist/services/admin-services-types.d.ts +16 -0
- package/dist/widgets/diff-viewer/diff-modal.d.ts +22 -0
- package/dist/widgets/diff-viewer/diff-modal.js +149 -0
- package/dist/widgets/diff-viewer/diff-modal.module.js +14 -0
- package/dist/widgets/diff-viewer/diff-modal_module.css +56 -0
- package/dist/widgets/status-badge/status-badge.d.ts +25 -0
- package/dist/widgets/status-badge/status-badge.js +37 -0
- package/dist/widgets/status-badge/status-badge.module.js +7 -0
- package/dist/widgets/status-badge/status-badge_module.css +20 -0
- package/package.json +14 -4
- package/src/fields/array/array-field.module.css +48 -0
- package/src/fields/array/array-field.tsx +267 -0
- package/src/fields/blocks/blocks-field.module.css +148 -0
- package/src/fields/blocks/blocks-field.tsx +323 -0
- package/src/fields/checkbox/checkbox-field.module.css +4 -0
- package/src/fields/checkbox/checkbox-field.tsx +54 -0
- package/src/fields/column-formatter.tsx +31 -0
- package/src/fields/date-time-formatter.tsx +22 -0
- package/src/fields/datetime/datetime-field.module.css +13 -0
- package/src/fields/datetime/datetime-field.tsx +54 -0
- package/src/fields/draggable-context-menu.module.css +127 -0
- package/src/fields/draggable-context-menu.tsx +87 -0
- package/src/fields/field-helpers.ts +69 -0
- package/src/fields/field-renderer.module.css +22 -0
- package/src/fields/field-renderer.tsx +288 -0
- package/src/fields/field-services-context.tsx +35 -0
- package/src/fields/field-services-types.ts +68 -0
- package/src/fields/file/file-field.module.css +153 -0
- package/src/fields/file/file-field.tsx +286 -0
- package/src/fields/file/file-upload-field.module.css +101 -0
- package/src/fields/file/file-upload-field.tsx +187 -0
- package/src/fields/group/group-field.module.css +43 -0
- package/src/fields/group/group-field.tsx +84 -0
- package/src/fields/image/image-field.module.css +155 -0
- package/src/fields/image/image-field.tsx +306 -0
- package/src/fields/image/image-upload-field.module.css +123 -0
- package/src/fields/image/image-upload-field.tsx +276 -0
- package/src/fields/local-date-time.tsx +88 -0
- package/src/fields/locale-badge.module.css +37 -0
- package/src/fields/locale-badge.tsx +32 -0
- package/src/fields/numerical/numerical-field.tsx +114 -0
- package/src/fields/relation/relation-display.module.css +36 -0
- package/src/fields/relation/relation-display.tsx +138 -0
- package/src/fields/relation/relation-field.module.css +83 -0
- package/src/fields/relation/relation-field.tsx +211 -0
- package/src/fields/relation/relation-picker.module.css +168 -0
- package/src/fields/relation/relation-picker.tsx +343 -0
- package/src/fields/relation/relation-summary.module.css +55 -0
- package/src/fields/relation/relation-summary.tsx +123 -0
- package/src/fields/select/select-field.module.css +13 -0
- package/src/fields/select/select-field.tsx +61 -0
- package/src/fields/sortable-item.module.css +167 -0
- package/src/fields/sortable-item.tsx +106 -0
- package/src/fields/text/text-field.module.css +13 -0
- package/src/fields/text/text-field.tsx +146 -0
- package/src/fields/text-area/text-area-field.module.css +13 -0
- package/src/fields/text-area/text-area-field.tsx +147 -0
- package/src/fields/use-field-change-handler.ts +112 -0
- package/src/forms/document-actions.module.css +160 -0
- package/src/forms/document-actions.tsx +482 -0
- package/src/forms/form-context.tsx +704 -0
- package/src/forms/form-renderer.module.css +321 -0
- package/src/forms/form-renderer.tsx +891 -0
- package/src/forms/navigation-guard.tsx +98 -0
- package/src/forms/path-widget.module.css +41 -0
- package/src/forms/path-widget.test.tsx +217 -0
- package/src/forms/path-widget.tsx +183 -0
- package/src/forms/upload-executor.ts +192 -0
- package/src/lib/translate-validation-error.ts +56 -0
- package/src/modules/admin-account/commands.ts +13 -0
- package/src/modules/admin-account/components/change-password.tsx +46 -31
- package/src/modules/admin-account/components/container.tsx +83 -38
- package/src/modules/admin-account/components/preferences.module.css +60 -0
- package/src/modules/admin-account/components/preferences.tsx +203 -0
- package/src/modules/admin-account/components/update.tsx +53 -27
- package/src/modules/admin-account/index.ts +3 -0
- package/src/modules/admin-account/schemas.ts +13 -0
- package/src/modules/admin-account/service.ts +12 -0
- package/src/modules/admin-permissions/components/inspector.tsx +22 -14
- package/src/modules/admin-roles/components/create.tsx +51 -23
- package/src/modules/admin-roles/components/permissions.tsx +25 -21
- package/src/modules/admin-roles/components/update.tsx +37 -19
- package/src/modules/admin-users/components/create.tsx +63 -34
- package/src/modules/admin-users/components/roles.tsx +9 -8
- package/src/modules/admin-users/components/set-password.tsx +34 -28
- package/src/modules/admin-users/components/update.tsx +58 -36
- package/src/modules/admin-users/dto.ts +1 -0
- package/src/modules/admin-users/repository.ts +17 -0
- package/src/modules/admin-users/schemas.ts +12 -0
- package/src/modules/auth/components/sign-in-form.tsx +14 -8
- package/src/presentation/group.module.css +41 -0
- package/src/presentation/group.tsx +40 -0
- package/src/presentation/row.module.css +32 -0
- package/src/presentation/row.tsx +33 -0
- package/src/presentation/tabs.module.css +107 -0
- package/src/presentation/tabs.tsx +84 -0
- package/src/react.ts +84 -0
- package/src/services/admin-services-types.ts +18 -0
- package/src/widgets/diff-viewer/diff-modal.module.css +79 -0
- package/src/widgets/diff-viewer/diff-modal.tsx +186 -0
- package/src/widgets/status-badge/status-badge.module.css +31 -0
- package/src/widgets/status-badge/status-badge.tsx +71 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SortableItem — drag-card wrapping array/blocks list rows.
|
|
3
|
+
*
|
|
4
|
+
* Override handles:
|
|
5
|
+
* .byline-sortable — root card
|
|
6
|
+
* .byline-sortable-dragging — added while the item is mid-drag
|
|
7
|
+
* .byline-sortable-collapsed — added when content is collapsed
|
|
8
|
+
* .byline-sortable-header — top row (gripper + label + actions)
|
|
9
|
+
* .byline-sortable-header-collapsed — added to header when collapsed
|
|
10
|
+
* .byline-sortable-grip — drag handle button
|
|
11
|
+
* .byline-sortable-grip-icon — drag handle icon
|
|
12
|
+
* .byline-sortable-label — header label text
|
|
13
|
+
* .byline-sortable-toggle — collapse/expand button
|
|
14
|
+
* .byline-sortable-toggle-icon — chevron icon (rotates when collapsed)
|
|
15
|
+
* .byline-sortable-content — body container (children)
|
|
16
|
+
* .byline-sortable-content-hidden — collapsed body state
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
.root,
|
|
20
|
+
:global(.byline-sortable) {
|
|
21
|
+
padding: var(--spacing-16);
|
|
22
|
+
padding-top: var(--spacing-8);
|
|
23
|
+
border: var(--border-width-thin) dashed var(--gray-600);
|
|
24
|
+
border-radius: var(--border-radius-md);
|
|
25
|
+
background-color: oklch(from var(--canvas-50) l c h / 0.5);
|
|
26
|
+
box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.dragging,
|
|
30
|
+
:global(.byline-sortable-dragging) {
|
|
31
|
+
background-color: oklch(from var(--canvas-50) l c h / 0.8);
|
|
32
|
+
box-shadow:
|
|
33
|
+
0 4px 6px -1px rgb(0 0 0 / 0.1),
|
|
34
|
+
0 2px 4px -2px rgb(0 0 0 / 0.1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.collapsed,
|
|
38
|
+
:global(.byline-sortable-collapsed) {
|
|
39
|
+
padding-top: var(--spacing-8);
|
|
40
|
+
padding-bottom: var(--spacing-8);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.header,
|
|
44
|
+
:global(.byline-sortable-header) {
|
|
45
|
+
display: flex;
|
|
46
|
+
align-items: center;
|
|
47
|
+
gap: var(--spacing-8);
|
|
48
|
+
margin-bottom: 0;
|
|
49
|
+
margin-left: -0.75rem;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.header-expanded,
|
|
53
|
+
:global(.byline-sortable-header-expanded) {
|
|
54
|
+
margin-bottom: var(--spacing-8);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.grip,
|
|
58
|
+
:global(.byline-sortable-grip) {
|
|
59
|
+
display: flex;
|
|
60
|
+
align-items: center;
|
|
61
|
+
justify-content: center;
|
|
62
|
+
padding: 0.25rem;
|
|
63
|
+
border: none;
|
|
64
|
+
background: none;
|
|
65
|
+
border-radius: var(--border-radius-sm);
|
|
66
|
+
color: var(--gray-400);
|
|
67
|
+
cursor: grab;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.grip:hover,
|
|
71
|
+
:global(.byline-sortable-grip):hover {
|
|
72
|
+
background-color: var(--gray-100);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.grip:active,
|
|
76
|
+
:global(.byline-sortable-grip):active {
|
|
77
|
+
cursor: grabbing;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.grip-icon,
|
|
81
|
+
:global(.byline-sortable-grip-icon) {
|
|
82
|
+
width: 1rem;
|
|
83
|
+
height: 1rem;
|
|
84
|
+
color: var(--primary-500);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.label,
|
|
88
|
+
:global(.byline-sortable-label) {
|
|
89
|
+
flex: 1;
|
|
90
|
+
min-width: 0;
|
|
91
|
+
font-size: 1rem;
|
|
92
|
+
font-weight: var(--font-weight-medium);
|
|
93
|
+
overflow: hidden;
|
|
94
|
+
text-overflow: ellipsis;
|
|
95
|
+
white-space: nowrap;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.toggle,
|
|
99
|
+
:global(.byline-sortable-toggle) {
|
|
100
|
+
display: flex;
|
|
101
|
+
align-items: center;
|
|
102
|
+
justify-content: center;
|
|
103
|
+
padding: 0.25rem;
|
|
104
|
+
border: none;
|
|
105
|
+
background: none;
|
|
106
|
+
border-radius: var(--border-radius-sm);
|
|
107
|
+
color: var(--gray-400);
|
|
108
|
+
cursor: pointer;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.toggle:hover,
|
|
112
|
+
:global(.byline-sortable-toggle):hover {
|
|
113
|
+
background-color: var(--gray-800);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.toggle-icon,
|
|
117
|
+
:global(.byline-sortable-toggle-icon) {
|
|
118
|
+
width: 1rem;
|
|
119
|
+
height: 1rem;
|
|
120
|
+
transition: transform 150ms ease;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.toggle-icon-rotated,
|
|
124
|
+
:global(.byline-sortable-toggle-icon-rotated) {
|
|
125
|
+
transform: rotate(180deg);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.content,
|
|
129
|
+
:global(.byline-sortable-content) {
|
|
130
|
+
position: relative;
|
|
131
|
+
display: flex;
|
|
132
|
+
flex-direction: column;
|
|
133
|
+
gap: var(--spacing-16);
|
|
134
|
+
transition: all 200ms ease;
|
|
135
|
+
opacity: 1;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.content-hidden,
|
|
139
|
+
:global(.byline-sortable-content-hidden) {
|
|
140
|
+
max-height: 0;
|
|
141
|
+
opacity: 0;
|
|
142
|
+
z-index: -10;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/* ─── Dark theme variants ───────────────────────────────────── */
|
|
146
|
+
|
|
147
|
+
:is([data-theme="dark"], :global(.dark)) {
|
|
148
|
+
.root,
|
|
149
|
+
:global(.byline-sortable) {
|
|
150
|
+
background-color: var(--canvas-800);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.dragging,
|
|
154
|
+
:global(.byline-sortable-dragging) {
|
|
155
|
+
background-color: oklch(from var(--canvas-700) l c h / 0.3);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.grip:hover,
|
|
159
|
+
:global(.byline-sortable-grip):hover {
|
|
160
|
+
background-color: var(--gray-800);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.grip-icon,
|
|
164
|
+
:global(.byline-sortable-grip-icon) {
|
|
165
|
+
color: var(--primary-200);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
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 { type ReactNode, useState } from 'react'
|
|
10
|
+
|
|
11
|
+
import { useTranslation } from '@byline/i18n/react'
|
|
12
|
+
import { ChevronDownIcon, GripperVerticalIcon, useSortable } from '@byline/ui/react'
|
|
13
|
+
import cx from 'classnames'
|
|
14
|
+
|
|
15
|
+
import { DraggableContextMenu } from './draggable-context-menu'
|
|
16
|
+
import styles from './sortable-item.module.css'
|
|
17
|
+
|
|
18
|
+
export const SortableItem = ({
|
|
19
|
+
id,
|
|
20
|
+
label,
|
|
21
|
+
children,
|
|
22
|
+
onAddBelow,
|
|
23
|
+
onRemove,
|
|
24
|
+
}: {
|
|
25
|
+
id: string
|
|
26
|
+
label: ReactNode
|
|
27
|
+
children: ReactNode
|
|
28
|
+
onAddBelow?: () => void
|
|
29
|
+
onRemove?: () => void
|
|
30
|
+
}) => {
|
|
31
|
+
const { t } = useTranslation('byline-admin')
|
|
32
|
+
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
|
33
|
+
id,
|
|
34
|
+
transition: {
|
|
35
|
+
duration: 250,
|
|
36
|
+
easing: 'cubic-bezier(0, 0.2, 0.2, 1)',
|
|
37
|
+
},
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
const [collapsed, setCollapsed] = useState(false)
|
|
41
|
+
|
|
42
|
+
const style = {
|
|
43
|
+
transform: transform ? `translate3d(${transform.x}px, ${transform.y}px, 0)` : undefined,
|
|
44
|
+
transition,
|
|
45
|
+
zIndex: isDragging ? 10 : 'auto',
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<div
|
|
50
|
+
ref={setNodeRef}
|
|
51
|
+
style={style}
|
|
52
|
+
className={cx(
|
|
53
|
+
'byline-sortable',
|
|
54
|
+
styles.root,
|
|
55
|
+
isDragging && ['byline-sortable-dragging', styles.dragging],
|
|
56
|
+
collapsed && ['byline-sortable-collapsed', styles.collapsed]
|
|
57
|
+
)}
|
|
58
|
+
>
|
|
59
|
+
<div
|
|
60
|
+
className={cx(
|
|
61
|
+
'byline-sortable-header',
|
|
62
|
+
styles.header,
|
|
63
|
+
!collapsed && ['byline-sortable-header-expanded', styles['header-expanded']]
|
|
64
|
+
)}
|
|
65
|
+
>
|
|
66
|
+
<button
|
|
67
|
+
type="button"
|
|
68
|
+
className={cx('byline-sortable-grip', styles.grip)}
|
|
69
|
+
{...attributes}
|
|
70
|
+
{...listeners}
|
|
71
|
+
>
|
|
72
|
+
<GripperVerticalIcon className={cx('byline-sortable-grip-icon', styles['grip-icon'])} />
|
|
73
|
+
</button>
|
|
74
|
+
<div className={cx('byline-sortable-label', styles.label)}>{label}</div>
|
|
75
|
+
<DraggableContextMenu onAddBelow={onAddBelow} onRemove={onRemove} />
|
|
76
|
+
<button
|
|
77
|
+
type="button"
|
|
78
|
+
className={cx('byline-sortable-toggle', styles.toggle)}
|
|
79
|
+
onClick={() => setCollapsed((prev) => !prev)}
|
|
80
|
+
aria-label={
|
|
81
|
+
collapsed
|
|
82
|
+
? t('fields.sortable.expandAriaLabel')
|
|
83
|
+
: t('fields.sortable.collapseAriaLabel')
|
|
84
|
+
}
|
|
85
|
+
>
|
|
86
|
+
<ChevronDownIcon
|
|
87
|
+
className={cx(
|
|
88
|
+
'byline-sortable-toggle-icon',
|
|
89
|
+
styles['toggle-icon'],
|
|
90
|
+
collapsed && ['byline-sortable-toggle-icon-rotated', styles['toggle-icon-rotated']]
|
|
91
|
+
)}
|
|
92
|
+
/>
|
|
93
|
+
</button>
|
|
94
|
+
</div>
|
|
95
|
+
<div
|
|
96
|
+
className={cx(
|
|
97
|
+
'byline-sortable-content',
|
|
98
|
+
styles.content,
|
|
99
|
+
collapsed && ['byline-sortable-content-hidden', styles['content-hidden']]
|
|
100
|
+
)}
|
|
101
|
+
>
|
|
102
|
+
{children}
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
)
|
|
106
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TextField — single-line text input wrapper.
|
|
3
|
+
*
|
|
4
|
+
* Override handles:
|
|
5
|
+
* .byline-field-text — the wrapper div
|
|
6
|
+
* .byline-field-text-label-row — the label + locale-badge row
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
.label-row,
|
|
10
|
+
:global(.byline-field-text-label-row) {
|
|
11
|
+
display: flex;
|
|
12
|
+
align-items: center;
|
|
13
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
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, TextField as FieldType } from '@byline/core'
|
|
12
|
+
import { Input, Label } 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-field.module.css'
|
|
18
|
+
|
|
19
|
+
export const TextField = ({
|
|
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
|
+
// Input'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 Input 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-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
|
+
<Input
|
|
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) => handleChange(e.target.value)}
|
|
131
|
+
error={fieldError != null}
|
|
132
|
+
errorText={fieldError}
|
|
133
|
+
/>
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return (
|
|
138
|
+
<div className={`byline-field-text ${field.name}`}>
|
|
139
|
+
{renderLabel()}
|
|
140
|
+
{BeforeField && <BeforeField {...slotBaseProps} />}
|
|
141
|
+
{renderInput()}
|
|
142
|
+
{AfterField && <AfterField {...slotBaseProps} />}
|
|
143
|
+
{CustomHelpText && <CustomHelpText {...slotBaseProps} helpText={field.helpText} />}
|
|
144
|
+
</div>
|
|
145
|
+
)
|
|
146
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TextAreaField — multi-line text input wrapper.
|
|
3
|
+
*
|
|
4
|
+
* Override handles:
|
|
5
|
+
* .byline-field-text-area — the wrapper div
|
|
6
|
+
* .byline-field-text-area-label-row — the label + locale-badge row
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
.label-row,
|
|
10
|
+
:global(.byline-field-text-area-label-row) {
|
|
11
|
+
display: flex;
|
|
12
|
+
align-items: center;
|
|
13
|
+
}
|
|
@@ -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
|
+
}
|