flexdesk 0.17.16 → 0.17.18
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 +36 -0
- package/LICENSE +0 -0
- package/README.md +0 -0
- package/THIRD_PARTY_LICENSES.md +0 -0
- package/canvas.js +0 -0
- package/charts.js +0 -0
- package/core.js +0 -0
- package/css/base.css +32 -1
- package/css/flexdesk.css +32 -1
- package/css/reset.css +0 -0
- package/css/tokens.css +3 -0
- package/dist/canvas.js +0 -0
- package/dist/canvas.js.map +0 -0
- package/dist/chunk-3PHCPZHT.js +0 -0
- package/dist/chunk-3PHCPZHT.js.map +0 -0
- package/dist/chunk-FOOS3T5L.js +0 -0
- package/dist/chunk-FOOS3T5L.js.map +0 -0
- package/dist/chunk-GOQLR5WR.js +0 -0
- package/dist/chunk-GOQLR5WR.js.map +0 -0
- package/dist/{chunk-JKZLNKG6.js → chunk-HKPB6OFA.js} +90 -25
- package/dist/{chunk-JKZLNKG6.js.map → chunk-HKPB6OFA.js.map} +2 -2
- package/dist/chunk-HNU6MM2B.js +0 -0
- package/dist/chunk-HNU6MM2B.js.map +0 -0
- package/dist/chunk-JYWURG5T.js +0 -0
- package/dist/chunk-JYWURG5T.js.map +0 -0
- package/dist/chunk-N7HC3ADV.js +0 -0
- package/dist/chunk-N7HC3ADV.js.map +0 -0
- package/dist/editor.js.map +0 -0
- package/dist/flexdesk.css +32 -1
- package/dist/flow.js.map +0 -0
- package/dist/host.js +0 -0
- package/dist/host.js.map +0 -0
- package/dist/reset.css +0 -0
- package/dist/tile_registry-6WZPTUZV.js +0 -0
- package/dist/tile_registry-6WZPTUZV.js.map +0 -0
- package/dist/tokens.css +3 -0
- package/dist/widgets.js +4115 -4089
- package/dist/widgets.js.map +4 -4
- package/dist/wm.js +115 -31
- package/dist/wm.js.map +2 -2
- package/docs/components/command-palette.md +0 -0
- package/docs/components/conversation.md +0 -0
- package/docs/components/data-table-grid.md +0 -0
- package/docs/components/data-table.md +0 -0
- package/docs/components/editor.md +0 -0
- package/docs/components/flow-canvas-editor.md +0 -0
- package/docs/components/flow-editors.md +0 -0
- package/docs/components/flow-lane-editor.md +0 -0
- package/docs/components/flow-outline-editor.md +0 -0
- package/docs/components/input-fields.md +0 -0
- package/docs/components/markdown-and-outline.md +0 -0
- package/docs/components/menus.md +38 -1
- package/docs/components/notifications.md +23 -1
- package/docs/components/panels-and-desktops.md +22 -0
- package/docs/components/record-list-and-fact-table.md +0 -0
- package/docs/components/settings-view.md +0 -0
- package/docs/components/shell.md +54 -0
- package/docs/components/wizard.md +0 -0
- package/docs/concepts/building-and-packaging.md +0 -0
- package/docs/concepts/domain-neutrality.md +0 -0
- package/docs/concepts/host-port.md +0 -0
- package/docs/concepts/packages-and-entry-points.md +0 -0
- package/docs/concepts/peer-dependencies.md +0 -0
- package/docs/quickstart.md +0 -0
- package/editor.js +0 -0
- package/flow.js +0 -0
- package/host.js +0 -0
- package/package.json +1 -1
- package/src/canvas/canvas_adapter.js +0 -0
- package/src/canvas/edge_router.js +0 -0
- package/src/canvas/graph/connector_router.js +0 -0
- package/src/canvas/graph/history_service.js +0 -0
- package/src/canvas/graph/node_platform.js +0 -0
- package/src/canvas/index.js +0 -0
- package/src/canvas/layered_layout.js +0 -0
- package/src/charts/data_series_plot_window.js +0 -0
- package/src/charts/downsample.js +0 -0
- package/src/charts/plot_config.js +0 -0
- package/src/charts/plotly_wrapper.js +0 -0
- package/src/core/event_bus.js +0 -0
- package/src/core/logging.js +0 -0
- package/src/core/settings.js +0 -0
- package/src/core/state_guard.js +0 -0
- package/src/core/state_machine.js +0 -0
- package/src/editor/editor_pane.js +0 -0
- package/src/editor/monaco_editor_factory.js +0 -0
- package/src/editor/monaco_loader.js +0 -0
- package/src/editor/notebook_search.js +0 -0
- package/src/editor/notebook_undo_manager.js +0 -0
- package/src/editor/split_pane_container.js +0 -0
- package/src/flow/canvas_editor/connections.js +0 -0
- package/src/flow/canvas_editor/editor.js +0 -0
- package/src/flow/canvas_editor/index.js +0 -0
- package/src/flow/canvas_editor/layout.js +0 -0
- package/src/flow/canvas_editor/strings.js +0 -0
- package/src/flow/kit/always_before.js +0 -0
- package/src/flow/kit/catalogue.js +0 -0
- package/src/flow/kit/chip_input.js +0 -0
- package/src/flow/kit/dom.js +0 -0
- package/src/flow/kit/findings.js +0 -0
- package/src/flow/kit/history.js +0 -0
- package/src/flow/kit/index.js +0 -0
- package/src/flow/kit/keys.js +0 -0
- package/src/flow/kit/logic_graph.js +0 -0
- package/src/flow/kit/references.js +0 -0
- package/src/flow/kit/settings_panel.js +0 -0
- package/src/flow/kit/settings_schema.js +0 -0
- package/src/flow/kit/step_picker.js +0 -0
- package/src/flow/kit/strings.js +0 -0
- package/src/flow/kit/value_picker.js +0 -0
- package/src/flow/kit/widgets.js +0 -0
- package/src/flow/lanes/editor.js +0 -0
- package/src/flow/lanes/index.js +0 -0
- package/src/flow/lanes/layout.js +0 -0
- package/src/flow/lanes/pipeline.js +0 -0
- package/src/flow/lanes/preview.js +0 -0
- package/src/flow/lanes/strings.js +0 -0
- package/src/flow/outline/editor.js +0 -0
- package/src/flow/outline/index.js +0 -0
- package/src/flow/outline/mapping.js +0 -0
- package/src/flow/outline/menu.js +0 -0
- package/src/flow/outline/operations.js +0 -0
- package/src/flow/outline/recognise.js +0 -0
- package/src/flow/outline/strings.js +0 -0
- package/src/help/help_modal.js +0 -0
- package/src/help/help_registry.js +0 -0
- package/src/host/external_links.js +0 -0
- package/src/host/file_picker.js +0 -0
- package/src/host/host.js +0 -0
- package/src/host/photino_host.js +0 -0
- package/src/host/photino_host.md +0 -0
- package/src/host/pywebview_host.js +0 -0
- package/src/tiles/chart_tiles.js +0 -0
- package/src/tiles/config_schema.js +0 -0
- package/src/tiles/figure_tile.js +0 -0
- package/src/tiles/layout_persistence.js +0 -0
- package/src/tiles/list_tiles.js +0 -0
- package/src/tiles/scope_picker.js +0 -0
- package/src/tiles/tile_config_form.js +0 -0
- package/src/tiles/tile_registry.js +0 -0
- package/src/tiling/bottom_panel_tabs.js +0 -0
- package/src/tiling/command_palette.js +0 -0
- package/src/tiling/content_registry.js +0 -0
- package/src/tiling/entity_sources.js +0 -0
- package/src/tiling/kind_taxonomy.js +21 -13
- package/src/tiling/landing_table.js +0 -0
- package/src/tiling/loading_overlay.js +0 -0
- package/src/tiling/nav_panel.js +0 -0
- package/src/tiling/page_factory.js +0 -0
- package/src/tiling/panel_availability.js +0 -0
- package/src/tiling/panel_keys.js +0 -0
- package/src/tiling/shell.js +4 -1
- package/src/tiling/tab_strip.js +0 -0
- package/src/tiling/tile_renderer.js +0 -0
- package/src/tiling/tile_tab_menu.js +0 -0
- package/src/tiling/wm.js +123 -28
- package/src/tiling/zoom.js +0 -0
- package/src/ui/base/component_base.js +0 -0
- package/src/ui/base/controller_base.js +0 -0
- package/src/ui/base/page_base.js +0 -0
- package/src/ui/components/about_dialog.js +0 -0
- package/src/ui/components/alert_log.js +0 -0
- package/src/ui/components/alerts_pane.js +0 -0
- package/src/ui/components/attribute_list_editor.js +0 -0
- package/src/ui/components/autocomplete_field.js +0 -0
- package/src/ui/components/cell_editor_host.js +0 -0
- package/src/ui/components/cell_editors.js +0 -0
- package/src/ui/components/cell_grid.js +0 -0
- package/src/ui/components/cell_paste.js +0 -0
- package/src/ui/components/cell_selection.js +0 -0
- package/src/ui/components/choice_controls.js +0 -0
- package/src/ui/components/color_field.js +0 -0
- package/src/ui/components/color_list_field.js +0 -0
- package/src/ui/components/column_fit.js +0 -0
- package/src/ui/components/column_groups.js +0 -0
- package/src/ui/components/column_order.js +0 -0
- package/src/ui/components/conversation_view.js +0 -0
- package/src/ui/components/crosstab.js +0 -0
- package/src/ui/components/detail_header.js +0 -0
- package/src/ui/components/document_outline.js +0 -0
- package/src/ui/components/drag_reorder.js +0 -0
- package/src/ui/components/fact_table.js +0 -0
- package/src/ui/components/fill_handle.js +0 -0
- package/src/ui/components/gallery_picker.js +0 -0
- package/src/ui/components/header_actions.js +0 -0
- package/src/ui/components/inline_renamer.js +0 -0
- package/src/ui/components/job_progress.js +0 -0
- package/src/ui/components/menu_bar.js +101 -24
- package/src/ui/components/notification_history.js +11 -1
- package/src/ui/components/progress.js +0 -0
- package/src/ui/components/record_list.js +0 -0
- package/src/ui/components/row_activation.js +0 -0
- package/src/ui/components/section_stack.js +0 -0
- package/src/ui/components/series.js +0 -0
- package/src/ui/components/settings_view.js +0 -0
- package/src/ui/components/slide_out_panel.js +0 -0
- package/src/ui/components/slider_field.js +0 -0
- package/src/ui/components/sortable_list.js +0 -0
- package/src/ui/components/staged_edits.js +0 -0
- package/src/ui/components/staged_history.js +0 -0
- package/src/ui/components/table_filter_chips.js +0 -0
- package/src/ui/components/table_state_store.js +0 -0
- package/src/ui/components/table_structure.js +0 -0
- package/src/ui/components/table_values.js +0 -0
- package/src/ui/components/tabs.js +0 -0
- package/src/ui/components/tag_input.js +0 -0
- package/src/ui/components/toast.js +0 -0
- package/src/ui/components/toggle_field.js +0 -0
- package/src/ui/components/tsv.js +0 -0
- package/src/ui/components/window_taskbar.js +0 -0
- package/src/ui/controllers/window_chrome_controller.js +4 -1
- package/src/ui/notification_center.js +47 -1
- package/src/ui/utils/auto_scrollbars.js +0 -0
- package/src/ui/utils/markdown.js +0 -0
- package/src/ui/utils/native_context_menu.js +0 -0
- package/src/ui/utils/overlay_scrollbar.js +0 -0
- package/src/ui/utils/raf_resize_observer.js +0 -0
- package/src/ui/utils/scroll_lock.js +0 -0
- package/src/ui/utils/strip_scroll.js +0 -0
- package/src/ui/utils/tooltip_service.js +0 -0
- package/tiles.js +0 -0
- package/wm.js +0 -0
package/src/tiling/wm.js
CHANGED
|
@@ -344,6 +344,29 @@ export class WindowManager {
|
|
|
344
344
|
//
|
|
345
345
|
// MOVE verbs (float, dock, drag a tab, move to desktop) carry the one
|
|
346
346
|
// instance somewhere else and are untouched.
|
|
347
|
+
//
|
|
348
|
+
// `single: 'desktop'` keeps the same promises per virtual desktop: a
|
|
349
|
+
// desktop is the tiles and panels the user arranged on it, so opening the
|
|
350
|
+
// kind looks only at the ACTIVE desktop — its tiles, their archived pages
|
|
351
|
+
// and the windows on it — and opens it there when that desktop has none.
|
|
352
|
+
// A window is on the desktop it was opened on or moved to (`desktopIdx`):
|
|
353
|
+
// a free-floating one is drawn over every desktop, but it is that one
|
|
354
|
+
// desktop's instance, so it never stands in for another desktop's. The
|
|
355
|
+
// move verbs refuse to carry the kind onto a desktop that already holds
|
|
356
|
+
// it (`desktopMoveRefusal`). Navigating inside a window looks on the
|
|
357
|
+
// window's own desktop (`openInWindow`). Removing a desktop brings its
|
|
358
|
+
// windows across, a window never being destroyed by a tile operation, so
|
|
359
|
+
// the active desktop can then hold the kind twice until the next reload
|
|
360
|
+
// keeps the first.
|
|
361
|
+
|
|
362
|
+
/** The key a tab's instance is unique under on desktop `desktopIdx`: its
|
|
363
|
+
* instance key, scoped to that desktop for a `single: 'desktop'` kind;
|
|
364
|
+
* null for a tab that is not single. */
|
|
365
|
+
_scopedKey(t, desktopIdx) {
|
|
366
|
+
const k = t ? this._instanceKey(t.kind, t.props) : null;
|
|
367
|
+
if (k == null || !this.taxonomy.isSinglePerDesktop(t.kind)) return k;
|
|
368
|
+
return `${desktopIdx}\u0000${k}`;
|
|
369
|
+
}
|
|
347
370
|
|
|
348
371
|
/** The instance key of a tab spec, or null when its kind is not single. */
|
|
349
372
|
_instanceKey(kind, props) {
|
|
@@ -356,27 +379,38 @@ export class WindowManager {
|
|
|
356
379
|
}
|
|
357
380
|
|
|
358
381
|
/** Where the one instance of a single kind is open, or null — also null
|
|
359
|
-
* for a kind that is not single.
|
|
382
|
+
* for a kind that is not single. A `single: 'desktop'` kind is looked for
|
|
383
|
+
* on the active desktop only. One of:
|
|
360
384
|
* `{ where: 'window', windowId, idx, desktopIdx }`
|
|
361
385
|
* `{ where: 'tab', desktopIdx, leafId, idx }`
|
|
362
386
|
* `{ where: 'archive', desktopIdx, leafId, page, idx }` — a tab of a
|
|
363
387
|
* top-nav page the tile has switched away from (`pageTabs`). */
|
|
364
388
|
findInstance(kind, props = {}) {
|
|
389
|
+
const on = this.taxonomy.isSinglePerDesktop(kind) ? this.desktops.activeIdx : null;
|
|
390
|
+
return this._findInstance(kind, props, on);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/** `findInstance`, looking only at desktop `on` when it is a number, and
|
|
394
|
+
* past the window `exceptWindowId`. */
|
|
395
|
+
_findInstance(kind, props, on, exceptWindowId = null) {
|
|
365
396
|
const key = this._instanceKey(kind, props);
|
|
366
397
|
if (key == null) return null;
|
|
367
398
|
const same = (t) => !!t && this._instanceKey(t.kind, t.props) === key;
|
|
399
|
+
const desktops = [...this.desktops.desktops.entries()]
|
|
400
|
+
.filter(([desktopIdx]) => on == null || desktopIdx === on);
|
|
368
401
|
for (const [windowId, rec] of this._windowToLeaf) {
|
|
402
|
+
if (windowId === exceptWindowId) continue;
|
|
403
|
+
if (on != null && rec.desktopIdx !== on) continue;
|
|
369
404
|
const idx = _windowTabSpecs(rec).findIndex(same);
|
|
370
405
|
if (idx >= 0) return { where: 'window', windowId, idx, desktopIdx: rec.desktopIdx };
|
|
371
406
|
}
|
|
372
|
-
const
|
|
373
|
-
for (const [desktopIdx, d] of desktops.entries()) {
|
|
407
|
+
for (const [desktopIdx, d] of desktops) {
|
|
374
408
|
for (const leaf of d.tree.leaves()) {
|
|
375
409
|
const idx = (leaf.tabs || []).findIndex(same);
|
|
376
410
|
if (idx >= 0) return { where: 'tab', desktopIdx, leafId: leaf.id, idx };
|
|
377
411
|
}
|
|
378
412
|
}
|
|
379
|
-
for (const [desktopIdx, d] of desktops
|
|
413
|
+
for (const [desktopIdx, d] of desktops) {
|
|
380
414
|
for (const leaf of d.tree.leaves()) {
|
|
381
415
|
for (const [page, arc] of this._archivedPages(leaf)) {
|
|
382
416
|
const idx = arc.tabs.findIndex(same);
|
|
@@ -387,6 +421,31 @@ export class WindowManager {
|
|
|
387
421
|
return null;
|
|
388
422
|
}
|
|
389
423
|
|
|
424
|
+
/** Why moving a pane's or a window's tabs onto desktop `idx` is refused,
|
|
425
|
+
* or null: that desktop already holds one of their `single: 'desktop'`
|
|
426
|
+
* kinds (in a tile, an archived page or a window), and a desktop holds
|
|
427
|
+
* such a kind once. `source` is `{ leafId }` (a tile of the active
|
|
428
|
+
* desktop) or `{ windowId }`. The move verbs ask it, and their menu rows
|
|
429
|
+
* are greyed with its sentence. */
|
|
430
|
+
desktopMoveRefusal(source, idx) {
|
|
431
|
+
let specs = [];
|
|
432
|
+
if (source?.windowId) {
|
|
433
|
+
const rec = this._windowToLeaf.get(source.windowId);
|
|
434
|
+
specs = rec ? _windowTabSpecs(rec) : [];
|
|
435
|
+
} else {
|
|
436
|
+
const leaf = this._tree().get(source?.leafId);
|
|
437
|
+
specs = leaf?.tabs?.length ? leaf.tabs : (leaf?.content ? [leaf.content] : []);
|
|
438
|
+
}
|
|
439
|
+
const d = this.desktops.desktops[idx];
|
|
440
|
+
if (!d) return null;
|
|
441
|
+
for (const t of specs) {
|
|
442
|
+
if (!t || !this.taxonomy.isSinglePerDesktop(t.kind)) continue;
|
|
443
|
+
if (!this._findInstance(t.kind, t.props, idx, source.windowId ?? null)) continue;
|
|
444
|
+
return `${this.taxonomy.kinds[t.kind]?.label || t.kind} is open on desktop ${d.label} already`;
|
|
445
|
+
}
|
|
446
|
+
return null;
|
|
447
|
+
}
|
|
448
|
+
|
|
390
449
|
/** Reveal the instance of a single kind that is open: answers whether
|
|
391
450
|
* there was one. Every open path asks this first.
|
|
392
451
|
*
|
|
@@ -400,7 +459,12 @@ export class WindowManager {
|
|
|
400
459
|
* open names no props at all — a top-nav button, `openInPrimary(kind)` —
|
|
401
460
|
* which asks to SEE the instance, not to empty it. */
|
|
402
461
|
revealInstance(kind, props = {}) {
|
|
403
|
-
|
|
462
|
+
return this._revealFound(this.findInstance(kind, props), kind, props);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/** Reveal the instance at `loc` (a `findInstance` result, or null) and
|
|
466
|
+
* hand it `props`. Answers whether there was one to reveal. */
|
|
467
|
+
_revealFound(loc, kind, props) {
|
|
404
468
|
if (!loc || !this._revealAt(loc)) return false;
|
|
405
469
|
if (props && Object.keys(props).length) this._deliverProps(kind, props);
|
|
406
470
|
return true;
|
|
@@ -516,7 +580,8 @@ export class WindowManager {
|
|
|
516
580
|
* soon as the tile leaves the page by a path other than `swapToPage`
|
|
517
581
|
* (`navigateActiveTab`). So a restored tab whose instance is live
|
|
518
582
|
* anywhere else is dropped from the tile and from the restored archive;
|
|
519
|
-
* the instance stays where it is.
|
|
583
|
+
* the instance stays where it is. For a `single: 'desktop'` kind,
|
|
584
|
+
* anywhere else on the tile's desktop. */
|
|
520
585
|
_swapToPage(tree, leafId, target, currentTopNav, targetTopNav, opts) {
|
|
521
586
|
const done = tree.swapToPage(leafId, target, currentTopNav, targetTopNav, opts);
|
|
522
587
|
if (done) this._dropRestoredCopies(tree, leafId, targetTopNav, target);
|
|
@@ -527,11 +592,12 @@ export class WindowManager {
|
|
|
527
592
|
const leaf = tree.get(leafId);
|
|
528
593
|
const tabs = Array.isArray(leaf?.tabs) ? leaf.tabs : null;
|
|
529
594
|
if (!tabs) return;
|
|
595
|
+
const desktopIdx = this.desktops.desktops.findIndex((d) => d.tree === tree);
|
|
530
596
|
const elsewhere = this._liveInstanceKeys(tree, leafId);
|
|
531
597
|
const keeper = () => {
|
|
532
598
|
const seen = new Set();
|
|
533
599
|
return (t) => {
|
|
534
|
-
const k =
|
|
600
|
+
const k = this._scopedKey(t, desktopIdx);
|
|
535
601
|
if (k == null) return true;
|
|
536
602
|
if (elsewhere.has(k) || seen.has(k)) return false;
|
|
537
603
|
seen.add(k);
|
|
@@ -569,16 +635,23 @@ export class WindowManager {
|
|
|
569
635
|
tree.setActiveLeafTab(leafId, Math.max(0, Math.min(tabs.length - 1, active)));
|
|
570
636
|
}
|
|
571
637
|
|
|
572
|
-
/** The
|
|
573
|
-
* desktop but `leafId` of `tree
|
|
638
|
+
/** The scoped keys (`_scopedKey`, for `tree`'s desktop) of every LIVE
|
|
639
|
+
* tab — windows, and every leaf of every desktop but `leafId` of `tree`;
|
|
640
|
+
* a `single: 'desktop'` tab counts only when it is on `tree`'s desktop.
|
|
641
|
+
* Archived pages are not live. */
|
|
574
642
|
_liveInstanceKeys(tree, leafId) {
|
|
643
|
+
const here = this.desktops.desktops.findIndex((d) => d.tree === tree);
|
|
575
644
|
const keys = new Set();
|
|
576
|
-
const
|
|
577
|
-
|
|
645
|
+
const adder = (onHere) => (t) => {
|
|
646
|
+
if (!onHere && t && this.taxonomy.isSinglePerDesktop(t.kind)) return;
|
|
647
|
+
const k = this._scopedKey(t, here);
|
|
578
648
|
if (k != null) keys.add(k);
|
|
579
649
|
};
|
|
580
|
-
for (const [, rec] of this._windowToLeaf)
|
|
650
|
+
for (const [, rec] of this._windowToLeaf) {
|
|
651
|
+
_windowTabSpecs(rec).forEach(adder(rec.desktopIdx === here));
|
|
652
|
+
}
|
|
581
653
|
for (const d of this.desktops.desktops) {
|
|
654
|
+
const add = adder(d.tree === tree);
|
|
582
655
|
for (const leaf of d.tree.leaves()) {
|
|
583
656
|
if (d.tree === tree && leaf.id === leafId) continue;
|
|
584
657
|
(leaf.tabs || []).forEach(add);
|
|
@@ -618,14 +691,15 @@ export class WindowManager {
|
|
|
618
691
|
return dropped;
|
|
619
692
|
}
|
|
620
693
|
|
|
621
|
-
/** A restored layout keeps the FIRST instance of each single kind
|
|
622
|
-
*
|
|
623
|
-
*
|
|
624
|
-
*
|
|
694
|
+
/** A restored layout keeps the FIRST instance of each single kind (of a
|
|
695
|
+
* `single: 'desktop'` kind, on each desktop): later live tabs are
|
|
696
|
+
* dropped (the tile shows the tab that took the dropped one's place, or
|
|
697
|
+
* closes when it held nothing else), and so are later archived ones.
|
|
698
|
+
* Answers whether anything was dropped. */
|
|
625
699
|
_dropDuplicateSingles() {
|
|
626
700
|
const seen = new Set();
|
|
627
|
-
return this._pruneRestoredTabs((t) => {
|
|
628
|
-
const k =
|
|
701
|
+
return this._pruneRestoredTabs((t, desktopIdx) => {
|
|
702
|
+
const k = this._scopedKey(t, desktopIdx);
|
|
629
703
|
if (k == null) return true;
|
|
630
704
|
if (seen.has(k)) return false;
|
|
631
705
|
seen.add(k);
|
|
@@ -633,19 +707,20 @@ export class WindowManager {
|
|
|
633
707
|
});
|
|
634
708
|
}
|
|
635
709
|
|
|
636
|
-
/** Drop every tab of the restored layout `keep(tab)` refuses,
|
|
637
|
-
* tiles and in their archived pages; a tile left empty closes
|
|
638
|
-
* content tile if none is left). Returns whether anything
|
|
710
|
+
/** Drop every tab of the restored layout `keep(tab, desktopIdx)` refuses,
|
|
711
|
+
* in the tiles and in their archived pages; a tile left empty closes
|
|
712
|
+
* (seeding a content tile if none is left). Returns whether anything
|
|
713
|
+
* went. */
|
|
639
714
|
_pruneRestoredTabs(keep) {
|
|
640
715
|
let dropped = false;
|
|
641
|
-
for (const d of this.desktops.desktops) {
|
|
716
|
+
for (const [desktopIdx, d] of this.desktops.desktops.entries()) {
|
|
642
717
|
const tree = d.tree;
|
|
643
718
|
for (const leaf of tree.leaves()) {
|
|
644
719
|
const tabs = Array.isArray(leaf.tabs) ? leaf.tabs : [];
|
|
645
720
|
let active = leaf.activeTabIdx || 0;
|
|
646
721
|
let changed = false;
|
|
647
722
|
for (let i = 0; i < tabs.length;) {
|
|
648
|
-
if (keep(tabs[i])) { i++; continue; }
|
|
723
|
+
if (keep(tabs[i], desktopIdx)) { i++; continue; }
|
|
649
724
|
tabs.splice(i, 1);
|
|
650
725
|
if (i < active) active -= 1;
|
|
651
726
|
changed = true;
|
|
@@ -664,10 +739,10 @@ export class WindowManager {
|
|
|
664
739
|
}
|
|
665
740
|
}
|
|
666
741
|
}
|
|
667
|
-
for (const d of this.desktops.desktops) {
|
|
742
|
+
for (const [desktopIdx, d] of this.desktops.desktops.entries()) {
|
|
668
743
|
for (const leaf of d.tree.leaves()) {
|
|
669
744
|
for (const [page, arc] of this._archivedPages(leaf)) {
|
|
670
|
-
const kept = arc.tabs.filter(keep);
|
|
745
|
+
const kept = arc.tabs.filter((t) => keep(t, desktopIdx));
|
|
671
746
|
if (kept.length === arc.tabs.length) continue;
|
|
672
747
|
dropped = true;
|
|
673
748
|
if (!kept.length) { delete leaf.pageTabs[page]; continue; }
|
|
@@ -1096,9 +1171,15 @@ export class WindowManager {
|
|
|
1096
1171
|
* and updates the window's title. */
|
|
1097
1172
|
openInWindow(winId, kind, props = {}) {
|
|
1098
1173
|
if (this._openWindowOnly(kind, props)) return;
|
|
1099
|
-
if (this.revealInstance(kind, props)) return;
|
|
1100
1174
|
const rec = this._windowToLeaf.get(winId);
|
|
1101
1175
|
if (!rec) { this.openInPrimary(kind, props); return; }
|
|
1176
|
+
// A window is on its own desktop, even a free-floating one drawn over
|
|
1177
|
+
// every desktop: a `single: 'desktop'` kind navigated to in it is
|
|
1178
|
+
// looked for on the window's desktop, not the one on screen.
|
|
1179
|
+
const loc = this.taxonomy.isSinglePerDesktop(kind)
|
|
1180
|
+
? this._findInstance(kind, props, rec.desktopIdx)
|
|
1181
|
+
: this.findInstance(kind, props);
|
|
1182
|
+
if (this._revealFound(loc, kind, props)) return;
|
|
1102
1183
|
try { rec.mountInfo?.destroy?.(); } catch {}
|
|
1103
1184
|
// R8. THE BODY, NOT THE CONTENT ELEMENT. A floated pane's window holds
|
|
1104
1185
|
// a tab strip above its body, and both are children of `contentEl` —
|
|
@@ -3149,6 +3230,11 @@ export class WindowManager {
|
|
|
3149
3230
|
// The window's home follows the drop. Without this the dock is applied
|
|
3150
3231
|
// to the tree of the desktop the window was PROMOTED from, and the tile
|
|
3151
3232
|
// the user aimed at is on the one they are looking at.
|
|
3233
|
+
// A free-floating window is drawn over every desktop, so a drop can
|
|
3234
|
+
// carry it to another one: refused, as the move verbs are, when that
|
|
3235
|
+
// desktop holds its `single: 'desktop'` kind already.
|
|
3236
|
+
if (typeof desktopIdx === 'number' && desktopIdx !== record.desktopIdx
|
|
3237
|
+
&& this.desktopMoveRefusal({ windowId: winId }, desktopIdx)) return false;
|
|
3152
3238
|
if (typeof desktopIdx === 'number') record.desktopIdx = desktopIdx;
|
|
3153
3239
|
|
|
3154
3240
|
// THE DRAG ALREADY ANSWERED, so there is nothing to ask — the probe
|
|
@@ -3236,6 +3322,8 @@ export class WindowManager {
|
|
|
3236
3322
|
if (rec.desktopIdx === targetIdx) return;
|
|
3237
3323
|
// C40. A fixed desktop is the embedder's, and nothing is moved onto it.
|
|
3238
3324
|
if (this.desktops.desktops[targetIdx]?.fixed) return;
|
|
3325
|
+
// A desktop holds a `single: 'desktop'` kind once.
|
|
3326
|
+
if (this.desktopMoveRefusal({ windowId }, targetIdx)) return;
|
|
3239
3327
|
this.desktops.ensureCount(targetIdx + 1);
|
|
3240
3328
|
rec.desktopIdx = targetIdx;
|
|
3241
3329
|
if (!rec.homeContainer && rec.homeLeafId) {
|
|
@@ -3411,9 +3499,11 @@ export class WindowManager {
|
|
|
3411
3499
|
if (targets.length) {
|
|
3412
3500
|
items.push({ separator: true });
|
|
3413
3501
|
for (const [i, d] of targets) {
|
|
3502
|
+
const refusal = this.desktopMoveRefusal({ windowId: winId }, i);
|
|
3414
3503
|
items.push({
|
|
3415
3504
|
label: `Move to desktop ${d.label}`, icon: 'sweep',
|
|
3416
|
-
action: `move:${i}`,
|
|
3505
|
+
action: `move:${i}`, disabled: !!refusal,
|
|
3506
|
+
title: refusal || undefined,
|
|
3417
3507
|
});
|
|
3418
3508
|
}
|
|
3419
3509
|
}
|
|
@@ -3988,7 +4078,9 @@ export class WindowManager {
|
|
|
3988
4078
|
* ruling is that a tile operation may move a window and never destroy it,
|
|
3989
4079
|
* and removing a desktop is the largest tile operation there is: they come
|
|
3990
4080
|
* across to the desktop that ends up active, re-homed onto its ground by
|
|
3991
|
-
* `_rehomeContainedWindows` on the render below.
|
|
4081
|
+
* `_rehomeContainedWindows` on the render below. One holding a
|
|
4082
|
+
* `single: 'desktop'` kind the active desktop already has makes it hold
|
|
4083
|
+
* the kind twice, until a reload keeps the first.
|
|
3992
4084
|
*/
|
|
3993
4085
|
removeDesktop(idx) {
|
|
3994
4086
|
const m = this.desktops;
|
|
@@ -4066,6 +4158,9 @@ export class WindowManager {
|
|
|
4066
4158
|
// Already there. Without this, `tree.close` below removes the tile from
|
|
4067
4159
|
// the same tree its tabs were just appended to, and it is gone.
|
|
4068
4160
|
if (idx === this.desktops.activeIdx) return;
|
|
4161
|
+
// A desktop holds a `single: 'desktop'` kind once: a pane carrying one
|
|
4162
|
+
// the target already has stays where it is.
|
|
4163
|
+
if (this.desktopMoveRefusal({ leafId: focused.id }, idx)) return;
|
|
4069
4164
|
this.desktops.ensureCount(idx + 1);
|
|
4070
4165
|
const target = this.desktops.desktops[idx];
|
|
4071
4166
|
// C40. THE PANE'S TABS JOIN THE TARGET'S PRIMARY LEAF, ALL OF THEM. It
|
package/src/tiling/zoom.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/ui/base/page_base.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -62,6 +62,15 @@
|
|
|
62
62
|
* shortcut, context, action }`), so the palette and the bar list identical
|
|
63
63
|
* verbs.
|
|
64
64
|
*
|
|
65
|
+
* ── Two styles: a bar, or one hamburger button (STORY-0133) ───────────
|
|
66
|
+
* `style: 'bar'` (the default) draws a title per menu: File Edit Help.
|
|
67
|
+
* `style: 'hamburger'` draws ONE icon button (`icon`, 'menu' by default,
|
|
68
|
+
* named by `label`) whose menu lists the menus, each flying out its items:
|
|
69
|
+
* ☰ › File › Open…. It is the same model, so `commands()`, `setEnabled`,
|
|
70
|
+
* `setChecked` and `onAction` do not change; `open(menuId)` opens the
|
|
71
|
+
* hamburger with that menu flown out. `setStyle(style)` switches at run
|
|
72
|
+
* time (a setting, a narrow window), closing whatever is open.
|
|
73
|
+
*
|
|
65
74
|
* ── Roles ─────────────────────────────────────────────────────────────
|
|
66
75
|
* The WAI-ARIA menubar pattern: `role="menubar"` on the element you pass,
|
|
67
76
|
* a `role="menuitem"` title per menu inside a `role="none"` wrapper, each
|
|
@@ -77,6 +86,9 @@
|
|
|
77
86
|
const SEL_TITLE = '.twm-menubar__title';
|
|
78
87
|
const SEL_ITEM = '.twm-menubar__item';
|
|
79
88
|
const SEL_MENU = '.twm-menubar__menu';
|
|
89
|
+
const STYLES = ['bar', 'hamburger'];
|
|
90
|
+
/** The hamburger's own model ids. A menu's row in it is `${HAMBURGER}:${menu.id}`. */
|
|
91
|
+
const HAMBURGER = 'twm-menubar:hamburger';
|
|
80
92
|
|
|
81
93
|
/** @returns {MenuBar} */
|
|
82
94
|
export function createMenuBar(el, opts) {
|
|
@@ -91,16 +103,22 @@ export class MenuBar {
|
|
|
91
103
|
* @param {Function} [opts.onAction] `(id, { item, checked }) => void`
|
|
92
104
|
* @param {string} [opts.label] the bar's accessible name ('Menu')
|
|
93
105
|
* @param {string} [opts.emptyLabel] the disabled row an empty submenu shows ('Empty')
|
|
106
|
+
* @param {string} [opts.style] 'bar' (a title per menu, the default) or 'hamburger' (one button)
|
|
107
|
+
* @param {string} [opts.icon] the hamburger's Material Symbols icon ('menu')
|
|
94
108
|
*/
|
|
95
|
-
constructor(el, { menus, onAction = null, label = 'Menu', emptyLabel = 'Empty' } = {}) {
|
|
109
|
+
constructor(el, { menus, onAction = null, label = 'Menu', emptyLabel = 'Empty', style = 'bar', icon = 'menu' } = {}) {
|
|
96
110
|
if (!el || typeof el.appendChild !== 'function') {
|
|
97
111
|
throw new TypeError('createMenuBar: `el` must be an element');
|
|
98
112
|
}
|
|
99
113
|
_validateMenus(menus);
|
|
114
|
+
_validateStyle(style);
|
|
100
115
|
this.el = el;
|
|
101
116
|
this.menus = menus;
|
|
102
117
|
this._onAction = typeof onAction === 'function' ? onAction : null;
|
|
103
118
|
this._emptyLabel = emptyLabel;
|
|
119
|
+
this._label = label;
|
|
120
|
+
this._icon = icon;
|
|
121
|
+
this._style = style;
|
|
104
122
|
this._doc = el.ownerDocument;
|
|
105
123
|
this._disabled = new Map(); // id -> boolean (override)
|
|
106
124
|
this._checked = new Map(); // id -> boolean (override, check items)
|
|
@@ -112,26 +130,10 @@ export class MenuBar {
|
|
|
112
130
|
this._returnTo = null; // what had focus before the bar did
|
|
113
131
|
this._altArmed = false;
|
|
114
132
|
|
|
115
|
-
el.innerHTML = '';
|
|
116
133
|
el.classList.add('twm-menubar');
|
|
117
134
|
el.setAttribute('role', 'menubar');
|
|
118
135
|
el.setAttribute('aria-label', label);
|
|
119
|
-
this.
|
|
120
|
-
const entry = this._doc.createElement('div');
|
|
121
|
-
entry.className = 'twm-menubar__entry';
|
|
122
|
-
entry.setAttribute('role', 'none');
|
|
123
|
-
const btn = this._doc.createElement('button');
|
|
124
|
-
btn.type = 'button';
|
|
125
|
-
btn.className = 'twm-menubar__title';
|
|
126
|
-
btn.setAttribute('role', 'menuitem');
|
|
127
|
-
btn.setAttribute('aria-haspopup', 'menu');
|
|
128
|
-
btn.setAttribute('aria-expanded', 'false');
|
|
129
|
-
btn.tabIndex = i === 0 ? 0 : -1;
|
|
130
|
-
btn.textContent = m.label;
|
|
131
|
-
entry.appendChild(btn);
|
|
132
|
-
el.appendChild(entry);
|
|
133
|
-
return btn;
|
|
134
|
-
});
|
|
136
|
+
this._renderTitles();
|
|
135
137
|
|
|
136
138
|
this._h = {
|
|
137
139
|
mousedown: (e) => this._onMouseDown(e),
|
|
@@ -163,12 +165,35 @@ export class MenuBar {
|
|
|
163
165
|
/** Whether a menu is open. */
|
|
164
166
|
isOpen() { return this._openIdx >= 0; }
|
|
165
167
|
|
|
166
|
-
/** Open the menu with this id (focus on its title, or on its first item).
|
|
168
|
+
/** Open the menu with this id (focus on its title, or on its first item).
|
|
169
|
+
* A hamburger opens with that menu flown out (focus on its row, or on
|
|
170
|
+
* the menu's first item). */
|
|
167
171
|
open(menuId, { focusItem = false } = {}) {
|
|
168
172
|
const i = this.menus.findIndex((m) => m.id === menuId);
|
|
169
173
|
if (i < 0) return;
|
|
170
174
|
this._enter();
|
|
171
|
-
this._open(i, focusItem ? 'first' : null);
|
|
175
|
+
if (this._style !== 'hamburger') { this._open(i, focusItem ? 'first' : null); return; }
|
|
176
|
+
this._open(0, null);
|
|
177
|
+
const row = this._itemsIn(this._stack[0].menu)
|
|
178
|
+
.find((b) => this._models.get(b)?.id === `${HAMBURGER}:${menuId}`);
|
|
179
|
+
if (!row) return;
|
|
180
|
+
row.focus({ preventScroll: true });
|
|
181
|
+
this._openSub(row, focusItem ? 'first' : null);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** 'bar' or 'hamburger'. */
|
|
185
|
+
get style() { return this._style; }
|
|
186
|
+
|
|
187
|
+
/** Draw the menus as a bar or as one hamburger button. Whatever is open
|
|
188
|
+
* closes; the model and every setEnabled / setChecked stay. */
|
|
189
|
+
setStyle(style) {
|
|
190
|
+
_validateStyle(style);
|
|
191
|
+
if (style === this._style) return;
|
|
192
|
+
const hadFocus = this.el.contains(this._doc.activeElement);
|
|
193
|
+
this._deactivate();
|
|
194
|
+
this._style = style;
|
|
195
|
+
this._renderTitles();
|
|
196
|
+
if (hadFocus) this._focusTitle(0);
|
|
172
197
|
}
|
|
173
198
|
|
|
174
199
|
/** Close every open menu. Focus stays in the bar if it was there. */
|
|
@@ -239,7 +264,7 @@ export class MenuBar {
|
|
|
239
264
|
this._doc.removeEventListener('mousedown', this._h.docMousedown, true);
|
|
240
265
|
this._doc.defaultView?.removeEventListener('blur', this._h.winBlur);
|
|
241
266
|
this.el.innerHTML = '';
|
|
242
|
-
this.el.classList.remove('twm-menubar');
|
|
267
|
+
this.el.classList.remove('twm-menubar', 'twm-menubar--hamburger');
|
|
243
268
|
this.el.removeAttribute('role');
|
|
244
269
|
this.el.removeAttribute('aria-label');
|
|
245
270
|
}
|
|
@@ -317,6 +342,50 @@ export class MenuBar {
|
|
|
317
342
|
}
|
|
318
343
|
}
|
|
319
344
|
|
|
345
|
+
// ── titles ────────────────────────────────────────────────────────
|
|
346
|
+
|
|
347
|
+
/** The titles for the current style. `_top` is what they open: the
|
|
348
|
+
* menus themselves, or the hamburger's one menu that lists them. */
|
|
349
|
+
_renderTitles() {
|
|
350
|
+
const d = this._doc;
|
|
351
|
+
const burger = this._style === 'hamburger';
|
|
352
|
+
this.el.innerHTML = '';
|
|
353
|
+
this.el.classList.toggle('twm-menubar--hamburger', burger);
|
|
354
|
+
this._top = burger
|
|
355
|
+
? [{ id: HAMBURGER, label: this._label, items: this.menus.map((m) => ({
|
|
356
|
+
id: `${HAMBURGER}:${m.id}`, label: m.label, submenu: m.items,
|
|
357
|
+
})) }]
|
|
358
|
+
: this.menus;
|
|
359
|
+
this._titles = this._top.map((m, i) => {
|
|
360
|
+
const entry = d.createElement('div');
|
|
361
|
+
entry.className = 'twm-menubar__entry';
|
|
362
|
+
entry.setAttribute('role', 'none');
|
|
363
|
+
const btn = d.createElement('button');
|
|
364
|
+
btn.type = 'button';
|
|
365
|
+
btn.className = 'twm-menubar__title';
|
|
366
|
+
btn.setAttribute('role', 'menuitem');
|
|
367
|
+
btn.setAttribute('aria-haspopup', 'menu');
|
|
368
|
+
btn.setAttribute('aria-expanded', 'false');
|
|
369
|
+
btn.tabIndex = i === 0 ? 0 : -1;
|
|
370
|
+
if (burger) {
|
|
371
|
+
btn.classList.add('twm-menubar__title--icon', 'twm-has-tooltip');
|
|
372
|
+
btn.setAttribute('aria-label', m.label);
|
|
373
|
+
btn.dataset.tooltip = m.label;
|
|
374
|
+
btn.dataset.tooltipPlacement = 'bottom';
|
|
375
|
+
const icon = d.createElement('span');
|
|
376
|
+
icon.className = 'material-symbols-outlined';
|
|
377
|
+
icon.setAttribute('aria-hidden', 'true');
|
|
378
|
+
icon.textContent = this._icon;
|
|
379
|
+
btn.appendChild(icon);
|
|
380
|
+
} else {
|
|
381
|
+
btn.textContent = m.label;
|
|
382
|
+
}
|
|
383
|
+
entry.appendChild(btn);
|
|
384
|
+
this.el.appendChild(entry);
|
|
385
|
+
return btn;
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
|
|
320
389
|
// ── open / close ──────────────────────────────────────────────────
|
|
321
390
|
|
|
322
391
|
_setRoving(i) {
|
|
@@ -359,7 +428,7 @@ export class MenuBar {
|
|
|
359
428
|
// being removed never holds focus.
|
|
360
429
|
this._focusTitle(i);
|
|
361
430
|
this._closeAll();
|
|
362
|
-
const m = this.
|
|
431
|
+
const m = this._top[i];
|
|
363
432
|
const title = this._titles[i];
|
|
364
433
|
const menu = this._buildMenu(m.items, m.label);
|
|
365
434
|
title.parentElement.appendChild(menu);
|
|
@@ -596,6 +665,8 @@ export class MenuBar {
|
|
|
596
665
|
_titleKey(key, i) {
|
|
597
666
|
const n = this._titles.length;
|
|
598
667
|
const open = this._openIdx >= 0;
|
|
668
|
+
// One title (a hamburger): there is no next or previous menu.
|
|
669
|
+
if (n === 1 && (key === 'ArrowRight' || key === 'ArrowLeft' || key === 'Home' || key === 'End')) return true;
|
|
599
670
|
switch (key) {
|
|
600
671
|
case 'ArrowRight': open ? this._open(i + 1, null) : this._focusTitle((i + 1) % n); return true;
|
|
601
672
|
case 'ArrowLeft': open ? this._open(i - 1, null) : this._focusTitle((i - 1 + n) % n); return true;
|
|
@@ -642,11 +713,11 @@ export class MenuBar {
|
|
|
642
713
|
case 'End': this._focusIn(menu, 'last'); return true;
|
|
643
714
|
case 'ArrowRight':
|
|
644
715
|
if (item?.submenu != null && !this._isDisabled(item)) this._openSub(btn, 'first');
|
|
645
|
-
else this._open(this._openIdx + 1, 'first');
|
|
716
|
+
else if (this._titles.length > 1) this._open(this._openIdx + 1, 'first');
|
|
646
717
|
return true;
|
|
647
718
|
case 'ArrowLeft':
|
|
648
719
|
if (level > 0) this._closeFrom(level);
|
|
649
|
-
else this._open(this._openIdx - 1, 'first');
|
|
720
|
+
else if (this._titles.length > 1) this._open(this._openIdx - 1, 'first');
|
|
650
721
|
return true;
|
|
651
722
|
case 'Enter': case ' ':
|
|
652
723
|
this._activate(btn);
|
|
@@ -706,6 +777,12 @@ function _kind(item) {
|
|
|
706
777
|
return 'menuitem';
|
|
707
778
|
}
|
|
708
779
|
|
|
780
|
+
function _validateStyle(style) {
|
|
781
|
+
if (!STYLES.includes(style)) {
|
|
782
|
+
throw new TypeError(`createMenuBar: \`style\` must be ${STYLES.map((s) => `'${s}'`).join(' or ')}`);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
709
786
|
/** A bad model is a boot error, not a menu that silently lacks a row. */
|
|
710
787
|
function _validateMenus(menus) {
|
|
711
788
|
if (!Array.isArray(menus) || !menus.length) {
|
|
@@ -21,7 +21,11 @@
|
|
|
21
21
|
* alertsTab the tab a click asks for, `bottompanel:show { tab }`; 'alerts'.
|
|
22
22
|
* onOpen a click, instead of the bus event — for an embedder with no bus,
|
|
23
23
|
* or whose panel must be opened before a tab can be chosen.
|
|
24
|
+
*
|
|
25
|
+
* A right-click on an entry opens the context menu with "Copy message"
|
|
26
|
+
* (STORY-0131), the same as on its toast; the popover stays open meanwhile.
|
|
24
27
|
*/
|
|
28
|
+
import { showNotificationMenu } from '../notification_center.js';
|
|
25
29
|
|
|
26
30
|
export class NotificationHistory {
|
|
27
31
|
constructor({ notificationCenter, eventBus, logger, mountPoint = null, alertLog = null,
|
|
@@ -209,10 +213,15 @@ export class NotificationHistory {
|
|
|
209
213
|
};
|
|
210
214
|
this._popoverCleanup = cleanup;
|
|
211
215
|
|
|
216
|
+
// The entries' context menu is outside the panel, and using it is not leaving it.
|
|
212
217
|
const onDocDown = (ev) => {
|
|
218
|
+
if (ev.target.closest?.('.twm-context-menu')) return;
|
|
213
219
|
if (!panel.contains(ev.target) && !this.triggerEl.contains(ev.target)) cleanup();
|
|
214
220
|
};
|
|
215
|
-
|
|
221
|
+
// Escape closes the innermost layer: an open entry menu closes itself.
|
|
222
|
+
const onKey = (ev) => {
|
|
223
|
+
if (ev.key === 'Escape' && !document.querySelector('.twm-context-menu')) cleanup();
|
|
224
|
+
};
|
|
216
225
|
const onResize = () => cleanup();
|
|
217
226
|
|
|
218
227
|
document.addEventListener('mousedown', onDocDown, true);
|
|
@@ -261,6 +270,7 @@ export class NotificationHistory {
|
|
|
261
270
|
body.className = 'twm-notification-history__item-body';
|
|
262
271
|
body.textContent = entry.message || '';
|
|
263
272
|
item.appendChild(body);
|
|
273
|
+
item.addEventListener('contextmenu', (event) => showNotificationMenu(event, entry, this.logger));
|
|
264
274
|
|
|
265
275
|
fragment.appendChild(item);
|
|
266
276
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|