@eturnity/eturnity_reusable_components 9.19.4 → 9.19.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.
Files changed (46) hide show
  1. package/package.json +25 -20
  2. package/src/DemoChart.vue +16 -16
  3. package/src/assets/svgIcons/3d_house.svg +3 -0
  4. package/src/assets/svgIcons/activities_app.svg +3 -0
  5. package/src/assets/svgIcons/apps_navigation.svg +3 -0
  6. package/src/assets/svgIcons/chevron_down.svg +3 -0
  7. package/src/assets/svgIcons/chevron_up.svg +3 -0
  8. package/src/assets/svgIcons/collapse_sidebar.svg +3 -0
  9. package/src/assets/svgIcons/consumption_profile.svg +3 -0
  10. package/src/assets/svgIcons/cross_filled.svg +3 -0
  11. package/src/assets/svgIcons/data_transfer.svg +2 -2
  12. package/src/assets/svgIcons/esdec.svg +3 -0
  13. package/src/assets/svgIcons/expand_sidebar.svg +3 -0
  14. package/src/assets/svgIcons/folder_unfilled.svg +3 -0
  15. package/src/assets/svgIcons/leads_app.svg +9 -0
  16. package/src/assets/svgIcons/library_app.svg +3 -0
  17. package/src/assets/svgIcons/mounting_system.svg +2 -2
  18. package/src/assets/svgIcons/projects_app.svg +9 -0
  19. package/src/assets/svgIcons/question_mark.svg +2 -2
  20. package/src/assets/svgIcons/recurring_costs.svg +3 -0
  21. package/src/assets/svgIcons/settings.svg +8 -2
  22. package/src/assets/svgIcons/shading_snow.svg +3 -0
  23. package/src/assets/svgIcons/signature.svg +3 -0
  24. package/src/assets/svgIcons/tariff_menu.svg +3 -0
  25. package/src/assets/svgIcons/zev_community_solar.svg +3 -0
  26. package/src/components/banner/actionBanner/index.vue +1 -1
  27. package/src/components/barchart/BottomFields.vue +5 -2
  28. package/src/components/barchart/ChartControls.vue +5 -5
  29. package/src/components/barchart/index.vue +7 -2
  30. package/src/components/buttons/buttonIcon/index.vue +9 -0
  31. package/src/components/buttons/mainButton/index.vue +26 -26
  32. package/src/components/buttons/splitButtons/index.vue +1 -1
  33. package/src/components/dropdown/index.vue +7 -7
  34. package/src/components/inputs/searchInput/SearchInput.stories.js +1 -1
  35. package/src/components/inputs/searchInput/searchInput.spec.js +1 -1
  36. package/src/components/inputs/select/index.vue +406 -79
  37. package/src/components/inputs/select/option/index.vue +9 -1
  38. package/src/components/modals/cookieConsent/CookieConsent.vue +4 -4
  39. package/src/components/navigationSideMenu/index.vue +1098 -0
  40. package/src/components/pageSubtitle/index.vue +1 -1
  41. package/src/components/projectMarker/ProjectMarker.stories.js +26 -36
  42. package/src/components/projectMarker/index.vue +172 -93
  43. package/src/components/tabsHeader/index.vue +9 -1
  44. package/src/components/videoThumbnail/index.vue +1 -1
  45. package/src/helpers/cookieHelper.js +23 -13
  46. package/src/helpers/dateTimeFormatting.js +0 -1
@@ -50,10 +50,10 @@
50
50
  buttonBgColor
51
51
  ? buttonBgColor
52
52
  : colorMode == 'dark'
53
- ? 'transparentBlack1'
54
- : colorMode == 'transparent'
55
- ? 'transparent'
56
- : 'white'
53
+ ? 'transparentBlack1'
54
+ : colorMode == 'transparent'
55
+ ? 'transparent'
56
+ : 'white'
57
57
  "
58
58
  class="select-button"
59
59
  :color-mode="colorMode"
@@ -101,6 +101,7 @@
101
101
  input-height="34px"
102
102
  :input-width="computedWidth"
103
103
  :no-border="true"
104
+ :placeholder="searchPlaceholder"
104
105
  tabindex="0"
105
106
  :value="textSearch"
106
107
  @click.stop
@@ -109,6 +110,7 @@
109
110
  />
110
111
  <Selector
111
112
  v-else
113
+ :allow-wrap="selectorAllowWrap"
112
114
  :disabled="disabled"
113
115
  :padding-left="paddingLeft"
114
116
  :select-width="selectWidth"
@@ -120,27 +122,22 @@
120
122
  v-if="showCaret"
121
123
  class="caret_dropdown"
122
124
  :color-mode="colorMode"
125
+ :sidebar-caret="caretIcons === 'sidebar'"
123
126
  @click.stop="toggleCaretDropdown"
124
127
  >
125
128
  <Icon
126
129
  v-if="isDropdownOpen"
127
- :color="
128
- caretColor || colorMode == 'dark' || colorMode == 'transparent'
129
- ? 'white'
130
- : 'transparentBlack1'
131
- "
132
- name="arrow_up"
133
- :size="colorMode === 'transparent' ? '8px' : '12px'"
130
+ :color="caretResolvedColor"
131
+ :hovered-color="caretHoveredColor"
132
+ :name="caretOpenIconName"
133
+ :size="caretIconPixelSize"
134
134
  />
135
135
  <Icon
136
136
  v-else
137
- :color="
138
- caretColor || colorMode == 'dark' || colorMode == 'transparent'
139
- ? 'white'
140
- : 'transparentBlack1'
141
- "
142
- name="arrow_down"
143
- :size="colorMode === 'transparent' ? '8px' : '12px'"
137
+ :color="caretResolvedColor"
138
+ :hovered-color="caretHoveredColor"
139
+ :name="caretClosedIconName"
140
+ :size="caretIconPixelSize"
144
141
  />
145
142
  </Caret>
146
143
  </SelectButton>
@@ -158,6 +155,7 @@
158
155
  : 'white'
159
156
  "
160
157
  class="rc-select-dropdown"
158
+ :dropdown-match-max-content="dropdownMatchMaxContent"
161
159
  :dropdown-position="dropdownPosition"
162
160
  :font-color="
163
161
  dropdownFontColor ||
@@ -171,11 +169,11 @@
171
169
  colorMode == 'dark'
172
170
  ? '#000000'
173
171
  : colorMode == 'transparent'
174
- ? 'grey6'
175
- : dropdownBgColor
172
+ ? 'grey6'
173
+ : dropdownBgColor
176
174
  "
177
175
  :hovered-index="hoveredIndex"
178
- :hovered-value="hoveredValue"
176
+ :hovered-value="hoveredValueDomAttr"
179
177
  :is-active="isActive"
180
178
  :is-fixed-dropdown-position="isFixedDropdownPosition"
181
179
  :is-parent-modal="isParentModal"
@@ -200,6 +198,35 @@
200
198
  >
201
199
  <!-- When deferDropdownSlotContent is true, slot VNodes are not created until the
202
200
  dropdown opens — avoids mounting huge option lists (e.g. library filters). -->
201
+ <DropdownSearchShell v-if="isDropdownSearchVisible" @click.stop>
202
+ <DropdownSearchRow>
203
+ <DropdownSearchInputWrap>
204
+ <InputText
205
+ ref="dropdownSearchInput"
206
+ background-color="transparent"
207
+ :data-qa-id="
208
+ dataQaId ? `${dataQaId}_dropdown_search` : undefined
209
+ "
210
+ :font-color="'eturnityGrey'"
211
+ :font-size="fontSize"
212
+ input-height="30px"
213
+ input-width="100%"
214
+ :no-border="true"
215
+ :placeholder="searchPlaceholder"
216
+ tabindex="0"
217
+ :value="textSearch"
218
+ @click.stop
219
+ @input-change="searchChange"
220
+ @keydown.stop="onKeyDown"
221
+ />
222
+ </DropdownSearchInputWrap>
223
+ <Icon
224
+ :color="dropdownSearchIconColor"
225
+ name="search"
226
+ size="16px"
227
+ />
228
+ </DropdownSearchRow>
229
+ </DropdownSearchShell>
203
230
  <slot
204
231
  v-if="!deferDropdownSlotContent || isSelectDropdownShown"
205
232
  name="dropdown"
@@ -247,6 +274,7 @@
247
274
  import InfoText from '../../infoText'
248
275
  import ErrorMessage from '../../errorMessage'
249
276
  import Icon from '../../icon'
277
+ import theme from '../../../assets/theme.js'
250
278
  import InputText from '../inputText'
251
279
  import DraggableInputHandle from '../../draggableInputHandle'
252
280
  import IsRequiredLabelStar from '../isRequiredLabelStar'
@@ -255,15 +283,23 @@
255
283
  const CARET_WIDTH = '30px'
256
284
  const BORDER_WIDTH = '1px'
257
285
 
258
- const CaretAttrs = { colorMode: String }
286
+ const CaretAttrs = { colorMode: String, sidebarCaret: Boolean }
259
287
  const Caret = styled('div', CaretAttrs)`
260
288
  display: flex;
261
289
  align-items: center;
262
290
  justify-content: center;
263
291
  width: ${(props) =>
264
- props.colorMode === 'transparent' ? '15px' : CARET_WIDTH};
292
+ props.sidebarCaret
293
+ ? '24px'
294
+ : props.colorMode === 'transparent'
295
+ ? '15px'
296
+ : CARET_WIDTH};
265
297
  min-width: ${(props) =>
266
- props.colorMode === 'transparent' ? '15px' : CARET_WIDTH};
298
+ props.sidebarCaret
299
+ ? '24px'
300
+ : props.colorMode === 'transparent'
301
+ ? '15px'
302
+ : CARET_WIDTH};
267
303
  height: 100%;
268
304
  align-items: center;
269
305
  cursor: pointer;
@@ -271,6 +307,7 @@
271
307
  `
272
308
 
273
309
  const selectorProps = {
310
+ allowWrap: Boolean,
274
311
  disabled: Boolean,
275
312
  selectWidth: String,
276
313
  paddingLeft: String,
@@ -280,7 +317,19 @@
280
317
  color: ${(props) => (props.disabled ? props.theme.colors.grey2 : '')};
281
318
  ${(props) =>
282
319
  props.selectWidth === '100%'
283
- ? 'width: 100%;'
320
+ ? props.allowWrap
321
+ ? `
322
+ width: 100%;
323
+ min-width: 0;
324
+ overflow: visible;
325
+ white-space: normal;
326
+ `
327
+ : `
328
+ width: 100%;
329
+ white-space: nowrap;
330
+ text-overflow: ellipsis;
331
+ overflow: hidden;
332
+ `
284
333
  : `width: calc(${props.selectWidth} -
285
334
  (
286
335
  ${CARET_WIDTH} +
@@ -365,32 +414,32 @@
365
414
  ? 'padding: 10px 15px 10px 5px;'
366
415
  : 'padding: 10px 15px;'
367
416
  : props.isSearchBarVisible
368
- ? ''
369
- : `padding-left: ${
370
- props.hasNoPadding
371
- ? '0'
372
- : props.tablePaddingLeft
373
- ? props.tablePaddingLeft
374
- : props.paddingLeft
375
- }`};
417
+ ? ''
418
+ : `padding-left: ${
419
+ props.hasNoPadding
420
+ ? '0'
421
+ : props.tablePaddingLeft
422
+ ? props.tablePaddingLeft
423
+ : props.paddingLeft
424
+ }`};
376
425
  text-align: ${(props) => (props.textCenter ? 'center' : 'left')};
377
426
  min-height: ${(props) =>
378
427
  props.selectHeight
379
428
  ? props.selectHeight
380
429
  : props.selectMinHeight
381
- ? props.selectMinHeight
382
- : props.height
383
- ? props.height
384
- : '36px'};
430
+ ? props.selectMinHeight
431
+ : props.height
432
+ ? props.height
433
+ : '36px'};
385
434
  display: flex;
386
435
  align-items: center;
387
- height: ${(props) => props.selectHeight};
436
+ ${(props) => (props.selectHeight ? `height: ${props.selectHeight};` : '')}
388
437
  ${({ showBorder, theme, hasError }) =>
389
438
  showBorder &&
390
439
  `
391
440
  border: ${BORDER_WIDTH} solid ${
392
- hasError ? theme.colors.red : theme.colors.grey4
393
- }
441
+ hasError ? theme.colors.red : theme.colors.grey4
442
+ }
394
443
  `}
395
444
  opacity: ${(props) =>
396
445
  props.colorMode === 'transparent' && props.disabled ? '0.4' : '1'};
@@ -398,18 +447,18 @@
398
447
  props.colorMode === 'transparent'
399
448
  ? 'transparent'
400
449
  : props.disabled && props.showDisabledBackground
401
- ? props.theme.colors.grey5
402
- : props.theme.colors[props.bgColor]
403
- ? props.theme.colors[props.bgColor]
404
- : props.bgColor} !important;
450
+ ? props.theme.colors.grey5
451
+ : props.theme.colors[props.bgColor]
452
+ ? props.theme.colors[props.bgColor]
453
+ : props.bgColor} !important;
405
454
  color: ${(props) =>
406
455
  props.colorMode === 'transparent'
407
456
  ? props.theme.colors.white
408
457
  : props.disabled && props.showDisabledBackground
409
- ? props.theme.colors.black
410
- : props.theme.colors[props.fontColor]
411
- ? props.theme.colors[props.fontColor]
412
- : props.fontColor};
458
+ ? props.theme.colors.black
459
+ : props.theme.colors[props.fontColor]
460
+ ? props.theme.colors[props.fontColor]
461
+ : props.fontColor};
413
462
  ${(props) => (props.disabled ? 'pointer-events: none' : '')};
414
463
  overflow: hidden;
415
464
  & > .handle {
@@ -433,6 +482,7 @@
433
482
  isParentModal: Boolean,
434
483
  isFixedDropdownPosition: Boolean,
435
484
  isTeleport: Boolean,
485
+ dropdownMatchMaxContent: Boolean,
436
486
  }
437
487
  const SelectDropdown = styled('div', selectDropdownAttrs)`
438
488
  box-sizing: border-box;
@@ -458,8 +508,8 @@
458
508
  props.minWidth
459
509
  ? props.minWidth
460
510
  : props.optionWidth
461
- ? props.optionWidth
462
- : '100%'};
511
+ ? props.optionWidth
512
+ : '100%'};
463
513
  background-color: ${(props) =>
464
514
  props.theme.colors[props.bgColor]
465
515
  ? props.theme.colors[props.bgColor]
@@ -470,6 +520,13 @@
470
520
  : props.fontColor};
471
521
  max-height: 300px;
472
522
  overflow-y: auto;
523
+ ${(props) =>
524
+ props.dropdownMatchMaxContent
525
+ ? `
526
+ overflow-x: visible;
527
+ align-items: stretch;
528
+ `
529
+ : ''}
473
530
  & > div[data-value='${(props) => props.hoveredValue}'] {
474
531
  background-color: ${(props) =>
475
532
  props.theme.colors[props.hoveredBgColor]
@@ -483,6 +540,29 @@
483
540
  const DropdownWrapper = styled('div', DropdownAttrs)`
484
541
  position: ${(props) => (props.noRelative ? 'static' : 'relative')};
485
542
  `
543
+
544
+ const DropdownSearchShell = styled.div`
545
+ flex-shrink: 0;
546
+ align-self: stretch;
547
+ box-sizing: border-box;
548
+ width: 100%;
549
+ padding: 8px 10px 10px;
550
+ `
551
+ const DropdownSearchRow = styled.div`
552
+ display: flex;
553
+ flex-direction: row;
554
+ align-items: center;
555
+ gap: 8px;
556
+ box-sizing: border-box;
557
+ width: 100%;
558
+ border: 1px solid ${(p) => p.theme.semanticColors.grey[300]};
559
+ border-radius: 8px;
560
+ background-color: ${(p) => p.theme.colors.white};
561
+ `
562
+ const DropdownSearchInputWrap = styled.div`
563
+ flex: 1;
564
+ min-width: 0;
565
+ `
486
566
  const inputAttrs = {
487
567
  alignItems: String,
488
568
  hasLabel: Boolean,
@@ -503,6 +583,8 @@
503
583
  const DROPDOWN_HEIGHT_OFFSET = 4
504
584
  const DROPDOWN_TOP_OFFSET = 21
505
585
  const MIN_OPTION_LENGTH = 5
586
+ /** DOM `data-value` for options whose value is null/undefined (Vue otherwise drops the attribute). */
587
+ const RC_SELECT_DOM_NULL = '__rc_select_null__'
506
588
 
507
589
  const DROPDOWN_MENU_POSITIONS = {
508
590
  Automatic: 'automatic',
@@ -530,6 +612,9 @@
530
612
  Caret,
531
613
  Selector,
532
614
  InputText,
615
+ DropdownSearchShell,
616
+ DropdownSearchRow,
617
+ DropdownSearchInputWrap,
533
618
  Teleport,
534
619
  DraggableInputHandle,
535
620
  IsRequiredLabelStar,
@@ -683,6 +768,19 @@
683
768
  type: Boolean,
684
769
  default: true,
685
770
  },
771
+ selectorAllowWrap: {
772
+ type: Boolean,
773
+ default: false,
774
+ },
775
+ dropdownMatchMaxContent: {
776
+ type: Boolean,
777
+ default: false,
778
+ },
779
+ caretIcons: {
780
+ type: String,
781
+ default: 'default',
782
+ validator: (v) => ['default', 'sidebar'].includes(v),
783
+ },
686
784
  isDraggable: {
687
785
  type: Boolean,
688
786
  default: false,
@@ -702,6 +800,19 @@
702
800
  type: Number,
703
801
  default: MIN_OPTION_LENGTH,
704
802
  },
803
+ searchPlaceholder: {
804
+ type: String,
805
+ default: '',
806
+ },
807
+ searchPlacement: {
808
+ type: String,
809
+ default: 'trigger',
810
+ validator: (v) => ['trigger', 'dropdown'].includes(v),
811
+ },
812
+ dropdownSearchHideUnassigned: {
813
+ type: Boolean,
814
+ default: false,
815
+ },
705
816
  dropdownMenuPosition: {
706
817
  type: String,
707
818
  default: DROPDOWN_MENU_POSITIONS.Automatic, // options: ['automatic', bottom]
@@ -752,7 +863,6 @@
752
863
  data() {
753
864
  return {
754
865
  selectedValue: null,
755
- paddingLeft: this.isDraggable ? '30px' : this.leftPadding,
756
866
  isDropdownOpen: false,
757
867
  isActive: false,
758
868
  textSearch: '',
@@ -772,6 +882,9 @@
772
882
  }
773
883
  },
774
884
  computed: {
885
+ paddingLeft() {
886
+ return this.isDraggable ? '30px' : this.leftPadding
887
+ },
775
888
  hasErrorMessage() {
776
889
  return this.errorMessage && this.errorMessage.length > 0
777
890
  },
@@ -780,11 +893,30 @@
780
893
  },
781
894
  isSearchBarVisible() {
782
895
  return (
896
+ this.searchPlacement !== 'dropdown' &&
897
+ this.isSearchable &&
898
+ this.optionLength >= this.minOptionLength &&
899
+ this.isDropdownOpen
900
+ )
901
+ },
902
+ isDropdownSearchVisible() {
903
+ return (
904
+ this.searchPlacement === 'dropdown' &&
783
905
  this.isSearchable &&
784
906
  this.optionLength >= this.minOptionLength &&
785
907
  this.isDropdownOpen
786
908
  )
787
909
  },
910
+ dropdownSearchIconColor() {
911
+ return theme.semanticColors.teal[800]
912
+ },
913
+ hoveredValueDomAttr() {
914
+ const h = this.hoveredValue
915
+ if (h === null || h === undefined) {
916
+ return RC_SELECT_DOM_NULL
917
+ }
918
+ return h
919
+ },
788
920
  computedWidth() {
789
921
  function removePX(item) {
790
922
  return Number(item.replace('px', ''))
@@ -824,6 +956,43 @@
824
956
  isParentModal() {
825
957
  return !!this.modalRef
826
958
  },
959
+ caretOpenIconName() {
960
+ return this.caretIcons === 'sidebar' ? 'chevron_up' : 'arrow_up'
961
+ },
962
+ caretClosedIconName() {
963
+ return this.caretIcons === 'sidebar' ? 'chevron_down' : 'arrow_down'
964
+ },
965
+ caretIconPixelSize() {
966
+ if (this.caretIcons === 'sidebar') {
967
+ return '10px'
968
+ }
969
+ return this.colorMode === 'transparent' ? '8px' : '12px'
970
+ },
971
+ caretResolvedColor() {
972
+ if (this.caretIcons === 'sidebar') {
973
+ if (this.caretColor) {
974
+ return this.caretColor
975
+ }
976
+ if (this.colorMode == 'dark' || this.colorMode == 'transparent') {
977
+ return 'white'
978
+ }
979
+ return theme.semanticColors.teal[800]
980
+ }
981
+ return this.caretColor ||
982
+ this.colorMode == 'dark' ||
983
+ this.colorMode == 'transparent'
984
+ ? 'white'
985
+ : 'transparentBlack1'
986
+ },
987
+ caretHoveredColor() {
988
+ if (this.caretIcons !== 'sidebar') {
989
+ return undefined
990
+ }
991
+ if (this.colorMode == 'dark' || this.colorMode == 'transparent') {
992
+ return undefined
993
+ }
994
+ return theme.semanticColors.purple[500]
995
+ },
827
996
  },
828
997
  watch: {
829
998
  value(val) {
@@ -842,6 +1011,7 @@
842
1011
  // Deferred dropdown slot content mounts after initial open in some hosts.
843
1012
  this.$nextTick(() => this.refreshOptionLength())
844
1013
  } else {
1014
+ this.$emit('on-dropdown-close')
845
1015
  this.dropdownPosition.left = null
846
1016
  this.optionLengthCount = 0
847
1017
  if (this.animationFrameId) {
@@ -854,7 +1024,13 @@
854
1024
  }
855
1025
  if (val && this.isSearchable) {
856
1026
  this.$nextTick(() => {
857
- if (this.$refs.searchInput && !this.isMobileDevice) {
1027
+ if (this.searchPlacement === 'dropdown') {
1028
+ const ref = this.$refs.dropdownSearchInput
1029
+ const input = ref?.$el?.querySelector?.('input')
1030
+ if (input && !this.isMobileDevice) {
1031
+ input.focus()
1032
+ }
1033
+ } else if (this.$refs.searchInput && !this.isMobileDevice) {
858
1034
  this.$refs.searchInput.$el.querySelector('input').focus()
859
1035
  }
860
1036
  })
@@ -867,6 +1043,9 @@
867
1043
  setTimeout(() => {
868
1044
  this.refreshOptionLength()
869
1045
  this.getDistanceBetweenSelectAndDropdownMenu()
1046
+ if (this.dropdownMatchMaxContent) {
1047
+ this.getDropdownWidth()
1048
+ }
870
1049
  }, 100)
871
1050
  },
872
1051
  selectTopPosition() {
@@ -886,6 +1065,9 @@
886
1065
  this.calculateSelectTopPosition
887
1066
  )
888
1067
  },
1068
+ updated() {
1069
+ this.maybeReapplyDropdownSearchDomFilter()
1070
+ },
889
1071
  beforeMount() {
890
1072
  this.selectedValue = this.value
891
1073
  document.addEventListener(
@@ -938,10 +1120,12 @@
938
1120
  this.searchChange('')
939
1121
  },
940
1122
  optionSelected(e) {
941
- this.selectedValue = e
1123
+ const normalized =
1124
+ typeof e === 'string' ? this.parseOptionDataValue(e) : e
1125
+ this.selectedValue = normalized
942
1126
  this.closeDropdown()
943
1127
  this.blur()
944
- this.$emit('input-change', e)
1128
+ this.$emit('input-change', normalized)
945
1129
  },
946
1130
  optionHovered: debounce(function (e) {
947
1131
  this.hoveredValue = e
@@ -965,15 +1149,63 @@
965
1149
  searchChange(value) {
966
1150
  this.textSearch = value
967
1151
  this.$emit('search-change', value)
968
- const dropdownChildren = [...this.$refs.dropdown.$el.children]
969
- dropdownChildren.forEach((el) => {
970
- if (!el.textContent.toLowerCase().includes(value.toLowerCase())) {
971
- el.style.display = 'none'
1152
+ this.applyDropdownSearchDomFilter()
1153
+ if (this.dropdownMatchMaxContent && this.isDropdownOpen) {
1154
+ this.$nextTick(() => this.getDropdownWidth())
1155
+ }
1156
+ },
1157
+ maybeReapplyDropdownSearchDomFilter() {
1158
+ if (
1159
+ !this.dropdownSearchHideUnassigned ||
1160
+ this.searchPlacement !== 'dropdown' ||
1161
+ !this.isSearchable ||
1162
+ !this.isDropdownOpen ||
1163
+ !(this.textSearch || '').trim()
1164
+ ) {
1165
+ return
1166
+ }
1167
+ this.applyDropdownSearchDomFilter()
1168
+ },
1169
+ applyDropdownSearchDomFilter() {
1170
+ const root = this.$refs.dropdown?.$el
1171
+ if (!root) return
1172
+ const raw = this.textSearch ?? ''
1173
+ const qTrim = raw.trim()
1174
+ const nodes = [...root.querySelectorAll('[data-value]')]
1175
+ if (!qTrim) {
1176
+ nodes.forEach((el) => {
1177
+ el.style.display = ''
1178
+ })
1179
+ return
1180
+ }
1181
+ const q = raw.toLowerCase()
1182
+ const hideUnassignedWhenOthers =
1183
+ this.dropdownSearchHideUnassigned &&
1184
+ this.searchPlacement === 'dropdown'
972
1185
 
973
- return
1186
+ let assignedMatchCount = 0
1187
+ if (hideUnassignedWhenOthers) {
1188
+ for (const el of nodes) {
1189
+ if (
1190
+ !this.isUnassignedOptionRow(el) &&
1191
+ el.textContent.toLowerCase().includes(q)
1192
+ ) {
1193
+ assignedMatchCount++
1194
+ }
974
1195
  }
1196
+ }
975
1197
 
976
- el.style.display = 'inherit'
1198
+ nodes.forEach((el) => {
1199
+ let show = el.textContent.toLowerCase().includes(q)
1200
+ if (
1201
+ show &&
1202
+ hideUnassignedWhenOthers &&
1203
+ this.isUnassignedOptionRow(el) &&
1204
+ assignedMatchCount > 0
1205
+ ) {
1206
+ show = false
1207
+ }
1208
+ el.style.display = show ? '' : 'none'
977
1209
  })
978
1210
  },
979
1211
  refreshOptionLength() {
@@ -991,6 +1223,36 @@
991
1223
  ? dropdownEl.children[0].childElementCount
992
1224
  : 0
993
1225
  },
1226
+ parseOptionDataValue(attr) {
1227
+ if (attr == null) return null
1228
+ const s = String(attr)
1229
+ if (
1230
+ s === RC_SELECT_DOM_NULL ||
1231
+ s === '' ||
1232
+ s === 'null' ||
1233
+ s === 'undefined'
1234
+ ) {
1235
+ return null
1236
+ }
1237
+ return attr
1238
+ },
1239
+ optionDataValuesEqual(domAttr, internalVal) {
1240
+ const parsed = this.parseOptionDataValue(domAttr)
1241
+ if (internalVal === parsed) return true
1242
+ if (internalVal == null && parsed == null) return true
1243
+ return String(internalVal) === String(parsed)
1244
+ },
1245
+ isUnassignedOptionRow(el) {
1246
+ if (!el?.getAttribute) return false
1247
+ const v = el.getAttribute('data-value')
1248
+ return (
1249
+ v === RC_SELECT_DOM_NULL ||
1250
+ v == null ||
1251
+ v === '' ||
1252
+ v === 'null' ||
1253
+ v === 'undefined'
1254
+ )
1255
+ },
994
1256
  clickOutside(event) {
995
1257
  const dropdownRef = this.$refs.dropdown
996
1258
  if (!this.isClickOutsideActive) return
@@ -1014,10 +1276,20 @@
1014
1276
  } else if (e.key == 'ArrowUp') {
1015
1277
  this.onArrowPress(-1)
1016
1278
  } else if (e.key == 'Enter') {
1017
- const optionHoveredComponent = [...this.$refs.dropdown.$el.children][
1018
- (this.hoveredIndex - 1 + this.optionLength) % this.optionLength
1019
- ]
1020
- this.optionSelected(optionHoveredComponent.$el.dataset.value)
1279
+ const root = this.$refs.dropdown?.$el
1280
+ if (!root) return
1281
+ const opts = [...root.querySelectorAll('[data-value]')].filter(
1282
+ (el) => getComputedStyle(el).display !== 'none'
1283
+ )
1284
+ if (!opts.length) return
1285
+ let idx = opts.findIndex((el) =>
1286
+ this.optionDataValuesEqual(
1287
+ el.getAttribute('data-value'),
1288
+ this.hoveredValue
1289
+ )
1290
+ )
1291
+ if (idx < 0) idx = 0
1292
+ this.optionSelected(opts[idx].getAttribute('data-value'))
1021
1293
  }
1022
1294
  },
1023
1295
  // If some part of the dropdown menu is outside viewport of the bottom of the screen,
@@ -1108,24 +1380,79 @@
1108
1380
  async getDropdownWidth() {
1109
1381
  if (!this.$refs.select) return
1110
1382
  await this.$nextTick()
1111
- this.dropdownWidth = `${this.$refs.select.$el.clientWidth}px`
1383
+ if (
1384
+ this.dropdownMatchMaxContent &&
1385
+ this.isDropdownOpen &&
1386
+ this.$refs.dropdown?.$el
1387
+ ) {
1388
+ await this.measureDropdownMaxContentWidth()
1389
+ } else {
1390
+ this.dropdownWidth = `${this.$refs.select.$el.clientWidth}px`
1391
+ }
1392
+ },
1393
+ async measureDropdownMaxContentWidth() {
1394
+ const sel = this.$refs.select?.$el
1395
+ const live = this.$refs.dropdown?.$el
1396
+ if (!sel || !live) return
1397
+ const selectW = Math.ceil(sel.getBoundingClientRect().width)
1398
+ const ghost = live.cloneNode(true)
1399
+ ghost.style.cssText = [
1400
+ 'position:fixed',
1401
+ 'left:-99999px',
1402
+ 'top:0',
1403
+ 'visibility:hidden',
1404
+ 'pointer-events:none',
1405
+ 'display:flex',
1406
+ 'flex-direction:column',
1407
+ 'align-items:stretch',
1408
+ 'width:min-content',
1409
+ 'min-width:min-content',
1410
+ 'max-height:none',
1411
+ 'overflow:visible',
1412
+ 'box-sizing:border-box',
1413
+ ].join(';')
1414
+ document.body.appendChild(ghost)
1415
+ await this.$nextTick()
1416
+ const measured = Math.ceil(ghost.scrollWidth)
1417
+ document.body.removeChild(ghost)
1418
+ this.dropdownWidth = `${Math.max(selectW, measured)}px`
1112
1419
  },
1113
1420
  onArrowPress(dir) {
1114
- let newHoveredElem
1115
- const currentHoveredElem = this.$refs.dropdown.$el.querySelector(
1116
- `[data-value="${this.hoveredValue}"]`
1421
+ const root = this.$refs.dropdown?.$el
1422
+ if (!root) return
1423
+ const opts = [...root.querySelectorAll('[data-value]')].filter(
1424
+ (el) => getComputedStyle(el).display !== 'none'
1117
1425
  )
1118
- if (currentHoveredElem) {
1119
- if (dir > 0) {
1120
- newHoveredElem = currentHoveredElem.nextElementSibling
1121
- } else {
1122
- newHoveredElem = currentHoveredElem.previousElementSibling
1123
- }
1124
- if (newHoveredElem) {
1125
- this.hoveredValue = newHoveredElem.getAttribute('data-value')
1126
- const topPos = newHoveredElem.offsetTop
1127
- this.$refs.dropdown.$el.scrollTop = topPos
1128
- }
1426
+ if (!opts.length) return
1427
+ let idx = opts.findIndex((el) =>
1428
+ this.optionDataValuesEqual(
1429
+ el.getAttribute('data-value'),
1430
+ this.hoveredValue
1431
+ )
1432
+ )
1433
+ if (idx < 0) {
1434
+ const newHoveredElem = dir > 0 ? opts[0] : opts[opts.length - 1]
1435
+ this.hoveredValue = this.parseOptionDataValue(
1436
+ newHoveredElem.getAttribute('data-value')
1437
+ )
1438
+ this.hoveredIndex =
1439
+ opts.findIndex((el) =>
1440
+ this.optionDataValuesEqual(
1441
+ el.getAttribute('data-value'),
1442
+ this.hoveredValue
1443
+ )
1444
+ ) + 1
1445
+ root.scrollTop = newHoveredElem.offsetTop
1446
+ return
1447
+ }
1448
+ const nextIdx = (idx + dir + opts.length) % opts.length
1449
+ const newHoveredElem = opts[nextIdx]
1450
+ if (newHoveredElem) {
1451
+ this.hoveredValue = this.parseOptionDataValue(
1452
+ newHoveredElem.getAttribute('data-value')
1453
+ )
1454
+ this.hoveredIndex = nextIdx + 1
1455
+ root.scrollTop = newHoveredElem.offsetTop
1129
1456
  }
1130
1457
  },
1131
1458
  getDistanceBetweenSelectAndDropdownMenu() {