dockview 7.0.4 → 8.0.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.
@@ -1,963 +1,28 @@
1
1
  /**
2
2
  * dockview
3
- * @version 7.0.4
4
- * @link https://github.com/mathuo/dockview
3
+ * @version 8.0.0
4
+ * @link https://github.com/dockview/dockview
5
5
  * @license MIT
6
6
  */
7
- 'use strict';
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
8
 
9
- Object.defineProperty(exports, '__esModule', { value: true });
10
-
11
- var dockviewCore = require('dockview-core');
12
-
13
- /**
14
- * dockview-modules
15
- * @version 7.0.4
16
- * @link https://github.com/mathuo/dockview
17
- * @license MIT
18
- */
19
-
20
- class TabGroupChipsService {
21
- constructor(host) {
22
- this._host = host;
23
- }
24
- attachToGroup(group) {
25
- return new dockviewCore.DockviewCompositeDisposable(group.model.onDidCreateTabGroup((e) => {
26
- this._host.fireDidCreateTabGroup(e);
27
- }), group.model.onDidDestroyTabGroup((e) => {
28
- this._host.fireDidDestroyTabGroup(e);
29
- }), group.model.onDidAddPanelToTabGroup((e) => {
30
- this._host.fireDidAddPanelToTabGroup(e);
31
- }), group.model.onDidRemovePanelFromTabGroup((e) => {
32
- this._host.fireDidRemovePanelFromTabGroup(e);
33
- }), group.model.onDidTabGroupChange((e) => {
34
- this._host.fireDidTabGroupChange(e);
35
- }), group.model.onDidTabGroupCollapsedChange((e) => {
36
- this._host.fireDidTabGroupCollapsedChange(e);
37
- }));
38
- }
39
- dispose() {
40
- // No internal state to tear down — emitters live on the host.
41
- }
42
- }
43
- const TabGroupChipsModule = dockviewCore.defineModule({
44
- name: 'TabGroupChips',
45
- serviceKey: 'tabGroupChipsService',
46
- create: (host) => new TabGroupChipsService(host),
47
- init: (host, service) => {
48
- // Self-attach to existing and future groups; tear down when groups
49
- // are removed. Component doesn't need to know about this wiring.
50
- const perGroupDisposables = new Map();
51
- return new dockviewCore.DockviewCompositeDisposable(host.onDidAddGroup((group) => {
52
- perGroupDisposables.set(group, service.attachToGroup(group));
53
- }), host.onDidRemoveGroup((group) => {
54
- var _a;
55
- (_a = perGroupDisposables.get(group)) === null || _a === void 0 ? void 0 : _a.dispose();
56
- perGroupDisposables.delete(group);
57
- }), {
58
- dispose: () => {
59
- for (const d of perGroupDisposables.values()) {
60
- d.dispose();
61
- }
62
- perGroupDisposables.clear();
63
- },
64
- });
65
- },
66
- });
67
-
68
- function popoverZIndexFor(target) {
69
- if (!(target instanceof HTMLElement)) {
70
- return undefined;
71
- }
72
- // Floating overlays live in the shell as siblings of the popover anchor
73
- // and the AriaLevelTracker sets their inline z-index. Without this, a
74
- // popover opened from inside a floating group would render behind it
75
- // because they share the shell stacking context.
76
- const relativeParent = dockviewCore.findRelativeZIndexParent(target);
77
- return (relativeParent === null || relativeParent === void 0 ? void 0 : relativeParent.style.zIndex)
78
- ? `calc(${relativeParent.style.zIndex} * 2)`
79
- : undefined;
80
- }
81
- let _nextId = 0;
82
- const nextContextMenuItemId = () => `dv-ctx-menu-item-${_nextId++}`;
83
- function isItemConfig(item) {
84
- return typeof item === 'object';
85
- }
86
- function buildItem(label, close, action, disabled) {
87
- const el = document.createElement('div');
88
- el.className = 'dv-context-menu-item';
89
- el.setAttribute('role', 'menuitem');
90
- if (disabled) {
91
- el.classList.add('dv-context-menu-item--disabled');
92
- el.setAttribute('aria-disabled', 'true');
93
- }
94
- el.textContent = label;
95
- if (!disabled) {
96
- el.addEventListener('click', () => {
97
- action();
98
- close();
99
- });
100
- }
101
- return el;
102
- }
103
- function buildSeparator() {
104
- const el = document.createElement('div');
105
- el.className = 'dv-context-menu-separator';
106
- el.setAttribute('role', 'separator');
107
- return el;
108
- }
109
- function isCoarsePrimaryInput() {
110
- if (globalThis.window === undefined || !globalThis.matchMedia) {
111
- return false;
112
- }
113
- const coarse = globalThis.matchMedia('(pointer: coarse)').matches;
114
- const fine = globalThis.matchMedia('(pointer: fine)').matches;
115
- return coarse && !fine;
116
- }
117
- function buildRenameInput(tabGroup) {
118
- const wrapper = document.createElement('div');
119
- wrapper.className = 'dv-context-menu-rename';
120
- const input = document.createElement('input');
121
- input.className = 'dv-context-menu-rename-input';
122
- input.type = 'text';
123
- input.placeholder = 'Name This Group';
124
- input.value = tabGroup.label;
125
- input.addEventListener('input', () => {
126
- tabGroup.setLabel(input.value);
127
- });
128
- input.addEventListener('keydown', (e) => {
129
- if (e.key !== 'Escape' && e.key !== 'Enter') {
130
- e.stopPropagation();
131
- }
132
- });
133
- input.addEventListener('click', (e) => {
134
- e.stopPropagation();
135
- });
136
- wrapper.appendChild(input);
137
- // Skip auto-focus on touch-primary devices: focusing the input pops the
138
- // on-screen keyboard, which fires `window resize`, which `PopupService`
139
- // listens to and uses to dismiss the popover — so the menu opens, the
140
- // keyboard appears, and the menu immediately closes before the user can
141
- // type. The user can still tap the input to focus it intentionally.
142
- if (!isCoarsePrimaryInput()) {
143
- requestAnimationFrame(() => {
144
- input.focus();
145
- input.select();
146
- });
147
- }
148
- return wrapper;
149
- }
150
- function buildColorPicker(tabGroup, palette) {
151
- const wrapper = document.createElement('div');
152
- wrapper.className = 'dv-context-menu-color-picker';
153
- if (!palette.enabled) {
154
- // Opt-out: render no swatches. Returning a wrapper rather than null
155
- // keeps the call site simple; the wrapper is empty and visually inert.
156
- return wrapper;
157
- }
158
- for (const entry of palette.entries()) {
159
- const swatch = document.createElement('div');
160
- swatch.className = 'dv-context-menu-color-swatch';
161
- // Use a CSS custom property rather than setting `backgroundColor`
162
- // directly: the IDL setter validates the value against a color
163
- // grammar and rejects `var(...)` references in some environments
164
- // (notably jsdom; some browsers have historically had similar
165
- // quirks). The matching SCSS rule reads the var at use time.
166
- swatch.style.setProperty('--dv-tab-group-color', entry.value);
167
- if (entry.label) {
168
- swatch.title = entry.label;
169
- }
170
- if (tabGroup.color === entry.id) {
171
- swatch.classList.add('dv-context-menu-color-swatch--selected');
172
- }
173
- swatch.addEventListener('click', () => {
174
- tabGroup.setColor(entry.id);
175
- });
176
- wrapper.appendChild(swatch);
177
- }
178
- return wrapper;
179
- }
180
- class ContextMenuController {
181
- constructor(accessor) {
182
- this.accessor = accessor;
183
- }
184
- show(panel, group, event) {
185
- var _a, _b;
186
- if (!this.accessor.options.getTabContextMenuItems) {
187
- return;
188
- }
189
- const items = this.accessor.options.getTabContextMenuItems({
190
- panel,
191
- group,
192
- api: this.accessor.api,
193
- event,
194
- });
195
- if (items.length === 0) {
196
- return;
197
- }
198
- event.preventDefault();
199
- const popupService = this.accessor.getPopupServiceForGroup(group);
200
- const close = () => popupService.close();
201
- const menuEl = document.createElement('div');
202
- menuEl.className = 'dv-context-menu';
203
- menuEl.setAttribute('role', 'menu');
204
- for (const item of items) {
205
- if (item === 'separator') {
206
- menuEl.appendChild(buildSeparator());
207
- }
208
- else if (item === 'close') {
209
- menuEl.appendChild(buildItem('Close', close, () => panel.api.close()));
210
- }
211
- else if (item === 'closeOthers') {
212
- menuEl.appendChild(buildItem('Close Others', close, () => {
213
- group.panels
214
- .filter((p) => p !== panel)
215
- .forEach((p) => p.api.close());
216
- }));
217
- }
218
- else if (item === 'closeAll') {
219
- menuEl.appendChild(buildItem('Close All', close, () => {
220
- [...group.panels].forEach((p) => p.api.close());
221
- }));
222
- }
223
- else if (isItemConfig(item) && item.element) {
224
- menuEl.appendChild(item.element);
225
- }
226
- else if (isItemConfig(item) && item.component) {
227
- const renderer = (_b = (_a = this.accessor.options).createContextMenuItemComponent) === null || _b === void 0 ? void 0 : _b.call(_a, {
228
- id: nextContextMenuItemId(),
229
- component: item.component,
230
- });
231
- if (renderer) {
232
- renderer.init({
233
- panel,
234
- group,
235
- api: this.accessor.api,
236
- close,
237
- componentProps: item.componentProps,
238
- });
239
- menuEl.appendChild(renderer.element);
240
- }
241
- }
242
- else if (isItemConfig(item) && item.label) {
243
- menuEl.appendChild(buildItem(item.label, close, () => { var _a; return (_a = item.action) === null || _a === void 0 ? void 0 : _a.call(item); }, item.disabled));
244
- }
245
- }
246
- popupService.openPopover(menuEl, {
247
- x: event.clientX,
248
- y: event.clientY,
249
- zIndex: popoverZIndexFor(event.target),
250
- });
251
- }
252
- showForChip(tabGroup, group, event) {
253
- if (!this.accessor.options.getTabGroupChipContextMenuItems) {
254
- return;
255
- }
256
- const items = this.accessor.options.getTabGroupChipContextMenuItems({
257
- tabGroup,
258
- group,
259
- api: this.accessor.api,
260
- event,
261
- });
262
- if (items.length === 0) {
263
- return;
264
- }
265
- event.preventDefault();
266
- const popupService = this.accessor.getPopupServiceForGroup(group);
267
- const close = () => popupService.close();
268
- const menuEl = document.createElement('div');
269
- menuEl.className = 'dv-context-menu';
270
- menuEl.setAttribute('role', 'menu');
271
- for (const item of items) {
272
- if (item === 'separator') {
273
- menuEl.appendChild(buildSeparator());
274
- }
275
- else if (item === 'rename') {
276
- menuEl.appendChild(buildRenameInput(tabGroup));
277
- }
278
- else if (item === 'colorPicker') {
279
- menuEl.appendChild(buildColorPicker(tabGroup, this.accessor.tabGroupColorPalette));
280
- }
281
- else if (isItemConfig(item) && item.element) {
282
- menuEl.appendChild(item.element);
283
- }
284
- else if (isItemConfig(item) && item.label) {
285
- menuEl.appendChild(buildItem(item.label, close, () => { var _a; return (_a = item.action) === null || _a === void 0 ? void 0 : _a.call(item); }, item.disabled));
286
- }
287
- }
288
- popupService.openPopover(menuEl, {
289
- x: event.clientX,
290
- y: event.clientY,
291
- zIndex: popoverZIndexFor(event.target),
292
- });
293
- }
294
- }
295
- const ContextMenuModule = dockviewCore.defineModule({
296
- name: 'ContextMenu',
297
- serviceKey: 'contextMenuService',
298
- create: (host) => new ContextMenuController(host),
299
- });
300
-
301
- /** Cursor offset of the group drag ghost, matched to the long-shipped default. */
302
- const GROUP_DRAG_GHOST_OFFSET_X = 30;
303
- const GROUP_DRAG_GHOST_OFFSET_Y = -10;
304
- /**
305
- * The narrow surface the {@link AdvancedDnDService} needs from the host
306
- * (the `DockviewComponent`).
307
- *
308
- * The `onWill*` emitters stay on the component so the public event shape is
309
- * unchanged whether or not this module is registered — the service is only the
310
- * dispatch point those fires are routed through. Engine policy (e.g. the
311
- * `disableDnd` guard) stays on the component, ahead of the dispatch.
312
- */
313
- /**
314
- * Owns the dispatch of the advanced drag-and-drop hooks — `onWillDragPanel`,
315
- * `onWillDragGroup`, `onWillDrop` and `onWillShowOverlay`.
316
- *
317
- * At this stage the service is a thin dispatcher that forwards to the host's
318
- * emitters; the customisation surface it gates (custom drop overlays, custom
319
- * group drag ghosts, hook veto/transform behaviour) is currently inlined in
320
- * core and is extracted into this service in later phases. Routing the
321
- * dispatch through a module slot is what lets that customisation layer move
322
- * into a separately-distributed package in a future major version without
323
- * changing the public event surface.
324
- *
325
- * The service holds no drag state of its own — the gesture is driven by the
326
- * DnD backends, and the per-group subscriptions live on the component's group
327
- * lifecycle (so groups created mid-move are not missed).
328
- */
329
- class AdvancedDnDService {
330
- constructor(host) {
331
- this.host = host;
332
- }
333
- dispatchWillDragPanel(event) {
334
- this.host.fireWillDragPanel(event);
335
- }
336
- dispatchWillDragGroup(event) {
337
- this.host.fireWillDragGroup(event);
338
- }
339
- dispatchWillDrop(event) {
340
- this.host.fireWillDrop(event);
341
- }
342
- dispatchWillShowOverlay(event) {
343
- this.host.fireWillShowOverlay(event);
344
- }
345
- buildGroupDragGhost(group) {
346
- const createGhost = this.host.options.createGroupDragGhostComponent;
347
- if (!createGhost) {
348
- return undefined;
349
- }
350
- const renderer = createGhost(group);
351
- renderer.init({ group, api: this.host.api });
352
- return {
353
- element: renderer.element,
354
- offsetX: GROUP_DRAG_GHOST_OFFSET_X,
355
- offsetY: GROUP_DRAG_GHOST_OFFSET_Y,
356
- dispose: renderer.dispose ? () => { var _a; return (_a = renderer.dispose) === null || _a === void 0 ? void 0 : _a.call(renderer); } : undefined,
357
- };
358
- }
359
- resolveOverlayModel(location, group) {
360
- var _a, _b;
361
- return (_b = (_a = this.host.options).dropOverlayModel) === null || _b === void 0 ? void 0 : _b.call(_a, { location, group });
362
- }
363
- showPreviewOverlay(group, position) {
364
- const target = group.model.contentDropTarget;
365
- target.showOverlay(position);
366
- return dockviewCore.DockviewDisposable.from(() => target.clearOverlay());
367
- }
368
- dispose() {
369
- // no-op — see class doc: the service holds no state to tear down.
370
- }
371
- }
372
- const AdvancedDnDModule = dockviewCore.defineModule({
373
- name: 'AdvancedDnD',
374
- serviceKey: 'advancedDnDService',
375
- create: (host) => new AdvancedDnDService(host),
376
- });
377
-
378
- /**
379
- * Marks (on the dockview root element) that a keyboard move is in progress, so
380
- * the default navigation listener stands down while the advanced docking module
381
- * drives the keys. A neutral DOM signal keeps the two listeners coordinated
382
- * without either service holding a reference to the other.
383
- */
384
- const KEYBOARD_MOVE_ATTRIBUTE = 'data-dv-kbd-moving';
385
- /**
386
- * Does `e` match a binding string like `'ctrl+]'` / `'shift+f6'`? Modifiers are
387
- * matched exactly (a binding without `shift` will not fire while Shift is held),
388
- * and the final part is compared to `KeyboardEvent.key`, lower-cased.
389
- */
390
- function matchesBinding(e, binding) {
391
- const parts = binding.toLowerCase().split('+');
392
- const key = parts[parts.length - 1];
393
- const mods = parts.slice(0, -1);
394
- return (e.ctrlKey === mods.includes('ctrl') &&
395
- e.shiftKey === mods.includes('shift') &&
396
- e.altKey === mods.includes('alt') &&
397
- e.metaKey === (mods.includes('meta') || mods.includes('cmd')) &&
398
- e.key.toLowerCase() === key);
399
- }
9
+ //#region src/index.ts
400
10
  /**
401
- * Resolve the `keyboardNavigation` opt-in to its options object, or `undefined`
402
- * when keyboard support is off. Both keyboard modules read the same opt-in.
403
- */
404
- function readKeyboardNavigation(options) {
405
- const opt = options.keyboardNavigation;
406
- if (!opt) {
407
- return undefined;
408
- }
409
- return opt === true ? {} : opt;
410
- }
11
+ * `dockview` is the recommended free entry point: a thin re-export of
12
+ * `dockview-core`. The separable enterprise feature modules now live in the
13
+ * separately-published `dockview-enterprise` package (which depends on and
14
+ * re-exports `dockview`); install that to opt into them.
15
+ *
16
+ * This module has no side effects (it's a pure re-export), so bundlers can
17
+ * fully tree-shake unused exports out of consumer builds.
18
+ */
411
19
 
412
- const DEFAULT_KEYMAP$1 = {
413
- nextTab: 'ctrl+]',
414
- prevTab: 'ctrl+[',
415
- focusNextGroup: 'f6',
416
- focusPrevGroup: 'shift+f6',
417
- focusTabs: 'ctrl+shift+\\',
418
- };
419
- /**
420
- * Keyboard navigation & focus management — operate the dock without a mouse.
421
- * Opt-in via `keyboardNavigation`, with a rebindable {@link DockviewKeybindings}
422
- * keymap.
423
- *
424
- * - **Switch tab** (`Ctrl+]` / `Ctrl+[`) — cycle the focused group's tabs.
425
- * - **Focus group** (`F6` / `Shift+F6`) — move focus to the next / previous
426
- * group in sequence.
427
- * - **Focus the tab strip** (`Ctrl+Shift+\`) — jump focus from panel content to
428
- * the active group's tab strip (the strip's roving-tabindex takes over).
429
- * - **Focus restore on close** — when removing a panel/group pulls focus out of
430
- * the dock, focus returns to the neighbour the layout just activated instead
431
- * of being stranded on `<body>`.
432
- * - **Floating `Esc`** — `Esc` inside a floating group returns focus to the
433
- * control that had it before entering the float (polite: bubble phase,
434
- * respects `defaultPrevented`, so panel content keeps `Esc`).
435
- * - **Floating Tab-containment** — Tab wraps within the floating group so focus
436
- * doesn't leak to the grid behind it.
437
- *
438
- * Stands down while an advanced keyboard move is in progress (see
439
- * {@link KEYBOARD_MOVE_ATTRIBUTE}) so the docking module owns the keys then.
440
- */
441
- class AccessibilityService extends dockviewCore.DockviewCompositeDisposable {
442
- constructor(host) {
443
- super();
444
- this.host = host;
445
- this._focusWasInside = false;
446
- // Listen on the document (capture) rather than the dockview element:
447
- // edge groups live in the shell *outside* the gridview, and the shell
448
- // is created after this service, so a fixed element would miss them.
449
- const doc = host.rootElement.ownerDocument;
450
- const onKeyDown = (e) => this._onKeyDown(e);
451
- doc.addEventListener('keydown', onKeyDown, true);
452
- // Remember the last control focused in the main dock (outside any
453
- // float) so Esc inside a floating group can return focus to its
454
- // invoking control. Observe-only — never consumes.
455
- const onFocusIn = (e) => {
456
- const t = e.target;
457
- if (t instanceof HTMLElement &&
458
- this.host.rootElement.contains(t) &&
459
- !t.closest('[role="dialog"]')) {
460
- this._lastNonFloatFocus = t;
461
- }
462
- };
463
- doc.addEventListener('focusin', onFocusIn, true);
464
- // Esc-from-float restore runs in the BUBBLE phase and respects
465
- // defaultPrevented, so panel content that uses Esc keeps priority.
466
- const onEscape = (e) => this._onFloatingEscape(e);
467
- doc.addEventListener('keydown', onEscape, false);
468
- this.addDisposables({
469
- dispose: () => doc.removeEventListener('keydown', onKeyDown, true),
470
- }, {
471
- dispose: () => doc.removeEventListener('focusin', onFocusIn, true),
472
- }, {
473
- dispose: () => doc.removeEventListener('keydown', onEscape, false),
474
- },
475
- // When a close pulls focus out of the dock, return it to the
476
- // neighbour the component just activated rather than leaving it
477
- // stranded on <body>. Snapshot before the teardown (focus still on
478
- // the closing panel), restore after.
479
- host.onWillMutateLayout((e) => {
480
- if (e.kind === 'remove' && this._nav) {
481
- this._focusWasInside = this._isFocusInside();
482
- }
483
- }), host.onDidMutateLayout((e) => {
484
- if (e.kind === 'remove' &&
485
- this._nav &&
486
- this._focusWasInside &&
487
- !this._isFocusInside()) {
488
- this._restoreFocus();
489
- }
490
- }));
491
- }
492
- get _moveActive() {
493
- return this.host.rootElement.hasAttribute(KEYBOARD_MOVE_ATTRIBUTE);
494
- }
495
- _isFocusInside() {
496
- const active = this.host.rootElement.ownerDocument.activeElement;
497
- return active instanceof Node && this.host.rootElement.contains(active);
498
- }
499
- _onFloatingEscape(e) {
500
- if (!this._nav ||
501
- this._moveActive ||
502
- e.defaultPrevented ||
503
- e.key !== 'Escape') {
504
- return;
505
- }
506
- const target = e.target;
507
- if (!(target instanceof Element)) {
508
- return;
509
- }
510
- // Only when focus is inside one of *this* dock's floating groups.
511
- const float = target.closest('[role="dialog"]');
512
- if (!float || !this.host.rootElement.contains(float)) {
513
- return;
514
- }
515
- e.preventDefault();
516
- e.stopPropagation();
517
- this._returnFocusFromFloat();
518
- }
519
- /**
520
- * Keep Tab inside the floating group that holds focus: at the last tabbable
521
- * Tab wraps to the first, at the first Shift+Tab wraps to the last. Returns
522
- * true if it handled the event. No-op outside a float.
523
- */
524
- _trapFloatTab(e) {
525
- const target = e.target;
526
- if (!(target instanceof Element)) {
527
- return false;
528
- }
529
- const float = target.closest('[role="dialog"]');
530
- if (!float || !this.host.rootElement.contains(float)) {
531
- return false;
532
- }
533
- // Always manage Tab inside a float, never just at the boundary: focus
534
- // often sits on non-tabbable plumbing (the content container, which is
535
- // tabindex="-1"), and the browser's default Tab from there escapes to
536
- // the grid behind. Drive the cursor through the float's tabbables
537
- // ourselves and swallow the default so it can't leak out.
538
- e.preventDefault();
539
- const tabbables = this._tabbables(float);
540
- if (tabbables.length === 0) {
541
- return true;
542
- }
543
- const active = float.ownerDocument.activeElement;
544
- const index = active instanceof HTMLElement ? tabbables.indexOf(active) : -1;
545
- const n = tabbables.length;
546
- const next = index === -1
547
- ? e.shiftKey
548
- ? n - 1
549
- : 0
550
- : (index + (e.shiftKey ? -1 : 1) + n) % n;
551
- tabbables[next].focus();
552
- return true;
553
- }
554
- _tabbables(root) {
555
- const nodes = root.querySelectorAll('a[href], button:not([disabled]), input:not([disabled]), ' +
556
- 'select:not([disabled]), textarea:not([disabled]), [tabindex]');
557
- // tabIndex >= 0 keeps naturally-focusable controls and roving anchors
558
- // (the active tab) while dropping tabindex="-1" plumbing (content
559
- // containers, inactive tabs).
560
- return Array.from(nodes).filter((el) => el.tabIndex >= 0);
561
- }
562
- _returnFocusFromFloat() {
563
- var _a;
564
- const prev = this._lastNonFloatFocus;
565
- if (prev &&
566
- prev.isConnected &&
567
- this.host.rootElement.contains(prev) &&
568
- !prev.closest('[role="dialog"]')) {
569
- prev.focus();
570
- return;
571
- }
572
- // Invoking control is gone — fall back to a grid group's content.
573
- (_a = this.host.groups
574
- .find((g) => g.api.location.type === 'grid')) === null || _a === void 0 ? void 0 : _a.model.focusContent();
575
- }
576
- get _nav() {
577
- return readKeyboardNavigation(this.host.options);
578
- }
579
- get _keymap() {
580
- var _a;
581
- return Object.assign(Object.assign({}, DEFAULT_KEYMAP$1), (_a = this._nav) === null || _a === void 0 ? void 0 : _a.keymap);
582
- }
583
- _onKeyDown(e) {
584
- if (!this._nav) {
585
- return;
586
- }
587
- // Stand down while the docking module is driving a keyboard move.
588
- if (this._moveActive) {
589
- return;
590
- }
591
- // Only act on events originating inside *this* dockview.
592
- if (!(e.target instanceof Node) ||
593
- !this.host.rootElement.contains(e.target)) {
594
- return;
595
- }
596
- // Trap Tab within a floating group so focus doesn't leak to the grid
597
- // behind it (the float is non-modal, but its Tab order should be).
598
- if (e.key === 'Tab' && this._trapFloatTab(e)) {
599
- return;
600
- }
601
- const keymap = this._keymap;
602
- if (matchesBinding(e, keymap.nextTab)) {
603
- this._consume(e);
604
- this._switchTab(false);
605
- }
606
- else if (matchesBinding(e, keymap.prevTab)) {
607
- this._consume(e);
608
- this._switchTab(true);
609
- }
610
- else if (matchesBinding(e, keymap.focusNextGroup)) {
611
- this._consume(e);
612
- this._cycleGroup(false);
613
- }
614
- else if (matchesBinding(e, keymap.focusPrevGroup)) {
615
- this._consume(e);
616
- this._cycleGroup(true);
617
- }
618
- else if (matchesBinding(e, keymap.focusTabs)) {
619
- this._consume(e);
620
- this._focusTabs();
621
- }
622
- }
623
- // --- navigation (uses the public group API + the adjacentGroup primitive) ---
624
- _focusTabs() {
625
- var _a;
626
- // Jump from panel content to the active group's tab strip; the
627
- // tablist's own roving-tabindex handler takes over from there.
628
- (_a = this.host.activeGroup) === null || _a === void 0 ? void 0 : _a.model.focusActiveTab();
629
- }
630
- _switchTab(reverse) {
631
- const group = this.host.activeGroup;
632
- if (!group) {
633
- return;
634
- }
635
- if (reverse) {
636
- group.model.moveToPrevious();
637
- }
638
- else {
639
- group.model.moveToNext();
640
- }
641
- // Keep DOM focus inside the dock: switching hides the previously
642
- // focused content, which would otherwise drop focus to <body> and
643
- // leave the keymap unable to see the next key.
644
- group.model.focusContent();
645
- }
646
- _cycleGroup(reverse) {
647
- const current = this.host.activeGroup;
648
- const target = current
649
- ? this.host.adjacentGroup(current, reverse)
650
- : this.host.groups[0];
651
- this._focusGroup(target);
652
- }
653
- _focusGroup(target) {
654
- if (!target) {
655
- return;
656
- }
657
- target.api.setActive();
658
- target.model.focusContent();
659
- }
660
- /** Return DOM focus to the active group's content, keeping it in the dock. */
661
- _restoreFocus() {
662
- var _a;
663
- (_a = this.host.activeGroup) === null || _a === void 0 ? void 0 : _a.model.focusContent();
664
- }
665
- _consume(e) {
666
- e.preventDefault();
667
- e.stopPropagation();
668
- }
669
- }
670
- const AccessibilityModule = dockviewCore.defineModule({
671
- name: 'Accessibility',
672
- serviceKey: 'accessibilityService',
673
- create: (host) => new AccessibilityService(host),
674
- });
675
-
676
- const EDGE_FROM_KEY = {
677
- ArrowLeft: 'left',
678
- ArrowRight: 'right',
679
- ArrowUp: 'top',
680
- ArrowDown: 'bottom',
681
- };
682
- const DEFAULT_KEYMAP = {
683
- focusGroupLeft: 'ctrl+shift+arrowleft',
684
- focusGroupRight: 'ctrl+shift+arrowright',
685
- focusGroupUp: 'ctrl+shift+arrowup',
686
- focusGroupDown: 'ctrl+shift+arrowdown',
687
- dock: 'ctrl+m',
688
- };
689
- /**
690
- * Advanced keyboard control — drive the layout itself without a mouse. Opt-in
691
- * via `keyboardNavigation` (shared with the default navigation module).
692
- *
693
- * - **Spatial group focus** (`Ctrl+Shift+Arrows`) — move focus to the visually
694
- * adjacent group via the `adjacentGroupInDirection` geometry primitive.
695
- * - **Keyboard docking** (`Ctrl+M`) — arms a two-phase move of the active panel
696
- * with a live drop preview + screen-reader narration:
697
- * 1. PICK TARGET — arrows cycle the groups (incl. the panel's own, so a tab
698
- * can be split out); `Enter` selects one.
699
- * 2. PICK EDGE — arrows choose a split edge (left/right/top/bottom) or the
700
- * centre (tab-into); `Enter` commits, `Escape` steps back.
701
- * `Escape` from the target phase cancels.
702
- *
703
- * While a move is in progress it marks the root with
704
- * {@link KEYBOARD_MOVE_ATTRIBUTE} so the default navigation listener stands
705
- * down and the move keys aren't double-handled.
706
- */
707
- class KeyboardDockingService extends dockviewCore.DockviewCompositeDisposable {
708
- constructor(host) {
709
- super();
710
- this.host = host;
711
- this._move = null;
712
- // Capture phase, on the document — matches the navigation listener so
713
- // edge groups (outside the gridview) are covered.
714
- const doc = host.rootElement.ownerDocument;
715
- const onKeyDown = (e) => this._onKeyDown(e);
716
- doc.addEventListener('keydown', onKeyDown, true);
717
- this.addDisposables({ dispose: () => this._exit() }, {
718
- dispose: () => doc.removeEventListener('keydown', onKeyDown, true),
719
- });
720
- }
721
- get _enabled() {
722
- return !!readKeyboardNavigation(this.host.options);
723
- }
724
- get _keymap() {
725
- var _a, _b, _c, _d, _e, _f, _g;
726
- // The public `keyboardNavigation.keymap` carries the default-navigation
727
- // bindings; this module's bindings are read from the same object when
728
- // present (so a consumer can rebind them too).
729
- const overrides = ((_b = (_a = readKeyboardNavigation(this.host.options)) === null || _a === void 0 ? void 0 : _a.keymap) !== null && _b !== void 0 ? _b : {});
730
- return {
731
- focusGroupLeft: (_c = overrides.focusGroupLeft) !== null && _c !== void 0 ? _c : DEFAULT_KEYMAP.focusGroupLeft,
732
- focusGroupRight: (_d = overrides.focusGroupRight) !== null && _d !== void 0 ? _d : DEFAULT_KEYMAP.focusGroupRight,
733
- focusGroupUp: (_e = overrides.focusGroupUp) !== null && _e !== void 0 ? _e : DEFAULT_KEYMAP.focusGroupUp,
734
- focusGroupDown: (_f = overrides.focusGroupDown) !== null && _f !== void 0 ? _f : DEFAULT_KEYMAP.focusGroupDown,
735
- dock: (_g = overrides.dock) !== null && _g !== void 0 ? _g : DEFAULT_KEYMAP.dock,
736
- };
737
- }
738
- get _messages() {
739
- return dockviewCore.resolveMessages(this.host.options.messages);
740
- }
741
- _onKeyDown(e) {
742
- if (!this._enabled) {
743
- return;
744
- }
745
- if (this._move) {
746
- this._onMoveKey(e);
747
- return;
748
- }
749
- // Only act on events originating inside *this* dockview.
750
- if (!(e.target instanceof Node) ||
751
- !this.host.rootElement.contains(e.target)) {
752
- return;
753
- }
754
- const keymap = this._keymap;
755
- if (matchesBinding(e, keymap.dock)) {
756
- this._enterMoveMode(e);
757
- }
758
- else if (matchesBinding(e, keymap.focusGroupLeft)) {
759
- this._consume(e);
760
- this._focusGroupInDirection('left');
761
- }
762
- else if (matchesBinding(e, keymap.focusGroupRight)) {
763
- this._consume(e);
764
- this._focusGroupInDirection('right');
765
- }
766
- else if (matchesBinding(e, keymap.focusGroupUp)) {
767
- this._consume(e);
768
- this._focusGroupInDirection('up');
769
- }
770
- else if (matchesBinding(e, keymap.focusGroupDown)) {
771
- this._consume(e);
772
- this._focusGroupInDirection('down');
773
- }
774
- }
775
- // --- spatial focus ---
776
- _focusGroupInDirection(direction) {
777
- const current = this.host.activeGroup;
778
- if (!current) {
779
- return;
780
- }
781
- // Geometry lives on the host as the shared `adjacentGroupInDirection`
782
- // primitive (also public on the api), so mouse and keyboard navigation
783
- // agree on what "the group to the left" is.
784
- this._focusGroup(this.host.adjacentGroupInDirection(current, direction));
785
- }
786
- _focusGroup(target) {
787
- if (!target) {
788
- return;
789
- }
790
- target.api.setActive();
791
- target.model.focusContent();
792
- }
793
- _restoreFocus() {
794
- var _a;
795
- (_a = this.host.activeGroup) === null || _a === void 0 ? void 0 : _a.model.focusContent();
796
- }
797
- // --- keyboard docking (move mode) ---
798
- _enterMoveMode(e) {
799
- const source = this.host.activePanel;
800
- const groups = this.host.groups;
801
- if (!source || groups.length === 0) {
802
- return;
803
- }
804
- e.preventDefault();
805
- e.stopPropagation();
806
- // Start on the panel's own group so a single group of tabs can still
807
- // split a tab out via the edge phase.
808
- const groupIndex = Math.max(0, groups.indexOf(source.group));
809
- this._move = {
810
- source,
811
- groups,
812
- groupIndex,
813
- phase: 'target',
814
- position: 'center',
815
- };
816
- // Signal the navigation module to stand down while the move runs.
817
- this.host.rootElement.setAttribute(KEYBOARD_MOVE_ATTRIBUTE, '');
818
- this._render();
819
- }
820
- _onMoveKey(e) {
821
- const move = this._move;
822
- if (e.key === 'Escape') {
823
- this._consume(e);
824
- if (move.phase === 'edge') {
825
- move.phase = 'target';
826
- move.position = 'center';
827
- this._render();
828
- }
829
- else {
830
- this._exit();
831
- this.host.announce(this._messages.moveCancelled());
832
- this._restoreFocus();
833
- }
834
- return;
835
- }
836
- if (e.key === 'Enter') {
837
- this._consume(e);
838
- if (move.phase === 'target') {
839
- move.phase = 'edge';
840
- move.position = 'center';
841
- this._render();
842
- }
843
- else {
844
- this._commit();
845
- }
846
- return;
847
- }
848
- if (move.phase === 'target') {
849
- const n = move.groups.length;
850
- if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {
851
- this._consume(e);
852
- move.groupIndex = (move.groupIndex + 1) % n;
853
- this._render();
854
- }
855
- else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {
856
- this._consume(e);
857
- move.groupIndex = (move.groupIndex - 1 + n) % n;
858
- this._render();
859
- }
860
- return;
861
- }
862
- // edge phase
863
- const edge = EDGE_FROM_KEY[e.key];
864
- if (edge) {
865
- this._consume(e);
866
- move.position = edge;
867
- this._render();
868
- }
869
- else if (e.key === ' ' || e.key === 'c' || e.key === 'C') {
870
- this._consume(e);
871
- move.position = 'center';
872
- this._render();
873
- }
874
- }
875
- _render() {
876
- var _a, _b;
877
- const move = this._move;
878
- const group = move.groups[move.groupIndex];
879
- this._clearPreview();
880
- this._preview = this.host.showDropPreview(group, move.position);
881
- const name = (_b = (_a = group.activePanel) === null || _a === void 0 ? void 0 : _a.title) !== null && _b !== void 0 ? _b : group.id;
882
- const m = this._messages;
883
- if (move.phase === 'target') {
884
- this.host.announce(m.movePickTarget(this._label(move.source), name, move.groupIndex + 1, move.groups.length));
885
- }
886
- else {
887
- this.host.announce(m.movePickEdge(move.position, name));
888
- }
889
- }
890
- _commit() {
891
- var _a, _b;
892
- const move = this._move;
893
- const group = move.groups[move.groupIndex];
894
- const position = move.position;
895
- const source = move.source;
896
- const name = (_b = (_a = group.activePanel) === null || _a === void 0 ? void 0 : _a.title) !== null && _b !== void 0 ? _b : group.id;
897
- const m = this._messages;
898
- this._exit();
899
- try {
900
- this.host.dockPanel(source, group, position);
901
- this.host.announce(m.moveCommitted(this._label(source), name, position));
902
- }
903
- catch (_c) {
904
- this.host.announce(m.moveNotAllowed());
905
- }
906
- // The move re-renders the grid; pull focus back into the dock so the
907
- // keymap keeps working without a click.
908
- this._restoreFocus();
909
- }
910
- _exit() {
911
- this._clearPreview();
912
- this._move = null;
913
- this.host.rootElement.removeAttribute(KEYBOARD_MOVE_ATTRIBUTE);
914
- }
915
- _clearPreview() {
916
- var _a;
917
- (_a = this._preview) === null || _a === void 0 ? void 0 : _a.dispose();
918
- this._preview = undefined;
919
- }
920
- _consume(e) {
921
- e.preventDefault();
922
- e.stopPropagation();
923
- }
924
- _label(panel) {
925
- var _a;
926
- return (_a = panel.title) !== null && _a !== void 0 ? _a : panel.id;
927
- }
928
- }
929
- dockviewCore.defineModule({
930
- name: 'KeyboardDocking',
931
- serviceKey: 'keyboardDockingService',
932
- create: (host) => new KeyboardDockingService(host),
933
- dependsOn: [AdvancedDnDModule, dockviewCore.LiveRegionModule],
934
- });
935
-
936
- /**
937
- * The set of modules provided by this package. `dockview` registers these via
938
- * `registerModules(Modules)` so they are available to every component.
939
- */
940
- const Modules = [
941
- TabGroupChipsModule,
942
- ContextMenuModule,
943
- AdvancedDnDModule,
944
- AccessibilityModule,
945
- ];
946
-
947
- /**
948
- * `dockview` is the batteries-included entry point: it re-exports the core API
949
- * and registers the separable feature modules so every component gets the full
950
- * feature set out of the box. Consumers who want only the core can depend on
951
- * `dockview-core` directly.
952
- */
953
- dockviewCore.registerModules(Modules);
954
- // Mark the public package as loaded so `dockview-core` doesn't warn about
955
- // direct usage. Purely drives that developer warning — no functional effect.
956
- dockviewCore.markDockviewPackageLoaded();
20
+ //#endregion
957
21
 
958
- Object.keys(dockviewCore).forEach(function (k) {
959
- if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
960
- enumerable: true,
961
- get: function () { return dockviewCore[k]; }
962
- });
22
+ var dockview_core = require("dockview-core");
23
+ Object.keys(dockview_core).forEach(function (k) {
24
+ if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
25
+ enumerable: true,
26
+ get: function () { return dockview_core[k]; }
27
+ });
963
28
  });