@wordpress/components 28.0.3 → 28.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +133 -120
- package/build/custom-select-control-v2/custom-select.js +1 -3
- package/build/custom-select-control-v2/custom-select.js.map +1 -1
- package/build/custom-select-control-v2/legacy-component/index.js +16 -10
- package/build/custom-select-control-v2/legacy-component/index.js.map +1 -1
- package/build/custom-select-control-v2/styles.js +34 -19
- package/build/custom-select-control-v2/styles.js.map +1 -1
- package/build/custom-select-control-v2/types.js.map +1 -1
- package/build/drop-zone/index.js +16 -79
- package/build/drop-zone/index.js.map +1 -1
- package/build/radio-control/index.js +1 -1
- package/build/radio-control/index.js.map +1 -1
- package/build-module/custom-select-control-v2/custom-select.js +1 -3
- package/build-module/custom-select-control-v2/custom-select.js.map +1 -1
- package/build-module/custom-select-control-v2/legacy-component/index.js +16 -11
- package/build-module/custom-select-control-v2/legacy-component/index.js.map +1 -1
- package/build-module/custom-select-control-v2/styles.js +33 -18
- package/build-module/custom-select-control-v2/styles.js.map +1 -1
- package/build-module/custom-select-control-v2/types.js.map +1 -1
- package/build-module/drop-zone/index.js +17 -80
- package/build-module/drop-zone/index.js.map +1 -1
- package/build-module/radio-control/index.js +1 -1
- package/build-module/radio-control/index.js.map +1 -1
- package/build-style/style-rtl.css +30 -5
- package/build-style/style.css +30 -5
- package/build-types/checkbox-control/stories/index.story.d.ts.map +1 -1
- package/build-types/custom-select-control/stories/index.story.d.ts +15 -0
- package/build-types/custom-select-control/stories/index.story.d.ts.map +1 -1
- package/build-types/custom-select-control-v2/legacy-component/index.d.ts.map +1 -1
- package/build-types/custom-select-control-v2/styles.d.ts +4 -0
- package/build-types/custom-select-control-v2/styles.d.ts.map +1 -1
- package/build-types/custom-select-control-v2/types.d.ts +1 -0
- package/build-types/custom-select-control-v2/types.d.ts.map +1 -1
- package/build-types/drop-zone/index.d.ts +3 -0
- package/build-types/drop-zone/index.d.ts.map +1 -1
- package/package.json +19 -19
- package/src/checkbox-control/stories/index.story.tsx +3 -0
- package/src/checkbox-control/style.scss +4 -2
- package/src/custom-select-control/stories/index.story.tsx +32 -3
- package/src/custom-select-control/test/index.js +205 -22
- package/src/custom-select-control-v2/custom-select.tsx +1 -1
- package/src/custom-select-control-v2/legacy-component/index.tsx +18 -10
- package/src/custom-select-control-v2/legacy-component/test/index.tsx +220 -38
- package/src/custom-select-control-v2/styles.ts +22 -10
- package/src/custom-select-control-v2/types.ts +1 -0
- package/src/drop-zone/index.tsx +17 -76
- package/src/drop-zone/style.scss +51 -16
- package/src/radio-control/index.tsx +1 -1
- package/src/radio-control/style.scss +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -14,7 +14,11 @@ import { useState } from '@wordpress/element';
|
|
|
14
14
|
*/
|
|
15
15
|
import UncontrolledCustomSelectControl from '..';
|
|
16
16
|
|
|
17
|
-
const
|
|
17
|
+
const customClassName = 'amber-skies';
|
|
18
|
+
const customStyles = {
|
|
19
|
+
backgroundColor: 'rgb(127, 255, 212)',
|
|
20
|
+
rotate: '13deg',
|
|
21
|
+
};
|
|
18
22
|
|
|
19
23
|
const props = {
|
|
20
24
|
label: 'label!',
|
|
@@ -26,7 +30,7 @@ const props = {
|
|
|
26
30
|
{
|
|
27
31
|
key: 'flower2',
|
|
28
32
|
name: 'crimson clover',
|
|
29
|
-
className:
|
|
33
|
+
className: customClassName,
|
|
30
34
|
},
|
|
31
35
|
{
|
|
32
36
|
key: 'flower3',
|
|
@@ -35,37 +39,114 @@ const props = {
|
|
|
35
39
|
{
|
|
36
40
|
key: 'color1',
|
|
37
41
|
name: 'amber',
|
|
38
|
-
className:
|
|
42
|
+
className: customClassName,
|
|
39
43
|
},
|
|
40
44
|
{
|
|
41
45
|
key: 'color2',
|
|
42
46
|
name: 'aquamarine',
|
|
43
|
-
style:
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
+
style: customStyles,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
key: 'color3',
|
|
51
|
+
name: 'tomato (with custom props)',
|
|
52
|
+
className: customClassName,
|
|
53
|
+
style: customStyles,
|
|
54
|
+
// try passing a valid HTML attribute
|
|
55
|
+
'aria-label': 'test label',
|
|
56
|
+
// try adding a custom prop
|
|
57
|
+
customPropFoo: 'foo',
|
|
47
58
|
},
|
|
48
59
|
],
|
|
49
60
|
};
|
|
50
61
|
|
|
51
|
-
const ControlledCustomSelectControl = ( {
|
|
52
|
-
|
|
62
|
+
const ControlledCustomSelectControl = ( {
|
|
63
|
+
options,
|
|
64
|
+
onChange: onChangeProp,
|
|
65
|
+
...restProps
|
|
66
|
+
} ) => {
|
|
67
|
+
const [ value, setValue ] = useState( restProps.value ?? options[ 0 ] );
|
|
68
|
+
|
|
69
|
+
const onChange = ( changeObject ) => {
|
|
70
|
+
onChangeProp?.( changeObject );
|
|
71
|
+
setValue( changeObject.selectedItem );
|
|
72
|
+
};
|
|
73
|
+
|
|
53
74
|
return (
|
|
54
75
|
<UncontrolledCustomSelectControl
|
|
55
76
|
{ ...restProps }
|
|
56
77
|
options={ options }
|
|
57
|
-
onChange={
|
|
78
|
+
onChange={ onChange }
|
|
58
79
|
value={ options.find( ( option ) => option.key === value.key ) }
|
|
59
80
|
/>
|
|
60
81
|
);
|
|
61
82
|
};
|
|
62
83
|
|
|
84
|
+
it( 'Should apply external controlled updates', async () => {
|
|
85
|
+
const mockOnChange = jest.fn();
|
|
86
|
+
const { rerender } = render(
|
|
87
|
+
<UncontrolledCustomSelectControl
|
|
88
|
+
{ ...props }
|
|
89
|
+
value={ props.options[ 0 ] }
|
|
90
|
+
onChange={ mockOnChange }
|
|
91
|
+
/>
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
const currentSelectedItem = screen.getByRole( 'button', {
|
|
95
|
+
expanded: false,
|
|
96
|
+
} );
|
|
97
|
+
|
|
98
|
+
expect( currentSelectedItem ).toHaveTextContent( props.options[ 0 ].name );
|
|
99
|
+
|
|
100
|
+
rerender(
|
|
101
|
+
<UncontrolledCustomSelectControl
|
|
102
|
+
{ ...props }
|
|
103
|
+
value={ props.options[ 1 ] }
|
|
104
|
+
/>
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
expect( currentSelectedItem ).toHaveTextContent( props.options[ 1 ].name );
|
|
108
|
+
|
|
109
|
+
expect( mockOnChange ).not.toHaveBeenCalled();
|
|
110
|
+
} );
|
|
111
|
+
|
|
63
112
|
describe.each( [
|
|
64
|
-
[ '
|
|
65
|
-
[ '
|
|
113
|
+
[ 'Uncontrolled', UncontrolledCustomSelectControl ],
|
|
114
|
+
[ 'Controlled', ControlledCustomSelectControl ],
|
|
66
115
|
] )( 'CustomSelectControl %s', ( ...modeAndComponent ) => {
|
|
67
116
|
const [ , Component ] = modeAndComponent;
|
|
68
117
|
|
|
118
|
+
it( 'Should select the first option when no explicit initial value is passed without firing onChange', () => {
|
|
119
|
+
const mockOnChange = jest.fn();
|
|
120
|
+
render( <Component { ...props } onChange={ mockOnChange } /> );
|
|
121
|
+
|
|
122
|
+
expect(
|
|
123
|
+
screen.getByRole( 'button', {
|
|
124
|
+
expanded: false,
|
|
125
|
+
} )
|
|
126
|
+
).toHaveTextContent( props.options[ 0 ].name );
|
|
127
|
+
|
|
128
|
+
expect( mockOnChange ).not.toHaveBeenCalled();
|
|
129
|
+
} );
|
|
130
|
+
|
|
131
|
+
it( 'Should pick the initially selected option if the value prop is passed without firing onChange', async () => {
|
|
132
|
+
const mockOnChange = jest.fn();
|
|
133
|
+
render(
|
|
134
|
+
<Component
|
|
135
|
+
{ ...props }
|
|
136
|
+
onChange={ mockOnChange }
|
|
137
|
+
value={ props.options[ 3 ] }
|
|
138
|
+
/>
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
expect(
|
|
142
|
+
screen.getByRole( 'button', {
|
|
143
|
+
expanded: false,
|
|
144
|
+
} )
|
|
145
|
+
).toHaveTextContent( props.options[ 3 ].name );
|
|
146
|
+
|
|
147
|
+
expect( mockOnChange ).not.toHaveBeenCalled();
|
|
148
|
+
} );
|
|
149
|
+
|
|
69
150
|
it( 'Should replace the initial selection when a new item is selected', async () => {
|
|
70
151
|
const user = userEvent.setup();
|
|
71
152
|
|
|
@@ -144,7 +225,7 @@ describe.each( [
|
|
|
144
225
|
// assert against filtered array
|
|
145
226
|
itemsWithClass.map( ( { name } ) =>
|
|
146
227
|
expect( screen.getByRole( 'option', { name } ) ).toHaveClass(
|
|
147
|
-
|
|
228
|
+
customClassName
|
|
148
229
|
)
|
|
149
230
|
);
|
|
150
231
|
|
|
@@ -156,15 +237,13 @@ describe.each( [
|
|
|
156
237
|
// assert against filtered array
|
|
157
238
|
itemsWithoutClass.map( ( { name } ) =>
|
|
158
239
|
expect( screen.getByRole( 'option', { name } ) ).not.toHaveClass(
|
|
159
|
-
|
|
240
|
+
customClassName
|
|
160
241
|
)
|
|
161
242
|
);
|
|
162
243
|
} );
|
|
163
244
|
|
|
164
245
|
it( 'Should apply styles only to options that have styles defined', async () => {
|
|
165
246
|
const user = userEvent.setup();
|
|
166
|
-
const customStyles =
|
|
167
|
-
'background-color: rgb(127, 255, 212); rotate: 13deg;';
|
|
168
247
|
|
|
169
248
|
render( <Component { ...props } /> );
|
|
170
249
|
|
|
@@ -262,6 +341,105 @@ describe.each( [
|
|
|
262
341
|
expect( screen.getByRole( 'option', { name: /hint/i } ) ).toBeVisible();
|
|
263
342
|
} );
|
|
264
343
|
|
|
344
|
+
it( 'Should return object onChange', async () => {
|
|
345
|
+
const user = userEvent.setup();
|
|
346
|
+
const mockOnChange = jest.fn();
|
|
347
|
+
|
|
348
|
+
render( <Component { ...props } onChange={ mockOnChange } /> );
|
|
349
|
+
|
|
350
|
+
await user.click(
|
|
351
|
+
screen.getByRole( 'button', {
|
|
352
|
+
expanded: false,
|
|
353
|
+
} )
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
await user.click(
|
|
357
|
+
screen.getByRole( 'option', {
|
|
358
|
+
name: 'aquamarine',
|
|
359
|
+
} )
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
expect( mockOnChange ).toHaveBeenCalledTimes( 1 );
|
|
363
|
+
expect( mockOnChange ).toHaveBeenLastCalledWith(
|
|
364
|
+
expect.objectContaining( {
|
|
365
|
+
inputValue: '',
|
|
366
|
+
isOpen: false,
|
|
367
|
+
selectedItem: expect.objectContaining( {
|
|
368
|
+
name: 'aquamarine',
|
|
369
|
+
} ),
|
|
370
|
+
type: expect.any( String ),
|
|
371
|
+
} )
|
|
372
|
+
);
|
|
373
|
+
} );
|
|
374
|
+
|
|
375
|
+
it( 'Should return selectedItem object when specified onChange', async () => {
|
|
376
|
+
const user = userEvent.setup();
|
|
377
|
+
const mockOnChange = jest.fn();
|
|
378
|
+
|
|
379
|
+
render( <Component { ...props } onChange={ mockOnChange } /> );
|
|
380
|
+
|
|
381
|
+
await user.tab();
|
|
382
|
+
expect(
|
|
383
|
+
screen.getByRole( 'button', {
|
|
384
|
+
expanded: false,
|
|
385
|
+
} )
|
|
386
|
+
).toHaveFocus();
|
|
387
|
+
|
|
388
|
+
await user.keyboard( 'p' );
|
|
389
|
+
await user.keyboard( '{enter}' );
|
|
390
|
+
|
|
391
|
+
expect( mockOnChange ).toHaveBeenCalledTimes( 1 );
|
|
392
|
+
expect( mockOnChange ).toHaveBeenLastCalledWith(
|
|
393
|
+
expect.objectContaining( {
|
|
394
|
+
selectedItem: expect.objectContaining( {
|
|
395
|
+
key: 'flower3',
|
|
396
|
+
name: 'poppy',
|
|
397
|
+
} ),
|
|
398
|
+
} )
|
|
399
|
+
);
|
|
400
|
+
} );
|
|
401
|
+
|
|
402
|
+
it( "Should pass arbitrary props to onChange's selectedItem, but apply only style and className to DOM elements", async () => {
|
|
403
|
+
const user = userEvent.setup();
|
|
404
|
+
const onChangeMock = jest.fn();
|
|
405
|
+
|
|
406
|
+
render( <Component { ...props } onChange={ onChangeMock } /> );
|
|
407
|
+
|
|
408
|
+
const currentSelectedItem = screen.getByRole( 'button', {
|
|
409
|
+
expanded: false,
|
|
410
|
+
} );
|
|
411
|
+
|
|
412
|
+
await user.click( currentSelectedItem );
|
|
413
|
+
|
|
414
|
+
const optionWithCustomAttributes = screen.getByRole( 'option', {
|
|
415
|
+
name: 'tomato (with custom props)',
|
|
416
|
+
} );
|
|
417
|
+
|
|
418
|
+
// Assert that the option element does not have the custom attributes
|
|
419
|
+
expect( optionWithCustomAttributes ).not.toHaveAttribute(
|
|
420
|
+
'customPropFoo'
|
|
421
|
+
);
|
|
422
|
+
expect( optionWithCustomAttributes ).not.toHaveAttribute(
|
|
423
|
+
'aria-label'
|
|
424
|
+
);
|
|
425
|
+
|
|
426
|
+
await user.click( optionWithCustomAttributes );
|
|
427
|
+
|
|
428
|
+
expect( onChangeMock ).toHaveBeenCalledTimes( 1 );
|
|
429
|
+
expect( onChangeMock ).toHaveBeenCalledWith(
|
|
430
|
+
expect.objectContaining( {
|
|
431
|
+
selectedItem: expect.objectContaining( {
|
|
432
|
+
key: 'color3',
|
|
433
|
+
name: 'tomato (with custom props)',
|
|
434
|
+
className: customClassName,
|
|
435
|
+
style: customStyles,
|
|
436
|
+
'aria-label': 'test label',
|
|
437
|
+
customPropFoo: 'foo',
|
|
438
|
+
} ),
|
|
439
|
+
} )
|
|
440
|
+
);
|
|
441
|
+
} );
|
|
442
|
+
|
|
265
443
|
describe( 'Keyboard behavior and accessibility', () => {
|
|
266
444
|
it( 'Captures the keypress event and does not let it propagate', async () => {
|
|
267
445
|
const user = userEvent.setup();
|
|
@@ -311,7 +489,9 @@ describe.each( [
|
|
|
311
489
|
await user.keyboard( '{arrowdown}' );
|
|
312
490
|
await user.keyboard( '{enter}' );
|
|
313
491
|
|
|
314
|
-
expect( currentSelectedItem ).toHaveTextContent(
|
|
492
|
+
expect( currentSelectedItem ).toHaveTextContent(
|
|
493
|
+
props.options[ 1 ].name
|
|
494
|
+
);
|
|
315
495
|
} );
|
|
316
496
|
|
|
317
497
|
it( 'Should be able to type characters to select matching options', async () => {
|
|
@@ -422,11 +602,14 @@ describe.each( [
|
|
|
422
602
|
const onBlurMock = jest.fn();
|
|
423
603
|
|
|
424
604
|
render(
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
605
|
+
<>
|
|
606
|
+
<Component
|
|
607
|
+
{ ...props }
|
|
608
|
+
onFocus={ onFocusMock }
|
|
609
|
+
onBlur={ onBlurMock }
|
|
610
|
+
/>
|
|
611
|
+
<button>Focus stop</button>
|
|
612
|
+
</>
|
|
430
613
|
);
|
|
431
614
|
|
|
432
615
|
const currentSelectedItem = screen.getByRole( 'button', {
|
|
@@ -14,7 +14,7 @@ import * as Styled from '../styles';
|
|
|
14
14
|
|
|
15
15
|
function CustomSelectControl( props: LegacyCustomSelectProps ) {
|
|
16
16
|
const {
|
|
17
|
-
__experimentalShowSelectedHint,
|
|
17
|
+
__experimentalShowSelectedHint = false,
|
|
18
18
|
__next40pxDefaultSize = false,
|
|
19
19
|
describedBy,
|
|
20
20
|
options,
|
|
@@ -27,7 +27,11 @@ function CustomSelectControl( props: LegacyCustomSelectProps ) {
|
|
|
27
27
|
// Forward props + store from v2 implementation
|
|
28
28
|
const store = Ariakit.useSelectStore( {
|
|
29
29
|
async setValue( nextValue ) {
|
|
30
|
-
|
|
30
|
+
const nextOption = options.find(
|
|
31
|
+
( item ) => item.name === nextValue
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
if ( ! onChange || ! nextOption ) {
|
|
31
35
|
return;
|
|
32
36
|
}
|
|
33
37
|
|
|
@@ -42,18 +46,21 @@ function CustomSelectControl( props: LegacyCustomSelectProps ) {
|
|
|
42
46
|
),
|
|
43
47
|
inputValue: '',
|
|
44
48
|
isOpen: state.open,
|
|
45
|
-
selectedItem:
|
|
46
|
-
name: nextValue as string,
|
|
47
|
-
key: nextValue as string,
|
|
48
|
-
},
|
|
49
|
+
selectedItem: nextOption,
|
|
49
50
|
type: '',
|
|
50
51
|
};
|
|
51
52
|
onChange( changeObject );
|
|
52
53
|
},
|
|
54
|
+
value: value?.name,
|
|
55
|
+
// Setting the first option as a default value when no value is provided
|
|
56
|
+
// is already done natively by the underlying Ariakit component,
|
|
57
|
+
// but doing this explicitly avoids the `onChange` callback from firing
|
|
58
|
+
// on initial render, thus making this implementation closer to the v1.
|
|
59
|
+
defaultValue: options[ 0 ]?.name,
|
|
53
60
|
} );
|
|
54
61
|
|
|
55
62
|
const children = options.map(
|
|
56
|
-
( { name, key, __experimentalHint,
|
|
63
|
+
( { name, key, __experimentalHint, style, className } ) => {
|
|
57
64
|
const withHint = (
|
|
58
65
|
<Styled.WithHintWrapper>
|
|
59
66
|
<span>{ name }</span>
|
|
@@ -68,7 +75,8 @@ function CustomSelectControl( props: LegacyCustomSelectProps ) {
|
|
|
68
75
|
key={ key }
|
|
69
76
|
value={ name }
|
|
70
77
|
children={ __experimentalHint ? withHint : name }
|
|
71
|
-
{
|
|
78
|
+
style={ style }
|
|
79
|
+
className={ className }
|
|
72
80
|
/>
|
|
73
81
|
);
|
|
74
82
|
}
|
|
@@ -82,12 +90,12 @@ function CustomSelectControl( props: LegacyCustomSelectProps ) {
|
|
|
82
90
|
);
|
|
83
91
|
|
|
84
92
|
return (
|
|
85
|
-
|
|
93
|
+
<Styled.SelectedExperimentalHintWrapper>
|
|
86
94
|
{ currentValue }
|
|
87
95
|
<Styled.SelectedExperimentalHintItem className="components-custom-select-control__hint">
|
|
88
96
|
{ currentHint?.__experimentalHint }
|
|
89
97
|
</Styled.SelectedExperimentalHintItem>
|
|
90
|
-
|
|
98
|
+
</Styled.SelectedExperimentalHintWrapper>
|
|
91
99
|
);
|
|
92
100
|
};
|
|
93
101
|
|