@nxgt/material 3.101.0 → 3.103.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 (155) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +32 -2
  3. package/dist/{alert-dialog-DYzxchiS.js → alert-dialog-NFyAiSqc.js} +1 -1
  4. package/dist/{async-slice-drJyPvuM.js → async-slice-D35bX79S.js} +1 -1
  5. package/dist/{bubble-input-CQwVSoU3.js → bubble-input-BxI1WmNs.js} +17 -5
  6. package/dist/{button-GeIsUekU.js → button-Be7GZzF9.js} +2 -2
  7. package/dist/{calculator-BS4PAm6l.js → calculator-BGy1Q7rn.js} +261 -460
  8. package/dist/calculator-field.js +7 -7
  9. package/dist/{calculator-segment-CEdwE24M.js → calculator-segment-uixk6lBg.js} +1 -1
  10. package/dist/calculator.js +1 -1
  11. package/dist/charts.js +3 -3
  12. package/dist/{chip-BdEtFQ85.js → chip-3rWebZEM.js} +2 -2
  13. package/dist/{code-editor-BByXZOGy.js → code-editor-BqSVMfy-.js} +178 -177
  14. package/dist/code-editor.js +5 -5
  15. package/dist/{code-language-items-CSZX5zxD.js → code-language-items-Cf2LJVg2.js} +14 -5
  16. package/dist/components/ui/actions/action-card/action-card-tile.css +47 -0
  17. package/dist/components/ui/display/code/code-moonlight.css +10 -10
  18. package/dist/{components-DQ4MljCr.js → components-D2liJ9Yu.js} +17725 -17287
  19. package/dist/components.js +38 -37
  20. package/dist/{conversion-variant-DTQqAyDt.js → conversion-variant-clFZ6ZMJ.js} +5 -5
  21. package/dist/{currency-field-Dnju7ss2.js → currency-field-CBFEVNM1.js} +24 -23
  22. package/dist/{dialog-5_2__tu8.js → dialog-CrIIINTk.js} +3 -3
  23. package/dist/dnd.js +1 -1
  24. package/dist/{dropdown-menu-FdGC5kIP.js → dropdown-menu-BnfLWu8n.js} +1 -1
  25. package/dist/editor.js +9 -9
  26. package/dist/{empty-state-BoepPG5v.js → empty-state-CuUaCPY7.js} +1 -1
  27. package/dist/{field-blur-DkiZOVNV.js → field-blur-DMJwPLsg.js} +9 -9
  28. package/dist/field-blur-context-3rKussBQ.js +29 -0
  29. package/dist/{financial-variant-BhqJp5C-.js → financial-variant-BY_VCUuk.js} +5 -5
  30. package/dist/{hooks-DjcbJZiO.js → hooks-hHoZbL-4.js} +106 -61
  31. package/dist/hooks.js +3 -3
  32. package/dist/{hover-card-CmHJeBeY.js → hover-card-Do2-uzlF.js} +1 -1
  33. package/dist/i18n.js +3 -3
  34. package/dist/{icon-badge-LGUPQh_c.js → icon-badge-CHaVNEmb.js} +1 -1
  35. package/dist/{icon-button-BSVpO1jP.js → icon-button-BIAubLeJ.js} +1 -1
  36. package/dist/key-layout-D2goCDkD.js +31 -0
  37. package/dist/keypad.js +551 -0
  38. package/dist/label-DR-zZyGQ.js +47 -0
  39. package/dist/lib/components/ui/display/code/code-block-parts.d.ts +1 -1
  40. package/dist/lib/components/ui/display/code/code-block.d.ts +4 -1
  41. package/dist/lib/components/ui/display/code/languages.d.ts +2 -0
  42. package/dist/lib/components/ui/display/code/moonlight-tokens.d.ts +10 -10
  43. package/dist/lib/components/ui/feedback/confirm-dialog/confirm-dialog-props.d.ts +73 -0
  44. package/dist/lib/components/ui/feedback/confirm-dialog/confirm-dialog-rules.d.ts +12 -0
  45. package/dist/lib/components/ui/feedback/confirm-dialog/confirm-dialog.d.ts +5 -61
  46. package/dist/lib/components/ui/feedback/confirm-dialog/confirm-footer.d.ts +12 -0
  47. package/dist/lib/components/ui/feedback/confirm-dialog/confirm-phrase.d.ts +26 -0
  48. package/dist/lib/components/ui/feedback/confirm-dialog/use-confirm-action.d.ts +1 -1
  49. package/dist/lib/components/ui/fields/contacts/placeholders.d.ts +1 -1
  50. package/dist/lib/components/ui/layout/index.d.ts +1 -0
  51. package/dist/lib/components/ui/layout/panel-card/index.d.ts +1 -0
  52. package/dist/lib/components/ui/layout/panel-card/panel-card.d.ts +21 -0
  53. package/dist/lib/components/ui/selection/index.d.ts +1 -0
  54. package/dist/lib/components/ui/selection/status-menu/index.d.ts +3 -0
  55. package/dist/lib/components/ui/selection/status-menu/partials/status-confirm.d.ts +12 -0
  56. package/dist/lib/components/ui/selection/status-menu/partials/status-icon.d.ts +6 -0
  57. package/dist/lib/components/ui/selection/status-menu/partials/status-items.d.ts +18 -0
  58. package/dist/lib/components/ui/selection/status-menu/partials/status-menu-list.d.ts +14 -0
  59. package/dist/lib/components/ui/selection/status-menu/partials/status-sheet-row.d.ts +11 -0
  60. package/dist/lib/components/ui/selection/status-menu/partials/status-sheet.d.ts +21 -0
  61. package/dist/lib/components/ui/selection/status-menu/partials/status-trigger.d.ts +18 -0
  62. package/dist/lib/components/ui/selection/status-menu/status-menu-data.d.ts +12 -0
  63. package/dist/lib/components/ui/selection/status-menu/status-menu-form-field.d.ts +20 -0
  64. package/dist/lib/components/ui/selection/status-menu/status-menu.d.ts +13 -0
  65. package/dist/lib/components/ui/selection/status-menu/types.d.ts +76 -0
  66. package/dist/lib/components/ui/selection/status-menu/use-status-change.d.ts +18 -0
  67. package/dist/lib/components/ui/selection/status-menu/use-status-menu.d.ts +30 -0
  68. package/dist/lib/components/ui/widgets/calculator/partials/calculator-keypad.d.ts +1 -5
  69. package/dist/lib/components/ui/widgets/calculator/variants/key-layout.d.ts +3 -49
  70. package/dist/lib/components/ui/widgets/keypad/cash-model.d.ts +16 -0
  71. package/dist/lib/components/ui/widgets/keypad/index.d.ts +5 -0
  72. package/dist/lib/components/ui/widgets/keypad/key-layout.d.ts +56 -0
  73. package/dist/lib/components/ui/widgets/keypad/keypad-binding.d.ts +32 -0
  74. package/dist/lib/components/ui/widgets/keypad/keypad-cash.d.ts +28 -0
  75. package/dist/lib/components/ui/widgets/keypad/keypad-display.d.ts +11 -0
  76. package/dist/lib/components/ui/widgets/keypad/keypad-entry.d.ts +39 -0
  77. package/dist/lib/components/ui/widgets/keypad/keypad-grid.d.ts +27 -0
  78. package/dist/lib/components/ui/widgets/{calculator/partials/calculator-key.d.ts → keypad/keypad-key.d.ts} +6 -3
  79. package/dist/lib/components/ui/widgets/keypad/keypad-layout.d.ts +19 -0
  80. package/dist/lib/components/ui/widgets/{calculator/partials → keypad}/keypad-navigation.d.ts +1 -1
  81. package/dist/lib/components/ui/widgets/keypad/keypad.d.ts +9 -0
  82. package/dist/lib/components/ui/widgets/keypad/keypad.types.d.ts +91 -0
  83. package/dist/lib/components/ui/widgets/keypad/use-keypad-cash.d.ts +14 -0
  84. package/dist/lib/components/ui/widgets/keypad/use-keypad-keyboard.d.ts +7 -0
  85. package/dist/lib/components/ui/widgets/keypad/use-keypad-number.d.ts +10 -0
  86. package/dist/lib/components/ui/widgets/keypad/use-keypad-quantity.d.ts +16 -0
  87. package/dist/lib/components/ui/widgets/keypad/use-keypad-value.d.ts +17 -0
  88. package/dist/lib/components/ui/widgets/keypad/use-keypad.d.ts +23 -0
  89. package/dist/lib/hooks/index.d.ts +1 -0
  90. package/dist/lib/hooks/use-barcode-scanner.d.ts +39 -0
  91. package/dist/lib/i18n/resources/en/en.d.ts +25 -0
  92. package/dist/lib/i18n/resources/en/material.json.d.ts +26 -1
  93. package/dist/lib/i18n/resources/fr/fr.d.ts +25 -0
  94. package/dist/lib/i18n/resources/fr/material.json.d.ts +26 -1
  95. package/dist/lib/i18n/resources/resources.d.ts +50 -0
  96. package/dist/lib/i18n/translator.d.ts +1 -1
  97. package/dist/lib/keypad/index.d.ts +20 -0
  98. package/dist/lib.js +1 -1
  99. package/dist/main.js +54 -53
  100. package/dist/map.js +9 -9
  101. package/dist/material.css +1 -1
  102. package/dist/pdf-reader.js +1 -1
  103. package/dist/{percent-field-DGlU_xii.js → percent-field-DU8eIMD3.js} +15 -14
  104. package/dist/{picker-surface-CGhqNR8a.js → picker-surface-XmA--JVI.js} +4 -4
  105. package/dist/{popover-ZxL7ktKk.js → popover-D8iHiAbl.js} +1 -1
  106. package/dist/{pos-types-BQ0QpYMp.js → pos-types-BEAY27Rm.js} +1 -1
  107. package/dist/{pos-variant-CKy8h_5i.js → pos-variant-ClGI92Lt.js} +25 -25
  108. package/dist/print.js +1 -1
  109. package/dist/{programmer-variant-CbHstE28.js → programmer-variant-Boq2O8hB.js} +30 -30
  110. package/dist/{public-D-tdOz4b.js → public-DOrXKmcL.js} +9 -9
  111. package/dist/{rich-code-block-CTPj1LuD.js → rich-code-block-Djj2XE_u.js} +18 -18
  112. package/dist/{scientific-variant-B8U9h3II.js → scientific-variant-BzF_aMQP.js} +8 -8
  113. package/dist/{sortable-provider-CSoWE7Ce.js → sortable-provider-DCZjsIIH.js} +2 -2
  114. package/dist/{spinner-ffuwBXHl.js → spinner-Cg0y_E_K.js} +1 -1
  115. package/dist/standard-layout-DTr9yy8Z.js +48 -0
  116. package/dist/{tabs-JgRq6ZJx.js → tabs-DpessV5s.js} +1 -1
  117. package/dist/{text-field-DURkIP-v.js → text-field-Dxk9lAKM.js} +16 -15
  118. package/dist/theme-picker.js +13 -13
  119. package/dist/{theme-toggle-n_Zg7JAA.js → theme-toggle-B6Jl5Kfn.js} +1 -1
  120. package/dist/{toolbar-items-IANZdcw4.js → toolbar-items-Cs1XrZsH.js} +4 -4
  121. package/dist/{translate-material-CMHUH5Ha.js → translate-material-Du0j0t97.js} +1 -1
  122. package/dist/{translator-9zA-Um3W.js → translator-BVs4n0Fz.js} +52 -2
  123. package/dist/use-calculator-keyboard-BZ1CA4Ut.js +222 -0
  124. package/dist/{use-chat-composer-BoIN7RCA.js → use-chat-composer-B4m3OEm1.js} +241 -240
  125. package/dist/{use-heading-spy-Ciw9GBHL.js → use-heading-spy-CO3_AtY9.js} +235 -154
  126. package/dist/{use-material-translation-CLNaMhUf.js → use-material-translation-uNc1FyC8.js} +1 -1
  127. package/dist/{use-return-focus-DWhbTGVe.js → use-return-focus-AfLRrUm-.js} +1 -1
  128. package/docs/README.md +1 -0
  129. package/docs/components/README.md +1 -0
  130. package/docs/components/buttons-and-actions.md +34 -2
  131. package/docs/components/data-display.md +4 -1
  132. package/docs/components/feedback-and-status.md +20 -0
  133. package/docs/components/navigation-and-layout.md +28 -0
  134. package/docs/components/rich-widgets.md +28 -0
  135. package/docs/components/selection-and-choice.md +120 -0
  136. package/docs/guides/calculator.md +1 -1
  137. package/docs/guides/cards-and-tiles.md +21 -24
  138. package/docs/guides/forms.md +1 -0
  139. package/docs/guides/helpers.md +49 -0
  140. package/docs/guides/keypad.md +225 -0
  141. package/docs/previews/data-display-codeblock.webp +0 -0
  142. package/docs/previews/data-display-codegroup.webp +0 -0
  143. package/docs/previews/data-display-jsonviewer.webp +0 -0
  144. package/docs/previews/navigation-layout-panelcard.webp +0 -0
  145. package/docs/previews/rich-widgets-keypad.webp +0 -0
  146. package/docs/previews/selection-choice-statusmenu.webp +0 -0
  147. package/docs/roadmap.md +21 -0
  148. package/docs/troubleshooting.md +63 -0
  149. package/lib/components/ui/actions/action-card/action-card-tile.css +47 -0
  150. package/lib/components/ui/display/code/code-moonlight.css +10 -10
  151. package/lib/components/ui/widgets/calculator/calculator.css +87 -0
  152. package/package.json +7 -2
  153. package/dist/field-blur-context-BLGgf_3z.js +0 -73
  154. package/dist/key-layout-cR79xfGE.js +0 -38
  155. package/dist/standard-layout-BuSRr3TY.js +0 -40
@@ -0,0 +1,225 @@
1
+ # Keypad
2
+
3
+ `@nxgt/material/keypad` is a numeric keypad you can use outside the Calculator
4
+ widget: a touch and keyboard entry for a quantity, a price, a percentage, or an
5
+ amount tendered in cash with the change worked out. The value is a canonical
6
+ decimal string (`'12.5'`), the arithmetic is exact (`0.1 + 0.2` is `0.3`), and
7
+ nothing is evaluated as code. The maths is `@nxgt/material-core/calculator`, the
8
+ same engine as the [calculator](calculator.md).
9
+
10
+ - [Set up](#set-up)
11
+ - [Standard entry](#standard-entry)
12
+ - [Decimal or minor-unit entry](#decimal-or-minor-unit-entry)
13
+ - [Calculations](#calculations)
14
+ - [Limits](#limits)
15
+ - [Keyboard](#keyboard)
16
+ - [Bind it to a field](#bind-it-to-a-field)
17
+ - [Cash](#cash)
18
+ - [Props](#props)
19
+ - [Sizing and styles](#sizing-and-styles)
20
+ - [Accessibility](#accessibility)
21
+ - [Troubleshooting](#troubleshooting)
22
+
23
+ ## Set up
24
+
25
+ ```tsx
26
+ import { Keypad } from '@nxgt/material/keypad';
27
+ ```
28
+
29
+ The entry is its own: it is not in `@nxgt/material` or
30
+ `@nxgt/material/components`, and it renders on the server. **There is no keypad
31
+ stylesheet**: the Calculator's `calculator.css` styles it, so import it once,
32
+ after `styles.css`:
33
+
34
+ ```css
35
+ @import "@nxgt/material/styles.css";
36
+ @import "@nxgt/material/calculator.css";
37
+ ```
38
+
39
+ (or `import '@nxgt/material/calculator.css'` in your entry module).
40
+
41
+ ## Standard entry
42
+
43
+ ```tsx
44
+ const [qty, setQty] = useState('');
45
+
46
+ <Keypad value={qty} onValueChange={setQty} onCommit={(value) => addLine(value)} />
47
+ ```
48
+
49
+ `value` is controlled with `onValueChange`; `defaultValue` leaves the keypad
50
+ uncontrolled. `onValueChange` receives the value text each time it changes
51
+ (`''` when emptied), but not while a calculation waits for `=`. The commit key (a check mark; "Enter" to a screen reader,
52
+ or your `commitLabel`) and the Enter key call `onCommit(value, expression)`:
53
+ the value held between `min` and `max`, and the expression that gave it. An
54
+ empty keypad commits nothing. The keypad keeps its value after a commit; empty
55
+ it from your side with a controlled `value`.
56
+
57
+ ## Decimal or minor-unit entry
58
+
59
+ `entry="decimal"` (the default) types the number as written: `1`, `2`, `.`, `5`
60
+ is `12.5`.
61
+
62
+ `entry="minor"` types it the way a till does: `1`, `2`, `5` is `1.25`, `00`
63
+ adds two zeros, backspace drops the last digit. It has no decimal key and no
64
+ operations. The decimals come from `currency` (EUR 2, JPY 0, KWD 3) or
65
+ `fractionDigits` (2 by default).
66
+
67
+ ```tsx
68
+ <Keypad entry="minor" currency="EUR" value={price} onValueChange={setPrice} />
69
+ ```
70
+
71
+ `currency` also draws the symbol after the amount. `locale` sets the decimal
72
+ separator, the grouping and the decimal key's glyph. `fractionDigits` limits the
73
+ decimals typed (a further digit is refused) and rounds a calculation half-up to
74
+ them.
75
+
76
+ ## Calculations
77
+
78
+ Pass `operations` to add calculation keys: `true` for add, subtract, multiply
79
+ and divide, or a list such as `['add', 'multiply']`. The commit key then reads
80
+ "=" while a calculation waits, and a second press commits the result:
81
+
82
+ ```tsx
83
+ <Keypad operations onCommit={(value, expression) => save(value, expression)} />
84
+ // 2 + 3 = then the commit key: onCommit('5', '2+3')
85
+ ```
86
+
87
+ An operator pressed after "=" continues from the result. `minor` entry and the
88
+ `cash` variant take no operations.
89
+
90
+ ## Limits
91
+
92
+ - `max`: a digit that would pass it is refused while typing.
93
+ - `min`: never stops typing (`5` on the way to `50` is fine); the commit key
94
+ holds the value at `min`, and tells `onValueChange` when it moved it.
95
+ - `fractionDigits`: see above; `0` removes the decimal key.
96
+
97
+ An operand past `max` inside a calculation is not caught while typing: only the
98
+ commit clamp holds the result.
99
+
100
+ ## Keyboard
101
+
102
+ `keyboard` says where the physical keyboard works:
103
+
104
+ | Value | |
105
+ | --- | --- |
106
+ | `'focus'` (default) | When focus is inside the keypad (after a tap or a Tab to a key) |
107
+ | `'global'` | Anywhere on the page, except in a field. Use it for a screen that is the keypad, such as a till |
108
+ | `false` | Never |
109
+
110
+ Digits, `.` or `,` (by locale), the operators you offer, Backspace, Escape (clear) and
111
+ Enter work. Enter on a focused key presses that key, as a button does; Enter
112
+ elsewhere commits. The keys form one tab stop with arrow keys over the grid,
113
+ Home and End along a row.
114
+
115
+ ## Bind it to a field
116
+
117
+ `useKeypadNumber` and `useKeypadQuantity` bind the keypad to a field you already
118
+ have. Pass the result as `binding`: the keypad reads the field's number, writes
119
+ each change back, takes its decimals, `min` and `max`, and **hides its own
120
+ display** (the field shows the number; pass `display` to show both). With
121
+ `operations`, the display stays on, since the field keeps its number while a
122
+ calculation is typed and `onValueChange` is called with the result at `=`.
123
+
124
+ ```tsx
125
+ import { NumberField } from '@nxgt/material/components';
126
+ import { Keypad, useKeypadNumber } from '@nxgt/material/keypad';
127
+
128
+ const [qty, setQty] = useState<number | null>(1);
129
+ const binding = useKeypadNumber({ value: qty, onChange: setQty, min: 1, max: 99 });
130
+
131
+ <NumberField label="Quantity" value={qty} onChange={setQty} />
132
+ <Keypad binding={binding} />
133
+ ```
134
+
135
+ | Field | Hook |
136
+ | --- | --- |
137
+ | `NumberField`, `PercentField` | `useKeypadNumber({ value, onChange, min, max, fractionDigits })` |
138
+ | `CurrencyField` | `useKeypadNumber({ ..., currency })`; with `minorUnits`, add `minorUnits: true`: the field holds cents (`1250`), the keypad types euros (`12.5`), and `min` / `max` stay in cents |
139
+ | `QuantityField` | `useKeypadQuantity({ value, onChange })`: the keypad types the amount, the unit stays as the field holds it |
140
+
141
+ An explicit prop on `Keypad` wins over the binding's, and both `onValueChange`
142
+ handlers are called. A cleared keypad writes `null`. While a calculation is
143
+ typed (`2+3`) the field keeps its number and nothing is written; the result is
144
+ written at `=`.
145
+
146
+ ## Cash
147
+
148
+ `variant="cash"` takes the amount a customer hands over and works out the change:
149
+
150
+ ```tsx
151
+ <Keypad
152
+ variant="cash"
153
+ total="17.35"
154
+ denominations={[5, 10, 20, 50]}
155
+ currency="EUR"
156
+ onTender={(tendered, settlement) => {
157
+ if (settlement.status === 'paid') charge(tendered, settlement.change);
158
+ else askForMore(settlement.due);
159
+ }}
160
+ />
161
+ ```
162
+
163
+ - The total is shown, then the change or what is **still due** as soon as an
164
+ amount is entered, and a live region says it ("Change 2.65", "Still due 7.35").
165
+ - `denominations` add amount buttons: the exact total, then the next round
166
+ amount at or above it for each bill or coin (`17.35` with `[5, 10, 20, 50]`
167
+ gives `17.35`, `20`, `50`), four buttons at most, none above `max`. A button
168
+ fills the amount; the commit key (named "Tender") reports it.
169
+ - `onTender(tendered, settlement)` is the only callback of the variant (`onCommit`
170
+ is not called). `settlement` is `{ status: 'paid', change }` (change may be
171
+ `'0'`) or `{ status: 'short', due }`, as exact decimal text. A short payment is a
172
+ settlement, not an error. **The app owns the charge**: the keypad only reports.
173
+ - `total` must be a valid, non-negative amount (a number or exact decimal text);
174
+ without one the commit key does nothing.
175
+
176
+ Use `entry="minor"` for a till that types cents, and `size="large"` for a
177
+ counter's touch screen.
178
+
179
+ ## Props
180
+
181
+ | Prop | Default | |
182
+ | --- | --- | --- |
183
+ | `value` / `defaultValue` / `onValueChange` | none | The value as decimal text, controlled or not |
184
+ | `onCommit` | none | `(value, expression)` on the commit key or Enter; standard variant |
185
+ | `binding` | none | From `useKeypadNumber` / `useKeypadQuantity` |
186
+ | `entry` | `'decimal'` | `'decimal'` or `'minor'` |
187
+ | `operations` | off | `true` or a list of `'add' \| 'subtract' \| 'multiply' \| 'divide'` |
188
+ | `locale` | the page's | Separator, grouping, decimal key glyph |
189
+ | `currency` | none | ISO 4217 code: the symbol and the currency's decimals |
190
+ | `fractionDigits` | the currency's, else any | Decimals kept |
191
+ | `min`, `max` | none | See [Limits](#limits) |
192
+ | `keyboard` | `'focus'` | `'focus'`, `'global'` or `false` |
193
+ | `size` | `'comfortable'` | `'comfortable'` or `'large'` |
194
+ | `display` | `true`, `false` with a `binding` and no `operations` | Shows the amount above the keys |
195
+ | `commitLabel` | translated "Enter" ("Tender" in cash) | The commit key's name and text |
196
+ | `disabled` | `false` | Keys do nothing and say so (`aria-disabled`) |
197
+ | `label` | "Keypad" | The group's accessible name |
198
+ | `variant`, `total`, `denominations`, `onTender` | `'standard'` | See [Cash](#cash) |
199
+ | `className` | | On the root |
200
+
201
+ Types: `KeypadProps`, `KeypadEntry`, `KeypadOperation`, `KeypadVariant`, `KeypadSettlement`, `KeypadBinding`.
202
+
203
+ ## Sizing and styles
204
+
205
+ The keypad sizes by its own width with the calculator's bands (compact under
206
+ 360px, wider keys from 600px) and is at most 24rem wide. `size="large"` gives
207
+ taller keys. Colours and shapes follow the theme and dark mode through
208
+ `styles.css`; the key rules are shared with the calculator in `calculator.css`.
209
+
210
+ ## Accessibility
211
+
212
+ - The keys are native buttons in one `role="group"` (one tab stop, arrow keys
213
+ over the grid), at least 44px on a coarse pointer at 360px and 320px.
214
+ - Each key's glyph is decoration; its name is spoken (Plus, Minus, Double zero,
215
+ Clear all, Enter, "Amount €20.00" in English, "Amount 20,00 €" in French). `commitLabel` replaces the commit key's name.
216
+ - Results and, in cash, the change or amount still due are announced in a
217
+ polite live region; the display itself is not live, so typing is not read digit by digit.
218
+ - Wording is the `material.keypad.*` and `material.calculator.keys.*` strings
219
+ (English and French shipped), overridable through `I18nProvider` `resources`.
220
+
221
+ ## Troubleshooting
222
+
223
+ See [the keypad entries](../troubleshooting.md#keypad): unstyled keys, a keyboard
224
+ that types nothing, a Tender
225
+ key that does nothing, and a digit that is refused.
package/docs/roadmap.md CHANGED
@@ -6,6 +6,15 @@ version they shipped in, and planned entries may name the major they target.
6
6
 
7
7
  ## Now
8
8
 
9
+ - **Keypad** — `@nxgt/material/keypad` is a touch and keyboard numeric keypad
10
+ you can use outside the Calculator widget, for quantity, price, percentage
11
+ and cash-tendered entry with change. Entry is a decimal or minor units, the
12
+ display is optional, and the keyboard is on `'focus'` by default (opt in to
13
+ `'global'`). `useKeypadNumber` and `useKeypadQuantity` bind it to
14
+ `NumberField`, `PercentField` and `CurrencyField`. A `cash` variant reports
15
+ `onTender(tendered, settlement)` and leaves the charge to your app. It is
16
+ styled by `calculator.css`, with no separate stylesheet
17
+ ([guide](guides/keypad.md)).
9
18
  - **Calculator** — `@nxgt/material/calculator` is an exact-decimal calculator
10
19
  (Standard, Scientific, point of sale, Financial, Conversion, Programmer;
11
20
  every variant but Standard loads on demand)
@@ -257,6 +266,9 @@ version they shipped in, and planned entries may name the major they target.
257
266
 
258
267
  ## Later
259
268
 
269
+ - **Keypad for Vue** — the `Keypad`, its field bindings and the cash variant on
270
+ `@nxgt/material-vue`, on the same engine and stylesheet
271
+ ([#2830](https://github.com/softistx/nxgt-material/issues/2830)).
260
272
  - **A lighter first load for the app shell** — `ActivityLayout` pulls about
261
273
  831 KB gzipped through the library's single shared chunk; splitting it so the
262
274
  shell costs tens of KB at first load, the rest loaded where a page uses it
@@ -379,6 +391,15 @@ version they shipped in, and planned entries may name the major they target.
379
391
 
380
392
  ## Shipped
381
393
 
394
+ - **A status chip that opens its choices** — `StatusMenu` shows a record's
395
+ status as a tone-and-icon chip and changes it in one tap: a menu on a wide
396
+ screen, a bottom sheet on a phone. Statuses not reachable from the current
397
+ one are greyed with the reason, a locked or read-only record explains why,
398
+ a slow save keeps the old status with a spinner and announces a failure,
399
+ and a choice can ask for confirmation. `StatusMenuFormField` binds it to a
400
+ form, and `@nxgt/material-core/status-menu` holds the model; the status
401
+ board recipe's detail sheet uses it
402
+ ([#1653](https://github.com/softistx/nxgt-material/issues/1653)).
382
403
  - **Archived status filter (3.99.0)** — `archivedStatusField()` adds an active /
383
404
  archived / all choice to a table's filter model as a segmented chip group
384
405
  that is always set; `parseArchivedStatus` reads it in a loader and
@@ -495,6 +495,13 @@ words in your error.
495
495
  - [`The point of sale's Charge button stays off`](#the-point-of-sales-charge-button-stays-off)
496
496
  - [`The Financial calculator shows no currency`](#the-financial-calculator-shows-no-currency)
497
497
 
498
+ **Keypad**
499
+
500
+ - [`The keypad is unstyled`](#the-keypad-is-unstyled)
501
+ - [`A keypad key types nothing on the page`](#a-keypad-key-types-nothing-on-the-page)
502
+ - [`A digit is refused by the keypad`](#a-digit-is-refused-by-the-keypad)
503
+ - [`The cash keypad's Tender key does nothing`](#the-cash-keypads-tender-key-does-nothing)
504
+
498
505
  **Barcodes**
499
506
 
500
507
  - [`Code 128 holds ASCII only (0-127)`](#code-128-holds-ascii-only-0-127)
@@ -504,6 +511,10 @@ words in your error.
504
511
  - [`<name> must be a number (millimetres) or a length such as "50mm"`](#name-must-be-a-number-millimetres-or-a-length-such-as-50mm)
505
512
  - [`Unknown symbology "x": use "code128" or "datamatrix"`](#unknown-symbology-x-use-code128-or-datamatrix)
506
513
 
514
+ **Status menu**
515
+
516
+ - [`[status-menu] the value "…" matches no choice`](#status-menu-the-value--matches-no-choice)
517
+
507
518
  ---
508
519
 
509
520
  ## Install
@@ -8422,6 +8433,39 @@ threw (offline, a 500). **Try again** calls it again. Pass the `signal` to your
8422
8433
 
8423
8434
  ---
8424
8435
 
8436
+ ## Keypad
8437
+
8438
+ ### `The keypad is unstyled`
8439
+
8440
+ The keys are plain buttons in a column with no shape.
8441
+
8442
+ **Why:** the keypad has no stylesheet of its own; the Calculator's `calculator.css` styles it, and `styles.css` does not import that.
8443
+ **Fix:** import it once, after `styles.css`: `@import "@nxgt/material/calculator.css";` (or `import '@nxgt/material/calculator.css'`).
8444
+
8445
+ ### `A keypad key types nothing on the page`
8446
+
8447
+ **When:** you press a digit on the physical keyboard and nothing is entered.
8448
+ **Why:** `keyboard` is `'focus'` by default: the keys work when focus is inside the keypad (tap a key or Tab to it first), and never in `false`. A key the keypad does not offer (`+` without `operations`, `.` at `fractionDigits={0}` or in `minor` entry) is left to the page.
8449
+ **Fix:** for a screen that is the keypad (a till), set `keyboard="global"`; it still never takes a key typed in a field.
8450
+
8451
+ ```tsx
8452
+ <Keypad keyboard="global" />
8453
+ ```
8454
+
8455
+ ### `A digit is refused by the keypad`
8456
+
8457
+ Not an error. A digit that would pass `max`, or add a decimal past `fractionDigits` (or the `currency`'s decimals), is ignored; `min` never refuses a digit, it holds the value when the commit key is pressed.
8458
+
8459
+ ### `The cash keypad's Tender key does nothing`
8460
+
8461
+ **When:** `variant="cash"` and the commit key (Tender) or Enter calls nothing.
8462
+ **Why:** `onTender` is called only for a valid, non-negative `total` and a non-empty amount. A missing, negative or non-numeric `total` does nothing; `onCommit` is never called in this variant.
8463
+ **Fix:** pass `total` as a number or exact decimal text and use `onTender`.
8464
+
8465
+ ```tsx
8466
+ <Keypad variant="cash" total="17.35" onTender={(tendered, settlement) => pay(tendered, settlement)} />
8467
+ ```
8468
+
8425
8469
  ## Barcodes
8426
8470
 
8427
8471
  ### `Code 128 holds ASCII only (0-127)`
@@ -8475,6 +8519,25 @@ Raised by `@nxgt/material/barcode` while rendering. `symbology` is neither value
8475
8519
 
8476
8520
  ---
8477
8521
 
8522
+ ## Status menu
8523
+
8524
+ ### `[status-menu] the value "…" matches no choice`
8525
+
8526
+ A development warning from `StatusMenu` and `StatusMenuFormField`. `value` is not among `choices`: an empty string (a form with no initial status) or a status the server sent that the app did not list. The chip then shows an empty value.
8527
+
8528
+ Give an initial status, or add the choice.
8529
+
8530
+ ```tsx
8531
+ const form = useForm({ defaultValues: { status: 'open' } });
8532
+
8533
+ const statuses: StatusChoice<'open' | 'archived'>[] = [
8534
+ { value: 'open', label: 'Open' },
8535
+ { value: 'archived', label: 'Archived' }, // a status the server can send
8536
+ ];
8537
+ ```
8538
+
8539
+ ---
8540
+
8478
8541
  ## Reporting a bug
8479
8542
 
8480
8543
  If an error comes from inside this package and none of the entries above
@@ -133,6 +133,53 @@
133
133
  align-self: start;
134
134
  }
135
135
 
136
+ /* A tile with an action wraps instead of overflowing: a flex line holds the
137
+ * icon, the title and the action; the title may shrink to --ac-title-min
138
+ * (4.5rem), so an action that does not fit beside it (a StatusMenu chip in
139
+ * a narrow tile) drops under the title, where an icon button still fits
140
+ * beside it. The status, description, meta and footer are full-width lines.
141
+ * No query, no measuring. */
142
+ .nxgt-action-card[data-size="tile"][data-has-action] {
143
+ --ac-icon-offset: 0px;
144
+ display: flex;
145
+ flex-wrap: wrap;
146
+ align-items: flex-start;
147
+ align-content: flex-start;
148
+ }
149
+ .nxgt-action-card[data-size="tile"][data-has-action][data-icon] {
150
+ --ac-icon-offset: 44px;
151
+ }
152
+ .nxgt-action-card[data-size="tile"][data-has-action]
153
+ > .nxgt-action-card-icon {
154
+ flex: none;
155
+ }
156
+ .nxgt-action-card[data-size="tile"][data-has-action] .nxgt-action-card-title {
157
+ order: 1;
158
+ flex: 1 1 var(--ac-title-min, 4.5rem);
159
+ }
160
+ .nxgt-action-card[data-size="tile"][data-has-action]
161
+ > .nxgt-action-card-action {
162
+ order: 2;
163
+ flex: none;
164
+ max-inline-size: 100%;
165
+ }
166
+ .nxgt-action-card[data-size="tile"][data-has-action]
167
+ > .nxgt-action-card-indicator {
168
+ order: 3;
169
+ flex: none;
170
+ margin-inline-start: auto;
171
+ }
172
+ .nxgt-action-card[data-size="tile"][data-has-action] .nxgt-action-card-status,
173
+ .nxgt-action-card[data-size="tile"][data-has-action]
174
+ .nxgt-action-card-description,
175
+ .nxgt-action-card[data-size="tile"][data-has-action] .nxgt-action-card-meta,
176
+ .nxgt-action-card[data-size="tile"][data-has-action]
177
+ .nxgt-action-card-footer {
178
+ order: 4;
179
+ flex: 0 0 calc(100% - var(--ac-icon-offset));
180
+ margin-inline-start: var(--ac-icon-offset);
181
+ }
182
+
136
183
  @media (forced-colors: active) {
137
184
  .nxgt-action-card:has(.nxgt-action-card-button:focus-visible) {
138
185
  outline: 3px solid CanvasText;
@@ -4,7 +4,7 @@
4
4
  [data-slot="code-block"][data-scheme="dark"] {
5
5
  --code-bg: #222436;
6
6
  --code-fg: #c8d3f5;
7
- --code-comment: #7a88cf;
7
+ --code-comment: #8f9ad6;
8
8
  --code-blue: #82aaff;
9
9
  --code-cyan: #86e1fc;
10
10
  --code-green: #c3e88d;
@@ -19,15 +19,15 @@
19
19
  [data-slot="code-block"][data-scheme="light"] {
20
20
  --code-bg: #f5f7ff;
21
21
  --code-fg: #383a51;
22
- --code-comment: #7a88cf;
23
- --code-blue: #3e68d7;
24
- --code-cyan: #0b8ec2;
25
- --code-green: #4db380;
26
- --code-orange: #c44c22;
27
- --code-yellow: #ac8300;
28
- --code-red: #c53b40;
29
- --code-magenta: #7a5af5;
30
- --code-pink: #b04d9a;
22
+ --code-comment: #4356ba;
23
+ --code-blue: #2a56ca;
24
+ --code-cyan: #08658a;
25
+ --code-green: #2d6a4c;
26
+ --code-orange: #a33f1c;
27
+ --code-yellow: #765a00;
28
+ --code-red: #ac3337;
29
+ --code-magenta: #5d36f3;
30
+ --code-pink: #924080;
31
31
  --code-selection: #d4daf0;
32
32
  }
33
33
 
@@ -95,3 +95,90 @@
95
95
  animation: none;
96
96
  }
97
97
  }
98
+
99
+ /* Keypad (`@nxgt/material/keypad`): the same keys and bands as the Calculator,
100
+ * on its own root. There is no keypad stylesheet; `.nxgt-calc` gives the root
101
+ * the container the bands read. A keypad is a column, never wider than a
102
+ * phone's keypad, and `large` is a till's. */
103
+ .nxgt-keypad {
104
+ max-width: 24rem;
105
+ }
106
+ .nxgt-keypad-body {
107
+ padding-block-start: 0;
108
+ }
109
+ .nxgt-keypad[data-size="large"] .nxgt-calc-body {
110
+ --calc-key: 4.5rem;
111
+ --calc-gap: 0.625rem;
112
+ }
113
+ .nxgt-keypad-display {
114
+ display: block;
115
+ min-width: 0;
116
+ padding: 1rem 1.25rem;
117
+ border-radius: var(--calc-radius);
118
+ font-size: var(--calc-result);
119
+ font-weight: 500;
120
+ line-height: 1.1;
121
+ letter-spacing: -0.02em;
122
+ font-variant-numeric: tabular-nums;
123
+ text-align: right;
124
+ direction: ltr;
125
+ overflow-wrap: anywhere;
126
+ }
127
+ .nxgt-keypad-display[data-kind="error"] {
128
+ color: var(--destructive);
129
+ }
130
+ .nxgt-keypad[data-disabled] .nxgt-calc-key {
131
+ opacity: 0.5;
132
+ cursor: not-allowed;
133
+ }
134
+
135
+ /* Keypad, cash variant: the total and change above the keys, the bill
136
+ * shortcuts under them. */
137
+ .nxgt-keypad-cash {
138
+ display: flex;
139
+ flex-direction: column;
140
+ gap: 0.25rem;
141
+ margin: 0;
142
+ padding-inline: 0.5rem;
143
+ font-variant-numeric: tabular-nums;
144
+ }
145
+ .nxgt-keypad-cash > div {
146
+ display: flex;
147
+ align-items: baseline;
148
+ justify-content: space-between;
149
+ gap: 0.5rem;
150
+ }
151
+ .nxgt-keypad-cash dt {
152
+ color: var(--muted-foreground);
153
+ }
154
+ .nxgt-keypad-cash dd {
155
+ margin: 0;
156
+ font-size: 1.25rem;
157
+ font-weight: 600;
158
+ }
159
+ .nxgt-keypad-cash [data-status="short"] dd {
160
+ color: var(--destructive);
161
+ }
162
+ .nxgt-keypad-shortcuts {
163
+ display: flex;
164
+ flex-wrap: wrap;
165
+ gap: var(--calc-gap);
166
+ }
167
+ .nxgt-keypad-shortcuts button {
168
+ flex: 1 1 4.5rem;
169
+ min-width: 0;
170
+ min-height: max(calc(var(--calc-key) * 0.8), 2.75rem);
171
+ border: 0;
172
+ border-radius: 9999px;
173
+ background: var(--secondary-container);
174
+ color: var(--foreground);
175
+ font: inherit;
176
+ font-weight: 500;
177
+ font-variant-numeric: tabular-nums;
178
+ cursor: pointer;
179
+ touch-action: manipulation;
180
+ }
181
+ .nxgt-keypad-shortcuts button:focus-visible {
182
+ outline: 2px solid var(--ring);
183
+ outline-offset: 2px;
184
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxgt/material",
3
- "version": "3.101.0",
3
+ "version": "3.103.0",
4
4
  "license": "MIT",
5
5
  "bugs": {
6
6
  "email": "stevegires@gmail.com"
@@ -85,6 +85,11 @@
85
85
  "types": "./dist/lib/calculator/index.d.ts",
86
86
  "require": "./dist/calculator.js"
87
87
  },
88
+ "./keypad": {
89
+ "import": "./dist/keypad.js",
90
+ "types": "./dist/lib/keypad/index.d.ts",
91
+ "require": "./dist/keypad.js"
92
+ },
88
93
  "./calculator-field": {
89
94
  "import": "./dist/calculator-field.js",
90
95
  "types": "./dist/lib/calculator-field/index.d.ts",
@@ -390,7 +395,7 @@
390
395
  "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"
391
396
  },
392
397
  "dependencies": {
393
- "@nxgt/material-core": "^0.57.0",
398
+ "@nxgt/material-core": "^0.58.0",
394
399
  "@base-ui/react": "^1.6.0",
395
400
  "@dnd-kit/core": "^6.3.1",
396
401
  "@dnd-kit/sortable": "^10.0.0",
@@ -1,73 +0,0 @@
1
- import { t as e } from "./utils-BLfntITJ.js";
2
- import { i as t } from "./dist-DWZXlCZR.js";
3
- import { t as n } from "./typography-CYEquVeb.js";
4
- import * as r from "react";
5
- import { createContext as i, use as a, useMemo as o } from "react";
6
- import { jsx as s, jsxs as c } from "react/jsx-runtime";
7
- //#region ../../node_modules/@radix-ui/react-label/dist/index.mjs
8
- var l = Object.defineProperty, u = /* @__PURE__ */ r.forwardRef(/* @__PURE__ */ ((e, t) => l(e, "name", {
9
- value: t,
10
- configurable: !0
11
- }))(function(e, n) {
12
- return /* @__PURE__ */ s(t.label, {
13
- ...e,
14
- ref: n,
15
- onMouseDown: (t) => {
16
- t.target.closest("button, input, select, textarea") || (e.onMouseDown?.(t), !t.defaultPrevented && t.detail > 1 && t.preventDefault());
17
- }
18
- });
19
- }, "Label"));
20
- //#endregion
21
- //#region lib/components/ui/primitives/helper-text/helper-text.tsx
22
- function d({ error: t, className: r, ...i }) {
23
- return /* @__PURE__ */ s(n, {
24
- "data-slot": "helper-text",
25
- variant: "caption",
26
- className: e("flex ps-0.5 truncate text-wrap", { "text-error-text": t }, r),
27
- ...i
28
- });
29
- }
30
- //#endregion
31
- //#region lib/components/ui/primitives/label/label.tsx
32
- function f() {
33
- return /* @__PURE__ */ s("svg", {
34
- "data-slot": "label-required-mark",
35
- "aria-hidden": "true",
36
- focusable: "false",
37
- viewBox: "0 0 10 10",
38
- fill: "none",
39
- stroke: "currentColor",
40
- strokeWidth: "1.75",
41
- strokeLinecap: "round",
42
- children: /* @__PURE__ */ s("path", { d: "M5 1v8M1.5 3l7 4M8.5 3l-7 4" })
43
- });
44
- }
45
- function p({ className: t, withAsterisk: n, ...r }) {
46
- return /* @__PURE__ */ s(u, {
47
- "data-slot": "label",
48
- className: e("flex items-center ps-0.5 gap-2 text-sm leading-5 -my-[3px] empty:my-0 font-semibold select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50", t),
49
- ...r,
50
- children: n ? /* @__PURE__ */ c("span", {
51
- "data-slot": "label-text",
52
- "data-required": "",
53
- className: "inline-flex",
54
- children: [r.children, /* @__PURE__ */ s(f, {})]
55
- }) : r.children
56
- });
57
- }
58
- //#endregion
59
- //#region lib/components/ui/forms/field-blur-context.ts
60
- var m = i(null);
61
- function h(e, t) {
62
- return o(() => e ? t ? (n) => {
63
- let r = e(n), i = typeof t == "function" ? t(n) : void 0;
64
- return typeof t != "function" && (t.current = n), () => {
65
- r?.(), typeof i == "function" ? i() : typeof t == "function" ? t(null) : t.current = null;
66
- };
67
- } : e : t, [e, t]);
68
- }
69
- function g(e) {
70
- return h(a(m), e);
71
- }
72
- //#endregion
73
- export { d as a, p as i, g as n, h as r, m as t };