haze-ui 1.8.1 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,6 +4,9 @@
4
4
 
5
5
  English | [简体中文](./README-zh_CN.md)
6
6
 
7
+ [![npm](https://img.shields.io/npm/v/haze-ui)](https://www.npmjs.com/package/haze-ui)
8
+ [![CI](https://github.com/wmzy/haze-ui/actions/workflows/ci.yml/badge.svg)](https://github.com/wmzy/haze-ui/actions/workflows/ci.yml)
9
+
7
10
  ## Features
8
11
 
9
12
  - Integrated [react use control](https://github.com/wmzy/react-use-control) provides component internal states
@@ -86,6 +89,40 @@ function ProfileForm() {
86
89
  surfaces the first error in a `role="alert"` element — no manual
87
90
  `FieldError` wiring.
88
91
 
92
+ #### `mode`: per-field validation timing (react-f0rm ≥ 0.6)
93
+
94
+ Pass `mode` to validate one field on its own schedule instead of the
95
+ form-wide validation mode — other fields are unaffected. It accepts
96
+ react-f0rm's `ValidationMode` values: `'onSubmit'` (default form
97
+ behavior), `'onBlur'`, `'onChange'`, `'onTouched'` or `'all'`. Omit it to
98
+ keep the form's mode.
99
+
100
+ ```jsx
101
+ <FormItem
102
+ form={form}
103
+ name="email"
104
+ label="Email"
105
+ mode="onBlur"
106
+ validate={(v) => (v.includes('@') ? undefined : 'must be an email')}
107
+ >
108
+ {({ id, errorId, invalid, onBlur, control }) => (
109
+ <Input
110
+ id={id}
111
+ value={control}
112
+ aria-invalid={invalid}
113
+ aria-describedby={errorId}
114
+ onBlur={onBlur}
115
+ />
116
+ )}
117
+ </FormItem>
118
+ ```
119
+
120
+ With `mode="onBlur"` (and the binding's `onBlur` passed to the control,
121
+ as above) the email field is validated the moment it loses focus — no
122
+ submit needed. `mode` accepts `'onSubmit'`, `'onBlur'`, `'onChange'`,
123
+ `'onTouched'` or `'all'`; only this field's schedule changes, the rest
124
+ of the form keeps its own `mode`.
125
+
89
126
  ## Related Projects
90
127
 
91
128
  - [react-use-control](https://github.com/wmzy/react-use-control)
@@ -1,22 +1,25 @@
1
1
  import { classnames as e } from "../../utils/classnames.js";
2
2
  /* empty css */
3
3
  import { jsx as t } from "react/jsx-runtime";
4
+ import { forwardRef as n } from "react";
4
5
  //#region src/lib/components/NavigationBar/NavLink.tsx
5
- var n = "haze-NavLink__link", r = "haze-NavLink__activeLink";
6
- function i({ href: i = "#", active: a, children: o, onClick: s, className: c }) {
6
+ var r = "haze-NavLink__link", i = "haze-NavLink__activeLink", a = n(function({ href: n = "#", active: a, children: o, onClick: s, className: c, "aria-current": l, ...u }, d) {
7
+ let f = a ?? l === "page";
7
8
  return /* @__PURE__ */ t("a", {
8
- href: i,
9
+ ref: d,
10
+ href: n,
11
+ "aria-current": f ? "page" : l,
9
12
  onClick: (e) => {
10
- s && (e.preventDefault(), s());
13
+ n === "#" && e.preventDefault(), s?.(e);
11
14
  },
12
- "aria-current": a ? "page" : void 0,
15
+ ...u,
13
16
  className: e([
14
- n,
15
- a && r,
17
+ r,
18
+ f && i,
16
19
  c
17
20
  ]),
18
21
  children: o
19
22
  });
20
- }
23
+ });
21
24
  //#endregion
22
- export { i as default };
25
+ export { a as default };
@@ -6,34 +6,34 @@ import { useId as i } from "react";
6
6
  import { useField as a, useFieldErrors as o } from "react-f0rm";
7
7
  //#region src/lib/form/FormItem.tsx
8
8
  var s = "haze-FormItem__item", c = "haze-FormItem__labelText", l = "haze-FormItem__errorText";
9
- function u({ form: u, name: d, label: f, validate: p, className: m, children: h }) {
10
- let g = `haze-field-${i()}`, _ = `${g}-error`;
11
- a({
9
+ function u({ form: u, name: d, label: f, validate: p, mode: m, className: h, children: g }) {
10
+ let _ = `haze-field-${i()}`, v = `${_}-error`, { onBlur: y } = a({
12
11
  form: u,
13
12
  name: d,
14
- validate: p
15
- });
16
- let v = o(u, d), y = t(u, d), b = v.length > 0;
13
+ validate: p,
14
+ mode: m
15
+ }), b = o(u, d), x = t(u, d), S = b.length > 0;
17
16
  return /* @__PURE__ */ r("div", {
18
- className: e([s, m]),
17
+ className: e([s, h]),
19
18
  children: [
20
19
  f !== void 0 && /* @__PURE__ */ n("label", {
21
- htmlFor: g,
20
+ htmlFor: _,
22
21
  className: e(c),
23
22
  children: f
24
23
  }),
25
- h({
26
- id: g,
27
- errorId: _,
28
- invalid: b,
29
- errors: v,
30
- control: y
31
- }),
32
- b && /* @__PURE__ */ n("span", {
24
+ g({
33
25
  id: _,
26
+ errorId: v,
27
+ invalid: S,
28
+ errors: b,
29
+ onBlur: y,
30
+ control: x
31
+ }),
32
+ S && /* @__PURE__ */ n("span", {
33
+ id: v,
34
34
  role: "alert",
35
35
  className: e(l),
36
- children: v[0].message
36
+ children: b[0].message
37
37
  })
38
38
  ]
39
39
  });
@@ -1,10 +1,42 @@
1
- import type { ReactNode } from 'react';
1
+ import type { ComponentPropsWithoutRef, MouseEvent, ReactNode } from 'react';
2
+ /**
3
+ * Extends the native `<a>` attributes. Everything not listed below is spread
4
+ * onto the rendered anchor, so callers (and routers composing NavLink via
5
+ * their `as` prop) can inject `target`, `rel`, `aria-*`, and friends.
6
+ */
2
7
  type NavLinkProps = {
3
8
  href?: string;
9
+ /**
10
+ * Explicit active state. Falls back to `aria-current="page"` when omitted,
11
+ * letting routers drive highlighting through the standard aria attribute.
12
+ */
4
13
  active?: boolean;
5
14
  children: ReactNode;
6
- onClick?: () => void;
15
+ /**
16
+ * Native click handler: receives the underlying mouse event. Whether to
17
+ * call `event.preventDefault()` is up to the caller (an SPA router Link
18
+ * does so itself), except for placeholder hrefs where NavLink keeps
19
+ * button semantics (see the onClick handler below).
20
+ */
21
+ onClick?: (event: MouseEvent<HTMLAnchorElement>) => void;
7
22
  className?: string;
8
- };
9
- export default function NavLink({ href, active, children, onClick, className }: NavLinkProps): import("react").JSX.Element;
23
+ } & Omit<ComponentPropsWithoutRef<'a'>, 'href' | 'children' | 'onClick' | 'className'>;
24
+ declare const _default: import("react").ForwardRefExoticComponent<{
25
+ href?: string;
26
+ /**
27
+ * Explicit active state. Falls back to `aria-current="page"` when omitted,
28
+ * letting routers drive highlighting through the standard aria attribute.
29
+ */
30
+ active?: boolean;
31
+ children: ReactNode;
32
+ /**
33
+ * Native click handler: receives the underlying mouse event. Whether to
34
+ * call `event.preventDefault()` is up to the caller (an SPA router Link
35
+ * does so itself), except for placeholder hrefs where NavLink keeps
36
+ * button semantics (see the onClick handler below).
37
+ */
38
+ onClick?: (event: MouseEvent<HTMLAnchorElement>) => void;
39
+ className?: string;
40
+ } & Omit<Omit<import("react").DetailedHTMLProps<import("react").AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>, "ref">, "className" | "children" | "onClick" | "href"> & import("react").RefAttributes<HTMLAnchorElement>>;
41
+ export default _default;
10
42
  export type { NavLinkProps };
@@ -1,5 +1,5 @@
1
1
  import type { ReactNode } from 'react';
2
- import type { FieldError, FieldPath, Name } from 'react-f0rm';
2
+ import type { FieldError, FieldPath, Name, ValidationMode } from 'react-f0rm';
3
3
  import type { Control } from 'react-use-control';
4
4
  import type { FormInstance, PathValueOf } from './useFormControl';
5
5
  /**
@@ -22,6 +22,10 @@ export type FormItemBinding<TValues, P extends FieldPath<TValues> | Name> = {
22
22
  invalid: boolean;
23
23
  /** every error registered for the field, in insertion order */
24
24
  errors: FieldError[];
25
+ /** react-f0rm blur hook for this field — pass to the control's `onBlur`
26
+ * (`<Input onBlur={onBlur}/>`) so blur-scheduled validation modes
27
+ * (`mode='onBlur' | 'onTouched' | 'all'`) fire when the field loses focus. */
28
+ onBlur: () => void;
25
29
  /** Control bound to the field value — pass to a control prop */
26
30
  control: Control<PathValueOf<TValues, P>>;
27
31
  };
@@ -32,6 +36,11 @@ export type FormItemProps<TValues extends Record<string, any> = any, P extends F
32
36
  /** Field-level validator, registered through react-f0rm's own
33
37
  * `useField` channel — validated per the form's `mode` and on submit. */
34
38
  validate?: FieldValidator;
39
+ /** Per-field validation mode override (react-f0rm ≥0.6): when given,
40
+ * this field validates on its own schedule — e.g. `'onBlur'` — instead
41
+ * of the form's `mode`; other fields are unaffected. Omit to keep the
42
+ * form-wide behavior. */
43
+ mode?: ValidationMode;
35
44
  className?: string;
36
45
  children: (binding: FormItemBinding<TValues, P>) => ReactNode;
37
46
  };
@@ -52,4 +61,4 @@ export type FormItemProps<TValues extends Record<string, any> = any, P extends F
52
61
  * `<span id={errorId} role='alert'>` next to the children; with no errors
53
62
  * no extra element is rendered.
54
63
  */
55
- export default function FormItem<TValues extends Record<string, any> = any, P extends FieldPath<TValues> | Name = Name>({ form, name, label, validate, className, children }: FormItemProps<TValues, P>): import("react").JSX.Element;
64
+ export default function FormItem<TValues extends Record<string, any> = any, P extends FieldPath<TValues> | Name = Name>({ form, name, label, validate, mode, className, children }: FormItemProps<TValues, P>): import("react").JSX.Element;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "haze-ui",
3
- "version": "1.8.1",
3
+ "version": "1.10.0",
4
4
  "type": "module",
5
5
  "description": "A React UI component library powered by react-use-control and Linaria",
6
6
  "main": "./dist/index.js",
@@ -55,7 +55,7 @@
55
55
  "react-f0rm": ">=0.3.0"
56
56
  },
57
57
  "dependencies": {
58
- "react-toolroom": "^0.7.0",
58
+ "react-toolroom": "^0.11.0",
59
59
  "react-use-control": "^1.3.2"
60
60
  },
61
61
  "devDependencies": {
@@ -65,26 +65,26 @@
65
65
  "@faker-js/faker": "^10.6.0",
66
66
  "@for-fun/event-emitter": "^1.0.1",
67
67
  "@linaria/core": "^8.2.0",
68
- "@native-router/core": "^1.5.0",
69
- "@native-router/react": "^1.3.0",
68
+ "@native-router/core": "^1.8.0",
69
+ "@native-router/react": "^1.6.1",
70
70
  "@testing-library/jest-dom": "^7.0.1",
71
71
  "@testing-library/react": "^16.3.2",
72
72
  "@testing-library/user-event": "^14.6.6",
73
73
  "@types/jest-axe": "^3.5.9",
74
- "@types/node": "^26.2.0",
74
+ "@types/node": "^26.3.0",
75
75
  "@types/ramda": "^0.32.0",
76
76
  "@types/react": "^19.2.18",
77
- "@types/react-dom": "^19.2.4",
77
+ "@types/react-dom": "^19.2.5",
78
78
  "@vitejs/plugin-react": "^6.1.0",
79
79
  "@vitest/coverage-v8": "4.1.11",
80
- "@wyw-in-js/babel-preset": "^2.4.2",
81
- "@wyw-in-js/vite": "^2.4.2",
80
+ "@wyw-in-js/babel-preset": "^2.4.3",
81
+ "@wyw-in-js/vite": "^2.4.3",
82
82
  "babel-plugin-transform-jsx-class": "^0.1.3",
83
83
  "babel-plugin-transform-jsx-condition": "^0.1.3",
84
84
  "commitizen": "^4.3.2",
85
85
  "core-js": "^3.50.0",
86
86
  "date-fns": "^4.4.0",
87
- "eslint": "^10.9.0",
87
+ "eslint": "^10.9.1",
88
88
  "eslint-plugin-compat": "^7.0.2",
89
89
  "gh-pages": "^6.3.0",
90
90
  "husky": "^9.1.7",
@@ -92,17 +92,17 @@
92
92
  "jsdom": "^30.0.1",
93
93
  "json-schema-faker": "^0.6.3",
94
94
  "lint-staged": "^17.3.0",
95
- "lucide-react": "^1.33.0",
95
+ "lucide-react": "^1.34.0",
96
96
  "prettier": "^3.9.6",
97
97
  "qss": "^3.0.0",
98
98
  "ramda": "^0.32.0",
99
99
  "react": "^19.2.8",
100
100
  "react-dom": "^19.2.8",
101
- "react-f0rm": "^0.5.0",
101
+ "react-f0rm": "^0.6.0",
102
102
  "react-icons": "^5.7.0",
103
103
  "rollup-plugin-type-as-json-schema": "^0.2.6",
104
104
  "semantic-release": "^25.0.9",
105
- "terser": "^5.50.0",
105
+ "terser": "^5.51.0",
106
106
  "tools-config": "^0.3.0",
107
107
  "tsc-alias": "^1.9.2",
108
108
  "typescript": "^6.0.3",