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,91 @@
# TreeSelect
TreeSelect component is used to generate select input fields.
## Usage
Render a user interface to select the parent page in a hierarchy of pages:
```jsx
import { useState } from 'react';
import { TreeSelect } from '@wordpress/components';
const MyTreeSelect = () => {
const [ page, setPage ] = useState( 'p21' );
return (
<TreeSelect
label="Parent page"
noOptionLabel="No parent page"
onChange={ ( newPage ) => setPage( newPage ) }
selectedId={ page }
tree={ [
{
name: 'Page 1',
id: 'p1',
children: [
{ name: 'Descend 1 of page 1', id: 'p11' },
{ name: 'Descend 2 of page 1', id: 'p12' },
],
},
{
name: 'Page 2',
id: 'p2',
children: [
{
name: 'Descend 1 of page 2',
id: 'p21',
children: [
{
name: 'Descend 1 of Descend 1 of page 2',
id: 'p211',
},
],
},
],
},
] }
/>
);
}
```
## Props
The set of props accepted by the component will be specified below.
Props not included in this set will be applied to the SelectControl component being used.
### label
If this property is added, a label will be generated using label property as the content.
- Type: `String`
- Required: No
### noOptionLabel
If this property is added, an option will be added with this label to represent empty selection.
- Type: `String`
- Required: No
### onChange
A function that receives the id of the new node element that is being selected.
- Type: `function`
- Required: Yes
### selectedId
The id of the currently selected node.
- Type: `string` | `string[]`
- Required: No
### tree
An array containing the tree objects with the possible nodes the user can select.
- Type: `Object[]`
- Required: No

View File

@@ -0,0 +1,106 @@
/**
* WordPress dependencies
*/
import { useMemo } from '@wordpress/element';
import { decodeEntities } from '@wordpress/html-entities';
/**
* Internal dependencies
*/
import { SelectControl } from '../select-control';
import type { TreeSelectProps, Tree, Truthy } from './types';
import { useDeprecated36pxDefaultSizeProp } from '../utils/use-deprecated-props';
function getSelectOptions(
tree: Tree[],
level = 0
): NonNullable< TreeSelectProps[ 'options' ] > {
return tree.flatMap( ( treeNode ) => [
{
value: treeNode.id,
label:
'\u00A0'.repeat( level * 3 ) + decodeEntities( treeNode.name ),
},
...getSelectOptions( treeNode.children || [], level + 1 ),
] );
}
/**
* TreeSelect component is used to generate select input fields.
*
* ```jsx
* import { TreeSelect } from '@wordpress/components';
* import { useState } from '@wordpress/element';
*
* const MyTreeSelect = () => {
* const [ page, setPage ] = useState( 'p21' );
*
* return (
* <TreeSelect
* label="Parent page"
* noOptionLabel="No parent page"
* onChange={ ( newPage ) => setPage( newPage ) }
* selectedId={ page }
* tree={ [
* {
* name: 'Page 1',
* id: 'p1',
* children: [
* { name: 'Descend 1 of page 1', id: 'p11' },
* { name: 'Descend 2 of page 1', id: 'p12' },
* ],
* },
* {
* name: 'Page 2',
* id: 'p2',
* children: [
* {
* name: 'Descend 1 of page 2',
* id: 'p21',
* children: [
* {
* name: 'Descend 1 of Descend 1 of page 2',
* id: 'p211',
* },
* ],
* },
* ],
* },
* ] }
* />
* );
* }
* ```
*/
export function TreeSelect( props: TreeSelectProps ) {
const {
label,
noOptionLabel,
onChange,
selectedId,
tree = [],
...restProps
} = useDeprecated36pxDefaultSizeProp(
props,
'wp.components.TreeSelect',
'6.4'
);
const options = useMemo( () => {
return [
noOptionLabel && { value: '', label: noOptionLabel },
...getSelectOptions( tree ),
].filter( < T, >( option: T ): option is Truthy< T > => !! option );
}, [ noOptionLabel, tree ] );
return (
<SelectControl
{ ...{ label, options, onChange } }
value={ selectedId }
{ ...restProps }
/>
);
}
export default TreeSelect;

View File

@@ -0,0 +1,80 @@
/**
* External dependencies
*/
import type { Meta, StoryFn } from '@storybook/react';
import type { ComponentProps } from 'react';
/**
* WordPress dependencies
*/
import { useState } from '@wordpress/element';
/**
* Internal dependencies
*/
import TreeSelect from '../';
const meta: Meta< typeof TreeSelect > = {
title: 'Components/TreeSelect',
component: TreeSelect,
argTypes: {
help: { control: { type: 'text' } },
label: { control: { type: 'text' } },
prefix: { control: { type: 'text' } },
suffix: { control: { type: 'text' } },
selectedId: { control: { type: null } },
},
parameters: {
controls: {
expanded: true,
},
docs: { canvas: { sourceState: 'shown' } },
},
};
export default meta;
const TreeSelectWithState: StoryFn< typeof TreeSelect > = ( props ) => {
const [ selection, setSelection ] =
useState< ComponentProps< typeof TreeSelect >[ 'selectedId' ] >();
return (
<TreeSelect
{ ...props }
onChange={ setSelection }
selectedId={ selection }
/>
);
};
export const Default = TreeSelectWithState.bind( {} );
Default.args = {
label: 'Label Text',
noOptionLabel: 'No parent page',
help: 'Help text to explain the select control.',
tree: [
{
name: 'Page 1',
id: 'p1',
children: [
{ name: 'Descend 1 of page 1', id: 'p11' },
{ name: 'Descend 2 of page 1', id: 'p12' },
],
},
{
name: 'Page 2',
id: 'p2',
children: [
{
name: 'Descend 1 of page 2',
id: 'p21',
children: [
{
name: 'Descend 1 of Descend 1 of page 2',
id: 'p211',
},
],
},
],
},
],
};

View File

@@ -0,0 +1,32 @@
/**
* Internal dependencies
*/
import type { SelectControlSingleSelectionProps } from '../select-control/types';
export type Truthy< T > = T extends false | '' | 0 | null | undefined
? never
: T;
export interface Tree {
id: string;
name: string;
children?: Tree[];
}
// `TreeSelect` inherits props from `SelectControl`, but only
// in single selection mode (ie. when the `multiple` prop is not defined).
export interface TreeSelectProps
extends Omit< SelectControlSingleSelectionProps, 'value' | 'multiple' > {
/**
* If this property is added, an option will be added with this label to represent empty selection.
*/
noOptionLabel?: string;
/**
* An array containing the tree objects with the possible nodes the user can select.
*/
tree?: Tree[];
/**
* The id of the currently selected node.
*/
selectedId?: SelectControlSingleSelectionProps[ 'value' ];
}