@octanejs/aria 0.0.46 → 0.0.47

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 (201) hide show
  1. package/README.md +7 -6
  2. package/UPSTREAM.md +58 -69
  3. package/package.json +11 -11
  4. package/src/calendar/useCalendarGrid.tsx +64 -62
  5. package/src/collections/Document.ts +3 -0
  6. package/src/color/useColorArea.tsx +95 -69
  7. package/src/color/useColorWheel.tsx +38 -37
  8. package/src/combobox/{useComboBox.ts → useComboBox.tsx} +239 -259
  9. package/src/components/ComboBox.ts +35 -2
  10. package/src/components/Dialog.ts +12 -1
  11. package/src/components/DropZone.tsx +3 -2
  12. package/src/components/FileTrigger.tsx +1 -0
  13. package/src/components/GridList.ts +11 -2
  14. package/src/components/ListBox.ts +1 -1
  15. package/src/components/Menu.ts +1 -0
  16. package/src/components/Meter.ts +3 -1
  17. package/src/components/Popover.ts +20 -8
  18. package/src/components/PreviewTrigger.tsx +101 -0
  19. package/src/components/ProgressBar.ts +10 -2
  20. package/src/components/Select.ts +1 -1
  21. package/src/components/Table.ts +26 -15
  22. package/src/components/TokenField.tsx +395 -0
  23. package/src/components/Tooltip.ts +4 -6
  24. package/src/components/Tree.ts +14 -7
  25. package/src/components/Virtualizer.tsx +30 -14
  26. package/src/components/index.ts +22 -0
  27. package/src/components/utils.ts +1 -1
  28. package/src/datepicker/useDateField.tsx +2 -11
  29. package/src/datepicker/useDatePickerGroup.tsx +44 -33
  30. package/src/datepicker/useDateSegment.tsx +31 -30
  31. package/src/dialog/useDialog.ts +8 -1
  32. package/src/dnd/DragManager.tsx +5 -1
  33. package/src/dnd/useDrag.tsx +2 -1
  34. package/src/focus/FocusScope.ts +2 -2
  35. package/src/grid/useGrid.ts +3 -0
  36. package/src/grid/{useGridCell.ts → useGridCell.tsx} +121 -64
  37. package/src/grid/utils.ts +1 -0
  38. package/src/gridlist/{useGridListItem.ts → useGridListItem.tsx} +152 -111
  39. package/src/index.ts +22 -0
  40. package/src/interactions/createEventHandler.ts +11 -8
  41. package/src/interactions/createKeyboardShortcutHandler.tsx +218 -0
  42. package/src/interactions/textSelection.ts +3 -3
  43. package/src/interactions/useContextMenu.tsx +125 -0
  44. package/src/interactions/useFocusVisible.ts +4 -4
  45. package/src/interactions/useKeyboard.ts +50 -2
  46. package/src/interactions/useLongPress.ts +33 -22
  47. package/src/interactions/useMove.ts +6 -6
  48. package/src/interactions/usePress.ts +8 -0
  49. package/src/interactions/utils.ts +10 -10
  50. package/src/intl/combobox/fi-FI.json +2 -2
  51. package/src/intl/previewtrigger/ar-AE.json +3 -0
  52. package/src/intl/previewtrigger/bg-BG.json +3 -0
  53. package/src/intl/previewtrigger/cs-CZ.json +3 -0
  54. package/src/intl/previewtrigger/da-DK.json +3 -0
  55. package/src/intl/previewtrigger/de-DE.json +3 -0
  56. package/src/intl/previewtrigger/el-GR.json +3 -0
  57. package/src/intl/previewtrigger/en-US.json +3 -0
  58. package/src/intl/previewtrigger/es-ES.json +3 -0
  59. package/src/intl/previewtrigger/et-EE.json +3 -0
  60. package/src/intl/previewtrigger/fi-FI.json +3 -0
  61. package/src/intl/previewtrigger/fr-FR.json +3 -0
  62. package/src/intl/previewtrigger/he-IL.json +3 -0
  63. package/src/intl/previewtrigger/hr-HR.json +3 -0
  64. package/src/intl/previewtrigger/hu-HU.json +3 -0
  65. package/src/intl/previewtrigger/index.ts +73 -0
  66. package/src/intl/previewtrigger/it-IT.json +3 -0
  67. package/src/intl/previewtrigger/ja-JP.json +3 -0
  68. package/src/intl/previewtrigger/ko-KR.json +3 -0
  69. package/src/intl/previewtrigger/lt-LT.json +3 -0
  70. package/src/intl/previewtrigger/lv-LV.json +3 -0
  71. package/src/intl/previewtrigger/nb-NO.json +3 -0
  72. package/src/intl/previewtrigger/nl-NL.json +3 -0
  73. package/src/intl/previewtrigger/pl-PL.json +3 -0
  74. package/src/intl/previewtrigger/pt-BR.json +3 -0
  75. package/src/intl/previewtrigger/pt-PT.json +3 -0
  76. package/src/intl/previewtrigger/ro-RO.json +3 -0
  77. package/src/intl/previewtrigger/ru-RU.json +3 -0
  78. package/src/intl/previewtrigger/sk-SK.json +3 -0
  79. package/src/intl/previewtrigger/sl-SI.json +3 -0
  80. package/src/intl/previewtrigger/sr-SP.json +3 -0
  81. package/src/intl/previewtrigger/sv-SE.json +3 -0
  82. package/src/intl/previewtrigger/tr-TR.json +3 -0
  83. package/src/intl/previewtrigger/uk-UA.json +3 -0
  84. package/src/intl/previewtrigger/zh-CN.json +3 -0
  85. package/src/intl/previewtrigger/zh-TW.json +3 -0
  86. package/src/intl/table/ar-AE.json +3 -3
  87. package/src/intl/table/bg-BG.json +3 -3
  88. package/src/intl/table/cs-CZ.json +3 -3
  89. package/src/intl/table/da-DK.json +3 -3
  90. package/src/intl/table/de-DE.json +3 -3
  91. package/src/intl/table/el-GR.json +3 -3
  92. package/src/intl/table/es-ES.json +3 -3
  93. package/src/intl/table/et-EE.json +3 -3
  94. package/src/intl/table/fi-FI.json +3 -3
  95. package/src/intl/table/fr-FR.json +3 -3
  96. package/src/intl/table/he-IL.json +3 -3
  97. package/src/intl/table/hr-HR.json +3 -3
  98. package/src/intl/table/hu-HU.json +3 -3
  99. package/src/intl/table/it-IT.json +3 -3
  100. package/src/intl/table/ja-JP.json +3 -3
  101. package/src/intl/table/ko-KR.json +3 -3
  102. package/src/intl/table/lt-LT.json +3 -3
  103. package/src/intl/table/lv-LV.json +3 -3
  104. package/src/intl/table/nb-NO.json +3 -3
  105. package/src/intl/table/nl-NL.json +3 -3
  106. package/src/intl/table/pl-PL.json +3 -3
  107. package/src/intl/table/pt-BR.json +3 -3
  108. package/src/intl/table/pt-PT.json +3 -3
  109. package/src/intl/table/ro-RO.json +3 -3
  110. package/src/intl/table/ru-RU.json +3 -3
  111. package/src/intl/table/sk-SK.json +3 -3
  112. package/src/intl/table/sl-SI.json +3 -3
  113. package/src/intl/table/sr-SP.json +3 -3
  114. package/src/intl/table/sv-SE.json +3 -3
  115. package/src/intl/table/tr-TR.json +3 -3
  116. package/src/intl/table/uk-UA.json +3 -3
  117. package/src/intl/table/zh-CN.json +4 -4
  118. package/src/intl/table/zh-TW.json +3 -3
  119. package/src/intl/tree/es-ES.json +1 -1
  120. package/src/link/useLink.ts +2 -0
  121. package/src/menu/useMenuItem.ts +33 -36
  122. package/src/menu/useMenuTrigger.tsx +217 -0
  123. package/src/menu/{useSubmenuTrigger.ts → useSubmenuTrigger.tsx} +112 -152
  124. package/src/numberfield/{useNumberField.ts → useNumberField.tsx} +185 -239
  125. package/src/overlays/Overlay.ts +5 -1
  126. package/src/overlays/ariaHideOutside.ts +6 -8
  127. package/src/overlays/useCloseOnScroll.ts +7 -5
  128. package/src/overlays/{useOverlay.ts → useOverlay.tsx} +72 -84
  129. package/src/overlays/usePopover.ts +10 -5
  130. package/src/overlays/usePreventScroll.ts +4 -4
  131. package/src/progress/useProgressBar.ts +2 -1
  132. package/src/radio/{useRadioGroup.ts → useRadioGroup.tsx} +83 -80
  133. package/src/searchfield/useSearchField.tsx +147 -0
  134. package/src/select/useSelect.ts +20 -20
  135. package/src/selection/useSelectableCollection.tsx +751 -0
  136. package/src/selection/useSelectableItem.ts +48 -11
  137. package/src/slider/{useSliderThumb.ts → useSliderThumb.tsx} +116 -159
  138. package/src/spinbutton/useSpinButton.tsx +330 -0
  139. package/src/stately/combobox/useComboBoxState.ts +1 -1
  140. package/src/stately/grid/useGridState.ts +9 -10
  141. package/src/stately/index.ts +11 -0
  142. package/src/stately/menu/useMenuTriggerState.ts +1 -1
  143. package/src/stately/menu/useSubmenuTriggerState.ts +2 -0
  144. package/src/stately/overlays/useOverlayTriggerState.ts +12 -1
  145. package/src/stately/radio/useRadioGroupState.ts +5 -1
  146. package/src/stately/table/TableUtils.ts +15 -1
  147. package/src/stately/table/useTableState.ts +7 -0
  148. package/src/stately/tokenfield/TokenFieldValue.tsx +440 -0
  149. package/src/stately/tokenfield/useTokenFieldState.tsx +57 -0
  150. package/src/stately/tooltip/useTooltipTriggerState.ts +16 -7
  151. package/src/stately/virtualizer/Layout.tsx +2 -0
  152. package/src/stately/virtualizer/ScrollAnchor.tsx +338 -0
  153. package/src/stately/virtualizer/Virtualizer.tsx +52 -1
  154. package/src/stately/virtualizer/types.tsx +2 -0
  155. package/src/table/useTableCell.ts +12 -0
  156. package/src/table/useTableColumnHeader.ts +8 -1
  157. package/src/table/useTableColumnResize.tsx +354 -0
  158. package/src/tag/useTag.ts +22 -15
  159. package/src/textfield/useTextField.ts +2 -0
  160. package/src/tokenfield/useToken.tsx +64 -0
  161. package/src/tokenfield/useTokenField.tsx +849 -0
  162. package/src/tooltip/usePreviewTrigger.tsx +266 -0
  163. package/src/tooltip/useSafeArea.tsx +171 -0
  164. package/src/upstream-exports/react-aria/mergeRefs.tsx +3 -0
  165. package/src/upstream-exports/react-aria/usePreviewTrigger.tsx +18 -0
  166. package/src/upstream-exports/react-aria/useTokenField.tsx +22 -0
  167. package/src/upstream-exports/react-stately/private/collections/getChildNodes.tsx +8 -0
  168. package/src/upstream-exports/react-stately/private/collections/getItemCount.tsx +2 -0
  169. package/src/upstream-exports/react-stately/private/grid/GridCollection.tsx +7 -0
  170. package/src/upstream-exports/react-stately/private/grid/useGridState.tsx +6 -0
  171. package/src/upstream-exports/react-stately/private/utils/number.tsx +2 -0
  172. package/src/upstream-exports/react-stately/useComboBoxState.tsx +23 -0
  173. package/src/upstream-exports/react-stately/useListState.tsx +15 -0
  174. package/src/upstream-exports/react-stately/useMenuTriggerState.tsx +26 -0
  175. package/src/upstream-exports/react-stately/useMultipleSelectionState.tsx +21 -0
  176. package/src/upstream-exports/react-stately/useNumberFieldState.tsx +20 -0
  177. package/src/upstream-exports/react-stately/useOverlayTriggerState.tsx +20 -0
  178. package/src/upstream-exports/react-stately/useRadioGroupState.tsx +16 -0
  179. package/src/upstream-exports/react-stately/useSearchFieldState.tsx +19 -0
  180. package/src/upstream-exports/react-stately/useSliderState.tsx +20 -0
  181. package/src/upstream-exports/react-stately/useTableState.tsx +45 -0
  182. package/src/upstream-exports/react-stately/useTokenFieldState.tsx +25 -0
  183. package/src/upstream-exports/react-stately/useTooltipTriggerState.tsx +20 -0
  184. package/src/upstream-exports/react-stately/useTreeState.tsx +14 -0
  185. package/src/utils/domHelpers.ts +74 -17
  186. package/src/utils/isVirtualEvent.ts +2 -1
  187. package/src/utils/openLink.ts +8 -3
  188. package/src/utils/platform.ts +7 -3
  189. package/src/utils/scrollIntoView.ts +4 -4
  190. package/src/utils/shadowdom/DOMFunctions.ts +42 -2
  191. package/src/utils/useId.ts +11 -2
  192. package/src/utils/useLoadMoreSentinel.ts +8 -3
  193. package/src/utils/useViewportSize.ts +3 -3
  194. package/src/virtualizer/ScrollView.tsx +13 -4
  195. package/src/virtualizer/VirtualizerItem.tsx +3 -1
  196. package/src/virtualizer/useVirtualizerItem.tsx +43 -3
  197. package/src/menu/useMenuTrigger.ts +0 -189
  198. package/src/searchfield/useSearchField.ts +0 -156
  199. package/src/selection/useSelectableCollection.ts +0 -750
  200. package/src/spinbutton/useSpinButton.ts +0 -380
  201. package/src/table/useTableColumnResize.ts +0 -374
@@ -0,0 +1,19 @@
1
+ /** @jsxImportSource octane */
2
+ /*
3
+ * Copyright 2020 Adobe. All rights reserved.
4
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License. You may obtain a copy
6
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
7
+ *
8
+ * Unless required by applicable law or agreed to in writing, software distributed under
9
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
10
+ * OF ANY KIND, either express or implied. See the License for the specific language
11
+ * governing permissions and limitations under the License.
12
+ */
13
+
14
+ export { useSearchFieldState } from '../../stately/searchfield/useSearchFieldState';
15
+
16
+ export type {
17
+ SearchFieldProps,
18
+ SearchFieldState,
19
+ } from '../../stately/searchfield/useSearchFieldState';
@@ -0,0 +1,20 @@
1
+ /** @jsxImportSource octane */
2
+ /*
3
+ * Copyright 2020 Adobe. All rights reserved.
4
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License. You may obtain a copy
6
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
7
+ *
8
+ * Unless required by applicable law or agreed to in writing, software distributed under
9
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
10
+ * OF ANY KIND, either express or implied. See the License for the specific language
11
+ * governing permissions and limitations under the License.
12
+ */
13
+
14
+ export { useSliderState } from '../../stately/slider/useSliderState';
15
+
16
+ export type {
17
+ SliderProps,
18
+ SliderStateOptions,
19
+ SliderState,
20
+ } from '../../stately/slider/useSliderState';
@@ -0,0 +1,45 @@
1
+ /** @jsxImportSource octane */
2
+ /*
3
+ * Copyright 2020 Adobe. All rights reserved.
4
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License. You may obtain a copy
6
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
7
+ *
8
+ * Unless required by applicable law or agreed to in writing, software distributed under
9
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
10
+ * OF ANY KIND, either express or implied. See the License for the specific language
11
+ * governing permissions and limitations under the License.
12
+ */
13
+
14
+ export type {
15
+ TableColumnResizeState,
16
+ TableColumnResizeStateProps,
17
+ } from '../../stately/table/useTableColumnResizeState';
18
+ export type { TableProps, TableState, TableStateProps } from '../../stately/table/useTableState';
19
+
20
+ export { useTableColumnResizeState } from '../../stately/table/useTableColumnResizeState';
21
+ export { useTableState, UNSTABLE_useFilteredTableState } from '../../stately/table/useTableState';
22
+
23
+ export type { CellProps, CellElement, CellRenderer } from '../../stately/table/Cell';
24
+ export { Cell } from '../../stately/table/Cell';
25
+
26
+ export type {
27
+ ColumnProps,
28
+ ColumnSize,
29
+ ColumnDynamicSize,
30
+ ColumnStaticSize,
31
+ ColumnElement,
32
+ ColumnRenderer,
33
+ } from '../../stately/table/Column';
34
+ export { Column } from '../../stately/table/Column';
35
+
36
+ export type { RowProps, RowElement } from '../../stately/table/Row';
37
+ export { Row } from '../../stately/table/Row';
38
+
39
+ export type { TableBodyProps } from '../../stately/table/TableBody';
40
+ export { TableBody } from '../../stately/table/TableBody';
41
+
42
+ export type { TableHeaderProps } from '../../stately/table/TableHeader';
43
+ export { TableHeader } from '../../stately/table/TableHeader';
44
+
45
+ export type { SortDescriptor, SortDirection } from '@react-types/shared';
@@ -0,0 +1,25 @@
1
+ /** @jsxImportSource octane */
2
+ // Ported from adobe/react-spectrum@5ecb3333001313e83898cd07644227897e3bae1f (packages/react-stately/exports/useTokenFieldState.ts).
3
+ /*
4
+ * Copyright 2026 Adobe. All rights reserved.
5
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License. You may obtain a copy
7
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
8
+ *
9
+ * Unless required by applicable law or agreed to in writing, software distributed under
10
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
11
+ * OF ANY KIND, either express or implied. See the License for the specific language
12
+ * governing permissions and limitations under the License.
13
+ */
14
+
15
+ export { useTokenFieldState } from '../../stately/tokenfield/useTokenFieldState';
16
+ export { TokenFieldValue } from '../../stately/tokenfield/TokenFieldValue';
17
+
18
+ export type { TokenFieldProps, TokenFieldState } from '../../stately/tokenfield/useTokenFieldState';
19
+ export type {
20
+ TokenFieldSegment,
21
+ TokenSegment,
22
+ TextSegment,
23
+ Position,
24
+ TokenFieldValueOptions,
25
+ } from '../../stately/tokenfield/TokenFieldValue';
@@ -0,0 +1,20 @@
1
+ /** @jsxImportSource octane */
2
+ // Ported from adobe/react-spectrum@5ecb3333001313e83898cd07644227897e3bae1f (packages/react-stately/exports/useTooltipTriggerState.ts).
3
+ /*
4
+ * Copyright 2020 Adobe. All rights reserved.
5
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License. You may obtain a copy
7
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
8
+ *
9
+ * Unless required by applicable law or agreed to in writing, software distributed under
10
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
11
+ * OF ANY KIND, either express or implied. See the License for the specific language
12
+ * governing permissions and limitations under the License.
13
+ */
14
+
15
+ export { useTooltipTriggerState } from '../../stately/tooltip/useTooltipTriggerState';
16
+
17
+ export type {
18
+ TooltipTriggerProps,
19
+ TooltipTriggerState,
20
+ } from '../../stately/tooltip/useTooltipTriggerState';
@@ -0,0 +1,14 @@
1
+ /** @jsxImportSource octane */
2
+ /*
3
+ * Copyright 2020 Adobe. All rights reserved.
4
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License. You may obtain a copy
6
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
7
+ *
8
+ * Unless required by applicable law or agreed to in writing, software distributed under
9
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
10
+ * OF ANY KIND, either express or implied. See the License for the specific language
11
+ * governing permissions and limitations under the License.
12
+ */
13
+ export type { TreeProps, TreeState } from '../../stately/tree/useTreeState';
14
+ export { useTreeState } from '../../stately/tree/useTreeState';
@@ -1,36 +1,93 @@
1
- // Ported from react-aria (source: .react-spectrum/packages/react-aria/src/utils/domHelpers.ts).
1
+ /*
2
+ * Copyright 2020 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+
13
+ import type { EventMapType } from '@react-types/shared';
14
+
15
+ export const getOwnerDocument = (target?: EventTarget | null): Document => {
16
+ if (isWindow(target)) return target.document;
2
17
 
3
- export const getOwnerDocument = (el: Element | null | undefined): Document => {
4
- return el?.ownerDocument ?? document;
18
+ if (isDocument(target)) return target;
19
+
20
+ // @ts-expect-error Ensure safe access in SSR environments.
21
+ return target?.ownerDocument ?? (typeof document !== 'undefined' ? document : undefined);
5
22
  };
6
23
 
7
- export const getOwnerWindow = (
8
- el: (Window & typeof globalThis) | Element | null | undefined,
9
- ): Window & typeof globalThis => {
10
- if (el && 'window' in el && el.window === el) {
11
- return el;
12
- }
24
+ export const getOwnerWindow = (target?: EventTarget | null): Window & typeof globalThis => {
25
+ let ownerDocument = getOwnerDocument(target);
13
26
 
14
- const doc = getOwnerDocument(el as Element | null | undefined);
15
- return doc.defaultView || window;
27
+ // @ts-expect-error Ensure safe access in SSR environments.
28
+ return ownerDocument?.defaultView ?? (typeof window !== 'undefined' ? window : undefined);
16
29
  };
17
30
 
18
31
  /**
19
32
  * Type guard that checks if a value is a Node. Verifies the presence and type of the nodeType
20
33
  * property.
21
34
  */
22
- function isNode(value: unknown): value is Node {
35
+ export function isNode(value: unknown): value is Node {
23
36
  return (
24
37
  value !== null &&
25
38
  typeof value === 'object' &&
26
39
  'nodeType' in value &&
27
- typeof (value as Node).nodeType === 'number'
40
+ typeof value.nodeType === 'number'
28
41
  );
29
42
  }
43
+
44
+ /**
45
+ * Type guard that checks if a value is a Window. Uses window self reference checks to
46
+ * distinguish Window from other values.
47
+ */
48
+ function isWindow(value: unknown): value is Window & typeof globalThis {
49
+ return typeof value === 'object' && value != null && 'window' in value && value.window === value;
50
+ }
51
+
30
52
  /**
31
- * Type guard that checks if a node is a ShadowRoot. Uses nodeType and host property checks to
32
- * distinguish ShadowRoot from other DocumentFragments.
53
+ * Type guard that checks if a value is a Document. Uses nodeType and host property checks to
54
+ * distinguish Document from other values.
33
55
  */
34
- export function isShadowRoot(node: Node | null): node is ShadowRoot {
35
- return isNode(node) && node.nodeType === Node.DOCUMENT_FRAGMENT_NODE && 'host' in node;
56
+ export function isDocument(value: unknown): value is Document {
57
+ return isNode(value) && value.nodeType === 9;
58
+ }
59
+
60
+ /**
61
+ * Type guard that checks if a value is a ShadowRoot. Uses nodeType and host property checks to
62
+ * distinguish ShadowRoot from other values.
63
+ */
64
+ export function isShadowRoot(value: unknown): value is ShadowRoot {
65
+ // 11 = DOCUMENT_FRAGMENT_NODE
66
+ return isNode(value) && value.nodeType === 11 && 'host' in value;
67
+ }
68
+
69
+ /**
70
+ * Attaches an event listener on target(s) and returns a cleanup function.
71
+ */
72
+ export function addEvent<T extends EventTarget, K extends keyof EventMapType<Exclude<T, null>>>(
73
+ target: T | EventTarget[] | null,
74
+ event: Extract<K, string> | (string & {}),
75
+ listener?: (this: T, ev: EventMapType<Exclude<T, null>>[K]) => any,
76
+ options?: boolean | AddEventListenerOptions,
77
+ ): () => void {
78
+ if (listener == null || target == null) {
79
+ return () => {};
80
+ }
81
+
82
+ let eventTargets = Array.isArray(target) ? target : [target];
83
+
84
+ for (let eventTarget of eventTargets) {
85
+ eventTarget.addEventListener(event, listener as EventListener, options);
86
+ }
87
+
88
+ return () => {
89
+ for (let eventTarget of eventTargets) {
90
+ eventTarget.removeEventListener(event, listener as EventListener, options);
91
+ }
92
+ };
36
93
  }
@@ -38,7 +38,8 @@ export function isVirtualPointerEvent(event: PointerEvent): boolean {
38
38
  // Talkback double tap from Windows Firefox touch screen press
39
39
  return (
40
40
  (!isAndroid() && event.width === 0 && event.height === 0) ||
41
- (event.width === 1 &&
41
+ (isAndroid() &&
42
+ event.width === 1 &&
42
43
  event.height === 1 &&
43
44
  event.pressure === 0 &&
44
45
  event.detail === 0 &&
@@ -100,7 +100,12 @@ export function openLink(target: HTMLAnchorElement, modifiers: Modifiers, setOpe
100
100
  // will prevent links with target="_blank" from opening. However, it does allow the event if the
101
101
  // Command/Control key is held, which opens the link in a background tab. This seems like the best we can do.
102
102
  // See https://bugzilla.mozilla.org/show_bug.cgi?id=257870 and https://bugzilla.mozilla.org/show_bug.cgi?id=746640.
103
- if (isFirefox() && window.event?.type?.startsWith('key') && target.target === '_blank') {
103
+ if (
104
+ !isWebKit() &&
105
+ isFirefox() &&
106
+ window.event?.type?.startsWith('key') &&
107
+ target.target === '_blank'
108
+ ) {
104
109
  if (isMac()) {
105
110
  metaKey = true;
106
111
  } else {
@@ -200,8 +205,8 @@ export function useLinkProps(...args: any[]): LinkDOMProps {
200
205
  const href = router.useHref(props?.href ?? '');
201
206
  let linkProps: LinkDOMProps = {};
202
207
  if (props) {
203
- for (let key of ['href', 'target', 'rel', 'download', 'ping', 'referrerPolicy']) {
204
- if (key in props) {
208
+ for (let key of ['href', 'target', 'rel', 'download', 'ping', 'referrerPolicy'] as const) {
209
+ if (key in props && props[key] !== undefined) {
205
210
  (linkProps as any)[key] = key === 'href' ? href : (props as any)[key];
206
211
  }
207
212
  }
@@ -58,11 +58,15 @@ export const isAppleDevice: () => boolean = cached(function () {
58
58
  });
59
59
 
60
60
  export const isWebKit: () => boolean = cached(function () {
61
- return testUserAgent(/AppleWebKit/i) && !isChrome();
61
+ return testUserAgent(/AppleWebKit/i) && (isIOS() || !isChrome());
62
+ });
63
+
64
+ export const isSafari: () => boolean = cached(function () {
65
+ return isWebKit() && !isChrome() && !isFirefox();
62
66
  });
63
67
 
64
68
  export const isChrome: () => boolean = cached(function () {
65
- return testUserAgent(/Chrome/i);
69
+ return testUserAgent(/Chrome|CriOS|CrMo/i);
66
70
  });
67
71
 
68
72
  export const isAndroid: () => boolean = cached(function () {
@@ -70,5 +74,5 @@ export const isAndroid: () => boolean = cached(function () {
70
74
  });
71
75
 
72
76
  export const isFirefox: () => boolean = cached(function () {
73
- return testUserAgent(/Firefox/i);
77
+ return testUserAgent(/(Firefox|FxiOS)/i);
74
78
  });
@@ -2,7 +2,7 @@
2
2
  // Verbatim (no React surface), including the NODE_ENV === 'test' direct-assignment branch
3
3
  // (jsdom has no scrollTo implementation).
4
4
  import { getScrollParents } from './getScrollParents';
5
- import { isIOS } from './platform';
5
+ import { isIOS, isWebKit } from '../utils/platform';
6
6
 
7
7
  interface ScrollIntoViewOpts {
8
8
  /** The position to align items along the block axis in. */
@@ -81,10 +81,10 @@ export function scrollIntoView(
81
81
  let scrollPortRight = viewRight - (isRoot ? 0 : borderRightWidth) - scrollPaddingRight;
82
82
 
83
83
  // IOS always positions the scrollbar on the right ¯\_(ツ)_/¯
84
- if (viewStyle.direction === 'rtl' && !isIOS()) {
85
- scrollPortLeft += scrollBarWidth;
86
- } else {
84
+ if ((isIOS() && isWebKit()) || viewStyle.direction === 'ltr') {
87
85
  scrollPortRight -= scrollBarWidth;
86
+ } else if (viewStyle.direction === 'rtl') {
87
+ scrollPortLeft += scrollBarWidth;
88
88
  }
89
89
 
90
90
  let shouldScrollBlock = scrollAreaTop < scrollPortTop || scrollAreaBottom > scrollPortBottom;
@@ -29,8 +29,8 @@ export function nodeContains(
29
29
  }
30
30
 
31
31
  if (
32
- (currentNode as HTMLSlotElement).tagName === 'SLOT' &&
33
- (currentNode as HTMLSlotElement).assignedSlot
32
+ typeof (currentNode as HTMLSlotElement).assignedElements !== 'function' &&
33
+ (currentNode as HTMLSlotElement).assignedSlot?.parentNode
34
34
  ) {
35
35
  // Element is slotted
36
36
  currentNode = (currentNode as HTMLSlotElement).assignedSlot!.parentNode;
@@ -77,6 +77,46 @@ export function getEventTarget<T extends Event>(event: T): EventTarget {
77
77
  return event.target as EventTarget;
78
78
  }
79
79
 
80
+ /**
81
+ * ShadowDOM safe fast version of node.contains(document.activeElement).
82
+ *
83
+ * @param node
84
+ * @returns
85
+ */
86
+ export function getPropagationTargets(
87
+ from: Element | null | undefined,
88
+ to?: Document | Window | Element | null,
89
+ ): EventTarget[] {
90
+ // If `to` is coming from a ref, its type technically allows `null`.
91
+ // In practice, this function will generally be called from within a useEffect.
92
+ // If the ref has not resolved by that point, then a coding error has been made.
93
+ // Better to return an empty array than `[window]`, which may appear to work
94
+ // in the light DOM, but fail in the shadow DOM.
95
+ if (to === null) {
96
+ return [];
97
+ }
98
+ to = to ?? getOwnerWindow(from);
99
+ let targets: EventTarget[] = [to];
100
+ if (!shadowDOM() || !from || from === to) {
101
+ return targets;
102
+ }
103
+
104
+ // The root `to` itself lives in. The event already reaches `to` once
105
+ // it is inside this root, so we must NOT collect this root or anything above
106
+ // it — only the shadow roots strictly between `refNode` and `to`.
107
+ // `window` has no getRootNode; its boundary is the document, which the walk
108
+ // reaches naturally (the document is not a ShadowRoot, so the loop exits).
109
+ let toRoot = 'getRootNode' in to ? to.getRootNode() : null;
110
+ let current: Node | null = from.getRootNode() ?? null;
111
+ while (isShadowRoot(current) && current !== toRoot) {
112
+ // order shouldn't matter
113
+ targets.push(current);
114
+ current = current.host.getRootNode();
115
+ }
116
+
117
+ return targets;
118
+ }
119
+
80
120
  /**
81
121
  * ShadowDOM safe fast version of node.contains(document.activeElement).
82
122
  *
@@ -29,6 +29,7 @@ if (typeof FinalizationRegistry !== 'undefined') {
29
29
  *
30
30
  * @param defaultId - Default component id.
31
31
  */
32
+ let registeredIds = new WeakMap<object, string>();
32
33
  export function useId(defaultId?: string): string;
33
34
  // Slot-threading form: sibling ported hooks pass their derived sub-slot as the trailing arg.
34
35
  export function useId(slot: symbol | undefined): string;
@@ -44,8 +45,15 @@ export function useId(...args: any[]): string {
44
45
  let res = useSSRSafeId(value, subSlot(slot, 'ssrId'));
45
46
  let cleanupRef = useRef(null, subSlot(slot, 'cleanupToken'));
46
47
 
47
- if (registry) {
48
- registry.register(cleanupRef, res);
48
+ let registeredId = registeredIds.get(cleanupRef);
49
+ if (registry && registeredId !== res) {
50
+ if (registeredId != null) {
51
+ registry.unregister(cleanupRef);
52
+ }
53
+ // oxlint-disable-next-line react/react-compiler
54
+ registry.register(cleanupRef, res, cleanupRef);
55
+ // oxlint-disable-next-line react/react-compiler
56
+ registeredIds.set(cleanupRef, res);
49
57
  }
50
58
 
51
59
  if (canUseDOM) {
@@ -65,6 +73,7 @@ export function useId(...args: any[]): string {
65
73
  // when it is though, also remove it from the finalization registry.
66
74
  if (registry) {
67
75
  registry.unregister(cleanupRef);
76
+ registeredIds.delete(cleanupRef);
68
77
  }
69
78
  idsUpdaterMap.delete(r);
70
79
  };
@@ -28,6 +28,7 @@ export interface LoadMoreSentinelProps extends Omit<AsyncLoadable, 'isLoading'>
28
28
  * @default 1
29
29
  */
30
30
  scrollOffset?: number;
31
+ direction?: 'start' | 'end';
31
32
  }
32
33
 
33
34
  export function useLoadMoreSentinel(
@@ -35,7 +36,7 @@ export function useLoadMoreSentinel(
35
36
  ref: RefObject<HTMLElement | null>,
36
37
  slot: symbol | undefined,
37
38
  ): void {
38
- let { collection, onLoadMore, scrollOffset = 1 } = props;
39
+ let { collection, onLoadMore, scrollOffset = 1, direction = 'end' } = props;
39
40
 
40
41
  let sentinelObserver = useRef<IntersectionObserver | null>(null, subSlot(slot, 'observer'));
41
42
 
@@ -59,9 +60,13 @@ export function useLoadMoreSentinel(
59
60
  if (ref.current) {
60
61
  // Tear down and set up a new IntersectionObserver when the collection changes so that we can properly trigger additional loadMores if there is room for more items
61
62
  // Need to do this tear down and set up since using a large rootMargin will mean the observer's callback isn't called even when scrolling the item into view beause its visibility hasn't actually changed
63
+ const margin = 100 * scrollOffset;
64
+ // For direction='start', right/left margins have no affect for vertical scroll containers. We are not supporting reverse horizontal scroll containers for now.
65
+ const rootMargin =
66
+ direction === 'start' ? `${margin}% 0px 0px 0px` : `0px ${margin}% ${margin}% ${margin}%`;
62
67
  sentinelObserver.current = new IntersectionObserver(triggerLoadMore, {
63
68
  root: getScrollParent(ref?.current) as HTMLElement,
64
- rootMargin: `0px ${100 * scrollOffset}% ${100 * scrollOffset}% ${100 * scrollOffset}%`,
69
+ rootMargin,
65
70
  });
66
71
  sentinelObserver.current.observe(ref.current);
67
72
  }
@@ -72,7 +77,7 @@ export function useLoadMoreSentinel(
72
77
  }
73
78
  };
74
79
  },
75
- [collection, ref, scrollOffset],
80
+ [collection, ref, scrollOffset, direction],
76
81
  subSlot(slot, 'observe'),
77
82
  );
78
83
  }
@@ -5,7 +5,7 @@
5
5
  import { useEffect, useState } from 'octane';
6
6
 
7
7
  import { getActiveElement, getEventTarget } from './shadowdom/DOMFunctions';
8
- import { isIOS } from './platform';
8
+ import { isIOS, isWebKit } from './platform';
9
9
  import { S, splitSlot, subSlot } from '../internal';
10
10
  import { useIsSSR } from '../ssr/SSRProvider';
11
11
  import { willOpenKeyboard } from './keyboard';
@@ -75,7 +75,7 @@ export function useViewportSize(...args: any[]): ViewportSize {
75
75
 
76
76
  updateSize(getViewportSize());
77
77
 
78
- if (isIOS()) {
78
+ if (isIOS() && isWebKit()) {
79
79
  window.addEventListener('blur', onBlur, true);
80
80
  }
81
81
 
@@ -87,7 +87,7 @@ export function useViewportSize(...args: any[]): ViewportSize {
87
87
 
88
88
  return () => {
89
89
  cancelAnimationFrame(frame);
90
- if (isIOS()) {
90
+ if (isIOS() && isWebKit()) {
91
91
  window.removeEventListener('blur', onBlur, true);
92
92
  }
93
93
  if (!visualViewport) {
@@ -1,4 +1,5 @@
1
1
  /** @jsxImportSource octane */
2
+ import { addEvent, getOwnerDocument } from '../utils/domHelpers';
2
3
  // Ported from adobe/react-spectrum@1c84a49a1faf50b571c84e00bcf9c60b22ddd03e (packages/react-aria/src/virtualizer/ScrollView.tsx).
3
4
  /*
4
5
  * Copyright 2020 Adobe. All rights reserved.
@@ -14,7 +15,11 @@
14
15
 
15
16
  // @ts-ignore
16
17
  import { flushSync } from 'octane';
17
- import { getEventTarget, nodeContains } from '../utils/shadowdom/DOMFunctions';
18
+ import {
19
+ getEventTarget,
20
+ getPropagationTargets,
21
+ nodeContains,
22
+ } from '../utils/shadowdom/DOMFunctions';
18
23
  import { getScrollLeft } from './utils';
19
24
  import { Point, Rect, Size } from '../upstream-exports/react-stately/useVirtualizerState';
20
25
  import React, {
@@ -221,9 +226,13 @@ export function useScrollView(
221
226
 
222
227
  // Attach a document-level capturing scroll listener so we can account for scrollable ancestors.
223
228
  useEffect(() => {
224
- document.addEventListener('scroll', onScroll, true);
225
- return () => document.removeEventListener('scroll', onScroll, true);
226
- }, [onScroll]);
229
+ return addEvent(
230
+ getPropagationTargets(ref.current, getOwnerDocument(ref.current)),
231
+ 'scroll',
232
+ onScroll,
233
+ true,
234
+ );
235
+ }, [onScroll, ref]);
227
236
 
228
237
  useEffect(() => {
229
238
  return () => {
@@ -24,10 +24,12 @@ interface VirtualizerItemProps extends Omit<VirtualizerItemOptions, 'ref'> {
24
24
  style?: CSSProperties;
25
25
  className?: string;
26
26
  children: ReactNode;
27
+ shouldObserveItemSize?: boolean;
27
28
  }
28
29
 
29
30
  export function VirtualizerItem(props: VirtualizerItemProps): JSX.Element {
30
- let { style, className, layoutInfo, virtualizer, parent, children } = props;
31
+ let { style, className, layoutInfo, virtualizer, parent, shouldObserveItemSize, children } =
32
+ props;
31
33
  let { direction } = useLocale();
32
34
  let ref = useRef<HTMLDivElement | null>(null);
33
35
  useVirtualizerItem({
@@ -1,4 +1,6 @@
1
1
  /** @jsxImportSource octane */
2
+ import { isElementVisible } from '../utils/isElementVisible';
3
+ import { useEffectEvent } from '../utils/useEffectEvent';
2
4
  // Ported from adobe/react-spectrum@1c84a49a1faf50b571c84e00bcf9c60b22ddd03e (packages/react-aria/src/virtualizer/useVirtualizerItem.ts).
3
5
  /*
4
6
  * Copyright 2020 Adobe. All rights reserved.
@@ -14,7 +16,7 @@
14
16
 
15
17
  import { Key, RefObject } from '@react-types/shared';
16
18
  import { LayoutInfo, Size } from '../upstream-exports/react-stately/useVirtualizerState';
17
- import { useCallback } from '../compat/react';
19
+ import { useCallback, useEffect } from '../compat/react';
18
20
  import { useLayoutEffect } from '../utils/useLayoutEffect';
19
21
 
20
22
  interface IVirtualizer {
@@ -25,25 +27,63 @@ export interface VirtualizerItemOptions {
25
27
  layoutInfo: LayoutInfo | null;
26
28
  virtualizer: IVirtualizer;
27
29
  ref: RefObject<HTMLElement | null>;
30
+ shouldObserveItemSize?: boolean;
28
31
  }
29
32
 
30
33
  export function useVirtualizerItem(options: VirtualizerItemOptions): { updateSize: () => void } {
31
- let { layoutInfo, virtualizer, ref } = options;
34
+ let { layoutInfo, virtualizer, ref, shouldObserveItemSize } = options;
32
35
  let key = layoutInfo?.key;
33
36
 
34
37
  let updateSize = useCallback(() => {
35
38
  if (key != null && ref.current) {
39
+ if (!isElementVisible(ref.current)) {
40
+ return;
41
+ }
36
42
  let size = getSize(ref.current);
37
43
  virtualizer.updateItemSize(key, size);
38
44
  }
39
45
  }, [virtualizer, key, ref]);
40
46
 
47
+ let updateSizeEvent = useEffectEvent(updateSize);
41
48
  useLayoutEffect(() => {
42
49
  if (layoutInfo?.estimatedSize) {
43
- updateSize();
50
+ updateSizeEvent();
44
51
  }
45
52
  });
46
53
 
54
+ // TODO: Consider using a MutationObserver in addition to ResizeObserver to detect
55
+ // when inner DOM structure changes cause an item's height to change.
56
+ // The current ResizeObserver only observes direct children,
57
+ // so mutations deeper in the tree won't trigger a remeasure, leading to stale cached heights and overlapping items.
58
+ // useResizeObserver observes one element via ref, but the wrapper height is fixed by layout
59
+ // and won't change when content grows. Observe direct children instead, then remeasure the
60
+ // wrapper in updateSize.
61
+ useEffect(() => {
62
+ if (!shouldObserveItemSize) {
63
+ return;
64
+ }
65
+
66
+ let el = ref.current;
67
+ if (!el || typeof ResizeObserver === 'undefined') {
68
+ return;
69
+ }
70
+
71
+ let resizeObserver = new ResizeObserver((entries) => {
72
+ if (!entries.length) {
73
+ return;
74
+ }
75
+ updateSizeEvent();
76
+ });
77
+
78
+ for (let child of el.children) {
79
+ resizeObserver.observe(child);
80
+ }
81
+
82
+ return () => {
83
+ resizeObserver.disconnect();
84
+ };
85
+ }, [shouldObserveItemSize, ref, key]);
86
+
47
87
  return { updateSize };
48
88
  }
49
89