@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.js CHANGED
@@ -5,6 +5,7 @@ var classNames = require('classnames');
5
5
  var React = require('react');
6
6
  var tslib = require('tslib');
7
7
  var LucideIcons = require('lucide-react');
8
+ var reactIs = require('react-is');
8
9
  var reactDom = require('react-dom');
9
10
  var framerMotion = require('framer-motion');
10
11
  var fileSelector = require('file-selector');
@@ -78,7 +79,7 @@ function useDisableBodyScrollEffect(disable) {
78
79
  timer = window.setTimeout(() => {
79
80
  unlock();
80
81
  observer === null || observer === void 0 ? void 0 : observer.disconnect();
81
- }, 3000);
82
+ }, 300);
82
83
  }
83
84
  else {
84
85
  unlock();
@@ -163,7 +164,7 @@ function getKeyboardFocusableElements(container) {
163
164
  const DPortalContext = React.createContext(undefined);
164
165
  function DPortalContextProvider({ portalName, children, availablePortals, }) {
165
166
  const { created } = usePortal(portalName);
166
- const [stack, { push, pop, isEmpty }] = useStackState([]);
167
+ const [stack, { push, pop }] = useStackState([]);
167
168
  useDisableBodyScrollEffect(Boolean(stack.length));
168
169
  const openPortal = React.useCallback(
169
170
  // eslint-disable-next-line prefer-arrow-callback
@@ -184,11 +185,9 @@ function DPortalContextProvider({ portalName, children, availablePortals, }) {
184
185
  (_a = document.activeElement) === null || _a === void 0 ? void 0 : _a.blur();
185
186
  }, [availablePortals, push]);
186
187
  const closePortal = React.useCallback(() => {
187
- if (isEmpty()) {
188
- return;
189
- }
188
+ // pop() is safe on empty stacks, so close remains idempotent.
190
189
  pop();
191
- }, [isEmpty, pop]);
190
+ }, [pop]);
192
191
  const publicStack = React.useMemo(() => stack.map(({ name, payload }) => ({ name, payload })), [stack]);
193
192
  const value = React.useMemo(() => ({
194
193
  stack: publicStack,
@@ -279,6 +278,40 @@ function useDPortalContext() {
279
278
  return context;
280
279
  }
281
280
 
281
+ function createConfirmModalStore() {
282
+ const listeners = new Set();
283
+ let entries = [];
284
+ function notify() {
285
+ listeners.forEach((listener) => listener([...entries]));
286
+ }
287
+ const store = {
288
+ subscribe(listener) {
289
+ listeners.add(listener);
290
+ listener([...entries]);
291
+ return () => {
292
+ listeners.delete(listener);
293
+ };
294
+ },
295
+ push(entry) {
296
+ entries = [...entries, entry];
297
+ notify();
298
+ },
299
+ remove(id) {
300
+ entries = entries.filter((e) => e.id !== id);
301
+ notify();
302
+ },
303
+ };
304
+ return store;
305
+ }
306
+ const ConfirmModalStoreContext = React.createContext(null);
307
+ function useConfirmModalStore() {
308
+ const store = React.useContext(ConfirmModalStoreContext);
309
+ if (!store) {
310
+ throw new Error('useConfirmModal must be used within a <DContextProvider>.');
311
+ }
312
+ return store;
313
+ }
314
+
282
315
  function getCssVariable(variable) {
283
316
  const computedStyle = getComputedStyle(document.documentElement);
284
317
  return computedStyle.getPropertyValue(variable).trim();
@@ -297,6 +330,7 @@ const DEFAULT_STATE = {
297
330
  familyPrefix: 'bi-',
298
331
  materialStyle: false,
299
332
  },
333
+ iconRegistry: undefined,
300
334
  iconMap: {
301
335
  x: 'X',
302
336
  xLg: 'X',
@@ -337,14 +371,37 @@ const DContext = React.createContext(DEFAULT_STATE);
337
371
  * component to configure icons, currency, language, and portal settings
338
372
  * for all descendant Dynamic UI components.
339
373
  *
374
+ * To enable confirmation modals you must also mount `DConfirmModalContainer`
375
+ * somewhere inside this provider (typically right before the closing tag of
376
+ * your root layout), similar to how `DToastContainer` works:
377
+ *
378
+ * ```tsx
379
+ * // Default: portalName="d-portal"
380
+ * <DContextProvider>
381
+ * <App />
382
+ * <DConfirmModalContainer nodeId="d-portal" />
383
+ * </DContextProvider>
384
+ * ```
385
+ *
386
+ * If you customize `portalName`, match it in `DConfirmModalContainer.nodeId`:
387
+ *
388
+ * ```tsx
389
+ * // Custom portalName
390
+ * <DContextProvider portalName="my-custom-portal">
391
+ * <App />
392
+ * <DConfirmModalContainer nodeId="my-custom-portal" />
393
+ * </DContextProvider>
394
+ * ```
395
+ *
340
396
  * @template T - Map of portal name → payload shape (e.g. `{ modal: { title: string } }`).
341
397
  * Pass it once at the top level: `<DContextProvider<MyPortals> ...>`.
342
398
  */
343
- function DContextProvider({ language = DEFAULT_STATE.language, currency = DEFAULT_STATE.currency, icon = DEFAULT_STATE.icon, iconMap = DEFAULT_STATE.iconMap, portalName = 'd-portal', availablePortals, children, }) {
399
+ function DContextProvider({ language = DEFAULT_STATE.language, currency = DEFAULT_STATE.currency, icon = DEFAULT_STATE.icon, iconRegistry = DEFAULT_STATE.iconRegistry, iconMap = DEFAULT_STATE.iconMap, portalName = 'd-portal', availablePortals, children, }) {
344
400
  const [internalContext, setInternalContext,] = React.useState({
345
401
  language,
346
402
  currency,
347
403
  icon,
404
+ iconRegistry,
348
405
  iconMap,
349
406
  breakpoints: DEFAULT_STATE.breakpoints,
350
407
  });
@@ -362,7 +419,8 @@ function DContextProvider({ language = DEFAULT_STATE.language, currency = DEFAUL
362
419
  });
363
420
  }, [setContext]);
364
421
  const value = React.useMemo(() => (Object.assign(Object.assign({}, internalContext), { setContext })), [internalContext, setContext]);
365
- return (jsxRuntime.jsx(DContext.Provider, { value: value, children: jsxRuntime.jsx(DPortalContextProvider, { portalName: portalName, availablePortals: availablePortals, children: children }) }));
422
+ const confirmStore = React.useMemo(() => createConfirmModalStore(), []);
423
+ return (jsxRuntime.jsx(ConfirmModalStoreContext.Provider, { value: confirmStore, children: jsxRuntime.jsx(DContext.Provider, { value: value, children: jsxRuntime.jsx(DPortalContextProvider, { portalName: portalName, availablePortals: availablePortals, children: children }) }) }));
366
424
  }
367
425
  /**
368
426
  * Returns the Dynamic UI context value set by `DContextProvider`.
@@ -460,17 +518,21 @@ function useResponsiveProp(useListener = false) {
460
518
  return { responsivePropValue };
461
519
  }
462
520
 
521
+ function isIconComponent(value) {
522
+ return typeof value !== 'string' && reactIs.isValidElementType(value);
523
+ }
463
524
  function DIconBase({ icon, color, style, className, size, useListenerSize = false, hasCircle = false, materialStyle = false, familyClass, familyPrefix, strokeWidth = 2, dataAttributes, }) {
464
525
  // If materialStyle is true, use Material Design icons (legacy)
465
- const useMaterialIcons = materialStyle;
526
+ const isStringIcon = typeof icon === 'string';
527
+ const useMaterialIcons = materialStyle && isStringIcon;
466
528
  // Get Lucide icon component
467
529
  const LucideIcon = React.useMemo(() => {
468
- if (useMaterialIcons)
530
+ if (!isStringIcon || useMaterialIcons)
469
531
  return null;
470
532
  // Try to find the icon in Lucide (expects PascalCase)
471
533
  const icons = LucideIcons__namespace;
472
534
  return icons[icon] || null;
473
- }, [icon, useMaterialIcons]);
535
+ }, [icon, isStringIcon, useMaterialIcons]);
474
536
  const { responsivePropValue } = useResponsiveProp(useListenerSize);
475
537
  const resolvedSize = React.useMemo(() => {
476
538
  if (!size)
@@ -490,24 +552,35 @@ function DIconBase({ icon, color, style, className, size, useListenerSize = fals
490
552
  }, [resolvedSize]);
491
553
  // Render Material Design icon (legacy support)
492
554
  if (useMaterialIcons) {
493
- return (jsxRuntime.jsx("i", Object.assign({ className: classNames(generateClasses, familyClass), style: generateStyleVariables }, dataAttributes, { children: icon })));
555
+ return (jsxRuntime.jsx("i", Object.assign({ className: classNames(generateClasses, familyClass), style: generateStyleVariables }, dataAttributes, { children: isStringIcon ? icon : null })));
556
+ }
557
+ if (isIconComponent(icon)) {
558
+ return (jsxRuntime.jsx("span", Object.assign({ className: classNames(generateClasses), style: generateStyleVariables }, dataAttributes, { children: React.createElement(icon, {
559
+ width: resolvedSize || 24,
560
+ height: resolvedSize || 24,
561
+ strokeWidth,
562
+ }) })));
494
563
  }
495
564
  // Render Lucide icon
496
565
  if (!LucideIcon) {
497
- if (familyClass && familyPrefix) {
566
+ if (isStringIcon && familyClass && familyPrefix) {
498
567
  return (jsxRuntime.jsx("i", Object.assign({ className: classNames(generateClasses, familyClass, `${familyPrefix}${icon}`), style: generateStyleVariables }, dataAttributes)));
499
568
  }
500
569
  // eslint-disable-next-line no-console
501
- console.warn(`Icon "${icon}" not found in Lucide. Make sure to use PascalCase names (e.g., "Home", "User", "Settings")`);
570
+ console.warn(`Icon "${String(icon)}" not found in Lucide. Make sure to use PascalCase names (e.g., "Home", "User", "Settings")`);
502
571
  return (jsxRuntime.jsx("span", Object.assign({ className: classNames(generateClasses), style: generateStyleVariables }, dataAttributes, { children: "?" })));
503
572
  }
504
573
  return (jsxRuntime.jsx("span", Object.assign({ className: classNames(generateClasses), style: generateStyleVariables }, dataAttributes, { children: jsxRuntime.jsx(LucideIcon, { size: iconSize || 24, strokeWidth: strokeWidth }) })));
505
574
  }
506
575
 
507
576
  function DIcon(_a) {
508
- var { familyClass: propFamilyClass, familyPrefix: propFamilyPrefix, materialStyle: propMaterialStyle } = _a, props = tslib.__rest(_a, ["familyClass", "familyPrefix", "materialStyle"]);
509
- const { icon: { familyClass, familyPrefix, materialStyle, }, } = useDContext();
510
- return (jsxRuntime.jsx(DIconBase, Object.assign({ familyClass: propFamilyClass || familyClass, familyPrefix: propFamilyPrefix || familyPrefix, materialStyle: propMaterialStyle || materialStyle }, props)));
577
+ var { icon, familyClass: propFamilyClass, familyPrefix: propFamilyPrefix, materialStyle: propMaterialStyle } = _a, props = tslib.__rest(_a, ["icon", "familyClass", "familyPrefix", "materialStyle"]);
578
+ const { icon: { familyClass, familyPrefix, materialStyle, }, iconRegistry, } = useDContext();
579
+ const registryIcon = typeof icon === 'string'
580
+ ? iconRegistry === null || iconRegistry === void 0 ? void 0 : iconRegistry[icon]
581
+ : undefined;
582
+ const resolvedIcon = registryIcon || icon;
583
+ return (jsxRuntime.jsx(DIconBase, Object.assign({ icon: resolvedIcon, familyClass: propFamilyClass !== null && propFamilyClass !== void 0 ? propFamilyClass : familyClass, familyPrefix: propFamilyPrefix !== null && propFamilyPrefix !== void 0 ? propFamilyPrefix : familyPrefix, materialStyle: propMaterialStyle !== null && propMaterialStyle !== void 0 ? propMaterialStyle : materialStyle }, props)));
511
584
  }
512
585
 
513
586
  function DAlert({ color = 'success', icon: iconProp, iconFamilyClass, iconFamilyPrefix, iconMaterialStyle = false, iconClose: iconCloseProp, iconCloseFamilyClass, iconCloseFamilyPrefix, iconCloseMaterialStyle = false, showClose, onClose, children, id, className, style, dataAttributes, }) {
@@ -1057,7 +1130,7 @@ function DBoxFile(_a) {
1057
1130
  }
1058
1131
 
1059
1132
  const DButton = React.forwardRef((props, ref) => {
1060
- 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 = tslib.__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']);
1133
+ 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 = tslib.__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']);
1061
1134
  // Responsive size resolution using useResponsiveProp
1062
1135
  const { responsivePropValue } = useResponsiveProp(true);
1063
1136
  const resolvedSize = React.useMemo(() => {
@@ -1072,9 +1145,9 @@ const DButton = React.forwardRef((props, ref) => {
1072
1145
  const isDisabled = React.useMemo(() => disabled || loading, [disabled, loading]);
1073
1146
  const content = React.useMemo(() => children || text, [children, text]);
1074
1147
  const classes = React.useMemo(() => {
1075
- const variantClass = variant
1076
- ? `btn-${variant}-${color}`
1077
- : `btn-${color}`;
1148
+ const variantClass = variant === 'solid'
1149
+ ? `btn-${color}`
1150
+ : `btn-${variant}-${color}`;
1078
1151
  return {
1079
1152
  btn: true,
1080
1153
  [variantClass]: true,
@@ -1132,9 +1205,9 @@ DButton.displayName = 'DButton';
1132
1205
  function DButtonIcon(_a) {
1133
1206
  var { id, icon, size, className, variant, state, loadingAriaLabel, iconMaterialStyle, disabled = false, color = 'primary', loading = false, href, target, rel, stopPropagationEnabled = true, style, iconFamilyClass, iconFamilyPrefix, dataAttributes, onClick, 'aria-label': ariaLabelProp } = _a, rest = tslib.__rest(_a, ["id", "icon", "size", "className", "variant", "state", "loadingAriaLabel", "iconMaterialStyle", "disabled", "color", "loading", "href", "target", "rel", "stopPropagationEnabled", "style", "iconFamilyClass", "iconFamilyPrefix", "dataAttributes", "onClick", 'aria-label']);
1134
1207
  const generateClasses = React.useMemo(() => {
1135
- const variantClass = variant
1136
- ? `btn-${variant}-${color}`
1137
- : `btn-${color}`;
1208
+ const variantClass = !variant || variant === 'solid'
1209
+ ? `btn-${color}`
1210
+ : `btn-${variant}-${color}`;
1138
1211
  return Object.assign(Object.assign(Object.assign({ 'btn d-button-icon': true, [variantClass]: true }, size && { [`btn-${size}`]: true }), (state && state !== 'disabled') && { [state]: true }), { loading });
1139
1212
  }, [variant, color, size, state, loading]);
1140
1213
  const isDisabled = React.useMemo(() => (state === 'disabled' || loading || disabled), [state, loading, disabled]);
@@ -1187,8 +1260,14 @@ function DCarouselSlide(_a) {
1187
1260
  return (jsxRuntime.jsx(reactSplide.SplideSlide, Object.assign({ className: classNames('d-carousel-slide', className) }, props)));
1188
1261
  }
1189
1262
 
1263
+ const DEFAULT_ARROW_PATH = 'm15.5 0.932-4.3 4.38 14.5 14.6-14.5 14.5 4.3 4.4 14.6-14.6 4.4-4.3-4.4-4.4-14.6-14.6z';
1264
+ function DefaultArrowIcon() {
1265
+ return (jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 40 40", width: 40, height: 40, focusable: "false", "aria-hidden": "true", children: jsxRuntime.jsx("path", { d: DEFAULT_ARROW_PATH }) }));
1266
+ }
1190
1267
  function DCarousel$1(_a, ref) {
1191
- var { children, className, style, options, dataAttributes } = _a, props = tslib.__rest(_a, ["children", "className", "style", "options", "dataAttributes"]);
1268
+ var { children, className, style, options, dataAttributes, iconArrowLeft, iconArrowRight, hasTrack: propsHasTrack } = _a, props = tslib.__rest(_a, ["children", "className", "style", "options", "dataAttributes", "iconArrowLeft", "iconArrowRight", "hasTrack"]);
1269
+ // Explicit `options.arrows === false` always wins, even when icon props are set.
1270
+ const hasCustomArrows = Boolean((iconArrowLeft || iconArrowRight) && (options === null || options === void 0 ? void 0 : options.arrows) !== false);
1192
1271
  return (jsxRuntime.jsx(reactSplide.Splide, Object.assign({ className: classNames('d-carousel', className), style: style, ref: ref, options: Object.assign(Object.assign({}, options), { classes: {
1193
1272
  // Arrows
1194
1273
  arrows: 'splide__arrows d-carousel-arrows',
@@ -1198,7 +1277,11 @@ function DCarousel$1(_a, ref) {
1198
1277
  // Paginator
1199
1278
  pagination: 'splide__pagination d-carousel-pagination',
1200
1279
  page: 'splide__pagination__page d-carousel-pagination-page',
1201
- } }) }, dataAttributes, props, { children: children })));
1280
+ } }) }, dataAttributes, props, {
1281
+ // Rendering our own arrows requires taking over the track wrapper, so this
1282
+ // must be applied after `...props` to prevent a consumer-provided `hasTrack`
1283
+ // from re-enabling Splide's automatic track when custom arrows are active.
1284
+ hasTrack: hasCustomArrows ? false : propsHasTrack, children: hasCustomArrows ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(reactSplide.SplideTrack, { children: children }), jsxRuntime.jsxs("div", { className: "splide__arrows d-carousel-arrows", children: [jsxRuntime.jsx("button", { type: "button", className: "splide__arrow splide__arrow--prev d-carousel-arrow d-carousel-arrow-prev", children: iconArrowLeft ? jsxRuntime.jsx(DIcon, Object.assign({}, iconArrowLeft)) : jsxRuntime.jsx(DefaultArrowIcon, {}) }), jsxRuntime.jsx("button", { type: "button", className: "splide__arrow splide__arrow--next d-carousel-arrow d-carousel-arrow-next", children: iconArrowRight ? jsxRuntime.jsx(DIcon, Object.assign({}, iconArrowRight)) : jsxRuntime.jsx(DefaultArrowIcon, {}) })] })] })) : children })));
1202
1285
  }
1203
1286
  const ForwardedDCarousel = React.forwardRef(DCarousel$1);
1204
1287
  ForwardedDCarousel.displayName = 'DCarousel';
@@ -1426,6 +1509,18 @@ function DDatePickerHeaderSelector({ date, changeYear, changeMonth, decreaseMont
1426
1509
 
1427
1510
  function DDatePicker(_a) {
1428
1511
  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 = tslib.__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"]);
1512
+ // Some test runtimes can resolve react-datepicker as a module object.
1513
+ // Normalize to a renderable component for both function and { default } shapes.
1514
+ const SafeDatePicker = React.useMemo(() => {
1515
+ const interopDefault = DatePicker.default;
1516
+ if (interopDefault !== undefined) {
1517
+ return interopDefault;
1518
+ }
1519
+ if (typeof DatePicker === 'function') {
1520
+ return DatePicker;
1521
+ }
1522
+ return DatePicker;
1523
+ }, []);
1429
1524
  const pickerType = React.useMemo(() => {
1430
1525
  if (props.showQuarterYearPicker)
1431
1526
  return PickerType.Quarter;
@@ -1453,7 +1548,7 @@ function DDatePicker(_a) {
1453
1548
  ]);
1454
1549
  const defaultRenderCustomHeader = React.useCallback((headerProps) => (jsxRuntime.jsx(DatePickerHeader, Object.assign({}, headerProps))), [DatePickerHeader]);
1455
1550
  const renderCustomHeader = React.useMemo(() => (renderCustomHeaderProp || defaultRenderCustomHeader), [defaultRenderCustomHeader, renderCustomHeaderProp]);
1456
- return (jsxRuntime.jsx(DatePicker, Object.assign({}, dataAttributes, props, { calendarClassName: "d-date-picker", renderCustomHeader: renderCustomHeader, placeholderText: placeholder, customInput: (jsxRuntime.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: (jsxRuntime.jsx(DDatePickerTime, { id: timeId, "aria-label": ariaLabelInputTime })) })));
1551
+ return (jsxRuntime.jsx(SafeDatePicker, Object.assign({}, dataAttributes, props, { calendarClassName: "d-date-picker", renderCustomHeader: renderCustomHeader, placeholderText: placeholder, customInput: (jsxRuntime.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: (jsxRuntime.jsx(DDatePickerTime, { id: timeId, "aria-label": ariaLabelInputTime })) })));
1457
1552
  }
1458
1553
 
1459
1554
  function DLayoutPane({ className, style, children, cols, colsXs, colsSm, colsMd, colsLg, colsXl, colsXxl, dataAttributes, }) {
@@ -1483,6 +1578,55 @@ var DLayout = Object.assign(DLayout$1, {
1483
1578
  Pane: DLayoutPane,
1484
1579
  });
1485
1580
 
1581
+ function normalizeValue(value) {
1582
+ return value !== null && value !== void 0 ? value : '';
1583
+ }
1584
+ function DInputSearch(_a, ref) {
1585
+ var { debounceMs = 300, onChange, onImmediateChange, value, defaultValue, placeholder = 'Search...' } = _a, props = tslib.__rest(_a, ["debounceMs", "onChange", "onImmediateChange", "value", "defaultValue", "placeholder"]);
1586
+ const inputRef = useProvidedRefOrCreate(ref);
1587
+ const isControlled = value !== undefined;
1588
+ const onChangeRef = React.useRef(onChange);
1589
+ onChangeRef.current = onChange;
1590
+ const [internalValue, setInternalValue] = React.useState(normalizeValue(isControlled ? value : defaultValue));
1591
+ // null = no keystroke has occurred yet (skip debounce on mount)
1592
+ const [pendingValue, setPendingValue] = React.useState(null);
1593
+ React.useEffect(() => {
1594
+ if (!isControlled)
1595
+ return;
1596
+ setInternalValue(normalizeValue(value));
1597
+ }, [isControlled, value]);
1598
+ const handleChange = React.useCallback((nextValue) => {
1599
+ if (!isControlled) {
1600
+ setInternalValue(nextValue);
1601
+ }
1602
+ setPendingValue(nextValue);
1603
+ onImmediateChange === null || onImmediateChange === void 0 ? void 0 : onImmediateChange(nextValue);
1604
+ }, [isControlled, onImmediateChange]);
1605
+ React.useEffect(() => {
1606
+ var _a;
1607
+ let timeoutId;
1608
+ if (pendingValue !== null) {
1609
+ if (debounceMs <= 0) {
1610
+ (_a = onChangeRef.current) === null || _a === void 0 ? void 0 : _a.call(onChangeRef, pendingValue);
1611
+ setPendingValue(null);
1612
+ }
1613
+ else {
1614
+ timeoutId = window.setTimeout(() => {
1615
+ var _a;
1616
+ (_a = onChangeRef.current) === null || _a === void 0 ? void 0 : _a.call(onChangeRef, pendingValue);
1617
+ setPendingValue(null);
1618
+ }, debounceMs);
1619
+ }
1620
+ }
1621
+ return () => {
1622
+ window.clearTimeout(timeoutId);
1623
+ };
1624
+ }, [debounceMs, pendingValue]);
1625
+ return (jsxRuntime.jsx(ForwardedDInput, Object.assign({ ref: inputRef }, props, { type: "search", value: internalValue, onChange: handleChange, placeholder: placeholder })));
1626
+ }
1627
+ const ForwardedDInputSearch = React.forwardRef(DInputSearch);
1628
+ ForwardedDInputSearch.displayName = 'DInputSearch';
1629
+
1486
1630
  function DInputMask(props, ref) {
1487
1631
  return (jsxRuntime.jsx(mask.InputMask, Object.assign({ ref: ref, component: ForwardedDInput }, props)));
1488
1632
  }
@@ -1587,6 +1731,64 @@ function useItemSelection({ getItemIdentifier: getItemIdentifierProp, previousSe
1587
1731
  };
1588
1732
  }
1589
1733
 
1734
+ /**
1735
+ * Hook to display a confirmation modal.
1736
+ *
1737
+ * Renders into the portal node specified by `DConfirmModalContainer` (typically `#d-portal`).
1738
+ * The confirm modal floats above the portal stack and handles its own Escape key,
1739
+ * preventing interference with underlying modals.
1740
+ *
1741
+ * Requires both `DContextProvider` wrapping your application AND an explicit
1742
+ * `<DConfirmModalContainer nodeId="d-portal" />` mounted in your app.
1743
+ *
1744
+ * @example
1745
+ * function App() {
1746
+ * return (
1747
+ * <DContextProvider>
1748
+ * <YourContent />
1749
+ * <DConfirmModalContainer nodeId="d-portal" />
1750
+ * </DContextProvider>
1751
+ * );
1752
+ * }
1753
+ *
1754
+ * function MyComponent() {
1755
+ * const confirm = useConfirmModal({
1756
+ * title: 'Delete Account',
1757
+ * message: 'This action cannot be undone.',
1758
+ * critical: { code: 'DELETE ACCOUNT' },
1759
+ * onConfirm: async () => { await deleteAccount(); },
1760
+ * });
1761
+ *
1762
+ * return <DButton onClick={confirm.open} text="Delete" color="danger" />;
1763
+ * }
1764
+ */
1765
+ function useConfirmModal(config) {
1766
+ const store = useConfirmModalStore();
1767
+ const idRef = React.useRef(`confirm-modal-${Math.random().toString(36).slice(2)}`);
1768
+ const close = React.useCallback(() => {
1769
+ store.remove(idRef.current);
1770
+ queueMicrotask(() => {
1771
+ var _a;
1772
+ (_a = config.onClose) === null || _a === void 0 ? void 0 : _a.call(config);
1773
+ });
1774
+ }, [store, config]);
1775
+ const open = React.useCallback(() => {
1776
+ const handleConfirmAction = async () => {
1777
+ try {
1778
+ await config.onConfirm();
1779
+ store.remove(idRef.current);
1780
+ }
1781
+ catch (_a) {
1782
+ // Keep modal open on error
1783
+ }
1784
+ };
1785
+ // Remove any existing entry with this id to prevent duplicates on re-entrancy
1786
+ store.remove(idRef.current);
1787
+ store.push(Object.assign(Object.assign({}, config), { id: idRef.current, onConfirmAction: handleConfirmAction, onCloseAction: close }));
1788
+ }, [store, config, close]);
1789
+ return { open };
1790
+ }
1791
+
1590
1792
  function DInputCounter(_a, ref) {
1591
1793
  var { minValue, maxValue, value = minValue, invalid, iconStart: iconStartProp, iconEnd: iconEndProp, iconStartAriaLabel = 'decrease action', iconEndAriaLabel = 'increase action', style, onChange } = _a, props = tslib.__rest(_a, ["minValue", "maxValue", "value", "invalid", "iconStart", "iconEnd", "iconStartAriaLabel", "iconEndAriaLabel", "style", "onChange"]);
1592
1794
  const { handleOnWheel, } = useDisableInputWheel(ref);
@@ -1718,7 +1920,7 @@ const DEFAULT_VALIDATION_MESSAGES = {
1718
1920
  notMatch: 'The password confirmation and the new password do not match.',
1719
1921
  };
1720
1922
  const DEFAULT_ENABLED_CHECKS = ['uppercase', 'lowercase', 'number', 'specialChar'];
1721
- function DPasswordStrengthMeter({ id, label = 'Password', placeholder, value = '', name, disabled = false, invalid = false, validationMessages = DEFAULT_VALIDATION_MESSAGES, enabledChecks = DEFAULT_ENABLED_CHECKS, className, style, dataAttributes, onChange, }) {
1923
+ 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, }) {
1722
1924
  const [password, setPassword] = React.useState(value);
1723
1925
  React.useEffect(() => {
1724
1926
  setPassword(value);
@@ -1727,7 +1929,7 @@ function DPasswordStrengthMeter({ id, label = 'Password', placeholder, value = '
1727
1929
  setPassword(newValue);
1728
1930
  onChange === null || onChange === void 0 ? void 0 : onChange(newValue);
1729
1931
  };
1730
- return (jsxRuntime.jsxs("div", Object.assign({ className: className, style: style }, dataAttributes, { children: [jsxRuntime.jsx(ForwardedDInputPassword, { id: id, label: label, placeholder: placeholder, value: password, name: name, disabled: disabled, invalid: invalid, onChange: handleChange }), jsxRuntime.jsx(PasswordChecksList, { password: password, validationMessages: validationMessages, enabledChecks: enabledChecks })] })));
1932
+ return (jsxRuntime.jsxs("div", Object.assign({ className: className, style: style }, dataAttributes, { children: [jsxRuntime.jsx(ForwardedDInputPassword, { id: id, label: label, placeholder: placeholder, value: password, name: name, disabled: disabled, invalid: invalid, onChange: handleChange, readonly: readonly }), jsxRuntime.jsx(PasswordChecksList, { password: password, validationMessages: validationMessages, enabledChecks: enabledChecks })] })));
1731
1933
  }
1732
1934
 
1733
1935
  function DInputCheck(_a) {
@@ -2142,10 +2344,37 @@ const defaultTransition = {
2142
2344
  ease: 'easeInOut',
2143
2345
  duration: 0.3,
2144
2346
  };
2145
- function DOffcanvas$1({ name, className, style, staticBackdrop, scrollable, openFrom = 'end', transition, children, dataAttributes, }) {
2347
+ function DOffcanvas$1({ name, className, style, staticBackdrop, scrollable, openFrom = 'end', width, height, transition, children, dataAttributes, }) {
2348
+ // Only subscribe to breakpoint-change listeners when a responsive object is
2349
+ // actually provided, avoiding unnecessary matchMedia subscriptions for the
2350
+ // common case of plain string values.
2351
+ const hasResponsiveProp = typeof openFrom === 'object'
2352
+ || typeof width === 'object'
2353
+ || typeof height === 'object';
2354
+ const { responsivePropValue } = useResponsiveProp(hasResponsiveProp);
2355
+ const resolvedOpenFrom = React.useMemo(() => {
2356
+ var _a;
2357
+ if (typeof openFrom === 'string')
2358
+ return openFrom;
2359
+ return (_a = responsivePropValue(openFrom)) !== null && _a !== void 0 ? _a : 'end';
2360
+ }, [responsivePropValue, openFrom]);
2361
+ const resolvedWidth = React.useMemo(() => {
2362
+ if (!width)
2363
+ return undefined;
2364
+ if (typeof width === 'string')
2365
+ return width;
2366
+ return responsivePropValue(width);
2367
+ }, [responsivePropValue, width]);
2368
+ const resolvedHeight = React.useMemo(() => {
2369
+ if (!height)
2370
+ return undefined;
2371
+ if (typeof height === 'string')
2372
+ return height;
2373
+ return responsivePropValue(height);
2374
+ }, [responsivePropValue, height]);
2146
2375
  return (jsxRuntime.jsx(framerMotion.motion.div, Object.assign({ className: classNames('offcanvas portal show', {
2147
- [`offcanvas-${openFrom}`]: openFrom,
2148
- }, 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 && ({
2376
+ [`offcanvas-${resolvedOpenFrom}`]: resolvedOpenFrom,
2377
+ }, 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 && ({
2149
2378
  [`data-${PREFIX_BS}backdrop`]: 'static',
2150
2379
  [`data-${PREFIX_BS}keyboard`]: 'false',
2151
2380
  }), scrollable && ({
@@ -2631,82 +2860,210 @@ function DCreditCard({ brand = 'visa', name, number, holderText = 'Card Holder',
2631
2860
  return (jsxRuntime.jsxs("div", { className: classNames('d-credit-card', isVertical && 'is-vertical', className), children: [jsxRuntime.jsxs("div", { className: "d-credit-card-header", children: [jsxRuntime.jsx("img", { src: logoImage || BRAND_LOGOS[brand] || DEFAULT_IMAGE, alt: brand, className: "d-credit-card-logo", width: 100 }), isChipVisible && (jsxRuntime.jsx("div", { className: "d-credit-card-chip", children: jsxRuntime.jsx("img", { src: CHIP_IMAGE, alt: "chip", width: 30, className: "d-credit-card-chip-image" }) }))] }), jsxRuntime.jsxs("div", { className: "d-credit-card-details", children: [jsxRuntime.jsx("div", { className: "d-credit-card-number", children: number }), jsxRuntime.jsx("small", { className: "d-credit-card-holder-text", children: holderText }), jsxRuntime.jsx("span", { className: "d-credit-card-name", children: name })] })] }));
2632
2861
  }
2633
2862
 
2634
- const getItemClass = (action) => {
2635
- const base = classNames({
2636
- 'dropdown-item d-flex align-items-center': true,
2637
- [`dropdown-item-${action.color}`]: !!action.color,
2638
- disabled: action.disabled,
2639
- });
2640
- return base;
2641
- };
2642
- function DDropdown({ actions, dropdownToggle, className, classNameMenu, }) {
2863
+ const getItemClass = (action) => classNames({
2864
+ 'dropdown-item d-flex align-items-center': true,
2865
+ [`dropdown-item-${action.color}`]: !!action.color,
2866
+ disabled: action.disabled,
2867
+ });
2868
+ function DDropdown({ actions, dropdownToggle, className, classNameMenu, asPortal = false, placement = 'auto', }) {
2643
2869
  const [open, setOpen] = React.useState(false);
2870
+ const [position, setPosition] = React.useState('down');
2871
+ const [menuCoords, setMenuCoords] = React.useState(null);
2872
+ // Used in default (non-portal) mode: wraps the entire dropdown
2644
2873
  const dropdownRef = React.useRef(null);
2645
- const [position, setPosition] = React.useState('down'); // 🆕
2646
- // Cerrar al hacer click fuera
2647
- React.useEffect(() => {
2648
- const handleClickOutside = (event) => {
2649
- if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
2650
- setOpen(false);
2651
- }
2652
- };
2653
- document.addEventListener('mousedown', handleClickOutside);
2654
- return () => document.removeEventListener('mousedown', handleClickOutside);
2655
- }, []);
2656
- React.useEffect(() => {
2657
- if (open && dropdownRef.current) {
2658
- const rect = dropdownRef.current.getBoundingClientRect();
2659
- const spaceBottom = window.innerHeight - rect.bottom;
2660
- const spaceRight = window.innerWidth - rect.right;
2874
+ // Used in portal mode: ref on the toggle wrapper to read its viewport coords
2875
+ const toggleRef = React.useRef(null);
2876
+ // Ref on the rendered <ul> — used for outside-click detection in portal mode
2877
+ const menuRef = React.useRef(null);
2878
+ const toggle = () => {
2879
+ setMenuCoords(null);
2880
+ setOpen((prev) => !prev);
2881
+ };
2882
+ const resolvedInlinePosition = placement === 'auto' ? position : placement;
2883
+ // Compute fixed coords from the toggle's bounding rect (portal mode only)
2884
+ const computePortalCoords = React.useCallback(() => {
2885
+ if (!toggleRef.current)
2886
+ return;
2887
+ const rect = toggleRef.current.getBoundingClientRect();
2888
+ const spaceRight = window.innerWidth - rect.right;
2889
+ const spaceBottom = window.innerHeight - rect.bottom;
2890
+ const spaceTop = rect.top;
2891
+ let resolvedPlacement;
2892
+ if (placement === 'auto') {
2661
2893
  if (spaceBottom < 150) {
2662
- setPosition('up');
2894
+ resolvedPlacement = 'up';
2663
2895
  }
2664
2896
  else if (spaceRight < 150) {
2665
- setPosition('start');
2897
+ resolvedPlacement = 'start';
2666
2898
  }
2667
2899
  else {
2668
- setPosition('down');
2900
+ resolvedPlacement = 'down';
2669
2901
  }
2670
2902
  }
2671
- }, [open]);
2672
- let ToggleElement;
2673
- if (dropdownToggle) {
2674
- if (typeof dropdownToggle === 'function') {
2675
- ToggleElement = dropdownToggle({ open, toggle: () => setOpen(!open) });
2903
+ else {
2904
+ resolvedPlacement = placement;
2905
+ }
2906
+ const openStart = resolvedPlacement === 'start';
2907
+ const openEnd = resolvedPlacement === 'end';
2908
+ const openUp = resolvedPlacement === 'up';
2909
+ let availableHeight = spaceBottom;
2910
+ if (openUp) {
2911
+ availableHeight = spaceTop;
2912
+ }
2913
+ else if (openStart || openEnd) {
2914
+ availableHeight = window.innerHeight - rect.top;
2915
+ }
2916
+ const verticalCoords = openUp
2917
+ ? { bottom: window.innerHeight - rect.top + 4 }
2918
+ : { top: openStart || openEnd ? rect.top : rect.bottom + 4 };
2919
+ let horizontalCoords;
2920
+ if (openStart) {
2921
+ horizontalCoords = { right: window.innerWidth - rect.left + 4 };
2922
+ }
2923
+ else if (openEnd) {
2924
+ horizontalCoords = { left: rect.right + 4 };
2676
2925
  }
2677
2926
  else {
2678
- ToggleElement = dropdownToggle;
2927
+ horizontalCoords = { left: rect.left };
2679
2928
  }
2929
+ setMenuCoords(Object.assign(Object.assign(Object.assign({}, verticalCoords), horizontalCoords), { minWidth: Math.max(rect.width, 160), maxHeight: `${Math.max(availableHeight - 12, 120)}px` }));
2930
+ }, [placement]);
2931
+ // Outside-click: default mode
2932
+ React.useEffect(() => {
2933
+ if (asPortal)
2934
+ return () => { };
2935
+ const handler = (event) => {
2936
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
2937
+ setOpen(false);
2938
+ }
2939
+ };
2940
+ document.addEventListener('mousedown', handler);
2941
+ return () => document.removeEventListener('mousedown', handler);
2942
+ }, [asPortal]);
2943
+ // Outside-click: portal mode (must check both toggle and floating menu)
2944
+ React.useEffect(() => {
2945
+ if (!asPortal || !open)
2946
+ return () => { };
2947
+ const handler = (event) => {
2948
+ var _a, _b;
2949
+ const target = event.target;
2950
+ 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))) {
2951
+ setOpen(false);
2952
+ }
2953
+ };
2954
+ document.addEventListener('mousedown', handler);
2955
+ return () => document.removeEventListener('mousedown', handler);
2956
+ }, [asPortal, open]);
2957
+ // Auto-position: default mode
2958
+ React.useEffect(() => {
2959
+ if (asPortal || !open || !dropdownRef.current || placement !== 'auto')
2960
+ return;
2961
+ const rect = dropdownRef.current.getBoundingClientRect();
2962
+ const spaceBottom = window.innerHeight - rect.bottom;
2963
+ const spaceRight = window.innerWidth - rect.right;
2964
+ if (spaceBottom < 150)
2965
+ setPosition('up');
2966
+ else if (spaceRight < 150)
2967
+ setPosition('start');
2968
+ else
2969
+ setPosition('down');
2970
+ }, [asPortal, open, placement]);
2971
+ // Compute + track coords: portal mode
2972
+ // Re-runs whenever the menu opens and keeps coords fresh on scroll/resize
2973
+ React.useEffect(() => {
2974
+ if (!asPortal || !open)
2975
+ return () => { };
2976
+ computePortalCoords();
2977
+ const update = () => computePortalCoords();
2978
+ window.addEventListener('scroll', update, { passive: true, capture: true });
2979
+ window.addEventListener('resize', update, { passive: true });
2980
+ return () => {
2981
+ window.removeEventListener('scroll', update, { capture: true });
2982
+ window.removeEventListener('resize', update);
2983
+ };
2984
+ }, [asPortal, open, computePortalCoords]);
2985
+ let ToggleElement;
2986
+ if (dropdownToggle) {
2987
+ ToggleElement = typeof dropdownToggle === 'function'
2988
+ ? dropdownToggle({ open, toggle })
2989
+ : dropdownToggle;
2680
2990
  }
2681
2991
  else {
2682
- ToggleElement = (jsxRuntime.jsx(DButtonIcon, { variant: "link", stopPropagationEnabled: false, "aria-label": "Toggle Dropdown", "aria-haspopup": "menu", "aria-expanded": open, onClick: () => setOpen(!open), icon: "MoreVertical" }));
2992
+ ToggleElement = (jsxRuntime.jsx(DButtonIcon, { variant: "link", stopPropagationEnabled: false, "aria-label": "Toggle Dropdown", "aria-haspopup": "menu", "aria-expanded": open, onClick: toggle, icon: "MoreVertical" }));
2683
2993
  }
2684
- return (jsxRuntime.jsxs("div", { className: classNames(`dropdown drop-${position}`, className), ref: dropdownRef, children: [ToggleElement, jsxRuntime.jsx("ul", { style: {
2685
- position: 'absolute',
2686
- top: position === 'up' ? 'auto' : '100%',
2687
- bottom: position === 'up' ? '100%' : 'auto',
2688
- left: position === 'start' ? 'auto' : 0,
2689
- right: position === 'start' ? '0' : 'auto',
2690
- transform: 'translateY(4px)',
2691
- }, className: classNames('dropdown-menu p-2', { show: open }, classNameMenu), children: actions.map((action, index) => {
2692
- if (action.isDivider) {
2693
- return (jsxRuntime.jsx("hr", { className: "dropdown-divider" }, index));
2694
- }
2695
- return (jsxRuntime.jsx("li", { children: action.href ? (jsxRuntime.jsxs("a", { className: getItemClass(action), href: action.href, onClick: (e) => {
2696
- if (action.disabled) {
2697
- e.preventDefault();
2698
- }
2699
- else {
2700
- setOpen(false);
2701
- }
2702
- }, children: [action.icon && (jsxRuntime.jsx(DIcon, { icon: action.icon, className: "me-2", size: "1rem" })), action.label] })) : (jsxRuntime.jsxs("button", { className: getItemClass(action), type: "button", onClick: () => {
2703
- var _a;
2704
- if (!action.disabled) {
2705
- (_a = action.onClick) === null || _a === void 0 ? void 0 : _a.call(action);
2706
- setOpen(false);
2707
- }
2708
- }, disabled: action.disabled, children: [action.icon && (jsxRuntime.jsx(DIcon, { icon: action.icon, className: "me-2", size: "1rem" })), action.label] })) }, index));
2709
- }) })] }));
2994
+ const portalMenuStyle = asPortal && menuCoords
2995
+ ? {
2996
+ position: 'fixed',
2997
+ top: menuCoords.top,
2998
+ bottom: menuCoords.bottom,
2999
+ left: menuCoords.left,
3000
+ right: menuCoords.right,
3001
+ minWidth: menuCoords.minWidth,
3002
+ maxHeight: menuCoords.maxHeight,
3003
+ overflowY: 'auto',
3004
+ zIndex: 1050,
3005
+ transform: 'none',
3006
+ }
3007
+ : undefined;
3008
+ let inlineTop = '100%';
3009
+ if (resolvedInlinePosition === 'up') {
3010
+ inlineTop = 'auto';
3011
+ }
3012
+ else if (resolvedInlinePosition === 'start' || resolvedInlinePosition === 'end') {
3013
+ inlineTop = 0;
3014
+ }
3015
+ let inlineLeft = 0;
3016
+ if (resolvedInlinePosition === 'start') {
3017
+ inlineLeft = 'auto';
3018
+ }
3019
+ else if (resolvedInlinePosition === 'end') {
3020
+ inlineLeft = '100%';
3021
+ }
3022
+ let inlineRight = 'auto';
3023
+ if (resolvedInlinePosition === 'start') {
3024
+ inlineRight = '100%';
3025
+ }
3026
+ let inlineTransform = 'translateY(4px)';
3027
+ if (resolvedInlinePosition === 'up') {
3028
+ inlineTransform = 'translateY(-4px)';
3029
+ }
3030
+ else if (resolvedInlinePosition === 'start') {
3031
+ inlineTransform = 'translateX(-4px)';
3032
+ }
3033
+ else if (resolvedInlinePosition === 'end') {
3034
+ inlineTransform = 'translateX(4px)';
3035
+ }
3036
+ const inlineMenuStyle = {
3037
+ position: 'absolute',
3038
+ top: inlineTop,
3039
+ bottom: resolvedInlinePosition === 'up' ? '100%' : 'auto',
3040
+ left: inlineLeft,
3041
+ right: inlineRight,
3042
+ transform: inlineTransform,
3043
+ };
3044
+ const menuItems = (jsxRuntime.jsx("ul", { ref: menuRef, role: "menu", className: classNames('dropdown-menu p-2', { show: open }, classNameMenu), style: portalMenuStyle !== null && portalMenuStyle !== void 0 ? portalMenuStyle : inlineMenuStyle, children: actions.map((action, index) => {
3045
+ if (action.isDivider) {
3046
+ return jsxRuntime.jsx("hr", { className: "dropdown-divider" }, index);
3047
+ }
3048
+ return (jsxRuntime.jsx("li", { children: action.href ? (jsxRuntime.jsxs("a", { className: getItemClass(action), href: action.href, onClick: (e) => {
3049
+ if (action.disabled) {
3050
+ e.preventDefault();
3051
+ }
3052
+ else {
3053
+ setOpen(false);
3054
+ }
3055
+ }, children: [action.icon && jsxRuntime.jsx(DIcon, { icon: action.icon, className: "me-2", size: "1rem" }), action.label] })) : (jsxRuntime.jsxs("button", { className: getItemClass(action), type: "button", onClick: () => {
3056
+ var _a;
3057
+ if (!action.disabled) {
3058
+ (_a = action.onClick) === null || _a === void 0 ? void 0 : _a.call(action);
3059
+ setOpen(false);
3060
+ }
3061
+ }, disabled: action.disabled, children: [action.icon && jsxRuntime.jsx(DIcon, { icon: action.icon, className: "me-2", size: "1rem" }), action.label] })) }, index));
3062
+ }) }));
3063
+ if (asPortal) {
3064
+ return (jsxRuntime.jsxs("div", { ref: toggleRef, className: classNames('dropdown', className), children: [ToggleElement, open && menuCoords && reactDom.createPortal(menuItems, document.body)] }));
3065
+ }
3066
+ return (jsxRuntime.jsxs("div", { className: classNames(`dropdown drop-${resolvedInlinePosition}`, className), ref: dropdownRef, children: [ToggleElement, menuItems] }));
2710
3067
  }
2711
3068
 
2712
3069
  function useScreenshot() {
@@ -2922,29 +3279,110 @@ function render(renderable) {
2922
3279
  return null;
2923
3280
  return typeof renderable === 'function' ? renderable() : renderable;
2924
3281
  }
2925
- function DDataStateWrapper({ isLoading, isError, data, onRetry, renderLoading, renderEmpty, renderError, children, }) {
3282
+ function DDataStateWrapper({ isLoading, isError, data, onRetry, messages, renderLoading, renderEmpty, renderError, children, }) {
2926
3283
  // 1. Loading
2927
3284
  if (isLoading) {
2928
3285
  if (renderLoading)
2929
3286
  return render(renderLoading);
2930
- return jsxRuntime.jsx(LoadingState, {});
3287
+ return jsxRuntime.jsx(LoadingState, { ariaLabel: messages === null || messages === void 0 ? void 0 : messages.loading });
2931
3288
  }
2932
3289
  // 2. Error
2933
3290
  if (isError) {
2934
3291
  if (renderError)
2935
3292
  return render(renderError);
2936
- return (jsxRuntime.jsx(ErrorState, { onRetry: onRetry }));
3293
+ return (jsxRuntime.jsx(ErrorState, { onRetry: onRetry, message: messages === null || messages === void 0 ? void 0 : messages.error, retryMessage: messages === null || messages === void 0 ? void 0 : messages.retry }));
2937
3294
  }
2938
3295
  // 3. Empty
2939
3296
  if (!(data === null || data === void 0 ? void 0 : data.length)) {
2940
3297
  if (renderEmpty)
2941
3298
  return render(renderEmpty);
2942
- return (jsxRuntime.jsx(EmptyState, {}));
3299
+ return (jsxRuntime.jsx(EmptyState, { message: messages === null || messages === void 0 ? void 0 : messages.empty }));
2943
3300
  }
2944
3301
  // 4. Success
2945
3302
  return jsxRuntime.jsx(jsxRuntime.Fragment, { children: children(data) });
2946
3303
  }
2947
3304
 
3305
+ function DConfirmModalUI({ entry }) {
3306
+ 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;
3307
+ const [confirmationCode, setConfirmationCode] = React.useState('');
3308
+ const [internalLoading, setInternalLoading] = React.useState(false);
3309
+ const isMountedRef = React.useRef(true);
3310
+ React.useEffect(() => {
3311
+ const cleanup = () => {
3312
+ isMountedRef.current = false;
3313
+ };
3314
+ return cleanup;
3315
+ }, []);
3316
+ const handleClose = React.useCallback(() => {
3317
+ onCloseAction();
3318
+ }, [onCloseAction]);
3319
+ const isConfirmDisabled = Boolean(internalLoading
3320
+ || (critical && confirmationCode !== critical.code));
3321
+ const handleConfirmClick = React.useCallback(() => {
3322
+ setInternalLoading(true);
3323
+ onConfirmAction()
3324
+ .catch(() => undefined)
3325
+ .finally(() => {
3326
+ // Only update state if the component is still mounted.
3327
+ // onConfirmAction() removes the entry on success, which unmounts this component.
3328
+ if (isMountedRef.current) {
3329
+ setInternalLoading(false);
3330
+ }
3331
+ });
3332
+ }, [onConfirmAction]);
3333
+ return (jsxRuntime.jsxs(DModal, { name: entry.id, size: "lg", className: `confirm-modal ${critical ? 'critical-modal' : ''}`, children: [jsxRuntime.jsx(DModal.Header, { onClose: handleClose, showCloseButton: true, children: jsxRuntime.jsx("h5", { className: "fw-bold", children: title }) }), jsxRuntime.jsxs(DModal.Body, { className: "py-3 px-5", children: [jsxRuntime.jsx("p", { className: "mb-4", children: message }), critical && (jsxRuntime.jsx(ForwardedDInput, { type: "text", label: critical.codeLabel || 'Confirmation code', placeholder: critical.inputPlaceholder, value: confirmationCode, onChange: (value) => setConfirmationCode(value), className: "mb-4", autoFocus: true }))] }), jsxRuntime.jsxs(DModal.Footer, { children: [jsxRuntime.jsx(DButton, { text: cancelLabel, variant: "outline", color: "secondary", onClick: handleClose, disabled: internalLoading }), jsxRuntime.jsx(DButton, { text: confirmLabel, color: confirmColor, onClick: handleConfirmClick, disabled: isConfirmDisabled, loading: internalLoading })] })] }));
3334
+ }
3335
+
3336
+ /**
3337
+ * Container that renders confirm modals into the specified portal node.
3338
+ *
3339
+ * Must be explicitly mounted by the user (typically as a sibling of app content
3340
+ * within `DContextProvider`). The container floats above the portal stack and
3341
+ * intercepts Escape key events to close the top confirm modal without affecting
3342
+ * underlying modals or overlays.
3343
+ *
3344
+ * @example
3345
+ * <DContextProvider>
3346
+ * <App />
3347
+ * <DConfirmModalContainer nodeId="d-portal" />
3348
+ * </DContextProvider>
3349
+ */
3350
+ function DConfirmModalContainer({ nodeId }) {
3351
+ const store = useConfirmModalStore();
3352
+ const [entries, setEntries] = React.useState([]);
3353
+ React.useEffect(() => {
3354
+ const unsubscribe = store.subscribe((next) => {
3355
+ setEntries(next);
3356
+ });
3357
+ return unsubscribe;
3358
+ }, [store]);
3359
+ // Capture Escape keydown to close the top confirm modal without affecting
3360
+ // the underlying portal stack (which also handles Escape).
3361
+ React.useEffect(() => {
3362
+ if (entries.length === 0) {
3363
+ return () => { };
3364
+ }
3365
+ const handleEscapeCapture = (event) => {
3366
+ if (event.key === 'Escape') {
3367
+ // Close the top (last) confirm modal
3368
+ entries[entries.length - 1].onCloseAction();
3369
+ // Prevent the event from reaching other handlers (e.g., DPortalContextProvider)
3370
+ event.stopPropagation();
3371
+ event.preventDefault();
3372
+ }
3373
+ };
3374
+ document.addEventListener('keydown', handleEscapeCapture, true);
3375
+ return () => {
3376
+ document.removeEventListener('keydown', handleEscapeCapture, true);
3377
+ };
3378
+ }, [entries]);
3379
+ const portalNode = document.getElementById(nodeId);
3380
+ if (!portalNode || entries.length === 0) {
3381
+ return null;
3382
+ }
3383
+ return reactDom.createPortal(jsxRuntime.jsx(framerMotion.AnimatePresence, { children: entries.map((entry) => (jsxRuntime.jsxs(framerMotion.motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0, transition: { delay: 0.3 } }, transition: { duration: 0.15, ease: 'linear' }, children: [jsxRuntime.jsx("div", { className: "backdrop backdrop-confirm-modal", onClick: entry.onCloseAction, role: "presentation" }), jsxRuntime.jsx(DConfirmModalUI, { entry: entry })] }, entry.id))) }), portalNode);
3384
+ }
3385
+
2948
3386
  Object.defineProperty(exports, "getErrorMessage", {
2949
3387
  enumerable: true,
2950
3388
  get: function () { return reactErrorBoundary.getErrorMessage; }
@@ -2968,6 +3406,7 @@ exports.DCarousel = DCarousel;
2968
3406
  exports.DCarouselSlide = DCarouselSlide;
2969
3407
  exports.DChip = DChip;
2970
3408
  exports.DCollapse = DCollapse;
3409
+ exports.DConfirmModalContainer = DConfirmModalContainer;
2971
3410
  exports.DContext = DContext;
2972
3411
  exports.DContextProvider = DContextProvider;
2973
3412
  exports.DCreditCard = DCreditCard;
@@ -2987,6 +3426,7 @@ exports.DInputPassword = ForwardedDInputPassword;
2987
3426
  exports.DInputPhone = ForwardedDInputPhone;
2988
3427
  exports.DInputPin = DInputPin;
2989
3428
  exports.DInputRange = ForwardedDInputRange;
3429
+ exports.DInputSearch = ForwardedDInputSearch;
2990
3430
  exports.DInputSelect = DInputSelect;
2991
3431
  exports.DInputSwitch = DInputSwitch;
2992
3432
  exports.DLayout = DLayout;
@@ -3017,6 +3457,9 @@ exports.DToast = DToast;
3017
3457
  exports.DToastContainer = DToastContainer;
3018
3458
  exports.DTooltip = DTooltip;
3019
3459
  exports.DVoucher = DVoucher;
3460
+ exports.EmptyState = EmptyState;
3461
+ exports.ErrorState = ErrorState;
3462
+ exports.LoadingState = LoadingState;
3020
3463
  exports.changeQueryString = changeQueryString;
3021
3464
  exports.checkMediaQuery = checkMediaQuery;
3022
3465
  exports.configureI18n = configureI8n;
@@ -3024,6 +3467,7 @@ exports.formatCurrency = formatCurrency;
3024
3467
  exports.getCssVariable = getCssVariable;
3025
3468
  exports.getQueryString = getQueryString;
3026
3469
  exports.subscribeToMediaQuery = subscribeToMediaQuery;
3470
+ exports.useConfirmModal = useConfirmModal;
3027
3471
  exports.useDContext = useDContext;
3028
3472
  exports.useDPortalContext = useDPortalContext;
3029
3473
  exports.useDToast = useDToast;