@cfasim-ui/docs 0.6.3 → 0.7.0

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 (38) hide show
  1. package/charts/BarChart/BarChart.md +7 -8
  2. package/charts/BarChart/BarChart.vue +13 -7
  3. package/charts/ChoroplethMap/ChoroplethMap.md +99 -29
  4. package/charts/ChoroplethMap/ChoroplethMap.vue +456 -127
  5. package/charts/DataTable/DataTable.vue +1 -7
  6. package/charts/LineChart/LineChart.md +7 -8
  7. package/charts/LineChart/LineChart.vue +13 -7
  8. package/charts/_shared/ChartZoomControls.vue +138 -0
  9. package/charts/_shared/chartProps.ts +7 -7
  10. package/charts/_shared/index.ts +4 -15
  11. package/charts/_shared/seriesCsv.ts +2 -2
  12. package/charts/_shared/touch.ts +8 -0
  13. package/charts/_shared/useChartFoundation.ts +0 -1
  14. package/components/MultiSelect/MultiSelect.md +1 -1
  15. package/components/MultiSelect/MultiSelect.vue +24 -83
  16. package/components/NumberInput/NumberInput.md +5 -5
  17. package/components/NumberInput/NumberInput.vue +12 -59
  18. package/components/SelectBox/SelectBox.md +1 -0
  19. package/components/SelectBox/SelectBox.vue +34 -125
  20. package/components/TextInput/TextInput.md +2 -1
  21. package/components/TextInput/TextInput.vue +11 -51
  22. package/components/ToggleGroup/ToggleGroup.md +2 -2
  23. package/components/ToggleGroup/ToggleGroup.vue +21 -26
  24. package/components/_internal/FieldLabel.vue +27 -0
  25. package/components/_internal/field.ts +27 -0
  26. package/components/_internal/input.css +54 -0
  27. package/components/_internal/listbox.css +53 -0
  28. package/components/env.d.ts +4 -0
  29. package/index.json +8 -8
  30. package/package.json +1 -1
  31. package/pyodide/messages.ts +23 -0
  32. package/pyodide/pyodide.worker.ts +2 -21
  33. package/pyodide/pyodideWorkerApi.ts +7 -44
  34. package/shared/index.ts +1 -0
  35. package/shared/workerError.ts +17 -0
  36. package/wasm/messages.ts +6 -0
  37. package/wasm/wasm.worker.ts +1 -7
  38. package/wasm/wasmWorkerApi.ts +8 -26
@@ -9,6 +9,7 @@ import {
9
9
  import ChartMenu from "../ChartMenu/ChartMenu.vue";
10
10
  import type { ChartMenuItem } from "../ChartMenu/ChartMenu.vue";
11
11
  import { downloadCsv } from "../ChartMenu/download.js";
12
+ import { escapeCsvField } from "../_shared/seriesCsv.js";
12
13
 
13
14
  export type TableRecord = Record<string, ArrayLike<number | string | boolean>>;
14
15
  export type TableData = TableRecord | ModelOutput;
@@ -193,13 +194,6 @@ function menuFilename() {
193
194
  return typeof props.menu === "string" ? props.menu : "data";
194
195
  }
195
196
 
196
- function escapeCsvField(val: string): string {
197
- if (val.includes(",") || val.includes('"') || val.includes("\n")) {
198
- return `"${val.replace(/"/g, '""')}"`;
199
- }
200
- return val;
201
- }
202
-
203
197
  function toCsv(): string {
204
198
  if (typeof props.csv === "function") return props.csv();
205
199
  if (typeof props.csv === "string") return props.csv;
@@ -1115,11 +1115,11 @@ until the user clicks Download:
1115
1115
 
1116
1116
  ## Accessibility
1117
1117
 
1118
- The chart's individual marks aren't exposed to assistive tech, so the chart
1119
- announces itself with a single accessible name. When the chart has a `title`,
1120
- its root element gets `role="figure"` and an `aria-label` set to the title, so
1121
- screen readers announce it as a labeled figure while the menu and download
1122
- controls stay reachable.
1118
+ The chart's `<svg>` is exposed as a single labeled image so screen readers
1119
+ announce one accessible name instead of wandering through the individual marks.
1120
+ When the chart has a `title`, the `<svg>` gets `role="img"` and an `aria-label`
1121
+ set to the title. The menu and download controls live outside the `<svg>`, so
1122
+ they stay reachable regardless.
1123
1123
 
1124
1124
  Set `ariaLabel` to give screen readers a fuller summary than the visible title
1125
1125
  (it overrides `title` for the accessible name only):
@@ -1132,9 +1132,8 @@ Set `ariaLabel` to give screen readers a fuller summary than the visible title
1132
1132
  />
1133
1133
  ```
1134
1134
 
1135
- Pass `role` to override the default, e.g. `role="img"` to expose the chart as a
1136
- single image (note this hides the inner menu/download controls from assistive
1137
- tech).
1135
+ Pass `role` to override the default `"img"` (e.g. `role="figure"`, or a role of
1136
+ your own).
1138
1137
 
1139
1138
  ## Props
1140
1139
 
@@ -240,12 +240,14 @@ const props = withDefaults(defineProps<LineChartProps>(), {
240
240
  yScaleType: "linear",
241
241
  });
242
242
 
243
- // Accessible name for the whole chart; falls back to the visible title.
243
+ // Accessible name for the chart; falls back to the visible title.
244
244
  const chartAriaLabel = computed(() => props.ariaLabel ?? props.title);
245
- // Label the chart as a figure when it has a name (keeps inner controls
246
- // reachable, unlike role="img"). An explicit `role` prop always wins.
245
+ // Expose the <svg> as a single labeled image so screen readers announce the
246
+ // name instead of wandering through the individual marks. The menu/download
247
+ // controls live outside the <svg>, so they stay reachable. An explicit `role`
248
+ // prop always wins.
247
249
  const chartRole = computed(
248
- () => props.role ?? (chartAriaLabel.value ? "figure" : undefined),
250
+ () => props.role ?? (chartAriaLabel.value ? "img" : undefined),
249
251
  );
250
252
 
251
253
  // The template root is a <Teleport>, so fallthrough attrs (class, style,
@@ -1056,8 +1058,6 @@ const positionedLegendItems = computed(() => {
1056
1058
  class="line-chart-wrapper"
1057
1059
  :class="{ 'is-fullscreen': isFullscreen }"
1058
1060
  :style="fullscreenStyle"
1059
- :role="chartRole || undefined"
1060
- :aria-label="chartAriaLabel || undefined"
1061
1061
  >
1062
1062
  <ChartMenu
1063
1063
  v-if="menu"
@@ -1068,7 +1068,13 @@ const positionedLegendItems = computed(() => {
1068
1068
  <div class="chart-sr-only" aria-live="polite">
1069
1069
  {{ isFullscreen ? "Chart expanded to fill window" : "" }}
1070
1070
  </div>
1071
- <svg ref="svgRef" :width="width" :height="totalHeight">
1071
+ <svg
1072
+ ref="svgRef"
1073
+ :width="width"
1074
+ :height="totalHeight"
1075
+ :role="chartRole || undefined"
1076
+ :aria-label="chartAriaLabel || undefined"
1077
+ >
1072
1078
  <!-- title -->
1073
1079
  <text
1074
1080
  v-if="title"
@@ -0,0 +1,138 @@
1
+ <script setup lang="ts">
2
+ withDefaults(
3
+ defineProps<{
4
+ canZoomIn?: boolean;
5
+ canZoomOut?: boolean;
6
+ /** Reset is a no-op at the identity transform — disable it there. */
7
+ canReset?: boolean;
8
+ /** Insets the stack further from the corner while the chart fills the
9
+ * window, matching the ChartMenu trigger area. */
10
+ isFullscreen?: boolean;
11
+ }>(),
12
+ { canZoomIn: true, canZoomOut: true, canReset: true, isFullscreen: false },
13
+ );
14
+
15
+ const emit = defineEmits<{
16
+ (e: "zoomIn"): void;
17
+ (e: "zoomOut"): void;
18
+ (e: "reset"): void;
19
+ }>();
20
+ </script>
21
+
22
+ <template>
23
+ <div
24
+ class="chart-zoom-controls"
25
+ :class="{ 'chart-zoom-controls--expanded': isFullscreen }"
26
+ >
27
+ <button
28
+ type="button"
29
+ class="chart-zoom-button"
30
+ aria-label="Zoom in"
31
+ :disabled="!canZoomIn"
32
+ @click="emit('zoomIn')"
33
+ >
34
+ <svg
35
+ width="14"
36
+ height="14"
37
+ viewBox="0 0 14 14"
38
+ fill="none"
39
+ stroke="currentColor"
40
+ stroke-width="1.5"
41
+ stroke-linecap="round"
42
+ aria-hidden="true"
43
+ >
44
+ <path d="M7 2v10M2 7h10" />
45
+ </svg>
46
+ </button>
47
+ <button
48
+ type="button"
49
+ class="chart-zoom-button"
50
+ aria-label="Zoom out"
51
+ :disabled="!canZoomOut"
52
+ @click="emit('zoomOut')"
53
+ >
54
+ <svg
55
+ width="14"
56
+ height="14"
57
+ viewBox="0 0 14 14"
58
+ fill="none"
59
+ stroke="currentColor"
60
+ stroke-width="1.5"
61
+ stroke-linecap="round"
62
+ aria-hidden="true"
63
+ >
64
+ <path d="M2 7h10" />
65
+ </svg>
66
+ </button>
67
+ <button
68
+ type="button"
69
+ class="chart-zoom-button"
70
+ aria-label="Reset view"
71
+ :disabled="!canReset"
72
+ @click="emit('reset')"
73
+ >
74
+ <!-- Counterclockwise "reset" arrow (rotate-ccw). -->
75
+ <svg
76
+ width="14"
77
+ height="14"
78
+ viewBox="0 0 14 14"
79
+ fill="none"
80
+ stroke="currentColor"
81
+ stroke-width="1.5"
82
+ stroke-linecap="round"
83
+ stroke-linejoin="round"
84
+ aria-hidden="true"
85
+ >
86
+ <path
87
+ d="M1.75 7a5.25 5.25 0 1 0 5.25-5.25c-1.48 0-2.9.6-3.94 1.6L1.75 4.7"
88
+ />
89
+ <path d="M1.75 1.75v2.95h2.95" />
90
+ </svg>
91
+ </button>
92
+ </div>
93
+ </template>
94
+
95
+ <style scoped>
96
+ .chart-zoom-controls {
97
+ position: absolute;
98
+ top: 0.5em;
99
+ left: 0.5em;
100
+ z-index: 1;
101
+ display: flex;
102
+ flex-direction: column;
103
+ gap: 4px;
104
+ }
105
+
106
+ /* Mirror .chart-menu-trigger-area--expanded: while the chart fills the
107
+ window the wrapper's padding doesn't move absolute children, so inset
108
+ further for modal-style breathing room. */
109
+ .chart-zoom-controls--expanded {
110
+ top: 1.25em;
111
+ left: 1.25em;
112
+ }
113
+
114
+ /* Same look as .chart-menu-button, but always visible — no hover reveal. */
115
+ .chart-zoom-button {
116
+ display: flex;
117
+ align-items: center;
118
+ justify-content: center;
119
+ width: 28px;
120
+ height: 28px;
121
+ padding: 0;
122
+ border: 1px solid var(--color-border, #e5e7eb);
123
+ border-radius: 0.25em;
124
+ background: var(--color-bg-0, #fff);
125
+ color: var(--color-text-secondary, #555);
126
+ cursor: pointer;
127
+ }
128
+
129
+ .chart-zoom-button:hover:not(:disabled) {
130
+ background: var(--color-bg-1, rgba(0, 0, 0, 0.05));
131
+ color: var(--color-text);
132
+ }
133
+
134
+ .chart-zoom-button:disabled {
135
+ opacity: 0.4;
136
+ cursor: default;
137
+ }
138
+ </style>
@@ -104,16 +104,16 @@ export interface ChartCommonProps {
104
104
  /** Styling for the chart title. See `TitleStyle`. */
105
105
  titleStyle?: TitleStyle;
106
106
  /**
107
- * ARIA role for the chart's root element. Defaults to `"figure"` when an
108
- * accessible name is present (from `ariaLabel` or `title`), so screen
109
- * readers announce the chart as a labeled figure while its controls (menu,
110
- * download) stay reachable. Pass `"img"` to expose it as a single image
111
- * (which hides the inner controls from assistive tech), or your own role.
107
+ * ARIA role for the chart's `<svg>`. Defaults to `"img"` when an accessible
108
+ * name is present (from `ariaLabel` or `title`), so screen readers announce
109
+ * the chart as a single labeled image instead of exposing the individual
110
+ * marks. The menu and download controls sit outside the `<svg>`, so they
111
+ * stay reachable regardless. Pass your own role to override.
112
112
  */
113
113
  role?: string;
114
114
  /**
115
- * Accessible name for the chart, announced by screen readers via the root
116
- * element's `aria-label`. Defaults to the `title` prop. The individual
115
+ * Accessible name for the chart, announced by screen readers via the
116
+ * `<svg>`'s `aria-label`. Defaults to the `title` prop. The individual
117
117
  * marks aren't exposed to assistive tech, so set this to a short summary of
118
118
  * what the chart shows.
119
119
  */
@@ -1,16 +1,9 @@
1
- export {
2
- snap,
3
- niceStep,
4
- intervalValues,
5
- formatTick,
6
- type ChartData,
7
- } from "./axes.js";
1
+ export { snap, formatTick, type ChartData } from "./axes.js";
8
2
  export { computeTickValues, type TickValueOptions } from "./computeTicks.js";
9
3
  export {
10
4
  scaleFraction,
11
5
  clampExtentForScale,
12
6
  computeLogTickValues,
13
- LOG_FLOOR,
14
7
  type ScaleType,
15
8
  } from "./scale.js";
16
9
  export { useChartSize, type ChartSizeOptions } from "./useChartSize.js";
@@ -36,6 +29,8 @@ export {
36
29
  } from "./useChartTooltip.js";
37
30
  export { useChartMenu, type ChartMenuOptions } from "./useChartMenu.js";
38
31
  export { useChartFullscreen } from "./useChartFullscreen.js";
32
+ export { isTouchDevice } from "./touch.js";
33
+ export { default as ChartZoomControls } from "./ChartZoomControls.vue";
39
34
  export { seriesToCsv, categoricalToCsv, type CsvSeries } from "./seriesCsv.js";
40
35
  export { default as ChartAnnotations } from "./ChartAnnotations.vue";
41
36
  export type { ChartAnnotation } from "./annotations.js";
@@ -54,13 +49,7 @@ export type {
54
49
  BlendMode,
55
50
  LineMarkStyle,
56
51
  } from "./chartProps.js";
57
- export {
58
- parseRgb,
59
- relativeLuminance,
60
- resolveColorToRgb,
61
- pickContrastColor,
62
- type Rgb,
63
- } from "./contrast.js";
52
+ export { resolveColorToRgb, pickContrastColor, type Rgb } from "./contrast.js";
64
53
  export {
65
54
  parseDate,
66
55
  isDateLike,
@@ -51,7 +51,7 @@ export function categoricalToCsv(
51
51
  : [categoryHeader, ...series.map((s, i) => s.label || `series_${i}`)];
52
52
  const rows = [headers.join(",")];
53
53
  for (let r = 0; r < categories.length; r++) {
54
- const cells = [escapeCsv(categories[r])];
54
+ const cells = [escapeCsvField(categories[r])];
55
55
  for (const s of series) {
56
56
  cells.push(r < s.data.length ? String(s.data[r]) : "");
57
57
  }
@@ -60,7 +60,7 @@ export function categoricalToCsv(
60
60
  return rows.join("\n");
61
61
  }
62
62
 
63
- function escapeCsv(value: string): string {
63
+ export function escapeCsvField(value: string): string {
64
64
  if (value.includes(",") || value.includes('"') || value.includes("\n")) {
65
65
  return `"${value.replace(/"/g, '""')}"`;
66
66
  }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Whether the current device has a touch screen. A function (not a
3
+ * module-level constant) so unit tests can mock it per-case and so SSR
4
+ * evaluation is deferred until an event actually needs the answer.
5
+ */
6
+ export function isTouchDevice(): boolean {
7
+ return typeof window !== "undefined" && "ontouchstart" in window;
8
+ }
@@ -153,7 +153,6 @@ export function useChartFoundation(opts: ChartFoundationOptions) {
153
153
  fullscreenStyle,
154
154
  teleportTarget,
155
155
  exitFullscreen,
156
- measuredHeight,
157
156
  };
158
157
  }
159
158
 
@@ -128,9 +128,9 @@ MultiSelect is built on reka-ui's Combobox, which implements the
128
128
  | `label` | `string` | No | — |
129
129
  | `hideLabel` | `boolean` | No | — |
130
130
  | `ariaLabel` | `string` | No | — |
131
+ | `hint` | `string` | No | — |
131
132
  | `options` | `MultiSelectOption[]` | Yes | — |
132
133
  | `placeholder` | `string` | No | — |
133
- | `hint` | `string` | No | — |
134
134
 
135
135
 
136
136
  ### MultiSelectOption
@@ -10,11 +10,12 @@ import {
10
10
  ComboboxRoot,
11
11
  ComboboxTrigger,
12
12
  ComboboxViewport,
13
- useId,
14
13
  } from "reka-ui";
15
14
  import { computed, nextTick, ref, watch } from "vue";
16
15
  import Icon from "../Icon/Icon.vue";
17
- import Hint from "../Hint/Hint.vue";
16
+ import FieldLabel from "../_internal/FieldLabel.vue";
17
+ import { useField, type FieldProps } from "../_internal/field";
18
+ import "../_internal/listbox.css";
18
19
 
19
20
  export interface MultiSelectOption {
20
21
  value: string;
@@ -23,16 +24,14 @@ export interface MultiSelectOption {
23
24
 
24
25
  const model = defineModel<string[]>({ default: () => [] });
25
26
 
26
- const props = defineProps<{
27
- label?: string;
28
- hideLabel?: boolean;
29
- ariaLabel?: string;
27
+ interface Props extends FieldProps {
30
28
  options: MultiSelectOption[];
31
29
  placeholder?: string;
32
- hint?: string;
33
- }>();
30
+ }
31
+
32
+ const props = defineProps<Props>();
34
33
 
35
- const id = useId();
34
+ const { labelId, ariaProps } = useField(props);
36
35
  const fieldRef = ref<HTMLElement | null>(null);
37
36
 
38
37
  // In multiple mode the list stays open after a selection, so reka keeps
@@ -70,15 +69,13 @@ function onInputKeydown(event: KeyboardEvent) {
70
69
 
71
70
  <template>
72
71
  <div class="multi-select">
73
- <label
74
- v-if="label"
75
- :id="`${id}-label`"
72
+ <FieldLabel
76
73
  class="multi-select-label"
77
- :class="{ 'visually-hidden': hideLabel }"
78
- >
79
- {{ label }}
80
- <Hint v-if="hint && !hideLabel" :text="hint" />
81
- </label>
74
+ :label="label"
75
+ :label-id="labelId"
76
+ :hide-label="hideLabel"
77
+ :hint="hint"
78
+ />
82
79
  <ComboboxRoot
83
80
  v-model="model"
84
81
  multiple
@@ -107,8 +104,7 @@ function onInputKeydown(event: KeyboardEvent) {
107
104
  <ComboboxInput
108
105
  class="multi-select-input"
109
106
  :placeholder="selectedOptions.length ? undefined : placeholder"
110
- :aria-labelledby="label ? `${id}-label` : undefined"
111
- :aria-label="!label ? ariaLabel : undefined"
107
+ v-bind="ariaProps"
112
108
  @keydown="onInputKeydown"
113
109
  />
114
110
  </div>
@@ -117,38 +113,29 @@ function onInputKeydown(event: KeyboardEvent) {
117
113
  aria-label="Toggle options"
118
114
  >
119
115
  <span class="multi-select-icon" aria-hidden="true">
120
- <svg
121
- width="12"
122
- height="12"
123
- viewBox="0 0 12 12"
124
- fill="none"
125
- stroke="currentColor"
126
- stroke-width="2"
127
- stroke-linecap="round"
128
- stroke-linejoin="round"
129
- >
130
- <path d="M3 4.5L6 7.5L9 4.5" />
131
- </svg>
116
+ <Icon icon="keyboard_arrow_down" :size="16" />
132
117
  </span>
133
118
  </ComboboxTrigger>
134
119
  </ComboboxAnchor>
135
120
  <ComboboxPortal>
136
121
  <ComboboxContent
137
- class="multi-select-content"
122
+ class="cfasim-listbox-content multi-select-content"
138
123
  position="popper"
139
124
  :side-offset="4"
140
125
  :body-lock="false"
141
126
  >
142
- <ComboboxViewport class="multi-select-viewport">
143
- <ComboboxEmpty class="multi-select-empty">No matches</ComboboxEmpty>
127
+ <ComboboxViewport class="cfasim-listbox-viewport">
128
+ <ComboboxEmpty class="cfasim-listbox-empty">
129
+ No matches
130
+ </ComboboxEmpty>
144
131
  <ComboboxItem
145
132
  v-for="opt in options"
146
133
  :key="opt.value"
147
134
  :value="opt.value"
148
- class="multi-select-item"
135
+ class="cfasim-listbox-item"
149
136
  >
150
137
  <span>{{ opt.label }}</span>
151
- <ComboboxItemIndicator class="multi-select-indicator">
138
+ <ComboboxItemIndicator class="cfasim-listbox-indicator">
152
139
  <Icon icon="check" :size="14" />
153
140
  </ComboboxItemIndicator>
154
141
  </ComboboxItem>
@@ -284,55 +271,9 @@ function onInputKeydown(event: KeyboardEvent) {
284
271
  </style>
285
272
 
286
273
  <style>
274
+ /* Sizing only — the dropdown skin lives in _internal/listbox.css. */
287
275
  .multi-select-content {
288
- z-index: 100;
289
- background: var(--color-bg-0);
290
- border: 1px solid var(--color-border);
291
- border-radius: 0.25em;
292
- box-shadow:
293
- 0 4px 6px -1px rgba(0, 0, 0, 0.1),
294
- 0 2px 4px -2px rgba(0, 0, 0, 0.1);
295
276
  width: var(--reka-combobox-trigger-width);
296
277
  max-height: var(--reka-combobox-content-available-height);
297
278
  }
298
-
299
- .multi-select-viewport {
300
- padding: 0.25em;
301
- }
302
-
303
- .multi-select-empty {
304
- padding: 0.5em;
305
- font-size: var(--font-size-sm);
306
- color: var(--color-text-secondary);
307
- text-align: center;
308
- }
309
-
310
- .multi-select-item {
311
- display: flex;
312
- align-items: center;
313
- justify-content: space-between;
314
- gap: 0.5em;
315
- padding: 0.25em 0.5em;
316
- border-radius: 0.25em;
317
- font-size: var(--font-size-sm);
318
- white-space: nowrap;
319
- cursor: pointer;
320
- user-select: none;
321
- outline: none;
322
- }
323
-
324
- .multi-select-item[data-highlighted] {
325
- background: var(--color-primary);
326
- color: white;
327
- }
328
-
329
- .multi-select-item[data-state="checked"] {
330
- font-weight: 600;
331
- }
332
-
333
- .multi-select-indicator {
334
- display: flex;
335
- align-items: center;
336
- flex-shrink: 0;
337
- }
338
279
  </style>
@@ -467,19 +467,19 @@ the input visually.
467
467
  |------|------|----------|---------|
468
468
  | `label` | `string` | No | — |
469
469
  | `hideLabel` | `boolean` | No | — |
470
+ | `ariaLabel` | `string` | No | — |
471
+ | `hint` | `string` | No | — |
470
472
  | `placeholder` | `string` | No | — |
471
473
  | `step` | `number` | No | — |
472
474
  | `min` | `number` | No | — |
473
475
  | `max` | `number` | No | — |
474
- | `hint` | `string` | No ||
475
- | `percent` | `boolean` | No | — |
476
+ | `percent` | `1"`) may not round-trip through the text input use
477
+ // `percent: true` for value scaling and `format` for display shaping.
478
+ format?: NumberFormat` | Yes | — |
476
479
  | `slider` | `boolean` | No | — |
477
480
  | `live` | `boolean` | No | — |
478
481
  | `numberType` | `"integer" \| "float"` | No | — |
479
482
  | `required` | `boolean` | No | — |
480
483
  | `decimals` | `number` | No | — |
481
- | `percent` | `1"`) may not round-trip through the text input — use
482
- // `percent: true` for value scaling and `format` for display shaping.
483
- format?: NumberFormat` | Yes | — |
484
484
  | `sliderDisplay` | `(value: number) =&gt; string` | No | — |
485
485