fluid-primitives 0.19.1 → 1.0.0-beta.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/README.md +9 -0
- package/dist/Client-HrMIqv7d.js +1934 -0
- package/dist/accordion.d.ts +2 -2
- package/dist/accordion.js +25 -29
- package/dist/button.d.ts +52 -0
- package/dist/button.js +135 -0
- package/dist/checkbox-group.d.ts +6 -5
- package/dist/checkbox-group.js +36 -20
- package/dist/{checkbox-group.registry-CGwuF7SF.js → checkbox-group.registry-D8P8A4LY.js} +13 -8
- package/dist/checkbox.d.ts +18 -4
- package/dist/checkbox.js +30 -27
- package/dist/client.d.ts +2 -2
- package/dist/client.js +2 -2
- package/dist/clipboard.d.ts +12 -5
- package/dist/clipboard.js +14 -16
- package/dist/collapsible.d.ts +3 -3
- package/dist/collapsible.js +13 -17
- package/dist/combobox.d.ts +37 -0
- package/dist/combobox.js +151 -0
- package/dist/date-picker.d.ts +67 -0
- package/dist/date-picker.js +289 -0
- package/dist/dialog.d.ts +3 -3
- package/dist/dialog.js +11 -12
- package/dist/field-array.d.ts +97 -0
- package/dist/field-array.js +223 -0
- package/dist/field.d.ts +30 -54
- package/dist/field.js +589 -175
- package/dist/fieldset.d.ts +15 -0
- package/dist/fieldset.js +27 -0
- package/dist/file-upload.d.ts +85 -0
- package/dist/file-upload.js +255 -0
- package/dist/form.d.ts +2 -83
- package/dist/form.fields-B58TZukv.js +329 -0
- package/dist/form.js +263 -276
- package/dist/index-B06gIN_k.d.ts +1358 -0
- package/dist/input.d.ts +96 -0
- package/dist/input.js +233 -0
- package/dist/menu.d.ts +55 -0
- package/dist/menu.js +144 -0
- package/dist/navigation-menu.d.ts +3 -3
- package/dist/navigation-menu.js +29 -43
- package/dist/number-input.d.ts +13 -4
- package/dist/number-input.js +30 -31
- package/dist/popover.d.ts +11 -3
- package/dist/popover.js +17 -18
- package/dist/radio-group.d.ts +4 -4
- package/dist/radio-group.js +35 -42
- package/dist/scroll-area.d.ts +3 -3
- package/dist/scroll-area.js +10 -9
- package/dist/select.d.ts +13 -51
- package/dist/select.js +62 -55
- package/dist/slider.d.ts +27 -0
- package/dist/slider.js +71 -0
- package/dist/switch.d.ts +4 -4
- package/dist/switch.js +24 -33
- package/dist/tabs.d.ts +3 -3
- package/dist/tabs.js +12 -15
- package/dist/textarea.d.ts +101 -0
- package/dist/textarea.js +231 -0
- package/dist/tooltip.d.ts +11 -3
- package/dist/tooltip.js +11 -10
- package/package.json +79 -24
- package/dist/Client-C51NSHxg.js +0 -248
- package/dist/field.dom-CJQXpQbZ.js +0 -11
- package/dist/form.registry-Cb8Pvoe8.d.ts +0 -7
- package/dist/form.registry-CmpTny_s.js +0 -51
- package/dist/index-B8JCdyld.d.ts +0 -118
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { c as Machine, z as Component } from "./index-B06gIN_k.js";
|
|
2
|
+
import * as _$_zag_js_types0 from "@zag-js/types";
|
|
3
|
+
import * as fieldset from "@zag-js/fieldset";
|
|
4
|
+
|
|
5
|
+
//#region Resources/Private/Primitives/Fieldset/Fieldset.d.ts
|
|
6
|
+
declare class Fieldset extends Component<fieldset.Props, fieldset.Api> {
|
|
7
|
+
static componentName: string;
|
|
8
|
+
initMachine(props: fieldset.Props): Machine<any>;
|
|
9
|
+
initApi(): fieldset.Api<_$_zag_js_types0.PropTypes<{
|
|
10
|
+
[x: string]: any;
|
|
11
|
+
}>>;
|
|
12
|
+
render(): void;
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
export { Fieldset };
|
package/dist/fieldset.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { a as normalizeProps, at as Machine, rt as Component } from "./Client-HrMIqv7d.js";
|
|
2
|
+
import * as fieldset from "@zag-js/fieldset";
|
|
3
|
+
|
|
4
|
+
//#region Resources/Private/Primitives/Fieldset/Fieldset.ts
|
|
5
|
+
var Fieldset = class extends Component {
|
|
6
|
+
static componentName = "fieldset";
|
|
7
|
+
initMachine(props) {
|
|
8
|
+
const [machineProps] = fieldset.splitProps(props);
|
|
9
|
+
return new Machine(fieldset.machine, machineProps);
|
|
10
|
+
}
|
|
11
|
+
initApi() {
|
|
12
|
+
return fieldset.connect(this.machine.service, normalizeProps);
|
|
13
|
+
}
|
|
14
|
+
render() {
|
|
15
|
+
const rootEl = this.hydrator.query("root");
|
|
16
|
+
if (rootEl) this.spreadProps(rootEl, this.api.getRootProps());
|
|
17
|
+
const legendEl = this.hydrator.query("legend");
|
|
18
|
+
if (legendEl) this.spreadProps(legendEl, this.api.getLegendProps());
|
|
19
|
+
const helperTextEl = this.hydrator.query("helperText");
|
|
20
|
+
if (helperTextEl) this.spreadProps(helperTextEl, this.api.getHelperTextProps());
|
|
21
|
+
const errorTextEl = this.hydrator.query("errorText");
|
|
22
|
+
if (errorTextEl) this.spreadProps(errorTextEl, this.api.getErrorTextProps());
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
//#endregion
|
|
27
|
+
export { Fieldset };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { X as ConverterMachineProps, b as FieldClientApi, c as Machine, y as FieldAwareComponent } from "./index-B06gIN_k.js";
|
|
2
|
+
import * as _$_zag_js_types0 from "@zag-js/types";
|
|
3
|
+
import * as fileUpload from "@zag-js/file-upload";
|
|
4
|
+
|
|
5
|
+
//#region Resources/Private/Primitives/FileUpload/src/file-upload.translations.d.ts
|
|
6
|
+
/** The translations as PHP ships them: plain strings, `itemPreview` and `deleteFile` with a `%fileName%` placeholder. */
|
|
7
|
+
type FileUploadTranslationTexts = {
|
|
8
|
+
dropzone?: string;
|
|
9
|
+
itemPreview?: string;
|
|
10
|
+
deleteFile?: string;
|
|
11
|
+
};
|
|
12
|
+
//#endregion
|
|
13
|
+
//#region Resources/Private/Primitives/FileUpload/FileUpload.d.ts
|
|
14
|
+
declare const fileUploadPropConverters: {
|
|
15
|
+
accept: (accept: fileUpload.Props["accept"]) => Record<string, string[]> | fileUpload.FileMimeType | fileUpload.FileMimeType[] | undefined;
|
|
16
|
+
};
|
|
17
|
+
declare module 'fluid-primitives' {
|
|
18
|
+
interface HydrationPropsOverrides {
|
|
19
|
+
fileUpload: ConverterMachineProps<typeof fileUploadPropConverters>;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* `existingFilesCount` is a plain integer (not a list of `File` objects) precisely so the already-
|
|
24
|
+
* persisted files it represents never enter the machine's own `acceptedFiles` - anything that does
|
|
25
|
+
* also ends up in the hidden input's `FileList` (via the machine's own `syncInputElement` action)
|
|
26
|
+
* and gets resubmitted on the next form post, which would silently overwrite an already-stored file
|
|
27
|
+
* with placeholder content reconstructed from just its name/size. Subtracting the count from
|
|
28
|
+
* `maxFiles` instead gets the same "remaining slots" UX (and the machine's own `TOO_MANY_FILES`
|
|
29
|
+
* rejection) without ever touching a real `File`.
|
|
30
|
+
*/
|
|
31
|
+
type FileUploadPrimitiveProps = Omit<fileUpload.Props, 'translations'> & {
|
|
32
|
+
existingFilesCount?: number;
|
|
33
|
+
translations?: FileUploadTranslationTexts;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* The stable, per-file identity stamped onto every rendered item's `data-value` (and folded into
|
|
37
|
+
* its `id`) - matches zag-js's own `getFileId` intent (`@zag-js/file-upload`), which does the same
|
|
38
|
+
* thing internally but isn't part of that package's public API (only its main entry and `./anatomy`
|
|
39
|
+
* are exported; the `.dom` module `getFileId` lives in isn't), and returns a hash rather than this
|
|
40
|
+
* plain, readable string. Exported so userland code can go from a rendered item's `data-value` back
|
|
41
|
+
* to the real `File` it represents (e.g. `acceptedFiles.find(f => fileValue(f) === value)`) without
|
|
42
|
+
* reimplementing this exact format - see the FileUpload "Custom Item Layout" docs example.
|
|
43
|
+
*/
|
|
44
|
+
declare function fileValue(file: File): string;
|
|
45
|
+
declare class FileUpload extends FieldAwareComponent<FileUploadPrimitiveProps, fileUpload.Api> {
|
|
46
|
+
static componentName: string;
|
|
47
|
+
private previewCleanups;
|
|
48
|
+
private lastAcceptedFiles;
|
|
49
|
+
private lastRejectedFiles;
|
|
50
|
+
propsWithField(props: FileUploadPrimitiveProps, field: FieldClientApi): FileUploadPrimitiveProps;
|
|
51
|
+
initMachine(props: FileUploadPrimitiveProps): Machine<any>;
|
|
52
|
+
initApi(): fileUpload.Api<_$_zag_js_types0.PropTypes<{
|
|
53
|
+
[x: string]: any;
|
|
54
|
+
}>>;
|
|
55
|
+
render(): void;
|
|
56
|
+
private renderItemGroups;
|
|
57
|
+
/**
|
|
58
|
+
* An `itemGroup` is "empty" only once none of its items - machine-managed or existing - are
|
|
59
|
+
* visible, so an edit form pre-populated with existing files never shows the empty state
|
|
60
|
+
* underneath them.
|
|
61
|
+
*/
|
|
62
|
+
private updateEmptyState;
|
|
63
|
+
/**
|
|
64
|
+
* Existing items carry their own `ui:fileUploadDeleteCheckbox` (unchecked, hidden) as a
|
|
65
|
+
* sibling of their `itemDeleteTrigger` button - not nested inside it, since a native `<button>`
|
|
66
|
+
* may not contain interactive descendants like `<input>`. Clicking the trigger checks that box
|
|
67
|
+
* (so the deletion is applied on the next form submission) and hides the item, mirroring how a
|
|
68
|
+
* machine-managed item disappears on delete. Wiring is idempotent so it's safe to call on every
|
|
69
|
+
* render.
|
|
70
|
+
*/
|
|
71
|
+
private wireExistingItemDeleteTriggers;
|
|
72
|
+
/**
|
|
73
|
+
* `rejectedItemTemplate` is an optional second `ui:template`, letting a consumer author
|
|
74
|
+
* completely different markup for rejected items (e.g. no preview, shown in its own list below
|
|
75
|
+
* the accepted files) instead of reusing `itemTemplate` for both - see the "Custom Item Layout"
|
|
76
|
+
* docs example. Falls back to `itemTemplate` when it isn't present, so existing single-template
|
|
77
|
+
* consumers are unaffected.
|
|
78
|
+
*/
|
|
79
|
+
private resolveItemTemplatePart;
|
|
80
|
+
private renderItems;
|
|
81
|
+
private renderPreview;
|
|
82
|
+
destroy(): void;
|
|
83
|
+
}
|
|
84
|
+
//#endregion
|
|
85
|
+
export { FileUpload, fileValue };
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { K as registerClientPropConverters, a as normalizeProps, at as Machine, n as Template, o as mergeProps, s as FieldAwareComponent } from "./Client-HrMIqv7d.js";
|
|
2
|
+
import * as fileUpload from "@zag-js/file-upload";
|
|
3
|
+
import { isValidFileType } from "@zag-js/file-utils";
|
|
4
|
+
|
|
5
|
+
//#region Resources/Private/Primitives/FileUpload/src/file-upload.translations.ts
|
|
6
|
+
const FILE_NAME_PLACEHOLDER = "%fileName%";
|
|
7
|
+
/**
|
|
8
|
+
* zag-js's `itemPreview`/`deleteFile` translations are functions (`(file: File) => string`) that
|
|
9
|
+
* interpolate the file's name - but Fluid has no callbacks to hand over, and the actual `File` only
|
|
10
|
+
* ever exists in the browser anyway. `FileUploadContext::getTranslations()` therefore translates
|
|
11
|
+
* them as plain strings containing a literal `%fileName%` placeholder (not `{fileName}` - Fluid's
|
|
12
|
+
* own inline array/object syntax already treats a bare `{...}` inside a string as a nested variable
|
|
13
|
+
* expression, so a curly-brace placeholder breaks the moment someone overrides `translations` from a
|
|
14
|
+
* template); this wraps such a string into the function zag-js actually expects, substituting the
|
|
15
|
+
* placeholder per file. An entry that is not a string is left to Zag's default.
|
|
16
|
+
*/
|
|
17
|
+
function resolveTranslations(translations) {
|
|
18
|
+
if (!translations) return void 0;
|
|
19
|
+
const resolved = {};
|
|
20
|
+
for (const [key, text] of Object.entries(translations)) {
|
|
21
|
+
if (typeof text !== "string") continue;
|
|
22
|
+
resolved[key] = key === "itemPreview" || key === "deleteFile" ? (file) => text.replaceAll(FILE_NAME_PLACEHOLDER, file.name) : text;
|
|
23
|
+
}
|
|
24
|
+
return resolved;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region Resources/Private/Primitives/FileUpload/FileUpload.ts
|
|
29
|
+
registerClientPropConverters("fileUpload", { accept: (accept) => accept });
|
|
30
|
+
function resolveMaxFiles(props) {
|
|
31
|
+
const existingFilesCount = props.existingFilesCount ?? 0;
|
|
32
|
+
if (existingFilesCount <= 0 || typeof props.maxFiles !== "number") return props.maxFiles;
|
|
33
|
+
return Math.max(0, props.maxFiles - existingFilesCount);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The stable, per-file identity stamped onto every rendered item's `data-value` (and folded into
|
|
37
|
+
* its `id`) - matches zag-js's own `getFileId` intent (`@zag-js/file-upload`), which does the same
|
|
38
|
+
* thing internally but isn't part of that package's public API (only its main entry and `./anatomy`
|
|
39
|
+
* are exported; the `.dom` module `getFileId` lives in isn't), and returns a hash rather than this
|
|
40
|
+
* plain, readable string. Exported so userland code can go from a rendered item's `data-value` back
|
|
41
|
+
* to the real `File` it represents (e.g. `acceptedFiles.find(f => fileValue(f) === value)`) without
|
|
42
|
+
* reimplementing this exact format - see the FileUpload "Custom Item Layout" docs example.
|
|
43
|
+
*/
|
|
44
|
+
function fileValue(file) {
|
|
45
|
+
return `${file.name}-${file.size}`;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Whether `file` should use the `itemPreview` variant tagged with `pattern` (e.g. `image/*` or
|
|
49
|
+
* `.pdf`). `.*`/`*`/empty are our own catch-all sentinel for a fallback variant - zag-js's own
|
|
50
|
+
* `isValidFileType` (`@zag-js/file-utils`) has no such "match everything" concept (a leading-dot
|
|
51
|
+
* pattern there means a literal file extension), so that case is handled before delegating.
|
|
52
|
+
* Otherwise reuses `isValidFileType`'s own accept-string matching (wildcard MIME types, exact MIME
|
|
53
|
+
* types, and file extensions) instead of re-implementing it.
|
|
54
|
+
*/
|
|
55
|
+
function matchesFileType(pattern, file) {
|
|
56
|
+
if (!pattern || pattern === ".*" || pattern === "*") return true;
|
|
57
|
+
return isValidFileType(file, pattern)[0];
|
|
58
|
+
}
|
|
59
|
+
function fileExtension(fileName) {
|
|
60
|
+
const dotIndex = fileName.lastIndexOf(".");
|
|
61
|
+
return dotIndex === -1 ? "" : fileName.slice(dotIndex + 1).toUpperCase();
|
|
62
|
+
}
|
|
63
|
+
var FileUpload = class extends FieldAwareComponent {
|
|
64
|
+
static componentName = "fileUpload";
|
|
65
|
+
previewCleanups = /* @__PURE__ */ new Map();
|
|
66
|
+
lastAcceptedFiles = null;
|
|
67
|
+
lastRejectedFiles = null;
|
|
68
|
+
propsWithField(props, field) {
|
|
69
|
+
return {
|
|
70
|
+
...props,
|
|
71
|
+
disabled: props.disabled ?? field.disabled,
|
|
72
|
+
readOnly: props.readOnly ?? field.readOnly,
|
|
73
|
+
required: props.required ?? field.required,
|
|
74
|
+
invalid: props.invalid ?? field.invalid,
|
|
75
|
+
name: props.name ?? field.name
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
initMachine(props) {
|
|
79
|
+
props = this.withFieldProps(props);
|
|
80
|
+
const { translations, ...rest } = props;
|
|
81
|
+
const [machineProps] = fileUpload.splitProps(rest);
|
|
82
|
+
return new Machine(fileUpload.machine, {
|
|
83
|
+
...machineProps,
|
|
84
|
+
maxFiles: resolveMaxFiles(props),
|
|
85
|
+
translations: resolveTranslations(translations)
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
initApi() {
|
|
89
|
+
return fileUpload.connect(this.machine.service, normalizeProps);
|
|
90
|
+
}
|
|
91
|
+
render() {
|
|
92
|
+
this.subscribeToFieldService();
|
|
93
|
+
const rootEl = this.hydrator.query("root");
|
|
94
|
+
if (rootEl) this.spreadProps(rootEl, this.api.getRootProps());
|
|
95
|
+
const labelEl = this.hydrator.query("label");
|
|
96
|
+
if (labelEl) this.spreadProps(labelEl, this.api.getLabelProps());
|
|
97
|
+
const fieldDescribedBy = { "aria-describedby": this.field?.ariaDescribedby };
|
|
98
|
+
const dropzoneEl = this.hydrator.query("dropzone");
|
|
99
|
+
if (dropzoneEl) this.spreadProps(dropzoneEl, mergeProps(this.api.getDropzoneProps(), fieldDescribedBy));
|
|
100
|
+
const triggerEl = this.hydrator.query("trigger");
|
|
101
|
+
if (triggerEl) this.spreadProps(triggerEl, mergeProps(this.api.getTriggerProps(), fieldDescribedBy));
|
|
102
|
+
const hiddenInputEl = this.hydrator.query("hiddenInput");
|
|
103
|
+
if (hiddenInputEl) this.spreadProps(hiddenInputEl, this.api.getHiddenInputProps());
|
|
104
|
+
const clearTriggerEl = this.hydrator.query("clearTrigger");
|
|
105
|
+
if (clearTriggerEl) this.spreadProps(clearTriggerEl, this.api.getClearTriggerProps());
|
|
106
|
+
this.renderItemGroups();
|
|
107
|
+
}
|
|
108
|
+
renderItemGroups() {
|
|
109
|
+
const acceptedFiles = this.api.acceptedFiles;
|
|
110
|
+
const rejectedFiles = this.api.rejectedFiles;
|
|
111
|
+
const filesChanged = acceptedFiles !== this.lastAcceptedFiles || rejectedFiles !== this.lastRejectedFiles;
|
|
112
|
+
this.hydrator.queryAll("itemGroup").forEach((itemGroupEl) => {
|
|
113
|
+
const type = itemGroupEl.dataset.value || "accepted";
|
|
114
|
+
this.spreadProps(itemGroupEl, this.api.getItemGroupProps({ type }));
|
|
115
|
+
this.wireExistingItemDeleteTriggers(itemGroupEl);
|
|
116
|
+
if (filesChanged) {
|
|
117
|
+
const entries = type === "rejected" ? rejectedFiles.map(({ file, errors }) => ({
|
|
118
|
+
file,
|
|
119
|
+
type,
|
|
120
|
+
errors
|
|
121
|
+
})) : acceptedFiles.map((file) => ({
|
|
122
|
+
file,
|
|
123
|
+
type
|
|
124
|
+
}));
|
|
125
|
+
this.renderItems(itemGroupEl, entries, type);
|
|
126
|
+
}
|
|
127
|
+
this.updateEmptyState(itemGroupEl);
|
|
128
|
+
});
|
|
129
|
+
this.lastAcceptedFiles = acceptedFiles;
|
|
130
|
+
this.lastRejectedFiles = rejectedFiles;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* An `itemGroup` is "empty" only once none of its items - machine-managed or existing - are
|
|
134
|
+
* visible, so an edit form pre-populated with existing files never shows the empty state
|
|
135
|
+
* underneath them.
|
|
136
|
+
*/
|
|
137
|
+
updateEmptyState(itemGroupEl) {
|
|
138
|
+
const emptyStateEl = this.hydrator.query("emptyState", itemGroupEl);
|
|
139
|
+
if (!emptyStateEl) return;
|
|
140
|
+
emptyStateEl.hidden = this.hydrator.queryAll("item", itemGroupEl).some((el) => !el.hidden);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Existing items carry their own `ui:fileUploadDeleteCheckbox` (unchecked, hidden) as a
|
|
144
|
+
* sibling of their `itemDeleteTrigger` button - not nested inside it, since a native `<button>`
|
|
145
|
+
* may not contain interactive descendants like `<input>`. Clicking the trigger checks that box
|
|
146
|
+
* (so the deletion is applied on the next form submission) and hides the item, mirroring how a
|
|
147
|
+
* machine-managed item disappears on delete. Wiring is idempotent so it's safe to call on every
|
|
148
|
+
* render.
|
|
149
|
+
*/
|
|
150
|
+
wireExistingItemDeleteTriggers(itemGroupEl) {
|
|
151
|
+
this.hydrator.queryAll("item", itemGroupEl).filter((el) => el.dataset.type === "existing" && !el.dataset.fileUploadWired).forEach((itemEl) => {
|
|
152
|
+
itemEl.dataset.fileUploadWired = "true";
|
|
153
|
+
const checkboxEl = itemEl.querySelector("input[type=\"checkbox\"]");
|
|
154
|
+
const deleteTriggerEl = this.hydrator.query("itemDeleteTrigger", itemEl);
|
|
155
|
+
if (!checkboxEl || !deleteTriggerEl) return;
|
|
156
|
+
deleteTriggerEl.addEventListener("click", () => {
|
|
157
|
+
if (this.api.disabled || this.api.readOnly) return;
|
|
158
|
+
checkboxEl.checked = true;
|
|
159
|
+
itemEl.hidden = true;
|
|
160
|
+
this.updateEmptyState(itemGroupEl);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* `rejectedItemTemplate` is an optional second `ui:template`, letting a consumer author
|
|
166
|
+
* completely different markup for rejected items (e.g. no preview, shown in its own list below
|
|
167
|
+
* the accepted files) instead of reusing `itemTemplate` for both - see the "Custom Item Layout"
|
|
168
|
+
* docs example. Falls back to `itemTemplate` when it isn't present, so existing single-template
|
|
169
|
+
* consumers are unaffected.
|
|
170
|
+
*/
|
|
171
|
+
resolveItemTemplatePart(type) {
|
|
172
|
+
if (type === "rejected" && this.hydrator.query("rejectedItemTemplate")) return "rejectedItemTemplate";
|
|
173
|
+
return "itemTemplate";
|
|
174
|
+
}
|
|
175
|
+
renderItems(itemGroupEl, entries, type) {
|
|
176
|
+
this.hydrator.queryAll("item", itemGroupEl).filter((el) => el.dataset.type !== "existing").forEach((el) => {
|
|
177
|
+
this.previewCleanups.get(el)?.();
|
|
178
|
+
this.previewCleanups.delete(el);
|
|
179
|
+
el.remove();
|
|
180
|
+
});
|
|
181
|
+
const templatePart = this.resolveItemTemplatePart(type);
|
|
182
|
+
entries.forEach(({ file, type, errors }) => {
|
|
183
|
+
const instance = new Template(this.hydrator, templatePart, { value: fileValue(file) });
|
|
184
|
+
const itemEl = instance.root;
|
|
185
|
+
this.spreadProps(itemEl, this.api.getItemProps({
|
|
186
|
+
file,
|
|
187
|
+
type
|
|
188
|
+
}));
|
|
189
|
+
const errorEl = instance.query("itemError");
|
|
190
|
+
if (errorEl) {
|
|
191
|
+
errorEl.hidden = !errors?.length;
|
|
192
|
+
errorEl.textContent = errors?.join(", ") ?? "";
|
|
193
|
+
}
|
|
194
|
+
const nameEl = instance.query("itemName");
|
|
195
|
+
if (nameEl) {
|
|
196
|
+
this.spreadProps(nameEl, this.api.getItemNameProps({
|
|
197
|
+
file,
|
|
198
|
+
type
|
|
199
|
+
}));
|
|
200
|
+
nameEl.textContent = file.name;
|
|
201
|
+
}
|
|
202
|
+
const sizeEl = instance.query("itemSizeText");
|
|
203
|
+
if (sizeEl) {
|
|
204
|
+
this.spreadProps(sizeEl, this.api.getItemSizeTextProps({
|
|
205
|
+
file,
|
|
206
|
+
type
|
|
207
|
+
}));
|
|
208
|
+
sizeEl.textContent = this.api.getFileSize(file);
|
|
209
|
+
}
|
|
210
|
+
this.renderPreview(instance, file, type);
|
|
211
|
+
const deleteTriggerEl = instance.query("itemDeleteTrigger");
|
|
212
|
+
if (deleteTriggerEl) this.spreadProps(deleteTriggerEl, this.api.getItemDeleteTriggerProps({
|
|
213
|
+
file,
|
|
214
|
+
type
|
|
215
|
+
}));
|
|
216
|
+
itemGroupEl.appendChild(instance);
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
renderPreview(instance, file, type) {
|
|
220
|
+
const previews = instance.queryAll("itemPreview");
|
|
221
|
+
if (previews.length === 0) return;
|
|
222
|
+
const matched = previews.find((el) => matchesFileType(el.dataset.match, file)) ?? previews[previews.length - 1];
|
|
223
|
+
previews.forEach((el) => {
|
|
224
|
+
if (el !== matched) {
|
|
225
|
+
el.remove();
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
this.spreadProps(el, this.api.getItemPreviewProps({
|
|
229
|
+
file,
|
|
230
|
+
type
|
|
231
|
+
}));
|
|
232
|
+
const fallbackEl = this.hydrator.query("itemPreviewFallback", el);
|
|
233
|
+
if (fallbackEl) fallbackEl.textContent = fileExtension(file.name);
|
|
234
|
+
});
|
|
235
|
+
if (!matched) return;
|
|
236
|
+
const imageEl = this.hydrator.query("itemPreviewImage", matched);
|
|
237
|
+
if (!imageEl || !file.type.startsWith("image/")) return;
|
|
238
|
+
const cleanup = this.api.createFileUrl(file, (url) => {
|
|
239
|
+
this.spreadProps(imageEl, this.api.getItemPreviewImageProps({
|
|
240
|
+
file,
|
|
241
|
+
url,
|
|
242
|
+
type
|
|
243
|
+
}));
|
|
244
|
+
});
|
|
245
|
+
this.previewCleanups.set(matched, cleanup);
|
|
246
|
+
}
|
|
247
|
+
destroy() {
|
|
248
|
+
this.previewCleanups.forEach((cleanup) => cleanup());
|
|
249
|
+
this.previewCleanups.clear();
|
|
250
|
+
super.destroy();
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
//#endregion
|
|
255
|
+
export { FileUpload, fileValue };
|
package/dist/form.d.ts
CHANGED
|
@@ -1,83 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import { EventObject } from "@zag-js/core";
|
|
4
|
-
import * as z from "zod";
|
|
5
|
-
import { JSX, PropTypes } from "@zag-js/types";
|
|
6
|
-
|
|
7
|
-
//#region Resources/Private/Primitives/Form/src/form.types.d.ts
|
|
8
|
-
interface FieldError {
|
|
9
|
-
messages: string[];
|
|
10
|
-
value?: FormDataEntryValue | FormDataEntryValue[] | null;
|
|
11
|
-
}
|
|
12
|
-
type FormErrors = Record<string, FieldError>;
|
|
13
|
-
type FormDirty = Record<string, boolean>;
|
|
14
|
-
type FormTouched = Record<string, boolean>;
|
|
15
|
-
/**
|
|
16
|
-
* Error thrown by post() when server returns 422 validation errors.
|
|
17
|
-
* The machine catches this and transitions to 'invalid' state.
|
|
18
|
-
*/
|
|
19
|
-
declare class ValidationError extends Error {
|
|
20
|
-
errors: FormErrors;
|
|
21
|
-
constructor(errors: FormErrors);
|
|
22
|
-
}
|
|
23
|
-
type ZodFormSchema = z.ZodObject | undefined;
|
|
24
|
-
interface FormProps {
|
|
25
|
-
id: string;
|
|
26
|
-
schema?: ZodFormSchema;
|
|
27
|
-
objectName?: string;
|
|
28
|
-
inputDebounceMs?: number;
|
|
29
|
-
onSubmit?: ({
|
|
30
|
-
formData,
|
|
31
|
-
api,
|
|
32
|
-
event,
|
|
33
|
-
post
|
|
34
|
-
}: {
|
|
35
|
-
formData: FormData;
|
|
36
|
-
api: FormApi;
|
|
37
|
-
event: JSX.FormEvent<HTMLElement>;
|
|
38
|
-
post: (url: string, data: FormData) => Promise<Response>;
|
|
39
|
-
}) => Promise<boolean> | boolean;
|
|
40
|
-
render?: (form: Form) => void;
|
|
41
|
-
}
|
|
42
|
-
interface FormSchema {
|
|
43
|
-
props: FormProps;
|
|
44
|
-
context: {
|
|
45
|
-
values: FormData;
|
|
46
|
-
initialValues: FormData;
|
|
47
|
-
errors: FormErrors;
|
|
48
|
-
dirty: FormDirty;
|
|
49
|
-
touched: FormTouched;
|
|
50
|
-
};
|
|
51
|
-
state: 'invalid' | 'ready' | 'submitting' | 'success' | 'error';
|
|
52
|
-
event: EventObject;
|
|
53
|
-
action: string;
|
|
54
|
-
effect: string;
|
|
55
|
-
}
|
|
56
|
-
interface FormApi {
|
|
57
|
-
isSubmitting: boolean;
|
|
58
|
-
isDirty: boolean;
|
|
59
|
-
isInvalid: boolean;
|
|
60
|
-
isSuccessful: boolean;
|
|
61
|
-
isError: boolean;
|
|
62
|
-
getFormProps(): PropTypes['element'];
|
|
63
|
-
getValues(): FormData;
|
|
64
|
-
getErrors(): FormErrors;
|
|
65
|
-
getDirty(): FormDirty;
|
|
66
|
-
getTouched(): FormTouched;
|
|
67
|
-
_userRenderFn: FormProps['render'];
|
|
68
|
-
getAllFields(): Map<string, FieldMachine>;
|
|
69
|
-
getField(name: string): FieldMachine | undefined;
|
|
70
|
-
getFormEl(): HTMLFormElement | null;
|
|
71
|
-
getAction(): string;
|
|
72
|
-
reset(): void;
|
|
73
|
-
}
|
|
74
|
-
//#endregion
|
|
75
|
-
//#region Resources/Private/Primitives/Form/Form.d.ts
|
|
76
|
-
declare class Form extends Component<FormProps, FormApi> {
|
|
77
|
-
static name: string;
|
|
78
|
-
initMachine(props: FormProps): Machine<FormSchema>;
|
|
79
|
-
initApi(): FormApi;
|
|
80
|
-
render(): void;
|
|
81
|
-
}
|
|
82
|
-
//#endregion
|
|
83
|
-
export { Form, ValidationError };
|
|
1
|
+
import { A as FormValueTree, D as FormSubmitResult, E as FormProps, M as FormValuesObject, N as Form, O as FormValidation, T as FormErrors, j as FormValues, k as FormValueLeaf, w as FormApi } from "./index-B06gIN_k.js";
|
|
2
|
+
export { Form, FormApi, FormErrors, FormProps, FormSubmitResult, FormValidation, FormValueLeaf, FormValueTree, FormValues, FormValuesObject };
|