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>
300 lines
11 KiB
JavaScript
300 lines
11 KiB
JavaScript
"use strict";
|
|
|
|
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
exports.default = void 0;
|
|
var _react = require("react");
|
|
var _classnames = _interopRequireDefault(require("classnames"));
|
|
var _i18n = require("@wordpress/i18n");
|
|
var _element = require("@wordpress/element");
|
|
var _compose = require("@wordpress/compose");
|
|
var _a11y = require("@wordpress/a11y");
|
|
var _icons = require("@wordpress/icons");
|
|
var _styles = require("./styles");
|
|
var _tokenInput = _interopRequireDefault(require("../form-token-field/token-input"));
|
|
var _suggestionsList = _interopRequireDefault(require("../form-token-field/suggestions-list"));
|
|
var _baseControl = _interopRequireDefault(require("../base-control"));
|
|
var _button = _interopRequireDefault(require("../button"));
|
|
var _flex = require("../flex");
|
|
var _withFocusOutside = _interopRequireDefault(require("../higher-order/with-focus-outside"));
|
|
var _hooks = require("../utils/hooks");
|
|
var _strings = require("../utils/strings");
|
|
var _useDeprecatedProps = require("../utils/use-deprecated-props");
|
|
/**
|
|
* External dependencies
|
|
*/
|
|
|
|
/**
|
|
* WordPress dependencies
|
|
*/
|
|
|
|
/**
|
|
* Internal dependencies
|
|
*/
|
|
|
|
const noop = () => {};
|
|
const DetectOutside = (0, _withFocusOutside.default)(class extends _element.Component {
|
|
handleFocusOutside(event) {
|
|
this.props.onFocusOutside(event);
|
|
}
|
|
render() {
|
|
return this.props.children;
|
|
}
|
|
});
|
|
const getIndexOfMatchingSuggestion = (selectedSuggestion, matchingSuggestions) => selectedSuggestion === null ? -1 : matchingSuggestions.indexOf(selectedSuggestion);
|
|
|
|
/**
|
|
* `ComboboxControl` is an enhanced version of a [`SelectControl`](../select-control/README.md) with the addition of
|
|
* being able to search for options using a search input.
|
|
*
|
|
* ```jsx
|
|
* import { ComboboxControl } from '@wordpress/components';
|
|
* import { useState } from '@wordpress/element';
|
|
*
|
|
* const options = [
|
|
* {
|
|
* value: 'small',
|
|
* label: 'Small',
|
|
* },
|
|
* {
|
|
* value: 'normal',
|
|
* label: 'Normal',
|
|
* },
|
|
* {
|
|
* value: 'large',
|
|
* label: 'Large',
|
|
* },
|
|
* ];
|
|
*
|
|
* function MyComboboxControl() {
|
|
* const [ fontSize, setFontSize ] = useState();
|
|
* const [ filteredOptions, setFilteredOptions ] = useState( options );
|
|
* return (
|
|
* <ComboboxControl
|
|
* label="Font Size"
|
|
* value={ fontSize }
|
|
* onChange={ setFontSize }
|
|
* options={ filteredOptions }
|
|
* onFilterValueChange={ ( inputValue ) =>
|
|
* setFilteredOptions(
|
|
* options.filter( ( option ) =>
|
|
* option.label
|
|
* .toLowerCase()
|
|
* .startsWith( inputValue.toLowerCase() )
|
|
* )
|
|
* )
|
|
* }
|
|
* />
|
|
* );
|
|
* }
|
|
* ```
|
|
*/
|
|
function ComboboxControl(props) {
|
|
var _currentOption$label;
|
|
const {
|
|
__nextHasNoMarginBottom = false,
|
|
__next40pxDefaultSize = false,
|
|
value: valueProp,
|
|
label,
|
|
options,
|
|
onChange: onChangeProp,
|
|
onFilterValueChange = noop,
|
|
hideLabelFromVision,
|
|
help,
|
|
allowReset = true,
|
|
className,
|
|
messages = {
|
|
selected: (0, _i18n.__)('Item selected.')
|
|
},
|
|
__experimentalRenderItem
|
|
} = (0, _useDeprecatedProps.useDeprecated36pxDefaultSizeProp)(props, 'wp.components.ComboboxControl');
|
|
const [value, setValue] = (0, _hooks.useControlledValue)({
|
|
value: valueProp,
|
|
onChange: onChangeProp
|
|
});
|
|
const currentOption = options.find(option => option.value === value);
|
|
const currentLabel = (_currentOption$label = currentOption?.label) !== null && _currentOption$label !== void 0 ? _currentOption$label : '';
|
|
// Use a custom prefix when generating the `instanceId` to avoid having
|
|
// duplicate input IDs when rendering this component and `FormTokenField`
|
|
// in the same page (see https://github.com/WordPress/gutenberg/issues/42112).
|
|
const instanceId = (0, _compose.useInstanceId)(ComboboxControl, 'combobox-control');
|
|
const [selectedSuggestion, setSelectedSuggestion] = (0, _element.useState)(currentOption || null);
|
|
const [isExpanded, setIsExpanded] = (0, _element.useState)(false);
|
|
const [inputHasFocus, setInputHasFocus] = (0, _element.useState)(false);
|
|
const [inputValue, setInputValue] = (0, _element.useState)('');
|
|
const inputContainer = (0, _element.useRef)(null);
|
|
const matchingSuggestions = (0, _element.useMemo)(() => {
|
|
const startsWithMatch = [];
|
|
const containsMatch = [];
|
|
const match = (0, _strings.normalizeTextString)(inputValue);
|
|
options.forEach(option => {
|
|
const index = (0, _strings.normalizeTextString)(option.label).indexOf(match);
|
|
if (index === 0) {
|
|
startsWithMatch.push(option);
|
|
} else if (index > 0) {
|
|
containsMatch.push(option);
|
|
}
|
|
});
|
|
return startsWithMatch.concat(containsMatch);
|
|
}, [inputValue, options]);
|
|
const onSuggestionSelected = newSelectedSuggestion => {
|
|
setValue(newSelectedSuggestion.value);
|
|
(0, _a11y.speak)(messages.selected, 'assertive');
|
|
setSelectedSuggestion(newSelectedSuggestion);
|
|
setInputValue('');
|
|
setIsExpanded(false);
|
|
};
|
|
const handleArrowNavigation = (offset = 1) => {
|
|
const index = getIndexOfMatchingSuggestion(selectedSuggestion, matchingSuggestions);
|
|
let nextIndex = index + offset;
|
|
if (nextIndex < 0) {
|
|
nextIndex = matchingSuggestions.length - 1;
|
|
} else if (nextIndex >= matchingSuggestions.length) {
|
|
nextIndex = 0;
|
|
}
|
|
setSelectedSuggestion(matchingSuggestions[nextIndex]);
|
|
setIsExpanded(true);
|
|
};
|
|
const onKeyDown = event => {
|
|
let preventDefault = false;
|
|
if (event.defaultPrevented ||
|
|
// 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;
|
|
}
|
|
switch (event.code) {
|
|
case 'Enter':
|
|
if (selectedSuggestion) {
|
|
onSuggestionSelected(selectedSuggestion);
|
|
preventDefault = true;
|
|
}
|
|
break;
|
|
case 'ArrowUp':
|
|
handleArrowNavigation(-1);
|
|
preventDefault = true;
|
|
break;
|
|
case 'ArrowDown':
|
|
handleArrowNavigation(1);
|
|
preventDefault = true;
|
|
break;
|
|
case 'Escape':
|
|
setIsExpanded(false);
|
|
setSelectedSuggestion(null);
|
|
preventDefault = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (preventDefault) {
|
|
event.preventDefault();
|
|
}
|
|
};
|
|
const onBlur = () => {
|
|
setInputHasFocus(false);
|
|
};
|
|
const onFocus = () => {
|
|
setInputHasFocus(true);
|
|
setIsExpanded(true);
|
|
onFilterValueChange('');
|
|
setInputValue('');
|
|
};
|
|
const onFocusOutside = () => {
|
|
setIsExpanded(false);
|
|
};
|
|
const onInputChange = event => {
|
|
const text = event.value;
|
|
setInputValue(text);
|
|
onFilterValueChange(text);
|
|
if (inputHasFocus) {
|
|
setIsExpanded(true);
|
|
}
|
|
};
|
|
const handleOnReset = () => {
|
|
setValue(null);
|
|
inputContainer.current?.focus();
|
|
};
|
|
|
|
// Update current selections when the filter input changes.
|
|
(0, _element.useEffect)(() => {
|
|
const hasMatchingSuggestions = matchingSuggestions.length > 0;
|
|
const hasSelectedMatchingSuggestions = getIndexOfMatchingSuggestion(selectedSuggestion, matchingSuggestions) > 0;
|
|
if (hasMatchingSuggestions && !hasSelectedMatchingSuggestions) {
|
|
// If the current selection isn't present in the list of suggestions, then automatically select the first item from the list of suggestions.
|
|
setSelectedSuggestion(matchingSuggestions[0]);
|
|
}
|
|
}, [matchingSuggestions, selectedSuggestion]);
|
|
|
|
// Announcements.
|
|
(0, _element.useEffect)(() => {
|
|
const hasMatchingSuggestions = matchingSuggestions.length > 0;
|
|
if (isExpanded) {
|
|
const message = hasMatchingSuggestions ? (0, _i18n.sprintf)( /* translators: %d: number of results. */
|
|
(0, _i18n._n)('%d result found, use up and down arrow keys to navigate.', '%d results found, use up and down arrow keys to navigate.', matchingSuggestions.length), matchingSuggestions.length) : (0, _i18n.__)('No results.');
|
|
(0, _a11y.speak)(message, 'polite');
|
|
}
|
|
}, [matchingSuggestions, isExpanded]);
|
|
|
|
// Disable reason: There is no appropriate role which describes the
|
|
// input container intended accessible usability.
|
|
// TODO: Refactor click detection to use blur to stop propagation.
|
|
/* eslint-disable jsx-a11y/no-static-element-interactions */
|
|
return (0, _react.createElement)(DetectOutside, {
|
|
onFocusOutside: onFocusOutside
|
|
}, (0, _react.createElement)(_baseControl.default, {
|
|
__nextHasNoMarginBottom: __nextHasNoMarginBottom,
|
|
className: (0, _classnames.default)(className, 'components-combobox-control'),
|
|
label: label,
|
|
id: `components-form-token-input-${instanceId}`,
|
|
hideLabelFromVision: hideLabelFromVision,
|
|
help: help
|
|
}, (0, _react.createElement)("div", {
|
|
className: "components-combobox-control__suggestions-container",
|
|
tabIndex: -1,
|
|
onKeyDown: onKeyDown
|
|
}, (0, _react.createElement)(_styles.InputWrapperFlex, {
|
|
__next40pxDefaultSize: __next40pxDefaultSize
|
|
}, (0, _react.createElement)(_flex.FlexBlock, null, (0, _react.createElement)(_tokenInput.default, {
|
|
className: "components-combobox-control__input",
|
|
instanceId: instanceId,
|
|
ref: inputContainer,
|
|
value: isExpanded ? inputValue : currentLabel,
|
|
onFocus: onFocus,
|
|
onBlur: onBlur,
|
|
isExpanded: isExpanded,
|
|
selectedSuggestionIndex: getIndexOfMatchingSuggestion(selectedSuggestion, matchingSuggestions),
|
|
onChange: onInputChange
|
|
})), allowReset && (0, _react.createElement)(_flex.FlexItem, null, (0, _react.createElement)(_button.default, {
|
|
className: "components-combobox-control__reset",
|
|
icon: _icons.closeSmall,
|
|
disabled: !value,
|
|
onClick: handleOnReset,
|
|
label: (0, _i18n.__)('Reset')
|
|
}))), isExpanded && (0, _react.createElement)(_suggestionsList.default, {
|
|
instanceId: instanceId
|
|
// The empty string for `value` here is not actually used, but is
|
|
// just a quick way to satisfy the TypeScript requirements of SuggestionsList.
|
|
// See: https://github.com/WordPress/gutenberg/pull/47581/files#r1091089330
|
|
,
|
|
match: {
|
|
label: inputValue,
|
|
value: ''
|
|
},
|
|
displayTransform: suggestion => suggestion.label,
|
|
suggestions: matchingSuggestions,
|
|
selectedIndex: getIndexOfMatchingSuggestion(selectedSuggestion, matchingSuggestions),
|
|
onHover: setSelectedSuggestion,
|
|
onSelect: onSuggestionSelected,
|
|
scrollIntoView: true,
|
|
__experimentalRenderItem: __experimentalRenderItem
|
|
}))));
|
|
/* eslint-enable jsx-a11y/no-static-element-interactions */
|
|
}
|
|
var _default = ComboboxControl;
|
|
exports.default = _default;
|
|
//# sourceMappingURL=index.js.map
|