@dashforge/tw 1.7.0 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -12,6 +12,37 @@ This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
12
12
  > duplicated intentionally — no shared "lowest common denominator" headless
13
13
  > layer.
14
14
 
15
+ ## [1.7.1] — 2026-09-25
16
+
17
+ Patch: closes the half of BUG 17 that never reached this package.
18
+
19
+ ### Fixed
20
+
21
+ - **An explicit `helperText` no longer swallows the field's validation
22
+ message** (BUG 32). This package carries its own copy of the
23
+ validation resolver at
24
+ `src/components/_shared/resolveValidationState.ts`, and it kept the
25
+ inverted precedence that BUG 17 corrected on the MUI side back on
26
+ 2026-09-15. The effect: any bridge-managed field given a constant
27
+ hint painted the danger state and set `aria-invalid="true"` while
28
+ still rendering the hint text, so the user saw a red field with no
29
+ stated reason. **Fourteen components route through that resolver**
30
+ (Autocomplete, Checkbox, DatePicker, DateRangePicker, DateTimePicker,
31
+ NumberField, OTPField, RadioGroup, Select, Slider, Switch, TextField,
32
+ Textarea, TimePicker), so a single line fixes all of them.
33
+ `helperText` now reads `autoMessage ?? explicitHelperText`: the
34
+ validation message wins while it is showing, the hint is the fallback
35
+ for the no-error state. The `error` boolean is unchanged — an
36
+ explicit prop still forces the visual.
37
+
38
+ The resolver's docstring previously advertised itself as matching the
39
+ MUI side "byte-for-byte", which is what let the divergence survive
40
+ ten days. It now carries an explicit warning that the two files are
41
+ hand-kept copies. New regression suite at
42
+ `src/components/_shared/resolveValidationState.test.ts` (7 assertions)
43
+ mirrors the MUI-side one, and each docstring points at the other.
44
+ See `README-BUG.md` § BUG 32.
45
+
15
46
  ## [1.7.0] — 2026-09-22
16
47
 
17
48
  ### Added
package/dist/index.esm.js CHANGED
@@ -870,23 +870,33 @@ const tooltipVariants = tv({
870
870
  /**
871
871
  * Compute `error` + `helperText` for a bridge-managed field.
872
872
  *
873
- * **Renderer-agnostic** — copy of the MUI-side
874
- * `textField.validation.ts`. Promoted to a `_shared/` directory inside
875
- * `@dashforge/tw` so all form components (TextField + Checkbox +
876
- * Switch) reuse the same precedence logic without depending on a
873
+ * **Renderer-agnostic.** Mirrors the MUI-side
874
+ * `ui/src/components/TextField/textField.validation.ts`. Promoted to a
875
+ * `_shared/` directory inside `@dashforge/tw` so all 14 bridge-managed
876
+ * form components reuse the same precedence without depending on a
877
877
  * specific component folder.
878
878
  *
879
- * Precedence rules (matches MUI side byte-for-byte):
880
- *
881
- * 1. **Explicit props win.** If the user passed `error` / `helperText`
882
- * explicitly, those values are used as-is.
883
- * 2. **Auto values are gated by interaction.** The bridge's error is
879
+ * ⚠️ This file is a COPY, not an import. The two sides have to be kept
880
+ * in step by hand, and once already were not: BUG 17 (2026-09-15)
881
+ * inverted the `helperText` precedence on the MUI side only, and this
882
+ * copy kept the broken order for ten days until BUG 32 caught it. The
883
+ * verification that missed it was a grep for imports of the MUI file,
884
+ * which by construction cannot reach a copy. **When you change the
885
+ * precedence here, change it there too — and vice versa.**
886
+ *
887
+ * Precedence rules:
888
+ *
889
+ * 1. **`error`: the explicit prop wins.** An author who passes `error`
890
+ * is forcing the visual state, so it overrides the auto-gate.
891
+ * 2. **`helperText`: the validation message wins while it is showing;
892
+ * the explicit prop is the fallback for the no-error state.** A
893
+ * constant hint ("Unique, uppercase") must not swallow the reason a
894
+ * required field was rejected — that leaves the user looking at a
895
+ * red field with no explanation. See README-BUG.md § BUG 17 / § BUG 32.
896
+ * 3. **Auto values are gated by interaction.** The bridge's error is
884
897
  * surfaced only when the field is `touched` OR the form has been
885
898
  * submitted at least once (`submitCount > 0`). This prevents the
886
899
  * "error spam while typing" anti-pattern.
887
- * 3. **`error === false` explicitly clears the bridge's error message
888
- * text.** Useful for forcing a "valid" visual state regardless of
889
- * bridge state.
890
900
  *
891
901
  * @param name field name registered with the bridge
892
902
  * @param bridge active `DashFormBridge` (guaranteed non-null at
@@ -899,8 +909,16 @@ const tooltipVariants = tv({
899
909
  const autoTouched = (_bridge_isTouched = bridge.isTouched(name)) != null ? _bridge_isTouched : false;
900
910
  const submitCount = (_bridge_submitCount = bridge.submitCount) != null ? _bridge_submitCount : 0;
901
911
  const allowAutoError = autoTouched || submitCount > 0;
912
+ // `error` still lets an explicit prop force the visual state.
902
913
  const error = explicitError != null ? explicitError : Boolean(autoErr) && allowAutoError;
903
- const helperText = explicitHelperText != null ? explicitHelperText : allowAutoError ? autoErr == null ? void 0 : autoErr.message : undefined;
914
+ // BUG 32: the validation message wins over the explicit hint while it
915
+ // is showing. The previous order (`explicitHelperText ?? autoMessage`)
916
+ // short-circuited on the hint, so any field carrying one painted the
917
+ // danger state and `aria-invalid="true"` while still rendering the
918
+ // hint text — a red field with no stated reason. Identical inversion
919
+ // to the one BUG 17 landed on the MUI side.
920
+ const autoMessage = allowAutoError ? autoErr == null ? void 0 : autoErr.message : undefined;
921
+ const helperText = autoMessage != null ? autoMessage : explicitHelperText;
904
922
  return {
905
923
  error,
906
924
  helperText
@@ -14,23 +14,33 @@ export interface ValidationState {
14
14
  /**
15
15
  * Compute `error` + `helperText` for a bridge-managed field.
16
16
  *
17
- * **Renderer-agnostic** — copy of the MUI-side
18
- * `textField.validation.ts`. Promoted to a `_shared/` directory inside
19
- * `@dashforge/tw` so all form components (TextField + Checkbox +
20
- * Switch) reuse the same precedence logic without depending on a
17
+ * **Renderer-agnostic.** Mirrors the MUI-side
18
+ * `ui/src/components/TextField/textField.validation.ts`. Promoted to a
19
+ * `_shared/` directory inside `@dashforge/tw` so all 14 bridge-managed
20
+ * form components reuse the same precedence without depending on a
21
21
  * specific component folder.
22
22
  *
23
- * Precedence rules (matches MUI side byte-for-byte):
23
+ * ⚠️ This file is a COPY, not an import. The two sides have to be kept
24
+ * in step by hand, and once already were not: BUG 17 (2026-09-15)
25
+ * inverted the `helperText` precedence on the MUI side only, and this
26
+ * copy kept the broken order for ten days until BUG 32 caught it. The
27
+ * verification that missed it was a grep for imports of the MUI file,
28
+ * which by construction cannot reach a copy. **When you change the
29
+ * precedence here, change it there too — and vice versa.**
24
30
  *
25
- * 1. **Explicit props win.** If the user passed `error` / `helperText`
26
- * explicitly, those values are used as-is.
27
- * 2. **Auto values are gated by interaction.** The bridge's error is
31
+ * Precedence rules:
32
+ *
33
+ * 1. **`error`: the explicit prop wins.** An author who passes `error`
34
+ * is forcing the visual state, so it overrides the auto-gate.
35
+ * 2. **`helperText`: the validation message wins while it is showing;
36
+ * the explicit prop is the fallback for the no-error state.** A
37
+ * constant hint ("Unique, uppercase") must not swallow the reason a
38
+ * required field was rejected — that leaves the user looking at a
39
+ * red field with no explanation. See README-BUG.md § BUG 17 / § BUG 32.
40
+ * 3. **Auto values are gated by interaction.** The bridge's error is
28
41
  * surfaced only when the field is `touched` OR the form has been
29
42
  * submitted at least once (`submitCount > 0`). This prevents the
30
43
  * "error spam while typing" anti-pattern.
31
- * 3. **`error === false` explicitly clears the bridge's error message
32
- * text.** Useful for forcing a "valid" visual state regardless of
33
- * bridge state.
34
44
  *
35
45
  * @param name field name registered with the bridge
36
46
  * @param bridge active `DashFormBridge` (guaranteed non-null at
@@ -1 +1 @@
1
- {"version":3,"file":"resolveValidationState.d.ts","sourceRoot":"","sources":["../../../../src/components/_shared/resolveValidationState.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEzD;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,SAAS,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,cAAc,EACtB,aAAa,EAAE,OAAO,GAAG,SAAS,EAClC,kBAAkB,EAAE,SAAS,GAAG,SAAS,GACxC,eAAe,CAYjB"}
1
+ {"version":3,"file":"resolveValidationState.d.ts","sourceRoot":"","sources":["../../../../src/components/_shared/resolveValidationState.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEzD;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,SAAS,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,cAAc,EACtB,aAAa,EAAE,OAAO,GAAG,SAAS,EAClC,kBAAkB,EAAE,SAAS,GAAG,SAAS,GACxC,eAAe,CAoBjB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dashforge/tw",
3
- "version": "1.7.0",
3
+ "version": "1.7.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./dist/index.esm.js",
@@ -35,9 +35,9 @@
35
35
  "tailwind-merge": "^3.0.0",
36
36
  "tailwind-variants": "^3.1.1",
37
37
  "@dashforge/calendar-core": "1.0.0",
38
- "@dashforge/rbac": "1.0.0",
39
38
  "@dashforge/forms": "1.2.0",
40
- "@dashforge/ui-core": "1.2.0"
39
+ "@dashforge/ui-core": "1.2.0",
40
+ "@dashforge/rbac": "1.0.0"
41
41
  },
42
42
  "peerDependencies": {
43
43
  "react": "^18.0.0 || ^19.0.0",