quilt-vanilla 0.2.4 → 0.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/README.md +35 -39
- package/dist/actions.d.ts +14 -0
- package/dist/actions.d.ts.map +1 -0
- package/dist/actions.js +115 -0
- package/dist/actions.js.map +1 -0
- package/dist/chrome-size.d.ts +7 -0
- package/dist/chrome-size.d.ts.map +1 -0
- package/dist/chrome-size.js +86 -0
- package/dist/chrome-size.js.map +1 -0
- package/dist/close.d.ts.map +1 -1
- package/dist/close.js +3 -1
- package/dist/close.js.map +1 -1
- package/dist/corners.d.ts +2 -1
- package/dist/corners.d.ts.map +1 -1
- package/dist/corners.js +2 -2
- package/dist/corners.js.map +1 -1
- package/dist/index.d.ts +6 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/menu.d.ts +9 -3
- package/dist/menu.d.ts.map +1 -1
- package/dist/menu.js +110 -37
- package/dist/menu.js.map +1 -1
- package/dist/panes.d.ts.map +1 -1
- package/dist/panes.js +84 -52
- package/dist/panes.js.map +1 -1
- package/dist/registry.d.ts +1 -0
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +4 -3
- package/dist/registry.js.map +1 -1
- package/dist/renderer.d.ts.map +1 -1
- package/dist/renderer.js +128 -36
- package/dist/renderer.js.map +1 -1
- package/dist/resize.d.ts +2 -1
- package/dist/resize.d.ts.map +1 -1
- package/dist/resize.js +2 -2
- package/dist/resize.js.map +1 -1
- package/dist/shortcut-registration.d.ts +9 -0
- package/dist/shortcut-registration.d.ts.map +1 -0
- package/dist/shortcut-registration.js +95 -0
- package/dist/shortcut-registration.js.map +1 -0
- package/dist/shortcuts.d.ts +7 -6
- package/dist/shortcuts.d.ts.map +1 -1
- package/dist/shortcuts.js +28 -50
- package/dist/shortcuts.js.map +1 -1
- package/dist/styles.css +74 -9
- package/dist/tabs.d.ts.map +1 -1
- package/dist/tabs.js +10 -12
- package/dist/tabs.js.map +1 -1
- package/dist/types.d.ts +50 -13
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/workspace-api.d.ts +186 -0
- package/dist/workspace-api.d.ts.map +1 -0
- package/dist/workspace-api.js +409 -0
- package/dist/workspace-api.js.map +1 -0
- package/package.json +2 -2
- package/src/actions.ts +125 -0
- package/src/chrome-size.ts +99 -0
- package/src/close.ts +6 -1
- package/src/corners.ts +2 -1
- package/src/index.ts +31 -4
- package/src/menu.ts +123 -43
- package/src/panes.ts +82 -55
- package/src/registry.ts +6 -4
- package/src/renderer.ts +181 -54
- package/src/resize.ts +2 -1
- package/src/shortcut-registration.ts +106 -0
- package/src/shortcuts.ts +38 -63
- package/src/styles.css +74 -9
- package/src/tabs.ts +9 -11
- package/src/types.ts +67 -14
- package/src/workspace-api.ts +583 -0
package/src/menu.ts
CHANGED
|
@@ -4,20 +4,23 @@ import type { ActionIcon } from './action-icons.js';
|
|
|
4
4
|
import { fillTabPicker } from './picker.js';
|
|
5
5
|
import { findNode, findParent, groups, paneIds } from 'quilt-core';
|
|
6
6
|
import type { Group, Pane } from 'quilt-core';
|
|
7
|
-
import type { ResolvedLayoutOptions } from './types.js';
|
|
8
|
-
import type {
|
|
7
|
+
import type { ResolvedLayoutOptions, WorkspaceAction, ActionContext } from './types.js';
|
|
8
|
+
import type { createActions } from './actions.js';
|
|
9
|
+
import { registeredShortcuts, formatShortcut, ariaShortcut } from './shortcut-registration.js';
|
|
9
10
|
import { el, Scope } from './lifetime.js';
|
|
10
11
|
export function createPaneMenu(
|
|
11
12
|
root: HTMLElement,
|
|
12
13
|
options: ResolvedLayoutOptions,
|
|
13
|
-
windows: Pick<Windows, 'open'>,
|
|
14
14
|
refresh: () => void,
|
|
15
15
|
report: (e: unknown) => void,
|
|
16
|
-
|
|
16
|
+
actions: ReturnType<typeof createActions>,
|
|
17
17
|
) {
|
|
18
18
|
const doc = root.ownerDocument,
|
|
19
19
|
win = doc.defaultView!;
|
|
20
20
|
let current: Scope | undefined;
|
|
21
|
+
let shortcutTarget:
|
|
22
|
+
{ dialog: HTMLDialogElement; context: ActionContext; close(): void } | undefined;
|
|
23
|
+
let refreshShortcuts = () => {};
|
|
21
24
|
const persistentPickers = new Map<string, Scope>();
|
|
22
25
|
const act = (fn: () => void) => {
|
|
23
26
|
try {
|
|
@@ -26,11 +29,10 @@ export function createPaneMenu(
|
|
|
26
29
|
report(error);
|
|
27
30
|
}
|
|
28
31
|
};
|
|
29
|
-
function button(text: string,
|
|
32
|
+
function button(text: string, label: string, action: () => void) {
|
|
30
33
|
const b = el(doc, 'button', 'layouts-button', text);
|
|
31
34
|
b.type = 'button';
|
|
32
|
-
b.
|
|
33
|
-
b.setAttribute('aria-label', title);
|
|
35
|
+
b.setAttribute('aria-label', label);
|
|
34
36
|
b.onclick = () => act(action);
|
|
35
37
|
return b;
|
|
36
38
|
}
|
|
@@ -59,17 +61,38 @@ export function createPaneMenu(
|
|
|
59
61
|
: message(options, 'Empty pane actions'),
|
|
60
62
|
);
|
|
61
63
|
local.add(() => dialog.remove());
|
|
62
|
-
const
|
|
64
|
+
const context = {
|
|
65
|
+
groupId: group.id,
|
|
66
|
+
...(pane && group.panes.includes(pane.id) ? { paneId: pane.id } : {}),
|
|
67
|
+
};
|
|
68
|
+
const hints: { element: HTMLButtonElement; action: WorkspaceAction }[] = [];
|
|
69
|
+
const bindAction = (element: HTMLButtonElement, action: WorkspaceAction) => {
|
|
70
|
+
hints.push({ element, action });
|
|
71
|
+
element.disabled = !actions.canExecuteAction(action, context);
|
|
72
|
+
element.onclick = () => {
|
|
73
|
+
local.dispose();
|
|
74
|
+
void actions.executeAction(action, context);
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
const add = (
|
|
78
|
+
icon: ActionIcon,
|
|
79
|
+
label: string,
|
|
80
|
+
enabled: boolean,
|
|
81
|
+
fn: (() => void) | WorkspaceAction,
|
|
82
|
+
target: HTMLElement = dialog,
|
|
83
|
+
) => {
|
|
63
84
|
const b = button(label, label, () => {
|
|
64
85
|
local.dispose();
|
|
65
|
-
act(fn);
|
|
86
|
+
if (typeof fn === 'function') act(fn);
|
|
66
87
|
});
|
|
67
88
|
const glyph = el(doc, 'span', 'layouts-tab-icon');
|
|
68
89
|
glyph.setAttribute('aria-hidden', 'true');
|
|
69
90
|
act(() => glyph.append(actionIcon(doc, icon, options)));
|
|
70
91
|
b.replaceChildren(glyph, el(doc, 'span', '', label.replace(/^\+ /, '')));
|
|
71
92
|
b.disabled = !enabled;
|
|
72
|
-
|
|
93
|
+
if (typeof fn === 'string') bindAction(b, fn);
|
|
94
|
+
if (target !== dialog) b.setAttribute('role', 'menuitem');
|
|
95
|
+
target.append(b);
|
|
73
96
|
return b;
|
|
74
97
|
};
|
|
75
98
|
const allowed = (cap: 'split' | 'join' | 'move') =>
|
|
@@ -188,7 +211,7 @@ export function createPaneMenu(
|
|
|
188
211
|
);
|
|
189
212
|
return;
|
|
190
213
|
}
|
|
191
|
-
add('add-tab', message(options, '+ Add tab'), available && allowed('move'),
|
|
214
|
+
add('add-tab', message(options, '+ Add tab'), available && allowed('move'), 'addTab');
|
|
192
215
|
const canCreate = !group.panes.length || available;
|
|
193
216
|
const flyouts: { trigger: HTMLElement; flyout: HTMLElement }[] = [];
|
|
194
217
|
let positionMenus = () => {};
|
|
@@ -207,7 +230,14 @@ export function createPaneMenu(
|
|
|
207
230
|
flyout.setAttribute('role', 'menu');
|
|
208
231
|
flyout.setAttribute('aria-label', label);
|
|
209
232
|
flyouts.push({ trigger, flyout });
|
|
233
|
+
let closeTimer: number | undefined;
|
|
234
|
+
const cancelClose = () => {
|
|
235
|
+
win.clearTimeout(closeTimer);
|
|
236
|
+
closeTimer = undefined;
|
|
237
|
+
};
|
|
238
|
+
local.add(cancelClose);
|
|
210
239
|
const setOpen = (open: boolean) => {
|
|
240
|
+
cancelClose();
|
|
211
241
|
if (open) {
|
|
212
242
|
for (const other of flyouts) {
|
|
213
243
|
other.flyout.hidden = true;
|
|
@@ -221,7 +251,11 @@ export function createPaneMenu(
|
|
|
221
251
|
local.listen(container, 'pointerenter', () => {
|
|
222
252
|
if (!trigger.disabled) setOpen(true);
|
|
223
253
|
});
|
|
224
|
-
|
|
254
|
+
// Allow the pointer to cross the dialog padding or move diagonally into the flyout.
|
|
255
|
+
local.listen(container, 'pointerleave', () => {
|
|
256
|
+
cancelClose();
|
|
257
|
+
closeTimer = win.setTimeout(() => setOpen(false), 300);
|
|
258
|
+
});
|
|
225
259
|
local.listen(trigger, 'keydown', (event) => {
|
|
226
260
|
if ((event as KeyboardEvent).key === 'ArrowRight') {
|
|
227
261
|
event.preventDefault();
|
|
@@ -241,16 +275,14 @@ export function createPaneMenu(
|
|
|
241
275
|
return flyout;
|
|
242
276
|
}
|
|
243
277
|
const flyout = submenu(message(options, 'Split'), 'split-right', canCreate && allowed('split'));
|
|
244
|
-
for (const [label, axis,
|
|
245
|
-
[message(options, 'Split left'), 'horizontal',
|
|
246
|
-
[message(options, 'Split right'), 'horizontal',
|
|
247
|
-
[message(options, 'Split up'), 'vertical',
|
|
248
|
-
[message(options, 'Split down'), 'vertical',
|
|
278
|
+
for (const [label, axis, action] of [
|
|
279
|
+
[message(options, 'Split left'), 'horizontal', 'splitLeft'],
|
|
280
|
+
[message(options, 'Split right'), 'horizontal', 'splitRight'],
|
|
281
|
+
[message(options, 'Split up'), 'vertical', 'splitUp'],
|
|
282
|
+
[message(options, 'Split down'), 'vertical', 'splitDown'],
|
|
249
283
|
] as const) {
|
|
250
|
-
const option = button(label, label, () => {
|
|
251
|
-
|
|
252
|
-
create(axis, before);
|
|
253
|
-
});
|
|
284
|
+
const option = button(label, label, () => {});
|
|
285
|
+
bindAction(option, action);
|
|
254
286
|
const icon = el(doc, 'span', 'layouts-tab-icon');
|
|
255
287
|
icon.setAttribute('aria-hidden', 'true');
|
|
256
288
|
icon.append(actionIcon(doc, axis === 'horizontal' ? 'split-right' : 'split-below', options));
|
|
@@ -263,7 +295,7 @@ export function createPaneMenu(
|
|
|
263
295
|
'join',
|
|
264
296
|
message(options, 'Join sibling region'),
|
|
265
297
|
Boolean(joinParent) && paneIds(joinParent!).every((id) => options.store.can(id, 'join')),
|
|
266
|
-
|
|
298
|
+
'joinSiblingRegion',
|
|
267
299
|
);
|
|
268
300
|
let preview: Scope | undefined;
|
|
269
301
|
const clearPreview = () => {
|
|
@@ -334,20 +366,14 @@ export function createPaneMenu(
|
|
|
334
366
|
? message(options, 'Restore region')
|
|
335
367
|
: message(options, 'Maximize region'),
|
|
336
368
|
true,
|
|
337
|
-
|
|
338
|
-
options.store.maximize(
|
|
339
|
-
options.store.getSnapshot().maximized === group.id ? null : group.id,
|
|
340
|
-
),
|
|
369
|
+
'maximize',
|
|
341
370
|
);
|
|
342
371
|
if (group.panes.length && pane && options.popouts !== false) {
|
|
343
372
|
add(
|
|
344
373
|
'popout',
|
|
345
374
|
message(options, 'Open in window'),
|
|
346
375
|
options.store.can(pane.id, 'popout'),
|
|
347
|
-
|
|
348
|
-
void windows.open(pane.id);
|
|
349
|
-
refresh();
|
|
350
|
-
},
|
|
376
|
+
'popout',
|
|
351
377
|
);
|
|
352
378
|
}
|
|
353
379
|
const orientation = submenu(message(options, 'Tab orientation'), 'tab-orientation');
|
|
@@ -357,10 +383,15 @@ export function createPaneMenu(
|
|
|
357
383
|
['left', message(options, 'Vertical')],
|
|
358
384
|
] as const) {
|
|
359
385
|
const selected = group.tabPlacement === value;
|
|
360
|
-
const option = button(label, label, () => {
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
386
|
+
const option = button(label, label, () => {});
|
|
387
|
+
bindAction(
|
|
388
|
+
option,
|
|
389
|
+
value === 'top'
|
|
390
|
+
? 'tabOrientationHorizontal'
|
|
391
|
+
: value === 'left'
|
|
392
|
+
? 'tabOrientationVertical'
|
|
393
|
+
: 'tabOrientationDefault',
|
|
394
|
+
);
|
|
364
395
|
option.setAttribute('role', 'menuitemradio');
|
|
365
396
|
option.setAttribute('aria-checked', String(selected));
|
|
366
397
|
const mark = el(doc, 'span', 'layouts-tab-icon', selected ? '✓' : '');
|
|
@@ -375,10 +406,15 @@ export function createPaneMenu(
|
|
|
375
406
|
['compact', message(options, 'Compact')],
|
|
376
407
|
] as const) {
|
|
377
408
|
const selected = group.tabDisplay === value;
|
|
378
|
-
const option = button(label, label, () => {
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
409
|
+
const option = button(label, label, () => {});
|
|
410
|
+
bindAction(
|
|
411
|
+
option,
|
|
412
|
+
value === 'automatic'
|
|
413
|
+
? 'tabDisplayAutomatic'
|
|
414
|
+
: value === 'compact'
|
|
415
|
+
? 'tabDisplayCompact'
|
|
416
|
+
: 'tabDisplayDefault',
|
|
417
|
+
);
|
|
382
418
|
option.setAttribute('role', 'menuitemradio');
|
|
383
419
|
option.setAttribute('aria-checked', String(selected));
|
|
384
420
|
const mark = el(doc, 'span', 'layouts-tab-icon', selected ? '✓' : '');
|
|
@@ -387,31 +423,34 @@ export function createPaneMenu(
|
|
|
387
423
|
display.append(option);
|
|
388
424
|
}
|
|
389
425
|
if (group.panes.length && pane) {
|
|
426
|
+
const close = submenu(message(options, 'Close'), 'close');
|
|
390
427
|
add(
|
|
391
428
|
'close',
|
|
392
429
|
message(options, 'Close active tab'),
|
|
393
430
|
options.store.can(pane.id, 'close'),
|
|
394
|
-
|
|
431
|
+
'closeActiveTab',
|
|
432
|
+
close,
|
|
395
433
|
);
|
|
396
434
|
add(
|
|
397
435
|
'close',
|
|
398
436
|
message(options, 'Close pane'),
|
|
399
437
|
group.panes.every((id) => options.store.can(id, 'close')),
|
|
400
|
-
|
|
438
|
+
'closePane',
|
|
439
|
+
close,
|
|
401
440
|
);
|
|
402
441
|
} else {
|
|
403
442
|
add(
|
|
404
443
|
'close',
|
|
405
444
|
message(options, 'Close empty pane'),
|
|
406
445
|
options.store.getSnapshot().root.id !== group.id,
|
|
407
|
-
|
|
446
|
+
'closeEmptyPane',
|
|
408
447
|
);
|
|
409
448
|
}
|
|
410
449
|
add(
|
|
411
450
|
'restore',
|
|
412
451
|
message(options, 'Restore closed tab'),
|
|
413
452
|
options.store.canRestoreClosedTab(),
|
|
414
|
-
|
|
453
|
+
'restoreClosedTab',
|
|
415
454
|
);
|
|
416
455
|
add('cancel', message(options, 'Cancel'), true, () => {});
|
|
417
456
|
root.append(dialog);
|
|
@@ -462,8 +501,47 @@ export function createPaneMenu(
|
|
|
462
501
|
local.dispose();
|
|
463
502
|
}
|
|
464
503
|
});
|
|
504
|
+
const updateHints = () => {
|
|
505
|
+
const registrations = registeredShortcuts(options);
|
|
506
|
+
for (const { element, action } of hints) {
|
|
507
|
+
element.querySelector('.layouts-shortcut')?.remove();
|
|
508
|
+
element.removeAttribute('aria-keyshortcuts');
|
|
509
|
+
element.disabled = !actions.canExecuteAction(action, context);
|
|
510
|
+
const label = element.getAttribute('aria-label') ?? '';
|
|
511
|
+
const registration = registrations.find((entry) => entry.action === action);
|
|
512
|
+
element.removeAttribute('title');
|
|
513
|
+
if (!registration) continue;
|
|
514
|
+
const labels = registration.bindings.map((binding) => {
|
|
515
|
+
try {
|
|
516
|
+
return options.formatShortcut?.(binding) ?? formatShortcut(binding);
|
|
517
|
+
} catch (error) {
|
|
518
|
+
report(error);
|
|
519
|
+
return formatShortcut(binding);
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
const badge = el(doc, 'kbd', 'layouts-shortcut', labels[0]);
|
|
523
|
+
badge.setAttribute('aria-hidden', 'true');
|
|
524
|
+
element.append(badge);
|
|
525
|
+
element.title = `${label} (${labels.join(' / ')})`;
|
|
526
|
+
if (!element.disabled)
|
|
527
|
+
element.setAttribute(
|
|
528
|
+
'aria-keyshortcuts',
|
|
529
|
+
registration.bindings.map(ariaShortcut).join(' '),
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
positionMenus();
|
|
533
|
+
};
|
|
534
|
+
refreshShortcuts = updateHints;
|
|
535
|
+
local.add(() => {
|
|
536
|
+
if (refreshShortcuts === updateHints) refreshShortcuts = () => {};
|
|
537
|
+
});
|
|
538
|
+
const target = { dialog, context, close: () => local.dispose() };
|
|
539
|
+
shortcutTarget = target;
|
|
540
|
+
local.add(() => {
|
|
541
|
+
if (shortcutTarget === target) shortcutTarget = undefined;
|
|
542
|
+
});
|
|
465
543
|
dialog.showModal();
|
|
466
|
-
|
|
544
|
+
updateHints();
|
|
467
545
|
const observer = new win.ResizeObserver(positionMenus);
|
|
468
546
|
observer.observe(dialog);
|
|
469
547
|
observer.observe(anchor);
|
|
@@ -478,6 +556,8 @@ export function createPaneMenu(
|
|
|
478
556
|
dialog.focus({ preventScroll: true });
|
|
479
557
|
}
|
|
480
558
|
return {
|
|
559
|
+
getShortcutTarget: () => shortcutTarget,
|
|
560
|
+
refreshShortcuts: () => refreshShortcuts(),
|
|
481
561
|
open,
|
|
482
562
|
addTab(anchor: HTMLElement, group: Group) {
|
|
483
563
|
const layout = options.store.getSnapshot();
|
package/src/panes.ts
CHANGED
|
@@ -24,67 +24,94 @@ export function mountPane(
|
|
|
24
24
|
const renderer = Object.hasOwn(options.renderers ?? {}, pane.type)
|
|
25
25
|
? options.renderers?.[pane.type]
|
|
26
26
|
: undefined;
|
|
27
|
-
let view: PaneView;
|
|
27
|
+
let view: PaneView = { dispose() {} };
|
|
28
|
+
let observer: ResizeObserver | undefined;
|
|
29
|
+
let disposed = false;
|
|
30
|
+
const controller = new (win as Window & typeof globalThis).AbortController();
|
|
31
|
+
const cleanups: (() => void)[] = [];
|
|
32
|
+
const reportCleanup = (error: unknown) => {
|
|
33
|
+
try {
|
|
34
|
+
options.onError?.(error);
|
|
35
|
+
} catch {
|
|
36
|
+
/* Finish teardown even if reporting fails. */
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
const run = (callback: () => void) => {
|
|
40
|
+
try {
|
|
41
|
+
callback();
|
|
42
|
+
} catch (error) {
|
|
43
|
+
reportCleanup(error);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const dispose = () => {
|
|
47
|
+
if (disposed) return;
|
|
48
|
+
disposed = true;
|
|
49
|
+
run(() => controller.abort());
|
|
50
|
+
run(() => observer?.disconnect());
|
|
51
|
+
run(() => view.dispose());
|
|
52
|
+
for (const cleanup of cleanups.splice(0).reverse()) run(cleanup);
|
|
53
|
+
element.remove();
|
|
54
|
+
};
|
|
28
55
|
let result: MountedPane | undefined;
|
|
29
56
|
const reportError = (error: unknown) => {
|
|
30
57
|
if (result) result.failed = true;
|
|
31
58
|
options.onError?.(error);
|
|
32
59
|
};
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
} catch (error) {
|
|
62
|
-
options.onError?.(error);
|
|
63
|
-
}
|
|
60
|
+
try {
|
|
61
|
+
if (renderer)
|
|
62
|
+
view = renderer({
|
|
63
|
+
signal: controller.signal,
|
|
64
|
+
onCleanup(callback) {
|
|
65
|
+
if (disposed) run(callback);
|
|
66
|
+
else cleanups.push(callback);
|
|
67
|
+
},
|
|
68
|
+
element,
|
|
69
|
+
reportError,
|
|
70
|
+
document: doc,
|
|
71
|
+
window: win,
|
|
72
|
+
pane,
|
|
73
|
+
state: options.getPaneState?.(pane.id),
|
|
74
|
+
location,
|
|
75
|
+
}) ?? { dispose() {} };
|
|
76
|
+
else {
|
|
77
|
+
element.append(
|
|
78
|
+
el(
|
|
79
|
+
doc,
|
|
80
|
+
'div',
|
|
81
|
+
'layouts-placeholder',
|
|
82
|
+
message(options, 'Unknown pane type: {type}. Register a renderer to display this pane.', {
|
|
83
|
+
type: pane.type,
|
|
84
|
+
}),
|
|
85
|
+
),
|
|
86
|
+
);
|
|
87
|
+
view = { dispose() {} };
|
|
64
88
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
renderer,
|
|
74
|
-
dispose() {
|
|
75
|
-
if (disposed) return;
|
|
76
|
-
disposed = true;
|
|
77
|
-
observer.disconnect();
|
|
78
|
-
try {
|
|
79
|
-
view.dispose();
|
|
80
|
-
} finally {
|
|
81
|
-
element.remove();
|
|
89
|
+
observer = new ResizeObserver((entries) => {
|
|
90
|
+
const rect = entries[0]?.contentRect;
|
|
91
|
+
if (rect) {
|
|
92
|
+
try {
|
|
93
|
+
view.resize?.(rect.width, rect.height);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
options.onError?.(error);
|
|
96
|
+
}
|
|
82
97
|
}
|
|
83
|
-
},
|
|
84
|
-
};
|
|
85
|
-
if (location === 'main')
|
|
86
|
-
void view.ready?.catch((error) => {
|
|
87
|
-
if (!disposed) reportError(error);
|
|
88
98
|
});
|
|
89
|
-
|
|
99
|
+
observer.observe(element);
|
|
100
|
+
result = {
|
|
101
|
+
element,
|
|
102
|
+
view,
|
|
103
|
+
pane,
|
|
104
|
+
signature: JSON.stringify(pane),
|
|
105
|
+
renderer,
|
|
106
|
+
dispose,
|
|
107
|
+
};
|
|
108
|
+
if (location === 'main')
|
|
109
|
+
void view.ready?.catch((error) => {
|
|
110
|
+
if (!disposed) reportError(error);
|
|
111
|
+
});
|
|
112
|
+
return result;
|
|
113
|
+
} catch (error) {
|
|
114
|
+
dispose();
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
90
117
|
}
|
package/src/registry.ts
CHANGED
|
@@ -46,6 +46,11 @@ export class TabRegistry {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
let instanceSequence = 0;
|
|
49
|
+
export function createPaneId(): string {
|
|
50
|
+
return (
|
|
51
|
+
globalThis.crypto?.randomUUID?.() ?? `quilt-${Date.now().toString(36)}-${++instanceSequence}`
|
|
52
|
+
);
|
|
53
|
+
}
|
|
49
54
|
|
|
50
55
|
export interface PaneRegistration<T> {
|
|
51
56
|
type: string;
|
|
@@ -90,10 +95,7 @@ export class PaneRegistry<T> {
|
|
|
90
95
|
title: stored.title,
|
|
91
96
|
...(stored.icon !== undefined ? { icon: stored.icon } : {}),
|
|
92
97
|
...supplied,
|
|
93
|
-
id:
|
|
94
|
-
supplied.id ??
|
|
95
|
-
globalThis.crypto?.randomUUID?.() ??
|
|
96
|
-
`quilt-${Date.now().toString(36)}-${++instanceSequence}`,
|
|
98
|
+
id: supplied.id ?? createPaneId(),
|
|
97
99
|
};
|
|
98
100
|
},
|
|
99
101
|
});
|