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>
131 lines
4.9 KiB
JavaScript
131 lines
4.9 KiB
JavaScript
import { createElement, Fragment } from "react";
|
|
/**
|
|
* External dependencies
|
|
*/
|
|
import classnames from 'classnames';
|
|
|
|
/**
|
|
* WordPress dependencies
|
|
*/
|
|
import { useLayoutEffect, useRef, useEffect, useState } from '@wordpress/element';
|
|
import { useAnchor } from '@wordpress/rich-text';
|
|
import { useDebounce, useMergeRefs, useRefEffect } from '@wordpress/compose';
|
|
import { speak } from '@wordpress/a11y';
|
|
import { __, _n, sprintf } from '@wordpress/i18n';
|
|
|
|
/**
|
|
* Internal dependencies
|
|
*/
|
|
import getDefaultUseItems from './get-default-use-items';
|
|
import Button from '../button';
|
|
import Popover from '../popover';
|
|
import { VisuallyHidden } from '../visually-hidden';
|
|
import { createPortal } from 'react-dom';
|
|
export function getAutoCompleterUI(autocompleter) {
|
|
const useItems = autocompleter.useItems ? autocompleter.useItems : getDefaultUseItems(autocompleter);
|
|
function AutocompleterUI({
|
|
filterValue,
|
|
instanceId,
|
|
listBoxId,
|
|
className,
|
|
selectedIndex,
|
|
onChangeOptions,
|
|
onSelect,
|
|
onReset,
|
|
reset,
|
|
contentRef
|
|
}) {
|
|
const [items] = useItems(filterValue);
|
|
const popoverAnchor = useAnchor({
|
|
editableContentElement: contentRef.current
|
|
});
|
|
const [needsA11yCompat, setNeedsA11yCompat] = useState(false);
|
|
const popoverRef = useRef(null);
|
|
const popoverRefs = useMergeRefs([popoverRef, useRefEffect(node => {
|
|
if (!contentRef.current) return;
|
|
|
|
// If the popover is rendered in a different document than
|
|
// the content, we need to duplicate the options list in the
|
|
// content document so that it's available to the screen
|
|
// readers, which check the DOM ID based aira-* attributes.
|
|
setNeedsA11yCompat(node.ownerDocument !== contentRef.current.ownerDocument);
|
|
}, [contentRef])]);
|
|
useOnClickOutside(popoverRef, reset);
|
|
const debouncedSpeak = useDebounce(speak, 500);
|
|
function announce(options) {
|
|
if (!debouncedSpeak) {
|
|
return;
|
|
}
|
|
if (!!options.length) {
|
|
if (filterValue) {
|
|
debouncedSpeak(sprintf( /* translators: %d: number of results. */
|
|
_n('%d result found, use up and down arrow keys to navigate.', '%d results found, use up and down arrow keys to navigate.', options.length), options.length), 'assertive');
|
|
} else {
|
|
debouncedSpeak(sprintf( /* translators: %d: number of results. */
|
|
_n('Initial %d result loaded. Type to filter all available results. Use up and down arrow keys to navigate.', 'Initial %d results loaded. Type to filter all available results. Use up and down arrow keys to navigate.', options.length), options.length), 'assertive');
|
|
}
|
|
} else {
|
|
debouncedSpeak(__('No results.'), 'assertive');
|
|
}
|
|
}
|
|
useLayoutEffect(() => {
|
|
onChangeOptions(items);
|
|
announce(items);
|
|
// Temporarily disabling exhaustive-deps to avoid introducing unexpected side effecst.
|
|
// See https://github.com/WordPress/gutenberg/pull/41820
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [items]);
|
|
if (items.length === 0) {
|
|
return null;
|
|
}
|
|
const ListBox = ({
|
|
Component = 'div'
|
|
}) => createElement(Component, {
|
|
id: listBoxId,
|
|
role: "listbox",
|
|
className: "components-autocomplete__results"
|
|
}, items.map((option, index) => createElement(Button, {
|
|
key: option.key,
|
|
id: `components-autocomplete-item-${instanceId}-${option.key}`,
|
|
role: "option",
|
|
"aria-selected": index === selectedIndex,
|
|
disabled: option.isDisabled,
|
|
className: classnames('components-autocomplete__result', className, {
|
|
'is-selected': index === selectedIndex
|
|
}),
|
|
onClick: () => onSelect(option)
|
|
}, option.label)));
|
|
return createElement(Fragment, null, createElement(Popover, {
|
|
focusOnMount: false,
|
|
onClose: onReset,
|
|
placement: "top-start",
|
|
className: "components-autocomplete__popover",
|
|
anchor: popoverAnchor,
|
|
ref: popoverRefs
|
|
}, createElement(ListBox, null)), contentRef.current && needsA11yCompat && createPortal(createElement(ListBox, {
|
|
Component: VisuallyHidden
|
|
}), contentRef.current.ownerDocument.body));
|
|
}
|
|
return AutocompleterUI;
|
|
}
|
|
function useOnClickOutside(ref, handler) {
|
|
useEffect(() => {
|
|
const listener = event => {
|
|
// Do nothing if clicking ref's element or descendent elements, or if the ref is not referencing an element
|
|
if (!ref.current || ref.current.contains(event.target)) {
|
|
return;
|
|
}
|
|
handler(event);
|
|
};
|
|
document.addEventListener('mousedown', listener);
|
|
document.addEventListener('touchstart', listener);
|
|
return () => {
|
|
document.removeEventListener('mousedown', listener);
|
|
document.removeEventListener('touchstart', listener);
|
|
};
|
|
// Disable reason: `ref` is a ref object and should not be included in a
|
|
// hook's dependency list.
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [handler]);
|
|
}
|
|
//# sourceMappingURL=autocompleter-ui.js.map
|