flexdesk 0.17.18 → 0.18.1
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 +91 -0
- package/README.md +1 -0
- package/css/flexdesk.css +7547 -5891
- package/css/reset.css +1 -1
- package/css/tokens.css +30 -1
- package/dist/charts.js +83 -13
- package/dist/charts.js.map +2 -2
- package/dist/chunk-3BLIVOFD.js +778 -0
- package/dist/chunk-3BLIVOFD.js.map +7 -0
- package/dist/{chunk-7OL6F2AM.js → chunk-4X4SY3CP.js} +7 -5
- package/dist/chunk-4X4SY3CP.js.map +7 -0
- package/dist/chunk-DNJ3N4T4.js +82 -0
- package/dist/chunk-DNJ3N4T4.js.map +7 -0
- package/dist/{chunk-PGZ4EHSE.js → chunk-HL3IIHU3.js} +501 -359
- package/dist/chunk-HL3IIHU3.js.map +7 -0
- package/dist/{chunk-REJ5HLIM.js → chunk-HYJYYEPK.js} +2 -2
- package/dist/{chunk-7U4IB65H.js → chunk-LLBJ2SJH.js} +5 -3
- package/dist/{chunk-7U4IB65H.js.map → chunk-LLBJ2SJH.js.map} +1 -1
- package/dist/{chunk-XJNADRGR.js → chunk-NFZJ6P42.js} +188 -96
- package/dist/chunk-NFZJ6P42.js.map +7 -0
- package/dist/chunk-PAI2ZUWV.js +260 -0
- package/dist/chunk-PAI2ZUWV.js.map +7 -0
- package/dist/chunk-Q7BALEPX.js +527 -0
- package/dist/chunk-Q7BALEPX.js.map +7 -0
- package/dist/{chunk-2Z2ENHR3.js → chunk-QRBOIUU3.js} +7 -8
- package/dist/chunk-QRBOIUU3.js.map +7 -0
- package/dist/chunk-QXAOTGCC.js +40 -0
- package/dist/chunk-QXAOTGCC.js.map +7 -0
- package/dist/chunk-UKP22RUG.js +219 -0
- package/dist/chunk-UKP22RUG.js.map +7 -0
- package/dist/chunk-VLOAI3HS.js +43 -0
- package/dist/chunk-VLOAI3HS.js.map +7 -0
- package/dist/{chunk-WUFLGETN.js → chunk-WVFGV5FT.js} +2 -39
- package/dist/{chunk-WUFLGETN.js.map → chunk-WVFGV5FT.js.map} +3 -3
- package/dist/editor.js +3 -2
- package/dist/editor.js.map +1 -1
- package/dist/flexdesk.css +7547 -5891
- package/dist/flow.js +29 -97
- package/dist/flow.js.map +3 -3
- package/dist/git.js +1356 -0
- package/dist/git.js.map +7 -0
- package/dist/reset.css +1 -1
- package/dist/tiles.js +20 -15
- package/dist/tiles.js.map +2 -2
- package/dist/tokens.css +30 -1
- package/dist/widgets.js +4146 -1199
- package/dist/widgets.js.map +4 -4
- package/dist/wm.js +120 -32
- package/dist/wm.js.map +2 -2
- package/docs/README.md +9 -4
- package/docs/best-practices.md +2 -2
- package/docs/components/charts.md +40 -0
- package/docs/components/chat-shell.md +204 -0
- package/docs/components/conversation.md +10 -0
- package/docs/components/filter-builder.md +292 -0
- package/docs/components/forms-and-dialogs.md +30 -0
- package/docs/components/git.md +355 -0
- package/docs/components/input-fields.md +191 -2
- package/docs/components/keymap.md +8 -0
- package/docs/components/menus.md +86 -2
- package/docs/components/panels-and-desktops.md +16 -5
- package/docs/components/shell.md +108 -1
- package/docs/components/workspace-picker.md +193 -0
- package/docs/concepts/building-and-packaging.md +2 -2
- package/docs/concepts/packages-and-entry-points.md +14 -13
- package/docs/concepts/state-guard.md +3 -3
- package/docs/quickstart.md +1 -1
- package/git.js +13 -0
- package/package.json +6 -1
- package/src/charts/plot_config_editor.js +92 -6
- package/src/charts/plot_grid.js +6 -1
- package/src/flow/kit/dom.js +28 -119
- package/src/flow/kit/step_picker.js +1 -1
- package/src/flow/kit/value_picker.js +1 -1
- package/src/flow/outline/menu.js +1 -1
- package/src/git/diff_viewer.js +212 -0
- package/src/git/diff_window.js +51 -0
- package/src/git/git_graph.js +401 -0
- package/src/git/git_ops.js +61 -0
- package/src/git/git_panel.js +755 -0
- package/src/git/index.js +9 -0
- package/src/git/line_diff.js +202 -0
- package/src/tiles/tile_base.js +5 -4
- package/src/tiling/desktops.js +10 -8
- package/src/tiling/keymap.js +6 -5
- package/src/tiling/kind_taxonomy.js +15 -2
- package/src/tiling/shell.js +124 -17
- package/src/tiling/tile_renderer.js +2 -3
- package/src/ui/components/action_dropdown.js +4 -78
- package/src/ui/components/anchored_layer.js +255 -0
- package/src/ui/components/chat_shell.js +926 -0
- package/src/ui/components/chat_state_machine.js +223 -0
- package/src/ui/components/choice_controls.js +45 -2
- package/src/ui/components/color_field.js +3 -2
- package/src/ui/components/color_list_field.js +3 -2
- package/src/ui/components/combobox.js +422 -0
- package/src/ui/components/context_menu.js +8 -2
- package/src/ui/components/conversation_view.js +17 -2
- package/src/ui/components/data_table.js +2 -2
- package/src/ui/components/filter_builder.js +791 -0
- package/src/ui/components/filter_model.js +682 -0
- package/src/ui/components/inline_renamer.js +10 -5
- package/src/ui/components/managed_window.js +24 -47
- package/src/ui/components/modal.js +104 -117
- package/src/ui/components/option_list.js +160 -0
- package/src/ui/components/popover.js +94 -0
- package/src/ui/components/recent_items.js +169 -0
- package/src/ui/components/select.js +322 -0
- package/src/ui/components/tag_input.js +77 -8
- package/src/ui/components/workspace_picker.js +1253 -0
- package/src/ui/utils/fire_event.js +17 -0
- package/src/ui/utils/focusable.js +25 -0
- package/src/ui/utils/modal_host.js +27 -0
- package/src/ui/utils/tooltip_service.js +81 -14
- package/widgets.js +12 -2
- package/css/base.css +0 -10547
- package/css/overrides.css +0 -2596
- package/dist/chunk-2Z2ENHR3.js.map +0 -7
- package/dist/chunk-7JI7HHSY.js +0 -107
- package/dist/chunk-7JI7HHSY.js.map +0 -7
- package/dist/chunk-7OL6F2AM.js.map +0 -7
- package/dist/chunk-MIW7D6WC.js +0 -526
- package/dist/chunk-MIW7D6WC.js.map +0 -7
- package/dist/chunk-PGZ4EHSE.js.map +0 -7
- package/dist/chunk-XJNADRGR.js.map +0 -7
- /package/dist/{chunk-REJ5HLIM.js.map → chunk-HYJYYEPK.js.map} +0 -0
package/docs/components/menus.md
CHANGED
|
@@ -33,6 +33,10 @@ items they hold:
|
|
|
33
33
|
|
|
34
34
|
Nothing changes in how you call them.
|
|
35
35
|
|
|
36
|
+
## A press inside no longer disarms a context menu (0.18.1)
|
|
37
|
+
|
|
38
|
+
`showContextMenu` kept its outside-press listener for one press. A press inside the menu (a separator, the padding, a disabled row) used it up, and from then on a press outside was ignored until Escape. It now listens until the menu closes. A consumer's own outside-press listener for this can go.
|
|
39
|
+
|
|
36
40
|
## A menu bar: `createMenuBar` (0.17.0)
|
|
37
41
|
|
|
38
42
|
File / Edit / View menus from one declarative model, instead of hand-built
|
|
@@ -307,5 +311,85 @@ category's `color` is any CSS colour; it reaches the rows as
|
|
|
307
311
|
|
|
308
312
|
The Add button is FlexDesk's `twm-btn twm-btn--primary`; everything else is
|
|
309
313
|
styled from the tokens (`tests/category_picker.test.mjs` checks there is no
|
|
310
|
-
colour literal in its rules
|
|
311
|
-
|
|
314
|
+
colour literal in its rules).
|
|
315
|
+
|
|
316
|
+
## A rich popover: `openPopover` (0.18.0)
|
|
317
|
+
|
|
318
|
+
`openPopover` hangs a panel of your own content off the element that opened it. It
|
|
319
|
+
is not a menu: it holds a form, a list, anything.
|
|
320
|
+
|
|
321
|
+
```js
|
|
322
|
+
import { openPopover } from '@flexdesk/widgets';
|
|
323
|
+
|
|
324
|
+
button.addEventListener('click', () => {
|
|
325
|
+
const pop = openPopover({
|
|
326
|
+
anchor: button,
|
|
327
|
+
content: shareForm,
|
|
328
|
+
label: 'Share',
|
|
329
|
+
focus: shareForm.querySelector('input'),
|
|
330
|
+
onClose: (reason) => saveDraft(),
|
|
331
|
+
});
|
|
332
|
+
// pop.close(), pop.reposition(), await pop.closed
|
|
333
|
+
});
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
| Option | What it does |
|
|
337
|
+
| --- | --- |
|
|
338
|
+
| `anchor`, `content` | What it hangs from, and its body |
|
|
339
|
+
| `label`, `className` | Its accessible name (`role="dialog"`), and extra classes on `.twm-popover` |
|
|
340
|
+
| `focus` | What takes focus on open; else the panel. On close, focus held inside it goes back to the anchor |
|
|
341
|
+
| `align` | `'start'` or `'end'`: which anchor edge it lines up with first |
|
|
342
|
+
| `inside` | A selector for floating panels of your own: a press in one is not "outside" |
|
|
343
|
+
| `onClose(reason)` | After it is gone: `'outside'`, `'escape'`, `'toggle'`, `'replaced'`, `'parent'`, `'detached'`, or what you passed to `close` |
|
|
344
|
+
|
|
345
|
+
It closes on a press outside it, on Escape and when its anchor leaves the page. While open
|
|
346
|
+
the anchor carries `.twm-is-open` and `aria-expanded="true"`. Opening one from the anchor
|
|
347
|
+
that holds an open one closes it: `toggled` is then true and `el` null, so a click handler
|
|
348
|
+
needs no state. Opening one anywhere else closes the open one, unless it owns the new
|
|
349
|
+
anchor (see [layers](#an-anchored-layer-positionanchored-openanchoredlayer-0180)). An anchor that
|
|
350
|
+
is not on the page opens nothing: `el` is null, `toggled` false, `onClose` is not called and
|
|
351
|
+
`closed` is already resolved.
|
|
352
|
+
|
|
353
|
+
**Tab stays inside**, going round the panel's controls, as in a modal or a `CategoryPicker`.
|
|
354
|
+
The panel is mounted at the end of the page, so past its edge lies whatever comes next in the
|
|
355
|
+
document, and over a modal that is behind the modal. Escape is the way out. A modal's own focus trap
|
|
356
|
+
leaves focus in a layer alone. This is new for the flow kit's popover, whose pickers used
|
|
357
|
+
to close when Tab took focus out of them.
|
|
358
|
+
|
|
359
|
+
The flow kit's popover is this one with the class `.twm-flow-popover`. It used to place
|
|
360
|
+
itself and now scrolls inside when it fits neither above nor below its anchor, where it used to be
|
|
361
|
+
clamped to the window. `@flexdesk/flow` still exports its own `openPopover`, `closeFlowPopovers`
|
|
362
|
+
and `openFlowPopover`.
|
|
363
|
+
|
|
364
|
+
## An anchored layer: `positionAnchored`, `openAnchoredLayer` (0.18.0)
|
|
365
|
+
|
|
366
|
+
There is one placement. `positionAnchored(anchor, panel, { align, gap, margin, matchWidth })`
|
|
367
|
+
is what `ActionDropdown.position` calls, and `CategoryPicker`, a tile's menu, `openPopover`,
|
|
368
|
+
`createSelect` and `createCombobox` place through it: `position: fixed`, under the anchor when it fits there (else
|
|
369
|
+
above it when it fits there, else on the side with more room, scrolling inside), `margin` inside
|
|
370
|
+
the window. `gap` is 8 by default, as it was for `ActionDropdown`.
|
|
371
|
+
|
|
372
|
+
`openAnchoredLayer({ anchor, el, align, gap, matchWidth, inside, returnFocus, onClose })` is the
|
|
373
|
+
behaviour around it, for a floating panel of your own. It answers `{ el, anchor, place, owns(node), close(reason) }`,
|
|
374
|
+
or `null` when the anchor is not on the page: nothing opened and `onClose` will not be called.
|
|
375
|
+
|
|
376
|
+
- `el` is mounted on `modalHost()` or the anchor's `document.body`, and taken off on
|
|
377
|
+
close, so a closed layer leaves nothing on the page.
|
|
378
|
+
- It is placed again on a window resize, on a scroll outside it and when its size
|
|
379
|
+
changes. An anchor gone from the page closes it.
|
|
380
|
+
- **Layers stack.** What a layer owns is one answer, for a press and for an anchor: its panel,
|
|
381
|
+
the layers above it, an `ActionDropdown` menu, a `CategoryPicker`, a context menu, a modal window
|
|
382
|
+
shown after it opened (a dialog its content opened), and what `inside` names. Opening a layer
|
|
383
|
+
closes every layer that does not own its anchor. A press on what a layer owns, or on its anchor,
|
|
384
|
+
is not outside it. Closing a layer closes those above it.
|
|
385
|
+
So a select in a popover works, a press in the select's list does not close the popover,
|
|
386
|
+
and neither does a list opening in a dialog the popover opened.
|
|
387
|
+
- **Escape goes to the top layer and to nothing else**, so the `ManagedWindow` around the field stays
|
|
388
|
+
open. From inside the panel it is heard as it bubbles out, so that the panel's content
|
|
389
|
+
can answer first, by stopping it or preventing its default.
|
|
390
|
+
- An open menu or picker, and a modal shown after the layer opened, have Escape first. A modal
|
|
391
|
+
already showing when the layer opened is under it, however long the chain of layers between them,
|
|
392
|
+
and hears Escape only when the layers are gone.
|
|
393
|
+
- `isInAnchoredLayer(node)` says whether a node is in an open layer, for a focus trap of your own.
|
|
394
|
+
|
|
395
|
+
`tests/select_combobox_popover.test.mjs` covers these in jsdom.
|
|
@@ -157,8 +157,7 @@ Text that is not JSON was always handled: the host logs `[host] bad JSON for
|
|
|
157
157
|
state key desktops` and answers `null`. A layout that is valid JSON but not a
|
|
158
158
|
layout (a split whose `sizes` is a number, a `null` desktop, a split with no
|
|
159
159
|
`children`) used to make `createShell` throw, on every start, until the file was
|
|
160
|
-
deleted by hand, so an app had to vet the file before booting.
|
|
161
|
-
through half understood: a desktop with no `tree` came back as an empty one.
|
|
160
|
+
deleted by hand, so an app had to vet the file before booting.
|
|
162
161
|
|
|
163
162
|
Now such a layout is ignored. `createShell` resolves, the shell opens its
|
|
164
163
|
default layout exactly as it does when nothing was saved, and one warning names
|
|
@@ -181,8 +180,8 @@ loggers) records that line as the message, with the key and the cause in it.
|
|
|
181
180
|
(any change to it) that save replaces the file, and until then every start
|
|
182
181
|
warns again.
|
|
183
182
|
- **What is unreadable** is a shape `serialize()` could not have written:
|
|
184
|
-
`desktops` not a non-empty array; a desktop that is not an object, or
|
|
185
|
-
`tree`
|
|
183
|
+
`desktops` not a non-empty array; a desktop that is not an object, or whose
|
|
184
|
+
`tree` is something other than `{ root }`, `null` or absent; a
|
|
186
185
|
`panels` or `panelShares` that is not an object; a node that is not a `leaf`
|
|
187
186
|
or a `split`; a split without a `dir` of `h` or `v`, without children, or
|
|
188
187
|
whose `sizes` are not one non-negative number per child; a tab without a
|
|
@@ -193,7 +192,7 @@ loggers) records that line as the message, with the key and the cause in it.
|
|
|
193
192
|
`group` and `fixed`, a panel's flag, a tab's props. A layout saved by an
|
|
194
193
|
earlier release loads too: the keys that came later (`panelShares`, `group`,
|
|
195
194
|
`fixed`, a leaf's `pageTabs`) may be missing, and so may a desktop's `id`,
|
|
196
|
-
`label` and `panels`, and a leaf's `tabs`. Keys it does not know are ignored, a
|
|
195
|
+
`label`, `tree` and `panels`, and a leaf's `tabs`. Keys it does not know are ignored, a
|
|
197
196
|
tab of a kind with no factory registered is the usual placeholder tile, and an
|
|
198
197
|
`activeIdx` past the last desktop is clamped.
|
|
199
198
|
|
|
@@ -201,5 +200,17 @@ loggers) records that line as the message, with the key and the cause in it.
|
|
|
201
200
|
throws the `TypeError` above for a blob that cannot be read, which is what
|
|
202
201
|
`wm.load()` catches; `wm.load()` itself does not throw for one.
|
|
203
202
|
|
|
203
|
+
### A desktop with no `tree` is an empty desktop (0.18.0)
|
|
204
|
+
|
|
205
|
+
A desktop whose `tree` is `null` or absent has nothing on it. It loads, its
|
|
206
|
+
siblings load with it, and the shell seeds it with the root kind. A host that
|
|
207
|
+
composes the blob can leave the key out: one that rebuilds a desktop per page on
|
|
208
|
+
every boot, say, and keeps only the desktops the user made.
|
|
209
|
+
|
|
210
|
+
From 0.17.14 to 0.17.18 an absent `tree` counted as unreadable. All or nothing
|
|
211
|
+
then set the user's desktops aside with it, and the next layout change saved the
|
|
212
|
+
default layout over them for good. A host like that must not ship on those
|
|
213
|
+
releases unless it writes `tree: null`.
|
|
214
|
+
|
|
204
215
|
`tests/saved_layout_unreadable.test.mjs` boots a real shell over each of 78
|
|
205
216
|
shapes of broken layout and over the readable ones.
|
package/docs/components/shell.md
CHANGED
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
> **Package:** `@flexdesk/wm` · **Next:** [Panels and desktops](panels-and-desktops.md), [Command palette](command-palette.md), [Keys, Back and tab layout](keymap.md) · [All docs](../README.md)
|
|
4
4
|
|
|
5
|
-
`createShell` boots the tiling window manager into an element of your page: tiles that split and tab, the top navigation, dockable panels, virtual desktops and the command palette. It knows nothing about your domain. You give it a taxonomy of *kinds*, a content factory for each kind and a [host](../concepts/host-port.md), and it draws and persists the rest.
|
|
5
|
+
`createShell` boots the tiling window manager into an element of your page: tiles that split and tab, the top navigation or a page rail, dockable panels, virtual desktops and the command palette. It knows nothing about your domain. You give it a taxonomy of *kinds*, a content factory for each kind and a [host](../concepts/host-port.md), and it draws and persists the rest.
|
|
6
6
|
|
|
7
7
|
A complete first boot is in the [quickstart](../quickstart.md#hello-shell). This page covers what you can add to it: the opt-in shell features, kinds that exist only once, what happens when a link names a kind nobody registered, and what a pane can tell its own chrome.
|
|
8
8
|
|
|
9
9
|
**On this page**
|
|
10
10
|
|
|
11
11
|
- [Opt-in shell features](#opt-in-shell-features)
|
|
12
|
+
- [The pages as a vertical rail: `chrome.rail` (0.18.0)](#the-pages-as-a-vertical-rail-chromerail-0180)
|
|
12
13
|
- [A kind that exists once: `single` (0.17.0)](#a-kind-that-exists-once-single-0170)
|
|
13
14
|
- [A kind that exists once per desktop: `single: 'desktop'` (0.17.17)](#a-kind-that-exists-once-per-desktop-single-desktop-01717)
|
|
14
15
|
- [An open single instance takes new props: `navigate(props)` (0.17.2)](#an-open-single-instance-takes-new-props-navigateprops-0172)
|
|
@@ -36,6 +37,112 @@ await createShell({
|
|
|
36
37
|
});
|
|
37
38
|
```
|
|
38
39
|
|
|
40
|
+
## Keyboard focus shows a tooltip (0.18.1)
|
|
41
|
+
|
|
42
|
+
`TooltipService` shows a `twm-has-tooltip` element's tooltip (or a `has-latex-tooltip` element's) when it takes keyboard focus, as hovering does, and hides it when focus leaves the element or on Esc. Focus moving inside the element keeps it. A pointer does not take it down: over another target the pointer's tooltip shows for as long as the pointer stays, then focus's comes back. A scroll does not hide it, the one the focus itself caused included: it goes with its target, and waits while a wheel has the target out of sight. Keyboard focus means the last press was a key, not a Ctrl, Cmd or Alt chord: the focus a click leaves shows nothing, nor does a field its handler then focuses. The browser's `:focus-visible` cannot tell this, being true for any text field. A consumer that wires `focusin` to `TooltipService.show` itself can stop.
|
|
43
|
+
|
|
44
|
+
## The pages as a vertical rail: `chrome.rail` (0.18.0)
|
|
45
|
+
|
|
46
|
+
`chrome.topNav` paints the pages as a strip in the top bar. `chrome.rail` paints
|
|
47
|
+
them as a fixed-left icon bar instead. Give it the element, and put the element
|
|
48
|
+
beside the window manager's host in a `.twm-workspace`:
|
|
49
|
+
|
|
50
|
+
```html
|
|
51
|
+
<body class="twm-body">
|
|
52
|
+
<div class="twm-global-top-bar">…</div>
|
|
53
|
+
<div class="twm-workspace">
|
|
54
|
+
<nav id="rail" aria-label="Sections"></nav>
|
|
55
|
+
<main id="root" class="twm-host"></main>
|
|
56
|
+
</div>
|
|
57
|
+
<div class="twm-global-bottom-bar">…</div>
|
|
58
|
+
</body>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```js
|
|
62
|
+
const taxonomy = createTaxonomy({
|
|
63
|
+
root: 'start',
|
|
64
|
+
kinds: {
|
|
65
|
+
start: { label: 'Start', icon: 'apps' },
|
|
66
|
+
atlas: { label: 'Atlas', icon: 'map', isTopNav: true, order: 10 },
|
|
67
|
+
log: { label: 'Ship log', icon: 'article', isTopNav: true, order: 20 },
|
|
68
|
+
settings: { label: 'Settings', icon: 'settings', isTopNav: true, railPin: 'bottom' },
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
await createShell({
|
|
73
|
+
root: document.getElementById('root'),
|
|
74
|
+
taxonomy, entities, content,
|
|
75
|
+
chrome: { rail: document.getElementById('rail') },
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
- **The look.** 50px wide, 44px items, 22px icons at weight 300. An item is grey
|
|
80
|
+
at rest and white on hover; the active one is white, with a white 2px bar at
|
|
81
|
+
its left on a transparent background; keyboard focus draws the accent outline.
|
|
82
|
+
A window too short for the items scrolls the bar, with no scrollbar. No
|
|
83
|
+
stylesheet of yours is needed.
|
|
84
|
+
- **`railPin: 'bottom'`** on a top-nav kind holds it at the bottom of the bar.
|
|
85
|
+
Pinned kinds come after the rest, in their own `order`, and stay together.
|
|
86
|
+
A `railPin` of any other value, or on a kind that is not `isTopNav`, is a boot
|
|
87
|
+
error. The top strip ignores it. A kind such as a settings page can be
|
|
88
|
+
`isTopNav` and `appGlobal` at once.
|
|
89
|
+
- **F1..F12 press the rail's buttons in the order they are drawn**, pinned ones
|
|
90
|
+
last, with no `navSelector`. `navSelector` remains for page buttons in markup
|
|
91
|
+
of your own.
|
|
92
|
+
- **Tooltips open to the right** of the bar, flipping at the window's edge, with
|
|
93
|
+
no attribute or patch of yours. They name the kind's `label`, and so do the
|
|
94
|
+
buttons' `aria-label`s (a `shortLabel` is for the strip's chips). As for every
|
|
95
|
+
`twm-has-tooltip` element, `TooltipService` draws them, and it comes with
|
|
96
|
+
`@flexdesk/widgets`: a page that imports only `@flexdesk/wm` has the labels
|
|
97
|
+
and no tooltips.
|
|
98
|
+
- **The layout.** `.twm-workspace` takes the host's place in `body.twm-body`'s
|
|
99
|
+
column, and a `.twm-host` inside it fills what the rail leaves, with the rule
|
|
100
|
+
a host directly under the body has. Outside `body.twm-body`, a `.twm-workspace`
|
|
101
|
+
needs a parent that gives it a height, as a host does.
|
|
102
|
+
- **The element is yours.** It gains `.twm-fl-bar` and, if it has none, the
|
|
103
|
+
`aria-label` "Pages". `shell.chrome.railEl` hands it back. `shell.dispose()`
|
|
104
|
+
empties it and takes the listener off, so a rebuilt shell paints its own.
|
|
105
|
+
- **One row of pages.** `chrome.rail` with `chrome.topNav` is a boot error.
|
|
106
|
+
|
|
107
|
+
Retheme or resize it from `tokens.css`:
|
|
108
|
+
|
|
109
|
+
| Token | Default | |
|
|
110
|
+
|---|---|---|
|
|
111
|
+
| `--twm-rail-bg` | `#333333` | the bar |
|
|
112
|
+
| `--twm-rail-icon` | `#9e9e9e` | an item at rest |
|
|
113
|
+
| `--twm-rail-icon-on` | `var(--text-bright)` | an item hovered, the active item and its bar |
|
|
114
|
+
| `--twm-rail-width` | `50px` | the bar's width |
|
|
115
|
+
| `--twm-rail-item-height` | `44px` | an item's height |
|
|
116
|
+
| `--twm-rail-icon-size` | `22px` | the icon's size |
|
|
117
|
+
|
|
118
|
+
If you supply your own token definitions instead of `tokens.css`, define these
|
|
119
|
+
too: the rules that read them have no fallback.
|
|
120
|
+
|
|
121
|
+
### Without `createShell`: `mountPageRail(el, { wm, activeKind?, decorate? })`
|
|
122
|
+
|
|
123
|
+
`@flexdesk/wm` exports the painter, for an embedder that constructs its own
|
|
124
|
+
`WindowManager`. It answers `{ el, sync(), dispose() }`; call `sync()` when the
|
|
125
|
+
window manager changes (`onChange`, `wm:changed`), once after the first load, and
|
|
126
|
+
`dispose()` when the rail goes. The keymap's default finds these buttons too.
|
|
127
|
+
|
|
128
|
+
```js
|
|
129
|
+
const rail = mountPageRail(document.getElementById('rail'), {
|
|
130
|
+
wm,
|
|
131
|
+
// Which page to light, or null for none. Default: the primary tile's page.
|
|
132
|
+
activeKind: (w) => pageOf(w),
|
|
133
|
+
// Once per button, with its topNavEntries() entry.
|
|
134
|
+
decorate: (btn, entry) => {
|
|
135
|
+
if (entry.kind === 'archive') btn.dataset.tooltip = 'Archive: shared by every crew';
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**What changed for existing markup.** `.twm-fl-bar` and `.twm-fl-item` had rules
|
|
141
|
+
and no code that drew them. `.twm-fl-bar` now has the rail's width, background
|
|
142
|
+
and overflow and no `height: 100%` (it stretches in its flex row), and
|
|
143
|
+
`.twm-fl-item`'s grey comes from `--twm-rail-icon`. Nothing changes for a shell
|
|
144
|
+
that uses `chrome.topNav`. `topNavEntries()` gains `railPin` (null unless set).
|
|
145
|
+
|
|
39
146
|
## A kind that exists once: `single` (0.17.0)
|
|
40
147
|
|
|
41
148
|
Some content holds heavy app-wide state and must not be mounted twice. Mark its
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# Workspace picker
|
|
2
|
+
|
|
3
|
+
> **Package:** `@flexdesk/widgets` · **See also:** [Menus](menus.md) · [Host port](../concepts/host-port.md) · [All docs](../README.md)
|
|
4
|
+
|
|
5
|
+
A field in the top bar that names what the window is working in, a switcher that changes it, and a store for "recently opened". FlexDesk does not know what is being picked: the items, the words and what a pick does are yours.
|
|
6
|
+
|
|
7
|
+
## A field and a switcher: `createWorkspacePicker` (0.18.0)
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
import { createWorkspacePicker } from '@flexdesk/widgets';
|
|
11
|
+
|
|
12
|
+
const picker = createWorkspacePicker(topBar, {
|
|
13
|
+
field: {
|
|
14
|
+
layout: 'centered',
|
|
15
|
+
avoid: '.bar-left > *, .bar-right > *',
|
|
16
|
+
icon: 'folder',
|
|
17
|
+
placeholder: 'Nothing open',
|
|
18
|
+
tooltip: 'Click to switch workspace',
|
|
19
|
+
},
|
|
20
|
+
chord: 'Ctrl+P', // none by default
|
|
21
|
+
label: 'Switch workspace',
|
|
22
|
+
placeholder: 'Switch workspace: type to filter, Enter to open',
|
|
23
|
+
glyph: 'folder_open',
|
|
24
|
+
load: () => recent.list(),
|
|
25
|
+
describe: (item, { current }) => ({
|
|
26
|
+
id: item.id,
|
|
27
|
+
name: item.name,
|
|
28
|
+
detail: item.location,
|
|
29
|
+
icon: current ? 'folder_open' : 'folder',
|
|
30
|
+
badge: current ? 'current' : '',
|
|
31
|
+
}),
|
|
32
|
+
onPick: (item) => openWorkspace(item),
|
|
33
|
+
onRemove: (item) => recent.remove(item),
|
|
34
|
+
actions: () => [
|
|
35
|
+
{ id: 'new', label: 'New workspace', icon: 'add', run: () => createOne() },
|
|
36
|
+
{ id: 'open', label: 'Open…', icon: 'folder_open', run: () => openOne() },
|
|
37
|
+
],
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
picker.setCurrent(openNow); // named on the field, marked in the list
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
A click on the field or the chord opens the switcher in the field's place, with its input over the field, so the field reads as a search box growing its results. A pick closes it, gives focus back to where it was, and calls `onPick`, or nothing if the row is the current one. What `onPick` does is yours: navigate the whole window, or swap what is open in place.
|
|
44
|
+
|
|
45
|
+
It returns `{ el, field, switcher, isOpen, current, open({select}), close(), toggle(), setCurrent(item), refresh(), reload(), destroy() }`. `createWorkspacePicker(hostEl, options)` takes everything `createPickerSwitcher` takes, and:
|
|
46
|
+
|
|
47
|
+
| Option | |
|
|
48
|
+
|---|---|
|
|
49
|
+
| `field` | `mountPickerField`'s options, plus `placeholder`: its name while nothing is current (drawn muted). `field.icon` is its icon whatever is current; without it the current item's `icon` is used. |
|
|
50
|
+
| `chord` | `'Ctrl+P'`, `'Ctrl+Shift+O'`. None by default. Ctrl also answers to Cmd, modifiers are exact, and a held key does not repeat. It is taken on `window` in the capture phase, ahead of an embedded editor and the browser's own use of the key, and it becomes the field's key hint unless `field.keyHint` says otherwise. It still works while the field is hidden for want of room. |
|
|
51
|
+
|
|
52
|
+
## The field: `mountPickerField` (0.18.0)
|
|
53
|
+
|
|
54
|
+
`mountPickerField(hostEl, options)` appends a real `<button>` to `hostEl` and returns `{ el, update(patch), setOpen(open), fit(), destroy() }`.
|
|
55
|
+
|
|
56
|
+
| Option | Default | |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| `name`, `icon`, `keyHint`, `tooltip`, `ariaLabel` | empty | Drawn when given. `update` changes any of them, and `muted`. |
|
|
59
|
+
| `muted` | `false` | The name drawn as naming nothing (`update({ name: 'n/a', muted: true })`). |
|
|
60
|
+
| `caret` | `false` | A trailing caret, for a field that reads as a dropdown. |
|
|
61
|
+
| `className` | | Extra classes. |
|
|
62
|
+
| `layout` | `'inline'` | `'centered'` stands it in the middle of `hostEl`, whatever stands left and right. |
|
|
63
|
+
| `avoid` | | Centred: a selector, within `hostEl`, of what it must not cover. An element counts on the side its centre is on; one taking no room does not count. |
|
|
64
|
+
| `maxWidth`, `minWidth`, `tightWidth`, `gap` | 440, 120, 230, 24 | Centred, its width is the room the tighter side leaves, twice over, less `gap`, up to `maxWidth`. Below `tightWidth` the key hint goes. Below `minWidth` the field is hidden. |
|
|
65
|
+
| `onActivate` | | A click. |
|
|
66
|
+
|
|
67
|
+
A centred field's `hostEl` must be positioned. It measures again whenever `hostEl` resizes and when the fonts arrive; call `fit()` after you change what stands beside it. An inline field's width is its host's, and only the key hint responds, to its own width.
|
|
68
|
+
|
|
69
|
+
## The switcher: `createPickerSwitcher` (0.18.0)
|
|
70
|
+
|
|
71
|
+
`createPickerSwitcher(options)` is the panel alone, on any anchor or on none. `createWorkspacePicker` is this plus a field.
|
|
72
|
+
|
|
73
|
+
**Where it opens.** `placement: 'over'` (the default) lays the input over `anchor`, at least `minWidth` (520) wide, kept inside the window; an anchor in the lower half of the window grows the panel upward instead. `placement: 'below'` hangs from it through `ActionDropdown.position`, flipped above when there is no room. With no `anchor`, or one that takes no room, it stands centred over a dimmed page. A window resize places it again.
|
|
74
|
+
|
|
75
|
+
**What it lists.** `load({ query, offset, limit, signal })` answers an array or `{ items, total?, status? }`, now or in a promise, and is asked on every open, so a list edited behind the page is never stale for long. The last unsearched answer per `cacheKey()` is drawn at once while it is asked again. `status` is the footer's left end, a string or `{ text, error }`. Only the newest question's answer paints; `signal` aborts the older ones.
|
|
76
|
+
|
|
77
|
+
| `search` | |
|
|
78
|
+
|---|---|
|
|
79
|
+
| `'local'` (default) | Asked once per open. Typing filters what it answered, on the name, `detail` and `keywords`, with name matches first. |
|
|
80
|
+
| `'remote'` | Typing is debounced (`debounceMs`, 200) and asked again. A `total` above what is drawn offers **Load more**, `pageSize` (20) at a time. A server that hands back only what is drawn has no more, whatever it said before. A failed page keeps the rows and says so. |
|
|
81
|
+
|
|
82
|
+
`describe(item, { current })` turns an item into a row:
|
|
83
|
+
|
|
84
|
+
| Field | |
|
|
85
|
+
|---|---|
|
|
86
|
+
| `id` | Required. Two items with one `id` are one row. Make it the form you compare by: `setCurrent` and `open({ select })` go through it. |
|
|
87
|
+
| `name` | Required. |
|
|
88
|
+
| `detail` | A second line. |
|
|
89
|
+
| `icon`, `badge` | An icon, and a short word on the right (the word for the current row, if you want one; the check mark is always drawn). |
|
|
90
|
+
| `keywords` | A string or an array: searched, not drawn. |
|
|
91
|
+
| `disabled` | Drawn greyed, stepped over by the arrows. |
|
|
92
|
+
| `removable` | `false` takes the bin off this row. |
|
|
93
|
+
| `title` | The row's tooltip; by default its name and detail. |
|
|
94
|
+
|
|
95
|
+
**The rest of the panel**, every part optional:
|
|
96
|
+
|
|
97
|
+
| Option | |
|
|
98
|
+
|---|---|
|
|
99
|
+
| `onRemove(item)` | A bin on each row, and Shift+Delete on the highlighted one. May answer a promise: while it is unsettled the panel stays open and it and its dimmed page step below managed windows, so a confirmation dialog can be answered with the mouse. Answering `false` keeps the row, rejecting keeps it and shows the error, anything else drops it and asks `load` again. The highlight moves to the next row. |
|
|
100
|
+
| `actions({ query })` | Rows after the items: `{ id, label, detail?, icon?, always?, run({ query }) }`. Typing narrows them on their `label` unless `always`, which is how a row offering to create what was typed stays; when nothing else matches, it is what Enter takes. |
|
|
101
|
+
| `heading({ query, shown, total })` | `{ title, count }` over the list. |
|
|
102
|
+
| `card({ current, row })` | A panel under the list for the current item: `{ heading?, icon?, name, detail?, open?: { label, icon?, run }, columns?, tiles?: [{ id, label, icon?, run }] }`, or `null`. `row` is the current item as the list read it, when it has. |
|
|
103
|
+
| `footer()` | `{ start?, end?, keys? }`. `start` and `end` are buttons, `{ id, label, icon?, trailing?, primary?, run }`. `keys`, `[{ keys: ['Shift', 'Del'], label: 'remove' }]`, are drawn only when the panel is centred. |
|
|
104
|
+
| `visibleRows` | Scroll past this many two-line rows. |
|
|
105
|
+
| `labels` | Any of `loading`, `empty()`, `noMatch(query)`, `failed(err)`, `failedMore(err)`, `failedRemove(err)`, `loadMore({ next, shown, total })`, `remove(name)`. |
|
|
106
|
+
| `label`, `listLabel`, `placeholder`, `glyph`, `className`, `onOpenChange(open)` | The panel's and the list's accessible names, the input's placeholder and icon, extra classes, and a callback. |
|
|
107
|
+
|
|
108
|
+
`refresh()` asks `actions`, `card` and `footer` again. `reload()` asks `load` again. `open({ select })` highlights the row with that `id` when the answer lands. `setCurrent(item)` takes the item whether or not any page holds it.
|
|
109
|
+
|
|
110
|
+
**Nothing is rebuilt under a pointer.** Every part is reconciled by key, so an answer landing while a row is being pressed leaves that row's node in place and the click still fires.
|
|
111
|
+
|
|
112
|
+
### The keyboard (0.18.1)
|
|
113
|
+
|
|
114
|
+
Focus is real: what the keyboard is on is `document.activeElement`, so a screen reader says each stop, the card's and the footer's buttons included, and Tab and the arrows agree about where the reader is.
|
|
115
|
+
|
|
116
|
+
| Key | |
|
|
117
|
+
|---|---|
|
|
118
|
+
| The arrows | Walk focus over every stop, wrapping: the input, the rows that can be chosen (a disabled one is stepped over), Load more, the action rows, the card's buttons, the footer's. After `open({ select })` they start from the row it chose. An answer or a redraw leaves focus where it is, on a row that moved too. |
|
|
119
|
+
| Tab | The rows are one tab stop: the row last focused, else the current one, else the first. So are the action rows. Out of the panel closes it. |
|
|
120
|
+
| A character | Goes to the input from anywhere in the panel, and to nothing else: a keymap does not read it as a shortcut. |
|
|
121
|
+
| Enter in the input | Takes what wears the highlight: the first row or action row of what is shown, or, while the answer to what was typed is on its way, the first one when it lands. |
|
|
122
|
+
| Enter or Space elsewhere | Takes the row or runs the button with focus. |
|
|
123
|
+
| Shift+Delete | Removes the row with focus, or the highlighted one from the input. Focus goes on to the next row. |
|
|
124
|
+
| Backspace | The input's in the input. Elsewhere it stops at the panel, where a window manager's keymap would read it as "up one level". |
|
|
125
|
+
| Esc | Closes, and gives focus back to where it was. |
|
|
126
|
+
|
|
127
|
+
A press outside closes and leaves focus where it was pressed. A row that held focus and went away, because an answer no longer holds it, gives focus to the input. Load more keeps focus while something loads: it is `aria-disabled` then, not `disabled`, and refuses the press itself.
|
|
128
|
+
|
|
129
|
+
### A trigger of your own (0.18.1)
|
|
130
|
+
|
|
131
|
+
The field is optional. `createPickerSwitcher` opens on any anchor, so a control of yours can be the trigger:
|
|
132
|
+
|
|
133
|
+
```js
|
|
134
|
+
const switcher = createPickerSwitcher({
|
|
135
|
+
anchor: trigger,
|
|
136
|
+
placement: 'below',
|
|
137
|
+
visibleRows: 5,
|
|
138
|
+
load, describe, onPick, card, footer,
|
|
139
|
+
onOpenChange: (open) => trigger.setAttribute('aria-expanded', String(open)),
|
|
140
|
+
});
|
|
141
|
+
trigger.addEventListener('click', () => switcher.toggle());
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
A press on the anchor does not count as a press outside, and Esc, a pick or a verb give focus back to it when it had focus. `setCurrent(item)` on the switcher marks the row; naming it on your trigger is yours.
|
|
145
|
+
|
|
146
|
+
## The field's look: tokens (0.18.1)
|
|
147
|
+
|
|
148
|
+
Set these over FlexDesk's defaults (`css/tokens.css`) instead of overriding the field's rules:
|
|
149
|
+
|
|
150
|
+
| Token | Default | |
|
|
151
|
+
|---|---|---|
|
|
152
|
+
| `--twm-wspicker-field-icon` | `var(--accent-bright)` | Its icon. |
|
|
153
|
+
| `--twm-wspicker-field-focus-border` | `var(--accent-bright)` | Its border, focused by keyboard and while open. |
|
|
154
|
+
| `--twm-wspicker-field-muted-icon` | `var(--text-muted)` | Its icon while it names nothing (`muted`). |
|
|
155
|
+
| `--twm-wspicker-field-muted-style` | `italic` | Its name's `font-style` while muted. |
|
|
156
|
+
| `--twm-wspicker-field-muted-opacity` | `0.55` | Its name's opacity while muted. |
|
|
157
|
+
|
|
158
|
+
## Recently opened: `createRecentItems` (0.18.0)
|
|
159
|
+
|
|
160
|
+
A most-recent-first list kept through the [host's](../concepts/host-port.md) `state` capability.
|
|
161
|
+
|
|
162
|
+
```js
|
|
163
|
+
import { createRecentItems } from '@flexdesk/widgets';
|
|
164
|
+
|
|
165
|
+
const recent = createRecentItems({
|
|
166
|
+
host,
|
|
167
|
+
key: 'recent-workspaces',
|
|
168
|
+
identity: (entry) => entry.path.replace(/\\/g, '/'),
|
|
169
|
+
isValid: (entry) => typeof entry?.path === 'string' && entry.path !== '',
|
|
170
|
+
onChange: () => bus.emit('recent:changed'),
|
|
171
|
+
logger,
|
|
172
|
+
});
|
|
173
|
+
await recent.load();
|
|
174
|
+
|
|
175
|
+
recent.add({ path, name }); // stamped `openedAt`, moved to the top
|
|
176
|
+
recent.remove({ path }); // by identity
|
|
177
|
+
recent.clear();
|
|
178
|
+
recent.list(); // frozen copies, most recent first
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
| Option | Default | |
|
|
182
|
+
|---|---|---|
|
|
183
|
+
| `host`, `key` | required | The host, and the state key. |
|
|
184
|
+
| `identity` | `entry.id` | Which two entries are the same one. |
|
|
185
|
+
| `isValid` | `identity` answers something | Stored entries failing this are dropped on read, as is one that makes it, `identity` or `groupOf` throw, so `identity` may assume an entry's shape. |
|
|
186
|
+
| `capacity` | 10 | The most entries kept, per group. |
|
|
187
|
+
| `groupOf` | one group | Counted per group, so one busy kind of entry never pushes another out. |
|
|
188
|
+
| `onChange` | | After every change, and after a read that replayed changes. |
|
|
189
|
+
| `logger` | | Its `warn` hears a failed read or write. |
|
|
190
|
+
|
|
191
|
+
**A read that fails is not an empty list.** While `host.state.read` rejects, `load()` answers `false` and the stored list is never written, because writing would replace entries nobody has seen. Changes made meanwhile show in `list()` at once, each asks for the read again, and they are replayed onto the stored list when it is read, which is then written. A read answering something other than an array is a read of an empty store. Once a read has succeeded, `load()` does not read again.
|
|
192
|
+
|
|
193
|
+
Writes run one at a time and the last carries the latest list, so a host answering out of order cannot leave the older list stored. A host with no `state` keeps the list in memory for the session.
|
|
@@ -19,8 +19,8 @@ map. See `.gitignore` — it says so there too, because it is the kind of thing
|
|
|
19
19
|
Every JavaScript entry in the `exports` map has two conditions: `import`, the
|
|
20
20
|
prebuilt bundle under `dist/`, and `development`, the source barrel at the
|
|
21
21
|
package root (`core.js`, `host.js`, `wm.js`, `widgets.js`, `charts.js`,
|
|
22
|
-
`tiles.js`, `editor.js`, `canvas.js`, `flow.js`), which re-exports `src/`.
|
|
23
|
-
Up to 0.17.0 those
|
|
22
|
+
`tiles.js`, `editor.js`, `canvas.js`, `flow.js`, `git.js`), which re-exports `src/`.
|
|
23
|
+
Up to 0.17.0 those barrels were missing from `files`, so the published
|
|
24
24
|
package did not contain them and a resolver that took the `development` branch
|
|
25
25
|
(`require.resolve` under `--conditions=development`, for one) got a path to
|
|
26
26
|
nothing. From 0.17.1 they ship, beside the `src/` they import.
|
|
@@ -2,26 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
> **Install:** see the [quickstart](../quickstart.md) · **Component pages:** [docs index](../README.md#components) · [All docs](../README.md)
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Ten entry points
|
|
6
6
|
|
|
7
7
|
Because someone who wants a DataTable should not have to download a window
|
|
8
8
|
manager, and neither of them should drag in a plotting engine or a code editor.
|
|
9
9
|
|
|
10
10
|
| Entry | Size | What is in it |
|
|
11
11
|
|---|---|---|
|
|
12
|
-
| `@flexdesk/core` |
|
|
13
|
-
| `@flexdesk/host` |
|
|
14
|
-
| `@flexdesk/wm` |
|
|
15
|
-
| `@flexdesk/widgets` |
|
|
16
|
-
| `@flexdesk/charts` |
|
|
17
|
-
| `@flexdesk/tiles` |
|
|
18
|
-
| `@flexdesk/editor` |
|
|
19
|
-
| `@flexdesk/canvas` |
|
|
20
|
-
| `@flexdesk/flow` |
|
|
12
|
+
| `@flexdesk/core` | 32 kB | Event bus, settings store, logging, state machine, state guard |
|
|
13
|
+
| `@flexdesk/host` | 18 kB | The [host port](host-port.md) |
|
|
14
|
+
| `@flexdesk/wm` | 403 kB | Tiling window manager: tabs, splits, desktops, command palette, taxonomy, `createShell()` |
|
|
15
|
+
| `@flexdesk/widgets` | 413 kB | DataTable, modals, managed windows, toasts, trees, panels, context menus, menu bars, a document outline, progress, a conversation view, an assistant chat shell, a rich select, a combobox, a popover, a filter builder, a workspace picker |
|
|
16
|
+
| `@flexdesk/charts` | 49 kB | Plotly wrapper, chart types, subplot layouts, downsampling, a plot grid, a plot-config editor, plot windows, injected series transforms |
|
|
17
|
+
| `@flexdesk/tiles` | 133 kB | Draggable/resizable widget grid, tile base class, widget registry, layout persistence, figure, chart and KPI tiles |
|
|
18
|
+
| `@flexdesk/editor` | 49 kB | Monaco loader + factory, multi-file tab bar, search, split panes, undo manager |
|
|
19
|
+
| `@flexdesk/canvas` | 5 kB | The canvas kernel: a pannable, zoomable surface, the edge router, a generic graph layer ([Flow editors](../components/flow-editors.md#flow-editors)) |
|
|
20
|
+
| `@flexdesk/flow` | 480 kB | The flow-editor kit and the canvas, outline and lane editors ([Flow editors](../components/flow-editors.md#flow-editors)) |
|
|
21
|
+
| `@flexdesk/git` | 47 kB | A source-control panel, an SVG commit graph and a diff viewer, over a written adapter contract ([Git panel](../components/git.md)) |
|
|
21
22
|
|
|
22
|
-
The sizes
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
The sizes are the entry files as `node build.mjs` reports them for 0.18.0,
|
|
24
|
+
unminified. Every entry also imports shared chunks, which load once however many
|
|
25
|
+
entries use them; the build prints their total.
|
|
25
26
|
|
|
26
27
|
They share code through chunks, so importing two of them does **not** give you two
|
|
27
28
|
event buses — and therefore not two copies of every module-level singleton. A test
|
|
@@ -40,7 +40,7 @@ global; choose strict mode in your own code.
|
|
|
40
40
|
write, so the shell, dialogs, forms, tooltips, tiles and flow editors run under
|
|
41
41
|
`mode: 'strict'` with no violation. The list also carries keys that FlexDesk
|
|
42
42
|
itself does not write, and it has not been audited key by key
|
|
43
|
-
([what 0.
|
|
43
|
+
([what 0.17.14 took out and what stayed](#thirteen-application-keys-leave-the-defaults-01714)).
|
|
44
44
|
A key that names your application's concepts is yours to add, on top:
|
|
45
45
|
|
|
46
46
|
```js
|
|
@@ -59,13 +59,13 @@ capital letter becomes `-` plus its lower-case form. So `rowID` is `row-i-d`
|
|
|
59
59
|
Before 0.17.0 a camelCase entry never matched, and some entries named keys no
|
|
60
60
|
element carries.
|
|
61
61
|
|
|
62
|
-
### Thirteen application keys leave the defaults (0.
|
|
62
|
+
### Thirteen application keys leave the defaults (0.17.14)
|
|
63
63
|
|
|
64
64
|
**Breaking for an application that writes one of these keys under a strict
|
|
65
65
|
guard.** `DEFAULT_STATE_GUARD_CONFIG.allowedDatasetKeys` no longer lists
|
|
66
66
|
thirteen keys that belonged to one embedding application, not to FlexDesk. No
|
|
67
67
|
FlexDesk widget writes any of them, so nothing FlexDesk draws changes; what
|
|
68
|
-
changes is that a write of one is now on no allow-list. Before 0.
|
|
68
|
+
changes is that a write of one is now on no allow-list. Before 0.17.14 the
|
|
69
69
|
defaults let it through silently, for every application that used them.
|
|
70
70
|
|
|
71
71
|
| Group, as the list labelled it | Keys removed |
|
package/docs/quickstart.md
CHANGED
|
@@ -14,7 +14,7 @@ The package ships a prebuilt `dist/`. Copy it into your site (the examples below
|
|
|
14
14
|
mkdir -p vendor && cp -r node_modules/flexdesk/dist vendor/flexdesk
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
Under a bundler you need none of that. Import the entries as `flexdesk/core`, `flexdesk/host`, `flexdesk/wm`, `flexdesk/widgets` (and `flexdesk/charts`, `flexdesk/tiles`, `flexdesk/editor`, `flexdesk/canvas`, `flexdesk/flow`), and the stylesheets as `flexdesk/css/tokens`, `flexdesk/css/reset` and `flexdesk/css`. The rest of these docs write the entries as `@flexdesk/wm` and so on, which is the name the import map below gives them.
|
|
17
|
+
Under a bundler you need none of that. Import the entries as `flexdesk/core`, `flexdesk/host`, `flexdesk/wm`, `flexdesk/widgets` (and `flexdesk/charts`, `flexdesk/tiles`, `flexdesk/editor`, `flexdesk/canvas`, `flexdesk/flow`, `flexdesk/git`), and the stylesheets as `flexdesk/css/tokens`, `flexdesk/css/reset` and `flexdesk/css`. The rest of these docs write the entries as `@flexdesk/wm` and so on, which is the name the import map below gives them.
|
|
18
18
|
|
|
19
19
|
## The page
|
|
20
20
|
|
package/git.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @flexdesk/git — a source-control panel, an SVG swimlane commit graph and a
|
|
3
|
+
* diff viewer, over a written adapter contract (docs/components/git.md).
|
|
4
|
+
*
|
|
5
|
+
* It runs no git and reaches no backend by itself: every repository operation
|
|
6
|
+
* is a method of the adapter object the application passes in.
|
|
7
|
+
*
|
|
8
|
+
* HAND-WRITTEN, not generated. The barrels that say "GENERATED by
|
|
9
|
+
* scripts/gen_barrels.py" name a script that is not in this repository: add a
|
|
10
|
+
* module to src/git/index.js when you add one under src/git/.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export * from './src/git/index.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flexdesk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.1",
|
|
4
4
|
"description": "A tiling window manager and widget set for desktop-class web applications. Zero runtime dependencies.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"editor.js",
|
|
28
28
|
"canvas.js",
|
|
29
29
|
"flow.js",
|
|
30
|
+
"git.js",
|
|
30
31
|
"css",
|
|
31
32
|
"docs",
|
|
32
33
|
"README.md",
|
|
@@ -73,6 +74,10 @@
|
|
|
73
74
|
"./flow": {
|
|
74
75
|
"import": "./dist/flow.js",
|
|
75
76
|
"development": "./flow.js"
|
|
77
|
+
},
|
|
78
|
+
"./git": {
|
|
79
|
+
"import": "./dist/git.js",
|
|
80
|
+
"development": "./git.js"
|
|
76
81
|
}
|
|
77
82
|
},
|
|
78
83
|
"scripts": {
|