Grabbed dndbeyond's source code
``` ~/go/bin/sourcemapper -output ddb -jsurl https://media.dndbeyond.com/character-app/static/js/main.90aa78c5.js ```
This commit is contained in:
+340
@@ -0,0 +1,340 @@
|
||||
'use client';
|
||||
|
||||
/* eslint-disable consistent-return, jsx-a11y/no-noninteractive-tabindex */
|
||||
import * as React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { exactProp, elementAcceptingRef, unstable_useForkRef as useForkRef, unstable_ownerDocument as ownerDocument } from '@mui/utils';
|
||||
import { jsx as _jsx } from "react/jsx-runtime";
|
||||
import { jsxs as _jsxs } from "react/jsx-runtime";
|
||||
// Inspired by https://github.com/focus-trap/tabbable
|
||||
const candidatesSelector = ['input', 'select', 'textarea', 'a[href]', 'button', '[tabindex]', 'audio[controls]', 'video[controls]', '[contenteditable]:not([contenteditable="false"])'].join(',');
|
||||
function getTabIndex(node) {
|
||||
const tabindexAttr = parseInt(node.getAttribute('tabindex') || '', 10);
|
||||
if (!Number.isNaN(tabindexAttr)) {
|
||||
return tabindexAttr;
|
||||
}
|
||||
|
||||
// Browsers do not return `tabIndex` correctly for contentEditable nodes;
|
||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=661108&q=contenteditable%20tabindex&can=2
|
||||
// so if they don't have a tabindex attribute specifically set, assume it's 0.
|
||||
// in Chrome, <details/>, <audio controls/> and <video controls/> elements get a default
|
||||
// `tabIndex` of -1 when the 'tabindex' attribute isn't specified in the DOM,
|
||||
// yet they are still part of the regular tab order; in FF, they get a default
|
||||
// `tabIndex` of 0; since Chrome still puts those elements in the regular tab
|
||||
// order, consider their tab index to be 0.
|
||||
if (node.contentEditable === 'true' || (node.nodeName === 'AUDIO' || node.nodeName === 'VIDEO' || node.nodeName === 'DETAILS') && node.getAttribute('tabindex') === null) {
|
||||
return 0;
|
||||
}
|
||||
return node.tabIndex;
|
||||
}
|
||||
function isNonTabbableRadio(node) {
|
||||
if (node.tagName !== 'INPUT' || node.type !== 'radio') {
|
||||
return false;
|
||||
}
|
||||
if (!node.name) {
|
||||
return false;
|
||||
}
|
||||
const getRadio = selector => node.ownerDocument.querySelector(`input[type="radio"]${selector}`);
|
||||
let roving = getRadio(`[name="${node.name}"]:checked`);
|
||||
if (!roving) {
|
||||
roving = getRadio(`[name="${node.name}"]`);
|
||||
}
|
||||
return roving !== node;
|
||||
}
|
||||
function isNodeMatchingSelectorFocusable(node) {
|
||||
if (node.disabled || node.tagName === 'INPUT' && node.type === 'hidden' || isNonTabbableRadio(node)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
function defaultGetTabbable(root) {
|
||||
const regularTabNodes = [];
|
||||
const orderedTabNodes = [];
|
||||
Array.from(root.querySelectorAll(candidatesSelector)).forEach((node, i) => {
|
||||
const nodeTabIndex = getTabIndex(node);
|
||||
if (nodeTabIndex === -1 || !isNodeMatchingSelectorFocusable(node)) {
|
||||
return;
|
||||
}
|
||||
if (nodeTabIndex === 0) {
|
||||
regularTabNodes.push(node);
|
||||
} else {
|
||||
orderedTabNodes.push({
|
||||
documentOrder: i,
|
||||
tabIndex: nodeTabIndex,
|
||||
node: node
|
||||
});
|
||||
}
|
||||
});
|
||||
return orderedTabNodes.sort((a, b) => a.tabIndex === b.tabIndex ? a.documentOrder - b.documentOrder : a.tabIndex - b.tabIndex).map(a => a.node).concat(regularTabNodes);
|
||||
}
|
||||
function defaultIsEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility component that locks focus inside the component.
|
||||
*
|
||||
* Demos:
|
||||
*
|
||||
* - [Focus Trap](https://mui.com/base-ui/react-focus-trap/)
|
||||
*
|
||||
* API:
|
||||
*
|
||||
* - [FocusTrap API](https://mui.com/base-ui/react-focus-trap/components-api/#focus-trap)
|
||||
*/
|
||||
function FocusTrap(props) {
|
||||
const {
|
||||
children,
|
||||
disableAutoFocus = false,
|
||||
disableEnforceFocus = false,
|
||||
disableRestoreFocus = false,
|
||||
getTabbable = defaultGetTabbable,
|
||||
isEnabled = defaultIsEnabled,
|
||||
open
|
||||
} = props;
|
||||
const ignoreNextEnforceFocus = React.useRef(false);
|
||||
const sentinelStart = React.useRef(null);
|
||||
const sentinelEnd = React.useRef(null);
|
||||
const nodeToRestore = React.useRef(null);
|
||||
const reactFocusEventTarget = React.useRef(null);
|
||||
// This variable is useful when disableAutoFocus is true.
|
||||
// It waits for the active element to move into the component to activate.
|
||||
const activated = React.useRef(false);
|
||||
const rootRef = React.useRef(null);
|
||||
// @ts-expect-error TODO upstream fix
|
||||
const handleRef = useForkRef(children.ref, rootRef);
|
||||
const lastKeydown = React.useRef(null);
|
||||
React.useEffect(() => {
|
||||
// We might render an empty child.
|
||||
if (!open || !rootRef.current) {
|
||||
return;
|
||||
}
|
||||
activated.current = !disableAutoFocus;
|
||||
}, [disableAutoFocus, open]);
|
||||
React.useEffect(() => {
|
||||
// We might render an empty child.
|
||||
if (!open || !rootRef.current) {
|
||||
return;
|
||||
}
|
||||
const doc = ownerDocument(rootRef.current);
|
||||
if (!rootRef.current.contains(doc.activeElement)) {
|
||||
if (!rootRef.current.hasAttribute('tabIndex')) {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
console.error(['MUI: The modal content node does not accept focus.', 'For the benefit of assistive technologies, ' + 'the tabIndex of the node is being set to "-1".'].join('\n'));
|
||||
}
|
||||
rootRef.current.setAttribute('tabIndex', '-1');
|
||||
}
|
||||
if (activated.current) {
|
||||
rootRef.current.focus();
|
||||
}
|
||||
}
|
||||
return () => {
|
||||
// restoreLastFocus()
|
||||
if (!disableRestoreFocus) {
|
||||
// In IE11 it is possible for document.activeElement to be null resulting
|
||||
// in nodeToRestore.current being null.
|
||||
// Not all elements in IE11 have a focus method.
|
||||
// Once IE11 support is dropped the focus() call can be unconditional.
|
||||
if (nodeToRestore.current && nodeToRestore.current.focus) {
|
||||
ignoreNextEnforceFocus.current = true;
|
||||
nodeToRestore.current.focus();
|
||||
}
|
||||
nodeToRestore.current = null;
|
||||
}
|
||||
};
|
||||
// Missing `disableRestoreFocus` which is fine.
|
||||
// We don't support changing that prop on an open FocusTrap
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [open]);
|
||||
React.useEffect(() => {
|
||||
// We might render an empty child.
|
||||
if (!open || !rootRef.current) {
|
||||
return;
|
||||
}
|
||||
const doc = ownerDocument(rootRef.current);
|
||||
const loopFocus = nativeEvent => {
|
||||
lastKeydown.current = nativeEvent;
|
||||
if (disableEnforceFocus || !isEnabled() || nativeEvent.key !== 'Tab') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the next tab starts from the right place.
|
||||
// doc.activeElement refers to the origin.
|
||||
if (doc.activeElement === rootRef.current && nativeEvent.shiftKey) {
|
||||
// We need to ignore the next contain as
|
||||
// it will try to move the focus back to the rootRef element.
|
||||
ignoreNextEnforceFocus.current = true;
|
||||
if (sentinelEnd.current) {
|
||||
sentinelEnd.current.focus();
|
||||
}
|
||||
}
|
||||
};
|
||||
const contain = () => {
|
||||
const rootElement = rootRef.current;
|
||||
|
||||
// Cleanup functions are executed lazily in React 17.
|
||||
// Contain can be called between the component being unmounted and its cleanup function being run.
|
||||
if (rootElement === null) {
|
||||
return;
|
||||
}
|
||||
if (!doc.hasFocus() || !isEnabled() || ignoreNextEnforceFocus.current) {
|
||||
ignoreNextEnforceFocus.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// The focus is already inside
|
||||
if (rootElement.contains(doc.activeElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The disableEnforceFocus is set and the focus is outside of the focus trap (and sentinel nodes)
|
||||
if (disableEnforceFocus && doc.activeElement !== sentinelStart.current && doc.activeElement !== sentinelEnd.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if the focus event is not coming from inside the children's react tree, reset the refs
|
||||
if (doc.activeElement !== reactFocusEventTarget.current) {
|
||||
reactFocusEventTarget.current = null;
|
||||
} else if (reactFocusEventTarget.current !== null) {
|
||||
return;
|
||||
}
|
||||
if (!activated.current) {
|
||||
return;
|
||||
}
|
||||
let tabbable = [];
|
||||
if (doc.activeElement === sentinelStart.current || doc.activeElement === sentinelEnd.current) {
|
||||
tabbable = getTabbable(rootRef.current);
|
||||
}
|
||||
|
||||
// one of the sentinel nodes was focused, so move the focus
|
||||
// to the first/last tabbable element inside the focus trap
|
||||
if (tabbable.length > 0) {
|
||||
var _lastKeydown$current, _lastKeydown$current2;
|
||||
const isShiftTab = Boolean(((_lastKeydown$current = lastKeydown.current) == null ? void 0 : _lastKeydown$current.shiftKey) && ((_lastKeydown$current2 = lastKeydown.current) == null ? void 0 : _lastKeydown$current2.key) === 'Tab');
|
||||
const focusNext = tabbable[0];
|
||||
const focusPrevious = tabbable[tabbable.length - 1];
|
||||
if (typeof focusNext !== 'string' && typeof focusPrevious !== 'string') {
|
||||
if (isShiftTab) {
|
||||
focusPrevious.focus();
|
||||
} else {
|
||||
focusNext.focus();
|
||||
}
|
||||
}
|
||||
// no tabbable elements in the trap focus or the focus was outside of the focus trap
|
||||
} else {
|
||||
rootElement.focus();
|
||||
}
|
||||
};
|
||||
doc.addEventListener('focusin', contain);
|
||||
doc.addEventListener('keydown', loopFocus, true);
|
||||
|
||||
// With Edge, Safari and Firefox, no focus related events are fired when the focused area stops being a focused area.
|
||||
// for example https://bugzilla.mozilla.org/show_bug.cgi?id=559561.
|
||||
// Instead, we can look if the active element was restored on the BODY element.
|
||||
//
|
||||
// The whatwg spec defines how the browser should behave but does not explicitly mention any events:
|
||||
// https://html.spec.whatwg.org/multipage/interaction.html#focus-fixup-rule.
|
||||
const interval = setInterval(() => {
|
||||
if (doc.activeElement && doc.activeElement.tagName === 'BODY') {
|
||||
contain();
|
||||
}
|
||||
}, 50);
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
doc.removeEventListener('focusin', contain);
|
||||
doc.removeEventListener('keydown', loopFocus, true);
|
||||
};
|
||||
}, [disableAutoFocus, disableEnforceFocus, disableRestoreFocus, isEnabled, open, getTabbable]);
|
||||
const onFocus = event => {
|
||||
if (nodeToRestore.current === null) {
|
||||
nodeToRestore.current = event.relatedTarget;
|
||||
}
|
||||
activated.current = true;
|
||||
reactFocusEventTarget.current = event.target;
|
||||
const childrenPropsHandler = children.props.onFocus;
|
||||
if (childrenPropsHandler) {
|
||||
childrenPropsHandler(event);
|
||||
}
|
||||
};
|
||||
const handleFocusSentinel = event => {
|
||||
if (nodeToRestore.current === null) {
|
||||
nodeToRestore.current = event.relatedTarget;
|
||||
}
|
||||
activated.current = true;
|
||||
};
|
||||
return /*#__PURE__*/_jsxs(React.Fragment, {
|
||||
children: [/*#__PURE__*/_jsx("div", {
|
||||
tabIndex: open ? 0 : -1,
|
||||
onFocus: handleFocusSentinel,
|
||||
ref: sentinelStart,
|
||||
"data-testid": "sentinelStart"
|
||||
}), /*#__PURE__*/React.cloneElement(children, {
|
||||
ref: handleRef,
|
||||
onFocus
|
||||
}), /*#__PURE__*/_jsx("div", {
|
||||
tabIndex: open ? 0 : -1,
|
||||
onFocus: handleFocusSentinel,
|
||||
ref: sentinelEnd,
|
||||
"data-testid": "sentinelEnd"
|
||||
})]
|
||||
});
|
||||
}
|
||||
process.env.NODE_ENV !== "production" ? FocusTrap.propTypes /* remove-proptypes */ = {
|
||||
// ┌────────────────────────────── Warning ──────────────────────────────┐
|
||||
// │ These PropTypes are generated from the TypeScript type definitions. │
|
||||
// │ To update them, edit the TypeScript types and run `pnpm proptypes`. │
|
||||
// └─────────────────────────────────────────────────────────────────────┘
|
||||
/**
|
||||
* A single child content element.
|
||||
*/
|
||||
children: elementAcceptingRef,
|
||||
/**
|
||||
* If `true`, the focus trap will not automatically shift focus to itself when it opens, and
|
||||
* replace it to the last focused element when it closes.
|
||||
* This also works correctly with any focus trap children that have the `disableAutoFocus` prop.
|
||||
*
|
||||
* Generally this should never be set to `true` as it makes the focus trap less
|
||||
* accessible to assistive technologies, like screen readers.
|
||||
* @default false
|
||||
*/
|
||||
disableAutoFocus: PropTypes.bool,
|
||||
/**
|
||||
* If `true`, the focus trap will not prevent focus from leaving the focus trap while open.
|
||||
*
|
||||
* Generally this should never be set to `true` as it makes the focus trap less
|
||||
* accessible to assistive technologies, like screen readers.
|
||||
* @default false
|
||||
*/
|
||||
disableEnforceFocus: PropTypes.bool,
|
||||
/**
|
||||
* If `true`, the focus trap will not restore focus to previously focused element once
|
||||
* focus trap is hidden or unmounted.
|
||||
* @default false
|
||||
*/
|
||||
disableRestoreFocus: PropTypes.bool,
|
||||
/**
|
||||
* Returns an array of ordered tabbable nodes (i.e. in tab order) within the root.
|
||||
* For instance, you can provide the "tabbable" npm dependency.
|
||||
* @param {HTMLElement} root
|
||||
*/
|
||||
getTabbable: PropTypes.func,
|
||||
/**
|
||||
* This prop extends the `open` prop.
|
||||
* It allows to toggle the open state without having to wait for a rerender when changing the `open` prop.
|
||||
* This prop should be memoized.
|
||||
* It can be used to support multiple focus trap mounted at the same time.
|
||||
* @default function defaultIsEnabled(): boolean {
|
||||
* return true;
|
||||
* }
|
||||
*/
|
||||
isEnabled: PropTypes.func,
|
||||
/**
|
||||
* If `true`, focus is locked.
|
||||
*/
|
||||
open: PropTypes.bool.isRequired
|
||||
} : void 0;
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
// eslint-disable-next-line
|
||||
FocusTrap['propTypes' + ''] = exactProp(FocusTrap.propTypes);
|
||||
}
|
||||
export { FocusTrap };
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import { exactProp, HTMLElementType, unstable_useEnhancedEffect as useEnhancedEffect, unstable_useForkRef as useForkRef, unstable_setRef as setRef } from '@mui/utils';
|
||||
import { jsx as _jsx } from "react/jsx-runtime";
|
||||
function getContainer(container) {
|
||||
return typeof container === 'function' ? container() : container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Portals provide a first-class way to render children into a DOM node
|
||||
* that exists outside the DOM hierarchy of the parent component.
|
||||
*
|
||||
* Demos:
|
||||
*
|
||||
* - [Portal](https://mui.com/base-ui/react-portal/)
|
||||
*
|
||||
* API:
|
||||
*
|
||||
* - [Portal API](https://mui.com/base-ui/react-portal/components-api/#portal)
|
||||
*/
|
||||
const Portal = /*#__PURE__*/React.forwardRef(function Portal(props, forwardedRef) {
|
||||
const {
|
||||
children,
|
||||
container,
|
||||
disablePortal = false
|
||||
} = props;
|
||||
const [mountNode, setMountNode] = React.useState(null);
|
||||
// @ts-expect-error TODO upstream fix
|
||||
const handleRef = useForkRef( /*#__PURE__*/React.isValidElement(children) ? children.ref : null, forwardedRef);
|
||||
useEnhancedEffect(() => {
|
||||
if (!disablePortal) {
|
||||
setMountNode(getContainer(container) || document.body);
|
||||
}
|
||||
}, [container, disablePortal]);
|
||||
useEnhancedEffect(() => {
|
||||
if (mountNode && !disablePortal) {
|
||||
setRef(forwardedRef, mountNode);
|
||||
return () => {
|
||||
setRef(forwardedRef, null);
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
}, [forwardedRef, mountNode, disablePortal]);
|
||||
if (disablePortal) {
|
||||
if ( /*#__PURE__*/React.isValidElement(children)) {
|
||||
const newProps = {
|
||||
ref: handleRef
|
||||
};
|
||||
return /*#__PURE__*/React.cloneElement(children, newProps);
|
||||
}
|
||||
return /*#__PURE__*/_jsx(React.Fragment, {
|
||||
children: children
|
||||
});
|
||||
}
|
||||
return /*#__PURE__*/_jsx(React.Fragment, {
|
||||
children: mountNode ? /*#__PURE__*/ReactDOM.createPortal(children, mountNode) : mountNode
|
||||
});
|
||||
});
|
||||
process.env.NODE_ENV !== "production" ? Portal.propTypes /* remove-proptypes */ = {
|
||||
// ┌────────────────────────────── Warning ──────────────────────────────┐
|
||||
// │ These PropTypes are generated from the TypeScript type definitions. │
|
||||
// │ To update them, edit the TypeScript types and run `pnpm proptypes`. │
|
||||
// └─────────────────────────────────────────────────────────────────────┘
|
||||
/**
|
||||
* The children to render into the `container`.
|
||||
*/
|
||||
children: PropTypes.node,
|
||||
/**
|
||||
* An HTML element or function that returns one.
|
||||
* The `container` will have the portal children appended to it.
|
||||
*
|
||||
* You can also provide a callback, which is called in a React layout effect.
|
||||
* This lets you set the container from a ref, and also makes server-side rendering possible.
|
||||
*
|
||||
* By default, it uses the body of the top-level document object,
|
||||
* so it's simply `document.body` most of the time.
|
||||
*/
|
||||
container: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([HTMLElementType, PropTypes.func]),
|
||||
/**
|
||||
* The `children` will be under the DOM hierarchy of the parent component.
|
||||
* @default false
|
||||
*/
|
||||
disablePortal: PropTypes.bool
|
||||
} : void 0;
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
// eslint-disable-next-line
|
||||
Portal['propTypes' + ''] = exactProp(Portal.propTypes);
|
||||
}
|
||||
export { Portal };
|
||||
+219
@@ -0,0 +1,219 @@
|
||||
'use client';
|
||||
|
||||
import _extends from "@babel/runtime/helpers/esm/extends";
|
||||
import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
|
||||
const _excluded = ["onChange", "maxRows", "minRows", "style", "value"];
|
||||
import * as React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { unstable_debounce as debounce, unstable_useForkRef as useForkRef, unstable_useEnhancedEffect as useEnhancedEffect, unstable_ownerWindow as ownerWindow } from '@mui/utils';
|
||||
import { jsx as _jsx } from "react/jsx-runtime";
|
||||
import { jsxs as _jsxs } from "react/jsx-runtime";
|
||||
function getStyleValue(value) {
|
||||
return parseInt(value, 10) || 0;
|
||||
}
|
||||
const styles = {
|
||||
shadow: {
|
||||
// Visibility needed to hide the extra text area on iPads
|
||||
visibility: 'hidden',
|
||||
// Remove from the content flow
|
||||
position: 'absolute',
|
||||
// Ignore the scrollbar width
|
||||
overflow: 'hidden',
|
||||
height: 0,
|
||||
top: 0,
|
||||
left: 0,
|
||||
// Create a new layer, increase the isolation of the computed values
|
||||
transform: 'translateZ(0)'
|
||||
}
|
||||
};
|
||||
function isEmpty(obj) {
|
||||
return obj === undefined || obj === null || Object.keys(obj).length === 0 || obj.outerHeightStyle === 0 && !obj.overflowing;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Demos:
|
||||
*
|
||||
* - [Textarea Autosize](https://mui.com/base-ui/react-textarea-autosize/)
|
||||
* - [Textarea Autosize](https://mui.com/material-ui/react-textarea-autosize/)
|
||||
*
|
||||
* API:
|
||||
*
|
||||
* - [TextareaAutosize API](https://mui.com/base-ui/react-textarea-autosize/components-api/#textarea-autosize)
|
||||
*/
|
||||
const TextareaAutosize = /*#__PURE__*/React.forwardRef(function TextareaAutosize(props, forwardedRef) {
|
||||
const {
|
||||
onChange,
|
||||
maxRows,
|
||||
minRows = 1,
|
||||
style,
|
||||
value
|
||||
} = props,
|
||||
other = _objectWithoutPropertiesLoose(props, _excluded);
|
||||
const {
|
||||
current: isControlled
|
||||
} = React.useRef(value != null);
|
||||
const inputRef = React.useRef(null);
|
||||
const handleRef = useForkRef(forwardedRef, inputRef);
|
||||
const shadowRef = React.useRef(null);
|
||||
const calculateTextareaStyles = React.useCallback(() => {
|
||||
const input = inputRef.current;
|
||||
const containerWindow = ownerWindow(input);
|
||||
const computedStyle = containerWindow.getComputedStyle(input);
|
||||
|
||||
// If input's width is shrunk and it's not visible, don't sync height.
|
||||
if (computedStyle.width === '0px') {
|
||||
return {
|
||||
outerHeightStyle: 0,
|
||||
overflowing: false
|
||||
};
|
||||
}
|
||||
const inputShallow = shadowRef.current;
|
||||
inputShallow.style.width = computedStyle.width;
|
||||
inputShallow.value = input.value || props.placeholder || 'x';
|
||||
if (inputShallow.value.slice(-1) === '\n') {
|
||||
// Certain fonts which overflow the line height will cause the textarea
|
||||
// to report a different scrollHeight depending on whether the last line
|
||||
// is empty. Make it non-empty to avoid this issue.
|
||||
inputShallow.value += ' ';
|
||||
}
|
||||
const boxSizing = computedStyle.boxSizing;
|
||||
const padding = getStyleValue(computedStyle.paddingBottom) + getStyleValue(computedStyle.paddingTop);
|
||||
const border = getStyleValue(computedStyle.borderBottomWidth) + getStyleValue(computedStyle.borderTopWidth);
|
||||
|
||||
// The height of the inner content
|
||||
const innerHeight = inputShallow.scrollHeight;
|
||||
|
||||
// Measure height of a textarea with a single row
|
||||
inputShallow.value = 'x';
|
||||
const singleRowHeight = inputShallow.scrollHeight;
|
||||
|
||||
// The height of the outer content
|
||||
let outerHeight = innerHeight;
|
||||
if (minRows) {
|
||||
outerHeight = Math.max(Number(minRows) * singleRowHeight, outerHeight);
|
||||
}
|
||||
if (maxRows) {
|
||||
outerHeight = Math.min(Number(maxRows) * singleRowHeight, outerHeight);
|
||||
}
|
||||
outerHeight = Math.max(outerHeight, singleRowHeight);
|
||||
|
||||
// Take the box sizing into account for applying this value as a style.
|
||||
const outerHeightStyle = outerHeight + (boxSizing === 'border-box' ? padding + border : 0);
|
||||
const overflowing = Math.abs(outerHeight - innerHeight) <= 1;
|
||||
return {
|
||||
outerHeightStyle,
|
||||
overflowing
|
||||
};
|
||||
}, [maxRows, minRows, props.placeholder]);
|
||||
const syncHeight = React.useCallback(() => {
|
||||
const textareaStyles = calculateTextareaStyles();
|
||||
if (isEmpty(textareaStyles)) {
|
||||
return;
|
||||
}
|
||||
const input = inputRef.current;
|
||||
input.style.height = `${textareaStyles.outerHeightStyle}px`;
|
||||
input.style.overflow = textareaStyles.overflowing ? 'hidden' : '';
|
||||
}, [calculateTextareaStyles]);
|
||||
useEnhancedEffect(() => {
|
||||
const handleResize = () => {
|
||||
syncHeight();
|
||||
};
|
||||
// Workaround a "ResizeObserver loop completed with undelivered notifications" error
|
||||
// in test.
|
||||
// Note that we might need to use this logic in production per https://github.com/WICG/resize-observer/issues/38
|
||||
// Also see https://github.com/mui/mui-x/issues/8733
|
||||
let rAF;
|
||||
const rAFHandleResize = () => {
|
||||
cancelAnimationFrame(rAF);
|
||||
rAF = requestAnimationFrame(() => {
|
||||
handleResize();
|
||||
});
|
||||
};
|
||||
const debounceHandleResize = debounce(handleResize);
|
||||
const input = inputRef.current;
|
||||
const containerWindow = ownerWindow(input);
|
||||
containerWindow.addEventListener('resize', debounceHandleResize);
|
||||
let resizeObserver;
|
||||
if (typeof ResizeObserver !== 'undefined') {
|
||||
resizeObserver = new ResizeObserver(process.env.NODE_ENV === 'test' ? rAFHandleResize : handleResize);
|
||||
resizeObserver.observe(input);
|
||||
}
|
||||
return () => {
|
||||
debounceHandleResize.clear();
|
||||
cancelAnimationFrame(rAF);
|
||||
containerWindow.removeEventListener('resize', debounceHandleResize);
|
||||
if (resizeObserver) {
|
||||
resizeObserver.disconnect();
|
||||
}
|
||||
};
|
||||
}, [calculateTextareaStyles, syncHeight]);
|
||||
useEnhancedEffect(() => {
|
||||
syncHeight();
|
||||
});
|
||||
const handleChange = event => {
|
||||
if (!isControlled) {
|
||||
syncHeight();
|
||||
}
|
||||
if (onChange) {
|
||||
onChange(event);
|
||||
}
|
||||
};
|
||||
return /*#__PURE__*/_jsxs(React.Fragment, {
|
||||
children: [/*#__PURE__*/_jsx("textarea", _extends({
|
||||
value: value,
|
||||
onChange: handleChange,
|
||||
ref: handleRef
|
||||
// Apply the rows prop to get a "correct" first SSR paint
|
||||
,
|
||||
rows: minRows,
|
||||
style: style
|
||||
}, other)), /*#__PURE__*/_jsx("textarea", {
|
||||
"aria-hidden": true,
|
||||
className: props.className,
|
||||
readOnly: true,
|
||||
ref: shadowRef,
|
||||
tabIndex: -1,
|
||||
style: _extends({}, styles.shadow, style, {
|
||||
paddingTop: 0,
|
||||
paddingBottom: 0
|
||||
})
|
||||
})]
|
||||
});
|
||||
});
|
||||
process.env.NODE_ENV !== "production" ? TextareaAutosize.propTypes /* remove-proptypes */ = {
|
||||
// ┌────────────────────────────── Warning ──────────────────────────────┐
|
||||
// │ These PropTypes are generated from the TypeScript type definitions. │
|
||||
// │ To update them, edit the TypeScript types and run `pnpm proptypes`. │
|
||||
// └─────────────────────────────────────────────────────────────────────┘
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
className: PropTypes.string,
|
||||
/**
|
||||
* Maximum number of rows to display.
|
||||
*/
|
||||
maxRows: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
||||
/**
|
||||
* Minimum number of rows to display.
|
||||
* @default 1
|
||||
*/
|
||||
minRows: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
onChange: PropTypes.func,
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
placeholder: PropTypes.string,
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
style: PropTypes.object,
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
value: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.string), PropTypes.number, PropTypes.string])
|
||||
} : void 0;
|
||||
export { TextareaAutosize };
|
||||
+213
@@ -0,0 +1,213 @@
|
||||
import { unstable_ownerWindow as ownerWindow, unstable_ownerDocument as ownerDocument, unstable_getScrollbarSize as getScrollbarSize } from '@mui/utils';
|
||||
// Is a vertical scrollbar displayed?
|
||||
function isOverflowing(container) {
|
||||
const doc = ownerDocument(container);
|
||||
if (doc.body === container) {
|
||||
return ownerWindow(container).innerWidth > doc.documentElement.clientWidth;
|
||||
}
|
||||
return container.scrollHeight > container.clientHeight;
|
||||
}
|
||||
export function ariaHidden(element, show) {
|
||||
if (show) {
|
||||
element.setAttribute('aria-hidden', 'true');
|
||||
} else {
|
||||
element.removeAttribute('aria-hidden');
|
||||
}
|
||||
}
|
||||
function getPaddingRight(element) {
|
||||
return parseInt(ownerWindow(element).getComputedStyle(element).paddingRight, 10) || 0;
|
||||
}
|
||||
function isAriaHiddenForbiddenOnElement(element) {
|
||||
// The forbidden HTML tags are the ones from ARIA specification that
|
||||
// can be children of body and can't have aria-hidden attribute.
|
||||
// cf. https://www.w3.org/TR/html-aria/#docconformance
|
||||
const forbiddenTagNames = ['TEMPLATE', 'SCRIPT', 'STYLE', 'LINK', 'MAP', 'META', 'NOSCRIPT', 'PICTURE', 'COL', 'COLGROUP', 'PARAM', 'SLOT', 'SOURCE', 'TRACK'];
|
||||
const isForbiddenTagName = forbiddenTagNames.indexOf(element.tagName) !== -1;
|
||||
const isInputHidden = element.tagName === 'INPUT' && element.getAttribute('type') === 'hidden';
|
||||
return isForbiddenTagName || isInputHidden;
|
||||
}
|
||||
function ariaHiddenSiblings(container, mountElement, currentElement, elementsToExclude, show) {
|
||||
const blacklist = [mountElement, currentElement, ...elementsToExclude];
|
||||
[].forEach.call(container.children, element => {
|
||||
const isNotExcludedElement = blacklist.indexOf(element) === -1;
|
||||
const isNotForbiddenElement = !isAriaHiddenForbiddenOnElement(element);
|
||||
if (isNotExcludedElement && isNotForbiddenElement) {
|
||||
ariaHidden(element, show);
|
||||
}
|
||||
});
|
||||
}
|
||||
function findIndexOf(items, callback) {
|
||||
let idx = -1;
|
||||
items.some((item, index) => {
|
||||
if (callback(item)) {
|
||||
idx = index;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
return idx;
|
||||
}
|
||||
function handleContainer(containerInfo, props) {
|
||||
const restoreStyle = [];
|
||||
const container = containerInfo.container;
|
||||
if (!props.disableScrollLock) {
|
||||
if (isOverflowing(container)) {
|
||||
// Compute the size before applying overflow hidden to avoid any scroll jumps.
|
||||
const scrollbarSize = getScrollbarSize(ownerDocument(container));
|
||||
restoreStyle.push({
|
||||
value: container.style.paddingRight,
|
||||
property: 'padding-right',
|
||||
el: container
|
||||
});
|
||||
// Use computed style, here to get the real padding to add our scrollbar width.
|
||||
container.style.paddingRight = `${getPaddingRight(container) + scrollbarSize}px`;
|
||||
|
||||
// .mui-fixed is a global helper.
|
||||
const fixedElements = ownerDocument(container).querySelectorAll('.mui-fixed');
|
||||
[].forEach.call(fixedElements, element => {
|
||||
restoreStyle.push({
|
||||
value: element.style.paddingRight,
|
||||
property: 'padding-right',
|
||||
el: element
|
||||
});
|
||||
element.style.paddingRight = `${getPaddingRight(element) + scrollbarSize}px`;
|
||||
});
|
||||
}
|
||||
let scrollContainer;
|
||||
if (container.parentNode instanceof DocumentFragment) {
|
||||
scrollContainer = ownerDocument(container).body;
|
||||
} else {
|
||||
// Support html overflow-y: auto for scroll stability between pages
|
||||
// https://css-tricks.com/snippets/css/force-vertical-scrollbar/
|
||||
const parent = container.parentElement;
|
||||
const containerWindow = ownerWindow(container);
|
||||
scrollContainer = (parent == null ? void 0 : parent.nodeName) === 'HTML' && containerWindow.getComputedStyle(parent).overflowY === 'scroll' ? parent : container;
|
||||
}
|
||||
|
||||
// Block the scroll even if no scrollbar is visible to account for mobile keyboard
|
||||
// screensize shrink.
|
||||
restoreStyle.push({
|
||||
value: scrollContainer.style.overflow,
|
||||
property: 'overflow',
|
||||
el: scrollContainer
|
||||
}, {
|
||||
value: scrollContainer.style.overflowX,
|
||||
property: 'overflow-x',
|
||||
el: scrollContainer
|
||||
}, {
|
||||
value: scrollContainer.style.overflowY,
|
||||
property: 'overflow-y',
|
||||
el: scrollContainer
|
||||
});
|
||||
scrollContainer.style.overflow = 'hidden';
|
||||
}
|
||||
const restore = () => {
|
||||
restoreStyle.forEach(({
|
||||
value,
|
||||
el,
|
||||
property
|
||||
}) => {
|
||||
if (value) {
|
||||
el.style.setProperty(property, value);
|
||||
} else {
|
||||
el.style.removeProperty(property);
|
||||
}
|
||||
});
|
||||
};
|
||||
return restore;
|
||||
}
|
||||
function getHiddenSiblings(container) {
|
||||
const hiddenSiblings = [];
|
||||
[].forEach.call(container.children, element => {
|
||||
if (element.getAttribute('aria-hidden') === 'true') {
|
||||
hiddenSiblings.push(element);
|
||||
}
|
||||
});
|
||||
return hiddenSiblings;
|
||||
}
|
||||
/**
|
||||
* @ignore - do not document.
|
||||
*
|
||||
* Proper state management for containers and the modals in those containers.
|
||||
* Simplified, but inspired by react-overlay's ModalManager class.
|
||||
* Used by the Modal to ensure proper styling of containers.
|
||||
*/
|
||||
export class ModalManager {
|
||||
constructor() {
|
||||
this.containers = void 0;
|
||||
this.modals = void 0;
|
||||
this.modals = [];
|
||||
this.containers = [];
|
||||
}
|
||||
add(modal, container) {
|
||||
let modalIndex = this.modals.indexOf(modal);
|
||||
if (modalIndex !== -1) {
|
||||
return modalIndex;
|
||||
}
|
||||
modalIndex = this.modals.length;
|
||||
this.modals.push(modal);
|
||||
|
||||
// If the modal we are adding is already in the DOM.
|
||||
if (modal.modalRef) {
|
||||
ariaHidden(modal.modalRef, false);
|
||||
}
|
||||
const hiddenSiblings = getHiddenSiblings(container);
|
||||
ariaHiddenSiblings(container, modal.mount, modal.modalRef, hiddenSiblings, true);
|
||||
const containerIndex = findIndexOf(this.containers, item => item.container === container);
|
||||
if (containerIndex !== -1) {
|
||||
this.containers[containerIndex].modals.push(modal);
|
||||
return modalIndex;
|
||||
}
|
||||
this.containers.push({
|
||||
modals: [modal],
|
||||
container,
|
||||
restore: null,
|
||||
hiddenSiblings
|
||||
});
|
||||
return modalIndex;
|
||||
}
|
||||
mount(modal, props) {
|
||||
const containerIndex = findIndexOf(this.containers, item => item.modals.indexOf(modal) !== -1);
|
||||
const containerInfo = this.containers[containerIndex];
|
||||
if (!containerInfo.restore) {
|
||||
containerInfo.restore = handleContainer(containerInfo, props);
|
||||
}
|
||||
}
|
||||
remove(modal, ariaHiddenState = true) {
|
||||
const modalIndex = this.modals.indexOf(modal);
|
||||
if (modalIndex === -1) {
|
||||
return modalIndex;
|
||||
}
|
||||
const containerIndex = findIndexOf(this.containers, item => item.modals.indexOf(modal) !== -1);
|
||||
const containerInfo = this.containers[containerIndex];
|
||||
containerInfo.modals.splice(containerInfo.modals.indexOf(modal), 1);
|
||||
this.modals.splice(modalIndex, 1);
|
||||
|
||||
// If that was the last modal in a container, clean up the container.
|
||||
if (containerInfo.modals.length === 0) {
|
||||
// The modal might be closed before it had the chance to be mounted in the DOM.
|
||||
if (containerInfo.restore) {
|
||||
containerInfo.restore();
|
||||
}
|
||||
if (modal.modalRef) {
|
||||
// In case the modal wasn't in the DOM yet.
|
||||
ariaHidden(modal.modalRef, ariaHiddenState);
|
||||
}
|
||||
ariaHiddenSiblings(containerInfo.container, modal.mount, modal.modalRef, containerInfo.hiddenSiblings, false);
|
||||
this.containers.splice(containerIndex, 1);
|
||||
} else {
|
||||
// Otherwise make sure the next top modal is visible to a screen reader.
|
||||
const nextTop = containerInfo.modals[containerInfo.modals.length - 1];
|
||||
// as soon as a modal is adding its modalRef is undefined. it can't set
|
||||
// aria-hidden because the dom element doesn't exist either
|
||||
// when modal was unmounted before modalRef gets null
|
||||
if (nextTop.modalRef) {
|
||||
ariaHidden(nextTop.modalRef, false);
|
||||
}
|
||||
}
|
||||
return modalIndex;
|
||||
}
|
||||
isTopModal(modal) {
|
||||
return this.modals.length > 0 && this.modals[this.modals.length - 1] === modal;
|
||||
}
|
||||
}
|
||||
+194
@@ -0,0 +1,194 @@
|
||||
'use client';
|
||||
|
||||
import _extends from "@babel/runtime/helpers/esm/extends";
|
||||
import * as React from 'react';
|
||||
import { unstable_ownerDocument as ownerDocument, unstable_useForkRef as useForkRef, unstable_useEventCallback as useEventCallback, unstable_createChainedFunction as createChainedFunction } from '@mui/utils';
|
||||
import { extractEventHandlers } from '../utils';
|
||||
import { ModalManager, ariaHidden } from './ModalManager';
|
||||
function getContainer(container) {
|
||||
return typeof container === 'function' ? container() : container;
|
||||
}
|
||||
function getHasTransition(children) {
|
||||
return children ? children.props.hasOwnProperty('in') : false;
|
||||
}
|
||||
|
||||
// A modal manager used to track and manage the state of open Modals.
|
||||
// Modals don't open on the server so this won't conflict with concurrent requests.
|
||||
const defaultManager = new ModalManager();
|
||||
/**
|
||||
*
|
||||
* Demos:
|
||||
*
|
||||
* - [Modal](https://mui.com/base-ui/react-modal/#hook)
|
||||
*
|
||||
* API:
|
||||
*
|
||||
* - [useModal API](https://mui.com/base-ui/react-modal/hooks-api/#use-modal)
|
||||
*/
|
||||
export function useModal(parameters) {
|
||||
const {
|
||||
container,
|
||||
disableEscapeKeyDown = false,
|
||||
disableScrollLock = false,
|
||||
// @ts-ignore internal logic - Base UI supports the manager as a prop too
|
||||
manager = defaultManager,
|
||||
closeAfterTransition = false,
|
||||
onTransitionEnter,
|
||||
onTransitionExited,
|
||||
children,
|
||||
onClose,
|
||||
open,
|
||||
rootRef
|
||||
} = parameters;
|
||||
|
||||
// @ts-ignore internal logic
|
||||
const modal = React.useRef({});
|
||||
const mountNodeRef = React.useRef(null);
|
||||
const modalRef = React.useRef(null);
|
||||
const handleRef = useForkRef(modalRef, rootRef);
|
||||
const [exited, setExited] = React.useState(!open);
|
||||
const hasTransition = getHasTransition(children);
|
||||
let ariaHiddenProp = true;
|
||||
if (parameters['aria-hidden'] === 'false' || parameters['aria-hidden'] === false) {
|
||||
ariaHiddenProp = false;
|
||||
}
|
||||
const getDoc = () => ownerDocument(mountNodeRef.current);
|
||||
const getModal = () => {
|
||||
modal.current.modalRef = modalRef.current;
|
||||
modal.current.mount = mountNodeRef.current;
|
||||
return modal.current;
|
||||
};
|
||||
const handleMounted = () => {
|
||||
manager.mount(getModal(), {
|
||||
disableScrollLock
|
||||
});
|
||||
|
||||
// Fix a bug on Chrome where the scroll isn't initially 0.
|
||||
if (modalRef.current) {
|
||||
modalRef.current.scrollTop = 0;
|
||||
}
|
||||
};
|
||||
const handleOpen = useEventCallback(() => {
|
||||
const resolvedContainer = getContainer(container) || getDoc().body;
|
||||
manager.add(getModal(), resolvedContainer);
|
||||
|
||||
// The element was already mounted.
|
||||
if (modalRef.current) {
|
||||
handleMounted();
|
||||
}
|
||||
});
|
||||
const isTopModal = React.useCallback(() => manager.isTopModal(getModal()), [manager]);
|
||||
const handlePortalRef = useEventCallback(node => {
|
||||
mountNodeRef.current = node;
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
if (open && isTopModal()) {
|
||||
handleMounted();
|
||||
} else if (modalRef.current) {
|
||||
ariaHidden(modalRef.current, ariaHiddenProp);
|
||||
}
|
||||
});
|
||||
const handleClose = React.useCallback(() => {
|
||||
manager.remove(getModal(), ariaHiddenProp);
|
||||
}, [ariaHiddenProp, manager]);
|
||||
React.useEffect(() => {
|
||||
return () => {
|
||||
handleClose();
|
||||
};
|
||||
}, [handleClose]);
|
||||
React.useEffect(() => {
|
||||
if (open) {
|
||||
handleOpen();
|
||||
} else if (!hasTransition || !closeAfterTransition) {
|
||||
handleClose();
|
||||
}
|
||||
}, [open, handleClose, hasTransition, closeAfterTransition, handleOpen]);
|
||||
const createHandleKeyDown = otherHandlers => event => {
|
||||
var _otherHandlers$onKeyD;
|
||||
(_otherHandlers$onKeyD = otherHandlers.onKeyDown) == null || _otherHandlers$onKeyD.call(otherHandlers, event);
|
||||
|
||||
// The handler doesn't take event.defaultPrevented into account:
|
||||
//
|
||||
// event.preventDefault() is meant to stop default behaviors like
|
||||
// clicking a checkbox to check it, hitting a button to submit a form,
|
||||
// and hitting left arrow to move the cursor in a text input etc.
|
||||
// Only special HTML elements have these default behaviors.
|
||||
if (event.key !== 'Escape' || event.which === 229 ||
|
||||
// Wait until IME is settled.
|
||||
!isTopModal()) {
|
||||
return;
|
||||
}
|
||||
if (!disableEscapeKeyDown) {
|
||||
// Swallow the event, in case someone is listening for the escape key on the body.
|
||||
event.stopPropagation();
|
||||
if (onClose) {
|
||||
onClose(event, 'escapeKeyDown');
|
||||
}
|
||||
}
|
||||
};
|
||||
const createHandleBackdropClick = otherHandlers => event => {
|
||||
var _otherHandlers$onClic;
|
||||
(_otherHandlers$onClic = otherHandlers.onClick) == null || _otherHandlers$onClic.call(otherHandlers, event);
|
||||
if (event.target !== event.currentTarget) {
|
||||
return;
|
||||
}
|
||||
if (onClose) {
|
||||
onClose(event, 'backdropClick');
|
||||
}
|
||||
};
|
||||
const getRootProps = (otherHandlers = {}) => {
|
||||
const propsEventHandlers = extractEventHandlers(parameters);
|
||||
|
||||
// The custom event handlers shouldn't be spread on the root element
|
||||
delete propsEventHandlers.onTransitionEnter;
|
||||
delete propsEventHandlers.onTransitionExited;
|
||||
const externalEventHandlers = _extends({}, propsEventHandlers, otherHandlers);
|
||||
return _extends({
|
||||
role: 'presentation'
|
||||
}, externalEventHandlers, {
|
||||
onKeyDown: createHandleKeyDown(externalEventHandlers),
|
||||
ref: handleRef
|
||||
});
|
||||
};
|
||||
const getBackdropProps = (otherHandlers = {}) => {
|
||||
const externalEventHandlers = otherHandlers;
|
||||
return _extends({
|
||||
'aria-hidden': true
|
||||
}, externalEventHandlers, {
|
||||
onClick: createHandleBackdropClick(externalEventHandlers),
|
||||
open
|
||||
});
|
||||
};
|
||||
const getTransitionProps = () => {
|
||||
const handleEnter = () => {
|
||||
setExited(false);
|
||||
if (onTransitionEnter) {
|
||||
onTransitionEnter();
|
||||
}
|
||||
};
|
||||
const handleExited = () => {
|
||||
setExited(true);
|
||||
if (onTransitionExited) {
|
||||
onTransitionExited();
|
||||
}
|
||||
if (closeAfterTransition) {
|
||||
handleClose();
|
||||
}
|
||||
};
|
||||
return {
|
||||
onEnter: createChainedFunction(handleEnter, children == null ? void 0 : children.props.onEnter),
|
||||
onExited: createChainedFunction(handleExited, children == null ? void 0 : children.props.onExited)
|
||||
};
|
||||
};
|
||||
return {
|
||||
getRootProps,
|
||||
getBackdropProps,
|
||||
getTransitionProps,
|
||||
rootRef: handleRef,
|
||||
portalRef: handlePortalRef,
|
||||
isTopModal,
|
||||
exited,
|
||||
hasTransition
|
||||
};
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import _extends from "@babel/runtime/helpers/esm/extends";
|
||||
import { isHostComponent } from './isHostComponent';
|
||||
|
||||
/**
|
||||
* Type of the ownerState based on the type of an element it applies to.
|
||||
* This resolves to the provided OwnerState for React components and `undefined` for host components.
|
||||
* Falls back to `OwnerState | undefined` when the exact type can't be determined in development time.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Appends the ownerState object to the props, merging with the existing one if necessary.
|
||||
*
|
||||
* @param elementType Type of the element that owns the `existingProps`. If the element is a DOM node or undefined, `ownerState` is not applied.
|
||||
* @param otherProps Props of the element.
|
||||
* @param ownerState
|
||||
*/
|
||||
export function appendOwnerState(elementType, otherProps, ownerState) {
|
||||
if (elementType === undefined || isHostComponent(elementType)) {
|
||||
return otherProps;
|
||||
}
|
||||
return _extends({}, otherProps, {
|
||||
ownerState: _extends({}, otherProps.ownerState, ownerState)
|
||||
});
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Extracts event handlers from a given object.
|
||||
* A prop is considered an event handler if it is a function and its name starts with `on`.
|
||||
*
|
||||
* @param object An object to extract event handlers from.
|
||||
* @param excludeKeys An array of keys to exclude from the returned object.
|
||||
*/
|
||||
export function extractEventHandlers(object, excludeKeys = []) {
|
||||
if (object === undefined) {
|
||||
return {};
|
||||
}
|
||||
const result = {};
|
||||
Object.keys(object).filter(prop => prop.match(/^on[A-Z]/) && typeof object[prop] === 'function' && !excludeKeys.includes(prop)).forEach(prop => {
|
||||
result[prop] = object[prop];
|
||||
});
|
||||
return result;
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Determines if a given element is a DOM element name (i.e. not a React component).
|
||||
*/
|
||||
export function isHostComponent(element) {
|
||||
return typeof element === 'string';
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
import _extends from "@babel/runtime/helpers/esm/extends";
|
||||
import clsx from 'clsx';
|
||||
import { extractEventHandlers } from './extractEventHandlers';
|
||||
import { omitEventHandlers } from './omitEventHandlers';
|
||||
/**
|
||||
* Merges the slot component internal props (usually coming from a hook)
|
||||
* with the externally provided ones.
|
||||
*
|
||||
* The merge order is (the latter overrides the former):
|
||||
* 1. The internal props (specified as a getter function to work with get*Props hook result)
|
||||
* 2. Additional props (specified internally on a Base UI component)
|
||||
* 3. External props specified on the owner component. These should only be used on a root slot.
|
||||
* 4. External props specified in the `slotProps.*` prop.
|
||||
* 5. The `className` prop - combined from all the above.
|
||||
* @param parameters
|
||||
* @returns
|
||||
*/
|
||||
export function mergeSlotProps(parameters) {
|
||||
const {
|
||||
getSlotProps,
|
||||
additionalProps,
|
||||
externalSlotProps,
|
||||
externalForwardedProps,
|
||||
className
|
||||
} = parameters;
|
||||
if (!getSlotProps) {
|
||||
// The simpler case - getSlotProps is not defined, so no internal event handlers are defined,
|
||||
// so we can simply merge all the props without having to worry about extracting event handlers.
|
||||
const joinedClasses = clsx(additionalProps == null ? void 0 : additionalProps.className, className, externalForwardedProps == null ? void 0 : externalForwardedProps.className, externalSlotProps == null ? void 0 : externalSlotProps.className);
|
||||
const mergedStyle = _extends({}, additionalProps == null ? void 0 : additionalProps.style, externalForwardedProps == null ? void 0 : externalForwardedProps.style, externalSlotProps == null ? void 0 : externalSlotProps.style);
|
||||
const props = _extends({}, additionalProps, externalForwardedProps, externalSlotProps);
|
||||
if (joinedClasses.length > 0) {
|
||||
props.className = joinedClasses;
|
||||
}
|
||||
if (Object.keys(mergedStyle).length > 0) {
|
||||
props.style = mergedStyle;
|
||||
}
|
||||
return {
|
||||
props,
|
||||
internalRef: undefined
|
||||
};
|
||||
}
|
||||
|
||||
// In this case, getSlotProps is responsible for calling the external event handlers.
|
||||
// We don't need to include them in the merged props because of this.
|
||||
|
||||
const eventHandlers = extractEventHandlers(_extends({}, externalForwardedProps, externalSlotProps));
|
||||
const componentsPropsWithoutEventHandlers = omitEventHandlers(externalSlotProps);
|
||||
const otherPropsWithoutEventHandlers = omitEventHandlers(externalForwardedProps);
|
||||
const internalSlotProps = getSlotProps(eventHandlers);
|
||||
|
||||
// The order of classes is important here.
|
||||
// Emotion (that we use in libraries consuming Base UI) depends on this order
|
||||
// to properly override style. It requires the most important classes to be last
|
||||
// (see https://github.com/mui/material-ui/pull/33205) for the related discussion.
|
||||
const joinedClasses = clsx(internalSlotProps == null ? void 0 : internalSlotProps.className, additionalProps == null ? void 0 : additionalProps.className, className, externalForwardedProps == null ? void 0 : externalForwardedProps.className, externalSlotProps == null ? void 0 : externalSlotProps.className);
|
||||
const mergedStyle = _extends({}, internalSlotProps == null ? void 0 : internalSlotProps.style, additionalProps == null ? void 0 : additionalProps.style, externalForwardedProps == null ? void 0 : externalForwardedProps.style, externalSlotProps == null ? void 0 : externalSlotProps.style);
|
||||
const props = _extends({}, internalSlotProps, additionalProps, otherPropsWithoutEventHandlers, componentsPropsWithoutEventHandlers);
|
||||
if (joinedClasses.length > 0) {
|
||||
props.className = joinedClasses;
|
||||
}
|
||||
if (Object.keys(mergedStyle).length > 0) {
|
||||
props.style = mergedStyle;
|
||||
}
|
||||
return {
|
||||
props,
|
||||
internalRef: internalSlotProps.ref
|
||||
};
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Removes event handlers from the given object.
|
||||
* A field is considered an event handler if it is a function with a name beginning with `on`.
|
||||
*
|
||||
* @param object Object to remove event handlers from.
|
||||
* @returns Object with event handlers removed.
|
||||
*/
|
||||
export function omitEventHandlers(object) {
|
||||
if (object === undefined) {
|
||||
return {};
|
||||
}
|
||||
const result = {};
|
||||
Object.keys(object).filter(prop => !(prop.match(/^on[A-Z]/) && typeof object[prop] === 'function')).forEach(prop => {
|
||||
result[prop] = object[prop];
|
||||
});
|
||||
return result;
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* If `componentProps` is a function, calls it with the provided `ownerState`.
|
||||
* Otherwise, just returns `componentProps`.
|
||||
*/
|
||||
export function resolveComponentProps(componentProps, ownerState, slotState) {
|
||||
if (typeof componentProps === 'function') {
|
||||
return componentProps(ownerState, slotState);
|
||||
}
|
||||
return componentProps;
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
'use client';
|
||||
|
||||
import _extends from "@babel/runtime/helpers/esm/extends";
|
||||
import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
|
||||
const _excluded = ["elementType", "externalSlotProps", "ownerState", "skipResolvingSlotProps"];
|
||||
import { unstable_useForkRef as useForkRef } from '@mui/utils';
|
||||
import { appendOwnerState } from './appendOwnerState';
|
||||
import { mergeSlotProps } from './mergeSlotProps';
|
||||
import { resolveComponentProps } from './resolveComponentProps';
|
||||
/**
|
||||
* @ignore - do not document.
|
||||
* Builds the props to be passed into the slot of an unstyled component.
|
||||
* It merges the internal props of the component with the ones supplied by the user, allowing to customize the behavior.
|
||||
* If the slot component is not a host component, it also merges in the `ownerState`.
|
||||
*
|
||||
* @param parameters.getSlotProps - A function that returns the props to be passed to the slot component.
|
||||
*/
|
||||
export function useSlotProps(parameters) {
|
||||
var _parameters$additiona;
|
||||
const {
|
||||
elementType,
|
||||
externalSlotProps,
|
||||
ownerState,
|
||||
skipResolvingSlotProps = false
|
||||
} = parameters,
|
||||
rest = _objectWithoutPropertiesLoose(parameters, _excluded);
|
||||
const resolvedComponentsProps = skipResolvingSlotProps ? {} : resolveComponentProps(externalSlotProps, ownerState);
|
||||
const {
|
||||
props: mergedProps,
|
||||
internalRef
|
||||
} = mergeSlotProps(_extends({}, rest, {
|
||||
externalSlotProps: resolvedComponentsProps
|
||||
}));
|
||||
const ref = useForkRef(internalRef, resolvedComponentsProps == null ? void 0 : resolvedComponentsProps.ref, (_parameters$additiona = parameters.additionalProps) == null ? void 0 : _parameters$additiona.ref);
|
||||
const props = appendOwnerState(elementType, _extends({}, mergedProps, {
|
||||
ref
|
||||
}), ownerState);
|
||||
return props;
|
||||
}
|
||||
Reference in New Issue
Block a user