@wordpress/components 21.1.2-next.4d3b314fd5.0 → 21.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 +14 -1
- package/build/border-box-control/utils.js +42 -2
- package/build/border-box-control/utils.js.map +1 -1
- package/build/combobox-control/index.js +0 -1
- package/build/combobox-control/index.js.map +1 -1
- package/build/custom-select-control/index.js +4 -2
- package/build/custom-select-control/index.js.map +1 -1
- package/build/font-size-picker/index.js +46 -55
- package/build/font-size-picker/index.js.map +1 -1
- package/build/font-size-picker/styles.js +73 -0
- package/build/font-size-picker/styles.js.map +1 -0
- package/build/font-size-picker/types.js +6 -0
- package/build/font-size-picker/types.js.map +1 -0
- package/build/font-size-picker/utils.js +17 -15
- package/build/font-size-picker/utils.js.map +1 -1
- package/build/form-token-field/token-input.js +20 -1
- package/build/form-token-field/token-input.js.map +1 -1
- package/build/index.js +6 -0
- package/build/index.js.map +1 -1
- package/build/navigator/navigator-screen/component.js +8 -1
- package/build/navigator/navigator-screen/component.js.map +1 -1
- package/build/resizable-box/resize-tooltip/utils.js +12 -14
- package/build/resizable-box/resize-tooltip/utils.js.map +1 -1
- package/build/sandbox/index.js +13 -8
- package/build/sandbox/index.js.map +1 -1
- package/build/sandbox/index.native.js +3 -1
- package/build/sandbox/index.native.js.map +1 -1
- package/build/search-control/index.native.js +6 -2
- package/build/search-control/index.native.js.map +1 -1
- package/build/slot-fill/bubbles-virtually/slot-fill-context.js +8 -2
- package/build/slot-fill/bubbles-virtually/slot-fill-context.js.map +1 -1
- package/build/slot-fill/bubbles-virtually/slot-fill-provider.js +31 -41
- package/build/slot-fill/bubbles-virtually/slot-fill-provider.js.map +1 -1
- package/build/slot-fill/bubbles-virtually/use-slot-fills.js +39 -0
- package/build/slot-fill/bubbles-virtually/use-slot-fills.js.map +1 -0
- package/build/slot-fill/bubbles-virtually/use-slot.js +13 -4
- package/build/slot-fill/bubbles-virtually/use-slot.js.map +1 -1
- package/build/slot-fill/index.js +8 -0
- package/build/slot-fill/index.js.map +1 -1
- package/build/toggle-group-control/toggle-group-control-option-base/styles.js +8 -8
- package/build/toggle-group-control/toggle-group-control-option-base/styles.js.map +1 -1
- package/build-module/border-box-control/utils.js +36 -1
- package/build-module/border-box-control/utils.js.map +1 -1
- package/build-module/combobox-control/index.js +0 -1
- package/build-module/combobox-control/index.js.map +1 -1
- package/build-module/custom-select-control/index.js +2 -1
- package/build-module/custom-select-control/index.js.map +1 -1
- package/build-module/font-size-picker/index.js +45 -53
- package/build-module/font-size-picker/index.js.map +1 -1
- package/build-module/font-size-picker/styles.js +62 -0
- package/build-module/font-size-picker/styles.js.map +1 -0
- package/build-module/font-size-picker/types.js +2 -0
- package/build-module/font-size-picker/types.js.map +1 -0
- package/build-module/font-size-picker/utils.js +21 -15
- package/build-module/font-size-picker/utils.js.map +1 -1
- package/build-module/form-token-field/token-input.js +21 -2
- package/build-module/form-token-field/token-input.js.map +1 -1
- package/build-module/index.js +1 -1
- package/build-module/index.js.map +1 -1
- package/build-module/navigator/navigator-screen/component.js +8 -1
- package/build-module/navigator/navigator-screen/component.js.map +1 -1
- package/build-module/resizable-box/resize-tooltip/utils.js +13 -15
- package/build-module/resizable-box/resize-tooltip/utils.js.map +1 -1
- package/build-module/sandbox/index.js +13 -8
- package/build-module/sandbox/index.js.map +1 -1
- package/build-module/sandbox/index.native.js +3 -1
- package/build-module/sandbox/index.native.js.map +1 -1
- package/build-module/search-control/index.native.js +6 -2
- package/build-module/search-control/index.native.js.map +1 -1
- package/build-module/slot-fill/bubbles-virtually/slot-fill-context.js +7 -2
- package/build-module/slot-fill/bubbles-virtually/slot-fill-context.js.map +1 -1
- package/build-module/slot-fill/bubbles-virtually/slot-fill-provider.js +30 -42
- package/build-module/slot-fill/bubbles-virtually/slot-fill-provider.js.map +1 -1
- package/build-module/slot-fill/bubbles-virtually/use-slot-fills.js +27 -0
- package/build-module/slot-fill/bubbles-virtually/use-slot-fills.js.map +1 -0
- package/build-module/slot-fill/bubbles-virtually/use-slot.js +13 -5
- package/build-module/slot-fill/bubbles-virtually/use-slot.js.map +1 -1
- package/build-module/slot-fill/index.js +1 -0
- package/build-module/slot-fill/index.js.map +1 -1
- package/build-module/toggle-group-control/toggle-group-control-option-base/styles.js +7 -7
- package/build-module/toggle-group-control/toggle-group-control-option-base/styles.js.map +1 -1
- package/build-style/style-rtl.css +1 -98
- package/build-style/style.css +1 -98
- package/build-types/border-box-control/utils.d.ts +1 -3
- package/build-types/border-box-control/utils.d.ts.map +1 -1
- package/build-types/custom-select-control/index.d.ts +13 -0
- package/build-types/custom-select-control/index.d.ts.map +1 -0
- package/build-types/custom-select-control/styles.d.ts +9 -0
- package/build-types/custom-select-control/styles.d.ts.map +1 -0
- package/build-types/font-size-picker/index.d.ts +5 -0
- package/build-types/font-size-picker/index.d.ts.map +1 -0
- package/build-types/font-size-picker/stories/e2e/index.d.ts +16 -0
- package/build-types/font-size-picker/stories/e2e/index.d.ts.map +1 -0
- package/build-types/font-size-picker/stories/index.d.ts +31 -0
- package/build-types/font-size-picker/stories/index.d.ts.map +1 -0
- package/build-types/font-size-picker/styles.d.ts +27 -0
- package/build-types/font-size-picker/styles.d.ts.map +1 -0
- package/build-types/font-size-picker/test/index.d.ts +2 -0
- package/build-types/font-size-picker/test/index.d.ts.map +1 -0
- package/build-types/font-size-picker/test/utils.d.ts +2 -0
- package/build-types/font-size-picker/test/utils.d.ts.map +1 -0
- package/build-types/font-size-picker/types.d.ts +93 -0
- package/build-types/font-size-picker/types.d.ts.map +1 -0
- package/build-types/font-size-picker/utils.d.ts +41 -0
- package/build-types/font-size-picker/utils.d.ts.map +1 -0
- package/build-types/form-token-field/token-input.d.ts.map +1 -1
- package/build-types/navigator/navigator-screen/component.d.ts.map +1 -1
- package/build-types/slot-fill/bubbles-virtually/slot-fill-context.d.ts +2 -2
- package/build-types/slot-fill/bubbles-virtually/slot-fill-context.d.ts.map +1 -1
- package/build-types/slot-fill/bubbles-virtually/slot-fill-provider.d.ts.map +1 -1
- package/build-types/slot-fill/bubbles-virtually/use-slot-fills.d.ts +2 -0
- package/build-types/slot-fill/bubbles-virtually/use-slot-fills.d.ts.map +1 -0
- package/build-types/slot-fill/bubbles-virtually/use-slot.d.ts.map +1 -1
- package/build-types/slot-fill/index.d.ts +1 -0
- package/build-types/slot-fill/index.d.ts.map +1 -1
- package/build-types/toggle-group-control/toggle-group-control-option-base/styles.d.ts +1 -1
- package/build-types/toggle-group-control/toggle-group-control-option-base/styles.d.ts.map +1 -1
- package/package.json +19 -18
- package/src/alignment-matrix-control/test/index.js +17 -62
- package/src/border-box-control/test/utils.js +48 -0
- package/src/border-box-control/utils.ts +44 -1
- package/src/combobox-control/index.js +0 -5
- package/src/custom-select-control/index.js +2 -1
- package/src/font-size-picker/{index.js → index.tsx} +113 -79
- package/src/font-size-picker/stories/e2e/{index.js → index.tsx} +13 -4
- package/src/font-size-picker/stories/{index.js → index.tsx} +42 -36
- package/src/font-size-picker/styles.ts +44 -0
- package/src/font-size-picker/test/{index.js → index.tsx} +1 -1
- package/src/font-size-picker/test/{utils.js → utils.ts} +6 -2
- package/src/font-size-picker/types.ts +98 -0
- package/src/font-size-picker/{utils.js → utils.ts} +51 -27
- package/src/form-token-field/test/index.tsx +22 -1
- package/src/form-token-field/token-input.tsx +25 -3
- package/src/index.js +1 -0
- package/src/navigator/navigator-screen/component.tsx +8 -1
- package/src/navigator/test/index.js +119 -54
- package/src/placeholder/style.scss +2 -2
- package/src/resizable-box/resize-tooltip/utils.ts +13 -13
- package/src/sandbox/index.js +13 -7
- package/src/sandbox/index.native.js +3 -0
- package/src/search-control/index.native.js +6 -0
- package/src/slot-fill/bubbles-virtually/slot-fill-context.js +6 -2
- package/src/slot-fill/bubbles-virtually/slot-fill-provider.js +51 -58
- package/src/slot-fill/bubbles-virtually/use-slot-fills.js +24 -0
- package/src/slot-fill/bubbles-virtually/use-slot.js +11 -6
- package/src/slot-fill/index.js +1 -0
- package/src/style.scss +0 -1
- package/src/toggle-group-control/test/__snapshots__/index.tsx.snap +1 -0
- package/src/toggle-group-control/toggle-group-control-option-base/styles.ts +9 -7
- package/tsconfig.json +0 -2
- package/tsconfig.tsbuildinfo +1 -1
- package/src/font-size-picker/style.scss +0 -78
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import type { ReactNode } from 'react';
|
|
5
|
+
|
|
6
|
+
export type FontSizePickerProps = {
|
|
7
|
+
/**
|
|
8
|
+
* If `true`, it will not be possible to choose a custom fontSize. The user
|
|
9
|
+
* will be forced to pick one of the pre-defined sizes passed in fontSizes.
|
|
10
|
+
*
|
|
11
|
+
* @default false
|
|
12
|
+
*/
|
|
13
|
+
disableCustomFontSizes?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* If no value exists, this prop defines the starting position for the font
|
|
16
|
+
* size picker slider. Only relevant if `withSlider` is `true`.
|
|
17
|
+
*/
|
|
18
|
+
fallbackFontSize?: number;
|
|
19
|
+
/**
|
|
20
|
+
* An array of font size objects. The object should contain properties size,
|
|
21
|
+
* name, and slug.
|
|
22
|
+
*/
|
|
23
|
+
fontSizes?: FontSize[];
|
|
24
|
+
/**
|
|
25
|
+
* A function that receives the new font size value.
|
|
26
|
+
* If onChange is called without any parameter, it should reset the value,
|
|
27
|
+
* attending to what reset means in that context, e.g., set the font size to
|
|
28
|
+
* undefined or set the font size a starting value.
|
|
29
|
+
*/
|
|
30
|
+
onChange?: ( value: number | string | undefined ) => void;
|
|
31
|
+
/**
|
|
32
|
+
* The current font size value.
|
|
33
|
+
*/
|
|
34
|
+
value?: number | string;
|
|
35
|
+
/**
|
|
36
|
+
* If `true`, the UI will contain a slider, instead of a numeric text input
|
|
37
|
+
* field. If `false`, no slider will be present.
|
|
38
|
+
*
|
|
39
|
+
* @default false
|
|
40
|
+
*/
|
|
41
|
+
withSlider?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* If `true`, a reset button will be displayed alongside the input field
|
|
44
|
+
* when a custom font size is active. Has no effect when
|
|
45
|
+
* `disableCustomFontSizes` or `withSlider` is `true`.
|
|
46
|
+
*
|
|
47
|
+
* @default true
|
|
48
|
+
*/
|
|
49
|
+
withReset?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Start opting into the new margin-free styles that will become the default
|
|
52
|
+
* in a future version, currently scheduled to be WordPress 6.4. (The prop
|
|
53
|
+
* can be safely removed once this happens.)
|
|
54
|
+
*
|
|
55
|
+
* @default false
|
|
56
|
+
*/
|
|
57
|
+
__nextHasNoMarginBottom?: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Size of the control.
|
|
60
|
+
*
|
|
61
|
+
* @default default
|
|
62
|
+
*/
|
|
63
|
+
size?: 'default' | '__unstable-large';
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export type FontSize = {
|
|
67
|
+
/**
|
|
68
|
+
* The property `size` contains a number with the font size value, in `px` or
|
|
69
|
+
* a string specifying the font size CSS property that should be used eg:
|
|
70
|
+
* "13px", "1em", or "clamp(12px, 5vw, 100px)".
|
|
71
|
+
*/
|
|
72
|
+
size: number | string;
|
|
73
|
+
/**
|
|
74
|
+
* The `name` property includes a label for that font size e.g.: `Small`.
|
|
75
|
+
*/
|
|
76
|
+
name?: string;
|
|
77
|
+
/**
|
|
78
|
+
* The `slug` property is a string with a unique identifier for the font
|
|
79
|
+
* size. Used for the class generation process.
|
|
80
|
+
*/
|
|
81
|
+
slug: string;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export type FontSizeOption = Omit< FontSize, 'size' > &
|
|
85
|
+
Partial< Pick< FontSize, 'size' > >;
|
|
86
|
+
|
|
87
|
+
export type FontSizeSelectOption = Pick< FontSizeOption, 'size' > & {
|
|
88
|
+
key: string;
|
|
89
|
+
name?: string;
|
|
90
|
+
__experimentalHint: ReactNode;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export type FontSizeToggleGroupOption = {
|
|
94
|
+
key: string;
|
|
95
|
+
value: number | string;
|
|
96
|
+
label: string;
|
|
97
|
+
name: string;
|
|
98
|
+
};
|
|
@@ -3,6 +3,17 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { __ } from '@wordpress/i18n';
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Internal dependencies
|
|
8
|
+
*/
|
|
9
|
+
import type {
|
|
10
|
+
FontSize,
|
|
11
|
+
FontSizeOption,
|
|
12
|
+
FontSizeSelectOption,
|
|
13
|
+
FontSizeToggleGroupOption,
|
|
14
|
+
FontSizePickerProps,
|
|
15
|
+
} from './types';
|
|
16
|
+
|
|
6
17
|
const DEFAULT_FONT_SIZE = 'default';
|
|
7
18
|
const DEFAULT_FONT_SIZE_OPTION = {
|
|
8
19
|
slug: DEFAULT_FONT_SIZE,
|
|
@@ -36,13 +47,18 @@ const FONT_SIZES_ALIASES = [
|
|
|
36
47
|
* Helper util to split a font size to its numeric value
|
|
37
48
|
* and its `unit`, if exists.
|
|
38
49
|
*
|
|
39
|
-
* @param
|
|
40
|
-
* @return
|
|
50
|
+
* @param size Font size.
|
|
51
|
+
* @return An array with the numeric value and the unit if exists.
|
|
41
52
|
*/
|
|
42
|
-
export function splitValueAndUnitFromSize(
|
|
43
|
-
|
|
53
|
+
export function splitValueAndUnitFromSize(
|
|
54
|
+
size: NonNullable< FontSizePickerProps[ 'value' ] >
|
|
55
|
+
) {
|
|
56
|
+
const [ numericValue, unit ] = `${ size }`.match( /[\d\.]+|\D+/g ) ?? [];
|
|
44
57
|
|
|
45
|
-
if (
|
|
58
|
+
if (
|
|
59
|
+
! isNaN( parseFloat( numericValue ) ) &&
|
|
60
|
+
isFinite( Number( numericValue ) )
|
|
61
|
+
) {
|
|
46
62
|
return [ numericValue, unit ];
|
|
47
63
|
}
|
|
48
64
|
|
|
@@ -53,28 +69,30 @@ export function splitValueAndUnitFromSize( size ) {
|
|
|
53
69
|
* Some themes use css vars for their font sizes, so until we
|
|
54
70
|
* have the way of calculating them don't display them.
|
|
55
71
|
*
|
|
56
|
-
* @param
|
|
57
|
-
* @return
|
|
72
|
+
* @param value The value that is checked.
|
|
73
|
+
* @return Whether the value is a simple css value.
|
|
58
74
|
*/
|
|
59
|
-
export function isSimpleCssValue(
|
|
75
|
+
export function isSimpleCssValue(
|
|
76
|
+
value: NonNullable< FontSizePickerProps[ 'value' ] >
|
|
77
|
+
) {
|
|
60
78
|
const sizeRegex = /^[\d\.]+(px|em|rem|vw|vh|%)?$/i;
|
|
61
|
-
return sizeRegex.test( value );
|
|
79
|
+
return sizeRegex.test( String( value ) );
|
|
62
80
|
}
|
|
63
81
|
|
|
64
82
|
/**
|
|
65
83
|
* Return font size options in the proper format depending
|
|
66
84
|
* on the currently used control (select, toggle group).
|
|
67
85
|
*
|
|
68
|
-
* @param
|
|
69
|
-
* @param
|
|
70
|
-
* @param
|
|
71
|
-
* @return
|
|
86
|
+
* @param useSelectControl Whether to use a select control.
|
|
87
|
+
* @param optionsArray Array of available font sizes objects.
|
|
88
|
+
* @param disableCustomFontSizes Flag that indicates if custom font sizes are disabled.
|
|
89
|
+
* @return Array of font sizes in proper format for the used control.
|
|
72
90
|
*/
|
|
73
91
|
export function getFontSizeOptions(
|
|
74
|
-
useSelectControl,
|
|
75
|
-
optionsArray,
|
|
76
|
-
disableCustomFontSizes
|
|
77
|
-
) {
|
|
92
|
+
useSelectControl: boolean,
|
|
93
|
+
optionsArray: FontSize[],
|
|
94
|
+
disableCustomFontSizes: boolean
|
|
95
|
+
): FontSizeSelectOption[] | FontSizeToggleGroupOption[] | null {
|
|
78
96
|
if ( disableCustomFontSizes && ! optionsArray.length ) {
|
|
79
97
|
return null;
|
|
80
98
|
}
|
|
@@ -83,8 +101,11 @@ export function getFontSizeOptions(
|
|
|
83
101
|
: getToggleGroupOptions( optionsArray );
|
|
84
102
|
}
|
|
85
103
|
|
|
86
|
-
function getSelectOptions(
|
|
87
|
-
|
|
104
|
+
function getSelectOptions(
|
|
105
|
+
optionsArray: FontSize[],
|
|
106
|
+
disableCustomFontSizes: boolean
|
|
107
|
+
): FontSizeSelectOption[] {
|
|
108
|
+
const options: FontSizeOption[] = [
|
|
88
109
|
DEFAULT_FONT_SIZE_OPTION,
|
|
89
110
|
...optionsArray,
|
|
90
111
|
...( disableCustomFontSizes ? [] : [ CUSTOM_FONT_SIZE_OPTION ] ),
|
|
@@ -94,21 +115,21 @@ function getSelectOptions( optionsArray, disableCustomFontSizes ) {
|
|
|
94
115
|
name,
|
|
95
116
|
size,
|
|
96
117
|
__experimentalHint:
|
|
97
|
-
size && isSimpleCssValue( size ) && parseFloat( size ),
|
|
118
|
+
size && isSimpleCssValue( size ) && parseFloat( String( size ) ),
|
|
98
119
|
} ) );
|
|
99
120
|
}
|
|
100
121
|
|
|
101
122
|
/**
|
|
102
123
|
* Build options for the toggle group options.
|
|
103
124
|
*
|
|
104
|
-
* @param
|
|
105
|
-
* @param
|
|
106
|
-
* @return
|
|
125
|
+
* @param optionsArray An array of font size options.
|
|
126
|
+
* @param labelAliases An array of alternative labels.
|
|
127
|
+
* @return Remapped optionsArray.
|
|
107
128
|
*/
|
|
108
129
|
export function getToggleGroupOptions(
|
|
109
|
-
optionsArray,
|
|
110
|
-
labelAliases = FONT_SIZES_ALIASES
|
|
111
|
-
) {
|
|
130
|
+
optionsArray: FontSize[],
|
|
131
|
+
labelAliases: string[] = FONT_SIZES_ALIASES
|
|
132
|
+
): FontSizeToggleGroupOption[] {
|
|
112
133
|
return optionsArray.map( ( { slug, size, name }, index ) => {
|
|
113
134
|
return {
|
|
114
135
|
key: slug,
|
|
@@ -119,7 +140,10 @@ export function getToggleGroupOptions(
|
|
|
119
140
|
} );
|
|
120
141
|
}
|
|
121
142
|
|
|
122
|
-
export function getSelectedOption(
|
|
143
|
+
export function getSelectedOption(
|
|
144
|
+
fontSizes: FontSize[],
|
|
145
|
+
value: FontSizePickerProps[ 'value' ]
|
|
146
|
+
): FontSizeOption {
|
|
123
147
|
if ( ! value ) {
|
|
124
148
|
return DEFAULT_FONT_SIZE_OPTION;
|
|
125
149
|
}
|
|
@@ -2057,7 +2057,12 @@ describe( 'FormTokenField', () => {
|
|
|
2057
2057
|
|
|
2058
2058
|
const suggestions = [ 'Pine', 'Pistachio', 'Sage' ];
|
|
2059
2059
|
|
|
2060
|
-
render(
|
|
2060
|
+
render(
|
|
2061
|
+
<>
|
|
2062
|
+
<FormTokenFieldWithState suggestions={ suggestions } />
|
|
2063
|
+
<button>Click me</button>
|
|
2064
|
+
</>
|
|
2065
|
+
);
|
|
2061
2066
|
|
|
2062
2067
|
// No suggestions visible
|
|
2063
2068
|
const input = screen.getByRole( 'combobox' );
|
|
@@ -2093,6 +2098,22 @@ describe( 'FormTokenField', () => {
|
|
|
2093
2098
|
pineSuggestion.id
|
|
2094
2099
|
);
|
|
2095
2100
|
|
|
2101
|
+
// Blur the input and make sure that the `aria-activedescendant`
|
|
2102
|
+
// is removed
|
|
2103
|
+
const button = screen.getByRole( 'button', { name: 'Click me' } );
|
|
2104
|
+
|
|
2105
|
+
await user.click( button );
|
|
2106
|
+
|
|
2107
|
+
expect( input ).not.toHaveAttribute( 'aria-activedescendant' );
|
|
2108
|
+
|
|
2109
|
+
// Focus the input again, `aria-activedescendant` should be added back.
|
|
2110
|
+
await user.click( input );
|
|
2111
|
+
|
|
2112
|
+
expect( input ).toHaveAttribute(
|
|
2113
|
+
'aria-activedescendant',
|
|
2114
|
+
pineSuggestion.id
|
|
2115
|
+
);
|
|
2116
|
+
|
|
2096
2117
|
// Add the suggestion, which hides the list
|
|
2097
2118
|
await user.keyboard( '[Enter]' );
|
|
2098
2119
|
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
* External dependencies
|
|
3
3
|
*/
|
|
4
4
|
import classnames from 'classnames';
|
|
5
|
-
import type { ChangeEvent, ForwardedRef } from 'react';
|
|
5
|
+
import type { ChangeEvent, ForwardedRef, FocusEventHandler } from 'react';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* WordPress dependencies
|
|
9
9
|
*/
|
|
10
|
-
import { forwardRef } from '@wordpress/element';
|
|
10
|
+
import { forwardRef, useState } from '@wordpress/element';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Internal dependencies
|
|
@@ -26,9 +26,13 @@ export function UnForwardedTokenInput(
|
|
|
26
26
|
selectedSuggestionIndex,
|
|
27
27
|
className,
|
|
28
28
|
onChange,
|
|
29
|
+
onFocus,
|
|
30
|
+
onBlur,
|
|
29
31
|
...restProps
|
|
30
32
|
} = props;
|
|
31
33
|
|
|
34
|
+
const [ hasFocus, setHasFocus ] = useState( false );
|
|
35
|
+
|
|
32
36
|
const size = value ? value.length + 1 : 0;
|
|
33
37
|
|
|
34
38
|
const onChangeHandler = ( event: ChangeEvent< HTMLInputElement > ) => {
|
|
@@ -39,6 +43,18 @@ export function UnForwardedTokenInput(
|
|
|
39
43
|
}
|
|
40
44
|
};
|
|
41
45
|
|
|
46
|
+
const onFocusHandler: FocusEventHandler< HTMLInputElement > = ( e ) => {
|
|
47
|
+
setHasFocus( true );
|
|
48
|
+
onFocus?.( e );
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const onBlurHandler: React.FocusEventHandler< HTMLInputElement > = (
|
|
52
|
+
e
|
|
53
|
+
) => {
|
|
54
|
+
setHasFocus( false );
|
|
55
|
+
onBlur?.( e );
|
|
56
|
+
};
|
|
57
|
+
|
|
42
58
|
return (
|
|
43
59
|
<input
|
|
44
60
|
ref={ ref }
|
|
@@ -47,6 +63,8 @@ export function UnForwardedTokenInput(
|
|
|
47
63
|
{ ...restProps }
|
|
48
64
|
value={ value || '' }
|
|
49
65
|
onChange={ onChangeHandler }
|
|
66
|
+
onFocus={ onFocusHandler }
|
|
67
|
+
onBlur={ onBlurHandler }
|
|
50
68
|
size={ size }
|
|
51
69
|
className={ classnames(
|
|
52
70
|
className,
|
|
@@ -62,7 +80,11 @@ export function UnForwardedTokenInput(
|
|
|
62
80
|
: undefined
|
|
63
81
|
}
|
|
64
82
|
aria-activedescendant={
|
|
65
|
-
|
|
83
|
+
// Only add the `aria-activedescendant` attribute when:
|
|
84
|
+
// - the user is actively interacting with the input (`hasFocus`)
|
|
85
|
+
// - there is a selected suggestion (`selectedSuggestionIndex !== -1`)
|
|
86
|
+
// - the list of suggestions are rendered in the DOM (`isExpanded`)
|
|
87
|
+
hasFocus && selectedSuggestionIndex !== -1 && isExpanded
|
|
66
88
|
? `components-form-token-suggestions-${ instanceId }-${ selectedSuggestionIndex }`
|
|
67
89
|
: undefined
|
|
68
90
|
}
|
package/src/index.js
CHANGED
|
@@ -188,6 +188,7 @@ export {
|
|
|
188
188
|
Fill,
|
|
189
189
|
Provider as SlotFillProvider,
|
|
190
190
|
useSlot as __experimentalUseSlot,
|
|
191
|
+
useSlotFills as __experimentalUseSlotFills,
|
|
191
192
|
} from './slot-fill';
|
|
192
193
|
export { default as __experimentalStyleProvider } from './style-provider';
|
|
193
194
|
export { ZStack as __experimentalZStack } from './z-stack';
|
|
@@ -83,6 +83,14 @@ function NavigatorScreen( props: Props, forwardedRef: ForwardedRef< any > ) {
|
|
|
83
83
|
return;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
const activeElement = wrapperRef.current.ownerDocument.activeElement;
|
|
87
|
+
|
|
88
|
+
// If an element is already focused within the wrapper do not focus the
|
|
89
|
+
// element. This prevents inputs or buttons from losing focus unecessarily.
|
|
90
|
+
if ( wrapperRef.current.contains( activeElement ) ) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
86
94
|
let elementToFocus: HTMLElement | null = null;
|
|
87
95
|
|
|
88
96
|
// When navigating back, if a selector is provided, use it to look for the
|
|
@@ -99,7 +107,6 @@ function NavigatorScreen( props: Props, forwardedRef: ForwardedRef< any > ) {
|
|
|
99
107
|
const firstTabbable = (
|
|
100
108
|
focus.tabbable.find( wrapperRef.current ) as HTMLElement[]
|
|
101
109
|
)[ 0 ];
|
|
102
|
-
|
|
103
110
|
elementToFocus = firstTabbable ?? wrapperRef.current;
|
|
104
111
|
}
|
|
105
112
|
|
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
* External dependencies
|
|
3
3
|
*/
|
|
4
4
|
import { render, screen, fireEvent } from '@testing-library/react';
|
|
5
|
+
import userEvent from '@testing-library/user-event';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* WordPress dependencies
|
|
9
|
+
*/
|
|
10
|
+
import { useState } from '@wordpress/element';
|
|
5
11
|
|
|
6
12
|
/**
|
|
7
13
|
* Internal dependencies
|
|
@@ -86,60 +92,74 @@ function CustomNavigatorBackButton( { onClick, ...props } ) {
|
|
|
86
92
|
const MyNavigation = ( {
|
|
87
93
|
initialPath = PATHS.HOME,
|
|
88
94
|
onNavigatorButtonClick,
|
|
89
|
-
} ) =>
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
<
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
<
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
</
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
95
|
+
} ) => {
|
|
96
|
+
const [ inputValue, setInputValue ] = useState( '' );
|
|
97
|
+
return (
|
|
98
|
+
<NavigatorProvider initialPath={ initialPath }>
|
|
99
|
+
<NavigatorScreen path={ PATHS.HOME }>
|
|
100
|
+
<p>This is the home screen.</p>
|
|
101
|
+
<CustomNavigatorButton
|
|
102
|
+
path={ PATHS.NOT_FOUND }
|
|
103
|
+
onClick={ onNavigatorButtonClick }
|
|
104
|
+
>
|
|
105
|
+
Navigate to non-existing screen.
|
|
106
|
+
</CustomNavigatorButton>
|
|
107
|
+
<CustomNavigatorButton
|
|
108
|
+
path={ PATHS.CHILD }
|
|
109
|
+
onClick={ onNavigatorButtonClick }
|
|
110
|
+
>
|
|
111
|
+
Navigate to child screen.
|
|
112
|
+
</CustomNavigatorButton>
|
|
113
|
+
<CustomNavigatorButton
|
|
114
|
+
path={ PATHS.INVALID_HTML_ATTRIBUTE }
|
|
115
|
+
onClick={ onNavigatorButtonClick }
|
|
116
|
+
>
|
|
117
|
+
Navigate to screen with an invalid HTML value as a path.
|
|
118
|
+
</CustomNavigatorButton>
|
|
119
|
+
</NavigatorScreen>
|
|
120
|
+
|
|
121
|
+
<NavigatorScreen path={ PATHS.CHILD }>
|
|
122
|
+
<p>This is the child screen.</p>
|
|
123
|
+
<CustomNavigatorButtonWithFocusRestoration
|
|
124
|
+
path={ PATHS.NESTED }
|
|
125
|
+
onClick={ onNavigatorButtonClick }
|
|
126
|
+
>
|
|
127
|
+
Navigate to nested screen.
|
|
128
|
+
</CustomNavigatorButtonWithFocusRestoration>
|
|
129
|
+
<CustomNavigatorBackButton onClick={ onNavigatorButtonClick }>
|
|
130
|
+
Go back
|
|
131
|
+
</CustomNavigatorBackButton>
|
|
132
|
+
|
|
133
|
+
<label htmlFor="test-input">This is a test input</label>
|
|
134
|
+
<input
|
|
135
|
+
name="test-input"
|
|
136
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
137
|
+
id="test-input"
|
|
138
|
+
onChange={ ( e ) => {
|
|
139
|
+
setInputValue( e.target.value );
|
|
140
|
+
} }
|
|
141
|
+
value={ inputValue }
|
|
142
|
+
/>
|
|
143
|
+
</NavigatorScreen>
|
|
144
|
+
|
|
145
|
+
<NavigatorScreen path={ PATHS.NESTED }>
|
|
146
|
+
<p>This is the nested screen.</p>
|
|
147
|
+
<CustomNavigatorBackButton onClick={ onNavigatorButtonClick }>
|
|
148
|
+
Go back
|
|
149
|
+
</CustomNavigatorBackButton>
|
|
150
|
+
</NavigatorScreen>
|
|
151
|
+
|
|
152
|
+
<NavigatorScreen path={ PATHS.INVALID_HTML_ATTRIBUTE }>
|
|
153
|
+
<p>This is the screen with an invalid HTML value as a path.</p>
|
|
154
|
+
<CustomNavigatorBackButton onClick={ onNavigatorButtonClick }>
|
|
155
|
+
Go back
|
|
156
|
+
</CustomNavigatorBackButton>
|
|
157
|
+
</NavigatorScreen>
|
|
158
|
+
|
|
159
|
+
{ /* A `NavigatorScreen` with `path={ PATHS.NOT_FOUND }` is purposefully not included. */ }
|
|
160
|
+
</NavigatorProvider>
|
|
161
|
+
);
|
|
162
|
+
};
|
|
143
163
|
|
|
144
164
|
const getNavigationScreenByText = ( text, { throwIfNotFound = true } = {} ) => {
|
|
145
165
|
const fnName = throwIfNotFound ? 'getByText' : 'queryByText';
|
|
@@ -194,6 +214,28 @@ const getBackButton = ( { throwIfNotFound } = {} ) =>
|
|
|
194
214
|
} );
|
|
195
215
|
|
|
196
216
|
describe( 'Navigator', () => {
|
|
217
|
+
const originalGetClientRects = window.Element.prototype.getClientRects;
|
|
218
|
+
|
|
219
|
+
// `getClientRects` needs to be mocked so that `isVisible` from the `@wordpress/dom`
|
|
220
|
+
// `focusable` module can pass, in a JSDOM env where the DOM elements have no width/height.
|
|
221
|
+
const mockedGetClientRects = jest.fn( () => [
|
|
222
|
+
{
|
|
223
|
+
x: 0,
|
|
224
|
+
y: 0,
|
|
225
|
+
width: 100,
|
|
226
|
+
height: 100,
|
|
227
|
+
},
|
|
228
|
+
] );
|
|
229
|
+
|
|
230
|
+
beforeAll( () => {
|
|
231
|
+
window.Element.prototype.getClientRects =
|
|
232
|
+
jest.fn( mockedGetClientRects );
|
|
233
|
+
} );
|
|
234
|
+
|
|
235
|
+
afterAll( () => {
|
|
236
|
+
window.Element.prototype.getClientRects = originalGetClientRects;
|
|
237
|
+
} );
|
|
238
|
+
|
|
197
239
|
it( 'should render', () => {
|
|
198
240
|
render( <MyNavigation /> );
|
|
199
241
|
|
|
@@ -404,4 +446,27 @@ describe( 'Navigator', () => {
|
|
|
404
446
|
expect( getHomeScreen() ).toBeInTheDocument();
|
|
405
447
|
expect( getToInvalidHTMLPathScreenButton() ).toHaveFocus();
|
|
406
448
|
} );
|
|
449
|
+
|
|
450
|
+
it( 'should keep focus on the element that is being interacted with, while re-rendering', async () => {
|
|
451
|
+
const user = userEvent.setup( {
|
|
452
|
+
advanceTimers: jest.advanceTimersByTime,
|
|
453
|
+
} );
|
|
454
|
+
|
|
455
|
+
render( <MyNavigation /> );
|
|
456
|
+
|
|
457
|
+
expect( getHomeScreen() ).toBeInTheDocument();
|
|
458
|
+
expect( getToChildScreenButton() ).toBeInTheDocument();
|
|
459
|
+
|
|
460
|
+
// Navigate to child screen.
|
|
461
|
+
await user.click( getToChildScreenButton() );
|
|
462
|
+
|
|
463
|
+
expect( getChildScreen() ).toBeInTheDocument();
|
|
464
|
+
expect( getBackButton() ).toBeInTheDocument();
|
|
465
|
+
expect( getToNestedScreenButton() ).toHaveFocus();
|
|
466
|
+
|
|
467
|
+
// Interact with the input, the focus should stay on the input element.
|
|
468
|
+
const input = screen.getByLabelText( 'This is a test input' );
|
|
469
|
+
await user.type( input, 'd' );
|
|
470
|
+
expect( input ).toHaveFocus();
|
|
471
|
+
} );
|
|
407
472
|
} );
|
|
@@ -178,9 +178,9 @@
|
|
|
178
178
|
min-width: 100px;
|
|
179
179
|
|
|
180
180
|
// Blur the background so layered dashed placeholders are still visually separate.
|
|
181
|
-
//
|
|
181
|
+
// Make the background transparent to not interfere with the background overlay in placeholder-style() pseudo element
|
|
182
182
|
backdrop-filter: blur(100px);
|
|
183
|
-
background-color:
|
|
183
|
+
background-color: transparent;
|
|
184
184
|
|
|
185
185
|
// Invert the colors in themes deemed dark.
|
|
186
186
|
.is-dark-theme & {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* WordPress dependencies
|
|
3
3
|
*/
|
|
4
|
-
import { useEffect, useRef, useState } from '@wordpress/element';
|
|
4
|
+
import { useCallback, useEffect, useRef, useState } from '@wordpress/element';
|
|
5
5
|
import { useResizeObserver } from '@wordpress/compose';
|
|
6
6
|
|
|
7
7
|
const noop = () => {};
|
|
@@ -84,23 +84,23 @@ export function useResizeLabel( {
|
|
|
84
84
|
*/
|
|
85
85
|
const moveTimeoutRef = useRef< number >();
|
|
86
86
|
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
87
|
+
const debounceUnsetMoveXY = useCallback( () => {
|
|
88
|
+
const unsetMoveXY = () => {
|
|
89
|
+
/*
|
|
90
|
+
* If axis is controlled, we will avoid resetting the moveX and moveY values.
|
|
91
|
+
* This will allow for the preferred axis values to persist in the label.
|
|
92
|
+
*/
|
|
93
|
+
if ( isAxisControlled ) return;
|
|
94
|
+
setMoveX( false );
|
|
95
|
+
setMoveY( false );
|
|
96
|
+
};
|
|
96
97
|
|
|
97
|
-
const debounceUnsetMoveXY = () => {
|
|
98
98
|
if ( moveTimeoutRef.current ) {
|
|
99
99
|
window.clearTimeout( moveTimeoutRef.current );
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
moveTimeoutRef.current = window.setTimeout( unsetMoveXY, fadeTimeout );
|
|
103
|
-
};
|
|
103
|
+
}, [ fadeTimeout, isAxisControlled ] );
|
|
104
104
|
|
|
105
105
|
useEffect( () => {
|
|
106
106
|
/*
|
|
@@ -143,7 +143,7 @@ export function useResizeLabel( {
|
|
|
143
143
|
|
|
144
144
|
onResize( { width, height } );
|
|
145
145
|
debounceUnsetMoveXY();
|
|
146
|
-
}, [ width, height ] );
|
|
146
|
+
}, [ width, height, onResize, debounceUnsetMoveXY ] );
|
|
147
147
|
|
|
148
148
|
const label = getSizeLabel( {
|
|
149
149
|
axis,
|