flexdesk 0.7.0 → 0.8.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 +13 -0
- package/css/base.css +75 -0
- package/css/flexdesk.css +75 -0
- package/dist/flexdesk.css +75 -0
- package/dist/wm.js +86 -13
- package/dist/wm.js.map +2 -2
- package/package.json +1 -1
- package/src/tiling/shell.js +6 -2
- package/src/tiling/tile_renderer.js +116 -18
- package/src/tiling/wm.js +24 -0
package/README.md
CHANGED
|
@@ -121,6 +121,7 @@ await createShell({
|
|
|
121
121
|
promoteInPlace: true, // a window floated out of a tile stays inside that tile's pane
|
|
122
122
|
backToOpenList: true, // Back in a record opened beside its list closes onto that list
|
|
123
123
|
floatActiveTab: true, // floating a tile takes the tab on screen, not the whole pane
|
|
124
|
+
tabLayout: 'header', // tabs in the tile header, in place of its title, with a + (0.8.0)
|
|
124
125
|
chrome: {
|
|
125
126
|
topNav: document.getElementById('nav'),
|
|
126
127
|
zoom: document.getElementById('status-bar'), // content zoom, 50–200%
|
|
@@ -151,6 +152,18 @@ views of one pane. The float button, Alt+F and the tile menu take only the tab
|
|
|
151
152
|
on screen and leave its siblings in the tile, so the window holds one tab and
|
|
152
153
|
draws no strip. Without it the whole pane floats, strip and all.
|
|
153
154
|
|
|
155
|
+
**`tabLayout`** says where a tile draws its tabs. `'bottom'` (the default) is
|
|
156
|
+
the spreadsheet strip under the body, shown from two tabs. `'top'` is the
|
|
157
|
+
editor tab bar between the header and the body, also from two tabs.
|
|
158
|
+
`'header'` (0.8.0) puts that tab bar inside the header in place of the title,
|
|
159
|
+
on every tile, one tab included, with a `+` after the last tab that opens the
|
|
160
|
+
taxonomy root in a new tab (`wm.newTab(leafId)`, the same seed a split uses).
|
|
161
|
+
A panel tile has no tabs and keeps its title. Under `'header'` the tab, not
|
|
162
|
+
the header, is what you drag to move a single-tab pane; the empty part of the
|
|
163
|
+
header still floats the pane on a pull-down or a double-click. Set
|
|
164
|
+
`data-twm-tabs` on the shell's root, or call `wm.setTabLayout()`, to switch
|
|
165
|
+
live; nothing mounted in a tile is unmounted.
|
|
166
|
+
|
|
154
167
|
**Back and the breadcrumb are one rule.** `wm.navigateBack()` (Backspace) and
|
|
155
168
|
`wm.navigateUp(kind, props, { ctx })` (every breadcrumb crumb) share it. With
|
|
156
169
|
`backToOpenList`, a record whose section has a list open closes onto that list;
|
package/css/base.css
CHANGED
|
@@ -4844,6 +4844,81 @@ button.twm-about-dialog__btn--close:focus {
|
|
|
4844
4844
|
.twm-window-body { flex: 1 1 auto; min-height: 0; min-width: 0; }
|
|
4845
4845
|
|
|
4846
4846
|
|
|
4847
|
+
/* ══ C38. THE TABS ARE THE TITLE ═════════════════════════════════════
|
|
4848
|
+
*
|
|
4849
|
+
* `tabLayout: 'header'` mounts the same strip INSIDE the chrome, in place of
|
|
4850
|
+
* the title, on every content tile (`tile_renderer.js`, C38). The renderer
|
|
4851
|
+
* marks such a leaf `twm-leaf--tabs-in-header`; a panel, which has no tabs,
|
|
4852
|
+
* is not marked and keeps its title.
|
|
4853
|
+
*
|
|
4854
|
+
* Every selector here is two classes or more, so none of it depends on
|
|
4855
|
+
* whether this sheet or the one holding `.twm-leaf__chrome` loads first.
|
|
4856
|
+
*
|
|
4857
|
+
* The chrome grows to the editor strip's 26px rather than the strip shrinking
|
|
4858
|
+
* to 22: one 26px row replaces a 22px row plus a 26px one. */
|
|
4859
|
+
.twm-leaf--tabs-in-header > .twm-leaf__chrome {
|
|
4860
|
+
height: 26px;
|
|
4861
|
+
padding-left: 0;
|
|
4862
|
+
gap: var(--space-sm);
|
|
4863
|
+
}
|
|
4864
|
+
.twm-leaf--tabs-in-header > .twm-leaf__chrome > .twm-leaf__title,
|
|
4865
|
+
.twm-leaf--tabs-in-header > .twm-leaf__chrome > .twm-leaf__icon.material-symbols-outlined {
|
|
4866
|
+
display: none;
|
|
4867
|
+
}
|
|
4868
|
+
|
|
4869
|
+
/* The strip takes the room the title had, and the actions keep the right. */
|
|
4870
|
+
.twm-leaf__tabbar.twm-leaf__tabbar--header {
|
|
4871
|
+
flex: 1 1 auto;
|
|
4872
|
+
align-self: stretch;
|
|
4873
|
+
min-width: 0;
|
|
4874
|
+
height: auto;
|
|
4875
|
+
padding: 0;
|
|
4876
|
+
border: none;
|
|
4877
|
+
background: transparent;
|
|
4878
|
+
overflow: hidden;
|
|
4879
|
+
}
|
|
4880
|
+
|
|
4881
|
+
/* `0 1 auto`, not the top strip's `1 1 auto`: the list is only as wide as
|
|
4882
|
+
* its tabs, so the `+` sits right after the last one rather than at the far
|
|
4883
|
+
* end of the chrome. It still shrinks, and scrolls, when the tabs outgrow it. */
|
|
4884
|
+
.twm-leaf__tabbar--header > .tabs.notebook-tabs {
|
|
4885
|
+
flex: 0 1 auto;
|
|
4886
|
+
min-width: 0;
|
|
4887
|
+
background: transparent;
|
|
4888
|
+
}
|
|
4889
|
+
|
|
4890
|
+
/* An inactive tab is the chrome's colour, focused or not, so the active one —
|
|
4891
|
+
* the body's colour — is the only box in the row. */
|
|
4892
|
+
.twm-leaf__tabbar--header .notebook-tabs > .tab:not(.active) { background: transparent; }
|
|
4893
|
+
.twm-leaf__tabbar--header .notebook-tabs > .tab:not(.active):hover {
|
|
4894
|
+
background: var(--state-hover);
|
|
4895
|
+
color: var(--text-primary);
|
|
4896
|
+
}
|
|
4897
|
+
|
|
4898
|
+
.twm-leaf__tabbar--header > .twm-leaf__tab-new {
|
|
4899
|
+
flex: 0 0 auto;
|
|
4900
|
+
display: inline-flex;
|
|
4901
|
+
align-items: center;
|
|
4902
|
+
justify-content: center;
|
|
4903
|
+
width: 24px;
|
|
4904
|
+
padding: 0;
|
|
4905
|
+
background: transparent;
|
|
4906
|
+
border: none;
|
|
4907
|
+
border-radius: 0;
|
|
4908
|
+
color: var(--text-muted);
|
|
4909
|
+
cursor: pointer;
|
|
4910
|
+
}
|
|
4911
|
+
.twm-leaf__tabbar--header > .twm-leaf__tab-new:hover {
|
|
4912
|
+
background: var(--state-hover);
|
|
4913
|
+
color: var(--text-primary);
|
|
4914
|
+
}
|
|
4915
|
+
/* Two classes, for the Material Symbols tie — see `.twm-leaf__icon`. */
|
|
4916
|
+
.twm-leaf__tab-new .material-symbols-outlined {
|
|
4917
|
+
font-size: 16px;
|
|
4918
|
+
line-height: 1;
|
|
4919
|
+
}
|
|
4920
|
+
|
|
4921
|
+
|
|
4847
4922
|
/* ══ NotebookTabBar ══════════════════════════════════════════════════
|
|
4848
4923
|
*
|
|
4849
4924
|
* A CLASS ONE SIDE KNOWS AND THE OTHER HAD NEVER HEARD OF — the fifth in this
|
package/css/flexdesk.css
CHANGED
|
@@ -5971,6 +5971,81 @@ body.twm-dragging * { user-select: none; }
|
|
|
5971
5971
|
* strip is pushed off the bottom edge. */
|
|
5972
5972
|
.twm-window-body { flex: 1 1 auto; min-height: 0; min-width: 0; }
|
|
5973
5973
|
|
|
5974
|
+
|
|
5975
|
+
/* ══ C38. THE TABS ARE THE TITLE ═════════════════════════════════════
|
|
5976
|
+
*
|
|
5977
|
+
* `tabLayout: 'header'` mounts the same strip INSIDE the chrome, in place of
|
|
5978
|
+
* the title, on every content tile (`tile_renderer.js`, C38). The renderer
|
|
5979
|
+
* marks such a leaf `twm-leaf--tabs-in-header`; a panel, which has no tabs,
|
|
5980
|
+
* is not marked and keeps its title.
|
|
5981
|
+
*
|
|
5982
|
+
* Every selector here is two classes or more, so none of it depends on
|
|
5983
|
+
* whether this sheet or the one holding `.twm-leaf__chrome` loads first.
|
|
5984
|
+
*
|
|
5985
|
+
* The chrome grows to the editor strip's 26px rather than the strip shrinking
|
|
5986
|
+
* to 22: one 26px row replaces a 22px row plus a 26px one. */
|
|
5987
|
+
.twm-leaf--tabs-in-header > .twm-leaf__chrome {
|
|
5988
|
+
height: 26px;
|
|
5989
|
+
padding-left: 0;
|
|
5990
|
+
gap: var(--space-sm);
|
|
5991
|
+
}
|
|
5992
|
+
.twm-leaf--tabs-in-header > .twm-leaf__chrome > .twm-leaf__title,
|
|
5993
|
+
.twm-leaf--tabs-in-header > .twm-leaf__chrome > .twm-leaf__icon.material-symbols-outlined {
|
|
5994
|
+
display: none;
|
|
5995
|
+
}
|
|
5996
|
+
|
|
5997
|
+
/* The strip takes the room the title had, and the actions keep the right. */
|
|
5998
|
+
.twm-leaf__tabbar.twm-leaf__tabbar--header {
|
|
5999
|
+
flex: 1 1 auto;
|
|
6000
|
+
align-self: stretch;
|
|
6001
|
+
min-width: 0;
|
|
6002
|
+
height: auto;
|
|
6003
|
+
padding: 0;
|
|
6004
|
+
border: none;
|
|
6005
|
+
background: transparent;
|
|
6006
|
+
overflow: hidden;
|
|
6007
|
+
}
|
|
6008
|
+
|
|
6009
|
+
/* `0 1 auto`, not the top strip's `1 1 auto`: the list is only as wide as
|
|
6010
|
+
* its tabs, so the `+` sits right after the last one rather than at the far
|
|
6011
|
+
* end of the chrome. It still shrinks, and scrolls, when the tabs outgrow it. */
|
|
6012
|
+
.twm-leaf__tabbar--header > .tabs.notebook-tabs {
|
|
6013
|
+
flex: 0 1 auto;
|
|
6014
|
+
min-width: 0;
|
|
6015
|
+
background: transparent;
|
|
6016
|
+
}
|
|
6017
|
+
|
|
6018
|
+
/* An inactive tab is the chrome's colour, focused or not, so the active one —
|
|
6019
|
+
* the body's colour — is the only box in the row. */
|
|
6020
|
+
.twm-leaf__tabbar--header .notebook-tabs > .tab:not(.active) { background: transparent; }
|
|
6021
|
+
.twm-leaf__tabbar--header .notebook-tabs > .tab:not(.active):hover {
|
|
6022
|
+
background: var(--state-hover);
|
|
6023
|
+
color: var(--text-primary);
|
|
6024
|
+
}
|
|
6025
|
+
|
|
6026
|
+
.twm-leaf__tabbar--header > .twm-leaf__tab-new {
|
|
6027
|
+
flex: 0 0 auto;
|
|
6028
|
+
display: inline-flex;
|
|
6029
|
+
align-items: center;
|
|
6030
|
+
justify-content: center;
|
|
6031
|
+
width: 24px;
|
|
6032
|
+
padding: 0;
|
|
6033
|
+
background: transparent;
|
|
6034
|
+
border: none;
|
|
6035
|
+
border-radius: 0;
|
|
6036
|
+
color: var(--text-muted);
|
|
6037
|
+
cursor: pointer;
|
|
6038
|
+
}
|
|
6039
|
+
.twm-leaf__tabbar--header > .twm-leaf__tab-new:hover {
|
|
6040
|
+
background: var(--state-hover);
|
|
6041
|
+
color: var(--text-primary);
|
|
6042
|
+
}
|
|
6043
|
+
/* Two classes, for the Material Symbols tie — see `.twm-leaf__icon`. */
|
|
6044
|
+
.twm-leaf__tab-new .material-symbols-outlined {
|
|
6045
|
+
font-size: 16px;
|
|
6046
|
+
line-height: 1;
|
|
6047
|
+
}
|
|
6048
|
+
|
|
5974
6049
|
/* ══ NotebookTabBar ══════════════════════════════════════════════════
|
|
5975
6050
|
*
|
|
5976
6051
|
* A CLASS ONE SIDE KNOWS AND THE OTHER HAD NEVER HEARD OF — the fifth in this
|
package/dist/flexdesk.css
CHANGED
|
@@ -5971,6 +5971,81 @@ body.twm-dragging * { user-select: none; }
|
|
|
5971
5971
|
* strip is pushed off the bottom edge. */
|
|
5972
5972
|
.twm-window-body { flex: 1 1 auto; min-height: 0; min-width: 0; }
|
|
5973
5973
|
|
|
5974
|
+
|
|
5975
|
+
/* ══ C38. THE TABS ARE THE TITLE ═════════════════════════════════════
|
|
5976
|
+
*
|
|
5977
|
+
* `tabLayout: 'header'` mounts the same strip INSIDE the chrome, in place of
|
|
5978
|
+
* the title, on every content tile (`tile_renderer.js`, C38). The renderer
|
|
5979
|
+
* marks such a leaf `twm-leaf--tabs-in-header`; a panel, which has no tabs,
|
|
5980
|
+
* is not marked and keeps its title.
|
|
5981
|
+
*
|
|
5982
|
+
* Every selector here is two classes or more, so none of it depends on
|
|
5983
|
+
* whether this sheet or the one holding `.twm-leaf__chrome` loads first.
|
|
5984
|
+
*
|
|
5985
|
+
* The chrome grows to the editor strip's 26px rather than the strip shrinking
|
|
5986
|
+
* to 22: one 26px row replaces a 22px row plus a 26px one. */
|
|
5987
|
+
.twm-leaf--tabs-in-header > .twm-leaf__chrome {
|
|
5988
|
+
height: 26px;
|
|
5989
|
+
padding-left: 0;
|
|
5990
|
+
gap: var(--space-sm);
|
|
5991
|
+
}
|
|
5992
|
+
.twm-leaf--tabs-in-header > .twm-leaf__chrome > .twm-leaf__title,
|
|
5993
|
+
.twm-leaf--tabs-in-header > .twm-leaf__chrome > .twm-leaf__icon.material-symbols-outlined {
|
|
5994
|
+
display: none;
|
|
5995
|
+
}
|
|
5996
|
+
|
|
5997
|
+
/* The strip takes the room the title had, and the actions keep the right. */
|
|
5998
|
+
.twm-leaf__tabbar.twm-leaf__tabbar--header {
|
|
5999
|
+
flex: 1 1 auto;
|
|
6000
|
+
align-self: stretch;
|
|
6001
|
+
min-width: 0;
|
|
6002
|
+
height: auto;
|
|
6003
|
+
padding: 0;
|
|
6004
|
+
border: none;
|
|
6005
|
+
background: transparent;
|
|
6006
|
+
overflow: hidden;
|
|
6007
|
+
}
|
|
6008
|
+
|
|
6009
|
+
/* `0 1 auto`, not the top strip's `1 1 auto`: the list is only as wide as
|
|
6010
|
+
* its tabs, so the `+` sits right after the last one rather than at the far
|
|
6011
|
+
* end of the chrome. It still shrinks, and scrolls, when the tabs outgrow it. */
|
|
6012
|
+
.twm-leaf__tabbar--header > .tabs.notebook-tabs {
|
|
6013
|
+
flex: 0 1 auto;
|
|
6014
|
+
min-width: 0;
|
|
6015
|
+
background: transparent;
|
|
6016
|
+
}
|
|
6017
|
+
|
|
6018
|
+
/* An inactive tab is the chrome's colour, focused or not, so the active one —
|
|
6019
|
+
* the body's colour — is the only box in the row. */
|
|
6020
|
+
.twm-leaf__tabbar--header .notebook-tabs > .tab:not(.active) { background: transparent; }
|
|
6021
|
+
.twm-leaf__tabbar--header .notebook-tabs > .tab:not(.active):hover {
|
|
6022
|
+
background: var(--state-hover);
|
|
6023
|
+
color: var(--text-primary);
|
|
6024
|
+
}
|
|
6025
|
+
|
|
6026
|
+
.twm-leaf__tabbar--header > .twm-leaf__tab-new {
|
|
6027
|
+
flex: 0 0 auto;
|
|
6028
|
+
display: inline-flex;
|
|
6029
|
+
align-items: center;
|
|
6030
|
+
justify-content: center;
|
|
6031
|
+
width: 24px;
|
|
6032
|
+
padding: 0;
|
|
6033
|
+
background: transparent;
|
|
6034
|
+
border: none;
|
|
6035
|
+
border-radius: 0;
|
|
6036
|
+
color: var(--text-muted);
|
|
6037
|
+
cursor: pointer;
|
|
6038
|
+
}
|
|
6039
|
+
.twm-leaf__tabbar--header > .twm-leaf__tab-new:hover {
|
|
6040
|
+
background: var(--state-hover);
|
|
6041
|
+
color: var(--text-primary);
|
|
6042
|
+
}
|
|
6043
|
+
/* Two classes, for the Material Symbols tie — see `.twm-leaf__icon`. */
|
|
6044
|
+
.twm-leaf__tab-new .material-symbols-outlined {
|
|
6045
|
+
font-size: 16px;
|
|
6046
|
+
line-height: 1;
|
|
6047
|
+
}
|
|
6048
|
+
|
|
5974
6049
|
/* ══ NotebookTabBar ══════════════════════════════════════════════════
|
|
5975
6050
|
*
|
|
5976
6051
|
* A CLASS ONE SIDE KNOWS AND THE OTHER HAD NEVER HEARD OF — the fifth in this
|
package/dist/wm.js
CHANGED
|
@@ -2689,7 +2689,7 @@ function createPageFactories({ eventBus = null, events = {}, refreshOn = {} } =
|
|
|
2689
2689
|
|
|
2690
2690
|
// src/tiling/tile_renderer.js
|
|
2691
2691
|
var SPLITTER_PX = 4;
|
|
2692
|
-
var TAB_LAYOUTS = ["bottom", "top"];
|
|
2692
|
+
var TAB_LAYOUTS = ["bottom", "top", "header"];
|
|
2693
2693
|
var DEFAULT_TAB_LAYOUT = "bottom";
|
|
2694
2694
|
function _normalizeTabLayout(value) {
|
|
2695
2695
|
return TAB_LAYOUTS.includes(value) ? value : DEFAULT_TAB_LAYOUT;
|
|
@@ -2702,7 +2702,7 @@ function _tabKeyIndex(key) {
|
|
|
2702
2702
|
return Number.isInteger(n) && n >= 0 ? n : -1;
|
|
2703
2703
|
}
|
|
2704
2704
|
var TILE_LIFT_PX = 24;
|
|
2705
|
-
var CHROME_NO_FLOAT = "button, .twm-leaf__tab";
|
|
2705
|
+
var CHROME_NO_FLOAT = "button, .twm-leaf__tab, .notebook-tabs .tab";
|
|
2706
2706
|
function _isDownwardPull(down, sideways) {
|
|
2707
2707
|
return down > 0 && sideways <= down;
|
|
2708
2708
|
}
|
|
@@ -2877,6 +2877,7 @@ var TileRenderer = class {
|
|
|
2877
2877
|
if (entry && entry.kindKey === kindKey) {
|
|
2878
2878
|
entry.titleEl.textContent = leaf.title || (leaf.content ? leaf.content.kind : "empty");
|
|
2879
2879
|
_paintLeafIcon(entry.iconEl, leaf, entry.content, this.ctx);
|
|
2880
|
+
if (this.tabLayout === "header") this._refreshHeaderLabels(leaf, entry);
|
|
2880
2881
|
return entry.wrapEl;
|
|
2881
2882
|
}
|
|
2882
2883
|
if (entry) {
|
|
@@ -2928,7 +2929,14 @@ var TileRenderer = class {
|
|
|
2928
2929
|
if (!Array.isArray(leaf.tabs) || leaf.tabs.length <= 1) {
|
|
2929
2930
|
tabBar.classList.add("twm-leaf__tabbar--hidden");
|
|
2930
2931
|
}
|
|
2931
|
-
wrap.append(chrome,
|
|
2932
|
+
wrap.append(chrome, body, tabBar);
|
|
2933
|
+
this._placeTabBar({
|
|
2934
|
+
wrapEl: wrap,
|
|
2935
|
+
bodyEl: body,
|
|
2936
|
+
chromeEl: chrome,
|
|
2937
|
+
titleEl: title,
|
|
2938
|
+
tabBarEl: tabBar
|
|
2939
|
+
});
|
|
2932
2940
|
wrap.addEventListener("mousedown", (e) => {
|
|
2933
2941
|
if (e.target.closest(".twm-splitter")) return;
|
|
2934
2942
|
this.tree.focus(leaf.id);
|
|
@@ -3148,9 +3156,10 @@ var TileRenderer = class {
|
|
|
3148
3156
|
* attached element is a re-parent, not a rebuild — the body keeps its
|
|
3149
3157
|
* DOM, its listeners and its scroll. */
|
|
3150
3158
|
_placeTabBar(entry) {
|
|
3151
|
-
const { wrapEl, bodyEl, tabBarEl } = entry;
|
|
3159
|
+
const { wrapEl, bodyEl, tabBarEl, chromeEl, titleEl } = entry;
|
|
3152
3160
|
if (!wrapEl || !tabBarEl || !bodyEl) return;
|
|
3153
|
-
if (this.tabLayout === "
|
|
3161
|
+
if (this.tabLayout === "header" && chromeEl && titleEl) titleEl.after(tabBarEl);
|
|
3162
|
+
else if (this.tabLayout === "top") wrapEl.insertBefore(tabBarEl, bodyEl);
|
|
3154
3163
|
else wrapEl.appendChild(tabBarEl);
|
|
3155
3164
|
}
|
|
3156
3165
|
/** Tear down the `top` layout's component, if this leaf has one. Safe to
|
|
@@ -3173,16 +3182,42 @@ var TileRenderer = class {
|
|
|
3173
3182
|
const bar = entry.tabBarEl;
|
|
3174
3183
|
if (!bar) return;
|
|
3175
3184
|
const tabs = Array.isArray(leaf.tabs) ? leaf.tabs : [];
|
|
3185
|
+
const header = this.tabLayout === "header";
|
|
3176
3186
|
bar.classList.toggle("twm-leaf__tabbar--top", this.tabLayout === "top");
|
|
3177
|
-
|
|
3187
|
+
bar.classList.toggle("twm-leaf__tabbar--header", header);
|
|
3188
|
+
const isPanel = String(leaf.content?.kind || "").startsWith("panel:");
|
|
3189
|
+
const inHeader = header && !isPanel && !!leaf.content && tabs.length >= 1;
|
|
3190
|
+
entry.wrapEl?.classList.toggle("twm-leaf--tabs-in-header", inHeader);
|
|
3191
|
+
if (!inHeader && tabs.length <= 1) {
|
|
3178
3192
|
bar.classList.add("twm-leaf__tabbar--hidden");
|
|
3179
3193
|
this._disposeTabStrip(entry);
|
|
3180
3194
|
bar.innerHTML = "";
|
|
3181
3195
|
return;
|
|
3182
3196
|
}
|
|
3183
3197
|
bar.classList.remove("twm-leaf__tabbar--hidden");
|
|
3184
|
-
if (this.tabLayout === "
|
|
3185
|
-
else this.
|
|
3198
|
+
if (this.tabLayout === "bottom") this._renderBottomTabBar(leaf, entry, tabs);
|
|
3199
|
+
else this._renderTopTabBar(leaf, entry, tabs);
|
|
3200
|
+
}
|
|
3201
|
+
/** C38. The labels the `header` strip shows for `leaf`, in one string, so
|
|
3202
|
+
* a repaint can tell whether anything it draws has changed. */
|
|
3203
|
+
_headerLabels(leaf, entry) {
|
|
3204
|
+
const tabs = Array.isArray(leaf.tabs) ? leaf.tabs : [];
|
|
3205
|
+
const activeIdx = Math.max(0, Math.min(tabs.length - 1, leaf.activeTabIdx || 0));
|
|
3206
|
+
return tabs.map((t, i) => this._tabLabel(t, i === activeIdx, entry)).join("\n");
|
|
3207
|
+
}
|
|
3208
|
+
/** C38. A cached leaf is not re-mounted, so a tab renamed in the tree
|
|
3209
|
+
* (`TileTree.renameMatchingTabs`) reaches the strip only through here. */
|
|
3210
|
+
_refreshHeaderLabels(leaf, entry) {
|
|
3211
|
+
const labels = this._headerLabels(leaf, entry);
|
|
3212
|
+
if (entry.headerLabels === labels) return;
|
|
3213
|
+
this._renderTabBar(leaf, entry);
|
|
3214
|
+
}
|
|
3215
|
+
/** What a tab in the `top` or `header` strip is called. A tab with no
|
|
3216
|
+
* title of its own falls back, when it is the active one, to the title
|
|
3217
|
+
* its content gave at mount — the string the chrome title would have
|
|
3218
|
+
* shown, and under `header` the chrome title is hidden (C38). */
|
|
3219
|
+
_tabLabel(t, active, entry) {
|
|
3220
|
+
return t.title || active && entry?.content?.title || t.kind || "";
|
|
3186
3221
|
}
|
|
3187
3222
|
/**
|
|
3188
3223
|
* C33. THE CHROME IS THE DRAG SOURCE FOR A LEAF THAT HAS ONE TAB.
|
|
@@ -3218,12 +3253,13 @@ var TileRenderer = class {
|
|
|
3218
3253
|
const tabs = Array.isArray(leaf.tabs) ? leaf.tabs : [];
|
|
3219
3254
|
const isPanel = String(leaf.content?.kind || "").startsWith("panel:");
|
|
3220
3255
|
const vetoed = this.leafChrome(leaf.id)?.promote === false;
|
|
3221
|
-
const on = tabs.length === 1 && !isPanel && !vetoed;
|
|
3256
|
+
const on = tabs.length === 1 && !isPanel && !vetoed && this.tabLayout !== "header";
|
|
3222
3257
|
if (on) chrome.setAttribute("draggable", "true");
|
|
3223
3258
|
else chrome.removeAttribute("draggable");
|
|
3224
3259
|
if (chrome.__twmTabDragBound) return;
|
|
3225
3260
|
chrome.__twmTabDragBound = true;
|
|
3226
3261
|
chrome.addEventListener("dragstart", (ev) => {
|
|
3262
|
+
if (entry.tabBarEl?.contains(ev.target)) return;
|
|
3227
3263
|
if (ev.target?.closest?.(CHROME_NO_FLOAT)) {
|
|
3228
3264
|
ev.preventDefault();
|
|
3229
3265
|
return;
|
|
@@ -3282,11 +3318,27 @@ var TileRenderer = class {
|
|
|
3282
3318
|
);
|
|
3283
3319
|
}, true);
|
|
3284
3320
|
}
|
|
3321
|
+
if (this.tabLayout === "header" && !bar.querySelector(".twm-leaf__tab-new")) {
|
|
3322
|
+
const add = document.createElement("button");
|
|
3323
|
+
add.type = "button";
|
|
3324
|
+
add.className = "twm-leaf__tab-new";
|
|
3325
|
+
add.dataset.action = "tab-new";
|
|
3326
|
+
add.title = "New tab";
|
|
3327
|
+
add.setAttribute("aria-label", "New tab");
|
|
3328
|
+
add.innerHTML = '<span class="material-symbols-outlined">add</span>';
|
|
3329
|
+
add.addEventListener("click", (ev) => {
|
|
3330
|
+
ev.preventDefault();
|
|
3331
|
+
ev.stopPropagation();
|
|
3332
|
+
this.ctx.onLeafTabAction?.(leaf.id, "new", {});
|
|
3333
|
+
});
|
|
3334
|
+
bar.appendChild(add);
|
|
3335
|
+
}
|
|
3285
3336
|
const activeIdx = Math.max(0, Math.min(tabs.length - 1, leaf.activeTabIdx || 0));
|
|
3337
|
+
entry.headerLabels = this._headerLabels(leaf, entry);
|
|
3286
3338
|
entry.tabStrip.update(
|
|
3287
3339
|
tabs.map((t, i) => ({
|
|
3288
3340
|
filePath: _tabKey(i),
|
|
3289
|
-
label: t
|
|
3341
|
+
label: this._tabLabel(t, i === activeIdx, entry),
|
|
3290
3342
|
// The component's `fileType` picks a glyph out of a static map
|
|
3291
3343
|
// of FILE kinds. A tile's kinds are the embedder's, and the
|
|
3292
3344
|
// taxonomy already answers for them — see `_paintTopTabs`.
|
|
@@ -3299,7 +3351,7 @@ var TileRenderer = class {
|
|
|
3299
3351
|
})),
|
|
3300
3352
|
_tabKey(activeIdx)
|
|
3301
3353
|
);
|
|
3302
|
-
this._paintTopTabs(entry.tabStripHostEl, tabs);
|
|
3354
|
+
this._paintTopTabs(entry.tabStripHostEl, tabs, activeIdx, entry);
|
|
3303
3355
|
}
|
|
3304
3356
|
/**
|
|
3305
3357
|
* The two things `NotebookTabBar` derives from a vocabulary a tile does not
|
|
@@ -3318,13 +3370,13 @@ var TileRenderer = class {
|
|
|
3318
3370
|
* rendered last. The class names used here are the component's published
|
|
3319
3371
|
* contract, stated in its header comment.
|
|
3320
3372
|
*/
|
|
3321
|
-
_paintTopTabs(hostEl, tabs) {
|
|
3373
|
+
_paintTopTabs(hostEl, tabs, activeIdx = -1, entry = null) {
|
|
3322
3374
|
if (!hostEl) return;
|
|
3323
3375
|
const els = hostEl.querySelectorAll(".tab");
|
|
3324
3376
|
els.forEach((el, i) => {
|
|
3325
3377
|
const spec = tabs[i];
|
|
3326
3378
|
if (!spec) return;
|
|
3327
|
-
el.title = spec
|
|
3379
|
+
el.title = this._tabLabel(spec, i === activeIdx, entry);
|
|
3328
3380
|
const icon = spec.kind ? this.ctx?.taxonomy?.meta?.(spec.kind)?.icon : null;
|
|
3329
3381
|
const glyph = el.querySelector(".tab-icon");
|
|
3330
3382
|
if (glyph && icon) glyph.textContent = icon;
|
|
@@ -4089,6 +4141,23 @@ var WindowManager = class _WindowManager {
|
|
|
4089
4141
|
setTabLayout(layout) {
|
|
4090
4142
|
this.renderer.setTabLayout(layout);
|
|
4091
4143
|
}
|
|
4144
|
+
/** C38. Open a new tab in a tile and show it. It holds the taxonomy root —
|
|
4145
|
+
* what a split seeds its new pane with, so "a new tab" and "a new pane"
|
|
4146
|
+
* start in the same place. Defaults to the focused tile. A panel tile
|
|
4147
|
+
* grows no tabs (`TileTree.appendLeafTab`); then this does nothing and
|
|
4148
|
+
* answers -1, else the new tab's index. */
|
|
4149
|
+
newTab(leafId = null) {
|
|
4150
|
+
const tree = this._tree();
|
|
4151
|
+
const id = leafId ?? tree.focusedLeafId;
|
|
4152
|
+
const seed = this._rootLeaf();
|
|
4153
|
+
const idx = tree.appendLeafTab(id, seed.content, seed.title);
|
|
4154
|
+
if (idx < 0) return -1;
|
|
4155
|
+
tree.focus(id);
|
|
4156
|
+
this.renderer.render();
|
|
4157
|
+
this._persist();
|
|
4158
|
+
this._notifyChange("tab-new");
|
|
4159
|
+
return idx;
|
|
4160
|
+
}
|
|
4092
4161
|
/** Emit on bus + call onChange. Use this instead of the bare callback
|
|
4093
4162
|
* so other surfaces (palette, top-bar toggles, page shortcuts) can
|
|
4094
4163
|
* subscribe through the existing event system. */
|
|
@@ -6706,6 +6775,10 @@ var WindowManager = class _WindowManager {
|
|
|
6706
6775
|
this._notifyChange("twm-tab-close");
|
|
6707
6776
|
return;
|
|
6708
6777
|
}
|
|
6778
|
+
if (action === "new") {
|
|
6779
|
+
this.newTab(leafId);
|
|
6780
|
+
return;
|
|
6781
|
+
}
|
|
6709
6782
|
if (action === "move") {
|
|
6710
6783
|
tree.moveLeafTab(leafId, data.from, data.to);
|
|
6711
6784
|
this.renderer.render();
|