1 | 'use client';
|
2 |
|
3 | import * as React from 'react';
|
4 | import { isFragment } from 'react-is';
|
5 | import PropTypes from 'prop-types';
|
6 | import clsx from 'clsx';
|
7 | import refType from '@mui/utils/refType';
|
8 | import composeClasses from '@mui/utils/composeClasses';
|
9 | import { useRtl } from '@mui/system/RtlProvider';
|
10 | import useSlotProps from '@mui/utils/useSlotProps';
|
11 | import { styled, useTheme } from "../zero-styled/index.js";
|
12 | import memoTheme from "../utils/memoTheme.js";
|
13 | import { useDefaultProps } from "../DefaultPropsProvider/index.js";
|
14 | import debounce from "../utils/debounce.js";
|
15 | import animate from "../internal/animate.js";
|
16 | import ScrollbarSize from "./ScrollbarSize.js";
|
17 | import TabScrollButton from "../TabScrollButton/index.js";
|
18 | import useEventCallback from "../utils/useEventCallback.js";
|
19 | import tabsClasses, { getTabsUtilityClass } from "./tabsClasses.js";
|
20 | import ownerDocument from "../utils/ownerDocument.js";
|
21 | import ownerWindow from "../utils/ownerWindow.js";
|
22 | import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
23 | const nextItem = (list, item) => {
|
24 | if (list === item) {
|
25 | return list.firstChild;
|
26 | }
|
27 | if (item && item.nextElementSibling) {
|
28 | return item.nextElementSibling;
|
29 | }
|
30 | return list.firstChild;
|
31 | };
|
32 | const previousItem = (list, item) => {
|
33 | if (list === item) {
|
34 | return list.lastChild;
|
35 | }
|
36 | if (item && item.previousElementSibling) {
|
37 | return item.previousElementSibling;
|
38 | }
|
39 | return list.lastChild;
|
40 | };
|
41 | const moveFocus = (list, currentFocus, traversalFunction) => {
|
42 | let wrappedOnce = false;
|
43 | let nextFocus = traversalFunction(list, currentFocus);
|
44 | while (nextFocus) {
|
45 |
|
46 | if (nextFocus === list.firstChild) {
|
47 | if (wrappedOnce) {
|
48 | return;
|
49 | }
|
50 | wrappedOnce = true;
|
51 | }
|
52 |
|
53 |
|
54 | const nextFocusDisabled = nextFocus.disabled || nextFocus.getAttribute('aria-disabled') === 'true';
|
55 | if (!nextFocus.hasAttribute('tabindex') || nextFocusDisabled) {
|
56 |
|
57 | nextFocus = traversalFunction(list, nextFocus);
|
58 | } else {
|
59 | nextFocus.focus();
|
60 | return;
|
61 | }
|
62 | }
|
63 | };
|
64 | const useUtilityClasses = ownerState => {
|
65 | const {
|
66 | vertical,
|
67 | fixed,
|
68 | hideScrollbar,
|
69 | scrollableX,
|
70 | scrollableY,
|
71 | centered,
|
72 | scrollButtonsHideMobile,
|
73 | classes
|
74 | } = ownerState;
|
75 | const slots = {
|
76 | root: ['root', vertical && 'vertical'],
|
77 | scroller: ['scroller', fixed && 'fixed', hideScrollbar && 'hideScrollbar', scrollableX && 'scrollableX', scrollableY && 'scrollableY'],
|
78 | flexContainer: ['flexContainer', vertical && 'flexContainerVertical', centered && 'centered'],
|
79 | indicator: ['indicator'],
|
80 | scrollButtons: ['scrollButtons', scrollButtonsHideMobile && 'scrollButtonsHideMobile'],
|
81 | scrollableX: [scrollableX && 'scrollableX'],
|
82 | hideScrollbar: [hideScrollbar && 'hideScrollbar']
|
83 | };
|
84 | return composeClasses(slots, getTabsUtilityClass, classes);
|
85 | };
|
86 | const TabsRoot = styled('div', {
|
87 | name: 'MuiTabs',
|
88 | slot: 'Root',
|
89 | overridesResolver: (props, styles) => {
|
90 | const {
|
91 | ownerState
|
92 | } = props;
|
93 | return [{
|
94 | [`& .${tabsClasses.scrollButtons}`]: styles.scrollButtons
|
95 | }, {
|
96 | [`& .${tabsClasses.scrollButtons}`]: ownerState.scrollButtonsHideMobile && styles.scrollButtonsHideMobile
|
97 | }, styles.root, ownerState.vertical && styles.vertical];
|
98 | }
|
99 | })(memoTheme(({
|
100 | theme
|
101 | }) => ({
|
102 | overflow: 'hidden',
|
103 | minHeight: 48,
|
104 |
|
105 | WebkitOverflowScrolling: 'touch',
|
106 | display: 'flex',
|
107 | variants: [{
|
108 | props: ({
|
109 | ownerState
|
110 | }) => ownerState.vertical,
|
111 | style: {
|
112 | flexDirection: 'column'
|
113 | }
|
114 | }, {
|
115 | props: ({
|
116 | ownerState
|
117 | }) => ownerState.scrollButtonsHideMobile,
|
118 | style: {
|
119 | [`& .${tabsClasses.scrollButtons}`]: {
|
120 | [theme.breakpoints.down('sm')]: {
|
121 | display: 'none'
|
122 | }
|
123 | }
|
124 | }
|
125 | }]
|
126 | })));
|
127 | const TabsScroller = styled('div', {
|
128 | name: 'MuiTabs',
|
129 | slot: 'Scroller',
|
130 | overridesResolver: (props, styles) => {
|
131 | const {
|
132 | ownerState
|
133 | } = props;
|
134 | return [styles.scroller, ownerState.fixed && styles.fixed, ownerState.hideScrollbar && styles.hideScrollbar, ownerState.scrollableX && styles.scrollableX, ownerState.scrollableY && styles.scrollableY];
|
135 | }
|
136 | })({
|
137 | position: 'relative',
|
138 | display: 'inline-block',
|
139 | flex: '1 1 auto',
|
140 | whiteSpace: 'nowrap',
|
141 | variants: [{
|
142 | props: ({
|
143 | ownerState
|
144 | }) => ownerState.fixed,
|
145 | style: {
|
146 | overflowX: 'hidden',
|
147 | width: '100%'
|
148 | }
|
149 | }, {
|
150 | props: ({
|
151 | ownerState
|
152 | }) => ownerState.hideScrollbar,
|
153 | style: {
|
154 |
|
155 | scrollbarWidth: 'none',
|
156 |
|
157 | '&::-webkit-scrollbar': {
|
158 | display: 'none'
|
159 | }
|
160 | }
|
161 | }, {
|
162 | props: ({
|
163 | ownerState
|
164 | }) => ownerState.scrollableX,
|
165 | style: {
|
166 | overflowX: 'auto',
|
167 | overflowY: 'hidden'
|
168 | }
|
169 | }, {
|
170 | props: ({
|
171 | ownerState
|
172 | }) => ownerState.scrollableY,
|
173 | style: {
|
174 | overflowY: 'auto',
|
175 | overflowX: 'hidden'
|
176 | }
|
177 | }]
|
178 | });
|
179 | const FlexContainer = styled('div', {
|
180 | name: 'MuiTabs',
|
181 | slot: 'FlexContainer',
|
182 | overridesResolver: (props, styles) => {
|
183 | const {
|
184 | ownerState
|
185 | } = props;
|
186 | return [styles.flexContainer, ownerState.vertical && styles.flexContainerVertical, ownerState.centered && styles.centered];
|
187 | }
|
188 | })({
|
189 | display: 'flex',
|
190 | variants: [{
|
191 | props: ({
|
192 | ownerState
|
193 | }) => ownerState.vertical,
|
194 | style: {
|
195 | flexDirection: 'column'
|
196 | }
|
197 | }, {
|
198 | props: ({
|
199 | ownerState
|
200 | }) => ownerState.centered,
|
201 | style: {
|
202 | justifyContent: 'center'
|
203 | }
|
204 | }]
|
205 | });
|
206 | const TabsIndicator = styled('span', {
|
207 | name: 'MuiTabs',
|
208 | slot: 'Indicator',
|
209 | overridesResolver: (props, styles) => styles.indicator
|
210 | })(memoTheme(({
|
211 | theme
|
212 | }) => ({
|
213 | position: 'absolute',
|
214 | height: 2,
|
215 | bottom: 0,
|
216 | width: '100%',
|
217 | transition: theme.transitions.create(),
|
218 | variants: [{
|
219 | props: {
|
220 | indicatorColor: 'primary'
|
221 | },
|
222 | style: {
|
223 | backgroundColor: (theme.vars || theme).palette.primary.main
|
224 | }
|
225 | }, {
|
226 | props: {
|
227 | indicatorColor: 'secondary'
|
228 | },
|
229 | style: {
|
230 | backgroundColor: (theme.vars || theme).palette.secondary.main
|
231 | }
|
232 | }, {
|
233 | props: ({
|
234 | ownerState
|
235 | }) => ownerState.vertical,
|
236 | style: {
|
237 | height: '100%',
|
238 | width: 2,
|
239 | right: 0
|
240 | }
|
241 | }]
|
242 | })));
|
243 | const TabsScrollbarSize = styled(ScrollbarSize)({
|
244 | overflowX: 'auto',
|
245 | overflowY: 'hidden',
|
246 |
|
247 | scrollbarWidth: 'none',
|
248 |
|
249 | '&::-webkit-scrollbar': {
|
250 | display: 'none'
|
251 | }
|
252 | });
|
253 | const defaultIndicatorStyle = {};
|
254 | let warnedOnceTabPresent = false;
|
255 | const Tabs = React.forwardRef(function Tabs(inProps, ref) {
|
256 | const props = useDefaultProps({
|
257 | props: inProps,
|
258 | name: 'MuiTabs'
|
259 | });
|
260 | const theme = useTheme();
|
261 | const isRtl = useRtl();
|
262 | const {
|
263 | 'aria-label': ariaLabel,
|
264 | 'aria-labelledby': ariaLabelledBy,
|
265 | action,
|
266 | centered = false,
|
267 | children: childrenProp,
|
268 | className,
|
269 | component = 'div',
|
270 | allowScrollButtonsMobile = false,
|
271 | indicatorColor = 'primary',
|
272 | onChange,
|
273 | orientation = 'horizontal',
|
274 | ScrollButtonComponent = TabScrollButton,
|
275 | scrollButtons = 'auto',
|
276 | selectionFollowsFocus,
|
277 | slots = {},
|
278 | slotProps = {},
|
279 | TabIndicatorProps = {},
|
280 | TabScrollButtonProps = {},
|
281 | textColor = 'primary',
|
282 | value,
|
283 | variant = 'standard',
|
284 | visibleScrollbar = false,
|
285 | ...other
|
286 | } = props;
|
287 | const scrollable = variant === 'scrollable';
|
288 | const vertical = orientation === 'vertical';
|
289 | const scrollStart = vertical ? 'scrollTop' : 'scrollLeft';
|
290 | const start = vertical ? 'top' : 'left';
|
291 | const end = vertical ? 'bottom' : 'right';
|
292 | const clientSize = vertical ? 'clientHeight' : 'clientWidth';
|
293 | const size = vertical ? 'height' : 'width';
|
294 | const ownerState = {
|
295 | ...props,
|
296 | component,
|
297 | allowScrollButtonsMobile,
|
298 | indicatorColor,
|
299 | orientation,
|
300 | vertical,
|
301 | scrollButtons,
|
302 | textColor,
|
303 | variant,
|
304 | visibleScrollbar,
|
305 | fixed: !scrollable,
|
306 | hideScrollbar: scrollable && !visibleScrollbar,
|
307 | scrollableX: scrollable && !vertical,
|
308 | scrollableY: scrollable && vertical,
|
309 | centered: centered && !scrollable,
|
310 | scrollButtonsHideMobile: !allowScrollButtonsMobile
|
311 | };
|
312 | const classes = useUtilityClasses(ownerState);
|
313 | const startScrollButtonIconProps = useSlotProps({
|
314 | elementType: slots.StartScrollButtonIcon,
|
315 | externalSlotProps: slotProps.startScrollButtonIcon,
|
316 | ownerState
|
317 | });
|
318 | const endScrollButtonIconProps = useSlotProps({
|
319 | elementType: slots.EndScrollButtonIcon,
|
320 | externalSlotProps: slotProps.endScrollButtonIcon,
|
321 | ownerState
|
322 | });
|
323 | if (process.env.NODE_ENV !== 'production') {
|
324 | if (centered && scrollable) {
|
325 | console.error('MUI: You can not use the `centered={true}` and `variant="scrollable"` properties ' + 'at the same time on a `Tabs` component.');
|
326 | }
|
327 | }
|
328 | const [mounted, setMounted] = React.useState(false);
|
329 | const [indicatorStyle, setIndicatorStyle] = React.useState(defaultIndicatorStyle);
|
330 | const [displayStartScroll, setDisplayStartScroll] = React.useState(false);
|
331 | const [displayEndScroll, setDisplayEndScroll] = React.useState(false);
|
332 | const [updateScrollObserver, setUpdateScrollObserver] = React.useState(false);
|
333 | const [scrollerStyle, setScrollerStyle] = React.useState({
|
334 | overflow: 'hidden',
|
335 | scrollbarWidth: 0
|
336 | });
|
337 | const valueToIndex = new Map();
|
338 | const tabsRef = React.useRef(null);
|
339 | const tabListRef = React.useRef(null);
|
340 | const getTabsMeta = () => {
|
341 | const tabsNode = tabsRef.current;
|
342 | let tabsMeta;
|
343 | if (tabsNode) {
|
344 | const rect = tabsNode.getBoundingClientRect();
|
345 |
|
346 | tabsMeta = {
|
347 | clientWidth: tabsNode.clientWidth,
|
348 | scrollLeft: tabsNode.scrollLeft,
|
349 | scrollTop: tabsNode.scrollTop,
|
350 | scrollWidth: tabsNode.scrollWidth,
|
351 | top: rect.top,
|
352 | bottom: rect.bottom,
|
353 | left: rect.left,
|
354 | right: rect.right
|
355 | };
|
356 | }
|
357 | let tabMeta;
|
358 | if (tabsNode && value !== false) {
|
359 | const children = tabListRef.current.children;
|
360 | if (children.length > 0) {
|
361 | const tab = children[valueToIndex.get(value)];
|
362 | if (process.env.NODE_ENV !== 'production') {
|
363 | if (!tab) {
|
364 | console.error([`MUI: The \`value\` provided to the Tabs component is invalid.`, `None of the Tabs' children match with "${value}".`, valueToIndex.keys ? `You can provide one of the following values: ${Array.from(valueToIndex.keys()).join(', ')}.` : null].join('\n'));
|
365 | }
|
366 | }
|
367 | tabMeta = tab ? tab.getBoundingClientRect() : null;
|
368 | if (process.env.NODE_ENV !== 'production') {
|
369 | if (process.env.NODE_ENV !== 'test' && !warnedOnceTabPresent && tabMeta && tabMeta.width === 0 && tabMeta.height === 0 &&
|
370 |
|
371 | tabsMeta.clientWidth !== 0) {
|
372 | tabsMeta = null;
|
373 | console.error(['MUI: The `value` provided to the Tabs component is invalid.', `The Tab with this \`value\` ("${value}") is not part of the document layout.`, "Make sure the tab item is present in the document or that it's not `display: none`."].join('\n'));
|
374 | warnedOnceTabPresent = true;
|
375 | }
|
376 | }
|
377 | }
|
378 | }
|
379 | return {
|
380 | tabsMeta,
|
381 | tabMeta
|
382 | };
|
383 | };
|
384 | const updateIndicatorState = useEventCallback(() => {
|
385 | const {
|
386 | tabsMeta,
|
387 | tabMeta
|
388 | } = getTabsMeta();
|
389 | let startValue = 0;
|
390 | let startIndicator;
|
391 | if (vertical) {
|
392 | startIndicator = 'top';
|
393 | if (tabMeta && tabsMeta) {
|
394 | startValue = tabMeta.top - tabsMeta.top + tabsMeta.scrollTop;
|
395 | }
|
396 | } else {
|
397 | startIndicator = isRtl ? 'right' : 'left';
|
398 | if (tabMeta && tabsMeta) {
|
399 | startValue = (isRtl ? -1 : 1) * (tabMeta[startIndicator] - tabsMeta[startIndicator] + tabsMeta.scrollLeft);
|
400 | }
|
401 | }
|
402 | const newIndicatorStyle = {
|
403 | [startIndicator]: startValue,
|
404 |
|
405 | [size]: tabMeta ? tabMeta[size] : 0
|
406 | };
|
407 | if (typeof indicatorStyle[startIndicator] !== 'number' || typeof indicatorStyle[size] !== 'number') {
|
408 | setIndicatorStyle(newIndicatorStyle);
|
409 | } else {
|
410 | const dStart = Math.abs(indicatorStyle[startIndicator] - newIndicatorStyle[startIndicator]);
|
411 | const dSize = Math.abs(indicatorStyle[size] - newIndicatorStyle[size]);
|
412 | if (dStart >= 1 || dSize >= 1) {
|
413 | setIndicatorStyle(newIndicatorStyle);
|
414 | }
|
415 | }
|
416 | });
|
417 | const scroll = (scrollValue, {
|
418 | animation = true
|
419 | } = {}) => {
|
420 | if (animation) {
|
421 | animate(scrollStart, tabsRef.current, scrollValue, {
|
422 | duration: theme.transitions.duration.standard
|
423 | });
|
424 | } else {
|
425 | tabsRef.current[scrollStart] = scrollValue;
|
426 | }
|
427 | };
|
428 | const moveTabsScroll = delta => {
|
429 | let scrollValue = tabsRef.current[scrollStart];
|
430 | if (vertical) {
|
431 | scrollValue += delta;
|
432 | } else {
|
433 | scrollValue += delta * (isRtl ? -1 : 1);
|
434 | }
|
435 | scroll(scrollValue);
|
436 | };
|
437 | const getScrollSize = () => {
|
438 | const containerSize = tabsRef.current[clientSize];
|
439 | let totalSize = 0;
|
440 | const children = Array.from(tabListRef.current.children);
|
441 | for (let i = 0; i < children.length; i += 1) {
|
442 | const tab = children[i];
|
443 | if (totalSize + tab[clientSize] > containerSize) {
|
444 |
|
445 |
|
446 | if (i === 0) {
|
447 | totalSize = containerSize;
|
448 | }
|
449 | break;
|
450 | }
|
451 | totalSize += tab[clientSize];
|
452 | }
|
453 | return totalSize;
|
454 | };
|
455 | const handleStartScrollClick = () => {
|
456 | moveTabsScroll(-1 * getScrollSize());
|
457 | };
|
458 | const handleEndScrollClick = () => {
|
459 | moveTabsScroll(getScrollSize());
|
460 | };
|
461 |
|
462 |
|
463 |
|
464 | const handleScrollbarSizeChange = React.useCallback(scrollbarWidth => {
|
465 | setScrollerStyle({
|
466 | overflow: null,
|
467 | scrollbarWidth
|
468 | });
|
469 | }, []);
|
470 | const getConditionalElements = () => {
|
471 | const conditionalElements = {};
|
472 | conditionalElements.scrollbarSizeListener = scrollable ? _jsx(TabsScrollbarSize, {
|
473 | onChange: handleScrollbarSizeChange,
|
474 | className: clsx(classes.scrollableX, classes.hideScrollbar)
|
475 | }) : null;
|
476 | const scrollButtonsActive = displayStartScroll || displayEndScroll;
|
477 | const showScrollButtons = scrollable && (scrollButtons === 'auto' && scrollButtonsActive || scrollButtons === true);
|
478 | conditionalElements.scrollButtonStart = showScrollButtons ? _jsx(ScrollButtonComponent, {
|
479 | slots: {
|
480 | StartScrollButtonIcon: slots.StartScrollButtonIcon
|
481 | },
|
482 | slotProps: {
|
483 | startScrollButtonIcon: startScrollButtonIconProps
|
484 | },
|
485 | orientation: orientation,
|
486 | direction: isRtl ? 'right' : 'left',
|
487 | onClick: handleStartScrollClick,
|
488 | disabled: !displayStartScroll,
|
489 | ...TabScrollButtonProps,
|
490 | className: clsx(classes.scrollButtons, TabScrollButtonProps.className)
|
491 | }) : null;
|
492 | conditionalElements.scrollButtonEnd = showScrollButtons ? _jsx(ScrollButtonComponent, {
|
493 | slots: {
|
494 | EndScrollButtonIcon: slots.EndScrollButtonIcon
|
495 | },
|
496 | slotProps: {
|
497 | endScrollButtonIcon: endScrollButtonIconProps
|
498 | },
|
499 | orientation: orientation,
|
500 | direction: isRtl ? 'left' : 'right',
|
501 | onClick: handleEndScrollClick,
|
502 | disabled: !displayEndScroll,
|
503 | ...TabScrollButtonProps,
|
504 | className: clsx(classes.scrollButtons, TabScrollButtonProps.className)
|
505 | }) : null;
|
506 | return conditionalElements;
|
507 | };
|
508 | const scrollSelectedIntoView = useEventCallback(animation => {
|
509 | const {
|
510 | tabsMeta,
|
511 | tabMeta
|
512 | } = getTabsMeta();
|
513 | if (!tabMeta || !tabsMeta) {
|
514 | return;
|
515 | }
|
516 | if (tabMeta[start] < tabsMeta[start]) {
|
517 |
|
518 | const nextScrollStart = tabsMeta[scrollStart] + (tabMeta[start] - tabsMeta[start]);
|
519 | scroll(nextScrollStart, {
|
520 | animation
|
521 | });
|
522 | } else if (tabMeta[end] > tabsMeta[end]) {
|
523 |
|
524 | const nextScrollStart = tabsMeta[scrollStart] + (tabMeta[end] - tabsMeta[end]);
|
525 | scroll(nextScrollStart, {
|
526 | animation
|
527 | });
|
528 | }
|
529 | });
|
530 | const updateScrollButtonState = useEventCallback(() => {
|
531 | if (scrollable && scrollButtons !== false) {
|
532 | setUpdateScrollObserver(!updateScrollObserver);
|
533 | }
|
534 | });
|
535 | React.useEffect(() => {
|
536 | const handleResize = debounce(() => {
|
537 |
|
538 |
|
539 |
|
540 |
|
541 |
|
542 |
|
543 | if (tabsRef.current) {
|
544 | updateIndicatorState();
|
545 | }
|
546 | });
|
547 | let resizeObserver;
|
548 |
|
549 | |
550 |
|
551 |
|
552 | const handleMutation = records => {
|
553 | records.forEach(record => {
|
554 | record.removedNodes.forEach(item => {
|
555 | resizeObserver?.unobserve(item);
|
556 | });
|
557 | record.addedNodes.forEach(item => {
|
558 | resizeObserver?.observe(item);
|
559 | });
|
560 | });
|
561 | handleResize();
|
562 | updateScrollButtonState();
|
563 | };
|
564 | const win = ownerWindow(tabsRef.current);
|
565 | win.addEventListener('resize', handleResize);
|
566 | let mutationObserver;
|
567 | if (typeof ResizeObserver !== 'undefined') {
|
568 | resizeObserver = new ResizeObserver(handleResize);
|
569 | Array.from(tabListRef.current.children).forEach(child => {
|
570 | resizeObserver.observe(child);
|
571 | });
|
572 | }
|
573 | if (typeof MutationObserver !== 'undefined') {
|
574 | mutationObserver = new MutationObserver(handleMutation);
|
575 | mutationObserver.observe(tabListRef.current, {
|
576 | childList: true
|
577 | });
|
578 | }
|
579 | return () => {
|
580 | handleResize.clear();
|
581 | win.removeEventListener('resize', handleResize);
|
582 | mutationObserver?.disconnect();
|
583 | resizeObserver?.disconnect();
|
584 | };
|
585 | }, [updateIndicatorState, updateScrollButtonState]);
|
586 |
|
587 | |
588 |
|
589 |
|
590 |
|
591 | React.useEffect(() => {
|
592 | const tabListChildren = Array.from(tabListRef.current.children);
|
593 | const length = tabListChildren.length;
|
594 | if (typeof IntersectionObserver !== 'undefined' && length > 0 && scrollable && scrollButtons !== false) {
|
595 | const firstTab = tabListChildren[0];
|
596 | const lastTab = tabListChildren[length - 1];
|
597 | const observerOptions = {
|
598 | root: tabsRef.current,
|
599 | threshold: 0.99
|
600 | };
|
601 | const handleScrollButtonStart = entries => {
|
602 | setDisplayStartScroll(!entries[0].isIntersecting);
|
603 | };
|
604 | const firstObserver = new IntersectionObserver(handleScrollButtonStart, observerOptions);
|
605 | firstObserver.observe(firstTab);
|
606 | const handleScrollButtonEnd = entries => {
|
607 | setDisplayEndScroll(!entries[0].isIntersecting);
|
608 | };
|
609 | const lastObserver = new IntersectionObserver(handleScrollButtonEnd, observerOptions);
|
610 | lastObserver.observe(lastTab);
|
611 | return () => {
|
612 | firstObserver.disconnect();
|
613 | lastObserver.disconnect();
|
614 | };
|
615 | }
|
616 | return undefined;
|
617 | }, [scrollable, scrollButtons, updateScrollObserver, childrenProp?.length]);
|
618 | React.useEffect(() => {
|
619 | setMounted(true);
|
620 | }, []);
|
621 | React.useEffect(() => {
|
622 | updateIndicatorState();
|
623 | });
|
624 | React.useEffect(() => {
|
625 |
|
626 | scrollSelectedIntoView(defaultIndicatorStyle !== indicatorStyle);
|
627 | }, [scrollSelectedIntoView, indicatorStyle]);
|
628 | React.useImperativeHandle(action, () => ({
|
629 | updateIndicator: updateIndicatorState,
|
630 | updateScrollButtons: updateScrollButtonState
|
631 | }), [updateIndicatorState, updateScrollButtonState]);
|
632 | const indicator = _jsx(TabsIndicator, {
|
633 | ...TabIndicatorProps,
|
634 | className: clsx(classes.indicator, TabIndicatorProps.className),
|
635 | ownerState: ownerState,
|
636 | style: {
|
637 | ...indicatorStyle,
|
638 | ...TabIndicatorProps.style
|
639 | }
|
640 | });
|
641 | let childIndex = 0;
|
642 | const children = React.Children.map(childrenProp, child => {
|
643 | if (! React.isValidElement(child)) {
|
644 | return null;
|
645 | }
|
646 | if (process.env.NODE_ENV !== 'production') {
|
647 | if (isFragment(child)) {
|
648 | console.error(["MUI: The Tabs component doesn't accept a Fragment as a child.", 'Consider providing an array instead.'].join('\n'));
|
649 | }
|
650 | }
|
651 | const childValue = child.props.value === undefined ? childIndex : child.props.value;
|
652 | valueToIndex.set(childValue, childIndex);
|
653 | const selected = childValue === value;
|
654 | childIndex += 1;
|
655 | return React.cloneElement(child, {
|
656 | fullWidth: variant === 'fullWidth',
|
657 | indicator: selected && !mounted && indicator,
|
658 | selected,
|
659 | selectionFollowsFocus,
|
660 | onChange,
|
661 | textColor,
|
662 | value: childValue,
|
663 | ...(childIndex === 1 && value === false && !child.props.tabIndex ? {
|
664 | tabIndex: 0
|
665 | } : {})
|
666 | });
|
667 | });
|
668 | const handleKeyDown = event => {
|
669 | const list = tabListRef.current;
|
670 | const currentFocus = ownerDocument(list).activeElement;
|
671 |
|
672 |
|
673 |
|
674 | const role = currentFocus.getAttribute('role');
|
675 | if (role !== 'tab') {
|
676 | return;
|
677 | }
|
678 | let previousItemKey = orientation === 'horizontal' ? 'ArrowLeft' : 'ArrowUp';
|
679 | let nextItemKey = orientation === 'horizontal' ? 'ArrowRight' : 'ArrowDown';
|
680 | if (orientation === 'horizontal' && isRtl) {
|
681 |
|
682 | previousItemKey = 'ArrowRight';
|
683 | nextItemKey = 'ArrowLeft';
|
684 | }
|
685 | switch (event.key) {
|
686 | case previousItemKey:
|
687 | event.preventDefault();
|
688 | moveFocus(list, currentFocus, previousItem);
|
689 | break;
|
690 | case nextItemKey:
|
691 | event.preventDefault();
|
692 | moveFocus(list, currentFocus, nextItem);
|
693 | break;
|
694 | case 'Home':
|
695 | event.preventDefault();
|
696 | moveFocus(list, null, nextItem);
|
697 | break;
|
698 | case 'End':
|
699 | event.preventDefault();
|
700 | moveFocus(list, null, previousItem);
|
701 | break;
|
702 | default:
|
703 | break;
|
704 | }
|
705 | };
|
706 | const conditionalElements = getConditionalElements();
|
707 | return _jsxs(TabsRoot, {
|
708 | className: clsx(classes.root, className),
|
709 | ownerState: ownerState,
|
710 | ref: ref,
|
711 | as: component,
|
712 | ...other,
|
713 | children: [conditionalElements.scrollButtonStart, conditionalElements.scrollbarSizeListener, _jsxs(TabsScroller, {
|
714 | className: classes.scroller,
|
715 | ownerState: ownerState,
|
716 | style: {
|
717 | overflow: scrollerStyle.overflow,
|
718 | [vertical ? `margin${isRtl ? 'Left' : 'Right'}` : 'marginBottom']: visibleScrollbar ? undefined : -scrollerStyle.scrollbarWidth
|
719 | },
|
720 | ref: tabsRef,
|
721 | children: [_jsx(FlexContainer, {
|
722 | "aria-label": ariaLabel,
|
723 | "aria-labelledby": ariaLabelledBy,
|
724 | "aria-orientation": orientation === 'vertical' ? 'vertical' : null,
|
725 | className: classes.flexContainer,
|
726 | ownerState: ownerState,
|
727 | onKeyDown: handleKeyDown,
|
728 | ref: tabListRef,
|
729 | role: "tablist",
|
730 | children: children
|
731 | }), mounted && indicator]
|
732 | }), conditionalElements.scrollButtonEnd]
|
733 | });
|
734 | });
|
735 | process.env.NODE_ENV !== "production" ? Tabs.propTypes = {
|
736 |
|
737 |
|
738 |
|
739 |
|
740 | |
741 |
|
742 |
|
743 |
|
744 |
|
745 |
|
746 |
|
747 |
|
748 | action: refType,
|
749 | |
750 |
|
751 |
|
752 |
|
753 |
|
754 | allowScrollButtonsMobile: PropTypes.bool,
|
755 | |
756 |
|
757 |
|
758 | 'aria-label': PropTypes.string,
|
759 | |
760 |
|
761 |
|
762 | 'aria-labelledby': PropTypes.string,
|
763 | |
764 |
|
765 |
|
766 |
|
767 |
|
768 | centered: PropTypes.bool,
|
769 | |
770 |
|
771 |
|
772 | children: PropTypes.node,
|
773 | |
774 |
|
775 |
|
776 | classes: PropTypes.object,
|
777 | |
778 |
|
779 |
|
780 | className: PropTypes.string,
|
781 | |
782 |
|
783 |
|
784 |
|
785 | component: PropTypes.elementType,
|
786 | |
787 |
|
788 |
|
789 |
|
790 | indicatorColor: PropTypes .oneOfType([PropTypes.oneOf(['primary', 'secondary']), PropTypes.string]),
|
791 | |
792 |
|
793 |
|
794 |
|
795 |
|
796 |
|
797 | onChange: PropTypes.func,
|
798 | |
799 |
|
800 |
|
801 |
|
802 | orientation: PropTypes.oneOf(['horizontal', 'vertical']),
|
803 | |
804 |
|
805 |
|
806 |
|
807 | ScrollButtonComponent: PropTypes.elementType,
|
808 | |
809 |
|
810 |
|
811 |
|
812 |
|
813 |
|
814 |
|
815 |
|
816 |
|
817 |
|
818 |
|
819 | scrollButtons: PropTypes .oneOf(['auto', false, true]),
|
820 | |
821 |
|
822 |
|
823 |
|
824 | selectionFollowsFocus: PropTypes.bool,
|
825 | |
826 |
|
827 |
|
828 |
|
829 |
|
830 | slotProps: PropTypes.shape({
|
831 | endScrollButtonIcon: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
|
832 | startScrollButtonIcon: PropTypes.oneOfType([PropTypes.func, PropTypes.object])
|
833 | }),
|
834 | |
835 |
|
836 |
|
837 |
|
838 | slots: PropTypes.shape({
|
839 | EndScrollButtonIcon: PropTypes.elementType,
|
840 | StartScrollButtonIcon: PropTypes.elementType
|
841 | }),
|
842 | |
843 |
|
844 |
|
845 | sx: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), PropTypes.func, PropTypes.object]),
|
846 | |
847 |
|
848 |
|
849 |
|
850 | TabIndicatorProps: PropTypes.object,
|
851 | |
852 |
|
853 |
|
854 |
|
855 | TabScrollButtonProps: PropTypes.object,
|
856 | |
857 |
|
858 |
|
859 |
|
860 | textColor: PropTypes.oneOf(['inherit', 'primary', 'secondary']),
|
861 | |
862 |
|
863 |
|
864 |
|
865 | value: PropTypes.any,
|
866 | |
867 |
|
868 |
|
869 |
|
870 |
|
871 |
|
872 |
|
873 |
|
874 |
|
875 |
|
876 | variant: PropTypes.oneOf(['fullWidth', 'scrollable', 'standard']),
|
877 | |
878 |
|
879 |
|
880 |
|
881 |
|
882 | visibleScrollbar: PropTypes.bool
|
883 | } : void 0;
|
884 | export default Tabs; |
\ | No newline at end of file |