flexdesk 0.17.14 → 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.
- package/CHANGELOG.md +39 -0
- package/css/base.css +237 -8
- package/css/flexdesk.css +344 -17
- package/css/overrides.css +6 -0
- package/dist/charts.js +104 -34
- package/dist/charts.js.map +3 -3
- package/dist/{chunk-2PM6ZMNS.js → chunk-2Z2ENHR3.js} +2 -2
- package/dist/{chunk-IG74S3ZH.js → chunk-7JI7HHSY.js} +2 -2
- package/dist/{chunk-TLIV6QSU.js → chunk-7OL6F2AM.js} +3 -3
- package/dist/{chunk-MADMGRX2.js → chunk-PGZ4EHSE.js} +156 -9
- package/dist/chunk-PGZ4EHSE.js.map +7 -0
- package/dist/{chunk-ZMH3OC7H.js → chunk-XJNADRGR.js} +4 -2
- package/dist/chunk-XJNADRGR.js.map +7 -0
- package/dist/flexdesk.css +344 -17
- package/dist/flow.js +3 -3
- package/dist/tiles.js +84 -9
- package/dist/tiles.js.map +2 -2
- package/dist/widgets.js +448 -5
- package/dist/widgets.js.map +4 -4
- package/dist/wm.js +110 -19
- package/dist/wm.js.map +2 -2
- package/docs/README.md +1 -1
- package/docs/components/charts.md +70 -3
- package/docs/components/forms-and-dialogs.md +80 -2
- package/docs/components/menus.md +85 -1
- package/docs/components/shell.md +52 -0
- package/docs/components/slide-out-panel.md +15 -0
- package/docs/components/tiles.md +37 -0
- package/package.json +1 -1
- package/src/charts/plot_config_editor.js +125 -29
- package/src/tiles/tile_base.js +48 -2
- package/src/tiles/tile_grid.js +42 -1
- package/src/tiling/kind_taxonomy.js +45 -0
- package/src/tiling/tile_breadcrumb.js +12 -4
- package/src/tiling/wm.js +86 -11
- package/src/ui/components/category_picker.js +556 -0
- package/src/ui/components/computing_status_window.js +3 -0
- package/src/ui/components/confirm_dialog.js +3 -0
- package/src/ui/components/managed_window.js +204 -9
- package/src/ui/components/modal.js +11 -2
- package/src/ui/components/wizard.js +4 -1
- package/widgets.js +2 -1
- package/dist/chunk-MADMGRX2.js.map +0 -7
- package/dist/chunk-ZMH3OC7H.js.map +0 -7
- /package/dist/{chunk-2PM6ZMNS.js.map → chunk-2Z2ENHR3.js.map} +0 -0
- /package/dist/{chunk-IG74S3ZH.js.map → chunk-7JI7HHSY.js.map} +0 -0
- /package/dist/{chunk-TLIV6QSU.js.map → chunk-7OL6F2AM.js.map} +0 -0
|
@@ -5,6 +5,9 @@ import {
|
|
|
5
5
|
// src/ui/components/managed_window.js
|
|
6
6
|
var DEFAULT_WINDOW_STORAGE_KEY = "flexdesk.managedWindows.v1";
|
|
7
7
|
var _storageKey = DEFAULT_WINDOW_STORAGE_KEY;
|
|
8
|
+
var DEFAULT_MAX_SAVED_WINDOWS = 50;
|
|
9
|
+
var MINTED_ID_PREFIX = "twm-mw-";
|
|
10
|
+
var _maxSavedWindows = DEFAULT_MAX_SAVED_WINDOWS;
|
|
8
11
|
var BASE_Z_INDEX = 6e3;
|
|
9
12
|
var MAX_Z_INDEX = 6999;
|
|
10
13
|
var TOP_BAR_HEIGHT = 35;
|
|
@@ -17,6 +20,7 @@ var SNAP_EDGE = 12;
|
|
|
17
20
|
var DEFAULT_ICON = "web_asset";
|
|
18
21
|
var _zIndexCounter = 0;
|
|
19
22
|
var _activeWindows = /* @__PURE__ */ new Map();
|
|
23
|
+
var _escAnswered = /* @__PURE__ */ new WeakSet();
|
|
20
24
|
function _collectFocusable(root) {
|
|
21
25
|
if (!root) return [];
|
|
22
26
|
const sel = [
|
|
@@ -39,7 +43,11 @@ function _loadState() {
|
|
|
39
43
|
if (_stateCache) return _stateCache;
|
|
40
44
|
try {
|
|
41
45
|
const raw = localStorage.getItem(_storageKey);
|
|
42
|
-
|
|
46
|
+
const parsed = raw ? JSON.parse(raw) : {};
|
|
47
|
+
_stateCache = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
48
|
+
for (const k of Object.keys(_stateCache)) {
|
|
49
|
+
if (k.startsWith(MINTED_ID_PREFIX)) delete _stateCache[k];
|
|
50
|
+
}
|
|
43
51
|
} catch {
|
|
44
52
|
_stateCache = {};
|
|
45
53
|
}
|
|
@@ -59,9 +67,50 @@ function _getWindowState(id) {
|
|
|
59
67
|
}
|
|
60
68
|
function _setWindowState(id, windowState) {
|
|
61
69
|
const state = _loadState();
|
|
62
|
-
|
|
70
|
+
let latest = 0;
|
|
71
|
+
for (const k of Object.keys(state)) latest = Math.max(latest, Number(state[k]?.u) || 0);
|
|
72
|
+
for (const k of Object.keys(state)) {
|
|
73
|
+
if (!(Number(state[k]?.u) > 0) && state[k] && typeof state[k] === "object") {
|
|
74
|
+
state[k] = { ...state[k], u: ++latest };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
state[id] = { ...windowState, u: latest + 1 };
|
|
78
|
+
const ids = Object.keys(state);
|
|
79
|
+
if (ids.length > _maxSavedWindows) {
|
|
80
|
+
ids.sort((a, b) => (Number(state[a]?.u) || 0) - (Number(state[b]?.u) || 0));
|
|
81
|
+
for (const k of ids.slice(0, ids.length - _maxSavedWindows)) delete state[k];
|
|
82
|
+
}
|
|
63
83
|
_saveState(state);
|
|
64
84
|
}
|
|
85
|
+
function _focusFirstIn(root) {
|
|
86
|
+
if (!root) return false;
|
|
87
|
+
const sel = [
|
|
88
|
+
"a[href]",
|
|
89
|
+
"button:not([disabled])",
|
|
90
|
+
'input:not([disabled]):not([type="hidden"])',
|
|
91
|
+
"select:not([disabled])",
|
|
92
|
+
"textarea:not([disabled])",
|
|
93
|
+
'[contenteditable="true"]',
|
|
94
|
+
'[tabindex]:not([tabindex="-1"])'
|
|
95
|
+
].join(",");
|
|
96
|
+
for (const el2 of root.querySelectorAll(sel)) {
|
|
97
|
+
if (el2.hidden || el2.closest("[hidden]")) continue;
|
|
98
|
+
try {
|
|
99
|
+
el2.focus({ preventScroll: true });
|
|
100
|
+
} catch {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (el2.ownerDocument.activeElement === el2) return true;
|
|
104
|
+
}
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
function _escAnswerer() {
|
|
108
|
+
const visible = Array.from(_activeWindows.values()).filter((w) => w.isVisible && !w.isMinimized && w.element).sort((a, b) => b.zIndex - a.zIndex);
|
|
109
|
+
if (visible[0]?.modal) return visible[0];
|
|
110
|
+
const active = document.activeElement;
|
|
111
|
+
const holder = active ? visible.find((w) => w.element.contains(active)) : null;
|
|
112
|
+
return holder || visible.find((w) => w.modal) || null;
|
|
113
|
+
}
|
|
65
114
|
var ManagedWindow = class {
|
|
66
115
|
/**
|
|
67
116
|
* @param {Object} options
|
|
@@ -99,6 +148,15 @@ var ManagedWindow = class {
|
|
|
99
148
|
* away.
|
|
100
149
|
* @param {Array} [options.titlebarButtons] - Extra buttons left of
|
|
101
150
|
* minimize: `{icon, title, onClick}`.
|
|
151
|
+
* @param {boolean} [options.persistGeometry=true] - Save and restore this
|
|
152
|
+
* window's position and size under its id. Pass false for a window whose
|
|
153
|
+
* id is minted per opening, so it never writes an entry.
|
|
154
|
+
* @param {boolean|'window'} [options.focusOnOpen=true] - When the window
|
|
155
|
+
* opens, move focus to the first focusable element of its content (the
|
|
156
|
+
* window itself when it has none; always the window itself with
|
|
157
|
+
* `'window'`), and on close give focus back to the element that had it
|
|
158
|
+
* before, if that element is still in the document and focus is still in
|
|
159
|
+
* this window or nowhere.
|
|
102
160
|
*/
|
|
103
161
|
constructor(options) {
|
|
104
162
|
this.id = options.id;
|
|
@@ -135,6 +193,10 @@ var ManagedWindow = class {
|
|
|
135
193
|
this._minimizeTimer = null;
|
|
136
194
|
this._restoreTimer = null;
|
|
137
195
|
this.titlebarButtons = Array.isArray(options.titlebarButtons) ? options.titlebarButtons : [];
|
|
196
|
+
this.persistGeometry = options.persistGeometry ?? true;
|
|
197
|
+
this.focusOnOpen = options.focusOnOpen ?? true;
|
|
198
|
+
this._returnFocusTo = null;
|
|
199
|
+
this._closed = false;
|
|
138
200
|
this.element = null;
|
|
139
201
|
this.backdropElement = null;
|
|
140
202
|
this.contentContainer = null;
|
|
@@ -159,6 +221,10 @@ var ManagedWindow = class {
|
|
|
159
221
|
* Show the window.
|
|
160
222
|
*/
|
|
161
223
|
show() {
|
|
224
|
+
if (this._closed) {
|
|
225
|
+
console.warn(`[ManagedWindow] show() on closed window "${this.id}"; build a new ManagedWindow to reopen it`);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
162
228
|
if (this.isVisible && !this.isMinimized) {
|
|
163
229
|
if (this.element && this.element.style.display === "none") {
|
|
164
230
|
this.element.style.display = "";
|
|
@@ -166,6 +232,7 @@ var ManagedWindow = class {
|
|
|
166
232
|
this.bringToFront();
|
|
167
233
|
return;
|
|
168
234
|
}
|
|
235
|
+
let opened = false;
|
|
169
236
|
if (!this.element) {
|
|
170
237
|
this._build();
|
|
171
238
|
this._restoreState();
|
|
@@ -190,9 +257,36 @@ var ManagedWindow = class {
|
|
|
190
257
|
}
|
|
191
258
|
this._installContainerResizeObserver();
|
|
192
259
|
this.isVisible = true;
|
|
260
|
+
opened = true;
|
|
193
261
|
}
|
|
194
262
|
this.bringToFront();
|
|
195
263
|
document.addEventListener("keydown", this._boundOnKeyDown);
|
|
264
|
+
if (opened && this.focusOnOpen) this._moveFocusIn();
|
|
265
|
+
}
|
|
266
|
+
/** BUG-0053. Focus into the content on open, remembering where it was. */
|
|
267
|
+
_moveFocusIn() {
|
|
268
|
+
const active = document.activeElement;
|
|
269
|
+
this._returnFocusTo = active && active !== document.body && !this.element.contains(active) ? active : null;
|
|
270
|
+
if (this.focusOnOpen !== "window" && _focusFirstIn(this.contentContainer)) return;
|
|
271
|
+
try {
|
|
272
|
+
this.element.focus({ preventScroll: true });
|
|
273
|
+
} catch {
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
/** BUG-0053. Give focus back on close — only when it is still in this
|
|
277
|
+
* window (or nowhere): a window closed while the user works elsewhere
|
|
278
|
+
* must not pull focus away from where they are. */
|
|
279
|
+
_returnFocus() {
|
|
280
|
+
const back = this._returnFocusTo;
|
|
281
|
+
this._returnFocusTo = null;
|
|
282
|
+
if (!back || !back.isConnected) return;
|
|
283
|
+
const active = document.activeElement;
|
|
284
|
+
const held = !active || active === document.body || this.element && this.element.contains(active);
|
|
285
|
+
if (!held) return;
|
|
286
|
+
try {
|
|
287
|
+
back.focus({ preventScroll: true });
|
|
288
|
+
} catch {
|
|
289
|
+
}
|
|
196
290
|
}
|
|
197
291
|
/**
|
|
198
292
|
* Hide/close the window.
|
|
@@ -219,6 +313,7 @@ var ManagedWindow = class {
|
|
|
219
313
|
if (this.onClose) {
|
|
220
314
|
this.onClose();
|
|
221
315
|
}
|
|
316
|
+
if (this.focusOnOpen) this._returnFocus();
|
|
222
317
|
if (this.element && this.element.parentNode) {
|
|
223
318
|
this.element.parentNode.removeChild(this.element);
|
|
224
319
|
}
|
|
@@ -235,6 +330,38 @@ var ManagedWindow = class {
|
|
|
235
330
|
window.dispatchEvent(new CustomEvent("managed-window-closed", {
|
|
236
331
|
detail: { id: this.id, container: this.container }
|
|
237
332
|
}));
|
|
333
|
+
this._release();
|
|
334
|
+
}
|
|
335
|
+
/** BUG-0053. A closed window leaves the registry and lets go of what it
|
|
336
|
+
* held: its content, its DOM and every consumer callback (an `onClose`
|
|
337
|
+
* typically captures its owner). Until now the registry kept all of it
|
|
338
|
+
* alive until the same id opened again, and `get(id)` answered with the
|
|
339
|
+
* dead window. */
|
|
340
|
+
_release() {
|
|
341
|
+
if (_activeWindows.get(this.id) === this) _activeWindows.delete(this.id);
|
|
342
|
+
this._closed = true;
|
|
343
|
+
if (!this._dragState && !this._resizeState) {
|
|
344
|
+
document.removeEventListener("pointermove", this._boundOnPointerMove);
|
|
345
|
+
document.removeEventListener("pointerup", this._boundOnPointerUp);
|
|
346
|
+
document.removeEventListener("pointercancel", this._boundOnPointerUp);
|
|
347
|
+
}
|
|
348
|
+
this.content = null;
|
|
349
|
+
this.element = null;
|
|
350
|
+
this.contentContainer = null;
|
|
351
|
+
this.backdropElement = null;
|
|
352
|
+
this.onClose = null;
|
|
353
|
+
this.beforeClose = null;
|
|
354
|
+
this.onMinimize = null;
|
|
355
|
+
this.onMaximize = null;
|
|
356
|
+
this.snapController = null;
|
|
357
|
+
this.dragHost = null;
|
|
358
|
+
this.dragBounds = null;
|
|
359
|
+
this.titlebarButtons = [];
|
|
360
|
+
}
|
|
361
|
+
/** True once the window has closed. A closed window cannot be shown
|
|
362
|
+
* again; `ManagedWindow.get(id)` no longer returns it. */
|
|
363
|
+
get isClosed() {
|
|
364
|
+
return this._closed;
|
|
238
365
|
}
|
|
239
366
|
/**
|
|
240
367
|
* Minimize to taskbar with animation.
|
|
@@ -521,6 +648,7 @@ var ManagedWindow = class {
|
|
|
521
648
|
this.element.classList.add("twm-managed-window--modal");
|
|
522
649
|
}
|
|
523
650
|
this.element.setAttribute("data-window-id", this.id);
|
|
651
|
+
this.element.tabIndex = -1;
|
|
524
652
|
const topbar = document.createElement("div");
|
|
525
653
|
topbar.className = "twm-managed-window__topbar";
|
|
526
654
|
const icon = document.createElement("span");
|
|
@@ -613,7 +741,15 @@ var ManagedWindow = class {
|
|
|
613
741
|
if (this.canMaximize) {
|
|
614
742
|
topbar.addEventListener("dblclick", () => this.toggleMaximize());
|
|
615
743
|
}
|
|
616
|
-
this.element.addEventListener("pointerdown", () =>
|
|
744
|
+
this.element.addEventListener("pointerdown", () => {
|
|
745
|
+
this.bringToFront();
|
|
746
|
+
if (this.element && !this.element.contains(document.activeElement)) {
|
|
747
|
+
try {
|
|
748
|
+
this.element.focus({ preventScroll: true });
|
|
749
|
+
} catch {
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
});
|
|
617
753
|
}
|
|
618
754
|
_addResizeHandles() {
|
|
619
755
|
const directions = ["n", "s", "e", "w", "ne", "nw", "se", "sw"];
|
|
@@ -883,7 +1019,7 @@ var ManagedWindow = class {
|
|
|
883
1019
|
const bounds = this._bounds();
|
|
884
1020
|
const maxWidth = bounds.width;
|
|
885
1021
|
const maxHeight = bounds.height;
|
|
886
|
-
const saved = this.modal ? null : _getWindowState(this.id);
|
|
1022
|
+
const saved = this.modal || !this.persistGeometry ? null : _getWindowState(this.id);
|
|
887
1023
|
if (saved) {
|
|
888
1024
|
this.x = saved.x ?? this.x;
|
|
889
1025
|
this.y = saved.y ?? this.y;
|
|
@@ -904,7 +1040,7 @@ var ManagedWindow = class {
|
|
|
904
1040
|
}
|
|
905
1041
|
}
|
|
906
1042
|
_saveCurrentState() {
|
|
907
|
-
if (this.modal) return;
|
|
1043
|
+
if (this.modal || !this.persistGeometry || this._closed) return;
|
|
908
1044
|
_setWindowState(this.id, {
|
|
909
1045
|
x: this._preMaximizeState?.x ?? this.x,
|
|
910
1046
|
y: this._preMaximizeState?.y ?? this.y,
|
|
@@ -1183,11 +1319,15 @@ var ManagedWindow = class {
|
|
|
1183
1319
|
_onKeyDown(e) {
|
|
1184
1320
|
if (!this.isVisible || this.isMinimized) return;
|
|
1185
1321
|
const topWindow = Array.from(_activeWindows.values()).filter((w) => w.isVisible && !w.isMinimized).sort((a, b) => b.zIndex - a.zIndex)[0];
|
|
1186
|
-
if (topWindow !== this) return;
|
|
1187
1322
|
if (e.key === "Escape") {
|
|
1188
|
-
|
|
1323
|
+
if (e.defaultPrevented || e.isComposing || _escAnswered.has(e)) return;
|
|
1324
|
+
if (_escAnswerer() === this) {
|
|
1325
|
+
_escAnswered.add(e);
|
|
1326
|
+
this.close();
|
|
1327
|
+
}
|
|
1189
1328
|
return;
|
|
1190
1329
|
}
|
|
1330
|
+
if (topWindow !== this) return;
|
|
1191
1331
|
if (e.key === "Tab" && this.modal && this.element) {
|
|
1192
1332
|
const focusables = _collectFocusable(this.element);
|
|
1193
1333
|
if (focusables.length === 0) {
|
|
@@ -1219,9 +1359,15 @@ var ManagedWindow = class {
|
|
|
1219
1359
|
*
|
|
1220
1360
|
* @param {object} [opts]
|
|
1221
1361
|
* @param {string} [opts.storageKey=DEFAULT_WINDOW_STORAGE_KEY]
|
|
1362
|
+
* @param {number} [opts.maxSavedWindows=DEFAULT_MAX_SAVED_WINDOWS] How many
|
|
1363
|
+
* ids keep saved geometry; the least recently saved go first.
|
|
1222
1364
|
*/
|
|
1223
|
-
static configure({
|
|
1365
|
+
static configure({
|
|
1366
|
+
storageKey = DEFAULT_WINDOW_STORAGE_KEY,
|
|
1367
|
+
maxSavedWindows = DEFAULT_MAX_SAVED_WINDOWS
|
|
1368
|
+
} = {}) {
|
|
1224
1369
|
_storageKey = storageKey;
|
|
1370
|
+
_maxSavedWindows = Math.max(1, Math.floor(maxSavedWindows) || DEFAULT_MAX_SAVED_WINDOWS);
|
|
1225
1371
|
_stateCache = null;
|
|
1226
1372
|
}
|
|
1227
1373
|
/** @returns {string} The localStorage key window geometry uses. */
|
|
@@ -1894,6 +2040,7 @@ function isRowActivation(ev, gesture = "click", { scope = null } = {}) {
|
|
|
1894
2040
|
|
|
1895
2041
|
export {
|
|
1896
2042
|
DEFAULT_WINDOW_STORAGE_KEY,
|
|
2043
|
+
DEFAULT_MAX_SAVED_WINDOWS,
|
|
1897
2044
|
ManagedWindow,
|
|
1898
2045
|
ToggleField,
|
|
1899
2046
|
isHexColor,
|
|
@@ -1910,4 +2057,4 @@ export {
|
|
|
1910
2057
|
endsTextSelection,
|
|
1911
2058
|
isRowActivation
|
|
1912
2059
|
};
|
|
1913
|
-
//# sourceMappingURL=chunk-
|
|
2060
|
+
//# sourceMappingURL=chunk-PGZ4EHSE.js.map
|