Introduce `<Noninteractive>` component to disable form elements non-visually (https://github.com/woocommerce/woocommerce-blocks/pull/5157)
* Noninteractive component based on Disabled * Implement in cart/checkout * Pass down props * Update 'use-debounce' library * aria disabled
This commit is contained in:
parent
cbdbc6c7a1
commit
c067e990b4
|
@ -0,0 +1,96 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useRef, useLayoutEffect } from '@wordpress/element';
|
||||
import { focus } from '@wordpress/dom';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
|
||||
/**
|
||||
* Names of control nodes which need to be disabled.
|
||||
*/
|
||||
const FOCUSABLE_NODE_NAMES = [
|
||||
'BUTTON',
|
||||
'FIELDSET',
|
||||
'INPUT',
|
||||
'OPTGROUP',
|
||||
'OPTION',
|
||||
'SELECT',
|
||||
'TEXTAREA',
|
||||
'A',
|
||||
];
|
||||
|
||||
/**
|
||||
* Noninteractive component
|
||||
*
|
||||
* Makes children elements Noninteractive, preventing both mouse and keyboard events without affecting how the elements
|
||||
* appear visually. Used for previews.
|
||||
*
|
||||
* Based on the <Disabled> component in WordPress.
|
||||
*
|
||||
* @see https://github.com/WordPress/gutenberg/blob/trunk/packages/components/src/disabled/index.js
|
||||
*/
|
||||
const Noninteractive = ( {
|
||||
children,
|
||||
style = {},
|
||||
...props
|
||||
}: {
|
||||
children: React.ReactChildren;
|
||||
style?: Record< string, string >;
|
||||
} ): JSX.Element => {
|
||||
const node = useRef< HTMLDivElement >( null );
|
||||
|
||||
const disableFocus = () => {
|
||||
if ( node.current ) {
|
||||
focus.focusable.find( node.current ).forEach( ( focusable ) => {
|
||||
if ( FOCUSABLE_NODE_NAMES.includes( focusable.nodeName ) ) {
|
||||
focusable.setAttribute( 'tabindex', '-1' );
|
||||
}
|
||||
if ( focusable.hasAttribute( 'contenteditable' ) ) {
|
||||
focusable.setAttribute( 'contenteditable', 'false' );
|
||||
}
|
||||
} );
|
||||
}
|
||||
};
|
||||
|
||||
// Debounce re-disable since disabling process itself will incur additional mutations which should be ignored.
|
||||
const debounced = useDebouncedCallback( disableFocus, 0, {
|
||||
leading: true,
|
||||
} );
|
||||
|
||||
useLayoutEffect( () => {
|
||||
let observer: MutationObserver | undefined;
|
||||
disableFocus();
|
||||
if ( node.current ) {
|
||||
observer = new window.MutationObserver( debounced );
|
||||
observer.observe( node.current, {
|
||||
childList: true,
|
||||
attributes: true,
|
||||
subtree: true,
|
||||
} );
|
||||
}
|
||||
return () => {
|
||||
if ( observer ) {
|
||||
observer.disconnect();
|
||||
}
|
||||
debounced.cancel();
|
||||
};
|
||||
}, [ debounced ] );
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ node }
|
||||
aria-disabled="true"
|
||||
style={ {
|
||||
userSelect: 'none',
|
||||
pointerEvents: 'none',
|
||||
cursor: 'normal',
|
||||
...style,
|
||||
} }
|
||||
{ ...props }
|
||||
>
|
||||
{ children }
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Noninteractive;
|
|
@ -161,7 +161,7 @@ const usePaymentMethodRegistration = (
|
|||
registeredPaymentMethods,
|
||||
] );
|
||||
|
||||
const [ debouncedRefreshCanMakePayments ] = useDebouncedCallback(
|
||||
const debouncedRefreshCanMakePayments = useDebouncedCallback(
|
||||
refreshCanMakePayments,
|
||||
500
|
||||
);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { useBlockProps } from '@wordpress/block-editor';
|
||||
import { Disabled } from '@wordpress/components';
|
||||
import Noninteractive from '@woocommerce/base-components/noninteractive';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -19,9 +19,9 @@ export const Edit = ( {
|
|||
|
||||
return (
|
||||
<div { ...blockProps }>
|
||||
<Disabled>
|
||||
<Noninteractive>
|
||||
<Block className={ className } />
|
||||
</Disabled>
|
||||
</Noninteractive>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
|
||||
import { Disabled, PanelBody, ToggleControl } from '@wordpress/components';
|
||||
import { PanelBody, ToggleControl } from '@wordpress/components';
|
||||
import { getSetting } from '@woocommerce/settings';
|
||||
import Noninteractive from '@woocommerce/base-components/noninteractive';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -97,13 +98,13 @@ export const Edit = ( {
|
|||
</PanelBody>
|
||||
) }
|
||||
</InspectorControls>
|
||||
<Disabled>
|
||||
<Noninteractive>
|
||||
<Block
|
||||
className={ className }
|
||||
showRateAfterTaxName={ showRateAfterTaxName }
|
||||
isShippingCalculatorEnabled={ isShippingCalculatorEnabled }
|
||||
/>
|
||||
</Disabled>
|
||||
</Noninteractive>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -6,8 +6,8 @@ import { useSelect } from '@wordpress/data';
|
|||
import { __ } from '@wordpress/i18n';
|
||||
import { InspectorControls, useBlockProps } from '@wordpress/block-editor';
|
||||
import PageSelector from '@woocommerce/editor-components/page-selector';
|
||||
import { Disabled } from '@wordpress/components';
|
||||
import { CART_PAGE_ID } from '@woocommerce/block-settings';
|
||||
import Noninteractive from '@woocommerce/base-components/noninteractive';
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
|
@ -60,12 +60,12 @@ export const Edit = ( {
|
|||
/>
|
||||
) }
|
||||
</InspectorControls>
|
||||
<Disabled>
|
||||
<Noninteractive>
|
||||
<Block
|
||||
checkoutPageId={ checkoutPageId }
|
||||
className={ className }
|
||||
/>
|
||||
</Disabled>
|
||||
</Noninteractive>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -6,8 +6,9 @@ import { useSelect } from '@wordpress/data';
|
|||
import { __ } from '@wordpress/i18n';
|
||||
import { InspectorControls, useBlockProps } from '@wordpress/block-editor';
|
||||
import PageSelector from '@woocommerce/editor-components/page-selector';
|
||||
import { PanelBody, ToggleControl, Disabled } from '@wordpress/components';
|
||||
import { PanelBody, ToggleControl } from '@wordpress/components';
|
||||
import { CHECKOUT_PAGE_ID } from '@woocommerce/block-settings';
|
||||
import Noninteractive from '@woocommerce/base-components/noninteractive';
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
|
@ -82,12 +83,12 @@ export const Edit = ( {
|
|||
/>
|
||||
) }
|
||||
</InspectorControls>
|
||||
<Disabled>
|
||||
<Noninteractive>
|
||||
<Block
|
||||
showReturnToCart={ showReturnToCart }
|
||||
cartPageId={ cartPageId }
|
||||
/>
|
||||
</Disabled>
|
||||
</Noninteractive>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -2,13 +2,13 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { useMemo, useEffect, Fragment } from '@wordpress/element';
|
||||
import { Disabled } from 'wordpress-components';
|
||||
import {
|
||||
useCheckoutAddress,
|
||||
useStoreEvents,
|
||||
useEditorContext,
|
||||
} from '@woocommerce/base-context';
|
||||
import { AddressForm } from '@woocommerce/base-components/cart-checkout';
|
||||
import Noninteractive from '@woocommerce/base-components/noninteractive';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -56,7 +56,7 @@ const Block = ( {
|
|||
};
|
||||
}, [ showCompanyField, requireCompanyField, showApartmentField ] );
|
||||
|
||||
const AddressFormWrapperComponent = isEditor ? Disabled : Fragment;
|
||||
const AddressFormWrapperComponent = isEditor ? Noninteractive : Fragment;
|
||||
|
||||
return (
|
||||
<AddressFormWrapperComponent>
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
*/
|
||||
import classnames from 'classnames';
|
||||
import { useBlockProps } from '@wordpress/block-editor';
|
||||
import { Disabled } from '@wordpress/components';
|
||||
import { innerBlockAreas } from '@woocommerce/blocks-checkout';
|
||||
import Noninteractive from '@woocommerce/base-components/noninteractive';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -44,9 +44,9 @@ export const Edit = ( {
|
|||
) }
|
||||
>
|
||||
<Controls />
|
||||
<Disabled>
|
||||
<Noninteractive>
|
||||
<Block allowCreateAccount={ allowCreateAccount } />
|
||||
</Disabled>
|
||||
</Noninteractive>
|
||||
<AdditionalFields block={ innerBlockAreas.CONTACT_INFORMATION } />
|
||||
</FormStepBlock>
|
||||
);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { useBlockProps } from '@wordpress/block-editor';
|
||||
import { Disabled } from '@wordpress/components';
|
||||
import Noninteractive from '@woocommerce/base-components/noninteractive';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -14,9 +14,9 @@ export const Edit = (): JSX.Element => {
|
|||
const blockProps = useBlockProps();
|
||||
return (
|
||||
<div { ...blockProps }>
|
||||
<Disabled>
|
||||
<Noninteractive>
|
||||
<Block />
|
||||
</Disabled>
|
||||
</Noninteractive>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
|
||||
import { Disabled, PanelBody, ToggleControl } from '@wordpress/components';
|
||||
import { PanelBody, ToggleControl } from '@wordpress/components';
|
||||
import { getSetting } from '@woocommerce/settings';
|
||||
import Noninteractive from '@woocommerce/base-components/noninteractive';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -65,11 +66,11 @@ export const Edit = ( {
|
|||
</PanelBody>
|
||||
) }
|
||||
</InspectorControls>
|
||||
<Disabled>
|
||||
<Noninteractive>
|
||||
<Block
|
||||
showRateAfterTaxName={ attributes.showRateAfterTaxName }
|
||||
/>
|
||||
</Disabled>
|
||||
</Noninteractive>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -4,10 +4,11 @@
|
|||
import classnames from 'classnames';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { InspectorControls, useBlockProps } from '@wordpress/block-editor';
|
||||
import { PanelBody, Disabled, ExternalLink } from '@wordpress/components';
|
||||
import { PanelBody, ExternalLink } from '@wordpress/components';
|
||||
import { ADMIN_URL, getSetting } from '@woocommerce/settings';
|
||||
import ExternalLinkCard from '@woocommerce/editor-components/external-link-card';
|
||||
import { innerBlockAreas } from '@woocommerce/blocks-checkout';
|
||||
import Noninteractive from '@woocommerce/base-components/noninteractive';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -85,9 +86,9 @@ export const Edit = ( {
|
|||
</PanelBody>
|
||||
) }
|
||||
</InspectorControls>
|
||||
<Disabled>
|
||||
<Noninteractive>
|
||||
<Block />
|
||||
</Disabled>
|
||||
</Noninteractive>
|
||||
<AdditionalFields block={ innerBlockAreas.PAYMENT_METHODS } />
|
||||
</FormStepBlock>
|
||||
);
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useMemo, useEffect, Fragment } from '@wordpress/element';
|
||||
import { Disabled } from 'wordpress-components';
|
||||
import { AddressForm } from '@woocommerce/base-components/cart-checkout';
|
||||
import {
|
||||
useCheckoutAddress,
|
||||
|
@ -11,6 +10,7 @@ import {
|
|||
useEditorContext,
|
||||
} from '@woocommerce/base-context';
|
||||
import { CheckboxControl } from '@woocommerce/blocks-checkout';
|
||||
import Noninteractive from '@woocommerce/base-components/noninteractive';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -60,7 +60,7 @@ const Block = ( {
|
|||
};
|
||||
}, [ showCompanyField, requireCompanyField, showApartmentField ] );
|
||||
|
||||
const AddressFormWrapperComponent = isEditor ? Disabled : Fragment;
|
||||
const AddressFormWrapperComponent = isEditor ? Noninteractive : Fragment;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -4,10 +4,11 @@
|
|||
import classnames from 'classnames';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { InspectorControls, useBlockProps } from '@wordpress/block-editor';
|
||||
import { PanelBody, Disabled, ExternalLink } from '@wordpress/components';
|
||||
import { PanelBody, ExternalLink } from '@wordpress/components';
|
||||
import { ADMIN_URL, getSetting } from '@woocommerce/settings';
|
||||
import ExternalLinkCard from '@woocommerce/editor-components/external-link-card';
|
||||
import { innerBlockAreas } from '@woocommerce/blocks-checkout';
|
||||
import Noninteractive from '@woocommerce/base-components/noninteractive';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -119,9 +120,9 @@ export const Edit = ( {
|
|||
</PanelBody>
|
||||
) }
|
||||
</InspectorControls>
|
||||
<Disabled>
|
||||
<Noninteractive>
|
||||
<Block />
|
||||
</Disabled>
|
||||
</Noninteractive>
|
||||
<AdditionalFields block={ innerBlockAreas.SHIPPING_METHODS } />
|
||||
</FormStepBlock>
|
||||
);
|
||||
|
|
|
@ -69,7 +69,7 @@ const PriceFilterBlock = ( { attributes, isEditor = false } ) => {
|
|||
);
|
||||
|
||||
// Updates the query after a short delay.
|
||||
const [ debouncedUpdateQuery ] = useDebouncedCallback( onSubmit, 500 );
|
||||
const debouncedUpdateQuery = useDebouncedCallback( onSubmit, 500 );
|
||||
|
||||
// Callback when slider or input fields are changed.
|
||||
const onChange = useCallback(
|
||||
|
|
|
@ -28,9 +28,7 @@ mockUtils.getProducts = jest.fn().mockImplementation( () =>
|
|||
// Add a mock implementation of debounce for testing so we can spy on the onSearch call.
|
||||
mockUseDebounce.useDebouncedCallback = jest
|
||||
.fn()
|
||||
.mockImplementation( ( search ) => [
|
||||
() => mockUtils.getProducts( search ),
|
||||
] );
|
||||
.mockImplementation( ( search ) => () => mockUtils.getProducts( search ) );
|
||||
|
||||
describe( 'withSearchedProducts Component', () => {
|
||||
const { getProducts } = mockUtils;
|
||||
|
|
|
@ -50,17 +50,14 @@ const withSearchedProducts = (
|
|||
.catch( setErrorState );
|
||||
}, [ selected ] );
|
||||
|
||||
const [ debouncedSearch ] = useDebouncedCallback(
|
||||
( search: string ) => {
|
||||
const debouncedSearch = useDebouncedCallback( ( search: string ) => {
|
||||
getProducts( { selected, search } )
|
||||
.then( ( results ) => {
|
||||
setProductsList( results as ProductResponseItem[] );
|
||||
setIsLoading( false );
|
||||
} )
|
||||
.catch( setErrorState );
|
||||
},
|
||||
400
|
||||
);
|
||||
}, 400 );
|
||||
|
||||
const onSearch = useCallback(
|
||||
( search: string ) => {
|
||||
|
|
|
@ -15795,6 +15795,16 @@
|
|||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"bindings": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
||||
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"file-uri-to-path": "1.0.0"
|
||||
}
|
||||
},
|
||||
"bl": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
|
||||
|
@ -21430,6 +21440,13 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"file-uri-to-path": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"filelist": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.2.tgz",
|
||||
|
@ -29326,6 +29343,13 @@
|
|||
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
|
||||
"dev": true
|
||||
},
|
||||
"nan": {
|
||||
"version": "2.15.0",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz",
|
||||
"integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"nanoid": {
|
||||
"version": "3.1.23",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz",
|
||||
|
@ -38756,9 +38780,9 @@
|
|||
}
|
||||
},
|
||||
"use-debounce": {
|
||||
"version": "3.4.3",
|
||||
"resolved": "https://registry.npmjs.org/use-debounce/-/use-debounce-3.4.3.tgz",
|
||||
"integrity": "sha512-nxy+opOxDccWfhMl36J5BSCTpvcj89iaQk2OZWLAtBJQj7ISCtx1gh+rFbdjGfMl6vtCZf6gke/kYvrkVfHMoA=="
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/use-debounce/-/use-debounce-7.0.1.tgz",
|
||||
"integrity": "sha512-fOrzIw2wstbAJuv8PC9Vg4XgwyTLEOdq4y/Z3IhVl8DAE4svRcgyEUvrEXu+BMNgMoc3YND6qLT61kkgEKXh7Q=="
|
||||
},
|
||||
"use-enhanced-state": {
|
||||
"version": "0.0.13",
|
||||
|
@ -39105,7 +39129,11 @@
|
|||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
|
||||
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"bindings": "^1.5.0",
|
||||
"nan": "^2.12.1"
|
||||
}
|
||||
},
|
||||
"glob-parent": {
|
||||
"version": "3.1.0",
|
||||
|
|
|
@ -207,7 +207,7 @@
|
|||
"react-number-format": "4.4.3",
|
||||
"reakit": "1.3.11",
|
||||
"trim-html": "0.1.9",
|
||||
"use-debounce": "3.4.3",
|
||||
"use-debounce": "7.0.1",
|
||||
"wordpress-components": "npm:@wordpress/components@11.1.6"
|
||||
},
|
||||
"husky": {
|
||||
|
|
Loading…
Reference in New Issue