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,187 @@
import { createElement } from "react";
/**
* External dependencies
*/
// eslint-disable-next-line no-restricted-imports
import * as Ariakit from '@ariakit/react';
import classnames from 'classnames';
/**
* WordPress dependencies
*/
import { forwardRef, useEffect, useLayoutEffect, useCallback } from '@wordpress/element';
import { useInstanceId, usePrevious } from '@wordpress/compose';
/**
* Internal dependencies
*/
import Button from '../button';
// Separate the actual tab name from the instance ID. This is
// necessary because Ariakit internally uses the element ID when
// a new tab is selected, but our implementation looks specifically
// for the tab name to be passed to the `onSelect` callback.
const extractTabName = id => {
if (typeof id === 'undefined' || id === null) {
return;
}
return id.match(/^tab-panel-[0-9]*-(.*)/)?.[1];
};
/**
* TabPanel is an ARIA-compliant tabpanel.
*
* TabPanels organize content across different screens, data sets, and interactions.
* It has two sections: a list of tabs, and the view to show when tabs are chosen.
*
* ```jsx
* import { TabPanel } from '@wordpress/components';
*
* const onSelect = ( tabName ) => {
* console.log( 'Selecting tab', tabName );
* };
*
* const MyTabPanel = () => (
* <TabPanel
* className="my-tab-panel"
* activeClass="active-tab"
* onSelect={ onSelect }
* tabs={ [
* {
* name: 'tab1',
* title: 'Tab 1',
* className: 'tab-one',
* },
* {
* name: 'tab2',
* title: 'Tab 2',
* className: 'tab-two',
* },
* ] }
* >
* { ( tab ) => <p>{ tab.title }</p> }
* </TabPanel>
* );
* ```
*/
const UnforwardedTabPanel = ({
className,
children,
tabs,
selectOnMove = true,
initialTabName,
orientation = 'horizontal',
activeClass = 'is-active',
onSelect
}, ref) => {
const instanceId = useInstanceId(TabPanel, 'tab-panel');
const prependInstanceId = useCallback(tabName => {
if (typeof tabName === 'undefined') {
return;
}
return `${instanceId}-${tabName}`;
}, [instanceId]);
const tabStore = Ariakit.useTabStore({
setSelectedId: newTabValue => {
if (typeof newTabValue === 'undefined' || newTabValue === null) {
return;
}
const newTab = tabs.find(t => prependInstanceId(t.name) === newTabValue);
if (newTab?.disabled || newTab === selectedTab) {
return;
}
const simplifiedTabName = extractTabName(newTabValue);
if (typeof simplifiedTabName === 'undefined') {
return;
}
onSelect?.(simplifiedTabName);
},
orientation,
selectOnMove,
defaultSelectedId: prependInstanceId(initialTabName)
});
const selectedTabName = extractTabName(tabStore.useState('selectedId'));
const setTabStoreSelectedId = useCallback(tabName => {
tabStore.setState('selectedId', prependInstanceId(tabName));
}, [prependInstanceId, tabStore]);
const selectedTab = tabs.find(({
name
}) => name === selectedTabName);
const previousSelectedTabName = usePrevious(selectedTabName);
// Ensure `onSelect` is called when the initial tab is selected.
useEffect(() => {
if (previousSelectedTabName !== selectedTabName && selectedTabName === initialTabName && !!selectedTabName) {
onSelect?.(selectedTabName);
}
}, [selectedTabName, initialTabName, onSelect, previousSelectedTabName]);
// Handle selecting the initial tab.
useLayoutEffect(() => {
// If there's a selected tab, don't override it.
if (selectedTab) {
return;
}
const initialTab = tabs.find(tab => tab.name === initialTabName);
// Wait for the denoted initial tab to be declared before making a
// selection. This ensures that if a tab is declared lazily it can
// still receive initial selection.
if (initialTabName && !initialTab) {
return;
}
if (initialTab && !initialTab.disabled) {
// Select the initial tab if it's not disabled.
setTabStoreSelectedId(initialTab.name);
} else {
// Fallback to the first enabled tab when the initial tab is
// disabled or it can't be found.
const firstEnabledTab = tabs.find(tab => !tab.disabled);
if (firstEnabledTab) {
setTabStoreSelectedId(firstEnabledTab.name);
}
}
}, [tabs, selectedTab, initialTabName, instanceId, setTabStoreSelectedId]);
// Handle the currently selected tab becoming disabled.
useEffect(() => {
// This effect only runs when the selected tab is defined and becomes disabled.
if (!selectedTab?.disabled) {
return;
}
const firstEnabledTab = tabs.find(tab => !tab.disabled);
// If the currently selected tab becomes disabled, select the first enabled tab.
// (if there is one).
if (firstEnabledTab) {
setTabStoreSelectedId(firstEnabledTab.name);
}
}, [tabs, selectedTab?.disabled, setTabStoreSelectedId, instanceId]);
return createElement("div", {
className: className,
ref: ref
}, createElement(Ariakit.TabList, {
store: tabStore,
className: "components-tab-panel__tabs"
}, tabs.map(tab => {
return createElement(Ariakit.Tab, {
key: tab.name,
id: prependInstanceId(tab.name),
className: classnames('components-tab-panel__tabs-item', tab.className, {
[activeClass]: tab.name === selectedTabName
}),
disabled: tab.disabled,
"aria-controls": `${prependInstanceId(tab.name)}-view`,
render: createElement(Button, {
icon: tab.icon,
label: tab.icon && tab.title,
showTooltip: !!tab.icon
})
}, !tab.icon && tab.title);
})), selectedTab && createElement(Ariakit.TabPanel, {
id: `${prependInstanceId(selectedTab.name)}-view`,
store: tabStore,
tabId: prependInstanceId(selectedTab.name),
className: 'components-tab-panel__tab-content'
}, children(selectedTab)));
};
export const TabPanel = forwardRef(UnforwardedTabPanel);
export default TabPanel;
//# sourceMappingURL=index.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/tab-panel/types.ts"],"sourcesContent":["/**\n * External dependencies\n */\nimport type { ReactNode } from 'react';\n\n/**\n * Internal dependencies\n */\nimport type { IconType } from '../icon';\n\ntype Tab = {\n\t/**\n\t * The key of the tab.\n\t */\n\tname: string;\n\t/**\n\t * The label of the tab.\n\t */\n\ttitle: string;\n\t/**\n\t * The class name to apply to the tab button.\n\t */\n\tclassName?: string;\n\t/**\n\t * The icon used for the tab button.\n\t */\n\ticon?: IconType;\n\t/**\n\t * Determines if the tab button should be disabled.\n\t */\n\tdisabled?: boolean;\n} & Record< any, any >;\n\nexport type TabPanelProps = {\n\t/**\n\t * The class name to add to the active tab.\n\t *\n\t * @default 'is-active'\n\t */\n\tactiveClass?: string;\n\t/**\n\t * A function which renders the tabviews given the selected tab.\n\t * The function is passed the active tab object as an argument as defined by the tabs prop.\n\t */\n\tchildren: ( tab: Tab ) => ReactNode;\n\t/**\n\t * The class name to give to the outer container for the TabPanel.\n\t */\n\tclassName?: string;\n\t/**\n\t * The name of the tab to be selected upon mounting of component.\n\t * If this prop is not set, the first tab will be selected by default.\n\t */\n\tinitialTabName?: string;\n\t/**\n\t * The function called when a tab has been selected.\n\t * It is passed the `tabName` as an argument.\n\t */\n\tonSelect?: ( tabName: string ) => void;\n\t/**\n\t * The orientation of the tablist.\n\t *\n\t * @default `horizontal`\n\t */\n\torientation?: 'horizontal' | 'vertical';\n\t/**\n\t * Array of tab objects. Each tab object should contain at least a `name` and a `title`.\n\t */\n\ttabs: Tab[];\n\t/**\n\t * When `true`, the tab will be selected when receiving focus (automatic tab\n\t * activation). When `false`, the tab will be selected only when clicked\n\t * (manual tab activation). See the official W3C docs for more info.\n\t * .\n\t *\n\t * @default true\n\t *\n\t * @see https://www.w3.org/WAI/ARIA/apg/patterns/tabpanel/\n\t */\n\tselectOnMove?: boolean;\n};\n"],"mappings":""}