@nxgt/material 3.13.1 → 3.15.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.
- package/CHANGELOG.md +22 -0
- package/README.md +1 -1
- package/dist/components/ui/display/chip/chip.css +12 -2
- package/dist/{components-BhGc4Bj7.js → components-j0Iv8MZi.js} +9036 -9017
- package/dist/components.js +25 -25
- package/dist/{dialog-rOMjlKJ7.js → dialog-YHZJjDrI.js} +1 -0
- package/dist/editor.js +3 -3
- package/dist/{icon-badge-_tY1MwU9.js → icon-badge-C0TGvF9o.js} +2 -2
- package/dist/lib/components/ui/collections/server-table/index.d.ts +1 -0
- package/dist/lib/components/ui/display/chip/chip.d.ts +1 -1
- package/dist/lib/components/ui/display/chip/chip.variants.d.ts +5 -2
- package/dist/lib/components/ui/feedback/confirm-dialog/confirm-dialog-rules.d.ts +17 -0
- package/dist/lib/components/ui/feedback/confirm-dialog/confirm-dialog.d.ts +10 -2
- package/dist/lib/components/ui/overlays/command/command.d.ts +7 -1
- package/dist/lib/components/ui/overlays/dialog/dialog-variants.d.ts +1 -1
- package/dist/main.js +42 -42
- package/dist/map.js +2 -2
- package/dist/material.css +1 -1
- package/dist/pdf-reader.js +1 -1
- package/dist/{public-D_Gz7wgr.js → public-Bj3rGSfS.js} +1 -1
- package/dist/{use-chat-composer-P4GxmCHa.js → use-chat-composer-CoXmJCkr.js} +7 -7
- package/docs/components/data-display.md +2 -2
- package/docs/components/data-table-filter-and-lists.md +1 -0
- package/docs/components/feedback-and-status.md +4 -0
- package/docs/components/overlays-and-menus.md +2 -1
- package/docs/components/primitives.md +2 -0
- package/docs/components/selection-and-choice.md +3 -1
- package/docs/guides/server-lists-url.md +15 -1
- package/docs/troubleshooting.md +41 -0
- package/lib/components/ui/display/chip/chip.css +12 -2
- package/package.json +2 -2
package/dist/pdf-reader.js
CHANGED
|
@@ -6,7 +6,7 @@ import { c as i, i as a, l as o, n as s, o as c, s as l } from "./dist-lmHmz_bu.
|
|
|
6
6
|
import { a as u, o as d } from "./tooltip-CkttDIab.js";
|
|
7
7
|
import { i as f } from "./dist-C4wtXCjf.js";
|
|
8
8
|
import { r as p, t as m } from "./in-dialog-Vwkyp-M5.js";
|
|
9
|
-
import { C as h, D as g, E as _, O as v, S as y, T as b, _ as x, f as S, g as ee, k as te, r as ne, t as C, u as re, w } from "./dialog-
|
|
9
|
+
import { C as h, D as g, E as _, O as v, S as y, T as b, _ as x, f as S, g as ee, k as te, r as ne, t as C, u as re, w } from "./dialog-YHZJjDrI.js";
|
|
10
10
|
import { c as T, d as ie, f as E, i as ae, l as oe, n as se, o as D, t as ce, u as O } from "./dropdown-menu-BEyP-A0J.js";
|
|
11
11
|
import { S as le, a as k, d as A, g as ue, h as de, i as fe, m as pe, n as j, r as me, t as M, u as he, w as ge } from "./toolbar-items-CVQBYp4K.js";
|
|
12
12
|
import { t as N } from "./use-material-translation-DGeVp-qP.js";
|
|
@@ -7,7 +7,7 @@ import { a as o, c as s, i as c, l, n as u, o as d, r as f, s as p, t as m } fro
|
|
|
7
7
|
import { _ as h, a as g, g as _, h as v, m as y, o as b, p as x, t as S, v as C, y as w } from "./tooltip-CkttDIab.js";
|
|
8
8
|
import { a as T, i as E, n as D, r as O, t as k } from "./dist-C4wtXCjf.js";
|
|
9
9
|
import { i as A, n as ee, t as j } from "./in-dialog-Vwkyp-M5.js";
|
|
10
|
-
import { A as M, C as N, D as te, E as ne, O as P, S as re, T as ie, _ as F, b as ae, f as oe, g as se, k as I, l as ce, m as le, n as ue, o as de, r as fe, s as pe, t as me, u as he, v as ge, w as _e, x as ve, y as ye } from "./dialog-
|
|
10
|
+
import { A as M, C as N, D as te, E as ne, O as P, S as re, T as ie, _ as F, b as ae, f as oe, g as se, k as I, l as ce, m as le, n as ue, o as de, r as fe, s as pe, t as me, u as he, v as ge, w as _e, x as ve, y as ye } from "./dialog-YHZJjDrI.js";
|
|
11
11
|
import { A as be, C as xe, D as Se, E as Ce, O as we, S as Te, T as Ee, _ as De, a as Oe, b as ke, d as Ae, f as je, g as Me, h as Ne, i as Pe, k as Fe, l as Ie, m as Le, n as Re, o as ze, p as Be, r as Ve, s as He, t as Ue, u as We, v as Ge, w as Ke, x as qe, y as Je } from "./floating-ui.dom-KkZSW8_E.js";
|
|
12
12
|
import { D as Ye, E as Xe, N as Ze, O as Qe, c as $e, d as et, f as tt, i as nt, k as rt, m as it, n as at, o as ot, p as st, t as ct, u as lt } from "./dropdown-menu-BEyP-A0J.js";
|
|
13
13
|
import { a as ut, i as dt, n as ft } from "./field-blur-context-Cft9s7Hg.js";
|
|
@@ -36,7 +36,7 @@ import { n as an } from "./use-slice-reducer-BGkeGtML.js";
|
|
|
36
36
|
import { t as on } from "./browser-only-DQ94lWF8.js";
|
|
37
37
|
import { t as sn } from "./compose-refs-DZ0iIVF-.js";
|
|
38
38
|
import { n as cn, r as ln, t as un } from "./chip-BZu-bvAJ.js";
|
|
39
|
-
import { n as dn, t as fn } from "./icon-badge-
|
|
39
|
+
import { n as dn, t as fn } from "./icon-badge-C0TGvF9o.js";
|
|
40
40
|
import { r as pn } from "./use-csp-nonce-ZKs9k-ov.js";
|
|
41
41
|
import { t as mn } from "./empty-state-ZtqqpX0S.js";
|
|
42
42
|
import { t as hn } from "./skeleton-DSdtr2qc.js";
|
|
@@ -1324,7 +1324,7 @@ function xu({ className: e, ...t }) {
|
|
|
1324
1324
|
function Su({ className: e, ...t }) {
|
|
1325
1325
|
return /* @__PURE__ */ G("kbd", {
|
|
1326
1326
|
"data-slot": "kbd",
|
|
1327
|
-
className: n("pointer-events-none inline-flex h-5 min-w-5 items-center justify-center rounded border bg-muted px-1 font-mono text-[10px] font-medium text-muted-foreground", e),
|
|
1327
|
+
className: n("pointer-events-none inline-flex h-5 min-w-5 items-center justify-center rounded border bg-muted px-1 font-mono text-[10px] font-medium text-muted-foreground", "in-[[data-slot=button][data-variant=filled]]:border-current/40 in-[[data-slot=button][data-variant=filled]]:bg-current/15 in-[[data-slot=button][data-variant=filled]]:text-inherit", e),
|
|
1328
1328
|
...t
|
|
1329
1329
|
});
|
|
1330
1330
|
}
|
|
@@ -7969,7 +7969,7 @@ function Hy({ slot: e, values: t, getOptionValue: r = (e) => e.value, getOptionL
|
|
|
7969
7969
|
ref: o,
|
|
7970
7970
|
"data-slot": e ?? "autocomplete-field",
|
|
7971
7971
|
"data-mode": a.mode,
|
|
7972
|
-
className: n("grid w-full gap-1.5", a.className),
|
|
7972
|
+
className: n("grid w-full content-start gap-1.5", a.className),
|
|
7973
7973
|
children: [
|
|
7974
7974
|
/* @__PURE__ */ G(dt, {
|
|
7975
7975
|
id: c,
|
|
@@ -8432,7 +8432,7 @@ function ub({ group: e, name: t, slot: r, className: i, contentClassName: a, inl
|
|
|
8432
8432
|
return /* @__PURE__ */ K("div", {
|
|
8433
8433
|
"data-slot": r,
|
|
8434
8434
|
"data-pw": t ?? r,
|
|
8435
|
-
className: n("grid gap-1 w-full", i),
|
|
8435
|
+
className: n("grid content-start gap-1 w-full", i),
|
|
8436
8436
|
...l,
|
|
8437
8437
|
...d,
|
|
8438
8438
|
ref: u,
|
|
@@ -9542,7 +9542,7 @@ function kx({ error: e, helperText: t, label: r, className: i, placeholder: a, m
|
|
|
9542
9542
|
return /* @__PURE__ */ K("div", {
|
|
9543
9543
|
ref: m,
|
|
9544
9544
|
"data-slot": "textarea-field",
|
|
9545
|
-
className: n("grid w-full gap-1.5", i),
|
|
9545
|
+
className: n("grid w-full content-start gap-1.5", i),
|
|
9546
9546
|
children: [
|
|
9547
9547
|
r && /* @__PURE__ */ G(dt, {
|
|
9548
9548
|
withAsterisk: p.required,
|
|
@@ -12073,7 +12073,7 @@ function GC({ id: e, value: t, defaultValue: r = "", onChange: i, onValueChange:
|
|
|
12073
12073
|
return /* @__PURE__ */ K("div", {
|
|
12074
12074
|
"data-slot": "search-field",
|
|
12075
12075
|
"data-pw": m ?? "search-field",
|
|
12076
|
-
className: n("grid w-full gap-1.5", h),
|
|
12076
|
+
className: n("grid w-full content-start gap-1.5", h),
|
|
12077
12077
|
children: [
|
|
12078
12078
|
l && /* @__PURE__ */ G(dt, {
|
|
12079
12079
|
htmlFor: ee,
|
|
@@ -368,7 +368,7 @@ reads as a status (a stronger container of the tone, the label in the tone's
|
|
|
368
368
|
text colour, 4.5:1 or more on the page and in a card; the `outlined` hairline
|
|
369
369
|
and the `tag` dot still read `--chip-tone`, not the tone channel); `tag`
|
|
370
370
|
a smaller tag led by a colour dot; `dashed` a muted "add" pill. 28px tall,
|
|
371
|
-
24px with `size="sm"`. Selected (`active`, or `aria-pressed` on a toggle
|
|
371
|
+
24px with `size="sm"`, 44px with `size="lg"`. Selected (`active`, or `aria-pressed` on a toggle
|
|
372
372
|
chip) takes a primary tint and a primary edge, and a pressed toggle with
|
|
373
373
|
nothing leading draws a check. The label is truncated as one piece; the ✕
|
|
374
374
|
and `trailing` stay whole.
|
|
@@ -397,7 +397,7 @@ import { Avatar, Chip } from '@nxgt/material/components';
|
|
|
397
397
|
| `variant` | `'outlined' \| 'tonal' \| 'filled' \| 'tag' \| 'dashed'` | `'outlined'` | the look: hairline, soft (`tonal` and `filled`), tag, or a muted dashed "add" pill that turns solid once `active` or pressed. `ghost` (soft) and `link` (hairline) are still accepted, deprecated |
|
|
398
398
|
| `active` | `boolean` | — | selected: primary tint and edge |
|
|
399
399
|
| `aria-pressed` | `boolean` | — | a toggle chip: selected while pressed, with a check when nothing leads |
|
|
400
|
-
| `size` | `'default' \| 'sm'` | `'default'` | 28px or
|
|
400
|
+
| `size` | `'default' \| 'sm' \| 'lg'` | `'default'` | 28px, 24px, or `lg`, a 44px box at every pointer (the touch target); every target stays at least 24px |
|
|
401
401
|
| `color` | `'default' \| 'primary' \| 'secondary' \| 'info' \| 'success' \| 'warning' \| 'error'` | neutral | tone: the edge (outlined), the fill and label (tonal, `filled`: a status look) or the dot and tint (tag). A `tag` without `color` (or with `default`) draws no dot |
|
|
402
402
|
| `onDismiss` | `() => void` | — | adds the ✕ button, named "Remove {label}" (`material.chip.dismiss-label`), `{label}` being the text `label` shows, a node's included; "Remove" when it shows none; the ✕ is drawn 18px (16px small) with a 24px target |
|
|
403
403
|
| `dismissLabel` | `string \| ((label: string) => string)` | — | names the ✕ instead of "Remove {label}": a string as is, or a function of the text the chip shows |
|
|
@@ -679,6 +679,7 @@ type Student = { id: string; name: string; level: string };
|
|
|
679
679
|
const PAGE_SIZE = 20;
|
|
680
680
|
// Module scope: the loader and the page read the same URL the same way.
|
|
681
681
|
const sorting = createSorting({ name: 'name', level: 'level' }, { id: 'name', desc: false });
|
|
682
|
+
// A GraphQL API with an ASC / DESC enum: add { direction: (d) => d.toUpperCase() as 'ASC' | 'DESC' } as the third argument.
|
|
682
683
|
const YEAR = [{ field: 'year', op: 'is', values: ['2026'], reason: 'This school year' }] as const;
|
|
683
684
|
|
|
684
685
|
export async function loader({ request }: Route.LoaderArgs) {
|
|
@@ -222,8 +222,12 @@ function DeleteProject({ onDelete }: { onDelete: () => Promise<void> }) {
|
|
|
222
222
|
| `onConfirm` | `() => void \| Promise<void>` | required | awaited with the dialog open and both buttons disabled; closes on success; on a throw or rejection the dialog stays open and shows the error's message, or the translated `material.confirm-dialog.failed` ("The action failed.") when it has none |
|
|
223
223
|
| `variant` | `'default' \| 'destructive'` | `'default'` | `destructive` paints the confirm button `error` |
|
|
224
224
|
| `loading` | `boolean` | `false` | disables both buttons and shows the translated `material.confirm-dialog.processing` label, as a pending `onConfirm` does |
|
|
225
|
+
| `confirmDisabled` | `boolean` | `false` | disables the confirm button, for example while an error of yours stands |
|
|
226
|
+
| `lockOnRefusal` | `boolean` | `false` | once `onConfirm` has refused (thrown), the confirm button stays disabled while its error shows, so a retry that cannot succeed is not invited; Cancel stays enabled. The error clears when the dialog closes |
|
|
225
227
|
| `confirmLabel` / `cancelLabel` | `string` | "Confirm" / "Cancel", translated (`material.confirm-dialog.confirm` / `.cancel`) | button text |
|
|
226
228
|
|
|
229
|
+
On a phone (below 640 px) the footer stacks. A `destructive` dialog puts Cancel first and the destructive action last; a default one keeps the action on top.
|
|
230
|
+
|
|
227
231
|
`ConfirmDialog` opens with its Cancel focused, so Enter cancels. On closing
|
|
228
232
|
it hands the focus back to what opened it, or to the menu's trigger when
|
|
229
233
|
that was a `DropdownMenuItem` (gone by then); `Dialog`, `Sheet` and
|
|
@@ -131,7 +131,7 @@ export function Terms() {
|
|
|
131
131
|
| `asChild` (`DialogTrigger`) | `boolean` | **`true`** | the trigger is its child — pass one element, usually a `Button` |
|
|
132
132
|
| `variant`, `color` (`DialogClose`) | as `IconButton` | `'ghost'` | a top-right X button, named "Close" (`material.buttons.close`, translated) while it shows only the icon; text children or an `aria-label` name it instead, and icon-only children leave it unnamed; children replace the X |
|
|
133
133
|
| `size` (`DialogContent`) | `'sm' \| 'md' \| 'lg' \| 'xl' \| 'full'` | `'md'` | its width from 640 px up: 384, 512, 672, 896 px, or the screen less 2rem a side |
|
|
134
|
-
| `mobilePresentation` (`DialogContent`) | `'dialog' \| 'bottom-sheet' \| 'fullscreen'` | `'dialog'` | below 640 px: a centred dialog, a sheet that springs up from the bottom edge (rounded top corners), or the whole screen |
|
|
134
|
+
| `mobilePresentation` (`DialogContent`) | `'dialog' \| 'bottom-sheet' \| 'top' \| 'fullscreen'` | `'dialog'` | below 640 px: a centred dialog, a sheet that springs up from the bottom edge (rounded top corners), a dialog pinned under the top edge, or the whole screen |
|
|
135
135
|
| `scrim` (`DialogContent`, `SheetContent`, `DrawerContent`) | `'dim' \| 'blur'` | `'dim'` | the scrim behind; `blur` also blurs the page a little |
|
|
136
136
|
|
|
137
137
|
The look lives in the stylesheet (`nxgt-dialog`, `nxgt-scrim`), under the
|
|
@@ -678,6 +678,7 @@ children and they replace that, as before.
|
|
|
678
678
|
| `alwaysRender` (`CommandSeparator`) | `boolean` | `false` | the separator is purely visual (`role="presentation"`, hidden from assistive technology) and hidden while the user searches; `true` keeps it visible then |
|
|
679
679
|
| `shouldFilter` (`Command`) | `boolean` | `true` | `false` when you filter the items yourself (server search) |
|
|
680
680
|
| `title` / `description` (`CommandDialog`) | `string` | `"Command Palette"` / `"Search for a command to run..."` (translated) | the dialog's title and description, inside the dialog and visually hidden |
|
|
681
|
+
| `mobilePresentation` (`CommandDialog`) | `'dialog' \| 'top' \| 'fullscreen'` | `'top'` | below 640 px: pinned under the top edge (so the input and the results stay above the on-screen keyboard), the whole screen, or the centred modal it was before |
|
|
681
682
|
| `open`, `onOpenChange` (`CommandDialog`) | as `Dialog` | — | `CommandDialog` takes every `Dialog` prop |
|
|
682
683
|
|
|
683
684
|
`CommandDialog` does not bind a keyboard shortcut; listen for ⌘K yourself and
|
|
@@ -526,6 +526,8 @@ import { Kbd, KbdShortcut } from '@nxgt/material/components';
|
|
|
526
526
|
| --- | --- | --- | --- |
|
|
527
527
|
| `keys` (`KbdShortcut`) | `string[]` | — (required) | one `Kbd` per key; `mod`, `shift` and `alt` become `⌘ ⇧ ⌥` on Apple platforms, `Ctrl Shift Alt` elsewhere |
|
|
528
528
|
|
|
529
|
+
Inside a filled `Button` the key, its border and the `+` between keys take the button's own text colour, so the shortcut keeps the contrast of the fill.
|
|
530
|
+
|
|
529
531
|
`Kbd` takes the props of `<kbd>`. `shortcutGlyph(key)` gives one glyph on its own.
|
|
530
532
|
|
|
531
533
|
**Storybook:** `Primitives/Kbd` · **Vue:** `Kbd`, `KbdShortcut`
|
|
@@ -328,6 +328,8 @@ when nothing matches. Options with a `group` are listed under its header
|
|
|
328
328
|
with their count, filtered within it (a group with no match goes); a
|
|
329
329
|
`description` is a muted line under the label, in the list only.
|
|
330
330
|
|
|
331
|
+
Once an option is picked, a round "Clear" button (`material.combobox.clear`, "Effacer" in French) appears at the end of the box, also in `ComboboxFormField`. It is a real button, named and exposed to assistive technology (`getByRole('button', { name: 'Clear' })`), described by the field's label, and kept out of the tab order like the other clearable fields: the keyboard clears with Backspace or by selecting the text and deleting it.
|
|
332
|
+
|
|
331
333
|
```tsx
|
|
332
334
|
import { ComboboxField } from '@nxgt/material/components';
|
|
333
335
|
import { useState } from 'react';
|
|
@@ -785,7 +787,7 @@ export function Topics() {
|
|
|
785
787
|
| `deselectable` | `boolean` | `false` | `single` only: pressing the chosen chip clears it |
|
|
786
788
|
| `max` / `min` | `number` | — | `multiple` only: `max` blocks the other chips once reached and speaks "{count} of {max} chosen"; `min` is a hint only |
|
|
787
789
|
| `options` | `{ value; label; icon?; count?; description?; disabled?; disabledReason? }[]` | — | the chips; `count`: see [option counts](#option-counts) |
|
|
788
|
-
| `size` | `'default' \| 'sm'` | `'default'` | the chip's size
|
|
790
|
+
| `size` | `'default' \| 'sm' \| 'lg'` | `'default'` | the chip's size: 28px, 24px, or `lg`, a 44px box at every pointer (a coarse pointer already gets a 44px box at any size) |
|
|
789
791
|
| `name` | `string` | — | put on every chip (`ChipGroupFormField` passes its field `name`), so `document.getElementsByName(name)` finds the group's chips in order |
|
|
790
792
|
| `label`, `helperText`, `error`, `required`, `disabled`, `id` | | — | the question and its help or error text, the required mark, disabling every chip, the group's `id` |
|
|
791
793
|
| `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | wrapping row or a column (each chip as wide as its label); in single mode it sets the `radiogroup`'s `aria-orientation` (ARIA allows none on a `group`), and every arrow key moves in both axes either way (the chips wrap) |
|
|
@@ -5,7 +5,10 @@ it and fetches the rows, the page shows the `Filter` bar with the same query,
|
|
|
5
5
|
and an edit in the bar writes the URL back. A shared link, a reload and the
|
|
6
6
|
browser's Back button all land on the same list.
|
|
7
7
|
|
|
8
|
-
It uses the URL codec
|
|
8
|
+
It uses the URL codec (`defineFilterModel`, `parseFilterQuery`,
|
|
9
|
+
`serializeFilterQuery`), which `@nxgt/material/components` re-exports (it is
|
|
10
|
+
also in `@nxgt/material-core/filter`, so the examples below work with either
|
|
11
|
+
import) and React Router's
|
|
9
12
|
framework mode. Any router works the same way: the codec takes and returns
|
|
10
13
|
`URLSearchParams`. If you render the list with
|
|
11
14
|
[`ServerTable`](../components/data-table-filter-and-lists.md#servertable), it
|
|
@@ -58,6 +61,12 @@ track and an integer beyond `Number.MAX_SAFE_INTEGER`, and it keeps the first
|
|
|
58
61
|
readable condition of a field. Whatever a
|
|
59
62
|
visitor types into the address bar, you get a valid `FilterQuery`.
|
|
60
63
|
|
|
64
|
+
A list condition with one unknown value (`?status=a&status=zzz`, a stale or
|
|
65
|
+
hand-edited link) is dropped whole. To keep the values it reads and drop only
|
|
66
|
+
the unknown one, pass `keepKnownValues: true`
|
|
67
|
+
(`parseFilterQuery(params, STUDENT_FILTERS, { locked, keepKnownValues: true })`);
|
|
68
|
+
a condition with no readable value left is still dropped.
|
|
69
|
+
|
|
61
70
|
```ts
|
|
62
71
|
// app/routes/students.tsx
|
|
63
72
|
import { effectiveConditions, parseFilterQuery } from '@nxgt/material-core/filter';
|
|
@@ -142,6 +151,11 @@ export default function Students({ loaderData }: Route.ComponentProps) {
|
|
|
142
151
|
re-encodes them (`f=level%3Ain%3ACE1%2CCE2`), which parses back the same. To
|
|
143
152
|
compare a URL you wrote with the one the router hands back, use
|
|
144
153
|
`paramsKey(a) === paramsKey(b)`.
|
|
154
|
+
- **Standard encoding.** If your list URLs are already written the
|
|
155
|
+
`URLSearchParams` way (`status=a%2Cb`), pass `encoding: 'standard'` to
|
|
156
|
+
`serializeFilterQuery` (and to `tableFilters` / `withoutFilters`): the params
|
|
157
|
+
returned are a plain `URLSearchParams`, so `toString()` encodes `:` and `,`.
|
|
158
|
+
Both forms parse back the same.
|
|
145
159
|
- **Dates and times** stay relative in the URL (`f=term:between:this-month`),
|
|
146
160
|
so a bookmark keeps meaning "this month".
|
|
147
161
|
|
package/docs/troubleshooting.md
CHANGED
|
@@ -120,6 +120,7 @@ words in your error.
|
|
|
120
120
|
- [The list loads again when the phone filter sheet closes](#the-list-loads-again-when-the-phone-filter-sheet-closes)
|
|
121
121
|
- [`FilterSheetTrigger must be used inside <Filter> or <ServerTable filter>`](#filtersheettrigger-must-be-used-inside-filter-or-servertable-filter)
|
|
122
122
|
- [A condition in the URL is missing from the filter](#a-condition-in-the-url-is-missing-from-the-filter)
|
|
123
|
+
- [The filter URL is not encoded like URLSearchParams](#the-filter-url-is-not-encoded-like-urlsearchparams)
|
|
123
124
|
- [A refused or reset edit stays in the filter bar](#a-refused-or-reset-edit-stays-in-the-filter-bar)
|
|
124
125
|
- [`defineFilterModel: field "level" cannot use "gt" on a select field`](#definefiltermodel-field-level-cannot-use-gt-on-a-select-field)
|
|
125
126
|
- [`@nxgt/material-core/filter: unknown time zone "…", using the runtime's`](#nxgtmaterial-corefilter-unknown-time-zone--using-the-runtimes)
|
|
@@ -1974,6 +1975,26 @@ a few frames after the alert has gone) and look it up by selector rather
|
|
|
1974
1975
|
than by role: the closing alert still hides the page from the accessibility
|
|
1975
1976
|
tree.
|
|
1976
1977
|
|
|
1978
|
+
### A `ConfirmDialog` keeps offering a refused action
|
|
1979
|
+
|
|
1980
|
+
When `onConfirm` throws, the dialog shows the error and the confirm button
|
|
1981
|
+
stays active, so the user can retry. If a retry cannot succeed (the item is
|
|
1982
|
+
still referenced), lock it:
|
|
1983
|
+
|
|
1984
|
+
```tsx
|
|
1985
|
+
<ConfirmDialog variant="destructive" lockOnRefusal onConfirm={remove} … />
|
|
1986
|
+
```
|
|
1987
|
+
|
|
1988
|
+
Cancel stays enabled. `confirmDisabled` is the switch for your own condition.
|
|
1989
|
+
|
|
1990
|
+
### Fields in a two-column row sit at different heights
|
|
1991
|
+
|
|
1992
|
+
A field is a grid of a label and a control. In earlier versions its rows stretched when a
|
|
1993
|
+
neighbour in the same row was taller (a helper or error text under it), so the
|
|
1994
|
+
shorter field's control sank by half the difference. Fields now keep their rows
|
|
1995
|
+
at the top (`content-start`); upgrade, and drop any `mt-*` or `items-end`
|
|
1996
|
+
workaround. A `DateField`'s tonal fill is deliberate and not a misalignment.
|
|
1997
|
+
|
|
1977
1998
|
## Navigation
|
|
1978
1999
|
|
|
1979
2000
|
### A `ListTile` link shows two chevrons
|
|
@@ -2233,6 +2254,26 @@ const query = parseFilterQuery(params, STUDENT_FILTERS, { locked: LOCKED });
|
|
|
2233
2254
|
const asWritten = parseFilterQuery(params, STUDENT_FILTERS);
|
|
2234
2255
|
```
|
|
2235
2256
|
|
|
2257
|
+
A list condition (`in`, a `repeat` param) with one unknown value
|
|
2258
|
+
(`?status=a&status=zzz`) is dropped whole by default. To keep `a` and drop only
|
|
2259
|
+
`zzz`, parse with `{ keepKnownValues: true }`.
|
|
2260
|
+
|
|
2261
|
+
### The filter URL is not encoded like URLSearchParams
|
|
2262
|
+
|
|
2263
|
+
**When:** links or tests of a list that already published its URLs with
|
|
2264
|
+
`URLSearchParams` (`status=a%2Cb`) now differ in text from what
|
|
2265
|
+
`serializeFilterQuery(...).toString()` or `tableFilters(...).withoutFilters(...)`
|
|
2266
|
+
writes.
|
|
2267
|
+
**Why:** their params leave `:` and `,` raw (legal in a query) so the URL reads
|
|
2268
|
+
well; `URLSearchParams` encodes them. Both parse back the same.
|
|
2269
|
+
**Fix:** pass `encoding: 'standard'` (to `serializeFilterQuery`, or in the
|
|
2270
|
+
options of `withoutFilters` / `tableFilters`) for a plain `URLSearchParams`:
|
|
2271
|
+
|
|
2272
|
+
```ts
|
|
2273
|
+
serializeFilterQuery(query, MODEL, { encoding: 'standard' }).toString();
|
|
2274
|
+
// status=a&status=b&f=level%3Ain%3ACE1%2CCE2
|
|
2275
|
+
```
|
|
2276
|
+
|
|
2236
2277
|
### `?archived=0` now shows every row
|
|
2237
2278
|
|
|
2238
2279
|
**When:** a stored link or bookmark with a `style: 'flag'` alias at `0`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* Chip: what the three looks share (data-look hairline, soft, tag). Sizes:
|
|
2
|
-
* default 28px, sm 24px (both at least the 24px target of WCAG 2.5.8)
|
|
3
|
-
* label truncates as one piece; the ✕ is drawn small, its target is 24×24
|
|
2
|
+
* default 28px, sm 24px (both at least the 24px target of WCAG 2.5.8), lg
|
|
3
|
+
* 44px (the touch target). The label truncates as one piece; the ✕ is drawn small, its target is 24×24
|
|
4
4
|
* through ::after, and it has its own focus ring. */
|
|
5
5
|
@layer components {
|
|
6
6
|
.nxgt-chip {
|
|
@@ -50,6 +50,16 @@
|
|
|
50
50
|
--chip-gap: 0.25rem;
|
|
51
51
|
--chip-x: 1rem;
|
|
52
52
|
}
|
|
53
|
+
/* lg: a 44px box at every pointer (the touch target, #1529). */
|
|
54
|
+
.nxgt-chip[data-size="lg"] {
|
|
55
|
+
--chip-h: 2.75rem;
|
|
56
|
+
--chip-px: 1rem;
|
|
57
|
+
--chip-font: 0.9375rem;
|
|
58
|
+
--chip-icon: 1rem;
|
|
59
|
+
--chip-gap: 0.375rem;
|
|
60
|
+
--chip-x: 1.5rem;
|
|
61
|
+
min-width: 2.75rem;
|
|
62
|
+
}
|
|
53
63
|
button.nxgt-chip,
|
|
54
64
|
.nxgt-chip > button[data-slot="chip-body"] {
|
|
55
65
|
cursor: pointer;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nxgt/material",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.15.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"bugs": {
|
|
6
6
|
"email": "stevegires@gmail.com"
|
|
@@ -357,7 +357,7 @@
|
|
|
357
357
|
"docs:previews": "rm -rf node_modules/.cache/nxgt-preview-free/missing && NXGT_PREVIEW_FREE=1 vitest run --project previews && bun .storybook/preview-free-report.ts"
|
|
358
358
|
},
|
|
359
359
|
"dependencies": {
|
|
360
|
-
"@nxgt/material-core": "^0.
|
|
360
|
+
"@nxgt/material-core": "^0.31.0",
|
|
361
361
|
"@base-ui/react": "^1.6.0",
|
|
362
362
|
"@dnd-kit/core": "^6.3.1",
|
|
363
363
|
"@dnd-kit/sortable": "^10.0.0",
|