@nxgt/material 3.39.0 → 3.42.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 (94) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +4 -4
  3. package/dist/{async-slice-BtUexP2l.js → async-slice-Dj0tAKRI.js} +2 -2
  4. package/dist/{button-CCAYSsgh.js → button-CTsT_LqL.js} +1 -1
  5. package/dist/charts.js +1733 -1364
  6. package/dist/{chip-Corr637n.js → chip-DP7EyhtT.js} +1 -1
  7. package/dist/{code-editor-B8_ny_jT.js → code-editor-BWn0AXBf.js} +4 -4
  8. package/dist/code-editor.js +4 -4
  9. package/dist/{code-language-items-1E5Qmfzb.js → code-language-items-CCRuLYpA.js} +2 -2
  10. package/dist/components/components.css +1 -0
  11. package/dist/components/ui/widgets/native-charts/native-charts-reference.css +102 -0
  12. package/dist/{components-CTKlqX8g.js → components-g8mWYJE8.js} +10896 -10827
  13. package/dist/components.js +17 -17
  14. package/dist/{dialog-DGlnqBbX.js → dialog-uQld1gKm.js} +1 -1
  15. package/dist/dnd.js +1 -1
  16. package/dist/{dropdown-menu-B-buMTGc.js → dropdown-menu-BA70o7HC.js} +1 -1
  17. package/dist/editor.js +529 -529
  18. package/dist/{empty-state-DKgPt475.js → empty-state-BzwyRX5J.js} +1 -1
  19. package/dist/hooks.js +2 -2
  20. package/dist/{i18n-provider-Bc7bdIRW.js → i18n-provider-DkeMr5bw.js} +1 -1
  21. package/dist/i18n.js +5 -5
  22. package/dist/{icon-badge-BzLsPW3c.js → icon-badge-D_z5BGbc.js} +1 -1
  23. package/dist/lib/charts/index.d.ts +3 -1
  24. package/dist/lib/components/ui/collections/server-table/bulk-action-targets.d.ts +18 -0
  25. package/dist/lib/components/ui/collections/server-table/bulk-action.d.ts +50 -3
  26. package/dist/lib/components/ui/collections/server-table/partials/server-table-bulk-buttons.d.ts +5 -3
  27. package/dist/lib/components/ui/collections/server-table/partials/server-table-bulk-focus.d.ts +31 -0
  28. package/dist/lib/components/ui/collections/server-table/partials/server-table-bulk-more.d.ts +24 -0
  29. package/dist/lib/components/ui/collections/server-table/partials/server-table-bulk-notice.d.ts +16 -0
  30. package/dist/lib/components/ui/collections/server-table/partials/server-table-bulk-texts.d.ts +9 -0
  31. package/dist/lib/components/ui/collections/server-table/server-table.d.ts +1 -1
  32. package/dist/lib/components/ui/fields/contacts/placeholders.d.ts +1 -1
  33. package/dist/lib/components/ui/selection/autocomplete/autocomplete-form-field.d.ts +5 -2
  34. package/dist/lib/components/ui/selection/autocomplete/autocomplete-text-rules.d.ts +20 -0
  35. package/dist/lib/components/ui/selection/autocomplete/autocomplete.d.ts +1 -1
  36. package/dist/lib/components/ui/selection/autocomplete/partials/autocomplete-chip-input.d.ts +13 -0
  37. package/dist/lib/components/ui/selection/autocomplete/partials/autocomplete-chips.d.ts +7 -1
  38. package/dist/lib/components/ui/selection/autocomplete/partials/autocomplete-text-input.d.ts +15 -0
  39. package/dist/lib/components/ui/selection/autocomplete/types.d.ts +15 -0
  40. package/dist/lib/components/ui/selection/autocomplete/use-autocomplete-handlers.d.ts +10 -3
  41. package/dist/lib/components/ui/selection/autocomplete/use-autocomplete.d.ts +6 -1
  42. package/dist/lib/components/ui/selection/combobox/combobox-input.d.ts +3 -1
  43. package/dist/lib/components/ui/widgets/native-charts/core/point-tones.d.ts +17 -0
  44. package/dist/lib/components/ui/widgets/native-charts/core/reference-text.d.ts +30 -0
  45. package/dist/lib/components/ui/widgets/native-charts/core/reference.d.ts +45 -0
  46. package/dist/lib/components/ui/widgets/native-charts/core/summary.d.ts +3 -0
  47. package/dist/lib/components/ui/widgets/native-charts/core/table.d.ts +7 -0
  48. package/dist/lib/components/ui/widgets/native-charts/core/types.d.ts +25 -0
  49. package/dist/lib/components/ui/widgets/native-charts/hooks/chart-model-types.d.ts +18 -1
  50. package/dist/lib/components/ui/widgets/native-charts/hooks/model-parts.d.ts +3 -0
  51. package/dist/lib/components/ui/widgets/native-charts/hooks/model-series.d.ts +3 -0
  52. package/dist/lib/components/ui/widgets/native-charts/hooks/model-tones.d.ts +19 -0
  53. package/dist/lib/components/ui/widgets/native-charts/hooks/use-chart-model.d.ts +3 -3
  54. package/dist/lib/components/ui/widgets/native-charts/index.d.ts +1 -1
  55. package/dist/lib/components/ui/widgets/native-charts/line-chart/line-chart-types.d.ts +26 -1
  56. package/dist/lib/components/ui/widgets/native-charts/line-chart/line-geometry.d.ts +4 -0
  57. package/dist/lib/components/ui/widgets/native-charts/line-chart/partials/reference-marks.d.ts +17 -0
  58. package/dist/lib/components/ui/widgets/native-charts/line-chart/partials/tone-points.d.ts +11 -0
  59. package/dist/lib/components/ui/widgets/native-charts/partials/chart-tooltip.d.ts +3 -0
  60. package/dist/lib/i18n/resources/en/en.d.ts +22 -0
  61. package/dist/lib/i18n/resources/en/material.json.d.ts +22 -0
  62. package/dist/lib/i18n/resources/fr/fr.d.ts +22 -0
  63. package/dist/lib/i18n/resources/fr/material.json.d.ts +22 -0
  64. package/dist/lib/i18n/resources/resources.d.ts +44 -0
  65. package/dist/lib/i18n/translator.d.ts +1 -1
  66. package/dist/lib.js +1 -1
  67. package/dist/main.js +23 -23
  68. package/dist/map.js +4 -4
  69. package/dist/material.css +1 -1
  70. package/dist/pdf-reader.js +1 -1
  71. package/dist/print.js +2 -2
  72. package/dist/{public-CNlIuQOj.js → public-ib0sK0Hr.js} +6 -6
  73. package/dist/{rich-code-block-Jo0OLy0J.js → rich-code-block-D_PTTZWQ.js} +4 -4
  74. package/dist/{sortable-provider-DgIo378H.js → sortable-provider-CrS51nKB.js} +1 -1
  75. package/dist/{spinner-BTR8rVN9.js → spinner-DLd_IHev.js} +1 -1
  76. package/dist/{stored-language-Bbwcy-Pv.js → stored-language-B5SMR-W2.js} +44 -0
  77. package/dist/theme-picker.js +2 -2
  78. package/dist/{theme-toggle-DMZ_3HiG.js → theme-toggle-wJYmnwgu.js} +1 -1
  79. package/dist/{toolbar-items-D3SPTgXE.js → toolbar-items-BLjrk8uU.js} +2 -2
  80. package/dist/{translate-material-CRF13PON.js → translate-material-BYRDtSEk.js} +2 -2
  81. package/dist/{translator-D7AVZCfQ.js → translator-C-_M57ZA.js} +1 -1
  82. package/dist/{use-chat-composer-D3kvp8Fw.js → use-chat-composer-D_hfO99B.js} +2647 -2474
  83. package/dist/{use-heading-spy-uyhOQV2O.js → use-heading-spy-wElRpO2G.js} +1 -1
  84. package/dist/{use-material-translation-kISeB-fY.js → use-material-translation-CFF73jk5.js} +2 -2
  85. package/docs/README.md +1 -1
  86. package/docs/components/data-table-filter-and-lists.md +59 -4
  87. package/docs/components/rich-widgets.md +6 -0
  88. package/docs/components/selection-and-choice.md +47 -1
  89. package/docs/guides/charts.md +78 -2
  90. package/docs/guides/i18n.md +1 -1
  91. package/docs/roadmap.md +7 -0
  92. package/lib/components/components.css +1 -0
  93. package/lib/components/ui/widgets/native-charts/native-charts-reference.css +102 -0
  94. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { r as e } from "./i18n-provider-Bc7bdIRW.js";
1
+ import { r as e } from "./i18n-provider-DkeMr5bw.js";
2
2
  import { _ as t, g as n, h as r, n as i, v as a, y as o } from "./number.utils-BG_eEt5g.js";
3
3
  import { useCallback as s, useEffect as c, useMemo as l, useRef as u } from "react";
4
4
  //#region ../../node_modules/date-fns/locale/fr/_lib/formatDistance.js
@@ -1,5 +1,5 @@
1
- import { r as e } from "./i18n-provider-Bc7bdIRW.js";
2
- import { t } from "./translate-material-CRF13PON.js";
1
+ import { r as e } from "./i18n-provider-DkeMr5bw.js";
2
+ import { t } from "./translate-material-BYRDtSEk.js";
3
3
  import { useCallback as n } from "react";
4
4
  //#region lib/i18n/use-material-translation.ts
5
5
  function r() {
package/docs/README.md CHANGED
@@ -9,7 +9,7 @@ detail: every option, the second example, and the traps.
9
9
  | [Components](components/README.md) | You are looking for a component — one preview per category, then one page per category |
10
10
  | [Forms](guides/forms.md) | You are building a form: the field / `*FormField` pair, React Hook Form + Zod, what each field emits, server errors, multi-step forms |
11
11
  | [Themes for your app](guides/themes.md) | You define your own themes: `defineTheme` modules, the Vite plugin's `themes` option, the `nxgt-theme` command and its CI check, the tone warnings |
12
- | [Charts](guides/charts.md) | You draw charts with `@nxgt/material/charts`: each chart, the shared props, zoom and phone scroll, the keyboard, accessibility, theming the `--chart-1..5` tokens, and moving off the Nivo charts, removed in 3.0 |
12
+ | [Charts](guides/charts.md) | You draw charts with `@nxgt/material/charts`: each chart, the shared props, reference bands, thresholds and point tones, zoom and phone scroll, the keyboard, accessibility, theming the `--chart-1..5` tokens, and moving off the Nivo charts, removed in 3.0 |
13
13
  | [Cards and tiles](guides/cards-and-tiles.md) | You want a `DataTable` as a list of cards or tiles: `layout`, `renderCard`, `renderTile`, the card's one link, selection, the sort control, columns by the list's width |
14
14
  | [DataGrid](guides/data-grid.md) | You are building an editable grid: columns and editors, save modes and conflicts, resizing, hiding and reordering columns, paste, fill and series, undo, totals, long lists, phones, custom editors, CSP |
15
15
  | [Code editor](guides/code-editor.md) | You want `CodeEditor` or `CodeDiff` in a theme preset or your own (`defineCodeTheme`), several themes on one page, Monaco bundled instead of the CDN, or the new `@nxgt/material/code-editor` import (the reader look, the view props, the `ref` handle and the file tabs are in the component's own page) |
@@ -861,12 +861,12 @@ export default function Students() {
861
861
  ```
862
862
  - **Bulk actions that count and run only their rows.** `bulkActions` also
863
863
  takes a list of `ServerTableBulkAction<T>` (`{ id, label, icon?, appliesTo?,
864
- onClick, variant?, color?, loading? }`). Each button reads "Label (n)", n
864
+ name?, max?, maxNotice?, onClick, variant?, color?, loading? }`). Each button reads "Label (n)", n
865
865
  being the selected rows the action applies to, and is named "Label n rows"
866
866
  (`material.server-table.bulk-action` and `bulk-action-name`, English and
867
867
  French); `variant` (default `'tonal'`) and `color` (`'destructive'` for a
868
868
  destructive one) style the button, `loading` makes it busy; the type is
869
- exported as `ServerTableBulkAction`. It is not rendered when n is 0, so 5 rows selected with none
869
+ exported as `ServerTableBulkAction`, with `ServerTableBulkContext`. It is not rendered when n is 0, so 5 rows selected with none
870
870
  archived show no "Restore". `onClick(ids, selection, rows)` receives only
871
871
  those ids and rows. An action without `appliesTo` applies to the whole
872
872
  selection: it shows the selection's size and gets every id. **Across
@@ -880,6 +880,61 @@ export default function Students() {
880
880
  (an action without it still gets every selected id). A row's snapshot is
881
881
  dropped when it is deselected, when the selection is cleared and when the
882
882
  query changes.
883
+
884
+ **Name, maximum and focus (`name`, `max`, `maxNotice`, `onClick`'s fourth
885
+ argument).** All three are optional and additive:
886
+ - `name: (count) => string` is the button's accessible name and tooltip,
887
+ e.g. `(n) => `Archive ${n} people``, instead of the generic "Archive 2
888
+ rows". Start it with the visible `label`: the button reads "Archive (2)",
889
+ and a name that begins with it lets speech input ("click Archive") and
890
+ screen readers agree (WCAG 2.5.3, Label in Name). It applies to the
891
+ toolbar, the phone's bar and the More menu's items alike.
892
+ - `max: 100` is the most rows one call takes, counted on the rows the action
893
+ applies to. Above it the button is replaced by `maxNotice`: a short muted
894
+ text, by default "Select 100 rows or fewer to archive"
895
+ (`material.server-table.bulk-max-notice`, English and French), or your own
896
+ node or `(count, max) => node`. The notice is told politely once, when the
897
+ action goes past `max`; moving from 101 to 102 rows says nothing more.
898
+ Under `max` again, the button returns. On a phone an action in the bar
899
+ shows the notice inline; one in the More menu is a disabled item whose
900
+ notice is its description (`aria-describedby`), so it reads under its label.
901
+ - `onClick(ids, selection, rows, { trigger, focusTrigger })`: `trigger` is the
902
+ button the action started from (the "More" button for a More-menu item),
903
+ and `focusTrigger()` gives it the focus back, found again at call time, so
904
+ it works after a re-render. When the action is gone by then (its count fell
905
+ to 0, or past `max`) the focus goes to the next bulk action, else "More",
906
+ else the table's "Select all" (the table itself without one), never the
907
+ body. A handler that takes three parameters stays valid.
908
+ ```tsx
909
+ const [confirm, setConfirm] = useState<{ ids: string[]; focus: () => void } | null>(null);
910
+ <ServerTable<Person>
911
+ {...props}
912
+ bulkActions={[
913
+ {
914
+ id: 'archive',
915
+ label: 'Archive',
916
+ appliesTo: (person) => !person.archived,
917
+ name: (n) => `Archive ${n} ${n === 1 ? 'person' : 'people'}`,
918
+ max: 100,
919
+ onClick: (ids, _selection, _rows, { focusTrigger }) =>
920
+ setConfirm({ ids, focus: focusTrigger }),
921
+ },
922
+ ]}
923
+ />
924
+ <Dialog open={confirm != null} onOpenChange={(open) => !open && setConfirm(null)}>
925
+ <DialogContent
926
+ // Without this, the focus goes back to what Radix remembers, which is
927
+ // gone when the action was in the More menu or has disappeared.
928
+ onCloseAutoFocus={(event) => {
929
+ event.preventDefault();
930
+ confirm?.focus();
931
+ }}
932
+ >
933
+ {/* … */}
934
+ </DialogContent>
935
+ </Dialog>
936
+ ```
937
+
883
938
  ```tsx
884
939
  <ServerTable<Item>
885
940
  {...props}
@@ -932,7 +987,7 @@ Its props are `ServerTableProps<T>`; `filter` is a `ServerTableFilter`,
932
987
  | `error` | `ServerTableError \| null` | — | the fetch failed: the failure and a Retry replace the rows |
933
988
  | `onRetry` | `() => unknown` | — | the error's Retry; a returned promise keeps it loading |
934
989
  | `onClearFilters` | `() => void` | focus the table | without `filter`, after "clear" |
935
- | `bulkActions` | `(ids, selection: ServerTableSelection) => ReactNode \| ServerTableBulkAction<T>[]` | — | the selection's toolbar: your own buttons, or a list of actions with `appliesTo` that each count and run only the rows they apply to |
990
+ | `bulkActions` | `(ids, selection: ServerTableSelection) => ReactNode \| ServerTableBulkAction<T>[]` | — | the selection's toolbar: your own buttons, or a list of actions (`appliesTo`, `name`, `max`, `maxNotice`; `onClick` also gets a `ServerTableBulkContext`) that each count and run only the rows they apply to |
936
991
  | `enableRowSelection`, `rowSelectionDisabledReason` | `boolean \| (row) => boolean`, `(row) => string \| undefined` | all rows, no reason | with `bulkActions`: which rows can be selected, and why one cannot ([DataTable](#datatable)) |
937
992
  | `pageHref` | `(params) => string` | `` `?${params}` `` | Previous / Next hrefs |
938
993
  | `pagingNames` | `{ after?, trail? }` | `after`, `trail` | the cursor's params |
@@ -965,7 +1020,7 @@ rows (the bulk bar is not drawn under it). With the declarative list form of
965
1020
  line rather than scroll, and past the third action the rest go into a "More"
966
1021
  menu (`material.server-table.bulk-more`), each item keeping its count. From
967
1022
  768px the buttons read "Label (n)" as before. When the focused button
968
- disappears the focus goes to the next one, else to "Select all". The function
1023
+ disappears the focus goes to the next one, else to "More", else to "Select all"; an action past `max` shows its notice there (an inline text, or a disabled More item). The function
969
1024
  form (`bulkActions={(ids) => node}`) is drawn whole in the row, so keep it to
970
1025
  one or two compact buttons; the footer's polite status still tells the selection once. The cards'
971
1026
  own list bar row is gone in this layout; the names, the sort menu and the
@@ -278,6 +278,12 @@ import { LineChart } from '@nxgt/material/charts';
278
278
  />;
279
279
  ```
280
280
 
281
+ A line chart can be read against a reference: `bands` shade a range of the
282
+ value axis (a normal range), `thresholds` draw labelled lines and `pointTone`
283
+ rings the values that need a word, which the tooltip, the summary and the
284
+ "View data" table repeat
285
+ ([guide](../guides/charts.md#reading-values-against-a-reference)).
286
+
281
287
  Parts: `LineChart`, `AreaChart`, `BarChart`, `PieChart`, `DonutChart`, `Gauge`,
282
288
  `RadialBarChart`, `FunnelChart`, `HeatmapChart`, `RadarChart`, `ScatterChart`,
283
289
  `ComboChart`.
@@ -373,7 +373,7 @@ export function Framework() {
373
373
 
374
374
  ![Autocomplete](../previews/selection-choice-autocomplete.webp)
375
375
 
376
- Single or multiple (chips) autocomplete with free-solo values, accent-insensitive matching and a debounced server search.
376
+ Single or multiple (chips) autocomplete with free-solo values, accent-insensitive matching and a debounced server search; a single value can be the input's own text (`display="text"`).
377
377
 
378
378
  ```tsx
379
379
  import { Autocomplete } from '@nxgt/material/components';
@@ -409,6 +409,8 @@ disabled with the field; Backspace in the empty input removes the last one.
409
409
  | Prop | Type | Default | Effect |
410
410
  | --- | --- | --- | --- |
411
411
  | `mode` | `'single' \| 'multiple'` | — | required; decides the `value` type |
412
+ | `display` | `'chip' \| 'text'` | `'chip'` | `mode="single"` only: how the value is drawn, a removable chip beside the input (and its placeholder), or the input's own text (type `AutocompleteDisplay`; see [One value as text](#one-value-as-text)). Ignored in `multiple` |
413
+ | `clearable` | `boolean` | `true` | shows the "Clear" button; with `display="text"`, whether emptying the text and leaving the field clears the value |
412
414
  | `value` | `string` / `string[]` | — | required, per `mode` |
413
415
  | `onValueChange` | `(value?: string \| null) => void` / `(value?: string[] \| null) => void` | — | per `mode` |
414
416
  | `options` | `T[]` (`{ value, label, disabled? }` by default) | `[]` | the items (`disabled` on one: listed, announced as disabled, never picked); with `onSearch`, the list before a search and the selected options' labels |
@@ -435,6 +437,50 @@ An avatar in the rows and the chips:
435
437
  `getOptionLeading={(person) => <Avatar src={person.photo} alt="" />}`; see
436
438
  [the field look](../guides/field-look-2-x.md#autocomplete-and-the-chips-box).
437
439
 
440
+ ### One value as text
441
+
442
+ By default a single value is drawn as a chip, like a multi-select with one
443
+ entry. For picking one entry out of a long directory (a person, a place, a
444
+ resource), `display="text"` makes the chosen option's label the input's own
445
+ text, as a `Select` shows it, and keeps the search:
446
+
447
+ ```tsx
448
+ <Autocomplete
449
+ mode="single"
450
+ display="text"
451
+ label="Member"
452
+ placeholder="Search a member"
453
+ options={selected ? [selected] : []}
454
+ onSearch={searchMembers}
455
+ value={value}
456
+ onValueChange={(next) => setValue(next ?? '')}
457
+ />
458
+ ```
459
+
460
+ - The label fills the input (on the server too); the placeholder shows only
461
+ while the field is empty. The text is selected on focus, so typing replaces
462
+ it and searches again (`onSearch` or the local `filter`); the list opened on
463
+ the label is the whole `options`, not filtered by it.
464
+ - Typing changes nothing until an option is picked. **Escape**, or leaving
465
+ the field (Tab, a click elsewhere), drops the typed text and brings the
466
+ label back; the value never moved. Escape on a closed list does the same
467
+ instead of clearing.
468
+ - The "Clear" button empties the text and the value. Emptying the text by
469
+ hand and leaving the field clears the value too, since the field is
470
+ `clearable`. With `clearable={false}` there is no button, and an emptied text
471
+ gets the label back on leave: the value can only be replaced by another pick.
472
+ - The option's `getOptionLeading` (or `renderSelectedLeading`) avatar shows
473
+ before the text while it is picked.
474
+ - `freeSolo` still adds through its "Add" button; text left in the input and
475
+ not added is dropped on leave, as any other typed text.
476
+ - Keep the selected option in `options` (as with `onSearch` and chips): a value
477
+ with no matching option has no label, so the input stays empty.
478
+ - The input is the combobox: `aria-expanded` follows the list,
479
+ `aria-activedescendant` names the highlighted option, the picked option is
480
+ `aria-selected`, and the field's live region announces the suggestions as
481
+ before. `AutocompleteFormField` takes `display` and `clearable` too. Leaving the
482
+ window (Alt-Tab) is not leaving the field: the typed text waits.
483
+
438
484
  ### Searching a server
439
485
 
440
486
  `onSearch` takes the query and an `AbortSignal`; hand the signal to `fetch`
@@ -63,7 +63,7 @@ export function SalesChart() {
63
63
  plot of the right height; the plot is drawn once the browser has measured its
64
64
  width, with its final geometry (the entry motion only runs after that).
65
65
  Nothing mismatches on hydration.
66
- - **Types:** `ChartRow`, `RowKey`, `ChartValue`, `ChartSeries`, `ChartAxis`, `ChartXAxis`,
66
+ - **Types:** `ChartRow`, `RowKey`, `ChartValue`, `ChartSeries`, `ChartAxis`, `ChartXAxis`, `ChartBand`, `ChartThreshold`,
67
67
  `ChartPoint`, `ChartBaseProps`, `ChartColor`, `ChartShape`, `ChartSlice`,
68
68
  `ScatterSeries`, `ScatterPoint`, `ComboSeries`, `ChartRange`,
69
69
  `ChartZoomOptions`, `ChartScrollOptions`, `ChartRangeProps`, `HeatColumn`,
@@ -203,6 +203,80 @@ The funnel is one series, so it takes no `legend`, `hidden` or `grid`; the round
203
203
  charts take no `grid`, and the heat map no `hidden`, `grid` or `patterns` (these
204
204
  are left out of their props types).
205
205
 
206
+ ## Reading values against a reference
207
+
208
+ `LineChart` (and `AreaChart`, which takes the same props) can draw the range a
209
+ value is read against, so it is not painted outside the chart where the
210
+ tooltip, the summary and the table would miss it.
211
+
212
+ ```tsx
213
+ 'use client';
214
+ import { LineChart } from '@nxgt/material/charts';
215
+ import type { ChartBand, ChartPoint, ChartThreshold } from '@nxgt/material/charts';
216
+
217
+ // Module-level (or memoized): the arrays and the function key the summary and the table.
218
+ const bands: ChartBand[] = [
219
+ { from: 40, to: 60, label: 'Normal range', tone: 'success' },
220
+ ];
221
+ const thresholds: ChartThreshold[] = [
222
+ { value: 70, label: 'Upper limit', tone: 'error' },
223
+ { value: 30, label: 'Lower limit', tone: 'info', dash: false },
224
+ ];
225
+ const toneOf = (point: ChartPoint) =>
226
+ point.value > 70 ? 'error' : point.value > 60 ? 'warning' : point.value < 40 ? 'info' : undefined;
227
+
228
+ <LineChart
229
+ title="Daily readings"
230
+ data={readings}
231
+ x="day"
232
+ series={[{ key: 'reading', label: 'Reading' }]}
233
+ points
234
+ bands={bands}
235
+ thresholds={thresholds}
236
+ pointTone={toneOf}
237
+ toneLabels={{ error: 'Critical', warning: 'High', info: 'Low' }}
238
+ />;
239
+ ```
240
+
241
+ | Prop | Effect |
242
+ | --- | --- |
243
+ | `bands` | `{ from, to, label, tone? }[]`: a wash across the plot behind the lines, with its `label` inside it at the left. `from` and `to` may come in either order. |
244
+ | `thresholds` | `{ value, label, tone?, dash? }[]`: a horizontal line behind the series with its `label` above it at the right end; dashed unless `dash: false`. |
245
+ | `pointTone` | `(point: ChartPoint, series: ChartSeries) => Tone \| undefined`: the tone of one value. It is given the point `onPointClick` gets, so `point.index` is the row of the full `data` even when the chart is zoomed. |
246
+ | `toneLabels` | `{ [tone]: word }`: the word of each tone, in your language and domain. Unset, the library's neutral word ("Error", translated by `material.charts.tone.*`); an app tone you declared falls back to its own name. |
247
+
248
+ **The value axis holds them.** Unless `yAxis.min` and `yAxis.max` pin it, the
249
+ axis grows to include every band end and threshold, so none is out of view.
250
+ With a pinned axis a band is clipped to the plot and a threshold outside it is
251
+ not drawn (it stays in the summary and the table). The scales are the chart's
252
+ own, so a `zoom` window, a phone `scroll` and a resize keep them right.
253
+
254
+ **The words are carried everywhere the picture is.**
255
+
256
+ - The **tooltip** and the **live status** add the tone's word to a toned
257
+ value: `Day 4: Reading 63 (High)`.
258
+ - The **summary** lists the ranges and lines (`Reference ranges: Normal range:
259
+ 40 to 60. Thresholds: Upper limit: 70`), says how many values of each series
260
+ lie outside each band (`Reading: 5 of 12 points outside Normal range.`) and
261
+ counts the tones (`Reading: 2 High, 1 Critical, 2 Low, of 12 points.`). Over
262
+ a `zoom` window it counts the window's rows.
263
+ - **View data** gets a column after each toned series (`Reading tone`) and a
264
+ list of the ranges and lines under the table.
265
+
266
+ **Never colour alone.** A toned point is drawn as a ring in the tone's colour
267
+ round the value (the series' own shape inside the ring with `patterns`), so it
268
+ differs from an untoned dot by its form. A band is a decorative tint: the
269
+ label, the summary and the table carry its meaning. Labels use the tone's text
270
+ colour (4.5:1) on a card-coloured halo, lines and rings hold 3:1, and in
271
+ forced colours the band is outlined and the lines, labels and rings use system
272
+ colours. Nothing animates: reduced motion changes nothing here. Labels are
273
+ cut to 45% of the plot's width, and a label in a right-to-left script is set
274
+ right to left (the plot itself is never mirrored, see
275
+ [Right-to-left pages](#right-to-left-pages)).
276
+
277
+ A custom `tooltip` function receives the points as before; read the tone from
278
+ your own `pointTone` if it should show the word.
279
+
206
280
  ## Shared props
207
281
 
208
282
  Every chart takes the shared props (`ChartBaseProps`):
@@ -398,7 +472,9 @@ keyboard behaviour.
398
472
  contrast alone, so the heat map is exempt from the 3:1 non-text rule for that
399
473
  step, and **View data** (the whole matrix as a table) is the accessible
400
474
  fallback; the number in a cell keeps 4.5:1 against its own fill.
401
- - **Never colour alone:** `patterns` adds shapes, dashes and fill patterns.
475
+ - **Never colour alone:** `patterns` adds shapes, dashes and fill patterns; a
476
+ toned point is a ring and its word is in the tooltip, the summary and the
477
+ table ([Reading values against a reference](#reading-values-against-a-reference)).
402
478
  - **Reduced motion:** with `prefers-reduced-motion`, the final geometry shows at
403
479
  once with a short 120ms fade; there is no bounce, draw-in or morph, and the
404
480
  crosshair jumps.
@@ -404,7 +404,7 @@ fichier.") when it cannot show it, `material.file-preview.download`
404
404
  "Seuls les premiers {size} de ce fichier sont affichés.") under a text cut
405
405
  by the size cap; the text or Markdown it shows scrolls in a region named by
406
406
  `material.file-preview.content` ("Contents of {name}", "Contenu de {name}").
407
- A `ServerTable` bulk action with a list entry reads `material.server-table.bulk-action` ("{label} ({count})", the same in French) and is named by `material.server-table.bulk-action-name` ("{label} {count, plural, one {# row} other {# rows}}"; "{label} : {count, plural, one {# ligne} other {# lignes}}").
407
+ A `ServerTable` bulk action with a list entry reads `material.server-table.bulk-action` ("{label} ({count})", the same in French) and is named by `material.server-table.bulk-action-name` ("{label} {count, plural, one {# row} other {# rows}}"; "{label} : {count, plural, one {# ligne} other {# lignes}}"). Past the action's `max` the notice that replaces its button reads `material.server-table.bulk-max-notice` ("Select {max, plural, one {# row} other {# rows}} or fewer to {label}"; "Sélectionnez {max, plural, one {# ligne} other {# lignes}} ou moins pour {label}"), unless the action gives its `maxNotice`; an action's `name` replaces `bulk-action-name`.
408
408
  `ServerTable`'s own live region (without `filter`) says
409
409
  `material.server-table.results` once a new query is answered ("{count,
410
410
  plural, =0 {No results} one {# result} other {# results}}"; "{count, plural,
package/docs/roadmap.md CHANGED
@@ -482,6 +482,13 @@ version they shipped in, and planned entries may name the major they target.
482
482
  slider handles) and a phone scroll that opens on the latest point. The
483
483
  light `--chart-4` is now a purple. Client-only, like `/editor`.
484
484
  ([Charts](guides/charts.md))
485
+ - **Reference bands, thresholds and point tones on the line chart** — `bands`
486
+ shade a range of the value axis, `thresholds` draw labelled lines, and
487
+ `pointTone` rings the values that need a word (`toneLabels` gives the words).
488
+ The words reach the tooltip, the live status, the summary (values outside a
489
+ band, tone counts) and a tone column of "View data"; the axis grows to hold
490
+ them and a zoom window keeps them right.
491
+ ([Reading values against a reference](guides/charts.md#reading-values-against-a-reference))
485
492
 
486
493
  - **The page's title in the app bar, a text breadcrumb root and the tenant in the menu** —
487
494
  a route's `handle.title` (a node, or a function of the match like
@@ -150,6 +150,7 @@
150
150
  @import "./ui/widgets/native-charts/native-charts-round.css";
151
151
  @import "./ui/widgets/native-charts/native-charts-heat.css";
152
152
  @import "./ui/widgets/native-charts/native-charts-points.css";
153
+ @import "./ui/widgets/native-charts/native-charts-reference.css";
153
154
  @import "./ui/widgets/native-charts/native-charts-zoom.css";
154
155
  /* The editor's documents: `.nxgt-prose`, shared with the viewer. */
155
156
  @import "./editor-prose.css";
@@ -0,0 +1,102 @@
1
+ /* A line chart's reference bands, threshold lines and toned points. Reached
2
+ * from components.css.
3
+ *
4
+ * A band is a wash across the plot, behind the lines: decorative, since its
5
+ * label (read aloud by the summary and listed under "View data") carries the
6
+ * meaning. A tone paints it with the tone itself at 12%; without one it is
7
+ * the foreground at 6%. Labels and threshold lines use the tone's text
8
+ * colour (4.5:1 on the card, both themes) or the foreground, and sit on a
9
+ * card-coloured halo so a line passing behind them does not cut the letters.
10
+ * A toned point is a ring round the value: a shape, never colour alone. */
11
+ .nxgt-chart-ref-band {
12
+ fill: var(--foreground);
13
+ opacity: 0.06;
14
+ }
15
+ .nxgt-chart-ref-band[data-tone] {
16
+ fill: var(--nxgt-tone);
17
+ opacity: 0.12;
18
+ }
19
+ .nxgt-chart-ref-line {
20
+ stroke: var(--nxgt-chart-axis);
21
+ stroke-width: 1.5;
22
+ stroke-dasharray: 6 4;
23
+ }
24
+ .nxgt-chart-ref-line[data-dash="false"] {
25
+ stroke-dasharray: none;
26
+ }
27
+ .nxgt-chart-ref-line[data-tone] {
28
+ stroke: var(--nxgt-tone-text);
29
+ }
30
+ .nxgt-chart-ref-label {
31
+ fill: var(--foreground);
32
+ font-size: 11px;
33
+ font-weight: 600;
34
+ paint-order: stroke;
35
+ stroke: var(--card);
36
+ stroke-width: 3px;
37
+ stroke-linejoin: round;
38
+ pointer-events: none;
39
+ }
40
+ .nxgt-chart-ref-label[data-tone] {
41
+ fill: var(--nxgt-tone-text);
42
+ }
43
+ .nxgt-chart[data-phone] .nxgt-chart-ref-label {
44
+ font-size: 10px;
45
+ }
46
+
47
+ .nxgt-chart-tone-ring {
48
+ fill: var(--card);
49
+ stroke: var(--nxgt-tone-text);
50
+ stroke-width: 2;
51
+ }
52
+ .nxgt-chart-tone-dot {
53
+ fill: var(--nxgt-tone-text);
54
+ }
55
+
56
+ /* The tone's word in the tooltip, with the ring the plot draws. */
57
+ .nxgt-chart-tip-tone {
58
+ display: inline-flex;
59
+ align-items: center;
60
+ gap: 4px;
61
+ color: var(--nxgt-tone-text);
62
+ font-size: 12px;
63
+ font-weight: 600;
64
+ }
65
+
66
+ /* Under "View data": the ranges and lines read as a list. */
67
+ .nxgt-chart-table-notes {
68
+ margin: 8px 0 0;
69
+ padding: 0;
70
+ list-style: none;
71
+ color: var(--muted-foreground);
72
+ font-size: 12px;
73
+ }
74
+
75
+ @media (forced-colors: active) {
76
+ .nxgt-chart-ref-band {
77
+ fill: Canvas;
78
+ stroke: GrayText;
79
+ stroke-width: 1;
80
+ stroke-dasharray: 2 3;
81
+ opacity: 1;
82
+ }
83
+ .nxgt-chart-ref-line,
84
+ .nxgt-chart-ref-line[data-tone] {
85
+ stroke: CanvasText;
86
+ }
87
+ .nxgt-chart-ref-label,
88
+ .nxgt-chart-ref-label[data-tone] {
89
+ fill: CanvasText;
90
+ stroke: Canvas;
91
+ }
92
+ .nxgt-chart-tone-ring {
93
+ fill: Canvas;
94
+ stroke: Highlight;
95
+ }
96
+ .nxgt-chart-tone-dot {
97
+ fill: Highlight;
98
+ }
99
+ .nxgt-chart-tip-tone {
100
+ color: CanvasText;
101
+ }
102
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxgt/material",
3
- "version": "3.39.0",
3
+ "version": "3.42.0",
4
4
  "license": "MIT",
5
5
  "bugs": {
6
6
  "email": "stevegires@gmail.com"