@salt-ds/lab 1.0.0-alpha.88 → 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 (171) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/css/salt-lab.css +53 -25
  3. package/dist-cjs/list-deprecated/ListStateContext.js +1 -1
  4. package/dist-cjs/list-deprecated/ListStateContext.js.map +1 -1
  5. package/dist-cjs/tabs-next/TabBar.css.js +1 -1
  6. package/dist-cjs/tabs-next/TabBar.js +1 -1
  7. package/dist-cjs/tabs-next/TabBar.js.map +1 -1
  8. package/dist-cjs/tabs-next/TabListLayoutContext.js +13 -0
  9. package/dist-cjs/tabs-next/TabListLayoutContext.js.map +1 -0
  10. package/dist-cjs/tabs-next/TabListNext.css.js +1 -1
  11. package/dist-cjs/tabs-next/TabListNext.js +179 -33
  12. package/dist-cjs/tabs-next/TabListNext.js.map +1 -1
  13. package/dist-cjs/tabs-next/TabNext.js +111 -7
  14. package/dist-cjs/tabs-next/TabNext.js.map +1 -1
  15. package/dist-cjs/tabs-next/TabNextAction.js +25 -2
  16. package/dist-cjs/tabs-next/TabNextAction.js.map +1 -1
  17. package/dist-cjs/tabs-next/TabNextPanel.js +31 -16
  18. package/dist-cjs/tabs-next/TabNextPanel.js.map +1 -1
  19. package/dist-cjs/tabs-next/TabNextTrigger.js +110 -9
  20. package/dist-cjs/tabs-next/TabNextTrigger.js.map +1 -1
  21. package/dist-cjs/tabs-next/TabOverflowList.css.js +1 -1
  22. package/dist-cjs/tabs-next/TabOverflowList.js +168 -64
  23. package/dist-cjs/tabs-next/TabOverflowList.js.map +1 -1
  24. package/dist-cjs/tabs-next/TabSlot.js +30 -0
  25. package/dist-cjs/tabs-next/TabSlot.js.map +1 -0
  26. package/dist-cjs/tabs-next/TabSlotRegistryContext.js +16 -0
  27. package/dist-cjs/tabs-next/TabSlotRegistryContext.js.map +1 -0
  28. package/dist-cjs/tabs-next/TabsNext.css.js +6 -0
  29. package/dist-cjs/tabs-next/TabsNext.css.js.map +1 -0
  30. package/dist-cjs/tabs-next/TabsNext.js +113 -47
  31. package/dist-cjs/tabs-next/TabsNext.js.map +1 -1
  32. package/dist-cjs/tabs-next/TabsNextContext.js +17 -3
  33. package/dist-cjs/tabs-next/TabsNextContext.js.map +1 -1
  34. package/dist-cjs/tabs-next/domUtils.js +13 -0
  35. package/dist-cjs/tabs-next/domUtils.js.map +1 -0
  36. package/dist-cjs/tabs-next/hooks/overflowMath.js +86 -0
  37. package/dist-cjs/tabs-next/hooks/overflowMath.js.map +1 -0
  38. package/dist-cjs/tabs-next/hooks/useCollection.js +147 -41
  39. package/dist-cjs/tabs-next/hooks/useCollection.js.map +1 -1
  40. package/dist-cjs/tabs-next/hooks/useFocusWithRetry.js +64 -0
  41. package/dist-cjs/tabs-next/hooks/useFocusWithRetry.js.map +1 -0
  42. package/dist-cjs/tabs-next/hooks/useOverflow.js +240 -156
  43. package/dist-cjs/tabs-next/hooks/useOverflow.js.map +1 -1
  44. package/dist-cjs/tabs-next/hooks/useOverflowLayoutState.js +99 -0
  45. package/dist-cjs/tabs-next/hooks/useOverflowLayoutState.js.map +1 -0
  46. package/dist-cjs/tabs-next/hooks/useOverflowSelectionState.js +60 -0
  47. package/dist-cjs/tabs-next/hooks/useOverflowSelectionState.js.map +1 -0
  48. package/dist-cjs/tabs-next/hooks/useRenderedTabWidth.js +92 -0
  49. package/dist-cjs/tabs-next/hooks/useRenderedTabWidth.js.map +1 -0
  50. package/dist-cjs/tabs-next/hooks/useRenderedTabsRegistry.js +200 -0
  51. package/dist-cjs/tabs-next/hooks/useRenderedTabsRegistry.js.map +1 -0
  52. package/dist-cjs/tabs-next/hooks/useTabListRecovery.js +76 -0
  53. package/dist-cjs/tabs-next/hooks/useTabListRecovery.js.map +1 -0
  54. package/dist-cjs/tabs-next/hooks/useTabRemovalHandler.js +165 -0
  55. package/dist-cjs/tabs-next/hooks/useTabRemovalHandler.js.map +1 -0
  56. package/dist-cjs/tabs-next/hooks/useTabSelectionFocus.js +80 -0
  57. package/dist-cjs/tabs-next/hooks/useTabSelectionFocus.js.map +1 -0
  58. package/dist-cjs/tabs-next/widthMeasurement.js +42 -0
  59. package/dist-cjs/tabs-next/widthMeasurement.js.map +1 -0
  60. package/dist-cjs/tree/Tree.css.js +1 -1
  61. package/dist-cjs/tree/TreeNode.css.js +1 -1
  62. package/dist-cjs/tree/TreeNode.js +1 -1
  63. package/dist-cjs/tree/TreeNode.js.map +1 -1
  64. package/dist-cjs/tree/TreeNodeExpansionIcon.css.js +1 -1
  65. package/dist-cjs/tree/TreeNodeTrigger.css.js +1 -1
  66. package/dist-cjs/tree/TreeNodeTrigger.js +2 -2
  67. package/dist-cjs/tree/TreeNodeTrigger.js.map +1 -1
  68. package/dist-cjs/utils/useEventCallback.js +5 -5
  69. package/dist-cjs/utils/useEventCallback.js.map +1 -1
  70. package/dist-es/list-deprecated/ListStateContext.js +1 -1
  71. package/dist-es/list-deprecated/ListStateContext.js.map +1 -1
  72. package/dist-es/tabs-next/TabBar.css.js +1 -1
  73. package/dist-es/tabs-next/TabBar.js +1 -1
  74. package/dist-es/tabs-next/TabBar.js.map +1 -1
  75. package/dist-es/tabs-next/TabListLayoutContext.js +10 -0
  76. package/dist-es/tabs-next/TabListLayoutContext.js.map +1 -0
  77. package/dist-es/tabs-next/TabListNext.css.js +1 -1
  78. package/dist-es/tabs-next/TabListNext.js +182 -36
  79. package/dist-es/tabs-next/TabListNext.js.map +1 -1
  80. package/dist-es/tabs-next/TabNext.js +113 -9
  81. package/dist-es/tabs-next/TabNext.js.map +1 -1
  82. package/dist-es/tabs-next/TabNextAction.js +25 -2
  83. package/dist-es/tabs-next/TabNextAction.js.map +1 -1
  84. package/dist-es/tabs-next/TabNextPanel.js +31 -16
  85. package/dist-es/tabs-next/TabNextPanel.js.map +1 -1
  86. package/dist-es/tabs-next/TabNextTrigger.js +110 -9
  87. package/dist-es/tabs-next/TabNextTrigger.js.map +1 -1
  88. package/dist-es/tabs-next/TabOverflowList.css.js +1 -1
  89. package/dist-es/tabs-next/TabOverflowList.js +172 -68
  90. package/dist-es/tabs-next/TabOverflowList.js.map +1 -1
  91. package/dist-es/tabs-next/TabSlot.js +28 -0
  92. package/dist-es/tabs-next/TabSlot.js.map +1 -0
  93. package/dist-es/tabs-next/TabSlotRegistryContext.js +13 -0
  94. package/dist-es/tabs-next/TabSlotRegistryContext.js.map +1 -0
  95. package/dist-es/tabs-next/TabsNext.css.js +4 -0
  96. package/dist-es/tabs-next/TabsNext.css.js.map +1 -0
  97. package/dist-es/tabs-next/TabsNext.js +114 -48
  98. package/dist-es/tabs-next/TabsNext.js.map +1 -1
  99. package/dist-es/tabs-next/TabsNextContext.js +17 -3
  100. package/dist-es/tabs-next/TabsNextContext.js.map +1 -1
  101. package/dist-es/tabs-next/domUtils.js +11 -0
  102. package/dist-es/tabs-next/domUtils.js.map +1 -0
  103. package/dist-es/tabs-next/hooks/overflowMath.js +82 -0
  104. package/dist-es/tabs-next/hooks/overflowMath.js.map +1 -0
  105. package/dist-es/tabs-next/hooks/useCollection.js +148 -42
  106. package/dist-es/tabs-next/hooks/useCollection.js.map +1 -1
  107. package/dist-es/tabs-next/hooks/useFocusWithRetry.js +62 -0
  108. package/dist-es/tabs-next/hooks/useFocusWithRetry.js.map +1 -0
  109. package/dist-es/tabs-next/hooks/useOverflow.js +242 -158
  110. package/dist-es/tabs-next/hooks/useOverflow.js.map +1 -1
  111. package/dist-es/tabs-next/hooks/useOverflowLayoutState.js +97 -0
  112. package/dist-es/tabs-next/hooks/useOverflowLayoutState.js.map +1 -0
  113. package/dist-es/tabs-next/hooks/useOverflowSelectionState.js +58 -0
  114. package/dist-es/tabs-next/hooks/useOverflowSelectionState.js.map +1 -0
  115. package/dist-es/tabs-next/hooks/useRenderedTabWidth.js +90 -0
  116. package/dist-es/tabs-next/hooks/useRenderedTabWidth.js.map +1 -0
  117. package/dist-es/tabs-next/hooks/useRenderedTabsRegistry.js +198 -0
  118. package/dist-es/tabs-next/hooks/useRenderedTabsRegistry.js.map +1 -0
  119. package/dist-es/tabs-next/hooks/useTabListRecovery.js +74 -0
  120. package/dist-es/tabs-next/hooks/useTabListRecovery.js.map +1 -0
  121. package/dist-es/tabs-next/hooks/useTabRemovalHandler.js +163 -0
  122. package/dist-es/tabs-next/hooks/useTabRemovalHandler.js.map +1 -0
  123. package/dist-es/tabs-next/hooks/useTabSelectionFocus.js +78 -0
  124. package/dist-es/tabs-next/hooks/useTabSelectionFocus.js.map +1 -0
  125. package/dist-es/tabs-next/widthMeasurement.js +36 -0
  126. package/dist-es/tabs-next/widthMeasurement.js.map +1 -0
  127. package/dist-es/tree/Tree.css.js +1 -1
  128. package/dist-es/tree/TreeNode.css.js +1 -1
  129. package/dist-es/tree/TreeNode.js +1 -1
  130. package/dist-es/tree/TreeNode.js.map +1 -1
  131. package/dist-es/tree/TreeNodeExpansionIcon.css.js +1 -1
  132. package/dist-es/tree/TreeNodeTrigger.css.js +1 -1
  133. package/dist-es/tree/TreeNodeTrigger.js +2 -2
  134. package/dist-es/tree/TreeNodeTrigger.js.map +1 -1
  135. package/dist-es/utils/useEventCallback.js +5 -5
  136. package/dist-es/utils/useEventCallback.js.map +1 -1
  137. package/dist-types/cascading-menu/internal/useMenuTriggerHandlers.d.ts +1 -1
  138. package/dist-types/list-deprecated/ListStateContext.d.ts +7 -2
  139. package/dist-types/tabs-next/TabListLayoutContext.d.ts +9 -0
  140. package/dist-types/tabs-next/TabNext.d.ts +1 -1
  141. package/dist-types/tabs-next/TabNextPanel.d.ts +2 -1
  142. package/dist-types/tabs-next/TabOverflowList.d.ts +3 -4
  143. package/dist-types/tabs-next/TabSlot.d.ts +6 -0
  144. package/dist-types/tabs-next/TabSlotRegistryContext.d.ts +5 -0
  145. package/dist-types/tabs-next/TabsNext.d.ts +2 -1
  146. package/dist-types/tabs-next/TabsNextContext.d.ts +26 -4
  147. package/dist-types/tabs-next/domUtils.d.ts +1 -0
  148. package/dist-types/tabs-next/hooks/overflowMath.d.ts +18 -0
  149. package/dist-types/tabs-next/hooks/useCollection.d.ts +15 -3
  150. package/dist-types/tabs-next/hooks/useFocusWithRetry.d.ts +9 -0
  151. package/dist-types/tabs-next/hooks/useOverflow.d.ts +5 -5
  152. package/dist-types/tabs-next/hooks/useOverflowLayoutState.d.ts +13 -0
  153. package/dist-types/tabs-next/hooks/useOverflowSelectionState.d.ts +13 -0
  154. package/dist-types/tabs-next/hooks/useRenderedTabWidth.d.ts +12 -0
  155. package/dist-types/tabs-next/hooks/useRenderedTabsRegistry.d.ts +12 -0
  156. package/dist-types/tabs-next/hooks/useTabListRecovery.d.ts +12 -0
  157. package/dist-types/tabs-next/hooks/useTabRemovalHandler.d.ts +32 -0
  158. package/dist-types/tabs-next/hooks/useTabSelectionFocus.d.ts +15 -0
  159. package/dist-types/tabs-next/widthMeasurement.d.ts +5 -0
  160. package/dist-types/utils/useEventCallback.d.ts +1 -1
  161. package/package.json +2 -2
  162. package/dist-cjs/tabs-next/hooks/useFocusOutside.js +0 -25
  163. package/dist-cjs/tabs-next/hooks/useFocusOutside.js.map +0 -1
  164. package/dist-cjs/tabs-next/hooks/useRestoreActiveTab.js +0 -93
  165. package/dist-cjs/tabs-next/hooks/useRestoreActiveTab.js.map +0 -1
  166. package/dist-es/tabs-next/hooks/useFocusOutside.js +0 -23
  167. package/dist-es/tabs-next/hooks/useFocusOutside.js.map +0 -1
  168. package/dist-es/tabs-next/hooks/useRestoreActiveTab.js +0 -91
  169. package/dist-es/tabs-next/hooks/useRestoreActiveTab.js.map +0 -1
  170. package/dist-types/tabs-next/hooks/useFocusOutside.d.ts +0 -2
  171. package/dist-types/tabs-next/hooks/useRestoreActiveTab.d.ts +0 -10
@@ -1,181 +1,265 @@
1
1
  'use strict';
2
2
 
3
3
  var core = require('@salt-ds/core');
4
- var window = require('@salt-ds/window');
5
4
  var react = require('react');
5
+ var domUtils = require('../domUtils.js');
6
+ var widthMeasurement = require('../widthMeasurement.js');
7
+ var overflowMath = require('./overflowMath.js');
6
8
 
7
- function getTabWidth(element) {
8
- const { width } = element.getBoundingClientRect();
9
- return Math.ceil(width);
9
+ function getTabWidth(tab) {
10
+ const width = tab.width || widthMeasurement.getMeasuredWidth(tab.root);
11
+ return width > overflowMath.MIN_TRUSTED_TAB_WIDTH ? width : null;
12
+ }
13
+ function getAvailableWidth(element) {
14
+ const parent = element.parentElement;
15
+ if (!parent) {
16
+ return widthMeasurement.getMeasuredWidth(element);
17
+ }
18
+ const parentWidth = widthMeasurement.getMeasuredWidth(parent);
19
+ const parentStyles = core.ownerWindow(parent).getComputedStyle(parent);
20
+ const parentGap = widthMeasurement.getGapValue(parentStyles);
21
+ const siblings = Array.from(parent.children).filter(
22
+ (child) => {
23
+ if (!domUtils.isHTMLElement(child) || child === element) {
24
+ return false;
25
+ }
26
+ return core.ownerWindow(child).getComputedStyle(child).display !== "none";
27
+ }
28
+ );
29
+ const siblingWidth = siblings.reduce((width, sibling) => {
30
+ return width + widthMeasurement.getMeasuredWidth(sibling);
31
+ }, 0);
32
+ const gapCount = siblings.length > 0 ? siblings.length : 0;
33
+ const availableWidth = Math.max(
34
+ 0,
35
+ parentWidth - siblingWidth - gapCount * parentGap
36
+ );
37
+ return availableWidth;
38
+ }
39
+ function isSelectedValueHidden(selected, hiddenValues) {
40
+ return selected !== void 0 && hiddenValues.includes(selected);
41
+ }
42
+ function getPinnedSelectionValue(selected, selectedIsHidden, pinnedSelectionRef) {
43
+ return selectedIsHidden ? selected : pinnedSelectionRef.current;
10
44
  }
11
45
  function useOverflow({
12
- tabs,
13
46
  container,
14
47
  overflowButton,
15
- children,
16
- selected
48
+ tabs,
49
+ selected,
50
+ menuOpen
17
51
  }) {
18
- const [{ visibleCount, isMeasuring }, setVisibleItems] = core.useValueEffect({
19
- visibleCount: tabs.length,
20
- isMeasuring: false
21
- });
22
- const targetWindow = window.useWindow();
23
- const realSelectedIndex = react.useRef(-1);
24
- const updateOverflow = core.useEventCallback(() => {
25
- const computeVisible = (visibleCount2) => {
26
- var _a;
27
- if (container.current && targetWindow) {
28
- const items = Array.from(
29
- container.current.querySelectorAll(
30
- "[data-overflowitem]"
31
- )
32
- );
33
- const selectedTab = (_a = container.current.querySelector(
34
- "[role=tab][aria-selected=true]"
35
- )) == null ? void 0 : _a.parentElement;
36
- let maxWidth = container.current.clientWidth ?? 0;
37
- const containerStyles = targetWindow.getComputedStyle(
38
- container.current
39
- );
40
- const gap = Number.parseInt(containerStyles.gap || "0", 10);
41
- let currentWidth = 0;
42
- let newVisibleCount = 0;
43
- const visible2 = [];
44
- while (newVisibleCount < items.length) {
45
- const element = items[newVisibleCount];
46
- if (element) {
47
- if (currentWidth + getTabWidth(element) + gap > maxWidth) {
48
- break;
49
- }
50
- currentWidth += getTabWidth(element) + gap;
51
- visible2.push(element);
52
- }
53
- newVisibleCount++;
54
- }
55
- if (newVisibleCount >= items.length) {
56
- return newVisibleCount;
57
- }
58
- const overflowButtonWidth = overflowButton.current ? overflowButton.current.offsetWidth + gap : 0;
59
- maxWidth -= overflowButtonWidth;
60
- while (currentWidth > maxWidth) {
61
- const removed = visible2.pop();
62
- if (!removed) break;
63
- currentWidth -= getTabWidth(removed) + gap;
64
- newVisibleCount--;
65
- }
66
- if (selectedTab && !visible2.includes(selectedTab)) {
67
- const selectedTabWidth = getTabWidth(selectedTab) + gap;
68
- while (currentWidth + selectedTabWidth > maxWidth) {
69
- const removed = visible2.pop();
70
- if (!removed) break;
71
- currentWidth -= getTabWidth(removed) + gap;
72
- newVisibleCount--;
73
- }
74
- }
75
- return Math.max(0, newVisibleCount);
76
- }
77
- return visibleCount2;
78
- };
79
- setVisibleItems(function* () {
80
- yield {
81
- visibleCount: tabs.length,
82
- isMeasuring: true
83
- };
84
- const newVisibleCount = computeVisible(tabs.length);
85
- const isMeasuring2 = newVisibleCount < tabs.length && newVisibleCount > 0;
86
- yield {
87
- visibleCount: newVisibleCount,
88
- isMeasuring: isMeasuring2
89
- };
90
- if (isMeasuring2) {
91
- yield {
92
- visibleCount: computeVisible(newVisibleCount),
93
- isMeasuring: false
94
- };
52
+ const orderedValues = react.useMemo(() => tabs.map((tab) => tab.value), [tabs]);
53
+ const measurementInputKey = react.useMemo(() => {
54
+ return tabs.map((tab) => `${tab.value}:${tab.width.toFixed(2)}`).join("\0");
55
+ }, [tabs]);
56
+ const [visibleCount, setVisibleCount] = react.useState(0);
57
+ const [isMeasuring, setIsMeasuring] = react.useState(true);
58
+ const [measureRetryVersion, setMeasureRetryVersion] = react.useState(0);
59
+ const pinnedSelectionRef = react.useRef(selected);
60
+ const previousOverflowButtonWidthRef = react.useRef(0);
61
+ const previousMeasurementInputKeyRef = react.useRef(measurementInputKey);
62
+ const previousMenuOpenRef = react.useRef(menuOpen);
63
+ const measureRetryFrameRef = react.useRef(null);
64
+ const measureRetryCountRef = react.useRef(0);
65
+ const baseHiddenValues = orderedValues.slice(visibleCount);
66
+ const selectedIsHidden = isSelectedValueHidden(selected, baseHiddenValues);
67
+ const pinnedValue = getPinnedSelectionValue(
68
+ selected,
69
+ selectedIsHidden,
70
+ pinnedSelectionRef
71
+ );
72
+ const getCurrentPinnedValue = react.useCallback(() => {
73
+ return getPinnedSelectionValue(
74
+ selected,
75
+ selectedIsHidden,
76
+ pinnedSelectionRef
77
+ );
78
+ }, [selected, selectedIsHidden]);
79
+ const markMeasurementStale = react.useCallback(() => {
80
+ setIsMeasuring(true);
81
+ }, []);
82
+ const measureVisibleCount = react.useCallback(
83
+ (pinnedValue2) => {
84
+ const element = container.current;
85
+ if (!element) {
86
+ return null;
95
87
  }
96
- });
97
- });
98
- core.useIsomorphicLayoutEffect(() => {
99
- updateOverflow();
100
- }, [selected]);
101
- react.useEffect(() => {
102
- const handleWindowResize = () => {
103
- updateOverflow();
104
- };
105
- targetWindow == null ? void 0 : targetWindow.addEventListener("resize", handleWindowResize);
106
- return () => {
107
- targetWindow == null ? void 0 : targetWindow.removeEventListener("resize", handleWindowResize);
108
- };
109
- }, [updateOverflow, targetWindow]);
110
- react.useEffect(() => {
111
- const element = container == null ? void 0 : container.current;
112
- if (!element) return;
113
- const win = core.ownerWindow(element);
114
- const resizeObserver = new win.ResizeObserver((entries) => {
115
- requestAnimationFrame(() => {
116
- if (entries.length === 0) return;
117
- updateOverflow();
88
+ const maxWidth = getAvailableWidth(element);
89
+ const styles = core.ownerWindow(element).getComputedStyle(element);
90
+ const gap = widthMeasurement.getGapValue(styles);
91
+ const overflowWidth = overflowButton.current ? overflowButton.current.offsetWidth + gap : 0;
92
+ const measuredTabs = tabs.map((tab) => ({
93
+ value: tab.value,
94
+ width: getTabWidth(tab)
95
+ }));
96
+ return overflowMath.calculateVisibleCount({
97
+ gap,
98
+ maxWidth,
99
+ overflowWidth,
100
+ pinnedValue: pinnedValue2,
101
+ tabs: measuredTabs
118
102
  });
119
- });
120
- resizeObserver.observe(element);
121
- if (element.parentElement) {
122
- resizeObserver.observe(element.parentElement);
103
+ },
104
+ [container, overflowButton, tabs]
105
+ );
106
+ const clearMeasureRetry = react.useCallback(() => {
107
+ const element = container.current;
108
+ const frame = measureRetryFrameRef.current;
109
+ if (element && frame != null) {
110
+ core.ownerWindow(element).cancelAnimationFrame(frame);
123
111
  }
124
- return () => {
125
- if (element) {
126
- resizeObserver.unobserve(element);
112
+ measureRetryFrameRef.current = null;
113
+ measureRetryCountRef.current = 0;
114
+ }, [container]);
115
+ react.useEffect(() => {
116
+ return clearMeasureRetry;
117
+ }, [clearMeasureRetry]);
118
+ core.useIsomorphicLayoutEffect(() => {
119
+ if (selected !== void 0 && selectedIsHidden) {
120
+ pinnedSelectionRef.current = selected;
121
+ const nextVisibleCount = measureVisibleCount(selected);
122
+ if (nextVisibleCount == null) {
123
+ markMeasurementStale();
124
+ return;
127
125
  }
128
- };
129
- }, [container, updateOverflow]);
126
+ if (nextVisibleCount !== visibleCount) {
127
+ setVisibleCount(nextVisibleCount);
128
+ }
129
+ if (isMeasuring) {
130
+ setIsMeasuring(false);
131
+ }
132
+ }
133
+ }, [
134
+ isMeasuring,
135
+ markMeasurementStale,
136
+ measureVisibleCount,
137
+ selected,
138
+ selectedIsHidden,
139
+ visibleCount
140
+ ]);
130
141
  react.useEffect(() => {
131
- const element = container == null ? void 0 : container.current;
132
- if (!element || isMeasuring) return;
133
- const win = core.ownerWindow(element);
134
- const mutationObserver = new win.MutationObserver(() => {
135
- requestAnimationFrame(() => {
136
- updateOverflow();
137
- });
138
- });
139
- mutationObserver.observe(element, {
140
- childList: true
141
- });
142
+ const element = container.current;
143
+ if (!element || menuOpen || isMeasuring) {
144
+ return;
145
+ }
146
+ const observedElements = [element];
147
+ const parent = element.parentElement;
148
+ if (parent) {
149
+ observedElements.push(parent);
150
+ for (const child of Array.from(parent.children)) {
151
+ if (domUtils.isHTMLElement(child) && child !== element) {
152
+ observedElements.push(child);
153
+ }
154
+ }
155
+ }
156
+ const widths = widthMeasurement.seedWidthMap(observedElements);
157
+ const resizeObserverCtor = core.ownerWindow(element).ResizeObserver;
158
+ if (!resizeObserverCtor) {
159
+ return;
160
+ }
161
+ const resizeObserver = new resizeObserverCtor(
162
+ (entries) => {
163
+ for (const entry of entries) {
164
+ if (!domUtils.isHTMLElement(entry.target)) {
165
+ continue;
166
+ }
167
+ const nextWidth = entry.contentRect.width;
168
+ if (widthMeasurement.updateWidthMap(widths, entry.target, nextWidth)) {
169
+ const nextVisibleCount = measureVisibleCount(
170
+ getCurrentPinnedValue()
171
+ );
172
+ if (nextVisibleCount != null && nextVisibleCount === visibleCount) {
173
+ continue;
174
+ }
175
+ markMeasurementStale();
176
+ return;
177
+ }
178
+ }
179
+ }
180
+ );
181
+ for (const observedElement of observedElements) {
182
+ resizeObserver.observe(observedElement);
183
+ }
142
184
  return () => {
143
- mutationObserver.disconnect();
185
+ resizeObserver.disconnect();
144
186
  };
145
- }, [container, updateOverflow, isMeasuring]);
146
- const childArray = react.useMemo(() => react.Children.toArray(children), [children]);
147
- const visible = react.useMemo(
148
- () => childArray.slice(0, visibleCount),
149
- [visibleCount, childArray]
150
- );
151
- const hidden = react.useMemo(
152
- () => childArray.slice(visibleCount),
153
- [childArray, visibleCount]
154
- );
155
- const hiddenSelectedIndex = hidden.findIndex(
156
- (child) => {
157
- var _a;
158
- return react.isValidElement(child) && ((_a = child == null ? void 0 : child.props) == null ? void 0 : _a.value) === selected;
187
+ }, [
188
+ container,
189
+ getCurrentPinnedValue,
190
+ isMeasuring,
191
+ markMeasurementStale,
192
+ measureVisibleCount,
193
+ menuOpen,
194
+ visibleCount
195
+ ]);
196
+ core.useIsomorphicLayoutEffect(() => {
197
+ if (previousMenuOpenRef.current && !menuOpen) {
198
+ markMeasurementStale();
159
199
  }
160
- );
200
+ previousMenuOpenRef.current = menuOpen;
201
+ }, [markMeasurementStale, menuOpen]);
202
+ core.useIsomorphicLayoutEffect(() => {
203
+ var _a;
204
+ const nextOverflowButtonWidth = ((_a = overflowButton.current) == null ? void 0 : _a.offsetWidth) ?? 0;
205
+ if (previousOverflowButtonWidthRef.current === nextOverflowButtonWidth) {
206
+ return;
207
+ }
208
+ previousOverflowButtonWidthRef.current = nextOverflowButtonWidth;
209
+ if (visibleCount < tabs.length) {
210
+ markMeasurementStale();
211
+ }
212
+ });
213
+ core.useIsomorphicLayoutEffect(() => {
214
+ if (previousMeasurementInputKeyRef.current !== measurementInputKey) {
215
+ previousMeasurementInputKeyRef.current = measurementInputKey;
216
+ markMeasurementStale();
217
+ }
218
+ }, [markMeasurementStale, measurementInputKey]);
161
219
  core.useIsomorphicLayoutEffect(() => {
162
- if (visibleCount === childArray.length) {
163
- realSelectedIndex.current = childArray.findIndex(
164
- (child) => {
165
- var _a;
166
- return react.isValidElement(child) && ((_a = child == null ? void 0 : child.props) == null ? void 0 : _a.value) === selected;
220
+ if (!isMeasuring || menuOpen) {
221
+ return;
222
+ }
223
+ const nextVisibleCount = measureVisibleCount(getCurrentPinnedValue());
224
+ if (nextVisibleCount == null) {
225
+ if (measureRetryFrameRef.current != null) {
226
+ return;
227
+ }
228
+ const element = container.current;
229
+ if (!element || widthMeasurement.getMeasuredWidth(element) <= overflowMath.MIN_TRUSTED_TAB_WIDTH) {
230
+ measureRetryCountRef.current = 0;
231
+ return;
232
+ }
233
+ if (measureRetryCountRef.current >= 5) {
234
+ return;
235
+ }
236
+ measureRetryCountRef.current += 1;
237
+ measureRetryFrameRef.current = core.ownerWindow(element).requestAnimationFrame(
238
+ () => {
239
+ measureRetryFrameRef.current = null;
240
+ setMeasureRetryVersion((currentVersion) => currentVersion + 1);
167
241
  }
168
242
  );
243
+ return;
169
244
  }
170
- }, [visibleCount, childArray, selected]);
171
- if (selected && hiddenSelectedIndex !== -1) {
172
- const removed = hidden.splice(hiddenSelectedIndex, 1);
173
- visible.push(removed[0]);
174
- }
175
- if (isMeasuring) {
176
- return [childArray, [], isMeasuring, realSelectedIndex];
177
- }
178
- return [visible, hidden, isMeasuring, realSelectedIndex];
245
+ clearMeasureRetry();
246
+ setVisibleCount(nextVisibleCount);
247
+ setIsMeasuring(false);
248
+ }, [
249
+ clearMeasureRetry,
250
+ container.current,
251
+ getCurrentPinnedValue,
252
+ isMeasuring,
253
+ measureRetryVersion,
254
+ measureVisibleCount,
255
+ menuOpen
256
+ ]);
257
+ const { visibleValues, hiddenValues } = overflowMath.partitionVisibleValues(
258
+ orderedValues,
259
+ visibleCount,
260
+ pinnedValue
261
+ );
262
+ return [visibleValues, hiddenValues, isMeasuring];
179
263
  }
180
264
 
181
265
  exports.useOverflow = 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":["useValueEffect","useWindow","useRef","useEventCallback","visibleCount","visible","isMeasuring","useIsomorphicLayoutEffect","useEffect","ownerWindow","useMemo","Children","isValidElement"],"mappings":"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"}
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":["getMeasuredWidth","MIN_TRUSTED_TAB_WIDTH","ownerWindow","getGapValue","isHTMLElement","useMemo","useState","useRef","useCallback","pinnedValue","calculateVisibleCount","useEffect","useIsomorphicLayoutEffect","seedWidthMap","updateWidthMap","partitionVisibleValues"],"mappings":"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"}
@@ -0,0 +1,99 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+
5
+ function getOverflowStartIndex(hiddenValues, currentValue, fallbackValue) {
6
+ const currentIndex = hiddenValues.indexOf(currentValue);
7
+ const fallbackIndex = fallbackValue != null ? hiddenValues.indexOf(fallbackValue) : 0;
8
+ return currentIndex >= 0 ? currentIndex : Math.max(0, fallbackIndex);
9
+ }
10
+ function useOverflowLayoutState({
11
+ hiddenValues,
12
+ menuOpen,
13
+ overflowMenuOpen,
14
+ visibleValues
15
+ }) {
16
+ const [requestedOverflowActiveValue, setRequestedOverflowActiveValue] = react.useState(null);
17
+ react.useEffect(() => {
18
+ if (!overflowMenuOpen) {
19
+ setRequestedOverflowActiveValue(null);
20
+ }
21
+ }, [overflowMenuOpen]);
22
+ const resolvedOverflowActiveValue = react.useMemo(() => {
23
+ if (!overflowMenuOpen) {
24
+ return null;
25
+ }
26
+ if (requestedOverflowActiveValue && hiddenValues.includes(requestedOverflowActiveValue)) {
27
+ return requestedOverflowActiveValue;
28
+ }
29
+ return hiddenValues[0] ?? null;
30
+ }, [hiddenValues, overflowMenuOpen, requestedOverflowActiveValue]);
31
+ const hiddenValueSet = react.useMemo(() => new Set(hiddenValues), [hiddenValues]);
32
+ const visibleValueSet = react.useMemo(
33
+ () => new Set(visibleValues),
34
+ [visibleValues]
35
+ );
36
+ const getLocation = react.useCallback(
37
+ (value) => {
38
+ if (visibleValueSet.has(value)) {
39
+ return "main";
40
+ }
41
+ if (menuOpen && hiddenValueSet.has(value)) {
42
+ return "overflow";
43
+ }
44
+ return "hidden";
45
+ },
46
+ [hiddenValueSet, menuOpen, visibleValueSet]
47
+ );
48
+ const moveOverflowFocus = react.useCallback(
49
+ (key, value) => {
50
+ if (hiddenValues.length < 1) {
51
+ return false;
52
+ }
53
+ const startIndex = getOverflowStartIndex(
54
+ hiddenValues,
55
+ value,
56
+ resolvedOverflowActiveValue
57
+ );
58
+ const lastIndex = hiddenValues.length - 1;
59
+ let nextIndex = startIndex;
60
+ switch (key) {
61
+ case "ArrowDown":
62
+ nextIndex = startIndex >= lastIndex ? 0 : startIndex + 1;
63
+ break;
64
+ case "ArrowUp":
65
+ nextIndex = startIndex <= 0 ? lastIndex : startIndex - 1;
66
+ break;
67
+ case "Home":
68
+ nextIndex = 0;
69
+ break;
70
+ case "End":
71
+ nextIndex = lastIndex;
72
+ break;
73
+ }
74
+ const nextValue = hiddenValues[nextIndex];
75
+ if (!nextValue) {
76
+ return false;
77
+ }
78
+ setRequestedOverflowActiveValue(nextValue);
79
+ return true;
80
+ },
81
+ [hiddenValues, resolvedOverflowActiveValue]
82
+ );
83
+ const tabListLayoutContext = react.useMemo(
84
+ () => ({
85
+ getLocation,
86
+ overflowActiveValue: resolvedOverflowActiveValue,
87
+ setOverflowActiveValue: setRequestedOverflowActiveValue,
88
+ moveOverflowFocus
89
+ }),
90
+ [getLocation, moveOverflowFocus, resolvedOverflowActiveValue]
91
+ );
92
+ return {
93
+ resolvedOverflowActiveValue,
94
+ tabListLayoutContext
95
+ };
96
+ }
97
+
98
+ exports.useOverflowLayoutState = useOverflowLayoutState;
99
+ //# 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":["useState","useEffect","useMemo","useCallback"],"mappings":"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"}