@astryxdesign/core 0.7.5-canary.c60f571 → 0.7.5-canary.cd53c2d

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 (54) hide show
  1. package/dist/DateInput/Wheel.d.ts.map +1 -1
  2. package/dist/DateInput/Wheel.js +1 -1
  3. package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts +10 -5
  4. package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts.map +1 -1
  5. package/dist/DropdownMenu/DropdownMenuRadioItem.js +13 -5
  6. package/dist/Field/inputDisabledFocus.d.ts +14 -0
  7. package/dist/Field/inputDisabledFocus.d.ts.map +1 -0
  8. package/dist/Field/inputDisabledFocus.js +19 -0
  9. package/dist/Layer/useTouchTrigger.d.ts +14 -1
  10. package/dist/Layer/useTouchTrigger.d.ts.map +1 -1
  11. package/dist/Layer/useTouchTrigger.js +72 -17
  12. package/dist/NumberInput/NumberInput.d.ts +3 -3
  13. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  14. package/dist/NumberInput/NumberInput.js +46 -20
  15. package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
  16. package/dist/SideNav/SideNavHeading.js +34 -0
  17. package/dist/Skeleton/Skeleton.d.ts.map +1 -1
  18. package/dist/Skeleton/Skeleton.js +12 -3
  19. package/dist/TextArea/TextArea.d.ts +6 -4
  20. package/dist/TextArea/TextArea.d.ts.map +1 -1
  21. package/dist/TextArea/TextArea.js +14 -11
  22. package/dist/TextInput/TextInput.d.ts +6 -4
  23. package/dist/TextInput/TextInput.d.ts.map +1 -1
  24. package/dist/TextInput/TextInput.js +27 -17
  25. package/dist/astryx.css +2 -3
  26. package/package.json +3 -3
  27. package/src/DateInput/DateInputTouch.test.tsx +9 -0
  28. package/src/DateInput/Wheel.tsx +4 -1
  29. package/src/DateInput/__tests__/WheelPinchZoom.a11y.chromium.spec.ts +158 -0
  30. package/src/DropdownMenu/DropdownMenuRadioItem.doc.mjs +5 -2
  31. package/src/DropdownMenu/DropdownMenuRadioItem.spec.md +226 -0
  32. package/src/DropdownMenu/DropdownMenuRadioItem.tsx +32 -16
  33. package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +158 -0
  34. package/src/DropdownMenu/__tests__/DropdownMenuRadioItemAudit.a11y.chromium.spec.ts +539 -0
  35. package/src/Field/inputDisabledFocus.ts +28 -0
  36. package/src/HoverCard/HoverCard.test.tsx +93 -3
  37. package/src/Layer/__tests__/TouchTrigger.a11y.chromium.spec.ts +199 -0
  38. package/src/Layer/useTouchTrigger.ts +95 -41
  39. package/src/NumberInput/NumberInput.doc.mjs +6 -6
  40. package/src/NumberInput/NumberInput.test.tsx +144 -14
  41. package/src/NumberInput/NumberInput.tsx +68 -21
  42. package/src/Pagination/Pagination.test.tsx +2 -2
  43. package/src/SideNav/SideNav.test.tsx +15 -6
  44. package/src/SideNav/SideNavHeading.tsx +41 -0
  45. package/src/Skeleton/Skeleton.test.tsx +108 -1
  46. package/src/Skeleton/Skeleton.tsx +30 -2
  47. package/src/TextArea/TextArea.doc.mjs +6 -6
  48. package/src/TextArea/TextArea.test.tsx +65 -6
  49. package/src/TextArea/TextArea.tsx +26 -14
  50. package/src/TextInput/TextInput.doc.mjs +8 -8
  51. package/src/TextInput/TextInput.test.tsx +83 -10
  52. package/src/TextInput/TextInput.tsx +38 -19
  53. package/src/Tooltip/Tooltip.test.tsx +148 -3
  54. package/src/Tooltip/__tests__/Tooltip.a11y.states.ts +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"Wheel.d.ts","sourceRoot":"","sources":["../../src/DateInput/Wheel.tsx"],"names":[],"mappings":"AA2MA,MAAM,WAAW,WAAW;IAC1B,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,iEAAiE;IACjE,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,2BAA2B;IAC3B,OAAO,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC;IACpC,wDAAwD;IACxD,KAAK,EAAE,MAAM,CAAC;IACd,sEAAsE;IACtE,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,KAAK,CAAC,EACpB,KAAK,EACL,OAAO,EACP,KAAK,EACL,QAAQ,EACR,QAAe,GAChB,EAAE,UAAU,+BAgOZ;yBAtOe,KAAK"}
1
+ {"version":3,"file":"Wheel.d.ts","sourceRoot":"","sources":["../../src/DateInput/Wheel.tsx"],"names":[],"mappings":"AA8MA,MAAM,WAAW,WAAW;IAC1B,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,iEAAiE;IACjE,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,2BAA2B;IAC3B,OAAO,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC;IACpC,wDAAwD;IACxD,KAAK,EAAE,MAAM,CAAC;IACd,sEAAsE;IACtE,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,KAAK,CAAC,EACpB,KAAK,EACL,OAAO,EACP,KAAK,EACL,QAAQ,EACR,QAAe,GAChB,EAAE,UAAU,+BAgOZ;yBAtOe,KAAK"}
@@ -68,7 +68,7 @@ const styles = {
68
68
  kmaZ5I: "x1oyok0e",
69
69
  k5wCbM: "x1rohswg",
70
70
  kI3sdo: "x1a2a7pz",
71
- kFalU9: "xx69xxh",
71
+ kFalU9: "xge0x1e",
72
72
  $$css: true
73
73
  }
74
74
  };
@@ -8,7 +8,8 @@
8
8
  * (role="menuitemradio"). The row owns the role and aria-checked; there is no
9
9
  * nested native <input> (per the WAI-ARIA menuitemradio pattern). Selection
10
10
  * state + onChange come from DropdownMenuRadioGroupContext. Keyboard nav +
11
- * Enter/Space activation come from the parent DropdownMenu.
11
+ * Enter/Space activation come from the parent DropdownMenu. Caller onClick and
12
+ * onPointerMove run after the row's own selection and hover focus.
12
13
  *
13
14
  * The round radio visual is the shared radio indicator, decorative
14
15
  * (aria-hidden) — the row owns the checked state, and menu radios pick up the
@@ -39,9 +40,9 @@ export interface DropdownMenuRadioItemProps extends Omit<BaseProps, 'role' | 'ar
39
40
  */
40
41
  icon?: ReactNode | IconType;
41
42
  /**
42
- * Whether this individual radio item is disabled. Disabled items stay
43
- * focusable (via `aria-disabled`) so they remain discoverable by keyboard
44
- * and assistive technology, but selection is blocked.
43
+ * Whether this individual radio item is disabled. Disabled items expose
44
+ * `aria-disabled`, block selection, and are currently skipped by
45
+ * DropdownMenu arrow-key navigation.
45
46
  * @default false
46
47
  */
47
48
  isDisabled?: boolean;
@@ -56,6 +57,10 @@ export interface DropdownMenuRadioItemProps extends Omit<BaseProps, 'role' | 'ar
56
57
  /**
57
58
  * A single option in a DropdownMenuRadioGroup (role="menuitemradio").
58
59
  *
60
+ * Consumer click and pointer-move handlers compose after the row's own
61
+ * selection and hover-focus behavior; disabled activation stops before the
62
+ * consumer click handler.
63
+ *
59
64
  * @example
60
65
  * ```
61
66
  * <DropdownMenuRadioGroup value={sort} onChange={setSort} label="Sort by">
@@ -64,7 +69,7 @@ export interface DropdownMenuRadioItemProps extends Omit<BaseProps, 'role' | 'ar
64
69
  * </DropdownMenuRadioGroup>
65
70
  * ```
66
71
  */
67
- export declare function DropdownMenuRadioItem({ value, label, description, icon, isDisabled, endContent, xstyle, className, style, ...rest }: DropdownMenuRadioItemProps): import("react").JSX.Element;
72
+ export declare function DropdownMenuRadioItem({ value, label, description, icon, isDisabled, endContent, xstyle, className, style, onClick, onPointerMove, ...rest }: DropdownMenuRadioItemProps): import("react").JSX.Element;
68
73
  export declare namespace DropdownMenuRadioItem {
69
74
  var displayName: string;
70
75
  }
@@ -1 +1 @@
1
- {"version":3,"file":"DropdownMenuRadioItem.d.ts","sourceRoot":"","sources":["../../src/DropdownMenu/DropdownMenuRadioItem.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAiC,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAErE,OAAO,EAAiB,KAAK,QAAQ,EAAC,MAAM,SAAS,CAAC;AAOtD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AA4C5C,MAAM,WAAW,0BAA2B,SAAQ,IAAI,CACtD,SAAS,EACT,MAAM,GAAG,cAAc,GAAG,UAAU,CACrC;IACC;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,KAAK,EAAE,SAAS,CAAC;IACjB;;OAEG;IACH,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB;;;OAGG;IACH,IAAI,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC5B;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB,mFAAmF;IACnF,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;CAC9B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,KAAK,EACL,KAAK,EACL,WAAW,EACX,IAAI,EACJ,UAAkB,EAClB,UAAU,EACV,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,IAAI,EACR,EAAE,0BAA0B,+BAmE5B;yBA9Ee,qBAAqB"}
1
+ {"version":3,"file":"DropdownMenuRadioItem.d.ts","sourceRoot":"","sources":["../../src/DropdownMenu/DropdownMenuRadioItem.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAIL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,OAAO,EAAiB,KAAK,QAAQ,EAAC,MAAM,SAAS,CAAC;AAOtD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AA4C5C,MAAM,WAAW,0BAA2B,SAAQ,IAAI,CACtD,SAAS,EACT,MAAM,GAAG,cAAc,GAAG,UAAU,CACrC;IACC;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,KAAK,EAAE,SAAS,CAAC;IACjB;;OAEG;IACH,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB;;;OAGG;IACH,IAAI,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC5B;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB,mFAAmF;IACnF,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;CAC9B;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,KAAK,EACL,KAAK,EACL,WAAW,EACX,IAAI,EACJ,UAAkB,EAClB,UAAU,EACV,MAAM,EACN,SAAS,EACT,KAAK,EACL,OAAO,EACP,aAAa,EACb,GAAG,IAAI,EACR,EAAE,0BAA0B,+BAuE5B;yBApFe,qBAAqB"}
@@ -12,7 +12,8 @@
12
12
  * (role="menuitemradio"). The row owns the role and aria-checked; there is no
13
13
  * nested native <input> (per the WAI-ARIA menuitemradio pattern). Selection
14
14
  * state + onChange come from DropdownMenuRadioGroupContext. Keyboard nav +
15
- * Enter/Space activation come from the parent DropdownMenu.
15
+ * Enter/Space activation come from the parent DropdownMenu. Caller onClick and
16
+ * onPointerMove run after the row's own selection and hover focus.
16
17
  *
17
18
  * The round radio visual is the shared radio indicator, decorative
18
19
  * (aria-hidden) — the row owns the checked state, and menu radios pick up the
@@ -29,7 +30,7 @@ import { useDropdownMenuContext } from "./DropdownMenuContext.js";
29
30
  import { focusMenuItemOnHover } from "./menuItemHover.js";
30
31
  import "../theme/tokens.stylex.js";
31
32
  import { colorVars, spacingVars } from "../theme/tokens.stylex.js";
32
- import { mergeProps, themeProps } from "../utils/index.js";
33
+ import { composeEventHandlers, mergeProps, themeProps } from "../utils/index.js";
33
34
  import { useDropdownMenuRadioGroupContext } from "./DropdownMenuContext.js";
34
35
  import { jsx as _jsx } from "react/jsx-runtime";
35
36
  const styles = {
@@ -61,6 +62,10 @@ const styles = {
61
62
  /**
62
63
  * A single option in a DropdownMenuRadioGroup (role="menuitemradio").
63
64
  *
65
+ * Consumer click and pointer-move handlers compose after the row's own
66
+ * selection and hover-focus behavior; disabled activation stops before the
67
+ * consumer click handler.
68
+ *
64
69
  * @example
65
70
  * ```
66
71
  * <DropdownMenuRadioGroup value={sort} onChange={setSort} label="Sort by">
@@ -79,6 +84,8 @@ export function DropdownMenuRadioItem({
79
84
  xstyle,
80
85
  className,
81
86
  style,
87
+ onClick,
88
+ onPointerMove,
82
89
  ...rest
83
90
  }) {
84
91
  const menuCtx = useDropdownMenuContext();
@@ -90,7 +97,7 @@ export function DropdownMenuRadioItem({
90
97
  const controlSize = menuSize === 'sm' ? 'sm' : 'md';
91
98
  const isChecked = groupCtx.value === value;
92
99
  const RadioControl = useIndicator('radio');
93
- const handleClick = useCallback(() => {
100
+ const handleClick = useCallback(event => {
94
101
  if (isDisabled) {
95
102
  return;
96
103
  }
@@ -98,14 +105,15 @@ export function DropdownMenuRadioItem({
98
105
  if (groupCtx.hasCloseOnSelect) {
99
106
  menuCtx?.closeMenu();
100
107
  }
101
- }, [isDisabled, groupCtx, value, menuCtx]);
108
+ onClick?.(event);
109
+ }, [isDisabled, groupCtx, value, menuCtx, onClick]);
102
110
  const handlePointerMove = useCallback(e => focusMenuItemOnHover(e, isDisabled), [isDisabled]);
103
111
  return /*#__PURE__*/_jsx(Item, {
104
112
  ...rest,
105
113
  role: "menuitemradio",
106
114
  "aria-checked": isChecked,
107
115
  tabIndex: isDisabled ? undefined : -1,
108
- onPointerMove: handlePointerMove,
116
+ onPointerMove: composeEventHandlers(handlePointerMove, onPointerMove),
109
117
  marker:
110
118
  /*#__PURE__*/
111
119
  // No wrapper — see DropdownMenuCheckboxItem: the target belongs on the
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @file inputDisabledFocus.ts
3
+ * @input Uses StyleX and theme tokens
4
+ * @output inputDisabledFocusStyles (internal; not re-exported from Field/index.ts)
5
+ * @position Field wrapper style shared by TextInput and TextArea
6
+ */
7
+ import * as stylex from '@stylexjs/stylex';
8
+ export declare const inputDisabledFocusStyles: Readonly<{
9
+ readonly ring: Readonly<{
10
+ readonly borderColor: stylex.StyleXClassNameFor<"borderColor", "light-dark(#0064E0, #2694FE)" | "light-dark(#CCD3DB, #494D53)">;
11
+ readonly boxShadow: stylex.StyleXClassNameFor<"boxShadow", "none" | `inset 0px 0px 0px 2px ${stylex.StyleXVar<"light-dark(#0082FB33, #0082FB3F)">}`>;
12
+ }>;
13
+ }>;
14
+ //# sourceMappingURL=inputDisabledFocus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inputDisabledFocus.d.ts","sourceRoot":"","sources":["../../src/Field/inputDisabledFocus.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AAEH,OAAO,KAAK,MAAM,MAAM,kBAAkB,CAAC;AAG3C,eAAO,MAAM,wBAAwB;;;;;EAenC,CAAC"}
@@ -0,0 +1,19 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file inputDisabledFocus.ts
5
+ * @input Uses StyleX and theme tokens
6
+ * @output inputDisabledFocusStyles (internal; not re-exported from Field/index.ts)
7
+ * @position Field wrapper style shared by TextInput and TextArea
8
+ */
9
+
10
+ import * as stylex from '@stylexjs/stylex';
11
+ import "../theme/tokens.stylex.js";
12
+ import { colorVars } from "../theme/tokens.stylex.js";
13
+ export const inputDisabledFocusStyles = {
14
+ ring: {
15
+ kVAM5u: "xvy26l8 x16it14f",
16
+ kGVxlE: "x1gnnqk1 xwmge8v",
17
+ $$css: true
18
+ }
19
+ };
@@ -20,6 +20,17 @@
20
20
  * lose, so the tap opens the layer and the next tap outside dismisses it. That
21
21
  * is `auto`; `tap` and `none` state the choice outright, which is what an
22
22
  * icon-button whose only job is to reveal the layer needs.
23
+ *
24
+ * A touch decides nothing until it is a tap. A finger landing on the trigger
25
+ * may be starting a scroll, a pinch or a long press, and only the browser
26
+ * knows which once the finger moves or lifts. Its `click` is that verdict (on
27
+ * iOS it comes from UIKit's own tap recognizer), so the press only records
28
+ * itself and the tap's `click` opens or closes the layer; a press the browser
29
+ * takes for scrolling ends in `pointercancel` and never gets there. That is
30
+ * how a UIKit tap behaves: it recognizes when the finger lifts and fails once
31
+ * the scroll view claims the touch. The tap outside that closes a tap-opened
32
+ * layer counts when the finger lifts too, or when a scroll takes the touch,
33
+ * which is when UIKit dismisses a popover.
23
34
  */
24
35
  import { type RefObject } from 'react';
25
36
  /**
@@ -86,7 +97,9 @@ export interface UseTouchTriggerReturn {
86
97
  /**
87
98
  * Attach to the trigger. Returns true when the press was a touch one and
88
99
  * this hook has dealt with it, meaning the caller's own pointer-down
89
- * behavior must not also run.
100
+ * behavior must not also run. A touch press opens or closes nothing by
101
+ * itself: the hook waits for that press's tap (the `click` the browser
102
+ * fires when the finger lifts without scrolling) on the same element.
90
103
  */
91
104
  handlePointerDown: (event: PointerEvent) => boolean;
92
105
  /** Forget a tap-open. Call from every other close path (Escape, controlled). */
@@ -1 +1 @@
1
- {"version":3,"file":"useTouchTrigger.d.ts","sourceRoot":"","sources":["../../src/Layer/useTouchTrigger.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAiC,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAMrE;;;;;;GAMG;AACH,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,CAAC;AAmCxD;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAoB7D;AAeD,MAAM,WAAW,sBAAsB;IACrC,sDAAsD;IACtD,YAAY,EAAE,iBAAiB,CAAC;IAEhC,oDAAoD;IACpD,SAAS,EAAE,OAAO,CAAC;IAEnB;;;OAGG;IACH,YAAY,EAAE,OAAO,CAAC;IAEtB,2CAA2C;IAC3C,MAAM,EAAE,OAAO,CAAC;IAEhB,0EAA0E;IAC1E,OAAO,EAAE,MAAM,CAAC;IAEhB,oDAAoD;IACpD,UAAU,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAE1C,uDAAuD;IACvD,IAAI,EAAE,MAAM,IAAI,CAAC;IAEjB,mCAAmC;IACnC,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB;AAED,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,iBAAiB,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC;IAEtC;;;;OAIG;IACH,kBAAkB,EAAE,MAAM,OAAO,CAAC;IAElC;;;;OAIG;IACH,kBAAkB,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;IAElD;;;;OAIG;IACH,iBAAiB,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC;IAEpD,gFAAgF;IAChF,YAAY,EAAE,MAAM,IAAI,CAAC;CAC1B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,sBAAsB,GAC9B,qBAAqB,CA+JvB"}
1
+ {"version":3,"file":"useTouchTrigger.d.ts","sourceRoot":"","sources":["../../src/Layer/useTouchTrigger.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EAAiC,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAOrE;;;;;;GAMG;AACH,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,CAAC;AAmCxD;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAoB7D;AAeD,MAAM,WAAW,sBAAsB;IACrC,sDAAsD;IACtD,YAAY,EAAE,iBAAiB,CAAC;IAEhC,oDAAoD;IACpD,SAAS,EAAE,OAAO,CAAC;IAEnB;;;OAGG;IACH,YAAY,EAAE,OAAO,CAAC;IAEtB,2CAA2C;IAC3C,MAAM,EAAE,OAAO,CAAC;IAEhB,0EAA0E;IAC1E,OAAO,EAAE,MAAM,CAAC;IAEhB,oDAAoD;IACpD,UAAU,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAE1C,uDAAuD;IACvD,IAAI,EAAE,MAAM,IAAI,CAAC;IAEjB,mCAAmC;IACnC,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB;AAED,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,iBAAiB,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC;IAEtC;;;;OAIG;IACH,kBAAkB,EAAE,MAAM,OAAO,CAAC;IAElC;;;;OAIG;IACH,kBAAkB,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;IAElD;;;;;;OAMG;IACH,iBAAiB,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC;IAEpD,gFAAgF;IAChF,YAAY,EAAE,MAAM,IAAI,CAAC;CAC1B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,sBAAsB,GAC9B,qBAAqB,CAuMvB"}
@@ -24,8 +24,20 @@
24
24
  * lose, so the tap opens the layer and the next tap outside dismisses it. That
25
25
  * is `auto`; `tap` and `none` state the choice outright, which is what an
26
26
  * icon-button whose only job is to reveal the layer needs.
27
+ *
28
+ * A touch decides nothing until it is a tap. A finger landing on the trigger
29
+ * may be starting a scroll, a pinch or a long press, and only the browser
30
+ * knows which once the finger moves or lifts. Its `click` is that verdict (on
31
+ * iOS it comes from UIKit's own tap recognizer), so the press only records
32
+ * itself and the tap's `click` opens or closes the layer; a press the browser
33
+ * takes for scrolling ends in `pointercancel` and never gets there. That is
34
+ * how a UIKit tap behaves: it recognizes when the finger lifts and fails once
35
+ * the scroll view claims the touch. The tap outside that closes a tap-opened
36
+ * layer counts when the finger lifts too, or when a scroll takes the touch,
37
+ * which is when UIKit dismisses a popover.
27
38
  */
28
39
  import { useCallback, useEffect, useRef } from 'react';
40
+ import { currentGesture } from "./gestureCounter.js";
29
41
  import { getInteractionModality, useInteractionModalityTracking } from "../utils/interactionModality.js";
30
42
 
31
43
  /**
@@ -144,6 +156,9 @@ export function useTouchTrigger(options) {
144
156
  // pointer-leave, and a controlled one is not ours to close.
145
157
  const isTapOpenRef = useRef(false);
146
158
  const outsideListenerRef = useRef(null);
159
+ // A touch press on the trigger still waiting for its tap: calling it stops
160
+ // waiting. The next press, a cancel, the tap itself or unmounting calls it.
161
+ const forgetPendingTapRef = useRef(null);
147
162
  useInteractionModalityTracking();
148
163
  const disarmOutsideDismiss = useCallback(() => {
149
164
  isTapOpenRef.current = false;
@@ -152,22 +167,26 @@ export function useTouchTrigger(options) {
152
167
  return;
153
168
  }
154
169
  outsideListenerRef.current = null;
155
- document.removeEventListener('pointerdown', listener, true);
170
+ document.removeEventListener('pointerup', listener, true);
171
+ document.removeEventListener('pointercancel', listener, true);
156
172
  }, []);
157
173
 
158
174
  // Capture phase, so a trigger that stops propagation cannot strand an open
159
- // layer. The opening tap's own capture phase at the document has already
160
- // passed by the time this runs, so the listener never sees it.
175
+ // layer. It is armed by the opening tap's `click`, after that tap's own
176
+ // `pointerup`, so the listener never sees the tap that opened the layer.
177
+ // A tap outside counts when it lifts (`pointerup`); a scroll or pinch that
178
+ // starts outside counts as soon as the browser takes it (`pointercancel`).
161
179
  const armOutsideDismiss = useCallback(() => {
162
180
  isTapOpenRef.current = true;
163
181
  if (outsideListenerRef.current != null) {
164
182
  return;
165
183
  }
166
- const handleOutsidePointerDown = event => {
184
+ const handleOutsidePointerEnd = event => {
167
185
  const target = event.target;
168
186
  if (target != null) {
169
- // The trigger's own handler owns the toggle.
170
- if (triggerRef.current?.contains(target) === true) {
187
+ // A tap on the trigger is the trigger's to toggle. A scroll that
188
+ // starts there is a scroll outside the layer, and it does close it.
189
+ if (event.type === 'pointerup' && triggerRef.current?.contains(target) === true) {
171
190
  return;
172
191
  }
173
192
  // Hover card content is interactive; a tap inside it is not a dismiss.
@@ -178,10 +197,12 @@ export function useTouchTrigger(options) {
178
197
  disarmOutsideDismiss();
179
198
  hideRef.current();
180
199
  };
181
- outsideListenerRef.current = handleOutsidePointerDown;
182
- document.addEventListener('pointerdown', handleOutsidePointerDown, true);
200
+ outsideListenerRef.current = handleOutsidePointerEnd;
201
+ document.addEventListener('pointerup', handleOutsidePointerEnd, true);
202
+ document.addEventListener('pointercancel', handleOutsidePointerEnd, true);
183
203
  }, [triggerRef, disarmOutsideDismiss]);
184
204
  useEffect(() => disarmOutsideDismiss, [disarmOutsideDismiss]);
205
+ useEffect(() => () => forgetPendingTapRef.current?.(), []);
185
206
  const isTouchInteraction = useCallback(() => isTouchPointerRef.current && getInteractionModality() === 'pointer', []);
186
207
  const handlePointerEnter = useCallback(event => {
187
208
  // Only a finger is hoverless on arrival. A pen in detection range hovers
@@ -190,19 +211,18 @@ export function useTouchTrigger(options) {
190
211
  // presses is a tap, and `handlePointerDown` still reads it as one.
191
212
  isTouchPointerRef.current = event.pointerType === 'touch';
192
213
  }, []);
193
- const handlePointerDown = useCallback(event => {
194
- const isTouch = TOUCH_POINTER_TYPES.has(event.pointerType);
195
- isTouchPointerRef.current = isTouch;
196
- if (!isTouch || isControlled) {
197
- return false;
198
- }
214
+
215
+ // What a tap on the trigger does. It runs from the tap's `click`, after the
216
+ // render that armed it, so it reads the latest props through this ref.
217
+ const answerTapRef = useRef(() => {});
218
+ answerTapRef.current = () => {
199
219
  const trigger = triggerRef.current;
200
220
  const mode = touchTrigger === 'auto' ? trigger != null && isActionTrigger(trigger) ? 'none' : 'tap' : touchTrigger;
201
221
  if (mode === 'none' || !isEnabled) {
202
222
  // A layer left open by an earlier tap must not survive the next one.
203
223
  disarmOutsideDismiss();
204
224
  hide();
205
- return true;
225
+ return;
206
226
  }
207
227
 
208
228
  // A lazily mounted layer reports `isOpen` a tick after `show()`, so the
@@ -210,12 +230,47 @@ export function useTouchTrigger(options) {
210
230
  if (isOpenRef.current || isTapOpenRef.current) {
211
231
  disarmOutsideDismiss();
212
232
  hide();
213
- return true;
233
+ return;
214
234
  }
215
235
  armOutsideDismiss();
216
236
  show();
237
+ };
238
+ const handlePointerDown = useCallback(event => {
239
+ const isTouch = TOUCH_POINTER_TYPES.has(event.pointerType);
240
+ isTouchPointerRef.current = isTouch;
241
+ // A new press replaces one that never became a tap.
242
+ forgetPendingTapRef.current?.();
243
+ if (!isTouch || isControlled) {
244
+ return false;
245
+ }
246
+ const trigger = event.currentTarget instanceof Element ? event.currentTarget : triggerRef.current;
247
+ if (trigger == null) {
248
+ return true;
249
+ }
250
+
251
+ // The tap must belong to this press. A key press or a new pointer press
252
+ // advances the gesture, so a keyboard activation that later fires
253
+ // `click` on the trigger (Enter on a button) is not mistaken for it.
254
+ const gesture = currentGesture();
255
+ const forget = () => {
256
+ trigger.removeEventListener('click', handleTap);
257
+ trigger.removeEventListener('pointercancel', forget);
258
+ if (forgetPendingTapRef.current === forget) {
259
+ forgetPendingTapRef.current = null;
260
+ }
261
+ };
262
+ const handleTap = () => {
263
+ forget();
264
+ if (currentGesture() === gesture) {
265
+ answerTapRef.current();
266
+ }
267
+ };
268
+ trigger.addEventListener('click', handleTap);
269
+ // The browser took the touch for a scroll or a pinch: no tap is coming.
270
+ trigger.addEventListener('pointercancel', forget);
271
+ forgetPendingTapRef.current = forget;
217
272
  return true;
218
- }, [touchTrigger, isEnabled, isControlled, triggerRef, show, hide, armOutsideDismiss, disarmOutsideDismiss]);
273
+ }, [isControlled, triggerRef]);
219
274
 
220
275
  // A layer closed by any other path (Escape, a consumer, the browser) leaves
221
276
  // no tap-open to dismiss. Only a true open→closed transition counts: a
@@ -67,7 +67,7 @@ interface NumberInputPropsBase extends Omit<FieldInputBaseProps, 'onChange' | 'd
67
67
  * Whether the input is read-only.
68
68
  * The value is shown at full opacity and still submits with the form, but
69
69
  * cannot be edited. Unlike `isDisabled`, a read-only input is not dimmed and
70
- * stays in the tab order — use it for a value the user should see and send
70
+ * keeps its form value — use it for a value the user should see and send
71
71
  * but not change. Stepping is off in every form while read-only: arrow keys,
72
72
  * the wheel, and the number steppers. `isDisabled` takes precedence when both
73
73
  * are set.
@@ -81,8 +81,8 @@ interface NumberInputPropsBase extends Omit<FieldInputBaseProps, 'onChange' | 'd
81
81
  * keyboard and assistive technology. The field cannot be edited (it becomes
82
82
  * read-only) while disabled.
83
83
  *
84
- * Use this instead of wrapping a disabled input in `Tooltip` — disabled
85
- * controls don't emit the pointer events an external tooltip needs.
84
+ * Use this instead of wrapping a disabled input in `Tooltip`, so the reason
85
+ * is attached to the field itself.
86
86
  *
87
87
  * @example
88
88
  * ```
@@ -1 +1 @@
1
- {"version":3,"file":"NumberInput.d.ts","sourceRoot":"","sources":["../../src/NumberInput/NumberInput.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAML,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,MAAM,MAAM,kBAAkB,CAAC;AAS3C,OAAO,EAGL,KAAK,WAAW,EAKhB,KAAK,kBAAkB,EACxB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAuB,KAAK,QAAQ,EAAC,MAAM,SAAS,CAAC;AA8I5D,QAAA,MAAM,UAAU;;;;;;;;;;EAUd,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,MAAM,OAAO,UAAU,CAAC;AAMtD,YAAY,EACV,WAAW,IAAI,iBAAiB,EAChC,eAAe,IAAI,qBAAqB,GACzC,MAAM,UAAU,CAAC;AAGlB,OAAO,EACL,KAAK,mBAAmB,EAEzB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,gBAAgB,CAAC;AAM9C,UAAU,oBAAqB,SAAQ,IAAI,CACzC,mBAAmB,EACnB,UAAU,GAAG,cAAc,CAC5B;IACC,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAClC;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;;;;;OASG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,SAAS,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IACjC;;OAEG;IACH,SAAS,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IACjC;;;;OAIG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,kBAAkB,CAAC;IACnC;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,eAAe,CAAC;IAEvB;;;OAGG;IACH,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACjC;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB;;;OAGG;IACH,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACxC;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,CAAC,gBAAgB,CAAC,KAAK,IAAI,CAAC;IACpD;;OAEG;IACH,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,CAAC,gBAAgB,CAAC,KAAK,IAAI,CAAC;IACnD;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB;;OAEG;IACH,SAAS,CAAC,EAAE,CAAC,CAAC,EAAE,aAAa,CAAC,gBAAgB,CAAC,KAAK,IAAI,CAAC;CAC1D;AAED;;;GAGG;AACH,KAAK,4BAA4B,GAAG,oBAAoB,GAAG;IACzD,QAAQ,CAAC,EAAE,KAAK,CAAC;IACjB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC,CAAC;AAEF,KAAK,yBAAyB,GAAG,oBAAoB,GAAG;IACtD;;;;;;OAMG;IACH,QAAQ,EAAE,IAAI,CAAC;IACf,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;CAC1C,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAC1B,4BAA4B,GAAG,yBAAyB,CAAC;AAsF3D;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,aAAqB,EACrB,WAAW,EACX,UAAkB,EAClB,UAAkB,EAClB,UAAkB,EAClB,UAAkB,EAClB,eAAe,EACf,SAAS,EACT,SAAS,EACT,MAAM,EACN,aAA0B,EAC1B,IAAI,EAAE,QAAQ,EACd,QAAQ,EACR,KAAK,EACL,WAAW,EACX,YAAY,EACZ,YAAoB,EACpB,QAAQ,EACR,YAAY,EACZ,GAAG,EACH,GAAG,EACH,IAAI,EACJ,WAAW,EACX,cAAqB,EACrB,iBAAyB,EACzB,KAAK,EACL,aAAqB,EACrB,OAAO,EACP,MAAM,EACN,QAAQ,EACR,OAAO,EACP,SAAS,EACT,KAAK,EACL,eAAe,EACf,kBAAkB,EAClB,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,GAAG,EACH,EAAE,EAAE,MAAM,EACV,kBAAkB,EAAE,mBAAmB,EACvC,GAAG,SAAS,EACb,EAAE,gBAAgB,+BAqflB;yBAliBe,WAAW"}
1
+ {"version":3,"file":"NumberInput.d.ts","sourceRoot":"","sources":["../../src/NumberInput/NumberInput.tsx"],"names":[],"mappings":"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"}
@@ -28,6 +28,7 @@ import { useTooltip } from "../Tooltip/index.js";
28
28
  import { getInputARIA } from "../utils/index.js";
29
29
  import { interactionOverlayStyles } from "../utils/interactionOverlay.stylex.js";
30
30
  import { usePressFeedback } from "../hooks/usePressFeedback.js";
31
+ import { inputDisabledFocusStyles } from "../Field/inputDisabledFocus.js";
31
32
  import { useSize } from "../SizeContext/SizeContext.js";
32
33
  import { useInputContainer } from "../hooks/useInputContainer.js";
33
34
  import { useInputStatusIcon } from "../hooks/useInputStatusIcon.js";
@@ -275,12 +276,14 @@ export function NumberInput({
275
276
  const [pendingInput, setPendingInput] = useState(null);
276
277
  const [isFocused, setIsFocused] = useState(false);
277
278
 
278
- // Disabled-reason tooltip. Disabled controls swallow pointer events, so the
279
- // tooltip listeners attach to the input container (which already exists) and
279
+ // Disabled-reason tooltip. The tooltip listeners attach to the input container (which already exists) and
280
280
  // the input stays perceivable via aria-disabled instead of the native
281
281
  // disabled attribute. The field is made read-only so it can't be typed into,
282
282
  // and value mutation is blocked by the isDisabled guard in the handlers.
283
283
  const showsDisabledMessage = isDisabled && !!disabledMessage;
284
+ // A disabled field keeps its tab stop but, like a natively disabled one,
285
+ // runs no caller event handler other than focus and blur (`spec:AST-085` FR2).
286
+ const fieldProps = isDisabled ? Object.fromEntries(Object.entries(rest).filter(([key]) => !/^on[A-Z]/.test(key) || /^on(Focus|Blur)/.test(key))) : rest;
284
287
  const disabledMessageTooltip = useTooltip({
285
288
  placement: 'above',
286
289
  // The container div is not naturally focusable; focusin bubbles up from
@@ -359,6 +362,12 @@ export function NumberInput({
359
362
  if (pendingInput === null) {
360
363
  return;
361
364
  }
365
+ if (isDisabled) {
366
+ // A field disabled mid-entry commits nothing; its draft is dropped, as
367
+ // a natively disabled field's was (spec:AST-085 FR2).
368
+ setPendingInput(null);
369
+ return;
370
+ }
362
371
  const decision = resolveNumberInputCommit(pendingInput, {
363
372
  min,
364
373
  max,
@@ -376,7 +385,7 @@ export function NumberInput({
376
385
  } else if (decision.type === 'commit' && decision.value !== value) {
377
386
  onChange(decision.value);
378
387
  }
379
- }, [hasClear, isIntegerOnly, locale, max, min, onChange, pendingInput, value]);
388
+ }, [hasClear, isDisabled, isIntegerOnly, locale, max, min, onChange, pendingInput, value]);
380
389
 
381
390
  // Blur ends the edit and displays the resulting committed value.
382
391
  const handleBlur = useCallback(e => {
@@ -419,6 +428,13 @@ export function NumberInput({
419
428
 
420
429
  // Handle keyboard events
421
430
  const handleKeyDown = useCallback(e => {
431
+ if (isDisabled) {
432
+ // A read-only field still submits its form on Enter.
433
+ if (e.key === 'Enter') {
434
+ e.preventDefault();
435
+ }
436
+ return;
437
+ }
422
438
  // The field is type="text" for formatted display, so an IME can compose
423
439
  // into it: Enter commits the candidate and the arrows walk the candidate
424
440
  // window. The composing keydown fires before compositionend, so without
@@ -442,7 +458,7 @@ export function NumberInput({
442
458
  onEnter?.();
443
459
  }
444
460
  onKeyDown?.(e);
445
- }, [commitPendingInput, onEnter, onKeyDown, stepValue]);
461
+ }, [commitPendingInput, isDisabled, onEnter, onKeyDown, stepValue]);
446
462
 
447
463
  // React's delegated wheel listener can be passive, so use a native,
448
464
  // explicitly non-passive listener to prevent page scrolling only when this
@@ -479,12 +495,13 @@ export function NumberInput({
479
495
  if (!e || e.detail === 0) {
480
496
  inputRef.current?.focus();
481
497
  } else {
482
- // Defer focus restoration past the button's unmount task so iOS Safari
483
- // and touch browsers don't jump the page scroll to 0 on tap.
484
- requestAnimationFrame(() => {
485
- inputRef.current?.focus({
486
- preventScroll: true
487
- });
498
+ // Focus inside the tap: iOS Safari brings up its keyboard only for a
499
+ // focus made while handling the touch, so a field the tap reaches
500
+ // unfocused gets its keyboard, as UIKit's clear button makes the
501
+ // field first responder. `preventScroll` keeps a scrolled page where
502
+ // it is.
503
+ inputRef.current?.focus({
504
+ preventScroll: true
488
505
  });
489
506
  }
490
507
  }, [hasClear, onChange]);
@@ -513,7 +530,7 @@ export function NumberInput({
513
530
  status: status?.type ?? null,
514
531
  disabled: isDisabled ? 'disabled' : null,
515
532
  readonly: isReadOnly ? 'readonly' : null
516
- }), stylex.props(inputWrapperStyles.base, styles.wrapper, hasNumberSteppers && styles.wrapperWithNumberSteppers, sizeStyles[size], isDisabled && inputWrapperStyles.disabled, status && inputStatusBorderStyles[status.type], status && !isDisabled && inputStatusHoverShadowStyles[status.type], status && inputStatusFocusWithinStyles[status.type], inputGroup && groupStyles.inGroup, controlXstyle), controlClassName, controlStyle),
533
+ }), stylex.props(inputWrapperStyles.base, styles.wrapper, hasNumberSteppers && styles.wrapperWithNumberSteppers, sizeStyles[size], isDisabled && inputWrapperStyles.disabled, isDisabled && inputDisabledFocusStyles.ring, status && inputStatusBorderStyles[status.type], status && !isDisabled && inputStatusHoverShadowStyles[status.type], status && inputStatusFocusWithinStyles[status.type], inputGroup && groupStyles.inGroup, controlXstyle), controlClassName, controlStyle),
517
534
  children: [startIcon && renderIconSlot(startIcon, {
518
535
  size: 'sm',
519
536
  color: 'secondary'
@@ -521,7 +538,7 @@ export function NumberInput({
521
538
  id: inputLabelID,
522
539
  children: label
523
540
  }), /*#__PURE__*/_jsx("input", {
524
- ...rest,
541
+ ...fieldProps,
525
542
  ref: mergedInputRef,
526
543
  id: id,
527
544
  name: isDisabled || formatValue ? undefined : htmlName,
@@ -535,13 +552,12 @@ export function NumberInput({
535
552
  onBlur: handleBlur,
536
553
  onKeyDown: handleKeyDown,
537
554
  placeholder: placeholder
538
- // With a disabledMessage the input keeps focusability via aria-disabled
539
- // so the reason is focus-discoverable; readOnly + the handler guards
540
- // keep the value from changing.
555
+ // A disabled input keeps its place in the tab order (spec:AST-085):
556
+ // aria-disabled instead of the native attribute, with readOnly + the
557
+ // handler guards keeping the value from changing.
541
558
  ,
542
- disabled: isDisabled && !showsDisabledMessage,
543
- "aria-disabled": showsDisabledMessage ? 'true' : undefined,
544
- readOnly: isReadOnly || showsDisabledMessage || undefined,
559
+ "aria-disabled": isDisabled ? 'true' : undefined,
560
+ readOnly: isReadOnly || isDisabled || undefined,
545
561
  autoFocus: hasAutoFocus,
546
562
  "data-autofocus": hasAutoFocus || undefined,
547
563
  "aria-valuemin": min ?? undefined,
@@ -597,12 +613,17 @@ export function NumberInput({
597
613
  type: "button",
598
614
  tabIndex: -1,
599
615
  ...pressable,
600
- disabled: isDisabled || isReadOnly || !canIncrement,
616
+ "aria-disabled": isDisabled || isReadOnly || !canIncrement || undefined,
601
617
  "aria-label": t('@astryx.numberInput.incrementLabel', {
602
618
  label
603
619
  }),
604
620
  onPointerDown: event => event.preventDefault(),
605
621
  onClick: () => {
622
+ // An aria-disabled stepper does nothing, as a natively disabled
623
+ // one did (spec:AST-085 FR2).
624
+ if (isDisabled || isReadOnly || !canIncrement) {
625
+ return;
626
+ }
606
627
  inputRef.current?.focus();
607
628
  stepValue(1);
608
629
  },
@@ -617,12 +638,17 @@ export function NumberInput({
617
638
  type: "button",
618
639
  tabIndex: -1,
619
640
  ...pressable,
620
- disabled: isDisabled || isReadOnly || !canDecrement,
641
+ "aria-disabled": isDisabled || isReadOnly || !canDecrement || undefined,
621
642
  "aria-label": t('@astryx.numberInput.decrementLabel', {
622
643
  label
623
644
  }),
624
645
  onPointerDown: event => event.preventDefault(),
625
646
  onClick: () => {
647
+ // An aria-disabled stepper does nothing, as a natively disabled
648
+ // one did (spec:AST-085 FR2).
649
+ if (isDisabled || isReadOnly || !canDecrement) {
650
+ return;
651
+ }
626
652
  inputRef.current?.focus();
627
653
  stepValue(-1);
628
654
  },
@@ -1 +1 @@
1
- {"version":3,"file":"SideNavHeading.d.ts","sourceRoot":"","sources":["../../src/SideNav/SideNavHeading.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAkB,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAsBtD,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,eAAe,CAAC;AAGrD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AA2N5C,MAAM,WAAW,mBAAoB,SAAQ,SAAS,CAAC,cAAc,CAAC;IACpE,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC;;OAEG;IACH,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB;;;;OAIG;IACH,EAAE,CAAC,EAAE,iBAAiB,CAAC;IACvB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,SAAS,CAAC;IAC7B;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,SAAS,CAAC;CAClB;AAQD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,cAAc,CAAC,EAC7B,EAAE,EACF,IAAI,EACJ,OAAO,EACP,WAAW,EACX,YAAY,EACZ,gBAAgB,EAChB,UAAU,EACV,cAAc,EACd,gBAAgB,EAChB,IAAI,EACJ,MAAM,EACN,SAAS,EACT,KAAK,EACL,aAAa,EAAE,MAAM,EACrB,GAAG,EACH,GAAG,KAAK,EACT,EAAE,mBAAmB,sCAkgBrB;yBAnhBe,cAAc"}
1
+ {"version":3,"file":"SideNavHeading.d.ts","sourceRoot":"","sources":["../../src/SideNav/SideNavHeading.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAkB,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAsBtD,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,eAAe,CAAC;AAGrD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAsO5C,MAAM,WAAW,mBAAoB,SAAQ,SAAS,CAAC,cAAc,CAAC;IACpE,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC;;OAEG;IACH,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB;;;;OAIG;IACH,EAAE,CAAC,EAAE,iBAAiB,CAAC;IACvB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,SAAS,CAAC;IAC7B;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,SAAS,CAAC;CAClB;AAQD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,cAAc,CAAC,EAC7B,EAAE,EACF,IAAI,EACJ,OAAO,EACP,WAAW,EACX,YAAY,EACZ,gBAAgB,EAChB,UAAU,EACV,cAAc,EACd,gBAAgB,EAChB,IAAI,EACJ,MAAM,EACN,SAAS,EACT,KAAK,EACL,aAAa,EAAE,MAAM,EACrB,GAAG,EACH,GAAG,KAAK,EACT,EAAE,mBAAmB,sCAgiBrB;yBAjjBe,cAAc"}
@@ -129,6 +129,17 @@ const styles = {
129
129
  kGuDYH: "x1qlqyl8",
130
130
  $$css: true
131
131
  },
132
+ linkBesideEndContent: {
133
+ k1xSpc: "x78zum5",
134
+ kGNEyG: "x6s0dn4",
135
+ kSGwAc: "xkh2ocl",
136
+ kOIVth: "x1txdalj",
137
+ kzQI83: "x1iyjqo2",
138
+ k7Eaqz: "xeuugli",
139
+ kybGjl: "x1hl2dhg",
140
+ kMwMTN: "x1heor9g",
141
+ $$css: true
142
+ },
132
143
  popoverHeading: {
133
144
  k1xSpc: "x78zum5",
134
145
  kGNEyG: "x6s0dn4",
@@ -484,6 +495,29 @@ export function SideNavHeading({
484
495
  }))]
485
496
  });
486
497
 
498
+ // Whole heading is a link (no menu, single headingHref). End content may
499
+ // be a control, which must not sit inside the link (no interactive content
500
+ // in an <a>), so with end content the row is a div holding the link and the
501
+ // end content side by side, as the mixed mode does.
502
+ if (isWholeHeadingLink && headingHref && headerEndContentElement) {
503
+ return /*#__PURE__*/_jsxs("div", {
504
+ ref: ref,
505
+ "data-testid": testId,
506
+ ...mergeProps(themeProps('side-nav-heading'), stylex.props(styles.root, xstyle), className, style),
507
+ children: [/*#__PURE__*/_jsxs(LinkComponent, {
508
+ href: headingHref,
509
+ ...focusOutlineProps.focusVisible(styles.linkBesideEndContent, styles.menuTrigger),
510
+ ...props,
511
+ children: [icon && /*#__PURE__*/_jsx("span", {
512
+ ...{
513
+ className: "x2lah0s x78zum5 x6s0dn4 xl56j7k"
514
+ },
515
+ children: icon
516
+ }), renderTextContent()]
517
+ }), headerEndContentElement]
518
+ });
519
+ }
520
+
487
521
  // Whole heading is a link (no menu, single headingHref)
488
522
  if (isWholeHeadingLink && headingHref) {
489
523
  return /*#__PURE__*/_jsxs(LinkComponent, {
@@ -1 +1 @@
1
- {"version":3,"file":"Skeleton.d.ts","sourceRoot":"","sources":["../../src/Skeleton/Skeleton.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,MAAM,MAAM,kBAAkB,CAAC;AAoE3C,QAAA,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;EAuBhB,CAAC;AAgBH,MAAM,MAAM,cAAc,GAAG,MAAM,OAAO,YAAY,CAAC;AAMvD,MAAM,WAAW,aAAc,SAAQ,SAAS,CAAC,cAAc,CAAC;IAC9D,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB;;;;;;;;;;OAUG;IACH,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,QAAQ,CAAC,EACvB,KAAc,EACd,MAAe,EACf,MAAM,EAAE,UAAc,EACtB,KAAS,EACT,aAAa,EAAE,MAAM,EACrB,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,EACH,GAAG,KAAK,EACT,EAAE,aAAa,+BA0Bf;yBArCe,QAAQ"}
1
+ {"version":3,"file":"Skeleton.d.ts","sourceRoot":"","sources":["../../src/Skeleton/Skeleton.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,MAAM,MAAM,kBAAkB,CAAC;AAgG3C,QAAA,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;EAuBhB,CAAC;AAgBH,MAAM,MAAM,cAAc,GAAG,MAAM,OAAO,YAAY,CAAC;AAMvD,MAAM,WAAW,aAAc,SAAQ,SAAS,CAAC,cAAc,CAAC;IAC9D,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB;;;;;;;;;;OAUG;IACH,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,QAAQ,CAAC,EACvB,KAAc,EACd,MAAe,EACf,MAAM,EAAE,UAAc,EACtB,KAAS,EACT,aAAa,EAAE,MAAM,EACrB,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,EACH,GAAG,KAAK,EACT,EAAE,aAAa,+BA0Bf;yBArCe,QAAQ"}