@vc-shell/vc-app-skill 2.0.11 → 2.1.0-pr249.229549
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/package.json +1 -1
- package/runtime/VERSION +1 -1
- package/runtime/agents/migration-agent.md +4 -2
- package/runtime/knowledge/docs/_BUILD_HASH.md +1 -1
- package/runtime/knowledge/docs/ui/components/organisms/vc-data-table/vc-data-table.docs.md +56 -36
- package/runtime/knowledge/docs/ui/composables/useDataTablePagination.docs.md +44 -54
- package/runtime/knowledge/docs/ui/composables/useDataTableSort.docs.md +16 -21
- package/runtime/knowledge/migration-prompts/datatable-migration.md +27 -18
- package/runtime/knowledge/migration-prompts/table-url-state-migration.md +221 -0
- package/runtime/knowledge/patterns/list-blade-pattern.md +85 -41
- package/runtime/vc-app.md +1 -0
- package/runtime/knowledge/docs/core/blade-navigation/table-query-state/useTableQueryState.docs.md +0 -95
- package/runtime/knowledge/docs/core/composables/usePlatformLocaleSync/usePlatformLocaleSync.docs.md +0 -34
- package/runtime/knowledge/docs/core/notifications/composables/useBladeNotifications.docs.md +0 -184
- package/runtime/knowledge/docs/core/notifications/composables/useBroadcastFilter.docs.md +0 -117
- package/runtime/knowledge/docs/core/notifications/composables/useNotificationContext.docs.md +0 -150
- package/runtime/knowledge/docs/core/notifications/composables/useNotificationStore.docs.md +0 -113
- package/runtime/knowledge/docs/modules/assets/assets-details.docs.md +0 -123
- package/runtime/knowledge/docs/shell/dashboard/draggable-dashboard/dashboard-widget-skeleton.docs.md +0 -33
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: table-url-state-migration
|
|
3
|
+
description: AI transformation rules for opting an existing VcDataTable list into URL-query state persistence (sort, search, page) via the composables' stateKey option.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Table URL State: persist sort / search / page in the blade URL
|
|
7
|
+
|
|
8
|
+
This retrofit applies to list blades that **already use `VcDataTable`** with the state
|
|
9
|
+
composables (`useDataTableSort`, `useDataTablePagination`, and search) but do **not** yet
|
|
10
|
+
persist the view to the URL. After it, sort, search keyword, and current page survive a
|
|
11
|
+
reload and travel in a shareable link.
|
|
12
|
+
|
|
13
|
+
It is independent of the VcTable→VcDataTable swap (`datatable-migration`) and of adopting
|
|
14
|
+
the pagination composable (`use-data-table-pagination-migration`). Run those first if the
|
|
15
|
+
file still uses `<VcTable>` or hand-rolled `pages`/`currentPage`.
|
|
16
|
+
|
|
17
|
+
## Concept
|
|
18
|
+
|
|
19
|
+
Each state composable accepts an optional `stateKey`. With it set, the composable reads its
|
|
20
|
+
initial value from the blade URL query on creation and writes changes back:
|
|
21
|
+
|
|
22
|
+
- `useDataTableSort({ stateKey })` → `<stateKey>_sort` (e.g. `createdDate:DESC`)
|
|
23
|
+
- `useTableSearch({ stateKey })` → `<stateKey>_search`
|
|
24
|
+
- `useDataTablePagination({ stateKey })` → `<stateKey>_page` (page 1 is encoded as absent)
|
|
25
|
+
|
|
26
|
+
The `state-key` **prop** on `<VcDataTable>` is unrelated — it persists column layout to
|
|
27
|
+
`localStorage`. Leave it as-is. The composable `stateKey` is the URL query store.
|
|
28
|
+
|
|
29
|
+
## Choosing the key
|
|
30
|
+
|
|
31
|
+
- One key per table, **the same string** across all three composables on that blade.
|
|
32
|
+
- Use snake_case, conventionally `<module>_list` (e.g. `orders_list`). It may equal the
|
|
33
|
+
table `state-key` prop — they are separate stores.
|
|
34
|
+
- The key must be unique among tables that can be visible simultaneously (a list plus a
|
|
35
|
+
child list in the same stack), so their query params do not collide.
|
|
36
|
+
|
|
37
|
+
## Precondition: the blade must be URL-addressable
|
|
38
|
+
|
|
39
|
+
`stateKey` only does something when the blade has a `url` in `defineBlade`. For a
|
|
40
|
+
non-routable or nested blade (no `url`) the query service is a no-op — skip that file and
|
|
41
|
+
note it in the report rather than adding dead options.
|
|
42
|
+
|
|
43
|
+
## RULE 1: Add `stateKey` to `useDataTableSort`
|
|
44
|
+
|
|
45
|
+
**BEFORE:**
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
const { sortField, sortOrder, sortExpression } = useDataTableSort({
|
|
49
|
+
initialField: "createdDate",
|
|
50
|
+
initialDirection: "DESC",
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**AFTER:**
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
const { sortField, sortOrder, sortExpression } = useDataTableSort({
|
|
58
|
+
stateKey: "orders_list",
|
|
59
|
+
initialField: "createdDate",
|
|
60
|
+
initialDirection: "DESC",
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## RULE 2: Own the search keyword with `useTableSearch({ stateKey })`
|
|
65
|
+
|
|
66
|
+
The keyword must live in a ref that `useTableSearch` controls, bound to
|
|
67
|
+
`v-model:search-value`. Three starting shapes:
|
|
68
|
+
|
|
69
|
+
**2a — bare `ref("")` in the blade:** replace it.
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
// BEFORE
|
|
73
|
+
const searchValue = ref("");
|
|
74
|
+
// AFTER
|
|
75
|
+
const { searchValue } = useTableSearch({ stateKey: "orders_list" });
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**2b — event-driven `@search` with no ref:** the table emits `@search` and a handler loads
|
|
79
|
+
directly. Convert to `v-model:search-value` + a watcher.
|
|
80
|
+
|
|
81
|
+
```vue
|
|
82
|
+
<!-- BEFORE -->
|
|
83
|
+
<VcDataTable :searchable="true" @search="onSearchChange" />
|
|
84
|
+
<!-- AFTER -->
|
|
85
|
+
<VcDataTable :searchable="true" v-model:search-value="searchValue" />
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
// BEFORE
|
|
90
|
+
async function onSearchChange(keyword: string | undefined) {
|
|
91
|
+
await loadItems({ ...searchQuery.value, keyword, skip: 0 });
|
|
92
|
+
}
|
|
93
|
+
// AFTER — remove onSearchChange; drive load from the watcher (see RULE 5)
|
|
94
|
+
const { searchValue } = useTableSearch({ stateKey: "orders_list" });
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**2c — the deprecated `useTableQueryState().read()` preview form:** delete the manual
|
|
98
|
+
restore block entirely (see RULE 6); the three composables now seed themselves.
|
|
99
|
+
|
|
100
|
+
In all cases add `v-model:search-value="searchValue"` to the `<VcDataTable>` and keep
|
|
101
|
+
`:searchable="true"`.
|
|
102
|
+
|
|
103
|
+
## RULE 3: Add `stateKey` to `useDataTablePagination`
|
|
104
|
+
|
|
105
|
+
If pagination is created in the blade, add the option directly:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
const pagination = useDataTablePagination({
|
|
109
|
+
stateKey: "orders_list",
|
|
110
|
+
pageSize: 20,
|
|
111
|
+
totalCount,
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
If pagination is created **inside the list composable** (common when `onPageChange` drives
|
|
116
|
+
the load), thread `stateKey` through as a composable option — the blade still owns the key:
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
// composable
|
|
120
|
+
export function useOrdersList(options?: { pageSize?: number; stateKey?: string }) {
|
|
121
|
+
// ...
|
|
122
|
+
const pagination = useDataTablePagination({
|
|
123
|
+
stateKey: options?.stateKey,
|
|
124
|
+
pageSize: options?.pageSize ?? 20,
|
|
125
|
+
totalCount: computed(() => searchResult.value?.totalCount ?? 0),
|
|
126
|
+
onPageChange: ({ skip }) => loadItems({ ...searchQuery.value, skip }),
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
// blade
|
|
133
|
+
const { items, pagination, loadItems } = useOrdersList({ stateKey: "orders_list" });
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Add the `stateKey?: string` field to the composable's options interface.
|
|
137
|
+
|
|
138
|
+
## RULE 4: Seed the initial load from the restored values
|
|
139
|
+
|
|
140
|
+
The first load on mount must read the restored sort, keyword, and page — otherwise the URL
|
|
141
|
+
is restored into the refs but the first fetch ignores them and shows page 1 unfiltered.
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
// BEFORE
|
|
145
|
+
onMounted(() => loadItems({ take: 20, sort: sortExpression.value }));
|
|
146
|
+
// AFTER
|
|
147
|
+
onMounted(() =>
|
|
148
|
+
loadItems({
|
|
149
|
+
take: 20,
|
|
150
|
+
sort: sortExpression.value,
|
|
151
|
+
keyword: searchValue.value || undefined,
|
|
152
|
+
skip: pagination.skip,
|
|
153
|
+
}),
|
|
154
|
+
);
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
For a `reload()` helper, use `skip: pagination.skip` (not a recomputed skip).
|
|
158
|
+
|
|
159
|
+
## RULE 5: Reset to page 1 when the search changes — REQUIRED
|
|
160
|
+
|
|
161
|
+
This is not optional. If the user is on page 3 and types a query that returns fewer pages,
|
|
162
|
+
leaving `_page=3` in the URL makes the next reload request page 3 of the filtered set:
|
|
163
|
+
`skip` overshoots and the table shows an empty "nothing found" state on reload. Resetting to
|
|
164
|
+
page 1 drops `_page` from the URL and keeps the `(search, page)` pair consistent.
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
watch(searchValue, () => pagination.setPage(1));
|
|
168
|
+
// then the reload watcher (debounced) picks up the change:
|
|
169
|
+
watch([sortExpression, searchValue, () => pagination.skip], debounce(load, 300));
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
If search loads through a separate handler (event-driven apps), call `pagination.setPage(1)`
|
|
173
|
+
at the top of that handler before loading with `skip: 0`. Apply the same reset when a global
|
|
174
|
+
filter changes.
|
|
175
|
+
|
|
176
|
+
## RULE 6: Remove the deprecated preview API
|
|
177
|
+
|
|
178
|
+
Delete any `useTableQueryState().read()` or `useTableQueryPersistence` usage and the manual
|
|
179
|
+
ref-seeding it drove — the `stateKey` option replaces it.
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
// DELETE this whole block:
|
|
183
|
+
const restored = useTableQueryState("orders_list").read();
|
|
184
|
+
if (restored.sort) {
|
|
185
|
+
/* ... */
|
|
186
|
+
}
|
|
187
|
+
if (restored.search) searchValue.value = restored.search;
|
|
188
|
+
if (restored.page) pagination.setPage(restored.page);
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Remove the now-unused `useTableQueryState` / `useTableQueryPersistence` import.
|
|
192
|
+
|
|
193
|
+
## Advanced: a shared list base with two views
|
|
194
|
+
|
|
195
|
+
Some apps render two datasets (e.g. flat list vs. category tree) through one
|
|
196
|
+
`VcDataTable`-wrapping base component, each with its own `useDataTablePagination`. Pass the
|
|
197
|
+
same `stateKey` to **both** view paginations (only one view is active at a time, so they
|
|
198
|
+
never write the page param simultaneously) and reset the page on search via the unified
|
|
199
|
+
pagination object the base holds (`props.pagination.setPage(1)`).
|
|
200
|
+
|
|
201
|
+
## What to add
|
|
202
|
+
|
|
203
|
+
- `stateKey` on `useDataTableSort`, `useDataTablePagination`, and `useTableSearch` (same key)
|
|
204
|
+
- `useTableSearch({ stateKey })` import + `v-model:search-value="searchValue"` binding
|
|
205
|
+
- `watch(searchValue, () => pagination.setPage(1))`
|
|
206
|
+
- `skip: pagination.skip` and `keyword: searchValue.value || undefined` in the initial load
|
|
207
|
+
- `stateKey?: string` on the list composable's options interface (when pagination is there)
|
|
208
|
+
|
|
209
|
+
## What to remove
|
|
210
|
+
|
|
211
|
+
- `useTableQueryState().read()` / `useTableQueryPersistence` calls and their imports
|
|
212
|
+
- Event-driven `@search` handlers that only set a keyword and reload (replaced by RULE 2/5)
|
|
213
|
+
- Any manual `currentPage`/`skip` math used purely to restore a page from the URL
|
|
214
|
+
|
|
215
|
+
## Verification
|
|
216
|
+
|
|
217
|
+
1. `npx vue-tsc --noEmit` passes.
|
|
218
|
+
2. Sort a column, search, go to page 2 — the URL gains `<key>_sort`, `<key>_search`,
|
|
219
|
+
`<key>_page`. Reload: the table restores the same view and shows results.
|
|
220
|
+
3. On page 2, type a search — the URL drops `_page` (reset to page 1) and results show.
|
|
221
|
+
4. No `useTableQueryState` / `useTableQueryPersistence` references remain.
|
|
@@ -20,12 +20,14 @@ Generic worked example for a typical list blade (search, sort, pagination, row a
|
|
|
20
20
|
class="tw-grow tw-basis-0"
|
|
21
21
|
:loading="loading"
|
|
22
22
|
:items="xxxList"
|
|
23
|
-
:total-count="totalCount"
|
|
24
|
-
:pagination="
|
|
23
|
+
:total-count="pagination.totalCount"
|
|
24
|
+
:pagination="pagination"
|
|
25
25
|
v-model:active-item-id="selectedItemId"
|
|
26
26
|
state-key="xxx_list"
|
|
27
27
|
v-model:sort-field="sortField"
|
|
28
28
|
v-model:sort-order="sortOrder"
|
|
29
|
+
v-model:search-value="searchValue"
|
|
30
|
+
:searchable="true"
|
|
29
31
|
:pull-to-refresh="true"
|
|
30
32
|
:empty-state="{
|
|
31
33
|
icon: 'lucide-<icon>',
|
|
@@ -34,7 +36,7 @@ Generic worked example for a typical list blade (search, sort, pagination, row a
|
|
|
34
36
|
actionHandler: onAddItem,
|
|
35
37
|
}"
|
|
36
38
|
@row-click="onItemClick"
|
|
37
|
-
@pagination-click="
|
|
39
|
+
@pagination-click="pagination.goToPage"
|
|
38
40
|
@pull-refresh="reload"
|
|
39
41
|
>
|
|
40
42
|
<!-- Each VcColumn is declared explicitly in the template (NOT via v-for).
|
|
@@ -87,9 +89,11 @@ Generic worked example for a typical list blade (search, sort, pagination, row a
|
|
|
87
89
|
|
|
88
90
|
**Key VcDataTable props:**
|
|
89
91
|
|
|
90
|
-
- `state-key` — unique string key for persisting column widths/order/visibility to localStorage. Use snake_case module name (e.g., `"team_list"`, `"catalog_list"`).
|
|
92
|
+
- `state-key` — unique string key for persisting column widths/order/visibility to localStorage. Use snake_case module name (e.g., `"team_list"`, `"catalog_list"`). This prop controls column layout only — URL query persistence (sort/search/page) comes from the composables' `stateKey` option.
|
|
91
93
|
- `v-model:active-item-id` — highlights the currently open row. Bind to `selectedItemId` ref.
|
|
92
94
|
- `v-model:sort-field` and `v-model:sort-order` — two-way sort binding from `useDataTableSort`.
|
|
95
|
+
- `v-model:search-value` and `:searchable="true"` — two-way search binding from `useTableSearch`.
|
|
96
|
+
- `:pagination="pagination"` and `@pagination-click="pagination.goToPage"` — pass the reactive object from `useDataTablePagination` directly.
|
|
93
97
|
- `:pull-to-refresh="true"` — enables mobile pull-to-refresh gesture.
|
|
94
98
|
- `:empty-state` — object with `icon`, `title`, `actionLabel`, `actionHandler`.
|
|
95
99
|
|
|
@@ -191,7 +195,8 @@ If the API defines an enum of statuses, populate the map from those values. If s
|
|
|
191
195
|
```vue
|
|
192
196
|
<script lang="ts" setup>
|
|
193
197
|
import { ref, computed, watch, onMounted } from "vue";
|
|
194
|
-
import { IBladeToolbar, useBlade, useDataTableSort } from "@vc-shell/framework";
|
|
198
|
+
import { IBladeToolbar, useBlade, useDataTableSort, useDataTablePagination, useTableSearch } from "@vc-shell/framework";
|
|
199
|
+
import { debounce } from "lodash-es";
|
|
195
200
|
import useXxxs from "../composables/useXxxs";
|
|
196
201
|
import { useI18n } from "vue-i18n";
|
|
197
202
|
|
|
@@ -211,19 +216,32 @@ defineBlade({
|
|
|
211
216
|
// 2. Core composables
|
|
212
217
|
const { openBlade, exposeToChildren, param } = useBlade();
|
|
213
218
|
const { t } = useI18n({ useScope: "global" });
|
|
214
|
-
const { getXxxs,
|
|
219
|
+
const { getXxxs, loading, xxxList, totalCount } = useXxxs();
|
|
215
220
|
|
|
216
|
-
// 3. Sort —
|
|
221
|
+
// 3. Sort — stateKey persists sort to blade URL; omit stateKey to opt out
|
|
217
222
|
const { sortField, sortOrder, sortExpression } = useDataTableSort({
|
|
223
|
+
stateKey: "xxx_list",
|
|
218
224
|
initialField: "createdDate",
|
|
219
225
|
initialDirection: "DESC",
|
|
220
226
|
});
|
|
221
227
|
|
|
222
|
-
// 4.
|
|
228
|
+
// 4. Search — stateKey persists keyword to blade URL; omit stateKey to opt out
|
|
229
|
+
const { searchValue } = useTableSearch({ stateKey: "xxx_list" });
|
|
230
|
+
|
|
231
|
+
// 5. Pagination — stateKey persists current page to blade URL; omit stateKey to opt out
|
|
232
|
+
// Note: state-key on <VcDataTable> is column-layout persistence only (localStorage).
|
|
233
|
+
// URL query persistence (sort/search/page) is controlled by the stateKey option here.
|
|
234
|
+
const pagination = useDataTablePagination({
|
|
235
|
+
stateKey: "xxx_list",
|
|
236
|
+
pageSize: 20,
|
|
237
|
+
totalCount,
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// 6. UI state
|
|
223
241
|
const selectedItemId = ref<string | undefined>();
|
|
224
242
|
const title = computed(() => t("XXX.PAGES.LIST.TITLE"));
|
|
225
243
|
|
|
226
|
-
//
|
|
244
|
+
// 7. Toolbar
|
|
227
245
|
const bladeToolbar = ref<IBladeToolbar[]>([
|
|
228
246
|
{
|
|
229
247
|
id: "refresh",
|
|
@@ -241,7 +259,7 @@ const bladeToolbar = ref<IBladeToolbar[]>([
|
|
|
241
259
|
},
|
|
242
260
|
]);
|
|
243
261
|
|
|
244
|
-
//
|
|
262
|
+
// 8. Status variant mapping (if status column exists)
|
|
245
263
|
const statusVariant = (status: string | undefined): "info" | "warning" | "danger" | "success" | "primary" => {
|
|
246
264
|
const map: Record<string, "info" | "warning" | "danger" | "success" | "primary"> = {
|
|
247
265
|
// TODO: adjust status variants to match your API statuses
|
|
@@ -256,12 +274,23 @@ const statusVariant = (status: string | undefined): "info" | "warning" | "danger
|
|
|
256
274
|
return map[status ?? ""] ?? "info";
|
|
257
275
|
};
|
|
258
276
|
|
|
259
|
-
//
|
|
260
|
-
|
|
261
|
-
await getXxxs({
|
|
262
|
-
|
|
277
|
+
// 9. Load function — explicit; called on mount and by watchers
|
|
278
|
+
const load = async () => {
|
|
279
|
+
await getXxxs({
|
|
280
|
+
sort: sortExpression.value,
|
|
281
|
+
keyword: searchValue.value || undefined,
|
|
282
|
+
skip: pagination.skip,
|
|
283
|
+
take: 20,
|
|
284
|
+
});
|
|
285
|
+
};
|
|
263
286
|
|
|
264
|
-
//
|
|
287
|
+
// 10. Reset to page 1 when search changes, then reload
|
|
288
|
+
watch(searchValue, () => pagination.setPage(1));
|
|
289
|
+
|
|
290
|
+
// 11. Reload when sort, search, or page changes (debounced to coalesce rapid changes)
|
|
291
|
+
watch([sortExpression, searchValue, () => pagination.skip], debounce(load, 300));
|
|
292
|
+
|
|
293
|
+
// 12. Track selected row when param changes (e.g., navigating directly to URL)
|
|
265
294
|
watch(
|
|
266
295
|
() => param.value,
|
|
267
296
|
() => {
|
|
@@ -270,30 +299,15 @@ watch(
|
|
|
270
299
|
{ immediate: true },
|
|
271
300
|
);
|
|
272
301
|
|
|
273
|
-
//
|
|
274
|
-
onMounted(
|
|
275
|
-
await reload();
|
|
276
|
-
});
|
|
302
|
+
// 13. Load data on mount
|
|
303
|
+
onMounted(() => load());
|
|
277
304
|
|
|
278
|
-
//
|
|
305
|
+
// 14. Reload helper — used by toolbar and exposed to child blades
|
|
279
306
|
const reload = async () => {
|
|
280
|
-
await
|
|
281
|
-
...searchQuery.value,
|
|
282
|
-
skip: (currentPage.value - 1) * (searchQuery.value?.take ?? 20),
|
|
283
|
-
sort: sortExpression.value,
|
|
284
|
-
});
|
|
285
|
-
};
|
|
286
|
-
|
|
287
|
-
// 11. Pagination
|
|
288
|
-
const onPaginationClick = async (page: number) => {
|
|
289
|
-
await getXxxs({
|
|
290
|
-
...searchQuery.value,
|
|
291
|
-
skip: (page - 1) * (searchQuery.value?.take ?? 20),
|
|
292
|
-
sort: sortExpression.value,
|
|
293
|
-
});
|
|
307
|
+
await load();
|
|
294
308
|
};
|
|
295
309
|
|
|
296
|
-
//
|
|
310
|
+
// 15. Row click → open details blade
|
|
297
311
|
const onItemClick = (event: { data: { id?: string } }) => {
|
|
298
312
|
openBlade({
|
|
299
313
|
name: "XxxDetails",
|
|
@@ -310,14 +324,14 @@ const onItemClick = (event: { data: { id?: string } }) => {
|
|
|
310
324
|
});
|
|
311
325
|
};
|
|
312
326
|
|
|
313
|
-
//
|
|
327
|
+
// 16. Add new item — open details blade with no param (creates new)
|
|
314
328
|
function onAddItem() {
|
|
315
329
|
openBlade({
|
|
316
330
|
name: "XxxDetails",
|
|
317
331
|
});
|
|
318
332
|
}
|
|
319
333
|
|
|
320
|
-
//
|
|
334
|
+
// 17. Expose reload to child blades so details can trigger list refresh after save/delete
|
|
321
335
|
exposeToChildren({ reload });
|
|
322
336
|
</script>
|
|
323
337
|
```
|
|
@@ -334,6 +348,7 @@ exposeToChildren({ reload });
|
|
|
334
348
|
|
|
335
349
|
```ts
|
|
336
350
|
const { sortField, sortOrder, sortExpression } = useDataTableSort({
|
|
351
|
+
stateKey: "xxx_list", // optional: persists sort to blade URL
|
|
337
352
|
initialField: "createdDate",
|
|
338
353
|
initialDirection: "DESC",
|
|
339
354
|
});
|
|
@@ -342,16 +357,45 @@ const { sortField, sortOrder, sortExpression } = useDataTableSort({
|
|
|
342
357
|
- `sortField` — reactive ref, two-way bound to `VcDataTable` via `v-model:sort-field`
|
|
343
358
|
- `sortOrder` — reactive ref (`"ASC"` | `"DESC"`), two-way bound via `v-model:sort-order`
|
|
344
359
|
- `sortExpression` — computed string `"field:DIR"` (e.g., `"createdDate:DESC"`), passed to the API
|
|
360
|
+
- `stateKey` — when provided, the composable reads the initial sort from the URL and writes back on change
|
|
345
361
|
|
|
346
|
-
###
|
|
362
|
+
### `useTableSearch` — search with optional URL persistence
|
|
347
363
|
|
|
348
364
|
```ts
|
|
349
|
-
|
|
350
|
-
|
|
365
|
+
const { searchValue } = useTableSearch({ stateKey: "xxx_list" });
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
- Returns `searchValue` ref, two-way bound via `v-model:search-value` on `VcDataTable`.
|
|
369
|
+
- With `stateKey`, restores and persists the keyword to the blade URL.
|
|
370
|
+
- Without `stateKey`, behaves like a plain `ref("")`.
|
|
371
|
+
|
|
372
|
+
### `useDataTablePagination` — pagination with optional URL persistence
|
|
373
|
+
|
|
374
|
+
```ts
|
|
375
|
+
const pagination = useDataTablePagination({
|
|
376
|
+
stateKey: "xxx_list", // optional: persists current page to blade URL
|
|
377
|
+
pageSize: 20,
|
|
378
|
+
totalCount,
|
|
351
379
|
});
|
|
352
380
|
```
|
|
353
381
|
|
|
354
|
-
|
|
382
|
+
- Pass `pagination` directly as `:pagination="pagination"` and `@pagination-click="pagination.goToPage"`.
|
|
383
|
+
- Access `pagination.skip` for API calls, `pagination.setPage(1)` to reset on search change.
|
|
384
|
+
- `stateKey` persists the current page to the blade URL; omit to opt out.
|
|
385
|
+
- The table `state-key` prop is column-layout persistence (localStorage) only and is independent.
|
|
386
|
+
|
|
387
|
+
### Load + watcher pattern
|
|
388
|
+
|
|
389
|
+
```ts
|
|
390
|
+
const load = async () => {
|
|
391
|
+
await getXxxs({ sort: sortExpression.value, keyword: searchValue.value || undefined, skip: pagination.skip, take: 20 });
|
|
392
|
+
};
|
|
393
|
+
watch(searchValue, () => pagination.setPage(1));
|
|
394
|
+
watch([sortExpression, searchValue, () => pagination.skip], debounce(load, 300));
|
|
395
|
+
onMounted(() => load());
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
Watch `[sortExpression, searchValue, () => pagination.skip]` to reload whenever any query dimension changes. Reset to page 1 when search changes before the reload watcher fires.
|
|
355
399
|
|
|
356
400
|
### `exposeToChildren`
|
|
357
401
|
|
package/runtime/vc-app.md
CHANGED
|
@@ -1613,6 +1613,7 @@ Map topic headings to migration prompt files and pattern files:
|
|
|
1613
1613
|
| Assets API / useAssets / useAssetsManager / use-assets-migration | `use-assets-migration` | `{KNOWLEDGE_BASE}/migration-prompts/use-assets-migration.md` | `{KNOWLEDGE_BASE}/patterns/assets-management.md` |
|
|
1614
1614
|
| Manual Migration Audit / useExternalWidgets / moment / useFunctions / resolveBladeByName / onParentCall / manual-migration-audit | `manual-migration-audit` | `{KNOWLEDGE_BASE}/migration-prompts/manual-migration-audit.md` | — |
|
|
1615
1615
|
| Pagination / useDataTablePagination / use-data-table-pagination-audit | `use-data-table-pagination-audit` | `{KNOWLEDGE_BASE}/migration-prompts/use-data-table-pagination-migration.md` | — |
|
|
1616
|
+
| Table URL State / stateKey / useTableQueryState / table-url-state-audit | `table-url-state-audit` | `{KNOWLEDGE_BASE}/migration-prompts/table-url-state-migration.md` | `{KNOWLEDGE_BASE}/patterns/list-blade-pattern.md` |
|
|
1616
1617
|
|
|
1617
1618
|
Build the `topics` array for the migration-agent using the canonical topic names above.
|
|
1618
1619
|
|
package/runtime/knowledge/docs/core/blade-navigation/table-query-state/useTableQueryState.docs.md
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: useTableQueryState
|
|
3
|
-
category: composables
|
|
4
|
-
group: data
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# useTableQueryState
|
|
8
|
-
|
|
9
|
-
Reads the table view state (`sort`, `search`, `page`) that `VcDataTable` persists to the URL query for URL-addressable blades. A list page calls `read()` in `setup` to seed its own refs from the restored state before its loader runs, so a reload makes one request.
|
|
10
|
-
|
|
11
|
-
Read-only: writing the view state to the URL stays inside `VcDataTable`. This composable only exposes the restored values to the page.
|
|
12
|
-
|
|
13
|
-
## When to Use
|
|
14
|
-
|
|
15
|
-
- A list blade is URL-addressable (workspace or routable) and uses `VcDataTable` with a `state-key`
|
|
16
|
-
- You want a single, coordinated load on reload (sort + search + page restored together)
|
|
17
|
-
- You are wiring the page's combined loader watcher and need the restored seed values
|
|
18
|
-
|
|
19
|
-
## When NOT to Use
|
|
20
|
-
|
|
21
|
-
- The table is standalone or its blade has no URL — there is nothing to restore (`read()` returns `{}`)
|
|
22
|
-
- You only need the live view state — that already lives in your own `sortField`/`searchValue`/`currentPage` refs
|
|
23
|
-
- You want to _write_ to the URL — that is automatic; do not persist manually
|
|
24
|
-
|
|
25
|
-
## Basic Usage
|
|
26
|
-
|
|
27
|
-
```ts
|
|
28
|
-
import { ref, watch } from "vue";
|
|
29
|
-
import { useDataTableSort, useTableQueryState, useFunctions } from "@vc-shell/framework";
|
|
30
|
-
|
|
31
|
-
const PAGE_SIZE = 20;
|
|
32
|
-
const { debounce } = useFunctions();
|
|
33
|
-
const { sortField, sortOrder, sortExpression } = useDataTableSort({
|
|
34
|
-
initialField: "createdDate",
|
|
35
|
-
initialDirection: "DESC",
|
|
36
|
-
});
|
|
37
|
-
const searchValue = ref<string>();
|
|
38
|
-
const currentPage = ref(1);
|
|
39
|
-
|
|
40
|
-
// Seed refs from the URL before the loader runs.
|
|
41
|
-
const restored = useTableQueryState("offers_list").read();
|
|
42
|
-
if (restored.sort) {
|
|
43
|
-
const [field, dir] = restored.sort.split(":");
|
|
44
|
-
sortField.value = field;
|
|
45
|
-
sortOrder.value = dir === "DESC" ? -1 : 1;
|
|
46
|
-
}
|
|
47
|
-
if (restored.search) searchValue.value = restored.search;
|
|
48
|
-
if (restored.page) currentPage.value = restored.page;
|
|
49
|
-
|
|
50
|
-
function load() {
|
|
51
|
-
return loadItems({
|
|
52
|
-
sort: sortExpression.value,
|
|
53
|
-
keyword: searchValue.value || undefined,
|
|
54
|
-
skip: (currentPage.value - 1) * PAGE_SIZE,
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
load();
|
|
59
|
-
watch(searchValue, () => (currentPage.value = 1));
|
|
60
|
-
watch([sortExpression, searchValue, currentPage], debounce(load, 300));
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
## API
|
|
64
|
-
|
|
65
|
-
### Parameters
|
|
66
|
-
|
|
67
|
-
| Parameter | Type | Default | Description |
|
|
68
|
-
| ---------- | --------------------- | ----------- | ------------------------------------------------------------------------- |
|
|
69
|
-
| `stateKey` | `string \| undefined` | `undefined` | The same `state-key` passed to `VcDataTable` for this table (namespacing) |
|
|
70
|
-
|
|
71
|
-
### Returns
|
|
72
|
-
|
|
73
|
-
| Property | Type | Description |
|
|
74
|
-
| -------- | ----------------------- | ------------------------------------------------------------------------------------------------------ |
|
|
75
|
-
| `read` | `() => TableQueryPatch` | Reads the restored `{ sort?, search?, page? }` from the URL. Returns `{}` when no service is available |
|
|
76
|
-
|
|
77
|
-
`TableQueryPatch`: `{ sort?: string; search?: string; page?: number }` — `sort` is a `"field:ASC"` / `"field:DESC"` expression, `page` is 1-based.
|
|
78
|
-
|
|
79
|
-
## Details
|
|
80
|
-
|
|
81
|
-
- **Read in `setup`**: `VcDataTable` does not emit restore events on init; the page reads the state and seeds its refs in `setup`. This avoids a separate load per restored field.
|
|
82
|
-
- **No-op without a service**: When no blade provides the persistence service (standalone table, non-URL blade), `read()` returns `{}`, so you can call it unconditionally.
|
|
83
|
-
- **Write-back is automatic**: View state → URL (debounced `router.replace`) is handled by `VcDataTable` via `useTableQueryPersistence`. Pages do not persist manually.
|
|
84
|
-
|
|
85
|
-
## Tips
|
|
86
|
-
|
|
87
|
-
- Call `read()` once, synchronously, before the loader. Seeding after it runs causes an extra load.
|
|
88
|
-
- Use `useDataTablePagination`'s `setPage(n)` to seed the page without firing `onPageChange`.
|
|
89
|
-
- Pass the same `state-key` you give `VcDataTable`, so the URL keys match.
|
|
90
|
-
|
|
91
|
-
## Related
|
|
92
|
-
|
|
93
|
-
- [`useDataTableSort`](../../../ui/composables/useDataTableSort.docs.md) — sort state composable for `VcDataTable`
|
|
94
|
-
- [`useDataTablePagination`](../../../ui/composables/useDataTablePagination.docs.md) — pagination state composable (`setPage` for restore seed)
|
|
95
|
-
- `VcDataTable` → _URL query persistence_ — the write-back side of this contract
|
package/runtime/knowledge/docs/core/composables/usePlatformLocaleSync/usePlatformLocaleSync.docs.md
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: usePlatformLocaleSync
|
|
3
|
-
category: composables
|
|
4
|
-
group: user
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# usePlatformLocaleSync
|
|
8
|
-
|
|
9
|
-
One-way reactive bridge from the VirtoCommerce platform's locale storage key (`NG_TRANSLATE_LANG_KEY`, set by AngularJS + angular-translate) to the shell's language service.
|
|
10
|
-
|
|
11
|
-
Call this composable only when the shell runs embedded inside the platform — `useShellBootstrap` invokes it automatically when `options.isEmbedded === true`. In standalone mode the shell owns its own locale via `VC_LANGUAGE_SETTINGS`, and this composable should not be used.
|
|
12
|
-
|
|
13
|
-
## When to Use
|
|
14
|
-
|
|
15
|
-
- Never call directly from feature code. This is a framework-internal sync primitive.
|
|
16
|
-
- It is invoked once per `VcApp` mount from `useShellBootstrap`.
|
|
17
|
-
|
|
18
|
-
## Behaviour
|
|
19
|
-
|
|
20
|
-
- Reads `localStorage["NG_TRANSLATE_LANG_KEY"]` via VueUse's `useLocalStorage`, which subscribes to `storage` events for cross-tab reactivity.
|
|
21
|
-
- On setup, if the value is non-empty, calls `LanguageService.setLocale(value)`. `setLocale` normalises the value (e.g. `en-US` → `en-us`), falls back to `en` for unsupported locales, updates `vue-i18n`, reconfigures `vee-validate`, and persists to `VC_LANGUAGE_SETTINGS`.
|
|
22
|
-
- On subsequent changes of the platform key, re-applies the value.
|
|
23
|
-
- Skips empty strings (platform clearing the key does not blank the shell locale).
|
|
24
|
-
- Skips values equal to `currentLocale` to avoid redundant re-configuration.
|
|
25
|
-
|
|
26
|
-
## How It Works
|
|
27
|
-
|
|
28
|
-
`useLocalStorage("NG_TRANSLATE_LANG_KEY", "")` returns a `Ref<string>` that VueUse keeps in sync with `localStorage` and the DOM `storage` event (which fires in tabs other than the writer). The composable applies the current ref value once synchronously and then registers a `watch` on it; any cross-tab mutation flows through the ref into `setLocale`.
|
|
29
|
-
|
|
30
|
-
The watcher is bound to the active effect scope (typically `VcApp`'s setup). When `VcApp` unmounts, the watcher stops; `useLocalStorage` cleans up its own `storage` listener.
|
|
31
|
-
|
|
32
|
-
## Relationship to `VC_LANGUAGE_SETTINGS`
|
|
33
|
-
|
|
34
|
-
The sync is strictly one-directional. `setLocale` writes to `VC_LANGUAGE_SETTINGS` as a side effect, but this composable never writes to `NG_TRANSLATE_LANG_KEY`. In embedded mode the in-shell `LanguageSelector` is unreachable (it lives inside `UserDropdownButton`, which is hidden when `isEmbedded` is `true`), so there is no competing writer from the shell side.
|