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

90
node_modules/web-vitals/src/attribution/onCLS.ts generated vendored Normal file
View File

@@ -0,0 +1,90 @@
/*
* 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 {getLoadState} from '../lib/getLoadState.js';
import {getSelector} from '../lib/getSelector.js';
import {onCLS as unattributedOnCLS} from '../onCLS.js';
import {
CLSReportCallback,
CLSReportCallbackWithAttribution,
CLSMetric,
CLSMetricWithAttribution,
ReportOpts,
} from '../types.js';
const getLargestLayoutShiftEntry = (entries: LayoutShift[]) => {
return entries.reduce((a, b) => (a && a.value > b.value ? a : b));
};
const getLargestLayoutShiftSource = (sources: LayoutShiftAttribution[]) => {
return sources.find((s) => s.node && s.node.nodeType === 1) || sources[0];
};
const attributeCLS = (metric: CLSMetric): void => {
if (metric.entries.length) {
const largestEntry = getLargestLayoutShiftEntry(metric.entries);
if (largestEntry && largestEntry.sources && largestEntry.sources.length) {
const largestSource = getLargestLayoutShiftSource(largestEntry.sources);
if (largestSource) {
(metric as CLSMetricWithAttribution).attribution = {
largestShiftTarget: getSelector(largestSource.node),
largestShiftTime: largestEntry.startTime,
largestShiftValue: largestEntry.value,
largestShiftSource: largestSource,
largestShiftEntry: largestEntry,
loadState: getLoadState(largestEntry.startTime),
};
return;
}
}
}
// Set an empty object if no other attribution has been set.
(metric as CLSMetricWithAttribution).attribution = {};
};
/**
* Calculates the [CLS](https://web.dev/articles/cls) value for the current page and
* calls the `callback` function once the value is ready to be reported, along
* with all `layout-shift` performance entries that were used in the metric
* value calculation. The reported value is a `double` (corresponding to a
* [layout shift score](https://web.dev/articles/cls#layout_shift_score)).
*
* If the `reportAllChanges` configuration option is set to `true`, the
* `callback` function will be called as soon as the value is initially
* determined as well as any time the value changes throughout the page
* lifespan.
*
* _**Important:** CLS should be continually monitored for changes throughout
* the entire lifespan of a page—including if the user returns to the page after
* it's been hidden/backgrounded. However, since browsers often [will not fire
* additional callbacks once the user has backgrounded a
* page](https://developer.chrome.com/blog/page-lifecycle-api/#advice-hidden),
* `callback` is always called when the page's visibility state changes to
* hidden. As a result, the `callback` function might be called multiple times
* during the same page load._
*/
export const onCLS = (
onReport: CLSReportCallbackWithAttribution,
opts?: ReportOpts,
) => {
unattributedOnCLS(
((metric: CLSMetricWithAttribution) => {
attributeCLS(metric);
onReport(metric);
}) as CLSReportCallback,
opts,
);
};

73
node_modules/web-vitals/src/attribution/onFCP.ts generated vendored Normal file
View File

@@ -0,0 +1,73 @@
/*
* 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 {getBFCacheRestoreTime} from '../lib/bfcache.js';
import {getLoadState} from '../lib/getLoadState.js';
import {getNavigationEntry} from '../lib/getNavigationEntry.js';
import {onFCP as unattributedOnFCP} from '../onFCP.js';
import {
FCPMetric,
FCPMetricWithAttribution,
FCPReportCallback,
FCPReportCallbackWithAttribution,
ReportOpts,
} from '../types.js';
const attributeFCP = (metric: FCPMetric): void => {
if (metric.entries.length) {
const navigationEntry = getNavigationEntry();
const fcpEntry = metric.entries[metric.entries.length - 1];
if (navigationEntry) {
const activationStart = navigationEntry.activationStart || 0;
const ttfb = Math.max(0, navigationEntry.responseStart - activationStart);
(metric as FCPMetricWithAttribution).attribution = {
timeToFirstByte: ttfb,
firstByteToFCP: metric.value - ttfb,
loadState: getLoadState(metric.entries[0].startTime),
navigationEntry,
fcpEntry,
};
return;
}
}
// Set an empty object if no other attribution has been set.
(metric as FCPMetricWithAttribution).attribution = {
timeToFirstByte: 0,
firstByteToFCP: metric.value,
loadState: getLoadState(getBFCacheRestoreTime()),
};
};
/**
* Calculates the [FCP](https://web.dev/articles/fcp) value for the current page and
* calls the `callback` function once the value is ready, along with the
* relevant `paint` performance entry used to determine the value. The reported
* value is a `DOMHighResTimeStamp`.
*/
export const onFCP = (
onReport: FCPReportCallbackWithAttribution,
opts?: ReportOpts,
) => {
unattributedOnFCP(
((metric: FCPMetricWithAttribution) => {
attributeFCP(metric);
onReport(metric);
}) as FCPReportCallback,
opts,
);
};

59
node_modules/web-vitals/src/attribution/onFID.ts generated vendored Normal file
View File

@@ -0,0 +1,59 @@
/*
* 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 {getLoadState} from '../lib/getLoadState.js';
import {getSelector} from '../lib/getSelector.js';
import {onFID as unattributedOnFID} from '../onFID.js';
import {
FIDMetric,
FIDMetricWithAttribution,
FIDReportCallback,
FIDReportCallbackWithAttribution,
ReportOpts,
} from '../types.js';
const attributeFID = (metric: FIDMetric): void => {
const fidEntry = metric.entries[0];
(metric as FIDMetricWithAttribution).attribution = {
eventTarget: getSelector(fidEntry.target),
eventType: fidEntry.name,
eventTime: fidEntry.startTime,
eventEntry: fidEntry,
loadState: getLoadState(fidEntry.startTime),
};
};
/**
* Calculates the [FID](https://web.dev/articles/fid) value for the current page and
* calls the `callback` function once the value is ready, along with the
* relevant `first-input` performance entry used to determine the value. The
* reported value is a `DOMHighResTimeStamp`.
*
* _**Important:** since FID is only reported after the user interacts with the
* page, it's possible that it will not be reported for some page loads._
*/
export const onFID = (
onReport: FIDReportCallbackWithAttribution,
opts?: ReportOpts,
) => {
unattributedOnFID(
((metric: FIDMetricWithAttribution) => {
attributeFID(metric);
onReport(metric);
}) as FIDReportCallback,
opts,
);
};

100
node_modules/web-vitals/src/attribution/onINP.ts generated vendored Normal file
View File

@@ -0,0 +1,100 @@
/*
* 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 {getLoadState} from '../lib/getLoadState.js';
import {getSelector} from '../lib/getSelector.js';
import {onINP as unattributedOnINP} from '../onINP.js';
import {
INPMetric,
INPMetricWithAttribution,
INPReportCallback,
INPReportCallbackWithAttribution,
ReportOpts,
} from '../types.js';
const attributeINP = (metric: INPMetric): void => {
if (metric.entries.length) {
const longestEntry = metric.entries.sort((a, b) => {
// Sort by: 1) duration (DESC), then 2) processing time (DESC)
return (
b.duration - a.duration ||
b.processingEnd -
b.processingStart -
(a.processingEnd - a.processingStart)
);
})[0];
// Currently Chrome can return a null target for certain event types
// (especially pointer events). As the event target should be the same
// for all events in the same interaction, we pick the first non-null one.
// TODO: remove when 1367329 is resolved
// https://bugs.chromium.org/p/chromium/issues/detail?id=1367329
const firstEntryWithTarget = metric.entries.find((entry) => entry.target);
(metric as INPMetricWithAttribution).attribution = {
eventTarget: getSelector(
firstEntryWithTarget && firstEntryWithTarget.target,
),
eventType: longestEntry.name,
eventTime: longestEntry.startTime,
eventEntry: longestEntry,
loadState: getLoadState(longestEntry.startTime),
};
return;
}
// Set an empty object if no other attribution has been set.
(metric as INPMetricWithAttribution).attribution = {};
};
/**
* Calculates the [INP](https://web.dev/articles/inp) value for the current
* page and calls the `callback` function once the value is ready, along with
* the `event` performance entries reported for that interaction. The reported
* value is a `DOMHighResTimeStamp`.
*
* A custom `durationThreshold` configuration option can optionally be passed to
* control what `event-timing` entries are considered for INP reporting. The
* default threshold is `40`, which means INP scores of less than 40 are
* reported as 0. Note that this will not affect your 75th percentile INP value
* unless that value is also less than 40 (well below the recommended
* [good](https://web.dev/articles/inp#what_is_a_good_inp_score) threshold).
*
* If the `reportAllChanges` configuration option is set to `true`, the
* `callback` function will be called as soon as the value is initially
* determined as well as any time the value changes throughout the page
* lifespan.
*
* _**Important:** INP should be continually monitored for changes throughout
* the entire lifespan of a page—including if the user returns to the page after
* it's been hidden/backgrounded. However, since browsers often [will not fire
* additional callbacks once the user has backgrounded a
* page](https://developer.chrome.com/blog/page-lifecycle-api/#advice-hidden),
* `callback` is always called when the page's visibility state changes to
* hidden. As a result, the `callback` function might be called multiple times
* during the same page load._
*/
export const onINP = (
onReport: INPReportCallbackWithAttribution,
opts?: ReportOpts,
) => {
unattributedOnINP(
((metric: INPMetricWithAttribution) => {
attributeINP(metric);
onReport(metric);
}) as INPReportCallback,
opts,
);
};

114
node_modules/web-vitals/src/attribution/onLCP.ts generated vendored Normal file
View File

@@ -0,0 +1,114 @@
/*
* 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 {getNavigationEntry} from '../lib/getNavigationEntry.js';
import {getSelector} from '../lib/getSelector.js';
import {onLCP as unattributedOnLCP} from '../onLCP.js';
import {
LCPAttribution,
LCPMetric,
LCPMetricWithAttribution,
LCPReportCallback,
LCPReportCallbackWithAttribution,
ReportOpts,
} from '../types.js';
const attributeLCP = (metric: LCPMetric) => {
if (metric.entries.length) {
const navigationEntry = getNavigationEntry();
if (navigationEntry) {
const activationStart = navigationEntry.activationStart || 0;
const lcpEntry = metric.entries[metric.entries.length - 1];
const lcpResourceEntry =
lcpEntry.url &&
performance
.getEntriesByType('resource')
.filter((e) => e.name === lcpEntry.url)[0];
const ttfb = Math.max(0, navigationEntry.responseStart - activationStart);
const lcpRequestStart = Math.max(
ttfb,
// Prefer `requestStart` (if TOA is set), otherwise use `startTime`.
lcpResourceEntry
? (lcpResourceEntry.requestStart || lcpResourceEntry.startTime) -
activationStart
: 0,
);
const lcpResponseEnd = Math.max(
lcpRequestStart,
lcpResourceEntry ? lcpResourceEntry.responseEnd - activationStart : 0,
);
const lcpRenderTime = Math.max(
lcpResponseEnd,
lcpEntry ? lcpEntry.startTime - activationStart : 0,
);
const attribution: LCPAttribution = {
element: getSelector(lcpEntry.element),
timeToFirstByte: ttfb,
resourceLoadDelay: lcpRequestStart - ttfb,
resourceLoadTime: lcpResponseEnd - lcpRequestStart,
elementRenderDelay: lcpRenderTime - lcpResponseEnd,
navigationEntry,
lcpEntry,
};
// Only attribution the URL and resource entry if they exist.
if (lcpEntry.url) {
attribution.url = lcpEntry.url;
}
if (lcpResourceEntry) {
attribution.lcpResourceEntry = lcpResourceEntry;
}
(metric as LCPMetricWithAttribution).attribution = attribution;
return;
}
}
// Set an empty object if no other attribution has been set.
(metric as LCPMetricWithAttribution).attribution = {
timeToFirstByte: 0,
resourceLoadDelay: 0,
resourceLoadTime: 0,
elementRenderDelay: metric.value,
};
};
/**
* Calculates the [LCP](https://web.dev/articles/lcp) value for the current page and
* calls the `callback` function once the value is ready (along with the
* relevant `largest-contentful-paint` performance entry used to determine the
* value). The reported value is a `DOMHighResTimeStamp`.
*
* If the `reportAllChanges` configuration option is set to `true`, the
* `callback` function will be called any time a new `largest-contentful-paint`
* performance entry is dispatched, or once the final value of the metric has
* been determined.
*/
export const onLCP = (
onReport: LCPReportCallbackWithAttribution,
opts?: ReportOpts,
) => {
unattributedOnLCP(
((metric: LCPMetricWithAttribution) => {
attributeLCP(metric);
onReport(metric);
}) as LCPReportCallback,
opts,
);
};

88
node_modules/web-vitals/src/attribution/onTTFB.ts generated vendored Normal file
View File

@@ -0,0 +1,88 @@
/*
* 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 {onTTFB as unattributedOnTTFB} from '../onTTFB.js';
import {
TTFBMetric,
TTFBMetricWithAttribution,
TTFBReportCallback,
TTFBReportCallbackWithAttribution,
ReportOpts,
} from '../types.js';
const attributeTTFB = (metric: TTFBMetric): void => {
if (metric.entries.length) {
const navigationEntry = metric.entries[0];
const activationStart = navigationEntry.activationStart || 0;
const dnsStart = Math.max(
navigationEntry.domainLookupStart - activationStart,
0,
);
const connectStart = Math.max(
navigationEntry.connectStart - activationStart,
0,
);
const requestStart = Math.max(
navigationEntry.requestStart - activationStart,
0,
);
(metric as TTFBMetricWithAttribution).attribution = {
waitingTime: dnsStart,
dnsTime: connectStart - dnsStart,
connectionTime: requestStart - connectStart,
requestTime: metric.value - requestStart,
navigationEntry: navigationEntry,
};
return;
}
// Set an empty object if no other attribution has been set.
(metric as TTFBMetricWithAttribution).attribution = {
waitingTime: 0,
dnsTime: 0,
connectionTime: 0,
requestTime: 0,
};
};
/**
* Calculates the [TTFB](https://web.dev/articles/ttfb) value for the
* current page and calls the `callback` function once the page has loaded,
* along with the relevant `navigation` performance entry used to determine the
* value. The reported value is a `DOMHighResTimeStamp`.
*
* Note, this function waits until after the page is loaded to call `callback`
* in order to ensure all properties of the `navigation` entry are populated.
* This is useful if you want to report on other metrics exposed by the
* [Navigation Timing API](https://w3c.github.io/navigation-timing/). For
* example, the TTFB metric starts from the page's [time
* origin](https://www.w3.org/TR/hr-time-2/#sec-time-origin), which means it
* includes time spent on DNS lookup, connection negotiation, network latency,
* and server processing time.
*/
export const onTTFB = (
onReport: TTFBReportCallbackWithAttribution,
opts?: ReportOpts,
) => {
unattributedOnTTFB(
((metric: TTFBMetricWithAttribution) => {
attributeTTFB(metric);
onReport(metric);
}) as TTFBReportCallback,
opts,
);
};