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,15 @@
/**
* WordPress dependencies
*/
import { createContext, useContext } from '@wordpress/element';
/**
* Internal dependencies
*/
export const NavigationMenuContext = createContext({
menu: undefined,
search: ''
});
export const useNavigationMenuContext = () => useContext(NavigationMenuContext);
//# sourceMappingURL=context.js.map

View File

@@ -0,0 +1 @@
{"version":3,"names":["createContext","useContext","NavigationMenuContext","menu","undefined","search","useNavigationMenuContext"],"sources":["@wordpress/components/src/navigation/menu/context.tsx"],"sourcesContent":["/**\n * WordPress dependencies\n */\nimport { createContext, useContext } from '@wordpress/element';\n\n/**\n * Internal dependencies\n */\nimport type { NavigationMenuContext as NavigationMenuContextType } from '../types';\n\nexport const NavigationMenuContext = createContext< NavigationMenuContextType >(\n\t{\n\t\tmenu: undefined,\n\t\tsearch: '',\n\t}\n);\nexport const useNavigationMenuContext = () =>\n\tuseContext( NavigationMenuContext );\n"],"mappings":"AAAA;AACA;AACA;AACA,SAASA,aAAa,EAAEC,UAAU,QAAQ,oBAAoB;;AAE9D;AACA;AACA;;AAGA,OAAO,MAAMC,qBAAqB,GAAGF,aAAa,CACjD;EACCG,IAAI,EAAEC,SAAS;EACfC,MAAM,EAAE;AACT,CACD,CAAC;AACD,OAAO,MAAMC,wBAAwB,GAAGA,CAAA,KACvCL,UAAU,CAAEC,qBAAsB,CAAC"}

View File

@@ -0,0 +1,81 @@
import { createElement } from "react";
/**
* External dependencies
*/
import classnames from 'classnames';
/**
* WordPress dependencies
*/
import { useState } from '@wordpress/element';
/**
* Internal dependencies
*/
import { ROOT_MENU } from '../constants';
import { NavigationMenuContext } from './context';
import { useNavigationContext } from '../context';
import { useNavigationTreeMenu } from './use-navigation-tree-menu';
import NavigationBackButton from '../back-button';
import NavigationMenuTitle from './menu-title';
import NavigationSearchNoResultsFound from './search-no-results-found';
import { NavigableMenu } from '../../navigable-container';
import { MenuUI } from '../styles/navigation-styles';
export function NavigationMenu(props) {
const {
backButtonLabel,
children,
className,
hasSearch,
menu = ROOT_MENU,
onBackButtonClick,
onSearch: setControlledSearch,
parentMenu,
search: controlledSearch,
isSearchDebouncing,
title,
titleAction
} = props;
const [uncontrolledSearch, setUncontrolledSearch] = useState('');
useNavigationTreeMenu(props);
const {
activeMenu
} = useNavigationContext();
const context = {
menu,
search: uncontrolledSearch
};
// Keep the children rendered to make sure invisible items are included in the navigation tree.
if (activeMenu !== menu) {
return createElement(NavigationMenuContext.Provider, {
value: context
}, children);
}
const isControlledSearch = !!setControlledSearch;
const search = isControlledSearch ? controlledSearch : uncontrolledSearch;
const onSearch = isControlledSearch ? setControlledSearch : setUncontrolledSearch;
const menuTitleId = `components-navigation__menu-title-${menu}`;
const classes = classnames('components-navigation__menu', className);
return createElement(NavigationMenuContext.Provider, {
value: context
}, createElement(MenuUI, {
className: classes
}, (parentMenu || onBackButtonClick) && createElement(NavigationBackButton, {
backButtonLabel: backButtonLabel,
parentMenu: parentMenu,
onClick: onBackButtonClick
}), title && createElement(NavigationMenuTitle, {
hasSearch: hasSearch,
onSearch: onSearch,
search: search,
title: title,
titleAction: titleAction
}), createElement(NavigableMenu, null, createElement("ul", {
"aria-labelledby": menuTitleId
}, children, search && !isSearchDebouncing && createElement(NavigationSearchNoResultsFound, {
search: search
})))));
}
export default NavigationMenu;
//# sourceMappingURL=index.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,83 @@
import { createElement } from "react";
/**
* WordPress dependencies
*/
import { useEffect, useRef } from '@wordpress/element';
import { __, _n, sprintf } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import withSpokenMessages from '../../higher-order/with-spoken-messages';
import { useNavigationMenuContext } from './context';
import { useNavigationContext } from '../context';
import { MenuTitleSearchUI } from '../styles/navigation-styles';
import { SEARCH_FOCUS_DELAY } from '../constants';
function MenuTitleSearch({
debouncedSpeak,
onCloseSearch,
onSearch,
search,
title
}) {
const {
navigationTree: {
items
}
} = useNavigationContext();
const {
menu
} = useNavigationMenuContext();
const inputRef = useRef(null);
// Wait for the slide-in animation to complete before autofocusing the input.
// This prevents scrolling to the input during the animation.
useEffect(() => {
const delayedFocus = setTimeout(() => {
inputRef.current?.focus();
}, SEARCH_FOCUS_DELAY);
return () => {
clearTimeout(delayedFocus);
};
}, []);
useEffect(() => {
if (!search) {
return;
}
const count = Object.values(items).filter(item => item._isVisible).length;
const resultsFoundMessage = sprintf( /* translators: %d: number of results. */
_n('%d result found.', '%d results found.', count), count);
debouncedSpeak(resultsFoundMessage);
// Ignore exhaustive-deps rule for now. See https://github.com/WordPress/gutenberg/pull/44090
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [items, search]);
const onClose = () => {
onSearch?.('');
onCloseSearch();
};
const onKeyDown = event => {
if (event.code === 'Escape' && !event.defaultPrevented) {
event.preventDefault();
onClose();
}
};
const inputId = `components-navigation__menu-title-search-${menu}`;
const placeholder = sprintf( /* translators: placeholder for menu search box. %s: menu title */
__('Search %s'), title?.toLowerCase()).trim();
return createElement("div", {
className: "components-navigation__menu-title-search"
}, createElement(MenuTitleSearchUI, {
autoComplete: "off",
className: "components-navigation__menu-search-input",
id: inputId,
onChange: value => onSearch?.(value),
onKeyDown: onKeyDown,
placeholder: placeholder,
onClose: onClose,
ref: inputRef,
type: "search",
value: search
}));
}
export default withSpokenMessages(MenuTitleSearch);
//# sourceMappingURL=menu-title-search.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,73 @@
import { createElement } from "react";
/**
* WordPress dependencies
*/
import { useRef, useState } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';
import { Icon, search as searchIcon } from '@wordpress/icons';
/**
* Internal dependencies
*/
import { getAnimateClassName } from '../../animate';
import Button from '../../button';
import MenuTitleSearch from './menu-title-search';
import { GroupTitleUI, MenuTitleActionsUI, MenuTitleUI } from '../styles/navigation-styles';
import { useNavigationMenuContext } from './context';
import { SEARCH_FOCUS_DELAY } from '../constants';
export default function NavigationMenuTitle({
hasSearch,
onSearch,
search,
title,
titleAction
}) {
const [isSearching, setIsSearching] = useState(false);
const {
menu
} = useNavigationMenuContext();
const searchButtonRef = useRef(null);
if (!title) {
return null;
}
const onCloseSearch = () => {
setIsSearching(false);
// Wait for the slide-in animation to complete before focusing the search button.
// eslint-disable-next-line @wordpress/react-no-unsafe-timeout
setTimeout(() => {
searchButtonRef.current?.focus();
}, SEARCH_FOCUS_DELAY);
};
const menuTitleId = `components-navigation__menu-title-${menu}`;
/* translators: search button label for menu search box. %s: menu title */
const searchButtonLabel = sprintf(__('Search in %s'), title);
return createElement(MenuTitleUI, {
className: "components-navigation__menu-title"
}, !isSearching && createElement(GroupTitleUI, {
as: "h2",
className: "components-navigation__menu-title-heading",
level: 3
}, createElement("span", {
id: menuTitleId
}, title), (hasSearch || titleAction) && createElement(MenuTitleActionsUI, null, titleAction, hasSearch && createElement(Button, {
size: "small",
variant: "tertiary",
label: searchButtonLabel,
onClick: () => setIsSearching(true),
ref: searchButtonRef
}, createElement(Icon, {
icon: searchIcon
})))), isSearching && createElement("div", {
className: getAnimateClassName({
type: 'slide-in',
origin: 'left'
})
}, createElement(MenuTitleSearch, {
onCloseSearch: onCloseSearch,
onSearch: onSearch,
search: search,
title: title
})));
}
//# sourceMappingURL=menu-title.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,26 @@
import { createElement } from "react";
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import { useNavigationContext } from '../context';
import { ItemBaseUI, ItemUI } from '../styles/navigation-styles';
export default function NavigationSearchNoResultsFound({
search
}) {
const {
navigationTree: {
items
}
} = useNavigationContext();
const resultsCount = Object.values(items).filter(item => item._isVisible).length;
if (!search || !!resultsCount) {
return null;
}
return createElement(ItemBaseUI, null, createElement(ItemUI, null, __('No results found.'), " "));
}
//# sourceMappingURL=search-no-results-found.js.map

View File

@@ -0,0 +1 @@
{"version":3,"names":["__","useNavigationContext","ItemBaseUI","ItemUI","NavigationSearchNoResultsFound","search","navigationTree","items","resultsCount","Object","values","filter","item","_isVisible","length","createElement"],"sources":["@wordpress/components/src/navigation/menu/search-no-results-found.tsx"],"sourcesContent":["/**\n * WordPress dependencies\n */\nimport { __ } from '@wordpress/i18n';\n\n/**\n * Internal dependencies\n */\nimport { useNavigationContext } from '../context';\nimport { ItemBaseUI, ItemUI } from '../styles/navigation-styles';\n\nimport type { NavigationSearchNoResultsFoundProps } from '../types';\n\nexport default function NavigationSearchNoResultsFound( {\n\tsearch,\n}: NavigationSearchNoResultsFoundProps ) {\n\tconst {\n\t\tnavigationTree: { items },\n\t} = useNavigationContext();\n\n\tconst resultsCount = Object.values( items ).filter(\n\t\t( item ) => item._isVisible\n\t).length;\n\n\tif ( ! search || !! resultsCount ) {\n\t\treturn null;\n\t}\n\n\treturn (\n\t\t<ItemBaseUI>\n\t\t\t<ItemUI>{ __( 'No results found.' ) } </ItemUI>\n\t\t</ItemBaseUI>\n\t);\n}\n"],"mappings":";AAAA;AACA;AACA;AACA,SAASA,EAAE,QAAQ,iBAAiB;;AAEpC;AACA;AACA;AACA,SAASC,oBAAoB,QAAQ,YAAY;AACjD,SAASC,UAAU,EAAEC,MAAM,QAAQ,6BAA6B;AAIhE,eAAe,SAASC,8BAA8BA,CAAE;EACvDC;AACoC,CAAC,EAAG;EACxC,MAAM;IACLC,cAAc,EAAE;MAAEC;IAAM;EACzB,CAAC,GAAGN,oBAAoB,CAAC,CAAC;EAE1B,MAAMO,YAAY,GAAGC,MAAM,CAACC,MAAM,CAAEH,KAAM,CAAC,CAACI,MAAM,CAC/CC,IAAI,IAAMA,IAAI,CAACC,UAClB,CAAC,CAACC,MAAM;EAER,IAAK,CAAET,MAAM,IAAI,CAAC,CAAEG,YAAY,EAAG;IAClC,OAAO,IAAI;EACZ;EAEA,OACCO,aAAA,CAACb,UAAU,QACVa,aAAA,CAACZ,MAAM,QAAGH,EAAE,CAAE,mBAAoB,CAAC,EAAE,GAAS,CACnC,CAAC;AAEf"}

View File

@@ -0,0 +1,31 @@
/**
* WordPress dependencies
*/
import { useEffect } from '@wordpress/element';
/**
* Internal dependencies
*/
import { useNavigationContext } from '../context';
import { ROOT_MENU } from '../constants';
export const useNavigationTreeMenu = props => {
const {
navigationTree: {
addMenu,
removeMenu
}
} = useNavigationContext();
const key = props.menu || ROOT_MENU;
useEffect(() => {
addMenu(key, {
...props,
menu: key
});
return () => {
removeMenu(key);
};
// Ignore exhaustive-deps rule for now. See https://github.com/WordPress/gutenberg/pull/44090
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
};
//# sourceMappingURL=use-navigation-tree-menu.js.map

View File

@@ -0,0 +1 @@
{"version":3,"names":["useEffect","useNavigationContext","ROOT_MENU","useNavigationTreeMenu","props","navigationTree","addMenu","removeMenu","key","menu"],"sources":["@wordpress/components/src/navigation/menu/use-navigation-tree-menu.tsx"],"sourcesContent":["/**\n * WordPress dependencies\n */\nimport { useEffect } from '@wordpress/element';\n\n/**\n * Internal dependencies\n */\nimport { useNavigationContext } from '../context';\nimport { ROOT_MENU } from '../constants';\n\nimport type { NavigationMenuProps } from '../types';\n\nexport const useNavigationTreeMenu = ( props: NavigationMenuProps ) => {\n\tconst {\n\t\tnavigationTree: { addMenu, removeMenu },\n\t} = useNavigationContext();\n\n\tconst key = props.menu || ROOT_MENU;\n\tuseEffect( () => {\n\t\taddMenu( key, { ...props, menu: key } );\n\n\t\treturn () => {\n\t\t\tremoveMenu( key );\n\t\t};\n\t\t// Ignore exhaustive-deps rule for now. See https://github.com/WordPress/gutenberg/pull/44090\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t}, [] );\n};\n"],"mappings":"AAAA;AACA;AACA;AACA,SAASA,SAAS,QAAQ,oBAAoB;;AAE9C;AACA;AACA;AACA,SAASC,oBAAoB,QAAQ,YAAY;AACjD,SAASC,SAAS,QAAQ,cAAc;AAIxC,OAAO,MAAMC,qBAAqB,GAAKC,KAA0B,IAAM;EACtE,MAAM;IACLC,cAAc,EAAE;MAAEC,OAAO;MAAEC;IAAW;EACvC,CAAC,GAAGN,oBAAoB,CAAC,CAAC;EAE1B,MAAMO,GAAG,GAAGJ,KAAK,CAACK,IAAI,IAAIP,SAAS;EACnCF,SAAS,CAAE,MAAM;IAChBM,OAAO,CAAEE,GAAG,EAAE;MAAE,GAAGJ,KAAK;MAAEK,IAAI,EAAED;IAAI,CAAE,CAAC;IAEvC,OAAO,MAAM;MACZD,UAAU,CAAEC,GAAI,CAAC;IAClB,CAAC;IACD;IACA;EACD,CAAC,EAAE,EAAG,CAAC;AACR,CAAC"}