flexdesk 0.18.0 → 0.18.2
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 +58 -0
- package/css/flexdesk.css +83 -5
- package/css/tokens.css +7 -0
- package/dist/charts.js +78 -12
- package/dist/charts.js.map +2 -2
- package/dist/{chunk-5W4EEKSK.js → chunk-3BLIVOFD.js} +3 -3
- package/dist/{chunk-P74F6I6P.js → chunk-4X4SY3CP.js} +4 -4
- package/dist/{chunk-P74F6I6P.js.map → chunk-4X4SY3CP.js.map} +2 -2
- package/dist/{chunk-ZL3VKINP.js → chunk-HL3IIHU3.js} +494 -3
- package/dist/{chunk-ZL3VKINP.js.map → chunk-HL3IIHU3.js.map} +4 -4
- package/dist/{chunk-RKVYIXDC.js → chunk-NFZJ6P42.js} +19 -500
- package/dist/chunk-NFZJ6P42.js.map +7 -0
- package/dist/{chunk-U5GKJUOC.js → chunk-QRBOIUU3.js} +5 -6
- package/dist/chunk-QRBOIUU3.js.map +7 -0
- package/dist/flexdesk.css +83 -5
- package/dist/flow.js +22 -10
- package/dist/flow.js.map +2 -2
- package/dist/git.js +3 -3
- package/dist/tiles.js +4 -4
- package/dist/tokens.css +7 -0
- package/dist/widgets.js +226 -58
- package/dist/widgets.js.map +2 -2
- package/dist/wm.js +2 -2
- package/docs/components/charts.md +40 -0
- package/docs/components/chat-shell.md +8 -2
- package/docs/components/filter-builder.md +52 -6
- package/docs/components/flow-editors.md +20 -0
- package/docs/components/forms-and-dialogs.md +8 -0
- package/docs/components/input-fields.md +24 -0
- package/docs/components/menus.md +4 -0
- package/docs/components/shell.md +4 -0
- package/docs/components/workspace-picker.md +46 -2
- package/package.json +1 -1
- package/src/charts/plot_config_editor.js +92 -6
- package/src/charts/plot_grid.js +6 -1
- package/src/flow/kit/settings_panel.js +24 -9
- package/src/ui/components/chat_shell.js +7 -1
- package/src/ui/components/context_menu.js +8 -2
- package/src/ui/components/filter_builder.js +53 -28
- package/src/ui/components/filter_model.js +86 -18
- package/src/ui/components/inline_renamer.js +10 -5
- package/src/ui/components/modal.js +25 -8
- package/src/ui/components/workspace_picker.js +125 -27
- package/src/ui/utils/tooltip_service.js +81 -14
- package/dist/chunk-RKVYIXDC.js.map +0 -7
- package/dist/chunk-U5GKJUOC.js.map +0 -7
- /package/dist/{chunk-5W4EEKSK.js.map → chunk-3BLIVOFD.js.map} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,63 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.18.2 (2026-11-07)
|
|
4
|
+
|
|
5
|
+
A patch release with one addition.
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- [A flat settings panel: `variant: 'sheet'` on `createSettingsPanel`](docs/components/flow-editors.md#a-flat-panel-variant-sheet-0182): no fill, the title and description in a margin at the left, the fields beside them, a rule between two sheets. For a settings page made of several panels.
|
|
10
|
+
|
|
11
|
+
### Changed behaviour
|
|
12
|
+
|
|
13
|
+
None. A panel created without `variant` is the card it was.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- A settings panel's checkbox (a `boolean` setting) sits at the start of its field, under the label's line; it was drawn in the middle of the field's width.
|
|
18
|
+
|
|
19
|
+
### Removed or renamed exports
|
|
20
|
+
|
|
21
|
+
None. New: the `variant` option. Classes: `.twm-flow-panel--sheet`, `.twm-flow-panel__sheet`, `.twm-flow-panel__margin`, `.twm-flow-panel__body`.
|
|
22
|
+
|
|
23
|
+
## 0.18.1 (2026-11-06)
|
|
24
|
+
|
|
25
|
+
A patch release: what four applications asked for when they adopted 0.18.0. The
|
|
26
|
+
entries under "Changed behaviour" change what embedders see; the workspace picker's
|
|
27
|
+
keyboard is the one to read first.
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- [`hide` on `renderPlotConfigEditor`](docs/components/charts.md#hiding-parts-of-the-editor-hide-0181): takes away the 3D chart types with the z axis, the stack group and the x axis section (STORY-0109).
|
|
32
|
+
- [A reference line that names a constant](docs/components/charts.md#a-reference-line-that-names-a-constant-constantsprovider-resolveconstant-0181): `constantsProvider` on `renderPlotConfigEditor`, `resolveConstant` on `renderPlotGrid` (STORY-0109).
|
|
33
|
+
- [`dismissOnBackdrop` on `openModal`](docs/components/forms-and-dialogs.md#a-click-beside-a-modal-dismissonbackdrop-0181) and [on `openFilterDialog`](docs/components/filter-builder.md#a-click-beside-the-dialog-dismissonbackdrop-0181): a click on the dimmed page beside it dismisses it, through `beforeClose`. The press takes no focus from the dialog, so when `beforeClose` refuses, the caret is where it was.
|
|
34
|
+
- [`confirm.message` as a function](docs/components/filter-builder.md#a-question-that-quotes-what-was-typed-0181) on `openFilterDialog`'s `actions`, computed when it is asked.
|
|
35
|
+
- [`groupBrackets` on `describeFilter`](docs/components/filter-builder.md#what-a-sentence-says-0181); [`model.partialRanges`](docs/components/filter-builder.md#a-range-with-one-end-filled-is-refused-0181) and [`model.moveClause`](docs/components/filter-builder.md#what-a-condition-keeps-when-its-field-changes-0181).
|
|
36
|
+
- [Tokens for the picker field's look](docs/components/workspace-picker.md#the-fields-look-tokens-0181), and [a trigger of your own on `createPickerSwitcher`](docs/components/workspace-picker.md#a-trigger-of-your-own-0181), written down and tested.
|
|
37
|
+
- `attachInlineRenamer`'s `onStart` is handed the input: [`{ value, triggerEvent, input }`](docs/components/input-fields.md#a-list-under-a-name-being-edited-attachinlinerenamer-0181).
|
|
38
|
+
|
|
39
|
+
### Changed behaviour
|
|
40
|
+
|
|
41
|
+
- **[The workspace picker's keyboard moves real focus](docs/components/workspace-picker.md#the-keyboard-0181).** It used to keep the caret in the input and walk a highlight, so the card's and the footer's buttons were never announced. The arrows now move `document.activeElement` over every stop, the input among them; the rows are one tab stop; a character typed anywhere in the panel goes to the input. **The first ArrowDown from the input lands on the first row, not the second**, so a sequence of arrows reaches one stop later than before: `ArrowDown, Enter` takes the first row. The first ArrowUp lands on the last stop, the footer's last button if there is one. After `open({ select })` the arrows go on from the row it chose, as they did. A test dispatching keys on the input keeps working, one arrow longer. An answer that moves the row with focus leaves focus on it. Load more is `aria-disabled` while something loads, not `disabled`, so it keeps focus and refuses the press itself: a test reading `.disabled` reads the attribute. Enter in the input, Esc, a press outside and Shift+Delete do as they did, and Shift+Delete also works on a focused row.
|
|
42
|
+
- **[Stop releases a turn that is waiting](docs/components/chat-shell.md#stop-releases-a-turn-that-is-waiting-0181).** A cancel promise that never settles, or a lost run-over, no longer hangs later sends: stopping the waiting turn sends the next one at once. A backend without a lock can then see two runs overlap; one that takes one run at a time should refuse the second.
|
|
43
|
+
- **[Keyboard focus shows a tooltip](docs/components/shell.md#keyboard-focus-shows-a-tooltip-0181)**: `TooltipService` listens to `focusin` and `focusout` as well as the mouse. Keyboard focus means the last press was a key, not a Ctrl, Cmd or Alt chord, so the focus a click leaves shows nothing, nor does a text field its handler then focuses. While focus holds a tooltip a scroll does not hide it, the one the focus itself caused included: it goes with its target, and waits while the target is out of sight. 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 consumer's own wiring of the same can go.
|
|
44
|
+
- **[A press inside no longer disarms a context menu](docs/components/menus.md#a-press-inside-no-longer-disarms-a-context-menu-0181).** `showContextMenu`'s outside-press listener lasts until the menu closes.
|
|
45
|
+
- **[`attachInlineRenamer` runs `onStart` before adding its own key listeners](docs/components/input-fields.md#a-list-under-a-name-being-edited-attachinlinerenamer-0181)**, so what it attaches hears a key first and Enter on a highlighted row commits the row. Taking its field off the page no longer fires a second commit through blur after a commit or an Esc, so a consumer's guard against that can go.
|
|
46
|
+
- **[`openFilterDialog` refuses a range with one end filled](docs/components/filter-builder.md#a-range-with-one-end-filled-is-refused-0181)**, whatever `requireValid` and `prune` say. Such a filter used to leave the dialog and match nothing.
|
|
47
|
+
- **[A seed goes beside a negated root](docs/components/filter-builder.md#a-seed-beside-a-negated-root-0181)**, not inside it, where it undid the filter.
|
|
48
|
+
- **[Changing a condition's field keeps its operator and value](docs/components/filter-builder.md#what-a-condition-keeps-when-its-field-changes-0181)** when the new field offers the one and the other still fits. It used to start over, and `prune` then dropped the row.
|
|
49
|
+
- **[`validateFilter` checks every value of a `multi` operator](docs/components/filter-builder.md#what-a-condition-keeps-when-its-field-changes-0181)** as it does a single one: `one_of ['abc']` on a number field, or `one_of ['soon']` on a date field whose `operators` offer `one_of`, is an error. It used to pass, and a server's type check was the first to refuse it.
|
|
50
|
+
- **[A held condition under an operator its field does not offer shows its value read-only](docs/components/filter-builder.md#what-a-condition-keeps-when-its-field-changes-0181)**, where a box looked empty over a value the result still carried, and a custom `editor` is not asked about one. Its operator select reads as the catalogue's label on an unknown field too: `is`, not `is (unknown)`. An unknown field's whole warning is the select's tooltip.
|
|
51
|
+
- **[`describeFilter` says more](docs/components/filter-builder.md#what-a-sentence-says-0181)**: every value of a list under a single-value operator, and an unknown field's operator and value (`⚠ unknown field “gone” is x`). A consumer's test on the old sentence sees the difference.
|
|
52
|
+
- [The filter dialog's name box opens with its text selected](docs/components/filter-builder.md#the-name-box-opens-selected-0181), and a days box has `min="0"`.
|
|
53
|
+
- [`openModal` leaves focus where `onMount` put it](docs/components/forms-and-dialogs.md#focus-placed-by-onmount-stays-0181), with its selection, rather than moving it to the first field.
|
|
54
|
+
- `mountChatShell`'s model button has `aria-expanded`, so its open state is announced.
|
|
55
|
+
- `activeConversation()` [answers an id](docs/components/chat-shell.md#conversations), as it always did; the docs read as if it answered a conversation.
|
|
56
|
+
|
|
57
|
+
### Removed or renamed exports
|
|
58
|
+
|
|
59
|
+
None. New: the `hide` and `constantsProvider` options, the `resolveConstant` option, `dismissOnBackdrop`, `groupBrackets`, `model.partialRanges`, `model.moveClause`. Custom properties in `tokens.css`: `--twm-wspicker-field-icon`, `--twm-wspicker-field-focus-border`, `--twm-wspicker-field-muted-icon`, `--twm-wspicker-field-muted-style`, `--twm-wspicker-field-muted-opacity`. Classes: `.twm-filter-builder__select--unknown`, `.twm-plot-config__constant`, `.twm-plot-config__constant-value`.
|
|
60
|
+
|
|
3
61
|
## 0.18.0 (2026-11-05)
|
|
4
62
|
|
|
5
63
|
A minor release. A new entry point, `@flexdesk/git`, and six new components for
|
package/css/flexdesk.css
CHANGED
|
@@ -4899,14 +4899,14 @@ body.twm-window-maximized .twm-resize-handle {
|
|
|
4899
4899
|
.twm-wspicker-field--open {
|
|
4900
4900
|
outline: none;
|
|
4901
4901
|
background: var(--state-active);
|
|
4902
|
-
border-color: var(--
|
|
4902
|
+
border-color: var(--twm-wspicker-field-focus-border);
|
|
4903
4903
|
}
|
|
4904
4904
|
.twm-wspicker-field [hidden] {
|
|
4905
4905
|
display: none;
|
|
4906
4906
|
}
|
|
4907
4907
|
.twm-wspicker-field__icon.material-symbols-outlined {
|
|
4908
4908
|
flex: none;
|
|
4909
|
-
color: var(--
|
|
4909
|
+
color: var(--twm-wspicker-field-icon);
|
|
4910
4910
|
font-size: 15px;
|
|
4911
4911
|
opacity: 0.85;
|
|
4912
4912
|
}
|
|
@@ -4921,11 +4921,11 @@ body.twm-window-maximized .twm-resize-handle {
|
|
|
4921
4921
|
white-space: nowrap;
|
|
4922
4922
|
}
|
|
4923
4923
|
.twm-wspicker-field--muted .twm-wspicker-field__name {
|
|
4924
|
-
font-style:
|
|
4925
|
-
opacity:
|
|
4924
|
+
font-style: var(--twm-wspicker-field-muted-style);
|
|
4925
|
+
opacity: var(--twm-wspicker-field-muted-opacity);
|
|
4926
4926
|
}
|
|
4927
4927
|
.twm-wspicker-field--muted .twm-wspicker-field__icon.material-symbols-outlined {
|
|
4928
|
-
color: var(--
|
|
4928
|
+
color: var(--twm-wspicker-field-muted-icon);
|
|
4929
4929
|
}
|
|
4930
4930
|
.twm-wspicker-field__key {
|
|
4931
4931
|
flex: none;
|
|
@@ -5063,6 +5063,10 @@ body.twm-window-maximized .twm-resize-handle {
|
|
|
5063
5063
|
background: var(--state-selected);
|
|
5064
5064
|
color: var(--text-bright);
|
|
5065
5065
|
}
|
|
5066
|
+
/* A row with focus wears the highlight; no ring as well. */
|
|
5067
|
+
.twm-wspicker__row:focus {
|
|
5068
|
+
outline: none;
|
|
5069
|
+
}
|
|
5066
5070
|
.twm-wspicker__row--disabled {
|
|
5067
5071
|
color: var(--text-dim);
|
|
5068
5072
|
cursor: default;
|
|
@@ -5143,6 +5147,10 @@ body.twm-window-maximized .twm-resize-handle {
|
|
|
5143
5147
|
width: calc(100% - 2 * var(--space-md));
|
|
5144
5148
|
margin: var(--space-xs) var(--space-md);
|
|
5145
5149
|
}
|
|
5150
|
+
.twm-wspicker__more[aria-disabled="true"] {
|
|
5151
|
+
opacity: 0.4;
|
|
5152
|
+
cursor: not-allowed;
|
|
5153
|
+
}
|
|
5146
5154
|
.twm-wspicker__more-label {
|
|
5147
5155
|
min-width: 0;
|
|
5148
5156
|
overflow: hidden;
|
|
@@ -5392,6 +5400,9 @@ body.twm-window-maximized .twm-resize-handle {
|
|
|
5392
5400
|
.twm-filter-builder__select--field {
|
|
5393
5401
|
flex: 0 1 140px;
|
|
5394
5402
|
}
|
|
5403
|
+
.twm-filter-builder__select--unknown {
|
|
5404
|
+
flex: 0 1 auto;
|
|
5405
|
+
}
|
|
5395
5406
|
.twm-filter-builder__select--op {
|
|
5396
5407
|
flex: 0 1 120px;
|
|
5397
5408
|
}
|
|
@@ -12372,6 +12383,23 @@ button.twm-alerts__ref:hover { text-decoration: underline; }
|
|
|
12372
12383
|
.twm-plot-config__row > .twm-plot-config__input--select {
|
|
12373
12384
|
flex: 0 1 auto;
|
|
12374
12385
|
}
|
|
12386
|
+
/* A reference line's value that may name a constant, and what the name is now. */
|
|
12387
|
+
.twm-plot-config__constant {
|
|
12388
|
+
display: inline-flex;
|
|
12389
|
+
flex: 1 1 140px;
|
|
12390
|
+
align-items: center;
|
|
12391
|
+
gap: var(--space-xs);
|
|
12392
|
+
min-width: 0;
|
|
12393
|
+
}
|
|
12394
|
+
.twm-plot-config__constant > .twm-plot-config__input {
|
|
12395
|
+
flex: 1 1 auto;
|
|
12396
|
+
min-width: 0;
|
|
12397
|
+
}
|
|
12398
|
+
.twm-plot-config__constant-value {
|
|
12399
|
+
flex: none;
|
|
12400
|
+
color: var(--text-muted);
|
|
12401
|
+
font-size: var(--font-size-xs);
|
|
12402
|
+
}
|
|
12375
12403
|
/* On a field line a colour is the swatch and a hex box sized to `#rrggbb`. */
|
|
12376
12404
|
.twm-plot-config__field > .twm-color-field {
|
|
12377
12405
|
flex: 0 1 auto;
|
|
@@ -13614,6 +13642,55 @@ button.twm-alerts__ref:hover { text-decoration: underline; }
|
|
|
13614
13642
|
color: var(--twm-flow-warning-text);
|
|
13615
13643
|
}
|
|
13616
13644
|
|
|
13645
|
+
/* ── the settings panel as a sheet (variant: 'sheet', 0.18.2) ──────────
|
|
13646
|
+
* Flat: no fill and no chip. The title and the description sit in a margin at
|
|
13647
|
+
* the left, the fields beside them; two sheets in a row are split by a rule, so
|
|
13648
|
+
* a page of them reads as one form in sections. Narrower than 560px, the margin
|
|
13649
|
+
* goes above the fields. */
|
|
13650
|
+
.twm-flow-panel--sheet {
|
|
13651
|
+
container-type: inline-size;
|
|
13652
|
+
gap: 0;
|
|
13653
|
+
padding: 14px 0;
|
|
13654
|
+
background: transparent;
|
|
13655
|
+
}
|
|
13656
|
+
.twm-flow-panel--sheet + .twm-flow-panel--sheet {
|
|
13657
|
+
border-top: 1px solid var(--border-subtle);
|
|
13658
|
+
}
|
|
13659
|
+
.twm-flow-panel__sheet {
|
|
13660
|
+
display: grid;
|
|
13661
|
+
grid-template-columns: minmax(120px, 180px) minmax(0, 1fr);
|
|
13662
|
+
gap: 10px 28px;
|
|
13663
|
+
align-items: start;
|
|
13664
|
+
}
|
|
13665
|
+
.twm-flow-panel__margin {
|
|
13666
|
+
display: flex;
|
|
13667
|
+
flex-direction: column;
|
|
13668
|
+
gap: 4px;
|
|
13669
|
+
min-width: 0;
|
|
13670
|
+
padding-top: 5px;
|
|
13671
|
+
}
|
|
13672
|
+
.twm-flow-panel__body {
|
|
13673
|
+
display: flex;
|
|
13674
|
+
flex-direction: column;
|
|
13675
|
+
gap: 12px;
|
|
13676
|
+
min-width: 0;
|
|
13677
|
+
}
|
|
13678
|
+
.twm-flow-panel--sheet .twm-flow-panel__icon { display: none; }
|
|
13679
|
+
.twm-flow-panel--sheet .twm-flow-panel__titles { flex-basis: auto; }
|
|
13680
|
+
.twm-flow-panel--sheet .twm-flow-panel__title,
|
|
13681
|
+
.twm-flow-panel--sheet .twm-flow-panel__title-input {
|
|
13682
|
+
font-size: var(--font-size-md);
|
|
13683
|
+
}
|
|
13684
|
+
.twm-flow-panel--sheet .twm-flow-panel__description {
|
|
13685
|
+
font-size: var(--font-size-xs);
|
|
13686
|
+
color: var(--twm-flow-muted);
|
|
13687
|
+
}
|
|
13688
|
+
.twm-flow-panel--sheet .twm-flow-panel__fields { gap: 10px; }
|
|
13689
|
+
.twm-flow-panel--sheet .twm-flow-field__label { flex-basis: 110px; }
|
|
13690
|
+
@container (max-width: 560px) {
|
|
13691
|
+
.twm-flow-panel__sheet { grid-template-columns: minmax(0, 1fr); }
|
|
13692
|
+
}
|
|
13693
|
+
|
|
13617
13694
|
/* ── a field: its label, its control, its finding, its help ─────────── */
|
|
13618
13695
|
.twm-flow-field {
|
|
13619
13696
|
display: flex;
|
|
@@ -13759,6 +13836,7 @@ button.twm-alerts__ref:hover { text-decoration: underline; }
|
|
|
13759
13836
|
resize: vertical;
|
|
13760
13837
|
}
|
|
13761
13838
|
.twm-flow-checkbox {
|
|
13839
|
+
align-self: flex-start;
|
|
13762
13840
|
margin: 6px 0 0;
|
|
13763
13841
|
accent-color: var(--twm-flow-focus);
|
|
13764
13842
|
}
|
package/css/tokens.css
CHANGED
|
@@ -138,6 +138,13 @@
|
|
|
138
138
|
--twm-rail-icon: #9e9e9e; /* an item at rest */
|
|
139
139
|
--twm-rail-icon-on: var(--text-bright); /* hovered, and the active item with its bar */
|
|
140
140
|
|
|
141
|
+
/* The workspace picker's field (0.18.1). */
|
|
142
|
+
--twm-wspicker-field-icon: var(--accent-bright);
|
|
143
|
+
--twm-wspicker-field-focus-border: var(--accent-bright); /* focused by keyboard, and open */
|
|
144
|
+
--twm-wspicker-field-muted-icon: var(--text-muted); /* naming nothing */
|
|
145
|
+
--twm-wspicker-field-muted-style: italic;
|
|
146
|
+
--twm-wspicker-field-muted-opacity: 0.55;
|
|
147
|
+
|
|
141
148
|
/* ── Overlay scrollbars (installOverlayScrollbar) ─────────── */
|
|
142
149
|
--scrollbar-size: 6px;
|
|
143
150
|
--scrollbar-radius: 3px;
|
package/dist/charts.js
CHANGED
|
@@ -55,17 +55,18 @@ import {
|
|
|
55
55
|
strideIndices,
|
|
56
56
|
uid,
|
|
57
57
|
updateChart
|
|
58
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-4X4SY3CP.js";
|
|
59
59
|
import {
|
|
60
60
|
AutocompleteField,
|
|
61
61
|
createRafResizeObserver
|
|
62
|
-
} from "./chunk-
|
|
62
|
+
} from "./chunk-QRBOIUU3.js";
|
|
63
63
|
import "./chunk-VLOAI3HS.js";
|
|
64
64
|
import {
|
|
65
65
|
ColorField,
|
|
66
66
|
ManagedWindow,
|
|
67
|
-
ToggleField
|
|
68
|
-
|
|
67
|
+
ToggleField,
|
|
68
|
+
attachInputOptions
|
|
69
|
+
} from "./chunk-HL3IIHU3.js";
|
|
69
70
|
import {
|
|
70
71
|
selectBox
|
|
71
72
|
} from "./chunk-Q7BALEPX.js";
|
|
@@ -709,6 +710,7 @@ var CHART_TYPE_OPTIONS = Object.freeze([
|
|
|
709
710
|
{ group: "2D", options: CHART_TYPES_2D },
|
|
710
711
|
{ group: "3D", options: CHART_TYPES_3D }
|
|
711
712
|
]);
|
|
713
|
+
var HIDEABLE = Object.freeze(["3d", "stackGroup", "xAxis"]);
|
|
712
714
|
var SCALES = Object.freeze([{ value: "linear", label: "Linear" }, { value: "log", label: "Log" }]);
|
|
713
715
|
var POSITIONS = Object.freeze([{ value: "left", label: "Left" }, { value: "right", label: "Right" }]);
|
|
714
716
|
var DASHES = Object.freeze([
|
|
@@ -829,9 +831,15 @@ function renderPlotConfigEditor(container, config, options = {}) {
|
|
|
829
831
|
overlaySources: null,
|
|
830
832
|
seriesTransforms: null,
|
|
831
833
|
extraSeriesFields: null,
|
|
834
|
+
hide: [],
|
|
835
|
+
constantsProvider: null,
|
|
832
836
|
logger: null,
|
|
833
837
|
...options
|
|
834
838
|
};
|
|
839
|
+
for (const part of opts.hide) {
|
|
840
|
+
if (!HIDEABLE.includes(part)) throw new TypeError(`[plot config editor] cannot hide "${part}"`);
|
|
841
|
+
}
|
|
842
|
+
const hidden = new Set(opts.hide);
|
|
835
843
|
const editorId = `twm-plot-config-${++editorCount}`;
|
|
836
844
|
let fieldCount = 0;
|
|
837
845
|
let active = 0;
|
|
@@ -985,6 +993,7 @@ function renderPlotConfigEditor(container, config, options = {}) {
|
|
|
985
993
|
}
|
|
986
994
|
function normalise(sp) {
|
|
987
995
|
if (!sp.xAxis) sp.xAxis = { useTime: true, variable: "" };
|
|
996
|
+
if (hidden.has("xAxis")) sp.xAxis.useTime = true;
|
|
988
997
|
if (!Array.isArray(sp.yAxes) || !sp.yAxes.length) sp.yAxes = [makeDefaultYAxis(0)];
|
|
989
998
|
for (const axis of sp.yAxes) if (!Array.isArray(axis.series)) axis.series = [];
|
|
990
999
|
if (is3DChart(sp.chartType) && !sp.zAxis) sp.zAxis = makeZAxis();
|
|
@@ -1009,9 +1018,9 @@ function renderPlotConfigEditor(container, config, options = {}) {
|
|
|
1009
1018
|
{ placeholder: "Untitled" }
|
|
1010
1019
|
))),
|
|
1011
1020
|
section("Display", ...displayFields(sp)),
|
|
1012
|
-
section("X axis", ...xAxisFields(sp)),
|
|
1021
|
+
hidden.has("xAxis") ? null : section("X axis", ...xAxisFields(sp)),
|
|
1013
1022
|
section("Y axes", yAxesEditor(sp, is3D, overlaySources)),
|
|
1014
|
-
is3D ? section("Z axis", ...zAxisFields(sp)) : null,
|
|
1023
|
+
is3D && !hidden.has("3d") ? section("Z axis", ...zAxisFields(sp)) : null,
|
|
1015
1024
|
section("Reference lines", referenceLinesEditor(sp)),
|
|
1016
1025
|
section("Shaded bands", shadedBandsEditor(sp)),
|
|
1017
1026
|
transformsSection(sp)
|
|
@@ -1019,7 +1028,7 @@ function renderPlotConfigEditor(container, config, options = {}) {
|
|
|
1019
1028
|
}
|
|
1020
1029
|
function displayFields(sp) {
|
|
1021
1030
|
return [
|
|
1022
|
-
field("Chart type", select(CHART_TYPE_OPTIONS, sp.chartType ?? "line", (v) => {
|
|
1031
|
+
field("Chart type", select(hidden.has("3d") ? CHART_TYPES_2D : CHART_TYPE_OPTIONS, sp.chartType ?? "line", (v) => {
|
|
1023
1032
|
const was3D = is3DChart(sp.chartType);
|
|
1024
1033
|
sp.chartType = v;
|
|
1025
1034
|
const now3D = is3DChart(v);
|
|
@@ -1206,16 +1215,18 @@ function renderPlotConfigEditor(container, config, options = {}) {
|
|
|
1206
1215
|
field("Dash", select(DASHES, s.lineStyle ?? "solid", (v) => {
|
|
1207
1216
|
s.lineStyle = v;
|
|
1208
1217
|
notify();
|
|
1209
|
-
}, { label: "Dash" }))
|
|
1210
|
-
|
|
1218
|
+
}, { label: "Dash" }))
|
|
1219
|
+
);
|
|
1220
|
+
if (!hidden.has("stackGroup")) {
|
|
1221
|
+
fields.appendChild(field("Stack group", textInput(
|
|
1211
1222
|
s.stackGroup,
|
|
1212
1223
|
(v) => {
|
|
1213
1224
|
s.stackGroup = v || null;
|
|
1214
1225
|
notify();
|
|
1215
1226
|
},
|
|
1216
1227
|
{ placeholder: "None", label: "Stack group" }
|
|
1217
|
-
))
|
|
1218
|
-
|
|
1228
|
+
)));
|
|
1229
|
+
}
|
|
1219
1230
|
if (opts.extraSeriesFields) fields.appendChild(extraFields(sp, axis, s));
|
|
1220
1231
|
if (opts.overlaySources) fields.appendChild(overlayEditor(s, overlaySources));
|
|
1221
1232
|
row.append(head, fields);
|
|
@@ -1333,6 +1344,61 @@ function renderPlotConfigEditor(container, config, options = {}) {
|
|
|
1333
1344
|
});
|
|
1334
1345
|
return wrap;
|
|
1335
1346
|
}
|
|
1347
|
+
function constantValueField(line) {
|
|
1348
|
+
const wrap = el("div", "twm-plot-config__constant");
|
|
1349
|
+
const input = el("input", "twm-plot-config__input");
|
|
1350
|
+
input.type = "text";
|
|
1351
|
+
input.spellcheck = false;
|
|
1352
|
+
input.placeholder = "Number or name";
|
|
1353
|
+
input.setAttribute("aria-label", "Value");
|
|
1354
|
+
input.value = line.value ?? "";
|
|
1355
|
+
const now = el("span", "twm-plot-config__constant-value");
|
|
1356
|
+
wrap.append(input, now);
|
|
1357
|
+
const isNumber = (text) => text !== "" && Number.isFinite(Number(text));
|
|
1358
|
+
const sayValue = () => {
|
|
1359
|
+
const named = opts.constantsProvider().find((c) => c.name === input.value.trim());
|
|
1360
|
+
now.textContent = named ? `= ${named.value}` : "";
|
|
1361
|
+
};
|
|
1362
|
+
const settle = () => {
|
|
1363
|
+
const text = input.value.trim();
|
|
1364
|
+
const next = isNumber(text) ? Number(text) : text || null;
|
|
1365
|
+
sayValue();
|
|
1366
|
+
if (next === (line.value ?? null)) return;
|
|
1367
|
+
line.value = next;
|
|
1368
|
+
notify();
|
|
1369
|
+
};
|
|
1370
|
+
const offered = (query) => {
|
|
1371
|
+
const words = (query ?? "").trim().toLowerCase();
|
|
1372
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1373
|
+
for (const c of opts.constantsProvider()) {
|
|
1374
|
+
const text = c.label ?? c.name;
|
|
1375
|
+
if (words && !`${c.name} ${text}`.toLowerCase().includes(words)) continue;
|
|
1376
|
+
if (!groups.has(c.group)) groups.set(c.group, []);
|
|
1377
|
+
groups.get(c.group).push({ value: c.name, label: text, hint: `= ${c.value}` });
|
|
1378
|
+
}
|
|
1379
|
+
return [...groups].flatMap(([group, rows]) => group ? [{ group, options: rows }] : rows);
|
|
1380
|
+
};
|
|
1381
|
+
const list = attachInputOptions(input, {
|
|
1382
|
+
options: offered,
|
|
1383
|
+
debounce: 0,
|
|
1384
|
+
autoHighlight: true,
|
|
1385
|
+
isCandidate: (typed) => isNumber(typed.trim()),
|
|
1386
|
+
openOnFocus: true,
|
|
1387
|
+
isCurrent: (option) => option.value === input.value.trim(),
|
|
1388
|
+
onPick: (option) => {
|
|
1389
|
+
input.value = option.value;
|
|
1390
|
+
settle();
|
|
1391
|
+
},
|
|
1392
|
+
onSettle: settle
|
|
1393
|
+
});
|
|
1394
|
+
input.addEventListener("change", settle);
|
|
1395
|
+
input.addEventListener("keydown", (event) => {
|
|
1396
|
+
if (event.key === "Enter") settle();
|
|
1397
|
+
});
|
|
1398
|
+
track(() => list.destroy());
|
|
1399
|
+
sayValue();
|
|
1400
|
+
return wrap;
|
|
1401
|
+
}
|
|
1336
1402
|
function referenceLinesEditor(sp) {
|
|
1337
1403
|
const axisOptions = sp.yAxes.map((a, i) => ({ value: a.id, label: a.label || `Y axis ${i + 1}` }));
|
|
1338
1404
|
return listEditor(sp, "referenceLines", {
|
|
@@ -1348,7 +1414,7 @@ function renderPlotConfigEditor(container, config, options = {}) {
|
|
|
1348
1414
|
},
|
|
1349
1415
|
{ label: "Axis" }
|
|
1350
1416
|
),
|
|
1351
|
-
numberInput(line.value, (v) => {
|
|
1417
|
+
opts.constantsProvider ? constantValueField(line) : numberInput(line.value, (v) => {
|
|
1352
1418
|
line.value = v;
|
|
1353
1419
|
notify();
|
|
1354
1420
|
}, { placeholder: "Value", label: "Value" }),
|