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,276 @@
import { createElement, Fragment } from "react";
/**
* External dependencies
*/
import { Platform } from 'react-native';
import Clipboard from '@react-native-clipboard/clipboard';
/**
* WordPress dependencies
*/
import { compose } from '@wordpress/compose';
import { withSelect } from '@wordpress/data';
import { isURL, prependHTTP } from '@wordpress/url';
import { useEffect, useState, useRef, useContext, useCallback } from '@wordpress/element';
import { link, external } from '@wordpress/icons';
/**
* Internal dependencies
*/
import BottomSheet from '../bottom-sheet';
import { BottomSheetContext } from '../bottom-sheet/bottom-sheet-context';
import PanelBody from '../../panel/body';
import TextControl from '../../text-control';
import ToggleControl from '../../toggle-control';
import FooterMessageControl from '../../footer-message-control';
import PanelActions from '../../panel/actions';
import LinkRelIcon from './link-rel';
import styles from './style.scss';
const NEW_TAB_REL = 'noreferrer noopener';
function LinkSettings({
// Control link settings `BottomSheet` visibility
isVisible,
// Callback that is called on closing bottom sheet
onClose,
// Function called to set attributes
setAttributes,
// Callback that is called when url input field is empty
onEmptyURL,
// Object of available options along with specific, customizable properties.
// Available options keys:
// * url - uses `TextControl` component to set `attributes.url`
// * linkLabel - uses `TextControl` component to set `attributes.label`
// * openInNewTab - uses `ToggleControl` component to set `attributes.linkTarget` and `attributes.rel`
// * linkRel - uses `TextControl` component to set `attributes.rel`
// * footer - uses `FooterMessageControl` component to display message, e.g. about missing functionality
// Available properties:
// * label - control component label, e.g. `Button Link URL`
// * placeholder - control component placeholder, e.g. `Add URL`
// * autoFocus (url only) - whether url input should be focused on sheet opening
// * autoFill (url only) - whether url input should be filled with url from clipboard
// Example:
// const options = {
// url: {
// label: __( 'Button Link URL' ),
// placeholder: __( 'Add URL' ),
// autoFocus: true,
// autoFill: true,
// }
// }
options,
// Specifies whether settings should be wrapped into `BottomSheet`
withBottomSheet,
// Defines buttons which will be displayed below the all options.
// It's an array of objects with following properties:
// * label - button title
// * onPress - callback that is called on pressing button
// Example:
// const actions = [
// {
// label: __( 'Remove link' ),
// onPress: () => setAttributes({ url: '' }),
// },
// ];
actions,
// Specifies whether general `BottomSheet` is opened
editorSidebarOpened,
// Specifies whether icon should be displayed next to the label
showIcon,
onLinkCellPressed,
urlValue,
// Attributes properties
url,
label = '',
linkTarget,
rel = ''
}) {
const [urlInputValue, setUrlInputValue] = useState('');
const [labelInputValue, setLabelInputValue] = useState('');
const [linkRelInputValue, setLinkRelInputValue] = useState('');
const onCloseSettingsSheetConsumed = useRef(false);
const prevEditorSidebarOpenedRef = useRef();
const {
onHandleClosingBottomSheet
} = useContext(BottomSheetContext);
useEffect(() => {
if (onHandleClosingBottomSheet) {
onHandleClosingBottomSheet(onCloseSettingsSheet);
}
// Disable reason: deferring this refactor to the native team.
// see https://github.com/WordPress/gutenberg/pull/41166
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [urlInputValue, labelInputValue, linkRelInputValue]);
useEffect(() => {
prevEditorSidebarOpenedRef.current = editorSidebarOpened;
});
const prevEditorSidebarOpened = prevEditorSidebarOpenedRef.current;
useEffect(() => {
if (url !== urlInputValue) {
setUrlInputValue(url || '');
}
// Disable reason: deferring this refactor to the native team.
// see https://github.com/WordPress/gutenberg/pull/41166
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [url]);
useEffect(() => {
setLabelInputValue(label || '');
}, [label]);
useEffect(() => {
setLinkRelInputValue(rel || '');
}, [rel]);
useEffect(() => {
const isSettingSheetOpen = isVisible || editorSidebarOpened;
if (isSettingSheetOpen) {
onCloseSettingsSheetConsumed.current = false;
}
if (options.url.autoFill && isSettingSheetOpen && !url) {
getURLFromClipboard();
}
if (prevEditorSidebarOpened && !editorSidebarOpened) {
onSetAttributes();
}
// Disable reason: deferring this refactor to the native team.
// see https://github.com/WordPress/gutenberg/pull/41166
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [editorSidebarOpened, isVisible]);
useEffect(() => {
if (!urlValue && onEmptyURL) {
onEmptyURL();
}
if (prependHTTP(urlValue) !== url) {
setAttributes({
url: prependHTTP(urlValue)
});
}
// Disable reason: deferring this refactor to the native team.
// see https://github.com/WordPress/gutenberg/pull/41166
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [urlValue]);
const onChangeURL = useCallback(value => {
if (!value && onEmptyURL) {
onEmptyURL();
}
setUrlInputValue(value);
}, [onEmptyURL]);
const onChangeLabel = useCallback(value => {
setLabelInputValue(value);
}, []);
const onSetAttributes = useCallback(() => {
const newURL = prependHTTP(urlInputValue);
if (url !== newURL || labelInputValue !== label || linkRelInputValue !== rel) {
setAttributes({
url: newURL,
label: labelInputValue,
rel: linkRelInputValue
});
}
// Disable reason: deferring this refactor to the native team.
// see https://github.com/WordPress/gutenberg/pull/41166
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [urlInputValue, labelInputValue, linkRelInputValue, setAttributes]);
const onCloseSettingsSheet = useCallback(() => {
if (onCloseSettingsSheetConsumed.current) {
return;
}
onCloseSettingsSheetConsumed.current = true;
onSetAttributes();
if (onClose) {
onClose();
}
}, [onClose, onSetAttributes]);
const onChangeOpenInNewTab = useCallback(value => {
const newLinkTarget = value ? '_blank' : undefined;
let updatedRel = linkRelInputValue;
if (newLinkTarget && !linkRelInputValue) {
updatedRel = NEW_TAB_REL;
} else if (!newLinkTarget && linkRelInputValue === NEW_TAB_REL) {
updatedRel = undefined;
}
setAttributes({
linkTarget: newLinkTarget,
rel: updatedRel
});
},
// Disable reason: deferring this refactor to the native team.
// see https://github.com/WordPress/gutenberg/pull/41166
// eslint-disable-next-line react-hooks/exhaustive-deps
[linkRelInputValue]);
const onChangeLinkRel = useCallback(value => {
setLinkRelInputValue(value);
}, []);
async function getURLFromClipboard() {
const clipboardText = await Clipboard.getString();
if (!clipboardText) {
return;
}
// Check if pasted text is URL.
if (!isURL(clipboardText)) {
return;
}
setAttributes({
url: clipboardText
});
}
function getSettings() {
return createElement(Fragment, null, options.url && (onLinkCellPressed ? createElement(BottomSheet.LinkCell, {
showIcon: showIcon,
value: url,
valueMask: options.url.valueMask,
onPress: onLinkCellPressed
}) : createElement(TextControl, {
icon: showIcon && link,
label: options.url.label,
value: urlInputValue,
valuePlaceholder: options.url.placeholder,
onChange: onChangeURL,
onSubmit: onCloseSettingsSheet,
autoCapitalize: "none",
autoCorrect: false
// eslint-disable-next-line jsx-a11y/no-autofocus
,
autoFocus: Platform.OS === 'ios' && options.url.autoFocus,
keyboardType: "url"
})), options.linkLabel && createElement(TextControl, {
label: options.linkLabel.label,
value: labelInputValue,
valuePlaceholder: options.linkLabel.placeholder,
onChange: onChangeLabel
}), !!urlInputValue && createElement(Fragment, null, options.openInNewTab && createElement(ToggleControl, {
icon: showIcon && external,
label: options.openInNewTab.label,
checked: linkTarget === '_blank',
onChange: onChangeOpenInNewTab
}), options.linkRel && createElement(TextControl, {
icon: showIcon && LinkRelIcon,
label: options.linkRel.label,
value: linkRelInputValue,
valuePlaceholder: options.linkRel.placeholder,
onChange: onChangeLinkRel,
onSubmit: onCloseSettingsSheet,
autoCapitalize: "none",
autoCorrect: false,
keyboardType: "default"
})));
}
if (!withBottomSheet) {
return getSettings();
}
return createElement(Fragment, null, createElement(PanelBody, {
style: styles.linkSettingsPanel
}, getSettings()), options.footer && createElement(PanelBody, {
style: styles.linkSettingsPanel
}, createElement(FooterMessageControl, {
label: options.footer.label,
separatorType: options.footer.separatorType
})), actions && createElement(PanelActions, {
actions: actions
}));
}
export default compose([withSelect(select => {
const {
isEditorSidebarOpened
} = select('core/edit-post');
return {
editorSidebarOpened: isEditorSidebarOpened()
};
})])(LinkSettings);
//# sourceMappingURL=index.native.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,12 @@
import { createElement } from "react";
/**
* WordPress dependencies
*/
import { Path, SVG } from '@wordpress/components';
export default createElement(SVG, {
viewBox: "0 0 24 24",
xmlns: "http://www.w3.org/2000/svg"
}, createElement(Path, {
d: "M17 16l-4-4V8.82C14.16 8.4 15 7.3 15 6c0-1.66-1.34-3-3-3S9 4.34 9 6c0 1.3.84 2.4 2 2.82V12l-4 4H3v5h5v-3.05l4-4.2 4 4.2V21h5v-5h-4z"
}));
//# sourceMappingURL=link-rel.native.js.map

View File

@@ -0,0 +1 @@
{"version":3,"names":["Path","SVG","createElement","viewBox","xmlns","d"],"sources":["@wordpress/components/src/mobile/link-settings/link-rel.native.js"],"sourcesContent":["/**\n * WordPress dependencies\n */\nimport { Path, SVG } from '@wordpress/components';\n\nexport default (\n\t<SVG viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n\t\t<Path d=\"M17 16l-4-4V8.82C14.16 8.4 15 7.3 15 6c0-1.66-1.34-3-3-3S9 4.34 9 6c0 1.3.84 2.4 2 2.82V12l-4 4H3v5h5v-3.05l4-4.2 4 4.2V21h5v-5h-4z\" />\n\t</SVG>\n);\n"],"mappings":";AAAA;AACA;AACA;AACA,SAASA,IAAI,EAAEC,GAAG,QAAQ,uBAAuB;AAEjD,eACCC,aAAA,CAACD,GAAG;EAACE,OAAO,EAAC,WAAW;EAACC,KAAK,EAAC;AAA4B,GAC1DF,aAAA,CAACF,IAAI;EAACK,CAAC,EAAC;AAAqI,CAAE,CAC3I,CAAC"}

View File

@@ -0,0 +1,46 @@
import { createElement } from "react";
/**
* WordPress dependencies
*/
import { memo } from '@wordpress/element';
/**
* Internal dependencies
*/
import BottomSheet from '../bottom-sheet';
import LinkSettingsScreen from './link-settings-screen';
import LinkPickerScreen from '../link-picker/link-picker-screen';
const linkSettingsScreens = {
settings: 'LinkSettingsScreen',
linkPicker: 'linkPicker'
};
function LinkSettingsNavigation(props) {
if (!props.withBottomSheet) {
return createElement(LinkSettingsScreen, {
...props
});
}
return createElement(BottomSheet, {
isVisible: props.isVisible,
onClose: props.onClose,
onDismiss: props.onDismiss,
testID: "link-settings-navigation",
hideHeader: true,
hasNavigation: true
}, createElement(BottomSheet.NavigationContainer, {
animate: true,
main: true
}, createElement(BottomSheet.NavigationScreen, {
name: linkSettingsScreens.settings
}, createElement(LinkSettingsScreen, {
...props,
withBottomSheet: true
})), createElement(BottomSheet.NavigationScreen, {
name: linkSettingsScreens.linkPicker,
isScrollable: true,
fullScreen: true
}, createElement(LinkPickerScreen, {
returnScreenName: linkSettingsScreens.settings
}))));
}
export default memo(LinkSettingsNavigation);
//# sourceMappingURL=link-settings-navigation.native.js.map

View File

@@ -0,0 +1 @@
{"version":3,"names":["memo","BottomSheet","LinkSettingsScreen","LinkPickerScreen","linkSettingsScreens","settings","linkPicker","LinkSettingsNavigation","props","withBottomSheet","createElement","isVisible","onClose","onDismiss","testID","hideHeader","hasNavigation","NavigationContainer","animate","main","NavigationScreen","name","isScrollable","fullScreen","returnScreenName"],"sources":["@wordpress/components/src/mobile/link-settings/link-settings-navigation.native.js"],"sourcesContent":["/**\n * WordPress dependencies\n */\nimport { memo } from '@wordpress/element';\n/**\n * Internal dependencies\n */\nimport BottomSheet from '../bottom-sheet';\nimport LinkSettingsScreen from './link-settings-screen';\nimport LinkPickerScreen from '../link-picker/link-picker-screen';\n\nconst linkSettingsScreens = {\n\tsettings: 'LinkSettingsScreen',\n\tlinkPicker: 'linkPicker',\n};\n\nfunction LinkSettingsNavigation( props ) {\n\tif ( ! props.withBottomSheet ) {\n\t\treturn <LinkSettingsScreen { ...props } />;\n\t}\n\treturn (\n\t\t<BottomSheet\n\t\t\tisVisible={ props.isVisible }\n\t\t\tonClose={ props.onClose }\n\t\t\tonDismiss={ props.onDismiss }\n\t\t\ttestID=\"link-settings-navigation\"\n\t\t\thideHeader\n\t\t\thasNavigation\n\t\t>\n\t\t\t<BottomSheet.NavigationContainer animate main>\n\t\t\t\t<BottomSheet.NavigationScreen\n\t\t\t\t\tname={ linkSettingsScreens.settings }\n\t\t\t\t>\n\t\t\t\t\t<LinkSettingsScreen { ...props } withBottomSheet />\n\t\t\t\t</BottomSheet.NavigationScreen>\n\t\t\t\t<BottomSheet.NavigationScreen\n\t\t\t\t\tname={ linkSettingsScreens.linkPicker }\n\t\t\t\t\tisScrollable\n\t\t\t\t\tfullScreen\n\t\t\t\t>\n\t\t\t\t\t<LinkPickerScreen\n\t\t\t\t\t\treturnScreenName={ linkSettingsScreens.settings }\n\t\t\t\t\t/>\n\t\t\t\t</BottomSheet.NavigationScreen>\n\t\t\t</BottomSheet.NavigationContainer>\n\t\t</BottomSheet>\n\t);\n}\n\nexport default memo( LinkSettingsNavigation );\n"],"mappings":";AAAA;AACA;AACA;AACA,SAASA,IAAI,QAAQ,oBAAoB;AACzC;AACA;AACA;AACA,OAAOC,WAAW,MAAM,iBAAiB;AACzC,OAAOC,kBAAkB,MAAM,wBAAwB;AACvD,OAAOC,gBAAgB,MAAM,mCAAmC;AAEhE,MAAMC,mBAAmB,GAAG;EAC3BC,QAAQ,EAAE,oBAAoB;EAC9BC,UAAU,EAAE;AACb,CAAC;AAED,SAASC,sBAAsBA,CAAEC,KAAK,EAAG;EACxC,IAAK,CAAEA,KAAK,CAACC,eAAe,EAAG;IAC9B,OAAOC,aAAA,CAACR,kBAAkB;MAAA,GAAMM;IAAK,CAAI,CAAC;EAC3C;EACA,OACCE,aAAA,CAACT,WAAW;IACXU,SAAS,EAAGH,KAAK,CAACG,SAAW;IAC7BC,OAAO,EAAGJ,KAAK,CAACI,OAAS;IACzBC,SAAS,EAAGL,KAAK,CAACK,SAAW;IAC7BC,MAAM,EAAC,0BAA0B;IACjCC,UAAU;IACVC,aAAa;EAAA,GAEbN,aAAA,CAACT,WAAW,CAACgB,mBAAmB;IAACC,OAAO;IAACC,IAAI;EAAA,GAC5CT,aAAA,CAACT,WAAW,CAACmB,gBAAgB;IAC5BC,IAAI,EAAGjB,mBAAmB,CAACC;EAAU,GAErCK,aAAA,CAACR,kBAAkB;IAAA,GAAMM,KAAK;IAAGC,eAAe;EAAA,CAAE,CACrB,CAAC,EAC/BC,aAAA,CAACT,WAAW,CAACmB,gBAAgB;IAC5BC,IAAI,EAAGjB,mBAAmB,CAACE,UAAY;IACvCgB,YAAY;IACZC,UAAU;EAAA,GAEVb,aAAA,CAACP,gBAAgB;IAChBqB,gBAAgB,EAAGpB,mBAAmB,CAACC;EAAU,CACjD,CAC4B,CACE,CACrB,CAAC;AAEhB;AAEA,eAAeL,IAAI,CAAEO,sBAAuB,CAAC"}

View File

@@ -0,0 +1,48 @@
import { createElement } from "react";
/**
* External dependencies
*/
import { useNavigation, useRoute } from '@react-navigation/native';
/**
* WordPress dependencies
*/
import { useMemo } from '@wordpress/element';
/**
* Internal dependencies
*/
import LinkSettings from './';
const LinkSettingsScreen = props => {
const navigation = useNavigation();
const route = useRoute();
const {
url = ''
} = props;
const {
inputValue = url
} = route.params || {};
const onLinkCellPressed = () => {
if (props.onLinkCellPressed) {
props.onLinkCellPressed({
navigation
});
} else {
navigation.navigate('linkPicker', {
inputValue
});
}
};
return useMemo(() => {
return createElement(LinkSettings, {
...props,
onLinkCellPressed: props.hasPicker ? onLinkCellPressed : undefined,
urlValue: inputValue
});
// Disable reason: deferring this refactor to the native team.
// see https://github.com/WordPress/gutenberg/pull/41166
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [props, inputValue, navigation, route]);
};
export default LinkSettingsScreen;
//# sourceMappingURL=link-settings-screen.native.js.map

View File

@@ -0,0 +1 @@
{"version":3,"names":["useNavigation","useRoute","useMemo","LinkSettings","LinkSettingsScreen","props","navigation","route","url","inputValue","params","onLinkCellPressed","navigate","createElement","hasPicker","undefined","urlValue"],"sources":["@wordpress/components/src/mobile/link-settings/link-settings-screen.native.js"],"sourcesContent":["/**\n * External dependencies\n */\nimport { useNavigation, useRoute } from '@react-navigation/native';\n\n/**\n * WordPress dependencies\n */\nimport { useMemo } from '@wordpress/element';\n\n/**\n * Internal dependencies\n */\nimport LinkSettings from './';\n\nconst LinkSettingsScreen = ( props ) => {\n\tconst navigation = useNavigation();\n\tconst route = useRoute();\n\tconst { url = '' } = props;\n\tconst { inputValue = url } = route.params || {};\n\n\tconst onLinkCellPressed = () => {\n\t\tif ( props.onLinkCellPressed ) {\n\t\t\tprops.onLinkCellPressed( { navigation } );\n\t\t} else {\n\t\t\tnavigation.navigate( 'linkPicker', { inputValue } );\n\t\t}\n\t};\n\n\treturn useMemo( () => {\n\t\treturn (\n\t\t\t<LinkSettings\n\t\t\t\t{ ...props }\n\t\t\t\tonLinkCellPressed={\n\t\t\t\t\tprops.hasPicker ? onLinkCellPressed : undefined\n\t\t\t\t}\n\t\t\t\turlValue={ inputValue }\n\t\t\t/>\n\t\t);\n\t\t// Disable reason: deferring this refactor to the native team.\n\t\t// see https://github.com/WordPress/gutenberg/pull/41166\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t}, [ props, inputValue, navigation, route ] );\n};\n\nexport default LinkSettingsScreen;\n"],"mappings":";AAAA;AACA;AACA;AACA,SAASA,aAAa,EAAEC,QAAQ,QAAQ,0BAA0B;;AAElE;AACA;AACA;AACA,SAASC,OAAO,QAAQ,oBAAoB;;AAE5C;AACA;AACA;AACA,OAAOC,YAAY,MAAM,IAAI;AAE7B,MAAMC,kBAAkB,GAAKC,KAAK,IAAM;EACvC,MAAMC,UAAU,GAAGN,aAAa,CAAC,CAAC;EAClC,MAAMO,KAAK,GAAGN,QAAQ,CAAC,CAAC;EACxB,MAAM;IAAEO,GAAG,GAAG;EAAG,CAAC,GAAGH,KAAK;EAC1B,MAAM;IAAEI,UAAU,GAAGD;EAAI,CAAC,GAAGD,KAAK,CAACG,MAAM,IAAI,CAAC,CAAC;EAE/C,MAAMC,iBAAiB,GAAGA,CAAA,KAAM;IAC/B,IAAKN,KAAK,CAACM,iBAAiB,EAAG;MAC9BN,KAAK,CAACM,iBAAiB,CAAE;QAAEL;MAAW,CAAE,CAAC;IAC1C,CAAC,MAAM;MACNA,UAAU,CAACM,QAAQ,CAAE,YAAY,EAAE;QAAEH;MAAW,CAAE,CAAC;IACpD;EACD,CAAC;EAED,OAAOP,OAAO,CAAE,MAAM;IACrB,OACCW,aAAA,CAACV,YAAY;MAAA,GACPE,KAAK;MACVM,iBAAiB,EAChBN,KAAK,CAACS,SAAS,GAAGH,iBAAiB,GAAGI,SACtC;MACDC,QAAQ,EAAGP;IAAY,CACvB,CAAC;IAEH;IACA;IACA;EACD,CAAC,EAAE,CAAEJ,KAAK,EAAEI,UAAU,EAAEH,UAAU,EAAEC,KAAK,CAAG,CAAC;AAC9C,CAAC;AAED,eAAeH,kBAAkB"}