flexdesk 0.4.1 → 0.4.2
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 +12 -0
- package/dist/wm.js +87 -3
- package/dist/wm.js.map +2 -2
- package/package.json +1 -1
- package/src/tiling/shell.js +8 -1
- package/src/tiling/tile_renderer.js +1 -1
- package/src/tiling/wm.js +94 -1
package/README.md
CHANGED
|
@@ -114,6 +114,7 @@ await createShell({
|
|
|
114
114
|
snapPromotion: true, // drag a floating window onto a tile: it snaps, and docks back in
|
|
115
115
|
promoteInPlace: true, // a window floated out of a tile stays inside that tile's pane
|
|
116
116
|
backToOpenList: true, // Back in a record opened beside its list closes onto that list
|
|
117
|
+
floatActiveTab: true, // floating a tile takes the tab on screen, not the whole pane
|
|
117
118
|
chrome: {
|
|
118
119
|
topNav: document.getElementById('nav'),
|
|
119
120
|
zoom: document.getElementById('status-bar'), // content zoom, 50–200%
|
|
@@ -139,6 +140,17 @@ the nearest tab to the left, else to the right, that is not a record (no
|
|
|
139
140
|
`props.id`) and belongs to the same top-nav section. Per-tab history still comes
|
|
140
141
|
first, and a record with no such list beside it still walks up in place.
|
|
141
142
|
|
|
143
|
+
**`floatActiveTab`** is for apps whose tabs are separate records rather than
|
|
144
|
+
views of one pane. The float button, Alt+F and the tile menu take only the tab
|
|
145
|
+
on screen and leave its siblings in the tile, so the window holds one tab and
|
|
146
|
+
draws no strip. Without it the whole pane floats, strip and all.
|
|
147
|
+
|
|
148
|
+
**Back in a floating window** always acts on that window, whichever option is
|
|
149
|
+
set. The last click decides: inside a window, Backspace walks that window up;
|
|
150
|
+
anywhere else, it walks the focused tile as before. With `backToOpenList`, a
|
|
151
|
+
record in a window closes onto an open list of its section, first among the
|
|
152
|
+
window's own tabs and then in any tile of its desktop.
|
|
153
|
+
|
|
142
154
|
## The host port
|
|
143
155
|
|
|
144
156
|
FlexDesk never touches `pywebview`, `fetch`, `localStorage` or the filesystem. It
|
package/dist/wm.js
CHANGED
|
@@ -2902,7 +2902,7 @@ var TileRenderer = class {
|
|
|
2902
2902
|
<button class="twm-leaf__btn" data-action="split-v" title="Split vertically (Alt+V)">
|
|
2903
2903
|
<span class="material-symbols-outlined">splitscreen_add</span>
|
|
2904
2904
|
</button>
|
|
2905
|
-
<button class="twm-leaf__btn" data-action="promote" title="Float this pane as a window (Alt+F)">
|
|
2905
|
+
<button class="twm-leaf__btn" data-action="promote" title="Float this ${this.ctx.wm?.floatActiveTab ? "tab" : "pane"} as a window (Alt+F)">
|
|
2906
2906
|
<span class="material-symbols-outlined">web_asset</span>
|
|
2907
2907
|
</button>`}
|
|
2908
2908
|
<button class="twm-leaf__btn" data-action="close" title="Close (Alt+W)">
|
|
@@ -3979,7 +3979,8 @@ var WindowManager = class _WindowManager {
|
|
|
3979
3979
|
snapPromotion = false,
|
|
3980
3980
|
promoteInPlace = false,
|
|
3981
3981
|
tabLayout = null,
|
|
3982
|
-
backToOpenList = false
|
|
3982
|
+
backToOpenList = false,
|
|
3983
|
+
floatActiveTab = false
|
|
3983
3984
|
}) {
|
|
3984
3985
|
if (!taxonomy) throw new Error("WindowManager: a taxonomy is required");
|
|
3985
3986
|
if (!content || typeof content.mount !== "function") {
|
|
@@ -3995,6 +3996,7 @@ var WindowManager = class _WindowManager {
|
|
|
3995
3996
|
this.eventBus = eventBus || null;
|
|
3996
3997
|
this.onChange = onChange || (() => {
|
|
3997
3998
|
});
|
|
3999
|
+
this.floatActiveTab = !!floatActiveTab;
|
|
3998
4000
|
this._rootLeaf = () => {
|
|
3999
4001
|
const kind = this.taxonomy.root;
|
|
4000
4002
|
return {
|
|
@@ -4006,6 +4008,12 @@ var WindowManager = class _WindowManager {
|
|
|
4006
4008
|
this.snapPromotion = !!snapPromotion;
|
|
4007
4009
|
this.promoteInPlace = !!promoteInPlace;
|
|
4008
4010
|
this.backToOpenList = !!backToOpenList;
|
|
4011
|
+
this._backWindowId = null;
|
|
4012
|
+
const doc = rootEl?.ownerDocument || globalThis.document;
|
|
4013
|
+
doc?.addEventListener?.("pointerdown", (e) => {
|
|
4014
|
+
const el = e.target?.closest?.("[data-window-id]");
|
|
4015
|
+
this._backWindowId = el && this._windowToLeaf.has(el.getAttribute("data-window-id")) ? el.getAttribute("data-window-id") : null;
|
|
4016
|
+
}, true);
|
|
4009
4017
|
this._snapCtl = null;
|
|
4010
4018
|
this.desktops = new DesktopManager({
|
|
4011
4019
|
seed: this._rootLeaf,
|
|
@@ -4150,6 +4158,7 @@ var WindowManager = class _WindowManager {
|
|
|
4150
4158
|
* methods (openFromContext / openInLeaf / navigateActiveTab) —
|
|
4151
4159
|
* they all record history by default. */
|
|
4152
4160
|
navigateBack() {
|
|
4161
|
+
if (this._navigateBackInWindow()) return;
|
|
4153
4162
|
const tree = this._tree();
|
|
4154
4163
|
const focusedId = tree.focusedLeafId;
|
|
4155
4164
|
const primaryId = tree.primaryLeafId();
|
|
@@ -4291,6 +4300,74 @@ var WindowManager = class _WindowManager {
|
|
|
4291
4300
|
this._notifyChange("navigate-back-close");
|
|
4292
4301
|
return true;
|
|
4293
4302
|
}
|
|
4303
|
+
/**
|
|
4304
|
+
* C34. Back while a floating window is the thing being read. Returns true
|
|
4305
|
+
* when it handled the press, false to let the tile path run.
|
|
4306
|
+
*
|
|
4307
|
+
* Only the window's ACTIVE tab is considered, in this order:
|
|
4308
|
+
* (1) with `backToOpenList`, a record closes onto an open list of its
|
|
4309
|
+
* section: first among the window's own tabs, then any tile of the
|
|
4310
|
+
* window's desktop, primary tile first. Closing the last tab closes
|
|
4311
|
+
* the window, which discards it (a plain close never docks back).
|
|
4312
|
+
* (2) otherwise it walks up IN the window, exactly as a tile does.
|
|
4313
|
+
* A window whose content has nowhere to go still counts as handled: the
|
|
4314
|
+
* press was aimed at it, and moving the tile behind it would be the bug.
|
|
4315
|
+
*/
|
|
4316
|
+
_navigateBackInWindow() {
|
|
4317
|
+
const winId = this._backWindowId;
|
|
4318
|
+
const rec = winId ? this._windowToLeaf.get(winId) : null;
|
|
4319
|
+
if (!rec) {
|
|
4320
|
+
this._backWindowId = null;
|
|
4321
|
+
return false;
|
|
4322
|
+
}
|
|
4323
|
+
const tabs = Array.isArray(rec.tabs) && rec.tabs.length ? rec.tabs : [rec.original].filter(Boolean);
|
|
4324
|
+
const activeIdx = Math.max(0, Math.min(tabs.length - 1, rec.activeTabIdx || 0));
|
|
4325
|
+
const active = tabs[activeIdx];
|
|
4326
|
+
if (!active) return true;
|
|
4327
|
+
const section = this.backToOpenList && _isEntityTab(active) ? this.taxonomy.topNavFor(active.kind) : null;
|
|
4328
|
+
const isList = (t) => !!t && !_isEntityTab(t) && this.taxonomy.topNavFor(t.kind) === section;
|
|
4329
|
+
if (section) {
|
|
4330
|
+
let own = -1;
|
|
4331
|
+
for (let i = activeIdx - 1; i >= 0 && own < 0; i--) if (isList(tabs[i])) own = i;
|
|
4332
|
+
for (let i = activeIdx + 1; i < tabs.length && own < 0; i++) if (isList(tabs[i])) own = i;
|
|
4333
|
+
if (own >= 0 && Array.isArray(rec.tabs) && rec.tabs.length > 1) {
|
|
4334
|
+
this._windowTabAction(winId, "close", { idx: activeIdx });
|
|
4335
|
+
this.showWindowTab(winId, own > activeIdx ? own - 1 : own);
|
|
4336
|
+
return true;
|
|
4337
|
+
}
|
|
4338
|
+
const desk = this.desktops.desktops[rec.desktopIdx] || this.desktops.active();
|
|
4339
|
+
const tree = desk.tree;
|
|
4340
|
+
const leaves = tree.leaves?.() || [];
|
|
4341
|
+
const primaryId = tree.primaryLeafId();
|
|
4342
|
+
const ordered = [...leaves.filter((l) => l.id === primaryId), ...leaves.filter((l) => l.id !== primaryId)];
|
|
4343
|
+
for (const leaf of ordered) {
|
|
4344
|
+
const ltabs = Array.isArray(leaf.tabs) ? leaf.tabs : [];
|
|
4345
|
+
const cur = Math.max(0, Math.min(ltabs.length - 1, leaf.activeTabIdx || 0));
|
|
4346
|
+
const hit = isList(ltabs[cur]) ? cur : ltabs.findIndex(isList);
|
|
4347
|
+
if (hit < 0) continue;
|
|
4348
|
+
this._backWindowId = null;
|
|
4349
|
+
if (Array.isArray(rec.tabs) && rec.tabs.length > 1) this._windowTabAction(winId, "close", { idx: activeIdx });
|
|
4350
|
+
else {
|
|
4351
|
+
try {
|
|
4352
|
+
rec.window.close({ force: true });
|
|
4353
|
+
} catch {
|
|
4354
|
+
}
|
|
4355
|
+
}
|
|
4356
|
+
if (this.desktops.active().tree === tree) {
|
|
4357
|
+
tree.setActiveLeafTab(leaf.id, hit);
|
|
4358
|
+
tree.focus(leaf.id);
|
|
4359
|
+
this.renderer.render();
|
|
4360
|
+
}
|
|
4361
|
+
this._persist();
|
|
4362
|
+
this._notifyChange("navigate-back-close");
|
|
4363
|
+
return true;
|
|
4364
|
+
}
|
|
4365
|
+
}
|
|
4366
|
+
const up = this.taxonomy.parentOf?.(active.kind, active.props || {});
|
|
4367
|
+
const parentKind = up ? up.kind : this.taxonomy.parentKindFor(active.kind);
|
|
4368
|
+
if (parentKind) this.openInWindow(winId, parentKind, up ? up.props || {} : {});
|
|
4369
|
+
return true;
|
|
4370
|
+
}
|
|
4294
4371
|
navigateActiveTab(leafId, kind, props = {}) {
|
|
4295
4372
|
const tree = this._tree();
|
|
4296
4373
|
const leaf = tree.get(leafId);
|
|
@@ -4632,6 +4709,11 @@ var WindowManager = class _WindowManager {
|
|
|
4632
4709
|
const tree = this._tree();
|
|
4633
4710
|
const focused = tree.focused();
|
|
4634
4711
|
if (!focused) return null;
|
|
4712
|
+
const tabs = Array.isArray(focused.tabs) ? focused.tabs : [];
|
|
4713
|
+
if (this.floatActiveTab && tabs.length > 1) {
|
|
4714
|
+
const idx = Math.max(0, Math.min(tabs.length - 1, focused.activeTabIdx || 0));
|
|
4715
|
+
return this.floatTabAsWindow(focused.id, idx);
|
|
4716
|
+
}
|
|
4635
4717
|
return this.floatPane(focused.id);
|
|
4636
4718
|
}
|
|
4637
4719
|
/** R8. Float a pane — every tab, with the strip — into a managed window.
|
|
@@ -7421,6 +7503,7 @@ async function createShell({
|
|
|
7421
7503
|
snapPromotion = false,
|
|
7422
7504
|
promoteInPlace = false,
|
|
7423
7505
|
backToOpenList = false,
|
|
7506
|
+
floatActiveTab = false,
|
|
7424
7507
|
tabLayout = null,
|
|
7425
7508
|
chrome = {},
|
|
7426
7509
|
// An embedder that moved its sections out of the top bar — into an icon
|
|
@@ -7461,6 +7544,7 @@ async function createShell({
|
|
|
7461
7544
|
snapPromotion,
|
|
7462
7545
|
promoteInPlace,
|
|
7463
7546
|
backToOpenList,
|
|
7547
|
+
floatActiveTab,
|
|
7464
7548
|
tabLayout,
|
|
7465
7549
|
// `ctx` is the delivery vehicle for leaf-mounted chrome: tile_renderer
|
|
7466
7550
|
// spreads it into every content factory, which is how the breadcrumb
|
|
@@ -7788,7 +7872,7 @@ function _tileContextMenu(wm, leafId, x, y) {
|
|
|
7788
7872
|
// direction is a dead control; in the mean direction it is a missing
|
|
7789
7873
|
// feature, and this file has shipped one of each.
|
|
7790
7874
|
{
|
|
7791
|
-
label: "Float this pane as a window",
|
|
7875
|
+
label: wm.floatActiveTab ? "Float this tab as a window" : "Float this pane as a window",
|
|
7792
7876
|
icon: "web_asset",
|
|
7793
7877
|
action: "promote",
|
|
7794
7878
|
disabled: isPanel || !leaf.content || wm.renderer?.leafChrome?.(leafId)?.promote === false
|