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,174 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
FirstInputPolyfillEntry,
FirstInputPolyfillCallback,
} from '../../types.js';
type addOrRemoveEventListener =
| typeof addEventListener
| typeof removeEventListener;
let firstInputEvent: Event | null;
let firstInputDelay: number;
let firstInputTimeStamp: Date;
let callbacks: FirstInputPolyfillCallback[];
const listenerOpts: AddEventListenerOptions = {passive: true, capture: true};
const startTimeStamp: Date = new Date();
/**
* Accepts a callback to be invoked once the first input delay and event
* are known.
*/
export const firstInputPolyfill = (
onFirstInput: FirstInputPolyfillCallback,
) => {
callbacks.push(onFirstInput);
reportFirstInputDelayIfRecordedAndValid();
};
export const resetFirstInputPolyfill = () => {
callbacks = [];
firstInputDelay = -1;
firstInputEvent = null;
eachEventType(addEventListener);
};
/**
* Records the first input delay and event, so subsequent events can be
* ignored. All added event listeners are then removed.
*/
const recordFirstInputDelay = (delay: number, event: Event) => {
if (!firstInputEvent) {
firstInputEvent = event;
firstInputDelay = delay;
firstInputTimeStamp = new Date();
eachEventType(removeEventListener);
reportFirstInputDelayIfRecordedAndValid();
}
};
/**
* Reports the first input delay and event (if they're recorded and valid)
* by running the array of callback functions.
*/
const reportFirstInputDelayIfRecordedAndValid = () => {
// In some cases the recorded delay is clearly wrong, e.g. it's negative
// or it's larger than the delta between now and initialization.
// - https://github.com/GoogleChromeLabs/first-input-delay/issues/4
// - https://github.com/GoogleChromeLabs/first-input-delay/issues/6
// - https://github.com/GoogleChromeLabs/first-input-delay/issues/7
if (
firstInputDelay >= 0 &&
// @ts-ignore (subtracting two dates always returns a number)
firstInputDelay < firstInputTimeStamp - startTimeStamp
) {
const entry = {
entryType: 'first-input',
name: firstInputEvent!.type,
target: firstInputEvent!.target,
cancelable: firstInputEvent!.cancelable,
startTime: firstInputEvent!.timeStamp,
processingStart: firstInputEvent!.timeStamp + firstInputDelay,
} as FirstInputPolyfillEntry;
callbacks.forEach(function (callback) {
callback(entry);
});
callbacks = [];
}
};
/**
* Handles pointer down events, which are a special case.
* Pointer events can trigger main or compositor thread behavior.
* We differentiate these cases based on whether or not we see a
* 'pointercancel' event, which are fired when we scroll. If we're scrolling
* we don't need to report input delay since FID excludes scrolling and
* pinch/zooming.
*/
const onPointerDown = (delay: number, event: Event) => {
/**
* Responds to 'pointerup' events and records a delay. If a pointer up event
* is the next event after a pointerdown event, then it's not a scroll or
* a pinch/zoom.
*/
const onPointerUp = () => {
recordFirstInputDelay(delay, event);
removePointerEventListeners();
};
/**
* Responds to 'pointercancel' events and removes pointer listeners.
* If a 'pointercancel' is the next event to fire after a pointerdown event,
* it means this is a scroll or pinch/zoom interaction.
*/
const onPointerCancel = () => {
removePointerEventListeners();
};
/**
* Removes added pointer event listeners.
*/
const removePointerEventListeners = () => {
removeEventListener('pointerup', onPointerUp, listenerOpts);
removeEventListener('pointercancel', onPointerCancel, listenerOpts);
};
addEventListener('pointerup', onPointerUp, listenerOpts);
addEventListener('pointercancel', onPointerCancel, listenerOpts);
};
/**
* Handles all input events and records the time between when the event
* was received by the operating system and when it's JavaScript listeners
* were able to run.
*/
const onInput = (event: Event) => {
// Only count cancelable events, which should trigger behavior
// important to the user.
if (event.cancelable) {
// In some browsers `event.timeStamp` returns a `DOMTimeStamp` value
// (epoch time) instead of the newer `DOMHighResTimeStamp`
// (document-origin time). To check for that we assume any timestamp
// greater than 1 trillion is a `DOMTimeStamp`, and compare it using
// the `Date` object rather than `performance.now()`.
// - https://github.com/GoogleChromeLabs/first-input-delay/issues/4
const isEpochTime = event.timeStamp > 1e12;
const now = isEpochTime ? new Date() : performance.now();
// Input delay is the delta between when the system received the event
// (e.g. event.timeStamp) and when it could run the callback (e.g. `now`).
const delay = (now as number) - event.timeStamp;
if (event.type == 'pointerdown') {
onPointerDown(delay, event);
} else {
recordFirstInputDelay(delay, event);
}
}
};
/**
* Invokes the passed callback const for = each event type with t =>he
* `onInput` const and = `listenerOpts =>`.
*/
const eachEventType = (callback: addOrRemoveEventListener) => {
const eventTypes = ['mousedown', 'keydown', 'touchstart', 'pointerdown'];
eventTypes.forEach((type) => callback(type, onInput, listenerOpts));
};

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
let firstHiddenTime = document.visibilityState === 'hidden' ? 0 : Infinity;
const onVisibilityChange = (event: Event) => {
if (document.visibilityState === 'hidden') {
firstHiddenTime = event.timeStamp;
removeEventListener('visibilitychange', onVisibilityChange, true);
}
};
// Note: do not add event listeners unconditionally (outside of polyfills).
addEventListener('visibilitychange', onVisibilityChange, true);
export const getFirstHiddenTime = () => firstHiddenTime;

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2022 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {observe} from '../observe.js';
declare global {
interface Performance {
interactionCount: number;
}
}
let interactionCountEstimate = 0;
let minKnownInteractionId = Infinity;
let maxKnownInteractionId = 0;
const updateEstimate = (entries: PerformanceEventTiming[]) => {
entries.forEach((e) => {
if (e.interactionId) {
minKnownInteractionId = Math.min(minKnownInteractionId, e.interactionId);
maxKnownInteractionId = Math.max(maxKnownInteractionId, e.interactionId);
interactionCountEstimate = maxKnownInteractionId
? (maxKnownInteractionId - minKnownInteractionId) / 7 + 1
: 0;
}
});
};
let po: PerformanceObserver | undefined;
/**
* Returns the `interactionCount` value using the native API (if available)
* or the polyfill estimate in this module.
*/
export const getInteractionCount = () => {
return po ? interactionCountEstimate : performance.interactionCount || 0;
};
/**
* Feature detects native support or initializes the polyfill if needed.
*/
export const initInteractionCountPolyfill = () => {
if ('interactionCount' in performance || po) return;
po = observe('event', updateEstimate, {
type: 'event',
buffered: true,
durationThreshold: 0,
} as PerformanceObserverInit);
};