@pocketprep/ui-kit 3.8.5 → 3.9.1

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.
Files changed (160) hide show
  1. package/dist/@pocketprep/ui-kit.css +1 -1
  2. package/dist/@pocketprep/ui-kit.js +14466 -17728
  3. package/dist/@pocketprep/ui-kit.js.map +1 -1
  4. package/dist/@pocketprep/ui-kit.umd.cjs +19 -29
  5. package/dist/@pocketprep/ui-kit.umd.cjs.map +1 -1
  6. package/eslint.config.ts +38 -11
  7. package/lib/SVGDefinitions.vue +32 -35
  8. package/lib/components/Banners/Banner.vue +10 -14
  9. package/lib/components/Blobs/Blob.vue +6 -14
  10. package/lib/components/Blobs/BlobEmptyState.vue +9 -8
  11. package/lib/components/Blobs/blob.d.ts +1 -1
  12. package/lib/components/BundleIcons/BundleIcon.vue +36 -63
  13. package/lib/components/BundleIcons/bundleIcon.d.ts +1 -1
  14. package/lib/components/Bundles/BundleList.vue +71 -59
  15. package/lib/components/Bundles/BundleSearch.vue +93 -117
  16. package/lib/components/Bundles/PremiumPill.vue +6 -12
  17. package/lib/components/Buttons/Button.vue +32 -35
  18. package/lib/components/Buttons/Link.vue +32 -31
  19. package/lib/components/Buttons/Tab.vue +14 -17
  20. package/lib/components/Calendar/Calendar.vue +87 -85
  21. package/lib/components/Charts/Bar.vue +192 -263
  22. package/lib/components/Charts/Pie.vue +55 -61
  23. package/lib/components/Charts/highcharts-wrap.ts +81 -0
  24. package/lib/components/Controls/SegmentControl.vue +26 -24
  25. package/lib/components/Controls/Slider.vue +51 -47
  26. package/lib/components/Controls/ToggleSwitch.vue +33 -31
  27. package/lib/components/EmptyStates/EmptyState.vue +69 -73
  28. package/lib/components/Exams/ExamCard.vue +59 -47
  29. package/lib/components/Exams/ExamMenuCard.vue +30 -28
  30. package/lib/components/Filters/FilterDropdown.vue +83 -86
  31. package/lib/components/Filters/FilterOptions.vue +83 -88
  32. package/lib/components/Forms/Checkbox.vue +27 -27
  33. package/lib/components/Forms/CheckboxOption.vue +30 -30
  34. package/lib/components/Forms/Errors.vue +21 -24
  35. package/lib/components/Forms/Input.vue +71 -59
  36. package/lib/components/Forms/Radio.vue +2 -2
  37. package/lib/components/Forms/RadioButton.vue +8 -8
  38. package/lib/components/Forms/Select.vue +265 -263
  39. package/lib/components/Forms/Textarea.vue +49 -35
  40. package/lib/components/Forms/select.d.ts +6 -0
  41. package/lib/components/Icons/IconAccordionArrow.vue +7 -9
  42. package/lib/components/Icons/IconActivity.vue +7 -9
  43. package/lib/components/Icons/IconAdd.vue +7 -11
  44. package/lib/components/Icons/IconAddCircle.vue +7 -9
  45. package/lib/components/Icons/IconArrow.vue +7 -9
  46. package/lib/components/Icons/IconBarChart.vue +7 -9
  47. package/lib/components/Icons/IconCalendar.vue +7 -9
  48. package/lib/components/Icons/IconCalendarPicker.vue +7 -9
  49. package/lib/components/Icons/IconChat.vue +7 -9
  50. package/lib/components/Icons/IconCheck.vue +7 -9
  51. package/lib/components/Icons/IconClose.vue +7 -9
  52. package/lib/components/Icons/IconConcept.vue +1 -1
  53. package/lib/components/Icons/IconCorrect.vue +7 -9
  54. package/lib/components/Icons/IconEdit.vue +7 -11
  55. package/lib/components/Icons/IconExam.vue +7 -9
  56. package/lib/components/Icons/IconExternalLink.vue +7 -9
  57. package/lib/components/Icons/IconEyeHide.vue +7 -9
  58. package/lib/components/Icons/IconEyeShow.vue +7 -9
  59. package/lib/components/Icons/IconFilter.vue +7 -9
  60. package/lib/components/Icons/IconFilterActive.vue +7 -9
  61. package/lib/components/Icons/IconFlag.vue +7 -9
  62. package/lib/components/Icons/IconFlagContent.vue +8 -9
  63. package/lib/components/Icons/IconFlagFeedback.vue +8 -10
  64. package/lib/components/Icons/IconFlagFilled.vue +7 -9
  65. package/lib/components/Icons/IconFullView.vue +7 -9
  66. package/lib/components/Icons/IconFullViewActive.vue +7 -9
  67. package/lib/components/Icons/IconGridDrag.vue +2 -2
  68. package/lib/components/Icons/IconHandle.vue +7 -9
  69. package/lib/components/Icons/IconHeart.vue +7 -9
  70. package/lib/components/Icons/IconHelp.vue +7 -9
  71. package/lib/components/Icons/IconHighlight.vue +2 -2
  72. package/lib/components/Icons/IconHourglass.vue +7 -9
  73. package/lib/components/Icons/IconImage.vue +7 -9
  74. package/lib/components/Icons/IconIncorrect.vue +7 -9
  75. package/lib/components/Icons/IconInfo.vue +7 -9
  76. package/lib/components/Icons/IconKeyboard.vue +7 -9
  77. package/lib/components/Icons/IconLaunch.vue +7 -9
  78. package/lib/components/Icons/IconLevelUp.vue +7 -9
  79. package/lib/components/Icons/IconLightbulb.vue +7 -9
  80. package/lib/components/Icons/IconLightning.vue +7 -9
  81. package/lib/components/Icons/IconLink.vue +7 -9
  82. package/lib/components/Icons/IconList.vue +7 -9
  83. package/lib/components/Icons/IconLoading.vue +7 -9
  84. package/lib/components/Icons/IconLoading2.vue +11 -11
  85. package/lib/components/Icons/IconLock.vue +7 -9
  86. package/lib/components/Icons/IconMissedQuestions.vue +7 -9
  87. package/lib/components/Icons/IconMoon.vue +7 -9
  88. package/lib/components/Icons/IconPaginationArrow.vue +7 -9
  89. package/lib/components/Icons/IconPaginationArrowDouble.vue +7 -9
  90. package/lib/components/Icons/IconPassage.vue +7 -9
  91. package/lib/components/Icons/IconPencil.vue +7 -9
  92. package/lib/components/Icons/IconPeople.vue +7 -9
  93. package/lib/components/Icons/IconPercent.vue +7 -9
  94. package/lib/components/Icons/IconPerson.vue +8 -9
  95. package/lib/components/Icons/IconPresent.vue +7 -9
  96. package/lib/components/Icons/IconPreview.vue +7 -9
  97. package/lib/components/Icons/IconQuestions.vue +7 -9
  98. package/lib/components/Icons/IconQuick10.vue +7 -9
  99. package/lib/components/Icons/IconQuickActions.vue +2 -2
  100. package/lib/components/Icons/IconRecommendedFilter.vue +1 -1
  101. package/lib/components/Icons/IconRemoveCircle.vue +7 -9
  102. package/lib/components/Icons/IconReviewFlag.vue +7 -9
  103. package/lib/components/Icons/IconSearch.vue +7 -9
  104. package/lib/components/Icons/IconShare.vue +7 -9
  105. package/lib/components/Icons/IconSideBar.vue +7 -9
  106. package/lib/components/Icons/IconSideBarActive.vue +7 -9
  107. package/lib/components/Icons/IconStar.vue +1 -1
  108. package/lib/components/Icons/IconStopwatch.vue +7 -9
  109. package/lib/components/Icons/IconStrike.vue +7 -9
  110. package/lib/components/Icons/IconSubject.vue +7 -9
  111. package/lib/components/Icons/IconText.vue +7 -9
  112. package/lib/components/Icons/IconTimer.vue +8 -9
  113. package/lib/components/Icons/IconWarning.vue +7 -9
  114. package/lib/components/Icons/icon.d.ts +1 -1
  115. package/lib/components/Loaders/SkeletonLoader.vue +1 -5
  116. package/lib/components/Modal/Modal.vue +23 -29
  117. package/lib/components/Modal/ModalContainer.vue +135 -135
  118. package/lib/components/Onboarding/EmailAuth.vue +66 -70
  119. package/lib/components/Onboarding/MagicCodeEntry.vue +88 -83
  120. package/lib/components/Pagination/QuestionReviewPagination.vue +3 -3
  121. package/lib/components/Pagination/TablePagination.vue +47 -44
  122. package/lib/components/PhonePerson/PhonePerson.vue +18 -18
  123. package/lib/components/PhonePerson/phonePerson.d.ts +1 -1
  124. package/lib/components/Quiz/FlagToggle.vue +45 -44
  125. package/lib/components/Quiz/GlobalMetricsToggle.vue +29 -28
  126. package/lib/components/Quiz/KeyboardShortcutsButton.vue +16 -23
  127. package/lib/components/Quiz/KeyboardShortcutsModal.vue +36 -37
  128. package/lib/components/Quiz/Question/BuildListChoicesContainer.vue +65 -65
  129. package/lib/components/Quiz/Question/ChoicesContainer.vue +5 -5
  130. package/lib/components/Quiz/Question/DropdownExplanation.vue +5 -5
  131. package/lib/components/Quiz/Question/Explanation.vue +6 -6
  132. package/lib/components/Quiz/Question/MPMCChoicesContainer.vue +17 -17
  133. package/lib/components/Quiz/Question/MPMCRadioGroup.vue +2 -2
  134. package/lib/components/Quiz/Question/MatrixChoicesContainer.vue +39 -39
  135. package/lib/components/Quiz/Question/MatrixRadioGroup.vue +6 -6
  136. package/lib/components/Quiz/Question/MobileMatrixChoicesContainer.vue +27 -28
  137. package/lib/components/Quiz/Question/MobileMatrixRadioGroup.vue +2 -2
  138. package/lib/components/Quiz/Question/PassageAndImage.vue +3 -3
  139. package/lib/components/Quiz/Question/PassageAndImageDropdown.vue +7 -7
  140. package/lib/components/Quiz/Question/Paywall.vue +2 -2
  141. package/lib/components/Quiz/Question/QuestionContext.vue +1 -1
  142. package/lib/components/Quiz/Question/StatsSummary.vue +2 -2
  143. package/lib/components/Quiz/Question/Summary.vue +11 -11
  144. package/lib/components/Quiz/Question.vue +90 -82
  145. package/lib/components/Quiz/QuizContainer.vue +1 -1
  146. package/lib/components/Quiz/QuizProgressBar.vue +23 -23
  147. package/lib/components/Quiz/question.d.ts +3 -3
  148. package/lib/components/Search/Pill.vue +16 -19
  149. package/lib/components/Search/Search.vue +52 -47
  150. package/lib/components/SidePanels/SidePanel.vue +168 -174
  151. package/lib/components/Tables/Table.vue +135 -122
  152. package/lib/components/Tables/TableActions.vue +81 -76
  153. package/lib/components/Tables/table.d.ts +1 -1
  154. package/lib/components/Tags/Tag.vue +49 -39
  155. package/lib/components/Toasts/Toast.vue +44 -42
  156. package/lib/components/Tooltips/OverflowTooltip.vue +39 -45
  157. package/lib/components/Tooltips/Tooltip.vue +69 -70
  158. package/lib/directives.ts +4 -4
  159. package/lib/utils.ts +13 -12
  160. package/package.json +27 -28
@@ -16,7 +16,7 @@
16
16
  }"
17
17
  >{{ label }}</label>
18
18
  <div
19
- ref="uikit-select__input-container"
19
+ ref="uikit-select__input-container"
20
20
  class="uikit-select__input-container"
21
21
  :tabindex="disabled || typeahead ? -1 : 0"
22
22
  role="combobox"
@@ -41,7 +41,7 @@
41
41
  >
42
42
  <slot name="selectValue" :item="modelValue">
43
43
  {{ modelValue ? modelValue.label : placeholder }}
44
-
44
+
45
45
  <div
46
46
  v-if="subtext"
47
47
  v-dark="isDarkMode"
@@ -148,316 +148,318 @@
148
148
  </div>
149
149
  </template>
150
150
 
151
- <script lang="ts">
152
- import { Component, Vue, Prop, Watch, Emit } from 'vue-facing-decorator'
151
+ <script setup lang="ts">
153
152
  import Icon from '../Icons/Icon.vue'
154
- import { dark } from '../../directives'
155
-
156
- interface IItem {
157
- value?: string | null
158
- label: string
159
- type?: 'option' | 'link'
153
+ import { dark as vDark } from '../../directives'
154
+ import { computed, nextTick, onMounted, ref, useTemplateRef, watch } from 'vue'
155
+ import type { IItem } from './select'
156
+
157
+ const {
158
+ label = '',
159
+ modelValue = null,
160
+ subtext = '',
161
+ placeholder = '',
162
+ data,
163
+ disabled = false,
164
+ autoFocus = false,
165
+ showEmptyOption = false,
166
+ emptyOptionLabel = '',
167
+ typeahead = false,
168
+ error = false,
169
+ openMenuAbove = false,
170
+ customDropdownTop = 0,
171
+ isDarkMode = false,
172
+ dropdownOverride = false,
173
+ showTypeaheadClear = false,
174
+ } = defineProps<{
175
+ label?: string
176
+ modelValue?: IItem | null
160
177
  subtext?: string
161
- }
178
+ placeholder?: string
179
+ data: IItem[]
180
+ disabled?: boolean
181
+ autoFocus?: boolean
182
+ showEmptyOption?: boolean
183
+ emptyOptionLabel?: string
184
+ typeahead?: boolean
185
+ error?: boolean
186
+ openMenuAbove?: boolean
187
+ customDropdownTop?: number
188
+ isDarkMode?: boolean
189
+ dropdownOverride?: boolean // Can override the icon to be an X instead
190
+ showTypeaheadClear?: boolean
191
+ }>()
192
+
193
+ const emit = defineEmits<{
194
+ 'update:modelValue': [modelVal: IItem | null]
195
+ 'linkClick': [item: IItem | null]
196
+ 'openDropdown': [open: boolean]
197
+ 'close': []
198
+ }>()
199
+
200
+ const hover = ref(false)
201
+ const focus = ref(false)
202
+ const showDropdown = ref(false)
203
+ const searchText = ref('')
204
+ const menuHeight = ref<number | null>(null)
205
+
206
+ const inputRef = useTemplateRef<HTMLElement>('uikit-select__input')
207
+ const inputContainerRef = useTemplateRef<HTMLElement>('uikit-select__input-container')
208
+ const menuRef = useTemplateRef<HTMLElement>('menu')
209
+ const itemRefs = useTemplateRef<HTMLElement[]>('uikit-select__items')
210
+
211
+ const filteredData = computed(() => {
212
+ if (typeahead && searchText.value) {
213
+ return data.filter(item => item.label.toLowerCase().includes(searchText.value.toLowerCase()))
214
+ } else {
215
+ if (showEmptyOption && modelValue) {
216
+ return [{ value: null, label: emptyOptionLabel || '' }, ...data ]
217
+ }
218
+ return data
219
+ }
220
+ })
162
221
 
163
- /**
164
- * @see [Designs for Inputs](https://marvelapp.com/adf8ab3/screen/70331421)
165
- */
166
- @Component({
167
- name: 'PocketSelect',
168
- components: {
169
- Icon,
170
- },
171
- directives: {
172
- dark,
173
- },
222
+ const menuPositionTop = computed(() => {
223
+ if (customDropdownTop) return customDropdownTop
224
+
225
+ if (menuHeight.value) return `-${menuHeight.value}`
226
+
227
+ if (subtext) return 60
228
+
229
+ return 36
174
230
  })
175
- export default class Select extends Vue {
176
- @Prop() label?: string
177
- @Prop() modelValue?: IItem
178
- @Prop() subtext?: string
179
- @Prop() placeholder?: string
180
- @Prop() data!: IItem[]
181
- @Prop() disabled?: boolean
182
- @Prop() autoFocus?: boolean
183
- @Prop() showEmptyOption?: boolean
184
- @Prop() emptyOptionLabel?: string
185
- @Prop() typeahead?: boolean
186
- @Prop() error?: boolean
187
- @Prop() openMenuAbove?: boolean
188
- @Prop() customDropdownTop?: number
189
- @Prop({ default: false }) isDarkMode!: boolean
190
- @Prop({ default: false }) dropdownOverride!: boolean // Can override the icon to be an X instead
191
- @Prop({ default: false }) showTypeaheadClear!: boolean
192
-
193
- hover = false
194
- focus = false
195
- showDropdown = false
196
- searchText = ''
197
- menuHeight: number | null = null
198
-
199
- get filteredData () {
200
- if (this.typeahead && this.searchText) {
201
- return this.data.filter(item => item.label.toLowerCase().includes(this.searchText.toLowerCase()))
231
+
232
+ onMounted(() => {
233
+ if (autoFocus) {
234
+ if (typeahead) {
235
+ inputRef.value?.focus()
202
236
  } else {
203
- if (this.showEmptyOption && this.modelValue) {
204
- return [{ value: null, label: this.emptyOptionLabel || '' }, ...this.data ]
205
- }
206
- return this.data
237
+ inputContainerRef.value?.focus()
207
238
  }
208
239
  }
209
240
 
210
- get menuPositionTop () {
211
- if (this.customDropdownTop) return this.customDropdownTop
241
+ if (typeahead && modelValue?.label) {
242
+ searchText.value = modelValue.label
243
+ }
212
244
 
213
- if (this.menuHeight) return `-${this.menuHeight}`
245
+ updateMenuHeight()
246
+ })
214
247
 
215
- if (this.subtext) return 60
248
+ const keydownListener = (e: Event | KeyboardEvent) => {
249
+ if (focus.value && 'key' in e && e.key.match(/^[A-Za-z0-9\s\-_@]$/)) {
250
+ e.stopPropagation()
251
+ }
252
+ }
216
253
 
217
- return 36
254
+ const blurMenu = (e?: FocusEvent) => {
255
+ if ((e?.relatedTarget as Element)?.tagName === 'LI') {
256
+ return
218
257
  }
219
258
 
220
- mounted () {
221
- if (this.autoFocus) {
222
- if (this.typeahead) {
223
- (this.$refs['uikit-select__input'] as HTMLElement).focus()
224
- } else {
225
- (this.$refs['uikit-select__input-container'] as HTMLElement).focus()
226
- }
227
- }
259
+ focus.value = false
260
+ showDropdown.value = false
228
261
 
229
- if (this.typeahead && this.modelValue?.label) {
230
- this.searchText = this.modelValue.label
231
- }
232
-
233
- this.updateMenuHeight()
262
+ if (typeahead && !searchText.value) {
263
+ emitUpdateModelValue(null)
264
+ } else if (typeahead && searchText.value && filteredData.value.length < 1 && !dropdownOverride) {
265
+ emitUpdateModelValue(null)
266
+ searchText.value = ''
267
+ } else if (typeahead && searchText.value && filteredData.value.length === 1 && filteredData.value[0]) {
268
+ emitUpdateModelValue(filteredData.value[0])
234
269
  }
270
+ }
235
271
 
236
- keydownListener (e: Event | KeyboardEvent) {
237
- if (this.focus && 'key' in e && e.key.match(/^[A-Za-z0-9\s\-_@]$/)) {
238
- e.stopPropagation()
272
+ const updateMenuHeight = () => {
273
+ if (openMenuAbove) {
274
+ const originalShowDropdownValue = showDropdown.value
275
+ if (!showDropdown.value) {
276
+ showDropdown.value = true
239
277
  }
278
+ nextTick(() => {
279
+ if (menuRef.value) {
280
+ menuHeight.value = menuRef.value.getBoundingClientRect().height || null
281
+ }
282
+ if (!originalShowDropdownValue) {
283
+ showDropdown.value = false
284
+ }
285
+ })
240
286
  }
241
287
 
242
- blurMenu (e?: FocusEvent) {
243
- if ((e?.relatedTarget as Element)?.tagName === 'LI') {
244
- return
245
- }
246
-
247
- this.focus = false
248
- this.showDropdown = false
288
+ menuHeight.value = null
289
+ }
249
290
 
250
- if (this.typeahead && !this.searchText) {
251
- this.emitUpdateModelValue(null)
252
- } else if (this.typeahead && this.searchText && this.filteredData.length < 1 && !this.dropdownOverride) {
253
- this.emitUpdateModelValue(null)
254
- this.searchText = ''
255
- } else if (this.typeahead && this.searchText && this.filteredData.length === 1 && this.filteredData[0]) {
256
- this.emitUpdateModelValue(this.filteredData[0])
257
- }
291
+ const keyPressedItem = (e: KeyboardEvent) => {
292
+ // select option on enter or space or tab (but not shift tab)
293
+ if (e.key === 'Enter' || e.key === ' ') {
294
+ e.preventDefault()
295
+ const itemValue = (e.target as HTMLElement).getAttribute('data-value')
296
+ const item = filteredData.value.find(i => itemValue ? String(i.value) === itemValue : i.value === null)
297
+ inputContainerRef.value?.focus()
298
+ item && selectItem(item)
258
299
  }
259
-
260
- updateMenuHeight () {
261
- if (this.openMenuAbove) {
262
- const originalShowDropdownValue = this.showDropdown
263
- if (!this.showDropdown) {
264
- this.showDropdown = true
265
- }
266
- this.$nextTick(() => {
267
- const menu = this.$refs['menu'] as HTMLElement
268
-
269
- if (menu) {
270
- this.menuHeight = menu.getBoundingClientRect().height
271
- }
272
- if (!originalShowDropdownValue) {
273
- this.showDropdown = false
274
- }
275
- })
276
- }
277
-
278
- this.menuHeight = null
300
+ // close menu on tab
301
+ if (e.key === 'Tab' && !e.shiftKey) {
302
+ blurMenu()
279
303
  }
280
-
281
- keyPressedItem (e: KeyboardEvent) {
282
- // select option on enter or space or tab (but not shift tab)
283
- if (e.key === 'Enter' || e.key === ' ') {
284
- e.preventDefault()
285
- const itemValue = (e.target as HTMLElement).getAttribute('data-value')
286
- const item = this.filteredData.find(i => itemValue ? String(i.value) === itemValue : i.value === null);
287
- (this.$refs['uikit-select__input-container'] as HTMLElement).focus()
288
- item && this.selectItem(item)
289
- }
290
- // close menu on tab
291
- if (e.key === 'Tab' && !e.shiftKey) {
292
- this.blurMenu()
293
- }
294
- // navigate items with up key
295
- if (e.key === 'ArrowUp') {
296
- e.preventDefault()
297
- const itemValue = (e.target as HTMLElement).getAttribute('data-value')
298
- const itemIndex = this.filteredData.findIndex(
299
- i => itemValue ? String(i.value) === itemValue : i.value === null
300
- )
301
- const prevIndex = itemIndex < 1 ? 0 : itemIndex - 1
302
- const prevValue = this.filteredData[prevIndex]?.value
303
- const items = this.$refs['uikit-select__items'] as HTMLElement[]
304
- const prevItem = items.find(
305
- item => prevValue
306
- ? String(prevValue) === item.getAttribute('data-value')
307
- : item.getAttribute('data-value') === null
308
- )
309
- if (prevItem) {
310
- prevItem.focus()
311
- this.showDropdown = true
312
- this.focus = true
313
- }
314
- }
315
- // navigate items with down key
316
- if (e.key === 'ArrowDown') {
317
- e.preventDefault()
318
- const data = this.filteredData
319
- const itemValue = (e.target as HTMLElement).getAttribute('data-value')
320
- const itemIndex = data.findIndex(i => itemValue ? String(i.value) === itemValue : i.value === null)
321
- const nextIndex = itemIndex >= data.length - 1 ? data.length - 1 : itemIndex + 1
322
- const nextValue = this.filteredData[nextIndex]?.value
323
- const items = this.$refs['uikit-select__items'] as HTMLElement[]
324
- const nextItem = items.find(
325
- item => nextValue
326
- ? String(nextValue) === item.getAttribute('data-value')
327
- : item.getAttribute('data-value') === null
328
- )
329
- if (nextItem) {
330
- nextItem.focus()
331
- this.showDropdown = true
332
- this.focus = true
333
- }
304
+ // navigate items with up key
305
+ if (e.key === 'ArrowUp') {
306
+ e.preventDefault()
307
+ const itemValue = (e.target as HTMLElement).getAttribute('data-value')
308
+ const itemIndex = filteredData.value.findIndex(
309
+ i => itemValue ? String(i.value) === itemValue : i.value === null
310
+ )
311
+ const prevIndex = itemIndex < 1 ? 0 : itemIndex - 1
312
+ const prevValue = filteredData.value[prevIndex]?.value
313
+ const prevItem = itemRefs.value?.find(
314
+ item => prevValue
315
+ ? prevValue === item.getAttribute('data-value')
316
+ : item.getAttribute('data-value') === null
317
+ )
318
+ if (prevItem) {
319
+ prevItem.focus()
320
+ showDropdown.value = true
321
+ focus.value = true
334
322
  }
335
323
  }
336
-
337
- keyPressedContainer (e: KeyboardEvent) {
338
- if (!(e.target as HTMLElement).className.includes('uikit-select__input-container')
339
- && !(e.target as HTMLElement).className.includes('uikit-select__input')
340
- ) {
341
- return
342
- }
343
-
344
- // open showDropdown on down arrow and select first item
345
- if (e.key === 'ArrowDown'
346
- || (e.key === 'Tab' && !e.shiftKey && this.typeahead && this.searchText && this.showDropdown)) {
347
- e.preventDefault()
348
- const firstValue = this.filteredData[0]?.value
349
- const items = this.$refs['uikit-select__items'] as HTMLElement[]
350
- const firstItem = items.find(
351
- item => firstValue
352
- ? String(firstValue) === item.getAttribute('data-value')
353
- : item.getAttribute('data-value') === null
354
- )
355
- if (firstItem) {
356
- firstItem.focus()
357
- this.showDropdown = true
358
- this.focus = true
359
- }
324
+ // navigate items with down key
325
+ if (e.key === 'ArrowDown') {
326
+ e.preventDefault()
327
+ const itemValue = (e.target as HTMLElement).getAttribute('data-value')
328
+ const itemIndex = filteredData.value.findIndex(i =>
329
+ itemValue
330
+ ? String(i.value) === itemValue
331
+ : i.value === null
332
+ )
333
+ const nextIndex = itemIndex >= filteredData.value.length - 1 ? filteredData.value.length - 1 : itemIndex + 1
334
+ const nextValue = filteredData.value[nextIndex]?.value
335
+ const nextItem = itemRefs.value?.find(
336
+ item => nextValue
337
+ ? nextValue === item.getAttribute('data-value')
338
+ : item.getAttribute('data-value') === null
339
+ )
340
+ if (nextItem) {
341
+ nextItem.focus()
342
+ showDropdown.value = true
343
+ focus.value = true
360
344
  }
345
+ }
346
+ }
361
347
 
362
- // toggle showDropdown on enter or space + not typeahead
363
- if (e.key === 'Enter' || (e.key === ' ' && !this.typeahead)) {
364
- e.preventDefault()
365
- this.showDropdown = !this.showDropdown
366
- }
348
+ const keyPressedContainer = (e: KeyboardEvent) => {
349
+ if (!(e.target as HTMLElement).className.includes('uikit-select__input-container')
350
+ && !(e.target as HTMLElement).className.includes('uikit-select__input')
351
+ ) {
352
+ return
353
+ }
367
354
 
368
- // escape to close dropdown
369
- if (e.key === 'Escape') {
370
- e.preventDefault()
371
- e.stopPropagation()
372
- this.showDropdown = false
355
+ // open showDropdown on down arrow and select first item
356
+ if (e.key === 'ArrowDown'
357
+ || (e.key === 'Tab' && !e.shiftKey && typeahead && searchText.value && showDropdown.value)) {
358
+ e.preventDefault()
359
+ const firstValue = filteredData.value[0]?.value
360
+ const firstItem = itemRefs.value?.find(
361
+ item => firstValue
362
+ ? firstValue === item.getAttribute('data-value')
363
+ : item.getAttribute('data-value') === null
364
+ )
365
+ if (firstItem) {
366
+ firstItem.focus()
367
+ showDropdown.value = true
368
+ focus.value = true
373
369
  }
374
370
  }
375
371
 
376
- mouseOverSelect () {
377
- this.hover = this.disabled ? false : true
372
+ // toggle showDropdown on enter or space + not typeahead
373
+ if (e.key === 'Enter' || (e.key === ' ' && !typeahead)) {
374
+ e.preventDefault()
375
+ showDropdown.value = !showDropdown.value
378
376
  }
379
377
 
380
- focusSelect () {
381
- this.focus = this.disabled ? false : true
382
- if (this.typeahead && !this.searchText) {
383
- this.showDropdown = true
384
- }
378
+ // escape to close dropdown
379
+ if (e.key === 'Escape') {
380
+ e.preventDefault()
381
+ e.stopPropagation()
382
+ showDropdown.value = false
385
383
  }
384
+ }
386
385
 
387
- selectItem (item: IItem) {
388
- if (item) {
389
- this.showDropdown = false
390
- }
386
+ const mouseOverSelect = () => {
387
+ hover.value = disabled ? false : true
388
+ }
391
389
 
392
- if (item.type === 'link') {
393
- this.emitLinkClick(item.value ? item : null)
394
- } else {
395
- this.emitUpdateModelValue(item.value ? item : null)
396
- }
390
+ const focusSelect = () => {
391
+ focus.value = disabled ? false : true
392
+ if (typeahead && !searchText.value) {
393
+ showDropdown.value = true
397
394
  }
395
+ }
398
396
 
399
- performCloseActions () {
400
- /**
401
- * If the dropdown is showing an X, there is some logic we need...
402
- * The first time, it should clear out search text -- the second time it should collapse dropdown
403
- * */
404
- if (this.searchText) {
405
- this.emitUpdateModelValue(null)
406
- this.searchText = ''
407
- } else {
408
- this.showDropdown = false
409
- this.emitClose()
410
- }
397
+ const selectItem = (item: IItem) => {
398
+ if (item) {
399
+ showDropdown.value = false
411
400
  }
412
401
 
413
- @Watch('showDropdown')
414
- showDropdownChanged (newVal: boolean) {
415
- this.emitOpenDropdown(newVal)
402
+ if (item.type === 'link') {
403
+ emitLinkClick(item.value ? item : null)
404
+ } else {
405
+ emitUpdateModelValue(item.value ? item : null)
416
406
  }
407
+ }
417
408
 
418
- @Watch('modelValue')
419
- valueChanged (newVal: IItem) {
420
- if (this.typeahead) {
421
- this.searchText = newVal?.label || ''
422
- }
409
+ const performCloseActions = () => {
410
+ /**
411
+ * If the dropdown is showing an X, there is some logic we need...
412
+ * The first time, it should clear out search text -- the second time it should collapse dropdown
413
+ * */
414
+ if (searchText.value) {
415
+ emitUpdateModelValue(null)
416
+ searchText.value = ''
417
+ } else {
418
+ showDropdown.value = false
419
+ emitClose()
423
420
  }
421
+ }
424
422
 
425
- @Watch('searchText')
426
- searchTextChanged (newVal: string) {
427
- if (this.typeahead) {
428
- const matchedItem = this.filteredData.find(item =>
429
- item.label.toLowerCase() === newVal.toLowerCase()
430
- )
431
- if (!matchedItem) {
432
- this.showDropdown = true
433
- }
434
- if (this.filteredData.length === 1 && matchedItem && this.modelValue?.label !== matchedItem.label) {
435
- this.selectItem(matchedItem)
436
- }
423
+ watch(showDropdown, (newVal: boolean) => {
424
+ emitOpenDropdown(newVal)
425
+ })
437
426
 
438
- this.updateMenuHeight()
439
- }
427
+ watch(() => modelValue, (newVal) => {
428
+ if (typeahead) {
429
+ searchText.value = newVal?.label || ''
440
430
  }
431
+ })
441
432
 
442
- @Emit('update:modelValue')
443
- emitUpdateModelValue (item: IItem | null) {
444
- return item
445
- }
433
+ watch(searchText, (newVal: string) => {
434
+ if (typeahead) {
435
+ const matchedItem = filteredData.value.find(item =>
436
+ item.label.toLowerCase() === newVal.toLowerCase()
437
+ )
438
+ if (!matchedItem) {
439
+ showDropdown.value = true
440
+ }
441
+ if (filteredData.value.length === 1 && matchedItem && modelValue?.label !== matchedItem.label) {
442
+ selectItem(matchedItem)
443
+ }
446
444
 
447
- @Emit('linkClick')
448
- emitLinkClick (item: IItem | null) {
449
- return item
445
+ updateMenuHeight()
450
446
  }
447
+ })
451
448
 
452
- @Emit('openDropdown')
453
- emitOpenDropdown (open: boolean) {
454
- return open
455
- }
449
+ const emitUpdateModelValue = (item: IItem | null) => {
450
+ emit('update:modelValue', item)
451
+ }
456
452
 
457
- @Emit('close')
458
- emitClose () {
459
- return true
460
- }
453
+ const emitLinkClick = (item: IItem | null) => {
454
+ emit('linkClick', item)
455
+ }
456
+
457
+ const emitOpenDropdown = (open: boolean) => {
458
+ emit('openDropdown', open)
459
+ }
460
+
461
+ const emitClose = () => {
462
+ emit('close')
461
463
  }
462
464
  </script>
463
465
 
@@ -613,7 +615,7 @@ export default class Select extends Vue {
613
615
  border-color: $pewter;
614
616
  color: $fog;
615
617
  caret-color: $banana-bread;
616
-
618
+
617
619
  &--hover {
618
620
  background-color: $charcoal;
619
621
  }