design-system-next 2.9.3 → 2.9.5

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.
@@ -23,46 +23,60 @@
23
23
  width: props.width,
24
24
  }"
25
25
  >
26
- <div @click="handleOptionsToggle">
27
- <spr-input
28
- v-model="inputText"
29
- :class="{
30
- 'spr-cursor-pointer': true,
26
+ <div ref="multiSelectRef">
27
+ <div @click="handleOptionsToggle">
28
+ <spr-input
29
+ v-model="inputText"
30
+ :id="props.id"
31
+ :class="{
32
+ 'spr-cursor-pointer': true,
33
+ }"
34
+ :placeholder="props.placeholder"
35
+ autocomplete="off"
36
+ :helper-text="props.helperText"
37
+ :helper-icon="props.helperIcon"
38
+ :display-helper="props.displayHelper"
39
+ :active="props.active"
40
+ :readonly="true"
41
+ :disabled="props.disabled"
42
+ :error="props.error"
43
+ >
44
+ <template #icon>
45
+ <div class="spr-flex spr-items-center spr-gap-1">
46
+ <Icon
47
+ v-if="props.clearable && inputText"
48
+ class="spr-cursor-pointer"
49
+ icon="ph:x"
50
+ @click.stop="handleClear"
51
+ />
52
+ <Icon icon="ph:caret-down" />
53
+ </div>
54
+ </template>
55
+
56
+ <template #helperMessage>
57
+ <slot name="helperMessage" />
58
+ </template>
59
+ </spr-input>
60
+
61
+ <!-- Hidden Select for QA automation -->
62
+ <select v-if="multiSelectOptions && multiSelectOptions.length" v-model="multiSelectModel" multiple hidden>
63
+ <option v-for="option in multiSelectOptions" :key="option.value" :value="option.value">
64
+ {{ option.text }}
65
+ </option>
66
+ </select>
67
+ </div>
68
+
69
+ <!-- This div used to poppulate popper menu -->
70
+ <div
71
+ :id="props.id"
72
+ :style="{
73
+ width: props.popperWidth,
31
74
  }"
32
- :placeholder="props.placeholder"
33
- :readonly="true"
34
- :disabled="props.disabled"
35
- autocomplete="off"
36
- :helper-text="props.helperText"
37
- :helper-icon="props.helperIcon"
38
- :display-helper="props.displayHelper"
39
- >
40
- <template #icon>
41
- <div class="spr-flex spr-items-center spr-gap-1">
42
- <Icon
43
- v-if="props.clearable && inputText"
44
- class="spr-cursor-pointer"
45
- icon="ph:x"
46
- @click.stop="handleClear"
47
- />
48
- <Icon icon="ph:caret-down" />
49
- </div>
50
- </template>
51
- </spr-input>
75
+ ></div>
52
76
  </div>
53
77
 
54
- <div
55
- :id="props.id"
56
- :style="{
57
- width: props.popperWidth,
58
- }"
59
- ></div>
60
-
61
78
  <template #popper>
62
- <div
63
- ref="multiSelectRef"
64
- class="spr-grid spr-max-h-[300px] spr-gap-0.5 spr-overflow-y-auto spr-overflow-x-hidden spr-p-2"
65
- >
79
+ <div class="spr-grid spr-max-h-[300px] spr-gap-0.5 spr-overflow-y-auto spr-overflow-x-hidden spr-p-2">
66
80
  <template v-if="multiSelectOptions.length > 0">
67
81
  <spr-list
68
82
  v-model="multiSelectedListItems"
@@ -118,7 +118,7 @@ export const useMultiSelect = (props: MultiSelectPropTypes, emit: SetupContext<M
118
118
  * Opens the multi-select options.
119
119
  */
120
120
  const handleOptionsToggle = () => {
121
- multiSelectPopperState.value = true;
121
+ multiSelectPopperState.value = !multiSelectPopperState.value;
122
122
  };
123
123
 
124
124
  /**
@@ -140,7 +140,6 @@ export const useMultiSelect = (props: MultiSelectPropTypes, emit: SetupContext<M
140
140
 
141
141
  hasUserSelected.value = true;
142
142
  multiSelectModel.value = selectedValues;
143
- multiSelectPopperState.value = true;
144
143
  inputTextBackup.value =
145
144
  multiSelectedItems.length > 3
146
145
  ? `${multiSelectedItems.length} items selected`
@@ -224,9 +223,23 @@ export const useMultiSelect = (props: MultiSelectPropTypes, emit: SetupContext<M
224
223
  updateMultiSelectedItemsFromValue();
225
224
  });
226
225
 
227
- watch(multiSelectOptions, () => {
228
- updateMultiSelectedItemsFromValue();
229
- });
226
+ watch(
227
+ multiSelectOptions,
228
+ () => {
229
+ updateMultiSelectedItemsFromValue();
230
+ },
231
+ { deep: true },
232
+ );
233
+
234
+ // Add watcher for options prop to re-process options when changed
235
+ watch(
236
+ options,
237
+ () => {
238
+ processOptions();
239
+ updateMultiSelectedItemsFromValue();
240
+ },
241
+ { deep: true },
242
+ );
230
243
 
231
244
  /**
232
245
  * Handles closing the multi-select when clicking outside.
@@ -29,6 +29,7 @@ export const selectPropTypes = {
29
29
  id: {
30
30
  type: String,
31
31
  required: true,
32
+ default: 'select',
32
33
  },
33
34
  modelValue: {
34
35
  type: [String, Number, Object, Array] as PropType<
@@ -102,10 +103,18 @@ export const selectPropTypes = {
102
103
  type: String,
103
104
  default: '',
104
105
  },
106
+ active: {
107
+ type: Boolean,
108
+ default: false,
109
+ },
105
110
  disabled: {
106
111
  type: Boolean,
107
112
  default: false,
108
113
  },
114
+ error: {
115
+ type: Boolean,
116
+ default: false,
117
+ },
109
118
  clearable: {
110
119
  type: Boolean,
111
120
  default: false,
@@ -23,58 +23,69 @@
23
23
  width: props.width,
24
24
  }"
25
25
  >
26
- <div @click="handleOptionsToggle">
27
- <spr-input
28
- v-model="inputText"
29
- :class="{
30
- 'spr-cursor-pointer': !props.searchable,
31
- }"
32
- :placeholder="props.placeholder"
33
- :readonly="!props.searchable"
34
- :disabled="props.disabled"
35
- autocomplete="off"
36
- :helper-text="props.helperText"
37
- :helper-icon="props.helperIcon"
38
- :display-helper="props.displayHelper"
39
- @keyup="handleSearch"
40
- >
41
- <template #icon>
42
- <div class="spr-flex spr-items-center spr-gap-1">
43
- <Icon
44
- v-if="props.clearable && inputText"
45
- class="spr-cursor-pointer"
46
- icon="ph:x"
47
- @click.stop="handleClear"
48
- />
49
- <Icon icon="ph:caret-down" />
50
- </div>
51
- </template>
52
- </spr-input>
26
+ <div ref="selectRef">
27
+ <div @click="handleOptionsToggle">
28
+ <spr-input
29
+ :id="props.id"
30
+ v-model="inputText"
31
+ :class="{
32
+ 'spr-cursor-pointer': !props.searchable,
33
+ }"
34
+ :placeholder="props.placeholder"
35
+ autocomplete="off"
36
+ :helper-text="props.helperText"
37
+ :helper-icon="props.helperIcon"
38
+ :display-helper="props.displayHelper"
39
+ :active="props.active"
40
+ :readonly="!props.searchable"
41
+ :disabled="props.disabled"
42
+ :error="props.error"
43
+ @keyup="handleSearch"
44
+ >
45
+ <template #icon>
46
+ <div class="spr-flex spr-cursor-pointer spr-items-center">
47
+ <Icon
48
+ v-if="props.clearable && inputText"
49
+ class="spr-cursor-pointer"
50
+ icon="ph:x"
51
+ @click.stop="handleClear"
52
+ />
53
+ <Icon icon="ph:caret-down" />
54
+ </div>
55
+ </template>
53
56
 
54
- <select
55
- v-if="selectOptions && selectOptions.length"
56
- :value="Array.isArray(selectModel) ? selectModel[0] : selectModel"
57
- data-testid="qa-hidden-select"
58
- tabindex="-1"
59
- aria-hidden="true"
60
- hidden
61
- >
62
- <option v-for="item in selectOptions" :key="item.value" :value="item.value">
63
- {{ item.text }}
64
- </option>
65
- </select>
66
- </div>
57
+ <template #helperMessage>
58
+ <slot name="helperMessage" />
59
+ </template>
60
+ </spr-input>
61
+
62
+ <!-- Hidden Select for QA automation -->
63
+ <select
64
+ v-if="selectOptions && selectOptions.length"
65
+ :value="Array.isArray(selectModel) ? selectModel[0] : selectModel"
66
+ data-testid="qa-hidden-select"
67
+ tabindex="-1"
68
+ aria-hidden="true"
69
+ hidden
70
+ >
71
+ <option v-for="item in selectOptions" :key="item.value" :value="item.value">
72
+ {{ item.text }}
73
+ </option>
74
+ </select>
75
+ </div>
67
76
 
68
- <div
69
- :id="props.id"
70
- :style="{
71
- width: props.popperWidth,
72
- }"
73
- ></div>
77
+ <!-- This div used to poppulate popper menu -->
78
+ <div
79
+ :id="props.id"
80
+ :style="{
81
+ width: props.popperWidth,
82
+ }"
83
+ ></div>
84
+ </div>
74
85
 
75
86
  <template #popper>
76
87
  <div
77
- ref="selectRef"
88
+ ref="selectPopperRef"
78
89
  class="spr-grid spr-max-h-[300px] spr-gap-0.5 spr-overflow-y-auto spr-overflow-x-hidden spr-p-2"
79
90
  >
80
91
  <template v-if="isSearching">
@@ -153,6 +164,7 @@ const {
153
164
  selectClasses,
154
165
  selectPopperState,
155
166
  selectRef,
167
+ selectPopperRef,
156
168
  selectModel,
157
169
  selectOptions,
158
170
  filteredSelectOptions,
@@ -34,6 +34,7 @@ export const useSelect = (props: SelectPropTypes, emit: SetupContext<SelectEmitT
34
34
  const isSelectPopperDisabled = computed(() => disabled.value);
35
35
 
36
36
  // Select Variables
37
+ const selectPopperRef = ref<HTMLDivElement | null>(null);
37
38
  const selectModel = useVModel(props, 'modelValue', emit);
38
39
  const selectedListItems = ref<MenuListType[]>();
39
40
  const selectOptions = ref<MenuListType[]>([]);
@@ -44,12 +45,6 @@ export const useSelect = (props: SelectPropTypes, emit: SetupContext<SelectEmitT
44
45
  const inputTextBackup = ref<string | number>('');
45
46
  const isSearching = ref<boolean>(false);
46
47
 
47
- const handleOptionsToggle = () => {
48
- selectPopperState.value = true;
49
-
50
- isSearching.value = false;
51
- };
52
-
53
48
  // Normalized value for internal use - always an array
54
49
  const normalizedValue = computed(() => {
55
50
  // If already an array, use it
@@ -147,14 +142,6 @@ export const useSelect = (props: SelectPropTypes, emit: SetupContext<SelectEmitT
147
142
  return selectOptions.value.filter((item) => item.text?.toString().toLowerCase().includes(query));
148
143
  });
149
144
 
150
- watch(
151
- options,
152
- () => {
153
- processOptions();
154
- },
155
- { deep: true },
156
- );
157
-
158
145
  // Search handler: always emit search-string, but only filter locally if local search is enabled
159
146
  const handleSearch = () => {
160
147
  isSearching.value = true;
@@ -166,24 +153,12 @@ export const useSelect = (props: SelectPropTypes, emit: SetupContext<SelectEmitT
166
153
  emit('search-string', inputText.value);
167
154
  }, 300);
168
155
 
169
- onClickOutside(selectRef, () => {
170
- selectPopperState.value = false;
171
-
172
- // If user was searching, restore inputText from backup
173
- if (isSearching.value) {
174
- inputText.value = inputTextBackup.value;
175
- }
156
+ // Toggle options popper state
157
+ const handleOptionsToggle = () => {
158
+ selectPopperState.value = !selectPopperState.value;
176
159
 
177
160
  isSearching.value = false;
178
- });
179
-
180
- useInfiniteScroll(
181
- selectRef,
182
- () => {
183
- emit('infinite-scroll-trigger', true);
184
- },
185
- { distance: 10 },
186
- );
161
+ };
187
162
 
188
163
  // Handle selected item for simple list component
189
164
  const handleSelectedItem = (selectedItems: MenuListType[]) => {
@@ -336,6 +311,33 @@ export const useSelect = (props: SelectPropTypes, emit: SetupContext<SelectEmitT
336
311
  updateSelectedItemsFromValue();
337
312
  });
338
313
 
314
+ watch(
315
+ options,
316
+ () => {
317
+ processOptions();
318
+ },
319
+ { deep: true },
320
+ );
321
+
322
+ onClickOutside(selectRef, () => {
323
+ selectPopperState.value = false;
324
+
325
+ // If user was searching, restore inputText from backup
326
+ if (isSearching.value) {
327
+ inputText.value = inputTextBackup.value;
328
+ }
329
+
330
+ isSearching.value = false;
331
+ });
332
+
333
+ useInfiniteScroll(
334
+ selectPopperRef,
335
+ () => {
336
+ emit('infinite-scroll-trigger', true);
337
+ },
338
+ { distance: 10 },
339
+ );
340
+
339
341
  onMounted(() => {
340
342
  processOptions();
341
343
 
@@ -351,8 +353,8 @@ export const useSelect = (props: SelectPropTypes, emit: SetupContext<SelectEmitT
351
353
  return {
352
354
  selectClasses,
353
355
  selectPopperState,
354
- handleOptionsToggle,
355
356
  selectRef,
357
+ selectPopperRef,
356
358
  selectModel: compatPreSelectedItems, // Use compatible format for lists
357
359
  selectOptions,
358
360
  filteredSelectOptions,
@@ -360,6 +362,7 @@ export const useSelect = (props: SelectPropTypes, emit: SetupContext<SelectEmitT
360
362
  inputText,
361
363
  isSelectPopperDisabled,
362
364
  isSearching,
365
+ handleOptionsToggle,
363
366
  handleSelectedItem,
364
367
  handleSearch,
365
368
  handleClear,