flexdesk 0.17.15 → 0.17.16

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 (42) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/css/base.css +224 -4
  3. package/css/flexdesk.css +314 -12
  4. package/dist/charts.js +104 -34
  5. package/dist/charts.js.map +3 -3
  6. package/dist/{chunk-2PM6ZMNS.js → chunk-2Z2ENHR3.js} +2 -2
  7. package/dist/{chunk-AD7FIA3Z.js → chunk-7JI7HHSY.js} +2 -2
  8. package/dist/{chunk-TLIV6QSU.js → chunk-7OL6F2AM.js} +3 -3
  9. package/dist/{chunk-MADMGRX2.js → chunk-PGZ4EHSE.js} +156 -9
  10. package/dist/chunk-PGZ4EHSE.js.map +7 -0
  11. package/dist/{chunk-OEG6NPAN.js → chunk-XJNADRGR.js} +2 -2
  12. package/dist/flexdesk.css +314 -12
  13. package/dist/flow.js +3 -3
  14. package/dist/tiles.js +84 -9
  15. package/dist/tiles.js.map +2 -2
  16. package/dist/widgets.js +448 -5
  17. package/dist/widgets.js.map +4 -4
  18. package/dist/wm.js +102 -14
  19. package/dist/wm.js.map +2 -2
  20. package/docs/README.md +1 -1
  21. package/docs/components/charts.md +70 -3
  22. package/docs/components/forms-and-dialogs.md +61 -0
  23. package/docs/components/menus.md +85 -1
  24. package/docs/components/shell.md +37 -0
  25. package/docs/components/tiles.md +37 -0
  26. package/package.json +1 -1
  27. package/src/charts/plot_config_editor.js +125 -29
  28. package/src/tiles/tile_base.js +48 -2
  29. package/src/tiles/tile_grid.js +42 -1
  30. package/src/tiling/kind_taxonomy.js +45 -0
  31. package/src/tiling/wm.js +86 -11
  32. package/src/ui/components/category_picker.js +556 -0
  33. package/src/ui/components/computing_status_window.js +3 -0
  34. package/src/ui/components/confirm_dialog.js +3 -0
  35. package/src/ui/components/managed_window.js +204 -9
  36. package/src/ui/components/wizard.js +4 -1
  37. package/widgets.js +2 -1
  38. package/dist/chunk-MADMGRX2.js.map +0 -7
  39. /package/dist/{chunk-2PM6ZMNS.js.map → chunk-2Z2ENHR3.js.map} +0 -0
  40. /package/dist/{chunk-AD7FIA3Z.js.map → chunk-7JI7HHSY.js.map} +0 -0
  41. /package/dist/{chunk-TLIV6QSU.js.map → chunk-7OL6F2AM.js.map} +0 -0
  42. /package/dist/{chunk-OEG6NPAN.js.map → chunk-XJNADRGR.js.map} +0 -0
package/dist/wm.js CHANGED
@@ -8,14 +8,14 @@ import {
8
8
  } from "./chunk-REJ5HLIM.js";
9
9
  import {
10
10
  showContextMenu
11
- } from "./chunk-AD7FIA3Z.js";
11
+ } from "./chunk-7JI7HHSY.js";
12
12
  import {
13
13
  openForm
14
- } from "./chunk-OEG6NPAN.js";
14
+ } from "./chunk-XJNADRGR.js";
15
15
  import {
16
16
  ManagedWindow,
17
17
  isRowActivation
18
- } from "./chunk-MADMGRX2.js";
18
+ } from "./chunk-PGZ4EHSE.js";
19
19
  import "./chunk-HNU6MM2B.js";
20
20
  import {
21
21
  NotebookTabBar
@@ -2642,6 +2642,28 @@ function createTaxonomy({ kinds, root } = {}) {
2642
2642
  `createTaxonomy: '${kind}'.single must be true or a prop name`
2643
2643
  );
2644
2644
  }
2645
+ if (def.window != null) {
2646
+ const w = def.window;
2647
+ const bad = (v) => v != null && !(typeof v === "number" && Number.isFinite(v) && v > 0);
2648
+ if (typeof w !== "object" || Array.isArray(w) || bad(w.width) || bad(w.height)) {
2649
+ throw new TypeError(
2650
+ `createTaxonomy: '${kind}'.window must be { width?, height? } in positive px`
2651
+ );
2652
+ }
2653
+ }
2654
+ if (def.windowOnly != null && typeof def.windowOnly !== "boolean") {
2655
+ throw new TypeError(`createTaxonomy: '${kind}'.windowOnly must be true or false`);
2656
+ }
2657
+ if (def.windowOnly && def.isTopNav) {
2658
+ throw new Error(
2659
+ `createTaxonomy: '${kind}' is a top-nav page and cannot be windowOnly \u2014 a page lives in tiles`
2660
+ );
2661
+ }
2662
+ }
2663
+ if (kinds[root].windowOnly) {
2664
+ throw new Error(
2665
+ `createTaxonomy: the root kind '${root}' cannot be windowOnly \u2014 every new pane, tab and desktop is seeded with it`
2666
+ );
2645
2667
  }
2646
2668
  if (kinds[root].single) {
2647
2669
  throw new Error(
@@ -2720,6 +2742,11 @@ function createTaxonomy({ kinds, root } = {}) {
2720
2742
  const v = props?.[by];
2721
2743
  return `${kind}\0${v == null ? "" : String(v)}`;
2722
2744
  };
2745
+ const isWindowOnly = (kind) => _kinds[kind]?.windowOnly === true;
2746
+ const windowSize = (kind) => {
2747
+ const w = _kinds[kind]?.window;
2748
+ return w ? { width: w.width ?? null, height: w.height ?? null } : null;
2749
+ };
2723
2750
  const singleReason = (kind) => `${_kinds[kind]?.label || kind} is open once: use the one that is open`;
2724
2751
  return Object.freeze({
2725
2752
  root,
@@ -2735,7 +2762,9 @@ function createTaxonomy({ kinds, root } = {}) {
2735
2762
  topNavEntries,
2736
2763
  isSingle,
2737
2764
  instanceKey,
2738
- singleReason
2765
+ singleReason,
2766
+ isWindowOnly,
2767
+ windowSize
2739
2768
  });
2740
2769
  }
2741
2770
 
@@ -5461,7 +5490,11 @@ var WindowManager = class _WindowManager {
5461
5490
  }
5462
5491
  }
5463
5492
  }
5464
- const deduped = this._dropDuplicateSingles();
5493
+ const windowOnlyDropped = this._dropWindowOnlyTabs();
5494
+ if (windowOnlyDropped.length) {
5495
+ (this.logger ?? console).warn?.(`[wm] the saved layout held ${windowOnlyDropped.length} tile tab(s) of window-only kind(s) ${[...new Set(windowOnlyDropped)].join(", ")}; they open in a window when asked for`);
5496
+ }
5497
+ const deduped = this._dropDuplicateSingles() || windowOnlyDropped.length > 0;
5465
5498
  for (const d of this.desktops.desktops) this._canonicalize(d.tree, d);
5466
5499
  this.renderer.tree = this.desktops.active().tree;
5467
5500
  this.renderer.render();
@@ -5728,20 +5761,52 @@ var WindowManager = class _WindowManager {
5728
5761
  }
5729
5762
  return keys;
5730
5763
  }
5764
+ /** Open a window-only kind (KindDef `windowOnly`) in its window — the one
5765
+ * that is open, or a new one — and say so; any other kind: false, and the
5766
+ * caller opens it where it asked (STORY-0127). */
5767
+ _openWindowOnly(kind, props) {
5768
+ if (!this.taxonomy.isWindowOnly?.(kind)) return false;
5769
+ this._navigateWindow(kind, props);
5770
+ return true;
5771
+ }
5772
+ /** Whether a window record holds a window-only kind: such a window never
5773
+ * docks into a tile (Back to tile, maximize, drag-to-dock). */
5774
+ _holdsWindowOnly(rec) {
5775
+ return !!this.taxonomy.isWindowOnly && _windowTabSpecs(rec).some((t) => this.taxonomy.isWindowOnly(t?.kind));
5776
+ }
5777
+ /** A restored layout never holds a window-only kind in a tile: its tabs,
5778
+ * and its tabs archived under a page, go. Windows are not restored, so
5779
+ * the content simply opens in a window the next time it is asked for.
5780
+ * Returns the kinds of the tabs that went, one entry per tab. */
5781
+ _dropWindowOnlyTabs() {
5782
+ if (!this.taxonomy.isWindowOnly) return [];
5783
+ const dropped = [];
5784
+ this._pruneRestoredTabs((t) => {
5785
+ if (!t || !this.taxonomy.isWindowOnly(t.kind)) return true;
5786
+ dropped.push(t.kind);
5787
+ return false;
5788
+ });
5789
+ return dropped;
5790
+ }
5731
5791
  /** A restored layout keeps the FIRST instance of each single kind: later
5732
5792
  * live tabs are dropped (the tile shows the tab that took the dropped
5733
5793
  * one's place, or closes when it held nothing else), and so are later
5734
5794
  * archived ones. Answers whether anything was dropped. */
5735
5795
  _dropDuplicateSingles() {
5736
5796
  const seen = /* @__PURE__ */ new Set();
5737
- let dropped = false;
5738
- const keep = (t) => {
5797
+ return this._pruneRestoredTabs((t) => {
5739
5798
  const k = t ? this._instanceKey(t.kind, t.props) : null;
5740
5799
  if (k == null) return true;
5741
5800
  if (seen.has(k)) return false;
5742
5801
  seen.add(k);
5743
5802
  return true;
5744
- };
5803
+ });
5804
+ }
5805
+ /** Drop every tab of the restored layout `keep(tab)` refuses, in the
5806
+ * tiles and in their archived pages; a tile left empty closes (seeding a
5807
+ * content tile if none is left). Returns whether anything went. */
5808
+ _pruneRestoredTabs(keep) {
5809
+ let dropped = false;
5745
5810
  for (const d of this.desktops.desktops) {
5746
5811
  const tree = d.tree;
5747
5812
  for (const leaf of tree.leaves()) {
@@ -5981,6 +6046,7 @@ var WindowManager = class _WindowManager {
5981
6046
  const tree = this._tree();
5982
6047
  const prior = tree.popActiveTabHistory?.(id);
5983
6048
  if (!prior || !prior.kind) return false;
6049
+ if (this._openWindowOnly(prior.kind, prior.props || {})) return true;
5984
6050
  if (this.revealInstance(prior.kind, prior.props || {})) return true;
5985
6051
  const curKind = tree.get(id)?.content?.kind;
5986
6052
  const curTopNav = curKind ? this.taxonomy.topNavFor(curKind) : null;
@@ -6097,6 +6163,7 @@ var WindowManager = class _WindowManager {
6097
6163
  * the leaf. Used by Backspace + breadcrumb segments + anything
6098
6164
  * else that should walk WITHIN the tile rather than reset it. */
6099
6165
  navigateActiveTab(leafId, kind, props = {}) {
6166
+ if (this._openWindowOnly(kind, props)) return;
6100
6167
  if (this.revealInstance(kind, props)) return;
6101
6168
  const tree = this._tree();
6102
6169
  const leaf = tree.get(leafId);
@@ -6151,6 +6218,7 @@ var WindowManager = class _WindowManager {
6151
6218
  * tab feature shipped), the behavior is identical to the old
6152
6219
  * setLeafContent: one tab in, one tab out. */
6153
6220
  openInLeaf(leafId, kind, props = {}) {
6221
+ if (this._openWindowOnly(kind, props)) return;
6154
6222
  if (this.revealInstance(kind, props)) return;
6155
6223
  const tree = this._tree();
6156
6224
  const leaf = tree.get(leafId);
@@ -6168,6 +6236,7 @@ var WindowManager = class _WindowManager {
6168
6236
  * previous mount, mounts the new kind into the same body element,
6169
6237
  * and updates the window's title. */
6170
6238
  openInWindow(winId, kind, props = {}) {
6239
+ if (this._openWindowOnly(kind, props)) return;
6171
6240
  if (this.revealInstance(kind, props)) return;
6172
6241
  const rec = this._windowToLeaf.get(winId);
6173
6242
  if (!rec) {
@@ -6207,6 +6276,7 @@ var WindowManager = class _WindowManager {
6207
6276
  * If only panel tiles exist (or the tree is empty), spawn a new
6208
6277
  * content leaf and canonicalize so the panels wrap it. */
6209
6278
  openInPrimary(kind, props = {}) {
6279
+ if (this._openWindowOnly(kind, props)) return;
6210
6280
  if (this.revealInstance(kind, props)) return;
6211
6281
  const tree = this._tree();
6212
6282
  let leafId = tree.primaryLeafId();
@@ -6267,6 +6337,7 @@ var WindowManager = class _WindowManager {
6267
6337
  * pane in a split never dismounts the original). Returns the new
6268
6338
  * leaf id, or null if the leaf can't be split. */
6269
6339
  splitLeafWith(leafId, dir, kind, props = {}, title = "") {
6340
+ if (this._openWindowOnly(kind, props)) return null;
6270
6341
  if (this.revealInstance(kind, props)) return null;
6271
6342
  const tree = this._tree();
6272
6343
  leafId = this._contentLeafId(leafId);
@@ -6676,6 +6747,9 @@ var WindowManager = class _WindowManager {
6676
6747
  canMaximize: true,
6677
6748
  canResize: true,
6678
6749
  modal: false,
6750
+ // BUG-0053. The id is minted per window and never opens again, so
6751
+ // geometry saved under it could never be restored.
6752
+ persistGeometry: false,
6679
6753
  // C15. Dropping a promoted window on a tile PUTS IT BACK — as that
6680
6754
  // tile's content, as a split of it, or as one of its tabs. Off
6681
6755
  // unless the embedder asked, because it changes what a drag to an
@@ -7130,6 +7204,7 @@ var WindowManager = class _WindowManager {
7130
7204
  bringBackWindow(windowId) {
7131
7205
  const rec = this._windowToLeaf.get(windowId);
7132
7206
  if (!rec) return false;
7207
+ if (this._holdsWindowOnly(rec)) return false;
7133
7208
  const tree = this.desktops.desktops[rec.desktopIdx]?.tree;
7134
7209
  const home = rec.homeLeafId ? tree?.get(rec.homeLeafId) : null;
7135
7210
  if (home && home.kind === "leaf") {
@@ -7748,6 +7823,7 @@ var WindowManager = class _WindowManager {
7748
7823
  dockWindowInto(windowId, target) {
7749
7824
  const rec = this._windowToLeaf.get(windowId);
7750
7825
  if (!rec || !target?.leafId) return false;
7826
+ if (this._holdsWindowOnly(rec)) return false;
7751
7827
  rec._dock = { ...target };
7752
7828
  rec._demoting = true;
7753
7829
  try {
@@ -7917,7 +7993,7 @@ var WindowManager = class _WindowManager {
7917
7993
  e.preventDefault();
7918
7994
  const rec = this._windowToLeaf.get(winId);
7919
7995
  if (!rec) return;
7920
- const items = [
7996
+ const items = this._holdsWindowOnly(rec) ? [] : [
7921
7997
  { label: "Back to tile", icon: "close_fullscreen", action: "back" }
7922
7998
  ];
7923
7999
  const targets = [...this.desktops.desktops.entries()].filter(([i, d]) => i !== rec.desktopIdx && !d.fixed);
@@ -8657,6 +8733,7 @@ var WindowManager = class _WindowManager {
8657
8733
  }
8658
8734
  /** The routing behind `navigate`, for a kind known to be registered. */
8659
8735
  _route(kind, props, opts) {
8736
+ if (this._openWindowOnly(kind, props)) return;
8660
8737
  const { ctx = null, transient = false } = opts;
8661
8738
  const background = !!opts.background;
8662
8739
  let { dest = "main", newTab = false } = opts;
@@ -8725,6 +8802,8 @@ var WindowManager = class _WindowManager {
8725
8802
  * source leaf — closing the window just disposes the content. */
8726
8803
  _navigateWindow(kind, props) {
8727
8804
  if (this.revealInstance(kind, props)) return;
8805
+ const size = this.taxonomy.windowSize?.(kind);
8806
+ const dockable = !this.taxonomy.isWindowOnly?.(kind);
8728
8807
  const winId = `twm-mw-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 5)}`;
8729
8808
  const contentEl = document.createElement("div");
8730
8809
  contentEl.className = "twm-window-content";
@@ -8741,17 +8820,22 @@ var WindowManager = class _WindowManager {
8741
8820
  title: mountInfo?.title || title,
8742
8821
  icon: "web_asset",
8743
8822
  content: contentEl,
8823
+ ...size?.width ? { defaultWidth: size.width } : {},
8824
+ ...size?.height ? { defaultHeight: size.height } : {},
8744
8825
  canMinimize: true,
8745
8826
  canMaximize: true,
8746
8827
  canResize: true,
8747
8828
  modal: false,
8829
+ // BUG-0053. The id is minted per window and never opens again, so
8830
+ // geometry saved under it could never be restored.
8831
+ persistGeometry: false,
8748
8832
  // C15, and this is the SECOND of the two places the WM builds a
8749
8833
  // window. Alt+N and "Open in new window" produce a window that is
8750
8834
  // every bit as dockable as a promoted one, and a window that can be
8751
8835
  // dragged onto a tile in one case and not the other is a rule
8752
8836
  // nobody can learn.
8753
- snap: this.snapPromotion,
8754
- snapController: this.snapPromotion ? this._snapController() : null,
8837
+ snap: dockable && this.snapPromotion,
8838
+ snapController: dockable && this.snapPromotion ? this._snapController() : null,
8755
8839
  // R1. THE PANE IS A BOX WITH `overflow: hidden`. A window contained
8756
8840
  // to one (C21) cannot be dragged a single pixel outside it, so
8757
8841
  // "drag a window from one tile to another" — the gesture all three
@@ -8769,9 +8853,11 @@ var WindowManager = class _WindowManager {
8769
8853
  // harder. So the maximize button docks — and the separate demote
8770
8854
  // button the WM used to inject beside it is gone, because two
8771
8855
  // buttons for one verb is how you get a chrome nobody reads.
8772
- onMaximize: () => this.bringBackWindow(winId),
8773
- maximizeIcon: "close_fullscreen",
8774
- maximizeTitle: "Back to tile",
8856
+ ...dockable ? {
8857
+ onMaximize: () => this.bringBackWindow(winId),
8858
+ maximizeIcon: "close_fullscreen",
8859
+ maximizeTitle: "Back to tile"
8860
+ } : {},
8775
8861
  onClose: () => this._onManagedWindowClosed(winId, mountInfo)
8776
8862
  });
8777
8863
  this._windowToLeaf.set(winId, {
@@ -8842,6 +8928,7 @@ var WindowManager = class _WindowManager {
8842
8928
  * routing) but uses `appendLeafTab` so the existing content
8843
8929
  * stays in place as a tab. */
8844
8930
  openInTabFromContext(ctx, kind, props = {}, transient = false, background = false) {
8931
+ if (this._openWindowOnly(kind, props)) return;
8845
8932
  if (this.revealInstance(kind, props)) return;
8846
8933
  if (ctx?.windowId && this._windowToLeaf.has(ctx.windowId)) {
8847
8934
  this.openInWindow(ctx.windowId, kind, props);
@@ -8875,6 +8962,7 @@ var WindowManager = class _WindowManager {
8875
8962
  * "Add row" button, which passes no ctx) reliably lands as a sibling
8876
8963
  * tab in the main tile rather than swapping its content. */
8877
8964
  openInTabInPrimary(kind, props = {}, transient = false, background = false) {
8965
+ if (this._openWindowOnly(kind, props)) return;
8878
8966
  if (this.revealInstance(kind, props)) return;
8879
8967
  const tree = this._tree();
8880
8968
  const leafId = tree.primaryLeafId();