@react-md/core 6.2.0 → 6.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. package/dist/_base.scss +1 -0
  2. package/dist/datetime/NativeDateField.d.ts +24 -0
  3. package/dist/datetime/NativeDateField.js +63 -0
  4. package/dist/datetime/NativeDateField.js.map +1 -0
  5. package/dist/datetime/NativeTimeField.d.ts +26 -0
  6. package/dist/datetime/NativeTimeField.js +63 -0
  7. package/dist/datetime/NativeTimeField.js.map +1 -0
  8. package/dist/datetime/useDateField.d.ts +120 -0
  9. package/dist/datetime/useDateField.js +35 -0
  10. package/dist/datetime/useDateField.js.map +1 -0
  11. package/dist/datetime/useTimeField.d.ts +124 -0
  12. package/dist/datetime/useTimeField.js +65 -0
  13. package/dist/datetime/useTimeField.js.map +1 -0
  14. package/dist/datetime/utils.d.ts +34 -0
  15. package/dist/datetime/utils.js +27 -0
  16. package/dist/datetime/utils.js.map +1 -0
  17. package/dist/draggable/utils.d.ts +3 -6
  18. package/dist/draggable/utils.js.map +1 -1
  19. package/dist/expansion-panel/ExpansionList.js +1 -1
  20. package/dist/expansion-panel/ExpansionList.js.map +1 -1
  21. package/dist/expansion-panel/useExpansionList.d.ts +2 -7
  22. package/dist/expansion-panel/useExpansionList.js.map +1 -1
  23. package/dist/form/FormMessage.js +3 -1
  24. package/dist/form/FormMessage.js.map +1 -1
  25. package/dist/form/FormMessageContainer.d.ts +2 -1
  26. package/dist/form/FormMessageContainer.js +3 -2
  27. package/dist/form/FormMessageContainer.js.map +1 -1
  28. package/dist/form/FormMessageCounter.d.ts +3 -2
  29. package/dist/form/FormMessageCounter.js +5 -2
  30. package/dist/form/FormMessageCounter.js.map +1 -1
  31. package/dist/form/Listbox.d.ts +3 -10
  32. package/dist/form/Listbox.js +8 -27
  33. package/dist/form/Listbox.js.map +1 -1
  34. package/dist/form/ListboxProvider.d.ts +17 -0
  35. package/dist/form/ListboxProvider.js +33 -1
  36. package/dist/form/ListboxProvider.js.map +1 -1
  37. package/dist/form/NativeSelect.js +1 -0
  38. package/dist/form/NativeSelect.js.map +1 -1
  39. package/dist/form/TextArea.js +1 -0
  40. package/dist/form/TextArea.js.map +1 -1
  41. package/dist/form/TextField.js +1 -0
  42. package/dist/form/TextField.js.map +1 -1
  43. package/dist/form/_form-message.scss +13 -0
  44. package/dist/form/_select.scss +1 -1
  45. package/dist/form/_slider.scss +1 -1
  46. package/dist/form/_text-field.scss +12 -3
  47. package/dist/form/formMessageContainerStyles.d.ts +7 -0
  48. package/dist/form/formMessageContainerStyles.js +4 -2
  49. package/dist/form/formMessageContainerStyles.js.map +1 -1
  50. package/dist/form/sliderUtils.d.ts +3 -7
  51. package/dist/form/sliderUtils.js.map +1 -1
  52. package/dist/form/types.d.ts +13 -0
  53. package/dist/form/types.js.map +1 -1
  54. package/dist/form/useCombobox.d.ts +6 -2
  55. package/dist/form/useCombobox.js +8 -9
  56. package/dist/form/useCombobox.js.map +1 -1
  57. package/dist/form/useFormReset.d.ts +4 -1
  58. package/dist/form/useFormReset.js +9 -4
  59. package/dist/form/useFormReset.js.map +1 -1
  60. package/dist/form/useNumberField.d.ts +5 -5
  61. package/dist/form/useNumberField.js +10 -2
  62. package/dist/form/useNumberField.js.map +1 -1
  63. package/dist/form/useSelectCombobox.js +2 -2
  64. package/dist/form/useSelectCombobox.js.map +1 -1
  65. package/dist/form/useTextField.d.ts +76 -59
  66. package/dist/form/useTextField.js +7 -1
  67. package/dist/form/useTextField.js.map +1 -1
  68. package/dist/interaction/utils.d.ts +14 -0
  69. package/dist/interaction/utils.js +23 -12
  70. package/dist/interaction/utils.js.map +1 -1
  71. package/dist/menu/MenuBar.js +1 -1
  72. package/dist/menu/MenuBar.js.map +1 -1
  73. package/dist/menu/MenuItemTextField.d.ts +1 -2
  74. package/dist/menu/MenuItemTextField.js.map +1 -1
  75. package/dist/menu/MenuWidget.js +3 -2
  76. package/dist/menu/MenuWidget.js.map +1 -1
  77. package/dist/movement/constants.d.ts +10 -0
  78. package/dist/movement/constants.js +20 -4
  79. package/dist/movement/constants.js.map +1 -1
  80. package/dist/movement/types.d.ts +59 -10
  81. package/dist/movement/types.js.map +1 -1
  82. package/dist/movement/useKeyboardMovementProvider.d.ts +5 -1
  83. package/dist/movement/useKeyboardMovementProvider.js +171 -73
  84. package/dist/movement/useKeyboardMovementProvider.js.map +1 -1
  85. package/dist/tabs/useTabList.js +1 -1
  86. package/dist/tabs/useTabList.js.map +1 -1
  87. package/dist/test-utils/drag.d.ts +6 -9
  88. package/dist/transition/useCarousel.d.ts +2 -2
  89. package/dist/transition/useCarousel.js.map +1 -1
  90. package/dist/tree/Tree.js +1 -1
  91. package/dist/tree/Tree.js.map +1 -1
  92. package/dist/tree/_tree.scss +1 -1
  93. package/dist/tree/useTreeMovement.d.ts +2 -1
  94. package/dist/tree/useTreeMovement.js +2 -1
  95. package/dist/tree/useTreeMovement.js.map +1 -1
  96. package/dist/types.d.ts +14 -0
  97. package/dist/types.js.map +1 -1
  98. package/dist/utils/getMiddleOfRange.d.ts +2 -3
  99. package/dist/utils/getMiddleOfRange.js.map +1 -1
  100. package/dist/utils/getPercentage.d.ts +2 -9
  101. package/dist/utils/getPercentage.js +1 -1
  102. package/dist/utils/getPercentage.js.map +1 -1
  103. package/dist/utils/getRangeSteps.d.ts +2 -3
  104. package/dist/utils/getRangeSteps.js +0 -3
  105. package/dist/utils/getRangeSteps.js.map +1 -1
  106. package/dist/utils/nearest.d.ts +2 -3
  107. package/dist/utils/nearest.js +0 -3
  108. package/dist/utils/nearest.js.map +1 -1
  109. package/dist/utils/trigonometry.d.ts +31 -0
  110. package/dist/utils/trigonometry.js +25 -0
  111. package/dist/utils/trigonometry.js.map +1 -0
  112. package/dist/window-splitter/_window-splitter.scss +1 -1
  113. package/dist/window-splitter/useWindowSplitter.d.ts +1 -1
  114. package/dist/window-splitter/useWindowSplitter.js.map +1 -1
  115. package/package.json +1 -1
  116. package/src/datetime/NativeDateField.tsx +92 -0
  117. package/src/datetime/NativeTimeField.tsx +94 -0
  118. package/src/datetime/useDateField.ts +193 -0
  119. package/src/datetime/useTimeField.ts +233 -0
  120. package/src/datetime/utils.ts +48 -0
  121. package/src/draggable/utils.ts +3 -6
  122. package/src/expansion-panel/ExpansionList.tsx +2 -1
  123. package/src/expansion-panel/useExpansionList.ts +6 -12
  124. package/src/form/FormMessage.tsx +4 -0
  125. package/src/form/FormMessageContainer.tsx +8 -4
  126. package/src/form/FormMessageCounter.tsx +17 -6
  127. package/src/form/Listbox.tsx +18 -46
  128. package/src/form/ListboxProvider.ts +61 -1
  129. package/src/form/NativeSelect.tsx +1 -0
  130. package/src/form/TextArea.tsx +1 -0
  131. package/src/form/TextField.tsx +1 -0
  132. package/src/form/formMessageContainerStyles.ts +10 -2
  133. package/src/form/sliderUtils.ts +3 -7
  134. package/src/form/types.ts +15 -0
  135. package/src/form/useCombobox.ts +15 -10
  136. package/src/form/useFormReset.ts +12 -5
  137. package/src/form/useNumberField.ts +17 -14
  138. package/src/form/useSelectCombobox.ts +2 -2
  139. package/src/form/useTextField.ts +102 -69
  140. package/src/interaction/utils.ts +18 -20
  141. package/src/menu/MenuBar.tsx +1 -1
  142. package/src/menu/MenuItemTextField.tsx +1 -3
  143. package/src/menu/MenuWidget.tsx +4 -2
  144. package/src/movement/constants.ts +26 -4
  145. package/src/movement/types.ts +84 -19
  146. package/src/movement/useKeyboardMovementProvider.ts +209 -95
  147. package/src/tabs/useTabList.ts +1 -1
  148. package/src/test-utils/drag.ts +8 -12
  149. package/src/transition/useCarousel.ts +2 -2
  150. package/src/tree/Tree.tsx +1 -1
  151. package/src/tree/useTreeMovement.ts +4 -0
  152. package/src/types.ts +16 -0
  153. package/src/utils/getMiddleOfRange.ts +2 -3
  154. package/src/utils/getPercentage.ts +3 -11
  155. package/src/utils/getRangeSteps.ts +3 -3
  156. package/src/utils/nearest.ts +3 -3
  157. package/src/utils/trigonometry.ts +46 -0
  158. package/src/window-splitter/useWindowSplitter.ts +3 -2
@@ -20,6 +20,7 @@ import {
20
20
  type FormMessageInputLengthCounterProps,
21
21
  type FormMessageProps,
22
22
  } from "./types.js";
23
+ import { useFormReset } from "./useFormReset.js";
23
24
  import {
24
25
  type ErrorMessageOptions,
25
26
  type GetErrorIcon,
@@ -41,12 +42,12 @@ const noop = (): void => {
41
42
  * @since 6.0.0 Added the `onInvalid` handler
42
43
  */
43
44
  export type TextFieldChangeHandlers<
44
- E extends HTMLInputElement | HTMLTextAreaElement,
45
+ E extends HTMLInputElement | HTMLTextAreaElement = HTMLInputElement,
45
46
  > = Pick<HTMLAttributes<E>, "onBlur" | "onChange" | "onInvalid">;
46
47
 
47
48
  /** @since 6.0.0 */
48
49
  export interface ErrorChangeHandlerOptions<
49
- E extends HTMLInputElement | HTMLTextAreaElement,
50
+ E extends HTMLInputElement | HTMLTextAreaElement = HTMLInputElement,
50
51
  > {
51
52
  /**
52
53
  * A ref containing the `TextField` or `TextArea` if you need access to that
@@ -94,7 +95,7 @@ export interface ErrorChangeHandlerOptions<
94
95
  * @since 6.0.0 Changed to object argument.
95
96
  */
96
97
  export type ErrorChangeHandler<
97
- E extends HTMLInputElement | HTMLTextAreaElement,
98
+ E extends HTMLInputElement | HTMLTextAreaElement = HTMLInputElement,
98
99
  > = (options: ErrorChangeHandlerOptions<E>) => void;
99
100
 
100
101
  /** @since 2.5.6 */
@@ -135,7 +136,7 @@ export interface ProvidedFormMessageProps
135
136
  * @since 2.5.0
136
137
  */
137
138
  export interface ProvidedTextFieldProps<
138
- E extends HTMLInputElement | HTMLTextAreaElement,
139
+ E extends HTMLInputElement | HTMLTextAreaElement = HTMLInputElement,
139
140
  > extends TextFieldValidationOptions,
140
141
  TextFieldChangeHandlers<E>,
141
142
  Required<Pick<TextFieldProps, "id" | "name" | "value" | "error">>,
@@ -153,7 +154,7 @@ export interface ProvidedTextFieldProps<
153
154
  * @since 2.5.0
154
155
  */
155
156
  export interface ProvidedTextFieldMessageProps<
156
- E extends HTMLInputElement | HTMLTextAreaElement,
157
+ E extends HTMLInputElement | HTMLTextAreaElement = HTMLInputElement,
157
158
  > extends ProvidedTextFieldProps<E> {
158
159
  /**
159
160
  * These props will be defined as long as the `disableMessage` prop is not
@@ -162,52 +163,30 @@ export interface ProvidedTextFieldMessageProps<
162
163
  messageProps: ProvidedFormMessageProps;
163
164
  }
164
165
 
165
- /** @since 2.5.6 */
166
- export interface TextFieldHookOptions<
167
- E extends HTMLInputElement | HTMLTextAreaElement,
168
- > extends TextFieldValidationOptions,
169
- TextFieldChangeHandlers<E> {
166
+ /**
167
+ * @since 6.3.0
168
+ */
169
+ export interface TextFieldHookComponentOptions<
170
+ E extends HTMLInputElement | HTMLTextAreaElement = HTMLInputElement,
171
+ > {
170
172
  /**
171
- * An optional id to use for the `TextField` or `TextArea` that is also used
172
- * to create an id for the inline help/error messages.
173
+ * An optional id to use for the `TextField`, `Password`, or `TextArea` that
174
+ * is also used to create an id for the inline help/error messages.
173
175
  *
174
176
  * @defaultValue `"text-field-" + useId()`
175
177
  */
176
178
  id?: string;
177
179
 
178
180
  /**
179
- * An optional ref that should be merged with the ref returned by this hook.
180
- * This should really only be used if you are making a custom component using
181
- * this hook and forwarding refs. If you need a ref to access the `<input>` or
182
- * `<textarea>` DOM node, you can use the `fieldRef` returned by this hook
183
- * instead.
184
- *
185
- * @example Accessing TextField DOM Node
186
- * ```tsx
187
- * import { TextField } from "@react-md/core/form/TextField";
188
- * import { useTextField } from "@react-md/core/form/useTextField";
189
- * import { useEffect } from "react";
190
- * import type { ReactElement } from "react";
191
- *
192
- * function Example(): ReactElement {
193
- * const { fieldRef, fieldProps } = useTextField({ name: "example" });
194
- *
195
- * useEffect(() => {
196
- * fieldRef.current;
197
- * // ^ HTMLInputElement | null
198
- * }, [fieldRef]);
199
- *
200
- * return <TextField {...fieldProps} label="Example" />;
201
- * }
202
- * ```
181
+ * A unique name to attach to the `TextField`, `TextArea`, or `Password`
182
+ * component.
203
183
  */
204
- ref?: Ref<E>;
184
+ name: string;
205
185
 
206
186
  /**
207
- * A unique name to attach to the `TextField`, `TextArea` or `Password`
208
- * component.
187
+ * @since 6.3.0
209
188
  */
210
- name: string;
189
+ form?: string;
211
190
 
212
191
  /**
213
192
  * Boolean if the `FormMessage` should also display a counter for the
@@ -220,25 +199,6 @@ export interface TextFieldHookOptions<
220
199
  */
221
200
  counter?: boolean;
222
201
 
223
- /**
224
- * This is used internally for the `useNumberField` hook and probably
225
- * shouldn't be used otherwise. This is just passed into the
226
- * {@link getErrorMessage} options and is used for additional validation.
227
- *
228
- * @defaultValue `false`
229
- */
230
- isNumber?: boolean;
231
-
232
- /**
233
- * The default value to use for the `TextField` or `TextArea` one initial
234
- * render. If you want to manually change the value to something else after
235
- * the initial render, either change the `key` for the component containing
236
- * this hook, or use the `setState` function returned from this hook.
237
- *
238
- * @defaultValue `""`
239
- */
240
- defaultValue?: UseStateInitializer<string>;
241
-
242
202
  /**
243
203
  * An optional help text to display in the `FormMessage` component when there
244
204
  * is not an error.
@@ -289,6 +249,15 @@ export interface TextFieldHookOptions<
289
249
  */
290
250
  onErrorChange?: ErrorChangeHandler<E>;
291
251
 
252
+ /**
253
+ * Set to `true` to prevent the state from automatically resetting with a
254
+ * form's `reset` event.
255
+ *
256
+ * @defaultValue `false`
257
+ * @since 6.3.0
258
+ */
259
+ disableReset?: boolean;
260
+
292
261
  /**
293
262
  * Set this to `true` to prevent the `errorMessage` from being
294
263
  * rendered inline below the `TextField`.
@@ -316,9 +285,63 @@ export interface TextFieldHookOptions<
316
285
  validationType?: TextFieldValidationType;
317
286
  }
318
287
 
288
+ /** @since 2.5.6 */
289
+ export interface TextFieldHookOptions<
290
+ E extends HTMLInputElement | HTMLTextAreaElement = HTMLInputElement,
291
+ > extends TextFieldValidationOptions,
292
+ TextFieldHookComponentOptions<E>,
293
+ TextFieldChangeHandlers<E> {
294
+ /**
295
+ * An optional ref that should be merged with the ref returned by this hook.
296
+ * This should really only be used if you are making a custom component using
297
+ * this hook and forwarding refs. If you need a ref to access the `<input>` or
298
+ * `<textarea>` DOM node, you can use the `fieldRef` returned by this hook
299
+ * instead.
300
+ *
301
+ * @example Accessing TextField DOM Node
302
+ * ```tsx
303
+ * import { TextField } from "@react-md/core/form/TextField";
304
+ * import { useTextField } from "@react-md/core/form/useTextField";
305
+ * import { useEffect } from "react";
306
+ * import type { ReactElement } from "react";
307
+ *
308
+ * function Example(): ReactElement {
309
+ * const { fieldRef, fieldProps } = useTextField({ name: "example" });
310
+ *
311
+ * useEffect(() => {
312
+ * fieldRef.current;
313
+ * // ^ HTMLInputElement | null
314
+ * }, [fieldRef]);
315
+ *
316
+ * return <TextField {...fieldProps} label="Example" />;
317
+ * }
318
+ * ```
319
+ */
320
+ ref?: Ref<E>;
321
+
322
+ /**
323
+ * This is used internally for the `useNumberField` hook and probably
324
+ * shouldn't be used otherwise. This is just passed into the
325
+ * {@link getErrorMessage} options and is used for additional validation.
326
+ *
327
+ * @defaultValue `false`
328
+ */
329
+ isNumber?: boolean;
330
+
331
+ /**
332
+ * The default value to use for the `TextField` or `TextArea` one initial
333
+ * render. If you want to manually change the value to something else after
334
+ * the initial render, either change the `key` for the component containing
335
+ * this hook, or use the `setState` function returned from this hook.
336
+ *
337
+ * @defaultValue `""`
338
+ */
339
+ defaultValue?: UseStateInitializer<string>;
340
+ }
341
+
319
342
  /** @since 6.0.0 */
320
343
  export interface TextFieldImplementation<
321
- E extends HTMLInputElement | HTMLTextAreaElement,
344
+ E extends HTMLInputElement | HTMLTextAreaElement = HTMLInputElement,
322
345
  > extends TextFieldHookState {
323
346
  fieldRef: RefObject<E>;
324
347
  reset: () => void;
@@ -328,14 +351,14 @@ export interface TextFieldImplementation<
328
351
 
329
352
  /** @since 6.0.0 */
330
353
  export interface TextFieldWithMessageImplementation<
331
- E extends HTMLInputElement | HTMLTextAreaElement,
354
+ E extends HTMLInputElement | HTMLTextAreaElement = HTMLInputElement,
332
355
  > extends TextFieldImplementation<E> {
333
356
  fieldProps: ProvidedTextFieldMessageProps<E>;
334
357
  }
335
358
 
336
359
  /** @since 6.0.0 */
337
360
  export interface ValidatedTextFieldImplementation<
338
- E extends HTMLInputElement | HTMLTextAreaElement,
361
+ E extends HTMLInputElement | HTMLTextAreaElement = HTMLInputElement,
339
362
  > extends TextFieldImplementation<E> {
340
363
  fieldProps: ProvidedTextFieldProps<E> | ProvidedTextFieldMessageProps<E>;
341
364
  }
@@ -365,7 +388,9 @@ export interface ValidatedTextFieldImplementation<
365
388
  * @see {@link https://react-md.dev/components/text-field | TextField Demos}
366
389
  * @see {@link https://react-md.dev/hooks/use-text-field | useTextField Demos}
367
390
  */
368
- export function useTextField<E extends HTMLInputElement | HTMLTextAreaElement>(
391
+ export function useTextField<
392
+ E extends HTMLInputElement | HTMLTextAreaElement = HTMLInputElement,
393
+ >(
369
394
  options: TextFieldHookOptions<E> & { disableMessage: true }
370
395
  ): TextFieldImplementation<E>;
371
396
 
@@ -481,21 +506,22 @@ export function useTextField<E extends HTMLInputElement | HTMLTextAreaElement>(
481
506
  * added the ability to display an inline counter and help text while disabling
482
507
  * the error messaging.
483
508
  */
484
- export function useTextField<E extends HTMLInputElement | HTMLTextAreaElement>(
485
- options: TextFieldHookOptions<E>
486
- ): TextFieldWithMessageImplementation<E>;
509
+ export function useTextField<
510
+ E extends HTMLInputElement | HTMLTextAreaElement = HTMLInputElement,
511
+ >(options: TextFieldHookOptions<E>): TextFieldWithMessageImplementation<E>;
487
512
  /**
488
513
  * @see {@link https://react-md.dev/components/text-field | TextField Demos}
489
514
  * @see {@link https://react-md.dev/hooks/use-text-field | useTextField Demos}
490
515
  * @since 6.0.0
491
516
  */
492
- export function useTextField<E extends HTMLInputElement | HTMLTextAreaElement>(
493
- options: TextFieldHookOptions<E>
494
- ): ValidatedTextFieldImplementation<E> {
517
+ export function useTextField<
518
+ E extends HTMLInputElement | HTMLTextAreaElement = HTMLInputElement,
519
+ >(options: TextFieldHookOptions<E>): ValidatedTextFieldImplementation<E> {
495
520
  const {
496
521
  id: propId,
497
522
  ref: propRef,
498
523
  name,
524
+ form,
499
525
  defaultValue = "",
500
526
  isNumber = false,
501
527
  required,
@@ -507,6 +533,7 @@ export function useTextField<E extends HTMLInputElement | HTMLTextAreaElement>(
507
533
  onInvalid = noop,
508
534
  counter = false,
509
535
  helpText,
536
+ disableReset,
510
537
  validationType = "recommended",
511
538
  disableMessage = false,
512
539
  disableMaxLength = false,
@@ -712,6 +739,12 @@ export function useTextField<E extends HTMLInputElement | HTMLTextAreaElement>(
712
739
  };
713
740
  }
714
741
 
742
+ useFormReset({
743
+ form,
744
+ onReset: disableReset ? undefined : reset,
745
+ elementRef: fieldRef,
746
+ });
747
+
715
748
  return {
716
749
  ...state,
717
750
  reset,
@@ -1,37 +1,35 @@
1
1
  import { type KeyboardEvent, type MouseEvent, type TouchEvent } from "react";
2
2
 
3
3
  import { findSizingContainer } from "../positioning/utils.js";
4
+ import { type Point } from "../types.js";
5
+ import { calcHypotenuse } from "../utils/trigonometry.js";
4
6
  import {
5
7
  type ElementInteractionState,
6
8
  type RippleState,
7
9
  type RippleStyle,
8
10
  } from "./types.js";
9
11
 
10
- /** @internal */
11
- function calcHypotenuse(a: number, b: number): number {
12
- return Math.sqrt(a * a + b * b);
12
+ /**
13
+ * @internal
14
+ * @since 6.3.0
15
+ */
16
+ interface GetRadiusOptions extends Point {
17
+ height: number;
18
+ width: number;
13
19
  }
14
20
 
15
21
  /**
16
- * Gets the current radius for a ripple based on the x and y page dimensions
17
- * as well as the size of the element.
18
- *
19
- * This is really just in a separate file so I can easily mock this and write
20
- * tests.
21
- *
22
22
  * @internal
23
+ * @since 6.3.0
23
24
  */
24
- function getRadius(
25
- x: number,
26
- y: number,
27
- offsetWidth: number,
28
- offsetHeight: number
29
- ): number {
25
+ export function getRadius(options: GetRadiusOptions): number {
26
+ const { x, y, height, width } = options;
27
+
30
28
  return Math.max(
31
- calcHypotenuse(x, y),
32
- calcHypotenuse(offsetWidth - x, y),
33
- calcHypotenuse(offsetWidth - x, offsetHeight - y),
34
- calcHypotenuse(x, offsetHeight - y)
29
+ calcHypotenuse({ x, y }),
30
+ calcHypotenuse({ x: width - x, y }),
31
+ calcHypotenuse({ x: width - x, y: height - y }),
32
+ calcHypotenuse({ x, y: height - y })
35
33
  );
36
34
  }
37
35
 
@@ -66,7 +64,7 @@ export function getRippleStyle(
66
64
  y = pageY - (top + window.scrollY);
67
65
  }
68
66
 
69
- const radius = getRadius(x, y, width, height);
67
+ const radius = getRadius({ x, y, width, height });
70
68
  const size = radius * 2;
71
69
 
72
70
  return {
@@ -89,6 +89,7 @@ export const MenuBar = forwardRef<HTMLUListElement, MenuBarProps>(
89
89
  });
90
90
  const { activeId, enableHoverMode } = menuBarContext;
91
91
  const { movementProps, movementContext } = useKeyboardMovementProvider({
92
+ ref,
92
93
  onClick,
93
94
  onFocus,
94
95
  onKeyDown,
@@ -110,7 +111,6 @@ export const MenuBar = forwardRef<HTMLUListElement, MenuBarProps>(
110
111
  <List
111
112
  {...remaining}
112
113
  {...movementProps}
113
- ref={ref}
114
114
  role="menubar"
115
115
  horizontal={horizontal}
116
116
  >
@@ -8,15 +8,13 @@ import { type PropsWithRef } from "../types.js";
8
8
 
9
9
  /**
10
10
  * @since 5.0.0
11
+ * @since 6.3.0 Removed the invalid `stretch` prop
11
12
  */
12
13
  export interface MenuItemTextFieldProps extends TextFieldProps {
13
14
  /**
14
15
  * Any additional props or a `ref` to apply to the surrounding `<li>` element.
15
16
  */
16
17
  liProps?: PropsWithRef<HTMLAttributes<HTMLLIElement>>;
17
-
18
- /** @defaultValue `true` */
19
- stretch?: boolean;
20
18
  }
21
19
 
22
20
  /**
@@ -84,6 +84,7 @@ export const MenuWidget = forwardRef<HTMLDivElement, MenuWidgetProps>(
84
84
  defaultActiveId: id,
85
85
  });
86
86
  const { movementProps, movementContext } = useKeyboardMovementProvider({
87
+ ref,
87
88
  onClick,
88
89
  onFocus(event) {
89
90
  onFocus(event);
@@ -110,9 +111,10 @@ export const MenuWidget = forwardRef<HTMLDivElement, MenuWidgetProps>(
110
111
  <div
111
112
  aria-orientation={horizontal ? "horizontal" : undefined}
112
113
  {...remaining}
113
- {...(isListbox ? { onClick, onFocus, onKeyDown } : movementProps)}
114
+ {...(isListbox
115
+ ? { onClick, onFocus, onKeyDown, ref }
116
+ : movementProps)}
114
117
  id={id}
115
- ref={ref}
116
118
  role={role}
117
119
  className={menu({
118
120
  className,
@@ -13,24 +13,46 @@ export const DEFAULT_KEYBOARD_MOVEMENT: Readonly<KeyboardMovementConfig> = {
13
13
  jumpToLastKeys: ["End"],
14
14
  };
15
15
 
16
+ /**
17
+ * @since 6.3.0
18
+ * @internal
19
+ */
20
+ export const DEFAULT_LTR_KEYBOARD_MOVEMENT_WITHOUT_JUMP: Readonly<KeyboardMovementConfig> =
21
+ {
22
+ incrementKeys: ["ArrowRight"],
23
+ decrementKeys: ["ArrowLeft"],
24
+ jumpToFirstKeys: [],
25
+ jumpToLastKeys: [],
26
+ };
27
+
16
28
  /**
17
29
  * @since 5.1.2
18
30
  * @internal
19
31
  */
20
32
  export const DEFAULT_LTR_KEYBOARD_MOVEMENT: Readonly<KeyboardMovementConfig> = {
21
- incrementKeys: ["ArrowRight"],
22
- decrementKeys: ["ArrowLeft"],
33
+ ...DEFAULT_LTR_KEYBOARD_MOVEMENT_WITHOUT_JUMP,
23
34
  jumpToFirstKeys: ["Home"],
24
35
  jumpToLastKeys: ["End"],
25
36
  };
26
37
 
38
+ /**
39
+ * @since 6.3.0
40
+ * @internal
41
+ */
42
+ export const DEFAULT_RTL_KEYBOARD_MOVEMENT_WITHOUT_JUMP: Readonly<KeyboardMovementConfig> =
43
+ {
44
+ incrementKeys: ["ArrowLeft"],
45
+ decrementKeys: ["ArrowRight"],
46
+ jumpToFirstKeys: [],
47
+ jumpToLastKeys: [],
48
+ };
49
+
27
50
  /**
28
51
  * @since 5.1.2
29
52
  * @internal
30
53
  */
31
54
  export const DEFAULT_RTL_KEYBOARD_MOVEMENT: Readonly<KeyboardMovementConfig> = {
32
- incrementKeys: ["ArrowLeft"],
33
- decrementKeys: ["ArrowRight"],
55
+ ...DEFAULT_RTL_KEYBOARD_MOVEMENT_WITHOUT_JUMP,
34
56
  jumpToFirstKeys: ["Home"],
35
57
  jumpToLastKeys: ["End"],
36
58
  };
@@ -1,16 +1,15 @@
1
- import type {
2
- FocusEvent,
3
- FocusEventHandler,
4
- KeyboardEvent,
5
- KeyboardEventHandler,
6
- MouseEvent,
7
- MouseEventHandler,
1
+ import {
2
+ type HTMLAttributes,
3
+ type KeyboardEvent,
4
+ type Ref,
5
+ type RefCallback,
6
+ type RefObject,
8
7
  } from "react";
9
8
 
10
- import type {
11
- NonNullMutableRef,
12
- NonNullRef,
13
- UseStateSetter,
9
+ import {
10
+ type NonNullMutableRef,
11
+ type NonNullRef,
12
+ type UseStateSetter,
14
13
  } from "../types.js";
15
14
 
16
15
  /**
@@ -136,6 +135,23 @@ export interface KeyboardMovementBehavior {
136
135
  horizontal?: boolean;
137
136
  }
138
137
 
138
+ /**
139
+ * @since 6.3.0
140
+ */
141
+ export interface KeyboardFocusFromKeyOptions {
142
+ key: string;
143
+ /** @defaultValue `false` */
144
+ reversed?: boolean;
145
+
146
+ /** @defaultValue `getFocusableElementsFromRef()` */
147
+ focusables?: readonly HTMLElement[];
148
+ }
149
+
150
+ /**
151
+ * @since 6.3.0
152
+ */
153
+ export type KeyboardFocusAction = (focusables?: readonly HTMLElement[]) => void;
154
+
139
155
  /**
140
156
  * @since 5.0.0
141
157
  * @since 6.0.0 Removed `attach`, `detach` and `watching`
@@ -154,6 +170,31 @@ export interface KeyboardMovementContext
154
170
  * has been set to `"roving"` or `"virtual"`.
155
171
  */
156
172
  activeDescendantId: string;
173
+
174
+ /**
175
+ * @since 6.3.0
176
+ */
177
+ focusFirst: KeyboardFocusAction;
178
+
179
+ /**
180
+ * @since 6.3.0
181
+ */
182
+ focusLast: KeyboardFocusAction;
183
+
184
+ /**
185
+ * @since 6.3.0
186
+ */
187
+ focusNext: KeyboardFocusAction;
188
+
189
+ /**
190
+ * @since 6.3.0
191
+ */
192
+ focusPrevious: KeyboardFocusAction;
193
+
194
+ /**
195
+ * @since 6.3.0
196
+ */
197
+ focusFromKey: (options: KeyboardFocusFromKeyOptions) => void;
157
198
  }
158
199
 
159
200
  /**
@@ -208,20 +249,33 @@ export interface KeyboardMovementExtensionData<E extends HTMLElement>
208
249
  setActiveDescendantId: (id: string) => void;
209
250
  }
210
251
 
252
+ /**
253
+ * @since 6.3.0
254
+ */
255
+ export type KeyboardMovementEventHandlers<E extends HTMLElement> = Pick<
256
+ HTMLAttributes<E>,
257
+ "onClick" | "onFocus" | "onKeyDown"
258
+ >;
259
+
260
+ /**
261
+ * @since 6.3.0
262
+ */
263
+ export interface SimpleKeyboardMovementWrapperOptions<E extends HTMLElement>
264
+ extends KeyboardMovementEventHandlers<E> {
265
+ ref?: Ref<E>;
266
+ }
267
+
211
268
  /**
212
269
  * @since 6.0.0
213
270
  * @internal
214
271
  */
215
272
  export interface KeyboardMovementProviderOptions<E extends HTMLElement>
216
273
  extends KeyboardMovementBehavior,
274
+ SimpleKeyboardMovementWrapperOptions<E>,
217
275
  KeyboardMovementConfiguration {
218
276
  /** @see {@link TabIndexBehavior} */
219
277
  tabIndexBehavior?: TabIndexBehavior;
220
278
 
221
- onClick?: (event: MouseEvent<E>) => void;
222
- onFocus?: (event: FocusEvent<E>) => void;
223
- onKeyDown?: (event: KeyboardEvent<E>) => void;
224
-
225
279
  /** @defaultValue `false` */
226
280
  disabled?: boolean;
227
281
 
@@ -262,13 +316,24 @@ export interface KeyboardMovementProviderOptions<E extends HTMLElement>
262
316
  * @defaultValue `false`
263
317
  */
264
318
  isNegativeOneAllowed?: boolean;
319
+
320
+ /**
321
+ * This was added to support spinbutton groups so the user can either use the
322
+ * ArrowLeft, ArrowRight, or Tab keys to move. Without this, switching
323
+ * between tab and the arrow keys would have the wrong tab index.
324
+ *
325
+ * @since 6.3.0
326
+ * @defaultValue `false`
327
+ */
328
+ trackTabKeys?: boolean;
265
329
  }
266
330
 
267
331
  /**
268
332
  * @since 6.0.0
269
333
  * @internal
270
334
  */
271
- export interface KeyboardMovementProps<E extends HTMLElement> {
335
+ export interface KeyboardMovementProps<E extends HTMLElement>
336
+ extends Required<KeyboardMovementEventHandlers<E>> {
272
337
  /**
273
338
  * This will only be provided if the {@link KeyboardMovementContext.tabIndexBehavior}
274
339
  * is set to `"virtual"`.
@@ -285,9 +350,8 @@ export interface KeyboardMovementProps<E extends HTMLElement> {
285
350
  * - a child element **should** have a `tabIndex={0}` instead
286
351
  */
287
352
  tabIndex?: number;
288
- onClick: MouseEventHandler<E>;
289
- onFocus: FocusEventHandler<E>;
290
- onKeyDown: KeyboardEventHandler<E>;
353
+
354
+ ref: RefCallback<E>;
291
355
  }
292
356
 
293
357
  /**
@@ -295,6 +359,7 @@ export interface KeyboardMovementProps<E extends HTMLElement> {
295
359
  * @internal
296
360
  */
297
361
  export interface KeyboardMovementProviderImplementation<E extends HTMLElement> {
362
+ nodeRef: RefObject<E>;
298
363
  movementProps: Readonly<KeyboardMovementProps<E>>;
299
364
  movementContext: Readonly<KeyboardMovementContext>;
300
365
  currentFocusIndex: NonNullMutableRef<number>;