neue Homepage auf react basis

This commit is contained in:
2026-08-07 21:39:30 +02:00
parent 164bdd19ad
commit ee89442c04
52191 changed files with 1230242 additions and 58 deletions
+137
View File
@@ -0,0 +1,137 @@
import * as React from 'react';
import { SxProps } from '@mui/system';
import { Theme } from "../styles/index.mjs";
import { TouchRippleActions, TouchRippleProps } from "./TouchRipple.mjs";
import { OverrideProps, OverridableComponent, OverridableTypeMap } from "../OverridableComponent/index.mjs";
import { ButtonBaseClasses } from "./buttonBaseClasses.mjs";
export { TouchRippleActions, TouchRippleProps };
export interface ButtonBaseOwnProps {
/**
* A ref for imperative actions.
* It currently only supports `focusVisible()` action.
*/
action?: React.Ref<ButtonBaseActions> | undefined;
/**
* If `true`, the ripples are centered.
* They won't start at the cursor interaction position.
* @default false
*/
centerRipple?: boolean | undefined;
/**
* The content of the component.
*/
children?: React.ReactNode;
/**
* Override or extend the styles applied to the component.
*/
classes?: Partial<ButtonBaseClasses> | undefined;
/**
* If `true`, the component is disabled.
* @default false
*/
disabled?: boolean | undefined;
/**
* If `true`, the ripple effect is disabled.
*
* ⚠️ Without a ripple there is no styling for :focus-visible by default. Be sure
* to highlight the element by applying separate styles with the `.Mui-focusVisible` class.
* @default false
*/
disableRipple?: boolean | undefined;
/**
* If `true`, the touch ripple effect is disabled.
* @default false
*/
disableTouchRipple?: boolean | undefined;
/**
* If `true`, the base button will have a keyboard focus ripple.
* @default false
*/
focusRipple?: boolean | undefined;
/**
* This prop can help identify which element has keyboard focus.
* The class name will be applied when the element gains the focus through keyboard interaction.
* It's a polyfill for the [CSS :focus-visible selector](https://drafts.csswg.org/selectors-4/#the-focus-visible-pseudo).
* The rationale for using this feature [is explained here](https://github.com/WICG/focus-visible/blob/HEAD/explainer.md).
* A [polyfill can be used](https://github.com/WICG/focus-visible) to apply a `focus-visible` class to other components
* if needed.
*/
focusVisibleClassName?: string | undefined;
/**
* The component used to render a link when the `href` prop is provided.
* @default 'a'
*/
LinkComponent?: React.ElementType | undefined;
/**
* Whether the custom component is expected to render a native `<button>` element
* when passing a React component to the `component` or `slots` prop.
*/
nativeButton?: boolean | undefined;
/**
* Callback fired when the component is focused with a keyboard.
* We trigger a `onFocus` callback too.
*/
onFocusVisible?: React.FocusEventHandler<any> | undefined;
/**
* The system prop that allows defining system overrides as well as additional CSS styles.
*/
sx?: SxProps<Theme> | undefined;
/**
* @default 0
*/
tabIndex?: NonNullable<React.HTMLAttributes<any>['tabIndex']> | undefined;
/**
* Props applied to the `TouchRipple` element.
*/
TouchRippleProps?: Partial<TouchRippleProps> | undefined;
/**
* A ref that points to the `TouchRipple` element.
*/
touchRippleRef?: React.Ref<TouchRippleActions> | undefined;
/**
* The HTML [`type`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/button#type)
* attribute applied to `button` and `a` elements.
* Ignored when rendering non-native buttons.
* @default 'button'
*/
type?: string | undefined;
}
export interface ButtonBaseTypeMap<AdditionalProps = {}, RootComponent extends React.ElementType = 'button'> {
props: AdditionalProps & ButtonBaseOwnProps;
defaultComponent: RootComponent;
}
/**
* utility to create component types that inherit props from ButtonBase.
* This component has an additional overload if the `href` prop is set which
* can make extension quite tricky
*/
export interface ExtendButtonBaseTypeMap<TypeMap extends OverridableTypeMap> {
props: TypeMap['props'] & Omit<ButtonBaseTypeMap['props'], 'classes' | keyof TypeMap['props']>;
defaultComponent: TypeMap['defaultComponent'];
}
export type ExtendButtonBase<TypeMap extends OverridableTypeMap> = ((props: {
href: string;
} & OverrideProps<ExtendButtonBaseTypeMap<TypeMap>, 'a'>) => React.JSX.Element) & OverridableComponent<ExtendButtonBaseTypeMap<TypeMap>>;
/**
* `ButtonBase` contains as few styles as possible.
* It aims to be a simple building block for creating a button.
* It contains a load of style reset and some focus/ripple logic.
*
* Demos:
*
* - [Button](https://mui.com/material-ui/react-button/)
*
* API:
*
* - [ButtonBase API](https://mui.com/material-ui/api/button-base/)
*/
declare const ButtonBase: ExtendButtonBase<ButtonBaseTypeMap>;
export type ButtonBaseProps<RootComponent extends React.ElementType = ButtonBaseTypeMap['defaultComponent'], AdditionalProps = {}> = OverrideProps<ButtonBaseTypeMap<AdditionalProps, RootComponent>, RootComponent> & {
component?: React.ElementType | undefined;
};
export interface ButtonBaseActions {
focusVisible(): void;
}
export default ButtonBase;
+137
View File
@@ -0,0 +1,137 @@
import * as React from 'react';
import { SxProps } from '@mui/system';
import { Theme } from "../styles/index.js";
import { TouchRippleActions, TouchRippleProps } from "./TouchRipple.js";
import { OverrideProps, OverridableComponent, OverridableTypeMap } from "../OverridableComponent/index.js";
import { ButtonBaseClasses } from "./buttonBaseClasses.js";
export { TouchRippleActions, TouchRippleProps };
export interface ButtonBaseOwnProps {
/**
* A ref for imperative actions.
* It currently only supports `focusVisible()` action.
*/
action?: React.Ref<ButtonBaseActions> | undefined;
/**
* If `true`, the ripples are centered.
* They won't start at the cursor interaction position.
* @default false
*/
centerRipple?: boolean | undefined;
/**
* The content of the component.
*/
children?: React.ReactNode;
/**
* Override or extend the styles applied to the component.
*/
classes?: Partial<ButtonBaseClasses> | undefined;
/**
* If `true`, the component is disabled.
* @default false
*/
disabled?: boolean | undefined;
/**
* If `true`, the ripple effect is disabled.
*
* ⚠️ Without a ripple there is no styling for :focus-visible by default. Be sure
* to highlight the element by applying separate styles with the `.Mui-focusVisible` class.
* @default false
*/
disableRipple?: boolean | undefined;
/**
* If `true`, the touch ripple effect is disabled.
* @default false
*/
disableTouchRipple?: boolean | undefined;
/**
* If `true`, the base button will have a keyboard focus ripple.
* @default false
*/
focusRipple?: boolean | undefined;
/**
* This prop can help identify which element has keyboard focus.
* The class name will be applied when the element gains the focus through keyboard interaction.
* It's a polyfill for the [CSS :focus-visible selector](https://drafts.csswg.org/selectors-4/#the-focus-visible-pseudo).
* The rationale for using this feature [is explained here](https://github.com/WICG/focus-visible/blob/HEAD/explainer.md).
* A [polyfill can be used](https://github.com/WICG/focus-visible) to apply a `focus-visible` class to other components
* if needed.
*/
focusVisibleClassName?: string | undefined;
/**
* The component used to render a link when the `href` prop is provided.
* @default 'a'
*/
LinkComponent?: React.ElementType | undefined;
/**
* Whether the custom component is expected to render a native `<button>` element
* when passing a React component to the `component` or `slots` prop.
*/
nativeButton?: boolean | undefined;
/**
* Callback fired when the component is focused with a keyboard.
* We trigger a `onFocus` callback too.
*/
onFocusVisible?: React.FocusEventHandler<any> | undefined;
/**
* The system prop that allows defining system overrides as well as additional CSS styles.
*/
sx?: SxProps<Theme> | undefined;
/**
* @default 0
*/
tabIndex?: NonNullable<React.HTMLAttributes<any>['tabIndex']> | undefined;
/**
* Props applied to the `TouchRipple` element.
*/
TouchRippleProps?: Partial<TouchRippleProps> | undefined;
/**
* A ref that points to the `TouchRipple` element.
*/
touchRippleRef?: React.Ref<TouchRippleActions> | undefined;
/**
* The HTML [`type`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/button#type)
* attribute applied to `button` and `a` elements.
* Ignored when rendering non-native buttons.
* @default 'button'
*/
type?: string | undefined;
}
export interface ButtonBaseTypeMap<AdditionalProps = {}, RootComponent extends React.ElementType = 'button'> {
props: AdditionalProps & ButtonBaseOwnProps;
defaultComponent: RootComponent;
}
/**
* utility to create component types that inherit props from ButtonBase.
* This component has an additional overload if the `href` prop is set which
* can make extension quite tricky
*/
export interface ExtendButtonBaseTypeMap<TypeMap extends OverridableTypeMap> {
props: TypeMap['props'] & Omit<ButtonBaseTypeMap['props'], 'classes' | keyof TypeMap['props']>;
defaultComponent: TypeMap['defaultComponent'];
}
export type ExtendButtonBase<TypeMap extends OverridableTypeMap> = ((props: {
href: string;
} & OverrideProps<ExtendButtonBaseTypeMap<TypeMap>, 'a'>) => React.JSX.Element) & OverridableComponent<ExtendButtonBaseTypeMap<TypeMap>>;
/**
* `ButtonBase` contains as few styles as possible.
* It aims to be a simple building block for creating a button.
* It contains a load of style reset and some focus/ripple logic.
*
* Demos:
*
* - [Button](https://mui.com/material-ui/react-button/)
*
* API:
*
* - [ButtonBase API](https://mui.com/material-ui/api/button-base/)
*/
declare const ButtonBase: ExtendButtonBase<ButtonBaseTypeMap>;
export type ButtonBaseProps<RootComponent extends React.ElementType = ButtonBaseTypeMap['defaultComponent'], AdditionalProps = {}> = OverrideProps<ButtonBaseTypeMap<AdditionalProps, RootComponent>, RootComponent> & {
component?: React.ElementType | undefined;
};
export interface ButtonBaseActions {
focusVisible(): void;
}
export default ButtonBase;
+474
View File
@@ -0,0 +1,474 @@
"use strict";
'use client';
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = exports.ButtonBaseRoot = void 0;
var React = _interopRequireWildcard(require("react"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var _clsx = _interopRequireDefault(require("clsx"));
var _refType = _interopRequireDefault(require("@mui/utils/refType"));
var _elementTypeAcceptingRef = _interopRequireDefault(require("@mui/utils/elementTypeAcceptingRef"));
var _composeClasses = _interopRequireDefault(require("@mui/utils/composeClasses"));
var _isFocusVisible = _interopRequireDefault(require("@mui/utils/isFocusVisible"));
var _zeroStyled = require("../zero-styled");
var _DefaultPropsProvider = require("../DefaultPropsProvider");
var _useForkRef = _interopRequireDefault(require("../utils/useForkRef"));
var _useEventCallback = _interopRequireDefault(require("../utils/useEventCallback"));
var _useButtonBase = _interopRequireDefault(require("./useButtonBase"));
var _useLazyRipple = _interopRequireDefault(require("../useLazyRipple"));
var _TouchRipple = _interopRequireDefault(require("./TouchRipple"));
var _buttonBaseClasses = _interopRequireWildcard(require("./buttonBaseClasses"));
var _jsxRuntime = require("react/jsx-runtime");
const useUtilityClasses = ownerState => {
const {
disabled,
focusVisible,
focusVisibleClassName,
suppressFocusVisible,
classes
} = ownerState;
const slots = {
root: ['root', disabled && 'disabled', focusVisible && !suppressFocusVisible && 'focusVisible']
};
const composedClasses = (0, _composeClasses.default)(slots, _buttonBaseClasses.getButtonBaseUtilityClass, classes);
if (focusVisible && !suppressFocusVisible && focusVisibleClassName) {
composedClasses.root += ` ${focusVisibleClassName}`;
}
return composedClasses;
};
const ButtonBaseRoot = exports.ButtonBaseRoot = (0, _zeroStyled.styled)('button', {
name: 'MuiButtonBase',
slot: 'Root'
})({
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
position: 'relative',
boxSizing: 'border-box',
WebkitTapHighlightColor: 'transparent',
backgroundColor: 'transparent',
// Reset default value
// We disable the focus ring for mouse, touch and keyboard users.
outline: 0,
border: 0,
margin: 0,
// Remove the margin in Safari
borderRadius: 0,
padding: 0,
// Remove the padding in Firefox
cursor: 'pointer',
userSelect: 'none',
verticalAlign: 'middle',
MozAppearance: 'none',
// Reset
WebkitAppearance: 'none',
// Reset
textDecoration: 'none',
// So we take precedent over the style of a native <a /> element.
color: 'inherit',
'&::-moz-focus-inner': {
borderStyle: 'none' // Remove Firefox dotted outline.
},
[`&.${_buttonBaseClasses.default.disabled}`]: {
pointerEvents: 'none',
// Disable link interactions
cursor: 'default'
},
'@media print': {
colorAdjust: 'exact'
}
});
/**
* `ButtonBase` contains as few styles as possible.
* It aims to be a simple building block for creating a button.
* It contains a load of style reset and some focus/ripple logic.
*/
const ButtonBase = /*#__PURE__*/React.forwardRef(function ButtonBase(inProps, ref) {
const props = (0, _DefaultPropsProvider.useDefaultProps)({
props: inProps,
name: 'MuiButtonBase'
});
const {
action,
centerRipple = false,
children,
className,
component = 'button',
disabled = false,
disableRipple = false,
disableTouchRipple = false,
focusRipple = false,
focusVisibleClassName,
/* eslint-disable react/prop-types */
// replaces internal handling in Chip, other components can opt-in individually to use this in the future
focusableWhenDisabled,
// escape hatch to suppress the focusVisible state and callback
// used by anchored <Menu>s to to suppress focus visible styling when opened with a pointer
suppressFocusVisible = false,
// private prop to allow native vs non-native button props to be resolved before mount
internalNativeButton: internalNativeButtonProp,
/* eslint-enable react/prop-types */
LinkComponent = 'a',
nativeButton: nativeButtonProp,
onBlur,
onClick: onClickProp,
onContextMenu,
onDragLeave,
onFocus,
onFocusVisible,
onKeyDown: onKeyDownProp,
onKeyUp: onKeyUpProp,
onMouseDown,
onMouseLeave,
onMouseUp,
onTouchEnd,
onTouchMove,
onTouchStart,
tabIndex = 0,
TouchRippleProps,
touchRippleRef,
type,
...other
} = props;
const isLink = Boolean(other.href || other.to);
const hasFormAction = Boolean(other.formAction);
let ComponentProp = component;
if (ComponentProp === 'button' && isLink) {
ComponentProp = LinkComponent;
}
const internalNativeButton = typeof ComponentProp === 'string' ? ComponentProp === 'button' : internalNativeButtonProp ?? false;
const nativeButton = nativeButtonProp ?? internalNativeButton;
const ripple = (0, _useLazyRipple.default)();
const handleRippleRef = (0, _useForkRef.default)(ripple.ref, touchRippleRef);
const [focusVisible, setFocusVisible] = React.useState(false);
if ((disabled || suppressFocusVisible) && focusVisible) {
setFocusVisible(false);
}
const handleBeforeKeyDown = (0, _useEventCallback.default)(event => {
// Check if key is already down to avoid repeats being counted as multiple activations
if (focusRipple && !event.repeat && focusVisible && event.key === ' ') {
ripple.stop(event, () => {
ripple.start(event);
});
}
});
const handleBeforeKeyUp = (0, _useEventCallback.default)(event => {
// calling preventDefault in keyUp on a <button> will not dispatch a click event if Space is pressed
// https://codesandbox.io/p/sandbox/button-keyup-preventdefault-dn7f0
if (focusRipple && event.key === ' ' && focusVisible && !event.defaultPrevented) {
ripple.stop(event, () => {
ripple.pulsate(event);
});
}
});
const {
getButtonProps,
rootRef: buttonRef
} = (0, _useButtonBase.default)({
nativeButton,
nativeButtonProp,
internalNativeButton,
allowInferredHostMismatch: isLink || typeof ComponentProp === 'string',
disabled,
type,
hasFormAction,
tabIndex,
onBeforeKeyDown: handleBeforeKeyDown,
onBeforeKeyUp: handleBeforeKeyUp
});
const {
onClick,
onKeyDown,
onKeyUp,
...buttonProps
} = getButtonProps({
onClick: onClickProp,
onKeyDown: onKeyDownProp,
onKeyUp: onKeyUpProp
});
React.useImperativeHandle(action, () => ({
focusVisible: () => {
setFocusVisible(true);
buttonRef.current.focus();
}
}), [buttonRef]);
const enableTouchRipple = ripple.shouldMount && !disableRipple && !disabled;
React.useEffect(() => {
if (focusVisible && focusRipple && !disableRipple) {
ripple.pulsate();
}
}, [disableRipple, focusRipple, focusVisible, ripple]);
const handleMouseDown = useRippleHandler(ripple, 'start', onMouseDown, disableTouchRipple);
const handleContextMenu = useRippleHandler(ripple, 'stop', onContextMenu, disableTouchRipple);
const handleDragLeave = useRippleHandler(ripple, 'stop', onDragLeave, disableTouchRipple);
const handleMouseUp = useRippleHandler(ripple, 'stop', onMouseUp, disableTouchRipple);
const handleMouseLeave = useRippleHandler(ripple, 'stop', event => {
if (focusVisible) {
event.preventDefault();
}
if (onMouseLeave) {
onMouseLeave(event);
}
}, disableTouchRipple);
const handleTouchStart = useRippleHandler(ripple, 'start', onTouchStart, disableTouchRipple);
const handleTouchEnd = useRippleHandler(ripple, 'stop', onTouchEnd, disableTouchRipple);
const handleTouchMove = useRippleHandler(ripple, 'stop', onTouchMove, disableTouchRipple);
const handleBlur = useRippleHandler(ripple, 'stop', event => {
if (!(0, _isFocusVisible.default)(event.target)) {
setFocusVisible(false);
}
if (onBlur) {
onBlur(event);
}
}, false);
const handleFocus = (0, _useEventCallback.default)(event => {
// Fix for https://github.com/react/react/issues/7769
if (!buttonRef.current) {
buttonRef.current = event.currentTarget;
}
if (!suppressFocusVisible && (0, _isFocusVisible.default)(event.target)) {
setFocusVisible(true);
if (onFocusVisible) {
onFocusVisible(event);
}
}
if (onFocus) {
onFocus(event);
}
});
const linkProps = {};
if (isLink) {
linkProps.tabIndex = disabled ? -1 : tabIndex;
if (disabled) {
linkProps['aria-disabled'] = disabled;
}
linkProps.type = type;
}
const handleRef = (0, _useForkRef.default)(ref, buttonRef);
const ownerState = {
...props,
centerRipple,
component,
disabled,
disableRipple,
disableTouchRipple,
focusRipple,
suppressFocusVisible,
tabIndex,
focusVisible
};
const classes = useUtilityClasses(ownerState);
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(ButtonBaseRoot, {
as: ComponentProp,
className: (0, _clsx.default)(classes.root, className),
ownerState: ownerState,
onBlur: handleBlur,
onClick: onClick,
onContextMenu: handleContextMenu,
onFocus: handleFocus,
onKeyDown: onKeyDown,
onKeyUp: onKeyUp,
onMouseDown: handleMouseDown,
onMouseLeave: handleMouseLeave,
onMouseUp: handleMouseUp,
onDragLeave: handleDragLeave,
onTouchEnd: handleTouchEnd,
onTouchMove: handleTouchMove,
onTouchStart: handleTouchStart,
ref: handleRef,
...(isLink ? linkProps : buttonProps),
...other,
children: [children, enableTouchRipple ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_TouchRipple.default, {
ref: handleRippleRef,
center: centerRipple,
...TouchRippleProps
}) : null]
});
});
function useRippleHandler(ripple, rippleAction, eventCallback, skipRippleAction = false) {
return (0, _useEventCallback.default)(event => {
if (eventCallback) {
eventCallback(event);
}
if (!skipRippleAction) {
ripple[rippleAction](event);
}
return true;
});
}
process.env.NODE_ENV !== "production" ? ButtonBase.propTypes /* remove-proptypes */ = {
// ┌────────────────────────────── Warning ──────────────────────────────┐
// │ These PropTypes are generated from the TypeScript type definitions. │
// │ To update them, edit the d.ts file and run `pnpm proptypes`. │
// └─────────────────────────────────────────────────────────────────────┘
/**
* A ref for imperative actions.
* It currently only supports `focusVisible()` action.
*/
action: _refType.default,
/**
* If `true`, the ripples are centered.
* They won't start at the cursor interaction position.
* @default false
*/
centerRipple: _propTypes.default.bool,
/**
* The content of the component.
*/
children: _propTypes.default.node,
/**
* Override or extend the styles applied to the component.
*/
classes: _propTypes.default.object,
/**
* @ignore
*/
className: _propTypes.default.string,
/**
* The component used for the root node.
* Either a string to use a HTML element or a component.
*/
component: _elementTypeAcceptingRef.default,
/**
* If `true`, the component is disabled.
* @default false
*/
disabled: _propTypes.default.bool,
/**
* If `true`, the ripple effect is disabled.
*
* ⚠️ Without a ripple there is no styling for :focus-visible by default. Be sure
* to highlight the element by applying separate styles with the `.Mui-focusVisible` class.
* @default false
*/
disableRipple: _propTypes.default.bool,
/**
* If `true`, the touch ripple effect is disabled.
* @default false
*/
disableTouchRipple: _propTypes.default.bool,
/**
* If `true`, the base button will have a keyboard focus ripple.
* @default false
*/
focusRipple: _propTypes.default.bool,
/**
* This prop can help identify which element has keyboard focus.
* The class name will be applied when the element gains the focus through keyboard interaction.
* It's a polyfill for the [CSS :focus-visible selector](https://drafts.csswg.org/selectors-4/#the-focus-visible-pseudo).
* The rationale for using this feature [is explained here](https://github.com/WICG/focus-visible/blob/HEAD/explainer.md).
* A [polyfill can be used](https://github.com/WICG/focus-visible) to apply a `focus-visible` class to other components
* if needed.
*/
focusVisibleClassName: _propTypes.default.string,
/**
* @ignore
*/
formAction: _propTypes.default.oneOfType([_propTypes.default.func, _propTypes.default.string]),
/**
* @ignore
*/
href: _propTypes.default /* @typescript-to-proptypes-ignore */.any,
/**
* The component used to render a link when the `href` prop is provided.
* @default 'a'
*/
LinkComponent: _propTypes.default.elementType,
/**
* Whether the custom component is expected to render a native `<button>` element
* when passing a React component to the `component` or `slots` prop.
*/
nativeButton: _propTypes.default.bool,
/**
* @ignore
*/
onBlur: _propTypes.default.func,
/**
* @ignore
*/
onClick: _propTypes.default.func,
/**
* @ignore
*/
onContextMenu: _propTypes.default.func,
/**
* @ignore
*/
onDragLeave: _propTypes.default.func,
/**
* @ignore
*/
onFocus: _propTypes.default.func,
/**
* Callback fired when the component is focused with a keyboard.
* We trigger a `onFocus` callback too.
*/
onFocusVisible: _propTypes.default.func,
/**
* @ignore
*/
onKeyDown: _propTypes.default.func,
/**
* @ignore
*/
onKeyUp: _propTypes.default.func,
/**
* @ignore
*/
onMouseDown: _propTypes.default.func,
/**
* @ignore
*/
onMouseLeave: _propTypes.default.func,
/**
* @ignore
*/
onMouseUp: _propTypes.default.func,
/**
* @ignore
*/
onTouchEnd: _propTypes.default.func,
/**
* @ignore
*/
onTouchMove: _propTypes.default.func,
/**
* @ignore
*/
onTouchStart: _propTypes.default.func,
/**
* The system prop that allows defining system overrides as well as additional CSS styles.
*/
sx: _propTypes.default.oneOfType([_propTypes.default.arrayOf(_propTypes.default.oneOfType([_propTypes.default.func, _propTypes.default.object, _propTypes.default.bool])), _propTypes.default.func, _propTypes.default.object]),
/**
* @default 0
*/
tabIndex: _propTypes.default.number,
/**
* Props applied to the `TouchRipple` element.
*/
TouchRippleProps: _propTypes.default.object,
/**
* A ref that points to the `TouchRipple` element.
*/
touchRippleRef: _propTypes.default.oneOfType([_propTypes.default.func, _propTypes.default.shape({
current: _propTypes.default.shape({
pulsate: _propTypes.default.func.isRequired,
start: _propTypes.default.func.isRequired,
stop: _propTypes.default.func.isRequired
})
})]),
/**
* The HTML [`type`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/button#type)
* attribute applied to `button` and `a` elements.
* Ignored when rendering non-native buttons.
* @default 'button'
*/
type: _propTypes.default.string
} : void 0;
var _default = exports.default = ButtonBase;
+467
View File
@@ -0,0 +1,467 @@
'use client';
import * as React from 'react';
import PropTypes from 'prop-types';
import clsx from 'clsx';
import refType from '@mui/utils/refType';
import elementTypeAcceptingRef from '@mui/utils/elementTypeAcceptingRef';
import composeClasses from '@mui/utils/composeClasses';
import isFocusVisible from '@mui/utils/isFocusVisible';
import { styled } from "../zero-styled/index.mjs";
import { useDefaultProps } from "../DefaultPropsProvider/index.mjs";
import useForkRef from "../utils/useForkRef.mjs";
import useEventCallback from "../utils/useEventCallback.mjs";
import useButtonBase from "./useButtonBase.mjs";
import useLazyRipple from "../useLazyRipple/index.mjs";
import TouchRipple from "./TouchRipple.mjs";
import buttonBaseClasses, { getButtonBaseUtilityClass } from "./buttonBaseClasses.mjs";
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
const useUtilityClasses = ownerState => {
const {
disabled,
focusVisible,
focusVisibleClassName,
suppressFocusVisible,
classes
} = ownerState;
const slots = {
root: ['root', disabled && 'disabled', focusVisible && !suppressFocusVisible && 'focusVisible']
};
const composedClasses = composeClasses(slots, getButtonBaseUtilityClass, classes);
if (focusVisible && !suppressFocusVisible && focusVisibleClassName) {
composedClasses.root += ` ${focusVisibleClassName}`;
}
return composedClasses;
};
export const ButtonBaseRoot = styled('button', {
name: 'MuiButtonBase',
slot: 'Root'
})({
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
position: 'relative',
boxSizing: 'border-box',
WebkitTapHighlightColor: 'transparent',
backgroundColor: 'transparent',
// Reset default value
// We disable the focus ring for mouse, touch and keyboard users.
outline: 0,
border: 0,
margin: 0,
// Remove the margin in Safari
borderRadius: 0,
padding: 0,
// Remove the padding in Firefox
cursor: 'pointer',
userSelect: 'none',
verticalAlign: 'middle',
MozAppearance: 'none',
// Reset
WebkitAppearance: 'none',
// Reset
textDecoration: 'none',
// So we take precedent over the style of a native <a /> element.
color: 'inherit',
'&::-moz-focus-inner': {
borderStyle: 'none' // Remove Firefox dotted outline.
},
[`&.${buttonBaseClasses.disabled}`]: {
pointerEvents: 'none',
// Disable link interactions
cursor: 'default'
},
'@media print': {
colorAdjust: 'exact'
}
});
/**
* `ButtonBase` contains as few styles as possible.
* It aims to be a simple building block for creating a button.
* It contains a load of style reset and some focus/ripple logic.
*/
const ButtonBase = /*#__PURE__*/React.forwardRef(function ButtonBase(inProps, ref) {
const props = useDefaultProps({
props: inProps,
name: 'MuiButtonBase'
});
const {
action,
centerRipple = false,
children,
className,
component = 'button',
disabled = false,
disableRipple = false,
disableTouchRipple = false,
focusRipple = false,
focusVisibleClassName,
/* eslint-disable react/prop-types */
// replaces internal handling in Chip, other components can opt-in individually to use this in the future
focusableWhenDisabled,
// escape hatch to suppress the focusVisible state and callback
// used by anchored <Menu>s to to suppress focus visible styling when opened with a pointer
suppressFocusVisible = false,
// private prop to allow native vs non-native button props to be resolved before mount
internalNativeButton: internalNativeButtonProp,
/* eslint-enable react/prop-types */
LinkComponent = 'a',
nativeButton: nativeButtonProp,
onBlur,
onClick: onClickProp,
onContextMenu,
onDragLeave,
onFocus,
onFocusVisible,
onKeyDown: onKeyDownProp,
onKeyUp: onKeyUpProp,
onMouseDown,
onMouseLeave,
onMouseUp,
onTouchEnd,
onTouchMove,
onTouchStart,
tabIndex = 0,
TouchRippleProps,
touchRippleRef,
type,
...other
} = props;
const isLink = Boolean(other.href || other.to);
const hasFormAction = Boolean(other.formAction);
let ComponentProp = component;
if (ComponentProp === 'button' && isLink) {
ComponentProp = LinkComponent;
}
const internalNativeButton = typeof ComponentProp === 'string' ? ComponentProp === 'button' : internalNativeButtonProp ?? false;
const nativeButton = nativeButtonProp ?? internalNativeButton;
const ripple = useLazyRipple();
const handleRippleRef = useForkRef(ripple.ref, touchRippleRef);
const [focusVisible, setFocusVisible] = React.useState(false);
if ((disabled || suppressFocusVisible) && focusVisible) {
setFocusVisible(false);
}
const handleBeforeKeyDown = useEventCallback(event => {
// Check if key is already down to avoid repeats being counted as multiple activations
if (focusRipple && !event.repeat && focusVisible && event.key === ' ') {
ripple.stop(event, () => {
ripple.start(event);
});
}
});
const handleBeforeKeyUp = useEventCallback(event => {
// calling preventDefault in keyUp on a <button> will not dispatch a click event if Space is pressed
// https://codesandbox.io/p/sandbox/button-keyup-preventdefault-dn7f0
if (focusRipple && event.key === ' ' && focusVisible && !event.defaultPrevented) {
ripple.stop(event, () => {
ripple.pulsate(event);
});
}
});
const {
getButtonProps,
rootRef: buttonRef
} = useButtonBase({
nativeButton,
nativeButtonProp,
internalNativeButton,
allowInferredHostMismatch: isLink || typeof ComponentProp === 'string',
disabled,
type,
hasFormAction,
tabIndex,
onBeforeKeyDown: handleBeforeKeyDown,
onBeforeKeyUp: handleBeforeKeyUp
});
const {
onClick,
onKeyDown,
onKeyUp,
...buttonProps
} = getButtonProps({
onClick: onClickProp,
onKeyDown: onKeyDownProp,
onKeyUp: onKeyUpProp
});
React.useImperativeHandle(action, () => ({
focusVisible: () => {
setFocusVisible(true);
buttonRef.current.focus();
}
}), [buttonRef]);
const enableTouchRipple = ripple.shouldMount && !disableRipple && !disabled;
React.useEffect(() => {
if (focusVisible && focusRipple && !disableRipple) {
ripple.pulsate();
}
}, [disableRipple, focusRipple, focusVisible, ripple]);
const handleMouseDown = useRippleHandler(ripple, 'start', onMouseDown, disableTouchRipple);
const handleContextMenu = useRippleHandler(ripple, 'stop', onContextMenu, disableTouchRipple);
const handleDragLeave = useRippleHandler(ripple, 'stop', onDragLeave, disableTouchRipple);
const handleMouseUp = useRippleHandler(ripple, 'stop', onMouseUp, disableTouchRipple);
const handleMouseLeave = useRippleHandler(ripple, 'stop', event => {
if (focusVisible) {
event.preventDefault();
}
if (onMouseLeave) {
onMouseLeave(event);
}
}, disableTouchRipple);
const handleTouchStart = useRippleHandler(ripple, 'start', onTouchStart, disableTouchRipple);
const handleTouchEnd = useRippleHandler(ripple, 'stop', onTouchEnd, disableTouchRipple);
const handleTouchMove = useRippleHandler(ripple, 'stop', onTouchMove, disableTouchRipple);
const handleBlur = useRippleHandler(ripple, 'stop', event => {
if (!isFocusVisible(event.target)) {
setFocusVisible(false);
}
if (onBlur) {
onBlur(event);
}
}, false);
const handleFocus = useEventCallback(event => {
// Fix for https://github.com/react/react/issues/7769
if (!buttonRef.current) {
buttonRef.current = event.currentTarget;
}
if (!suppressFocusVisible && isFocusVisible(event.target)) {
setFocusVisible(true);
if (onFocusVisible) {
onFocusVisible(event);
}
}
if (onFocus) {
onFocus(event);
}
});
const linkProps = {};
if (isLink) {
linkProps.tabIndex = disabled ? -1 : tabIndex;
if (disabled) {
linkProps['aria-disabled'] = disabled;
}
linkProps.type = type;
}
const handleRef = useForkRef(ref, buttonRef);
const ownerState = {
...props,
centerRipple,
component,
disabled,
disableRipple,
disableTouchRipple,
focusRipple,
suppressFocusVisible,
tabIndex,
focusVisible
};
const classes = useUtilityClasses(ownerState);
return /*#__PURE__*/_jsxs(ButtonBaseRoot, {
as: ComponentProp,
className: clsx(classes.root, className),
ownerState: ownerState,
onBlur: handleBlur,
onClick: onClick,
onContextMenu: handleContextMenu,
onFocus: handleFocus,
onKeyDown: onKeyDown,
onKeyUp: onKeyUp,
onMouseDown: handleMouseDown,
onMouseLeave: handleMouseLeave,
onMouseUp: handleMouseUp,
onDragLeave: handleDragLeave,
onTouchEnd: handleTouchEnd,
onTouchMove: handleTouchMove,
onTouchStart: handleTouchStart,
ref: handleRef,
...(isLink ? linkProps : buttonProps),
...other,
children: [children, enableTouchRipple ? /*#__PURE__*/_jsx(TouchRipple, {
ref: handleRippleRef,
center: centerRipple,
...TouchRippleProps
}) : null]
});
});
function useRippleHandler(ripple, rippleAction, eventCallback, skipRippleAction = false) {
return useEventCallback(event => {
if (eventCallback) {
eventCallback(event);
}
if (!skipRippleAction) {
ripple[rippleAction](event);
}
return true;
});
}
process.env.NODE_ENV !== "production" ? ButtonBase.propTypes /* remove-proptypes */ = {
// ┌────────────────────────────── Warning ──────────────────────────────┐
// │ These PropTypes are generated from the TypeScript type definitions. │
// │ To update them, edit the d.ts file and run `pnpm proptypes`. │
// └─────────────────────────────────────────────────────────────────────┘
/**
* A ref for imperative actions.
* It currently only supports `focusVisible()` action.
*/
action: refType,
/**
* If `true`, the ripples are centered.
* They won't start at the cursor interaction position.
* @default false
*/
centerRipple: PropTypes.bool,
/**
* The content of the component.
*/
children: PropTypes.node,
/**
* Override or extend the styles applied to the component.
*/
classes: PropTypes.object,
/**
* @ignore
*/
className: PropTypes.string,
/**
* The component used for the root node.
* Either a string to use a HTML element or a component.
*/
component: elementTypeAcceptingRef,
/**
* If `true`, the component is disabled.
* @default false
*/
disabled: PropTypes.bool,
/**
* If `true`, the ripple effect is disabled.
*
* ⚠️ Without a ripple there is no styling for :focus-visible by default. Be sure
* to highlight the element by applying separate styles with the `.Mui-focusVisible` class.
* @default false
*/
disableRipple: PropTypes.bool,
/**
* If `true`, the touch ripple effect is disabled.
* @default false
*/
disableTouchRipple: PropTypes.bool,
/**
* If `true`, the base button will have a keyboard focus ripple.
* @default false
*/
focusRipple: PropTypes.bool,
/**
* This prop can help identify which element has keyboard focus.
* The class name will be applied when the element gains the focus through keyboard interaction.
* It's a polyfill for the [CSS :focus-visible selector](https://drafts.csswg.org/selectors-4/#the-focus-visible-pseudo).
* The rationale for using this feature [is explained here](https://github.com/WICG/focus-visible/blob/HEAD/explainer.md).
* A [polyfill can be used](https://github.com/WICG/focus-visible) to apply a `focus-visible` class to other components
* if needed.
*/
focusVisibleClassName: PropTypes.string,
/**
* @ignore
*/
formAction: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
/**
* @ignore
*/
href: PropTypes /* @typescript-to-proptypes-ignore */.any,
/**
* The component used to render a link when the `href` prop is provided.
* @default 'a'
*/
LinkComponent: PropTypes.elementType,
/**
* Whether the custom component is expected to render a native `<button>` element
* when passing a React component to the `component` or `slots` prop.
*/
nativeButton: PropTypes.bool,
/**
* @ignore
*/
onBlur: PropTypes.func,
/**
* @ignore
*/
onClick: PropTypes.func,
/**
* @ignore
*/
onContextMenu: PropTypes.func,
/**
* @ignore
*/
onDragLeave: PropTypes.func,
/**
* @ignore
*/
onFocus: PropTypes.func,
/**
* Callback fired when the component is focused with a keyboard.
* We trigger a `onFocus` callback too.
*/
onFocusVisible: PropTypes.func,
/**
* @ignore
*/
onKeyDown: PropTypes.func,
/**
* @ignore
*/
onKeyUp: PropTypes.func,
/**
* @ignore
*/
onMouseDown: PropTypes.func,
/**
* @ignore
*/
onMouseLeave: PropTypes.func,
/**
* @ignore
*/
onMouseUp: PropTypes.func,
/**
* @ignore
*/
onTouchEnd: PropTypes.func,
/**
* @ignore
*/
onTouchMove: PropTypes.func,
/**
* @ignore
*/
onTouchStart: PropTypes.func,
/**
* The system prop that allows defining system overrides as well as additional CSS styles.
*/
sx: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), PropTypes.func, PropTypes.object]),
/**
* @default 0
*/
tabIndex: PropTypes.number,
/**
* Props applied to the `TouchRipple` element.
*/
TouchRippleProps: PropTypes.object,
/**
* A ref that points to the `TouchRipple` element.
*/
touchRippleRef: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({
current: PropTypes.shape({
pulsate: PropTypes.func.isRequired,
start: PropTypes.func.isRequired,
stop: PropTypes.func.isRequired
})
})]),
/**
* The HTML [`type`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/button#type)
* attribute applied to `button` and `a` elements.
* Ignored when rendering non-native buttons.
* @default 'button'
*/
type: PropTypes.string
} : void 0;
export default ButtonBase;
+103
View File
@@ -0,0 +1,103 @@
"use strict";
'use client';
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var React = _interopRequireWildcard(require("react"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var _clsx = _interopRequireDefault(require("clsx"));
var _useTimeout = _interopRequireDefault(require("@mui/utils/useTimeout"));
var _jsxRuntime = require("react/jsx-runtime");
/**
* @ignore - internal component.
*/function Ripple(props) {
const {
className,
classes,
pulsate = false,
rippleX,
rippleY,
rippleSize,
in: inProp,
onExited,
timeout
} = props;
const [leaving, setLeaving] = React.useState(false);
const exitTimer = (0, _useTimeout.default)();
const exitTimerStartedRef = React.useRef(false);
const onExitedRef = React.useRef(onExited);
onExitedRef.current = onExited;
const hasExitedCallback = onExited != null;
const rippleClassName = (0, _clsx.default)(className, classes.ripple, classes.rippleVisible, pulsate && classes.ripplePulsate);
const rippleStyles = {
width: rippleSize,
height: rippleSize,
top: -(rippleSize / 2) + rippleY,
left: -(rippleSize / 2) + rippleX
};
const childClassName = (0, _clsx.default)(classes.child, leaving && classes.childLeaving, pulsate && classes.childPulsate);
if (!inProp && !leaving) {
setLeaving(true);
}
React.useEffect(() => {
if (!inProp && hasExitedCallback) {
if (!exitTimerStartedRef.current) {
exitTimerStartedRef.current = true;
exitTimer.start(timeout, () => {
exitTimerStartedRef.current = false;
onExitedRef.current?.();
});
}
} else {
exitTimerStartedRef.current = false;
exitTimer.clear();
}
}, [exitTimer, hasExitedCallback, inProp, timeout]);
return /*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
className: rippleClassName,
style: rippleStyles,
children: /*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
className: childClassName
})
});
}
process.env.NODE_ENV !== "production" ? Ripple.propTypes /* remove-proptypes */ = {
/**
* Override or extend the styles applied to the component.
*/
classes: _propTypes.default.object.isRequired,
className: _propTypes.default.string,
/**
* @ignore - controlled by TouchRipple
*/
in: _propTypes.default.bool,
/**
* @ignore - controlled by TouchRipple
*/
onExited: _propTypes.default.func,
/**
* If `true`, the ripple pulsates, typically indicating the keyboard focus state of an element.
*/
pulsate: _propTypes.default.bool,
/**
* Diameter of the ripple.
*/
rippleSize: _propTypes.default.number,
/**
* Horizontal position of the ripple center.
*/
rippleX: _propTypes.default.number,
/**
* Vertical position of the ripple center.
*/
rippleY: _propTypes.default.number,
/**
* Exit delay.
*/
timeout: _propTypes.default.number.isRequired
} : void 0;
var _default = exports.default = Ripple;
+98
View File
@@ -0,0 +1,98 @@
'use client';
import * as React from 'react';
import PropTypes from 'prop-types';
import clsx from 'clsx';
import useTimeout from '@mui/utils/useTimeout';
/**
* @ignore - internal component.
*/
import { jsx as _jsx } from "react/jsx-runtime";
function Ripple(props) {
const {
className,
classes,
pulsate = false,
rippleX,
rippleY,
rippleSize,
in: inProp,
onExited,
timeout
} = props;
const [leaving, setLeaving] = React.useState(false);
const exitTimer = useTimeout();
const exitTimerStartedRef = React.useRef(false);
const onExitedRef = React.useRef(onExited);
onExitedRef.current = onExited;
const hasExitedCallback = onExited != null;
const rippleClassName = clsx(className, classes.ripple, classes.rippleVisible, pulsate && classes.ripplePulsate);
const rippleStyles = {
width: rippleSize,
height: rippleSize,
top: -(rippleSize / 2) + rippleY,
left: -(rippleSize / 2) + rippleX
};
const childClassName = clsx(classes.child, leaving && classes.childLeaving, pulsate && classes.childPulsate);
if (!inProp && !leaving) {
setLeaving(true);
}
React.useEffect(() => {
if (!inProp && hasExitedCallback) {
if (!exitTimerStartedRef.current) {
exitTimerStartedRef.current = true;
exitTimer.start(timeout, () => {
exitTimerStartedRef.current = false;
onExitedRef.current?.();
});
}
} else {
exitTimerStartedRef.current = false;
exitTimer.clear();
}
}, [exitTimer, hasExitedCallback, inProp, timeout]);
return /*#__PURE__*/_jsx("span", {
className: rippleClassName,
style: rippleStyles,
children: /*#__PURE__*/_jsx("span", {
className: childClassName
})
});
}
process.env.NODE_ENV !== "production" ? Ripple.propTypes /* remove-proptypes */ = {
/**
* Override or extend the styles applied to the component.
*/
classes: PropTypes.object.isRequired,
className: PropTypes.string,
/**
* @ignore - controlled by TouchRipple
*/
in: PropTypes.bool,
/**
* @ignore - controlled by TouchRipple
*/
onExited: PropTypes.func,
/**
* If `true`, the ripple pulsates, typically indicating the keyboard focus state of an element.
*/
pulsate: PropTypes.bool,
/**
* Diameter of the ripple.
*/
rippleSize: PropTypes.number,
/**
* Horizontal position of the ripple center.
*/
rippleX: PropTypes.number,
/**
* Vertical position of the ripple center.
*/
rippleY: PropTypes.number,
/**
* Exit delay.
*/
timeout: PropTypes.number.isRequired
} : void 0;
export default Ripple;
+22
View File
@@ -0,0 +1,22 @@
import * as React from 'react';
import { InternalStandardProps as StandardProps } from "../internal/index.mjs";
import { TouchRippleClasses, TouchRippleClassKey } from "./touchRippleClasses.mjs";
export { TouchRippleClassKey };
export interface StartActionOptions {
pulsate?: boolean | undefined;
center?: boolean | undefined;
}
export interface TouchRippleActions {
start: (event?: React.SyntheticEvent, options?: StartActionOptions, callback?: () => void) => void;
pulsate: (event?: React.SyntheticEvent) => void;
stop: (event?: React.SyntheticEvent, callback?: () => void) => void;
}
export type TouchRippleProps = StandardProps<React.HTMLAttributes<HTMLElement>> & {
center?: boolean | undefined;
/**
* Override or extend the styles applied to the component.
*/
classes?: Partial<TouchRippleClasses> | undefined;
};
declare const TouchRipple: React.ForwardRefRenderFunction<TouchRippleActions, TouchRippleProps>;
export default TouchRipple;
+22
View File
@@ -0,0 +1,22 @@
import * as React from 'react';
import { InternalStandardProps as StandardProps } from "../internal/index.js";
import { TouchRippleClasses, TouchRippleClassKey } from "./touchRippleClasses.js";
export { TouchRippleClassKey };
export interface StartActionOptions {
pulsate?: boolean | undefined;
center?: boolean | undefined;
}
export interface TouchRippleActions {
start: (event?: React.SyntheticEvent, options?: StartActionOptions, callback?: () => void) => void;
pulsate: (event?: React.SyntheticEvent) => void;
stop: (event?: React.SyntheticEvent, callback?: () => void) => void;
}
export type TouchRippleProps = StandardProps<React.HTMLAttributes<HTMLElement>> & {
center?: boolean | undefined;
/**
* Override or extend the styles applied to the component.
*/
classes?: Partial<TouchRippleClasses> | undefined;
};
declare const TouchRipple: React.ForwardRefRenderFunction<TouchRippleActions, TouchRippleProps>;
export default TouchRipple;
+475
View File
@@ -0,0 +1,475 @@
"use strict";
'use client';
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = exports.TouchRippleRoot = exports.TouchRippleRipple = exports.DELAY_RIPPLE = void 0;
var React = _interopRequireWildcard(require("react"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var _clsx = _interopRequireDefault(require("clsx"));
var _useOnMount = _interopRequireDefault(require("@mui/utils/useOnMount"));
var _useTimeout = _interopRequireDefault(require("@mui/utils/useTimeout"));
var _zeroStyled = require("../zero-styled");
var _DefaultPropsProvider = require("../DefaultPropsProvider");
var _Ripple = _interopRequireDefault(require("./Ripple"));
var _touchRippleClasses = _interopRequireDefault(require("./touchRippleClasses"));
var _useEventCallback = _interopRequireDefault(require("../utils/useEventCallback"));
var _useReducedMotion = _interopRequireDefault(require("../transitions/useReducedMotion"));
var _jsxRuntime = require("react/jsx-runtime");
const DURATION = 550;
const DELAY_RIPPLE = exports.DELAY_RIPPLE = 80;
const EMPTY_OBJ = {};
const EMPTY_ARRAY = [];
const NOOP = () => {};
/**
* Keep the same DOM order TouchRipple had when it used react-transition-group:
* exiting ripples stay in place, and new ripples are inserted before the final
* group of ripples that are waiting for their exit animation to finish.
*
* @param {number[]} prevOrder The previous DOM order, including ripples that may be exiting.
* @param {number[]} nextActiveKeys The ripples that should still be treated as active.
* @returns {number[]} The next DOM order, preserving the position of exiting ripples where possible.
*/
function mergeRippleOrder(prevOrder, nextActiveKeys) {
const nextKeySet = new Set(nextActiveKeys);
const nextKeysPending = new Map();
let pendingKeys = [];
for (const prevKey of prevOrder) {
if (nextKeySet.has(prevKey)) {
if (pendingKeys.length > 0) {
nextKeysPending.set(prevKey, pendingKeys);
pendingKeys = [];
}
} else {
pendingKeys.push(prevKey);
}
}
const nextOrder = [];
for (const nextKey of nextActiveKeys) {
const pendingBefore = nextKeysPending.get(nextKey);
if (pendingBefore) {
nextOrder.push(...pendingBefore);
}
nextOrder.push(nextKey);
}
nextOrder.push(...pendingKeys);
return nextOrder;
}
/**
* Calculate where the ripple should start and how large it must be to cover the host element.
*
* @param {object} params
* @param {object} params.event The mouse or touch event that started the ripple.
* @param {HTMLElement | null} params.element The host element used for measurements. Tests pass `null`.
* @param {boolean} params.center If `true`, start the ripple from the center of the host element.
* @returns {{ rippleX: number, rippleY: number, rippleSize: number }} The ripple position and size.
*/
function computeRippleState({
event,
element,
center
}) {
const rect = element ? element.getBoundingClientRect() : {
width: 0,
height: 0,
left: 0,
top: 0
};
let rippleX;
let rippleY;
if (center || event === undefined || event.clientX === 0 && event.clientY === 0 || !event.clientX && !event.touches) {
rippleX = Math.round(rect.width / 2);
rippleY = Math.round(rect.height / 2);
} else {
const {
clientX,
clientY
} = event.touches && event.touches.length > 0 ? event.touches[0] : event;
rippleX = Math.round(clientX - rect.left);
rippleY = Math.round(clientY - rect.top);
}
let rippleSize;
if (center) {
rippleSize = Math.sqrt((2 * rect.width ** 2 + rect.height ** 2) / 3);
// Mobile Chrome can skip this animation for even pixel sizes.
if (rippleSize % 2 === 0) {
rippleSize += 1;
}
} else {
const sizeX = Math.max(Math.abs((element ? element.clientWidth : 0) - rippleX), rippleX) * 2 + 2;
const sizeY = Math.max(Math.abs((element ? element.clientHeight : 0) - rippleY), rippleY) * 2 + 2;
rippleSize = Math.sqrt(sizeX ** 2 + sizeY ** 2);
}
return {
rippleX,
rippleY,
rippleSize
};
}
const enterKeyframe = (0, _zeroStyled.keyframes)`
0% {
transform: scale(0);
opacity: 0.1;
}
100% {
transform: scale(1);
opacity: 0.3;
}
`;
const exitKeyframe = (0, _zeroStyled.keyframes)`
0% {
opacity: 1;
}
100% {
opacity: 0;
}
`;
const pulsateKeyframe = (0, _zeroStyled.keyframes)`
0% {
transform: scale(1);
}
50% {
transform: scale(0.92);
}
100% {
transform: scale(1);
}
`;
function getAnimationStyles(theme) {
if (theme.motion.reducedMotion === 'always') {
return null;
}
const styles = (0, _zeroStyled.css)`
&.${_touchRippleClasses.default.rippleVisible} {
animation-name: ${enterKeyframe};
animation-duration: ${DURATION}ms;
animation-timing-function: ${theme.transitions.easing.easeInOut};
}
&.${_touchRippleClasses.default.ripplePulsate} {
animation-duration: ${theme.transitions.duration.shorter}ms;
}
& .${_touchRippleClasses.default.childLeaving} {
animation-name: ${exitKeyframe};
animation-duration: ${DURATION}ms;
animation-timing-function: ${theme.transitions.easing.easeInOut};
}
& .${_touchRippleClasses.default.childPulsate} {
animation-name: ${pulsateKeyframe};
animation-duration: 2500ms;
animation-timing-function: ${theme.transitions.easing.easeInOut};
animation-iteration-count: infinite;
animation-delay: 200ms;
}
`;
if (theme.motion.reducedMotion === 'system') {
return (0, _zeroStyled.css)`
@media (prefers-reduced-motion: no-preference) {
${styles}
}
`;
}
return styles;
}
const TouchRippleRoot = exports.TouchRippleRoot = (0, _zeroStyled.styled)('span', {
name: 'MuiTouchRipple',
slot: 'Root'
})({
overflow: 'hidden',
pointerEvents: 'none',
position: 'absolute',
zIndex: 0,
top: 0,
right: 0,
bottom: 0,
left: 0,
borderRadius: 'inherit'
});
// This `styled()` call uses keyframes. styled-components only supports keyframes
// in template strings, so do not convert these styles to a JS object.
const TouchRippleRipple = exports.TouchRippleRipple = (0, _zeroStyled.styled)(_Ripple.default, {
name: 'MuiTouchRipple',
slot: 'Ripple'
})`
opacity: 0;
position: absolute;
&.${_touchRippleClasses.default.rippleVisible} {
opacity: 0.3;
transform: scale(1);
}
/*
* Order matters: 'child', 'childLeaving' and 'childPulsate' apply to the same
* element with equal specificity, so the later rule wins. 'child' must come
* before 'childLeaving' so the leaving 'opacity: 0' takes precedence. A focus
* (pulsate) ripple keeps 'pulsateKeyframe' (no opacity animation) on exit, so
* it relies on this static 'opacity: 0' to disappear on blur instead of
* lingering until removal.
*/
& .${_touchRippleClasses.default.child} {
opacity: 1;
display: block;
width: 100%;
height: 100%;
border-radius: 50%;
background-color: currentColor;
}
& .${_touchRippleClasses.default.childLeaving} {
opacity: 0;
}
& .${_touchRippleClasses.default.childPulsate} {
position: absolute;
/* @noflip */
left: 0px;
top: 0;
}
${({
theme
}) => getAnimationStyles(theme)}
`;
/**
* @ignore - internal component.
*/
const TouchRipple = /*#__PURE__*/React.forwardRef(function TouchRipple(inProps, ref) {
const props = (0, _DefaultPropsProvider.useDefaultProps)({
props: inProps,
name: 'MuiTouchRipple'
});
const theme = (0, _zeroStyled.useTheme)();
const reducedMotion = (0, _useReducedMotion.default)(theme.motion.reducedMotion, false);
const {
center: centerProp = false,
classes = EMPTY_OBJ,
className,
...other
} = props;
// Store ripples as data so we can keep exiting ripples mounted until their
// exit animation ends. Ripple calls onExited when it is safe to remove one.
const [rippleState, setRippleState] = React.useState({
items: EMPTY_ARRAY,
order: EMPTY_ARRAY
});
const ripples = rippleState.items;
const nextKey = React.useRef(0);
const rippleCallback = React.useRef(null);
const mountedRef = React.useRef(false);
(0, _useOnMount.default)(() => {
mountedRef.current = true;
return () => {
mountedRef.current = false;
};
});
React.useEffect(() => {
if (rippleCallback.current) {
rippleCallback.current();
rippleCallback.current = null;
}
}, [ripples]);
// Used to filter out mouse emulated events on mobile.
const ignoringMouseDown = React.useRef(false);
// We use a timer in order to only show the ripples for touch "click" like events.
// We don't want to display the ripple for touch scroll events.
const startTimer = (0, _useTimeout.default)();
// Holds delayed touch-start work until the delay expires or touchend forces it to run.
const startTimerCommit = React.useRef(null);
const container = React.useRef(null);
const handleExited = (0, _useEventCallback.default)(key => {
if (!mountedRef.current) {
return;
}
setRippleState(prevState => {
const nextItems = prevState.items.filter(ripple => ripple.key !== key);
const nextOrder = mergeRippleOrder(prevState.order.filter(rippleKey => rippleKey !== key), nextItems.filter(ripple => !ripple.exiting).map(ripple => ripple.key));
return {
items: nextItems,
order: nextOrder
};
});
});
const startCommit = (0, _useEventCallback.default)(params => {
const {
pulsate,
rippleX,
rippleY,
rippleSize,
cb
} = params;
const key = nextKey.current;
nextKey.current += 1;
setRippleState(prevState => {
const nextItems = [...prevState.items, {
key,
pulsate,
rippleX,
rippleY,
rippleSize,
exiting: false
}];
return {
items: nextItems,
order: mergeRippleOrder(prevState.order, nextItems.filter(ripple => !ripple.exiting).map(ripple => ripple.key))
};
});
rippleCallback.current = cb;
});
const start = (0, _useEventCallback.default)((event = EMPTY_OBJ, options = EMPTY_OBJ, cb = NOOP) => {
const {
pulsate = false,
center = centerProp || options.pulsate,
fakeElement = false // Used only by tests.
} = options;
if (event?.type === 'mousedown' && ignoringMouseDown.current) {
ignoringMouseDown.current = false;
return;
}
if (event?.type === 'touchstart') {
ignoringMouseDown.current = true;
}
const element = fakeElement ? null : container.current;
const {
rippleX,
rippleY,
rippleSize
} = computeRippleState({
event,
element,
center
});
// Delay touch ripples so scroll gestures do not flash a ripple.
if (event?.touches) {
// Ignore extra touchstart events from multi-touch. There is only one
// delayed start callback to clear on unmount.
if (startTimerCommit.current === null) {
startTimerCommit.current = () => {
startCommit({
pulsate,
rippleX,
rippleY,
rippleSize,
cb
});
};
startTimer.start(DELAY_RIPPLE, () => {
if (startTimerCommit.current) {
startTimerCommit.current();
startTimerCommit.current = null;
}
});
}
} else {
startCommit({
pulsate,
rippleX,
rippleY,
rippleSize,
cb
});
}
});
const pulsate = (0, _useEventCallback.default)(() => {
start(EMPTY_OBJ, {
pulsate: true
});
});
const stop = (0, _useEventCallback.default)((event, cb) => {
startTimer.clear();
// If touch ends before the delay finishes, show the ripple now and stop it
// on the next tick so the user still gets feedback.
if (event?.type === 'touchend' && startTimerCommit.current) {
startTimerCommit.current();
startTimerCommit.current = null;
startTimer.start(0, () => {
stop(event, cb);
});
return;
}
startTimerCommit.current = null;
setRippleState(prevState => {
const firstActiveIndex = prevState.items.findIndex(ripple => !ripple.exiting);
if (firstActiveIndex === -1) {
return prevState;
}
const nextItems = prevState.items.slice();
nextItems[firstActiveIndex] = {
...nextItems[firstActiveIndex],
exiting: true
};
return {
items: nextItems,
order: mergeRippleOrder(prevState.order, nextItems.filter(ripple => !ripple.exiting).map(ripple => ripple.key))
};
});
rippleCallback.current = cb;
});
React.useImperativeHandle(ref, () => ({
pulsate,
start,
stop
}), [pulsate, start, stop]);
const rippleByKey = new Map(ripples.map(ripple => [ripple.key, ripple]));
const orderedRipples = rippleState.order.map(rippleKey => rippleByKey.get(rippleKey)).filter(Boolean);
// Keep the old react-transition-group DOM order:
// exiting ripples stay in place, and new ripples are inserted before the
// final group waiting for its exit animation to finish.
return /*#__PURE__*/(0, _jsxRuntime.jsx)(TouchRippleRoot, {
className: (0, _clsx.default)(_touchRippleClasses.default.root, classes.root, className),
ref: container,
...other,
children: orderedRipples.map(ripple => /*#__PURE__*/(0, _jsxRuntime.jsx)(TouchRippleRipple, {
classes: {
ripple: (0, _clsx.default)(classes.ripple, _touchRippleClasses.default.ripple),
rippleVisible: (0, _clsx.default)(classes.rippleVisible, _touchRippleClasses.default.rippleVisible),
ripplePulsate: (0, _clsx.default)(classes.ripplePulsate, _touchRippleClasses.default.ripplePulsate),
child: (0, _clsx.default)(classes.child, _touchRippleClasses.default.child),
childLeaving: (0, _clsx.default)(classes.childLeaving, _touchRippleClasses.default.childLeaving),
childPulsate: (0, _clsx.default)(classes.childPulsate, _touchRippleClasses.default.childPulsate)
},
timeout: reducedMotion.shouldReduceMotion ? 0 : DURATION,
pulsate: ripple.pulsate,
rippleX: ripple.rippleX,
rippleY: ripple.rippleY,
rippleSize: ripple.rippleSize,
in: !ripple.exiting,
onExited: () => handleExited(ripple.key)
}, ripple.key))
});
});
process.env.NODE_ENV !== "production" ? TouchRipple.propTypes /* remove-proptypes */ = {
/**
* If `true`, the ripple starts at the center of the component
* rather than at the point of interaction.
*/
center: _propTypes.default.bool,
/**
* Override or extend the styles applied to the component.
*/
classes: _propTypes.default.object,
/**
* @ignore
*/
className: _propTypes.default.string
} : void 0;
var _default = exports.default = TouchRipple;
+468
View File
@@ -0,0 +1,468 @@
'use client';
import * as React from 'react';
import PropTypes from 'prop-types';
import clsx from 'clsx';
import useOnMount from '@mui/utils/useOnMount';
import useTimeout from '@mui/utils/useTimeout';
import { keyframes, css, styled, useTheme } from "../zero-styled/index.mjs";
import { useDefaultProps } from "../DefaultPropsProvider/index.mjs";
import Ripple from "./Ripple.mjs";
import touchRippleClasses from "./touchRippleClasses.mjs";
import useEventCallback from "../utils/useEventCallback.mjs";
import useReducedMotion from "../transitions/useReducedMotion.mjs";
import { jsx as _jsx } from "react/jsx-runtime";
const DURATION = 550;
export const DELAY_RIPPLE = 80;
const EMPTY_OBJ = {};
const EMPTY_ARRAY = [];
const NOOP = () => {};
/**
* Keep the same DOM order TouchRipple had when it used react-transition-group:
* exiting ripples stay in place, and new ripples are inserted before the final
* group of ripples that are waiting for their exit animation to finish.
*
* @param {number[]} prevOrder The previous DOM order, including ripples that may be exiting.
* @param {number[]} nextActiveKeys The ripples that should still be treated as active.
* @returns {number[]} The next DOM order, preserving the position of exiting ripples where possible.
*/
function mergeRippleOrder(prevOrder, nextActiveKeys) {
const nextKeySet = new Set(nextActiveKeys);
const nextKeysPending = new Map();
let pendingKeys = [];
for (const prevKey of prevOrder) {
if (nextKeySet.has(prevKey)) {
if (pendingKeys.length > 0) {
nextKeysPending.set(prevKey, pendingKeys);
pendingKeys = [];
}
} else {
pendingKeys.push(prevKey);
}
}
const nextOrder = [];
for (const nextKey of nextActiveKeys) {
const pendingBefore = nextKeysPending.get(nextKey);
if (pendingBefore) {
nextOrder.push(...pendingBefore);
}
nextOrder.push(nextKey);
}
nextOrder.push(...pendingKeys);
return nextOrder;
}
/**
* Calculate where the ripple should start and how large it must be to cover the host element.
*
* @param {object} params
* @param {object} params.event The mouse or touch event that started the ripple.
* @param {HTMLElement | null} params.element The host element used for measurements. Tests pass `null`.
* @param {boolean} params.center If `true`, start the ripple from the center of the host element.
* @returns {{ rippleX: number, rippleY: number, rippleSize: number }} The ripple position and size.
*/
function computeRippleState({
event,
element,
center
}) {
const rect = element ? element.getBoundingClientRect() : {
width: 0,
height: 0,
left: 0,
top: 0
};
let rippleX;
let rippleY;
if (center || event === undefined || event.clientX === 0 && event.clientY === 0 || !event.clientX && !event.touches) {
rippleX = Math.round(rect.width / 2);
rippleY = Math.round(rect.height / 2);
} else {
const {
clientX,
clientY
} = event.touches && event.touches.length > 0 ? event.touches[0] : event;
rippleX = Math.round(clientX - rect.left);
rippleY = Math.round(clientY - rect.top);
}
let rippleSize;
if (center) {
rippleSize = Math.sqrt((2 * rect.width ** 2 + rect.height ** 2) / 3);
// Mobile Chrome can skip this animation for even pixel sizes.
if (rippleSize % 2 === 0) {
rippleSize += 1;
}
} else {
const sizeX = Math.max(Math.abs((element ? element.clientWidth : 0) - rippleX), rippleX) * 2 + 2;
const sizeY = Math.max(Math.abs((element ? element.clientHeight : 0) - rippleY), rippleY) * 2 + 2;
rippleSize = Math.sqrt(sizeX ** 2 + sizeY ** 2);
}
return {
rippleX,
rippleY,
rippleSize
};
}
const enterKeyframe = keyframes`
0% {
transform: scale(0);
opacity: 0.1;
}
100% {
transform: scale(1);
opacity: 0.3;
}
`;
const exitKeyframe = keyframes`
0% {
opacity: 1;
}
100% {
opacity: 0;
}
`;
const pulsateKeyframe = keyframes`
0% {
transform: scale(1);
}
50% {
transform: scale(0.92);
}
100% {
transform: scale(1);
}
`;
function getAnimationStyles(theme) {
if (theme.motion.reducedMotion === 'always') {
return null;
}
const styles = css`
&.${touchRippleClasses.rippleVisible} {
animation-name: ${enterKeyframe};
animation-duration: ${DURATION}ms;
animation-timing-function: ${theme.transitions.easing.easeInOut};
}
&.${touchRippleClasses.ripplePulsate} {
animation-duration: ${theme.transitions.duration.shorter}ms;
}
& .${touchRippleClasses.childLeaving} {
animation-name: ${exitKeyframe};
animation-duration: ${DURATION}ms;
animation-timing-function: ${theme.transitions.easing.easeInOut};
}
& .${touchRippleClasses.childPulsate} {
animation-name: ${pulsateKeyframe};
animation-duration: 2500ms;
animation-timing-function: ${theme.transitions.easing.easeInOut};
animation-iteration-count: infinite;
animation-delay: 200ms;
}
`;
if (theme.motion.reducedMotion === 'system') {
return css`
@media (prefers-reduced-motion: no-preference) {
${styles}
}
`;
}
return styles;
}
export const TouchRippleRoot = styled('span', {
name: 'MuiTouchRipple',
slot: 'Root'
})({
overflow: 'hidden',
pointerEvents: 'none',
position: 'absolute',
zIndex: 0,
top: 0,
right: 0,
bottom: 0,
left: 0,
borderRadius: 'inherit'
});
// This `styled()` call uses keyframes. styled-components only supports keyframes
// in template strings, so do not convert these styles to a JS object.
export const TouchRippleRipple = styled(Ripple, {
name: 'MuiTouchRipple',
slot: 'Ripple'
})`
opacity: 0;
position: absolute;
&.${touchRippleClasses.rippleVisible} {
opacity: 0.3;
transform: scale(1);
}
/*
* Order matters: 'child', 'childLeaving' and 'childPulsate' apply to the same
* element with equal specificity, so the later rule wins. 'child' must come
* before 'childLeaving' so the leaving 'opacity: 0' takes precedence. A focus
* (pulsate) ripple keeps 'pulsateKeyframe' (no opacity animation) on exit, so
* it relies on this static 'opacity: 0' to disappear on blur instead of
* lingering until removal.
*/
& .${touchRippleClasses.child} {
opacity: 1;
display: block;
width: 100%;
height: 100%;
border-radius: 50%;
background-color: currentColor;
}
& .${touchRippleClasses.childLeaving} {
opacity: 0;
}
& .${touchRippleClasses.childPulsate} {
position: absolute;
/* @noflip */
left: 0px;
top: 0;
}
${({
theme
}) => getAnimationStyles(theme)}
`;
/**
* @ignore - internal component.
*/
const TouchRipple = /*#__PURE__*/React.forwardRef(function TouchRipple(inProps, ref) {
const props = useDefaultProps({
props: inProps,
name: 'MuiTouchRipple'
});
const theme = useTheme();
const reducedMotion = useReducedMotion(theme.motion.reducedMotion, false);
const {
center: centerProp = false,
classes = EMPTY_OBJ,
className,
...other
} = props;
// Store ripples as data so we can keep exiting ripples mounted until their
// exit animation ends. Ripple calls onExited when it is safe to remove one.
const [rippleState, setRippleState] = React.useState({
items: EMPTY_ARRAY,
order: EMPTY_ARRAY
});
const ripples = rippleState.items;
const nextKey = React.useRef(0);
const rippleCallback = React.useRef(null);
const mountedRef = React.useRef(false);
useOnMount(() => {
mountedRef.current = true;
return () => {
mountedRef.current = false;
};
});
React.useEffect(() => {
if (rippleCallback.current) {
rippleCallback.current();
rippleCallback.current = null;
}
}, [ripples]);
// Used to filter out mouse emulated events on mobile.
const ignoringMouseDown = React.useRef(false);
// We use a timer in order to only show the ripples for touch "click" like events.
// We don't want to display the ripple for touch scroll events.
const startTimer = useTimeout();
// Holds delayed touch-start work until the delay expires or touchend forces it to run.
const startTimerCommit = React.useRef(null);
const container = React.useRef(null);
const handleExited = useEventCallback(key => {
if (!mountedRef.current) {
return;
}
setRippleState(prevState => {
const nextItems = prevState.items.filter(ripple => ripple.key !== key);
const nextOrder = mergeRippleOrder(prevState.order.filter(rippleKey => rippleKey !== key), nextItems.filter(ripple => !ripple.exiting).map(ripple => ripple.key));
return {
items: nextItems,
order: nextOrder
};
});
});
const startCommit = useEventCallback(params => {
const {
pulsate,
rippleX,
rippleY,
rippleSize,
cb
} = params;
const key = nextKey.current;
nextKey.current += 1;
setRippleState(prevState => {
const nextItems = [...prevState.items, {
key,
pulsate,
rippleX,
rippleY,
rippleSize,
exiting: false
}];
return {
items: nextItems,
order: mergeRippleOrder(prevState.order, nextItems.filter(ripple => !ripple.exiting).map(ripple => ripple.key))
};
});
rippleCallback.current = cb;
});
const start = useEventCallback((event = EMPTY_OBJ, options = EMPTY_OBJ, cb = NOOP) => {
const {
pulsate = false,
center = centerProp || options.pulsate,
fakeElement = false // Used only by tests.
} = options;
if (event?.type === 'mousedown' && ignoringMouseDown.current) {
ignoringMouseDown.current = false;
return;
}
if (event?.type === 'touchstart') {
ignoringMouseDown.current = true;
}
const element = fakeElement ? null : container.current;
const {
rippleX,
rippleY,
rippleSize
} = computeRippleState({
event,
element,
center
});
// Delay touch ripples so scroll gestures do not flash a ripple.
if (event?.touches) {
// Ignore extra touchstart events from multi-touch. There is only one
// delayed start callback to clear on unmount.
if (startTimerCommit.current === null) {
startTimerCommit.current = () => {
startCommit({
pulsate,
rippleX,
rippleY,
rippleSize,
cb
});
};
startTimer.start(DELAY_RIPPLE, () => {
if (startTimerCommit.current) {
startTimerCommit.current();
startTimerCommit.current = null;
}
});
}
} else {
startCommit({
pulsate,
rippleX,
rippleY,
rippleSize,
cb
});
}
});
const pulsate = useEventCallback(() => {
start(EMPTY_OBJ, {
pulsate: true
});
});
const stop = useEventCallback((event, cb) => {
startTimer.clear();
// If touch ends before the delay finishes, show the ripple now and stop it
// on the next tick so the user still gets feedback.
if (event?.type === 'touchend' && startTimerCommit.current) {
startTimerCommit.current();
startTimerCommit.current = null;
startTimer.start(0, () => {
stop(event, cb);
});
return;
}
startTimerCommit.current = null;
setRippleState(prevState => {
const firstActiveIndex = prevState.items.findIndex(ripple => !ripple.exiting);
if (firstActiveIndex === -1) {
return prevState;
}
const nextItems = prevState.items.slice();
nextItems[firstActiveIndex] = {
...nextItems[firstActiveIndex],
exiting: true
};
return {
items: nextItems,
order: mergeRippleOrder(prevState.order, nextItems.filter(ripple => !ripple.exiting).map(ripple => ripple.key))
};
});
rippleCallback.current = cb;
});
React.useImperativeHandle(ref, () => ({
pulsate,
start,
stop
}), [pulsate, start, stop]);
const rippleByKey = new Map(ripples.map(ripple => [ripple.key, ripple]));
const orderedRipples = rippleState.order.map(rippleKey => rippleByKey.get(rippleKey)).filter(Boolean);
// Keep the old react-transition-group DOM order:
// exiting ripples stay in place, and new ripples are inserted before the
// final group waiting for its exit animation to finish.
return /*#__PURE__*/_jsx(TouchRippleRoot, {
className: clsx(touchRippleClasses.root, classes.root, className),
ref: container,
...other,
children: orderedRipples.map(ripple => /*#__PURE__*/_jsx(TouchRippleRipple, {
classes: {
ripple: clsx(classes.ripple, touchRippleClasses.ripple),
rippleVisible: clsx(classes.rippleVisible, touchRippleClasses.rippleVisible),
ripplePulsate: clsx(classes.ripplePulsate, touchRippleClasses.ripplePulsate),
child: clsx(classes.child, touchRippleClasses.child),
childLeaving: clsx(classes.childLeaving, touchRippleClasses.childLeaving),
childPulsate: clsx(classes.childPulsate, touchRippleClasses.childPulsate)
},
timeout: reducedMotion.shouldReduceMotion ? 0 : DURATION,
pulsate: ripple.pulsate,
rippleX: ripple.rippleX,
rippleY: ripple.rippleY,
rippleSize: ripple.rippleSize,
in: !ripple.exiting,
onExited: () => handleExited(ripple.key)
}, ripple.key))
});
});
process.env.NODE_ENV !== "production" ? TouchRipple.propTypes /* remove-proptypes */ = {
/**
* If `true`, the ripple starts at the center of the component
* rather than at the point of interaction.
*/
center: PropTypes.bool,
/**
* Override or extend the styles applied to the component.
*/
classes: PropTypes.object,
/**
* @ignore
*/
className: PropTypes.string
} : void 0;
export default TouchRipple;
+12
View File
@@ -0,0 +1,12 @@
export interface ButtonBaseClasses {
/** Styles applied to the root element. */
root: string;
/** State class applied to the root element if `disabled={true}`. */
disabled: string;
/** State class applied to the root element if keyboard focused. */
focusVisible: string;
}
export type ButtonBaseClassKey = keyof ButtonBaseClasses;
export declare function getButtonBaseUtilityClass(slot: string): string;
declare const buttonBaseClasses: ButtonBaseClasses;
export default buttonBaseClasses;
+12
View File
@@ -0,0 +1,12 @@
export interface ButtonBaseClasses {
/** Styles applied to the root element. */
root: string;
/** State class applied to the root element if `disabled={true}`. */
disabled: string;
/** State class applied to the root element if keyboard focused. */
focusVisible: string;
}
export type ButtonBaseClassKey = keyof ButtonBaseClasses;
export declare function getButtonBaseUtilityClass(slot: string): string;
declare const buttonBaseClasses: ButtonBaseClasses;
export default buttonBaseClasses;
+15
View File
@@ -0,0 +1,15 @@
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
exports.getButtonBaseUtilityClass = getButtonBaseUtilityClass;
var _generateUtilityClasses = _interopRequireDefault(require("@mui/utils/generateUtilityClasses"));
var _generateUtilityClass = _interopRequireDefault(require("@mui/utils/generateUtilityClass"));
function getButtonBaseUtilityClass(slot) {
return (0, _generateUtilityClass.default)('MuiButtonBase', slot);
}
const buttonBaseClasses = (0, _generateUtilityClasses.default)('MuiButtonBase', ['root', 'disabled', 'focusVisible']);
var _default = exports.default = buttonBaseClasses;
+7
View File
@@ -0,0 +1,7 @@
import generateUtilityClasses from '@mui/utils/generateUtilityClasses';
import generateUtilityClass from '@mui/utils/generateUtilityClass';
export function getButtonBaseUtilityClass(slot) {
return generateUtilityClass('MuiButtonBase', slot);
}
const buttonBaseClasses = generateUtilityClasses('MuiButtonBase', ['root', 'disabled', 'focusVisible']);
export default buttonBaseClasses;
+6
View File
@@ -0,0 +1,6 @@
export { default } from "./ButtonBase.mjs";
export * from "./ButtonBase.mjs";
export { default as buttonBaseClasses } from "./buttonBaseClasses.mjs";
export * from "./buttonBaseClasses.mjs";
export { default as touchRippleClasses } from "./touchRippleClasses.mjs";
export * from "./touchRippleClasses.mjs";
+6
View File
@@ -0,0 +1,6 @@
export { default } from "./ButtonBase.js";
export * from "./ButtonBase.js";
export { default as buttonBaseClasses } from "./buttonBaseClasses.js";
export * from "./buttonBaseClasses.js";
export { default as touchRippleClasses } from "./touchRippleClasses.js";
export * from "./touchRippleClasses.js";
+54
View File
@@ -0,0 +1,54 @@
"use strict";
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
var _exportNames = {
buttonBaseClasses: true,
touchRippleClasses: true
};
Object.defineProperty(exports, "buttonBaseClasses", {
enumerable: true,
get: function () {
return _buttonBaseClasses.default;
}
});
Object.defineProperty(exports, "default", {
enumerable: true,
get: function () {
return _ButtonBase.default;
}
});
Object.defineProperty(exports, "touchRippleClasses", {
enumerable: true,
get: function () {
return _touchRippleClasses.default;
}
});
var _ButtonBase = _interopRequireDefault(require("./ButtonBase"));
var _buttonBaseClasses = _interopRequireWildcard(require("./buttonBaseClasses"));
Object.keys(_buttonBaseClasses).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
if (key in exports && exports[key] === _buttonBaseClasses[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _buttonBaseClasses[key];
}
});
});
var _touchRippleClasses = _interopRequireWildcard(require("./touchRippleClasses"));
Object.keys(_touchRippleClasses).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
if (key in exports && exports[key] === _touchRippleClasses[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _touchRippleClasses[key];
}
});
});
+5
View File
@@ -0,0 +1,5 @@
export { default } from "./ButtonBase.mjs";
export { default as buttonBaseClasses } from "./buttonBaseClasses.mjs";
export * from "./buttonBaseClasses.mjs";
export { default as touchRippleClasses } from "./touchRippleClasses.mjs";
export * from "./touchRippleClasses.mjs";
+20
View File
@@ -0,0 +1,20 @@
export interface TouchRippleClasses {
/** Styles applied to the root element. */
root: string;
/** Styles applied to the internal `Ripple` components `ripple` class. */
ripple: string;
/** Styles applied to the internal `Ripple` components `rippleVisible` class. */
rippleVisible: string;
/** Styles applied to the internal `Ripple` components `ripplePulsate` class. */
ripplePulsate: string;
/** Styles applied to the internal `Ripple` components `child` class. */
child: string;
/** Styles applied to the internal `Ripple` components `childLeaving` class. */
childLeaving: string;
/** Styles applied to the internal `Ripple` components `childPulsate` class. */
childPulsate: string;
}
export type TouchRippleClassKey = keyof TouchRippleClasses;
export declare function getTouchRippleUtilityClass(slot: string): string;
declare const touchRippleClasses: TouchRippleClasses;
export default touchRippleClasses;
+20
View File
@@ -0,0 +1,20 @@
export interface TouchRippleClasses {
/** Styles applied to the root element. */
root: string;
/** Styles applied to the internal `Ripple` components `ripple` class. */
ripple: string;
/** Styles applied to the internal `Ripple` components `rippleVisible` class. */
rippleVisible: string;
/** Styles applied to the internal `Ripple` components `ripplePulsate` class. */
ripplePulsate: string;
/** Styles applied to the internal `Ripple` components `child` class. */
child: string;
/** Styles applied to the internal `Ripple` components `childLeaving` class. */
childLeaving: string;
/** Styles applied to the internal `Ripple` components `childPulsate` class. */
childPulsate: string;
}
export type TouchRippleClassKey = keyof TouchRippleClasses;
export declare function getTouchRippleUtilityClass(slot: string): string;
declare const touchRippleClasses: TouchRippleClasses;
export default touchRippleClasses;
+15
View File
@@ -0,0 +1,15 @@
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
exports.getTouchRippleUtilityClass = getTouchRippleUtilityClass;
var _generateUtilityClasses = _interopRequireDefault(require("@mui/utils/generateUtilityClasses"));
var _generateUtilityClass = _interopRequireDefault(require("@mui/utils/generateUtilityClass"));
function getTouchRippleUtilityClass(slot) {
return (0, _generateUtilityClass.default)('MuiTouchRipple', slot);
}
const touchRippleClasses = (0, _generateUtilityClasses.default)('MuiTouchRipple', ['root', 'ripple', 'rippleVisible', 'ripplePulsate', 'child', 'childLeaving', 'childPulsate']);
var _default = exports.default = touchRippleClasses;
+7
View File
@@ -0,0 +1,7 @@
import generateUtilityClasses from '@mui/utils/generateUtilityClasses';
import generateUtilityClass from '@mui/utils/generateUtilityClass';
export function getTouchRippleUtilityClass(slot) {
return generateUtilityClass('MuiTouchRipple', slot);
}
const touchRippleClasses = generateUtilityClasses('MuiTouchRipple', ['root', 'ripple', 'rippleVisible', 'ripplePulsate', 'child', 'childLeaving', 'childPulsate']);
export default touchRippleClasses;
+91
View File
@@ -0,0 +1,91 @@
import * as React from 'react';
export interface UseButtonBaseParameters {
/**
* Whether the root should be treated as a native `<button>` for render-time semantics.
*/
nativeButton: boolean;
/**
* The explicit `nativeButton` value provided by the caller, if any.
*/
nativeButtonProp?: boolean | undefined;
/**
* Whether the default rendered element is expected to be a native button when
* `nativeButton` was not explicitly provided.
* @default nativeButton
*/
internalNativeButton?: boolean | undefined;
/**
* Whether to perform additional checks in dev mode on whether the resolved element
* matches the default native or non-native button expectation.
* Set to `true` to allow hook callers bypass this check, e.g. when the `component`
* prop is a string.
* @default false
*/
allowInferredHostMismatch?: boolean | undefined;
/**
* The disabled state of the component.
*/
disabled: boolean;
/**
* The `type` attribute for the element.
*/
type?: string | undefined;
/**
* Whether the element has a `formAction` attribute. When true, the hook
* will not default `type` to `'button'` for native buttons so the browser
* can use its natural submit behaviour.
* @default false
*/
hasFormAction?: boolean | undefined;
/**
* The `tabIndex` attribute for the element.
* @default 0
*/
tabIndex?: number | undefined;
/**
* When `true`, a disabled root can remain focusable.
* When `undefined`, the feature is not enabled.
*/
focusableWhenDisabled?: boolean | undefined;
/**
* When `true`, calls `event.stopPropagation()` on click before the disabled guard runs.
* @default false
*/
stopEventPropagation?: boolean | undefined;
/**
* An additional function that will run before the user's `onKeyDown`, e.g.
* to trigger the ripple effect in `<ButtonBase>`.
*/
onBeforeKeyDown?: React.KeyboardEventHandler<HTMLElement> | undefined;
/**
* An additional function that will run before the user's `onKeyUp`, e.g.
* to control the ripple effect in `<ButtonBase>`.
*/
onBeforeKeyUp?: React.KeyboardEventHandler<HTMLElement> | undefined;
}
export interface ButtonBaseButtonProps {
role?: string | undefined;
'aria-disabled'?: boolean | undefined;
type?: string | undefined;
disabled?: boolean | undefined;
tabIndex: number;
}
export interface ButtonBaseExternalProps extends React.HTMLAttributes<any> {
'aria-disabled'?: boolean | undefined;
disabled?: boolean | undefined;
type?: string | undefined;
}
export interface ButtonBaseEventHandlers {
onClick: React.MouseEventHandler<HTMLElement>;
onKeyDown: React.KeyboardEventHandler<HTMLElement>;
onKeyUp: React.KeyboardEventHandler<HTMLElement>;
}
export interface UseButtonBaseReturnValue {
/**
* @param externalProps additional props for the button
* @returns props that should be spread on the button
*/
getButtonProps: <ExternalProps extends ButtonBaseExternalProps = ButtonBaseExternalProps>(externalProps?: ExternalProps) => Omit<ExternalProps, keyof ButtonBaseEventHandlers> & ButtonBaseButtonProps & ButtonBaseEventHandlers;
rootRef: React.RefObject<HTMLElement | null>;
}
export default function useButtonBase(parameters: UseButtonBaseParameters): UseButtonBaseReturnValue;
+91
View File
@@ -0,0 +1,91 @@
import * as React from 'react';
export interface UseButtonBaseParameters {
/**
* Whether the root should be treated as a native `<button>` for render-time semantics.
*/
nativeButton: boolean;
/**
* The explicit `nativeButton` value provided by the caller, if any.
*/
nativeButtonProp?: boolean | undefined;
/**
* Whether the default rendered element is expected to be a native button when
* `nativeButton` was not explicitly provided.
* @default nativeButton
*/
internalNativeButton?: boolean | undefined;
/**
* Whether to perform additional checks in dev mode on whether the resolved element
* matches the default native or non-native button expectation.
* Set to `true` to allow hook callers bypass this check, e.g. when the `component`
* prop is a string.
* @default false
*/
allowInferredHostMismatch?: boolean | undefined;
/**
* The disabled state of the component.
*/
disabled: boolean;
/**
* The `type` attribute for the element.
*/
type?: string | undefined;
/**
* Whether the element has a `formAction` attribute. When true, the hook
* will not default `type` to `'button'` for native buttons so the browser
* can use its natural submit behaviour.
* @default false
*/
hasFormAction?: boolean | undefined;
/**
* The `tabIndex` attribute for the element.
* @default 0
*/
tabIndex?: number | undefined;
/**
* When `true`, a disabled root can remain focusable.
* When `undefined`, the feature is not enabled.
*/
focusableWhenDisabled?: boolean | undefined;
/**
* When `true`, calls `event.stopPropagation()` on click before the disabled guard runs.
* @default false
*/
stopEventPropagation?: boolean | undefined;
/**
* An additional function that will run before the user's `onKeyDown`, e.g.
* to trigger the ripple effect in `<ButtonBase>`.
*/
onBeforeKeyDown?: React.KeyboardEventHandler<HTMLElement> | undefined;
/**
* An additional function that will run before the user's `onKeyUp`, e.g.
* to control the ripple effect in `<ButtonBase>`.
*/
onBeforeKeyUp?: React.KeyboardEventHandler<HTMLElement> | undefined;
}
export interface ButtonBaseButtonProps {
role?: string | undefined;
'aria-disabled'?: boolean | undefined;
type?: string | undefined;
disabled?: boolean | undefined;
tabIndex: number;
}
export interface ButtonBaseExternalProps extends React.HTMLAttributes<any> {
'aria-disabled'?: boolean | undefined;
disabled?: boolean | undefined;
type?: string | undefined;
}
export interface ButtonBaseEventHandlers {
onClick: React.MouseEventHandler<HTMLElement>;
onKeyDown: React.KeyboardEventHandler<HTMLElement>;
onKeyUp: React.KeyboardEventHandler<HTMLElement>;
}
export interface UseButtonBaseReturnValue {
/**
* @param externalProps additional props for the button
* @returns props that should be spread on the button
*/
getButtonProps: <ExternalProps extends ButtonBaseExternalProps = ButtonBaseExternalProps>(externalProps?: ExternalProps) => Omit<ExternalProps, keyof ButtonBaseEventHandlers> & ButtonBaseButtonProps & ButtonBaseEventHandlers;
rootRef: React.RefObject<HTMLElement | null>;
}
export default function useButtonBase(parameters: UseButtonBaseParameters): UseButtonBaseReturnValue;
+174
View File
@@ -0,0 +1,174 @@
"use strict";
'use client';
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = useButtonBase;
var React = _interopRequireWildcard(require("react"));
var _useFocusableWhenDisabled = _interopRequireDefault(require("../utils/useFocusableWhenDisabled"));
const EMPTY = {};
function useButtonBase(parameters) {
const {
nativeButton,
nativeButtonProp,
internalNativeButton = nativeButton,
allowInferredHostMismatch = false,
disabled,
type,
hasFormAction = false,
tabIndex = 0,
focusableWhenDisabled: focusableWhenDisabledParam,
stopEventPropagation = false,
onBeforeKeyDown,
onBeforeKeyUp
} = parameters;
const rootRef = React.useRef(null);
const focusableWhenDisabled = focusableWhenDisabledParam === true;
const focusableWhenDisabledProps = (0, _useFocusableWhenDisabled.default)({
focusableWhenDisabled,
disabled,
isNativeButton: nativeButton,
tabIndex
});
if (process.env.NODE_ENV !== 'production') {
// eslint-disable-next-line react-hooks/rules-of-hooks
React.useEffect(() => {
const root = rootRef.current;
if (root == null) {
return;
}
const isButtonTag = root.tagName === 'BUTTON';
if (nativeButtonProp !== undefined) {
if (nativeButtonProp && !isButtonTag) {
const message = 'MUI: A component that acts as a button expected a native <button> because the ' + '`nativeButton` prop is true. Rendering a non-<button> removes native button ' + 'semantics, which can impact forms and accessibility. Render a real <button> ' + 'or set `nativeButton` to `false`.';
console.error(message);
}
if (!nativeButtonProp && isButtonTag) {
const message = 'MUI: A component that acts as a button expected a non-<button> because the `nativeButton` ' + 'prop is false. Rendering a <button> keeps native behavior while additionally applies ' + 'non-native attributes and handlers, which can add unintended extra attributes (such ' + 'as `role` or `aria-disabled`). Render a non-<button> such as <div>, or set ' + '`nativeButton` to `true`.';
console.error(message);
}
return;
}
if (allowInferredHostMismatch) {
return;
}
// warn when expecting a native <button> element but a non-string `component` prop resolved to a non-button element
if (internalNativeButton && !isButtonTag) {
const message = 'MUI: A component rendering a native <button> resolved to a non-<button> element, ' + 'but `nativeButton={false}` was not specified and the resolved root is a non-<button>. ' + 'When rendering a custom component, set `nativeButton={false}` explicitly or render a <button> element.';
console.error(message);
}
// warn when expecting a non-button but a non-string `component` prop resolved to a native <button> element
if (!internalNativeButton && isButtonTag) {
const message = 'MUI: A component that acts as a non-native button resolved to a native <button> element, ' + 'but `nativeButton={true}` was not specified. ' + 'When rendering a custom component, set `nativeButton={true}` explicitly or render a non-<button> element.';
console.error(message);
}
}, [allowInferredHostMismatch, internalNativeButton, nativeButtonProp]);
}
// A helper for event handlers to determine whether to use browser-defined keyboard activation
// for native elements like <button> and <a href>, or synthesize Enter/Space clicks for non-native
// elements like `<div role="button">`.
// This is UNSAFE TO USE outside of event handers, e.g. in render.
const hasNativeKeyboardActivation = React.useCallback(() => {
const root = rootRef.current;
if (root == null) {
return nativeButton;
}
if (root.tagName === 'BUTTON') {
return true;
}
// Although this hook is not intended for links, this check is for
// backward compatibility with `<ButtonBase href="#" />` since ButtonBase
// uses the returned event handlers.
return Boolean(root.tagName === 'A' && root.href);
}, [nativeButton]);
const buttonProps = React.useMemo(() => {
const resolvedButtonProps = focusableWhenDisabled ? {} : {
tabIndex: disabled ? -1 : tabIndex
};
if (nativeButton) {
resolvedButtonProps.type = type === undefined && !hasFormAction ? 'button' : type;
if (!focusableWhenDisabled) {
resolvedButtonProps.disabled = disabled;
}
} else {
resolvedButtonProps.role = 'button';
if (!focusableWhenDisabled && disabled) {
resolvedButtonProps['aria-disabled'] = disabled;
}
}
if (focusableWhenDisabled) {
return {
...resolvedButtonProps,
...focusableWhenDisabledProps
};
}
return resolvedButtonProps;
}, [disabled, focusableWhenDisabled, focusableWhenDisabledProps, hasFormAction, nativeButton, tabIndex, type]);
const getButtonProps = React.useCallback((externalProps = EMPTY) => {
const {
onClick: externalOnClick,
onKeyDown: externalOnKeyDown,
onKeyUp: externalOnKeyUp,
...otherExternalProps
} = externalProps;
const handleClick = event => {
if (stopEventPropagation) {
event.stopPropagation();
}
if (disabled) {
event.preventDefault();
return;
}
externalOnClick?.(event);
};
const handleKeyDown = event => {
if (focusableWhenDisabled) {
focusableWhenDisabledProps.onKeyDown(event);
}
if (disabled) {
return;
}
onBeforeKeyDown?.(event);
externalOnKeyDown?.(event);
if (event.target !== event.currentTarget || hasNativeKeyboardActivation()) {
return;
}
if (event.key === ' ') {
event.preventDefault();
return;
}
if (event.key === 'Enter') {
event.preventDefault();
event.currentTarget.click();
}
};
const handleKeyUp = event => {
if (disabled) {
return;
}
onBeforeKeyUp?.(event);
externalOnKeyUp?.(event);
if (event.target === event.currentTarget && !hasNativeKeyboardActivation() && event.key === ' ' && !event.defaultPrevented) {
event.currentTarget.click();
}
};
return {
...buttonProps,
...otherExternalProps,
onClick: handleClick,
onKeyDown: handleKeyDown,
onKeyUp: handleKeyUp
};
}, [buttonProps, disabled, focusableWhenDisabled, focusableWhenDisabledProps, hasNativeKeyboardActivation, onBeforeKeyDown, onBeforeKeyUp, stopEventPropagation]);
return {
getButtonProps,
rootRef
};
}
+167
View File
@@ -0,0 +1,167 @@
'use client';
import * as React from 'react';
import useFocusableWhenDisabled from "../utils/useFocusableWhenDisabled.mjs";
const EMPTY = {};
export default function useButtonBase(parameters) {
const {
nativeButton,
nativeButtonProp,
internalNativeButton = nativeButton,
allowInferredHostMismatch = false,
disabled,
type,
hasFormAction = false,
tabIndex = 0,
focusableWhenDisabled: focusableWhenDisabledParam,
stopEventPropagation = false,
onBeforeKeyDown,
onBeforeKeyUp
} = parameters;
const rootRef = React.useRef(null);
const focusableWhenDisabled = focusableWhenDisabledParam === true;
const focusableWhenDisabledProps = useFocusableWhenDisabled({
focusableWhenDisabled,
disabled,
isNativeButton: nativeButton,
tabIndex
});
if (process.env.NODE_ENV !== 'production') {
// eslint-disable-next-line react-hooks/rules-of-hooks
React.useEffect(() => {
const root = rootRef.current;
if (root == null) {
return;
}
const isButtonTag = root.tagName === 'BUTTON';
if (nativeButtonProp !== undefined) {
if (nativeButtonProp && !isButtonTag) {
const message = 'MUI: A component that acts as a button expected a native <button> because the ' + '`nativeButton` prop is true. Rendering a non-<button> removes native button ' + 'semantics, which can impact forms and accessibility. Render a real <button> ' + 'or set `nativeButton` to `false`.';
console.error(message);
}
if (!nativeButtonProp && isButtonTag) {
const message = 'MUI: A component that acts as a button expected a non-<button> because the `nativeButton` ' + 'prop is false. Rendering a <button> keeps native behavior while additionally applies ' + 'non-native attributes and handlers, which can add unintended extra attributes (such ' + 'as `role` or `aria-disabled`). Render a non-<button> such as <div>, or set ' + '`nativeButton` to `true`.';
console.error(message);
}
return;
}
if (allowInferredHostMismatch) {
return;
}
// warn when expecting a native <button> element but a non-string `component` prop resolved to a non-button element
if (internalNativeButton && !isButtonTag) {
const message = 'MUI: A component rendering a native <button> resolved to a non-<button> element, ' + 'but `nativeButton={false}` was not specified and the resolved root is a non-<button>. ' + 'When rendering a custom component, set `nativeButton={false}` explicitly or render a <button> element.';
console.error(message);
}
// warn when expecting a non-button but a non-string `component` prop resolved to a native <button> element
if (!internalNativeButton && isButtonTag) {
const message = 'MUI: A component that acts as a non-native button resolved to a native <button> element, ' + 'but `nativeButton={true}` was not specified. ' + 'When rendering a custom component, set `nativeButton={true}` explicitly or render a non-<button> element.';
console.error(message);
}
}, [allowInferredHostMismatch, internalNativeButton, nativeButtonProp]);
}
// A helper for event handlers to determine whether to use browser-defined keyboard activation
// for native elements like <button> and <a href>, or synthesize Enter/Space clicks for non-native
// elements like `<div role="button">`.
// This is UNSAFE TO USE outside of event handers, e.g. in render.
const hasNativeKeyboardActivation = React.useCallback(() => {
const root = rootRef.current;
if (root == null) {
return nativeButton;
}
if (root.tagName === 'BUTTON') {
return true;
}
// Although this hook is not intended for links, this check is for
// backward compatibility with `<ButtonBase href="#" />` since ButtonBase
// uses the returned event handlers.
return Boolean(root.tagName === 'A' && root.href);
}, [nativeButton]);
const buttonProps = React.useMemo(() => {
const resolvedButtonProps = focusableWhenDisabled ? {} : {
tabIndex: disabled ? -1 : tabIndex
};
if (nativeButton) {
resolvedButtonProps.type = type === undefined && !hasFormAction ? 'button' : type;
if (!focusableWhenDisabled) {
resolvedButtonProps.disabled = disabled;
}
} else {
resolvedButtonProps.role = 'button';
if (!focusableWhenDisabled && disabled) {
resolvedButtonProps['aria-disabled'] = disabled;
}
}
if (focusableWhenDisabled) {
return {
...resolvedButtonProps,
...focusableWhenDisabledProps
};
}
return resolvedButtonProps;
}, [disabled, focusableWhenDisabled, focusableWhenDisabledProps, hasFormAction, nativeButton, tabIndex, type]);
const getButtonProps = React.useCallback((externalProps = EMPTY) => {
const {
onClick: externalOnClick,
onKeyDown: externalOnKeyDown,
onKeyUp: externalOnKeyUp,
...otherExternalProps
} = externalProps;
const handleClick = event => {
if (stopEventPropagation) {
event.stopPropagation();
}
if (disabled) {
event.preventDefault();
return;
}
externalOnClick?.(event);
};
const handleKeyDown = event => {
if (focusableWhenDisabled) {
focusableWhenDisabledProps.onKeyDown(event);
}
if (disabled) {
return;
}
onBeforeKeyDown?.(event);
externalOnKeyDown?.(event);
if (event.target !== event.currentTarget || hasNativeKeyboardActivation()) {
return;
}
if (event.key === ' ') {
event.preventDefault();
return;
}
if (event.key === 'Enter') {
event.preventDefault();
event.currentTarget.click();
}
};
const handleKeyUp = event => {
if (disabled) {
return;
}
onBeforeKeyUp?.(event);
externalOnKeyUp?.(event);
if (event.target === event.currentTarget && !hasNativeKeyboardActivation() && event.key === ' ' && !event.defaultPrevented) {
event.currentTarget.click();
}
};
return {
...buttonProps,
...otherExternalProps,
onClick: handleClick,
onKeyDown: handleKeyDown,
onKeyUp: handleKeyUp
};
}, [buttonProps, disabled, focusableWhenDisabled, focusableWhenDisabledProps, hasNativeKeyboardActivation, onBeforeKeyDown, onBeforeKeyUp, stopEventPropagation]);
return {
getButtonProps,
rootRef
};
}