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
@@ -14,23 +14,52 @@
14
14
  * `unknown-component` error (a typo, an invented or removed name).
15
15
  */
16
16
  import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
17
- import { basename, join, relative, sep } from 'node:path';
17
+ import { basename, dirname, join, relative, resolve, sep } from 'node:path';
18
18
 
19
19
  const BB_NAME = /^Bb[A-Z0-9]/;
20
20
 
21
+ /**
22
+ * Whether an import source is the library itself. `bitboss-ui` always is.
23
+ * Inside the library's own repo the showcases import the SOURCE
24
+ * (`@/components/BbSelect/BbSelect.vue`, `../BbSelect.vue`): the rules'
25
+ * `libraryImports` option lists those prefixes (empty by default, so a
26
+ * consumer's own `Bb*` imports stay theirs). A relative source is resolved
27
+ * against the importing file and matched as a path from `cwd`, so
28
+ * `src/components/` covers `../BbSelect.vue` from a showcase.
29
+ * @param {string} source
30
+ * @param {{ libraryImports?: string[], filename?: string, cwd?: string }} [library]
31
+ */
32
+ export function isLibrarySource(source, library) {
33
+ if (source === 'bitboss-ui' || source.startsWith('bitboss-ui/')) return true;
34
+ const prefixes = library?.libraryImports ?? [];
35
+ if (!prefixes.length) return false;
36
+ let candidate = source;
37
+ if (source.startsWith('.') && library?.filename) {
38
+ candidate = relative(
39
+ library.cwd ?? process.cwd(),
40
+ resolve(dirname(library.filename), source)
41
+ )
42
+ .split(sep)
43
+ .join('/');
44
+ }
45
+ return prefixes.some((prefix) => candidate.startsWith(prefix));
46
+ }
47
+
21
48
  /**
22
49
  * `Bb*` names a file imports from anywhere but `bitboss-ui` (its subpaths
23
- * included) — default and named imports alike. Type-only imports are skipped.
50
+ * included, and the `libraryImports` prefixes, see `isLibrarySource`) —
51
+ * default and named imports alike. Type-only imports are skipped.
24
52
  * @param {string} code
53
+ * @param {{ libraryImports?: string[], filename?: string, cwd?: string }} [library]
25
54
  * @returns {Map<string, number>} name → 1-based line of the import
26
55
  */
27
- export function localBbImports(code) {
56
+ export function localBbImports(code, library) {
28
57
  const found = new Map();
29
58
  const IMPORT = /\bimport\s+(type\s+)?([\s\S]*?)\s+from\s*['"]([^'"]+)['"]/g;
30
59
  for (const match of code.matchAll(IMPORT)) {
31
60
  const [, typeOnly, clause, source] = match;
32
61
  if (typeOnly) continue;
33
- if (source === 'bitboss-ui' || source.startsWith('bitboss-ui/')) continue;
62
+ if (isLibrarySource(source, library)) continue;
34
63
  const line = code.slice(0, match.index).split('\n').length;
35
64
  const names = [];
36
65
  const defaultName = /^\s*([A-Za-z_$][\w$]*)/.exec(
@@ -0,0 +1,165 @@
1
+ /**
2
+ * `check`: a locale the library does not ship (owner ruling 2026-10-07). The
3
+ * locale list is fixed and typed, so `locale: 'en-UK'` is a type error where
4
+ * TypeScript sees it — but a `vite.config.js`, a Nuxt config or a
5
+ * `setConfig({ locale })` in a plain `<script>` is not type-checked, and the
6
+ * plugin's own warning only reaches whoever reads the dev-server log.
7
+ *
8
+ * `unknown-locale` reads the build plugin's inline `locale` / `enabledLocales`
9
+ * literals and every `setConfig({ locale: '…' })` literal, and names the
10
+ * nearest shipped locale (`en-UK` → `en-GB`, `de-LU` → `de`). The shipped list
11
+ * is the manifest's `BuiltinLocaleKey` values; the aliases the runtime takes
12
+ * (`zh-cn`, `pt`, `it-IT`) are derived the way the runtime defines them: any
13
+ * case, and the full code of a language's default region (CLDR likely
14
+ * subtags, `Intl.Locale.prototype.maximize`). Regexes over comment-blanked
15
+ * source, the same trade the rest of `check` makes.
16
+ */
17
+
18
+ import { readFileSync } from 'node:fs';
19
+ import { readPluginOptions } from './hand-imported-styles.mjs';
20
+
21
+ export const UNKNOWN_LOCALE_KIND = 'unknown-locale';
22
+
23
+ /** The canonical keys, from the manifest. */
24
+ const shippedKeys = (manifest) => {
25
+ const types = manifest?.types;
26
+ const list =
27
+ types instanceof Map ? [...types.values()] : Object.values(types ?? {});
28
+ return list.find((type) => type?.name === 'BuiltinLocaleKey')?.values ?? [];
29
+ };
30
+
31
+ /** `language-REGION` of a tag's likely subtags, or `undefined`. */
32
+ const likely = (tag) => {
33
+ try {
34
+ const { language, region } = new Intl.Locale(tag).maximize();
35
+ return region ? `${language}-${region}` : language;
36
+ } catch {
37
+ return undefined;
38
+ }
39
+ };
40
+
41
+ /**
42
+ * Resolves a locale as written to its canonical key, or `undefined`.
43
+ * @param {string} input
44
+ * @param {string[]} keys the canonical keys
45
+ */
46
+ export function resolveLocale(input, keys) {
47
+ const lower = input.trim().replace(/_/g, '-').toLowerCase();
48
+ const byLower = new Map(keys.map((key) => [key.toLowerCase(), key]));
49
+ if (byLower.has(lower)) return byLower.get(lower);
50
+ const [language] = lower.split('-');
51
+ const full = likely(lower)?.toLowerCase();
52
+ // A bare language whose default region is a key (`pt` → `pt-BR`).
53
+ if (!lower.includes('-') && full && byLower.has(full))
54
+ return byLower.get(full);
55
+ // The default region spelled out (`it-IT` → `it`, `en-US` → `en`).
56
+ if (byLower.has(language) && likely(language)?.toLowerCase() === lower)
57
+ return byLower.get(language);
58
+ return undefined;
59
+ }
60
+
61
+ /** The nearest shipped locale: same language, `UK` read as `GB`; else the bare language. */
62
+ export function nearestLocale(input, keys) {
63
+ if (resolveLocale(input, keys)) return undefined;
64
+ const [language, region] = input.trim().replace(/_/g, '-').split('-');
65
+ if (!language) return undefined;
66
+ const fixed = region
67
+ ? ({ UK: 'GB' }[region.toUpperCase()] ?? region.toUpperCase())
68
+ : undefined;
69
+ return (
70
+ (fixed && resolveLocale(`${language}-${fixed}`, keys)) ||
71
+ resolveLocale(language, keys)
72
+ );
73
+ }
74
+
75
+ const lineAt = (code, index) => code.slice(0, index).split('\n').length;
76
+ const blank = (text) => text.replace(/[^\n]/g, ' ');
77
+ const blankComments = (code) =>
78
+ code
79
+ .replace(/\/\*[\s\S]*?\*\//g, blank)
80
+ .replace(/(^|[^:\\])(\/\/[^\n]*)/g, (_, before, c) => before + blank(c))
81
+ .replace(/<!--[\s\S]*?-->/g, blank);
82
+
83
+ const finding = (file, code, index, value, keys, where) => {
84
+ const near = nearestLocale(value, keys);
85
+ return {
86
+ file,
87
+ line: lineAt(code, index),
88
+ kind: UNKNOWN_LOCALE_KIND,
89
+ component: 'bitboss-ui',
90
+ attr: 'locale',
91
+ message: `${where} \`'${value}'\` is not a locale bitboss-ui ships${near ? ` — did you mean '${near}'?` : ''} Strings fall back to the default locale. The list: \`BuiltinLocaleKey\` (guides/installation-and-plugin-setup.md § Locales).`,
92
+ ...(near ? { suggestion: near } : {}),
93
+ };
94
+ };
95
+
96
+ /** Every `locale: '…'` / `enabledLocales: [ … ]` literal in `text`, offset by `base`. */
97
+ function optionLiterals(text, base) {
98
+ const out = [];
99
+ for (const match of text.matchAll(/\blocale\s*:\s*(['"])([^'"\n]+)\1/g))
100
+ out.push({
101
+ value: match[2],
102
+ index: base + match.index,
103
+ where: '`locale`',
104
+ });
105
+ for (const match of text.matchAll(/\benabledLocales\s*:\s*\[([^\]]*)\]/g)) {
106
+ const start = base + match.index + match[0].indexOf('[');
107
+ for (const entry of match[1].matchAll(/(['"])([^'"\n]+)\1/g))
108
+ out.push({
109
+ value: entry[2],
110
+ index: start + 1 + entry.index,
111
+ where: '`enabledLocales` entry',
112
+ });
113
+ }
114
+ return out;
115
+ }
116
+
117
+ /**
118
+ * Locales the library does not ship, in the build config and in
119
+ * `setConfig({ locale })` calls.
120
+ * @param {string} projectRoot
121
+ * @param {{ path: string, relPath: string }[]} files
122
+ * @param {object} manifest
123
+ * @param {(path: string) => string} [read]
124
+ */
125
+ export function unknownLocaleFindings(
126
+ projectRoot,
127
+ files,
128
+ manifest,
129
+ read = (path) => readFileSync(path, 'utf-8')
130
+ ) {
131
+ const keys = shippedKeys(manifest);
132
+ if (!keys.length) return [];
133
+ const findings = [];
134
+ const plugin = readPluginOptions(projectRoot);
135
+ if (plugin?.options) {
136
+ const base = plugin.code.indexOf(plugin.options);
137
+ for (const { value, index, where } of optionLiterals(plugin.options, base))
138
+ if (!resolveLocale(value, keys))
139
+ findings.push(
140
+ finding(plugin.config, plugin.code, index, value, keys, where)
141
+ );
142
+ }
143
+ for (const { path, relPath } of files) {
144
+ if (!/\.(?:vue|[cm]?[jt]sx?)$/.test(relPath) || relPath === plugin?.config)
145
+ continue;
146
+ let code;
147
+ try {
148
+ code = blankComments(read(path));
149
+ } catch {
150
+ continue;
151
+ }
152
+ if (!code.includes('setConfig')) continue;
153
+ const CALL =
154
+ /\bsetConfig\s*\(\s*\{[^}]*?\blocale\s*:\s*(['"])([^'"\n]+)\1/g;
155
+ for (const match of code.matchAll(CALL)) {
156
+ const value = match[2];
157
+ if (resolveLocale(value, keys)) continue;
158
+ const index = match.index + match[0].lastIndexOf(value) - 1;
159
+ findings.push(
160
+ finding(relPath, code, index, value, keys, '`setConfig({ locale })`')
161
+ );
162
+ }
163
+ }
164
+ return findings;
165
+ }
@@ -0,0 +1,458 @@
1
+ /**
2
+ * Portal names (Q63.8, owner 2026-10-06): a named portal is a string on one
3
+ * side and a `<…Portal name>` on the other, so a typo, a portal of the wrong
4
+ * family or a forgotten declaration opens nothing, silently. Three findings:
5
+ *
6
+ * - `unknown-portal` (error): a call site names a portal no file declares,
7
+ * with a did-you-mean;
8
+ * - `portal-family-mismatch` (error): the name belongs to the other family
9
+ * (a hover-card portal opened by `v-bb-popover`);
10
+ * - `unused-portal` (warning): a declared portal nothing opens.
11
+ *
12
+ * Call sites: `v-bb-hover-card` / `v-bb-popover` with `"'name'"` or
13
+ * `{ portal: 'name' }`, and `confirm({ portal: 'name' })` in a script.
14
+ * Declarations: `<BbHoverCardPortal | BbPopoverPortal | BbConfirmPortal
15
+ * name="x">`. Everything else is dynamic and says nothing: a `:name` binding
16
+ * leaves its family's call sites unjudged, a non-literal call site (a variable,
17
+ * a template literal, `confirm(options)`) leaves its family's declarations
18
+ * unjudged, and a declared name that appears as ANY string literal in the
19
+ * project counts as opened. A false report costs more than a missed one.
20
+ *
21
+ * The project is scanned once per root (cached, like unimported-components),
22
+ * tests skipped. `check` and the ESLint rules (`bitboss-ui/portal-names`,
23
+ * `bitboss-ui/unused-portal`) share this module.
24
+ */
25
+ import { readFileSync } from 'node:fs';
26
+ import { parse } from 'vue/compiler-sfc';
27
+ import { walk } from './unimported-components.mjs';
28
+ import { nearestName } from './validate-bb-markup.mjs';
29
+
30
+ /** Each family: its declaration tag, and how its call sites are written. */
31
+ export const PORTAL_FAMILIES = {
32
+ hoverCard: {
33
+ component: 'BbHoverCardPortal',
34
+ directive: 'bb-hover-card',
35
+ opener: 'v-bb-hover-card',
36
+ openWith: (name) => `v-bb-hover-card="'${name}'"`,
37
+ },
38
+ popover: {
39
+ component: 'BbPopoverPortal',
40
+ directive: 'bb-popover',
41
+ opener: 'v-bb-popover',
42
+ openWith: (name) => `v-bb-popover="'${name}'"`,
43
+ },
44
+ confirm: {
45
+ component: 'BbConfirmPortal',
46
+ directive: null,
47
+ opener: 'confirm()',
48
+ openWith: (name) => `confirm({ portal: '${name}' })`,
49
+ },
50
+ };
51
+
52
+ const FAMILY_BY_COMPONENT = new Map(
53
+ Object.entries(PORTAL_FAMILIES).map(([family, f]) => [f.component, family])
54
+ );
55
+ const FAMILY_BY_DIRECTIVE = new Map(
56
+ Object.entries(PORTAL_FAMILIES)
57
+ .filter(([, f]) => f.directive)
58
+ .map(([family, f]) => [f.directive, family])
59
+ );
60
+
61
+ const NODE_ELEMENT = 1;
62
+ const PROP_ATTRIBUTE = 6;
63
+ const PROP_DIRECTIVE = 7;
64
+
65
+ const pascal = (tag) =>
66
+ tag.includes('-')
67
+ ? tag.replace(/(?:^|-)([a-z0-9])/g, (_, c) => c.toUpperCase())
68
+ : tag;
69
+
70
+ /** `'name'` / `"name"` as a whole expression, else null. */
71
+ const stringLiteral = (text) =>
72
+ /^\s*(['"])([^'"\\\n]+)\1\s*$/.exec(text ?? '')?.[2] ?? null;
73
+
74
+ const LITERALS = /(['"])([^'"\\\n]{1,120})\1/g;
75
+
76
+ /** Text that may hold a portal site: a quick test before any parse. */
77
+ export const MAY_HAVE_PORTALS =
78
+ /Portal|portal|confirm|bb-popover|bb-hover-card/;
79
+
80
+ /** Every quoted string in a piece of code: a name there may open a portal. */
81
+ const literalsIn = (text, into) => {
82
+ for (const [, , value] of (text ?? '').matchAll(LITERALS)) into.add(value);
83
+ };
84
+
85
+ /**
86
+ * A PascalCase identifier the file imports or declares: a component portal,
87
+ * never a name. Anything else non-literal is dynamic.
88
+ */
89
+ const isComponentBinding = (identifier, code) =>
90
+ /^[A-Z][\w$]*$/.test(identifier) &&
91
+ (new RegExp(`\\bimport\\b[^;]*\\b${identifier}\\b[^;]*\\bfrom\\b`).test(
92
+ code
93
+ ) ||
94
+ new RegExp(`\\b(?:const|let|var|function|class)\\s+${identifier}\\b`).test(
95
+ code
96
+ ));
97
+
98
+ /**
99
+ * What a `portal` value is: `{ name }`, `{ component: true }` or
100
+ * `{ dynamic: true }`.
101
+ */
102
+ const classifyPortalValue = (text, code) => {
103
+ const name = stringLiteral(text);
104
+ if (name) return { name };
105
+ const trimmed = (text ?? '').trim();
106
+ if (/^[A-Za-z_$][\w$]*$/.test(trimmed) && isComponentBinding(trimmed, code))
107
+ return { component: true };
108
+ return { dynamic: true };
109
+ };
110
+
111
+ /**
112
+ * The `portal` key of an object literal's source: a literal name (with its
113
+ * line offset inside `text`), a component, dynamic, or absent.
114
+ */
115
+ const portalKey = (text, code) => {
116
+ const keyed = /\bportal\s*:\s*/.exec(text);
117
+ if (keyed) {
118
+ const start = keyed.index + keyed[0].length;
119
+ const rest = text.slice(start);
120
+ const quoted = /^(['"])([^'"\\\n]+)\1/.exec(rest);
121
+ const offset = (text.slice(0, start).match(/\n/g) ?? []).length;
122
+ if (quoted) return { name: quoted[2], offset };
123
+ const ident = /^([A-Za-z_$][\w$]*)\s*(?:[,}\n]|$)/.exec(rest)?.[1];
124
+ if (ident && isComponentBinding(ident, code)) return { component: true };
125
+ return { dynamic: true };
126
+ }
127
+ // `{ portal, portalProps }`: shorthand, a variable.
128
+ if (/[{,]\s*portal\s*[,}]/.test(text)) return { dynamic: true };
129
+ return null;
130
+ };
131
+
132
+ /** The text of a call's arguments, from the `(` at `open`. */
133
+ const callArguments = (code, open) => {
134
+ let depth = 0;
135
+ let quote = null;
136
+ for (let i = open; i < code.length; i++) {
137
+ const c = code[i];
138
+ if (quote) {
139
+ if (c === '\\') i++;
140
+ else if (c === quote) quote = null;
141
+ continue;
142
+ }
143
+ if (c === "'" || c === '"' || c === '`') quote = c;
144
+ else if (c === '(') depth++;
145
+ else if (c === ')' && --depth === 0) return code.slice(open + 1, i);
146
+ }
147
+ return null;
148
+ };
149
+
150
+ const lineAt = (text, index) =>
151
+ (text.slice(0, index).match(/\n/g) ?? []).length;
152
+
153
+ /**
154
+ * The script with its comments and template literals blanked (newlines
155
+ * kept, so lines still count): a `confirm({ portal: 'x' })` in a JSDoc
156
+ * example or inside a string is not a call. Quoted strings stay, they are
157
+ * the names.
158
+ */
159
+ const maskScript = (code) => {
160
+ let out = '';
161
+ let state = null;
162
+ for (let i = 0; i < code.length; i++) {
163
+ const c = code[i];
164
+ const next = code[i + 1];
165
+ const blank = c === '\n' ? '\n' : ' ';
166
+ if (state === 'line') {
167
+ if (c === '\n') {
168
+ state = null;
169
+ out += c;
170
+ } else out += ' ';
171
+ } else if (state === 'block') {
172
+ if (c === '*' && next === '/') {
173
+ state = null;
174
+ out += ' ';
175
+ i++;
176
+ } else out += blank;
177
+ } else if (state === '`') {
178
+ if (c === '\\') {
179
+ out += ' ';
180
+ i++;
181
+ } else if (c === '`') {
182
+ state = null;
183
+ out += c;
184
+ } else out += blank;
185
+ } else if (state === "'" || state === '"') {
186
+ out += c;
187
+ if (c === '\\') {
188
+ out += next ?? '';
189
+ i++;
190
+ } else if (c === state || c === '\n') state = null;
191
+ } else if (c === '/' && next === '/') {
192
+ state = 'line';
193
+ out += ' ';
194
+ i++;
195
+ } else if (c === '/' && next === '*') {
196
+ state = 'block';
197
+ out += ' ';
198
+ i++;
199
+ } else {
200
+ if (c === "'" || c === '"' || c === '`') state = c;
201
+ out += c;
202
+ }
203
+ }
204
+ return out;
205
+ };
206
+
207
+ /**
208
+ * `confirm(…)` calls in a script: literal portals become call sites, any other
209
+ * argument that could carry a portal marks the confirm family dynamic.
210
+ */
211
+ const scriptSites = (source, firstLine, code, sites) => {
212
+ const script = maskScript(source);
213
+ literalsIn(script, sites.literals);
214
+ // A template literal with no interpolation is a plain string too.
215
+ for (const [, value] of source.matchAll(/`([^`$\\\n]{1,120})`/g))
216
+ sites.literals.add(value);
217
+ for (const match of script.matchAll(/\bconfirm\s*\(/g)) {
218
+ const open = match.index + match[0].length - 1;
219
+ const args = callArguments(script, open);
220
+ if (args == null) continue;
221
+ const trimmed = args.trim();
222
+ if (!trimmed || /^(['"`])/.test(trimmed)) continue;
223
+ if (!trimmed.startsWith('{')) {
224
+ sites.dynamicCalls.add('confirm');
225
+ continue;
226
+ }
227
+ const portal = portalKey(trimmed, code);
228
+ if (!portal || portal.component) continue;
229
+ if (portal.dynamic) {
230
+ sites.dynamicCalls.add('confirm');
231
+ continue;
232
+ }
233
+ sites.calls.push({
234
+ family: 'confirm',
235
+ name: portal.name,
236
+ line:
237
+ firstLine +
238
+ lineAt(script, open) +
239
+ lineAt(args, args.indexOf(trimmed)) +
240
+ portal.offset,
241
+ });
242
+ }
243
+ };
244
+
245
+ /** A declaration's `name`: a static attribute, a literal binding, or dynamic. */
246
+ const declarationName = (node) => {
247
+ for (const prop of node.props ?? []) {
248
+ if (prop.type === PROP_ATTRIBUTE && prop.name === 'name')
249
+ return { name: prop.value?.content ?? '', line: prop.loc.start.line };
250
+ if (
251
+ prop.type === PROP_DIRECTIVE &&
252
+ prop.name === 'bind' &&
253
+ prop.arg?.content === 'name'
254
+ ) {
255
+ const name = stringLiteral(prop.exp?.content);
256
+ return name ? { name, line: prop.loc.start.line } : { dynamic: true };
257
+ }
258
+ // `v-bind="obj"`: the name may be in it.
259
+ if (prop.type === PROP_DIRECTIVE && prop.name === 'bind' && !prop.arg)
260
+ return { dynamic: true };
261
+ }
262
+ return { dynamic: true };
263
+ };
264
+
265
+ /**
266
+ * The portal sites of one file.
267
+ * @param {string} code
268
+ * @param {string} [path] decides `.vue` vs a script file
269
+ */
270
+ export function fileSites(code, path = 'x.vue') {
271
+ const sites = {
272
+ declarations: [],
273
+ calls: [],
274
+ dynamicDeclarations: new Set(),
275
+ dynamicCalls: new Set(),
276
+ literals: new Set(),
277
+ };
278
+ if (!path.endsWith('.vue')) {
279
+ if (/\bconfirm\s*\(/.test(code) || /['"]/.test(code))
280
+ scriptSites(code, 1, code, sites);
281
+ return sites;
282
+ }
283
+ let descriptor;
284
+ try {
285
+ ({ descriptor } = parse(code, { sourceMap: false }));
286
+ } catch {
287
+ return sites;
288
+ }
289
+ for (const block of [descriptor.script, descriptor.scriptSetup])
290
+ if (block) scriptSites(block.content, block.loc.start.line, code, sites);
291
+
292
+ const walkTemplate = (node) => {
293
+ if (!node || typeof node !== 'object') return;
294
+ if (node.type === NODE_ELEMENT) {
295
+ const family = FAMILY_BY_COMPONENT.get(pascal(node.tag));
296
+ if (family) {
297
+ const declared = declarationName(node);
298
+ if (declared.dynamic) sites.dynamicDeclarations.add(family);
299
+ else sites.declarations.push({ family, ...declared });
300
+ }
301
+ for (const prop of node.props ?? []) {
302
+ if (prop.type !== PROP_DIRECTIVE) continue;
303
+ const exp = prop.exp?.content;
304
+ literalsIn(exp, sites.literals);
305
+ const directiveFamily = FAMILY_BY_DIRECTIVE.get(prop.name);
306
+ if (!directiveFamily || !exp) continue;
307
+ const line = prop.exp.loc?.start?.line ?? prop.loc.start.line;
308
+ const trimmed = exp.trim();
309
+ if (trimmed.startsWith('{')) {
310
+ const portal = portalKey(trimmed, code);
311
+ if (!portal || portal.component) continue;
312
+ if (portal.dynamic) sites.dynamicCalls.add(directiveFamily);
313
+ else
314
+ sites.calls.push({
315
+ family: directiveFamily,
316
+ name: portal.name,
317
+ line: line + lineAt(exp, exp.indexOf(trimmed)) + portal.offset,
318
+ });
319
+ continue;
320
+ }
321
+ const value = classifyPortalValue(trimmed, code);
322
+ if (value.dynamic) sites.dynamicCalls.add(directiveFamily);
323
+ else if (value.name)
324
+ sites.calls.push({ family: directiveFamily, name: value.name, line });
325
+ }
326
+ }
327
+ for (const child of node.children ?? []) walkTemplate(child);
328
+ };
329
+ walkTemplate(descriptor.template?.ast);
330
+ return sites;
331
+ }
332
+
333
+ const SOURCE = /\.(?:vue|[cm]?[jt]sx?)$/;
334
+
335
+ const cache = new Map();
336
+
337
+ /**
338
+ * Every portal site of a project, scanned once per root (tests skipped).
339
+ * @param {string} projectRoot
340
+ */
341
+ export function projectPortalIndex(projectRoot) {
342
+ if (cache.has(projectRoot)) return cache.get(projectRoot);
343
+ const index = emptyIndex();
344
+ for (const path of walk(projectRoot)) {
345
+ if (path.endsWith('.d.ts') || !SOURCE.test(path)) continue;
346
+ let code;
347
+ try {
348
+ code = readFileSync(path, 'utf-8');
349
+ } catch {
350
+ continue;
351
+ }
352
+ if (!MAY_HAVE_PORTALS.test(code)) {
353
+ // Still a source of string literals for `unused-portal`.
354
+ literalsIn(code, index.literals);
355
+ for (const [, value] of code.matchAll(/`([^`$\\\n]{1,120})`/g))
356
+ index.literals.add(value);
357
+ continue;
358
+ }
359
+ addSites(index, fileSites(code, path), path);
360
+ }
361
+ cache.set(projectRoot, index);
362
+ return index;
363
+ }
364
+
365
+ function emptyIndex() {
366
+ return {
367
+ /** family → name → files */
368
+ declared: new Map(Object.keys(PORTAL_FAMILIES).map((f) => [f, new Map()])),
369
+ dynamicDeclarations: new Set(),
370
+ dynamicCalls: new Set(),
371
+ literals: new Set(),
372
+ };
373
+ }
374
+
375
+ function addSites(index, sites, path) {
376
+ for (const { family, name } of sites.declarations) {
377
+ const byName = index.declared.get(family);
378
+ if (!byName.has(name)) byName.set(name, new Set());
379
+ byName.get(name).add(path);
380
+ }
381
+ for (const family of sites.dynamicDeclarations)
382
+ index.dynamicDeclarations.add(family);
383
+ for (const family of sites.dynamicCalls) index.dynamicCalls.add(family);
384
+ for (const value of sites.literals) index.literals.add(value);
385
+ }
386
+
387
+ /** For tests: forget cached project scans. */
388
+ export const clearPortalCache = () => cache.clear();
389
+
390
+ const quoteCall = (family, name) =>
391
+ family === 'confirm'
392
+ ? `\`confirm({ portal: '${name}' })\``
393
+ : `\`${PORTAL_FAMILIES[family].openWith(name)}\``;
394
+
395
+ /**
396
+ * The findings of one file against its project (`index`; `null` judges the
397
+ * file alone). The file's own sites are read fresh, so an editor sees its
398
+ * edits before the cached scan does.
399
+ * @param {string} code
400
+ * @param {string} path
401
+ * @param {ReturnType<typeof projectPortalIndex> | null} index
402
+ */
403
+ export function portalFindings(code, path, index) {
404
+ const sites = fileSites(code, path);
405
+ if (!sites.calls.length && !sites.declarations.length) return [];
406
+ const merged = emptyIndex();
407
+ if (index) {
408
+ for (const [family, byName] of index.declared)
409
+ for (const [name, files] of byName)
410
+ merged.declared.get(family).set(name, new Set(files));
411
+ for (const family of index.dynamicDeclarations)
412
+ merged.dynamicDeclarations.add(family);
413
+ for (const family of index.dynamicCalls) merged.dynamicCalls.add(family);
414
+ for (const value of index.literals) merged.literals.add(value);
415
+ }
416
+ addSites(merged, sites, path);
417
+
418
+ const findings = [];
419
+ for (const { family, name, line } of sites.calls) {
420
+ const own = merged.declared.get(family);
421
+ if (own.has(name) || merged.dynamicDeclarations.has(family)) continue;
422
+ const f = PORTAL_FAMILIES[family];
423
+ const other = Object.keys(PORTAL_FAMILIES).find(
424
+ (g) => g !== family && merged.declared.get(g).has(name)
425
+ );
426
+ if (other) {
427
+ findings.push({
428
+ component: f.component,
429
+ attr: f.opener,
430
+ kind: 'portal-family-mismatch',
431
+ line,
432
+ message: `\`${name}\` is a \`<${PORTAL_FAMILIES[other].component}>\`, not a \`<${f.component}>\`, so ${quoteCall(family, name)} opens nothing; open it with ${quoteCall(other, name)} or declare \`<${f.component} name="${name}">\``,
433
+ });
434
+ continue;
435
+ }
436
+ const guess = nearestName(name, own.keys());
437
+ findings.push({
438
+ component: f.component,
439
+ attr: f.opener,
440
+ kind: 'unknown-portal',
441
+ line,
442
+ message: `${quoteCall(family, name)} opens nothing: no \`<${f.component} name="${name}">\` in this project${guess ? `; did you mean \`${guess}\`?` : ''}`,
443
+ });
444
+ }
445
+ for (const { family, name, line } of sites.declarations) {
446
+ if (merged.dynamicCalls.has(family) || merged.literals.has(name)) continue;
447
+ const f = PORTAL_FAMILIES[family];
448
+ findings.push({
449
+ component: f.component,
450
+ attr: 'name',
451
+ kind: 'unused-portal',
452
+ severity: 'warning',
453
+ line,
454
+ message: `no \`${f.opener}\` opens the portal \`${name}\`; open it with ${quoteCall(family, name)} or remove it`,
455
+ });
456
+ }
457
+ return findings;
458
+ }