flexdesk 0.4.6 → 0.4.7
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 +301 -276
- package/dist/charts.js +782 -782
- package/dist/{chunk-HWCX5I4R.js → chunk-F5FYDCOA.js} +2373 -2373
- package/dist/{chunk-HWCX5I4R.js.map → chunk-F5FYDCOA.js.map} +7 -7
- package/dist/{chunk-CYTWMP2Y.js → chunk-WMGOJ3I6.js} +2628 -2622
- package/dist/chunk-WMGOJ3I6.js.map +7 -0
- package/dist/tiles.js +2195 -2195
- package/dist/widgets.js +5796 -5796
- package/dist/wm.js +8074 -8015
- package/dist/wm.js.map +7 -7
- package/package.json +81 -81
- package/src/tiling/tile_breadcrumb.js +21 -0
- package/src/tiling/tile_renderer.js +11 -0
- package/src/tiling/wm.js +3916 -3872
- package/src/ui/components/data_table.js +3294 -3281
- package/dist/chunk-CYTWMP2Y.js.map +0 -7
package/README.md
CHANGED
|
@@ -1,276 +1,301 @@
|
|
|
1
|
-
# FlexDesk
|
|
2
|
-
|
|
3
|
-
A tiling window manager and widget set for desktop-class web applications.
|
|
4
|
-
|
|
5
|
-
Tiles that split and tab, a command palette, dockable panels, virtual desktops, a
|
|
6
|
-
DataTable with sticky headers and CSV export, managed windows, modals, toasts,
|
|
7
|
-
trees and context menus.
|
|
8
|
-
|
|
9
|
-
**Zero runtime dependencies.** No React, no framework, nothing. If you install
|
|
10
|
-
FlexDesk, you install FlexDesk.
|
|
11
|
-
|
|
12
|
-
FlexDesk was extracted from [EcoAgent](https://github.com/nor-os/EcoAgent), a
|
|
13
|
-
macroeconomic simulation platform, and it knows nothing about economics: no
|
|
14
|
-
application vocabulary, no import of anything application-specific, in any
|
|
15
|
-
shipped file.
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
npm install flexdesk
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
Or vendor `dist/` and load it through an import map — no bundler, no build step.
|
|
22
|
-
That is exactly what EcoAgent does:
|
|
23
|
-
|
|
24
|
-
```html
|
|
25
|
-
<script type="importmap">
|
|
26
|
-
{ "imports": {
|
|
27
|
-
"@flexdesk/core": "./vendor/flexdesk/core.js",
|
|
28
|
-
"@flexdesk/host": "./vendor/flexdesk/host.js",
|
|
29
|
-
"@flexdesk/wm": "./vendor/flexdesk/wm.js",
|
|
30
|
-
"@flexdesk/widgets": "./vendor/flexdesk/widgets.js"
|
|
31
|
-
} }
|
|
32
|
-
</script>
|
|
33
|
-
<link href="./vendor/flexdesk/flexdesk.css" rel="stylesheet" />
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
## Seven entry points
|
|
37
|
-
|
|
38
|
-
Because someone who wants a DataTable should not have to download a window
|
|
39
|
-
manager, and neither of them should drag in a plotting engine or a code editor.
|
|
40
|
-
|
|
41
|
-
| Entry | Size | What is in it |
|
|
42
|
-
|---|---|---|
|
|
43
|
-
| `@flexdesk/core` | 30 kB | Event bus, settings store, logging, state machine, state guard |
|
|
44
|
-
| `@flexdesk/host` | 5 kB | The host port (below) |
|
|
45
|
-
| `@flexdesk/wm` | 166 kB | Tiling window manager: tabs, splits, desktops, command palette, taxonomy, `createShell()` |
|
|
46
|
-
| `@flexdesk/widgets` | 192 kB | DataTable, modals, managed windows, toasts, trees, panels, context menus |
|
|
47
|
-
| `@flexdesk/charts` | 26 kB | Plotly wrapper, chart types, subplot layouts, downsampling, plot windows |
|
|
48
|
-
| `@flexdesk/tiles` | 67 kB | Draggable/resizable widget grid, tile base class, widget registry, layout persistence |
|
|
49
|
-
| `@flexdesk/editor` | 62 kB | Monaco loader + factory, multi-file tab bar, search, split panes, undo manager |
|
|
50
|
-
|
|
51
|
-
They share code through chunks, so importing two of them does **not** give you two
|
|
52
|
-
event buses — and therefore not two copies of every module-level singleton. A test
|
|
53
|
-
asserts each kernel symbol is defined exactly once across the whole `dist/`.
|
|
54
|
-
|
|
55
|
-
Two different things here are called "tiles", which is unfortunate but honest to
|
|
56
|
-
the code: `@flexdesk/wm` tiles the *window*; `@flexdesk/tiles` is a dashboard
|
|
57
|
-
*widget grid*.
|
|
58
|
-
|
|
59
|
-
## Hello, shell
|
|
60
|
-
|
|
61
|
-
```js
|
|
62
|
-
import { createShell, createTaxonomy, createEntityCatalog } from '@flexdesk/wm';
|
|
63
|
-
import { createHost } from '@flexdesk/host';
|
|
64
|
-
import { DataTable } from '@flexdesk/widgets';
|
|
65
|
-
|
|
66
|
-
const store = new Map();
|
|
67
|
-
|
|
68
|
-
const shell = await createShell({
|
|
69
|
-
root: document.getElementById('app'),
|
|
70
|
-
|
|
71
|
-
// YOUR ontology. FlexDesk has none.
|
|
72
|
-
taxonomy: createTaxonomy({
|
|
73
|
-
root: 'cosmos',
|
|
74
|
-
kinds: {
|
|
75
|
-
cosmos: { label: 'Cosmos', isTopNav: true, sources: ['planet'] },
|
|
76
|
-
planet: { label: 'Planet', icon: 'circle' },
|
|
77
|
-
moon: {
|
|
78
|
-
label: 'Moon',
|
|
79
|
-
// "jupiter/europa" — your naming convention, not the library's.
|
|
80
|
-
ancestors: ({ id }) => {
|
|
81
|
-
const i = String(id).indexOf('/');
|
|
82
|
-
return i > 0 ? [{ kind: 'planet', id: String(id).slice(0, i) }] : [];
|
|
83
|
-
},
|
|
84
|
-
},
|
|
85
|
-
},
|
|
86
|
-
}),
|
|
87
|
-
|
|
88
|
-
entities: createEntityCatalog({ sources: [/* what the palette searches */] }),
|
|
89
|
-
content: { planet: (hostEl, props) => { /* render a tile */ } },
|
|
90
|
-
|
|
91
|
-
// Where persistence goes. An in-memory Map is a perfectly good host.
|
|
92
|
-
host: createHost({
|
|
93
|
-
state: {
|
|
94
|
-
read: async (k) => store.get(k) ?? null,
|
|
95
|
-
write: async (k, v) => (store.set(k, v), true),
|
|
96
|
-
},
|
|
97
|
-
}),
|
|
98
|
-
|
|
99
|
-
// Chrome is ELEMENTS, not selectors. The library never guesses at your markup.
|
|
100
|
-
chrome: { topNav: document.getElementById('nav') },
|
|
101
|
-
});
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
A working version of exactly this — planets and moons, a mock host, no backend —
|
|
105
|
-
lives in `demo/`.
|
|
106
|
-
|
|
107
|
-
### Opt-in shell features
|
|
108
|
-
|
|
109
|
-
These change what the user sees, so all of them are off until you ask:
|
|
110
|
-
|
|
111
|
-
```js
|
|
112
|
-
await createShell({
|
|
113
|
-
// …everything above…
|
|
114
|
-
snapPromotion: true, // drag a floating window onto a tile: it snaps, and docks back in
|
|
115
|
-
promoteInPlace: true, // a window floated out of a tile stays inside that tile's pane
|
|
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
|
|
118
|
-
chrome: {
|
|
119
|
-
topNav: document.getElementById('nav'),
|
|
120
|
-
zoom: document.getElementById('status-bar'), // content zoom, 50–200%
|
|
121
|
-
},
|
|
122
|
-
});
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
**`chrome.zoom`** paints a − / track / + / readout control into the element you
|
|
126
|
-
pass, restores the saved zoom through the host's `state` capability, and scales
|
|
127
|
-
the content of tiles and floating windows. It deliberately scales *content only*
|
|
128
|
-
— never the root, a tile's frame or a window's frame — because CSS `zoom`
|
|
129
|
-
establishes a scaled coordinate space and every window drag, resize and snap
|
|
130
|
-
measures in real pixels. `shell.chrome.zoom` exposes `get()` and `set(percent)`
|
|
131
|
-
for a settings pane. An embedder that builds its own `WindowManager` instead of
|
|
132
|
-
calling `createShell` mounts the same control with `mountZoomControl(el, { root,
|
|
133
|
-
host })`.
|
|
134
|
-
|
|
135
|
-
**`backToOpenList`** is for apps that open a record from a list in a new tab.
|
|
136
|
-
Without it, Back in that record has no history to pop, so it walks the taxonomy
|
|
137
|
-
up and rewrites the record into its parent, and the tile now shows the list
|
|
138
|
-
twice. With it, Back closes the record tab and activates the list it came from:
|
|
139
|
-
the nearest tab to the left, else to the right, that is not a record (no
|
|
140
|
-
`props.id`) and belongs to the same top-nav section. Per-tab history still comes
|
|
141
|
-
first, and a record with no such list beside it still walks up in place.
|
|
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 and the breadcrumb are one rule.** `wm.navigateBack()` (Backspace) and
|
|
149
|
-
`wm.navigateUp(kind, props, { ctx })` (every breadcrumb crumb) share it. With
|
|
150
|
-
`backToOpenList`, a record whose section has a list open closes onto that list;
|
|
151
|
-
otherwise Back walks the taxonomy up and a crumb navigates to its level, in
|
|
152
|
-
place. A crumb naming an entity, such as an epic above a story, opens that
|
|
153
|
-
entity. `navigate` stays the verb for going to exactly a target; use
|
|
154
|
-
`navigateUp` for anything that means "up to this level".
|
|
155
|
-
|
|
156
|
-
A filter or other props passed to `openInPrimary` for a list that is already
|
|
157
|
-
showing are applied to it. A bare open, such as a top-nav click, keeps the tab
|
|
158
|
-
as you left it.
|
|
159
|
-
|
|
160
|
-
**Back in a floating window** always acts on that window, whichever option is
|
|
161
|
-
set. The last click decides: inside a window, Backspace walks that window up;
|
|
162
|
-
anywhere else, it walks the focused tile as before. With `backToOpenList`, a
|
|
163
|
-
record in a window closes onto an open list of its section, first among the
|
|
164
|
-
window's own tabs and then in any tile of its desktop.
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
`
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
1
|
+
# FlexDesk
|
|
2
|
+
|
|
3
|
+
A tiling window manager and widget set for desktop-class web applications.
|
|
4
|
+
|
|
5
|
+
Tiles that split and tab, a command palette, dockable panels, virtual desktops, a
|
|
6
|
+
DataTable with sticky headers and CSV export, managed windows, modals, toasts,
|
|
7
|
+
trees and context menus.
|
|
8
|
+
|
|
9
|
+
**Zero runtime dependencies.** No React, no framework, nothing. If you install
|
|
10
|
+
FlexDesk, you install FlexDesk.
|
|
11
|
+
|
|
12
|
+
FlexDesk was extracted from [EcoAgent](https://github.com/nor-os/EcoAgent), a
|
|
13
|
+
macroeconomic simulation platform, and it knows nothing about economics: no
|
|
14
|
+
application vocabulary, no import of anything application-specific, in any
|
|
15
|
+
shipped file.
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install flexdesk
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Or vendor `dist/` and load it through an import map — no bundler, no build step.
|
|
22
|
+
That is exactly what EcoAgent does:
|
|
23
|
+
|
|
24
|
+
```html
|
|
25
|
+
<script type="importmap">
|
|
26
|
+
{ "imports": {
|
|
27
|
+
"@flexdesk/core": "./vendor/flexdesk/core.js",
|
|
28
|
+
"@flexdesk/host": "./vendor/flexdesk/host.js",
|
|
29
|
+
"@flexdesk/wm": "./vendor/flexdesk/wm.js",
|
|
30
|
+
"@flexdesk/widgets": "./vendor/flexdesk/widgets.js"
|
|
31
|
+
} }
|
|
32
|
+
</script>
|
|
33
|
+
<link href="./vendor/flexdesk/flexdesk.css" rel="stylesheet" />
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Seven entry points
|
|
37
|
+
|
|
38
|
+
Because someone who wants a DataTable should not have to download a window
|
|
39
|
+
manager, and neither of them should drag in a plotting engine or a code editor.
|
|
40
|
+
|
|
41
|
+
| Entry | Size | What is in it |
|
|
42
|
+
|---|---|---|
|
|
43
|
+
| `@flexdesk/core` | 30 kB | Event bus, settings store, logging, state machine, state guard |
|
|
44
|
+
| `@flexdesk/host` | 5 kB | The host port (below) |
|
|
45
|
+
| `@flexdesk/wm` | 166 kB | Tiling window manager: tabs, splits, desktops, command palette, taxonomy, `createShell()` |
|
|
46
|
+
| `@flexdesk/widgets` | 192 kB | DataTable, modals, managed windows, toasts, trees, panels, context menus |
|
|
47
|
+
| `@flexdesk/charts` | 26 kB | Plotly wrapper, chart types, subplot layouts, downsampling, plot windows |
|
|
48
|
+
| `@flexdesk/tiles` | 67 kB | Draggable/resizable widget grid, tile base class, widget registry, layout persistence |
|
|
49
|
+
| `@flexdesk/editor` | 62 kB | Monaco loader + factory, multi-file tab bar, search, split panes, undo manager |
|
|
50
|
+
|
|
51
|
+
They share code through chunks, so importing two of them does **not** give you two
|
|
52
|
+
event buses — and therefore not two copies of every module-level singleton. A test
|
|
53
|
+
asserts each kernel symbol is defined exactly once across the whole `dist/`.
|
|
54
|
+
|
|
55
|
+
Two different things here are called "tiles", which is unfortunate but honest to
|
|
56
|
+
the code: `@flexdesk/wm` tiles the *window*; `@flexdesk/tiles` is a dashboard
|
|
57
|
+
*widget grid*.
|
|
58
|
+
|
|
59
|
+
## Hello, shell
|
|
60
|
+
|
|
61
|
+
```js
|
|
62
|
+
import { createShell, createTaxonomy, createEntityCatalog } from '@flexdesk/wm';
|
|
63
|
+
import { createHost } from '@flexdesk/host';
|
|
64
|
+
import { DataTable } from '@flexdesk/widgets';
|
|
65
|
+
|
|
66
|
+
const store = new Map();
|
|
67
|
+
|
|
68
|
+
const shell = await createShell({
|
|
69
|
+
root: document.getElementById('app'),
|
|
70
|
+
|
|
71
|
+
// YOUR ontology. FlexDesk has none.
|
|
72
|
+
taxonomy: createTaxonomy({
|
|
73
|
+
root: 'cosmos',
|
|
74
|
+
kinds: {
|
|
75
|
+
cosmos: { label: 'Cosmos', isTopNav: true, sources: ['planet'] },
|
|
76
|
+
planet: { label: 'Planet', icon: 'circle' },
|
|
77
|
+
moon: {
|
|
78
|
+
label: 'Moon',
|
|
79
|
+
// "jupiter/europa" — your naming convention, not the library's.
|
|
80
|
+
ancestors: ({ id }) => {
|
|
81
|
+
const i = String(id).indexOf('/');
|
|
82
|
+
return i > 0 ? [{ kind: 'planet', id: String(id).slice(0, i) }] : [];
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
}),
|
|
87
|
+
|
|
88
|
+
entities: createEntityCatalog({ sources: [/* what the palette searches */] }),
|
|
89
|
+
content: { planet: (hostEl, props) => { /* render a tile */ } },
|
|
90
|
+
|
|
91
|
+
// Where persistence goes. An in-memory Map is a perfectly good host.
|
|
92
|
+
host: createHost({
|
|
93
|
+
state: {
|
|
94
|
+
read: async (k) => store.get(k) ?? null,
|
|
95
|
+
write: async (k, v) => (store.set(k, v), true),
|
|
96
|
+
},
|
|
97
|
+
}),
|
|
98
|
+
|
|
99
|
+
// Chrome is ELEMENTS, not selectors. The library never guesses at your markup.
|
|
100
|
+
chrome: { topNav: document.getElementById('nav') },
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
A working version of exactly this — planets and moons, a mock host, no backend —
|
|
105
|
+
lives in `demo/`.
|
|
106
|
+
|
|
107
|
+
### Opt-in shell features
|
|
108
|
+
|
|
109
|
+
These change what the user sees, so all of them are off until you ask:
|
|
110
|
+
|
|
111
|
+
```js
|
|
112
|
+
await createShell({
|
|
113
|
+
// …everything above…
|
|
114
|
+
snapPromotion: true, // drag a floating window onto a tile: it snaps, and docks back in
|
|
115
|
+
promoteInPlace: true, // a window floated out of a tile stays inside that tile's pane
|
|
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
|
|
118
|
+
chrome: {
|
|
119
|
+
topNav: document.getElementById('nav'),
|
|
120
|
+
zoom: document.getElementById('status-bar'), // content zoom, 50–200%
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**`chrome.zoom`** paints a − / track / + / readout control into the element you
|
|
126
|
+
pass, restores the saved zoom through the host's `state` capability, and scales
|
|
127
|
+
the content of tiles and floating windows. It deliberately scales *content only*
|
|
128
|
+
— never the root, a tile's frame or a window's frame — because CSS `zoom`
|
|
129
|
+
establishes a scaled coordinate space and every window drag, resize and snap
|
|
130
|
+
measures in real pixels. `shell.chrome.zoom` exposes `get()` and `set(percent)`
|
|
131
|
+
for a settings pane. An embedder that builds its own `WindowManager` instead of
|
|
132
|
+
calling `createShell` mounts the same control with `mountZoomControl(el, { root,
|
|
133
|
+
host })`.
|
|
134
|
+
|
|
135
|
+
**`backToOpenList`** is for apps that open a record from a list in a new tab.
|
|
136
|
+
Without it, Back in that record has no history to pop, so it walks the taxonomy
|
|
137
|
+
up and rewrites the record into its parent, and the tile now shows the list
|
|
138
|
+
twice. With it, Back closes the record tab and activates the list it came from:
|
|
139
|
+
the nearest tab to the left, else to the right, that is not a record (no
|
|
140
|
+
`props.id`) and belongs to the same top-nav section. Per-tab history still comes
|
|
141
|
+
first, and a record with no such list beside it still walks up in place.
|
|
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 and the breadcrumb are one rule.** `wm.navigateBack()` (Backspace) and
|
|
149
|
+
`wm.navigateUp(kind, props, { ctx })` (every breadcrumb crumb) share it. With
|
|
150
|
+
`backToOpenList`, a record whose section has a list open closes onto that list;
|
|
151
|
+
otherwise Back walks the taxonomy up and a crumb navigates to its level, in
|
|
152
|
+
place. A crumb naming an entity, such as an epic above a story, opens that
|
|
153
|
+
entity. `navigate` stays the verb for going to exactly a target; use
|
|
154
|
+
`navigateUp` for anything that means "up to this level".
|
|
155
|
+
|
|
156
|
+
A filter or other props passed to `openInPrimary` for a list that is already
|
|
157
|
+
showing are applied to it. A bare open, such as a top-nav click, keeps the tab
|
|
158
|
+
as you left it.
|
|
159
|
+
|
|
160
|
+
**Back in a floating window** always acts on that window, whichever option is
|
|
161
|
+
set. The last click decides: inside a window, Backspace walks that window up;
|
|
162
|
+
anywhere else, it walks the focused tile as before. With `backToOpenList`, a
|
|
163
|
+
record in a window closes onto an open list of its section, first among the
|
|
164
|
+
window's own tabs and then in any tile of its desktop.
|
|
165
|
+
|
|
166
|
+
**A page's own levels come first** (0.4.7). A page can have levels that are not
|
|
167
|
+
tabs, such as a section whose list opens one member in place of itself. Return
|
|
168
|
+
`navigateBack()` from the content factory's mount, and Backspace asks it before
|
|
169
|
+
anything above: it climbs one of the page's own levels and returns `true`. Only
|
|
170
|
+
a strict `true` counts. `false`, any other value, a missing function or a throw
|
|
171
|
+
(logged as a warning) all let Back walk the tab history, the open list and the
|
|
172
|
+
taxonomy exactly as before. It is asked in the same place as the rest of Back:
|
|
173
|
+
the window you last clicked in, else the focused tile. Return
|
|
174
|
+
`canNavigateBack()` as well, and `wm.canNavigateBack()` reports the page's own
|
|
175
|
+
levels, for that same window or tile. `TileRenderer.contentOf(leafId)` reads
|
|
176
|
+
what a tile's factory returned. To draw the page's levels, call
|
|
177
|
+
`renderBreadcrumb(container, segments)` from `@flexdesk/wm`, with
|
|
178
|
+
`segments = [{ label, icon?, onClick? }]`. It uses the tile breadcrumb's
|
|
179
|
+
markup, so it looks the same, and each `onClick` is yours: the last segment,
|
|
180
|
+
and any without `onClick`, is drawn as plain text.
|
|
181
|
+
|
|
182
|
+
### Column sizing
|
|
183
|
+
|
|
184
|
+
`fitColumns(columns, avail)` (from `@flexdesk/widgets`) turns per-column
|
|
185
|
+
measurements into widths. Pass each column's widest cell (`natural`), the width
|
|
186
|
+
most cells fit (`typical`), the header label (`header`) and any user-dragged
|
|
187
|
+
width (`pinned`). Short columns such as ids, dates and statuses keep their full
|
|
188
|
+
values, and free-text columns (`text`, or anything 240px and wider) take the
|
|
189
|
+
rest. When space runs short, short columns lose their rare outliers first,
|
|
190
|
+
then text shrinks to a floor. The widths are fractional and add up to `avail`
|
|
191
|
+
exactly whenever the floors fit. `DataTable` does not use it yet; an embedder
|
|
192
|
+
that measures its own columns can.
|
|
193
|
+
|
|
194
|
+
### Sorting a paged DataTable
|
|
195
|
+
|
|
196
|
+
A client-paged `DataTable` (no `onPageChange`) keeps its page when it is
|
|
197
|
+
sorted, so a sort on page 3 shows rows 201–300 of the new order. Pass
|
|
198
|
+
`resetPageOnSort: true` (0.4.7) to start every new order at the first page, a
|
|
199
|
+
header click included, the way a new filter already does. It is off by default
|
|
200
|
+
because keeping the page is what every earlier release did. Server-side paging
|
|
201
|
+
is not affected: its page is your `offset`, which a sort never changes.
|
|
202
|
+
|
|
203
|
+
### Scrollbars
|
|
204
|
+
|
|
205
|
+
`installAutoScrollbars(root)` (from `@flexdesk/widgets`) replaces the browser's
|
|
206
|
+
scrollbars under `root` with thin overlay bars that show while an element is
|
|
207
|
+
hovered or scrolling and fade out afterwards. Native bars are hidden, so no
|
|
208
|
+
element reserves layout width for one. The overlay bars live in one fixed
|
|
209
|
+
layer, so a container that redraws its contents cannot lose its bar, and
|
|
210
|
+
textareas get one too. Containers that already use `installOverlayScrollbar`
|
|
211
|
+
keep their own.
|
|
212
|
+
|
|
213
|
+
## The host port
|
|
214
|
+
|
|
215
|
+
FlexDesk never touches `pywebview`, `fetch`, `localStorage` or the filesystem. It
|
|
216
|
+
asks a **Host**, and you supply one. `createHost({...})` validates the capabilities
|
|
217
|
+
you pass and freezes the result; anything you leave out is simply absent, and the
|
|
218
|
+
library degrades rather than throwing.
|
|
219
|
+
|
|
220
|
+
```js
|
|
221
|
+
createHost({
|
|
222
|
+
state: { read, write }, // layout persistence
|
|
223
|
+
dialogs: { saveFile }, // optional — CSV export falls back to a Blob
|
|
224
|
+
window: { minimize, maximize, ... }, // optional — no native chrome buttons without it
|
|
225
|
+
})
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
`NULL_HOST` is a valid host with nothing behind it.
|
|
229
|
+
|
|
230
|
+
## Peer dependencies
|
|
231
|
+
|
|
232
|
+
`@flexdesk/charts` needs Plotly (4.4 MB). `@flexdesk/editor` needs Monaco (14 MB).
|
|
233
|
+
Neither is bundled, and **neither is guessed at**:
|
|
234
|
+
|
|
235
|
+
```js
|
|
236
|
+
import { setPlotlySource } from '@flexdesk/charts';
|
|
237
|
+
import { setMonacoBasePath, setDefaultLanguage } from '@flexdesk/editor';
|
|
238
|
+
|
|
239
|
+
setPlotlySource('/vendor/plotly/plotly.min.js');
|
|
240
|
+
setMonacoBasePath('/vendor/monaco');
|
|
241
|
+
setDefaultLanguage('my-dsl'); // default: 'plaintext'
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
There is deliberately **no default path**. A hardcoded `'vendor/monaco'` is one
|
|
245
|
+
application's directory layout, and for everyone else it is a 404 that surfaces as
|
|
246
|
+
an editor which silently never appears. `initMonaco()` throws instead.
|
|
247
|
+
|
|
248
|
+
If you already load Plotly yourself (a `<script>` tag that sets `window.Plotly`),
|
|
249
|
+
the chart loader short-circuits and you can skip `setPlotlySource`.
|
|
250
|
+
|
|
251
|
+
## Layouts are keyed by whatever you key them by
|
|
252
|
+
|
|
253
|
+
`@flexdesk/tiles` stores layouts under keys **you** choose and resolves a starting
|
|
254
|
+
layout through a callback **you** supply:
|
|
255
|
+
|
|
256
|
+
```js
|
|
257
|
+
import { configureLayoutPersistence } from '@flexdesk/tiles';
|
|
258
|
+
|
|
259
|
+
configureLayoutPersistence({
|
|
260
|
+
layoutsKey: 'myapp.layouts', // default: 'twm.tiles.layouts'
|
|
261
|
+
resolveTemplate: (ctx) => pickTemplate(ctx) ?? null, // null => empty grid
|
|
262
|
+
});
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
**Configure it before anything reads it.** The store is a singleton; built before
|
|
266
|
+
you supply your key, it is built with the default, and every layout your users have
|
|
267
|
+
saved becomes silently invisible. That is a data-loss bug that raises no error, so
|
|
268
|
+
the configuration step is explicit rather than lazy.
|
|
269
|
+
|
|
270
|
+
A layout is keyed by *something* — a document, a user, a run — and the library has
|
|
271
|
+
no opinion about which. It calls that a `contextId`.
|
|
272
|
+
|
|
273
|
+
## Styling
|
|
274
|
+
|
|
275
|
+
Three stylesheets, and that is the whole CSS contract:
|
|
276
|
+
|
|
277
|
+
| | |
|
|
278
|
+
|---|---|
|
|
279
|
+
| `dist/tokens.css` | Every rule reads `var(--…)` from here. Swap this one file and the shell restyles. |
|
|
280
|
+
| `dist/reset.css` | The reset. |
|
|
281
|
+
| `dist/flexdesk.css` | The library. Every class is `twm-` prefixed (the original working name; the prefix is stable and not worth a breaking rename). |
|
|
282
|
+
|
|
283
|
+
Icons are rendered by applying the `material-symbols-outlined` class. FlexDesk does
|
|
284
|
+
not ship the font — supply it from Google Fonts or self-host. Without it, icons fall
|
|
285
|
+
back to their text names and nothing breaks.
|
|
286
|
+
|
|
287
|
+
## Build
|
|
288
|
+
|
|
289
|
+
```bash
|
|
290
|
+
npm install
|
|
291
|
+
npm run build # -> dist/
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
`dist/` is committed. The packaging decision is *prebuilt dist*: this repo owns the
|
|
295
|
+
build so that a consumer with no bundler can load the library through a plain import
|
|
296
|
+
map. See `.gitignore` — it says so there too, because it is the kind of thing someone
|
|
297
|
+
"tidies up" later.
|
|
298
|
+
|
|
299
|
+
## Licence
|
|
300
|
+
|
|
301
|
+
MIT. See [`LICENSE`](LICENSE) and [`THIRD_PARTY_LICENSES.md`](THIRD_PARTY_LICENSES.md).
|