@jbrowse/app-core 5.0.0-beta.11 → 5.0.0-beta.14

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.
Files changed (78) hide show
  1. package/esm/AppSession/AppSessionMixin.d.ts +110 -92
  2. package/esm/Assemblies/AssembliesMixin.d.ts +30 -0
  3. package/esm/HistoryManagement/index.d.ts +2 -0
  4. package/esm/HistoryManagement/index.js +2 -0
  5. package/esm/JBrowseConfig/RootConfiguration.d.ts +4 -10
  6. package/esm/JBrowseConfig/RootConfiguration.js +5 -8
  7. package/esm/JBrowseConfig/index.d.ts +3 -11
  8. package/esm/JBrowseConfig/index.js +2 -3
  9. package/esm/JBrowseModel/index.d.ts +10 -9
  10. package/esm/JBrowseModel/index.js +8 -7
  11. package/esm/JbApi/jbApi.d.ts +2 -2
  12. package/esm/JbApi/jbApi.js +16 -11
  13. package/esm/RootMenu/index.d.ts +2 -0
  14. package/esm/RootMenu/index.js +2 -0
  15. package/esm/SessionSpec/index.d.ts +2 -2
  16. package/esm/SessionSpec/index.js +1 -1
  17. package/esm/SessionSpec/loadSessionSpec.js +2 -12
  18. package/esm/SessionSpec/parseSessionSpecUrl.d.ts +19 -0
  19. package/esm/SessionSpec/parseSessionSpecUrl.js +29 -0
  20. package/esm/WorkspaceLayout/LayoutRenderer.d.ts +0 -10
  21. package/esm/WorkspaceLayout/LayoutRenderer.js +34 -101
  22. package/esm/WorkspaceLayout/PanelView.js +19 -45
  23. package/esm/WorkspaceLayout/TabStrip.d.ts +2 -2
  24. package/esm/WorkspaceLayout/TabStrip.js +36 -104
  25. package/esm/WorkspaceLayout/WorkspaceContainer.d.ts +0 -9
  26. package/esm/WorkspaceLayout/WorkspaceContainer.js +9 -42
  27. package/esm/WorkspaceLayout/WorkspacePanelActions.d.ts +1 -16
  28. package/esm/WorkspaceLayout/WorkspacePanelActions.js +46 -51
  29. package/esm/WorkspaceLayout/WorkspaceTab.d.ts +3 -19
  30. package/esm/WorkspaceLayout/WorkspaceTab.js +12 -48
  31. package/esm/WorkspaceLayout/dropZone.d.ts +7 -37
  32. package/esm/WorkspaceLayout/dropZone.js +7 -44
  33. package/esm/WorkspaceLayout/model.d.ts +45 -120
  34. package/esm/WorkspaceLayout/model.js +145 -303
  35. package/esm/WorkspaceLayout/panelChrome.d.ts +4 -7
  36. package/esm/WorkspaceLayout/panelChrome.js +0 -2
  37. package/esm/WorkspaceLayout/spec.d.ts +10 -51
  38. package/esm/WorkspaceLayout/spec.js +12 -80
  39. package/esm/WorkspaceLayout/splitter.d.ts +3 -19
  40. package/esm/WorkspaceLayout/splitter.js +5 -21
  41. package/esm/WorkspaceLayout/tabName.d.ts +8 -0
  42. package/esm/WorkspaceLayout/tabName.js +14 -0
  43. package/esm/WorkspaceLayout/tree.d.ts +19 -79
  44. package/esm/WorkspaceLayout/tree.js +34 -131
  45. package/esm/WorkspaceLayout/useLayoutDrag.d.ts +4 -22
  46. package/esm/WorkspaceLayout/useLayoutDrag.js +55 -177
  47. package/esm/WorkspaceLayout/usePointerGesture.d.ts +24 -0
  48. package/esm/WorkspaceLayout/usePointerGesture.js +83 -0
  49. package/esm/WorkspaceLayout/workspaceTheme.d.ts +27 -0
  50. package/esm/WorkspaceLayout/workspaceTheme.js +37 -0
  51. package/esm/index.d.ts +5 -3
  52. package/esm/index.js +3 -2
  53. package/esm/queryParams.d.ts +2 -0
  54. package/esm/queryParams.js +16 -5
  55. package/esm/sessionSummary.d.ts +16 -0
  56. package/esm/sessionSummary.js +57 -0
  57. package/esm/ui/App/App.js +3 -0
  58. package/esm/ui/App/DrawerHeaderHelpDialog.d.ts +3 -2
  59. package/esm/ui/App/DrawerHeaderHelpDialog.js +4 -2
  60. package/esm/ui/App/JBrowseTabMenu.d.ts +3 -2
  61. package/esm/ui/App/JBrowseTabMenu.js +5 -3
  62. package/esm/ui/App/ThemeModeButton.d.ts +4 -5
  63. package/esm/ui/App/ThemeModeButton.js +22 -14
  64. package/esm/ui/App/ViewContainer.js +7 -1
  65. package/esm/ui/App/ViewMenu.js +69 -42
  66. package/esm/ui/App/ViewWrapper.js +2 -2
  67. package/esm/ui/App/ViewsContainer.js +3 -4
  68. package/esm/ui/App/lazyParts.d.ts +0 -5
  69. package/esm/ui/App/lazyParts.js +0 -1
  70. package/esm/ui/App/preloadAppShell.js +3 -6
  71. package/esm/ui/App/types.d.ts +3 -2
  72. package/esm/ui/App/viewTitle.d.ts +6 -1
  73. package/esm/ui/App/viewTitle.js +7 -2
  74. package/package.json +7 -7
  75. package/esm/WorkspaceLayout/dockviewTheme.d.ts +0 -69
  76. package/esm/WorkspaceLayout/dockviewTheme.js +0 -82
  77. package/esm/ui/App/ClassicViewsContainer.d.ts +0 -6
  78. package/esm/ui/App/ClassicViewsContainer.js +0 -22
@@ -1,7 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { Suspense, useCallback, useEffect, useMemo } from 'react';
2
+ import { Suspense, useCallback, useMemo } from 'react';
3
3
  import { makeStyles } from '@jbrowse/core/util/tss-react';
4
- import { autorun } from 'mobx';
5
4
  import { observer } from 'mobx-react';
6
5
  import ViewStack from '../ui/App/ViewStack.js';
7
6
  import { ViewLauncher } from '../ui/App/lazyParts.js';
@@ -17,11 +16,6 @@ const useStyles = makeStyles()(theme => ({
17
16
  gridRow: 'components',
18
17
  background: theme.palette.background.default,
19
18
  },
20
- stack: {
21
- display: 'flex',
22
- flexDirection: 'column',
23
- minWidth: 0,
24
- },
25
19
  empty: {
26
20
  display: 'flex',
27
21
  alignItems: 'center',
@@ -29,30 +23,11 @@ const useStyles = makeStyles()(theme => ({
29
23
  height: '100%',
30
24
  },
31
25
  }));
32
- /**
33
- * The workspace. Compare `TiledViewsContainer` + `useDockviewController`, which
34
- * this replaces: there is no api to hold, no `onReady`, no event to subscribe
35
- * to, and no reconciliation — the layout is session state and this renders it.
36
- *
37
- * The one reaction left is homing: `session.views` is owned by the session, so
38
- * a view launched from a menu arrives belonging to no tab and has to land
39
- * somewhere. That is one-directional and idempotent, and nothing reads back.
40
- */
41
26
  export const WorkspaceContainer = observer(function WorkspaceContainer({ session, }) {
42
27
  const { classes } = useStyles();
43
28
  const { drag, handlers } = useLayoutDrag(session);
44
- // #region autorunInEffect
45
- useEffect(() => autorun(() => {
46
- // reads session.views itself, so it re-runs when the view set changes;
47
- // homeUnassignedViews is an action and would not be tracked from inside
48
- session.homeUnassignedViews(session.views.map(v => v.id));
49
- }), [session]);
50
- // #endregion
51
- // The layout does not own views, so closing anything that holds them is
52
- // explicitly the pair. Stated ONCE, here, because every spelling of it is a
53
- // chance to drop the node and leave its views in the session forever — and
54
- // three gestures want it now: the tab's ⋮ menu, middle-clicking the tab, and
55
- // the cell's ×.
29
+ // the layout does not own views, so every close gesture goes through here
30
+ // or leaks them
56
31
  const closeViews = useCallback((viewIds) => {
57
32
  for (const view of viewsOf(session, viewIds)) {
58
33
  session.removeView(view);
@@ -74,33 +49,25 @@ export const WorkspaceContainer = observer(function WorkspaceContainer({ session
74
49
  closeViews(panel.tabs.flatMap(t => t.viewIds));
75
50
  session.closePanel(panelId);
76
51
  }, [session, closeViews]);
77
- /**
78
- * Memoised because this reaches every panel, so a fresh one per render
79
- * defeats `observer`'s memo for all of them and each re-render rebuilds a
80
- * `ViewStack`. `drag` is deliberately NOT in here — it goes down its own prop
81
- * so it reaches only the cell it describes.
82
- *
83
- * Nothing does this for us: `observer(function(){})` is not compiled by the
84
- * React Compiler.
85
- */
52
+ // memoised by hand (the React Compiler skips `observer`): a fresh object
53
+ // re-renders every panel's ViewStack. `drag` stays out for the same reason.
86
54
  const chrome = useMemo(() => ({
87
55
  dragHandlers: handlers,
88
56
  onTabClose: closeTab,
89
57
  renderPanelActions: panel => (_jsx(WorkspacePanelActions, { panel: panel, session: session, onClose: () => {
90
58
  closePanel(panel.id);
91
59
  } })),
92
- renderTabLabel: tab => (_jsx(WorkspaceTab, { tab: tab, views: viewsOf(session, tab.viewIds), session: session, layout: session, onClose: () => {
60
+ renderTabLabel: tab => (_jsx(WorkspaceTab, { tab: tab, views: viewsOf(session, tab.viewIds), session: session, onClose: () => {
93
61
  closeTab(tab.id);
94
62
  } })),
95
63
  renderTabContent: tab => {
96
64
  const views = viewsOf(session, tab.viewIds);
97
- return views.length > 0 ? (_jsx(ViewStack, { views: views, session: session, className: classes.stack })) : (_jsx("div", { className: classes.empty, children: _jsx(Suspense, { fallback: null, children: _jsx(ViewLauncher, { session: session }) }) }));
65
+ return views.length > 0 ? (_jsx(ViewStack, { views: views, session: session })) : (_jsx("div", { className: classes.empty, children: _jsx(Suspense, { fallback: null, children: _jsx(ViewLauncher, { session: session }) }) }));
98
66
  },
99
- }), [session, handlers, closeTab, closePanel, classes.stack, classes.empty]);
67
+ }), [session, handlers, closeTab, closePanel, classes.empty]);
100
68
  return (_jsx("div", { className: classes.container, "data-testid": "workspace", children: _jsx(LayoutRenderer, { node: session.visibleTree, layout: session, drag: drag, chrome: chrome }) }));
101
69
  });
102
- // `session.views` is the one ordering of views, in both layout modes, so a
103
- // tab's membership list is filtered through it rather than read as an order.
70
+ // `session.views` is the order; a tab's `viewIds` is membership only
104
71
  function viewsOf(session, viewIds) {
105
72
  const members = new Set(viewIds);
106
73
  return session.views.filter(v => members.has(v.id));
@@ -1,24 +1,9 @@
1
1
  import type { WorkspaceSessionType } from '../ui/App/types.ts';
2
2
  import type { WorkspaceLayout } from './model.ts';
3
3
  import type { PanelNode } from './tree.ts';
4
- /**
5
- * The per-cell buttons: new tab, split, close.
6
- *
7
- * Every one of these is a single MST action on the layout, where the dockview
8
- * versions were an api call plus a session write that had to be kept consistent
9
- * with it — `handleSplit` was `containerApi.addGroup(...)` followed by
10
- * `addEmptyTab(newGroup)`, and the gap between those two was where the
11
- * "resource is already disposed" crash lived.
12
- */
13
4
  export declare const WorkspacePanelActions: ({ panel, session, onClose, }: {
14
5
  panel: PanelNode;
15
6
  session: WorkspaceSessionType & WorkspaceLayout;
16
- /**
17
- * Closing a cell closes the views its tabs held, and the layout does not own
18
- * views — so the pair is one function at the call site, the same way
19
- * `WorkspaceTab` takes its `onClose`. Spelled here as well, this file would
20
- * be the third place stating "and also remove the views", which is one more
21
- * than the number that can be kept in step.
22
- */
7
+ /** closes the cell and the views its tabs hold */
23
8
  onClose: () => void;
24
9
  }) => import("react").JSX.Element;
@@ -14,34 +14,26 @@ import ViewColumnIcon from '@mui/icons-material/ViewColumn';
14
14
  import ViewModuleIcon from '@mui/icons-material/ViewModule';
15
15
  import { IconButton, Tooltip } from '@mui/material';
16
16
  import { observer } from 'mobx-react';
17
- import { dv } from './dockviewTheme.js';
17
+ import { stripIconColor, workspaceTheme } from './workspaceTheme.js';
18
18
  const useStyles = makeStyles()({
19
19
  actions: { display: 'flex', alignItems: 'center', flex: '0 0 auto' },
20
- // dockview's `.dv-default-tab-action`: inherits the strip's colour, and takes
21
- // a small rounded chip on hover rather than MUI's circular ripple surface
22
20
  button: {
23
21
  padding: 4,
24
22
  borderRadius: 2,
25
- color: dv.activeGroupHiddenTabColor,
26
- '&:hover': { background: dv.iconHoverBackground, borderRadius: 2 },
23
+ color: stripIconColor,
24
+ '&:hover': {
25
+ background: workspaceTheme.iconHoverBackground,
26
+ borderRadius: 2,
27
+ },
27
28
  },
28
29
  icon: { fontSize: 16 },
29
30
  });
30
- /**
31
- * The per-cell buttons: new tab, split, close.
32
- *
33
- * Every one of these is a single MST action on the layout, where the dockview
34
- * versions were an api call plus a session write that had to be kept consistent
35
- * with it — `handleSplit` was `containerApi.addGroup(...)` followed by
36
- * `addEmptyTab(newGroup)`, and the gap between those two was where the
37
- * "resource is already disposed" crash lived.
38
- */
39
31
  export const WorkspacePanelActions = observer(function WorkspacePanelActions({ panel, session, onClose, }) {
40
32
  const { classes } = useStyles();
41
33
  const canClose = session.panels.length > 1;
42
34
  const maximized = session.maximizedPanelId === panel.id;
43
35
  const tile = (mode) => {
44
- session.tileViews(mode, session.views.map(v => v.id));
36
+ session.tileViews(mode);
45
37
  };
46
38
  return (_jsxs("div", { className: classes.actions, children: [_jsx(CascadingMenuButton, { menuItems: [
47
39
  {
@@ -52,23 +44,21 @@ export const WorkspacePanelActions = observer(function WorkspacePanelActions({ p
52
44
  },
53
45
  },
54
46
  {
55
- label: 'New empty split horizontal',
47
+ label: 'New empty split (right)',
56
48
  icon: VerticalSplitIcon,
57
49
  onClick: () => {
58
50
  session.splitPanel(panel.id, 'row');
59
51
  },
60
52
  },
61
53
  {
62
- label: 'New empty split vertical',
54
+ label: 'New empty split (below)',
63
55
  icon: HorizontalSplitIcon,
64
56
  onClick: () => {
65
57
  session.splitPanel(panel.id, 'column');
66
58
  },
67
59
  },
68
- // The strip's double-click is the gesture; this is how anyone finds
69
- // it, and the only way to reach it from the keyboard. Gated on there
70
- // being another cell to hide, since maximizing the only one is a
71
- // no-op with a label promising otherwise.
60
+ // the keyboard route to the strip's double-click; hidden when there
61
+ // is no other cell to hide
72
62
  ...(session.panels.length > 1 || maximized
73
63
  ? [
74
64
  {
@@ -80,41 +70,46 @@ export const WorkspacePanelActions = observer(function WorkspacePanelActions({ p
80
70
  },
81
71
  ]
82
72
  : []),
83
- // The whole-workspace commands, kept behind the same "Global:" prefix
84
- // and the same >1-view gate they had on the dockview header, so a
85
- // returning user finds them where they were and the label still says
86
- // that the button in THIS cell rearranges every cell.
87
73
  ...(session.views.length > 1
88
74
  ? [
89
75
  {
90
- label: 'Global: change layout into set of tabs',
91
- icon: DynamicFeedIcon,
92
- onClick: () => {
93
- tile('tabs');
94
- },
95
- },
96
- {
97
- label: 'Global: tile horizontally',
98
- icon: ViewColumnIcon,
99
- onClick: () => {
100
- tile('horizontal');
101
- },
102
- },
103
- {
104
- label: 'Global: tile vertically',
105
- icon: TableRowsIcon,
106
- onClick: () => {
107
- tile('vertical');
108
- },
109
- },
110
- {
111
- label: 'Global: tile grid',
76
+ label: 'Arrange all views',
112
77
  icon: ViewModuleIcon,
113
- onClick: () => {
114
- tile('grid');
115
- },
78
+ type: 'subMenu',
79
+ subMenu: [
80
+ {
81
+ label: 'As tabs',
82
+ icon: DynamicFeedIcon,
83
+ onClick: () => {
84
+ tile('tabs');
85
+ },
86
+ },
87
+ {
88
+ label: 'Side by side',
89
+ icon: ViewColumnIcon,
90
+ onClick: () => {
91
+ tile('horizontal');
92
+ },
93
+ },
94
+ {
95
+ label: 'Stacked',
96
+ icon: TableRowsIcon,
97
+ onClick: () => {
98
+ tile('vertical');
99
+ },
100
+ },
101
+ {
102
+ label: 'Grid',
103
+ icon: ViewModuleIcon,
104
+ onClick: () => {
105
+ tile('grid');
106
+ },
107
+ },
108
+ ],
116
109
  },
117
110
  ]
118
111
  : []),
119
- ], size: "small", className: classes.button, children: _jsx(AddIcon, { className: classes.icon }) }), canClose ? (_jsx(Tooltip, { title: "Close panel", children: _jsx(IconButton, { size: "small", className: classes.button, onClick: onClose, children: _jsx(CloseIcon, { className: classes.icon }) }) })) : null] }));
112
+ ], tooltip: "Panel menu", size: "small", className: classes.button, children: _jsx(AddIcon, { className: classes.icon }) }), maximized ? (_jsx(Tooltip, { title: "Restore panel", children: _jsx(IconButton, { size: "small", className: classes.button, onClick: () => {
113
+ session.restorePanels();
114
+ }, children: _jsx(CloseFullscreenIcon, { className: classes.icon }) }) })) : null, canClose ? (_jsx(Tooltip, { title: "Close panel", children: _jsx(IconButton, { size: "small", className: classes.button, onClick: onClose, children: _jsx(CloseIcon, { className: classes.icon }) }) })) : null] }));
120
115
  });
@@ -2,26 +2,10 @@ import type { WorkspaceSessionType } from '../ui/App/types.ts';
2
2
  import type { WorkspaceLayout } from './model.ts';
3
3
  import type { TabNode } from './tree.ts';
4
4
  import type { AbstractViewModel } from '@jbrowse/core/util';
5
- /**
6
- * A tab's name is **derived** from the views it holds, unless the user set one.
7
- *
8
- * `title === undefined` is the sentinel for "not renamed", which is a plain
9
- * `maybe` on the tab rather than the comparison the dockview version needed:
10
- * dockview restores an unset title as the panel id, so telling a real title
11
- * from a restored one meant testing `title !== panelId` and hoping nobody named
12
- * a tab after a panel.
13
- */
14
- export declare function tabDisplayName(tab: TabNode, views: AbstractViewModel[], session: WorkspaceSessionType): string;
15
- export declare const WorkspaceTab: ({ tab, views, session, layout, onClose, }: {
5
+ export declare const WorkspaceTab: ({ tab, views, session, onClose, }: {
16
6
  tab: TabNode;
17
7
  views: AbstractViewModel[];
18
- session: WorkspaceSessionType;
19
- layout: WorkspaceLayout;
20
- /**
21
- * Closing a tab closes the views it held, and the layout does not own views —
22
- * so the pair is one function at the call site rather than spelled out here.
23
- * The strip's middle-click needs the same pair, and two spellings of "and
24
- * also remove the views" is how one of them comes to be missing it.
25
- */
8
+ session: WorkspaceSessionType & WorkspaceLayout;
9
+ /** closes the tab and the views it holds */
26
10
  onClose: () => void;
27
11
  }) => import("react").JSX.Element;
@@ -4,20 +4,15 @@ import { makeStyles } from '@jbrowse/core/util/tss-react';
4
4
  import { InputBase, Typography } from '@mui/material';
5
5
  import { observer } from 'mobx-react';
6
6
  import JBrowseTabMenu from '../ui/App/JBrowseTabMenu.js';
7
- import { viewName } from '../ui/App/viewTitle.js';
7
+ import { tabDisplayName } from './tabName.js';
8
8
  const useStyles = makeStyles()(theme => ({
9
- // Only the label and its menu. The background, the selected state and the
10
- // hover live on the strip's own `[role=tab]` wrapper, which is where the
11
- // selection is known — two places styling one tab is how they drift.
9
+ // the `[role=tab]` wrapper in TabStrip owns background and selected state
12
10
  tab: {
13
11
  display: 'flex',
14
12
  alignItems: 'center',
15
13
  gap: 4,
16
14
  minWidth: 0,
17
15
  },
18
- // dockview's `.dv-default-tab-content`: takes the space, truncates with an
19
- // ellipsis, and takes its colour from the tab, which is what encodes
20
- // active/inactive
21
16
  title: {
22
17
  flexGrow: 1,
23
18
  marginRight: 4,
@@ -36,38 +31,20 @@ const useStyles = makeStyles()(theme => ({
36
31
  flex: 1,
37
32
  },
38
33
  }));
39
- /**
40
- * A tab's name is **derived** from the views it holds, unless the user set one.
41
- *
42
- * `title === undefined` is the sentinel for "not renamed", which is a plain
43
- * `maybe` on the tab rather than the comparison the dockview version needed:
44
- * dockview restores an unset title as the panel id, so telling a real title
45
- * from a restored one meant testing `title !== panelId` and hoping nobody named
46
- * a tab after a panel.
47
- */
48
- export function tabDisplayName(tab, views, session) {
49
- if (tab.title) {
50
- return tab.title;
51
- }
52
- if (views.length === 0) {
53
- return 'Empty';
54
- }
55
- if (views.length === 1) {
56
- return viewName(views[0], r => session.assemblyManager.getDisplayName(r));
57
- }
58
- return `${views.length} views`;
59
- }
60
- export const WorkspaceTab = observer(function WorkspaceTab({ tab, views, session, layout, onClose, }) {
34
+ export const WorkspaceTab = observer(function WorkspaceTab({ tab, views, session, onClose, }) {
61
35
  const { classes } = useStyles();
62
36
  const [editing, setEditing] = useState(false);
63
37
  const [draft, setDraft] = useState('');
64
38
  const title = tabDisplayName(tab, views, session);
39
+ const startEditing = () => {
40
+ setDraft(title);
41
+ setEditing(true);
42
+ };
43
+ // an empty box or the unchanged automatic name keeps the name automatic
65
44
  const save = () => {
66
- // An empty box means "go back to the automatic name". `title === undefined`
67
- // is the sentinel for "not renamed" and `renameTab` takes it, but nothing
68
- // in the UI passed it: clearing the box just discarded the edit, so a
69
- // rename could be made and never unmade.
70
- layout.renameTab(tab.id, draft.trim() || undefined);
45
+ const name = draft.trim();
46
+ const automatic = tabDisplayName({ ...tab, title: undefined }, views, session);
47
+ session.renameTab(tab.id, name && name !== automatic ? name : undefined);
71
48
  setEditing(false);
72
49
  };
73
50
  return (_jsx("div", { className: classes.tab, children: editing ? (_jsx(InputBase, { autoFocus: true, className: classes.editInput, value: draft, onChange: e => {
@@ -75,13 +52,6 @@ export const WorkspaceTab = observer(function WorkspaceTab({ tab, views, session
75
52
  }, onFocus: e => {
76
53
  e.target.select();
77
54
  }, onBlur: save, onKeyDown: e => {
78
- // The box sits INSIDE the `role="tab"` that owns the strip's roving
79
- // tabindex, and every key it handles — the arrows, Home, End, Enter
80
- // and Space — it also preventDefault()s. All of them reach that
81
- // handler by bubbling out of here, so while the box is open the
82
- // arrows jumped to the next tab instead of moving the caret and a
83
- // space never reached the input at all: a tab could not be given a
84
- // name with a space in it.
85
55
  e.stopPropagation();
86
56
  if (e.key === 'Enter') {
87
57
  save();
@@ -95,11 +65,5 @@ export const WorkspaceTab = observer(function WorkspaceTab({ tab, views, session
95
65
  e.stopPropagation();
96
66
  }, onClick: e => {
97
67
  e.stopPropagation();
98
- } })) : (_jsxs(_Fragment, { children: [_jsx(Typography, { className: classes.title, variant: "body2", onDoubleClick: () => {
99
- setDraft(title);
100
- setEditing(true);
101
- }, children: title }), _jsx(JBrowseTabMenu, { onRename: () => {
102
- setDraft(title);
103
- setEditing(true);
104
- }, onClose: onClose })] })) }));
68
+ } })) : (_jsxs(_Fragment, { children: [_jsx(Typography, { className: classes.title, variant: "body2", onDoubleClick: startEditing, children: title }), _jsx(JBrowseTabMenu, { onRename: startEditing, onClose: onClose })] })) }));
105
69
  });
@@ -1,11 +1,4 @@
1
- /**
2
- * Where a pointer sitting over a panel would drop a view.
3
- *
4
- * Kept as geometry over a plain rect rather than reading the DOM, because this
5
- * is the part of drag-and-drop with actual decisions in it — the edge band, the
6
- * tie-break between two edges, what happens in the middle — and all of it is
7
- * checkable without rendering anything or synthesising a pointer.
8
- */
1
+ /** Drop geometry over plain rects, so it is testable without a DOM. */
9
2
  export type DropZone = 'center' | 'left' | 'right' | 'top' | 'bottom';
10
3
  export interface Rect {
11
4
  left: number;
@@ -13,11 +6,7 @@ export interface Rect {
13
6
  width: number;
14
7
  height: number;
15
8
  }
16
- /**
17
- * Where a drop would land. `zone` alone says which half of a cell; `strip` is
18
- * set when the pointer is on the tab strip itself, which is a finer answer than
19
- * `center` — that one appends, this one says where in the order.
20
- */
9
+ /** `strip` is set on the tab strip, and gives a position in the tab order. */
21
10
  export interface DropTarget {
22
11
  zone: DropZone;
23
12
  strip?: StripDrop;
@@ -30,33 +19,14 @@ export interface StripDrop {
30
19
  left: number;
31
20
  }
32
21
  /**
33
- * `center` means "add as a tab in this panel"; an edge means "split this panel
34
- * and put the view in the new half".
35
- *
36
- * The bands are proportional rather than a fixed pixel depth so a narrow panel
37
- * stays droppable — at a fixed 40px a 100px-wide panel would be all edge and
38
- * have no centre to drop a tab into. `edgeFraction` is capped below 0.5 for the
39
- * same reason: at 0.5 the centre vanishes entirely.
40
- *
41
- * Corners go to whichever edge the pointer is proportionally deeper into, so
42
- * the diagonal is the tie-break and neither axis silently wins. **This is a
43
- * deliberate divergence from dockview**, whose `calculateQuadrantAsPercentage`
44
- * tests left, right, top, bottom in that order and returns the first hit — so
45
- * in its top-left corner `left` always wins, however far into the top band the
46
- * pointer is. Its band is 20% where this defaults to 25%.
22
+ * `center` adds as a tab; an edge splits the panel. Bands are proportional and
23
+ * capped below half, so a narrow panel keeps a centre. A corner goes to the
24
+ * edge the pointer is proportionally deeper into.
47
25
  */
48
26
  export declare function dropZoneAt(rect: Rect, x: number, y: number, edgeFraction?: number): DropZone;
49
27
  /**
50
- * Which gap between tabs a pointer at `x` is nearest — the insertion index a
51
- * drop there would use, and the x to draw the caret at.
52
- *
53
- * The test is each tab's MIDPOINT rather than its edges, so every x belongs to
54
- * exactly one gap and there is no dead band between tabs where a drop would
55
- * have to fall back to appending. `rects` are in strip order and `x` is in
56
- * their coordinate space, whatever that is — this function does not know
57
- * whether it was handed viewport or panel-relative numbers.
58
- *
59
- * An empty strip is index 0, which is the only sensible place for a first tab.
28
+ * The gap between tabs nearest `x`, by tab midpoint so every x has exactly one.
29
+ * `x` is in the rects' coordinate space, whichever that is.
60
30
  */
61
31
  export declare function stripDropAt(rects: Rect[], x: number): StripDrop;
62
32
  /**
@@ -1,26 +1,8 @@
1
+ /** Drop geometry over plain rects, so it is testable without a DOM. */
1
2
  /**
2
- * Where a pointer sitting over a panel would drop a view.
3
- *
4
- * Kept as geometry over a plain rect rather than reading the DOM, because this
5
- * is the part of drag-and-drop with actual decisions in it — the edge band, the
6
- * tie-break between two edges, what happens in the middle — and all of it is
7
- * checkable without rendering anything or synthesising a pointer.
8
- */
9
- /**
10
- * `center` means "add as a tab in this panel"; an edge means "split this panel
11
- * and put the view in the new half".
12
- *
13
- * The bands are proportional rather than a fixed pixel depth so a narrow panel
14
- * stays droppable — at a fixed 40px a 100px-wide panel would be all edge and
15
- * have no centre to drop a tab into. `edgeFraction` is capped below 0.5 for the
16
- * same reason: at 0.5 the centre vanishes entirely.
17
- *
18
- * Corners go to whichever edge the pointer is proportionally deeper into, so
19
- * the diagonal is the tie-break and neither axis silently wins. **This is a
20
- * deliberate divergence from dockview**, whose `calculateQuadrantAsPercentage`
21
- * tests left, right, top, bottom in that order and returns the first hit — so
22
- * in its top-left corner `left` always wins, however far into the top band the
23
- * pointer is. Its band is 20% where this defaults to 25%.
3
+ * `center` adds as a tab; an edge splits the panel. Bands are proportional and
4
+ * capped below half, so a narrow panel keeps a centre. A corner goes to the
5
+ * edge the pointer is proportionally deeper into.
24
6
  */
25
7
  export function dropZoneAt(rect, x, y, edgeFraction = 0.25) {
26
8
  const fraction = Math.min(Math.max(edgeFraction, 0), 0.49);
@@ -43,16 +25,8 @@ export function dropZoneAt(rect, x, y, edgeFraction = 0.25) {
43
25
  return depths.reduce((best, candidate) => candidate.depth > best.depth ? candidate : best).zone;
44
26
  }
45
27
  /**
46
- * Which gap between tabs a pointer at `x` is nearest — the insertion index a
47
- * drop there would use, and the x to draw the caret at.
48
- *
49
- * The test is each tab's MIDPOINT rather than its edges, so every x belongs to
50
- * exactly one gap and there is no dead band between tabs where a drop would
51
- * have to fall back to appending. `rects` are in strip order and `x` is in
52
- * their coordinate space, whatever that is — this function does not know
53
- * whether it was handed viewport or panel-relative numbers.
54
- *
55
- * An empty strip is index 0, which is the only sensible place for a first tab.
28
+ * The gap between tabs nearest `x`, by tab midpoint so every x has exactly one.
29
+ * `x` is in the rects' coordinate space, whichever that is.
56
30
  */
57
31
  export function stripDropAt(rects, x) {
58
32
  for (const [i, rect] of rects.entries()) {
@@ -66,18 +40,7 @@ export function stripDropAt(rects, x) {
66
40
  left: last ? last.left + last.width : 0,
67
41
  };
68
42
  }
69
- /**
70
- * What each zone means, in one table.
71
- *
72
- * `split` is the split a drop there asks for — which way the panel divides and
73
- * whether the new half goes first; `center` is not a split. `rect` is where the
74
- * indicator is drawn, as CSS percentages of the panel.
75
- *
76
- * One table rather than two switches because the pair has to agree: the shaded
77
- * half IS the half the drop lands in, and that is the whole content of the
78
- * indicator. Written apart, `right` could shade the left half and nothing would
79
- * be wrong with either function on its own.
80
- */
43
+ // one table, so the shaded half and the half a drop lands in cannot disagree
81
44
  const ZONES = {
82
45
  left: {
83
46
  split: { direction: 'row', before: true },