@phreshos/react-ui 0.1.36 → 0.1.38

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 (202) hide show
  1. package/README.md +147 -167
  2. package/dist/accordion.d.ts +7 -6
  3. package/dist/accordion.js +5 -5
  4. package/dist/alert-dialog.d.ts +2 -2
  5. package/dist/alert-dialog.js +3 -1
  6. package/dist/alert.d.ts +17 -0
  7. package/dist/alert.js +30 -0
  8. package/dist/app-layout.d.ts +13 -0
  9. package/dist/app-layout.js +84 -0
  10. package/dist/button.d.ts +31 -27
  11. package/dist/button.js +48 -40
  12. package/dist/calendar-layout.d.ts +9 -5
  13. package/dist/calendar-layout.js +67 -78
  14. package/dist/calendar-trigger.d.ts +4 -4
  15. package/dist/calendar-trigger.js +7 -29
  16. package/dist/calendar.d.ts +2 -2
  17. package/dist/calendar.js +6 -5
  18. package/dist/checkbox.d.ts +4 -4
  19. package/dist/checkbox.js +16 -6
  20. package/dist/color-area.d.ts +20 -0
  21. package/dist/color-area.js +30 -0
  22. package/dist/color-field.d.ts +17 -0
  23. package/dist/color-field.js +17 -0
  24. package/dist/color-picker.d.ts +24 -0
  25. package/dist/color-picker.js +30 -0
  26. package/dist/color-slider.d.ts +19 -0
  27. package/dist/color-slider.js +29 -0
  28. package/dist/color-swatch-picker.d.ts +23 -0
  29. package/dist/color-swatch-picker.js +36 -0
  30. package/dist/color-swatch.d.ts +18 -0
  31. package/dist/color-swatch.js +20 -0
  32. package/dist/combo-box.d.ts +18 -10
  33. package/dist/combo-box.js +43 -58
  34. package/dist/context-menu.d.ts +14 -10
  35. package/dist/context-menu.js +7 -6
  36. package/dist/control/color-thumb.d.ts +13 -0
  37. package/dist/control/color-thumb.js +33 -0
  38. package/dist/control/color-value.d.ts +6 -0
  39. package/dist/control/color-value.js +7 -0
  40. package/dist/control/control.d.ts +60 -0
  41. package/dist/control/control.js +71 -0
  42. package/dist/control/field-button.d.ts +13 -0
  43. package/dist/control/field-button.js +28 -0
  44. package/dist/control/field.d.ts +20 -0
  45. package/dist/control/field.js +34 -0
  46. package/dist/control/icon.d.ts +11 -0
  47. package/dist/control/icon.js +8 -0
  48. package/dist/control/item.d.ts +37 -0
  49. package/dist/control/item.js +57 -0
  50. package/dist/control/open-state.d.ts +25 -0
  51. package/dist/control/open-state.js +8 -0
  52. package/dist/control/selection-track.d.ts +39 -0
  53. package/dist/control/selection-track.js +122 -0
  54. package/dist/control/selection.d.ts +44 -0
  55. package/dist/{selection.js → control/selection.js} +16 -0
  56. package/dist/control/surface-render.d.ts +10 -0
  57. package/dist/control/surface-render.js +12 -0
  58. package/dist/control/text-control.d.ts +32 -0
  59. package/dist/control/text-control.js +31 -0
  60. package/dist/control/toggle.d.ts +32 -0
  61. package/dist/control/toggle.js +80 -0
  62. package/dist/date-control.d.ts +9 -9
  63. package/dist/date-control.js +31 -34
  64. package/dist/date-field.d.ts +7 -6
  65. package/dist/date-field.js +8 -6
  66. package/dist/date-picker.d.ts +13 -6
  67. package/dist/date-picker.js +9 -6
  68. package/dist/date-range-picker.d.ts +15 -6
  69. package/dist/date-range-picker.js +12 -9
  70. package/dist/dialog.d.ts +29 -15
  71. package/dist/dialog.js +37 -28
  72. package/dist/disclosure.d.ts +8 -7
  73. package/dist/disclosure.js +31 -52
  74. package/dist/drop-zone.d.ts +18 -0
  75. package/dist/drop-zone.js +32 -0
  76. package/dist/dropdown-menu.d.ts +11 -8
  77. package/dist/dropdown-menu.js +5 -4
  78. package/dist/fieldset.d.ts +13 -0
  79. package/dist/fieldset.js +26 -0
  80. package/dist/file-trigger.d.ts +13 -0
  81. package/dist/file-trigger.js +6 -0
  82. package/dist/flex.d.ts +1 -1
  83. package/dist/flex.js +5 -4
  84. package/dist/foundation/appearance.d.ts +83 -0
  85. package/dist/foundation/appearance.js +38 -0
  86. package/dist/{aria-direction.js → foundation/aria-direction.js} +3 -1
  87. package/dist/foundation/color.d.ts +61 -0
  88. package/dist/foundation/color.js +247 -0
  89. package/dist/{layout.d.ts → foundation/layout.d.ts} +6 -4
  90. package/dist/{layout.js → foundation/layout.js} +7 -5
  91. package/dist/foundation/motion-style.d.ts +9 -0
  92. package/dist/foundation/motion-style.js +50 -0
  93. package/dist/foundation/preferences.d.ts +15 -0
  94. package/dist/{ui-provider.d.ts → foundation/provider.d.ts} +9 -5
  95. package/dist/{ui-provider.js → foundation/provider.js} +18 -27
  96. package/dist/{radius.d.ts → foundation/radius.d.ts} +6 -3
  97. package/dist/foundation/radius.js +11 -0
  98. package/dist/foundation/spacing.d.ts +11 -0
  99. package/dist/foundation/spacing.js +4 -0
  100. package/dist/foundation/visual.d.ts +42 -0
  101. package/dist/{appearance-context.js → foundation/visual.js} +41 -27
  102. package/dist/grid.d.ts +1 -1
  103. package/dist/grid.js +5 -4
  104. package/dist/icons/main.d.ts +5 -3
  105. package/dist/icons/main.js +5 -3
  106. package/dist/input.d.ts +2 -2
  107. package/dist/input.js +9 -7
  108. package/dist/list-box.d.ts +24 -19
  109. package/dist/list-box.js +52 -109
  110. package/dist/main.d.ts +28 -17
  111. package/dist/main.js +22 -9
  112. package/dist/menu.d.ts +28 -32
  113. package/dist/menu.js +47 -93
  114. package/dist/number-field.d.ts +17 -0
  115. package/dist/number-field.js +28 -0
  116. package/dist/panel.d.ts +10 -5
  117. package/dist/panel.js +33 -12
  118. package/dist/popover.d.ts +26 -12
  119. package/dist/popover.js +22 -15
  120. package/dist/progress-bar.d.ts +8 -10
  121. package/dist/progress-bar.js +18 -34
  122. package/dist/radio.d.ts +15 -12
  123. package/dist/radio.js +27 -37
  124. package/dist/range-calendar.d.ts +5 -3
  125. package/dist/range-calendar.js +8 -7
  126. package/dist/scroll-area.d.ts +2 -2
  127. package/dist/scroll-area.js +10 -9
  128. package/dist/segmented-control.d.ts +23 -0
  129. package/dist/segmented-control.js +38 -0
  130. package/dist/select.d.ts +40 -13
  131. package/dist/select.js +63 -18
  132. package/dist/slider.d.ts +7 -3
  133. package/dist/slider.js +51 -50
  134. package/dist/spinner.d.ts +5 -8
  135. package/dist/spinner.js +12 -27
  136. package/dist/surface/grain.d.ts +12 -0
  137. package/dist/surface/grain.js +65 -0
  138. package/dist/{material-options.d.ts → surface/material-options.d.ts} +1 -2
  139. package/dist/{material-options.js → surface/material-options.js} +2 -2
  140. package/dist/surface/paint-class.d.ts +13 -0
  141. package/dist/surface/paint-class.js +47 -0
  142. package/dist/{shadow-options.d.ts → surface/shadow-options.d.ts} +13 -1
  143. package/dist/{shadow-options.js → surface/shadow-options.js} +17 -2
  144. package/dist/surface/surface.d.ts +80 -0
  145. package/dist/surface/surface.js +239 -0
  146. package/dist/switch.d.ts +4 -5
  147. package/dist/switch.js +17 -8
  148. package/dist/table.d.ts +29 -32
  149. package/dist/table.js +68 -106
  150. package/dist/tabs.d.ts +16 -15
  151. package/dist/tabs.js +44 -86
  152. package/dist/textarea.d.ts +2 -2
  153. package/dist/textarea.js +10 -14
  154. package/dist/time-field.d.ts +6 -6
  155. package/dist/time-field.js +8 -6
  156. package/dist/toolbar.d.ts +6 -4
  157. package/dist/toolbar.js +16 -15
  158. package/dist/tooltip.d.ts +17 -8
  159. package/dist/tooltip.js +17 -15
  160. package/dist/tree.d.ts +22 -27
  161. package/dist/tree.js +54 -99
  162. package/dist/window.d.ts +4 -4
  163. package/dist/window.js +18 -20
  164. package/package.json +3 -3
  165. package/dist/appearance-context.d.ts +0 -54
  166. package/dist/appearance.d.ts +0 -11
  167. package/dist/appearance.js +0 -11
  168. package/dist/color.d.ts +0 -69
  169. package/dist/color.js +0 -192
  170. package/dist/control-surface.d.ts +0 -20
  171. package/dist/control-surface.js +0 -11
  172. package/dist/control.d.ts +0 -70
  173. package/dist/control.js +0 -105
  174. package/dist/field-style.d.ts +0 -3
  175. package/dist/field-style.js +0 -12
  176. package/dist/material-paint.d.ts +0 -13
  177. package/dist/material-paint.js +0 -119
  178. package/dist/motion-style.d.ts +0 -13
  179. package/dist/motion-style.js +0 -75
  180. package/dist/preferences.d.ts +0 -12
  181. package/dist/radius.js +0 -11
  182. package/dist/readiness.d.ts +0 -37
  183. package/dist/readiness.js +0 -134
  184. package/dist/selection.d.ts +0 -18
  185. package/dist/spacing.d.ts +0 -8
  186. package/dist/spacing.js +0 -8
  187. package/dist/surface-edge.d.ts +0 -11
  188. package/dist/surface-edge.js +0 -55
  189. package/dist/surface.d.ts +0 -27
  190. package/dist/surface.js +0 -92
  191. package/dist/text-control.d.ts +0 -10
  192. package/dist/text-control.js +0 -0
  193. package/dist/toggle-indicator.d.ts +0 -18
  194. package/dist/toggle-indicator.js +0 -49
  195. /package/dist/{aria-direction.d.ts → foundation/aria-direction.d.ts} +0 -0
  196. /package/dist/{direction.d.ts → foundation/direction.d.ts} +0 -0
  197. /package/dist/{direction.js → foundation/direction.js} +0 -0
  198. /package/dist/{overlay-placement.d.ts → foundation/overlay-placement.d.ts} +0 -0
  199. /package/dist/{overlay-placement.js → foundation/overlay-placement.js} +0 -0
  200. /package/dist/{preferences.js → foundation/preferences.js} +0 -0
  201. /package/dist/{scale.d.ts → foundation/scale.d.ts} +0 -0
  202. /package/dist/{scale.js → foundation/scale.js} +0 -0
package/dist/dialog.js CHANGED
@@ -1,32 +1,36 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { forwardRef } from "react";
3
3
  import { Dialog as AriaDialog, DialogTrigger as AriaDialogTrigger, Heading as AriaHeading, Modal as AriaModal, ModalOverlay as AriaModalOverlay, Text as AriaText } from "react-aria-components";
4
- import { useAppearance } from "./ui-provider.js";
5
- import { useResolvedAppearance } from "./appearance-context.js";
6
- import { colorOpacity, orderColors, resolveColor } from "./color.js";
7
- import { controlFontSizes, controlFontWeight, controlOpacity } from "./control.js";
8
4
  import { Button } from "./button.js";
9
- import MotionStyle, { backdropMotionClass, overlayMotionClass, overlayTransition } from "./motion-style.js";
10
- import { scale } from "./scale.js";
11
- import { Surface } from "./surface.js";
12
- import { resolveDirection, useDirection } from "./direction.js";
13
- export function DialogRoot(properties) {
14
- return _jsx(AriaDialogTrigger, { ...properties });
5
+ import { controlFontSizes, controlFontWeight, controlOpacity } from "./control/control.js";
6
+ import { ariaOpenState } from "./control/open-state.js";
7
+ import { colorOpacity, darkCanvas } from "./foundation/color.js";
8
+ import { resolveDirection, useDirection } from "./foundation/direction.js";
9
+ import MotionStyle, { backdropMotionClass, overlayMotionClass, overlayTransition } from "./foundation/motion-style.js";
10
+ import { scale } from "./foundation/scale.js";
11
+ import { useVisual } from "./foundation/visual.js";
12
+ import { floatingShadow } from "./surface/shadow-options.js";
13
+ import { Surface } from "./surface/surface.js";
14
+ export function DialogRoot({ children, ...state }) {
15
+ return _jsx(AriaDialogTrigger, { ...ariaOpenState(state), children: children });
15
16
  }
16
17
  export const DialogTrigger = forwardRef(function DialogTrigger(properties, ref) {
17
18
  return _jsx(Button, { ...properties, ref: ref });
18
19
  });
19
- export const DialogBackdrop = forwardRef(function DialogBackdrop({ className, color, style, ...properties }, ref) {
20
- const resolved = useResolvedAppearance();
21
- const inset = scale(resolved.appearance.spacing, "medium");
22
- const colors = resolved.colors;
23
- const base = color === undefined
24
- ? orderColors(colors.background, colors.foreground).darker
25
- : resolveColor(color, colors);
26
- const backdrop = colorOpacity(base, 0.32);
27
- const transition = overlayTransition(resolved.transaction, resolved.preferences.animations);
28
- const direction = resolveDirection(properties.dir, useDirection());
29
- return _jsxs(_Fragment, { children: [_jsx(MotionStyle, {}), _jsx(AriaModalOverlay, { ...properties, ref: ref, dir: direction, className: [backdropMotionClass, className].filter(Boolean).join(" "), style: {
20
+ // A blurred backdrop still dims a little, so the Dialog stays distinct on a
21
+ // light canvas where blur alone barely separates it.
22
+ const backdropDimming = { dim: 0.32, blur: 0.16 };
23
+ export const DialogBackdrop = forwardRef(function DialogBackdrop({ children, className, dir, dismissable = false, keyboardDismissable = true, style, variant = "dim", ...attributes }, ref) {
24
+ const visual = useVisual();
25
+ const inset = scale(visual.spacing, "medium");
26
+ const colors = visual.colors;
27
+ // The backdrop dims toward whichever Appearance color is darker, in any Theme.
28
+ const darker = darkCanvas(colors) ? colors.background : colors.foreground;
29
+ const backdrop = colorOpacity(darker, backdropDimming[variant]);
30
+ const blur = variant === "blur" && visual.material.backdrop > 0 ? `blur(${visual.material.backdrop}px)` : undefined;
31
+ const transition = overlayTransition(visual);
32
+ const direction = resolveDirection(dir, useDirection());
33
+ return _jsxs(_Fragment, { children: [_jsx(MotionStyle, {}), _jsx(AriaModalOverlay, { ...attributes, isDismissable: dismissable, isKeyboardDismissDisabled: !keyboardDismissable, ref: ref, dir: direction, className: [backdropMotionClass, className].filter(Boolean).join(" "), style: {
30
34
  ...transition,
31
35
  position: "fixed",
32
36
  inset: 0,
@@ -37,20 +41,25 @@ export const DialogBackdrop = forwardRef(function DialogBackdrop({ className, co
37
41
  padding: inset,
38
42
  overflow: "auto",
39
43
  background: backdrop,
44
+ backdropFilter: blur,
45
+ WebkitBackdropFilter: blur,
40
46
  ...style
41
- } })] });
47
+ }, children: children })] });
42
48
  });
43
49
  export const DialogContent = forwardRef(function DialogContent({ children, className, color, material, radius, shadow, style, ...properties }, ref) {
44
- const resolved = useResolvedAppearance();
45
- const inset = scale(resolved.appearance.spacing, "medium");
46
- const transition = overlayTransition(resolved.transaction, resolved.preferences.animations);
50
+ const visual = useVisual();
51
+ const inset = scale(visual.spacing, "medium");
52
+ const transition = overlayTransition(visual);
47
53
  const direction = resolveDirection(properties.dir, useDirection());
48
54
  return _jsxs(_Fragment, { children: [_jsx(MotionStyle, {}), _jsx(AriaModal, { dir: direction, className: overlayMotionClass, style: {
49
55
  ...transition,
50
56
  width: `min(32rem, calc(100vw - ${inset * 2}px))`,
51
57
  maxHeight: `calc(100vh - ${inset * 2}px)`,
52
58
  outline: "none"
53
- }, children: _jsx(Surface, { className: className, color: color, material: material, radius: radius, shadow: shadow, style: {
59
+ }, children: _jsx(Surface, { className: className, color: color, material: material,
60
+ // Dialog content is composed by its consumer, so no owned rounded shape
61
+ // sits at a known inset; it keeps the plain Appearance radius like Popover.
62
+ radius: radius, shadow: shadow ?? floatingShadow(visual.shadow), style: {
54
63
  boxSizing: "border-box",
55
64
  width: "100%",
56
65
  maxHeight: "inherit",
@@ -67,7 +76,7 @@ export const DialogContent = forwardRef(function DialogContent({ children, class
67
76
  }, children: children }) }) })] });
68
77
  });
69
78
  export const DialogHeader = forwardRef(function DialogHeader({ style, ...properties }, ref) {
70
- const gap = scale(useAppearance().spacing, "small");
79
+ const gap = scale(useVisual().spacing, "small");
71
80
  return _jsx("div", { ...properties, ref: ref, style: { display: "grid", gap, minWidth: 0, ...style } });
72
81
  });
73
82
  export const DialogTitle = forwardRef(function DialogTitle({ style, ...properties }, ref) {
@@ -95,7 +104,7 @@ export const DialogBody = forwardRef(function DialogBody({ style, ...properties
95
104
  return _jsx("div", { ...properties, ref: ref, style: { minWidth: 0, minHeight: 0, ...style } });
96
105
  });
97
106
  export const DialogFooter = forwardRef(function DialogFooter({ style, ...properties }, ref) {
98
- const gap = scale(useAppearance().spacing, "small");
107
+ const gap = scale(useVisual().spacing, "small");
99
108
  return _jsx("div", { ...properties, ref: ref, style: { display: "flex", justifyContent: "flex-end", flexWrap: "wrap", gap, minWidth: 0, ...style } });
100
109
  });
101
110
  export const DialogClose = forwardRef(function DialogClose(properties, ref) {
@@ -1,23 +1,24 @@
1
1
  import type { CSSProperties, ReactElement, ReactNode } from "react";
2
2
  import type { ButtonProps as AriaButtonProps, DisclosurePanelProps as AriaDisclosurePanelProps, DisclosureProps as AriaDisclosureProps } from "react-aria-components";
3
- import type { ControlColor } from "./control.js";
4
- import type { RadiusProps } from "./radius.js";
5
- import type { ScaleLevel } from "./scale.js";
6
- import { type Spacing } from "./spacing.js";
3
+ import type { Color } from "./foundation/color.js";
4
+ import type { RadiusProps } from "./foundation/radius.js";
5
+ import type { ScaleLevel } from "./foundation/scale.js";
6
+ import { type Spacing } from "./foundation/spacing.js";
7
7
  export interface DisclosureRootProps extends Omit<AriaDisclosureProps, "children" | "className" | "defaultExpanded" | "isDisabled" | "isExpanded" | "onExpandedChange" | "style">, RadiusProps {
8
8
  readonly children?: ReactNode;
9
9
  readonly className?: string;
10
- readonly color?: ControlColor;
10
+ /** Paint of the trigger. Transparent by default, so it only veils on interaction. */
11
+ readonly color?: Color;
11
12
  readonly defaultExpanded?: boolean;
12
13
  readonly disabled?: boolean;
13
14
  readonly expanded?: boolean;
14
- readonly onChange?: (expanded: boolean) => void;
15
+ readonly onExpandedChange?: (expanded: boolean) => void;
15
16
  readonly size?: ScaleLevel;
16
17
  readonly style?: CSSProperties;
17
18
  }
18
19
  /** One independently expandable region. */
19
20
  export declare const DisclosureRoot: import("react").ForwardRefExoticComponent<DisclosureRootProps & import("react").RefAttributes<HTMLDivElement>>;
20
- export interface DisclosureTriggerProps extends Omit<AriaButtonProps, "children" | "className" | "isDisabled" | "onPress" | "style"> {
21
+ export interface DisclosureTriggerProps extends Omit<AriaButtonProps, "children" | "className" | "isDisabled" | "isPending" | "onPress" | "render" | "style"> {
21
22
  readonly children?: ReactNode;
22
23
  readonly className?: string;
23
24
  readonly style?: CSSProperties;
@@ -1,83 +1,62 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { createContext, forwardRef, useContext, useMemo } from "react";
3
3
  import { Button as AriaButton, Disclosure as AriaDisclosure, DisclosurePanel as AriaDisclosurePanel, DisclosureStateContext } from "react-aria-components";
4
- import { useResolvedAppearance } from "./appearance-context.js";
5
- import { controlOpacity, useControlTheme } from "./control.js";
6
- import { transitionTiming } from "./motion-style.js";
7
- import { resolveSpacing } from "./spacing.js";
8
- const DisclosureThemeContext = createContext(null);
4
+ import { transition, useControlMetrics } from "./control/control.js";
5
+ import { surfaceRender } from "./control/surface-render.js";
6
+ import { resolveSpacing } from "./foundation/spacing.js";
7
+ import { ChevronDown } from "lucide-react";
8
+ import { iconProps } from "./control/icon.js";
9
+ const DisclosureStyleContext = createContext(null);
9
10
  /** One independently expandable region. */
10
- export const DisclosureRoot = forwardRef(function DisclosureRoot({ children, className, color, defaultExpanded, disabled = false, expanded, onChange, radius = "medium", size = "medium", style, ...properties }, ref) {
11
- const theme = useControlTheme({ color, radius, size });
12
- const resolved = useResolvedAppearance();
13
- const context = useMemo(() => ({
14
- motion: transitionTiming(resolved.transaction, resolved.preferences.animations),
15
- theme
16
- }), [resolved.preferences.animations, resolved.transaction, theme]);
17
- return _jsx(DisclosureThemeContext.Provider, { value: context, children: _jsx(AriaDisclosure, { ...properties, ...(expanded === undefined ? {} : { isExpanded: expanded }), ...(defaultExpanded === undefined ? {} : { defaultExpanded }), ref: ref, className: className, isDisabled: disabled, onExpandedChange: onChange, style: {
18
- display: "grid",
19
- minWidth: 0,
20
- fontFamily: "inherit",
21
- fontSize: theme.fontSize,
22
- ...style
23
- }, children: children }) });
11
+ export const DisclosureRoot = forwardRef(function Disclosure({ children, className, color = "transparent", defaultExpanded, disabled = false, expanded, onExpandedChange, radius, size, style, ...properties }, ref) {
12
+ const metrics = useControlMetrics(size, radius);
13
+ const context = useMemo(() => ({ color, metrics }), [color, metrics]);
14
+ return _jsx(DisclosureStyleContext.Provider, { value: context, children: _jsx(AriaDisclosure, { ...properties, ...(expanded === undefined ? {} : { isExpanded: expanded }), ...(defaultExpanded === undefined ? {} : { defaultExpanded }), ref: ref, className: className, isDisabled: disabled, onExpandedChange: onExpandedChange, style: { display: "grid", minWidth: 0, fontFamily: "inherit", fontSize: metrics.fontSize, ...style }, children: children }) });
24
15
  });
25
16
  /** The control that changes the Disclosure's expansion state. */
26
17
  export const DisclosureTrigger = forwardRef(function DisclosureTrigger({ children, className, style, ...properties }, ref) {
27
- const { motion, theme } = useDisclosureTheme();
18
+ const { color, metrics } = useDisclosureStyle();
28
19
  const disclosure = useDisclosureState();
29
- return _jsxs(AriaButton, { ...properties, ref: ref, slot: "trigger", className: className, style: state => ({
30
- ...theme.transition,
20
+ return _jsxs(AriaButton, { ...properties, ref: ref, slot: "trigger", className: className, render: surfaceRender("button", state => ({
21
+ color,
22
+ radius: metrics.radius,
23
+ shadow: false,
24
+ interaction: { hovered: state.isHovered, pressed: state.isPressed, focusVisible: state.isFocusVisible, disabled: state.isDisabled }
25
+ })), style: {
31
26
  appearance: "none",
32
27
  display: "grid",
33
28
  gridTemplateColumns: "minmax(0, 1fr) auto",
34
29
  alignItems: "center",
35
- gap: theme.gap,
30
+ gap: metrics.gap,
36
31
  width: "100%",
37
32
  minWidth: 0,
38
- minHeight: theme.height,
39
- paddingBlock: theme.gap,
40
- paddingInline: Math.max(8, theme.spacing),
33
+ minHeight: metrics.height,
34
+ paddingBlock: metrics.gap / 2,
35
+ paddingInline: metrics.inset,
41
36
  boxSizing: "border-box",
42
37
  border: 0,
43
- borderRadius: theme.radius,
44
- outline: state.isFocusVisible ? `1px solid ${theme.focusColor}` : "none",
45
- outlineOffset: 1,
46
- cursor: state.isDisabled ? "not-allowed" : "pointer",
47
- opacity: state.isDisabled ? controlOpacity.disabled : 1,
38
+ background: "none",
39
+ cursor: "pointer",
48
40
  font: "inherit",
49
- lineHeight: 1.5,
41
+ fontWeight: 500,
42
+ lineHeight: 1.45,
50
43
  textAlign: "start",
51
44
  userSelect: "none",
52
45
  WebkitTapHighlightColor: "transparent",
53
- ...(disclosure.isExpanded
54
- ? state.isPressed ? theme.paints.palette.pressed : state.isHovered ? theme.paints.palette.hover : theme.paints.palette.rest
55
- : state.isPressed ? theme.paints.subtle.pressed : state.isHovered ? theme.paints.subtle.hover : { background: "transparent", color: "inherit" }),
56
46
  ...style
57
- }), children: [_jsx("span", { style: { minWidth: 0 }, children: children }), _jsx("span", { "aria-hidden": "true", style: {
58
- ...motion,
59
- transitionProperty: "transform",
60
- width: "0.5em",
61
- height: "0.5em",
62
- marginInline: "0.25em",
63
- borderInlineEnd: "1px solid currentColor",
64
- borderBlockEnd: "1px solid currentColor",
65
- transform: disclosure.isExpanded ? "rotate(225deg)" : "rotate(45deg)"
66
- } })] });
47
+ }, children: [_jsx("span", { style: { minWidth: 0 }, children: children }), _jsx(ChevronDown, { ...iconProps(14), style: { ...transition(metrics.visual, "rotate"), rotate: disclosure.isExpanded ? "180deg" : "0deg" } })] });
67
48
  });
68
49
  /** Content revealed by a Disclosure trigger. */
69
50
  export const DisclosureContent = forwardRef(function DisclosureContent({ children, className, padding = "small", style, ...properties }, ref) {
70
- const { motion } = useDisclosureTheme();
51
+ const { metrics } = useDisclosureStyle();
71
52
  const disclosure = useDisclosureState();
72
- const { appearance } = useResolvedAppearance();
73
53
  return _jsx(AriaDisclosurePanel, { ...properties, ref: ref, className: className, style: {
74
- ...motion,
75
- transitionProperty: "height, opacity",
54
+ ...transition(metrics.visual, "height, opacity"),
76
55
  height: "var(--disclosure-panel-height)",
77
56
  overflow: "clip",
78
57
  opacity: disclosure.isExpanded ? 1 : 0,
79
58
  ...style
80
- }, children: _jsx("div", { style: { minHeight: 0, overflow: "hidden" }, children: _jsx("div", { style: { padding: resolveSpacing(padding, appearance) }, children: children }) }) });
59
+ }, children: _jsx("div", { style: { minHeight: 0, overflow: "hidden" }, children: _jsx("div", { style: { padding: resolveSpacing(padding, metrics.visual.spacing) }, children: children }) }) });
81
60
  });
82
61
  function useDisclosureState() {
83
62
  const state = useContext(DisclosureStateContext);
@@ -85,8 +64,8 @@ function useDisclosureState() {
85
64
  throw new Error("Disclosure parts must be used inside Disclosure");
86
65
  return state;
87
66
  }
88
- function useDisclosureTheme() {
89
- const context = useContext(DisclosureThemeContext);
67
+ function useDisclosureStyle() {
68
+ const context = useContext(DisclosureStyleContext);
90
69
  if (context == null)
91
70
  throw new Error("Disclosure parts must be used inside Disclosure");
92
71
  return context;
@@ -0,0 +1,18 @@
1
+ import type { CSSProperties, ReactNode } from "react";
2
+ import type { DropZoneProps as AriaDropZoneProps } from "react-aria-components";
3
+ import { type RadiusProps } from "./foundation/radius.js";
4
+ import type { MaterialOverrides } from "./surface/material-options.js";
5
+ export interface DropZoneProps extends Omit<AriaDropZoneProps, "onDrop" | "getDropOperation" | "isDisabled" | "className" | "style" | "children">, RadiusProps, MaterialOverrides {
6
+ readonly children?: ReactNode;
7
+ /** MIME types it accepts, such as `image/png`, or a family such as `image/*`. Anything, when absent. */
8
+ readonly accept?: readonly string[];
9
+ readonly onDrop?: (files: File[]) => void;
10
+ readonly disabled?: boolean;
11
+ readonly className?: string;
12
+ readonly style?: CSSProperties;
13
+ }
14
+ /**
15
+ * An area that takes files dragged onto it. It recesses like every value
16
+ * holder, and takes the subtle `primary` level while files hover it.
17
+ */
18
+ export declare const DropZone: import("react").ForwardRefExoticComponent<DropZoneProps & import("react").RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,32 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import { DropZone as AriaDropZone } from "react-aria-components";
4
+ import { useControlMetrics } from "./control/control.js";
5
+ import { surfaceRender } from "./control/surface-render.js";
6
+ import { colorLevel } from "./foundation/color.js";
7
+ import { resolveRadius } from "./foundation/radius.js";
8
+ import { dimmedClass } from "./surface/surface.js";
9
+ /**
10
+ * An area that takes files dragged onto it. It recesses like every value
11
+ * holder, and takes the subtle `primary` level while files hover it.
12
+ */
13
+ export const DropZone = forwardRef(function DropZone({ children, accept, onDrop, disabled, radius, material, className, style, ...properties }, ref) {
14
+ const metrics = useControlMetrics();
15
+ const { colors } = metrics.visual;
16
+ const accepts = (type) => accept === undefined || accept.some(pattern => pattern.endsWith("/*") ? type.startsWith(pattern.slice(0, -1)) : type === pattern);
17
+ return _jsx(AriaDropZone, { ...properties, ref: ref, isDisabled: disabled,
18
+ // A drag names exact types only, so a family such as `image/*` is checked on drop.
19
+ getDropOperation: types => accept === undefined || accept.some(pattern => pattern.endsWith("/*") || types.has(pattern)) ? "copy" : "cancel", onDrop: async (event) => {
20
+ const files = await Promise.all(event.items
21
+ .filter((item) => item.kind === "file" && accepts(item.type))
22
+ .map(item => item.getFile()));
23
+ if (files.length > 0)
24
+ onDrop?.(files);
25
+ }, className: state => dimmedClass(state.isDisabled, className ?? state.defaultClassName) ?? "", render: surfaceRender("div", state => ({
26
+ color: state.isDropTarget ? colorLevel(colors.primary, "subtle", colors) : "background",
27
+ depth: "recessed",
28
+ material,
29
+ radius: resolveRadius(radius ?? "medium", metrics.visual.radius),
30
+ interaction: { hovered: false, focusVisible: state.isFocusVisible || state.isDropTarget, disabled: state.isDisabled }
31
+ })), style: { display: "grid", placeItems: "center", gap: metrics.gap, padding: metrics.spacing * 2, textAlign: "center", ...style }, children: children });
32
+ });
@@ -1,17 +1,20 @@
1
1
  import type { ComponentProps } from "react";
2
2
  import type { MenuTriggerProps as AriaMenuTriggerProps } from "react-aria-components";
3
- import { type ButtonProps } from "./button.js";
4
- export type DropdownMenuRootProps = AriaMenuTriggerProps;
5
- export declare function DropdownMenuRoot(properties: DropdownMenuRootProps): import("react").JSX.Element;
6
- export type DropdownMenuTriggerProps = ButtonProps;
7
- export declare const DropdownMenuTrigger: import("react").ForwardRefExoticComponent<ButtonProps & import("react").RefAttributes<HTMLButtonElement>>;
3
+ import { type ButtonActionProps } from "./button.js";
4
+ import { type OverlayRootProps } from "./control/open-state.js";
5
+ export type DropdownMenuRootProps = OverlayRootProps & Readonly<Pick<AriaMenuTriggerProps, "trigger">>;
6
+ export declare function DropdownMenuRoot({ children, trigger, ...state }: DropdownMenuRootProps): import("react").JSX.Element;
7
+ export type DropdownMenuTriggerProps = ButtonActionProps;
8
+ export declare const DropdownMenuTrigger: import("react").ForwardRefExoticComponent<ButtonActionProps & import("react").RefAttributes<HTMLButtonElement>>;
8
9
  /** A button-triggered overlay. Its Content owns positioning; Menu owns commands and selection. */
9
10
  export declare const DropdownMenu: typeof DropdownMenuRoot & {
10
- Trigger: import("react").ForwardRefExoticComponent<ButtonProps & import("react").RefAttributes<HTMLButtonElement>>;
11
- Content: import("react").ForwardRefExoticComponent<import("./popover.js").PopoverContentProps & import("react").RefAttributes<HTMLElement>>;
11
+ Trigger: import("react").ForwardRefExoticComponent<ButtonActionProps & import("react").RefAttributes<HTMLButtonElement>>;
12
+ Content: import("react").ForwardRefExoticComponent<import("./popover.js").PopoverContentProps & Readonly<{
13
+ nonModal?: boolean;
14
+ }> & import("react").RefAttributes<HTMLElement>>;
12
15
  Menu: import("./menu.js").MenuRootComponent & {
13
16
  Item: import("./menu.js").MenuItemComponent;
14
- Section: <T>(props: import("react-aria-components").MenuSectionProps<T> & React.RefAttributes<HTMLElement>) => import("react").ReactElement<unknown, string | React.JSXElementConstructor<any>> | null;
17
+ Section: <T extends object = object>(properties: import("./menu.js").MenuSectionProps<T>) => import("react").ReactElement | null;
15
18
  Header: import("react").ForwardRefExoticComponent<import("react-aria-components").HeaderProps & import("react").RefAttributes<HTMLElement>>;
16
19
  Separator: import("react").ForwardRefExoticComponent<import("react-aria-components").SeparatorProps & import("react").RefAttributes<HTMLElement>>;
17
20
  };
@@ -2,10 +2,11 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { forwardRef } from "react";
3
3
  import { MenuTrigger as AriaMenuTrigger } from "react-aria-components";
4
4
  import { Button } from "./button.js";
5
+ import { ariaOpenState } from "./control/open-state.js";
5
6
  import { Menu } from "./menu.js";
6
- import { PopoverContent } from "./popover.js";
7
- export function DropdownMenuRoot(properties) {
8
- return _jsx(AriaMenuTrigger, { ...properties });
7
+ import { MenuContent } from "./popover.js";
8
+ export function DropdownMenuRoot({ children, trigger, ...state }) {
9
+ return _jsx(AriaMenuTrigger, { ...ariaOpenState(state), trigger: trigger, children: children });
9
10
  }
10
11
  export const DropdownMenuTrigger = forwardRef(function DropdownMenuTrigger(properties, ref) {
11
12
  return _jsx(Button, { ...properties, ref: ref });
@@ -13,6 +14,6 @@ export const DropdownMenuTrigger = forwardRef(function DropdownMenuTrigger(prope
13
14
  /** A button-triggered overlay. Its Content owns positioning; Menu owns commands and selection. */
14
15
  export const DropdownMenu = Object.assign(DropdownMenuRoot, {
15
16
  Trigger: DropdownMenuTrigger,
16
- Content: PopoverContent,
17
+ Content: MenuContent,
17
18
  Menu
18
19
  });
@@ -0,0 +1,13 @@
1
+ import type { FieldsetHTMLAttributes, ReactNode } from "react";
2
+ export interface FieldsetProps extends Omit<FieldsetHTMLAttributes<HTMLFieldSetElement>, "title"> {
3
+ /** Names the group for everyone, including assistive technology. */
4
+ readonly title: ReactNode;
5
+ readonly description?: ReactNode;
6
+ readonly children: ReactNode;
7
+ }
8
+ /**
9
+ * A titled group of related fields, such as one section of settings. Each
10
+ * field keeps its own label; the group adds only its title, its description,
11
+ * and the spacing between fields.
12
+ */
13
+ export declare const Fieldset: import("react").ForwardRefExoticComponent<FieldsetProps & import("react").RefAttributes<HTMLFieldSetElement>>;
@@ -0,0 +1,26 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { forwardRef, useId } from "react";
3
+ import { controlFontSizes, controlFontWeight, controlOpacity, useControlMetrics } from "./control/control.js";
4
+ import { resolveSpacing } from "./foundation/spacing.js";
5
+ /**
6
+ * A titled group of related fields, such as one section of settings. Each
7
+ * field keeps its own label; the group adds only its title, its description,
8
+ * and the spacing between fields.
9
+ */
10
+ export const Fieldset = forwardRef(function Fieldset({ title, description, children, style, ...properties }, ref) {
11
+ const metrics = useControlMetrics();
12
+ const { spacing } = metrics.visual;
13
+ const titleId = useId();
14
+ const descriptionId = useId();
15
+ // The title names the group through aria-labelledby: a styled legend loses
16
+ // its naming role in some browsers.
17
+ return _jsxs("fieldset", { "aria-labelledby": titleId, "aria-describedby": description != null ? descriptionId : undefined, ...properties, ref: ref, style: {
18
+ display: "grid",
19
+ gap: resolveSpacing("large", spacing),
20
+ minWidth: 0,
21
+ margin: 0,
22
+ padding: 0,
23
+ border: 0,
24
+ ...style
25
+ }, children: [_jsxs("div", { style: { display: "grid", gap: resolveSpacing("xsmall", spacing) }, children: [_jsx("span", { id: titleId, style: { fontSize: controlFontSizes.large, fontWeight: controlFontWeight }, children: title }), description != null && _jsx("span", { id: descriptionId, style: { fontSize: controlFontSizes.medium, opacity: controlOpacity.secondary }, children: description })] }), _jsx("div", { style: { display: "grid", gap: resolveSpacing("large", spacing), minWidth: 0 }, children: children })] });
26
+ });
@@ -0,0 +1,13 @@
1
+ import type { ReactNode } from "react";
2
+ export interface FileTriggerProps {
3
+ /** A Button, or any pressable React UI control, that opens the file chooser. */
4
+ readonly children: ReactNode;
5
+ /** MIME types or extensions the chooser offers, such as `image/*` or `.html`. */
6
+ readonly accept?: readonly string[];
7
+ readonly multiple?: boolean;
8
+ /** Choose a directory instead of files. */
9
+ readonly directory?: boolean;
10
+ readonly onSelect?: (files: File[]) => void;
11
+ }
12
+ /** Opens the system file chooser from the control it wraps. It draws nothing of its own. */
13
+ export declare function FileTrigger({ children, accept, multiple, directory, onSelect }: FileTriggerProps): import("react").JSX.Element;
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { FileTrigger as AriaFileTrigger } from "react-aria-components";
3
+ /** Opens the system file chooser from the control it wraps. It draws nothing of its own. */
4
+ export function FileTrigger({ children, accept, multiple, directory, onSelect }) {
5
+ return _jsx(AriaFileTrigger, { acceptedFileTypes: accept, allowsMultiple: multiple, acceptDirectory: directory, onSelect: files => onSelect?.(files === null ? [] : [...files]), children: children });
6
+ }
package/dist/flex.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { ComponentPropsWithoutRef } from "react";
2
- import type { LayoutAlignment, LayoutGap, LayoutJustification } from "./layout.js";
2
+ import type { LayoutAlignment, LayoutGap, LayoutJustification } from "./foundation/layout.js";
3
3
  /** Properties accepted by the Flex layout primitive. */
4
4
  export interface FlexProps extends ComponentPropsWithoutRef<"div"> {
5
5
  /** Main-axis direction. The browser default is `row`. */
package/dist/flex.js CHANGED
@@ -1,17 +1,18 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { forwardRef } from "react";
3
- import { alignment, justification, resolveGap } from "./layout.js";
4
- import { useAppearance } from "./ui-provider.js";
3
+ import { alignment, justification } from "./foundation/layout.js";
4
+ import { resolveSpacing } from "./foundation/spacing.js";
5
+ import { useVisual } from "./foundation/visual.js";
5
6
  /** A predictable Flexbox container with no visual appearance of its own. */
6
7
  export const Flex = forwardRef(function Flex({ align, direction, gap, inline = false, justify, style, wrap, ...properties }, ref) {
7
- const appearance = useAppearance();
8
+ const { spacing } = useVisual();
8
9
  return _jsx("div", { ...properties, ref: ref, style: {
9
10
  ...style,
10
11
  display: inline ? "inline-flex" : "flex",
11
12
  alignItems: alignment(align) ?? style?.alignItems,
12
13
  flexDirection: direction ?? style?.flexDirection,
13
14
  flexWrap: wrap === undefined ? style?.flexWrap : wrap === "reverse" ? "wrap-reverse" : wrap ? "wrap" : "nowrap",
14
- gap: gap === undefined ? style?.gap : resolveGap(gap, appearance),
15
+ gap: gap === undefined ? style?.gap : resolveSpacing(gap, spacing),
15
16
  justifyContent: justification(justify) ?? style?.justifyContent
16
17
  } });
17
18
  });
@@ -0,0 +1,83 @@
1
+ import { type Appearance as SystemAppearance, type AppearanceColors, type AppearanceMaterial, type AppearanceShadow, type AppearanceTransaction } from "@phreshos/core";
2
+ export type { AppearanceColor, AppearanceColors, AppearanceMaterial, AppearanceRange, AppearanceShadow, AppearanceTransaction, Easing, ThemedValue } from "@phreshos/core";
3
+ /** The System Appearance fields consumed by React UI. */
4
+ export type Appearance = Pick<SystemAppearance, "colors" | "spacing" | "radius" | "shadow" | "material" | "transaction">;
5
+ type Themed<Value> = Readonly<{
6
+ light?: Readonly<Partial<Value>>;
7
+ dark?: Readonly<Partial<Value>>;
8
+ }>;
9
+ type AppearanceUpdateFields = Readonly<{
10
+ colors?: Themed<AppearanceColors>;
11
+ spacing?: number;
12
+ radius?: number;
13
+ shadow?: Themed<AppearanceShadow>;
14
+ material?: Themed<AppearanceMaterial>;
15
+ transaction?: Readonly<Partial<AppearanceTransaction>>;
16
+ }>;
17
+ /** At least one partial visual field merged recursively with the nearest UIProvider. */
18
+ export type AppearanceUpdate = {
19
+ [Field in keyof AppearanceUpdateFields]-?: Readonly<Required<Pick<AppearanceUpdateFields, Field>> & Omit<AppearanceUpdateFields, Field>>;
20
+ }[keyof AppearanceUpdateFields];
21
+ /** Bounds for the visual Appearance fields consumed by React UI. */
22
+ export declare const appearanceLimits: Readonly<{
23
+ spacing: Readonly<{
24
+ minimum: 6;
25
+ maximum: 18;
26
+ }>;
27
+ radius: Readonly<{
28
+ minimum: 6;
29
+ maximum: 18;
30
+ }>;
31
+ shadow: Readonly<{
32
+ x: Readonly<{
33
+ minimum: -48;
34
+ maximum: 48;
35
+ }>;
36
+ y: Readonly<{
37
+ minimum: -48;
38
+ maximum: 48;
39
+ }>;
40
+ blur: Readonly<{
41
+ minimum: 0;
42
+ maximum: 96;
43
+ }>;
44
+ spread: Readonly<{
45
+ minimum: -24;
46
+ maximum: 24;
47
+ }>;
48
+ opacity: Readonly<{
49
+ minimum: 0;
50
+ maximum: 1;
51
+ }>;
52
+ }>;
53
+ material: Readonly<{
54
+ grain: Readonly<{
55
+ minimum: 0;
56
+ maximum: 1;
57
+ }>;
58
+ grainAmount: Readonly<{
59
+ minimum: 0;
60
+ maximum: 1;
61
+ }>;
62
+ backdrop: Readonly<{
63
+ minimum: 0;
64
+ maximum: 24;
65
+ }>;
66
+ opacity: Readonly<{
67
+ minimum: 0;
68
+ maximum: 1;
69
+ }>;
70
+ distortion: Readonly<{
71
+ minimum: 0;
72
+ maximum: 140;
73
+ }>;
74
+ saturation: Readonly<{
75
+ minimum: 1;
76
+ maximum: 2.6;
77
+ }>;
78
+ }>;
79
+ }>;
80
+ /** The visual portion of the System's default Appearance. */
81
+ export declare const defaultAppearance: Appearance;
82
+ /** Applies one partial update; omitted leaves continue to inherit. */
83
+ export declare function mergeAppearance(appearance: Appearance, update: AppearanceUpdate): Appearance;
@@ -0,0 +1,38 @@
1
+ import { appearanceLimits as coreAppearanceLimits, defaultAppearance as systemDefaultAppearance } from "@phreshos/core";
2
+ /** Bounds for the visual Appearance fields consumed by React UI. */
3
+ export const appearanceLimits = Object.freeze({
4
+ spacing: coreAppearanceLimits.spacing,
5
+ radius: coreAppearanceLimits.radius,
6
+ shadow: coreAppearanceLimits.shadow,
7
+ material: coreAppearanceLimits.material
8
+ });
9
+ /** The visual portion of the System's default Appearance. */
10
+ export const defaultAppearance = Object.freeze({
11
+ colors: systemDefaultAppearance.colors,
12
+ spacing: systemDefaultAppearance.spacing,
13
+ radius: systemDefaultAppearance.radius,
14
+ shadow: systemDefaultAppearance.shadow,
15
+ material: systemDefaultAppearance.material,
16
+ transaction: systemDefaultAppearance.transaction
17
+ });
18
+ /** Applies one partial update; omitted leaves continue to inherit. */
19
+ export function mergeAppearance(appearance, update) {
20
+ return Object.freeze({
21
+ colors: mergeThemed(appearance.colors, update.colors),
22
+ spacing: update.spacing ?? appearance.spacing,
23
+ radius: update.radius ?? appearance.radius,
24
+ shadow: mergeThemed(appearance.shadow, update.shadow),
25
+ material: mergeThemed(appearance.material, update.material),
26
+ transaction: update.transaction === undefined
27
+ ? appearance.transaction
28
+ : Object.freeze({ ...appearance.transaction, ...update.transaction })
29
+ });
30
+ }
31
+ function mergeThemed(current, update) {
32
+ if (update === undefined)
33
+ return current;
34
+ return Object.freeze({
35
+ light: update.light === undefined ? current.light : Object.freeze({ ...current.light, ...update.light }),
36
+ dark: update.dark === undefined ? current.dark : Object.freeze({ ...current.dark, ...update.dark })
37
+ });
38
+ }
@@ -3,6 +3,8 @@ import { useMemo } from "react";
3
3
  import { useLocale } from "react-aria/I18nProvider";
4
4
  const ariaDirectionBoundary = "data-phreshos-ui-aria-direction";
5
5
  const redirectedEvents = new WeakSet();
6
+ // Text entry keeps native caret movement; choice inputs move selection by direction.
7
+ const directionalInputTypes = new Set(["radio", "checkbox", "range"]);
6
8
  /** Aligns React Aria's directional keyboard behavior with the React UI direction contract. */
7
9
  export function useAriaDirectionBridge(direction) {
8
10
  const { direction: ariaDirection } = useLocale();
@@ -18,7 +20,7 @@ export function useAriaDirectionBridge(direction) {
18
20
  if (!(target instanceof Element) || target.closest(`[${ariaDirectionBoundary}]`) !== event.currentTarget)
19
21
  return;
20
22
  const editable = target.closest("[contenteditable=true]");
21
- if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement || (editable && editable.getAttribute("role") !== "spinbutton"))
23
+ if ((target instanceof HTMLInputElement && !directionalInputTypes.has(target.type)) || target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement || (editable && editable.getAttribute("role") !== "spinbutton"))
22
24
  return;
23
25
  event.preventDefault();
24
26
  event.stopPropagation();