@wordpress/components 32.5.0 → 32.5.2-next.v.202604091042.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/AGENTS.md +2 -2
- package/CHANGELOG.md +20 -0
- package/README.md +18 -4
- package/build/autocomplete/autocompleter-ui.cjs +75 -79
- package/build/autocomplete/autocompleter-ui.cjs.map +2 -2
- package/build/autocomplete/get-autocomplete-match.cjs +91 -0
- package/build/autocomplete/get-autocomplete-match.cjs.map +7 -0
- package/build/autocomplete/index.cjs +104 -107
- package/build/autocomplete/index.cjs.map +3 -3
- package/build/box-control/index.cjs +0 -8
- package/build/box-control/index.cjs.map +2 -2
- package/build/box-control/utils.cjs +1 -10
- package/build/box-control/utils.cjs.map +2 -2
- package/build/calendar/utils/use-localization-props.cjs +3 -2
- package/build/calendar/utils/use-localization-props.cjs.map +2 -2
- package/build/custom-select-control/index.cjs.map +3 -3
- package/build/custom-select-control-v2/custom-select.cjs +2 -2
- package/build/custom-select-control-v2/custom-select.cjs.map +2 -2
- package/build/custom-select-control-v2/index.cjs.map +3 -3
- package/build/sandbox/index.cjs +2 -2
- package/build/sandbox/index.cjs.map +2 -2
- package/build/validated-form-controls/control-with-error.cjs +12 -8
- package/build/validated-form-controls/control-with-error.cjs.map +2 -2
- package/build-module/autocomplete/autocompleter-ui.mjs +74 -78
- package/build-module/autocomplete/autocompleter-ui.mjs.map +2 -2
- package/build-module/autocomplete/get-autocomplete-match.mjs +56 -0
- package/build-module/autocomplete/get-autocomplete-match.mjs.map +7 -0
- package/build-module/autocomplete/index.mjs +103 -107
- package/build-module/autocomplete/index.mjs.map +3 -3
- package/build-module/box-control/index.mjs +1 -9
- package/build-module/box-control/index.mjs.map +2 -2
- package/build-module/box-control/utils.mjs +1 -9
- package/build-module/box-control/utils.mjs.map +2 -2
- package/build-module/calendar/utils/use-localization-props.mjs +3 -2
- package/build-module/calendar/utils/use-localization-props.mjs.map +2 -2
- package/build-module/custom-select-control/index.mjs +2 -2
- package/build-module/custom-select-control/index.mjs.map +2 -2
- package/build-module/custom-select-control-v2/custom-select.mjs +2 -2
- package/build-module/custom-select-control-v2/custom-select.mjs.map +2 -2
- package/build-module/custom-select-control-v2/index.mjs +2 -2
- package/build-module/custom-select-control-v2/index.mjs.map +2 -2
- package/build-module/sandbox/index.mjs +2 -2
- package/build-module/sandbox/index.mjs.map +2 -2
- package/build-module/validated-form-controls/control-with-error.mjs +12 -8
- package/build-module/validated-form-controls/control-with-error.mjs.map +2 -2
- package/build-style/style-rtl.css +0 -3
- package/build-style/style.css +0 -3
- package/build-types/autocomplete/autocompleter-ui.d.ts +2 -2
- package/build-types/autocomplete/autocompleter-ui.d.ts.map +1 -1
- package/build-types/autocomplete/get-autocomplete-match.d.ts +11 -0
- package/build-types/autocomplete/get-autocomplete-match.d.ts.map +1 -0
- package/build-types/autocomplete/index.d.ts +8 -0
- package/build-types/autocomplete/index.d.ts.map +1 -1
- package/build-types/autocomplete/test/get-autocomplete-match.d.ts +2 -0
- package/build-types/autocomplete/test/get-autocomplete-match.d.ts.map +1 -0
- package/build-types/autocomplete/types.d.ts +23 -9
- package/build-types/autocomplete/types.d.ts.map +1 -1
- package/build-types/box-control/index.d.ts.map +1 -1
- package/build-types/box-control/utils.d.ts +7 -16
- package/build-types/box-control/utils.d.ts.map +1 -1
- package/build-types/button/stories/index.story.d.ts +0 -1
- package/build-types/button/stories/index.story.d.ts.map +1 -1
- package/build-types/calendar/utils/use-localization-props.d.ts +3 -3
- package/build-types/calendar/utils/use-localization-props.d.ts.map +1 -1
- package/build-types/custom-gradient-picker/constants.d.ts +2 -2
- package/build-types/custom-select-control-v2/custom-select.d.ts +3 -3
- package/build-types/custom-select-control-v2/custom-select.d.ts.map +1 -1
- package/build-types/custom-select-control-v2/types.d.ts +1 -1
- package/build-types/custom-select-control-v2/types.d.ts.map +1 -1
- package/build-types/font-size-picker/constants.d.ts +2 -2
- package/build-types/font-size-picker/constants.d.ts.map +1 -1
- package/build-types/palette-edit/index.d.ts +1 -1
- package/build-types/validated-form-controls/control-with-error.d.ts.map +1 -1
- package/package.json +21 -21
- package/src/alignment-matrix-control/README.md +1 -1
- package/src/angle-picker-control/style.module.scss +1 -0
- package/src/autocomplete/README.md +2 -2
- package/src/autocomplete/autocompleter-ui.native.js +166 -173
- package/src/autocomplete/autocompleter-ui.tsx +114 -116
- package/src/autocomplete/get-autocomplete-match.ts +115 -0
- package/src/autocomplete/index.tsx +129 -208
- package/src/autocomplete/test/get-autocomplete-match.ts +338 -0
- package/src/autocomplete/test/index.tsx +112 -4
- package/src/autocomplete/types.ts +17 -10
- package/src/box-control/index.tsx +1 -19
- package/src/box-control/utils.ts +1 -19
- package/src/button/README.md +1 -1
- package/src/button/stories/index.story.tsx +0 -1
- package/src/button/style.scss +0 -6
- package/src/calendar/utils/use-localization-props.ts +3 -4
- package/src/custom-select-control/index.tsx +3 -3
- package/src/custom-select-control-v2/custom-select.tsx +4 -4
- package/src/custom-select-control-v2/index.tsx +2 -2
- package/src/custom-select-control-v2/types.ts +1 -1
- package/src/divider/README.md +5 -6
- package/src/flex/stories/index.story.tsx +1 -1
- package/src/form-file-upload/README.md +3 -3
- package/src/gradient-picker/README.md +2 -2
- package/src/h-stack/README.md +10 -15
- package/src/h-stack/stories/index.story.tsx +2 -2
- package/src/heading/stories/index.story.tsx +1 -1
- package/src/higher-order/with-focus-outside/index.native.js +21 -20
- package/src/icon/README.md +1 -1
- package/src/menu/README.md +2 -2
- package/src/mobile/utils/get-px-from-css-unit.native.js +1 -1
- package/src/sandbox/index.native.js +2 -2
- package/src/sandbox/index.tsx +2 -2
- package/src/tabs/README.md +6 -6
- package/src/text/stories/index.story.tsx +1 -1
- package/src/toolbar/toolbar-button/toolbar-button-container.native.js +3 -1
- package/src/tree-select/README.md +1 -1
- package/src/v-stack/README.md +10 -15
- package/src/v-stack/stories/index.story.tsx +2 -2
- package/src/validated-form-controls/control-with-error.tsx +17 -12
- package/src/validated-form-controls/test/control-with-error.tsx +28 -1
- package/src/view/README.md +2 -5
- package/src/button/stories/style.css +0 -8
|
@@ -34,185 +34,178 @@ import { __unstableAutocompletionItemsFill as AutocompletionItemsFill } from '..
|
|
|
34
34
|
|
|
35
35
|
const { compose: stylesCompose } = StyleSheet;
|
|
36
36
|
|
|
37
|
-
export function
|
|
37
|
+
export function AutocompleterUI( {
|
|
38
|
+
autocompleter,
|
|
39
|
+
filterValue,
|
|
40
|
+
selectedIndex,
|
|
41
|
+
onChangeOptions,
|
|
42
|
+
onSelect,
|
|
43
|
+
reset,
|
|
44
|
+
} ) {
|
|
45
|
+
// The useItems hook is derived from the autocompleter prop. This is safe
|
|
46
|
+
// because the parent renders this component with a key that triggers
|
|
47
|
+
// remount when the completer changes.
|
|
38
48
|
const useItems = autocompleter.useItems
|
|
39
49
|
? autocompleter.useItems
|
|
40
50
|
: getDefaultUseItems( autocompleter );
|
|
51
|
+
const [ items ] = useItems( filterValue );
|
|
52
|
+
const filteredItems = items.filter( ( item ) => ! item.isDisabled );
|
|
53
|
+
const scrollViewRef = useRef();
|
|
54
|
+
const animationValue = useRef( new Animated.Value( 0 ) ).current;
|
|
55
|
+
const [ isVisible, setIsVisible ] = useState( false );
|
|
56
|
+
|
|
57
|
+
useEffect( () => {
|
|
58
|
+
if ( ! isVisible && filterValue.length > 0 ) {
|
|
59
|
+
setIsVisible( true );
|
|
60
|
+
}
|
|
61
|
+
}, [ isVisible, filterValue ] );
|
|
41
62
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const [ items ] = useItems( filterValue );
|
|
51
|
-
const filteredItems = items.filter( ( item ) => ! item.isDisabled );
|
|
52
|
-
const scrollViewRef = useRef();
|
|
53
|
-
const animationValue = useRef( new Animated.Value( 0 ) ).current;
|
|
54
|
-
const [ isVisible, setIsVisible ] = useState( false );
|
|
55
|
-
const { text } = value;
|
|
56
|
-
|
|
57
|
-
useEffect( () => {
|
|
58
|
-
if ( ! isVisible && text.length > 0 ) {
|
|
59
|
-
setIsVisible( true );
|
|
60
|
-
}
|
|
61
|
-
}, [ isVisible, text ] );
|
|
62
|
-
|
|
63
|
-
useLayoutEffect( () => {
|
|
64
|
-
onChangeOptions( items );
|
|
65
|
-
scrollViewRef.current?.scrollTo( { x: 0, animated: false } );
|
|
66
|
-
|
|
67
|
-
if ( isVisible && text.length > 0 ) {
|
|
68
|
-
startAnimation( true );
|
|
69
|
-
} else if ( isVisible && text.length === 0 ) {
|
|
70
|
-
startAnimation( false );
|
|
71
|
-
}
|
|
72
|
-
// We want to avoid introducing unexpected side effects.
|
|
73
|
-
// See https://github.com/WordPress/gutenberg/pull/41820
|
|
74
|
-
}, [ items, isVisible, text ] );
|
|
75
|
-
|
|
76
|
-
const activeItemStyles = usePreferredColorSchemeStyle(
|
|
77
|
-
styles[ 'components-autocomplete__item-active' ],
|
|
78
|
-
styles[ 'components-autocomplete__item-active-dark' ]
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
const iconStyles = usePreferredColorSchemeStyle(
|
|
82
|
-
styles[ 'components-autocomplete__icon' ],
|
|
83
|
-
styles[ 'components-autocomplete__icon-active-dark' ]
|
|
84
|
-
);
|
|
85
|
-
|
|
86
|
-
const activeIconStyles = usePreferredColorSchemeStyle(
|
|
87
|
-
styles[ 'components-autocomplete__icon-active ' ],
|
|
88
|
-
styles[ 'components-autocomplete__icon-active-dark' ]
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
const textStyles = usePreferredColorSchemeStyle(
|
|
92
|
-
styles[ 'components-autocomplete__text' ],
|
|
93
|
-
styles[ 'components-autocomplete__text-dark' ]
|
|
94
|
-
);
|
|
95
|
-
|
|
96
|
-
const activeTextStyles = usePreferredColorSchemeStyle(
|
|
97
|
-
styles[ 'components-autocomplete__text-active' ],
|
|
98
|
-
styles[ 'components-autocomplete__text-active-dark' ]
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
const startAnimation = useCallback(
|
|
102
|
-
( show ) => {
|
|
103
|
-
Animated.timing( animationValue, {
|
|
104
|
-
toValue: show ? 1 : 0,
|
|
105
|
-
duration: show ? 200 : 100,
|
|
106
|
-
useNativeDriver: true,
|
|
107
|
-
} ).start( ( { finished } ) => {
|
|
108
|
-
if ( finished && ! show && isVisible ) {
|
|
109
|
-
setIsVisible( false );
|
|
110
|
-
reset();
|
|
111
|
-
}
|
|
112
|
-
} );
|
|
113
|
-
},
|
|
114
|
-
// We want to avoid introducing unexpected side effects.
|
|
115
|
-
// See https://github.com/WordPress/gutenberg/pull/41820
|
|
116
|
-
[ isVisible ]
|
|
117
|
-
);
|
|
118
|
-
|
|
119
|
-
const contentStyles = {
|
|
120
|
-
transform: [
|
|
121
|
-
{
|
|
122
|
-
translateY: animationValue.interpolate( {
|
|
123
|
-
inputRange: [ 0, 1 ],
|
|
124
|
-
outputRange: [
|
|
125
|
-
styles[ 'components-autocomplete' ].height,
|
|
126
|
-
0,
|
|
127
|
-
],
|
|
128
|
-
} ),
|
|
129
|
-
},
|
|
130
|
-
],
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
if ( ! filteredItems.length > 0 || ! isVisible ) {
|
|
134
|
-
return null;
|
|
63
|
+
useLayoutEffect( () => {
|
|
64
|
+
onChangeOptions( items );
|
|
65
|
+
scrollViewRef.current?.scrollTo( { x: 0, animated: false } );
|
|
66
|
+
|
|
67
|
+
if ( isVisible && filterValue.length > 0 ) {
|
|
68
|
+
startAnimation( true );
|
|
69
|
+
} else if ( isVisible && filterValue.length === 0 ) {
|
|
70
|
+
startAnimation( false );
|
|
135
71
|
}
|
|
72
|
+
// We want to avoid introducing unexpected side effects.
|
|
73
|
+
// See https://github.com/WordPress/gutenberg/pull/41820
|
|
74
|
+
}, [ items, isVisible, filterValue ] );
|
|
75
|
+
|
|
76
|
+
const activeItemStyles = usePreferredColorSchemeStyle(
|
|
77
|
+
styles[ 'components-autocomplete__item-active' ],
|
|
78
|
+
styles[ 'components-autocomplete__item-active-dark' ]
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const iconStyles = usePreferredColorSchemeStyle(
|
|
82
|
+
styles[ 'components-autocomplete__icon' ],
|
|
83
|
+
styles[ 'components-autocomplete__icon-active-dark' ]
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const activeIconStyles = usePreferredColorSchemeStyle(
|
|
87
|
+
styles[ 'components-autocomplete__icon-active ' ],
|
|
88
|
+
styles[ 'components-autocomplete__icon-active-dark' ]
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const textStyles = usePreferredColorSchemeStyle(
|
|
92
|
+
styles[ 'components-autocomplete__text' ],
|
|
93
|
+
styles[ 'components-autocomplete__text-dark' ]
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
const activeTextStyles = usePreferredColorSchemeStyle(
|
|
97
|
+
styles[ 'components-autocomplete__text-active' ],
|
|
98
|
+
styles[ 'components-autocomplete__text-active-dark' ]
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const startAnimation = useCallback(
|
|
102
|
+
( show ) => {
|
|
103
|
+
Animated.timing( animationValue, {
|
|
104
|
+
toValue: show ? 1 : 0,
|
|
105
|
+
duration: show ? 200 : 100,
|
|
106
|
+
useNativeDriver: true,
|
|
107
|
+
} ).start( ( { finished } ) => {
|
|
108
|
+
if ( finished && ! show && isVisible ) {
|
|
109
|
+
setIsVisible( false );
|
|
110
|
+
reset();
|
|
111
|
+
}
|
|
112
|
+
} );
|
|
113
|
+
},
|
|
114
|
+
// We want to avoid introducing unexpected side effects.
|
|
115
|
+
// See https://github.com/WordPress/gutenberg/pull/41820
|
|
116
|
+
[ isVisible ]
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
const contentStyles = {
|
|
120
|
+
transform: [
|
|
121
|
+
{
|
|
122
|
+
translateY: animationValue.interpolate( {
|
|
123
|
+
inputRange: [ 0, 1 ],
|
|
124
|
+
outputRange: [
|
|
125
|
+
styles[ 'components-autocomplete' ].height,
|
|
126
|
+
0,
|
|
127
|
+
],
|
|
128
|
+
} ),
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
};
|
|
136
132
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
<View style={ styles[ 'components-autocomplete' ] }>
|
|
140
|
-
<Animated.View style={ contentStyles }>
|
|
141
|
-
<BackgroundView>
|
|
142
|
-
<ScrollView
|
|
143
|
-
testID="autocompleter"
|
|
144
|
-
ref={ scrollViewRef }
|
|
145
|
-
horizontal
|
|
146
|
-
contentContainerStyle={
|
|
147
|
-
styles[ 'components-autocomplete__content' ]
|
|
148
|
-
}
|
|
149
|
-
showsHorizontalScrollIndicator={ false }
|
|
150
|
-
keyboardShouldPersistTaps="always"
|
|
151
|
-
accessibilityLabel={
|
|
152
|
-
// translators: Slash inserter autocomplete results
|
|
153
|
-
__( 'Slash inserter results' )
|
|
154
|
-
}
|
|
155
|
-
>
|
|
156
|
-
{ filteredItems.map( ( option, index ) => {
|
|
157
|
-
const isActive = index === selectedIndex;
|
|
158
|
-
const itemStyle = stylesCompose(
|
|
159
|
-
styles[
|
|
160
|
-
'components-autocomplete__item'
|
|
161
|
-
],
|
|
162
|
-
isActive && activeItemStyles
|
|
163
|
-
);
|
|
164
|
-
const textStyle = stylesCompose(
|
|
165
|
-
textStyles,
|
|
166
|
-
isActive && activeTextStyles
|
|
167
|
-
);
|
|
168
|
-
const iconStyle = stylesCompose(
|
|
169
|
-
iconStyles,
|
|
170
|
-
isActive && activeIconStyles
|
|
171
|
-
);
|
|
172
|
-
const iconSource =
|
|
173
|
-
option?.value?.icon?.src ||
|
|
174
|
-
option?.value?.icon;
|
|
175
|
-
|
|
176
|
-
return (
|
|
177
|
-
<TouchableOpacity
|
|
178
|
-
activeOpacity={ 0.5 }
|
|
179
|
-
style={ itemStyle }
|
|
180
|
-
key={ index }
|
|
181
|
-
onPress={ () => onSelect( option ) }
|
|
182
|
-
accessibilityLabel={ sprintf(
|
|
183
|
-
// translators: %s: Block name e.g. "Image block"
|
|
184
|
-
__( '%s block' ),
|
|
185
|
-
option?.value?.title
|
|
186
|
-
) }
|
|
187
|
-
>
|
|
188
|
-
<View
|
|
189
|
-
style={
|
|
190
|
-
styles[
|
|
191
|
-
'components-autocomplete__icon'
|
|
192
|
-
]
|
|
193
|
-
}
|
|
194
|
-
>
|
|
195
|
-
<Icon
|
|
196
|
-
icon={ iconSource }
|
|
197
|
-
size={ 24 }
|
|
198
|
-
style={ iconStyle }
|
|
199
|
-
/>
|
|
200
|
-
</View>
|
|
201
|
-
<Text style={ textStyle }>
|
|
202
|
-
{ option?.value?.title }
|
|
203
|
-
</Text>
|
|
204
|
-
</TouchableOpacity>
|
|
205
|
-
);
|
|
206
|
-
} ) }
|
|
207
|
-
</ScrollView>
|
|
208
|
-
</BackgroundView>
|
|
209
|
-
</Animated.View>
|
|
210
|
-
</View>
|
|
211
|
-
</AutocompletionItemsFill>
|
|
212
|
-
);
|
|
133
|
+
if ( ! filteredItems.length > 0 || ! isVisible ) {
|
|
134
|
+
return null;
|
|
213
135
|
}
|
|
214
136
|
|
|
215
|
-
return
|
|
137
|
+
return (
|
|
138
|
+
<AutocompletionItemsFill>
|
|
139
|
+
<View style={ styles[ 'components-autocomplete' ] }>
|
|
140
|
+
<Animated.View style={ contentStyles }>
|
|
141
|
+
<BackgroundView>
|
|
142
|
+
<ScrollView
|
|
143
|
+
testID="autocompleter"
|
|
144
|
+
ref={ scrollViewRef }
|
|
145
|
+
horizontal
|
|
146
|
+
contentContainerStyle={
|
|
147
|
+
styles[ 'components-autocomplete__content' ]
|
|
148
|
+
}
|
|
149
|
+
showsHorizontalScrollIndicator={ false }
|
|
150
|
+
keyboardShouldPersistTaps="always"
|
|
151
|
+
accessibilityLabel={
|
|
152
|
+
// translators: Slash inserter autocomplete results
|
|
153
|
+
__( 'Slash inserter results' )
|
|
154
|
+
}
|
|
155
|
+
>
|
|
156
|
+
{ filteredItems.map( ( option, index ) => {
|
|
157
|
+
const isActive = index === selectedIndex;
|
|
158
|
+
const itemStyle = stylesCompose(
|
|
159
|
+
styles[ 'components-autocomplete__item' ],
|
|
160
|
+
isActive && activeItemStyles
|
|
161
|
+
);
|
|
162
|
+
const textStyle = stylesCompose(
|
|
163
|
+
textStyles,
|
|
164
|
+
isActive && activeTextStyles
|
|
165
|
+
);
|
|
166
|
+
const iconStyle = stylesCompose(
|
|
167
|
+
iconStyles,
|
|
168
|
+
isActive && activeIconStyles
|
|
169
|
+
);
|
|
170
|
+
const iconSource =
|
|
171
|
+
option?.value?.icon?.src ||
|
|
172
|
+
option?.value?.icon;
|
|
173
|
+
|
|
174
|
+
return (
|
|
175
|
+
<TouchableOpacity
|
|
176
|
+
activeOpacity={ 0.5 }
|
|
177
|
+
style={ itemStyle }
|
|
178
|
+
key={ index }
|
|
179
|
+
onPress={ () => onSelect( option ) }
|
|
180
|
+
accessibilityLabel={ sprintf(
|
|
181
|
+
// translators: %s: Block name e.g. "Image block"
|
|
182
|
+
__( '%s block' ),
|
|
183
|
+
option?.value?.title
|
|
184
|
+
) }
|
|
185
|
+
>
|
|
186
|
+
<View
|
|
187
|
+
style={
|
|
188
|
+
styles[
|
|
189
|
+
'components-autocomplete__icon'
|
|
190
|
+
]
|
|
191
|
+
}
|
|
192
|
+
>
|
|
193
|
+
<Icon
|
|
194
|
+
icon={ iconSource }
|
|
195
|
+
size={ 24 }
|
|
196
|
+
style={ iconStyle }
|
|
197
|
+
/>
|
|
198
|
+
</View>
|
|
199
|
+
<Text style={ textStyle }>
|
|
200
|
+
{ option?.value?.title }
|
|
201
|
+
</Text>
|
|
202
|
+
</TouchableOpacity>
|
|
203
|
+
);
|
|
204
|
+
} ) }
|
|
205
|
+
</ScrollView>
|
|
206
|
+
</BackgroundView>
|
|
207
|
+
</Animated.View>
|
|
208
|
+
</View>
|
|
209
|
+
</AutocompletionItemsFill>
|
|
210
|
+
);
|
|
216
211
|
}
|
|
217
|
-
|
|
218
|
-
export default getAutoCompleterUI;
|
|
@@ -25,7 +25,7 @@ import getDefaultUseItems from './get-default-use-items';
|
|
|
25
25
|
import Button from '../button';
|
|
26
26
|
import Popover from '../popover';
|
|
27
27
|
import { VisuallyHidden } from '../visually-hidden';
|
|
28
|
-
import type { AutocompleterUIProps, KeyedOption
|
|
28
|
+
import type { AutocompleterUIProps, KeyedOption } from './types';
|
|
29
29
|
|
|
30
30
|
type ListBoxProps = {
|
|
31
31
|
items: KeyedOption[];
|
|
@@ -79,112 +79,125 @@ function ListBox( {
|
|
|
79
79
|
);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
export function
|
|
82
|
+
export function AutocompleterUI( {
|
|
83
|
+
autocompleter,
|
|
84
|
+
filterValue,
|
|
85
|
+
instanceId,
|
|
86
|
+
listBoxId,
|
|
87
|
+
className,
|
|
88
|
+
selectedIndex,
|
|
89
|
+
onChangeOptions,
|
|
90
|
+
onSelect,
|
|
91
|
+
reset,
|
|
92
|
+
contentRef,
|
|
93
|
+
}: AutocompleterUIProps ) {
|
|
94
|
+
// The useItems hook is derived from the autocompleter prop. This is safe
|
|
95
|
+
// because the parent renders this component with key={autocompleter.name},
|
|
96
|
+
// ensuring a fresh mount (and stable hook identity) when the completer changes.
|
|
83
97
|
const useItems =
|
|
84
98
|
autocompleter.useItems ?? getDefaultUseItems( autocompleter );
|
|
99
|
+
// eslint-disable-next-line react-compiler/react-compiler
|
|
100
|
+
const [ items ] = useItems( filterValue );
|
|
101
|
+
const popoverAnchor = useAnchor( {
|
|
102
|
+
editableContentElement: contentRef.current,
|
|
103
|
+
} );
|
|
104
|
+
|
|
105
|
+
const [ needsA11yCompat, setNeedsA11yCompat ] = useState( false );
|
|
106
|
+
const popoverRef = useRef< HTMLElement >( null );
|
|
107
|
+
const popoverRefs = useMergeRefs( [
|
|
108
|
+
popoverRef,
|
|
109
|
+
useRefEffect(
|
|
110
|
+
( node ) => {
|
|
111
|
+
if ( ! contentRef.current ) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
85
114
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
// the content, we need to duplicate the options list in the
|
|
115
|
-
// content document so that it's available to the screen
|
|
116
|
-
// readers, which check the DOM ID based aria-* attributes.
|
|
117
|
-
setNeedsA11yCompat(
|
|
118
|
-
node.ownerDocument !== contentRef.current.ownerDocument
|
|
119
|
-
);
|
|
120
|
-
},
|
|
121
|
-
[ contentRef ]
|
|
122
|
-
),
|
|
123
|
-
] );
|
|
124
|
-
|
|
125
|
-
useOnClickOutside( popoverRef, reset );
|
|
126
|
-
|
|
127
|
-
const debouncedSpeak = useDebounce( speak, 500 );
|
|
128
|
-
|
|
129
|
-
function announce( options: Array< KeyedOption > ) {
|
|
130
|
-
if ( ! debouncedSpeak ) {
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
if ( !! options.length ) {
|
|
134
|
-
if ( filterValue ) {
|
|
135
|
-
debouncedSpeak(
|
|
136
|
-
sprintf(
|
|
137
|
-
/* translators: %d: number of results. */
|
|
138
|
-
_n(
|
|
139
|
-
'%d result found, use up and down arrow keys to navigate.',
|
|
140
|
-
'%d results found, use up and down arrow keys to navigate.',
|
|
141
|
-
options.length
|
|
142
|
-
),
|
|
115
|
+
// If the popover is rendered in a different document than
|
|
116
|
+
// the content, we need to duplicate the options list in the
|
|
117
|
+
// content document so that it's available to the screen
|
|
118
|
+
// readers, which check the DOM ID based aria-* attributes.
|
|
119
|
+
setNeedsA11yCompat(
|
|
120
|
+
node.ownerDocument !== contentRef.current.ownerDocument
|
|
121
|
+
);
|
|
122
|
+
},
|
|
123
|
+
[ contentRef ]
|
|
124
|
+
),
|
|
125
|
+
] );
|
|
126
|
+
|
|
127
|
+
useOnClickOutside( popoverRef, reset );
|
|
128
|
+
|
|
129
|
+
const debouncedSpeak = useDebounce( speak, 500 );
|
|
130
|
+
|
|
131
|
+
function announce( options: Array< KeyedOption > ) {
|
|
132
|
+
if ( ! debouncedSpeak ) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if ( !! options.length ) {
|
|
136
|
+
if ( filterValue ) {
|
|
137
|
+
debouncedSpeak(
|
|
138
|
+
sprintf(
|
|
139
|
+
/* translators: %d: number of results. */
|
|
140
|
+
_n(
|
|
141
|
+
'%d result found, use up and down arrow keys to navigate.',
|
|
142
|
+
'%d results found, use up and down arrow keys to navigate.',
|
|
143
143
|
options.length
|
|
144
144
|
),
|
|
145
|
-
|
|
146
|
-
)
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
145
|
+
options.length
|
|
146
|
+
),
|
|
147
|
+
'assertive'
|
|
148
|
+
);
|
|
149
|
+
} else {
|
|
150
|
+
debouncedSpeak(
|
|
151
|
+
sprintf(
|
|
152
|
+
/* translators: %d: number of results. */
|
|
153
|
+
_n(
|
|
154
|
+
'Initial %d result loaded. Type to filter all available results. Use up and down arrow keys to navigate.',
|
|
155
|
+
'Initial %d results loaded. Type to filter all available results. Use up and down arrow keys to navigate.',
|
|
156
156
|
options.length
|
|
157
157
|
),
|
|
158
|
-
|
|
159
|
-
)
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
debouncedSpeak( __( 'No results.' ), 'assertive' );
|
|
158
|
+
options.length
|
|
159
|
+
),
|
|
160
|
+
'assertive'
|
|
161
|
+
);
|
|
163
162
|
}
|
|
163
|
+
} else {
|
|
164
|
+
debouncedSpeak( __( 'No results.' ), 'assertive' );
|
|
164
165
|
}
|
|
166
|
+
}
|
|
165
167
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
168
|
+
useLayoutEffect( () => {
|
|
169
|
+
onChangeOptions( items );
|
|
170
|
+
announce( items );
|
|
171
|
+
// We want to avoid introducing unexpected side effects.
|
|
172
|
+
// See https://github.com/WordPress/gutenberg/pull/41820
|
|
173
|
+
}, [ items ] );
|
|
172
174
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
175
|
+
if ( items.length === 0 ) {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
176
178
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
179
|
+
return (
|
|
180
|
+
<>
|
|
181
|
+
<Popover
|
|
182
|
+
offset={ 8 }
|
|
183
|
+
focusOnMount={ false }
|
|
184
|
+
placement="top-start"
|
|
185
|
+
className="components-autocomplete__popover"
|
|
186
|
+
anchor={ popoverAnchor }
|
|
187
|
+
ref={ popoverRefs }
|
|
188
|
+
>
|
|
189
|
+
<ListBox
|
|
190
|
+
items={ items }
|
|
191
|
+
onSelect={ onSelect }
|
|
192
|
+
selectedIndex={ selectedIndex }
|
|
193
|
+
instanceId={ instanceId }
|
|
194
|
+
listBoxId={ listBoxId }
|
|
195
|
+
className={ className }
|
|
196
|
+
/>
|
|
197
|
+
</Popover>
|
|
198
|
+
{ contentRef.current &&
|
|
199
|
+
needsA11yCompat &&
|
|
200
|
+
createPortal(
|
|
188
201
|
<ListBox
|
|
189
202
|
items={ items }
|
|
190
203
|
onSelect={ onSelect }
|
|
@@ -192,27 +205,12 @@ export function getAutoCompleterUI( autocompleter: WPCompleter ) {
|
|
|
192
205
|
instanceId={ instanceId }
|
|
193
206
|
listBoxId={ listBoxId }
|
|
194
207
|
className={ className }
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
items={ items }
|
|
202
|
-
onSelect={ onSelect }
|
|
203
|
-
selectedIndex={ selectedIndex }
|
|
204
|
-
instanceId={ instanceId }
|
|
205
|
-
listBoxId={ listBoxId }
|
|
206
|
-
className={ className }
|
|
207
|
-
Component={ VisuallyHidden }
|
|
208
|
-
/>,
|
|
209
|
-
contentRef.current.ownerDocument.body
|
|
210
|
-
) }
|
|
211
|
-
</>
|
|
212
|
-
);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
return AutocompleterUI;
|
|
208
|
+
Component={ VisuallyHidden }
|
|
209
|
+
/>,
|
|
210
|
+
contentRef.current.ownerDocument.body
|
|
211
|
+
) }
|
|
212
|
+
</>
|
|
213
|
+
);
|
|
216
214
|
}
|
|
217
215
|
|
|
218
216
|
function useOnClickOutside(
|