@vc-shell/vc-app-skill 2.0.0-alpha.33-pr220.455e322 → 2.0.0-alpha.34
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/CHANGELOG.md +27 -0
- package/README.md +35 -11
- package/package.json +1 -1
- package/runtime/VERSION +1 -1
- package/runtime/knowledge/docs/_BUILD_HASH.md +1 -1
- package/runtime/knowledge/docs/core/composables/useAssetsManager/useAssetsManager.docs.md +4 -4
- package/runtime/knowledge/docs/core/composables/useBladeWidgets/index.docs.md +37 -37
- package/runtime/knowledge/docs/core/composables/useMenuExpanded/index.docs.md +1 -1
- package/runtime/knowledge/docs/core/composables/usePlatformLocaleSync/usePlatformLocaleSync.docs.md +28 -0
- package/runtime/knowledge/docs/core/composables/useSettings/useSettings.docs.md +2 -2
- package/runtime/knowledge/docs/core/plugins/extension-points/extension-points.docs.md +38 -37
- package/runtime/knowledge/docs/core/plugins/modularity/modularity.docs.md +7 -7
- package/runtime/knowledge/docs/shell/components/settings-menu/settings-menu.docs.md +1 -1
- package/runtime/knowledge/docs/ui/components/organisms/vc-data-table/composables/table-composables.docs.md +81 -16
- package/runtime/knowledge/docs/ui/components/organisms/vc-data-table/vc-data-table.docs.md +40 -2
- package/runtime/knowledge/docs/ui/components/organisms/vc-gallery/vc-gallery.docs.md +17 -17
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,30 @@
|
|
|
1
|
+
# [2.0.0-alpha.34](https://github.com/VirtoCommerce/vc-shell/compare/v2.0.0-alpha.33...v2.0.0-alpha.34) (2026-04-22)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **datatable:** normalise date-range filter values to YYYY-MM-DD ([d89864a](https://github.com/VirtoCommerce/vc-shell/commit/d89864aa635e7479137fb0ad501197adf335f99e))
|
|
7
|
+
* **migrate:** datatable prompt forbids 'removed filters for green build' shortcut ([af5ae8e](https://github.com/VirtoCommerce/vc-shell/commit/af5ae8e4259b435729e6cea9e4c61d74b41d3952))
|
|
8
|
+
* **migrate:** use-data-table-pagination prompt — per-file scope skip ([8949f01](https://github.com/VirtoCommerce/vc-shell/commit/8949f01da97b69c15fc6e3a2fca951608731979c))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### chore
|
|
12
|
+
|
|
13
|
+
* **scripts:** normalize yarn scripts per industry standards ([1cdd0cb](https://github.com/VirtoCommerce/vc-shell/commit/1cdd0cb517d2436ef2a509c6b6c358f6a48630d1))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Features
|
|
17
|
+
|
|
18
|
+
* **migrate:** add use-data-table-pagination-audit + AI migration prompt ([0c2e7b6](https://github.com/VirtoCommerce/vc-shell/commit/0c2e7b6efba387d9fafe04e5bdcd21ea20500259))
|
|
19
|
+
* **migrate:** expand v2 migration tooling — icon/asset/audit prompts and blade-event cleanup ([f4788d4](https://github.com/VirtoCommerce/vc-shell/commit/f4788d4d9c588157ca5c11facfe558a69c254c2e)), closes [#41](https://github.com/VirtoCommerce/vc-shell/issues/41)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### BREAKING CHANGES
|
|
23
|
+
|
|
24
|
+
* **scripts:** for external consumers: old script names
|
|
25
|
+
(storybook-serve, build-framework, check-locales etc) are removed.
|
|
26
|
+
Legacy aliases are deliberately not provided — they would perpetuate
|
|
27
|
+
the non-standard naming this commit eliminates.
|
|
1
28
|
# [2.0.0-alpha.33](https://github.com/VirtoCommerce/vc-shell/compare/v2.0.0-alpha.32...v2.0.0-alpha.33) (2026-04-14)
|
|
2
29
|
|
|
3
30
|
### Bug Fixes
|
package/README.md
CHANGED
|
@@ -14,6 +14,7 @@ The skill covers:
|
|
|
14
14
|
- Enhancing existing modules with surgical modifications (add columns, fields, toolbar actions, logic, blade links)
|
|
15
15
|
- Generating full multi-module applications from a free-text prompt (design command)
|
|
16
16
|
- Promoting prototype modules from mock data to real API clients
|
|
17
|
+
- Migrating existing apps to the latest `@vc-shell/framework` version (runs the CLI migrator, regenerates API clients, completes AI-assisted manual refactors)
|
|
17
18
|
- Following vc-shell conventions: Vue 3 + TypeScript, Tailwind with `tw-` prefix, `<script setup>`, BEM class names
|
|
18
19
|
|
|
19
20
|
## Installation
|
|
@@ -154,6 +155,28 @@ When you generate a module without an API client, it uses mock data with `// vc-
|
|
|
154
155
|
- **Phase 4: Code Transformation** — replaces mock code with real API calls, renames fields, updates locales
|
|
155
156
|
- **Phase 5: Cleanup** — type-checks, removes prototype marker on success
|
|
156
157
|
|
|
158
|
+
### `/vc-app migrate`
|
|
159
|
+
|
|
160
|
+
Fully automatic migration of an existing app to the latest `@vc-shell/framework` version. Runs the CLI migrator for mechanical transforms, regenerates API clients with the new Interface-style output, installs updated dependencies, and dispatches AI agents to complete manual refactors flagged in `MIGRATION_REPORT.md`.
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
/vc-app migrate
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Flow:
|
|
167
|
+
|
|
168
|
+
- **Step 1: Pre-flight** — verifies the project uses `@vc-shell/framework`; warns on uncommitted changes
|
|
169
|
+
- **Step 2: CLI migrator** — runs `@vc-shell/migrate --update-deps` to apply mechanical transforms and align peer-dependency versions (including `@vc-shell/*`, ESLint, Vite, TypeScript, VueUse, `vue-router` and other curated peer deps)
|
|
170
|
+
- **Step 2.5: API client regeneration** — adds `APP_TYPE_STYLE=Interface`, runs `generate-api-client`, verifies types compile
|
|
171
|
+
- **Step 3: Install** — `yarn install` to refresh the lockfile
|
|
172
|
+
- **Step 4: Parse report** — reads `MIGRATION_REPORT.md`, maps "Manual Migration Required" topics to migration prompts and patterns
|
|
173
|
+
- **Step 5: AI migration** — dispatches `migration-agent` on affected files per topic (supports partial resume via `.vc-app-migrate-state.json`)
|
|
174
|
+
- **Step 6: Verify** — runs `vue-tsc --noEmit` and `yarn build`, iteratively fixes type errors
|
|
175
|
+
- **Step 6.5: Format** — runs Prettier across the project
|
|
176
|
+
- **Step 7: Summary** — updates the report and prints a completion summary with remaining issues
|
|
177
|
+
|
|
178
|
+
Handles topics: widgets, form management (`useBladeForm`), injection-key renames, NSwag class-to-interface, blade props simplification, notifications, VcTable → VcDataTable, icon replacements, assets API, pagination, and a manual-audit catch-all.
|
|
179
|
+
|
|
157
180
|
## Update
|
|
158
181
|
|
|
159
182
|
```bash
|
|
@@ -215,17 +238,18 @@ cli/vc-app-skill/
|
|
|
215
238
|
|
|
216
239
|
The skill dispatches specialized agents for different tasks:
|
|
217
240
|
|
|
218
|
-
| Agent | Purpose
|
|
219
|
-
| ------------------------- |
|
|
220
|
-
| `api-analyzer` | Discovers entities and CRUD methods in API client files
|
|
221
|
-
| `list-blade-generator` | Generates list blade + plural composable
|
|
222
|
-
| `details-blade-generator` | Generates details blade + singular composable
|
|
223
|
-
| `locales-generator` | Scans generated files for i18n keys, writes locale JSON
|
|
224
|
-
| `module-assembler` | Creates barrel files and registers module (create + append modes)
|
|
225
|
-
| `type-checker` | Runs vue-tsc, iteratively fixes type errors
|
|
226
|
-
| `promote-agent` | Transforms mock composables/blades/locales to use real API
|
|
227
|
-
| `
|
|
228
|
-
| `
|
|
241
|
+
| Agent | Purpose |
|
|
242
|
+
| ------------------------- | -------------------------------------------------------------------------- |
|
|
243
|
+
| `api-analyzer` | Discovers entities and CRUD methods in API client files |
|
|
244
|
+
| `list-blade-generator` | Generates list blade + plural composable |
|
|
245
|
+
| `details-blade-generator` | Generates details blade + singular composable |
|
|
246
|
+
| `locales-generator` | Scans generated files for i18n keys, writes locale JSON |
|
|
247
|
+
| `module-assembler` | Creates barrel files and registers module (create + append modes) |
|
|
248
|
+
| `type-checker` | Runs vue-tsc, iteratively fixes type errors |
|
|
249
|
+
| `promote-agent` | Transforms mock composables/blades/locales to use real API |
|
|
250
|
+
| `migration-agent` | Applies AI-assisted manual migrations on files flagged by the migrate flow |
|
|
251
|
+
| `module-analyzer` | Analyzes existing module structure (read-only) |
|
|
252
|
+
| `blade-enhancer` | Surgical edits to existing blades/composables/locales |
|
|
229
253
|
|
|
230
254
|
### Running locally
|
|
231
255
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vc-shell/vc-app-skill",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.34",
|
|
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": [
|
package/runtime/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.0.0-alpha.
|
|
1
|
+
2.0.0-alpha.34
|
|
@@ -1 +1 @@
|
|
|
1
|
-
Synced from framework at commit
|
|
1
|
+
Synced from framework at commit cec6c9078 on 2026-04-22T09:43:16.584Z
|
|
@@ -56,14 +56,14 @@ import { toRef } from "vue";
|
|
|
56
56
|
|
|
57
57
|
const assets = useAssetsManager(
|
|
58
58
|
computed({
|
|
59
|
-
get: () =>
|
|
59
|
+
get: () => offer.value.images ?? [],
|
|
60
60
|
set: (val) => {
|
|
61
|
-
|
|
61
|
+
offer.value.images = val;
|
|
62
62
|
},
|
|
63
63
|
}),
|
|
64
64
|
{
|
|
65
|
-
uploadPath: () => `
|
|
66
|
-
confirmRemove: () => showConfirmation(t("
|
|
65
|
+
uploadPath: () => `offers/${offer.value?.id ?? "new"}`,
|
|
66
|
+
confirmRemove: () => showConfirmation(t("OFFERS.ALERTS.IMAGE_DELETE")),
|
|
67
67
|
},
|
|
68
68
|
);
|
|
69
69
|
```
|
|
@@ -22,21 +22,21 @@ import { useBladeWidgets } from "@vc-shell/framework";
|
|
|
22
22
|
|
|
23
23
|
const { refreshAll } = useBladeWidgets([
|
|
24
24
|
{
|
|
25
|
-
id: "
|
|
25
|
+
id: "OffersWidget",
|
|
26
26
|
icon: "lucide-tag",
|
|
27
|
-
title: "
|
|
28
|
-
badge:
|
|
29
|
-
loading:
|
|
30
|
-
onClick: () => openBlade({ name: "
|
|
31
|
-
onRefresh: () =>
|
|
27
|
+
title: "OFFERS.TITLE",
|
|
28
|
+
badge: offersCount,
|
|
29
|
+
loading: offersLoading,
|
|
30
|
+
onClick: () => openBlade({ name: "OffersList" }),
|
|
31
|
+
onRefresh: () => reloadOffers(),
|
|
32
32
|
},
|
|
33
33
|
{
|
|
34
|
-
id: "
|
|
34
|
+
id: "ReviewsWidget",
|
|
35
35
|
icon: "lucide-star",
|
|
36
|
-
title: "
|
|
37
|
-
badge:
|
|
36
|
+
title: "REVIEWS.TITLE",
|
|
37
|
+
badge: reviewsCount,
|
|
38
38
|
isVisible: computed(() => !!item.value?.id),
|
|
39
|
-
onClick: () => openBlade({ name: "
|
|
39
|
+
onClick: () => openBlade({ name: "ReviewsList" }),
|
|
40
40
|
},
|
|
41
41
|
]);
|
|
42
42
|
|
|
@@ -119,7 +119,7 @@ import { MessageWidget } from "./components/widgets";
|
|
|
119
119
|
registerExternalWidget({
|
|
120
120
|
id: "MessageWidget",
|
|
121
121
|
component: markRaw(MessageWidget),
|
|
122
|
-
targetBlades: ["
|
|
122
|
+
targetBlades: ["ProductDetails", "OrderDetails"],
|
|
123
123
|
isVisible: (blade?: BladeDescriptor) => !!blade?.param,
|
|
124
124
|
});
|
|
125
125
|
```
|
|
@@ -176,61 +176,61 @@ import { useBladeWidgets } from "@vc-shell/framework";
|
|
|
176
176
|
const { refresh, refreshAll } = useBladeWidgets([]);
|
|
177
177
|
|
|
178
178
|
async function save() {
|
|
179
|
-
await api.
|
|
179
|
+
await api.saveProduct(product.value);
|
|
180
180
|
refreshAll(); // refresh all widgets (including MessageWidget)
|
|
181
181
|
// or: refresh("MessageWidget"); // refresh a specific widget by ID
|
|
182
182
|
}
|
|
183
183
|
</script>
|
|
184
184
|
```
|
|
185
185
|
|
|
186
|
-
## Recipe:
|
|
186
|
+
## Recipe: Product Detail Blade with Multiple Widgets
|
|
187
187
|
|
|
188
188
|
```vue
|
|
189
189
|
<script setup lang="ts">
|
|
190
190
|
import { ref, computed } from "vue";
|
|
191
191
|
import { useBladeWidgets, defineBladeContext } from "@vc-shell/framework";
|
|
192
192
|
|
|
193
|
-
const
|
|
194
|
-
const
|
|
195
|
-
const
|
|
196
|
-
const
|
|
193
|
+
const product = ref({ id: "prod-1", name: "Widget A" });
|
|
194
|
+
const offersCount = ref(0);
|
|
195
|
+
const reviewsCount = ref(0);
|
|
196
|
+
const offersLoading = ref(false);
|
|
197
197
|
|
|
198
|
-
// Expose
|
|
199
|
-
defineBladeContext(computed(() => ({ id:
|
|
198
|
+
// Expose product data to widgets
|
|
199
|
+
defineBladeContext(computed(() => ({ id: product.value?.id })));
|
|
200
200
|
|
|
201
|
-
async function
|
|
202
|
-
|
|
201
|
+
async function reloadOffers() {
|
|
202
|
+
offersLoading.value = true;
|
|
203
203
|
try {
|
|
204
|
-
const result = await api.
|
|
205
|
-
|
|
204
|
+
const result = await api.searchOffers({ productId: product.value.id });
|
|
205
|
+
offersCount.value = result.totalCount;
|
|
206
206
|
} finally {
|
|
207
|
-
|
|
207
|
+
offersLoading.value = false;
|
|
208
208
|
}
|
|
209
209
|
}
|
|
210
210
|
|
|
211
211
|
const { refreshAll } = useBladeWidgets([
|
|
212
212
|
{
|
|
213
|
-
id: "
|
|
213
|
+
id: "OffersWidget",
|
|
214
214
|
icon: "lucide-tag",
|
|
215
|
-
title: "
|
|
216
|
-
badge:
|
|
217
|
-
loading:
|
|
218
|
-
isVisible: computed(() => !!
|
|
219
|
-
onClick: () => openBlade({ name: "
|
|
220
|
-
onRefresh:
|
|
215
|
+
title: "PRODUCT.WIDGETS.OFFERS",
|
|
216
|
+
badge: offersCount,
|
|
217
|
+
loading: offersLoading,
|
|
218
|
+
isVisible: computed(() => !!product.value?.id),
|
|
219
|
+
onClick: () => openBlade({ name: "OffersList" }),
|
|
220
|
+
onRefresh: reloadOffers,
|
|
221
221
|
},
|
|
222
222
|
{
|
|
223
|
-
id: "
|
|
223
|
+
id: "ReviewsWidget",
|
|
224
224
|
icon: "lucide-star",
|
|
225
|
-
title: "
|
|
226
|
-
badge:
|
|
227
|
-
isVisible: computed(() => !!
|
|
228
|
-
onClick: () => openBlade({ name: "
|
|
225
|
+
title: "PRODUCT.WIDGETS.REVIEWS",
|
|
226
|
+
badge: reviewsCount,
|
|
227
|
+
isVisible: computed(() => !!product.value?.id),
|
|
228
|
+
onClick: () => openBlade({ name: "ReviewsList" }),
|
|
229
229
|
},
|
|
230
230
|
]);
|
|
231
231
|
|
|
232
232
|
async function save() {
|
|
233
|
-
await api.
|
|
233
|
+
await api.saveProduct(product.value);
|
|
234
234
|
// Refresh all widget counts after saving
|
|
235
235
|
refreshAll();
|
|
236
236
|
}
|
|
@@ -67,7 +67,7 @@ const isVisuallyExpanded = computed(() => isExpanded.value || isHoverExpanded.va
|
|
|
67
67
|
|
|
68
68
|
## Details
|
|
69
69
|
|
|
70
|
-
- **Storage key scoping**: The key is scoped per application using the first URL path segment: `VC_APP_MENU_EXPANDED_{appName}`. For example, if the app is hosted at `/
|
|
70
|
+
- **Storage key scoping**: The key is scoped per application using the first URL path segment: `VC_APP_MENU_EXPANDED_{appName}`. For example, if the app is hosted at `/vendor-portal/`, the key is `VC_APP_MENU_EXPANDED_vendor-portal`. This allows multiple vc-shell apps on the same domain to maintain independent sidebar states.
|
|
71
71
|
- **Hover delay**: Opening uses a 200ms debounce to prevent accidental expansion when the cursor briefly passes over the sidebar. Closing is immediate to feel responsive.
|
|
72
72
|
- **Cleanup**: Pending hover timeouts are cleaned up via `onScopeDispose` to prevent memory leaks when the composable's effect scope is destroyed.
|
|
73
73
|
- **Default state**: The sidebar starts pinned open (`true`) on first visit, which is the most user-friendly default for new users.
|
package/runtime/knowledge/docs/core/composables/usePlatformLocaleSync/usePlatformLocaleSync.docs.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# usePlatformLocaleSync
|
|
2
|
+
|
|
3
|
+
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.
|
|
4
|
+
|
|
5
|
+
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.
|
|
6
|
+
|
|
7
|
+
## When to Use
|
|
8
|
+
|
|
9
|
+
- Never call directly from feature code. This is a framework-internal sync primitive.
|
|
10
|
+
- It is invoked once per `VcApp` mount from `useShellBootstrap`.
|
|
11
|
+
|
|
12
|
+
## Behaviour
|
|
13
|
+
|
|
14
|
+
- Reads `localStorage["NG_TRANSLATE_LANG_KEY"]` via VueUse's `useLocalStorage`, which subscribes to `storage` events for cross-tab reactivity.
|
|
15
|
+
- 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`.
|
|
16
|
+
- On subsequent changes of the platform key, re-applies the value.
|
|
17
|
+
- Skips empty strings (platform clearing the key does not blank the shell locale).
|
|
18
|
+
- Skips values equal to `currentLocale` to avoid redundant re-configuration.
|
|
19
|
+
|
|
20
|
+
## How It Works
|
|
21
|
+
|
|
22
|
+
`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`.
|
|
23
|
+
|
|
24
|
+
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.
|
|
25
|
+
|
|
26
|
+
## Relationship to `VC_LANGUAGE_SETTINGS`
|
|
27
|
+
|
|
28
|
+
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.
|
|
@@ -88,8 +88,8 @@ const { applySettings } = useSettings();
|
|
|
88
88
|
|
|
89
89
|
// Override default platform settings with module-specific branding
|
|
90
90
|
applySettings({
|
|
91
|
-
logo: "/modules/
|
|
92
|
-
title: "
|
|
91
|
+
logo: "/modules/vendor-portal/logo.svg",
|
|
92
|
+
title: "Vendor Portal",
|
|
93
93
|
});
|
|
94
94
|
</script>
|
|
95
95
|
```
|
|
@@ -51,7 +51,7 @@ This is how vc-shell achieves its modular architecture: modules can extend each
|
|
|
51
51
|
|
|
52
52
|
<!-- Other modules can inject components here -->
|
|
53
53
|
<ExtensionPoint
|
|
54
|
-
name="
|
|
54
|
+
name="seller:commissions"
|
|
55
55
|
separator
|
|
56
56
|
gap="1rem"
|
|
57
57
|
/>
|
|
@@ -66,14 +66,14 @@ import { ExtensionPoint } from "@vc-shell/framework";
|
|
|
66
66
|
**Plugin module** -- registers a component into that extension point:
|
|
67
67
|
|
|
68
68
|
```typescript
|
|
69
|
-
// modules/
|
|
69
|
+
// modules/marketplace-commissions/index.ts
|
|
70
70
|
import { defineAppModule, useExtensionPoint } from "@vc-shell/framework";
|
|
71
|
-
import
|
|
71
|
+
import CommissionFields from "./components/CommissionFields.vue";
|
|
72
72
|
|
|
73
|
-
const { add } = useExtensionPoint("
|
|
73
|
+
const { add } = useExtensionPoint("seller:commissions");
|
|
74
74
|
add({
|
|
75
|
-
id: "
|
|
76
|
-
component:
|
|
75
|
+
id: "marketplace-commission",
|
|
76
|
+
component: CommissionFields,
|
|
77
77
|
props: { showAdvanced: true },
|
|
78
78
|
priority: 10,
|
|
79
79
|
});
|
|
@@ -83,7 +83,7 @@ export default defineAppModule({
|
|
|
83
83
|
});
|
|
84
84
|
```
|
|
85
85
|
|
|
86
|
-
When the seller details page renders, `
|
|
86
|
+
When the seller details page renders, `CommissionFields` appears automatically below the main form -- with a separator and 1rem gap.
|
|
87
87
|
|
|
88
88
|
---
|
|
89
89
|
|
|
@@ -97,9 +97,9 @@ Extension points follow a two-role architecture:
|
|
|
97
97
|
HOST (declares) PLUGIN (registers)
|
|
98
98
|
----------------- ------------------
|
|
99
99
|
"I have a slot called "I want to put my
|
|
100
|
-
|
|
100
|
+
seller:commissions CommissionFields
|
|
101
101
|
where plugins can component in the
|
|
102
|
-
inject content."
|
|
102
|
+
inject content." seller:commissions slot."
|
|
103
103
|
|
|
104
104
|
| |
|
|
105
105
|
v v
|
|
@@ -116,8 +116,8 @@ Neither side imports the other. They communicate through a shared **name string*
|
|
|
116
116
|
|
|
117
117
|
Plugins can register components **before** the host declares the extension point. The reactive store handles this gracefully:
|
|
118
118
|
|
|
119
|
-
1. Plugin calls `useExtensionPoint("
|
|
120
|
-
2. Later, host calls `defineExtensionPoint("
|
|
119
|
+
1. Plugin calls `useExtensionPoint("seller:commissions")` and `add(...)` -- the store creates an undeclared entry and stores the component.
|
|
120
|
+
2. Later, host calls `defineExtensionPoint("seller:commissions")` -- the store upgrades the entry to "declared" and preserves all previously registered components.
|
|
121
121
|
3. The host's `components` computed ref reactively picks up the registered components.
|
|
122
122
|
|
|
123
123
|
This means module load order does not matter. Remote modules loaded via Module Federation may install in any sequence, and extensions still work.
|
|
@@ -165,7 +165,7 @@ Used in pages or components that **accept** plugin content. Declares the extensi
|
|
|
165
165
|
```typescript
|
|
166
166
|
import { defineExtensionPoint } from "@vc-shell/framework";
|
|
167
167
|
|
|
168
|
-
const { components, hasComponents } = defineExtensionPoint("
|
|
168
|
+
const { components, hasComponents } = defineExtensionPoint("seller:commissions", {
|
|
169
169
|
description: "Commission fee fields in the seller details form",
|
|
170
170
|
});
|
|
171
171
|
|
|
@@ -201,7 +201,7 @@ Used in modules that **provide** content to an extension point. Returns `add` an
|
|
|
201
201
|
import { useExtensionPoint } from "@vc-shell/framework";
|
|
202
202
|
import MyComponent from "./MyComponent.vue";
|
|
203
203
|
|
|
204
|
-
const { add, remove } = useExtensionPoint("
|
|
204
|
+
const { add, remove } = useExtensionPoint("seller:commissions");
|
|
205
205
|
|
|
206
206
|
// Register a component
|
|
207
207
|
add({
|
|
@@ -231,7 +231,7 @@ The `<ExtensionPoint>` component is the easiest way to render extensions in a te
|
|
|
231
231
|
|
|
232
232
|
```vue
|
|
233
233
|
<template>
|
|
234
|
-
<ExtensionPoint name="
|
|
234
|
+
<ExtensionPoint name="seller:commissions" />
|
|
235
235
|
</template>
|
|
236
236
|
|
|
237
237
|
<script setup lang="ts">
|
|
@@ -260,10 +260,10 @@ The component has three rendering modes, chosen automatically:
|
|
|
260
260
|
|
|
261
261
|
```vue
|
|
262
262
|
<!-- Layout mode: separator + gap -->
|
|
263
|
-
<ExtensionPoint name="
|
|
263
|
+
<ExtensionPoint name="seller:commissions" separator gap="1rem" wrapper-class="tw-p-4" />
|
|
264
264
|
|
|
265
265
|
<!-- Plain mode: no wrapper -->
|
|
266
|
-
<ExtensionPoint name="
|
|
266
|
+
<ExtensionPoint name="seller:commissions" />
|
|
267
267
|
```
|
|
268
268
|
|
|
269
269
|
### Scoped Slots for Custom Rendering
|
|
@@ -334,7 +334,7 @@ Currently defined constants:
|
|
|
334
334
|
| --------------------------------- | ------------------- | ---------------------------------- |
|
|
335
335
|
| `ExtensionPoints.AUTH_AFTER_FORM` | `"auth:after-form"` | Login page, below the sign-in form |
|
|
336
336
|
|
|
337
|
-
App-level extension point names (e.g., `"
|
|
337
|
+
App-level extension point names (e.g., `"seller:commissions"`) are the app's responsibility to define. Consider creating a shared constants file in your application.
|
|
338
338
|
|
|
339
339
|
---
|
|
340
340
|
|
|
@@ -356,7 +356,7 @@ Scenario: You want to add commission fee fields to the Seller Details page, whic
|
|
|
356
356
|
|
|
357
357
|
<ExtensionPoint
|
|
358
358
|
v-if="sellerDetails?.id"
|
|
359
|
-
name="
|
|
359
|
+
name="seller:commissions"
|
|
360
360
|
wrapper-class="tw-p-2"
|
|
361
361
|
/>
|
|
362
362
|
</VcBlade>
|
|
@@ -368,13 +368,13 @@ Scenario: You want to add commission fee fields to the Seller Details page, whic
|
|
|
368
368
|
```typescript
|
|
369
369
|
// commissions/index.ts
|
|
370
370
|
import { defineAppModule, useExtensionPoint } from "@vc-shell/framework";
|
|
371
|
-
import
|
|
371
|
+
import CommissionFields from "./components/CommissionFields.vue";
|
|
372
372
|
import en from "./locales/en.json";
|
|
373
373
|
|
|
374
|
-
const { add } = useExtensionPoint("
|
|
374
|
+
const { add } = useExtensionPoint("seller:commissions");
|
|
375
375
|
add({
|
|
376
376
|
id: "commission-fields",
|
|
377
|
-
component:
|
|
377
|
+
component: CommissionFields,
|
|
378
378
|
props: { editable: true },
|
|
379
379
|
priority: 10,
|
|
380
380
|
});
|
|
@@ -382,10 +382,10 @@ add({
|
|
|
382
382
|
export default defineAppModule({ locales: { en } });
|
|
383
383
|
```
|
|
384
384
|
|
|
385
|
-
**Step 3:** `
|
|
385
|
+
**Step 3:** `CommissionFields.vue` receives props and renders:
|
|
386
386
|
|
|
387
387
|
```vue
|
|
388
|
-
<!-- commissions/components/
|
|
388
|
+
<!-- commissions/components/CommissionFields.vue -->
|
|
389
389
|
<template>
|
|
390
390
|
<div class="commission-fields">
|
|
391
391
|
<h3>{{ $t("COMMISSIONS.TITLE") }}</h3>
|
|
@@ -449,14 +449,14 @@ The host renders them in order: ShippingInfo, PaymentInfo, OrderNotes.
|
|
|
449
449
|
If you call `add()` with an `id` that already exists, the component is replaced:
|
|
450
450
|
|
|
451
451
|
```typescript
|
|
452
|
-
const { add } = useExtensionPoint("
|
|
452
|
+
const { add } = useExtensionPoint("seller:commissions");
|
|
453
453
|
|
|
454
454
|
// Original registration (e.g., from another module or earlier in the code)
|
|
455
|
-
add({ id: "commission-fields", component:
|
|
455
|
+
add({ id: "commission-fields", component: OldCommissionFields, priority: 10 });
|
|
456
456
|
|
|
457
457
|
// Override with a new component (same id)
|
|
458
|
-
add({ id: "commission-fields", component:
|
|
459
|
-
// Only
|
|
458
|
+
add({ id: "commission-fields", component: NewCommissionFields, priority: 10 });
|
|
459
|
+
// Only NewCommissionFields is rendered
|
|
460
460
|
```
|
|
461
461
|
|
|
462
462
|
This is useful for overriding default behavior provided by a base module.
|
|
@@ -504,7 +504,7 @@ add({ id: "quick-action", component: QuickAction, meta: { zone: "toolbar" } });
|
|
|
504
504
|
// WRONG -- typo: "seller:comissions" (single 'm')
|
|
505
505
|
const { add } = useExtensionPoint("seller:comissions");
|
|
506
506
|
add({ id: "my-fields", component: MyFields });
|
|
507
|
-
// Component is registered but never rendered because the host declared "
|
|
507
|
+
// Component is registered but never rendered because the host declared "seller:commissions"
|
|
508
508
|
```
|
|
509
509
|
|
|
510
510
|
The dev-mode console warning (`Extension point "seller:comissions" is not declared`) will alert you to this. Always check the console in development.
|
|
@@ -514,7 +514,7 @@ The dev-mode console warning (`Extension point "seller:comissions" is not declar
|
|
|
514
514
|
> ```typescript
|
|
515
515
|
> // shared/extension-points.ts
|
|
516
516
|
> export const EP = {
|
|
517
|
-
>
|
|
517
|
+
> SELLER_COMMISSIONS: "seller:commissions",
|
|
518
518
|
> ORDER_SIDEBAR: "order:sidebar",
|
|
519
519
|
> } as const;
|
|
520
520
|
> ```
|
|
@@ -524,7 +524,7 @@ The dev-mode console warning (`Extension point "seller:comissions" is not declar
|
|
|
524
524
|
```vue
|
|
525
525
|
<!-- WRONG -- ExtensionPoint is not imported -->
|
|
526
526
|
<template>
|
|
527
|
-
<ExtensionPoint name="
|
|
527
|
+
<ExtensionPoint name="seller:commissions" />
|
|
528
528
|
</template>
|
|
529
529
|
|
|
530
530
|
<script setup lang="ts">
|
|
@@ -546,7 +546,7 @@ add({ id: "my-component", component: ComponentB });
|
|
|
546
546
|
Use globally unique IDs. A good convention is `module-name:component-name`:
|
|
547
547
|
|
|
548
548
|
```typescript
|
|
549
|
-
add({ id: "
|
|
549
|
+
add({ id: "marketplace:commission-fields", component: CommissionFields });
|
|
550
550
|
add({ id: "shipping:delivery-estimate", component: DeliveryEstimate });
|
|
551
551
|
```
|
|
552
552
|
|
|
@@ -668,9 +668,10 @@ These are used internally by `defineExtensionPoint` and `useExtensionPoint`. You
|
|
|
668
668
|
|
|
669
669
|
## Related
|
|
670
670
|
|
|
671
|
-
| Resource
|
|
672
|
-
|
|
|
673
|
-
| Modularity Plugin
|
|
674
|
-
| Extension Point Store
|
|
675
|
-
| ExtensionPoint Component
|
|
676
|
-
| Types
|
|
671
|
+
| Resource | Path | Description |
|
|
672
|
+
| ------------------------------ | -------------------------------------------------- | --------------------------------------------- |
|
|
673
|
+
| Modularity Plugin | `core/plugins/modularity/` | Module definition and registration |
|
|
674
|
+
| Extension Point Store | `core/plugins/extension-points/store.ts` | Reactive registry implementation |
|
|
675
|
+
| ExtensionPoint Component | `core/plugins/extension-points/ExtensionPoint.vue` | Declarative render component |
|
|
676
|
+
| Types | `core/plugins/extension-points/types.ts` | `ExtensionComponent`, `ExtensionPointOptions` |
|
|
677
|
+
| Seller Details (usage example) | `apps/vendor-portal/src/modules/seller-details/` | Real-world extension point host |
|
|
@@ -703,16 +703,16 @@ export default defineAppModule({});
|
|
|
703
703
|
A module can extend another module's UI by using extension points (see the [Extension Points Plugin](../extension-points/extension-points.docs.md)):
|
|
704
704
|
|
|
705
705
|
```typescript
|
|
706
|
-
// modules/
|
|
706
|
+
// modules/marketplace-commissions/index.ts
|
|
707
707
|
import { defineAppModule, useExtensionPoint } from "@vc-shell/framework";
|
|
708
|
-
import
|
|
708
|
+
import CommissionFields from "./components/CommissionFields.vue";
|
|
709
709
|
import en from "./locales/en.json";
|
|
710
710
|
|
|
711
|
-
// Register into the
|
|
712
|
-
const { add } = useExtensionPoint("
|
|
711
|
+
// Register into the seller details extension point
|
|
712
|
+
const { add } = useExtensionPoint("seller:commissions");
|
|
713
713
|
add({
|
|
714
|
-
id: "
|
|
715
|
-
component:
|
|
714
|
+
id: "marketplace-commission",
|
|
715
|
+
component: CommissionFields,
|
|
716
716
|
props: { showAdvanced: true },
|
|
717
717
|
priority: 10,
|
|
718
718
|
});
|
|
@@ -733,7 +733,7 @@ The host blade declares the extension point:
|
|
|
733
733
|
<!-- Other modules can inject components here -->
|
|
734
734
|
<ExtensionPoint
|
|
735
735
|
v-if="sellerDetails?.id"
|
|
736
|
-
name="
|
|
736
|
+
name="seller:commissions"
|
|
737
737
|
wrapper-class="tw-p-2"
|
|
738
738
|
/>
|
|
739
739
|
</VcBlade>
|
|
@@ -46,7 +46,7 @@ This component has no props. All content is driven by the settings menu service
|
|
|
46
46
|
A typical module registers all its settings entries during the module install phase:
|
|
47
47
|
|
|
48
48
|
```ts
|
|
49
|
-
//
|
|
49
|
+
// vendor-portal-module/index.ts
|
|
50
50
|
import { markRaw } from "vue";
|
|
51
51
|
import { useSettingsMenu, ThemeSelector, LanguageSelector, ChangePasswordButton, LogoutButton } from "@vc-shell/framework";
|
|
52
52
|
|
|
@@ -10,12 +10,13 @@ The composables follow a PrimeVue-inspired pattern: each returns reactive state
|
|
|
10
10
|
|
|
11
11
|
### Data & State
|
|
12
12
|
|
|
13
|
-
| Composable
|
|
14
|
-
|
|
|
15
|
-
| `useDataTableState`
|
|
16
|
-
| `useTableColumns`
|
|
17
|
-
| `
|
|
18
|
-
| `
|
|
13
|
+
| Composable | Purpose |
|
|
14
|
+
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
15
|
+
| `useDataTableState` | Persists column state (v2 schema: weights, order, hidden/shown IDs) to localStorage/sessionStorage. Auto-migrates v1 (pixel-based) state on first load. Key format: `VC_DATATABLE_{KEY}`. Debounced auto-save (150ms) with restore-on-mount. |
|
|
16
|
+
| `useTableColumns` | Column ordering, width management via `columnState` (weight store), computed pixel widths via `engineOutput`. Exposes `recompute()` to trigger a recalculation pass. Watches `visibleColumns` and appends new columns without dropping hidden ones. |
|
|
17
|
+
| `useColumnWidthEngine` | Pure functions for deterministic column width computation (see below). |
|
|
18
|
+
| `useDataProcessing` | Client-side sort pipeline (single/multi) and row grouping. Skipped when `lazy: true` (server-side). |
|
|
19
|
+
| `useTableContext` | Provides/injects table-level context for sub-components. |
|
|
19
20
|
|
|
20
21
|
### Sorting & Filtering
|
|
21
22
|
|
|
@@ -27,13 +28,13 @@ The composables follow a PrimeVue-inspired pattern: each returns reactive state
|
|
|
27
28
|
|
|
28
29
|
### Interaction
|
|
29
30
|
|
|
30
|
-
| Composable | Purpose
|
|
31
|
-
| ------------------------ |
|
|
32
|
-
| `useTableRowReorder` | Drag-and-drop row reordering with live-swap at 50% vertical threshold. Commits via `dragend` (always fires) with `drop` as preferred path.
|
|
33
|
-
| `useTableColumnsReorder` | Drag-and-drop column reordering with 50% horizontal threshold. Returns `getReorderHeadProps()` for easy binding.
|
|
34
|
-
| `useTableColumnsResize` |
|
|
35
|
-
| `useTableSelectionV2` | Row selection: single, multiple (checkbox), and row-click modes. Emits `RowSelectEvent` / `RowSelectAllEvent`.
|
|
36
|
-
| `useTableSwipe` | Mobile swipe context (provide/inject). Tracks which row has an active swipe action.
|
|
31
|
+
| Composable | Purpose |
|
|
32
|
+
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
33
|
+
| `useTableRowReorder` | Drag-and-drop row reordering with live-swap at 50% vertical threshold. Commits via `dragend` (always fires) with `drop` as preferred path. |
|
|
34
|
+
| `useTableColumnsReorder` | Drag-and-drop column reordering with 50% horizontal threshold. Returns `getReorderHeadProps()` for easy binding. |
|
|
35
|
+
| `useTableColumnsResize` | Weight-based resize: dragging adjusts the weights of the dragged column and its right neighbor without touching other columns. DOM-based px updates during drag for smooth 60fps; commits new weights to `columnState` on mouseup. No `ResizeObserver` scaling. |
|
|
36
|
+
| `useTableSelectionV2` | Row selection: single, multiple (checkbox), and row-click modes. Emits `RowSelectEvent` / `RowSelectAllEvent`. |
|
|
37
|
+
| `useTableSwipe` | Mobile swipe context (provide/inject). Tracks which row has an active swipe action. |
|
|
37
38
|
|
|
38
39
|
### Editing
|
|
39
40
|
|
|
@@ -53,6 +54,68 @@ The composables follow a PrimeVue-inspired pattern: each returns reactive state
|
|
|
53
54
|
| `useTableRowGrouping` | Groups rows by field and inserts group header rows. |
|
|
54
55
|
| `useMobileCardLayout` | Mobile card view layout logic for responsive table display. |
|
|
55
56
|
|
|
57
|
+
## useColumnWidthEngine
|
|
58
|
+
|
|
59
|
+
`useColumnWidthEngine` is a collection of **pure functions** (no reactive state) for deterministic column width computation. `useTableColumns` calls these internally on every render pass and on container resize.
|
|
60
|
+
|
|
61
|
+
### Core functions
|
|
62
|
+
|
|
63
|
+
#### `computeColumnWidths(input: EngineInput): EngineOutput`
|
|
64
|
+
|
|
65
|
+
The central engine. Distributes `availableWidth` among visible columns according to their current weights, enforcing `minWidth` / `maxWidth` constraints.
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
interface EngineInput {
|
|
69
|
+
visibleIds: string[]; // Ordered list of visible column IDs
|
|
70
|
+
specs: Record<string, ColumnSpec>; // Per-column: weight, minWidth, maxWidth
|
|
71
|
+
availableWidth: number; // Container width in px
|
|
72
|
+
fitMode: "gap" | "fit"; // "gap" leaves filler space, "fit" fills width
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
interface EngineOutput {
|
|
76
|
+
widths: Record<string, number>; // Computed px width per column ID
|
|
77
|
+
totalWidth: number; // Sum of all computed widths
|
|
78
|
+
overflow: boolean; // true when sum(minWidth) > availableWidth
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
In crisis (`overflow: true`), each column receives its `minWidth` regardless of weight, and a console warning is emitted.
|
|
83
|
+
|
|
84
|
+
#### `parseColumnWidth(value: string | number | undefined, availableWidth: number): ParsedWidth`
|
|
85
|
+
|
|
86
|
+
Parses a `VcColumn` `width` prop into a concrete pixel value.
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
type ParsedWidth =
|
|
90
|
+
| { type: "px"; value: number } // "200", 200, "200px"
|
|
91
|
+
| { type: "pct"; value: number } // "20%" → 0.2 * availableWidth
|
|
92
|
+
| { type: "auto"; value: undefined }; // undefined, "auto"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
#### `buildInitialWeights(parsed: ParsedWidth[], availableWidth: number): Record<string, number>`
|
|
96
|
+
|
|
97
|
+
Converts an array of `ParsedWidth` values (one per column) into initial weights. Auto columns receive an equal share of the space not claimed by px/% columns.
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
// Example: three columns — 200px, 20%, auto — with 800px available
|
|
101
|
+
// px-column → weight 200
|
|
102
|
+
// pct-column → weight 160 (20% of 800)
|
|
103
|
+
// auto-column→ weight 440 (800 - 200 - 160)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
#### `normalizeWeights(specs: Record<string, ColumnSpec>, visibleIds: string[]): void`
|
|
107
|
+
|
|
108
|
+
Mutates `specs` in place so that the weights of `visibleIds` sum to 1.0. Called before a `"fit"` mode computation pass.
|
|
109
|
+
|
|
110
|
+
### When weights update
|
|
111
|
+
|
|
112
|
+
| User action | Weight change |
|
|
113
|
+
| --------------------------- | --------------------------------------------------------------------------------------- |
|
|
114
|
+
| Column resize (drag border) | Dragged column and right neighbor exchange weight proportionally |
|
|
115
|
+
| Column show/hide | Hidden column's weight is preserved; shown column uses saved or initial weight |
|
|
116
|
+
| Reset columns | All weights rebuilt from declarative `width` props |
|
|
117
|
+
| Container resize | Weights unchanged; engine recomputes px widths from same weights × new `availableWidth` |
|
|
118
|
+
|
|
56
119
|
## Usage
|
|
57
120
|
|
|
58
121
|
These composables are consumed internally by VcDataTable. If you need to interact with them, use VcDataTable's props and events:
|
|
@@ -79,10 +142,12 @@ register({
|
|
|
79
142
|
|
|
80
143
|
## Tips
|
|
81
144
|
|
|
82
|
-
- `useTableColumns` never removes entries from `
|
|
145
|
+
- `useTableColumns` never removes entries from `columnState` — hidden columns preserve their weight and order for when they reappear. Use `engineOutput` (not `columnState` directly) to read computed pixel widths for rendering.
|
|
146
|
+
- Call `recompute()` (returned by `useTableColumns`) whenever the container width changes to force the engine to redistribute widths without altering weights.
|
|
147
|
+
- `useDataTableState` stores the v2 schema (weights, order, hidden/shown IDs). It automatically migrates v1 state (pixel-based) on the first restore. Guard against save-during-restore loops with the `isRestoring` flag.
|
|
148
|
+
- `useColumnWidthEngine` functions are pure — pass immutable copies of `specs` when testing or previewing layouts without committing to state.
|
|
83
149
|
- `useTableRowReorder`: `event.preventDefault()` in `dragover` MUST be called on every event or `drop` never fires.
|
|
84
|
-
- `useTableColumnsResize`
|
|
85
|
-
- `useDataTableState` guards against save-during-restore loops with an `isRestoring` flag.
|
|
150
|
+
- `useTableColumnsResize` applies DOM-level px changes during drag for 60fps performance, then commits final weights to `columnState` on mouseup. No `ResizeObserver` scaling is involved.
|
|
86
151
|
- `useVirtualScroll` requires a fixed `itemSize` (row height in pixels) for accurate positioning.
|
|
87
152
|
|
|
88
153
|
## Related
|
|
@@ -551,6 +551,40 @@ Columns are reorderable by default. Drag a column header to a new position. Disa
|
|
|
551
551
|
</VcDataTable>
|
|
552
552
|
```
|
|
553
553
|
|
|
554
|
+
### Column Width Model
|
|
555
|
+
|
|
556
|
+
VcDataTable uses a **weight-based engine** to compute exact pixel widths for every column deterministically, based on the container's available width.
|
|
557
|
+
|
|
558
|
+
**How it works:**
|
|
559
|
+
|
|
560
|
+
1. Developer declares initial widths via the `VcColumn` `width` prop (px, %, or omitted for auto).
|
|
561
|
+
2. At runtime, these declarations become proportional weights. The engine converts weights to exact pixel values by distributing `availableWidth` proportionally.
|
|
562
|
+
3. When a user resizes a column, only the weights change — the engine recomputes all pixel widths from the new weights on the next render.
|
|
563
|
+
4. Clicking **Reset columns** returns all columns to their declarative hints.
|
|
564
|
+
|
|
565
|
+
**`fitMode` prop** controls what happens to leftover space:
|
|
566
|
+
|
|
567
|
+
| Value | Behavior |
|
|
568
|
+
| ----------------- | ----------------------------------------------------------------------------- |
|
|
569
|
+
| `"gap"` (default) | A filler pseudo-element absorbs unused space at the right end. |
|
|
570
|
+
| `"fit"` | All column weights are normalized so columns fill the entire container width. |
|
|
571
|
+
|
|
572
|
+
**Width prop contract:**
|
|
573
|
+
|
|
574
|
+
| Declaration | Meaning |
|
|
575
|
+
| ------------------------------- | ------------------------------------------------------------ |
|
|
576
|
+
| `width="200"` or `:width="200"` | Initial 200 px hint |
|
|
577
|
+
| `width="20%"` | Initial hint based on 20% of available width |
|
|
578
|
+
| `width` omitted | Auto — splits remaining space equally among all auto columns |
|
|
579
|
+
|
|
580
|
+
After initialization the column lives in the weight model. Container resizes recompute px values without changing weights.
|
|
581
|
+
|
|
582
|
+
**`minWidth` / `maxWidth`:**
|
|
583
|
+
|
|
584
|
+
- `minWidth` and `maxWidth` are enforced by the engine on every computation pass.
|
|
585
|
+
- Default `minWidth` is 40 px when not specified.
|
|
586
|
+
- In crisis (sum of all `minWidth` values exceeds available width), the engine squeezes columns below their minimums and emits a console warning rather than breaking layout.
|
|
587
|
+
|
|
554
588
|
### Column Switcher
|
|
555
589
|
|
|
556
590
|
A built-in panel lets users show/hide columns. Enabled by default when `column-switcher` is truthy.
|
|
@@ -992,6 +1026,10 @@ Persist column widths, column order, hidden columns, sort, and filters across pa
|
|
|
992
1026
|
|
|
993
1027
|
**Storage key format:** `VC_DATATABLE_PRODUCT-LIST` (uppercased `state-key`).
|
|
994
1028
|
|
|
1029
|
+
**Schema version:** The persisted state uses the **v2 schema**, which stores column weights, column order, and hidden/shown column IDs. `containerWidth` is no longer stored because weights are container-independent — the engine recomputes pixel values from weights on every mount.
|
|
1030
|
+
|
|
1031
|
+
If an older browser tab wrote **v1** state (pixel-based widths), it is automatically migrated to v2 on first load. No manual migration is needed.
|
|
1032
|
+
|
|
995
1033
|
Use sessionStorage instead of localStorage:
|
|
996
1034
|
|
|
997
1035
|
```vue
|
|
@@ -1534,9 +1572,9 @@ function onRowRemove(event: { data: Product; index: number; cancel: () => void }
|
|
|
1534
1572
|
|
|
1535
1573
|
## Recipes
|
|
1536
1574
|
|
|
1537
|
-
### Recipe 1:
|
|
1575
|
+
### Recipe 1: Products List Blade
|
|
1538
1576
|
|
|
1539
|
-
A typical list blade with search, pagination, row actions, and empty states.
|
|
1577
|
+
A typical list blade with search, pagination, row actions, and empty states -- modeled after real vendor-portal usage.
|
|
1540
1578
|
|
|
1541
1579
|
```vue
|
|
1542
1580
|
<template>
|
|
@@ -11,23 +11,23 @@ A responsive multi-image gallery with drag-and-drop reorder, file upload, lightb
|
|
|
11
11
|
|
|
12
12
|
## Props
|
|
13
13
|
|
|
14
|
-
| Prop | Type | Default | Description
|
|
15
|
-
| --------------- | --------------------------------------------------------- | --------------------------------------------- |
|
|
16
|
-
| `layout` | `"filmstrip" \| "grid"` | `"filmstrip"` | Layout mode — filmstrip shows a single scrollable row with expand/collapse; grid shows the classic multi-row auto-fill layout.
|
|
17
|
-
| `label` | `string` | `undefined` | Label text displayed in the gallery header.
|
|
18
|
-
| `required` | `boolean` | `false` | Shows a required indicator (`*`) on the label.
|
|
19
|
-
| `images` | `ICommonAsset[]` | `[]` | Array of image assets to display.
|
|
20
|
-
| `disabled` | `boolean` | `false` | Disables all interactive actions.
|
|
21
|
-
| `multiple` | `boolean` | `false` | Allow selecting multiple files in upload dialog.
|
|
22
|
-
| `loading` | `boolean` | `false` | Shows a loading overlay with spinner on the gallery.
|
|
23
|
-
| `itemActions` | `{ preview?: boolean; edit?: boolean; remove?: boolean }` | `{ preview: true, edit: true, remove: true }` | Per-tile action visibility.
|
|
24
|
-
| `rules` | `IValidationRules` | `undefined` | Validation rules for uploaded files.
|
|
25
|
-
| `name` | `string` | `"Gallery"` | Field name for validation messages.
|
|
26
|
-
| `accept` | `string` | `
|
|
27
|
-
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | Tile size preset. Sizes are smaller on mobile.
|
|
28
|
-
| `gap` | `number` | `8` | Gap between tiles in pixels.
|
|
29
|
-
| `imagefit` | `"contain" \| "cover"` | `"contain"` | How images fit within tiles.
|
|
30
|
-
| `thumbnailSize` | `ThumbnailSize` | auto from `size` | Thumbnail size for tile images. Auto-mapped: sm→128x128, md→216x216, lg→348x348. Preview thumbnails use 64x64.
|
|
14
|
+
| Prop | Type | Default | Description |
|
|
15
|
+
| --------------- | --------------------------------------------------------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
16
|
+
| `layout` | `"filmstrip" \| "grid"` | `"filmstrip"` | Layout mode — filmstrip shows a single scrollable row with expand/collapse; grid shows the classic multi-row auto-fill layout. |
|
|
17
|
+
| `label` | `string` | `undefined` | Label text displayed in the gallery header. |
|
|
18
|
+
| `required` | `boolean` | `false` | Shows a required indicator (`*`) on the label. |
|
|
19
|
+
| `images` | `ICommonAsset[]` | `[]` | Array of image assets to display. |
|
|
20
|
+
| `disabled` | `boolean` | `false` | Disables all interactive actions. |
|
|
21
|
+
| `multiple` | `boolean` | `false` | Allow selecting multiple files in upload dialog. |
|
|
22
|
+
| `loading` | `boolean` | `false` | Shows a loading overlay with spinner on the gallery. |
|
|
23
|
+
| `itemActions` | `{ preview?: boolean; edit?: boolean; remove?: boolean }` | `{ preview: true, edit: true, remove: true }` | Per-tile action visibility. |
|
|
24
|
+
| `rules` | `IValidationRules` | `undefined` | Validation rules for uploaded files. |
|
|
25
|
+
| `name` | `string` | `"Gallery"` | Field name for validation messages. |
|
|
26
|
+
| `accept` | `string` | `"image/*"` | Accepted file MIME types / extensions. Gallery is image-only by default — non-image files dropped from the OS are filtered out. Override (e.g. `"image/png,image/jpeg"`) to narrow further. |
|
|
27
|
+
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | Tile size preset. Sizes are smaller on mobile. |
|
|
28
|
+
| `gap` | `number` | `8` | Gap between tiles in pixels. |
|
|
29
|
+
| `imagefit` | `"contain" \| "cover"` | `"contain"` | How images fit within tiles. |
|
|
30
|
+
| `thumbnailSize` | `ThumbnailSize` | auto from `size` | Thumbnail size for tile images. Auto-mapped: sm→128x128, md→216x216, lg→348x348. Preview thumbnails use 64x64. |
|
|
31
31
|
|
|
32
32
|
## Events
|
|
33
33
|
|