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>
395 lines
12 KiB
JavaScript
395 lines
12 KiB
JavaScript
import { createElement, Fragment } from "react";
|
|
/**
|
|
* External dependencies
|
|
*/
|
|
import classnames from 'classnames';
|
|
|
|
/**
|
|
* WordPress dependencies
|
|
*/
|
|
import { useState, useRef, useEffect, useCallback, useMemo } from '@wordpress/element';
|
|
import { __, sprintf } from '@wordpress/i18n';
|
|
import { lineSolid, moreVertical, plus } from '@wordpress/icons';
|
|
import { __experimentalUseFocusOutside as useFocusOutside, useDebounce } from '@wordpress/compose';
|
|
|
|
/**
|
|
* Internal dependencies
|
|
*/
|
|
import Button from '../button';
|
|
import { ColorPicker } from '../color-picker';
|
|
import { FlexItem } from '../flex';
|
|
import { HStack } from '../h-stack';
|
|
import { ItemGroup } from '../item-group';
|
|
import { VStack } from '../v-stack';
|
|
import GradientPicker from '../gradient-picker';
|
|
import ColorPalette from '../color-palette';
|
|
import DropdownMenu from '../dropdown-menu';
|
|
import Popover from '../popover';
|
|
import { PaletteActionsContainer, PaletteEditStyles, PaletteHeading, PaletteHStackHeader, IndicatorStyled, PaletteItem, NameContainer, NameInputControl, DoneButton, RemoveButton } from './styles';
|
|
import { NavigableMenu } from '../navigable-container';
|
|
import { DEFAULT_GRADIENT } from '../custom-gradient-picker/constants';
|
|
import CustomGradientPicker from '../custom-gradient-picker';
|
|
import { kebabCase } from '../utils/strings';
|
|
const DEFAULT_COLOR = '#000';
|
|
function NameInput({
|
|
value,
|
|
onChange,
|
|
label
|
|
}) {
|
|
return createElement(NameInputControl, {
|
|
label: label,
|
|
hideLabelFromVision: true,
|
|
value: value,
|
|
onChange: onChange
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Returns a name for a palette item in the format "Color + id".
|
|
* To ensure there are no duplicate ids, this function checks all slugs.
|
|
* It expects slugs to be in the format: slugPrefix + color- + number.
|
|
* It then sets the id component of the new name based on the incremented id of the highest existing slug id.
|
|
*
|
|
* @param elements An array of color palette items.
|
|
* @param slugPrefix The slug prefix used to match the element slug.
|
|
*
|
|
* @return A unique name for a palette item.
|
|
*/
|
|
export function getNameForPosition(elements, slugPrefix) {
|
|
const nameRegex = new RegExp(`^${slugPrefix}color-([\\d]+)$`);
|
|
const position = elements.reduce((previousValue, currentValue) => {
|
|
if (typeof currentValue?.slug === 'string') {
|
|
const matches = currentValue?.slug.match(nameRegex);
|
|
if (matches) {
|
|
const id = parseInt(matches[1], 10);
|
|
if (id >= previousValue) {
|
|
return id + 1;
|
|
}
|
|
}
|
|
}
|
|
return previousValue;
|
|
}, 1);
|
|
return sprintf( /* translators: %s: is an id for a custom color */
|
|
__('Color %s'), position);
|
|
}
|
|
function ColorPickerPopover({
|
|
isGradient,
|
|
element,
|
|
onChange,
|
|
popoverProps: receivedPopoverProps,
|
|
onClose = () => {}
|
|
}) {
|
|
const popoverProps = useMemo(() => ({
|
|
shift: true,
|
|
offset: 20,
|
|
// Disabling resize as it would otherwise cause the popover to show
|
|
// scrollbars while dragging the color picker's handle close to the
|
|
// popover edge.
|
|
resize: false,
|
|
placement: 'left-start',
|
|
...receivedPopoverProps,
|
|
className: classnames('components-palette-edit__popover', receivedPopoverProps?.className)
|
|
}), [receivedPopoverProps]);
|
|
return createElement(Popover, {
|
|
...popoverProps,
|
|
onClose: onClose
|
|
}, !isGradient && createElement(ColorPicker, {
|
|
color: element.color,
|
|
enableAlpha: true,
|
|
onChange: newColor => {
|
|
onChange({
|
|
...element,
|
|
color: newColor
|
|
});
|
|
}
|
|
}), isGradient && createElement("div", {
|
|
className: "components-palette-edit__popover-gradient-picker"
|
|
}, createElement(CustomGradientPicker, {
|
|
__nextHasNoMargin: true,
|
|
__experimentalIsRenderedInSidebar: true,
|
|
value: element.gradient,
|
|
onChange: newGradient => {
|
|
onChange({
|
|
...element,
|
|
gradient: newGradient
|
|
});
|
|
}
|
|
})));
|
|
}
|
|
function Option({
|
|
canOnlyChangeValues,
|
|
element,
|
|
onChange,
|
|
isEditing,
|
|
onStartEditing,
|
|
onRemove,
|
|
onStopEditing,
|
|
popoverProps: receivedPopoverProps,
|
|
slugPrefix,
|
|
isGradient
|
|
}) {
|
|
const focusOutsideProps = useFocusOutside(onStopEditing);
|
|
const value = isGradient ? element.gradient : element.color;
|
|
|
|
// Use internal state instead of a ref to make sure that the component
|
|
// re-renders when the popover's anchor updates.
|
|
const [popoverAnchor, setPopoverAnchor] = useState(null);
|
|
const popoverProps = useMemo(() => ({
|
|
...receivedPopoverProps,
|
|
// Use the custom palette color item as the popover anchor.
|
|
anchor: popoverAnchor
|
|
}), [popoverAnchor, receivedPopoverProps]);
|
|
return createElement(PaletteItem, {
|
|
className: isEditing ? 'is-selected' : undefined,
|
|
as: "div",
|
|
onClick: onStartEditing,
|
|
ref: setPopoverAnchor,
|
|
...(isEditing ? {
|
|
...focusOutsideProps
|
|
} : {
|
|
style: {
|
|
cursor: 'pointer'
|
|
}
|
|
})
|
|
}, createElement(HStack, {
|
|
justify: "flex-start"
|
|
}, createElement(FlexItem, null, createElement(IndicatorStyled, {
|
|
style: {
|
|
background: value,
|
|
color: 'transparent'
|
|
}
|
|
})), createElement(FlexItem, null, isEditing && !canOnlyChangeValues ? createElement(NameInput, {
|
|
label: isGradient ? __('Gradient name') : __('Color name'),
|
|
value: element.name,
|
|
onChange: nextName => onChange({
|
|
...element,
|
|
name: nextName,
|
|
slug: slugPrefix + kebabCase(nextName !== null && nextName !== void 0 ? nextName : '')
|
|
})
|
|
}) : createElement(NameContainer, null, element.name)), isEditing && !canOnlyChangeValues && createElement(FlexItem, null, createElement(RemoveButton, {
|
|
size: "small",
|
|
icon: lineSolid,
|
|
label: __('Remove color'),
|
|
onClick: onRemove
|
|
}))), isEditing && createElement(ColorPickerPopover, {
|
|
isGradient: isGradient,
|
|
onChange: onChange,
|
|
element: element,
|
|
popoverProps: popoverProps
|
|
}));
|
|
}
|
|
function PaletteEditListView({
|
|
elements,
|
|
onChange,
|
|
editingElement,
|
|
setEditingElement,
|
|
canOnlyChangeValues,
|
|
slugPrefix,
|
|
isGradient,
|
|
popoverProps
|
|
}) {
|
|
// When unmounting the component if there are empty elements (the user did not complete the insertion) clean them.
|
|
const elementsReference = useRef();
|
|
useEffect(() => {
|
|
elementsReference.current = elements;
|
|
}, [elements]);
|
|
const debounceOnChange = useDebounce(onChange, 100);
|
|
return createElement(VStack, {
|
|
spacing: 3
|
|
}, createElement(ItemGroup, {
|
|
isRounded: true
|
|
}, elements.map((element, index) => createElement(Option, {
|
|
isGradient: isGradient,
|
|
canOnlyChangeValues: canOnlyChangeValues,
|
|
key: index,
|
|
element: element,
|
|
onStartEditing: () => {
|
|
if (editingElement !== index) {
|
|
setEditingElement(index);
|
|
}
|
|
},
|
|
onChange: newElement => {
|
|
debounceOnChange(elements.map((currentElement, currentIndex) => {
|
|
if (currentIndex === index) {
|
|
return newElement;
|
|
}
|
|
return currentElement;
|
|
}));
|
|
},
|
|
onRemove: () => {
|
|
setEditingElement(null);
|
|
const newElements = elements.filter((_currentElement, currentIndex) => {
|
|
if (currentIndex === index) {
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
onChange(newElements.length ? newElements : undefined);
|
|
},
|
|
isEditing: index === editingElement,
|
|
onStopEditing: () => {
|
|
if (index === editingElement) {
|
|
setEditingElement(null);
|
|
}
|
|
},
|
|
slugPrefix: slugPrefix,
|
|
popoverProps: popoverProps
|
|
}))));
|
|
}
|
|
const EMPTY_ARRAY = [];
|
|
|
|
/**
|
|
* Allows editing a palette of colors or gradients.
|
|
*
|
|
* ```jsx
|
|
* import { PaletteEdit } from '@wordpress/components';
|
|
* const MyPaletteEdit = () => {
|
|
* const [ controlledColors, setControlledColors ] = useState( colors );
|
|
*
|
|
* return (
|
|
* <PaletteEdit
|
|
* colors={ controlledColors }
|
|
* onChange={ ( newColors?: Color[] ) => {
|
|
* setControlledColors( newColors );
|
|
* } }
|
|
* paletteLabel="Here is a label"
|
|
* />
|
|
* );
|
|
* };
|
|
* ```
|
|
*/
|
|
export function PaletteEdit({
|
|
gradients,
|
|
colors = EMPTY_ARRAY,
|
|
onChange,
|
|
paletteLabel,
|
|
paletteLabelHeadingLevel = 2,
|
|
emptyMessage,
|
|
canOnlyChangeValues,
|
|
canReset,
|
|
slugPrefix = '',
|
|
popoverProps
|
|
}) {
|
|
const isGradient = !!gradients;
|
|
const elements = isGradient ? gradients : colors;
|
|
const [isEditing, setIsEditing] = useState(false);
|
|
const [editingElement, setEditingElement] = useState(null);
|
|
const isAdding = isEditing && !!editingElement && elements[editingElement] && !elements[editingElement].slug;
|
|
const elementsLength = elements.length;
|
|
const hasElements = elementsLength > 0;
|
|
const debounceOnChange = useDebounce(onChange, 100);
|
|
const onSelectPaletteItem = useCallback((value, newEditingElementIndex) => {
|
|
const selectedElement = newEditingElementIndex === undefined ? undefined : elements[newEditingElementIndex];
|
|
const key = isGradient ? 'gradient' : 'color';
|
|
// Ensures that the index returned matches a known element value.
|
|
if (!!selectedElement && selectedElement[key] === value) {
|
|
setEditingElement(newEditingElementIndex);
|
|
} else {
|
|
setIsEditing(true);
|
|
}
|
|
}, [isGradient, elements]);
|
|
return createElement(PaletteEditStyles, null, createElement(PaletteHStackHeader, null, createElement(PaletteHeading, {
|
|
level: paletteLabelHeadingLevel
|
|
}, paletteLabel), createElement(PaletteActionsContainer, null, hasElements && isEditing && createElement(DoneButton, {
|
|
size: "small",
|
|
onClick: () => {
|
|
setIsEditing(false);
|
|
setEditingElement(null);
|
|
}
|
|
}, __('Done')), !canOnlyChangeValues && createElement(Button, {
|
|
size: "small",
|
|
isPressed: isAdding,
|
|
icon: plus,
|
|
label: isGradient ? __('Add gradient') : __('Add color'),
|
|
onClick: () => {
|
|
const optionName = getNameForPosition(elements, slugPrefix);
|
|
if (!!gradients) {
|
|
onChange([...gradients, {
|
|
gradient: DEFAULT_GRADIENT,
|
|
name: optionName,
|
|
slug: slugPrefix + kebabCase(optionName)
|
|
}]);
|
|
} else {
|
|
onChange([...colors, {
|
|
color: DEFAULT_COLOR,
|
|
name: optionName,
|
|
slug: slugPrefix + kebabCase(optionName)
|
|
}]);
|
|
}
|
|
setIsEditing(true);
|
|
setEditingElement(elements.length);
|
|
}
|
|
}), hasElements && (!isEditing || !canOnlyChangeValues || canReset) && createElement(DropdownMenu, {
|
|
icon: moreVertical,
|
|
label: isGradient ? __('Gradient options') : __('Color options'),
|
|
toggleProps: {
|
|
isSmall: true
|
|
}
|
|
}, ({
|
|
onClose
|
|
}) => createElement(Fragment, null, createElement(NavigableMenu, {
|
|
role: "menu"
|
|
}, !isEditing && createElement(Button, {
|
|
variant: "tertiary",
|
|
onClick: () => {
|
|
setIsEditing(true);
|
|
onClose();
|
|
},
|
|
className: "components-palette-edit__menu-button"
|
|
}, __('Show details')), !canOnlyChangeValues && createElement(Button, {
|
|
variant: "tertiary",
|
|
onClick: () => {
|
|
setEditingElement(null);
|
|
setIsEditing(false);
|
|
onChange();
|
|
onClose();
|
|
},
|
|
className: "components-palette-edit__menu-button"
|
|
}, isGradient ? __('Remove all gradients') : __('Remove all colors')), canReset && createElement(Button, {
|
|
variant: "tertiary",
|
|
onClick: () => {
|
|
setEditingElement(null);
|
|
onChange();
|
|
onClose();
|
|
}
|
|
}, isGradient ? __('Reset gradient') : __('Reset colors'))))))), hasElements && createElement(Fragment, null, isEditing && createElement(PaletteEditListView, {
|
|
canOnlyChangeValues: canOnlyChangeValues,
|
|
elements: elements
|
|
// @ts-expect-error TODO: Don't know how to resolve
|
|
,
|
|
onChange: onChange,
|
|
editingElement: editingElement,
|
|
setEditingElement: setEditingElement,
|
|
slugPrefix: slugPrefix,
|
|
isGradient: isGradient,
|
|
popoverProps: popoverProps
|
|
}), !isEditing && editingElement !== null && createElement(ColorPickerPopover, {
|
|
isGradient: isGradient,
|
|
onClose: () => setEditingElement(null),
|
|
onChange: newElement => {
|
|
debounceOnChange(
|
|
// @ts-expect-error TODO: Don't know how to resolve
|
|
elements.map((currentElement, currentIndex) => {
|
|
if (currentIndex === editingElement) {
|
|
return newElement;
|
|
}
|
|
return currentElement;
|
|
}));
|
|
},
|
|
element: elements[editingElement !== null && editingElement !== void 0 ? editingElement : -1],
|
|
popoverProps: popoverProps
|
|
}), !isEditing && (isGradient ? createElement(GradientPicker, {
|
|
__nextHasNoMargin: true,
|
|
gradients: gradients,
|
|
onChange: onSelectPaletteItem,
|
|
clearable: false,
|
|
disableCustomGradients: true
|
|
}) : createElement(ColorPalette, {
|
|
colors: colors,
|
|
onChange: onSelectPaletteItem,
|
|
clearable: false,
|
|
disableCustomColors: true
|
|
}))), !hasElements && emptyMessage);
|
|
}
|
|
export default PaletteEdit;
|
|
//# sourceMappingURL=index.js.map
|