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

327
node_modules/@wordpress/dom/src/test/dom.js generated vendored Normal file
View File

@@ -0,0 +1,327 @@
/**
* Internal dependencies
*/
import {
isHorizontalEdge,
placeCaretAtHorizontalEdge,
isTextField,
removeInvalidHTML,
isEmpty,
} from '../dom';
import { getPhrasingContentSchema } from '../phrasing-content';
describe( 'DOM', () => {
let parent;
beforeEach( () => {
parent = document.createElement( 'div' );
document.body.appendChild( parent );
} );
afterEach( () => {
parent.remove();
} );
describe( 'isHorizontalEdge', () => {
it( 'should return true for empty input', () => {
const input = document.createElement( 'input' );
parent.appendChild( input );
input.focus();
expect( isHorizontalEdge( input, true ) ).toBe( true );
expect( isHorizontalEdge( input, false ) ).toBe( true );
} );
it( 'should return the right values if we focus the end of the input', () => {
const input = document.createElement( 'input' );
parent.appendChild( input );
input.value = 'value';
input.focus();
input.selectionStart = 5;
input.selectionEnd = 5;
expect( isHorizontalEdge( input, true ) ).toBe( false );
expect( isHorizontalEdge( input, false ) ).toBe( true );
} );
it( 'should return the right values if we focus the start of the input', () => {
const input = document.createElement( 'input' );
parent.appendChild( input );
input.value = 'value';
input.focus();
input.selectionStart = 0;
input.selectionEnd = 0;
expect( isHorizontalEdge( input, true ) ).toBe( true );
expect( isHorizontalEdge( input, false ) ).toBe( false );
} );
it( 'should return false if were not at the edge', () => {
const input = document.createElement( 'input' );
parent.appendChild( input );
input.value = 'value';
input.focus();
input.selectionStart = 3;
input.selectionEnd = 3;
expect( isHorizontalEdge( input, true ) ).toBe( false );
expect( isHorizontalEdge( input, false ) ).toBe( false );
} );
it( 'should return false if the selection is not collapseds', () => {
const input = document.createElement( 'input' );
parent.appendChild( input );
input.value = 'value';
input.focus();
input.selectionStart = 0;
input.selectionEnd = 5;
expect( isHorizontalEdge( input, true ) ).toBe( false );
expect( isHorizontalEdge( input, false ) ).toBe( false );
} );
it( 'should always return true for non content editabless', () => {
const div = document.createElement( 'div' );
parent.appendChild( div );
expect( isHorizontalEdge( div, true ) ).toBe( true );
expect( isHorizontalEdge( div, false ) ).toBe( true );
} );
it( 'should return true for input types that do not have selection ranges', () => {
const input = document.createElement( 'input' );
input.setAttribute( 'type', 'checkbox' );
parent.appendChild( input );
expect( isHorizontalEdge( input, true ) ).toBe( true );
expect( isHorizontalEdge( input, false ) ).toBe( true );
} );
} );
describe( 'placeCaretAtHorizontalEdge', () => {
it( 'should place caret at the start of the input', () => {
const input = document.createElement( 'input' );
input.value = 'value';
placeCaretAtHorizontalEdge( input, true );
expect( isHorizontalEdge( input, false ) ).toBe( true );
} );
it( 'should place caret at the end of the input', () => {
const input = document.createElement( 'input' );
input.value = 'value';
placeCaretAtHorizontalEdge( input, false );
expect( isHorizontalEdge( input, true ) ).toBe( true );
} );
} );
describe( 'isTextField', () => {
/**
* A sampling of input types expected not to be text eligible.
*
* @type {string[]}
*/
const NON_TEXT_INPUT_TYPES = [
'button',
'checkbox',
'hidden',
'file',
'radio',
'image',
'range',
'reset',
'submit',
'email',
'time',
];
/**
* A sampling of input types expected to be text eligible.
*
* @type {string[]}
*/
const TEXT_INPUT_TYPES = [ 'text', 'password', 'search', 'url' ];
it( 'should return false for non-text input elements', () => {
NON_TEXT_INPUT_TYPES.forEach( ( type ) => {
const input = document.createElement( 'input' );
input.type = type;
expect( isTextField( input ) ).toBe( false );
} );
} );
it( 'should return true for text input elements', () => {
TEXT_INPUT_TYPES.forEach( ( type ) => {
const input = document.createElement( 'input' );
input.type = type;
expect( isTextField( input ) ).toBe( true );
} );
} );
it( 'should return true for an textarea element', () => {
expect( isTextField( document.createElement( 'textarea' ) ) ).toBe(
true
);
} );
it( 'should return true for a contenteditable element', () => {
const div = document.createElement( 'div' );
div.contentEditable = 'true';
expect( isTextField( div ) ).toBe( true );
} );
it( 'should return true for a normal div element', () => {
expect( isTextField( document.createElement( 'div' ) ) ).toBe(
false
);
} );
} );
} );
describe( 'removeInvalidHTML', () => {
const phrasingContentSchema = getPhrasingContentSchema();
const schema = {
p: {
children: phrasingContentSchema,
},
figure: {
require: [ 'img' ],
children: {
img: {
attributes: [ 'src', 'alt' ],
classes: [ 'alignleft' ],
},
figcaption: {
children: phrasingContentSchema,
},
},
},
...phrasingContentSchema,
};
it( 'should leave plain text alone', () => {
const input = 'test';
expect( removeInvalidHTML( input, schema ) ).toBe( input );
} );
it( 'should leave valid phrasing content alone', () => {
const input = '<strong>test</strong>';
expect( removeInvalidHTML( input, schema ) ).toBe( input );
} );
it( 'should remove unrecognised tags from phrasing content', () => {
const input = '<strong><div>test</div></strong>';
const output = '<strong>test</strong>';
expect( removeInvalidHTML( input, schema ) ).toBe( output );
} );
it( 'should remove unwanted whitespace outside phrasing content', () => {
const input = '<figure><img src=""> </figure>';
const output = '<figure><img src=""></figure>';
expect( removeInvalidHTML( input, schema ) ).toBe( output );
} );
it( 'should remove attributes', () => {
const input = '<p class="test">test</p>';
const output = '<p>test</p>';
expect( removeInvalidHTML( input, schema ) ).toBe( output );
} );
it( 'should remove id attributes', () => {
const input = '<p id="foo">test</p>';
const output = '<p>test</p>';
expect( removeInvalidHTML( input, schema ) ).toBe( output );
} );
it( 'should remove multiple attributes', () => {
const input = '<p class="test" id="test">test</p>';
const output = '<p>test</p>';
expect( removeInvalidHTML( input, schema ) ).toBe( output );
} );
it( 'should deep remove attributes', () => {
const input = '<p class="test">test <em id="test">test</em></p>';
const output = '<p>test <em>test</em></p>';
expect( removeInvalidHTML( input, schema ) ).toBe( output );
} );
it( 'should remove data-* attributes', () => {
const input = '<p data-reactid="1">test</p>';
const output = '<p>test</p>';
expect( removeInvalidHTML( input, schema ) ).toBe( output );
} );
it( 'should keep some attributes', () => {
const input = '<a href="#keep" target="_blank">test</a>';
const output = '<a href="#keep" target="_blank">test</a>';
expect( removeInvalidHTML( input, schema ) ).toBe( output );
} );
it( 'should keep some classes', () => {
const input = '<figure><img class="alignleft test" src=""></figure>';
const output = '<figure><img class="alignleft" src=""></figure>';
expect( removeInvalidHTML( input, schema ) ).toBe( output );
} );
it( 'should remove empty nodes that should have children', () => {
const input = '<figure> </figure>';
const output = '';
expect( removeInvalidHTML( input, schema ) ).toBe( output );
} );
it( 'should break up block content with phrasing schema', () => {
const input = '<p>test</p><p>test</p>';
const output = 'test<br>test';
expect( removeInvalidHTML( input, phrasingContentSchema, true ) ).toBe(
output
);
} );
it( 'should unwrap node that does not satisfy require', () => {
const input =
'<figure><p>test</p><figcaption>test</figcaption></figure>';
const output = '<p>test</p>test';
expect( removeInvalidHTML( input, schema ) ).toBe( output );
} );
it( 'should remove invalid phrasing content', () => {
const input = '<strong><p>test</p></strong>';
const output = '<p>test</p>';
expect( removeInvalidHTML( input, schema ) ).toEqual( output );
} );
} );
describe( 'isEmpty', () => {
function isEmptyHTML( HTML ) {
const doc = document.implementation.createHTMLDocument( '' );
doc.body.innerHTML = HTML;
return isEmpty( doc.body );
}
it( 'should return true for empty element', () => {
expect( isEmptyHTML( '' ) ).toBe( true );
} );
it( 'should return true for element with only whitespace', () => {
expect( isEmptyHTML( ' ' ) ).toBe( true );
} );
it( 'should return true for element with non breaking space', () => {
expect( isEmptyHTML( '&nbsp;' ) ).toBe( true );
} );
it( 'should return true for element with BR', () => {
expect( isEmptyHTML( '<br>' ) ).toBe( true );
} );
it( 'should return true for element with empty element', () => {
expect( isEmptyHTML( '<em></em>' ) ).toBe( true );
} );
it( 'should return false for element with image', () => {
expect( isEmptyHTML( '<img src="">' ) ).toBe( false );
} );
it( 'should return true for element with mixed empty pieces', () => {
expect( isEmptyHTML( ' <br><br><em>&nbsp; </em>' ) ).toBe( true );
} );
} );

161
node_modules/@wordpress/dom/src/test/focusable.js generated vendored Normal file
View File

@@ -0,0 +1,161 @@
/**
* Internal dependencies
*/
import createElement from './utils/create-element';
import { find } from '../focusable';
describe( 'focusable', () => {
beforeEach( () => {
document.body.innerHTML = '';
} );
describe( 'find()', () => {
it( 'returns empty array if no children', () => {
const node = createElement( 'div' );
expect( find( node ) ).toEqual( [] );
} );
it( 'returns empty array if no focusable children', () => {
const node = createElement( 'div' );
node.appendChild( createElement( 'div' ) );
expect( find( node ) ).toEqual( [] );
} );
it( 'returns array of focusable children', () => {
const node = createElement( 'div' );
node.appendChild( createElement( 'input' ) );
const focusable = find( node );
expect( focusable ).toHaveLength( 1 );
expect( focusable[ 0 ].nodeName ).toBe( 'INPUT' );
} );
it( 'finds nested focusable child', () => {
const node = createElement( 'div' );
node.appendChild( createElement( 'div' ) );
node.firstChild.appendChild( createElement( 'input' ) );
const focusable = find( node );
expect( focusable ).toHaveLength( 1 );
expect( focusable[ 0 ].nodeName ).toBe( 'INPUT' );
} );
it( 'finds link with no href but tabindex', () => {
const node = createElement( 'div' );
const link = createElement( 'a' );
link.tabIndex = 0;
node.appendChild( link );
expect( find( node ) ).toEqual( [ link ] );
} );
it( 'finds valid area focusable', () => {
const map = createElement( 'map' );
map.name = 'testfocus';
const area = createElement( 'area' );
area.href = '';
map.appendChild( area );
const img = createElement( 'img' );
img.setAttribute( 'usemap', '#testfocus' );
document.body.appendChild( map );
document.body.appendChild( img );
const focusable = find( map );
expect( focusable ).toHaveLength( 1 );
expect( focusable[ 0 ].nodeName ).toBe( 'AREA' );
} );
it( 'ignores invalid area focusable', () => {
const map = createElement( 'map' );
map.name = 'testfocus';
const area = createElement( 'area' );
area.href = '';
map.appendChild( area );
const img = createElement( 'img' );
img.setAttribute( 'usemap', '#testfocus' );
img.style.display = 'none';
document.body.appendChild( map );
document.body.appendChild( img );
expect( find( map ) ).toEqual( [] );
} );
it( 'finds contenteditable', () => {
const node = createElement( 'div' );
const div = createElement( 'div' );
node.appendChild( div );
div.setAttribute( 'contenteditable', '' );
expect( find( node ) ).toEqual( [ div ] );
div.setAttribute( 'contenteditable', 'true' );
expect( find( node ) ).toEqual( [ div ] );
} );
it( 'ignores contenteditable=false', () => {
const node = createElement( 'div' );
const div = createElement( 'div' );
node.appendChild( div );
div.setAttribute( 'contenteditable', 'false' );
expect( find( node ) ).toEqual( [] );
} );
it( 'ignores invisible inputs', () => {
const node = createElement( 'div' );
const input = createElement( 'input' );
node.appendChild( input );
input.style.visibility = 'hidden';
expect( find( node ) ).toEqual( [] );
input.style.visibility = 'visible';
input.style.display = 'none';
expect( find( node ) ).toEqual( [] );
input.style.display = 'inline-block';
const focusable = find( node );
expect( focusable ).toHaveLength( 1 );
expect( focusable[ 0 ].nodeName ).toBe( 'INPUT' );
} );
it( 'ignores inputs in invisible ancestors', () => {
const node = createElement( 'div' );
const input = createElement( 'input' );
node.appendChild( input );
node.style.visibility = 'hidden';
expect( find( node ) ).toEqual( [] );
node.style.visibility = 'visible';
node.style.display = 'none';
expect( find( node ) ).toEqual( [] );
node.style.display = 'block';
const focusable = find( node );
expect( focusable ).toHaveLength( 1 );
expect( focusable[ 0 ].nodeName ).toBe( 'INPUT' );
} );
it( 'does not return context even if focusable', () => {
const node = createElement( 'div' );
node.tabIndex = 0;
expect( find( node ) ).toEqual( [] );
} );
it( 'limits found focusables to specific context', () => {
const node = createElement( 'div' );
node.appendChild( createElement( 'div' ) );
document.body.appendChild( node );
document.body.appendChild( createElement( 'input' ) );
expect( find( node ) ).toEqual( [] );
} );
} );
} );

117
node_modules/@wordpress/dom/src/test/tabbable.js generated vendored Normal file
View File

@@ -0,0 +1,117 @@
/**
* Internal dependencies
*/
import createElement from './utils/create-element';
import { find } from '../tabbable';
describe( 'tabbable', () => {
beforeEach( () => {
document.body.innerHTML = '';
} );
describe( 'find()', () => {
it( 'returns focusables in order of tabindex', () => {
const node = createElement( 'div' );
const absent = createElement( 'input' );
absent.tabIndex = -1;
const first = createElement( 'input' );
const second = createElement( 'span' );
second.tabIndex = 0;
const third = createElement( 'input' );
third.tabIndex = 1;
node.appendChild( third );
node.appendChild( first );
node.appendChild( second );
node.appendChild( absent );
const tabbables = find( node );
expect( tabbables ).toEqual( [ first, second, third ] );
} );
it( 'consolidates radio group to the first, if unchecked', () => {
const node = createElement( 'div' );
const firstRadio = createElement( 'input' );
firstRadio.type = 'radio';
firstRadio.name = 'a';
firstRadio.value = 'firstRadio';
const secondRadio = createElement( 'input' );
secondRadio.type = 'radio';
secondRadio.name = 'a';
secondRadio.value = 'secondRadio';
const text = createElement( 'input' );
text.type = 'text';
text.name = 'b';
const thirdRadio = createElement( 'input' );
thirdRadio.type = 'radio';
thirdRadio.name = 'a';
thirdRadio.value = 'thirdRadio';
const fourthRadio = createElement( 'input' );
fourthRadio.type = 'radio';
fourthRadio.name = 'b';
fourthRadio.value = 'fourthRadio';
const fifthRadio = createElement( 'input' );
fifthRadio.type = 'radio';
fifthRadio.name = 'b';
fifthRadio.value = 'fifthRadio';
node.appendChild( firstRadio );
node.appendChild( secondRadio );
node.appendChild( text );
node.appendChild( thirdRadio );
node.appendChild( fourthRadio );
node.appendChild( fifthRadio );
const tabbables = find( node );
expect( tabbables ).toEqual( [ firstRadio, text, fourthRadio ] );
} );
it( 'consolidates radio group to the checked', () => {
const node = createElement( 'div' );
const firstRadio = createElement( 'input' );
firstRadio.type = 'radio';
firstRadio.name = 'a';
firstRadio.value = 'firstRadio';
const secondRadio = createElement( 'input' );
secondRadio.type = 'radio';
secondRadio.name = 'a';
secondRadio.value = 'secondRadio';
const text = createElement( 'input' );
text.type = 'text';
text.name = 'b';
const thirdRadio = createElement( 'input' );
thirdRadio.type = 'radio';
thirdRadio.name = 'a';
thirdRadio.value = 'thirdRadio';
thirdRadio.checked = true;
node.appendChild( firstRadio );
node.appendChild( secondRadio );
node.appendChild( text );
node.appendChild( thirdRadio );
const tabbables = find( node );
expect( tabbables ).toEqual( [ text, thirdRadio ] );
} );
it( 'not consolidate unnamed radio inputs', () => {
const node = createElement( 'div' );
const firstRadio = createElement( 'input' );
firstRadio.type = 'radio';
firstRadio.value = 'firstRadio';
const text = createElement( 'input' );
text.type = 'text';
text.name = 'b';
const secondRadio = createElement( 'input' );
secondRadio.type = 'radio';
secondRadio.value = 'secondRadio';
node.appendChild( firstRadio );
node.appendChild( text );
node.appendChild( secondRadio );
const tabbables = find( node );
expect( tabbables ).toEqual( [ firstRadio, text, secondRadio ] );
} );
} );
} );

25
node_modules/@wordpress/dom/src/test/utils.js generated vendored Normal file
View File

@@ -0,0 +1,25 @@
/**
* Internal dependencies
*/
import { assertIsDefined } from '../utils/assert-is-defined';
describe( 'assertIsDefined', () => {
it( 'should throw if the variable is null', () => {
expect( () => assertIsDefined( null, 'val' ) ).toThrow(
"Expected 'val' to be defined, but received null"
);
} );
it( 'should throw if the variable is undefined', () => {
expect( () => assertIsDefined( undefined, 'val' ) ).toThrow(
"Expected 'val' to be defined, but received undefined"
);
} );
it.each( [ 0, '', NaN, -0, 1, new String(), {}, [], false, Infinity ] )(
'should not throw if the value is %s',
( val ) => {
expect( () => assertIsDefined( val, 'val' ) ).not.toThrow();
}
);
} );

View File

@@ -0,0 +1,55 @@
/**
* Given an element type, returns an HTMLElement with an emulated layout,
* since JSDOM does have its own internal layout engine.
*
* @param {string} type Element type.
*
* @return {HTMLElement} Layout-emulated element.
*/
export default function createElement( type ) {
const element = document.createElement( type );
const ifNotHidden = ( value, elseValue ) =>
function () {
let isHidden = false;
let node = this;
do {
isHidden =
node.style.display === 'none' ||
node.style.visibility === 'hidden';
node = node.parentNode;
} while (
! isHidden &&
node &&
node.nodeType === node.ELEMENT_NODE
);
return isHidden ? elseValue : value;
};
Object.defineProperties( element, {
offsetHeight: {
get: ifNotHidden( 10, 0 ),
},
offsetWidth: {
get: ifNotHidden( 10, 0 ),
},
} );
element.getClientRects = ifNotHidden(
[
{
width: 10,
height: 10,
top: 0,
right: 10,
bottom: 10,
left: 0,
},
],
[]
);
return element;
}