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,169 @@
import { createElement } from "react";
/**
* External dependencies
*/
/**
* WordPress dependencies
*/
import { Component, forwardRef } from '@wordpress/element';
import { focus } from '@wordpress/dom';
/**
* Internal dependencies
*/
const noop = () => {};
const MENU_ITEM_ROLES = ['menuitem', 'menuitemradio', 'menuitemcheckbox'];
function cycleValue(value, total, offset) {
const nextValue = value + offset;
if (nextValue < 0) {
return total + nextValue;
} else if (nextValue >= total) {
return nextValue - total;
}
return nextValue;
}
class NavigableContainer extends Component {
constructor(args) {
super(args);
this.onKeyDown = this.onKeyDown.bind(this);
this.bindContainer = this.bindContainer.bind(this);
this.getFocusableContext = this.getFocusableContext.bind(this);
this.getFocusableIndex = this.getFocusableIndex.bind(this);
}
componentDidMount() {
if (!this.container) {
return;
}
// We use DOM event listeners instead of React event listeners
// because we want to catch events from the underlying DOM tree
// The React Tree can be different from the DOM tree when using
// portals. Block Toolbars for instance are rendered in a separate
// React Trees.
this.container.addEventListener('keydown', this.onKeyDown);
}
componentWillUnmount() {
if (!this.container) {
return;
}
this.container.removeEventListener('keydown', this.onKeyDown);
}
bindContainer(ref) {
const {
forwardedRef
} = this.props;
this.container = ref;
if (typeof forwardedRef === 'function') {
forwardedRef(ref);
} else if (forwardedRef && 'current' in forwardedRef) {
forwardedRef.current = ref;
}
}
getFocusableContext(target) {
if (!this.container) {
return null;
}
const {
onlyBrowserTabstops
} = this.props;
const finder = onlyBrowserTabstops ? focus.tabbable : focus.focusable;
const focusables = finder.find(this.container);
const index = this.getFocusableIndex(focusables, target);
if (index > -1 && target) {
return {
index,
target,
focusables
};
}
return null;
}
getFocusableIndex(focusables, target) {
return focusables.indexOf(target);
}
onKeyDown(event) {
if (this.props.onKeyDown) {
this.props.onKeyDown(event);
}
const {
getFocusableContext
} = this;
const {
cycle = true,
eventToOffset,
onNavigate = noop,
stopNavigationEvents
} = this.props;
const offset = eventToOffset(event);
// eventToOffset returns undefined if the event is not handled by the component.
if (offset !== undefined && stopNavigationEvents) {
// Prevents arrow key handlers bound to the document directly interfering.
event.stopImmediatePropagation();
// When navigating a collection of items, prevent scroll containers
// from scrolling. The preventDefault also prevents Voiceover from
// 'handling' the event, as voiceover will try to use arrow keys
// for highlighting text.
const targetRole = event.target?.getAttribute('role');
const targetHasMenuItemRole = !!targetRole && MENU_ITEM_ROLES.includes(targetRole);
if (targetHasMenuItemRole) {
event.preventDefault();
}
}
if (!offset) {
return;
}
const activeElement = event.target?.ownerDocument?.activeElement;
if (!activeElement) {
return;
}
const context = getFocusableContext(activeElement);
if (!context) {
return;
}
const {
index,
focusables
} = context;
const nextIndex = cycle ? cycleValue(index, focusables.length, offset) : index + offset;
if (nextIndex >= 0 && nextIndex < focusables.length) {
focusables[nextIndex].focus();
onNavigate(nextIndex, focusables[nextIndex]);
// `preventDefault()` on tab to avoid having the browser move the focus
// after this component has already moved it.
if (event.code === 'Tab') {
event.preventDefault();
}
}
}
render() {
const {
children,
stopNavigationEvents,
eventToOffset,
onNavigate,
onKeyDown,
cycle,
onlyBrowserTabstops,
forwardedRef,
...restProps
} = this.props;
return createElement("div", {
ref: this.bindContainer,
...restProps
}, children);
}
}
const forwardedNavigableContainer = (props, ref) => {
return createElement(NavigableContainer, {
...props,
forwardedRef: ref
});
};
forwardedNavigableContainer.displayName = 'NavigableContainer';
export default forwardRef(forwardedNavigableContainer);
//# sourceMappingURL=container.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,6 @@
/**
* Internal Dependencies
*/
export { default as NavigableMenu } from './menu';
export { default as TabbableContainer } from './tabbable';
//# sourceMappingURL=index.js.map

View File

@@ -0,0 +1 @@
{"version":3,"names":["default","NavigableMenu","TabbableContainer"],"sources":["@wordpress/components/src/navigable-container/index.tsx"],"sourcesContent":["/**\n * Internal Dependencies\n */\nexport { default as NavigableMenu } from './menu';\nexport { default as TabbableContainer } from './tabbable';\n"],"mappings":"AAAA;AACA;AACA;AACA,SAASA,OAAO,IAAIC,aAAa,QAAQ,QAAQ;AACjD,SAASD,OAAO,IAAIE,iBAAiB,QAAQ,YAAY"}

View File

@@ -0,0 +1,84 @@
import { createElement } from "react";
/**
* External dependencies
*/
/**
* WordPress dependencies
*/
import { forwardRef } from '@wordpress/element';
/**
* Internal dependencies
*/
import NavigableContainer from './container';
export function UnforwardedNavigableMenu({
role = 'menu',
orientation = 'vertical',
...rest
}, ref) {
const eventToOffset = evt => {
const {
code
} = evt;
let next = ['ArrowDown'];
let previous = ['ArrowUp'];
if (orientation === 'horizontal') {
next = ['ArrowRight'];
previous = ['ArrowLeft'];
}
if (orientation === 'both') {
next = ['ArrowRight', 'ArrowDown'];
previous = ['ArrowLeft', 'ArrowUp'];
}
if (next.includes(code)) {
return 1;
} else if (previous.includes(code)) {
return -1;
} else if (['ArrowDown', 'ArrowUp', 'ArrowLeft', 'ArrowRight'].includes(code)) {
// Key press should be handled, e.g. have event propagation and
// default behavior handled by NavigableContainer but not result
// in an offset.
return 0;
}
return undefined;
};
return createElement(NavigableContainer, {
ref: ref,
stopNavigationEvents: true,
onlyBrowserTabstops: false,
role: role,
"aria-orientation": role !== 'presentation' && (orientation === 'vertical' || orientation === 'horizontal') ? orientation : undefined,
eventToOffset: eventToOffset,
...rest
});
}
/**
* A container for a navigable menu.
*
* ```jsx
* import {
* NavigableMenu,
* Button,
* } from '@wordpress/components';
*
* function onNavigate( index, target ) {
* console.log( `Navigates to ${ index }`, target );
* }
*
* const MyNavigableContainer = () => (
* <div>
* <span>Navigable Menu:</span>
* <NavigableMenu onNavigate={ onNavigate } orientation="horizontal">
* <Button variant="secondary">Item 1</Button>
* <Button variant="secondary">Item 2</Button>
* <Button variant="secondary">Item 3</Button>
* </NavigableMenu>
* </div>
* );
* ```
*/
export const NavigableMenu = forwardRef(UnforwardedNavigableMenu);
export default NavigableMenu;
//# sourceMappingURL=menu.js.map

View File

@@ -0,0 +1 @@
{"version":3,"names":["forwardRef","NavigableContainer","UnforwardedNavigableMenu","role","orientation","rest","ref","eventToOffset","evt","code","next","previous","includes","undefined","createElement","stopNavigationEvents","onlyBrowserTabstops","NavigableMenu"],"sources":["@wordpress/components/src/navigable-container/menu.tsx"],"sourcesContent":["/**\n * External dependencies\n */\nimport type { ForwardedRef } from 'react';\n\n/**\n * WordPress dependencies\n */\nimport { forwardRef } from '@wordpress/element';\n\n/**\n * Internal dependencies\n */\nimport NavigableContainer from './container';\nimport type { NavigableMenuProps } from './types';\n\nexport function UnforwardedNavigableMenu(\n\t{ role = 'menu', orientation = 'vertical', ...rest }: NavigableMenuProps,\n\tref: ForwardedRef< any >\n) {\n\tconst eventToOffset = ( evt: KeyboardEvent ) => {\n\t\tconst { code } = evt;\n\n\t\tlet next = [ 'ArrowDown' ];\n\t\tlet previous = [ 'ArrowUp' ];\n\n\t\tif ( orientation === 'horizontal' ) {\n\t\t\tnext = [ 'ArrowRight' ];\n\t\t\tprevious = [ 'ArrowLeft' ];\n\t\t}\n\n\t\tif ( orientation === 'both' ) {\n\t\t\tnext = [ 'ArrowRight', 'ArrowDown' ];\n\t\t\tprevious = [ 'ArrowLeft', 'ArrowUp' ];\n\t\t}\n\n\t\tif ( next.includes( code ) ) {\n\t\t\treturn 1;\n\t\t} else if ( previous.includes( code ) ) {\n\t\t\treturn -1;\n\t\t} else if (\n\t\t\t[ 'ArrowDown', 'ArrowUp', 'ArrowLeft', 'ArrowRight' ].includes(\n\t\t\t\tcode\n\t\t\t)\n\t\t) {\n\t\t\t// Key press should be handled, e.g. have event propagation and\n\t\t\t// default behavior handled by NavigableContainer but not result\n\t\t\t// in an offset.\n\t\t\treturn 0;\n\t\t}\n\n\t\treturn undefined;\n\t};\n\n\treturn (\n\t\t<NavigableContainer\n\t\t\tref={ ref }\n\t\t\tstopNavigationEvents\n\t\t\tonlyBrowserTabstops={ false }\n\t\t\trole={ role }\n\t\t\taria-orientation={\n\t\t\t\trole !== 'presentation' &&\n\t\t\t\t( orientation === 'vertical' || orientation === 'horizontal' )\n\t\t\t\t\t? orientation\n\t\t\t\t\t: undefined\n\t\t\t}\n\t\t\teventToOffset={ eventToOffset }\n\t\t\t{ ...rest }\n\t\t/>\n\t);\n}\n\n/**\n * A container for a navigable menu.\n *\n * ```jsx\n * import {\n * NavigableMenu,\n * Button,\n * } from '@wordpress/components';\n *\n * function onNavigate( index, target ) {\n * console.log( `Navigates to ${ index }`, target );\n * }\n *\n * const MyNavigableContainer = () => (\n * <div>\n * <span>Navigable Menu:</span>\n * <NavigableMenu onNavigate={ onNavigate } orientation=\"horizontal\">\n * <Button variant=\"secondary\">Item 1</Button>\n * <Button variant=\"secondary\">Item 2</Button>\n * <Button variant=\"secondary\">Item 3</Button>\n * </NavigableMenu>\n * </div>\n * );\n * ```\n */\nexport const NavigableMenu = forwardRef( UnforwardedNavigableMenu );\n\nexport default NavigableMenu;\n"],"mappings":";AAAA;AACA;AACA;;AAGA;AACA;AACA;AACA,SAASA,UAAU,QAAQ,oBAAoB;;AAE/C;AACA;AACA;AACA,OAAOC,kBAAkB,MAAM,aAAa;AAG5C,OAAO,SAASC,wBAAwBA,CACvC;EAAEC,IAAI,GAAG,MAAM;EAAEC,WAAW,GAAG,UAAU;EAAE,GAAGC;AAAyB,CAAC,EACxEC,GAAwB,EACvB;EACD,MAAMC,aAAa,GAAKC,GAAkB,IAAM;IAC/C,MAAM;MAAEC;IAAK,CAAC,GAAGD,GAAG;IAEpB,IAAIE,IAAI,GAAG,CAAE,WAAW,CAAE;IAC1B,IAAIC,QAAQ,GAAG,CAAE,SAAS,CAAE;IAE5B,IAAKP,WAAW,KAAK,YAAY,EAAG;MACnCM,IAAI,GAAG,CAAE,YAAY,CAAE;MACvBC,QAAQ,GAAG,CAAE,WAAW,CAAE;IAC3B;IAEA,IAAKP,WAAW,KAAK,MAAM,EAAG;MAC7BM,IAAI,GAAG,CAAE,YAAY,EAAE,WAAW,CAAE;MACpCC,QAAQ,GAAG,CAAE,WAAW,EAAE,SAAS,CAAE;IACtC;IAEA,IAAKD,IAAI,CAACE,QAAQ,CAAEH,IAAK,CAAC,EAAG;MAC5B,OAAO,CAAC;IACT,CAAC,MAAM,IAAKE,QAAQ,CAACC,QAAQ,CAAEH,IAAK,CAAC,EAAG;MACvC,OAAO,CAAC,CAAC;IACV,CAAC,MAAM,IACN,CAAE,WAAW,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,CAAE,CAACG,QAAQ,CAC7DH,IACD,CAAC,EACA;MACD;MACA;MACA;MACA,OAAO,CAAC;IACT;IAEA,OAAOI,SAAS;EACjB,CAAC;EAED,OACCC,aAAA,CAACb,kBAAkB;IAClBK,GAAG,EAAGA,GAAK;IACXS,oBAAoB;IACpBC,mBAAmB,EAAG,KAAO;IAC7Bb,IAAI,EAAGA,IAAM;IACb,oBACCA,IAAI,KAAK,cAAc,KACrBC,WAAW,KAAK,UAAU,IAAIA,WAAW,KAAK,YAAY,CAAE,GAC3DA,WAAW,GACXS,SACH;IACDN,aAAa,EAAGA,aAAe;IAAA,GAC1BF;EAAI,CACT,CAAC;AAEJ;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMY,aAAa,GAAGjB,UAAU,CAAEE,wBAAyB,CAAC;AAEnE,eAAee,aAAa"}

View File

@@ -0,0 +1,89 @@
import { createElement } from "react";
/**
* External dependencies
*/
/**
* WordPress dependencies
*/
import { forwardRef } from '@wordpress/element';
/**
* Internal dependencies
*/
import NavigableContainer from './container';
export function UnforwardedTabbableContainer({
eventToOffset,
...props
}, ref) {
const innerEventToOffset = evt => {
const {
code,
shiftKey
} = evt;
if ('Tab' === code) {
return shiftKey ? -1 : 1;
}
// Allow custom handling of keys besides Tab.
//
// By default, TabbableContainer will move focus forward on Tab and
// backward on Shift+Tab. The handler below will be used for all other
// events. The semantics for `eventToOffset`'s return
// values are the following:
//
// - +1: move focus forward
// - -1: move focus backward
// - 0: don't move focus, but acknowledge event and thus stop it
// - undefined: do nothing, let the event propagate.
if (eventToOffset) {
return eventToOffset(evt);
}
return undefined;
};
return createElement(NavigableContainer, {
ref: ref,
stopNavigationEvents: true,
onlyBrowserTabstops: true,
eventToOffset: innerEventToOffset,
...props
});
}
/**
* A container for tabbable elements.
*
* ```jsx
* import {
* TabbableContainer,
* Button,
* } from '@wordpress/components';
*
* function onNavigate( index, target ) {
* console.log( `Navigates to ${ index }`, target );
* }
*
* const MyTabbableContainer = () => (
* <div>
* <span>Tabbable Container:</span>
* <TabbableContainer onNavigate={ onNavigate }>
* <Button variant="secondary" tabIndex="0">
* Section 1
* </Button>
* <Button variant="secondary" tabIndex="0">
* Section 2
* </Button>
* <Button variant="secondary" tabIndex="0">
* Section 3
* </Button>
* <Button variant="secondary" tabIndex="0">
* Section 4
* </Button>
* </TabbableContainer>
* </div>
* );
* ```
*/
export const TabbableContainer = forwardRef(UnforwardedTabbableContainer);
export default TabbableContainer;
//# sourceMappingURL=tabbable.js.map

View File

@@ -0,0 +1 @@
{"version":3,"names":["forwardRef","NavigableContainer","UnforwardedTabbableContainer","eventToOffset","props","ref","innerEventToOffset","evt","code","shiftKey","undefined","createElement","stopNavigationEvents","onlyBrowserTabstops","TabbableContainer"],"sources":["@wordpress/components/src/navigable-container/tabbable.tsx"],"sourcesContent":["/**\n * External dependencies\n */\nimport type { ForwardedRef } from 'react';\n\n/**\n * WordPress dependencies\n */\nimport { forwardRef } from '@wordpress/element';\n\n/**\n * Internal dependencies\n */\nimport NavigableContainer from './container';\nimport type { TabbableContainerProps } from './types';\n\nexport function UnforwardedTabbableContainer(\n\t{ eventToOffset, ...props }: TabbableContainerProps,\n\tref: ForwardedRef< any >\n) {\n\tconst innerEventToOffset = ( evt: KeyboardEvent ) => {\n\t\tconst { code, shiftKey } = evt;\n\t\tif ( 'Tab' === code ) {\n\t\t\treturn shiftKey ? -1 : 1;\n\t\t}\n\n\t\t// Allow custom handling of keys besides Tab.\n\t\t//\n\t\t// By default, TabbableContainer will move focus forward on Tab and\n\t\t// backward on Shift+Tab. The handler below will be used for all other\n\t\t// events. The semantics for `eventToOffset`'s return\n\t\t// values are the following:\n\t\t//\n\t\t// - +1: move focus forward\n\t\t// - -1: move focus backward\n\t\t// - 0: don't move focus, but acknowledge event and thus stop it\n\t\t// - undefined: do nothing, let the event propagate.\n\t\tif ( eventToOffset ) {\n\t\t\treturn eventToOffset( evt );\n\t\t}\n\n\t\treturn undefined;\n\t};\n\n\treturn (\n\t\t<NavigableContainer\n\t\t\tref={ ref }\n\t\t\tstopNavigationEvents\n\t\t\tonlyBrowserTabstops\n\t\t\teventToOffset={ innerEventToOffset }\n\t\t\t{ ...props }\n\t\t/>\n\t);\n}\n\n/**\n * A container for tabbable elements.\n *\n * ```jsx\n * import {\n * TabbableContainer,\n * Button,\n * } from '@wordpress/components';\n *\n * function onNavigate( index, target ) {\n * console.log( `Navigates to ${ index }`, target );\n * }\n *\n * const MyTabbableContainer = () => (\n * <div>\n * <span>Tabbable Container:</span>\n * <TabbableContainer onNavigate={ onNavigate }>\n * <Button variant=\"secondary\" tabIndex=\"0\">\n * Section 1\n * </Button>\n * <Button variant=\"secondary\" tabIndex=\"0\">\n * Section 2\n * </Button>\n * <Button variant=\"secondary\" tabIndex=\"0\">\n * Section 3\n * </Button>\n * <Button variant=\"secondary\" tabIndex=\"0\">\n * Section 4\n * </Button>\n * </TabbableContainer>\n * </div>\n * );\n * ```\n */\nexport const TabbableContainer = forwardRef( UnforwardedTabbableContainer );\n\nexport default TabbableContainer;\n"],"mappings":";AAAA;AACA;AACA;;AAGA;AACA;AACA;AACA,SAASA,UAAU,QAAQ,oBAAoB;;AAE/C;AACA;AACA;AACA,OAAOC,kBAAkB,MAAM,aAAa;AAG5C,OAAO,SAASC,4BAA4BA,CAC3C;EAAEC,aAAa;EAAE,GAAGC;AAA8B,CAAC,EACnDC,GAAwB,EACvB;EACD,MAAMC,kBAAkB,GAAKC,GAAkB,IAAM;IACpD,MAAM;MAAEC,IAAI;MAAEC;IAAS,CAAC,GAAGF,GAAG;IAC9B,IAAK,KAAK,KAAKC,IAAI,EAAG;MACrB,OAAOC,QAAQ,GAAG,CAAC,CAAC,GAAG,CAAC;IACzB;;IAEA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA,IAAKN,aAAa,EAAG;MACpB,OAAOA,aAAa,CAAEI,GAAI,CAAC;IAC5B;IAEA,OAAOG,SAAS;EACjB,CAAC;EAED,OACCC,aAAA,CAACV,kBAAkB;IAClBI,GAAG,EAAGA,GAAK;IACXO,oBAAoB;IACpBC,mBAAmB;IACnBV,aAAa,EAAGG,kBAAoB;IAAA,GAC/BF;EAAK,CACV,CAAC;AAEJ;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMU,iBAAiB,GAAGd,UAAU,CAAEE,4BAA6B,CAAC;AAE3E,eAAeY,iBAAiB"}

View File

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

View File

@@ -0,0 +1 @@
{"version":3,"names":[],"sources":["@wordpress/components/src/navigable-container/types.ts"],"sourcesContent":["/**\n * External dependencies\n */\nimport type { ForwardedRef, ReactNode } from 'react';\n\n/**\n * Internal dependencies\n */\nimport type { WordPressComponentProps } from '../context';\n\ntype BaseProps = {\n\t/**\n\t * The component children.\n\t */\n\tchildren?: ReactNode;\n\t/**\n\t * A boolean which tells the component whether or not to cycle from the end back to the beginning and vice versa.\n\t *\n\t * @default true\n\t */\n\tcycle?: boolean;\n\t/**\n\t * A callback invoked on the keydown event.\n\t */\n\tonKeyDown?: ( event: KeyboardEvent ) => void;\n\t/**\n\t * A callback invoked when the menu navigates to one of its children passing the index and child as an argument\n\t */\n\tonNavigate?: ( index: number, focusable: HTMLElement ) => void;\n};\n\nexport type NavigableContainerProps = WordPressComponentProps<\n\tBaseProps & {\n\t\t/**\n\t\t * Gets an offset, given an event.\n\t\t */\n\t\teventToOffset: ( event: KeyboardEvent ) => -1 | 0 | 1 | undefined;\n\t\t/**\n\t\t * The forwarded ref.\n\t\t */\n\t\tforwardedRef?: ForwardedRef< any >;\n\t\t/**\n\t\t * Whether to only consider browser tab stops.\n\t\t *\n\t\t * @default false\n\t\t */\n\t\tonlyBrowserTabstops: boolean;\n\t\t/**\n\t\t * Whether to stop navigation events.\n\t\t *\n\t\t * @default false\n\t\t */\n\t\tstopNavigationEvents: boolean;\n\t},\n\t'div',\n\tfalse\n>;\n\nexport type NavigableMenuProps = WordPressComponentProps<\n\tBaseProps & {\n\t\t/**\n\t\t * The orientation of the menu.\n\t\t *\n\t\t * @default 'vertical'\n\t\t */\n\t\torientation?: 'vertical' | 'horizontal' | 'both';\n\t},\n\t'div',\n\tfalse\n>;\n\nexport type TabbableContainerProps = WordPressComponentProps<\n\tBaseProps & Partial< Pick< NavigableContainerProps, 'eventToOffset' > >,\n\t'div',\n\tfalse\n>;\n"],"mappings":""}