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

37
node_modules/@wordpress/a11y/src/add-container.js generated vendored Normal file
View File

@@ -0,0 +1,37 @@
/**
* Build the live regions markup.
*
* @param {string} [ariaLive] Value for the 'aria-live' attribute; default: 'polite'.
*
* @return {HTMLDivElement} The ARIA live region HTML element.
*/
export default function addContainer( ariaLive = 'polite' ) {
const container = document.createElement( 'div' );
container.id = `a11y-speak-${ ariaLive }`;
container.className = 'a11y-speak-region';
container.setAttribute(
'style',
'position: absolute;' +
'margin: -1px;' +
'padding: 0;' +
'height: 1px;' +
'width: 1px;' +
'overflow: hidden;' +
'clip: rect(1px, 1px, 1px, 1px);' +
'-webkit-clip-path: inset(50%);' +
'clip-path: inset(50%);' +
'border: 0;' +
'word-wrap: normal !important;'
);
container.setAttribute( 'aria-live', ariaLive );
container.setAttribute( 'aria-relevant', 'additions text' );
container.setAttribute( 'aria-atomic', 'true' );
const { body } = document;
if ( body ) {
body.appendChild( container );
}
return container;
}

43
node_modules/@wordpress/a11y/src/add-intro-text.js generated vendored Normal file
View File

@@ -0,0 +1,43 @@
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
/**
* Build the explanatory text to be placed before the aria live regions.
*
* This text is initially hidden from assistive technologies by using a `hidden`
* HTML attribute which is then removed once a message fills the aria-live regions.
*
* @return {HTMLParagraphElement} The explanatory text HTML element.
*/
export default function addIntroText() {
const introText = document.createElement( 'p' );
introText.id = 'a11y-speak-intro-text';
introText.className = 'a11y-speak-intro-text';
introText.textContent = __( 'Notifications' );
introText.setAttribute(
'style',
'position: absolute;' +
'margin: -1px;' +
'padding: 0;' +
'height: 1px;' +
'width: 1px;' +
'overflow: hidden;' +
'clip: rect(1px, 1px, 1px, 1px);' +
'-webkit-clip-path: inset(50%);' +
'clip-path: inset(50%);' +
'border: 0;' +
'word-wrap: normal !important;'
);
introText.setAttribute( 'hidden', 'hidden' );
const { body } = document;
if ( body ) {
body.appendChild( introText );
}
return introText;
}

16
node_modules/@wordpress/a11y/src/clear.js generated vendored Normal file
View File

@@ -0,0 +1,16 @@
/**
* Clears the a11y-speak-region elements and hides the explanatory text.
*/
export default function clear() {
const regions = document.getElementsByClassName( 'a11y-speak-region' );
const introText = document.getElementById( 'a11y-speak-intro-text' );
for ( let i = 0; i < regions.length; i++ ) {
regions[ i ].textContent = '';
}
// Make sure the explanatory text is hidden from assistive technologies.
if ( introText ) {
introText.setAttribute( 'hidden', 'hidden' );
}
}

31
node_modules/@wordpress/a11y/src/filter-message.js generated vendored Normal file
View File

@@ -0,0 +1,31 @@
let previousMessage = '';
/**
* Filter the message to be announced to the screenreader.
*
* @param {string} message The message to be announced.
*
* @return {string} The filtered message.
*/
export default function filterMessage( message ) {
/*
* Strip HTML tags (if any) from the message string. Ideally, messages should
* be simple strings, carefully crafted for specific use with A11ySpeak.
* When re-using already existing strings this will ensure simple HTML to be
* stripped out and replaced with a space. Browsers will collapse multiple
* spaces natively.
*/
message = message.replace( /<[^<>]+>/g, ' ' );
/*
* Safari + VoiceOver don't announce repeated, identical strings. We use
* a `no-break space` to force them to think identical strings are different.
*/
if ( previousMessage === message ) {
message += '\u00A0';
}
previousMessage = message;
return message;
}

88
node_modules/@wordpress/a11y/src/index.js generated vendored Normal file
View File

@@ -0,0 +1,88 @@
/**
* WordPress dependencies
*/
import domReady from '@wordpress/dom-ready';
/**
* Internal dependencies
*/
import addIntroText from './add-intro-text';
import addContainer from './add-container';
import clear from './clear';
import filterMessage from './filter-message';
/**
* Create the live regions.
*/
export function setup() {
const introText = document.getElementById( 'a11y-speak-intro-text' );
const containerAssertive = document.getElementById(
'a11y-speak-assertive'
);
const containerPolite = document.getElementById( 'a11y-speak-polite' );
if ( introText === null ) {
addIntroText();
}
if ( containerAssertive === null ) {
addContainer( 'assertive' );
}
if ( containerPolite === null ) {
addContainer( 'polite' );
}
}
/**
* Run setup on domReady.
*/
domReady( setup );
/**
* Allows you to easily announce dynamic interface updates to screen readers using ARIA live regions.
* This module is inspired by the `speak` function in `wp-a11y.js`.
*
* @param {string} message The message to be announced by assistive technologies.
* @param {string} [ariaLive] The politeness level for aria-live; default: 'polite'.
*
* @example
* ```js
* import { speak } from '@wordpress/a11y';
*
* // For polite messages that shouldn't interrupt what screen readers are currently announcing.
* speak( 'The message you want to send to the ARIA live region' );
*
* // For assertive messages that should interrupt what screen readers are currently announcing.
* speak( 'The message you want to send to the ARIA live region', 'assertive' );
* ```
*/
export function speak( message, ariaLive ) {
/*
* Clear previous messages to allow repeated strings being read out and hide
* the explanatory text from assistive technologies.
*/
clear();
message = filterMessage( message );
const introText = document.getElementById( 'a11y-speak-intro-text' );
const containerAssertive = document.getElementById(
'a11y-speak-assertive'
);
const containerPolite = document.getElementById( 'a11y-speak-polite' );
if ( containerAssertive && ariaLive === 'assertive' ) {
containerAssertive.textContent = message;
} else if ( containerPolite ) {
containerPolite.textContent = message;
}
/*
* Make the explanatory text available to assistive technologies by removing
* the 'hidden' HTML attribute.
*/
if ( introText ) {
introText.removeAttribute( 'hidden' );
}
}

18
node_modules/@wordpress/a11y/src/index.native.js generated vendored Normal file
View File

@@ -0,0 +1,18 @@
/**
* Internal dependencies
*/
import filterMessage from './filter-message';
/**
* Update the ARIA live notification area text node.
*
* @param {string} message The message to be announced by Assistive Technologies.
* @param {string} [ariaLive] The politeness level for aria-live; default: 'polite'.
*/
export function speak( message, ariaLive ) {
message = filterMessage( message );
// TODO: Use native module to speak message.
if ( ariaLive === 'assertive' ) {
} else {
}
}

View File

@@ -0,0 +1,57 @@
/**
* Internal dependencies
*/
import addContainer from '../add-container';
describe( 'addContainer', () => {
describe( 'with polite param', () => {
it( 'should create an aria-live element with aria-live attr set to polite', () => {
const container = addContainer( 'polite' );
expect( container ).not.toBeNull();
expect( container.className ).toBe( 'a11y-speak-region' );
expect( container.id ).toBe( 'a11y-speak-polite' );
expect( container ).toHaveAttribute( 'style' );
expect( container ).toHaveAttribute( 'aria-live', 'polite' );
expect( container ).toHaveAttribute(
'aria-relevant',
'additions text'
);
expect( container ).toHaveAttribute( 'aria-atomic', 'true' );
} );
} );
describe( 'with assertive param', () => {
it( 'should create an aria-live element with aria-live attr set to assertive', () => {
const container = addContainer( 'assertive' );
expect( container ).not.toBeNull();
expect( container.className ).toBe( 'a11y-speak-region' );
expect( container.id ).toBe( 'a11y-speak-assertive' );
expect( container ).toHaveAttribute( 'style' );
expect( container ).toHaveAttribute( 'aria-live', 'assertive' );
expect( container ).toHaveAttribute(
'aria-relevant',
'additions text'
);
expect( container ).toHaveAttribute( 'aria-atomic', 'true' );
} );
} );
describe( 'without param', () => {
it( 'should default to creating an aria-live element with aria-live attr set to polite', () => {
const container = addContainer( 'polite' );
expect( container ).not.toBeNull();
expect( container.className ).toBe( 'a11y-speak-region' );
expect( container.id ).toBe( 'a11y-speak-polite' );
expect( container ).toHaveAttribute( 'style' );
expect( container ).toHaveAttribute( 'aria-live', 'polite' );
expect( container ).toHaveAttribute(
'aria-relevant',
'additions text'
);
expect( container ).toHaveAttribute( 'aria-atomic', 'true' );
} );
} );
} );

22
node_modules/@wordpress/a11y/src/test/clear.test.js generated vendored Normal file
View File

@@ -0,0 +1,22 @@
/**
* Internal dependencies
*/
import clear from '../clear';
describe( 'clear', () => {
it( 'should clear all a11y-speak-region elements', () => {
const container1 = document.createElement( 'div' );
container1.className = 'a11y-speak-region';
container1.textContent = 'not empty';
document.body.appendChild( container1 );
const container2 = document.createElement( 'div' );
container2.className = 'a11y-speak-region';
container2.textContent = 'not empty';
document.body.appendChild( container2 );
clear();
expect( container1 ).toBeEmptyDOMElement();
expect( container2 ).toBeEmptyDOMElement();
} );
} );

View File

@@ -0,0 +1,28 @@
/**
* Internal dependencies
*/
import filterMessage from '../filter-message';
describe( 'filterMessage', () => {
describe( 'when a clean message is passed in', () => {
it( 'should return the message unfiltered', () => {
const actual = filterMessage( 'clean message.' );
expect( actual ).toBe( 'clean message.' );
} );
} );
describe( 'when a message is passed in twice in a row', () => {
it( 'should add a space to the message to make sure it is announced again', () => {
filterMessage( 'repeated message.' );
const actual = filterMessage( 'repeated message.' );
expect( actual ).toBe( 'repeated message.\u00A0' );
} );
} );
describe( 'when a message contains html tags', () => {
it( 'should strip the html tags and replace them with spaces', () => {
const actual = filterMessage( '<p>html paragraph</p>' );
expect( actual ).toBe( ' html paragraph ' );
} );
} );
} );

124
node_modules/@wordpress/a11y/src/test/index.test.js generated vendored Normal file
View File

@@ -0,0 +1,124 @@
/**
* WordPress dependencies
*/
import domReady from '@wordpress/dom-ready';
/**
* Internal dependencies
*/
import { setup, speak } from '../';
import clear from '../clear';
import filterMessage from '../filter-message';
jest.mock( '../clear', () => {
return jest.fn();
} );
jest.mock( '@wordpress/dom-ready', () => {
return jest.fn( ( callback ) => {
callback();
} );
} );
jest.mock( '../filter-message', () => {
return jest.fn( ( message ) => {
return message;
} );
} );
describe( 'speak', () => {
let containerPolite = document.getElementById( 'a11y-speak-polite' );
let containerAssertive = document.getElementById( 'a11y-speak-assertive' );
beforeEach( () => {
containerPolite.textContent = '';
containerAssertive.textContent = '';
} );
describe( 'on import', () => {
it( 'should call domReady', () => {
expect( domReady ).toHaveBeenCalled();
} );
} );
describe( 'in default mode', () => {
it( 'should set the textcontent of the polite aria-live region', () => {
speak( 'default message' );
expect( containerPolite ).toHaveTextContent( 'default message' );
expect( containerAssertive ).toBeEmptyDOMElement();
expect( clear ).toHaveBeenCalled();
expect( filterMessage ).toHaveBeenCalledWith( 'default message' );
} );
} );
describe( 'in assertive mode', () => {
it( 'should set the textcontent of the assertive aria-live region', () => {
speak( 'assertive message', 'assertive' );
expect( containerPolite ).toBeEmptyDOMElement();
expect( containerAssertive ).toHaveTextContent(
'assertive message'
);
} );
} );
describe( 'in explicit polite mode', () => {
it( 'should set the textcontent of the polite aria-live region', () => {
speak( 'polite message', 'polite' );
expect( containerPolite ).toHaveTextContent( 'polite message' );
expect( containerAssertive ).toBeEmptyDOMElement();
} );
} );
describe( 'when somehow the assertive container is not present', () => {
beforeEach( () => {
document.getElementById( 'a11y-speak-assertive' ).remove();
} );
afterEach( () => {
setup();
containerAssertive = document.getElementById(
'a11y-speak-assertive'
);
} );
it( 'should set the textcontent of the polite aria-live region', () => {
speak( 'message', 'assertive' );
expect( containerPolite ).toHaveTextContent( 'message' );
expect(
document.getElementById( 'a11y-speak-assertive' )
).toBeNull();
} );
} );
describe( 'when somehow the both containers are not present', () => {
beforeEach( () => {
containerAssertive.remove();
containerPolite.remove();
} );
afterEach( () => {
setup();
containerPolite = document.getElementById( 'a11y-speak-polite' );
containerAssertive = document.getElementById(
'a11y-speak-assertive'
);
} );
it( 'should set the textcontent of the polite aria-live region', () => {
expect( document.getElementById( 'a11y-speak-polite' ) ).toBeNull();
expect(
document.getElementById( 'a11y-speak-assertive' )
).toBeNull();
} );
} );
describe( 'setup when the elements already exist', () => {
it( 'should not create the aria live regions again', () => {
const before =
document.getElementsByClassName( 'a11y-speak-region' ).length;
setup();
const after =
document.getElementsByClassName( 'a11y-speak-region' ).length;
expect( before ).toBe( after );
} );
} );
} );