flexdesk 0.4.4 → 0.4.6
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 +10 -0
- package/css/base.css +102 -12
- package/css/flexdesk.css +120 -18
- package/css/overrides.css +15 -5
- package/dist/charts.js +2 -2
- package/dist/{chunk-P4AALM3A.js → chunk-CYTWMP2Y.js} +609 -55
- package/dist/chunk-CYTWMP2Y.js.map +7 -0
- package/dist/{chunk-4RGQSRMC.js → chunk-HWCX5I4R.js} +2 -2
- package/dist/{chunk-QNQHQ24V.js → chunk-NLTOY57M.js} +72 -1
- package/dist/chunk-NLTOY57M.js.map +7 -0
- package/dist/editor.js +1 -1
- package/dist/flexdesk.css +120 -18
- package/dist/tiles.js +2 -2
- package/dist/widgets.js +230 -1
- package/dist/widgets.js.map +3 -3
- package/dist/wm.js +13 -2
- package/dist/wm.js.map +2 -2
- package/package.json +1 -1
- package/src/editor/notebook_tab_bar.js +89 -0
- package/src/tiling/tile_tree.js +5 -0
- package/src/tiling/wm.js +14 -1
- package/src/ui/components/data_table.js +688 -63
- package/src/ui/utils/auto_scrollbars.js +270 -0
- package/dist/chunk-P4AALM3A.js.map +0 -7
- package/dist/chunk-QNQHQ24V.js.map +0 -7
- /package/dist/{chunk-4RGQSRMC.js.map → chunk-HWCX5I4R.js.map} +0 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* auto_scrollbars.js — the overlay scrollbar, on EVERY scrollable element.
|
|
3
|
+
*
|
|
4
|
+
* `installOverlayScrollbar` (overlay_scrollbar.js) gives one container a thin
|
|
5
|
+
* bar that shows on hover and fades away. It is installed container by
|
|
6
|
+
* container, it puts the bar INSIDE the container, and it holds a resize
|
|
7
|
+
* observer, a subtree mutation observer and a window listener for each one.
|
|
8
|
+
* That is fine for a handful of known panes and wrong for "all of them": a
|
|
9
|
+
* page that redraws with `innerHTML = ''` deletes its bar for good, and a
|
|
10
|
+
* table body, a dropdown, a textarea and a dialog each want one.
|
|
11
|
+
*
|
|
12
|
+
* So this is the same look, done once for a whole subtree:
|
|
13
|
+
*
|
|
14
|
+
* - Native scrollbars are hidden under `root`, so no element reserves
|
|
15
|
+
* layout width for one and nothing reflows when a bar appears.
|
|
16
|
+
* - Bars live in ONE fixed layer on `document.body`, positioned over the
|
|
17
|
+
* element they belong to. Nothing is added to a scroll container, so a
|
|
18
|
+
* redraw cannot remove a bar, and a textarea (which cannot hold children)
|
|
19
|
+
* gets one too.
|
|
20
|
+
* - An element gets its bars when the pointer is over it or it scrolls, if
|
|
21
|
+
* it actually overflows. They fade out a moment after the pointer leaves
|
|
22
|
+
* or the scrolling stops. Nested scroll areas each show their own.
|
|
23
|
+
* - The thumb drags and the track pages, as a native bar does.
|
|
24
|
+
*
|
|
25
|
+
* Discovery is lazy: the pointer and scroll events name the elements worth
|
|
26
|
+
* measuring, so nothing walks the DOM looking for scroll areas.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
const LAYER_ID = 'twm-autoscroll-layer';
|
|
30
|
+
const ROOT_CLASS = 'twm-autoscroll';
|
|
31
|
+
const HIDE_AFTER_MS = 900;
|
|
32
|
+
const MIN_THUMB = 30;
|
|
33
|
+
const BAR = 6;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Where the thumb goes. Pure, so it can be tested without a layout engine.
|
|
37
|
+
*
|
|
38
|
+
* @param {number} scrollSize scrollHeight / scrollWidth
|
|
39
|
+
* @param {number} clientSize clientHeight / clientWidth
|
|
40
|
+
* @param {number} scrollPos scrollTop / scrollLeft
|
|
41
|
+
* @param {number} trackLen the bar's length on screen (px)
|
|
42
|
+
* @returns {{size:number, pos:number}|null} null when nothing overflows
|
|
43
|
+
*/
|
|
44
|
+
export function thumbGeometry(scrollSize, clientSize, scrollPos, trackLen) {
|
|
45
|
+
const max = scrollSize - clientSize;
|
|
46
|
+
if (!(max > 1) || !(trackLen > 0)) return null;
|
|
47
|
+
const size = Math.min(trackLen, Math.max(MIN_THUMB, trackLen * clientSize / scrollSize));
|
|
48
|
+
const pos = (trackLen - size) * Math.min(1, Math.max(0, scrollPos / max));
|
|
49
|
+
return { size, pos };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Whether an element scrolls on an axis: its overflow allows it and its
|
|
53
|
+
* content is larger than its box. */
|
|
54
|
+
function scrollsOn(el, axis) {
|
|
55
|
+
if (!(el instanceof Element)) return false;
|
|
56
|
+
const cs = getComputedStyle(el);
|
|
57
|
+
const overflow = axis === 'y' ? cs.overflowY : cs.overflowX;
|
|
58
|
+
if (overflow !== 'auto' && overflow !== 'scroll' && !(el.tagName === 'TEXTAREA' && overflow !== 'hidden')) return false;
|
|
59
|
+
return axis === 'y' ? el.scrollHeight - el.clientHeight > 1 : el.scrollWidth - el.clientWidth > 1;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Give every scrollable element under `root` an auto-hiding overlay scrollbar.
|
|
64
|
+
*
|
|
65
|
+
* @param {HTMLElement} [root=document.body]
|
|
66
|
+
* @param {{exclude?: (el: Element) => boolean}} [opts] elements to leave to
|
|
67
|
+
* their own scrollbar. Containers that already carry an
|
|
68
|
+
* `installOverlayScrollbar` bar are always left alone.
|
|
69
|
+
* @returns {{dispose: () => void}}
|
|
70
|
+
*/
|
|
71
|
+
export function installAutoScrollbars(root = document.body, { exclude = null } = {}) {
|
|
72
|
+
const doc = root.ownerDocument || document;
|
|
73
|
+
const win = doc.defaultView || window;
|
|
74
|
+
root.classList.add(ROOT_CLASS);
|
|
75
|
+
|
|
76
|
+
let layer = doc.getElementById(LAYER_ID);
|
|
77
|
+
if (!layer) {
|
|
78
|
+
layer = doc.createElement('div');
|
|
79
|
+
layer.id = LAYER_ID;
|
|
80
|
+
layer.className = 'twm-autoscroll-layer';
|
|
81
|
+
doc.body.appendChild(layer);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** element -> { v, h, timer, hovered } */
|
|
85
|
+
const managed = new Map();
|
|
86
|
+
let hoverChain = new Set();
|
|
87
|
+
|
|
88
|
+
const eligible = (el) => el instanceof Element
|
|
89
|
+
&& root.contains(el)
|
|
90
|
+
&& !el.__overlayScrollbarInstalled
|
|
91
|
+
&& !el.closest?.('.twm-autoscroll-layer')
|
|
92
|
+
&& !(exclude && exclude(el));
|
|
93
|
+
|
|
94
|
+
const makeBar = (el, axis) => {
|
|
95
|
+
const bar = doc.createElement('div');
|
|
96
|
+
bar.className = `twm-autoscroll-bar twm-autoscroll-bar--${axis}`;
|
|
97
|
+
const thumb = doc.createElement('div');
|
|
98
|
+
thumb.className = 'twm-autoscroll-thumb';
|
|
99
|
+
bar.appendChild(thumb);
|
|
100
|
+
layer.appendChild(bar);
|
|
101
|
+
|
|
102
|
+
// The track pages, like a native bar's.
|
|
103
|
+
bar.addEventListener('mousedown', (e) => {
|
|
104
|
+
if (e.target !== bar) return;
|
|
105
|
+
e.preventDefault();
|
|
106
|
+
const r = thumb.getBoundingClientRect();
|
|
107
|
+
const before = axis === 'y' ? e.clientY < r.top : e.clientX < r.left;
|
|
108
|
+
const page = (axis === 'y' ? el.clientHeight : el.clientWidth) * 0.9;
|
|
109
|
+
if (axis === 'y') el.scrollTop += before ? -page : page;
|
|
110
|
+
else el.scrollLeft += before ? -page : page;
|
|
111
|
+
});
|
|
112
|
+
// The thumb drags.
|
|
113
|
+
thumb.addEventListener('mousedown', (e) => {
|
|
114
|
+
e.preventDefault();
|
|
115
|
+
e.stopPropagation();
|
|
116
|
+
const rec = managed.get(el);
|
|
117
|
+
if (rec) rec.dragging = true;
|
|
118
|
+
const start = axis === 'y' ? e.clientY : e.clientX;
|
|
119
|
+
const startScroll = axis === 'y' ? el.scrollTop : el.scrollLeft;
|
|
120
|
+
const trackLen = axis === 'y' ? bar.getBoundingClientRect().height : bar.getBoundingClientRect().width;
|
|
121
|
+
const thumbLen = axis === 'y' ? thumb.getBoundingClientRect().height : thumb.getBoundingClientRect().width;
|
|
122
|
+
const max = axis === 'y' ? el.scrollHeight - el.clientHeight : el.scrollWidth - el.clientWidth;
|
|
123
|
+
const perPx = trackLen > thumbLen ? max / (trackLen - thumbLen) : 0;
|
|
124
|
+
bar.classList.add('twm-autoscroll-bar--active');
|
|
125
|
+
const move = (mv) => {
|
|
126
|
+
const d = (axis === 'y' ? mv.clientY : mv.clientX) - start;
|
|
127
|
+
if (axis === 'y') el.scrollTop = startScroll + d * perPx;
|
|
128
|
+
else el.scrollLeft = startScroll + d * perPx;
|
|
129
|
+
};
|
|
130
|
+
const up = () => {
|
|
131
|
+
doc.removeEventListener('mousemove', move);
|
|
132
|
+
doc.removeEventListener('mouseup', up);
|
|
133
|
+
bar.classList.remove('twm-autoscroll-bar--active');
|
|
134
|
+
if (rec) rec.dragging = false;
|
|
135
|
+
hideLater(el);
|
|
136
|
+
};
|
|
137
|
+
doc.addEventListener('mousemove', move);
|
|
138
|
+
doc.addEventListener('mouseup', up);
|
|
139
|
+
});
|
|
140
|
+
bar.addEventListener('mouseenter', () => { const rec = managed.get(el); if (rec) { rec.onBar = true; show(el); } });
|
|
141
|
+
bar.addEventListener('mouseleave', () => { const rec = managed.get(el); if (rec) { rec.onBar = false; hideLater(el); } });
|
|
142
|
+
return { bar, thumb };
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const record = (el) => {
|
|
146
|
+
let rec = managed.get(el);
|
|
147
|
+
if (!rec) {
|
|
148
|
+
rec = { v: null, h: null, timer: 0, dragging: false, onBar: false };
|
|
149
|
+
managed.set(el, rec);
|
|
150
|
+
}
|
|
151
|
+
return rec;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
/** Place and size an element's bars; drop the element when it is gone. */
|
|
155
|
+
const place = (el) => {
|
|
156
|
+
const rec = managed.get(el);
|
|
157
|
+
if (!rec) return;
|
|
158
|
+
if (!el.isConnected) { drop(el); return; }
|
|
159
|
+
const r = el.getBoundingClientRect();
|
|
160
|
+
const vw = win.innerWidth;
|
|
161
|
+
const vh = win.innerHeight;
|
|
162
|
+
const top = Math.max(0, r.top);
|
|
163
|
+
const left = Math.max(0, r.left);
|
|
164
|
+
const bottom = Math.min(vh, r.bottom);
|
|
165
|
+
const right = Math.min(vw, r.right);
|
|
166
|
+
// Screen px per CSS px, for content inside a zoomed tile.
|
|
167
|
+
const z = el.offsetHeight ? r.height / el.offsetHeight : 1;
|
|
168
|
+
for (const axis of ['y', 'x']) {
|
|
169
|
+
const key = axis === 'y' ? 'v' : 'h';
|
|
170
|
+
const overflows = scrollsOn(el, axis);
|
|
171
|
+
if (!overflows) { if (rec[key]) rec[key].bar.style.display = 'none'; continue; }
|
|
172
|
+
if (!rec[key]) rec[key] = makeBar(el, axis);
|
|
173
|
+
const { bar, thumb } = rec[key];
|
|
174
|
+
bar.style.display = '';
|
|
175
|
+
if (axis === 'y') {
|
|
176
|
+
const len = bottom - top;
|
|
177
|
+
Object.assign(bar.style, { top: `${top}px`, left: `${right - BAR - 1}px`, height: `${len}px`, width: `${BAR}px` });
|
|
178
|
+
const g = thumbGeometry(el.scrollHeight * z, el.clientHeight * z, el.scrollTop * z, len);
|
|
179
|
+
if (!g) { bar.style.display = 'none'; continue; }
|
|
180
|
+
Object.assign(thumb.style, { top: `${g.pos}px`, height: `${g.size}px`, left: '0', width: '100%' });
|
|
181
|
+
} else {
|
|
182
|
+
const len = right - left;
|
|
183
|
+
Object.assign(bar.style, { left: `${left}px`, top: `${bottom - BAR - 1}px`, width: `${len}px`, height: `${BAR}px` });
|
|
184
|
+
const g = thumbGeometry(el.scrollWidth * z, el.clientWidth * z, el.scrollLeft * z, len);
|
|
185
|
+
if (!g) { bar.style.display = 'none'; continue; }
|
|
186
|
+
Object.assign(thumb.style, { left: `${g.pos}px`, width: `${g.size}px`, top: '0', height: '100%' });
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const setVisible = (el, on) => {
|
|
192
|
+
const rec = managed.get(el);
|
|
193
|
+
if (!rec) return;
|
|
194
|
+
for (const b of [rec.v, rec.h]) if (b) b.bar.classList.toggle('twm-autoscroll-bar--on', on);
|
|
195
|
+
};
|
|
196
|
+
const show = (el) => {
|
|
197
|
+
const rec = record(el);
|
|
198
|
+
clearTimeout(rec.timer);
|
|
199
|
+
place(el);
|
|
200
|
+
setVisible(el, true);
|
|
201
|
+
};
|
|
202
|
+
const hideLater = (el) => {
|
|
203
|
+
const rec = managed.get(el);
|
|
204
|
+
if (!rec) return;
|
|
205
|
+
clearTimeout(rec.timer);
|
|
206
|
+
rec.timer = setTimeout(() => {
|
|
207
|
+
if (rec.dragging || rec.onBar || hoverChain.has(el)) return;
|
|
208
|
+
setVisible(el, false);
|
|
209
|
+
}, HIDE_AFTER_MS);
|
|
210
|
+
};
|
|
211
|
+
const drop = (el) => {
|
|
212
|
+
const rec = managed.get(el);
|
|
213
|
+
if (!rec) return;
|
|
214
|
+
clearTimeout(rec.timer);
|
|
215
|
+
rec.v?.bar.remove();
|
|
216
|
+
rec.h?.bar.remove();
|
|
217
|
+
managed.delete(el);
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
/** The scrollable elements from `target` up to `root`. */
|
|
221
|
+
const scrollChain = (target) => {
|
|
222
|
+
const out = new Set();
|
|
223
|
+
for (let el = target instanceof Element ? target : target?.parentElement; el && el !== doc.documentElement; el = el.parentElement) {
|
|
224
|
+
if (el.closest?.('.twm-autoscroll-layer')) return out;
|
|
225
|
+
if (eligible(el) && (scrollsOn(el, 'y') || scrollsOn(el, 'x'))) out.add(el);
|
|
226
|
+
if (el === root) break;
|
|
227
|
+
}
|
|
228
|
+
return out;
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const onOver = (e) => {
|
|
232
|
+
if (e.target?.closest?.('.twm-autoscroll-layer')) return;
|
|
233
|
+
const next = scrollChain(e.target);
|
|
234
|
+
for (const el of hoverChain) if (!next.has(el)) hideLater(el);
|
|
235
|
+
for (const el of next) show(el);
|
|
236
|
+
hoverChain = next;
|
|
237
|
+
};
|
|
238
|
+
const onLeaveWindow = (e) => {
|
|
239
|
+
if (e.relatedTarget) return;
|
|
240
|
+
for (const el of hoverChain) hideLater(el);
|
|
241
|
+
hoverChain = new Set();
|
|
242
|
+
};
|
|
243
|
+
const onScroll = (e) => {
|
|
244
|
+
const el = e.target === doc ? null : e.target;
|
|
245
|
+
if (el && eligible(el)) { show(el); hideLater(el); }
|
|
246
|
+
// A scrolling ancestor moves every bar inside it.
|
|
247
|
+
for (const other of managed.keys()) if (other !== el) place(other);
|
|
248
|
+
};
|
|
249
|
+
let raf = 0;
|
|
250
|
+
const onResize = () => {
|
|
251
|
+
cancelAnimationFrame(raf);
|
|
252
|
+
raf = requestAnimationFrame(() => { for (const el of managed.keys()) place(el); });
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
doc.addEventListener('mouseover', onOver, true);
|
|
256
|
+
doc.addEventListener('mouseout', onLeaveWindow, true);
|
|
257
|
+
doc.addEventListener('scroll', onScroll, true);
|
|
258
|
+
win.addEventListener('resize', onResize);
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
dispose() {
|
|
262
|
+
doc.removeEventListener('mouseover', onOver, true);
|
|
263
|
+
doc.removeEventListener('mouseout', onLeaveWindow, true);
|
|
264
|
+
doc.removeEventListener('scroll', onScroll, true);
|
|
265
|
+
win.removeEventListener('resize', onResize);
|
|
266
|
+
for (const el of [...managed.keys()]) drop(el);
|
|
267
|
+
root.classList.remove(ROOT_CLASS);
|
|
268
|
+
},
|
|
269
|
+
};
|
|
270
|
+
}
|