vue-dockable-desktop 1.1.1 → 1.1.2
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 +100 -0
- package/dist/components/VddContextMenu.vue.d.ts +3 -2
- package/dist/composables/useOverlayHost.d.ts +2 -1
- package/dist/composables/usePanel.d.ts +10 -0
- package/dist/composables/useWorkspace.d.ts +1 -1
- package/dist/core/dragResize.d.ts +19 -0
- package/dist/core/escape.d.ts +4 -0
- package/dist/core/eventBus.d.ts +1 -1
- package/dist/core/toast.d.ts +17 -0
- package/dist/core/workspace.d.ts +2 -2
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +685 -596
- package/dist/index.js.map +1 -1
- package/dist/styles.css +18 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,106 @@ correspondence lives, alongside the feature-by-feature map in [docs/PARITY.md](d
|
|
|
11
11
|
|
|
12
12
|
## [Unreleased]
|
|
13
13
|
|
|
14
|
+
## [1.1.2] — 2026-09-24
|
|
15
|
+
|
|
16
|
+
**Parity: react-dockable-desktop 6.3.1.**
|
|
17
|
+
|
|
18
|
+
Fixes from a user bug report. Every item was reproduced before it was fixed, and each fix has a
|
|
19
|
+
regression test that fails against 1.1.1. No public API changes: the export list in
|
|
20
|
+
`api-surface.json` is unchanged.
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- **One Escape could close several things.** Modals, drawers, the context menu, the toolbar
|
|
25
|
+
flyout and the toolbar search all listen for Escape, most of them on `document`, and the
|
|
26
|
+
answering modal's `stopPropagation()` never stopped the other listeners there. So a drawer
|
|
27
|
+
opened *after* a modal closed along with it; a context menu, toolbar flyout or toolbar search
|
|
28
|
+
inside a modal or drawer closed its host too; and with a drawer on each side, one press
|
|
29
|
+
closed both. Whoever acts on Escape now claims the event, and every other listener checks for
|
|
30
|
+
a claim first. Transient UI answers before overlays: the menu and flyout listen in the
|
|
31
|
+
capture phase, and the search claims on its own input. With two drawers open, the one opened
|
|
32
|
+
last closes first. An application widget inside a modal can keep the modal open by calling
|
|
33
|
+
`event.preventDefault()` on its own Escape. [ADR 0016](docs/decisions/0016-escape-claiming.md),
|
|
34
|
+
divergence D17 in [PARITY.md](docs/PARITY.md). Covered by `test/components/escapeRouting.test.ts`.
|
|
35
|
+
|
|
36
|
+
- **A custom context-menu slot closed before its items could be clicked.** In slot mode the
|
|
37
|
+
outside-press check had no element for the slot, so the `pointerdown` of any click inside it
|
|
38
|
+
counted as outside and closed the menu; the `click` then never reached the item. The slot is
|
|
39
|
+
now wrapped in a `display: contents` element that counts as inside. The docs said dismissal
|
|
40
|
+
"becomes yours" in slot mode; it does not — Escape and an outside press still close the menu.
|
|
41
|
+
|
|
42
|
+
- **`usePanel()` did not work inside a modal or side panel.** It looked the panel up in the
|
|
43
|
+
docked-panel state, which holds no overlays. So `onBeforeClose` registered nothing, and Escape
|
|
44
|
+
or `close()` closed the overlay regardless; `onSaveState` and `minimize` warned that the panel
|
|
45
|
+
was "rendered standalone"; `title` returned the instance id, and `dirty` stayed `false` after
|
|
46
|
+
`setDirty(true)`. The overlay now supplies its title, dirty state and guard registration, and
|
|
47
|
+
`onSaveState` and `minimize` say accurately why they do nothing there.
|
|
48
|
+
|
|
49
|
+
- **A toast adapter's `show` was never called.** A new toast only went into the built-in queue;
|
|
50
|
+
only a repeat of its id reached the adapter, as `update`. The queued records were never
|
|
51
|
+
removed either, so they piled up and all appeared at once if the adapter was later unset.
|
|
52
|
+
In adapter mode nothing is queued now: a new id goes to `show` — with `type` and the
|
|
53
|
+
container's `defaultDuration` and `defaultClosable` filled in — a repeated id to `update`,
|
|
54
|
+
and a dismissed id used again is a new `show`. Toasts raised before the adapter mounts are
|
|
55
|
+
handed to it. A toast's `onClose` is not called in adapter mode, and the docs now say so.
|
|
56
|
+
|
|
57
|
+
- **`loadLayout` accepted a partial layout and cleared the workspace.** Valid JSON with a
|
|
58
|
+
`gridRoot` but without `floating`, `minimized` or `panels` parsed to an *empty* workspace,
|
|
59
|
+
which `loadLayout` applied and reported as success — every open panel closed, and `true`
|
|
60
|
+
returned. It now returns `false` with a warning and leaves the layout unchanged.
|
|
61
|
+
|
|
62
|
+
- **Right-to-left gestures went the wrong way.** A pointer delta is physical, while the sizes,
|
|
63
|
+
sides and tab indexes it changes are logical, and a flex row reverses under RTL:
|
|
64
|
+
- a horizontal split divider moved away from the pointer;
|
|
65
|
+
- a sidebar resizer shrank the drawer when dragged away from its edge;
|
|
66
|
+
- a toolbar flyout was placed by the workspace's direction, though the strip sits outside the
|
|
67
|
+
workspace and follows the page — so an RTL workspace in an LTR page opened a left strip's
|
|
68
|
+
flyout off-screen, and the viewport clamp pulled it back over the strip;
|
|
69
|
+
- a tab dropped on another tab's left half was inserted on its right, and vice versa.
|
|
70
|
+
|
|
71
|
+
Each gesture now reads its own element's computed direction when it starts. The manual's RTL
|
|
72
|
+
section says that `<VddSidebar>` and `<VddToolbar>` follow the page's `dir`, not the
|
|
73
|
+
workspace's.
|
|
74
|
+
|
|
75
|
+
- **`.vdd-fill-viewport` did nothing on `<VddDesktop>` itself.** There the class shares an
|
|
76
|
+
element with `.vdd-workspace`, whose `height: 100%` has equal specificity and comes later. A
|
|
77
|
+
compound selector now outranks it. The zero-height warning, which recommends the class, says
|
|
78
|
+
where it can go.
|
|
79
|
+
|
|
80
|
+
- **A quick click on a taskbar icon could land on its preview.** The preview rose into place
|
|
81
|
+
from below — starting at 90% of its lift, over the icon — so for its first frames a click or
|
|
82
|
+
right-click meant for the icon hit the preview; and its hover bridge reached 4px past the gap
|
|
83
|
+
even at rest. It now drifts down into place from just above, so neither the preview nor its
|
|
84
|
+
bridge ever covers the icon, and the bridge spans exactly the 8px gap. Measured frame by
|
|
85
|
+
frame in Chrome: a start below the resting place, even by 6px, still put the bridge over the
|
|
86
|
+
top of the icon.
|
|
87
|
+
|
|
88
|
+
- **A collapsed toolbar left a 1px line and kept its buttons in the Tab order.** Collapsing only
|
|
89
|
+
set the strip's size to zero. It now also removes the edge border and makes the strip `inert`.
|
|
90
|
+
|
|
91
|
+
- **Invalid ARIA.** Toolbar flyout tools were `role="menuitem"` with `aria-pressed`; they are
|
|
92
|
+
now `menuitemradio` with `aria-checked`. Checkbox context-menu items carried `aria-checked` on
|
|
93
|
+
a plain `menuitem`; they are now `menuitemcheckbox`. The toast container's `aria-label` sat on
|
|
94
|
+
a `<div>` with no role, where it is ignored; it now has `role="region"`.
|
|
95
|
+
|
|
96
|
+
- **An event map declared as an `interface` failed to compile.** The manual shows
|
|
97
|
+
`interface MyEvents { … }`, but the type parameter was constrained to
|
|
98
|
+
`Record<string, unknown>`, which an interface does not satisfy (TS2344). The constraint on
|
|
99
|
+
`createWorkspace`, `Workspace`, `useWorkspace` and `EventBus` is now `object`, so interfaces
|
|
100
|
+
and type aliases both work. Payloads are still checked against the map.
|
|
101
|
+
|
|
102
|
+
### Documentation
|
|
103
|
+
|
|
104
|
+
- Removed the `#panel-actions` slot on `<VddDesktop>`, which was documented but never existed
|
|
105
|
+
(manual and PARITY.md); the manual now says where a panel's own actions belong.
|
|
106
|
+
- `updateSplitSizes`: the root branch's path is `[]`; `[0]` is its first child.
|
|
107
|
+
- `<VddFloatingWidget>` takes `widget-id`, not `id`, in the migration guide.
|
|
108
|
+
- A close guard does not replace the unsaved-changes question: it runs first, and a dirty panel
|
|
109
|
+
is still asked about.
|
|
110
|
+
- Only `useSidebar()` works from a panel in the workspace; `useSidebarTab()` needs a drawer tab.
|
|
111
|
+
- Filled in the empty table of theme tokens that have no base value, and corrected the prose
|
|
112
|
+
around it: every rule that reads them has a fallback, and no built-in skin sets them.
|
|
113
|
+
|
|
14
114
|
## [1.1.1] — 2026-09-19
|
|
15
115
|
|
|
16
116
|
**Parity: react-dockable-desktop 6.3.1.**
|
|
@@ -12,8 +12,9 @@ type __VLS_Slots = {
|
|
|
12
12
|
* and it needs no provider, no ref handshake and no adapter object.
|
|
13
13
|
*
|
|
14
14
|
* `items` is the pending menu, `x`/`y` the requested position, and `close` dismisses it.
|
|
15
|
-
* Positioning and
|
|
16
|
-
*
|
|
15
|
+
* Positioning and keyboard navigation are then yours: the built-in viewport clamping
|
|
16
|
+
* belongs to the markup this replaces. Dismissal is not — Escape and a press outside the
|
|
17
|
+
* slot still close the menu, and a press inside it does not.
|
|
17
18
|
*/
|
|
18
19
|
default?: (props: {
|
|
19
20
|
items: ContextMenuItem[];
|
|
@@ -12,7 +12,8 @@ import type { OverlayInstance } from '../core/overlays';
|
|
|
12
12
|
* **Escape routing** lives here too, and is the reason it can be stated once: Escape belongs
|
|
13
13
|
* to the topmost modal, and reaches a drawer only when no modal is open. rdd expressed the
|
|
14
14
|
* same rule twice, in two different shapes (`modals.length === 0` in one,
|
|
15
|
-
* `isTopmost` in the other).
|
|
15
|
+
* `isTopmost` in the other). With a drawer on each side and no modal, the one opened last
|
|
16
|
+
* answers. Whoever answers claims the event (`../core/escape`), so one Escape closes one thing.
|
|
16
17
|
*/
|
|
17
18
|
export declare function useOverlayHost(instance: MaybeRefOrGetter<OverlayInstance>): {
|
|
18
19
|
/** The instance's title, resolved through the formatter, with a trailing `*` when dirty. */
|
|
@@ -15,6 +15,16 @@ export interface PanelContext {
|
|
|
15
15
|
width: number;
|
|
16
16
|
height: number;
|
|
17
17
|
} | null>;
|
|
18
|
+
/** Live title and dirty state, for a container whose panel is not in `ws.state.panels`. */
|
|
19
|
+
title?: Ref<Label>;
|
|
20
|
+
dirty?: Ref<boolean>;
|
|
21
|
+
/** Register a close guard with the container; returns its disposer. */
|
|
22
|
+
onBeforeClose?: (guard: () => boolean | Promise<boolean>) => () => void;
|
|
23
|
+
/**
|
|
24
|
+
* Set by a modal or side panel: it is not part of the layout, so it has nothing to save
|
|
25
|
+
* and nowhere to minimise to. Lets `usePanel()` say so rather than call it standalone.
|
|
26
|
+
*/
|
|
27
|
+
overlay?: boolean;
|
|
18
28
|
}
|
|
19
29
|
export declare const PANEL_KEY: InjectionKey<PanelContext>;
|
|
20
30
|
/** @internal — used by containers to tell their content which panel it is. */
|
|
@@ -15,4 +15,4 @@ import type { Workspace } from '../core/workspace';
|
|
|
15
15
|
* `subscribe()` is wrapped so a subscription made during `setup` is disposed with the
|
|
16
16
|
* component. Outside a scope it behaves exactly like `workspace.subscribe`.
|
|
17
17
|
*/
|
|
18
|
-
export declare function useWorkspace<TEvents extends
|
|
18
|
+
export declare function useWorkspace<TEvents extends object = Record<string, unknown>>(): Workspace<TEvents>;
|
|
@@ -35,6 +35,25 @@ export interface PointerDragConfig<TStart> {
|
|
|
35
35
|
* cleans up automatically on release or cancel.
|
|
36
36
|
*/
|
|
37
37
|
export declare function startPointerDrag<TStart>(config: PointerDragConfig<TStart>): void;
|
|
38
|
+
/**
|
|
39
|
+
* Whether `el` lays out right to left, as the browser computed it.
|
|
40
|
+
*
|
|
41
|
+
* A pointer delta is physical, but a flex row reverses under RTL, so a gesture that changes a
|
|
42
|
+
* logical size or index has to know which way its own element runs. The element's computed
|
|
43
|
+
* direction is the only source that is right everywhere: the sidebar and toolbar sit
|
|
44
|
+
* *outside* the workspace, and take their direction from the host page, not from the
|
|
45
|
+
* workspace's `dir`. Read it once, when the gesture starts.
|
|
46
|
+
*/
|
|
47
|
+
export declare function isRtlElement(el: Element | null | undefined): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Which logical side of a tab the pointer is over: `'left'` means before it in tab order,
|
|
50
|
+
* `'right'` after it — the meaning the insertion index and the RTL indicator CSS both use.
|
|
51
|
+
* Under RTL the physical left half is the logical end.
|
|
52
|
+
*/
|
|
53
|
+
export declare function tabSide(clientX: number, rect: {
|
|
54
|
+
left: number;
|
|
55
|
+
width: number;
|
|
56
|
+
}, rtl: boolean): 'left' | 'right';
|
|
38
57
|
export type ResizeDir = 'n' | 'ne' | 'e' | 'se' | 's' | 'sw' | 'w' | 'nw';
|
|
39
58
|
export interface ResizeRect {
|
|
40
59
|
x: number;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Mark an Escape as answered, so no other listener in the library acts on it. */
|
|
2
|
+
export declare function claimEscape(event: Event): void;
|
|
3
|
+
/** Whether a listener in the library, or the application via `preventDefault()`, already answered it. */
|
|
4
|
+
export declare function isEscapeClaimed(event: Event): boolean;
|
package/dist/core/eventBus.d.ts
CHANGED
|
@@ -50,7 +50,7 @@ export interface BuiltInEvents {
|
|
|
50
50
|
}[];
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
|
-
export declare class EventBus<TEvents extends
|
|
53
|
+
export declare class EventBus<TEvents extends object = Record<string, unknown>> {
|
|
54
54
|
private listeners;
|
|
55
55
|
subscribe<K extends keyof (TEvents & BuiltInEvents) & string>(event: K, callback: (data: (TEvents & BuiltInEvents)[K]) => void): () => void;
|
|
56
56
|
publish<K extends keyof (TEvents & BuiltInEvents) & string>(event: K, data: (TEvents & BuiltInEvents)[K]): void;
|
package/dist/core/toast.d.ts
CHANGED
|
@@ -58,6 +58,11 @@ export interface ToastAdapter {
|
|
|
58
58
|
dismiss(id?: string): void;
|
|
59
59
|
component: Component | null;
|
|
60
60
|
}
|
|
61
|
+
/** What `<VddToasts>` would fill in for an option the caller left out. */
|
|
62
|
+
export interface ToastDefaults {
|
|
63
|
+
duration: number;
|
|
64
|
+
closable: boolean;
|
|
65
|
+
}
|
|
61
66
|
/** The live queue. Exported for `<VddToasts>` and for tests; applications use `toast`. */
|
|
62
67
|
export declare const toastQueue: {
|
|
63
68
|
items: {
|
|
@@ -438,6 +443,10 @@ export declare const toastQueue: {
|
|
|
438
443
|
__name?: string | undefined;
|
|
439
444
|
} | null;
|
|
440
445
|
} | null;
|
|
446
|
+
defaults: {
|
|
447
|
+
duration: number;
|
|
448
|
+
closable: boolean;
|
|
449
|
+
};
|
|
441
450
|
};
|
|
442
451
|
/** Begin the exit transition. `<VddToasts>` removes the record when it finishes. */
|
|
443
452
|
export declare function startExit(id: string): void;
|
|
@@ -472,3 +481,11 @@ export interface ToastFunction {
|
|
|
472
481
|
export declare const toast: ToastFunction;
|
|
473
482
|
/** Empty the queue with no animation. For tests and for a full application teardown. */
|
|
474
483
|
export declare function resetToasts(): void;
|
|
484
|
+
/**
|
|
485
|
+
* Hand `toast.*` to an adapter, or take it back with `null`. Used by `<VddToasts>`.
|
|
486
|
+
*
|
|
487
|
+
* Toasts raised before an adapter arrives are waiting in `items`; they are handed to it, so a
|
|
488
|
+
* toast raised during start-up is not lost. The adapter's ids are forgotten when it changes —
|
|
489
|
+
* a new adapter has shown none of them.
|
|
490
|
+
*/
|
|
491
|
+
export declare function setToastAdapter(adapter: ToastAdapter | null, defaults: ToastDefaults): void;
|
package/dist/core/workspace.d.ts
CHANGED
|
@@ -95,7 +95,7 @@ export interface WorkspaceState {
|
|
|
95
95
|
edgeSplitRatio: number;
|
|
96
96
|
}
|
|
97
97
|
/** A workspace instance: reactive state, actions, registry, and a Vue plugin. */
|
|
98
|
-
export interface Workspace<TEvents extends
|
|
98
|
+
export interface Workspace<TEvents extends object = Record<string, unknown>> {
|
|
99
99
|
/** The whole state, read-only. Reactive — use it in templates and `computed`. */
|
|
100
100
|
readonly state: Readonly<WorkspaceState>;
|
|
101
101
|
/** The panel catalogue. */
|
|
@@ -219,4 +219,4 @@ export declare const WORKSPACE_KEY: InjectionKey<Workspace<never>>;
|
|
|
219
219
|
* The returned object is both the imperative API and a Vue plugin, matching the
|
|
220
220
|
* `createPinia()` / `createRouter()` shape a Vue developer already knows.
|
|
221
221
|
*/
|
|
222
|
-
export declare function createWorkspace<TEvents extends
|
|
222
|
+
export declare function createWorkspace<TEvents extends object = Record<string, unknown>>(config?: WorkspaceConfig): Workspace<TEvents>;
|