- Updated all component headers and documentation
- Changed navbar and footer branding
- Updated homepage hero badge
- Modified page title in index.html
- Simplified footer text to 'Built with ❤️'
- Consistent V2 capitalization across all references
87 lines
2.6 KiB
JavaScript
87 lines
2.6 KiB
JavaScript
'use client';
|
|
import { getAllFocusable, getActiveElement, isTabbable } from '@chakra-ui/utils';
|
|
import { useRef, useCallback } from 'react';
|
|
import { useEventListener } from './use-event-listener.mjs';
|
|
import { useSafeLayoutEffect } from './use-safe-layout-effect.mjs';
|
|
import { useUpdateEffect } from './use-update-effect.mjs';
|
|
|
|
function preventReturnFocus(containerRef) {
|
|
const el = containerRef.current;
|
|
if (!el)
|
|
return false;
|
|
const activeElement = getActiveElement(el);
|
|
if (!activeElement)
|
|
return false;
|
|
if (el.contains(activeElement))
|
|
return false;
|
|
if (isTabbable(activeElement))
|
|
return true;
|
|
return false;
|
|
}
|
|
function useFocusOnHide(containerRef, options) {
|
|
const { shouldFocus: shouldFocusProp, visible, focusRef } = options;
|
|
const shouldFocus = shouldFocusProp && !visible;
|
|
useUpdateEffect(() => {
|
|
if (!shouldFocus)
|
|
return;
|
|
if (preventReturnFocus(containerRef)) {
|
|
return;
|
|
}
|
|
const el = focusRef?.current || containerRef.current;
|
|
let rafId;
|
|
if (el) {
|
|
rafId = requestAnimationFrame(() => {
|
|
el.focus({ preventScroll: true });
|
|
});
|
|
return () => {
|
|
cancelAnimationFrame(rafId);
|
|
};
|
|
}
|
|
}, [shouldFocus, containerRef, focusRef]);
|
|
}
|
|
const defaultOptions = {
|
|
preventScroll: true,
|
|
shouldFocus: false
|
|
};
|
|
function useFocusOnShow(target, options = defaultOptions) {
|
|
const { focusRef, preventScroll, shouldFocus, visible } = options;
|
|
const element = isRefObject(target) ? target.current : target;
|
|
const autoFocusValue = shouldFocus && visible;
|
|
const autoFocusRef = useRef(autoFocusValue);
|
|
const lastVisibleRef = useRef(visible);
|
|
useSafeLayoutEffect(() => {
|
|
if (!lastVisibleRef.current && visible) {
|
|
autoFocusRef.current = autoFocusValue;
|
|
}
|
|
lastVisibleRef.current = visible;
|
|
}, [visible, autoFocusValue]);
|
|
const onFocus = useCallback(() => {
|
|
if (!visible || !element || !autoFocusRef.current)
|
|
return;
|
|
autoFocusRef.current = false;
|
|
if (element.contains(document.activeElement))
|
|
return;
|
|
if (focusRef?.current) {
|
|
requestAnimationFrame(() => {
|
|
focusRef.current?.focus({ preventScroll });
|
|
});
|
|
} else {
|
|
const tabbableEls = getAllFocusable(element);
|
|
if (tabbableEls.length > 0) {
|
|
requestAnimationFrame(() => {
|
|
tabbableEls[0].focus({ preventScroll });
|
|
});
|
|
}
|
|
}
|
|
}, [visible, preventScroll, element, focusRef]);
|
|
useUpdateEffect(() => {
|
|
onFocus();
|
|
}, [onFocus]);
|
|
useEventListener(element, "transitionend", onFocus);
|
|
}
|
|
function isRefObject(val) {
|
|
return "current" in val;
|
|
}
|
|
|
|
export { useFocusOnHide, useFocusOnShow };
|