@eturnity/eturnity_reusable_components 9.13.5 → 9.16.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/dist/index.es3.js CHANGED
@@ -788,6 +788,31 @@ const theme = (() => {
788
788
  iconWidth: "14px"
789
789
  }
790
790
  }
791
+ },
792
+ chartGradients: {
793
+ simple: {
794
+ from: semanticColors.purple[500],
795
+ to: semanticColors.purple[400]
796
+ },
797
+ stacked: [{
798
+ from: "#F5EDFF",
799
+ to: "#DEC5FF"
800
+ }, {
801
+ from: "#CAA2FF",
802
+ to: "#F2E8FF"
803
+ }, {
804
+ from: "#B987FC",
805
+ to: "#904AEF"
806
+ }, {
807
+ from: "#8B40F2",
808
+ to: "#4A1394"
809
+ }, {
810
+ from: "#6A05F2",
811
+ to: "#4905A5"
812
+ }, {
813
+ from: "#5402C3",
814
+ to: "#2B0362"
815
+ }]
791
816
  }
792
817
  };
793
818
  })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eturnity/eturnity_reusable_components",
3
- "version": "9.13.5",
3
+ "version": "9.16.0",
4
4
  "files": [
5
5
  "dist",
6
6
  "src"
@@ -790,6 +790,20 @@ const theme = (() => {
790
790
  },
791
791
  },
792
792
  },
793
+ chartGradients: {
794
+ simple: {
795
+ from: semanticColors.purple[500],
796
+ to: semanticColors.purple[400],
797
+ },
798
+ stacked: [
799
+ { from: '#F5EDFF', to: '#DEC5FF' },
800
+ { from: '#CAA2FF', to: '#F2E8FF' },
801
+ { from: '#B987FC', to: '#904AEF' },
802
+ { from: '#8B40F2', to: '#4A1394' },
803
+ { from: '#6A05F2', to: '#4905A5' },
804
+ { from: '#5402C3', to: '#2B0362' },
805
+ ],
806
+ },
793
807
  }
794
808
  })()
795
809
 
@@ -1,35 +1,6 @@
1
1
  <template>
2
2
  <ContainerWrapper @click="$emit('on-container-click')">
3
3
  <UpperContainer v-if="filterViews && filterViews.length">
4
- <ViewContainer :max-width="activeView.length">
5
- <SelectComponent
6
- align-items="vertical"
7
- :data-id="componentName + '_filter_view_select'"
8
- :data-qa-id="componentName + '_filter_view_select'"
9
- font-size="13px"
10
- :label="$gettext('active_filter')"
11
- select-height="36px"
12
- select-width="100%"
13
- @click.stop
14
- >
15
- <template #selector>
16
- <OptionTitle>
17
- {{ activeView }}
18
- </OptionTitle>
19
- </template>
20
- <template #dropdown>
21
- <Option
22
- v-for="(item, idx) in filterViews"
23
- :key="idx + '_view'"
24
- :value="item.name"
25
- @click="$emit('on-view-select', item)"
26
- >
27
- {{ item.name }}
28
- <DeleteIcon @click.stop="$emit('on-view-delete', item)" />
29
- </Option>
30
- </template>
31
- </SelectComponent>
32
- </ViewContainer>
33
4
  <ResetButton
34
5
  :data-id="componentName + '_reset_filters_button'"
35
6
  :data-qa-id="componentName + '_reset_filters_button'"
@@ -110,10 +81,10 @@
110
81
  :is-searchable="filter.choices.length > 7"
111
82
  :label="filter.label"
112
83
  :label-data-id="filter.dataId"
84
+ lazy-dropdown-content
113
85
  :min-option-length="1"
114
86
  select-height="36px"
115
87
  select-width="100%"
116
- :should-use-teleport="false"
117
88
  >
118
89
  <template #selector>
119
90
  <OptionTitle> {{ filter.selectedText }} </OptionTitle>
@@ -261,9 +232,11 @@
261
232
  :enable-time-picker="false"
262
233
  format="yyyy-MM-dd"
263
234
  :locale="getDatePickerLanguage()"
235
+ menu-class-name="filter-settings-dp-menu"
264
236
  model-type="format"
265
237
  :model-value="filter.range.start"
266
238
  :placeholder="$gettext('Date from')"
239
+ teleport="body"
267
240
  text-input
268
241
  @close="onDatepickerBlur()"
269
242
  @focus="onDatepickerFocus(filter.range)"
@@ -289,9 +262,11 @@
289
262
  :enable-time-picker="false"
290
263
  format="yyyy-MM-dd"
291
264
  :locale="getDatePickerLanguage()"
265
+ menu-class-name="filter-settings-dp-menu"
292
266
  model-type="format"
293
267
  :model-value="filter.range.end"
294
268
  :placeholder="$gettext('Date to')"
269
+ teleport="body"
295
270
  text-input
296
271
  @close="onDatepickerBlur()"
297
272
  @focus="onDatepickerFocus(filter.range)"
@@ -315,9 +290,9 @@
315
290
  font-size="13px"
316
291
  :is-searchable="filter.choices.length > 7"
317
292
  :label="filter.label"
293
+ lazy-dropdown-content
318
294
  select-height="36px"
319
295
  select-width="100%"
320
- :should-use-teleport="false"
321
296
  >
322
297
  <template #selector="{ selectedValue }">
323
298
  <OptionTitle>
@@ -560,7 +535,6 @@
560
535
  display: inline-flex;
561
536
  gap: 16px;
562
537
  width: max-content;
563
- margin-top: 20px;
564
538
  align-self: center;
565
539
  font-size: 13px;
566
540
  color: ${(props) => props.theme.colors.primary};
@@ -745,7 +719,11 @@
745
719
  return type === 'boolean'
746
720
  },
747
721
  isMultipleSelector(type) {
748
- return type === 'multi_select_integer' || type === 'multi_select_string'
722
+ return (
723
+ type === 'multi_select_integer' ||
724
+ type === 'multi_select_string' ||
725
+ type === 'multiple_choice'
726
+ )
749
727
  },
750
728
  isRangeSelector(type) {
751
729
  return type === 'integer_range' || type === 'number_range'
@@ -754,8 +732,16 @@
754
732
  return type === 'integer_range'
755
733
  },
756
734
  isDateSelector(type) {
757
- return type === 'datetime'
735
+ return type === 'datetime' || type === 'date_range'
758
736
  },
759
737
  },
760
738
  }
761
739
  </script>
740
+
741
+ <!-- Teleported menu is under body; unscoped. Expert main.scss also sets z-index so linked package builds pick it up. -->
742
+ <style>
743
+ /* Must exceed app modal stacks (often ~1e7 in eturnity_expert); default dp menu is 99999. */
744
+ body .dp__menu.filter-settings-dp-menu {
745
+ z-index: 10000050 !important;
746
+ }
747
+ </style>
@@ -8,7 +8,8 @@
8
8
  @on-toggle="onToggleDropdown()"
9
9
  />
10
10
  <FilterSettings
11
- v-if="isDropdownOpen"
11
+ v-if="filterSettingsInDOM"
12
+ v-show="isDropdownOpen"
12
13
  :active-language="activeLanguage"
13
14
  :active-view="activeView"
14
15
  :button-text="buttonText"
@@ -82,6 +83,12 @@
82
83
  default: null,
83
84
  required: false,
84
85
  },
86
+ /** Host store slice (e.g. getCurrentFilterData) so the badge tracks live `filters`. */
87
+ currentFilterData: {
88
+ type: Object,
89
+ default: null,
90
+ required: false,
91
+ },
85
92
  componentName: {
86
93
  type: String,
87
94
  required: false,
@@ -92,14 +99,62 @@
92
99
  isDropdownOpen: false,
93
100
  activeFilter: null,
94
101
  preventOutsideClick: false,
102
+ /** Once true, FilterSettings stays mounted; toggled with v-show for instant reopen. */
103
+ filterSettingsInDOM: false,
104
+ _unmounted: false,
105
+ _idlePrewarmId: null,
106
+ _prewarmTimerId: null,
95
107
  }
96
108
  },
97
109
  computed: {
110
+ /** Count applied data filters for the settings-trigger badge (not column visibility). */
98
111
  numberOfFiltersApplied() {
99
- const filterColumns = this.activeFilterView?.columns?.filter(
100
- (column) => column.custom_view_filter !== null
101
- )
102
- return filterColumns?.length > 0 ? filterColumns.length : 0
112
+ const view = this.activeFilterView
113
+ const live = this.currentFilterData
114
+ const resolvedFilters =
115
+ live != null && Array.isArray(live.filters)
116
+ ? live.filters
117
+ : view && Array.isArray(view.filters)
118
+ ? view.filters
119
+ : []
120
+
121
+ const countFromFilterList = (filters) => {
122
+ if (!Array.isArray(filters) || !filters.length) {
123
+ return 0
124
+ }
125
+ const first = filters[0]
126
+ if (
127
+ first &&
128
+ typeof first === 'object' &&
129
+ 'custom_view_filter' in first
130
+ ) {
131
+ return filters.filter((f) => f && f.custom_view_filter).length
132
+ }
133
+ // Flat rows (library / tariffs): multi-select emits one row per value —
134
+ // badge = number of filter *fields* narrowed, not row count.
135
+ if (first && typeof first === 'object' && 'field' in first) {
136
+ return new Set(filters.map((f) => f.field).filter(Boolean)).size
137
+ }
138
+ return filters.length
139
+ }
140
+
141
+ const filtersCount = countFromFilterList(resolvedFilters)
142
+
143
+ if (!view) {
144
+ return filtersCount
145
+ }
146
+
147
+ const cols = view.columns
148
+ if (Array.isArray(cols) && cols.length && typeof cols[0] === 'string') {
149
+ return filtersCount
150
+ }
151
+ if (Array.isArray(cols)) {
152
+ const n = cols.filter(
153
+ (c) => c && typeof c === 'object' && c.custom_view_filter
154
+ ).length
155
+ return n > 0 ? n : filtersCount
156
+ }
157
+ return filtersCount
103
158
  },
104
159
  },
105
160
  watch: {
@@ -111,13 +166,49 @@
111
166
  },
112
167
  mounted() {
113
168
  document.addEventListener('click', this.clickOutside)
169
+ // Build the heavy panel during idle so the first click often only toggles v-show.
170
+ const prewarm = () => {
171
+ if (this._unmounted || this.filterSettingsInDOM) {
172
+ return
173
+ }
174
+ this.filterSettingsInDOM = true
175
+ }
176
+ if (typeof requestIdleCallback !== 'undefined') {
177
+ this._idlePrewarmId = requestIdleCallback(prewarm, { timeout: 2000 })
178
+ } else {
179
+ this._prewarmTimerId = setTimeout(prewarm, 600)
180
+ }
114
181
  },
115
182
  beforeUnmount() {
183
+ this._unmounted = true
184
+ if (
185
+ this._idlePrewarmId != null &&
186
+ typeof cancelIdleCallback !== 'undefined'
187
+ ) {
188
+ cancelIdleCallback(this._idlePrewarmId)
189
+ }
190
+ if (this._prewarmTimerId != null) {
191
+ clearTimeout(this._prewarmTimerId)
192
+ }
116
193
  document.removeEventListener('click', this.clickOutside)
117
194
  },
118
195
  methods: {
196
+ emitFilterPanelOpen() {
197
+ this.$nextTick(() => {
198
+ if (this.isDropdownOpen) {
199
+ this.$emit('on-filter-panel-open')
200
+ }
201
+ })
202
+ },
119
203
  onToggleDropdown() {
204
+ const opening = !this.isDropdownOpen
120
205
  this.isDropdownOpen = !this.isDropdownOpen
206
+ if (opening) {
207
+ if (!this.filterSettingsInDOM) {
208
+ this.filterSettingsInDOM = true
209
+ }
210
+ this.emitFilterPanelOpen()
211
+ }
121
212
  },
122
213
  onContainerClick() {
123
214
  // due to newer versions of Chrome (121), contains() is not always working.
@@ -252,7 +252,10 @@
252
252
  },
253
253
  computed: {
254
254
  hasLabel() {
255
- return !!this.label && !!this.label.length
255
+ if (this.label == null || this.label === '') {
256
+ return false
257
+ }
258
+ return String(this.label).length > 0
256
259
  },
257
260
  },
258
261
  methods: {
@@ -198,7 +198,12 @@
198
198
  @option-hovered="optionHovered"
199
199
  @option-selected="optionSelected"
200
200
  >
201
- <slot name="dropdown"></slot>
201
+ <!-- When lazy, skip building option/checkbox nodes until the user opens
202
+ this select (filter settings can have thousands of options otherwise). -->
203
+ <slot
204
+ v-if="!lazyDropdownContent || isDropdownOpen"
205
+ name="dropdown"
206
+ ></slot>
202
207
  </SelectDropdown>
203
208
  </Component>
204
209
  </DropdownWrapper>
@@ -726,6 +731,12 @@
726
731
  required: false,
727
732
  default: true,
728
733
  },
734
+ /** If true, dropdown slot content is only rendered while the menu is open. */
735
+ lazyDropdownContent: {
736
+ type: Boolean,
737
+ required: false,
738
+ default: false,
739
+ },
729
740
  },
730
741
  setup() {
731
742
  const modalRef = inject('modalRef', null)
@@ -10,7 +10,7 @@
10
10
  >
11
11
  <ArrowIconContainer>
12
12
  <RCIcon
13
- :color="getTheme.colors.brightBlue"
13
+ :color="getTheme.colors.primary"
14
14
  name="arrow_left"
15
15
  size="12px"
16
16
  />
@@ -73,7 +73,7 @@
73
73
  <ArrowText>{{ $gettext('forward') }}</ArrowText>
74
74
  <ArrowIconContainer>
75
75
  <RCIcon
76
- :color="getTheme.colors.brightBlue"
76
+ :color="getTheme.colors.primary"
77
77
  name="arrow_right"
78
78
  size="12px"
79
79
  />
@@ -88,7 +88,7 @@
88
88
  import theme from '@/assets/theme.js'
89
89
 
90
90
  const PaginationWrapper = styled.nav`
91
- color: ${(props) => props.theme.colors.brightBlue};
91
+ color: ${(props) => props.theme.colors.primary};
92
92
  font-size: 13px;
93
93
  display: flex;
94
94
  flex-wrap: wrap;
@@ -103,17 +103,23 @@
103
103
  border-radius: 3px;
104
104
  white-space: nowrap;
105
105
  cursor: pointer;
106
- color: ${(props) => props.theme.colors.brightBlue};
106
+ height: 100%;
107
+ align-items: center;
108
+ color: ${(props) => props.theme.colors.primary};
109
+ &:hover {
110
+ color: ${(props) => props.theme.semanticColors.purple[500]};
111
+ background-color: ${(props) => props.theme.semanticColors.purple[100]};
112
+ }
107
113
 
108
114
  &.active {
109
- color: ${(props) => props.theme.colors.white};
110
- background-color: ${(props) => props.theme.colors.brightBlue};
115
+ color: ${(props) => props.theme.semanticColors.purple[500]};
116
+ background-color: ${(props) => props.theme.semanticColors.purple[50]};
111
117
  padding: 7px 12px;
112
118
  border-radius: 4px;
113
119
  }
114
120
  `
115
121
  const ArrowText = styled.div`
116
- color: ${(props) => props.theme.colors.brightBlue};
122
+ color: ${(props) => props.theme.colors.primary};
117
123
  `
118
124
  const ArrowIconContainer = styled.div`
119
125
  margin: 0 10px;
@@ -54,7 +54,14 @@
54
54
  v-for="(item, index) in options"
55
55
  :key="item.value"
56
56
  :color-theme="colorTheme"
57
- :data-id="item.dataId"
57
+ :data-id="
58
+ item.dataId ||
59
+ 'three_dots_option_item_' + rowIndex + '_' + item.value
60
+ "
61
+ :data-qa-id="
62
+ item.dataId ||
63
+ 'three_dots_option_item_' + rowIndex + '_' + item.value
64
+ "
58
65
  :is-disabled="item.disabled"
59
66
  tabindex="0"
60
67
  :title="item.title"
@@ -359,6 +366,11 @@
359
366
  default: '',
360
367
  type: String,
361
368
  },
369
+ rowIndex: {
370
+ required: false,
371
+ default: 0,
372
+ type: Number,
373
+ },
362
374
  },
363
375
  data() {
364
376
  return {