@webitel/ui-sdk 26.8.86 → 26.8.88

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 (68) hide show
  1. package/dist/{clients-CkhN_A5L.js → clients-cfVUVngv.js} +317 -289
  2. package/dist/{components-CV6dUJR6.js → components-BSM66otL.js} +1065 -1853
  3. package/dist/{composables-BtWSAI5D.js → composables-OP5kSvlh.js} +3 -3
  4. package/dist/debounce-A4B4PTkL.js +5 -0
  5. package/dist/{deep-copy-CTgLtgMO.js → deep-copy-7O5ej7vT.js} +1 -1
  6. package/dist/dist-DB8lC4KJ.js +112 -0
  7. package/dist/{dist-CAOIU8xq.js → dist-DJZOILzq.js} +1 -1
  8. package/dist/{genClient-HHiOUj56.js → genClient-5JkKKac8.js} +6 -7
  9. package/dist/ui-sdk.css +1 -1
  10. package/dist/ui-sdk.js +2 -2
  11. package/dist/ui-sdk.umd.cjs +675 -675
  12. package/dist/{useValidation-CEed9cxY.js → useValidation-Dmm2xos_.js} +1 -1
  13. package/dist/{useWtTable-DLWucXPx.js → useWtTable-CUpN0LZ5.js} +1 -1
  14. package/dist/{vue-i18n-Db_oYfO8.js → vue-i18n-B88GcHzn.js} +215 -215
  15. package/dist/{vue-router-Dwey8MMv.js → vue-router-CobN9tLi.js} +1 -1
  16. package/dist/{wt-action-bar-DtAECixF.js → wt-action-bar-BpuER6TA.js} +1 -1
  17. package/dist/{wt-chat-emoji-CPKosJ8P.js → wt-chat-emoji-zW3fKPW8.js} +1 -1
  18. package/dist/{wt-datepicker-BgjjbOMX.js → wt-datepicker-BS3przUJ.js} +2 -2
  19. package/dist/{wt-display-chip-items-CK-cnYdD.js → wt-display-chip-items-D-lzTp0d.js} +1 -1
  20. package/dist/{wt-expansion-card-BT586Mul.js → wt-expansion-card-8YzHs8F1.js} +1 -1
  21. package/dist/{wt-galleria-DMCgiiju.js → wt-galleria-CmaNfz7E.js} +2 -2
  22. package/dist/{wt-icon-action-DJzm0kEk.js → wt-icon-action-B8vuj9cC.js} +1 -1
  23. package/dist/{wt-pagination-G2ZGU4Pi.js → wt-pagination-DLRMTDRV.js} +1 -1
  24. package/dist/{wt-player-CFrKSilF.js → wt-player-CqjIR3Q-.js} +2 -2
  25. package/dist/{wt-popover-BCYPu5NC.js → wt-popover-DuZFw3bg.js} +1 -1
  26. package/dist/{wt-search-bar-DosqJLXT.js → wt-search-bar-CE4WhuJ6.js} +2 -2
  27. package/dist/{wt-selection-popup-CjYw4ppb.js → wt-selection-popup-Bd4EuLl-.js} +1 -1
  28. package/dist/{wt-send-message-popup-C4XEqrKj.js → wt-send-message-popup-CiswBW69.js} +287 -306
  29. package/dist/{wt-slider-BPzRTeYU.js → wt-slider-Co6hZQu_.js} +1 -1
  30. package/dist/{wt-start-page-BFQExFUj.js → wt-start-page-Bp7b0ir-.js} +1 -1
  31. package/dist/{wt-status-select-Dnv9tj7o.js → wt-status-select-z4n_FL2z.js} +1 -1
  32. package/dist/{wt-table-PR_Kx7Sv.js → wt-table-C-DMIrud.js} +2 -2
  33. package/dist/{wt-table-column-select-CmbjLO9b.js → wt-table-column-select-BtFhZKPT.js} +26 -19
  34. package/dist/{wt-timepicker-B4MOaSEZ.js → wt-timepicker-D-tnO-gX.js} +2 -2
  35. package/dist/{wt-tree-BYk31Ees.js → wt-tree-BH2o2iCt.js} +843 -844
  36. package/dist/{wt-tree-table-ZJSw_3hx.js → wt-tree-table-D4nCNUq_.js} +1 -1
  37. package/dist/{wt-type-extension-value-input-CuxKUgu-.js → wt-type-extension-value-input-DTjVuaQe.js} +3 -3
  38. package/dist/{wt-vidstack-player-O1Xcyw-A.js → wt-vidstack-player-BUFABzVR.js} +997 -991
  39. package/package.json +6 -2
  40. package/src/api/transformers/starToSearch/starToSearch.transformer.js +1 -1
  41. package/src/components/wt-table-column-select/wt-table-column-select.vue +21 -2
  42. package/src/components/wt-textarea/wt-textarea.vue +12 -1
  43. package/src/components/wt-vidstack-player/wt-vidstack-player.vue +16 -0
  44. package/src/composables/useCachedItemInstanceName/useCachedItemInstanceName.js +1 -1
  45. package/src/modules/AuditForm/components/audit-form.vue +1 -1
  46. package/src/modules/CardStoreModule/store/CardStoreModule.js +1 -1
  47. package/src/modules/TableVariableColumnSelect/components/__tests__/wt-table-variable-column-select.spec.ts +101 -0
  48. package/src/modules/TableVariableColumnSelect/components/wt-table-variable-column-select.vue +278 -0
  49. package/src/modules/TableVariableColumnSelect/composable/__tests__/useTableVariableHeaders.spec.ts +129 -0
  50. package/src/modules/TableVariableColumnSelect/composable/useTableVariableHeaders.ts +59 -0
  51. package/src/modules/TableVariableColumnSelect/index.ts +7 -0
  52. package/src/modules/Userinfo/mappings/mappings.ts +1 -1
  53. package/src/modules/Userinfo/stores/userinfoStore.ts +1 -1
  54. package/src/scripts/debounce.js +1 -1
  55. package/src/scripts/logger.js +1 -1
  56. package/src/scripts/updateObject.js +1 -2
  57. package/src/store/BaseStoreModules/BaseStoreModule.js +1 -1
  58. package/src/store/new/helpers/createStoreModule.js +1 -1
  59. package/src/store/new/modules/baseStoreModule/baseStoreModule.js +1 -1
  60. package/src/store/new/modules/cardStoreModule/cardStoreModule.js +1 -1
  61. package/types/.tsbuildinfo +1 -1
  62. package/types/components/wt-textarea/wt-textarea.vue.d.ts +11 -0
  63. package/types/modules/TableVariableColumnSelect/components/wt-table-variable-column-select.vue.d.ts +17 -0
  64. package/types/modules/TableVariableColumnSelect/composable/useTableVariableHeaders.d.ts +14 -0
  65. package/types/modules/TableVariableColumnSelect/index.d.ts +2 -0
  66. package/dist/debounce-Bp3jy7S5.js +0 -133
  67. package/dist/dist-Crpfx4FA.js +0 -87
  68. package/dist/rolldown-runtime-BEcgMyyN.js +0 -27
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webitel/ui-sdk",
3
- "version": "26.8.86",
3
+ "version": "26.8.88",
4
4
  "private": false,
5
5
  "scripts": {
6
6
  "dev": "npm run docs:dev",
@@ -131,7 +131,7 @@
131
131
  "@tsconfig/node22": "^22.0.0",
132
132
  "@types/deep-equal": "^1.0.4",
133
133
  "@types/file-saver-es": "^2.0.3",
134
- "@types/lodash": "^4.17.24",
134
+ "@types/lodash-es": "^4.17.12",
135
135
  "@types/node": "^22.13.10",
136
136
  "@vitejs/plugin-vue": "^6.0.3",
137
137
  "@vitest/coverage-v8": "^4.1.3",
@@ -373,6 +373,10 @@
373
373
  "types": "./types/modules/SaveCopyPopup/index.d.ts",
374
374
  "import": "./src/modules/SaveCopyPopup/index.ts"
375
375
  },
376
+ "./modules/TableVariableColumnSelect": {
377
+ "types": "./types/modules/TableVariableColumnSelect/index.d.ts",
378
+ "import": "./src/modules/TableVariableColumnSelect/index.ts"
379
+ },
376
380
  "./plugins*": {
377
381
  "types": "./types/plugins*",
378
382
  "import": "./src/plugins*"
@@ -1,5 +1,5 @@
1
1
  import deepcopy from 'deep-copy';
2
- import get from 'lodash/get.js';
2
+ import { get } from 'lodash-es';
3
3
 
4
4
  import updateObject from '../../../scripts/updateObject.js';
5
5
 
@@ -35,7 +35,10 @@
35
35
  </wt-input-text>
36
36
  <div
37
37
  class="wt-table-column-select__popup-list-wrap"
38
- :class="{'wt-table-column-select__popup-list-wrap__height-fixed': enableSearch}"
38
+ :class="{
39
+ 'wt-table-column-select__popup-list-wrap__height-fixed': enableSearch,
40
+ 'wt-table-column-select__popup-list-wrap--empty': !changeableDraft.length,
41
+ }"
39
42
  >
40
43
  <ul
41
44
  v-if="changeableDraft.length"
@@ -63,11 +66,15 @@
63
66
  v-else
64
67
  :image="darkMode ? EmptyImageDark : EmptyImageLight"
65
68
  :text="$t('webitelUI.empty.text.filters')"
69
+ size="sm"
66
70
  />
67
71
  </div>
68
72
  </template>
69
73
  <template #actions>
70
- <wt-button @click="setShownColumns">
74
+ <wt-button
75
+ :disabled="!hasShownColumns"
76
+ @click="setShownColumns"
77
+ >
71
78
  {{ $t('reusable.add') }}
72
79
  </wt-button>
73
80
  <wt-button
@@ -147,6 +154,10 @@ const changeableDraft = computed(() =>
147
154
  ),
148
155
  );
149
156
 
157
+ const hasShownColumns = computed(() =>
158
+ draft.value.some((header) => header.show),
159
+ );
160
+
150
161
  function fillHeadersDraft() {
151
162
  draft.value = deepCopy(props.headers);
152
163
  }
@@ -157,6 +168,7 @@ function openPopup() {
157
168
 
158
169
  function close() {
159
170
  isColumnSelectPopup.value = false;
171
+ search.value = '';
160
172
  }
161
173
 
162
174
  function setShownColumns() {
@@ -190,6 +202,13 @@ watch(isColumnSelectPopup, () => {
190
202
  height: 400px;
191
203
  }
192
204
 
205
+ .wt-table-column-select__popup-list-wrap--empty {
206
+ display: flex;
207
+ align-items: center;
208
+ justify-content: center;
209
+ width: 100%;
210
+ }
211
+
193
212
  .wt-table-column-select__popup-list {
194
213
  max-width: 100%;
195
214
  display: flex;
@@ -122,6 +122,16 @@ interface Props extends /* @vue-ignore */ TextareaProps {
122
122
  * @default false
123
123
  */
124
124
  autoresize?: boolean;
125
+ /**
126
+ * @author PolinaSukhorukova-webitel
127
+ *
128
+ * [WTEL-10388](https://webitel.atlassian.net/browse/WTEL-10388)
129
+ * Decouples submit-on-Enter from `autoresize` so a consumer can keep
130
+ * auto-height while Enter inserts a newline (e.g. mobile chat).
131
+ * @type {boolean}
132
+ * @default true
133
+ */
134
+ submitOnEnter?: boolean;
125
135
  /**
126
136
  * Validation rules
127
137
  */
@@ -144,6 +154,7 @@ const props = withDefaults(defineProps<Props>(), {
144
154
  rows: 1,
145
155
  labelProps: undefined,
146
156
  autoresize: false,
157
+ submitOnEnter: true,
147
158
  v: undefined,
148
159
  customValidators: () => [],
149
160
  });
@@ -181,7 +192,7 @@ const handleKeypress = (event: KeyboardEvent) => {
181
192
  emit('keydown', event);
182
193
  if (!props.autoresize) return;
183
194
 
184
- if (event.key === 'Enter' && !event.shiftKey) {
195
+ if (props.submitOnEnter && event.key === 'Enter' && !event.shiftKey) {
185
196
  emit('enter');
186
197
  event.preventDefault();
187
198
  }
@@ -14,6 +14,7 @@
14
14
  playsinline
15
15
  @close="emit('close')"
16
16
  @can-play="onCanPlay"
17
+ @provider-change="onProviderChange"
17
18
  >
18
19
  <media-provider class="wt-vidstack-player__provider"></media-provider>
19
20
 
@@ -52,6 +53,7 @@
52
53
  <script lang="ts" setup>
53
54
  import 'vidstack/player';
54
55
  import 'vidstack/player/ui';
56
+ import { isVideoProvider, type MediaProviderChangeEvent } from 'vidstack';
55
57
  import { computed, provide, ref, toRefs, useTemplateRef } from 'vue';
56
58
 
57
59
  import { ComponentSize } from '../../enums';
@@ -202,6 +204,20 @@ const onCanPlay = (ev: Event) => {
202
204
  playPromise.catch(() => {});
203
205
  }
204
206
  };
207
+
208
+ /**
209
+ * @author PolinaSukhorukova-webitel
210
+ *
211
+ * [WTEL-10386](https://webitel.atlassian.net/browse/WTEL-10386)
212
+ * iOS 18 opens the native <video> in system fullscreen when `playsinline` is
213
+ * applied late (only by the time playback starts). Setting it on `provider-change`
214
+ * guarantees the attribute is present before iOS decides inline vs fullscreen.
215
+ */
216
+ const onProviderChange = (event: MediaProviderChangeEvent) => {
217
+ if (isVideoProvider(event.detail)) {
218
+ event.detail.video.playsInline = true;
219
+ }
220
+ };
205
221
  </script>
206
222
 
207
223
  <style scoped>
@@ -1,4 +1,4 @@
1
- import get from 'lodash/get.js';
1
+ import { get } from 'lodash-es';
2
2
  import { onMounted, ref, watch } from 'vue';
3
3
 
4
4
  export const useCachedItemInstanceName = (
@@ -44,7 +44,7 @@ import type {
44
44
  EngineQuestion,
45
45
  EngineQuestionAnswer,
46
46
  } from '@webitel/api-services/gen/models';
47
- import cloneDeep from 'lodash/cloneDeep.js';
47
+ import { cloneDeep } from 'lodash-es';
48
48
  import {
49
49
  computed,
50
50
  nextTick,
@@ -1,5 +1,5 @@
1
1
  import deepCopy from 'deep-copy';
2
- import set from 'lodash/set.js';
2
+ import { set } from 'lodash-es';
3
3
 
4
4
  import BaseStoreModule from '../../../store/BaseStoreModules/BaseStoreModule.js';
5
5
 
@@ -0,0 +1,101 @@
1
+ import { mount, shallowMount } from '@vue/test-utils';
2
+ import { beforeEach, describe, expect, it } from 'vitest';
3
+ import { nextTick } from 'vue';
4
+
5
+ import WtTableVariableColumnSelect from '../wt-table-variable-column-select.vue';
6
+
7
+ const storageKey = 'test/variable-headers';
8
+
9
+ const mountSelect = (
10
+ props: Partial<{
11
+ storageKey: string;
12
+ title: string;
13
+ }> = {},
14
+ ) =>
15
+ mount(WtTableVariableColumnSelect, {
16
+ props: {
17
+ storageKey,
18
+ title: 'Select variables',
19
+ ...props,
20
+ },
21
+ global: {
22
+ stubs: {
23
+ WtBadge: {
24
+ template: '<div><slot /></div>',
25
+ },
26
+ WtIconBtn: true,
27
+ WtPopup: {
28
+ props: [
29
+ 'shown',
30
+ 'size',
31
+ ],
32
+ template:
33
+ '<div v-if="shown" class="wt-popup__popup"><slot name="title" /><slot name="main" /><slot name="actions" /></div>',
34
+ },
35
+ WtInputText: true,
36
+ WtButton: {
37
+ props: [
38
+ 'disabled',
39
+ 'loading',
40
+ 'color',
41
+ ],
42
+ template:
43
+ '<button :disabled="disabled" @click="$attrs.onClick?.() || $emit(\'click\')"><slot /></button>',
44
+ },
45
+ },
46
+ },
47
+ });
48
+
49
+ describe('WtTableVariableColumnSelect', () => {
50
+ beforeEach(() => {
51
+ localStorage.clear();
52
+ });
53
+
54
+ it('renders a component', () => {
55
+ const wrapper = shallowMount(WtTableVariableColumnSelect, {
56
+ props: {
57
+ storageKey,
58
+ title: 'Select variables',
59
+ },
60
+ });
61
+ expect(wrapper.classes('wt-table-variable-column-select')).toBe(true);
62
+ });
63
+
64
+ it('opens popup on icon click', async () => {
65
+ const wrapper = mountSelect();
66
+ expect(wrapper.find('.wt-popup__popup').exists()).toBe(false);
67
+
68
+ wrapper
69
+ .findComponent({
70
+ name: 'WtIconBtn',
71
+ })
72
+ .vm.$emit('click');
73
+ await nextTick();
74
+
75
+ expect(wrapper.find('.wt-popup__popup').exists()).toBe(true);
76
+ });
77
+
78
+ it('restores keys from localStorage and emits update:variable-headers', async () => {
79
+ localStorage.setItem(storageKey, 'foo;bar');
80
+
81
+ const wrapper = mountSelect();
82
+ await nextTick();
83
+
84
+ const emitted = wrapper.emitted('update:variable-headers');
85
+ expect(emitted).toBeTruthy();
86
+ expect(emitted?.[0]?.[0]).toEqual(
87
+ expect.arrayContaining([
88
+ expect.objectContaining({
89
+ field: 'variables.foo',
90
+ text: 'foo',
91
+ show: false,
92
+ }),
93
+ expect.objectContaining({
94
+ field: 'variables.bar',
95
+ text: 'bar',
96
+ show: false,
97
+ }),
98
+ ]),
99
+ );
100
+ });
101
+ });
@@ -0,0 +1,278 @@
1
+ <template>
2
+ <div class="wt-table-variable-column-select">
3
+ <wt-badge
4
+ v-tooltip="title"
5
+ :hidden="!draft.length"
6
+ >
7
+ <wt-icon-btn
8
+ icon="variable-select"
9
+ @click="open"
10
+ />
11
+ </wt-badge>
12
+
13
+ <wt-popup
14
+ :shown="shownPopup"
15
+ :size="size"
16
+ class="wt-table-variable-column-select__popup"
17
+ @close="close"
18
+ >
19
+ <template #title>
20
+ {{ title }}
21
+ </template>
22
+ <template #main>
23
+ <div class="wt-table-variable-column-select__form">
24
+ <wt-input-text
25
+ v-model:model-value="newVariableKey"
26
+ :label="t('vocabulary.keys', 1)"
27
+ class="wt-table-variable-column-select__input"
28
+ />
29
+ <wt-button
30
+ :disabled="r$.$error"
31
+ @click="addVariableHeader(newVariableKey)"
32
+ >
33
+ {{ t('reusable.add') }}
34
+ </wt-button>
35
+ </div>
36
+
37
+ <ul class="wt-table-variable-column-select__list">
38
+ <li
39
+ v-for="key in draft"
40
+ :key="key.field"
41
+ class="wt-table-variable-column-select__item"
42
+ >
43
+ <span class="wt-table-variable-column-select__label">{{ key.text }}</span>
44
+ <wt-icon-btn
45
+ icon="bucket"
46
+ @click="deleteKey(key)"
47
+ />
48
+ </li>
49
+ </ul>
50
+ </template>
51
+ <template #actions>
52
+ <wt-button
53
+ :disabled="!hasChanges"
54
+ :loading="isLoading"
55
+ @click="save"
56
+ >
57
+ {{ t('reusable.save') }}
58
+ </wt-button>
59
+ <wt-button
60
+ color="secondary"
61
+ @click="close"
62
+ >
63
+ {{ t('reusable.cancel') }}
64
+ </wt-button>
65
+ </template>
66
+ </wt-popup>
67
+ </div>
68
+ </template>
69
+
70
+ <script lang="ts" setup>
71
+ import type { Maybe } from '@regle/core';
72
+ import { createRule, useRegle } from '@regle/core';
73
+ import { computed, reactive, ref } from 'vue';
74
+ import { useI18n } from 'vue-i18n';
75
+ import { ComponentSize } from '../../../enums/ComponentSize/ComponentSize';
76
+ import isEmpty from '../../../scripts/isEmpty';
77
+ import {
78
+ type TableVariableHeader,
79
+ VARIABLE_FIELD_PREFIX,
80
+ } from '../composable/useTableVariableHeaders';
81
+
82
+ const props = withDefaults(
83
+ defineProps<{
84
+ storageKey: string;
85
+ title: string;
86
+ fieldPrefix?: string;
87
+ size?: ComponentSize;
88
+ }>(),
89
+ {
90
+ fieldPrefix: VARIABLE_FIELD_PREFIX,
91
+ size: ComponentSize.SM,
92
+ },
93
+ );
94
+
95
+ const emit = defineEmits<{
96
+ 'update:variable-headers': [
97
+ value: TableVariableHeader[],
98
+ ];
99
+ }>();
100
+
101
+ const { t } = useI18n();
102
+
103
+ const shownPopup = ref(false);
104
+
105
+ const newVariableKey = ref('');
106
+
107
+ const draft = reactive<TableVariableHeader[]>([]);
108
+
109
+ let savedHeaders: TableVariableHeader[] = [];
110
+
111
+ const isLoading = ref(false);
112
+
113
+ const hasChanges = ref(false);
114
+
115
+ const variablesFromDraft = computed(() => {
116
+ return draft.map(({ value }) => value.replace(props.fieldPrefix, ''));
117
+ });
118
+
119
+ const requiredRule = createRule({
120
+ validator: (value: Maybe<string>) => Boolean(value),
121
+ message: 'required',
122
+ });
123
+
124
+ const alreadyExistsRule = createRule({
125
+ validator: (value: Maybe<string>) =>
126
+ !value || !variablesFromDraft.value.includes(value),
127
+ message: 'alreadyExists',
128
+ });
129
+
130
+ const { r$ } = useRegle(
131
+ {
132
+ newVariableKey,
133
+ },
134
+ () => ({
135
+ newVariableKey: {
136
+ required: requiredRule,
137
+ alreadyExists: alreadyExistsRule,
138
+ },
139
+ }),
140
+ {
141
+ autoDirty: true,
142
+ },
143
+ );
144
+ r$.$touch();
145
+
146
+ const cloneHeaders = (headers: TableVariableHeader[]) =>
147
+ headers.map((header) => ({
148
+ ...header,
149
+ }));
150
+
151
+ const replaceDraft = (headers: TableVariableHeader[]) => {
152
+ draft.splice(0, draft.length, ...cloneHeaders(headers));
153
+ };
154
+
155
+ const open = () => {
156
+ replaceDraft(savedHeaders);
157
+ newVariableKey.value = '';
158
+ hasChanges.value = false;
159
+ shownPopup.value = true;
160
+ };
161
+
162
+ const close = () => {
163
+ replaceDraft(savedHeaders);
164
+ newVariableKey.value = '';
165
+ hasChanges.value = false;
166
+ shownPopup.value = false;
167
+ };
168
+
169
+ const deleteKey = (keyToDelete: TableVariableHeader) => {
170
+ draft.splice(draft.indexOf(keyToDelete), 1);
171
+ hasChanges.value = true;
172
+ };
173
+
174
+ const localStorageSeparator = ';';
175
+
176
+ const setToLocalStorage = (variableHeaders: string[]) => {
177
+ const storedValue = variableHeaders.join(localStorageSeparator);
178
+ if (storedValue) {
179
+ localStorage.setItem(props.storageKey, storedValue);
180
+ } else {
181
+ localStorage.removeItem(props.storageKey);
182
+ }
183
+ };
184
+
185
+ const getFromLocalStorage = () => {
186
+ return localStorage.getItem(props.storageKey)?.split(localStorageSeparator);
187
+ };
188
+
189
+ const addVariableHeader = (
190
+ variableKey: string,
191
+ {
192
+ trackChanges = true,
193
+ }: {
194
+ trackChanges?: boolean;
195
+ } = {},
196
+ ) => {
197
+ const variableHeader: TableVariableHeader = {
198
+ value: `${props.fieldPrefix}${variableKey}`,
199
+ show: true,
200
+ field: `${props.fieldPrefix}${variableKey}`,
201
+ text: variableKey,
202
+ };
203
+
204
+ draft.unshift(variableHeader);
205
+ if (trackChanges) {
206
+ hasChanges.value = true;
207
+ }
208
+
209
+ if (newVariableKey.value) {
210
+ newVariableKey.value = '';
211
+ }
212
+ };
213
+
214
+ const save = () => {
215
+ isLoading.value = true;
216
+ try {
217
+ setToLocalStorage(variablesFromDraft.value);
218
+ savedHeaders = cloneHeaders(draft);
219
+ emit('update:variable-headers', draft);
220
+ } finally {
221
+ isLoading.value = false;
222
+ }
223
+ close();
224
+ };
225
+
226
+ const restore = () => {
227
+ const storedValue = getFromLocalStorage();
228
+ if (!isEmpty(storedValue)) {
229
+ storedValue.forEach((variableKey) => {
230
+ addVariableHeader(variableKey, {
231
+ trackChanges: false,
232
+ });
233
+ });
234
+ draft.forEach((variable) => {
235
+ variable.show = false;
236
+ });
237
+ savedHeaders = cloneHeaders(draft);
238
+ emit('update:variable-headers', draft);
239
+ }
240
+ };
241
+
242
+ restore();
243
+ </script>
244
+
245
+ <style lang="scss" scoped>
246
+ .wt-table-variable-column-select {
247
+ line-height: 0;
248
+ }
249
+
250
+ .wt-table-variable-column-select__form {
251
+ display: flex;
252
+ align-items: flex-end;
253
+ justify-content: space-between;
254
+ width: 100%;
255
+ gap: var(--spacing-xs);
256
+ }
257
+
258
+ .wt-table-variable-column-select__input {
259
+ flex: 1;
260
+ }
261
+
262
+ .wt-table-variable-column-select__list {
263
+ display: flex;
264
+ flex-direction: column;
265
+ margin-top: var(--spacing-sm);
266
+ gap: var(--spacing-sm);
267
+ }
268
+
269
+ .wt-table-variable-column-select__item {
270
+ display: flex;
271
+ justify-content: space-between;
272
+ }
273
+
274
+ .wt-table-variable-column-select__label {
275
+ display: flex;
276
+ align-items: center;
277
+ }
278
+ </style>
@@ -0,0 +1,129 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { ref } from 'vue';
3
+
4
+ import {
5
+ isVariableHeader,
6
+ useTableVariableHeaders,
7
+ VARIABLE_FIELD_PREFIX,
8
+ } from '../useTableVariableHeaders';
9
+
10
+ describe('isVariableHeader', () => {
11
+ it('detects variable headers by field or value prefix', () => {
12
+ expect(
13
+ isVariableHeader({
14
+ field: `${VARIABLE_FIELD_PREFIX}city`,
15
+ }),
16
+ ).toBe(true);
17
+ expect(
18
+ isVariableHeader({
19
+ value: `${VARIABLE_FIELD_PREFIX}city`,
20
+ }),
21
+ ).toBe(true);
22
+ expect(
23
+ isVariableHeader({
24
+ field: 'name',
25
+ value: 'name',
26
+ }),
27
+ ).toBe(false);
28
+ });
29
+ });
30
+
31
+ describe('useTableVariableHeaders', () => {
32
+ it('replaces existing variable headers and appends new ones', () => {
33
+ const headers = ref([
34
+ {
35
+ field: 'name',
36
+ value: 'name',
37
+ show: true,
38
+ },
39
+ {
40
+ field: `${VARIABLE_FIELD_PREFIX}old`,
41
+ value: `${VARIABLE_FIELD_PREFIX}old`,
42
+ text: 'old',
43
+ show: true,
44
+ },
45
+ ]);
46
+ const updateShownHeaders = vi.fn();
47
+
48
+ const { updateVariableHeaders } = useTableVariableHeaders({
49
+ headers,
50
+ updateShownHeaders,
51
+ });
52
+
53
+ updateVariableHeaders([
54
+ {
55
+ field: `${VARIABLE_FIELD_PREFIX}old`,
56
+ value: `${VARIABLE_FIELD_PREFIX}old`,
57
+ text: 'old',
58
+ show: true,
59
+ },
60
+ {
61
+ field: `${VARIABLE_FIELD_PREFIX}new`,
62
+ value: `${VARIABLE_FIELD_PREFIX}new`,
63
+ text: 'new',
64
+ show: true,
65
+ },
66
+ ]);
67
+
68
+ expect(updateShownHeaders).toHaveBeenCalledWith([
69
+ {
70
+ field: 'name',
71
+ value: 'name',
72
+ show: true,
73
+ },
74
+ {
75
+ field: `${VARIABLE_FIELD_PREFIX}old`,
76
+ value: `${VARIABLE_FIELD_PREFIX}old`,
77
+ text: 'old',
78
+ show: true,
79
+ },
80
+ {
81
+ field: `${VARIABLE_FIELD_PREFIX}new`,
82
+ value: `${VARIABLE_FIELD_PREFIX}new`,
83
+ text: 'new',
84
+ show: true,
85
+ },
86
+ ]);
87
+ });
88
+
89
+ it('removes variable headers missing from the incoming list', () => {
90
+ const headers = ref([
91
+ {
92
+ field: `${VARIABLE_FIELD_PREFIX}keep`,
93
+ value: `${VARIABLE_FIELD_PREFIX}keep`,
94
+ text: 'keep',
95
+ show: false,
96
+ },
97
+ {
98
+ field: `${VARIABLE_FIELD_PREFIX}drop`,
99
+ value: `${VARIABLE_FIELD_PREFIX}drop`,
100
+ text: 'drop',
101
+ show: true,
102
+ },
103
+ ]);
104
+ const updateShownHeaders = vi.fn();
105
+
106
+ const { updateVariableHeaders } = useTableVariableHeaders({
107
+ headers,
108
+ updateShownHeaders,
109
+ });
110
+
111
+ updateVariableHeaders([
112
+ {
113
+ field: `${VARIABLE_FIELD_PREFIX}keep`,
114
+ value: `${VARIABLE_FIELD_PREFIX}keep`,
115
+ text: 'keep',
116
+ show: true,
117
+ },
118
+ ]);
119
+
120
+ expect(updateShownHeaders).toHaveBeenCalledWith([
121
+ {
122
+ field: `${VARIABLE_FIELD_PREFIX}keep`,
123
+ value: `${VARIABLE_FIELD_PREFIX}keep`,
124
+ text: 'keep',
125
+ show: false,
126
+ },
127
+ ]);
128
+ });
129
+ });