@salt-ds/lab 1.0.0-alpha.87 → 1.0.0-alpha.89

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 (177) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/css/salt-lab.css +64 -45
  3. package/dist-cjs/calendar/internal/CalendarDay.css.js +1 -1
  4. package/dist-cjs/contact-details/ContactDetails.css.js +1 -1
  5. package/dist-cjs/list-deprecated/ListStateContext.js +1 -1
  6. package/dist-cjs/list-deprecated/ListStateContext.js.map +1 -1
  7. package/dist-cjs/tabs-next/TabBar.css.js +1 -1
  8. package/dist-cjs/tabs-next/TabBar.js +1 -1
  9. package/dist-cjs/tabs-next/TabBar.js.map +1 -1
  10. package/dist-cjs/tabs-next/TabListLayoutContext.js +13 -0
  11. package/dist-cjs/tabs-next/TabListLayoutContext.js.map +1 -0
  12. package/dist-cjs/tabs-next/TabListNext.css.js +1 -1
  13. package/dist-cjs/tabs-next/TabListNext.js +179 -33
  14. package/dist-cjs/tabs-next/TabListNext.js.map +1 -1
  15. package/dist-cjs/tabs-next/TabNext.js +111 -7
  16. package/dist-cjs/tabs-next/TabNext.js.map +1 -1
  17. package/dist-cjs/tabs-next/TabNextAction.js +25 -2
  18. package/dist-cjs/tabs-next/TabNextAction.js.map +1 -1
  19. package/dist-cjs/tabs-next/TabNextPanel.js +31 -16
  20. package/dist-cjs/tabs-next/TabNextPanel.js.map +1 -1
  21. package/dist-cjs/tabs-next/TabNextTrigger.js +110 -9
  22. package/dist-cjs/tabs-next/TabNextTrigger.js.map +1 -1
  23. package/dist-cjs/tabs-next/TabOverflowList.css.js +1 -1
  24. package/dist-cjs/tabs-next/TabOverflowList.js +168 -64
  25. package/dist-cjs/tabs-next/TabOverflowList.js.map +1 -1
  26. package/dist-cjs/tabs-next/TabSlot.js +30 -0
  27. package/dist-cjs/tabs-next/TabSlot.js.map +1 -0
  28. package/dist-cjs/tabs-next/TabSlotRegistryContext.js +16 -0
  29. package/dist-cjs/tabs-next/TabSlotRegistryContext.js.map +1 -0
  30. package/dist-cjs/tabs-next/TabsNext.css.js +6 -0
  31. package/dist-cjs/tabs-next/TabsNext.css.js.map +1 -0
  32. package/dist-cjs/tabs-next/TabsNext.js +113 -47
  33. package/dist-cjs/tabs-next/TabsNext.js.map +1 -1
  34. package/dist-cjs/tabs-next/TabsNextContext.js +17 -3
  35. package/dist-cjs/tabs-next/TabsNextContext.js.map +1 -1
  36. package/dist-cjs/tabs-next/domUtils.js +13 -0
  37. package/dist-cjs/tabs-next/domUtils.js.map +1 -0
  38. package/dist-cjs/tabs-next/hooks/overflowMath.js +86 -0
  39. package/dist-cjs/tabs-next/hooks/overflowMath.js.map +1 -0
  40. package/dist-cjs/tabs-next/hooks/useCollection.js +147 -41
  41. package/dist-cjs/tabs-next/hooks/useCollection.js.map +1 -1
  42. package/dist-cjs/tabs-next/hooks/useFocusWithRetry.js +64 -0
  43. package/dist-cjs/tabs-next/hooks/useFocusWithRetry.js.map +1 -0
  44. package/dist-cjs/tabs-next/hooks/useOverflow.js +240 -156
  45. package/dist-cjs/tabs-next/hooks/useOverflow.js.map +1 -1
  46. package/dist-cjs/tabs-next/hooks/useOverflowLayoutState.js +99 -0
  47. package/dist-cjs/tabs-next/hooks/useOverflowLayoutState.js.map +1 -0
  48. package/dist-cjs/tabs-next/hooks/useOverflowSelectionState.js +60 -0
  49. package/dist-cjs/tabs-next/hooks/useOverflowSelectionState.js.map +1 -0
  50. package/dist-cjs/tabs-next/hooks/useRenderedTabWidth.js +92 -0
  51. package/dist-cjs/tabs-next/hooks/useRenderedTabWidth.js.map +1 -0
  52. package/dist-cjs/tabs-next/hooks/useRenderedTabsRegistry.js +200 -0
  53. package/dist-cjs/tabs-next/hooks/useRenderedTabsRegistry.js.map +1 -0
  54. package/dist-cjs/tabs-next/hooks/useTabListRecovery.js +76 -0
  55. package/dist-cjs/tabs-next/hooks/useTabListRecovery.js.map +1 -0
  56. package/dist-cjs/tabs-next/hooks/useTabRemovalHandler.js +165 -0
  57. package/dist-cjs/tabs-next/hooks/useTabRemovalHandler.js.map +1 -0
  58. package/dist-cjs/tabs-next/hooks/useTabSelectionFocus.js +80 -0
  59. package/dist-cjs/tabs-next/hooks/useTabSelectionFocus.js.map +1 -0
  60. package/dist-cjs/tabs-next/widthMeasurement.js +42 -0
  61. package/dist-cjs/tabs-next/widthMeasurement.js.map +1 -0
  62. package/dist-cjs/tree/Tree.css.js +1 -1
  63. package/dist-cjs/tree/TreeNode.css.js +1 -1
  64. package/dist-cjs/tree/TreeNode.js +1 -1
  65. package/dist-cjs/tree/TreeNode.js.map +1 -1
  66. package/dist-cjs/tree/TreeNodeExpansionIcon.css.js +1 -1
  67. package/dist-cjs/tree/TreeNodeTrigger.css.js +1 -1
  68. package/dist-cjs/tree/TreeNodeTrigger.js +2 -2
  69. package/dist-cjs/tree/TreeNodeTrigger.js.map +1 -1
  70. package/dist-cjs/utils/useEventCallback.js +5 -5
  71. package/dist-cjs/utils/useEventCallback.js.map +1 -1
  72. package/dist-es/calendar/internal/CalendarDay.css.js +1 -1
  73. package/dist-es/contact-details/ContactDetails.css.js +1 -1
  74. package/dist-es/list-deprecated/ListStateContext.js +1 -1
  75. package/dist-es/list-deprecated/ListStateContext.js.map +1 -1
  76. package/dist-es/tabs-next/TabBar.css.js +1 -1
  77. package/dist-es/tabs-next/TabBar.js +1 -1
  78. package/dist-es/tabs-next/TabBar.js.map +1 -1
  79. package/dist-es/tabs-next/TabListLayoutContext.js +10 -0
  80. package/dist-es/tabs-next/TabListLayoutContext.js.map +1 -0
  81. package/dist-es/tabs-next/TabListNext.css.js +1 -1
  82. package/dist-es/tabs-next/TabListNext.js +182 -36
  83. package/dist-es/tabs-next/TabListNext.js.map +1 -1
  84. package/dist-es/tabs-next/TabNext.js +113 -9
  85. package/dist-es/tabs-next/TabNext.js.map +1 -1
  86. package/dist-es/tabs-next/TabNextAction.js +25 -2
  87. package/dist-es/tabs-next/TabNextAction.js.map +1 -1
  88. package/dist-es/tabs-next/TabNextPanel.js +31 -16
  89. package/dist-es/tabs-next/TabNextPanel.js.map +1 -1
  90. package/dist-es/tabs-next/TabNextTrigger.js +110 -9
  91. package/dist-es/tabs-next/TabNextTrigger.js.map +1 -1
  92. package/dist-es/tabs-next/TabOverflowList.css.js +1 -1
  93. package/dist-es/tabs-next/TabOverflowList.js +172 -68
  94. package/dist-es/tabs-next/TabOverflowList.js.map +1 -1
  95. package/dist-es/tabs-next/TabSlot.js +28 -0
  96. package/dist-es/tabs-next/TabSlot.js.map +1 -0
  97. package/dist-es/tabs-next/TabSlotRegistryContext.js +13 -0
  98. package/dist-es/tabs-next/TabSlotRegistryContext.js.map +1 -0
  99. package/dist-es/tabs-next/TabsNext.css.js +4 -0
  100. package/dist-es/tabs-next/TabsNext.css.js.map +1 -0
  101. package/dist-es/tabs-next/TabsNext.js +114 -48
  102. package/dist-es/tabs-next/TabsNext.js.map +1 -1
  103. package/dist-es/tabs-next/TabsNextContext.js +17 -3
  104. package/dist-es/tabs-next/TabsNextContext.js.map +1 -1
  105. package/dist-es/tabs-next/domUtils.js +11 -0
  106. package/dist-es/tabs-next/domUtils.js.map +1 -0
  107. package/dist-es/tabs-next/hooks/overflowMath.js +82 -0
  108. package/dist-es/tabs-next/hooks/overflowMath.js.map +1 -0
  109. package/dist-es/tabs-next/hooks/useCollection.js +148 -42
  110. package/dist-es/tabs-next/hooks/useCollection.js.map +1 -1
  111. package/dist-es/tabs-next/hooks/useFocusWithRetry.js +62 -0
  112. package/dist-es/tabs-next/hooks/useFocusWithRetry.js.map +1 -0
  113. package/dist-es/tabs-next/hooks/useOverflow.js +242 -158
  114. package/dist-es/tabs-next/hooks/useOverflow.js.map +1 -1
  115. package/dist-es/tabs-next/hooks/useOverflowLayoutState.js +97 -0
  116. package/dist-es/tabs-next/hooks/useOverflowLayoutState.js.map +1 -0
  117. package/dist-es/tabs-next/hooks/useOverflowSelectionState.js +58 -0
  118. package/dist-es/tabs-next/hooks/useOverflowSelectionState.js.map +1 -0
  119. package/dist-es/tabs-next/hooks/useRenderedTabWidth.js +90 -0
  120. package/dist-es/tabs-next/hooks/useRenderedTabWidth.js.map +1 -0
  121. package/dist-es/tabs-next/hooks/useRenderedTabsRegistry.js +198 -0
  122. package/dist-es/tabs-next/hooks/useRenderedTabsRegistry.js.map +1 -0
  123. package/dist-es/tabs-next/hooks/useTabListRecovery.js +74 -0
  124. package/dist-es/tabs-next/hooks/useTabListRecovery.js.map +1 -0
  125. package/dist-es/tabs-next/hooks/useTabRemovalHandler.js +163 -0
  126. package/dist-es/tabs-next/hooks/useTabRemovalHandler.js.map +1 -0
  127. package/dist-es/tabs-next/hooks/useTabSelectionFocus.js +78 -0
  128. package/dist-es/tabs-next/hooks/useTabSelectionFocus.js.map +1 -0
  129. package/dist-es/tabs-next/widthMeasurement.js +36 -0
  130. package/dist-es/tabs-next/widthMeasurement.js.map +1 -0
  131. package/dist-es/tree/Tree.css.js +1 -1
  132. package/dist-es/tree/TreeNode.css.js +1 -1
  133. package/dist-es/tree/TreeNode.js +1 -1
  134. package/dist-es/tree/TreeNode.js.map +1 -1
  135. package/dist-es/tree/TreeNodeExpansionIcon.css.js +1 -1
  136. package/dist-es/tree/TreeNodeTrigger.css.js +1 -1
  137. package/dist-es/tree/TreeNodeTrigger.js +2 -2
  138. package/dist-es/tree/TreeNodeTrigger.js.map +1 -1
  139. package/dist-es/utils/useEventCallback.js +5 -5
  140. package/dist-es/utils/useEventCallback.js.map +1 -1
  141. package/dist-types/cascading-menu/internal/useMenuTriggerHandlers.d.ts +1 -1
  142. package/dist-types/list-deprecated/ListStateContext.d.ts +7 -2
  143. package/dist-types/tabs-next/TabListLayoutContext.d.ts +9 -0
  144. package/dist-types/tabs-next/TabNext.d.ts +1 -1
  145. package/dist-types/tabs-next/TabNextPanel.d.ts +2 -1
  146. package/dist-types/tabs-next/TabOverflowList.d.ts +3 -4
  147. package/dist-types/tabs-next/TabSlot.d.ts +6 -0
  148. package/dist-types/tabs-next/TabSlotRegistryContext.d.ts +5 -0
  149. package/dist-types/tabs-next/TabsNext.d.ts +2 -1
  150. package/dist-types/tabs-next/TabsNextContext.d.ts +26 -4
  151. package/dist-types/tabs-next/domUtils.d.ts +1 -0
  152. package/dist-types/tabs-next/hooks/overflowMath.d.ts +18 -0
  153. package/dist-types/tabs-next/hooks/useCollection.d.ts +15 -3
  154. package/dist-types/tabs-next/hooks/useFocusWithRetry.d.ts +9 -0
  155. package/dist-types/tabs-next/hooks/useOverflow.d.ts +5 -5
  156. package/dist-types/tabs-next/hooks/useOverflowLayoutState.d.ts +13 -0
  157. package/dist-types/tabs-next/hooks/useOverflowSelectionState.d.ts +13 -0
  158. package/dist-types/tabs-next/hooks/useRenderedTabWidth.d.ts +12 -0
  159. package/dist-types/tabs-next/hooks/useRenderedTabsRegistry.d.ts +12 -0
  160. package/dist-types/tabs-next/hooks/useTabListRecovery.d.ts +12 -0
  161. package/dist-types/tabs-next/hooks/useTabRemovalHandler.d.ts +32 -0
  162. package/dist-types/tabs-next/hooks/useTabSelectionFocus.d.ts +15 -0
  163. package/dist-types/tabs-next/widthMeasurement.d.ts +5 -0
  164. package/dist-types/tokenized-input/internal/InputPill.d.ts +1 -1
  165. package/dist-types/tokenized-input-next/internal/InputPill.d.ts +1 -1
  166. package/dist-types/utils/useEventCallback.d.ts +1 -1
  167. package/package.json +3 -3
  168. package/dist-cjs/tabs-next/hooks/useFocusOutside.js +0 -25
  169. package/dist-cjs/tabs-next/hooks/useFocusOutside.js.map +0 -1
  170. package/dist-cjs/tabs-next/hooks/useRestoreActiveTab.js +0 -93
  171. package/dist-cjs/tabs-next/hooks/useRestoreActiveTab.js.map +0 -1
  172. package/dist-es/tabs-next/hooks/useFocusOutside.js +0 -23
  173. package/dist-es/tabs-next/hooks/useFocusOutside.js.map +0 -1
  174. package/dist-es/tabs-next/hooks/useRestoreActiveTab.js +0 -91
  175. package/dist-es/tabs-next/hooks/useRestoreActiveTab.js.map +0 -1
  176. package/dist-types/tabs-next/hooks/useFocusOutside.d.ts +0 -2
  177. package/dist-types/tabs-next/hooks/useRestoreActiveTab.d.ts +0 -10
@@ -1,179 +1,263 @@
1
- import { useValueEffect, useEventCallback, useIsomorphicLayoutEffect, ownerWindow } from '@salt-ds/core';
2
- import { useWindow } from '@salt-ds/window';
3
- import { useRef, useEffect, useMemo, Children, isValidElement } from 'react';
1
+ import { ownerWindow, useIsomorphicLayoutEffect } from '@salt-ds/core';
2
+ import { useMemo, useState, useRef, useCallback, useEffect } from 'react';
3
+ import { isHTMLElement } from '../domUtils.js';
4
+ import { seedWidthMap, updateWidthMap, getMeasuredWidth, getGapValue } from '../widthMeasurement.js';
5
+ import { calculateVisibleCount, MIN_TRUSTED_TAB_WIDTH, partitionVisibleValues } from './overflowMath.js';
4
6
 
5
- function getTabWidth(element) {
6
- const { width } = element.getBoundingClientRect();
7
- return Math.ceil(width);
7
+ function getTabWidth(tab) {
8
+ const width = tab.width || getMeasuredWidth(tab.root);
9
+ return width > MIN_TRUSTED_TAB_WIDTH ? width : null;
10
+ }
11
+ function getAvailableWidth(element) {
12
+ const parent = element.parentElement;
13
+ if (!parent) {
14
+ return getMeasuredWidth(element);
15
+ }
16
+ const parentWidth = getMeasuredWidth(parent);
17
+ const parentStyles = ownerWindow(parent).getComputedStyle(parent);
18
+ const parentGap = getGapValue(parentStyles);
19
+ const siblings = Array.from(parent.children).filter(
20
+ (child) => {
21
+ if (!isHTMLElement(child) || child === element) {
22
+ return false;
23
+ }
24
+ return ownerWindow(child).getComputedStyle(child).display !== "none";
25
+ }
26
+ );
27
+ const siblingWidth = siblings.reduce((width, sibling) => {
28
+ return width + getMeasuredWidth(sibling);
29
+ }, 0);
30
+ const gapCount = siblings.length > 0 ? siblings.length : 0;
31
+ const availableWidth = Math.max(
32
+ 0,
33
+ parentWidth - siblingWidth - gapCount * parentGap
34
+ );
35
+ return availableWidth;
36
+ }
37
+ function isSelectedValueHidden(selected, hiddenValues) {
38
+ return selected !== void 0 && hiddenValues.includes(selected);
39
+ }
40
+ function getPinnedSelectionValue(selected, selectedIsHidden, pinnedSelectionRef) {
41
+ return selectedIsHidden ? selected : pinnedSelectionRef.current;
8
42
  }
9
43
  function useOverflow({
10
- tabs,
11
44
  container,
12
45
  overflowButton,
13
- children,
14
- selected
46
+ tabs,
47
+ selected,
48
+ menuOpen
15
49
  }) {
16
- const [{ visibleCount, isMeasuring }, setVisibleItems] = useValueEffect({
17
- visibleCount: tabs.length,
18
- isMeasuring: false
19
- });
20
- const targetWindow = useWindow();
21
- const realSelectedIndex = useRef(-1);
22
- const updateOverflow = useEventCallback(() => {
23
- const computeVisible = (visibleCount2) => {
24
- var _a;
25
- if (container.current && targetWindow) {
26
- const items = Array.from(
27
- container.current.querySelectorAll(
28
- "[data-overflowitem]"
29
- )
30
- );
31
- const selectedTab = (_a = container.current.querySelector(
32
- "[role=tab][aria-selected=true]"
33
- )) == null ? void 0 : _a.parentElement;
34
- let maxWidth = container.current.clientWidth ?? 0;
35
- const containerStyles = targetWindow.getComputedStyle(
36
- container.current
37
- );
38
- const gap = Number.parseInt(containerStyles.gap || "0", 10);
39
- let currentWidth = 0;
40
- let newVisibleCount = 0;
41
- const visible2 = [];
42
- while (newVisibleCount < items.length) {
43
- const element = items[newVisibleCount];
44
- if (element) {
45
- if (currentWidth + getTabWidth(element) + gap > maxWidth) {
46
- break;
47
- }
48
- currentWidth += getTabWidth(element) + gap;
49
- visible2.push(element);
50
- }
51
- newVisibleCount++;
52
- }
53
- if (newVisibleCount >= items.length) {
54
- return newVisibleCount;
55
- }
56
- const overflowButtonWidth = overflowButton.current ? overflowButton.current.offsetWidth + gap : 0;
57
- maxWidth -= overflowButtonWidth;
58
- while (currentWidth > maxWidth) {
59
- const removed = visible2.pop();
60
- if (!removed) break;
61
- currentWidth -= getTabWidth(removed) + gap;
62
- newVisibleCount--;
63
- }
64
- if (selectedTab && !visible2.includes(selectedTab)) {
65
- const selectedTabWidth = getTabWidth(selectedTab) + gap;
66
- while (currentWidth + selectedTabWidth > maxWidth) {
67
- const removed = visible2.pop();
68
- if (!removed) break;
69
- currentWidth -= getTabWidth(removed) + gap;
70
- newVisibleCount--;
71
- }
72
- }
73
- return Math.max(0, newVisibleCount);
74
- }
75
- return visibleCount2;
76
- };
77
- setVisibleItems(function* () {
78
- yield {
79
- visibleCount: tabs.length,
80
- isMeasuring: true
81
- };
82
- const newVisibleCount = computeVisible(tabs.length);
83
- const isMeasuring2 = newVisibleCount < tabs.length && newVisibleCount > 0;
84
- yield {
85
- visibleCount: newVisibleCount,
86
- isMeasuring: isMeasuring2
87
- };
88
- if (isMeasuring2) {
89
- yield {
90
- visibleCount: computeVisible(newVisibleCount),
91
- isMeasuring: false
92
- };
50
+ const orderedValues = useMemo(() => tabs.map((tab) => tab.value), [tabs]);
51
+ const measurementInputKey = useMemo(() => {
52
+ return tabs.map((tab) => `${tab.value}:${tab.width.toFixed(2)}`).join("\0");
53
+ }, [tabs]);
54
+ const [visibleCount, setVisibleCount] = useState(0);
55
+ const [isMeasuring, setIsMeasuring] = useState(true);
56
+ const [measureRetryVersion, setMeasureRetryVersion] = useState(0);
57
+ const pinnedSelectionRef = useRef(selected);
58
+ const previousOverflowButtonWidthRef = useRef(0);
59
+ const previousMeasurementInputKeyRef = useRef(measurementInputKey);
60
+ const previousMenuOpenRef = useRef(menuOpen);
61
+ const measureRetryFrameRef = useRef(null);
62
+ const measureRetryCountRef = useRef(0);
63
+ const baseHiddenValues = orderedValues.slice(visibleCount);
64
+ const selectedIsHidden = isSelectedValueHidden(selected, baseHiddenValues);
65
+ const pinnedValue = getPinnedSelectionValue(
66
+ selected,
67
+ selectedIsHidden,
68
+ pinnedSelectionRef
69
+ );
70
+ const getCurrentPinnedValue = useCallback(() => {
71
+ return getPinnedSelectionValue(
72
+ selected,
73
+ selectedIsHidden,
74
+ pinnedSelectionRef
75
+ );
76
+ }, [selected, selectedIsHidden]);
77
+ const markMeasurementStale = useCallback(() => {
78
+ setIsMeasuring(true);
79
+ }, []);
80
+ const measureVisibleCount = useCallback(
81
+ (pinnedValue2) => {
82
+ const element = container.current;
83
+ if (!element) {
84
+ return null;
93
85
  }
94
- });
95
- });
96
- useIsomorphicLayoutEffect(() => {
97
- updateOverflow();
98
- }, [selected]);
99
- useEffect(() => {
100
- const handleWindowResize = () => {
101
- updateOverflow();
102
- };
103
- targetWindow == null ? void 0 : targetWindow.addEventListener("resize", handleWindowResize);
104
- return () => {
105
- targetWindow == null ? void 0 : targetWindow.removeEventListener("resize", handleWindowResize);
106
- };
107
- }, [updateOverflow, targetWindow]);
108
- useEffect(() => {
109
- const element = container == null ? void 0 : container.current;
110
- if (!element) return;
111
- const win = ownerWindow(element);
112
- const resizeObserver = new win.ResizeObserver((entries) => {
113
- requestAnimationFrame(() => {
114
- if (entries.length === 0) return;
115
- updateOverflow();
86
+ const maxWidth = getAvailableWidth(element);
87
+ const styles = ownerWindow(element).getComputedStyle(element);
88
+ const gap = getGapValue(styles);
89
+ const overflowWidth = overflowButton.current ? overflowButton.current.offsetWidth + gap : 0;
90
+ const measuredTabs = tabs.map((tab) => ({
91
+ value: tab.value,
92
+ width: getTabWidth(tab)
93
+ }));
94
+ return calculateVisibleCount({
95
+ gap,
96
+ maxWidth,
97
+ overflowWidth,
98
+ pinnedValue: pinnedValue2,
99
+ tabs: measuredTabs
116
100
  });
117
- });
118
- resizeObserver.observe(element);
119
- if (element.parentElement) {
120
- resizeObserver.observe(element.parentElement);
101
+ },
102
+ [container, overflowButton, tabs]
103
+ );
104
+ const clearMeasureRetry = useCallback(() => {
105
+ const element = container.current;
106
+ const frame = measureRetryFrameRef.current;
107
+ if (element && frame != null) {
108
+ ownerWindow(element).cancelAnimationFrame(frame);
121
109
  }
122
- return () => {
123
- if (element) {
124
- resizeObserver.unobserve(element);
110
+ measureRetryFrameRef.current = null;
111
+ measureRetryCountRef.current = 0;
112
+ }, [container]);
113
+ useEffect(() => {
114
+ return clearMeasureRetry;
115
+ }, [clearMeasureRetry]);
116
+ useIsomorphicLayoutEffect(() => {
117
+ if (selected !== void 0 && selectedIsHidden) {
118
+ pinnedSelectionRef.current = selected;
119
+ const nextVisibleCount = measureVisibleCount(selected);
120
+ if (nextVisibleCount == null) {
121
+ markMeasurementStale();
122
+ return;
125
123
  }
126
- };
127
- }, [container, updateOverflow]);
124
+ if (nextVisibleCount !== visibleCount) {
125
+ setVisibleCount(nextVisibleCount);
126
+ }
127
+ if (isMeasuring) {
128
+ setIsMeasuring(false);
129
+ }
130
+ }
131
+ }, [
132
+ isMeasuring,
133
+ markMeasurementStale,
134
+ measureVisibleCount,
135
+ selected,
136
+ selectedIsHidden,
137
+ visibleCount
138
+ ]);
128
139
  useEffect(() => {
129
- const element = container == null ? void 0 : container.current;
130
- if (!element || isMeasuring) return;
131
- const win = ownerWindow(element);
132
- const mutationObserver = new win.MutationObserver(() => {
133
- requestAnimationFrame(() => {
134
- updateOverflow();
135
- });
136
- });
137
- mutationObserver.observe(element, {
138
- childList: true
139
- });
140
+ const element = container.current;
141
+ if (!element || menuOpen || isMeasuring) {
142
+ return;
143
+ }
144
+ const observedElements = [element];
145
+ const parent = element.parentElement;
146
+ if (parent) {
147
+ observedElements.push(parent);
148
+ for (const child of Array.from(parent.children)) {
149
+ if (isHTMLElement(child) && child !== element) {
150
+ observedElements.push(child);
151
+ }
152
+ }
153
+ }
154
+ const widths = seedWidthMap(observedElements);
155
+ const resizeObserverCtor = ownerWindow(element).ResizeObserver;
156
+ if (!resizeObserverCtor) {
157
+ return;
158
+ }
159
+ const resizeObserver = new resizeObserverCtor(
160
+ (entries) => {
161
+ for (const entry of entries) {
162
+ if (!isHTMLElement(entry.target)) {
163
+ continue;
164
+ }
165
+ const nextWidth = entry.contentRect.width;
166
+ if (updateWidthMap(widths, entry.target, nextWidth)) {
167
+ const nextVisibleCount = measureVisibleCount(
168
+ getCurrentPinnedValue()
169
+ );
170
+ if (nextVisibleCount != null && nextVisibleCount === visibleCount) {
171
+ continue;
172
+ }
173
+ markMeasurementStale();
174
+ return;
175
+ }
176
+ }
177
+ }
178
+ );
179
+ for (const observedElement of observedElements) {
180
+ resizeObserver.observe(observedElement);
181
+ }
140
182
  return () => {
141
- mutationObserver.disconnect();
183
+ resizeObserver.disconnect();
142
184
  };
143
- }, [container, updateOverflow, isMeasuring]);
144
- const childArray = useMemo(() => Children.toArray(children), [children]);
145
- const visible = useMemo(
146
- () => childArray.slice(0, visibleCount),
147
- [visibleCount, childArray]
148
- );
149
- const hidden = useMemo(
150
- () => childArray.slice(visibleCount),
151
- [childArray, visibleCount]
152
- );
153
- const hiddenSelectedIndex = hidden.findIndex(
154
- (child) => {
155
- var _a;
156
- return isValidElement(child) && ((_a = child == null ? void 0 : child.props) == null ? void 0 : _a.value) === selected;
185
+ }, [
186
+ container,
187
+ getCurrentPinnedValue,
188
+ isMeasuring,
189
+ markMeasurementStale,
190
+ measureVisibleCount,
191
+ menuOpen,
192
+ visibleCount
193
+ ]);
194
+ useIsomorphicLayoutEffect(() => {
195
+ if (previousMenuOpenRef.current && !menuOpen) {
196
+ markMeasurementStale();
157
197
  }
158
- );
198
+ previousMenuOpenRef.current = menuOpen;
199
+ }, [markMeasurementStale, menuOpen]);
200
+ useIsomorphicLayoutEffect(() => {
201
+ var _a;
202
+ const nextOverflowButtonWidth = ((_a = overflowButton.current) == null ? void 0 : _a.offsetWidth) ?? 0;
203
+ if (previousOverflowButtonWidthRef.current === nextOverflowButtonWidth) {
204
+ return;
205
+ }
206
+ previousOverflowButtonWidthRef.current = nextOverflowButtonWidth;
207
+ if (visibleCount < tabs.length) {
208
+ markMeasurementStale();
209
+ }
210
+ });
211
+ useIsomorphicLayoutEffect(() => {
212
+ if (previousMeasurementInputKeyRef.current !== measurementInputKey) {
213
+ previousMeasurementInputKeyRef.current = measurementInputKey;
214
+ markMeasurementStale();
215
+ }
216
+ }, [markMeasurementStale, measurementInputKey]);
159
217
  useIsomorphicLayoutEffect(() => {
160
- if (visibleCount === childArray.length) {
161
- realSelectedIndex.current = childArray.findIndex(
162
- (child) => {
163
- var _a;
164
- return isValidElement(child) && ((_a = child == null ? void 0 : child.props) == null ? void 0 : _a.value) === selected;
218
+ if (!isMeasuring || menuOpen) {
219
+ return;
220
+ }
221
+ const nextVisibleCount = measureVisibleCount(getCurrentPinnedValue());
222
+ if (nextVisibleCount == null) {
223
+ if (measureRetryFrameRef.current != null) {
224
+ return;
225
+ }
226
+ const element = container.current;
227
+ if (!element || getMeasuredWidth(element) <= MIN_TRUSTED_TAB_WIDTH) {
228
+ measureRetryCountRef.current = 0;
229
+ return;
230
+ }
231
+ if (measureRetryCountRef.current >= 5) {
232
+ return;
233
+ }
234
+ measureRetryCountRef.current += 1;
235
+ measureRetryFrameRef.current = ownerWindow(element).requestAnimationFrame(
236
+ () => {
237
+ measureRetryFrameRef.current = null;
238
+ setMeasureRetryVersion((currentVersion) => currentVersion + 1);
165
239
  }
166
240
  );
241
+ return;
167
242
  }
168
- }, [visibleCount, childArray, selected]);
169
- if (selected && hiddenSelectedIndex !== -1) {
170
- const removed = hidden.splice(hiddenSelectedIndex, 1);
171
- visible.push(removed[0]);
172
- }
173
- if (isMeasuring) {
174
- return [childArray, [], isMeasuring, realSelectedIndex];
175
- }
176
- return [visible, hidden, isMeasuring, realSelectedIndex];
243
+ clearMeasureRetry();
244
+ setVisibleCount(nextVisibleCount);
245
+ setIsMeasuring(false);
246
+ }, [
247
+ clearMeasureRetry,
248
+ container.current,
249
+ getCurrentPinnedValue,
250
+ isMeasuring,
251
+ measureRetryVersion,
252
+ measureVisibleCount,
253
+ menuOpen
254
+ ]);
255
+ const { visibleValues, hiddenValues } = partitionVisibleValues(
256
+ orderedValues,
257
+ visibleCount,
258
+ pinnedValue
259
+ );
260
+ return [visibleValues, hiddenValues, isMeasuring];
177
261
  }
178
262
 
179
263
  export { useOverflow };
@@ -1 +1 @@
1
- {"version":3,"file":"useOverflow.js","sources":["../src/tabs-next/hooks/useOverflow.ts"],"sourcesContent":["import {\n ownerWindow,\n useEventCallback,\n useIsomorphicLayoutEffect,\n useValueEffect,\n} from \"@salt-ds/core\";\nimport { useWindow } from \"@salt-ds/window\";\nimport {\n Children,\n isValidElement,\n type ReactNode,\n type RefObject,\n useEffect,\n useMemo,\n useRef,\n} from \"react\";\nimport type { TabNextProps } from \"../TabNext\";\nimport type { Item } from \"./useCollection\";\n\ninterface UseOverflowProps {\n container: RefObject<HTMLElement>;\n selected?: string;\n children: ReactNode;\n tabs: Item[];\n overflowButton: RefObject<HTMLButtonElement>;\n}\n\nfunction getTabWidth(element: HTMLElement) {\n const { width } = element.getBoundingClientRect();\n return Math.ceil(width);\n}\n\nexport function useOverflow({\n tabs,\n container,\n overflowButton,\n children,\n selected,\n}: UseOverflowProps) {\n /**\n * `visibleCount` doesn't include newly selected tab from overflow menu, which is removed in `computeVisible`\n */\n const [{ visibleCount, isMeasuring }, setVisibleItems] = useValueEffect({\n visibleCount: tabs.length,\n isMeasuring: false,\n });\n const targetWindow = useWindow();\n const realSelectedIndex = useRef<number>(-1);\n\n const updateOverflow = useEventCallback(() => {\n const computeVisible = (visibleCount: number) => {\n if (container.current && targetWindow) {\n const items = Array.from(\n container.current.querySelectorAll<HTMLElement>(\n \"[data-overflowitem]\",\n ),\n );\n const selectedTab = container.current.querySelector<HTMLElement>(\n \"[role=tab][aria-selected=true]\",\n )?.parentElement;\n\n let maxWidth = container.current.clientWidth ?? 0;\n\n const containerStyles = targetWindow.getComputedStyle(\n container.current,\n );\n const gap = Number.parseInt(containerStyles.gap || \"0\", 10);\n\n let currentWidth = 0;\n let newVisibleCount = 0;\n\n const visible = [];\n\n while (newVisibleCount < items.length) {\n const element = items[newVisibleCount];\n if (element) {\n if (currentWidth + getTabWidth(element) + gap > maxWidth) {\n break;\n }\n currentWidth += getTabWidth(element) + gap;\n visible.push(element);\n }\n newVisibleCount++;\n }\n\n if (newVisibleCount >= items.length) {\n return newVisibleCount;\n }\n\n const overflowButtonWidth = overflowButton.current\n ? overflowButton.current.offsetWidth + gap\n : 0;\n maxWidth -= overflowButtonWidth;\n\n while (currentWidth > maxWidth) {\n const removed = visible.pop();\n if (!removed) break;\n currentWidth -= getTabWidth(removed) + gap;\n newVisibleCount--;\n }\n\n if (selectedTab && !visible.includes(selectedTab)) {\n const selectedTabWidth = getTabWidth(selectedTab) + gap;\n while (currentWidth + selectedTabWidth > maxWidth) {\n const removed = visible.pop();\n if (!removed) break;\n currentWidth -= getTabWidth(removed) + gap;\n newVisibleCount--;\n }\n }\n\n // minimal count should be 0, if there is no space for any tab apart from selected tab from the overflow menu\n return Math.max(0, newVisibleCount);\n }\n return visibleCount;\n };\n\n setVisibleItems(function* () {\n // Show all\n yield {\n visibleCount: tabs.length,\n isMeasuring: true,\n };\n\n // Measure the visible count\n const newVisibleCount = computeVisible(tabs.length);\n const isMeasuring = newVisibleCount < tabs.length && newVisibleCount > 0;\n yield {\n visibleCount: newVisibleCount,\n isMeasuring,\n };\n\n // ensure the visible count is correct\n if (isMeasuring) {\n yield {\n visibleCount: computeVisible(newVisibleCount),\n isMeasuring: false,\n };\n }\n });\n });\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: we want to update when selected changes.\n useIsomorphicLayoutEffect(() => {\n updateOverflow();\n }, [selected]);\n\n useEffect(() => {\n const handleWindowResize = () => {\n updateOverflow();\n };\n\n targetWindow?.addEventListener(\"resize\", handleWindowResize);\n\n return () => {\n targetWindow?.removeEventListener(\"resize\", handleWindowResize);\n };\n }, [updateOverflow, targetWindow]);\n\n useEffect(() => {\n const element = container?.current;\n if (!element) return;\n\n const win = ownerWindow(element);\n\n const resizeObserver = new win.ResizeObserver((entries) => {\n requestAnimationFrame(() => {\n if (entries.length === 0) return;\n\n updateOverflow();\n });\n });\n resizeObserver.observe(element);\n if (element.parentElement) {\n resizeObserver.observe(element.parentElement);\n }\n\n return () => {\n if (element) {\n resizeObserver.unobserve(element);\n }\n };\n }, [container, updateOverflow]);\n\n useEffect(() => {\n const element = container?.current;\n if (!element || isMeasuring) return;\n\n const win = ownerWindow(element);\n\n const mutationObserver = new win.MutationObserver(() => {\n requestAnimationFrame(() => {\n updateOverflow();\n });\n });\n\n mutationObserver.observe(element, {\n childList: true,\n });\n\n return () => {\n mutationObserver.disconnect();\n };\n }, [container, updateOverflow, isMeasuring]);\n\n const childArray = useMemo(() => Children.toArray(children), [children]);\n const visible = useMemo(\n () => childArray.slice(0, visibleCount),\n [visibleCount, childArray],\n );\n const hidden = useMemo(\n () => childArray.slice(visibleCount),\n [childArray, visibleCount],\n );\n\n const hiddenSelectedIndex = hidden.findIndex(\n (child) =>\n isValidElement<TabNextProps>(child) && child?.props?.value === selected,\n );\n\n useIsomorphicLayoutEffect(() => {\n if (visibleCount === childArray.length) {\n realSelectedIndex.current = childArray.findIndex(\n (child) =>\n isValidElement<TabNextProps>(child) &&\n child?.props?.value === selected,\n );\n }\n }, [visibleCount, childArray, selected]);\n\n if (selected && hiddenSelectedIndex !== -1) {\n const removed = hidden.splice(hiddenSelectedIndex, 1);\n visible.push(removed[0]);\n }\n\n if (isMeasuring) {\n return [childArray, [], isMeasuring, realSelectedIndex] as const;\n }\n\n return [visible, hidden, isMeasuring, realSelectedIndex] as const;\n}\n"],"names":["visibleCount","visible","isMeasuring"],"mappings":";;;;AA2BA,SAAS,YAAY,OAAA,EAAsB;AACzC,EAAA,MAAM,EAAE,KAAA,EAAM,GAAI,OAAA,CAAQ,qBAAA,EAAsB;AAChD,EAAA,OAAO,IAAA,CAAK,KAAK,KAAK,CAAA;AACxB;AAEO,SAAS,WAAA,CAAY;AAAA,EAC1B,IAAA;AAAA,EACA,SAAA;AAAA,EACA,cAAA;AAAA,EACA,QAAA;AAAA,EACA;AACF,CAAA,EAAqB;AAInB,EAAA,MAAM,CAAC,EAAE,YAAA,EAAc,aAAY,EAAG,eAAe,IAAI,cAAA,CAAe;AAAA,IACtE,cAAc,IAAA,CAAK,MAAA;AAAA,IACnB,WAAA,EAAa;AAAA,GACd,CAAA;AACD,EAAA,MAAM,eAAe,SAAA,EAAU;AAC/B,EAAA,MAAM,iBAAA,GAAoB,OAAe,EAAE,CAAA;AAE3C,EAAA,MAAM,cAAA,GAAiB,iBAAiB,MAAM;AAC5C,IAAA,MAAM,cAAA,GAAiB,CAACA,aAAAA,KAAyB;AAlDrD,MAAA,IAAA,EAAA;AAmDM,MAAA,IAAI,SAAA,CAAU,WAAW,YAAA,EAAc;AACrC,QAAA,MAAM,QAAQ,KAAA,CAAM,IAAA;AAAA,UAClB,UAAU,OAAA,CAAQ,gBAAA;AAAA,YAChB;AAAA;AACF,SACF;AACA,QAAA,MAAM,WAAA,GAAA,CAAc,eAAU,OAAA,CAAQ,aAAA;AAAA,UACpC;AAAA,cADkB,IAAA,GAAA,MAAA,GAAA,EAAA,CAEjB,aAAA;AAEH,QAAA,IAAI,QAAA,GAAW,SAAA,CAAU,OAAA,CAAQ,WAAA,IAAe,CAAA;AAEhD,QAAA,MAAM,kBAAkB,YAAA,CAAa,gBAAA;AAAA,UACnC,SAAA,CAAU;AAAA,SACZ;AACA,QAAA,MAAM,MAAM,MAAA,CAAO,QAAA,CAAS,eAAA,CAAgB,GAAA,IAAO,KAAK,EAAE,CAAA;AAE1D,QAAA,IAAI,YAAA,GAAe,CAAA;AACnB,QAAA,IAAI,eAAA,GAAkB,CAAA;AAEtB,QAAA,MAAMC,WAAU,EAAC;AAEjB,QAAA,OAAO,eAAA,GAAkB,MAAM,MAAA,EAAQ;AACrC,UAAA,MAAM,OAAA,GAAU,MAAM,eAAe,CAAA;AACrC,UAAA,IAAI,OAAA,EAAS;AACX,YAAA,IAAI,YAAA,GAAe,WAAA,CAAY,OAAO,CAAA,GAAI,MAAM,QAAA,EAAU;AACxD,cAAA;AAAA,YACF;AACA,YAAA,YAAA,IAAgB,WAAA,CAAY,OAAO,CAAA,GAAI,GAAA;AACvC,YAAAA,QAAAA,CAAQ,KAAK,OAAO,CAAA;AAAA,UACtB;AACA,UAAA,eAAA,EAAA;AAAA,QACF;AAEA,QAAA,IAAI,eAAA,IAAmB,MAAM,MAAA,EAAQ;AACnC,UAAA,OAAO,eAAA;AAAA,QACT;AAEA,QAAA,MAAM,sBAAsB,cAAA,CAAe,OAAA,GACvC,cAAA,CAAe,OAAA,CAAQ,cAAc,GAAA,GACrC,CAAA;AACJ,QAAA,QAAA,IAAY,mBAAA;AAEZ,QAAA,OAAO,eAAe,QAAA,EAAU;AAC9B,UAAA,MAAM,OAAA,GAAUA,SAAQ,GAAA,EAAI;AAC5B,UAAA,IAAI,CAAC,OAAA,EAAS;AACd,UAAA,YAAA,IAAgB,WAAA,CAAY,OAAO,CAAA,GAAI,GAAA;AACvC,UAAA,eAAA,EAAA;AAAA,QACF;AAEA,QAAA,IAAI,WAAA,IAAe,CAACA,QAAAA,CAAQ,QAAA,CAAS,WAAW,CAAA,EAAG;AACjD,UAAA,MAAM,gBAAA,GAAmB,WAAA,CAAY,WAAW,CAAA,GAAI,GAAA;AACpD,UAAA,OAAO,YAAA,GAAe,mBAAmB,QAAA,EAAU;AACjD,YAAA,MAAM,OAAA,GAAUA,SAAQ,GAAA,EAAI;AAC5B,YAAA,IAAI,CAAC,OAAA,EAAS;AACd,YAAA,YAAA,IAAgB,WAAA,CAAY,OAAO,CAAA,GAAI,GAAA;AACvC,YAAA,eAAA,EAAA;AAAA,UACF;AAAA,QACF;AAGA,QAAA,OAAO,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,eAAe,CAAA;AAAA,MACpC;AACA,MAAA,OAAOD,aAAAA;AAAA,IACT,CAAA;AAEA,IAAA,eAAA,CAAgB,aAAa;AAE3B,MAAA,MAAM;AAAA,QACJ,cAAc,IAAA,CAAK,MAAA;AAAA,QACnB,WAAA,EAAa;AAAA,OACf;AAGA,MAAA,MAAM,eAAA,GAAkB,cAAA,CAAe,IAAA,CAAK,MAAM,CAAA;AAClD,MAAA,MAAME,YAAAA,GAAc,eAAA,GAAkB,IAAA,CAAK,MAAA,IAAU,eAAA,GAAkB,CAAA;AACvE,MAAA,MAAM;AAAA,QACJ,YAAA,EAAc,eAAA;AAAA,QACd,WAAA,EAAAA;AAAA,OACF;AAGA,MAAA,IAAIA,YAAAA,EAAa;AACf,QAAA,MAAM;AAAA,UACJ,YAAA,EAAc,eAAe,eAAe,CAAA;AAAA,UAC5C,WAAA,EAAa;AAAA,SACf;AAAA,MACF;AAAA,IACF,CAAC,CAAA;AAAA,EACH,CAAC,CAAA;AAGD,EAAA,yBAAA,CAA0B,MAAM;AAC9B,IAAA,cAAA,EAAe;AAAA,EACjB,CAAA,EAAG,CAAC,QAAQ,CAAC,CAAA;AAEb,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,MAAM,qBAAqB,MAAM;AAC/B,MAAA,cAAA,EAAe;AAAA,IACjB,CAAA;AAEA,IAAA,YAAA,IAAA,IAAA,GAAA,MAAA,GAAA,YAAA,CAAc,iBAAiB,QAAA,EAAU,kBAAA,CAAA;AAEzC,IAAA,OAAO,MAAM;AACX,MAAA,YAAA,IAAA,IAAA,GAAA,MAAA,GAAA,YAAA,CAAc,oBAAoB,QAAA,EAAU,kBAAA,CAAA;AAAA,IAC9C,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,cAAA,EAAgB,YAAY,CAAC,CAAA;AAEjC,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,MAAM,UAAU,SAAA,IAAA,IAAA,GAAA,MAAA,GAAA,SAAA,CAAW,OAAA;AAC3B,IAAA,IAAI,CAAC,OAAA,EAAS;AAEd,IAAA,MAAM,GAAA,GAAM,YAAY,OAAO,CAAA;AAE/B,IAAA,MAAM,cAAA,GAAiB,IAAI,GAAA,CAAI,cAAA,CAAe,CAAC,OAAA,KAAY;AACzD,MAAA,qBAAA,CAAsB,MAAM;AAC1B,QAAA,IAAI,OAAA,CAAQ,WAAW,CAAA,EAAG;AAE1B,QAAA,cAAA,EAAe;AAAA,MACjB,CAAC,CAAA;AAAA,IACH,CAAC,CAAA;AACD,IAAA,cAAA,CAAe,QAAQ,OAAO,CAAA;AAC9B,IAAA,IAAI,QAAQ,aAAA,EAAe;AACzB,MAAA,cAAA,CAAe,OAAA,CAAQ,QAAQ,aAAa,CAAA;AAAA,IAC9C;AAEA,IAAA,OAAO,MAAM;AACX,MAAA,IAAI,OAAA,EAAS;AACX,QAAA,cAAA,CAAe,UAAU,OAAO,CAAA;AAAA,MAClC;AAAA,IACF,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,SAAA,EAAW,cAAc,CAAC,CAAA;AAE9B,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,MAAM,UAAU,SAAA,IAAA,IAAA,GAAA,MAAA,GAAA,SAAA,CAAW,OAAA;AAC3B,IAAA,IAAI,CAAC,WAAW,WAAA,EAAa;AAE7B,IAAA,MAAM,GAAA,GAAM,YAAY,OAAO,CAAA;AAE/B,IAAA,MAAM,gBAAA,GAAmB,IAAI,GAAA,CAAI,gBAAA,CAAiB,MAAM;AACtD,MAAA,qBAAA,CAAsB,MAAM;AAC1B,QAAA,cAAA,EAAe;AAAA,MACjB,CAAC,CAAA;AAAA,IACH,CAAC,CAAA;AAED,IAAA,gBAAA,CAAiB,QAAQ,OAAA,EAAS;AAAA,MAChC,SAAA,EAAW;AAAA,KACZ,CAAA;AAED,IAAA,OAAO,MAAM;AACX,MAAA,gBAAA,CAAiB,UAAA,EAAW;AAAA,IAC9B,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,SAAA,EAAW,cAAA,EAAgB,WAAW,CAAC,CAAA;AAE3C,EAAA,MAAM,UAAA,GAAa,QAAQ,MAAM,QAAA,CAAS,QAAQ,QAAQ,CAAA,EAAG,CAAC,QAAQ,CAAC,CAAA;AACvE,EAAA,MAAM,OAAA,GAAU,OAAA;AAAA,IACd,MAAM,UAAA,CAAW,KAAA,CAAM,CAAA,EAAG,YAAY,CAAA;AAAA,IACtC,CAAC,cAAc,UAAU;AAAA,GAC3B;AACA,EAAA,MAAM,MAAA,GAAS,OAAA;AAAA,IACb,MAAM,UAAA,CAAW,KAAA,CAAM,YAAY,CAAA;AAAA,IACnC,CAAC,YAAY,YAAY;AAAA,GAC3B;AAEA,EAAA,MAAM,sBAAsB,MAAA,CAAO,SAAA;AAAA,IACjC,CAAC,KAAA,KAAO;AAxNZ,MAAA,IAAA,EAAA;AAyNM,MAAA,OAAA,cAAA,CAA6B,KAAK,CAAA,IAAA,CAAA,CAAK,EAAA,GAAA,KAAA,IAAA,IAAA,GAAA,MAAA,GAAA,KAAA,CAAO,KAAA,KAAP,mBAAc,KAAA,MAAU,QAAA;AAAA,IAAA;AAAA,GACnE;AAEA,EAAA,yBAAA,CAA0B,MAAM;AAC9B,IAAA,IAAI,YAAA,KAAiB,WAAW,MAAA,EAAQ;AACtC,MAAA,iBAAA,CAAkB,UAAU,UAAA,CAAW,SAAA;AAAA,QACrC,CAAC,KAAA,KAAO;AA/NhB,UAAA,IAAA,EAAA;AAgOU,UAAA,OAAA,cAAA,CAA6B,KAAK,CAAA,IAAA,CAAA,CAClC,EAAA,GAAA,KAAA,IAAA,IAAA,GAAA,MAAA,GAAA,KAAA,CAAO,KAAA,KAAP,mBAAc,KAAA,MAAU,QAAA;AAAA,QAAA;AAAA,OAC5B;AAAA,IACF;AAAA,EACF,CAAA,EAAG,CAAC,YAAA,EAAc,UAAA,EAAY,QAAQ,CAAC,CAAA;AAEvC,EAAA,IAAI,QAAA,IAAY,wBAAwB,EAAA,EAAI;AAC1C,IAAA,MAAM,OAAA,GAAU,MAAA,CAAO,MAAA,CAAO,mBAAA,EAAqB,CAAC,CAAA;AACpD,IAAA,OAAA,CAAQ,IAAA,CAAK,OAAA,CAAQ,CAAC,CAAC,CAAA;AAAA,EACzB;AAEA,EAAA,IAAI,WAAA,EAAa;AACf,IAAA,OAAO,CAAC,UAAA,EAAY,EAAC,EAAG,aAAa,iBAAiB,CAAA;AAAA,EACxD;AAEA,EAAA,OAAO,CAAC,OAAA,EAAS,MAAA,EAAQ,WAAA,EAAa,iBAAiB,CAAA;AACzD;;;;"}
1
+ {"version":3,"file":"useOverflow.js","sources":["../src/tabs-next/hooks/useOverflow.ts"],"sourcesContent":["import { ownerWindow, useIsomorphicLayoutEffect } from \"@salt-ds/core\";\nimport {\n type MutableRefObject,\n type RefObject,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport { isHTMLElement } from \"../domUtils\";\nimport type { RenderedTab } from \"../TabsNextContext\";\nimport {\n getGapValue,\n getMeasuredWidth,\n seedWidthMap,\n updateWidthMap,\n} from \"../widthMeasurement\";\nimport {\n calculateVisibleCount,\n MIN_TRUSTED_TAB_WIDTH,\n partitionVisibleValues,\n} from \"./overflowMath\";\n\ninterface UseOverflowProps {\n container: RefObject<HTMLElement>;\n selected?: string;\n tabs: RenderedTab[];\n overflowButton: RefObject<HTMLButtonElement>;\n menuOpen: boolean;\n}\n\nfunction getTabWidth(tab: RenderedTab) {\n const width = tab.width || getMeasuredWidth(tab.root);\n return width > MIN_TRUSTED_TAB_WIDTH ? width : null;\n}\n\nfunction getAvailableWidth(element: HTMLElement) {\n const parent = element.parentElement;\n if (!parent) {\n return getMeasuredWidth(element);\n }\n\n const parentWidth = getMeasuredWidth(parent);\n const parentStyles = ownerWindow(parent).getComputedStyle(parent);\n const parentGap = getGapValue(parentStyles);\n const siblings = Array.from(parent.children).filter(\n (child): child is HTMLElement => {\n if (!isHTMLElement(child) || child === element) {\n return false;\n }\n\n return ownerWindow(child).getComputedStyle(child).display !== \"none\";\n },\n );\n\n const siblingWidth = siblings.reduce((width, sibling) => {\n return width + getMeasuredWidth(sibling);\n }, 0);\n const gapCount = siblings.length > 0 ? siblings.length : 0;\n const availableWidth = Math.max(\n 0,\n parentWidth - siblingWidth - gapCount * parentGap,\n );\n return availableWidth;\n}\n\nfunction isSelectedValueHidden(\n selected: string | undefined,\n hiddenValues: string[],\n) {\n return selected !== undefined && hiddenValues.includes(selected);\n}\n\nfunction getPinnedSelectionValue(\n selected: string | undefined,\n selectedIsHidden: boolean,\n pinnedSelectionRef: MutableRefObject<string | undefined>,\n) {\n return selectedIsHidden ? selected : pinnedSelectionRef.current;\n}\n\nexport function useOverflow({\n container,\n overflowButton,\n tabs,\n selected,\n menuOpen,\n}: UseOverflowProps) {\n const orderedValues = useMemo(() => tabs.map((tab) => tab.value), [tabs]);\n const measurementInputKey = useMemo(() => {\n return tabs.map((tab) => `${tab.value}:${tab.width.toFixed(2)}`).join(\"\\0\");\n }, [tabs]);\n const [visibleCount, setVisibleCount] = useState(0);\n const [isMeasuring, setIsMeasuring] = useState(true);\n const [measureRetryVersion, setMeasureRetryVersion] = useState(0);\n const pinnedSelectionRef = useRef(selected);\n const previousOverflowButtonWidthRef = useRef(0);\n const previousMeasurementInputKeyRef = useRef(measurementInputKey);\n const previousMenuOpenRef = useRef(menuOpen);\n const measureRetryFrameRef = useRef<number | null>(null);\n const measureRetryCountRef = useRef(0);\n const baseHiddenValues = orderedValues.slice(visibleCount);\n const selectedIsHidden = isSelectedValueHidden(selected, baseHiddenValues);\n const pinnedValue = getPinnedSelectionValue(\n selected,\n selectedIsHidden,\n pinnedSelectionRef,\n );\n const getCurrentPinnedValue = useCallback(() => {\n return getPinnedSelectionValue(\n selected,\n selectedIsHidden,\n pinnedSelectionRef,\n );\n }, [selected, selectedIsHidden]);\n const markMeasurementStale = useCallback(() => {\n setIsMeasuring(true);\n }, []);\n\n const measureVisibleCount = useCallback(\n (pinnedValue?: string) => {\n const element = container.current;\n if (!element) {\n return null;\n }\n\n const maxWidth = getAvailableWidth(element);\n const styles = ownerWindow(element).getComputedStyle(element);\n const gap = getGapValue(styles);\n const overflowWidth = overflowButton.current\n ? overflowButton.current.offsetWidth + gap\n : 0;\n const measuredTabs = tabs.map((tab) => ({\n value: tab.value,\n width: getTabWidth(tab),\n }));\n\n return calculateVisibleCount({\n gap,\n maxWidth,\n overflowWidth,\n pinnedValue,\n tabs: measuredTabs,\n });\n },\n [container, overflowButton, tabs],\n );\n const clearMeasureRetry = useCallback(() => {\n const element = container.current;\n const frame = measureRetryFrameRef.current;\n\n if (element && frame != null) {\n ownerWindow(element).cancelAnimationFrame(frame);\n }\n\n measureRetryFrameRef.current = null;\n measureRetryCountRef.current = 0;\n }, [container]);\n\n useEffect(() => {\n return clearMeasureRetry;\n }, [clearMeasureRetry]);\n\n useIsomorphicLayoutEffect(() => {\n if (selected !== undefined && selectedIsHidden) {\n pinnedSelectionRef.current = selected;\n const nextVisibleCount = measureVisibleCount(selected);\n if (nextVisibleCount == null) {\n markMeasurementStale();\n return;\n }\n if (nextVisibleCount !== visibleCount) {\n setVisibleCount(nextVisibleCount);\n }\n if (isMeasuring) {\n setIsMeasuring(false);\n }\n }\n }, [\n isMeasuring,\n markMeasurementStale,\n measureVisibleCount,\n selected,\n selectedIsHidden,\n visibleCount,\n ]);\n\n useEffect(() => {\n const element = container.current;\n if (!element || menuOpen || isMeasuring) {\n return;\n }\n\n const observedElements = [element];\n const parent = element.parentElement;\n if (parent) {\n observedElements.push(parent);\n for (const child of Array.from(parent.children)) {\n if (isHTMLElement(child) && child !== element) {\n observedElements.push(child);\n }\n }\n }\n\n const widths = seedWidthMap(observedElements);\n const resizeObserverCtor = ownerWindow(element).ResizeObserver;\n if (!resizeObserverCtor) {\n return;\n }\n\n const resizeObserver = new resizeObserverCtor(\n (entries: ResizeObserverEntry[]) => {\n for (const entry of entries) {\n if (!isHTMLElement(entry.target)) {\n continue;\n }\n\n const nextWidth = entry.contentRect.width;\n if (updateWidthMap(widths, entry.target, nextWidth)) {\n const nextVisibleCount = measureVisibleCount(\n getCurrentPinnedValue(),\n );\n\n if (nextVisibleCount != null && nextVisibleCount === visibleCount) {\n continue;\n }\n\n markMeasurementStale();\n return;\n }\n }\n },\n );\n\n for (const observedElement of observedElements) {\n resizeObserver.observe(observedElement);\n }\n\n return () => {\n resizeObserver.disconnect();\n };\n }, [\n container,\n getCurrentPinnedValue,\n isMeasuring,\n markMeasurementStale,\n measureVisibleCount,\n menuOpen,\n visibleCount,\n ]);\n\n useIsomorphicLayoutEffect(() => {\n if (previousMenuOpenRef.current && !menuOpen) {\n markMeasurementStale();\n }\n\n previousMenuOpenRef.current = menuOpen;\n }, [markMeasurementStale, menuOpen]);\n\n useIsomorphicLayoutEffect(() => {\n const nextOverflowButtonWidth = overflowButton.current?.offsetWidth ?? 0;\n if (previousOverflowButtonWidthRef.current === nextOverflowButtonWidth) {\n return;\n }\n\n previousOverflowButtonWidthRef.current = nextOverflowButtonWidth;\n if (visibleCount < tabs.length) {\n markMeasurementStale();\n }\n });\n\n useIsomorphicLayoutEffect(() => {\n if (previousMeasurementInputKeyRef.current !== measurementInputKey) {\n previousMeasurementInputKeyRef.current = measurementInputKey;\n markMeasurementStale();\n }\n }, [markMeasurementStale, measurementInputKey]);\n\n useIsomorphicLayoutEffect(() => {\n // A content-only tab width update can briefly leave a tab without a\n // trustworthy measured width after it moves through the portal slots.\n // Retry on the next frame instead of leaving overflow stuck measuring.\n void measureRetryVersion;\n\n if (!isMeasuring || menuOpen) {\n return;\n }\n\n const nextVisibleCount = measureVisibleCount(getCurrentPinnedValue());\n\n if (nextVisibleCount == null) {\n if (measureRetryFrameRef.current != null) {\n return;\n }\n\n const element = container.current;\n if (!element || getMeasuredWidth(element) <= MIN_TRUSTED_TAB_WIDTH) {\n measureRetryCountRef.current = 0;\n return;\n }\n\n if (measureRetryCountRef.current >= 5) {\n return;\n }\n\n measureRetryCountRef.current += 1;\n measureRetryFrameRef.current = ownerWindow(element).requestAnimationFrame(\n () => {\n measureRetryFrameRef.current = null;\n setMeasureRetryVersion((currentVersion) => currentVersion + 1);\n },\n );\n return;\n }\n\n clearMeasureRetry();\n\n setVisibleCount(nextVisibleCount);\n setIsMeasuring(false);\n }, [\n clearMeasureRetry,\n container.current,\n getCurrentPinnedValue,\n isMeasuring,\n measureRetryVersion,\n measureVisibleCount,\n menuOpen,\n ]);\n\n const { visibleValues, hiddenValues } = partitionVisibleValues(\n orderedValues,\n visibleCount,\n pinnedValue,\n );\n\n return [visibleValues, hiddenValues, isMeasuring] as const;\n}\n"],"names":["pinnedValue"],"mappings":"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"}
@@ -0,0 +1,97 @@
1
+ import { useState, useEffect, useMemo, useCallback } from 'react';
2
+
3
+ function getOverflowStartIndex(hiddenValues, currentValue, fallbackValue) {
4
+ const currentIndex = hiddenValues.indexOf(currentValue);
5
+ const fallbackIndex = fallbackValue != null ? hiddenValues.indexOf(fallbackValue) : 0;
6
+ return currentIndex >= 0 ? currentIndex : Math.max(0, fallbackIndex);
7
+ }
8
+ function useOverflowLayoutState({
9
+ hiddenValues,
10
+ menuOpen,
11
+ overflowMenuOpen,
12
+ visibleValues
13
+ }) {
14
+ const [requestedOverflowActiveValue, setRequestedOverflowActiveValue] = useState(null);
15
+ useEffect(() => {
16
+ if (!overflowMenuOpen) {
17
+ setRequestedOverflowActiveValue(null);
18
+ }
19
+ }, [overflowMenuOpen]);
20
+ const resolvedOverflowActiveValue = useMemo(() => {
21
+ if (!overflowMenuOpen) {
22
+ return null;
23
+ }
24
+ if (requestedOverflowActiveValue && hiddenValues.includes(requestedOverflowActiveValue)) {
25
+ return requestedOverflowActiveValue;
26
+ }
27
+ return hiddenValues[0] ?? null;
28
+ }, [hiddenValues, overflowMenuOpen, requestedOverflowActiveValue]);
29
+ const hiddenValueSet = useMemo(() => new Set(hiddenValues), [hiddenValues]);
30
+ const visibleValueSet = useMemo(
31
+ () => new Set(visibleValues),
32
+ [visibleValues]
33
+ );
34
+ const getLocation = useCallback(
35
+ (value) => {
36
+ if (visibleValueSet.has(value)) {
37
+ return "main";
38
+ }
39
+ if (menuOpen && hiddenValueSet.has(value)) {
40
+ return "overflow";
41
+ }
42
+ return "hidden";
43
+ },
44
+ [hiddenValueSet, menuOpen, visibleValueSet]
45
+ );
46
+ const moveOverflowFocus = useCallback(
47
+ (key, value) => {
48
+ if (hiddenValues.length < 1) {
49
+ return false;
50
+ }
51
+ const startIndex = getOverflowStartIndex(
52
+ hiddenValues,
53
+ value,
54
+ resolvedOverflowActiveValue
55
+ );
56
+ const lastIndex = hiddenValues.length - 1;
57
+ let nextIndex = startIndex;
58
+ switch (key) {
59
+ case "ArrowDown":
60
+ nextIndex = startIndex >= lastIndex ? 0 : startIndex + 1;
61
+ break;
62
+ case "ArrowUp":
63
+ nextIndex = startIndex <= 0 ? lastIndex : startIndex - 1;
64
+ break;
65
+ case "Home":
66
+ nextIndex = 0;
67
+ break;
68
+ case "End":
69
+ nextIndex = lastIndex;
70
+ break;
71
+ }
72
+ const nextValue = hiddenValues[nextIndex];
73
+ if (!nextValue) {
74
+ return false;
75
+ }
76
+ setRequestedOverflowActiveValue(nextValue);
77
+ return true;
78
+ },
79
+ [hiddenValues, resolvedOverflowActiveValue]
80
+ );
81
+ const tabListLayoutContext = useMemo(
82
+ () => ({
83
+ getLocation,
84
+ overflowActiveValue: resolvedOverflowActiveValue,
85
+ setOverflowActiveValue: setRequestedOverflowActiveValue,
86
+ moveOverflowFocus
87
+ }),
88
+ [getLocation, moveOverflowFocus, resolvedOverflowActiveValue]
89
+ );
90
+ return {
91
+ resolvedOverflowActiveValue,
92
+ tabListLayoutContext
93
+ };
94
+ }
95
+
96
+ export { useOverflowLayoutState };
97
+ //# sourceMappingURL=useOverflowLayoutState.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useOverflowLayoutState.js","sources":["../src/tabs-next/hooks/useOverflowLayoutState.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useState } from \"react\";\nimport type {\n TabListLayoutContextValue,\n TabSlotLocation,\n} from \"../TabListLayoutContext\";\n\nfunction getOverflowStartIndex(\n hiddenValues: string[],\n currentValue: string,\n fallbackValue: string | null,\n) {\n const currentIndex = hiddenValues.indexOf(currentValue);\n const fallbackIndex =\n fallbackValue != null ? hiddenValues.indexOf(fallbackValue) : 0;\n\n return currentIndex >= 0 ? currentIndex : Math.max(0, fallbackIndex);\n}\n\ninterface UseOverflowLayoutStateArgs {\n hiddenValues: string[];\n menuOpen: boolean;\n overflowMenuOpen: boolean;\n visibleValues: string[];\n}\n\ninterface UseOverflowLayoutStateResult {\n resolvedOverflowActiveValue: string | null;\n tabListLayoutContext: TabListLayoutContextValue;\n}\n\nexport function useOverflowLayoutState({\n hiddenValues,\n menuOpen,\n overflowMenuOpen,\n visibleValues,\n}: UseOverflowLayoutStateArgs): UseOverflowLayoutStateResult {\n const [requestedOverflowActiveValue, setRequestedOverflowActiveValue] =\n useState<string | null>(null);\n\n useEffect(() => {\n if (!overflowMenuOpen) {\n setRequestedOverflowActiveValue(null);\n }\n }, [overflowMenuOpen]);\n\n const resolvedOverflowActiveValue = useMemo(() => {\n if (!overflowMenuOpen) {\n return null;\n }\n\n if (\n requestedOverflowActiveValue &&\n hiddenValues.includes(requestedOverflowActiveValue)\n ) {\n return requestedOverflowActiveValue;\n }\n\n return hiddenValues[0] ?? null;\n }, [hiddenValues, overflowMenuOpen, requestedOverflowActiveValue]);\n\n const hiddenValueSet = useMemo(() => new Set(hiddenValues), [hiddenValues]);\n const visibleValueSet = useMemo(\n () => new Set(visibleValues),\n [visibleValues],\n );\n\n const getLocation = useCallback(\n (value: string): TabSlotLocation => {\n if (visibleValueSet.has(value)) {\n return \"main\";\n }\n\n if (menuOpen && hiddenValueSet.has(value)) {\n return \"overflow\";\n }\n\n return \"hidden\";\n },\n [hiddenValueSet, menuOpen, visibleValueSet],\n );\n\n const moveOverflowFocus = useCallback(\n (key: \"ArrowDown\" | \"ArrowUp\" | \"Home\" | \"End\", value: string) => {\n if (hiddenValues.length < 1) {\n return false;\n }\n\n const startIndex = getOverflowStartIndex(\n hiddenValues,\n value,\n resolvedOverflowActiveValue,\n );\n const lastIndex = hiddenValues.length - 1;\n let nextIndex = startIndex;\n\n switch (key) {\n case \"ArrowDown\":\n nextIndex = startIndex >= lastIndex ? 0 : startIndex + 1;\n break;\n case \"ArrowUp\":\n nextIndex = startIndex <= 0 ? lastIndex : startIndex - 1;\n break;\n case \"Home\":\n nextIndex = 0;\n break;\n case \"End\":\n nextIndex = lastIndex;\n break;\n }\n\n const nextValue = hiddenValues[nextIndex];\n if (!nextValue) {\n return false;\n }\n\n setRequestedOverflowActiveValue(nextValue);\n return true;\n },\n [hiddenValues, resolvedOverflowActiveValue],\n );\n\n const tabListLayoutContext = useMemo(\n () => ({\n getLocation,\n overflowActiveValue: resolvedOverflowActiveValue,\n setOverflowActiveValue: setRequestedOverflowActiveValue,\n moveOverflowFocus,\n }),\n [getLocation, moveOverflowFocus, resolvedOverflowActiveValue],\n );\n\n return {\n resolvedOverflowActiveValue,\n tabListLayoutContext,\n };\n}\n"],"names":[],"mappings":";;AAMA,SAAS,qBAAA,CACP,YAAA,EACA,YAAA,EACA,aAAA,EACA;AACA,EAAA,MAAM,YAAA,GAAe,YAAA,CAAa,OAAA,CAAQ,YAAY,CAAA;AACtD,EAAA,MAAM,gBACJ,aAAA,IAAiB,IAAA,GAAO,YAAA,CAAa,OAAA,CAAQ,aAAa,CAAA,GAAI,CAAA;AAEhE,EAAA,OAAO,gBAAgB,CAAA,GAAI,YAAA,GAAe,IAAA,CAAK,GAAA,CAAI,GAAG,aAAa,CAAA;AACrE;AAcO,SAAS,sBAAA,CAAuB;AAAA,EACrC,YAAA;AAAA,EACA,QAAA;AAAA,EACA,gBAAA;AAAA,EACA;AACF,CAAA,EAA6D;AAC3D,EAAA,MAAM,CAAC,4BAAA,EAA8B,+BAA+B,CAAA,GAClE,SAAwB,IAAI,CAAA;AAE9B,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,gBAAA,EAAkB;AACrB,MAAA,+BAAA,CAAgC,IAAI,CAAA;AAAA,IACtC;AAAA,EACF,CAAA,EAAG,CAAC,gBAAgB,CAAC,CAAA;AAErB,EAAA,MAAM,2BAAA,GAA8B,QAAQ,MAAM;AAChD,IAAA,IAAI,CAAC,gBAAA,EAAkB;AACrB,MAAA,OAAO,IAAA;AAAA,IACT;AAEA,IAAA,IACE,4BAAA,IACA,YAAA,CAAa,QAAA,CAAS,4BAA4B,CAAA,EAClD;AACA,MAAA,OAAO,4BAAA;AAAA,IACT;AAEA,IAAA,OAAO,YAAA,CAAa,CAAC,CAAA,IAAK,IAAA;AAAA,EAC5B,CAAA,EAAG,CAAC,YAAA,EAAc,gBAAA,EAAkB,4BAA4B,CAAC,CAAA;AAEjE,EAAA,MAAM,cAAA,GAAiB,QAAQ,MAAM,IAAI,IAAI,YAAY,CAAA,EAAG,CAAC,YAAY,CAAC,CAAA;AAC1E,EAAA,MAAM,eAAA,GAAkB,OAAA;AAAA,IACtB,MAAM,IAAI,GAAA,CAAI,aAAa,CAAA;AAAA,IAC3B,CAAC,aAAa;AAAA,GAChB;AAEA,EAAA,MAAM,WAAA,GAAc,WAAA;AAAA,IAClB,CAAC,KAAA,KAAmC;AAClC,MAAA,IAAI,eAAA,CAAgB,GAAA,CAAI,KAAK,CAAA,EAAG;AAC9B,QAAA,OAAO,MAAA;AAAA,MACT;AAEA,MAAA,IAAI,QAAA,IAAY,cAAA,CAAe,GAAA,CAAI,KAAK,CAAA,EAAG;AACzC,QAAA,OAAO,UAAA;AAAA,MACT;AAEA,MAAA,OAAO,QAAA;AAAA,IACT,CAAA;AAAA,IACA,CAAC,cAAA,EAAgB,QAAA,EAAU,eAAe;AAAA,GAC5C;AAEA,EAAA,MAAM,iBAAA,GAAoB,WAAA;AAAA,IACxB,CAAC,KAA+C,KAAA,KAAkB;AAChE,MAAA,IAAI,YAAA,CAAa,SAAS,CAAA,EAAG;AAC3B,QAAA,OAAO,KAAA;AAAA,MACT;AAEA,MAAA,MAAM,UAAA,GAAa,qBAAA;AAAA,QACjB,YAAA;AAAA,QACA,KAAA;AAAA,QACA;AAAA,OACF;AACA,MAAA,MAAM,SAAA,GAAY,aAAa,MAAA,GAAS,CAAA;AACxC,MAAA,IAAI,SAAA,GAAY,UAAA;AAEhB,MAAA,QAAQ,GAAA;AAAK,QACX,KAAK,WAAA;AACH,UAAA,SAAA,GAAY,UAAA,IAAc,SAAA,GAAY,CAAA,GAAI,UAAA,GAAa,CAAA;AACvD,UAAA;AAAA,QACF,KAAK,SAAA;AACH,UAAA,SAAA,GAAY,UAAA,IAAc,CAAA,GAAI,SAAA,GAAY,UAAA,GAAa,CAAA;AACvD,UAAA;AAAA,QACF,KAAK,MAAA;AACH,UAAA,SAAA,GAAY,CAAA;AACZ,UAAA;AAAA,QACF,KAAK,KAAA;AACH,UAAA,SAAA,GAAY,SAAA;AACZ,UAAA;AAAA;AAGJ,MAAA,MAAM,SAAA,GAAY,aAAa,SAAS,CAAA;AACxC,MAAA,IAAI,CAAC,SAAA,EAAW;AACd,QAAA,OAAO,KAAA;AAAA,MACT;AAEA,MAAA,+BAAA,CAAgC,SAAS,CAAA;AACzC,MAAA,OAAO,IAAA;AAAA,IACT,CAAA;AAAA,IACA,CAAC,cAAc,2BAA2B;AAAA,GAC5C;AAEA,EAAA,MAAM,oBAAA,GAAuB,OAAA;AAAA,IAC3B,OAAO;AAAA,MACL,WAAA;AAAA,MACA,mBAAA,EAAqB,2BAAA;AAAA,MACrB,sBAAA,EAAwB,+BAAA;AAAA,MACxB;AAAA,KACF,CAAA;AAAA,IACA,CAAC,WAAA,EAAa,iBAAA,EAAmB,2BAA2B;AAAA,GAC9D;AAEA,EAAA,OAAO;AAAA,IACL,2BAAA;AAAA,IACA;AAAA,GACF;AACF;;;;"}
@@ -0,0 +1,58 @@
1
+ import { usePrevious, useIsomorphicLayoutEffect } from '@salt-ds/core';
2
+ import { useRef, useCallback, useEffect } from 'react';
3
+
4
+ function useOverflowSelectionState({
5
+ commitSelection,
6
+ menuOpen,
7
+ selected,
8
+ setMenuOpen
9
+ }) {
10
+ const previousSelected = usePrevious(selected, [selected]);
11
+ const selectionFromOverflowValueRef = useRef(null);
12
+ const pendingOverflowSelectionRef = useRef(
13
+ null
14
+ );
15
+ const setSelected = useCallback(
16
+ (event, value, source = "main") => {
17
+ const selectedFromOverflow = source === "overflow";
18
+ selectionFromOverflowValueRef.current = selectedFromOverflow ? value : null;
19
+ if (selectedFromOverflow) {
20
+ pendingOverflowSelectionRef.current = { event, value };
21
+ setMenuOpen(false);
22
+ return;
23
+ }
24
+ pendingOverflowSelectionRef.current = null;
25
+ setMenuOpen(false);
26
+ commitSelection(event, value);
27
+ },
28
+ [commitSelection, setMenuOpen]
29
+ );
30
+ useIsomorphicLayoutEffect(() => {
31
+ if (menuOpen) {
32
+ return;
33
+ }
34
+ const pendingSelection = pendingOverflowSelectionRef.current;
35
+ if (!pendingSelection) {
36
+ return;
37
+ }
38
+ pendingOverflowSelectionRef.current = null;
39
+ commitSelection(pendingSelection.event, pendingSelection.value);
40
+ }, [commitSelection, menuOpen]);
41
+ useEffect(() => {
42
+ const selectedFromOverflow = selectionFromOverflowValueRef.current;
43
+ if (selectedFromOverflow == null || pendingOverflowSelectionRef.current) {
44
+ return;
45
+ }
46
+ if (selected === selectedFromOverflow && selected !== previousSelected) {
47
+ return;
48
+ }
49
+ selectionFromOverflowValueRef.current = null;
50
+ }, [previousSelected, selected]);
51
+ return {
52
+ selectionFromOverflowValueRef,
53
+ setSelected
54
+ };
55
+ }
56
+
57
+ export { useOverflowSelectionState };
58
+ //# sourceMappingURL=useOverflowSelectionState.js.map