flexdesk 0.18.0 → 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.
Files changed (44) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/css/flexdesk.css +33 -5
  3. package/css/tokens.css +7 -0
  4. package/dist/charts.js +78 -12
  5. package/dist/charts.js.map +2 -2
  6. package/dist/{chunk-5W4EEKSK.js → chunk-3BLIVOFD.js} +3 -3
  7. package/dist/{chunk-P74F6I6P.js → chunk-4X4SY3CP.js} +4 -4
  8. package/dist/{chunk-P74F6I6P.js.map → chunk-4X4SY3CP.js.map} +2 -2
  9. package/dist/{chunk-ZL3VKINP.js → chunk-HL3IIHU3.js} +494 -3
  10. package/dist/{chunk-ZL3VKINP.js.map → chunk-HL3IIHU3.js.map} +4 -4
  11. package/dist/{chunk-RKVYIXDC.js → chunk-NFZJ6P42.js} +19 -500
  12. package/dist/chunk-NFZJ6P42.js.map +7 -0
  13. package/dist/{chunk-U5GKJUOC.js → chunk-QRBOIUU3.js} +5 -6
  14. package/dist/chunk-QRBOIUU3.js.map +7 -0
  15. package/dist/flexdesk.css +33 -5
  16. package/dist/flow.js +1 -1
  17. package/dist/git.js +3 -3
  18. package/dist/tiles.js +4 -4
  19. package/dist/tokens.css +7 -0
  20. package/dist/widgets.js +226 -58
  21. package/dist/widgets.js.map +2 -2
  22. package/dist/wm.js +2 -2
  23. package/docs/components/charts.md +40 -0
  24. package/docs/components/chat-shell.md +8 -2
  25. package/docs/components/filter-builder.md +52 -6
  26. package/docs/components/forms-and-dialogs.md +8 -0
  27. package/docs/components/input-fields.md +24 -0
  28. package/docs/components/menus.md +4 -0
  29. package/docs/components/shell.md +4 -0
  30. package/docs/components/workspace-picker.md +46 -2
  31. package/package.json +1 -1
  32. package/src/charts/plot_config_editor.js +92 -6
  33. package/src/charts/plot_grid.js +6 -1
  34. package/src/ui/components/chat_shell.js +7 -1
  35. package/src/ui/components/context_menu.js +8 -2
  36. package/src/ui/components/filter_builder.js +53 -28
  37. package/src/ui/components/filter_model.js +86 -18
  38. package/src/ui/components/inline_renamer.js +10 -5
  39. package/src/ui/components/modal.js +25 -8
  40. package/src/ui/components/workspace_picker.js +125 -27
  41. package/src/ui/utils/tooltip_service.js +81 -14
  42. package/dist/chunk-RKVYIXDC.js.map +0 -7
  43. package/dist/chunk-U5GKJUOC.js.map +0 -7
  44. /package/dist/{chunk-5W4EEKSK.js.map → chunk-3BLIVOFD.js.map} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,43 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.18.1 (2026-11-06)
4
+
5
+ A patch release: what four applications asked for when they adopted 0.18.0. The
6
+ entries under "Changed behaviour" change what embedders see; the workspace picker's
7
+ keyboard is the one to read first.
8
+
9
+ ### Added
10
+
11
+ - [`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).
12
+ - [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).
13
+ - [`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.
14
+ - [`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.
15
+ - [`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).
16
+ - [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.
17
+ - `attachInlineRenamer`'s `onStart` is handed the input: [`{ value, triggerEvent, input }`](docs/components/input-fields.md#a-list-under-a-name-being-edited-attachinlinerenamer-0181).
18
+
19
+ ### Changed behaviour
20
+
21
+ - **[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.
22
+ - **[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.
23
+ - **[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.
24
+ - **[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.
25
+ - **[`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.
26
+ - **[`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.
27
+ - **[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.
28
+ - **[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.
29
+ - **[`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.
30
+ - **[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.
31
+ - **[`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.
32
+ - [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"`.
33
+ - [`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.
34
+ - `mountChatShell`'s model button has `aria-expanded`, so its open state is announced.
35
+ - `activeConversation()` [answers an id](docs/components/chat-shell.md#conversations), as it always did; the docs read as if it answered a conversation.
36
+
37
+ ### Removed or renamed exports
38
+
39
+ 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`.
40
+
3
41
  ## 0.18.0 (2026-11-05)
4
42
 
5
43
  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(--accent-bright);
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(--accent-bright);
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: italic;
4925
- opacity: 0.55;
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(--text-muted);
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;
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-P74F6I6P.js";
58
+ } from "./chunk-4X4SY3CP.js";
59
59
  import {
60
60
  AutocompleteField,
61
61
  createRafResizeObserver
62
- } from "./chunk-U5GKJUOC.js";
62
+ } from "./chunk-QRBOIUU3.js";
63
63
  import "./chunk-VLOAI3HS.js";
64
64
  import {
65
65
  ColorField,
66
66
  ManagedWindow,
67
- ToggleField
68
- } from "./chunk-ZL3VKINP.js";
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
- field("Stack group", textInput(
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" }),