@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
@@ -2,6 +2,7 @@
2
2
 
3
3
  import {
4
4
  createContext,
5
+ useCallback,
5
6
  useContext,
6
7
  useEffect,
7
8
  useMemo,
@@ -12,6 +13,7 @@ import {
12
13
  import { getFocusableElements as defaultGetFocusableElements } from "../focus/utils.js";
13
14
  import { useUserInteractionMode } from "../interaction/UserInteractionModeProvider.js";
14
15
  import { useDir } from "../typography/WritingDirectionProvider.js";
16
+ import { useEnsuredRef } from "../useEnsuredRef.js";
15
17
  import { useIsomorphicLayoutEffect } from "../useIsomorphicLayoutEffect.js";
16
18
  import {
17
19
  DEFAULT_KEYBOARD_MOVEMENT,
@@ -19,12 +21,13 @@ import {
19
21
  DEFAULT_RTL_KEYBOARD_MOVEMENT,
20
22
  } from "./constants.js";
21
23
  import { findMatchIndex } from "./findMatchIndex.js";
22
- import type {
23
- KeyboardMovementConfig,
24
- KeyboardMovementConfiguration,
25
- KeyboardMovementContext,
26
- KeyboardMovementProviderImplementation,
27
- KeyboardMovementProviderOptions,
24
+ import {
25
+ type KeyboardFocusFromKeyOptions,
26
+ type KeyboardMovementConfig,
27
+ type KeyboardMovementConfiguration,
28
+ type KeyboardMovementContext,
29
+ type KeyboardMovementProviderImplementation,
30
+ type KeyboardMovementProviderOptions,
28
31
  } from "./types.js";
29
32
  import {
30
33
  getFirstFocusableIndex,
@@ -38,19 +41,36 @@ import {
38
41
  recalculateFocusIndex,
39
42
  } from "./utils.js";
40
43
 
44
+ const noop = (): void => {
45
+ // do nothing
46
+ };
47
+
48
+ /**
49
+ * @since 6.3.0
50
+ */
51
+ export const DEFAULT_KEYBOARD_MOVEMENT_CONTEXT: Readonly<KeyboardMovementContext> =
52
+ {
53
+ config: { current: DEFAULT_KEYBOARD_MOVEMENT },
54
+ loopable: false,
55
+ searchable: false,
56
+ horizontal: false,
57
+ includeDisabled: false,
58
+ tabIndexBehavior: undefined,
59
+ activeDescendantId: "",
60
+ focusFirst: noop,
61
+ focusLast: noop,
62
+ focusNext: noop,
63
+ focusPrevious: noop,
64
+ focusFromKey: noop,
65
+ };
66
+
41
67
  /**
42
68
  * @since 5.0.0
43
69
  * @internal
44
70
  */
45
- const context = createContext<KeyboardMovementContext>({
46
- config: { current: DEFAULT_KEYBOARD_MOVEMENT },
47
- loopable: false,
48
- searchable: false,
49
- horizontal: false,
50
- includeDisabled: false,
51
- tabIndexBehavior: undefined,
52
- activeDescendantId: "",
53
- });
71
+ const context = createContext<KeyboardMovementContext>(
72
+ DEFAULT_KEYBOARD_MOVEMENT_CONTEXT
73
+ );
54
74
  context.displayName = "KeyboardMovement";
55
75
  export const { Provider: KeyboardMovementProvider } = context;
56
76
 
@@ -62,10 +82,6 @@ export function useKeyboardMovementContext(): Readonly<KeyboardMovementContext>
62
82
  return useContext(context);
63
83
  }
64
84
 
65
- const noop = (): void => {
66
- // do nothing
67
- };
68
-
69
85
  const returnNegative1 = (): number => -1;
70
86
 
71
87
  /**
@@ -193,6 +209,7 @@ export function useKeyboardMovementProvider<E extends HTMLElement>(
193
209
  options: KeyboardMovementProviderOptions<E> = {}
194
210
  ): KeyboardMovementProviderImplementation<E> {
195
211
  const {
212
+ ref: propRef,
196
213
  onClick = noop,
197
214
  onFocus = noop,
198
215
  onKeyDown = noop,
@@ -200,6 +217,7 @@ export function useKeyboardMovementProvider<E extends HTMLElement>(
200
217
  disabled,
201
218
  searchable = false,
202
219
  horizontal = false,
220
+ trackTabKeys = false,
203
221
  includeDisabled = false,
204
222
  tabIndexBehavior,
205
223
  extendKeyDown = noop,
@@ -214,6 +232,8 @@ export function useKeyboardMovementProvider<E extends HTMLElement>(
214
232
  isNegativeOneAllowed = false,
215
233
  } = options;
216
234
 
235
+ const [nodeRef, nodeRefCallback] = useEnsuredRef(propRef);
236
+
217
237
  const isRTL = useDir().dir === "rtl";
218
238
  let defaults: Readonly<Required<KeyboardMovementConfiguration>>;
219
239
  if (horizontal) {
@@ -240,29 +260,10 @@ export function useKeyboardMovementProvider<E extends HTMLElement>(
240
260
  config.current = configuration;
241
261
  });
242
262
 
243
- const [activeDescendantId, setActiveDescendantId] = useState("");
244
- const movementContext = useMemo<KeyboardMovementContext>(
245
- () => ({
246
- config,
247
- loopable,
248
- searchable,
249
- horizontal,
250
- includeDisabled,
251
- tabIndexBehavior,
252
- activeDescendantId,
253
- }),
254
- [
255
- activeDescendantId,
256
- horizontal,
257
- includeDisabled,
258
- loopable,
259
- searchable,
260
- tabIndexBehavior,
261
- ]
262
- );
263
- const currentFocusIndex = useRef(-1);
264
263
  const mode = useUserInteractionMode();
265
264
  const refocus = useRef(false);
265
+ const currentFocusIndex = useRef(-1);
266
+ const [activeDescendantId, setActiveDescendantId] = useState("");
266
267
 
267
268
  if (process.env.NODE_ENV !== "production") {
268
269
  // this fixes issues during hot reloading and using the `useId()` hook
@@ -282,10 +283,155 @@ export function useKeyboardMovementProvider<E extends HTMLElement>(
282
283
  : 0;
283
284
  }
284
285
 
286
+ const getFocusableElementsFromRef = useCallback(() => {
287
+ const container = nodeRef.current;
288
+ if (!container) {
289
+ return [];
290
+ }
291
+
292
+ return getFocusableElements(container, programmatic);
293
+ }, [getFocusableElements, nodeRef, programmatic]);
294
+ const updateFocusIndex = useCallback(
295
+ (index: number, focusables = getFocusableElementsFromRef()) => {
296
+ if (currentFocusIndex.current === index || index === -1) {
297
+ return;
298
+ }
299
+
300
+ currentFocusIndex.current = index;
301
+ const focused = focusables[index];
302
+ if (tabIndexBehavior) {
303
+ focused.scrollIntoView({
304
+ block: "nearest",
305
+ inline: "nearest",
306
+ });
307
+ setActiveDescendantId(focused.id);
308
+ }
309
+
310
+ if (tabIndexBehavior !== "virtual") {
311
+ focused.focus();
312
+ }
313
+
314
+ onFocusChange({
315
+ index,
316
+ element: focused,
317
+ });
318
+ },
319
+ [getFocusableElementsFromRef, onFocusChange, tabIndexBehavior]
320
+ );
321
+
322
+ const focusNext = useCallback(
323
+ (focusables = getFocusableElementsFromRef()) => {
324
+ updateFocusIndex(
325
+ getNextFocusableIndex({
326
+ loopable,
327
+ increment: true,
328
+ focusables,
329
+ includeDisabled: true,
330
+ currentFocusIndex: currentFocusIndex.current,
331
+ }),
332
+ focusables
333
+ );
334
+ },
335
+ [getFocusableElementsFromRef, loopable, updateFocusIndex]
336
+ );
337
+ const focusPrevious = useCallback(
338
+ (focusables = getFocusableElementsFromRef()) => {
339
+ updateFocusIndex(
340
+ getNextFocusableIndex({
341
+ loopable,
342
+ increment: false,
343
+ focusables,
344
+ includeDisabled: true,
345
+ currentFocusIndex: currentFocusIndex.current,
346
+ }),
347
+ focusables
348
+ );
349
+ },
350
+ [getFocusableElementsFromRef, loopable, updateFocusIndex]
351
+ );
352
+ const focusFirst = useCallback(
353
+ (focusables = getFocusableElementsFromRef()) => {
354
+ updateFocusIndex(
355
+ getFirstFocusableIndex({
356
+ focusables,
357
+ includeDisabled,
358
+ }),
359
+ focusables
360
+ );
361
+ },
362
+ [getFocusableElementsFromRef, includeDisabled, updateFocusIndex]
363
+ );
364
+ const focusLast = useCallback(
365
+ (focusables = getFocusableElementsFromRef()) => {
366
+ updateFocusIndex(
367
+ getLastFocusableIndex({
368
+ focusables,
369
+ includeDisabled,
370
+ }),
371
+ focusables
372
+ );
373
+ },
374
+ [getFocusableElementsFromRef, includeDisabled, updateFocusIndex]
375
+ );
376
+ const focusFromKey = useCallback(
377
+ (options: KeyboardFocusFromKeyOptions) => {
378
+ const {
379
+ key,
380
+ reversed,
381
+ focusables = getFocusableElementsFromRef(),
382
+ } = options;
383
+ if (!searchable) {
384
+ return;
385
+ }
386
+
387
+ const index = findMatchIndex({
388
+ value: key,
389
+ values: focusables.map((element) =>
390
+ getSearchText(element, !isNotFocusable(element, includeDisabled))
391
+ ),
392
+ startIndex: reversed ? -1 : currentFocusIndex.current,
393
+ });
394
+ updateFocusIndex(index, focusables);
395
+ },
396
+ [getFocusableElementsFromRef, includeDisabled, searchable, updateFocusIndex]
397
+ );
398
+
399
+ const movementContext = useMemo<KeyboardMovementContext>(
400
+ () => ({
401
+ config,
402
+ loopable,
403
+ searchable,
404
+ horizontal,
405
+ focusFirst,
406
+ focusLast,
407
+ focusNext,
408
+ focusPrevious,
409
+ focusFromKey,
410
+ includeDisabled,
411
+ tabIndexBehavior,
412
+ activeDescendantId,
413
+ }),
414
+ [
415
+ activeDescendantId,
416
+ focusFirst,
417
+ focusFromKey,
418
+ focusLast,
419
+ focusNext,
420
+ focusPrevious,
421
+ horizontal,
422
+ includeDisabled,
423
+ loopable,
424
+ searchable,
425
+ tabIndexBehavior,
426
+ ]
427
+ );
428
+
285
429
  return {
430
+ nodeRef,
286
431
  movementProps: {
287
432
  "aria-activedescendant":
288
433
  tabIndexBehavior === "virtual" ? activeDescendantId : undefined,
434
+ ref: nodeRefCallback,
289
435
  tabIndex,
290
436
 
291
437
  // Note: This used to be on the `onFocus` event, but this causes issues in
@@ -411,28 +557,7 @@ export function useKeyboardMovementProvider<E extends HTMLElement>(
411
557
  ): void => {
412
558
  event.preventDefault();
413
559
  event.stopPropagation();
414
- if (currentFocusIndex.current === index || index === -1) {
415
- return;
416
- }
417
-
418
- currentFocusIndex.current = index;
419
- const focused = focusables[index];
420
- if (tabIndexBehavior) {
421
- focused.scrollIntoView({
422
- block: "nearest",
423
- inline: "nearest",
424
- });
425
- setActiveDescendantId(focused.id);
426
- }
427
-
428
- if (tabIndexBehavior !== "virtual") {
429
- focused.focus();
430
- }
431
-
432
- onFocusChange({
433
- index,
434
- element: focused,
435
- });
560
+ updateFocusIndex(index, focusables);
436
561
  };
437
562
 
438
563
  extendKeyDown({
@@ -487,15 +612,14 @@ export function useKeyboardMovementProvider<E extends HTMLElement>(
487
612
  } = config.current;
488
613
 
489
614
  if (searchable && isSearchableEvent(event)) {
490
- const focusables = getFocusableElements(currentTarget, programmatic);
491
- const index = findMatchIndex({
492
- value: key,
493
- values: focusables.map((element) =>
494
- getSearchText(element, !isNotFocusable(element, includeDisabled))
495
- ),
496
- startIndex: shiftKey ? -1 : currentFocusIndex.current,
497
- });
498
- setFocusIndex(index, focusables);
615
+ event.preventDefault();
616
+ event.stopPropagation();
617
+ focusFromKey({ key, reversed: shiftKey });
618
+ return;
619
+ }
620
+
621
+ if (trackTabKeys && key === "Tab") {
622
+ currentFocusIndex.current += event.shiftKey ? -1 : 1;
499
623
  return;
500
624
  }
501
625
 
@@ -509,33 +633,23 @@ export function useKeyboardMovementProvider<E extends HTMLElement>(
509
633
  !increment &&
510
634
  decrementKeys.includes(key);
511
635
 
512
- if (!jumpToFirst && !jumpToLast && !increment && !decrement) {
513
- return;
514
- }
515
- const focusables = getFocusableElements(currentTarget, programmatic);
516
-
517
- let index: number;
518
636
  if (jumpToFirst) {
519
- index = getFirstFocusableIndex({
520
- focusables,
521
- includeDisabled,
522
- });
637
+ event.preventDefault();
638
+ event.stopPropagation();
639
+ focusFirst();
523
640
  } else if (jumpToLast) {
524
- index = getLastFocusableIndex({
525
- focusables,
526
- includeDisabled,
527
- });
528
- } else {
529
- index = getNextFocusableIndex({
530
- loopable,
531
- increment,
532
- focusables,
533
- includeDisabled,
534
- currentFocusIndex: currentFocusIndex.current,
535
- });
641
+ event.preventDefault();
642
+ event.stopPropagation();
643
+ focusLast();
644
+ } else if (increment) {
645
+ event.preventDefault();
646
+ event.stopPropagation();
647
+ focusNext();
648
+ } else if (decrement) {
649
+ event.preventDefault();
650
+ event.stopPropagation();
651
+ focusPrevious();
536
652
  }
537
-
538
- setFocusIndex(index, focusables);
539
653
  },
540
654
  },
541
655
  movementContext,
@@ -176,6 +176,7 @@ export function useTabList(
176
176
  const forwardRef = useRef<HTMLDivElement>(null);
177
177
  const backwardRef = useRef<HTMLDivElement>(null);
178
178
  const { movementProps, movementContext } = useKeyboardMovementProvider({
179
+ ref: tabListRef,
179
180
  onClick(event) {
180
181
  onClick(event);
181
182
  if (event.isPropagationStopped() || !(event.target instanceof Element)) {
@@ -226,7 +227,6 @@ export function useTabList(
226
227
  return {
227
228
  elementProps: {
228
229
  "aria-orientation": vertical ? "vertical" : "horizontal",
229
- ref: tabListRef,
230
230
  style: {
231
231
  ...style,
232
232
  ...(disableTransition ? undefined : indicatorStyles),
@@ -1,25 +1,21 @@
1
1
  import { fireEvent } from "@testing-library/dom";
2
2
  import type { MouseEvent } from "react";
3
3
 
4
+ import { type Point } from "../types.js";
4
5
  import { wait } from "../utils/wait.js";
5
6
 
6
- interface XYCoords {
7
- x: number;
8
- y: number;
9
- }
10
-
11
7
  interface BaseDragOptions {
12
- to?: XYCoords | Element;
13
- from?: XYCoords;
14
- delta?: XYCoords;
8
+ to?: Point | Element;
9
+ from?: Point;
10
+ delta?: Point;
15
11
  steps?: number;
16
12
  duration?: number;
17
13
  }
18
14
 
19
15
  type DragOptions = BaseDragOptions &
20
- ({ to: XYCoords | Element; delta?: never } | { delta: XYCoords; to?: never });
16
+ ({ to: Point | Element; delta?: never } | { delta: Point; to?: never });
21
17
 
22
- const getElementClientCenter = (element: Element): XYCoords => {
18
+ const getElementClientCenter = (element: Element): Point => {
23
19
  const { left, top, width, height } = element.getBoundingClientRect();
24
20
  return {
25
21
  x: left + width / 2,
@@ -27,7 +23,7 @@ const getElementClientCenter = (element: Element): XYCoords => {
27
23
  };
28
24
  };
29
25
 
30
- const getCoords = (elementOrCoords: Element | XYCoords): XYCoords =>
26
+ const getCoords = (elementOrCoords: Element | Point): Point =>
31
27
  "x" in elementOrCoords && "y" in elementOrCoords
32
28
  ? elementOrCoords
33
29
  : getElementClientCenter(elementOrCoords);
@@ -58,7 +54,7 @@ export async function drag(
58
54
  }
59
55
  : getCoords(inTo);
60
56
 
61
- const step: XYCoords = {
57
+ const step: Point = {
62
58
  x: (to.x - from.x) / steps,
63
59
  y: (to.y - from.y) / steps,
64
60
  };
@@ -2,10 +2,10 @@
2
2
 
3
3
  import { useCallback, useEffect, useState } from "react";
4
4
 
5
- import type { UseStateSetter } from "../types.js";
5
+ import { type UseStateSetter } from "../types.js";
6
6
  import { useToggle } from "../useToggle.js";
7
7
  import { loop } from "../utils/loop.js";
8
- import type { SlideDirection } from "./SlideContainer.js";
8
+ import { type SlideDirection } from "./SlideContainer.js";
9
9
 
10
10
  /** @since 6.0.0 */
11
11
  export interface CarouselSlideState {
package/src/tree/Tree.tsx CHANGED
@@ -270,6 +270,7 @@ export function Tree<T extends TreeItemNode>(
270
270
  });
271
271
 
272
272
  const { metadataLookup, movementContext, movementProps } = useTreeMovement({
273
+ ref: treeRef,
273
274
  onClick,
274
275
  onFocus,
275
276
  onKeyDown,
@@ -305,7 +306,6 @@ export function Tree<T extends TreeItemNode>(
305
306
  {...remaining}
306
307
  {...movementProps}
307
308
  id={treeId}
308
- ref={treeRef}
309
309
  role="tree"
310
310
  tabIndex={0}
311
311
  className={tree({ className })}
@@ -4,6 +4,7 @@ import type {
4
4
  FocusEventHandler,
5
5
  KeyboardEventHandler,
6
6
  MouseEventHandler,
7
+ Ref,
7
8
  } from "react";
8
9
  import { useRef } from "react";
9
10
 
@@ -59,6 +60,7 @@ const getVisibleTreeItems = (
59
60
  * @internal
60
61
  */
61
62
  interface TreeMovementOptions<T extends TreeItemNode> extends TreeExpansion {
63
+ ref?: Ref<HTMLUListElement>;
62
64
  data: TreeData<T>;
63
65
  onClick: MouseEventHandler<HTMLUListElement> | undefined;
64
66
  onFocus: FocusEventHandler<HTMLUListElement> | undefined;
@@ -88,6 +90,7 @@ export function useTreeMovement<T extends TreeItemNode>(
88
90
  options: TreeMovementOptions<T>
89
91
  ): TreeMovement {
90
92
  const {
93
+ ref,
91
94
  onClick,
92
95
  onFocus,
93
96
  onKeyDown,
@@ -106,6 +109,7 @@ export function useTreeMovement<T extends TreeItemNode>(
106
109
  itemToElement: {},
107
110
  });
108
111
  const movement = useKeyboardMovementProvider({
112
+ ref,
109
113
  onClick,
110
114
  onFocus,
111
115
  onKeyDown,
package/src/types.ts CHANGED
@@ -227,6 +227,22 @@ export interface ElementSize {
227
227
  width: number;
228
228
  }
229
229
 
230
+ /**
231
+ * @since 6.3.0
232
+ */
233
+ export interface Point {
234
+ x: number;
235
+ y: number;
236
+ }
237
+
238
+ /**
239
+ * @since 6.3.0
240
+ */
241
+ export interface MinMaxRange {
242
+ min: number;
243
+ max: number;
244
+ }
245
+
230
246
  /**
231
247
  * @since 6.2.0
232
248
  */
@@ -1,12 +1,11 @@
1
+ import { type MinMaxRange } from "../types.js";
1
2
  import { getRangeSteps } from "./getRangeSteps.js";
2
3
  import { nearest } from "./nearest.js";
3
4
 
4
5
  /**
5
6
  * @since 6.0.0
6
7
  */
7
- export interface GetMiddleOfRangeOptions {
8
- min: number;
9
- max: number;
8
+ export interface GetMiddleOfRangeOptions extends MinMaxRange {
10
9
  step: number;
11
10
  }
12
11
 
@@ -1,15 +1,7 @@
1
- /** @since 4.0.1 */
2
- export interface GetPercentageOptions {
3
- /**
4
- * The min value allowed.
5
- */
6
- min: number;
7
-
8
- /**
9
- * The max value allowed.
10
- */
11
- max: number;
1
+ import { type MinMaxRange } from "../types.js";
12
2
 
3
+ /** @since 4.0.1 */
4
+ export interface GetPercentageOptions extends MinMaxRange {
13
5
  /**
14
6
  * The current value
15
7
  */
@@ -1,10 +1,10 @@
1
+ import { type MinMaxRange } from "../types.js";
2
+
1
3
  /**
2
4
  * @internal
3
5
  * @since 6.0.0
4
6
  */
5
- export interface RangeStepsOptions {
6
- min: number;
7
- max: number;
7
+ export interface RangeStepsOptions extends MinMaxRange {
8
8
  step: number;
9
9
  }
10
10
 
@@ -1,10 +1,10 @@
1
+ import { type MinMaxRange } from "../types.js";
2
+
1
3
  /**
2
4
  * @internal
3
5
  * @since 6.0.0
4
6
  */
5
- export interface NearestOptions {
6
- min: number;
7
- max: number;
7
+ export interface NearestOptions extends MinMaxRange {
8
8
  steps: number;
9
9
  value: number;
10
10
 
@@ -0,0 +1,46 @@
1
+ import { type Point } from "../types.js";
2
+
3
+ /**
4
+ * @since 6.3.0
5
+ * @internal
6
+ */
7
+ export const radiansToDegrees = (radians: number): number =>
8
+ (radians * 180) / Math.PI;
9
+
10
+ /**
11
+ * @since 6.3.0
12
+ * @internal
13
+ */
14
+ export const degreesToRadians = (degrees: number): number =>
15
+ (degrees * Math.PI) / 180;
16
+
17
+ /**
18
+ * @since 6.3.0
19
+ * @internal
20
+ */
21
+ interface IsPointInCircleOptions {
22
+ point: Point;
23
+ center: Point;
24
+ radius: number;
25
+ }
26
+
27
+ /**
28
+ * @since 6.3.0
29
+ * @internal
30
+ */
31
+ export function isPointInCircle(options: IsPointInCircleOptions): boolean {
32
+ const { point, center, radius } = options;
33
+
34
+ const distance = (center.x - point.x) ** 2 + (center.y - point.y) ** 2;
35
+ return distance <= radius ** 2;
36
+ }
37
+
38
+ /**
39
+ * @internal
40
+ * @since 6.3.0
41
+ */
42
+ export function calcHypotenuse(point: Point): number {
43
+ const { x, y } = point;
44
+
45
+ return Math.sqrt(x * x + y * y);
46
+ }
@@ -42,8 +42,9 @@ export type WindowSplitterOptions<E extends HTMLElement = HTMLButtonElement> =
42
42
  /**
43
43
  * @since 6.0.0
44
44
  */
45
- export interface WindowSplitterWidgetProps<E extends HTMLElement>
46
- extends Required<DraggableMouseEventHandlers<E>>,
45
+ export interface WindowSplitterWidgetProps<
46
+ E extends HTMLElement = HTMLButtonElement,
47
+ > extends Required<DraggableMouseEventHandlers<E>>,
47
48
  Required<DraggableKeyboardEventHandlers<E>> {
48
49
  "aria-orientation": "vertical" | undefined;
49
50
  "aria-valuenow": number;