@dynamic-framework/ui-react 2.5.1 → 2.6.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 (104) hide show
  1. package/README.md +4 -0
  2. package/dist/css/dynamic-ui.css +38 -10
  3. package/dist/css/dynamic-ui.min.css +2 -2
  4. package/dist/index.esm.js +466 -83
  5. package/dist/index.esm.js.map +1 -1
  6. package/dist/index.js +471 -82
  7. package/dist/index.js.map +1 -1
  8. package/dist/types/components/DAlert/DAlert.d.ts +1 -1
  9. package/dist/types/components/DAvatar/DAvatar.d.ts +2 -1
  10. package/dist/types/components/DBadge/DBadge.d.ts +2 -1
  11. package/dist/types/components/DBox/DBox.d.ts +1 -1
  12. package/dist/types/components/DBoxFile/DBoxFile.d.ts +1 -1
  13. package/dist/types/components/DButtonIcon/DButtonIcon.d.ts +1 -1
  14. package/dist/types/components/DCard/DCard.d.ts +1 -1
  15. package/dist/types/components/DCard/components/DCardBody.d.ts +1 -1
  16. package/dist/types/components/DCard/components/DCardFooter.d.ts +1 -1
  17. package/dist/types/components/DCard/components/DCardHeader.d.ts +1 -1
  18. package/dist/types/components/DCarousel/components/DCarouselSlide.d.ts +1 -1
  19. package/dist/types/components/DChip/DChip.d.ts +2 -1
  20. package/dist/types/components/DCollapse/DCollapse.d.ts +1 -1
  21. package/dist/types/components/DConfirmModal/DConfirmModalContainer.d.ts +21 -0
  22. package/dist/types/components/DConfirmModal/DConfirmModalUI.d.ts +7 -0
  23. package/dist/types/components/DConfirmModal/confirmModalStore.d.ts +6 -0
  24. package/dist/types/components/DConfirmModal/types.d.ts +45 -0
  25. package/dist/types/components/DCreditCard/DCreditCard.d.ts +2 -1
  26. package/dist/types/components/DCurrencyText/DCurrencyText.d.ts +2 -1
  27. package/dist/types/components/DDataStateWrapper/DDataStateWrapper.d.ts +8 -1
  28. package/dist/types/components/DDataStateWrapper/components/EmptyState.d.ts +2 -1
  29. package/dist/types/components/DDataStateWrapper/components/ErrorState.d.ts +2 -1
  30. package/dist/types/components/DDataStateWrapper/components/LoadingState.d.ts +2 -1
  31. package/dist/types/components/DDataStateWrapper/index.d.ts +4 -0
  32. package/dist/types/components/DDatePicker/DDatePicker.d.ts +2 -1
  33. package/dist/types/components/DDatePicker/components/DDatePickerHeaderSelector.d.ts +1 -1
  34. package/dist/types/components/DDatePicker/components/DDatePickerTime.d.ts +1 -1
  35. package/dist/types/components/DDropdown/DDropdown.d.ts +4 -1
  36. package/dist/types/components/DErrorBoundary/DErrorBoundary.d.ts +1 -1
  37. package/dist/types/components/DErrorBoundary/components/DefaultErrorBoundary.d.ts +2 -1
  38. package/dist/types/components/DIcon/DIcon.d.ts +1 -1
  39. package/dist/types/components/DIconBase/DIconBase.d.ts +2 -1
  40. package/dist/types/components/DInputCheck/DInputCheck.d.ts +1 -1
  41. package/dist/types/components/DInputPin/DInputPin.d.ts +2 -1
  42. package/dist/types/components/DInputSearch/DInputSearch.d.ts +11 -0
  43. package/dist/types/components/DInputSearch/index.d.ts +3 -0
  44. package/dist/types/components/DInputSelect/DInputSelect.d.ts +1 -1
  45. package/dist/types/components/DInputSwitch/DInputSwitch.d.ts +2 -1
  46. package/dist/types/components/DLayout/DLayout.d.ts +1 -1
  47. package/dist/types/components/DLayout/components/DLayoutPane.d.ts +1 -1
  48. package/dist/types/components/DListGroup/DListGroup.d.ts +1 -1
  49. package/dist/types/components/DListGroup/components/DListGroupItem.d.ts +1 -1
  50. package/dist/types/components/DModal/DModal.d.ts +1 -1
  51. package/dist/types/components/DModal/components/DModalBody.d.ts +1 -1
  52. package/dist/types/components/DModal/components/DModalFooter.d.ts +1 -1
  53. package/dist/types/components/DModal/components/DModalHeader.d.ts +1 -1
  54. package/dist/types/components/DOffcanvas/DOffcanvas.d.ts +1 -1
  55. package/dist/types/components/DOffcanvas/components/DOffcanvasBody.d.ts +1 -1
  56. package/dist/types/components/DOffcanvas/components/DOffcanvasFooter.d.ts +1 -1
  57. package/dist/types/components/DOffcanvas/components/DOffcanvasHeader.d.ts +1 -1
  58. package/dist/types/components/DOtp/DOtp.d.ts +1 -1
  59. package/dist/types/components/DOtp/components/OtpCountdown.d.ts +2 -1
  60. package/dist/types/components/DPaginator/DPaginator.d.ts +2 -1
  61. package/dist/types/components/DPasswordStrengthMeter/DPasswordStrengthMeter.d.ts +3 -1
  62. package/dist/types/components/DPasswordStrengthMeter/PasswordCheckItem.d.ts +2 -1
  63. package/dist/types/components/DPasswordStrengthMeter/PasswordCheckList.d.ts +2 -1
  64. package/dist/types/components/DPasswordStrengthMeter/PasswordStrength.d.ts +2 -1
  65. package/dist/types/components/DPopover/DPopover.d.ts +1 -1
  66. package/dist/types/components/DProgress/DProgress.d.ts +2 -1
  67. package/dist/types/components/DSelect/DSelect.d.ts +2 -1
  68. package/dist/types/components/DSelect/components/DSelectClearIndicator.d.ts +2 -1
  69. package/dist/types/components/DSelect/components/DSelectDropdownIndicator.d.ts +2 -1
  70. package/dist/types/components/DSelect/components/DSelectLoadingIndicator.d.ts +2 -1
  71. package/dist/types/components/DSelect/components/DSelectMultiValueRemove.d.ts +2 -1
  72. package/dist/types/components/DSelect/components/DSelectOptionCheck.d.ts +2 -1
  73. package/dist/types/components/DSelect/components/DSelectOptionEmoji.d.ts +2 -1
  74. package/dist/types/components/DSelect/components/DSelectOptionIcon.d.ts +2 -1
  75. package/dist/types/components/DSelect/components/DSelectPlaceholder.d.ts +2 -1
  76. package/dist/types/components/DSelect/components/DSelectSingleValueEmoji.d.ts +2 -1
  77. package/dist/types/components/DSelect/components/DSelectSingleValueEmojiText.d.ts +2 -1
  78. package/dist/types/components/DSelect/components/DSelectSingleValueIconText.d.ts +2 -1
  79. package/dist/types/components/DStepper/DStepper.d.ts +2 -1
  80. package/dist/types/components/DStepperDesktop/DStepperDesktop.d.ts +2 -1
  81. package/dist/types/components/DStepperMobile/DStepperMobile.d.ts +2 -1
  82. package/dist/types/components/DTabs/DTabs.d.ts +1 -1
  83. package/dist/types/components/DTabs/components/DTabContent.d.ts +1 -1
  84. package/dist/types/components/DTimeline/DTimeline.d.ts +1 -1
  85. package/dist/types/components/DToast/DToast.d.ts +1 -1
  86. package/dist/types/components/DToast/components/DToastBody.d.ts +1 -1
  87. package/dist/types/components/DToast/components/DToastHeader.d.ts +1 -1
  88. package/dist/types/components/DToastContainer/DToastContainer.d.ts +1 -1
  89. package/dist/types/components/DTooltip/DTooltip.d.ts +1 -1
  90. package/dist/types/components/DVoucher/DVoucher.d.ts +1 -1
  91. package/dist/types/components/index.d.ts +6 -1
  92. package/dist/types/components/interface.d.ts +1 -1
  93. package/dist/types/contexts/DContext.d.ts +25 -3
  94. package/dist/types/contexts/DPortalContext.d.ts +1 -1
  95. package/dist/types/hooks/index.d.ts +2 -0
  96. package/dist/types/hooks/useConfirmModal.d.ts +34 -0
  97. package/package.json +7 -2
  98. package/src/style/_shame.scss +13 -0
  99. package/src/style/abstracts/variables/_forms.scss +1 -1
  100. package/src/style/abstracts/variables/_list-group.scss +1 -1
  101. package/src/style/base/_backdrop.scss +7 -0
  102. package/src/style/components/_d-carousel.scss +1 -1
  103. package/src/style/components/_d-modal.scss +8 -0
  104. package/src/style/root/_root.scss +31 -0
package/dist/index.esm.js CHANGED
@@ -58,7 +58,7 @@ function useDisableBodyScrollEffect(disable) {
58
58
  timer = window.setTimeout(() => {
59
59
  unlock();
60
60
  observer === null || observer === void 0 ? void 0 : observer.disconnect();
61
- }, 3000);
61
+ }, 300);
62
62
  }
63
63
  else {
64
64
  unlock();
@@ -143,7 +143,7 @@ function getKeyboardFocusableElements(container) {
143
143
  const DPortalContext = createContext(undefined);
144
144
  function DPortalContextProvider({ portalName, children, availablePortals, }) {
145
145
  const { created } = usePortal(portalName);
146
- const [stack, { push, pop, isEmpty }] = useStackState([]);
146
+ const [stack, { push, pop }] = useStackState([]);
147
147
  useDisableBodyScrollEffect(Boolean(stack.length));
148
148
  const openPortal = useCallback(
149
149
  // eslint-disable-next-line prefer-arrow-callback
@@ -164,11 +164,9 @@ function DPortalContextProvider({ portalName, children, availablePortals, }) {
164
164
  (_a = document.activeElement) === null || _a === void 0 ? void 0 : _a.blur();
165
165
  }, [availablePortals, push]);
166
166
  const closePortal = useCallback(() => {
167
- if (isEmpty()) {
168
- return;
169
- }
167
+ // pop() is safe on empty stacks, so close remains idempotent.
170
168
  pop();
171
- }, [isEmpty, pop]);
169
+ }, [pop]);
172
170
  const publicStack = useMemo(() => stack.map(({ name, payload }) => ({ name, payload })), [stack]);
173
171
  const value = useMemo(() => ({
174
172
  stack: publicStack,
@@ -259,6 +257,40 @@ function useDPortalContext() {
259
257
  return context;
260
258
  }
261
259
 
260
+ function createConfirmModalStore() {
261
+ const listeners = new Set();
262
+ let entries = [];
263
+ function notify() {
264
+ listeners.forEach((listener) => listener([...entries]));
265
+ }
266
+ const store = {
267
+ subscribe(listener) {
268
+ listeners.add(listener);
269
+ listener([...entries]);
270
+ return () => {
271
+ listeners.delete(listener);
272
+ };
273
+ },
274
+ push(entry) {
275
+ entries = [...entries, entry];
276
+ notify();
277
+ },
278
+ remove(id) {
279
+ entries = entries.filter((e) => e.id !== id);
280
+ notify();
281
+ },
282
+ };
283
+ return store;
284
+ }
285
+ const ConfirmModalStoreContext = createContext(null);
286
+ function useConfirmModalStore() {
287
+ const store = useContext(ConfirmModalStoreContext);
288
+ if (!store) {
289
+ throw new Error('useConfirmModal must be used within a <DContextProvider>.');
290
+ }
291
+ return store;
292
+ }
293
+
262
294
  function getCssVariable(variable) {
263
295
  const computedStyle = getComputedStyle(document.documentElement);
264
296
  return computedStyle.getPropertyValue(variable).trim();
@@ -317,6 +349,28 @@ const DContext = createContext(DEFAULT_STATE);
317
349
  * component to configure icons, currency, language, and portal settings
318
350
  * for all descendant Dynamic UI components.
319
351
  *
352
+ * To enable confirmation modals you must also mount `DConfirmModalContainer`
353
+ * somewhere inside this provider (typically right before the closing tag of
354
+ * your root layout), similar to how `DToastContainer` works:
355
+ *
356
+ * ```tsx
357
+ * // Default: portalName="d-portal"
358
+ * <DContextProvider>
359
+ * <App />
360
+ * <DConfirmModalContainer nodeId="d-portal" />
361
+ * </DContextProvider>
362
+ * ```
363
+ *
364
+ * If you customize `portalName`, match it in `DConfirmModalContainer.nodeId`:
365
+ *
366
+ * ```tsx
367
+ * // Custom portalName
368
+ * <DContextProvider portalName="my-custom-portal">
369
+ * <App />
370
+ * <DConfirmModalContainer nodeId="my-custom-portal" />
371
+ * </DContextProvider>
372
+ * ```
373
+ *
320
374
  * @template T - Map of portal name → payload shape (e.g. `{ modal: { title: string } }`).
321
375
  * Pass it once at the top level: `<DContextProvider<MyPortals> ...>`.
322
376
  */
@@ -342,7 +396,8 @@ function DContextProvider({ language = DEFAULT_STATE.language, currency = DEFAUL
342
396
  });
343
397
  }, [setContext]);
344
398
  const value = useMemo(() => (Object.assign(Object.assign({}, internalContext), { setContext })), [internalContext, setContext]);
345
- return (jsx(DContext.Provider, { value: value, children: jsx(DPortalContextProvider, { portalName: portalName, availablePortals: availablePortals, children: children }) }));
399
+ const confirmStore = useMemo(() => createConfirmModalStore(), []);
400
+ return (jsx(ConfirmModalStoreContext.Provider, { value: confirmStore, children: jsx(DContext.Provider, { value: value, children: jsx(DPortalContextProvider, { portalName: portalName, availablePortals: availablePortals, children: children }) }) }));
346
401
  }
347
402
  /**
348
403
  * Returns the Dynamic UI context value set by `DContextProvider`.
@@ -1037,7 +1092,7 @@ function DBoxFile(_a) {
1037
1092
  }
1038
1093
 
1039
1094
  const DButton = forwardRef((props, ref) => {
1040
- const { color = 'primary', size, variant, text, children, iconStart, iconStartFamilyClass, iconStartFamilyPrefix, iconStartMaterialStyle, iconEnd, iconEndFamilyClass, iconEndFamilyPrefix, iconEndMaterialStyle, loading = false, loadingText, loadingAriaLabel, disabled = false, className, style, dataAttributes, onClick, type = 'button', target, rel, href, 'aria-label': ariaLabelProp } = props, rest = __rest(props, ["color", "size", "variant", "text", "children", "iconStart", "iconStartFamilyClass", "iconStartFamilyPrefix", "iconStartMaterialStyle", "iconEnd", "iconEndFamilyClass", "iconEndFamilyPrefix", "iconEndMaterialStyle", "loading", "loadingText", "loadingAriaLabel", "disabled", "className", "style", "dataAttributes", "onClick", "type", "target", "rel", "href", 'aria-label']);
1095
+ const { color = 'primary', size, variant = 'solid', text, children, iconStart, iconStartFamilyClass, iconStartFamilyPrefix, iconStartMaterialStyle, iconEnd, iconEndFamilyClass, iconEndFamilyPrefix, iconEndMaterialStyle, loading = false, loadingText, loadingAriaLabel, disabled = false, className, style, dataAttributes, onClick, type = 'button', target, rel, href, 'aria-label': ariaLabelProp } = props, rest = __rest(props, ["color", "size", "variant", "text", "children", "iconStart", "iconStartFamilyClass", "iconStartFamilyPrefix", "iconStartMaterialStyle", "iconEnd", "iconEndFamilyClass", "iconEndFamilyPrefix", "iconEndMaterialStyle", "loading", "loadingText", "loadingAriaLabel", "disabled", "className", "style", "dataAttributes", "onClick", "type", "target", "rel", "href", 'aria-label']);
1041
1096
  // Responsive size resolution using useResponsiveProp
1042
1097
  const { responsivePropValue } = useResponsiveProp(true);
1043
1098
  const resolvedSize = useMemo(() => {
@@ -1052,9 +1107,9 @@ const DButton = forwardRef((props, ref) => {
1052
1107
  const isDisabled = useMemo(() => disabled || loading, [disabled, loading]);
1053
1108
  const content = useMemo(() => children || text, [children, text]);
1054
1109
  const classes = useMemo(() => {
1055
- const variantClass = variant
1056
- ? `btn-${variant}-${color}`
1057
- : `btn-${color}`;
1110
+ const variantClass = variant === 'solid'
1111
+ ? `btn-${color}`
1112
+ : `btn-${variant}-${color}`;
1058
1113
  return {
1059
1114
  btn: true,
1060
1115
  [variantClass]: true,
@@ -1112,9 +1167,9 @@ DButton.displayName = 'DButton';
1112
1167
  function DButtonIcon(_a) {
1113
1168
  var { id, icon, size, className, variant, state, loadingAriaLabel, iconMaterialStyle, disabled = false, color = 'primary', loading = false, href, target, rel, stopPropagationEnabled = true, style, iconFamilyClass, iconFamilyPrefix, dataAttributes, onClick, 'aria-label': ariaLabelProp } = _a, rest = __rest(_a, ["id", "icon", "size", "className", "variant", "state", "loadingAriaLabel", "iconMaterialStyle", "disabled", "color", "loading", "href", "target", "rel", "stopPropagationEnabled", "style", "iconFamilyClass", "iconFamilyPrefix", "dataAttributes", "onClick", 'aria-label']);
1114
1169
  const generateClasses = useMemo(() => {
1115
- const variantClass = variant
1116
- ? `btn-${variant}-${color}`
1117
- : `btn-${color}`;
1170
+ const variantClass = !variant || variant === 'solid'
1171
+ ? `btn-${color}`
1172
+ : `btn-${variant}-${color}`;
1118
1173
  return Object.assign(Object.assign(Object.assign({ 'btn d-button-icon': true, [variantClass]: true }, size && { [`btn-${size}`]: true }), (state && state !== 'disabled') && { [state]: true }), { loading });
1119
1174
  }, [variant, color, size, state, loading]);
1120
1175
  const isDisabled = useMemo(() => (state === 'disabled' || loading || disabled), [state, loading, disabled]);
@@ -1406,6 +1461,18 @@ function DDatePickerHeaderSelector({ date, changeYear, changeMonth, decreaseMont
1406
1461
 
1407
1462
  function DDatePicker(_a) {
1408
1463
  var { inputLabel, inputHint, inputAriaLabel, ariaLabelInputTime, inputActionAriaLabel = 'open calendar', inputId = 'input-calendar', timeId = 'input-time', timeInputLabel, minYearSelect, maxYearSelect, iconHeaderSize, iconMaterialStyle, iconInput, headerPrevMonthAriaLabel, headerNextMonthAriaLabel, invalid = false, valid = false, renderCustomHeader: renderCustomHeaderProp, className, dateFormatCalendar: dateFormatCalendarProp, style, dataAttributes, placeholder, showHeaderSelectors, formatHeaderDate } = _a, props = __rest(_a, ["inputLabel", "inputHint", "inputAriaLabel", "ariaLabelInputTime", "inputActionAriaLabel", "inputId", "timeId", "timeInputLabel", "minYearSelect", "maxYearSelect", "iconHeaderSize", "iconMaterialStyle", "iconInput", "headerPrevMonthAriaLabel", "headerNextMonthAriaLabel", "invalid", "valid", "renderCustomHeader", "className", "dateFormatCalendar", "style", "dataAttributes", "placeholder", "showHeaderSelectors", "formatHeaderDate"]);
1464
+ // Some test runtimes can resolve react-datepicker as a module object.
1465
+ // Normalize to a renderable component for both function and { default } shapes.
1466
+ const SafeDatePicker = useMemo(() => {
1467
+ const interopDefault = DatePicker.default;
1468
+ if (interopDefault !== undefined) {
1469
+ return interopDefault;
1470
+ }
1471
+ if (typeof DatePicker === 'function') {
1472
+ return DatePicker;
1473
+ }
1474
+ return DatePicker;
1475
+ }, []);
1409
1476
  const pickerType = useMemo(() => {
1410
1477
  if (props.showQuarterYearPicker)
1411
1478
  return PickerType.Quarter;
@@ -1433,7 +1500,7 @@ function DDatePicker(_a) {
1433
1500
  ]);
1434
1501
  const defaultRenderCustomHeader = useCallback((headerProps) => (jsx(DatePickerHeader, Object.assign({}, headerProps))), [DatePickerHeader]);
1435
1502
  const renderCustomHeader = useMemo(() => (renderCustomHeaderProp || defaultRenderCustomHeader), [defaultRenderCustomHeader, renderCustomHeaderProp]);
1436
- return (jsx(DatePicker, Object.assign({}, dataAttributes, props, { calendarClassName: "d-date-picker", renderCustomHeader: renderCustomHeader, placeholderText: placeholder, customInput: (jsx(ForwardedDDatePickerInput, { id: inputId, "aria-label": inputAriaLabel, iconEndAriaLabel: inputActionAriaLabel, iconMaterialStyle: iconMaterialStyle, iconEnd: iconInput, inputLabel: inputLabel, className: className, style: style, invalid: invalid, valid: valid, hint: inputHint })), customTimeInput: (jsx(DDatePickerTime, { id: timeId, "aria-label": ariaLabelInputTime })) })));
1503
+ return (jsx(SafeDatePicker, Object.assign({}, dataAttributes, props, { calendarClassName: "d-date-picker", renderCustomHeader: renderCustomHeader, placeholderText: placeholder, customInput: (jsx(ForwardedDDatePickerInput, { id: inputId, "aria-label": inputAriaLabel, iconEndAriaLabel: inputActionAriaLabel, iconMaterialStyle: iconMaterialStyle, iconEnd: iconInput, inputLabel: inputLabel, className: className, style: style, invalid: invalid, valid: valid, hint: inputHint })), customTimeInput: (jsx(DDatePickerTime, { id: timeId, "aria-label": ariaLabelInputTime })) })));
1437
1504
  }
1438
1505
 
1439
1506
  function DLayoutPane({ className, style, children, cols, colsXs, colsSm, colsMd, colsLg, colsXl, colsXxl, dataAttributes, }) {
@@ -1463,6 +1530,55 @@ var DLayout$1 = Object.assign(DLayout, {
1463
1530
  Pane: DLayoutPane,
1464
1531
  });
1465
1532
 
1533
+ function normalizeValue(value) {
1534
+ return value !== null && value !== void 0 ? value : '';
1535
+ }
1536
+ function DInputSearch(_a, ref) {
1537
+ var { debounceMs = 300, onChange, onImmediateChange, value, defaultValue, placeholder = 'Search...' } = _a, props = __rest(_a, ["debounceMs", "onChange", "onImmediateChange", "value", "defaultValue", "placeholder"]);
1538
+ const inputRef = useProvidedRefOrCreate(ref);
1539
+ const isControlled = value !== undefined;
1540
+ const onChangeRef = useRef(onChange);
1541
+ onChangeRef.current = onChange;
1542
+ const [internalValue, setInternalValue] = useState(normalizeValue(isControlled ? value : defaultValue));
1543
+ // null = no keystroke has occurred yet (skip debounce on mount)
1544
+ const [pendingValue, setPendingValue] = useState(null);
1545
+ useEffect(() => {
1546
+ if (!isControlled)
1547
+ return;
1548
+ setInternalValue(normalizeValue(value));
1549
+ }, [isControlled, value]);
1550
+ const handleChange = useCallback((nextValue) => {
1551
+ if (!isControlled) {
1552
+ setInternalValue(nextValue);
1553
+ }
1554
+ setPendingValue(nextValue);
1555
+ onImmediateChange === null || onImmediateChange === void 0 ? void 0 : onImmediateChange(nextValue);
1556
+ }, [isControlled, onImmediateChange]);
1557
+ useEffect(() => {
1558
+ var _a;
1559
+ let timeoutId;
1560
+ if (pendingValue !== null) {
1561
+ if (debounceMs <= 0) {
1562
+ (_a = onChangeRef.current) === null || _a === void 0 ? void 0 : _a.call(onChangeRef, pendingValue);
1563
+ setPendingValue(null);
1564
+ }
1565
+ else {
1566
+ timeoutId = window.setTimeout(() => {
1567
+ var _a;
1568
+ (_a = onChangeRef.current) === null || _a === void 0 ? void 0 : _a.call(onChangeRef, pendingValue);
1569
+ setPendingValue(null);
1570
+ }, debounceMs);
1571
+ }
1572
+ }
1573
+ return () => {
1574
+ window.clearTimeout(timeoutId);
1575
+ };
1576
+ }, [debounceMs, pendingValue]);
1577
+ return (jsx(ForwardedDInput, Object.assign({ ref: inputRef }, props, { type: "search", value: internalValue, onChange: handleChange, placeholder: placeholder })));
1578
+ }
1579
+ const ForwardedDInputSearch = forwardRef(DInputSearch);
1580
+ ForwardedDInputSearch.displayName = 'DInputSearch';
1581
+
1466
1582
  function DInputMask(props, ref) {
1467
1583
  return (jsx(InputMask, Object.assign({ ref: ref, component: ForwardedDInput }, props)));
1468
1584
  }
@@ -1567,6 +1683,64 @@ function useItemSelection({ getItemIdentifier: getItemIdentifierProp, previousSe
1567
1683
  };
1568
1684
  }
1569
1685
 
1686
+ /**
1687
+ * Hook to display a confirmation modal.
1688
+ *
1689
+ * Renders into the portal node specified by `DConfirmModalContainer` (typically `#d-portal`).
1690
+ * The confirm modal floats above the portal stack and handles its own Escape key,
1691
+ * preventing interference with underlying modals.
1692
+ *
1693
+ * Requires both `DContextProvider` wrapping your application AND an explicit
1694
+ * `<DConfirmModalContainer nodeId="d-portal" />` mounted in your app.
1695
+ *
1696
+ * @example
1697
+ * function App() {
1698
+ * return (
1699
+ * <DContextProvider>
1700
+ * <YourContent />
1701
+ * <DConfirmModalContainer nodeId="d-portal" />
1702
+ * </DContextProvider>
1703
+ * );
1704
+ * }
1705
+ *
1706
+ * function MyComponent() {
1707
+ * const confirm = useConfirmModal({
1708
+ * title: 'Delete Account',
1709
+ * message: 'This action cannot be undone.',
1710
+ * critical: { code: 'DELETE ACCOUNT' },
1711
+ * onConfirm: async () => { await deleteAccount(); },
1712
+ * });
1713
+ *
1714
+ * return <DButton onClick={confirm.open} text="Delete" color="danger" />;
1715
+ * }
1716
+ */
1717
+ function useConfirmModal(config) {
1718
+ const store = useConfirmModalStore();
1719
+ const idRef = useRef(`confirm-modal-${Math.random().toString(36).slice(2)}`);
1720
+ const close = useCallback(() => {
1721
+ store.remove(idRef.current);
1722
+ queueMicrotask(() => {
1723
+ var _a;
1724
+ (_a = config.onClose) === null || _a === void 0 ? void 0 : _a.call(config);
1725
+ });
1726
+ }, [store, config]);
1727
+ const open = useCallback(() => {
1728
+ const handleConfirmAction = async () => {
1729
+ try {
1730
+ await config.onConfirm();
1731
+ store.remove(idRef.current);
1732
+ }
1733
+ catch (_a) {
1734
+ // Keep modal open on error
1735
+ }
1736
+ };
1737
+ // Remove any existing entry with this id to prevent duplicates on re-entrancy
1738
+ store.remove(idRef.current);
1739
+ store.push(Object.assign(Object.assign({}, config), { id: idRef.current, onConfirmAction: handleConfirmAction, onCloseAction: close }));
1740
+ }, [store, config, close]);
1741
+ return { open };
1742
+ }
1743
+
1570
1744
  function DInputCounter(_a, ref) {
1571
1745
  var { minValue, maxValue, value = minValue, invalid, iconStart: iconStartProp, iconEnd: iconEndProp, iconStartAriaLabel = 'decrease action', iconEndAriaLabel = 'increase action', style, onChange } = _a, props = __rest(_a, ["minValue", "maxValue", "value", "invalid", "iconStart", "iconEnd", "iconStartAriaLabel", "iconEndAriaLabel", "style", "onChange"]);
1572
1746
  const { handleOnWheel, } = useDisableInputWheel(ref);
@@ -1698,7 +1872,7 @@ const DEFAULT_VALIDATION_MESSAGES = {
1698
1872
  notMatch: 'The password confirmation and the new password do not match.',
1699
1873
  };
1700
1874
  const DEFAULT_ENABLED_CHECKS = ['uppercase', 'lowercase', 'number', 'specialChar'];
1701
- function DPasswordStrengthMeter({ id, label = 'Password', placeholder, value = '', name, disabled = false, invalid = false, validationMessages = DEFAULT_VALIDATION_MESSAGES, enabledChecks = DEFAULT_ENABLED_CHECKS, className, style, dataAttributes, onChange, }) {
1875
+ function DPasswordStrengthMeter({ id, label = 'Password', placeholder, value = '', name, disabled = false, invalid = false, validationMessages = DEFAULT_VALIDATION_MESSAGES, enabledChecks = DEFAULT_ENABLED_CHECKS, className, style, dataAttributes, onChange, readonly = false, }) {
1702
1876
  const [password, setPassword] = useState(value);
1703
1877
  useEffect(() => {
1704
1878
  setPassword(value);
@@ -1707,7 +1881,7 @@ function DPasswordStrengthMeter({ id, label = 'Password', placeholder, value = '
1707
1881
  setPassword(newValue);
1708
1882
  onChange === null || onChange === void 0 ? void 0 : onChange(newValue);
1709
1883
  };
1710
- return (jsxs("div", Object.assign({ className: className, style: style }, dataAttributes, { children: [jsx(ForwardedDInputPassword, { id: id, label: label, placeholder: placeholder, value: password, name: name, disabled: disabled, invalid: invalid, onChange: handleChange }), jsx(PasswordChecksList, { password: password, validationMessages: validationMessages, enabledChecks: enabledChecks })] })));
1884
+ return (jsxs("div", Object.assign({ className: className, style: style }, dataAttributes, { children: [jsx(ForwardedDInputPassword, { id: id, label: label, placeholder: placeholder, value: password, name: name, disabled: disabled, invalid: invalid, onChange: handleChange, readonly: readonly }), jsx(PasswordChecksList, { password: password, validationMessages: validationMessages, enabledChecks: enabledChecks })] })));
1711
1885
  }
1712
1886
 
1713
1887
  function DInputCheck(_a) {
@@ -2611,82 +2785,210 @@ function DCreditCard({ brand = 'visa', name, number, holderText = 'Card Holder',
2611
2785
  return (jsxs("div", { className: classNames('d-credit-card', isVertical && 'is-vertical', className), children: [jsxs("div", { className: "d-credit-card-header", children: [jsx("img", { src: logoImage || BRAND_LOGOS[brand] || DEFAULT_IMAGE, alt: brand, className: "d-credit-card-logo", width: 100 }), isChipVisible && (jsx("div", { className: "d-credit-card-chip", children: jsx("img", { src: CHIP_IMAGE, alt: "chip", width: 30, className: "d-credit-card-chip-image" }) }))] }), jsxs("div", { className: "d-credit-card-details", children: [jsx("div", { className: "d-credit-card-number", children: number }), jsx("small", { className: "d-credit-card-holder-text", children: holderText }), jsx("span", { className: "d-credit-card-name", children: name })] })] }));
2612
2786
  }
2613
2787
 
2614
- const getItemClass = (action) => {
2615
- const base = classNames({
2616
- 'dropdown-item d-flex align-items-center': true,
2617
- [`dropdown-item-${action.color}`]: !!action.color,
2618
- disabled: action.disabled,
2619
- });
2620
- return base;
2621
- };
2622
- function DDropdown({ actions, dropdownToggle, className, classNameMenu, }) {
2788
+ const getItemClass = (action) => classNames({
2789
+ 'dropdown-item d-flex align-items-center': true,
2790
+ [`dropdown-item-${action.color}`]: !!action.color,
2791
+ disabled: action.disabled,
2792
+ });
2793
+ function DDropdown({ actions, dropdownToggle, className, classNameMenu, asPortal = false, placement = 'auto', }) {
2623
2794
  const [open, setOpen] = useState(false);
2795
+ const [position, setPosition] = useState('down');
2796
+ const [menuCoords, setMenuCoords] = useState(null);
2797
+ // Used in default (non-portal) mode: wraps the entire dropdown
2624
2798
  const dropdownRef = useRef(null);
2625
- const [position, setPosition] = useState('down'); // 🆕
2626
- // Cerrar al hacer click fuera
2627
- useEffect(() => {
2628
- const handleClickOutside = (event) => {
2629
- if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
2630
- setOpen(false);
2631
- }
2632
- };
2633
- document.addEventListener('mousedown', handleClickOutside);
2634
- return () => document.removeEventListener('mousedown', handleClickOutside);
2635
- }, []);
2636
- useEffect(() => {
2637
- if (open && dropdownRef.current) {
2638
- const rect = dropdownRef.current.getBoundingClientRect();
2639
- const spaceBottom = window.innerHeight - rect.bottom;
2640
- const spaceRight = window.innerWidth - rect.right;
2799
+ // Used in portal mode: ref on the toggle wrapper to read its viewport coords
2800
+ const toggleRef = useRef(null);
2801
+ // Ref on the rendered <ul> — used for outside-click detection in portal mode
2802
+ const menuRef = useRef(null);
2803
+ const toggle = () => {
2804
+ setMenuCoords(null);
2805
+ setOpen((prev) => !prev);
2806
+ };
2807
+ const resolvedInlinePosition = placement === 'auto' ? position : placement;
2808
+ // Compute fixed coords from the toggle's bounding rect (portal mode only)
2809
+ const computePortalCoords = useCallback(() => {
2810
+ if (!toggleRef.current)
2811
+ return;
2812
+ const rect = toggleRef.current.getBoundingClientRect();
2813
+ const spaceRight = window.innerWidth - rect.right;
2814
+ const spaceBottom = window.innerHeight - rect.bottom;
2815
+ const spaceTop = rect.top;
2816
+ let resolvedPlacement;
2817
+ if (placement === 'auto') {
2641
2818
  if (spaceBottom < 150) {
2642
- setPosition('up');
2819
+ resolvedPlacement = 'up';
2643
2820
  }
2644
2821
  else if (spaceRight < 150) {
2645
- setPosition('start');
2822
+ resolvedPlacement = 'start';
2646
2823
  }
2647
2824
  else {
2648
- setPosition('down');
2825
+ resolvedPlacement = 'down';
2649
2826
  }
2650
2827
  }
2651
- }, [open]);
2652
- let ToggleElement;
2653
- if (dropdownToggle) {
2654
- if (typeof dropdownToggle === 'function') {
2655
- ToggleElement = dropdownToggle({ open, toggle: () => setOpen(!open) });
2828
+ else {
2829
+ resolvedPlacement = placement;
2830
+ }
2831
+ const openStart = resolvedPlacement === 'start';
2832
+ const openEnd = resolvedPlacement === 'end';
2833
+ const openUp = resolvedPlacement === 'up';
2834
+ let availableHeight = spaceBottom;
2835
+ if (openUp) {
2836
+ availableHeight = spaceTop;
2837
+ }
2838
+ else if (openStart || openEnd) {
2839
+ availableHeight = window.innerHeight - rect.top;
2840
+ }
2841
+ const verticalCoords = openUp
2842
+ ? { bottom: window.innerHeight - rect.top + 4 }
2843
+ : { top: openStart || openEnd ? rect.top : rect.bottom + 4 };
2844
+ let horizontalCoords;
2845
+ if (openStart) {
2846
+ horizontalCoords = { right: window.innerWidth - rect.left + 4 };
2847
+ }
2848
+ else if (openEnd) {
2849
+ horizontalCoords = { left: rect.right + 4 };
2656
2850
  }
2657
2851
  else {
2658
- ToggleElement = dropdownToggle;
2852
+ horizontalCoords = { left: rect.left };
2659
2853
  }
2854
+ setMenuCoords(Object.assign(Object.assign(Object.assign({}, verticalCoords), horizontalCoords), { minWidth: Math.max(rect.width, 160), maxHeight: `${Math.max(availableHeight - 12, 120)}px` }));
2855
+ }, [placement]);
2856
+ // Outside-click: default mode
2857
+ useEffect(() => {
2858
+ if (asPortal)
2859
+ return () => { };
2860
+ const handler = (event) => {
2861
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
2862
+ setOpen(false);
2863
+ }
2864
+ };
2865
+ document.addEventListener('mousedown', handler);
2866
+ return () => document.removeEventListener('mousedown', handler);
2867
+ }, [asPortal]);
2868
+ // Outside-click: portal mode (must check both toggle and floating menu)
2869
+ useEffect(() => {
2870
+ if (!asPortal || !open)
2871
+ return () => { };
2872
+ const handler = (event) => {
2873
+ var _a, _b;
2874
+ const target = event.target;
2875
+ if (!((_a = toggleRef.current) === null || _a === void 0 ? void 0 : _a.contains(target)) && !((_b = menuRef.current) === null || _b === void 0 ? void 0 : _b.contains(target))) {
2876
+ setOpen(false);
2877
+ }
2878
+ };
2879
+ document.addEventListener('mousedown', handler);
2880
+ return () => document.removeEventListener('mousedown', handler);
2881
+ }, [asPortal, open]);
2882
+ // Auto-position: default mode
2883
+ useEffect(() => {
2884
+ if (asPortal || !open || !dropdownRef.current || placement !== 'auto')
2885
+ return;
2886
+ const rect = dropdownRef.current.getBoundingClientRect();
2887
+ const spaceBottom = window.innerHeight - rect.bottom;
2888
+ const spaceRight = window.innerWidth - rect.right;
2889
+ if (spaceBottom < 150)
2890
+ setPosition('up');
2891
+ else if (spaceRight < 150)
2892
+ setPosition('start');
2893
+ else
2894
+ setPosition('down');
2895
+ }, [asPortal, open, placement]);
2896
+ // Compute + track coords: portal mode
2897
+ // Re-runs whenever the menu opens and keeps coords fresh on scroll/resize
2898
+ useEffect(() => {
2899
+ if (!asPortal || !open)
2900
+ return () => { };
2901
+ computePortalCoords();
2902
+ const update = () => computePortalCoords();
2903
+ window.addEventListener('scroll', update, { passive: true, capture: true });
2904
+ window.addEventListener('resize', update, { passive: true });
2905
+ return () => {
2906
+ window.removeEventListener('scroll', update, { capture: true });
2907
+ window.removeEventListener('resize', update);
2908
+ };
2909
+ }, [asPortal, open, computePortalCoords]);
2910
+ let ToggleElement;
2911
+ if (dropdownToggle) {
2912
+ ToggleElement = typeof dropdownToggle === 'function'
2913
+ ? dropdownToggle({ open, toggle })
2914
+ : dropdownToggle;
2660
2915
  }
2661
2916
  else {
2662
- ToggleElement = (jsx(DButtonIcon, { variant: "link", stopPropagationEnabled: false, "aria-label": "Toggle Dropdown", "aria-haspopup": "menu", "aria-expanded": open, onClick: () => setOpen(!open), icon: "MoreVertical" }));
2917
+ ToggleElement = (jsx(DButtonIcon, { variant: "link", stopPropagationEnabled: false, "aria-label": "Toggle Dropdown", "aria-haspopup": "menu", "aria-expanded": open, onClick: toggle, icon: "MoreVertical" }));
2663
2918
  }
2664
- return (jsxs("div", { className: classNames(`dropdown drop-${position}`, className), ref: dropdownRef, children: [ToggleElement, jsx("ul", { style: {
2665
- position: 'absolute',
2666
- top: position === 'up' ? 'auto' : '100%',
2667
- bottom: position === 'up' ? '100%' : 'auto',
2668
- left: position === 'start' ? 'auto' : 0,
2669
- right: position === 'start' ? '0' : 'auto',
2670
- transform: 'translateY(4px)',
2671
- }, className: classNames('dropdown-menu p-2', { show: open }, classNameMenu), children: actions.map((action, index) => {
2672
- if (action.isDivider) {
2673
- return (jsx("hr", { className: "dropdown-divider" }, index));
2674
- }
2675
- return (jsx("li", { children: action.href ? (jsxs("a", { className: getItemClass(action), href: action.href, onClick: (e) => {
2676
- if (action.disabled) {
2677
- e.preventDefault();
2678
- }
2679
- else {
2680
- setOpen(false);
2681
- }
2682
- }, children: [action.icon && (jsx(DIcon, { icon: action.icon, className: "me-2", size: "1rem" })), action.label] })) : (jsxs("button", { className: getItemClass(action), type: "button", onClick: () => {
2683
- var _a;
2684
- if (!action.disabled) {
2685
- (_a = action.onClick) === null || _a === void 0 ? void 0 : _a.call(action);
2686
- setOpen(false);
2687
- }
2688
- }, disabled: action.disabled, children: [action.icon && (jsx(DIcon, { icon: action.icon, className: "me-2", size: "1rem" })), action.label] })) }, index));
2689
- }) })] }));
2919
+ const portalMenuStyle = asPortal && menuCoords
2920
+ ? {
2921
+ position: 'fixed',
2922
+ top: menuCoords.top,
2923
+ bottom: menuCoords.bottom,
2924
+ left: menuCoords.left,
2925
+ right: menuCoords.right,
2926
+ minWidth: menuCoords.minWidth,
2927
+ maxHeight: menuCoords.maxHeight,
2928
+ overflowY: 'auto',
2929
+ zIndex: 1050,
2930
+ transform: 'none',
2931
+ }
2932
+ : undefined;
2933
+ let inlineTop = '100%';
2934
+ if (resolvedInlinePosition === 'up') {
2935
+ inlineTop = 'auto';
2936
+ }
2937
+ else if (resolvedInlinePosition === 'start' || resolvedInlinePosition === 'end') {
2938
+ inlineTop = 0;
2939
+ }
2940
+ let inlineLeft = 0;
2941
+ if (resolvedInlinePosition === 'start') {
2942
+ inlineLeft = 'auto';
2943
+ }
2944
+ else if (resolvedInlinePosition === 'end') {
2945
+ inlineLeft = '100%';
2946
+ }
2947
+ let inlineRight = 'auto';
2948
+ if (resolvedInlinePosition === 'start') {
2949
+ inlineRight = '100%';
2950
+ }
2951
+ let inlineTransform = 'translateY(4px)';
2952
+ if (resolvedInlinePosition === 'up') {
2953
+ inlineTransform = 'translateY(-4px)';
2954
+ }
2955
+ else if (resolvedInlinePosition === 'start') {
2956
+ inlineTransform = 'translateX(-4px)';
2957
+ }
2958
+ else if (resolvedInlinePosition === 'end') {
2959
+ inlineTransform = 'translateX(4px)';
2960
+ }
2961
+ const inlineMenuStyle = {
2962
+ position: 'absolute',
2963
+ top: inlineTop,
2964
+ bottom: resolvedInlinePosition === 'up' ? '100%' : 'auto',
2965
+ left: inlineLeft,
2966
+ right: inlineRight,
2967
+ transform: inlineTransform,
2968
+ };
2969
+ const menuItems = (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) => {
2970
+ if (action.isDivider) {
2971
+ return jsx("hr", { className: "dropdown-divider" }, index);
2972
+ }
2973
+ return (jsx("li", { children: action.href ? (jsxs("a", { className: getItemClass(action), href: action.href, onClick: (e) => {
2974
+ if (action.disabled) {
2975
+ e.preventDefault();
2976
+ }
2977
+ else {
2978
+ setOpen(false);
2979
+ }
2980
+ }, children: [action.icon && jsx(DIcon, { icon: action.icon, className: "me-2", size: "1rem" }), action.label] })) : (jsxs("button", { className: getItemClass(action), type: "button", onClick: () => {
2981
+ var _a;
2982
+ if (!action.disabled) {
2983
+ (_a = action.onClick) === null || _a === void 0 ? void 0 : _a.call(action);
2984
+ setOpen(false);
2985
+ }
2986
+ }, disabled: action.disabled, children: [action.icon && jsx(DIcon, { icon: action.icon, className: "me-2", size: "1rem" }), action.label] })) }, index));
2987
+ }) }));
2988
+ if (asPortal) {
2989
+ return (jsxs("div", { ref: toggleRef, className: classNames('dropdown', className), children: [ToggleElement, open && menuCoords && createPortal(menuItems, document.body)] }));
2990
+ }
2991
+ return (jsxs("div", { className: classNames(`dropdown drop-${resolvedInlinePosition}`, className), ref: dropdownRef, children: [ToggleElement, menuItems] }));
2690
2992
  }
2691
2993
 
2692
2994
  function useScreenshot() {
@@ -2902,28 +3204,109 @@ function render(renderable) {
2902
3204
  return null;
2903
3205
  return typeof renderable === 'function' ? renderable() : renderable;
2904
3206
  }
2905
- function DDataStateWrapper({ isLoading, isError, data, onRetry, renderLoading, renderEmpty, renderError, children, }) {
3207
+ function DDataStateWrapper({ isLoading, isError, data, onRetry, messages, renderLoading, renderEmpty, renderError, children, }) {
2906
3208
  // 1. Loading
2907
3209
  if (isLoading) {
2908
3210
  if (renderLoading)
2909
3211
  return render(renderLoading);
2910
- return jsx(LoadingState, {});
3212
+ return jsx(LoadingState, { ariaLabel: messages === null || messages === void 0 ? void 0 : messages.loading });
2911
3213
  }
2912
3214
  // 2. Error
2913
3215
  if (isError) {
2914
3216
  if (renderError)
2915
3217
  return render(renderError);
2916
- return (jsx(ErrorState, { onRetry: onRetry }));
3218
+ return (jsx(ErrorState, { onRetry: onRetry, message: messages === null || messages === void 0 ? void 0 : messages.error, retryMessage: messages === null || messages === void 0 ? void 0 : messages.retry }));
2917
3219
  }
2918
3220
  // 3. Empty
2919
3221
  if (!(data === null || data === void 0 ? void 0 : data.length)) {
2920
3222
  if (renderEmpty)
2921
3223
  return render(renderEmpty);
2922
- return (jsx(EmptyState, {}));
3224
+ return (jsx(EmptyState, { message: messages === null || messages === void 0 ? void 0 : messages.empty }));
2923
3225
  }
2924
3226
  // 4. Success
2925
3227
  return jsx(Fragment, { children: children(data) });
2926
3228
  }
2927
3229
 
2928
- export { DAlert, DAvatar, DBadge, DBox, DBoxFile, DButton, DButtonIcon, DCard$1 as DCard, DCardBody, DCardFooter, DCardHeader, DCarousel$1 as DCarousel, DCarouselSlide, DChip, DCollapse, DContext, DContextProvider, DCreditCard, DCurrencyText, DDataStateWrapper, DDatePicker, DDropdown, DErrorBoundary, DIcon, DIconBase, ForwardedDInput as DInput, DInputCheck, ForwardedDInputCounter as DInputCounter, ForwardedDInputCurrency as DInputCurrency, ForwardedDInputMask as DInputMask, ForwardedDInputPassword as DInputPassword, ForwardedDInputPhone as DInputPhone, DInputPin, ForwardedDInputRange as DInputRange, DInputSelect, DInputSwitch, DLayout$1 as DLayout, DLayoutPane, DListGroup$1 as DListGroup, DListGroupItem, DModal$1 as DModal, DModalBody, DModalFooter, DModalHeader, DOffcanvas$1 as DOffcanvas, DOffcanvasBody, DOffcanvasFooter, DOffcanvasHeader, DOtp, DPaginator, DPasswordStrengthMeter, DPopover, DProgress, DSelect$1 as DSelect, DStepper, DStepper$2 as DStepperDesktop, DStepper$1 as DStepperMobile, DTabContent, DTabs$1 as DTabs, DTimeline, DToast$1 as DToast, DToastContainer, DTooltip, DVoucher, changeQueryString, checkMediaQuery, configureI8n as configureI18n, formatCurrency, getCssVariable, getQueryString, subscribeToMediaQuery, useDContext, useDPortalContext, useDToast, useDisableBodyScrollEffect, useDisableInputWheel, useFormatCurrency, useInputCurrency, useItemSelection, useMediaBreakpointUpLg, useMediaBreakpointUpMd, useMediaBreakpointUpSm, useMediaBreakpointUpXl, useMediaBreakpointUpXs, useMediaBreakpointUpXxl, useMediaQuery, useProvidedRefOrCreate, useStackState, useTabContext, validatePhoneNumber };
3230
+ function DConfirmModalUI({ entry }) {
3231
+ const { title = 'Are you sure you want to proceed?', message = 'Please confirm if you want to continue with this action.', confirmLabel = 'Confirm', cancelLabel = 'Cancel', confirmColor = 'primary', critical, onConfirmAction, onCloseAction, } = entry;
3232
+ const [confirmationCode, setConfirmationCode] = useState('');
3233
+ const [internalLoading, setInternalLoading] = useState(false);
3234
+ const isMountedRef = useRef(true);
3235
+ useEffect(() => {
3236
+ const cleanup = () => {
3237
+ isMountedRef.current = false;
3238
+ };
3239
+ return cleanup;
3240
+ }, []);
3241
+ const handleClose = useCallback(() => {
3242
+ onCloseAction();
3243
+ }, [onCloseAction]);
3244
+ const isConfirmDisabled = Boolean(internalLoading
3245
+ || (critical && confirmationCode !== critical.code));
3246
+ const handleConfirmClick = useCallback(() => {
3247
+ setInternalLoading(true);
3248
+ onConfirmAction()
3249
+ .catch(() => undefined)
3250
+ .finally(() => {
3251
+ // Only update state if the component is still mounted.
3252
+ // onConfirmAction() removes the entry on success, which unmounts this component.
3253
+ if (isMountedRef.current) {
3254
+ setInternalLoading(false);
3255
+ }
3256
+ });
3257
+ }, [onConfirmAction]);
3258
+ return (jsxs(DModal$1, { name: entry.id, size: "lg", className: `confirm-modal ${critical ? 'critical-modal' : ''}`, children: [jsx(DModal$1.Header, { onClose: handleClose, showCloseButton: true, children: jsx("h5", { className: "fw-bold", children: title }) }), jsxs(DModal$1.Body, { className: "py-3 px-5", children: [jsx("p", { className: "mb-4", children: message }), critical && (jsx(ForwardedDInput, { type: "text", label: critical.codeLabel || 'Confirmation code', placeholder: critical.inputPlaceholder, value: confirmationCode, onChange: (value) => setConfirmationCode(value), className: "mb-4", autoFocus: true }))] }), jsxs(DModal$1.Footer, { children: [jsx(DButton, { text: cancelLabel, variant: "outline", color: "secondary", onClick: handleClose, disabled: internalLoading }), jsx(DButton, { text: confirmLabel, color: confirmColor, onClick: handleConfirmClick, disabled: isConfirmDisabled, loading: internalLoading })] })] }));
3259
+ }
3260
+
3261
+ /**
3262
+ * Container that renders confirm modals into the specified portal node.
3263
+ *
3264
+ * Must be explicitly mounted by the user (typically as a sibling of app content
3265
+ * within `DContextProvider`). The container floats above the portal stack and
3266
+ * intercepts Escape key events to close the top confirm modal without affecting
3267
+ * underlying modals or overlays.
3268
+ *
3269
+ * @example
3270
+ * <DContextProvider>
3271
+ * <App />
3272
+ * <DConfirmModalContainer nodeId="d-portal" />
3273
+ * </DContextProvider>
3274
+ */
3275
+ function DConfirmModalContainer({ nodeId }) {
3276
+ const store = useConfirmModalStore();
3277
+ const [entries, setEntries] = useState([]);
3278
+ useEffect(() => {
3279
+ const unsubscribe = store.subscribe((next) => {
3280
+ setEntries(next);
3281
+ });
3282
+ return unsubscribe;
3283
+ }, [store]);
3284
+ // Capture Escape keydown to close the top confirm modal without affecting
3285
+ // the underlying portal stack (which also handles Escape).
3286
+ useEffect(() => {
3287
+ if (entries.length === 0) {
3288
+ return () => { };
3289
+ }
3290
+ const handleEscapeCapture = (event) => {
3291
+ if (event.key === 'Escape') {
3292
+ // Close the top (last) confirm modal
3293
+ entries[entries.length - 1].onCloseAction();
3294
+ // Prevent the event from reaching other handlers (e.g., DPortalContextProvider)
3295
+ event.stopPropagation();
3296
+ event.preventDefault();
3297
+ }
3298
+ };
3299
+ document.addEventListener('keydown', handleEscapeCapture, true);
3300
+ return () => {
3301
+ document.removeEventListener('keydown', handleEscapeCapture, true);
3302
+ };
3303
+ }, [entries]);
3304
+ const portalNode = document.getElementById(nodeId);
3305
+ if (!portalNode || entries.length === 0) {
3306
+ return null;
3307
+ }
3308
+ return createPortal(jsx(AnimatePresence, { children: entries.map((entry) => (jsxs(motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0, transition: { delay: 0.3 } }, transition: { duration: 0.15, ease: 'linear' }, children: [jsx("div", { className: "backdrop backdrop-confirm-modal", onClick: entry.onCloseAction, role: "presentation" }), jsx(DConfirmModalUI, { entry: entry })] }, entry.id))) }), portalNode);
3309
+ }
3310
+
3311
+ export { DAlert, DAvatar, DBadge, DBox, DBoxFile, DButton, DButtonIcon, DCard$1 as DCard, DCardBody, DCardFooter, DCardHeader, DCarousel$1 as DCarousel, DCarouselSlide, DChip, DCollapse, DConfirmModalContainer, DContext, DContextProvider, DCreditCard, DCurrencyText, DDataStateWrapper, DDatePicker, DDropdown, DErrorBoundary, DIcon, DIconBase, ForwardedDInput as DInput, DInputCheck, ForwardedDInputCounter as DInputCounter, ForwardedDInputCurrency as DInputCurrency, ForwardedDInputMask as DInputMask, ForwardedDInputPassword as DInputPassword, ForwardedDInputPhone as DInputPhone, DInputPin, ForwardedDInputRange as DInputRange, ForwardedDInputSearch as DInputSearch, DInputSelect, DInputSwitch, DLayout$1 as DLayout, DLayoutPane, DListGroup$1 as DListGroup, DListGroupItem, DModal$1 as DModal, DModalBody, DModalFooter, DModalHeader, DOffcanvas$1 as DOffcanvas, DOffcanvasBody, DOffcanvasFooter, DOffcanvasHeader, DOtp, DPaginator, DPasswordStrengthMeter, DPopover, DProgress, DSelect$1 as DSelect, DStepper, DStepper$2 as DStepperDesktop, DStepper$1 as DStepperMobile, DTabContent, DTabs$1 as DTabs, DTimeline, DToast$1 as DToast, DToastContainer, DTooltip, DVoucher, EmptyState, ErrorState, LoadingState, changeQueryString, checkMediaQuery, configureI8n as configureI18n, formatCurrency, getCssVariable, getQueryString, subscribeToMediaQuery, useConfirmModal, useDContext, useDPortalContext, useDToast, useDisableBodyScrollEffect, useDisableInputWheel, useFormatCurrency, useInputCurrency, useItemSelection, useMediaBreakpointUpLg, useMediaBreakpointUpMd, useMediaBreakpointUpSm, useMediaBreakpointUpXl, useMediaBreakpointUpXs, useMediaBreakpointUpXxl, useMediaQuery, useProvidedRefOrCreate, useStackState, useTabContext, validatePhoneNumber };
2929
3312
  //# sourceMappingURL=index.esm.js.map