fix: prevent asset conflicts between React and Grid.js versions

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>
This commit is contained in:
dwindown
2026-04-18 17:02:14 +07:00
parent bd9cdac02e
commit e8fbfb14c1
74973 changed files with 6658406 additions and 71 deletions

View File

@@ -0,0 +1,225 @@
import { createElement, Fragment } from "react";
/**
* External dependencies
*/
/**
* WordPress dependencies
*/
import { throttle } from '@wordpress/compose';
import { useEffect, useRef } from '@wordpress/element';
/**
* Internal dependencies
*/
const dragImageClass = 'components-draggable__invisible-drag-image';
const cloneWrapperClass = 'components-draggable__clone';
const clonePadding = 0;
const bodyClass = 'is-dragging-components-draggable';
/**
* `Draggable` is a Component that provides a way to set up a cross-browser
* (including IE) customizable drag image and the transfer data for the drag
* event. It decouples the drag handle and the element to drag: use it by
* wrapping the component that will become the drag handle and providing the DOM
* ID of the element to drag.
*
* Note that the drag handle needs to declare the `draggable="true"` property
* and bind the `Draggable`s `onDraggableStart` and `onDraggableEnd` event
* handlers to its own `onDragStart` and `onDragEnd` respectively. `Draggable`
* takes care of the logic to setup the drag image and the transfer data, but is
* not concerned with creating an actual DOM element that is draggable.
*
* ```jsx
* import { Draggable, Panel, PanelBody } from '@wordpress/components';
* import { Icon, more } from '@wordpress/icons';
*
* const MyDraggable = () => (
* <div id="draggable-panel">
* <Panel header="Draggable panel">
* <PanelBody>
* <Draggable elementId="draggable-panel" transferData={ {} }>
* { ( { onDraggableStart, onDraggableEnd } ) => (
* <div
* className="example-drag-handle"
* draggable
* onDragStart={ onDraggableStart }
* onDragEnd={ onDraggableEnd }
* >
* <Icon icon={ more } />
* </div>
* ) }
* </Draggable>
* </PanelBody>
* </Panel>
* </div>
* );
* ```
*/
export function Draggable({
children,
onDragStart,
onDragOver,
onDragEnd,
appendToOwnerDocument = false,
cloneClassname,
elementId,
transferData,
__experimentalTransferDataType: transferDataType = 'text',
__experimentalDragComponent: dragComponent
}) {
const dragComponentRef = useRef(null);
const cleanup = useRef(() => {});
/**
* Removes the element clone, resets cursor, and removes drag listener.
*
* @param event The non-custom DragEvent.
*/
function end(event) {
event.preventDefault();
cleanup.current();
if (onDragEnd) {
onDragEnd(event);
}
}
/**
* This method does a couple of things:
*
* - Clones the current element and spawns clone over original element.
* - Adds a fake temporary drag image to avoid browser defaults.
* - Sets transfer data.
* - Adds dragover listener.
*
* @param event The non-custom DragEvent.
*/
function start(event) {
const {
ownerDocument
} = event.target;
event.dataTransfer.setData(transferDataType, JSON.stringify(transferData));
const cloneWrapper = ownerDocument.createElement('div');
// Reset position to 0,0. Natural stacking order will position this lower, even with a transform otherwise.
cloneWrapper.style.top = '0';
cloneWrapper.style.left = '0';
const dragImage = ownerDocument.createElement('div');
// Set a fake drag image to avoid browser defaults. Remove from DOM
// right after. event.dataTransfer.setDragImage is not supported yet in
// IE, we need to check for its existence first.
if ('function' === typeof event.dataTransfer.setDragImage) {
dragImage.classList.add(dragImageClass);
ownerDocument.body.appendChild(dragImage);
event.dataTransfer.setDragImage(dragImage, 0, 0);
}
cloneWrapper.classList.add(cloneWrapperClass);
if (cloneClassname) {
cloneWrapper.classList.add(cloneClassname);
}
let x = 0;
let y = 0;
// If a dragComponent is defined, the following logic will clone the
// HTML node and inject it into the cloneWrapper.
if (dragComponentRef.current) {
// Position dragComponent at the same position as the cursor.
x = event.clientX;
y = event.clientY;
cloneWrapper.style.transform = `translate( ${x}px, ${y}px )`;
const clonedDragComponent = ownerDocument.createElement('div');
clonedDragComponent.innerHTML = dragComponentRef.current.innerHTML;
cloneWrapper.appendChild(clonedDragComponent);
// Inject the cloneWrapper into the DOM.
ownerDocument.body.appendChild(cloneWrapper);
} else {
const element = ownerDocument.getElementById(elementId);
// Prepare element clone and append to element wrapper.
const elementRect = element.getBoundingClientRect();
const elementWrapper = element.parentNode;
const elementTopOffset = elementRect.top;
const elementLeftOffset = elementRect.left;
cloneWrapper.style.width = `${elementRect.width + clonePadding * 2}px`;
const clone = element.cloneNode(true);
clone.id = `clone-${elementId}`;
// Position clone right over the original element (20px padding).
x = elementLeftOffset - clonePadding;
y = elementTopOffset - clonePadding;
cloneWrapper.style.transform = `translate( ${x}px, ${y}px )`;
// Hack: Remove iFrames as it's causing the embeds drag clone to freeze.
Array.from(clone.querySelectorAll('iframe')).forEach(child => child.parentNode?.removeChild(child));
cloneWrapper.appendChild(clone);
// Inject the cloneWrapper into the DOM.
if (appendToOwnerDocument) {
ownerDocument.body.appendChild(cloneWrapper);
} else {
elementWrapper?.appendChild(cloneWrapper);
}
}
// Mark the current cursor coordinates.
let cursorLeft = event.clientX;
let cursorTop = event.clientY;
function over(e) {
// Skip doing any work if mouse has not moved.
if (cursorLeft === e.clientX && cursorTop === e.clientY) {
return;
}
const nextX = x + e.clientX - cursorLeft;
const nextY = y + e.clientY - cursorTop;
cloneWrapper.style.transform = `translate( ${nextX}px, ${nextY}px )`;
cursorLeft = e.clientX;
cursorTop = e.clientY;
x = nextX;
y = nextY;
if (onDragOver) {
onDragOver(e);
}
}
// Aim for 60fps (16 ms per frame) for now. We can potentially use requestAnimationFrame (raf) instead,
// note that browsers may throttle raf below 60fps in certain conditions.
// @ts-ignore
const throttledDragOver = throttle(over, 16);
ownerDocument.addEventListener('dragover', throttledDragOver);
// Update cursor to 'grabbing', document wide.
ownerDocument.body.classList.add(bodyClass);
if (onDragStart) {
onDragStart(event);
}
cleanup.current = () => {
// Remove drag clone.
if (cloneWrapper && cloneWrapper.parentNode) {
cloneWrapper.parentNode.removeChild(cloneWrapper);
}
if (dragImage && dragImage.parentNode) {
dragImage.parentNode.removeChild(dragImage);
}
// Reset cursor.
ownerDocument.body.classList.remove(bodyClass);
ownerDocument.removeEventListener('dragover', throttledDragOver);
};
}
useEffect(() => () => {
cleanup.current();
}, []);
return createElement(Fragment, null, children({
onDraggableStart: start,
onDraggableEnd: end
}), dragComponent && createElement("div", {
className: "components-draggable-drag-component-root",
style: {
display: 'none'
},
ref: dragComponentRef
}, dragComponent));
}
export default Draggable;
//# sourceMappingURL=index.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,202 @@
import { createElement } from "react";
/**
* External dependencies
*/
import { Gesture, GestureDetector, LongPressGestureHandler } from 'react-native-gesture-handler';
import Animated, { useSharedValue, runOnJS, useAnimatedReaction, useAnimatedGestureHandler } from 'react-native-reanimated';
/**
* WordPress dependencies
*/
import { createContext, useContext, useRef, useMemo } from '@wordpress/element';
/**
* Internal dependencies
*/
import styles from './style.scss';
const Context = createContext({});
const {
Provider
} = Context;
/**
* Draggable component.
*
* @param {Object} props Component props.
* @param {JSX.Element} props.children Children to be rendered.
* @param {Function} [props.onDragEnd] Callback when dragging ends.
* @param {Function} [props.onDragOver] Callback when dragging happens over an element.
* @param {Function} [props.onDragStart] Callback when dragging starts.
* @param {string} [props.testID] Id used for querying the pan gesture in tests.
*
* @return {JSX.Element} The component to be rendered.
*/
const Draggable = ({
children,
onDragEnd,
onDragOver,
onDragStart,
testID
}) => {
const isDragging = useSharedValue(false);
const isPanActive = useSharedValue(false);
const draggingId = useSharedValue('');
const panGestureRef = useRef();
const currentFirstTouchId = useSharedValue(null);
const initialPosition = {
x: useSharedValue(0),
y: useSharedValue(0)
};
const lastPosition = {
x: useSharedValue(0),
y: useSharedValue(0)
};
useAnimatedReaction(() => isDragging.value, (result, previous) => {
if (result === previous || previous === null) {
return;
}
if (result) {
if (onDragStart) {
onDragStart({
x: initialPosition.x.value,
y: initialPosition.y.value,
id: draggingId.value
});
}
} else if (onDragEnd) {
onDragEnd({
x: lastPosition.x.value,
y: lastPosition.y.value,
id: draggingId.value
});
}
});
function getFirstTouchEvent(event) {
'worklet';
return event.allTouches.find(touch => touch.id === currentFirstTouchId.value);
}
const panGesture = Gesture.Pan().manualActivation(true).onTouchesDown(event => {
if (!currentFirstTouchId.value) {
const firstEvent = event.allTouches[0];
const {
x = 0,
y = 0
} = firstEvent;
currentFirstTouchId.value = firstEvent.id;
initialPosition.x.value = x;
initialPosition.y.value = y;
}
}).onTouchesMove((event, state) => {
if (!isPanActive.value && isDragging.value) {
isPanActive.value = true;
state.activate();
}
if (isPanActive.value && isDragging.value) {
const firstEvent = getFirstTouchEvent(event);
if (!firstEvent) {
state.end();
return;
}
lastPosition.x.value = firstEvent.x;
lastPosition.y.value = firstEvent.y;
if (onDragOver) {
onDragOver(firstEvent);
}
}
}).onTouchesCancelled((_event, state) => {
state.end();
}).onEnd(() => {
currentFirstTouchId.value = null;
isPanActive.value = false;
isDragging.value = false;
}).withRef(panGestureRef).shouldCancelWhenOutside(false).withTestId(testID);
const providerValue = useMemo(() => {
return {
panGestureRef,
isDragging,
isPanActive,
draggingId
};
}, [
// `isDragging`, `isPanActive` and `draggingId` are created using the
// `useSharedValue` hook provided by the `react-native-reanimated`, which in
// theory should guarantee that the value of these variables remains stable.
// ESLint can't pick this up, and that's why they have to be specified as
// dependencies for this hook call.
isDragging, isPanActive, draggingId]);
return createElement(GestureDetector, {
gesture: panGesture
}, createElement(Animated.View, {
style: styles.draggable__container
}, createElement(Provider, {
value: providerValue
}, children)));
};
/**
* Draggable trigger component.
*
* This component acts as the trigger for the dragging functionality.
*
* @param {Object} props Component props.
* @param {JSX.Element} props.children Children to be rendered.
* @param {*} props.id Identifier passed within the event callbacks.
* @param {boolean} [props.enabled] Enables the long-press gesture.
* @param {number} [props.maxDistance] Maximum distance, that defines how far the finger is allowed to travel during a long press gesture.
* @param {number} [props.minDuration] Minimum time, that a finger must remain pressed on the corresponding view.
* @param {Function} [props.onLongPress] Callback when long-press gesture is triggered over an element.
* @param {Function} [props.onLongPressEnd] Callback when long-press gesture ends.
* @param {string} [props.testID] Id used for querying the long-press gesture handler in tests.
*
* @return {JSX.Element} The component to be rendered.
*/
const DraggableTrigger = ({
children,
enabled = true,
id,
maxDistance = 1000,
minDuration = 500,
onLongPress,
onLongPressEnd,
testID
}) => {
const {
panGestureRef,
isDragging,
isPanActive,
draggingId
} = useContext(Context);
const gestureHandler = useAnimatedGestureHandler({
onActive: () => {
if (isDragging.value) {
return;
}
draggingId.value = id;
isDragging.value = true;
if (onLongPress) {
runOnJS(onLongPress)(id);
}
},
onEnd: () => {
if (!isPanActive.value) {
isDragging.value = false;
}
if (onLongPressEnd) {
runOnJS(onLongPressEnd)(id);
}
}
});
return createElement(LongPressGestureHandler, {
enabled: enabled,
minDurationMs: minDuration,
maxDist: maxDistance,
simultaneousHandlers: panGestureRef,
shouldCancelWhenOutside: false,
onGestureEvent: gestureHandler,
testID: testID
}, children);
};
export { DraggableTrigger };
export default Draggable;
//# sourceMappingURL=index.native.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=types.js.map

View File

@@ -0,0 +1 @@
{"version":3,"names":[],"sources":["@wordpress/components/src/draggable/types.ts"],"sourcesContent":["/**\n * External dependencies\n */\nimport type { DragEvent, ReactNode } from 'react';\n\nexport type DraggableProps = {\n\t/**\n\t * Children.\n\t */\n\tchildren: ( props: {\n\t\t/**\n\t\t * `onDragStart` handler.\n\t\t */\n\t\tonDraggableStart: ( event: DragEvent ) => void;\n\t\t/**\n\t\t * `onDragEnd` handler.\n\t\t */\n\t\tonDraggableEnd: ( event: DragEvent ) => void;\n\t} ) => JSX.Element | null;\n\t/**\n\t * Whether to append the cloned element to the `ownerDocument` body.\n\t * By default, elements sourced by id are appended to the element's wrapper.\n\t *\n\t * @default false\n\t */\n\tappendToOwnerDocument?: boolean;\n\t/**\n\t * Classname for the cloned element.\n\t */\n\tcloneClassname?: string;\n\t/**\n\t * The HTML id of the element to clone on drag\n\t */\n\telementId: string;\n\t/**\n\t * A function called when dragging ends. This callback receives the `event`\n\t * object from the `dragend` event as its first parameter.\n\t */\n\tonDragEnd?: ( event: DragEvent ) => void;\n\t/**\n\t * A function called when the element being dragged is dragged over a valid\n\t * drop target. This callback receives the `event` object from the\n\t * `dragover` event as its first parameter.\n\t */\n\tonDragOver?: ( event: DragEvent ) => void;\n\t/**\n\t * A function called when dragging starts. This callback receives the\n\t * `event` object from the `dragstart` event as its first parameter.\n\t */\n\tonDragStart?: ( event: DragEvent ) => void;\n\t/**\n\t * Arbitrary data object attached to the drag and drop event.\n\t */\n\ttransferData: unknown;\n\t/**\n\t * The transfer data type to set.\n\t *\n\t * @default 'text'\n\t */\n\t__experimentalTransferDataType?: string;\n\t/**\n\t * Component to show when dragging.\n\t */\n\t__experimentalDragComponent?: ReactNode;\n};\n"],"mappings":""}