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

185
node_modules/@wordpress/priority-queue/src/index.js generated vendored Normal file
View File

@@ -0,0 +1,185 @@
/**
* Internal dependencies
*/
import requestIdleCallback from './request-idle-callback';
/**
* Enqueued callback to invoke once idle time permits.
*
* @typedef {()=>void} WPPriorityQueueCallback
*/
/**
* An object used to associate callbacks in a particular context grouping.
*
* @typedef {{}} WPPriorityQueueContext
*/
/**
* Function to add callback to priority queue.
*
* @typedef {(element:WPPriorityQueueContext,item:WPPriorityQueueCallback)=>void} WPPriorityQueueAdd
*/
/**
* Function to flush callbacks from priority queue.
*
* @typedef {(element:WPPriorityQueueContext)=>boolean} WPPriorityQueueFlush
*/
/**
* Reset the queue.
*
* @typedef {()=>void} WPPriorityQueueReset
*/
/**
* Priority queue instance.
*
* @typedef {Object} WPPriorityQueue
*
* @property {WPPriorityQueueAdd} add Add callback to queue for context.
* @property {WPPriorityQueueFlush} flush Flush queue for context.
* @property {WPPriorityQueueFlush} cancel Clear queue for context.
* @property {WPPriorityQueueReset} reset Reset queue.
*/
/**
* Creates a context-aware queue that only executes
* the last task of a given context.
*
* @example
*```js
* import { createQueue } from '@wordpress/priority-queue';
*
* const queue = createQueue();
*
* // Context objects.
* const ctx1 = {};
* const ctx2 = {};
*
* // For a given context in the queue, only the last callback is executed.
* queue.add( ctx1, () => console.log( 'This will be printed first' ) );
* queue.add( ctx2, () => console.log( 'This won\'t be printed' ) );
* queue.add( ctx2, () => console.log( 'This will be printed second' ) );
*```
*
* @return {WPPriorityQueue} Queue object with `add`, `flush` and `reset` methods.
*/
export const createQueue = () => {
/** @type {Map<WPPriorityQueueContext, WPPriorityQueueCallback>} */
const waitingList = new Map();
let isRunning = false;
/**
* Callback to process as much queue as time permits.
*
* Map Iteration follows the original insertion order. This means that here
* we can iterate the queue and know that the first contexts which were
* added will be run first. On the other hand, if anyone adds a new callback
* for an existing context it will supplant the previously-set callback for
* that context because we reassigned that map key's value.
*
* In the case that a callback adds a new callback to its own context then
* the callback it adds will appear at the end of the iteration and will be
* run only after all other existing contexts have finished executing.
*
* @param {IdleDeadline|number} deadline Idle callback deadline object, or
* animation frame timestamp.
*/
const runWaitingList = ( deadline ) => {
for ( const [ nextElement, callback ] of waitingList ) {
waitingList.delete( nextElement );
callback();
if (
'number' === typeof deadline ||
deadline.timeRemaining() <= 0
) {
break;
}
}
if ( waitingList.size === 0 ) {
isRunning = false;
return;
}
requestIdleCallback( runWaitingList );
};
/**
* Add a callback to the queue for a given context.
*
* If errors with undefined callbacks are encountered double check that
* all of your useSelect calls have the right dependencies set correctly
* in their second parameter. Missing dependencies can cause unexpected
* loops and race conditions in the queue.
*
* @type {WPPriorityQueueAdd}
*
* @param {WPPriorityQueueContext} element Context object.
* @param {WPPriorityQueueCallback} item Callback function.
*/
const add = ( element, item ) => {
waitingList.set( element, item );
if ( ! isRunning ) {
isRunning = true;
requestIdleCallback( runWaitingList );
}
};
/**
* Flushes queue for a given context, returning true if the flush was
* performed, or false if there is no queue for the given context.
*
* @type {WPPriorityQueueFlush}
*
* @param {WPPriorityQueueContext} element Context object.
*
* @return {boolean} Whether flush was performed.
*/
const flush = ( element ) => {
const callback = waitingList.get( element );
if ( undefined === callback ) {
return false;
}
waitingList.delete( element );
callback();
return true;
};
/**
* Clears the queue for a given context, cancelling the callbacks without
* executing them. Returns `true` if there were scheduled callbacks to cancel,
* or `false` if there was is no queue for the given context.
*
* @type {WPPriorityQueueFlush}
*
* @param {WPPriorityQueueContext} element Context object.
*
* @return {boolean} Whether any callbacks got cancelled.
*/
const cancel = ( element ) => {
return waitingList.delete( element );
};
/**
* Reset the queue without running the pending callbacks.
*
* @type {WPPriorityQueueReset}
*/
const reset = () => {
waitingList.clear();
isRunning = false;
};
return {
add,
flush,
cancel,
reset,
};
};

View File

@@ -0,0 +1,23 @@
/**
* External dependencies
*/
import 'requestidlecallback';
/**
* @typedef {( timeOrDeadline: IdleDeadline | number ) => void} Callback
*/
/**
* @return {(callback: Callback) => void} RequestIdleCallback
*/
export function createRequestIdleCallback() {
if ( typeof window === 'undefined' ) {
return ( callback ) => {
setTimeout( () => callback( Date.now() ), 0 );
};
}
return window.requestIdleCallback;
}
export default createRequestIdleCallback();

View File

@@ -0,0 +1,158 @@
/**
* Internal dependencies
*/
import { createQueue } from '../';
import requestIdleCallback from '../request-idle-callback';
jest.mock( '../request-idle-callback', () => {
const emitter = new ( jest.requireActual( 'events' ).EventEmitter )();
return Object.assign(
( callback ) =>
emitter.once( 'tick', ( deadline = Date.now() ) =>
callback( deadline )
),
{ tick: ( deadline ) => emitter.emit( 'tick', deadline ) }
);
} );
describe( 'createQueue', () => {
let queue;
beforeEach( () => {
queue = createQueue();
} );
describe( 'add', () => {
it( 'runs callback after processing waiting queue', () => {
const callback = jest.fn();
queue.add( {}, callback );
expect( callback ).not.toHaveBeenCalled();
requestIdleCallback.tick();
expect( callback ).toHaveBeenCalled();
} );
it( 'runs callbacks in order by distinct added element', () => {
const elementA = {};
const elementB = {};
const callbackElementA = jest.fn();
const callbackElementB = jest.fn();
queue.add( elementA, callbackElementA );
queue.add( elementB, callbackElementB );
expect( callbackElementA ).not.toHaveBeenCalled();
expect( callbackElementB ).not.toHaveBeenCalled();
// ElementA was added first, and should be called first after tick.
requestIdleCallback.tick();
expect( callbackElementA ).toHaveBeenCalledTimes( 1 );
expect( callbackElementB ).not.toHaveBeenCalled();
// ElementB will be processed after second tick.
requestIdleCallback.tick();
expect( callbackElementA ).toHaveBeenCalledTimes( 1 );
expect( callbackElementB ).toHaveBeenCalledTimes( 1 );
} );
it( 'calls most recently added callback if added for same element', () => {
const element = {};
const callbackOne = jest.fn();
const callbackTwo = jest.fn();
queue.add( element, callbackOne );
queue.add( element, callbackTwo );
expect( callbackOne ).not.toHaveBeenCalled();
expect( callbackTwo ).not.toHaveBeenCalled();
requestIdleCallback.tick();
expect( callbackOne ).not.toHaveBeenCalled();
expect( callbackTwo ).toHaveBeenCalledTimes( 1 );
} );
it( 'processes queue as long as time allows, with idle deadline implementation', () => {
const elementA = {};
const elementB = {};
const elementC = {};
const callbackElementA = jest.fn();
const callbackElementB = jest.fn();
const callbackElementC = jest.fn();
queue.add( elementA, callbackElementA );
queue.add( elementB, callbackElementB );
queue.add( elementC, callbackElementC );
expect( callbackElementA ).not.toHaveBeenCalled();
expect( callbackElementB ).not.toHaveBeenCalled();
expect( callbackElementC ).not.toHaveBeenCalled();
// Mock implementation such that with the first call, it reports as
// having some time remaining, but no time remaining on the second.
const timeRemaining = jest
.fn()
.mockImplementationOnce( () => 100 )
.mockImplementationOnce( () => 0 );
requestIdleCallback.tick( { timeRemaining } );
// Given the above mock, expect that the initial callback would
// process A, then time remaining would allow for B to be processed,
// but C would not be processed because no time remains.
expect( callbackElementA ).toHaveBeenCalledTimes( 1 );
expect( callbackElementB ).toHaveBeenCalledTimes( 1 );
expect( callbackElementC ).not.toHaveBeenCalled();
} );
} );
describe( 'flush', () => {
it( 'invokes all callbacks associated with element', () => {
const elementA = {};
const elementB = {};
const callbackElementA = jest.fn();
const callbackElementB = jest.fn();
queue.add( elementA, callbackElementA );
queue.add( elementB, callbackElementB );
expect( callbackElementA ).not.toHaveBeenCalled();
expect( callbackElementB ).not.toHaveBeenCalled();
queue.flush( elementA );
// Only ElementA callback should have been called (synchronously).
expect( callbackElementA ).toHaveBeenCalledTimes( 1 );
expect( callbackElementB ).not.toHaveBeenCalled();
// Verify that callback still called only once after tick (verify
// removal).
requestIdleCallback.tick();
expect( callbackElementA ).toHaveBeenCalledTimes( 1 );
expect( callbackElementB ).toHaveBeenCalledTimes( 1 );
} );
} );
describe( 'cancel', () => {
it( 'removes all callbacks associated with element without executing', () => {
const elementA = {};
const elementB = {};
const callbackElementA = jest.fn();
const callbackElementB = jest.fn();
queue.add( elementA, callbackElementA );
queue.add( elementB, callbackElementB );
expect( callbackElementA ).not.toHaveBeenCalled();
expect( callbackElementB ).not.toHaveBeenCalled();
expect( queue.cancel( elementA ) ).toBe( true );
// No callbacks should have been called.
expect( callbackElementA ).not.toHaveBeenCalled();
expect( callbackElementB ).not.toHaveBeenCalled();
// A subsequent `flush` has nothing to remove.
expect( queue.flush( elementA ) ).toBe( false );
// The queue for `elementA` remained intact and can be successfully flushed.
expect( queue.flush( elementB ) ).toBe( true );
expect( callbackElementB ).toHaveBeenCalledTimes( 1 );
} );
} );
} );