Add coexistence checks to all enqueue methods to prevent loading both React and Grid.js assets simultaneously. Changes: - ReactAdmin.php: Only enqueue React assets when ?react=1 - Init.php: Skip Grid.js when React active on admin pages - Form.php, Coupon.php, Access.php: Restore classic assets when ?react=0 - Customer.php, Product.php, License.php: Add coexistence checks Now the toggle between Classic and React versions works correctly. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
302 lines
12 KiB
JavaScript
302 lines
12 KiB
JavaScript
"use strict";
|
||
|
||
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
||
Object.defineProperty(exports, "__esModule", {
|
||
value: true
|
||
});
|
||
exports.default = exports.Modal = void 0;
|
||
var _react = require("react");
|
||
var _classnames = _interopRequireDefault(require("classnames"));
|
||
var _element = require("@wordpress/element");
|
||
var _compose = require("@wordpress/compose");
|
||
var _i18n = require("@wordpress/i18n");
|
||
var _icons = require("@wordpress/icons");
|
||
var _dom = require("@wordpress/dom");
|
||
var ariaHelper = _interopRequireWildcard(require("./aria-helper"));
|
||
var _button = _interopRequireDefault(require("../button"));
|
||
var _styleProvider = _interopRequireDefault(require("../style-provider"));
|
||
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
||
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
||
/**
|
||
* External dependencies
|
||
*/
|
||
|
||
/**
|
||
* WordPress dependencies
|
||
*/
|
||
|
||
/**
|
||
* Internal dependencies
|
||
*/
|
||
|
||
// Used to track and dismiss the prior modal when another opens unless nested.
|
||
const ModalContext = (0, _element.createContext)([]);
|
||
|
||
// Used to track body class names applied while modals are open.
|
||
const bodyOpenClasses = new Map();
|
||
function UnforwardedModal(props, forwardedRef) {
|
||
const {
|
||
bodyOpenClassName = 'modal-open',
|
||
role = 'dialog',
|
||
title = null,
|
||
focusOnMount = true,
|
||
shouldCloseOnEsc = true,
|
||
shouldCloseOnClickOutside = true,
|
||
isDismissible = true,
|
||
/* Accessibility. */
|
||
aria = {
|
||
labelledby: undefined,
|
||
describedby: undefined
|
||
},
|
||
onRequestClose,
|
||
icon,
|
||
closeButtonLabel,
|
||
children,
|
||
style,
|
||
overlayClassName,
|
||
className,
|
||
contentLabel,
|
||
onKeyDown,
|
||
isFullScreen = false,
|
||
size,
|
||
headerActions = null,
|
||
__experimentalHideHeader = false
|
||
} = props;
|
||
const ref = (0, _element.useRef)();
|
||
const instanceId = (0, _compose.useInstanceId)(Modal);
|
||
const headingId = title ? `components-modal-header-${instanceId}` : aria.labelledby;
|
||
|
||
// The focus hook does not support 'firstContentElement' but this is a valid
|
||
// value for the Modal's focusOnMount prop. The following code ensures the focus
|
||
// hook will focus the first focusable node within the element to which it is applied.
|
||
// When `firstContentElement` is passed as the value of the focusOnMount prop,
|
||
// the focus hook is applied to the Modal's content element.
|
||
// Otherwise, the focus hook is applied to the Modal's ref. This ensures that the
|
||
// focus hook will focus the first element in the Modal's **content** when
|
||
// `firstContentElement` is passed.
|
||
const focusOnMountRef = (0, _compose.useFocusOnMount)(focusOnMount === 'firstContentElement' ? 'firstElement' : focusOnMount);
|
||
const constrainedTabbingRef = (0, _compose.useConstrainedTabbing)();
|
||
const focusReturnRef = (0, _compose.useFocusReturn)();
|
||
const contentRef = (0, _element.useRef)(null);
|
||
const childrenContainerRef = (0, _element.useRef)(null);
|
||
const [hasScrolledContent, setHasScrolledContent] = (0, _element.useState)(false);
|
||
const [hasScrollableContent, setHasScrollableContent] = (0, _element.useState)(false);
|
||
let sizeClass;
|
||
if (isFullScreen || size === 'fill') {
|
||
sizeClass = 'is-full-screen';
|
||
} else if (size) {
|
||
sizeClass = `has-size-${size}`;
|
||
}
|
||
|
||
// Determines whether the Modal content is scrollable and updates the state.
|
||
const isContentScrollable = (0, _element.useCallback)(() => {
|
||
if (!contentRef.current) {
|
||
return;
|
||
}
|
||
const closestScrollContainer = (0, _dom.getScrollContainer)(contentRef.current);
|
||
if (contentRef.current === closestScrollContainer) {
|
||
setHasScrollableContent(true);
|
||
} else {
|
||
setHasScrollableContent(false);
|
||
}
|
||
}, [contentRef]);
|
||
|
||
// Accessibly isolates/unisolates the modal.
|
||
(0, _element.useEffect)(() => {
|
||
ariaHelper.modalize(ref.current);
|
||
return () => ariaHelper.unmodalize();
|
||
}, []);
|
||
|
||
// Keeps a fresh ref for the subsequent effect.
|
||
const refOnRequestClose = (0, _element.useRef)();
|
||
(0, _element.useEffect)(() => {
|
||
refOnRequestClose.current = onRequestClose;
|
||
}, [onRequestClose]);
|
||
|
||
// The list of `onRequestClose` callbacks of open (non-nested) Modals. Only
|
||
// one should remain open at a time and the list enables closing prior ones.
|
||
const dismissers = (0, _element.useContext)(ModalContext);
|
||
// Used for the tracking and dismissing any nested modals.
|
||
const nestedDismissers = (0, _element.useRef)([]);
|
||
|
||
// Updates the stack tracking open modals at this level and calls
|
||
// onRequestClose for any prior and/or nested modals as applicable.
|
||
(0, _element.useEffect)(() => {
|
||
dismissers.push(refOnRequestClose);
|
||
const [first, second] = dismissers;
|
||
if (second) first?.current?.();
|
||
const nested = nestedDismissers.current;
|
||
return () => {
|
||
nested[0]?.current?.();
|
||
dismissers.shift();
|
||
};
|
||
}, [dismissers]);
|
||
|
||
// Adds/removes the value of bodyOpenClassName to body element.
|
||
(0, _element.useEffect)(() => {
|
||
var _bodyOpenClasses$get;
|
||
const theClass = bodyOpenClassName;
|
||
const oneMore = 1 + ((_bodyOpenClasses$get = bodyOpenClasses.get(theClass)) !== null && _bodyOpenClasses$get !== void 0 ? _bodyOpenClasses$get : 0);
|
||
bodyOpenClasses.set(theClass, oneMore);
|
||
document.body.classList.add(bodyOpenClassName);
|
||
return () => {
|
||
const oneLess = bodyOpenClasses.get(theClass) - 1;
|
||
if (oneLess === 0) {
|
||
document.body.classList.remove(theClass);
|
||
bodyOpenClasses.delete(theClass);
|
||
} else {
|
||
bodyOpenClasses.set(theClass, oneLess);
|
||
}
|
||
};
|
||
}, [bodyOpenClassName]);
|
||
|
||
// Calls the isContentScrollable callback when the Modal children container resizes.
|
||
(0, _element.useLayoutEffect)(() => {
|
||
if (!window.ResizeObserver || !childrenContainerRef.current) {
|
||
return;
|
||
}
|
||
const resizeObserver = new ResizeObserver(isContentScrollable);
|
||
resizeObserver.observe(childrenContainerRef.current);
|
||
isContentScrollable();
|
||
return () => {
|
||
resizeObserver.disconnect();
|
||
};
|
||
}, [isContentScrollable, childrenContainerRef]);
|
||
function handleEscapeKeyDown(event) {
|
||
if (
|
||
// Ignore keydowns from IMEs
|
||
event.nativeEvent.isComposing ||
|
||
// Workaround for Mac Safari where the final Enter/Backspace of an IME composition
|
||
// is `isComposing=false`, even though it's technically still part of the composition.
|
||
// These can only be detected by keyCode.
|
||
event.keyCode === 229) {
|
||
return;
|
||
}
|
||
if (shouldCloseOnEsc && (event.code === 'Escape' || event.key === 'Escape') && !event.defaultPrevented) {
|
||
event.preventDefault();
|
||
if (onRequestClose) {
|
||
onRequestClose(event);
|
||
}
|
||
}
|
||
}
|
||
const onContentContainerScroll = (0, _element.useCallback)(e => {
|
||
var _e$currentTarget$scro;
|
||
const scrollY = (_e$currentTarget$scro = e?.currentTarget?.scrollTop) !== null && _e$currentTarget$scro !== void 0 ? _e$currentTarget$scro : -1;
|
||
if (!hasScrolledContent && scrollY > 0) {
|
||
setHasScrolledContent(true);
|
||
} else if (hasScrolledContent && scrollY <= 0) {
|
||
setHasScrolledContent(false);
|
||
}
|
||
}, [hasScrolledContent]);
|
||
let pressTarget = null;
|
||
const overlayPressHandlers = {
|
||
onPointerDown: event => {
|
||
if (event.target === event.currentTarget) {
|
||
pressTarget = event.target;
|
||
// Avoids focus changing so that focus return works as expected.
|
||
event.preventDefault();
|
||
}
|
||
},
|
||
// Closes the modal with two exceptions. 1. Opening the context menu on
|
||
// the overlay. 2. Pressing on the overlay then dragging the pointer
|
||
// over the modal and releasing. Due to the modal being a child of the
|
||
// overlay, such a gesture is a `click` on the overlay and cannot be
|
||
// excepted by a `click` handler. Thus the tactic of handling
|
||
// `pointerup` and comparing its target to that of the `pointerdown`.
|
||
onPointerUp: ({
|
||
target,
|
||
button
|
||
}) => {
|
||
const isSameTarget = target === pressTarget;
|
||
pressTarget = null;
|
||
if (button === 0 && isSameTarget) onRequestClose();
|
||
}
|
||
};
|
||
const modal =
|
||
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
||
(0, _react.createElement)("div", {
|
||
ref: (0, _compose.useMergeRefs)([ref, forwardedRef]),
|
||
className: (0, _classnames.default)('components-modal__screen-overlay', overlayClassName),
|
||
onKeyDown: handleEscapeKeyDown,
|
||
...(shouldCloseOnClickOutside ? overlayPressHandlers : {})
|
||
}, (0, _react.createElement)(_styleProvider.default, {
|
||
document: document
|
||
}, (0, _react.createElement)("div", {
|
||
className: (0, _classnames.default)('components-modal__frame', sizeClass, className),
|
||
style: style,
|
||
ref: (0, _compose.useMergeRefs)([constrainedTabbingRef, focusReturnRef, focusOnMount !== 'firstContentElement' ? focusOnMountRef : null]),
|
||
role: role,
|
||
"aria-label": contentLabel,
|
||
"aria-labelledby": contentLabel ? undefined : headingId,
|
||
"aria-describedby": aria.describedby,
|
||
tabIndex: -1,
|
||
onKeyDown: onKeyDown
|
||
}, (0, _react.createElement)("div", {
|
||
className: (0, _classnames.default)('components-modal__content', {
|
||
'hide-header': __experimentalHideHeader,
|
||
'is-scrollable': hasScrollableContent,
|
||
'has-scrolled-content': hasScrolledContent
|
||
}),
|
||
role: "document",
|
||
onScroll: onContentContainerScroll,
|
||
ref: contentRef,
|
||
"aria-label": hasScrollableContent ? (0, _i18n.__)('Scrollable section') : undefined,
|
||
tabIndex: hasScrollableContent ? 0 : undefined
|
||
}, !__experimentalHideHeader && (0, _react.createElement)("div", {
|
||
className: "components-modal__header"
|
||
}, (0, _react.createElement)("div", {
|
||
className: "components-modal__header-heading-container"
|
||
}, icon && (0, _react.createElement)("span", {
|
||
className: "components-modal__icon-container",
|
||
"aria-hidden": true
|
||
}, icon), title && (0, _react.createElement)("h1", {
|
||
id: headingId,
|
||
className: "components-modal__header-heading"
|
||
}, title)), headerActions, isDismissible && (0, _react.createElement)(_button.default, {
|
||
onClick: onRequestClose,
|
||
icon: _icons.close,
|
||
label: closeButtonLabel || (0, _i18n.__)('Close')
|
||
})), (0, _react.createElement)("div", {
|
||
ref: (0, _compose.useMergeRefs)([childrenContainerRef, focusOnMount === 'firstContentElement' ? focusOnMountRef : null])
|
||
}, children)))));
|
||
return (0, _element.createPortal)((0, _react.createElement)(ModalContext.Provider, {
|
||
value: nestedDismissers.current
|
||
}, modal), document.body);
|
||
}
|
||
|
||
/**
|
||
* Modals give users information and choices related to a task they’re trying to
|
||
* accomplish. They can contain critical information, require decisions, or
|
||
* involve multiple tasks.
|
||
*
|
||
* ```jsx
|
||
* import { Button, Modal } from '@wordpress/components';
|
||
* import { useState } from '@wordpress/element';
|
||
*
|
||
* const MyModal = () => {
|
||
* const [ isOpen, setOpen ] = useState( false );
|
||
* const openModal = () => setOpen( true );
|
||
* const closeModal = () => setOpen( false );
|
||
*
|
||
* return (
|
||
* <>
|
||
* <Button variant="secondary" onClick={ openModal }>
|
||
* Open Modal
|
||
* </Button>
|
||
* { isOpen && (
|
||
* <Modal title="This is my modal" onRequestClose={ closeModal }>
|
||
* <Button variant="secondary" onClick={ closeModal }>
|
||
* My custom close button
|
||
* </Button>
|
||
* </Modal>
|
||
* ) }
|
||
* </>
|
||
* );
|
||
* };
|
||
* ```
|
||
*/
|
||
const Modal = (0, _element.forwardRef)(UnforwardedModal);
|
||
exports.Modal = Modal;
|
||
var _default = Modal;
|
||
exports.default = _default;
|
||
//# sourceMappingURL=index.js.map
|