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,33 @@
# ScrollLock
ScrollLock is a content-free React component for declaratively preventing scroll bleed from modal UI to the page body. This component applies a `lockscroll` class to the `document.documentElement` and `document.scrollingElement` elements to stop the body from scrolling. When it is present, the lock is applied.
## Usage
Declare scroll locking as part of modal UI.
```jsx
import { useState } from 'react';
import { ScrollLock, Button } from '@wordpress/components';
const MyScrollLock = () => {
const [ isScrollLocked, setIsScrollLocked ] = useState( false );
const toggleLock = () => {
setIsScrollLocked( ( locked ) => ! locked ) );
};
return (
<div>
<Button variant="secondary" onClick={ toggleLock }>
Toggle scroll lock
</Button>
{ isScrollLocked && <ScrollLock /> }
<p>
Scroll locked:
<strong>{ isScrollLocked ? 'Yes' : 'No' }</strong>
</p>
</div>
);
};
```

View File

@@ -0,0 +1,88 @@
/**
* WordPress dependencies
*/
import { useEffect } from '@wordpress/element';
/*
* Setting `overflow: hidden` on html and body elements resets body scroll in iOS.
* Save scroll top so we can restore it after locking scroll.
*
* NOTE: It would be cleaner and possibly safer to find a localized solution such
* as preventing default on certain touchmove events.
*/
let previousScrollTop = 0;
function setLocked( locked: boolean ) {
const scrollingElement = document.scrollingElement || document.body;
if ( locked ) {
previousScrollTop = scrollingElement.scrollTop;
}
const methodName = locked ? 'add' : 'remove';
scrollingElement.classList[ methodName ]( 'lockscroll' );
// Adding the class to the document element seems to be necessary in iOS.
document.documentElement.classList[ methodName ]( 'lockscroll' );
if ( ! locked ) {
scrollingElement.scrollTop = previousScrollTop;
}
}
let lockCounter = 0;
/**
* ScrollLock is a content-free React component for declaratively preventing
* scroll bleed from modal UI to the page body. This component applies a
* `lockscroll` class to the `document.documentElement` and
* `document.scrollingElement` elements to stop the body from scrolling. When it
* is present, the lock is applied.
*
* ```jsx
* import { ScrollLock, Button } from '@wordpress/components';
* import { useState } from '@wordpress/element';
*
* const MyScrollLock = () => {
* const [ isScrollLocked, setIsScrollLocked ] = useState( false );
*
* const toggleLock = () => {
* setIsScrollLocked( ( locked ) => ! locked ) );
* };
*
* return (
* <div>
* <Button variant="secondary" onClick={ toggleLock }>
* Toggle scroll lock
* </Button>
* { isScrollLocked && <ScrollLock /> }
* <p>
* Scroll locked:
* <strong>{ isScrollLocked ? 'Yes' : 'No' }</strong>
* </p>
* </div>
* );
* };
* ```
*/
export function ScrollLock(): null {
useEffect( () => {
if ( lockCounter === 0 ) {
setLocked( true );
}
++lockCounter;
return () => {
if ( lockCounter === 1 ) {
setLocked( false );
}
--lockCounter;
};
}, [] );
return null;
}
export default ScrollLock;

View File

@@ -0,0 +1,96 @@
/**
* External dependencies
*/
import type { Meta, StoryFn } from '@storybook/react';
import type { ReactNode } from 'react';
/**
* WordPress dependencies
*/
import { useState } from '@wordpress/element';
/**
* Internal dependencies
*/
import Button from '../../button';
import ScrollLock from '..';
const meta: Meta< typeof ScrollLock > = {
component: ScrollLock,
title: 'Components/ScrollLock',
parameters: {
controls: { hideNoControlsWarning: true },
docs: { canvas: { sourceState: 'shown' } },
},
};
export default meta;
function StripedBackground( props: { children: ReactNode } ) {
return (
<div
style={ {
backgroundColor: '#fff',
backgroundImage:
'linear-gradient(transparent 50%, rgba(0, 0, 0, 0.05) 50%)',
backgroundSize: '50px 50px',
height: 3000,
position: 'relative',
} }
{ ...props }
/>
);
}
function ToggleContainer( props: { children: ReactNode } ) {
const { children } = props;
return (
<div
style={ {
position: 'sticky',
top: 0,
padding: 40,
display: 'flex',
justifyContent: 'center',
textAlign: 'center',
} }
>
<div>{ children }</div>
</div>
);
}
export const Default: StoryFn< typeof ScrollLock > = () => {
const [ isScrollLocked, setScrollLocked ] = useState( false );
const toggleLock = () => setScrollLocked( ! isScrollLocked );
return (
<div style={ { height: 1000 } }>
<div
style={ {
overflow: 'auto',
height: 240,
border: '1px solid lightgray',
} }
>
<StripedBackground>
<div>
Start scrolling down. Once you scroll to the end of this
container with the stripes, the rest of the page will
continue scrolling. <code>ScrollLock</code> prevents
this &quot;scroll bleed&quot; from happening.
</div>
<ToggleContainer>
<Button variant="primary" onClick={ toggleLock }>
Toggle Scroll Lock
</Button>
{ isScrollLocked && <ScrollLock /> }
<p>
Scroll locked:{ ' ' }
<strong>{ isScrollLocked ? 'Yes' : 'No' }</strong>
</p>
</ToggleContainer>
</StripedBackground>
</div>
</div>
);
};

View File

@@ -0,0 +1,4 @@
html.lockscroll,
body.lockscroll {
overflow: hidden;
}

View File

@@ -0,0 +1,26 @@
/**
* External dependencies
*/
import { render } from '@testing-library/react';
/**
* Internal dependencies
*/
import ScrollLock from '..';
describe( 'scroll-lock', () => {
const lockingClassName = 'lockscroll';
it( 'locks when mounted', () => {
expect( document.documentElement ).not.toHaveClass( lockingClassName );
render( <ScrollLock /> );
expect( document.documentElement ).toHaveClass( lockingClassName );
} );
it( 'unlocks when unmounted', () => {
const { unmount } = render( <ScrollLock /> );
expect( document.documentElement ).toHaveClass( lockingClassName );
unmount();
expect( document.documentElement ).not.toHaveClass( lockingClassName );
} );
} );