@vc-shell/vc-app-skill 2.3.0-pr281.3c002aa → 2.4.0
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/knowledge/docs/_BUILD_HASH.md +1 -1
- package/runtime/knowledge/docs/core/composables/useApiClient/useApiClient.docs.md +1 -1
- package/runtime/knowledge/docs/core/composables/useKeyboardShortcuts/useKeyboardShortcuts.docs.md +219 -0
- package/runtime/knowledge/docs/core/composables/useLanguages/useLanguages.docs.md +8 -8
- package/runtime/knowledge/docs/shell/components/settings-menu-item/settings-menu-item.docs.md +11 -0
- package/runtime/knowledge/docs/shell/dashboard/draggable-dashboard/draggable-dashboard.docs.md +17 -0
- package/runtime/knowledge/docs/ui/components/atoms/vc-video/vc-video.docs.md +22 -12
- package/runtime/knowledge/docs/ui/components/molecules/vc-breadcrumbs/vc-breadcrumbs.docs.md +4 -0
- package/runtime/knowledge/docs/ui/components/molecules/vc-input/vc-input.docs.md +1 -0
- package/runtime/knowledge/docs/ui/components/molecules/vc-menu/vc-menu.docs.md +11 -7
- package/runtime/knowledge/docs/ui/components/organisms/vc-data-table/composables/table-composables.docs.md +13 -13
- package/runtime/knowledge/docs/ui/components/organisms/vc-data-table/vc-data-table.docs.md +27 -15
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vc-shell/vc-app-skill",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
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.
|
|
1
|
+
2.4.0
|
|
@@ -1 +1 @@
|
|
|
1
|
-
Synced from framework at commit
|
|
1
|
+
Synced from framework at commit d4d56d4fa on 2026-08-04T07:36:41.124Z
|
|
@@ -6,7 +6,7 @@ group: data
|
|
|
6
6
|
|
|
7
7
|
# useApiClient
|
|
8
8
|
|
|
9
|
-
Creates a typed API client instance for communicating with VirtoCommerce platform APIs. The composable accepts a generated client class constructor (extending `AuthApiBase`) and returns an async factory function that produces a client instance. The composable itself is intentionally thin — it constructs the client and returns it. Platform authentication flows through the session cookie that the browser replays on every same-origin API call; the framework's fetch wrapper enforces timeout, offline checks, and
|
|
9
|
+
Creates a typed API client instance for communicating with VirtoCommerce platform APIs. The composable accepts a generated client class constructor (extending `AuthApiBase`) and returns an async factory function that produces a client instance. The composable itself is intentionally thin — it constructs the client and returns it. Platform authentication flows through the session cookie that the browser replays on every same-origin API call; the framework's fetch wrapper enforces timeout, offline checks, and redirect-to-login on top. An expired session is detected from a `401` **or** from a response that was redirected to the login page (some platform configurations answer that way instead of a 401, and `fetch` follows the redirect transparently). A `403` is not treated as expiry — authenticated-but-unauthorized keeps the session.
|
|
10
10
|
|
|
11
11
|
!!! tip "Always call getApiClient inside async functions"
|
|
12
12
|
`getApiClient` is async. Never call it at the top level of `<script setup>` — call it inside the async function you pass to `useAsync`. Holding one client instance for the lifetime of the component couples your code to a single object across action runs; prefer one `await getApiClient()` per action so the call shape stays uniform with `useAsync`.
|
package/runtime/knowledge/docs/core/composables/useKeyboardShortcuts/useKeyboardShortcuts.docs.md
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: useKeyboardShortcuts
|
|
3
|
+
category: composables
|
|
4
|
+
group: utilities
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
!!! tip "Long page"
|
|
8
|
+
Use the section headings to jump directly to what you need: [Primary pattern: declare a shortcut on a toolbar item](#primary-pattern-declare-a-shortcut-on-a-toolbar-item), [Quick Start](#quick-start), [Built-in blade shortcuts](#built-in-blade-shortcuts), or [API Reference](#api-reference).
|
|
9
|
+
|
|
10
|
+
# useKeyboardShortcuts
|
|
11
|
+
|
|
12
|
+
`useKeyboardShortcuts` is the framework's keyboard-shortcut toolkit: the `hotkey` fluent builder for describing a combination, and OS-aware formatting for the tooltip and `aria-keyshortcuts` that a toolbar button shows automatically once it declares one. There is no manual `keydown` wiring in module code -- a shortcut is a plain field on a toolbar item, and the blade navigation layer dispatches it globally.
|
|
13
|
+
|
|
14
|
+
## Primary pattern: declare a shortcut on a toolbar item
|
|
15
|
+
|
|
16
|
+
Add `shortcut: hotkey.mod.s` to any `IBladeToolbar` entry. `hotkey.mod.s` resolves to `{ key: "s", mod: true }` -- `mod` is the platform's primary modifier (Cmd on macOS, Ctrl elsewhere), so one definition covers both operating systems.
|
|
17
|
+
|
|
18
|
+
```vue title="orders-details.vue"
|
|
19
|
+
<script setup lang="ts">
|
|
20
|
+
import { ref } from "vue";
|
|
21
|
+
import { hotkey, VcBlade, type IBladeToolbar } from "@vc-shell/framework";
|
|
22
|
+
|
|
23
|
+
const bladeToolbar = ref<IBladeToolbar[]>([
|
|
24
|
+
{
|
|
25
|
+
id: "save",
|
|
26
|
+
title: "Save",
|
|
27
|
+
icon: "lucide-save",
|
|
28
|
+
shortcut: hotkey.mod.s,
|
|
29
|
+
clickHandler: () => save(),
|
|
30
|
+
},
|
|
31
|
+
]);
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<template>
|
|
35
|
+
<VcBlade :toolbar-items="bladeToolbar" />
|
|
36
|
+
</template>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
That single field is the whole integration. The rendered "Save" button now shows a `⌘S` / `Ctrl+S` tooltip, sets `aria-keyshortcuts="Meta+S"`, and the blade navigation layer fires `clickHandler` when the user presses the combination while the blade is active -- no listener, no cleanup.
|
|
40
|
+
|
|
41
|
+
## When to reach for it
|
|
42
|
+
|
|
43
|
+
- **Declaring a shortcut on a toolbar item.** The common case: add `shortcut: hotkey.mod.s` next to `clickHandler` on an `IBladeToolbar` entry (array pattern or `useToolbar().registerToolbarItem`, see [useToolbar](../useToolbar/)).
|
|
44
|
+
- **Formatting a shortcut for custom UI.** Call `useKeyboardShortcuts()` and use `formatShortcut(def)` when you need the OS-aware label somewhere other than the built-in toolbar tooltip (a help panel, a command palette row).
|
|
45
|
+
- **Detecting the platform.** `isMac` is exposed for any OS-conditional copy or icon choice tied to a shortcut.
|
|
46
|
+
- **Not for building your own key listener.** There is no public "register a global shortcut" API outside toolbar items -- see [Common mistakes](#common-mistakes).
|
|
47
|
+
|
|
48
|
+
## Quick Start
|
|
49
|
+
|
|
50
|
+
### Import vs `useKeyboardShortcuts()` destructure
|
|
51
|
+
|
|
52
|
+
`hotkey` is a context-free singleton -- import it directly wherever you only need to build a definition:
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import { hotkey } from "@vc-shell/framework";
|
|
56
|
+
|
|
57
|
+
const saveShortcut = hotkey.mod.s; // { key: "s", mod: true }
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Reach for the composable form only when you also need `formatShortcut` or `isMac` (both are bound together so `formatShortcut` doesn't need an `isMac` argument at the call site):
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import { useKeyboardShortcuts } from "@vc-shell/framework";
|
|
64
|
+
|
|
65
|
+
const { hotkey, formatShortcut, isMac } = useKeyboardShortcuts();
|
|
66
|
+
|
|
67
|
+
const saveShortcut = hotkey.mod.s;
|
|
68
|
+
const { parts, aria } = formatShortcut(saveShortcut);
|
|
69
|
+
// isMac ? parts = ["⌘", "S"] : parts = ["Ctrl", "S"]
|
|
70
|
+
// aria = "Meta+S" on macOS, "Control+S" elsewhere
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Both forms return the same `hotkey` singleton -- the composable exists for convenience when a component already needs `isMac`/`formatShortcut`, not because `hotkey` itself carries state.
|
|
74
|
+
|
|
75
|
+
## Built-in blade shortcuts
|
|
76
|
+
|
|
77
|
+
Two shortcuts are wired up by the framework at the blade navigation root -- no client code required, and no toolbar item needed to get them:
|
|
78
|
+
|
|
79
|
+
| Shortcut | Effect | Gate |
|
|
80
|
+
| ------------ | ----------------------- | --------------------------------------------------------------------------- |
|
|
81
|
+
| `Esc` | Closes the active blade | Only if the blade is closable (has a `parentId`; workspace roots ignore it) |
|
|
82
|
+
| `Cmd/Ctrl+\` | Toggles expand/maximize | Only on desktop and only if the blade is closable |
|
|
83
|
+
|
|
84
|
+
Both are evaluated against the currently active blade, and both are skipped while the corresponding condition doesn't hold -- `Esc` on a non-closable workspace root does nothing, and `Cmd/Ctrl+\` does nothing on mobile viewports even on a closable blade.
|
|
85
|
+
|
|
86
|
+
**An explicit toolbar shortcut always wins.** The dispatcher checks the active blade's toolbar items before either built-in, so a toolbar item declared with `shortcut: hotkey.escape` (for example, a "Cancel" button) intercepts `Esc` and the built-in close never runs.
|
|
87
|
+
|
|
88
|
+
## OS adaptation
|
|
89
|
+
|
|
90
|
+
`formatShortcut(def)` returns two independent representations from one `ShortcutDefinition`, so you never branch on OS yourself:
|
|
91
|
+
|
|
92
|
+
- `parts` -- an array of OS-native keycap labels for display. macOS renders modifier glyphs in Apple's canonical order (`⌃ ⌥ ⇧ ⌘`) with no separator; Windows/Linux render words (`Ctrl`, `Alt`, `Shift`) with a `+` between chips.
|
|
93
|
+
- `aria` -- a single canonical string in W3C `aria-keyshortcuts` format (`"Control+Shift+S"`), OS-independent by design -- this is what assistive tech reads, not what's painted on screen.
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
import { useKeyboardShortcuts } from "@vc-shell/framework";
|
|
97
|
+
|
|
98
|
+
const { hotkey, formatShortcut, isMac } = useKeyboardShortcuts();
|
|
99
|
+
const { parts } = formatShortcut(hotkey.mod.s);
|
|
100
|
+
|
|
101
|
+
// isMac === true → parts = ["⌘", "S"] → tooltip renders "⌘S" (no separator)
|
|
102
|
+
// isMac === false → parts = ["Ctrl", "S"] → tooltip renders "Ctrl+S" (with separator)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The toolbar button itself (`ToolbarBaseButton`) already does this for you: it calls `formatShortcut` internally and renders the result through the `ShortcutKbd` chip component inside a tooltip. `isMac` also determines `matchesEvent`'s runtime resolution of `mod` -- the same flag drives both what the user sees and what actually fires.
|
|
106
|
+
|
|
107
|
+
## Accessibility
|
|
108
|
+
|
|
109
|
+
- **`aria-keyshortcuts`.** Set automatically on the button element whenever `shortcut` is present, using the canonical `aria` string from `formatShortcut` (for example `aria-keyshortcuts="Meta+S"`). No shortcut, no attribute -- it's never rendered empty.
|
|
110
|
+
- **Tooltip discovery.** A sighted user discovers the shortcut through the `<kbd>` chips shown on hover/focus of the toolbar button (`ShortcutKbd`, wrapped in `VcTooltip`). This is the only visual affordance; there is no separate "shortcuts list" surface in v1.
|
|
111
|
+
- **No button, no discovery.** Both `aria-keyshortcuts` and the tooltip are properties of the rendered toolbar button -- they exist only because `shortcut` lives on an `IBladeToolbar` item that renders as a button. There is currently no way to advertise a shortcut that isn't attached to a visible button (see [Common mistakes](#common-mistakes)).
|
|
112
|
+
- **Accessible name still comes from the title.** `shortcut` does not change how the button's accessible name is computed -- it still comes from the visible `title` text. Keep `title` non-empty; `shortcut` is additive, not a substitute for a labeled button.
|
|
113
|
+
- **Suppressed behind a modal.** Blade shortcuts (toolbar and built-in) do not fire while a modal (`aria-modal="true"`) is open, so a shortcut never acts on a blade hidden behind it.
|
|
114
|
+
|
|
115
|
+
## Common mistakes
|
|
116
|
+
|
|
117
|
+
### Expecting a bare key to fire while an input is focused
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
// Wrong -- assumes "e" fires globally
|
|
121
|
+
registerToolbarItem({ id: "edit", title: "Edit", shortcut: { key: "e" }, clickHandler: () => edit() });
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
A shortcut with no modifier is suppressed while focus is inside a text input, textarea, select, or a `contenteditable` element -- otherwise typing the letter "e" into a form field would trigger the button. This guard only applies to bare keys; add a modifier (`hotkey.mod.e`) if the shortcut must also fire while a field is focused.
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
// Correct -- a modified combination is not suppressed by input focus
|
|
128
|
+
registerToolbarItem({ id: "edit", title: "Edit", shortcut: hotkey.mod.e, clickHandler: () => edit() });
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Two toolbar items sharing the same combination
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
// Wrong -- both items declare mod+s on the same blade
|
|
135
|
+
const bladeToolbar = ref<IBladeToolbar[]>([
|
|
136
|
+
{ id: "save", title: "Save", shortcut: hotkey.mod.s, clickHandler: () => save() },
|
|
137
|
+
{ id: "save-as", title: "Save As", shortcut: hotkey.mod.s, clickHandler: () => saveAs() },
|
|
138
|
+
]);
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The dispatcher walks a blade's toolbar items in declaration order and fires the _first_ item whose shortcut matches, then stops -- `save-as` never receives `Cmd/Ctrl+S` as long as `save` is registered ahead of it. In development, the dispatcher emits a deduplicated `console.warn` on keydown for this case (first item wins, second is named in the warning), for a toolbar item with an unrecognized `shortcut.key`, and for a toolbar item that overrides a built-in (`Esc`, `Cmd/Ctrl+\`). Production stays silent -- these are warnings, not thrown errors -- so still treat shortcut collisions within a blade's toolbar as a review-time concern: keep combinations unique per blade.
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
// Correct -- distinct combinations
|
|
145
|
+
const bladeToolbar = ref<IBladeToolbar[]>([
|
|
146
|
+
{ id: "save", title: "Save", shortcut: hotkey.mod.s, clickHandler: () => save() },
|
|
147
|
+
{ id: "save-as", title: "Save As", shortcut: hotkey.mod.shift.s, clickHandler: () => saveAs() },
|
|
148
|
+
]);
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Expecting a button-less shortcut
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
// Wrong -- there is no API to register a shortcut without a toolbar item
|
|
155
|
+
onMounted(() => {
|
|
156
|
+
window.addEventListener("keydown", (e) => {
|
|
157
|
+
if (e.metaKey && e.key === "s") save();
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Rolling your own `keydown` listener bypasses `isTextInputFocused` suppression, the active-blade scoping, and the tooltip/`aria-keyshortcuts` discovery that toolbar shortcuts get for free -- and it risks double-firing alongside the framework dispatcher. In v1, every shortcut must be attached to a visible `IBladeToolbar` item; there is no headless/button-less registration path. If the action genuinely has no natural button, add one (it can be low-priority/overflowed) rather than reaching for a manual listener.
|
|
163
|
+
|
|
164
|
+
## API Reference
|
|
165
|
+
|
|
166
|
+
### `hotkey`
|
|
167
|
+
|
|
168
|
+
A fluent builder, importable directly or via the composable. Modifier and key names are finite string unions, so a typo (`hotkey.mdo.s`) is a compile error, not a silent no-op.
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
hotkey.mod.s; // { key: "s", mod: true }
|
|
172
|
+
hotkey.mod.shift.s; // { key: "s", mod: true, shift: true }
|
|
173
|
+
hotkey.ctrl.alt.delete; // { key: "delete", ctrl: true, alt: true }
|
|
174
|
+
hotkey.escape; // { key: "escape" }
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Chainable modifiers: `mod`, `ctrl`, `meta`, `alt`, `shift` (any order, any combination). Terminal keys: letters `a`-`z`, digits `0`-`9`, `enter`, `escape`, `tab`, `space`, `delete`, `backspace`, `arrowup`/`arrowdown`/`arrowleft`/`arrowright`, `f1`-`f12`, `backslash`, `period`, `comma`, `slash`.
|
|
178
|
+
|
|
179
|
+
### `useKeyboardShortcuts()`
|
|
180
|
+
|
|
181
|
+
Context-free -- no inject/lifecycle, usable in or out of a component `setup()`.
|
|
182
|
+
|
|
183
|
+
#### Returns: `UseKeyboardShortcutsReturn`
|
|
184
|
+
|
|
185
|
+
| Property | Type | Description |
|
|
186
|
+
| ---------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------- |
|
|
187
|
+
| `hotkey` | `HotkeyBuilder` | The same singleton as the named `hotkey` import |
|
|
188
|
+
| `formatShortcut` | `(def: ShortcutDefinition) => { parts: string[]; aria: string }` | `isMac`-bound: formats a definition into display chips and an aria string |
|
|
189
|
+
| `isMac` | `boolean` | `true` when the user agent reports macOS/iOS |
|
|
190
|
+
|
|
191
|
+
### `formatShortcut(def, isMac)`
|
|
192
|
+
|
|
193
|
+
The standalone function (also exported directly) if you need to format for an explicit `isMac` value instead of the detected one:
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
import { formatShortcut } from "@vc-shell/framework";
|
|
197
|
+
|
|
198
|
+
formatShortcut({ key: "s", mod: true }, true); // { parts: ["⌘", "S"], aria: "Meta+S" }
|
|
199
|
+
formatShortcut({ key: "s", mod: true }, false); // { parts: ["Ctrl", "S"], aria: "Control+S" }
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### `ShortcutDefinition`
|
|
203
|
+
|
|
204
|
+
| Property | Type | Required | Description |
|
|
205
|
+
| -------- | --------- | -------- | ----------------------------------------------------------------------------------- |
|
|
206
|
+
| `key` | `string` | Yes | Single key: `"s"`, `"1"`, `"enter"`, `"escape"`, `"f2"`, `"arrowup"`, `"backslash"` |
|
|
207
|
+
| `mod` | `boolean` | No | Platform primary modifier -- Cmd on macOS, Ctrl elsewhere |
|
|
208
|
+
| `ctrl` | `boolean` | No | Literal Ctrl, regardless of platform |
|
|
209
|
+
| `meta` | `boolean` | No | Literal Cmd/Win key, regardless of platform |
|
|
210
|
+
| `alt` | `boolean` | No | Alt/Option |
|
|
211
|
+
| `shift` | `boolean` | No | Shift |
|
|
212
|
+
|
|
213
|
+
Building definitions by hand (`{ key: "s", mod: true }`) works identically to `hotkey.mod.s` -- the builder is sugar over this shape, useful when a definition needs to be constructed dynamically.
|
|
214
|
+
|
|
215
|
+
## Related
|
|
216
|
+
|
|
217
|
+
- [useToolbar](../useToolbar/) -- registers the toolbar items that carry `shortcut`; see its `IBladeToolbar`/`IToolbarItem` reference for the full item shape
|
|
218
|
+
- [Blade Navigation Composables](../../blade-navigation/) -- the blade stack that scopes shortcut dispatch to the active blade and hosts the two built-in shortcuts
|
|
219
|
+
- `IBladeToolbar` in [Core types](../../types/) -- the `shortcut?: ShortcutDefinition` field lives here
|
|
@@ -62,14 +62,14 @@ None.
|
|
|
62
62
|
|
|
63
63
|
### Returns (`ILanguageService`)
|
|
64
64
|
|
|
65
|
-
| Property / Method | Type | Description
|
|
66
|
-
| ------------------------ | -------------------------------------------- |
|
|
67
|
-
| `currentLocale` | `ComputedRef<string>` | The currently active locale code (e.g., `"en-US"`, `"de-DE"`).
|
|
68
|
-
| `setLocale` | `(locale: string) => void` | Switches the application locale.
|
|
69
|
-
| `getLocaleByTag` | `(localeTag: string) => string \| undefined` | Resolves a locale tag to its native display name. Regional tags stay distinct (e.g., `"en-US"` → `"American English"`, `"en-GB"` → `"British English"`); plain codes resolve to the base native name (`"fr"` → `"Français"`). Returns `undefined` if the tag is not recognized.
|
|
70
|
-
| `resolveCamelCaseLocale` | `(locale: string) => string` | Normalizes and validates a locale code to a supported hyphenated lowercase locale (e.g., `"enUS"` to `"en-us"`), falling back to `"en"` if the locale is not supported. Delegates to `resolveSupportedLocale`.
|
|
71
|
-
| `getFlag` | `(language: string) => Promise<string>` | Fetches a flag image URL for the given language/locale. Returns a promise because flags may be loaded lazily.
|
|
72
|
-
| `getCountryCode` | `(language: string) => string` | Extracts the lowercase country code from a language tag (e.g., `"en-US"` to `"us"`, `"de-DE"` to `"de"`). Falls back to `"xx"` for unknown languages.
|
|
65
|
+
| Property / Method | Type | Description |
|
|
66
|
+
| ------------------------ | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
67
|
+
| `currentLocale` | `ComputedRef<string>` | The currently active locale code (e.g., `"en-US"`, `"de-DE"`). |
|
|
68
|
+
| `setLocale` | `(locale: string) => void` | Switches the application locale. Updates `vue-i18n`'s locale, re-renders translated text, configures vee-validate messages, persists the choice, and sets `<html lang>` to the resolved locale so assistive technology picks the right pronunciation dictionary (WCAG 3.1.1). Applications do not need to manage `<html lang>` themselves. |
|
|
69
|
+
| `getLocaleByTag` | `(localeTag: string) => string \| undefined` | Resolves a locale tag to its native display name. Regional tags stay distinct (e.g., `"en-US"` → `"American English"`, `"en-GB"` → `"British English"`); plain codes resolve to the base native name (`"fr"` → `"Français"`). Returns `undefined` if the tag is not recognized. |
|
|
70
|
+
| `resolveCamelCaseLocale` | `(locale: string) => string` | Normalizes and validates a locale code to a supported hyphenated lowercase locale (e.g., `"enUS"` to `"en-us"`), falling back to `"en"` if the locale is not supported. Delegates to `resolveSupportedLocale`. |
|
|
71
|
+
| `getFlag` | `(language: string) => Promise<string>` | Fetches a flag image URL for the given language/locale. Returns a promise because flags may be loaded lazily. |
|
|
72
|
+
| `getCountryCode` | `(language: string) => string` | Extracts the lowercase country code from a language tag (e.g., `"en-US"` to `"us"`, `"de-DE"` to `"de"`). Falls back to `"xx"` for unknown languages. |
|
|
73
73
|
|
|
74
74
|
### Additional Exports
|
|
75
75
|
|
package/runtime/knowledge/docs/shell/components/settings-menu-item/settings-menu-item.docs.md
CHANGED
|
@@ -198,6 +198,17 @@ Do not set `triggerAction="none"` and then rely on `@trigger:click` -- the event
|
|
|
198
198
|
<SettingsMenuItem trigger-action="click" @trigger:click="doSomething" />
|
|
199
199
|
```
|
|
200
200
|
|
|
201
|
+
## Accessibility
|
|
202
|
+
|
|
203
|
+
- The trigger renders as a native `<button type="button">`, so it is in the tab order and Enter/Space activate it. No `tabindex` or `keydown` wiring is needed.
|
|
204
|
+
- Focus ring on `:focus-visible` only, colored via `--menu-item-focus-ring-color`
|
|
205
|
+
- `disabled` is applied to the button element, not only to the click handler — the control is skipped by keyboard navigation instead of being focusable-but-inert
|
|
206
|
+
- With a submenu, the trigger reports `aria-haspopup="true"` and an `aria-expanded` that tracks the open state (both on desktop popover and mobile inline expansion)
|
|
207
|
+
- Without a submenu neither attribute is rendered, so a plain action item is not announced as a menu
|
|
208
|
+
|
|
209
|
+
!!! warning "A custom `#trigger` slot owns its own semantics"
|
|
210
|
+
When you pass `#trigger`, the wrapper stays a `<div>` — nesting your control inside our button would produce invalid, unusable markup. Render an interactive element (a `<button>` or a link) inside the slot yourself, otherwise that item is not keyboard operable.
|
|
211
|
+
|
|
201
212
|
## Related Components
|
|
202
213
|
|
|
203
214
|
- [SettingsMenu](../settings-menu/settings-menu.docs.md) -- parent container
|
package/runtime/knowledge/docs/shell/dashboard/draggable-dashboard/draggable-dashboard.docs.md
CHANGED
|
@@ -140,6 +140,23 @@ function resetLayout() {
|
|
|
140
140
|
- The 12-column grid means common widget widths are: 3 (quarter), 4 (third), 6 (half), and 12 (full width).
|
|
141
141
|
- Register widgets during module `install()` before the dashboard component mounts. Late registrations may not be picked up.
|
|
142
142
|
|
|
143
|
+
## Accessibility
|
|
144
|
+
|
|
145
|
+
Widgets can be rearranged without a pointer, which WCAG 2.5.7 Dragging Movements requires:
|
|
146
|
+
|
|
147
|
+
| Key | Action |
|
|
148
|
+
| ------------------- | --------------------------------------------------------------- |
|
|
149
|
+
| `Tab` | Move focus between widgets (each one is in the tab order) |
|
|
150
|
+
| `Enter` / `Space` | Pick the focused widget up, and drop it again |
|
|
151
|
+
| Arrow keys | While picked up, move the widget one grid cell |
|
|
152
|
+
| `Shift` + arrow key | While picked up, resize by one cell (needs `resizable`) |
|
|
153
|
+
| `Escape` | Cancel the move and return the widget to where it was picked up |
|
|
154
|
+
|
|
155
|
+
Every step is announced through the component's `aria-live` region, and the picked-up widget is outlined so the state is visible to sighted keyboard users. Moves are clamped at the grid edges and at the 2×2 minimum widget size, and the layout is persisted when the widget is dropped — the same as after a mouse drag.
|
|
156
|
+
|
|
157
|
+
!!! note "The widget itself is the control"
|
|
158
|
+
There is no separate "move" button. Gridstack only implements pointer dragging, so the widget is focusable and handles the keys directly. If you render your own interactive elements inside a widget, they keep working — the arrow keys only act while the widget has been explicitly picked up.
|
|
159
|
+
|
|
143
160
|
## Advanced / Exports
|
|
144
161
|
|
|
145
162
|
Besides the `DraggableDashboard` component, `draggable-dashboard/index.ts` re-exports these symbols through the framework root, for building a custom Gridstack dashboard:
|
|
@@ -34,12 +34,12 @@ import { VcVideo } from "@vc-shell/framework";
|
|
|
34
34
|
|
|
35
35
|
## Key Props
|
|
36
36
|
|
|
37
|
-
| Prop | Type | Default | Description
|
|
38
|
-
| ------------------- | -------- | ------- |
|
|
39
|
-
| `source` | `string` | -- | Embed URL for the video (e.g., YouTube embed link)
|
|
40
|
-
| `label` | `string` | -- | Label text displayed above the video
|
|
41
|
-
| `tooltip` | `string` | -- | Tooltip text shown on the label's info icon
|
|
42
|
-
| `additionalSandbox` | `string` | -- | Extra space-separated iframe sandbox tokens appended to the
|
|
37
|
+
| Prop | Type | Default | Description |
|
|
38
|
+
| ------------------- | -------- | ------- | ------------------------------------------------------------------------------------------- |
|
|
39
|
+
| `source` | `string` | -- | Embed URL for the video (e.g., YouTube embed link) |
|
|
40
|
+
| `label` | `string` | -- | Label text displayed above the video |
|
|
41
|
+
| `tooltip` | `string` | -- | Tooltip text shown on the label's info icon |
|
|
42
|
+
| `additionalSandbox` | `string` | -- | Extra space-separated iframe sandbox tokens appended to the resolved sandbox (see Security) |
|
|
43
43
|
|
|
44
44
|
::storybook id="data-display-vcvideo--with-tooltip" height="400"
|
|
45
45
|
|
|
@@ -117,7 +117,7 @@ When `source` is not provided, VcVideo renders a centered film icon placeholder
|
|
|
117
117
|
|
|
118
118
|
- Always use the **embed** URL format, not the standard watch URL. For YouTube, use `https://www.youtube.com/embed/VIDEO_ID` instead of `https://www.youtube.com/watch?v=VIDEO_ID`.
|
|
119
119
|
- The iframe has `loading="lazy"`, so videos below the fold are not loaded until the user scrolls to them. This keeps initial page load fast.
|
|
120
|
-
- The `sandbox` attribute
|
|
120
|
+
- The `sandbox` attribute is resolved from the `source`: `allow-scripts allow-presentation` for any host, plus `allow-same-origin` when the source points at a known video host (YouTube, Vimeo). If your host needs more, append tokens via `additionalSandbox` — see the Security section below.
|
|
121
121
|
- The iframe sets a fixed `allow` permissions policy (`accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture`) plus `allowfullscreen`, so embedded players can autoplay, go fullscreen, and use picture-in-picture. These are not configurable per-instance.
|
|
122
122
|
- The iframe renders at a fixed height of 300px. To customize the height, override the iframe styles via a scoped CSS rule targeting `.vc-video__container iframe`.
|
|
123
123
|
- The placeholder has a height of 200px so the layout does not collapse when no source is provided.
|
|
@@ -125,21 +125,31 @@ When `source` is not provided, VcVideo renders a centered film icon placeholder
|
|
|
125
125
|
## Accessibility
|
|
126
126
|
|
|
127
127
|
- The iframe uses the `title` attribute (set to `label` or "Video") for screen readers
|
|
128
|
-
- `sandbox`
|
|
128
|
+
- `sandbox` is resolved per source: `allow-scripts allow-presentation` always, plus `allow-same-origin` for known video hosts
|
|
129
129
|
- `loading="lazy"` defers iframe load until visible
|
|
130
130
|
- Placeholder state uses `role="img"` with `aria-label="No video source"`
|
|
131
131
|
|
|
132
132
|
## Security
|
|
133
133
|
|
|
134
|
-
The iframe `sandbox`
|
|
134
|
+
The iframe `sandbox` is resolved from the `source`, not fixed:
|
|
135
|
+
|
|
136
|
+
| Source | Resolved sandbox |
|
|
137
|
+
| ----------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
|
|
138
|
+
| Known video host (youtube.com, youtube-nocookie.com, youtu.be, vimeo.com, and their subdomains) | `allow-scripts allow-presentation allow-same-origin` |
|
|
139
|
+
| Any other host | `allow-scripts allow-presentation` |
|
|
140
|
+
| Relative or malformed URL | `allow-scripts allow-presentation` |
|
|
141
|
+
|
|
142
|
+
Mainstream players need their own origin to start up: without `allow-same-origin` the framed document gets an opaque origin and YouTube's bootstrap throws on storage access before it can build the player. Restricting that token to an allowlist keeps playback working without loosening the sandbox for arbitrary sources.
|
|
143
|
+
|
|
144
|
+
To add capabilities a specific host needs, pass them through `additionalSandbox`:
|
|
135
145
|
|
|
136
146
|
```vue
|
|
137
147
|
<!-- Some embeds need popups for auth flows -->
|
|
138
148
|
<VcVideo source="https://example.com/embed" additional-sandbox="allow-popups" />
|
|
139
149
|
```
|
|
140
150
|
|
|
141
|
-
!!! danger "Never
|
|
142
|
-
|
|
151
|
+
!!! danger "Never add `allow-same-origin` for a source on your own origin"
|
|
152
|
+
`allow-same-origin` combined with `allow-scripts` is only dangerous when the framed document shares the host page's origin — then its script can reach `parent.document` and remove the sandbox attribute. For a cross-origin video host the Same-Origin Policy already blocks that, which is why the allowlist above is safe. `VcVideo` renders whatever `source` you pass, so never add the token via `additionalSandbox` for user-supplied URLs or for assets served from the app's own origin.
|
|
143
153
|
|
|
144
154
|
!!! warning "Always use embed URLs, not watch URLs"
|
|
145
155
|
YouTube watch URLs (`youtube.com/watch?v=...`) will be blocked by the browser's frame policy. Always convert to the embed format (`youtube.com/embed/VIDEO_ID`). Vimeo similarly requires `player.vimeo.com/video/VIDEO_ID`.
|
|
@@ -156,7 +166,7 @@ YouTube watch URLs (`youtube.com/watch?v=...`) will be blocked by the browser's
|
|
|
156
166
|
|
|
157
167
|
- VcVideo lives in `framework/ui/components/atoms/vc-video/`.
|
|
158
168
|
- The component is a thin wrapper around a native `<iframe>` — no custom video controls are implemented.
|
|
159
|
-
- The `sandbox` attribute is computed from a
|
|
169
|
+
- The `sandbox` attribute is computed from a base (`allow-scripts allow-presentation`), plus `allow-same-origin` when `isTrustedEmbedHost(source)` matches `TRUSTED_EMBED_HOSTS`, plus any `additionalSandbox` tokens, deduped. Host matching is exact or on a dot boundary, so `evil-youtube.com` does not match; an unparseable URL falls back to the strict base — see the Security section.
|
|
160
170
|
- The label is rendered via `VcLabel` (internal atom) with the `tooltip` prop forwarded as the VcLabel tooltip slot content.
|
|
161
171
|
- Placeholder state (`source` is falsy) swaps the iframe for a `<div>` with `role="img"` containing a `VcIcon` with `lucide-film`.
|
|
162
172
|
|
package/runtime/knowledge/docs/ui/components/molecules/vc-breadcrumbs/vc-breadcrumbs.docs.md
CHANGED
|
@@ -285,6 +285,10 @@ clickHandler: () => { navigate(); return true; }
|
|
|
285
285
|
- The last visible item carries `aria-current="page"` to indicate the current location.
|
|
286
286
|
- Separator characters are marked `aria-hidden="true"` so screen readers skip them.
|
|
287
287
|
- When items overflow, the dropdown is accessible via the trigger button with standard keyboard interaction (Enter/Space to open).
|
|
288
|
+
- An item with an icon and no `title` (a back control, for example) would otherwise have no accessible name — its button falls back to a localized `aria-label` ("Back" / "Zurück"). Items with a visible title set no `aria-label`, so the name is never duplicated.
|
|
289
|
+
|
|
290
|
+
!!! tip "Prefer a real title over relying on the fallback"
|
|
291
|
+
The fallback keeps an icon-only item usable, but "Back" says less than the destination does. Pass a `title` whenever you know it — screen-reader users then hear where the control leads, and sighted users get the truncation tooltip.
|
|
288
292
|
|
|
289
293
|
## Related Components
|
|
290
294
|
|
|
@@ -687,6 +687,7 @@ VcInput blocks the minus key for both `number` and `integer` types. If you need
|
|
|
687
687
|
| `required` | `boolean` | `false` | Adds a red asterisk to the label (visual only, no validation) |
|
|
688
688
|
| `loading` | `boolean` | `false` | Shows a spinning loader icon inside the field |
|
|
689
689
|
| `autofocus` | `boolean` | `false` | Auto-focuses the input on mount |
|
|
690
|
+
| `autocomplete` | `string` | -- | Autofill hint set on the native input (e.g. `"username"`, `"current-password"`) |
|
|
690
691
|
| `error` | `boolean` | `false` | Activates error styling (red border and ring) |
|
|
691
692
|
| `errorMessage` | `string` | -- | Error text shown below the field; also activates error styling when truthy |
|
|
692
693
|
| `debounce` | `string \| number` | -- | Delay in ms before emitting model updates |
|
|
@@ -109,16 +109,20 @@ When `expanded` is `false`, the menu shows only icons and letter abbreviations.
|
|
|
109
109
|
|
|
110
110
|
## CSS Variables
|
|
111
111
|
|
|
112
|
-
| Variable
|
|
113
|
-
|
|
|
114
|
-
| `--vc-menu-gap`
|
|
112
|
+
| Variable | Default | Description |
|
|
113
|
+
| --------------------------------- | -------------------- | --------------------------------------- |
|
|
114
|
+
| `--vc-menu-gap` | `8px` | Gap between menu items |
|
|
115
|
+
| `--vc-menu-item-focus-ring-color` | `var(--primary-500)` | Focus ring color on keyboard navigation |
|
|
115
116
|
|
|
116
117
|
## Accessibility
|
|
117
118
|
|
|
118
|
-
-
|
|
119
|
-
-
|
|
120
|
-
-
|
|
121
|
-
-
|
|
119
|
+
- `VcMenuItem` renders its interactive row as a native `<button type="button">`, so it is in the tab order and the browser maps Enter/Space to activation. There is no `tabindex` juggling and no `keydown` handler to keep in sync.
|
|
120
|
+
- Focus ring on `:focus-visible` only (not on mouse click), colored via `--vc-menu-item-focus-ring-color`
|
|
121
|
+
- The active item exposes `aria-current="page"`, so assistive technology announces the current location — `active` drives both the styling and the attribute
|
|
122
|
+
- When the menu is collapsed to icons the visible title is hidden, so the button carries `aria-label` with the item title; expanded items rely on their visible text instead and set no `aria-label`
|
|
123
|
+
- Icons and letter abbreviations are `aria-hidden="true"` — they are decorative next to the accessible name
|
|
124
|
+
- `VcMenuGroup` with `variant="section"` renders a native button that reports `aria-expanded` and points `aria-controls` at the children wrapper it toggles
|
|
125
|
+
- Collapsed mode shows tooltips for discoverability (in addition to, not instead of, the accessible name)
|
|
122
126
|
|
|
123
127
|
## Related Components
|
|
124
128
|
|
|
@@ -17,13 +17,13 @@ The composables follow a PrimeVue-inspired pattern: each returns reactive state
|
|
|
17
17
|
|
|
18
18
|
### Data & State
|
|
19
19
|
|
|
20
|
-
| Composable | Purpose
|
|
21
|
-
| ---------------------- |
|
|
22
|
-
| `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.
|
|
23
|
-
| `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. |
|
|
24
|
-
| `useColumnWidthEngine` | Pure functions for deterministic column width computation (see below).
|
|
25
|
-
| `useDataProcessing` | Client-side sort pipeline (single/multi) and row grouping. Skipped when `lazy: true` (server-side).
|
|
26
|
-
| `useTableContext` | Provides/injects table-level context for sub-components.
|
|
20
|
+
| Composable | Purpose |
|
|
21
|
+
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
22
|
+
| `useDataTableState` | Persists column state (v2 schema: weights, order, hidden/shown IDs, `userSized`) 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. |
|
|
23
|
+
| `useTableColumns` | Column ordering, width management via `columnState` (weight store), computed pixel widths via `engineOutput`. Exposes `recompute()` to trigger a recalculation pass, which also re-derives weights from `VcColumn` props until `markSizingCustomized()` is called. Watches `visibleColumns` and appends new columns without dropping hidden ones. |
|
|
24
|
+
| `useColumnWidthEngine` | Pure functions for deterministic column width computation (see below). |
|
|
25
|
+
| `useDataProcessing` | Client-side sort pipeline (single/multi) and row grouping. Skipped when `lazy: true` (server-side). |
|
|
26
|
+
| `useTableContext` | Provides/injects table-level context for sub-components. |
|
|
27
27
|
|
|
28
28
|
### Sorting & Filtering
|
|
29
29
|
|
|
@@ -114,12 +114,12 @@ Mutates `specs` in place so that the weights of `visibleIds` sum to 1.0. Called
|
|
|
114
114
|
|
|
115
115
|
### When weights update
|
|
116
116
|
|
|
117
|
-
| User action | Weight change
|
|
118
|
-
| --------------------------- |
|
|
119
|
-
| Column resize (drag border) | Dragged column and right neighbor exchange weight proportionally
|
|
120
|
-
| Column show/hide | Hidden column's weight is preserved; shown column uses saved or initial weight
|
|
121
|
-
| Reset columns | All weights rebuilt from declarative `width` props
|
|
122
|
-
| Container resize |
|
|
117
|
+
| User action | Weight change |
|
|
118
|
+
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
119
|
+
| Column resize (drag border) | Dragged column and right neighbor exchange weight proportionally; sizing becomes **customized** (`userSized` persisted) |
|
|
120
|
+
| Column show/hide | Hidden column's weight is preserved; shown column uses saved or initial weight |
|
|
121
|
+
| Reset columns | All weights rebuilt from declarative `width` props; sizing returns to declarative mode |
|
|
122
|
+
| Container resize | Declarative mode: weights re-derived from `VcColumn` props at the new width (declared px stay exact). Customized: weights unchanged, px scale with it |
|
|
123
123
|
|
|
124
124
|
## Usage
|
|
125
125
|
|
|
@@ -780,6 +780,16 @@ const expandedRows = ref<Order[]>([]);
|
|
|
780
780
|
</script>
|
|
781
781
|
```
|
|
782
782
|
|
|
783
|
+
### On mobile
|
|
784
|
+
|
|
785
|
+
Expandable rows work the same way in the mobile card view — no extra markup. The `expander` column itself is not rendered as a card field (special columns never are); instead each expandable card gets a chevron toggle on its right edge, and the `#expansion` slot renders inside the card, below the card body. Expansion state is shared with the desktop view, so `expanded-rows` behaves identically on both.
|
|
786
|
+
|
|
787
|
+
Two mobile-specific details:
|
|
788
|
+
|
|
789
|
+
- Tapping the chevron does not trigger `row-click`, so opening the details of a row and expanding it stay separate gestures.
|
|
790
|
+
- The card adds **no padding** around the expansion — the slot content owns its own spacing. Screen width is scarce on a phone, and slot content usually carries padding already, so anything added here would inset it twice.
|
|
791
|
+
- The expansion area scrolls horizontally when its content is wider than the card. Slot content laid out for desktop widths therefore stays reachable on a phone, though a layout that reflows for narrow screens reads better.
|
|
792
|
+
|
|
783
793
|
---
|
|
784
794
|
|
|
785
795
|
## Row Grouping
|
|
@@ -1043,7 +1053,9 @@ Persist column widths, column order, and column visibility across page reloads.
|
|
|
1043
1053
|
|
|
1044
1054
|
**Storage key format:** `VC_DATATABLE_PRODUCT-LIST` (uppercased `state-key`).
|
|
1045
1055
|
|
|
1046
|
-
**Schema version:** The persisted state uses the **v2 schema**, which stores column weights, column order,
|
|
1056
|
+
**Schema version:** The persisted state uses the **v2 schema**, which stores column weights, column order, hidden/shown column IDs, and a `userSized` flag. `containerWidth` is not stored because weights are container-independent — the engine recomputes pixel values from weights on every mount.
|
|
1057
|
+
|
|
1058
|
+
**`userSized` and declarative widths:** until the user actually resizes a column, weights are treated as _declarative_ — re-derived from the `VcColumn` `width`/`minWidth`/`maxWidth` props at the current container width on every recompute. This heals weights captured at a transient width (blades animate their `width` for ~300ms, so the first measurement often happens mid-animation) and means `width="60"` renders as 60px at any blade width. A mouse resize sets `userSized: true`: from then on the saved weights are the user's data — restored as-is and scaled proportionally when the blade width changes. Restored states without the flag (including states saved by older versions) stay declarative; column order and hidden/shown IDs are restored either way. **Reset columns** returns the table to the declarative mode.
|
|
1047
1059
|
|
|
1048
1060
|
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.
|
|
1049
1061
|
|
|
@@ -1658,20 +1670,20 @@ function onRowRemove(event: { data: Product; index: number; cancel: () => void }
|
|
|
1658
1670
|
|
|
1659
1671
|
## VcDataTable Slots Reference
|
|
1660
1672
|
|
|
1661
|
-
| Slot | Props | Description
|
|
1662
|
-
| ----------------------- | --------------------------------------------------------------- |
|
|
1663
|
-
| `default` | -- | VcColumn declarations (required).
|
|
1664
|
-
| `header` | -- | Custom header content above the table.
|
|
1665
|
-
| `footer` | -- | Custom footer content below the table body.
|
|
1666
|
-
| `search-header-actions` | -- | Extra buttons in the search toolbar (beside filter icon).
|
|
1667
|
-
| `selection-banner` | `{ count, totalCount, isSelectAll, selectAll, clearSelection }` | Custom selection banner.
|
|
1668
|
-
| `expansion` | `{ data: T, index: number }` | Content rendered below an expanded row.
|
|
1669
|
-
| `empty` | -- | Custom empty state (no items, no search).
|
|
1670
|
-
| `not-found` | -- | Custom not-found state (no items + active search/filters).
|
|
1671
|
-
| `loading` | -- | Custom loading state.
|
|
1672
|
-
| `groupheader` | `{ data: T, index: number }` | Custom row group header.
|
|
1673
|
-
| `groupfooter` | `{ data: T, index: number }` | Custom row group footer.
|
|
1674
|
-
| `pagination` | `{ pages, currentPage, onPageClick }` | Custom pagination replacing built-in VcPagination.
|
|
1673
|
+
| Slot | Props | Description |
|
|
1674
|
+
| ----------------------- | --------------------------------------------------------------- | ------------------------------------------------------------ |
|
|
1675
|
+
| `default` | -- | VcColumn declarations (required). |
|
|
1676
|
+
| `header` | -- | Custom header content above the table. |
|
|
1677
|
+
| `footer` | -- | Custom footer content below the table body. |
|
|
1678
|
+
| `search-header-actions` | -- | Extra buttons in the search toolbar (beside filter icon). |
|
|
1679
|
+
| `selection-banner` | `{ count, totalCount, isSelectAll, selectAll, clearSelection }` | Custom selection banner. |
|
|
1680
|
+
| `expansion` | `{ data: T, index: number }` | Content rendered below an expanded row (desktop and mobile). |
|
|
1681
|
+
| `empty` | -- | Custom empty state (no items, no search). |
|
|
1682
|
+
| `not-found` | -- | Custom not-found state (no items + active search/filters). |
|
|
1683
|
+
| `loading` | -- | Custom loading state. |
|
|
1684
|
+
| `groupheader` | `{ data: T, index: number }` | Custom row group header. |
|
|
1685
|
+
| `groupfooter` | `{ data: T, index: number }` | Custom row group footer. |
|
|
1686
|
+
| `pagination` | `{ pages, currentPage, onPageClick }` | Custom pagination replacing built-in VcPagination. |
|
|
1675
1687
|
|
|
1676
1688
|
---
|
|
1677
1689
|
|