@cosmicdrift/kumiko-renderer-web 0.193.1 → 0.194.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -4
- package/src/__tests__/create-app.test.tsx +1 -1
- package/src/__tests__/entity-edit-redirect.test.tsx +111 -0
- package/src/__tests__/field-hide-label.test.tsx +166 -1
- package/src/__tests__/kumiko-screen.test.tsx +59 -0
- package/src/__tests__/nav-tree.test.tsx +43 -11
- package/src/__tests__/primitives.test.tsx +33 -2
- package/src/__tests__/render-edit.test.tsx +344 -9
- package/src/__tests__/test-utils.tsx +20 -0
- package/src/__tests__/wizard-form-validation.test.tsx +129 -1
- package/src/app/__tests__/merge-content-editors.test.ts +2 -2
- package/src/app/__tests__/plain-content-editor.test.tsx +4 -0
- package/src/app/__tests__/rich-content-editor.test.tsx +5 -2
- package/src/app/__tests__/tiptap-editor.test.tsx +50 -0
- package/src/app/client-plugin.tsx +3 -3
- package/src/app/create-app.tsx +40 -49
- package/src/app/tiptap-editor.tsx +10 -0
- package/src/layout/__tests__/workspace-switcher.test.tsx +40 -0
- package/src/layout/sidebar-panel.tsx +1 -4
- package/src/layout/workspace-switcher.tsx +20 -13
- package/src/lib/clamp.ts +3 -0
- package/src/primitives/__tests__/data-table-logic.test.ts +15 -3
- package/src/primitives/__tests__/date-field.test.tsx +37 -0
- package/src/primitives/__tests__/date-parse.test.ts +14 -2
- package/src/primitives/__tests__/embedded-list-input.test.tsx +36 -2
- package/src/primitives/__tests__/file-upload.test.tsx +24 -0
- package/src/primitives/date-field.tsx +8 -0
- package/src/primitives/date-parse.ts +6 -1
- package/src/primitives/dropdown-menu.tsx +23 -0
- package/src/primitives/embedded-list-input.tsx +14 -5
- package/src/primitives/file-upload.tsx +3 -0
- package/src/primitives/index.tsx +114 -120
- package/src/primitives/money-input.tsx +4 -7
- package/src/ui/sheet.tsx +3 -1
- package/src/widgets/__tests__/drawer.test.tsx +293 -0
- package/src/widgets/__tests__/infinity-list.test.tsx +38 -0
- package/src/widgets/__tests__/upload-zone.test.tsx +90 -1
- package/src/widgets/__tests__/widgets.test.tsx +25 -0
- package/src/widgets/ai-text-field.tsx +3 -1
- package/src/widgets/drawer.tsx +54 -29
- package/src/widgets/infinity-list.tsx +18 -5
- package/src/widgets/progress-bar.tsx +3 -1
- package/src/widgets/step-bar.tsx +1 -1
- package/src/widgets/upload-zone.tsx +40 -2
|
@@ -18,7 +18,9 @@ export function ProgressBar({
|
|
|
18
18
|
readonly className?: string;
|
|
19
19
|
readonly testId?: string;
|
|
20
20
|
}): ReactNode {
|
|
21
|
-
|
|
21
|
+
// `value` is typically `done / total`; total:0 produces NaN, which would
|
|
22
|
+
// otherwise leak into `width: "NaN%"` and an invalid aria-valuenow.
|
|
23
|
+
const pct = Number.isFinite(value) ? Math.max(0, Math.min(1, value)) : 0;
|
|
22
24
|
return (
|
|
23
25
|
<div className={cn("w-full", className)}>
|
|
24
26
|
<div
|
package/src/widgets/step-bar.tsx
CHANGED
|
@@ -49,7 +49,7 @@ export function StepBar({
|
|
|
49
49
|
{isDone ? (
|
|
50
50
|
<>
|
|
51
51
|
<Check aria-hidden="true" className="size-3.5" />
|
|
52
|
-
<span className="sr-only">{t("kumiko.widget.
|
|
52
|
+
<span className="sr-only">{t("kumiko.widget.step-bar.done")}</span>
|
|
53
53
|
</>
|
|
54
54
|
) : (
|
|
55
55
|
<span className="text-xs font-semibold">{i + 1}</span>
|
|
@@ -45,6 +45,32 @@ function toAcceptAttr(accept?: readonly string[]): string | undefined {
|
|
|
45
45
|
return accept.map((a) => (a.startsWith(".") || a.includes("/") ? a : `.${a}`)).join(",");
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
// `accept` on the native <input> only filters the file-picker dialog — a
|
|
49
|
+
// drag&drop drop is never routed through it, so any file type lands in
|
|
50
|
+
// `onUpload` regardless of what `accept` promises. Same matching rules as
|
|
51
|
+
// the native attribute: MIME type (with an optional "type/*" wildcard) or
|
|
52
|
+
// file extension.
|
|
53
|
+
function matchesAccept(file: File, accept?: readonly string[]): boolean {
|
|
54
|
+
if (accept === undefined || accept.length === 0) return true;
|
|
55
|
+
const name = file.name.toLowerCase();
|
|
56
|
+
const type = file.type.toLowerCase();
|
|
57
|
+
return accept.some((raw) => {
|
|
58
|
+
const a = raw.toLowerCase();
|
|
59
|
+
if (a.includes("/")) return a.endsWith("/*") ? type.startsWith(a.slice(0, -1)) : type === a;
|
|
60
|
+
return name.endsWith(a.startsWith(".") ? a : `.${a}`);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function partitionByAccept(
|
|
65
|
+
files: readonly File[],
|
|
66
|
+
accept: readonly string[] | undefined,
|
|
67
|
+
): readonly [accepted: readonly File[], rejected: readonly File[]] {
|
|
68
|
+
const accepted: File[] = [];
|
|
69
|
+
const rejected: File[] = [];
|
|
70
|
+
for (const file of files) (matchesAccept(file, accept) ? accepted : rejected).push(file);
|
|
71
|
+
return [accepted, rejected];
|
|
72
|
+
}
|
|
73
|
+
|
|
48
74
|
/** Drop zone + multi-file picker with a per-file status row (uploading/done/
|
|
49
75
|
* error). For screens that accept several files and upload them
|
|
50
76
|
* independently — unlike `FileField` (single file, FileRef value inside a
|
|
@@ -72,7 +98,7 @@ export function UploadZone({
|
|
|
72
98
|
await onUpload(await resizeImageBeforeUpload(file));
|
|
73
99
|
setRows((prev) => prev.map((row) => (row.id === rowId ? { ...row, status: "done" } : row)));
|
|
74
100
|
} catch (cause) {
|
|
75
|
-
const message = cause instanceof Error ? cause.message : "
|
|
101
|
+
const message = cause instanceof Error ? cause.message : t("kumiko.widget.upload.error");
|
|
76
102
|
setRows((prev) =>
|
|
77
103
|
prev.map((row) => (row.id === rowId ? { ...row, status: "error", error: message } : row)),
|
|
78
104
|
);
|
|
@@ -82,7 +108,19 @@ export function UploadZone({
|
|
|
82
108
|
async function uploadFiles(files: FileList | null): Promise<void> {
|
|
83
109
|
if (files === null || files.length === 0) return;
|
|
84
110
|
const picked = multiple ? Array.from(files) : files[0] !== undefined ? [files[0]] : [];
|
|
85
|
-
|
|
111
|
+
const [accepted, rejected] = partitionByAccept(picked, accept);
|
|
112
|
+
for (const file of rejected) {
|
|
113
|
+
setRows((prev) => [
|
|
114
|
+
...prev,
|
|
115
|
+
{
|
|
116
|
+
id: crypto.randomUUID(),
|
|
117
|
+
fileName: file.name,
|
|
118
|
+
status: "error",
|
|
119
|
+
error: t("kumiko.widget.upload.rejected-type"),
|
|
120
|
+
},
|
|
121
|
+
]);
|
|
122
|
+
}
|
|
123
|
+
await Promise.all(accepted.map((file) => uploadOne(file)));
|
|
86
124
|
// Reset so re-picking the SAME file still fires change — the browser
|
|
87
125
|
// suppresses the event otherwise.
|
|
88
126
|
if (inputRef.current) inputRef.current.value = "";
|