33 <!-- use a `<div>` instead of button because Firefox has an issue with draggable `<button>` https://bugzilla.mozilla.org/show_bug.cgi?id=568313 -->
44 <div
55 class =" WdsSelect__trigger"
6+ :class =" {
7+ 'WdsSelect__trigger--placeholder': isPlaceholderSelected,
8+ }"
69 role =" button"
710 tabindex =" 0"
811 @click =" isOpen = !isOpen"
3437 @close =" handleRemoveValue(option.value)"
3538 />
3639 <p
37- v-if =" selectedOptions.length === 0 && placeholder "
40+ v-if =" selectedOptions.length === 0"
3841 class =" WdsSelect__trigger__multiSelectLabel__placeholder"
3942 >
40- {{ placeholder }}
43+ {{ placeholderLabel }}
4144 </p >
4245 </div >
4346 <div
4649 data-writer-tooltip-strategy =" overflow"
4750 :data-writer-tooltip =" currentLabel"
4851 >
49- {{ currentLabel ?? placeholder }}
52+ {{ currentLabel ?? placeholderLabel }}
5053 </div >
5154 <div class =" WdsSelect__trigger__arrow" >
5255 <WdsIcon :name =" isOpen ? 'chevron-up' : 'chevron-down'" />
6063 :enable-multi-selection =" enableMultiSelection"
6164 :hide-icons =" hideIcons"
6265 :loading =" loading"
63- :options =" options "
66+ :options =" selectOptions "
6467 :selected =" currentValue"
6568 :style =" floatingStyles"
6669 @select =" onSelect"
@@ -102,6 +105,7 @@ const props = defineProps({
102105 enableSearch: { type: Boolean , required: false },
103106 enableMultiSelection: { type: Boolean , required: false },
104107 loading: { type: Boolean , required: false },
108+ required: { type: Boolean , required: false , default: false },
105109});
106110
107111const currentValue = defineModel ({
@@ -129,35 +133,77 @@ const { floatingStyles, update: updateFloatingStyle } = useFloating(
129133 },
130134);
131135
136+ const PLACEHOLDER_VALUE = " " ;
137+ const placeholderLabel = computed (
138+ () => props .placeholder ?? " Select an option.." ,
139+ );
140+
141+ const shouldInjectPlaceholder = computed (
142+ () => ! props .required && ! props .enableMultiSelection ,
143+ );
144+
145+ const selectOptions = computed <WdsDropdownMenuOption []>(() => {
146+ const normalized = (props .options ?? []).map ((option ) => ({
147+ ... option ,
148+ label:
149+ option .label ??
150+ (option .value !== undefined ? String (option .value ) : " " ),
151+ }));
152+
153+ if (! shouldInjectPlaceholder .value ) {
154+ return normalized ;
155+ }
156+
157+ return [
158+ {
159+ value: PLACEHOLDER_VALUE ,
160+ label: placeholderLabel .value ,
161+ isPlaceholder: true ,
162+ },
163+ ... normalized .filter (
164+ (option ) =>
165+ ! option .isPlaceholder && option .value !== PLACEHOLDER_VALUE ,
166+ ),
167+ ];
168+ });
169+
132170const currentValueArray = computed (() => {
133- if (! currentValue .value ) return [];
134- const array = Array .isArray (currentValue .value )
135- ? currentValue .value
136- : [currentValue .value ];
137- return array .filter (Boolean );
171+ const value = currentValue .value ;
172+ if (value === undefined || value === null ) return [];
173+ const array = Array .isArray (value ) ? value : [value ];
174+ return array .filter ((v ) => v !== undefined && v !== null ) as string [];
138175});
139176
177+ function findOption(value : string | undefined ) {
178+ if (value === undefined ) return undefined ;
179+ return selectOptions .value .find ((option ) => option .value === value );
180+ }
181+
140182const selectedOptions = computed <WdsDropdownMenuOption []>(() =>
141183 currentValueArray .value .map (
142- (v ) =>
143- props .options .find ((o ) => o .value === v ) ?? { value: v , label: v },
184+ (value ) =>
185+ findOption (value ) ?? {
186+ value ,
187+ label: String (value ),
188+ },
144189 ),
145190);
146191
147- const hasUnknowOptionSelected = computed (() => {
148- return (
149- currentValue .value &&
150- ! props .options .some ((o ) => o .value === currentValue .value )
151- );
152- });
192+ const hasUnknowOptionSelected = computed (() =>
193+ currentValueArray .value .some ((value ) => ! findOption (value )),
194+ );
153195
154196const currentLabel = computed (() => {
155- if (hasUnknowOptionSelected .value ) return String (currentValue .value );
197+ if (hasUnknowOptionSelected .value ) {
198+ return Array .isArray (currentValue .value )
199+ ? currentValue .value .filter (Boolean ).join (" / " )
200+ : String (currentValue .value ?? " " );
201+ }
156202
157- return selectedOptions .value
158- . map (( o ) => o . label )
159- . sort ( )
160- . join ( " / " ) ;
203+ if ( ! selectedOptions .value . length ) return undefined ;
204+ return props . enableMultiSelection
205+ ? selectedOptions . value . map (( o ) => o . label ). join ( " / " )
206+ : selectedOptions . value [ 0 ]. label ;
161207});
162208
163209const currentIcon = computed (() => {
@@ -170,6 +216,47 @@ const currentIcon = computed(() => {
170216 );
171217});
172218
219+ const isPlaceholderSelected = computed (() => {
220+ if (props .enableMultiSelection || props .required ) return false ;
221+ return findOption (
222+ typeof currentValue .value === " string" ? currentValue .value : undefined ,
223+ )?.isPlaceholder ;
224+ });
225+
226+ watch (
227+ [
228+ selectOptions ,
229+ () => props .required ,
230+ () => props .enableMultiSelection ,
231+ () => currentValue .value ,
232+ ],
233+ ensureValidSelection ,
234+ { immediate: true },
235+ );
236+
237+ function ensureValidSelection() {
238+ if (props .enableMultiSelection ) return ;
239+
240+ const options = selectOptions .value ;
241+ if (! options .length ) return ;
242+
243+ const current = currentValue .value ;
244+ const asString = typeof current === " string" ? current : undefined ;
245+ const hasCurrentSelection =
246+ asString !== undefined && Boolean (findOption (asString ));
247+
248+ if (props .required ) {
249+ if (! hasCurrentSelection || asString === " " ) {
250+ currentValue .value = options [0 ].value ;
251+ }
252+ return ;
253+ }
254+
255+ if (! hasCurrentSelection ) {
256+ currentValue .value = PLACEHOLDER_VALUE ;
257+ }
258+ }
259+
173260// close the dropdown when clicking outside
174261const hasFocus = useFocusWithin (trigger );
175262watch (
@@ -244,6 +331,9 @@ function handleRemoveValue(value: string) {
244331 font-weight : 300 ;
245332 cursor : pointer ;
246333}
334+ .WdsSelect__trigger--placeholder .WdsSelect__trigger__label {
335+ color : var (--wdsColorGray4 );
336+ }
247337
248338.WdsSelect__trigger__multiSelectLabel {
249339 flex-grow : 1 ;
0 commit comments