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

286
node_modules/intl-messageformat/src/compiler.ts generated vendored Normal file
View File

@@ -0,0 +1,286 @@
/*
Copyright (c) 2014, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License.
See the accompanying LICENSE file for terms.
*/
import {
MessageFormatPattern,
MessageTextElement,
ArgumentElement,
PluralFormat as ParserPluralFormat,
SelectFormat as ParserSelectFormat
} from 'intl-messageformat-parser';
export interface Formats {
number: Record<string, Intl.NumberFormatOptions>;
date: Record<string, Intl.DateTimeFormatOptions>;
time: Record<string, Intl.DateTimeFormatOptions>;
}
export interface Formatters {
getNumberFormat(
...args: ConstructorParameters<typeof Intl.NumberFormat>
): Intl.NumberFormat;
getDateTimeFormat(
...args: ConstructorParameters<typeof Intl.DateTimeFormat>
): Intl.DateTimeFormat;
getPluralRules(
...args: ConstructorParameters<typeof Intl.PluralRules>
): Intl.PluralRules;
}
export type Pattern =
| string
| PluralOffsetString
| PluralFormat
| SelectFormat
| StringFormat;
export default class Compiler {
private locales: string | string[] = [];
private formats: Formats = {
number: {},
date: {},
time: {}
};
private pluralNumberFormat: Intl.NumberFormat | null = null;
private currentPlural: ArgumentElement | null | undefined = null;
private pluralStack: Array<ArgumentElement | null | undefined> = [];
private formatters: Formatters;
constructor(
locales: string | string[],
formats: Formats,
formatters: Formatters
) {
this.locales = locales;
this.formats = formats;
this.formatters = formatters;
}
compile(ast: MessageFormatPattern): Pattern[] {
this.pluralStack = [];
this.currentPlural = null;
this.pluralNumberFormat = null;
return this.compileMessage(ast);
}
compileMessage(ast: MessageFormatPattern) {
if (!(ast && ast.type === 'messageFormatPattern')) {
throw new Error('Message AST is not of type: "messageFormatPattern"');
}
const { elements } = ast;
const pattern = elements
.filter<MessageTextElement | ArgumentElement>(
(el): el is MessageTextElement | ArgumentElement =>
el.type === 'messageTextElement' || el.type === 'argumentElement'
)
.map(el =>
el.type === 'messageTextElement'
? this.compileMessageText(el)
: this.compileArgument(el)
);
if (pattern.length !== elements.length) {
throw new Error('Message element does not have a valid type');
}
return pattern;
}
compileMessageText(element: MessageTextElement) {
// When this `element` is part of plural sub-pattern and its value contains
// an unescaped '#', use a `PluralOffsetString` helper to properly output
// the number with the correct offset in the string.
if (this.currentPlural && /(^|[^\\])#/g.test(element.value)) {
// Create a cache a NumberFormat instance that can be reused for any
// PluralOffsetString instance in this message.
if (!this.pluralNumberFormat) {
this.pluralNumberFormat = new Intl.NumberFormat(this.locales);
}
return new PluralOffsetString(
this.currentPlural.id,
(this.currentPlural.format as ParserPluralFormat).offset,
this.pluralNumberFormat,
element.value
);
}
// Unescape the escaped '#'s in the message text.
return element.value.replace(/\\#/g, '#');
}
compileArgument(element: ArgumentElement) {
const { format, id } = element;
const { formatters } = this;
if (!format) {
return new StringFormat(id);
}
const { formats, locales } = this;
switch (format.type) {
case 'numberFormat':
return {
id,
format: formatters.getNumberFormat(
locales,
formats.number[format.style]
).format
};
case 'dateFormat':
return {
id,
format: formatters.getDateTimeFormat(
locales,
formats.date[format.style]
).format
};
case 'timeFormat':
return {
id,
format: formatters.getDateTimeFormat(
locales,
formats.time[format.style]
).format
};
case 'pluralFormat':
return new PluralFormat(
id,
format.offset,
this.compileOptions(element),
formatters.getPluralRules(locales, {
type: format.ordinal ? 'ordinal' : 'cardinal'
})
);
case 'selectFormat':
return new SelectFormat(id, this.compileOptions(element));
default:
throw new Error('Message element does not have a valid format type');
}
}
compileOptions(element: ArgumentElement) {
const format = element.format as ParserPluralFormat | ParserSelectFormat;
const { options } = format;
// Save the current plural element, if any, then set it to a new value when
// compiling the options sub-patterns. This conforms the spec's algorithm
// for handling `"#"` syntax in message text.
this.pluralStack.push(this.currentPlural);
this.currentPlural = format.type === 'pluralFormat' ? element : null;
const optionsHash = options.reduce(
(all: Record<string, Array<Pattern>>, option) => {
// Compile the sub-pattern and save it under the options's selector.
all[option.selector] = this.compileMessage(option.value);
return all;
},
{}
);
// Pop the plural stack to put back the original current plural value.
this.currentPlural = this.pluralStack.pop();
return optionsHash;
}
}
// -- Compiler Helper Classes --------------------------------------------------
abstract class Formatter {
public id: string;
constructor(id: string) {
this.id = id;
}
abstract format(value: string | number): string;
}
class StringFormat extends Formatter {
format(value: number | string) {
if (!value && typeof value !== 'number') {
return '';
}
return typeof value === 'string' ? value : String(value);
}
}
class PluralFormat {
public id: string;
private offset: number;
private options: Record<string, Pattern[]>;
private pluralRules: Intl.PluralRules;
constructor(
id: string,
offset: number,
options: Record<string, Pattern[]>,
pluralRules: Intl.PluralRules
) {
this.id = id;
this.offset = offset;
this.options = options;
this.pluralRules = pluralRules;
}
getOption(value: number) {
const { options } = this;
const option =
options['=' + value] ||
options[this.pluralRules.select(value - this.offset)];
return option || options.other;
}
}
export class PluralOffsetString extends Formatter {
private offset: number;
private numberFormat: Intl.NumberFormat;
private string: string;
constructor(
id: string,
offset: number,
numberFormat: Intl.NumberFormat,
string: string
) {
super(id);
this.offset = offset;
this.numberFormat = numberFormat;
this.string = string;
}
format(value: number) {
const number = this.numberFormat.format(value - this.offset);
return this.string
.replace(/(^|[^\\])#/g, '$1' + number)
.replace(/\\#/g, '#');
}
}
export class SelectFormat {
public id: string;
private options: Record<string, Pattern[]>;
constructor(id: string, options: Record<string, Pattern[]>) {
this.id = id;
this.options = options;
}
getOption(value: string) {
const { options } = this;
return options[value] || options.other;
}
}
export function isSelectOrPluralFormat(
f: any
): f is SelectFormat | PluralFormat {
return !!f.options;
}

269
node_modules/intl-messageformat/src/core.ts generated vendored Normal file
View File

@@ -0,0 +1,269 @@
/*
Copyright (c) 2014, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License.
See the accompanying LICENSE file for terms.
*/
/* jslint esnext: true */
import Compiler, {
Formats,
isSelectOrPluralFormat,
Pattern,
Formatters
} from './compiler';
import parser, { MessageFormatPattern } from 'intl-messageformat-parser';
// -- MessageFormat --------------------------------------------------------
function resolveLocale(locales: string | string[]): string {
if (typeof locales === 'string') {
locales = [locales];
}
try {
return Intl.NumberFormat.supportedLocalesOf(locales, {
// IE11 localeMatcher `lookup` seems to convert `en` -> `en-US`
// but not other browsers,
localeMatcher: 'best fit'
})[0];
} catch (e) {
return IntlMessageFormat.defaultLocale;
}
}
function formatPatterns(
pattern: Pattern[],
values?: Record<string, string | number | boolean | null | undefined>
) {
let result = '';
for (const part of pattern) {
// Exist early for string parts.
if (typeof part === 'string') {
result += part;
continue;
}
const { id } = part;
// Enforce that all required values are provided by the caller.
if (!(values && id in values)) {
throw new FormatError(`A value must be provided for: ${id}`, id);
}
const value = values[id];
// Recursively format plural and select parts' option — which can be a
// nested pattern structure. The choosing of the option to use is
// abstracted-by and delegated-to the part helper object.
if (isSelectOrPluralFormat(part)) {
result += formatPatterns(part.getOption(value as any), values);
} else {
result += part.format(value as any);
}
}
return result;
}
function mergeConfig(c1: Record<string, object>, c2?: Record<string, object>) {
if (!c2) {
return c1;
}
return {
...(c1 || {}),
...(c2 || {}),
...Object.keys(c1).reduce((all: Record<string, object>, k) => {
all[k] = {
...c1[k],
...(c2[k] || {})
};
return all;
}, {})
};
}
function mergeConfigs(
defaultConfig: Formats,
configs?: Partial<Formats>
): Formats {
if (!configs) {
return defaultConfig;
}
return (Object.keys(defaultConfig) as Array<keyof Formats>).reduce(
(all: Formats, k: keyof Formats) => {
all[k] = mergeConfig(defaultConfig[k], configs[k]);
return all;
},
{ ...defaultConfig }
);
}
class FormatError extends Error {
public readonly variableId?: string;
constructor(msg?: string, variableId?: string) {
super(msg);
this.variableId = variableId;
}
}
export interface Options {
formatters?: Formatters;
}
export function createDefaultFormatters(): Formatters {
return {
getNumberFormat(...args) {
return new Intl.NumberFormat(...args);
},
getDateTimeFormat(...args) {
return new Intl.DateTimeFormat(...args);
},
getPluralRules(...args) {
return new Intl.PluralRules(...args);
}
};
}
export class IntlMessageFormat {
private ast: MessageFormatPattern;
private locale: string;
private pattern: Pattern[];
private message: string | MessageFormatPattern;
constructor(
message: string | MessageFormatPattern,
locales: string | string[] = IntlMessageFormat.defaultLocale,
overrideFormats?: Partial<Formats>,
opts?: Options
) {
if (typeof message === 'string') {
if (!IntlMessageFormat.__parse) {
throw new TypeError(
'IntlMessageFormat.__parse must be set to process `message` of type `string`'
);
}
// Parse string messages into an AST.
this.ast = IntlMessageFormat.__parse(message);
} else {
this.ast = message;
}
this.message = message;
if (!(this.ast && this.ast.type === 'messageFormatPattern')) {
throw new TypeError('A message must be provided as a String or AST.');
}
// Creates a new object with the specified `formats` merged with the default
// formats.
const formats = mergeConfigs(IntlMessageFormat.formats, overrideFormats);
// Defined first because it's used to build the format pattern.
this.locale = resolveLocale(locales || []);
let formatters = (opts && opts.formatters) || createDefaultFormatters();
// Compile the `ast` to a pattern that is highly optimized for repeated
// `format()` invocations. **Note:** This passes the `locales` set provided
// to the constructor instead of just the resolved locale.
this.pattern = new Compiler(locales, formats, formatters).compile(this.ast);
// "Bind" `format()` method to `this` so it can be passed by reference like
// the other `Intl` APIs.
}
format = (
values?: Record<string, string | number | boolean | null | undefined>
) => {
try {
return formatPatterns(this.pattern, values);
} catch (e) {
if (e.variableId) {
throw new Error(
`The intl string context variable '${e.variableId}' was not provided to the string '${this.message}'`
);
} else {
throw e;
}
}
};
resolvedOptions() {
return { locale: this.locale };
}
getAst() {
return this.ast;
}
static defaultLocale = 'en';
static __parse: typeof parser['parse'] | undefined = undefined;
// Default format options used as the prototype of the `formats` provided to the
// constructor. These are used when constructing the internal Intl.NumberFormat
// and Intl.DateTimeFormat instances.
static formats = {
number: {
currency: {
style: 'currency'
},
percent: {
style: 'percent'
}
},
date: {
short: {
month: 'numeric',
day: 'numeric',
year: '2-digit'
},
medium: {
month: 'short',
day: 'numeric',
year: 'numeric'
},
long: {
month: 'long',
day: 'numeric',
year: 'numeric'
},
full: {
weekday: 'long',
month: 'long',
day: 'numeric',
year: 'numeric'
}
},
time: {
short: {
hour: 'numeric',
minute: 'numeric'
},
medium: {
hour: 'numeric',
minute: 'numeric',
second: 'numeric'
},
long: {
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
timeZoneName: 'short'
},
full: {
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
timeZoneName: 'short'
}
}
};
}
export { Formats, Pattern } from './compiler';
export default IntlMessageFormat;

15
node_modules/intl-messageformat/src/index.ts generated vendored Normal file
View File

@@ -0,0 +1,15 @@
/*
Copyright (c) 2014, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License.
See the accompanying LICENSE file for terms.
*/
import parser from 'intl-messageformat-parser';
import IntlMessageFormat from './core';
IntlMessageFormat.__parse = parser.parse;
export { Formats, Pattern } from './compiler';
export * from './core';
export { Formatters } from './compiler';
export default IntlMessageFormat;