iryx-ui 0.0.1 → 0.1.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.
Files changed (57) hide show
  1. package/README.md +161 -2
  2. package/dist/component-names.d.ts +1 -1
  3. package/dist/component-names.js +13 -1
  4. package/dist/components/App.js +5 -0
  5. package/dist/components/App.vue.d.ts +36 -0
  6. package/dist/components/App.vue_vue_type_script_setup_true_lang.js +51 -0
  7. package/dist/components/Button.vue.d.ts +1 -0
  8. package/dist/components/Button.vue_vue_type_script_setup_true_lang.js +8 -5
  9. package/dist/components/Checkbox.js +5 -0
  10. package/dist/components/Checkbox.vue.d.ts +46 -0
  11. package/dist/components/Checkbox.vue_vue_type_script_setup_true_lang.js +79 -0
  12. package/dist/components/Form.js +5 -0
  13. package/dist/components/Form.vue.d.ts +54 -0
  14. package/dist/components/Form.vue_vue_type_script_setup_true_lang.js +79 -0
  15. package/dist/components/FormField.js +5 -0
  16. package/dist/components/FormField.vue.d.ts +46 -0
  17. package/dist/components/FormField.vue_vue_type_script_setup_true_lang.js +93 -0
  18. package/dist/components/Input.js +5 -0
  19. package/dist/components/Input.vue.d.ts +29 -0
  20. package/dist/components/Input.vue_vue_type_script_setup_true_lang.js +57 -0
  21. package/dist/components/Label.js +5 -0
  22. package/dist/components/Label.vue.d.ts +23 -0
  23. package/dist/components/Label.vue_vue_type_script_setup_true_lang.js +41 -0
  24. package/dist/components/RadioGroup.js +5 -0
  25. package/dist/components/RadioGroup.vue.d.ts +41 -0
  26. package/dist/components/RadioGroup.vue_vue_type_script_setup_true_lang.js +72 -0
  27. package/dist/components/Select.js +5 -0
  28. package/dist/components/Select.vue.d.ts +42 -0
  29. package/dist/components/Select.vue_vue_type_script_setup_true_lang.js +84 -0
  30. package/dist/components/Switch.vue.d.ts +25 -2
  31. package/dist/components/Switch.vue_vue_type_script_setup_true_lang.js +45 -14
  32. package/dist/components/Textarea.js +5 -0
  33. package/dist/components/Textarea.vue.d.ts +28 -0
  34. package/dist/components/Textarea.vue_vue_type_script_setup_true_lang.js +57 -0
  35. package/dist/components/index.d.ts +9 -0
  36. package/dist/components/index.js +24 -6
  37. package/dist/composables/form.d.ts +67 -0
  38. package/dist/composables/form.js +20 -0
  39. package/dist/index.d.ts +10 -0
  40. package/dist/index.js +24 -9
  41. package/dist/plugin.js +8 -8
  42. package/dist/theme/checkbox.d.ts +89 -0
  43. package/dist/theme/checkbox.js +37 -0
  44. package/dist/theme/form.d.ts +68 -0
  45. package/dist/theme/form.js +14 -0
  46. package/dist/theme/index.d.ts +5 -0
  47. package/dist/theme/input.d.ts +60 -0
  48. package/dist/theme/input.js +27 -0
  49. package/dist/theme/label.d.ts +14 -0
  50. package/dist/theme/label.js +8 -0
  51. package/dist/theme/radio-group.d.ts +71 -0
  52. package/dist/theme/radio-group.js +30 -0
  53. package/dist/theme/select.d.ts +56 -0
  54. package/dist/theme/select.js +20 -0
  55. package/dist/theme/switch.d.ts +27 -0
  56. package/dist/theme/switch.js +5 -1
  57. package/package.json +8 -8
package/README.md CHANGED
@@ -85,6 +85,42 @@ import { ArrowRight, Search } from 'lucide-vue-next'
85
85
 
86
86
  When `loading` is set, a spinner appears in the leading position.
87
87
 
88
+ ## The `IApp` wrapper
89
+
90
+ Wrap your app once to configure everything below it. Unlike the plugin options
91
+ (which are read at install time), `IApp`'s props are **reactive** — change them
92
+ and every component updates.
93
+
94
+ ```vue
95
+ <template>
96
+ <IApp theme="emerald" appearance="system" dir="ltr">
97
+ <RouterView />
98
+ </IApp>
99
+ </template>
100
+ ```
101
+
102
+ It renders **no wrapper element** by default. Pass `as` (plus `class`) if you'd
103
+ rather it own your page shell:
104
+
105
+ ```vue
106
+ <IApp as="div" class="min-h-screen bg-background text-foreground">
107
+ <RouterView />
108
+ </IApp>
109
+ ```
110
+
111
+ | Prop | What it does |
112
+ | --- | --- |
113
+ | `theme` | Applies a preset or custom theme, reactively. Removing it restores the defaults. |
114
+ | `appearance` | Startup `light` / `dark` / `system`. A stored user preference wins. Omit it and `IApp` won't touch dark mode at all. |
115
+ | `unstyled` | Strips built-in classes from every descendant. |
116
+ | `dir` / `locale` / `scrollBody` / `nonce` | Forwarded to Reka UI's `ConfigProvider`, so RTL and locale-aware primitives work. |
117
+
118
+ Per-component props still win over the app config, so `<IButton :unstyled="false">`
119
+ stays styled inside an `<IApp unstyled>`.
120
+
121
+ `IApp` is optional — the plugin options and `applyTheme()` / `useAppearance()`
122
+ still work on their own.
123
+
88
124
  ## Appearance (light / dark)
89
125
 
90
126
  Dark mode is class-based: the `.dark` class on `<html>` flips every token.
@@ -181,10 +217,133 @@ app.use(createIryxUi({ unstyled: true }))
181
217
 
182
218
  | Component | Description |
183
219
  | --- | --- |
220
+ | `IApp` | Root wrapper — reactive global config, theme, appearance, RTL/locale |
221
+ | `IForm` | Validating form wrapper — any Standard Schema validator, or your own function |
222
+ | `IFormField` | Label, description, hint, help and error text around a control |
184
223
  | `IButton` | Variants (`solid`, `outline`, `ghost`, `link`), five sizes, `loading` and `block` states, polymorphic via `as` / `asChild` |
185
- | `ISwitch` | Accessible toggle built on Reka UI's Switch |
224
+ | `IInput` | Text field with `sm`/`md`/`lg` sizes, `invalid` state, `v-model` |
225
+ | `ITextarea` | Multi-line field with matching sizes and `invalid` state |
226
+ | `ILabel` | Field label with optional `required` asterisk |
227
+ | `ICheckbox` | Tri-state checkbox (`true` / `false` / `'indeterminate'`), optional `label` + `description` |
228
+ | `ISelect` | Listbox with keyboard nav and typeahead, driven by an `items` array |
229
+ | `IRadioGroup` | Radio list with labels wired up automatically; items take a `description` |
230
+ | `ISwitch` | Accessible toggle, optional `label` + `description` |
231
+
232
+ Every component supports `unstyled`, a `class` override, and multi-part ones take a `ui` prop for per-slot classes. More on the way.
233
+
234
+ ### Forms
235
+
236
+ ```vue
237
+ <script setup lang="ts">
238
+ import { ref } from 'vue'
239
+
240
+ const email = ref('')
241
+ const plan = ref('pro')
242
+ const framework = ref('vue')
243
+ </script>
244
+
245
+ <template>
246
+ <ILabel for="email" required>
247
+ Email
248
+ </ILabel>
249
+ <IInput id="email" v-model="email" type="email" placeholder="you@example.com" />
250
+
251
+ <ILabel class="gap-2">
252
+ <ICheckbox v-model="accepted" /> Accept terms
253
+ </ILabel>
254
+
255
+ <ISelect
256
+ v-model="framework"
257
+ placeholder="Pick one"
258
+ :items="['Vue', 'React', { label: 'Angular', value: 'ng', disabled: true }]"
259
+ />
260
+
261
+ <IRadioGroup v-model="plan" :items="['free', 'pro']" />
262
+ </template>
263
+ ```
264
+
265
+ `ISelect` and `IRadioGroup` accept plain strings or `{ label, value, disabled }` objects. Both also take a default slot if you'd rather compose the Reka primitives yourself.
266
+
267
+ ### Validated forms
268
+
269
+ `IForm` handles client-side validation. It accepts any [Standard Schema](https://standardschema.dev) validator — Zod 3.24+, Valibot, ArkType — so Iryx doesn't depend on a validation library. Wrap each control in an `IFormField` with a `name` matching the schema path and errors wire themselves up.
270
+
271
+ ```vue
272
+ <script setup lang="ts">
273
+ import { reactive } from 'vue'
274
+ import * as z from 'zod'
275
+
276
+ const schema = z.object({
277
+ email: z.string().email('That doesn\'t look like an email'),
278
+ password: z.string().min(8, 'Use at least 8 characters'),
279
+ })
280
+
281
+ const state = reactive({ email: '', password: '' })
282
+
283
+ function onSubmit(event) {
284
+ console.log(event.data) // only fires when valid
285
+ }
286
+ </script>
287
+
288
+ <template>
289
+ <IForm :state="state" :schema="schema" @submit="onSubmit">
290
+ <IFormField name="email" label="Email" required description="We'll never share it.">
291
+ <IInput v-model="state.email" type="email" />
292
+ </IFormField>
293
+ <IFormField name="password" label="Password" help="At least 8 characters.">
294
+ <IInput v-model="state.password" type="password" />
295
+ </IFormField>
296
+ <IButton type="submit">
297
+ Create account
298
+ </IButton>
299
+ </IForm>
300
+ </template>
301
+ ```
302
+
303
+ The control inside a field automatically inherits its `id`, invalid styling and `aria-describedby` — no wiring needed. On a failed submit, focus moves to the first invalid control.
304
+
305
+ **Validation timing** — `validate-on` defaults to `['blur', 'change']`; submit always validates everything.
306
+
307
+ **Custom rules** — pass `validate` for anything a schema can't express (it runs alongside the schema, and works without one):
308
+
309
+ ```vue
310
+ <IForm
311
+ :state="state"
312
+ :validate="s => s.email.endsWith('@corp.com') ? [] : [{ name: 'email', message: 'Must be a work email' }]"
313
+ />
314
+ ```
315
+
316
+ **Server errors and manual control** — grab a template ref to the form and call `validate()`, `clear(name?)` or `setErrors()`. `IFormField` also takes a plain `error` prop that bypasses validation entirely.
317
+
318
+ ### Labels and descriptions
319
+
320
+ `ICheckbox` and `ISwitch` render bare by default. Give them a `label` and/or `description` and they render a wired-up layout instead — the text is clickable, and the description is linked with `aria-describedby`. `IRadioGroup` items take a `description` too.
321
+
322
+ ```vue
323
+ <template>
324
+ <ICheckbox
325
+ v-model="accepted"
326
+ label="Accept terms"
327
+ description="You agree to the terms of service and privacy policy."
328
+ />
329
+
330
+ <ISwitch
331
+ v-model="notify"
332
+ label="Push notifications"
333
+ description="Send alerts to this device."
334
+ />
335
+
336
+ <IRadioGroup
337
+ v-model="plan"
338
+ :items="[
339
+ { label: 'Free', value: 'free', description: 'Up to 3 projects.' },
340
+ { label: 'Pro', value: 'pro', description: 'Unlimited projects.' },
341
+ ]"
342
+ />
343
+ </template>
344
+ ```
186
345
 
187
- More on the way.
346
+ Use the `#label` / `#description` slots instead of the props when you need markup (a link, a badge) inside the text.
188
347
 
189
348
  ## License
190
349
 
@@ -3,5 +3,5 @@
3
3
  * Consumed by the Vue plugin (global registration) and the Nuxt module
4
4
  * (auto-imports). Keep in sync with `src/components/index.ts`.
5
5
  */
6
- export declare const componentNames: readonly ["Button", "Switch"];
6
+ export declare const componentNames: readonly ["App", "Button", "Checkbox", "Form", "FormField", "Input", "Label", "RadioGroup", "Select", "Switch", "Textarea"];
7
7
  export type ComponentName = (typeof componentNames)[number];
@@ -1,4 +1,16 @@
1
1
  //#region src/component-names.ts
2
- var e = ["Button", "Switch"];
2
+ var e = [
3
+ "App",
4
+ "Button",
5
+ "Checkbox",
6
+ "Form",
7
+ "FormField",
8
+ "Input",
9
+ "Label",
10
+ "RadioGroup",
11
+ "Select",
12
+ "Switch",
13
+ "Textarea"
14
+ ];
3
15
  //#endregion
4
16
  export { e as componentNames };
@@ -0,0 +1,5 @@
1
+ import e from "./App.vue_vue_type_script_setup_true_lang.js";
2
+ //#region src/components/App.vue
3
+ var t = e;
4
+ //#endregion
5
+ export { t as default };
@@ -0,0 +1,36 @@
1
+ import type { ConfigProviderProps } from 'reka-ui';
2
+ import type { Appearance } from '../composables/appearance';
3
+ import type { Theme, ThemePresetName } from '../theme/presets';
4
+ export interface AppProps extends /* @vue-ignore */ ConfigProviderProps {
5
+ /**
6
+ * Render bare Reka UI primitives without Iryx's classes, for everything
7
+ * below. Reactive, unlike the install-time plugin option.
8
+ */
9
+ unstyled?: boolean;
10
+ /** Colour theme: a preset name (`'emerald'`…) or a custom theme. */
11
+ theme?: Theme | ThemePresetName;
12
+ /** Startup appearance. A preference the user already stored wins over this. */
13
+ appearance?: Appearance;
14
+ /**
15
+ * Element to render as the root. Defaults to `template`, i.e. no wrapper —
16
+ * pass `div` (with `class`) if you want App to own your page shell.
17
+ */
18
+ as?: string;
19
+ class?: string;
20
+ }
21
+ declare var __VLS_14: {};
22
+ type __VLS_Slots = {} & {
23
+ default?: (props: typeof __VLS_14) => any;
24
+ };
25
+ declare const __VLS_base: import("vue").DefineComponent<AppProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<AppProps> & Readonly<{}>, {
26
+ unstyled: boolean;
27
+ as: string;
28
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
29
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
30
+ declare const _default: typeof __VLS_export;
31
+ export default _default;
32
+ type __VLS_WithSlots<T, S> = T & {
33
+ new (): {
34
+ $slots: S;
35
+ };
36
+ };
@@ -0,0 +1,51 @@
1
+ import { initAppearance as e, useAppearance as t } from "../composables/appearance.js";
2
+ import { defaultConfig as n, iryxUiConfigKey as r } from "../config.js";
3
+ import { applyTheme as i, clearTheme as a } from "../theme/presets.js";
4
+ import { computed as o, createBlock as s, createVNode as c, defineComponent as l, guardReactiveProps as u, normalizeClass as d, normalizeProps as f, openBlock as p, provide as m, reactive as h, renderSlot as g, unref as _, watch as v, withCtx as y } from "vue";
5
+ import { ConfigProvider as b, Primitive as x } from "reka-ui";
6
+ //#region src/components/App.vue?vue&type=script&setup=true&lang.ts
7
+ var S = /*@__PURE__*/ l({
8
+ __name: "App",
9
+ props: {
10
+ unstyled: {
11
+ type: Boolean,
12
+ default: void 0
13
+ },
14
+ theme: {},
15
+ appearance: {},
16
+ as: { default: "template" },
17
+ class: {}
18
+ },
19
+ setup(l) {
20
+ let S = l;
21
+ m(r, h({
22
+ ...n,
23
+ unstyled: o(() => S.unstyled ?? n.unstyled)
24
+ })), v(() => S.theme, (e) => {
25
+ e ? i(e) : a();
26
+ }, { immediate: !0 });
27
+ let C = !1;
28
+ v(() => S.appearance, (n) => {
29
+ n && (C ? t().setAppearance(n) : (e(n), C = !0));
30
+ }, { immediate: !0 });
31
+ let w = o(() => ({
32
+ dir: S.dir,
33
+ locale: S.locale,
34
+ scrollBody: S.scrollBody,
35
+ nonce: S.nonce,
36
+ useId: S.useId
37
+ }));
38
+ return (e, t) => (p(), s(_(b), f(u(w.value)), {
39
+ default: y(() => [c(_(x), {
40
+ as: S.as,
41
+ class: d(S.class)
42
+ }, {
43
+ default: y(() => [g(e.$slots, "default")]),
44
+ _: 3
45
+ }, 8, ["as", "class"])]),
46
+ _: 3
47
+ }, 16));
48
+ }
49
+ });
50
+ //#endregion
51
+ export { S as default };
@@ -20,6 +20,7 @@ type __VLS_Slots = {} & {
20
20
  default?: (props: typeof __VLS_13) => any;
21
21
  };
22
22
  declare const __VLS_base: import("vue").DefineComponent<ButtonProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<ButtonProps> & Readonly<{}>, {
23
+ unstyled: boolean;
23
24
  as: string;
24
25
  type: "button" | "submit" | "reset";
25
26
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -1,8 +1,8 @@
1
1
  import { useIryxUiConfig as e } from "../config.js";
2
2
  import { buttonTheme as t } from "../theme/button.js";
3
3
  import { computed as n, createBlock as r, createCommentVNode as i, defineComponent as a, normalizeClass as o, openBlock as s, renderSlot as c, unref as l, withCtx as u } from "vue";
4
- import { LoaderCircle as d } from "lucide-vue-next";
5
- import { Primitive as f } from "reka-ui";
4
+ import { Primitive as d } from "reka-ui";
5
+ import { LoaderCircle as f } from "lucide-vue-next";
6
6
  //#region src/components/Button.vue?vue&type=script&setup=true&lang.ts
7
7
  var p = /*@__PURE__*/ a({
8
8
  __name: "Button",
@@ -14,7 +14,10 @@ var p = /*@__PURE__*/ a({
14
14
  block: { type: Boolean },
15
15
  loading: { type: Boolean },
16
16
  disabled: { type: Boolean },
17
- unstyled: { type: Boolean },
17
+ unstyled: {
18
+ type: Boolean,
19
+ default: void 0
20
+ },
18
21
  type: { default: "button" },
19
22
  class: {}
20
23
  },
@@ -25,14 +28,14 @@ var p = /*@__PURE__*/ a({
25
28
  block: p.block,
26
29
  class: p.class
27
30
  }));
28
- return (e, t) => (s(), r(l(f), {
31
+ return (e, t) => (s(), r(l(d), {
29
32
  as: p.as,
30
33
  "as-child": p.asChild,
31
34
  type: p.as === "button" ? p.type : void 0,
32
35
  disabled: p.disabled || p.loading || void 0,
33
36
  class: o(g.value)
34
37
  }, {
35
- default: u(() => [p.loading ? (s(), r(l(d), {
38
+ default: u(() => [p.loading ? (s(), r(l(f), {
36
39
  key: 0,
37
40
  class: "animate-spin",
38
41
  "aria-hidden": "true"
@@ -0,0 +1,5 @@
1
+ import e from "./Checkbox.vue_vue_type_script_setup_true_lang.js";
2
+ //#region src/components/Checkbox.vue
3
+ var t = e;
4
+ //#endregion
5
+ export { t as default };
@@ -0,0 +1,46 @@
1
+ import type { CheckboxRootProps } from 'reka-ui';
2
+ export interface CheckboxProps extends CheckboxRootProps {
3
+ /** Text shown beside the box. Also makes the text clickable. */
4
+ label?: string;
5
+ /** Secondary text under the label. */
6
+ description?: string;
7
+ size?: 'sm' | 'md' | 'lg';
8
+ id?: string;
9
+ /** Skip built-in classes; you take over styling entirely. */
10
+ unstyled?: boolean;
11
+ class?: string;
12
+ /** Override classes per slot, e.g. `{ indicator: 'text-black' }`. */
13
+ ui?: {
14
+ wrapper?: string;
15
+ root?: string;
16
+ indicator?: string;
17
+ content?: string;
18
+ label?: string;
19
+ description?: string;
20
+ };
21
+ }
22
+ declare var __VLS_13: {}, __VLS_31: {}, __VLS_33: {}, __VLS_48: {};
23
+ type __VLS_Slots = {} & {
24
+ default?: (props: typeof __VLS_13) => any;
25
+ } & {
26
+ label?: (props: typeof __VLS_31) => any;
27
+ } & {
28
+ description?: (props: typeof __VLS_33) => any;
29
+ } & {
30
+ default?: (props: typeof __VLS_48) => any;
31
+ };
32
+ declare const __VLS_base: import("vue").DefineComponent<CheckboxProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
33
+ "update:modelValue": (value: boolean | "indeterminate") => any;
34
+ }, string, import("vue").PublicProps, Readonly<CheckboxProps> & Readonly<{
35
+ "onUpdate:modelValue"?: ((value: boolean | "indeterminate") => any) | undefined;
36
+ }>, {
37
+ unstyled: boolean;
38
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
39
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
40
+ declare const _default: typeof __VLS_export;
41
+ export default _default;
42
+ type __VLS_WithSlots<T, S> = T & {
43
+ new (): {
44
+ $slots: S;
45
+ };
46
+ };
@@ -0,0 +1,79 @@
1
+ import { useIryxUiConfig as e } from "../config.js";
2
+ import { checkboxTheme as t } from "../theme/checkbox.js";
3
+ import { computed as n, createBlock as r, createCommentVNode as i, createElementBlock as a, createElementVNode as o, createTextVNode as s, createVNode as c, defineComponent as l, mergeProps as u, normalizeClass as d, openBlock as f, renderSlot as p, toDisplayString as m, unref as h, withCtx as g } from "vue";
4
+ import { CheckboxIndicator as _, CheckboxRoot as v, Label as y, useForwardPropsEmits as b, useId as x } from "reka-ui";
5
+ import { Check as S, Minus as C } from "lucide-vue-next";
6
+ //#region src/components/Checkbox.vue?vue&type=script&setup=true&lang.ts
7
+ var w = ["id"], T = /*@__PURE__*/ l({
8
+ __name: "Checkbox",
9
+ props: {
10
+ label: {},
11
+ description: {},
12
+ size: {},
13
+ id: {},
14
+ unstyled: {
15
+ type: Boolean,
16
+ default: void 0
17
+ },
18
+ class: {},
19
+ ui: {},
20
+ defaultValue: {},
21
+ modelValue: {},
22
+ disabled: { type: Boolean },
23
+ value: {},
24
+ trueValue: {},
25
+ falseValue: {},
26
+ asChild: { type: Boolean },
27
+ as: {},
28
+ name: {},
29
+ required: { type: Boolean }
30
+ },
31
+ emits: ["update:modelValue"],
32
+ setup(l, { emit: T }) {
33
+ let E = l, D = T, O = b(n(() => {
34
+ let { label: e, description: t, size: n, id: r, unstyled: i, class: a, ui: o, ...s } = E;
35
+ return s;
36
+ }), D), k = x(), A = n(() => E.id ?? k), j = n(() => !!(E.label || E.description)), M = e(), N = n(() => E.unstyled ?? M.unstyled), P = n(() => t({
37
+ size: E.size,
38
+ withText: j.value
39
+ })), F = n(() => N.value ? E.ui?.wrapper : P.value.wrapper({ class: E.ui?.wrapper })), I = n(() => N.value ? [E.ui?.root, E.class] : P.value.root({ class: [E.ui?.root, E.class] })), L = n(() => N.value ? E.ui?.indicator : P.value.indicator({ class: E.ui?.indicator })), R = n(() => N.value ? E.ui?.content : P.value.content({ class: E.ui?.content })), z = n(() => N.value ? E.ui?.label : P.value.label({ class: E.ui?.label })), B = n(() => N.value ? E.ui?.description : P.value.description({ class: E.ui?.description }));
40
+ return (e, t) => j.value ? (f(), a("div", {
41
+ key: 0,
42
+ class: d(F.value)
43
+ }, [c(h(v), u({ id: A.value }, h(O), {
44
+ "aria-describedby": E.description ? `${A.value}-description` : void 0,
45
+ class: I.value
46
+ }), {
47
+ default: g(() => [c(h(_), { class: d(L.value) }, {
48
+ default: g(() => [p(e.$slots, "default", {}, () => [E.modelValue === "indeterminate" ? (f(), r(h(C), { key: 0 })) : (f(), r(h(S), { key: 1 }))])]),
49
+ _: 3
50
+ }, 8, ["class"])]),
51
+ _: 3
52
+ }, 16, [
53
+ "id",
54
+ "aria-describedby",
55
+ "class"
56
+ ]), o("div", { class: d(R.value) }, [c(h(y), {
57
+ for: A.value,
58
+ class: d(z.value)
59
+ }, {
60
+ default: g(() => [p(e.$slots, "label", {}, () => [s(m(E.label), 1)])]),
61
+ _: 3
62
+ }, 8, ["for", "class"]), E.description || e.$slots.description ? (f(), a("p", {
63
+ key: 0,
64
+ id: `${A.value}-description`,
65
+ class: d(B.value)
66
+ }, [p(e.$slots, "description", {}, () => [s(m(E.description), 1)])], 10, w)) : i("", !0)], 2)], 2)) : (f(), r(h(v), u({
67
+ key: 1,
68
+ id: E.id
69
+ }, h(O), { class: I.value }), {
70
+ default: g(() => [c(h(_), { class: d(L.value) }, {
71
+ default: g(() => [p(e.$slots, "default", {}, () => [E.modelValue === "indeterminate" ? (f(), r(h(C), { key: 0 })) : (f(), r(h(S), { key: 1 }))])]),
72
+ _: 3
73
+ }, 8, ["class"])]),
74
+ _: 3
75
+ }, 16, ["id", "class"]));
76
+ }
77
+ });
78
+ //#endregion
79
+ export { T as default };
@@ -0,0 +1,5 @@
1
+ import e from "./Form.vue_vue_type_script_setup_true_lang.js";
2
+ //#region src/components/Form.vue
3
+ var t = e;
4
+ //#endregion
5
+ export { t as default };
@@ -0,0 +1,54 @@
1
+ import type { FormError, FormSubmitEvent, FormValidateOn, StandardSchemaLike } from '../composables/form';
2
+ export interface FormProps<S extends Record<string, any>> {
3
+ /** Reactive object holding the field values. */
4
+ state: S;
5
+ /** Any Standard Schema validator (Zod 3.24+, Valibot, ArkType…). */
6
+ schema?: StandardSchemaLike;
7
+ /** Custom validation, run after the schema. Return the errors you found. */
8
+ validate?: (state: S) => FormError[] | Promise<FormError[]>;
9
+ /** Which interactions re-validate a field. Submit always validates everything. */
10
+ validateOn?: FormValidateOn[];
11
+ disabled?: boolean;
12
+ class?: string;
13
+ }
14
+ declare const __VLS_export: <T extends Record<string, any>>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
15
+ props: import("vue").PublicProps & __VLS_PrettifyLocal<FormProps<T> & {
16
+ onSubmit?: ((event: FormSubmitEvent<T>) => any) | undefined;
17
+ onError?: ((errors: FormError[]) => any) | undefined;
18
+ }> & (typeof globalThis extends {
19
+ __VLS_PROPS_FALLBACK: infer P;
20
+ } ? P : {});
21
+ expose: (exposed: import("vue").ShallowUnwrapRef<{
22
+ validate: (options?: {
23
+ silent?: boolean;
24
+ }) => Promise<T | false>;
25
+ clear: (name?: string) => void;
26
+ setErrors: (next: FormError[]) => void;
27
+ errors: import("vue").Ref<{
28
+ name?: string | undefined;
29
+ message: string;
30
+ }[], FormError[] | {
31
+ name?: string | undefined;
32
+ message: string;
33
+ }[]>;
34
+ }>) => void;
35
+ attrs: any;
36
+ slots: {
37
+ default?: (props: {
38
+ errors: {
39
+ name?: string | undefined;
40
+ message: string;
41
+ }[];
42
+ }) => any;
43
+ };
44
+ emit: ((evt: "submit", event: FormSubmitEvent<T>) => void) & ((evt: "error", errors: FormError[]) => void);
45
+ }>) => import("vue").VNode & {
46
+ __ctx?: Awaited<typeof __VLS_setup>;
47
+ };
48
+ declare const _default: typeof __VLS_export;
49
+ export default _default;
50
+ type __VLS_PrettifyLocal<T> = (T extends any ? {
51
+ [K in keyof T]: T[K];
52
+ } : {
53
+ [K in keyof T as K]: T[K];
54
+ }) & {};
@@ -0,0 +1,79 @@
1
+ import { formContextKey as e, isStandardSchema as t, issuePath as n } from "../composables/form.js";
2
+ import { computed as r, createElementBlock as i, defineComponent as a, normalizeClass as o, openBlock as s, provide as c, ref as l, renderSlot as u } from "vue";
3
+ //#region src/components/Form.vue?vue&type=script&setup=true&lang.ts
4
+ var d = /*@__PURE__*/ a({
5
+ __name: "Form",
6
+ props: {
7
+ state: {},
8
+ schema: {},
9
+ validate: {},
10
+ validateOn: { default: () => ["blur", "change"] },
11
+ disabled: { type: Boolean },
12
+ class: {}
13
+ },
14
+ emits: ["submit", "error"],
15
+ setup(a, { expose: d, emit: f }) {
16
+ let p = a, m = f, h = l([]);
17
+ async function g() {
18
+ let e = [];
19
+ if (p.schema && t(p.schema)) {
20
+ let t = await p.schema["~standard"].validate(p.state);
21
+ if (t.issues) for (let r of t.issues) e.push({
22
+ name: n(r),
23
+ message: r.message
24
+ });
25
+ }
26
+ return p.validate && e.push(...await p.validate(p.state)), e;
27
+ }
28
+ async function _(e = {}) {
29
+ let t = await g();
30
+ return e.silent || (h.value = t), t.length ? !1 : p.state;
31
+ }
32
+ async function v(e) {
33
+ let t = await g();
34
+ h.value = [...h.value.filter((t) => t.name !== e), ...t.filter((t) => t.name === e)];
35
+ }
36
+ function y(e) {
37
+ h.value = e ? h.value.filter((t) => t.name !== e) : [];
38
+ }
39
+ function b(e) {
40
+ h.value = e;
41
+ }
42
+ function x(e) {
43
+ if (e) return h.value.find((t) => t.name === e)?.message;
44
+ }
45
+ async function S(e) {
46
+ e.preventDefault();
47
+ let t = e.currentTarget, n = await g();
48
+ if (h.value = n, n.length) {
49
+ m("error", n);
50
+ let e = n.find((e) => e.name)?.name;
51
+ if (e && t) {
52
+ let n = `[data-iryx-field="${CSS.escape(e)}"] :is(input,textarea,select,button,[tabindex])`;
53
+ t.querySelector(n)?.focus();
54
+ }
55
+ return;
56
+ }
57
+ m("submit", { data: p.state });
58
+ }
59
+ return c(e, {
60
+ errors: h,
61
+ validateOn: r(() => p.validateOn),
62
+ disabled: r(() => p.disabled ?? !1),
63
+ state: r(() => p.state),
64
+ errorFor: x,
65
+ validateField: v
66
+ }), d({
67
+ validate: _,
68
+ clear: y,
69
+ setErrors: b,
70
+ errors: h
71
+ }), (e, t) => (s(), i("form", {
72
+ novalidate: "",
73
+ class: o(p.class),
74
+ onSubmit: S
75
+ }, [u(e.$slots, "default", { errors: h.value })], 34));
76
+ }
77
+ });
78
+ //#endregion
79
+ export { d as default };
@@ -0,0 +1,5 @@
1
+ import e from "./FormField.vue_vue_type_script_setup_true_lang.js";
2
+ //#region src/components/FormField.vue
3
+ var t = e;
4
+ //#endregion
5
+ export { t as default };
@@ -0,0 +1,46 @@
1
+ export interface FormFieldProps {
2
+ /** Dot-path into the form state. Links this field to its validation errors. */
3
+ name?: string;
4
+ label?: string;
5
+ /** Muted text under the label, explaining the field. */
6
+ description?: string;
7
+ /** Muted text on the right of the label, e.g. "Optional". */
8
+ hint?: string;
9
+ /** Muted text under the control, shown while there's no error. */
10
+ help?: string;
11
+ required?: boolean;
12
+ /** Force an error message, bypassing the form's validation. */
13
+ error?: string;
14
+ /** Skip built-in classes; you take over styling entirely. */
15
+ unstyled?: boolean;
16
+ class?: string;
17
+ ui?: {
18
+ root?: string;
19
+ header?: string;
20
+ label?: string;
21
+ hint?: string;
22
+ description?: string;
23
+ error?: string;
24
+ help?: string;
25
+ };
26
+ }
27
+ declare var __VLS_7: {}, __VLS_9: {
28
+ id: string;
29
+ error: string | undefined;
30
+ };
31
+ type __VLS_Slots = {} & {
32
+ hint?: (props: typeof __VLS_7) => any;
33
+ } & {
34
+ default?: (props: typeof __VLS_9) => any;
35
+ };
36
+ declare const __VLS_base: import("vue").DefineComponent<FormFieldProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<FormFieldProps> & Readonly<{}>, {
37
+ unstyled: boolean;
38
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
39
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
40
+ declare const _default: typeof __VLS_export;
41
+ export default _default;
42
+ type __VLS_WithSlots<T, S> = T & {
43
+ new (): {
44
+ $slots: S;
45
+ };
46
+ };