From b65d473796841afb6eb626d7d7243d70ee37a650 Mon Sep 17 00:00:00 2001 From: Joshua T Flowers Date: Thu, 22 Sep 2022 12:08:26 -0700 Subject: [PATCH] Add Sortable component accessibility (#34539) * Add key event handlers * Track selected item and focus * Fix selected indexes on up down selection and drop * Consolidate update item order logic * Hide horizontal items on drag * Update naming of ordering persistence * Announce keyboard events to screen readers * Consolidate reset method * Simplify drop index numbers and conditions * Fix up announcements * Add tests around new utils * Fall back for item name in announcement from aria label or alt text * Update lock file * Add changelog entry * Handle PR feedback * Fix up lock file after rebase * Update lock file * Update lock file again * Update lock file after pnpm7 * Use trunk lock file * Update lock file with a11y package * Try new lock file * Try lock file with pnpm add * pnpm add from root * Fix itemToString in SelectControl * Downgrade a11y to v3.5.0 --- .../changelog/add-sortable-accessibility | 4 + packages/js/components/package.json | 1 + .../select-control.tsx | 2 +- .../src/image-gallery/image-gallery.scss | 4 + .../src/sortable/sortable-handle.tsx | 1 - .../src/sortable/sortable-item.scss | 5 +- .../components/src/sortable/sortable-item.tsx | 33 +++- .../js/components/src/sortable/sortable.tsx | 169 +++++++++++++--- .../js/components/src/sortable/test/utils.ts | 48 +++-- packages/js/components/src/sortable/utils.ts | 96 +++++++--- pnpm-lock.yaml | 181 ++++++++++-------- 11 files changed, 395 insertions(+), 149 deletions(-) create mode 100644 packages/js/components/changelog/add-sortable-accessibility diff --git a/packages/js/components/changelog/add-sortable-accessibility b/packages/js/components/changelog/add-sortable-accessibility new file mode 100644 index 00000000000..407bba232b4 --- /dev/null +++ b/packages/js/components/changelog/add-sortable-accessibility @@ -0,0 +1,4 @@ +Significance: minor +Type: enhancement + +Improve Sortable component acessibility diff --git a/packages/js/components/package.json b/packages/js/components/package.json index 7c4bacb8fff..048cbd788ea 100644 --- a/packages/js/components/package.json +++ b/packages/js/components/package.json @@ -34,6 +34,7 @@ "@woocommerce/data": "workspace:*", "@woocommerce/date": "workspace:*", "@woocommerce/navigation": "workspace:*", + "@wordpress/a11y": "3.5.0", "@wordpress/api-fetch": "^6.0.1", "@wordpress/components": "^19.5.0", "@wordpress/compose": "^5.1.2", diff --git a/packages/js/components/src/experimental-select-control/select-control.tsx b/packages/js/components/src/experimental-select-control/select-control.tsx index 9e6c4f79ab4..e16f247505f 100644 --- a/packages/js/components/src/experimental-select-control/select-control.tsx +++ b/packages/js/components/src/experimental-select-control/select-control.tsx @@ -91,7 +91,7 @@ function SelectControl< ItemType = DefaultItemType >( { removeSelectedItem, // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - } = useMultipleSelection( { itemToString } ); + } = useMultipleSelection( { itemToString: getItemLabel } ); let selectedItems = selected === null ? [] : selected; selectedItems = Array.isArray( selectedItems ) ? selectedItems diff --git a/packages/js/components/src/image-gallery/image-gallery.scss b/packages/js/components/src/image-gallery/image-gallery.scss index cd87e1055d0..c8f38a7ecde 100644 --- a/packages/js/components/src/image-gallery/image-gallery.scss +++ b/packages/js/components/src/image-gallery/image-gallery.scss @@ -15,6 +15,10 @@ } .woocommerce-sortable__item { + &.is-dragging { + position: absolute; + } + &.is-dragging-over-before:before { left: calc( $gap / -2 - 1px ); } diff --git a/packages/js/components/src/sortable/sortable-handle.tsx b/packages/js/components/src/sortable/sortable-handle.tsx index 42ea901d46b..c9683e78466 100644 --- a/packages/js/components/src/sortable/sortable-handle.tsx +++ b/packages/js/components/src/sortable/sortable-handle.tsx @@ -26,7 +26,6 @@ export const SortableHandle = ( { draggable onDragStart={ onDragStart } onDragEnd={ onDragEnd } - aria-label={ __( 'Move this item', 'woocommerce' ) } > { children ? children : } diff --git a/packages/js/components/src/sortable/sortable-item.scss b/packages/js/components/src/sortable/sortable-item.scss index 06af0a43d5e..2d8b1a9710c 100644 --- a/packages/js/components/src/sortable/sortable-item.scss +++ b/packages/js/components/src/sortable/sortable-item.scss @@ -3,9 +3,12 @@ $place-holder-size: 3px; .woocommerce-sortable__item { margin: 0; position: relative; + list-style: none; &.is-dragging { - display: none; + opacity: 0; + height: 0; + width: 0; } &:before, diff --git a/packages/js/components/src/sortable/sortable-item.tsx b/packages/js/components/src/sortable/sortable-item.tsx index 22905bf8ada..b2aa1d45faa 100644 --- a/packages/js/components/src/sortable/sortable-item.tsx +++ b/packages/js/components/src/sortable/sortable-item.tsx @@ -1,9 +1,15 @@ /** * External dependencies */ -import { DragEvent, DragEventHandler } from 'react'; +import { __ } from '@wordpress/i18n'; +import { DragEvent, DragEventHandler, KeyboardEvent, useEffect } from 'react'; import classnames from 'classnames'; -import { cloneElement, createElement, Fragment } from '@wordpress/element'; +import { + cloneElement, + createElement, + Fragment, + useRef, +} from '@wordpress/element'; import { Draggable } from '@wordpress/components'; /** @@ -16,21 +22,27 @@ export type SortableItemProps = { index: number; children: SortableChild; className: string; + onKeyDown?: ( event: KeyboardEvent< HTMLLIElement > ) => void; isDragging?: boolean; onDragStart?: DragEventHandler< HTMLDivElement >; onDragEnd?: DragEventHandler< HTMLDivElement >; onDragOver?: DragEventHandler< HTMLLIElement >; + isSelected?: boolean; }; export const SortableItem = ( { id, children, className, + onKeyDown, isDragging = false, + isSelected = false, onDragStart = () => null, onDragEnd = () => null, onDragOver = () => null, }: SortableItemProps ) => { + const ref = useRef< HTMLLIElement >( null ); + const handleDragStart = ( event: DragEvent< HTMLDivElement > ) => { onDragStart( event ); }; @@ -40,13 +52,30 @@ export const SortableItem = ( { onDragEnd( event ); }; + useEffect( () => { + if ( isSelected && ref.current ) { + ref.current.focus(); + } + }, [ isSelected ] ); + return (
  • null, onOrderChange = () => null, }: SortableProps ) => { + const ref = useRef< HTMLOListElement >( null ); const [ items, setItems ] = useState< SortableChild[] >( [] ); + const [ selectedIndex, setSelectedIndex ] = useState< number >( 0 ); const [ dragIndex, setDragIndex ] = useState< number | null >( null ); const [ dropIndex, setDropIndex ] = useState< number | null >( null ); @@ -54,19 +62,14 @@ export const Sortable = ( { setItems( Array.isArray( children ) ? children : [ children ] ); }, [ children ] ); - const handleDragStart = ( - event: DragEvent< HTMLDivElement >, - index: number - ) => { - setDropIndex( index ); - setDragIndex( index ); - onDragStart( event ); + const resetIndexes = () => { + setTimeout( () => { + setDragIndex( null ); + setDropIndex( null ); + }, THROTTLE_TIME ); }; - const handleDragEnd = ( - event: DragEvent< HTMLDivElement >, - index: number - ) => { + const persistItemOrder = () => { if ( dropIndex !== null && dragIndex !== null && @@ -76,34 +79,154 @@ export const Sortable = ( { setItems( nextItems as JSX.Element[] ); onOrderChange( nextItems ); } + resetIndexes(); + }; - setTimeout( () => { - setDragIndex( null ); - setDropIndex( null ); - onDragEnd( event ); - }, THROTTLE_TIME ); + const handleDragStart = ( + event: DragEvent< HTMLDivElement >, + index: number + ) => { + setDropIndex( index ); + setDragIndex( index ); + onDragStart( event ); + }; + + const handleDragEnd = ( event: DragEvent< HTMLDivElement > ) => { + persistItemOrder(); + onDragEnd( event ); }; const handleDragOver = ( event: DragEvent< HTMLLIElement >, index: number ) => { - const targetIndex = isBefore( event, isHorizontal ) ? index : index + 1; + if ( dragIndex === null ) { + return; + } + + // Items before the current item cause a one off error when + // removed from the old array and spliced into the new array. + let targetIndex = dragIndex < index ? index : index + 1; + if ( isBefore( event, isHorizontal ) ) { + targetIndex--; + } + setDropIndex( targetIndex ); onDragOver( event ); }; const throttledHandleDragOver = useCallback( throttle( handleDragOver, THROTTLE_TIME ), - [] + [ dragIndex ] ); + const handleKeyDown = ( event: KeyboardEvent< HTMLLIElement > ) => { + const { key } = event; + const isSelecting = dragIndex === null || dropIndex === null; + const selectedLabel = getItemName( ref.current, selectedIndex ); + + // Select or drop on spacebar press. + if ( key === ' ' ) { + if ( isSelecting ) { + speak( + sprintf( + /** Translators: Selected item label */ + __( + '%s selected, use up and down arrow keys to reorder', + 'woocommerce' + ), + selectedLabel + ), + 'assertive' + ); + setDragIndex( selectedIndex ); + setDropIndex( selectedIndex ); + return; + } + + setSelectedIndex( dropIndex ); + speak( + sprintf( + /* translators: %1$s: Selected item label, %2$d: Current position in list, %3$d: List total length */ + __( + '%1$s dropped, position in list: %2$d of %3$d', + 'woocommerce' + ), + selectedLabel, + dropIndex + 1, + items.length + ), + 'assertive' + ); + persistItemOrder(); + return; + } + + if ( key === 'ArrowUp' ) { + if ( isSelecting ) { + setSelectedIndex( + getPreviousIndex( selectedIndex, items.length ) + ); + return; + } + const previousDropIndex = getPreviousIndex( + dropIndex, + items.length + ); + setDropIndex( previousDropIndex ); + speak( + sprintf( + /* translators: %1$s: Selected item label, %2$d: Current position in list, %3$d: List total length */ + __( '%1$s, position in list: %2$d of %3$d', 'woocommerce' ), + selectedLabel, + previousDropIndex + 1, + items.length + ), + 'assertive' + ); + return; + } + + if ( key === 'ArrowDown' ) { + if ( isSelecting ) { + setSelectedIndex( getNextIndex( selectedIndex, items.length ) ); + return; + } + const nextDropIndex = getNextIndex( dropIndex, items.length ); + setDropIndex( nextDropIndex ); + speak( + sprintf( + /* translators: %1$s: Selected item label, %2$d: Current position in list, %3$d: List total length */ + __( '%1$s, position in list: %2$d of %3$d', 'woocommerce' ), + selectedLabel, + nextDropIndex + 1, + items.length + ), + 'assertive' + ); + return; + } + + if ( key === 'Escape' ) { + resetIndexes(); + speak( + __( + 'Reordering cancelled. Restoring the original list order', + 'woocommerce' + ), + 'assertive' + ); + } + }; + return ( -
      { items.map( ( child, index ) => { const isDragging = index === dragIndex; @@ -131,7 +254,8 @@ export const Sortable = ( { id={ index } index={ index } isDragging={ isDragging } - onDragEnd={ ( event ) => handleDragEnd( event, index ) } + isSelected={ selectedIndex === index } + onDragEnd={ ( event ) => handleDragEnd( event ) } onDragStart={ ( event ) => handleDragStart( event, index ) } @@ -139,11 +263,12 @@ export const Sortable = ( { event.preventDefault(); throttledHandleDragOver( event, index ); } } + onKeyDown={ ( event ) => handleKeyDown( event ) } > { child } ); } ) } -
    + ); }; diff --git a/packages/js/components/src/sortable/test/utils.ts b/packages/js/components/src/sortable/test/utils.ts index 969c5ef5fef..5e0222a6f9e 100644 --- a/packages/js/components/src/sortable/test/utils.ts +++ b/packages/js/components/src/sortable/test/utils.ts @@ -7,23 +7,25 @@ import { DragEvent } from 'react'; * Internal dependencies */ import { - moveIndex, + getNextIndex, + getPreviousIndex, isBefore, isDraggingOverAfter, isDraggingOverBefore, isLastDroppable, + moveIndex, } from '../utils'; describe( 'moveIndex', () => { it( 'should move the from index to a higher index', () => { const arr = [ 'apple', 'orange', 'banana' ]; - const newArr = moveIndex( 0, 2, arr ); + const newArr = moveIndex( 0, 1, arr ); expect( newArr ).toEqual( [ 'orange', 'apple', 'banana' ] ); } ); it( 'should move the from index to the last index', () => { const arr = [ 'apple', 'orange', 'banana' ]; - const newArr = moveIndex( 0, 3, arr ); + const newArr = moveIndex( 0, 2, arr ); expect( newArr ).toEqual( [ 'orange', 'banana', 'apple' ] ); } ); @@ -142,30 +144,22 @@ describe( 'isDraggingOverAfter', () => { expect( isDraggingOverAfter( 0, 5, 2 ) ).toBeFalsy(); } ); - it( 'should return false when the item is being dragged', () => { - expect( isDraggingOverAfter( 2, 2, 3 ) ).toBeFalsy(); - } ); - - it( 'should return true when the item after is being dragged and the drop index is immediately after', () => { - expect( isDraggingOverAfter( 3, 4, 5 ) ).toBeTruthy(); + it( 'should return true when the an item before is dragged to the current index position', () => { + expect( isDraggingOverAfter( 3, 2, 3 ) ).toBeTruthy(); } ); } ); describe( 'isDraggingOverBefore', () => { - it( 'should return true when the item is the same as the drop index', () => { - expect( isDraggingOverBefore( 0, 1, 0 ) ).toBeTruthy(); - } ); - - it( 'should return false when the item is being dragged', () => { - expect( isDraggingOverBefore( 1, 1, 1 ) ).toBeFalsy(); + it( 'should return true when the item is being dropped immediately before this index', () => { + expect( isDraggingOverBefore( 1, 0, 0 ) ).toBeTruthy(); } ); it( 'should return false when the drop index is different', () => { expect( isDraggingOverBefore( 2, 1, 5 ) ).toBeFalsy(); } ); - it( 'should return true when the item before is being dragged and is also the drop index', () => { - expect( isDraggingOverBefore( 3, 2, 2 ) ).toBeTruthy(); + it( 'should return true when the item being dragged is a greater index and is dragged to this index', () => { + expect( isDraggingOverBefore( 3, 4, 3 ) ).toBeTruthy(); } ); } ); @@ -182,3 +176,23 @@ describe( 'isLastDroppable', () => { expect( isLastDroppable( 3, 4, 5 ) ).toBeTruthy(); } ); } ); + +describe( 'getNextIndex', () => { + it( 'should return the next index when one exists', () => { + expect( getNextIndex( 1, 5 ) ).toBe( 2 ); + } ); + + it( 'should return 0 when the end of the list has been reached', () => { + expect( getNextIndex( 4, 5 ) ).toBe( 0 ); + } ); +} ); + +describe( 'getPreviousIndex', () => { + it( 'should return the previous index when one exists', () => { + expect( getPreviousIndex( 3, 5 ) ).toBe( 2 ); + } ); + + it( 'should return the last index when the beginning of the list has been reached', () => { + expect( getPreviousIndex( 0, 5 ) ).toBe( 4 ); + } ); +} ); diff --git a/packages/js/components/src/sortable/utils.ts b/packages/js/components/src/sortable/utils.ts index 592225c420c..e9ef5d9cbc6 100644 --- a/packages/js/components/src/sortable/utils.ts +++ b/packages/js/components/src/sortable/utils.ts @@ -1,6 +1,7 @@ /** * External dependencies */ +import { __ } from '@wordpress/i18n'; import { DragEvent } from 'react'; /** @@ -19,10 +20,7 @@ export const moveIndex = < T >( const newArr = [ ...arr ]; const item = arr[ fromIndex ]; newArr.splice( fromIndex, 1 ); - - // Splicing the array reduces the array size by 1 after removal. - // Lower index items affect the position of where the item should be inserted. - newArr.splice( fromIndex < toIndex ? toIndex - 1 : toIndex, 0, item ); + newArr.splice( toIndex, 0, item ); return newArr; }; @@ -52,44 +50,36 @@ export const isBefore = ( return relativeY < middle; }; -export const isDraggingOverBefore = ( - index: number, - dragIndex: number | null, - dropIndex: number | null -) => { - if ( index === dragIndex ) { - return false; - } - - if ( dropIndex === index ) { - return true; - } - - if ( dragIndex === index - 1 && index - 1 === dropIndex ) { - return true; - } - - return false; -}; - export const isDraggingOverAfter = ( index: number, dragIndex: number | null, dropIndex: number | null ) => { - if ( index === dragIndex ) { + if ( dragIndex === null ) { return false; } - if ( dropIndex === index + 1 ) { - return true; + if ( dragIndex < index ) { + return dropIndex === index; } - if ( dragIndex === index + 1 && index + 2 === dropIndex ) { - return true; + return dropIndex === index + 1; +}; + +export const isDraggingOverBefore = ( + index: number, + dragIndex: number | null, + dropIndex: number | null +) => { + if ( dragIndex === null ) { + return false; } - return false; + if ( dragIndex < index ) { + return dropIndex === index - 1; + } + + return dropIndex === index; }; export const isLastDroppable = ( @@ -111,3 +101,49 @@ export const isLastDroppable = ( return false; }; + +export const getNextIndex = ( currentIndex: number, itemCount: number ) => { + let index = currentIndex + 1; + + if ( index > itemCount - 1 ) { + index = 0; + } + + return index; +}; + +export const getPreviousIndex = ( currentIndex: number, itemCount: number ) => { + let index = currentIndex - 1; + + if ( index < 0 ) { + index = itemCount - 1; + } + + return index; +}; + +export const getItemName = ( + parentNode: HTMLOListElement | null, + index: number +) => { + const listItemNode = parentNode?.childNodes[ index ] as HTMLLIElement; + + if ( index === null || ! listItemNode ) { + return null; + } + + if ( listItemNode.querySelector( '[aria-label]' ) ) { + return listItemNode.querySelector( '[aria-label]' )?.ariaLabel; + } + + if ( listItemNode.textContent ) { + return listItemNode.textContent; + } + + if ( listItemNode.querySelector( '[alt]' ) ) { + return ( listItemNode.querySelector( '[alt]' ) as HTMLImageElement ) + .alt; + } + + return __( 'Item', 'woocommerce' ); +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 58a4a2adeb9..c471374d7c8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -213,6 +213,7 @@ importers: '@woocommerce/eslint-plugin': workspace:* '@woocommerce/internal-style-build': workspace:* '@woocommerce/navigation': workspace:* + '@wordpress/a11y': 3.5.0 '@wordpress/api-fetch': ^6.0.1 '@wordpress/browserslist-config': ^4.1.1 '@wordpress/components': ^19.5.0 @@ -274,6 +275,7 @@ importers: '@woocommerce/data': link:../data '@woocommerce/date': link:../date '@woocommerce/navigation': link:../navigation + '@wordpress/a11y': 3.5.0 '@wordpress/api-fetch': 6.1.1 '@wordpress/components': 19.6.1_lxraipvdfcmyzw3sdzk3k7kygu '@wordpress/compose': 5.2.1_react@17.0.2 @@ -726,11 +728,11 @@ importers: semver: ^7.3.2 sprintf-js: ^1.1.2 dependencies: - '@automattic/puppeteer-utils': github.com/Automattic/puppeteer-utils/0f3ec50 + '@automattic/puppeteer-utils': github.com/Automattic/puppeteer-utils/0f3ec50_react-native@0.70.0 '@jest/test-sequencer': 27.5.1 '@slack/web-api': 6.5.1 '@woocommerce/api': link:../api - '@wordpress/e2e-test-utils': 4.16.1_ujr7gcpwq6xmoiv7mmimozpxs4 + '@wordpress/e2e-test-utils': 4.16.1_eod7vs2qyqnfu2oldnxglnszkq '@wordpress/jest-preset-default': 7.1.3_lzj7uau34542hrpvigopp7itta app-root-path: 3.0.0 commander: 4.1.1 @@ -786,7 +788,7 @@ importers: eslint: ^8.1.0 fishery: ^1.2.0 dependencies: - '@automattic/puppeteer-utils': github.com/Automattic/puppeteer-utils/0f3ec50_react-native@0.70.0 + '@automattic/puppeteer-utils': github.com/Automattic/puppeteer-utils/0f3ec50 '@woocommerce/api': link:../api '@wordpress/deprecated': 3.2.3 '@wordpress/e2e-test-utils': 5.3.2_ujr7gcpwq6xmoiv7mmimozpxs4 @@ -826,7 +828,7 @@ importers: typescript: ^4.6.2 dependencies: '@typescript-eslint/parser': 5.15.0_qfndwjbknwkswbha2khu23tpva - '@wordpress/eslint-plugin': 11.0.1_nxdi2qub4ra46tpyph3fb3wi2e + '@wordpress/eslint-plugin': 11.0.1_dkjil42ze2w7xdnhihp2ya7hea eslint-plugin-react-hooks: 4.3.0_eslint@8.12.0 eslint-plugin-testing-library: 5.1.0_qfndwjbknwkswbha2khu23tpva requireindex: 1.2.0 @@ -6598,7 +6600,7 @@ packages: react-native: '>=0.14.0 <1' dependencies: '@emotion/primitives-core': 10.0.27_qzeatvug73zaio2r3dlvejynye - react-native: 0.70.0_z264xedublairnjtgpe7xwxvmm + react-native: 0.70.0_wahjskecmqaqgpksn6xwa65wrm transitivePeerDependencies: - '@emotion/core' - react @@ -13170,6 +13172,7 @@ packages: typescript: 4.2.4 transitivePeerDependencies: - supports-color + dev: true /@typescript-eslint/parser/5.15.0_qfndwjbknwkswbha2khu23tpva: resolution: {integrity: sha512-NGAYP/+RDM2sVfmKiKOCgJYPstAO40vPAgACoWPO/+yoYKSgAXIFaBKsV8P0Cc7fwKgvj27SjRNX4L7f4/jCKQ==} @@ -13463,6 +13466,7 @@ packages: typescript: 4.2.4 transitivePeerDependencies: - supports-color + dev: true /@typescript-eslint/typescript-estree/5.15.0_typescript@4.6.2: resolution: {integrity: sha512-Hb0e3dGc35b75xLzixM3cSbG1sSbrTBQDfIScqdyvrfJZVEi4XWAT+UL/HMxEdrJNB8Yk28SKxPLtAhfCbBInA==} @@ -14029,7 +14033,7 @@ packages: engines: {node: '>=12'} dependencies: '@babel/runtime': 7.17.7 - '@wordpress/dom-ready': 3.4.1 + '@wordpress/dom-ready': 3.10.0 '@wordpress/i18n': 4.16.0 dev: false @@ -14038,7 +14042,7 @@ packages: engines: {node: '>=12'} dependencies: '@babel/runtime': 7.17.7 - '@wordpress/dom-ready': 3.5.0 + '@wordpress/dom-ready': 3.10.0 '@wordpress/i18n': 4.5.0 dev: false @@ -14287,7 +14291,7 @@ packages: '@wordpress/deprecated': 3.16.0 '@wordpress/dom': 3.4.1 '@wordpress/element': 4.14.0 - '@wordpress/hooks': 3.5.0 + '@wordpress/hooks': 3.16.0 '@wordpress/html-entities': 3.4.1 '@wordpress/i18n': 4.16.0 '@wordpress/is-shallow-equal': 4.4.1 @@ -15007,13 +15011,6 @@ packages: '@babel/runtime': 7.17.7 dev: false - /@wordpress/dom-ready/3.5.0: - resolution: {integrity: sha512-xhxZx3qH0UoWI3AMvZpB7NnKkHR5m5ifrBlinXM3+kSPQ8bIUkuOi2cFYdCnglPi0a+dd7OahWKFzXwDvgjO1w==} - engines: {node: '>=12'} - dependencies: - '@babel/runtime': 7.17.7 - dev: false - /@wordpress/dom/2.18.0: resolution: {integrity: sha512-tM2WeQuSObl3nzWjUTF0/dyLnA7sdl/MXaSe32D64OF89bjSyJvjUipI7gjKzI3kJ7ddGhwcTggGvSB06MOoCQ==} dependencies: @@ -15080,6 +15077,25 @@ packages: - react-native dev: false + /@wordpress/e2e-test-utils/4.16.1_eod7vs2qyqnfu2oldnxglnszkq: + resolution: {integrity: sha512-Dpsq5m0VSvjIhro2MjACSzkOkOf1jGEryzgEMW1ikbT6YI+motspHfGtisKXgYhZJOnjV4PwuEg+9lPVnd971g==} + engines: {node: '>=8'} + peerDependencies: + jest: '>=24' + puppeteer: '>=1.19.0' + dependencies: + '@babel/runtime': 7.17.7 + '@wordpress/keycodes': 2.19.3 + '@wordpress/url': 2.22.2_react-native@0.70.0 + jest: 27.5.1 + lodash: 4.17.21 + node-fetch: 2.6.7 + puppeteer: 2.1.1 + transitivePeerDependencies: + - encoding + - react-native + dev: false + /@wordpress/e2e-test-utils/4.16.1_ujr7gcpwq6xmoiv7mmimozpxs4: resolution: {integrity: sha512-Dpsq5m0VSvjIhro2MjACSzkOkOf1jGEryzgEMW1ikbT6YI+motspHfGtisKXgYhZJOnjV4PwuEg+9lPVnd971g==} engines: {node: '>=8'} @@ -15251,6 +15267,47 @@ packages: - supports-color dev: true + /@wordpress/eslint-plugin/11.0.1_dkjil42ze2w7xdnhihp2ya7hea: + resolution: {integrity: sha512-HDKwKjOmCaWdyJEtWKRAd0xK/NAXL/ykUP/I8l+zCvzvCXbS1UuixWN09RRzl09tv17JUtPiEqehDilkWRCBZg==} + engines: {node: '>=12', npm: '>=6.9'} + peerDependencies: + '@babel/core': '>=7' + eslint: '>=8' + prettier: '>=2' + typescript: '>=4' + peerDependenciesMeta: + prettier: + optional: true + typescript: + optional: true + dependencies: + '@babel/core': 7.17.8 + '@babel/eslint-parser': 7.17.0_6wgsqylbyqb6adwodmhnbhszeq + '@typescript-eslint/eslint-plugin': 5.15.0_zczsjq25e4fz43me4nms5bdgea + '@typescript-eslint/parser': 5.15.0_qfndwjbknwkswbha2khu23tpva + '@wordpress/babel-preset-default': 6.6.1 + '@wordpress/prettier-config': 1.1.3 + cosmiconfig: 7.0.1 + eslint: 8.12.0 + eslint-config-prettier: 8.5.0_eslint@8.12.0 + eslint-plugin-import: 2.25.4_zry6r357nk6buau6dccmtrgyfm + eslint-plugin-jest: 25.7.0_npxzm6erx3gbvnqfpyuutjmdj4 + eslint-plugin-jsdoc: 37.9.7_eslint@8.12.0 + eslint-plugin-jsx-a11y: 6.5.1_eslint@8.12.0 + eslint-plugin-prettier: 3.4.1_vd5mkele5dxuckzmv7qvtxxknq + eslint-plugin-react: 7.29.4_eslint@8.12.0 + eslint-plugin-react-hooks: 4.3.0_eslint@8.12.0 + globals: 13.12.0 + prettier: 2.3.0 + requireindex: 1.2.0 + typescript: 4.6.2 + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - jest + - supports-color + dev: false + /@wordpress/eslint-plugin/11.0.1_nxdi2qub4ra46tpyph3fb3wi2e: resolution: {integrity: sha512-HDKwKjOmCaWdyJEtWKRAd0xK/NAXL/ykUP/I8l+zCvzvCXbS1UuixWN09RRzl09tv17JUtPiEqehDilkWRCBZg==} engines: {node: '>=12', npm: '>=6.9'} @@ -15289,6 +15346,7 @@ packages: - eslint-import-resolver-webpack - jest - supports-color + dev: true /@wordpress/eslint-plugin/7.4.0_e6rt7vlgxfprtuallp2t3cvyi4: resolution: {integrity: sha512-HJpDYz2drtC9rY8MiYtYJ3cimioEIweGyb3P2DQTjUZ3sC4AGg+97PhXLHUdKfsFQ31JRxyLS9kKuGdDVBwWww==} @@ -15355,13 +15413,6 @@ packages: dependencies: '@babel/runtime': 7.17.7 - /@wordpress/hooks/3.15.0: - resolution: {integrity: sha512-w0kFs8xX4C+ofTszaNaggdvs+cuVl4wOCPULncOfXLEWo4MBwUpx82BFTeV5ql44oOF6iEEKHcR75gOOXCXOVQ==} - engines: {node: '>=12'} - dependencies: - '@babel/runtime': 7.17.7 - dev: false - /@wordpress/hooks/3.16.0: resolution: {integrity: sha512-KpY8KFp2/3TX6lKmffNmdkeaH9c4CN1iJ8SiCufjGgRCnVWmWe/HcEJ5OjhUvBnRkhsLMY7pvlXMU8Mh7nLxyA==} engines: {node: '>=12'} @@ -15428,7 +15479,7 @@ packages: hasBin: true dependencies: '@babel/runtime': 7.17.7 - '@wordpress/hooks': 3.15.0 + '@wordpress/hooks': 3.16.0 gettext-parser: 1.4.0 lodash: 4.17.21 memize: 1.1.0 @@ -15928,7 +15979,7 @@ packages: react: ^17.0.0 dependencies: '@babel/runtime': 7.17.7 - '@wordpress/a11y': 3.4.1 + '@wordpress/a11y': 3.10.0 '@wordpress/compose': 5.2.1_react@17.0.2 '@wordpress/data': 6.15.0_react@17.0.2 '@wordpress/element': 4.14.0 @@ -21508,7 +21559,7 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 5.15.0_7bdza2waopngrtr4qhziihsire + '@typescript-eslint/parser': 5.15.0_qfndwjbknwkswbha2khu23tpva debug: 3.2.7 eslint-import-resolver-node: 0.3.6 find-up: 2.1.0 @@ -21641,7 +21692,7 @@ packages: '@typescript-eslint/parser': optional: true dependencies: - '@typescript-eslint/parser': 5.15.0_7bdza2waopngrtr4qhziihsire + '@typescript-eslint/parser': 5.15.0_qfndwjbknwkswbha2khu23tpva array-includes: 3.1.4 array.prototype.flat: 1.2.5 debug: 2.6.9 @@ -21899,6 +21950,7 @@ packages: eslint: 8.12.0 eslint-config-prettier: 8.5.0_eslint@8.12.0 prettier-linter-helpers: 1.0.0 + dev: true /eslint-plugin-prettier/3.4.1_gs3qp45fhmeuf44rtqp7mvwogy: resolution: {integrity: sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==} @@ -21917,6 +21969,23 @@ packages: prettier-linter-helpers: 1.0.0 dev: true + /eslint-plugin-prettier/3.4.1_vd5mkele5dxuckzmv7qvtxxknq: + resolution: {integrity: sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==} + engines: {node: '>=6.0.0'} + peerDependencies: + eslint: '>=5.0.0' + eslint-config-prettier: '*' + prettier: '>=1.13.0' + peerDependenciesMeta: + eslint-config-prettier: + optional: true + dependencies: + eslint: 8.12.0 + eslint-config-prettier: 8.5.0_eslint@8.12.0 + prettier: 2.3.0 + prettier-linter-helpers: 1.0.0 + dev: false + /eslint-plugin-prettier/3.4.1_y56j6i6hor3dgpuevglq6yd6ay: resolution: {integrity: sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==} engines: {node: '>=6.0.0'} @@ -28578,35 +28647,6 @@ packages: resolution: {integrity: sha512-KmxeBlRjwoqCnBBKGsihFtvsBHyUFlBxJPK4FzeYcIuBfdjv6jFys44JITAgSTbQD+vIdwMEfyZklsuQX0yI1Q==} dev: false - /jscodeshift/0.13.1: - resolution: {integrity: sha512-lGyiEbGOvmMRKgWk4vf+lUrCWO/8YR8sUR3FKF1Cq5fovjZDlIcw3Hu5ppLHAnEXshVffvaM0eyuY/AbOeYpnQ==} - hasBin: true - peerDependencies: - '@babel/preset-env': ^7.1.6 - dependencies: - '@babel/core': 7.17.8 - '@babel/parser': 7.17.8 - '@babel/plugin-proposal-class-properties': 7.16.7_@babel+core@7.17.8 - '@babel/plugin-proposal-nullish-coalescing-operator': 7.16.7_@babel+core@7.17.8 - '@babel/plugin-proposal-optional-chaining': 7.16.7_@babel+core@7.17.8 - '@babel/plugin-transform-modules-commonjs': 7.17.7_@babel+core@7.17.8 - '@babel/preset-flow': 7.16.7_@babel+core@7.17.8 - '@babel/preset-typescript': 7.16.7_@babel+core@7.17.8 - '@babel/register': 7.18.9_@babel+core@7.17.8 - babel-core: 7.0.0-bridge.0_@babel+core@7.17.8 - chalk: 4.1.2 - flow-parser: 0.121.0 - graceful-fs: 4.2.9 - micromatch: 3.1.10 - neo-async: 2.6.2 - node-dir: 0.1.17 - recast: 0.20.5 - temp: 0.8.4 - write-file-atomic: 2.4.1 - transitivePeerDependencies: - - supports-color - dev: false - /jscodeshift/0.13.1_@babel+preset-env@7.16.11: resolution: {integrity: sha512-lGyiEbGOvmMRKgWk4vf+lUrCWO/8YR8sUR3FKF1Cq5fovjZDlIcw3Hu5ppLHAnEXshVffvaM0eyuY/AbOeYpnQ==} hasBin: true @@ -28635,7 +28675,6 @@ packages: write-file-atomic: 2.4.1 transitivePeerDependencies: - supports-color - dev: true /jsdoc-type-pratt-parser/1.1.1: resolution: {integrity: sha512-uelRmpghNwPBuZScwgBG/OzodaFk5RbO5xaivBdsAY70icWfShwZ7PCMO0x1zSkOa8T1FzHThmrdoyg/0AwV5g==} @@ -30437,10 +30476,6 @@ packages: brorand: 1.1.0 dev: true - /mime-db/1.51.0: - resolution: {integrity: sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==} - engines: {node: '>= 0.6'} - /mime-db/1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} @@ -30451,12 +30486,6 @@ packages: charset: 1.0.1 dev: false - /mime-types/2.1.34: - resolution: {integrity: sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==} - engines: {node: '>= 0.6'} - dependencies: - mime-db: 1.51.0 - /mime-types/2.1.35: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} @@ -30472,6 +30501,7 @@ packages: resolution: {integrity: sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==} engines: {node: '>=4.0.0'} hasBin: true + dev: true /mime/2.6.0: resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} @@ -33231,7 +33261,6 @@ packages: resolution: {integrity: sha512-kXtO4s0Lz/DW/IJ9QdWhAf7/NmPWQXkFr/r/WkR3vyI+0v8amTDxiaQSLzs8NBlytfLWX/7uQUMIW677yLKl4w==} engines: {node: '>=10.13.0'} hasBin: true - dev: true /pretty-bytes/3.0.1: resolution: {integrity: sha512-eb7ZAeUTgfh294cElcu51w+OTRp/6ItW758LjwJSK72LDevcuJn0P4eD71PLMDGPwwatXmAmYHTkzvpKlJE3ow==} @@ -33577,8 +33606,8 @@ packages: debug: 4.3.4 extract-zip: 1.7.0 https-proxy-agent: 4.0.0 - mime: 2.5.2 - mime-types: 2.1.34 + mime: 2.6.0 + mime-types: 2.1.35 progress: 2.0.3 proxy-from-env: 1.1.0 rimraf: 2.7.1 @@ -34158,12 +34187,12 @@ packages: moment: 2.29.1 dev: false - /react-native-codegen/0.70.4: + /react-native-codegen/0.70.4_@babel+preset-env@7.16.11: resolution: {integrity: sha512-bPyd5jm840omfx24VRyMP+KPzAefpRDwE18w5ywMWHCWZBSqLn1qI9WgBPnavlIrjTEuzxznWQNcaA26lw8AMQ==} dependencies: '@babel/parser': 7.17.8 flow-parser: 0.121.0 - jscodeshift: 0.13.1 + jscodeshift: 0.13.1_@babel+preset-env@7.16.11 nullthrows: 1.1.1 transitivePeerDependencies: - '@babel/preset-env' @@ -34186,11 +34215,11 @@ packages: peerDependencies: react-native: '*' dependencies: - react-native: 0.70.0_z264xedublairnjtgpe7xwxvmm + react-native: 0.70.0_wahjskecmqaqgpksn6xwa65wrm whatwg-url-without-unicode: 8.0.0-3 dev: false - /react-native/0.70.0_z264xedublairnjtgpe7xwxvmm: + /react-native/0.70.0_wahjskecmqaqgpksn6xwa65wrm: resolution: {integrity: sha512-QjXLbrK9f+/B2eCzn6kAvglLV/8nwPuFGaFv7ggPpAzFRyx5bVN1dwQLHL3MrP7iXR/M7Jc6Nnid7tmRSic6vA==} engines: {node: '>=14'} hasBin: true @@ -34220,7 +34249,7 @@ packages: promise: 8.2.0 react: 16.14.0 react-devtools-core: 4.24.0 - react-native-codegen: 0.70.4 + react-native-codegen: 0.70.4_@babel+preset-env@7.16.11 react-native-gradle-plugin: 0.70.2 react-refresh: 0.4.3 react-shallow-renderer: 16.15.0_react@16.14.0 @@ -35518,7 +35547,7 @@ packages: is-typedarray: 1.0.0 isstream: 0.1.2 json-stringify-safe: 5.0.1 - mime-types: 2.1.34 + mime-types: 2.1.35 oauth-sign: 0.9.0 performance-now: 2.1.0 qs: 6.5.2 @@ -38428,6 +38457,7 @@ packages: dependencies: tslib: 1.14.1 typescript: 4.2.4 + dev: true /tsutils/3.21.0_typescript@4.4.4: resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} @@ -38689,6 +38719,7 @@ packages: resolution: {integrity: sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==} engines: {node: '>=4.2.0'} hasBin: true + dev: true /typescript/4.4.4: resolution: {integrity: sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==}