@pindoba/react-tanstack-form 0.0.0-alpha-20260907031008 → 0.0.0-alpha-20261110151933

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/dist/index.d.ts CHANGED
@@ -1,11 +1,12 @@
1
- import { AnyFieldApi, AnyFormApi, FormFieldErrorMode, FormFieldOwnProps } from "./src/types.js";
1
+ import { AnyFieldApi, AnyFormApi, FormControlReservedAttribute, FormFieldErrorMode, FormFieldOwnProps } from "./src/types.js";
2
2
  import { FormField, FormFieldProps } from "./src/FormField.js";
3
- import { FormInput, FormInputProps } from "./src/FormInput.js";
4
- import { FormSelect, FormSelectProps } from "./src/FormSelect.js";
5
- import { FormCheckbox, FormCheckboxProps } from "./src/FormCheckbox.js";
3
+ import { FormInput, FormInputAttributes, FormInputProps } from "./src/FormInput.js";
4
+ import { FormTextarea, FormTextareaAttributes, FormTextareaProps } from "./src/FormTextarea.js";
5
+ import { FormSelect, FormSelectAttributes, FormSelectProps } from "./src/FormSelect.js";
6
+ import { FormCheckbox, FormCheckboxAttributes, FormCheckboxProps } from "./src/FormCheckbox.js";
6
7
  import { FormSubmit, FormSubmitProps } from "./src/FormSubmit.js";
7
8
  import { useFocusInvalidOnSubmit } from "./src/useFocusInvalidOnSubmit.js";
8
9
  import { getVisibleErrors, normalizeErrors } from "./src/field-errors.js";
9
10
  import { useForm, useStore } from "@tanstack/react-form";
10
11
  import { FocusInvalidOptions } from "@pindoba/core-tanstack-form";
11
- export { type AnyFieldApi, type AnyFormApi, type FocusInvalidOptions, FormCheckbox, type FormCheckboxProps, FormField, type FormFieldErrorMode, type FormFieldOwnProps, type FormFieldProps, FormInput, type FormInputProps, FormSelect, type FormSelectProps, FormSubmit, type FormSubmitProps, getVisibleErrors, normalizeErrors, useFocusInvalidOnSubmit, useForm, useStore };
12
+ export { type AnyFieldApi, type AnyFormApi, type FocusInvalidOptions, FormCheckbox, type FormCheckboxAttributes, type FormCheckboxProps, type FormControlReservedAttribute, FormField, type FormFieldErrorMode, type FormFieldOwnProps, type FormFieldProps, FormInput, type FormInputAttributes, type FormInputProps, FormSelect, type FormSelectAttributes, type FormSelectProps, FormSubmit, type FormSubmitProps, FormTextarea, type FormTextareaAttributes, type FormTextareaProps, getVisibleErrors, normalizeErrors, useFocusInvalidOnSubmit, useForm, useStore };
package/dist/index.js CHANGED
@@ -1,9 +1,10 @@
1
1
  import { getVisibleErrors, normalizeErrors } from "./src/field-errors.js";
2
2
  import { FormField } from "./src/FormField.js";
3
3
  import { FormInput } from "./src/FormInput.js";
4
+ import { FormTextarea } from "./src/FormTextarea.js";
4
5
  import { FormSelect } from "./src/FormSelect.js";
5
6
  import { FormCheckbox } from "./src/FormCheckbox.js";
6
7
  import { FormSubmit } from "./src/FormSubmit.js";
7
8
  import { useFocusInvalidOnSubmit } from "./src/useFocusInvalidOnSubmit.js";
8
9
  import { useForm, useStore } from "@tanstack/react-form";
9
- export { FormCheckbox, FormField, FormInput, FormSelect, FormSubmit, getVisibleErrors, normalizeErrors, useFocusInvalidOnSubmit, useForm, useStore };
10
+ export { FormCheckbox, FormField, FormInput, FormSelect, FormSubmit, FormTextarea, getVisibleErrors, normalizeErrors, useFocusInvalidOnSubmit, useForm, useStore };
@@ -1,22 +1,33 @@
1
- import { AnyFormApi, FormFieldOwnProps } from "./types.js";
2
- import { JSX, ReactNode } from "react";
1
+ import { AnyFormApi, FormControlReservedAttribute, FormFieldOwnProps } from "./types.js";
2
+ import React, { InputHTMLAttributes, ReactNode } from "react";
3
3
  import { FieldSize } from "@pindoba/core-field";
4
4
  //#region src/FormCheckbox.d.ts
5
- type FormCheckboxProps = Omit<FormFieldOwnProps, "size"> & {
5
+ /**
6
+ * Native attributes for the checkbox `<input>` (`value`, `autoFocus`,
7
+ * `data-*`, …). Routed through `passThrough.input.props` so they land on the
8
+ * `<input>`, not the Checkbox's root `<label>`.
9
+ */
10
+ export type FormCheckboxAttributes = Omit<InputHTMLAttributes<HTMLInputElement>, Exclude<FormControlReservedAttribute, "value"> | "type">;
11
+ export type FormCheckboxProps = Omit<FormFieldOwnProps, "size"> & FormCheckboxAttributes & {
6
12
  /** The TanStack form instance returned by `useForm(...)`. */
7
13
  form: AnyFormApi;
8
- /** Field name (a boolean key of the form's data). */
14
+ /**
15
+ * Field name (a boolean key of the form's data). Also the native `name`
16
+ * of the checkbox `<input>`.
17
+ */
9
18
  name: string;
10
19
  /** Inline text/content rendered beside the checkbox. */
11
20
  children?: ReactNode;
12
21
  /** Size variant. Checkbox supports `sm`/`md`/`lg` (no `xs`). */
13
22
  size?: Exclude<FieldSize, "xs">;
23
+ /**
24
+ * Ref forwarded to the native checkbox `<input>` (through the underlying
25
+ * `<Checkbox>`) — the focus target, not the root `<label>`.
26
+ * @group Advanced
27
+ */
28
+ ref?: React.Ref<HTMLInputElement>;
14
29
  };
15
30
  /** FormCheckbox — a `<Checkbox>` bound to a boolean TanStack form field. */
16
- declare function FormCheckbox({ form, name, children, size, ...fieldProps }: FormCheckboxProps): JSX.Element;
17
- declare namespace FormCheckbox {
18
- var displayName: string;
19
- }
31
+ export declare const FormCheckbox: React.ForwardRefExoticComponent<Omit<FormCheckboxProps, "ref"> & React.RefAttributes<HTMLInputElement>>;
20
32
  //#endregion
21
- export { FormCheckbox, FormCheckboxProps };
22
33
  //# sourceMappingURL=FormCheckbox.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"FormCheckbox.d.ts","names":[],"sources":["../../src/FormCheckbox.tsx"],"mappings":";;;;KAMY,oBAAoB,KAAK;;EAEnC,MAAM;;EAEN;;EAEA,WAAW;;EAEX,OAAO,QAAQ;;;iBAID,eACd,MACA,MACA,UACA,SACG,cACF,oBAAoB,IAAI;kBANX;MAAA"}
1
+ {"version":3,"file":"FormCheckbox.d.ts","names":[],"sources":["../../src/FormCheckbox.tsx"],"mappings":";;;;;;;;;YAuBY,yBAAyB,KACnC,oBAAoB,mBAEpB,QAAQ;YAGE,oBAAoB,KAAK,6BACnC;;EAEE,MAAM;;;;;EAKN;;EAEA,WAAW;;EAEX,OAAO,QAAQ;;;;;;EAMf,MAAM,MAAM,IAAI;;;qBAIP,cAAY,MAAA,0BAAA,KAAA,4BAAA,MAAA,cAAA"}
@@ -1,24 +1,41 @@
1
1
  import { FormField } from "./FormField.js";
2
+ import { routeCheckboxControlProps, splitFormFieldProps } from "@pindoba/core-tanstack-form";
2
3
  import { jsx } from "react/jsx-runtime";
4
+ import React from "react";
3
5
  import Checkbox from "@pindoba/react-checkbox";
4
6
  //#region src/FormCheckbox.tsx
5
7
  /** FormCheckbox — a `<Checkbox>` bound to a boolean TanStack form field. */
6
- function FormCheckbox({ form, name, children, size, ...fieldProps }) {
8
+ const FormCheckbox = React.forwardRef(function FormCheckbox({ form, name, children, ...rest }, ref) {
9
+ const { fieldProps, controlAttrs } = splitFormFieldProps(rest);
10
+ const { onChange, onBlur, ...inputAttrs } = controlAttrs;
7
11
  return /* @__PURE__ */ jsx(FormField, {
8
12
  form,
9
13
  name,
10
- size,
11
14
  ...fieldProps,
12
- control: (props, field) => /* @__PURE__ */ jsx(Checkbox, {
13
- ...props,
14
- size,
15
- checked: Boolean(field.state.value),
16
- onChange: (event) => field.handleChange(event.target.checked),
17
- onBlur: () => field.handleBlur(),
18
- children
19
- })
15
+ control: (props, field) => {
16
+ const { checkboxProps, inputProps } = routeCheckboxControlProps(props, {
17
+ ...inputAttrs,
18
+ name
19
+ });
20
+ return /* @__PURE__ */ jsx(Checkbox, {
21
+ ...checkboxProps,
22
+ ref,
23
+ size: fieldProps.size,
24
+ passThrough: { input: { props: inputProps } },
25
+ checked: Boolean(field.state.value),
26
+ onChange: (event) => {
27
+ field.handleChange(event.target.checked);
28
+ onChange?.(event);
29
+ },
30
+ onBlur: (event) => {
31
+ field.handleBlur();
32
+ onBlur?.(event);
33
+ },
34
+ children
35
+ });
36
+ }
20
37
  });
21
- }
38
+ });
22
39
  FormCheckbox.displayName = "FormCheckbox";
23
40
  //#endregion
24
41
  export { FormCheckbox };
@@ -1 +1 @@
1
- {"version":3,"file":"FormCheckbox.js","names":[],"sources":["../../src/FormCheckbox.tsx"],"sourcesContent":["import type { FieldSize } from \"@pindoba/core-field\";\nimport Checkbox from \"@pindoba/react-checkbox\";\nimport type { JSX, ReactNode } from \"react\";\nimport { FormField } from \"./FormField\";\nimport type { AnyFormApi, FormFieldOwnProps } from \"./types\";\n\nexport type FormCheckboxProps = Omit<FormFieldOwnProps, \"size\"> & {\n /** The TanStack form instance returned by `useForm(...)`. */\n form: AnyFormApi;\n /** Field name (a boolean key of the form's data). */\n name: string;\n /** Inline text/content rendered beside the checkbox. */\n children?: ReactNode;\n /** Size variant. Checkbox supports `sm`/`md`/`lg` (no `xs`). */\n size?: Exclude<FieldSize, \"xs\">;\n};\n\n/** FormCheckbox — a `<Checkbox>` bound to a boolean TanStack form field. */\nexport function FormCheckbox({\n form,\n name,\n children,\n size,\n ...fieldProps\n}: FormCheckboxProps): JSX.Element {\n return (\n <FormField\n form={form}\n name={name}\n size={size}\n {...fieldProps}\n control={(props, field) => (\n <Checkbox\n {...props}\n size={size}\n checked={Boolean(field.state.value)}\n onChange={(event) => field.handleChange(event.target.checked)}\n onBlur={() => field.handleBlur()}\n >\n {children}\n </Checkbox>\n )}\n />\n );\n}\n\nFormCheckbox.displayName = \"FormCheckbox\";\n"],"mappings":";;;;;AAkBA,SAAgB,aAAa,EAC3B,MACA,MACA,UACA,MACA,GAAG,cAC8B;CACjC,OACE,oBAAC,WAAD;EACQ;EACA;EACA;EACN,GAAI;EACJ,UAAU,OAAO,UACf,oBAAC,UAAD;GACE,GAAI;GACE;GACN,SAAS,QAAQ,MAAM,MAAM,KAAK;GAClC,WAAW,UAAU,MAAM,aAAa,MAAM,OAAO,OAAO;GAC5D,cAAc,MAAM,WAAW;GAE9B;EACO,CAAA;CAEb,CAAA;AAEL;AAEA,aAAa,cAAc"}
1
+ {"version":3,"file":"FormCheckbox.js","names":[],"sources":["../../src/FormCheckbox.tsx"],"sourcesContent":["import type { FieldSize } from \"@pindoba/core-field\";\nimport {\n routeCheckboxControlProps,\n splitFormFieldProps,\n} from \"@pindoba/core-tanstack-form\";\nimport Checkbox from \"@pindoba/react-checkbox\";\nimport React, {\n type InputHTMLAttributes,\n type JSX,\n type ReactNode,\n} from \"react\";\nimport { FormField } from \"./FormField\";\nimport type {\n AnyFormApi,\n FormControlReservedAttribute,\n FormFieldOwnProps,\n} from \"./types\";\n\n/**\n * Native attributes for the checkbox `<input>` (`value`, `autoFocus`,\n * `data-*`, …). Routed through `passThrough.input.props` so they land on the\n * `<input>`, not the Checkbox's root `<label>`.\n */\nexport type FormCheckboxAttributes = Omit<\n InputHTMLAttributes<HTMLInputElement>,\n // `value` stays: it is the checkbox's native submission value.\n Exclude<FormControlReservedAttribute, \"value\"> | \"type\"\n>;\n\nexport type FormCheckboxProps = Omit<FormFieldOwnProps, \"size\"> &\n FormCheckboxAttributes & {\n /** The TanStack form instance returned by `useForm(...)`. */\n form: AnyFormApi;\n /**\n * Field name (a boolean key of the form's data). Also the native `name`\n * of the checkbox `<input>`.\n */\n name: string;\n /** Inline text/content rendered beside the checkbox. */\n children?: ReactNode;\n /** Size variant. Checkbox supports `sm`/`md`/`lg` (no `xs`). */\n size?: Exclude<FieldSize, \"xs\">;\n /**\n * Ref forwarded to the native checkbox `<input>` (through the underlying\n * `<Checkbox>`) — the focus target, not the root `<label>`.\n * @group Advanced\n */\n ref?: React.Ref<HTMLInputElement>;\n };\n\n/** FormCheckbox — a `<Checkbox>` bound to a boolean TanStack form field. */\nexport const FormCheckbox = React.forwardRef<\n HTMLInputElement,\n FormCheckboxProps\n>(function FormCheckbox({ form, name, children, ...rest }, ref): JSX.Element {\n const { fieldProps, controlAttrs } = splitFormFieldProps(rest);\n const { onChange, onBlur, ...inputAttrs } = controlAttrs;\n return (\n <FormField\n form={form}\n name={name}\n {...fieldProps}\n control={(props, field) => {\n // The Field's aria wiring must reach the `<input>`: Checkbox spreads\n // unknown props onto its root `<label>`.\n const { checkboxProps, inputProps } = routeCheckboxControlProps(props, {\n ...inputAttrs,\n name,\n });\n return (\n <Checkbox\n {...checkboxProps}\n ref={ref}\n size={fieldProps.size}\n passThrough={{ input: { props: inputProps } }}\n checked={Boolean(field.state.value)}\n onChange={(event) => {\n field.handleChange(event.target.checked);\n onChange?.(event);\n }}\n onBlur={(event) => {\n field.handleBlur();\n onBlur?.(event);\n }}\n >\n {children}\n </Checkbox>\n );\n }}\n />\n );\n});\n\nFormCheckbox.displayName = \"FormCheckbox\";\n"],"mappings":";;;;;;;AAmDA,MAAa,eAAe,MAAM,WAGhC,SAAS,aAAa,EAAE,MAAM,MAAM,UAAU,GAAG,QAAQ,KAAkB;CAC3E,MAAM,EAAE,YAAY,iBAAiB,oBAAoB,IAAI;CAC7D,MAAM,EAAE,UAAU,QAAQ,GAAG,eAAe;CAC5C,OACE,oBAAC,WAAD;EACQ;EACA;EACN,GAAI;EACJ,UAAU,OAAO,UAAU;GAGzB,MAAM,EAAE,eAAe,eAAe,0BAA0B,OAAO;IACrE,GAAG;IACH;GACF,CAAC;GACD,OACE,oBAAC,UAAD;IACE,GAAI;IACC;IACL,MAAM,WAAW;IACjB,aAAa,EAAE,OAAO,EAAE,OAAO,WAAW,EAAE;IAC5C,SAAS,QAAQ,MAAM,MAAM,KAAK;IAClC,WAAW,UAAU;KACnB,MAAM,aAAa,MAAM,OAAO,OAAO;KACvC,WAAW,KAAK;IAClB;IACA,SAAS,UAAU;KACjB,MAAM,WAAW;KACjB,SAAS,KAAK;IAChB;IAEC;GACO,CAAA;EAEd;CACD,CAAA;AAEL,CAAC;AAED,aAAa,cAAc"}
@@ -2,7 +2,7 @@ import { AnyFieldApi, AnyFormApi, FormFieldOwnProps } from "./types.js";
2
2
  import { JSX, ReactNode } from "react";
3
3
  import { FieldControlProps } from "@pindoba/core-field";
4
4
  //#region src/FormField.d.ts
5
- type FormFieldProps = FormFieldOwnProps & {
5
+ export type FormFieldProps = FormFieldOwnProps & {
6
6
  /** The TanStack form instance returned by `useForm(...)`. */
7
7
  form: AnyFormApi;
8
8
  /** Field name (a key of the form's data). */
@@ -20,10 +20,9 @@ type FormFieldProps = FormFieldOwnProps & {
20
20
  * control via the `control` render-prop, which receives the Field's `controlProps`
21
21
  * bag plus the raw `field` API.
22
22
  */
23
- declare function FormField({ form, name, control, errorMode, ...fieldProps }: FormFieldProps): JSX.Element;
24
- declare namespace FormField {
23
+ export declare function FormField({ form, name, control, errorMode, ...fieldProps }: FormFieldProps): JSX.Element;
24
+ export declare namespace FormField {
25
25
  var displayName: string;
26
26
  }
27
27
  //#endregion
28
- export { FormField, FormFieldProps };
29
28
  //# sourceMappingURL=FormField.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"FormField.d.ts","names":[],"sources":["../../src/FormField.tsx"],"mappings":";;;;KAOY,iBAAiB;;EAE3B,MAAM;;EAEN;;;;;EAKA,UAAU,cAAc,mBAAmB,OAAO,gBAAgB;;;;;;;;;iBAUpD,YACd,MACA,MACA,SACA,cACG,cACF,iBAAiB,IAAI;kBANR;MAAA"}
1
+ {"version":3,"file":"FormField.d.ts","names":[],"sources":["../../src/FormField.tsx"],"mappings":";;;;YAOY,iBAAiB;;EAE3B,MAAM;;EAEN;;;;;EAKA,UAAU,cAAc,mBAAmB,OAAO,gBAAgB;;;;;;;;;wBAUpD,YACd,MACA,MACA,SACA,cACG,cACF,iBAAiB,IAAI;yBANR;MAAA"}
@@ -1,22 +1,39 @@
1
- import { AnyFormApi, FormFieldOwnProps } from "./types.js";
1
+ import { AnyFormApi, FormControlReservedAttribute, FormFieldOwnProps } from "./types.js";
2
2
  import { InputProps } from "@pindoba/react-input";
3
- import { JSX } from "react";
3
+ import React from "react";
4
4
  //#region src/FormInput.d.ts
5
- type FormInputProps = FormFieldOwnProps & {
5
+ /**
6
+ * Native `<input>` attributes a consumer may pass straight through
7
+ * (`autoComplete`, `inputMode`, `autoCapitalize`, `spellCheck`,
8
+ * `enterKeyHint`, `pattern`, `min`/`max`/`step`, `maxLength`, `data-*`, …).
9
+ * The form owns the value and the Field owns the id/aria wiring, so those are
10
+ * reserved.
11
+ */
12
+ export type FormInputAttributes = Omit<InputProps, FormControlReservedAttribute | "type" | "placeholder" | "ref">;
13
+ export type FormInputProps = FormFieldOwnProps & FormInputAttributes & {
6
14
  /** The TanStack form instance returned by `useForm(...)`. */
7
15
  form: AnyFormApi;
8
- /** Field name (a key of the form's data). */
16
+ /**
17
+ * Field name (a key of the form's data). Also rendered as the native
18
+ * `name` attribute, so browsers and password managers can autofill.
19
+ */
9
20
  name: string;
10
21
  /** HTML input type forwarded to the underlying `<Input>`. */
11
22
  type?: InputProps["type"];
12
23
  /** Placeholder forwarded to the underlying `<Input>`. */
13
24
  placeholder?: string;
25
+ /**
26
+ * Ref forwarded to the native `<input>` (through the underlying
27
+ * `<Input>`) — the focus target (`ref.current.focus()`).
28
+ * @group Advanced
29
+ */
30
+ ref?: React.Ref<HTMLInputElement>;
14
31
  };
15
- /** FormInput — a text `<Input>` bound to a TanStack form field. */
16
- declare function FormInput({ form, name, type, placeholder, size, ...fieldProps }: FormInputProps): JSX.Element;
17
- declare namespace FormInput {
18
- var displayName: string;
19
- }
32
+ /**
33
+ * FormInput — a text `<Input>` bound to a TanStack form field. Every prop
34
+ * that isn't a Field prop (label, description, …) is forwarded to the
35
+ * `<input>`; consumer `onChange`/`onBlur` run after the form's own handlers.
36
+ */
37
+ export declare const FormInput: React.ForwardRefExoticComponent<Omit<FormInputProps, "ref"> & React.RefAttributes<HTMLInputElement>>;
20
38
  //#endregion
21
- export { FormInput, FormInputProps };
22
39
  //# sourceMappingURL=FormInput.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"FormInput.d.ts","names":[],"sources":["../../src/FormInput.tsx"],"mappings":";;;;KAKY,iBAAiB;;EAE3B,MAAM;;EAEN;;EAEA,OAAO;;EAEP;;;iBAIc,YACd,MACA,MACA,MACA,aACA,SACG,cACF,iBAAiB,IAAI;kBAPR;MAAA"}
1
+ {"version":3,"file":"FormInput.d.ts","names":[],"sources":["../../src/FormInput.tsx"],"mappings":";;;;;;;;;;;YAiBY,sBAAsB,KAChC,YACA;YAGU,iBAAiB,oBAC3B;;EAEE,MAAM;;;;;EAKN;;EAEA,OAAO;;EAEP;;;;;;EAMA,MAAM,MAAM,IAAI;;;;;;;qBAQP,WAAS,MAAA,0BAAA,KAAA,yBAAA,MAAA,cAAA"}
@@ -1,25 +1,41 @@
1
1
  import { FormField } from "./FormField.js";
2
+ import { splitFormFieldProps } from "@pindoba/core-tanstack-form";
2
3
  import { jsx } from "react/jsx-runtime";
3
4
  import Input from "@pindoba/react-input";
5
+ import React from "react";
4
6
  //#region src/FormInput.tsx
5
- /** FormInput — a text `<Input>` bound to a TanStack form field. */
6
- function FormInput({ form, name, type = "text", placeholder, size, ...fieldProps }) {
7
+ /**
8
+ * FormInput — a text `<Input>` bound to a TanStack form field. Every prop
9
+ * that isn't a Field prop (label, description, …) is forwarded to the
10
+ * `<input>`; consumer `onChange`/`onBlur` run after the form's own handlers.
11
+ */
12
+ const FormInput = React.forwardRef(function FormInput({ form, name, type = "text", placeholder, ...rest }, ref) {
13
+ const { fieldProps, controlAttrs } = splitFormFieldProps(rest);
14
+ const { onChange, onBlur, ...inputAttrs } = controlAttrs;
7
15
  return /* @__PURE__ */ jsx(FormField, {
8
16
  form,
9
17
  name,
10
- size,
11
18
  ...fieldProps,
12
19
  control: (props, field) => /* @__PURE__ */ jsx(Input, {
20
+ ...inputAttrs,
13
21
  ...props,
14
- size,
22
+ ref,
23
+ name,
24
+ size: fieldProps.size,
15
25
  type,
16
26
  placeholder,
17
27
  value: String(field.state.value ?? ""),
18
- onChange: (event) => field.handleChange(event.target.value),
19
- onBlur: () => field.handleBlur()
28
+ onChange: (event) => {
29
+ field.handleChange(event.target.value);
30
+ onChange?.(event);
31
+ },
32
+ onBlur: (event) => {
33
+ field.handleBlur();
34
+ onBlur?.(event);
35
+ }
20
36
  })
21
37
  });
22
- }
38
+ });
23
39
  FormInput.displayName = "FormInput";
24
40
  //#endregion
25
41
  export { FormInput };
@@ -1 +1 @@
1
- {"version":3,"file":"FormInput.js","names":[],"sources":["../../src/FormInput.tsx"],"sourcesContent":["import Input, { type InputProps } from \"@pindoba/react-input\";\nimport type { JSX } from \"react\";\nimport { FormField } from \"./FormField\";\nimport type { AnyFormApi, FormFieldOwnProps } from \"./types\";\n\nexport type FormInputProps = FormFieldOwnProps & {\n /** The TanStack form instance returned by `useForm(...)`. */\n form: AnyFormApi;\n /** Field name (a key of the form's data). */\n name: string;\n /** HTML input type forwarded to the underlying `<Input>`. */\n type?: InputProps[\"type\"];\n /** Placeholder forwarded to the underlying `<Input>`. */\n placeholder?: string;\n};\n\n/** FormInput — a text `<Input>` bound to a TanStack form field. */\nexport function FormInput({\n form,\n name,\n type = \"text\",\n placeholder,\n size,\n ...fieldProps\n}: FormInputProps): JSX.Element {\n return (\n <FormField\n form={form}\n name={name}\n size={size}\n {...fieldProps}\n control={(props, field) => (\n <Input\n {...props}\n size={size}\n type={type}\n placeholder={placeholder}\n value={String(field.state.value ?? \"\")}\n onChange={(event) => field.handleChange(event.target.value)}\n onBlur={() => field.handleBlur()}\n />\n )}\n />\n );\n}\n\nFormInput.displayName = \"FormInput\";\n"],"mappings":";;;;;AAiBA,SAAgB,UAAU,EACxB,MACA,MACA,OAAO,QACP,aACA,MACA,GAAG,cAC2B;CAC9B,OACE,oBAAC,WAAD;EACQ;EACA;EACA;EACN,GAAI;EACJ,UAAU,OAAO,UACf,oBAAC,OAAD;GACE,GAAI;GACE;GACA;GACO;GACb,OAAO,OAAO,MAAM,MAAM,SAAS,EAAE;GACrC,WAAW,UAAU,MAAM,aAAa,MAAM,OAAO,KAAK;GAC1D,cAAc,MAAM,WAAW;EAChC,CAAA;CAEJ,CAAA;AAEL;AAEA,UAAU,cAAc"}
1
+ {"version":3,"file":"FormInput.js","names":[],"sources":["../../src/FormInput.tsx"],"sourcesContent":["import { splitFormFieldProps } from \"@pindoba/core-tanstack-form\";\nimport Input, { type InputProps } from \"@pindoba/react-input\";\nimport React, { type JSX } from \"react\";\nimport { FormField } from \"./FormField\";\nimport type {\n AnyFormApi,\n FormControlReservedAttribute,\n FormFieldOwnProps,\n} from \"./types\";\n\n/**\n * Native `<input>` attributes a consumer may pass straight through\n * (`autoComplete`, `inputMode`, `autoCapitalize`, `spellCheck`,\n * `enterKeyHint`, `pattern`, `min`/`max`/`step`, `maxLength`, `data-*`, …).\n * The form owns the value and the Field owns the id/aria wiring, so those are\n * reserved.\n */\nexport type FormInputAttributes = Omit<\n InputProps,\n FormControlReservedAttribute | \"type\" | \"placeholder\" | \"ref\"\n>;\n\nexport type FormInputProps = FormFieldOwnProps &\n FormInputAttributes & {\n /** The TanStack form instance returned by `useForm(...)`. */\n form: AnyFormApi;\n /**\n * Field name (a key of the form's data). Also rendered as the native\n * `name` attribute, so browsers and password managers can autofill.\n */\n name: string;\n /** HTML input type forwarded to the underlying `<Input>`. */\n type?: InputProps[\"type\"];\n /** Placeholder forwarded to the underlying `<Input>`. */\n placeholder?: string;\n /**\n * Ref forwarded to the native `<input>` (through the underlying\n * `<Input>`) — the focus target (`ref.current.focus()`).\n * @group Advanced\n */\n ref?: React.Ref<HTMLInputElement>;\n };\n\n/**\n * FormInput — a text `<Input>` bound to a TanStack form field. Every prop\n * that isn't a Field prop (label, description, …) is forwarded to the\n * `<input>`; consumer `onChange`/`onBlur` run after the form's own handlers.\n */\nexport const FormInput = React.forwardRef<HTMLInputElement, FormInputProps>(\n function FormInput(\n { form, name, type = \"text\", placeholder, ...rest },\n ref,\n ): JSX.Element {\n const { fieldProps, controlAttrs } = splitFormFieldProps(rest);\n const { onChange, onBlur, ...inputAttrs } = controlAttrs;\n return (\n <FormField\n form={form}\n name={name}\n {...fieldProps}\n control={(props, field) => (\n <Input\n {...inputAttrs}\n {...props}\n ref={ref}\n name={name}\n size={fieldProps.size}\n type={type}\n placeholder={placeholder}\n value={String(field.state.value ?? \"\")}\n onChange={(event) => {\n field.handleChange(event.target.value);\n onChange?.(event);\n }}\n onBlur={(event) => {\n field.handleBlur();\n onBlur?.(event);\n }}\n />\n )}\n />\n );\n },\n);\n\nFormInput.displayName = \"FormInput\";\n"],"mappings":";;;;;;;;;;;AAgDA,MAAa,YAAY,MAAM,WAC7B,SAAS,UACP,EAAE,MAAM,MAAM,OAAO,QAAQ,aAAa,GAAG,QAC7C,KACa;CACb,MAAM,EAAE,YAAY,iBAAiB,oBAAoB,IAAI;CAC7D,MAAM,EAAE,UAAU,QAAQ,GAAG,eAAe;CAC5C,OACE,oBAAC,WAAD;EACQ;EACA;EACN,GAAI;EACJ,UAAU,OAAO,UACf,oBAAC,OAAD;GACE,GAAI;GACJ,GAAI;GACC;GACC;GACN,MAAM,WAAW;GACX;GACO;GACb,OAAO,OAAO,MAAM,MAAM,SAAS,EAAE;GACrC,WAAW,UAAU;IACnB,MAAM,aAAa,MAAM,OAAO,KAAK;IACrC,WAAW,KAAK;GAClB;GACA,SAAS,UAAU;IACjB,MAAM,WAAW;IACjB,SAAS,KAAK;GAChB;EACD,CAAA;CAEJ,CAAA;AAEL,CACF;AAEA,UAAU,cAAc"}
@@ -1,12 +1,22 @@
1
- import { AnyFormApi, FormFieldOwnProps } from "./types.js";
2
- import { JSX } from "react";
1
+ import { AnyFormApi, FormControlReservedAttribute, FormFieldOwnProps } from "./types.js";
2
+ import React from "react";
3
+ import { SelectProps } from "@pindoba/react-select";
3
4
  import { FieldSize } from "@pindoba/core-field";
4
5
  import { ListBoxNodeInput } from "@pindoba/core-listbox";
5
6
  //#region src/FormSelect.d.ts
6
- type FormSelectProps = Omit<FormFieldOwnProps, "size"> & {
7
+ /**
8
+ * Select props and trigger attributes a consumer may pass through
9
+ * (`clearable`, `leading`, `listbox*`, `data-*`, `aria-*`, …). The form owns
10
+ * the (single) selection, so the value/selection-mode props are reserved.
11
+ */
12
+ export type FormSelectAttributes = Omit<SelectProps, FormControlReservedAttribute | "items" | "placeholder" | "selectionMode" | "onValueChange" | "ref">;
13
+ export type FormSelectProps = Omit<FormFieldOwnProps, "size"> & FormSelectAttributes & {
7
14
  /** The TanStack form instance returned by `useForm(...)`. */
8
15
  form: AnyFormApi;
9
- /** Field name (a key of the form's data holding the selected option key). */
16
+ /**
17
+ * Field name (a key of the form's data holding the selected option key).
18
+ * Also the `name` of the hidden native `<select>` mirror.
19
+ */
10
20
  name: string;
11
21
  /** Options to choose from. */
12
22
  items: ListBoxNodeInput[];
@@ -14,12 +24,19 @@ type FormSelectProps = Omit<FormFieldOwnProps, "size"> & {
14
24
  placeholder?: string;
15
25
  /** Size variant. Select supports `sm`/`md`/`lg` (no `xs`). */
16
26
  size?: Exclude<FieldSize, "xs">;
27
+ /**
28
+ * Autofill hint (e.g. `"country"`) set on the hidden native `<select>`
29
+ * mirror, which the browser autofills and the Select syncs from.
30
+ */
31
+ autoComplete?: string;
32
+ /**
33
+ * Ref forwarded to the Select's focusable trigger (`role="combobox"`) —
34
+ * the focus target (`ref.current.focus()`).
35
+ * @group Advanced
36
+ */
37
+ ref?: React.Ref<HTMLDivElement>;
17
38
  };
18
39
  /** FormSelect — a single-select `<Select>` bound to a TanStack form field. */
19
- declare function FormSelect({ form, name, items, placeholder, size, ...fieldProps }: FormSelectProps): JSX.Element;
20
- declare namespace FormSelect {
21
- var displayName: string;
22
- }
40
+ export declare const FormSelect: React.ForwardRefExoticComponent<Omit<FormSelectProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
23
41
  //#endregion
24
- export { FormSelect, FormSelectProps };
25
42
  //# sourceMappingURL=FormSelect.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"FormSelect.d.ts","names":[],"sources":["../../src/FormSelect.tsx"],"mappings":";;;;;KAOY,kBAAkB,KAAK;;EAEjC,MAAM;;EAEN;;EAEA,OAAO;;EAEP;;EAEA,OAAO,QAAQ;;;iBAID,aACd,MACA,MACA,OACA,aACA,SACG,cACF,kBAAkB,IAAI;kBAPT;MAAA"}
1
+ {"version":3,"file":"FormSelect.d.ts","names":[],"sources":["../../src/FormSelect.tsx"],"mappings":";;;;;;;;;;;YAsBY,uBAAuB,KACjC,aACE;YAQQ,kBAAkB,KAAK,6BACjC;;EAEE,MAAM;;;;;EAKN;;EAEA,OAAO;;EAEP;;EAEA,OAAO,QAAQ;;;;;EAKf;;;;;;EAMA,MAAM,MAAM,IAAI;;;qBAIP,YAAU,MAAA,0BAAA,KAAA,0BAAA,MAAA,cAAA"}
@@ -1,27 +1,35 @@
1
1
  import { FormField } from "./FormField.js";
2
+ import { fromSelectValue, markTouchedOnSelectClose, splitFormFieldProps, toSelectValue } from "@pindoba/core-tanstack-form";
2
3
  import { jsx } from "react/jsx-runtime";
4
+ import React from "react";
3
5
  import Select from "@pindoba/react-select";
4
6
  //#region src/FormSelect.tsx
5
7
  /** FormSelect — a single-select `<Select>` bound to a TanStack form field. */
6
- function FormSelect({ form, name, items, placeholder, size, ...fieldProps }) {
8
+ const FormSelect = React.forwardRef(function FormSelect({ form, name, items, placeholder, autoComplete, ...rest }, ref) {
9
+ const { fieldProps, controlAttrs } = splitFormFieldProps(rest);
10
+ const { onOpenChange, ...selectAttrs } = controlAttrs;
7
11
  return /* @__PURE__ */ jsx(FormField, {
8
12
  form,
9
13
  name,
10
- size,
11
14
  ...fieldProps,
12
15
  control: (props, field) => /* @__PURE__ */ jsx(Select, {
16
+ ...selectAttrs,
13
17
  ...props,
14
- size,
18
+ ref,
19
+ name,
20
+ size: fieldProps.size,
15
21
  items,
16
22
  placeholder,
17
- value: typeof field.state.value === "string" && field.state.value ? field.state.value : null,
18
- onValueChange: (next) => field.handleChange(typeof next === "string" ? next : ""),
23
+ ...autoComplete ? { passThrough: { hiddenSelect: { props: { autoComplete } } } } : {},
24
+ value: toSelectValue(field.state.value),
25
+ onValueChange: (next) => field.handleChange(fromSelectValue(next)),
19
26
  onOpenChange: (open) => {
20
- if (!open) field.handleBlur();
27
+ markTouchedOnSelectClose(field, open);
28
+ onOpenChange?.(open);
21
29
  }
22
30
  })
23
31
  });
24
- }
32
+ });
25
33
  FormSelect.displayName = "FormSelect";
26
34
  //#endregion
27
35
  export { FormSelect };
@@ -1 +1 @@
1
- {"version":3,"file":"FormSelect.js","names":[],"sources":["../../src/FormSelect.tsx"],"sourcesContent":["import type { FieldSize } from \"@pindoba/core-field\";\nimport type { ListBoxNodeInput } from \"@pindoba/core-listbox\";\nimport Select from \"@pindoba/react-select\";\nimport type { JSX } from \"react\";\nimport { FormField } from \"./FormField\";\nimport type { AnyFormApi, FormFieldOwnProps } from \"./types\";\n\nexport type FormSelectProps = Omit<FormFieldOwnProps, \"size\"> & {\n /** The TanStack form instance returned by `useForm(...)`. */\n form: AnyFormApi;\n /** Field name (a key of the form's data holding the selected option key). */\n name: string;\n /** Options to choose from. */\n items: ListBoxNodeInput[];\n /** Placeholder shown when nothing is selected. */\n placeholder?: string;\n /** Size variant. Select supports `sm`/`md`/`lg` (no `xs`). */\n size?: Exclude<FieldSize, \"xs\">;\n};\n\n/** FormSelect — a single-select `<Select>` bound to a TanStack form field. */\nexport function FormSelect({\n form,\n name,\n items,\n placeholder,\n size,\n ...fieldProps\n}: FormSelectProps): JSX.Element {\n return (\n <FormField\n form={form}\n name={name}\n size={size}\n {...fieldProps}\n control={(props, field) => (\n <Select\n {...props}\n size={size}\n items={items}\n placeholder={placeholder}\n value={\n typeof field.state.value === \"string\" && field.state.value\n ? field.state.value\n : null\n }\n onValueChange={(next) =>\n field.handleChange(typeof next === \"string\" ? next : \"\")\n }\n onOpenChange={(open) => {\n // Mark touched when the dropdown closes — so `errorMode=\"touched\"`\n // surfaces a required error after opening and dismissing without\n // picking.\n if (!open) field.handleBlur();\n }}\n />\n )}\n />\n );\n}\n\nFormSelect.displayName = \"FormSelect\";\n"],"mappings":";;;;;AAqBA,SAAgB,WAAW,EACzB,MACA,MACA,OACA,aACA,MACA,GAAG,cAC4B;CAC/B,OACE,oBAAC,WAAD;EACQ;EACA;EACA;EACN,GAAI;EACJ,UAAU,OAAO,UACf,oBAAC,QAAD;GACE,GAAI;GACE;GACC;GACM;GACb,OACE,OAAO,MAAM,MAAM,UAAU,YAAY,MAAM,MAAM,QACjD,MAAM,MAAM,QACZ;GAEN,gBAAgB,SACd,MAAM,aAAa,OAAO,SAAS,WAAW,OAAO,EAAE;GAEzD,eAAe,SAAS;IAItB,IAAI,CAAC,MAAM,MAAM,WAAW;GAC9B;EACD,CAAA;CAEJ,CAAA;AAEL;AAEA,WAAW,cAAc"}
1
+ {"version":3,"file":"FormSelect.js","names":[],"sources":["../../src/FormSelect.tsx"],"sourcesContent":["import type { FieldSize } from \"@pindoba/core-field\";\nimport type { ListBoxNodeInput } from \"@pindoba/core-listbox\";\nimport {\n fromSelectValue,\n markTouchedOnSelectClose,\n splitFormFieldProps,\n toSelectValue,\n} from \"@pindoba/core-tanstack-form\";\nimport Select, { type SelectProps } from \"@pindoba/react-select\";\nimport React, { type JSX } from \"react\";\nimport { FormField } from \"./FormField\";\nimport type {\n AnyFormApi,\n FormControlReservedAttribute,\n FormFieldOwnProps,\n} from \"./types\";\n\n/**\n * Select props and trigger attributes a consumer may pass through\n * (`clearable`, `leading`, `listbox*`, `data-*`, `aria-*`, …). The form owns\n * the (single) selection, so the value/selection-mode props are reserved.\n */\nexport type FormSelectAttributes = Omit<\n SelectProps,\n | FormControlReservedAttribute\n | \"items\"\n | \"placeholder\"\n | \"selectionMode\"\n | \"onValueChange\"\n | \"ref\"\n>;\n\nexport type FormSelectProps = Omit<FormFieldOwnProps, \"size\"> &\n FormSelectAttributes & {\n /** The TanStack form instance returned by `useForm(...)`. */\n form: AnyFormApi;\n /**\n * Field name (a key of the form's data holding the selected option key).\n * Also the `name` of the hidden native `<select>` mirror.\n */\n name: string;\n /** Options to choose from. */\n items: ListBoxNodeInput[];\n /** Placeholder shown when nothing is selected. */\n placeholder?: string;\n /** Size variant. Select supports `sm`/`md`/`lg` (no `xs`). */\n size?: Exclude<FieldSize, \"xs\">;\n /**\n * Autofill hint (e.g. `\"country\"`) set on the hidden native `<select>`\n * mirror, which the browser autofills and the Select syncs from.\n */\n autoComplete?: string;\n /**\n * Ref forwarded to the Select's focusable trigger (`role=\"combobox\"`) —\n * the focus target (`ref.current.focus()`).\n * @group Advanced\n */\n ref?: React.Ref<HTMLDivElement>;\n };\n\n/** FormSelect — a single-select `<Select>` bound to a TanStack form field. */\nexport const FormSelect = React.forwardRef<HTMLDivElement, FormSelectProps>(\n function FormSelect(\n { form, name, items, placeholder, autoComplete, ...rest },\n ref,\n ): JSX.Element {\n const { fieldProps, controlAttrs } = splitFormFieldProps(rest);\n const { onOpenChange, ...selectAttrs } = controlAttrs;\n return (\n <FormField\n form={form}\n name={name}\n {...fieldProps}\n control={(props, field) => (\n <Select\n {...selectAttrs}\n {...props}\n ref={ref}\n name={name}\n size={fieldProps.size}\n items={items}\n placeholder={placeholder}\n {...(autoComplete\n ? { passThrough: { hiddenSelect: { props: { autoComplete } } } }\n : {})}\n value={toSelectValue(field.state.value)}\n onValueChange={(next) => field.handleChange(fromSelectValue(next))}\n onOpenChange={(open) => {\n // Mark touched when the dropdown closes — so `errorMode=\"touched\"`\n // surfaces a required error after opening and dismissing without\n // picking.\n markTouchedOnSelectClose(field, open);\n onOpenChange?.(open);\n }}\n />\n )}\n />\n );\n },\n);\n\nFormSelect.displayName = \"FormSelect\";\n"],"mappings":";;;;;;;AA6DA,MAAa,aAAa,MAAM,WAC9B,SAAS,WACP,EAAE,MAAM,MAAM,OAAO,aAAa,cAAc,GAAG,QACnD,KACa;CACb,MAAM,EAAE,YAAY,iBAAiB,oBAAoB,IAAI;CAC7D,MAAM,EAAE,cAAc,GAAG,gBAAgB;CACzC,OACE,oBAAC,WAAD;EACQ;EACA;EACN,GAAI;EACJ,UAAU,OAAO,UACf,oBAAC,QAAD;GACE,GAAI;GACJ,GAAI;GACC;GACC;GACN,MAAM,WAAW;GACV;GACM;GACb,GAAK,eACD,EAAE,aAAa,EAAE,cAAc,EAAE,OAAO,EAAE,aAAa,EAAE,EAAE,EAAE,IAC7D,CAAC;GACL,OAAO,cAAc,MAAM,MAAM,KAAK;GACtC,gBAAgB,SAAS,MAAM,aAAa,gBAAgB,IAAI,CAAC;GACjE,eAAe,SAAS;IAItB,yBAAyB,OAAO,IAAI;IACpC,eAAe,IAAI;GACrB;EACD,CAAA;CAEJ,CAAA;AAEL,CACF;AAEA,WAAW,cAAc"}
@@ -1,8 +1,8 @@
1
1
  import { AnyFormApi } from "./types.js";
2
+ import React, { ReactNode } from "react";
2
3
  import { ButtonProps } from "@pindoba/react-button";
3
- import { JSX, ReactNode } from "react";
4
4
  //#region src/FormSubmit.d.ts
5
- type FormSubmitProps = {
5
+ export type FormSubmitProps = {
6
6
  /** The TanStack form instance returned by `useForm(...)`. */
7
7
  form: AnyFormApi;
8
8
  /**
@@ -18,16 +18,20 @@ type FormSubmitProps = {
18
18
  size?: ButtonProps["size"];
19
19
  /** Button label content. Falls back to "Submit". */
20
20
  children?: ReactNode;
21
+ /**
22
+ * Ref forwarded to the native submit `<button>` (through the underlying
23
+ * `<Button>`).
24
+ * @group Advanced
25
+ */
26
+ ref?: React.Ref<HTMLButtonElement>;
21
27
  };
22
28
  /**
23
29
  * FormSubmit — a submit `<Button>` wired to the form's submit-readiness. While
24
- * the `onSubmit` promise is in flight the label is blurred with a corner spinner
25
- * (`Loading variant="busy"`) and the button is disabled + `aria-busy`.
30
+ * the `onSubmit` promise is in flight the button keeps its width and colour and
31
+ * its label is swapped for a spinner (`Loading variant="busy"
32
+ * indicator="inline"`); it's `aria-busy` and inert, not disabled — a disabled
33
+ * button would grey out as if the action weren't available.
26
34
  */
27
- declare function FormSubmit({ form, disableWhenInvalid, emphasis, size, children }: FormSubmitProps): JSX.Element;
28
- declare namespace FormSubmit {
29
- var displayName: string;
30
- }
35
+ export declare const FormSubmit: React.ForwardRefExoticComponent<Omit<FormSubmitProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
31
36
  //#endregion
32
- export { FormSubmit, FormSubmitProps };
33
37
  //# sourceMappingURL=FormSubmit.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"FormSubmit.d.ts","names":[],"sources":["../../src/FormSubmit.tsx"],"mappings":";;;;KAMY;;EAEV,MAAM;;;;;;;EAON;;EAEA,WAAW;;EAEX,OAAO;;EAEP,WAAW;;;;;;;iBAQG,aACd,MACA,oBACA,UACA,MACA,YACC,kBAAkB,IAAI;kBANT;MAAA"}
1
+ {"version":3,"file":"FormSubmit.d.ts","names":[],"sources":["../../src/FormSubmit.tsx"],"mappings":";;;;YAOY;;EAEV,MAAM;;;;;;;EAON;;EAEA,WAAW;;EAEX,OAAO;;EAEP,WAAW;;;;;;EAMX,MAAM,MAAM,IAAI;;;;;;;;;qBAUL,YAAU,MAAA,0BAAA,KAAA,0BAAA,MAAA,cAAA"}
@@ -1,32 +1,41 @@
1
1
  import { useStore } from "@tanstack/react-form";
2
+ import { getSubmitButtonState } from "@pindoba/core-tanstack-form";
2
3
  import { jsx } from "react/jsx-runtime";
4
+ import React from "react";
3
5
  import Button from "@pindoba/react-button";
4
6
  import Loading from "@pindoba/react-loading";
5
7
  //#region src/FormSubmit.tsx
6
8
  /**
7
9
  * FormSubmit — a submit `<Button>` wired to the form's submit-readiness. While
8
- * the `onSubmit` promise is in flight the label is blurred with a corner spinner
9
- * (`Loading variant="busy"`) and the button is disabled + `aria-busy`.
10
+ * the `onSubmit` promise is in flight the button keeps its width and colour and
11
+ * its label is swapped for a spinner (`Loading variant="busy"
12
+ * indicator="inline"`); it's `aria-busy` and inert, not disabled — a disabled
13
+ * button would grey out as if the action weren't available.
10
14
  */
11
- function FormSubmit({ form, disableWhenInvalid = false, emphasis = "primary", size, children }) {
15
+ const FormSubmit = React.forwardRef(function FormSubmit({ form, disableWhenInvalid = false, emphasis = "primary", size, children }, ref) {
12
16
  const { canSubmit, isSubmitting } = useStore(form.store, (state) => ({
13
17
  canSubmit: state.canSubmit,
14
18
  isSubmitting: state.isSubmitting
15
19
  }));
20
+ const button = getSubmitButtonState({
21
+ canSubmit,
22
+ isSubmitting
23
+ }, disableWhenInvalid);
16
24
  return /* @__PURE__ */ jsx(Loading, {
17
- loading: isSubmitting,
25
+ loading: button.busy,
18
26
  variant: "busy",
19
- indicator: true,
27
+ indicator: "inline",
20
28
  children: /* @__PURE__ */ jsx(Button, {
29
+ ref,
21
30
  type: "submit",
22
31
  emphasis,
23
32
  size,
24
- disabled: isSubmitting || disableWhenInvalid && !canSubmit,
25
- "aria-busy": isSubmitting ? "true" : void 0,
33
+ disabled: button.disabled,
34
+ "aria-busy": button.ariaBusy,
26
35
  children: children ?? "Submit"
27
36
  })
28
37
  });
29
- }
38
+ });
30
39
  FormSubmit.displayName = "FormSubmit";
31
40
  //#endregion
32
41
  export { FormSubmit };
@@ -1 +1 @@
1
- {"version":3,"file":"FormSubmit.js","names":[],"sources":["../../src/FormSubmit.tsx"],"sourcesContent":["import Button, { type ButtonProps } from \"@pindoba/react-button\";\nimport Loading from \"@pindoba/react-loading\";\nimport { useStore } from \"@tanstack/react-form\";\nimport type { JSX, ReactNode } from \"react\";\nimport type { AnyFormApi } from \"./types\";\n\nexport type FormSubmitProps = {\n /** The TanStack form instance returned by `useForm(...)`. */\n form: AnyFormApi;\n /**\n * Also disable while the form is invalid (not just while submitting). Off by\n * default — the button stays clickable so submitting an invalid form surfaces\n * its errors instead of leaving the user stuck on a disabled control.\n * @default false\n */\n disableWhenInvalid?: boolean;\n /** Emphasis forwarded to the underlying `<Button>`. */\n emphasis?: ButtonProps[\"emphasis\"];\n /** Size forwarded to the underlying `<Button>`. */\n size?: ButtonProps[\"size\"];\n /** Button label content. Falls back to \"Submit\". */\n children?: ReactNode;\n};\n\n/**\n * FormSubmit — a submit `<Button>` wired to the form's submit-readiness. While\n * the `onSubmit` promise is in flight the label is blurred with a corner spinner\n * (`Loading variant=\"busy\"`) and the button is disabled + `aria-busy`.\n */\nexport function FormSubmit({\n form,\n disableWhenInvalid = false,\n emphasis = \"primary\",\n size,\n children,\n}: FormSubmitProps): JSX.Element {\n const { canSubmit, isSubmitting } = useStore(form.store, (state) => ({\n canSubmit: state.canSubmit,\n isSubmitting: state.isSubmitting,\n }));\n\n return (\n <Loading loading={isSubmitting} variant=\"busy\" indicator>\n <Button\n type=\"submit\"\n emphasis={emphasis}\n size={size}\n disabled={isSubmitting || (disableWhenInvalid && !canSubmit)}\n aria-busy={isSubmitting ? \"true\" : undefined}\n >\n {children ?? \"Submit\"}\n </Button>\n </Loading>\n );\n}\n\nFormSubmit.displayName = \"FormSubmit\";\n"],"mappings":";;;;;;;;;;AA6BA,SAAgB,WAAW,EACzB,MACA,qBAAqB,OACrB,WAAW,WACX,MACA,YAC+B;CAC/B,MAAM,EAAE,WAAW,iBAAiB,SAAS,KAAK,QAAQ,WAAW;EACnE,WAAW,MAAM;EACjB,cAAc,MAAM;CACtB,EAAE;CAEF,OACE,oBAAC,SAAD;EAAS,SAAS;EAAc,SAAQ;EAAO,WAAA;EAC7C,UAAA,oBAAC,QAAD;GACE,MAAK;GACK;GACJ;GACN,UAAU,gBAAiB,sBAAsB,CAAC;GAClD,aAAW,eAAe,SAAS,KAAA;GAElC,UAAA,YAAY;EACP,CAAA;CACD,CAAA;AAEb;AAEA,WAAW,cAAc"}
1
+ {"version":3,"file":"FormSubmit.js","names":[],"sources":["../../src/FormSubmit.tsx"],"sourcesContent":["import { getSubmitButtonState } from \"@pindoba/core-tanstack-form\";\nimport Button, { type ButtonProps } from \"@pindoba/react-button\";\nimport Loading from \"@pindoba/react-loading\";\nimport { useStore } from \"@tanstack/react-form\";\nimport React, { type JSX, type ReactNode } from \"react\";\nimport type { AnyFormApi } from \"./types\";\n\nexport type FormSubmitProps = {\n /** The TanStack form instance returned by `useForm(...)`. */\n form: AnyFormApi;\n /**\n * Also disable while the form is invalid (not just while submitting). Off by\n * default — the button stays clickable so submitting an invalid form surfaces\n * its errors instead of leaving the user stuck on a disabled control.\n * @default false\n */\n disableWhenInvalid?: boolean;\n /** Emphasis forwarded to the underlying `<Button>`. */\n emphasis?: ButtonProps[\"emphasis\"];\n /** Size forwarded to the underlying `<Button>`. */\n size?: ButtonProps[\"size\"];\n /** Button label content. Falls back to \"Submit\". */\n children?: ReactNode;\n /**\n * Ref forwarded to the native submit `<button>` (through the underlying\n * `<Button>`).\n * @group Advanced\n */\n ref?: React.Ref<HTMLButtonElement>;\n};\n\n/**\n * FormSubmit — a submit `<Button>` wired to the form's submit-readiness. While\n * the `onSubmit` promise is in flight the button keeps its width and colour and\n * its label is swapped for a spinner (`Loading variant=\"busy\"\n * indicator=\"inline\"`); it's `aria-busy` and inert, not disabled — a disabled\n * button would grey out as if the action weren't available.\n */\nexport const FormSubmit = React.forwardRef<HTMLButtonElement, FormSubmitProps>(\n function FormSubmit(\n { form, disableWhenInvalid = false, emphasis = \"primary\", size, children },\n ref,\n ): JSX.Element {\n const { canSubmit, isSubmitting } = useStore(form.store, (state) => ({\n canSubmit: state.canSubmit,\n isSubmitting: state.isSubmitting,\n }));\n\n const button = getSubmitButtonState(\n { canSubmit, isSubmitting },\n disableWhenInvalid,\n );\n\n return (\n <Loading loading={button.busy} variant=\"busy\" indicator=\"inline\">\n <Button\n ref={ref}\n type=\"submit\"\n emphasis={emphasis}\n size={size}\n disabled={button.disabled}\n aria-busy={button.ariaBusy}\n >\n {children ?? \"Submit\"}\n </Button>\n </Loading>\n );\n },\n);\n\nFormSubmit.displayName = \"FormSubmit\";\n"],"mappings":";;;;;;;;;;;;;;AAsCA,MAAa,aAAa,MAAM,WAC9B,SAAS,WACP,EAAE,MAAM,qBAAqB,OAAO,WAAW,WAAW,MAAM,YAChE,KACa;CACb,MAAM,EAAE,WAAW,iBAAiB,SAAS,KAAK,QAAQ,WAAW;EACnE,WAAW,MAAM;EACjB,cAAc,MAAM;CACtB,EAAE;CAEF,MAAM,SAAS,qBACb;EAAE;EAAW;CAAa,GAC1B,kBACF;CAEA,OACE,oBAAC,SAAD;EAAS,SAAS,OAAO;EAAM,SAAQ;EAAO,WAAU;EACtD,UAAA,oBAAC,QAAD;GACO;GACL,MAAK;GACK;GACJ;GACN,UAAU,OAAO;GACjB,aAAW,OAAO;GAEjB,UAAA,YAAY;EACP,CAAA;CACD,CAAA;AAEb,CACF;AAEA,WAAW,cAAc"}
@@ -0,0 +1,34 @@
1
+ import { AnyFormApi, FormControlReservedAttribute, FormFieldOwnProps } from "./types.js";
2
+ import React from "react";
3
+ import { TextareaProps } from "@pindoba/react-textarea";
4
+ //#region src/FormTextarea.d.ts
5
+ /**
6
+ * Native `<textarea>` attributes (`autoComplete`, `maxLength`, `spellCheck`,
7
+ * `wrap`, `data-*`, …) plus the Textarea's own `rows`/`resize`/`autoResize`.
8
+ * The form owns the value and the Field owns the id/aria wiring.
9
+ */
10
+ export type FormTextareaAttributes = Omit<TextareaProps, FormControlReservedAttribute | "ref">;
11
+ export type FormTextareaProps = FormFieldOwnProps & FormTextareaAttributes & {
12
+ /** The TanStack form instance returned by `useForm(...)`. */
13
+ form: AnyFormApi;
14
+ /**
15
+ * Field name (a key of the form's data). Also rendered as the native
16
+ * `name` attribute.
17
+ */
18
+ name: string;
19
+ /**
20
+ * Ref forwarded to the native `<textarea>` (through the underlying
21
+ * `<Textarea>`) — the focus target (`ref.current.focus()`).
22
+ * @group Advanced
23
+ */
24
+ ref?: React.Ref<HTMLTextAreaElement>;
25
+ };
26
+ /**
27
+ * FormTextarea — a multi-line `<Textarea>` bound to a TanStack form field.
28
+ * Same wiring as `<FormInput>`: Field chrome, `aria-invalid` /
29
+ * `aria-describedby` / `aria-errormessage`, change + blur handlers. Consumer
30
+ * `onChange`/`onBlur` run after the form's own handlers.
31
+ */
32
+ export declare const FormTextarea: React.ForwardRefExoticComponent<Omit<FormTextareaProps, "ref"> & React.RefAttributes<HTMLTextAreaElement>>;
33
+ //#endregion
34
+ //# sourceMappingURL=FormTextarea.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FormTextarea.d.ts","names":[],"sources":["../../src/FormTextarea.tsx"],"mappings":";;;;;;;;;YAeY,yBAAyB,KACnC,eACA;YAGU,oBAAoB,oBAC9B;;EAEE,MAAM;;;;;EAKN;;;;;;EAMA,MAAM,MAAM,IAAI;;;;;;;;qBASP,cAAY,MAAA,0BAAA,KAAA,4BAAA,MAAA,cAAA"}
@@ -0,0 +1,42 @@
1
+ import { FormField } from "./FormField.js";
2
+ import { splitFormFieldProps } from "@pindoba/core-tanstack-form";
3
+ import { jsx } from "react/jsx-runtime";
4
+ import React from "react";
5
+ import Textarea from "@pindoba/react-textarea";
6
+ //#region src/FormTextarea.tsx
7
+ /**
8
+ * FormTextarea — a multi-line `<Textarea>` bound to a TanStack form field.
9
+ * Same wiring as `<FormInput>`: Field chrome, `aria-invalid` /
10
+ * `aria-describedby` / `aria-errormessage`, change + blur handlers. Consumer
11
+ * `onChange`/`onBlur` run after the form's own handlers.
12
+ */
13
+ const FormTextarea = React.forwardRef(function FormTextarea({ form, name, ...rest }, ref) {
14
+ const { fieldProps, controlAttrs } = splitFormFieldProps(rest);
15
+ const { onChange, onBlur, ...textareaAttrs } = controlAttrs;
16
+ return /* @__PURE__ */ jsx(FormField, {
17
+ form,
18
+ name,
19
+ ...fieldProps,
20
+ control: (props, field) => /* @__PURE__ */ jsx(Textarea, {
21
+ ...textareaAttrs,
22
+ ...props,
23
+ ref,
24
+ name,
25
+ size: fieldProps.size,
26
+ value: String(field.state.value ?? ""),
27
+ onChange: (event) => {
28
+ field.handleChange(event.target.value);
29
+ onChange?.(event);
30
+ },
31
+ onBlur: (event) => {
32
+ field.handleBlur();
33
+ onBlur?.(event);
34
+ }
35
+ })
36
+ });
37
+ });
38
+ FormTextarea.displayName = "FormTextarea";
39
+ //#endregion
40
+ export { FormTextarea };
41
+
42
+ //# sourceMappingURL=FormTextarea.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FormTextarea.js","names":[],"sources":["../../src/FormTextarea.tsx"],"sourcesContent":["import { splitFormFieldProps } from \"@pindoba/core-tanstack-form\";\nimport Textarea, { type TextareaProps } from \"@pindoba/react-textarea\";\nimport React, { type JSX } from \"react\";\nimport { FormField } from \"./FormField\";\nimport type {\n AnyFormApi,\n FormControlReservedAttribute,\n FormFieldOwnProps,\n} from \"./types\";\n\n/**\n * Native `<textarea>` attributes (`autoComplete`, `maxLength`, `spellCheck`,\n * `wrap`, `data-*`, …) plus the Textarea's own `rows`/`resize`/`autoResize`.\n * The form owns the value and the Field owns the id/aria wiring.\n */\nexport type FormTextareaAttributes = Omit<\n TextareaProps,\n FormControlReservedAttribute | \"ref\"\n>;\n\nexport type FormTextareaProps = FormFieldOwnProps &\n FormTextareaAttributes & {\n /** The TanStack form instance returned by `useForm(...)`. */\n form: AnyFormApi;\n /**\n * Field name (a key of the form's data). Also rendered as the native\n * `name` attribute.\n */\n name: string;\n /**\n * Ref forwarded to the native `<textarea>` (through the underlying\n * `<Textarea>`) — the focus target (`ref.current.focus()`).\n * @group Advanced\n */\n ref?: React.Ref<HTMLTextAreaElement>;\n };\n\n/**\n * FormTextarea — a multi-line `<Textarea>` bound to a TanStack form field.\n * Same wiring as `<FormInput>`: Field chrome, `aria-invalid` /\n * `aria-describedby` / `aria-errormessage`, change + blur handlers. Consumer\n * `onChange`/`onBlur` run after the form's own handlers.\n */\nexport const FormTextarea = React.forwardRef<\n HTMLTextAreaElement,\n FormTextareaProps\n>(function FormTextarea({ form, name, ...rest }, ref): JSX.Element {\n const { fieldProps, controlAttrs } = splitFormFieldProps(rest);\n const { onChange, onBlur, ...textareaAttrs } = controlAttrs;\n return (\n <FormField\n form={form}\n name={name}\n {...fieldProps}\n control={(props, field) => (\n <Textarea\n {...textareaAttrs}\n {...props}\n ref={ref}\n name={name}\n size={fieldProps.size}\n value={String(field.state.value ?? \"\")}\n onChange={(event) => {\n field.handleChange(event.target.value);\n onChange?.(event);\n }}\n onBlur={(event) => {\n field.handleBlur();\n onBlur?.(event);\n }}\n />\n )}\n />\n );\n});\n\nFormTextarea.displayName = \"FormTextarea\";\n"],"mappings":";;;;;;;;;;;;AA2CA,MAAa,eAAe,MAAM,WAGhC,SAAS,aAAa,EAAE,MAAM,MAAM,GAAG,QAAQ,KAAkB;CACjE,MAAM,EAAE,YAAY,iBAAiB,oBAAoB,IAAI;CAC7D,MAAM,EAAE,UAAU,QAAQ,GAAG,kBAAkB;CAC/C,OACE,oBAAC,WAAD;EACQ;EACA;EACN,GAAI;EACJ,UAAU,OAAO,UACf,oBAAC,UAAD;GACE,GAAI;GACJ,GAAI;GACC;GACC;GACN,MAAM,WAAW;GACjB,OAAO,OAAO,MAAM,MAAM,SAAS,EAAE;GACrC,WAAW,UAAU;IACnB,MAAM,aAAa,MAAM,OAAO,KAAK;IACrC,WAAW,KAAK;GAClB;GACA,SAAS,UAAU;IACjB,MAAM,WAAW;IACjB,SAAS,KAAK;GAChB;EACD,CAAA;CAEJ,CAAA;AAEL,CAAC;AAED,aAAa,cAAc"}
@@ -1,2 +1 @@
1
- import { getVisibleErrors, normalizeErrors } from "@pindoba/core-tanstack-form";
2
- export { getVisibleErrors, normalizeErrors };
1
+ export { getVisibleErrors, normalizeErrors } from "@pindoba/core-tanstack-form";
@@ -5,15 +5,21 @@ import { FieldPassThrough } from "@pindoba/core-field";
5
5
  //#region src/types.d.ts
6
6
  /**
7
7
  * Field-level presentation props shared by `<FormField>` and every convenience
8
- * wrapper (`<FormInput>`, `<FormSelect>`, `<FormCheckbox>`). These mirror the
8
+ * wrapper (`<FormInput>`, `<FormTextarea>`, `<FormSelect>`, `<FormCheckbox>`). These mirror the
9
9
  * presentational `<Field>` props; the adapter supplies `error` from TanStack.
10
10
  */
11
- interface FormFieldOwnProps extends Omit<FormFieldBaseProps, "form" | "name" | "labelTooltip" | "passThrough"> {
11
+ export interface FormFieldOwnProps extends Omit<FormFieldBaseProps, "form" | "name" | "labelTooltip" | "passThrough"> {
12
12
  /** Tooltip attached to an info trigger next to the label. */
13
13
  labelTooltip?: ReactNode;
14
14
  /** Per-slot escape hatch for styling and attributes. */
15
15
  passThrough?: FieldPassThrough;
16
16
  }
17
+ /**
18
+ * Keys a convenience wrapper never forwards from its native-attribute bucket:
19
+ * the TanStack binding owns `form`/`name`/value, the Field owns `id` + the
20
+ * shared `size`/`tone`/`disabled`/`required`/`passThrough` props.
21
+ */
22
+ export type FormControlReservedAttribute = "form" | "name" | "value" | "defaultValue" | "checked" | "defaultChecked" | "id" | "size" | "tone" | "disabled" | "required" | "passThrough" | "children";
17
23
  //#endregion
18
- export { type AnyFieldApi, type AnyFormApi, type FormFieldErrorMode, FormFieldOwnProps };
24
+ export type { AnyFieldApi, AnyFormApi, FormFieldErrorMode };
19
25
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","names":[],"sources":["../../src/types.ts"],"mappings":";;;;;;;;;;UAciB,0BAA0B,KACzC;;EAIA,eAAe;;EAEf,cAAc"}
1
+ {"version":3,"file":"types.d.ts","names":[],"sources":["../../src/types.ts"],"mappings":";;;;;;;;;;iBAciB,0BAA0B,KACzC;;EAIA,eAAe;;EAEf,cAAc;;;;;;;YAQJ"}
@@ -21,7 +21,8 @@ import { FocusInvalidOptions } from "@pindoba/core-tanstack-form";
21
21
  * markers are in the DOM. TanStack Form does no DOM focus itself, so without
22
22
  * this a keyboard user is stranded on the submit button (WCAG 3.3.1).
23
23
  */
24
- declare function useFocusInvalidOnSubmit(form: AnyFormApi, options?: FocusInvalidOptions): React.RefObject<HTMLFormElement | null>;
24
+ export declare function useFocusInvalidOnSubmit(form: AnyFormApi, options?: FocusInvalidOptions): {
25
+ current: HTMLFormElement | null;
26
+ };
25
27
  //#endregion
26
- export { useFocusInvalidOnSubmit };
27
28
  //# sourceMappingURL=useFocusInvalidOnSubmit.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useFocusInvalidOnSubmit.d.ts","names":[],"sources":["../../src/useFocusInvalidOnSubmit.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;iBA6BgB,wBACd,MAAM,YACN,UAAU,sBACT,MAAM,UAAU"}
1
+ {"version":3,"file":"useFocusInvalidOnSubmit.d.ts","names":[],"sources":["../../src/useFocusInvalidOnSubmit.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;wBA6BgB,wBACd,MAAM,YACN,UAAU;EACP,SAAS"}
@@ -26,18 +26,20 @@ function useFocusInvalidOnSubmit(form, options) {
26
26
  const formRef = useRef(null);
27
27
  const submissionAttempts = useStore(form.store, (s) => s.submissionAttempts);
28
28
  const isValidating = useStore(form.store, (s) => s.isValidating);
29
+ const isSubmitting = useStore(form.store, (s) => s.isSubmitting);
29
30
  const isValid = useStore(form.store, (s) => s.isValid);
30
31
  const trackerRef = useRef(null);
31
32
  if (trackerRef.current === null) trackerRef.current = createSubmitFocusTracker(() => focusFirstInvalidControl(formRef.current, options), submissionAttempts);
33
+ const pipelineBusy = isValidating || isSubmitting;
32
34
  useEffect(() => {
33
35
  trackerRef.current?.({
34
36
  submissionAttempts,
35
- isValidating,
37
+ isValidating: pipelineBusy,
36
38
  isValid
37
39
  });
38
40
  }, [
39
41
  submissionAttempts,
40
- isValidating,
42
+ pipelineBusy,
41
43
  isValid
42
44
  ]);
43
45
  return formRef;
@@ -1 +1 @@
1
- {"version":3,"file":"useFocusInvalidOnSubmit.js","names":[],"sources":["../../src/useFocusInvalidOnSubmit.ts"],"sourcesContent":["import { useEffect, useRef } from \"react\";\nimport { useStore } from \"@tanstack/react-form\";\nimport {\n createSubmitFocusTracker,\n focusFirstInvalidControl,\n type FocusInvalidOptions,\n} from \"@pindoba/core-tanstack-form\";\nimport type { AnyFormApi } from \"./types\";\n\n/**\n * Returns a ref to attach to your `<form>` that moves focus to the first\n * invalid control after a failed submit:\n *\n * ```tsx\n * const formRef = useFocusInvalidOnSubmit(form);\n * return (\n * <form ref={formRef} onSubmit={(e) => { e.preventDefault(); form.handleSubmit(); }}>\n * …\n * </form>\n * );\n * ```\n *\n * It reads the form's submit-attempt / validation state, and once an attempt\n * ends invalid (after async validators settle) focuses the first control marked\n * `aria-invalid=\"true\"` inside the form. The state is read via `useStore`, so\n * the focus effect runs after React has committed the error render and the\n * markers are in the DOM. TanStack Form does no DOM focus itself, so without\n * this a keyboard user is stranded on the submit button (WCAG 3.3.1).\n */\nexport function useFocusInvalidOnSubmit(\n form: AnyFormApi,\n options?: FocusInvalidOptions,\n): React.RefObject<HTMLFormElement | null> {\n const formRef = useRef<HTMLFormElement>(null);\n\n const submissionAttempts = useStore(form.store, (s) => s.submissionAttempts);\n const isValidating = useStore(form.store, (s) => s.isValidating);\n const isValid = useStore(form.store, (s) => s.isValid);\n\n // The tracker is stateful (it remembers the last attempt count), so it must\n // survive re-renders. Seed it once with the current attempt count so a form\n // mounted mid-flight doesn't focus spuriously.\n const trackerRef = useRef<\n | ((s: {\n submissionAttempts: number;\n isValidating: boolean;\n isValid: boolean;\n }) => void)\n | null\n >(null);\n if (trackerRef.current === null) {\n trackerRef.current = createSubmitFocusTracker(\n () => focusFirstInvalidControl(formRef.current, options),\n submissionAttempts,\n );\n }\n\n // Runs after commit → aria-invalid markers are in the DOM.\n useEffect(() => {\n trackerRef.current?.({ submissionAttempts, isValidating, isValid });\n }, [submissionAttempts, isValidating, isValid]);\n\n return formRef;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA6BA,SAAgB,wBACd,MACA,SACyC;CACzC,MAAM,UAAU,OAAwB,IAAI;CAE5C,MAAM,qBAAqB,SAAS,KAAK,QAAQ,MAAM,EAAE,kBAAkB;CAC3E,MAAM,eAAe,SAAS,KAAK,QAAQ,MAAM,EAAE,YAAY;CAC/D,MAAM,UAAU,SAAS,KAAK,QAAQ,MAAM,EAAE,OAAO;CAKrD,MAAM,aAAa,OAOjB,IAAI;CACN,IAAI,WAAW,YAAY,MACzB,WAAW,UAAU,+BACb,yBAAyB,QAAQ,SAAS,OAAO,GACvD,kBACF;CAIF,gBAAgB;EACd,WAAW,UAAU;GAAE;GAAoB;GAAc;EAAQ,CAAC;CACpE,GAAG;EAAC;EAAoB;EAAc;CAAO,CAAC;CAE9C,OAAO;AACT"}
1
+ {"version":3,"file":"useFocusInvalidOnSubmit.js","names":[],"sources":["../../src/useFocusInvalidOnSubmit.ts"],"sourcesContent":["import { useEffect, useRef } from \"react\";\nimport { useStore } from \"@tanstack/react-form\";\nimport {\n createSubmitFocusTracker,\n focusFirstInvalidControl,\n type FocusInvalidOptions,\n} from \"@pindoba/core-tanstack-form\";\nimport type { AnyFormApi } from \"./types\";\n\n/**\n * Returns a ref to attach to your `<form>` that moves focus to the first\n * invalid control after a failed submit:\n *\n * ```tsx\n * const formRef = useFocusInvalidOnSubmit(form);\n * return (\n * <form ref={formRef} onSubmit={(e) => { e.preventDefault(); form.handleSubmit(); }}>\n * …\n * </form>\n * );\n * ```\n *\n * It reads the form's submit-attempt / validation state, and once an attempt\n * ends invalid (after async validators settle) focuses the first control marked\n * `aria-invalid=\"true\"` inside the form. The state is read via `useStore`, so\n * the focus effect runs after React has committed the error render and the\n * markers are in the DOM. TanStack Form does no DOM focus itself, so without\n * this a keyboard user is stranded on the submit button (WCAG 3.3.1).\n */\nexport function useFocusInvalidOnSubmit(\n form: AnyFormApi,\n options?: FocusInvalidOptions,\n): { current: HTMLFormElement | null } {\n // Returned as a structural `{ current }` box, not `React.RefObject`:\n // RefObject<T> means `current: T | null` on @types/react@18 but `current: T`\n // on 19, so no single RefObject spelling is a valid `<form ref>` on both.\n const formRef = useRef<HTMLFormElement | null>(null);\n\n const submissionAttempts = useStore(form.store, (s) => s.submissionAttempts);\n const isValidating = useStore(form.store, (s) => s.isValidating);\n // TanStack's `handleSubmit` bumps `submissionAttempts` in a store write of\n // its own, then flags `isSubmitting`, and only then awaits the validators —\n // so a pristine form still reads `isValid: true` when the attempt lands.\n // The whole submit pipeline counts as \"still validating\"; the attempt is\n // judged once `isSubmitting` drops.\n const isSubmitting = useStore(form.store, (s) => s.isSubmitting);\n const isValid = useStore(form.store, (s) => s.isValid);\n\n // The tracker is stateful (it remembers the last attempt count), so it must\n // survive re-renders. Seed it once with the current attempt count so a form\n // mounted mid-flight doesn't focus spuriously.\n const trackerRef = useRef<\n | ((s: {\n submissionAttempts: number;\n isValidating: boolean;\n isValid: boolean;\n }) => void)\n | null\n >(null);\n if (trackerRef.current === null) {\n trackerRef.current = createSubmitFocusTracker(\n () => focusFirstInvalidControl(formRef.current, options),\n submissionAttempts,\n );\n }\n\n // Runs after commit → aria-invalid markers are in the DOM.\n const pipelineBusy = isValidating || isSubmitting;\n useEffect(() => {\n trackerRef.current?.({\n submissionAttempts,\n isValidating: pipelineBusy,\n isValid,\n });\n }, [submissionAttempts, pipelineBusy, isValid]);\n\n return formRef;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA6BA,SAAgB,wBACd,MACA,SACqC;CAIrC,MAAM,UAAU,OAA+B,IAAI;CAEnD,MAAM,qBAAqB,SAAS,KAAK,QAAQ,MAAM,EAAE,kBAAkB;CAC3E,MAAM,eAAe,SAAS,KAAK,QAAQ,MAAM,EAAE,YAAY;CAM/D,MAAM,eAAe,SAAS,KAAK,QAAQ,MAAM,EAAE,YAAY;CAC/D,MAAM,UAAU,SAAS,KAAK,QAAQ,MAAM,EAAE,OAAO;CAKrD,MAAM,aAAa,OAOjB,IAAI;CACN,IAAI,WAAW,YAAY,MACzB,WAAW,UAAU,+BACb,yBAAyB,QAAQ,SAAS,OAAO,GACvD,kBACF;CAIF,MAAM,eAAe,gBAAgB;CACrC,gBAAgB;EACd,WAAW,UAAU;GACnB;GACA,cAAc;GACd;EACF,CAAC;CACH,GAAG;EAAC;EAAoB;EAAc;CAAO,CAAC;CAE9C,OAAO;AACT"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pindoba/react-tanstack-form",
3
- "version": "0.0.0-alpha-20260907031008",
3
+ "version": "0.0.0-alpha-20261110151933",
4
4
  "homepage": "https://github.com/EduardoLopes/pindoba/tree/main/packages/blocks/react/tanstack-form#README.md",
5
5
  "repository": {
6
6
  "type": "git",
@@ -10,7 +10,8 @@
10
10
  "sideEffects": false,
11
11
  "license": "MIT",
12
12
  "files": [
13
- "dist"
13
+ "dist",
14
+ "!dist/**/__test__"
14
15
  ],
15
16
  "type": "module",
16
17
  "main": "./dist/index.js",
@@ -19,37 +20,34 @@
19
20
  ".": {
20
21
  "types": "./dist/index.d.ts",
21
22
  "import": "./dist/index.js"
22
- },
23
- "./demos": "./src/demos/index.ts",
24
- "./demos/basic": "./src/demos/basic.tsx",
25
- "./demos/mixed-controls": "./src/demos/mixed-controls.tsx",
26
- "./demos/generic-field": "./src/demos/generic-field.tsx"
23
+ }
27
24
  },
28
25
  "devDependencies": {
26
+ "@pindoba/build-config": "0.0.0",
29
27
  "@pindoba/typescript-config": "0.0.0-alpha-20260320034942",
30
- "@types/react": "^19.2.18",
31
- "@types/react-dom": "^19.2.5",
32
- "react": "^19.2.8",
33
- "react-dom": "^19.2.8",
34
- "tsdown": "^0.22.14",
28
+ "@types/react": "^19.3.0",
29
+ "@types/react-dom": "^19.3.0",
30
+ "react": "^19.3.0",
31
+ "react-dom": "^19.3.0",
32
+ "tsdown": "^0.23.0",
35
33
  "typescript": "^6.0.3",
36
- "zod": "^4.5.1"
34
+ "zod": "^4.6.5"
37
35
  },
38
36
  "peerDependencies": {
39
37
  "react": "^18.3.1 || ^19.0.0"
40
38
  },
41
39
  "dependencies": {
42
- "@pindoba/core-field": "0.0.0-alpha-20260829223243",
43
- "@pindoba/core-listbox": "0.0.0-alpha-20260829223243",
44
- "@pindoba/core-tanstack-form": "0.0.0-alpha-20260829223243",
45
- "@pindoba/react-affix": "0.0.0-alpha-20260829223243",
46
- "@pindoba/react-button": "0.0.0-alpha-20260829223243",
47
- "@pindoba/react-checkbox": "0.0.0-alpha-20260829223243",
48
- "@pindoba/react-field": "0.0.0-alpha-20260907031008",
49
- "@pindoba/react-input": "0.0.0-alpha-20260829223243",
50
- "@pindoba/react-loading": "0.0.0-alpha-20260829223243",
51
- "@pindoba/react-select": "0.0.0-alpha-20260829223243",
52
- "@pindoba/styled-system": "0.0.0-alpha-20260829223243",
40
+ "@pindoba/core-field": "0.0.0-alpha-20261110151933",
41
+ "@pindoba/core-listbox": "0.0.0-alpha-20261110151933",
42
+ "@pindoba/core-tanstack-form": "0.0.0-alpha-20261110151933",
43
+ "@pindoba/react-button": "0.0.0-alpha-20261110151933",
44
+ "@pindoba/react-checkbox": "0.0.0-alpha-20261110151933",
45
+ "@pindoba/react-field": "0.0.0-alpha-20261110151933",
46
+ "@pindoba/react-input": "0.0.0-alpha-20261110151933",
47
+ "@pindoba/react-loading": "0.0.0-alpha-20261110151933",
48
+ "@pindoba/react-select": "0.0.0-alpha-20261110151933",
49
+ "@pindoba/react-textarea": "0.0.0-alpha-20261110151933",
50
+ "@pindoba/styled-system": "0.0.0-alpha-20261110151933",
53
51
  "@tanstack/react-form": "^1.33.5"
54
52
  },
55
53
  "publishConfig": {
package/dist/src/types.js DELETED
@@ -1 +0,0 @@
1
- export {};