@nxgt/material 3.75.0 → 3.76.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 (239) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +37 -1
  3. package/dist/{alert-dialog-BfO_ri-N.js → alert-dialog-653apk3f.js} +8 -7
  4. package/dist/{async-slice-roMAJVzG.js → async-slice-CXLbQuOc.js} +1 -1
  5. package/dist/{bubble-input-B7BVd9fX.js → bubble-input-CQwVSoU3.js} +30 -29
  6. package/dist/{button-DgenqmpS.js → button-3op3rtZh.js} +5 -4
  7. package/dist/calculator-BaSMe0SK.js +1189 -0
  8. package/dist/calculator-announce-DRd2c6Dq.js +89 -0
  9. package/dist/calculator-context-BugcKuVG.js +10 -0
  10. package/dist/calculator-field.js +172 -0
  11. package/dist/calculator-panel-announce-AZSnZUSr.js +29 -0
  12. package/dist/calculator-segment-BlJE1LaS.js +22 -0
  13. package/dist/calculator.js +2 -0
  14. package/dist/charts.js +3 -3
  15. package/dist/{chip-CiDczNxO.js → chip-MuKDwtkG.js} +2 -2
  16. package/dist/{code-editor-CohlhFsA.js → code-editor-Bp2RrdNx.js} +13 -13
  17. package/dist/code-editor.js +37 -37
  18. package/dist/{code-language-items-Cs8GLYDp.js → code-language-items-B7WR5ua_.js} +19 -24
  19. package/dist/components/base-content.css +9 -4
  20. package/dist/components/ui/collections/server-table/server-table-phone-row.css +10 -3
  21. package/dist/{components-zJdfH4im.js → components-Ci3Zo8kC.js} +15837 -17245
  22. package/dist/components.js +37 -28
  23. package/dist/conversion-variant-BDFAxqe9.js +529 -0
  24. package/dist/copy-to-clipboard-Be5Ffvs_.js +7 -0
  25. package/dist/currency-B-r3ig11.js +16 -0
  26. package/dist/currency-field-BJ9rSrif.js +285 -0
  27. package/dist/{number.utils-BG_eEt5g.js → date.utils-DUvqcjan.js} +1 -7
  28. package/dist/dialog-Bm5LvVlB.js +166 -0
  29. package/dist/dist-BZe2evxL.js +139 -0
  30. package/dist/dist-CxhKjOYm.js +28 -0
  31. package/dist/dist-DHdUUM1w.js +241 -0
  32. package/dist/{dist-C4wtXCjf.js → dist-DOK3zo5C.js} +33 -32
  33. package/dist/dist-DWZXlCZR.js +182 -0
  34. package/dist/dist-DkQbkF9T.js +33 -0
  35. package/dist/dnd.js +1 -1
  36. package/dist/{dropdown-menu-CzR6JP31.js → dropdown-menu-xlZ6NYUA.js} +160 -159
  37. package/dist/editor.js +299 -298
  38. package/dist/{empty-state-DjHGMp5F.js → empty-state-C0kviQdW.js} +2 -2
  39. package/dist/{field-blur-CdASxjL7.js → field-blur-DkiZOVNV.js} +178 -180
  40. package/dist/{field-blur-context-kZ4SD7xu.js → field-blur-context-BLGgf_3z.js} +2 -2
  41. package/dist/financial-variant-BsZEa5sF.js +459 -0
  42. package/dist/{hooks-Dqq8O3Az.js → hooks-Dg94jPRq.js} +12 -12
  43. package/dist/hooks.js +2 -2
  44. package/dist/{hover-card-BPKqte4a.js → hover-card-B1g55Hi6.js} +44 -43
  45. package/dist/i18n.js +3 -3
  46. package/dist/icon-badge-BcW66bRk.js +17 -0
  47. package/dist/{icon-button-Cr-DyDGg.js → icon-button-Cogxy1cy.js} +5 -4
  48. package/dist/{in-dialog-Vwkyp-M5.js → in-dialog-D61Yeaol.js} +2 -1
  49. package/dist/input-CC2joTjB.js +56 -0
  50. package/dist/key-layout-cR79xfGE.js +38 -0
  51. package/dist/lib/calculator/index.d.ts +16 -0
  52. package/dist/lib/calculator-field/index.d.ts +12 -0
  53. package/dist/lib/components/ui/collections/filter/use-filter-field-config.d.ts +2 -2
  54. package/dist/lib/components/ui/feedback/moment/moment-body.d.ts +17 -2
  55. package/dist/lib/components/ui/feedback/moment/moment-passive.d.ts +13 -0
  56. package/dist/lib/components/ui/feedback/moment/moment.d.ts +16 -2
  57. package/dist/lib/components/ui/fields/calculator-field/apply-calculated.d.ts +17 -0
  58. package/dist/lib/components/ui/fields/calculator-field/calculator-field-content.d.ts +16 -0
  59. package/dist/lib/components/ui/fields/calculator-field/calculator-field-trigger.d.ts +4 -0
  60. package/dist/lib/components/ui/fields/calculator-field/calculator-field.d.ts +12 -0
  61. package/dist/lib/components/ui/fields/calculator-field/calculator-field.types.d.ts +31 -0
  62. package/dist/lib/components/ui/fields/calculator-field/calculator-form-field.d.ts +4 -0
  63. package/dist/lib/components/ui/fields/calculator-field/index.d.ts +3 -0
  64. package/dist/lib/components/ui/fields/calculator-field/use-calculator-field.d.ts +15 -0
  65. package/dist/lib/components/ui/fields/contacts/placeholders.d.ts +1 -1
  66. package/dist/lib/components/ui/fields/currency-field/currency-field.d.ts +7 -1
  67. package/dist/lib/components/ui/layout/app-shell/app-sidebar-context.d.ts +4 -0
  68. package/dist/lib/components/ui/widgets/calculator/calculator-announce.d.ts +27 -0
  69. package/dist/lib/components/ui/widgets/calculator/calculator-clipboard.d.ts +19 -0
  70. package/dist/lib/components/ui/widgets/calculator/calculator-context.d.ts +49 -0
  71. package/dist/lib/components/ui/widgets/calculator/calculator-feedback.d.ts +18 -0
  72. package/dist/lib/components/ui/widgets/calculator/calculator-keyboard.d.ts +30 -0
  73. package/dist/lib/components/ui/widgets/calculator/calculator-storage.d.ts +17 -0
  74. package/dist/lib/components/ui/widgets/calculator/calculator-variant-steps.d.ts +16 -0
  75. package/dist/lib/components/ui/widgets/calculator/calculator.d.ts +11 -0
  76. package/dist/lib/components/ui/widgets/calculator/calculator.selectors.d.ts +23 -0
  77. package/dist/lib/components/ui/widgets/calculator/calculator.slice.d.ts +1881 -0
  78. package/dist/lib/components/ui/widgets/calculator/currency.d.ts +9 -0
  79. package/dist/lib/components/ui/widgets/calculator/engine.d.ts +22 -0
  80. package/dist/lib/components/ui/widgets/calculator/index.d.ts +3 -0
  81. package/dist/lib/components/ui/widgets/calculator/partials/calculator-announcer.d.ts +2 -0
  82. package/dist/lib/components/ui/widgets/calculator/partials/calculator-display.d.ts +3 -0
  83. package/dist/lib/components/ui/widgets/calculator/partials/calculator-key.d.ts +17 -0
  84. package/dist/lib/components/ui/widgets/calculator/partials/calculator-keypad.d.ts +8 -0
  85. package/dist/lib/components/ui/widgets/calculator/partials/calculator-loading.d.ts +6 -0
  86. package/dist/lib/components/ui/widgets/calculator/partials/calculator-memory.d.ts +2 -0
  87. package/dist/lib/components/ui/widgets/calculator/partials/calculator-panel-announce.d.ts +10 -0
  88. package/dist/lib/components/ui/widgets/calculator/partials/calculator-panel-body.d.ts +9 -0
  89. package/dist/lib/components/ui/widgets/calculator/partials/calculator-screen.d.ts +4 -0
  90. package/dist/lib/components/ui/widgets/calculator/partials/calculator-segment.d.ts +21 -0
  91. package/dist/lib/components/ui/widgets/calculator/partials/calculator-shell.d.ts +9 -0
  92. package/dist/lib/components/ui/widgets/calculator/partials/calculator-switcher.d.ts +5 -0
  93. package/dist/lib/components/ui/widgets/calculator/partials/calculator-tape.d.ts +4 -0
  94. package/dist/lib/components/ui/widgets/calculator/partials/calculator-tools.d.ts +4 -0
  95. package/dist/lib/components/ui/widgets/calculator/partials/keypad-navigation.d.ts +24 -0
  96. package/dist/lib/components/ui/widgets/calculator/types.d.ts +102 -0
  97. package/dist/lib/components/ui/widgets/calculator/use-calculator-clipboard.d.ts +19 -0
  98. package/dist/lib/components/ui/widgets/calculator/use-calculator-conversion.d.ts +4 -0
  99. package/dist/lib/components/ui/widgets/calculator/use-calculator-feedback.d.ts +3 -0
  100. package/dist/lib/components/ui/widgets/calculator/use-calculator-keyboard.d.ts +22 -0
  101. package/dist/lib/components/ui/widgets/calculator/use-calculator-persistence.d.ts +14 -0
  102. package/dist/lib/components/ui/widgets/calculator/use-calculator-pos.d.ts +25 -0
  103. package/dist/lib/components/ui/widgets/calculator/use-calculator-press.d.ts +26 -0
  104. package/dist/lib/components/ui/widgets/calculator/use-calculator-tape.d.ts +29 -0
  105. package/dist/lib/components/ui/widgets/calculator/use-calculator-value.d.ts +9 -0
  106. package/dist/lib/components/ui/widgets/calculator/use-calculator-variant.d.ts +23 -0
  107. package/dist/lib/components/ui/widgets/calculator/use-calculator.d.ts +10 -0
  108. package/dist/lib/components/ui/widgets/calculator/use-engine-settings.d.ts +10 -0
  109. package/dist/lib/components/ui/widgets/calculator/variants/conversion/conversion-fields.d.ts +19 -0
  110. package/dist/lib/components/ui/widgets/calculator/variants/conversion/conversion-labels.d.ts +17 -0
  111. package/dist/lib/components/ui/widgets/calculator/variants/conversion/conversion-math.d.ts +31 -0
  112. package/dist/lib/components/ui/widgets/calculator/variants/conversion/conversion-names.d.ts +4 -0
  113. package/dist/lib/components/ui/widgets/calculator/variants/conversion/conversion-panel.d.ts +7 -0
  114. package/dist/lib/components/ui/widgets/calculator/variants/conversion/conversion-rates.d.ts +29 -0
  115. package/dist/lib/components/ui/widgets/calculator/variants/conversion/conversion-result.d.ts +15 -0
  116. package/dist/lib/components/ui/widgets/calculator/variants/conversion/conversion-state.d.ts +36 -0
  117. package/dist/lib/components/ui/widgets/calculator/variants/conversion/conversion-variant.d.ts +3 -0
  118. package/dist/lib/components/ui/widgets/calculator/variants/conversion/use-conversion.d.ts +24 -0
  119. package/dist/lib/components/ui/widgets/calculator/variants/financial/financial-change.d.ts +4 -0
  120. package/dist/lib/components/ui/widgets/calculator/variants/financial/financial-figures.d.ts +43 -0
  121. package/dist/lib/components/ui/widgets/calculator/variants/financial/financial-format.d.ts +12 -0
  122. package/dist/lib/components/ui/widgets/calculator/variants/financial/financial-loan-math.d.ts +14 -0
  123. package/dist/lib/components/ui/widgets/calculator/variants/financial/financial-loan.d.ts +4 -0
  124. package/dist/lib/components/ui/widgets/calculator/variants/financial/financial-margin.d.ts +4 -0
  125. package/dist/lib/components/ui/widgets/calculator/variants/financial/financial-math.d.ts +10 -0
  126. package/dist/lib/components/ui/widgets/calculator/variants/financial/financial-panel.d.ts +7 -0
  127. package/dist/lib/components/ui/widgets/calculator/variants/financial/financial-parts.d.ts +35 -0
  128. package/dist/lib/components/ui/widgets/calculator/variants/financial/financial-table.d.ts +7 -0
  129. package/dist/lib/components/ui/widgets/calculator/variants/financial/financial-variant.d.ts +3 -0
  130. package/dist/lib/components/ui/widgets/calculator/variants/financial/financial-vat.d.ts +4 -0
  131. package/dist/lib/components/ui/widgets/calculator/variants/key-layout.d.ts +51 -0
  132. package/dist/lib/components/ui/widgets/calculator/variants/pos/pos-announce.d.ts +16 -0
  133. package/dist/lib/components/ui/widgets/calculator/variants/pos/pos-aside.d.ts +2 -0
  134. package/dist/lib/components/ui/widgets/calculator/variants/pos/pos-display.d.ts +2 -0
  135. package/dist/lib/components/ui/widgets/calculator/variants/pos/pos-fields.d.ts +2 -0
  136. package/dist/lib/components/ui/widgets/calculator/variants/pos/pos-layout.d.ts +9 -0
  137. package/dist/lib/components/ui/widgets/calculator/variants/pos/pos-line.d.ts +6 -0
  138. package/dist/lib/components/ui/widgets/calculator/variants/pos/pos-names.d.ts +2 -0
  139. package/dist/lib/components/ui/widgets/calculator/variants/pos/pos-state.d.ts +3 -0
  140. package/dist/lib/components/ui/widgets/calculator/variants/pos/pos-tender.d.ts +2 -0
  141. package/dist/lib/components/ui/widgets/calculator/variants/pos/pos-ticket-data.d.ts +41 -0
  142. package/dist/lib/components/ui/widgets/calculator/variants/pos/pos-ticket.d.ts +5 -0
  143. package/dist/lib/components/ui/widgets/calculator/variants/pos/pos-totals.d.ts +2 -0
  144. package/dist/lib/components/ui/widgets/calculator/variants/pos/pos-types.d.ts +76 -0
  145. package/dist/lib/components/ui/widgets/calculator/variants/pos/pos-variant.d.ts +3 -0
  146. package/dist/lib/components/ui/widgets/calculator/variants/pos/pos-vat.d.ts +2 -0
  147. package/dist/lib/components/ui/widgets/calculator/variants/pos/pos-view.d.ts +28 -0
  148. package/dist/lib/components/ui/widgets/calculator/variants/programmer/programmer-announce.d.ts +19 -0
  149. package/dist/lib/components/ui/widgets/calculator/variants/programmer/programmer-base.d.ts +11 -0
  150. package/dist/lib/components/ui/widgets/calculator/variants/programmer/programmer-bitpad.d.ts +14 -0
  151. package/dist/lib/components/ui/widgets/calculator/variants/programmer/programmer-display.d.ts +9 -0
  152. package/dist/lib/components/ui/widgets/calculator/variants/programmer/programmer-layout.d.ts +6 -0
  153. package/dist/lib/components/ui/widgets/calculator/variants/programmer/programmer-names.d.ts +2 -0
  154. package/dist/lib/components/ui/widgets/calculator/variants/programmer/programmer-readouts.d.ts +14 -0
  155. package/dist/lib/components/ui/widgets/calculator/variants/programmer/programmer-reduce.d.ts +29 -0
  156. package/dist/lib/components/ui/widgets/calculator/variants/programmer/programmer-state.d.ts +22 -0
  157. package/dist/lib/components/ui/widgets/calculator/variants/programmer/programmer-toolbar.d.ts +5 -0
  158. package/dist/lib/components/ui/widgets/calculator/variants/programmer/programmer-variant.d.ts +3 -0
  159. package/dist/lib/components/ui/widgets/calculator/variants/programmer/programmer-word.d.ts +15 -0
  160. package/dist/lib/components/ui/widgets/calculator/variants/registry.d.ts +21 -0
  161. package/dist/lib/components/ui/widgets/calculator/variants/scientific/scientific-layout.d.ts +3 -0
  162. package/dist/lib/components/ui/widgets/calculator/variants/scientific/scientific-names.d.ts +2 -0
  163. package/dist/lib/components/ui/widgets/calculator/variants/scientific/scientific-toolbar.d.ts +2 -0
  164. package/dist/lib/components/ui/widgets/calculator/variants/scientific/scientific-variant.d.ts +3 -0
  165. package/dist/lib/components/ui/widgets/calculator/variants/standard/standard-layout.d.ts +3 -0
  166. package/dist/lib/components/ui/widgets/calculator/variants/variant-loader.d.ts +34 -0
  167. package/dist/lib/components/ui/widgets/calculator/variants/variant-logic.d.ts +40 -0
  168. package/dist/lib/components/ui/widgets/calculator/variants/variant-types.d.ts +62 -0
  169. package/dist/lib/i18n/resources/en/en.d.ts +412 -0
  170. package/dist/lib/i18n/resources/en/material.json.d.ts +412 -0
  171. package/dist/lib/i18n/resources/fr/fr.d.ts +412 -0
  172. package/dist/lib/i18n/resources/fr/material.json.d.ts +412 -0
  173. package/dist/lib/i18n/resources/resources.d.ts +824 -0
  174. package/dist/lib/i18n/translator.d.ts +1 -1
  175. package/dist/lib.js +4 -3
  176. package/dist/main.js +50 -40
  177. package/dist/map.js +66 -65
  178. package/dist/material.css +1 -1
  179. package/dist/native-select-w_FlBLHV.js +40 -0
  180. package/dist/number.utils-CSueQZcv.js +7 -0
  181. package/dist/pdf-reader.js +1 -1
  182. package/dist/percent-field-lahyDJDG.js +14 -0
  183. package/dist/picker-surface-CV09H7Nb.js +1220 -0
  184. package/dist/popover-Cd7DkND4.js +233 -0
  185. package/dist/pos-types-DKCkOTYy.js +49 -0
  186. package/dist/pos-variant-GrUfbTTP.js +603 -0
  187. package/dist/print.js +1 -1
  188. package/dist/programmer-variant-eKJNKENL.js +501 -0
  189. package/dist/programmer-word-CNCF7DJF.js +15 -0
  190. package/dist/{public-DC9VQw4Z.js → public-y6g4ddxd.js} +895 -892
  191. package/dist/{rich-code-block-DBSL9zEN.js → rich-code-block-CRQ0iyLb.js} +5 -5
  192. package/dist/scientific-variant-2y3UxAAU.js +149 -0
  193. package/dist/{sortable-provider-C4tk5esY.js → sortable-provider-DdiI3Fwk.js} +2 -2
  194. package/dist/{spinner-B2MubVWb.js → spinner-C1QIewWv.js} +133 -159
  195. package/dist/standard-layout-BuSRr3TY.js +40 -0
  196. package/dist/tabs-DzEqMb1W.js +383 -0
  197. package/dist/{icon-badge-B9uvDoyY.js → text-field-BAdY-kpO.js} +20 -34
  198. package/dist/theme-picker.js +3 -3
  199. package/dist/{theme-toggle-BInlu21G.js → theme-toggle-C_VeiiCm.js} +2 -2
  200. package/dist/toggle-FeCCJqbg.js +40 -0
  201. package/dist/{toggle-group-DXXlqid2.js → toggle-group-CVMxEpWi.js} +1 -1
  202. package/dist/{toolbar-items-CRJSx1MU.js → toolbar-items-D310xDuM.js} +333 -554
  203. package/dist/{translate-material-pVlPJ1v4.js → translate-material-BypJJ1l5.js} +1 -1
  204. package/dist/{translator-BoYCward.js → translator-DJkf4J7t.js} +824 -0
  205. package/dist/{typography-B6YMbCge.js → typography-CYEquVeb.js} +1 -1
  206. package/dist/{use-chat-composer-BvWQogJe.js → use-chat-composer-DMXu9E1O.js} +4198 -4600
  207. package/dist/{use-heading-spy-B_Z4ArDh.js → use-heading-spy-DJR8YYm6.js} +19 -19
  208. package/dist/{use-material-translation-CIS2n3RB.js → use-material-translation-CD6QoVal.js} +1 -1
  209. package/dist/{use-return-focus-DNQtADAR.js → use-return-focus-Cevw_6yq.js} +106 -105
  210. package/dist/useLatest-C2PnQEeD.js +8 -0
  211. package/docs/README.md +1 -0
  212. package/docs/components/feedback-and-status.md +10 -1
  213. package/docs/components/form-fields.md +1 -0
  214. package/docs/components/rich-widgets.md +108 -0
  215. package/docs/getting-started.md +1 -0
  216. package/docs/guides/calculator.md +468 -0
  217. package/docs/guides/forms.md +4 -2
  218. package/docs/guides/i18n.md +5 -0
  219. package/docs/previews/rich-widgets-calculator-conversion.webp +0 -0
  220. package/docs/previews/rich-widgets-calculator-point-of-sale.webp +0 -0
  221. package/docs/previews/rich-widgets-calculator.webp +0 -0
  222. package/docs/roadmap.md +9 -3
  223. package/docs/troubleshooting.md +134 -0
  224. package/lib/components/base-content.css +9 -4
  225. package/lib/components/ui/collections/server-table/server-table-phone-row.css +10 -3
  226. package/lib/components/ui/widgets/calculator/calculator-bands.css +146 -0
  227. package/lib/components/ui/widgets/calculator/calculator-display.css +83 -0
  228. package/lib/components/ui/widgets/calculator/calculator-keys.css +118 -0
  229. package/lib/components/ui/widgets/calculator/calculator-pos-ticket.css +185 -0
  230. package/lib/components/ui/widgets/calculator/calculator-pos.css +38 -0
  231. package/lib/components/ui/widgets/calculator/calculator-tape.css +71 -0
  232. package/lib/components/ui/widgets/calculator/calculator.css +97 -0
  233. package/lib/components/ui/widgets/calculator/variants/conversion/conversion.css +98 -0
  234. package/lib/components/ui/widgets/calculator/variants/financial/financial.css +162 -0
  235. package/lib/components/ui/widgets/calculator/variants/programmer/programmer.css +162 -0
  236. package/package.json +17 -3
  237. package/dist/dialog-AGsB3bv4.js +0 -219
  238. package/dist/dist-B7RjY7SL.js +0 -158
  239. package/dist/dist-lmHmz_bu.js +0 -419
@@ -0,0 +1,468 @@
1
+ # Calculator
2
+
3
+ `@nxgt/material/calculator` is a calculator for your app: exact decimal
4
+ arithmetic (`0.1 + 0.2` is `0.3`), the usual precedence (`2 + 3 × 4` is `14`),
5
+ the whole expression shown above the result, a tape of past results, a memory,
6
+ the physical keyboard, copy and paste, and a screen-reader announcement of each
7
+ result. Standard, Scientific, the point of sale, Financial, Conversion and
8
+ Programmer ship.
9
+
10
+ The maths is `@nxgt/material-core/calculator`: a decimal engine on `BigInt`
11
+ with its own expression parser, so nothing is evaluated as code and a strict
12
+ Content Security Policy is satisfied. The component is a thin shell over it.
13
+
14
+ - [Set up](#set-up)
15
+ - [The Standard calculator](#the-standard-calculator)
16
+ - [The Scientific calculator](#the-scientific-calculator)
17
+ - [The point-of-sale calculator](#the-point-of-sale-calculator)
18
+ - [The Financial calculator](#the-financial-calculator)
19
+ - [The conversion calculator](#the-conversion-calculator)
20
+ - [The Programmer calculator](#the-programmer-calculator)
21
+ - [Props](#props)
22
+ - [Keyboard](#keyboard)
23
+ - [Tape and memory](#tape-and-memory)
24
+ - [Copy and paste](#copy-and-paste)
25
+ - [What is stored](#what-is-stored)
26
+ - [Feedback](#feedback)
27
+ - [Sizing](#sizing)
28
+ - [Accessibility](#accessibility)
29
+ - [Translations](#translations)
30
+ - [Troubleshooting](#troubleshooting)
31
+
32
+ ## Set up
33
+
34
+ ```tsx
35
+ import { Calculator } from '@nxgt/material/calculator';
36
+ ```
37
+
38
+ The entry is its own: it is not in `@nxgt/material` or
39
+ `@nxgt/material/components`. Its stylesheet is opt-in too: `styles.css` does
40
+ not carry it, so import `@nxgt/material/calculator.css` once, after
41
+ `styles.css` (also for `CalculatorField`, which opens the calculator):
42
+
43
+ ```css
44
+ @import "@nxgt/material/styles.css";
45
+ @import "@nxgt/material/calculator.css";
46
+ ```
47
+
48
+ (or `import '@nxgt/material/calculator.css'` in your entry module). Colours and
49
+ tokens still come from `styles.css`, which your app already imports. It renders on the server; the tape and memory
50
+ are read from `localStorage` after mount, so the first client frame matches the
51
+ server's.
52
+
53
+ ## The Standard calculator
54
+
55
+ ```tsx
56
+ <Calculator />
57
+ <Calculator locale="fr" persist={{ key: 'till-1' }} onResult={(value) => save(value)} />
58
+ ```
59
+
60
+ The display shows the expression above a large result that previews the value
61
+ while you type. The keypad has `( )` and the memory keys on top, then `C`, `±`,
62
+ `%`, `÷ × − +`, the digits, the decimal separator of the locale, `⌫` and `=`, a
63
+ primary pill. `%` follows the `percent` prop below. A result is
64
+ exact; an inexact one (`1 / 3`) is rounded to 34 digits and shown with 16.
65
+
66
+ ## Loading
67
+
68
+ Only Standard is in the entry itself. Every other variant, with its engine, is loaded
69
+ on demand the first time it is shown (by `variant`, `defaultVariant` or the switcher,
70
+ which starts the download on hover and focus). Until it arrives the body is a skeleton
71
+ of Standard's size with a polite "Loading the calculator" status
72
+ (`material.calculator.loading`); a download that fails says "This calculator could not
73
+ be loaded." (`material.calculator.load-failed`) and offers "Try again"
74
+ (`material.calculator.retry`), which loads it again. On the server that loading body is what
75
+ renders, then the client hydrates and fetches the variant. The brief loading state the
76
+ first time is expected, and there is no public preload. A browser may keep a failed dynamic
77
+ import until the page reloads, so if Try again fails again, reload the page. Keys
78
+ and paste do nothing while a variant loads. See
79
+ [the troubleshooting entry](../troubleshooting.md#this-calculator-could-not-be-loaded).
80
+
81
+ ## The Scientific calculator
82
+
83
+ ```tsx
84
+ <Calculator defaultVariant="scientific" />
85
+ <Calculator variants={['scientific']} />
86
+ ```
87
+
88
+ The switcher shows Standard, Scientific, Point of sale, Financial, Conversion and Programmer; `variants` narrows it (one hides
89
+ it). Scientific calculates with `@nxgt/material-core/calculator/scientific`:
90
+ exact to `precision` digits (34), so `sin(30)` is `0.5`, `cos(90)` is `0` and
91
+ `tan(90)` is an error, never a huge number. It brings a toolbar and a pane of
92
+ functions above the Standard keys on a phone, beside them from 600px to 719px
93
+ and from 1000px up (from 720px to 999px the tape takes its column and the pane
94
+ stays above).
95
+
96
+ | Keys | |
97
+ | --- | --- |
98
+ | `sin cos tan sinh cosh tanh` | In the toolbar's angle unit; **2nd** turns them into `asin acos atan asinh acosh atanh` (the button stays, so focus stays) |
99
+ | `ln log log₂ 10ˣ eˣ |x|` | `log` is base 10; `10ˣ` is `10^x` for any exponent (`10^2.5` is `316.2277…`) |
100
+ | `x² xʸ √ ʸ√x n! 1/x` | `√`, `ʸ√x` (`nroot(x, y)`), `x²` and `1/x` open a call: type the argument; `=` closes it |
101
+ | `π e EE mod , Ans` | `EE` starts the exponent of the number you are typing, so `3 EE 5` is `3e5` (300000), `± ` after it signs the exponent (`3e-5`) and it does nothing after an operator or `=`; `,` separates the arguments of `ʸ√x` (`nroot(x, y)`), `Ans` is the last result |
102
+
103
+ Toolbar: **DEG RAD GRAD** (a named group of toggle buttons with
104
+ `aria-pressed`; the unit applies to the trigonometric functions and their
105
+ inverses), **2nd** (a toggle with `aria-pressed`, announced "Second functions
106
+ on") and **Auto Sci Eng** (the same kind of group). Auto switches to powers of ten for results from
107
+ 16 digits or below 1e-7; Sci writes `1.2345678E+7` and Eng
108
+ `12.345678E+6`, exponent a multiple of 3. Changing the unit or the notation is
109
+ announced ("Angles in radians", "Scientific notation"). `%` is `/100` here
110
+ (`100 + 10%` is `100.1`) unless you set `percent`. The keyboard also types
111
+ `^`, `!`, `p` (π), `r` (√) and `;` between arguments. No graphs and no complex
112
+ numbers: `sqrt(-1)` is "Undefined result".
113
+
114
+ ## The point-of-sale calculator
115
+
116
+ ```tsx
117
+ <Calculator
118
+ defaultVariant="pos"
119
+ currency="EUR"
120
+ vatRates={[20, 5.5]}
121
+ denominations={[5, 10, 20, 50]}
122
+ onCharge={(total, ticket) => pay(total, ticket)}
123
+ />
124
+ ```
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`; the
127
+ tab is named "Point of sale". The keys type an amount the way a till does:
128
+ `1`, `2`, `5` is `1.25`, `00` adds two zeros, backspace drops the last digit,
129
+ `C` clears it. `+` adds it to the ticket. `×` types a quantity first (`2.50 ×
130
+ 3 +` is a line of 7.50), `%` types a discount first, as a whole percent or,
131
+ with the **Amount** choice, an amount; a discount typed with no price applies
132
+ to the whole ticket. The chips above the keys (Price, Quantity, Discount,
133
+ Tendered) show what the keys type into.
134
+
135
+ The ticket lists its lines, each removable on its own; on a phone it sits under
136
+ the keys and folds away, from 600px it sits beside them. The totals show the
137
+ subtotal and discount when there is one, the VAT of each rate and the total.
138
+ **Tendered** types what the customer hands over (the `+` key then only
139
+ announces the result); the bills under the ticket are the exact total and up to
140
+ three round amounts from `denominations` (`[5, 10, 20, 50]` for 17.30 gives
141
+ 17.30, 20, 50). The line under them is the change, or what is still due, and
142
+ **Charge** is off while the payment is short.
143
+
144
+ | Prop | Default | |
145
+ | --- | --- | --- |
146
+ | `currency` | none | ISO 4217 code (a prop of the calculator, shared by the variants that show amounts): sets the decimals (`JPY` 0, `EUR` 2, `KWD` 3) and is written beside the amounts. None: two decimals and no code |
147
+ | `vatRates` | none | The VAT rates in percent (`[20, '5.5']`); the first is the default, a chip chooses the rate of the next line. None: no VAT |
148
+ | `pricesIncludeVat` | `true` | The prices typed include the VAT; `false`: the VAT is added to them. The **Incl. VAT / Excl. VAT** choice switches it on screen |
149
+ | `denominations` | none | Bills and coins for the round amounts; without them only the exact amount is offered |
150
+ | `onCharge` | none | `(total, ticket)`: `total` is the exact amount as plain text (`'12.50'`), `ticket` a `PosTicket`, plain JSON-safe data (`JSON.stringify` it as is): `{ currency?, taxInclusive, lines: [{ id, unitPrice, quantity, vatRate, discount?, total }], discount?, totals: { subtotal, discount, vatByRate: [{ rate, base, vat }], total } }`, every amount as text with the currency's decimals (`'12.50'`), rates in percent (`'5.5'`). The ticket is then cleared |
151
+
152
+ The amounts are exact decimals rounded to the currency's minor unit per line,
153
+ so the lines add up to the total. The ticket lives in memory only: it is never
154
+ written to `localStorage`, and the tape does not apply. The live region says
155
+ the total after a line is added or removed, the change or the amount due after
156
+ a tender, and the total charged; typing digits is not announced. Percentages
157
+ are whole numbers (7.5 % is not typeable) and quantities are whole.
158
+
159
+ ## The Financial calculator
160
+
161
+ ```tsx
162
+ <Calculator defaultVariant="financial" currency="EUR" locale="fr" />
163
+ <Calculator variants={['financial']} />
164
+ ```
165
+
166
+ Financial is a panel of forms, not a keypad: four sub-tabs, each with the
167
+ library's own fields (`CurrencyField`, `PercentField`, `NumberField`) and its
168
+ results in a card below. The maths is
169
+ `@nxgt/material-core/calculator/financial`, exact decimals, amounts rounded to
170
+ the currency's minor unit (half-up).
171
+
172
+ | Tab | What it does |
173
+ | --- | --- |
174
+ | VAT | **Add VAT** to a price excluding VAT, or **Remove VAT** from one that includes it: price excluding VAT, VAT and price including VAT always add up |
175
+ | Margin | **Rates** of a cost and a selling price (margin is the share of the PRICE that is profit, markup the share of the COST, so 80 sold at 100 is a 20 % margin and a 25 % markup); **Price** from a cost and a margin or a markup; **Cost** from a price and a margin or a markup |
176
+ | % change | Percent change from one figure to another, and the difference (from `0` is an error) |
177
+ | Loan | Monthly payment of a loan (yearly rate, number of monthly payments, `0` % allowed), total interest, total paid, and the first 12 payments as a real `<table>` with a caption ("First 12 of 240 payments"): payment, interest, principal and balance, the last payment absorbing the rounding |
178
+
179
+ `currency` (an ISO 4217 code, shared with the point of sale; see
180
+ [the troubleshooting entry](../troubleshooting.md#the-financial-calculator-shows-no-currency))
181
+ is written after each amount and sets the decimals (`JPY` 0, `EUR` 2, `KWD`
182
+ 3); without it amounts have two decimals and no code. Amounts and percents are
183
+ written in `locale`. Every tab keeps what was typed while another is open. A
184
+ result is read by the live region once the fields stop changing, not at each
185
+ digit, and an error (a margin of 100 % or more, a change from `0`) is read in
186
+ the same place. The fields hand over JavaScript `number`s, so a figure beyond
187
+ about 15 significant digits is out of range. The calculator keys, the tape and
188
+ `globalKeyboard` do not apply to this variant, and typing in a field never
189
+ reaches them.
190
+
191
+ ## The conversion calculator
192
+
193
+ ```tsx
194
+ <Calculator defaultVariant="conversion" />
195
+ <Calculator
196
+ defaultVariant="conversion"
197
+ currencyRates={{ base: 'EUR', rates: { USD: '1.085', JPY: '160.5' }, asOf: '2026-10-28' }}
198
+ />
199
+ <Calculator
200
+ defaultVariant="conversion"
201
+ currencyRates={(signal) => fetch('/api/rates', { signal }).then((r) => r.json())}
202
+ />
203
+ ```
204
+
205
+ A converter on `@nxgt/material-core/calculator/units`; the tab is named
206
+ "Conversion". Choose a **Category** (length, mass, temperature, volume, area,
207
+ speed, time, data), type a **Value** (in the locale's own separators: `1 234,5`
208
+ in French) and pick the unit it is in (**From**) and the unit wanted (**To**).
209
+ The swap button turns the pair around and keeps the value; choosing on one side
210
+ the unit the other side has swaps them. The result is exact (`1 in` is `2.54
211
+ cm`, `212 °F` is `100 °C`; temperatures keep their offsets) and written in the
212
+ locale with up to 16 significant digits. The live region says it once the value
213
+ stops changing, not at every digit. The variant has no keypad, tape or memory,
214
+ and takes no calculator keys.
215
+
216
+ `currencyRates` adds a **Currency** category, and only then:
217
+
218
+ - An object `{ base, rates, asOf? }`: `rates[code]` is how much of `code` one
219
+ `base` buys, every rate a positive number or text (text keeps the digits
220
+ exact). `asOf` is shown as "Rates as of ...".
221
+ - A function `(signal: AbortSignal) => Promise<{ base, rates, asOf? }>`, called
222
+ the first time Currency is chosen, with an `AbortSignal` that aborts when the panel closes
223
+ or the fetch is repeated (pass it to `fetch`). Meanwhile the panel says
224
+ "Loading exchange rates..."; when the promise rejects or the rates are not
225
+ valid it says so, with **Try again**, which calls the function again. Its
226
+ identity does not matter (an inline arrow is fine); only Try again calls it
227
+ a second time. A function that throws at once is an error like a rejection.
228
+
229
+ A currency amount is rounded to the minor unit of the target currency (`JPY` 0,
230
+ `EUR` 2, `KWD` 3). Nothing is fetched by the library, and no rate is built in.
231
+ The types are `CalculatorCurrencyRates` (the object), `CalculatorRatesSource`
232
+ (the object or the function) and `CalculatorConversionProps`, exported from
233
+ `@nxgt/material/calculator`.
234
+
235
+ Currency names follow the i18n language (the one the unit names are translated
236
+ in); `locale` only formats the number and the quote date.
237
+
238
+ ### Wording
239
+
240
+ The words live under `material.calculator.conversion` (categories, units,
241
+ `rates.loading`, `rates.error`, `rates.retry`, `rates.as-of`, the announcement);
242
+ the engine's messages are under `material.calculator.error`. Override one key
243
+ with `mergeResources` (per language, your value wins; see the
244
+ [i18n guide](./i18n.md)) and give the result to the provider:
245
+
246
+ ```tsx
247
+ import { mergeResources, resources as materialResources } from '@nxgt/material/i18n';
248
+
249
+ export const resources = mergeResources(materialResources, {
250
+ en: { material: { calculator: { conversion: { rates: { retry: 'Reload rates' } } } } },
251
+ });
252
+ // <I18nProvider resources={resources}>
253
+ ```
254
+
255
+ | Prop | Default | |
256
+ | --- | --- | --- |
257
+ | `currencyRates` | none | The rates, or a function that fetches them (above). None: no Currency category |
258
+ | `locale`, `precision`, `rounding` | | Read as everywhere: number format, significant digits and rounding of a result that does not terminate |
259
+
260
+ ## The Programmer calculator
261
+
262
+ ```tsx
263
+ <Calculator defaultVariant="programmer" defaultWordSize={32} defaultSigned={false} />
264
+ ```
265
+
266
+ Integers in a word of 8, 16, 32 or 64 bits, signed (two's complement, the
267
+ default) or unsigned; every result wraps into the word (`127 + 1` is `-128` in
268
+ a signed 8-bit word). The maths is
269
+ `@nxgt/material-core/calculator/programmer`. The keypad adds `A` to `F`, the
270
+ word operators (`AND`, `OR`, `XOR`, `NOT`, `NAND`, `NOR`), shifts (`<<` and
271
+ `>>`, arithmetic) and rotates (`ROL`, `ROR`), and `mod`. `^` on the keyboard is
272
+ `XOR`; the `>>` key is an arithmetic shift (it copies the sign bit in), and the
273
+ logical shift `>>>` can be typed as text.
274
+
275
+ - **Bases**: four readouts (HEX, DEC, OCT, BIN) show the number being edited
276
+ and are a single choice: the base chosen is the one the digit keys type in
277
+ and the big line is written in. Changing it rewrites the number in the new
278
+ base: an expression that calculates (`10+5`) collapses to its value and the
279
+ live region says "Converted to F"; one that does not calculate yet (`10+(`)
280
+ has each number literal rewritten in place (`a+(`), as does one that ends in
281
+ an operator (`10+` is `a+` in hex, without collapsing), and one that cannot be
282
+ rewritten cleanly keeps its base and says "Base not changed". Digits the base
283
+ does not have (`A` to `F` in decimal, `8` and `9` in octal, `2` to `9` in
284
+ binary) are `aria-disabled` (they stay in the tab order), press nothing, and
285
+ say "Not available in Octal", from the keypad or the keyboard.
286
+ - **Word**: the size buttons and the **Signed** toggle change the word. A
287
+ narrower word cuts the number to its low bits and the live region says
288
+ "Value cut to 8 bits: 44"; a wider one, or a change of sign, rereads the same
289
+ bits.
290
+ - **Bit pad**: behind a **Bit pad** disclosure, one toggle button per bit
291
+ (`aria-pressed`, "Bit 7"), most significant first, grouped by four: one
292
+ nibble a row under 400px, two from 400, four from 760. Pressing a bit flips it
293
+ and announces it. While the expression does not calculate (`10+(`) the pad is
294
+ inert (`aria-disabled`) and leaves the expression alone.
295
+ - Copy writes the value in the base shown, upper case, with no prefix (`FF`);
296
+ Paste reads the text in the current base, so Copy then Paste round-trips. The
297
+ tape keeps the expression as typed and the result in decimal. Results are
298
+ announced in the chosen base.
299
+
300
+ `defaultWordSize` (`8 | 16 | 32 | 64`, default `64`) and `defaultSigned`
301
+ (default `true`) are read once, when the calculator mounts; the user changes
302
+ the word from then on.
303
+
304
+ ## In a form: CalculatorField
305
+
306
+ `CalculatorField` and `CalculatorFormField` are their own entry,
307
+ `@nxgt/material/calculator-field`, so an app that only uses `Calculator` does
308
+ not pay for the popover and sheet. `CalculatorField` is a number field with an "Open calculator" button inside it.
309
+ The button opens a `Calculator` seeded with the field's number: a popover from
310
+ 768px wide, a bottom sheet below. **Apply** writes the result to the field;
311
+ **Cancel** and Escape keep the number. Focus goes back to the button either way.
312
+
313
+ ```tsx
314
+ import { CalculatorField, CalculatorFormField } from '@nxgt/material/calculator-field';
315
+
316
+ <CalculatorField label="Quantity" value={qty} onChange={setQty} />
317
+ <CalculatorField label="Price" currency="EUR" value={price} onChange={setPrice} onExactValueChange={(exact) => console.log(exact)} />
318
+ <CalculatorFormField control={form.control} name="total" label="Total" currency="EUR" />
319
+ ```
320
+
321
+ It is a `NumberField` (steppers, `min`, `max`, `step`, `suffix`), or a
322
+ `CurrencyField` when `currency` is set; a currency field takes no `suffix`, `step` or `hideSteppers`, and `min` / `max` do not limit what is typed (Apply still holds the amount to them). It adds:
323
+
324
+ | Prop | Default | |
325
+ | --- | --- | --- |
326
+ | `calculatorVariant` | `standard` | The variant the calculator opens with |
327
+ | `calculatorVariants` | `['standard']` | The variants its switcher offers; one hides the switcher. Pass more to opt in: `['standard', 'scientific', 'financial']` |
328
+ | `surface` | `auto` | `auto` (popover from 768px, sheet below), `popover` or `sheet` |
329
+ | `onExactValueChange` | none | Called on Apply with the exact decimal result (`'3.3333333333333333333333333333333333'`), before it is rounded |
330
+ | `fractionDigits` | the currency's | Decimals of a currency amount |
331
+
332
+ Apply takes what the display shows, so an unfinished sum (`2 + 3 × 4` with no
333
+ `=`) applies its result. A currency amount is rounded half-up to the currency's
334
+ decimals (`10 ÷ 3` is `3.33` in EUR, `1235` for `1234.5` in JPY); any other
335
+ number is the nearest `number`. The result is held to `min` and `max`, as typing
336
+ is. Apply is disabled while the display has no number (empty, or an error), see [troubleshooting](../troubleshooting.md#calculator). The
337
+ calculator keeps no tape or memory there (`persist={false}`), and each opening
338
+ starts from the field's number again.
339
+
340
+ `CalculatorFormField` takes `control` and `name`; the value is `number | null`.
341
+ Focus moving into the calculator and back is not leaving, so the field is
342
+ touched when focus leaves the field and its calculator.
343
+
344
+ Two props of `Calculator` serve it, and an application can use them too:
345
+ `defaultValue` (a number such as `'12.5'` the calculator opens with, as a
346
+ finished result: a digit starts a new number, an operator carries on from it)
347
+ and `onValueChange` (the display's exact value each time it changes, `''` when
348
+ there is none).
349
+
350
+ ## Props
351
+
352
+ | Prop | Default | |
353
+ | --- | --- | --- |
354
+ | `variant` / `defaultVariant` / `onVariantChange` | `standard` | The variant shown, controlled or not. A variant that is not available yet falls back to Standard |
355
+ | `variants` | every registered variant | The variants the switcher offers; one hides the switcher. The default list grows with each variant the library adds, so an app that wants a fixed set passes `variants` |
356
+ | `currency` | none | ISO 4217 code of the amounts (decimals and code), see [above](#the-point-of-sale-calculator) |
357
+ | `vatRates`, `pricesIncludeVat`, `denominations`, `onCharge` | | Read only by the point of sale, [above](#the-point-of-sale-calculator) |
358
+ | `defaultWordSize`, `defaultSigned` | `64`, `true` | Read only by the Programmer variant, [above](#the-programmer-calculator) |
359
+ | `currencyRates` | none | Read only by Conversion: its Currency category, [above](#the-conversion-calculator) |
360
+ | `locale` | the page's language | Separators, grouping and the decimal key's glyph |
361
+ | `precision`, `rounding` | 34, `half-even` | The engine's context |
362
+ | `percent` | `contextual` | `contextual` (`100 + 10%` is 110) or `literal` (always `/100`) |
363
+ | `persist` | `true` | Keep the tape and memory in `localStorage`; `{ key }` names the slot; `false` keeps nothing |
364
+ | `feedback` | none | `{ haptics, sound }`, both off |
365
+ | `globalKeyboard` | `false` | Also take the keys typed anywhere on the page |
366
+ | `defaultValue` | none | A number the calculator opens with, as a finished result, see [CalculatorField](#in-a-form-calculatorfield) |
367
+ | `onValueChange` | none | `(value)`: the display's exact value (result or live preview) each time it changes; `''` when there is none |
368
+ | `onResult` | none | `(value, expression)` after each `=`: the exact result and the canonical expression |
369
+ | `className`, `aria-label` | | The root `role="group"`, named "Calculator" by default |
370
+
371
+ ## Keyboard
372
+
373
+ The calculator takes the keys typed inside it (a key, the display or the
374
+ tape): digits, `.` and `,` (the locale's decimal), `+ - * / x`, `%`, `(` `)`,
375
+ `=` and Enter, Backspace, Delete (`CE`), Escape (`C`), and `Ctrl`/`Cmd` + `C`
376
+ (copy) and `V` (paste, the browser's own paste event). It reads `event.key`, so an AZERTY keyboard types the digits it
377
+ prints. Enter on a focused key is that key's click, not `=`.
378
+
379
+ In the Programmer variant `a` to `f` type the hex digits (only where the base
380
+ has them) and `^` is `XOR`; `&`, `|` and `~` are not mapped, use the `AND`,
381
+ `OR` and `NOT` keys. `>>` is an arithmetic shift, and `>>>` (logical) can be
382
+ typed as text.
383
+
384
+ The keypad is one tab stop. Inside it the arrow keys move over the grid in two
385
+ dimensions, Home and End along a row, and Ctrl with them to the first and last
386
+ key. Escape closes the history when it lies over the keypad, and means `C`
387
+ otherwise.
388
+
389
+ `globalKeyboard` adds a window listener for the keys typed elsewhere on the
390
+ page. It never takes a key typed in an input, a textarea, a select or an
391
+ editable element.
392
+
393
+ ## Tape and memory
394
+
395
+ Each `=` writes an entry to the tape (newest first, 100 at most). Open it with
396
+ the history button; it takes the keypad's place below 720px and sits beside it
397
+ from there (the history button stays, and focus moves into the
398
+ history and back). The
399
+ expression button replays the entry into the display, ready to edit; the result
400
+ button types the result into the current expression. The bin clears the tape.
401
+
402
+ `MC MR M+ M−` clear, recall, add to and subtract from the memory, at full
403
+ precision. An `M` chip shows while the memory holds a value.
404
+
405
+ ## Copy and paste
406
+
407
+ The copy button (or `Ctrl`/`Cmd` + `C` with nothing selected) copies the exact
408
+ result in the locale's decimal, without grouping. The paste button
409
+ (which asks the browser for clipboard permission) and `Ctrl`/`Cmd` + `V` (no
410
+ permission, it reads the paste event) put the clipboard's text in as the expression, accepting
411
+ the usual separators (`1.234,5 × 2` in German, `1,234.5 × 2` in English) and
412
+ Arabic-Indic digits; press `=` to calculate it. A browser that denies
413
+ clipboard access leaves the calculator as it was.
414
+
415
+ ## What is stored
416
+
417
+ With `persist` on, `localStorage` holds `nxgt-calculator:<key>:history` (the
418
+ tape) and `nxgt-calculator:<key>:memory`, versioned JSON that is read after
419
+ mount and validated: anything unreadable gives an empty tape and a memory of
420
+ `0`. Writes are debounced. A blocked or full storage is ignored. Use
421
+ `persist={false}` where a calculator must not remember (a shared till), or a
422
+ `key` per till.
423
+
424
+ ## Feedback
425
+
426
+ `feedback={{ haptics: true }}` vibrates 8 ms on a key press where
427
+ `navigator.vibrate` exists, and never under `prefers-reduced-motion`.
428
+ `feedback={{ sound: true }}` plays a short tick through the Web Audio API,
429
+ created on the first press. Both are off by default.
430
+
431
+ ## Sizing
432
+
433
+ The calculator sizes itself by its own width (container queries), not the
434
+ window's: under 360px a compact layout, 360-599 a phone, 600-899 a tablet,
435
+ 900 and up a desktop with a wider tape (where the tape sits is in
436
+ [Tape and memory](#tape-and-memory)). Long results shrink instead of clipping. Keys
437
+ are at least 44px on a coarse pointer. The result swap animation is off under
438
+ reduced motion, and forced colours get borders.
439
+
440
+ ## Accessibility
441
+
442
+ - The root is a named `role="group"`; the keypad is a `role="group"` of native
443
+ buttons, each with a spoken name ("Plus", "Divided by", "Equals"); the glyph
444
+ is decoration.
445
+ - The display is a named group, not an input and not a tab stop. The expression and the result are read
446
+ left to right in a right-to-left page, as are the keys.
447
+ - A polite live region announces the result after `=`, errors, memory changes,
448
+ copy and paste, not every key.
449
+ - Errors are words ("Cannot divide by zero"), never `NaN` or `Infinity`.
450
+
451
+ ## Translations
452
+
453
+ Every string is `material.calculator.*` in the base bundle, English and
454
+ French; override a leaf in your own resources.
455
+
456
+ The Programmer keys have their own leaves: `material.calculator.programmer.*`
457
+ (the bases and their `set-*` announcements, word size, `signed`, `bits`, `bit`,
458
+ `bit-on`, `bit-off`, `converted`, `base-kept`, `unavailable`), the key names
459
+ under `material.calculator.keys` (`hex-a` to `hex-f`, `and`, `or`, `xor`,
460
+ `not`, `nand`, `nor`, the shifts and rotates), and
461
+ `material.calculator.announce.truncated`, `wordSigned` and `wordUnsigned`.
462
+
463
+ `material.calculator.field.*` (`open`, `title`, `apply`, `cancel`) name the
464
+ `CalculatorField` button and its dialog.
465
+
466
+ ## Troubleshooting
467
+
468
+ See [the Calculator entries](../troubleshooting.md#calculator).
@@ -116,7 +116,7 @@ export function PlanForm() {
116
116
  | --- | --- |
117
117
  | one input (`TextFormField`, `EmailFormField`, `TextareaFormField`, `SwitchFormField`, `CheckboxFormField`, …) | its input loses focus, as a native `blur` |
118
118
  | several controls (`OpeningHoursFormField`, inline by default: focus leaves the week editor and its phone sheet; `ContactsFormField`, inline by default: focus leaves the rows and the phone sheet; `AttributesFormField`, inline by default: focus leaves the rows, their open fields and the phone sheet; `PasswordFormField` and its eye button, `NumberFormField` and its steppers, `QuantityFormField`, `PhoneFormField`, `OtpFormField`, `SliderFormField` with two thumbs, `DurationFormField`, `RatingFormField`, `RadioGroupFormField`, `CheckboxGroupFormField`, `ChipGroupFormField`, `ChoiceCardGroupFormField`, `SortableListFormField`, `TransferListFormField`, `InlineEditFormField`, `LocalizedTextFormField`, `AttributeValuesFormFieldSet`, …) | focus leaves all of them; moving between them is not leaving |
119
- | a picker (`SelectFormField`, `ComboboxFormField`, `AutocompleteFormField`, `TreeSelectFormField`, `CountryFormField`, `TimezoneFormField`, `ColorFormField`, `DateFormField`, `DateRangeFormField`, `TimeFormField`, …) | focus leaves the trigger **and** the popover it opened. Opening the popover, or picking in it, is not leaving; closing it hands focus back to the trigger, and the next Tab or click elsewhere is. A typed `DateFormField`, `DateRangeFormField` or `TimeFormField` is one trigger: moving between its segments (both ends of a range) and its buttons is not leaving |
119
+ | a picker (`SelectFormField`, `ComboboxFormField`, `AutocompleteFormField`, `TreeSelectFormField`, `CountryFormField`, `TimezoneFormField`, `ColorFormField`, `DateFormField`, `DateRangeFormField`, `TimeFormField`, `CalculatorFormField` (the number field and its calculator button), …) | focus leaves the trigger **and** the popover it opened. Opening the popover, or picking in it, is not leaving; closing it hands focus back to the trigger, and the next Tab or click elsewhere is. A typed `DateFormField`, `DateRangeFormField` or `TimeFormField` is one trigger: moving between its segments (both ends of a range) and its buttons is not leaving |
120
120
  | a dialog editor (`PostalAddressFormField`, `ContactsFormField` with `editing="dialog"`, `OpeningHoursFormField` with `editing="dialog"`, `AttributesFormField` with `editing="dialog"`, and `LocationFormField` from `@nxgt/material/map`) | focus leaves its buttons and the dialog they open. Closing the dialog hands focus back to the button that opened it |
121
121
  | an upload (`UploadFormField`, `NetworkUploadFormField`, `S3UploadFormField`, `ImageFormField`, `ImageS3FormField`, `FileListFormField`, `FileListS3FormField`) | focus leaves the drop zone and its buttons. Choosing a file in the browser's own dialog is not leaving |
122
122
  | `RichTextEditorFormField`, `CodeEditorFormField` | focus leaves the editor and its toolbar |
@@ -277,7 +277,8 @@ form grows with the page, and the page is the one scroll. The actions
277
277
  (`[data-slot="form-actions"]`) are sticky at the bottom of whatever scrolls
278
278
  the form, on a translucent, blurred background, so Cancel and Save stay in
279
279
  reach while the fields scroll under them; a form shorter than the screen
280
- shows them right under its fields.
280
+ shows them right under its fields, on the fields' own surface (same
281
+ tint, no gap): one slab, not two.
281
282
 
282
283
  - In a `DialogContent` without a `DialogBody`, or a `SheetContent`, which are capped to the screen
283
284
  and scroll themselves, the actions stick to the dialog's or the sheet's
@@ -488,6 +489,7 @@ or fails validation.
488
489
  | `PercentField` · `PercentFormField` | `number \| null`, `min` 0, `max` 100, `step` 1 by default | `null` |
489
490
  | `QuantityField` · `QuantityFormField` | `{ value: number \| null, unit: string }`. An error on the field, `.value` or `.unit` shows as one message under the pair | `{ value: null, unit: '' }` |
490
491
  | `CurrencyField` · `CurrencyFormField` | `number \| null`, the amount only, not rounded. `currency` (default `'EUR'`) and `fractionDigits` (the currency's own decimals by default) are display | `null` |
492
+ | `CalculatorField` · `CalculatorFormField` (`@nxgt/material/calculator-field`) | `number \| null`. `null` when empty; what Apply writes (a currency amount rounded half-up to its decimals, held to `min` / `max`). `onExactValueChange` gets the exact decimal string | `null` |
491
493
  | `PhoneField` · `PhoneFormField` | E.164 `string` (`'+33612345678'`), or `null` when empty. `PhoneFormField` reports a non-empty number that is not yet possible as the field error `material.phone-field.incomplete`; `PhoneField` signals it with `onValidityChange`. `defaultCountry` is `'FR'` | `null` |
492
494
  | `UrlField` · `UrlFormField` | `string` (as typed) | `''` |
493
495
  | `SearchField` · `SearchFormField` | `string`, after `debounceMs` (default `0`) | `''` |
@@ -515,6 +515,11 @@ them the merged resources (see "Overriding a component's words" below).
515
515
  <Spinner variant="4" /> // role="img" aria-label="Chargement..."
516
516
  ```
517
517
 
518
+ The calculator's loading state reads `material.calculator.loading` ("Loading the
519
+ calculator"), `material.calculator.load-failed` ("This calculator could not be
520
+ loaded.") and `material.calculator.retry` ("Try again"), shown while a variant
521
+ other than Standard loads on demand or when its download failed.
522
+
518
523
  ### Overriding a component's words
519
524
 
520
525
  Give the provider the merged resources and every component translates through
package/docs/roadmap.md CHANGED
@@ -6,8 +6,14 @@ version they shipped in, and planned entries may name the major they target.
6
6
 
7
7
  ## Now
8
8
 
9
- - **Calculator** — `@nxgt/material/calculator` adds `Calculator`, in six variants (Standard, Scientific, point of sale, Financial, Unit conversion, Programmer) chosen by `variant` or by its own switcher, and `CalculatorField` / `CalculatorFormField`, a number field whose button opens it in a popover, or a bottom sheet on a phone. Every variant shows the whole expression above the result, honours operator precedence and computes in exact decimals (0.1 + 0.2 = 0.3) without `eval`, so a strict CSP holds. A history tape (persisted, with an opt-out), memory keys, the physical keyboard, copy and paste, locale formatting and screen-reader announcements come with each; haptic and sound feedback are opt-in. Point of sale charges through `onCharge(total)`; currency rates come from the app. React only for now; the Vue port is tracked in [#2100](https://github.com/softistx/nxgt-material/issues/2100). Tracked in [#2101](https://github.com/softistx/nxgt-material/issues/2101).
10
-
9
+ - **Calculator** — `@nxgt/material/calculator` is an exact-decimal calculator
10
+ (Standard, Scientific, point of sale, Financial, Conversion, Programmer;
11
+ every variant but Standard loads on demand)
12
+ with a tape, a memory, the keyboard and screen-reader announcements, and
13
+ `@nxgt/material/calculator-field` adds `CalculatorField`, a number or
14
+ currency field whose button opens it. Its stylesheet is opt-in:
15
+ import `@nxgt/material/calculator.css` after `styles.css`
16
+ ([guide](guides/calculator.md)).
11
17
  - **Barcodes and label sheets** — `@nxgt/material/barcode` draws Code 128
12
18
  and Data Matrix as accessible SVG (themed, dark-aware, black on white in
13
19
  print) and lays labels out on a sheet to print: label size, columns, rows,
@@ -371,7 +377,7 @@ version they shipped in, and planned entries may name the major they target.
371
377
 
372
378
  ## Shipped
373
379
 
374
- - **Moment (next minor)** — a transient full-screen announcement for a one-off event (a player is eliminated, a role is revealed): a modal dialog named by its title, with an illustration, description, action and tone, dismissed by Escape, a tap, its action or a timer, and no motion under reduced motion ([#2220](https://github.com/softistx/nxgt-material/issues/2220)).
380
+ - **Moment (3.74.0)** — a transient full-screen announcement for a one-off event (a player is eliminated, a role is revealed): a modal dialog named by its title, with an illustration, description, action and tone, dismissed by Escape, a tap, its action or a timer, and no motion under reduced motion ([#2220](https://github.com/softistx/nxgt-material/issues/2220), [#2247](https://github.com/softistx/nxgt-material/pull/2247)). A `modal={false}` variant for live events (focus stays, taps pass through, polite status) follows in the next minor ([#2313](https://github.com/softistx/nxgt-material/issues/2313)).
375
381
  - **RecordActionsMenu (next minor)** — one menu for a record's lifecycle: archive with an Undo toast (or confirm first with `archivePolicy="confirm"`), restore, and delete that always confirms and shows the backend's refusal, with `ConfirmDialog` gaining `details` and `refusal` slots.
376
382
  - **Assistant approval card (next minor)** — `ChatActionProposal` shows an action an assistant proposes, with its status, deadline and Confirm / Reject, and `ChatToolActivity` is a compact row for a tool step in a chat thread ([#2134](https://github.com/softistx/nxgt-material/issues/2134)).
377
383