@webitel/ui-datalist 26.8.6 → 26.8.7

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/Readme.md CHANGED
@@ -36,6 +36,7 @@ This package contains:
36
36
  ```ts
37
37
  import {
38
38
  createTableStore, // pinia store factory
39
+ useNestedTableList, // list of a card page's nested tab: owned, loaded and reset by the card
39
40
  } from '@webitel/ui-datalist';
40
41
  ```
41
42
 
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@webitel/ui-datalist",
3
- "version": "26.8.6",
3
+ "version": "26.8.7",
4
4
  "description": "Toolkit for building data lists in webitel ui system",
5
5
  "scripts": {
6
6
  "build:types": "vue-tsc -b tsconfig.build.json",
7
7
  "typecheck": "vue-tsc --noEmit -p ./tsconfig.typecheck.json",
8
8
  "lint:fix": "npx biome check --write ./src",
9
9
  "biome:ci:gh": "biome ci ./src --reporter=github",
10
- "test:unit": "node -e \"process.exit(0)\"",
10
+ "test:unit": "vitest run --root ../.. packages/ui-datalist/src",
11
11
  "lint": "biome ci ./src",
12
12
  "test": "npm run test:unit",
13
13
  "ci:check": "npm run biome:ci:gh && npm run test:unit",
@@ -44,6 +44,7 @@
44
44
  "@webitel/ui-sdk": "~26.8",
45
45
  "axios": "^1.15.2",
46
46
  "date-fns": "^4",
47
+ "lodash-es": "^4.17.21",
47
48
  "pinia": "^3.x",
48
49
  "vue": "^3.5",
49
50
  "zod": "^4.x"
@@ -40,9 +40,9 @@ describe('tableFiltersStoreBody', () => {
40
40
  runWithRouter = (fn) => app.runWithContext(fn);
41
41
  });
42
42
 
43
- const setUpStore = async () => {
43
+ const setUpStore = async (options?: { isNested?: boolean }) => {
44
44
  const store = tableFiltersStoreBody(namespace);
45
- await runWithRouter(() => store.setupPersistence());
45
+ await runWithRouter(() => store.setupPersistence(options));
46
46
  return store;
47
47
  };
48
48
 
@@ -133,6 +133,42 @@ describe('tableFiltersStoreBody', () => {
133
133
  expect(router.currentRoute.value.query.searchMode).toBeUndefined();
134
134
  expect(localStorage.getItem(`${namespace}/searchMode`)).toBe('subject');
135
135
  });
136
+
137
+ describe('a nested list', () => {
138
+ it('keeps an added filter out of the route query', async () => {
139
+ const store = await setUpStore({
140
+ isNested: true,
141
+ });
142
+
143
+ store.addFilter({
144
+ name: 'status',
145
+ value: 'open',
146
+ });
147
+ await new Promise((resolve) => setTimeout(resolve));
148
+
149
+ expect(
150
+ router.currentRoute.value.query[filtersQueryKey],
151
+ ).toBeUndefined();
152
+ expect(sessionStorage.getItem(`${namespace}/filters`)).toContain(
153
+ 'open',
154
+ );
155
+ });
156
+
157
+ it('restores filters from sessionStorage, not the route query', async () => {
158
+ sessionStorage.setItem(
159
+ `${namespace}/filters`,
160
+ JSON.stringify({
161
+ status_val: 'open',
162
+ }),
163
+ );
164
+
165
+ const store = await setUpStore({
166
+ isNested: true,
167
+ });
168
+
169
+ expect(store.filtersManager.getFilter('status')?.value).toBe('open');
170
+ });
171
+ });
136
172
  });
137
173
 
138
174
  describe('syncPersistence', () => {
@@ -45,7 +45,11 @@ export const tableFiltersStoreBody = (
45
45
 
46
46
  let persistedStorageControllers: PersistedStorageController[] = [];
47
47
 
48
- const setupPersistence = () => {
48
+ const setupPersistence = ({
49
+ isNested = false,
50
+ }: {
51
+ isNested?: boolean;
52
+ } = {}) => {
49
53
  const filtersStorage = usePersistedStorage({
50
54
  name: 'filters',
51
55
 
@@ -53,10 +57,14 @@ export const tableFiltersStoreBody = (
53
57
  () => filtersManager,
54
58
  ) /* computed is used to provide value as ref(), not reactive() – as per usePersistedStorage interface */,
55
59
 
56
- storages: [
57
- PersistedStorageType.Route,
58
- PersistedStorageType.SessionStorage,
59
- ],
60
+ storages: isNested
61
+ ? [
62
+ PersistedStorageType.SessionStorage,
63
+ ]
64
+ : [
65
+ PersistedStorageType.Route,
66
+ PersistedStorageType.SessionStorage,
67
+ ],
60
68
  storagePath: namespace,
61
69
 
62
70
  /* use custom .toString() logic, provided by FiltersManager */
@@ -1,7 +1,7 @@
1
1
  import { SysTypesAPI as sysTypes } from '@webitel/api-services/api';
2
2
  import type { DataField } from '@webitel/api-services/gen/models';
3
3
  import { WtTypeExtensionFieldKind } from '@webitel/ui-sdk/enums';
4
- import { get } from 'lodash';
4
+ import { get } from 'lodash-es';
5
5
  import type {
6
6
  BaseFilterConfig,
7
7
  FilterConfigBaseParams,
@@ -58,6 +58,7 @@ describe('tableHeadersStoreBody', () => {
58
58
 
59
59
  beforeEach(async () => {
60
60
  localStorage.clear();
61
+ sessionStorage.clear();
61
62
 
62
63
  router = createRouter({
63
64
  history: createMemoryHistory(),
@@ -73,9 +74,9 @@ describe('tableHeadersStoreBody', () => {
73
74
  runWithRouter = (fn) => app.runWithContext(fn);
74
75
  });
75
76
 
76
- const setUpStore = async () => {
77
+ const setUpStore = async (options?: { isNested?: boolean }) => {
77
78
  const store = createStore();
78
- await runWithRouter(() => store.setupPersistence());
79
+ await runWithRouter(() => store.setupPersistence(options));
79
80
  return store;
80
81
  };
81
82
 
@@ -226,6 +227,37 @@ describe('tableHeadersStoreBody', () => {
226
227
  ]);
227
228
  expect(store.headers.value.some(({ field }) => !field)).toBe(false);
228
229
  });
230
+
231
+ describe('a nested list', () => {
232
+ it('keeps a changed sort out of the route query, in a namespaced sessionStorage key instead', async () => {
233
+ const store = await setUpStore({
234
+ isNested: true,
235
+ });
236
+
237
+ store.updateSort(store.headers.value[0], 'asc');
238
+ await flushWrites();
239
+
240
+ expect(router.currentRoute.value.query.sort).toBeUndefined();
241
+ expect(sessionStorage.getItem(`${id}/sort`)).toBe('+name');
242
+ });
243
+
244
+ it('keeps a changed field set out of the route query, in localStorage instead', async () => {
245
+ const store = await setUpStore({
246
+ isNested: true,
247
+ });
248
+
249
+ store.updateShownHeaders(
250
+ store.headers.value.map((header) => ({
251
+ ...header,
252
+ show: header.field === 'name',
253
+ })),
254
+ );
255
+ await flushWrites();
256
+
257
+ expect(router.currentRoute.value.query[fieldsQueryKey]).toBeUndefined();
258
+ expect(localStorage.getItem(fieldsQueryKey)).toBe('name');
259
+ });
260
+ });
229
261
  });
230
262
 
231
263
  describe('access', () => {
@@ -249,15 +249,23 @@ export const tableHeadersStoreBody = ({
249
249
 
250
250
  let persistedStorageControllers: PersistedStorageController[] = [];
251
251
 
252
- const setupPersistence = async () => {
252
+ const setupPersistence = async ({
253
+ isNested = false,
254
+ }: {
255
+ isNested?: boolean;
256
+ } = {}) => {
253
257
  const fieldsStorage = usePersistedStorage({
254
258
  name: 'fields',
255
259
  value: fields,
256
260
  /* order is the restore priority: a shared link wins over local columns */
257
- storages: [
258
- PersistedStorageType.Route,
259
- PersistedStorageType.LocalStorage,
260
- ],
261
+ storages: isNested
262
+ ? [
263
+ PersistedStorageType.LocalStorage,
264
+ ]
265
+ : [
266
+ PersistedStorageType.Route,
267
+ PersistedStorageType.LocalStorage,
268
+ ],
261
269
  storagePath: id,
262
270
  onStore: (save, { name }) => {
263
271
  const value = fields.value.join(',');
@@ -277,6 +285,12 @@ export const tableHeadersStoreBody = ({
277
285
  const sortStorage = usePersistedStorage({
278
286
  name: 'sort',
279
287
  value: sort,
288
+ ...(isNested && {
289
+ storages: [
290
+ PersistedStorageType.SessionStorage,
291
+ ],
292
+ storagePath: id,
293
+ }),
280
294
  });
281
295
 
282
296
  const columnWidthsStorage = usePersistedStorage({
@@ -19,6 +19,8 @@ describe('tablePaginationStoreBody', () => {
19
19
  let runWithRouter: <T>(fn: () => T) => T;
20
20
 
21
21
  beforeEach(async () => {
22
+ sessionStorage.clear();
23
+
22
24
  router = createRouter({
23
25
  history: createMemoryHistory(),
24
26
  routes,
@@ -33,9 +35,14 @@ describe('tablePaginationStoreBody', () => {
33
35
  runWithRouter = (fn) => app.runWithContext(fn);
34
36
  });
35
37
 
36
- const setUpStore = async () => {
37
- const store = tablePaginationStoreBody();
38
- await runWithRouter(() => store.setupPersistence());
38
+ const setUpStore = async (
39
+ options?: {
40
+ isNested?: boolean;
41
+ },
42
+ namespace?: string,
43
+ ) => {
44
+ const store = tablePaginationStoreBody(namespace);
45
+ await runWithRouter(() => store.setupPersistence(options));
39
46
  return store;
40
47
  };
41
48
 
@@ -131,4 +138,21 @@ describe('tablePaginationStoreBody', () => {
131
138
  expect(router.currentRoute.value.query.page).toBe('5');
132
139
  });
133
140
  });
141
+
142
+ describe('a nested list', () => {
143
+ it('keeps a changed page out of the route query, in a namespaced sessionStorage key instead', async () => {
144
+ const store = await setUpStore(
145
+ {
146
+ isNested: true,
147
+ },
148
+ 'cases',
149
+ );
150
+
151
+ store.updatePage(3);
152
+ await new Promise((resolve) => setTimeout(resolve));
153
+
154
+ expect(router.currentRoute.value.query.page).toBeUndefined();
155
+ expect(sessionStorage.getItem('cases/page')).toBe('3');
156
+ });
157
+ });
134
158
  });
@@ -1,12 +1,15 @@
1
1
  import { ref } from 'vue';
2
2
 
3
3
  import { createDatalistStore } from '../_shared/createDatalistStore';
4
- import type { PersistedStorageController } from '../persist/PersistedStorage.types';
4
+ import {
5
+ type PersistedStorageController,
6
+ PersistedStorageType,
7
+ } from '../persist/PersistedStorage.types';
5
8
  import { usePersistedStorage } from '../persist/usePersistedStorage';
6
9
  import type { Identifiable } from '../types/createDatalistStore.types';
7
10
  import type { useTableStoreConfig } from '../types/tableStore.types';
8
11
 
9
- export const tablePaginationStoreBody = () => {
12
+ export const tablePaginationStoreBody = (namespace?: string) => {
10
13
  const page = ref(1);
11
14
  const size = ref(10);
12
15
  const next = ref(false);
@@ -27,10 +30,20 @@ export const tablePaginationStoreBody = () => {
27
30
 
28
31
  let persistedStorageControllers: PersistedStorageController[] = [];
29
32
 
30
- const setupPersistence = () => {
33
+ const setupPersistence = ({
34
+ isNested = false,
35
+ }: {
36
+ isNested?: boolean;
37
+ } = {}) => {
31
38
  const pageStorage = usePersistedStorage({
32
39
  name: 'page',
33
40
  value: page,
41
+ ...(isNested && {
42
+ storages: [
43
+ PersistedStorageType.SessionStorage,
44
+ ],
45
+ storagePath: namespace,
46
+ }),
34
47
  onStore: (save, { name }) => {
35
48
  return save({
36
49
  name,
@@ -47,6 +60,12 @@ export const tablePaginationStoreBody = () => {
47
60
  const sizeStorage = usePersistedStorage({
48
61
  name: 'size',
49
62
  value: size,
63
+ ...(isNested && {
64
+ storages: [
65
+ PersistedStorageType.SessionStorage,
66
+ ],
67
+ storagePath: namespace,
68
+ }),
50
69
  onStore: (save, { name }) => {
51
70
  return save({
52
71
  name,
@@ -98,7 +117,7 @@ export const createTablePaginationStore = <Entity extends Identifiable>(
98
117
  ) => {
99
118
  const id = `${namespace}/pagination`;
100
119
  return createDatalistStore({
101
- storeBody: tablePaginationStoreBody,
120
+ storeBody: () => tablePaginationStoreBody(namespace),
102
121
  namespace: id,
103
122
  config,
104
123
  });
@@ -259,4 +259,30 @@ describe('tableStoreBody', () => {
259
259
  );
260
260
  });
261
261
  });
262
+
263
+ describe('filters persistence of a nested list', () => {
264
+ it('keeps an added filter out of the route query, in sessionStorage instead', async () => {
265
+ const useStore = createTableStore('cases-nested-filters/datalist', {
266
+ apiModule: {
267
+ getList,
268
+ },
269
+ headers,
270
+ });
271
+
272
+ mountTab(useStore);
273
+ await flush();
274
+
275
+ const store = useStore();
276
+ store.addFilter({
277
+ name: 'search',
278
+ value: 'lorem',
279
+ });
280
+ await flush();
281
+
282
+ expect(router.currentRoute.value.query).toEqual({});
283
+ expect(
284
+ sessionStorage.getItem('cases-nested-filters/datalist/filters'),
285
+ ).toContain('lorem');
286
+ });
287
+ });
262
288
  });
@@ -1,5 +1,5 @@
1
1
  import deepEqual from 'deep-equal';
2
- import set from 'lodash/set';
2
+ import { set } from 'lodash-es';
3
3
  import { nextTick, type Ref, ref, toRaw, watch } from 'vue';
4
4
 
5
5
  import {
@@ -317,9 +317,15 @@ export const tableStoreBody = <Entity extends Identifiable>(
317
317
 
318
318
  if (!disablePersistence) {
319
319
  await Promise.allSettled([
320
- setupPaginationPersistence(),
321
- setupFiltersPersistence(),
322
- setupHeadersPersistence(),
320
+ setupPaginationPersistence({
321
+ isNested: isNested.value,
322
+ }),
323
+ setupFiltersPersistence({
324
+ isNested: isNested.value,
325
+ }),
326
+ setupHeadersPersistence({
327
+ isNested: isNested.value,
328
+ }),
323
329
  ]);
324
330
  }
325
331
 
@@ -367,14 +373,6 @@ export const tableStoreBody = <Entity extends Identifiable>(
367
373
 
368
374
  await setupStore();
369
375
 
370
- /*
371
- on the first setup the restore path is authoritative.
372
-
373
- a store initialized with a parentId is a list nested in a card page, not a
374
- registry: it shares the query param names with the registry stores, and
375
- the url it would publish into is the card one – so it keeps writing on
376
- change only, and syncs merely on demand
377
- */
378
376
  if (isStoreAlreadySetUp && !disablePersistence && !storeParentId) {
379
377
  await syncPersistence();
380
378
  }