@dynamic-framework/ui-react 2.9.0 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (478) hide show
  1. package/dist/DPortalStack-6zJm6JoD.js +14 -0
  2. package/dist/DPortalStack-6zJm6JoD.js.map +1 -0
  3. package/dist/DPortalStack-D1-GGU4C.js +12 -0
  4. package/dist/DPortalStack-D1-GGU4C.js.map +1 -0
  5. package/dist/api.json +7429 -0
  6. package/dist/css/dynamic-ui.css +95 -26
  7. package/dist/css/dynamic-ui.min.css +2 -2
  8. package/dist/esm/components/DAlert/DAlert.js +16 -0
  9. package/dist/esm/components/DAlert/DAlert.js.map +1 -0
  10. package/dist/esm/components/DAlert/index.js +6 -0
  11. package/dist/esm/components/DAlert/index.js.map +1 -0
  12. package/dist/esm/components/DAvatar/DAvatar.js +23 -0
  13. package/dist/esm/components/DAvatar/DAvatar.js.map +1 -0
  14. package/dist/esm/components/DAvatar/index.js +6 -0
  15. package/dist/esm/components/DAvatar/index.js.map +1 -0
  16. package/dist/esm/components/DBadge/DBadge.js +31 -0
  17. package/dist/esm/components/DBadge/DBadge.js.map +1 -0
  18. package/dist/esm/components/DBadge/index.js +6 -0
  19. package/dist/esm/components/DBadge/index.js.map +1 -0
  20. package/dist/esm/components/DBox/DBox.js +9 -0
  21. package/dist/esm/components/DBox/DBox.js.map +1 -0
  22. package/dist/esm/components/DBox/index.js +6 -0
  23. package/dist/esm/components/DBox/index.js.map +1 -0
  24. package/dist/esm/components/DBoxFile/DBoxFile.js +26 -0
  25. package/dist/esm/components/DBoxFile/DBoxFile.js.map +1 -0
  26. package/dist/esm/components/DBoxFile/index.js +6 -0
  27. package/dist/esm/components/DBoxFile/index.js.map +1 -0
  28. package/dist/esm/components/DBoxFile/useDBoxFile.js +236 -0
  29. package/dist/esm/components/DBoxFile/useDBoxFile.js.map +1 -0
  30. package/dist/esm/components/DBoxFile/utils.js +127 -0
  31. package/dist/esm/components/DBoxFile/utils.js.map +1 -0
  32. package/dist/esm/components/DButton/DButton.js +84 -0
  33. package/dist/esm/components/DButton/DButton.js.map +1 -0
  34. package/dist/esm/components/DButton/index.js +6 -0
  35. package/dist/esm/components/DButton/index.js.map +1 -0
  36. package/dist/esm/components/DButtonIcon/DButtonIcon.js +55 -0
  37. package/dist/esm/components/DButtonIcon/DButtonIcon.js.map +1 -0
  38. package/dist/esm/components/DButtonIcon/index.js +6 -0
  39. package/dist/esm/components/DButtonIcon/index.js.map +1 -0
  40. package/dist/esm/components/DButtonIcon/warnMissingAccessibleName.js +40 -0
  41. package/dist/esm/components/DButtonIcon/warnMissingAccessibleName.js.map +1 -0
  42. package/dist/esm/components/DCard/DCard.js +17 -0
  43. package/dist/esm/components/DCard/DCard.js.map +1 -0
  44. package/dist/esm/components/DCard/components/DCardBody.js +9 -0
  45. package/dist/esm/components/DCard/components/DCardBody.js.map +1 -0
  46. package/dist/esm/components/DCard/components/DCardFooter.js +9 -0
  47. package/dist/esm/components/DCard/components/DCardFooter.js.map +1 -0
  48. package/dist/esm/components/DCard/components/DCardHeader.js +9 -0
  49. package/dist/esm/components/DCard/components/DCardHeader.js.map +1 -0
  50. package/dist/esm/components/DCard/index.js +9 -0
  51. package/dist/esm/components/DCard/index.js.map +1 -0
  52. package/dist/esm/components/DCarousel/DCarousel.js +39 -0
  53. package/dist/esm/components/DCarousel/DCarousel.js.map +1 -0
  54. package/dist/esm/components/DCarousel/components/DCarouselSlide.js +12 -0
  55. package/dist/esm/components/DCarousel/components/DCarouselSlide.js.map +1 -0
  56. package/dist/esm/components/DCarousel/index.js +7 -0
  57. package/dist/esm/components/DCarousel/index.js.map +1 -0
  58. package/dist/esm/components/DChip/DChip.js +18 -0
  59. package/dist/esm/components/DChip/DChip.js.map +1 -0
  60. package/dist/esm/components/DChip/index.js +6 -0
  61. package/dist/esm/components/DChip/index.js.map +1 -0
  62. package/dist/esm/components/DCollapse/DCollapse.js +30 -0
  63. package/dist/esm/components/DCollapse/DCollapse.js.map +1 -0
  64. package/dist/esm/components/DCollapse/index.js +6 -0
  65. package/dist/esm/components/DCollapse/index.js.map +1 -0
  66. package/dist/esm/components/DConfirmModal/DConfirmModalContainer.js +59 -0
  67. package/dist/esm/components/DConfirmModal/DConfirmModalContainer.js.map +1 -0
  68. package/dist/esm/components/DConfirmModal/DConfirmModalUI.js +39 -0
  69. package/dist/esm/components/DConfirmModal/DConfirmModalUI.js.map +1 -0
  70. package/dist/esm/components/DConfirmModal/confirmModalStore.js +38 -0
  71. package/dist/esm/components/DConfirmModal/confirmModalStore.js.map +1 -0
  72. package/dist/esm/components/DCreditCard/DCreditCard.js +15 -0
  73. package/dist/esm/components/DCreditCard/DCreditCard.js.map +1 -0
  74. package/dist/esm/components/DCreditCard/index.js +6 -0
  75. package/dist/esm/components/DCreditCard/index.js.map +1 -0
  76. package/dist/esm/components/DCurrencyText/DCurrencyText.js +10 -0
  77. package/dist/esm/components/DCurrencyText/DCurrencyText.js.map +1 -0
  78. package/dist/esm/components/DCurrencyText/index.js +6 -0
  79. package/dist/esm/components/DCurrencyText/index.js.map +1 -0
  80. package/dist/esm/components/DDataStateWrapper/DDataStateWrapper.js +42 -0
  81. package/dist/esm/components/DDataStateWrapper/DDataStateWrapper.js.map +1 -0
  82. package/dist/esm/components/DDataStateWrapper/components/EmptyState.js +10 -0
  83. package/dist/esm/components/DDataStateWrapper/components/EmptyState.js.map +1 -0
  84. package/dist/esm/components/DDataStateWrapper/components/ErrorState.js +10 -0
  85. package/dist/esm/components/DDataStateWrapper/components/ErrorState.js.map +1 -0
  86. package/dist/esm/components/DDataStateWrapper/components/LoadingState.js +8 -0
  87. package/dist/esm/components/DDataStateWrapper/components/LoadingState.js.map +1 -0
  88. package/dist/esm/components/DDataStateWrapper/index.js +9 -0
  89. package/dist/esm/components/DDataStateWrapper/index.js.map +1 -0
  90. package/dist/esm/components/DDatePicker/DDatePicker.js +54 -0
  91. package/dist/esm/components/DDatePicker/DDatePicker.js.map +1 -0
  92. package/dist/esm/components/DDatePicker/components/DDatePickerHeaderSelector.js +50 -0
  93. package/dist/esm/components/DDatePicker/components/DDatePickerHeaderSelector.js.map +1 -0
  94. package/dist/esm/components/DDatePicker/components/DDatePickerInput.js +16 -0
  95. package/dist/esm/components/DDatePicker/components/DDatePickerInput.js.map +1 -0
  96. package/dist/esm/components/DDatePicker/components/DDatePickerTime.js +11 -0
  97. package/dist/esm/components/DDatePicker/components/DDatePickerTime.js.map +1 -0
  98. package/dist/esm/components/DDatePicker/index.js +6 -0
  99. package/dist/esm/components/DDatePicker/index.js.map +1 -0
  100. package/dist/esm/components/DDropdown/DDropdown.js +315 -0
  101. package/dist/esm/components/DDropdown/DDropdown.js.map +1 -0
  102. package/dist/esm/components/DDropdown/index.js +6 -0
  103. package/dist/esm/components/DDropdown/index.js.map +1 -0
  104. package/dist/esm/components/DErrorBoundary/DErrorBoundary.js +22 -0
  105. package/dist/esm/components/DErrorBoundary/DErrorBoundary.js.map +1 -0
  106. package/dist/esm/components/DErrorBoundary/components/DefaultErrorBoundary.js +10 -0
  107. package/dist/esm/components/DErrorBoundary/components/DefaultErrorBoundary.js.map +1 -0
  108. package/dist/esm/components/DErrorBoundary/index.js +7 -0
  109. package/dist/esm/components/DErrorBoundary/index.js.map +1 -0
  110. package/dist/esm/components/DIcon/DIcon.js +21 -0
  111. package/dist/esm/components/DIcon/DIcon.js.map +1 -0
  112. package/dist/esm/components/DIcon/index.js +6 -0
  113. package/dist/esm/components/DIcon/index.js.map +1 -0
  114. package/dist/esm/components/DIconBase/DIconBase.js +146 -0
  115. package/dist/esm/components/DIconBase/DIconBase.js.map +1 -0
  116. package/dist/esm/components/DIconBase/index.js +6 -0
  117. package/dist/esm/components/DIconBase/index.js.map +1 -0
  118. package/dist/esm/components/DIconBase/resolveIconFromRegistry.js +20 -0
  119. package/dist/esm/components/DIconBase/resolveIconFromRegistry.js.map +1 -0
  120. package/dist/esm/components/DIconBase/warnUnknownIcon.js +59 -0
  121. package/dist/esm/components/DIconBase/warnUnknownIcon.js.map +1 -0
  122. package/dist/esm/components/DInput/DInput.js +95 -0
  123. package/dist/esm/components/DInput/DInput.js.map +1 -0
  124. package/dist/esm/components/DInput/index.js +6 -0
  125. package/dist/esm/components/DInput/index.js.map +1 -0
  126. package/dist/esm/components/DInputCheck/DInputCheck.js +94 -0
  127. package/dist/esm/components/DInputCheck/DInputCheck.js.map +1 -0
  128. package/dist/esm/components/DInputCheck/index.js +6 -0
  129. package/dist/esm/components/DInputCheck/index.js.map +1 -0
  130. package/dist/esm/components/DInputCounter/DInputCounter.js +107 -0
  131. package/dist/esm/components/DInputCounter/DInputCounter.js.map +1 -0
  132. package/dist/esm/components/DInputCounter/index.js +6 -0
  133. package/dist/esm/components/DInputCounter/index.js.map +1 -0
  134. package/dist/esm/components/DInputCurrency/DInputCurrency.js +25 -0
  135. package/dist/esm/components/DInputCurrency/DInputCurrency.js.map +1 -0
  136. package/dist/esm/components/DInputCurrency/index.js +6 -0
  137. package/dist/esm/components/DInputCurrency/index.js.map +1 -0
  138. package/dist/esm/components/DInputMask/DInputMask.js +13 -0
  139. package/dist/esm/components/DInputMask/DInputMask.js.map +1 -0
  140. package/dist/esm/components/DInputMask/index.js +6 -0
  141. package/dist/esm/components/DInputMask/index.js.map +1 -0
  142. package/dist/esm/components/DInputPassword/DInputPassword.js +24 -0
  143. package/dist/esm/components/DInputPassword/DInputPassword.js.map +1 -0
  144. package/dist/esm/components/DInputPassword/index.js +6 -0
  145. package/dist/esm/components/DInputPassword/index.js.map +1 -0
  146. package/dist/esm/components/DInputPhone/DInputPhone.js +108 -0
  147. package/dist/esm/components/DInputPhone/DInputPhone.js.map +1 -0
  148. package/dist/esm/components/DInputPhone/index.js +6 -0
  149. package/dist/esm/components/DInputPhone/index.js.map +1 -0
  150. package/dist/esm/components/DInputPin/DInputPin.js +88 -0
  151. package/dist/esm/components/DInputPin/DInputPin.js.map +1 -0
  152. package/dist/esm/components/DInputPin/index.js +6 -0
  153. package/dist/esm/components/DInputPin/index.js.map +1 -0
  154. package/dist/esm/components/DInputRange/DInputRange.js +60 -0
  155. package/dist/esm/components/DInputRange/DInputRange.js.map +1 -0
  156. package/dist/esm/components/DInputRange/index.js +6 -0
  157. package/dist/esm/components/DInputRange/index.js.map +1 -0
  158. package/dist/esm/components/DInputSearch/DInputSearch.js +57 -0
  159. package/dist/esm/components/DInputSearch/DInputSearch.js.map +1 -0
  160. package/dist/esm/components/DInputSearch/index.js +6 -0
  161. package/dist/esm/components/DInputSearch/index.js.map +1 -0
  162. package/dist/esm/components/DInputSelect/DInputSelect.js +104 -0
  163. package/dist/esm/components/DInputSelect/DInputSelect.js.map +1 -0
  164. package/dist/esm/components/DInputSelect/index.js +6 -0
  165. package/dist/esm/components/DInputSelect/index.js.map +1 -0
  166. package/dist/esm/components/DInputSwitch/DInputSwitch.js +43 -0
  167. package/dist/esm/components/DInputSwitch/DInputSwitch.js.map +1 -0
  168. package/dist/esm/components/DInputSwitch/index.js +6 -0
  169. package/dist/esm/components/DInputSwitch/index.js.map +1 -0
  170. package/dist/esm/components/DLayout/DLayout.js +22 -0
  171. package/dist/esm/components/DLayout/DLayout.js.map +1 -0
  172. package/dist/esm/components/DLayout/components/DLayoutPane.js +16 -0
  173. package/dist/esm/components/DLayout/components/DLayoutPane.js.map +1 -0
  174. package/dist/esm/components/DLayout/index.js +7 -0
  175. package/dist/esm/components/DLayout/index.js.map +1 -0
  176. package/dist/esm/components/DListGroup/DListGroup.js +39 -0
  177. package/dist/esm/components/DListGroup/DListGroup.js.map +1 -0
  178. package/dist/esm/components/DListGroup/ListGroupContext.js +11 -0
  179. package/dist/esm/components/DListGroup/ListGroupContext.js.map +1 -0
  180. package/dist/esm/components/DListGroup/components/DListGroupItem.js +59 -0
  181. package/dist/esm/components/DListGroup/components/DListGroupItem.js.map +1 -0
  182. package/dist/esm/components/DListGroup/components/warnInvalidListMarkup.js +30 -0
  183. package/dist/esm/components/DListGroup/components/warnInvalidListMarkup.js.map +1 -0
  184. package/dist/esm/components/DListGroup/index.js +7 -0
  185. package/dist/esm/components/DListGroup/index.js.map +1 -0
  186. package/dist/esm/components/DModal/DModal.js +37 -0
  187. package/dist/esm/components/DModal/DModal.js.map +1 -0
  188. package/dist/esm/components/DModal/components/DModalBody.js +9 -0
  189. package/dist/esm/components/DModal/components/DModalBody.js.map +1 -0
  190. package/dist/esm/components/DModal/components/DModalFooter.js +14 -0
  191. package/dist/esm/components/DModal/components/DModalFooter.js.map +1 -0
  192. package/dist/esm/components/DModal/components/DModalHeader.js +15 -0
  193. package/dist/esm/components/DModal/components/DModalHeader.js.map +1 -0
  194. package/dist/esm/components/DModal/index.js +9 -0
  195. package/dist/esm/components/DModal/index.js.map +1 -0
  196. package/dist/esm/components/DOffcanvas/DOffcanvas.js +82 -0
  197. package/dist/esm/components/DOffcanvas/DOffcanvas.js.map +1 -0
  198. package/dist/esm/components/DOffcanvas/components/DOffcanvasBody.js +9 -0
  199. package/dist/esm/components/DOffcanvas/components/DOffcanvasBody.js.map +1 -0
  200. package/dist/esm/components/DOffcanvas/components/DOffcanvasFooter.js +14 -0
  201. package/dist/esm/components/DOffcanvas/components/DOffcanvasFooter.js.map +1 -0
  202. package/dist/esm/components/DOffcanvas/components/DOffcanvasHeader.js +15 -0
  203. package/dist/esm/components/DOffcanvas/components/DOffcanvasHeader.js.map +1 -0
  204. package/dist/esm/components/DOffcanvas/index.js +9 -0
  205. package/dist/esm/components/DOffcanvas/index.js.map +1 -0
  206. package/dist/esm/components/DOtp/DOtp.js +26 -0
  207. package/dist/esm/components/DOtp/DOtp.js.map +1 -0
  208. package/dist/esm/components/DOtp/components/OtpCountdown.js +12 -0
  209. package/dist/esm/components/DOtp/components/OtpCountdown.js.map +1 -0
  210. package/dist/esm/components/DOtp/index.js +6 -0
  211. package/dist/esm/components/DOtp/index.js.map +1 -0
  212. package/dist/esm/components/DPaginator/DPaginator.js +12 -0
  213. package/dist/esm/components/DPaginator/DPaginator.js.map +1 -0
  214. package/dist/esm/components/DPaginator/index.js +6 -0
  215. package/dist/esm/components/DPaginator/index.js.map +1 -0
  216. package/dist/esm/components/DPasswordStrengthMeter/DPasswordStrengthMeter.js +27 -0
  217. package/dist/esm/components/DPasswordStrengthMeter/DPasswordStrengthMeter.js.map +1 -0
  218. package/dist/esm/components/DPasswordStrengthMeter/PasswordCheckItem.js +10 -0
  219. package/dist/esm/components/DPasswordStrengthMeter/PasswordCheckItem.js.map +1 -0
  220. package/dist/esm/components/DPasswordStrengthMeter/PasswordCheckList.js +41 -0
  221. package/dist/esm/components/DPasswordStrengthMeter/PasswordCheckList.js.map +1 -0
  222. package/dist/esm/components/DPasswordStrengthMeter/PasswordStrength.js +23 -0
  223. package/dist/esm/components/DPasswordStrengthMeter/PasswordStrength.js.map +1 -0
  224. package/dist/esm/components/DPasswordStrengthMeter/index.js +6 -0
  225. package/dist/esm/components/DPasswordStrengthMeter/index.js.map +1 -0
  226. package/dist/esm/components/DPopover/DPopover.js +49 -0
  227. package/dist/esm/components/DPopover/DPopover.js.map +1 -0
  228. package/dist/esm/components/DPopover/index.js +6 -0
  229. package/dist/esm/components/DPopover/index.js.map +1 -0
  230. package/dist/esm/components/DProgress/DProgress.js +22 -0
  231. package/dist/esm/components/DProgress/DProgress.js.map +1 -0
  232. package/dist/esm/components/DProgress/index.js +6 -0
  233. package/dist/esm/components/DProgress/index.js.map +1 -0
  234. package/dist/esm/components/DSelect/DSelect.js +88 -0
  235. package/dist/esm/components/DSelect/DSelect.js.map +1 -0
  236. package/dist/esm/components/DSelect/components/DSelectClearIndicator.js +12 -0
  237. package/dist/esm/components/DSelect/components/DSelectClearIndicator.js.map +1 -0
  238. package/dist/esm/components/DSelect/components/DSelectDropdownIndicator.js +12 -0
  239. package/dist/esm/components/DSelect/components/DSelectDropdownIndicator.js.map +1 -0
  240. package/dist/esm/components/DSelect/components/DSelectLoadingIndicator.js +12 -0
  241. package/dist/esm/components/DSelect/components/DSelectLoadingIndicator.js.map +1 -0
  242. package/dist/esm/components/DSelect/components/DSelectMultiValueRemove.js +12 -0
  243. package/dist/esm/components/DSelect/components/DSelectMultiValueRemove.js.map +1 -0
  244. package/dist/esm/components/DSelect/components/DSelectOptionCheck.js +14 -0
  245. package/dist/esm/components/DSelect/components/DSelectOptionCheck.js.map +1 -0
  246. package/dist/esm/components/DSelect/components/DSelectOptionEmoji.js +14 -0
  247. package/dist/esm/components/DSelect/components/DSelectOptionEmoji.js.map +1 -0
  248. package/dist/esm/components/DSelect/components/DSelectOptionIcon.js +15 -0
  249. package/dist/esm/components/DSelect/components/DSelectOptionIcon.js.map +1 -0
  250. package/dist/esm/components/DSelect/components/DSelectPlaceholder.js +14 -0
  251. package/dist/esm/components/DSelect/components/DSelectPlaceholder.js.map +1 -0
  252. package/dist/esm/components/DSelect/components/DSelectSingleValueEmoji.js +15 -0
  253. package/dist/esm/components/DSelect/components/DSelectSingleValueEmoji.js.map +1 -0
  254. package/dist/esm/components/DSelect/components/DSelectSingleValueEmojiText.js +15 -0
  255. package/dist/esm/components/DSelect/components/DSelectSingleValueEmojiText.js.map +1 -0
  256. package/dist/esm/components/DSelect/components/DSelectSingleValueIconText.js +16 -0
  257. package/dist/esm/components/DSelect/components/DSelectSingleValueIconText.js.map +1 -0
  258. package/dist/esm/components/DSelect/createAriaGuidance.js +57 -0
  259. package/dist/esm/components/DSelect/createAriaGuidance.js.map +1 -0
  260. package/dist/esm/components/DSelect/index.js +6 -0
  261. package/dist/esm/components/DSelect/index.js.map +1 -0
  262. package/dist/esm/components/DStepper/DStepper.js +13 -0
  263. package/dist/esm/components/DStepper/DStepper.js.map +1 -0
  264. package/dist/esm/components/DStepper/index.js +6 -0
  265. package/dist/esm/components/DStepper/index.js.map +1 -0
  266. package/dist/esm/components/DStepperDesktop/DStepperDesktop.js +27 -0
  267. package/dist/esm/components/DStepperDesktop/DStepperDesktop.js.map +1 -0
  268. package/dist/esm/components/DStepperDesktop/index.js +6 -0
  269. package/dist/esm/components/DStepperDesktop/index.js.map +1 -0
  270. package/dist/esm/components/DStepperMobile/DStepperMobile.js +37 -0
  271. package/dist/esm/components/DStepperMobile/DStepperMobile.js.map +1 -0
  272. package/dist/esm/components/DStepperMobile/index.js +6 -0
  273. package/dist/esm/components/DStepperMobile/index.js.map +1 -0
  274. package/dist/esm/components/DTabs/DTabs.js +106 -0
  275. package/dist/esm/components/DTabs/DTabs.js.map +1 -0
  276. package/dist/esm/components/DTabs/TabContext.js +14 -0
  277. package/dist/esm/components/DTabs/TabContext.js.map +1 -0
  278. package/dist/esm/components/DTabs/components/DTabContent.js +14 -0
  279. package/dist/esm/components/DTabs/components/DTabContent.js.map +1 -0
  280. package/dist/esm/components/DTabs/components/DTabsProvider.js +22 -0
  281. package/dist/esm/components/DTabs/components/DTabsProvider.js.map +1 -0
  282. package/dist/esm/components/DTabs/index.js +9 -0
  283. package/dist/esm/components/DTabs/index.js.map +1 -0
  284. package/dist/esm/components/DTimeline/DTimeline.js +12 -0
  285. package/dist/esm/components/DTimeline/DTimeline.js.map +1 -0
  286. package/dist/esm/components/DTimeline/index.js +6 -0
  287. package/dist/esm/components/DTimeline/index.js.map +1 -0
  288. package/dist/esm/components/DToast/DToast.js +20 -0
  289. package/dist/esm/components/DToast/DToast.js.map +1 -0
  290. package/dist/esm/components/DToast/components/DToastBody.js +9 -0
  291. package/dist/esm/components/DToast/components/DToastBody.js.map +1 -0
  292. package/dist/esm/components/DToast/components/DToastHeader.js +9 -0
  293. package/dist/esm/components/DToast/components/DToastHeader.js.map +1 -0
  294. package/dist/esm/components/DToast/index.js +8 -0
  295. package/dist/esm/components/DToast/index.js.map +1 -0
  296. package/dist/esm/components/DToastContainer/DToastContainer.js +9 -0
  297. package/dist/esm/components/DToastContainer/DToastContainer.js.map +1 -0
  298. package/dist/esm/components/DToastContainer/index.js +7 -0
  299. package/dist/esm/components/DToastContainer/index.js.map +1 -0
  300. package/dist/esm/components/DToastContainer/useDToast.js +58 -0
  301. package/dist/esm/components/DToastContainer/useDToast.js.map +1 -0
  302. package/dist/esm/components/DTooltip/DTooltip.js +51 -0
  303. package/dist/esm/components/DTooltip/DTooltip.js.map +1 -0
  304. package/dist/esm/components/DTooltip/index.js +6 -0
  305. package/dist/esm/components/DTooltip/index.js.map +1 -0
  306. package/dist/esm/components/DVoucher/DVoucher.js +55 -0
  307. package/dist/esm/components/DVoucher/DVoucher.js.map +1 -0
  308. package/dist/esm/components/DVoucher/hooks/useScreenshot.js +30 -0
  309. package/dist/esm/components/DVoucher/hooks/useScreenshot.js.map +1 -0
  310. package/dist/esm/components/DVoucher/hooks/useScreenshotDownload.js +30 -0
  311. package/dist/esm/components/DVoucher/hooks/useScreenshotDownload.js.map +1 -0
  312. package/dist/esm/components/DVoucher/hooks/useScreenshotWebShare.js +27 -0
  313. package/dist/esm/components/DVoucher/hooks/useScreenshotWebShare.js.map +1 -0
  314. package/dist/esm/components/DVoucher/index.js +9 -0
  315. package/dist/esm/components/DVoucher/index.js.map +1 -0
  316. package/dist/esm/components/config.js +14 -0
  317. package/dist/esm/components/config.js.map +1 -0
  318. package/dist/esm/components/internal/DFormLabel.js +46 -0
  319. package/dist/esm/components/internal/DFormLabel.js.map +1 -0
  320. package/dist/esm/contexts/DContext.js +124 -0
  321. package/dist/esm/contexts/DContext.js.map +1 -0
  322. package/dist/esm/contexts/DPortalContext.js +163 -0
  323. package/dist/esm/contexts/DPortalContext.js.map +1 -0
  324. package/dist/esm/contexts/portal/DPortalStack.js +12 -0
  325. package/dist/esm/contexts/portal/DPortalStack.js.map +1 -0
  326. package/dist/esm/hooks/useConfirmModal.js +63 -0
  327. package/dist/esm/hooks/useConfirmModal.js.map +1 -0
  328. package/dist/esm/hooks/useControlledState.js +51 -0
  329. package/dist/esm/hooks/useControlledState.js.map +1 -0
  330. package/dist/esm/hooks/useCountdown.js +45 -0
  331. package/dist/esm/hooks/useCountdown.js.map +1 -0
  332. package/dist/esm/hooks/useDisableBodyScrollEffect.js +52 -0
  333. package/dist/esm/hooks/useDisableBodyScrollEffect.js.map +1 -0
  334. package/dist/esm/hooks/useDisableInputWheel.js +17 -0
  335. package/dist/esm/hooks/useDisableInputWheel.js.map +1 -0
  336. package/dist/esm/hooks/useFormatCurrency.js +16 -0
  337. package/dist/esm/hooks/useFormatCurrency.js.map +1 -0
  338. package/dist/esm/hooks/useInputCurrency.js +130 -0
  339. package/dist/esm/hooks/useInputCurrency.js.map +1 -0
  340. package/dist/esm/hooks/useItemSelection.js +35 -0
  341. package/dist/esm/hooks/useItemSelection.js.map +1 -0
  342. package/dist/esm/hooks/useMediaBreakpointUp.js +69 -0
  343. package/dist/esm/hooks/useMediaBreakpointUp.js.map +1 -0
  344. package/dist/esm/hooks/useMediaQuery.js +11 -0
  345. package/dist/esm/hooks/useMediaQuery.js.map +1 -0
  346. package/dist/esm/hooks/useOtp.js +107 -0
  347. package/dist/esm/hooks/useOtp.js.map +1 -0
  348. package/dist/esm/hooks/usePortal.js +26 -0
  349. package/dist/esm/hooks/usePortal.js.map +1 -0
  350. package/dist/esm/hooks/useProvidedRefOrCreate.js +24 -0
  351. package/dist/esm/hooks/useProvidedRefOrCreate.js.map +1 -0
  352. package/dist/esm/hooks/useResponsiveProp.js +61 -0
  353. package/dist/esm/hooks/useResponsiveProp.js.map +1 -0
  354. package/dist/esm/hooks/useStackState.js +40 -0
  355. package/dist/esm/hooks/useStackState.js.map +1 -0
  356. package/dist/esm/icons/coreIcons.js +55 -0
  357. package/dist/esm/icons/coreIcons.js.map +1 -0
  358. package/dist/esm/index.js +99 -0
  359. package/dist/esm/index.js.map +1 -0
  360. package/dist/esm/utils/attr-accept.js +38 -0
  361. package/dist/esm/utils/attr-accept.js.map +1 -0
  362. package/dist/esm/utils/buildUrl.js +57 -0
  363. package/dist/esm/utils/buildUrl.js.map +1 -0
  364. package/dist/esm/utils/changeQueryString.js +19 -0
  365. package/dist/esm/utils/changeQueryString.js.map +1 -0
  366. package/dist/esm/utils/configureI18n.js +20 -0
  367. package/dist/esm/utils/configureI18n.js.map +1 -0
  368. package/dist/esm/utils/formatCurrency.js +8 -0
  369. package/dist/esm/utils/formatCurrency.js.map +1 -0
  370. package/dist/esm/utils/getCssVariable.js +7 -0
  371. package/dist/esm/utils/getCssVariable.js.map +1 -0
  372. package/dist/esm/utils/getKeyboardFocusableElements.js +11 -0
  373. package/dist/esm/utils/getKeyboardFocusableElements.js.map +1 -0
  374. package/dist/esm/utils/getQueryString.js +8 -0
  375. package/dist/esm/utils/getQueryString.js.map +1 -0
  376. package/dist/esm/utils/hasLabelContent.js +48 -0
  377. package/dist/esm/utils/hasLabelContent.js.map +1 -0
  378. package/dist/esm/utils/isTextLabel.js +23 -0
  379. package/dist/esm/utils/isTextLabel.js.map +1 -0
  380. package/dist/esm/utils/labelClickGuard.js +61 -0
  381. package/dist/esm/utils/labelClickGuard.js.map +1 -0
  382. package/dist/esm/utils/mediaQuery.js +13 -0
  383. package/dist/esm/utils/mediaQuery.js.map +1 -0
  384. package/dist/esm/utils/sanitizeHref.js +67 -0
  385. package/dist/esm/utils/sanitizeHref.js.map +1 -0
  386. package/dist/esm/utils/validatePhoneNumber.js +14 -0
  387. package/dist/esm/utils/validatePhoneNumber.js.map +1 -0
  388. package/dist/esm/utils/warnLabelUsage.js +57 -0
  389. package/dist/esm/utils/warnLabelUsage.js.map +1 -0
  390. package/dist/icons-core.json +33 -0
  391. package/dist/index.esm.js +1151 -184
  392. package/dist/index.esm.js.map +1 -1
  393. package/dist/index.js +1151 -182
  394. package/dist/index.js.map +1 -1
  395. package/dist/types/components/DAlert/DAlert.d.ts +16 -2
  396. package/dist/types/components/DButtonIcon/DButtonIcon.d.ts +9 -1
  397. package/dist/types/components/DButtonIcon/warnMissingAccessibleName.d.ts +18 -0
  398. package/dist/types/components/DDataStateWrapper/DDataStateWrapper.d.ts +29 -4
  399. package/dist/types/components/DDataStateWrapper/index.d.ts +1 -1
  400. package/dist/types/components/DErrorBoundary/DErrorBoundary.d.ts +6 -1
  401. package/dist/types/components/DErrorBoundary/components/DefaultErrorBoundary.d.ts +3 -1
  402. package/dist/types/components/DIcon/DIcon.d.ts +8 -0
  403. package/dist/types/components/DIconBase/DIconBase.d.ts +43 -1
  404. package/dist/types/components/DIconBase/resolveIconFromRegistry.d.ts +14 -0
  405. package/dist/types/components/DIconBase/warnUnknownIcon.d.ts +13 -0
  406. package/dist/types/components/DInput/DInput.d.ts +11 -2
  407. package/dist/types/components/DInputCheck/DInputCheck.d.ts +28 -3
  408. package/dist/types/components/DInputCounter/DInputCounter.d.ts +15 -3
  409. package/dist/types/components/DInputCurrency/DInputCurrency.d.ts +10 -3
  410. package/dist/types/components/DInputPhone/DInputPhone.d.ts +11 -2
  411. package/dist/types/components/DInputPin/DInputPin.d.ts +12 -2
  412. package/dist/types/components/DInputRange/DInputRange.d.ts +16 -3
  413. package/dist/types/components/DInputSelect/DInputSelect.d.ts +18 -3
  414. package/dist/types/components/DInputSwitch/DInputSwitch.d.ts +28 -3
  415. package/dist/types/components/DListGroup/DListGroup.d.ts +12 -1
  416. package/dist/types/components/DListGroup/ListGroupContext.d.ts +8 -0
  417. package/dist/types/components/DListGroup/components/DListGroupItem.d.ts +6 -1
  418. package/dist/types/components/DListGroup/components/warnInvalidListMarkup.d.ts +13 -0
  419. package/dist/types/components/DPasswordStrengthMeter/DPasswordStrengthMeter.d.ts +18 -3
  420. package/dist/types/components/DProgress/DProgress.d.ts +13 -1
  421. package/dist/types/components/DSelect/DSelect.d.ts +23 -3
  422. package/dist/types/components/DSelect/createAriaGuidance.d.ts +21 -0
  423. package/dist/types/components/DTabs/DTabs.d.ts +13 -2
  424. package/dist/types/components/DTabs/TabContext.d.ts +5 -0
  425. package/dist/types/components/DTabs/components/DTabsProvider.d.ts +11 -0
  426. package/dist/types/components/DTabs/index.d.ts +1 -0
  427. package/dist/types/components/DToast/DToast.d.ts +10 -3
  428. package/dist/types/components/DToastContainer/useDToast.d.ts +8 -1
  429. package/dist/types/components/config.d.ts +10 -0
  430. package/dist/types/components/index.d.ts +2 -2
  431. package/dist/types/components/interface.d.ts +7 -0
  432. package/dist/types/components/internal/DFormLabel.d.ts +30 -0
  433. package/dist/types/contexts/portal/DPortalStack.d.ts +13 -0
  434. package/dist/types/hooks/useControlledState.d.ts +17 -0
  435. package/dist/types/hooks/useInputCurrency.d.ts +26 -1
  436. package/dist/types/hooks/useMediaBreakpointUp.d.ts +10 -0
  437. package/dist/types/icons/coreIcons.d.ts +24 -0
  438. package/dist/types/icons/index.d.ts +2 -0
  439. package/dist/types/index.d.ts +1 -0
  440. package/dist/types/utils/hasLabelContent.d.ts +30 -0
  441. package/dist/types/utils/isTextLabel.d.ts +17 -0
  442. package/dist/types/utils/labelClickGuard.d.ts +20 -0
  443. package/dist/types/utils/warnLabelUsage.d.ts +43 -0
  444. package/package.json +10 -5
  445. package/src/style/abstracts/variables/_body.scss +2 -2
  446. package/src/style/abstracts/variables/_chips.scss +2 -0
  447. package/src/style/abstracts/variables/_colors.scss +2 -2
  448. package/src/style/base/_buttons.scss +4 -1
  449. package/src/style/base/_input-group.scss +5 -2
  450. package/src/style/base/_list-group.scss +55 -0
  451. package/src/style/components/_d-chip.scss +7 -2
  452. package/src/style/components/_d-tabs.scss +3 -0
  453. package/src/style/helpers/_color-bg.scss +5 -0
  454. package/src/style/root/_root.scss +9 -0
  455. package/dist/types/components/DVoucher/hooks/useScreenshotDownload.spec.d.ts +0 -1
  456. package/dist/types/components/DVoucher/hooks/useScreenshotWebShare.spec.d.ts +0 -1
  457. package/dist/types/hooks/tests/useCountdown.spec.d.ts +0 -1
  458. package/dist/types/hooks/tests/useDisableBodyScrollEffect.spec.d.ts +0 -1
  459. package/dist/types/hooks/tests/useDisableInputWheel.spec.d.ts +0 -1
  460. package/dist/types/hooks/tests/useFormatCurrency.spec.d.ts +0 -1
  461. package/dist/types/hooks/tests/useInputCurrency.spec.d.ts +0 -1
  462. package/dist/types/hooks/tests/useItemSelection.spec.d.ts +0 -1
  463. package/dist/types/hooks/tests/useMediaBreakpointUp.spec.d.ts +0 -1
  464. package/dist/types/hooks/tests/useMediaQuery.spec.d.ts +0 -1
  465. package/dist/types/hooks/tests/usePortal.spec.d.ts +0 -1
  466. package/dist/types/hooks/tests/useProvidedRefOrCreate.spec.d.ts +0 -1
  467. package/dist/types/hooks/tests/useStackState.spec.d.ts +0 -1
  468. package/dist/types/utils/tests/attr-accept.spec.d.ts +0 -1
  469. package/dist/types/utils/tests/buildUrl.spec.d.ts +0 -1
  470. package/dist/types/utils/tests/changeQueryString.spec.d.ts +0 -1
  471. package/dist/types/utils/tests/configureI18n.spec.d.ts +0 -1
  472. package/dist/types/utils/tests/formatCurrency.spec.d.ts +0 -1
  473. package/dist/types/utils/tests/getCssVariable.spec.d.ts +0 -1
  474. package/dist/types/utils/tests/getKeyboardFocusableElements.spec.d.ts +0 -1
  475. package/dist/types/utils/tests/getQueryString.spec.d.ts +0 -1
  476. package/dist/types/utils/tests/mediaQuery.spec.d.ts +0 -1
  477. package/dist/types/utils/tests/sanitizeHref.spec.d.ts +0 -1
  478. package/dist/types/utils/tests/validatePhoneNumber.spec.d.ts +0 -1
package/dist/index.esm.js CHANGED
@@ -1,13 +1,13 @@
1
1
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
2
  import classNames from 'classnames';
3
- import React, { useEffect, useState, useCallback, useMemo, useContext, createContext, useLayoutEffect, useSyncExternalStore, createElement, forwardRef, useId, useRef, isValidElement, cloneElement, createRef } from 'react';
3
+ import React, { useEffect, useState, useCallback, useMemo, useContext, useRef, createContext, useLayoutEffect, useSyncExternalStore, createElement, isValidElement, forwardRef, useId, cloneElement, Children } from 'react';
4
4
  import { __rest } from 'tslib';
5
5
  import * as LucideIcons from 'lucide-react';
6
6
  import { isValidElementType } from 'react-is';
7
7
  import { createPortal } from 'react-dom';
8
- import { AnimatePresence, motion } from 'framer-motion';
9
8
  import { fromEvent } from 'file-selector';
10
9
  import { SplideSlide, Splide, SplideTrack } from '@splidejs/react-splide';
10
+ import { motion, AnimatePresence } from 'framer-motion';
11
11
  import currency from 'currency.js';
12
12
  import DatePicker from 'react-datepicker';
13
13
  import { getYear, format, getMonth } from 'date-fns';
@@ -25,6 +25,16 @@ import i18n from 'i18next';
25
25
  import { initReactI18next } from 'react-i18next';
26
26
 
27
27
  const PREFIX_BS = 'bs-';
28
+ /**
29
+ * Default icon-font family configured by `DContextProvider`.
30
+ *
31
+ * `DIconBase` compares the family it receives against these values to tell an
32
+ * unconfigured consumer apart from one that deliberately opted into an icon
33
+ * font (Material Symbols, Bootstrap Icons). Only the former gets the
34
+ * development warning for an unresolved icon name.
35
+ */
36
+ const DEFAULT_ICON_FAMILY_CLASS = 'bi';
37
+ const DEFAULT_ICON_FAMILY_PREFIX = 'bi-';
28
38
 
29
39
  /* eslint-disable no-lonely-if */
30
40
  function useDisableBodyScrollEffect(disable) {
@@ -141,11 +151,30 @@ function getKeyboardFocusableElements(container) {
141
151
  ].filter((element) => !element.hasAttribute('disabled'));
142
152
  }
143
153
 
154
+ const loadPortalStack = () => import('./DPortalStack-D1-GGU4C.js').then((module) => module.default);
144
155
  const DPortalContext = createContext(undefined);
145
156
  function DPortalContextProvider({ portalName, children, availablePortals, }) {
146
157
  const { created } = usePortal(portalName);
147
158
  const [stack, { push, pop }] = useStackState([]);
159
+ const [PortalStack, setPortalStack] = useState(null);
160
+ const pending = useRef([]);
161
+ const ready = useRef(false);
148
162
  useDisableBodyScrollEffect(Boolean(stack.length));
163
+ // Loads framer-motion off the critical path only when portals are configured,
164
+ // so the first openPortal does not wait for it.
165
+ const hasPortals = Object.keys(availablePortals !== null && availablePortals !== void 0 ? availablePortals : {}).length > 0;
166
+ useEffect(() => {
167
+ if (!hasPortals)
168
+ return;
169
+ loadPortalStack()
170
+ .then((Stack) => {
171
+ ready.current = true;
172
+ setPortalStack(() => Stack);
173
+ })
174
+ .catch(() => {
175
+ // openPortal retries the import and reports the failure.
176
+ });
177
+ }, [hasPortals]);
149
178
  const openPortal = useCallback(
150
179
  // eslint-disable-next-line prefer-arrow-callback
151
180
  function openPortalImpl(name, payload) {
@@ -161,10 +190,30 @@ function DPortalContextProvider({ portalName, children, availablePortals, }) {
161
190
  }
162
191
  // K is a specific member of keyof T & string so the object satisfies
163
192
  // InternalStackItem<T>, but TS can't verify generic-over-union assignability.
164
- push({ name, Component, payload });
193
+ const item = { name, Component, payload };
194
+ if (ready.current) {
195
+ push(item);
196
+ }
197
+ else {
198
+ // Not loaded yet: the portal enters the stack together with its renderer.
199
+ pending.current.push(item);
200
+ loadPortalStack().then((Stack) => {
201
+ ready.current = true;
202
+ setPortalStack(() => Stack);
203
+ pending.current.splice(0).forEach(push);
204
+ }).catch((error) => {
205
+ pending.current = [];
206
+ // eslint-disable-next-line no-console
207
+ console.error('[DPortalContext] Could not load the portal stack', error);
208
+ });
209
+ }
165
210
  (_a = document.activeElement) === null || _a === void 0 ? void 0 : _a.blur();
166
211
  }, [availablePortals, push]);
167
212
  const closePortal = useCallback(() => {
213
+ if (pending.current.length > 0) {
214
+ pending.current.pop();
215
+ return;
216
+ }
168
217
  // pop() is safe on empty stacks, so close remains idempotent.
169
218
  pop();
170
219
  }, [pop]);
@@ -221,13 +270,10 @@ function DPortalContextProvider({ portalName, children, availablePortals, }) {
221
270
  window.removeEventListener('keydown', keyEvent);
222
271
  };
223
272
  }, [handleClose, portalName, stack.length]);
224
- return (jsxs(DPortalContext.Provider, { value: value, children: [children, created && createPortal(
273
+ return (jsxs(DPortalContext.Provider, { value: value, children: [children, created && PortalStack && createPortal(
225
274
  // eslint-disable-next-line max-len
226
275
  // eslint-disable-next-line jsx-a11y/no-static-element-interactions
227
- jsx("div", { onClick: ({ target }) => handleClose(target), onKeyDown: () => { }, children: jsx(AnimatePresence, { children: stack.flatMap(({ Component, name, payload, }) => [
228
- jsx(motion.div, { className: "backdrop", initial: { opacity: 0 }, animate: { opacity: 0.5 }, exit: { opacity: 0, transition: { delay: 0.3 } }, transition: { duration: 0.15, ease: 'linear' } }, `${name}-backdrop`),
229
- jsx(Component, { name: name, payload: payload }, name),
230
- ]) }) }), document.getElementById(portalName))] }));
276
+ jsx("div", { onClick: ({ target }) => handleClose(target), onKeyDown: () => { }, children: jsx(PortalStack, { stack: stack }) }), document.getElementById(portalName))] }));
231
277
  }
232
278
  /**
233
279
  * Hook to open/close registered portals (modals, offcanvas, etc.).
@@ -306,8 +352,8 @@ const DEFAULT_STATE = {
306
352
  decimal: '.',
307
353
  },
308
354
  icon: {
309
- familyClass: 'bi',
310
- familyPrefix: 'bi-',
355
+ familyClass: DEFAULT_ICON_FAMILY_CLASS,
356
+ familyPrefix: DEFAULT_ICON_FAMILY_PREFIX,
311
357
  materialStyle: false,
312
358
  },
313
359
  iconRegistry: undefined,
@@ -412,6 +458,79 @@ function useDContext() {
412
458
  return useContext(DContext);
413
459
  }
414
460
 
461
+ /**
462
+ * Resolves a string icon name against the registry supplied through
463
+ * `DContextProvider`'s `iconRegistry` prop.
464
+ *
465
+ * This is the single implementation shared by `DIcon` and `DIconBase`, so both
466
+ * public entry points — and every Dynamic UI component that renders an icon
467
+ * through them — resolve names identically.
468
+ *
469
+ * A non-string `icon` is returned untouched: a component passed directly needs
470
+ * no lookup. A name missing from the registry is also returned untouched, so
471
+ * the caller can fall through to lucide-react and then to the icon-font family.
472
+ */
473
+ function resolveIconFromRegistry(icon, iconRegistry) {
474
+ if (typeof icon !== 'string')
475
+ return icon;
476
+ return (iconRegistry === null || iconRegistry === void 0 ? void 0 : iconRegistry[icon]) || icon;
477
+ }
478
+
479
+ /**
480
+ * Names already reported, so a name repeated across renders or across many
481
+ * instances only warns once per page load.
482
+ */
483
+ const warnedIconNames = new Set();
484
+ /**
485
+ * True when the icon-font family is the one `DContextProvider` defaults to.
486
+ *
487
+ * The warning is limited to that case so a consumer who configured a different
488
+ * font — Material Symbols, for instance — is not told about names that font
489
+ * resolves perfectly well.
490
+ *
491
+ * It cannot tell "never configured a font" apart from "configured exactly the
492
+ * defaults", and those defaults are also the standard Bootstrap Icons setup, so
493
+ * a consumer who deliberately uses Bootstrap Icons is warned too. The message
494
+ * says so; silencing that case would need a new context flag, which is beyond
495
+ * what this helper should decide.
496
+ */
497
+ function isDefaultIconFamily(familyClass, familyPrefix) {
498
+ return familyClass === DEFAULT_ICON_FAMILY_CLASS
499
+ && familyPrefix === DEFAULT_ICON_FAMILY_PREFIX;
500
+ }
501
+ /**
502
+ * Warns, once per name and only outside production builds, that an icon name
503
+ * resolved to neither the registry nor lucide-react and is falling back to the
504
+ * default icon font, which this package no longer ships.
505
+ *
506
+ * Whether that fallback renders depends on the host app: if it loads the font
507
+ * itself the `<i>` shows the glyph, and if it does not, nothing appears. The
508
+ * message states both possibilities instead of assuming the second.
509
+ *
510
+ * The `process.env.NODE_ENV` guard is what bundlers constant-fold, so the whole
511
+ * call — and this module — drops out of a consumer's production bundle.
512
+ */
513
+ function warnUnknownIcon(name, familyClass, familyPrefix) {
514
+ if (!isDefaultIconFamily(familyClass, familyPrefix))
515
+ return;
516
+ if (warnedIconNames.has(name))
517
+ return;
518
+ warnedIconNames.add(name);
519
+ const fallbackClass = `${DEFAULT_ICON_FAMILY_CLASS} ${DEFAULT_ICON_FAMILY_PREFIX}${name}`;
520
+ // eslint-disable-next-line no-console
521
+ console.warn(`[Dynamic UI] Icon "${name}" is not in the icon registry and is not a `
522
+ + 'lucide-react export, so it falls back to the default icon font: '
523
+ + `<i class="${fallbackClass}">. That renders only if your app loads that `
524
+ + 'font, which this package stopped shipping in v2.0.0. '
525
+ + 'If the icon is missing, pass a PascalCase lucide-react name such as '
526
+ + '"Home", or register the component with '
527
+ + '<DContextProvider iconRegistry={{ MyIcon }}>. '
528
+ + `If you use the "${DEFAULT_ICON_FAMILY_CLASS}" font on purpose, this is a `
529
+ + 'false positive: the warning fires whenever the configured family equals '
530
+ + 'the package default, which is also the standard Bootstrap Icons setup. '
531
+ + 'It never appears in production builds.');
532
+ }
533
+
415
534
  function subscribeToMediaQuery(query, callback) {
416
535
  const mediaQueryList = window.matchMedia(query);
417
536
  mediaQueryList.addEventListener('change', callback);
@@ -429,9 +548,43 @@ function useMediaQuery(mediaQuery, useListener = false) {
429
548
  return useSyncExternalStore(useListener ? (cb) => subscribeToMediaQuery(mediaQuery, cb) : noop, () => (mediaQuery ? checkMediaQuery(mediaQuery) : true), () => false);
430
549
  }
431
550
 
432
- function useMediaBreakpointUp(breakpoint, useListener = false) {
551
+ const BREAKPOINTS = ['xs', 'sm', 'md', 'lg', 'xl', 'xxl'];
552
+ // Breakpoints read from the CSS on first use and shared by every component
553
+ // that resolves responsive props: one computed-style read for the page,
554
+ // instead of one per breakpoint per render. `dynamic-ui.css` has to be loaded
555
+ // before rendering; if it isn't, the empty result is kept too, so a
556
+ // misconfigured tree doesn't pay the read on every render (responsive props
557
+ // then keep their default value and useResponsiveProp warns in development).
558
+ let cssBreakpoints = null;
559
+ function readCssBreakpoint(breakpoint) {
560
+ if (cssBreakpoints)
561
+ return cssBreakpoints[breakpoint];
562
+ if (typeof document === 'undefined')
563
+ return '';
564
+ const style = getComputedStyle(document.documentElement);
565
+ const read = Object.fromEntries(BREAKPOINTS.map((name) => [
566
+ name,
567
+ style.getPropertyValue(`--${PREFIX_BS}breakpoint-${name}`).trim(),
568
+ ]));
569
+ cssBreakpoints = read;
570
+ return read[breakpoint];
571
+ }
572
+ /**
573
+ * Pixel value of a breakpoint. `DContextProvider` reads them from the CSS and
574
+ * shares them through the context; outside of it (a tree without the
575
+ * provider, or its first render) they are read from the same CSS variables
576
+ * here, so responsive props resolve either way.
577
+ */
578
+ function useBreakpointValue(breakpoint) {
433
579
  const { breakpoints } = useDContext();
434
- const mediaQuery = useMemo(() => (`(min-width: ${breakpoints[breakpoint]})`), [breakpoint, breakpoints]);
580
+ const fromContext = breakpoints[breakpoint];
581
+ return useMemo(() => fromContext || readCssBreakpoint(breakpoint), [fromContext, breakpoint]);
582
+ }
583
+ function useMediaBreakpointUp(breakpoint, useListener = false) {
584
+ const value = useBreakpointValue(breakpoint);
585
+ // Without a breakpoint the query can't match; `not all` keeps it false
586
+ // instead of an invalid `(min-width: )`.
587
+ const mediaQuery = value ? `(min-width: ${value})` : 'not all';
435
588
  return useMediaQuery(mediaQuery, useListener);
436
589
  }
437
590
  function useMediaBreakpointUpXs(useListener = false) {
@@ -453,6 +606,7 @@ function useMediaBreakpointUpXxl(useListener = false) {
453
606
  return useMediaBreakpointUp('xxl', useListener);
454
607
  }
455
608
 
609
+ let warnedMissingBreakpoints = false;
456
610
  /**
457
611
  * React hook to resolve a responsive property value based on the current viewport breakpoint.
458
612
  *
@@ -478,7 +632,16 @@ function useResponsiveProp(useListener = false) {
478
632
  const bpLgUp = useMediaBreakpointUpLg(useListener);
479
633
  const bpXlUp = useMediaBreakpointUpXl(useListener);
480
634
  const bpXxlUp = useMediaBreakpointUpXxl(useListener);
635
+ // `xs` is `0` and can't tell a missing variable apart, so `sm` is checked.
636
+ const hasBreakpoints = !!useBreakpointValue('sm');
481
637
  const responsivePropValue = useCallback((prop) => {
638
+ if (process.env.NODE_ENV !== 'production' && !hasBreakpoints && !warnedMissingBreakpoints) {
639
+ warnedMissingBreakpoints = true;
640
+ // eslint-disable-next-line no-console
641
+ console.warn(`[Dynamic UI] The --${PREFIX_BS}breakpoint-* CSS variables are not available, so a `
642
+ + 'responsive prop (an object by breakpoint) falls back to its default value. Load '
643
+ + 'dynamic-ui.css before rendering. It never appears in production builds.');
644
+ }
482
645
  // Pick the highest matched breakpoint value that is defined in prop
483
646
  if (prop.xxl !== undefined && bpXxlUp)
484
647
  return prop.xxl;
@@ -494,14 +657,38 @@ function useResponsiveProp(useListener = false) {
494
657
  return prop.xs;
495
658
  // Fallback: return undefined if no breakpoint matches
496
659
  return undefined;
497
- }, [bpSmUp, bpMdUp, bpLgUp, bpXlUp, bpXxlUp, bpXsUp]);
660
+ }, [bpSmUp, bpMdUp, bpLgUp, bpXlUp, bpXxlUp, bpXsUp, hasBreakpoints]);
498
661
  return { responsivePropValue };
499
662
  }
500
663
 
501
664
  function isIconComponent(value) {
502
665
  return typeof value !== 'string' && isValidElementType(value);
503
666
  }
504
- function DIconBase({ icon, color, style, className, size, useListenerSize = false, hasCircle = false, materialStyle = false, familyClass, familyPrefix, strokeWidth = 2, dataAttributes, }) {
667
+ /**
668
+ * Renders an icon, resolving the `icon` prop in this order:
669
+ *
670
+ * 1. **Icon registry** — a string name found in `DContextProvider`'s
671
+ * `iconRegistry` renders the component registered under it. A component
672
+ * passed directly skips this step and renders as-is.
673
+ * 2. **lucide-react** — a PascalCase name exported by lucide-react renders that
674
+ * icon. When `materialStyle` is on, this step is skipped and the name is
675
+ * emitted as the text content of the icon-font element instead.
676
+ * 3. **Icon-font family** — anything left over renders as
677
+ * `<i class="{familyClass} {familyPrefix}{name}">`, the legacy path for
678
+ * Material Symbols and Bootstrap Icons. Outside production builds, a name
679
+ * that reaches this step while the family is still the package default logs
680
+ * a one-time warning.
681
+ *
682
+ * A name that resolves nowhere and has no family configured renders `?`.
683
+ *
684
+ * Every path is hidden from the accessibility tree by default (`aria-hidden`),
685
+ * since icons are overwhelmingly decorative. See `ariaHidden` / `ariaLabel` to
686
+ * override that for an icon that carries meaning on its own.
687
+ */
688
+ function DIconBase({ icon: iconProp, color, style, className, size, useListenerSize = false, hasCircle = false, materialStyle = false, familyClass, familyPrefix, strokeWidth = 2, ariaHidden, ariaLabel, dataAttributes, }) {
689
+ const { iconRegistry } = useDContext();
690
+ // The registry wins over every other source, including Material icons.
691
+ const icon = resolveIconFromRegistry(iconProp, iconRegistry);
505
692
  // If materialStyle is true, use Material Design icons (legacy)
506
693
  const isStringIcon = typeof icon === 'string';
507
694
  const useMaterialIcons = materialStyle && isStringIcon;
@@ -523,6 +710,61 @@ function DIconBase({ icon, color, style, className, size, useListenerSize = fals
523
710
  }, [responsivePropValue, size]);
524
711
  const generateStyleVariables = useMemo(() => (Object.assign(Object.assign(Object.assign({}, resolvedSize && { [`--${PREFIX_BS}icon-component-size`]: resolvedSize }), hasCircle && { [`--${PREFIX_BS}icon-component-padding`]: `calc(var(--${PREFIX_BS}icon-component-size, 24px) * 0.4)` }), style)), [resolvedSize, hasCircle, style]);
525
712
  const generateClasses = useMemo(() => (Object.assign(Object.assign(Object.assign({ 'd-icon': true }, className && { [className]: true }), { 'd-icon-has-circle': hasCircle }), color && { [`d-icon-color-${color}`]: true })), [className, hasCircle, color]);
713
+ /**
714
+ * Decorative by default. `ariaHidden` and `ariaLabel` are not symmetric:
715
+ * hiding is absolute, so `ariaHidden` wins over a name, but `ariaHidden={false}`
716
+ * and `ariaLabel` agree — both expose the icon — and the name is honoured.
717
+ */
718
+ const accessibilityProps = useMemo(() => {
719
+ // Hiding is absolute: it wins over a name.
720
+ if (ariaHidden === true)
721
+ return { 'aria-hidden': true };
722
+ // A name exposes the icon, which is also what `ariaHidden={false}` asks for.
723
+ if (ariaLabel)
724
+ return { role: 'img', 'aria-label': ariaLabel };
725
+ // Exposed with no name: an explicit opt-out of the default, warned about below.
726
+ if (ariaHidden === false)
727
+ return {};
728
+ return { 'aria-hidden': true };
729
+ }, [ariaHidden, ariaLabel]);
730
+ /**
731
+ * `dataAttributes` is typed to `data-*`, but casting through it was the only
732
+ * way to reach the DOM before `ariaHidden` existed, so it still wins over the
733
+ * computed values — and therefore decides the effective state below.
734
+ */
735
+ const domAttributes = useMemo(() => (Object.assign(Object.assign({}, accessibilityProps), dataAttributes)), [accessibilityProps, dataAttributes]);
736
+ /**
737
+ * lucide-react hides its own `<svg>` unless it receives an a11y prop, and a
738
+ * registry component may do the same, which would keep the graphic out of the
739
+ * tree even when this wrapper is not hidden. Only matters when the icon is
740
+ * exposed without a name: under `role="img"` the wrapper is a leaf, so a
741
+ * hidden child changes nothing.
742
+ *
743
+ * A registry component that hardcodes `aria-hidden` on its own `<svg>` instead
744
+ * of spreading props still wins — nothing here can reach inside it.
745
+ */
746
+ const isExposedWithoutName = useMemo(() => {
747
+ const attributes = domAttributes;
748
+ const hidden = attributes['aria-hidden'];
749
+ return hidden !== true && hidden !== 'true' && !attributes.role;
750
+ }, [domAttributes]);
751
+ /**
752
+ * Diagnostics read the effective state, so a `dataAttributes` override that
753
+ * hides the icon after all is not reported as exposing it. Memoised like the
754
+ * values above, so re-rendering with the same props does not repeat them.
755
+ */
756
+ useMemo(() => {
757
+ if (process.env.NODE_ENV === 'production')
758
+ return;
759
+ if (ariaHidden === true && ariaLabel) {
760
+ // eslint-disable-next-line no-console
761
+ console.warn(`DIcon: ariaLabel "${ariaLabel}" is ignored because ariaHidden is true. Drop ariaHidden to expose the name.`);
762
+ }
763
+ if (ariaHidden === false && isExposedWithoutName) {
764
+ // eslint-disable-next-line no-console
765
+ console.warn('DIcon: ariaHidden={false} without an ariaLabel exposes an unnamed graphic to assistive technology. Pass ariaLabel to name it.');
766
+ }
767
+ }, [ariaHidden, ariaLabel, isExposedWithoutName]);
526
768
  const iconSize = useMemo(() => {
527
769
  if (resolvedSize) {
528
770
  const numSize = parseInt(resolvedSize, 10);
@@ -532,43 +774,46 @@ function DIconBase({ icon, color, style, className, size, useListenerSize = fals
532
774
  }, [resolvedSize]);
533
775
  // Render Material Design icon (legacy support)
534
776
  if (useMaterialIcons) {
535
- return (jsx("i", Object.assign({ className: classNames(generateClasses, familyClass), style: generateStyleVariables }, dataAttributes, { children: isStringIcon ? icon : null })));
777
+ return (jsx("i", Object.assign({ className: classNames(generateClasses, familyClass), style: generateStyleVariables }, domAttributes, { children: isStringIcon ? icon : null })));
536
778
  }
537
779
  if (isIconComponent(icon)) {
538
- return (jsx("span", Object.assign({ className: classNames(generateClasses), style: generateStyleVariables }, dataAttributes, { children: createElement(icon, {
539
- width: resolvedSize || 24,
540
- height: resolvedSize || 24,
541
- strokeWidth,
542
- }) })));
780
+ return (jsx("span", Object.assign({ className: classNames(generateClasses), style: generateStyleVariables }, domAttributes, { children: createElement(icon, Object.assign({ width: resolvedSize || 24, height: resolvedSize || 24, strokeWidth }, isExposedWithoutName && { 'aria-hidden': false })) })));
543
781
  }
544
782
  // Render Lucide icon
545
783
  if (!LucideIcon) {
546
- if (isStringIcon && familyClass && familyPrefix) {
547
- return (jsx("i", Object.assign({ className: classNames(generateClasses, familyClass, `${familyPrefix}${icon}`), style: generateStyleVariables }, dataAttributes)));
784
+ if (typeof icon === 'string' && familyClass && familyPrefix) {
785
+ if (process.env.NODE_ENV !== 'production') {
786
+ warnUnknownIcon(icon, familyClass, familyPrefix);
787
+ }
788
+ return (jsx("i", Object.assign({ className: classNames(generateClasses, familyClass, `${familyPrefix}${icon}`), style: generateStyleVariables }, domAttributes)));
548
789
  }
549
790
  // eslint-disable-next-line no-console
550
791
  console.warn(`Icon "${String(icon)}" not found in Lucide. Make sure to use PascalCase names (e.g., "Home", "User", "Settings")`);
551
- return (jsx("span", Object.assign({ className: classNames(generateClasses), style: generateStyleVariables }, dataAttributes, { children: "?" })));
792
+ return (jsx("span", Object.assign({ className: classNames(generateClasses), style: generateStyleVariables }, domAttributes, { children: "?" })));
552
793
  }
553
- return (jsx("span", Object.assign({ className: classNames(generateClasses), style: generateStyleVariables }, dataAttributes, { children: jsx(LucideIcon, { size: iconSize || 24, strokeWidth: strokeWidth }) })));
794
+ return (jsx("span", Object.assign({ className: classNames(generateClasses), style: generateStyleVariables }, domAttributes, { children: jsx(LucideIcon, Object.assign({ size: iconSize || 24, strokeWidth: strokeWidth }, isExposedWithoutName && { 'aria-hidden': false })) })));
554
795
  }
555
796
 
797
+ /**
798
+ * Thin wrapper over `DIconBase` that fills the icon-font configuration
799
+ * (`familyClass`, `familyPrefix`, `materialStyle`) from `DContextProvider`
800
+ * whenever the caller does not pass it explicitly.
801
+ *
802
+ * Name resolution itself — registry, then lucide-react, then the icon-font
803
+ * family — lives in `DIconBase`, so both components behave identically.
804
+ */
556
805
  function DIcon(_a) {
557
806
  var { icon, familyClass: propFamilyClass, familyPrefix: propFamilyPrefix, materialStyle: propMaterialStyle } = _a, props = __rest(_a, ["icon", "familyClass", "familyPrefix", "materialStyle"]);
558
- const { icon: { familyClass, familyPrefix, materialStyle, }, iconRegistry, } = useDContext();
559
- const registryIcon = typeof icon === 'string'
560
- ? iconRegistry === null || iconRegistry === void 0 ? void 0 : iconRegistry[icon]
561
- : undefined;
562
- const resolvedIcon = registryIcon || icon;
563
- return (jsx(DIconBase, Object.assign({ icon: resolvedIcon, familyClass: propFamilyClass !== null && propFamilyClass !== void 0 ? propFamilyClass : familyClass, familyPrefix: propFamilyPrefix !== null && propFamilyPrefix !== void 0 ? propFamilyPrefix : familyPrefix, materialStyle: propMaterialStyle !== null && propMaterialStyle !== void 0 ? propMaterialStyle : materialStyle }, props)));
807
+ const { icon: { familyClass, familyPrefix, materialStyle, }, } = useDContext();
808
+ return (jsx(DIconBase, Object.assign({ icon: icon, familyClass: propFamilyClass !== null && propFamilyClass !== void 0 ? propFamilyClass : familyClass, familyPrefix: propFamilyPrefix !== null && propFamilyPrefix !== void 0 ? propFamilyPrefix : familyPrefix, materialStyle: propMaterialStyle !== null && propMaterialStyle !== void 0 ? propMaterialStyle : materialStyle }, props)));
564
809
  }
565
810
 
566
- function DAlert({ color = 'success', icon: iconProp, iconFamilyClass, iconFamilyPrefix, iconMaterialStyle, iconClose: iconCloseProp, iconCloseFamilyClass, iconCloseFamilyPrefix, iconCloseMaterialStyle, showClose, onClose, children, id, className, style, dataAttributes, }) {
811
+ function DAlert({ color = 'success', role = 'alert', icon: iconProp, showIcon = true, iconFamilyClass, iconFamilyPrefix, iconMaterialStyle, iconClose: iconCloseProp, iconCloseFamilyClass, iconCloseFamilyPrefix, iconCloseMaterialStyle, showClose, closeAriaLabel = 'Close', onClose, children, id, className, style, dataAttributes, }) {
567
812
  const { icon: { materialStyle, familyClass, familyPrefix, }, iconMap: { alert, xLg, }, } = useDContext();
568
813
  const icon = useMemo(() => iconProp || alert[color], [alert, iconProp, color]);
569
814
  const iconClose = useMemo(() => (iconCloseProp || xLg), [iconCloseProp, xLg]);
570
815
  const generateClasses = useMemo(() => (Object.assign({ alert: true, [`alert-${color}`]: true, 'fade show': !!showClose }, className && { [className]: true })), [color, showClose, className]);
571
- return (jsxs("div", Object.assign({ className: classNames(generateClasses), style: style, role: "alert", id: id }, dataAttributes, { children: [icon && (jsx(DIcon, { className: "alert-icon", icon: icon, familyClass: iconFamilyClass !== null && iconFamilyClass !== void 0 ? iconFamilyClass : familyClass, familyPrefix: iconFamilyPrefix !== null && iconFamilyPrefix !== void 0 ? iconFamilyPrefix : familyPrefix, materialStyle: iconMaterialStyle !== null && iconMaterialStyle !== void 0 ? iconMaterialStyle : materialStyle })), jsx("div", { className: "alert-text", children: children }), showClose && (jsx("button", { type: "button", className: "d-close", "aria-label": "Close", onClick: onClose, children: jsx(DIcon, { icon: iconClose, familyClass: iconCloseFamilyClass !== null && iconCloseFamilyClass !== void 0 ? iconCloseFamilyClass : familyClass, familyPrefix: iconCloseFamilyPrefix !== null && iconCloseFamilyPrefix !== void 0 ? iconCloseFamilyPrefix : familyPrefix, materialStyle: iconCloseMaterialStyle !== null && iconCloseMaterialStyle !== void 0 ? iconCloseMaterialStyle : materialStyle }) }))] })));
816
+ return (jsxs("div", Object.assign({ className: classNames(generateClasses), style: style }, role !== 'none' && { role }, { id: id }, dataAttributes, { children: [showIcon && icon && (jsx(DIcon, { className: "alert-icon", icon: icon, familyClass: iconFamilyClass !== null && iconFamilyClass !== void 0 ? iconFamilyClass : familyClass, familyPrefix: iconFamilyPrefix !== null && iconFamilyPrefix !== void 0 ? iconFamilyPrefix : familyPrefix, materialStyle: iconMaterialStyle !== null && iconMaterialStyle !== void 0 ? iconMaterialStyle : materialStyle })), jsx("div", { className: "alert-text", children: children }), showClose && (jsx("button", { type: "button", className: "d-close", "aria-label": closeAriaLabel, onClick: onClose, children: jsx(DIcon, { icon: iconClose, familyClass: iconCloseFamilyClass !== null && iconCloseFamilyClass !== void 0 ? iconCloseFamilyClass : familyClass, familyPrefix: iconCloseFamilyPrefix !== null && iconCloseFamilyPrefix !== void 0 ? iconCloseFamilyPrefix : familyPrefix, materialStyle: iconCloseMaterialStyle !== null && iconCloseMaterialStyle !== void 0 ? iconCloseMaterialStyle : materialStyle }) }))] })));
572
817
  }
573
818
 
574
819
  function DAvatar({ id, size, image, name: nameProp, useNameAsInitials = false, className, style, dataAttributes, }) {
@@ -634,6 +879,223 @@ function useProvidedRefOrCreate(providedRef) {
634
879
  return providedRef !== null && providedRef !== void 0 ? providedRef : createdRef;
635
880
  }
636
881
 
882
+ /**
883
+ * Descendants the browser already exempts from a label's click forwarding.
884
+ *
885
+ * Per the HTML spec a label does nothing for events targeted at its interactive
886
+ * content descendants, so these need — and must get — no help from us: calling
887
+ * `preventDefault` on a click targeting a link, or on an image map area, would
888
+ * cancel the navigation it was meant to perform.
889
+ *
890
+ * The list mirrors the spec's interactive content, image maps included, since
891
+ * one of those carrying a `tabindex` would otherwise fall through to the
892
+ * pseudo-interactive branch below and be suppressed by mistake.
893
+ */
894
+ const NATIVE_INTERACTIVE = 'a[href], area[href], button, input, select, textarea, details, summary, img[usemap], object[usemap], audio[controls], video[controls], embed, iframe';
895
+ /**
896
+ * Descendants that act like controls without being interactive content.
897
+ *
898
+ * A tooltip or modal trigger built from a `span` with a role and a tabindex is
899
+ * the common case. Browsers do not exempt these, so a click on one activates
900
+ * the labelled control as well.
901
+ */
902
+ const PSEUDO_INTERACTIVE = '[role="button"], [role="link"], [role="checkbox"], [role="switch"], [role="menuitem"], [tabindex]';
903
+ /**
904
+ * Stops a click on a non-native trigger inside a `<label>` from also activating
905
+ * the labelled control.
906
+ *
907
+ * Verified against Chromium, which forwards the click for every one of the
908
+ * `PSEUDO_INTERACTIVE` selectors above while exempting the native ones. Note
909
+ * that jsdom does not reproduce this: its `isInteractiveContent` counts any
910
+ * element carrying a `tabindex` as interactive content, so a jsdom test passes
911
+ * whether or not this guard exists. That is why the guard is unit-tested
912
+ * directly rather than through a rendered component.
913
+ *
914
+ * Native interactive content returns early rather than falling through, since
915
+ * `preventDefault` on those clicks would cancel the link navigation or the
916
+ * image map the label is meant to let through.
917
+ *
918
+ * `DFormLabel` wires this in the capture phase so a trigger that stops
919
+ * propagation cannot skip it.
920
+ */
921
+ function labelClickGuard(event) {
922
+ const label = event.currentTarget;
923
+ const target = event.target;
924
+ if (!(target === null || target === void 0 ? void 0 : target.closest))
925
+ return;
926
+ /**
927
+ * `closest` walks the whole ancestor chain, so a match may sit above the
928
+ * label — a card with a `role` or a `tabindex` wrapping the control and its
929
+ * label, say. Acting on one of those would suppress the plain label text's
930
+ * own click and leave the control unresponsive, so only matches inside the
931
+ * label count.
932
+ */
933
+ const inside = (match) => (!!match && match !== label && label.contains(match));
934
+ if (inside(target.closest(NATIVE_INTERACTIVE)))
935
+ return;
936
+ if (inside(target.closest(PSEUDO_INTERACTIVE))) {
937
+ event.preventDefault();
938
+ }
939
+ }
940
+
941
+ /**
942
+ * The `<label>` every form control renders, with the click guard already wired.
943
+ *
944
+ * Internal: it exists so the guard — and the lint exception it needs — lives in
945
+ * one place instead of being repeated across every input component.
946
+ *
947
+ * The guard runs in the capture phase, not on bubble: a tooltip or modal
948
+ * trigger that calls `stopPropagation()` in its own handler — a common pattern —
949
+ * would otherwise keep the guard from ever running, and the browser would go on
950
+ * to activate the labelled control. Confirmed in Chromium, where the same
951
+ * trigger toggles the control with a bubble-phase guard and does not with this
952
+ * one. Capturing changes nothing else: `preventDefault` applies to the default
953
+ * action, which is evaluated after propagation either way.
954
+ *
955
+ * The `jsx-a11y` rules below assume a click handler is being used to make a
956
+ * non-interactive element interactive, which then owes the keyboard an
957
+ * equivalent. This handler does the opposite: it suppresses a pointer-only
958
+ * behaviour, the click a label forwards to its control, and adds no way to
959
+ * activate anything. Keyboard support for a nested trigger is the consumer's:
960
+ * `<a href>` carries its own, while a `span` with a role and a tabindex is
961
+ * focusable but inert until they handle Enter and Space themselves.
962
+ */
963
+ function DFormLabel({ htmlFor, className, children }) {
964
+ /**
965
+ * React 19 renders a bigint child; React 18 throws on one, and this package
966
+ * supports both (`react: >=18 <20`). The label helpers count bigint as text
967
+ * because that is what it draws as, so it is turned into its string form here
968
+ * — the one place every control's label passes through — rather than pushing
969
+ * a version check onto consumers.
970
+ *
971
+ * Only a bigint at the top level: one buried inside an array or a fragment is
972
+ * the consumer's own React child, and normalising it would mean walking and
973
+ * rebuilding the whole subtree.
974
+ */
975
+ const content = typeof children === 'bigint' ? String(children) : children;
976
+ return (
977
+ /* eslint-disable-next-line jsx-a11y/click-events-have-key-events,
978
+ jsx-a11y/no-noninteractive-element-interactions */
979
+ jsx("label", { htmlFor: htmlFor, className: className, onClickCapture: labelClickGuard, children: content }));
980
+ }
981
+
982
+ /**
983
+ * Whether a `label` node has something to render.
984
+ *
985
+ * The render paths cannot test `label` for truthiness: since the prop widened
986
+ * to `ReactNode` it also accepts `0`, which React renders as the text "0" and
987
+ * which `isTextLabel` counts as a text label, yet which a truthiness check
988
+ * would drop — leaving the control visibly unlabelled.
989
+ *
990
+ * Absent (`undefined`, `null`), empty (`''`) and boolean labels render nothing,
991
+ * so they are the ones that stand for "no label". The empty string matters
992
+ * because it is the default several components fall back to.
993
+ *
994
+ * Arrays are inspected rather than taken at face value: `label={items.map(...)}`
995
+ * over an empty list yields `[]`, while a list of skipped entries yields
996
+ * `[false, null]`. Both render nothing, so treating them as content would leave
997
+ * an empty `<label>` on the page — and make the naming warning fire over a
998
+ * label that was never there.
999
+ *
1000
+ * Only arrays, though, even though `ReactNode` admits any iterable. Reading a
1001
+ * one-shot iterator — a generator — would exhaust it here, and the component
1002
+ * goes on to render the same value, so the label would vanish precisely because
1003
+ * we checked for it. An array can be walked as often as needed; anything else
1004
+ * is taken on faith.
1005
+ *
1006
+ * Anything else — an element, a portal, a generator — counts as content: what
1007
+ * it renders is only knowable by rendering it, and an element that draws
1008
+ * nothing visible is the consumer's decision rather than an absent label.
1009
+ */
1010
+ function hasLabelContent(label) {
1011
+ if (label === undefined || label === null || typeof label === 'boolean') {
1012
+ return false;
1013
+ }
1014
+ if (typeof label === 'string') {
1015
+ return label !== '';
1016
+ }
1017
+ if (isValidElement(label)) {
1018
+ return true;
1019
+ }
1020
+ if (Array.isArray(label)) {
1021
+ return label.some(hasLabelContent);
1022
+ }
1023
+ return true;
1024
+ }
1025
+
1026
+ /**
1027
+ * Narrows a `label` node to the plain-text case.
1028
+ *
1029
+ * `label` accepts any `ReactNode` so patterns like a terms-and-conditions link
1030
+ * or an info trigger can live next to the field name. Anything that is not text
1031
+ * cannot be forwarded to a string-only attribute (`aria-label`, `placeholder`)
1032
+ * nor laid out by Bootstrap's `form-floating`, so those paths check this first.
1033
+ *
1034
+ * Numbers and bigints count as text: React renders them as their string form
1035
+ * and so does `String()`, which is what the string-only attributes end up
1036
+ * receiving. `bigint` is part of `ReactNode` under these React types, so
1037
+ * leaving it out made a label React draws as text get treated as a rich node.
1038
+ * React 18 cannot render one, which `DFormLabel` handles by rendering its
1039
+ * string form — the classification stays true for every supported React.
1040
+ */
1041
+ function isTextLabel(label) {
1042
+ return typeof label === 'string'
1043
+ || typeof label === 'number'
1044
+ || typeof label === 'bigint';
1045
+ }
1046
+
1047
+ /**
1048
+ * Components already reported, keyed by component and reason, so the same
1049
+ * mistake only warns once per page load however often it re-renders or however
1050
+ * many instances exist.
1051
+ */
1052
+ const warnedLabels = new Set();
1053
+ function warnOnce(key, message) {
1054
+ if (warnedLabels.has(key))
1055
+ return;
1056
+ warnedLabels.add(key);
1057
+ // eslint-disable-next-line no-console
1058
+ console.warn(`[Dynamic UI] ${message} It never appears in production builds.`);
1059
+ }
1060
+ /**
1061
+ * Warns, only outside production builds, about the two ways a non-text `label`
1062
+ * goes wrong.
1063
+ *
1064
+ * A text label doubles as the control's accessible name. A `ReactNode` one does
1065
+ * not: the name becomes whatever the subtree happens to compute to, which for a
1066
+ * label carrying a link or an icon trigger reads as the wrong thing or as
1067
+ * nothing at all — so an explicit name is needed. Which prop carries it differs
1068
+ * per component, hence `accessibleNameProp`: naming the wrong one would send
1069
+ * the developer to a prop their component rejects.
1070
+ *
1071
+ * `form-floating` is the second case: it animates a single line of text between
1072
+ * the placeholder and the label position, so a node with its own height or its
1073
+ * own interactive children breaks the layout rather than the semantics.
1074
+ *
1075
+ * Labels the render paths drop are skipped through the same `hasLabelContent`
1076
+ * they use, so `label={condition && <span />}` with a false condition warns
1077
+ * about nothing: there is no label on the page to name.
1078
+ *
1079
+ * The `process.env.NODE_ENV` guard belongs at the call site — that is what
1080
+ * bundlers constant-fold, dropping this module from a consumer's production
1081
+ * bundle.
1082
+ */
1083
+ function warnLabelUsage({ component, label, hasAccessibleName, accessibleNameProp, floatingLabel = false, }) {
1084
+ if (!hasLabelContent(label) || isTextLabel(label))
1085
+ return;
1086
+ if (!hasAccessibleName) {
1087
+ warnOnce(`${component}:name`, `${component}: a non-text "label" does not give the control a reliable accessible name. `
1088
+ + `Pass ${accessibleNameProp} with the plain-text name of the field, since the label `
1089
+ + 'subtree may include links, icons or markup that read as the wrong name or as none at all.');
1090
+ }
1091
+ if (floatingLabel) {
1092
+ warnOnce(`${component}:floating`, `${component}: "floatingLabel" expects a text "label". `
1093
+ + 'The floating layout animates a single line of text, so a node with its own height '
1094
+ + 'or its own interactive children overflows the control. '
1095
+ + 'Use the default label layout for rich labels.');
1096
+ }
1097
+ }
1098
+
637
1099
  function DInput(_a, ref) {
638
1100
  var { id: idProp, style, className, label = '', disabled = false, loading = false, iconFamilyClass, iconFamilyPrefix, iconMaterialStyle, iconStart, iconStartDisabled, iconStartFamilyClass, iconStartFamilyPrefix, iconStartAriaLabel, iconStartTabIndex, iconStartMaterialStyle, iconEnd, iconEndDisabled, iconEndFamilyClass, iconEndFamilyPrefix, iconEndAriaLabel, iconEndTabIndex, iconEndMaterialStyle, hint, size, invalid = false, valid = false, floatingLabel = false, inputStart, inputEnd, value, placeholder = '', dataAttributes, readonly, onChange, onIconStartClick, onIconEndClick } = _a, inputProps = __rest(_a, ["id", "style", "className", "label", "disabled", "loading", "iconFamilyClass", "iconFamilyPrefix", "iconMaterialStyle", "iconStart", "iconStartDisabled", "iconStartFamilyClass", "iconStartFamilyPrefix", "iconStartAriaLabel", "iconStartTabIndex", "iconStartMaterialStyle", "iconEnd", "iconEndDisabled", "iconEndFamilyClass", "iconEndFamilyPrefix", "iconEndAriaLabel", "iconEndTabIndex", "iconEndMaterialStyle", "hint", "size", "invalid", "valid", "floatingLabel", "inputStart", "inputEnd", "value", "placeholder", "dataAttributes", "readonly", "onChange", "onIconStartClick", "onIconEndClick"]);
639
1101
  const inputRef = useProvidedRefOrCreate(ref);
@@ -673,7 +1135,7 @@ function DInput(_a, ref) {
673
1135
  const inputComponent = useMemo(() => (jsx("input", Object.assign({ ref: inputRef, id: id, className: classNames('form-control', {
674
1136
  'is-invalid': invalid,
675
1137
  'is-valid': valid,
676
- }), disabled: disabled || loading, readOnly: readonly, value: value, onChange: handleOnChange }, (floatingLabel || placeholder) && { placeholder: floatingLabel ? '' : placeholder }, ariaDescribedby && { 'aria-describedby': ariaDescribedby }, inputProps))), [
1138
+ }), disabled: disabled || loading, readOnly: readonly, value: value, onChange: handleOnChange }, (floatingLabel || placeholder) && { placeholder: floatingLabel ? '' : placeholder }, ariaDescribedby && { 'aria-describedby': ariaDescribedby }, invalid && { 'aria-invalid': true }, inputProps))), [
677
1139
  ariaDescribedby,
678
1140
  disabled,
679
1141
  handleOnChange,
@@ -688,7 +1150,7 @@ function DInput(_a, ref) {
688
1150
  value,
689
1151
  readonly,
690
1152
  ]);
691
- const labelComponent = useMemo(() => (jsx("label", { htmlFor: id, children: label })), [
1153
+ const labelComponent = useMemo(() => (jsx(DFormLabel, { htmlFor: id, children: label })), [
692
1154
  id,
693
1155
  label,
694
1156
  ]);
@@ -698,11 +1160,20 @@ function DInput(_a, ref) {
698
1160
  }
699
1161
  return inputComponent;
700
1162
  }, [floatingLabel, inputComponent, labelComponent]);
701
- return (jsxs("div", Object.assign({ className: className, style: style }, dataAttributes, { children: [label && !floatingLabel && labelComponent, jsxs("div", { className: classNames({
1163
+ if (process.env.NODE_ENV !== 'production') {
1164
+ warnLabelUsage({
1165
+ component: 'DInput',
1166
+ label,
1167
+ hasAccessibleName: !!inputProps['aria-label'] || !!inputProps['aria-labelledby'],
1168
+ accessibleNameProp: 'aria-label',
1169
+ floatingLabel,
1170
+ });
1171
+ }
1172
+ return (jsxs("div", Object.assign({ className: className, style: style }, dataAttributes, { children: [hasLabelContent(label) && !floatingLabel && labelComponent, jsxs("div", { className: classNames({
702
1173
  [`input-group-${size}`]: !!size,
703
1174
  'input-group': true,
704
1175
  'has-validation': invalid || valid,
705
- }), children: [!!inputStart && (jsx("div", { className: "input-group-text", id: `${id}InputStart`, children: inputStart })), iconStart && (onIconStartClick ? (jsx("button", { type: "button", className: "input-group-text", id: `${id}Start`, onClick: handleOnIconStartClick, disabled: disabled || loading || iconStartDisabled, "aria-label": iconStartAriaLabel || (typeof iconStart === 'string' ? iconStart : 'start icon'), tabIndex: iconStartTabIndex, children: jsx(DIcon, { icon: iconStart, familyClass: iconStartFamilyClass !== null && iconStartFamilyClass !== void 0 ? iconStartFamilyClass : familyClass, familyPrefix: iconStartFamilyPrefix !== null && iconStartFamilyPrefix !== void 0 ? iconStartFamilyPrefix : familyPrefix, materialStyle: iconStartMaterialStyle !== null && iconStartMaterialStyle !== void 0 ? iconStartMaterialStyle : materialStyle }) })) : (jsx("div", { className: "input-group-text", id: `${id}Start`, "aria-hidden": "true", tabIndex: -1, children: jsx(DIcon, { icon: iconStart, familyClass: iconStartFamilyClass !== null && iconStartFamilyClass !== void 0 ? iconStartFamilyClass : familyClass, familyPrefix: iconStartFamilyPrefix !== null && iconStartFamilyPrefix !== void 0 ? iconStartFamilyPrefix : familyPrefix, materialStyle: iconStartMaterialStyle !== null && iconStartMaterialStyle !== void 0 ? iconStartMaterialStyle : materialStyle }) }))), dynamicComponent, (iconEnd && !loading) && (onIconEndClick ? (jsx("button", { type: "button", className: "input-group-text", id: `${id}End`, onClick: handleOnIconEndClick, disabled: disabled || loading || iconEndDisabled, "aria-label": iconEndAriaLabel || (typeof iconEnd === 'string' ? iconEnd : 'end icon'), tabIndex: iconEndTabIndex, children: jsx(DIcon, { icon: iconEnd, familyClass: iconEndFamilyClass !== null && iconEndFamilyClass !== void 0 ? iconEndFamilyClass : familyClass, familyPrefix: iconEndFamilyPrefix !== null && iconEndFamilyPrefix !== void 0 ? iconEndFamilyPrefix : familyPrefix, materialStyle: iconEndMaterialStyle !== null && iconEndMaterialStyle !== void 0 ? iconEndMaterialStyle : materialStyle }) })) : (jsx("div", { className: "input-group-text", id: `${id}End`, "aria-hidden": "true", tabIndex: -1, children: jsx(DIcon, { icon: iconEnd, familyClass: iconEndFamilyClass !== null && iconEndFamilyClass !== void 0 ? iconEndFamilyClass : familyClass, familyPrefix: iconEndFamilyPrefix !== null && iconEndFamilyPrefix !== void 0 ? iconEndFamilyPrefix : familyPrefix, materialStyle: iconEndMaterialStyle !== null && iconEndMaterialStyle !== void 0 ? iconEndMaterialStyle : materialStyle }) }))), loading && (jsx("div", { className: "input-group-text", id: `${id}Loading`, children: jsx("span", { className: "spinner-border spinner-border-sm", role: "status", "aria-hidden": "true", "data-testid": "loading-spinner", children: jsx("span", { className: "visually-hidden", children: "Loading..." }) }) })), !!inputEnd && (jsx("div", { className: "input-group-text", id: `${id}InputEnd`, children: inputEnd }))] }), hint && (jsx("div", { className: "form-text", id: `${id}Hint`, children: hint }))] })));
1176
+ }), children: [!!inputStart && (jsx("div", { className: "input-group-text", id: `${id}InputStart`, children: inputStart })), iconStart && (onIconStartClick ? (jsx("button", { type: "button", className: "input-group-text", id: `${id}Start`, onClick: handleOnIconStartClick, disabled: disabled || loading || iconStartDisabled, "aria-label": iconStartAriaLabel || (typeof iconStart === 'string' ? iconStart : 'start icon'), tabIndex: iconStartTabIndex, children: jsx(DIcon, { icon: iconStart, familyClass: iconStartFamilyClass !== null && iconStartFamilyClass !== void 0 ? iconStartFamilyClass : familyClass, familyPrefix: iconStartFamilyPrefix !== null && iconStartFamilyPrefix !== void 0 ? iconStartFamilyPrefix : familyPrefix, materialStyle: iconStartMaterialStyle !== null && iconStartMaterialStyle !== void 0 ? iconStartMaterialStyle : materialStyle }) })) : (jsx("div", { className: "input-group-text", id: `${id}Start`, tabIndex: -1, children: jsx(DIcon, { icon: iconStart, familyClass: iconStartFamilyClass !== null && iconStartFamilyClass !== void 0 ? iconStartFamilyClass : familyClass, familyPrefix: iconStartFamilyPrefix !== null && iconStartFamilyPrefix !== void 0 ? iconStartFamilyPrefix : familyPrefix, materialStyle: iconStartMaterialStyle !== null && iconStartMaterialStyle !== void 0 ? iconStartMaterialStyle : materialStyle }) }))), dynamicComponent, (iconEnd && !loading) && (onIconEndClick ? (jsx("button", { type: "button", className: "input-group-text", id: `${id}End`, onClick: handleOnIconEndClick, disabled: disabled || loading || iconEndDisabled, "aria-label": iconEndAriaLabel || (typeof iconEnd === 'string' ? iconEnd : 'end icon'), tabIndex: iconEndTabIndex, children: jsx(DIcon, { icon: iconEnd, familyClass: iconEndFamilyClass !== null && iconEndFamilyClass !== void 0 ? iconEndFamilyClass : familyClass, familyPrefix: iconEndFamilyPrefix !== null && iconEndFamilyPrefix !== void 0 ? iconEndFamilyPrefix : familyPrefix, materialStyle: iconEndMaterialStyle !== null && iconEndMaterialStyle !== void 0 ? iconEndMaterialStyle : materialStyle }) })) : (jsx("div", { className: "input-group-text", id: `${id}End`, tabIndex: -1, children: jsx(DIcon, { icon: iconEnd, familyClass: iconEndFamilyClass !== null && iconEndFamilyClass !== void 0 ? iconEndFamilyClass : familyClass, familyPrefix: iconEndFamilyPrefix !== null && iconEndFamilyPrefix !== void 0 ? iconEndFamilyPrefix : familyPrefix, materialStyle: iconEndMaterialStyle !== null && iconEndMaterialStyle !== void 0 ? iconEndMaterialStyle : materialStyle }) }))), loading && (jsx("div", { className: "input-group-text", id: `${id}Loading`, children: jsx("span", { className: "spinner-border spinner-border-sm", role: "status", "aria-hidden": "true", "data-testid": "loading-spinner", children: jsx("span", { className: "visually-hidden", children: "Loading..." }) }) })), !!inputEnd && (jsx("div", { className: "input-group-text", id: `${id}InputEnd`, children: inputEnd }))] }), hint && (jsx("div", { className: "form-text", id: `${id}Hint`, children: hint }))] })));
706
1177
  }
707
1178
  const ForwardedDInput = forwardRef(DInput);
708
1179
  ForwardedDInput.displayName = 'DInput';
@@ -1185,8 +1656,46 @@ const DButton = forwardRef((props, ref) => {
1185
1656
  });
1186
1657
  DButton.displayName = 'DButton';
1187
1658
 
1659
+ /**
1660
+ * Controls already reported, keyed by kind and icon, so the same unlabelled one
1661
+ * only warns once per page load however often it re-renders or however many
1662
+ * instances exist.
1663
+ */
1664
+ const warnedControls = new Set();
1665
+ /**
1666
+ * Warns, once per icon and only outside production builds, that an icon-only
1667
+ * button has no accessible name.
1668
+ *
1669
+ * The control has no text to fall back on and its icon is hidden from the
1670
+ * accessibility tree, so the name has to be given explicitly. `aria-label` is
1671
+ * the direct way; on a button `aria-labelledby` and `title` count too, since
1672
+ * `rest` reaches the DOM there, while an anchor drops them — hence `kind`, so
1673
+ * the advice matches what the element can actually receive.
1674
+ *
1675
+ * A legacy icon-font icon used to leak its ligature as the name by accident —
1676
+ * never a good one, being unlocalised and often meaningless ("more_vert") — and
1677
+ * no longer does, so the omission is now visible instead of silent.
1678
+ *
1679
+ * The `process.env.NODE_ENV` guard is what bundlers constant-fold, so the whole
1680
+ * call — and this module — drops out of a consumer's production bundle.
1681
+ */
1682
+ function warnMissingAccessibleName(icon, kind) {
1683
+ const key = `${kind}:${icon}`;
1684
+ if (warnedControls.has(key))
1685
+ return;
1686
+ warnedControls.add(key);
1687
+ const alternatives = kind === 'button'
1688
+ ? 'On a button, aria-labelledby or title work too. '
1689
+ : 'A link renders from the href branch, which does not forward aria-labelledby or title, so aria-label is the only option. ';
1690
+ // eslint-disable-next-line no-console
1691
+ console.warn(`[Dynamic UI] DButtonIcon: the "${icon}" ${kind} has no accessible name. `
1692
+ + 'Pass aria-label, since an icon-only control has no text to fall back on '
1693
+ + `and its icon is hidden from the accessibility tree. ${alternatives}`
1694
+ + 'It never appears in production builds.');
1695
+ }
1696
+
1188
1697
  function DButtonIcon(_a) {
1189
- var { id, icon, size, className, variant, state, loadingAriaLabel, iconMaterialStyle, disabled = false, color = 'primary', loading = false, href, target, rel, stopPropagationEnabled = true, style, iconFamilyClass, iconFamilyPrefix, dataAttributes, onClick, 'aria-label': ariaLabelProp } = _a, rest = __rest(_a, ["id", "icon", "size", "className", "variant", "state", "loadingAriaLabel", "iconMaterialStyle", "disabled", "color", "loading", "href", "target", "rel", "stopPropagationEnabled", "style", "iconFamilyClass", "iconFamilyPrefix", "dataAttributes", "onClick", 'aria-label']);
1698
+ var { id, icon, size, iconSize, className, variant, state, loadingAriaLabel, iconMaterialStyle, disabled = false, color = 'primary', loading = false, href, target, rel, stopPropagationEnabled = true, style, iconFamilyClass, iconFamilyPrefix, dataAttributes, onClick, 'aria-label': ariaLabelProp } = _a, rest = __rest(_a, ["id", "icon", "size", "iconSize", "className", "variant", "state", "loadingAriaLabel", "iconMaterialStyle", "disabled", "color", "loading", "href", "target", "rel", "stopPropagationEnabled", "style", "iconFamilyClass", "iconFamilyPrefix", "dataAttributes", "onClick", 'aria-label']);
1190
1699
  const { icon: { familyClass, familyPrefix, materialStyle, }, } = useDContext();
1191
1700
  const generateClasses = useMemo(() => {
1192
1701
  const variantClass = !variant || variant === 'solid'
@@ -1194,6 +1703,9 @@ function DButtonIcon(_a) {
1194
1703
  : `btn-${variant}-${color}`;
1195
1704
  return Object.assign(Object.assign(Object.assign({ 'btn d-button-icon': true, [variantClass]: true }, size && { [`btn-${size}`]: true }), (state && state !== 'disabled') && { [state]: true }), { loading });
1196
1705
  }, [variant, color, size, state, loading]);
1706
+ // A responsive iconSize has to follow viewport changes, so DIcon only
1707
+ // listens to breakpoints when it gets an object; a plain string needs none.
1708
+ const useIconSizeListener = typeof iconSize === 'object';
1197
1709
  const isDisabled = useMemo(() => (state === 'disabled' || loading || disabled), [state, loading, disabled]);
1198
1710
  const clickHandler = useCallback((event) => {
1199
1711
  if (stopPropagationEnabled) {
@@ -1208,14 +1720,23 @@ function DButtonIcon(_a) {
1208
1720
  const ariaLabel = useMemo(() => (loading
1209
1721
  ? loadingAriaLabel || ariaLabelProp
1210
1722
  : ariaLabelProp), [loading, loadingAriaLabel, ariaLabelProp]);
1723
+ /**
1724
+ * `aria-labelledby` and `title` also name a control, but they travel in `rest`,
1725
+ * which only the button branch spreads — an anchor drops them, so there the
1726
+ * button really is unnamed and the warning still applies.
1727
+ */
1728
+ const hasAccessibleName = useMemo(() => !!ariaLabel || (!href && (!!rest['aria-labelledby'] || !!rest.title)), [ariaLabel, href, rest]);
1729
+ if (process.env.NODE_ENV !== 'production' && !hasAccessibleName) {
1730
+ warnMissingAccessibleName(icon, href ? 'link' : 'button');
1731
+ }
1211
1732
  if (href) {
1212
1733
  return (jsx("a", Object.assign({ id: id, href: href, target: target, rel: rel, className: classNames(generateClasses, className), style: style, onClick: clickHandler, "aria-label": ariaLabel, "aria-disabled": isDisabled }, dataAttributes, { children: loading
1213
1734
  ? (jsx("span", { className: "spinner-border spinner-border-sm", role: "status", "aria-hidden": "true", children: jsx("span", { className: "visually-hidden", children: "Loading..." }) }))
1214
- : (jsx(DIcon, { icon: icon, familyClass: iconFamilyClass !== null && iconFamilyClass !== void 0 ? iconFamilyClass : familyClass, familyPrefix: iconFamilyPrefix !== null && iconFamilyPrefix !== void 0 ? iconFamilyPrefix : familyPrefix, materialStyle: iconMaterialStyle !== null && iconMaterialStyle !== void 0 ? iconMaterialStyle : materialStyle })) })));
1735
+ : (jsx(DIcon, { icon: icon, size: iconSize, useListenerSize: useIconSizeListener, familyClass: iconFamilyClass !== null && iconFamilyClass !== void 0 ? iconFamilyClass : familyClass, familyPrefix: iconFamilyPrefix !== null && iconFamilyPrefix !== void 0 ? iconFamilyPrefix : familyPrefix, materialStyle: iconMaterialStyle !== null && iconMaterialStyle !== void 0 ? iconMaterialStyle : materialStyle })) })));
1215
1736
  }
1216
1737
  return (jsx("button", Object.assign({ className: classNames(generateClasses, className), style: style, disabled: state === 'disabled' || loading, onClick: clickHandler, "aria-label": ariaLabel }, dataAttributes, rest, { children: loading
1217
1738
  ? (jsx("span", { className: "spinner-border spinner-border-sm", role: "status", "aria-hidden": "true", children: jsx("span", { className: "visually-hidden", children: "Loading..." }) }))
1218
- : (jsx(DIcon, { icon: icon, familyClass: iconFamilyClass !== null && iconFamilyClass !== void 0 ? iconFamilyClass : familyClass, familyPrefix: iconFamilyPrefix !== null && iconFamilyPrefix !== void 0 ? iconFamilyPrefix : familyPrefix, materialStyle: iconMaterialStyle !== null && iconMaterialStyle !== void 0 ? iconMaterialStyle : materialStyle })) })));
1739
+ : (jsx(DIcon, { icon: icon, size: iconSize, useListenerSize: useIconSizeListener, familyClass: iconFamilyClass !== null && iconFamilyClass !== void 0 ? iconFamilyClass : familyClass, familyPrefix: iconFamilyPrefix !== null && iconFamilyPrefix !== void 0 ? iconFamilyPrefix : familyPrefix, materialStyle: iconMaterialStyle !== null && iconMaterialStyle !== void 0 ? iconMaterialStyle : materialStyle })) })));
1219
1740
  }
1220
1741
 
1221
1742
  function DCardHeader({ className, style, children, }) {
@@ -1411,24 +1932,102 @@ function DSelectPlaceholder(_a) {
1411
1932
  return (jsx(components.Placeholder, Object.assign({ innerProps: innerProps, selectProps: selectProps }, props, { children: children })));
1412
1933
  }
1413
1934
 
1935
+ /**
1936
+ * Name of the elements an `aria-labelledby` points at, joined as the accessible
1937
+ * name computation does. Read when the message is built rather than when the
1938
+ * select renders, since the referenced elements may render after it.
1939
+ *
1940
+ * Each element contributes its own `aria-label` when it has one, and its text
1941
+ * otherwise. That covers the targets a select is labelled by in practice — a
1942
+ * heading, a span, a labelled region — without shipping the full accessible
1943
+ * name algorithm; names derived from descendants' `aria-label`, `alt` or
1944
+ * hidden content are not resolved, so pass `ariaLiveMessages` for those.
1945
+ */
1946
+ function resolveLabelledBy(labelledBy) {
1947
+ if (typeof document === 'undefined')
1948
+ return '';
1949
+ return labelledBy
1950
+ .split(/\s+/)
1951
+ .map((ref) => {
1952
+ var _a, _b;
1953
+ const element = document.getElementById(ref);
1954
+ return ((_a = element === null || element === void 0 ? void 0 : element.getAttribute('aria-label')) === null || _a === void 0 ? void 0 : _a.trim()) || ((_b = element === null || element === void 0 ? void 0 : element.textContent) === null || _b === void 0 ? void 0 : _b.trim());
1955
+ })
1956
+ .filter(Boolean)
1957
+ .join(' ');
1958
+ }
1959
+ /**
1960
+ * Builds react-select's `guidance` live message around the control's name.
1961
+ *
1962
+ * react-select announces "<aria-label> is focused" on the first focus of the
1963
+ * input, falling back to "Select" when there is no `aria-label`. Neither a text
1964
+ * `label` nor `aria-labelledby` reaches that message, so the announcement would
1965
+ * disagree with the name assistive technology exposes.
1966
+ *
1967
+ * The name follows the accessible name precedence: `aria-labelledby`, then
1968
+ * `aria-label`, then the `<label>`. react-select does not export its default
1969
+ * messages, so this mirrors them for every context and only swaps the name.
1970
+ */
1971
+ function createAriaGuidance({ labelledBy, label }) {
1972
+ return ({ 'aria-label': ariaLabel, context, isSearchable, isMulti, tabSelectsValue, isInitialFocus, }) => {
1973
+ switch (context) {
1974
+ case 'menu':
1975
+ return `Use Up and Down to choose options, press Enter to select the currently focused option, press Escape to exit the menu${tabSelectsValue ? ', press Tab to select the option and exit the menu' : ''}.`;
1976
+ case 'input': {
1977
+ if (!isInitialFocus)
1978
+ return '';
1979
+ const name = (labelledBy && resolveLabelledBy(labelledBy)) || ariaLabel || label || 'Select';
1980
+ return `${name} is focused ${isSearchable ? ',type to refine list' : ''}, press Down to open the menu, ${isMulti ? ' press left to focus selected values' : ''}`;
1981
+ }
1982
+ case 'value':
1983
+ return 'Use left and right to toggle between focused values, press Backspace to remove the currently focused value';
1984
+ default:
1985
+ return '';
1986
+ }
1987
+ };
1988
+ }
1989
+
1414
1990
  function DSelect(_a) {
1415
- var { id: idProp, className, style, label, hint, iconFamilyClass, iconFamilyPrefix, iconStart, iconStartFamilyClass, iconStartFamilyPrefix, iconStartAriaLabel, iconStartTabIndex, iconEnd, iconEndFamilyClass, iconEndFamilyPrefix, iconEndAriaLabel, iconEndTabIndex, invalid, valid, menuWithMaxContent = false, disabled, clearable, loading, floatingLabel = false, rtl, searchable, multi, components, defaultValue, placeholder, onIconStartClick, onIconEndClick, dataAttributes, ariaLabel = 'Search for an option' } = _a, props = __rest(_a, ["id", "className", "style", "label", "hint", "iconFamilyClass", "iconFamilyPrefix", "iconStart", "iconStartFamilyClass", "iconStartFamilyPrefix", "iconStartAriaLabel", "iconStartTabIndex", "iconEnd", "iconEndFamilyClass", "iconEndFamilyPrefix", "iconEndAriaLabel", "iconEndTabIndex", "invalid", "valid", "menuWithMaxContent", "disabled", "clearable", "loading", "floatingLabel", "rtl", "searchable", "multi", "components", "defaultValue", "placeholder", "onIconStartClick", "onIconEndClick", "dataAttributes", "ariaLabel"]);
1991
+ var { id: idProp, inputId: inputIdProp, className, style, label, hint, iconFamilyClass, iconFamilyPrefix, iconStart, iconStartFamilyClass, iconStartFamilyPrefix, iconStartAriaLabel, iconStartTabIndex, iconEnd, iconEndFamilyClass, iconEndFamilyPrefix, iconEndAriaLabel, iconEndTabIndex, invalid, valid, menuWithMaxContent = false, disabled, clearable, loading, floatingLabel = false, rtl, searchable, multi, components, defaultValue, placeholder, onIconStartClick, onIconEndClick, dataAttributes, ariaLabel, ariaLiveMessages } = _a, props = __rest(_a, ["id", "inputId", "className", "style", "label", "hint", "iconFamilyClass", "iconFamilyPrefix", "iconStart", "iconStartFamilyClass", "iconStartFamilyPrefix", "iconStartAriaLabel", "iconStartTabIndex", "iconEnd", "iconEndFamilyClass", "iconEndFamilyPrefix", "iconEndAriaLabel", "iconEndTabIndex", "invalid", "valid", "menuWithMaxContent", "disabled", "clearable", "loading", "floatingLabel", "rtl", "searchable", "multi", "components", "defaultValue", "placeholder", "onIconStartClick", "onIconEndClick", "dataAttributes", "ariaLabel", "ariaLiveMessages"]);
1416
1992
  const innerId = useId();
1417
1993
  const id = useMemo(() => idProp || innerId, [idProp, innerId]);
1994
+ // The `<label>` must point at the element react-select renders the input
1995
+ // with, so an explicit `inputId` wins over `id` for both.
1996
+ const inputId = inputIdProp || id;
1997
+ // A text label or `aria-labelledby` names the control without going through
1998
+ // `aria-label`, which is all react-select's focus announcement reads, so it
1999
+ // is handed those names explicitly. Messages passed by the consumer still
2000
+ // take precedence.
2001
+ const textLabel = hasLabelContent(label) && isTextLabel(label) ? String(label) : undefined;
2002
+ const labelledBy = props['aria-labelledby'];
2003
+ const liveMessages = useMemo(() => (textLabel || labelledBy
2004
+ ? Object.assign({ guidance: createAriaGuidance({ labelledBy, label: textLabel }) }, ariaLiveMessages) : ariaLiveMessages), [textLabel, labelledBy, ariaLiveMessages]);
1418
2005
  const handleOnIconStartClick = useCallback(() => {
1419
2006
  onIconStartClick === null || onIconStartClick === void 0 ? void 0 : onIconStartClick(defaultValue);
1420
2007
  }, [onIconStartClick, defaultValue]);
1421
2008
  const handleOnIconEndClick = useCallback(() => {
1422
2009
  onIconEndClick === null || onIconEndClick === void 0 ? void 0 : onIconEndClick(defaultValue);
1423
2010
  }, [onIconEndClick, defaultValue]);
2011
+ if (process.env.NODE_ENV !== 'production') {
2012
+ warnLabelUsage({
2013
+ component: 'DSelect',
2014
+ label,
2015
+ // `{...props}` is spread after `ariaLabel`, so a native `aria-label` in it
2016
+ // is the one that reaches the input, even when it is `undefined`.
2017
+ hasAccessibleName: !!('aria-label' in props ? props['aria-label'] : ariaLabel)
2018
+ || !!labelledBy,
2019
+ accessibleNameProp: 'ariaLabel',
2020
+ floatingLabel,
2021
+ });
2022
+ }
1424
2023
  return (jsxs("div", Object.assign({ className: classNames('d-select', className, {
1425
2024
  'd-select-floating': floatingLabel,
1426
2025
  disabled: disabled || loading,
1427
- }), style: style }, dataAttributes, { children: [label && (jsx("label", { htmlFor: id, children: label })), jsxs("div", { className: classNames({
2026
+ }), style: style }, dataAttributes, { children: [hasLabelContent(label) && (jsx(DFormLabel, { htmlFor: inputId, children: label })), jsxs("div", { className: classNames({
1428
2027
  'input-group': true,
1429
2028
  'has-validation': invalid,
1430
2029
  disabled: disabled || loading,
1431
- }), children: [iconStart && (jsx("button", { type: "button", className: "input-group-text", id: `${id}Start`, onClick: handleOnIconStartClick, disabled: disabled || loading, "aria-label": iconStartAriaLabel, tabIndex: iconStartTabIndex, children: jsx(DIcon, { icon: iconStart, familyClass: iconStartFamilyClass, familyPrefix: iconStartFamilyPrefix }) })), jsx(Select, Object.assign({ id: `${id}Container`, inputId: id, "aria-label": ariaLabel, styles: {
2030
+ }), children: [iconStart && (jsx("button", { type: "button", className: "input-group-text", id: `${id}Start`, onClick: handleOnIconStartClick, disabled: disabled || loading, "aria-label": iconStartAriaLabel, tabIndex: iconStartTabIndex, children: jsx(DIcon, { icon: iconStart, familyClass: iconStartFamilyClass, familyPrefix: iconStartFamilyPrefix }) })), jsx(Select, Object.assign({ id: `${id}Container`, inputId: inputId, "aria-label": ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : (hasLabelContent(label) || props['aria-labelledby'] ? undefined : 'Search for an option'), ariaLiveMessages: liveMessages, styles: {
1432
2031
  control: (base) => (Object.assign(Object.assign({}, base), { minHeight: 'unset' })),
1433
2032
  container: (base) => (Object.assign(Object.assign({}, base), { flex: 1 })),
1434
2033
  menu: (base) => (Object.assign(Object.assign({}, base), { width: menuWithMaxContent ? 'max-context' : '100%', zIndex: 1000 })),
@@ -1623,10 +2222,40 @@ function formatValue(value, currencyOptions) {
1623
2222
  }
1624
2223
  return currency(value, Object.assign(Object.assign({}, currencyOptions), { symbol: '' })).format();
1625
2224
  }
1626
- function useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref, minValue, maxValue) {
2225
+ /**
2226
+ * State and handlers for a currency input: a formatted string while the field
2227
+ * is idle and the raw number while it is being edited.
2228
+ *
2229
+ * `minValue`/`maxValue` bound the value. With `clamp` (default `true`) an
2230
+ * out-of-range value is brought into range whenever it is not being typed:
2231
+ * on mount, when `value` or the bounds change, and on blur. Every time the
2232
+ * clamp changes the value, `onChange` receives the clamped number, so the
2233
+ * input and the consumer's state never disagree. While the field is focused
2234
+ * the typed number is reported as is, so a partial entry is not rewritten.
2235
+ *
2236
+ * With `clamp: false` the value is never changed: the bounds only feed
2237
+ * `isOverMax` / `isUnderMin`, so the consumer can show its own message (e.g.
2238
+ * "You exceeded the limit") and keep the entered amount.
2239
+ *
2240
+ * Controlled usage follows the same contract as a native controlled input:
2241
+ * reflect `onChange` into `value` in the same event (`setState` in the
2242
+ * handler). The hook keeps showing `value` whenever it isn't being edited, so
2243
+ * a consumer can reject a change by keeping `value` as it was. Deferring the
2244
+ * update (a timer, awaiting a request) makes the hook see a stale `value` in
2245
+ * between: the field can flash the previous amount and a clamp can be
2246
+ * reported again when the deferred value arrives.
2247
+ */
2248
+ function useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref, minValue, maxValue, clamp = true) {
1627
2249
  const inputRef = useProvidedRefOrCreate(ref);
2250
+ const onChangeRef = useRef(onChange);
2251
+ onChangeRef.current = onChange;
2252
+ // Last clamp reported through `onChange`, as the value it came from and the
2253
+ // value it produced. StrictMode replays effects, and a consumer may reflect
2254
+ // `onChange` asynchronously, both before `value` catches up: the same clamp
2255
+ // must not be reported twice.
2256
+ const lastReportedClampRef = useRef(null);
1628
2257
  const clampValue = useCallback((newValue) => {
1629
- if (newValue === undefined) {
2258
+ if (newValue === undefined || !clamp) {
1630
2259
  return newValue;
1631
2260
  }
1632
2261
  let clampedValue = newValue;
@@ -1637,10 +2266,9 @@ function useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref
1637
2266
  clampedValue = Math.min(clampedValue, maxValue);
1638
2267
  }
1639
2268
  return clampedValue;
1640
- }, [minValue, maxValue]);
2269
+ }, [minValue, maxValue, clamp]);
1641
2270
  const [innerType, setInnerType] = useState('text');
1642
- const [innerNumber, setInnerNumber] = useState(clampValue(value));
1643
- const [innerString, setInnerString] = useState(formatValue(clampValue(value), currencyOptions));
2271
+ const [innerNumber, setInnerNumber] = useState(() => clampValue(value));
1644
2272
  const handleOnFocus = useCallback((event) => {
1645
2273
  event.stopPropagation();
1646
2274
  setInnerType('number');
@@ -1652,35 +2280,60 @@ function useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref
1652
2280
  const clampedNumber = clampValue(innerNumber);
1653
2281
  if (clampedNumber !== innerNumber) {
1654
2282
  setInnerNumber(clampedNumber);
1655
- setInnerString(formatValue(clampedNumber, currencyOptions));
2283
+ // Recorded before notifying, so the sync effect that runs when editing
2284
+ // ends doesn't report the same clamp again while `value` still holds the
2285
+ // typed number.
2286
+ lastReportedClampRef.current = { from: value, to: clampedNumber };
1656
2287
  onChange === null || onChange === void 0 ? void 0 : onChange(clampedNumber);
1657
2288
  }
1658
2289
  onBlur === null || onBlur === void 0 ? void 0 : onBlur(event);
1659
- }, [onBlur, innerNumber, clampValue, currencyOptions, onChange]);
2290
+ }, [onBlur, innerNumber, clampValue, onChange, value]);
1660
2291
  const handleOnChange = useCallback((newValue) => {
1661
2292
  const newNumber = (newValue === undefined || newValue === '') ? undefined : Number(newValue);
1662
2293
  if (newNumber !== innerNumber) {
1663
2294
  setInnerNumber(newNumber);
1664
- setInnerString(formatValue(newNumber, currencyOptions));
1665
2295
  onChange === null || onChange === void 0 ? void 0 : onChange(newNumber);
1666
2296
  }
1667
- }, [currencyOptions, onChange, innerNumber]);
1668
- const isMountedRef = useRef(false);
2297
+ }, [onChange, innerNumber]);
2298
+ const isEditing = innerType === 'number';
2299
+ // Keep the inner value in sync with `value` and the bounds. While the user
2300
+ // is typing, the consumer echoes the typed number back through `value`, so
2301
+ // it is taken as is; otherwise it is clamped, and a clamp that changes it is
2302
+ // reported through `onChange`.
1669
2303
  useEffect(() => {
1670
- if (!isMountedRef.current) {
1671
- isMountedRef.current = true;
1672
- return;
2304
+ var _a;
2305
+ const nextNumber = isEditing ? value : clampValue(value);
2306
+ if (nextNumber !== innerNumber) {
2307
+ setInnerNumber(nextNumber);
2308
+ }
2309
+ if (nextNumber !== value) {
2310
+ const last = lastReportedClampRef.current;
2311
+ if (!last || last.from !== value || last.to !== nextNumber) {
2312
+ lastReportedClampRef.current = { from: value, to: nextNumber };
2313
+ (_a = onChangeRef.current) === null || _a === void 0 ? void 0 : _a.call(onChangeRef, nextNumber);
2314
+ }
1673
2315
  }
1674
- if (value !== innerNumber) {
1675
- setInnerNumber(value);
1676
- setInnerString(formatValue(value, currencyOptions));
2316
+ else {
2317
+ lastReportedClampRef.current = null;
1677
2318
  }
1678
- }, [value, currencyOptions, innerNumber]);
1679
- const innerValue = useMemo(() => { var _a; return (innerType === 'number' ? (_a = innerNumber === null || innerNumber === void 0 ? void 0 : innerNumber.toString()) !== null && _a !== void 0 ? _a : '' : innerString !== null && innerString !== void 0 ? innerString : ''); }, [innerType, innerNumber, innerString]);
2319
+ // `innerNumber` is read, not tracked: the effect reacts to the consumer's
2320
+ // value and to the bounds, not to its own updates.
2321
+ // eslint-disable-next-line react-hooks/exhaustive-deps
2322
+ }, [value, clampValue, isEditing]);
2323
+ // Derived, not stored: the formatted text always matches the current number
2324
+ // and options, even when both change in the same render. It depends on the
2325
+ // options object itself, so a new custom `format` function is honored too;
2326
+ // formatting is cheap and doesn't feed the synchronization effect.
2327
+ const innerString = useMemo(() => formatValue(innerNumber, currencyOptions), [innerNumber, currencyOptions]);
2328
+ const innerValue = useMemo(() => { var _a; return (innerType === 'number' ? (_a = innerNumber === null || innerNumber === void 0 ? void 0 : innerNumber.toString()) !== null && _a !== void 0 ? _a : '' : innerString); }, [innerType, innerNumber, innerString]);
2329
+ const isOverMax = maxValue !== undefined && innerNumber !== undefined && innerNumber > maxValue;
2330
+ const isUnderMin = minValue !== undefined && innerNumber !== undefined && innerNumber < minValue;
1680
2331
  return {
1681
2332
  inputRef,
1682
2333
  innerValue,
1683
2334
  innerType,
2335
+ isOverMax,
2336
+ isUnderMin,
1684
2337
  handleOnFocus,
1685
2338
  handleOnChange,
1686
2339
  handleOnBlur,
@@ -1931,38 +2584,142 @@ function useOtp({ action, otpSize = 6, seconds = 15, }) {
1931
2584
  };
1932
2585
  }
1933
2586
 
2587
+ /**
2588
+ * Backs a value that the consumer may or may not drive, so a control works in
2589
+ * both modes without a copy of the state drifting away from the props.
2590
+ *
2591
+ * A control counts as controlled only when the consumer passes the value *and*
2592
+ * an `onChange` to push it back. The value on its own has always meant "start
2593
+ * here" in this library — `<DInputCheck type="radio" checked />` inside a radio
2594
+ * group, for one — so treating it as controlled would silently freeze those
2595
+ * controls, and React emits no warning to say why: the components always attach
2596
+ * their own `onChange` handler.
2597
+ *
2598
+ * @param value the value coming from props, or `undefined` when not passed.
2599
+ * @param isControlled whether the consumer can drive the value back.
2600
+ * @param initialValue what to start from when no value is passed.
2601
+ * @returns the value to render, and a setter to call on every user change.
2602
+ */
2603
+ function useControlledState(value, isControlled, initialValue) {
2604
+ const [internalValue, setInternalValue] = useState(value === undefined ? initialValue : value);
2605
+ // The internal copy follows `value` in both modes.
2606
+ //
2607
+ // Uncontrolled, `value` without an `onChange` still means "start here", and a
2608
+ // later flip of it from outside must still land — but between flips the
2609
+ // control has to keep moving on its own, which is why this depends on `value`
2610
+ // alone and not on `internalValue`.
2611
+ //
2612
+ // Controlled, the copy is not what gets rendered, but it is what the control
2613
+ // falls back to if the consumer later stops passing a value — `value={locked
2614
+ // ? undefined : x}`, say. Keeping it current means that hand-off carries on
2615
+ // from where the control is instead of snapping back to the mount-time seed.
2616
+ useEffect(() => {
2617
+ if (value === undefined) {
2618
+ return;
2619
+ }
2620
+ setInternalValue(value);
2621
+ }, [value]);
2622
+ const setValue = useCallback((next) => {
2623
+ if (isControlled) {
2624
+ return;
2625
+ }
2626
+ setInternalValue(next);
2627
+ }, [isControlled]);
2628
+ return [
2629
+ isControlled && value !== undefined ? value : internalValue,
2630
+ setValue,
2631
+ ];
2632
+ }
2633
+
1934
2634
  function DInputCounter(_a, ref) {
1935
- var { minValue, maxValue, value = minValue, invalid, iconStart: iconStartProp, iconEnd: iconEndProp, iconStartAriaLabel = 'decrease action', iconEndAriaLabel = 'increase action', style, onChange } = _a, props = __rest(_a, ["minValue", "maxValue", "value", "invalid", "iconStart", "iconEnd", "iconStartAriaLabel", "iconEndAriaLabel", "style", "onChange"]);
2635
+ var { minValue, maxValue, value, defaultValue, invalid, iconStart: iconStartProp, iconEnd: iconEndProp, iconStartAriaLabel = 'decrease action', iconEndAriaLabel = 'increase action', style, onChange } = _a, props = __rest(_a, ["minValue", "maxValue", "value", "defaultValue", "invalid", "iconStart", "iconEnd", "iconStartAriaLabel", "iconEndAriaLabel", "style", "onChange"]);
1936
2636
  const { handleOnWheel, } = useDisableInputWheel(ref);
1937
2637
  const inputRef = useProvidedRefOrCreate(ref);
1938
- const [internalIsInvalid, setInternalIsInvalid] = useState(false);
1939
- const [internalValue, setInternalValue] = useState(value);
2638
+ // See `useControlledState` for why `onChange` takes part in this decision.
2639
+ const isControlled = value !== undefined && onChange !== undefined;
2640
+ const [currentValue, setCurrentValue] = useControlledState(value, isControlled, defaultValue !== null && defaultValue !== void 0 ? defaultValue : minValue);
2641
+ // The step handlers move *from* the current value rather than replacing it,
2642
+ // so they need the latest one even when several clicks land in the same React
2643
+ // batch and no render has happened in between — the functional update this
2644
+ // replaced handled that on its own. Written on every render so it follows the
2645
+ // props, and by `commitValue` so a second step in the same batch starts from
2646
+ // where the first left off instead of collapsing into it.
2647
+ const currentValueRef = useRef(currentValue);
2648
+ currentValueRef.current = currentValue;
2649
+ // `onChange` used to be called from an effect watching the internal value,
2650
+ // which made controlling the counter impossible: a controlled counter never
2651
+ // moves that value, so the effect never fired. Reporting from the handlers
2652
+ // instead works in both modes.
2653
+ const commitValue = useCallback((newValue) => {
2654
+ // Clicking at a bound, or typing the value that is already there, is not a
2655
+ // change. The effect this replaced watched the value itself and so stayed
2656
+ // quiet too; without this a batch of clicks against `minValue` would report
2657
+ // the same number once per click.
2658
+ if (newValue === currentValueRef.current) {
2659
+ return;
2660
+ }
2661
+ // Only the uncontrolled path moves the ref ahead of a render. There the
2662
+ // component owns the value, so a second step in the same batch has to build
2663
+ // on the first. Controlled, the prop is the truth and every step proposes
2664
+ // from it: a parent that rejects without re-rendering — `onChange={() => {}}`,
2665
+ // or a setter returning the previous state — never re-runs the assignment
2666
+ // above, so a speculative ref would climb away from the value on screen and
2667
+ // each further click would report a number further from it.
2668
+ if (!isControlled) {
2669
+ currentValueRef.current = newValue;
2670
+ }
2671
+ setCurrentValue(newValue);
2672
+ onChange === null || onChange === void 0 ? void 0 : onChange(newValue);
2673
+ }, [isControlled, setCurrentValue, onChange]);
2674
+ // Consumers have always been handed the starting value through `onChange` on
2675
+ // mount, and some seed their state with it, so that one call stays. The ref
2676
+ // keeps it to the first run: the old effect also re-fired on every render
2677
+ // that passed a fresh inline `onChange`. Nothing is marked as reported until
2678
+ // there is a handler to report to, so a counter mounted without `onChange`
2679
+ // still hands over its starting value once one is attached.
2680
+ const hasReportedInitialValue = useRef(false);
1940
2681
  useEffect(() => {
1941
- setInternalValue(value);
1942
- }, [value]);
2682
+ if (hasReportedInitialValue.current || !onChange) {
2683
+ return;
2684
+ }
2685
+ hasReportedInitialValue.current = true;
2686
+ onChange(currentValue);
2687
+ }, [onChange, currentValue]);
2688
+ // `value` used to default to `minValue`, so a counter left to its own devices
2689
+ // re-seeded — and reported — whenever `minValue` moved. Consumers use it as a
2690
+ // live bound, so that stays; an explicit `value` or `defaultValue` opts out,
2691
+ // exactly as passing `value` did before. The ref keeps this to real changes,
2692
+ // so mounting still reports once and a click is never undone.
2693
+ const hasExplicitStartingValue = value !== undefined || defaultValue !== undefined;
2694
+ const previousMinValue = useRef(minValue);
1943
2695
  useEffect(() => {
1944
- onChange === null || onChange === void 0 ? void 0 : onChange(Number(internalValue));
1945
- }, [onChange, internalValue]);
2696
+ if (previousMinValue.current === minValue) {
2697
+ return;
2698
+ }
2699
+ previousMinValue.current = minValue;
2700
+ if (hasExplicitStartingValue) {
2701
+ return;
2702
+ }
2703
+ commitValue(minValue);
2704
+ }, [hasExplicitStartingValue, minValue, commitValue]);
1946
2705
  const handleOnChange = useCallback((newValue) => {
1947
- setInternalValue(Number(newValue || '0'));
1948
- }, []);
2706
+ commitValue(Number(newValue || '0'));
2707
+ }, [commitValue]);
1949
2708
  const handleOnIconStartClick = useCallback(() => {
1950
- setInternalValue((prevInternalValue) => Math.max(prevInternalValue - 1, minValue));
1951
- }, [minValue]);
2709
+ commitValue(Math.max(currentValueRef.current - 1, minValue));
2710
+ }, [commitValue, minValue]);
1952
2711
  const handleOnIconEndClick = useCallback(() => {
1953
- setInternalValue((prevInternalValue) => Math.min(prevInternalValue + 1, maxValue));
1954
- }, [maxValue]);
2712
+ commitValue(Math.min(currentValueRef.current + 1, maxValue));
2713
+ }, [commitValue, maxValue]);
1955
2714
  const generateStyleVariables = useMemo(() => (Object.assign(Object.assign({}, style), { [`--${PREFIX_BS}form-control-component-text-align`]: 'center' })), [style]);
1956
- const valueString = useMemo(() => (internalValue.toString()), [internalValue]);
1957
- useEffect(() => {
1958
- setInternalIsInvalid(!(internalValue >= minValue && internalValue <= maxValue));
1959
- }, [internalValue, minValue, maxValue]);
2715
+ const valueString = useMemo(() => (currentValue.toString()), [currentValue]);
2716
+ const internalIsInvalid = useMemo(() => (!(currentValue >= minValue && currentValue <= maxValue)), [currentValue, minValue, maxValue]);
1960
2717
  const { iconMap: { input } } = useDContext();
1961
2718
  const iconEnd = useMemo(() => iconEndProp || input.increase, [iconEndProp, input.increase]);
1962
2719
  const iconStart = useMemo(() => iconStartProp || input.decrease, [iconStartProp, input.decrease]);
1963
- return (jsx(ForwardedDInput, Object.assign({ ref: inputRef, value: valueString, style: generateStyleVariables, iconStart: iconStart, iconEnd: iconEnd, invalid: internalIsInvalid || invalid, type: "number", onChange: handleOnChange, onWheel: handleOnWheel, onIconStartClick: handleOnIconStartClick, onIconEndClick: handleOnIconEndClick, iconStartAriaLabel: iconStartAriaLabel, iconEndAriaLabel: iconEndAriaLabel }, internalValue === minValue && {
2720
+ return (jsx(ForwardedDInput, Object.assign({ ref: inputRef, value: valueString, style: generateStyleVariables, iconStart: iconStart, iconEnd: iconEnd, invalid: internalIsInvalid || invalid, type: "number", onChange: handleOnChange, onWheel: handleOnWheel, onIconStartClick: handleOnIconStartClick, onIconEndClick: handleOnIconEndClick, iconStartAriaLabel: iconStartAriaLabel, iconEndAriaLabel: iconEndAriaLabel }, currentValue === minValue && {
1964
2721
  iconStartDisabled: true,
1965
- }, internalValue === maxValue && {
2722
+ }, currentValue === maxValue && {
1966
2723
  iconEndDisabled: true,
1967
2724
  }, props)));
1968
2725
  }
@@ -1970,11 +2727,16 @@ const ForwardedDInputCounter = forwardRef(DInputCounter);
1970
2727
  ForwardedDInputCounter.displayName = 'DInputCounter';
1971
2728
 
1972
2729
  function DInputCurrency(_a, ref) {
1973
- var { value, minValue, maxValue, currencyCode, onFocus, onBlur, onChange } = _a, props = __rest(_a, ["value", "minValue", "maxValue", "currencyCode", "onFocus", "onBlur", "onChange"]);
2730
+ var { value, minValue, maxValue, clamp = true, currencyCode, onFocus, onBlur, onChange, invalid: invalidProp, valid: validProp } = _a, props = __rest(_a, ["value", "minValue", "maxValue", "clamp", "currencyCode", "onFocus", "onBlur", "onChange", "invalid", "valid"]);
1974
2731
  const { currency: currencyOptions } = useDContext();
1975
2732
  const { handleOnWheel, } = useDisableInputWheel(ref);
1976
- const { inputRef, innerValue, innerType, handleOnFocus, handleOnChange, handleOnBlur, } = useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref, minValue, maxValue);
1977
- return (jsx(ForwardedDInput, Object.assign({ ref: inputRef, value: innerValue, onChange: handleOnChange, inputMode: "decimal", type: innerType, onFocus: handleOnFocus, onBlur: handleOnBlur, onWheel: handleOnWheel, inputStart: (jsx("span", { slot: "input-start", className: "d-input-currency-symbol", children: currencyCode || currencyOptions.symbol })) }, props)));
2733
+ const { inputRef, innerValue, innerType, isOverMax, isUnderMin, handleOnFocus, handleOnChange, handleOnBlur, } = useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref, minValue, maxValue, clamp);
2734
+ const outOfRange = !clamp && (isOverMax || isUnderMin);
2735
+ // One validation state: an explicit `invalid` wins; otherwise an
2736
+ // out-of-range value is invalid and can't also be shown as valid.
2737
+ const invalid = invalidProp !== null && invalidProp !== void 0 ? invalidProp : outOfRange;
2738
+ const valid = invalid ? false : validProp;
2739
+ return (jsx(ForwardedDInput, Object.assign({ ref: inputRef, value: innerValue, onChange: handleOnChange, inputMode: "decimal", type: innerType, onFocus: handleOnFocus, onBlur: handleOnBlur, onWheel: handleOnWheel, invalid: invalid, valid: valid, inputStart: (jsx("span", { slot: "input-start", className: "d-input-currency-symbol", children: currencyCode || currencyOptions.symbol })) }, props)));
1978
2740
  }
1979
2741
  const ForwardedDInputCurrency = forwardRef(DInputCurrency);
1980
2742
  ForwardedDInputCurrency.displayName = 'DInputCurrency';
@@ -2061,7 +2823,7 @@ const DEFAULT_VALIDATION_MESSAGES = {
2061
2823
  notMatch: 'The password confirmation and the new password do not match.',
2062
2824
  };
2063
2825
  const DEFAULT_ENABLED_CHECKS = ['uppercase', 'lowercase', 'number', 'specialChar'];
2064
- function DPasswordStrengthMeter({ id, label = 'Password', placeholder, value = '', name, disabled = false, invalid = false, validationMessages = DEFAULT_VALIDATION_MESSAGES, enabledChecks = DEFAULT_ENABLED_CHECKS, className, style, dataAttributes, onChange, readonly = false, }) {
2826
+ function DPasswordStrengthMeter({ id, label = 'Password', 'aria-label': ariaLabel, placeholder, value = '', name, disabled = false, invalid = false, validationMessages = DEFAULT_VALIDATION_MESSAGES, enabledChecks = DEFAULT_ENABLED_CHECKS, className, style, dataAttributes, onChange, readonly = false, }) {
2065
2827
  const [password, setPassword] = useState(value);
2066
2828
  useEffect(() => {
2067
2829
  setPassword(value);
@@ -2070,17 +2832,31 @@ function DPasswordStrengthMeter({ id, label = 'Password', placeholder, value = '
2070
2832
  setPassword(newValue);
2071
2833
  onChange === null || onChange === void 0 ? void 0 : onChange(newValue);
2072
2834
  };
2073
- return (jsxs("div", Object.assign({ className: className, style: style }, dataAttributes, { children: [jsx(ForwardedDInputPassword, { id: id, label: label, placeholder: placeholder, value: password, name: name, disabled: disabled, invalid: invalid, onChange: handleChange, readonly: readonly }), jsx(PasswordChecksList, { password: password, validationMessages: validationMessages, enabledChecks: enabledChecks })] })));
2835
+ return (jsxs("div", Object.assign({ className: className, style: style }, dataAttributes, { children: [jsx(ForwardedDInputPassword, { id: id, label: label, "aria-label": ariaLabel, placeholder: placeholder, value: password, name: name, disabled: disabled, invalid: invalid, onChange: handleChange, readonly: readonly }), jsx(PasswordChecksList, { password: password, validationMessages: validationMessages, enabledChecks: enabledChecks })] })));
2074
2836
  }
2075
2837
 
2076
2838
  function DInputCheck(_a) {
2077
- var { id: idProp, type, name, label, ariaLabel, checked = false, disabled = false, invalid = false, valid = false, indeterminate, inputClassName, value, hint, onChange, className, style, dataAttributes } = _a, props = __rest(_a, ["id", "type", "name", "label", "ariaLabel", "checked", "disabled", "invalid", "valid", "indeterminate", "inputClassName", "value", "hint", "onChange", "className", "style", "dataAttributes"]);
2839
+ var { id: idProp, type, name, label, ariaLabel, checked, defaultChecked, disabled = false, invalid = false, valid = false, indeterminate, inputClassName, value, hint, onChange, className, style, dataAttributes } = _a, props = __rest(_a, ["id", "type", "name", "label", "ariaLabel", "checked", "defaultChecked", "disabled", "invalid", "valid", "indeterminate", "inputClassName", "value", "hint", "onChange", "className", "style", "dataAttributes"]);
2078
2840
  const innerRef = useRef(null);
2841
+ // See `useControlledState` for why `onChange` takes part in this decision.
2842
+ const isControlled = checked !== undefined && onChange !== undefined;
2079
2843
  const innerId = useId();
2080
2844
  const id = useMemo(() => idProp || innerId, [idProp, innerId]);
2081
2845
  const handleChange = useCallback((event) => {
2082
2846
  onChange === null || onChange === void 0 ? void 0 : onChange(event);
2083
- }, [onChange]);
2847
+ // Controlled only. Activating a checkbox clears the DOM `indeterminate`
2848
+ // flag, and it has no HTML attribute for React to restore the way it
2849
+ // restores `checked` when the parent rejects the change, so the mixed state
2850
+ // would be gone after the first click — the effect below only re-runs when
2851
+ // the prop moves. Uncontrolled keeps the browser's behaviour, where the
2852
+ // click owns the state and `indeterminate` was only the starting look.
2853
+ // Reapplied after `onChange` so a handler reading
2854
+ // `event.target.indeterminate` still sees what the browser left, and a
2855
+ // parent that does move the prop wins through that effect.
2856
+ if (isControlled && innerRef.current) {
2857
+ innerRef.current.indeterminate = type === 'checkbox' && Boolean(indeterminate);
2858
+ }
2859
+ }, [onChange, isControlled, indeterminate, type]);
2084
2860
  const ariaDescribedby = useMemo(() => ([
2085
2861
  !!hint && `${id}Hint`,
2086
2862
  ]
@@ -2094,15 +2870,24 @@ function DInputCheck(_a) {
2094
2870
  innerRef.current.indeterminate = type === 'checkbox' && Boolean(indeterminate);
2095
2871
  }
2096
2872
  }, [indeterminate, type]);
2873
+ // Legacy path only: a `checked` with no `onChange` behind it still lands on
2874
+ // the element, but through the DOM, so the input stays uncontrolled and both
2875
+ // clicking it and the native radio-group behaviour keep working.
2097
2876
  useEffect(() => {
2098
- if (innerRef.current) {
2099
- innerRef.current.checked = checked;
2877
+ if (isControlled || checked === undefined || !innerRef.current) {
2878
+ return;
2100
2879
  }
2101
- }, [checked]);
2102
- const inputComponent = useMemo(() => (jsx("input", Object.assign({ ref: innerRef, onChange: handleChange, className: classNames('form-check-input', {
2880
+ innerRef.current.checked = checked;
2881
+ }, [isControlled, checked]);
2882
+ const inputComponent = useMemo(() => (jsx("input", Object.assign({ ref: innerRef }, isControlled
2883
+ ? { checked }
2884
+ : defaultChecked !== undefined && { defaultChecked }, { onChange: handleChange, className: classNames('form-check-input', {
2103
2885
  'is-invalid': invalid,
2104
2886
  'is-valid': valid,
2105
2887
  }, inputClassName), style: style, id: id, disabled: disabled, type: type, name: name, value: value, "aria-label": ariaLabel }, ariaDescribedby && { 'aria-describedby': ariaDescribedby }, props))), [
2888
+ isControlled,
2889
+ checked,
2890
+ defaultChecked,
2106
2891
  handleChange,
2107
2892
  invalid,
2108
2893
  valid,
@@ -2117,10 +2902,21 @@ function DInputCheck(_a) {
2117
2902
  ariaDescribedby,
2118
2903
  props,
2119
2904
  ]);
2120
- if (!label) {
2905
+ if (process.env.NODE_ENV !== 'production') {
2906
+ warnLabelUsage({
2907
+ component: 'DInputCheck',
2908
+ label,
2909
+ // `{...props}` is spread after `aria-label={ariaLabel}`, so a native
2910
+ // `aria-label` wins — including when it is explicitly undefined.
2911
+ hasAccessibleName: !!('aria-label' in props ? props['aria-label'] : ariaLabel)
2912
+ || !!props['aria-labelledby'],
2913
+ accessibleNameProp: 'ariaLabel',
2914
+ });
2915
+ }
2916
+ if (!hasLabelContent(label)) {
2121
2917
  return inputComponent;
2122
2918
  }
2123
- return (jsxs("div", Object.assign({ className: classNames('form-check', className) }, dataAttributes, { children: [inputComponent, jsx("label", { className: "form-check-label", htmlFor: id, children: label }), hint && (jsx("div", { className: "form-text", id: `${id}Hint`, children: hint }))] })));
2919
+ return (jsxs("div", Object.assign({ className: classNames('form-check', className) }, dataAttributes, { children: [inputComponent, jsx(DFormLabel, { className: "form-check-label", htmlFor: id, children: label }), hint && (jsx("div", { className: "form-text", id: `${id}Hint`, children: hint }))] })));
2124
2920
  }
2125
2921
 
2126
2922
  function DInputPin({ id: idProp, label = '', placeholder, type = 'text', disabled = false, loading = false, secret = false, characters = 4, innerInputMode = 'text', hint, invalid = false, valid = false, className, style, dataAttributes, onChange, 'aria-label': ariaLabel = 'Pin character number', }) {
@@ -2196,14 +2992,14 @@ function DInputPin({ id: idProp, label = '', placeholder, type = 'text', disable
2196
2992
  const wheelInput = useCallback((event) => {
2197
2993
  event.currentTarget.blur();
2198
2994
  }, []);
2199
- return (jsxs("div", Object.assign({ className: classNames('d-input-pin', className), style: style }, dataAttributes, { children: [label && (jsx("label", { htmlFor: "pinIndex0", children: label })), jsxs("div", { className: "d-input-pin-group", id: id, children: [Array.from({ length: characters }).map((_, index) => (jsx("input", Object.assign({ className: classNames({
2995
+ return (jsxs("div", Object.assign({ className: classNames('d-input-pin', className), style: style }, dataAttributes, { children: [hasLabelContent(label) && (jsx(DFormLabel, { htmlFor: "pinIndex0", children: label })), jsxs("div", { className: "d-input-pin-group", id: id, children: [Array.from({ length: characters }).map((_, index) => (jsx("input", Object.assign({ className: classNames({
2200
2996
  'form-control': true,
2201
2997
  'is-invalid': invalid,
2202
2998
  'is-valid': valid,
2203
2999
  }), value: activeInput[index], type: secret ? 'password' : type, "aria-label": `${ariaLabel} ${index + 1} of ${characters}`, inputMode: innerInputMode, id: `pinIndex${index}`, name: `pin-${index}`, maxLength: 1, onInput: (event) => nextInput(event, index), onKeyDown: (event) => prevInput(event, index), onFocus: () => focusInput(index), onWheel: wheelInput, onClick: (event) => event.preventDefault(), onPaste: (event) => handlePaste(event), autoComplete: "off", placeholder: placeholder, disabled: disabled || loading, required: true }, type === 'number' && ({ min: 0, max: 9 })), index))), loading && (jsx("div", { className: "input-group-text", children: jsx("span", { className: "spinner-border spinner-border-sm", role: "status", "aria-hidden": "true", children: jsx("span", { className: "visually-hidden", children: "Loading..." }) }) }))] }), hint && (jsx("div", { className: "form-text", id: `${id}Hint`, children: hint }))] })));
2204
3000
  }
2205
3001
 
2206
- function DInputSelect({ id: idProp, name, label = '', className, style, options = [], disabled = false, loading = false, iconStart, iconStartFamilyClass, iconStartFamilyPrefix, iconStartAriaLabel, iconEnd, iconEndFamilyClass, iconEndFamilyPrefix, iconEndAriaLabel, hint, value, size, floatingLabel = false, invalid = false, valid = false, dataAttributes, valueExtractor, labelExtractor, onChange, onBlur, onIconStartClick, onIconEndClick, }) {
3002
+ function DInputSelect({ id: idProp, name, label = '', ariaLabel, className, style, options = [], disabled = false, loading = false, iconStart, iconStartFamilyClass, iconStartFamilyPrefix, iconStartAriaLabel, iconEnd, iconEndFamilyClass, iconEndFamilyPrefix, iconEndAriaLabel, hint, value, size, floatingLabel = false, invalid = false, valid = false, dataAttributes, valueExtractor, labelExtractor, onChange, onBlur, onIconStartClick, onIconEndClick, }) {
2207
3003
  const innerId = useId();
2208
3004
  const id = useMemo(() => idProp || innerId, [idProp, innerId]);
2209
3005
  const internalValueExtractor = useCallback((option) => {
@@ -2253,8 +3049,9 @@ function DInputSelect({ id: idProp, name, label = '', className, style, options
2253
3049
  'floating-label': floatingLabel,
2254
3050
  'is-invalid': invalid,
2255
3051
  'is-valid': valid,
2256
- }), "aria-label": label, disabled: disabled || loading, onChange: changeHandler, onBlur: blurHandler }, ariaDescribedby && { 'aria-describedby': ariaDescribedby }, value && { value }, { children: options.map((option) => (jsx("option", { value: internalValueExtractor(option), children: internalLabelExtractor(option) }, internalValueExtractor(option)))) }))), [
3052
+ }), "aria-label": ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : (isTextLabel(label) ? String(label) : undefined), disabled: disabled || loading, onChange: changeHandler, onBlur: blurHandler }, ariaDescribedby && { 'aria-describedby': ariaDescribedby }, value && { value }, { children: options.map((option) => (jsx("option", { value: internalValueExtractor(option), children: internalLabelExtractor(option) }, internalValueExtractor(option)))) }))), [
2257
3053
  ariaDescribedby,
3054
+ ariaLabel,
2258
3055
  blurHandler,
2259
3056
  changeHandler,
2260
3057
  disabled,
@@ -2271,7 +3068,7 @@ function DInputSelect({ id: idProp, name, label = '', className, style, options
2271
3068
  valid,
2272
3069
  size,
2273
3070
  ]);
2274
- const labelComponent = useMemo(() => (jsx("label", { htmlFor: id, children: label })), [
3071
+ const labelComponent = useMemo(() => (jsx(DFormLabel, { htmlFor: id, children: label })), [
2275
3072
  id,
2276
3073
  label,
2277
3074
  ]);
@@ -2281,15 +3078,26 @@ function DInputSelect({ id: idProp, name, label = '', className, style, options
2281
3078
  }
2282
3079
  return selectComponent;
2283
3080
  }, [floatingLabel, labelComponent, selectComponent]);
2284
- return (jsxs("div", Object.assign({ className: className, style: style }, dataAttributes, { children: [label && !floatingLabel && (labelComponent), jsxs("div", { className: classNames({
3081
+ if (process.env.NODE_ENV !== 'production') {
3082
+ warnLabelUsage({
3083
+ component: 'DInputSelect',
3084
+ label,
3085
+ hasAccessibleName: !!ariaLabel,
3086
+ accessibleNameProp: 'ariaLabel',
3087
+ floatingLabel,
3088
+ });
3089
+ }
3090
+ return (jsxs("div", Object.assign({ className: className, style: style }, dataAttributes, { children: [hasLabelContent(label) && !floatingLabel && (labelComponent), jsxs("div", { className: classNames({
2285
3091
  'input-group': true,
2286
3092
  }), children: [iconStart && (jsx("button", { type: "button", className: "input-group-text", id: `${id}Start`, onClick: iconStartClickHandler, disabled: disabled || loading, "aria-label": iconStartAriaLabel, children: iconStart && (jsx(DIcon, { icon: iconStart, familyClass: iconStartFamilyClass, familyPrefix: iconStartFamilyPrefix })) })), dynamicComponent, iconEnd && !loading && (jsx("button", { type: "button", className: "input-group-text", id: `${id}End`, onClick: iconEndClickHandler, disabled: disabled || loading, "aria-label": iconEndAriaLabel, children: iconEnd && (jsx(DIcon, { icon: iconEnd, familyClass: iconEndFamilyClass, familyPrefix: iconEndFamilyPrefix })) })), loading && (jsx("div", { className: "input-group-text form-control-icon loading", children: jsx("span", { className: "spinner-border spinner-border-sm", role: "status", "aria-hidden": "true", children: jsx("span", { className: "visually-hidden", children: "Loading..." }) }) }))] }), hint && (jsx("div", { className: "form-text", id: `${id}Hint`, children: hint }))] })));
2287
3093
  }
2288
3094
 
2289
- function DInputSwitch({ id: idProp, label, ariaLabel, name, checked, disabled, invalid = false, valid = false, hint, readonly, className, style, dataAttributes, inputClassName, onChange, }) {
3095
+ function DInputSwitch({ id: idProp, label, ariaLabel, name, checked, defaultChecked = false, disabled, invalid = false, valid = false, hint, readonly, className, style, dataAttributes, inputClassName, onChange, }) {
2290
3096
  const innerId = useId();
2291
3097
  const id = useMemo(() => idProp || innerId, [idProp, innerId]);
2292
- const [internalIsChecked, setInternalIsChecked] = useState(checked);
3098
+ // See `useControlledState` for why `onChange` takes part in this decision.
3099
+ const isControlled = checked !== undefined && onChange !== undefined;
3100
+ const [isChecked, setIsChecked] = useControlledState(checked, isControlled, defaultChecked);
2293
3101
  const ariaDescribedby = useMemo(() => ([
2294
3102
  !!hint && `${id}Hint`,
2295
3103
  ]
@@ -2298,18 +3106,23 @@ function DInputSwitch({ id: idProp, label, ariaLabel, name, checked, disabled, i
2298
3106
  id,
2299
3107
  hint,
2300
3108
  ]);
2301
- useEffect(() => {
2302
- setInternalIsChecked(checked);
2303
- }, [checked]);
2304
3109
  const changeHandler = useCallback((event) => {
2305
3110
  const value = event.currentTarget.checked;
2306
- setInternalIsChecked(value);
3111
+ setIsChecked(value);
2307
3112
  onChange === null || onChange === void 0 ? void 0 : onChange(value);
2308
- }, [onChange]);
3113
+ }, [setIsChecked, onChange]);
3114
+ if (process.env.NODE_ENV !== 'production') {
3115
+ warnLabelUsage({
3116
+ component: 'DInputSwitch',
3117
+ label,
3118
+ hasAccessibleName: !!ariaLabel,
3119
+ accessibleNameProp: 'ariaLabel',
3120
+ });
3121
+ }
2309
3122
  return (jsxs("div", Object.assign({ className: classNames('form-check form-switch', className) }, dataAttributes, { children: [jsx("input", Object.assign({ id: id, name: name, onChange: readonly ? () => false : changeHandler, className: classNames('form-check-input', {
2310
3123
  'is-invalid': invalid,
2311
3124
  'is-valid': valid,
2312
- }, inputClassName), style: style, type: "checkbox", role: "switch", checked: internalIsChecked, disabled: disabled, "aria-label": ariaLabel }, ariaDescribedby && { 'aria-describedby': ariaDescribedby })), label && (jsx("label", { className: "form-check-label", htmlFor: id, children: label })), hint && (jsx("div", { className: "form-text", id: `${id}Hint`, children: hint }))] })));
3125
+ }, inputClassName), style: style, type: "checkbox", role: "switch", checked: isChecked, disabled: disabled, "aria-label": ariaLabel }, ariaDescribedby && { 'aria-describedby': ariaDescribedby })), hasLabelContent(label) && (jsx(DFormLabel, { className: "form-check-label", htmlFor: id, children: label })), hint && (jsx("div", { className: "form-text", id: `${id}Hint`, children: hint }))] })));
2313
3126
  }
2314
3127
 
2315
3128
  function DInputRange(_a, ref) {
@@ -2341,15 +3154,61 @@ function DInputRange(_a, ref) {
2341
3154
  props,
2342
3155
  value,
2343
3156
  ]);
2344
- if (!label) {
3157
+ if (process.env.NODE_ENV !== 'production') {
3158
+ warnLabelUsage({
3159
+ component: 'DInputRange',
3160
+ label,
3161
+ // `{...props}` is spread after `aria-label={ariaLabel}`, so a native
3162
+ // `aria-label` wins — including when it is explicitly undefined.
3163
+ hasAccessibleName: !!('aria-label' in props ? props['aria-label'] : ariaLabel)
3164
+ || !!props['aria-labelledby'],
3165
+ accessibleNameProp: 'ariaLabel',
3166
+ });
3167
+ }
3168
+ if (!hasLabelContent(label)) {
2345
3169
  return inputComponent;
2346
3170
  }
2347
- return (jsxs(Fragment, { children: [jsx("label", { className: "form-label", htmlFor: id, children: label }), inputComponent] }));
3171
+ return (jsxs(Fragment, { children: [jsx(DFormLabel, { className: "form-label", htmlFor: id, children: label }), inputComponent] }));
2348
3172
  }
2349
3173
  const ForwardedDInputRange = forwardRef(DInputRange);
2350
3174
  ForwardedDInputRange.displayName = 'DInputRange';
2351
3175
 
2352
- function DListGroupItem({ as = 'li', action: actionProp, active, disabled, href, onClick, color, iconStart, iconStartFamilyClass, iconStartFamilyPrefix, iconStartMaterialStyle, iconEnd, iconEndFamilyClass, iconEndFamilyPrefix, iconEndMaterialStyle, children, className, style, dataAttributes, }) {
3176
+ /**
3177
+ * Element the enclosing `DListGroup` renders, so each `DListGroupItem` can
3178
+ * check that its own element is valid content for it. `undefined` outside a
3179
+ * `DListGroup`.
3180
+ */
3181
+ const ListGroupContext = createContext(undefined);
3182
+
3183
+ /**
3184
+ * Combinations already reported, keyed by container and item element, so the
3185
+ * same mismatch only warns once per page load however many items share it.
3186
+ */
3187
+ const warnedCombinations = new Set();
3188
+ /**
3189
+ * Warns, once per combination and only outside production builds, that a
3190
+ * `DListGroupItem` renders an element its `DListGroup` container can't hold.
3191
+ *
3192
+ * Links and buttons inside a <ul>/<ol> are wrapped in an <li>, so the only
3193
+ * invalid pair left is a plain item (an <li>) inside `as="div"`: an <li>
3194
+ * outside of any list. It doesn't fail visibly, which is why it's reported.
3195
+ *
3196
+ * The `process.env.NODE_ENV` guard at the call site is what bundlers
3197
+ * constant-fold, so the whole call — and this module — drops out of a
3198
+ * consumer's production bundle.
3199
+ */
3200
+ function warnInvalidListMarkup(container, item) {
3201
+ const key = `${container}>${item}`;
3202
+ if (warnedCombinations.has(key))
3203
+ return;
3204
+ warnedCombinations.add(key);
3205
+ // eslint-disable-next-line no-console
3206
+ console.warn(`[Dynamic UI] DListGroupItem: a <${item}> inside a <${container}> is invalid markup. `
3207
+ + 'Keep the container as a list (the default `as="ul"`, or `numbered`), or render the item '
3208
+ + 'as a link or button. It never appears in production builds.');
3209
+ }
3210
+
3211
+ function DListGroupItem({ as = 'li', action: actionProp, active, ariaCurrent = 'true', disabled, href, onClick, color, iconStart, iconStartFamilyClass, iconStartFamilyPrefix, iconStartMaterialStyle, iconEnd, iconEndFamilyClass, iconEndFamilyPrefix, iconEndMaterialStyle, children, className, style, dataAttributes, }) {
2353
3212
  const { icon: { familyClass, familyPrefix, materialStyle, }, } = useDContext();
2354
3213
  const Tag = useMemo(() => {
2355
3214
  if (href) {
@@ -2360,29 +3219,45 @@ function DListGroupItem({ as = 'li', action: actionProp, active, disabled, href,
2360
3219
  }
2361
3220
  return as;
2362
3221
  }, [href, as, actionProp]);
2363
- const action = useMemo(() => {
2364
- if (Tag === 'a' || Tag === 'button') {
2365
- return true;
2366
- }
2367
- return actionProp;
2368
- }, [Tag, actionProp]);
3222
+ const container = useContext(ListGroupContext);
3223
+ const isInteractive = Tag === 'a' || Tag === 'button';
3224
+ // Inside a <ul>/<ol>, a link or button is wrapped in the <li> that carries
3225
+ // the item styles, so the group keeps list semantics (`list > listitem >
3226
+ // link|button`) without breaking Bootstrap's sibling selectors.
3227
+ const isWrapped = isInteractive && (container === 'ul' || container === 'ol');
3228
+ if (process.env.NODE_ENV !== 'production' && container === 'div' && Tag === 'li') {
3229
+ warnInvalidListMarkup(container, Tag);
3230
+ }
2369
3231
  const generateClasses = useMemo(() => ({
2370
3232
  'list-group-item': true,
2371
- 'list-group-item-action': action,
3233
+ 'list-group-item-action': isInteractive || actionProp,
3234
+ 'd-list-group-item-interactive': isWrapped,
2372
3235
  [`list-group-item-${color}`]: !!color,
2373
3236
  active,
2374
3237
  disabled,
2375
- }), [action, active, disabled, color]);
2376
- const ariaAttributes = useMemo(() => {
3238
+ }), [isInteractive, actionProp, isWrapped, active, disabled, color]);
3239
+ // A disabled link leaves the tab order and can't be activated: without
3240
+ // `href` and `onClick`, Enter does nothing. Dropping `href` also drops the
3241
+ // implicit link role, so it is set back explicitly. A button uses
3242
+ // `disabled`.
3243
+ const interactiveProps = useMemo(() => {
2377
3244
  if (Tag === 'button') {
2378
- return Object.assign(Object.assign({}, active && { 'aria-current': true }), disabled && { disabled: true });
3245
+ return Object.assign(Object.assign(Object.assign({ type: 'button' }, onClick && { onClick }), active && { 'aria-current': ariaCurrent }), disabled && { disabled: true });
3246
+ }
3247
+ if (Tag === 'a') {
3248
+ return disabled
3249
+ ? Object.assign({ role: 'link', 'aria-disabled': true, tabIndex: -1 }, active && { 'aria-current': ariaCurrent }) : Object.assign(Object.assign(Object.assign({}, href && { href }), onClick && { onClick }), active && { 'aria-current': ariaCurrent });
2379
3250
  }
2380
- return Object.assign(Object.assign({}, active && { 'aria-current': true }), disabled && { 'aria-disabled': true });
2381
- }, [Tag, active, disabled]);
2382
- return (jsxs(Tag, Object.assign({ className: classNames(generateClasses, className), style: style }, Tag === 'a' && href && { href }, onClick && { onClick }, ariaAttributes, dataAttributes, Tag === 'button' && { type: 'button' }, { children: [iconStart && (jsx(DIcon, { icon: iconStart, familyClass: iconStartFamilyClass !== null && iconStartFamilyClass !== void 0 ? iconStartFamilyClass : familyClass, familyPrefix: iconStartFamilyPrefix !== null && iconStartFamilyPrefix !== void 0 ? iconStartFamilyPrefix : familyPrefix, materialStyle: iconStartMaterialStyle !== null && iconStartMaterialStyle !== void 0 ? iconStartMaterialStyle : materialStyle })), children, iconEnd && (jsx(DIcon, { icon: iconEnd, familyClass: iconEndFamilyClass !== null && iconEndFamilyClass !== void 0 ? iconEndFamilyClass : familyClass, familyPrefix: iconEndFamilyPrefix !== null && iconEndFamilyPrefix !== void 0 ? iconEndFamilyPrefix : familyPrefix, materialStyle: iconEndMaterialStyle !== null && iconEndMaterialStyle !== void 0 ? iconEndMaterialStyle : materialStyle, className: "ms-auto" }))] })));
3251
+ return Object.assign(Object.assign(Object.assign({}, onClick && { onClick }), active && { 'aria-current': ariaCurrent }), disabled && { 'aria-disabled': true });
3252
+ }, [Tag, href, onClick, active, ariaCurrent, disabled]);
3253
+ const content = (jsxs(Fragment, { children: [iconStart && (jsx(DIcon, { icon: iconStart, familyClass: iconStartFamilyClass !== null && iconStartFamilyClass !== void 0 ? iconStartFamilyClass : familyClass, familyPrefix: iconStartFamilyPrefix !== null && iconStartFamilyPrefix !== void 0 ? iconStartFamilyPrefix : familyPrefix, materialStyle: iconStartMaterialStyle !== null && iconStartMaterialStyle !== void 0 ? iconStartMaterialStyle : materialStyle })), children, iconEnd && (jsx(DIcon, { icon: iconEnd, familyClass: iconEndFamilyClass !== null && iconEndFamilyClass !== void 0 ? iconEndFamilyClass : familyClass, familyPrefix: iconEndFamilyPrefix !== null && iconEndFamilyPrefix !== void 0 ? iconEndFamilyPrefix : familyPrefix, materialStyle: iconEndMaterialStyle !== null && iconEndMaterialStyle !== void 0 ? iconEndMaterialStyle : materialStyle, className: "ms-auto" }))] }));
3254
+ if (isWrapped) {
3255
+ return (jsx("li", { className: classNames(generateClasses, className), style: style, children: jsx(Tag, Object.assign({ className: "d-list-group-item-link" }, interactiveProps, dataAttributes, { children: content })) }));
3256
+ }
3257
+ return (jsx(Tag, Object.assign({ className: classNames(generateClasses, className), style: style }, interactiveProps, dataAttributes, { children: content })));
2383
3258
  }
2384
3259
 
2385
- function DListGroup({ as = 'ul', numbered, flush, horizontal, children, className, style, dataAttributes, }) {
3260
+ function DListGroup({ as = 'ul', numbered, flush, horizontal, ariaLabel, ariaLabelledBy, children, className, style, dataAttributes, }) {
2386
3261
  const Tag = useMemo(() => {
2387
3262
  if (numbered) {
2388
3263
  return 'ol';
@@ -2400,7 +3275,14 @@ function DListGroup({ as = 'ul', numbered, flush, horizontal, children, classNam
2400
3275
  [listGroupHorizontalClass]: !!horizontal,
2401
3276
  };
2402
3277
  }, [flush, horizontal, numbered]);
2403
- return (jsx(Tag, Object.assign({ className: classNames(generateClasses, className), style: style }, dataAttributes, { children: children })));
3278
+ const labelProps = useMemo(() => {
3279
+ if (!ariaLabelledBy && !ariaLabel)
3280
+ return {};
3281
+ return Object.assign(Object.assign({}, ariaLabelledBy
3282
+ ? { 'aria-labelledby': ariaLabelledBy }
3283
+ : { 'aria-label': ariaLabel }), Tag === 'div' && { role: 'group' });
3284
+ }, [ariaLabel, ariaLabelledBy, Tag]);
3285
+ return (jsx(ListGroupContext.Provider, { value: Tag, children: jsx(Tag, Object.assign({ className: classNames(generateClasses, className), style: style }, labelProps, dataAttributes, { children: children })) }));
2404
3286
  }
2405
3287
  var DListGroup$1 = Object.assign(DListGroup, {
2406
3288
  Item: DListGroupItem,
@@ -2586,14 +3468,20 @@ function DPopover({ children, renderComponent, open, setOpen, adjustContentToRen
2586
3468
  }), ref: refs.setFloating, style: floatingStyles, "aria-labelledby": headingId }, getFloatingProps(), { children: children })) }))] })));
2587
3469
  }
2588
3470
 
2589
- function DProgress({ className, style, currentValue, minValue = 0, maxValue = 100, hideCurrentValue = false, enableStripedAnimation = false, height, dataAttributes, }) {
2590
- const percentage = useMemo(() => (Math.round((currentValue * 100) / maxValue)), [currentValue, maxValue]);
3471
+ function DProgress({ className, style, currentValue, minValue = 0, maxValue = 100, hideCurrentValue = false, enableStripedAnimation = false, height, ariaLabel, ariaLabelledBy, dataAttributes, }) {
3472
+ const percentage = useMemo(() => {
3473
+ const range = maxValue - minValue;
3474
+ if (range <= 0) {
3475
+ return 0;
3476
+ }
3477
+ return Math.round(((currentValue - minValue) * 100) / range);
3478
+ }, [currentValue, minValue, maxValue]);
2591
3479
  const formatProgress = useMemo(() => `${percentage}%`, [percentage]);
2592
3480
  const generateClasses = useMemo(() => ({
2593
3481
  'progress-bar': true,
2594
3482
  'progress-bar-striped progress-bar-animated': enableStripedAnimation,
2595
3483
  }), [enableStripedAnimation]);
2596
- return (jsx("div", Object.assign({ className: classNames('progress', className), style: Object.assign({ height }, style) }, dataAttributes, { children: jsx("div", { className: classNames(generateClasses), role: "progressbar", "aria-label": "Progress bar", style: { width: formatProgress }, "aria-valuenow": currentValue, "aria-valuemin": minValue, "aria-valuemax": maxValue, children: !hideCurrentValue && formatProgress }) })));
3484
+ return (jsx("div", Object.assign({ className: classNames('progress', className), style: Object.assign({ height }, style) }, dataAttributes, { children: jsx("div", { className: classNames(generateClasses), role: "progressbar", "aria-label": ariaLabelledBy ? undefined : (ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : 'Progress bar'), "aria-labelledby": ariaLabelledBy, style: { width: formatProgress }, "aria-valuenow": currentValue, "aria-valuemin": minValue, "aria-valuemax": maxValue, children: !hideCurrentValue && formatProgress }) })));
2597
3485
  }
2598
3486
 
2599
3487
  function DStepper$2({ options, currentStep, iconSuccess: iconSuccessProp, iconSuccessFamilyClass, iconSuccessFamilyPrefix, iconSuccessMaterialStyle, vertical = false, completed, alignStart = false, className, style, }) {
@@ -2697,14 +3585,15 @@ function DTooltip({ className, childrenClassName, style, offSet = ARROW_HEIGHT +
2697
3585
  function DTimeline({ className, style, dataAttributes, items, }) {
2698
3586
  return (jsx("div", Object.assign({ style: style, className: classNames('d-timeline', className) }, dataAttributes, { children: items.map((item, index) => (jsxs("div", { className: classNames('d-timeline-item', {
2699
3587
  [`d-timeline-item-${item.status}`]: item.status,
2700
- }), children: [jsx("div", { className: "d-timeline-item-connector" }), jsx("div", { className: "d-timeline-item-icon", children: jsx(DIcon, { icon: item.icon || 'check', size: "1rem" }) }), jsxs("div", { className: "d-timeline-item-content", children: [jsx("div", { className: "d-timeline-item-title", children: item.title }), item.description && jsx("div", { className: "d-timeline-item-description", children: item.description }), item.time && jsx("div", { className: "d-timeline-item-time", children: item.time }), item.children] })] }, index))) })));
3588
+ }), children: [jsx("div", { className: "d-timeline-item-connector" }), jsx("div", { className: "d-timeline-item-icon", children: jsx(DIcon, { icon: item.icon || 'Check', size: "1rem" }) }), jsxs("div", { className: "d-timeline-item-content", children: [jsx("div", { className: "d-timeline-item-title", children: item.title }), item.description && jsx("div", { className: "d-timeline-item-description", children: item.description }), item.time && jsx("div", { className: "d-timeline-item-time", children: item.time }), item.children] })] }, index))) })));
2701
3589
  }
2702
3590
 
2703
3591
  const TabContext = createContext(undefined);
3592
+ const TabsStateContext = createContext(undefined);
2704
3593
  function useTabContext() {
2705
3594
  const context = useContext(TabContext);
2706
3595
  if (context === undefined) {
2707
- throw new Error('useTabContext was used outside of MTab');
3596
+ throw new Error('useTabContext was used outside of DTabs');
2708
3597
  }
2709
3598
  return context;
2710
3599
  }
@@ -2714,33 +3603,44 @@ function DTabContent({ tab, children, className, style, }) {
2714
3603
  if (!isSelected(tab)) {
2715
3604
  return null;
2716
3605
  }
2717
- return (jsx("div", { className: classNames('tab-pane fade show active', className), id: `${tab}Pane`, role: "tabpanel", tabIndex: 0, "aria-labelledby": `${tab}Tab`, style: style, children: children }));
3606
+ return (jsx("div", { className: classNames('tab-pane fade show active', className), id: `${tab}Pane`, role: "tabpanel", tabIndex: 0, "aria-labelledby": `${tab}Tab`, style: style, children: jsx(TabsStateContext.Provider, { value: undefined, children: children }) }));
2718
3607
  }
2719
3608
 
2720
- function DTabs({ children, defaultSelected, onChange, options, className, classNameTab, style, vertical, variant = 'underline', dataAttributes, ariaLabel, ariaLabelledBy, }) {
3609
+ /**
3610
+ * Owns the selected tab so the tab bar (`DTabs`) and its panels (`DTabs.Tab`)
3611
+ * can live anywhere inside it, e.g. the bar in a header and the panels in
3612
+ * another column.
3613
+ */
3614
+ function DTabsProvider({ defaultSelected, children, }) {
2721
3615
  const [selected, setSelected] = useState(defaultSelected);
3616
+ useEffect(() => {
3617
+ setSelected(defaultSelected);
3618
+ }, [defaultSelected]);
3619
+ const isSelected = useCallback((tab) => (selected === tab), [selected]);
3620
+ const state = useMemo(() => ({ selected, setSelected }), [selected]);
3621
+ const value = useMemo(() => ({ isSelected }), [isSelected]);
3622
+ return (jsx(TabsStateContext.Provider, { value: state, children: jsx(TabContext.Provider, { value: value, children: children }) }));
3623
+ }
3624
+
3625
+ function DTabs({ children, defaultSelected, onChange, options, className, classNameTab, classNameContent, style, vertical, variant = 'underline', dataAttributes, ariaLabel, ariaLabelledBy, }) {
3626
+ var _a;
3627
+ const shared = useContext(TabsStateContext);
3628
+ const [ownSelected, setOwnSelected] = useState(defaultSelected);
3629
+ const selected = shared
3630
+ ? shared.selected
3631
+ : ownSelected !== null && ownSelected !== void 0 ? ownSelected : (_a = options.find((opt) => !opt.disabled)) === null || _a === void 0 ? void 0 : _a.tab;
3632
+ const setSelected = shared ? shared.setSelected : setOwnSelected;
2722
3633
  const onSelect = useCallback((option) => {
2723
3634
  if (option.tab) {
2724
3635
  setSelected(option.tab);
2725
3636
  }
2726
3637
  onChange === null || onChange === void 0 ? void 0 : onChange(option);
2727
- }, [onChange]);
3638
+ }, [onChange, setSelected]);
2728
3639
  useEffect(() => {
2729
- setSelected(defaultSelected);
3640
+ setOwnSelected(defaultSelected);
2730
3641
  }, [defaultSelected]);
2731
3642
  const generateClasses = useMemo(() => (Object.assign({ nav: true, 'd-tabs-nav-vertical': vertical && variant !== 'tabs', [`nav-${variant}`]: true }, className && { [className]: true })), [vertical, variant, className]);
2732
3643
  const tabRefs = useRef([]);
2733
- // Always holds the latest `options` without needing to be a dependency:
2734
- // `options` is commonly passed as an inline array literal (e.g.
2735
- // `options={[{ label: 'SMS', tab: 'sms' }, ...]}`), so it's a new array
2736
- // reference on every parent render even when its content hasn't changed.
2737
- // Reading it from this ref (updated synchronously on every render) lets
2738
- // the focus effect below react only to `selected` changing.
2739
- const optionsRef = useRef(options);
2740
- optionsRef.current = options;
2741
- useEffect(() => {
2742
- tabRefs.current = options.map((_, i) => tabRefs.current[i] || createRef());
2743
- }, [options]);
2744
3644
  // Ensure selected is never disabled
2745
3645
  useEffect(() => {
2746
3646
  if (options.length === 0)
@@ -2751,29 +3651,16 @@ function DTabs({ children, defaultSelected, onChange, options, className, classN
2751
3651
  if (firstEnabled)
2752
3652
  setSelected(firstEnabled.tab);
2753
3653
  }
2754
- }, [options, selected]);
2755
- // Declarative focus management. Wrapped in `useCallback` with an empty
2756
- // dependency array since it only reads from the `tabRefs` ref, so its
2757
- // identity stays stable across renders and it can safely be used as an
2758
- // effect dependency below.
3654
+ }, [options, selected, setSelected]);
3655
+ // Focus only moves in response to the user: arrow keys and clicks call
3656
+ // this directly. Changes to `selected` that don't come from an interaction
3657
+ // (mount, a new `defaultSelected`, the disabled-tab fallback above) leave
3658
+ // focus where it is, so the page doesn't scroll to the tablist and no other
3659
+ // element (a search field, an OTP input) loses focus.
2759
3660
  const focusTab = useCallback((idx) => {
2760
3661
  var _a;
2761
- if ((_a = tabRefs.current[idx]) === null || _a === void 0 ? void 0 : _a.current) {
2762
- tabRefs.current[idx].current.focus();
2763
- }
3662
+ (_a = tabRefs.current[idx]) === null || _a === void 0 ? void 0 : _a.focus();
2764
3663
  }, []);
2765
- // Focus selected tab when selected changes.
2766
- // Reads `options` from `optionsRef` (see comment above) instead of
2767
- // depending on `options` directly, so a parent re-render that merely
2768
- // creates a new `options` reference with identical content doesn't call
2769
- // `focusTab` again and steal focus away from unrelated elements on the
2770
- // page (e.g. an OTP input).
2771
- useEffect(() => {
2772
- const idx = optionsRef.current.findIndex((opt) => opt.tab === selected && !opt.disabled);
2773
- if (idx !== -1) {
2774
- focusTab(idx);
2775
- }
2776
- }, [selected, focusTab]);
2777
3664
  const handleKeyDown = useCallback((idx, e) => {
2778
3665
  const count = options.length;
2779
3666
  if (count === 0)
@@ -2802,7 +3689,7 @@ function DTabs({ children, defaultSelected, onChange, options, className, classN
2802
3689
  }
2803
3690
  }
2804
3691
  }
2805
- }, [options, vertical, focusTab]);
3692
+ }, [options, vertical, focusTab, setSelected]);
2806
3693
  let tablistProps = {};
2807
3694
  if (ariaLabelledBy) {
2808
3695
  tablistProps = { 'aria-labelledby': ariaLabelledBy };
@@ -2819,11 +3706,17 @@ function DTabs({ children, defaultSelected, onChange, options, className, classN
2819
3706
  'd-tabs-column': !vertical || variant === 'tabs',
2820
3707
  }), style: style }, dataAttributes, { children: [jsx("ul", Object.assign({ className: classNames(generateClasses), role: "tablist", "aria-orientation": vertical ? 'vertical' : undefined }, tablistProps, { children: options.map((option, idx) => {
2821
3708
  const isTabSelected = !!option.tab && option.tab === selected;
2822
- return (jsx("li", { role: "presentation", className: "nav-item", children: jsx("button", { ref: tabRefs.current[idx], id: `${option.tab}Tab`, className: classNames('nav-link', { active: isTabSelected }, classNameTab), type: "button", role: "tab", "aria-controls": `${option.tab}Pane`, "aria-selected": isTabSelected, tabIndex: isTabSelected ? 0 : -1, disabled: option.disabled, onClick: () => onSelect(option), onKeyDown: (e) => handleKeyDown(idx, e), children: option.label }) }, option.tab));
2823
- }) })), jsx("div", { className: "d-tabs-content tab-content", children: children })] })) }));
3709
+ return (jsx("li", { role: "presentation", className: "nav-item", children: jsx("button", { ref: (element) => {
3710
+ tabRefs.current[idx] = element;
3711
+ }, id: `${option.tab}Tab`, className: classNames('nav-link', { active: isTabSelected }, classNameTab), type: "button", role: "tab", "aria-controls": `${option.tab}Pane`, "aria-selected": isTabSelected, tabIndex: isTabSelected ? 0 : -1, disabled: option.disabled, onClick: () => {
3712
+ focusTab(idx);
3713
+ onSelect(option);
3714
+ }, onKeyDown: (e) => handleKeyDown(idx, e), children: option.label }) }, option.tab));
3715
+ }) })), Children.toArray(children).length > 0 && (jsx("div", { className: classNames('d-tabs-content tab-content', classNameContent), children: children }))] })) }));
2824
3716
  }
2825
3717
  var DTabs$1 = Object.assign(DTabs, {
2826
3718
  Tab: DTabContent,
3719
+ Provider: DTabsProvider,
2827
3720
  });
2828
3721
 
2829
3722
  function DToastHeader({ children, className, style }) {
@@ -2834,8 +3727,13 @@ function DToastBody({ children, className, style }) {
2834
3727
  return (jsx("div", { className: classNames('toast-body', className), style: style, children: children }));
2835
3728
  }
2836
3729
 
2837
- function DToast({ children, className, style, dataAttributes, }) {
2838
- return (jsx("div", Object.assign({ className: classNames('toast', className), role: "alert", "aria-live": "assertive", "aria-atomic": "true", style: style }, dataAttributes, { children: children })));
3730
+ const LIVE_REGION_ATTRIBUTES = {
3731
+ alert: { role: 'alert', 'aria-live': 'assertive', 'aria-atomic': 'true' },
3732
+ status: { role: 'status', 'aria-live': 'polite', 'aria-atomic': 'true' },
3733
+ none: {},
3734
+ };
3735
+ function DToast({ children, role = 'alert', className, style, dataAttributes, }) {
3736
+ return (jsx("div", Object.assign({ className: classNames('toast', className) }, LIVE_REGION_ATTRIBUTES[role], { style: style }, dataAttributes, { children: children })));
2839
3737
  }
2840
3738
  var DToast$1 = Object.assign(DToast, {
2841
3739
  Header: DToastHeader,
@@ -2874,19 +3772,19 @@ function useDToast() {
2874
3772
  if (typeof data === 'function') {
2875
3773
  return toast.custom(data, toastProps);
2876
3774
  }
2877
- const { title, description, icon, closeIcon, timestamp, color, } = data;
3775
+ const { title, description, icon, closeIcon, timestamp, color, role, closeAriaLabel = 'Close', } = data;
2878
3776
  return toast.custom(({ id, visible }) => {
2879
3777
  if (!visible) {
2880
3778
  return null;
2881
3779
  }
2882
3780
  if (!description) {
2883
- return (jsx(DToast$1, { className: classNames({
3781
+ return (jsx(DToast$1, { role: role, className: classNames({
2884
3782
  [`toast-${color}`]: !!color,
2885
- }, 'show'), children: jsxs(DToast$1.Body, { children: [icon && (jsx(DIcon, { className: "toast-icon", icon: icon })), jsx("p", { className: "toast-title", children: title }), jsx("button", { type: "button", className: "d-close", "aria-label": "Close", onClick: () => toast.dismiss(id), children: jsx(DIcon, { icon: closeIcon || xLg }) })] }) }));
3783
+ }, 'show'), children: jsxs(DToast$1.Body, { children: [icon && (jsx(DIcon, { className: "toast-icon", icon: icon })), jsx("p", { className: "toast-title", children: title }), jsx("button", { type: "button", className: "d-close", "aria-label": closeAriaLabel, onClick: () => toast.dismiss(id), children: jsx(DIcon, { icon: closeIcon || xLg }) })] }) }));
2886
3784
  }
2887
- return (jsxs(DToast$1, { className: classNames({
3785
+ return (jsxs(DToast$1, { role: role, className: classNames({
2888
3786
  [`toast-${color}`]: !!color,
2889
- }, 'show'), children: [jsxs(DToast$1.Header, { children: [icon && (jsx(DIcon, { className: "toast-icon", icon: icon })), jsx("p", { className: "toast-title", children: title }), timestamp && (jsx("small", { className: "toast-timestamp", children: timestamp })), jsx("button", { type: "button", className: "d-close", "aria-label": "Close", onClick: () => toast.dismiss(id), children: jsx(DIcon, { icon: closeIcon || xLg }) })] }), jsx(DToast$1.Body, { children: jsx("span", { children: description }) })] }));
3787
+ }, 'show'), children: [jsxs(DToast$1.Header, { children: [icon && (jsx(DIcon, { className: "toast-icon", icon: icon })), jsx("p", { className: "toast-title", children: title }), timestamp && (jsx("small", { className: "toast-timestamp", children: timestamp })), jsx("button", { type: "button", className: "d-close", "aria-label": closeAriaLabel, onClick: () => toast.dismiss(id), children: jsx(DIcon, { icon: closeIcon || xLg }) })] }), jsx(DToast$1.Body, { children: jsx("span", { children: description }) })] }));
2890
3788
  }, toastProps);
2891
3789
  }, [xLg]);
2892
3790
  return {
@@ -3122,7 +4020,7 @@ function DInputPhone(_a, ref) {
3122
4020
  placeholder,
3123
4021
  valid,
3124
4022
  ]);
3125
- const labelComponent = useMemo(() => (jsx("label", { htmlFor: id, children: label })), [
4023
+ const labelComponent = useMemo(() => (jsx(DFormLabel, { htmlFor: id, children: label })), [
3126
4024
  id,
3127
4025
  label,
3128
4026
  ]);
@@ -3136,7 +4034,16 @@ function DInputPhone(_a, ref) {
3136
4034
  inputComponent,
3137
4035
  labelComponent,
3138
4036
  ]);
3139
- return (jsxs("div", Object.assign({ className: classNames('d-input-phone', className), style: style }, dataAttributes, { children: [label && !floatingLabel && labelComponent, jsxs("div", { className: classNames({
4037
+ if (process.env.NODE_ENV !== 'production') {
4038
+ warnLabelUsage({
4039
+ component: 'DInputPhone',
4040
+ label,
4041
+ hasAccessibleName: !!inputProps['aria-label'] || !!inputProps['aria-labelledby'],
4042
+ accessibleNameProp: 'aria-label',
4043
+ floatingLabel,
4044
+ });
4045
+ }
4046
+ return (jsxs("div", Object.assign({ className: classNames('d-input-phone', className), style: style }, dataAttributes, { children: [hasLabelContent(label) && !floatingLabel && labelComponent, jsxs("div", { className: classNames({
3140
4047
  [`input-group-${size}`]: !!size,
3141
4048
  'input-group': true,
3142
4049
  'has-validation': invalid || valid,
@@ -3603,11 +4510,11 @@ function DOtp({ className, action, isLoading, otpSize = 6, texts = TEXT_PROPS, s
3603
4510
  }, loading: isLoading || isSubmitting }), jsx("p", { className: "d-otp-contact", children: texts.contact })] })] })] }));
3604
4511
  }
3605
4512
 
3606
- function DefaultErrorBoundary({ resetErrorBoundary }) {
3607
- return (jsx(DAlert, { color: "danger", showClose: false, children: jsxs("div", { className: "d-error-boundary-content", children: [jsx("span", { children: "An unexpected error occurred." }), jsx(DButton, { color: "secondary", variant: "outline", size: "sm", onClick: resetErrorBoundary, children: "Retry" })] }) }));
4513
+ function DefaultErrorBoundary({ resetErrorBoundary, message = 'An unexpected error occurred.', retryMessage = 'Retry', }) {
4514
+ return (jsx(DAlert, { color: "danger", showClose: false, children: jsxs("div", { className: "d-error-boundary-content", children: [jsx("span", { children: message }), jsx(DButton, { color: "secondary", variant: "outline", size: "sm", onClick: resetErrorBoundary, children: retryMessage })] }) }));
3608
4515
  }
3609
4516
 
3610
- function DErrorBoundary({ name, fallback, resetKeys, onReset, onError, children, }) {
4517
+ function DErrorBoundary({ name, fallback, resetKeys, onReset, onError, messages, children, }) {
3611
4518
  const handleError = useCallback((error, info) => {
3612
4519
  // eslint-disable-next-line no-console
3613
4520
  console.error(`[DErrorBoundary${name ? `:${name}` : ''}]`, getErrorMessage(error), info);
@@ -3616,8 +4523,8 @@ function DErrorBoundary({ name, fallback, resetKeys, onReset, onError, children,
3616
4523
  const FallbackRender = useCallback((props) => {
3617
4524
  if (fallback)
3618
4525
  return fallback(props);
3619
- return (jsx(DefaultErrorBoundary, { resetErrorBoundary: props.resetErrorBoundary }));
3620
- }, [fallback]);
4526
+ return (jsx(DefaultErrorBoundary, { resetErrorBoundary: props.resetErrorBoundary, message: messages === null || messages === void 0 ? void 0 : messages.error, retryMessage: messages === null || messages === void 0 ? void 0 : messages.retry }));
4527
+ }, [fallback, messages]);
3621
4528
  return (jsx(ErrorBoundary, { resetKeys: resetKeys, onReset: onReset, onError: handleError, fallbackRender: FallbackRender, children: children }));
3622
4529
  }
3623
4530
 
@@ -3638,6 +4545,11 @@ function render(renderable) {
3638
4545
  return null;
3639
4546
  return typeof renderable === 'function' ? renderable() : renderable;
3640
4547
  }
4548
+ function isEmpty(data) {
4549
+ if (Array.isArray(data))
4550
+ return data.length === 0;
4551
+ return data === null || data === undefined;
4552
+ }
3641
4553
  function DDataStateWrapper({ isLoading, isError, data, onRetry, messages, renderLoading, renderEmpty, renderError, children, }) {
3642
4554
  // 1. Loading
3643
4555
  if (isLoading) {
@@ -3651,13 +4563,15 @@ function DDataStateWrapper({ isLoading, isError, data, onRetry, messages, render
3651
4563
  return render(renderError);
3652
4564
  return (jsx(ErrorState, { onRetry: onRetry, message: messages === null || messages === void 0 ? void 0 : messages.error, retryMessage: messages === null || messages === void 0 ? void 0 : messages.retry }));
3653
4565
  }
3654
- // 3. Empty
3655
- if (!(data === null || data === void 0 ? void 0 : data.length)) {
4566
+ // 3. Empty: no items for a collection, null/undefined for a single resource
4567
+ if (isEmpty(data)) {
3656
4568
  if (renderEmpty)
3657
4569
  return render(renderEmpty);
3658
4570
  return (jsx(EmptyState, { message: messages === null || messages === void 0 ? void 0 : messages.empty }));
3659
4571
  }
3660
- // 4. Success
4572
+ // 4. Success: the render prop gets the same shape it was given
4573
+ // Both overloads pair `data` with its own `children` signature, so the
4574
+ // value is handed back exactly as it was received.
3661
4575
  return jsx(Fragment, { children: children(data) });
3662
4576
  }
3663
4577
 
@@ -3742,5 +4656,58 @@ function DConfirmModalContainer({ nodeId }) {
3742
4656
  return createPortal(jsx(AnimatePresence, { children: entries.map((entry) => (jsxs(motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0, transition: { delay: 0.3 } }, transition: { duration: 0.15, ease: 'linear' }, children: [jsx("div", { className: "backdrop backdrop-confirm-modal", onClick: entry.onCloseAction, role: "presentation" }), jsx(DConfirmModalUI, { entry: entry })] }, entry.id))) }), portalNode);
3743
4657
  }
3744
4658
 
3745
- export { DAlert, DAvatar, DBadge, DBox, DBoxFile, DButton, DButtonIcon, DCard$1 as DCard, DCardBody, DCardFooter, DCardHeader, DCarousel$1 as DCarousel, DCarouselSlide, DChip, DCollapse, DConfirmModalContainer, DContext, DContextProvider, DCreditCard, DCurrencyText, DDataStateWrapper, DDatePicker, DDropdown, DErrorBoundary, DIcon, DIconBase, ForwardedDInput as DInput, DInputCheck, ForwardedDInputCounter as DInputCounter, ForwardedDInputCurrency as DInputCurrency, ForwardedDInputMask as DInputMask, ForwardedDInputPassword as DInputPassword, ForwardedDInputPhone as DInputPhone, DInputPin, ForwardedDInputRange as DInputRange, ForwardedDInputSearch as DInputSearch, DInputSelect, DInputSwitch, DLayout$1 as DLayout, DLayoutPane, DListGroup$1 as DListGroup, DListGroupItem, DModal$1 as DModal, DModalBody, DModalFooter, DModalHeader, DOffcanvas$1 as DOffcanvas, DOffcanvasBody, DOffcanvasFooter, DOffcanvasHeader, DOtp, DPaginator, DPasswordStrengthMeter, DPopover, DProgress, DSelect$1 as DSelect, DStepper, DStepper$2 as DStepperDesktop, DStepper$1 as DStepperMobile, DTabContent, DTabs$1 as DTabs, DTimeline, DToast$1 as DToast, DToastContainer, DTooltip, DVoucher, EmptyState, ErrorState, LoadingState, buildUrl, changeQueryString, checkMediaQuery, configureI8n as configureI18n, formatCurrency, getCssVariable, getQueryString, sanitizeHref, subscribeToMediaQuery, useConfirmModal, useCountdown, useDContext, useDPortalContext, useDToast, useDisableBodyScrollEffect, useDisableInputWheel, useFormatCurrency, useInputCurrency, useItemSelection, useMediaBreakpointUpLg, useMediaBreakpointUpMd, useMediaBreakpointUpSm, useMediaBreakpointUpXl, useMediaBreakpointUpXs, useMediaBreakpointUpXxl, useMediaQuery, useOtp, useProvidedRefOrCreate, useScreenshot, useScreenshotDownload, useScreenshotWebShare, useStackState, useTabContext, validatePhoneNumber };
4659
+ /**
4660
+ * The lucide-react icons Dynamic UI renders on its own, without the consumer
4661
+ * asking for them by name.
4662
+ *
4663
+ * Two sources feed this list:
4664
+ *
4665
+ * - The `iconMap` defaults of `DContextProvider` (`src/contexts/DContext.tsx`),
4666
+ * used by close buttons, chevrons, alert states, input adornments and the
4667
+ * stepper checkmark.
4668
+ * - Names hard-coded in component JSX or in a prop default, such as
4669
+ * `DDropdown`'s toggle or `DVoucher`'s action buttons.
4670
+ *
4671
+ * Any bundler-level optimisation that narrows lucide-react down to the icons a
4672
+ * widget actually uses must keep this set, or Dynamic UI components break for
4673
+ * reasons the widget author cannot see in their own source. That is why the
4674
+ * list ships in the package as `dist/icons-core.json` and is exported here.
4675
+ *
4676
+ * `src/icons/coreIcons.spec.ts` scans `src/components/**` and fails when an
4677
+ * internally used icon name is missing from this list or absent from
4678
+ * lucide-react, so the two cannot drift apart.
4679
+ */
4680
+ const CORE_LUCIDE_ICONS = [
4681
+ // iconMap defaults — src/contexts/DContext.tsx
4682
+ 'AlertCircle',
4683
+ 'AlertTriangle',
4684
+ 'Calendar',
4685
+ 'Check',
4686
+ 'CheckCircle',
4687
+ 'ChevronDown',
4688
+ 'ChevronLeft',
4689
+ 'ChevronRight',
4690
+ 'ChevronUp',
4691
+ 'Eye',
4692
+ 'EyeOff',
4693
+ 'Info',
4694
+ 'Minus',
4695
+ 'Plus',
4696
+ 'Search',
4697
+ 'Upload',
4698
+ 'X',
4699
+ // Hard-coded in component JSX or in a prop default
4700
+ 'Circle',
4701
+ 'CircleCheck',
4702
+ 'CircleCheckBig',
4703
+ 'Download',
4704
+ 'FileText',
4705
+ 'MoreVertical',
4706
+ 'Paperclip',
4707
+ 'RefreshCw',
4708
+ 'Share2',
4709
+ 'Trash',
4710
+ ];
4711
+
4712
+ export { CORE_LUCIDE_ICONS, DAlert, DAvatar, DBadge, DBox, DBoxFile, DButton, DButtonIcon, DCard$1 as DCard, DCardBody, DCardFooter, DCardHeader, DCarousel$1 as DCarousel, DCarouselSlide, DChip, DCollapse, DConfirmModalContainer, DContext, DContextProvider, DCreditCard, DCurrencyText, DDataStateWrapper, DDatePicker, DDropdown, DErrorBoundary, DIcon, DIconBase, ForwardedDInput as DInput, DInputCheck, ForwardedDInputCounter as DInputCounter, ForwardedDInputCurrency as DInputCurrency, ForwardedDInputMask as DInputMask, ForwardedDInputPassword as DInputPassword, ForwardedDInputPhone as DInputPhone, DInputPin, ForwardedDInputRange as DInputRange, ForwardedDInputSearch as DInputSearch, DInputSelect, DInputSwitch, DLayout$1 as DLayout, DLayoutPane, DListGroup$1 as DListGroup, DListGroupItem, DModal$1 as DModal, DModalBody, DModalFooter, DModalHeader, DOffcanvas$1 as DOffcanvas, DOffcanvasBody, DOffcanvasFooter, DOffcanvasHeader, DOtp, DPaginator, DPasswordStrengthMeter, DPopover, DProgress, DSelect$1 as DSelect, DStepper, DStepper$2 as DStepperDesktop, DStepper$1 as DStepperMobile, DTabContent, DTabs$1 as DTabs, DTabsProvider, DTimeline, DToast$1 as DToast, DToastContainer, DTooltip, DVoucher, EmptyState, ErrorState, LoadingState, buildUrl, changeQueryString, checkMediaQuery, configureI8n as configureI18n, formatCurrency, getCssVariable, getQueryString, sanitizeHref, subscribeToMediaQuery, useConfirmModal, useCountdown, useDContext, useDPortalContext, useDToast, useDisableBodyScrollEffect, useDisableInputWheel, useFormatCurrency, useInputCurrency, useItemSelection, useMediaBreakpointUpLg, useMediaBreakpointUpMd, useMediaBreakpointUpSm, useMediaBreakpointUpXl, useMediaBreakpointUpXs, useMediaBreakpointUpXxl, useMediaQuery, useOtp, useProvidedRefOrCreate, useScreenshot, useScreenshotDownload, useScreenshotWebShare, useStackState, useTabContext, validatePhoneNumber };
3746
4713
  //# sourceMappingURL=index.esm.js.map