@dynamic-framework/ui-react 2.5.2 → 2.7.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 (107) hide show
  1. package/README.md +4 -0
  2. package/dist/css/dynamic-ui.css +44 -12
  3. package/dist/css/dynamic-ui.min.css +2 -2
  4. package/dist/index.esm.js +538 -100
  5. package/dist/index.esm.js.map +1 -1
  6. package/dist/index.js +541 -97
  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/DCarousel.d.ts +9 -2
  19. package/dist/types/components/DCarousel/components/DCarouselSlide.d.ts +1 -1
  20. package/dist/types/components/DChip/DChip.d.ts +2 -1
  21. package/dist/types/components/DCollapse/DCollapse.d.ts +1 -1
  22. package/dist/types/components/DConfirmModal/DConfirmModalContainer.d.ts +21 -0
  23. package/dist/types/components/DConfirmModal/DConfirmModalUI.d.ts +7 -0
  24. package/dist/types/components/DConfirmModal/confirmModalStore.d.ts +6 -0
  25. package/dist/types/components/DConfirmModal/types.d.ts +45 -0
  26. package/dist/types/components/DCreditCard/DCreditCard.d.ts +2 -1
  27. package/dist/types/components/DCurrencyText/DCurrencyText.d.ts +2 -1
  28. package/dist/types/components/DDataStateWrapper/DDataStateWrapper.d.ts +8 -1
  29. package/dist/types/components/DDataStateWrapper/components/EmptyState.d.ts +2 -1
  30. package/dist/types/components/DDataStateWrapper/components/ErrorState.d.ts +2 -1
  31. package/dist/types/components/DDataStateWrapper/components/LoadingState.d.ts +2 -1
  32. package/dist/types/components/DDataStateWrapper/index.d.ts +4 -0
  33. package/dist/types/components/DDatePicker/DDatePicker.d.ts +2 -1
  34. package/dist/types/components/DDatePicker/components/DDatePickerHeaderSelector.d.ts +1 -1
  35. package/dist/types/components/DDatePicker/components/DDatePickerTime.d.ts +1 -1
  36. package/dist/types/components/DDropdown/DDropdown.d.ts +4 -1
  37. package/dist/types/components/DErrorBoundary/DErrorBoundary.d.ts +1 -1
  38. package/dist/types/components/DErrorBoundary/components/DefaultErrorBoundary.d.ts +2 -1
  39. package/dist/types/components/DIcon/DIcon.d.ts +4 -4
  40. package/dist/types/components/DIconBase/DIconBase.d.ts +5 -3
  41. package/dist/types/components/DIconBase/index.d.ts +1 -0
  42. package/dist/types/components/DInputCheck/DInputCheck.d.ts +1 -1
  43. package/dist/types/components/DInputPin/DInputPin.d.ts +2 -1
  44. package/dist/types/components/DInputSearch/DInputSearch.d.ts +11 -0
  45. package/dist/types/components/DInputSearch/index.d.ts +3 -0
  46. package/dist/types/components/DInputSelect/DInputSelect.d.ts +1 -1
  47. package/dist/types/components/DInputSwitch/DInputSwitch.d.ts +2 -1
  48. package/dist/types/components/DLayout/DLayout.d.ts +1 -1
  49. package/dist/types/components/DLayout/components/DLayoutPane.d.ts +1 -1
  50. package/dist/types/components/DListGroup/DListGroup.d.ts +1 -1
  51. package/dist/types/components/DListGroup/components/DListGroupItem.d.ts +1 -1
  52. package/dist/types/components/DModal/DModal.d.ts +1 -1
  53. package/dist/types/components/DModal/components/DModalBody.d.ts +1 -1
  54. package/dist/types/components/DModal/components/DModalFooter.d.ts +1 -1
  55. package/dist/types/components/DModal/components/DModalHeader.d.ts +1 -1
  56. package/dist/types/components/DOffcanvas/DOffcanvas.d.ts +15 -3
  57. package/dist/types/components/DOffcanvas/components/DOffcanvasBody.d.ts +1 -1
  58. package/dist/types/components/DOffcanvas/components/DOffcanvasFooter.d.ts +1 -1
  59. package/dist/types/components/DOffcanvas/components/DOffcanvasHeader.d.ts +1 -1
  60. package/dist/types/components/DOtp/DOtp.d.ts +1 -1
  61. package/dist/types/components/DOtp/components/OtpCountdown.d.ts +2 -1
  62. package/dist/types/components/DPaginator/DPaginator.d.ts +2 -1
  63. package/dist/types/components/DPasswordStrengthMeter/DPasswordStrengthMeter.d.ts +3 -1
  64. package/dist/types/components/DPasswordStrengthMeter/PasswordCheckItem.d.ts +2 -1
  65. package/dist/types/components/DPasswordStrengthMeter/PasswordCheckList.d.ts +2 -1
  66. package/dist/types/components/DPasswordStrengthMeter/PasswordStrength.d.ts +2 -1
  67. package/dist/types/components/DPopover/DPopover.d.ts +1 -1
  68. package/dist/types/components/DProgress/DProgress.d.ts +2 -1
  69. package/dist/types/components/DSelect/DSelect.d.ts +2 -1
  70. package/dist/types/components/DSelect/components/DSelectClearIndicator.d.ts +2 -1
  71. package/dist/types/components/DSelect/components/DSelectDropdownIndicator.d.ts +2 -1
  72. package/dist/types/components/DSelect/components/DSelectLoadingIndicator.d.ts +2 -1
  73. package/dist/types/components/DSelect/components/DSelectMultiValueRemove.d.ts +2 -1
  74. package/dist/types/components/DSelect/components/DSelectOptionCheck.d.ts +2 -1
  75. package/dist/types/components/DSelect/components/DSelectOptionEmoji.d.ts +2 -1
  76. package/dist/types/components/DSelect/components/DSelectOptionIcon.d.ts +2 -1
  77. package/dist/types/components/DSelect/components/DSelectPlaceholder.d.ts +2 -1
  78. package/dist/types/components/DSelect/components/DSelectSingleValueEmoji.d.ts +2 -1
  79. package/dist/types/components/DSelect/components/DSelectSingleValueEmojiText.d.ts +2 -1
  80. package/dist/types/components/DSelect/components/DSelectSingleValueIconText.d.ts +2 -1
  81. package/dist/types/components/DStepper/DStepper.d.ts +2 -1
  82. package/dist/types/components/DStepperDesktop/DStepperDesktop.d.ts +2 -1
  83. package/dist/types/components/DStepperMobile/DStepperMobile.d.ts +2 -1
  84. package/dist/types/components/DTabs/DTabs.d.ts +1 -1
  85. package/dist/types/components/DTabs/components/DTabContent.d.ts +1 -1
  86. package/dist/types/components/DTimeline/DTimeline.d.ts +1 -1
  87. package/dist/types/components/DToast/DToast.d.ts +1 -1
  88. package/dist/types/components/DToast/components/DToastBody.d.ts +1 -1
  89. package/dist/types/components/DToast/components/DToastHeader.d.ts +1 -1
  90. package/dist/types/components/DToastContainer/DToastContainer.d.ts +1 -1
  91. package/dist/types/components/DTooltip/DTooltip.d.ts +1 -1
  92. package/dist/types/components/DVoucher/DVoucher.d.ts +1 -1
  93. package/dist/types/components/index.d.ts +6 -1
  94. package/dist/types/components/interface.d.ts +6 -4
  95. package/dist/types/contexts/DContext.d.ts +27 -4
  96. package/dist/types/contexts/DPortalContext.d.ts +1 -1
  97. package/dist/types/hooks/index.d.ts +2 -0
  98. package/dist/types/hooks/useConfirmModal.d.ts +34 -0
  99. package/package.json +6 -2
  100. package/src/style/_shame.scss +13 -0
  101. package/src/style/abstracts/variables/_forms.scss +1 -1
  102. package/src/style/abstracts/variables/_list-group.scss +1 -1
  103. package/src/style/abstracts/variables/_offcanvas.scss +8 -0
  104. package/src/style/base/_backdrop.scss +7 -0
  105. package/src/style/components/_d-carousel.scss +20 -2
  106. package/src/style/components/_d-modal.scss +8 -0
  107. package/src/style/root/_root.scss +31 -0
package/dist/index.esm.js CHANGED
@@ -1,12 +1,13 @@
1
1
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
2
  import classNames from 'classnames';
3
- import React, { useEffect, useState, useCallback, useMemo, useContext, createContext, useLayoutEffect, useSyncExternalStore, forwardRef, useId, useRef, isValidElement, cloneElement, createRef } from 'react';
3
+ import React, { useEffect, useState, useCallback, useMemo, useContext, createContext, useLayoutEffect, useSyncExternalStore, createElement, forwardRef, useId, useRef, isValidElement, cloneElement, createRef } from 'react';
4
4
  import { __rest } from 'tslib';
5
5
  import * as LucideIcons from 'lucide-react';
6
+ import { isValidElementType } from 'react-is';
6
7
  import { createPortal } from 'react-dom';
7
8
  import { AnimatePresence, motion } from 'framer-motion';
8
9
  import { fromEvent } from 'file-selector';
9
- import { SplideSlide, Splide } from '@splidejs/react-splide';
10
+ import { SplideSlide, Splide, SplideTrack } from '@splidejs/react-splide';
10
11
  import currency from 'currency.js';
11
12
  import DatePicker from 'react-datepicker';
12
13
  import { getYear, format, getMonth } from 'date-fns';
@@ -58,7 +59,7 @@ function useDisableBodyScrollEffect(disable) {
58
59
  timer = window.setTimeout(() => {
59
60
  unlock();
60
61
  observer === null || observer === void 0 ? void 0 : observer.disconnect();
61
- }, 3000);
62
+ }, 300);
62
63
  }
63
64
  else {
64
65
  unlock();
@@ -143,7 +144,7 @@ function getKeyboardFocusableElements(container) {
143
144
  const DPortalContext = createContext(undefined);
144
145
  function DPortalContextProvider({ portalName, children, availablePortals, }) {
145
146
  const { created } = usePortal(portalName);
146
- const [stack, { push, pop, isEmpty }] = useStackState([]);
147
+ const [stack, { push, pop }] = useStackState([]);
147
148
  useDisableBodyScrollEffect(Boolean(stack.length));
148
149
  const openPortal = useCallback(
149
150
  // eslint-disable-next-line prefer-arrow-callback
@@ -164,11 +165,9 @@ function DPortalContextProvider({ portalName, children, availablePortals, }) {
164
165
  (_a = document.activeElement) === null || _a === void 0 ? void 0 : _a.blur();
165
166
  }, [availablePortals, push]);
166
167
  const closePortal = useCallback(() => {
167
- if (isEmpty()) {
168
- return;
169
- }
168
+ // pop() is safe on empty stacks, so close remains idempotent.
170
169
  pop();
171
- }, [isEmpty, pop]);
170
+ }, [pop]);
172
171
  const publicStack = useMemo(() => stack.map(({ name, payload }) => ({ name, payload })), [stack]);
173
172
  const value = useMemo(() => ({
174
173
  stack: publicStack,
@@ -259,6 +258,40 @@ function useDPortalContext() {
259
258
  return context;
260
259
  }
261
260
 
261
+ function createConfirmModalStore() {
262
+ const listeners = new Set();
263
+ let entries = [];
264
+ function notify() {
265
+ listeners.forEach((listener) => listener([...entries]));
266
+ }
267
+ const store = {
268
+ subscribe(listener) {
269
+ listeners.add(listener);
270
+ listener([...entries]);
271
+ return () => {
272
+ listeners.delete(listener);
273
+ };
274
+ },
275
+ push(entry) {
276
+ entries = [...entries, entry];
277
+ notify();
278
+ },
279
+ remove(id) {
280
+ entries = entries.filter((e) => e.id !== id);
281
+ notify();
282
+ },
283
+ };
284
+ return store;
285
+ }
286
+ const ConfirmModalStoreContext = createContext(null);
287
+ function useConfirmModalStore() {
288
+ const store = useContext(ConfirmModalStoreContext);
289
+ if (!store) {
290
+ throw new Error('useConfirmModal must be used within a <DContextProvider>.');
291
+ }
292
+ return store;
293
+ }
294
+
262
295
  function getCssVariable(variable) {
263
296
  const computedStyle = getComputedStyle(document.documentElement);
264
297
  return computedStyle.getPropertyValue(variable).trim();
@@ -277,6 +310,7 @@ const DEFAULT_STATE = {
277
310
  familyPrefix: 'bi-',
278
311
  materialStyle: false,
279
312
  },
313
+ iconRegistry: undefined,
280
314
  iconMap: {
281
315
  x: 'X',
282
316
  xLg: 'X',
@@ -317,14 +351,37 @@ const DContext = createContext(DEFAULT_STATE);
317
351
  * component to configure icons, currency, language, and portal settings
318
352
  * for all descendant Dynamic UI components.
319
353
  *
354
+ * To enable confirmation modals you must also mount `DConfirmModalContainer`
355
+ * somewhere inside this provider (typically right before the closing tag of
356
+ * your root layout), similar to how `DToastContainer` works:
357
+ *
358
+ * ```tsx
359
+ * // Default: portalName="d-portal"
360
+ * <DContextProvider>
361
+ * <App />
362
+ * <DConfirmModalContainer nodeId="d-portal" />
363
+ * </DContextProvider>
364
+ * ```
365
+ *
366
+ * If you customize `portalName`, match it in `DConfirmModalContainer.nodeId`:
367
+ *
368
+ * ```tsx
369
+ * // Custom portalName
370
+ * <DContextProvider portalName="my-custom-portal">
371
+ * <App />
372
+ * <DConfirmModalContainer nodeId="my-custom-portal" />
373
+ * </DContextProvider>
374
+ * ```
375
+ *
320
376
  * @template T - Map of portal name → payload shape (e.g. `{ modal: { title: string } }`).
321
377
  * Pass it once at the top level: `<DContextProvider<MyPortals> ...>`.
322
378
  */
323
- function DContextProvider({ language = DEFAULT_STATE.language, currency = DEFAULT_STATE.currency, icon = DEFAULT_STATE.icon, iconMap = DEFAULT_STATE.iconMap, portalName = 'd-portal', availablePortals, children, }) {
379
+ 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, }) {
324
380
  const [internalContext, setInternalContext,] = useState({
325
381
  language,
326
382
  currency,
327
383
  icon,
384
+ iconRegistry,
328
385
  iconMap,
329
386
  breakpoints: DEFAULT_STATE.breakpoints,
330
387
  });
@@ -342,7 +399,8 @@ function DContextProvider({ language = DEFAULT_STATE.language, currency = DEFAUL
342
399
  });
343
400
  }, [setContext]);
344
401
  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 }) }));
402
+ const confirmStore = useMemo(() => createConfirmModalStore(), []);
403
+ return (jsx(ConfirmModalStoreContext.Provider, { value: confirmStore, children: jsx(DContext.Provider, { value: value, children: jsx(DPortalContextProvider, { portalName: portalName, availablePortals: availablePortals, children: children }) }) }));
346
404
  }
347
405
  /**
348
406
  * Returns the Dynamic UI context value set by `DContextProvider`.
@@ -440,17 +498,21 @@ function useResponsiveProp(useListener = false) {
440
498
  return { responsivePropValue };
441
499
  }
442
500
 
501
+ function isIconComponent(value) {
502
+ return typeof value !== 'string' && isValidElementType(value);
503
+ }
443
504
  function DIconBase({ icon, color, style, className, size, useListenerSize = false, hasCircle = false, materialStyle = false, familyClass, familyPrefix, strokeWidth = 2, dataAttributes, }) {
444
505
  // If materialStyle is true, use Material Design icons (legacy)
445
- const useMaterialIcons = materialStyle;
506
+ const isStringIcon = typeof icon === 'string';
507
+ const useMaterialIcons = materialStyle && isStringIcon;
446
508
  // Get Lucide icon component
447
509
  const LucideIcon = useMemo(() => {
448
- if (useMaterialIcons)
510
+ if (!isStringIcon || useMaterialIcons)
449
511
  return null;
450
512
  // Try to find the icon in Lucide (expects PascalCase)
451
513
  const icons = LucideIcons;
452
514
  return icons[icon] || null;
453
- }, [icon, useMaterialIcons]);
515
+ }, [icon, isStringIcon, useMaterialIcons]);
454
516
  const { responsivePropValue } = useResponsiveProp(useListenerSize);
455
517
  const resolvedSize = useMemo(() => {
456
518
  if (!size)
@@ -470,24 +532,35 @@ function DIconBase({ icon, color, style, className, size, useListenerSize = fals
470
532
  }, [resolvedSize]);
471
533
  // Render Material Design icon (legacy support)
472
534
  if (useMaterialIcons) {
473
- return (jsx("i", Object.assign({ className: classNames(generateClasses, familyClass), style: generateStyleVariables }, dataAttributes, { children: icon })));
535
+ return (jsx("i", Object.assign({ className: classNames(generateClasses, familyClass), style: generateStyleVariables }, dataAttributes, { children: isStringIcon ? icon : null })));
536
+ }
537
+ if (isIconComponent(icon)) {
538
+ return (jsx("span", Object.assign({ className: classNames(generateClasses), style: generateStyleVariables }, dataAttributes, { children: createElement(icon, {
539
+ width: resolvedSize || 24,
540
+ height: resolvedSize || 24,
541
+ strokeWidth,
542
+ }) })));
474
543
  }
475
544
  // Render Lucide icon
476
545
  if (!LucideIcon) {
477
- if (familyClass && familyPrefix) {
546
+ if (isStringIcon && familyClass && familyPrefix) {
478
547
  return (jsx("i", Object.assign({ className: classNames(generateClasses, familyClass, `${familyPrefix}${icon}`), style: generateStyleVariables }, dataAttributes)));
479
548
  }
480
549
  // eslint-disable-next-line no-console
481
- console.warn(`Icon "${icon}" not found in Lucide. Make sure to use PascalCase names (e.g., "Home", "User", "Settings")`);
550
+ console.warn(`Icon "${String(icon)}" not found in Lucide. Make sure to use PascalCase names (e.g., "Home", "User", "Settings")`);
482
551
  return (jsx("span", Object.assign({ className: classNames(generateClasses), style: generateStyleVariables }, dataAttributes, { children: "?" })));
483
552
  }
484
553
  return (jsx("span", Object.assign({ className: classNames(generateClasses), style: generateStyleVariables }, dataAttributes, { children: jsx(LucideIcon, { size: iconSize || 24, strokeWidth: strokeWidth }) })));
485
554
  }
486
555
 
487
556
  function DIcon(_a) {
488
- var { familyClass: propFamilyClass, familyPrefix: propFamilyPrefix, materialStyle: propMaterialStyle } = _a, props = __rest(_a, ["familyClass", "familyPrefix", "materialStyle"]);
489
- const { icon: { familyClass, familyPrefix, materialStyle, }, } = useDContext();
490
- return (jsx(DIconBase, Object.assign({ familyClass: propFamilyClass || familyClass, familyPrefix: propFamilyPrefix || familyPrefix, materialStyle: propMaterialStyle || materialStyle }, props)));
557
+ var { icon, familyClass: propFamilyClass, familyPrefix: propFamilyPrefix, materialStyle: propMaterialStyle } = _a, props = __rest(_a, ["icon", "familyClass", "familyPrefix", "materialStyle"]);
558
+ const { icon: { familyClass, familyPrefix, materialStyle, }, iconRegistry, } = useDContext();
559
+ const registryIcon = typeof icon === 'string'
560
+ ? iconRegistry === null || iconRegistry === void 0 ? void 0 : iconRegistry[icon]
561
+ : undefined;
562
+ const resolvedIcon = registryIcon || icon;
563
+ return (jsx(DIconBase, Object.assign({ icon: resolvedIcon, familyClass: propFamilyClass !== null && propFamilyClass !== void 0 ? propFamilyClass : familyClass, familyPrefix: propFamilyPrefix !== null && propFamilyPrefix !== void 0 ? propFamilyPrefix : familyPrefix, materialStyle: propMaterialStyle !== null && propMaterialStyle !== void 0 ? propMaterialStyle : materialStyle }, props)));
491
564
  }
492
565
 
493
566
  function DAlert({ color = 'success', icon: iconProp, iconFamilyClass, iconFamilyPrefix, iconMaterialStyle = false, iconClose: iconCloseProp, iconCloseFamilyClass, iconCloseFamilyPrefix, iconCloseMaterialStyle = false, showClose, onClose, children, id, className, style, dataAttributes, }) {
@@ -1037,7 +1110,7 @@ function DBoxFile(_a) {
1037
1110
  }
1038
1111
 
1039
1112
  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']);
1113
+ 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
1114
  // Responsive size resolution using useResponsiveProp
1042
1115
  const { responsivePropValue } = useResponsiveProp(true);
1043
1116
  const resolvedSize = useMemo(() => {
@@ -1052,9 +1125,9 @@ const DButton = forwardRef((props, ref) => {
1052
1125
  const isDisabled = useMemo(() => disabled || loading, [disabled, loading]);
1053
1126
  const content = useMemo(() => children || text, [children, text]);
1054
1127
  const classes = useMemo(() => {
1055
- const variantClass = variant
1056
- ? `btn-${variant}-${color}`
1057
- : `btn-${color}`;
1128
+ const variantClass = variant === 'solid'
1129
+ ? `btn-${color}`
1130
+ : `btn-${variant}-${color}`;
1058
1131
  return {
1059
1132
  btn: true,
1060
1133
  [variantClass]: true,
@@ -1112,9 +1185,9 @@ DButton.displayName = 'DButton';
1112
1185
  function DButtonIcon(_a) {
1113
1186
  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
1187
  const generateClasses = useMemo(() => {
1115
- const variantClass = variant
1116
- ? `btn-${variant}-${color}`
1117
- : `btn-${color}`;
1188
+ const variantClass = !variant || variant === 'solid'
1189
+ ? `btn-${color}`
1190
+ : `btn-${variant}-${color}`;
1118
1191
  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
1192
  }, [variant, color, size, state, loading]);
1120
1193
  const isDisabled = useMemo(() => (state === 'disabled' || loading || disabled), [state, loading, disabled]);
@@ -1167,8 +1240,14 @@ function DCarouselSlide(_a) {
1167
1240
  return (jsx(SplideSlide, Object.assign({ className: classNames('d-carousel-slide', className) }, props)));
1168
1241
  }
1169
1242
 
1243
+ 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';
1244
+ function DefaultArrowIcon() {
1245
+ return (jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 40 40", width: 40, height: 40, focusable: "false", "aria-hidden": "true", children: jsx("path", { d: DEFAULT_ARROW_PATH }) }));
1246
+ }
1170
1247
  function DCarousel(_a, ref) {
1171
- var { children, className, style, options, dataAttributes } = _a, props = __rest(_a, ["children", "className", "style", "options", "dataAttributes"]);
1248
+ var { children, className, style, options, dataAttributes, iconArrowLeft, iconArrowRight, hasTrack: propsHasTrack } = _a, props = __rest(_a, ["children", "className", "style", "options", "dataAttributes", "iconArrowLeft", "iconArrowRight", "hasTrack"]);
1249
+ // Explicit `options.arrows === false` always wins, even when icon props are set.
1250
+ const hasCustomArrows = Boolean((iconArrowLeft || iconArrowRight) && (options === null || options === void 0 ? void 0 : options.arrows) !== false);
1172
1251
  return (jsx(Splide, Object.assign({ className: classNames('d-carousel', className), style: style, ref: ref, options: Object.assign(Object.assign({}, options), { classes: {
1173
1252
  // Arrows
1174
1253
  arrows: 'splide__arrows d-carousel-arrows',
@@ -1178,7 +1257,11 @@ function DCarousel(_a, ref) {
1178
1257
  // Paginator
1179
1258
  pagination: 'splide__pagination d-carousel-pagination',
1180
1259
  page: 'splide__pagination__page d-carousel-pagination-page',
1181
- } }) }, dataAttributes, props, { children: children })));
1260
+ } }) }, dataAttributes, props, {
1261
+ // Rendering our own arrows requires taking over the track wrapper, so this
1262
+ // must be applied after `...props` to prevent a consumer-provided `hasTrack`
1263
+ // from re-enabling Splide's automatic track when custom arrows are active.
1264
+ hasTrack: hasCustomArrows ? false : propsHasTrack, children: hasCustomArrows ? (jsxs(Fragment, { children: [jsx(SplideTrack, { children: children }), jsxs("div", { className: "splide__arrows d-carousel-arrows", children: [jsx("button", { type: "button", className: "splide__arrow splide__arrow--prev d-carousel-arrow d-carousel-arrow-prev", children: iconArrowLeft ? jsx(DIcon, Object.assign({}, iconArrowLeft)) : jsx(DefaultArrowIcon, {}) }), jsx("button", { type: "button", className: "splide__arrow splide__arrow--next d-carousel-arrow d-carousel-arrow-next", children: iconArrowRight ? jsx(DIcon, Object.assign({}, iconArrowRight)) : jsx(DefaultArrowIcon, {}) })] })] })) : children })));
1182
1265
  }
1183
1266
  const ForwardedDCarousel = forwardRef(DCarousel);
1184
1267
  ForwardedDCarousel.displayName = 'DCarousel';
@@ -1406,6 +1489,18 @@ function DDatePickerHeaderSelector({ date, changeYear, changeMonth, decreaseMont
1406
1489
 
1407
1490
  function DDatePicker(_a) {
1408
1491
  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"]);
1492
+ // Some test runtimes can resolve react-datepicker as a module object.
1493
+ // Normalize to a renderable component for both function and { default } shapes.
1494
+ const SafeDatePicker = useMemo(() => {
1495
+ const interopDefault = DatePicker.default;
1496
+ if (interopDefault !== undefined) {
1497
+ return interopDefault;
1498
+ }
1499
+ if (typeof DatePicker === 'function') {
1500
+ return DatePicker;
1501
+ }
1502
+ return DatePicker;
1503
+ }, []);
1409
1504
  const pickerType = useMemo(() => {
1410
1505
  if (props.showQuarterYearPicker)
1411
1506
  return PickerType.Quarter;
@@ -1433,7 +1528,7 @@ function DDatePicker(_a) {
1433
1528
  ]);
1434
1529
  const defaultRenderCustomHeader = useCallback((headerProps) => (jsx(DatePickerHeader, Object.assign({}, headerProps))), [DatePickerHeader]);
1435
1530
  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 })) })));
1531
+ 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
1532
  }
1438
1533
 
1439
1534
  function DLayoutPane({ className, style, children, cols, colsXs, colsSm, colsMd, colsLg, colsXl, colsXxl, dataAttributes, }) {
@@ -1463,6 +1558,55 @@ var DLayout$1 = Object.assign(DLayout, {
1463
1558
  Pane: DLayoutPane,
1464
1559
  });
1465
1560
 
1561
+ function normalizeValue(value) {
1562
+ return value !== null && value !== void 0 ? value : '';
1563
+ }
1564
+ function DInputSearch(_a, ref) {
1565
+ var { debounceMs = 300, onChange, onImmediateChange, value, defaultValue, placeholder = 'Search...' } = _a, props = __rest(_a, ["debounceMs", "onChange", "onImmediateChange", "value", "defaultValue", "placeholder"]);
1566
+ const inputRef = useProvidedRefOrCreate(ref);
1567
+ const isControlled = value !== undefined;
1568
+ const onChangeRef = useRef(onChange);
1569
+ onChangeRef.current = onChange;
1570
+ const [internalValue, setInternalValue] = useState(normalizeValue(isControlled ? value : defaultValue));
1571
+ // null = no keystroke has occurred yet (skip debounce on mount)
1572
+ const [pendingValue, setPendingValue] = useState(null);
1573
+ useEffect(() => {
1574
+ if (!isControlled)
1575
+ return;
1576
+ setInternalValue(normalizeValue(value));
1577
+ }, [isControlled, value]);
1578
+ const handleChange = useCallback((nextValue) => {
1579
+ if (!isControlled) {
1580
+ setInternalValue(nextValue);
1581
+ }
1582
+ setPendingValue(nextValue);
1583
+ onImmediateChange === null || onImmediateChange === void 0 ? void 0 : onImmediateChange(nextValue);
1584
+ }, [isControlled, onImmediateChange]);
1585
+ useEffect(() => {
1586
+ var _a;
1587
+ let timeoutId;
1588
+ if (pendingValue !== null) {
1589
+ if (debounceMs <= 0) {
1590
+ (_a = onChangeRef.current) === null || _a === void 0 ? void 0 : _a.call(onChangeRef, pendingValue);
1591
+ setPendingValue(null);
1592
+ }
1593
+ else {
1594
+ timeoutId = window.setTimeout(() => {
1595
+ var _a;
1596
+ (_a = onChangeRef.current) === null || _a === void 0 ? void 0 : _a.call(onChangeRef, pendingValue);
1597
+ setPendingValue(null);
1598
+ }, debounceMs);
1599
+ }
1600
+ }
1601
+ return () => {
1602
+ window.clearTimeout(timeoutId);
1603
+ };
1604
+ }, [debounceMs, pendingValue]);
1605
+ return (jsx(ForwardedDInput, Object.assign({ ref: inputRef }, props, { type: "search", value: internalValue, onChange: handleChange, placeholder: placeholder })));
1606
+ }
1607
+ const ForwardedDInputSearch = forwardRef(DInputSearch);
1608
+ ForwardedDInputSearch.displayName = 'DInputSearch';
1609
+
1466
1610
  function DInputMask(props, ref) {
1467
1611
  return (jsx(InputMask, Object.assign({ ref: ref, component: ForwardedDInput }, props)));
1468
1612
  }
@@ -1567,6 +1711,64 @@ function useItemSelection({ getItemIdentifier: getItemIdentifierProp, previousSe
1567
1711
  };
1568
1712
  }
1569
1713
 
1714
+ /**
1715
+ * Hook to display a confirmation modal.
1716
+ *
1717
+ * Renders into the portal node specified by `DConfirmModalContainer` (typically `#d-portal`).
1718
+ * The confirm modal floats above the portal stack and handles its own Escape key,
1719
+ * preventing interference with underlying modals.
1720
+ *
1721
+ * Requires both `DContextProvider` wrapping your application AND an explicit
1722
+ * `<DConfirmModalContainer nodeId="d-portal" />` mounted in your app.
1723
+ *
1724
+ * @example
1725
+ * function App() {
1726
+ * return (
1727
+ * <DContextProvider>
1728
+ * <YourContent />
1729
+ * <DConfirmModalContainer nodeId="d-portal" />
1730
+ * </DContextProvider>
1731
+ * );
1732
+ * }
1733
+ *
1734
+ * function MyComponent() {
1735
+ * const confirm = useConfirmModal({
1736
+ * title: 'Delete Account',
1737
+ * message: 'This action cannot be undone.',
1738
+ * critical: { code: 'DELETE ACCOUNT' },
1739
+ * onConfirm: async () => { await deleteAccount(); },
1740
+ * });
1741
+ *
1742
+ * return <DButton onClick={confirm.open} text="Delete" color="danger" />;
1743
+ * }
1744
+ */
1745
+ function useConfirmModal(config) {
1746
+ const store = useConfirmModalStore();
1747
+ const idRef = useRef(`confirm-modal-${Math.random().toString(36).slice(2)}`);
1748
+ const close = useCallback(() => {
1749
+ store.remove(idRef.current);
1750
+ queueMicrotask(() => {
1751
+ var _a;
1752
+ (_a = config.onClose) === null || _a === void 0 ? void 0 : _a.call(config);
1753
+ });
1754
+ }, [store, config]);
1755
+ const open = useCallback(() => {
1756
+ const handleConfirmAction = async () => {
1757
+ try {
1758
+ await config.onConfirm();
1759
+ store.remove(idRef.current);
1760
+ }
1761
+ catch (_a) {
1762
+ // Keep modal open on error
1763
+ }
1764
+ };
1765
+ // Remove any existing entry with this id to prevent duplicates on re-entrancy
1766
+ store.remove(idRef.current);
1767
+ store.push(Object.assign(Object.assign({}, config), { id: idRef.current, onConfirmAction: handleConfirmAction, onCloseAction: close }));
1768
+ }, [store, config, close]);
1769
+ return { open };
1770
+ }
1771
+
1570
1772
  function DInputCounter(_a, ref) {
1571
1773
  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
1774
  const { handleOnWheel, } = useDisableInputWheel(ref);
@@ -1698,7 +1900,7 @@ const DEFAULT_VALIDATION_MESSAGES = {
1698
1900
  notMatch: 'The password confirmation and the new password do not match.',
1699
1901
  };
1700
1902
  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, }) {
1903
+ 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
1904
  const [password, setPassword] = useState(value);
1703
1905
  useEffect(() => {
1704
1906
  setPassword(value);
@@ -1707,7 +1909,7 @@ function DPasswordStrengthMeter({ id, label = 'Password', placeholder, value = '
1707
1909
  setPassword(newValue);
1708
1910
  onChange === null || onChange === void 0 ? void 0 : onChange(newValue);
1709
1911
  };
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 })] })));
1912
+ 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
1913
  }
1712
1914
 
1713
1915
  function DInputCheck(_a) {
@@ -2122,10 +2324,37 @@ const defaultTransition = {
2122
2324
  ease: 'easeInOut',
2123
2325
  duration: 0.3,
2124
2326
  };
2125
- function DOffcanvas({ name, className, style, staticBackdrop, scrollable, openFrom = 'end', transition, children, dataAttributes, }) {
2327
+ function DOffcanvas({ name, className, style, staticBackdrop, scrollable, openFrom = 'end', width, height, transition, children, dataAttributes, }) {
2328
+ // Only subscribe to breakpoint-change listeners when a responsive object is
2329
+ // actually provided, avoiding unnecessary matchMedia subscriptions for the
2330
+ // common case of plain string values.
2331
+ const hasResponsiveProp = typeof openFrom === 'object'
2332
+ || typeof width === 'object'
2333
+ || typeof height === 'object';
2334
+ const { responsivePropValue } = useResponsiveProp(hasResponsiveProp);
2335
+ const resolvedOpenFrom = useMemo(() => {
2336
+ var _a;
2337
+ if (typeof openFrom === 'string')
2338
+ return openFrom;
2339
+ return (_a = responsivePropValue(openFrom)) !== null && _a !== void 0 ? _a : 'end';
2340
+ }, [responsivePropValue, openFrom]);
2341
+ const resolvedWidth = useMemo(() => {
2342
+ if (!width)
2343
+ return undefined;
2344
+ if (typeof width === 'string')
2345
+ return width;
2346
+ return responsivePropValue(width);
2347
+ }, [responsivePropValue, width]);
2348
+ const resolvedHeight = useMemo(() => {
2349
+ if (!height)
2350
+ return undefined;
2351
+ if (typeof height === 'string')
2352
+ return height;
2353
+ return responsivePropValue(height);
2354
+ }, [responsivePropValue, height]);
2126
2355
  return (jsx(motion.div, Object.assign({ className: classNames('offcanvas portal show', {
2127
- [`offcanvas-${openFrom}`]: openFrom,
2128
- }, 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 && ({
2356
+ [`offcanvas-${resolvedOpenFrom}`]: resolvedOpenFrom,
2357
+ }, 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 && ({
2129
2358
  [`data-${PREFIX_BS}backdrop`]: 'static',
2130
2359
  [`data-${PREFIX_BS}keyboard`]: 'false',
2131
2360
  }), scrollable && ({
@@ -2611,82 +2840,210 @@ function DCreditCard({ brand = 'visa', name, number, holderText = 'Card Holder',
2611
2840
  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
2841
  }
2613
2842
 
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, }) {
2843
+ const getItemClass = (action) => classNames({
2844
+ 'dropdown-item d-flex align-items-center': true,
2845
+ [`dropdown-item-${action.color}`]: !!action.color,
2846
+ disabled: action.disabled,
2847
+ });
2848
+ function DDropdown({ actions, dropdownToggle, className, classNameMenu, asPortal = false, placement = 'auto', }) {
2623
2849
  const [open, setOpen] = useState(false);
2850
+ const [position, setPosition] = useState('down');
2851
+ const [menuCoords, setMenuCoords] = useState(null);
2852
+ // Used in default (non-portal) mode: wraps the entire dropdown
2624
2853
  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;
2854
+ // Used in portal mode: ref on the toggle wrapper to read its viewport coords
2855
+ const toggleRef = useRef(null);
2856
+ // Ref on the rendered <ul> — used for outside-click detection in portal mode
2857
+ const menuRef = useRef(null);
2858
+ const toggle = () => {
2859
+ setMenuCoords(null);
2860
+ setOpen((prev) => !prev);
2861
+ };
2862
+ const resolvedInlinePosition = placement === 'auto' ? position : placement;
2863
+ // Compute fixed coords from the toggle's bounding rect (portal mode only)
2864
+ const computePortalCoords = useCallback(() => {
2865
+ if (!toggleRef.current)
2866
+ return;
2867
+ const rect = toggleRef.current.getBoundingClientRect();
2868
+ const spaceRight = window.innerWidth - rect.right;
2869
+ const spaceBottom = window.innerHeight - rect.bottom;
2870
+ const spaceTop = rect.top;
2871
+ let resolvedPlacement;
2872
+ if (placement === 'auto') {
2641
2873
  if (spaceBottom < 150) {
2642
- setPosition('up');
2874
+ resolvedPlacement = 'up';
2643
2875
  }
2644
2876
  else if (spaceRight < 150) {
2645
- setPosition('start');
2877
+ resolvedPlacement = 'start';
2646
2878
  }
2647
2879
  else {
2648
- setPosition('down');
2880
+ resolvedPlacement = 'down';
2649
2881
  }
2650
2882
  }
2651
- }, [open]);
2652
- let ToggleElement;
2653
- if (dropdownToggle) {
2654
- if (typeof dropdownToggle === 'function') {
2655
- ToggleElement = dropdownToggle({ open, toggle: () => setOpen(!open) });
2883
+ else {
2884
+ resolvedPlacement = placement;
2885
+ }
2886
+ const openStart = resolvedPlacement === 'start';
2887
+ const openEnd = resolvedPlacement === 'end';
2888
+ const openUp = resolvedPlacement === 'up';
2889
+ let availableHeight = spaceBottom;
2890
+ if (openUp) {
2891
+ availableHeight = spaceTop;
2892
+ }
2893
+ else if (openStart || openEnd) {
2894
+ availableHeight = window.innerHeight - rect.top;
2895
+ }
2896
+ const verticalCoords = openUp
2897
+ ? { bottom: window.innerHeight - rect.top + 4 }
2898
+ : { top: openStart || openEnd ? rect.top : rect.bottom + 4 };
2899
+ let horizontalCoords;
2900
+ if (openStart) {
2901
+ horizontalCoords = { right: window.innerWidth - rect.left + 4 };
2902
+ }
2903
+ else if (openEnd) {
2904
+ horizontalCoords = { left: rect.right + 4 };
2656
2905
  }
2657
2906
  else {
2658
- ToggleElement = dropdownToggle;
2907
+ horizontalCoords = { left: rect.left };
2659
2908
  }
2909
+ setMenuCoords(Object.assign(Object.assign(Object.assign({}, verticalCoords), horizontalCoords), { minWidth: Math.max(rect.width, 160), maxHeight: `${Math.max(availableHeight - 12, 120)}px` }));
2910
+ }, [placement]);
2911
+ // Outside-click: default mode
2912
+ useEffect(() => {
2913
+ if (asPortal)
2914
+ return () => { };
2915
+ const handler = (event) => {
2916
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
2917
+ setOpen(false);
2918
+ }
2919
+ };
2920
+ document.addEventListener('mousedown', handler);
2921
+ return () => document.removeEventListener('mousedown', handler);
2922
+ }, [asPortal]);
2923
+ // Outside-click: portal mode (must check both toggle and floating menu)
2924
+ useEffect(() => {
2925
+ if (!asPortal || !open)
2926
+ return () => { };
2927
+ const handler = (event) => {
2928
+ var _a, _b;
2929
+ const target = event.target;
2930
+ 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))) {
2931
+ setOpen(false);
2932
+ }
2933
+ };
2934
+ document.addEventListener('mousedown', handler);
2935
+ return () => document.removeEventListener('mousedown', handler);
2936
+ }, [asPortal, open]);
2937
+ // Auto-position: default mode
2938
+ useEffect(() => {
2939
+ if (asPortal || !open || !dropdownRef.current || placement !== 'auto')
2940
+ return;
2941
+ const rect = dropdownRef.current.getBoundingClientRect();
2942
+ const spaceBottom = window.innerHeight - rect.bottom;
2943
+ const spaceRight = window.innerWidth - rect.right;
2944
+ if (spaceBottom < 150)
2945
+ setPosition('up');
2946
+ else if (spaceRight < 150)
2947
+ setPosition('start');
2948
+ else
2949
+ setPosition('down');
2950
+ }, [asPortal, open, placement]);
2951
+ // Compute + track coords: portal mode
2952
+ // Re-runs whenever the menu opens and keeps coords fresh on scroll/resize
2953
+ useEffect(() => {
2954
+ if (!asPortal || !open)
2955
+ return () => { };
2956
+ computePortalCoords();
2957
+ const update = () => computePortalCoords();
2958
+ window.addEventListener('scroll', update, { passive: true, capture: true });
2959
+ window.addEventListener('resize', update, { passive: true });
2960
+ return () => {
2961
+ window.removeEventListener('scroll', update, { capture: true });
2962
+ window.removeEventListener('resize', update);
2963
+ };
2964
+ }, [asPortal, open, computePortalCoords]);
2965
+ let ToggleElement;
2966
+ if (dropdownToggle) {
2967
+ ToggleElement = typeof dropdownToggle === 'function'
2968
+ ? dropdownToggle({ open, toggle })
2969
+ : dropdownToggle;
2660
2970
  }
2661
2971
  else {
2662
- ToggleElement = (jsx(DButtonIcon, { variant: "link", stopPropagationEnabled: false, "aria-label": "Toggle Dropdown", "aria-haspopup": "menu", "aria-expanded": open, onClick: () => setOpen(!open), icon: "MoreVertical" }));
2972
+ ToggleElement = (jsx(DButtonIcon, { variant: "link", stopPropagationEnabled: false, "aria-label": "Toggle Dropdown", "aria-haspopup": "menu", "aria-expanded": open, onClick: toggle, icon: "MoreVertical" }));
2663
2973
  }
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
- }) })] }));
2974
+ const portalMenuStyle = asPortal && menuCoords
2975
+ ? {
2976
+ position: 'fixed',
2977
+ top: menuCoords.top,
2978
+ bottom: menuCoords.bottom,
2979
+ left: menuCoords.left,
2980
+ right: menuCoords.right,
2981
+ minWidth: menuCoords.minWidth,
2982
+ maxHeight: menuCoords.maxHeight,
2983
+ overflowY: 'auto',
2984
+ zIndex: 1050,
2985
+ transform: 'none',
2986
+ }
2987
+ : undefined;
2988
+ let inlineTop = '100%';
2989
+ if (resolvedInlinePosition === 'up') {
2990
+ inlineTop = 'auto';
2991
+ }
2992
+ else if (resolvedInlinePosition === 'start' || resolvedInlinePosition === 'end') {
2993
+ inlineTop = 0;
2994
+ }
2995
+ let inlineLeft = 0;
2996
+ if (resolvedInlinePosition === 'start') {
2997
+ inlineLeft = 'auto';
2998
+ }
2999
+ else if (resolvedInlinePosition === 'end') {
3000
+ inlineLeft = '100%';
3001
+ }
3002
+ let inlineRight = 'auto';
3003
+ if (resolvedInlinePosition === 'start') {
3004
+ inlineRight = '100%';
3005
+ }
3006
+ let inlineTransform = 'translateY(4px)';
3007
+ if (resolvedInlinePosition === 'up') {
3008
+ inlineTransform = 'translateY(-4px)';
3009
+ }
3010
+ else if (resolvedInlinePosition === 'start') {
3011
+ inlineTransform = 'translateX(-4px)';
3012
+ }
3013
+ else if (resolvedInlinePosition === 'end') {
3014
+ inlineTransform = 'translateX(4px)';
3015
+ }
3016
+ const inlineMenuStyle = {
3017
+ position: 'absolute',
3018
+ top: inlineTop,
3019
+ bottom: resolvedInlinePosition === 'up' ? '100%' : 'auto',
3020
+ left: inlineLeft,
3021
+ right: inlineRight,
3022
+ transform: inlineTransform,
3023
+ };
3024
+ 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) => {
3025
+ if (action.isDivider) {
3026
+ return jsx("hr", { className: "dropdown-divider" }, index);
3027
+ }
3028
+ return (jsx("li", { children: action.href ? (jsxs("a", { className: getItemClass(action), href: action.href, onClick: (e) => {
3029
+ if (action.disabled) {
3030
+ e.preventDefault();
3031
+ }
3032
+ else {
3033
+ setOpen(false);
3034
+ }
3035
+ }, children: [action.icon && jsx(DIcon, { icon: action.icon, className: "me-2", size: "1rem" }), action.label] })) : (jsxs("button", { className: getItemClass(action), type: "button", onClick: () => {
3036
+ var _a;
3037
+ if (!action.disabled) {
3038
+ (_a = action.onClick) === null || _a === void 0 ? void 0 : _a.call(action);
3039
+ setOpen(false);
3040
+ }
3041
+ }, disabled: action.disabled, children: [action.icon && jsx(DIcon, { icon: action.icon, className: "me-2", size: "1rem" }), action.label] })) }, index));
3042
+ }) }));
3043
+ if (asPortal) {
3044
+ return (jsxs("div", { ref: toggleRef, className: classNames('dropdown', className), children: [ToggleElement, open && menuCoords && createPortal(menuItems, document.body)] }));
3045
+ }
3046
+ return (jsxs("div", { className: classNames(`dropdown drop-${resolvedInlinePosition}`, className), ref: dropdownRef, children: [ToggleElement, menuItems] }));
2690
3047
  }
2691
3048
 
2692
3049
  function useScreenshot() {
@@ -2902,28 +3259,109 @@ function render(renderable) {
2902
3259
  return null;
2903
3260
  return typeof renderable === 'function' ? renderable() : renderable;
2904
3261
  }
2905
- function DDataStateWrapper({ isLoading, isError, data, onRetry, renderLoading, renderEmpty, renderError, children, }) {
3262
+ function DDataStateWrapper({ isLoading, isError, data, onRetry, messages, renderLoading, renderEmpty, renderError, children, }) {
2906
3263
  // 1. Loading
2907
3264
  if (isLoading) {
2908
3265
  if (renderLoading)
2909
3266
  return render(renderLoading);
2910
- return jsx(LoadingState, {});
3267
+ return jsx(LoadingState, { ariaLabel: messages === null || messages === void 0 ? void 0 : messages.loading });
2911
3268
  }
2912
3269
  // 2. Error
2913
3270
  if (isError) {
2914
3271
  if (renderError)
2915
3272
  return render(renderError);
2916
- return (jsx(ErrorState, { onRetry: onRetry }));
3273
+ 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
3274
  }
2918
3275
  // 3. Empty
2919
3276
  if (!(data === null || data === void 0 ? void 0 : data.length)) {
2920
3277
  if (renderEmpty)
2921
3278
  return render(renderEmpty);
2922
- return (jsx(EmptyState, {}));
3279
+ return (jsx(EmptyState, { message: messages === null || messages === void 0 ? void 0 : messages.empty }));
2923
3280
  }
2924
3281
  // 4. Success
2925
3282
  return jsx(Fragment, { children: children(data) });
2926
3283
  }
2927
3284
 
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 };
3285
+ function DConfirmModalUI({ entry }) {
3286
+ 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;
3287
+ const [confirmationCode, setConfirmationCode] = useState('');
3288
+ const [internalLoading, setInternalLoading] = useState(false);
3289
+ const isMountedRef = useRef(true);
3290
+ useEffect(() => {
3291
+ const cleanup = () => {
3292
+ isMountedRef.current = false;
3293
+ };
3294
+ return cleanup;
3295
+ }, []);
3296
+ const handleClose = useCallback(() => {
3297
+ onCloseAction();
3298
+ }, [onCloseAction]);
3299
+ const isConfirmDisabled = Boolean(internalLoading
3300
+ || (critical && confirmationCode !== critical.code));
3301
+ const handleConfirmClick = useCallback(() => {
3302
+ setInternalLoading(true);
3303
+ onConfirmAction()
3304
+ .catch(() => undefined)
3305
+ .finally(() => {
3306
+ // Only update state if the component is still mounted.
3307
+ // onConfirmAction() removes the entry on success, which unmounts this component.
3308
+ if (isMountedRef.current) {
3309
+ setInternalLoading(false);
3310
+ }
3311
+ });
3312
+ }, [onConfirmAction]);
3313
+ 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 })] })] }));
3314
+ }
3315
+
3316
+ /**
3317
+ * Container that renders confirm modals into the specified portal node.
3318
+ *
3319
+ * Must be explicitly mounted by the user (typically as a sibling of app content
3320
+ * within `DContextProvider`). The container floats above the portal stack and
3321
+ * intercepts Escape key events to close the top confirm modal without affecting
3322
+ * underlying modals or overlays.
3323
+ *
3324
+ * @example
3325
+ * <DContextProvider>
3326
+ * <App />
3327
+ * <DConfirmModalContainer nodeId="d-portal" />
3328
+ * </DContextProvider>
3329
+ */
3330
+ function DConfirmModalContainer({ nodeId }) {
3331
+ const store = useConfirmModalStore();
3332
+ const [entries, setEntries] = useState([]);
3333
+ useEffect(() => {
3334
+ const unsubscribe = store.subscribe((next) => {
3335
+ setEntries(next);
3336
+ });
3337
+ return unsubscribe;
3338
+ }, [store]);
3339
+ // Capture Escape keydown to close the top confirm modal without affecting
3340
+ // the underlying portal stack (which also handles Escape).
3341
+ useEffect(() => {
3342
+ if (entries.length === 0) {
3343
+ return () => { };
3344
+ }
3345
+ const handleEscapeCapture = (event) => {
3346
+ if (event.key === 'Escape') {
3347
+ // Close the top (last) confirm modal
3348
+ entries[entries.length - 1].onCloseAction();
3349
+ // Prevent the event from reaching other handlers (e.g., DPortalContextProvider)
3350
+ event.stopPropagation();
3351
+ event.preventDefault();
3352
+ }
3353
+ };
3354
+ document.addEventListener('keydown', handleEscapeCapture, true);
3355
+ return () => {
3356
+ document.removeEventListener('keydown', handleEscapeCapture, true);
3357
+ };
3358
+ }, [entries]);
3359
+ const portalNode = document.getElementById(nodeId);
3360
+ if (!portalNode || entries.length === 0) {
3361
+ return null;
3362
+ }
3363
+ 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);
3364
+ }
3365
+
3366
+ 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
3367
  //# sourceMappingURL=index.esm.js.map