@dynamic-framework/ui-react 2.10.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 (145) 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 +2 -2
  9. package/dist/esm/components/DAlert/DAlert.js.map +1 -1
  10. package/dist/esm/components/DButtonIcon/DButtonIcon.js +6 -3
  11. package/dist/esm/components/DButtonIcon/DButtonIcon.js.map +1 -1
  12. package/dist/esm/components/DDataStateWrapper/DDataStateWrapper.js +10 -3
  13. package/dist/esm/components/DDataStateWrapper/DDataStateWrapper.js.map +1 -1
  14. package/dist/esm/components/DErrorBoundary/DErrorBoundary.js +3 -3
  15. package/dist/esm/components/DErrorBoundary/DErrorBoundary.js.map +1 -1
  16. package/dist/esm/components/DErrorBoundary/components/DefaultErrorBoundary.js +2 -2
  17. package/dist/esm/components/DErrorBoundary/components/DefaultErrorBoundary.js.map +1 -1
  18. package/dist/esm/components/DInput/DInput.js +15 -3
  19. package/dist/esm/components/DInput/DInput.js.map +1 -1
  20. package/dist/esm/components/DInputCheck/DInputCheck.js +45 -8
  21. package/dist/esm/components/DInputCheck/DInputCheck.js.map +1 -1
  22. package/dist/esm/components/DInputCounter/DInputCounter.js +78 -20
  23. package/dist/esm/components/DInputCounter/DInputCounter.js.map +1 -1
  24. package/dist/esm/components/DInputCurrency/DInputCurrency.js +8 -3
  25. package/dist/esm/components/DInputCurrency/DInputCurrency.js.map +1 -1
  26. package/dist/esm/components/DInputPhone/DInputPhone.js +14 -2
  27. package/dist/esm/components/DInputPhone/DInputPhone.js.map +1 -1
  28. package/dist/esm/components/DInputPin/DInputPin.js +3 -1
  29. package/dist/esm/components/DInputPin/DInputPin.js.map +1 -1
  30. package/dist/esm/components/DInputRange/DInputRange.js +16 -2
  31. package/dist/esm/components/DInputRange/DInputRange.js.map +1 -1
  32. package/dist/esm/components/DInputSelect/DInputSelect.js +18 -4
  33. package/dist/esm/components/DInputSelect/DInputSelect.js.map +1 -1
  34. package/dist/esm/components/DInputSwitch/DInputSwitch.js +20 -9
  35. package/dist/esm/components/DInputSwitch/DInputSwitch.js.map +1 -1
  36. package/dist/esm/components/DListGroup/DListGroup.js +10 -2
  37. package/dist/esm/components/DListGroup/DListGroup.js.map +1 -1
  38. package/dist/esm/components/DListGroup/ListGroupContext.js +11 -0
  39. package/dist/esm/components/DListGroup/ListGroupContext.js.map +1 -0
  40. package/dist/esm/components/DListGroup/components/DListGroupItem.js +34 -16
  41. package/dist/esm/components/DListGroup/components/DListGroupItem.js.map +1 -1
  42. package/dist/esm/components/DListGroup/components/warnInvalidListMarkup.js +30 -0
  43. package/dist/esm/components/DListGroup/components/warnInvalidListMarkup.js.map +1 -0
  44. package/dist/esm/components/DPasswordStrengthMeter/DPasswordStrengthMeter.js +2 -2
  45. package/dist/esm/components/DPasswordStrengthMeter/DPasswordStrengthMeter.js.map +1 -1
  46. package/dist/esm/components/DProgress/DProgress.js +9 -3
  47. package/dist/esm/components/DProgress/DProgress.js.map +1 -1
  48. package/dist/esm/components/DSelect/DSelect.js +31 -3
  49. package/dist/esm/components/DSelect/DSelect.js.map +1 -1
  50. package/dist/esm/components/DSelect/createAriaGuidance.js +57 -0
  51. package/dist/esm/components/DSelect/createAriaGuidance.js.map +1 -0
  52. package/dist/esm/components/DTabs/DTabs.js +29 -40
  53. package/dist/esm/components/DTabs/DTabs.js.map +1 -1
  54. package/dist/esm/components/DTabs/TabContext.js +3 -2
  55. package/dist/esm/components/DTabs/TabContext.js.map +1 -1
  56. package/dist/esm/components/DTabs/components/DTabContent.js +2 -2
  57. package/dist/esm/components/DTabs/components/DTabContent.js.map +1 -1
  58. package/dist/esm/components/DTabs/components/DTabsProvider.js +22 -0
  59. package/dist/esm/components/DTabs/components/DTabsProvider.js.map +1 -0
  60. package/dist/esm/components/DTabs/index.js +1 -0
  61. package/dist/esm/components/DTabs/index.js.map +1 -1
  62. package/dist/esm/components/DToast/DToast.js +7 -2
  63. package/dist/esm/components/DToast/DToast.js.map +1 -1
  64. package/dist/esm/components/DToastContainer/useDToast.js +5 -5
  65. package/dist/esm/components/DToastContainer/useDToast.js.map +1 -1
  66. package/dist/esm/components/internal/DFormLabel.js +46 -0
  67. package/dist/esm/components/internal/DFormLabel.js.map +1 -0
  68. package/dist/esm/contexts/DPortalContext.js +43 -8
  69. package/dist/esm/contexts/DPortalContext.js.map +1 -1
  70. package/dist/esm/contexts/portal/DPortalStack.js +12 -0
  71. package/dist/esm/contexts/portal/DPortalStack.js.map +1 -0
  72. package/dist/esm/hooks/useControlledState.js +51 -0
  73. package/dist/esm/hooks/useControlledState.js.map +1 -0
  74. package/dist/esm/hooks/useInputCurrency.js +73 -19
  75. package/dist/esm/hooks/useInputCurrency.js.map +1 -1
  76. package/dist/esm/hooks/useMediaBreakpointUp.js +42 -3
  77. package/dist/esm/hooks/useMediaBreakpointUp.js.map +1 -1
  78. package/dist/esm/hooks/useResponsiveProp.js +13 -2
  79. package/dist/esm/hooks/useResponsiveProp.js.map +1 -1
  80. package/dist/esm/index.js +1 -0
  81. package/dist/esm/index.js.map +1 -1
  82. package/dist/esm/utils/hasLabelContent.js +48 -0
  83. package/dist/esm/utils/hasLabelContent.js.map +1 -0
  84. package/dist/esm/utils/isTextLabel.js +23 -0
  85. package/dist/esm/utils/isTextLabel.js.map +1 -0
  86. package/dist/esm/utils/labelClickGuard.js +61 -0
  87. package/dist/esm/utils/labelClickGuard.js.map +1 -0
  88. package/dist/esm/utils/warnLabelUsage.js +57 -0
  89. package/dist/esm/utils/warnLabelUsage.js.map +1 -0
  90. package/dist/icons-core.json +1 -1
  91. package/dist/index.esm.js +865 -163
  92. package/dist/index.esm.js.map +1 -1
  93. package/dist/index.js +864 -161
  94. package/dist/index.js.map +1 -1
  95. package/dist/types/components/DAlert/DAlert.d.ts +16 -2
  96. package/dist/types/components/DButtonIcon/DButtonIcon.d.ts +9 -1
  97. package/dist/types/components/DDataStateWrapper/DDataStateWrapper.d.ts +29 -4
  98. package/dist/types/components/DDataStateWrapper/index.d.ts +1 -1
  99. package/dist/types/components/DErrorBoundary/DErrorBoundary.d.ts +6 -1
  100. package/dist/types/components/DErrorBoundary/components/DefaultErrorBoundary.d.ts +3 -1
  101. package/dist/types/components/DInput/DInput.d.ts +11 -2
  102. package/dist/types/components/DInputCheck/DInputCheck.d.ts +28 -3
  103. package/dist/types/components/DInputCounter/DInputCounter.d.ts +15 -3
  104. package/dist/types/components/DInputCurrency/DInputCurrency.d.ts +10 -3
  105. package/dist/types/components/DInputPhone/DInputPhone.d.ts +11 -2
  106. package/dist/types/components/DInputPin/DInputPin.d.ts +12 -2
  107. package/dist/types/components/DInputRange/DInputRange.d.ts +15 -2
  108. package/dist/types/components/DInputSelect/DInputSelect.d.ts +18 -3
  109. package/dist/types/components/DInputSwitch/DInputSwitch.d.ts +28 -3
  110. package/dist/types/components/DListGroup/DListGroup.d.ts +12 -1
  111. package/dist/types/components/DListGroup/ListGroupContext.d.ts +8 -0
  112. package/dist/types/components/DListGroup/components/DListGroupItem.d.ts +6 -1
  113. package/dist/types/components/DListGroup/components/warnInvalidListMarkup.d.ts +13 -0
  114. package/dist/types/components/DPasswordStrengthMeter/DPasswordStrengthMeter.d.ts +18 -3
  115. package/dist/types/components/DProgress/DProgress.d.ts +13 -1
  116. package/dist/types/components/DSelect/DSelect.d.ts +23 -3
  117. package/dist/types/components/DSelect/createAriaGuidance.d.ts +21 -0
  118. package/dist/types/components/DTabs/DTabs.d.ts +13 -2
  119. package/dist/types/components/DTabs/TabContext.d.ts +5 -0
  120. package/dist/types/components/DTabs/components/DTabsProvider.d.ts +11 -0
  121. package/dist/types/components/DTabs/index.d.ts +1 -0
  122. package/dist/types/components/DToast/DToast.d.ts +10 -3
  123. package/dist/types/components/DToastContainer/useDToast.d.ts +8 -1
  124. package/dist/types/components/index.d.ts +2 -2
  125. package/dist/types/components/interface.d.ts +7 -0
  126. package/dist/types/components/internal/DFormLabel.d.ts +30 -0
  127. package/dist/types/contexts/portal/DPortalStack.d.ts +13 -0
  128. package/dist/types/hooks/useControlledState.d.ts +17 -0
  129. package/dist/types/hooks/useInputCurrency.d.ts +26 -1
  130. package/dist/types/hooks/useMediaBreakpointUp.d.ts +10 -0
  131. package/dist/types/utils/hasLabelContent.d.ts +30 -0
  132. package/dist/types/utils/isTextLabel.d.ts +17 -0
  133. package/dist/types/utils/labelClickGuard.d.ts +20 -0
  134. package/dist/types/utils/warnLabelUsage.d.ts +43 -0
  135. package/package.json +8 -3
  136. package/src/style/abstracts/variables/_body.scss +2 -2
  137. package/src/style/abstracts/variables/_chips.scss +2 -0
  138. package/src/style/abstracts/variables/_colors.scss +2 -2
  139. package/src/style/base/_buttons.scss +4 -1
  140. package/src/style/base/_input-group.scss +5 -2
  141. package/src/style/base/_list-group.scss +55 -0
  142. package/src/style/components/_d-chip.scss +7 -2
  143. package/src/style/components/_d-tabs.scss +3 -0
  144. package/src/style/helpers/_color-bg.scss +5 -0
  145. package/src/style/root/_root.scss +9 -0
package/dist/index.js CHANGED
@@ -7,9 +7,9 @@ var tslib = require('tslib');
7
7
  var LucideIcons = require('lucide-react');
8
8
  var reactIs = require('react-is');
9
9
  var reactDom = require('react-dom');
10
- var framerMotion = require('framer-motion');
11
10
  var fileSelector = require('file-selector');
12
11
  var reactSplide = require('@splidejs/react-splide');
12
+ var framerMotion = require('framer-motion');
13
13
  var currency = require('currency.js');
14
14
  var DatePicker = require('react-datepicker');
15
15
  var dateFns = require('date-fns');
@@ -171,11 +171,30 @@ function getKeyboardFocusableElements(container) {
171
171
  ].filter((element) => !element.hasAttribute('disabled'));
172
172
  }
173
173
 
174
+ const loadPortalStack = () => Promise.resolve().then(function () { return require('./DPortalStack-6zJm6JoD.js'); }).then((module) => module.default);
174
175
  const DPortalContext = React.createContext(undefined);
175
176
  function DPortalContextProvider({ portalName, children, availablePortals, }) {
176
177
  const { created } = usePortal(portalName);
177
178
  const [stack, { push, pop }] = useStackState([]);
179
+ const [PortalStack, setPortalStack] = React.useState(null);
180
+ const pending = React.useRef([]);
181
+ const ready = React.useRef(false);
178
182
  useDisableBodyScrollEffect(Boolean(stack.length));
183
+ // Loads framer-motion off the critical path only when portals are configured,
184
+ // so the first openPortal does not wait for it.
185
+ const hasPortals = Object.keys(availablePortals !== null && availablePortals !== void 0 ? availablePortals : {}).length > 0;
186
+ React.useEffect(() => {
187
+ if (!hasPortals)
188
+ return;
189
+ loadPortalStack()
190
+ .then((Stack) => {
191
+ ready.current = true;
192
+ setPortalStack(() => Stack);
193
+ })
194
+ .catch(() => {
195
+ // openPortal retries the import and reports the failure.
196
+ });
197
+ }, [hasPortals]);
179
198
  const openPortal = React.useCallback(
180
199
  // eslint-disable-next-line prefer-arrow-callback
181
200
  function openPortalImpl(name, payload) {
@@ -191,10 +210,30 @@ function DPortalContextProvider({ portalName, children, availablePortals, }) {
191
210
  }
192
211
  // K is a specific member of keyof T & string so the object satisfies
193
212
  // InternalStackItem<T>, but TS can't verify generic-over-union assignability.
194
- push({ name, Component, payload });
213
+ const item = { name, Component, payload };
214
+ if (ready.current) {
215
+ push(item);
216
+ }
217
+ else {
218
+ // Not loaded yet: the portal enters the stack together with its renderer.
219
+ pending.current.push(item);
220
+ loadPortalStack().then((Stack) => {
221
+ ready.current = true;
222
+ setPortalStack(() => Stack);
223
+ pending.current.splice(0).forEach(push);
224
+ }).catch((error) => {
225
+ pending.current = [];
226
+ // eslint-disable-next-line no-console
227
+ console.error('[DPortalContext] Could not load the portal stack', error);
228
+ });
229
+ }
195
230
  (_a = document.activeElement) === null || _a === void 0 ? void 0 : _a.blur();
196
231
  }, [availablePortals, push]);
197
232
  const closePortal = React.useCallback(() => {
233
+ if (pending.current.length > 0) {
234
+ pending.current.pop();
235
+ return;
236
+ }
198
237
  // pop() is safe on empty stacks, so close remains idempotent.
199
238
  pop();
200
239
  }, [pop]);
@@ -251,13 +290,10 @@ function DPortalContextProvider({ portalName, children, availablePortals, }) {
251
290
  window.removeEventListener('keydown', keyEvent);
252
291
  };
253
292
  }, [handleClose, portalName, stack.length]);
254
- return (jsxRuntime.jsxs(DPortalContext.Provider, { value: value, children: [children, created && reactDom.createPortal(
293
+ return (jsxRuntime.jsxs(DPortalContext.Provider, { value: value, children: [children, created && PortalStack && reactDom.createPortal(
255
294
  // eslint-disable-next-line max-len
256
295
  // eslint-disable-next-line jsx-a11y/no-static-element-interactions
257
- jsxRuntime.jsx("div", { onClick: ({ target }) => handleClose(target), onKeyDown: () => { }, children: jsxRuntime.jsx(framerMotion.AnimatePresence, { children: stack.flatMap(({ Component, name, payload, }) => [
258
- jsxRuntime.jsx(framerMotion.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`),
259
- jsxRuntime.jsx(Component, { name: name, payload: payload }, name),
260
- ]) }) }), document.getElementById(portalName))] }));
296
+ jsxRuntime.jsx("div", { onClick: ({ target }) => handleClose(target), onKeyDown: () => { }, children: jsxRuntime.jsx(PortalStack, { stack: stack }) }), document.getElementById(portalName))] }));
261
297
  }
262
298
  /**
263
299
  * Hook to open/close registered portals (modals, offcanvas, etc.).
@@ -532,9 +568,43 @@ function useMediaQuery(mediaQuery, useListener = false) {
532
568
  return React.useSyncExternalStore(useListener ? (cb) => subscribeToMediaQuery(mediaQuery, cb) : noop, () => (mediaQuery ? checkMediaQuery(mediaQuery) : true), () => false);
533
569
  }
534
570
 
535
- function useMediaBreakpointUp(breakpoint, useListener = false) {
571
+ const BREAKPOINTS = ['xs', 'sm', 'md', 'lg', 'xl', 'xxl'];
572
+ // Breakpoints read from the CSS on first use and shared by every component
573
+ // that resolves responsive props: one computed-style read for the page,
574
+ // instead of one per breakpoint per render. `dynamic-ui.css` has to be loaded
575
+ // before rendering; if it isn't, the empty result is kept too, so a
576
+ // misconfigured tree doesn't pay the read on every render (responsive props
577
+ // then keep their default value and useResponsiveProp warns in development).
578
+ let cssBreakpoints = null;
579
+ function readCssBreakpoint(breakpoint) {
580
+ if (cssBreakpoints)
581
+ return cssBreakpoints[breakpoint];
582
+ if (typeof document === 'undefined')
583
+ return '';
584
+ const style = getComputedStyle(document.documentElement);
585
+ const read = Object.fromEntries(BREAKPOINTS.map((name) => [
586
+ name,
587
+ style.getPropertyValue(`--${PREFIX_BS}breakpoint-${name}`).trim(),
588
+ ]));
589
+ cssBreakpoints = read;
590
+ return read[breakpoint];
591
+ }
592
+ /**
593
+ * Pixel value of a breakpoint. `DContextProvider` reads them from the CSS and
594
+ * shares them through the context; outside of it (a tree without the
595
+ * provider, or its first render) they are read from the same CSS variables
596
+ * here, so responsive props resolve either way.
597
+ */
598
+ function useBreakpointValue(breakpoint) {
536
599
  const { breakpoints } = useDContext();
537
- const mediaQuery = React.useMemo(() => (`(min-width: ${breakpoints[breakpoint]})`), [breakpoint, breakpoints]);
600
+ const fromContext = breakpoints[breakpoint];
601
+ return React.useMemo(() => fromContext || readCssBreakpoint(breakpoint), [fromContext, breakpoint]);
602
+ }
603
+ function useMediaBreakpointUp(breakpoint, useListener = false) {
604
+ const value = useBreakpointValue(breakpoint);
605
+ // Without a breakpoint the query can't match; `not all` keeps it false
606
+ // instead of an invalid `(min-width: )`.
607
+ const mediaQuery = value ? `(min-width: ${value})` : 'not all';
538
608
  return useMediaQuery(mediaQuery, useListener);
539
609
  }
540
610
  function useMediaBreakpointUpXs(useListener = false) {
@@ -556,6 +626,7 @@ function useMediaBreakpointUpXxl(useListener = false) {
556
626
  return useMediaBreakpointUp('xxl', useListener);
557
627
  }
558
628
 
629
+ let warnedMissingBreakpoints = false;
559
630
  /**
560
631
  * React hook to resolve a responsive property value based on the current viewport breakpoint.
561
632
  *
@@ -581,7 +652,16 @@ function useResponsiveProp(useListener = false) {
581
652
  const bpLgUp = useMediaBreakpointUpLg(useListener);
582
653
  const bpXlUp = useMediaBreakpointUpXl(useListener);
583
654
  const bpXxlUp = useMediaBreakpointUpXxl(useListener);
655
+ // `xs` is `0` and can't tell a missing variable apart, so `sm` is checked.
656
+ const hasBreakpoints = !!useBreakpointValue('sm');
584
657
  const responsivePropValue = React.useCallback((prop) => {
658
+ if (process.env.NODE_ENV !== 'production' && !hasBreakpoints && !warnedMissingBreakpoints) {
659
+ warnedMissingBreakpoints = true;
660
+ // eslint-disable-next-line no-console
661
+ console.warn(`[Dynamic UI] The --${PREFIX_BS}breakpoint-* CSS variables are not available, so a `
662
+ + 'responsive prop (an object by breakpoint) falls back to its default value. Load '
663
+ + 'dynamic-ui.css before rendering. It never appears in production builds.');
664
+ }
585
665
  // Pick the highest matched breakpoint value that is defined in prop
586
666
  if (prop.xxl !== undefined && bpXxlUp)
587
667
  return prop.xxl;
@@ -597,7 +677,7 @@ function useResponsiveProp(useListener = false) {
597
677
  return prop.xs;
598
678
  // Fallback: return undefined if no breakpoint matches
599
679
  return undefined;
600
- }, [bpSmUp, bpMdUp, bpLgUp, bpXlUp, bpXxlUp, bpXsUp]);
680
+ }, [bpSmUp, bpMdUp, bpLgUp, bpXlUp, bpXxlUp, bpXsUp, hasBreakpoints]);
601
681
  return { responsivePropValue };
602
682
  }
603
683
 
@@ -748,12 +828,12 @@ function DIcon(_a) {
748
828
  return (jsxRuntime.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)));
749
829
  }
750
830
 
751
- function DAlert({ color = 'success', icon: iconProp, iconFamilyClass, iconFamilyPrefix, iconMaterialStyle, iconClose: iconCloseProp, iconCloseFamilyClass, iconCloseFamilyPrefix, iconCloseMaterialStyle, showClose, onClose, children, id, className, style, dataAttributes, }) {
831
+ 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, }) {
752
832
  const { icon: { materialStyle, familyClass, familyPrefix, }, iconMap: { alert, xLg, }, } = useDContext();
753
833
  const icon = React.useMemo(() => iconProp || alert[color], [alert, iconProp, color]);
754
834
  const iconClose = React.useMemo(() => (iconCloseProp || xLg), [iconCloseProp, xLg]);
755
835
  const generateClasses = React.useMemo(() => (Object.assign({ alert: true, [`alert-${color}`]: true, 'fade show': !!showClose }, className && { [className]: true })), [color, showClose, className]);
756
- return (jsxRuntime.jsxs("div", Object.assign({ className: classNames(generateClasses), style: style, role: "alert", id: id }, dataAttributes, { children: [icon && (jsxRuntime.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 })), jsxRuntime.jsx("div", { className: "alert-text", children: children }), showClose && (jsxRuntime.jsx("button", { type: "button", className: "d-close", "aria-label": "Close", onClick: onClose, children: jsxRuntime.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 }) }))] })));
836
+ return (jsxRuntime.jsxs("div", Object.assign({ className: classNames(generateClasses), style: style }, role !== 'none' && { role }, { id: id }, dataAttributes, { children: [showIcon && icon && (jsxRuntime.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 })), jsxRuntime.jsx("div", { className: "alert-text", children: children }), showClose && (jsxRuntime.jsx("button", { type: "button", className: "d-close", "aria-label": closeAriaLabel, onClick: onClose, children: jsxRuntime.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 }) }))] })));
757
837
  }
758
838
 
759
839
  function DAvatar({ id, size, image, name: nameProp, useNameAsInitials = false, className, style, dataAttributes, }) {
@@ -819,6 +899,223 @@ function useProvidedRefOrCreate(providedRef) {
819
899
  return providedRef !== null && providedRef !== void 0 ? providedRef : createdRef;
820
900
  }
821
901
 
902
+ /**
903
+ * Descendants the browser already exempts from a label's click forwarding.
904
+ *
905
+ * Per the HTML spec a label does nothing for events targeted at its interactive
906
+ * content descendants, so these need — and must get — no help from us: calling
907
+ * `preventDefault` on a click targeting a link, or on an image map area, would
908
+ * cancel the navigation it was meant to perform.
909
+ *
910
+ * The list mirrors the spec's interactive content, image maps included, since
911
+ * one of those carrying a `tabindex` would otherwise fall through to the
912
+ * pseudo-interactive branch below and be suppressed by mistake.
913
+ */
914
+ const NATIVE_INTERACTIVE = 'a[href], area[href], button, input, select, textarea, details, summary, img[usemap], object[usemap], audio[controls], video[controls], embed, iframe';
915
+ /**
916
+ * Descendants that act like controls without being interactive content.
917
+ *
918
+ * A tooltip or modal trigger built from a `span` with a role and a tabindex is
919
+ * the common case. Browsers do not exempt these, so a click on one activates
920
+ * the labelled control as well.
921
+ */
922
+ const PSEUDO_INTERACTIVE = '[role="button"], [role="link"], [role="checkbox"], [role="switch"], [role="menuitem"], [tabindex]';
923
+ /**
924
+ * Stops a click on a non-native trigger inside a `<label>` from also activating
925
+ * the labelled control.
926
+ *
927
+ * Verified against Chromium, which forwards the click for every one of the
928
+ * `PSEUDO_INTERACTIVE` selectors above while exempting the native ones. Note
929
+ * that jsdom does not reproduce this: its `isInteractiveContent` counts any
930
+ * element carrying a `tabindex` as interactive content, so a jsdom test passes
931
+ * whether or not this guard exists. That is why the guard is unit-tested
932
+ * directly rather than through a rendered component.
933
+ *
934
+ * Native interactive content returns early rather than falling through, since
935
+ * `preventDefault` on those clicks would cancel the link navigation or the
936
+ * image map the label is meant to let through.
937
+ *
938
+ * `DFormLabel` wires this in the capture phase so a trigger that stops
939
+ * propagation cannot skip it.
940
+ */
941
+ function labelClickGuard(event) {
942
+ const label = event.currentTarget;
943
+ const target = event.target;
944
+ if (!(target === null || target === void 0 ? void 0 : target.closest))
945
+ return;
946
+ /**
947
+ * `closest` walks the whole ancestor chain, so a match may sit above the
948
+ * label — a card with a `role` or a `tabindex` wrapping the control and its
949
+ * label, say. Acting on one of those would suppress the plain label text's
950
+ * own click and leave the control unresponsive, so only matches inside the
951
+ * label count.
952
+ */
953
+ const inside = (match) => (!!match && match !== label && label.contains(match));
954
+ if (inside(target.closest(NATIVE_INTERACTIVE)))
955
+ return;
956
+ if (inside(target.closest(PSEUDO_INTERACTIVE))) {
957
+ event.preventDefault();
958
+ }
959
+ }
960
+
961
+ /**
962
+ * The `<label>` every form control renders, with the click guard already wired.
963
+ *
964
+ * Internal: it exists so the guard — and the lint exception it needs — lives in
965
+ * one place instead of being repeated across every input component.
966
+ *
967
+ * The guard runs in the capture phase, not on bubble: a tooltip or modal
968
+ * trigger that calls `stopPropagation()` in its own handler — a common pattern —
969
+ * would otherwise keep the guard from ever running, and the browser would go on
970
+ * to activate the labelled control. Confirmed in Chromium, where the same
971
+ * trigger toggles the control with a bubble-phase guard and does not with this
972
+ * one. Capturing changes nothing else: `preventDefault` applies to the default
973
+ * action, which is evaluated after propagation either way.
974
+ *
975
+ * The `jsx-a11y` rules below assume a click handler is being used to make a
976
+ * non-interactive element interactive, which then owes the keyboard an
977
+ * equivalent. This handler does the opposite: it suppresses a pointer-only
978
+ * behaviour, the click a label forwards to its control, and adds no way to
979
+ * activate anything. Keyboard support for a nested trigger is the consumer's:
980
+ * `<a href>` carries its own, while a `span` with a role and a tabindex is
981
+ * focusable but inert until they handle Enter and Space themselves.
982
+ */
983
+ function DFormLabel({ htmlFor, className, children }) {
984
+ /**
985
+ * React 19 renders a bigint child; React 18 throws on one, and this package
986
+ * supports both (`react: >=18 <20`). The label helpers count bigint as text
987
+ * because that is what it draws as, so it is turned into its string form here
988
+ * — the one place every control's label passes through — rather than pushing
989
+ * a version check onto consumers.
990
+ *
991
+ * Only a bigint at the top level: one buried inside an array or a fragment is
992
+ * the consumer's own React child, and normalising it would mean walking and
993
+ * rebuilding the whole subtree.
994
+ */
995
+ const content = typeof children === 'bigint' ? String(children) : children;
996
+ return (
997
+ /* eslint-disable-next-line jsx-a11y/click-events-have-key-events,
998
+ jsx-a11y/no-noninteractive-element-interactions */
999
+ jsxRuntime.jsx("label", { htmlFor: htmlFor, className: className, onClickCapture: labelClickGuard, children: content }));
1000
+ }
1001
+
1002
+ /**
1003
+ * Whether a `label` node has something to render.
1004
+ *
1005
+ * The render paths cannot test `label` for truthiness: since the prop widened
1006
+ * to `ReactNode` it also accepts `0`, which React renders as the text "0" and
1007
+ * which `isTextLabel` counts as a text label, yet which a truthiness check
1008
+ * would drop — leaving the control visibly unlabelled.
1009
+ *
1010
+ * Absent (`undefined`, `null`), empty (`''`) and boolean labels render nothing,
1011
+ * so they are the ones that stand for "no label". The empty string matters
1012
+ * because it is the default several components fall back to.
1013
+ *
1014
+ * Arrays are inspected rather than taken at face value: `label={items.map(...)}`
1015
+ * over an empty list yields `[]`, while a list of skipped entries yields
1016
+ * `[false, null]`. Both render nothing, so treating them as content would leave
1017
+ * an empty `<label>` on the page — and make the naming warning fire over a
1018
+ * label that was never there.
1019
+ *
1020
+ * Only arrays, though, even though `ReactNode` admits any iterable. Reading a
1021
+ * one-shot iterator — a generator — would exhaust it here, and the component
1022
+ * goes on to render the same value, so the label would vanish precisely because
1023
+ * we checked for it. An array can be walked as often as needed; anything else
1024
+ * is taken on faith.
1025
+ *
1026
+ * Anything else — an element, a portal, a generator — counts as content: what
1027
+ * it renders is only knowable by rendering it, and an element that draws
1028
+ * nothing visible is the consumer's decision rather than an absent label.
1029
+ */
1030
+ function hasLabelContent(label) {
1031
+ if (label === undefined || label === null || typeof label === 'boolean') {
1032
+ return false;
1033
+ }
1034
+ if (typeof label === 'string') {
1035
+ return label !== '';
1036
+ }
1037
+ if (React.isValidElement(label)) {
1038
+ return true;
1039
+ }
1040
+ if (Array.isArray(label)) {
1041
+ return label.some(hasLabelContent);
1042
+ }
1043
+ return true;
1044
+ }
1045
+
1046
+ /**
1047
+ * Narrows a `label` node to the plain-text case.
1048
+ *
1049
+ * `label` accepts any `ReactNode` so patterns like a terms-and-conditions link
1050
+ * or an info trigger can live next to the field name. Anything that is not text
1051
+ * cannot be forwarded to a string-only attribute (`aria-label`, `placeholder`)
1052
+ * nor laid out by Bootstrap's `form-floating`, so those paths check this first.
1053
+ *
1054
+ * Numbers and bigints count as text: React renders them as their string form
1055
+ * and so does `String()`, which is what the string-only attributes end up
1056
+ * receiving. `bigint` is part of `ReactNode` under these React types, so
1057
+ * leaving it out made a label React draws as text get treated as a rich node.
1058
+ * React 18 cannot render one, which `DFormLabel` handles by rendering its
1059
+ * string form — the classification stays true for every supported React.
1060
+ */
1061
+ function isTextLabel(label) {
1062
+ return typeof label === 'string'
1063
+ || typeof label === 'number'
1064
+ || typeof label === 'bigint';
1065
+ }
1066
+
1067
+ /**
1068
+ * Components already reported, keyed by component and reason, so the same
1069
+ * mistake only warns once per page load however often it re-renders or however
1070
+ * many instances exist.
1071
+ */
1072
+ const warnedLabels = new Set();
1073
+ function warnOnce(key, message) {
1074
+ if (warnedLabels.has(key))
1075
+ return;
1076
+ warnedLabels.add(key);
1077
+ // eslint-disable-next-line no-console
1078
+ console.warn(`[Dynamic UI] ${message} It never appears in production builds.`);
1079
+ }
1080
+ /**
1081
+ * Warns, only outside production builds, about the two ways a non-text `label`
1082
+ * goes wrong.
1083
+ *
1084
+ * A text label doubles as the control's accessible name. A `ReactNode` one does
1085
+ * not: the name becomes whatever the subtree happens to compute to, which for a
1086
+ * label carrying a link or an icon trigger reads as the wrong thing or as
1087
+ * nothing at all — so an explicit name is needed. Which prop carries it differs
1088
+ * per component, hence `accessibleNameProp`: naming the wrong one would send
1089
+ * the developer to a prop their component rejects.
1090
+ *
1091
+ * `form-floating` is the second case: it animates a single line of text between
1092
+ * the placeholder and the label position, so a node with its own height or its
1093
+ * own interactive children breaks the layout rather than the semantics.
1094
+ *
1095
+ * Labels the render paths drop are skipped through the same `hasLabelContent`
1096
+ * they use, so `label={condition && <span />}` with a false condition warns
1097
+ * about nothing: there is no label on the page to name.
1098
+ *
1099
+ * The `process.env.NODE_ENV` guard belongs at the call site — that is what
1100
+ * bundlers constant-fold, dropping this module from a consumer's production
1101
+ * bundle.
1102
+ */
1103
+ function warnLabelUsage({ component, label, hasAccessibleName, accessibleNameProp, floatingLabel = false, }) {
1104
+ if (!hasLabelContent(label) || isTextLabel(label))
1105
+ return;
1106
+ if (!hasAccessibleName) {
1107
+ warnOnce(`${component}:name`, `${component}: a non-text "label" does not give the control a reliable accessible name. `
1108
+ + `Pass ${accessibleNameProp} with the plain-text name of the field, since the label `
1109
+ + 'subtree may include links, icons or markup that read as the wrong name or as none at all.');
1110
+ }
1111
+ if (floatingLabel) {
1112
+ warnOnce(`${component}:floating`, `${component}: "floatingLabel" expects a text "label". `
1113
+ + 'The floating layout animates a single line of text, so a node with its own height '
1114
+ + 'or its own interactive children overflows the control. '
1115
+ + 'Use the default label layout for rich labels.');
1116
+ }
1117
+ }
1118
+
822
1119
  function DInput(_a, ref) {
823
1120
  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 = tslib.__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"]);
824
1121
  const inputRef = useProvidedRefOrCreate(ref);
@@ -858,7 +1155,7 @@ function DInput(_a, ref) {
858
1155
  const inputComponent = React.useMemo(() => (jsxRuntime.jsx("input", Object.assign({ ref: inputRef, id: id, className: classNames('form-control', {
859
1156
  'is-invalid': invalid,
860
1157
  'is-valid': valid,
861
- }), disabled: disabled || loading, readOnly: readonly, value: value, onChange: handleOnChange }, (floatingLabel || placeholder) && { placeholder: floatingLabel ? '' : placeholder }, ariaDescribedby && { 'aria-describedby': ariaDescribedby }, inputProps))), [
1158
+ }), disabled: disabled || loading, readOnly: readonly, value: value, onChange: handleOnChange }, (floatingLabel || placeholder) && { placeholder: floatingLabel ? '' : placeholder }, ariaDescribedby && { 'aria-describedby': ariaDescribedby }, invalid && { 'aria-invalid': true }, inputProps))), [
862
1159
  ariaDescribedby,
863
1160
  disabled,
864
1161
  handleOnChange,
@@ -873,7 +1170,7 @@ function DInput(_a, ref) {
873
1170
  value,
874
1171
  readonly,
875
1172
  ]);
876
- const labelComponent = React.useMemo(() => (jsxRuntime.jsx("label", { htmlFor: id, children: label })), [
1173
+ const labelComponent = React.useMemo(() => (jsxRuntime.jsx(DFormLabel, { htmlFor: id, children: label })), [
877
1174
  id,
878
1175
  label,
879
1176
  ]);
@@ -883,7 +1180,16 @@ function DInput(_a, ref) {
883
1180
  }
884
1181
  return inputComponent;
885
1182
  }, [floatingLabel, inputComponent, labelComponent]);
886
- return (jsxRuntime.jsxs("div", Object.assign({ className: className, style: style }, dataAttributes, { children: [label && !floatingLabel && labelComponent, jsxRuntime.jsxs("div", { className: classNames({
1183
+ if (process.env.NODE_ENV !== 'production') {
1184
+ warnLabelUsage({
1185
+ component: 'DInput',
1186
+ label,
1187
+ hasAccessibleName: !!inputProps['aria-label'] || !!inputProps['aria-labelledby'],
1188
+ accessibleNameProp: 'aria-label',
1189
+ floatingLabel,
1190
+ });
1191
+ }
1192
+ return (jsxRuntime.jsxs("div", Object.assign({ className: className, style: style }, dataAttributes, { children: [hasLabelContent(label) && !floatingLabel && labelComponent, jsxRuntime.jsxs("div", { className: classNames({
887
1193
  [`input-group-${size}`]: !!size,
888
1194
  'input-group': true,
889
1195
  'has-validation': invalid || valid,
@@ -1409,7 +1715,7 @@ function warnMissingAccessibleName(icon, kind) {
1409
1715
  }
1410
1716
 
1411
1717
  function DButtonIcon(_a) {
1412
- 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 = tslib.__rest(_a, ["id", "icon", "size", "className", "variant", "state", "loadingAriaLabel", "iconMaterialStyle", "disabled", "color", "loading", "href", "target", "rel", "stopPropagationEnabled", "style", "iconFamilyClass", "iconFamilyPrefix", "dataAttributes", "onClick", 'aria-label']);
1718
+ 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 = tslib.__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']);
1413
1719
  const { icon: { familyClass, familyPrefix, materialStyle, }, } = useDContext();
1414
1720
  const generateClasses = React.useMemo(() => {
1415
1721
  const variantClass = !variant || variant === 'solid'
@@ -1417,6 +1723,9 @@ function DButtonIcon(_a) {
1417
1723
  : `btn-${variant}-${color}`;
1418
1724
  return Object.assign(Object.assign(Object.assign({ 'btn d-button-icon': true, [variantClass]: true }, size && { [`btn-${size}`]: true }), (state && state !== 'disabled') && { [state]: true }), { loading });
1419
1725
  }, [variant, color, size, state, loading]);
1726
+ // A responsive iconSize has to follow viewport changes, so DIcon only
1727
+ // listens to breakpoints when it gets an object; a plain string needs none.
1728
+ const useIconSizeListener = typeof iconSize === 'object';
1420
1729
  const isDisabled = React.useMemo(() => (state === 'disabled' || loading || disabled), [state, loading, disabled]);
1421
1730
  const clickHandler = React.useCallback((event) => {
1422
1731
  if (stopPropagationEnabled) {
@@ -1443,11 +1752,11 @@ function DButtonIcon(_a) {
1443
1752
  if (href) {
1444
1753
  return (jsxRuntime.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
1445
1754
  ? (jsxRuntime.jsx("span", { className: "spinner-border spinner-border-sm", role: "status", "aria-hidden": "true", children: jsxRuntime.jsx("span", { className: "visually-hidden", children: "Loading..." }) }))
1446
- : (jsxRuntime.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 })) })));
1755
+ : (jsxRuntime.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 })) })));
1447
1756
  }
1448
1757
  return (jsxRuntime.jsx("button", Object.assign({ className: classNames(generateClasses, className), style: style, disabled: state === 'disabled' || loading, onClick: clickHandler, "aria-label": ariaLabel }, dataAttributes, rest, { children: loading
1449
1758
  ? (jsxRuntime.jsx("span", { className: "spinner-border spinner-border-sm", role: "status", "aria-hidden": "true", children: jsxRuntime.jsx("span", { className: "visually-hidden", children: "Loading..." }) }))
1450
- : (jsxRuntime.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 })) })));
1759
+ : (jsxRuntime.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 })) })));
1451
1760
  }
1452
1761
 
1453
1762
  function DCardHeader({ className, style, children, }) {
@@ -1643,24 +1952,102 @@ function DSelectPlaceholder(_a) {
1643
1952
  return (jsxRuntime.jsx(Select.components.Placeholder, Object.assign({ innerProps: innerProps, selectProps: selectProps }, props, { children: children })));
1644
1953
  }
1645
1954
 
1955
+ /**
1956
+ * Name of the elements an `aria-labelledby` points at, joined as the accessible
1957
+ * name computation does. Read when the message is built rather than when the
1958
+ * select renders, since the referenced elements may render after it.
1959
+ *
1960
+ * Each element contributes its own `aria-label` when it has one, and its text
1961
+ * otherwise. That covers the targets a select is labelled by in practice — a
1962
+ * heading, a span, a labelled region — without shipping the full accessible
1963
+ * name algorithm; names derived from descendants' `aria-label`, `alt` or
1964
+ * hidden content are not resolved, so pass `ariaLiveMessages` for those.
1965
+ */
1966
+ function resolveLabelledBy(labelledBy) {
1967
+ if (typeof document === 'undefined')
1968
+ return '';
1969
+ return labelledBy
1970
+ .split(/\s+/)
1971
+ .map((ref) => {
1972
+ var _a, _b;
1973
+ const element = document.getElementById(ref);
1974
+ 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());
1975
+ })
1976
+ .filter(Boolean)
1977
+ .join(' ');
1978
+ }
1979
+ /**
1980
+ * Builds react-select's `guidance` live message around the control's name.
1981
+ *
1982
+ * react-select announces "<aria-label> is focused" on the first focus of the
1983
+ * input, falling back to "Select" when there is no `aria-label`. Neither a text
1984
+ * `label` nor `aria-labelledby` reaches that message, so the announcement would
1985
+ * disagree with the name assistive technology exposes.
1986
+ *
1987
+ * The name follows the accessible name precedence: `aria-labelledby`, then
1988
+ * `aria-label`, then the `<label>`. react-select does not export its default
1989
+ * messages, so this mirrors them for every context and only swaps the name.
1990
+ */
1991
+ function createAriaGuidance({ labelledBy, label }) {
1992
+ return ({ 'aria-label': ariaLabel, context, isSearchable, isMulti, tabSelectsValue, isInitialFocus, }) => {
1993
+ switch (context) {
1994
+ case 'menu':
1995
+ 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' : ''}.`;
1996
+ case 'input': {
1997
+ if (!isInitialFocus)
1998
+ return '';
1999
+ const name = (labelledBy && resolveLabelledBy(labelledBy)) || ariaLabel || label || 'Select';
2000
+ return `${name} is focused ${isSearchable ? ',type to refine list' : ''}, press Down to open the menu, ${isMulti ? ' press left to focus selected values' : ''}`;
2001
+ }
2002
+ case 'value':
2003
+ return 'Use left and right to toggle between focused values, press Backspace to remove the currently focused value';
2004
+ default:
2005
+ return '';
2006
+ }
2007
+ };
2008
+ }
2009
+
1646
2010
  function DSelect$1(_a) {
1647
- 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 = tslib.__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"]);
2011
+ 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 = tslib.__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"]);
1648
2012
  const innerId = React.useId();
1649
2013
  const id = React.useMemo(() => idProp || innerId, [idProp, innerId]);
2014
+ // The `<label>` must point at the element react-select renders the input
2015
+ // with, so an explicit `inputId` wins over `id` for both.
2016
+ const inputId = inputIdProp || id;
2017
+ // A text label or `aria-labelledby` names the control without going through
2018
+ // `aria-label`, which is all react-select's focus announcement reads, so it
2019
+ // is handed those names explicitly. Messages passed by the consumer still
2020
+ // take precedence.
2021
+ const textLabel = hasLabelContent(label) && isTextLabel(label) ? String(label) : undefined;
2022
+ const labelledBy = props['aria-labelledby'];
2023
+ const liveMessages = React.useMemo(() => (textLabel || labelledBy
2024
+ ? Object.assign({ guidance: createAriaGuidance({ labelledBy, label: textLabel }) }, ariaLiveMessages) : ariaLiveMessages), [textLabel, labelledBy, ariaLiveMessages]);
1650
2025
  const handleOnIconStartClick = React.useCallback(() => {
1651
2026
  onIconStartClick === null || onIconStartClick === void 0 ? void 0 : onIconStartClick(defaultValue);
1652
2027
  }, [onIconStartClick, defaultValue]);
1653
2028
  const handleOnIconEndClick = React.useCallback(() => {
1654
2029
  onIconEndClick === null || onIconEndClick === void 0 ? void 0 : onIconEndClick(defaultValue);
1655
2030
  }, [onIconEndClick, defaultValue]);
2031
+ if (process.env.NODE_ENV !== 'production') {
2032
+ warnLabelUsage({
2033
+ component: 'DSelect',
2034
+ label,
2035
+ // `{...props}` is spread after `ariaLabel`, so a native `aria-label` in it
2036
+ // is the one that reaches the input, even when it is `undefined`.
2037
+ hasAccessibleName: !!('aria-label' in props ? props['aria-label'] : ariaLabel)
2038
+ || !!labelledBy,
2039
+ accessibleNameProp: 'ariaLabel',
2040
+ floatingLabel,
2041
+ });
2042
+ }
1656
2043
  return (jsxRuntime.jsxs("div", Object.assign({ className: classNames('d-select', className, {
1657
2044
  'd-select-floating': floatingLabel,
1658
2045
  disabled: disabled || loading,
1659
- }), style: style }, dataAttributes, { children: [label && (jsxRuntime.jsx("label", { htmlFor: id, children: label })), jsxRuntime.jsxs("div", { className: classNames({
2046
+ }), style: style }, dataAttributes, { children: [hasLabelContent(label) && (jsxRuntime.jsx(DFormLabel, { htmlFor: inputId, children: label })), jsxRuntime.jsxs("div", { className: classNames({
1660
2047
  'input-group': true,
1661
2048
  'has-validation': invalid,
1662
2049
  disabled: disabled || loading,
1663
- }), children: [iconStart && (jsxRuntime.jsx("button", { type: "button", className: "input-group-text", id: `${id}Start`, onClick: handleOnIconStartClick, disabled: disabled || loading, "aria-label": iconStartAriaLabel, tabIndex: iconStartTabIndex, children: jsxRuntime.jsx(DIcon, { icon: iconStart, familyClass: iconStartFamilyClass, familyPrefix: iconStartFamilyPrefix }) })), jsxRuntime.jsx(Select, Object.assign({ id: `${id}Container`, inputId: id, "aria-label": ariaLabel, styles: {
2050
+ }), children: [iconStart && (jsxRuntime.jsx("button", { type: "button", className: "input-group-text", id: `${id}Start`, onClick: handleOnIconStartClick, disabled: disabled || loading, "aria-label": iconStartAriaLabel, tabIndex: iconStartTabIndex, children: jsxRuntime.jsx(DIcon, { icon: iconStart, familyClass: iconStartFamilyClass, familyPrefix: iconStartFamilyPrefix }) })), jsxRuntime.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: {
1664
2051
  control: (base) => (Object.assign(Object.assign({}, base), { minHeight: 'unset' })),
1665
2052
  container: (base) => (Object.assign(Object.assign({}, base), { flex: 1 })),
1666
2053
  menu: (base) => (Object.assign(Object.assign({}, base), { width: menuWithMaxContent ? 'max-context' : '100%', zIndex: 1000 })),
@@ -1855,10 +2242,40 @@ function formatValue(value, currencyOptions) {
1855
2242
  }
1856
2243
  return currency(value, Object.assign(Object.assign({}, currencyOptions), { symbol: '' })).format();
1857
2244
  }
1858
- function useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref, minValue, maxValue) {
2245
+ /**
2246
+ * State and handlers for a currency input: a formatted string while the field
2247
+ * is idle and the raw number while it is being edited.
2248
+ *
2249
+ * `minValue`/`maxValue` bound the value. With `clamp` (default `true`) an
2250
+ * out-of-range value is brought into range whenever it is not being typed:
2251
+ * on mount, when `value` or the bounds change, and on blur. Every time the
2252
+ * clamp changes the value, `onChange` receives the clamped number, so the
2253
+ * input and the consumer's state never disagree. While the field is focused
2254
+ * the typed number is reported as is, so a partial entry is not rewritten.
2255
+ *
2256
+ * With `clamp: false` the value is never changed: the bounds only feed
2257
+ * `isOverMax` / `isUnderMin`, so the consumer can show its own message (e.g.
2258
+ * "You exceeded the limit") and keep the entered amount.
2259
+ *
2260
+ * Controlled usage follows the same contract as a native controlled input:
2261
+ * reflect `onChange` into `value` in the same event (`setState` in the
2262
+ * handler). The hook keeps showing `value` whenever it isn't being edited, so
2263
+ * a consumer can reject a change by keeping `value` as it was. Deferring the
2264
+ * update (a timer, awaiting a request) makes the hook see a stale `value` in
2265
+ * between: the field can flash the previous amount and a clamp can be
2266
+ * reported again when the deferred value arrives.
2267
+ */
2268
+ function useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref, minValue, maxValue, clamp = true) {
1859
2269
  const inputRef = useProvidedRefOrCreate(ref);
2270
+ const onChangeRef = React.useRef(onChange);
2271
+ onChangeRef.current = onChange;
2272
+ // Last clamp reported through `onChange`, as the value it came from and the
2273
+ // value it produced. StrictMode replays effects, and a consumer may reflect
2274
+ // `onChange` asynchronously, both before `value` catches up: the same clamp
2275
+ // must not be reported twice.
2276
+ const lastReportedClampRef = React.useRef(null);
1860
2277
  const clampValue = React.useCallback((newValue) => {
1861
- if (newValue === undefined) {
2278
+ if (newValue === undefined || !clamp) {
1862
2279
  return newValue;
1863
2280
  }
1864
2281
  let clampedValue = newValue;
@@ -1869,10 +2286,9 @@ function useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref
1869
2286
  clampedValue = Math.min(clampedValue, maxValue);
1870
2287
  }
1871
2288
  return clampedValue;
1872
- }, [minValue, maxValue]);
2289
+ }, [minValue, maxValue, clamp]);
1873
2290
  const [innerType, setInnerType] = React.useState('text');
1874
- const [innerNumber, setInnerNumber] = React.useState(clampValue(value));
1875
- const [innerString, setInnerString] = React.useState(formatValue(clampValue(value), currencyOptions));
2291
+ const [innerNumber, setInnerNumber] = React.useState(() => clampValue(value));
1876
2292
  const handleOnFocus = React.useCallback((event) => {
1877
2293
  event.stopPropagation();
1878
2294
  setInnerType('number');
@@ -1884,35 +2300,60 @@ function useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref
1884
2300
  const clampedNumber = clampValue(innerNumber);
1885
2301
  if (clampedNumber !== innerNumber) {
1886
2302
  setInnerNumber(clampedNumber);
1887
- setInnerString(formatValue(clampedNumber, currencyOptions));
2303
+ // Recorded before notifying, so the sync effect that runs when editing
2304
+ // ends doesn't report the same clamp again while `value` still holds the
2305
+ // typed number.
2306
+ lastReportedClampRef.current = { from: value, to: clampedNumber };
1888
2307
  onChange === null || onChange === void 0 ? void 0 : onChange(clampedNumber);
1889
2308
  }
1890
2309
  onBlur === null || onBlur === void 0 ? void 0 : onBlur(event);
1891
- }, [onBlur, innerNumber, clampValue, currencyOptions, onChange]);
2310
+ }, [onBlur, innerNumber, clampValue, onChange, value]);
1892
2311
  const handleOnChange = React.useCallback((newValue) => {
1893
2312
  const newNumber = (newValue === undefined || newValue === '') ? undefined : Number(newValue);
1894
2313
  if (newNumber !== innerNumber) {
1895
2314
  setInnerNumber(newNumber);
1896
- setInnerString(formatValue(newNumber, currencyOptions));
1897
2315
  onChange === null || onChange === void 0 ? void 0 : onChange(newNumber);
1898
2316
  }
1899
- }, [currencyOptions, onChange, innerNumber]);
1900
- const isMountedRef = React.useRef(false);
2317
+ }, [onChange, innerNumber]);
2318
+ const isEditing = innerType === 'number';
2319
+ // Keep the inner value in sync with `value` and the bounds. While the user
2320
+ // is typing, the consumer echoes the typed number back through `value`, so
2321
+ // it is taken as is; otherwise it is clamped, and a clamp that changes it is
2322
+ // reported through `onChange`.
1901
2323
  React.useEffect(() => {
1902
- if (!isMountedRef.current) {
1903
- isMountedRef.current = true;
1904
- return;
2324
+ var _a;
2325
+ const nextNumber = isEditing ? value : clampValue(value);
2326
+ if (nextNumber !== innerNumber) {
2327
+ setInnerNumber(nextNumber);
2328
+ }
2329
+ if (nextNumber !== value) {
2330
+ const last = lastReportedClampRef.current;
2331
+ if (!last || last.from !== value || last.to !== nextNumber) {
2332
+ lastReportedClampRef.current = { from: value, to: nextNumber };
2333
+ (_a = onChangeRef.current) === null || _a === void 0 ? void 0 : _a.call(onChangeRef, nextNumber);
2334
+ }
1905
2335
  }
1906
- if (value !== innerNumber) {
1907
- setInnerNumber(value);
1908
- setInnerString(formatValue(value, currencyOptions));
2336
+ else {
2337
+ lastReportedClampRef.current = null;
1909
2338
  }
1910
- }, [value, currencyOptions, innerNumber]);
1911
- const innerValue = React.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]);
2339
+ // `innerNumber` is read, not tracked: the effect reacts to the consumer's
2340
+ // value and to the bounds, not to its own updates.
2341
+ // eslint-disable-next-line react-hooks/exhaustive-deps
2342
+ }, [value, clampValue, isEditing]);
2343
+ // Derived, not stored: the formatted text always matches the current number
2344
+ // and options, even when both change in the same render. It depends on the
2345
+ // options object itself, so a new custom `format` function is honored too;
2346
+ // formatting is cheap and doesn't feed the synchronization effect.
2347
+ const innerString = React.useMemo(() => formatValue(innerNumber, currencyOptions), [innerNumber, currencyOptions]);
2348
+ const innerValue = React.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]);
2349
+ const isOverMax = maxValue !== undefined && innerNumber !== undefined && innerNumber > maxValue;
2350
+ const isUnderMin = minValue !== undefined && innerNumber !== undefined && innerNumber < minValue;
1912
2351
  return {
1913
2352
  inputRef,
1914
2353
  innerValue,
1915
2354
  innerType,
2355
+ isOverMax,
2356
+ isUnderMin,
1916
2357
  handleOnFocus,
1917
2358
  handleOnChange,
1918
2359
  handleOnBlur,
@@ -2163,38 +2604,142 @@ function useOtp({ action, otpSize = 6, seconds = 15, }) {
2163
2604
  };
2164
2605
  }
2165
2606
 
2607
+ /**
2608
+ * Backs a value that the consumer may or may not drive, so a control works in
2609
+ * both modes without a copy of the state drifting away from the props.
2610
+ *
2611
+ * A control counts as controlled only when the consumer passes the value *and*
2612
+ * an `onChange` to push it back. The value on its own has always meant "start
2613
+ * here" in this library — `<DInputCheck type="radio" checked />` inside a radio
2614
+ * group, for one — so treating it as controlled would silently freeze those
2615
+ * controls, and React emits no warning to say why: the components always attach
2616
+ * their own `onChange` handler.
2617
+ *
2618
+ * @param value the value coming from props, or `undefined` when not passed.
2619
+ * @param isControlled whether the consumer can drive the value back.
2620
+ * @param initialValue what to start from when no value is passed.
2621
+ * @returns the value to render, and a setter to call on every user change.
2622
+ */
2623
+ function useControlledState(value, isControlled, initialValue) {
2624
+ const [internalValue, setInternalValue] = React.useState(value === undefined ? initialValue : value);
2625
+ // The internal copy follows `value` in both modes.
2626
+ //
2627
+ // Uncontrolled, `value` without an `onChange` still means "start here", and a
2628
+ // later flip of it from outside must still land — but between flips the
2629
+ // control has to keep moving on its own, which is why this depends on `value`
2630
+ // alone and not on `internalValue`.
2631
+ //
2632
+ // Controlled, the copy is not what gets rendered, but it is what the control
2633
+ // falls back to if the consumer later stops passing a value — `value={locked
2634
+ // ? undefined : x}`, say. Keeping it current means that hand-off carries on
2635
+ // from where the control is instead of snapping back to the mount-time seed.
2636
+ React.useEffect(() => {
2637
+ if (value === undefined) {
2638
+ return;
2639
+ }
2640
+ setInternalValue(value);
2641
+ }, [value]);
2642
+ const setValue = React.useCallback((next) => {
2643
+ if (isControlled) {
2644
+ return;
2645
+ }
2646
+ setInternalValue(next);
2647
+ }, [isControlled]);
2648
+ return [
2649
+ isControlled && value !== undefined ? value : internalValue,
2650
+ setValue,
2651
+ ];
2652
+ }
2653
+
2166
2654
  function DInputCounter(_a, ref) {
2167
- var { minValue, maxValue, value = minValue, invalid, iconStart: iconStartProp, iconEnd: iconEndProp, iconStartAriaLabel = 'decrease action', iconEndAriaLabel = 'increase action', style, onChange } = _a, props = tslib.__rest(_a, ["minValue", "maxValue", "value", "invalid", "iconStart", "iconEnd", "iconStartAriaLabel", "iconEndAriaLabel", "style", "onChange"]);
2655
+ var { minValue, maxValue, value, defaultValue, invalid, iconStart: iconStartProp, iconEnd: iconEndProp, iconStartAriaLabel = 'decrease action', iconEndAriaLabel = 'increase action', style, onChange } = _a, props = tslib.__rest(_a, ["minValue", "maxValue", "value", "defaultValue", "invalid", "iconStart", "iconEnd", "iconStartAriaLabel", "iconEndAriaLabel", "style", "onChange"]);
2168
2656
  const { handleOnWheel, } = useDisableInputWheel(ref);
2169
2657
  const inputRef = useProvidedRefOrCreate(ref);
2170
- const [internalIsInvalid, setInternalIsInvalid] = React.useState(false);
2171
- const [internalValue, setInternalValue] = React.useState(value);
2658
+ // See `useControlledState` for why `onChange` takes part in this decision.
2659
+ const isControlled = value !== undefined && onChange !== undefined;
2660
+ const [currentValue, setCurrentValue] = useControlledState(value, isControlled, defaultValue !== null && defaultValue !== void 0 ? defaultValue : minValue);
2661
+ // The step handlers move *from* the current value rather than replacing it,
2662
+ // so they need the latest one even when several clicks land in the same React
2663
+ // batch and no render has happened in between — the functional update this
2664
+ // replaced handled that on its own. Written on every render so it follows the
2665
+ // props, and by `commitValue` so a second step in the same batch starts from
2666
+ // where the first left off instead of collapsing into it.
2667
+ const currentValueRef = React.useRef(currentValue);
2668
+ currentValueRef.current = currentValue;
2669
+ // `onChange` used to be called from an effect watching the internal value,
2670
+ // which made controlling the counter impossible: a controlled counter never
2671
+ // moves that value, so the effect never fired. Reporting from the handlers
2672
+ // instead works in both modes.
2673
+ const commitValue = React.useCallback((newValue) => {
2674
+ // Clicking at a bound, or typing the value that is already there, is not a
2675
+ // change. The effect this replaced watched the value itself and so stayed
2676
+ // quiet too; without this a batch of clicks against `minValue` would report
2677
+ // the same number once per click.
2678
+ if (newValue === currentValueRef.current) {
2679
+ return;
2680
+ }
2681
+ // Only the uncontrolled path moves the ref ahead of a render. There the
2682
+ // component owns the value, so a second step in the same batch has to build
2683
+ // on the first. Controlled, the prop is the truth and every step proposes
2684
+ // from it: a parent that rejects without re-rendering — `onChange={() => {}}`,
2685
+ // or a setter returning the previous state — never re-runs the assignment
2686
+ // above, so a speculative ref would climb away from the value on screen and
2687
+ // each further click would report a number further from it.
2688
+ if (!isControlled) {
2689
+ currentValueRef.current = newValue;
2690
+ }
2691
+ setCurrentValue(newValue);
2692
+ onChange === null || onChange === void 0 ? void 0 : onChange(newValue);
2693
+ }, [isControlled, setCurrentValue, onChange]);
2694
+ // Consumers have always been handed the starting value through `onChange` on
2695
+ // mount, and some seed their state with it, so that one call stays. The ref
2696
+ // keeps it to the first run: the old effect also re-fired on every render
2697
+ // that passed a fresh inline `onChange`. Nothing is marked as reported until
2698
+ // there is a handler to report to, so a counter mounted without `onChange`
2699
+ // still hands over its starting value once one is attached.
2700
+ const hasReportedInitialValue = React.useRef(false);
2172
2701
  React.useEffect(() => {
2173
- setInternalValue(value);
2174
- }, [value]);
2702
+ if (hasReportedInitialValue.current || !onChange) {
2703
+ return;
2704
+ }
2705
+ hasReportedInitialValue.current = true;
2706
+ onChange(currentValue);
2707
+ }, [onChange, currentValue]);
2708
+ // `value` used to default to `minValue`, so a counter left to its own devices
2709
+ // re-seeded — and reported — whenever `minValue` moved. Consumers use it as a
2710
+ // live bound, so that stays; an explicit `value` or `defaultValue` opts out,
2711
+ // exactly as passing `value` did before. The ref keeps this to real changes,
2712
+ // so mounting still reports once and a click is never undone.
2713
+ const hasExplicitStartingValue = value !== undefined || defaultValue !== undefined;
2714
+ const previousMinValue = React.useRef(minValue);
2175
2715
  React.useEffect(() => {
2176
- onChange === null || onChange === void 0 ? void 0 : onChange(Number(internalValue));
2177
- }, [onChange, internalValue]);
2716
+ if (previousMinValue.current === minValue) {
2717
+ return;
2718
+ }
2719
+ previousMinValue.current = minValue;
2720
+ if (hasExplicitStartingValue) {
2721
+ return;
2722
+ }
2723
+ commitValue(minValue);
2724
+ }, [hasExplicitStartingValue, minValue, commitValue]);
2178
2725
  const handleOnChange = React.useCallback((newValue) => {
2179
- setInternalValue(Number(newValue || '0'));
2180
- }, []);
2726
+ commitValue(Number(newValue || '0'));
2727
+ }, [commitValue]);
2181
2728
  const handleOnIconStartClick = React.useCallback(() => {
2182
- setInternalValue((prevInternalValue) => Math.max(prevInternalValue - 1, minValue));
2183
- }, [minValue]);
2729
+ commitValue(Math.max(currentValueRef.current - 1, minValue));
2730
+ }, [commitValue, minValue]);
2184
2731
  const handleOnIconEndClick = React.useCallback(() => {
2185
- setInternalValue((prevInternalValue) => Math.min(prevInternalValue + 1, maxValue));
2186
- }, [maxValue]);
2732
+ commitValue(Math.min(currentValueRef.current + 1, maxValue));
2733
+ }, [commitValue, maxValue]);
2187
2734
  const generateStyleVariables = React.useMemo(() => (Object.assign(Object.assign({}, style), { [`--${PREFIX_BS}form-control-component-text-align`]: 'center' })), [style]);
2188
- const valueString = React.useMemo(() => (internalValue.toString()), [internalValue]);
2189
- React.useEffect(() => {
2190
- setInternalIsInvalid(!(internalValue >= minValue && internalValue <= maxValue));
2191
- }, [internalValue, minValue, maxValue]);
2735
+ const valueString = React.useMemo(() => (currentValue.toString()), [currentValue]);
2736
+ const internalIsInvalid = React.useMemo(() => (!(currentValue >= minValue && currentValue <= maxValue)), [currentValue, minValue, maxValue]);
2192
2737
  const { iconMap: { input } } = useDContext();
2193
2738
  const iconEnd = React.useMemo(() => iconEndProp || input.increase, [iconEndProp, input.increase]);
2194
2739
  const iconStart = React.useMemo(() => iconStartProp || input.decrease, [iconStartProp, input.decrease]);
2195
- return (jsxRuntime.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 && {
2740
+ return (jsxRuntime.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 && {
2196
2741
  iconStartDisabled: true,
2197
- }, internalValue === maxValue && {
2742
+ }, currentValue === maxValue && {
2198
2743
  iconEndDisabled: true,
2199
2744
  }, props)));
2200
2745
  }
@@ -2202,11 +2747,16 @@ const ForwardedDInputCounter = React.forwardRef(DInputCounter);
2202
2747
  ForwardedDInputCounter.displayName = 'DInputCounter';
2203
2748
 
2204
2749
  function DInputCurrency(_a, ref) {
2205
- var { value, minValue, maxValue, currencyCode, onFocus, onBlur, onChange } = _a, props = tslib.__rest(_a, ["value", "minValue", "maxValue", "currencyCode", "onFocus", "onBlur", "onChange"]);
2750
+ var { value, minValue, maxValue, clamp = true, currencyCode, onFocus, onBlur, onChange, invalid: invalidProp, valid: validProp } = _a, props = tslib.__rest(_a, ["value", "minValue", "maxValue", "clamp", "currencyCode", "onFocus", "onBlur", "onChange", "invalid", "valid"]);
2206
2751
  const { currency: currencyOptions } = useDContext();
2207
2752
  const { handleOnWheel, } = useDisableInputWheel(ref);
2208
- const { inputRef, innerValue, innerType, handleOnFocus, handleOnChange, handleOnBlur, } = useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref, minValue, maxValue);
2209
- return (jsxRuntime.jsx(ForwardedDInput, Object.assign({ ref: inputRef, value: innerValue, onChange: handleOnChange, inputMode: "decimal", type: innerType, onFocus: handleOnFocus, onBlur: handleOnBlur, onWheel: handleOnWheel, inputStart: (jsxRuntime.jsx("span", { slot: "input-start", className: "d-input-currency-symbol", children: currencyCode || currencyOptions.symbol })) }, props)));
2753
+ const { inputRef, innerValue, innerType, isOverMax, isUnderMin, handleOnFocus, handleOnChange, handleOnBlur, } = useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref, minValue, maxValue, clamp);
2754
+ const outOfRange = !clamp && (isOverMax || isUnderMin);
2755
+ // One validation state: an explicit `invalid` wins; otherwise an
2756
+ // out-of-range value is invalid and can't also be shown as valid.
2757
+ const invalid = invalidProp !== null && invalidProp !== void 0 ? invalidProp : outOfRange;
2758
+ const valid = invalid ? false : validProp;
2759
+ return (jsxRuntime.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: (jsxRuntime.jsx("span", { slot: "input-start", className: "d-input-currency-symbol", children: currencyCode || currencyOptions.symbol })) }, props)));
2210
2760
  }
2211
2761
  const ForwardedDInputCurrency = React.forwardRef(DInputCurrency);
2212
2762
  ForwardedDInputCurrency.displayName = 'DInputCurrency';
@@ -2293,7 +2843,7 @@ const DEFAULT_VALIDATION_MESSAGES = {
2293
2843
  notMatch: 'The password confirmation and the new password do not match.',
2294
2844
  };
2295
2845
  const DEFAULT_ENABLED_CHECKS = ['uppercase', 'lowercase', 'number', 'specialChar'];
2296
- 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, }) {
2846
+ 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, }) {
2297
2847
  const [password, setPassword] = React.useState(value);
2298
2848
  React.useEffect(() => {
2299
2849
  setPassword(value);
@@ -2302,17 +2852,31 @@ function DPasswordStrengthMeter({ id, label = 'Password', placeholder, value = '
2302
2852
  setPassword(newValue);
2303
2853
  onChange === null || onChange === void 0 ? void 0 : onChange(newValue);
2304
2854
  };
2305
- return (jsxRuntime.jsxs("div", Object.assign({ className: className, style: style }, dataAttributes, { children: [jsxRuntime.jsx(ForwardedDInputPassword, { id: id, label: label, placeholder: placeholder, value: password, name: name, disabled: disabled, invalid: invalid, onChange: handleChange, readonly: readonly }), jsxRuntime.jsx(PasswordChecksList, { password: password, validationMessages: validationMessages, enabledChecks: enabledChecks })] })));
2855
+ return (jsxRuntime.jsxs("div", Object.assign({ className: className, style: style }, dataAttributes, { children: [jsxRuntime.jsx(ForwardedDInputPassword, { id: id, label: label, "aria-label": ariaLabel, placeholder: placeholder, value: password, name: name, disabled: disabled, invalid: invalid, onChange: handleChange, readonly: readonly }), jsxRuntime.jsx(PasswordChecksList, { password: password, validationMessages: validationMessages, enabledChecks: enabledChecks })] })));
2306
2856
  }
2307
2857
 
2308
2858
  function DInputCheck(_a) {
2309
- 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 = tslib.__rest(_a, ["id", "type", "name", "label", "ariaLabel", "checked", "disabled", "invalid", "valid", "indeterminate", "inputClassName", "value", "hint", "onChange", "className", "style", "dataAttributes"]);
2859
+ 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 = tslib.__rest(_a, ["id", "type", "name", "label", "ariaLabel", "checked", "defaultChecked", "disabled", "invalid", "valid", "indeterminate", "inputClassName", "value", "hint", "onChange", "className", "style", "dataAttributes"]);
2310
2860
  const innerRef = React.useRef(null);
2861
+ // See `useControlledState` for why `onChange` takes part in this decision.
2862
+ const isControlled = checked !== undefined && onChange !== undefined;
2311
2863
  const innerId = React.useId();
2312
2864
  const id = React.useMemo(() => idProp || innerId, [idProp, innerId]);
2313
2865
  const handleChange = React.useCallback((event) => {
2314
2866
  onChange === null || onChange === void 0 ? void 0 : onChange(event);
2315
- }, [onChange]);
2867
+ // Controlled only. Activating a checkbox clears the DOM `indeterminate`
2868
+ // flag, and it has no HTML attribute for React to restore the way it
2869
+ // restores `checked` when the parent rejects the change, so the mixed state
2870
+ // would be gone after the first click — the effect below only re-runs when
2871
+ // the prop moves. Uncontrolled keeps the browser's behaviour, where the
2872
+ // click owns the state and `indeterminate` was only the starting look.
2873
+ // Reapplied after `onChange` so a handler reading
2874
+ // `event.target.indeterminate` still sees what the browser left, and a
2875
+ // parent that does move the prop wins through that effect.
2876
+ if (isControlled && innerRef.current) {
2877
+ innerRef.current.indeterminate = type === 'checkbox' && Boolean(indeterminate);
2878
+ }
2879
+ }, [onChange, isControlled, indeterminate, type]);
2316
2880
  const ariaDescribedby = React.useMemo(() => ([
2317
2881
  !!hint && `${id}Hint`,
2318
2882
  ]
@@ -2326,15 +2890,24 @@ function DInputCheck(_a) {
2326
2890
  innerRef.current.indeterminate = type === 'checkbox' && Boolean(indeterminate);
2327
2891
  }
2328
2892
  }, [indeterminate, type]);
2893
+ // Legacy path only: a `checked` with no `onChange` behind it still lands on
2894
+ // the element, but through the DOM, so the input stays uncontrolled and both
2895
+ // clicking it and the native radio-group behaviour keep working.
2329
2896
  React.useEffect(() => {
2330
- if (innerRef.current) {
2331
- innerRef.current.checked = checked;
2897
+ if (isControlled || checked === undefined || !innerRef.current) {
2898
+ return;
2332
2899
  }
2333
- }, [checked]);
2334
- const inputComponent = React.useMemo(() => (jsxRuntime.jsx("input", Object.assign({ ref: innerRef, onChange: handleChange, className: classNames('form-check-input', {
2900
+ innerRef.current.checked = checked;
2901
+ }, [isControlled, checked]);
2902
+ const inputComponent = React.useMemo(() => (jsxRuntime.jsx("input", Object.assign({ ref: innerRef }, isControlled
2903
+ ? { checked }
2904
+ : defaultChecked !== undefined && { defaultChecked }, { onChange: handleChange, className: classNames('form-check-input', {
2335
2905
  'is-invalid': invalid,
2336
2906
  'is-valid': valid,
2337
2907
  }, inputClassName), style: style, id: id, disabled: disabled, type: type, name: name, value: value, "aria-label": ariaLabel }, ariaDescribedby && { 'aria-describedby': ariaDescribedby }, props))), [
2908
+ isControlled,
2909
+ checked,
2910
+ defaultChecked,
2338
2911
  handleChange,
2339
2912
  invalid,
2340
2913
  valid,
@@ -2349,10 +2922,21 @@ function DInputCheck(_a) {
2349
2922
  ariaDescribedby,
2350
2923
  props,
2351
2924
  ]);
2352
- if (!label) {
2925
+ if (process.env.NODE_ENV !== 'production') {
2926
+ warnLabelUsage({
2927
+ component: 'DInputCheck',
2928
+ label,
2929
+ // `{...props}` is spread after `aria-label={ariaLabel}`, so a native
2930
+ // `aria-label` wins — including when it is explicitly undefined.
2931
+ hasAccessibleName: !!('aria-label' in props ? props['aria-label'] : ariaLabel)
2932
+ || !!props['aria-labelledby'],
2933
+ accessibleNameProp: 'ariaLabel',
2934
+ });
2935
+ }
2936
+ if (!hasLabelContent(label)) {
2353
2937
  return inputComponent;
2354
2938
  }
2355
- return (jsxRuntime.jsxs("div", Object.assign({ className: classNames('form-check', className) }, dataAttributes, { children: [inputComponent, jsxRuntime.jsx("label", { className: "form-check-label", htmlFor: id, children: label }), hint && (jsxRuntime.jsx("div", { className: "form-text", id: `${id}Hint`, children: hint }))] })));
2939
+ return (jsxRuntime.jsxs("div", Object.assign({ className: classNames('form-check', className) }, dataAttributes, { children: [inputComponent, jsxRuntime.jsx(DFormLabel, { className: "form-check-label", htmlFor: id, children: label }), hint && (jsxRuntime.jsx("div", { className: "form-text", id: `${id}Hint`, children: hint }))] })));
2356
2940
  }
2357
2941
 
2358
2942
  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', }) {
@@ -2428,14 +3012,14 @@ function DInputPin({ id: idProp, label = '', placeholder, type = 'text', disable
2428
3012
  const wheelInput = React.useCallback((event) => {
2429
3013
  event.currentTarget.blur();
2430
3014
  }, []);
2431
- return (jsxRuntime.jsxs("div", Object.assign({ className: classNames('d-input-pin', className), style: style }, dataAttributes, { children: [label && (jsxRuntime.jsx("label", { htmlFor: "pinIndex0", children: label })), jsxRuntime.jsxs("div", { className: "d-input-pin-group", id: id, children: [Array.from({ length: characters }).map((_, index) => (jsxRuntime.jsx("input", Object.assign({ className: classNames({
3015
+ return (jsxRuntime.jsxs("div", Object.assign({ className: classNames('d-input-pin', className), style: style }, dataAttributes, { children: [hasLabelContent(label) && (jsxRuntime.jsx(DFormLabel, { htmlFor: "pinIndex0", children: label })), jsxRuntime.jsxs("div", { className: "d-input-pin-group", id: id, children: [Array.from({ length: characters }).map((_, index) => (jsxRuntime.jsx("input", Object.assign({ className: classNames({
2432
3016
  'form-control': true,
2433
3017
  'is-invalid': invalid,
2434
3018
  'is-valid': valid,
2435
3019
  }), 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 && (jsxRuntime.jsx("div", { className: "input-group-text", children: jsxRuntime.jsx("span", { className: "spinner-border spinner-border-sm", role: "status", "aria-hidden": "true", children: jsxRuntime.jsx("span", { className: "visually-hidden", children: "Loading..." }) }) }))] }), hint && (jsxRuntime.jsx("div", { className: "form-text", id: `${id}Hint`, children: hint }))] })));
2436
3020
  }
2437
3021
 
2438
- 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, }) {
3022
+ 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, }) {
2439
3023
  const innerId = React.useId();
2440
3024
  const id = React.useMemo(() => idProp || innerId, [idProp, innerId]);
2441
3025
  const internalValueExtractor = React.useCallback((option) => {
@@ -2485,8 +3069,9 @@ function DInputSelect({ id: idProp, name, label = '', className, style, options
2485
3069
  'floating-label': floatingLabel,
2486
3070
  'is-invalid': invalid,
2487
3071
  'is-valid': valid,
2488
- }), "aria-label": label, disabled: disabled || loading, onChange: changeHandler, onBlur: blurHandler }, ariaDescribedby && { 'aria-describedby': ariaDescribedby }, value && { value }, { children: options.map((option) => (jsxRuntime.jsx("option", { value: internalValueExtractor(option), children: internalLabelExtractor(option) }, internalValueExtractor(option)))) }))), [
3072
+ }), "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) => (jsxRuntime.jsx("option", { value: internalValueExtractor(option), children: internalLabelExtractor(option) }, internalValueExtractor(option)))) }))), [
2489
3073
  ariaDescribedby,
3074
+ ariaLabel,
2490
3075
  blurHandler,
2491
3076
  changeHandler,
2492
3077
  disabled,
@@ -2503,7 +3088,7 @@ function DInputSelect({ id: idProp, name, label = '', className, style, options
2503
3088
  valid,
2504
3089
  size,
2505
3090
  ]);
2506
- const labelComponent = React.useMemo(() => (jsxRuntime.jsx("label", { htmlFor: id, children: label })), [
3091
+ const labelComponent = React.useMemo(() => (jsxRuntime.jsx(DFormLabel, { htmlFor: id, children: label })), [
2507
3092
  id,
2508
3093
  label,
2509
3094
  ]);
@@ -2513,15 +3098,26 @@ function DInputSelect({ id: idProp, name, label = '', className, style, options
2513
3098
  }
2514
3099
  return selectComponent;
2515
3100
  }, [floatingLabel, labelComponent, selectComponent]);
2516
- return (jsxRuntime.jsxs("div", Object.assign({ className: className, style: style }, dataAttributes, { children: [label && !floatingLabel && (labelComponent), jsxRuntime.jsxs("div", { className: classNames({
3101
+ if (process.env.NODE_ENV !== 'production') {
3102
+ warnLabelUsage({
3103
+ component: 'DInputSelect',
3104
+ label,
3105
+ hasAccessibleName: !!ariaLabel,
3106
+ accessibleNameProp: 'ariaLabel',
3107
+ floatingLabel,
3108
+ });
3109
+ }
3110
+ return (jsxRuntime.jsxs("div", Object.assign({ className: className, style: style }, dataAttributes, { children: [hasLabelContent(label) && !floatingLabel && (labelComponent), jsxRuntime.jsxs("div", { className: classNames({
2517
3111
  'input-group': true,
2518
3112
  }), children: [iconStart && (jsxRuntime.jsx("button", { type: "button", className: "input-group-text", id: `${id}Start`, onClick: iconStartClickHandler, disabled: disabled || loading, "aria-label": iconStartAriaLabel, children: iconStart && (jsxRuntime.jsx(DIcon, { icon: iconStart, familyClass: iconStartFamilyClass, familyPrefix: iconStartFamilyPrefix })) })), dynamicComponent, iconEnd && !loading && (jsxRuntime.jsx("button", { type: "button", className: "input-group-text", id: `${id}End`, onClick: iconEndClickHandler, disabled: disabled || loading, "aria-label": iconEndAriaLabel, children: iconEnd && (jsxRuntime.jsx(DIcon, { icon: iconEnd, familyClass: iconEndFamilyClass, familyPrefix: iconEndFamilyPrefix })) })), loading && (jsxRuntime.jsx("div", { className: "input-group-text form-control-icon loading", children: jsxRuntime.jsx("span", { className: "spinner-border spinner-border-sm", role: "status", "aria-hidden": "true", children: jsxRuntime.jsx("span", { className: "visually-hidden", children: "Loading..." }) }) }))] }), hint && (jsxRuntime.jsx("div", { className: "form-text", id: `${id}Hint`, children: hint }))] })));
2519
3113
  }
2520
3114
 
2521
- function DInputSwitch({ id: idProp, label, ariaLabel, name, checked, disabled, invalid = false, valid = false, hint, readonly, className, style, dataAttributes, inputClassName, onChange, }) {
3115
+ function DInputSwitch({ id: idProp, label, ariaLabel, name, checked, defaultChecked = false, disabled, invalid = false, valid = false, hint, readonly, className, style, dataAttributes, inputClassName, onChange, }) {
2522
3116
  const innerId = React.useId();
2523
3117
  const id = React.useMemo(() => idProp || innerId, [idProp, innerId]);
2524
- const [internalIsChecked, setInternalIsChecked] = React.useState(checked);
3118
+ // See `useControlledState` for why `onChange` takes part in this decision.
3119
+ const isControlled = checked !== undefined && onChange !== undefined;
3120
+ const [isChecked, setIsChecked] = useControlledState(checked, isControlled, defaultChecked);
2525
3121
  const ariaDescribedby = React.useMemo(() => ([
2526
3122
  !!hint && `${id}Hint`,
2527
3123
  ]
@@ -2530,18 +3126,23 @@ function DInputSwitch({ id: idProp, label, ariaLabel, name, checked, disabled, i
2530
3126
  id,
2531
3127
  hint,
2532
3128
  ]);
2533
- React.useEffect(() => {
2534
- setInternalIsChecked(checked);
2535
- }, [checked]);
2536
3129
  const changeHandler = React.useCallback((event) => {
2537
3130
  const value = event.currentTarget.checked;
2538
- setInternalIsChecked(value);
3131
+ setIsChecked(value);
2539
3132
  onChange === null || onChange === void 0 ? void 0 : onChange(value);
2540
- }, [onChange]);
3133
+ }, [setIsChecked, onChange]);
3134
+ if (process.env.NODE_ENV !== 'production') {
3135
+ warnLabelUsage({
3136
+ component: 'DInputSwitch',
3137
+ label,
3138
+ hasAccessibleName: !!ariaLabel,
3139
+ accessibleNameProp: 'ariaLabel',
3140
+ });
3141
+ }
2541
3142
  return (jsxRuntime.jsxs("div", Object.assign({ className: classNames('form-check form-switch', className) }, dataAttributes, { children: [jsxRuntime.jsx("input", Object.assign({ id: id, name: name, onChange: readonly ? () => false : changeHandler, className: classNames('form-check-input', {
2542
3143
  'is-invalid': invalid,
2543
3144
  'is-valid': valid,
2544
- }, inputClassName), style: style, type: "checkbox", role: "switch", checked: internalIsChecked, disabled: disabled, "aria-label": ariaLabel }, ariaDescribedby && { 'aria-describedby': ariaDescribedby })), label && (jsxRuntime.jsx("label", { className: "form-check-label", htmlFor: id, children: label })), hint && (jsxRuntime.jsx("div", { className: "form-text", id: `${id}Hint`, children: hint }))] })));
3145
+ }, inputClassName), style: style, type: "checkbox", role: "switch", checked: isChecked, disabled: disabled, "aria-label": ariaLabel }, ariaDescribedby && { 'aria-describedby': ariaDescribedby })), hasLabelContent(label) && (jsxRuntime.jsx(DFormLabel, { className: "form-check-label", htmlFor: id, children: label })), hint && (jsxRuntime.jsx("div", { className: "form-text", id: `${id}Hint`, children: hint }))] })));
2545
3146
  }
2546
3147
 
2547
3148
  function DInputRange(_a, ref) {
@@ -2573,15 +3174,61 @@ function DInputRange(_a, ref) {
2573
3174
  props,
2574
3175
  value,
2575
3176
  ]);
2576
- if (!label) {
3177
+ if (process.env.NODE_ENV !== 'production') {
3178
+ warnLabelUsage({
3179
+ component: 'DInputRange',
3180
+ label,
3181
+ // `{...props}` is spread after `aria-label={ariaLabel}`, so a native
3182
+ // `aria-label` wins — including when it is explicitly undefined.
3183
+ hasAccessibleName: !!('aria-label' in props ? props['aria-label'] : ariaLabel)
3184
+ || !!props['aria-labelledby'],
3185
+ accessibleNameProp: 'ariaLabel',
3186
+ });
3187
+ }
3188
+ if (!hasLabelContent(label)) {
2577
3189
  return inputComponent;
2578
3190
  }
2579
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("label", { className: "form-label", htmlFor: id, children: label }), inputComponent] }));
3191
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(DFormLabel, { className: "form-label", htmlFor: id, children: label }), inputComponent] }));
2580
3192
  }
2581
3193
  const ForwardedDInputRange = React.forwardRef(DInputRange);
2582
3194
  ForwardedDInputRange.displayName = 'DInputRange';
2583
3195
 
2584
- function DListGroupItem({ as = 'li', action: actionProp, active, disabled, href, onClick, color, iconStart, iconStartFamilyClass, iconStartFamilyPrefix, iconStartMaterialStyle, iconEnd, iconEndFamilyClass, iconEndFamilyPrefix, iconEndMaterialStyle, children, className, style, dataAttributes, }) {
3196
+ /**
3197
+ * Element the enclosing `DListGroup` renders, so each `DListGroupItem` can
3198
+ * check that its own element is valid content for it. `undefined` outside a
3199
+ * `DListGroup`.
3200
+ */
3201
+ const ListGroupContext = React.createContext(undefined);
3202
+
3203
+ /**
3204
+ * Combinations already reported, keyed by container and item element, so the
3205
+ * same mismatch only warns once per page load however many items share it.
3206
+ */
3207
+ const warnedCombinations = new Set();
3208
+ /**
3209
+ * Warns, once per combination and only outside production builds, that a
3210
+ * `DListGroupItem` renders an element its `DListGroup` container can't hold.
3211
+ *
3212
+ * Links and buttons inside a <ul>/<ol> are wrapped in an <li>, so the only
3213
+ * invalid pair left is a plain item (an <li>) inside `as="div"`: an <li>
3214
+ * outside of any list. It doesn't fail visibly, which is why it's reported.
3215
+ *
3216
+ * The `process.env.NODE_ENV` guard at the call site is what bundlers
3217
+ * constant-fold, so the whole call — and this module — drops out of a
3218
+ * consumer's production bundle.
3219
+ */
3220
+ function warnInvalidListMarkup(container, item) {
3221
+ const key = `${container}>${item}`;
3222
+ if (warnedCombinations.has(key))
3223
+ return;
3224
+ warnedCombinations.add(key);
3225
+ // eslint-disable-next-line no-console
3226
+ console.warn(`[Dynamic UI] DListGroupItem: a <${item}> inside a <${container}> is invalid markup. `
3227
+ + 'Keep the container as a list (the default `as="ul"`, or `numbered`), or render the item '
3228
+ + 'as a link or button. It never appears in production builds.');
3229
+ }
3230
+
3231
+ 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, }) {
2585
3232
  const { icon: { familyClass, familyPrefix, materialStyle, }, } = useDContext();
2586
3233
  const Tag = React.useMemo(() => {
2587
3234
  if (href) {
@@ -2592,29 +3239,45 @@ function DListGroupItem({ as = 'li', action: actionProp, active, disabled, href,
2592
3239
  }
2593
3240
  return as;
2594
3241
  }, [href, as, actionProp]);
2595
- const action = React.useMemo(() => {
2596
- if (Tag === 'a' || Tag === 'button') {
2597
- return true;
2598
- }
2599
- return actionProp;
2600
- }, [Tag, actionProp]);
3242
+ const container = React.useContext(ListGroupContext);
3243
+ const isInteractive = Tag === 'a' || Tag === 'button';
3244
+ // Inside a <ul>/<ol>, a link or button is wrapped in the <li> that carries
3245
+ // the item styles, so the group keeps list semantics (`list > listitem >
3246
+ // link|button`) without breaking Bootstrap's sibling selectors.
3247
+ const isWrapped = isInteractive && (container === 'ul' || container === 'ol');
3248
+ if (process.env.NODE_ENV !== 'production' && container === 'div' && Tag === 'li') {
3249
+ warnInvalidListMarkup(container, Tag);
3250
+ }
2601
3251
  const generateClasses = React.useMemo(() => ({
2602
3252
  'list-group-item': true,
2603
- 'list-group-item-action': action,
3253
+ 'list-group-item-action': isInteractive || actionProp,
3254
+ 'd-list-group-item-interactive': isWrapped,
2604
3255
  [`list-group-item-${color}`]: !!color,
2605
3256
  active,
2606
3257
  disabled,
2607
- }), [action, active, disabled, color]);
2608
- const ariaAttributes = React.useMemo(() => {
3258
+ }), [isInteractive, actionProp, isWrapped, active, disabled, color]);
3259
+ // A disabled link leaves the tab order and can't be activated: without
3260
+ // `href` and `onClick`, Enter does nothing. Dropping `href` also drops the
3261
+ // implicit link role, so it is set back explicitly. A button uses
3262
+ // `disabled`.
3263
+ const interactiveProps = React.useMemo(() => {
2609
3264
  if (Tag === 'button') {
2610
- return Object.assign(Object.assign({}, active && { 'aria-current': true }), disabled && { disabled: true });
3265
+ return Object.assign(Object.assign(Object.assign({ type: 'button' }, onClick && { onClick }), active && { 'aria-current': ariaCurrent }), disabled && { disabled: true });
2611
3266
  }
2612
- return Object.assign(Object.assign({}, active && { 'aria-current': true }), disabled && { 'aria-disabled': true });
2613
- }, [Tag, active, disabled]);
2614
- return (jsxRuntime.jsxs(Tag, Object.assign({ className: classNames(generateClasses, className), style: style }, Tag === 'a' && href && { href }, onClick && { onClick }, ariaAttributes, dataAttributes, Tag === 'button' && { type: 'button' }, { children: [iconStart && (jsxRuntime.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 && (jsxRuntime.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" }))] })));
3267
+ if (Tag === 'a') {
3268
+ return disabled
3269
+ ? 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 });
3270
+ }
3271
+ return Object.assign(Object.assign(Object.assign({}, onClick && { onClick }), active && { 'aria-current': ariaCurrent }), disabled && { 'aria-disabled': true });
3272
+ }, [Tag, href, onClick, active, ariaCurrent, disabled]);
3273
+ const content = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [iconStart && (jsxRuntime.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 && (jsxRuntime.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" }))] }));
3274
+ if (isWrapped) {
3275
+ return (jsxRuntime.jsx("li", { className: classNames(generateClasses, className), style: style, children: jsxRuntime.jsx(Tag, Object.assign({ className: "d-list-group-item-link" }, interactiveProps, dataAttributes, { children: content })) }));
3276
+ }
3277
+ return (jsxRuntime.jsx(Tag, Object.assign({ className: classNames(generateClasses, className), style: style }, interactiveProps, dataAttributes, { children: content })));
2615
3278
  }
2616
3279
 
2617
- function DListGroup$1({ as = 'ul', numbered, flush, horizontal, children, className, style, dataAttributes, }) {
3280
+ function DListGroup$1({ as = 'ul', numbered, flush, horizontal, ariaLabel, ariaLabelledBy, children, className, style, dataAttributes, }) {
2618
3281
  const Tag = React.useMemo(() => {
2619
3282
  if (numbered) {
2620
3283
  return 'ol';
@@ -2632,7 +3295,14 @@ function DListGroup$1({ as = 'ul', numbered, flush, horizontal, children, classN
2632
3295
  [listGroupHorizontalClass]: !!horizontal,
2633
3296
  };
2634
3297
  }, [flush, horizontal, numbered]);
2635
- return (jsxRuntime.jsx(Tag, Object.assign({ className: classNames(generateClasses, className), style: style }, dataAttributes, { children: children })));
3298
+ const labelProps = React.useMemo(() => {
3299
+ if (!ariaLabelledBy && !ariaLabel)
3300
+ return {};
3301
+ return Object.assign(Object.assign({}, ariaLabelledBy
3302
+ ? { 'aria-labelledby': ariaLabelledBy }
3303
+ : { 'aria-label': ariaLabel }), Tag === 'div' && { role: 'group' });
3304
+ }, [ariaLabel, ariaLabelledBy, Tag]);
3305
+ return (jsxRuntime.jsx(ListGroupContext.Provider, { value: Tag, children: jsxRuntime.jsx(Tag, Object.assign({ className: classNames(generateClasses, className), style: style }, labelProps, dataAttributes, { children: children })) }));
2636
3306
  }
2637
3307
  var DListGroup = Object.assign(DListGroup$1, {
2638
3308
  Item: DListGroupItem,
@@ -2818,14 +3488,20 @@ function DPopover({ children, renderComponent, open, setOpen, adjustContentToRen
2818
3488
  }), ref: refs.setFloating, style: floatingStyles, "aria-labelledby": headingId }, getFloatingProps(), { children: children })) }))] })));
2819
3489
  }
2820
3490
 
2821
- function DProgress({ className, style, currentValue, minValue = 0, maxValue = 100, hideCurrentValue = false, enableStripedAnimation = false, height, dataAttributes, }) {
2822
- const percentage = React.useMemo(() => (Math.round((currentValue * 100) / maxValue)), [currentValue, maxValue]);
3491
+ function DProgress({ className, style, currentValue, minValue = 0, maxValue = 100, hideCurrentValue = false, enableStripedAnimation = false, height, ariaLabel, ariaLabelledBy, dataAttributes, }) {
3492
+ const percentage = React.useMemo(() => {
3493
+ const range = maxValue - minValue;
3494
+ if (range <= 0) {
3495
+ return 0;
3496
+ }
3497
+ return Math.round(((currentValue - minValue) * 100) / range);
3498
+ }, [currentValue, minValue, maxValue]);
2823
3499
  const formatProgress = React.useMemo(() => `${percentage}%`, [percentage]);
2824
3500
  const generateClasses = React.useMemo(() => ({
2825
3501
  'progress-bar': true,
2826
3502
  'progress-bar-striped progress-bar-animated': enableStripedAnimation,
2827
3503
  }), [enableStripedAnimation]);
2828
- return (jsxRuntime.jsx("div", Object.assign({ className: classNames('progress', className), style: Object.assign({ height }, style) }, dataAttributes, { children: jsxRuntime.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 }) })));
3504
+ return (jsxRuntime.jsx("div", Object.assign({ className: classNames('progress', className), style: Object.assign({ height }, style) }, dataAttributes, { children: jsxRuntime.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 }) })));
2829
3505
  }
2830
3506
 
2831
3507
  function DStepper$2({ options, currentStep, iconSuccess: iconSuccessProp, iconSuccessFamilyClass, iconSuccessFamilyPrefix, iconSuccessMaterialStyle, vertical = false, completed, alignStart = false, className, style, }) {
@@ -2933,10 +3609,11 @@ function DTimeline({ className, style, dataAttributes, items, }) {
2933
3609
  }
2934
3610
 
2935
3611
  const TabContext = React.createContext(undefined);
3612
+ const TabsStateContext = React.createContext(undefined);
2936
3613
  function useTabContext() {
2937
3614
  const context = React.useContext(TabContext);
2938
3615
  if (context === undefined) {
2939
- throw new Error('useTabContext was used outside of MTab');
3616
+ throw new Error('useTabContext was used outside of DTabs');
2940
3617
  }
2941
3618
  return context;
2942
3619
  }
@@ -2946,33 +3623,44 @@ function DTabContent({ tab, children, className, style, }) {
2946
3623
  if (!isSelected(tab)) {
2947
3624
  return null;
2948
3625
  }
2949
- return (jsxRuntime.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 }));
3626
+ return (jsxRuntime.jsx("div", { className: classNames('tab-pane fade show active', className), id: `${tab}Pane`, role: "tabpanel", tabIndex: 0, "aria-labelledby": `${tab}Tab`, style: style, children: jsxRuntime.jsx(TabsStateContext.Provider, { value: undefined, children: children }) }));
2950
3627
  }
2951
3628
 
2952
- function DTabs$1({ children, defaultSelected, onChange, options, className, classNameTab, style, vertical, variant = 'underline', dataAttributes, ariaLabel, ariaLabelledBy, }) {
3629
+ /**
3630
+ * Owns the selected tab so the tab bar (`DTabs`) and its panels (`DTabs.Tab`)
3631
+ * can live anywhere inside it, e.g. the bar in a header and the panels in
3632
+ * another column.
3633
+ */
3634
+ function DTabsProvider({ defaultSelected, children, }) {
2953
3635
  const [selected, setSelected] = React.useState(defaultSelected);
3636
+ React.useEffect(() => {
3637
+ setSelected(defaultSelected);
3638
+ }, [defaultSelected]);
3639
+ const isSelected = React.useCallback((tab) => (selected === tab), [selected]);
3640
+ const state = React.useMemo(() => ({ selected, setSelected }), [selected]);
3641
+ const value = React.useMemo(() => ({ isSelected }), [isSelected]);
3642
+ return (jsxRuntime.jsx(TabsStateContext.Provider, { value: state, children: jsxRuntime.jsx(TabContext.Provider, { value: value, children: children }) }));
3643
+ }
3644
+
3645
+ function DTabs$1({ children, defaultSelected, onChange, options, className, classNameTab, classNameContent, style, vertical, variant = 'underline', dataAttributes, ariaLabel, ariaLabelledBy, }) {
3646
+ var _a;
3647
+ const shared = React.useContext(TabsStateContext);
3648
+ const [ownSelected, setOwnSelected] = React.useState(defaultSelected);
3649
+ const selected = shared
3650
+ ? shared.selected
3651
+ : ownSelected !== null && ownSelected !== void 0 ? ownSelected : (_a = options.find((opt) => !opt.disabled)) === null || _a === void 0 ? void 0 : _a.tab;
3652
+ const setSelected = shared ? shared.setSelected : setOwnSelected;
2954
3653
  const onSelect = React.useCallback((option) => {
2955
3654
  if (option.tab) {
2956
3655
  setSelected(option.tab);
2957
3656
  }
2958
3657
  onChange === null || onChange === void 0 ? void 0 : onChange(option);
2959
- }, [onChange]);
3658
+ }, [onChange, setSelected]);
2960
3659
  React.useEffect(() => {
2961
- setSelected(defaultSelected);
3660
+ setOwnSelected(defaultSelected);
2962
3661
  }, [defaultSelected]);
2963
3662
  const generateClasses = React.useMemo(() => (Object.assign({ nav: true, 'd-tabs-nav-vertical': vertical && variant !== 'tabs', [`nav-${variant}`]: true }, className && { [className]: true })), [vertical, variant, className]);
2964
3663
  const tabRefs = React.useRef([]);
2965
- // Always holds the latest `options` without needing to be a dependency:
2966
- // `options` is commonly passed as an inline array literal (e.g.
2967
- // `options={[{ label: 'SMS', tab: 'sms' }, ...]}`), so it's a new array
2968
- // reference on every parent render even when its content hasn't changed.
2969
- // Reading it from this ref (updated synchronously on every render) lets
2970
- // the focus effect below react only to `selected` changing.
2971
- const optionsRef = React.useRef(options);
2972
- optionsRef.current = options;
2973
- React.useEffect(() => {
2974
- tabRefs.current = options.map((_, i) => tabRefs.current[i] || React.createRef());
2975
- }, [options]);
2976
3664
  // Ensure selected is never disabled
2977
3665
  React.useEffect(() => {
2978
3666
  if (options.length === 0)
@@ -2983,29 +3671,16 @@ function DTabs$1({ children, defaultSelected, onChange, options, className, clas
2983
3671
  if (firstEnabled)
2984
3672
  setSelected(firstEnabled.tab);
2985
3673
  }
2986
- }, [options, selected]);
2987
- // Declarative focus management. Wrapped in `useCallback` with an empty
2988
- // dependency array since it only reads from the `tabRefs` ref, so its
2989
- // identity stays stable across renders and it can safely be used as an
2990
- // effect dependency below.
3674
+ }, [options, selected, setSelected]);
3675
+ // Focus only moves in response to the user: arrow keys and clicks call
3676
+ // this directly. Changes to `selected` that don't come from an interaction
3677
+ // (mount, a new `defaultSelected`, the disabled-tab fallback above) leave
3678
+ // focus where it is, so the page doesn't scroll to the tablist and no other
3679
+ // element (a search field, an OTP input) loses focus.
2991
3680
  const focusTab = React.useCallback((idx) => {
2992
3681
  var _a;
2993
- if ((_a = tabRefs.current[idx]) === null || _a === void 0 ? void 0 : _a.current) {
2994
- tabRefs.current[idx].current.focus();
2995
- }
3682
+ (_a = tabRefs.current[idx]) === null || _a === void 0 ? void 0 : _a.focus();
2996
3683
  }, []);
2997
- // Focus selected tab when selected changes.
2998
- // Reads `options` from `optionsRef` (see comment above) instead of
2999
- // depending on `options` directly, so a parent re-render that merely
3000
- // creates a new `options` reference with identical content doesn't call
3001
- // `focusTab` again and steal focus away from unrelated elements on the
3002
- // page (e.g. an OTP input).
3003
- React.useEffect(() => {
3004
- const idx = optionsRef.current.findIndex((opt) => opt.tab === selected && !opt.disabled);
3005
- if (idx !== -1) {
3006
- focusTab(idx);
3007
- }
3008
- }, [selected, focusTab]);
3009
3684
  const handleKeyDown = React.useCallback((idx, e) => {
3010
3685
  const count = options.length;
3011
3686
  if (count === 0)
@@ -3034,7 +3709,7 @@ function DTabs$1({ children, defaultSelected, onChange, options, className, clas
3034
3709
  }
3035
3710
  }
3036
3711
  }
3037
- }, [options, vertical, focusTab]);
3712
+ }, [options, vertical, focusTab, setSelected]);
3038
3713
  let tablistProps = {};
3039
3714
  if (ariaLabelledBy) {
3040
3715
  tablistProps = { 'aria-labelledby': ariaLabelledBy };
@@ -3051,11 +3726,17 @@ function DTabs$1({ children, defaultSelected, onChange, options, className, clas
3051
3726
  'd-tabs-column': !vertical || variant === 'tabs',
3052
3727
  }), style: style }, dataAttributes, { children: [jsxRuntime.jsx("ul", Object.assign({ className: classNames(generateClasses), role: "tablist", "aria-orientation": vertical ? 'vertical' : undefined }, tablistProps, { children: options.map((option, idx) => {
3053
3728
  const isTabSelected = !!option.tab && option.tab === selected;
3054
- return (jsxRuntime.jsx("li", { role: "presentation", className: "nav-item", children: jsxRuntime.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));
3055
- }) })), jsxRuntime.jsx("div", { className: "d-tabs-content tab-content", children: children })] })) }));
3729
+ return (jsxRuntime.jsx("li", { role: "presentation", className: "nav-item", children: jsxRuntime.jsx("button", { ref: (element) => {
3730
+ tabRefs.current[idx] = element;
3731
+ }, 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: () => {
3732
+ focusTab(idx);
3733
+ onSelect(option);
3734
+ }, onKeyDown: (e) => handleKeyDown(idx, e), children: option.label }) }, option.tab));
3735
+ }) })), React.Children.toArray(children).length > 0 && (jsxRuntime.jsx("div", { className: classNames('d-tabs-content tab-content', classNameContent), children: children }))] })) }));
3056
3736
  }
3057
3737
  var DTabs = Object.assign(DTabs$1, {
3058
3738
  Tab: DTabContent,
3739
+ Provider: DTabsProvider,
3059
3740
  });
3060
3741
 
3061
3742
  function DToastHeader({ children, className, style }) {
@@ -3066,8 +3747,13 @@ function DToastBody({ children, className, style }) {
3066
3747
  return (jsxRuntime.jsx("div", { className: classNames('toast-body', className), style: style, children: children }));
3067
3748
  }
3068
3749
 
3069
- function DToast$1({ children, className, style, dataAttributes, }) {
3070
- return (jsxRuntime.jsx("div", Object.assign({ className: classNames('toast', className), role: "alert", "aria-live": "assertive", "aria-atomic": "true", style: style }, dataAttributes, { children: children })));
3750
+ const LIVE_REGION_ATTRIBUTES = {
3751
+ alert: { role: 'alert', 'aria-live': 'assertive', 'aria-atomic': 'true' },
3752
+ status: { role: 'status', 'aria-live': 'polite', 'aria-atomic': 'true' },
3753
+ none: {},
3754
+ };
3755
+ function DToast$1({ children, role = 'alert', className, style, dataAttributes, }) {
3756
+ return (jsxRuntime.jsx("div", Object.assign({ className: classNames('toast', className) }, LIVE_REGION_ATTRIBUTES[role], { style: style }, dataAttributes, { children: children })));
3071
3757
  }
3072
3758
  var DToast = Object.assign(DToast$1, {
3073
3759
  Header: DToastHeader,
@@ -3106,19 +3792,19 @@ function useDToast() {
3106
3792
  if (typeof data === 'function') {
3107
3793
  return reactHotToast.toast.custom(data, toastProps);
3108
3794
  }
3109
- const { title, description, icon, closeIcon, timestamp, color, } = data;
3795
+ const { title, description, icon, closeIcon, timestamp, color, role, closeAriaLabel = 'Close', } = data;
3110
3796
  return reactHotToast.toast.custom(({ id, visible }) => {
3111
3797
  if (!visible) {
3112
3798
  return null;
3113
3799
  }
3114
3800
  if (!description) {
3115
- return (jsxRuntime.jsx(DToast, { className: classNames({
3801
+ return (jsxRuntime.jsx(DToast, { role: role, className: classNames({
3116
3802
  [`toast-${color}`]: !!color,
3117
- }, 'show'), children: jsxRuntime.jsxs(DToast.Body, { children: [icon && (jsxRuntime.jsx(DIcon, { className: "toast-icon", icon: icon })), jsxRuntime.jsx("p", { className: "toast-title", children: title }), jsxRuntime.jsx("button", { type: "button", className: "d-close", "aria-label": "Close", onClick: () => reactHotToast.toast.dismiss(id), children: jsxRuntime.jsx(DIcon, { icon: closeIcon || xLg }) })] }) }));
3803
+ }, 'show'), children: jsxRuntime.jsxs(DToast.Body, { children: [icon && (jsxRuntime.jsx(DIcon, { className: "toast-icon", icon: icon })), jsxRuntime.jsx("p", { className: "toast-title", children: title }), jsxRuntime.jsx("button", { type: "button", className: "d-close", "aria-label": closeAriaLabel, onClick: () => reactHotToast.toast.dismiss(id), children: jsxRuntime.jsx(DIcon, { icon: closeIcon || xLg }) })] }) }));
3118
3804
  }
3119
- return (jsxRuntime.jsxs(DToast, { className: classNames({
3805
+ return (jsxRuntime.jsxs(DToast, { role: role, className: classNames({
3120
3806
  [`toast-${color}`]: !!color,
3121
- }, 'show'), children: [jsxRuntime.jsxs(DToast.Header, { children: [icon && (jsxRuntime.jsx(DIcon, { className: "toast-icon", icon: icon })), jsxRuntime.jsx("p", { className: "toast-title", children: title }), timestamp && (jsxRuntime.jsx("small", { className: "toast-timestamp", children: timestamp })), jsxRuntime.jsx("button", { type: "button", className: "d-close", "aria-label": "Close", onClick: () => reactHotToast.toast.dismiss(id), children: jsxRuntime.jsx(DIcon, { icon: closeIcon || xLg }) })] }), jsxRuntime.jsx(DToast.Body, { children: jsxRuntime.jsx("span", { children: description }) })] }));
3807
+ }, 'show'), children: [jsxRuntime.jsxs(DToast.Header, { children: [icon && (jsxRuntime.jsx(DIcon, { className: "toast-icon", icon: icon })), jsxRuntime.jsx("p", { className: "toast-title", children: title }), timestamp && (jsxRuntime.jsx("small", { className: "toast-timestamp", children: timestamp })), jsxRuntime.jsx("button", { type: "button", className: "d-close", "aria-label": closeAriaLabel, onClick: () => reactHotToast.toast.dismiss(id), children: jsxRuntime.jsx(DIcon, { icon: closeIcon || xLg }) })] }), jsxRuntime.jsx(DToast.Body, { children: jsxRuntime.jsx("span", { children: description }) })] }));
3122
3808
  }, toastProps);
3123
3809
  }, [xLg]);
3124
3810
  return {
@@ -3354,7 +4040,7 @@ function DInputPhone(_a, ref) {
3354
4040
  placeholder,
3355
4041
  valid,
3356
4042
  ]);
3357
- const labelComponent = React.useMemo(() => (jsxRuntime.jsx("label", { htmlFor: id, children: label })), [
4043
+ const labelComponent = React.useMemo(() => (jsxRuntime.jsx(DFormLabel, { htmlFor: id, children: label })), [
3358
4044
  id,
3359
4045
  label,
3360
4046
  ]);
@@ -3368,7 +4054,16 @@ function DInputPhone(_a, ref) {
3368
4054
  inputComponent,
3369
4055
  labelComponent,
3370
4056
  ]);
3371
- return (jsxRuntime.jsxs("div", Object.assign({ className: classNames('d-input-phone', className), style: style }, dataAttributes, { children: [label && !floatingLabel && labelComponent, jsxRuntime.jsxs("div", { className: classNames({
4057
+ if (process.env.NODE_ENV !== 'production') {
4058
+ warnLabelUsage({
4059
+ component: 'DInputPhone',
4060
+ label,
4061
+ hasAccessibleName: !!inputProps['aria-label'] || !!inputProps['aria-labelledby'],
4062
+ accessibleNameProp: 'aria-label',
4063
+ floatingLabel,
4064
+ });
4065
+ }
4066
+ return (jsxRuntime.jsxs("div", Object.assign({ className: classNames('d-input-phone', className), style: style }, dataAttributes, { children: [hasLabelContent(label) && !floatingLabel && labelComponent, jsxRuntime.jsxs("div", { className: classNames({
3372
4067
  [`input-group-${size}`]: !!size,
3373
4068
  'input-group': true,
3374
4069
  'has-validation': invalid || valid,
@@ -3835,11 +4530,11 @@ function DOtp({ className, action, isLoading, otpSize = 6, texts = TEXT_PROPS, s
3835
4530
  }, loading: isLoading || isSubmitting }), jsxRuntime.jsx("p", { className: "d-otp-contact", children: texts.contact })] })] })] }));
3836
4531
  }
3837
4532
 
3838
- function DefaultErrorBoundary({ resetErrorBoundary }) {
3839
- return (jsxRuntime.jsx(DAlert, { color: "danger", showClose: false, children: jsxRuntime.jsxs("div", { className: "d-error-boundary-content", children: [jsxRuntime.jsx("span", { children: "An unexpected error occurred." }), jsxRuntime.jsx(DButton, { color: "secondary", variant: "outline", size: "sm", onClick: resetErrorBoundary, children: "Retry" })] }) }));
4533
+ function DefaultErrorBoundary({ resetErrorBoundary, message = 'An unexpected error occurred.', retryMessage = 'Retry', }) {
4534
+ return (jsxRuntime.jsx(DAlert, { color: "danger", showClose: false, children: jsxRuntime.jsxs("div", { className: "d-error-boundary-content", children: [jsxRuntime.jsx("span", { children: message }), jsxRuntime.jsx(DButton, { color: "secondary", variant: "outline", size: "sm", onClick: resetErrorBoundary, children: retryMessage })] }) }));
3840
4535
  }
3841
4536
 
3842
- function DErrorBoundary({ name, fallback, resetKeys, onReset, onError, children, }) {
4537
+ function DErrorBoundary({ name, fallback, resetKeys, onReset, onError, messages, children, }) {
3843
4538
  const handleError = React.useCallback((error, info) => {
3844
4539
  // eslint-disable-next-line no-console
3845
4540
  console.error(`[DErrorBoundary${name ? `:${name}` : ''}]`, reactErrorBoundary.getErrorMessage(error), info);
@@ -3848,8 +4543,8 @@ function DErrorBoundary({ name, fallback, resetKeys, onReset, onError, children,
3848
4543
  const FallbackRender = React.useCallback((props) => {
3849
4544
  if (fallback)
3850
4545
  return fallback(props);
3851
- return (jsxRuntime.jsx(DefaultErrorBoundary, { resetErrorBoundary: props.resetErrorBoundary }));
3852
- }, [fallback]);
4546
+ return (jsxRuntime.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 }));
4547
+ }, [fallback, messages]);
3853
4548
  return (jsxRuntime.jsx(reactErrorBoundary.ErrorBoundary, { resetKeys: resetKeys, onReset: onReset, onError: handleError, fallbackRender: FallbackRender, children: children }));
3854
4549
  }
3855
4550
 
@@ -3870,6 +4565,11 @@ function render(renderable) {
3870
4565
  return null;
3871
4566
  return typeof renderable === 'function' ? renderable() : renderable;
3872
4567
  }
4568
+ function isEmpty(data) {
4569
+ if (Array.isArray(data))
4570
+ return data.length === 0;
4571
+ return data === null || data === undefined;
4572
+ }
3873
4573
  function DDataStateWrapper({ isLoading, isError, data, onRetry, messages, renderLoading, renderEmpty, renderError, children, }) {
3874
4574
  // 1. Loading
3875
4575
  if (isLoading) {
@@ -3883,13 +4583,15 @@ function DDataStateWrapper({ isLoading, isError, data, onRetry, messages, render
3883
4583
  return render(renderError);
3884
4584
  return (jsxRuntime.jsx(ErrorState, { onRetry: onRetry, message: messages === null || messages === void 0 ? void 0 : messages.error, retryMessage: messages === null || messages === void 0 ? void 0 : messages.retry }));
3885
4585
  }
3886
- // 3. Empty
3887
- if (!(data === null || data === void 0 ? void 0 : data.length)) {
4586
+ // 3. Empty: no items for a collection, null/undefined for a single resource
4587
+ if (isEmpty(data)) {
3888
4588
  if (renderEmpty)
3889
4589
  return render(renderEmpty);
3890
4590
  return (jsxRuntime.jsx(EmptyState, { message: messages === null || messages === void 0 ? void 0 : messages.empty }));
3891
4591
  }
3892
- // 4. Success
4592
+ // 4. Success: the render prop gets the same shape it was given
4593
+ // Both overloads pair `data` with its own `children` signature, so the
4594
+ // value is handed back exactly as it was received.
3893
4595
  return jsxRuntime.jsx(jsxRuntime.Fragment, { children: children(data) });
3894
4596
  }
3895
4597
 
@@ -4097,6 +4799,7 @@ exports.DStepperDesktop = DStepper$2;
4097
4799
  exports.DStepperMobile = DStepper$1;
4098
4800
  exports.DTabContent = DTabContent;
4099
4801
  exports.DTabs = DTabs;
4802
+ exports.DTabsProvider = DTabsProvider;
4100
4803
  exports.DTimeline = DTimeline;
4101
4804
  exports.DToast = DToast;
4102
4805
  exports.DToastContainer = DToastContainer;