bitboss-ui 3.0.0-beta.48 → 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 (356) hide show
  1. package/README.md +24 -7
  2. package/bin/bitboss-ui-mcp.mjs +15 -3
  3. package/bin/bitboss-ui.mjs +115 -13
  4. package/dist/ai/BbAvatar.md +1 -1
  5. package/dist/ai/BbBreadcrumbs.md +65 -65
  6. package/dist/ai/BbButton.md +16 -5
  7. package/dist/ai/BbCheckbox.md +0 -2
  8. package/dist/ai/BbCheckboxGroup.md +13 -20
  9. package/dist/ai/BbDatePickerInput.md +4 -2
  10. package/dist/ai/BbDialog.md +1 -8
  11. package/dist/ai/BbDropdown.md +231 -220
  12. package/dist/ai/BbDropdownButton.md +65 -65
  13. package/dist/ai/BbDropdownGroup.md +2 -2
  14. package/dist/ai/BbDropzone.md +10 -10
  15. package/dist/ai/BbForm.md +43 -52
  16. package/dist/ai/BbHoverCard.md +4 -2
  17. package/dist/ai/BbNumberInput.md +88 -21
  18. package/dist/ai/BbPopover.md +29 -23
  19. package/dist/ai/BbProgress.md +48 -22
  20. package/dist/ai/BbRadioGroup.md +13 -19
  21. package/dist/ai/BbSelect.md +16 -36
  22. package/dist/ai/BbSelectPopover.md +14 -30
  23. package/dist/ai/BbSmoothHeight.md +0 -2
  24. package/dist/ai/BbSwitchGroup.md +13 -20
  25. package/dist/ai/BbTable.md +11 -10
  26. package/dist/ai/BbTabs.md +106 -105
  27. package/dist/ai/BbTabsList.md +2 -2
  28. package/dist/ai/BbTabsPanels.md +1 -1
  29. package/dist/ai/BbTabsRoot.md +2 -2
  30. package/dist/ai/BbTooltip.md +24 -10
  31. package/dist/ai/BbTree.md +14 -11
  32. package/dist/ai/changelog.json +256 -5
  33. package/dist/ai/components.json +36 -34
  34. package/dist/ai/composables/useBbDropdownContext.md +16 -16
  35. package/dist/ai/composables/useBbFormField.md +5 -1
  36. package/dist/ai/composables/useBbTabsContext.md +14 -14
  37. package/dist/ai/guides/agent-contract.md +27 -27
  38. package/dist/ai/guides/ai-router.md +1 -1
  39. package/dist/ai/guides/component-picker.md +3 -3
  40. package/dist/ai/guides/fetch-items-playbook.md +1 -1
  41. package/dist/ai/guides/icons-policy.md +10 -10
  42. package/dist/ai/guides/inertia-helpers.md +120 -67
  43. package/dist/ai/guides/installation-and-plugin-setup.md +43 -15
  44. package/dist/ai/guides/migration/components/bb-breadcrumbs.md +3 -2
  45. package/dist/ai/guides/migration/components/bb-checkbox-group.md +16 -16
  46. package/dist/ai/guides/migration/components/bb-dropdown-button.md +9 -8
  47. package/dist/ai/guides/migration/components/bb-dropdown.md +15 -14
  48. package/dist/ai/guides/migration/components/bb-radio-group.md +2 -1
  49. package/dist/ai/guides/migration/components/bb-select-popover.md +6 -1
  50. package/dist/ai/guides/migration/components/bb-select.md +10 -2
  51. package/dist/ai/guides/migration/components/bb-switch-group.md +2 -1
  52. package/dist/ai/guides/migration/components/bb-tabs.md +9 -8
  53. package/dist/ai/guides/migration/components/bb-tooltip.md +1 -1
  54. package/dist/ai/guides/migration/v2-to-v3.md +114 -17
  55. package/dist/ai/guides/migration/v3-beta-tooling.md +11 -0
  56. package/dist/ai/guides/options-items-playbook.md +30 -24
  57. package/dist/ai/guides/passthrough.md +2 -2
  58. package/dist/ai/guides/preset-components.md +18 -3
  59. package/dist/ai/guides/typescript-generic-components.md +1 -1
  60. package/dist/ai/guides/validated-forms.md +11 -22
  61. package/dist/ai/guides/validation-providers.md +4 -3
  62. package/dist/ai/helpers/inertia/manifest.json +636 -0
  63. package/dist/ai/helpers/inertia/types.ts +15 -0
  64. package/dist/ai/helpers/inertia/useAsyncFn.ts +110 -0
  65. package/dist/ai/helpers/inertia/useConfirmOnRouteLeave.ts +102 -0
  66. package/dist/ai/helpers/inertia/useDeleteCmd.ts +30 -0
  67. package/dist/ai/helpers/inertia/useHasChanged.ts +36 -0
  68. package/dist/ai/helpers/inertia/useIndexById.ts +23 -0
  69. package/dist/ai/helpers/inertia/useLazyProp.ts +148 -0
  70. package/dist/ai/helpers/inertia/useLazyPropGetter.ts +50 -0
  71. package/dist/ai/helpers/inertia/useLoading.ts +20 -0
  72. package/dist/ai/helpers/inertia/usePaginatedResponseContext.ts +88 -0
  73. package/dist/ai/helpers/inertia/usePromiseForm.ts +62 -0
  74. package/dist/ai/helpers/inertia/useProp.ts +63 -0
  75. package/dist/ai/helpers/inertia/useQuery.ts +19 -0
  76. package/dist/ai/helpers/inertia/useQueryObject.ts +116 -0
  77. package/dist/ai/helpers/inertia/useResourceContext.ts +69 -0
  78. package/dist/ai/helpers/inertia/useRouteQuery.ts +54 -0
  79. package/dist/ai/helpers/inertia/useScreenSizes.ts +23 -0
  80. package/dist/ai/helpers/inertia/useTableContext.ts +34 -0
  81. package/dist/ai/helpers/inertia/useTableSelectionContext.ts +48 -0
  82. package/dist/ai/helpers/inertia/useUntil.ts +33 -0
  83. package/dist/ai/helpers/inertia/utils.ts +24 -0
  84. package/dist/ai/index.md +6 -6
  85. package/dist/ai/manifest/components/BbBreadcrumbs.json +10 -6
  86. package/dist/ai/manifest/components/BbBreadcrumbs.tree.json +1 -1
  87. package/dist/ai/manifest/components/BbButton.json +1 -1
  88. package/dist/ai/manifest/components/BbCheckboxGroup.json +2 -2
  89. package/dist/ai/manifest/components/BbDropdown.json +23 -20
  90. package/dist/ai/manifest/components/BbDropdown.tree.json +2 -2
  91. package/dist/ai/manifest/components/BbDropdownButton.json +22 -19
  92. package/dist/ai/manifest/components/BbDropdownButton.tree.json +1 -1
  93. package/dist/ai/manifest/components/BbDropdownGroup.json +2 -2
  94. package/dist/ai/manifest/components/BbForm.json +1 -0
  95. package/dist/ai/manifest/components/BbNumberInput.json +6 -1
  96. package/dist/ai/manifest/components/BbNumberInput.tree.json +2 -1
  97. package/dist/ai/manifest/components/BbPopover.json +1 -0
  98. package/dist/ai/manifest/components/BbProgress.json +6 -6
  99. package/dist/ai/manifest/components/BbProgress.tree.json +1 -0
  100. package/dist/ai/manifest/components/BbRadioGroup.json +2 -2
  101. package/dist/ai/manifest/components/BbSelect.json +2 -2
  102. package/dist/ai/manifest/components/BbSelectPopover.json +2 -2
  103. package/dist/ai/manifest/components/BbSwitchGroup.json +2 -2
  104. package/dist/ai/manifest/components/BbTabs.json +7 -6
  105. package/dist/ai/manifest/components/BbTabsList.json +3 -3
  106. package/dist/ai/manifest/components/BbTabsPanels.json +1 -1
  107. package/dist/ai/manifest/components/BbTabsRoot.json +5 -4
  108. package/dist/ai/manifest/components/BbTooltip.json +1 -1
  109. package/dist/ai/manifest/index.json +2 -2
  110. package/dist/ai/manifest/meta.json +2 -0
  111. package/dist/ai/manifest/types.advanced.json +10 -9
  112. package/dist/ai/manifest/types.api.json +5 -4
  113. package/dist/ai/recipes/inertia/approvals-inbox.md +16 -18
  114. package/dist/ai/recipes/inertia/inline-edit-workspace.md +23 -33
  115. package/dist/ai/recipes/inertia/layout-scaffold.md +16 -16
  116. package/dist/ai/recipes/inertia/onboarding.md +6 -10
  117. package/dist/ai/recipes/inertia/ownership-atlas.md +15 -18
  118. package/dist/ai/recipes/inertia/record-form.md +11 -11
  119. package/dist/ai/recipes/inertia/records-workspace.md +38 -41
  120. package/dist/ai/recipes/inertia/settings-workspace.md +26 -38
  121. package/dist/ai/recipes/inertia/upload-center.md +31 -39
  122. package/dist/ai/recipes/inertia/wizard-form.md +10 -19
  123. package/dist/ai/recipes/nuxt/approvals-inbox.md +7 -8
  124. package/dist/ai/recipes/nuxt/inline-edit-workspace.md +14 -24
  125. package/dist/ai/recipes/nuxt/layout-scaffold.md +16 -16
  126. package/dist/ai/recipes/nuxt/onboarding.md +2 -6
  127. package/dist/ai/recipes/nuxt/ownership-atlas.md +7 -9
  128. package/dist/ai/recipes/nuxt/record-form.md +5 -5
  129. package/dist/ai/recipes/nuxt/records-workspace.md +29 -31
  130. package/dist/ai/recipes/nuxt/settings-workspace.md +22 -34
  131. package/dist/ai/recipes/nuxt/upload-center.md +22 -29
  132. package/dist/ai/recipes/nuxt/wizard-form.md +3 -11
  133. package/dist/ai/recipes/vue/approvals-inbox.md +7 -8
  134. package/dist/ai/recipes/vue/inline-edit-workspace.md +14 -24
  135. package/dist/ai/recipes/vue/layout-scaffold.md +16 -16
  136. package/dist/ai/recipes/vue/onboarding.md +2 -6
  137. package/dist/ai/recipes/vue/ownership-atlas.md +7 -9
  138. package/dist/ai/recipes/vue/record-form.md +5 -5
  139. package/dist/ai/recipes/vue/records-workspace.md +29 -31
  140. package/dist/ai/recipes/vue/settings-workspace.md +22 -34
  141. package/dist/ai/recipes/vue/upload-center.md +22 -29
  142. package/dist/ai/recipes/vue/wizard-form.md +3 -11
  143. package/dist/ai/source/BbAccordion.md +1 -1
  144. package/dist/ai/source/BbAlert.md +3 -3
  145. package/dist/ai/source/BbBadge.md +2 -2
  146. package/dist/ai/source/BbBadgeButton.md +5 -4
  147. package/dist/ai/source/BbBaseButton.md +1 -1
  148. package/dist/ai/source/BbBaseRadioIcon.md +1 -0
  149. package/dist/ai/source/BbBaseSwitchIcon.md +1 -0
  150. package/dist/ai/source/BbBreadcrumbs.md +63 -42
  151. package/dist/ai/source/BbButton.md +71 -33
  152. package/dist/ai/source/BbCalendar.md +17 -16
  153. package/dist/ai/source/BbCheckbox.md +3 -2
  154. package/dist/ai/source/BbCheckboxGroup.md +11 -4
  155. package/dist/ai/source/BbColorInput.md +10 -9
  156. package/dist/ai/source/BbConfirm.md +1 -1
  157. package/dist/ai/source/BbConfirmPortal.md +1 -1
  158. package/dist/ai/source/BbDatePicker.md +2 -2
  159. package/dist/ai/source/BbDatePickerInput.md +47 -20
  160. package/dist/ai/source/BbDialog.md +5 -5
  161. package/dist/ai/source/BbDropdown.md +230 -158
  162. package/dist/ai/source/BbDropdownButton.md +19 -12
  163. package/dist/ai/source/BbDropdownGroup.md +126 -93
  164. package/dist/ai/source/BbForm.md +25 -1
  165. package/dist/ai/source/BbIndicator.md +4 -0
  166. package/dist/ai/source/BbNumberInput.md +855 -19
  167. package/dist/ai/source/BbOffCanvas.md +27 -11
  168. package/dist/ai/source/BbPagination.md +3 -3
  169. package/dist/ai/source/BbPopover.md +152 -8
  170. package/dist/ai/source/BbPopoverPortal.md +11 -0
  171. package/dist/ai/source/BbProgress.md +82 -18
  172. package/dist/ai/source/BbRadioGroup.md +12 -5
  173. package/dist/ai/source/BbRating.md +11 -9
  174. package/dist/ai/source/BbSelect.md +16 -10
  175. package/dist/ai/source/BbSelectPopover.md +29 -23
  176. package/dist/ai/source/BbSlider.md +12 -6
  177. package/dist/ai/source/BbSwitch.md +3 -2
  178. package/dist/ai/source/BbSwitchGroup.md +5 -2
  179. package/dist/ai/source/BbTable.md +102 -18
  180. package/dist/ai/source/BbTabs.md +27 -23
  181. package/dist/ai/source/BbTabsList.md +26 -22
  182. package/dist/ai/source/BbTabsPanels.md +26 -22
  183. package/dist/ai/source/BbTabsRoot.md +25 -21
  184. package/dist/ai/source/BbTag.md +10 -9
  185. package/dist/ai/source/BbTextInput.md +5 -4
  186. package/dist/ai/source/BbTextarea.md +6 -5
  187. package/dist/ai/source/BbTimePicker.md +1 -1
  188. package/dist/ai/source/BbTimePickerInput.md +46 -9
  189. package/dist/ai/source/BbToast.md +10 -7
  190. package/dist/ai/source/BbToastPortal.md +10 -7
  191. package/dist/ai/source/BbTooltip.md +54 -8
  192. package/dist/ai/source/BbTree.md +21 -3
  193. package/dist/ai/source/CommonField.md +6 -4
  194. package/dist/ai/source/CommonFieldInput.md +15 -10
  195. package/dist/ai/source/CommonPopover.md +6 -0
  196. package/dist/ai/source/CommonTimeSelector.md +5 -4
  197. package/dist/components/BbBadge/BbBadgeButton.vue_vue_type_script_setup_true_lang.js +10 -8
  198. package/dist/components/BbBaseButton/BbBaseButton.vue_vue_type_script_setup_true_lang.js +8 -8
  199. package/dist/components/BbBreadcrumbs/BbBreadcrumbs.vue_vue_type_script_setup_true_lang.js +45 -45
  200. package/dist/components/BbBreadcrumbs/types.d.ts +25 -17
  201. package/dist/components/BbButton/BbButton.vue_vue_type_script_setup_true_lang.js +84 -71
  202. package/dist/components/BbButton/toggleRoving.d.ts +6 -0
  203. package/dist/components/BbButton/toggleRoving.js +45 -0
  204. package/dist/components/BbButton/types.d.ts +4 -1
  205. package/dist/components/BbCalendar/BbCalendar.vue_vue_type_script_setup_true_lang.js +178 -178
  206. package/dist/components/BbCalendar/CalendarMonthPanel.vue_vue_type_script_setup_true_lang.js +15 -15
  207. package/dist/components/BbCalendar/CalendarYearPanel.vue_vue_type_script_setup_true_lang.js +28 -28
  208. package/dist/components/BbCalendar/useCalendarGrid.js +141 -139
  209. package/dist/components/BbCalendar/useDayjsLocale.d.ts +38 -15
  210. package/dist/components/BbCalendar/useDayjsLocale.js +31 -30
  211. package/dist/components/BbCheckbox/BbCheckbox.vue_vue_type_script_setup_true_lang.js +48 -48
  212. package/dist/components/BbCheckboxGroup/BbCheckboxGroup.vue_vue_type_script_setup_true_lang.js +81 -80
  213. package/dist/components/BbCheckboxGroup/types.d.ts +6 -2
  214. package/dist/components/BbColorInput/BbColorInput.vue_vue_type_script_setup_true_lang.js +69 -69
  215. package/dist/components/BbDatePickerInput/BbDatePickerInput.vue_vue_type_script_setup_true_lang.js +363 -349
  216. package/dist/components/BbDropdown/AdaptiveDropdown.vue_vue_type_script_setup_true_lang.js +91 -86
  217. package/dist/components/BbDropdown/BbDropdown.vue_vue_type_script_setup_true_lang.js +298 -289
  218. package/dist/components/BbDropdown/BbDropdownList.vue_vue_type_script_setup_true_lang.js +322 -356
  219. package/dist/components/BbDropdown/DropdownPipelineResolver.vue_vue_type_script_setup_true_lang.js +17 -15
  220. package/dist/components/BbDropdown/DropdownRow.vue.d.ts +40 -0
  221. package/dist/components/BbDropdown/DropdownRow.vue.js +5 -0
  222. package/dist/components/BbDropdown/DropdownRow.vue_vue_type_script_setup_true_lang.js +60 -0
  223. package/dist/components/BbDropdown/normalizeGroups.d.ts +8 -2
  224. package/dist/components/BbDropdown/normalizeGroups.js +60 -46
  225. package/dist/components/BbDropdown/types.d.ts +119 -88
  226. package/dist/components/BbDropdownButton/BbDropdownButton.vue_vue_type_script_setup_true_lang.js +11 -2
  227. package/dist/components/BbDropdownButton/types.d.ts +2 -2
  228. package/dist/components/BbForm/BbForm.vue.d.ts +9 -0
  229. package/dist/components/BbForm/BbForm.vue_vue_type_script_setup_true_lang.js +114 -105
  230. package/dist/components/BbForm/BbFormCustomField.js +1 -1
  231. package/dist/components/BbForm/bindFormField.js +2 -1
  232. package/dist/components/BbForm/formFields.d.ts +5 -0
  233. package/dist/components/BbNumberInput/BbNumberInput.vue.d.ts +0 -1
  234. package/dist/components/BbNumberInput/BbNumberInput.vue_vue_type_script_setup_true_lang.js +452 -125
  235. package/dist/components/BbNumberInput/numberDisplay.d.ts +256 -0
  236. package/dist/components/BbNumberInput/numberDisplay.js +499 -0
  237. package/dist/components/BbNumberInput/types.d.ts +54 -1
  238. package/dist/components/BbNumberInput/warroom/corpus.d.ts +58 -0
  239. package/dist/components/BbNumberInput/warroom/currency-verify.rows.d.ts +28 -0
  240. package/dist/components/BbNumberInput/warroom/currency.rows.d.ts +2 -0
  241. package/dist/components/BbNumberInput/warroom/harness.d.ts +217 -0
  242. package/dist/components/BbNumberInput/warroom/oracle.d.ts +150 -0
  243. package/dist/components/BbPagination/BbPagination.vue_vue_type_script_setup_true_lang.js +7 -7
  244. package/dist/components/BbPopover/BbPopover.vue.d.ts +1 -0
  245. package/dist/components/BbPopover/BbPopover.vue_vue_type_script_setup_true_lang.js +207 -164
  246. package/dist/components/BbPopover/types.d.ts +11 -0
  247. package/dist/components/BbProgress/BbProgress.vue_vue_type_script_setup_true_lang.js +15 -7
  248. package/dist/components/BbProgress/types.d.ts +23 -7
  249. package/dist/components/BbProgress/types.js +1 -1
  250. package/dist/components/BbRadioGroup/BbRadioGroup.vue_vue_type_script_setup_true_lang.js +102 -101
  251. package/dist/components/BbRadioGroup/types.d.ts +6 -2
  252. package/dist/components/BbRating/BbRating.vue_vue_type_script_setup_true_lang.js +63 -63
  253. package/dist/components/BbSelect/BbSelect.vue_vue_type_script_setup_true_lang.js +156 -154
  254. package/dist/components/BbSelect/types.d.ts +6 -2
  255. package/dist/components/BbSelectPopover/BbSelectPopover.vue_vue_type_script_setup_true_lang.js +2 -1
  256. package/dist/components/BbSelectPopover/types.d.ts +6 -2
  257. package/dist/components/BbSlider/BbSlider.vue_vue_type_script_setup_true_lang.js +153 -153
  258. package/dist/components/BbSwitch/BbSwitch.vue_vue_type_script_setup_true_lang.js +62 -62
  259. package/dist/components/BbSwitchGroup/BbSwitchGroup.vue_vue_type_script_setup_true_lang.js +131 -130
  260. package/dist/components/BbTable/BbTable.vue_vue_type_script_setup_true_lang.js +711 -676
  261. package/dist/components/BbTabs/BbTabs.vue_vue_type_script_setup_true_lang.js +1 -1
  262. package/dist/components/BbTabs/BbTabsList.vue_vue_type_script_setup_true_lang.js +1 -1
  263. package/dist/components/BbTabs/types.d.ts +25 -21
  264. package/dist/components/BbTag/BbTag.vue_vue_type_script_setup_true_lang.js +76 -76
  265. package/dist/components/BbTextInput/BbTextInput.vue_vue_type_script_setup_true_lang.js +58 -58
  266. package/dist/components/BbTextarea/BbTextarea.vue_vue_type_script_setup_true_lang.js +82 -82
  267. package/dist/components/BbTimePickerInput/BbTimePickerInput.vue_vue_type_script_setup_true_lang.js +246 -236
  268. package/dist/components/BbTooltip/BbTooltip.vue_vue_type_script_setup_true_lang.js +102 -97
  269. package/dist/components/BbTooltip/tooltipWarmup.d.ts +23 -0
  270. package/dist/components/BbTooltip/tooltipWarmup.js +7 -0
  271. package/dist/components/BbTooltip/types.d.ts +7 -2
  272. package/dist/components/BbTree/BbTree.vue_vue_type_script_setup_true_lang.js +29 -21
  273. package/dist/components/BbTree/BbTreeLevel.vue_vue_type_script_setup_true_lang.js +10 -7
  274. package/dist/components/CommonFieldInput/CommonFieldInput.vue_vue_type_script_setup_true_lang.js +37 -36
  275. package/dist/components/CommonTimeSelector/CommonTimeSelector.vue_vue_type_script_setup_true_lang.js +9 -9
  276. package/dist/composables/formSubmitLock.d.ts +6 -0
  277. package/dist/composables/formSubmitLock.js +4 -0
  278. package/dist/composables/useBaseOptions.d.ts +7 -1
  279. package/dist/composables/useBaseOptions.js +32 -32
  280. package/dist/composables/useBbDropdownContext.d.ts +8 -8
  281. package/dist/composables/useBbFormField.d.ts +9 -0
  282. package/dist/composables/useBbFormField.js +5 -3
  283. package/dist/composables/useBbTabsContext.d.ts +10 -10
  284. package/dist/composables/useBbTabsContext.js +18 -18
  285. package/dist/composables/useFormBinding.d.ts +11 -0
  286. package/dist/composables/useFormBinding.js +15 -13
  287. package/dist/composables/useItemText.d.ts +5 -1
  288. package/dist/composables/useItemText.js +8 -3
  289. package/dist/composables/useItemValue.d.ts +5 -1
  290. package/dist/composables/useItemValue.js +8 -3
  291. package/dist/composables/useLocale.js +10 -9
  292. package/dist/deprecation/ai-deprecations.json.d.ts +130 -0
  293. package/dist/deprecation/ai-deprecations.json.js +1 -1
  294. package/dist/deprecation/removedProps.js +1 -1
  295. package/dist/directives/bbDropdown.js +1 -1
  296. package/dist/i18n/index.d.ts +7 -12
  297. package/dist/i18n/locales/cs.json +11 -14
  298. package/dist/i18n/locales/da.json +13 -16
  299. package/dist/i18n/locales/de-CH.json +137 -0
  300. package/dist/i18n/locales/de.json +14 -17
  301. package/dist/i18n/locales/el.json +10 -13
  302. package/dist/i18n/locales/en-GB.json +137 -0
  303. package/dist/i18n/locales/en.json +8 -11
  304. package/dist/i18n/locales/es-MX.json +137 -0
  305. package/dist/i18n/locales/es.json +16 -19
  306. package/dist/i18n/locales/fi.json +8 -11
  307. package/dist/i18n/locales/fr.json +23 -26
  308. package/dist/i18n/locales/hu.json +8 -11
  309. package/dist/i18n/locales/it.json +12 -15
  310. package/dist/i18n/locales/ja.json +13 -16
  311. package/dist/i18n/locales/ko.json +10 -13
  312. package/dist/i18n/locales/nb.json +13 -16
  313. package/dist/i18n/locales/nl.json +21 -24
  314. package/dist/i18n/locales/pl.json +10 -13
  315. package/dist/i18n/locales/{pt.json → pt-BR.json} +19 -22
  316. package/dist/i18n/locales/pt-PT.json +137 -0
  317. package/dist/i18n/locales/ro.json +22 -25
  318. package/dist/i18n/locales/ru.json +11 -14
  319. package/dist/i18n/locales/sv.json +16 -19
  320. package/dist/i18n/locales/tr.json +14 -17
  321. package/dist/i18n/locales/uk.json +11 -14
  322. package/dist/i18n/locales/{zh-cn.json → zh-CN.json} +10 -13
  323. package/dist/i18n/locales/zh-HK.json +137 -0
  324. package/dist/i18n/locales/zh-TW.json +137 -0
  325. package/dist/i18n/regionHints.d.ts +16 -0
  326. package/dist/i18n/regionHints.js +49 -0
  327. package/dist/i18n/regions.d.ts +286 -0
  328. package/dist/i18n/regions.js +265 -0
  329. package/dist/index.d.ts +1 -1
  330. package/dist/index.js +4 -4
  331. package/dist/llms-full.txt +1785 -1637
  332. package/dist/llms-medium.txt +74 -46
  333. package/dist/llms.txt +7 -7
  334. package/dist/locale-blueprint.json +16 -13
  335. package/dist/styles.css +2 -2
  336. package/dist/types/ptComponentMap.d.ts +1 -1
  337. package/dist/utilities/functions/slotKey.d.ts +2 -2
  338. package/dist/utils/versionCheck.js +1 -1
  339. package/dist/vite-plugin.d.ts +22 -19
  340. package/dist/vite.js +309 -297
  341. package/llms.txt +7 -7
  342. package/package.json +5 -3
  343. package/scripts/lib/agent-manifest.mjs +2 -1
  344. package/scripts/lib/eslint-disable.mjs +12 -4
  345. package/scripts/lib/eslint-plugin.d.ts +5 -1
  346. package/scripts/lib/eslint-plugin.mjs +187 -6
  347. package/scripts/lib/helper-canon.mjs +176 -0
  348. package/scripts/lib/helpers-add.mjs +1303 -0
  349. package/scripts/lib/local-components.mjs +33 -4
  350. package/scripts/lib/locale-check.mjs +165 -0
  351. package/scripts/lib/redundant-item-accessor.mjs +398 -0
  352. package/scripts/lib/renamed-item-field.mjs +343 -0
  353. package/scripts/lib/slot-key.mjs +2 -2
  354. package/scripts/lib/slot-renames.mjs +1 -1
  355. package/scripts/lib/validate-bb-markup.mjs +123 -56
  356. 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,398 @@
1
+ /**
2
+ * `redundant-item-accessor` (Q60.24, owner 2026-10-07): with no `item-text`
3
+ * an option reads its own `label`, else its own `text`; with no `item-value`
4
+ * its own `value`. An accessor that names exactly that field restates the
5
+ * default and is cruft: `item-text="label"`, `item-text="text"`,
6
+ * `item-value="value"` (static or `:item-text="'label'"`, kebab or camel) on
7
+ * the option surfaces, and `itemText: 'label'` / `'text'` /
8
+ * `itemValue: 'value'` in a BbDropdown group.
9
+ *
10
+ * Reported only when removing it cannot change what renders:
11
+ * - `item-text="label"` / `item-value="value"`: when the items are not
12
+ * visible in the file (the field is assumed present), or when every visible
13
+ * item is an object literal spelling that field. A primitive item or an
14
+ * object without the field reads differently through the default.
15
+ * - `item-text="text"`: only when every visible item spells `text` and none
16
+ * spells `label` or spreads (`label` would win over `text`); never on items
17
+ * the file cannot see.
18
+ * - A dropdown group stays a mapped group after the removal: its `items` is a
19
+ * function, or it keeps `itemProps`, `max` or another mapper. A group whose
20
+ * only mapper is the redundant one would turn into a hand-written group.
21
+ *
22
+ * The ESLint twin is `bitboss-ui/no-redundant-item-accessor`
23
+ * (eslint-plugin.mjs): both read the findings below; each carries the range
24
+ * the autofix deletes.
25
+ */
26
+ import { babelParse, parse } from 'vue/compiler-sfc';
27
+ import { libraryAliases, localBbImports } from './local-components.mjs';
28
+
29
+ const NODE_ELEMENT = 1;
30
+ const PROP_ATTRIBUTE = 6;
31
+ const PROP_DIRECTIVE = 7;
32
+
33
+ /** The components whose options read the default chain. */
34
+ export const ACCESSOR_SURFACES = new Set([
35
+ 'BbSelect',
36
+ 'BbSelectPopover',
37
+ 'BbRadioGroup',
38
+ 'BbCheckboxGroup',
39
+ 'BbSwitchGroup',
40
+ ]);
41
+
42
+ /** Hosts whose `items` hold BbDropdown groups. */
43
+ const DROPDOWN_HOSTS = new Set(['BbDropdown', 'BbDropdownButton']);
44
+
45
+ /** accessor → the field values that restate its default. */
46
+ const DEFAULT_FIELDS = { itemText: ['label', 'text'], itemValue: ['value'] };
47
+
48
+ export const REDUNDANT_ACCESSOR_MESSAGES = {
49
+ attr: (attr, field) =>
50
+ `\`${attr}\` restates the default: ${
51
+ field === 'value'
52
+ ? "an option's value is its own `value`"
53
+ : 'an option reads its own `label`, else its own `text`'
54
+ }. Remove it.`,
55
+ group: (prop, field) =>
56
+ `\`${prop}: '${field}'\` restates the group's default: ${
57
+ field === 'value'
58
+ ? "a row's value is its own `value`"
59
+ : 'a row reads its own `label`, else its own `text`'
60
+ }. Remove it.`,
61
+ };
62
+
63
+ export const REDUNDANT_ACCESSOR_HINT =
64
+ '`{ label, value }` and `{ text, value }` items work as they are; an accessor is only for another shape';
65
+
66
+ const pascal = (tag) =>
67
+ tag.includes('-')
68
+ ? tag.replace(/(?:^|-)([a-z0-9])/g, (_, c) => c.toUpperCase())
69
+ : tag;
70
+
71
+ const camel = (name) => name.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
72
+
73
+ const unwrap = (node) => {
74
+ let current = node;
75
+ while (
76
+ current &&
77
+ [
78
+ 'TSAsExpression',
79
+ 'TSSatisfiesExpression',
80
+ 'TSNonNullExpression',
81
+ 'TSTypeAssertion',
82
+ 'ParenthesizedExpression',
83
+ ].includes(current.type)
84
+ )
85
+ current = current.expression;
86
+ return current;
87
+ };
88
+
89
+ const keyName = (property) =>
90
+ (property.type === 'ObjectProperty' || property.type === 'ObjectMethod') &&
91
+ !property.computed
92
+ ? property.key.type === 'Identifier'
93
+ ? property.key.name
94
+ : property.key.type === 'StringLiteral'
95
+ ? property.key.value
96
+ : undefined
97
+ : undefined;
98
+
99
+ const propertyOf = (object, key) =>
100
+ object.properties.find((p) => keyName(p) === key);
101
+
102
+ /** A string literal (`'label'`, `"label"`, `` `label` ``) → its value. */
103
+ const stringValue = (node) => {
104
+ const value = unwrap(node);
105
+ if (value?.type === 'StringLiteral') return value.value;
106
+ if (
107
+ value?.type === 'TemplateLiteral' &&
108
+ value.expressions.length === 0 &&
109
+ value.quasis.length === 1
110
+ )
111
+ return value.quasis[0].value.cooked;
112
+ return undefined;
113
+ };
114
+
115
+ /**
116
+ * The visible items of an array: `null` when the array cannot be judged
117
+ * (not in the file, or mutated), else one entry per element —
118
+ * `{ keys, spread }` for an object literal, `null` for anything else.
119
+ */
120
+ const shapeOf = (arrayNode) =>
121
+ arrayNode
122
+ ? arrayNode.elements.map((raw) => {
123
+ const element = unwrap(raw);
124
+ if (element?.type !== 'ObjectExpression') return null;
125
+ return {
126
+ keys: new Set(element.properties.map(keyName).filter(Boolean)),
127
+ spread: element.properties.some((p) => p.type === 'SpreadElement'),
128
+ };
129
+ })
130
+ : null;
131
+
132
+ /**
133
+ * Whether `accessor = field` restates the default for items of this shape.
134
+ * @param {'itemText' | 'itemValue'} accessor
135
+ * @param {string} field
136
+ * @param {null | Array<null | { keys: Set<string>, spread: boolean }>} shape
137
+ */
138
+ export function restatesDefault(accessor, field, shape) {
139
+ if (!DEFAULT_FIELDS[accessor]?.includes(field)) return false;
140
+ if (accessor === 'itemText' && field === 'text') {
141
+ if (!shape) return false;
142
+ return shape.every(
143
+ (item) =>
144
+ item && !item.spread && !item.keys.has('label') && item.keys.has('text')
145
+ );
146
+ }
147
+ if (!shape) return true;
148
+ return shape.every((item) => item && item.keys.has(field));
149
+ }
150
+
151
+ /** Resolves an `items` expression to an ArrayExpression, or null. */
152
+ const arrayOf = (node, arrays) => {
153
+ const value = unwrap(node);
154
+ if (value?.type === 'ArrayExpression') return value;
155
+ if (value?.type === 'Identifier' && !arrays?.mutated?.has(value.name))
156
+ return arrays?.get(value.name) ?? null;
157
+ return null;
158
+ };
159
+
160
+ /** The range to delete with an attribute: it and the whitespace before it. */
161
+ const attrRemoval = (code, start, end) => {
162
+ let from = start;
163
+ while (from > 0 && /\s/.test(code[from - 1])) from--;
164
+ return [from, end];
165
+ };
166
+
167
+ /** The range to delete with an object property: it and one comma. */
168
+ const propertyRemoval = (code, start, end) => {
169
+ let to = end;
170
+ while (to < code.length && /[ \t]/.test(code[to])) to++;
171
+ if (code[to] === ',') {
172
+ to++;
173
+ // Alone on its line: take the whole line.
174
+ let lineStart = start;
175
+ while (lineStart > 0 && /[ \t]/.test(code[lineStart - 1])) lineStart--;
176
+ let after = to;
177
+ while (after < code.length && /[ \t]/.test(code[after])) after++;
178
+ if (
179
+ (lineStart === 0 || code[lineStart - 1] === '\n') &&
180
+ code[after] === '\n'
181
+ )
182
+ return [lineStart, after + 1];
183
+ while (to < code.length && /[ \t]/.test(code[to])) to++;
184
+ return [start, to];
185
+ }
186
+ // The last property: take the comma before it.
187
+ let from = start;
188
+ while (from > 0 && /\s/.test(code[from - 1])) from--;
189
+ if (code[from - 1] === ',') from--;
190
+ return [from, end];
191
+ };
192
+
193
+ const parseExpression = (content) => {
194
+ try {
195
+ const program = babelParse(`(${content})`, {
196
+ sourceType: 'module',
197
+ plugins: ['typescript'],
198
+ }).program;
199
+ return program.body[0]?.expression ?? null;
200
+ } catch {
201
+ return null;
202
+ }
203
+ };
204
+
205
+ /**
206
+ * @param {string} code a whole SFC (ranges are offsets into it) or a snippet
207
+ * @param {{ templateAst?: any, arrays?: Map<string, any> & { mutated?: Set<string> }, library?: Parameters<typeof localBbImports>[1] }} [options]
208
+ * `arrays`: the file's script-setup array consts (`scriptSetupArrayConsts`)
209
+ * `library`: what counts as the library besides `bitboss-ui` (`isLibrarySource`)
210
+ * @returns {Array<{ component: string, attr: string, kind: 'redundant-item-accessor', line: number, message: string, hint: string, severity: 'warning', range?: [number, number], edit?: { range: [number, number], text: string } }>}
211
+ * `edit` is the autofix as a text replacement (a deletion is `text: ''`), so a
212
+ * later finding can rewrite instead of delete without touching the rule
213
+ */
214
+ export function redundantItemAccessorFindings(code, options = {}) {
215
+ if (!/item-?(text|value)|itemText|itemValue/i.test(code)) return [];
216
+ let descriptor = null;
217
+ try {
218
+ ({ descriptor } = parse(code, { sourceMap: false }));
219
+ } catch {
220
+ descriptor = null;
221
+ }
222
+ const ast = options.templateAst ?? descriptor?.template?.ast;
223
+ if (!ast) return [];
224
+ // Offsets are only trusted on the file's own template AST.
225
+ const ranged =
226
+ !options.templateAst || options.templateAst === descriptor?.template?.ast;
227
+ const arrays = options.arrays;
228
+ const aliases = libraryAliases(code);
229
+ const own = localBbImports(code, options.library);
230
+ const scriptOffset = descriptor?.scriptSetup?.loc.start.offset ?? 0;
231
+ const scriptLine = descriptor?.scriptSetup?.loc.start.line ?? 1;
232
+ const findings = [];
233
+
234
+ const itemsShape = (element) => {
235
+ for (const prop of element.props ?? []) {
236
+ if (prop.type !== PROP_DIRECTIVE || prop.name !== 'bind') continue;
237
+ if (!prop.arg?.isStatic || prop.arg.content !== 'items') continue;
238
+ const expression = parseExpression(prop.exp?.content ?? '');
239
+ return shapeOf(arrayOf(expression, arrays));
240
+ }
241
+ return null;
242
+ };
243
+
244
+ const checkSurface = (element, component) => {
245
+ let shape;
246
+ for (const prop of element.props ?? []) {
247
+ let name;
248
+ let field;
249
+ if (prop.type === PROP_ATTRIBUTE) {
250
+ name = camel(prop.name);
251
+ field = prop.value?.content;
252
+ } else if (
253
+ prop.type === PROP_DIRECTIVE &&
254
+ prop.name === 'bind' &&
255
+ prop.arg?.isStatic
256
+ ) {
257
+ name = camel(prop.arg.content);
258
+ if (!(name in DEFAULT_FIELDS)) continue;
259
+ field = stringValue(parseExpression(prop.exp?.content ?? ''));
260
+ }
261
+ if (!(name in DEFAULT_FIELDS) || field === undefined) continue;
262
+ shape ??= itemsShape(element);
263
+ if (!restatesDefault(name, field, shape)) continue;
264
+ const attr = code.slice(prop.loc.start.offset, prop.loc.end.offset);
265
+ findings.push({
266
+ component,
267
+ attr: prop.type === PROP_ATTRIBUTE ? prop.name : `:${prop.arg.content}`,
268
+ kind: 'redundant-item-accessor',
269
+ line: prop.loc.start.line,
270
+ message: REDUNDANT_ACCESSOR_MESSAGES.attr(
271
+ ranged ? attr : `${prop.name}="${field}"`,
272
+ field
273
+ ),
274
+ hint: REDUNDANT_ACCESSOR_HINT,
275
+ severity: 'warning',
276
+ ...(ranged
277
+ ? {
278
+ range: [prop.loc.start.offset, prop.loc.end.offset],
279
+ edit: {
280
+ range: attrRemoval(
281
+ code,
282
+ prop.loc.start.offset,
283
+ prop.loc.end.offset
284
+ ),
285
+ text: '',
286
+ },
287
+ }
288
+ : {}),
289
+ });
290
+ }
291
+ };
292
+
293
+ /** Groups inside a host's `items` array (script-side or inline). */
294
+ const checkGroups = (array, component, base) => {
295
+ for (const raw of array.elements) {
296
+ const entry = unwrap(raw);
297
+ if (entry?.type !== 'ObjectExpression') continue;
298
+ const itemsProp = propertyOf(entry, 'items');
299
+ const itemsValue =
300
+ itemsProp?.type === 'ObjectMethod'
301
+ ? itemsProp
302
+ : unwrap(itemsProp?.value);
303
+ const isFunction =
304
+ itemsValue?.type === 'ObjectMethod' ||
305
+ itemsValue?.type === 'ArrowFunctionExpression' ||
306
+ itemsValue?.type === 'FunctionExpression';
307
+ const childArray = isFunction ? null : arrayOf(itemsValue, arrays);
308
+ const shape = isFunction ? null : shapeOf(childArray);
309
+ const mappers = ['itemText', 'itemValue']
310
+ .map((name) => ({ name, property: propertyOf(entry, name) }))
311
+ .filter((m) => m.property);
312
+ const redundant = mappers.filter(({ name, property }) => {
313
+ if (property.type !== 'ObjectProperty') return false;
314
+ const field = stringValue(property.value);
315
+ return field !== undefined && restatesDefault(name, field, shape);
316
+ });
317
+ const staysMapped =
318
+ isFunction ||
319
+ !!propertyOf(entry, 'itemProps') ||
320
+ !!propertyOf(entry, 'max') ||
321
+ mappers.length > redundant.length;
322
+ // An `items` identifier the file cannot see may be a function, or not.
323
+ if (redundant.length && staysMapped) {
324
+ for (const { name, property } of redundant) {
325
+ const field = stringValue(property.value);
326
+ findings.push({
327
+ component,
328
+ attr: name,
329
+ kind: 'redundant-item-accessor',
330
+ line: base.line + property.loc.start.line - 1,
331
+ message: REDUNDANT_ACCESSOR_MESSAGES.group(name, field),
332
+ hint: REDUNDANT_ACCESSOR_HINT,
333
+ severity: 'warning',
334
+ ...(base.offset !== null
335
+ ? {
336
+ range: [
337
+ base.offset + property.start,
338
+ base.offset + property.end,
339
+ ],
340
+ edit: {
341
+ range: propertyRemoval(
342
+ code,
343
+ base.offset + property.start,
344
+ base.offset + property.end
345
+ ),
346
+ text: '',
347
+ },
348
+ }
349
+ : {}),
350
+ });
351
+ }
352
+ }
353
+ if (childArray) checkGroups(childArray, component, base);
354
+ }
355
+ };
356
+
357
+ const seenArrays = new Set();
358
+ const checkHost = (element, component) => {
359
+ for (const prop of element.props ?? []) {
360
+ if (prop.type !== PROP_DIRECTIVE || prop.name !== 'bind') continue;
361
+ if (!prop.arg?.isStatic || prop.arg.content !== 'items') continue;
362
+ const content = prop.exp?.content ?? '';
363
+ const expression = parseExpression(content);
364
+ const value = unwrap(expression);
365
+ if (value?.type === 'Identifier') {
366
+ const array = arrayOf(value, arrays);
367
+ // A script const bound by two hosts is reported once.
368
+ if (!array || seenArrays.has(array)) continue;
369
+ seenArrays.add(array);
370
+ checkGroups(array, component, {
371
+ offset: ranged && descriptor?.scriptSetup ? scriptOffset : null,
372
+ line: scriptLine,
373
+ });
374
+ } else if (value?.type === 'ArrayExpression') {
375
+ // `(` was prepended to the expression before parsing.
376
+ const start = prop.exp.loc.start.offset - 1;
377
+ checkGroups(value, component, {
378
+ offset: ranged ? start : null,
379
+ line: prop.exp.loc.start.line,
380
+ });
381
+ }
382
+ }
383
+ };
384
+
385
+ const walk = (node) => {
386
+ if (!node || typeof node !== 'object') return;
387
+ if (node.type === NODE_ELEMENT && !own.has(node.tag)) {
388
+ const written = pascal(node.tag);
389
+ const component =
390
+ aliases.get(node.tag) ?? aliases.get(written) ?? written;
391
+ if (ACCESSOR_SURFACES.has(component)) checkSurface(node, component);
392
+ if (DROPDOWN_HOSTS.has(component)) checkHost(node, component);
393
+ }
394
+ for (const child of node.children ?? []) walk(child);
395
+ };
396
+ walk(ast);
397
+ return findings;
398
+ }