@vc-shell/vc-app-skill 2.0.11-pr247.0f1e741 → 2.0.11-pr247.8c6c87f
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vc-shell/vc-app-skill",
|
|
3
|
-
"version": "2.0.11-pr247.
|
|
3
|
+
"version": "2.0.11-pr247.8c6c87f",
|
|
4
4
|
"description": "AI coding skill for scaffolding and generating VirtoCommerce Shell applications. Works with Claude Code, OpenCode, Gemini, Codex, Cursor.",
|
|
5
5
|
"bin": "./bin/install.cjs",
|
|
6
6
|
"files": [
|
|
@@ -11,21 +11,21 @@ Replace the imperative `VcTable` component (columns array, manual sort handlers,
|
|
|
11
11
|
|
|
12
12
|
Full prop/event mapping:
|
|
13
13
|
|
|
14
|
-
| VcTable prop/event | VcDataTable equivalent
|
|
15
|
-
| -------------------------- |
|
|
16
|
-
| `:columns="columns"` | `<VcColumn>` child components (see RULE 2)
|
|
17
|
-
| `:selected-item-id="id"` | `v-model:active-item-id="id"`
|
|
18
|
-
| `:sort="sortExpression"` | `v-model:sort-field` + `v-model:sort-order`
|
|
19
|
-
| `:multiselect="true"` | `:selection-mode="'multiple'"`
|
|
20
|
-
| `@selection-changed="fn"` | `v-model:selection="ref"`
|
|
21
|
-
| `:search-value="val"` |
|
|
22
|
-
| `@search:change="fn"` | `@search="fn"`
|
|
23
|
-
| `@item-click="fn"` | `@row-click="fn"` (different signature)
|
|
24
|
-
| `@header-click="fn"` | Remove (sort is declarative via v-model)
|
|
25
|
-
| `@scroll:ptr="fn"` | `:pull-to-refresh="true"` + `@pull-refresh="fn"`
|
|
26
|
-
| `:pages` + `:current-page` | `:pagination="{
|
|
27
|
-
| `:active-filter-count` | Remove (managed by global-filters internally)
|
|
28
|
-
| `<!--@vue-generic {T}-->` | Remove (no longer needed)
|
|
14
|
+
| VcTable prop/event | VcDataTable equivalent |
|
|
15
|
+
| -------------------------- | ----------------------------------------------------------------------- |
|
|
16
|
+
| `:columns="columns"` | `<VcColumn>` child components (see RULE 2) |
|
|
17
|
+
| `:selected-item-id="id"` | `v-model:active-item-id="id"` |
|
|
18
|
+
| `:sort="sortExpression"` | `v-model:sort-field` + `v-model:sort-order` |
|
|
19
|
+
| `:multiselect="true"` | `:selection-mode="'multiple'"` |
|
|
20
|
+
| `@selection-changed="fn"` | `v-model:selection="ref"` |
|
|
21
|
+
| `:search-value="val"` | `v-model:search-value` bound to `useTableSearch({ stateKey? })` |
|
|
22
|
+
| `@search:change="fn"` | `@search="fn"` (or drop; use watcher on `searchValue`) |
|
|
23
|
+
| `@item-click="fn"` | `@row-click="fn"` (different signature) |
|
|
24
|
+
| `@header-click="fn"` | Remove (sort is declarative via v-model) |
|
|
25
|
+
| `@scroll:ptr="fn"` | `:pull-to-refresh="true"` + `@pull-refresh="fn"` |
|
|
26
|
+
| `:pages` + `:current-page` | `:pagination="pagination"` from `useDataTablePagination({ stateKey? })` |
|
|
27
|
+
| `:active-filter-count` | Remove (managed by global-filters internally) |
|
|
28
|
+
| `<!--@vue-generic {T}-->` | Remove (no longer needed) |
|
|
29
29
|
|
|
30
30
|
**BEFORE:**
|
|
31
31
|
|
|
@@ -310,7 +310,7 @@ The Props interface keeps `columns` — the parent still passes column definitio
|
|
|
310
310
|
|
|
311
311
|
## RULE 3: Sort Composable Replacement
|
|
312
312
|
|
|
313
|
-
Replace `useTableSort` with `useDataTableSort`. The new composable returns `sortField` and `sortOrder` as separate refs that bind directly to `v-model:sort-field` and `v-model:sort-order`. Remove the `onHeaderClick` handler entirely.
|
|
313
|
+
Replace `useTableSort` with `useDataTableSort`. The new composable returns `sortField` and `sortOrder` as separate refs that bind directly to `v-model:sort-field` and `v-model:sort-order`. Remove the `onHeaderClick` handler entirely. Pass `stateKey` to persist sort to the blade URL (opt-in; omit for no URL persistence).
|
|
314
314
|
|
|
315
315
|
**BEFORE:**
|
|
316
316
|
|
|
@@ -339,6 +339,7 @@ watch(sortExpression, async () => {
|
|
|
339
339
|
import { useDataTableSort } from "@vc-shell/framework";
|
|
340
340
|
|
|
341
341
|
const { sortField, sortOrder, sortExpression } = useDataTableSort({
|
|
342
|
+
stateKey: "my_list", // optional: persists sort to blade URL
|
|
342
343
|
initialField: "createdDate",
|
|
343
344
|
initialDirection: "DESC",
|
|
344
345
|
});
|
|
@@ -684,10 +685,14 @@ Key points:
|
|
|
684
685
|
- The `@filter` event fires when the user applies filters. The `event.filters` object is keyed by filter `id`, with values matching the selected option values.
|
|
685
686
|
- The active filter count badge is managed internally by VcDataTable — no need for a manual `activeFilterCount` computed.
|
|
686
687
|
|
|
687
|
-
## RULE 8: Pagination —
|
|
688
|
+
## RULE 8: Pagination and Search — useDataTablePagination + useTableSearch
|
|
688
689
|
|
|
689
690
|
Replace manual `pages`/`currentPage` computed properties and `@pagination-click` handlers with the `useDataTablePagination` composable. The composable manages skip/page math internally and returns a reactive pagination object that VcDataTable consumes directly.
|
|
690
691
|
|
|
692
|
+
Pass `stateKey` to persist the current page to the blade URL (opt-in). The table `state-key` prop is column-layout persistence only and does not control URL query state.
|
|
693
|
+
|
|
694
|
+
For search, replace a bare `ref("")` with `useTableSearch({ stateKey? })`. The returned `searchValue` ref binds to `v-model:search-value` on VcDataTable. With `stateKey`, the keyword is persisted to the blade URL. Without `stateKey`, it behaves like a plain ref.
|
|
695
|
+
|
|
691
696
|
**BEFORE (composable):**
|
|
692
697
|
|
|
693
698
|
```typescript
|
|
@@ -733,6 +738,7 @@ export function useMyList(options?: { pageSize?: number }) {
|
|
|
733
738
|
});
|
|
734
739
|
|
|
735
740
|
const pagination = useDataTablePagination({
|
|
741
|
+
stateKey: "my_list", // optional: persists current page to blade URL
|
|
736
742
|
pageSize,
|
|
737
743
|
totalCount: computed(() => searchResult.value?.totalCount ?? 0),
|
|
738
744
|
onPageChange: ({ skip }) => loadItems({ ...searchQuery.value, skip }),
|
|
@@ -778,12 +784,15 @@ const { items, pagination, loadItems, loading } = useMyList();
|
|
|
778
784
|
- `totalCount`, `pages`, `currentPage` computed from composable return
|
|
779
785
|
- Manual `onPaginationClick` function in blade pages
|
|
780
786
|
- Manual `skip` calculation `(page - 1) * pageSize`
|
|
787
|
+
- Any `useTableQueryPersistence` or `useTableQueryState().read()` calls — URL query persistence is now handled by `stateKey` on the composables
|
|
781
788
|
|
|
782
789
|
**What to add:**
|
|
783
790
|
|
|
784
|
-
- `useDataTablePagination` import in composable
|
|
791
|
+
- `useDataTablePagination` import in composable (with optional `stateKey` for URL persistence)
|
|
792
|
+
- `useTableSearch({ stateKey? })` in the blade to replace a bare `ref("")` for search
|
|
785
793
|
- `pagination` object in composable return
|
|
786
794
|
- `:pagination="pagination"` and `@pagination-click="pagination.goToPage"` in template
|
|
795
|
+
- `v-model:search-value="searchValue"` from `useTableSearch` in template
|
|
787
796
|
|
|
788
797
|
## Verification
|
|
789
798
|
|
|
@@ -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
|
|