@vc-shell/vc-app-skill 2.2.0 → 2.3.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/useAsync/useAsync.docs.md +3 -0
- package/runtime/knowledge/docs/core/composables/useKeyboardShortcuts/useKeyboardShortcuts.docs.md +219 -0
- package/runtime/knowledge/docs/core/composables/useToolbar/useToolbar.docs.md +32 -12
- package/runtime/knowledge/docs/shell/components/error-interceptor/error-interceptor.docs.md +3 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vc-shell/vc-app-skill",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.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.3.0
|
|
@@ -1 +1 @@
|
|
|
1
|
-
Synced from framework at commit
|
|
1
|
+
Synced from framework at commit eb3675e7a on 2026-07-28T09:06:26.940Z
|
|
@@ -172,6 +172,8 @@ const {
|
|
|
172
172
|
|
|
173
173
|
By default, `useAsync` shows a toast notification on failure. The notification is **deferred** via `setTimeout(0)` so that the `ErrorInterceptor` can cancel it when a blade error banner is already displayed -- this prevents duplicate toast + banner for the same error.
|
|
174
174
|
|
|
175
|
+
No toast is scheduled at all while the session is flagged as expired. When a platform API call returns 401, the fetch interceptor signs the user out and redirects to the login page; every data load in flight on the page being left fails at the same time. Suppressing those toasts keeps the single "session expired" message from being buried under a cascade of load errors. The `error` ref is still populated as usual -- only the toast is skipped -- and the flag is cleared when a new sign-in begins.
|
|
176
|
+
|
|
175
177
|
```typescript
|
|
176
178
|
// Default: shows toast on error
|
|
177
179
|
const { action: save } = useAsync(async () => {
|
|
@@ -419,6 +421,7 @@ const { action: save, loading: saveLoading } = useAsync(async () => saveData());
|
|
|
419
421
|
- Errors are parsed via `parseError()` into `DisplayableError` objects that have a user-friendly `message` property.
|
|
420
422
|
- Toast notifications are deferred with `setTimeout(0)` and registered via `setPendingErrorNotification`. The `ErrorInterceptor` (blade-level `onErrorCaptured`) can call `cancelPendingErrorNotification` to suppress the toast when a blade error banner is shown instead.
|
|
421
423
|
- The notification module is lazy-imported to avoid circular dependencies with `@core/composables`.
|
|
424
|
+
- `isSessionExpired()` from `@core/utilities/sessionExpiration` gates the notification. The flag is set by the fetch interceptor on the 401 that kills the session and cleared by `useUser.signIn`. It is imported directly, not through the `@core/utilities` barrel, for the same circular-dependency reason as `pendingErrorNotifications`.
|
|
422
425
|
|
|
423
426
|
<!-- internal:end -->
|
|
424
427
|
|
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
|
|
@@ -234,6 +234,24 @@ registerToolbarItem({
|
|
|
234
234
|
});
|
|
235
235
|
```
|
|
236
236
|
|
|
237
|
+
### Keyboard shortcut for a button
|
|
238
|
+
|
|
239
|
+
Add `shortcut: hotkey.mod.s` to give a button a `Cmd/Ctrl+S`-style shortcut. The button automatically shows an OS-aware `⌘S`/`Ctrl+S` tooltip and sets `aria-keyshortcuts` -- no extra wiring:
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
import { hotkey } from "@vc-shell/framework";
|
|
243
|
+
|
|
244
|
+
registerToolbarItem({
|
|
245
|
+
id: "save",
|
|
246
|
+
title: "Save",
|
|
247
|
+
icon: "fas fa-save",
|
|
248
|
+
clickHandler: () => save(),
|
|
249
|
+
shortcut: hotkey.mod.s,
|
|
250
|
+
});
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
See [useKeyboardShortcuts](../useKeyboardShortcuts/) for the full `hotkey` builder, OS adaptation, and accessibility details.
|
|
254
|
+
|
|
237
255
|
## Common mistakes
|
|
238
256
|
|
|
239
257
|
### Reaching for `useToolbar` before considering the array pattern
|
|
@@ -334,18 +352,19 @@ function helperFunction() {
|
|
|
334
352
|
|
|
335
353
|
### IToolbarItem
|
|
336
354
|
|
|
337
|
-
| Property | Type | Required | Description
|
|
338
|
-
| -------------- | ----------------------------------------------------------------------------------------------------------------- | -------- |
|
|
339
|
-
| `id` | `string` | Yes | Unique identifier for the button
|
|
340
|
-
| `title` | `string \| Ref<string> \| ComputedRef<string>` | No | Button label (supports reactive values)
|
|
341
|
-
| `icon` | `string \| (() => string)` | No | Icon class (e.g., `"fas fa-save"`) or a function returning one
|
|
342
|
-
| `clickHandler` | `(app?) => void` | No | Click callback
|
|
343
|
-
| `disabled` | `boolean \| ComputedRef<boolean \| undefined>` | No | Whether the button is disabled
|
|
344
|
-
| `isVisible` | `boolean \| Ref<boolean \| undefined> \| ComputedRef<boolean \| undefined> \| ((blade?) => boolean \| undefined)` | No | Controls button visibility
|
|
345
|
-
| `priority` | `number` | No | Sort order (higher = displayed first, default `0`)
|
|
346
|
-
| `separator` | `"left" \| "right" \| "both"` | No | Adds a visual divider next to the button
|
|
347
|
-
| `permissions` | `string \| string[]` | No | Required permission(s) to display the button
|
|
348
|
-
| `bladeId` | `string` | No | Target blade ID (auto-resolved from context)
|
|
355
|
+
| Property | Type | Required | Description |
|
|
356
|
+
| -------------- | ----------------------------------------------------------------------------------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------- |
|
|
357
|
+
| `id` | `string` | Yes | Unique identifier for the button |
|
|
358
|
+
| `title` | `string \| Ref<string> \| ComputedRef<string>` | No | Button label (supports reactive values) |
|
|
359
|
+
| `icon` | `string \| (() => string)` | No | Icon class (e.g., `"fas fa-save"`) or a function returning one |
|
|
360
|
+
| `clickHandler` | `(app?) => void` | No | Click callback |
|
|
361
|
+
| `disabled` | `boolean \| ComputedRef<boolean \| undefined>` | No | Whether the button is disabled |
|
|
362
|
+
| `isVisible` | `boolean \| Ref<boolean \| undefined> \| ComputedRef<boolean \| undefined> \| ((blade?) => boolean \| undefined)` | No | Controls button visibility |
|
|
363
|
+
| `priority` | `number` | No | Sort order (higher = displayed first, default `0`) |
|
|
364
|
+
| `separator` | `"left" \| "right" \| "both"` | No | Adds a visual divider next to the button |
|
|
365
|
+
| `permissions` | `string \| string[]` | No | Required permission(s) to display the button |
|
|
366
|
+
| `bladeId` | `string` | No | Target blade ID (auto-resolved from context) |
|
|
367
|
+
| `shortcut` | `ShortcutDefinition` | No | Keyboard shortcut that triggers `clickHandler`; build with `hotkey.*`. See [useKeyboardShortcuts](../useKeyboardShortcuts/) |
|
|
349
368
|
|
|
350
369
|
`IToolbarItem` is the shape consumed by `ToolbarService`. The blade-level array binding uses `IBladeToolbar` (see [Core types](../../types/)), a near-identical shape that the framework normalizes into `IToolbarItem` before render.
|
|
351
370
|
|
|
@@ -354,4 +373,5 @@ function helperFunction() {
|
|
|
354
373
|
- [useBlade](../useBlade/) -- blade context that toolbar items are scoped to
|
|
355
374
|
- [usePermissions](../usePermissions/) -- conditionally register toolbar items based on permissions
|
|
356
375
|
- [useAsync](../useAsync/) -- wraps async operations with loading state for disabling buttons
|
|
376
|
+
- [useKeyboardShortcuts](../useKeyboardShortcuts/) -- the `hotkey` builder and OS-aware formatting behind the `shortcut` field
|
|
357
377
|
- `IBladeToolbar` in [Core types](../../types/) — the shape used by the `:toolbar-items` array binding
|
|
@@ -21,6 +21,7 @@ The component is renderless -- it renders its default slot and passes the curren
|
|
|
21
21
|
2. When inside a blade (`hasBlade = true`), errors are intercepted and forwarded to `bladeStack.setBladeError()`. This displays the error in the blade's built-in error banner and prevents the error from propagating to the global error handler (avoiding duplicate toast notifications).
|
|
22
22
|
3. When not inside a blade or when `capture` is true, errors are captured and exposed via the slot's `error` prop.
|
|
23
23
|
4. Pending `useAsync` error notifications are cancelled via `cancelPendingErrorNotification` when the blade banner takes over.
|
|
24
|
+
5. If the session has expired (a platform API call returned 401 and the app is redirecting to login), the banner is skipped -- the deferred toast is still cancelled in step 4, so the failed load leaves no trace on the page being abandoned.
|
|
24
25
|
|
|
25
26
|
## Props
|
|
26
27
|
|
|
@@ -74,6 +75,7 @@ The component is renderless -- it renders its default slot and passes the curren
|
|
|
74
75
|
- **Inside a blade**: Errors set `BladeDescriptor.error` via the stack. The blade header renders the error banner. Calling `reset` clears the blade error.
|
|
75
76
|
- **Outside a blade** (with `capture`): Errors are stored in a local ref and exposed via slot props. No blade banner is involved.
|
|
76
77
|
- **Error propagation**: When inside a blade, the error is stopped from propagating (prevents duplicate toasts from the global handler). The `capture` prop also stops propagation.
|
|
78
|
+
- **Expired session**: While the session is flagged as expired, no blade banner is set. A dead auth cookie fails every data load on the page at once, and one redirect to login is more useful than a banner on each blade being left behind.
|
|
77
79
|
|
|
78
80
|
## Exports
|
|
79
81
|
|
|
@@ -93,4 +95,5 @@ The component must be imported before use, as shown above.
|
|
|
93
95
|
|
|
94
96
|
- `framework/core/composables/useErrorHandler/` -- the underlying composable
|
|
95
97
|
- `framework/core/utilities/pendingErrorNotifications.ts` -- cancels deferred toasts
|
|
98
|
+
- `framework/core/utilities/sessionExpiration.ts` -- the expired-session flag that suppresses the banner
|
|
96
99
|
- `framework/core/blade-navigation/` -- BladeStack error management (types the interceptor injects)
|