@webitel/ui-sdk 26.8.149 → 26.8.151

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.
@@ -1,4 +1,4 @@
1
- import "./components-BQNe8f-D.js";
1
+ import "./components-DFU2gMZC.js";
2
2
  import { t as e } from "./_plugin-vue_export-helper-B3ysoDQm.js";
3
3
  import { E as t, h as n } from "./enums-CKer6GJR.js";
4
4
  import { t as r } from "./wt-button-DnbIcGvU.js";
@@ -1,4 +1,4 @@
1
- import { C as e, i as t } from "./components-BQNe8f-D.js";
1
+ import { C as e, i as t } from "./components-DFU2gMZC.js";
2
2
  import { t as n } from "./_plugin-vue_export-helper-B3ysoDQm.js";
3
3
  import { t as r } from "./utils-DQQQIpjz.js";
4
4
  import { Fragment as i, computed as a, createCommentVNode as o, createElementBlock as s, createTextVNode as c, createVNode as l, defineComponent as u, openBlock as d, renderList as f, renderSlot as p, toDisplayString as m, unref as h, withCtx as g } from "vue";
@@ -1,6 +1,6 @@
1
- import { n as e } from "./components-BQNe8f-D.js";
1
+ import { n as e } from "./components-DFU2gMZC.js";
2
2
  import "./convertDuration-CIPonKKW.js";
3
- import { i as t, r as n } from "./clients-CvTW64Rq.js";
3
+ import { i as t, r as n } from "./clients-Bn77MDFb.js";
4
4
  import { n as r } from "./vue-i18n-VBiROMZ4.js";
5
5
  import { n as i } from "./useEventBus-B-ZeklLu.js";
6
6
  import { createBlock as a, createTextVNode as o, createVNode as s, defineComponent as c, mergeProps as l, onMounted as u, openBlock as d, ref as f, resolveComponent as p, toDisplayString as m, unref as h, withCtx as g } from "vue";
@@ -1,7 +1,7 @@
1
1
  import "./dist-EXdgfpvN.js";
2
2
  import { n as e } from "./genClient-CvWlQ0TZ.js";
3
- import { a as t, o as n, r, s as i, w as a } from "./components-BQNe8f-D.js";
4
- import { n as o, t as s } from "./clients-CvTW64Rq.js";
3
+ import { a as t, o as n, r, s as i, w as a } from "./components-DFU2gMZC.js";
4
+ import { n as o, t as s } from "./clients-Bn77MDFb.js";
5
5
  import { a as c, c as l, i as u, n as d, o as f, p, r as m, s as h, t as g } from "./enums-CKer6GJR.js";
6
6
  import { n as _ } from "./vue-i18n-VBiROMZ4.js";
7
7
  import "./wt-toast-3-3ZylQ9.js";
@@ -1,4 +1,4 @@
1
- import { T as e } from "./components-BQNe8f-D.js";
1
+ import { T as e } from "./components-DFU2gMZC.js";
2
2
  import "./convertDuration-CIPonKKW.js";
3
3
  import { t } from "./_plugin-vue_export-helper-B3ysoDQm.js";
4
4
  import { h as n } from "./enums-CKer6GJR.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webitel/ui-sdk",
3
- "version": "26.8.149",
3
+ "version": "26.8.151",
4
4
  "private": false,
5
5
  "scripts": {
6
6
  "dev": "npm run docs:dev",
@@ -440,6 +440,10 @@
440
440
  "types": "./types/modules/Appearance/index.d.ts",
441
441
  "import": "./src/modules/Appearance/index.ts"
442
442
  },
443
+ "./modules/SystemSettings": {
444
+ "types": "./types/modules/SystemSettings/index.d.ts",
445
+ "import": "./src/modules/SystemSettings/index.ts"
446
+ },
443
447
  "./modules/ObjectPermissions": {
444
448
  "types": "./types/modules/ObjectPermissions/index.d.ts",
445
449
  "import": "./src/modules/ObjectPermissions/index.ts"
@@ -114,4 +114,29 @@ describe('WtSingleSelect', () => {
114
114
  .vm.$emit('hide');
115
115
  expect(wrapper.emitted().hide).toBeTruthy();
116
116
  });
117
+
118
+ it('emits the filter text on search and on Enter', async () => {
119
+ const wrapper = mount(WtSingleSelect, {
120
+ global: {
121
+ stubs: {
122
+ Select: {
123
+ name: 'Select',
124
+ template: '<div><slot name="header" /></div>',
125
+ },
126
+ },
127
+ },
128
+ });
129
+ const filter = wrapper.findComponent({
130
+ name: 'wt-input-text',
131
+ });
132
+ await filter.vm.$emit('update:model-value', 'custom-file.mp3');
133
+ expect(wrapper.emitted('search-change')?.at(-1)).toEqual([
134
+ 'custom-file.mp3',
135
+ ]);
136
+
137
+ await filter.trigger('keydown.enter');
138
+ expect(wrapper.emitted('search-enter')?.at(-1)).toEqual([
139
+ 'custom-file.mp3',
140
+ ]);
141
+ });
117
142
  });
@@ -46,8 +46,8 @@
46
46
  class="wt-single-select__input"
47
47
  :model-value="filterText"
48
48
  :size="ComponentSize.SM"
49
- @update:model-value="filterOptions($event)"
50
- @keydown.enter.stop="onInputKeydown"
49
+ @update:model-value="onFilterModelUpdate"
50
+ @keydown.enter.stop.prevent="onFilterEnter"
51
51
  >
52
52
  <template #suffix>
53
53
  <wt-icon v-if="allowCustomValues && filterText" disabled icon="select-custom-value-enter" />
@@ -194,6 +194,12 @@ const model = defineModel<string>({
194
194
  const emit = defineEmits<{
195
195
  reset: [];
196
196
  hide: [];
197
+ 'search-change': [
198
+ value: string,
199
+ ];
200
+ 'search-enter': [
201
+ value: string,
202
+ ];
197
203
  }>();
198
204
 
199
205
  const selectId = `select-${Math.random().toString(36).slice(2, 11)}`;
@@ -257,6 +263,18 @@ const handleDropdownHide = () => {
257
263
  onDropdownHide();
258
264
  emit('hide');
259
265
  };
266
+
267
+ const onFilterModelUpdate = (value: string) => {
268
+ const text = value ?? '';
269
+ filterOptions(text);
270
+ emit('search-change', text);
271
+ };
272
+
273
+ const onFilterEnter = () => {
274
+ emit('search-enter', filterText.value);
275
+ onInputKeydown();
276
+ selectRef.value?.hide?.();
277
+ };
260
278
  </script>
261
279
 
262
280
  <style scoped>
@@ -0,0 +1,210 @@
1
+ import { ConfigurationsAPI } from '@webitel/api-services/api';
2
+ import { EngineSystemSettingName } from '@webitel/api-services/gen/models';
3
+ import { createPinia, defineStore, setActivePinia } from 'pinia';
4
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
5
+ import type { Client } from 'webitel-sdk';
6
+
7
+ import {
8
+ type SystemSettingChange,
9
+ useSystemSettings,
10
+ } from '../useSystemSettings';
11
+
12
+ vi.mock('@webitel/api-services/api', () => ({
13
+ ConfigurationsAPI: {
14
+ getList: vi.fn(),
15
+ },
16
+ }));
17
+
18
+ const getList = vi.mocked(ConfigurationsAPI.getList);
19
+
20
+ const createSystemSettings = () =>
21
+ useSystemSettings([
22
+ EngineSystemSettingName.WbtHideContact,
23
+ EngineSystemSettingName.ShowFullContact,
24
+ ]);
25
+
26
+ describe('useSystemSettings', () => {
27
+ beforeEach(() => {
28
+ getList.mockReset();
29
+ getList.mockResolvedValue({
30
+ items: [],
31
+ });
32
+ });
33
+
34
+ it('initialize loads all tracked settings with a single request, even if called several times', async () => {
35
+ getList.mockResolvedValue({
36
+ items: [
37
+ {
38
+ name: EngineSystemSettingName.ShowFullContact,
39
+ value: true,
40
+ },
41
+ ],
42
+ });
43
+ const systemSettings = createSystemSettings();
44
+
45
+ await Promise.all([
46
+ systemSettings.initialize(),
47
+ systemSettings.initialize(),
48
+ ]);
49
+
50
+ expect(getList).toHaveBeenCalledTimes(1);
51
+ expect(getList).toHaveBeenCalledWith({
52
+ name: [
53
+ EngineSystemSettingName.WbtHideContact,
54
+ EngineSystemSettingName.ShowFullContact,
55
+ ],
56
+ });
57
+ expect(
58
+ systemSettings.settings.value[EngineSystemSettingName.ShowFullContact],
59
+ ).toBe(true);
60
+ expect(
61
+ systemSettings.settings.value[EngineSystemSettingName.WbtHideContact],
62
+ ).toBeUndefined();
63
+ });
64
+
65
+ it('initialize loads settings again after a failed load', async () => {
66
+ getList.mockRejectedValueOnce(new Error('network'));
67
+ const systemSettings = createSystemSettings();
68
+
69
+ await expect(systemSettings.initialize()).rejects.toThrow('network');
70
+ await systemSettings.initialize();
71
+
72
+ expect(getList).toHaveBeenCalledTimes(2);
73
+ });
74
+
75
+ it('reload loads settings again after initialize', async () => {
76
+ const systemSettings = createSystemSettings();
77
+ await systemSettings.initialize();
78
+ getList.mockResolvedValue({
79
+ items: [
80
+ {
81
+ name: EngineSystemSettingName.WbtHideContact,
82
+ value: true,
83
+ },
84
+ ],
85
+ });
86
+
87
+ await systemSettings.reload();
88
+
89
+ expect(getList).toHaveBeenCalledTimes(2);
90
+ expect(
91
+ systemSettings.settings.value[EngineSystemSettingName.WbtHideContact],
92
+ ).toBe(true);
93
+ });
94
+
95
+ it('applyChange applies the value from the event, including falsy values, without a request', async () => {
96
+ const systemSettings = createSystemSettings();
97
+
98
+ await systemSettings.applyChange({
99
+ name: EngineSystemSettingName.WbtHideContact,
100
+ value: true,
101
+ });
102
+ expect(
103
+ systemSettings.settings.value[EngineSystemSettingName.WbtHideContact],
104
+ ).toBe(true);
105
+
106
+ await systemSettings.applyChange({
107
+ name: EngineSystemSettingName.WbtHideContact,
108
+ value: false,
109
+ });
110
+ expect(
111
+ systemSettings.settings.value[EngineSystemSettingName.WbtHideContact],
112
+ ).toBe(false);
113
+
114
+ expect(getList).not.toHaveBeenCalled();
115
+ });
116
+
117
+ it('applyChange re-reads the setting if the event has no value', async () => {
118
+ const systemSettings = createSystemSettings();
119
+ await systemSettings.applyChange({
120
+ name: EngineSystemSettingName.ShowFullContact,
121
+ value: true,
122
+ });
123
+
124
+ await systemSettings.applyChange({
125
+ name: EngineSystemSettingName.ShowFullContact,
126
+ });
127
+
128
+ expect(getList).toHaveBeenCalledWith({
129
+ name: [
130
+ EngineSystemSettingName.ShowFullContact,
131
+ ],
132
+ });
133
+ expect(
134
+ systemSettings.settings.value[EngineSystemSettingName.ShowFullContact],
135
+ ).toBeUndefined();
136
+ });
137
+
138
+ it('applyChange ignores settings that are not tracked', async () => {
139
+ const systemSettings = createSystemSettings();
140
+
141
+ await systemSettings.applyChange({
142
+ name: 'some_future_setting',
143
+ value: 1,
144
+ });
145
+ await systemSettings.applyChange({
146
+ name: EngineSystemSettingName.CallEndSoundNotification,
147
+ });
148
+
149
+ expect(systemSettings.settings.value).toEqual({});
150
+ expect(getList).not.toHaveBeenCalled();
151
+ });
152
+
153
+ it('does not send requests if no settings are tracked', async () => {
154
+ const systemSettings = useSystemSettings([]);
155
+
156
+ await systemSettings.initialize();
157
+ await systemSettings.reload();
158
+ await systemSettings.applyChange({
159
+ name: EngineSystemSettingName.WbtHideContact,
160
+ });
161
+
162
+ expect(getList).not.toHaveBeenCalled();
163
+ expect(systemSettings.settings.value).toEqual({});
164
+ });
165
+
166
+ it('subscribe applies "system_settings_changed" events of the passed client', () => {
167
+ const systemSettings = createSystemSettings();
168
+ let emit: (change: SystemSettingChange) => void = () => {};
169
+ const client = {
170
+ on: vi.fn((_event, listener) => {
171
+ emit = listener;
172
+ }),
173
+ };
174
+
175
+ systemSettings.subscribe(client as unknown as Client);
176
+ emit({
177
+ name: EngineSystemSettingName.WbtHideContact,
178
+ value: true,
179
+ });
180
+
181
+ expect(client.on).toHaveBeenCalledWith(
182
+ 'system_settings_changed',
183
+ expect.any(Function),
184
+ );
185
+ expect(
186
+ systemSettings.settings.value[EngineSystemSettingName.WbtHideContact],
187
+ ).toBe(true);
188
+ });
189
+
190
+ it('works as the state of a setup store', async () => {
191
+ setActivePinia(createPinia());
192
+ const useSystemSettingsStore = defineStore('systemSettings', () => {
193
+ const { settings, applyChange } = useSystemSettings([
194
+ EngineSystemSettingName.WbtHideContact,
195
+ ]);
196
+ return {
197
+ settings,
198
+ applyChange,
199
+ };
200
+ });
201
+ const store = useSystemSettingsStore();
202
+
203
+ await store.applyChange({
204
+ name: EngineSystemSettingName.WbtHideContact,
205
+ value: true,
206
+ });
207
+
208
+ expect(store.settings[EngineSystemSettingName.WbtHideContact]).toBe(true);
209
+ });
210
+ });
@@ -0,0 +1,79 @@
1
+ import { ConfigurationsAPI } from '@webitel/api-services/api';
2
+ import type {
3
+ EngineSystemSetting,
4
+ EngineSystemSettingName,
5
+ } from '@webitel/api-services/gen/models';
6
+ import { type Ref, ref } from 'vue';
7
+ import type { Client, ClientEvents } from 'webitel-sdk';
8
+
9
+ /**
10
+ * [Claude] Payload of webitel-sdk `system_settings_changed` event (webitel-sdk >= 26.4.35).
11
+ * No `value` means the setting was reset to default.
12
+ * Derived from `ClientEvents`, because webitel-sdk does not export the payload type itself.
13
+ */
14
+ export type SystemSettingChange = Parameters<
15
+ ClientEvents['system_settings_changed']
16
+ >[0];
17
+
18
+ /**
19
+ * [Claude] Each call creates its own state, so call it once, inside an app's setup store.
20
+ */
21
+ export const useSystemSettings = <Name extends EngineSystemSettingName>(
22
+ names: readonly Name[],
23
+ ) => {
24
+ const isTrackedName = (name: string): name is Name =>
25
+ (names as readonly string[]).includes(name);
26
+
27
+ const settings = ref({}) as Ref<Partial<Record<Name, unknown>>>;
28
+
29
+ let initPromise: Promise<void> | null = null;
30
+
31
+ const loadSettings = async (namesToLoad: readonly Name[]) => {
32
+ // [Claude] nothing is tracked: skip the request instead of sending an empty name filter
33
+ if (!namesToLoad.length) return;
34
+ const { items } = await ConfigurationsAPI.getList({
35
+ name: namesToLoad,
36
+ });
37
+ namesToLoad.forEach((name) => {
38
+ settings.value[name] = items.find(
39
+ (item: EngineSystemSetting) => item.name === name,
40
+ )?.value;
41
+ });
42
+ };
43
+
44
+ // [Claude] loads once, afterwards values are kept up to date by applyChange()
45
+ const initialize = () => {
46
+ initPromise ??= loadSettings(names).catch((err) => {
47
+ initPromise = null;
48
+ throw err;
49
+ });
50
+ return initPromise;
51
+ };
52
+
53
+ // [Claude] changes made while the socket was down are never delivered, so call it after reconnect
54
+ const reload = () => loadSettings(names);
55
+
56
+ const applyChange = async ({ name, value }: SystemSettingChange) => {
57
+ if (!isTrackedName(name)) return;
58
+ if (value === undefined) {
59
+ await loadSettings([
60
+ name,
61
+ ]);
62
+ return;
63
+ }
64
+ settings.value[name] = value;
65
+ };
66
+
67
+ const subscribe = (client: Client) => {
68
+ client.on('system_settings_changed', applyChange);
69
+ };
70
+
71
+ return {
72
+ settings,
73
+
74
+ initialize,
75
+ reload,
76
+ applyChange,
77
+ subscribe,
78
+ };
79
+ };
@@ -0,0 +1,4 @@
1
+ export {
2
+ type SystemSettingChange,
3
+ useSystemSettings,
4
+ } from './composables/useSystemSettings';