@nxgt/material 3.101.0 → 3.104.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 +82 -0
- package/README.md +37 -5
- package/dist/{alert-dialog-DYzxchiS.js → alert-dialog-CK_47jfm.js} +1 -1
- package/dist/{async-slice-drJyPvuM.js → async-slice-1ZNMiwyJ.js} +1 -1
- package/dist/{bubble-input-CQwVSoU3.js → bubble-input-BxI1WmNs.js} +17 -5
- package/dist/{button-GeIsUekU.js → button-YfEMZbRO.js} +2 -2
- package/dist/{calculator-BS4PAm6l.js → calculator-BmYZId4c.js} +261 -460
- package/dist/calculator-field.js +7 -7
- package/dist/{calculator-segment-CEdwE24M.js → calculator-segment-DJ08DJ-n.js} +1 -1
- package/dist/calculator.js +1 -1
- package/dist/charts.js +819 -764
- package/dist/{chip-BdEtFQ85.js → chip-DPj6e-3v.js} +2 -2
- package/dist/{code-editor-BByXZOGy.js → code-editor-CZg3nLZO.js} +178 -177
- package/dist/code-editor.js +5 -5
- package/dist/{code-language-items-CSZX5zxD.js → code-language-items-Nq1iOGId.js} +14 -5
- package/dist/components/ui/actions/action-card/action-card-tile.css +47 -0
- package/dist/components/ui/display/chip/chip-soft.css +27 -2
- package/dist/components/ui/display/code/code-moonlight.css +10 -10
- package/dist/components/ui/primitives/collapsible/collapsible.css +9 -0
- package/dist/{components-DQ4MljCr.js → components-B2wrH_W0.js} +19302 -18583
- package/dist/components.js +38 -37
- package/dist/{conversion-variant-DTQqAyDt.js → conversion-variant-C-LPx5ek.js} +5 -5
- package/dist/{currency-field-Dnju7ss2.js → currency-field-DnsRIA80.js} +24 -23
- package/dist/{dialog-5_2__tu8.js → dialog-BKBsDGFc.js} +3 -3
- package/dist/dnd.js +1 -1
- package/dist/{dropdown-menu-FdGC5kIP.js → dropdown-menu-QMDbRidH.js} +1 -1
- package/dist/editor.js +9 -9
- package/dist/{empty-state-BoepPG5v.js → empty-state-BLIJ6znY.js} +1 -1
- package/dist/{field-blur-DkiZOVNV.js → field-blur-DMJwPLsg.js} +9 -9
- package/dist/field-blur-context-3rKussBQ.js +29 -0
- package/dist/{financial-variant-BhqJp5C-.js → financial-variant-Ws0Jp01H.js} +5 -5
- package/dist/{hooks-DjcbJZiO.js → hooks-C3vkQmzh.js} +106 -61
- package/dist/hooks.js +3 -3
- package/dist/{hover-card-CmHJeBeY.js → hover-card-Df649U6I.js} +1 -1
- package/dist/i18n.js +3 -3
- package/dist/{icon-badge-LGUPQh_c.js → icon-badge-Cma1qOOJ.js} +1 -1
- package/dist/{icon-button-BSVpO1jP.js → icon-button-CpKxV264.js} +1 -1
- package/dist/key-layout-D2goCDkD.js +31 -0
- package/dist/keypad.js +551 -0
- package/dist/label-DR-zZyGQ.js +47 -0
- package/dist/lib/components/ui/actions/index.d.ts +1 -0
- package/dist/lib/components/ui/actions/product-tile/index.d.ts +1 -0
- package/dist/lib/components/ui/actions/product-tile/product-tile-link.d.ts +16 -0
- package/dist/lib/components/ui/actions/product-tile/product-tile.d.ts +41 -0
- package/dist/lib/components/ui/collections/data-table/data-table-density.d.ts +1 -1
- package/dist/lib/components/ui/collections/data-table/partials/data-table-scroll-region.d.ts +4 -1
- package/dist/lib/components/ui/collections/data-table/partials/layouts/data-table-list-bar.d.ts +6 -3
- package/dist/lib/components/ui/collections/data-table/partials/layouts/list-bar-in-row.d.ts +2 -1
- package/dist/lib/components/ui/collections/data-table/types.d.ts +11 -8
- package/dist/lib/components/ui/collections/data-table/virtual-option.d.ts +9 -3
- package/dist/lib/components/ui/collections/filter/use-filter-field-config.d.ts +2 -0
- package/dist/lib/components/ui/collections/server-table/partials/server-table-passed-through.d.ts +1 -0
- package/dist/lib/components/ui/collections/server-table/types.d.ts +1 -1
- package/dist/lib/components/ui/display/chip/chip.variants.d.ts +5 -4
- package/dist/lib/components/ui/display/code/code-block-parts.d.ts +1 -1
- package/dist/lib/components/ui/display/code/code-block.d.ts +4 -1
- package/dist/lib/components/ui/display/code/languages.d.ts +2 -0
- package/dist/lib/components/ui/display/code/moonlight-tokens.d.ts +10 -10
- package/dist/lib/components/ui/feedback/confirm-dialog/confirm-dialog-props.d.ts +73 -0
- package/dist/lib/components/ui/feedback/confirm-dialog/confirm-dialog-rules.d.ts +12 -0
- package/dist/lib/components/ui/feedback/confirm-dialog/confirm-dialog.d.ts +5 -61
- package/dist/lib/components/ui/feedback/confirm-dialog/confirm-footer.d.ts +12 -0
- package/dist/lib/components/ui/feedback/confirm-dialog/confirm-phrase.d.ts +26 -0
- package/dist/lib/components/ui/feedback/confirm-dialog/use-confirm-action.d.ts +1 -1
- package/dist/lib/components/ui/fields/contacts/placeholders.d.ts +1 -1
- package/dist/lib/components/ui/fields/index.d.ts +1 -0
- package/dist/lib/components/ui/fields/quantity-field/types.d.ts +1 -1
- package/dist/lib/components/ui/fields/quantity-stepper/index.d.ts +2 -0
- package/dist/lib/components/ui/fields/quantity-stepper/quantity-stepper-model.d.ts +9 -0
- package/dist/lib/components/ui/fields/quantity-stepper/quantity-stepper-value.d.ts +15 -0
- package/dist/lib/components/ui/fields/quantity-stepper/quantity-stepper.d.ts +9 -0
- package/dist/lib/components/ui/fields/quantity-stepper/quantity-stepper.types.d.ts +35 -0
- package/dist/lib/components/ui/fields/quantity-stepper/use-quantity-stepper.d.ts +28 -0
- package/dist/lib/components/ui/layout/index.d.ts +1 -0
- package/dist/lib/components/ui/layout/pane-layout/pane-layout.d.ts +8 -3
- package/dist/lib/components/ui/layout/pane-layout/partials/pane-layout-classes.d.ts +2 -2
- package/dist/lib/components/ui/layout/pane-layout/partials/pane-layout-context.d.ts +9 -0
- package/dist/lib/components/ui/layout/pane-layout/partials/use-pane-layout-state.d.ts +7 -2
- package/dist/lib/components/ui/layout/panel-card/index.d.ts +1 -0
- package/dist/lib/components/ui/layout/panel-card/panel-card.d.ts +23 -0
- package/dist/lib/components/ui/primitives/collapsible/collapsible.d.ts +9 -1
- package/dist/lib/components/ui/primitives/tabs/partials/tabs-list.d.ts +6 -1
- package/dist/lib/components/ui/primitives/tabs/tabs-classes.d.ts +1 -0
- package/dist/lib/components/ui/selection/chip-group-field/chip-group-field.d.ts +8 -0
- package/dist/lib/components/ui/selection/chip-group-field/chip-group-field.types.d.ts +6 -0
- package/dist/lib/components/ui/selection/chip-group-field/partials/chip-group-reasons.d.ts +10 -0
- package/dist/lib/components/ui/selection/chip-group-field/partials/use-chip-rail.d.ts +12 -0
- package/dist/lib/components/ui/selection/choice-group/choice-group-root.d.ts +5 -2
- package/dist/lib/components/ui/selection/index.d.ts +1 -0
- package/dist/lib/components/ui/selection/status-menu/index.d.ts +3 -0
- package/dist/lib/components/ui/selection/status-menu/partials/status-confirm.d.ts +12 -0
- package/dist/lib/components/ui/selection/status-menu/partials/status-icon.d.ts +6 -0
- package/dist/lib/components/ui/selection/status-menu/partials/status-items.d.ts +18 -0
- package/dist/lib/components/ui/selection/status-menu/partials/status-menu-list.d.ts +14 -0
- package/dist/lib/components/ui/selection/status-menu/partials/status-sheet-row.d.ts +11 -0
- package/dist/lib/components/ui/selection/status-menu/partials/status-sheet.d.ts +21 -0
- package/dist/lib/components/ui/selection/status-menu/partials/status-trigger.d.ts +18 -0
- package/dist/lib/components/ui/selection/status-menu/status-menu-data.d.ts +12 -0
- package/dist/lib/components/ui/selection/status-menu/status-menu-form-field.d.ts +20 -0
- package/dist/lib/components/ui/selection/status-menu/status-menu.d.ts +13 -0
- package/dist/lib/components/ui/selection/status-menu/types.d.ts +76 -0
- package/dist/lib/components/ui/selection/status-menu/use-status-change.d.ts +18 -0
- package/dist/lib/components/ui/selection/status-menu/use-status-menu.d.ts +30 -0
- package/dist/lib/components/ui/widgets/calculator/partials/calculator-keypad.d.ts +1 -5
- package/dist/lib/components/ui/widgets/calculator/variants/key-layout.d.ts +3 -49
- package/dist/lib/components/ui/widgets/keypad/cash-model.d.ts +16 -0
- package/dist/lib/components/ui/widgets/keypad/index.d.ts +5 -0
- package/dist/lib/components/ui/widgets/keypad/key-layout.d.ts +56 -0
- package/dist/lib/components/ui/widgets/keypad/keypad-binding.d.ts +32 -0
- package/dist/lib/components/ui/widgets/keypad/keypad-cash.d.ts +28 -0
- package/dist/lib/components/ui/widgets/keypad/keypad-display.d.ts +11 -0
- package/dist/lib/components/ui/widgets/keypad/keypad-entry.d.ts +39 -0
- package/dist/lib/components/ui/widgets/keypad/keypad-grid.d.ts +27 -0
- package/dist/lib/components/ui/widgets/{calculator/partials/calculator-key.d.ts → keypad/keypad-key.d.ts} +6 -3
- package/dist/lib/components/ui/widgets/keypad/keypad-layout.d.ts +19 -0
- package/dist/lib/components/ui/widgets/{calculator/partials → keypad}/keypad-navigation.d.ts +1 -1
- package/dist/lib/components/ui/widgets/keypad/keypad.d.ts +9 -0
- package/dist/lib/components/ui/widgets/keypad/keypad.types.d.ts +91 -0
- package/dist/lib/components/ui/widgets/keypad/use-keypad-cash.d.ts +14 -0
- package/dist/lib/components/ui/widgets/keypad/use-keypad-keyboard.d.ts +7 -0
- package/dist/lib/components/ui/widgets/keypad/use-keypad-number.d.ts +10 -0
- package/dist/lib/components/ui/widgets/keypad/use-keypad-quantity.d.ts +16 -0
- package/dist/lib/components/ui/widgets/keypad/use-keypad-value.d.ts +17 -0
- package/dist/lib/components/ui/widgets/keypad/use-keypad.d.ts +23 -0
- package/dist/lib/components/ui/widgets/native-charts/core/label-placement.d.ts +25 -0
- package/dist/lib/components/ui/widgets/native-charts/core/reference-geometry.d.ts +3 -2
- package/dist/lib/hooks/index.d.ts +1 -0
- package/dist/lib/hooks/use-barcode-scanner.d.ts +39 -0
- package/dist/lib/i18n/resources/en/en.d.ts +28 -0
- package/dist/lib/i18n/resources/en/material.json.d.ts +30 -2
- package/dist/lib/i18n/resources/fr/fr.d.ts +28 -0
- package/dist/lib/i18n/resources/fr/material.json.d.ts +30 -2
- package/dist/lib/i18n/resources/resources.d.ts +56 -0
- package/dist/lib/i18n/translator.d.ts +1 -1
- package/dist/lib/keypad/index.d.ts +20 -0
- package/dist/lib/print/output/csv.d.ts +37 -0
- package/dist/lib/print/output/index.d.ts +1 -0
- package/dist/lib.js +1 -1
- package/dist/main.js +54 -53
- package/dist/map.js +9 -9
- package/dist/material.css +1 -1
- package/dist/pdf-reader.js +1 -1
- package/dist/{percent-field-DGlU_xii.js → percent-field-sAq7BaxN.js} +15 -14
- package/dist/{picker-surface-CGhqNR8a.js → picker-surface--6E-nvpU.js} +4 -4
- package/dist/{popover-ZxL7ktKk.js → popover-bEB8I_-w.js} +1 -1
- package/dist/{pos-types-BQ0QpYMp.js → pos-types-Cpdc4E33.js} +1 -1
- package/dist/{pos-variant-CKy8h_5i.js → pos-variant-ClMGu59I.js} +25 -25
- package/dist/print.js +146 -127
- package/dist/{programmer-variant-CbHstE28.js → programmer-variant-ChzgyA7x.js} +30 -30
- package/dist/{public-D-tdOz4b.js → public-BY8N5NDT.js} +9 -9
- package/dist/{rich-code-block-CTPj1LuD.js → rich-code-block-D2GiWuqD.js} +18 -18
- package/dist/{scientific-variant-B8U9h3II.js → scientific-variant-31_93gcZ.js} +8 -8
- package/dist/{sortable-provider-CSoWE7Ce.js → sortable-provider-DGiVn72t.js} +2 -2
- package/dist/{spinner-ffuwBXHl.js → spinner-CEVomQ5K.js} +1 -1
- package/dist/standard-layout-DTr9yy8Z.js +48 -0
- package/dist/{tabs-JgRq6ZJx.js → tabs-B8MXBbPW.js} +14 -14
- package/dist/{text-field-DURkIP-v.js → text-field-DN88qD0Q.js} +16 -15
- package/dist/theme-picker.js +13 -13
- package/dist/{theme-toggle-n_Zg7JAA.js → theme-toggle-C7pnu1Xh.js} +1 -1
- package/dist/{toolbar-items-IANZdcw4.js → toolbar-items-BDfoh8QB.js} +4 -4
- package/dist/{translate-material-CMHUH5Ha.js → translate-material-s_lxIV43.js} +1 -1
- package/dist/{translator-9zA-Um3W.js → translator-Cgql9srO.js} +56 -4
- package/dist/use-calculator-keyboard-DysnFK8m.js +222 -0
- package/dist/{use-chat-composer-BoIN7RCA.js → use-chat-composer-xCcp9fVc.js} +2250 -2195
- package/dist/{use-heading-spy-Ciw9GBHL.js → use-heading-spy-FIZlf9qj.js} +235 -154
- package/dist/{use-material-translation-CLNaMhUf.js → use-material-translation-xNzbSVgS.js} +1 -1
- package/dist/{use-return-focus-DWhbTGVe.js → use-return-focus-CW9KkDkp.js} +1 -1
- package/docs/README.md +2 -1
- package/docs/components/README.md +2 -0
- package/docs/components/buttons-and-actions.md +81 -2
- package/docs/components/data-display.md +6 -3
- package/docs/components/data-table-filter-and-lists.md +19 -6
- package/docs/components/feedback-and-status.md +20 -0
- package/docs/components/form-fields.md +60 -7
- package/docs/components/navigation-and-layout.md +48 -2
- package/docs/components/primitives.md +44 -2
- package/docs/components/rich-widgets.md +28 -0
- package/docs/components/selection-and-choice.md +176 -1
- package/docs/guides/calculator.md +1 -1
- package/docs/guides/cards-and-tiles.md +31 -24
- package/docs/guides/csp.md +4 -3
- package/docs/guides/data-grid.md +5 -4
- package/docs/guides/field-look-refined.md +2 -2
- package/docs/guides/forms.md +1 -0
- package/docs/guides/helpers.md +49 -0
- package/docs/guides/keypad.md +233 -0
- package/docs/guides/printing.md +51 -1
- package/docs/previews/buttons-actions-producttile.webp +0 -0
- package/docs/previews/data-display-codeblock.webp +0 -0
- package/docs/previews/data-display-codegroup.webp +0 -0
- package/docs/previews/data-display-jsonviewer.webp +0 -0
- package/docs/previews/form-fields-quantitystepper.webp +0 -0
- package/docs/previews/navigation-layout-panelcard.webp +0 -0
- package/docs/previews/rich-widgets-keypad.webp +0 -0
- package/docs/previews/selection-choice-statusmenu.webp +0 -0
- package/docs/roadmap.md +21 -0
- package/docs/troubleshooting.md +68 -4
- package/lib/components/ui/actions/action-card/action-card-tile.css +47 -0
- package/lib/components/ui/display/chip/chip-soft.css +27 -2
- package/lib/components/ui/display/code/code-moonlight.css +10 -10
- package/lib/components/ui/primitives/collapsible/collapsible.css +9 -0
- package/lib/components/ui/widgets/calculator/calculator-keys.css +3 -0
- package/lib/components/ui/widgets/calculator/calculator.css +118 -0
- package/package.json +7 -2
- package/dist/field-blur-context-BLGgf_3z.js +0 -73
- package/dist/key-layout-cR79xfGE.js +0 -38
- package/dist/standard-layout-BuSRr3TY.js +0 -40
|
@@ -29,6 +29,7 @@ value type each one writes. Everything is imported from
|
|
|
29
29
|
| <img src="../previews/selection-choice-sortablelist.webp" width="96" alt=""> | [SortableList](#sortablelist) | Reorderable list: drag or keyboard, announced; two-line labels, row menu |
|
|
30
30
|
| <img src="../previews/selection-choice-treeselectfield.webp" width="96" alt=""> | [TreeSelectField](#treeselectfield) | Pick from a tree, single or multiple |
|
|
31
31
|
| <img src="../previews/selection-choice-switcher.webp" width="96" alt=""> | [Switcher](#switcher) | Workspace, establishment or class switcher: menu, search, groups, links; `size="compact"` an icon-only round trigger for the app bar's `titleAfter` slot |
|
|
32
|
+
| <img src="../previews/selection-choice-statusmenu.webp" width="96" alt=""> | [StatusMenu](#statusmenu) | A record's status as a soft chip that opens a menu (a bottom sheet on a phone) of statuses: transitions, locked, busy, read-only; `StatusMenuFormField` for forms |
|
|
32
33
|
|
|
33
34
|
## Select
|
|
34
35
|
|
|
@@ -839,6 +840,61 @@ export function Topics() {
|
|
|
839
840
|
}
|
|
840
841
|
```
|
|
841
842
|
|
|
843
|
+
<a id="chipgroupfield-tones"></a>
|
|
844
|
+
|
|
845
|
+
An option's `tone` colours its chip with the tones `Badge` takes (`primary`,
|
|
846
|
+
`secondary`, `tertiary`, `success`, `info`, `warning`, `error`, `neutral`, and
|
|
847
|
+
an app's own from `AppTones`), so a category filter can match the toned tiles
|
|
848
|
+
below it. An unchosen chip shows a soft tint of the tone with the tone's text
|
|
849
|
+
colour; a chosen chip shows the filled tone under its on-colour, both at AA
|
|
850
|
+
contrast in light and dark. The leading check, `aria-checked`, the roving
|
|
851
|
+
focus and the boundary of an unchosen chip are unchanged. No `tone` keeps the
|
|
852
|
+
neutral chip.
|
|
853
|
+
|
|
854
|
+
```tsx
|
|
855
|
+
<ChipGroupField
|
|
856
|
+
label="Categories"
|
|
857
|
+
options={[
|
|
858
|
+
{ value: 'drinks', label: 'Drinks', icon: 'glass', tone: 'info' },
|
|
859
|
+
{ value: 'bakery', label: 'Bakery', tone: 'warning' },
|
|
860
|
+
{ value: 'other', label: 'Other' },
|
|
861
|
+
]}
|
|
862
|
+
value={value}
|
|
863
|
+
onValueChange={setValue}
|
|
864
|
+
/>
|
|
865
|
+
```
|
|
866
|
+
|
|
867
|
+
`overflow="scroll"` keeps the chips on one line that scrolls sideways (touch,
|
|
868
|
+
trackpad, the wheel; a focused chip scrolls into view), with a fade at each
|
|
869
|
+
edge that hides more chips and no page scroll. The roving focus and the
|
|
870
|
+
selection are the same. The default `overflow="wrap"` lets the chips flow onto
|
|
871
|
+
several lines; `columns` and `groups` draw a grid and ignore `overflow`.
|
|
872
|
+
|
|
873
|
+
`overflow="scroll"` for one sideways-scrolling line:
|
|
874
|
+
|
|
875
|
+
```tsx
|
|
876
|
+
import { ChipGroupField } from '@nxgt/material/components';
|
|
877
|
+
import { useState } from 'react';
|
|
878
|
+
|
|
879
|
+
const subjects = ['Algebra', 'Biology', 'Chemistry', 'Drama', 'Economics', 'French'].map(
|
|
880
|
+
(label) => ({ value: label.toLowerCase(), label }),
|
|
881
|
+
);
|
|
882
|
+
|
|
883
|
+
export function Subjects() {
|
|
884
|
+
const [value, setValue] = useState<string[]>([]);
|
|
885
|
+
return (
|
|
886
|
+
<ChipGroupField
|
|
887
|
+
label="Subjects"
|
|
888
|
+
type="multiple"
|
|
889
|
+
overflow="scroll"
|
|
890
|
+
options={subjects}
|
|
891
|
+
value={value}
|
|
892
|
+
onValueChange={setValue}
|
|
893
|
+
/>
|
|
894
|
+
);
|
|
895
|
+
}
|
|
896
|
+
```
|
|
897
|
+
|
|
842
898
|
Balanced by default, and the unbalanced fill:
|
|
843
899
|
|
|
844
900
|
```tsx
|
|
@@ -857,7 +913,7 @@ const four = ['a', 'b', 'c', 'd'].map((value) => ({ value, title: value.toUpperC
|
|
|
857
913
|
| `value` / `onValueChange` | `string \| null` or `string[]` | — | the chosen value, by `type` |
|
|
858
914
|
| `deselectable` | `boolean` | `false` | `single` only: pressing the chosen chip clears it |
|
|
859
915
|
| `max` / `min` | `number` | — | `multiple` only: `max` blocks the other chips once reached and speaks "{count} of {max} chosen"; `min` is a hint only |
|
|
860
|
-
| `options` | `{ value; label; icon?; count?; description?; disabled?; disabledReason? }[]` | — | the chips, in one flow; `count`: see [option counts](#option-counts) |
|
|
916
|
+
| `options` | `{ value; label; icon?; tone?; count?; description?; disabled?; disabledReason? }[]` | — | the chips, in one flow; `count`: see [option counts](#option-counts); `tone`: see [tones](#chipgroupfield-tones) |
|
|
861
917
|
| `groups` | `{ label: string; options }[]` | — | titled sections instead of `options` (it wins when both are given): see [slot picker](#slot-picker-columns-groups-and-reasons) |
|
|
862
918
|
| `columns` | `number \| { base?; sm?; md?; lg? }` | — | equal-width columns (1 to 12; one count per breakpoint, each holding to the next); Up and Down then move by row. Unset, the chips flow and wrap |
|
|
863
919
|
| `showDisabledReason` | `boolean` | `true` | prints a disabled option's `disabledReason` below the whole group ("Label: reason"), so the chip row keeps its wrapping; `false` leaves it to the tooltip and the accessible description |
|
|
@@ -1020,6 +1076,125 @@ on a touch screen.
|
|
|
1020
1076
|
|
|
1021
1077
|
---
|
|
1022
1078
|
|
|
1079
|
+
## StatusMenu
|
|
1080
|
+
|
|
1081
|
+

|
|
1082
|
+
|
|
1083
|
+
A record's status (a ticket, an order, a patient, a device) as a chip that
|
|
1084
|
+
changes it in one tap: a soft Badge pill (the tone's container, the tone's
|
|
1085
|
+
icon, the words, a chevron) that opens a menu of radio items. The held status
|
|
1086
|
+
is checked and inert. The button is named "Status of Ticket 42: Open", and
|
|
1087
|
+
`onSelect` never runs for the held status, for a blocked one, or when locked or
|
|
1088
|
+
busy.
|
|
1089
|
+
|
|
1090
|
+
- `transitions` greys the statuses it does not reach, each saying why. A
|
|
1091
|
+
terminal status needs `done: []`; unlike `useAllowedTransitions`, a missing
|
|
1092
|
+
key allows every move.
|
|
1093
|
+
- `locked` picks nothing and shows its `reason` as a tooltip and as the menu's
|
|
1094
|
+
first line.
|
|
1095
|
+
- `busy` keeps the old status and swaps the chevron for a spinner. A returned
|
|
1096
|
+
promise does the same until it settles; a rejection is announced and sent to
|
|
1097
|
+
`onError`.
|
|
1098
|
+
- `confirm` on a choice asks first, in a dialog.
|
|
1099
|
+
- `readOnly` draws a plain label, no button.
|
|
1100
|
+
|
|
1101
|
+
On a phone (under 768px) the statuses open in a bottom sheet of 44px rows
|
|
1102
|
+
instead of the menu; a status that cannot be picked says why in its own row
|
|
1103
|
+
(touch has no tooltip). `presentation` is `'auto'` (default), `'menu'` or
|
|
1104
|
+
`'sheet'`.
|
|
1105
|
+
|
|
1106
|
+
```tsx
|
|
1107
|
+
import { StatusMenu, type StatusChoice } from '@nxgt/material/components';
|
|
1108
|
+
|
|
1109
|
+
const statuses: StatusChoice<'todo' | 'progress' | 'done'>[] = [
|
|
1110
|
+
{ value: 'todo', label: 'To do', tone: 'neutral', icon: 'circle' },
|
|
1111
|
+
{ value: 'progress', label: 'In progress', tone: 'info', icon: 'circle-half-stroke' },
|
|
1112
|
+
{ value: 'done', label: 'Done', tone: 'success', icon: 'circle-check' },
|
|
1113
|
+
];
|
|
1114
|
+
|
|
1115
|
+
<StatusMenu
|
|
1116
|
+
label="Ticket 42"
|
|
1117
|
+
value={status}
|
|
1118
|
+
choices={statuses}
|
|
1119
|
+
transitions={{ done: ['progress'] }}
|
|
1120
|
+
presentation="auto" // 'menu' | 'sheet' to force one
|
|
1121
|
+
onSelect={(next) => setStatus(next)}
|
|
1122
|
+
/>;
|
|
1123
|
+
```
|
|
1124
|
+
|
|
1125
|
+
Saving through a server, with a status that asks first:
|
|
1126
|
+
|
|
1127
|
+
```tsx
|
|
1128
|
+
const statuses: StatusChoice<'open' | 'cancelled'>[] = [
|
|
1129
|
+
{ value: 'open', label: 'Open', tone: 'info' },
|
|
1130
|
+
{
|
|
1131
|
+
value: 'cancelled',
|
|
1132
|
+
label: 'Cancelled',
|
|
1133
|
+
tone: 'error',
|
|
1134
|
+
confirm: { title: 'Cancel this ticket?', confirmLabel: 'Cancel ticket', destructive: true },
|
|
1135
|
+
},
|
|
1136
|
+
];
|
|
1137
|
+
|
|
1138
|
+
<StatusMenu
|
|
1139
|
+
label="Ticket 42"
|
|
1140
|
+
value={status}
|
|
1141
|
+
choices={statuses}
|
|
1142
|
+
onSelect={async (next) => {
|
|
1143
|
+
await saveStatus(ticketId, next); // pending until it settles; a throw is announced
|
|
1144
|
+
setStatus(next);
|
|
1145
|
+
}}
|
|
1146
|
+
onError={(error) => toast.error(String(error))}
|
|
1147
|
+
/>;
|
|
1148
|
+
```
|
|
1149
|
+
|
|
1150
|
+
`confirm` defaults: `confirmLabel` "Confirm", `destructive` `false`, `description`
|
|
1151
|
+
"Change the status to {label}?" (`material.status-menu.confirm-default`). The menu
|
|
1152
|
+
marks such a choice "Asks first" (`material.status-menu.confirms`). A confirmed
|
|
1153
|
+
save shows its progress in the dialog (busy buttons, a failure kept in it), not on the chip.
|
|
1154
|
+
|
|
1155
|
+
| Prop | Type | Default | Effect |
|
|
1156
|
+
| --- | --- | --- | --- |
|
|
1157
|
+
| `label` | `string` | — | what the status belongs to; names the button "Status of {label}: {status}" |
|
|
1158
|
+
| `value` / `choices` | `V` / `readonly StatusChoice<V>[]` | — | the held status, and every status: `value`, `label`, `tone`, `icon`, `description`, `disabled`, `disabledReason`, `confirm` (`title`, `description`, `confirmLabel`, `destructive`: a `ConfirmDialog` runs `onSelect` on confirm, a failure stays in the dialog and reaches `onError`) |
|
|
1159
|
+
| `onSelect` | `(next, prev) => void \| Promise<void>` | — | a different status was picked; a returned promise keeps the chip pending (menu closed, focus on the chip, spinner, `aria-busy`, old status shown) until it settles |
|
|
1160
|
+
| `onError` | `(error) => void` | — | the promise rejected (or threw): the failure is announced and the old status stays |
|
|
1161
|
+
| `transitions` | `Partial<Record<V, readonly V[]>>` | — | the statuses reachable from each status (no key: every move; a terminal status is `[]`) |
|
|
1162
|
+
| `unavailable` | `'disabled' \| 'hidden'` | `'disabled'` | an unreachable status is greyed with "Not available from {status}", or dropped |
|
|
1163
|
+
| `locked` / `reason` | `boolean` / `string` | — | nothing can be picked; `reason` is the tooltip, the button's description and the menu's first line |
|
|
1164
|
+
| `busy` | `boolean` | `false` | `aria-busy` and `aria-disabled`, a spinner, the old status kept, the menu does not open |
|
|
1165
|
+
| `readOnly` | `boolean` | `false` | a plain label |
|
|
1166
|
+
| `size` | `'sm' \| 'md'` | `'md'` | chip height |
|
|
1167
|
+
| `presentation` | `'auto' \| 'menu' \| 'sheet'` | `'auto'` | a dropdown menu, a bottom sheet of 44px rows, or the sheet under 768px |
|
|
1168
|
+
| `open` / `onOpenChange` | `boolean` / `(open) => void` | — | controlled menu |
|
|
1169
|
+
| `triggerProps` / `className` | | | on the button / on the menu |
|
|
1170
|
+
|
|
1171
|
+
Texts come from `material.status-menu.*` (English and French). The rules
|
|
1172
|
+
(which statuses can be picked, and why not) are `@nxgt/material-core/status-menu`.
|
|
1173
|
+
|
|
1174
|
+
**Form wrapper:** `StatusMenuFormField` takes `control`, `name`, `rules` and the
|
|
1175
|
+
StatusMenu props except `value` (the form holds it). A pick runs
|
|
1176
|
+
`onSelect` first; the form takes the new status once `onSelect` resolves (a
|
|
1177
|
+
promise keeps the pending state and the old status, and a rejection keeps the old
|
|
1178
|
+
value). `field.onBlur` fires when focus leaves the chip and its menu or sheet. An
|
|
1179
|
+
empty value (`''`) matches no choice, so the chip shows an empty value: give an
|
|
1180
|
+
initial status. The error message (or `helperText`) is written under the chip and linked to
|
|
1181
|
+
it with `aria-describedby` and `aria-invalid`, a refused submit focuses the chip,
|
|
1182
|
+
and `disabled` (or the form's) locks it.
|
|
1183
|
+
|
|
1184
|
+
```tsx
|
|
1185
|
+
<StatusMenuFormField
|
|
1186
|
+
control={form.control}
|
|
1187
|
+
name="status"
|
|
1188
|
+
label="Ticket 42"
|
|
1189
|
+
choices={statuses}
|
|
1190
|
+
rules={{ required: 'Pick a status.' }}
|
|
1191
|
+
/>
|
|
1192
|
+
```
|
|
1193
|
+
|
|
1194
|
+
**Storybook:** `Selection & choice/StatusMenu` (and `/In a form`) · **Vue:** React only for now (#2816)
|
|
1195
|
+
|
|
1196
|
+
---
|
|
1197
|
+
|
|
1023
1198
|
## OptionTile
|
|
1024
1199
|
|
|
1025
1200
|

|
|
@@ -123,7 +123,7 @@ numbers: `sqrt(-1)` is "Undefined result".
|
|
|
123
123
|
/>
|
|
124
124
|
```
|
|
125
125
|
|
|
126
|
-
The props below are read only by this variant (`currency` is shared). A point of sale till for a counter, on `@nxgt/material-core/calculator/pos
|
|
126
|
+
The props below are read only by this variant (`currency` is shared). A point of sale till for a counter, on `@nxgt/material-core/calculator/pos` (the engine; apps import `@nxgt/material/calculator`). The
|
|
127
127
|
tab is named "Point of sale". The keys type an amount the way a till does:
|
|
128
128
|
`1`, `2`, `5` is `1.25`, `00` adds two zeros, backspace drops the last digit,
|
|
129
129
|
`C` clears it. `+` adds it to the ticket. `×` types a quantity first (`2.50 ×
|
|
@@ -265,6 +265,16 @@ focus. Below 30rem of list width the "Sort:" prefix is visually hidden (the name
|
|
|
265
265
|
stays, and the button's accessible name is still "Sort: Name, Ascending").
|
|
266
266
|
With no sortable column there is no control, and with neither a sortable column nor a `select` column there is no list bar.
|
|
267
267
|
|
|
268
|
+
For a short list the band is noise: `showSort={false}` drops the button (on a
|
|
269
|
+
phone too) and leaves every column sortable, so a
|
|
270
|
+
`layout={{ phone: 'cards', wide: 'table' }}` keeps its header sorting on desktop.
|
|
271
|
+
A list with a `select` column keeps its bar for "Select all". `DataTable` only:
|
|
272
|
+
`ServerTable` does not pass it through.
|
|
273
|
+
|
|
274
|
+
```tsx
|
|
275
|
+
<DataTable layout="cards" showSort={false} renderCard={…} … />
|
|
276
|
+
```
|
|
277
|
+
|
|
268
278
|
## Columns
|
|
269
279
|
|
|
270
280
|
Tiles are always one column. For cards, the list's own width, not the window's, picks the columns: one, two from 36rem,
|
|
@@ -326,49 +336,46 @@ grid of compact cells you read at a glance and change in place. Use
|
|
|
326
336
|
[`ActionCard variant="tile"`](../components/buttons-and-actions.md#actioncard-tile)
|
|
327
337
|
in a `ResponsiveGrid minItem="xs"`, one grid per section under its own `h2`:
|
|
328
338
|
|
|
329
|
-
- **`
|
|
330
|
-
|
|
339
|
+
- **`action`** is a [`StatusMenu`](../components/selection-and-choice.md#statusmenu)
|
|
340
|
+
chip: it shows the status as a word, an icon and a tone (never colour alone),
|
|
341
|
+
so the tile needs no `status` badge, and it changes the status in place (a
|
|
342
|
+
bottom sheet on a phone). Its button is named "Status of {name}: {status}".
|
|
343
|
+
The title is the one button (it opens the detail), so the card and its chip
|
|
344
|
+
are two tab stops and nothing nests. When the tile is too narrow for the title
|
|
345
|
+
and the chip side by side (two per row at 360px), the chip wraps under the
|
|
346
|
+
title.
|
|
331
347
|
- **`meta`** is one line, the occupant only while occupied; a long one is
|
|
332
348
|
clipped and shown in full in a tooltip.
|
|
333
|
-
- **`action`** is a `RowMenu` with a `DropdownMenuRadioGroup` of statuses,
|
|
334
|
-
named "Change status of {name}". The title is the one button (it opens the
|
|
335
|
-
detail), so the card and its menu are two tab stops and nothing nests.
|
|
336
349
|
|
|
337
350
|
```tsx
|
|
338
|
-
import {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
} from '@nxgt/material/components';
|
|
351
|
+
import { ActionCard, ResponsiveGrid, StatusMenu } from '@nxgt/material/components';
|
|
352
|
+
|
|
353
|
+
const choices = [
|
|
354
|
+
{ value: 'free', label: 'Free', tone: 'success', icon: 'check' },
|
|
355
|
+
{ value: 'occupied', label: 'Occupied', tone: 'warning', icon: 'user' },
|
|
356
|
+
{ value: 'out', label: 'Out of service', tone: 'error', icon: 'ban' },
|
|
357
|
+
];
|
|
346
358
|
|
|
347
359
|
<ResponsiveGrid minItem="xs">
|
|
348
360
|
<ActionCard
|
|
349
361
|
variant="tile"
|
|
350
362
|
title="Bed A1"
|
|
351
|
-
status={<Badge variant="soft" tone="warning" icon="user">Occupied</Badge>}
|
|
352
363
|
meta="Amara Diallo"
|
|
353
364
|
onClick={() => openDetail('a1')}
|
|
354
365
|
action={
|
|
355
|
-
<
|
|
366
|
+
<StatusMenu
|
|
356
367
|
label="Bed A1"
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
<DropdownMenuRadioItem value="occupied">Occupied</DropdownMenuRadioItem>
|
|
362
|
-
<DropdownMenuRadioItem value="cleaning">Cleaning</DropdownMenuRadioItem>
|
|
363
|
-
</DropdownMenuRadioGroup>
|
|
364
|
-
</RowMenu>
|
|
368
|
+
value={status}
|
|
369
|
+
choices={choices}
|
|
370
|
+
onSelect={setStatus}
|
|
371
|
+
/>
|
|
365
372
|
}
|
|
366
373
|
/>
|
|
367
374
|
</ResponsiveGrid>;
|
|
368
375
|
```
|
|
369
376
|
|
|
370
377
|
`title` is required with `action`: it is the card's button, named after the
|
|
371
|
-
title
|
|
378
|
+
title and the meta line.
|
|
372
379
|
|
|
373
380
|
For the figures of the whole board, put a `StatCardGroup` above a `DataTable`
|
|
374
381
|
with one row per section, one numeric column per status (they add up to the
|
package/docs/guides/csp.md
CHANGED
|
@@ -84,9 +84,10 @@ A virtual `DataTable` (`virtual`) adds no `style` attribute either: the
|
|
|
84
84
|
server renders its first rows as plain rows, and the spacer rows' heights
|
|
85
85
|
are custom properties written through `style.setProperty` after mount,
|
|
86
86
|
which `style-src-attr` does not govern (`StrictCspVirtual` renders one under
|
|
87
|
-
`style-src-attr 'none'`). Its scroll region's `maxHeight` is
|
|
88
|
-
`style` on the server, one of #596's
|
|
89
|
-
`
|
|
87
|
+
`style-src-attr 'none'`). Its scroll region's `maxHeight` is an inline
|
|
88
|
+
`style` on the server, one of #596's: give `maxHeightClassName`
|
|
89
|
+
(`maxHeightClassName="max-h-[28rem]"`, as `DataGrid` has) instead, and the
|
|
90
|
+
region has no `style` attribute.
|
|
90
91
|
|
|
91
92
|
A keyline `Carousel` (`multi-browse`, `hero`) renders no `style` attribute
|
|
92
93
|
either: its first frame comes from container-query CSS, and the masks that
|
package/docs/guides/data-grid.md
CHANGED
|
@@ -1123,10 +1123,11 @@ A strict policy works with no `style-src-attr 'unsafe-inline'` for the grid:
|
|
|
1123
1123
|
the CSSOM, which `style-src-attr` does not govern. A frozen column's drag
|
|
1124
1124
|
rewrites `--dg-start-*` the same way.
|
|
1125
1125
|
- A custom editor must follow the same rule: classes only.
|
|
1126
|
-
- `DataTable`'s own `maxHeight`
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
`
|
|
1126
|
+
- `DataTable`'s own `maxHeight` is an inline `style={{ maxHeight }}` on its
|
|
1127
|
+
scroll region, so it is not covered by this list; the grid takes a class
|
|
1128
|
+
instead. A virtual `DataTable` under a strict CSP takes
|
|
1129
|
+
`maxHeightClassName` (a class such as `max-h-[28rem]`, no inline style;
|
|
1130
|
+
[#800](https://github.com/softistx/nxgt-material/issues/800)).
|
|
1130
1131
|
|
|
1131
1132
|
The grid's popups (the select's list, the date's calendar, the menus) are
|
|
1132
1133
|
library components and add nothing to the policy: see the
|
|
@@ -41,7 +41,7 @@ import { TextField } from '@nxgt/material/components';
|
|
|
41
41
|
| --- | --- |
|
|
42
42
|
| `TextField` and the `*Field` text wrappers | `leading` / `trailing` icons and actions are muted with no well fill; a text unit (`"/ 20"`, a currency code) sits inline at the end in muted text. `CurrencyField` follows |
|
|
43
43
|
| `NumberField`, `PercentField`, `CalculatorField` | one `role="group"` box; the value is end-aligned in tabular figures, the suffix hugs it, the Decrease and Increase buttons are a tinted round pair at the end (the `DurationField` look), no dividers |
|
|
44
|
-
| `QuantityField`, `AgeField` | the value, the unit and the pair share one box; the unit is a chip that opens a menu |
|
|
44
|
+
| `QuantityField`, `AgeField` | the value, the unit and the pair share one box; the unit is a chip that opens a menu (no chip without `units` or `allowCustomUnit`) |
|
|
45
45
|
| `NumberField variant="score"` | a big value with its maximum, no box at rest; see [the form fields page](../components/form-fields.md#numberfield) |
|
|
46
46
|
| Date, time and duration fields | the shared focus ring, a dashed hairline when read-only, the recessed surface when disabled |
|
|
47
47
|
|
|
@@ -49,7 +49,7 @@ import { TextField } from '@nxgt/material/components';
|
|
|
49
49
|
|
|
50
50
|
- The `NumberField` order is the input, Decrease, Increase (it was Decrease,
|
|
51
51
|
input, Increase). With a `CalculatorField`: input, Open calculator,
|
|
52
|
-
Decrease, Increase. A `QuantityField` is value, unit, Decrease, Increase.
|
|
52
|
+
Decrease, Increase. A `QuantityField` is value, unit (when it has one), Decrease, Increase.
|
|
53
53
|
- The number box is a `group`. A test that queries an unnamed
|
|
54
54
|
`getByRole('group')` now also matches it: name the one you mean.
|
|
55
55
|
- A `QuantityField` unit is a button named "Unit: mg", not a `combobox`:
|
package/docs/guides/forms.md
CHANGED
|
@@ -537,6 +537,7 @@ or fails validation.
|
|
|
537
537
|
| `Checkbox` · `CheckboxFormField` | `boolean` | `false` (see [Traps](#traps)) |
|
|
538
538
|
| `CheckboxGroup` · `CheckboxGroupFormField` | `string[]` | `[]` |
|
|
539
539
|
| `RadioGroup` · `RadioGroupFormField` | `string` | `''` |
|
|
540
|
+
| `StatusMenu` · `StatusMenuFormField` | `string`: the picked `choices` value | `''` matches no choice, so the chip shows an empty value: give an initial status |
|
|
540
541
|
| `CheckCardField` · `CheckCardFormField` | `boolean`: `true` for the first card, `false` for the second | `false` |
|
|
541
542
|
| `ChipGroupField` · `ChipGroupFormField` | `type="single"` (default): `string \| null`. `type="multiple"`: `string[]` | `null` / `[]` |
|
|
542
543
|
| `ChoiceCardGroup` · `ChoiceCardGroupFormField` | same as `ChipGroupField` | `null` / `[]` |
|
package/docs/guides/helpers.md
CHANGED
|
@@ -139,6 +139,7 @@ Vue: the Redux helpers are Pinia store factories: `defineLocalStore`
|
|
|
139
139
|
| `useFocusOnMount<T>(focus = true)` | A ref whose element takes focus once mounted, and again whenever `focus` turns true: the heading of what replaced the content the user acted on. See [below](#usefocusonmount). |
|
|
140
140
|
| `useScrollAway(ref?, { threshold? })` | True while the reader scrolls down (by `threshold`, 24 px, since the scroll last turned); false scrolling up, at the top or at the end. Folds an extended `Fab`. Without `ref`, the window; false on the server. See [Fab](../components/buttons-and-actions.md#fab). |
|
|
141
141
|
| `useLatched(value)`, `useLatchedWhileClosed(open, value)` | The last value that was not `null` / `undefined`, and the value from the last open render while `open` is false: keep a dialog's text while it fades out after the app cleared its subject. `ConfirmDialog` does it itself. See [below](#uselatched). |
|
|
142
|
+
| `useBarcodeScanner({ onScan, enabled?, acceptIn?, maxGap?, minLength?, onKey? })`, `createScanner()`, `attachBarcodeScanner(target, options)` | Hears a keyboard-wedge barcode reader anywhere on the page: a run of keys each within `maxGap` ms (50), at least `minLength` (4) long, closed by Enter, reaches `onScan(code)`; slower or shorter runs are typing. `createScanner` is the framework-free detector (`@nxgt/material-core/barcode-scanner`, also re-exported by `@nxgt/material/hooks`). See [below](#usebarcodescanner). |
|
|
142
143
|
| `useCountdown(duration, { paused?, onExpire?, key? })` | Seconds left of `duration` (the clock `CountdownTimer` draws), for a button or a message that waits: ticks on the client only, `duration` on the server and first render. A new `duration` **or a new `key`** starts over, so the same `retryAfter` twice restarts it. See [below](#usecountdown). |
|
|
143
144
|
| `useSubmitOnce(busy?)` | One submit in flight: wraps a submit handler so a double click or a second Enter is dropped until the request ends (the handler's promise, or `busy` back to false). See [below](#usesubmitonce). |
|
|
144
145
|
| `useDrafts<T>(storageKey, { serialize?, parse?, storage? })`, `clearDrafts(storageKey, storage?)` | Unsent values kept by key in `sessionStorage`: `{ get, set, drop, clear, keys, loaded }`. Read after the first render (no hydration mismatch), storage errors swallowed, `drop(key)` once saved; `clearDrafts` empties a store key without a hook (sign-out). See [below](#usedrafts). |
|
|
@@ -260,6 +261,54 @@ export function ItemDialog({ item, onClose }: { item: Item | null; onClose: () =
|
|
|
260
261
|
things: it returns `value` while `open`, and the value of the last open render
|
|
261
262
|
once `open` is false.
|
|
262
263
|
|
|
264
|
+
### `useBarcodeScanner`
|
|
265
|
+
|
|
266
|
+
A USB or Bluetooth barcode reader is a keyboard: it types the whole code in a
|
|
267
|
+
few milliseconds a key and ends with Enter. The hook tells that run from a
|
|
268
|
+
person typing by timing alone, with one capture-phase `keydown` listener on
|
|
269
|
+
the document, for a till, a stock count or a check-in screen. It does not draw
|
|
270
|
+
barcodes: that is [`Barecode`](../components/data-display.md#barecode).
|
|
271
|
+
|
|
272
|
+
```tsx
|
|
273
|
+
import { useState } from 'react';
|
|
274
|
+
import { useBarcodeScanner } from '@nxgt/material/hooks';
|
|
275
|
+
|
|
276
|
+
export function Till({ add }: { add: (code: string) => void }) {
|
|
277
|
+
const [query, setQuery] = useState('');
|
|
278
|
+
useBarcodeScanner({ onScan: add, acceptIn: '[data-till-search]' });
|
|
279
|
+
return (
|
|
280
|
+
<input
|
|
281
|
+
data-till-search=""
|
|
282
|
+
aria-label="Search an item"
|
|
283
|
+
value={query}
|
|
284
|
+
onChange={(event) => setQuery(event.target.value)}
|
|
285
|
+
/>
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
| Option | Default | Effect |
|
|
291
|
+
| --- | --- | --- |
|
|
292
|
+
| `onScan` | required | called with the code when a scan ends; the Enter that closed it is swallowed (`preventDefault`, `stopPropagation`) |
|
|
293
|
+
| `enabled` | `true` | while `false` nothing is listened to and nothing is swallowed |
|
|
294
|
+
| `acceptIn` | none | a CSS selector of the form fields a scan is also accepted in (the search field); every other `input`, `textarea`, `select` and editable element is typed in. Outside form fields a scan is always accepted. In an accepted field the scan's characters are already typed in when Enter arrives: `onScan` clears the field itself |
|
|
295
|
+
| `maxGap` | `50` | the longest pause between two keys of one scan, in ms |
|
|
296
|
+
| `minLength` | `4` | the shortest code taken for a scan |
|
|
297
|
+
| `onKey` | none | `(event, { result, pending })` for each key the scanner looked at: a screen with its own shortcuts skips them while `pending.length > 1`, since those keys are a scan's |
|
|
298
|
+
|
|
299
|
+
Ctrl, Meta and Alt chords and key repeat are ignored. `onScan` and `onKey` are
|
|
300
|
+
read from a ref, so a new function each render never re-binds the listener.
|
|
301
|
+
The hook does nothing on the server. A reader that types slower than 50 ms a
|
|
302
|
+
key (some Bluetooth modes) needs a larger `maxGap`.
|
|
303
|
+
|
|
304
|
+
Outside React, `createScanner({ maxGap?, minLength? })` from
|
|
305
|
+
`@nxgt/material-core/barcode-scanner` (or `@nxgt/material/hooks`, which re-exports it) is the pure detector:
|
|
306
|
+
`feed(key, time)` answers `{ kind: 'pass' }`, `{ kind: 'buffering' }` or
|
|
307
|
+
`{ kind: 'scan', code }`; `reset()` and `pending` complete it. The timing is
|
|
308
|
+
the caller's (`event.timeStamp`), so it is tested without a clock.
|
|
309
|
+
`attachBarcodeScanner(target, options)` is the DOM binding without React and
|
|
310
|
+
returns what removes the listener.
|
|
311
|
+
|
|
263
312
|
### `useCountdown`
|
|
264
313
|
|
|
265
314
|
The clock behind [`CountdownTimer`](../components/feedback-and-status.md#countdowntimer),
|