@dynamic-framework/ui-react 2.6.0 → 2.8.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 (49) hide show
  1. package/dist/css/dynamic-ui.css +278 -3
  2. package/dist/css/dynamic-ui.min.css +2 -2
  3. package/dist/index.esm.js +575 -284
  4. package/dist/index.esm.js.map +1 -1
  5. package/dist/index.js +577 -281
  6. package/dist/index.js.map +1 -1
  7. package/dist/types/components/DCarousel/DCarousel.d.ts +9 -2
  8. package/dist/types/components/DDropdown/DDropdown.d.ts +10 -2
  9. package/dist/types/components/DIcon/DIcon.d.ts +4 -4
  10. package/dist/types/components/DIconBase/DIconBase.d.ts +3 -2
  11. package/dist/types/components/DIconBase/index.d.ts +1 -0
  12. package/dist/types/components/DOffcanvas/DOffcanvas.d.ts +15 -3
  13. package/dist/types/components/DOtp/DOtp.d.ts +1 -0
  14. package/dist/types/components/DOtp/components/OtpCountdown.d.ts +3 -2
  15. package/dist/types/components/DVoucher/DVoucher.d.ts +3 -1
  16. package/dist/types/components/DVoucher/hooks/useScreenshotDownload.d.ts +1 -1
  17. package/dist/types/components/DVoucher/hooks/useScreenshotDownload.spec.d.ts +1 -0
  18. package/dist/types/components/DVoucher/hooks/useScreenshotWebShare.d.ts +1 -1
  19. package/dist/types/components/DVoucher/hooks/useScreenshotWebShare.spec.d.ts +1 -0
  20. package/dist/types/components/DVoucher/index.d.ts +3 -0
  21. package/dist/types/components/index.d.ts +1 -1
  22. package/dist/types/components/interface.d.ts +5 -3
  23. package/dist/types/contexts/DContext.d.ts +3 -2
  24. package/dist/types/hooks/index.d.ts +3 -0
  25. package/dist/types/hooks/tests/useCountdown.spec.d.ts +1 -0
  26. package/dist/types/hooks/useCountdown.d.ts +14 -0
  27. package/dist/types/hooks/useInputCurrency.d.ts +1 -6
  28. package/dist/types/hooks/useOtp.d.ts +98 -0
  29. package/package.json +3 -1
  30. package/src/style/abstracts/variables/_+import.scss +1 -0
  31. package/src/style/abstracts/variables/_input-currency.scss +1 -0
  32. package/src/style/abstracts/variables/_offcanvas.scss +8 -0
  33. package/src/style/components/_+import.scss +7 -0
  34. package/src/style/components/_d-box-file.scss +5 -0
  35. package/src/style/components/_d-carousel.scss +19 -1
  36. package/src/style/components/_d-close.scss +1 -0
  37. package/src/style/components/_d-confirm-modal.scss +17 -0
  38. package/src/style/components/_d-data-state-wrapper.scss +47 -0
  39. package/src/style/components/_d-datepicker.scss +11 -0
  40. package/src/style/components/_d-error-boundary.scss +7 -0
  41. package/src/style/components/_d-input-currency.scss +5 -0
  42. package/src/style/components/_d-otp.scss +54 -0
  43. package/src/style/components/_d-password-strength-meter.scss +76 -0
  44. package/src/style/components/_d-popover.scss +4 -0
  45. package/src/style/components/_d-stepper.scss +8 -0
  46. package/src/style/components/_d-tabs.scss +18 -0
  47. package/src/style/components/_d-voucher.scss +30 -0
  48. package/dist/types/components/DOtp/DOtpspec.d.ts +0 -0
  49. package/dist/types/components/DOtp/hooks/useCountdown.d.ts +0 -4
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ var classNames = require('classnames');
5
5
  var React = require('react');
6
6
  var tslib = require('tslib');
7
7
  var LucideIcons = require('lucide-react');
8
+ var reactIs = require('react-is');
8
9
  var reactDom = require('react-dom');
9
10
  var framerMotion = require('framer-motion');
10
11
  var fileSelector = require('file-selector');
@@ -329,6 +330,7 @@ const DEFAULT_STATE = {
329
330
  familyPrefix: 'bi-',
330
331
  materialStyle: false,
331
332
  },
333
+ iconRegistry: undefined,
332
334
  iconMap: {
333
335
  x: 'X',
334
336
  xLg: 'X',
@@ -394,11 +396,12 @@ const DContext = React.createContext(DEFAULT_STATE);
394
396
  * @template T - Map of portal name → payload shape (e.g. `{ modal: { title: string } }`).
395
397
  * Pass it once at the top level: `<DContextProvider<MyPortals> ...>`.
396
398
  */
397
- function DContextProvider({ language = DEFAULT_STATE.language, currency = DEFAULT_STATE.currency, icon = DEFAULT_STATE.icon, iconMap = DEFAULT_STATE.iconMap, portalName = 'd-portal', availablePortals, children, }) {
399
+ function DContextProvider({ language = DEFAULT_STATE.language, currency = DEFAULT_STATE.currency, icon = DEFAULT_STATE.icon, iconRegistry = DEFAULT_STATE.iconRegistry, iconMap = DEFAULT_STATE.iconMap, portalName = 'd-portal', availablePortals, children, }) {
398
400
  const [internalContext, setInternalContext,] = React.useState({
399
401
  language,
400
402
  currency,
401
403
  icon,
404
+ iconRegistry,
402
405
  iconMap,
403
406
  breakpoints: DEFAULT_STATE.breakpoints,
404
407
  });
@@ -515,17 +518,21 @@ function useResponsiveProp(useListener = false) {
515
518
  return { responsivePropValue };
516
519
  }
517
520
 
521
+ function isIconComponent(value) {
522
+ return typeof value !== 'string' && reactIs.isValidElementType(value);
523
+ }
518
524
  function DIconBase({ icon, color, style, className, size, useListenerSize = false, hasCircle = false, materialStyle = false, familyClass, familyPrefix, strokeWidth = 2, dataAttributes, }) {
519
525
  // If materialStyle is true, use Material Design icons (legacy)
520
- const useMaterialIcons = materialStyle;
526
+ const isStringIcon = typeof icon === 'string';
527
+ const useMaterialIcons = materialStyle && isStringIcon;
521
528
  // Get Lucide icon component
522
529
  const LucideIcon = React.useMemo(() => {
523
- if (useMaterialIcons)
530
+ if (!isStringIcon || useMaterialIcons)
524
531
  return null;
525
532
  // Try to find the icon in Lucide (expects PascalCase)
526
533
  const icons = LucideIcons__namespace;
527
534
  return icons[icon] || null;
528
- }, [icon, useMaterialIcons]);
535
+ }, [icon, isStringIcon, useMaterialIcons]);
529
536
  const { responsivePropValue } = useResponsiveProp(useListenerSize);
530
537
  const resolvedSize = React.useMemo(() => {
531
538
  if (!size)
@@ -545,24 +552,35 @@ function DIconBase({ icon, color, style, className, size, useListenerSize = fals
545
552
  }, [resolvedSize]);
546
553
  // Render Material Design icon (legacy support)
547
554
  if (useMaterialIcons) {
548
- return (jsxRuntime.jsx("i", Object.assign({ className: classNames(generateClasses, familyClass), style: generateStyleVariables }, dataAttributes, { children: icon })));
555
+ return (jsxRuntime.jsx("i", Object.assign({ className: classNames(generateClasses, familyClass), style: generateStyleVariables }, dataAttributes, { children: isStringIcon ? icon : null })));
556
+ }
557
+ if (isIconComponent(icon)) {
558
+ return (jsxRuntime.jsx("span", Object.assign({ className: classNames(generateClasses), style: generateStyleVariables }, dataAttributes, { children: React.createElement(icon, {
559
+ width: resolvedSize || 24,
560
+ height: resolvedSize || 24,
561
+ strokeWidth,
562
+ }) })));
549
563
  }
550
564
  // Render Lucide icon
551
565
  if (!LucideIcon) {
552
- if (familyClass && familyPrefix) {
566
+ if (isStringIcon && familyClass && familyPrefix) {
553
567
  return (jsxRuntime.jsx("i", Object.assign({ className: classNames(generateClasses, familyClass, `${familyPrefix}${icon}`), style: generateStyleVariables }, dataAttributes)));
554
568
  }
555
569
  // eslint-disable-next-line no-console
556
- console.warn(`Icon "${icon}" not found in Lucide. Make sure to use PascalCase names (e.g., "Home", "User", "Settings")`);
570
+ console.warn(`Icon "${String(icon)}" not found in Lucide. Make sure to use PascalCase names (e.g., "Home", "User", "Settings")`);
557
571
  return (jsxRuntime.jsx("span", Object.assign({ className: classNames(generateClasses), style: generateStyleVariables }, dataAttributes, { children: "?" })));
558
572
  }
559
573
  return (jsxRuntime.jsx("span", Object.assign({ className: classNames(generateClasses), style: generateStyleVariables }, dataAttributes, { children: jsxRuntime.jsx(LucideIcon, { size: iconSize || 24, strokeWidth: strokeWidth }) })));
560
574
  }
561
575
 
562
576
  function DIcon(_a) {
563
- var { familyClass: propFamilyClass, familyPrefix: propFamilyPrefix, materialStyle: propMaterialStyle } = _a, props = tslib.__rest(_a, ["familyClass", "familyPrefix", "materialStyle"]);
564
- const { icon: { familyClass, familyPrefix, materialStyle, }, } = useDContext();
565
- return (jsxRuntime.jsx(DIconBase, Object.assign({ familyClass: propFamilyClass || familyClass, familyPrefix: propFamilyPrefix || familyPrefix, materialStyle: propMaterialStyle || materialStyle }, props)));
577
+ var { icon, familyClass: propFamilyClass, familyPrefix: propFamilyPrefix, materialStyle: propMaterialStyle } = _a, props = tslib.__rest(_a, ["icon", "familyClass", "familyPrefix", "materialStyle"]);
578
+ const { icon: { familyClass, familyPrefix, materialStyle, }, iconRegistry, } = useDContext();
579
+ const registryIcon = typeof icon === 'string'
580
+ ? iconRegistry === null || iconRegistry === void 0 ? void 0 : iconRegistry[icon]
581
+ : undefined;
582
+ const resolvedIcon = registryIcon || icon;
583
+ return (jsxRuntime.jsx(DIconBase, Object.assign({ icon: resolvedIcon, familyClass: propFamilyClass !== null && propFamilyClass !== void 0 ? propFamilyClass : familyClass, familyPrefix: propFamilyPrefix !== null && propFamilyPrefix !== void 0 ? propFamilyPrefix : familyPrefix, materialStyle: propMaterialStyle !== null && propMaterialStyle !== void 0 ? propMaterialStyle : materialStyle }, props)));
566
584
  }
567
585
 
568
586
  function DAlert({ color = 'success', icon: iconProp, iconFamilyClass, iconFamilyPrefix, iconMaterialStyle = false, iconClose: iconCloseProp, iconCloseFamilyClass, iconCloseFamilyPrefix, iconCloseMaterialStyle = false, showClose, onClose, children, id, className, style, dataAttributes, }) {
@@ -1108,7 +1126,7 @@ function DBoxFile(_a) {
1108
1126
  'd-box-file-invalid': isDragInvalid,
1109
1127
  }, className), style: style }, dataAttributes, { children: jsxRuntime.jsxs("div", Object.assign({ className: "d-box-file-dropzone", ref: rootRef, onDragEnter: handleDragEnter, onDragOver: (e) => e.preventDefault(), onDragLeave: handleDragLeave, onDrop: handleDrop, onClick: handleClick, onKeyDown: handleKeyDown }, (!props.disabled && !props.noKeyboard ? { tabIndex: 0 } : {}), { role: "presentation", children: [jsxRuntime.jsx("input", { type: "file", multiple: props.multiple, style: { display: 'none' }, ref: inputRef, disabled: props.disabled, onChange: handleFileSelect, onClick: (e) => e.stopPropagation(), tabIndex: -1, accept: acceptAttr }), icon && iconProp !== false && (jsxRuntime.jsx(DIcon, { icon: icon, familyClass: iconFamilyClass, familyPrefix: iconFamilyPrefix, materialStyle: iconMaterialStyle })), jsxRuntime.jsx("div", { className: "d-box-content", children: typeof children === 'function'
1110
1128
  ? children(openFileDialog)
1111
- : children || (jsxRuntime.jsx("p", { className: "text-center m-0", children: "Drag and drop some files here, or click to select files" })) })] })) })), !!files.length && (jsxRuntime.jsx("ul", { className: "d-box-files", children: files.map((file, index) => (jsxRuntime.jsx(ForwardedDInput, { value: file.name, iconStart: "Paperclip", iconEnd: "Trash", readOnly: true, onIconEndClick: () => handleRemoveFile(index) }, `${file.name} ${index}`))) }))] }));
1129
+ : children || (jsxRuntime.jsx("p", { className: "d-box-file-empty-text", children: "Drag and drop some files here, or click to select files" })) })] })) })), !!files.length && (jsxRuntime.jsx("ul", { className: "d-box-files", children: files.map((file, index) => (jsxRuntime.jsx(ForwardedDInput, { value: file.name, iconStart: "Paperclip", iconEnd: "Trash", readOnly: true, onIconEndClick: () => handleRemoveFile(index) }, `${file.name} ${index}`))) }))] }));
1112
1130
  }
1113
1131
 
1114
1132
  const DButton = React.forwardRef((props, ref) => {
@@ -1242,8 +1260,14 @@ function DCarouselSlide(_a) {
1242
1260
  return (jsxRuntime.jsx(reactSplide.SplideSlide, Object.assign({ className: classNames('d-carousel-slide', className) }, props)));
1243
1261
  }
1244
1262
 
1263
+ const DEFAULT_ARROW_PATH = 'm15.5 0.932-4.3 4.38 14.5 14.6-14.5 14.5 4.3 4.4 14.6-14.6 4.4-4.3-4.4-4.4-14.6-14.6z';
1264
+ function DefaultArrowIcon() {
1265
+ return (jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 40 40", width: 40, height: 40, focusable: "false", "aria-hidden": "true", children: jsxRuntime.jsx("path", { d: DEFAULT_ARROW_PATH }) }));
1266
+ }
1245
1267
  function DCarousel$1(_a, ref) {
1246
- var { children, className, style, options, dataAttributes } = _a, props = tslib.__rest(_a, ["children", "className", "style", "options", "dataAttributes"]);
1268
+ var { children, className, style, options, dataAttributes, iconArrowLeft, iconArrowRight, hasTrack: propsHasTrack } = _a, props = tslib.__rest(_a, ["children", "className", "style", "options", "dataAttributes", "iconArrowLeft", "iconArrowRight", "hasTrack"]);
1269
+ // Explicit `options.arrows === false` always wins, even when icon props are set.
1270
+ const hasCustomArrows = Boolean((iconArrowLeft || iconArrowRight) && (options === null || options === void 0 ? void 0 : options.arrows) !== false);
1247
1271
  return (jsxRuntime.jsx(reactSplide.Splide, Object.assign({ className: classNames('d-carousel', className), style: style, ref: ref, options: Object.assign(Object.assign({}, options), { classes: {
1248
1272
  // Arrows
1249
1273
  arrows: 'splide__arrows d-carousel-arrows',
@@ -1253,7 +1277,11 @@ function DCarousel$1(_a, ref) {
1253
1277
  // Paginator
1254
1278
  pagination: 'splide__pagination d-carousel-pagination',
1255
1279
  page: 'splide__pagination__page d-carousel-pagination-page',
1256
- } }) }, dataAttributes, props, { children: children })));
1280
+ } }) }, dataAttributes, props, {
1281
+ // Rendering our own arrows requires taking over the track wrapper, so this
1282
+ // must be applied after `...props` to prevent a consumer-provided `hasTrack`
1283
+ // from re-enabling Splide's automatic track when custom arrows are active.
1284
+ hasTrack: hasCustomArrows ? false : propsHasTrack, children: hasCustomArrows ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(reactSplide.SplideTrack, { children: children }), jsxRuntime.jsxs("div", { className: "splide__arrows d-carousel-arrows", children: [jsxRuntime.jsx("button", { type: "button", className: "splide__arrow splide__arrow--prev d-carousel-arrow d-carousel-arrow-prev", children: iconArrowLeft ? jsxRuntime.jsx(DIcon, Object.assign({}, iconArrowLeft)) : jsxRuntime.jsx(DefaultArrowIcon, {}) }), jsxRuntime.jsx("button", { type: "button", className: "splide__arrow splide__arrow--next d-carousel-arrow d-carousel-arrow-next", children: iconArrowRight ? jsxRuntime.jsx(DIcon, Object.assign({}, iconArrowRight)) : jsxRuntime.jsx(DefaultArrowIcon, {}) })] })] })) : children })));
1257
1285
  }
1258
1286
  const ForwardedDCarousel = React.forwardRef(DCarousel$1);
1259
1287
  ForwardedDCarousel.displayName = 'DCarousel';
@@ -1474,9 +1502,9 @@ function DDatePickerHeaderSelector({ date, changeYear, changeMonth, decreaseMont
1474
1502
  return (jsxRuntime.jsxs("div", { className: classNames('react-datepicker__header-selector react-datepicker__header-year-selector', className), style: style, children: [jsxRuntime.jsx(DButtonIcon, { icon: iconPrev || chevronLeft, size: iconSize, variant: "link", onClick: decreaseYear, disabled: prevYearButtonDisabled, "aria-label": prevYearAriaLabel, className: "header-button" }), jsxRuntime.jsx("p", { children: `${startYear} - ${endYear}` }), jsxRuntime.jsx(DButtonIcon, { icon: iconNext || chevronRight, size: iconSize, variant: "link", onClick: increaseYear, disabled: nextYearButtonDisabled, "aria-label": nextYearAriaLabel, className: "header-button" })] }));
1475
1503
  }
1476
1504
  if (pickerType === PickerType.Quarter || pickerType === PickerType.Month) {
1477
- return (jsxRuntime.jsxs("div", { className: classNames(`react-datepicker__header-selector react-datepicker__header-${pickerType}-selector`, className), style: style, children: [jsxRuntime.jsx(DButtonIcon, { icon: iconPrev || chevronLeft, size: iconSize, variant: "link", onClick: decreaseYear, disabled: prevMonthButtonDisabled, "aria-label": prevMonthAriaLabel, className: "header-button", style: { visibility: customHeaderCount === 0 ? 'visible' : 'hidden' } }), jsxRuntime.jsx("div", { className: "d-flex justify-content-center flex-grow-1", children: showHeaderSelectors ? (jsxRuntime.jsx(DSelect, { options: years, value: defaultYear, defaultValue: defaultYear, onChange: (year) => changeYear(Number(year === null || year === void 0 ? void 0 : year.value)), searchable: false })) : (jsxRuntime.jsx("p", { children: defaultYear === null || defaultYear === void 0 ? void 0 : defaultYear.label })) }), jsxRuntime.jsx(DButtonIcon, { icon: iconNext || chevronRight, size: iconSize, variant: "link", onClick: increaseYear, disabled: nextMonthButtonDisabled, "aria-label": nextMonthAriaLabel, className: "header-button", style: { visibility: customHeaderCount === monthsShown - 1 ? 'visible' : 'hidden' } })] }));
1505
+ return (jsxRuntime.jsxs("div", { className: classNames(`react-datepicker__header-selector react-datepicker__header-${pickerType}-selector`, className), style: style, children: [jsxRuntime.jsx(DButtonIcon, { icon: iconPrev || chevronLeft, size: iconSize, variant: "link", onClick: decreaseYear, disabled: prevMonthButtonDisabled, "aria-label": prevMonthAriaLabel, className: "header-button", style: { visibility: customHeaderCount === 0 ? 'visible' : 'hidden' } }), jsxRuntime.jsx("div", { className: "react-datepicker__header-year-selector", children: showHeaderSelectors ? (jsxRuntime.jsx(DSelect, { options: years, value: defaultYear, defaultValue: defaultYear, onChange: (year) => changeYear(Number(year === null || year === void 0 ? void 0 : year.value)), searchable: false })) : (jsxRuntime.jsx("p", { children: defaultYear === null || defaultYear === void 0 ? void 0 : defaultYear.label })) }), jsxRuntime.jsx(DButtonIcon, { icon: iconNext || chevronRight, size: iconSize, variant: "link", onClick: increaseYear, disabled: nextMonthButtonDisabled, "aria-label": nextMonthAriaLabel, className: "header-button", style: { visibility: customHeaderCount === monthsShown - 1 ? 'visible' : 'hidden' } })] }));
1478
1506
  }
1479
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: "datepicker-top-header", children: [showHeaderSelectors && (jsxRuntime.jsx(DSelect, { options: years, value: defaultYear, defaultValue: defaultYear, onChange: (year) => changeYear(Number(year === null || year === void 0 ? void 0 : year.value)), searchable: false, className: "custom-year-selector" })), jsxRuntime.jsx("h4", { className: "mb-0 fw-normal", children: dateFns.format(monthDate, formatHeaderDate, { locale }) })] }), jsxRuntime.jsxs("div", { className: classNames('react-datepicker__header-selector react-datepicker__header-day-selector', className), style: style, children: [jsxRuntime.jsx(DButtonIcon, { icon: iconPrev || chevronLeft, size: iconSize, variant: "link", onClick: decreaseMonth, disabled: prevMonthButtonDisabled, "aria-label": prevMonthAriaLabel, className: "header-button", style: { visibility: customHeaderCount === 0 ? 'visible' : 'hidden' } }), showHeaderSelectors ? (jsxRuntime.jsx(DSelect, { options: months, value: defaultMonth, defaultValue: defaultMonth, onChange: (month) => changeMonth((month === null || month === void 0 ? void 0 : month.value) || 0), searchable: false, className: "custom-month-selector" })) : (jsxRuntime.jsx("p", { children: `${defaultMonth.label} ${defaultYear === null || defaultYear === void 0 ? void 0 : defaultYear.label}` })), jsxRuntime.jsx(DButtonIcon, { icon: iconNext || chevronRight, size: iconSize, variant: "link", onClick: increaseMonth, disabled: nextMonthButtonDisabled, "aria-label": nextMonthAriaLabel, className: "header-button", style: { visibility: customHeaderCount === monthsShown - 1 ? 'visible' : 'hidden' } })] })] }));
1507
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: "datepicker-top-header", children: [showHeaderSelectors && (jsxRuntime.jsx(DSelect, { options: years, value: defaultYear, defaultValue: defaultYear, onChange: (year) => changeYear(Number(year === null || year === void 0 ? void 0 : year.value)), searchable: false, className: "custom-year-selector" })), jsxRuntime.jsx("h4", { className: "datepicker-top-header-title", children: dateFns.format(monthDate, formatHeaderDate, { locale }) })] }), jsxRuntime.jsxs("div", { className: classNames('react-datepicker__header-selector react-datepicker__header-day-selector', className), style: style, children: [jsxRuntime.jsx(DButtonIcon, { icon: iconPrev || chevronLeft, size: iconSize, variant: "link", onClick: decreaseMonth, disabled: prevMonthButtonDisabled, "aria-label": prevMonthAriaLabel, className: "header-button", style: { visibility: customHeaderCount === 0 ? 'visible' : 'hidden' } }), showHeaderSelectors ? (jsxRuntime.jsx(DSelect, { options: months, value: defaultMonth, defaultValue: defaultMonth, onChange: (month) => changeMonth((month === null || month === void 0 ? void 0 : month.value) || 0), searchable: false, className: "custom-month-selector" })) : (jsxRuntime.jsx("p", { children: `${defaultMonth.label} ${defaultYear === null || defaultYear === void 0 ? void 0 : defaultYear.label}` })), jsxRuntime.jsx(DButtonIcon, { icon: iconNext || chevronRight, size: iconSize, variant: "link", onClick: increaseMonth, disabled: nextMonthButtonDisabled, "aria-label": nextMonthAriaLabel, className: "header-button", style: { visibility: customHeaderCount === monthsShown - 1 ? 'visible' : 'hidden' } })] })] }));
1480
1508
  }
1481
1509
 
1482
1510
  function DDatePicker(_a) {
@@ -1611,11 +1639,24 @@ function formatValue(value, currencyOptions) {
1611
1639
  }
1612
1640
  return currency(value, Object.assign(Object.assign({}, currencyOptions), { symbol: '' })).format();
1613
1641
  }
1614
- function useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref) {
1642
+ function useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref, minValue, maxValue) {
1615
1643
  const inputRef = useProvidedRefOrCreate(ref);
1644
+ const clampValue = React.useCallback((newValue) => {
1645
+ if (newValue === undefined) {
1646
+ return newValue;
1647
+ }
1648
+ let clampedValue = newValue;
1649
+ if (minValue !== undefined) {
1650
+ clampedValue = Math.max(clampedValue, minValue);
1651
+ }
1652
+ if (maxValue !== undefined) {
1653
+ clampedValue = Math.min(clampedValue, maxValue);
1654
+ }
1655
+ return clampedValue;
1656
+ }, [minValue, maxValue]);
1616
1657
  const [innerType, setInnerType] = React.useState('text');
1617
- const [innerNumber, setInnerNumber] = React.useState(value);
1618
- const [innerString, setInnerString] = React.useState(formatValue(value, currencyOptions));
1658
+ const [innerNumber, setInnerNumber] = React.useState(clampValue(value));
1659
+ const [innerString, setInnerString] = React.useState(formatValue(clampValue(value), currencyOptions));
1619
1660
  const handleOnFocus = React.useCallback((event) => {
1620
1661
  event.stopPropagation();
1621
1662
  setInnerType('number');
@@ -1624,15 +1665,14 @@ function useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref
1624
1665
  const handleOnBlur = React.useCallback((event) => {
1625
1666
  event.stopPropagation();
1626
1667
  setInnerType('text');
1668
+ const clampedNumber = clampValue(innerNumber);
1669
+ if (clampedNumber !== innerNumber) {
1670
+ setInnerNumber(clampedNumber);
1671
+ setInnerString(formatValue(clampedNumber, currencyOptions));
1672
+ onChange === null || onChange === void 0 ? void 0 : onChange(clampedNumber);
1673
+ }
1627
1674
  onBlur === null || onBlur === void 0 ? void 0 : onBlur(event);
1628
- }, [onBlur]);
1629
- const generateStyleVariables = React.useMemo(() => ({
1630
- [`--${PREFIX_BS}input-currency-component-symbol-color`]: `var(--${PREFIX_BS}secondary)`,
1631
- [`--${PREFIX_BS}input-currency-symbol-color`]: `var(--${PREFIX_BS}input-currency-component-symbol-color)`,
1632
- }), []);
1633
- const generateSymbolStyleVariables = React.useMemo(() => ({
1634
- color: `var(--${PREFIX_BS}input-currency-symbol-color)`,
1635
- }), []);
1675
+ }, [onBlur, innerNumber, clampValue, currencyOptions, onChange]);
1636
1676
  const handleOnChange = React.useCallback((newValue) => {
1637
1677
  const newNumber = (newValue === undefined || newValue === '') ? undefined : Number(newValue);
1638
1678
  if (newNumber !== innerNumber) {
@@ -1641,7 +1681,12 @@ function useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref
1641
1681
  onChange === null || onChange === void 0 ? void 0 : onChange(newNumber);
1642
1682
  }
1643
1683
  }, [currencyOptions, onChange, innerNumber]);
1684
+ const isMountedRef = React.useRef(false);
1644
1685
  React.useEffect(() => {
1686
+ if (!isMountedRef.current) {
1687
+ isMountedRef.current = true;
1688
+ return;
1689
+ }
1645
1690
  if (value !== innerNumber) {
1646
1691
  setInnerNumber(value);
1647
1692
  setInnerString(formatValue(value, currencyOptions));
@@ -1655,8 +1700,6 @@ function useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref
1655
1700
  handleOnFocus,
1656
1701
  handleOnChange,
1657
1702
  handleOnBlur,
1658
- generateStyleVariables,
1659
- generateSymbolStyleVariables,
1660
1703
  };
1661
1704
  }
1662
1705
 
@@ -1761,6 +1804,149 @@ function useConfirmModal(config) {
1761
1804
  return { open };
1762
1805
  }
1763
1806
 
1807
+ /**
1808
+ * Generic seconds-based countdown hook.
1809
+ *
1810
+ * Starts counting down from `seconds` to `0` and exposes `restartCountdown`
1811
+ * to reset and restart the timer (e.g. for "resend code in Xs" patterns,
1812
+ * session-expiry banners, or any other cooldown/throttle UI).
1813
+ *
1814
+ * @example
1815
+ * const { secondsLeft, restartCountdown } = useCountdown(30);
1816
+ */
1817
+ function useCountdown(seconds) {
1818
+ const [secondsLeft, setSecondsLeft] = React.useState(seconds);
1819
+ const [isActive, setIsActive] = React.useState(true);
1820
+ const resetCountdown = React.useCallback((newSeconds = seconds) => {
1821
+ setIsActive(false);
1822
+ setSecondsLeft(newSeconds);
1823
+ }, [seconds]);
1824
+ const restartCountdown = React.useCallback(() => {
1825
+ resetCountdown(seconds);
1826
+ setIsActive(true);
1827
+ }, [resetCountdown, seconds]);
1828
+ React.useEffect(() => {
1829
+ if (!isActive) {
1830
+ return () => { };
1831
+ }
1832
+ const interval = setInterval(() => {
1833
+ setSecondsLeft((prevSeconds) => {
1834
+ const newSeconds = prevSeconds - 1;
1835
+ if (newSeconds <= 0) {
1836
+ clearInterval(interval);
1837
+ setIsActive(false);
1838
+ return 0;
1839
+ }
1840
+ return newSeconds;
1841
+ });
1842
+ }, 1000);
1843
+ return () => clearInterval(interval);
1844
+ }, [isActive]);
1845
+ return { secondsLeft, restartCountdown };
1846
+ }
1847
+
1848
+ /**
1849
+ * Headless hook exposing the same OTP logic used internally by `DOtp`
1850
+ * (code state, length + correctness validation, submit action and resend
1851
+ * countdown), without any bundled markup/styles.
1852
+ *
1853
+ * Use this when the default `DOtp` component doesn't match your design and
1854
+ * you need to build a fully custom UI while reusing the same behavior.
1855
+ *
1856
+ * Internally composes `useCountdown` for the resend timer and re-exposes
1857
+ * `secondsLeft`/`restartCountdown`, so there is no need to call
1858
+ * `useCountdown` separately.
1859
+ *
1860
+ * `invalid` becomes `true` both when the OTP is shorter than `otpSize` and
1861
+ * when `action` throws/rejects (e.g. the backend rejected the entered code
1862
+ * as incorrect). In the latter case, the thrown value is exposed as `error`
1863
+ * so custom UIs can render an appropriate message (e.g. "Invalid code" vs.
1864
+ * "Enter all digits"). `invalid`/`error` are cleared automatically as soon
1865
+ * as the user edits the code via `setOtp`.
1866
+ *
1867
+ * @example
1868
+ * function CustomOtp() {
1869
+ * const {
1870
+ * otp,
1871
+ * setOtp,
1872
+ * invalid,
1873
+ * error,
1874
+ * submit,
1875
+ * isLoading,
1876
+ * secondsLeft,
1877
+ * restartCountdown,
1878
+ * } = useOtp({ action: async () => verifyOtp(otp), otpSize: 6, seconds: 15 });
1879
+ *
1880
+ * return (
1881
+ * <>
1882
+ * <MyCustomPinInput value={otp} onChange={setOtp} invalid={invalid} />
1883
+ * {invalid && <span>{error ? 'Invalid code' : 'Enter all digits'}</span>}
1884
+ * <button disabled={isLoading} onClick={() => { submit().catch(() => {}); }}>
1885
+ * {isLoading ? 'Submitting...' : 'Submit'}
1886
+ * </button>
1887
+ * <button disabled={secondsLeft > 0} onClick={restartCountdown}>
1888
+ * Resend {secondsLeft > 0 ? `(${secondsLeft}s)` : ''}
1889
+ * </button>
1890
+ * </>
1891
+ * );
1892
+ * }
1893
+ */
1894
+ function useOtp({ action, otpSize = 6, seconds = 15, }) {
1895
+ const [otp, setOtpValue] = React.useState('');
1896
+ const [invalid, setInvalid] = React.useState(false);
1897
+ const [error, setError] = React.useState(null);
1898
+ const [isLoading, setIsLoading] = React.useState(false);
1899
+ const { secondsLeft, restartCountdown } = useCountdown(seconds);
1900
+ const setOtp = React.useCallback((value) => {
1901
+ setOtpValue((prevOtp) => {
1902
+ // Guard against no-op calls (e.g. DInputPin re-notifying the same
1903
+ // value when its onChange reference changes) so we don't wipe out
1904
+ // an `invalid`/`error` state that was just set by submit().
1905
+ if (prevOtp === value) {
1906
+ return prevOtp;
1907
+ }
1908
+ setInvalid(false);
1909
+ setError(null);
1910
+ return value;
1911
+ });
1912
+ }, []);
1913
+ const submit = React.useCallback(async () => {
1914
+ if (otp.length < otpSize) {
1915
+ setInvalid(true);
1916
+ setError(null);
1917
+ return;
1918
+ }
1919
+ setIsLoading(true);
1920
+ try {
1921
+ await action();
1922
+ setInvalid(false);
1923
+ setError(null);
1924
+ }
1925
+ catch (err) {
1926
+ setInvalid(true);
1927
+ setError(err);
1928
+ throw err;
1929
+ }
1930
+ finally {
1931
+ setIsLoading(false);
1932
+ }
1933
+ }, [
1934
+ otp.length,
1935
+ action,
1936
+ otpSize,
1937
+ ]);
1938
+ return {
1939
+ otp,
1940
+ setOtp,
1941
+ invalid,
1942
+ error,
1943
+ submit,
1944
+ isLoading,
1945
+ secondsLeft,
1946
+ restartCountdown,
1947
+ };
1948
+ }
1949
+
1764
1950
  function DInputCounter(_a, ref) {
1765
1951
  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"]);
1766
1952
  const { handleOnWheel, } = useDisableInputWheel(ref);
@@ -1803,8 +1989,8 @@ function DInputCurrency(_a, ref) {
1803
1989
  var { value, minValue, maxValue, currencyCode, onFocus, onBlur, onChange } = _a, props = tslib.__rest(_a, ["value", "minValue", "maxValue", "currencyCode", "onFocus", "onBlur", "onChange"]);
1804
1990
  const { currency: currencyOptions } = useDContext();
1805
1991
  const { handleOnWheel, } = useDisableInputWheel(ref);
1806
- const { inputRef, innerValue, innerType, handleOnFocus, handleOnChange, handleOnBlur, generateStyleVariables, generateSymbolStyleVariables, } = useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref);
1807
- return (jsxRuntime.jsx(ForwardedDInput, Object.assign({ ref: inputRef, value: innerValue, onChange: handleOnChange, style: generateStyleVariables, inputMode: "decimal", type: innerType, onFocus: handleOnFocus, onBlur: handleOnBlur, onWheel: handleOnWheel, inputStart: (jsxRuntime.jsx("span", { slot: "input-start", style: generateSymbolStyleVariables, children: currencyCode || currencyOptions.symbol })) }, props)));
1992
+ const { inputRef, innerValue, innerType, handleOnFocus, handleOnChange, handleOnBlur, } = useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref, minValue, maxValue);
1993
+ 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)));
1808
1994
  }
1809
1995
  const ForwardedDInputCurrency = React.forwardRef(DInputCurrency);
1810
1996
  ForwardedDInputCurrency.displayName = 'DInputCurrency';
@@ -1826,26 +2012,25 @@ ForwardedDInputPassword.displayName = 'DInputPassword';
1826
2012
 
1827
2013
  function PasswordCheckItem({ password, regex, text, }) {
1828
2014
  const isValid = regex.test(password);
1829
- return (jsxRuntime.jsxs("li", { className: "d-flex gap-2 align-items-start small text-gray-600", children: [jsxRuntime.jsx(DIcon, { className: classNames('flex-shrink-0', isValid ? 'text-success' : 'text-gray-300'), icon: isValid ? 'CircleCheck' : 'Circle', size: "16px" }), jsxRuntime.jsx("span", { className: classNames({ 'text-success': isValid }), children: text })] }));
2015
+ return (jsxRuntime.jsxs("li", { className: `d-password-check-item${isValid ? ' is-valid' : ''}`, children: [jsxRuntime.jsx(DIcon, { className: "d-password-check-item-icon", icon: isValid ? 'CircleCheck' : 'Circle', size: "16px" }), jsxRuntime.jsx("span", { children: text })] }));
1830
2016
  }
1831
2017
 
1832
- const getColorClass = (strength, total) => {
2018
+ const getStrengthModifier = (strength, total) => {
1833
2019
  const percentage = total > 0 ? strength / total : 0;
1834
2020
  if (percentage === 0)
1835
- return 'bg-gray-200';
2021
+ return 'is-empty';
1836
2022
  if (percentage <= 0.25)
1837
- return 'bg-danger';
2023
+ return 'is-weak';
1838
2024
  if (percentage <= 0.5)
1839
- return 'bg-warning';
2025
+ return 'is-fair';
1840
2026
  if (percentage <= 0.75)
1841
- return 'bg-info';
1842
- return 'bg-success';
2027
+ return 'is-good';
2028
+ return 'is-strong';
1843
2029
  };
1844
2030
  function PasswordStrengthBar({ strength, total }) {
1845
2031
  const percentage = total > 0 ? (strength / total) * 100 : 0;
1846
- return (jsxRuntime.jsx("div", { className: "w-100 rounded-3 overflow-hidden bg-gray-100 mb-2", style: { height: '8px' }, children: jsxRuntime.jsx("div", { className: `h-100 ${getColorClass(strength, total)}`, style: {
2032
+ return (jsxRuntime.jsx("div", { className: "d-password-strength-bar", children: jsxRuntime.jsx("div", { className: `d-password-strength-bar-fill ${getStrengthModifier(strength, total)}`, style: {
1847
2033
  width: `${percentage}%`,
1848
- transition: 'width 0.3s ease-in-out',
1849
2034
  } }) }));
1850
2035
  }
1851
2036
 
@@ -1881,7 +2066,7 @@ function PasswordChecksList({ password, validationMessages, enabledChecks, }) {
1881
2066
  const passwordChecks = allChecks.filter((check) => enabledChecks.includes(check.key));
1882
2067
  const passed = passwordChecks.filter((r) => r.regex.test(password)).length;
1883
2068
  const total = passwordChecks.length;
1884
- return (jsxRuntime.jsxs("div", { className: "mt-2", children: [jsxRuntime.jsx(PasswordStrengthBar, { strength: passed, total: total }), jsxRuntime.jsx("ul", { className: "list-unstyled m-0 d-flex flex-column gap-2", children: passwordChecks.map(({ key, regex, text }) => (jsxRuntime.jsx(PasswordCheckItem, { password: password, regex: regex, text: text }, key))) })] }));
2069
+ return (jsxRuntime.jsxs("div", { className: "d-password-strength-meter", children: [jsxRuntime.jsx(PasswordStrengthBar, { strength: passed, total: total }), jsxRuntime.jsx("ul", { className: "d-password-check-list", children: passwordChecks.map(({ key, regex, text }) => (jsxRuntime.jsx(PasswordCheckItem, { password: password, regex: regex, text: text }, key))) })] }));
1885
2070
  }
1886
2071
 
1887
2072
  const DEFAULT_VALIDATION_MESSAGES = {
@@ -2231,7 +2416,7 @@ var DListGroup = Object.assign(DListGroup$1, {
2231
2416
  function DModalHeader({ showCloseButton, onClose, children, className, style, iconFamilyClass, iconFamilyPrefix, icon: iconProp, materialStyle = false, }) {
2232
2417
  const { iconMap: { xLg, }, } = useDContext();
2233
2418
  const icon = React.useMemo(() => iconProp || xLg, [iconProp, xLg]);
2234
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: classNames('modal-header', className), style: style, children: [jsxRuntime.jsx("div", { children: children }), showCloseButton && (jsxRuntime.jsx("button", { type: "button", className: "d-close align-self-center", "aria-label": "Close", onClick: onClose, children: jsxRuntime.jsx(DIcon, { icon: icon, familyClass: iconFamilyClass, familyPrefix: iconFamilyPrefix, materialStyle: materialStyle }) }))] }), jsxRuntime.jsx("div", { className: "d-modal-separator" })] }));
2419
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: classNames('modal-header', className), style: style, children: [jsxRuntime.jsx("div", { children: children }), showCloseButton && (jsxRuntime.jsx("button", { type: "button", className: "d-close", "aria-label": "Close", onClick: onClose, children: jsxRuntime.jsx(DIcon, { icon: icon, familyClass: iconFamilyClass, familyPrefix: iconFamilyPrefix, materialStyle: materialStyle }) }))] }), jsxRuntime.jsx("div", { className: "d-modal-separator" })] }));
2235
2420
  }
2236
2421
 
2237
2422
  function DModalBody({ children, className, style, }) {
@@ -2275,7 +2460,7 @@ var DModal = Object.assign(DModal$1, {
2275
2460
  function DOffcanvasHeader({ showCloseButton, onClose, children, className, style, iconFamilyClass, iconFamilyPrefix, icon: iconProp, materialStyle = false, }) {
2276
2461
  const { iconMap: { xLg, }, } = useDContext();
2277
2462
  const icon = React.useMemo(() => iconProp || xLg, [iconProp, xLg]);
2278
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: classNames('offcanvas-header', className), style: style, children: [jsxRuntime.jsx("div", { children: children }), showCloseButton && (jsxRuntime.jsx("button", { type: "button", className: "d-close align-self-center", "aria-label": "Close", onClick: onClose, children: jsxRuntime.jsx(DIcon, { icon: icon, familyClass: iconFamilyClass, familyPrefix: iconFamilyPrefix, materialStyle: materialStyle }) }))] }), jsxRuntime.jsx("div", { className: "d-offcanvas-separator" })] }));
2463
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: classNames('offcanvas-header', className), style: style, children: [jsxRuntime.jsx("div", { children: children }), showCloseButton && (jsxRuntime.jsx("button", { type: "button", className: "d-close", "aria-label": "Close", onClick: onClose, children: jsxRuntime.jsx(DIcon, { icon: icon, familyClass: iconFamilyClass, familyPrefix: iconFamilyPrefix, materialStyle: materialStyle }) }))] }), jsxRuntime.jsx("div", { className: "d-offcanvas-separator" })] }));
2279
2464
  }
2280
2465
 
2281
2466
  function DOffcanvasBody({ children, className, style, }) {
@@ -2316,10 +2501,37 @@ const defaultTransition = {
2316
2501
  ease: 'easeInOut',
2317
2502
  duration: 0.3,
2318
2503
  };
2319
- function DOffcanvas$1({ name, className, style, staticBackdrop, scrollable, openFrom = 'end', transition, children, dataAttributes, }) {
2504
+ function DOffcanvas$1({ name, className, style, staticBackdrop, scrollable, openFrom = 'end', width, height, transition, children, dataAttributes, }) {
2505
+ // Only subscribe to breakpoint-change listeners when a responsive object is
2506
+ // actually provided, avoiding unnecessary matchMedia subscriptions for the
2507
+ // common case of plain string values.
2508
+ const hasResponsiveProp = typeof openFrom === 'object'
2509
+ || typeof width === 'object'
2510
+ || typeof height === 'object';
2511
+ const { responsivePropValue } = useResponsiveProp(hasResponsiveProp);
2512
+ const resolvedOpenFrom = React.useMemo(() => {
2513
+ var _a;
2514
+ if (typeof openFrom === 'string')
2515
+ return openFrom;
2516
+ return (_a = responsivePropValue(openFrom)) !== null && _a !== void 0 ? _a : 'end';
2517
+ }, [responsivePropValue, openFrom]);
2518
+ const resolvedWidth = React.useMemo(() => {
2519
+ if (!width)
2520
+ return undefined;
2521
+ if (typeof width === 'string')
2522
+ return width;
2523
+ return responsivePropValue(width);
2524
+ }, [responsivePropValue, width]);
2525
+ const resolvedHeight = React.useMemo(() => {
2526
+ if (!height)
2527
+ return undefined;
2528
+ if (typeof height === 'string')
2529
+ return height;
2530
+ return responsivePropValue(height);
2531
+ }, [responsivePropValue, height]);
2320
2532
  return (jsxRuntime.jsx(framerMotion.motion.div, Object.assign({ className: classNames('offcanvas portal show', {
2321
- [`offcanvas-${openFrom}`]: openFrom,
2322
- }, className), style: Object.assign(Object.assign({}, style), { transition: 'none' }), id: name, tabIndex: -1, "aria-labelledby": `${name}Label`, "aria-hidden": "false", custom: openFrom, variants: variants, initial: "hidden", animate: "visible", exit: "hidden", transition: Object.assign(Object.assign({}, (transition !== null && transition !== void 0 ? transition : defaultTransition)), { delay: 0.15 }) }, staticBackdrop && ({
2533
+ [`offcanvas-${resolvedOpenFrom}`]: resolvedOpenFrom,
2534
+ }, className), style: Object.assign(Object.assign(Object.assign(Object.assign({}, style), { transition: 'none' }), (resolvedWidth && { [`--${PREFIX_BS}offcanvas-width`]: resolvedWidth })), (resolvedHeight && { [`--${PREFIX_BS}offcanvas-height`]: resolvedHeight })), id: name, tabIndex: -1, "aria-labelledby": `${name}Label`, "aria-hidden": "false", custom: resolvedOpenFrom, variants: variants, initial: "hidden", animate: "visible", exit: "hidden", transition: Object.assign(Object.assign({}, (transition !== null && transition !== void 0 ? transition : defaultTransition)), { delay: 0.15 }) }, staticBackdrop && ({
2323
2535
  [`data-${PREFIX_BS}backdrop`]: 'static',
2324
2536
  [`data-${PREFIX_BS}keyboard`]: 'false',
2325
2537
  }), scrollable && ({
@@ -2375,7 +2587,7 @@ function DPopover({ children, renderComponent, open, setOpen, adjustContentToRen
2375
2587
  }
2376
2588
  const reference = React.cloneElement(triggerElement, Object.assign({ ref: refs.setReference }, getReferenceProps(triggerElement.props)));
2377
2589
  return (jsxRuntime.jsxs("div", Object.assign({ className: classNames('d-popover', className), style: generateStyleVariables }, dataAttributes, { children: [reference, isOpen && (jsxRuntime.jsx(react.FloatingFocusManager, { context: context, modal: false, children: jsxRuntime.jsx("div", Object.assign({ className: classNames('d-popover-content', {
2378
- 'w-100': adjustContentToRender,
2590
+ 'd-popover-content-fill': adjustContentToRender,
2379
2591
  }), ref: refs.setFloating, style: floatingStyles, "aria-labelledby": headingId }, getFloatingProps(), { children: children })) }))] })));
2380
2592
  }
2381
2593
 
@@ -2439,13 +2651,13 @@ function DStepper$1({ options, currentStep, className, style, }) {
2439
2651
  }
2440
2652
 
2441
2653
  function DStepper({ options, currentStep, iconSuccess, iconSuccessFamilyClass, iconSuccessFamilyPrefix, iconSuccessMaterialStyle = false, vertical = false, breakpoint = 'lg', className, completed = false, style, dataAttributes, }) {
2442
- return (jsxRuntime.jsxs("div", Object.assign({ className: className, style: style }, dataAttributes, { children: [jsxRuntime.jsx("div", { className: `d-block d-${breakpoint}-none`, children: jsxRuntime.jsx(DStepper$1, { options: options, currentStep: currentStep }) }), jsxRuntime.jsx("div", { className: `d-none d-${breakpoint}-block`, children: jsxRuntime.jsx(DStepper$2, { options: options, currentStep: currentStep, vertical: vertical, iconSuccess: iconSuccess, iconSuccessFamilyClass: iconSuccessFamilyClass, iconSuccessFamilyPrefix: iconSuccessFamilyPrefix, iconSuccessMaterialStyle: iconSuccessMaterialStyle, completed: completed }) })] })));
2654
+ return (jsxRuntime.jsxs("div", Object.assign({ className: className, style: style }, dataAttributes, { children: [jsxRuntime.jsx("div", { className: classNames('d-stepper-mobile', `d-block d-${breakpoint}-none`), children: jsxRuntime.jsx(DStepper$1, { options: options, currentStep: currentStep }) }), jsxRuntime.jsx("div", { className: classNames('d-stepper-desktop', `d-none d-${breakpoint}-block`), children: jsxRuntime.jsx(DStepper$2, { options: options, currentStep: currentStep, vertical: vertical, iconSuccess: iconSuccess, iconSuccessFamilyClass: iconSuccessFamilyClass, iconSuccessFamilyPrefix: iconSuccessFamilyPrefix, iconSuccessMaterialStyle: iconSuccessMaterialStyle, completed: completed }) })] })));
2443
2655
  }
2444
2656
 
2445
2657
  const ARROW_WIDTH = 8;
2446
2658
  const ARROW_HEIGHT = 4;
2447
- const GAP = 2;
2448
- function DTooltip({ className, childrenClassName, style, offSet = ARROW_HEIGHT + GAP, padding, withFocus = false, withClick = false, withHover = true, open = false, placement = 'top', size, Component, children, }) {
2659
+ const GAP$1 = 2;
2660
+ function DTooltip({ className, childrenClassName, style, offSet = ARROW_HEIGHT + GAP$1, padding, withFocus = false, withClick = false, withHover = true, open = false, placement = 'top', size, Component, children, }) {
2449
2661
  const [isOpen, setIsOpen] = React.useState(open);
2450
2662
  const arrowRef = React.useRef(null);
2451
2663
  const { refs, context, floatingStyles, } = react.useFloating({
@@ -2514,8 +2726,16 @@ function DTabs$1({ children, defaultSelected, onChange, options, className, clas
2514
2726
  React.useEffect(() => {
2515
2727
  setSelected(defaultSelected);
2516
2728
  }, [defaultSelected]);
2517
- const generateClasses = React.useMemo(() => (Object.assign({ nav: true, 'flex-column align-items-center': vertical && variant !== 'tabs', [`nav-${variant}`]: true }, className && { [className]: true })), [vertical, variant, className]);
2729
+ const generateClasses = React.useMemo(() => (Object.assign({ nav: true, 'd-tabs-nav-vertical': vertical && variant !== 'tabs', [`nav-${variant}`]: true }, className && { [className]: true })), [vertical, variant, className]);
2518
2730
  const tabRefs = React.useRef([]);
2731
+ // Always holds the latest `options` without needing to be a dependency:
2732
+ // `options` is commonly passed as an inline array literal (e.g.
2733
+ // `options={[{ label: 'SMS', tab: 'sms' }, ...]}`), so it's a new array
2734
+ // reference on every parent render even when its content hasn't changed.
2735
+ // Reading it from this ref (updated synchronously on every render) lets
2736
+ // the focus effect below react only to `selected` changing.
2737
+ const optionsRef = React.useRef(options);
2738
+ optionsRef.current = options;
2519
2739
  React.useEffect(() => {
2520
2740
  tabRefs.current = options.map((_, i) => tabRefs.current[i] || React.createRef());
2521
2741
  }, [options]);
@@ -2530,20 +2750,28 @@ function DTabs$1({ children, defaultSelected, onChange, options, className, clas
2530
2750
  setSelected(firstEnabled.tab);
2531
2751
  }
2532
2752
  }, [options, selected]);
2533
- // Declarative focus management
2534
- const focusTab = (idx) => {
2753
+ // Declarative focus management. Wrapped in `useCallback` with an empty
2754
+ // dependency array since it only reads from the `tabRefs` ref, so its
2755
+ // identity stays stable across renders and it can safely be used as an
2756
+ // effect dependency below.
2757
+ const focusTab = React.useCallback((idx) => {
2535
2758
  var _a;
2536
2759
  if ((_a = tabRefs.current[idx]) === null || _a === void 0 ? void 0 : _a.current) {
2537
2760
  tabRefs.current[idx].current.focus();
2538
2761
  }
2539
- };
2540
- // Focus selected tab when selected changes
2762
+ }, []);
2763
+ // Focus selected tab when selected changes.
2764
+ // Reads `options` from `optionsRef` (see comment above) instead of
2765
+ // depending on `options` directly, so a parent re-render that merely
2766
+ // creates a new `options` reference with identical content doesn't call
2767
+ // `focusTab` again and steal focus away from unrelated elements on the
2768
+ // page (e.g. an OTP input).
2541
2769
  React.useEffect(() => {
2542
- const idx = options.findIndex((opt) => opt.tab === selected && !opt.disabled);
2770
+ const idx = optionsRef.current.findIndex((opt) => opt.tab === selected && !opt.disabled);
2543
2771
  if (idx !== -1) {
2544
2772
  focusTab(idx);
2545
2773
  }
2546
- }, [selected, options]);
2774
+ }, [selected, focusTab]);
2547
2775
  const handleKeyDown = React.useCallback((idx, e) => {
2548
2776
  const count = options.length;
2549
2777
  if (count === 0)
@@ -2572,7 +2800,7 @@ function DTabs$1({ children, defaultSelected, onChange, options, className, clas
2572
2800
  }
2573
2801
  }
2574
2802
  }
2575
- }, [options, vertical]);
2803
+ }, [options, vertical, focusTab]);
2576
2804
  let tablistProps = {};
2577
2805
  if (ariaLabelledBy) {
2578
2806
  tablistProps = { 'aria-labelledby': ariaLabelledBy };
@@ -2585,12 +2813,12 @@ function DTabs$1({ children, defaultSelected, onChange, options, className, clas
2585
2813
  isSelected,
2586
2814
  }), [isSelected]);
2587
2815
  return (jsxRuntime.jsx(TabContext.Provider, { value: value, children: jsxRuntime.jsxs("div", Object.assign({ className: classNames({
2588
- 'd-flex w-100': true,
2589
- 'flex-column': !vertical || variant === 'tabs',
2816
+ 'd-tabs': true,
2817
+ 'd-tabs-column': !vertical || variant === 'tabs',
2590
2818
  }), 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) => {
2591
2819
  const isTabSelected = !!option.tab && option.tab === selected;
2592
2820
  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));
2593
- }) })), jsxRuntime.jsx("div", { className: "tab-content w-100", children: children })] })) }));
2821
+ }) })), jsxRuntime.jsx("div", { className: "d-tabs-content tab-content", children: children })] })) }));
2594
2822
  }
2595
2823
  var DTabs = Object.assign(DTabs$1, {
2596
2824
  Tab: DTabContent,
@@ -2652,11 +2880,11 @@ function useDToast() {
2652
2880
  if (!description) {
2653
2881
  return (jsxRuntime.jsx(DToast, { className: classNames({
2654
2882
  [`toast-${color}`]: !!color,
2655
- }, '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 align-self-center", "aria-label": "Close", onClick: () => reactHotToast.toast.dismiss(id), children: jsxRuntime.jsx(DIcon, { icon: closeIcon || xLg }) })] }) }));
2883
+ }, '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 }) })] }) }));
2656
2884
  }
2657
2885
  return (jsxRuntime.jsxs(DToast, { className: classNames({
2658
2886
  [`toast-${color}`]: !!color,
2659
- }, '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 align-self-center", "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 }) })] }));
2887
+ }, '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 }) })] }));
2660
2888
  }, toastProps);
2661
2889
  }, [xLg]);
2662
2890
  return {
@@ -2678,11 +2906,10 @@ async function configureI8n(resources, _a = {}) {
2678
2906
  .then((t) => t);
2679
2907
  }
2680
2908
 
2681
- function getQueryString(value, config = {
2682
- useSearch: true,
2683
- }) {
2684
- const urlParams = new URLSearchParams(config.useSearch ? window.location.search : '');
2685
- return urlParams.get(value) || config.default;
2909
+ function getQueryString(value, config = {}) {
2910
+ const { useSearch = true, default: defaultValue } = config;
2911
+ const urlParams = new URLSearchParams(useSearch ? window.location.search : '');
2912
+ return urlParams.get(value) || defaultValue;
2686
2913
  }
2687
2914
 
2688
2915
  function changeQueryString(values, { useSearch = true, pushState = false, } = {}) {
@@ -2805,89 +3032,228 @@ function DCreditCard({ brand = 'visa', name, number, holderText = 'Card Holder',
2805
3032
  return (jsxRuntime.jsxs("div", { className: classNames('d-credit-card', isVertical && 'is-vertical', className), children: [jsxRuntime.jsxs("div", { className: "d-credit-card-header", children: [jsxRuntime.jsx("img", { src: logoImage || BRAND_LOGOS[brand] || DEFAULT_IMAGE, alt: brand, className: "d-credit-card-logo", width: 100 }), isChipVisible && (jsxRuntime.jsx("div", { className: "d-credit-card-chip", children: jsxRuntime.jsx("img", { src: CHIP_IMAGE, alt: "chip", width: 30, className: "d-credit-card-chip-image" }) }))] }), jsxRuntime.jsxs("div", { className: "d-credit-card-details", children: [jsxRuntime.jsx("div", { className: "d-credit-card-number", children: number }), jsxRuntime.jsx("small", { className: "d-credit-card-holder-text", children: holderText }), jsxRuntime.jsx("span", { className: "d-credit-card-name", children: name })] })] }));
2806
3033
  }
2807
3034
 
3035
+ // Gap between the toggle and the menu.
3036
+ const GAP = 4;
3037
+ // Minimum distance the menu should keep from the viewport edges.
3038
+ const VIEWPORT_PADDING = 8;
3039
+ // Minimum usable height left for the menu before we consider a side unusable.
3040
+ const MIN_USABLE_SPACE = 120;
2808
3041
  const getItemClass = (action) => classNames({
2809
3042
  'dropdown-item d-flex align-items-center': true,
2810
3043
  [`dropdown-item-${action.color}`]: !!action.color,
2811
3044
  disabled: action.disabled,
2812
3045
  });
2813
- function DDropdown({ actions, dropdownToggle, className, classNameMenu, asPortal = false, placement = 'auto', }) {
3046
+ const clamp = (value, min, max) => {
3047
+ if (max < min)
3048
+ return min;
3049
+ return Math.min(Math.max(value, min), max);
3050
+ };
3051
+ /**
3052
+ * `position: fixed` is normally relative to the viewport, but any ancestor
3053
+ * with a `transform`, `perspective`, `filter`, `will-change` (of those) or a
3054
+ * non-trivial `contain` establishes a new containing block, making `fixed`
3055
+ * behave like `absolute` relative to that ancestor instead (e.g. Storybook's
3056
+ * docs canvas wrapper, animated cards, sticky containers, etc.). We detect
3057
+ * that ancestor so the computed coordinates can be adjusted accordingly,
3058
+ * keeping the menu correctly anchored to the toggle in every context.
3059
+ */
3060
+ const getFixedPositioningOffset = (fromEl) => {
3061
+ var _a;
3062
+ let node = (_a = fromEl === null || fromEl === void 0 ? void 0 : fromEl.parentElement) !== null && _a !== void 0 ? _a : null;
3063
+ while (node && node !== document.body) {
3064
+ const cs = window.getComputedStyle(node);
3065
+ // Treat empty/undefined values as "unset" (jsdom returns "" rather than
3066
+ // the CSS initial value like "none" for some of these properties).
3067
+ const isSet = (value) => !!value && value !== 'none';
3068
+ const establishesContainingBlock = isSet(cs.transform)
3069
+ || isSet(cs.perspective)
3070
+ || isSet(cs.filter)
3071
+ || isSet(cs.backdropFilter)
3072
+ || ['transform', 'perspective', 'filter'].some((prop) => { var _a; return (_a = cs.willChange) === null || _a === void 0 ? void 0 : _a.includes(prop); })
3073
+ || cs.contain.split(' ').some((value) => ['layout', 'paint', 'strict', 'content'].includes(value));
3074
+ if (establishesContainingBlock) {
3075
+ const rect = node.getBoundingClientRect();
3076
+ return { top: rect.top, left: rect.left };
3077
+ }
3078
+ node = node.parentElement;
3079
+ }
3080
+ return { top: 0, left: 0 };
3081
+ };
3082
+ /**
3083
+ * Resolves where the menu should render (flip) and where it should sit along
3084
+ * the cross axis (shift), so it always stays fully inside the viewport
3085
+ * regardless of where the toggle is placed or how the viewport is resized.
3086
+ * This mimics floating-ui's `flip` + `shift` middlewares without adding the
3087
+ * dependency, keeping the existing Bootstrap markup/classes.
3088
+ */
3089
+ const computeCoords = (toggleRect, menuSize, placement, alignment) => {
3090
+ const viewportWidth = window.innerWidth;
3091
+ const viewportHeight = window.innerHeight;
3092
+ const spaceBelow = viewportHeight - toggleRect.bottom;
3093
+ const spaceAbove = toggleRect.top;
3094
+ const spaceLeft = toggleRect.left;
3095
+ const spaceRight = viewportWidth - toggleRect.right;
3096
+ let side = placement === 'auto'
3097
+ ? 'down'
3098
+ : placement;
3099
+ if (placement === 'auto') {
3100
+ side = spaceBelow >= menuSize.height + GAP || spaceBelow >= spaceAbove ? 'down' : 'up';
3101
+ }
3102
+ // Flip within the current axis if the chosen side can't fit the menu but
3103
+ // the opposite side (same axis) can.
3104
+ if (side === 'down' && spaceBelow < menuSize.height + GAP && spaceAbove > spaceBelow) {
3105
+ side = 'up';
3106
+ }
3107
+ else if (side === 'up' && spaceAbove < menuSize.height + GAP && spaceBelow > spaceAbove) {
3108
+ side = 'down';
3109
+ }
3110
+ else if (side === 'start' && spaceLeft < menuSize.width + GAP && spaceRight > spaceLeft) {
3111
+ side = 'end';
3112
+ }
3113
+ else if (side === 'end' && spaceRight < menuSize.width + GAP && spaceLeft > spaceRight) {
3114
+ side = 'start';
3115
+ }
3116
+ // Cross-axis fallback: e.g. a horizontally-centered toggle on a narrow
3117
+ // viewport can't open sideways (neither `start` nor `end` fit), but there
3118
+ // is plenty of room above/below — switch the whole axis in that case
3119
+ // (and vice versa for `down`/`up` squeezed against a short viewport).
3120
+ const bestVerticalSpace = Math.max(spaceBelow, spaceAbove);
3121
+ const bestHorizontalSpace = Math.max(spaceLeft, spaceRight);
3122
+ if (side === 'down' || side === 'up') {
3123
+ if (bestVerticalSpace < MIN_USABLE_SPACE && bestHorizontalSpace > bestVerticalSpace) {
3124
+ side = spaceRight >= spaceLeft ? 'end' : 'start';
3125
+ }
3126
+ }
3127
+ else if (bestHorizontalSpace < menuSize.width + GAP
3128
+ && bestVerticalSpace > bestHorizontalSpace) {
3129
+ side = spaceBelow >= spaceAbove ? 'down' : 'up';
3130
+ }
3131
+ if (side === 'down' || side === 'up') {
3132
+ const top = side === 'down'
3133
+ ? toggleRect.bottom + GAP
3134
+ : toggleRect.top - menuSize.height - GAP;
3135
+ // Align horizontally per the requested `alignment` (start/end/center), then
3136
+ // fall back to the opposite edge if the preferred alignment would overflow
3137
+ // the viewport, and clamp as a final safety net so it never overflows.
3138
+ let left;
3139
+ if (alignment === 'end') {
3140
+ left = toggleRect.right - menuSize.width;
3141
+ }
3142
+ else if (alignment === 'center') {
3143
+ left = toggleRect.left + (toggleRect.width / 2) - (menuSize.width / 2);
3144
+ }
3145
+ else {
3146
+ left = toggleRect.left;
3147
+ }
3148
+ if (left + menuSize.width > viewportWidth - VIEWPORT_PADDING && alignment !== 'end') {
3149
+ const altLeft = toggleRect.right - menuSize.width;
3150
+ if (altLeft >= VIEWPORT_PADDING)
3151
+ left = altLeft;
3152
+ }
3153
+ else if (left < VIEWPORT_PADDING && alignment !== 'start') {
3154
+ const altLeft = toggleRect.left;
3155
+ if (altLeft + menuSize.width <= viewportWidth - VIEWPORT_PADDING)
3156
+ left = altLeft;
3157
+ }
3158
+ left = clamp(left, VIEWPORT_PADDING, Math.max(viewportWidth - menuSize.width - VIEWPORT_PADDING, VIEWPORT_PADDING));
3159
+ const availableHeight = side === 'down' ? spaceBelow : spaceAbove;
3160
+ // Cap strictly to the space actually available on the resolved side —
3161
+ // MIN_USABLE_SPACE is only meant to influence the flip/shift decision
3162
+ // above, not to force a taller box than what physically fits and
3163
+ // overflow the viewport.
3164
+ const maxUsableHeight = Math.max(availableHeight - GAP - VIEWPORT_PADDING, 0);
3165
+ return {
3166
+ top,
3167
+ left,
3168
+ minWidth: Math.max(toggleRect.width, 160),
3169
+ maxHeight: `${maxUsableHeight}px`,
3170
+ resolvedSide: side,
3171
+ };
3172
+ }
3173
+ // side is 'start' or 'end'
3174
+ let left = side === 'start'
3175
+ ? toggleRect.left - menuSize.width - GAP
3176
+ : toggleRect.right + GAP;
3177
+ // Safety-net clamp: in rare cases where the toggle is squeezed so tightly
3178
+ // that no side of any axis has enough room, keep the menu inside the
3179
+ // viewport rather than letting it render off-screen.
3180
+ left = clamp(left, VIEWPORT_PADDING, Math.max(viewportWidth - menuSize.width - VIEWPORT_PADDING, VIEWPORT_PADDING));
3181
+ // Shift vertically: align with the toggle's top edge by default, but switch
3182
+ // to the bottom edge (or clamp) if that would overflow the viewport.
3183
+ let { top } = toggleRect;
3184
+ if (top + menuSize.height > viewportHeight - VIEWPORT_PADDING) {
3185
+ top = toggleRect.bottom - menuSize.height;
3186
+ }
3187
+ top = clamp(top, VIEWPORT_PADDING, Math.max(viewportHeight - menuSize.height - VIEWPORT_PADDING, VIEWPORT_PADDING));
3188
+ return {
3189
+ top,
3190
+ left,
3191
+ minWidth: Math.max(toggleRect.width, 160),
3192
+ maxHeight: `${Math.max(viewportHeight - top - VIEWPORT_PADDING, 0)}px`,
3193
+ resolvedSide: side,
3194
+ };
3195
+ };
3196
+ function DDropdown({ actions, dropdownToggle, className, classNameMenu, asPortal = false, placement = 'auto', alignment = 'start', }) {
2814
3197
  const [open, setOpen] = React.useState(false);
2815
- const [position, setPosition] = React.useState('down');
2816
- const [menuCoords, setMenuCoords] = React.useState(null);
2817
- // Used in default (non-portal) mode: wraps the entire dropdown
2818
- const dropdownRef = React.useRef(null);
2819
- // Used in portal mode: ref on the toggle wrapper to read its viewport coords
3198
+ const [coords, setCoords] = React.useState(null);
3199
+ // Initialize from `placement` (falling back to 'down' for 'auto') so the
3200
+ // wrapper's `drop-*` class reflects the requested side even before the
3201
+ // first open/measurement, matching the previous `drop-${placement}` class
3202
+ // behavior (relevant for Bootstrap caret/orientation styling).
3203
+ const [resolvedSide, setResolvedSide] = React.useState(placement === 'auto' ? 'down' : placement);
3204
+ // Wraps the toggle in both modes — used to read its viewport-relative rect.
2820
3205
  const toggleRef = React.useRef(null);
2821
- // Ref on the rendered <ul> — used for outside-click detection in portal mode
3206
+ // Ref on the rendered <ul> — used to measure its size and for outside-click detection.
2822
3207
  const menuRef = React.useRef(null);
2823
3208
  const toggle = () => {
2824
- setMenuCoords(null);
3209
+ setCoords(null);
2825
3210
  setOpen((prev) => !prev);
2826
3211
  };
2827
- const resolvedInlinePosition = placement === 'auto' ? position : placement;
2828
- // Compute fixed coords from the toggle's bounding rect (portal mode only)
2829
- const computePortalCoords = React.useCallback(() => {
2830
- if (!toggleRef.current)
3212
+ // Measures the toggle + menu and (re)computes fixed, pixel-perfect coords
3213
+ // with collision detection, so the menu never overflows the viewport.
3214
+ const updatePosition = React.useCallback(() => {
3215
+ if (!toggleRef.current || !menuRef.current)
3216
+ return;
3217
+ const toggleRect = toggleRef.current.getBoundingClientRect();
3218
+ // If the toggle itself has scrolled completely out of the viewport
3219
+ // (regardless of `placement`), close the menu instead of leaving it
3220
+ // rendered detached from the toggle that opened it.
3221
+ const toggleIsMeasured = toggleRect.width > 0 || toggleRect.height > 0;
3222
+ const toggleIsOutOfView = toggleRect.bottom <= 0
3223
+ || toggleRect.top >= window.innerHeight
3224
+ || toggleRect.right <= 0
3225
+ || toggleRect.left >= window.innerWidth;
3226
+ if (toggleIsMeasured && toggleIsOutOfView) {
3227
+ setOpen(false);
2831
3228
  return;
2832
- const rect = toggleRef.current.getBoundingClientRect();
2833
- const spaceRight = window.innerWidth - rect.right;
2834
- const spaceBottom = window.innerHeight - rect.bottom;
2835
- const spaceTop = rect.top;
2836
- let resolvedPlacement;
2837
- if (placement === 'auto') {
2838
- if (spaceBottom < 150) {
2839
- resolvedPlacement = 'up';
2840
- }
2841
- else if (spaceRight < 150) {
2842
- resolvedPlacement = 'start';
2843
- }
2844
- else {
2845
- resolvedPlacement = 'down';
2846
- }
2847
- }
2848
- else {
2849
- resolvedPlacement = placement;
2850
- }
2851
- const openStart = resolvedPlacement === 'start';
2852
- const openEnd = resolvedPlacement === 'end';
2853
- const openUp = resolvedPlacement === 'up';
2854
- let availableHeight = spaceBottom;
2855
- if (openUp) {
2856
- availableHeight = spaceTop;
2857
- }
2858
- else if (openStart || openEnd) {
2859
- availableHeight = window.innerHeight - rect.top;
2860
- }
2861
- const verticalCoords = openUp
2862
- ? { bottom: window.innerHeight - rect.top + 4 }
2863
- : { top: openStart || openEnd ? rect.top : rect.bottom + 4 };
2864
- let horizontalCoords;
2865
- if (openStart) {
2866
- horizontalCoords = { right: window.innerWidth - rect.left + 4 };
2867
- }
2868
- else if (openEnd) {
2869
- horizontalCoords = { left: rect.right + 4 };
2870
- }
2871
- else {
2872
- horizontalCoords = { left: rect.left };
2873
3229
  }
2874
- setMenuCoords(Object.assign(Object.assign(Object.assign({}, verticalCoords), horizontalCoords), { minWidth: Math.max(rect.width, 160), maxHeight: `${Math.max(availableHeight - 12, 120)}px` }));
2875
- }, [placement]);
2876
- // Outside-click: default mode
2877
- React.useEffect(() => {
2878
- if (asPortal)
2879
- return () => { };
2880
- const handler = (event) => {
2881
- if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
2882
- setOpen(false);
2883
- }
2884
- };
2885
- document.addEventListener('mousedown', handler);
2886
- return () => document.removeEventListener('mousedown', handler);
2887
- }, [asPortal]);
2888
- // Outside-click: portal mode (must check both toggle and floating menu)
2889
- React.useEffect(() => {
2890
- if (!asPortal || !open)
3230
+ const menuRect = menuRef.current.getBoundingClientRect();
3231
+ const _a = computeCoords(toggleRect, { width: menuRect.width, height: menuRect.height }, placement, alignment), { resolvedSide: side } = _a, menuCoords = tslib.__rest(_a, ["resolvedSide"]);
3232
+ // In portal mode the menu is appended to document.body, whose containing
3233
+ // block for `fixed` is always the viewport (no adjustment needed). In
3234
+ // inline mode the menu stays next to the toggle, so we must look for a
3235
+ // containing-block-establishing ancestor starting from the toggle.
3236
+ const offset = asPortal
3237
+ ? { top: 0, left: 0 }
3238
+ : getFixedPositioningOffset(toggleRef.current);
3239
+ setResolvedSide(side);
3240
+ setCoords(Object.assign(Object.assign({}, menuCoords), { top: menuCoords.top - offset.top, left: menuCoords.left - offset.left }));
3241
+ }, [placement, alignment, asPortal]);
3242
+ // Throttles scroll/resize-triggered updates to at most once per frame,
3243
+ // avoiding repeated forced reflows on busy/high-frequency scroll events.
3244
+ const rafIdRef = React.useRef(null);
3245
+ const scheduleUpdatePosition = React.useCallback(() => {
3246
+ if (rafIdRef.current !== null)
3247
+ return;
3248
+ rafIdRef.current = window.requestAnimationFrame(() => {
3249
+ rafIdRef.current = null;
3250
+ updatePosition();
3251
+ });
3252
+ }, [updatePosition]);
3253
+ // Outside-click detection (checks both the toggle and the floating menu,
3254
+ // since in portal mode the menu isn't a DOM descendant of the toggle).
3255
+ React.useLayoutEffect(() => {
3256
+ if (!open)
2891
3257
  return () => { };
2892
3258
  const handler = (event) => {
2893
3259
  var _a, _b;
@@ -2898,35 +3264,25 @@ function DDropdown({ actions, dropdownToggle, className, classNameMenu, asPortal
2898
3264
  };
2899
3265
  document.addEventListener('mousedown', handler);
2900
3266
  return () => document.removeEventListener('mousedown', handler);
2901
- }, [asPortal, open]);
2902
- // Auto-position: default mode
2903
- React.useEffect(() => {
2904
- if (asPortal || !open || !dropdownRef.current || placement !== 'auto')
2905
- return;
2906
- const rect = dropdownRef.current.getBoundingClientRect();
2907
- const spaceBottom = window.innerHeight - rect.bottom;
2908
- const spaceRight = window.innerWidth - rect.right;
2909
- if (spaceBottom < 150)
2910
- setPosition('up');
2911
- else if (spaceRight < 150)
2912
- setPosition('start');
2913
- else
2914
- setPosition('down');
2915
- }, [asPortal, open, placement]);
2916
- // Compute + track coords: portal mode
2917
- // Re-runs whenever the menu opens and keeps coords fresh on scroll/resize
2918
- React.useEffect(() => {
2919
- if (!asPortal || !open)
3267
+ }, [open]);
3268
+ // Computes the initial position synchronously (before paint, to avoid a
3269
+ // flicker) and keeps it fresh while the menu is open and the viewport
3270
+ // scrolls or resizes (throttled to one update per frame, see above).
3271
+ React.useLayoutEffect(() => {
3272
+ if (!open)
2920
3273
  return () => { };
2921
- computePortalCoords();
2922
- const update = () => computePortalCoords();
2923
- window.addEventListener('scroll', update, { passive: true, capture: true });
2924
- window.addEventListener('resize', update, { passive: true });
3274
+ updatePosition();
3275
+ window.addEventListener('scroll', scheduleUpdatePosition, { passive: true, capture: true });
3276
+ window.addEventListener('resize', scheduleUpdatePosition, { passive: true });
2925
3277
  return () => {
2926
- window.removeEventListener('scroll', update, { capture: true });
2927
- window.removeEventListener('resize', update);
3278
+ window.removeEventListener('scroll', scheduleUpdatePosition, { capture: true });
3279
+ window.removeEventListener('resize', scheduleUpdatePosition);
3280
+ if (rafIdRef.current !== null) {
3281
+ window.cancelAnimationFrame(rafIdRef.current);
3282
+ rafIdRef.current = null;
3283
+ }
2928
3284
  };
2929
- }, [asPortal, open, computePortalCoords]);
3285
+ }, [open, updatePosition, scheduleUpdatePosition]);
2930
3286
  let ToggleElement;
2931
3287
  if (dropdownToggle) {
2932
3288
  ToggleElement = typeof dropdownToggle === 'function'
@@ -2936,57 +3292,30 @@ function DDropdown({ actions, dropdownToggle, className, classNameMenu, asPortal
2936
3292
  else {
2937
3293
  ToggleElement = (jsxRuntime.jsx(DButtonIcon, { variant: "link", stopPropagationEnabled: false, "aria-label": "Toggle Dropdown", "aria-haspopup": "menu", "aria-expanded": open, onClick: toggle, icon: "MoreVertical" }));
2938
3294
  }
2939
- const portalMenuStyle = asPortal && menuCoords
3295
+ // Until the first measurement is available, keep the menu off-screen but
3296
+ // measurable (visibility: hidden preserves layout, unlike display: none).
3297
+ const menuStyle = coords
2940
3298
  ? {
2941
3299
  position: 'fixed',
2942
- top: menuCoords.top,
2943
- bottom: menuCoords.bottom,
2944
- left: menuCoords.left,
2945
- right: menuCoords.right,
2946
- minWidth: menuCoords.minWidth,
2947
- maxHeight: menuCoords.maxHeight,
3300
+ top: coords.top,
3301
+ left: coords.left,
3302
+ minWidth: coords.minWidth,
3303
+ maxHeight: coords.maxHeight,
2948
3304
  overflowY: 'auto',
2949
3305
  zIndex: 1050,
2950
3306
  transform: 'none',
3307
+ visibility: 'visible',
2951
3308
  }
2952
- : undefined;
2953
- let inlineTop = '100%';
2954
- if (resolvedInlinePosition === 'up') {
2955
- inlineTop = 'auto';
2956
- }
2957
- else if (resolvedInlinePosition === 'start' || resolvedInlinePosition === 'end') {
2958
- inlineTop = 0;
2959
- }
2960
- let inlineLeft = 0;
2961
- if (resolvedInlinePosition === 'start') {
2962
- inlineLeft = 'auto';
2963
- }
2964
- else if (resolvedInlinePosition === 'end') {
2965
- inlineLeft = '100%';
2966
- }
2967
- let inlineRight = 'auto';
2968
- if (resolvedInlinePosition === 'start') {
2969
- inlineRight = '100%';
2970
- }
2971
- let inlineTransform = 'translateY(4px)';
2972
- if (resolvedInlinePosition === 'up') {
2973
- inlineTransform = 'translateY(-4px)';
2974
- }
2975
- else if (resolvedInlinePosition === 'start') {
2976
- inlineTransform = 'translateX(-4px)';
2977
- }
2978
- else if (resolvedInlinePosition === 'end') {
2979
- inlineTransform = 'translateX(4px)';
2980
- }
2981
- const inlineMenuStyle = {
2982
- position: 'absolute',
2983
- top: inlineTop,
2984
- bottom: resolvedInlinePosition === 'up' ? '100%' : 'auto',
2985
- left: inlineLeft,
2986
- right: inlineRight,
2987
- transform: inlineTransform,
2988
- };
2989
- const menuItems = (jsxRuntime.jsx("ul", { ref: menuRef, role: "menu", className: classNames('dropdown-menu p-2', { show: open }, classNameMenu), style: portalMenuStyle !== null && portalMenuStyle !== void 0 ? portalMenuStyle : inlineMenuStyle, children: actions.map((action, index) => {
3309
+ : {
3310
+ position: 'fixed',
3311
+ top: 0,
3312
+ left: 0,
3313
+ visibility: 'hidden',
3314
+ pointerEvents: 'none',
3315
+ zIndex: 1050,
3316
+ transform: 'none',
3317
+ };
3318
+ const menuItems = (jsxRuntime.jsx("ul", { ref: menuRef, role: "menu", className: classNames('dropdown-menu p-2', { show: open }, classNameMenu), style: menuStyle, children: actions.map((action, index) => {
2990
3319
  if (action.isDivider) {
2991
3320
  return jsxRuntime.jsx("hr", { className: "dropdown-divider" }, index);
2992
3321
  }
@@ -3005,10 +3334,7 @@ function DDropdown({ actions, dropdownToggle, className, classNameMenu, asPortal
3005
3334
  }
3006
3335
  }, disabled: action.disabled, children: [action.icon && jsxRuntime.jsx(DIcon, { icon: action.icon, className: "me-2", size: "1rem" }), action.label] })) }, index));
3007
3336
  }) }));
3008
- if (asPortal) {
3009
- return (jsxRuntime.jsxs("div", { ref: toggleRef, className: classNames('dropdown', className), children: [ToggleElement, open && menuCoords && reactDom.createPortal(menuItems, document.body)] }));
3010
- }
3011
- return (jsxRuntime.jsxs("div", { className: classNames(`dropdown drop-${resolvedInlinePosition}`, className), ref: dropdownRef, children: [ToggleElement, menuItems] }));
3337
+ return (jsxRuntime.jsxs("div", { ref: toggleRef, className: classNames(`dropdown drop-${resolvedSide} d-inline-block`, className), children: [ToggleElement, open && (asPortal ? reactDom.createPortal(menuItems, document.body) : menuItems)] }));
3012
3338
  }
3013
3339
 
3014
3340
  function useScreenshot() {
@@ -3038,13 +3364,18 @@ function useScreenshot() {
3038
3364
 
3039
3365
  function useScreenshotDownload() {
3040
3366
  const { clipRef, takeBlob } = useScreenshot();
3041
- const download = React.useCallback(async () => {
3042
- const blob = await takeBlob();
3367
+ const download = React.useCallback(async (fileName = 'voucher') => {
3368
+ const blob = await takeBlob('image/jpeg');
3043
3369
  const url = window.URL.createObjectURL(blob);
3044
3370
  const link = window.document.createElement('a');
3045
3371
  link.style.display = 'none';
3046
3372
  link.href = url;
3047
- link.download = 'voucher.jpg';
3373
+ // Fall back to the default base name when the caller passes a blank/whitespace value,
3374
+ // so the file is never named just ".jpg".
3375
+ const safeFileName = fileName.trim() || 'voucher';
3376
+ // The extension is always appended by the hook to guarantee a valid image file,
3377
+ // regardless of what fileName the caller passes (e.g. "receipt.png" -> "receipt.png.jpg").
3378
+ link.download = `${safeFileName}.jpg`;
3048
3379
  document.body.appendChild(link);
3049
3380
  link.click();
3050
3381
  document.body.removeChild(link);
@@ -3058,11 +3389,15 @@ function useScreenshotDownload() {
3058
3389
 
3059
3390
  function useScreenshotWebShare() {
3060
3391
  const { takeBlob, clipRef } = useScreenshot();
3061
- const share = React.useCallback(async () => {
3062
- const blob = await takeBlob();
3063
- const image = new File([blob], 'voucher.jpeg', { type: 'image/jpeg' });
3064
- if (!navigator.canShare
3065
- && (navigator.canShare && !navigator.canShare({ files: [image] }))) {
3392
+ const share = React.useCallback(async (fileName = 'voucher') => {
3393
+ const blob = await takeBlob('image/jpeg');
3394
+ // Fall back to the default base name when the caller passes a blank/whitespace value,
3395
+ // so the file is never named just ".jpeg".
3396
+ const safeFileName = fileName.trim() || 'voucher';
3397
+ // The extension is always appended by the hook to guarantee a valid image file,
3398
+ // regardless of what fileName the caller passes (e.g. "receipt.png" -> "receipt.png.jpeg").
3399
+ const image = new File([blob], `${safeFileName}.jpeg`, { type: 'image/jpeg' });
3400
+ if (!navigator.canShare || !navigator.canShare({ files: [image] })) {
3066
3401
  window.print();
3067
3402
  return;
3068
3403
  }
@@ -3074,11 +3409,11 @@ function useScreenshotWebShare() {
3074
3409
  };
3075
3410
  }
3076
3411
 
3077
- function DVoucher({ amount, amountDetails, icon, title, onError, message, downloadText = 'Download', shareText = 'Share', className, children, }) {
3412
+ function DVoucher({ amount, amountDetails, icon, title, onError, message, downloadText = 'Download', shareText = 'Share', fileName = 'voucher', hideActions = false, className, children, }) {
3078
3413
  const { shareRef, share } = useScreenshotWebShare();
3079
3414
  const { downloadRef, download } = useScreenshotDownload();
3080
3415
  const handleShare = () => {
3081
- share()
3416
+ share(fileName)
3082
3417
  .catch(async (err) => {
3083
3418
  if (onError) {
3084
3419
  await onError(err);
@@ -3089,7 +3424,7 @@ function DVoucher({ amount, amountDetails, icon, title, onError, message, downlo
3089
3424
  });
3090
3425
  };
3091
3426
  const handleDownload = () => {
3092
- download()
3427
+ download(fileName)
3093
3428
  .catch(async (err) => {
3094
3429
  if (onError) {
3095
3430
  await onError(err);
@@ -3117,80 +3452,36 @@ function DVoucher({ amount, amountDetails, icon, title, onError, message, downlo
3117
3452
  return (jsxRuntime.jsx("div", { className: classNames('d-voucher', className), ref: (el) => {
3118
3453
  shareRef.current = el;
3119
3454
  downloadRef.current = el;
3120
- }, children: jsxRuntime.jsxs("div", { children: [jsxRuntime.jsxs("div", { className: "d-voucher-header", children: [resolvedIconProps && (jsxRuntime.jsx(DIcon, Object.assign({}, resolvedIconProps))), jsxRuntime.jsxs("div", { className: "text-center", children: [jsxRuntime.jsx("h3", { className: "mb-2", children: title }), jsxRuntime.jsx("p", { className: "m-0", children: message })] })] }), amount && (jsxRuntime.jsxs("div", { className: "d-voucher-amount", children: [jsxRuntime.jsx("div", { className: classNames('text-center fw-bold fs-3', amountDetails ? 'mb-1' : 'm-0'), children: amount }), amountDetails] })), jsxRuntime.jsx("hr", { className: "my-4" }), children, jsxRuntime.jsx("hr", { className: "my-4" }), jsxRuntime.jsxs("div", { className: "d-voucher-footer", children: [jsxRuntime.jsx(DButton, { onClick: handleShare, iconStart: "Share2", text: shareText, variant: "outline", size: "sm" }), jsxRuntime.jsx(DButton, { onClick: handleDownload, iconStart: "Download", text: downloadText, variant: "outline", size: "sm" })] })] }) }));
3121
- }
3122
-
3123
- function useCountdown(seconds) {
3124
- const [secondsLeft, setSecondsLeft] = React.useState(seconds);
3125
- const [isActive, setIsActive] = React.useState(true);
3126
- const resetCountdown = React.useCallback((newSeconds = seconds) => {
3127
- setIsActive(false);
3128
- setSecondsLeft(newSeconds);
3129
- }, [seconds]);
3130
- const restartCountdown = React.useCallback(() => {
3131
- resetCountdown(seconds);
3132
- setIsActive(true);
3133
- }, [resetCountdown, seconds]);
3134
- React.useEffect(() => {
3135
- if (!isActive) {
3136
- return () => { };
3137
- }
3138
- const interval = setInterval(() => {
3139
- setSecondsLeft((prevSeconds) => {
3140
- const newSeconds = prevSeconds - 1;
3141
- if (newSeconds <= 0) {
3142
- clearInterval(interval);
3143
- setIsActive(false);
3144
- return 0;
3145
- }
3146
- return newSeconds;
3147
- });
3148
- }, 1000);
3149
- return () => clearInterval(interval);
3150
- }, [isActive]);
3151
- return { secondsLeft, restartCountdown };
3455
+ }, children: jsxRuntime.jsxs("div", { children: [jsxRuntime.jsxs("div", { className: "d-voucher-header", children: [resolvedIconProps && (jsxRuntime.jsx(DIcon, Object.assign({}, resolvedIconProps))), jsxRuntime.jsxs("div", { className: "d-voucher-title-group", children: [jsxRuntime.jsx("h3", { className: "d-voucher-title", children: title }), jsxRuntime.jsx("p", { className: "d-voucher-message", children: message })] })] }), amount && (jsxRuntime.jsxs("div", { className: "d-voucher-amount", children: [jsxRuntime.jsx("div", { className: classNames('d-voucher-amount-value', { 'd-voucher-amount-value-with-details': !!amountDetails }), children: amount }), amountDetails] })), jsxRuntime.jsx("hr", { className: "d-voucher-divider" }), children, !hideActions && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("hr", { className: "my-4" }), jsxRuntime.jsxs("div", { className: "d-voucher-footer", children: [jsxRuntime.jsx(DButton, { onClick: handleShare, iconStart: "Share2", text: shareText, variant: "outline", size: "sm" }), jsxRuntime.jsx(DButton, { onClick: handleDownload, iconStart: "Download", text: downloadText, variant: "outline", size: "sm" })] })] }))] }) }));
3152
3456
  }
3153
3457
 
3154
3458
  const defaultMessage = (secs) => (secs > 0
3155
3459
  ? `Didn't get any code? Resend in: ${secs}s`
3156
3460
  : "Didn't get any code?");
3157
- function OtpCountdown({ seconds, resendText, message, }) {
3158
- const { secondsLeft, restartCountdown } = useCountdown(seconds);
3159
- return (jsxRuntime.jsxs("div", { className: "d-flex gap-2 align-items-center", children: [jsxRuntime.jsx("p", { className: "mb-0 flex-1", children: message ? message(secondsLeft) : defaultMessage(secondsLeft) }), jsxRuntime.jsx(DButton, { text: resendText, variant: "link", className: "text-nowrap", disabled: secondsLeft > 0, onClick: restartCountdown })] }));
3461
+ function OtpCountdown({ secondsLeft, restartCountdown, resendText, message, }) {
3462
+ return (jsxRuntime.jsxs("div", { className: "d-otp-countdown", children: [jsxRuntime.jsx("p", { className: "d-otp-countdown-text", children: message ? message(secondsLeft) : defaultMessage(secondsLeft) }), jsxRuntime.jsx(DButton, { text: resendText, variant: "link", className: "text-nowrap", disabled: secondsLeft > 0, onClick: restartCountdown })] }));
3160
3463
  }
3161
3464
 
3162
3465
  const TEXT_PROPS = {
3163
3466
  resend: 'Resend',
3164
- resendText: 'Resend code',
3467
+ resendText: 'Resend',
3165
3468
  submit: 'Authorize and continue',
3166
3469
  title: 'We will send you a 6-digit code to your associated phone number so you can continue with your request.',
3470
+ invalidCode: 'Invalid code, please try again.',
3167
3471
  contact: (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("span", { children: "Problems with your digital token? Contact us" }), ' ', jsxRuntime.jsx("a", { href: "https://www.modyo.com", className: "link-primary text-nowrap", target: "_blank", rel: "noreferrer", children: "Contact us" })] })),
3168
3472
  };
3169
3473
  function DOtp({ className, action, isLoading, otpSize = 6, texts = TEXT_PROPS, seconds = 15, }) {
3170
- const [otp, setOtp] = React.useState('');
3171
- const [invalid, setInvalid] = React.useState(false);
3172
- const handler = React.useCallback(async () => {
3173
- if (otp.length < otpSize) {
3174
- setInvalid(true);
3175
- return;
3176
- }
3177
- setInvalid(false);
3178
- await action();
3179
- }, [
3180
- otp.length,
3181
- action,
3182
- otpSize,
3183
- ]);
3184
- return (jsxRuntime.jsxs("div", { className: className, children: [jsxRuntime.jsx("p", { children: texts.title }), jsxRuntime.jsxs("div", { className: "d-flex flex-column gap-6 pb-4 px-3", children: [jsxRuntime.jsxs("div", { className: "d-flex flex-column gap-6", children: [jsxRuntime.jsx(DInputPin, { className: "modal-otp-pin", characters: otpSize, onChange: (e) => setOtp(e), invalid: invalid && otp.length < otpSize, placeholder: "0" }), jsxRuntime.jsx(OtpCountdown, { seconds: seconds, resendText: texts.resend })] }), jsxRuntime.jsx("hr", { className: "m-0" }), jsxRuntime.jsxs("div", { className: "d-flex flex-column flex-lg-row gap-4 align-items-center", children: [jsxRuntime.jsx(DButton, { text: texts.submit, onClick: () => {
3185
- handler().catch((err) => {
3474
+ const { setOtp, invalid, submit, isLoading: isSubmitting, secondsLeft, restartCountdown, } = useOtp({ action, otpSize, seconds });
3475
+ return (jsxRuntime.jsxs("div", { className: className, children: [jsxRuntime.jsx("p", { children: texts.title }), jsxRuntime.jsxs("div", { className: "d-otp d-otp-content", children: [jsxRuntime.jsxs("div", { className: "d-otp-fields", children: [jsxRuntime.jsx(DInputPin, { className: "modal-otp-pin", characters: otpSize, onChange: (e) => setOtp(e), invalid: invalid, hint: invalid ? texts.invalidCode : undefined, placeholder: "0" }), jsxRuntime.jsx(OtpCountdown, { secondsLeft: secondsLeft, restartCountdown: restartCountdown, resendText: texts.resendText })] }), jsxRuntime.jsx("hr", { className: "d-otp-divider" }), jsxRuntime.jsxs("div", { className: "d-otp-footer", children: [jsxRuntime.jsx(DButton, { text: texts.submit, onClick: () => {
3476
+ submit().catch((err) => {
3186
3477
  // eslint-disable-next-line no-console
3187
3478
  console.error('Error in DOtp action:', err);
3188
3479
  });
3189
- }, loading: isLoading }), jsxRuntime.jsx("p", { className: "small ms-lg-auto mb-0", children: texts.contact })] })] })] }));
3480
+ }, loading: isLoading || isSubmitting }), jsxRuntime.jsx("p", { className: "d-otp-contact", children: texts.contact })] })] })] }));
3190
3481
  }
3191
3482
 
3192
3483
  function DefaultErrorBoundary({ resetErrorBoundary }) {
3193
- return (jsxRuntime.jsx(DAlert, { color: "danger", showClose: false, children: jsxRuntime.jsxs("div", { className: "d-flex align-items-center gap-2", children: [jsxRuntime.jsx("span", { children: "An unexpected error occurred." }), jsxRuntime.jsx(DButton, { color: "secondary", variant: "outline", size: "sm", onClick: resetErrorBoundary, children: "Retry" })] }) }));
3484
+ 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" })] }) }));
3194
3485
  }
3195
3486
 
3196
3487
  function DErrorBoundary({ name, fallback, resetKeys, onReset, onError, children, }) {
@@ -3208,15 +3499,15 @@ function DErrorBoundary({ name, fallback, resetKeys, onReset, onError, children,
3208
3499
  }
3209
3500
 
3210
3501
  function ErrorState({ message, onRetry, retryMessage = 'Retry', color = 'danger', }) {
3211
- return (jsxRuntime.jsxs(DAlert, { color: color, className: "d-flex align-items-center gap-3", children: [jsxRuntime.jsx("div", { className: "flex-grow-1", children: jsxRuntime.jsx("p", { className: "mb-0", children: message !== null && message !== void 0 ? message : 'An unexpected error occurred.' }) }), onRetry && (jsxRuntime.jsx(DButton, { onClick: onRetry, text: retryMessage, variant: "outline", iconStart: "RefreshCw" }))] }));
3502
+ return (jsxRuntime.jsxs(DAlert, { color: color, className: "d-error-state", children: [jsxRuntime.jsx("div", { className: "d-error-state-content", children: jsxRuntime.jsx("p", { className: "d-error-state-message", children: message !== null && message !== void 0 ? message : 'An unexpected error occurred.' }) }), onRetry && (jsxRuntime.jsx(DButton, { onClick: onRetry, text: retryMessage, variant: "outline", iconStart: "RefreshCw" }))] }));
3212
3503
  }
3213
3504
 
3214
3505
  function EmptyState({ message, icon = 'FileText', actionText, onAction, }) {
3215
- return (jsxRuntime.jsxs("div", { className: "d-flex flex-column align-items-center justify-content-center p-5 text-center", children: [jsxRuntime.jsx(DIcon, { icon: icon, size: "3rem", className: "text-secondary mb-3" }), jsxRuntime.jsx("p", { className: "text-secondary mb-3", children: message !== null && message !== void 0 ? message : 'No data available.' }), actionText && onAction && (jsxRuntime.jsx(DButton, { onClick: onAction, text: actionText, variant: "outline" }))] }));
3506
+ return (jsxRuntime.jsxs("div", { className: "d-empty-state", children: [jsxRuntime.jsx(DIcon, { icon: icon, size: "3rem", className: "d-empty-state-icon" }), jsxRuntime.jsx("p", { className: "d-empty-state-message", children: message !== null && message !== void 0 ? message : 'No data available.' }), actionText && onAction && (jsxRuntime.jsx(DButton, { onClick: onAction, text: actionText, variant: "outline" }))] }));
3216
3507
  }
3217
3508
 
3218
3509
  function LoadingState({ ariaLabel = 'Loading...', className }) {
3219
- return (jsxRuntime.jsx("div", { className: `d-flex align-items-center justify-content-center p-4 ${className || ''}`.trim(), "aria-busy": "true", "aria-live": "polite", children: jsxRuntime.jsx("span", { className: "spinner-border", role: "status", "aria-label": ariaLabel }) }));
3510
+ return (jsxRuntime.jsx("div", { className: `d-loading-state ${className || ''}`.trim(), "aria-busy": "true", "aria-live": "polite", children: jsxRuntime.jsx("span", { className: "spinner-border", role: "status", "aria-label": ariaLabel }) }));
3220
3511
  }
3221
3512
 
3222
3513
  function render(renderable) {
@@ -3275,7 +3566,7 @@ function DConfirmModalUI({ entry }) {
3275
3566
  }
3276
3567
  });
3277
3568
  }, [onConfirmAction]);
3278
- return (jsxRuntime.jsxs(DModal, { name: entry.id, size: "lg", className: `confirm-modal ${critical ? 'critical-modal' : ''}`, children: [jsxRuntime.jsx(DModal.Header, { onClose: handleClose, showCloseButton: true, children: jsxRuntime.jsx("h5", { className: "fw-bold", children: title }) }), jsxRuntime.jsxs(DModal.Body, { className: "py-3 px-5", children: [jsxRuntime.jsx("p", { className: "mb-4", children: message }), critical && (jsxRuntime.jsx(ForwardedDInput, { type: "text", label: critical.codeLabel || 'Confirmation code', placeholder: critical.inputPlaceholder, value: confirmationCode, onChange: (value) => setConfirmationCode(value), className: "mb-4", autoFocus: true }))] }), jsxRuntime.jsxs(DModal.Footer, { children: [jsxRuntime.jsx(DButton, { text: cancelLabel, variant: "outline", color: "secondary", onClick: handleClose, disabled: internalLoading }), jsxRuntime.jsx(DButton, { text: confirmLabel, color: confirmColor, onClick: handleConfirmClick, disabled: isConfirmDisabled, loading: internalLoading })] })] }));
3569
+ return (jsxRuntime.jsxs(DModal, { name: entry.id, size: "lg", className: `confirm-modal ${critical ? 'critical-modal' : ''}`, children: [jsxRuntime.jsx(DModal.Header, { onClose: handleClose, showCloseButton: true, children: jsxRuntime.jsx("h5", { className: "fw-bold", children: title }) }), jsxRuntime.jsxs(DModal.Body, { className: "d-confirm-modal-body", children: [jsxRuntime.jsx("p", { className: "d-confirm-modal-message", children: message }), critical && (jsxRuntime.jsx(ForwardedDInput, { type: "text", label: critical.codeLabel || 'Confirmation code', placeholder: critical.inputPlaceholder, value: confirmationCode, onChange: (value) => setConfirmationCode(value), className: "d-confirm-modal-field", autoFocus: true }))] }), jsxRuntime.jsxs(DModal.Footer, { children: [jsxRuntime.jsx(DButton, { text: cancelLabel, variant: "outline", color: "secondary", onClick: handleClose, disabled: internalLoading }), jsxRuntime.jsx(DButton, { text: confirmLabel, color: confirmColor, onClick: handleConfirmClick, disabled: isConfirmDisabled, loading: internalLoading })] })] }));
3279
3570
  }
3280
3571
 
3281
3572
  /**
@@ -3413,6 +3704,7 @@ exports.getCssVariable = getCssVariable;
3413
3704
  exports.getQueryString = getQueryString;
3414
3705
  exports.subscribeToMediaQuery = subscribeToMediaQuery;
3415
3706
  exports.useConfirmModal = useConfirmModal;
3707
+ exports.useCountdown = useCountdown;
3416
3708
  exports.useDContext = useDContext;
3417
3709
  exports.useDPortalContext = useDPortalContext;
3418
3710
  exports.useDToast = useDToast;
@@ -3428,7 +3720,11 @@ exports.useMediaBreakpointUpXl = useMediaBreakpointUpXl;
3428
3720
  exports.useMediaBreakpointUpXs = useMediaBreakpointUpXs;
3429
3721
  exports.useMediaBreakpointUpXxl = useMediaBreakpointUpXxl;
3430
3722
  exports.useMediaQuery = useMediaQuery;
3723
+ exports.useOtp = useOtp;
3431
3724
  exports.useProvidedRefOrCreate = useProvidedRefOrCreate;
3725
+ exports.useScreenshot = useScreenshot;
3726
+ exports.useScreenshotDownload = useScreenshotDownload;
3727
+ exports.useScreenshotWebShare = useScreenshotWebShare;
3432
3728
  exports.useStackState = useStackState;
3433
3729
  exports.useTabContext = useTabContext;
3434
3730
  exports.validatePhoneNumber = validatePhoneNumber;