flexdesk 0.6.3 → 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/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, ...this.tabLayout === "top" ? [tabBar, body] : [body, tabBar]);
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 === "top") wrapEl.insertBefore(tabBarEl, bodyEl);
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
- if (tabs.length <= 1) {
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 === "top") this._renderTopTabBar(leaf, entry, tabs);
3185
- else this._renderBottomTabBar(leaf, entry, tabs);
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.title || t.kind || "",
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.title || spec.kind || "";
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();