@nxgt/material 3.5.0 → 3.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 (83) hide show
  1. package/CHANGELOG.md +92 -0
  2. package/README.md +46 -1
  3. package/dist/components/components.css +2 -0
  4. package/dist/components/theme-tokens.css +84 -72
  5. package/dist/components/tones/tokens.css +5 -5
  6. package/dist/components/ui/display/chip/chip-hairline.css +1 -1
  7. package/dist/components/ui/layout/sidebar/sidebar-tokens.css +1 -1
  8. package/dist/components/ui/overlays/dialog/dialog.css +2 -2
  9. package/dist/components/ui/primitives/field-box/field-box-tokens.css +1 -1
  10. package/dist/components/ui/primitives/toggle-group/toggle-group-segmented.css +2 -2
  11. package/dist/components/ui/primitives/toggle-group/toggle-group-separate.css +1 -1
  12. package/dist/components/ui/widgets/drawing/drawing-animate.css +123 -0
  13. package/dist/components/ui/widgets/drawing/drawing.css +459 -0
  14. package/dist/{components-CXpL1zEs.js → components-BX4icy-g.js} +734 -734
  15. package/dist/components.js +16 -16
  16. package/dist/{dialog-JQT6iETD.js → dialog-DdJ1C4BR.js} +168 -168
  17. package/dist/dnd.js +1 -1
  18. package/dist/draw-animate.js +106 -0
  19. package/dist/draw-png.js +2 -0
  20. package/dist/draw-sketch.js +38 -0
  21. package/dist/draw.js +65 -0
  22. package/dist/drawing-B8VuG7nx.js +390 -0
  23. package/dist/drawing-context-CeKeZE6b.js +10 -0
  24. package/dist/editor.js +110 -110
  25. package/dist/{icon-badge-C30yKsAM.js → icon-badge-Bb5-73XP.js} +18 -18
  26. package/dist/{icon-button-BXlI3DJ_.js → icon-button-5A6Mrj6w.js} +60 -90
  27. package/dist/lib/components/ui/display/shape/use-shape-morph.d.ts +1 -0
  28. package/dist/lib/components/ui/widgets/drawing/drawing-a11y.d.ts +20 -0
  29. package/dist/lib/components/ui/widgets/drawing/drawing-context.d.ts +23 -0
  30. package/dist/lib/components/ui/widgets/drawing/drawing-types.d.ts +63 -0
  31. package/dist/lib/components/ui/widgets/drawing/drawing.d.ts +17 -0
  32. package/dist/lib/components/ui/widgets/drawing/index.d.ts +5 -0
  33. package/dist/lib/components/ui/widgets/drawing/partials/drawing-root.d.ts +17 -0
  34. package/dist/lib/components/ui/widgets/drawing/partials/interactive-node.d.ts +22 -0
  35. package/dist/lib/components/ui/widgets/drawing/partials/primitives/content.d.ts +20 -0
  36. package/dist/lib/components/ui/widgets/drawing/partials/primitives/path.d.ts +29 -0
  37. package/dist/lib/components/ui/widgets/drawing/partials/primitives/primitive.d.ts +19 -0
  38. package/dist/lib/components/ui/widgets/drawing/partials/primitives/shapes.d.ts +18 -0
  39. package/dist/lib/components/ui/widgets/drawing/partials/react-attrs.d.ts +5 -0
  40. package/dist/lib/components/ui/widgets/drawing/partials/ring.d.ts +9 -0
  41. package/dist/lib/components/ui/widgets/drawing/partials/svg-tree.d.ts +29 -0
  42. package/dist/lib/components/ui/widgets/drawing/use-exact-text.d.ts +16 -0
  43. package/dist/lib/draw/index.d.ts +32 -0
  44. package/dist/lib/draw-animate/index.d.ts +19 -0
  45. package/dist/lib/draw-animate/morph-drawing.d.ts +32 -0
  46. package/dist/lib/draw-animate/reduced-motion.d.ts +2 -0
  47. package/dist/lib/draw-animate/use-drawing-controls.d.ts +27 -0
  48. package/dist/lib/draw-animate/use-scene-morph.d.ts +15 -0
  49. package/dist/lib/draw-png/index.d.ts +11 -0
  50. package/dist/lib/draw-sketch/index.d.ts +17 -0
  51. package/dist/lib/draw-sketch/sketch.d.ts +19 -0
  52. package/dist/lib/print/components/print-drawing.d.ts +16 -0
  53. package/dist/lib/print/index.d.ts +2 -1
  54. package/dist/main.js +21 -21
  55. package/dist/map.js +63 -63
  56. package/dist/material.css +1 -1
  57. package/dist/pdf-reader.js +1 -1
  58. package/dist/print.js +123 -115
  59. package/dist/{public-Cde8_SN8.js → public-CBlV2XuA.js} +742 -742
  60. package/dist/{sortable-provider-DYlpq8lP.js → sortable-provider-BvD98s8Y.js} +19 -19
  61. package/dist/{use-chat-composer-BwpSgP_D.js → use-chat-composer-BhdPNmYm.js} +912 -911
  62. package/dist/use-shape-morph-BKA_R5P5.js +35 -0
  63. package/docs/README.md +1 -0
  64. package/docs/components/data-display.md +2 -2
  65. package/docs/components/rich-widgets.md +196 -0
  66. package/docs/guides/drawing.md +499 -0
  67. package/docs/guides/printing.md +47 -0
  68. package/docs/guides/theming.md +86 -43
  69. package/docs/previews/rich-widgets-drawing.webp +0 -0
  70. package/docs/roadmap.md +17 -0
  71. package/docs/troubleshooting.md +166 -2
  72. package/lib/components/components.css +2 -0
  73. package/lib/components/theme-tokens.css +84 -72
  74. package/lib/components/tones/tokens.css +5 -5
  75. package/lib/components/ui/display/chip/chip-hairline.css +1 -1
  76. package/lib/components/ui/layout/sidebar/sidebar-tokens.css +1 -1
  77. package/lib/components/ui/overlays/dialog/dialog.css +2 -2
  78. package/lib/components/ui/primitives/field-box/field-box-tokens.css +1 -1
  79. package/lib/components/ui/primitives/toggle-group/toggle-group-segmented.css +2 -2
  80. package/lib/components/ui/primitives/toggle-group/toggle-group-separate.css +1 -1
  81. package/lib/components/ui/widgets/drawing/drawing-animate.css +123 -0
  82. package/lib/components/ui/widgets/drawing/drawing.css +459 -0
  83. package/package.json +22 -2
@@ -90,6 +90,48 @@ container is the accent colour itself, the same as `bg-accent`.
90
90
 
91
91
  Put `text-foreground` on a container; it stays well above 4.5:1 in both themes.
92
92
 
93
+ ## The neutral palette
94
+
95
+ The neutrals of both themes are softly tinted toward violet (hue 290, low
96
+ chroma), to sit with the primary indigo instead of fighting it. The values:
97
+
98
+ | Token | Light | Dark |
99
+ | --- | --- | --- |
100
+ | `--background` | `oklch(0.973 0.008 293.9)` | `oklch(0.192 0.016 290.5)` |
101
+ | `--card`, `--popover` / `--sidebar` | white / `oklch(0.985 0.006 293.9)` | `oklch(0.232 0.019 289.4)` |
102
+ | `--foreground` | `oklch(0.227 0.017 290)` | `oklch(0.943 0.012 291.3)` |
103
+ | `--muted`, `--accent` | `oklch(0.955 0.012 292)`, `oklch(0.91 0.016 293)` | `oklch(0.27 0.022 290)` |
104
+ | `--muted-foreground` | `oklch(0.478 0.032 292)` | `oklch(0.725 0.03 292.2)` |
105
+ | `--border`, `--input` | `oklch(0.912 0.016 293.7)` | `oklch(0.301 0.025 291.4)`, `oklch(0.34 0.027 291.4)` |
106
+
107
+ Earlier versions used a blue-slate neutral (hue 247 to 266; the dark
108
+ background `oklch(0.14 0.036 258.53)` was a deep navy). To bring it back,
109
+ redefine the tokens after the stylesheet import, under the same selectors;
110
+ the main ones are below and the changelog of `@nxgt/material` 3.6.0 lists
111
+ every old value (accent, ring, sidebar, tooltip, secondary and the others):
112
+
113
+ ```css
114
+ :root {
115
+ --background: oklch(1 0 0);
116
+ --foreground: oklch(0.14 0.036 258.53);
117
+ --muted: oklch(0.968 0.007 247.896);
118
+ --muted-foreground: oklch(0.52 0.046 257.417);
119
+ --border: oklch(0.929 0.013 255.508);
120
+ --input: oklch(0.929 0.013 255.508);
121
+ }
122
+ .dark,
123
+ [data-theme="dark"] {
124
+ --background: oklch(0.14 0.036 258.53);
125
+ --foreground: oklch(0.984 0.003 247.858);
126
+ --card: oklch(0.208 0.042 265.755);
127
+ --popover: oklch(0.208 0.042 265.755);
128
+ --muted: oklch(0.279 0.041 260.031);
129
+ --muted-foreground: oklch(0.66 0.046 257.417);
130
+ --border: oklch(1 0 0 / 10%);
131
+ --input: oklch(1 0 0 / 15%);
132
+ }
133
+ ```
134
+
93
135
  ## Motion and shape tokens
94
136
 
95
137
  `styles.css` also carries Material 3 expressive motion and corner tokens, as
@@ -227,12 +269,12 @@ any tone, yours included:
227
269
  | `--nxgt-tone-on` | `text-tone-on` | what is drawn on it |
228
270
  | `--nxgt-tone-container` | `bg-tone-container` | a tint of it over the surface it sits on |
229
271
  | `--nxgt-tone-text` | `text-tone-text` | it as text on the page or its container |
230
- | `--nxgt-tone-ring-share` | — | the share of the text colour in the ring below (`65%`, set on `:root`) |
272
+ | `--nxgt-tone-ring-share` | — | the share of the text colour in the ring below (`68%`, set on `:root`) |
231
273
  | `--nxgt-tone-ring` | `border-(--nxgt-tone-ring)` | a hairline: its text colour mixed into the surface at that share, 3:1 against it |
232
274
 
233
275
  The ring is drawn by: the `Avatar` fallback (the inset hairline, or the stroke of a shaped one); the tonal `StatCard` outline, the edges of its filled card and the ring round its icon chip; the outline and rank badge of a toned metric card; and, in a [calm zone](#calm-zone), the edge of a tonal `Shape`, an `Avatar`, a `StatCard` and a rank badge once their container steps back.
234
276
 
235
- Which knob changes which ring: `--nxgt-tone-ring-share` is the shared one, and being inherited it works on `:root` (`:root { --nxgt-tone-ring-share: 80%; }` makes every ring stronger) or on a wrapper (only the rings inside it). A component with a knob of its own, `--avatar-ring-share` or `--stat-card-ring-share`, takes it over for that component only: it applies to the component's own element, it is not inherited by a tone nested inside it, and `--stat-card-ring-share` draws every edge of the `StatCard`. Keep a ring at 3:1 or more against its surface (65 % does in light and dark) when you change the share.
277
+ Which knob changes which ring: `--nxgt-tone-ring-share` is the shared one, and being inherited it works on `:root` (`:root { --nxgt-tone-ring-share: 80%; }` makes every ring stronger) or on a wrapper (only the rings inside it). A component with a knob of its own, `--avatar-ring-share` or `--stat-card-ring-share`, takes it over for that component only: it applies to the component's own element, it is not inherited by a tone nested inside it, and `--stat-card-ring-share` draws every edge of the `StatCard`. Keep a ring at 3:1 or more against its surface (68 % does in light and dark) when you change the share.
236
278
 
237
279
  ```tsx
238
280
  <span data-tone="success" className="bg-tone-container text-tone-text">
@@ -742,22 +784,22 @@ the rest yourself. Without the class the anchor keeps the prose style.
742
784
  ## Contrast and reduced motion
743
785
 
744
786
  **Contrast.** The shipped light palette keeps the text of every filled status
745
- colour at 4.5:1 or more (WCAG 1.4.3): `--secondary` 5.7:1, `--success` 5.6:1,
746
- `--info` 5.8:1 and `--warning` 5.5:1 against their `-foreground`, and
787
+ colour at 4.5:1 or more (WCAG 1.4.3): `--secondary` 6.2:1, `--success` 6.1:1,
788
+ `--info` 6.3:1 and `--warning` 5.5:1 against their `-foreground`, and
747
789
  `--secondary` as text (a `tonal`, `outlined` or `link` `Button`) 6.0:1 on
748
790
  `--background`, 4.8:1 on its own 15 % `tonal` tint.
749
791
 
750
792
  The dark theme keeps brighter values, read as text on the dark surfaces: the
751
- dark `--tertiary` (`oklch(0.7 0.15 310.75)`) is 7.0:1 on `--background` and
752
- 6.3:1 on `--card`, with a dark `--tertiary-foreground` that keeps 7.0:1 on
793
+ dark `--tertiary` (`oklch(0.7 0.15 310.75)`) is 6.5:1 on `--background` and
794
+ 5.9:1 on `--card`, with a dark `--tertiary-foreground` that keeps 6.5:1 on
753
795
  a filled tertiary surface (light: 7.6:1 under near-white). The dark
754
- `--secondary` (`oklch(0.66 0.1135 245.65)`) is 6.5:1 on `--background`
755
- and 5.8:1 on `--card` (a `tonal` secondary `Button` 5.4:1 and 4.6:1). Under
796
+ `--secondary` (`oklch(0.675 0.1135 245.65)`) is 6.3:1 on `--background`
797
+ and 5.8:1 on `--card` (a `tonal` secondary `Button` 5.1:1 and 4.6:1). Under
756
798
  near-white text those brights are too light, so every filled surface built on
757
799
  them is tinted in the dark theme: a filled `secondary` `Button`, `IconButton`
758
- or `Badge` at 70 % (5.2:1 on `--background`, 4.9:1 on `--card`), a filled
759
- `success`, `info` or `warning` one at 60 % (7.3:1, 5.7:1 and 6.0:1 on
760
- `--background`; 6.7:1, 5.3:1 and 5.6:1 on `--card`; the `warning` `Button` 6.5:1). Their hover, focus and
800
+ or `Badge` at 70 % (4.9 to 5.2:1 on `--background`, 4.7 to 4.9:1 on `--card`), a filled
801
+ `success`, `info` or `warning` one at 60 % (7.1:1, 5.6:1 and 5.8:1 on
802
+ `--background`; 6.8:1, 5.3:1 and 5.5:1 on `--card`; the `warning` `Button` 5.9:1). Their hover, focus and
761
803
  pressed states are tinted further down, so they only gain contrast.
762
804
 
763
805
  In the light theme the filled `primary`, `secondary`, `success`, `info`,
@@ -773,11 +815,11 @@ The `tonal`, `outlined` and `link` variants of `Button` and `IconButton` draw
773
815
  their text and icon in one tone per colour, read on `--background`, on
774
816
  `--card` and on the variant's own tint (15 % at most, at rest or focused). In
775
817
  the light theme `primary`, `secondary`, `info` and `success` use the token
776
- itself (5.8, 4.8, 4.9 and 4.7:1 at their lowest) and `warning` and `error` the
777
- token mixed with 15 % black (6.4 and 5.4:1). In the dark theme `primary` is
778
- mixed with 60 % white, the `--primary-text` token (8.1:1 on `--card`: the token itself is a dark indigo,
779
- 2.2:1), `success` and `warning` with 15 % white (5.0 and 6.3:1), `error` with
780
- 30 % white (5.9:1), and `secondary` and `info` use the token (4.6 and 5.3:1).
818
+ itself (5.4, 4.8, 4.9 and 4.7:1 at their lowest) and `warning` and `error` the
819
+ token mixed with 15 % black (6.0 and 5.0:1). In the dark theme `primary` is
820
+ mixed with 60 % white, the `--primary-text` token (7.7:1 at its lowest: the token itself is a dark indigo,
821
+ 2.2:1), `success` and `warning` with 15 % white (4.8 and 5.8:1), `error` with
822
+ 30 % white (5.5:1), and `secondary` and `info` use the token (4.6 and 5.0:1).
781
823
  The tones are mixed from the tokens (`color-mix(in oklab, var(--color-error)
782
824
  85%, black)`), so an override of a token moves its tone with it; a text colour
783
825
  of your own on one of those buttons (`className="text-primary-text"`) replaces the tone
@@ -796,32 +838,32 @@ the filled surface's near-white text below 4.5:1 (`0.62` gives 3.85:1).
796
838
  the light theme, 70 in the dark one), so a warning `Alert`, `Banner`, `Badge`
797
839
  or toast does not read as a failure. Light `oklch(0.52 0.13 62)` is 5.5:1
798
840
  under its near-white `-foreground`; dark `oklch(0.7 0.15 70)` reads as text at
799
- 7.3:1 on `--background` and 6.5:1 on `--card` (6.6 and 5.8:1 on its 10 % tint). An app that overrides `--warning` keeps its own
841
+ 6.7:1 on `--background` and 6.1:1 on `--card` (5.9 and 5.3:1 on its 10 % tint). An app that overrides `--warning` keeps its own
800
842
  value; one that keeps the shipped value sees its `bg-warning` and
801
843
  `text-warning` surfaces change hue, and re-checks its custom pairs.
802
844
 
803
845
  **A selected `DataTable` row** (`TableRow`, `data-state="selected"`) is a 15 %
804
846
  primary tint with `foreground` text, a bar on the inline-start edge of its
805
847
  first cell (`before:`, primary in the light theme, primary mixed with 60 %
806
- white in the dark one: 5.9:1 in light and 8.2:1 or more in dark against the
807
- row), and the checkbox, drawn in the same dark colour (8.2:1 or more). The
808
- tint is too light to be told from its neighbour alone (1.2:1 in light, 1.06 to
848
+ white in the dark one: 5.4:1 in light and 7.7:1 or more in dark against the
849
+ row), and the checkbox, drawn in the same dark colour (7.7:1 or more). The
850
+ tint is too light to be told from its neighbour alone (1.25:1 in light, 1.10 to
809
851
  1.11:1 in dark), which is what keeps the text at 4.5:1 or more on a page and on
810
- a `Card`: 15.9:1 in light, and in dark 17.5:1 on a page, 17.1:1 in a `DataTable`
811
- on a `Card` and 15.4:1 for a bare `TableRow` on a `Card`. Links and
812
- `text-muted-foreground` texts (drawn as `foreground` at 70 %) are 7.0:1 in
813
- light and 8.15:1 in dark for a bare row on a `Card`. A `text-error-text` text
852
+ a `Card`: 12.6:1 in light, and in dark 14.1:1 on a page, 12.9:1 in a `DataTable`
853
+ on a `Card` and for a bare `TableRow` on a `Card`. Links and
854
+ `text-muted-foreground` texts (drawn as `foreground` at 70 %) are 5.6:1 in
855
+ light and 7.0:1 in dark for a bare row on a `Card`. A `text-error-text` text
814
856
  keeps its colour. Do not
815
857
  put `text-primary-foreground` in a cell for a selected row: it is near-white
816
858
  on that tint.
817
859
 
818
860
  **Error text has a token of its own**, `--error-text` (`text-error-text`), so
819
861
  the fill can stay dark while the text is light enough to read. The light value
820
- is `color-mix(in oklab, var(--error) 94%, black)` (5.7:1 on `--background`
821
- and `--card`, 4.8:1 on the `bg-error/10` tint of a focused menu item, where
862
+ is `color-mix(in oklab, var(--error) 92%, black)` (5.6:1 on `--background`,
863
+ 6.0:1 on `--card`, 5.0:1 on the `bg-error/10` tint of a focused menu item, where
822
864
  `--error` gives 5.0 and 4.2:1); the dark one is
823
- `color-mix(in oklab, var(--error) 80%, white)`, 5.9:1 on `--background` and
824
- 5.3:1 on `--card`, where `--error` itself gives 4.2 and 3.8:1. Field messages,
865
+ `color-mix(in oklab, var(--error) 76%, white)`, 5.85:1 on `--background` and
866
+ 5.35:1 on `--card` (4.78:1 on a focused error menu item), where `--error` itself gives 3.9 and 3.5:1. Field messages,
825
867
  `HelperText` in its error state, form, dialog and upload errors, a textarea's
826
868
  counter over its limit, the error lines of `Terminal`, a
827
869
  `variant="error"` menu item and the falling values of the metric cards use it.
@@ -846,7 +888,8 @@ the override on the root.
846
888
  **Primary text has a token of its own**, `--primary-text` (`text-primary-text`),
847
889
  for the same reason: the brand colour is a dark fill (it keeps near-white text
848
890
  readable on a button) and reads at only about 2:1 as text on the dark surfaces.
849
- The light value is `var(--primary)`; the dark one is
891
+ The light value is `color-mix(in oklab, var(--primary) 96%, black)` (primary
892
+ 4 % toward black, 7.3:1 on the violet page); the dark one is
850
893
  `color-mix(in oklab, var(--primary) 40%, white)` (primary mixed 60 % with
851
894
  white, the recipe of the tonal `Button` labels). `Typography tone="primary"`,
852
895
  the `Alert` primary icon, links, selected labels and the other primary text of
@@ -871,33 +914,33 @@ at rest, hovered, focused and pressed, the tracks and the tabs;
871
914
  tokens after an override.
872
915
 
873
916
  **Tabs.** An `underline` tab's active label is `--primary` in the light
874
- theme (7.3:1 on `--background` and `--card`, 6.7:1 on `--muted`) and
875
- `--primary` mixed with 60 % white in the dark one (10.0:1, 9.0:1 and 7.4:1);
917
+ theme (6.8:1 on `--background`, 7.3:1 on `--card`, 6.4:1 on `--muted`) and
918
+ `--primary` mixed with 60 % white in the dark one (9.3:1, 8.5:1 and 7.6:1);
876
919
  its bar and every tab's focus outline are `--primary` in the light theme and
877
- `--track-fill` in the dark one (at least 6.7:1 and 9.0:1 on those surfaces).
920
+ `--track-fill` in the dark one (at least 6.7:1 and 9.4:1 on those surfaces).
878
921
  An inactive label is `--muted-foreground`, and turns to `--foreground` when
879
922
  hovered. A `segmented` tab's active label is `--foreground` on the raised
880
- surface (19.9:1 light, 17.0:1 dark); that surface is only 1.1:1 to 1.2:1 on
881
- the track, so it has a `--muted-foreground` border (5.0:1 light, 4.7:1 dark
923
+ surface (17.1:1 light, 14.2:1 dark); that surface is only 1.1:1 to 1.2:1 on
924
+ the track, so it has a `--muted-foreground` border (5.8:1 light, 6.2:1 dark
882
925
  on the track), and the label also changes colour and weight. Storybook's
883
926
  `Guides/Theming/Contrast` story `Tabs` measures each label at rest, hovered
884
927
  and focused, the outline, the bar and the border, on the three surfaces, in
885
928
  both themes.
886
929
 
887
- `--muted-foreground` is 5.5:1 on `--background` and `--card`, 5.1:1 on the
888
- "paper" tint (`bg-background-paper`, 5 % primary) and 5.0:1 on `--muted` in
889
- the light theme; in the dark theme it is 6.4:1 on `--background`, 5.7:1 on
890
- `--card`, 6.3:1 on the paper tint and 4.7:1 on `--muted`. The dark `--muted`
891
- is a dark surface (`oklch(0.279 0.041 260.031)`, it was the light value, a
892
- near-white block on a dark page) and `--foreground` on it reaches 14.0:1.
930
+ `--muted-foreground` is 6.2:1 on `--background`, 6.7:1 on `--card`, 5.7:1 on the
931
+ "paper" tint (`bg-background-paper`, 5 % primary) and 5.8:1 on `--muted` in
932
+ the light theme; in the dark theme it is 7.5:1 on `--background`, 6.9:1 on
933
+ `--card`, 7.3:1 on the paper tint and 6.2:1 on `--muted`. The dark `--muted`
934
+ is a dark surface (`oklch(0.27 0.022 290)`, it was the light value, a
935
+ near-white block on a dark page) and `--foreground` on it reaches 12.8:1.
893
936
 
894
937
  **Tracks.** The empty part of a `Slider`, of the password strength meter and
895
938
  the `Drawer` handle use `--track` (`bg-track`), and the `Slider`'s range and
896
939
  thumb border `--track-fill` (`bg-track-fill`, `border-track-fill`). In the
897
- dark theme the track reaches 3.6:1 on `--background` and 3.2:1 on `--card`
940
+ dark theme the track reaches 3.5:1 on `--background` and 3.2:1 on `--card`
898
941
  (WCAG 1.4.11; `bg-muted`, a dark surface now, gave 1.4:1 and 1.2:1), and the
899
- fill 3.4:1 on the track. The light theme keeps a pale track (`--track` is the
900
- light `--muted`) and `--track-fill` is `--primary`: the thumb, 7.3:1 on the
942
+ fill 3.3:1 on the track. The light theme keeps a pale track (`--track` is the
943
+ light `--muted`) and `--track-fill` is `--primary`: the thumb, 6.8:1 on the
901
944
  page, shows the value. A track, bar, handle or divider of your own drawn with
902
945
  `bg-muted` is near invisible in the dark theme now that `--muted` is a dark
903
946
  surface: draw it with `bg-track`.
package/docs/roadmap.md CHANGED
@@ -6,6 +6,23 @@ version they shipped in, and planned entries may name the major they target.
6
6
 
7
7
  ## Now
8
8
 
9
+ - **Vector drawings in code** (in the next prerelease) — `@nxgt/material/draw`
10
+ draws a scene of plain data, or JSX primitives (`Rect`, `Path`, `Text`...), as
11
+ SVG that follows the theme: colours are Material 3 roles and tones, with no
12
+ inline `style`, so a strict CSP is satisfied and a server renders the same
13
+ markup as the browser. A drawing is named (`title`) or `decorative`, enforced
14
+ by the type; nodes with events are focusable buttons with a focus ring; wrapped
15
+ text breaks with Inter's metrics and is refined once mounted. Material 3
16
+ Expressive's shapes, morphs and layout helpers (`stack`, `grid`, `align`)
17
+ come with it. `/draw/sketch` redraws any drawing by hand (seeded, so the
18
+ server and the browser agree), `/draw/animate` traces strokes, fades nodes in
19
+ and morphs one scene into another (nothing moves under reduced motion),
20
+ `/draw/png` saves a PNG with the page's colours, and `/print` places the scene
21
+ in a PDF as vectors. For e-mail, `toSVG(scene, { standalone: true })` gives an
22
+ SVG with literal colours and a PNG made in a browser covers the clients that
23
+ do not show SVG. React only for now; the Vue port is on the Vue roadmap.
24
+ ([Drawing guide](guides/drawing.md), [Drawing](components/rich-widgets.md#drawing))
25
+
9
26
  - **A modern code editor** — `CodeEditor` and `CodeDiff` get their own entry,
10
27
  `@nxgt/material/code-editor`, and theme presets: App (from your tokens),
11
28
  Moonlight and Soft, light and dark, or your own `defineCodeTheme`, several on
@@ -27,6 +27,7 @@ words in your error.
27
27
 
28
28
  - [Components render unstyled, with no error](#components-render-unstyled-with-no-error)
29
29
  - [A link is blue and underlined on hover and focus](#a-link-of-mine-or-of-a-component-is-blue-and-underlined-on-hover-and-focus)
30
+ - [The light and dark neutrals turned violet after upgrading to 3.6](#the-light-and-dark-neutrals-turned-violet-after-upgrading-to-36)
30
31
  - [Math renders in the system fonts](#math-renders-in-the-system-fonts)
31
32
  - [`<Icon>` renders nothing](#icon-renders-nothing)
32
33
  - [An accessibility audit reports low contrast on a badge, a button, a toast or a menu entry](#an-accessibility-audit-reports-low-contrast-on-a-badge-a-button-a-toast-or-a-menu-entry)
@@ -388,6 +389,19 @@ words in your error.
388
389
  - [My gradient colour override stopped working](#my-gradient-colour-override-stopped-working)
389
390
  - [A preset's items use class names my own CSS also uses (`fl`, `d1`, `k1`, `mo`)](#a-presets-items-use-class-names-my-own-css-also-uses-fl-d1-k1-mo)
390
391
 
392
+ **Drawing**
393
+
394
+ - [`A decorative drawing has an interactive node: give the Drawing a title instead`](#a-decorative-drawing-has-an-interactive-node-give-the-drawing-a-title-instead)
395
+ - [`Unsafe drawing at <tag>: <reason>` from a scene or a JSX drawing](#unsafe-drawing-at-tag-reason-from-a-scene-or-a-jsx-drawing)
396
+ - [`Tones "a" and "b" meet on one element: use two nodes in a group`](#tones-a-and-b-meet-on-one-element-use-two-nodes-in-a-group)
397
+ - [`No colour for "brand": pass palette: { "brand": "#rrggbb" }`](#no-colour-for-brand-pass-palette--brand-rrggbb-)
398
+ - [`shapeOutline: unknown shape "x"; expected one of SHAPE_NAMES (…)`](#shapeoutline-unknown-shape-x-expected-one-of-shape_names-)
399
+ - [`drawingToPng: cannot read the image "<url>" (<reason>)…`](#drawingtopng-cannot-read-the-image-url-reason)
400
+ - [`drawingToPng: the canvas gave no PNG.` and its siblings](#drawingtopng-the-canvas-gave-no-png-and-its-siblings)
401
+ - [A drawing is black, or its role colours do nothing](#a-drawing-is-black-or-its-role-colours-do-nothing)
402
+ - [A Server Component that renders `Drawing` fails](#a-server-component-that-renders-drawing-fails)
403
+ - [`Duplicate identifier 'text'` when importing from `/draw` and `/print`](#duplicate-identifier-text-when-importing-from-draw-and-print)
404
+
391
405
  **Content Security Policy**
392
406
 
393
407
  - [`[nxgt-material] html.cspNonce is set, but the CSP is in hash mode (the default): set nxgtMaterial({ csp: { mode: 'nonce' } }) or drop html.cspNonce.`](#nxgt-material-htmlcspnonce-is-set-but-the-csp-is-in-hash-mode-the-default-set-nxgtmaterial-csp--mode-nonce---or-drop-htmlcspnonce)
@@ -740,6 +754,34 @@ public/assets/icons/sprites/sharp-solid.svg ← <Icon name="atom" family="sharp"
740
754
 
741
755
  Serve one sheet per style you use, from your own Font Awesome Pro kit.
742
756
 
757
+ ### The light and dark neutrals turned violet after upgrading to 3.6
758
+
759
+ **When:** after `@nxgt/material` 3.6, the page, cards, borders and muted text
760
+ are softly violet-tinted (the dark background was a deep navy), and a
761
+ screenshot test, or a brand that sat on the old slate, no longer matches.
762
+ **Why:** the neutral tokens moved to hue 290 with low chroma in both themes
763
+ (the palette is listed in the [theming guide](guides/theming.md#the-neutral-palette)).
764
+ Primary and the status colours did not change, except that the light
765
+ `--secondary`, `--success` and `--info` and the dark `--secondary` moved
766
+ 0.02 in lightness, so their tonal tints keep 4.5:1 on the new page colour.
767
+ **Fix:** update the screenshots, or redefine the old neutrals after the
768
+ stylesheet import, under `:root` and `.dark, [data-theme="dark"]`:
769
+
770
+ ```css
771
+ .dark,
772
+ [data-theme="dark"] {
773
+ --background: oklch(0.14 0.036 258.53);
774
+ --card: oklch(0.208 0.042 265.755);
775
+ --popover: oklch(0.208 0.042 265.755);
776
+ --muted: oklch(0.279 0.041 260.031);
777
+ --border: oklch(1 0 0 / 10%);
778
+ }
779
+ ```
780
+
781
+ The [theming guide](guides/theming.md#the-neutral-palette) has the main old
782
+ values for both themes (the `:root` block too); the changelog of 3.6.0 lists
783
+ every one.
784
+
743
785
  ### An accessibility audit reports low contrast on a badge, a button, a toast or a menu entry
744
786
 
745
787
  **When:** axe (or Lighthouse) reports `color-contrast` on a `Badge` with
@@ -795,7 +837,7 @@ yours still fail, you override them.
795
837
  In the dark theme, a filled `secondary` `Button` gave its near-white text
796
838
  4.3:1, a `tonal` secondary one 3.9:1 (3.4:1 on `--card`), and a filled
797
839
  `success`, `info` or `warning` one 3.5:1, 2.5:1 and 3.4:1. The dark
798
- `--secondary` is brighter (`oklch(0.66 ...)`, 6.5:1 as text on
840
+ `--secondary` is brighter (`oklch(0.675 ...)`, 6.3:1 as text on
799
841
  `--background`), and the filled `secondary`, `success`, `info` and `warning`
800
842
  `Button`s and `IconButton`s are filled in the dark theme with an opaque mix
801
843
  toward `--background` (about 70 %), so their text keeps 4.5:1 over any backdrop.
@@ -872,7 +914,7 @@ subtitle colour of your own on a selected `ListTile` must reach 4.5:1 on
872
914
  ### An accessibility audit reports low contrast on error text in the dark theme
873
915
 
874
916
  **When:** an error message, a validation hint or a red value of your own, in
875
- the dark theme, fails 4.5:1 (about 4.2:1 on the page, 3.8:1 on a card); the
917
+ the dark theme, fails 4.5:1 (about 3.9:1 on the page, 3.5:1 on a card); the
876
918
  catalogue's own field messages pass.
877
919
  **Why:** it is drawn with `text-error`, the fill colour. The dark `--error` is
878
920
  kept dark enough for near-white text on a filled error surface, which makes it
@@ -6759,6 +6801,128 @@ import '@nxgt/material/backgrounds.css'; // every preset
6759
6801
 
6760
6802
  ---
6761
6803
 
6804
+ ## Drawing
6805
+
6806
+ How to use it is in the [Drawing guide](guides/drawing.md); the engine's own
6807
+ errors (`Invalid colour`, `Invalid path data`, `Unsupported scene version`,
6808
+ `[pdf] A drawing mask is not supported in a PDF: dropped.`) are in the
6809
+ [core troubleshooting](https://cdn.jsdelivr.net/npm/@nxgt/material-core@0/docs/troubleshooting.md#drawing).
6810
+
6811
+ ### `A decorative drawing has an interactive node: give the Drawing a title instead`
6812
+
6813
+ **When:** a `Drawing` with `decorative` renders, or contains, a node with `onClick` (or any event), `interactive`, or an entry in `handlers`. It throws on the render.
6814
+ **Why:** `decorative` is `aria-hidden`, and a focusable node inside it would be reachable by keyboard and invisible to assistive technology.
6815
+ **Fix:** give the drawing a `title` (and each interactive node a `label`), or drop the events:
6816
+
6817
+ ```tsx
6818
+ <Drawing scene={board} title="Board columns" onNodeClick={open} />
6819
+ ```
6820
+
6821
+ ### `Unsafe drawing at <tag>: <reason>` from a scene or a JSX drawing
6822
+
6823
+ **When:** `Drawing`, `toSVG`, `compileScene` or `assertSafeDrawing` throws `Unsafe drawing at <image>: href must be http(s), relative or data:image (png, jpeg, gif, webp)` or another reason (`tag not allowed`, `style not allowed`, `<attribute> not finite`, `id used twice`...).
6824
+ **Why:** a strict CSP could not accept what the scene would write. From a scene loaded as JSON the cause is usually an `image` `href` (`javascript:`, `file:` or `data:image/svg+xml`), a `className` or node `id` with unusual characters, or a coordinate that is `NaN`.
6825
+ **Fix:** correct the value the message names. Check untrusted JSON before drawing it:
6826
+
6827
+ ```ts
6828
+ import { assertSafeDrawing } from '@nxgt/material/draw';
6829
+
6830
+ assertSafeDrawing(scene); // throws with the element and the reason
6831
+ ```
6832
+
6833
+ The reasons are listed in the [core troubleshooting](https://cdn.jsdelivr.net/npm/@nxgt/material-core@0/docs/troubleshooting.md#unsafe-drawing-at-tag-reason).
6834
+
6835
+ ### `Tones "a" and "b" meet on one element: use two nodes in a group`
6836
+
6837
+ **When:** a node has `fill: tone('brand')` and `stroke: tone('accent')` where both are app tones.
6838
+ **Why:** an app tone is carried by one `data-tone` attribute on the element, so an element can have only one.
6839
+ **Fix:** one node per tone, in a group, or name a theme tone for one of them:
6840
+
6841
+ ```tsx
6842
+ group({}, [
6843
+ circle({ cx: 20, cy: 20, r: 14, fill: tone('brand', 'container') }),
6844
+ circle({ cx: 20, cy: 20, r: 14, fill: 'transparent', stroke: tone('accent') }),
6845
+ ]);
6846
+ ```
6847
+
6848
+ ### `No colour for "brand": pass palette: { "brand": "#rrggbb" }`
6849
+
6850
+ **When:** `toSVG` (the default `colors: 'literal'`) or a PDF meets an app tone or token that has no literal colour.
6851
+ **Why:** a standalone SVG has no theme to read, so every token needs a hex; only the theme's own roles have one.
6852
+ **Fix:** pass it, or use `colors: 'vars'` for an inline SVG:
6853
+
6854
+ ```ts
6855
+ toSVG(art, { palette: { brand: '#0a7c59' } });
6856
+ ```
6857
+
6858
+ ### `shapeOutline: unknown shape "x"; expected one of SHAPE_NAMES (…)`
6859
+
6860
+ **When:** `shape('cookie-99', …)` or `morph` is given a name the registry does not have.
6861
+ **Why:** a typo, or a name from another library. The message lists the valid ones.
6862
+ **Fix:** use a name of `SHAPE_NAMES`, with or without the `m3-` prefix:
6863
+
6864
+ ```ts
6865
+ import { SHAPE_NAMES, shape } from '@nxgt/material/draw';
6866
+
6867
+ shape('cookie-9', { x: 0, y: 0, w: 80, h: 80 }); // or 'm3-cookie-9'
6868
+ console.log(SHAPE_NAMES);
6869
+ ```
6870
+
6871
+ ### `drawingToPng: cannot read the image "<url>" (<reason>)…`
6872
+
6873
+ **When:** `drawingToPng` or `downloadDrawingPng` rejects, and the drawing holds an `image` with a URL.
6874
+ **Why:** an SVG drawn as an image loads nothing external, so the function fetches each image and inlines its bytes; the browser refused the fetch, almost always CORS. Nothing is left to taint the canvas: the failure is reported before it can happen.
6875
+ **Fix:** serve the image with `Access-Control-Allow-Origin`, or put a `data:image/png|jpeg|gif|webp` URL in the scene:
6876
+
6877
+ ```ts
6878
+ image({ href: 'data:image/png;base64,…', x: 0, y: 0, w: 64, h: 64 });
6879
+ ```
6880
+
6881
+ ### `drawingToPng: the canvas gave no PNG.` and its siblings
6882
+
6883
+ **When:** a PNG fails with one of `drawingToPng: the canvas gave no PNG.`, `drawingToPng: no 2D canvas in this browser.`, `drawingToPng: scale must be above 0.`, `drawingToPng: the drawing has no size.` or `drawingToPng: the browser could not draw the SVG.`.
6884
+ **Why:** respectively a canvas the browser refused to encode (usually far too large: `scale` times the drawing's size), no canvas support, a zero or negative `scale`, a live `<svg>` with no `width`, `height` or `viewBox`, and an SVG that does not load as an image.
6885
+ **Fix:** lower the `scale`, pass a positive one, give the `<svg>` a `viewBox`, or draw from the scene instead of the live element:
6886
+
6887
+ ```ts
6888
+ await drawingToPng(scene, { scale: 1 });
6889
+ ```
6890
+
6891
+ ### A drawing is black, or its role colours do nothing
6892
+
6893
+ **When:** a `Drawing` renders in black (or the browser's default colours), whatever its `fill="primary"`.
6894
+ **Why:** role colours are classes of `styles.css` (`nxgt-draw-f-primary`...); without the stylesheet nothing maps them. A standalone `toSVG` file does not use classes: it writes hex.
6895
+ **Fix:** import the stylesheet once in the app:
6896
+
6897
+ ```ts
6898
+ import '@nxgt/material/styles.css';
6899
+ ```
6900
+
6901
+ ### A Server Component that renders `Drawing` fails
6902
+
6903
+ **When:** a Next.js or RSC page imports `Drawing`, a primitive, `Sketch` or the animate hooks and the build reports that a hook or a context only works in a Client Component.
6904
+ **Why:** `@nxgt/material/draw` is its own entry whose built files carry no `'use client'` directive (the components use hooks).
6905
+ **Fix:** render it from a file of yours that starts with `'use client'`; the builders and `toSVG` can stay in the Server Component and pass the scene as a prop:
6906
+
6907
+ ```tsx
6908
+ // drawing-client.tsx
6909
+ 'use client';
6910
+ export { Drawing } from '@nxgt/material/draw';
6911
+ ```
6912
+
6913
+ ### `Duplicate identifier 'text'` when importing from `/draw` and `/print`
6914
+
6915
+ **When:** TypeScript reports `Duplicate identifier 'text'` (or `Import declaration conflicts with local declaration of 'text'`, or the same for `image`) in a file that imports from both entries.
6916
+ **Why:** `@nxgt/material/draw` and `@nxgt/material/print` each export a `text` and an `image`: the first builds a drawing node, the second a PDF block.
6917
+ **Fix:** rename one of them:
6918
+
6919
+ ```ts
6920
+ import { text as drawText } from '@nxgt/material/draw';
6921
+ import { text } from '@nxgt/material/print';
6922
+ ```
6923
+
6924
+ ---
6925
+
6762
6926
  ## Content Security Policy
6763
6927
 
6764
6928
  How to set up the policy is in the [Content Security Policy guide](guides/csp.md).
@@ -196,3 +196,5 @@
196
196
  @import "../backgrounds/css/base.css";
197
197
  @import "../backgrounds/css/gradient.css";
198
198
  @import "../backgrounds/css/motion.css";
199
+ @import "./ui/widgets/drawing/drawing.css"; /* @nxgt/material/draw: role colours, focus ring */
200
+ @import "./ui/widgets/drawing/drawing-animate.css";