@panneau/fields 5.0.0-alpha.3 → 5.0.0-alpha.5
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/README.md +67 -20
- package/dist/file/FileField.css +54 -0
- package/dist/file/FileField.d.ts +50 -0
- package/dist/file/FileField.d.ts.map +1 -0
- package/dist/file/FileField.js +198 -0
- package/dist/file/FileField.js.map +1 -0
- package/dist/file/FileField.module.js +22 -0
- package/dist/file/FileField.module.js.map +1 -0
- package/dist/file/index.d.ts +3 -0
- package/dist/file/index.d.ts.map +1 -0
- package/dist/file/index.js +1 -0
- package/dist/index.d.ts +9 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -2
- package/dist/localized/LocalizedField.d.ts.map +1 -1
- package/dist/localized/LocalizedField.js +1 -0
- package/dist/localized/LocalizedField.js.map +1 -1
- package/dist/relation/RelationField.d.ts.map +1 -1
- package/dist/relation/RelationField.js +24 -24
- package/dist/relation/RelationField.js.map +1 -1
- package/dist/shared/DefinitionField.d.ts +3 -1
- package/dist/shared/DefinitionField.d.ts.map +1 -1
- package/dist/shared/DefinitionField.js +80 -72
- package/dist/shared/DefinitionField.js.map +1 -1
- package/dist/shared/builtIn.d.ts +3 -2
- package/dist/shared/builtIn.d.ts.map +1 -1
- package/dist/shared/builtIn.js +5 -2
- package/dist/shared/builtIn.js.map +1 -1
- package/dist/shared/check.d.ts +27 -0
- package/dist/shared/check.d.ts.map +1 -0
- package/dist/shared/check.js +18 -0
- package/dist/shared/check.js.map +1 -0
- package/dist/shared/context.d.ts +15 -2
- package/dist/shared/context.d.ts.map +1 -1
- package/dist/shared/context.js +26 -18
- package/dist/shared/context.js.map +1 -1
- package/dist/shared/entry.d.ts +4 -0
- package/dist/shared/entry.d.ts.map +1 -1
- package/dist/shared/entry.js +12 -1
- package/dist/shared/entry.js.map +1 -1
- package/dist/shared/types.d.ts +2 -0
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/slug/SlugField.css +18 -0
- package/dist/slug/SlugField.d.ts +31 -3
- package/dist/slug/SlugField.d.ts.map +1 -1
- package/dist/slug/SlugField.js +231 -159
- package/dist/slug/SlugField.js.map +1 -1
- package/dist/slug/SlugField.module.js +14 -0
- package/dist/slug/SlugField.module.js.map +1 -0
- package/dist/slug/index.d.ts +1 -1
- package/dist/slug/index.d.ts.map +1 -1
- package/dist/slug/index.js +1 -1
- package/dist/upload/FilePicker.css +24 -0
- package/dist/upload/FilePicker.d.ts +14 -0
- package/dist/upload/FilePicker.d.ts.map +1 -0
- package/dist/upload/FilePicker.js +47 -0
- package/dist/upload/FilePicker.js.map +1 -0
- package/dist/upload/FilePicker.module.js +12 -0
- package/dist/upload/FilePicker.module.js.map +1 -0
- package/dist/upload/UploadList.css +93 -0
- package/dist/upload/UploadList.d.ts +18 -0
- package/dist/upload/UploadList.d.ts.map +1 -0
- package/dist/upload/UploadList.js +144 -0
- package/dist/upload/UploadList.js.map +1 -0
- package/dist/upload/UploadList.module.js +32 -0
- package/dist/upload/UploadList.module.js.map +1 -0
- package/dist/upload/files.d.ts +5 -0
- package/dist/upload/files.d.ts.map +1 -0
- package/dist/upload/files.js +26 -0
- package/dist/upload/files.js.map +1 -0
- package/dist/upload/types.d.ts +58 -0
- package/dist/upload/types.d.ts.map +1 -0
- package/dist/upload/useRejectionMessage.d.ts +4 -0
- package/dist/upload/useRejectionMessage.d.ts.map +1 -0
- package/dist/upload/useRejectionMessage.js +25 -0
- package/dist/upload/useRejectionMessage.js.map +1 -0
- package/dist/upload/useUploads.d.ts +30 -0
- package/dist/upload/useUploads.d.ts.map +1 -0
- package/dist/upload/useUploads.js +115 -0
- package/dist/upload/useUploads.js.map +1 -0
- package/dist/uppy/index.d.ts +3 -0
- package/dist/uppy/index.d.ts.map +1 -0
- package/dist/uppy/index.js +1 -0
- package/dist/uppy/uploader.d.ts +44 -0
- package/dist/uppy/uploader.d.ts.map +1 -0
- package/dist/uppy/uploader.js +139 -0
- package/dist/uppy/uploader.js.map +1 -0
- package/package.json +30 -5
package/dist/slug/SlugField.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import { useFieldsContext } from "../shared/context.js";
|
|
1
2
|
import { getFieldProps, getSetting, isString } from "../shared/entry.js";
|
|
2
3
|
import { messages } from "../shared/messages.js";
|
|
3
4
|
import { TextField } from "../text/TextField.js";
|
|
5
|
+
import SlugField_module_default from "./SlugField.module.js";
|
|
4
6
|
import { c } from "react/compiler-runtime";
|
|
5
|
-
import { IconButton, usePanneauIntl } from "@panneau/ui";
|
|
6
|
-
import { useEffect, useRef } from "react";
|
|
7
|
-
import { jsx } from "react/jsx-runtime";
|
|
7
|
+
import { Button, IconButton, usePanneauIntl } from "@panneau/ui";
|
|
8
|
+
import { useEffect, useRef, useState } from "react";
|
|
9
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
10
|
import { WandSparkles } from "lucide-react";
|
|
9
11
|
//#region src/slug/SlugField.tsx
|
|
10
12
|
/**
|
|
@@ -15,145 +17,180 @@ function slugify(text) {
|
|
|
15
17
|
return text.normalize("NFKD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
16
18
|
}
|
|
17
19
|
/**
|
|
18
|
-
*
|
|
19
|
-
* it is
|
|
20
|
+
* Checks a slug after a pause in typing, and keeps the latest answer for it. A check that
|
|
21
|
+
* fails leaves the field as it is: saving still validates the slug.
|
|
20
22
|
*/
|
|
21
|
-
function
|
|
22
|
-
const $ = c(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
let
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
$[2] = onBlur;
|
|
36
|
-
$[3] = onChange;
|
|
37
|
-
$[4] = props;
|
|
38
|
-
$[5] = readOnly;
|
|
39
|
-
$[6] = source;
|
|
40
|
-
$[7] = sourceLabel;
|
|
41
|
-
$[8] = value;
|
|
42
|
-
} else {
|
|
43
|
-
disabled = $[1];
|
|
44
|
-
onBlur = $[2];
|
|
45
|
-
onChange = $[3];
|
|
46
|
-
props = $[4];
|
|
47
|
-
readOnly = $[5];
|
|
48
|
-
source = $[6];
|
|
49
|
-
sourceLabel = $[7];
|
|
50
|
-
value = $[8];
|
|
51
|
-
}
|
|
52
|
-
const intl = usePanneauIntl();
|
|
53
|
-
const previousSource = useRef(source);
|
|
54
|
-
const editable = disabled !== true && readOnly !== true;
|
|
23
|
+
function useSlugCheck(value, onCheck, delay, key) {
|
|
24
|
+
const $ = c(11);
|
|
25
|
+
const [result, setResult] = useState(null);
|
|
26
|
+
const latest = useRef(onCheck);
|
|
27
|
+
let t0;
|
|
28
|
+
if ($[0] !== onCheck) {
|
|
29
|
+
t0 = () => {
|
|
30
|
+
latest.current = onCheck;
|
|
31
|
+
};
|
|
32
|
+
$[0] = onCheck;
|
|
33
|
+
$[1] = t0;
|
|
34
|
+
} else t0 = $[1];
|
|
35
|
+
useEffect(t0);
|
|
36
|
+
const enabled = onCheck !== null && onCheck !== void 0;
|
|
55
37
|
let t1;
|
|
56
|
-
|
|
57
|
-
if ($[9] !== editable || $[10] !== onChange || $[11] !== source || $[12] !== value) {
|
|
38
|
+
if ($[2] !== delay || $[3] !== enabled || $[4] !== value) {
|
|
58
39
|
t1 = () => {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
40
|
+
if (!enabled || value === "") return;
|
|
41
|
+
const controller = new AbortController();
|
|
42
|
+
const timer = setTimeout(() => {
|
|
43
|
+
const check = latest.current;
|
|
44
|
+
if (check === null || check === void 0) return;
|
|
45
|
+
check(value, { signal: controller.signal }).then((check_0) => {
|
|
46
|
+
if (!controller.signal.aborted) setResult({
|
|
47
|
+
value,
|
|
48
|
+
check: check_0
|
|
49
|
+
});
|
|
50
|
+
}).catch(_temp);
|
|
51
|
+
}, delay);
|
|
52
|
+
return () => {
|
|
53
|
+
clearTimeout(timer);
|
|
54
|
+
controller.abort();
|
|
55
|
+
};
|
|
67
56
|
};
|
|
57
|
+
$[2] = delay;
|
|
58
|
+
$[3] = enabled;
|
|
59
|
+
$[4] = value;
|
|
60
|
+
$[5] = t1;
|
|
61
|
+
} else t1 = $[5];
|
|
62
|
+
let t2;
|
|
63
|
+
if ($[6] !== delay || $[7] !== enabled || $[8] !== key || $[9] !== value) {
|
|
68
64
|
t2 = [
|
|
69
|
-
source,
|
|
70
65
|
value,
|
|
71
|
-
|
|
72
|
-
|
|
66
|
+
enabled,
|
|
67
|
+
delay,
|
|
68
|
+
key
|
|
73
69
|
];
|
|
74
|
-
$[
|
|
75
|
-
$[
|
|
76
|
-
$[
|
|
77
|
-
$[
|
|
78
|
-
$[
|
|
79
|
-
|
|
80
|
-
} else {
|
|
81
|
-
t1 = $[13];
|
|
82
|
-
t2 = $[14];
|
|
83
|
-
}
|
|
70
|
+
$[6] = delay;
|
|
71
|
+
$[7] = enabled;
|
|
72
|
+
$[8] = key;
|
|
73
|
+
$[9] = value;
|
|
74
|
+
$[10] = t2;
|
|
75
|
+
} else t2 = $[10];
|
|
84
76
|
useEffect(t1, t2);
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
77
|
+
return result !== null && result.value === value ? result.check : null;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* A slug for URLs. Typed text is cleaned up on blur. With a `source`, the slug follows it until
|
|
81
|
+
* it is edited, and a button generates it again. With `onCheck`, the backend says whether the
|
|
82
|
+
* slug is taken, and gives its URL.
|
|
83
|
+
*/
|
|
84
|
+
function _temp() {}
|
|
85
|
+
function SlugField({ value, onChange, onBlur, source, sourceLabel, onCheck, checkDelay = 300, checkKey = "", disabled, readOnly, prefix, errors, description, ...props }) {
|
|
86
|
+
const intl = usePanneauIntl();
|
|
87
|
+
const previousSource = useRef(source);
|
|
88
|
+
const check = useSlugCheck(value ?? "", onCheck, checkDelay, checkKey);
|
|
89
|
+
const disabledByBackend = check?.disabled === true;
|
|
90
|
+
const editable = disabled !== true && readOnly !== true && !disabledByBackend;
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
const previous = previousSource.current;
|
|
93
|
+
previousSource.current = source;
|
|
94
|
+
if (!editable || source === previous || source === void 0) return;
|
|
95
|
+
const current = value ?? "";
|
|
96
|
+
if (current === "" || current === slugify(previous ?? "")) {
|
|
97
|
+
const next = slugify(source ?? "");
|
|
98
|
+
if (next !== current) onChange?.(next === "" ? null : next);
|
|
99
|
+
}
|
|
100
|
+
}, [
|
|
101
|
+
source,
|
|
102
|
+
value,
|
|
103
|
+
onChange,
|
|
104
|
+
editable
|
|
105
|
+
]);
|
|
106
|
+
const generated = slugify(source ?? "");
|
|
107
|
+
const taken = check !== null && !check.available;
|
|
108
|
+
const suggestion = taken && isString(check.suggestion) ? check.suggestion : null;
|
|
109
|
+
const url = check !== null && isString(check.url) && !taken ? check.url : null;
|
|
110
|
+
return /* @__PURE__ */ jsx(TextField, {
|
|
111
|
+
autoComplete: "off",
|
|
112
|
+
...props,
|
|
113
|
+
value,
|
|
114
|
+
onChange,
|
|
115
|
+
disabled,
|
|
116
|
+
readOnly: readOnly === true || disabledByBackend,
|
|
117
|
+
...check !== null && isString(check.prefix) ? { prefix: check.prefix } : prefix !== void 0 ? { prefix } : {},
|
|
118
|
+
...errors !== void 0 ? { errors } : {},
|
|
119
|
+
description: taken || url !== null || description !== void 0 ? /* @__PURE__ */ jsxs("span", {
|
|
120
|
+
className: SlugField_module_default.description,
|
|
121
|
+
children: [
|
|
122
|
+
description !== void 0 ? /* @__PURE__ */ jsx("span", { children: description }) : null,
|
|
123
|
+
url !== null ? /* @__PURE__ */ jsx("a", {
|
|
124
|
+
href: url,
|
|
125
|
+
target: "_blank",
|
|
126
|
+
rel: "noreferrer",
|
|
127
|
+
className: SlugField_module_default.url,
|
|
128
|
+
children: url
|
|
129
|
+
}) : null,
|
|
130
|
+
taken ? /* @__PURE__ */ jsxs("span", {
|
|
131
|
+
className: SlugField_module_default.taken,
|
|
132
|
+
role: "status",
|
|
133
|
+
children: [intl.formatMessage(messages.slugTaken), suggestion !== null && editable ? /* @__PURE__ */ jsx(Button, {
|
|
134
|
+
size: "sm",
|
|
135
|
+
appearance: "ghost",
|
|
136
|
+
onPress: () => onChange?.(suggestion),
|
|
137
|
+
children: intl.formatMessage(messages.useSuggestion, { suggestion })
|
|
138
|
+
}) : null]
|
|
139
|
+
}) : null
|
|
140
|
+
]
|
|
141
|
+
}) : void 0,
|
|
142
|
+
onBlur: () => {
|
|
96
143
|
if (value !== null && value !== void 0) {
|
|
97
144
|
const next_0 = slugify(value);
|
|
98
145
|
if (next_0 !== value) onChange?.(next_0 === "" ? null : next_0);
|
|
99
146
|
}
|
|
100
147
|
onBlur?.();
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
$[18] = onChange;
|
|
104
|
-
$[19] = value;
|
|
105
|
-
$[20] = t5;
|
|
106
|
-
} else t5 = $[20];
|
|
107
|
-
let t6;
|
|
108
|
-
if ($[21] !== editable || $[22] !== generated || $[23] !== intl || $[24] !== onChange || $[25] !== source || $[26] !== sourceLabel || $[27] !== value) {
|
|
109
|
-
t6 = source !== void 0 && editable ? /* @__PURE__ */ jsx(IconButton, {
|
|
148
|
+
},
|
|
149
|
+
suffix: source !== void 0 && editable ? /* @__PURE__ */ jsx(IconButton, {
|
|
110
150
|
icon: WandSparkles,
|
|
111
151
|
size: "sm",
|
|
112
152
|
label: intl.formatMessage(messages.generateSlug, { field: sourceLabel ?? "" }),
|
|
113
153
|
isDisabled: generated === "" || generated === value,
|
|
114
154
|
onPress: () => onChange?.(generated === "" ? null : generated)
|
|
115
|
-
}) : void 0
|
|
116
|
-
|
|
117
|
-
$[22] = generated;
|
|
118
|
-
$[23] = intl;
|
|
119
|
-
$[24] = onChange;
|
|
120
|
-
$[25] = source;
|
|
121
|
-
$[26] = sourceLabel;
|
|
122
|
-
$[27] = value;
|
|
123
|
-
$[28] = t6;
|
|
124
|
-
} else t6 = $[28];
|
|
125
|
-
let t7;
|
|
126
|
-
if ($[29] !== disabled || $[30] !== onChange || $[31] !== props || $[32] !== readOnly || $[33] !== t5 || $[34] !== t6 || $[35] !== value) {
|
|
127
|
-
t7 = /* @__PURE__ */ jsx(TextField, {
|
|
128
|
-
autoComplete: "off",
|
|
129
|
-
...props,
|
|
130
|
-
value,
|
|
131
|
-
onChange,
|
|
132
|
-
disabled,
|
|
133
|
-
readOnly,
|
|
134
|
-
onBlur: t5,
|
|
135
|
-
suffix: t6
|
|
136
|
-
});
|
|
137
|
-
$[29] = disabled;
|
|
138
|
-
$[30] = onChange;
|
|
139
|
-
$[31] = props;
|
|
140
|
-
$[32] = readOnly;
|
|
141
|
-
$[33] = t5;
|
|
142
|
-
$[34] = t6;
|
|
143
|
-
$[35] = value;
|
|
144
|
-
$[36] = t7;
|
|
145
|
-
} else t7 = $[36];
|
|
146
|
-
return t7;
|
|
155
|
+
}) : void 0
|
|
156
|
+
});
|
|
147
157
|
}
|
|
148
|
-
|
|
158
|
+
/**
|
|
159
|
+
* The text of a source value: the value itself, or for a localized value, its value in `locale`.
|
|
160
|
+
* Without a locale, the first locale that has a value.
|
|
161
|
+
*/
|
|
162
|
+
function sourceText(value, locale) {
|
|
149
163
|
if (typeof value === "string") return value;
|
|
150
|
-
if (value !== null && typeof value === "object")
|
|
164
|
+
if (value !== null && typeof value === "object") {
|
|
165
|
+
const record = value;
|
|
166
|
+
if (locale !== void 0) {
|
|
167
|
+
const text = record[locale];
|
|
168
|
+
return isString(text) ? text : null;
|
|
169
|
+
}
|
|
170
|
+
return Object.values(record).find(isString) ?? null;
|
|
171
|
+
}
|
|
151
172
|
return null;
|
|
152
173
|
}
|
|
153
|
-
/** The
|
|
174
|
+
/** The form's values that a check depends on: all but the slug itself. */
|
|
175
|
+
function checkKeyOf(values, name) {
|
|
176
|
+
if (values === null) return "";
|
|
177
|
+
const { [name]: _slug, ...others } = values;
|
|
178
|
+
try {
|
|
179
|
+
return JSON.stringify(others);
|
|
180
|
+
} catch {
|
|
181
|
+
return "";
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* The `slug` type: `from` names the field it follows, in the slug's own locale, or in the default
|
|
186
|
+
* locale (the first of the fields context) for a slug that isn't localized. Setting `prefix`
|
|
187
|
+
* shows a URL prefix. With `check`, the fields context's check source checks the slug of a field
|
|
188
|
+
* at the top of the resource, with the form's values.
|
|
189
|
+
*/
|
|
154
190
|
function SlugFieldEntry(props) {
|
|
155
|
-
const $ = c(
|
|
156
|
-
const { field, parentValue } = props;
|
|
191
|
+
const $ = c(33);
|
|
192
|
+
const { field, parentValue, locale, path } = props;
|
|
193
|
+
const { locales, checkSource } = useFieldsContext();
|
|
157
194
|
let t0;
|
|
158
195
|
if ($[0] !== field) {
|
|
159
196
|
t0 = getSetting(field, "prefix", isString);
|
|
@@ -161,56 +198,91 @@ function SlugFieldEntry(props) {
|
|
|
161
198
|
$[1] = t0;
|
|
162
199
|
} else t0 = $[1];
|
|
163
200
|
const prefix = t0;
|
|
201
|
+
const values = parentValue !== null && typeof parentValue === "object" ? parentValue : null;
|
|
164
202
|
let t1;
|
|
165
|
-
if ($[2] !== field.from || $[3] !==
|
|
166
|
-
t1 = field.from !== void 0 &&
|
|
203
|
+
if ($[2] !== field.from || $[3] !== locale || $[4] !== locales || $[5] !== values) {
|
|
204
|
+
t1 = field.from !== void 0 && values !== null ? sourceText(values[field.from], locale ?? locales[0]) : void 0;
|
|
167
205
|
$[2] = field.from;
|
|
168
|
-
$[3] =
|
|
169
|
-
$[4] =
|
|
170
|
-
|
|
206
|
+
$[3] = locale;
|
|
207
|
+
$[4] = locales;
|
|
208
|
+
$[5] = values;
|
|
209
|
+
$[6] = t1;
|
|
210
|
+
} else t1 = $[6];
|
|
171
211
|
const source = t1;
|
|
212
|
+
const topLevel = path === field.name || path === `${field.name}.${locale ?? ""}`;
|
|
172
213
|
let t2;
|
|
173
|
-
if ($[
|
|
174
|
-
t2 =
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
214
|
+
if ($[7] !== checkSource || $[8] !== field.check || $[9] !== field.name || $[10] !== locale || $[11] !== topLevel || $[12] !== values) {
|
|
215
|
+
t2 = field.check === true && checkSource !== null && topLevel ? (value, t3) => {
|
|
216
|
+
const { signal } = t3;
|
|
217
|
+
return checkSource.check(field.name, {
|
|
218
|
+
value,
|
|
219
|
+
...locale !== void 0 ? { locale } : {},
|
|
220
|
+
values: values ?? {}
|
|
221
|
+
}, { signal });
|
|
222
|
+
} : null;
|
|
223
|
+
$[7] = checkSource;
|
|
224
|
+
$[8] = field.check;
|
|
225
|
+
$[9] = field.name;
|
|
226
|
+
$[10] = locale;
|
|
227
|
+
$[11] = topLevel;
|
|
228
|
+
$[12] = values;
|
|
229
|
+
$[13] = t2;
|
|
230
|
+
} else t2 = $[13];
|
|
231
|
+
const onCheck = t2;
|
|
232
|
+
let t3;
|
|
233
|
+
if ($[14] !== props) {
|
|
234
|
+
t3 = getFieldProps(props);
|
|
235
|
+
$[14] = props;
|
|
236
|
+
$[15] = t3;
|
|
237
|
+
} else t3 = $[15];
|
|
238
|
+
const t4 = isString(props.value) ? props.value : null;
|
|
239
|
+
let t5;
|
|
240
|
+
if ($[16] !== field.name || $[17] !== onCheck || $[18] !== values) {
|
|
241
|
+
t5 = onCheck !== null ? checkKeyOf(values, field.name) : "";
|
|
242
|
+
$[16] = field.name;
|
|
243
|
+
$[17] = onCheck;
|
|
244
|
+
$[18] = values;
|
|
245
|
+
$[19] = t5;
|
|
246
|
+
} else t5 = $[19];
|
|
247
|
+
let t6;
|
|
248
|
+
if ($[20] !== field.from || $[21] !== source) {
|
|
249
|
+
t6 = source !== void 0 ? {
|
|
182
250
|
source,
|
|
183
251
|
sourceLabel: field.from ?? ""
|
|
184
252
|
} : {};
|
|
185
|
-
$[
|
|
186
|
-
$[
|
|
187
|
-
$[
|
|
188
|
-
} else
|
|
189
|
-
let
|
|
190
|
-
if ($[
|
|
191
|
-
|
|
192
|
-
$[
|
|
193
|
-
$[
|
|
194
|
-
} else
|
|
195
|
-
let
|
|
196
|
-
if ($[
|
|
197
|
-
|
|
198
|
-
...
|
|
199
|
-
value:
|
|
253
|
+
$[20] = field.from;
|
|
254
|
+
$[21] = source;
|
|
255
|
+
$[22] = t6;
|
|
256
|
+
} else t6 = $[22];
|
|
257
|
+
let t7;
|
|
258
|
+
if ($[23] !== prefix) {
|
|
259
|
+
t7 = prefix !== void 0 ? { prefix } : {};
|
|
260
|
+
$[23] = prefix;
|
|
261
|
+
$[24] = t7;
|
|
262
|
+
} else t7 = $[24];
|
|
263
|
+
let t8;
|
|
264
|
+
if ($[25] !== onCheck || $[26] !== props.onChange || $[27] !== t3 || $[28] !== t4 || $[29] !== t5 || $[30] !== t6 || $[31] !== t7) {
|
|
265
|
+
t8 = /* @__PURE__ */ jsx(SlugField, {
|
|
266
|
+
...t3,
|
|
267
|
+
value: t4,
|
|
200
268
|
onChange: props.onChange,
|
|
201
|
-
|
|
202
|
-
|
|
269
|
+
onCheck,
|
|
270
|
+
checkKey: t5,
|
|
271
|
+
...t6,
|
|
272
|
+
...t7
|
|
203
273
|
});
|
|
204
|
-
$[
|
|
205
|
-
$[
|
|
206
|
-
$[
|
|
207
|
-
$[
|
|
208
|
-
$[
|
|
209
|
-
$[
|
|
210
|
-
|
|
211
|
-
|
|
274
|
+
$[25] = onCheck;
|
|
275
|
+
$[26] = props.onChange;
|
|
276
|
+
$[27] = t3;
|
|
277
|
+
$[28] = t4;
|
|
278
|
+
$[29] = t5;
|
|
279
|
+
$[30] = t6;
|
|
280
|
+
$[31] = t7;
|
|
281
|
+
$[32] = t8;
|
|
282
|
+
} else t8 = $[32];
|
|
283
|
+
return t8;
|
|
212
284
|
}
|
|
213
285
|
//#endregion
|
|
214
|
-
export { SlugField, SlugFieldEntry, slugify };
|
|
286
|
+
export { SlugField, SlugFieldEntry, slugify, sourceText };
|
|
215
287
|
|
|
216
288
|
//# sourceMappingURL=SlugField.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SlugField.js","names":["next","WandSparkles","value","onChange","disabled","readOnly","_c"],"sources":["../../src/slug/SlugField.tsx"],"sourcesContent":["import { WandSparkles } from 'lucide-react';\nimport { useEffect, useRef } from 'react';\n\nimport { IconButton, usePanneauIntl } from '@panneau/ui';\n\nimport { getFieldProps, getSetting, isString } from '../shared/entry';\nimport { messages } from '../shared/messages';\nimport type { FieldEntryProps } from '../shared/types';\nimport { TextField, type TextFieldProps } from '../text/TextField';\n\n/**\n * Turns a text into a slug: lowercase ASCII letters and digits separated by dashes, without\n * accents (`Été à Montréal` gives `ete-a-montreal`).\n */\nexport function slugify(text: string): string {\n return text\n .normalize('NFKD')\n .replace(/[̀-ͯ]/g, '')\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '');\n}\n\nexport interface SlugFieldProps extends Omit<TextFieldProps, 'type' | 'suffix'> {\n /**\n * The text the slug is made from (a title). While the slug is empty or still matches it, the\n * slug follows it.\n */\n source?: string | null;\n /** Name of the source, for the button that generates the slug from it. */\n sourceLabel?: string;\n}\n\n/**\n * A slug for URLs. Typed text is cleaned up on blur. With a `source`, the slug follows it until\n * it is edited, and a button generates it again.\n */\nexport function SlugField({\n value,\n onChange,\n onBlur,\n source,\n sourceLabel,\n disabled,\n readOnly,\n ...props\n}: SlugFieldProps) {\n const intl = usePanneauIntl();\n const previousSource = useRef(source);\n const editable = disabled !== true && readOnly !== true;\n\n useEffect(() => {\n const previous = previousSource.current;\n previousSource.current = source;\n if (!editable || source === previous || source === undefined) {\n return;\n }\n const current = value ?? '';\n if (current === '' || current === slugify(previous ?? '')) {\n const next = slugify(source ?? '');\n if (next !== current) {\n onChange?.(next === '' ? null : next);\n }\n }\n }, [source, value, onChange, editable]);\n\n const generated = slugify(source ?? '');\n return (\n <TextField\n autoComplete=\"off\"\n {...props}\n value={value}\n onChange={onChange}\n disabled={disabled}\n readOnly={readOnly}\n onBlur={() => {\n if (value !== null && value !== undefined) {\n const next = slugify(value);\n if (next !== value) {\n onChange?.(next === '' ? null : next);\n }\n }\n onBlur?.();\n }}\n suffix={\n source !== undefined && editable ? (\n <IconButton\n icon={WandSparkles}\n size=\"sm\"\n label={intl.formatMessage(messages.generateSlug, {\n field: sourceLabel ?? '',\n })}\n isDisabled={generated === '' || generated === value}\n onPress={() => onChange?.(generated === '' ? null : generated)}\n />\n ) : undefined\n }\n />\n );\n}\n\nfunction sourceText(value: unknown): string | null {\n if (typeof value === 'string') {\n return value;\n }\n // A localized source: its first locale.\n if (value !== null && typeof value === 'object') {\n const first = Object.values(value as Record<string, unknown>).find(isString);\n return first ?? null;\n }\n return null;\n}\n\n/** The `slug` type: `from` names the field it follows, and setting `prefix` shows a URL prefix. */\nexport function SlugFieldEntry(props: FieldEntryProps) {\n const { field, parentValue } = props;\n const prefix = getSetting(field, 'prefix', isString);\n const source =\n field.from !== undefined && parentValue !== null && typeof parentValue === 'object'\n ? sourceText((parentValue as Record<string, unknown>)[field.from])\n : undefined;\n return (\n <SlugField\n {...getFieldProps(props)}\n value={isString(props.value) ? props.value : null}\n onChange={props.onChange}\n {...(source !== undefined ? { source, sourceLabel: field.from ?? '' } : {})}\n {...(prefix !== undefined ? { prefix } : {})}\n />\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SlugField.js","names":["check","next","onCheck","_c","styles"],"sources":["../../src/slug/SlugField.tsx"],"sourcesContent":["import { WandSparkles } from 'lucide-react';\nimport { useEffect, useRef, useState } from 'react';\n\nimport type { FieldCheckResponse } from '@panneau/protocol';\nimport { Button, IconButton, usePanneauIntl } from '@panneau/ui';\n\nimport { useFieldsContext } from '../shared/context';\nimport { getFieldProps, getSetting, isString } from '../shared/entry';\nimport { messages } from '../shared/messages';\nimport type { FieldEntryProps } from '../shared/types';\nimport { TextField, type TextFieldProps } from '../text/TextField';\nimport styles from './SlugField.module.css';\n\n/**\n * Turns a text into a slug: lowercase ASCII letters and digits separated by dashes, without\n * accents (`Été à Montréal` gives `ete-a-montreal`).\n */\nexport function slugify(text: string): string {\n return text\n .normalize('NFKD')\n .replace(/[̀-ͯ]/g, '')\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '');\n}\n\n/** Checks a slug with the backend: whether it is available, and its URL. */\nexport type SlugCheck = (\n value: string,\n options: { signal: AbortSignal },\n) => Promise<FieldCheckResponse>;\n\nexport interface SlugFieldProps extends Omit<TextFieldProps, 'type' | 'suffix'> {\n /**\n * The text the slug is made from (a title). While the slug is empty or still matches it, the\n * slug follows it.\n */\n source?: string | null;\n /** Name of the source, for the button that generates the slug from it. */\n sourceLabel?: string;\n /**\n * Checks the slug as it is typed, and once when the field shows: the field then shows the\n * URL the backend gives, and says when the slug is taken, with a free one to use instead.\n */\n onCheck?: SlugCheck | null;\n /** How long typing must pause before a check, in milliseconds. Defaults to 300. */\n checkDelay?: number;\n /**\n * What the check depends on besides the slug, such as the form's values: the slug is checked\n * again when it changes. `onCheck` itself can change on every render.\n */\n checkKey?: string;\n}\n\n/**\n * Checks a slug after a pause in typing, and keeps the latest answer for it. A check that\n * fails leaves the field as it is: saving still validates the slug.\n */\nfunction useSlugCheck(\n value: string,\n onCheck: SlugCheck | null | undefined,\n delay: number,\n key: string,\n): FieldCheckResponse | null {\n const [result, setResult] = useState<{ value: string; check: FieldCheckResponse } | null>(null);\n const latest = useRef(onCheck);\n useEffect(() => {\n latest.current = onCheck;\n });\n const enabled = onCheck !== null && onCheck !== undefined;\n useEffect(() => {\n if (!enabled || value === '') {\n return undefined;\n }\n const controller = new AbortController();\n const timer = setTimeout(() => {\n const check = latest.current;\n if (check === null || check === undefined) {\n return;\n }\n check(value, { signal: controller.signal })\n .then((check) => {\n if (!controller.signal.aborted) {\n setResult({ value, check });\n }\n })\n .catch(() => undefined);\n }, delay);\n return () => {\n clearTimeout(timer);\n controller.abort();\n };\n }, [value, enabled, delay, key]);\n return result !== null && result.value === value ? result.check : null;\n}\n\n/**\n * A slug for URLs. Typed text is cleaned up on blur. With a `source`, the slug follows it until\n * it is edited, and a button generates it again. With `onCheck`, the backend says whether the\n * slug is taken, and gives its URL.\n */\nexport function SlugField({\n value,\n onChange,\n onBlur,\n source,\n sourceLabel,\n onCheck,\n checkDelay = 300,\n checkKey = '',\n disabled,\n readOnly,\n prefix,\n errors,\n description,\n ...props\n}: SlugFieldProps) {\n const intl = usePanneauIntl();\n const previousSource = useRef(source);\n const check = useSlugCheck(value ?? '', onCheck, checkDelay, checkKey);\n const disabledByBackend = check?.disabled === true;\n const editable = disabled !== true && readOnly !== true && !disabledByBackend;\n\n useEffect(() => {\n const previous = previousSource.current;\n previousSource.current = source;\n if (!editable || source === previous || source === undefined) {\n return;\n }\n const current = value ?? '';\n if (current === '' || current === slugify(previous ?? '')) {\n const next = slugify(source ?? '');\n if (next !== current) {\n onChange?.(next === '' ? null : next);\n }\n }\n }, [source, value, onChange, editable]);\n\n const generated = slugify(source ?? '');\n const taken = check !== null && !check.available;\n const suggestion = taken && isString(check.suggestion) ? check.suggestion : null;\n const url = check !== null && isString(check.url) && !taken ? check.url : null;\n return (\n <TextField\n autoComplete=\"off\"\n {...props}\n value={value}\n onChange={onChange}\n disabled={disabled}\n readOnly={readOnly === true || disabledByBackend}\n {...(check !== null && isString(check.prefix)\n ? { prefix: check.prefix }\n : prefix !== undefined\n ? { prefix }\n : {})}\n {...(errors !== undefined ? { errors } : {})}\n description={\n taken || url !== null || description !== undefined ? (\n <span className={styles.description}>\n {description !== undefined ? <span>{description}</span> : null}\n {url !== null ? (\n <a href={url} target=\"_blank\" rel=\"noreferrer\" className={styles.url}>\n {url}\n </a>\n ) : null}\n {taken ? (\n <span className={styles.taken} role=\"status\">\n {intl.formatMessage(messages.slugTaken)}\n {suggestion !== null && editable ? (\n <Button\n size=\"sm\"\n appearance=\"ghost\"\n onPress={() => onChange?.(suggestion)}\n >\n {intl.formatMessage(messages.useSuggestion, {\n suggestion,\n })}\n </Button>\n ) : null}\n </span>\n ) : null}\n </span>\n ) : undefined\n }\n onBlur={() => {\n if (value !== null && value !== undefined) {\n const next = slugify(value);\n if (next !== value) {\n onChange?.(next === '' ? null : next);\n }\n }\n onBlur?.();\n }}\n suffix={\n source !== undefined && editable ? (\n <IconButton\n icon={WandSparkles}\n size=\"sm\"\n label={intl.formatMessage(messages.generateSlug, {\n field: sourceLabel ?? '',\n })}\n isDisabled={generated === '' || generated === value}\n onPress={() => onChange?.(generated === '' ? null : generated)}\n />\n ) : undefined\n }\n />\n );\n}\n\n/**\n * The text of a source value: the value itself, or for a localized value, its value in `locale`.\n * Without a locale, the first locale that has a value.\n */\nexport function sourceText(value: unknown, locale?: string): string | null {\n if (typeof value === 'string') {\n return value;\n }\n if (value !== null && typeof value === 'object') {\n const record = value as Record<string, unknown>;\n if (locale !== undefined) {\n const text = record[locale];\n return isString(text) ? text : null;\n }\n return Object.values(record).find(isString) ?? null;\n }\n return null;\n}\n\n/** The form's values that a check depends on: all but the slug itself. */\nfunction checkKeyOf(values: Record<string, unknown> | null, name: string): string {\n if (values === null) {\n return '';\n }\n const { [name]: _slug, ...others } = values;\n try {\n return JSON.stringify(others);\n } catch {\n return '';\n }\n}\n\n/**\n * The `slug` type: `from` names the field it follows, in the slug's own locale, or in the default\n * locale (the first of the fields context) for a slug that isn't localized. Setting `prefix`\n * shows a URL prefix. With `check`, the fields context's check source checks the slug of a field\n * at the top of the resource, with the form's values.\n */\nexport function SlugFieldEntry(props: FieldEntryProps) {\n const { field, parentValue, locale, path } = props;\n const { locales, checkSource } = useFieldsContext();\n const prefix = getSetting(field, 'prefix', isString);\n const values =\n parentValue !== null && typeof parentValue === 'object'\n ? (parentValue as Record<string, unknown>)\n : null;\n const source =\n field.from !== undefined && values !== null\n ? sourceText(values[field.from], locale ?? locales[0])\n : undefined;\n const topLevel = path === field.name || path === `${field.name}.${locale ?? ''}`;\n const onCheck: SlugCheck | null =\n field.check === true && checkSource !== null && topLevel\n ? (value, { signal }) =>\n checkSource.check(\n field.name,\n {\n value,\n ...(locale !== undefined ? { locale } : {}),\n values: values ?? {},\n },\n { signal },\n )\n : null;\n return (\n <SlugField\n {...getFieldProps(props)}\n value={isString(props.value) ? props.value : null}\n onChange={props.onChange}\n onCheck={onCheck}\n checkKey={onCheck !== null ? checkKeyOf(values, field.name) : ''}\n {...(source !== undefined ? { source, sourceLabel: field.from ?? '' } : {})}\n {...(prefix !== undefined ? { prefix } : {})}\n />\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import "./SlugField.css";
|
|
2
|
+
//#region src/slug/SlugField.module.css
|
|
3
|
+
var description = "panneau-slug-field-description";
|
|
4
|
+
var url = "panneau-slug-field-url";
|
|
5
|
+
var taken = "panneau-slug-field-taken";
|
|
6
|
+
var SlugField_module_default = {
|
|
7
|
+
description,
|
|
8
|
+
url,
|
|
9
|
+
taken
|
|
10
|
+
};
|
|
11
|
+
//#endregion
|
|
12
|
+
export { SlugField_module_default as default, description, taken, url };
|
|
13
|
+
|
|
14
|
+
//# sourceMappingURL=SlugField.module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SlugField.module.js","names":[],"sources":["../../src/slug/SlugField.module.css"],"sourcesContent":[".description {\n display: flex;\n flex-direction: column;\n gap: var(--panneau-space-1);\n}\n\n.url {\n overflow-wrap: anywhere;\n color: var(--panneau-color-link);\n}\n\n.taken {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--panneau-space-2);\n color: var(--panneau-color-warning);\n}\n"],"mappings":""}
|
package/dist/slug/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { SlugField, SlugFieldEntry, type SlugFieldProps, slugify } from './SlugField';
|
|
1
|
+
export { SlugField, SlugFieldEntry, type SlugCheck, type SlugFieldProps, slugify, sourceText, } from './SlugField';
|
|
2
2
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/slug/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/slug/index.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/slug/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,SAAS,EACT,cAAc,EACd,KAAK,SAAS,EACd,KAAK,cAAc,EACnB,OAAO,EACP,UAAU,GACb,MAAM,aAAa,CAAC"}
|
package/dist/slug/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { SlugField, SlugFieldEntry, slugify } from "./SlugField.js";
|
|
1
|
+
export { SlugField, SlugFieldEntry, slugify, sourceText } from "./SlugField.js";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
.panneau-file-picker-zone {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-wrap: wrap;
|
|
4
|
+
align-items: center;
|
|
5
|
+
gap: var(--panneau-space-2);
|
|
6
|
+
padding: var(--panneau-space-3);
|
|
7
|
+
border: var(--panneau-border-width) dashed var(--panneau-color-border-control);
|
|
8
|
+
border-radius: var(--panneau-radius-md);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.panneau-file-picker-zone[data-drop-target] {
|
|
12
|
+
background-color: var(--panneau-color-accent-subtle);
|
|
13
|
+
border-color: var(--panneau-color-accent);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.panneau-file-picker-zone[data-focus-visible] {
|
|
17
|
+
outline: 2px solid var(--panneau-color-focus);
|
|
18
|
+
outline-offset: 2px;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.panneau-file-picker-hint {
|
|
22
|
+
font-size: var(--panneau-font-size-sm);
|
|
23
|
+
color: var(--panneau-color-text-muted);
|
|
24
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface FilePickerProps {
|
|
2
|
+
/** Called with the files chosen or dropped. */
|
|
3
|
+
onSelect: (files: File[]) => void;
|
|
4
|
+
/** Accepted MIME types, which the file dialog offers. */
|
|
5
|
+
accept?: readonly string[] | null;
|
|
6
|
+
multiple?: boolean;
|
|
7
|
+
isDisabled?: boolean;
|
|
8
|
+
/** Text of the button. Defaults to "Choose a file", or "Choose files" with `multiple`. */
|
|
9
|
+
label?: string;
|
|
10
|
+
className?: string;
|
|
11
|
+
}
|
|
12
|
+
/** A button that opens the file dialog, in a zone where files can be dropped. */
|
|
13
|
+
export declare function FilePicker({ onSelect, accept, multiple, isDisabled, label, className, }: FilePickerProps): import("react").JSX.Element;
|
|
14
|
+
//# sourceMappingURL=FilePicker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FilePicker.d.ts","sourceRoot":"","sources":["../../src/upload/FilePicker.tsx"],"names":[],"mappings":"AASA,MAAM,WAAW,eAAe;IAC5B,+CAA+C;IAC/C,QAAQ,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;IAClC,yDAAyD;IACzD,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAAC;IAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,0FAA0F;IAC1F,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AASD,iFAAiF;AACjF,wBAAgB,UAAU,CAAC,EACvB,QAAQ,EACR,MAAM,EACN,QAAgB,EAChB,UAAkB,EAClB,KAAK,EACL,SAAS,GACZ,EAAE,eAAe,+BAmCjB"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { cx } from "../shared/cx.js";
|
|
2
|
+
import { messages } from "../shared/messages.js";
|
|
3
|
+
import FilePicker_module_default from "./FilePicker.module.js";
|
|
4
|
+
import { Button, usePanneauIntl } from "@panneau/ui";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import { Upload } from "lucide-react";
|
|
7
|
+
import { DropZone, FileTrigger } from "react-aria-components";
|
|
8
|
+
//#region src/upload/FilePicker.tsx
|
|
9
|
+
async function readDrop(items) {
|
|
10
|
+
return (await Promise.all(items.map((item) => item.kind === "file" ? item.getFile() : Promise.resolve(null)))).filter((file) => file !== null);
|
|
11
|
+
}
|
|
12
|
+
/** A button that opens the file dialog, in a zone where files can be dropped. */
|
|
13
|
+
function FilePicker({ onSelect, accept, multiple = false, isDisabled = false, label, className }) {
|
|
14
|
+
const intl = usePanneauIntl();
|
|
15
|
+
return /* @__PURE__ */ jsxs(DropZone, {
|
|
16
|
+
className: cx(FilePicker_module_default.zone, className),
|
|
17
|
+
"aria-label": intl.formatMessage(messages.dropZone),
|
|
18
|
+
isDisabled,
|
|
19
|
+
onDrop: (event) => {
|
|
20
|
+
readDrop(event.items).then((files) => {
|
|
21
|
+
if (files.length > 0) onSelect(multiple ? files : files.slice(0, 1));
|
|
22
|
+
});
|
|
23
|
+
},
|
|
24
|
+
children: [/* @__PURE__ */ jsx(FileTrigger, {
|
|
25
|
+
...accept !== null && accept !== void 0 && accept.length > 0 ? { acceptedFileTypes: accept } : {},
|
|
26
|
+
allowsMultiple: multiple,
|
|
27
|
+
onSelect: (list) => {
|
|
28
|
+
const files_0 = list === null ? [] : [...list];
|
|
29
|
+
if (files_0.length > 0) onSelect(files_0);
|
|
30
|
+
},
|
|
31
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
32
|
+
size: "sm",
|
|
33
|
+
appearance: "outline",
|
|
34
|
+
icon: Upload,
|
|
35
|
+
isDisabled,
|
|
36
|
+
children: label ?? intl.formatMessage(multiple ? messages.chooseFiles : messages.chooseFile)
|
|
37
|
+
})
|
|
38
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
39
|
+
className: FilePicker_module_default.hint,
|
|
40
|
+
children: intl.formatMessage(messages.dropHere)
|
|
41
|
+
})]
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
45
|
+
export { FilePicker };
|
|
46
|
+
|
|
47
|
+
//# sourceMappingURL=FilePicker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FilePicker.js","names":["files","styles"],"sources":["../../src/upload/FilePicker.tsx"],"sourcesContent":["import { Upload } from 'lucide-react';\nimport { type DropItem, DropZone, FileTrigger } from 'react-aria-components';\n\nimport { Button, usePanneauIntl } from '@panneau/ui';\n\nimport { cx } from '../shared/cx';\nimport { messages } from '../shared/messages';\nimport styles from './FilePicker.module.css';\n\nexport interface FilePickerProps {\n /** Called with the files chosen or dropped. */\n onSelect: (files: File[]) => void;\n /** Accepted MIME types, which the file dialog offers. */\n accept?: readonly string[] | null;\n multiple?: boolean;\n isDisabled?: boolean;\n /** Text of the button. Defaults to \"Choose a file\", or \"Choose files\" with `multiple`. */\n label?: string;\n className?: string;\n}\n\nasync function readDrop(items: DropItem[]): Promise<File[]> {\n const files = await Promise.all(\n items.map((item) => (item.kind === 'file' ? item.getFile() : Promise.resolve(null))),\n );\n return files.filter((file): file is File => file !== null);\n}\n\n/** A button that opens the file dialog, in a zone where files can be dropped. */\nexport function FilePicker({\n onSelect,\n accept,\n multiple = false,\n isDisabled = false,\n label,\n className,\n}: FilePickerProps) {\n const intl = usePanneauIntl();\n return (\n <DropZone\n className={cx(styles.zone, className)}\n aria-label={intl.formatMessage(messages.dropZone)}\n isDisabled={isDisabled}\n onDrop={(event) => {\n void readDrop(event.items).then((files) => {\n if (files.length > 0) {\n onSelect(multiple ? files : files.slice(0, 1));\n }\n });\n }}\n >\n <FileTrigger\n {...(accept !== null && accept !== undefined && accept.length > 0\n ? { acceptedFileTypes: accept }\n : {})}\n allowsMultiple={multiple}\n onSelect={(list) => {\n const files = list === null ? [] : [...list];\n if (files.length > 0) {\n onSelect(files);\n }\n }}\n >\n <Button size=\"sm\" appearance=\"outline\" icon={Upload} isDisabled={isDisabled}>\n {label ??\n intl.formatMessage(multiple ? messages.chooseFiles : messages.chooseFile)}\n </Button>\n </FileTrigger>\n <span className={styles.hint}>{intl.formatMessage(messages.dropHere)}</span>\n </DropZone>\n );\n}\n"],"mappings":";;;;;;;;AAqBA,eAAe,SAAS,OAAoC;CAIxD,QAAO,MAHa,QAAQ,IACxB,MAAM,KAAK,SAAU,KAAK,SAAS,SAAS,KAAK,QAAQ,IAAI,QAAQ,QAAQ,IAAI,CAAE,CACvF,EAAA,CACa,QAAQ,SAAuB,SAAS,IAAI;AAC7D;;AAGA,SAAgB,WAAW,EACvB,UACA,QACA,WAAW,OACX,aAAa,OACb,OACA,aACgB;CAChB,MAAM,OAAO,eAAe;CAC5B,OACI,qBAAC,UAAD;EACI,WAAW,GAAGC,0BAAO,MAAM,SAAS;EACpC,cAAY,KAAK,cAAc,SAAS,QAAQ;EACpC;EACZ,SAAS,UAAU;GACf,SAAc,MAAM,KAAK,CAAC,CAAC,MAAM,UAAU;IACvC,IAAI,MAAM,SAAS,GACf,SAAS,WAAW,QAAQ,MAAM,MAAM,GAAG,CAAC,CAAC;GAErD,CAAC;EACL;EAVJ,UAAA,CAYI,oBAAC,aAAD;GACI,GAAK,WAAW,QAAQ,WAAW,KAAA,KAAa,OAAO,SAAS,IAC1D,EAAE,mBAAmB,OAAO,IAC5B,CAAC;GACP,gBAAgB;GAChB,WAAW,SAAS;IAChB,MAAMD,UAAQ,SAAS,OAAO,CAAA,IAAK,CAAC,GAAG,IAAI;IAC3C,IAAIA,QAAM,SAAS,GACf,SAASA,OAAK;GAEtB;GAEA,UAAA,oBAAC,QAAD;IAAQ,MAAK;IAAK,YAAW;IAAU,MAAM;IAAoB;IAC5D,UAAA,SACG,KAAK,cAAc,WAAW,SAAS,cAAc,SAAS,UAAU;GACxE,CAAA;EACC,CAAA,GACb,oBAAC,QAAD;GAAM,WAAWC,0BAAO;GAAO,UAAA,KAAK,cAAc,SAAS,QAAQ;EAAQ,CAAA,CACrE;;AAElB"}
|