@byline/ui 2.2.10 → 2.3.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/field-renderer.js +2 -1
- package/dist/fields/file/file-field.d.ts +4 -2
- package/dist/fields/file/file-field.js +182 -81
- package/dist/fields/file/file-field.module.js +10 -5
- package/dist/fields/file/file-field_module.css +99 -32
- package/dist/fields/file/file-upload-field.d.ts +21 -0
- package/dist/fields/file/file-upload-field.js +128 -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/image/image-field.js +33 -17
- package/dist/fields/image/image-field.module.js +1 -0
- package/dist/fields/image/image-field_module.css +23 -10
- package/dist/fields/relation/relation-field.js +37 -24
- package/dist/fields/relation/relation-field.module.js +1 -1
- package/dist/fields/relation/relation-field_module.css +16 -16
- package/dist/forms/form-context.d.ts +11 -0
- package/dist/forms/form-context.js +47 -3
- package/dist/forms/form-renderer.js +5 -3
- package/dist/icons/index.d.ts +1 -0
- package/dist/icons/index.js +1 -0
- package/dist/icons/video-icon.d.ts +6 -0
- package/dist/icons/video-icon.js +36 -0
- package/dist/react.d.ts +1 -0
- package/dist/react.js +1 -0
- package/package.json +5 -4
- package/src/fields/field-renderer.tsx +1 -0
- package/src/fields/file/file-field.module.css +114 -49
- package/src/fields/file/file-field.tsx +220 -56
- package/src/fields/file/file-upload-field.module.css +101 -0
- package/src/fields/file/file-upload-field.tsx +183 -0
- package/src/fields/image/image-field.module.css +27 -13
- package/src/fields/image/image-field.tsx +35 -12
- package/src/fields/relation/relation-field.module.css +21 -21
- package/src/fields/relation/relation-field.tsx +24 -20
- package/src/forms/form-context.tsx +73 -0
- package/src/forms/form-renderer.tsx +9 -2
- package/src/icons/index.ts +1 -0
- package/src/icons/video-icon.tsx +32 -0
- package/src/react.ts +1 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useRef, useState } from "react";
|
|
3
|
+
import { createPendingStoredFileValue } from "@byline/core";
|
|
4
|
+
import classnames from "classnames";
|
|
5
|
+
import { useFormContext } from "../../forms/form-context.js";
|
|
6
|
+
import file_upload_field_module from "./file-upload-field.module.js";
|
|
7
|
+
const FileUploadField = ({ field: _field, collectionPath, fieldPath, onUploaded, accept })=>{
|
|
8
|
+
const inputRef = useRef(null);
|
|
9
|
+
const [status, setStatus] = useState('idle');
|
|
10
|
+
const [errorMessage, setErrorMessage] = useState(null);
|
|
11
|
+
const [isDragOver, setIsDragOver] = useState(false);
|
|
12
|
+
const { addPendingUpload } = useFormContext();
|
|
13
|
+
const handleFileSelected = useCallback((file)=>{
|
|
14
|
+
setStatus('processing');
|
|
15
|
+
setErrorMessage(null);
|
|
16
|
+
const previewUrl = URL.createObjectURL(file);
|
|
17
|
+
const pendingValue = createPendingStoredFileValue(file, previewUrl);
|
|
18
|
+
addPendingUpload(fieldPath, {
|
|
19
|
+
file,
|
|
20
|
+
previewUrl,
|
|
21
|
+
collectionPath
|
|
22
|
+
});
|
|
23
|
+
setStatus('idle');
|
|
24
|
+
onUploaded(pendingValue);
|
|
25
|
+
}, [
|
|
26
|
+
collectionPath,
|
|
27
|
+
fieldPath,
|
|
28
|
+
addPendingUpload,
|
|
29
|
+
onUploaded
|
|
30
|
+
]);
|
|
31
|
+
const handleFileChange = useCallback((e)=>{
|
|
32
|
+
const file = e.target.files?.[0];
|
|
33
|
+
if (file) handleFileSelected(file);
|
|
34
|
+
e.target.value = '';
|
|
35
|
+
}, [
|
|
36
|
+
handleFileSelected
|
|
37
|
+
]);
|
|
38
|
+
const handleBrowseClick = useCallback(()=>{
|
|
39
|
+
inputRef.current?.click();
|
|
40
|
+
}, []);
|
|
41
|
+
const handleDragOver = useCallback((e)=>{
|
|
42
|
+
e.preventDefault();
|
|
43
|
+
setIsDragOver(true);
|
|
44
|
+
}, []);
|
|
45
|
+
const handleDragLeave = useCallback((e)=>{
|
|
46
|
+
e.preventDefault();
|
|
47
|
+
setIsDragOver(false);
|
|
48
|
+
}, []);
|
|
49
|
+
const handleDrop = useCallback((e)=>{
|
|
50
|
+
e.preventDefault();
|
|
51
|
+
setIsDragOver(false);
|
|
52
|
+
const file = e.dataTransfer.files?.[0];
|
|
53
|
+
if (file) handleFileSelected(file);
|
|
54
|
+
}, [
|
|
55
|
+
handleFileSelected
|
|
56
|
+
]);
|
|
57
|
+
const isProcessing = 'processing' === status;
|
|
58
|
+
return /*#__PURE__*/ jsxs("div", {
|
|
59
|
+
className: classnames('byline-field-file-upload', file_upload_field_module.root),
|
|
60
|
+
children: [
|
|
61
|
+
/*#__PURE__*/ jsx("input", {
|
|
62
|
+
ref: inputRef,
|
|
63
|
+
type: "file",
|
|
64
|
+
accept: accept,
|
|
65
|
+
className: classnames('byline-field-file-upload-input', file_upload_field_module.input),
|
|
66
|
+
onChange: handleFileChange,
|
|
67
|
+
disabled: isProcessing,
|
|
68
|
+
"aria-hidden": "true",
|
|
69
|
+
tabIndex: -1
|
|
70
|
+
}),
|
|
71
|
+
/*#__PURE__*/ jsxs("div", {
|
|
72
|
+
role: "button",
|
|
73
|
+
tabIndex: 0,
|
|
74
|
+
"aria-label": "Upload file — drag and drop or click to browse",
|
|
75
|
+
onDragOver: handleDragOver,
|
|
76
|
+
onDragLeave: handleDragLeave,
|
|
77
|
+
onDrop: handleDrop,
|
|
78
|
+
onClick: handleBrowseClick,
|
|
79
|
+
onKeyDown: (e)=>{
|
|
80
|
+
if ('Enter' === e.key || ' ' === e.key) {
|
|
81
|
+
e.preventDefault();
|
|
82
|
+
handleBrowseClick();
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
className: classnames('byline-field-file-upload-zone', file_upload_field_module.zone, isDragOver && !isProcessing && [
|
|
86
|
+
'byline-field-file-upload-zone-active',
|
|
87
|
+
file_upload_field_module["zone-active"]
|
|
88
|
+
], isProcessing && [
|
|
89
|
+
'byline-field-file-upload-zone-busy',
|
|
90
|
+
file_upload_field_module["zone-busy"]
|
|
91
|
+
]),
|
|
92
|
+
children: [
|
|
93
|
+
/*#__PURE__*/ jsx("svg", {
|
|
94
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
95
|
+
className: classnames('byline-field-file-upload-icon', file_upload_field_module.icon),
|
|
96
|
+
fill: "none",
|
|
97
|
+
viewBox: "0 0 24 24",
|
|
98
|
+
stroke: "currentColor",
|
|
99
|
+
strokeWidth: 1.5,
|
|
100
|
+
"aria-hidden": "true",
|
|
101
|
+
children: /*#__PURE__*/ jsx("path", {
|
|
102
|
+
strokeLinecap: "round",
|
|
103
|
+
strokeLinejoin: "round",
|
|
104
|
+
d: "M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5"
|
|
105
|
+
})
|
|
106
|
+
}),
|
|
107
|
+
/*#__PURE__*/ jsxs("span", {
|
|
108
|
+
className: classnames('byline-field-file-upload-label', file_upload_field_module.label),
|
|
109
|
+
children: [
|
|
110
|
+
"Drop file here or",
|
|
111
|
+
' ',
|
|
112
|
+
/*#__PURE__*/ jsx("span", {
|
|
113
|
+
className: classnames('byline-field-file-upload-action', file_upload_field_module.action),
|
|
114
|
+
children: "browse"
|
|
115
|
+
})
|
|
116
|
+
]
|
|
117
|
+
})
|
|
118
|
+
]
|
|
119
|
+
}),
|
|
120
|
+
'error' === status && errorMessage && /*#__PURE__*/ jsx("p", {
|
|
121
|
+
className: classnames('byline-field-file-upload-error', file_upload_field_module.error),
|
|
122
|
+
role: "alert",
|
|
123
|
+
children: errorMessage
|
|
124
|
+
})
|
|
125
|
+
]
|
|
126
|
+
});
|
|
127
|
+
};
|
|
128
|
+
export { FileUploadField };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import "./file-upload-field_module.css";
|
|
2
|
+
const file_upload_field_module = {
|
|
3
|
+
root: "root-Eb8eWY",
|
|
4
|
+
input: "input-xBB8ah",
|
|
5
|
+
zone: "zone-HdQNmA",
|
|
6
|
+
"zone-active": "zone-active-iYZTsU",
|
|
7
|
+
zoneActive: "zone-active-iYZTsU",
|
|
8
|
+
"zone-busy": "zone-busy-GmToil",
|
|
9
|
+
zoneBusy: "zone-busy-GmToil",
|
|
10
|
+
icon: "icon-vXw1Do",
|
|
11
|
+
label: "label-XCy7AX",
|
|
12
|
+
action: "action-LUT6jM",
|
|
13
|
+
error: "error-fFWMW2"
|
|
14
|
+
};
|
|
15
|
+
export default file_upload_field_module;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
:is(.root-Eb8eWY, .byline-field-file-upload) {
|
|
2
|
+
margin-top: .25rem;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
:is(.input-xBB8ah, .byline-field-file-upload-input) {
|
|
6
|
+
clip: rect(0, 0, 0, 0);
|
|
7
|
+
white-space: nowrap;
|
|
8
|
+
border: 0;
|
|
9
|
+
width: 1px;
|
|
10
|
+
height: 1px;
|
|
11
|
+
margin: -1px;
|
|
12
|
+
padding: 0;
|
|
13
|
+
position: absolute;
|
|
14
|
+
overflow: hidden;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
:is(.zone-HdQNmA, .byline-field-file-upload-zone) {
|
|
18
|
+
justify-content: center;
|
|
19
|
+
align-items: center;
|
|
20
|
+
gap: var(--spacing-8);
|
|
21
|
+
border: 2px dashed var(--gray-600);
|
|
22
|
+
border-radius: var(--border-radius-lg);
|
|
23
|
+
color: var(--gray-400);
|
|
24
|
+
text-align: center;
|
|
25
|
+
cursor: pointer;
|
|
26
|
+
-webkit-user-select: none;
|
|
27
|
+
user-select: none;
|
|
28
|
+
flex-direction: column;
|
|
29
|
+
padding: 1.5rem 1rem;
|
|
30
|
+
transition: color .15s, background-color .15s, border-color .15s;
|
|
31
|
+
display: flex;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
:is(.zone-HdQNmA:hover, .byline-field-file-upload-zone:hover) {
|
|
35
|
+
border-color: var(--primary-500);
|
|
36
|
+
background-color: oklch(from var(--primary-900) l c h / .1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
:is(.zone-active-iYZTsU, .byline-field-file-upload-zone-active) {
|
|
40
|
+
border-color: var(--primary-400);
|
|
41
|
+
background-color: oklch(from var(--primary-900) l c h / .2);
|
|
42
|
+
color: var(--primary-300);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
:is(.zone-busy-GmToil, .byline-field-file-upload-zone-busy) {
|
|
46
|
+
border-color: var(--gray-700);
|
|
47
|
+
background-color: oklch(from var(--canvas-800) l c h / .5);
|
|
48
|
+
color: var(--gray-600);
|
|
49
|
+
cursor: not-allowed;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
:is(.icon-vXw1Do, .byline-field-file-upload-icon) {
|
|
53
|
+
opacity: .6;
|
|
54
|
+
width: 1.75rem;
|
|
55
|
+
height: 1.75rem;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
:is(.label-XCy7AX, .byline-field-file-upload-label) {
|
|
59
|
+
font-size: var(--font-size-xs);
|
|
60
|
+
font-weight: var(--font-weight-medium);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
:is(.action-LUT6jM, .byline-field-file-upload-action) {
|
|
64
|
+
color: var(--primary-400);
|
|
65
|
+
text-underline-offset: 2px;
|
|
66
|
+
text-decoration: underline;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
:is(.error-fFWMW2, .byline-field-file-upload-error) {
|
|
70
|
+
color: var(--red-400);
|
|
71
|
+
font-size: var(--font-size-xs);
|
|
72
|
+
margin-top: .375rem;
|
|
73
|
+
}
|
|
74
|
+
|
|
@@ -2,8 +2,10 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useState } from "react";
|
|
3
3
|
import { isPendingStoredFileValue } from "@byline/core";
|
|
4
4
|
import classnames from "classnames";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { IconButton } from "../../components/button/icon-button.js";
|
|
6
|
+
import { useFieldError, useFieldValue, useFormContext, useIsDirty, useIsFieldUploading } from "../../forms/form-context.js";
|
|
7
|
+
import { CloseIcon } from "../../icons/close-icon.js";
|
|
8
|
+
import { ErrorText, HelpText, Label, LoaderRing } from "../../uikit.js";
|
|
7
9
|
import { ImageLightbox } from "../../widgets/image-lightbox/image-lightbox.js";
|
|
8
10
|
import { useFieldChangeHandler } from "../use-field-change-handler.js";
|
|
9
11
|
import image_field_module from "./image-field.module.js";
|
|
@@ -13,6 +15,7 @@ const ImageField = ({ field, collectionPath, value, defaultValue, onChange: _onC
|
|
|
13
15
|
const fieldError = useFieldError(fieldPath);
|
|
14
16
|
const isDirty = useIsDirty(fieldPath);
|
|
15
17
|
const fieldValue = useFieldValue(fieldPath);
|
|
18
|
+
const isUploading = useIsFieldUploading(fieldPath);
|
|
16
19
|
const { removePendingUpload } = useFormContext();
|
|
17
20
|
const handleChange = useFieldChangeHandler(field, fieldPath);
|
|
18
21
|
const incomingValue = isDirty ? fieldValue ?? null : value ?? fieldValue ?? defaultValue ?? null;
|
|
@@ -35,22 +38,14 @@ const ImageField = ({ field, collectionPath, value, defaultValue, onChange: _onC
|
|
|
35
38
|
return /*#__PURE__*/ jsxs("div", {
|
|
36
39
|
className: `byline-field-image ${field.name}`,
|
|
37
40
|
children: [
|
|
38
|
-
/*#__PURE__*/
|
|
41
|
+
/*#__PURE__*/ jsx("div", {
|
|
39
42
|
className: classnames('byline-field-image-header', image_field_module.header),
|
|
40
|
-
children:
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}),
|
|
47
|
-
!showUploadWidget && collectionPath && /*#__PURE__*/ jsx("button", {
|
|
48
|
-
type: "button",
|
|
49
|
-
className: classnames('byline-field-image-remove', image_field_module.remove),
|
|
50
|
-
onClick: handleRemove,
|
|
51
|
-
children: "Remove"
|
|
52
|
-
})
|
|
53
|
-
]
|
|
43
|
+
children: /*#__PURE__*/ jsx(Label, {
|
|
44
|
+
id: htmlId,
|
|
45
|
+
htmlFor: htmlId,
|
|
46
|
+
label: field.label ?? field.name,
|
|
47
|
+
required: !field.optional
|
|
48
|
+
})
|
|
54
49
|
}),
|
|
55
50
|
showUploadWidget ? collectionPath ? /*#__PURE__*/ jsx(ImageUploadField, {
|
|
56
51
|
field: field,
|
|
@@ -65,6 +60,27 @@ const ImageField = ({ field, collectionPath, value, defaultValue, onChange: _onC
|
|
|
65
60
|
}) : /*#__PURE__*/ jsxs("div", {
|
|
66
61
|
className: classnames('byline-field-image-tile', image_field_module.tile),
|
|
67
62
|
children: [
|
|
63
|
+
isUploading && /*#__PURE__*/ jsx("div", {
|
|
64
|
+
className: classnames('byline-field-image-uploading', image_field_module.uploading),
|
|
65
|
+
"aria-live": "polite",
|
|
66
|
+
"aria-busy": "true",
|
|
67
|
+
children: /*#__PURE__*/ jsx(LoaderRing, {})
|
|
68
|
+
}),
|
|
69
|
+
collectionPath && /*#__PURE__*/ jsx("div", {
|
|
70
|
+
className: classnames('byline-field-image-remove', image_field_module.remove),
|
|
71
|
+
children: /*#__PURE__*/ jsx(IconButton, {
|
|
72
|
+
type: "button",
|
|
73
|
+
intent: "noeffect",
|
|
74
|
+
onClick: handleRemove,
|
|
75
|
+
size: "xs",
|
|
76
|
+
disabled: isUploading,
|
|
77
|
+
"aria-label": "Remove image",
|
|
78
|
+
children: /*#__PURE__*/ jsx(CloseIcon, {
|
|
79
|
+
width: "15px",
|
|
80
|
+
height: "15px"
|
|
81
|
+
})
|
|
82
|
+
})
|
|
83
|
+
}),
|
|
68
84
|
previewUrl && /*#__PURE__*/ jsxs("div", {
|
|
69
85
|
className: classnames('byline-field-image-preview-wrap', image_field_module["preview-wrap"]),
|
|
70
86
|
children: [
|
|
@@ -6,18 +6,18 @@
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
:is(.remove-ib7eKx, .byline-field-image-remove) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
border: none;
|
|
14
|
-
padding: 0;
|
|
9
|
+
top: var(--spacing-6);
|
|
10
|
+
right: var(--spacing-6);
|
|
11
|
+
z-index: 1;
|
|
12
|
+
position: absolute;
|
|
15
13
|
}
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
color: var(--
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
.byline-field-image-remove .byline-button {
|
|
16
|
+
color: var(--gray-900);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
:is(.dark .byline-field-image-remove .byline-button, [data-theme="dark"] .byline-field-image-remove .byline-button) {
|
|
20
|
+
color: var(--gray-200);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
:is(.empty-b8pdoJ, .byline-field-image-empty) {
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
border-radius: var(--border-radius-md);
|
|
34
34
|
margin-top: .25rem;
|
|
35
35
|
display: flex;
|
|
36
|
+
position: relative;
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
:is(.preview-wrap-Cjr0PD, .byline-field-image-preview-wrap) {
|
|
@@ -90,7 +91,19 @@
|
|
|
90
91
|
left: .25rem;
|
|
91
92
|
}
|
|
92
93
|
|
|
94
|
+
:is(.uploading-nqh2Gh, .byline-field-image-uploading) {
|
|
95
|
+
z-index: 2;
|
|
96
|
+
background-color: oklch(from var(--gray-950) l c h / .5);
|
|
97
|
+
border-radius: var(--border-radius-md);
|
|
98
|
+
justify-content: center;
|
|
99
|
+
align-items: center;
|
|
100
|
+
display: flex;
|
|
101
|
+
position: absolute;
|
|
102
|
+
inset: 0;
|
|
103
|
+
}
|
|
104
|
+
|
|
93
105
|
:is(.meta-uHyKiu, .byline-field-image-meta) {
|
|
106
|
+
padding-right: var(--spacing-32);
|
|
94
107
|
color: var(--gray-200);
|
|
95
108
|
font-size: var(--font-size-xs);
|
|
96
109
|
flex-direction: column;
|
|
@@ -3,7 +3,9 @@ import { useState } from "react";
|
|
|
3
3
|
import { getCollectionAdminConfig, getCollectionDefinition } from "@byline/core";
|
|
4
4
|
import classnames from "classnames";
|
|
5
5
|
import { useFieldError, useFieldValue } from "../../forms/form-context.js";
|
|
6
|
-
import {
|
|
6
|
+
import { CloseIcon } from "../../icons/close-icon.js";
|
|
7
|
+
import { EditIcon } from "../../icons/edit-icon.js";
|
|
8
|
+
import { Button, ErrorText, IconButton, Label } from "../../uikit.js";
|
|
7
9
|
import relation_field_module from "./relation-field.module.js";
|
|
8
10
|
import { RelationPicker } from "./relation-picker.js";
|
|
9
11
|
import { RelationSummary } from "./relation-summary.js";
|
|
@@ -38,22 +40,14 @@ const RelationField = ({ field, value, defaultValue, onChange, id, path })=>{
|
|
|
38
40
|
return /*#__PURE__*/ jsxs("div", {
|
|
39
41
|
className: `byline-field-relation ${field.name}`,
|
|
40
42
|
children: [
|
|
41
|
-
/*#__PURE__*/
|
|
43
|
+
/*#__PURE__*/ jsx("div", {
|
|
42
44
|
className: classnames('byline-field-relation-header', relation_field_module.header),
|
|
43
|
-
children:
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}),
|
|
50
|
-
incomingValue && !isUnknown && /*#__PURE__*/ jsx("button", {
|
|
51
|
-
type: "button",
|
|
52
|
-
className: classnames('byline-field-relation-remove', relation_field_module.remove),
|
|
53
|
-
onClick: handleRemove,
|
|
54
|
-
children: "Remove"
|
|
55
|
-
})
|
|
56
|
-
]
|
|
45
|
+
children: /*#__PURE__*/ jsx(Label, {
|
|
46
|
+
id: `${htmlId}-label`,
|
|
47
|
+
htmlFor: htmlId,
|
|
48
|
+
label: field.label ?? field.name,
|
|
49
|
+
required: !field.optional
|
|
50
|
+
})
|
|
57
51
|
}),
|
|
58
52
|
field.helpText && /*#__PURE__*/ jsx("div", {
|
|
59
53
|
className: classnames('byline-field-relation-help', relation_field_module.help),
|
|
@@ -92,14 +86,33 @@ const RelationField = ({ field, value, defaultValue, onChange, id, path })=>{
|
|
|
92
86
|
value: incomingValue,
|
|
93
87
|
cachedRecord: cachedRecord
|
|
94
88
|
}),
|
|
95
|
-
/*#__PURE__*/
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
89
|
+
/*#__PURE__*/ jsxs("div", {
|
|
90
|
+
className: classnames('byline-field-relation-actions', relation_field_module.actions),
|
|
91
|
+
children: [
|
|
92
|
+
/*#__PURE__*/ jsx(IconButton, {
|
|
93
|
+
id: htmlId,
|
|
94
|
+
type: "button",
|
|
95
|
+
intent: "noeffect",
|
|
96
|
+
size: "xs",
|
|
97
|
+
"aria-label": `Change ${targetDef.labels.singular}`,
|
|
98
|
+
onClick: ()=>setPickerOpen(true),
|
|
99
|
+
children: /*#__PURE__*/ jsx(EditIcon, {
|
|
100
|
+
width: "15px",
|
|
101
|
+
height: "15px"
|
|
102
|
+
})
|
|
103
|
+
}),
|
|
104
|
+
/*#__PURE__*/ jsx(IconButton, {
|
|
105
|
+
type: "button",
|
|
106
|
+
intent: "noeffect",
|
|
107
|
+
size: "xs",
|
|
108
|
+
"aria-label": `Remove ${targetDef.labels.singular}`,
|
|
109
|
+
onClick: handleRemove,
|
|
110
|
+
children: /*#__PURE__*/ jsx(CloseIcon, {
|
|
111
|
+
width: "15px",
|
|
112
|
+
height: "15px"
|
|
113
|
+
})
|
|
114
|
+
})
|
|
115
|
+
]
|
|
103
116
|
})
|
|
104
117
|
]
|
|
105
118
|
}) : /*#__PURE__*/ jsxs(Button, {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import "./relation-field_module.css";
|
|
2
2
|
const relation_field_module = {
|
|
3
3
|
header: "header-y7lSnb",
|
|
4
|
-
remove: "remove-po_DBR",
|
|
5
4
|
help: "help-DEGVPH",
|
|
6
5
|
"error-tile": "error-tile-y2kiiq",
|
|
7
6
|
errorTile: "error-tile-y2kiiq",
|
|
8
7
|
"error-text": "error-text-jHQh34",
|
|
9
8
|
errorText: "error-text-jHQh34",
|
|
10
9
|
tile: "tile-Y3_yre",
|
|
10
|
+
actions: "actions-Nov8hS",
|
|
11
11
|
mono: "mono-y8Xo6b"
|
|
12
12
|
};
|
|
13
13
|
export default relation_field_module;
|
|
@@ -5,21 +5,6 @@
|
|
|
5
5
|
display: flex;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
:is(.remove-po_DBR, .byline-field-relation-remove) {
|
|
9
|
-
color: var(--red-500);
|
|
10
|
-
font-size: var(--font-size-xs);
|
|
11
|
-
cursor: pointer;
|
|
12
|
-
background: none;
|
|
13
|
-
border: none;
|
|
14
|
-
padding: 0;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
:is(.remove-po_DBR:hover, .byline-field-relation-remove:hover) {
|
|
18
|
-
color: var(--red-400);
|
|
19
|
-
text-underline-offset: 2px;
|
|
20
|
-
text-decoration: underline;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
8
|
:is(.help-DEGVPH, .byline-field-relation-help) {
|
|
24
9
|
color: var(--gray-400);
|
|
25
10
|
font-size: var(--font-size-xs);
|
|
@@ -45,7 +30,7 @@
|
|
|
45
30
|
|
|
46
31
|
:is(.tile-Y3_yre, .byline-field-relation-tile) {
|
|
47
32
|
justify-content: space-between;
|
|
48
|
-
align-items:
|
|
33
|
+
align-items: flex-start;
|
|
49
34
|
gap: var(--spacing-8);
|
|
50
35
|
padding: var(--spacing-8);
|
|
51
36
|
border: var(--border-width-thin) var(--border-style-solid) var(--primary-500);
|
|
@@ -56,6 +41,21 @@
|
|
|
56
41
|
display: flex;
|
|
57
42
|
}
|
|
58
43
|
|
|
44
|
+
:is(.actions-Nov8hS, .byline-field-relation-actions) {
|
|
45
|
+
align-items: center;
|
|
46
|
+
gap: var(--spacing-4);
|
|
47
|
+
flex-shrink: 0;
|
|
48
|
+
display: flex;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.byline-field-relation-actions .byline-button {
|
|
52
|
+
color: var(--gray-900);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
:is(.dark .byline-field-relation-actions .byline-button, [data-theme="dark"] .byline-field-relation-actions .byline-button) {
|
|
56
|
+
color: var(--gray-200);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
59
|
:is(.mono-y8Xo6b, .byline-field-relation-mono) {
|
|
60
60
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
61
61
|
}
|
|
@@ -28,6 +28,7 @@ type FieldListener = (value: any) => void;
|
|
|
28
28
|
type ErrorsListener = (errors: FormError[]) => void;
|
|
29
29
|
type MetaListener = () => void;
|
|
30
30
|
type SystemPathListener = (value: string | null) => void;
|
|
31
|
+
type FieldUploadingListener = (uploading: boolean) => void;
|
|
31
32
|
interface FormContextType {
|
|
32
33
|
setFieldValue: (name: string, value: any) => void;
|
|
33
34
|
setFieldStore: (name: string, value: any) => void;
|
|
@@ -54,6 +55,9 @@ interface FormContextType {
|
|
|
54
55
|
getPendingUploads: () => Map<string, PendingUpload>;
|
|
55
56
|
hasPendingUploads: () => boolean;
|
|
56
57
|
clearPendingUploads: () => void;
|
|
58
|
+
setFieldUploading: (fieldPath: string, uploading: boolean) => void;
|
|
59
|
+
getIsFieldUploading: (fieldPath: string) => boolean;
|
|
60
|
+
subscribeFieldUploading: (fieldPath: string, listener: FieldUploadingListener) => () => void;
|
|
57
61
|
getSystemPath: () => string | null;
|
|
58
62
|
setSystemPath: (value: string | null) => void;
|
|
59
63
|
subscribeSystemPath: (listener: SystemPathListener) => () => void;
|
|
@@ -75,4 +79,11 @@ export declare const useFormMeta: () => {
|
|
|
75
79
|
};
|
|
76
80
|
export declare const useIsDirty: (name: string) => boolean;
|
|
77
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;
|
|
78
89
|
export {};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
import { createContext, useCallback, useContext, useEffect, useRef, useState } from "react";
|
|
4
4
|
import { normalizeHooks } from "@byline/core";
|
|
5
|
-
import { get, set } from "lodash-es";
|
|
5
|
+
import { get, set as external_lodash_es_set } from "lodash-es";
|
|
6
6
|
const FormContext = /*#__PURE__*/ createContext(null);
|
|
7
7
|
const useFormContext = ()=>{
|
|
8
8
|
const context = useContext(FormContext);
|
|
@@ -16,6 +16,8 @@ const FormProvider = ({ children, initialData = {} })=>{
|
|
|
16
16
|
const dirtyFields = useRef(new Set());
|
|
17
17
|
const patchesRef = useRef([]);
|
|
18
18
|
const pendingUploadsRef = useRef(new Map());
|
|
19
|
+
const uploadingFieldsRef = useRef(new Set());
|
|
20
|
+
const uploadingListenersRef = useRef(new Map());
|
|
19
21
|
const fieldListeners = useRef(new Map());
|
|
20
22
|
const errorListeners = useRef(new Set());
|
|
21
23
|
const metaListeners = useRef(new Set());
|
|
@@ -65,7 +67,7 @@ const FormProvider = ({ children, initialData = {} })=>{
|
|
|
65
67
|
const newFieldValues = {
|
|
66
68
|
...fieldValues.current
|
|
67
69
|
};
|
|
68
|
-
|
|
70
|
+
external_lodash_es_set(newFieldValues, name, value);
|
|
69
71
|
fieldValues.current = newFieldValues;
|
|
70
72
|
dirtyFields.current.add(name);
|
|
71
73
|
notifyFieldListeners(name, value);
|
|
@@ -180,6 +182,34 @@ const FormProvider = ({ children, initialData = {} })=>{
|
|
|
180
182
|
for (const upload of pendingUploadsRef.current.values())URL.revokeObjectURL(upload.previewUrl);
|
|
181
183
|
pendingUploadsRef.current.clear();
|
|
182
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
|
+
}, []);
|
|
183
213
|
useEffect(()=>()=>{
|
|
184
214
|
for (const upload of pendingUploadsRef.current.values())URL.revokeObjectURL(upload.previewUrl);
|
|
185
215
|
}, []);
|
|
@@ -343,6 +373,9 @@ const FormProvider = ({ children, initialData = {} })=>{
|
|
|
343
373
|
getPendingUploads,
|
|
344
374
|
hasPendingUploads,
|
|
345
375
|
clearPendingUploads,
|
|
376
|
+
setFieldUploading,
|
|
377
|
+
getIsFieldUploading,
|
|
378
|
+
subscribeFieldUploading,
|
|
346
379
|
getSystemPath,
|
|
347
380
|
setSystemPath,
|
|
348
381
|
subscribeSystemPath
|
|
@@ -419,4 +452,15 @@ const useFieldValue = (name)=>{
|
|
|
419
452
|
]);
|
|
420
453
|
return value;
|
|
421
454
|
};
|
|
422
|
-
|
|
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 };
|
|
@@ -14,7 +14,7 @@ import { FormProvider, useFieldValue, useFormContext } from "./form-context.js";
|
|
|
14
14
|
import form_renderer_module from "./form-renderer.module.js";
|
|
15
15
|
import { useNavigationGuardAdapter } from "./navigation-guard.js";
|
|
16
16
|
import { PathWidget } from "./path-widget.js";
|
|
17
|
-
import {
|
|
17
|
+
import { executeUploadsWithProgress } from "./upload-executor.js";
|
|
18
18
|
const FormStatusDisplay = ({ initialData, workflowStatuses, publishedVersion, onUnpublish })=>{
|
|
19
19
|
const statusCode = initialData?.status;
|
|
20
20
|
const statusLabel = workflowStatuses?.find((s)=>s.name === statusCode)?.label ?? statusCode;
|
|
@@ -139,7 +139,7 @@ function computeStatusTransitions(currentStatus, workflowStatuses, nextStatus) {
|
|
|
139
139
|
};
|
|
140
140
|
}
|
|
141
141
|
const FormContent = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpublish, onDelete, onDuplicate, onCopyToLocale, contentLocales, nextStatus, workflowStatuses, publishedVersion, initialData, adminConfig, useAsTitle, useAsPath, headingLabel, headerSlot, collectionPath, initialLocale, onLocaleChange, defaultLocale = 'en', useNavigationGuard: useNavigationGuardProp, restoreWarnings, _activeTabBySet, _onTabChange })=>{
|
|
142
|
-
const { getFieldValues, runFieldHooks, validateForm, errors: initialErrors, hasChanges: hasChangesFn, resetHasChanges, getPatches, getSystemPath, subscribeErrors, subscribeMeta, setFieldValue, setFieldError, getPendingUploads, clearPendingUploads } = useFormContext();
|
|
142
|
+
const { getFieldValues, runFieldHooks, validateForm, errors: initialErrors, hasChanges: hasChangesFn, resetHasChanges, getPatches, getSystemPath, subscribeErrors, subscribeMeta, setFieldValue, setFieldError, getPendingUploads, clearPendingUploads, setFieldUploading } = useFormContext();
|
|
143
143
|
const [errors, setErrors] = useState(initialErrors);
|
|
144
144
|
const [hasChanges, setHasChanges] = useState(hasChangesFn());
|
|
145
145
|
const [statusBusy, setStatusBusy] = useState(false);
|
|
@@ -273,7 +273,9 @@ const FormContent = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpub
|
|
|
273
273
|
if (pendingUploads.size > 0) {
|
|
274
274
|
setIsUploading(true);
|
|
275
275
|
try {
|
|
276
|
-
const uploadResult = await
|
|
276
|
+
const uploadResult = await executeUploadsWithProgress(pendingUploads, uploadField, ({ fieldPath, status })=>{
|
|
277
|
+
setFieldUploading(fieldPath, 'uploading' === status);
|
|
278
|
+
});
|
|
277
279
|
if (!uploadResult.allSucceeded) {
|
|
278
280
|
for (const [fieldPath, errorMessage] of uploadResult.errors.entries())setFieldError(fieldPath, `Upload failed: ${errorMessage}`);
|
|
279
281
|
console.error('One or more uploads failed:', uploadResult.errors);
|