@nxgt/material 3.27.0 → 3.29.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 (102) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +1 -1
  3. package/dist/{async-slice-gAbGnQT8.js → async-slice-DmhasXmq.js} +2 -2
  4. package/dist/{button-Cr9GIIj4.js → button-BjIg_Lz6.js} +1 -1
  5. package/dist/charts.js +3 -3
  6. package/dist/{chip-s11lOfxB.js → chip-DIBIDUZH.js} +1 -1
  7. package/dist/{code-editor-DGIjL8Hr.js → code-editor-2EU-q9FV.js} +4 -4
  8. package/dist/code-editor.js +4 -4
  9. package/dist/{code-language-items-DCOQnaCo.js → code-language-items-C_UeMQMm.js} +2 -2
  10. package/dist/components/ui/display/chip/chip-parts.css +19 -0
  11. package/dist/{components-DgpD3FJo.js → components-CujnhLIR.js} +12164 -11640
  12. package/dist/components.js +26 -26
  13. package/dist/{dialog-Crvg-MN5.js → dialog-LtXwv6N3.js} +1 -1
  14. package/dist/dnd.js +1 -1
  15. package/dist/{dropdown-menu-B71aggp0.js → dropdown-menu-ppMPD12c.js} +1 -1
  16. package/dist/editor.js +7 -7
  17. package/dist/{empty-state-8LddxMU4.js → empty-state-X_nikpBS.js} +1 -1
  18. package/dist/hooks.js +2 -2
  19. package/dist/{i18n-provider-BDi9-6DO.js → i18n-provider-BfyDgd2o.js} +1 -1
  20. package/dist/i18n.js +5 -5
  21. package/dist/{icon-badge-DG7wLnHh.js → icon-badge-oLBh3Mlk.js} +1 -1
  22. package/dist/lib/components/ui/collections/facet-group/facet-group.d.ts +1 -1
  23. package/dist/lib/components/ui/collections/facet-group/types.d.ts +2 -0
  24. package/dist/lib/components/ui/collections/filter/bar/filter-bar-classes.d.ts +16 -1
  25. package/dist/lib/components/ui/collections/filter/bar/{filter-bar.d.ts → filter-bar-view.d.ts} +2 -2
  26. package/dist/lib/components/ui/collections/filter/bar/partials/filter-clear-all.d.ts +2 -2
  27. package/dist/lib/components/ui/collections/filter/bar/partials/filter-quick-pill.d.ts +4 -1
  28. package/dist/lib/components/ui/collections/filter/bar/partials/filter-quick-toggle.d.ts +3 -0
  29. package/dist/lib/components/ui/collections/filter/bar/partials/filter-scope-list.d.ts +6 -0
  30. package/dist/lib/components/ui/collections/filter/bar/partials/filter-scope-pill.d.ts +13 -0
  31. package/dist/lib/components/ui/collections/filter/bar/partials/use-quick-condition.d.ts +19 -0
  32. package/dist/lib/components/ui/collections/filter/bar/partials/use-quick-pill.d.ts +30 -0
  33. package/dist/lib/components/ui/collections/filter/bar/use-filter-bar-wiring.d.ts +5 -0
  34. package/dist/lib/components/ui/collections/filter/editors/boolean-editor.d.ts +3 -2
  35. package/dist/lib/components/ui/collections/filter/editors/checkbox-editor.d.ts +1 -1
  36. package/dist/lib/components/ui/collections/filter/editors/switch-editor.d.ts +1 -1
  37. package/dist/lib/components/ui/collections/filter/facets/filter-facets.d.ts +2 -2
  38. package/dist/lib/components/ui/collections/filter/filter-bar-url.d.ts +51 -0
  39. package/dist/lib/components/ui/collections/filter/index.d.ts +2 -0
  40. package/dist/lib/components/ui/collections/filter/internal.d.ts +4 -1
  41. package/dist/lib/components/ui/collections/filter/query/filter-results-status.d.ts +5 -1
  42. package/dist/lib/components/ui/collections/filter/query/types.d.ts +37 -3
  43. package/dist/lib/components/ui/collections/{server-table → filter/query}/use-answer-wait.d.ts +3 -1
  44. package/dist/lib/components/ui/collections/filter/query/use-apply-now.d.ts +6 -0
  45. package/dist/lib/components/ui/collections/filter/query/use-defaults.d.ts +15 -0
  46. package/dist/lib/components/ui/collections/filter/query/use-field-error.d.ts +5 -0
  47. package/dist/lib/components/ui/collections/filter/query/use-filter-query-options.d.ts +16 -0
  48. package/dist/lib/components/ui/collections/filter/query/use-filter-query-value.d.ts +4 -0
  49. package/dist/lib/components/ui/collections/filter/query/use-filter-query.d.ts +11 -3
  50. package/dist/lib/components/ui/collections/filter/query/use-filter-url-query.d.ts +52 -0
  51. package/dist/lib/components/ui/collections/filter/query/use-refusal-announcement.d.ts +10 -0
  52. package/dist/lib/components/ui/collections/filter/sections/chips/condition-panel.d.ts +2 -1
  53. package/dist/lib/components/ui/collections/filter/sections/chips/filter-chip-body.d.ts +4 -2
  54. package/dist/lib/components/ui/collections/filter/sections/chips/filter-chip-list.d.ts +3 -2
  55. package/dist/lib/components/ui/collections/filter/sections/chips/filter-default-chip.d.ts +14 -0
  56. package/dist/lib/components/ui/collections/filter/sections/chips/filter-default-group.d.ts +16 -0
  57. package/dist/lib/components/ui/collections/filter/sections/chips/filter-default-parts.d.ts +12 -0
  58. package/dist/lib/components/ui/collections/filter/sections/chips/filter-error-parts.d.ts +22 -0
  59. package/dist/lib/components/ui/collections/filter/sections/chips/use-control-description.d.ts +13 -0
  60. package/dist/lib/components/ui/collections/filter/sections/filter-facet-section.d.ts +5 -0
  61. package/dist/lib/components/ui/collections/filter/sections/filter-form-error.d.ts +8 -0
  62. package/dist/lib/components/ui/collections/filter/sections/filter-search-error.d.ts +9 -0
  63. package/dist/lib/components/ui/collections/filter/sections/filter-search.d.ts +4 -3
  64. package/dist/lib/components/ui/collections/filter/sections/filter-sections-classes.d.ts +8 -0
  65. package/dist/lib/components/ui/collections/filter/sections/renderers/choice-facet.d.ts +1 -1
  66. package/dist/lib/components/ui/collections/filter/sections/use-filter-shortcuts.d.ts +4 -3
  67. package/dist/lib/components/ui/collections/server-table/partials/server-table-empty-chips.d.ts +1 -1
  68. package/dist/lib/components/ui/collections/server-table/types.d.ts +24 -1
  69. package/dist/lib/components/ui/collections/server-table/use-filter-params.d.ts +5 -4
  70. package/dist/lib/components/ui/collections/server-table/use-table-answer.d.ts +1 -1
  71. package/dist/lib/i18n/resources/en/en.d.ts +11 -0
  72. package/dist/lib/i18n/resources/en/material.json.d.ts +11 -0
  73. package/dist/lib/i18n/resources/fr/fr.d.ts +11 -0
  74. package/dist/lib/i18n/resources/fr/material.json.d.ts +11 -0
  75. package/dist/lib/i18n/resources/resources.d.ts +22 -0
  76. package/dist/lib/i18n/translator.d.ts +1 -1
  77. package/dist/lib.js +1 -1
  78. package/dist/main.js +43 -43
  79. package/dist/map.js +4 -4
  80. package/dist/material.css +1 -1
  81. package/dist/pdf-reader.js +1 -1
  82. package/dist/print.js +2 -2
  83. package/dist/{public-QUiFLEoj.js → public-BeM7-x-m.js} +6 -6
  84. package/dist/{rich-code-block-CjD54THr.js → rich-code-block-B4TIjZRA.js} +4 -4
  85. package/dist/{sortable-provider-DYnyzfgR.js → sortable-provider-Dcsw0pgW.js} +1 -1
  86. package/dist/{spinner-CJtTRNAX.js → spinner-1Ia6Akhy.js} +1 -1
  87. package/dist/{stored-language-ClkfkueV.js → stored-language-DoWMr969.js} +22 -0
  88. package/dist/theme-picker.js +2 -2
  89. package/dist/{theme-toggle-ufQL3SO6.js → theme-toggle-GlKtdgYz.js} +1 -1
  90. package/dist/{toolbar-items-DlioFgpM.js → toolbar-items-C1GdN9P9.js} +2 -2
  91. package/dist/{translate-material-O-uasx3u.js → translate-material-BneuBHXZ.js} +2 -2
  92. package/dist/{translator-C0uxN1Nh.js → translator-D88BbpXZ.js} +1 -1
  93. package/dist/{use-chat-composer-DzVO5mwz.js → use-chat-composer-beyfZPBD.js} +12 -12
  94. package/dist/{use-heading-spy-BHCVdJfr.js → use-heading-spy-BN0yeBod.js} +1 -1
  95. package/dist/{use-material-translation-B4Frk3vC.js → use-material-translation-Cqcwkuur.js} +2 -2
  96. package/docs/components/data-table-filter-and-lists.md +226 -3
  97. package/docs/guides/i18n.md +7 -0
  98. package/docs/guides/server-lists-url.md +114 -1
  99. package/docs/roadmap.md +16 -0
  100. package/docs/troubleshooting.md +80 -3
  101. package/lib/components/ui/display/chip/chip-parts.css +19 -0
  102. package/package.json +2 -2
@@ -48,7 +48,9 @@ field that declares `param` and `style` (`'repeat'`, `'single'` or `'flag'`),
48
48
  the app's own param, so an older link such as `?archived=1` keeps working.
49
49
  A flag alias at `0` (`?archived=0`) means off: it is no condition, so the link
50
50
  shows every row. To filter on "No", write `f=archived:is:0`, which the
51
- serializer does for you.
51
+ serializer does for you. A field declared `off: 'remove'` (a narrowing switch
52
+ such as "Only unassigned") has no "No": its `:is:0` and `=0` are both
53
+ dropped, and the serializer never writes them.
52
54
  A range is written `a..b` with either end open (`6..`, `..8`).
53
55
 
54
56
  ## Parse it in the loader
@@ -171,6 +173,107 @@ export default function Students({ loaderData }: Route.ComponentProps) {
171
173
  - **Dates and times** stay relative in the URL (`f=term:between:this-month`),
172
174
  so a bookmark keeps meaning "this month".
173
175
 
176
+ ## A list that is no table: `FilterBar`
177
+
178
+ A feed or a card list has no `ServerTable` to bind the URL for it. `FilterBar`
179
+ does the same: pass it the router's search params and a setter, and it reads
180
+ and writes the query as above (the codec options, `reset` for the cursor, the
181
+ page's other params kept):
182
+
183
+ ```tsx
184
+ import { FilterBar } from '@nxgt/material/components';
185
+ import { useSearchParams } from 'react-router';
186
+
187
+ const [params, setParams] = useSearchParams();
188
+ <FilterBar
189
+ model={FEED_FILTERS}
190
+ params={params}
191
+ onParamsChange={(next) => setParams(next, { replace: true })}
192
+ codec={{ keepKnownValues: true }} // the loader's own options
193
+ reset={['after']}
194
+ resultCount={loaderData.total}
195
+ loading={loading}
196
+ />
197
+ ```
198
+
199
+ `useFilterUrlQuery` is the same binding as a hook, for a page that places the
200
+ `Filter` itself (it takes a `UseFilterUrlQueryOptions` and returns a
201
+ `FilterUrlQuery`; `FilterBarProps` and `FilterUrlCodec` name the bar's props
202
+ and the codec options). `ServerTable`'s `filter` uses it, so the two cannot
203
+ drift.
204
+
205
+ ## A default the loader computes
206
+
207
+ Some views start narrowed: "Mine only" on for some users, a list showing
208
+ `active` and `upcoming` while nothing is picked. Declare it on the field, so an
209
+ absent param means it, and the URL still holds the user's explicit choices only:
210
+
211
+ ```ts
212
+ const FILTERS = defineFilterModel({
213
+ fields: [
214
+ { id: 'status', type: 'multi-select', label: 'Status', options,
215
+ default: ['active', 'upcoming'] },
216
+ // The page decides per request: `defaults` passes the value.
217
+ { id: 'mine', type: 'switch', label: 'Mine only', default: 'page' },
218
+ ],
219
+ });
220
+
221
+ export async function loader({ request, context }: Route.LoaderArgs) {
222
+ const query = parseFilterQuery(new URL(request.url).searchParams, FILTERS);
223
+ const defaults = { mine: context.user.prefersMine ? ['1'] : [] };
224
+ const conditions = effectiveConditions(query, FILTERS, LOCKED, { defaults });
225
+ return api.students({ search: query.search, conditions });
226
+ }
227
+ ```
228
+
229
+ Order is locked, then the user's, then defaulted. `resolveDefaults(query,
230
+ FILTERS, defaults, LOCKED)` also names the fields it defaulted. A default is
231
+ never in `query`, so `isFiltered`, `withoutFilters` and "Clear all" are
232
+ unchanged, and "Clear all" returns the field to its default. Choosing a value
233
+ replaces the default, also a value equal to it, which stays in the URL as
234
+ written. An explicit off of a default-bearing switch is `f=mine:is:0`, or
235
+ `mine=0` for a `style: 'flag'` field; on a field without `default` that `0`
236
+ keeps its old meaning. A link shared between users with different `'page'`
237
+ defaults renders per viewer, by design. A `'page'` field the page gives no value
238
+ for has no default, yet `mine=0` still reads as an explicit off for it. Pass the same `defaults` (and `locked`)
239
+ to `matchQuery` and `valueCounts` in client mode.
240
+
241
+ The page shows the default too: pass the same `defaults` to `ServerTable`'s
242
+ `filter` (`filter={{ model, defaults }}`), `FilterBar` or `Filter`. A defaulted
243
+ value is shown but uncounted, with no ✕ and no announced count, and never
244
+ written to the URL:
245
+
246
+ - a select or multi-select reads as a dashed hint chip, "Status: Active,
247
+ Upcoming · Default". It is a button named "Status: Active, Upcoming, default
248
+ value. Change": it opens the field's editor on the default, like an applied
249
+ chip, and has no ✕. A quick pill reads the same;
250
+ - a switch or checkbox quick toggle is pressed, marked "Default"; pressing it
251
+ writes the explicit off (`mine=0`), which is then counted as a "No" chip;
252
+ - the facets panel and the phone sheet show the default values checked, with
253
+ "Default" beside the field's title. Unchecking or changing one writes an
254
+ explicit choice (counted, in the URL); "Clear all" goes back to the checked
255
+ defaults. The section starts open;
256
+ - "Filters (N)" and the announced count leave defaults out, and "Clear all"
257
+ (which shows from two filters) returns every field to its default.
258
+
259
+ `defaults` must be the loader's own object, or the chips and the rows disagree.
260
+
261
+ ## A scope, a refusal and a list without a search
262
+
263
+ - **A scope** (`scope: true` with a `default`, `'page'` for "the current
264
+ period") is the value a list is scoped by and the user changes but never
265
+ clears. The loader treats it as any condition (it is in `query`, and in
266
+ `effectiveConditions`), but `isFiltered`, `withoutFilters` and the bar's
267
+ count and "Clear all" leave it out: `/students?f=period:is:p1&q=ada` resets
268
+ to `?f=period:is:p1`. An invalid value means the default.
269
+ - **A refusal.** When the loader refuses a value, return the message per
270
+ field and pass it as `errors` (`filter={{ model, errors }}`): the chip reads
271
+ "Invalid", described by the message, and the editor and the live region
272
+ show it. Clear it once the user changes the condition.
273
+ - **`search: false`** in `defineFilterModel` drops the search box and frees the
274
+ `q` param: `parseFilterQuery` returns `search: ''` and
275
+ `serializeFilterQuery` never writes it.
276
+
174
277
  ## Client mode
175
278
 
176
279
  When all the rows are already in the page, skip the loader's fetch and filter
@@ -198,5 +301,15 @@ page, or the hydrating page renders another count.
198
301
  `FILTER_MAX_VALUES` or `FILTER_MAX_VALUE_LENGTH`, a slider value off its
199
302
  track or an unsafe integer is dropped silently: see
200
303
  [a condition in the URL is missing from the filter](../troubleshooting.md#a-condition-in-the-url-is-missing-from-the-filter).
304
+ - A field you switch to `off: 'remove'` stops reading its old `f=x:is:0`
305
+ links as "No": they are dropped silently, so a shared link that meant
306
+ "not x" now shows every row. Leave `off` out when "No" matters.
307
+ - A `default: 'page'` field reads its values from `defaults`: give the loader and
308
+ the page the same record, or the counts and the chips disagree. Compute a
309
+ period-style default in the loader and pass it as a string, never
310
+ `new Date()` in render (a hydration mismatch).
311
+ - Adding `default` to a field changes its `x=0` and `f=x:is:0` links from "no
312
+ condition" (or dropped) to an explicit off, and an absent param from "every
313
+ row" to the default.
201
314
  - Parse with the same `locked` list in the loader and in the page, or the two
202
315
  read different queries.
package/docs/roadmap.md CHANGED
@@ -330,6 +330,22 @@ version they shipped in, and planned entries may name the major they target.
330
330
 
331
331
  ## Shipped
332
332
 
333
+ - **One filter model: defaults, a scope, a refusal, `FilterBar`** — a filter
334
+ field says what an absent value means and what role it plays, so a list can
335
+ start narrowed and a page can keep a value the user never clears.
336
+ `default` (values, or `'page'` with `defaults` from the loader) is shown but
337
+ never counted, written to the URL or removed by "Clear all": a hint chip, a
338
+ pressed toggle, or checked values tagged "Default". `scope: true` makes a
339
+ field such as the period a leading pill that "Clear all" and `withoutFilters`
340
+ keep. `off: 'remove'` turns a switch into a plain "only when on" filter.
341
+ `errors` shows a server's refusal on the control, and `search: false` drops
342
+ the search box. `FilterBar` and `useFilterUrlQuery` bind the bar to the URL
343
+ for a feed or a list that is no table, the same binding `ServerTable` uses.
344
+ React only for now; the Vue gap is tracked in issue #1569.
345
+ [Filter bar](components/data-table-filter-and-lists.md#filter-bar) ·
346
+ [A field's default](components/data-table-filter-and-lists.md#a-fields-default) ·
347
+ [A scope field](components/data-table-filter-and-lists.md#a-scope-field) ·
348
+ [Server lists in the URL](guides/server-lists-url.md)
333
349
  - **Display polish** — `Badge` is a status badge: a 20 px pill with the
334
350
  `label-sm` text and no hover, a `soft` variant (the tone's container under
335
351
  the tone's text, 4.5:1) and a `tone` prop for `soft` and `outlined`, taking
@@ -131,6 +131,10 @@ words in your error.
131
131
  - [The filter URL is not encoded like URLSearchParams](#the-filter-url-is-not-encoded-like-urlsearchparams)
132
132
  - [A refused or reset edit stays in the filter bar](#a-refused-or-reset-edit-stays-in-the-filter-bar)
133
133
  - [`defineFilterModel: field "level" cannot use "gt" on a select field`](#definefiltermodel-field-level-cannot-use-gt-on-a-select-field)
134
+ - [`defineFilterModel: field "x" can only set addable to false`](https://cdn.jsdelivr.net/npm/@nxgt/material-core@0/docs/troubleshooting.md#definefiltermodel-field-x-can-only-set-addable-to-false)
135
+ - [`defineFilterModel: field "x" default …` and `off …`](#definefiltermodel-field-x-default--and-off-)
136
+ - [`defineFilterModel: field "period" scope needs a default operator taking one value`](#definefiltermodel-field-period-scope-needs-a-default-operator-taking-one-value)
137
+ - [`errors` shows nothing on the filter bar](#errors-shows-nothing-on-the-filter-bar)
134
138
  - [`@nxgt/material-core/filter: unknown time zone "…", using the runtime's`](#nxgtmaterial-corefilter-unknown-time-zone--using-the-runtimes)
135
139
  - [``Filter: the autocomplete field(s) … have neither `options` nor a `search` in `fieldProps`: their editor has nothing to offer.``](#filter-the-autocomplete-fields--have-neither-options-nor-a-search-in-fieldprops-their-editor-has-nothing-to-offer)
136
140
  - [`Filter bar parts must be used inside <Filter variant="bar">`](#filter-bar-parts-must-be-used-inside-filter-variantbar)
@@ -2471,8 +2475,11 @@ other messages say `must match [A-Za-z0-9_-]+` (a field id is written in the
2471
2475
  URL), `is declared twice`, `has an unknown type`, `declares an operator twice`,
2472
2476
  `uses the param "<p>" of the search` (two fields, or a field and the search,
2473
2477
  share a URL param), `can only use style "flag" as a switch or checkbox`,
2474
- `needs a default operator taking several values for style "repeat"`, `needs a default operator taking one value for style "single"`, and, for a
2475
- search param, `f` is reserved for the conditions.
2478
+ `needs a default operator taking several values for style "repeat"`, `needs a default operator taking one value for style "single"`, the scope
2479
+ checks (`scope needs a default`, `scope cannot be combined with widens`,
2480
+ `scope needs a single-value type`, `scope default must hold exactly one
2481
+ value`, `can only set scope to true`), and, for a search param, `f` is
2482
+ reserved for the conditions (a model with `search: false` has none).
2476
2483
  **Fix:** use the operators the type allows: numbers and sliders compare with
2477
2484
  `gt`, `lt`, `gte`, `lte`, dates and times with `before` and `after`; the
2478
2485
  first operator you list is the default.
@@ -2481,6 +2488,75 @@ first operator you list is the default.
2481
2488
  { id: 'age', type: 'number', label: 'Age', operators: ['gte', 'lte', 'between'] }
2482
2489
  ```
2483
2490
 
2491
+ ### `defineFilterModel: field "x" default …` and `off …`
2492
+
2493
+ **When:** the module that calls `defineFilterModel` is imported, with a
2494
+ `default` that is not an array of strings or `"page"`, repeats or does not
2495
+ read a value, is off on a field declared `off: 'remove'`, or sits on a
2496
+ `widens` field, or with an `off` that is not `'remove'` or `'no'`, is on
2497
+ something other than a switch or checkbox, or is `'no'` on a `widens` field.
2498
+ **Why:** a default is applied as the field's condition, so it must be one the
2499
+ URL could hold; and `off` only says what off means for a boolean.
2500
+ **Fix:** see the exact messages and fixes in the core page:
2501
+ [`default …`](https://cdn.jsdelivr.net/npm/@nxgt/material-core@0/docs/troubleshooting.md#definefiltermodel-field-x-default-) and
2502
+ [`off …`](https://cdn.jsdelivr.net/npm/@nxgt/material-core@0/docs/troubleshooting.md#definefiltermodel-field-x-widens-needs-off-remove-and-the-other-off-errors).
2503
+
2504
+ ### `defineFilterModel: field "period" scope needs a default operator taking one value`
2505
+
2506
+ **When:** the module that calls `defineFilterModel` is imported, with a
2507
+ `scope: true` field whose default operator (the first of its `operators`)
2508
+ takes several values, such as `in` on a `select`.
2509
+ **Why:** a scope is one value the list is scoped by, shown in a pill
2510
+ ("Period: P2"), so its default operator (the first of `operators`, or the
2511
+ type's own) must read exactly one value. The other scope checks name their
2512
+ own rule: `scope needs a default`, `scope needs a single-value type`,
2513
+ `scope default must hold exactly one value`, `scope cannot be combined with
2514
+ widens`.
2515
+ **Fix:** list a one-value operator first, or use a single-value type
2516
+ (`select`, `radio-group`, `chips-single`):
2517
+
2518
+ ```ts
2519
+ { id: 'period', type: 'select', label: 'Period', options: PERIODS,
2520
+ operators: ['is'], scope: true, default: ['p2'] }
2521
+ ```
2522
+
2523
+ ### `errors` shows nothing on the filter bar
2524
+
2525
+ **When:** a page passes `errors={{ period: 'Too long' }}` (to `Filter`,
2526
+ `FilterBar` or `ServerTable`'s `filter`) and the field's chip, pill or toggle
2527
+ keeps its normal look, with no "Invalid" and nothing announced. Nothing is
2528
+ printed.
2529
+ **Why:** only a field of the model that the page does not lock shows an error:
2530
+ a key that is not a field id (a typo, the search param, an alias), an empty
2531
+ message, or a field in `locked` is left out, since a locked field shows its
2532
+ lock only.
2533
+ **Fix:** key `errors` by the field's `id`, give a non-empty message, and keep
2534
+ the field out of `locked`. `fieldErrors(errors, model, locked)` from
2535
+ `@nxgt/material-core/filter` lists what the bar will show:
2536
+
2537
+ ```tsx
2538
+ <ServerTable filter={{ model, locked, errors: { period: 'The period cannot exceed 366 days.' } }} {...table} />
2539
+ ```
2540
+
2541
+ ### A refused search or a refusal of no field shows nothing
2542
+
2543
+ **When:** the server refuses the typed search, or refuses with a message that
2544
+ names no field, and the page passes it as `errors={{ item: 'Not a valid item' }}`
2545
+ (the search param as a key) or `errors={{ form: '…' }}`: the search box looks
2546
+ normal and no Alert shows. Nothing is printed.
2547
+ **Why:** `errors` only reads field ids. The search box and a refusal of no field
2548
+ have their own props, `searchError` and `formError`.
2549
+ **Fix:** pass them as props, or split one flat record with `splitFilterErrors`
2550
+ from `@nxgt/material-core/filter` (the key equal to `model.search.param` is the
2551
+ search, `'$form'` is the form). A model with `search: false` shows no
2552
+ `searchError`, and an empty message shows nothing. The page must drop them when
2553
+ the search changes:
2554
+
2555
+ ```tsx
2556
+ <FilterBar model={model} params={params} onParamsChange={setParams}
2557
+ {...splitFilterErrors({ item: 'Not a valid item.', $form: 'Try again later.' }, model)} />
2558
+ ```
2559
+
2484
2560
  ### `@nxgt/material-core/filter: unknown time zone "…", using the runtime's`
2485
2561
 
2486
2562
  **When:** in the console, once, when a date preset (`this-month`) is matched or
@@ -2509,7 +2585,8 @@ const conditions = resolveDateConditions(effectiveConditions(query, model, locke
2509
2585
  neither, it has nothing to offer. The functions live in `fieldProps`, never in
2510
2586
  the model, so the loader can share it.
2511
2587
  **Fix:** give the field `options`, or a `search` (and `resolveLabels`, for the
2512
- ids a link brings) by field id:
2588
+ ids a link brings) by field id. A field only a link sets (`addable: false`)
2589
+ is never offered for picking and needs neither, so it raises no warning:
2513
2590
 
2514
2591
  ```tsx
2515
2592
  const fieldProps: FilterFieldProps = {
@@ -82,6 +82,25 @@
82
82
  background: color-mix(in oklab, var(--foreground) 4%, transparent);
83
83
  }
84
84
 
85
+ /* A dashed chip in the error tone (a refused filter): the line and the
86
+ * text take the tone, dashed or selected, hover and focus included. */
87
+ .nxgt-chip[data-dashed][data-tone="error"] {
88
+ --chip-tone: var(--error-text);
89
+ border-color: color-mix(in oklab, var(--error-text) 60%, var(--background));
90
+ color: var(--error-text);
91
+ }
92
+ .nxgt-chip[data-dashed][data-tone="error"]:not([data-selected]):is(
93
+ :hover,
94
+ :focus-visible
95
+ ) {
96
+ color: var(--error-text);
97
+ border-color: var(--error-text);
98
+ background: color-mix(in oklab, var(--error-text) 6%, transparent);
99
+ }
100
+ .nxgt-chip[data-dashed][data-tone="error"][data-selected] {
101
+ background: color-mix(in oklab, var(--error-text) 8%, var(--background));
102
+ }
103
+
85
104
  .nxgt-chip[data-tone="neutral"] {
86
105
  --chip-tone: var(--muted-foreground);
87
106
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxgt/material",
3
- "version": "3.27.0",
3
+ "version": "3.29.0",
4
4
  "license": "MIT",
5
5
  "bugs": {
6
6
  "email": "stevegires@gmail.com"
@@ -362,7 +362,7 @@
362
362
  "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"
363
363
  },
364
364
  "dependencies": {
365
- "@nxgt/material-core": "^0.35.0",
365
+ "@nxgt/material-core": "^0.37.0",
366
366
  "@base-ui/react": "^1.6.0",
367
367
  "@dnd-kit/core": "^6.3.1",
368
368
  "@dnd-kit/sortable": "^10.0.0",