bitboss-ui 3.0.0-beta.47 → 3.0.0-beta.49

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 (398) hide show
  1. package/README.md +24 -7
  2. package/bin/bitboss-ui-mcp.mjs +15 -3
  3. package/bin/bitboss-ui.mjs +167 -14
  4. package/dist/ai/BbAccordion.md +1 -1
  5. package/dist/ai/BbAvatar.md +1 -1
  6. package/dist/ai/BbBreadcrumbs.md +65 -65
  7. package/dist/ai/BbButton.md +28 -8
  8. package/dist/ai/BbCheckbox.md +0 -2
  9. package/dist/ai/BbCheckboxGroup.md +13 -20
  10. package/dist/ai/BbCollapsible.md +1 -1
  11. package/dist/ai/BbColorPalette.md +1 -1
  12. package/dist/ai/BbConfirm.md +8 -0
  13. package/dist/ai/BbConfirmPortal.md +1 -1
  14. package/dist/ai/BbDatePickerInput.md +4 -2
  15. package/dist/ai/BbDialog.md +1 -8
  16. package/dist/ai/BbDropdown.md +231 -220
  17. package/dist/ai/BbDropdownButton.md +65 -65
  18. package/dist/ai/BbDropdownGroup.md +2 -2
  19. package/dist/ai/BbDropzone.md +10 -10
  20. package/dist/ai/BbForm.md +43 -52
  21. package/dist/ai/BbHoverCard.md +19 -7
  22. package/dist/ai/BbHoverCardPortal.md +1 -1
  23. package/dist/ai/BbNumberInput.md +88 -21
  24. package/dist/ai/BbPopover.md +47 -40
  25. package/dist/ai/BbPopoverPortal.md +1 -1
  26. package/dist/ai/BbProgress.md +48 -22
  27. package/dist/ai/BbRadioGroup.md +13 -19
  28. package/dist/ai/BbSelect.md +16 -36
  29. package/dist/ai/BbSelectPopover.md +14 -30
  30. package/dist/ai/BbSmoothHeight.md +0 -2
  31. package/dist/ai/BbSwitchGroup.md +13 -20
  32. package/dist/ai/BbTable.md +11 -10
  33. package/dist/ai/BbTabs.md +107 -106
  34. package/dist/ai/BbTabsList.md +2 -2
  35. package/dist/ai/BbTabsPanels.md +1 -1
  36. package/dist/ai/BbTabsRoot.md +3 -3
  37. package/dist/ai/BbTooltip.md +29 -13
  38. package/dist/ai/BbTree.md +14 -11
  39. package/dist/ai/CommonPopover.md +1 -1
  40. package/dist/ai/changelog.json +297 -6
  41. package/dist/ai/components.json +50 -45
  42. package/dist/ai/composables/useBbDropdownContext.md +16 -16
  43. package/dist/ai/composables/useBbFormField.md +5 -1
  44. package/dist/ai/composables/useBbTabsContext.md +14 -14
  45. package/dist/ai/guides/agent-contract.md +27 -27
  46. package/dist/ai/guides/ai-router.md +1 -1
  47. package/dist/ai/guides/component-picker.md +3 -3
  48. package/dist/ai/guides/fetch-items-playbook.md +1 -1
  49. package/dist/ai/guides/icons-policy.md +10 -10
  50. package/dist/ai/guides/inertia-helpers.md +120 -67
  51. package/dist/ai/guides/installation-and-plugin-setup.md +45 -15
  52. package/dist/ai/guides/migration/components/bb-breadcrumbs.md +3 -2
  53. package/dist/ai/guides/migration/components/bb-checkbox-group.md +16 -16
  54. package/dist/ai/guides/migration/components/bb-dropdown-button.md +9 -8
  55. package/dist/ai/guides/migration/components/bb-dropdown.md +15 -14
  56. package/dist/ai/guides/migration/components/bb-radio-group.md +2 -1
  57. package/dist/ai/guides/migration/components/bb-select-popover.md +6 -1
  58. package/dist/ai/guides/migration/components/bb-select.md +10 -2
  59. package/dist/ai/guides/migration/components/bb-switch-group.md +2 -1
  60. package/dist/ai/guides/migration/components/bb-tabs.md +9 -8
  61. package/dist/ai/guides/migration/components/bb-tooltip.md +1 -1
  62. package/dist/ai/guides/migration/v2-to-v3.md +143 -28
  63. package/dist/ai/guides/migration/v3-beta-css-classes.md +29 -27
  64. package/dist/ai/guides/migration/v3-beta-tooling.md +11 -0
  65. package/dist/ai/guides/options-items-playbook.md +30 -24
  66. package/dist/ai/guides/passthrough.md +4 -4
  67. package/dist/ai/guides/preset-components.md +18 -3
  68. package/dist/ai/guides/typescript-generic-components.md +1 -1
  69. package/dist/ai/guides/validated-forms.md +11 -22
  70. package/dist/ai/guides/validation-providers.md +4 -3
  71. package/dist/ai/helpers/inertia/manifest.json +636 -0
  72. package/dist/ai/helpers/inertia/types.ts +15 -0
  73. package/dist/ai/helpers/inertia/useAsyncFn.ts +110 -0
  74. package/dist/ai/helpers/inertia/useConfirmOnRouteLeave.ts +102 -0
  75. package/dist/ai/helpers/inertia/useDeleteCmd.ts +30 -0
  76. package/dist/ai/helpers/inertia/useHasChanged.ts +36 -0
  77. package/dist/ai/helpers/inertia/useIndexById.ts +23 -0
  78. package/dist/ai/helpers/inertia/useLazyProp.ts +148 -0
  79. package/dist/ai/helpers/inertia/useLazyPropGetter.ts +50 -0
  80. package/dist/ai/helpers/inertia/useLoading.ts +20 -0
  81. package/dist/ai/helpers/inertia/usePaginatedResponseContext.ts +88 -0
  82. package/dist/ai/helpers/inertia/usePromiseForm.ts +62 -0
  83. package/dist/ai/helpers/inertia/useProp.ts +63 -0
  84. package/dist/ai/helpers/inertia/useQuery.ts +19 -0
  85. package/dist/ai/helpers/inertia/useQueryObject.ts +116 -0
  86. package/dist/ai/helpers/inertia/useResourceContext.ts +69 -0
  87. package/dist/ai/helpers/inertia/useRouteQuery.ts +54 -0
  88. package/dist/ai/helpers/inertia/useScreenSizes.ts +23 -0
  89. package/dist/ai/helpers/inertia/useTableContext.ts +34 -0
  90. package/dist/ai/helpers/inertia/useTableSelectionContext.ts +48 -0
  91. package/dist/ai/helpers/inertia/useUntil.ts +33 -0
  92. package/dist/ai/helpers/inertia/utils.ts +24 -0
  93. package/dist/ai/index.md +6 -6
  94. package/dist/ai/manifest/components/BbAccordion.json +1 -1
  95. package/dist/ai/manifest/components/BbBreadcrumbs.json +10 -6
  96. package/dist/ai/manifest/components/BbBreadcrumbs.tree.json +1 -1
  97. package/dist/ai/manifest/components/BbButton.json +2 -1
  98. package/dist/ai/manifest/components/BbButton.tree.json +1 -1
  99. package/dist/ai/manifest/components/BbCheckboxGroup.json +2 -2
  100. package/dist/ai/manifest/components/BbCollapsible.json +1 -1
  101. package/dist/ai/manifest/components/BbColorPalette.json +1 -1
  102. package/dist/ai/manifest/components/BbConfirmPortal.json +5 -2
  103. package/dist/ai/manifest/components/BbDropdown.json +23 -20
  104. package/dist/ai/manifest/components/BbDropdown.tree.json +2 -2
  105. package/dist/ai/manifest/components/BbDropdownButton.json +22 -19
  106. package/dist/ai/manifest/components/BbDropdownButton.tree.json +1 -1
  107. package/dist/ai/manifest/components/BbDropdownGroup.json +2 -2
  108. package/dist/ai/manifest/components/BbForm.json +1 -0
  109. package/dist/ai/manifest/components/BbHoverCard.json +1 -1
  110. package/dist/ai/manifest/components/BbHoverCardPortal.json +2 -2
  111. package/dist/ai/manifest/components/BbNumberInput.json +6 -1
  112. package/dist/ai/manifest/components/BbNumberInput.tree.json +2 -1
  113. package/dist/ai/manifest/components/BbPopover.json +2 -1
  114. package/dist/ai/manifest/components/BbPopoverPortal.json +2 -2
  115. package/dist/ai/manifest/components/BbProgress.json +6 -6
  116. package/dist/ai/manifest/components/BbProgress.tree.json +1 -0
  117. package/dist/ai/manifest/components/BbRadioGroup.json +2 -2
  118. package/dist/ai/manifest/components/BbSelect.json +2 -2
  119. package/dist/ai/manifest/components/BbSelect.tree.json +2 -2
  120. package/dist/ai/manifest/components/BbSelectPopover.json +2 -2
  121. package/dist/ai/manifest/components/BbSelectPopover.tree.json +1 -1
  122. package/dist/ai/manifest/components/BbSwitchGroup.json +2 -2
  123. package/dist/ai/manifest/components/BbTabs.json +8 -7
  124. package/dist/ai/manifest/components/BbTabsList.json +3 -3
  125. package/dist/ai/manifest/components/BbTabsPanels.json +1 -1
  126. package/dist/ai/manifest/components/BbTabsRoot.json +6 -5
  127. package/dist/ai/manifest/components/BbTag.tree.json +1 -1
  128. package/dist/ai/manifest/components/BbTooltip.json +2 -2
  129. package/dist/ai/manifest/components/ChipsBox.tree.json +1 -1
  130. package/dist/ai/manifest/components/CommonPopover.json +1 -1
  131. package/dist/ai/manifest/index.json +2 -2
  132. package/dist/ai/manifest/meta.json +5 -0
  133. package/dist/ai/manifest/types.advanced.json +10 -9
  134. package/dist/ai/manifest/types.api.json +8 -5
  135. package/dist/ai/manifest/types.wrapper.json +1 -0
  136. package/dist/ai/recipes/inertia/approvals-inbox.md +16 -18
  137. package/dist/ai/recipes/inertia/inline-edit-workspace.md +23 -33
  138. package/dist/ai/recipes/inertia/layout-scaffold.md +16 -16
  139. package/dist/ai/recipes/inertia/onboarding.md +6 -10
  140. package/dist/ai/recipes/inertia/ownership-atlas.md +15 -18
  141. package/dist/ai/recipes/inertia/record-form.md +11 -11
  142. package/dist/ai/recipes/inertia/records-workspace.md +38 -41
  143. package/dist/ai/recipes/inertia/settings-workspace.md +26 -38
  144. package/dist/ai/recipes/inertia/upload-center.md +31 -39
  145. package/dist/ai/recipes/inertia/wizard-form.md +10 -19
  146. package/dist/ai/recipes/nuxt/approvals-inbox.md +7 -8
  147. package/dist/ai/recipes/nuxt/inline-edit-workspace.md +14 -24
  148. package/dist/ai/recipes/nuxt/layout-scaffold.md +16 -16
  149. package/dist/ai/recipes/nuxt/onboarding.md +2 -6
  150. package/dist/ai/recipes/nuxt/ownership-atlas.md +7 -9
  151. package/dist/ai/recipes/nuxt/record-form.md +5 -5
  152. package/dist/ai/recipes/nuxt/records-workspace.md +29 -31
  153. package/dist/ai/recipes/nuxt/settings-workspace.md +22 -34
  154. package/dist/ai/recipes/nuxt/upload-center.md +22 -29
  155. package/dist/ai/recipes/nuxt/wizard-form.md +3 -11
  156. package/dist/ai/recipes/vue/approvals-inbox.md +7 -8
  157. package/dist/ai/recipes/vue/inline-edit-workspace.md +14 -24
  158. package/dist/ai/recipes/vue/layout-scaffold.md +16 -16
  159. package/dist/ai/recipes/vue/onboarding.md +2 -6
  160. package/dist/ai/recipes/vue/ownership-atlas.md +7 -9
  161. package/dist/ai/recipes/vue/record-form.md +5 -5
  162. package/dist/ai/recipes/vue/records-workspace.md +29 -31
  163. package/dist/ai/recipes/vue/settings-workspace.md +22 -34
  164. package/dist/ai/recipes/vue/upload-center.md +22 -29
  165. package/dist/ai/recipes/vue/wizard-form.md +3 -11
  166. package/dist/ai/source/BbAccordion.md +1 -1
  167. package/dist/ai/source/BbAlert.md +3 -3
  168. package/dist/ai/source/BbBadge.md +14 -10
  169. package/dist/ai/source/BbBadgeButton.md +17 -12
  170. package/dist/ai/source/BbBaseButton.md +1 -1
  171. package/dist/ai/source/BbBaseRadioIcon.md +1 -0
  172. package/dist/ai/source/BbBaseSwitchIcon.md +1 -0
  173. package/dist/ai/source/BbBreadcrumbs.md +63 -42
  174. package/dist/ai/source/BbButton.md +84 -38
  175. package/dist/ai/source/BbCalendar.md +17 -16
  176. package/dist/ai/source/BbCheckbox.md +3 -2
  177. package/dist/ai/source/BbCheckboxGroup.md +11 -4
  178. package/dist/ai/source/BbCollapsible.md +2 -1
  179. package/dist/ai/source/BbColorInput.md +10 -9
  180. package/dist/ai/source/BbColorPalette.md +2 -1
  181. package/dist/ai/source/BbConfirm.md +18 -1
  182. package/dist/ai/source/BbConfirmPortal.md +20 -6
  183. package/dist/ai/source/BbDatePicker.md +2 -2
  184. package/dist/ai/source/BbDatePickerInput.md +47 -20
  185. package/dist/ai/source/BbDialog.md +5 -5
  186. package/dist/ai/source/BbDropdown.md +231 -158
  187. package/dist/ai/source/BbDropdownButton.md +19 -12
  188. package/dist/ai/source/BbDropdownGroup.md +127 -93
  189. package/dist/ai/source/BbForm.md +25 -1
  190. package/dist/ai/source/BbHoverCard.md +30 -3
  191. package/dist/ai/source/BbHoverCardPortal.md +18 -3
  192. package/dist/ai/source/BbIndicator.md +4 -0
  193. package/dist/ai/source/BbNumberInput.md +855 -19
  194. package/dist/ai/source/BbOffCanvas.md +27 -11
  195. package/dist/ai/source/BbPagination.md +3 -3
  196. package/dist/ai/source/BbPopover.md +185 -12
  197. package/dist/ai/source/BbPopoverPortal.md +30 -3
  198. package/dist/ai/source/BbProgress.md +82 -18
  199. package/dist/ai/source/BbRadioGroup.md +12 -5
  200. package/dist/ai/source/BbRating.md +11 -9
  201. package/dist/ai/source/BbSelect.md +16 -10
  202. package/dist/ai/source/BbSelectPopover.md +29 -23
  203. package/dist/ai/source/BbSlider.md +12 -6
  204. package/dist/ai/source/BbSwitch.md +3 -2
  205. package/dist/ai/source/BbSwitchGroup.md +5 -2
  206. package/dist/ai/source/BbTable.md +102 -18
  207. package/dist/ai/source/BbTabs.md +30 -24
  208. package/dist/ai/source/BbTabsList.md +29 -23
  209. package/dist/ai/source/BbTabsPanels.md +29 -23
  210. package/dist/ai/source/BbTabsRoot.md +28 -22
  211. package/dist/ai/source/BbTag.md +10 -9
  212. package/dist/ai/source/BbTextInput.md +5 -4
  213. package/dist/ai/source/BbTextarea.md +6 -5
  214. package/dist/ai/source/BbTimePicker.md +1 -1
  215. package/dist/ai/source/BbTimePickerInput.md +46 -9
  216. package/dist/ai/source/BbToast.md +10 -7
  217. package/dist/ai/source/BbToastPortal.md +10 -7
  218. package/dist/ai/source/BbTooltip.md +88 -11
  219. package/dist/ai/source/BbTree.md +21 -3
  220. package/dist/ai/source/CommonField.md +6 -4
  221. package/dist/ai/source/CommonFieldInput.md +15 -10
  222. package/dist/ai/source/CommonPopover.md +8 -1
  223. package/dist/ai/source/CommonTimeSelector.md +5 -4
  224. package/dist/components/BbBadge/BbBadgeButton.vue_vue_type_script_setup_true_lang.js +10 -8
  225. package/dist/components/BbBaseButton/BbBaseButton.vue_vue_type_script_setup_true_lang.js +8 -8
  226. package/dist/components/BbBreadcrumbs/BbBreadcrumbs.vue_vue_type_script_setup_true_lang.js +45 -45
  227. package/dist/components/BbBreadcrumbs/types.d.ts +25 -17
  228. package/dist/components/BbButton/BbButton.vue_vue_type_script_setup_true_lang.js +88 -70
  229. package/dist/components/BbButton/toggleRoving.d.ts +6 -0
  230. package/dist/components/BbButton/toggleRoving.js +45 -0
  231. package/dist/components/BbButton/types.d.ts +12 -4
  232. package/dist/components/BbButton/types.js +5 -1
  233. package/dist/components/BbCalendar/BbCalendar.vue_vue_type_script_setup_true_lang.js +178 -178
  234. package/dist/components/BbCalendar/CalendarMonthPanel.vue_vue_type_script_setup_true_lang.js +15 -15
  235. package/dist/components/BbCalendar/CalendarYearPanel.vue_vue_type_script_setup_true_lang.js +28 -28
  236. package/dist/components/BbCalendar/useCalendarGrid.js +141 -139
  237. package/dist/components/BbCalendar/useDayjsLocale.d.ts +38 -15
  238. package/dist/components/BbCalendar/useDayjsLocale.js +31 -30
  239. package/dist/components/BbCheckbox/BbCheckbox.vue_vue_type_script_setup_true_lang.js +48 -48
  240. package/dist/components/BbCheckboxGroup/BbCheckboxGroup.vue_vue_type_script_setup_true_lang.js +81 -80
  241. package/dist/components/BbCheckboxGroup/types.d.ts +6 -2
  242. package/dist/components/BbCollapsible/types.d.ts +2 -1
  243. package/dist/components/BbColorInput/BbColorInput.vue_vue_type_script_setup_true_lang.js +69 -69
  244. package/dist/components/BbColorPalette/types.d.ts +2 -1
  245. package/dist/components/BbConfirm/BbConfirmPortal.vue.d.ts +2 -5
  246. package/dist/components/BbConfirm/types.d.ts +15 -0
  247. package/dist/components/BbDatePickerInput/BbDatePickerInput.vue_vue_type_script_setup_true_lang.js +363 -349
  248. package/dist/components/BbDropdown/AdaptiveDropdown.vue_vue_type_script_setup_true_lang.js +91 -86
  249. package/dist/components/BbDropdown/BbDropdown.vue_vue_type_script_setup_true_lang.js +298 -289
  250. package/dist/components/BbDropdown/BbDropdownList.vue_vue_type_script_setup_true_lang.js +322 -356
  251. package/dist/components/BbDropdown/DropdownPipelineResolver.vue_vue_type_script_setup_true_lang.js +17 -15
  252. package/dist/components/BbDropdown/DropdownRow.vue.d.ts +40 -0
  253. package/dist/components/BbDropdown/DropdownRow.vue.js +5 -0
  254. package/dist/components/BbDropdown/DropdownRow.vue_vue_type_script_setup_true_lang.js +60 -0
  255. package/dist/components/BbDropdown/normalizeGroups.d.ts +8 -2
  256. package/dist/components/BbDropdown/normalizeGroups.js +60 -46
  257. package/dist/components/BbDropdown/types.d.ts +120 -88
  258. package/dist/components/BbDropdownButton/BbDropdownButton.vue_vue_type_script_setup_true_lang.js +11 -2
  259. package/dist/components/BbDropdownButton/types.d.ts +2 -2
  260. package/dist/components/BbForm/BbForm.vue.d.ts +9 -0
  261. package/dist/components/BbForm/BbForm.vue_vue_type_script_setup_true_lang.js +114 -105
  262. package/dist/components/BbForm/BbFormCustomField.js +1 -1
  263. package/dist/components/BbForm/bindFormField.js +2 -1
  264. package/dist/components/BbForm/formFields.d.ts +5 -0
  265. package/dist/components/BbHoverCard/BbHoverCard.vue_vue_type_script_setup_true_lang.js +145 -142
  266. package/dist/components/BbHoverCard/BbHoverCardPortal.vue.d.ts +2 -2
  267. package/dist/components/BbHoverCard/types.d.ts +14 -1
  268. package/dist/components/BbNumberInput/BbNumberInput.vue.d.ts +0 -1
  269. package/dist/components/BbNumberInput/BbNumberInput.vue_vue_type_script_setup_true_lang.js +452 -125
  270. package/dist/components/BbNumberInput/numberDisplay.d.ts +256 -0
  271. package/dist/components/BbNumberInput/numberDisplay.js +499 -0
  272. package/dist/components/BbNumberInput/types.d.ts +54 -1
  273. package/dist/components/BbNumberInput/warroom/corpus.d.ts +58 -0
  274. package/dist/components/BbNumberInput/warroom/currency-verify.rows.d.ts +28 -0
  275. package/dist/components/BbNumberInput/warroom/currency.rows.d.ts +2 -0
  276. package/dist/components/BbNumberInput/warroom/harness.d.ts +217 -0
  277. package/dist/components/BbNumberInput/warroom/oracle.d.ts +150 -0
  278. package/dist/components/BbPagination/BbPagination.vue_vue_type_script_setup_true_lang.js +7 -7
  279. package/dist/components/BbPopover/BbPopover.vue.d.ts +1 -0
  280. package/dist/components/BbPopover/BbPopover.vue_vue_type_script_setup_true_lang.js +207 -162
  281. package/dist/components/BbPopover/BbPopoverPortal.vue.d.ts +2 -2
  282. package/dist/components/BbPopover/types.d.ts +26 -1
  283. package/dist/components/BbProgress/BbProgress.vue_vue_type_script_setup_true_lang.js +15 -7
  284. package/dist/components/BbProgress/types.d.ts +23 -7
  285. package/dist/components/BbProgress/types.js +1 -1
  286. package/dist/components/BbRadioGroup/BbRadioGroup.vue_vue_type_script_setup_true_lang.js +102 -101
  287. package/dist/components/BbRadioGroup/types.d.ts +6 -2
  288. package/dist/components/BbRating/BbRating.vue_vue_type_script_setup_true_lang.js +63 -63
  289. package/dist/components/BbSelect/BbSelect.vue_vue_type_script_setup_true_lang.js +156 -154
  290. package/dist/components/BbSelect/types.d.ts +6 -2
  291. package/dist/components/BbSelectPopover/BbSelectPopover.vue_vue_type_script_setup_true_lang.js +2 -1
  292. package/dist/components/BbSelectPopover/types.d.ts +6 -2
  293. package/dist/components/BbSlider/BbSlider.vue_vue_type_script_setup_true_lang.js +153 -153
  294. package/dist/components/BbSwitch/BbSwitch.vue_vue_type_script_setup_true_lang.js +62 -62
  295. package/dist/components/BbSwitchGroup/BbSwitchGroup.vue_vue_type_script_setup_true_lang.js +131 -130
  296. package/dist/components/BbTable/BbTable.vue_vue_type_script_setup_true_lang.js +711 -676
  297. package/dist/components/BbTabs/BbTabs.vue_vue_type_script_setup_true_lang.js +1 -1
  298. package/dist/components/BbTabs/BbTabsList.vue_vue_type_script_setup_true_lang.js +1 -1
  299. package/dist/components/BbTabs/types.d.ts +28 -22
  300. package/dist/components/BbTag/BbTag.vue_vue_type_script_setup_true_lang.js +76 -76
  301. package/dist/components/BbTextInput/BbTextInput.vue_vue_type_script_setup_true_lang.js +58 -58
  302. package/dist/components/BbTextarea/BbTextarea.vue_vue_type_script_setup_true_lang.js +82 -82
  303. package/dist/components/BbTimePickerInput/BbTimePickerInput.vue_vue_type_script_setup_true_lang.js +246 -236
  304. package/dist/components/BbTooltip/BbTooltip.vue_vue_type_script_setup_true_lang.js +99 -86
  305. package/dist/components/BbTooltip/tooltipWarmup.d.ts +23 -0
  306. package/dist/components/BbTooltip/tooltipWarmup.js +7 -0
  307. package/dist/components/BbTooltip/types.d.ts +10 -3
  308. package/dist/components/BbTree/BbTree.vue_vue_type_script_setup_true_lang.js +29 -21
  309. package/dist/components/BbTree/BbTreeLevel.vue_vue_type_script_setup_true_lang.js +10 -7
  310. package/dist/components/CommonFieldInput/CommonFieldInput.vue_vue_type_script_setup_true_lang.js +37 -36
  311. package/dist/components/CommonPopover/types.d.ts +2 -1
  312. package/dist/components/CommonTimeSelector/CommonTimeSelector.vue_vue_type_script_setup_true_lang.js +9 -9
  313. package/dist/composables/formSubmitLock.d.ts +6 -0
  314. package/dist/composables/formSubmitLock.js +4 -0
  315. package/dist/composables/useBaseOptions.d.ts +7 -1
  316. package/dist/composables/useBaseOptions.js +32 -32
  317. package/dist/composables/useBbDropdownContext.d.ts +8 -8
  318. package/dist/composables/useBbFormField.d.ts +9 -0
  319. package/dist/composables/useBbFormField.js +5 -3
  320. package/dist/composables/useBbTabsContext.d.ts +10 -10
  321. package/dist/composables/useBbTabsContext.js +18 -18
  322. package/dist/composables/useFormBinding.d.ts +11 -0
  323. package/dist/composables/useFormBinding.js +15 -13
  324. package/dist/composables/useItemText.d.ts +5 -1
  325. package/dist/composables/useItemText.js +8 -3
  326. package/dist/composables/useItemValue.d.ts +5 -1
  327. package/dist/composables/useItemValue.js +8 -3
  328. package/dist/composables/useLocale.js +10 -9
  329. package/dist/deprecation/ai-deprecations.json.d.ts +130 -0
  330. package/dist/deprecation/ai-deprecations.json.js +1 -1
  331. package/dist/deprecation/removedProps.js +1 -1
  332. package/dist/directives/bbDropdown.js +1 -1
  333. package/dist/directives/directivePortal.d.ts +13 -7
  334. package/dist/directives/directivePortal.js +1 -4
  335. package/dist/i18n/index.d.ts +7 -12
  336. package/dist/i18n/locales/cs.json +11 -14
  337. package/dist/i18n/locales/da.json +13 -16
  338. package/dist/i18n/locales/de-CH.json +137 -0
  339. package/dist/i18n/locales/de.json +14 -17
  340. package/dist/i18n/locales/el.json +10 -13
  341. package/dist/i18n/locales/en-GB.json +137 -0
  342. package/dist/i18n/locales/en.json +8 -11
  343. package/dist/i18n/locales/es-MX.json +137 -0
  344. package/dist/i18n/locales/es.json +16 -19
  345. package/dist/i18n/locales/fi.json +8 -11
  346. package/dist/i18n/locales/fr.json +23 -26
  347. package/dist/i18n/locales/hu.json +8 -11
  348. package/dist/i18n/locales/it.json +12 -15
  349. package/dist/i18n/locales/ja.json +13 -16
  350. package/dist/i18n/locales/ko.json +10 -13
  351. package/dist/i18n/locales/nb.json +13 -16
  352. package/dist/i18n/locales/nl.json +21 -24
  353. package/dist/i18n/locales/pl.json +10 -13
  354. package/dist/i18n/locales/{pt.json → pt-BR.json} +19 -22
  355. package/dist/i18n/locales/pt-PT.json +137 -0
  356. package/dist/i18n/locales/ro.json +22 -25
  357. package/dist/i18n/locales/ru.json +11 -14
  358. package/dist/i18n/locales/sv.json +16 -19
  359. package/dist/i18n/locales/tr.json +14 -17
  360. package/dist/i18n/locales/uk.json +11 -14
  361. package/dist/i18n/locales/{zh-cn.json → zh-CN.json} +10 -13
  362. package/dist/i18n/locales/zh-HK.json +137 -0
  363. package/dist/i18n/locales/zh-TW.json +137 -0
  364. package/dist/i18n/regionHints.d.ts +16 -0
  365. package/dist/i18n/regionHints.js +49 -0
  366. package/dist/i18n/regions.d.ts +286 -0
  367. package/dist/i18n/regions.js +265 -0
  368. package/dist/index.d.ts +4 -4
  369. package/dist/index.js +4 -4
  370. package/dist/llms-full.txt +1914 -1714
  371. package/dist/llms-medium.txt +76 -46
  372. package/dist/llms.txt +7 -7
  373. package/dist/locale-blueprint.json +16 -13
  374. package/dist/styles.css +2 -2
  375. package/dist/types/passthrough.d.ts +1 -1
  376. package/dist/types/ptComponentMap.d.ts +2 -2
  377. package/dist/utilities/functions/slotKey.d.ts +2 -2
  378. package/dist/utils/versionCheck.js +1 -1
  379. package/dist/vite-plugin.d.ts +22 -19
  380. package/dist/vite.js +309 -297
  381. package/llms.txt +7 -7
  382. package/package.json +5 -3
  383. package/scripts/lib/agent-manifest.mjs +2 -1
  384. package/scripts/lib/eslint-disable.mjs +17 -4
  385. package/scripts/lib/eslint-plugin.d.ts +9 -1
  386. package/scripts/lib/eslint-plugin.mjs +256 -6
  387. package/scripts/lib/helper-canon.mjs +176 -0
  388. package/scripts/lib/helpers-add.mjs +1303 -0
  389. package/scripts/lib/local-components.mjs +33 -4
  390. package/scripts/lib/locale-check.mjs +165 -0
  391. package/scripts/lib/portal-names.mjs +458 -0
  392. package/scripts/lib/redundant-item-accessor.mjs +398 -0
  393. package/scripts/lib/renamed-item-field.mjs +343 -0
  394. package/scripts/lib/slot-key.mjs +2 -2
  395. package/scripts/lib/slot-renames.mjs +1 -1
  396. package/scripts/lib/unimported-components.mjs +5 -2
  397. package/scripts/lib/validate-bb-markup.mjs +123 -56
  398. package/dist/i18n/index.js +0 -27
@@ -28,7 +28,7 @@
28
28
  :loading="loading"
29
29
  :name="name"
30
30
  :passthrough="passthrough"
31
- :readonly="readonly"
31
+ :readonly="fieldReadonly"
32
32
  :reverse="reverse"
33
33
  :show-asterisk="showAsterisk"
34
34
  :show-hint="showHint"
@@ -59,7 +59,7 @@
59
59
  :loading="loading"
60
60
  :passthrough="passthrough"
61
61
  :prepend:icon="props['prepend:icon']"
62
- :readonly="readonly"
62
+ :readonly="fieldReadonly"
63
63
  :reserve-append-space="clearable"
64
64
  :variant="variant"
65
65
  @click:clear="emit('update:modelValue', null)"
@@ -85,18 +85,19 @@
85
85
  :aria-disabled="disabled || undefined"
86
86
  :aria-invalid="fieldHasErrors || undefined"
87
87
  :aria-labelledby="ariaLabelledby"
88
- :aria-readonly="readonly || undefined"
88
+ :aria-readonly="fieldReadonly || undefined"
89
89
  :aria-valuemax="max !== Number.MAX_SAFE_INTEGER ? max : undefined"
90
90
  :aria-valuemin="min !== Number.MIN_SAFE_INTEGER ? min : undefined"
91
91
  :aria-valuenow="isNotNil(fieldModel) ? fieldModel : undefined"
92
+ :aria-valuetext="ariaValuetext"
92
93
  :autocomplete="autocomplete"
93
94
  :autofocus="autofocus"
94
95
  class="bb-number-input__input"
95
96
  :disabled="disabled"
96
97
  inputmode="decimal"
97
- :name="name"
98
+ :name="disableFormatting ? name : undefined"
98
99
  :placeholder="placeholder"
99
- :readonly="readonly"
100
+ :readonly="fieldReadonly"
100
101
  :required="fieldRequired"
101
102
  :aria-required="fieldAriaRequired"
102
103
  role="spinbutton"
@@ -121,6 +122,18 @@
121
122
  ></slot
122
123
  ></template>
123
124
  </CommonFieldInput>
125
+ <!-- Formatted mode posts the raw period string from here: the visible
126
+ text is the locale's (`10.005,6`). `form` routes from the consumer's
127
+ attributes, like BbSelect's value channel. -->
128
+ <input
129
+ v-if="!disableFormatting && name"
130
+ ref="posted"
131
+ :disabled="disabled"
132
+ :form="(passthrough.controlAttrs as { form?: string }).form"
133
+ :name="name"
134
+ type="hidden"
135
+ :value="submitValue"
136
+ />
124
137
  </template>
125
138
  </CommonField>
126
139
  </template>
@@ -143,7 +156,15 @@ import { warnRemovedAttrs } from '@/utilities/functions/warnRemovedAttrs';
143
156
  * - Supports keyboard stepping with ArrowUp/ArrowDown.
144
157
  * - Hands `increase()` / `decrease()` to every slot for step controls.
145
158
  */
146
- import { computed, mergeProps, onMounted, ref, useAttrs, watch } from 'vue';
159
+ import {
160
+ computed,
161
+ mergeProps,
162
+ onBeforeUnmount,
163
+ onMounted,
164
+ ref,
165
+ useAttrs,
166
+ watch,
167
+ } from 'vue';
147
168
  import CommonField from '../CommonField/CommonField.vue';
148
169
  import CommonFieldInput from '../CommonFieldInput/CommonFieldInput.vue';
149
170
  import { useConfig } from '@/composables/useConfig';
@@ -158,6 +179,30 @@ import { clampPrecision } from '@/utilities/functions/clampPrecision';
158
179
  import { isNil } from '@/utilities/functions/isNil';
159
180
  import { isNotNil } from '@/utilities/functions/isNotNil';
160
181
  import { vSsrValue } from '@/utilities/misc/ssrValue';
182
+ import { intlLocaleOf } from '../BbCalendar/useDayjsLocale';
183
+ import {
184
+ affixOf,
185
+ applyEdit,
186
+ currencyDigitsOf,
187
+ displayIndexOf,
188
+ formatAffixed,
189
+ isShellOnly,
190
+ numberSymbolsOf,
191
+ PLAIN_SYMBOLS,
192
+ rawAtRest,
193
+ rawIndexOf,
194
+ rawOfNumber,
195
+ readText,
196
+ snapOffset,
197
+ spokenAffixed,
198
+ stripAffix,
199
+ valueOfRaw,
200
+ withPaddedMark,
201
+ writeStrategy,
202
+ type Display,
203
+ type Edit,
204
+ type EditState,
205
+ } from './numberDisplay';
161
206
  import {
162
207
  BB_NUMBER_INPUT_PT,
163
208
  type BbNumberInputProps,
@@ -166,13 +211,13 @@ import {
166
211
  } from './types';
167
212
 
168
213
  defineOptions({ inheritAttrs: false });
169
- // `min` / `max` / `step` / `maxPrecision` drive the clamp and step math and
170
- // `aria-valuemin` / `aria-valuemax` (the SAFE_INTEGER sentinels stay silent),
171
- // so they are defaulted here, before anything reads them.
214
+ // `min` / `max` / `step` drive the clamp and step math and `aria-valuemin` /
215
+ // `aria-valuemax` (the SAFE_INTEGER sentinels stay silent), so they are
216
+ // defaulted here, before anything reads them. `maxPrecision` / `minPrecision`
217
+ // default below: with `currency` they follow the currency's digits.
172
218
  const declaredProps = withDefaults(defineProps<BbNumberInputProps>(), {
173
219
  autocomplete: 'off',
174
220
  max: Number.MAX_SAFE_INTEGER,
175
- maxPrecision: 8,
176
221
  min: Number.MIN_SAFE_INTEGER,
177
222
  step: 1,
178
223
  });
@@ -188,6 +233,7 @@ const {
188
233
  required: fieldRequired,
189
234
  ariaRequired: fieldAriaRequired,
190
235
  model: fieldModel,
236
+ readonly: fieldReadonly,
191
237
  } = useFormBinding(declaredProps, declaredEmit, {
192
238
  componentName: 'BbNumberInput',
193
239
  identifierFallback: () => declaredProps.label,
@@ -228,6 +274,612 @@ const passthrough = usePassthrough({
228
274
 
229
275
  const input = ref<HTMLInputElement | null>(null);
230
276
 
277
+ // --- Q60.25: the locale display (owner 2026-10-07) -------------------------
278
+ //
279
+ // The field shows its number the locale's way while the user types
280
+ // (`10.005,6` in it). The edited value lives in `edit.raw` (period decimal, no
281
+ // grouping); every keystroke is cancelled in `beforeinput`, applied to raw by
282
+ // the engine (`numberDisplay.ts`) and written back through
283
+ // `execCommand('insertText')`, which keeps the browser's own undo. Anything
284
+ // the browser applies itself (IME, a non-cancelable Android keystroke, undo,
285
+ // autofill, a restore) is re-read from the text. `disableFormatting` without a
286
+ // `minPrecision` is today's code path, unchanged (`legacy`).
287
+
288
+ /**
289
+ * Q60.25b: the currency or unit painted inside the text, as a shell around
290
+ * the number (`numberDisplay.ts`). `disableFormatting` ignores both; a code
291
+ * `Intl` refuses gives a plain number.
292
+ */
293
+ const affix = computed(() =>
294
+ props.disableFormatting
295
+ ? null
296
+ : affixOf(intlLocaleOf(config.locale), props.currency, props.unit)
297
+ );
298
+ /** The currency's own fraction digits (2 for EUR, 0 for JPY); `null` without one. */
299
+ const currencyDigits = computed(() =>
300
+ affix.value?.kind === 'currency'
301
+ ? currencyDigitsOf(affix.value.code, affix.value.tag)
302
+ : null
303
+ );
304
+ /** An explicit prop wins; else the currency's digits; else 8 (and at least `minPrecision`). */
305
+ const maxPrecision = computed(
306
+ () =>
307
+ props.maxPrecision ??
308
+ (currencyDigits.value
309
+ ? Math.max(currencyDigits.value.max, props.minPrecision ?? 0)
310
+ : 8)
311
+ );
312
+ const minPrecision = computed(
313
+ () => props.minPrecision ?? currencyDigits.value?.min ?? 0
314
+ );
315
+
316
+ /** Today's path: period display, Q51.2 on the whole text, `name` on the input. */
317
+ const legacy = computed(
318
+ () => !!props.disableFormatting && !(minPrecision.value > 0)
319
+ );
320
+ /** The locale's marks; period and no grouping when formatting is off. */
321
+ const symbols = computed(() =>
322
+ props.disableFormatting
323
+ ? PLAIN_SYMBOLS
324
+ : numberSymbolsOf(
325
+ intlLocaleOf(config.locale),
326
+ affix.value?.kind === 'currency' ? affix.value.code : undefined
327
+ )
328
+ );
329
+ /** `maxPrecision` wins over a larger `minPrecision` (documented, no warning). */
330
+ const shownMinPrecision = computed(() =>
331
+ Math.max(0, Math.min(minPrecision.value, maxPrecision.value))
332
+ );
333
+ const editOptions = () => ({
334
+ maxPrecision: maxPrecision.value,
335
+ minPrecision: minPrecision.value,
336
+ max: props.max,
337
+ localeKey: symbols.value.decimal === ',' ? ',' : '.',
338
+ });
339
+ /** `raw` as the field shows it: the locale's number, in the affix's shell. */
340
+ const displayOf = (raw: string) =>
341
+ formatAffixed(
342
+ raw,
343
+ symbols.value,
344
+ { minPrecision: shownMinPrecision.value },
345
+ affix.value
346
+ );
347
+ /** Text read back (paste, undo, restore) as raw: the shell stripped first. */
348
+ const readBack = (text: string) =>
349
+ readText(stripAffix(text, affix.value), symbols.value);
350
+
351
+ let edit: EditState = { raw: '', decChar: '', groupChar: '' };
352
+ let display: Display = { text: '', positions: [] };
353
+ /** The text this field last wrote; anything else in the input came from outside. */
354
+ let lastWritten = '';
355
+ /** Inside our own `execCommand` write: its nested events are ours. */
356
+ let busy = false;
357
+ /** The nested `input` of a write was emitted as the edit's `input`. */
358
+ let emittedInput = false;
359
+ let composing = false;
360
+
361
+ /** A model value as a number: a numeric string (an untyped app) counts too. */
362
+ const asNumber = (value: unknown): number | null => {
363
+ if (value === null || value === undefined || value === '') return null;
364
+ const number = Number(value);
365
+ return Number.isFinite(number) ? number : null;
366
+ };
367
+
368
+ /**
369
+ * The last valid value: what the model holds while the text is half-typed
370
+ * (`-`), what blur restores, and what the hidden input posts.
371
+ */
372
+ const committed = ref<number | null>(asNumber(props.modelValue));
373
+ const submitValue = computed(() =>
374
+ rawAtRest(committed.value, maxPrecision.value)
375
+ );
376
+
377
+ /** The display of a value at rest: rebuilt from the number, useless digits gone. */
378
+ const restText = (value: number | null | undefined) =>
379
+ displayOf(rawAtRest(value, maxPrecision.value)).text;
380
+
381
+ /** Set once mounted: the server never prints `Intl` text (CURRENCY-SPEC § 2.11). */
382
+ const mounted = ref(false);
383
+
384
+ /**
385
+ * Read only while not focused: a value text that changes per keystroke
386
+ * double-speaks. With a currency or unit, the spoken form (`1,234.50 euros`,
387
+ * `20 kilograms`), not the glyph. Only after mount, on every field: computed
388
+ * on the server, ICU drift between Node and the browser would be an attribute
389
+ * hydration mismatch.
390
+ */
391
+ const ariaValuetext = computed(() => {
392
+ if (!mounted.value) return undefined;
393
+ if (props.disableFormatting || active.value || isNil(props.modelValue))
394
+ return undefined;
395
+ const value = asNumber(props.modelValue);
396
+ return affix.value && value !== null
397
+ ? spokenAffixed(
398
+ rawAtRest(value, maxPrecision.value),
399
+ shownMinPrecision.value,
400
+ affix.value
401
+ )
402
+ : restText(value);
403
+ });
404
+
405
+ /** A raw value from outside: the decimal counts as the locale's key (owner Q6). */
406
+ const setRaw = (raw: string) => {
407
+ edit = {
408
+ raw: withPaddedMark(raw, shownMinPrecision.value),
409
+ decChar: '',
410
+ groupChar: '',
411
+ };
412
+ };
413
+
414
+ const isFocused = () =>
415
+ !!input.value && input.value.ownerDocument.activeElement === input.value;
416
+
417
+ /**
418
+ * Writes `displayOf(edit.raw)` into the input. A user edit goes through
419
+ * `execCommand('insertText')` over the changed span (a native undo step):
420
+ * the digits only while the shell stays (the symbol stays out of the undo
421
+ * step and the screen-reader echo), the whole value when the shell changes
422
+ * (first digit, sign, plural); anything else, or a failed command, sets
423
+ * `value`.
424
+ */
425
+ const paint = (caretRaw?: number, asEdit = false) => {
426
+ const el = input.value;
427
+ if (!el) return;
428
+ const shell = display.shell;
429
+ display = displayOf(edit.raw);
430
+ const next = display.text;
431
+ if (el.value !== next) {
432
+ if (asEdit && isFocused()) writeAsEdit(el, next, shell !== display.shell);
433
+ if (el.value !== next) el.value = next;
434
+ }
435
+ lastWritten = next;
436
+ if (caretRaw !== undefined && isFocused()) {
437
+ const at = displayIndexOf(display, caretRaw);
438
+ el.setSelectionRange(at, at);
439
+ }
440
+ };
441
+
442
+ const writeAsEdit = (el: HTMLInputElement, next: string, whole: boolean) => {
443
+ const current = el.value;
444
+ let head = 0;
445
+ while (
446
+ !whole &&
447
+ head < current.length &&
448
+ head < next.length &&
449
+ current[head] === next[head]
450
+ )
451
+ head++;
452
+ let tail = 0;
453
+ if (writeStrategy.span === 'minimal' && !whole) {
454
+ while (
455
+ tail < current.length - head &&
456
+ tail < next.length - head &&
457
+ current[current.length - 1 - tail] === next[next.length - 1 - tail]
458
+ )
459
+ tail++;
460
+ }
461
+ const text = next.slice(head, next.length - tail);
462
+ busy = true;
463
+ try {
464
+ el.setSelectionRange(head, current.length - tail);
465
+ // A WebKit `execCommand` inside an input listener can re-enter it: the
466
+ // `busy` guard drops the nested events (WebKit hung without one).
467
+ if (text) document.execCommand('insertText', false, text);
468
+ else document.execCommand('delete');
469
+ } catch {
470
+ // Falls through to the `value` write in `paint`.
471
+ } finally {
472
+ busy = false;
473
+ }
474
+ };
475
+
476
+ /** Emits the typed value; a half-typed text (`-`) leaves the last valid one standing. */
477
+ const commitTyped = () => {
478
+ const value = valueOfRaw(edit.raw);
479
+ if (value === undefined) return;
480
+ committed.value = value;
481
+ emit('update:modelValue', value);
482
+ };
483
+
484
+ /** Applies one edit to raw and writes the result. */
485
+ const runEdit = (op: Edit, event: Event, asEdit: boolean) => {
486
+ const result = applyEdit(edit, op, editOptions());
487
+ if (result.rejected) {
488
+ // A refused key leaves the caret where it was when that is the same
489
+ // raw spot (Delete at `12,00|`: not pulled back before the placeholders).
490
+ const el = input.value;
491
+ const stays =
492
+ !!el &&
493
+ el.value === display.text &&
494
+ el.selectionStart === el.selectionEnd &&
495
+ rawIndexOf(display, el.selectionStart ?? 0) === result.caret;
496
+ edit = result;
497
+ paint(stays ? undefined : result.caret);
498
+ return;
499
+ }
500
+ edit = result;
501
+ emittedInput = false;
502
+ paint(result.caret, asEdit);
503
+ if (!emittedInput) emit('input', event);
504
+ commitTyped();
505
+ };
506
+
507
+ /** The raw range of the input's selection. */
508
+ const rawSelection = (el: HTMLInputElement): [number, number] => [
509
+ rawIndexOf(display, el.selectionStart ?? el.value.length),
510
+ rawIndexOf(display, el.selectionEnd ?? el.value.length),
511
+ ];
512
+
513
+ const DELETES: Record<string, Edit['type']> = {
514
+ deleteContentBackward: 'deleteBackward',
515
+ deleteContent: 'deleteBackward',
516
+ deleteByCut: 'deleteBackward',
517
+ deleteByDrag: 'deleteBackward',
518
+ deleteContentForward: 'deleteForward',
519
+ deleteWordBackward: 'deleteToStart',
520
+ deleteSoftLineBackward: 'deleteToStart',
521
+ deleteHardLineBackward: 'deleteToStart',
522
+ deleteWordForward: 'deleteToEnd',
523
+ deleteSoftLineForward: 'deleteToEnd',
524
+ deleteHardLineForward: 'deleteToEnd',
525
+ };
526
+
527
+ /**
528
+ * The text of an `insertReplacementText` (desktop spellcheck, a keyboard
529
+ * suggestion): `data`, or `dataTransfer` where Chrome and Firefox put it;
530
+ * `null` when the event carries neither.
531
+ */
532
+ const replacementText = (event: InputEvent): string | null =>
533
+ event.data ??
534
+ (event.dataTransfer ? event.dataTransfer.getData('text/plain') : null);
535
+
536
+ /**
537
+ * A replacement over raw `[start, end)`: a word is text, read back like a
538
+ * paste (`$56,789.00` is 56789, not the keys `5` `6` `,` …); one character is
539
+ * a keystroke (`-` still toggles the sign); nothing deletes the range.
540
+ */
541
+ const replacementEdit = (text: string, start: number, end: number): Edit =>
542
+ [...text].length > 1
543
+ ? { type: 'paste', start, end, raw: readBack(text) }
544
+ : text
545
+ ? { type: 'insert', start, end, text }
546
+ : { type: 'deleteBackward', start, end };
547
+
548
+ /**
549
+ * Every edit the browser announces and lets us cancel: applied to raw. Undo
550
+ * and redo run natively (the steps are our own writes) and are re-read on
551
+ * `input`; IME composition and non-cancelable events take the fallback there.
552
+ */
553
+ const onBeforeinput = (event: InputEvent) => {
554
+ const el = input.value;
555
+ if (legacy.value || busy || !el) return;
556
+ if (composing || event.isComposing || !event.cancelable) return;
557
+ // A keyboard that announces no text (Android, empty `data`): the browser
558
+ // applies it and the fallback reads it on `input` (GROK-INGEST 3).
559
+ if (event.inputType === 'insertText' && !event.data) return;
560
+ const type = event.inputType;
561
+ const [start, end] = rawSelection(el);
562
+ let op: Edit | undefined;
563
+ if (type === 'insertText') {
564
+ op = { type: 'insert', start, end, text: event.data ?? '' };
565
+ } else if (type === 'insertReplacementText') {
566
+ const text = replacementText(event);
567
+ // Nothing to read, or nothing over a caret: the browser applies it and
568
+ // the fallback reads it on `input`.
569
+ if (text === null || (!text && start === end)) return;
570
+ op = replacementEdit(text, start, end);
571
+ } else if (type === 'insertFromPaste' || type === 'insertFromDrop') {
572
+ const text = event.dataTransfer?.getData('text/plain') ?? event.data ?? '';
573
+ op = { type: 'paste', start, end, raw: readBack(text) };
574
+ } else if (DELETES[type]) {
575
+ const from = el.selectionStart ?? 0;
576
+ const to = el.selectionEnd ?? 0;
577
+ if (isShellOnly(display, from, to)) {
578
+ // `€` selected in `12,00 €`, then Backspace: nothing to delete. The
579
+ // caret collapses to the nearer edge of the number.
580
+ event.preventDefault();
581
+ const edge = snapOffset(
582
+ display,
583
+ to <= display.hole![0] ? display.hole![0] : display.hole![1]
584
+ );
585
+ el.setSelectionRange(edge, edge);
586
+ return;
587
+ }
588
+ op = { type: DELETES[type], start, end } as Edit;
589
+ }
590
+ if (!op) return;
591
+ event.preventDefault();
592
+ runEdit(op, event, true);
593
+ };
594
+
595
+ /** A paste reads the clipboard: a legal number in this locale strictly, else Q51.2. */
596
+ const onPasteFormatted = (event: ClipboardEvent) => {
597
+ const el = input.value;
598
+ if (legacy.value || !el || !event.clipboardData) return;
599
+ // Not cancelled: the browser's own paste into a readonly field does nothing.
600
+ if (fieldReadonly.value || props.disabled) return;
601
+ const [start, end] = rawSelection(el);
602
+ const text = event.clipboardData.getData('text/plain');
603
+ event.preventDefault();
604
+ runEdit({ type: 'paste', start, end, raw: readBack(text) }, event, true);
605
+ };
606
+
607
+ /** A drag that started in this field: the browser's own move (`deleteByDrag` + `insertFromDrop`). */
608
+ let dragFromSelf = false;
609
+
610
+ /** The display offset under a drop point; the end when the engine cannot say. */
611
+ const dropOffsetOf = (el: HTMLInputElement, event: DragEvent) => {
612
+ const doc = el.ownerDocument as Document & {
613
+ caretPositionFromPoint?: (
614
+ x: number,
615
+ y: number
616
+ ) => { offsetNode: Node; offset: number } | null;
617
+ };
618
+ const position = doc.caretPositionFromPoint?.(event.clientX, event.clientY);
619
+ return position?.offsetNode === el ? position.offset : el.value.length;
620
+ };
621
+
622
+ /**
623
+ * A drop from outside is a paste at the drop point (PANEL § 2.6): cancelled
624
+ * and read here, never left to the default action (a synthetic drop has none,
625
+ * and its `insertFromDrop` would never come).
626
+ */
627
+ const onDropFormatted = (event: DragEvent) => {
628
+ const el = input.value;
629
+ if (legacy.value || !el || !event.dataTransfer || dragFromSelf) return;
630
+ if (fieldReadonly.value || props.disabled) return;
631
+ const text = event.dataTransfer.getData('text/plain');
632
+ event.preventDefault();
633
+ const at = rawIndexOf(display, dropOffsetOf(el, event));
634
+ el.focus();
635
+ runEdit(
636
+ { type: 'paste', start: at, end: at, raw: readBack(text) },
637
+ event,
638
+ true
639
+ );
640
+ };
641
+
642
+ /**
643
+ * Re-reads text the browser put in the field — undo / redo, autofill, a
644
+ * restore, an outside `el.value` write — as a number in this locale.
645
+ */
646
+ const rereadAll = () => {
647
+ const el = input.value;
648
+ if (!el) return;
649
+ const parsed = readBack(el.value);
650
+ const result = applyEdit(
651
+ { raw: '', decChar: '', groupChar: '' },
652
+ { type: 'paste', start: 0, end: 0, raw: parsed },
653
+ editOptions()
654
+ );
655
+ // Past the 20-character cap (owner round 7): refused, the field keeps
656
+ // its number and our text comes back.
657
+ if (result.rejected) {
658
+ paint(isFocused() ? edit.raw.length : undefined);
659
+ return;
660
+ }
661
+ setRaw(result.raw);
662
+ display = displayOf(edit.raw);
663
+ if (display.text === el.value) {
664
+ lastWritten = el.value;
665
+ return;
666
+ }
667
+ paint(isFocused() ? edit.raw.length : undefined);
668
+ };
669
+
670
+ /**
671
+ * The fallback for edits the browser applied itself (an Android keyboard's
672
+ * non-cancelable keystroke, a finished IME composition): the change between
673
+ * the text we last wrote and the text now there — its common prefix capped at
674
+ * the caret, so a deletion inside a run of equal digits lands where the user
675
+ * was — is mapped to raw and re-applied as a keystroke. Undo is lost on this
676
+ * path only.
677
+ */
678
+ const applyBrowserEdit = (event: Event) => {
679
+ const el = input.value;
680
+ if (!el) return;
681
+ const before = lastWritten;
682
+ const after = el.value;
683
+ if (before === after) return;
684
+ if ((event as InputEvent).inputType === 'insertReplacementText') {
685
+ applyReplacement(event as InputEvent, before, after);
686
+ return;
687
+ }
688
+ const caret = el.selectionStart ?? after.length;
689
+ // The change ends at the caret too: an insert inside a run of equal digits
690
+ // (`1.1|11` + `1`) is at the caret, not past it.
691
+ let tail = 0;
692
+ while (
693
+ tail < before.length &&
694
+ tail < after.length - caret &&
695
+ before[before.length - 1 - tail] === after[after.length - 1 - tail]
696
+ )
697
+ tail++;
698
+ let head = 0;
699
+ while (
700
+ head < before.length - tail &&
701
+ head < after.length - tail &&
702
+ head < caret &&
703
+ before[head] === after[head]
704
+ )
705
+ head++;
706
+ const inserted = after.slice(head, after.length - tail);
707
+ const start = rawIndexOf(display, head);
708
+ const end = rawIndexOf(display, before.length - tail);
709
+ // A shell character removed by the browser (an Android Backspace on `€`)
710
+ // heals: nothing to apply, the text is painted back.
711
+ if (!inserted && isShellOnly(display, head, before.length - tail)) {
712
+ const caret = rawIndexOf(display, head);
713
+ paint(caret);
714
+ return;
715
+ }
716
+ // A deletion of a group mark alone (`start === end`) is a Backspace (or a
717
+ // Delete) next to it: the digit beyond goes.
718
+ const forward = /Forward$/.test((event as InputEvent).inputType ?? '');
719
+ const op: Edit = inserted
720
+ ? { type: 'insert', start, end, text: inserted }
721
+ : { type: forward ? 'deleteForward' : 'deleteBackward', start, end };
722
+ runEdit(op, event, false);
723
+ };
724
+
725
+ /**
726
+ * Where a replacement of `text` went: the `[from, to)` of `before` that
727
+ * `after` has `text` in place of, the one nearest the caret when the text
728
+ * could sit in several places (runs of equal digits). `null` when `after` is
729
+ * not `before` with one span swapped for `text`.
730
+ */
731
+ const replacedSpan = (
732
+ before: string,
733
+ after: string,
734
+ text: string,
735
+ caret: number
736
+ ): [number, number] | null => {
737
+ const removed = before.length - (after.length - text.length);
738
+ if (removed < 0) return null;
739
+ let best: number | null = null;
740
+ for (let at = 0; at + text.length <= after.length; at++) {
741
+ if (before.slice(0, at) !== after.slice(0, at)) break;
742
+ if (
743
+ after.slice(at, at + text.length) !== text ||
744
+ before.slice(at + removed) !== after.slice(at + text.length)
745
+ )
746
+ continue;
747
+ const distance = Math.abs(at + text.length - caret);
748
+ if (best === null || distance < Math.abs(best + text.length - caret))
749
+ best = at;
750
+ }
751
+ return best === null ? null : [best, best + removed];
752
+ };
753
+
754
+ /**
755
+ * A replacement the browser applied itself (non-cancelable: an Android
756
+ * suggestion or autocorrect). The text it carries is read back over the span
757
+ * it replaced, the same edit the cancelable path makes; never the diff
758
+ * fragment (`1.234` → `1.234.567` shares `1.234`, and `.567` alone is not
759
+ * the number). A replacement that says nothing of its text swaps the word,
760
+ * and a number field holds one: the whole new text is read back.
761
+ */
762
+ const applyReplacement = (event: InputEvent, before: string, after: string) => {
763
+ const el = input.value!;
764
+ const text = replacementText(event);
765
+ const span =
766
+ text === null
767
+ ? null
768
+ : replacedSpan(before, after, text, el.selectionStart ?? after.length);
769
+ const op: Edit =
770
+ text !== null && span
771
+ ? replacementEdit(
772
+ text,
773
+ rawIndexOf(display, span[0]),
774
+ rawIndexOf(display, span[1])
775
+ )
776
+ : { type: 'paste', start: 0, end: edit.raw.length, raw: readBack(after) };
777
+ runEdit(op, event, false);
778
+ };
779
+
780
+ const REREAD_TYPES = new Set(['historyUndo', 'historyRedo', '']);
781
+
782
+ const onFormattedInput = (event: Event) => {
783
+ if (busy) {
784
+ // The nested `input` of our own write: the edit's one `input` event.
785
+ emittedInput = true;
786
+ emit('input', event);
787
+ return;
788
+ }
789
+ if (composing) return;
790
+ const type = (event as InputEvent).inputType ?? '';
791
+ if (REREAD_TYPES.has(type) || !(event instanceof InputEvent)) {
792
+ rereadAll();
793
+ emit('input', event);
794
+ commitTyped();
795
+ return;
796
+ }
797
+ applyBrowserEdit(event);
798
+ };
799
+
800
+ const onCompositionstart = () => {
801
+ if (!legacy.value) composing = true;
802
+ };
803
+
804
+ const onCompositionend = (event: CompositionEvent) => {
805
+ if (legacy.value || !composing) return;
806
+ composing = false;
807
+ applyBrowserEdit(event);
808
+ };
809
+
810
+ /** The hidden input a native POST reads (formatted mode with a `name`). */
811
+ const posted = ref<HTMLInputElement | null>(null);
812
+
813
+ /** The text differs from what we wrote (a restore, autofill): read it again. */
814
+ const reconcile = () => {
815
+ if (legacy.value || !input.value) return;
816
+ if (input.value.value !== lastWritten) {
817
+ rereadAll();
818
+ commitTyped();
819
+ // A submit reads the hidden input before Vue re-renders it.
820
+ if (posted.value) posted.value.value = submitValue.value;
821
+ }
822
+ };
823
+
824
+ /** Blur shows the value at rest: rebuilt from the last valid value. */
825
+ const settle = (caretAtEnd = false) => {
826
+ if (legacy.value) return;
827
+ setRaw(rawAtRest(committed.value, maxPrecision.value));
828
+ paint(caretAtEnd ? edit.raw.length : undefined);
829
+ };
830
+
831
+ const onPageshow = (event: PageTransitionEvent) => {
832
+ if (event.persisted) reconcile();
833
+ };
834
+
835
+ /**
836
+ * A silent autofill (no event) is read before the form submits: capture runs
837
+ * ahead of the consumer's own `submit` listener and of the form data. Heard
838
+ * on the document and matched against the form that owns the field at
839
+ * submit time, so a `form=` that changes after mount is followed.
840
+ */
841
+ let submitDocument: Document | null = null;
842
+ const onSubmitCapture = (event: Event) => {
843
+ const owner = (posted.value ?? input.value)?.form;
844
+ if (owner && event.target === owner) reconcile();
845
+ };
846
+
847
+ /** Between pointerdown and pointerup on the input: no clamp (a drag from the prefix). */
848
+ let pointerHeld = false;
849
+
850
+ /**
851
+ * Q60.25b (CURRENCY-SPEC § 2.3): a collapsed caret never rests on a currency /
852
+ * unit shell. A click, an arrow, Home / End landing on it moves to the nearest
853
+ * edge of the number (never before a negative value's minus). Ranges are never
854
+ * touched: select-all and a double-click on `€` stay as they are (a copy keeps
855
+ * the symbol), and a delete over the shell alone does nothing (`onBeforeinput`).
856
+ * The clamp is cosmetic: an edit with the caret on the shell still edits the
857
+ * number's edge.
858
+ */
859
+ const snapSelection = () => {
860
+ const el = input.value;
861
+ if (!el || !display.hole || busy || composing || pointerHeld) return;
862
+ if (!isFocused() || el.value !== display.text) return;
863
+ const at = el.selectionStart ?? 0;
864
+ if (at !== el.selectionEnd) return;
865
+ const snapped = snapOffset(display, at);
866
+ if (snapped !== at) el.setSelectionRange(snapped, snapped);
867
+ };
868
+
869
+ const releasePointer = () => {
870
+ pointerHeld = false;
871
+ window.removeEventListener('pointerup', releasePointer);
872
+ window.removeEventListener('pointercancel', releasePointer);
873
+ snapSelection();
874
+ };
875
+
876
+ const holdPointer = () => {
877
+ if (!display.hole) return;
878
+ pointerHeld = true;
879
+ window.addEventListener('pointerup', releasePointer);
880
+ window.addEventListener('pointercancel', releasePointer);
881
+ };
882
+
231
883
  /**
232
884
  * Applies precision and range guards to finalized numeric values. While the
233
885
  * user types, only `max` applies (`withMin: false`): the digits of 15 pass
@@ -236,7 +888,7 @@ const input = ref<HTMLInputElement | null>(null);
236
888
  */
237
889
  const normalizeNumericValue = (value: number, withMin = true): number => {
238
890
  const precisionAligned = Number(
239
- clampPrecision(value.toString(), props.maxPrecision)
891
+ clampPrecision(value.toString(), maxPrecision.value)
240
892
  );
241
893
  return clamp(precisionAligned, withMin ? props.min : -Infinity, props.max);
242
894
  };
@@ -247,6 +899,17 @@ const normalizeNumericValue = (value: number, withMin = true): number => {
247
899
  * "15" with min 10 could not be typed through its "1".
248
900
  */
249
901
  const applyMin = () => {
902
+ if (!legacy.value) {
903
+ // Enter re-reads an outside text first, as blur does (PANEL § 2.7).
904
+ reconcile();
905
+ const typed = valueOfRaw(edit.raw);
906
+ if (typeof typed !== 'number' || typed >= props.min) return;
907
+ setRaw(rawOfNumber(props.min, maxPrecision.value));
908
+ committed.value = props.min;
909
+ paint(isFocused() ? edit.raw.length : undefined);
910
+ emit('update:modelValue', props.min);
911
+ return;
912
+ }
250
913
  if (!input.value?.value) return;
251
914
  const typed = Number(makeValidNumberString(input.value.value));
252
915
  if (!Number.isFinite(typed) || typed >= props.min) return;
@@ -295,6 +958,7 @@ const increase = () => {
295
958
  * 7) Sync visible input and preserve caret position when transformed.
296
959
  */
297
960
  const onInput = (event: Event) => {
961
+ if (!legacy.value) return onFormattedInput(event);
298
962
  if (!input.value) return;
299
963
  if (event.target instanceof HTMLInputElement) {
300
964
  const value = event.target.value;
@@ -319,7 +983,7 @@ const onInput = (event: Event) => {
319
983
  }
320
984
  }
321
985
  emit('update:modelValue', valueToEmit);
322
- const clamped = clampPrecision(nextInputValue, props.maxPrecision);
986
+ const clamped = clampPrecision(nextInputValue, maxPrecision.value);
323
987
  if (input.value.value !== clamped) {
324
988
  input.value.value = clamped;
325
989
  if (selectionStart !== null && selectionEnd !== null) {
@@ -346,6 +1010,9 @@ const onKeydown = (event: KeyboardEvent) => {
346
1010
  // Q56.14: before the browser's implicit submission (it follows keydown),
347
1011
  // so a surrounding form submits the corrected value, not the typed one.
348
1012
  applyMin();
1013
+ // Owner 2026-10-07: Enter shows the value at rest as blur does (`1,50`
1014
+ // → `1,5`), the caret after the number; not while an IME commits.
1015
+ if (!event.isComposing && !composing) settle(true);
349
1016
  }
350
1017
  };
351
1018
 
@@ -441,7 +1108,7 @@ const makeValidNumberString = (value: string): string => {
441
1108
  let [first, second] = value.split('.');
442
1109
  first = first || '0';
443
1110
  if (!second) return first;
444
- second = second?.slice(0, props.maxPrecision) || '0';
1111
+ second = second?.slice(0, maxPrecision.value) || '0';
445
1112
  const unified = `${first}.${second}`;
446
1113
  return unified;
447
1114
  };
@@ -452,18 +1119,27 @@ const makeValidNumberString = (value: string): string => {
452
1119
  * bound separately (`@input.stop`).
453
1120
  */
454
1121
  const eventListeners = {
1122
+ onBeforeinput: (event: InputEvent) => onBeforeinput(event),
455
1123
  onBlur: (event: FocusEvent) => {
456
1124
  active.value = false;
1125
+ reconcile();
457
1126
  applyMin();
1127
+ settle();
458
1128
  emit('blur', event);
459
1129
  },
460
1130
  onChange: (event: Event) => emit('change', event),
461
1131
  onClick: (event: MouseEvent) => emit('click', event),
462
- onCompositionend: (event: CompositionEvent) => emit('compositionend', event),
463
- onCompositionstart: (event: CompositionEvent) =>
464
- emit('compositionstart', event),
1132
+ onCompositionend: (event: CompositionEvent) => {
1133
+ onCompositionend(event);
1134
+ emit('compositionend', event);
1135
+ },
1136
+ onCompositionstart: (event: CompositionEvent) => {
1137
+ onCompositionstart();
1138
+ emit('compositionstart', event);
1139
+ },
465
1140
  onFocus: (event: FocusEvent) => {
466
1141
  active.value = true;
1142
+ reconcile();
467
1143
  emit('focus', event);
468
1144
  },
469
1145
  onKeydown: (event: KeyboardEvent) => {
@@ -473,7 +1149,16 @@ const eventListeners = {
473
1149
  onKeyup: (event: KeyboardEvent) => emit('keyup', event),
474
1150
  onMousedown: (event: MouseEvent) => emit('mousedown', event),
475
1151
  onMouseup: (event: MouseEvent) => emit('mouseup', event),
476
- onPaste: (event: ClipboardEvent) => emit('paste', event),
1152
+ onPointerdown: () => holdPointer(),
1153
+ onPaste: (event: ClipboardEvent) => {
1154
+ onPasteFormatted(event);
1155
+ emit('paste', event);
1156
+ },
1157
+ onDragstart: () => (dragFromSelf = true),
1158
+ onDragend: () => (dragFromSelf = false),
1159
+ onDrop: (event: DragEvent) => onDropFormatted(event),
1160
+ // Fires on the control for click, keyboard, touch and script moves.
1161
+ onSelectionchange: () => snapSelection(),
477
1162
  };
478
1163
 
479
1164
  /**
@@ -496,6 +1181,15 @@ const inputBindings = computed(() =>
496
1181
  */
497
1182
  const alignInputValue = (value: string | number | null) => {
498
1183
  if (isNil(input.value)) return;
1184
+ if (!legacy.value) {
1185
+ committed.value = asNumber(value);
1186
+ // The echo of what the text emits, while the user types: the text stays
1187
+ // as typed (`10,` holds 10). Anything else replaces it (Q57.4).
1188
+ if (isFocused() && valueOfRaw(edit.raw) === committed.value) return;
1189
+ setRaw(rawAtRest(committed.value, maxPrecision.value));
1190
+ paint(isFocused() ? edit.raw.length : undefined);
1191
+ return;
1192
+ }
499
1193
 
500
1194
  const currentValue = input.value.value;
501
1195
  // This is triggered while the user is writing numbers like -0.75. Only while
@@ -536,13 +1230,51 @@ watch(
536
1230
  * the `value` attribute (`v-ssr-value`, server-only) — the same string the
537
1231
  * mount-time `alignInputValue` writes, so nothing changes at hydration.
538
1232
  */
539
- const ssrDisplayValue = () => props.modelValue?.toString() || '';
1233
+ const ssrDisplayValue = () =>
1234
+ legacy.value
1235
+ ? props.modelValue?.toString() || ''
1236
+ : rawAtRest(asNumber(props.modelValue), maxPrecision.value);
540
1237
 
541
1238
  /**
542
1239
  * Ensures initial value is painted once the native input ref exists.
543
1240
  */
544
1241
  onMounted(() => {
1242
+ // Focused before hydration (PANEL § 2.11): the server text is raw, so its
1243
+ // caret is a raw index; the formatted paint keeps it on the same digit.
1244
+ const el = input.value;
1245
+ // No `focus` event will come for a focus that predates mount.
1246
+ if (isFocused()) active.value = true;
1247
+ const caretRaw =
1248
+ el && !legacy.value && isFocused() && el.value === ssrDisplayValue()
1249
+ ? el.selectionStart
1250
+ : null;
545
1251
  alignInputValue(props.modelValue);
1252
+ if (el && caretRaw !== null && isFocused()) {
1253
+ const at = displayIndexOf(display, Math.min(caretRaw, edit.raw.length));
1254
+ el.setSelectionRange(at, at);
1255
+ }
1256
+ window.addEventListener('pageshow', onPageshow);
1257
+ submitDocument = el?.ownerDocument ?? null;
1258
+ submitDocument?.addEventListener('submit', onSubmitCapture, true);
1259
+ mounted.value = true;
1260
+ });
1261
+
1262
+ onBeforeUnmount(() => {
1263
+ window.removeEventListener('pageshow', onPageshow);
1264
+ submitDocument?.removeEventListener('submit', onSubmitCapture, true);
1265
+ window.removeEventListener('pointerup', releasePointer);
1266
+ window.removeEventListener('pointercancel', releasePointer);
1267
+ });
1268
+
1269
+ // A new locale or precision repaints the same raw value.
1270
+ watch([symbols, shownMinPrecision, maxPrecision, legacy, affix], () => {
1271
+ if (!input.value) return;
1272
+ if (legacy.value) {
1273
+ input.value.value = props.modelValue?.toString() || '';
1274
+ return;
1275
+ }
1276
+ if (!isFocused()) setRaw(rawAtRest(committed.value, maxPrecision.value));
1277
+ paint(isFocused() ? edit.raw.length : undefined);
546
1278
  });
547
1279
  </script>
548
1280
 
@@ -589,6 +1321,66 @@ export const BB_NUMBER_INPUT_PT = {
589
1321
  states: readonly BbNumberInputPtState[];
590
1322
  };
591
1323
 
1324
+ /**
1325
+ * The 45 simple units `Intl.NumberFormat` sanctions (ECMA-402;
1326
+ * `Intl.supportedValuesOf('unit')`).
1327
+ */
1328
+ export type BbNumberSimpleUnit =
1329
+ | 'acre'
1330
+ | 'bit'
1331
+ | 'byte'
1332
+ | 'celsius'
1333
+ | 'centimeter'
1334
+ | 'day'
1335
+ | 'degree'
1336
+ | 'fahrenheit'
1337
+ | 'fluid-ounce'
1338
+ | 'foot'
1339
+ | 'gallon'
1340
+ | 'gigabit'
1341
+ | 'gigabyte'
1342
+ | 'gram'
1343
+ | 'hectare'
1344
+ | 'hour'
1345
+ | 'inch'
1346
+ | 'kilobit'
1347
+ | 'kilobyte'
1348
+ | 'kilogram'
1349
+ | 'kilometer'
1350
+ | 'liter'
1351
+ | 'megabit'
1352
+ | 'megabyte'
1353
+ | 'meter'
1354
+ | 'microsecond'
1355
+ | 'mile'
1356
+ | 'mile-scandinavian'
1357
+ | 'milliliter'
1358
+ | 'millimeter'
1359
+ | 'millisecond'
1360
+ | 'minute'
1361
+ | 'month'
1362
+ | 'nanosecond'
1363
+ | 'ounce'
1364
+ | 'percent'
1365
+ | 'petabyte'
1366
+ | 'pound'
1367
+ | 'second'
1368
+ | 'stone'
1369
+ | 'terabit'
1370
+ | 'terabyte'
1371
+ | 'week'
1372
+ | 'yard'
1373
+ | 'year';
1374
+
1375
+ /**
1376
+ * A unit for `BbNumberInput`'s `unit`: a sanctioned simple unit, or two joined
1377
+ * by `-per-` (`kilometer-per-hour`; the halves are not checked, so the
1378
+ * manifest stays small). `square-meter` and `kilowatt` are not
1379
+ * `Intl` units, so they are type errors. `percent` holds the number you see:
1380
+ * 15 shows `15%`.
1381
+ */
1382
+ export type BbNumberUnit = BbNumberSimpleUnit | `${string}-per-${string}`;
1383
+
592
1384
  // The passthrough props are a mapped type written INLINE with this
593
1385
  // component's own unions (Vue's SFC type resolver does not substitute a
594
1386
  // generic alias's type parameters — plans/PASSTHROUGH.md T20); the test
@@ -635,6 +1427,16 @@ export type BbNumberInputProps = {
635
1427
  */
636
1428
  compact?: boolean;
637
1429
 
1430
+ /**
1431
+ * An ISO 4217 code (`'EUR'`, `'USD'`, `'JPY'`): the field shows the amount
1432
+ * the locale's way, symbol included (`1.234,56 €` in Italian, `$1,234.56`
1433
+ * in English), inside the text, so `#prefix` / `#suffix` stay free. The
1434
+ * model is the number (`1234.56`). The decimals default to the currency's
1435
+ * own; `minPrecision` / `maxPrecision` win when set. One or the other with
1436
+ * `unit`.
1437
+ */
1438
+ currency?: string;
1439
+
638
1440
  /**
639
1441
  * Visual variant of the field box — the same names and tokens as the
640
1442
  * `BbButton` variants. Colours only: height, padding and border width are
@@ -761,12 +1563,37 @@ export type BbNumberInputProps = {
761
1563
  max?: number;
762
1564
 
763
1565
  /**
764
- * Maximum precision of the number.
1566
+ * Maximum precision of the number: fraction digits past it are refused as
1567
+ * typed (never rounded). With `currency`, the currency's own digits (`2`
1568
+ * for EUR, `0` for JPY, `3` for KWD), or `minPrecision` when that is larger.
765
1569
  *
766
1570
  * @defaultValue `8`
767
1571
  */
768
1572
  maxPrecision?: number;
769
1573
 
1574
+ /**
1575
+ * Fraction digits always shown (`2`: `1` shows `1,00`, `1,5` shows `1,50`),
1576
+ * also while typing, where the padding zeros are placeholders the user types
1577
+ * over. The model stays the number (`1.5`). A `minPrecision` above
1578
+ * `maxPrecision` gives way to it. Applies with `disableFormatting` too
1579
+ * (the padding only, period decimal). With `currency`, the currency's own
1580
+ * digits (`2` for EUR, `0` for JPY).
1581
+ *
1582
+ * @defaultValue `0`
1583
+ */
1584
+ minPrecision?: number;
1585
+
1586
+ /**
1587
+ * Shows the number as typed, with a period decimal and no grouping — the
1588
+ * field before Q60.25: no locale display, `name` on the visible input.
1589
+ * Without it the field shows the number the locale's way while the user
1590
+ * types (`10.005,6` in Italian), groups it for them, and posts the raw value
1591
+ * (`10005.6`) through a hidden input carrying `name`.
1592
+ *
1593
+ * @defaultValue `false`
1594
+ */
1595
+ disableFormatting?: boolean;
1596
+
770
1597
  /**
771
1598
  * Minimum value cap. Applied on blur and on Enter (before a surrounding
772
1599
  * form submits), not while typing, so a value above a `min` greater than 1
@@ -826,6 +1653,15 @@ export type BbNumberInputProps = {
826
1653
  * @defaultValue `1`
827
1654
  */
828
1655
  step?: number;
1656
+
1657
+ /**
1658
+ * A unit `Intl.NumberFormat` knows (`'kilogram'`, `'kilometer-per-hour'`,
1659
+ * `'celsius'`, `'percent'`; a typo is a type error): the field shows it
1660
+ * the locale's way inside the text (`20 kg`, `15 %`). The model is the number you see: `unit="percent"`
1661
+ * showing `15 %` holds `15`, not `0.15`. One or the other with `currency`
1662
+ * (set both and `currency` wins).
1663
+ */
1664
+ unit?: BbNumberUnit;
829
1665
  } & FormFieldProps<number | string | null>;
830
1666
 
831
1667
  export type BbNumberInputEvents = {