@vantageos/mosaic 0.2.2 → 0.3.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/CHANGELOG.md +145 -0
- package/README.md +193 -0
- package/dist/Alert.schema-LPJE5gFP.d.cts +71 -0
- package/dist/Alert.schema-LPJE5gFP.d.ts +71 -0
- package/dist/ConfirmDialog-B3CQQGmJ.d.cts +34 -0
- package/dist/ConfirmDialog-B3CQQGmJ.d.ts +34 -0
- package/dist/MarkdownRenderer.schema-Rx7kIeVA.d.cts +22 -0
- package/dist/MarkdownRenderer.schema-Rx7kIeVA.d.ts +22 -0
- package/dist/ProgressBar.schema-CdVYx0Wq.d.cts +22 -0
- package/dist/ProgressBar.schema-CdVYx0Wq.d.ts +22 -0
- package/dist/StatusBadge.schema-1tOjB4Ir.d.cts +22 -0
- package/dist/StatusBadge.schema-1tOjB4Ir.d.ts +22 -0
- package/dist/Tabs.schema-BzcEyeq8.d.cts +79 -0
- package/dist/Tabs.schema-BzcEyeq8.d.ts +79 -0
- package/dist/artifacts.cjs +144 -2
- package/dist/artifacts.cjs.map +1 -1
- package/dist/artifacts.d.cts +3 -21
- package/dist/artifacts.d.ts +3 -21
- package/dist/artifacts.js +144 -2
- package/dist/artifacts.js.map +1 -1
- package/dist/confirmation.cjs +168 -2
- package/dist/confirmation.cjs.map +1 -1
- package/dist/confirmation.d.cts +24 -20
- package/dist/confirmation.d.ts +24 -20
- package/dist/confirmation.js +165 -3
- package/dist/confirmation.js.map +1 -1
- package/dist/display.cjs +272 -2
- package/dist/display.cjs.map +1 -1
- package/dist/display.d.cts +132 -1
- package/dist/display.d.ts +132 -1
- package/dist/display.js +267 -3
- package/dist/display.js.map +1 -1
- package/dist/forms.cjs +1415 -0
- package/dist/forms.cjs.map +1 -0
- package/dist/forms.d.cts +159 -0
- package/dist/forms.d.ts +159 -0
- package/dist/forms.js +1334 -0
- package/dist/forms.js.map +1 -0
- package/dist/index-CwXVgtGb.d.cts +917 -0
- package/dist/index-CwXVgtGb.d.ts +917 -0
- package/dist/index.cjs +315 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -6
- package/dist/index.d.ts +11 -6
- package/dist/index.js +304 -3
- package/dist/index.js.map +1 -1
- package/dist/input.cjs +19 -0
- package/dist/input.cjs.map +1 -1
- package/dist/input.d.cts +4 -34
- package/dist/input.d.ts +4 -34
- package/dist/input.js +18 -1
- package/dist/input.js.map +1 -1
- package/dist/media.cjs +144 -2
- package/dist/media.cjs.map +1 -1
- package/dist/media.d.cts +3 -21
- package/dist/media.d.ts +3 -21
- package/dist/media.js +144 -2
- package/dist/media.js.map +1 -1
- package/dist/preact/artifacts.d.ts +39 -1
- package/dist/preact/artifacts.js +234 -0
- package/dist/preact/artifacts.js.map +1 -1
- package/dist/preact/confirmation.d.ts +115 -1
- package/dist/preact/confirmation.js +503 -0
- package/dist/preact/confirmation.js.map +1 -1
- package/dist/preact/display.d.ts +366 -1
- package/dist/preact/display.js +774 -0
- package/dist/preact/display.js.map +1 -1
- package/dist/preact/forms.d.ts +875 -0
- package/dist/preact/forms.js +1326 -0
- package/dist/preact/forms.js.map +1 -0
- package/dist/preact/input.d.ts +88 -1
- package/dist/preact/input.js +194 -0
- package/dist/preact/input.js.map +1 -1
- package/dist/preact/progress.d.ts +35 -1
- package/dist/preact/progress.js +263 -0
- package/dist/preact/progress.js.map +1 -1
- package/dist/progress.cjs +144 -2
- package/dist/progress.cjs.map +1 -1
- package/dist/progress.d.cts +3 -21
- package/dist/progress.d.ts +3 -21
- package/dist/progress.js +144 -2
- package/dist/progress.js.map +1 -1
- package/dist/react/artifacts.cjs +241 -0
- package/dist/react/artifacts.cjs.map +1 -1
- package/dist/react/artifacts.d.cts +20 -1
- package/dist/react/artifacts.d.ts +20 -1
- package/dist/react/artifacts.js +234 -0
- package/dist/react/artifacts.js.map +1 -1
- package/dist/react/confirmation.cjs +513 -0
- package/dist/react/confirmation.cjs.map +1 -1
- package/dist/react/confirmation.d.cts +21 -1
- package/dist/react/confirmation.d.ts +21 -1
- package/dist/react/confirmation.js +503 -0
- package/dist/react/confirmation.js.map +1 -1
- package/dist/react/display.cjs +788 -0
- package/dist/react/display.cjs.map +1 -1
- package/dist/react/display.d.cts +129 -1
- package/dist/react/display.d.ts +129 -1
- package/dist/react/display.js +771 -0
- package/dist/react/display.js.map +1 -1
- package/dist/react/forms.cjs +1387 -0
- package/dist/react/forms.cjs.map +1 -0
- package/dist/react/forms.d.cts +5 -0
- package/dist/react/forms.d.ts +5 -0
- package/dist/react/forms.js +1326 -0
- package/dist/react/forms.js.map +1 -0
- package/dist/react/input.cjs +197 -0
- package/dist/react/input.cjs.map +1 -1
- package/dist/react/input.d.cts +13 -1
- package/dist/react/input.d.ts +13 -1
- package/dist/react/input.js +194 -0
- package/dist/react/input.js.map +1 -1
- package/dist/react/progress.cjs +266 -0
- package/dist/react/progress.cjs.map +1 -1
- package/dist/react/progress.d.cts +17 -1
- package/dist/react/progress.d.ts +17 -1
- package/dist/react/progress.js +263 -0
- package/dist/react/progress.js.map +1 -1
- package/package.json +87 -61
- package/registry.yaml +167 -1
|
@@ -0,0 +1,1326 @@
|
|
|
1
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
2
|
+
import { useForm, FormProvider as FormProvider$1, Controller, useFieldArray as useFieldArray$1 } from 'react-hook-form';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { createContext, useContext, useId, useRef, useCallback, useEffect, useState, useMemo } from 'preact/compat';
|
|
5
|
+
import { jsx, jsxs } from 'preact/jsx-runtime';
|
|
6
|
+
|
|
7
|
+
// src/runtimes/preact/components/forms/useMosaicForm.tsx
|
|
8
|
+
|
|
9
|
+
// src/components/forms/useMosaicForm.logic.ts
|
|
10
|
+
function toRhfMode(mode) {
|
|
11
|
+
return mode;
|
|
12
|
+
}
|
|
13
|
+
var MosaicFormModeSchema = z.enum(["onBlur", "onChange", "onSubmit"]);
|
|
14
|
+
var UseMosaicFormOptionsSchema = z.object({
|
|
15
|
+
mode: MosaicFormModeSchema.default("onBlur")
|
|
16
|
+
});
|
|
17
|
+
function validateOptions(raw) {
|
|
18
|
+
return UseMosaicFormOptionsSchema.parse(raw);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// src/runtimes/preact/components/forms/useMosaicForm.tsx
|
|
22
|
+
function useMosaicForm(options) {
|
|
23
|
+
const parsedOptions = validateOptions({ mode: options.mode });
|
|
24
|
+
const form = useForm({
|
|
25
|
+
// biome-ignore lint/suspicious/noExplicitAny: zodResolver's generic shape forces a cast at this boundary
|
|
26
|
+
resolver: zodResolver(options.schema),
|
|
27
|
+
defaultValues: options.defaultValues,
|
|
28
|
+
mode: toRhfMode(parsedOptions.mode)
|
|
29
|
+
});
|
|
30
|
+
const extended = form;
|
|
31
|
+
extended.mosaicSchema = options.schema;
|
|
32
|
+
extended.mosaicMode = parsedOptions.mode;
|
|
33
|
+
return extended;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// src/components/forms/FormProvider.logic.ts
|
|
37
|
+
var FORM_PROVIDER_CONTEXT_MISSING = "useMosaicFormContext must be used inside <FormProvider>. Wrap your tree with <FormProvider form={...}> from @vantageos/mosaic/{react,preact}/forms.";
|
|
38
|
+
var MosaicFormContext = createContext(null);
|
|
39
|
+
function FormProvider({
|
|
40
|
+
form,
|
|
41
|
+
children
|
|
42
|
+
}) {
|
|
43
|
+
return /* @__PURE__ */ jsx(MosaicFormContext.Provider, { value: form, children: /* @__PURE__ */ jsx(FormProvider$1, { ...form, children }) });
|
|
44
|
+
}
|
|
45
|
+
function useMosaicFormContext() {
|
|
46
|
+
const ctx = useContext(MosaicFormContext);
|
|
47
|
+
if (!ctx) {
|
|
48
|
+
throw new Error(FORM_PROVIDER_CONTEXT_MISSING);
|
|
49
|
+
}
|
|
50
|
+
return ctx;
|
|
51
|
+
}
|
|
52
|
+
function FormField({
|
|
53
|
+
name,
|
|
54
|
+
children
|
|
55
|
+
}) {
|
|
56
|
+
const form = useMosaicFormContext();
|
|
57
|
+
return /* @__PURE__ */ jsx(
|
|
58
|
+
Controller,
|
|
59
|
+
{
|
|
60
|
+
control: form.control,
|
|
61
|
+
name,
|
|
62
|
+
render: ({ field, fieldState, formState }) => children({
|
|
63
|
+
field,
|
|
64
|
+
fieldState,
|
|
65
|
+
formState
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/components/forms/ErrorDisplay.logic.ts
|
|
72
|
+
function formatErrorMessage(error, messageMap) {
|
|
73
|
+
if (!error) return void 0;
|
|
74
|
+
if (error.message && error.message.length > 0) return error.message;
|
|
75
|
+
const mapped = messageMap?.[error.type];
|
|
76
|
+
if (mapped && mapped.length > 0) return mapped;
|
|
77
|
+
return "Invalid value";
|
|
78
|
+
}
|
|
79
|
+
function ErrorDisplay({ error, messageMap, className }) {
|
|
80
|
+
const message = formatErrorMessage(error, messageMap);
|
|
81
|
+
if (!message) return null;
|
|
82
|
+
return /* @__PURE__ */ jsx("span", { role: "alert", className: className ?? "mosaic-forms-error", children: message });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// src/components/forms/SubmitButton.logic.ts
|
|
86
|
+
function computeSubmitButtonState(input) {
|
|
87
|
+
return {
|
|
88
|
+
disabled: input.isSubmitting || !input.isValid,
|
|
89
|
+
loading: input.isSubmitting
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
var SubmitButtonPropsSchema = z.object({
|
|
93
|
+
label: z.string().min(1).default("Submit"),
|
|
94
|
+
loadingLabel: z.string().min(1).default("Submitting\u2026")
|
|
95
|
+
});
|
|
96
|
+
function validateSubmitButtonProps(raw) {
|
|
97
|
+
return SubmitButtonPropsSchema.parse(raw);
|
|
98
|
+
}
|
|
99
|
+
function SubmitButton(props) {
|
|
100
|
+
const { label, loadingLabel } = validateSubmitButtonProps({
|
|
101
|
+
label: props.label,
|
|
102
|
+
loadingLabel: props.loadingLabel
|
|
103
|
+
});
|
|
104
|
+
const form = useMosaicFormContext();
|
|
105
|
+
const { isValid, isSubmitting } = form.formState;
|
|
106
|
+
const { disabled, loading } = computeSubmitButtonState({ isValid, isSubmitting });
|
|
107
|
+
return /* @__PURE__ */ jsx(
|
|
108
|
+
"button",
|
|
109
|
+
{
|
|
110
|
+
type: "submit",
|
|
111
|
+
disabled,
|
|
112
|
+
"aria-busy": loading || void 0,
|
|
113
|
+
className: props.className ?? "mosaic-forms-submit",
|
|
114
|
+
children: loading ? loadingLabel : label
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// src/components/forms/Input.logic.ts
|
|
120
|
+
function buildErrorDescribedBy(inputId, hasError) {
|
|
121
|
+
return hasError ? `${inputId}-error` : void 0;
|
|
122
|
+
}
|
|
123
|
+
function resolveHtmlType(type) {
|
|
124
|
+
return type ?? "text";
|
|
125
|
+
}
|
|
126
|
+
function Input({
|
|
127
|
+
name,
|
|
128
|
+
type,
|
|
129
|
+
label,
|
|
130
|
+
placeholder,
|
|
131
|
+
disabled,
|
|
132
|
+
autoComplete,
|
|
133
|
+
id,
|
|
134
|
+
messageMap,
|
|
135
|
+
className,
|
|
136
|
+
inputClassName,
|
|
137
|
+
errorClassName
|
|
138
|
+
}) {
|
|
139
|
+
const form = useMosaicFormContext();
|
|
140
|
+
const reactId = useId();
|
|
141
|
+
const inputId = id ?? `mosaic-input-${reactId.replace(/[^a-zA-Z0-9_-]/g, "")}-${String(name)}`;
|
|
142
|
+
const htmlType = resolveHtmlType(type);
|
|
143
|
+
return /* @__PURE__ */ jsx(
|
|
144
|
+
Controller,
|
|
145
|
+
{
|
|
146
|
+
control: form.control,
|
|
147
|
+
name,
|
|
148
|
+
render: ({ field, fieldState }) => {
|
|
149
|
+
const f = field;
|
|
150
|
+
const hasError = Boolean(fieldState.error);
|
|
151
|
+
const describedBy = buildErrorDescribedBy(inputId, hasError);
|
|
152
|
+
const message = formatErrorMessage(
|
|
153
|
+
fieldState.error ? { type: fieldState.error.type, message: fieldState.error.message } : void 0,
|
|
154
|
+
messageMap
|
|
155
|
+
);
|
|
156
|
+
const value = f.value ?? "";
|
|
157
|
+
return /* @__PURE__ */ jsxs("div", { className: className ?? "mosaic-forms-input", children: [
|
|
158
|
+
/* @__PURE__ */ jsx("label", { htmlFor: inputId, children: label }),
|
|
159
|
+
/* @__PURE__ */ jsx(
|
|
160
|
+
"input",
|
|
161
|
+
{
|
|
162
|
+
id: inputId,
|
|
163
|
+
name: f.name,
|
|
164
|
+
type: htmlType,
|
|
165
|
+
value,
|
|
166
|
+
onChange: f.onChange,
|
|
167
|
+
onBlur: f.onBlur,
|
|
168
|
+
ref: f.ref,
|
|
169
|
+
placeholder,
|
|
170
|
+
disabled: disabled ?? false,
|
|
171
|
+
autoComplete,
|
|
172
|
+
"aria-invalid": hasError || void 0,
|
|
173
|
+
"aria-describedby": describedBy,
|
|
174
|
+
className: inputClassName
|
|
175
|
+
}
|
|
176
|
+
),
|
|
177
|
+
hasError && message ? /* @__PURE__ */ jsx(
|
|
178
|
+
"span",
|
|
179
|
+
{
|
|
180
|
+
id: `${inputId}-error`,
|
|
181
|
+
role: "alert",
|
|
182
|
+
className: errorClassName ?? "mosaic-forms-error",
|
|
183
|
+
children: message
|
|
184
|
+
}
|
|
185
|
+
) : null
|
|
186
|
+
] });
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// src/components/forms/Textarea.logic.ts
|
|
193
|
+
var DEFAULT_TEXTAREA_ROWS = 3;
|
|
194
|
+
function clampToMaxLength(value, maxLength) {
|
|
195
|
+
if (maxLength === void 0) return value;
|
|
196
|
+
if (value.length <= maxLength) return value;
|
|
197
|
+
return value.slice(0, maxLength);
|
|
198
|
+
}
|
|
199
|
+
function resolveRows(rows) {
|
|
200
|
+
if (rows === void 0) return DEFAULT_TEXTAREA_ROWS;
|
|
201
|
+
if (!Number.isFinite(rows) || rows < 1) return DEFAULT_TEXTAREA_ROWS;
|
|
202
|
+
return Math.floor(rows);
|
|
203
|
+
}
|
|
204
|
+
function computeAutoResizeHeight(scrollHeight, minHeightPx) {
|
|
205
|
+
if (!Number.isFinite(scrollHeight) || scrollHeight <= 0) return minHeightPx;
|
|
206
|
+
return Math.max(scrollHeight, minHeightPx);
|
|
207
|
+
}
|
|
208
|
+
function Textarea(props) {
|
|
209
|
+
const {
|
|
210
|
+
name,
|
|
211
|
+
rows,
|
|
212
|
+
maxLength,
|
|
213
|
+
autoResize = false,
|
|
214
|
+
placeholder,
|
|
215
|
+
disabled,
|
|
216
|
+
label,
|
|
217
|
+
className,
|
|
218
|
+
errorMessageMap
|
|
219
|
+
} = props;
|
|
220
|
+
const effectiveRows = resolveRows(rows);
|
|
221
|
+
const reactId = useId();
|
|
222
|
+
const fieldId = `mosaic-textarea-${reactId}`;
|
|
223
|
+
const errorId = `${fieldId}-error`;
|
|
224
|
+
return /* @__PURE__ */ jsx(FormField, { name, children: ({ field, fieldState }) => {
|
|
225
|
+
const hasError = Boolean(fieldState.error);
|
|
226
|
+
return /* @__PURE__ */ jsx(
|
|
227
|
+
TextareaInner,
|
|
228
|
+
{
|
|
229
|
+
id: fieldId,
|
|
230
|
+
errorId,
|
|
231
|
+
label,
|
|
232
|
+
value: field.value ?? "",
|
|
233
|
+
onChange: (next) => field.onChange(clampToMaxLength(next, maxLength)),
|
|
234
|
+
onBlur: field.onBlur,
|
|
235
|
+
rows: effectiveRows,
|
|
236
|
+
maxLength,
|
|
237
|
+
autoResize,
|
|
238
|
+
placeholder,
|
|
239
|
+
disabled,
|
|
240
|
+
hasError,
|
|
241
|
+
className,
|
|
242
|
+
children: /* @__PURE__ */ jsx(
|
|
243
|
+
ErrorDisplay,
|
|
244
|
+
{
|
|
245
|
+
error: fieldState.error ? {
|
|
246
|
+
type: fieldState.error.type ?? "validation",
|
|
247
|
+
message: fieldState.error.message
|
|
248
|
+
} : void 0,
|
|
249
|
+
messageMap: errorMessageMap,
|
|
250
|
+
className: "mosaic-forms-error"
|
|
251
|
+
}
|
|
252
|
+
)
|
|
253
|
+
}
|
|
254
|
+
);
|
|
255
|
+
} });
|
|
256
|
+
}
|
|
257
|
+
function TextareaInner(props) {
|
|
258
|
+
const {
|
|
259
|
+
id,
|
|
260
|
+
errorId,
|
|
261
|
+
label,
|
|
262
|
+
value,
|
|
263
|
+
onChange,
|
|
264
|
+
onBlur,
|
|
265
|
+
rows,
|
|
266
|
+
maxLength,
|
|
267
|
+
autoResize,
|
|
268
|
+
placeholder,
|
|
269
|
+
disabled,
|
|
270
|
+
hasError,
|
|
271
|
+
className,
|
|
272
|
+
children
|
|
273
|
+
} = props;
|
|
274
|
+
const ref = useRef(null);
|
|
275
|
+
const resize = useCallback(() => {
|
|
276
|
+
if (!autoResize) return;
|
|
277
|
+
const el = ref.current;
|
|
278
|
+
if (!el) return;
|
|
279
|
+
el.style.height = "auto";
|
|
280
|
+
const minPx = rows * 20;
|
|
281
|
+
const next = computeAutoResizeHeight(el.scrollHeight, minPx);
|
|
282
|
+
el.style.height = `${next}px`;
|
|
283
|
+
}, [autoResize, rows]);
|
|
284
|
+
useEffect(() => {
|
|
285
|
+
resize();
|
|
286
|
+
}, [resize]);
|
|
287
|
+
return /* @__PURE__ */ jsxs("div", { className: className ?? "mosaic-forms-textarea", children: [
|
|
288
|
+
label ? /* @__PURE__ */ jsx("label", { htmlFor: id, className: "mosaic-forms-textarea-label", children: label }) : null,
|
|
289
|
+
/* @__PURE__ */ jsx(
|
|
290
|
+
"textarea",
|
|
291
|
+
{
|
|
292
|
+
ref,
|
|
293
|
+
id,
|
|
294
|
+
value,
|
|
295
|
+
rows,
|
|
296
|
+
maxLength,
|
|
297
|
+
placeholder,
|
|
298
|
+
disabled,
|
|
299
|
+
"aria-invalid": hasError || void 0,
|
|
300
|
+
"aria-describedby": hasError ? errorId : void 0,
|
|
301
|
+
onChange: (e) => {
|
|
302
|
+
onChange(e.target.value);
|
|
303
|
+
resize();
|
|
304
|
+
},
|
|
305
|
+
onBlur
|
|
306
|
+
}
|
|
307
|
+
),
|
|
308
|
+
/* @__PURE__ */ jsx("div", { id: errorId, children })
|
|
309
|
+
] });
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// src/components/forms/FieldArray.logic.ts
|
|
313
|
+
function buildListAriaLabel(name) {
|
|
314
|
+
return `${name} items`;
|
|
315
|
+
}
|
|
316
|
+
function buildRemoveAriaLabel(name, index) {
|
|
317
|
+
return `Remove ${name} item ${index + 1}`;
|
|
318
|
+
}
|
|
319
|
+
function buildAddAriaLabel(name) {
|
|
320
|
+
return `Add ${name} item`;
|
|
321
|
+
}
|
|
322
|
+
function nextFocusIndexAfterRemove(previousLength, removedIndex) {
|
|
323
|
+
if (previousLength <= 1) return -1;
|
|
324
|
+
const newLength = previousLength - 1;
|
|
325
|
+
return Math.min(removedIndex, newLength - 1);
|
|
326
|
+
}
|
|
327
|
+
function useFieldArray({ name, control }) {
|
|
328
|
+
const ctx = useMosaicFormContext();
|
|
329
|
+
const resolvedControl = control ?? ctx.control;
|
|
330
|
+
return useFieldArray$1({
|
|
331
|
+
// biome-ignore lint/suspicious/noExplicitAny: bridging erased generic
|
|
332
|
+
control: resolvedControl,
|
|
333
|
+
name
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
function FieldArray({ name, children, ariaLabel }) {
|
|
337
|
+
const ctx = useMosaicFormContext();
|
|
338
|
+
const { fields, append, remove, move, swap } = useFieldArray$1({
|
|
339
|
+
// biome-ignore lint/suspicious/noExplicitAny: bridging erased generic
|
|
340
|
+
control: ctx.control,
|
|
341
|
+
name
|
|
342
|
+
});
|
|
343
|
+
const controls = {
|
|
344
|
+
fields,
|
|
345
|
+
append,
|
|
346
|
+
remove,
|
|
347
|
+
move,
|
|
348
|
+
swap
|
|
349
|
+
};
|
|
350
|
+
return /* @__PURE__ */ jsx(
|
|
351
|
+
"ul",
|
|
352
|
+
{
|
|
353
|
+
"aria-label": ariaLabel ?? buildListAriaLabel(name),
|
|
354
|
+
style: { listStyle: "none", padding: 0, margin: 0 },
|
|
355
|
+
children: fields.map((field, index) => /* @__PURE__ */ jsx(
|
|
356
|
+
"li",
|
|
357
|
+
{
|
|
358
|
+
"data-mosaic-fieldarray-item": true,
|
|
359
|
+
"data-mosaic-fieldarray-name": name,
|
|
360
|
+
"data-mosaic-fieldarray-index": index,
|
|
361
|
+
children: children(
|
|
362
|
+
{
|
|
363
|
+
field,
|
|
364
|
+
index
|
|
365
|
+
},
|
|
366
|
+
controls
|
|
367
|
+
)
|
|
368
|
+
},
|
|
369
|
+
field.id
|
|
370
|
+
))
|
|
371
|
+
}
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// src/components/forms/Checkbox.logic.ts
|
|
376
|
+
function resolveAriaChecked(checked, indeterminate) {
|
|
377
|
+
if (indeterminate) return "mixed";
|
|
378
|
+
return checked;
|
|
379
|
+
}
|
|
380
|
+
function resolveDescribedBy(descriptionId, description, hasError, errorId) {
|
|
381
|
+
const parts = [];
|
|
382
|
+
if (description) parts.push(descriptionId);
|
|
383
|
+
if (hasError) parts.push(errorId);
|
|
384
|
+
return parts.length > 0 ? parts.join(" ") : void 0;
|
|
385
|
+
}
|
|
386
|
+
function Checkbox(props) {
|
|
387
|
+
const { name, label, disabled, indeterminate, description, className, errorMessageMap } = props;
|
|
388
|
+
const reactId = useId();
|
|
389
|
+
const fieldId = `mosaic-checkbox-${reactId}`;
|
|
390
|
+
const errorId = `${fieldId}-error`;
|
|
391
|
+
const descriptionId = `${fieldId}-description`;
|
|
392
|
+
return /* @__PURE__ */ jsx(FormField, { name, children: ({ field, fieldState }) => {
|
|
393
|
+
const hasError = Boolean(fieldState.error);
|
|
394
|
+
return /* @__PURE__ */ jsx(
|
|
395
|
+
CheckboxInner,
|
|
396
|
+
{
|
|
397
|
+
id: fieldId,
|
|
398
|
+
errorId,
|
|
399
|
+
descriptionId,
|
|
400
|
+
label,
|
|
401
|
+
checked: field.value ?? false,
|
|
402
|
+
onChange: (next) => field.onChange(next),
|
|
403
|
+
onBlur: field.onBlur,
|
|
404
|
+
disabled,
|
|
405
|
+
indeterminate,
|
|
406
|
+
description,
|
|
407
|
+
hasError,
|
|
408
|
+
className,
|
|
409
|
+
children: /* @__PURE__ */ jsx(
|
|
410
|
+
ErrorDisplay,
|
|
411
|
+
{
|
|
412
|
+
error: fieldState.error ? {
|
|
413
|
+
type: fieldState.error.type ?? "validation",
|
|
414
|
+
message: fieldState.error.message
|
|
415
|
+
} : void 0,
|
|
416
|
+
messageMap: errorMessageMap,
|
|
417
|
+
className: "mosaic-forms-error"
|
|
418
|
+
}
|
|
419
|
+
)
|
|
420
|
+
}
|
|
421
|
+
);
|
|
422
|
+
} });
|
|
423
|
+
}
|
|
424
|
+
function CheckboxInner(props) {
|
|
425
|
+
const {
|
|
426
|
+
id,
|
|
427
|
+
errorId,
|
|
428
|
+
descriptionId,
|
|
429
|
+
label,
|
|
430
|
+
checked,
|
|
431
|
+
onChange,
|
|
432
|
+
onBlur,
|
|
433
|
+
disabled,
|
|
434
|
+
indeterminate,
|
|
435
|
+
description,
|
|
436
|
+
hasError,
|
|
437
|
+
className,
|
|
438
|
+
children
|
|
439
|
+
} = props;
|
|
440
|
+
const ref = useRef(null);
|
|
441
|
+
useEffect(() => {
|
|
442
|
+
if (ref.current) {
|
|
443
|
+
ref.current.indeterminate = Boolean(indeterminate);
|
|
444
|
+
}
|
|
445
|
+
}, [indeterminate]);
|
|
446
|
+
const ariaChecked = resolveAriaChecked(checked, indeterminate);
|
|
447
|
+
const describedBy = resolveDescribedBy(descriptionId, description, hasError, errorId);
|
|
448
|
+
return /* @__PURE__ */ jsxs("div", { className: className ?? "mosaic-forms-checkbox", children: [
|
|
449
|
+
/* @__PURE__ */ jsxs("label", { htmlFor: id, className: "mosaic-forms-checkbox-label", children: [
|
|
450
|
+
/* @__PURE__ */ jsx(
|
|
451
|
+
"input",
|
|
452
|
+
{
|
|
453
|
+
ref,
|
|
454
|
+
type: "checkbox",
|
|
455
|
+
id,
|
|
456
|
+
checked,
|
|
457
|
+
disabled,
|
|
458
|
+
"aria-checked": ariaChecked,
|
|
459
|
+
"aria-invalid": hasError || void 0,
|
|
460
|
+
"aria-describedby": describedBy,
|
|
461
|
+
onChange: (e) => onChange(e.target.checked),
|
|
462
|
+
onBlur
|
|
463
|
+
}
|
|
464
|
+
),
|
|
465
|
+
label
|
|
466
|
+
] }),
|
|
467
|
+
description ? /* @__PURE__ */ jsx("span", { id: descriptionId, className: "mosaic-forms-checkbox-description", children: description }) : null,
|
|
468
|
+
/* @__PURE__ */ jsx("div", { id: errorId, children })
|
|
469
|
+
] });
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// src/components/forms/MultiSelect.logic.ts
|
|
473
|
+
function getAvailableOptions(options, selected) {
|
|
474
|
+
const set = new Set(selected);
|
|
475
|
+
return options.filter((opt) => !set.has(opt.value));
|
|
476
|
+
}
|
|
477
|
+
function getSelectedOptions(options, selected) {
|
|
478
|
+
const byValue = new Map(options.map((opt) => [opt.value, opt]));
|
|
479
|
+
const out = [];
|
|
480
|
+
for (const value of selected) {
|
|
481
|
+
const opt = byValue.get(value);
|
|
482
|
+
if (opt) out.push(opt);
|
|
483
|
+
}
|
|
484
|
+
return out;
|
|
485
|
+
}
|
|
486
|
+
function filterBySearch(options, query) {
|
|
487
|
+
const q = query.trim().toLowerCase();
|
|
488
|
+
if (q.length === 0) return [...options];
|
|
489
|
+
return options.filter(
|
|
490
|
+
(opt) => opt.label.toLowerCase().includes(q) || opt.value.toLowerCase().includes(q)
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
function addValue(current, value, maxItems) {
|
|
494
|
+
if (current.includes(value)) return current;
|
|
495
|
+
if (typeof maxItems === "number" && current.length >= maxItems) return current;
|
|
496
|
+
return [...current, value];
|
|
497
|
+
}
|
|
498
|
+
function removeValue(current, value) {
|
|
499
|
+
return current.filter((v) => v !== value);
|
|
500
|
+
}
|
|
501
|
+
function isAtMaxItems(current, maxItems) {
|
|
502
|
+
return typeof maxItems === "number" && current.length >= maxItems;
|
|
503
|
+
}
|
|
504
|
+
var MultiSelectOptionSchema = z.object({
|
|
505
|
+
value: z.string().min(1),
|
|
506
|
+
label: z.string().min(1)
|
|
507
|
+
});
|
|
508
|
+
var MultiSelectPropsSchema = z.object({
|
|
509
|
+
name: z.string().min(1),
|
|
510
|
+
label: z.string().min(1),
|
|
511
|
+
options: z.array(MultiSelectOptionSchema).min(1),
|
|
512
|
+
placeholder: z.string().optional(),
|
|
513
|
+
disabled: z.boolean().optional(),
|
|
514
|
+
searchable: z.boolean().optional(),
|
|
515
|
+
maxItems: z.number().int().positive().optional()
|
|
516
|
+
});
|
|
517
|
+
function MultiSelect(props) {
|
|
518
|
+
const parsed = MultiSelectPropsSchema.parse({
|
|
519
|
+
name: props.name,
|
|
520
|
+
label: props.label,
|
|
521
|
+
options: props.options,
|
|
522
|
+
placeholder: props.placeholder,
|
|
523
|
+
disabled: props.disabled,
|
|
524
|
+
searchable: props.searchable,
|
|
525
|
+
maxItems: props.maxItems
|
|
526
|
+
});
|
|
527
|
+
const { name, label, options, placeholder, disabled, searchable, maxItems } = parsed;
|
|
528
|
+
const reactId = useId();
|
|
529
|
+
const triggerId = `${reactId}-trigger`;
|
|
530
|
+
const listboxId = `${reactId}-listbox`;
|
|
531
|
+
const labelId = `${reactId}-label`;
|
|
532
|
+
return /* @__PURE__ */ jsx(FormField, { name, children: ({ field }) => {
|
|
533
|
+
const value = Array.isArray(field.value) ? field.value : [];
|
|
534
|
+
return /* @__PURE__ */ jsx(
|
|
535
|
+
Inner,
|
|
536
|
+
{
|
|
537
|
+
triggerId,
|
|
538
|
+
listboxId,
|
|
539
|
+
labelId,
|
|
540
|
+
label,
|
|
541
|
+
options,
|
|
542
|
+
value,
|
|
543
|
+
disabled,
|
|
544
|
+
searchable,
|
|
545
|
+
maxItems,
|
|
546
|
+
placeholder,
|
|
547
|
+
onChange: (next) => field.onChange(next),
|
|
548
|
+
onBlur: () => field.onBlur(),
|
|
549
|
+
className: props.className
|
|
550
|
+
}
|
|
551
|
+
);
|
|
552
|
+
} });
|
|
553
|
+
}
|
|
554
|
+
function Inner(p) {
|
|
555
|
+
const [open, setOpen] = useState(false);
|
|
556
|
+
const [query, setQuery] = useState("");
|
|
557
|
+
const [activeIndex, setActiveIndex] = useState(0);
|
|
558
|
+
const triggerRef = useRef(null);
|
|
559
|
+
const selectedOptions = useMemo(
|
|
560
|
+
() => getSelectedOptions(p.options, p.value),
|
|
561
|
+
[p.options, p.value]
|
|
562
|
+
);
|
|
563
|
+
const availableAfterSelect = useMemo(
|
|
564
|
+
() => getAvailableOptions(p.options, p.value),
|
|
565
|
+
[p.options, p.value]
|
|
566
|
+
);
|
|
567
|
+
const visibleOptions = useMemo(
|
|
568
|
+
() => p.searchable ? filterBySearch(availableAfterSelect, query) : availableAfterSelect,
|
|
569
|
+
[availableAfterSelect, p.searchable, query]
|
|
570
|
+
);
|
|
571
|
+
const atMax = isAtMaxItems(p.value, p.maxItems);
|
|
572
|
+
const handleSelect = useCallback(
|
|
573
|
+
(val) => {
|
|
574
|
+
const next = addValue(p.value, val, p.maxItems);
|
|
575
|
+
if (next !== p.value) {
|
|
576
|
+
p.onChange(next);
|
|
577
|
+
}
|
|
578
|
+
setQuery("");
|
|
579
|
+
},
|
|
580
|
+
[p]
|
|
581
|
+
);
|
|
582
|
+
const handleRemove = useCallback(
|
|
583
|
+
(val) => {
|
|
584
|
+
p.onChange(removeValue(p.value, val));
|
|
585
|
+
},
|
|
586
|
+
[p]
|
|
587
|
+
);
|
|
588
|
+
const handleTriggerKeyDown = useCallback(
|
|
589
|
+
(e) => {
|
|
590
|
+
if (p.disabled) return;
|
|
591
|
+
if (e.key === "Backspace" || e.key === "Delete") {
|
|
592
|
+
const last = p.value[p.value.length - 1];
|
|
593
|
+
if (last !== void 0) {
|
|
594
|
+
e.preventDefault();
|
|
595
|
+
handleRemove(last);
|
|
596
|
+
}
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
if (e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") {
|
|
600
|
+
e.preventDefault();
|
|
601
|
+
setOpen(true);
|
|
602
|
+
setActiveIndex(0);
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
if (e.key === "Escape") {
|
|
606
|
+
setOpen(false);
|
|
607
|
+
}
|
|
608
|
+
},
|
|
609
|
+
[p.disabled, p.value, handleRemove]
|
|
610
|
+
);
|
|
611
|
+
const handleListKeyDown = useCallback(
|
|
612
|
+
(e) => {
|
|
613
|
+
if (e.key === "ArrowDown") {
|
|
614
|
+
e.preventDefault();
|
|
615
|
+
setActiveIndex((i) => Math.min(i + 1, Math.max(visibleOptions.length - 1, 0)));
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
if (e.key === "ArrowUp") {
|
|
619
|
+
e.preventDefault();
|
|
620
|
+
setActiveIndex((i) => Math.max(i - 1, 0));
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
if (e.key === "Enter") {
|
|
624
|
+
e.preventDefault();
|
|
625
|
+
const opt = visibleOptions[activeIndex];
|
|
626
|
+
if (opt) handleSelect(opt.value);
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
if (e.key === "Escape") {
|
|
630
|
+
e.preventDefault();
|
|
631
|
+
setOpen(false);
|
|
632
|
+
triggerRef.current?.focus();
|
|
633
|
+
}
|
|
634
|
+
},
|
|
635
|
+
[visibleOptions, activeIndex, handleSelect]
|
|
636
|
+
);
|
|
637
|
+
return /* @__PURE__ */ jsxs("div", { className: p.className ?? "mosaic-multiselect", onBlur: p.onBlur, children: [
|
|
638
|
+
/* @__PURE__ */ jsx("span", { id: p.labelId, className: "mosaic-multiselect-label", children: p.label }),
|
|
639
|
+
/* @__PURE__ */ jsx("div", { className: "mosaic-multiselect-chips", "data-testid": "multiselect-chips", children: selectedOptions.map((opt) => /* @__PURE__ */ jsxs("span", { className: "mosaic-multiselect-chip", "data-chip-value": opt.value, children: [
|
|
640
|
+
opt.label,
|
|
641
|
+
/* @__PURE__ */ jsx(
|
|
642
|
+
"button",
|
|
643
|
+
{
|
|
644
|
+
type: "button",
|
|
645
|
+
"aria-label": `Remove ${opt.label}`,
|
|
646
|
+
"data-testid": `chip-remove-${opt.value}`,
|
|
647
|
+
className: "mosaic-multiselect-chip-remove",
|
|
648
|
+
disabled: p.disabled,
|
|
649
|
+
onClick: () => handleRemove(opt.value),
|
|
650
|
+
children: "\xD7"
|
|
651
|
+
}
|
|
652
|
+
)
|
|
653
|
+
] }, opt.value)) }),
|
|
654
|
+
/* @__PURE__ */ jsx(
|
|
655
|
+
"button",
|
|
656
|
+
{
|
|
657
|
+
ref: triggerRef,
|
|
658
|
+
id: p.triggerId,
|
|
659
|
+
type: "button",
|
|
660
|
+
role: "combobox",
|
|
661
|
+
"aria-multiselectable": true,
|
|
662
|
+
"aria-expanded": open,
|
|
663
|
+
"aria-controls": p.listboxId,
|
|
664
|
+
"aria-labelledby": p.labelId,
|
|
665
|
+
"aria-disabled": p.disabled || atMax || void 0,
|
|
666
|
+
disabled: p.disabled,
|
|
667
|
+
"data-testid": "multiselect-trigger",
|
|
668
|
+
className: "mosaic-multiselect-trigger",
|
|
669
|
+
onClick: () => !p.disabled && setOpen((o) => !o),
|
|
670
|
+
onKeyDown: handleTriggerKeyDown,
|
|
671
|
+
children: p.value.length === 0 ? p.placeholder ?? "Select\u2026" : `${p.value.length} selected`
|
|
672
|
+
}
|
|
673
|
+
),
|
|
674
|
+
open ? /* @__PURE__ */ jsxs("div", { className: "mosaic-multiselect-panel", children: [
|
|
675
|
+
p.searchable ? /* @__PURE__ */ jsx(
|
|
676
|
+
"input",
|
|
677
|
+
{
|
|
678
|
+
type: "text",
|
|
679
|
+
"aria-label": "Search options",
|
|
680
|
+
"data-testid": "multiselect-search",
|
|
681
|
+
className: "mosaic-multiselect-search",
|
|
682
|
+
value: query,
|
|
683
|
+
onChange: (e) => {
|
|
684
|
+
setQuery(e.target.value);
|
|
685
|
+
setActiveIndex(0);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
) : null,
|
|
689
|
+
/* @__PURE__ */ jsx(
|
|
690
|
+
"ul",
|
|
691
|
+
{
|
|
692
|
+
id: p.listboxId,
|
|
693
|
+
role: "listbox",
|
|
694
|
+
"aria-multiselectable": true,
|
|
695
|
+
"aria-labelledby": p.labelId,
|
|
696
|
+
tabIndex: -1,
|
|
697
|
+
className: "mosaic-multiselect-listbox",
|
|
698
|
+
onKeyDown: handleListKeyDown,
|
|
699
|
+
children: visibleOptions.length === 0 ? /* @__PURE__ */ jsx("li", { className: "mosaic-multiselect-empty", children: "No options" }) : visibleOptions.map((opt, i) => (
|
|
700
|
+
// biome-ignore lint/a11y/useFocusableInteractive: focus stays on the listbox; APG combobox active-descendant pattern.
|
|
701
|
+
// biome-ignore lint/a11y/useKeyWithClickEvents: keyboard handled at listbox level per APG.
|
|
702
|
+
/* @__PURE__ */ jsx(
|
|
703
|
+
"li",
|
|
704
|
+
{
|
|
705
|
+
role: "option",
|
|
706
|
+
"aria-selected": false,
|
|
707
|
+
"data-testid": `option-${opt.value}`,
|
|
708
|
+
"data-active": i === activeIndex || void 0,
|
|
709
|
+
className: "mosaic-multiselect-option",
|
|
710
|
+
onClick: () => handleSelect(opt.value),
|
|
711
|
+
children: opt.label
|
|
712
|
+
},
|
|
713
|
+
opt.value
|
|
714
|
+
)
|
|
715
|
+
))
|
|
716
|
+
}
|
|
717
|
+
)
|
|
718
|
+
] }) : null
|
|
719
|
+
] });
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// src/components/forms/RadioGroup.logic.ts
|
|
723
|
+
function getNavKeys(orientation) {
|
|
724
|
+
return orientation === "vertical" ? { prev: "ArrowUp", next: "ArrowDown" } : { prev: "ArrowLeft", next: "ArrowRight" };
|
|
725
|
+
}
|
|
726
|
+
function findNextEnabledIndex(options, currentIndex, direction) {
|
|
727
|
+
const len = options.length;
|
|
728
|
+
if (len === 0) return currentIndex;
|
|
729
|
+
let i = (currentIndex + direction + len) % len;
|
|
730
|
+
for (let tries = 0; tries < len; tries++) {
|
|
731
|
+
if (!options[i]?.disabled) return i;
|
|
732
|
+
i = (i + direction + len) % len;
|
|
733
|
+
}
|
|
734
|
+
return currentIndex;
|
|
735
|
+
}
|
|
736
|
+
function findFirstEnabledIndex(options) {
|
|
737
|
+
const idx = options.findIndex((o) => !o.disabled);
|
|
738
|
+
return idx === -1 ? 0 : idx;
|
|
739
|
+
}
|
|
740
|
+
function findLastEnabledIndex(options) {
|
|
741
|
+
for (let i = options.length - 1; i >= 0; i--) {
|
|
742
|
+
if (!options[i]?.disabled) return i;
|
|
743
|
+
}
|
|
744
|
+
return options.length - 1;
|
|
745
|
+
}
|
|
746
|
+
function findSelectedIndex(options, selectedValue) {
|
|
747
|
+
if (selectedValue === void 0 || selectedValue === "") return -1;
|
|
748
|
+
return options.findIndex((o) => o.value === selectedValue);
|
|
749
|
+
}
|
|
750
|
+
function getRovingTabIndex(options, selectedValue) {
|
|
751
|
+
const selectedIdx = findSelectedIndex(options, selectedValue);
|
|
752
|
+
if (selectedIdx >= 0) return selectedIdx;
|
|
753
|
+
return findFirstEnabledIndex(options);
|
|
754
|
+
}
|
|
755
|
+
function getGroupLabelId(instanceId, name) {
|
|
756
|
+
return `${instanceId}-${name}-label`;
|
|
757
|
+
}
|
|
758
|
+
function getOptionLabelId(instanceId, name, optionValue) {
|
|
759
|
+
return `${instanceId}-${name}-${optionValue}-label`;
|
|
760
|
+
}
|
|
761
|
+
function getOptionDescriptionId(instanceId, name, optionValue) {
|
|
762
|
+
return `${instanceId}-${name}-${optionValue}-desc`;
|
|
763
|
+
}
|
|
764
|
+
function getGroupClasses(orientation) {
|
|
765
|
+
return orientation === "vertical" ? "flex flex-col gap-2" : "flex flex-row flex-wrap gap-4";
|
|
766
|
+
}
|
|
767
|
+
function getOptionRowClasses(isDisabled, orientation) {
|
|
768
|
+
const base = "flex items-start gap-2";
|
|
769
|
+
const orient = orientation === "vertical" ? "flex-row" : "flex-row";
|
|
770
|
+
const state = isDisabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer";
|
|
771
|
+
return [base, orient, state].join(" ");
|
|
772
|
+
}
|
|
773
|
+
function getRadioControlClasses(isSelected, isDisabled) {
|
|
774
|
+
const base = "inline-flex items-center justify-center w-4 h-4 mt-0.5 rounded-full border-2 transition-colors focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600 shrink-0";
|
|
775
|
+
const state = isDisabled ? "border-slate-300 bg-slate-100" : isSelected ? "border-blue-600 bg-white" : "border-slate-400 bg-white hover:border-slate-600";
|
|
776
|
+
return [base, state].join(" ");
|
|
777
|
+
}
|
|
778
|
+
function isActivationKey(key) {
|
|
779
|
+
return key === " " || key === "Space" || key === "Enter";
|
|
780
|
+
}
|
|
781
|
+
var RadioGroupOptionSchema = z.object({
|
|
782
|
+
value: z.string().min(1),
|
|
783
|
+
label: z.string().min(1),
|
|
784
|
+
description: z.string().optional(),
|
|
785
|
+
disabled: z.boolean().optional().default(false)
|
|
786
|
+
});
|
|
787
|
+
var RadioGroupPropsSchema = z.object({
|
|
788
|
+
name: z.string().min(1),
|
|
789
|
+
label: z.string().min(1),
|
|
790
|
+
options: z.array(RadioGroupOptionSchema).min(1),
|
|
791
|
+
orientation: z.enum(["vertical", "horizontal"]).default("vertical"),
|
|
792
|
+
disabled: z.boolean().optional().default(false),
|
|
793
|
+
locale: z.enum(["en", "fr"]).default("en")
|
|
794
|
+
});
|
|
795
|
+
function validateRadioGroupProps(raw) {
|
|
796
|
+
return RadioGroupPropsSchema.parse(raw);
|
|
797
|
+
}
|
|
798
|
+
function RadioGroup(props) {
|
|
799
|
+
const validated = validateRadioGroupProps({
|
|
800
|
+
name: props.name,
|
|
801
|
+
label: props.label,
|
|
802
|
+
options: props.options,
|
|
803
|
+
orientation: props.orientation,
|
|
804
|
+
disabled: props.disabled
|
|
805
|
+
});
|
|
806
|
+
const { name, label, options, orientation, disabled } = validated;
|
|
807
|
+
const instanceId = useId();
|
|
808
|
+
const groupLabelId = getGroupLabelId(instanceId, name);
|
|
809
|
+
const { prev: prevKey, next: nextKey } = getNavKeys(orientation);
|
|
810
|
+
const radioRefs = useRef([]);
|
|
811
|
+
const focusAndSelect = useCallback(
|
|
812
|
+
(index, onChange) => {
|
|
813
|
+
const el = radioRefs.current[index];
|
|
814
|
+
if (el) el.focus();
|
|
815
|
+
const opt = options[index];
|
|
816
|
+
if (opt && !opt.disabled) {
|
|
817
|
+
onChange(opt.value);
|
|
818
|
+
}
|
|
819
|
+
},
|
|
820
|
+
[options]
|
|
821
|
+
);
|
|
822
|
+
return /* @__PURE__ */ jsx(FormField, { name, children: ({ field, fieldState }) => {
|
|
823
|
+
const selectedValue = field.value;
|
|
824
|
+
const rovingIdx = getRovingTabIndex(options, selectedValue);
|
|
825
|
+
const handleKeyDown = (e, currentIndex) => {
|
|
826
|
+
const { key } = e;
|
|
827
|
+
if (key === prevKey || key === nextKey) {
|
|
828
|
+
e.preventDefault();
|
|
829
|
+
const direction = key === nextKey ? 1 : -1;
|
|
830
|
+
const nextIdx = findNextEnabledIndex(options, currentIndex, direction);
|
|
831
|
+
focusAndSelect(nextIdx, field.onChange);
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
if (key === "Home") {
|
|
835
|
+
e.preventDefault();
|
|
836
|
+
focusAndSelect(findFirstEnabledIndex(options), field.onChange);
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
if (key === "End") {
|
|
840
|
+
e.preventDefault();
|
|
841
|
+
focusAndSelect(findLastEnabledIndex(options), field.onChange);
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
if (isActivationKey(key)) {
|
|
845
|
+
e.preventDefault();
|
|
846
|
+
const opt = options[currentIndex];
|
|
847
|
+
if (opt && !opt.disabled) {
|
|
848
|
+
field.onChange(opt.value);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
};
|
|
852
|
+
return /* @__PURE__ */ jsxs("div", { className: props.className ?? "mosaic-forms-radiogroup", children: [
|
|
853
|
+
/* @__PURE__ */ jsx("span", { id: groupLabelId, className: "mosaic-forms-radiogroup-label", children: label }),
|
|
854
|
+
/* @__PURE__ */ jsx(
|
|
855
|
+
"div",
|
|
856
|
+
{
|
|
857
|
+
role: "radiogroup",
|
|
858
|
+
"aria-labelledby": groupLabelId,
|
|
859
|
+
"aria-orientation": orientation,
|
|
860
|
+
"aria-disabled": disabled || void 0,
|
|
861
|
+
className: getGroupClasses(orientation),
|
|
862
|
+
children: options.map((option, index) => {
|
|
863
|
+
const isSelected = selectedValue === option.value;
|
|
864
|
+
const isDisabled = !!(disabled || option.disabled);
|
|
865
|
+
const optLabelId = getOptionLabelId(instanceId, name, option.value);
|
|
866
|
+
const optDescId = option.description ? getOptionDescriptionId(instanceId, name, option.value) : void 0;
|
|
867
|
+
const tabIdx = index === rovingIdx ? 0 : -1;
|
|
868
|
+
return /* @__PURE__ */ jsxs("div", { className: getOptionRowClasses(isDisabled, orientation), children: [
|
|
869
|
+
/* @__PURE__ */ jsx(
|
|
870
|
+
"input",
|
|
871
|
+
{
|
|
872
|
+
ref: (el) => {
|
|
873
|
+
radioRefs.current[index] = el;
|
|
874
|
+
},
|
|
875
|
+
type: "radio",
|
|
876
|
+
id: `${instanceId}-${name}-${option.value}`,
|
|
877
|
+
name: field.name,
|
|
878
|
+
value: option.value,
|
|
879
|
+
checked: isSelected,
|
|
880
|
+
disabled: isDisabled,
|
|
881
|
+
"aria-labelledby": optLabelId,
|
|
882
|
+
"aria-describedby": optDescId,
|
|
883
|
+
tabIndex: isDisabled ? -1 : tabIdx,
|
|
884
|
+
className: "mosaic-forms-radiogroup-input sr-only",
|
|
885
|
+
onChange: () => {
|
|
886
|
+
field.onChange(option.value);
|
|
887
|
+
},
|
|
888
|
+
onKeyDown: (e) => {
|
|
889
|
+
if (!isDisabled) handleKeyDown(e, index);
|
|
890
|
+
},
|
|
891
|
+
onBlur: field.onBlur
|
|
892
|
+
}
|
|
893
|
+
),
|
|
894
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-0.5", children: [
|
|
895
|
+
/* @__PURE__ */ jsx("span", { id: optLabelId, className: "mosaic-forms-radiogroup-option-label", children: option.label }),
|
|
896
|
+
option.description ? /* @__PURE__ */ jsx(
|
|
897
|
+
"span",
|
|
898
|
+
{
|
|
899
|
+
id: optDescId,
|
|
900
|
+
className: "mosaic-forms-radiogroup-option-description text-sm text-slate-500",
|
|
901
|
+
children: option.description
|
|
902
|
+
}
|
|
903
|
+
) : null
|
|
904
|
+
] })
|
|
905
|
+
] }, option.value);
|
|
906
|
+
})
|
|
907
|
+
}
|
|
908
|
+
),
|
|
909
|
+
fieldState.error ? /* @__PURE__ */ jsx("span", { role: "alert", className: "mosaic-forms-radiogroup-error text-sm text-red-600", children: fieldState.error.message }) : null
|
|
910
|
+
] });
|
|
911
|
+
} });
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// src/components/forms/Select.logic.ts
|
|
915
|
+
function getTriggerId(instanceId) {
|
|
916
|
+
return `${instanceId}-trigger`;
|
|
917
|
+
}
|
|
918
|
+
function getListboxId(instanceId) {
|
|
919
|
+
return `${instanceId}-listbox`;
|
|
920
|
+
}
|
|
921
|
+
function getOptionId(instanceId, value) {
|
|
922
|
+
return `${instanceId}-opt-${value}`;
|
|
923
|
+
}
|
|
924
|
+
function filterOptions(options, query) {
|
|
925
|
+
const q = query.trim().toLowerCase();
|
|
926
|
+
if (q === "") return options;
|
|
927
|
+
return options.filter((o) => o.label.toLowerCase().includes(q));
|
|
928
|
+
}
|
|
929
|
+
function indexOfValue(options, value) {
|
|
930
|
+
if (value == null) return -1;
|
|
931
|
+
return options.findIndex((o) => o.value === value);
|
|
932
|
+
}
|
|
933
|
+
function findNextEnabledIndex2(options, startIndex, direction) {
|
|
934
|
+
const len = options.length;
|
|
935
|
+
if (len === 0) return -1;
|
|
936
|
+
let i = startIndex;
|
|
937
|
+
for (let tries = 0; tries < len; tries++) {
|
|
938
|
+
i = (i + direction + len) % len;
|
|
939
|
+
if (!options[i]?.disabled) return i;
|
|
940
|
+
}
|
|
941
|
+
return -1;
|
|
942
|
+
}
|
|
943
|
+
function findFirstEnabledIndex2(options) {
|
|
944
|
+
const idx = options.findIndex((o) => !o.disabled);
|
|
945
|
+
return idx === -1 ? -1 : idx;
|
|
946
|
+
}
|
|
947
|
+
function findLastEnabledIndex2(options) {
|
|
948
|
+
for (let i = options.length - 1; i >= 0; i--) {
|
|
949
|
+
if (!options[i]?.disabled) return i;
|
|
950
|
+
}
|
|
951
|
+
return -1;
|
|
952
|
+
}
|
|
953
|
+
function findByTypeAhead(options, buffer) {
|
|
954
|
+
if (buffer === "") return -1;
|
|
955
|
+
const needle = buffer.toLowerCase();
|
|
956
|
+
for (let i = 0; i < options.length; i++) {
|
|
957
|
+
const o = options[i];
|
|
958
|
+
if (!o || o.disabled) continue;
|
|
959
|
+
if (o.label.toLowerCase().startsWith(needle)) return i;
|
|
960
|
+
}
|
|
961
|
+
return -1;
|
|
962
|
+
}
|
|
963
|
+
function getTriggerClasses(isDisabled, isOpen) {
|
|
964
|
+
const base = "inline-flex items-center justify-between gap-2 w-full rounded-md border px-3 py-2 text-sm transition-colors focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600";
|
|
965
|
+
const state = isDisabled ? "border-slate-200 text-slate-400 bg-slate-50 cursor-not-allowed" : isOpen ? "border-blue-600 bg-white text-slate-900" : "border-slate-300 bg-white text-slate-900 hover:border-slate-400";
|
|
966
|
+
return `${base} ${state}`;
|
|
967
|
+
}
|
|
968
|
+
function getListboxClasses() {
|
|
969
|
+
return "absolute z-50 mt-1 max-h-60 w-full overflow-auto rounded-md border border-slate-200 bg-white py-1 shadow-lg focus:outline-none";
|
|
970
|
+
}
|
|
971
|
+
function getOptionClasses(isActive, isSelected, isDisabled) {
|
|
972
|
+
const base = "flex items-center px-3 py-2 text-sm select-none";
|
|
973
|
+
if (isDisabled) return `${base} text-slate-400 cursor-not-allowed`;
|
|
974
|
+
const active = isActive ? "bg-blue-50 text-blue-900" : "text-slate-900";
|
|
975
|
+
const selected = isSelected ? "font-medium" : "";
|
|
976
|
+
return `${base} ${active} ${selected} cursor-pointer`;
|
|
977
|
+
}
|
|
978
|
+
function getSearchInputClasses() {
|
|
979
|
+
return "w-full px-3 py-2 text-sm border-b border-slate-200 focus:outline-none focus:border-blue-600";
|
|
980
|
+
}
|
|
981
|
+
function resolveSelectedLabel(options, value) {
|
|
982
|
+
if (value == null) return null;
|
|
983
|
+
const o = options.find((opt) => opt.value === value);
|
|
984
|
+
return o?.label ?? null;
|
|
985
|
+
}
|
|
986
|
+
function Select({
|
|
987
|
+
name,
|
|
988
|
+
label,
|
|
989
|
+
options,
|
|
990
|
+
placeholder,
|
|
991
|
+
disabled = false,
|
|
992
|
+
searchable = false,
|
|
993
|
+
className,
|
|
994
|
+
errorMessageMap
|
|
995
|
+
}) {
|
|
996
|
+
const reactId = useId();
|
|
997
|
+
const instanceId = `mosaic-select-${reactId.replace(/[^a-zA-Z0-9-]/g, "")}`;
|
|
998
|
+
const triggerId = getTriggerId(instanceId);
|
|
999
|
+
const listboxId = getListboxId(instanceId);
|
|
1000
|
+
const labelId = `${instanceId}-label`;
|
|
1001
|
+
const errorId = `${instanceId}-error`;
|
|
1002
|
+
return /* @__PURE__ */ jsx(FormField, { name, children: ({ field, fieldState }) => {
|
|
1003
|
+
const hasError = Boolean(fieldState.error);
|
|
1004
|
+
return /* @__PURE__ */ jsx(
|
|
1005
|
+
SelectInner,
|
|
1006
|
+
{
|
|
1007
|
+
field,
|
|
1008
|
+
label,
|
|
1009
|
+
labelId,
|
|
1010
|
+
triggerId,
|
|
1011
|
+
listboxId,
|
|
1012
|
+
errorId,
|
|
1013
|
+
instanceId,
|
|
1014
|
+
options,
|
|
1015
|
+
placeholder,
|
|
1016
|
+
disabled,
|
|
1017
|
+
searchable,
|
|
1018
|
+
className,
|
|
1019
|
+
hasError,
|
|
1020
|
+
children: /* @__PURE__ */ jsx(
|
|
1021
|
+
ErrorDisplay,
|
|
1022
|
+
{
|
|
1023
|
+
error: fieldState.error ? {
|
|
1024
|
+
type: fieldState.error.type ?? "validation",
|
|
1025
|
+
message: fieldState.error.message
|
|
1026
|
+
} : void 0,
|
|
1027
|
+
messageMap: errorMessageMap,
|
|
1028
|
+
className: "mosaic-forms-error"
|
|
1029
|
+
}
|
|
1030
|
+
)
|
|
1031
|
+
}
|
|
1032
|
+
);
|
|
1033
|
+
} });
|
|
1034
|
+
}
|
|
1035
|
+
function SelectInner({
|
|
1036
|
+
field,
|
|
1037
|
+
label,
|
|
1038
|
+
labelId,
|
|
1039
|
+
triggerId,
|
|
1040
|
+
listboxId,
|
|
1041
|
+
errorId,
|
|
1042
|
+
instanceId,
|
|
1043
|
+
options,
|
|
1044
|
+
placeholder,
|
|
1045
|
+
disabled,
|
|
1046
|
+
searchable,
|
|
1047
|
+
className,
|
|
1048
|
+
hasError,
|
|
1049
|
+
children
|
|
1050
|
+
}) {
|
|
1051
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
1052
|
+
const [query, setQuery] = useState("");
|
|
1053
|
+
const [activeIndex, setActiveIndex] = useState(-1);
|
|
1054
|
+
const triggerRef = useRef(null);
|
|
1055
|
+
const searchRef = useRef(null);
|
|
1056
|
+
const typeAheadRef = useRef({
|
|
1057
|
+
buffer: "",
|
|
1058
|
+
timer: null
|
|
1059
|
+
});
|
|
1060
|
+
const visibleOptions = useMemo(
|
|
1061
|
+
() => searchable ? filterOptions(options, query) : options,
|
|
1062
|
+
[options, query, searchable]
|
|
1063
|
+
);
|
|
1064
|
+
useEffect(() => {
|
|
1065
|
+
if (!isOpen) return;
|
|
1066
|
+
const selectedIdx = indexOfValue(visibleOptions, field.value);
|
|
1067
|
+
if (selectedIdx >= 0 && !visibleOptions[selectedIdx]?.disabled) {
|
|
1068
|
+
setActiveIndex(selectedIdx);
|
|
1069
|
+
} else {
|
|
1070
|
+
setActiveIndex(findFirstEnabledIndex2(visibleOptions));
|
|
1071
|
+
}
|
|
1072
|
+
}, [isOpen, visibleOptions, field.value]);
|
|
1073
|
+
const closePopup = useCallback((restoreFocus = true) => {
|
|
1074
|
+
setIsOpen(false);
|
|
1075
|
+
setQuery("");
|
|
1076
|
+
if (restoreFocus) triggerRef.current?.focus();
|
|
1077
|
+
}, []);
|
|
1078
|
+
const commitOption = useCallback(
|
|
1079
|
+
(idx) => {
|
|
1080
|
+
const opt = visibleOptions[idx];
|
|
1081
|
+
if (!opt || opt.disabled) return;
|
|
1082
|
+
field.onChange(opt.value);
|
|
1083
|
+
closePopup(true);
|
|
1084
|
+
field.onBlur();
|
|
1085
|
+
},
|
|
1086
|
+
[visibleOptions, field, closePopup]
|
|
1087
|
+
);
|
|
1088
|
+
const handleKeyDown = useCallback(
|
|
1089
|
+
(e) => {
|
|
1090
|
+
if (disabled) return;
|
|
1091
|
+
if (!isOpen) {
|
|
1092
|
+
if (e.key === "Enter" || e.key === " " || e.key === "ArrowDown" || e.key === "ArrowUp") {
|
|
1093
|
+
e.preventDefault();
|
|
1094
|
+
setIsOpen(true);
|
|
1095
|
+
}
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
switch (e.key) {
|
|
1099
|
+
case "ArrowDown": {
|
|
1100
|
+
e.preventDefault();
|
|
1101
|
+
const start = activeIndex < 0 ? -1 : activeIndex;
|
|
1102
|
+
const next = findNextEnabledIndex2(visibleOptions, start, 1);
|
|
1103
|
+
if (next >= 0) setActiveIndex(next);
|
|
1104
|
+
break;
|
|
1105
|
+
}
|
|
1106
|
+
case "ArrowUp": {
|
|
1107
|
+
e.preventDefault();
|
|
1108
|
+
const start = activeIndex < 0 ? visibleOptions.length : activeIndex;
|
|
1109
|
+
const prev = findNextEnabledIndex2(visibleOptions, start, -1);
|
|
1110
|
+
if (prev >= 0) setActiveIndex(prev);
|
|
1111
|
+
break;
|
|
1112
|
+
}
|
|
1113
|
+
case "Home": {
|
|
1114
|
+
e.preventDefault();
|
|
1115
|
+
const first = findFirstEnabledIndex2(visibleOptions);
|
|
1116
|
+
if (first >= 0) setActiveIndex(first);
|
|
1117
|
+
break;
|
|
1118
|
+
}
|
|
1119
|
+
case "End": {
|
|
1120
|
+
e.preventDefault();
|
|
1121
|
+
const last = findLastEnabledIndex2(visibleOptions);
|
|
1122
|
+
if (last >= 0) setActiveIndex(last);
|
|
1123
|
+
break;
|
|
1124
|
+
}
|
|
1125
|
+
case "Enter": {
|
|
1126
|
+
e.preventDefault();
|
|
1127
|
+
if (activeIndex >= 0) commitOption(activeIndex);
|
|
1128
|
+
break;
|
|
1129
|
+
}
|
|
1130
|
+
case "Escape": {
|
|
1131
|
+
e.preventDefault();
|
|
1132
|
+
closePopup(true);
|
|
1133
|
+
break;
|
|
1134
|
+
}
|
|
1135
|
+
default: {
|
|
1136
|
+
if (!searchable && e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
|
|
1137
|
+
e.preventDefault();
|
|
1138
|
+
const ta = typeAheadRef.current;
|
|
1139
|
+
ta.buffer += e.key.toLowerCase();
|
|
1140
|
+
if (ta.timer) clearTimeout(ta.timer);
|
|
1141
|
+
ta.timer = setTimeout(() => {
|
|
1142
|
+
ta.buffer = "";
|
|
1143
|
+
}, 500);
|
|
1144
|
+
const idx = findByTypeAhead(visibleOptions, ta.buffer);
|
|
1145
|
+
if (idx >= 0) setActiveIndex(idx);
|
|
1146
|
+
}
|
|
1147
|
+
break;
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
},
|
|
1151
|
+
[activeIndex, closePopup, commitOption, disabled, isOpen, searchable, visibleOptions]
|
|
1152
|
+
);
|
|
1153
|
+
const rootRef = useRef(null);
|
|
1154
|
+
useEffect(() => {
|
|
1155
|
+
if (!isOpen) return;
|
|
1156
|
+
function onDocClick(e) {
|
|
1157
|
+
if (!rootRef.current) return;
|
|
1158
|
+
if (!rootRef.current.contains(e.target)) {
|
|
1159
|
+
setIsOpen(false);
|
|
1160
|
+
setQuery("");
|
|
1161
|
+
field.onBlur();
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
document.addEventListener("mousedown", onDocClick);
|
|
1165
|
+
return () => document.removeEventListener("mousedown", onDocClick);
|
|
1166
|
+
}, [isOpen, field]);
|
|
1167
|
+
const selectedLabel = resolveSelectedLabel(options, field.value);
|
|
1168
|
+
const activeId = activeIndex >= 0 ? getOptionId(instanceId, visibleOptions[activeIndex]?.value ?? "") : void 0;
|
|
1169
|
+
return /* @__PURE__ */ jsxs("div", { ref: rootRef, className: className ?? "relative inline-block w-full", children: [
|
|
1170
|
+
/* @__PURE__ */ jsx(
|
|
1171
|
+
"label",
|
|
1172
|
+
{
|
|
1173
|
+
id: labelId,
|
|
1174
|
+
htmlFor: triggerId,
|
|
1175
|
+
className: "block text-sm font-medium text-slate-700 mb-1",
|
|
1176
|
+
children: label
|
|
1177
|
+
}
|
|
1178
|
+
),
|
|
1179
|
+
/* @__PURE__ */ jsxs(
|
|
1180
|
+
"button",
|
|
1181
|
+
{
|
|
1182
|
+
type: "button",
|
|
1183
|
+
id: triggerId,
|
|
1184
|
+
ref: (el) => {
|
|
1185
|
+
triggerRef.current = el;
|
|
1186
|
+
field.ref(el);
|
|
1187
|
+
},
|
|
1188
|
+
role: "combobox",
|
|
1189
|
+
"aria-haspopup": "listbox",
|
|
1190
|
+
"aria-expanded": isOpen,
|
|
1191
|
+
"aria-controls": listboxId,
|
|
1192
|
+
"aria-labelledby": labelId,
|
|
1193
|
+
"aria-activedescendant": isOpen ? activeId : void 0,
|
|
1194
|
+
"aria-invalid": hasError || void 0,
|
|
1195
|
+
"aria-describedby": hasError ? errorId : void 0,
|
|
1196
|
+
disabled,
|
|
1197
|
+
className: getTriggerClasses(disabled, isOpen),
|
|
1198
|
+
onClick: () => !disabled && setIsOpen((o) => !o),
|
|
1199
|
+
onKeyDown: handleKeyDown,
|
|
1200
|
+
onBlur: (e) => {
|
|
1201
|
+
if (rootRef.current?.contains(e.relatedTarget)) return;
|
|
1202
|
+
if (!isOpen) field.onBlur();
|
|
1203
|
+
},
|
|
1204
|
+
children: [
|
|
1205
|
+
/* @__PURE__ */ jsx("span", { className: selectedLabel ? "" : "text-slate-400", children: selectedLabel ?? placeholder ?? "" }),
|
|
1206
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u25BE" })
|
|
1207
|
+
]
|
|
1208
|
+
}
|
|
1209
|
+
),
|
|
1210
|
+
isOpen ? /* @__PURE__ */ jsxs("div", { className: getListboxClasses(), children: [
|
|
1211
|
+
searchable ? /* @__PURE__ */ jsx(
|
|
1212
|
+
"input",
|
|
1213
|
+
{
|
|
1214
|
+
ref: searchRef,
|
|
1215
|
+
type: "text",
|
|
1216
|
+
className: getSearchInputClasses(),
|
|
1217
|
+
value: query,
|
|
1218
|
+
onChange: (e) => setQuery(e.target.value),
|
|
1219
|
+
onKeyDown: handleKeyDown,
|
|
1220
|
+
placeholder: "Search\u2026",
|
|
1221
|
+
autoFocus: true,
|
|
1222
|
+
"aria-label": "Search options"
|
|
1223
|
+
}
|
|
1224
|
+
) : null,
|
|
1225
|
+
/* @__PURE__ */ jsx("ul", { id: listboxId, role: "listbox", "aria-labelledby": labelId, tabIndex: -1, children: visibleOptions.length === 0 ? /* @__PURE__ */ jsx("li", { className: "px-3 py-2 text-sm text-slate-500", role: "presentation", children: "No options" }) : visibleOptions.map((opt, idx) => {
|
|
1226
|
+
const isSelected = opt.value === field.value;
|
|
1227
|
+
const isActive = idx === activeIndex;
|
|
1228
|
+
return (
|
|
1229
|
+
// biome-ignore lint/a11y/useFocusableInteractive: APG combobox — options are managed via aria-activedescendant on the combobox and are intentionally not in the tab order
|
|
1230
|
+
/* @__PURE__ */ jsx(
|
|
1231
|
+
"li",
|
|
1232
|
+
{
|
|
1233
|
+
id: getOptionId(instanceId, opt.value),
|
|
1234
|
+
role: "option",
|
|
1235
|
+
"aria-selected": isSelected,
|
|
1236
|
+
"aria-disabled": opt.disabled || void 0,
|
|
1237
|
+
className: getOptionClasses(isActive, isSelected, !!opt.disabled),
|
|
1238
|
+
onMouseDown: (e) => {
|
|
1239
|
+
e.preventDefault();
|
|
1240
|
+
if (!opt.disabled) commitOption(idx);
|
|
1241
|
+
},
|
|
1242
|
+
onMouseEnter: () => {
|
|
1243
|
+
if (!opt.disabled) setActiveIndex(idx);
|
|
1244
|
+
},
|
|
1245
|
+
children: opt.label
|
|
1246
|
+
},
|
|
1247
|
+
opt.value
|
|
1248
|
+
)
|
|
1249
|
+
);
|
|
1250
|
+
}) })
|
|
1251
|
+
] }) : null,
|
|
1252
|
+
/* @__PURE__ */ jsx("div", { id: errorId, children })
|
|
1253
|
+
] });
|
|
1254
|
+
}
|
|
1255
|
+
var FieldErrorShapeSchema = z.object({
|
|
1256
|
+
type: z.string(),
|
|
1257
|
+
message: z.string().optional()
|
|
1258
|
+
});
|
|
1259
|
+
var ErrorDisplayPropsSchema = z.object({
|
|
1260
|
+
error: FieldErrorShapeSchema.optional(),
|
|
1261
|
+
messageMap: z.record(z.string(), z.string()).optional()
|
|
1262
|
+
});
|
|
1263
|
+
var FormFieldPropsSchema = z.object({
|
|
1264
|
+
name: z.string().min(1)
|
|
1265
|
+
});
|
|
1266
|
+
var FormProviderPropsSchema = z.object({
|
|
1267
|
+
name: z.string().optional()
|
|
1268
|
+
});
|
|
1269
|
+
var InputTypeSchema = z.enum(["text", "email", "password", "number", "url"]);
|
|
1270
|
+
var InputPropsSchema = z.object({
|
|
1271
|
+
name: z.string().min(1),
|
|
1272
|
+
type: InputTypeSchema.default("text").optional(),
|
|
1273
|
+
placeholder: z.string().optional(),
|
|
1274
|
+
disabled: z.boolean().optional(),
|
|
1275
|
+
autoComplete: z.string().optional(),
|
|
1276
|
+
label: z.string().min(1)
|
|
1277
|
+
});
|
|
1278
|
+
var TextareaPropsSchema = z.object({
|
|
1279
|
+
name: z.string().min(1),
|
|
1280
|
+
rows: z.number().int().min(1).default(3),
|
|
1281
|
+
maxLength: z.number().int().min(1).optional(),
|
|
1282
|
+
autoResize: z.boolean().default(false),
|
|
1283
|
+
placeholder: z.string().optional(),
|
|
1284
|
+
disabled: z.boolean().optional(),
|
|
1285
|
+
label: z.string().optional()
|
|
1286
|
+
});
|
|
1287
|
+
function validateTextareaProps(raw) {
|
|
1288
|
+
return TextareaPropsSchema.parse(raw);
|
|
1289
|
+
}
|
|
1290
|
+
var FieldArrayPropsSchema = z.object({
|
|
1291
|
+
name: z.string().min(1)
|
|
1292
|
+
});
|
|
1293
|
+
var UseFieldArrayOptionsSchema = z.object({
|
|
1294
|
+
name: z.string().min(1)
|
|
1295
|
+
});
|
|
1296
|
+
var CheckboxPropsSchema = z.object({
|
|
1297
|
+
name: z.string().min(1),
|
|
1298
|
+
label: z.string().min(1),
|
|
1299
|
+
disabled: z.boolean().optional(),
|
|
1300
|
+
indeterminate: z.boolean().optional(),
|
|
1301
|
+
description: z.string().optional()
|
|
1302
|
+
});
|
|
1303
|
+
function validateCheckboxProps(raw) {
|
|
1304
|
+
return CheckboxPropsSchema.parse(raw);
|
|
1305
|
+
}
|
|
1306
|
+
var SelectOptionSchema = z.object({
|
|
1307
|
+
value: z.string().min(1),
|
|
1308
|
+
label: z.string().min(1),
|
|
1309
|
+
disabled: z.boolean().optional().default(false)
|
|
1310
|
+
});
|
|
1311
|
+
var SelectPropsSchema = z.object({
|
|
1312
|
+
name: z.string().min(1),
|
|
1313
|
+
label: z.string().min(1),
|
|
1314
|
+
options: z.array(SelectOptionSchema).min(1),
|
|
1315
|
+
placeholder: z.string().optional(),
|
|
1316
|
+
disabled: z.boolean().optional().default(false),
|
|
1317
|
+
searchable: z.boolean().optional().default(false),
|
|
1318
|
+
locale: z.enum(["en", "fr"]).default("en")
|
|
1319
|
+
});
|
|
1320
|
+
function validateSelectProps(raw) {
|
|
1321
|
+
return SelectPropsSchema.parse(raw);
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
export { Checkbox, CheckboxPropsSchema, DEFAULT_TEXTAREA_ROWS, ErrorDisplay, ErrorDisplayPropsSchema, FieldArray, FieldArrayPropsSchema, FieldErrorShapeSchema, FormField, FormFieldPropsSchema, FormProvider, FormProviderPropsSchema, Input, InputPropsSchema, InputTypeSchema, MosaicFormModeSchema, MultiSelect, MultiSelectOptionSchema, MultiSelectPropsSchema, RadioGroup, RadioGroupOptionSchema, RadioGroupPropsSchema, Select, SelectOptionSchema, SelectPropsSchema, SubmitButton, SubmitButtonPropsSchema, Textarea, TextareaPropsSchema, UseFieldArrayOptionsSchema, UseMosaicFormOptionsSchema, buildAddAriaLabel, buildListAriaLabel, buildRemoveAriaLabel, clampToMaxLength, computeAutoResizeHeight, findFirstEnabledIndex, findLastEnabledIndex, findNextEnabledIndex, findSelectedIndex, getGroupClasses, getGroupLabelId, getNavKeys, getOptionDescriptionId, getOptionLabelId, getOptionRowClasses, getRadioControlClasses, getRovingTabIndex, isActivationKey, nextFocusIndexAfterRemove, resolveAriaChecked, resolveDescribedBy, resolveRows, useFieldArray, useMosaicForm, useMosaicFormContext, validateCheckboxProps, validateRadioGroupProps, validateSelectProps, validateTextareaProps };
|
|
1325
|
+
//# sourceMappingURL=forms.js.map
|
|
1326
|
+
//# sourceMappingURL=forms.js.map
|