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.
Files changed (47) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/css/base.css +237 -8
  3. package/css/flexdesk.css +344 -17
  4. package/css/overrides.css +6 -0
  5. package/dist/charts.js +104 -34
  6. package/dist/charts.js.map +3 -3
  7. package/dist/{chunk-2PM6ZMNS.js → chunk-2Z2ENHR3.js} +2 -2
  8. package/dist/{chunk-IG74S3ZH.js → chunk-7JI7HHSY.js} +2 -2
  9. package/dist/{chunk-TLIV6QSU.js → chunk-7OL6F2AM.js} +3 -3
  10. package/dist/{chunk-MADMGRX2.js → chunk-PGZ4EHSE.js} +156 -9
  11. package/dist/chunk-PGZ4EHSE.js.map +7 -0
  12. package/dist/{chunk-ZMH3OC7H.js → chunk-XJNADRGR.js} +4 -2
  13. package/dist/chunk-XJNADRGR.js.map +7 -0
  14. package/dist/flexdesk.css +344 -17
  15. package/dist/flow.js +3 -3
  16. package/dist/tiles.js +84 -9
  17. package/dist/tiles.js.map +2 -2
  18. package/dist/widgets.js +448 -5
  19. package/dist/widgets.js.map +4 -4
  20. package/dist/wm.js +110 -19
  21. package/dist/wm.js.map +2 -2
  22. package/docs/README.md +1 -1
  23. package/docs/components/charts.md +70 -3
  24. package/docs/components/forms-and-dialogs.md +80 -2
  25. package/docs/components/menus.md +85 -1
  26. package/docs/components/shell.md +52 -0
  27. package/docs/components/slide-out-panel.md +15 -0
  28. package/docs/components/tiles.md +37 -0
  29. package/package.json +1 -1
  30. package/src/charts/plot_config_editor.js +125 -29
  31. package/src/tiles/tile_base.js +48 -2
  32. package/src/tiles/tile_grid.js +42 -1
  33. package/src/tiling/kind_taxonomy.js +45 -0
  34. package/src/tiling/tile_breadcrumb.js +12 -4
  35. package/src/tiling/wm.js +86 -11
  36. package/src/ui/components/category_picker.js +556 -0
  37. package/src/ui/components/computing_status_window.js +3 -0
  38. package/src/ui/components/confirm_dialog.js +3 -0
  39. package/src/ui/components/managed_window.js +204 -9
  40. package/src/ui/components/modal.js +11 -2
  41. package/src/ui/components/wizard.js +4 -1
  42. package/widgets.js +2 -1
  43. package/dist/chunk-MADMGRX2.js.map +0 -7
  44. package/dist/chunk-ZMH3OC7H.js.map +0 -7
  45. /package/dist/{chunk-2PM6ZMNS.js.map → chunk-2Z2ENHR3.js.map} +0 -0
  46. /package/dist/{chunk-IG74S3ZH.js.map → chunk-7JI7HHSY.js.map} +0 -0
  47. /package/dist/{chunk-TLIV6QSU.js.map → chunk-7OL6F2AM.js.map} +0 -0
package/docs/README.md CHANGED
@@ -49,7 +49,7 @@ FlexDesk is nine entry points that share their code, so you load only what you u
49
49
  - [Settings page](components/settings-view.md): a settings store's schema drawn as a page.
50
50
  - [Wizard](components/wizard.md): a multi-step flow, inline or in a window.
51
51
  - [Input fields: slider and tags](components/input-fields.md): `SliderField` and `createTagInput`.
52
- - [Menus](components/menus.md): `createMenuBar` and `ActionDropdown`.
52
+ - [Menus](components/menus.md): `createMenuBar`, `ActionDropdown` and `CategoryPicker`.
53
53
 
54
54
  ### Layout, content and feedback
55
55
 
@@ -4,6 +4,70 @@
4
4
 
5
5
  Plotly-backed charts: the wrapper, the chart types, subplot layouts, downsampling, the plot grid, the plot-config editor and the two plot windows. Plotly itself is not in the bundle; call `setPlotlySource()` with the path to your copy. The plot window's `streamEvents` option is described with the [other application-neutral options](../concepts/domain-neutrality.md#no-application-globals-the-gallery-picker-and-plot-window-events-0170).
6
6
 
7
+ ## A plot config series is one block, and an embedder's fields share it (0.17.16)
8
+
9
+ [`renderPlotConfigEditor`](#editing-a-plot-config-renderplotconfigeditor-0170)
10
+ drew each series as one wrapped line: the series picker, the legend label, the
11
+ colour swatch with a wide hex box, the width, the dash and the stack group,
12
+ then the embedder's `extraSeriesFields` and the overlay select on label
13
+ columns of their own. Each series is now one block:
14
+
15
+ - **A header line**: the series picker, the legend label and the remove
16
+ button. It wraps when the window is narrow.
17
+ - **Its fields, one per line, on the editor's label column** (the same 96px
18
+ column as the subplot's and the axes' fields): **Colour** (a `ColorField`:
19
+ the swatch and a hex box sized to `#rrggbb`), **Width**, **Dash**,
20
+ **Stack group**, then the embedder's fields, then **Overlay** (with
21
+ `overlaySources`). An overlay's own label, colour, dash, width and mode sit
22
+ under its source, set in by a rule, with their controls still on the
23
+ series' control column.
24
+
25
+ It reads at the usual ~600px config window and down to 360px without
26
+ overflowing. The classes are `.twm-plot-config__series-head` and
27
+ `.twm-plot-config__series-fields`; `.twm-plot-config__series-main` is gone.
28
+
29
+ `extraSeriesFields(el, series, ctx)` can now put its fields on that same
30
+ column, in either of two ways, and the 0.17.0 form keeps working:
31
+
32
+ ```js
33
+ renderPlotConfigEditor(el, plotConfig, {
34
+ // 1. return field specs: the editor lays them out
35
+ extraSeriesFields: (el, series, { onChange }) => [
36
+ { label: 'Scenario', control: scenarioSelect(series, onChange),
37
+ hint: 'The run this series is read from' },
38
+ { label: 'Note', render: (slot) => mountMyWidget(slot, series, onChange) }, // may return { dispose }
39
+ ],
40
+ // 2. or draw rows with the editor's own helper
41
+ // extraSeriesFields: (el, series, { onChange, field }) => {
42
+ // el.appendChild(field('Scenario', scenarioSelect(series, onChange), { hint: '…' }));
43
+ // },
44
+ });
45
+ ```
46
+
47
+ - `ctx.field(label, control, { hint })` returns a field line (a `<label>`
48
+ when the control is a single input, select or textarea). A hint is drawn
49
+ under the control, outside the label: the line is then a `<div>`, its label
50
+ text is the `<label for>` of a single control, and the hint is the control's
51
+ `aria-describedby`, so it describes the control without joining its name.
52
+ - Only an ARRAY return is field specs. A spec is
53
+ `{ label: string, control: HTMLElement, hint? }` or
54
+ `{ label: string, render(slot), hint? }`; `render` draws into an empty slot
55
+ on the field line and may return `{ dispose }`. Specs are laid out after
56
+ anything drawn into `el`. A spec without a string `label`, with a `control`
57
+ that is not an Element, with neither `control` nor `render`, or whose
58
+ `render` throws is skipped with a warning (`logger.warn`, else
59
+ `console.warn`); the rest of the row is drawn. Any other return is a handle
60
+ as in 0.17.0 (`{ dispose }`), even one that has a `fields` property.
61
+ - `el` sits in the field column (`display: contents`), so rows appended to it
62
+ line up with the editor's; content drawn there any other way shows as
63
+ before. A returned `{ dispose }` is still disposed on every redraw.
64
+
65
+ The config the editor reads and writes is unchanged: no key was added, renamed
66
+ or defaulted differently, opening a series writes nothing, and each control
67
+ writes its own key only.
68
+
69
+ `tests/plot_config_editor.test.mjs` §12 pins it.
70
+
7
71
  ## An empty cell can name what is missing: `emptyMessage` as a function (0.17.12)
8
72
 
9
73
  [`renderPlotGrid`](#a-plot-config-in-any-container-renderplotgrid-0170),
@@ -303,7 +367,7 @@ const editor = renderPlotConfigEditor(el, plotConfig, {
303
367
  seriesProvider: ({ value, role, subplot }) => names(value), // items, or a promise of them
304
368
  seriesTransforms, // their toggles, per subplot
305
369
  overlaySources: () => mySources(), // optional
306
- extraSeriesFields: (el, series, { subplot, axis, onChange }) => { /* your fields */ },
370
+ extraSeriesFields: (el, series, { subplot, axis, onChange, field }) => { /* your fields */ },
307
371
  });
308
372
  editor.dispose();
309
373
  ```
@@ -341,8 +405,11 @@ config it was given and then calls `onChange(config)`.
341
405
  dash, width and mode. `renderPlotGrid` draws it from the data you pass under
342
406
  the series' id in `overlays`. **None** removes `series.overlay`. Without
343
407
  `overlaySources` there are no overlay controls.
344
- - `extraSeriesFields(el, series, { subplot, axis, onChange })` puts fields of
345
- your own in each series row; call `onChange()` after you edit the series. What
408
+ - `extraSeriesFields(el, series, { subplot, axis, onChange, field })` puts fields of
409
+ your own in each series row; call `onChange()` after you edit the series.
410
+ Since 0.17.16 they can share the series' label column (`field`, or returned
411
+ field specs; see
412
+ [above](#a-plot-config-series-is-one-block-and-an-embedders-fields-share-it-01716)). What
346
413
  it returns is disposed (`dispose()`) when the row is redrawn or the editor
347
414
  is disposed. A `dispose()` that throws is reported to `logger.warn` and does
348
415
  not stop the rest of the editor being disposed.
@@ -230,18 +230,35 @@ openForm({
230
230
  row hidden by `setVisible` drops its bounds until it is shown again, as it
231
231
  drops `required`. As with any `<input type="number">`, a `min` is also where
232
232
  `step` counts from.
233
- - **`{ row: [field, ...] }`** puts its fields on one line, sharing the width.
233
+ - **`{ row: [field, ...] }`** puts its fields on one line; their inputs share
234
+ what the labels leave equally (see below).
234
235
  The row is layout only and has no name: each field in it is an ordinary
235
236
  field with its own label, hint, error, `setVisible`, value and submitted
236
237
  key, so hiding one hides only that one. The first keeps the form's label
237
238
  column; the others size their label to its text. A form narrower than
238
239
  360px stacks them (a container query, `twm-form`, on a form that has a row;
239
240
  a form without one is not made a container). A form with a row opens at the
240
- wide (640px) layout. The row is `.twm-modal__field-group`.
241
+ wide (640px) layout, or wider for a row of three or more fields (below).
242
+ The row is `.twm-modal__field-group`.
241
243
 
242
244
  A form with neither renders exactly as before.
243
245
  `tests/open_form_rows_bounds.test.mjs` covers both in jsdom.
244
246
 
247
+ ### The inputs of a row are equally wide (0.17.15)
248
+
249
+ Every input in a `{ row }` gets the same width: each field takes its label's
250
+ width (the first one the form's 130px label column), and what is left of the
251
+ row is shared equally between the inputs. The share is equal up to each
252
+ control's own padding and border: a native `<input>` or `<textarea>` keeps its
253
+ ~18px, a select, toggle or picker (which sit in a padding-less slot) does not,
254
+ so in a row that mixes the two the native input is that much wider. A form
255
+ whose widest row has three or more fields opens wider: `170 + 230 × fields` px
256
+ (860px for three, 1090px for four), at least 640px, otherwise at most 90% of the
257
+ window; forms without such a row open as before. Until 0.17.14 every field was
258
+ an equal share of the row, including the first one's label column, so its input
259
+ was a sliver beside the others: about 15px for the first of four number fields
260
+ in a 640px form (BUG-0050).
261
+
245
262
  ### A switch: `ToggleField` and `type: 'toggle'` (0.17.0)
246
263
 
247
264
  ```js
@@ -292,3 +309,64 @@ the caller's own DOM.
292
309
 
293
310
  `tests/confirm_escaping.test.mjs` opens each of them in jsdom with hostile
294
311
  names and labels.
312
+
313
+ ## Windows: closing, focus and Esc (0.17.16)
314
+
315
+ Every dialog above is a `ManagedWindow`, and so is any window you build
316
+ yourself with `new ManagedWindow({ id, title, content, ... })`. Three things
317
+ changed for all of them (BUG-0053):
318
+
319
+ - **A closed window is gone.** `close()` (once `beforeClose` allows it) runs
320
+ `onClose`, removes the window from the registry and releases its content,
321
+ its DOM and every callback it was given. `ManagedWindow.get(id)` then answers
322
+ `null`, `ManagedWindow.all()` no longer lists it and `win.isClosed` is
323
+ `true`. A closed window cannot be shown again: `show()` on it warns and does
324
+ nothing. To reopen, build a new `ManagedWindow` with the same id; it restores
325
+ the geometry the last one saved under that id. Until 0.17.15 a closed window
326
+ stayed registered, holding its content and whatever its `onClose` captured,
327
+ until the same id opened again.
328
+ - **Saved geometry is bounded.** Position and size are still saved per id
329
+ under `flexdesk.managedWindows.v1` (or your `storageKey`), but only for the
330
+ 50 most recently saved ids; older entries are dropped.
331
+ `ManagedWindow.configure({ maxSavedWindows })` changes the cap
332
+ (`DEFAULT_MAX_SAVED_WINDOWS` is 50). Entries saved by an earlier version
333
+ carry no use stamp: those under the window manager's minted ids
334
+ (`twm-mw-…`, which never open again) are purged when the entry is read, and
335
+ the others are stamped in the order they were stored, as older than anything
336
+ saved since, so your stable ids keep their geometry. A window whose id is
337
+ minted per opening passes `persistGeometry: false` and neither saves nor
338
+ restores geometry; the window manager's own windows do. Modal windows never
339
+ saved geometry and still do not.
340
+ - **Focus moves in and back.** With `focusOnOpen` (default `true`), opening a
341
+ window focuses the first element of its content that takes focus (a link,
342
+ button, input, select, textarea, `contenteditable` or `tabindex` element
343
+ that is not disabled or hidden), or the window itself when there is none.
344
+ `focusOnOpen: 'window'` always focuses the window itself, for a dialog whose
345
+ first button must not catch a stray Enter (`showRunProgressDialog` uses it,
346
+ so Stop is never focused). Closing gives focus back to the element that had
347
+ it before the window opened, if that element is still in the document and
348
+ focus is still in the window or nowhere (on `body`); a window closed while
349
+ the user works elsewhere does not pull focus back. Restoring a minimised
350
+ window is not an open and moves nothing. A dialog that focuses a field of
351
+ its own a frame later, as `openForm` and `openConfirm` do, still wins, and
352
+ `Wizard.openInWindow` renders its first step before it shows the window, so
353
+ focus lands on the step's first field. Pass `focusOnOpen: false` for a
354
+ window that must not take focus when it appears; `showComputingWindow`
355
+ does.
356
+ - **The window itself holds focus.** Every window element has
357
+ `tabindex="-1"`: never a Tab stop, but a press on its title bar or on empty
358
+ content moves focus into the window (when it was not there already), so the
359
+ window's Esc still works after the user clicked it.
360
+ - **Esc closes a non-modal window only from inside it.** Which window an Esc
361
+ closes:
362
+ 1. a modal window on top, wherever focus is (as before);
363
+ 2. otherwise the window holding focus, modal or not — so a modal under a
364
+ non-modal window still closes on an Esc from inside it;
365
+ 3. otherwise (focus in no window) the topmost modal.
366
+ A non-modal window that does not hold focus never closes on Esc, so an Esc
367
+ meant for an editor beside it is left alone. An Esc something inside the
368
+ window already handled with `preventDefault()` (an autocomplete closing its
369
+ list) or one ending an IME composition (`isComposing`) closes nothing. One
370
+ Esc closes one window, even when the close hands focus to another window.
371
+
372
+ `tests/managed_window_lifecycle.test.mjs` holds all of these.
@@ -2,7 +2,7 @@
2
2
 
3
3
  > **Package:** `@flexdesk/widgets` · [All docs](../README.md)
4
4
 
5
- `createMenuBar` draws File / Edit / View menus from one declarative model, with pointer and keyboard behaviour and the WAI-ARIA menubar roles, and plugs into the shell. `ActionDropdown` draws a dropdown of action buttons from a trigger.
5
+ `createMenuBar` draws File / Edit / View menus from one declarative model, with pointer and keyboard behaviour and the WAI-ARIA menubar roles, and plugs into the shell. `ActionDropdown` draws a dropdown of action buttons from a trigger. `CategoryPicker` draws a categorised picker (search, categories, items, a detail pane) from a trigger.
6
6
 
7
7
  ## Context menus stay inside the window (0.17.9)
8
8
 
@@ -188,3 +188,87 @@ its right edge. Either flips to the other edge when the menu would leave the
188
188
  window. A tile's ⋯ menu uses `'end'`
189
189
  ([Dashboard tiles](tiles.md#the--menu-is-drawn-over-the-page-0179)).
190
190
 
191
+ ## A categorised picker: `CategoryPicker` (0.17.16)
192
+
193
+ An `ActionDropdown` is a flat list. When the choices fall into categories and
194
+ each needs a sentence (and perhaps a sample) before it is chosen, use
195
+ `CategoryPicker`: a search field over three columns — the categories (a
196
+ colour dot, the label, the item count), the active category's items (an icon
197
+ and the label), and a detail pane for the highlighted item (its title,
198
+ description, an optional monospace preview and an Add button).
199
+
200
+ ```js
201
+ import { CategoryPicker } from '@flexdesk/widgets';
202
+
203
+ const picker = new CategoryPicker({
204
+ trigger: addCellButton,
205
+ categories: [
206
+ { id: 'text', label: 'Text', color: 'var(--accent-light)', items: [
207
+ { id: 'heading', label: 'Heading', badge: 'H',
208
+ description: 'A section heading; it shapes the outline.',
209
+ preview: '## Debt dynamics' },
210
+ { id: 'doc', label: 'Documentation', icon: 'description',
211
+ description: 'Markdown with LaTeX math.', keywords: ['markdown', 'latex'] },
212
+ ] },
213
+ { id: 'functions', label: 'Functions', color: 'var(--color-warning)', items: [
214
+ { id: 'lag', label: 'Delay / Lag', badge: 'Δt',
215
+ description: 'The value of an expression some time ago.',
216
+ preview: 'expected = lag(inflation, 2)', keywords: 'delay history' },
217
+ ] },
218
+ ],
219
+ onSelect: (item, category) => insertCell(category.id, item.id),
220
+ placeholder: 'Search cells…', // the search field's placeholder (default 'Search…')
221
+ addLabel: 'Add cell', // the detail pane's button (default 'Add')
222
+ previewLabel: 'Writes', // a caption over the preview (none by default)
223
+ align: 'start', // or 'end', as ActionDropdown.position takes it
224
+ label: 'Add a cell', // the popover's accessible name (default 'Pick')
225
+ });
226
+ // The trigger's click toggles it; or drive it:
227
+ picker.open(); picker.close(); picker.toggle(); picker.position();
228
+ picker.setCategories(next); // finds the active category and the highlight again by id
229
+ picker.destroy();
230
+ ```
231
+
232
+ An item has `id` and `label`, and optionally `icon` (a Material Symbols
233
+ ligature) or `badge` (a short text drawn as a monospace badge in the
234
+ category's colour), `description`, `preview` (text, shown monospace and
235
+ pre-wrapped) and `keywords` (an array or a string; searched, never shown). A
236
+ category's `color` is any CSS colour; it reaches the rows as
237
+ `--twm-category-picker-color`, and without one the dot and badge are muted.
238
+ `onSelect(item, category)` gets your own objects back.
239
+
240
+ - **Anchored like ActionDropdown.** It is appended to `body`, placed by
241
+ `ActionDropdown.position` (`position: fixed`, under the trigger when it fits,
242
+ flipped above it when it does not, kept 8px inside the window, capped to the
243
+ room there), and drawn on `--z-over-windows`, over the managed window it
244
+ opened from. Its columns scroll inside; the popover itself does not. The
245
+ trigger gets `aria-haspopup="dialog"`, `aria-expanded` and `twm-is-open`.
246
+ - **Search.** Focus is in the search field on open (the query starts empty).
247
+ Typing filters every category's items by label, description and keywords:
248
+ each word must appear, case-insensitively. While a query is set the items
249
+ column is one hit list grouped under the category labels, the counts are hit
250
+ counts (a category with none is dimmed), and the first hit is highlighted.
251
+ Clicking a category then jumps to its first hit.
252
+ - **Keyboard.** With no query, Up/Down move within the current column (the
253
+ categories, or the items) and wrap; Left/Right move between the two. With a
254
+ query, Up/Down walk the hit list across categories and Left/Right stay the
255
+ caret's. Enter picks the highlighted item. A printable key typed while a row
256
+ has focus goes to the search field. Tab and Shift+Tab stay inside: they
257
+ cycle the search field and Add (when it is enabled), as a modal
258
+ ManagedWindow's focus trap does. Keys an IME is composing with are left to
259
+ it. Escape closes the picker and nothing
260
+ else (it is caught first and stopped, so a ManagedWindow around it stays
261
+ open). Escape and a pick give focus back to the trigger.
262
+ - **Pointer.** Hovering or clicking an item shows it in the detail pane;
263
+ double-clicking it, or Add, picks it. A click outside closes the picker.
264
+ Pressing anywhere inside but the search field keeps focus where it is, so
265
+ the keyboard keeps working (the preview's text cannot be drag-selected).
266
+ - **Accessibility.** The popover is a named `role="dialog"`. The search field
267
+ is a combobox whose `aria-activedescendant` is the active category while the
268
+ categories column is current, else the highlighted item. While a query is
269
+ set, each category's hits are a `role="group"` named by its header.
270
+
271
+ The Add button is FlexDesk's `twm-btn twm-btn--primary`; everything else is
272
+ styled from the tokens (`tests/category_picker.test.mjs` checks there is no
273
+ colour literal in its rules, and that base.css and flexdesk.css carry the same
274
+ block).
@@ -155,6 +155,58 @@ content: {
155
155
  chrome shows only — the tree, the tab labels and the saved layout are left
156
156
  alone — and a call from content whose pane has since been rebuilt does nothing.
157
157
 
158
+ ## A tile breadcrumb of one segment is not drawn (0.17.15)
159
+
160
+ `mountTileBreadcrumb` (and the page shell around a content, which mounts one)
161
+ draws the path to where a tile is: an optional root crumb, the top-nav page,
162
+ the trail, the entity's ancestors and the entity. When that path is a single
163
+ segment (or none), for instance a top-nav page with no root crumb, no trail and
164
+ no entity, or an `appGlobal` kind, the strip says nothing the tile's own title
165
+ bar does not already say. It is then `hidden`, and
166
+ `.twm-page-shell__breadcrumb` around it takes no room, so the tile's content
167
+ starts right under its title. `refresh()` shows the strip again as soon as the
168
+ path has two segments; a path of two or more is drawn as before. Until 0.17.14
169
+ a top-level page showed its name twice, in the title bar and in a one-segment
170
+ breadcrumb under it (STORY-0124). `renderBreadcrumb`, which draws segments a
171
+ content routes itself, is unchanged.
172
+
173
+ ## A kind that lives in a window: `windowOnly` and `window` (0.17.16)
174
+
175
+ A kind can be a tool rather than a page: it opens in a managed window, never in
176
+ a tile, and it can say how big that window is.
177
+
178
+ ```js
179
+ const taxonomy = createTaxonomy({
180
+ root: 'start',
181
+ kinds: {
182
+ start: { label: 'Start', icon: 'apps' },
183
+ estimation: { label: 'Parameter Estimation', icon: 'tune',
184
+ single: true, windowOnly: true, window: { width: 1100, height: 700 } },
185
+ },
186
+ });
187
+ wm.navigate('estimation'); // a window, whatever `dest` says
188
+ ```
189
+
190
+ - **`window: { width?, height? }`** is the initial size, in px, of a window the
191
+ kind opens in, by `navigate(…, { dest: 'window' })` or, for a window-only
192
+ kind, always. ManagedWindow clamps it to the screen and to its minimum
193
+ (400 × 300), so a smaller size opens at the minimum. Without it a window
194
+ opens at ManagedWindow's default (600 × 400).
195
+ - **`windowOnly: true`** sends every open path to a window: `navigate` with
196
+ any `dest`, `openInPrimary`, `openInLeaf`, the tab verbs and
197
+ `splitLeafWith`, `navigateActiveTab` (crumbs, Backspace) and
198
+ `openInWindow`. With `single`, opening it again reveals the window that is
199
+ open. Its window never goes into a tile: it has no *Back to tile*, its
200
+ maximise button maximises it, and it does not snap onto a tile when dragged.
201
+ A restored layout that still holds a tab of the kind in a tile (or archived
202
+ under a page) drops it and warns once, naming the kinds; the rest of the
203
+ layout stays, and the next open makes a window. An entry of it in a tab's
204
+ back history (a layout saved before the kind was window-only) opens its
205
+ window when Backspace reaches it. Neither the root kind nor a top-nav
206
+ page can be window-only (`createTaxonomy` refuses).
207
+ - `taxonomy.isWindowOnly(kind)` and `taxonomy.windowSize(kind)` answer both
208
+ (STORY-0127).
209
+
158
210
  ## Buttons that change with the pane: `ctx.setChromeActions` (0.17.0)
159
211
 
160
212
  A factory may return `chromeActions` — `[{icon, title, onClick}]`, drawn in the
@@ -66,3 +66,18 @@ Until 0.17.4 a panel in a narrow tile reached past the tile's left edge across
66
66
  the neighbouring pane, and neither icon had a size of its own: both took
67
67
  whatever the page's global icon size was. `tests/slide_out_panel_clamp.test.mjs`
68
68
  holds it.
69
+
70
+ ## Never drawn outside its box (0.17.15)
71
+
72
+ While it is open the panel is `position: fixed`, pinned to its host's right
73
+ edge, so the host's `overflow: hidden` no longer clips it. Until 0.17.14 it
74
+ slid in (and out) by `translateX(100%)` and, on the way, was drawn across
75
+ whatever lies right of the host: a dock panel, an inspector. It no longer
76
+ moves: a clip alone carries the motion, revealing it from its right edge
77
+ leftwards (`clip-path: inset(0 0 0 100%)` closed, `inset(0 0 0 -16px)` open,
78
+ 200ms ease-out), so no frame paints outside its final box, which lies inside
79
+ the host. No transform is animated beside the clip: the two could drift apart,
80
+ the transform often running on the compositor. The left inset of -16px keeps
81
+ the panel's shadow; when the panel is as wide as its host (BUG-0035's cap)
82
+ that shadow falls on the neighbouring pane, as before. Nothing changes for
83
+ callers (BUG-0051).
@@ -233,3 +233,40 @@ Until 0.17.8 the menu was `position: absolute` inside the tile, so in a tile
233
233
  narrower than about 220px its left part and its labels were cut off
234
234
  (BUG-0043). `tests/tile_menu_unclipped.test.mjs` holds it.
235
235
 
236
+
237
+ ## A double-click on the title strip configures the tile (0.17.16)
238
+
239
+ A double-click on a tile's header (`.tile-header`: its title, its drag grip or
240
+ the strip between them) does what the cogwheel (`.tile-config-btn`) does: it
241
+ calls the same `_onConfigClick`, so the tile is selected and
242
+ `tile:config-request` goes out on the event bus and as a DOM event exactly as
243
+ for a click on the cogwheel. A double-click on one of the strip's buttons
244
+ (cogwheel, ⋯, info, expand, remove) or in an editable field you put in the
245
+ strip (`input`, `textarea`, `select`, `contenteditable`) does not. A read-only
246
+ tile has no cogwheel and opens nothing.
247
+
248
+ `new TileGrid({ configOnDoubleClick: false })` turns it off for every tile of
249
+ the grid; a tile takes the same option (`tile.configOnDoubleClick`, read at the
250
+ double-click). `tests/tile_title_dblclick_config.test.mjs` holds it.
251
+
252
+ ## Several tiles marked as being configured: `grid.setConfiguring` (0.17.16)
253
+
254
+ A config request (cogwheel or double-click) highlights the requesting tile with
255
+ `tile--selected` and takes that highlight off every other tile of the grid:
256
+ one selection per grid, which suits one config panel. An embedder that keeps a
257
+ config window open per tile marks each of those tiles on the grid:
258
+
259
+ ```js
260
+ grid.setConfiguring(tileId, true); // when its config window opens
261
+ grid.setConfiguring(tileId, false); // when its config window closes
262
+ grid.isConfiguring(tileId); // true while marked
263
+ ```
264
+
265
+ The mark is the class `tile--configuring`, drawn exactly like `tile--selected`
266
+ (same rules in `flexdesk.css`). It is the embedder's alone: no config request,
267
+ on that tile or another, clears it. The grid holds it **by tile id**, so a tile
268
+ that `setLayout` disposes and rebuilds (or an id added later) is marked again,
269
+ and clearing it after a rebuild reaches the live tile. `tile.setConfiguring(on)`
270
+ and `tile.isConfiguring` remain as conveniences: on a tile in a grid they go
271
+ through the grid. Do not add or remove `tile--selected` from outside; it
272
+ belongs to the config request.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flexdesk",
3
- "version": "0.17.14",
3
+ "version": "0.17.16",
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": {