@dynamic-framework/ui-react 2.10.0 → 2.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/DPortalStack-6zJm6JoD.js +14 -0
- package/dist/DPortalStack-6zJm6JoD.js.map +1 -0
- package/dist/DPortalStack-D1-GGU4C.js +12 -0
- package/dist/DPortalStack-D1-GGU4C.js.map +1 -0
- package/dist/api.json +7429 -0
- package/dist/css/dynamic-ui.css +95 -26
- package/dist/css/dynamic-ui.min.css +2 -2
- package/dist/esm/components/DAlert/DAlert.js +2 -2
- package/dist/esm/components/DAlert/DAlert.js.map +1 -1
- package/dist/esm/components/DButtonIcon/DButtonIcon.js +6 -3
- package/dist/esm/components/DButtonIcon/DButtonIcon.js.map +1 -1
- package/dist/esm/components/DDataStateWrapper/DDataStateWrapper.js +10 -3
- package/dist/esm/components/DDataStateWrapper/DDataStateWrapper.js.map +1 -1
- package/dist/esm/components/DErrorBoundary/DErrorBoundary.js +3 -3
- package/dist/esm/components/DErrorBoundary/DErrorBoundary.js.map +1 -1
- package/dist/esm/components/DErrorBoundary/components/DefaultErrorBoundary.js +2 -2
- package/dist/esm/components/DErrorBoundary/components/DefaultErrorBoundary.js.map +1 -1
- package/dist/esm/components/DInput/DInput.js +15 -3
- package/dist/esm/components/DInput/DInput.js.map +1 -1
- package/dist/esm/components/DInputCheck/DInputCheck.js +45 -8
- package/dist/esm/components/DInputCheck/DInputCheck.js.map +1 -1
- package/dist/esm/components/DInputCounter/DInputCounter.js +78 -20
- package/dist/esm/components/DInputCounter/DInputCounter.js.map +1 -1
- package/dist/esm/components/DInputCurrency/DInputCurrency.js +8 -3
- package/dist/esm/components/DInputCurrency/DInputCurrency.js.map +1 -1
- package/dist/esm/components/DInputPhone/DInputPhone.js +14 -2
- package/dist/esm/components/DInputPhone/DInputPhone.js.map +1 -1
- package/dist/esm/components/DInputPin/DInputPin.js +3 -1
- package/dist/esm/components/DInputPin/DInputPin.js.map +1 -1
- package/dist/esm/components/DInputRange/DInputRange.js +16 -2
- package/dist/esm/components/DInputRange/DInputRange.js.map +1 -1
- package/dist/esm/components/DInputSelect/DInputSelect.js +18 -4
- package/dist/esm/components/DInputSelect/DInputSelect.js.map +1 -1
- package/dist/esm/components/DInputSwitch/DInputSwitch.js +20 -9
- package/dist/esm/components/DInputSwitch/DInputSwitch.js.map +1 -1
- package/dist/esm/components/DListGroup/DListGroup.js +10 -2
- package/dist/esm/components/DListGroup/DListGroup.js.map +1 -1
- package/dist/esm/components/DListGroup/ListGroupContext.js +11 -0
- package/dist/esm/components/DListGroup/ListGroupContext.js.map +1 -0
- package/dist/esm/components/DListGroup/components/DListGroupItem.js +34 -16
- package/dist/esm/components/DListGroup/components/DListGroupItem.js.map +1 -1
- package/dist/esm/components/DListGroup/components/warnInvalidListMarkup.js +30 -0
- package/dist/esm/components/DListGroup/components/warnInvalidListMarkup.js.map +1 -0
- package/dist/esm/components/DPasswordStrengthMeter/DPasswordStrengthMeter.js +2 -2
- package/dist/esm/components/DPasswordStrengthMeter/DPasswordStrengthMeter.js.map +1 -1
- package/dist/esm/components/DProgress/DProgress.js +9 -3
- package/dist/esm/components/DProgress/DProgress.js.map +1 -1
- package/dist/esm/components/DSelect/DSelect.js +31 -3
- package/dist/esm/components/DSelect/DSelect.js.map +1 -1
- package/dist/esm/components/DSelect/createAriaGuidance.js +57 -0
- package/dist/esm/components/DSelect/createAriaGuidance.js.map +1 -0
- package/dist/esm/components/DTabs/DTabs.js +29 -40
- package/dist/esm/components/DTabs/DTabs.js.map +1 -1
- package/dist/esm/components/DTabs/TabContext.js +3 -2
- package/dist/esm/components/DTabs/TabContext.js.map +1 -1
- package/dist/esm/components/DTabs/components/DTabContent.js +2 -2
- package/dist/esm/components/DTabs/components/DTabContent.js.map +1 -1
- package/dist/esm/components/DTabs/components/DTabsProvider.js +22 -0
- package/dist/esm/components/DTabs/components/DTabsProvider.js.map +1 -0
- package/dist/esm/components/DTabs/index.js +1 -0
- package/dist/esm/components/DTabs/index.js.map +1 -1
- package/dist/esm/components/DToast/DToast.js +7 -2
- package/dist/esm/components/DToast/DToast.js.map +1 -1
- package/dist/esm/components/DToastContainer/useDToast.js +5 -5
- package/dist/esm/components/DToastContainer/useDToast.js.map +1 -1
- package/dist/esm/components/internal/DFormLabel.js +46 -0
- package/dist/esm/components/internal/DFormLabel.js.map +1 -0
- package/dist/esm/contexts/DPortalContext.js +43 -8
- package/dist/esm/contexts/DPortalContext.js.map +1 -1
- package/dist/esm/contexts/portal/DPortalStack.js +12 -0
- package/dist/esm/contexts/portal/DPortalStack.js.map +1 -0
- package/dist/esm/hooks/useControlledState.js +51 -0
- package/dist/esm/hooks/useControlledState.js.map +1 -0
- package/dist/esm/hooks/useInputCurrency.js +73 -19
- package/dist/esm/hooks/useInputCurrency.js.map +1 -1
- package/dist/esm/hooks/useMediaBreakpointUp.js +42 -3
- package/dist/esm/hooks/useMediaBreakpointUp.js.map +1 -1
- package/dist/esm/hooks/useResponsiveProp.js +13 -2
- package/dist/esm/hooks/useResponsiveProp.js.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/utils/hasLabelContent.js +48 -0
- package/dist/esm/utils/hasLabelContent.js.map +1 -0
- package/dist/esm/utils/isTextLabel.js +23 -0
- package/dist/esm/utils/isTextLabel.js.map +1 -0
- package/dist/esm/utils/labelClickGuard.js +61 -0
- package/dist/esm/utils/labelClickGuard.js.map +1 -0
- package/dist/esm/utils/warnLabelUsage.js +57 -0
- package/dist/esm/utils/warnLabelUsage.js.map +1 -0
- package/dist/icons-core.json +1 -1
- package/dist/index.esm.js +865 -163
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +864 -161
- package/dist/index.js.map +1 -1
- package/dist/types/components/DAlert/DAlert.d.ts +16 -2
- package/dist/types/components/DButtonIcon/DButtonIcon.d.ts +9 -1
- package/dist/types/components/DDataStateWrapper/DDataStateWrapper.d.ts +29 -4
- package/dist/types/components/DDataStateWrapper/index.d.ts +1 -1
- package/dist/types/components/DErrorBoundary/DErrorBoundary.d.ts +6 -1
- package/dist/types/components/DErrorBoundary/components/DefaultErrorBoundary.d.ts +3 -1
- package/dist/types/components/DInput/DInput.d.ts +11 -2
- package/dist/types/components/DInputCheck/DInputCheck.d.ts +28 -3
- package/dist/types/components/DInputCounter/DInputCounter.d.ts +15 -3
- package/dist/types/components/DInputCurrency/DInputCurrency.d.ts +10 -3
- package/dist/types/components/DInputPhone/DInputPhone.d.ts +11 -2
- package/dist/types/components/DInputPin/DInputPin.d.ts +12 -2
- package/dist/types/components/DInputRange/DInputRange.d.ts +15 -2
- package/dist/types/components/DInputSelect/DInputSelect.d.ts +18 -3
- package/dist/types/components/DInputSwitch/DInputSwitch.d.ts +28 -3
- package/dist/types/components/DListGroup/DListGroup.d.ts +12 -1
- package/dist/types/components/DListGroup/ListGroupContext.d.ts +8 -0
- package/dist/types/components/DListGroup/components/DListGroupItem.d.ts +6 -1
- package/dist/types/components/DListGroup/components/warnInvalidListMarkup.d.ts +13 -0
- package/dist/types/components/DPasswordStrengthMeter/DPasswordStrengthMeter.d.ts +18 -3
- package/dist/types/components/DProgress/DProgress.d.ts +13 -1
- package/dist/types/components/DSelect/DSelect.d.ts +23 -3
- package/dist/types/components/DSelect/createAriaGuidance.d.ts +21 -0
- package/dist/types/components/DTabs/DTabs.d.ts +13 -2
- package/dist/types/components/DTabs/TabContext.d.ts +5 -0
- package/dist/types/components/DTabs/components/DTabsProvider.d.ts +11 -0
- package/dist/types/components/DTabs/index.d.ts +1 -0
- package/dist/types/components/DToast/DToast.d.ts +10 -3
- package/dist/types/components/DToastContainer/useDToast.d.ts +8 -1
- package/dist/types/components/index.d.ts +2 -2
- package/dist/types/components/interface.d.ts +7 -0
- package/dist/types/components/internal/DFormLabel.d.ts +30 -0
- package/dist/types/contexts/portal/DPortalStack.d.ts +13 -0
- package/dist/types/hooks/useControlledState.d.ts +17 -0
- package/dist/types/hooks/useInputCurrency.d.ts +26 -1
- package/dist/types/hooks/useMediaBreakpointUp.d.ts +10 -0
- package/dist/types/utils/hasLabelContent.d.ts +30 -0
- package/dist/types/utils/isTextLabel.d.ts +17 -0
- package/dist/types/utils/labelClickGuard.d.ts +20 -0
- package/dist/types/utils/warnLabelUsage.d.ts +43 -0
- package/package.json +8 -3
- package/src/style/abstracts/variables/_body.scss +2 -2
- package/src/style/abstracts/variables/_chips.scss +2 -0
- package/src/style/abstracts/variables/_colors.scss +2 -2
- package/src/style/base/_buttons.scss +4 -1
- package/src/style/base/_input-group.scss +5 -2
- package/src/style/base/_list-group.scss +55 -0
- package/src/style/components/_d-chip.scss +7 -2
- package/src/style/components/_d-tabs.scss +3 -0
- package/src/style/helpers/_color-bg.scss +5 -0
- package/src/style/root/_root.scss +9 -0
package/dist/index.esm.js
CHANGED
|
@@ -1,13 +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, createElement, forwardRef, useId,
|
|
3
|
+
import React, { useEffect, useState, useCallback, useMemo, useContext, useRef, createContext, useLayoutEffect, useSyncExternalStore, createElement, isValidElement, forwardRef, useId, cloneElement, Children } from 'react';
|
|
4
4
|
import { __rest } from 'tslib';
|
|
5
5
|
import * as LucideIcons from 'lucide-react';
|
|
6
6
|
import { isValidElementType } from 'react-is';
|
|
7
7
|
import { createPortal } from 'react-dom';
|
|
8
|
-
import { AnimatePresence, motion } from 'framer-motion';
|
|
9
8
|
import { fromEvent } from 'file-selector';
|
|
10
9
|
import { SplideSlide, Splide, SplideTrack } from '@splidejs/react-splide';
|
|
10
|
+
import { motion, AnimatePresence } from 'framer-motion';
|
|
11
11
|
import currency from 'currency.js';
|
|
12
12
|
import DatePicker from 'react-datepicker';
|
|
13
13
|
import { getYear, format, getMonth } from 'date-fns';
|
|
@@ -151,11 +151,30 @@ function getKeyboardFocusableElements(container) {
|
|
|
151
151
|
].filter((element) => !element.hasAttribute('disabled'));
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
+
const loadPortalStack = () => import('./DPortalStack-D1-GGU4C.js').then((module) => module.default);
|
|
154
155
|
const DPortalContext = createContext(undefined);
|
|
155
156
|
function DPortalContextProvider({ portalName, children, availablePortals, }) {
|
|
156
157
|
const { created } = usePortal(portalName);
|
|
157
158
|
const [stack, { push, pop }] = useStackState([]);
|
|
159
|
+
const [PortalStack, setPortalStack] = useState(null);
|
|
160
|
+
const pending = useRef([]);
|
|
161
|
+
const ready = useRef(false);
|
|
158
162
|
useDisableBodyScrollEffect(Boolean(stack.length));
|
|
163
|
+
// Loads framer-motion off the critical path only when portals are configured,
|
|
164
|
+
// so the first openPortal does not wait for it.
|
|
165
|
+
const hasPortals = Object.keys(availablePortals !== null && availablePortals !== void 0 ? availablePortals : {}).length > 0;
|
|
166
|
+
useEffect(() => {
|
|
167
|
+
if (!hasPortals)
|
|
168
|
+
return;
|
|
169
|
+
loadPortalStack()
|
|
170
|
+
.then((Stack) => {
|
|
171
|
+
ready.current = true;
|
|
172
|
+
setPortalStack(() => Stack);
|
|
173
|
+
})
|
|
174
|
+
.catch(() => {
|
|
175
|
+
// openPortal retries the import and reports the failure.
|
|
176
|
+
});
|
|
177
|
+
}, [hasPortals]);
|
|
159
178
|
const openPortal = useCallback(
|
|
160
179
|
// eslint-disable-next-line prefer-arrow-callback
|
|
161
180
|
function openPortalImpl(name, payload) {
|
|
@@ -171,10 +190,30 @@ function DPortalContextProvider({ portalName, children, availablePortals, }) {
|
|
|
171
190
|
}
|
|
172
191
|
// K is a specific member of keyof T & string so the object satisfies
|
|
173
192
|
// InternalStackItem<T>, but TS can't verify generic-over-union assignability.
|
|
174
|
-
|
|
193
|
+
const item = { name, Component, payload };
|
|
194
|
+
if (ready.current) {
|
|
195
|
+
push(item);
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
// Not loaded yet: the portal enters the stack together with its renderer.
|
|
199
|
+
pending.current.push(item);
|
|
200
|
+
loadPortalStack().then((Stack) => {
|
|
201
|
+
ready.current = true;
|
|
202
|
+
setPortalStack(() => Stack);
|
|
203
|
+
pending.current.splice(0).forEach(push);
|
|
204
|
+
}).catch((error) => {
|
|
205
|
+
pending.current = [];
|
|
206
|
+
// eslint-disable-next-line no-console
|
|
207
|
+
console.error('[DPortalContext] Could not load the portal stack', error);
|
|
208
|
+
});
|
|
209
|
+
}
|
|
175
210
|
(_a = document.activeElement) === null || _a === void 0 ? void 0 : _a.blur();
|
|
176
211
|
}, [availablePortals, push]);
|
|
177
212
|
const closePortal = useCallback(() => {
|
|
213
|
+
if (pending.current.length > 0) {
|
|
214
|
+
pending.current.pop();
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
178
217
|
// pop() is safe on empty stacks, so close remains idempotent.
|
|
179
218
|
pop();
|
|
180
219
|
}, [pop]);
|
|
@@ -231,13 +270,10 @@ function DPortalContextProvider({ portalName, children, availablePortals, }) {
|
|
|
231
270
|
window.removeEventListener('keydown', keyEvent);
|
|
232
271
|
};
|
|
233
272
|
}, [handleClose, portalName, stack.length]);
|
|
234
|
-
return (jsxs(DPortalContext.Provider, { value: value, children: [children, created && createPortal(
|
|
273
|
+
return (jsxs(DPortalContext.Provider, { value: value, children: [children, created && PortalStack && createPortal(
|
|
235
274
|
// eslint-disable-next-line max-len
|
|
236
275
|
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
|
237
|
-
jsx("div", { onClick: ({ target }) => handleClose(target), onKeyDown: () => { }, children: jsx(
|
|
238
|
-
jsx(motion.div, { className: "backdrop", initial: { opacity: 0 }, animate: { opacity: 0.5 }, exit: { opacity: 0, transition: { delay: 0.3 } }, transition: { duration: 0.15, ease: 'linear' } }, `${name}-backdrop`),
|
|
239
|
-
jsx(Component, { name: name, payload: payload }, name),
|
|
240
|
-
]) }) }), document.getElementById(portalName))] }));
|
|
276
|
+
jsx("div", { onClick: ({ target }) => handleClose(target), onKeyDown: () => { }, children: jsx(PortalStack, { stack: stack }) }), document.getElementById(portalName))] }));
|
|
241
277
|
}
|
|
242
278
|
/**
|
|
243
279
|
* Hook to open/close registered portals (modals, offcanvas, etc.).
|
|
@@ -512,9 +548,43 @@ function useMediaQuery(mediaQuery, useListener = false) {
|
|
|
512
548
|
return useSyncExternalStore(useListener ? (cb) => subscribeToMediaQuery(mediaQuery, cb) : noop, () => (mediaQuery ? checkMediaQuery(mediaQuery) : true), () => false);
|
|
513
549
|
}
|
|
514
550
|
|
|
515
|
-
|
|
551
|
+
const BREAKPOINTS = ['xs', 'sm', 'md', 'lg', 'xl', 'xxl'];
|
|
552
|
+
// Breakpoints read from the CSS on first use and shared by every component
|
|
553
|
+
// that resolves responsive props: one computed-style read for the page,
|
|
554
|
+
// instead of one per breakpoint per render. `dynamic-ui.css` has to be loaded
|
|
555
|
+
// before rendering; if it isn't, the empty result is kept too, so a
|
|
556
|
+
// misconfigured tree doesn't pay the read on every render (responsive props
|
|
557
|
+
// then keep their default value and useResponsiveProp warns in development).
|
|
558
|
+
let cssBreakpoints = null;
|
|
559
|
+
function readCssBreakpoint(breakpoint) {
|
|
560
|
+
if (cssBreakpoints)
|
|
561
|
+
return cssBreakpoints[breakpoint];
|
|
562
|
+
if (typeof document === 'undefined')
|
|
563
|
+
return '';
|
|
564
|
+
const style = getComputedStyle(document.documentElement);
|
|
565
|
+
const read = Object.fromEntries(BREAKPOINTS.map((name) => [
|
|
566
|
+
name,
|
|
567
|
+
style.getPropertyValue(`--${PREFIX_BS}breakpoint-${name}`).trim(),
|
|
568
|
+
]));
|
|
569
|
+
cssBreakpoints = read;
|
|
570
|
+
return read[breakpoint];
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Pixel value of a breakpoint. `DContextProvider` reads them from the CSS and
|
|
574
|
+
* shares them through the context; outside of it (a tree without the
|
|
575
|
+
* provider, or its first render) they are read from the same CSS variables
|
|
576
|
+
* here, so responsive props resolve either way.
|
|
577
|
+
*/
|
|
578
|
+
function useBreakpointValue(breakpoint) {
|
|
516
579
|
const { breakpoints } = useDContext();
|
|
517
|
-
const
|
|
580
|
+
const fromContext = breakpoints[breakpoint];
|
|
581
|
+
return useMemo(() => fromContext || readCssBreakpoint(breakpoint), [fromContext, breakpoint]);
|
|
582
|
+
}
|
|
583
|
+
function useMediaBreakpointUp(breakpoint, useListener = false) {
|
|
584
|
+
const value = useBreakpointValue(breakpoint);
|
|
585
|
+
// Without a breakpoint the query can't match; `not all` keeps it false
|
|
586
|
+
// instead of an invalid `(min-width: )`.
|
|
587
|
+
const mediaQuery = value ? `(min-width: ${value})` : 'not all';
|
|
518
588
|
return useMediaQuery(mediaQuery, useListener);
|
|
519
589
|
}
|
|
520
590
|
function useMediaBreakpointUpXs(useListener = false) {
|
|
@@ -536,6 +606,7 @@ function useMediaBreakpointUpXxl(useListener = false) {
|
|
|
536
606
|
return useMediaBreakpointUp('xxl', useListener);
|
|
537
607
|
}
|
|
538
608
|
|
|
609
|
+
let warnedMissingBreakpoints = false;
|
|
539
610
|
/**
|
|
540
611
|
* React hook to resolve a responsive property value based on the current viewport breakpoint.
|
|
541
612
|
*
|
|
@@ -561,7 +632,16 @@ function useResponsiveProp(useListener = false) {
|
|
|
561
632
|
const bpLgUp = useMediaBreakpointUpLg(useListener);
|
|
562
633
|
const bpXlUp = useMediaBreakpointUpXl(useListener);
|
|
563
634
|
const bpXxlUp = useMediaBreakpointUpXxl(useListener);
|
|
635
|
+
// `xs` is `0` and can't tell a missing variable apart, so `sm` is checked.
|
|
636
|
+
const hasBreakpoints = !!useBreakpointValue('sm');
|
|
564
637
|
const responsivePropValue = useCallback((prop) => {
|
|
638
|
+
if (process.env.NODE_ENV !== 'production' && !hasBreakpoints && !warnedMissingBreakpoints) {
|
|
639
|
+
warnedMissingBreakpoints = true;
|
|
640
|
+
// eslint-disable-next-line no-console
|
|
641
|
+
console.warn(`[Dynamic UI] The --${PREFIX_BS}breakpoint-* CSS variables are not available, so a `
|
|
642
|
+
+ 'responsive prop (an object by breakpoint) falls back to its default value. Load '
|
|
643
|
+
+ 'dynamic-ui.css before rendering. It never appears in production builds.');
|
|
644
|
+
}
|
|
565
645
|
// Pick the highest matched breakpoint value that is defined in prop
|
|
566
646
|
if (prop.xxl !== undefined && bpXxlUp)
|
|
567
647
|
return prop.xxl;
|
|
@@ -577,7 +657,7 @@ function useResponsiveProp(useListener = false) {
|
|
|
577
657
|
return prop.xs;
|
|
578
658
|
// Fallback: return undefined if no breakpoint matches
|
|
579
659
|
return undefined;
|
|
580
|
-
}, [bpSmUp, bpMdUp, bpLgUp, bpXlUp, bpXxlUp, bpXsUp]);
|
|
660
|
+
}, [bpSmUp, bpMdUp, bpLgUp, bpXlUp, bpXxlUp, bpXsUp, hasBreakpoints]);
|
|
581
661
|
return { responsivePropValue };
|
|
582
662
|
}
|
|
583
663
|
|
|
@@ -728,12 +808,12 @@ function DIcon(_a) {
|
|
|
728
808
|
return (jsx(DIconBase, Object.assign({ icon: icon, familyClass: propFamilyClass !== null && propFamilyClass !== void 0 ? propFamilyClass : familyClass, familyPrefix: propFamilyPrefix !== null && propFamilyPrefix !== void 0 ? propFamilyPrefix : familyPrefix, materialStyle: propMaterialStyle !== null && propMaterialStyle !== void 0 ? propMaterialStyle : materialStyle }, props)));
|
|
729
809
|
}
|
|
730
810
|
|
|
731
|
-
function DAlert({ color = 'success', icon: iconProp, iconFamilyClass, iconFamilyPrefix, iconMaterialStyle, iconClose: iconCloseProp, iconCloseFamilyClass, iconCloseFamilyPrefix, iconCloseMaterialStyle, showClose, onClose, children, id, className, style, dataAttributes, }) {
|
|
811
|
+
function DAlert({ color = 'success', role = 'alert', icon: iconProp, showIcon = true, iconFamilyClass, iconFamilyPrefix, iconMaterialStyle, iconClose: iconCloseProp, iconCloseFamilyClass, iconCloseFamilyPrefix, iconCloseMaterialStyle, showClose, closeAriaLabel = 'Close', onClose, children, id, className, style, dataAttributes, }) {
|
|
732
812
|
const { icon: { materialStyle, familyClass, familyPrefix, }, iconMap: { alert, xLg, }, } = useDContext();
|
|
733
813
|
const icon = useMemo(() => iconProp || alert[color], [alert, iconProp, color]);
|
|
734
814
|
const iconClose = useMemo(() => (iconCloseProp || xLg), [iconCloseProp, xLg]);
|
|
735
815
|
const generateClasses = useMemo(() => (Object.assign({ alert: true, [`alert-${color}`]: true, 'fade show': !!showClose }, className && { [className]: true })), [color, showClose, className]);
|
|
736
|
-
return (jsxs("div", Object.assign({ className: classNames(generateClasses), style: style, role
|
|
816
|
+
return (jsxs("div", Object.assign({ className: classNames(generateClasses), style: style }, role !== 'none' && { role }, { id: id }, dataAttributes, { children: [showIcon && icon && (jsx(DIcon, { className: "alert-icon", icon: icon, familyClass: iconFamilyClass !== null && iconFamilyClass !== void 0 ? iconFamilyClass : familyClass, familyPrefix: iconFamilyPrefix !== null && iconFamilyPrefix !== void 0 ? iconFamilyPrefix : familyPrefix, materialStyle: iconMaterialStyle !== null && iconMaterialStyle !== void 0 ? iconMaterialStyle : materialStyle })), jsx("div", { className: "alert-text", children: children }), showClose && (jsx("button", { type: "button", className: "d-close", "aria-label": closeAriaLabel, onClick: onClose, children: jsx(DIcon, { icon: iconClose, familyClass: iconCloseFamilyClass !== null && iconCloseFamilyClass !== void 0 ? iconCloseFamilyClass : familyClass, familyPrefix: iconCloseFamilyPrefix !== null && iconCloseFamilyPrefix !== void 0 ? iconCloseFamilyPrefix : familyPrefix, materialStyle: iconCloseMaterialStyle !== null && iconCloseMaterialStyle !== void 0 ? iconCloseMaterialStyle : materialStyle }) }))] })));
|
|
737
817
|
}
|
|
738
818
|
|
|
739
819
|
function DAvatar({ id, size, image, name: nameProp, useNameAsInitials = false, className, style, dataAttributes, }) {
|
|
@@ -799,6 +879,223 @@ function useProvidedRefOrCreate(providedRef) {
|
|
|
799
879
|
return providedRef !== null && providedRef !== void 0 ? providedRef : createdRef;
|
|
800
880
|
}
|
|
801
881
|
|
|
882
|
+
/**
|
|
883
|
+
* Descendants the browser already exempts from a label's click forwarding.
|
|
884
|
+
*
|
|
885
|
+
* Per the HTML spec a label does nothing for events targeted at its interactive
|
|
886
|
+
* content descendants, so these need — and must get — no help from us: calling
|
|
887
|
+
* `preventDefault` on a click targeting a link, or on an image map area, would
|
|
888
|
+
* cancel the navigation it was meant to perform.
|
|
889
|
+
*
|
|
890
|
+
* The list mirrors the spec's interactive content, image maps included, since
|
|
891
|
+
* one of those carrying a `tabindex` would otherwise fall through to the
|
|
892
|
+
* pseudo-interactive branch below and be suppressed by mistake.
|
|
893
|
+
*/
|
|
894
|
+
const NATIVE_INTERACTIVE = 'a[href], area[href], button, input, select, textarea, details, summary, img[usemap], object[usemap], audio[controls], video[controls], embed, iframe';
|
|
895
|
+
/**
|
|
896
|
+
* Descendants that act like controls without being interactive content.
|
|
897
|
+
*
|
|
898
|
+
* A tooltip or modal trigger built from a `span` with a role and a tabindex is
|
|
899
|
+
* the common case. Browsers do not exempt these, so a click on one activates
|
|
900
|
+
* the labelled control as well.
|
|
901
|
+
*/
|
|
902
|
+
const PSEUDO_INTERACTIVE = '[role="button"], [role="link"], [role="checkbox"], [role="switch"], [role="menuitem"], [tabindex]';
|
|
903
|
+
/**
|
|
904
|
+
* Stops a click on a non-native trigger inside a `<label>` from also activating
|
|
905
|
+
* the labelled control.
|
|
906
|
+
*
|
|
907
|
+
* Verified against Chromium, which forwards the click for every one of the
|
|
908
|
+
* `PSEUDO_INTERACTIVE` selectors above while exempting the native ones. Note
|
|
909
|
+
* that jsdom does not reproduce this: its `isInteractiveContent` counts any
|
|
910
|
+
* element carrying a `tabindex` as interactive content, so a jsdom test passes
|
|
911
|
+
* whether or not this guard exists. That is why the guard is unit-tested
|
|
912
|
+
* directly rather than through a rendered component.
|
|
913
|
+
*
|
|
914
|
+
* Native interactive content returns early rather than falling through, since
|
|
915
|
+
* `preventDefault` on those clicks would cancel the link navigation or the
|
|
916
|
+
* image map the label is meant to let through.
|
|
917
|
+
*
|
|
918
|
+
* `DFormLabel` wires this in the capture phase so a trigger that stops
|
|
919
|
+
* propagation cannot skip it.
|
|
920
|
+
*/
|
|
921
|
+
function labelClickGuard(event) {
|
|
922
|
+
const label = event.currentTarget;
|
|
923
|
+
const target = event.target;
|
|
924
|
+
if (!(target === null || target === void 0 ? void 0 : target.closest))
|
|
925
|
+
return;
|
|
926
|
+
/**
|
|
927
|
+
* `closest` walks the whole ancestor chain, so a match may sit above the
|
|
928
|
+
* label — a card with a `role` or a `tabindex` wrapping the control and its
|
|
929
|
+
* label, say. Acting on one of those would suppress the plain label text's
|
|
930
|
+
* own click and leave the control unresponsive, so only matches inside the
|
|
931
|
+
* label count.
|
|
932
|
+
*/
|
|
933
|
+
const inside = (match) => (!!match && match !== label && label.contains(match));
|
|
934
|
+
if (inside(target.closest(NATIVE_INTERACTIVE)))
|
|
935
|
+
return;
|
|
936
|
+
if (inside(target.closest(PSEUDO_INTERACTIVE))) {
|
|
937
|
+
event.preventDefault();
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
/**
|
|
942
|
+
* The `<label>` every form control renders, with the click guard already wired.
|
|
943
|
+
*
|
|
944
|
+
* Internal: it exists so the guard — and the lint exception it needs — lives in
|
|
945
|
+
* one place instead of being repeated across every input component.
|
|
946
|
+
*
|
|
947
|
+
* The guard runs in the capture phase, not on bubble: a tooltip or modal
|
|
948
|
+
* trigger that calls `stopPropagation()` in its own handler — a common pattern —
|
|
949
|
+
* would otherwise keep the guard from ever running, and the browser would go on
|
|
950
|
+
* to activate the labelled control. Confirmed in Chromium, where the same
|
|
951
|
+
* trigger toggles the control with a bubble-phase guard and does not with this
|
|
952
|
+
* one. Capturing changes nothing else: `preventDefault` applies to the default
|
|
953
|
+
* action, which is evaluated after propagation either way.
|
|
954
|
+
*
|
|
955
|
+
* The `jsx-a11y` rules below assume a click handler is being used to make a
|
|
956
|
+
* non-interactive element interactive, which then owes the keyboard an
|
|
957
|
+
* equivalent. This handler does the opposite: it suppresses a pointer-only
|
|
958
|
+
* behaviour, the click a label forwards to its control, and adds no way to
|
|
959
|
+
* activate anything. Keyboard support for a nested trigger is the consumer's:
|
|
960
|
+
* `<a href>` carries its own, while a `span` with a role and a tabindex is
|
|
961
|
+
* focusable but inert until they handle Enter and Space themselves.
|
|
962
|
+
*/
|
|
963
|
+
function DFormLabel({ htmlFor, className, children }) {
|
|
964
|
+
/**
|
|
965
|
+
* React 19 renders a bigint child; React 18 throws on one, and this package
|
|
966
|
+
* supports both (`react: >=18 <20`). The label helpers count bigint as text
|
|
967
|
+
* because that is what it draws as, so it is turned into its string form here
|
|
968
|
+
* — the one place every control's label passes through — rather than pushing
|
|
969
|
+
* a version check onto consumers.
|
|
970
|
+
*
|
|
971
|
+
* Only a bigint at the top level: one buried inside an array or a fragment is
|
|
972
|
+
* the consumer's own React child, and normalising it would mean walking and
|
|
973
|
+
* rebuilding the whole subtree.
|
|
974
|
+
*/
|
|
975
|
+
const content = typeof children === 'bigint' ? String(children) : children;
|
|
976
|
+
return (
|
|
977
|
+
/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,
|
|
978
|
+
jsx-a11y/no-noninteractive-element-interactions */
|
|
979
|
+
jsx("label", { htmlFor: htmlFor, className: className, onClickCapture: labelClickGuard, children: content }));
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
/**
|
|
983
|
+
* Whether a `label` node has something to render.
|
|
984
|
+
*
|
|
985
|
+
* The render paths cannot test `label` for truthiness: since the prop widened
|
|
986
|
+
* to `ReactNode` it also accepts `0`, which React renders as the text "0" and
|
|
987
|
+
* which `isTextLabel` counts as a text label, yet which a truthiness check
|
|
988
|
+
* would drop — leaving the control visibly unlabelled.
|
|
989
|
+
*
|
|
990
|
+
* Absent (`undefined`, `null`), empty (`''`) and boolean labels render nothing,
|
|
991
|
+
* so they are the ones that stand for "no label". The empty string matters
|
|
992
|
+
* because it is the default several components fall back to.
|
|
993
|
+
*
|
|
994
|
+
* Arrays are inspected rather than taken at face value: `label={items.map(...)}`
|
|
995
|
+
* over an empty list yields `[]`, while a list of skipped entries yields
|
|
996
|
+
* `[false, null]`. Both render nothing, so treating them as content would leave
|
|
997
|
+
* an empty `<label>` on the page — and make the naming warning fire over a
|
|
998
|
+
* label that was never there.
|
|
999
|
+
*
|
|
1000
|
+
* Only arrays, though, even though `ReactNode` admits any iterable. Reading a
|
|
1001
|
+
* one-shot iterator — a generator — would exhaust it here, and the component
|
|
1002
|
+
* goes on to render the same value, so the label would vanish precisely because
|
|
1003
|
+
* we checked for it. An array can be walked as often as needed; anything else
|
|
1004
|
+
* is taken on faith.
|
|
1005
|
+
*
|
|
1006
|
+
* Anything else — an element, a portal, a generator — counts as content: what
|
|
1007
|
+
* it renders is only knowable by rendering it, and an element that draws
|
|
1008
|
+
* nothing visible is the consumer's decision rather than an absent label.
|
|
1009
|
+
*/
|
|
1010
|
+
function hasLabelContent(label) {
|
|
1011
|
+
if (label === undefined || label === null || typeof label === 'boolean') {
|
|
1012
|
+
return false;
|
|
1013
|
+
}
|
|
1014
|
+
if (typeof label === 'string') {
|
|
1015
|
+
return label !== '';
|
|
1016
|
+
}
|
|
1017
|
+
if (isValidElement(label)) {
|
|
1018
|
+
return true;
|
|
1019
|
+
}
|
|
1020
|
+
if (Array.isArray(label)) {
|
|
1021
|
+
return label.some(hasLabelContent);
|
|
1022
|
+
}
|
|
1023
|
+
return true;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
/**
|
|
1027
|
+
* Narrows a `label` node to the plain-text case.
|
|
1028
|
+
*
|
|
1029
|
+
* `label` accepts any `ReactNode` so patterns like a terms-and-conditions link
|
|
1030
|
+
* or an info trigger can live next to the field name. Anything that is not text
|
|
1031
|
+
* cannot be forwarded to a string-only attribute (`aria-label`, `placeholder`)
|
|
1032
|
+
* nor laid out by Bootstrap's `form-floating`, so those paths check this first.
|
|
1033
|
+
*
|
|
1034
|
+
* Numbers and bigints count as text: React renders them as their string form
|
|
1035
|
+
* and so does `String()`, which is what the string-only attributes end up
|
|
1036
|
+
* receiving. `bigint` is part of `ReactNode` under these React types, so
|
|
1037
|
+
* leaving it out made a label React draws as text get treated as a rich node.
|
|
1038
|
+
* React 18 cannot render one, which `DFormLabel` handles by rendering its
|
|
1039
|
+
* string form — the classification stays true for every supported React.
|
|
1040
|
+
*/
|
|
1041
|
+
function isTextLabel(label) {
|
|
1042
|
+
return typeof label === 'string'
|
|
1043
|
+
|| typeof label === 'number'
|
|
1044
|
+
|| typeof label === 'bigint';
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
/**
|
|
1048
|
+
* Components already reported, keyed by component and reason, so the same
|
|
1049
|
+
* mistake only warns once per page load however often it re-renders or however
|
|
1050
|
+
* many instances exist.
|
|
1051
|
+
*/
|
|
1052
|
+
const warnedLabels = new Set();
|
|
1053
|
+
function warnOnce(key, message) {
|
|
1054
|
+
if (warnedLabels.has(key))
|
|
1055
|
+
return;
|
|
1056
|
+
warnedLabels.add(key);
|
|
1057
|
+
// eslint-disable-next-line no-console
|
|
1058
|
+
console.warn(`[Dynamic UI] ${message} It never appears in production builds.`);
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* Warns, only outside production builds, about the two ways a non-text `label`
|
|
1062
|
+
* goes wrong.
|
|
1063
|
+
*
|
|
1064
|
+
* A text label doubles as the control's accessible name. A `ReactNode` one does
|
|
1065
|
+
* not: the name becomes whatever the subtree happens to compute to, which for a
|
|
1066
|
+
* label carrying a link or an icon trigger reads as the wrong thing or as
|
|
1067
|
+
* nothing at all — so an explicit name is needed. Which prop carries it differs
|
|
1068
|
+
* per component, hence `accessibleNameProp`: naming the wrong one would send
|
|
1069
|
+
* the developer to a prop their component rejects.
|
|
1070
|
+
*
|
|
1071
|
+
* `form-floating` is the second case: it animates a single line of text between
|
|
1072
|
+
* the placeholder and the label position, so a node with its own height or its
|
|
1073
|
+
* own interactive children breaks the layout rather than the semantics.
|
|
1074
|
+
*
|
|
1075
|
+
* Labels the render paths drop are skipped through the same `hasLabelContent`
|
|
1076
|
+
* they use, so `label={condition && <span />}` with a false condition warns
|
|
1077
|
+
* about nothing: there is no label on the page to name.
|
|
1078
|
+
*
|
|
1079
|
+
* The `process.env.NODE_ENV` guard belongs at the call site — that is what
|
|
1080
|
+
* bundlers constant-fold, dropping this module from a consumer's production
|
|
1081
|
+
* bundle.
|
|
1082
|
+
*/
|
|
1083
|
+
function warnLabelUsage({ component, label, hasAccessibleName, accessibleNameProp, floatingLabel = false, }) {
|
|
1084
|
+
if (!hasLabelContent(label) || isTextLabel(label))
|
|
1085
|
+
return;
|
|
1086
|
+
if (!hasAccessibleName) {
|
|
1087
|
+
warnOnce(`${component}:name`, `${component}: a non-text "label" does not give the control a reliable accessible name. `
|
|
1088
|
+
+ `Pass ${accessibleNameProp} with the plain-text name of the field, since the label `
|
|
1089
|
+
+ 'subtree may include links, icons or markup that read as the wrong name or as none at all.');
|
|
1090
|
+
}
|
|
1091
|
+
if (floatingLabel) {
|
|
1092
|
+
warnOnce(`${component}:floating`, `${component}: "floatingLabel" expects a text "label". `
|
|
1093
|
+
+ 'The floating layout animates a single line of text, so a node with its own height '
|
|
1094
|
+
+ 'or its own interactive children overflows the control. '
|
|
1095
|
+
+ 'Use the default label layout for rich labels.');
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
|
|
802
1099
|
function DInput(_a, ref) {
|
|
803
1100
|
var { id: idProp, style, className, label = '', disabled = false, loading = false, iconFamilyClass, iconFamilyPrefix, iconMaterialStyle, iconStart, iconStartDisabled, iconStartFamilyClass, iconStartFamilyPrefix, iconStartAriaLabel, iconStartTabIndex, iconStartMaterialStyle, iconEnd, iconEndDisabled, iconEndFamilyClass, iconEndFamilyPrefix, iconEndAriaLabel, iconEndTabIndex, iconEndMaterialStyle, hint, size, invalid = false, valid = false, floatingLabel = false, inputStart, inputEnd, value, placeholder = '', dataAttributes, readonly, onChange, onIconStartClick, onIconEndClick } = _a, inputProps = __rest(_a, ["id", "style", "className", "label", "disabled", "loading", "iconFamilyClass", "iconFamilyPrefix", "iconMaterialStyle", "iconStart", "iconStartDisabled", "iconStartFamilyClass", "iconStartFamilyPrefix", "iconStartAriaLabel", "iconStartTabIndex", "iconStartMaterialStyle", "iconEnd", "iconEndDisabled", "iconEndFamilyClass", "iconEndFamilyPrefix", "iconEndAriaLabel", "iconEndTabIndex", "iconEndMaterialStyle", "hint", "size", "invalid", "valid", "floatingLabel", "inputStart", "inputEnd", "value", "placeholder", "dataAttributes", "readonly", "onChange", "onIconStartClick", "onIconEndClick"]);
|
|
804
1101
|
const inputRef = useProvidedRefOrCreate(ref);
|
|
@@ -838,7 +1135,7 @@ function DInput(_a, ref) {
|
|
|
838
1135
|
const inputComponent = useMemo(() => (jsx("input", Object.assign({ ref: inputRef, id: id, className: classNames('form-control', {
|
|
839
1136
|
'is-invalid': invalid,
|
|
840
1137
|
'is-valid': valid,
|
|
841
|
-
}), disabled: disabled || loading, readOnly: readonly, value: value, onChange: handleOnChange }, (floatingLabel || placeholder) && { placeholder: floatingLabel ? '' : placeholder }, ariaDescribedby && { 'aria-describedby': ariaDescribedby }, inputProps))), [
|
|
1138
|
+
}), disabled: disabled || loading, readOnly: readonly, value: value, onChange: handleOnChange }, (floatingLabel || placeholder) && { placeholder: floatingLabel ? '' : placeholder }, ariaDescribedby && { 'aria-describedby': ariaDescribedby }, invalid && { 'aria-invalid': true }, inputProps))), [
|
|
842
1139
|
ariaDescribedby,
|
|
843
1140
|
disabled,
|
|
844
1141
|
handleOnChange,
|
|
@@ -853,7 +1150,7 @@ function DInput(_a, ref) {
|
|
|
853
1150
|
value,
|
|
854
1151
|
readonly,
|
|
855
1152
|
]);
|
|
856
|
-
const labelComponent = useMemo(() => (jsx(
|
|
1153
|
+
const labelComponent = useMemo(() => (jsx(DFormLabel, { htmlFor: id, children: label })), [
|
|
857
1154
|
id,
|
|
858
1155
|
label,
|
|
859
1156
|
]);
|
|
@@ -863,7 +1160,16 @@ function DInput(_a, ref) {
|
|
|
863
1160
|
}
|
|
864
1161
|
return inputComponent;
|
|
865
1162
|
}, [floatingLabel, inputComponent, labelComponent]);
|
|
866
|
-
|
|
1163
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
1164
|
+
warnLabelUsage({
|
|
1165
|
+
component: 'DInput',
|
|
1166
|
+
label,
|
|
1167
|
+
hasAccessibleName: !!inputProps['aria-label'] || !!inputProps['aria-labelledby'],
|
|
1168
|
+
accessibleNameProp: 'aria-label',
|
|
1169
|
+
floatingLabel,
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
return (jsxs("div", Object.assign({ className: className, style: style }, dataAttributes, { children: [hasLabelContent(label) && !floatingLabel && labelComponent, jsxs("div", { className: classNames({
|
|
867
1173
|
[`input-group-${size}`]: !!size,
|
|
868
1174
|
'input-group': true,
|
|
869
1175
|
'has-validation': invalid || valid,
|
|
@@ -1389,7 +1695,7 @@ function warnMissingAccessibleName(icon, kind) {
|
|
|
1389
1695
|
}
|
|
1390
1696
|
|
|
1391
1697
|
function DButtonIcon(_a) {
|
|
1392
|
-
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']);
|
|
1698
|
+
var { id, icon, size, iconSize, className, variant, state, loadingAriaLabel, iconMaterialStyle, disabled = false, color = 'primary', loading = false, href, target, rel, stopPropagationEnabled = true, style, iconFamilyClass, iconFamilyPrefix, dataAttributes, onClick, 'aria-label': ariaLabelProp } = _a, rest = __rest(_a, ["id", "icon", "size", "iconSize", "className", "variant", "state", "loadingAriaLabel", "iconMaterialStyle", "disabled", "color", "loading", "href", "target", "rel", "stopPropagationEnabled", "style", "iconFamilyClass", "iconFamilyPrefix", "dataAttributes", "onClick", 'aria-label']);
|
|
1393
1699
|
const { icon: { familyClass, familyPrefix, materialStyle, }, } = useDContext();
|
|
1394
1700
|
const generateClasses = useMemo(() => {
|
|
1395
1701
|
const variantClass = !variant || variant === 'solid'
|
|
@@ -1397,6 +1703,9 @@ function DButtonIcon(_a) {
|
|
|
1397
1703
|
: `btn-${variant}-${color}`;
|
|
1398
1704
|
return Object.assign(Object.assign(Object.assign({ 'btn d-button-icon': true, [variantClass]: true }, size && { [`btn-${size}`]: true }), (state && state !== 'disabled') && { [state]: true }), { loading });
|
|
1399
1705
|
}, [variant, color, size, state, loading]);
|
|
1706
|
+
// A responsive iconSize has to follow viewport changes, so DIcon only
|
|
1707
|
+
// listens to breakpoints when it gets an object; a plain string needs none.
|
|
1708
|
+
const useIconSizeListener = typeof iconSize === 'object';
|
|
1400
1709
|
const isDisabled = useMemo(() => (state === 'disabled' || loading || disabled), [state, loading, disabled]);
|
|
1401
1710
|
const clickHandler = useCallback((event) => {
|
|
1402
1711
|
if (stopPropagationEnabled) {
|
|
@@ -1423,11 +1732,11 @@ function DButtonIcon(_a) {
|
|
|
1423
1732
|
if (href) {
|
|
1424
1733
|
return (jsx("a", Object.assign({ id: id, href: href, target: target, rel: rel, className: classNames(generateClasses, className), style: style, onClick: clickHandler, "aria-label": ariaLabel, "aria-disabled": isDisabled }, dataAttributes, { children: loading
|
|
1425
1734
|
? (jsx("span", { className: "spinner-border spinner-border-sm", role: "status", "aria-hidden": "true", children: jsx("span", { className: "visually-hidden", children: "Loading..." }) }))
|
|
1426
|
-
: (jsx(DIcon, { icon: icon, familyClass: iconFamilyClass !== null && iconFamilyClass !== void 0 ? iconFamilyClass : familyClass, familyPrefix: iconFamilyPrefix !== null && iconFamilyPrefix !== void 0 ? iconFamilyPrefix : familyPrefix, materialStyle: iconMaterialStyle !== null && iconMaterialStyle !== void 0 ? iconMaterialStyle : materialStyle })) })));
|
|
1735
|
+
: (jsx(DIcon, { icon: icon, size: iconSize, useListenerSize: useIconSizeListener, familyClass: iconFamilyClass !== null && iconFamilyClass !== void 0 ? iconFamilyClass : familyClass, familyPrefix: iconFamilyPrefix !== null && iconFamilyPrefix !== void 0 ? iconFamilyPrefix : familyPrefix, materialStyle: iconMaterialStyle !== null && iconMaterialStyle !== void 0 ? iconMaterialStyle : materialStyle })) })));
|
|
1427
1736
|
}
|
|
1428
1737
|
return (jsx("button", Object.assign({ className: classNames(generateClasses, className), style: style, disabled: state === 'disabled' || loading, onClick: clickHandler, "aria-label": ariaLabel }, dataAttributes, rest, { children: loading
|
|
1429
1738
|
? (jsx("span", { className: "spinner-border spinner-border-sm", role: "status", "aria-hidden": "true", children: jsx("span", { className: "visually-hidden", children: "Loading..." }) }))
|
|
1430
|
-
: (jsx(DIcon, { icon: icon, familyClass: iconFamilyClass !== null && iconFamilyClass !== void 0 ? iconFamilyClass : familyClass, familyPrefix: iconFamilyPrefix !== null && iconFamilyPrefix !== void 0 ? iconFamilyPrefix : familyPrefix, materialStyle: iconMaterialStyle !== null && iconMaterialStyle !== void 0 ? iconMaterialStyle : materialStyle })) })));
|
|
1739
|
+
: (jsx(DIcon, { icon: icon, size: iconSize, useListenerSize: useIconSizeListener, familyClass: iconFamilyClass !== null && iconFamilyClass !== void 0 ? iconFamilyClass : familyClass, familyPrefix: iconFamilyPrefix !== null && iconFamilyPrefix !== void 0 ? iconFamilyPrefix : familyPrefix, materialStyle: iconMaterialStyle !== null && iconMaterialStyle !== void 0 ? iconMaterialStyle : materialStyle })) })));
|
|
1431
1740
|
}
|
|
1432
1741
|
|
|
1433
1742
|
function DCardHeader({ className, style, children, }) {
|
|
@@ -1623,24 +1932,102 @@ function DSelectPlaceholder(_a) {
|
|
|
1623
1932
|
return (jsx(components.Placeholder, Object.assign({ innerProps: innerProps, selectProps: selectProps }, props, { children: children })));
|
|
1624
1933
|
}
|
|
1625
1934
|
|
|
1935
|
+
/**
|
|
1936
|
+
* Name of the elements an `aria-labelledby` points at, joined as the accessible
|
|
1937
|
+
* name computation does. Read when the message is built rather than when the
|
|
1938
|
+
* select renders, since the referenced elements may render after it.
|
|
1939
|
+
*
|
|
1940
|
+
* Each element contributes its own `aria-label` when it has one, and its text
|
|
1941
|
+
* otherwise. That covers the targets a select is labelled by in practice — a
|
|
1942
|
+
* heading, a span, a labelled region — without shipping the full accessible
|
|
1943
|
+
* name algorithm; names derived from descendants' `aria-label`, `alt` or
|
|
1944
|
+
* hidden content are not resolved, so pass `ariaLiveMessages` for those.
|
|
1945
|
+
*/
|
|
1946
|
+
function resolveLabelledBy(labelledBy) {
|
|
1947
|
+
if (typeof document === 'undefined')
|
|
1948
|
+
return '';
|
|
1949
|
+
return labelledBy
|
|
1950
|
+
.split(/\s+/)
|
|
1951
|
+
.map((ref) => {
|
|
1952
|
+
var _a, _b;
|
|
1953
|
+
const element = document.getElementById(ref);
|
|
1954
|
+
return ((_a = element === null || element === void 0 ? void 0 : element.getAttribute('aria-label')) === null || _a === void 0 ? void 0 : _a.trim()) || ((_b = element === null || element === void 0 ? void 0 : element.textContent) === null || _b === void 0 ? void 0 : _b.trim());
|
|
1955
|
+
})
|
|
1956
|
+
.filter(Boolean)
|
|
1957
|
+
.join(' ');
|
|
1958
|
+
}
|
|
1959
|
+
/**
|
|
1960
|
+
* Builds react-select's `guidance` live message around the control's name.
|
|
1961
|
+
*
|
|
1962
|
+
* react-select announces "<aria-label> is focused" on the first focus of the
|
|
1963
|
+
* input, falling back to "Select" when there is no `aria-label`. Neither a text
|
|
1964
|
+
* `label` nor `aria-labelledby` reaches that message, so the announcement would
|
|
1965
|
+
* disagree with the name assistive technology exposes.
|
|
1966
|
+
*
|
|
1967
|
+
* The name follows the accessible name precedence: `aria-labelledby`, then
|
|
1968
|
+
* `aria-label`, then the `<label>`. react-select does not export its default
|
|
1969
|
+
* messages, so this mirrors them for every context and only swaps the name.
|
|
1970
|
+
*/
|
|
1971
|
+
function createAriaGuidance({ labelledBy, label }) {
|
|
1972
|
+
return ({ 'aria-label': ariaLabel, context, isSearchable, isMulti, tabSelectsValue, isInitialFocus, }) => {
|
|
1973
|
+
switch (context) {
|
|
1974
|
+
case 'menu':
|
|
1975
|
+
return `Use Up and Down to choose options, press Enter to select the currently focused option, press Escape to exit the menu${tabSelectsValue ? ', press Tab to select the option and exit the menu' : ''}.`;
|
|
1976
|
+
case 'input': {
|
|
1977
|
+
if (!isInitialFocus)
|
|
1978
|
+
return '';
|
|
1979
|
+
const name = (labelledBy && resolveLabelledBy(labelledBy)) || ariaLabel || label || 'Select';
|
|
1980
|
+
return `${name} is focused ${isSearchable ? ',type to refine list' : ''}, press Down to open the menu, ${isMulti ? ' press left to focus selected values' : ''}`;
|
|
1981
|
+
}
|
|
1982
|
+
case 'value':
|
|
1983
|
+
return 'Use left and right to toggle between focused values, press Backspace to remove the currently focused value';
|
|
1984
|
+
default:
|
|
1985
|
+
return '';
|
|
1986
|
+
}
|
|
1987
|
+
};
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1626
1990
|
function DSelect(_a) {
|
|
1627
|
-
var { id: idProp, className, style, label, hint, iconFamilyClass, iconFamilyPrefix, iconStart, iconStartFamilyClass, iconStartFamilyPrefix, iconStartAriaLabel, iconStartTabIndex, iconEnd, iconEndFamilyClass, iconEndFamilyPrefix, iconEndAriaLabel, iconEndTabIndex, invalid, valid, menuWithMaxContent = false, disabled, clearable, loading, floatingLabel = false, rtl, searchable, multi, components, defaultValue, placeholder, onIconStartClick, onIconEndClick, dataAttributes, ariaLabel
|
|
1991
|
+
var { id: idProp, inputId: inputIdProp, className, style, label, hint, iconFamilyClass, iconFamilyPrefix, iconStart, iconStartFamilyClass, iconStartFamilyPrefix, iconStartAriaLabel, iconStartTabIndex, iconEnd, iconEndFamilyClass, iconEndFamilyPrefix, iconEndAriaLabel, iconEndTabIndex, invalid, valid, menuWithMaxContent = false, disabled, clearable, loading, floatingLabel = false, rtl, searchable, multi, components, defaultValue, placeholder, onIconStartClick, onIconEndClick, dataAttributes, ariaLabel, ariaLiveMessages } = _a, props = __rest(_a, ["id", "inputId", "className", "style", "label", "hint", "iconFamilyClass", "iconFamilyPrefix", "iconStart", "iconStartFamilyClass", "iconStartFamilyPrefix", "iconStartAriaLabel", "iconStartTabIndex", "iconEnd", "iconEndFamilyClass", "iconEndFamilyPrefix", "iconEndAriaLabel", "iconEndTabIndex", "invalid", "valid", "menuWithMaxContent", "disabled", "clearable", "loading", "floatingLabel", "rtl", "searchable", "multi", "components", "defaultValue", "placeholder", "onIconStartClick", "onIconEndClick", "dataAttributes", "ariaLabel", "ariaLiveMessages"]);
|
|
1628
1992
|
const innerId = useId();
|
|
1629
1993
|
const id = useMemo(() => idProp || innerId, [idProp, innerId]);
|
|
1994
|
+
// The `<label>` must point at the element react-select renders the input
|
|
1995
|
+
// with, so an explicit `inputId` wins over `id` for both.
|
|
1996
|
+
const inputId = inputIdProp || id;
|
|
1997
|
+
// A text label or `aria-labelledby` names the control without going through
|
|
1998
|
+
// `aria-label`, which is all react-select's focus announcement reads, so it
|
|
1999
|
+
// is handed those names explicitly. Messages passed by the consumer still
|
|
2000
|
+
// take precedence.
|
|
2001
|
+
const textLabel = hasLabelContent(label) && isTextLabel(label) ? String(label) : undefined;
|
|
2002
|
+
const labelledBy = props['aria-labelledby'];
|
|
2003
|
+
const liveMessages = useMemo(() => (textLabel || labelledBy
|
|
2004
|
+
? Object.assign({ guidance: createAriaGuidance({ labelledBy, label: textLabel }) }, ariaLiveMessages) : ariaLiveMessages), [textLabel, labelledBy, ariaLiveMessages]);
|
|
1630
2005
|
const handleOnIconStartClick = useCallback(() => {
|
|
1631
2006
|
onIconStartClick === null || onIconStartClick === void 0 ? void 0 : onIconStartClick(defaultValue);
|
|
1632
2007
|
}, [onIconStartClick, defaultValue]);
|
|
1633
2008
|
const handleOnIconEndClick = useCallback(() => {
|
|
1634
2009
|
onIconEndClick === null || onIconEndClick === void 0 ? void 0 : onIconEndClick(defaultValue);
|
|
1635
2010
|
}, [onIconEndClick, defaultValue]);
|
|
2011
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
2012
|
+
warnLabelUsage({
|
|
2013
|
+
component: 'DSelect',
|
|
2014
|
+
label,
|
|
2015
|
+
// `{...props}` is spread after `ariaLabel`, so a native `aria-label` in it
|
|
2016
|
+
// is the one that reaches the input, even when it is `undefined`.
|
|
2017
|
+
hasAccessibleName: !!('aria-label' in props ? props['aria-label'] : ariaLabel)
|
|
2018
|
+
|| !!labelledBy,
|
|
2019
|
+
accessibleNameProp: 'ariaLabel',
|
|
2020
|
+
floatingLabel,
|
|
2021
|
+
});
|
|
2022
|
+
}
|
|
1636
2023
|
return (jsxs("div", Object.assign({ className: classNames('d-select', className, {
|
|
1637
2024
|
'd-select-floating': floatingLabel,
|
|
1638
2025
|
disabled: disabled || loading,
|
|
1639
|
-
}), style: style }, dataAttributes, { children: [label && (jsx(
|
|
2026
|
+
}), style: style }, dataAttributes, { children: [hasLabelContent(label) && (jsx(DFormLabel, { htmlFor: inputId, children: label })), jsxs("div", { className: classNames({
|
|
1640
2027
|
'input-group': true,
|
|
1641
2028
|
'has-validation': invalid,
|
|
1642
2029
|
disabled: disabled || loading,
|
|
1643
|
-
}), children: [iconStart && (jsx("button", { type: "button", className: "input-group-text", id: `${id}Start`, onClick: handleOnIconStartClick, disabled: disabled || loading, "aria-label": iconStartAriaLabel, tabIndex: iconStartTabIndex, children: jsx(DIcon, { icon: iconStart, familyClass: iconStartFamilyClass, familyPrefix: iconStartFamilyPrefix }) })), jsx(Select, Object.assign({ id: `${id}Container`, inputId:
|
|
2030
|
+
}), children: [iconStart && (jsx("button", { type: "button", className: "input-group-text", id: `${id}Start`, onClick: handleOnIconStartClick, disabled: disabled || loading, "aria-label": iconStartAriaLabel, tabIndex: iconStartTabIndex, children: jsx(DIcon, { icon: iconStart, familyClass: iconStartFamilyClass, familyPrefix: iconStartFamilyPrefix }) })), jsx(Select, Object.assign({ id: `${id}Container`, inputId: inputId, "aria-label": ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : (hasLabelContent(label) || props['aria-labelledby'] ? undefined : 'Search for an option'), ariaLiveMessages: liveMessages, styles: {
|
|
1644
2031
|
control: (base) => (Object.assign(Object.assign({}, base), { minHeight: 'unset' })),
|
|
1645
2032
|
container: (base) => (Object.assign(Object.assign({}, base), { flex: 1 })),
|
|
1646
2033
|
menu: (base) => (Object.assign(Object.assign({}, base), { width: menuWithMaxContent ? 'max-context' : '100%', zIndex: 1000 })),
|
|
@@ -1835,10 +2222,40 @@ function formatValue(value, currencyOptions) {
|
|
|
1835
2222
|
}
|
|
1836
2223
|
return currency(value, Object.assign(Object.assign({}, currencyOptions), { symbol: '' })).format();
|
|
1837
2224
|
}
|
|
1838
|
-
|
|
2225
|
+
/**
|
|
2226
|
+
* State and handlers for a currency input: a formatted string while the field
|
|
2227
|
+
* is idle and the raw number while it is being edited.
|
|
2228
|
+
*
|
|
2229
|
+
* `minValue`/`maxValue` bound the value. With `clamp` (default `true`) an
|
|
2230
|
+
* out-of-range value is brought into range whenever it is not being typed:
|
|
2231
|
+
* on mount, when `value` or the bounds change, and on blur. Every time the
|
|
2232
|
+
* clamp changes the value, `onChange` receives the clamped number, so the
|
|
2233
|
+
* input and the consumer's state never disagree. While the field is focused
|
|
2234
|
+
* the typed number is reported as is, so a partial entry is not rewritten.
|
|
2235
|
+
*
|
|
2236
|
+
* With `clamp: false` the value is never changed: the bounds only feed
|
|
2237
|
+
* `isOverMax` / `isUnderMin`, so the consumer can show its own message (e.g.
|
|
2238
|
+
* "You exceeded the limit") and keep the entered amount.
|
|
2239
|
+
*
|
|
2240
|
+
* Controlled usage follows the same contract as a native controlled input:
|
|
2241
|
+
* reflect `onChange` into `value` in the same event (`setState` in the
|
|
2242
|
+
* handler). The hook keeps showing `value` whenever it isn't being edited, so
|
|
2243
|
+
* a consumer can reject a change by keeping `value` as it was. Deferring the
|
|
2244
|
+
* update (a timer, awaiting a request) makes the hook see a stale `value` in
|
|
2245
|
+
* between: the field can flash the previous amount and a clamp can be
|
|
2246
|
+
* reported again when the deferred value arrives.
|
|
2247
|
+
*/
|
|
2248
|
+
function useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref, minValue, maxValue, clamp = true) {
|
|
1839
2249
|
const inputRef = useProvidedRefOrCreate(ref);
|
|
2250
|
+
const onChangeRef = useRef(onChange);
|
|
2251
|
+
onChangeRef.current = onChange;
|
|
2252
|
+
// Last clamp reported through `onChange`, as the value it came from and the
|
|
2253
|
+
// value it produced. StrictMode replays effects, and a consumer may reflect
|
|
2254
|
+
// `onChange` asynchronously, both before `value` catches up: the same clamp
|
|
2255
|
+
// must not be reported twice.
|
|
2256
|
+
const lastReportedClampRef = useRef(null);
|
|
1840
2257
|
const clampValue = useCallback((newValue) => {
|
|
1841
|
-
if (newValue === undefined) {
|
|
2258
|
+
if (newValue === undefined || !clamp) {
|
|
1842
2259
|
return newValue;
|
|
1843
2260
|
}
|
|
1844
2261
|
let clampedValue = newValue;
|
|
@@ -1849,10 +2266,9 @@ function useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref
|
|
|
1849
2266
|
clampedValue = Math.min(clampedValue, maxValue);
|
|
1850
2267
|
}
|
|
1851
2268
|
return clampedValue;
|
|
1852
|
-
}, [minValue, maxValue]);
|
|
2269
|
+
}, [minValue, maxValue, clamp]);
|
|
1853
2270
|
const [innerType, setInnerType] = useState('text');
|
|
1854
|
-
const [innerNumber, setInnerNumber] = useState(clampValue(value));
|
|
1855
|
-
const [innerString, setInnerString] = useState(formatValue(clampValue(value), currencyOptions));
|
|
2271
|
+
const [innerNumber, setInnerNumber] = useState(() => clampValue(value));
|
|
1856
2272
|
const handleOnFocus = useCallback((event) => {
|
|
1857
2273
|
event.stopPropagation();
|
|
1858
2274
|
setInnerType('number');
|
|
@@ -1864,35 +2280,60 @@ function useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref
|
|
|
1864
2280
|
const clampedNumber = clampValue(innerNumber);
|
|
1865
2281
|
if (clampedNumber !== innerNumber) {
|
|
1866
2282
|
setInnerNumber(clampedNumber);
|
|
1867
|
-
|
|
2283
|
+
// Recorded before notifying, so the sync effect that runs when editing
|
|
2284
|
+
// ends doesn't report the same clamp again while `value` still holds the
|
|
2285
|
+
// typed number.
|
|
2286
|
+
lastReportedClampRef.current = { from: value, to: clampedNumber };
|
|
1868
2287
|
onChange === null || onChange === void 0 ? void 0 : onChange(clampedNumber);
|
|
1869
2288
|
}
|
|
1870
2289
|
onBlur === null || onBlur === void 0 ? void 0 : onBlur(event);
|
|
1871
|
-
}, [onBlur, innerNumber, clampValue,
|
|
2290
|
+
}, [onBlur, innerNumber, clampValue, onChange, value]);
|
|
1872
2291
|
const handleOnChange = useCallback((newValue) => {
|
|
1873
2292
|
const newNumber = (newValue === undefined || newValue === '') ? undefined : Number(newValue);
|
|
1874
2293
|
if (newNumber !== innerNumber) {
|
|
1875
2294
|
setInnerNumber(newNumber);
|
|
1876
|
-
setInnerString(formatValue(newNumber, currencyOptions));
|
|
1877
2295
|
onChange === null || onChange === void 0 ? void 0 : onChange(newNumber);
|
|
1878
2296
|
}
|
|
1879
|
-
}, [
|
|
1880
|
-
const
|
|
2297
|
+
}, [onChange, innerNumber]);
|
|
2298
|
+
const isEditing = innerType === 'number';
|
|
2299
|
+
// Keep the inner value in sync with `value` and the bounds. While the user
|
|
2300
|
+
// is typing, the consumer echoes the typed number back through `value`, so
|
|
2301
|
+
// it is taken as is; otherwise it is clamped, and a clamp that changes it is
|
|
2302
|
+
// reported through `onChange`.
|
|
1881
2303
|
useEffect(() => {
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
2304
|
+
var _a;
|
|
2305
|
+
const nextNumber = isEditing ? value : clampValue(value);
|
|
2306
|
+
if (nextNumber !== innerNumber) {
|
|
2307
|
+
setInnerNumber(nextNumber);
|
|
1885
2308
|
}
|
|
1886
|
-
if (
|
|
1887
|
-
|
|
1888
|
-
|
|
2309
|
+
if (nextNumber !== value) {
|
|
2310
|
+
const last = lastReportedClampRef.current;
|
|
2311
|
+
if (!last || last.from !== value || last.to !== nextNumber) {
|
|
2312
|
+
lastReportedClampRef.current = { from: value, to: nextNumber };
|
|
2313
|
+
(_a = onChangeRef.current) === null || _a === void 0 ? void 0 : _a.call(onChangeRef, nextNumber);
|
|
2314
|
+
}
|
|
2315
|
+
}
|
|
2316
|
+
else {
|
|
2317
|
+
lastReportedClampRef.current = null;
|
|
1889
2318
|
}
|
|
1890
|
-
|
|
1891
|
-
|
|
2319
|
+
// `innerNumber` is read, not tracked: the effect reacts to the consumer's
|
|
2320
|
+
// value and to the bounds, not to its own updates.
|
|
2321
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2322
|
+
}, [value, clampValue, isEditing]);
|
|
2323
|
+
// Derived, not stored: the formatted text always matches the current number
|
|
2324
|
+
// and options, even when both change in the same render. It depends on the
|
|
2325
|
+
// options object itself, so a new custom `format` function is honored too;
|
|
2326
|
+
// formatting is cheap and doesn't feed the synchronization effect.
|
|
2327
|
+
const innerString = useMemo(() => formatValue(innerNumber, currencyOptions), [innerNumber, currencyOptions]);
|
|
2328
|
+
const innerValue = useMemo(() => { var _a; return (innerType === 'number' ? (_a = innerNumber === null || innerNumber === void 0 ? void 0 : innerNumber.toString()) !== null && _a !== void 0 ? _a : '' : innerString); }, [innerType, innerNumber, innerString]);
|
|
2329
|
+
const isOverMax = maxValue !== undefined && innerNumber !== undefined && innerNumber > maxValue;
|
|
2330
|
+
const isUnderMin = minValue !== undefined && innerNumber !== undefined && innerNumber < minValue;
|
|
1892
2331
|
return {
|
|
1893
2332
|
inputRef,
|
|
1894
2333
|
innerValue,
|
|
1895
2334
|
innerType,
|
|
2335
|
+
isOverMax,
|
|
2336
|
+
isUnderMin,
|
|
1896
2337
|
handleOnFocus,
|
|
1897
2338
|
handleOnChange,
|
|
1898
2339
|
handleOnBlur,
|
|
@@ -2143,38 +2584,142 @@ function useOtp({ action, otpSize = 6, seconds = 15, }) {
|
|
|
2143
2584
|
};
|
|
2144
2585
|
}
|
|
2145
2586
|
|
|
2587
|
+
/**
|
|
2588
|
+
* Backs a value that the consumer may or may not drive, so a control works in
|
|
2589
|
+
* both modes without a copy of the state drifting away from the props.
|
|
2590
|
+
*
|
|
2591
|
+
* A control counts as controlled only when the consumer passes the value *and*
|
|
2592
|
+
* an `onChange` to push it back. The value on its own has always meant "start
|
|
2593
|
+
* here" in this library — `<DInputCheck type="radio" checked />` inside a radio
|
|
2594
|
+
* group, for one — so treating it as controlled would silently freeze those
|
|
2595
|
+
* controls, and React emits no warning to say why: the components always attach
|
|
2596
|
+
* their own `onChange` handler.
|
|
2597
|
+
*
|
|
2598
|
+
* @param value the value coming from props, or `undefined` when not passed.
|
|
2599
|
+
* @param isControlled whether the consumer can drive the value back.
|
|
2600
|
+
* @param initialValue what to start from when no value is passed.
|
|
2601
|
+
* @returns the value to render, and a setter to call on every user change.
|
|
2602
|
+
*/
|
|
2603
|
+
function useControlledState(value, isControlled, initialValue) {
|
|
2604
|
+
const [internalValue, setInternalValue] = useState(value === undefined ? initialValue : value);
|
|
2605
|
+
// The internal copy follows `value` in both modes.
|
|
2606
|
+
//
|
|
2607
|
+
// Uncontrolled, `value` without an `onChange` still means "start here", and a
|
|
2608
|
+
// later flip of it from outside must still land — but between flips the
|
|
2609
|
+
// control has to keep moving on its own, which is why this depends on `value`
|
|
2610
|
+
// alone and not on `internalValue`.
|
|
2611
|
+
//
|
|
2612
|
+
// Controlled, the copy is not what gets rendered, but it is what the control
|
|
2613
|
+
// falls back to if the consumer later stops passing a value — `value={locked
|
|
2614
|
+
// ? undefined : x}`, say. Keeping it current means that hand-off carries on
|
|
2615
|
+
// from where the control is instead of snapping back to the mount-time seed.
|
|
2616
|
+
useEffect(() => {
|
|
2617
|
+
if (value === undefined) {
|
|
2618
|
+
return;
|
|
2619
|
+
}
|
|
2620
|
+
setInternalValue(value);
|
|
2621
|
+
}, [value]);
|
|
2622
|
+
const setValue = useCallback((next) => {
|
|
2623
|
+
if (isControlled) {
|
|
2624
|
+
return;
|
|
2625
|
+
}
|
|
2626
|
+
setInternalValue(next);
|
|
2627
|
+
}, [isControlled]);
|
|
2628
|
+
return [
|
|
2629
|
+
isControlled && value !== undefined ? value : internalValue,
|
|
2630
|
+
setValue,
|
|
2631
|
+
];
|
|
2632
|
+
}
|
|
2633
|
+
|
|
2146
2634
|
function DInputCounter(_a, ref) {
|
|
2147
|
-
var { minValue, maxValue, value
|
|
2635
|
+
var { minValue, maxValue, value, defaultValue, invalid, iconStart: iconStartProp, iconEnd: iconEndProp, iconStartAriaLabel = 'decrease action', iconEndAriaLabel = 'increase action', style, onChange } = _a, props = __rest(_a, ["minValue", "maxValue", "value", "defaultValue", "invalid", "iconStart", "iconEnd", "iconStartAriaLabel", "iconEndAriaLabel", "style", "onChange"]);
|
|
2148
2636
|
const { handleOnWheel, } = useDisableInputWheel(ref);
|
|
2149
2637
|
const inputRef = useProvidedRefOrCreate(ref);
|
|
2150
|
-
|
|
2151
|
-
const
|
|
2638
|
+
// See `useControlledState` for why `onChange` takes part in this decision.
|
|
2639
|
+
const isControlled = value !== undefined && onChange !== undefined;
|
|
2640
|
+
const [currentValue, setCurrentValue] = useControlledState(value, isControlled, defaultValue !== null && defaultValue !== void 0 ? defaultValue : minValue);
|
|
2641
|
+
// The step handlers move *from* the current value rather than replacing it,
|
|
2642
|
+
// so they need the latest one even when several clicks land in the same React
|
|
2643
|
+
// batch and no render has happened in between — the functional update this
|
|
2644
|
+
// replaced handled that on its own. Written on every render so it follows the
|
|
2645
|
+
// props, and by `commitValue` so a second step in the same batch starts from
|
|
2646
|
+
// where the first left off instead of collapsing into it.
|
|
2647
|
+
const currentValueRef = useRef(currentValue);
|
|
2648
|
+
currentValueRef.current = currentValue;
|
|
2649
|
+
// `onChange` used to be called from an effect watching the internal value,
|
|
2650
|
+
// which made controlling the counter impossible: a controlled counter never
|
|
2651
|
+
// moves that value, so the effect never fired. Reporting from the handlers
|
|
2652
|
+
// instead works in both modes.
|
|
2653
|
+
const commitValue = useCallback((newValue) => {
|
|
2654
|
+
// Clicking at a bound, or typing the value that is already there, is not a
|
|
2655
|
+
// change. The effect this replaced watched the value itself and so stayed
|
|
2656
|
+
// quiet too; without this a batch of clicks against `minValue` would report
|
|
2657
|
+
// the same number once per click.
|
|
2658
|
+
if (newValue === currentValueRef.current) {
|
|
2659
|
+
return;
|
|
2660
|
+
}
|
|
2661
|
+
// Only the uncontrolled path moves the ref ahead of a render. There the
|
|
2662
|
+
// component owns the value, so a second step in the same batch has to build
|
|
2663
|
+
// on the first. Controlled, the prop is the truth and every step proposes
|
|
2664
|
+
// from it: a parent that rejects without re-rendering — `onChange={() => {}}`,
|
|
2665
|
+
// or a setter returning the previous state — never re-runs the assignment
|
|
2666
|
+
// above, so a speculative ref would climb away from the value on screen and
|
|
2667
|
+
// each further click would report a number further from it.
|
|
2668
|
+
if (!isControlled) {
|
|
2669
|
+
currentValueRef.current = newValue;
|
|
2670
|
+
}
|
|
2671
|
+
setCurrentValue(newValue);
|
|
2672
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(newValue);
|
|
2673
|
+
}, [isControlled, setCurrentValue, onChange]);
|
|
2674
|
+
// Consumers have always been handed the starting value through `onChange` on
|
|
2675
|
+
// mount, and some seed their state with it, so that one call stays. The ref
|
|
2676
|
+
// keeps it to the first run: the old effect also re-fired on every render
|
|
2677
|
+
// that passed a fresh inline `onChange`. Nothing is marked as reported until
|
|
2678
|
+
// there is a handler to report to, so a counter mounted without `onChange`
|
|
2679
|
+
// still hands over its starting value once one is attached.
|
|
2680
|
+
const hasReportedInitialValue = useRef(false);
|
|
2152
2681
|
useEffect(() => {
|
|
2153
|
-
|
|
2154
|
-
|
|
2682
|
+
if (hasReportedInitialValue.current || !onChange) {
|
|
2683
|
+
return;
|
|
2684
|
+
}
|
|
2685
|
+
hasReportedInitialValue.current = true;
|
|
2686
|
+
onChange(currentValue);
|
|
2687
|
+
}, [onChange, currentValue]);
|
|
2688
|
+
// `value` used to default to `minValue`, so a counter left to its own devices
|
|
2689
|
+
// re-seeded — and reported — whenever `minValue` moved. Consumers use it as a
|
|
2690
|
+
// live bound, so that stays; an explicit `value` or `defaultValue` opts out,
|
|
2691
|
+
// exactly as passing `value` did before. The ref keeps this to real changes,
|
|
2692
|
+
// so mounting still reports once and a click is never undone.
|
|
2693
|
+
const hasExplicitStartingValue = value !== undefined || defaultValue !== undefined;
|
|
2694
|
+
const previousMinValue = useRef(minValue);
|
|
2155
2695
|
useEffect(() => {
|
|
2156
|
-
|
|
2157
|
-
|
|
2696
|
+
if (previousMinValue.current === minValue) {
|
|
2697
|
+
return;
|
|
2698
|
+
}
|
|
2699
|
+
previousMinValue.current = minValue;
|
|
2700
|
+
if (hasExplicitStartingValue) {
|
|
2701
|
+
return;
|
|
2702
|
+
}
|
|
2703
|
+
commitValue(minValue);
|
|
2704
|
+
}, [hasExplicitStartingValue, minValue, commitValue]);
|
|
2158
2705
|
const handleOnChange = useCallback((newValue) => {
|
|
2159
|
-
|
|
2160
|
-
}, []);
|
|
2706
|
+
commitValue(Number(newValue || '0'));
|
|
2707
|
+
}, [commitValue]);
|
|
2161
2708
|
const handleOnIconStartClick = useCallback(() => {
|
|
2162
|
-
|
|
2163
|
-
}, [minValue]);
|
|
2709
|
+
commitValue(Math.max(currentValueRef.current - 1, minValue));
|
|
2710
|
+
}, [commitValue, minValue]);
|
|
2164
2711
|
const handleOnIconEndClick = useCallback(() => {
|
|
2165
|
-
|
|
2166
|
-
}, [maxValue]);
|
|
2712
|
+
commitValue(Math.min(currentValueRef.current + 1, maxValue));
|
|
2713
|
+
}, [commitValue, maxValue]);
|
|
2167
2714
|
const generateStyleVariables = useMemo(() => (Object.assign(Object.assign({}, style), { [`--${PREFIX_BS}form-control-component-text-align`]: 'center' })), [style]);
|
|
2168
|
-
const valueString = useMemo(() => (
|
|
2169
|
-
|
|
2170
|
-
setInternalIsInvalid(!(internalValue >= minValue && internalValue <= maxValue));
|
|
2171
|
-
}, [internalValue, minValue, maxValue]);
|
|
2715
|
+
const valueString = useMemo(() => (currentValue.toString()), [currentValue]);
|
|
2716
|
+
const internalIsInvalid = useMemo(() => (!(currentValue >= minValue && currentValue <= maxValue)), [currentValue, minValue, maxValue]);
|
|
2172
2717
|
const { iconMap: { input } } = useDContext();
|
|
2173
2718
|
const iconEnd = useMemo(() => iconEndProp || input.increase, [iconEndProp, input.increase]);
|
|
2174
2719
|
const iconStart = useMemo(() => iconStartProp || input.decrease, [iconStartProp, input.decrease]);
|
|
2175
|
-
return (jsx(ForwardedDInput, Object.assign({ ref: inputRef, value: valueString, style: generateStyleVariables, iconStart: iconStart, iconEnd: iconEnd, invalid: internalIsInvalid || invalid, type: "number", onChange: handleOnChange, onWheel: handleOnWheel, onIconStartClick: handleOnIconStartClick, onIconEndClick: handleOnIconEndClick, iconStartAriaLabel: iconStartAriaLabel, iconEndAriaLabel: iconEndAriaLabel },
|
|
2720
|
+
return (jsx(ForwardedDInput, Object.assign({ ref: inputRef, value: valueString, style: generateStyleVariables, iconStart: iconStart, iconEnd: iconEnd, invalid: internalIsInvalid || invalid, type: "number", onChange: handleOnChange, onWheel: handleOnWheel, onIconStartClick: handleOnIconStartClick, onIconEndClick: handleOnIconEndClick, iconStartAriaLabel: iconStartAriaLabel, iconEndAriaLabel: iconEndAriaLabel }, currentValue === minValue && {
|
|
2176
2721
|
iconStartDisabled: true,
|
|
2177
|
-
},
|
|
2722
|
+
}, currentValue === maxValue && {
|
|
2178
2723
|
iconEndDisabled: true,
|
|
2179
2724
|
}, props)));
|
|
2180
2725
|
}
|
|
@@ -2182,11 +2727,16 @@ const ForwardedDInputCounter = forwardRef(DInputCounter);
|
|
|
2182
2727
|
ForwardedDInputCounter.displayName = 'DInputCounter';
|
|
2183
2728
|
|
|
2184
2729
|
function DInputCurrency(_a, ref) {
|
|
2185
|
-
var { value, minValue, maxValue, currencyCode, onFocus, onBlur, onChange } = _a, props = __rest(_a, ["value", "minValue", "maxValue", "currencyCode", "onFocus", "onBlur", "onChange"]);
|
|
2730
|
+
var { value, minValue, maxValue, clamp = true, currencyCode, onFocus, onBlur, onChange, invalid: invalidProp, valid: validProp } = _a, props = __rest(_a, ["value", "minValue", "maxValue", "clamp", "currencyCode", "onFocus", "onBlur", "onChange", "invalid", "valid"]);
|
|
2186
2731
|
const { currency: currencyOptions } = useDContext();
|
|
2187
2732
|
const { handleOnWheel, } = useDisableInputWheel(ref);
|
|
2188
|
-
const { inputRef, innerValue, innerType, handleOnFocus, handleOnChange, handleOnBlur, } = useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref, minValue, maxValue);
|
|
2189
|
-
|
|
2733
|
+
const { inputRef, innerValue, innerType, isOverMax, isUnderMin, handleOnFocus, handleOnChange, handleOnBlur, } = useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref, minValue, maxValue, clamp);
|
|
2734
|
+
const outOfRange = !clamp && (isOverMax || isUnderMin);
|
|
2735
|
+
// One validation state: an explicit `invalid` wins; otherwise an
|
|
2736
|
+
// out-of-range value is invalid and can't also be shown as valid.
|
|
2737
|
+
const invalid = invalidProp !== null && invalidProp !== void 0 ? invalidProp : outOfRange;
|
|
2738
|
+
const valid = invalid ? false : validProp;
|
|
2739
|
+
return (jsx(ForwardedDInput, Object.assign({ ref: inputRef, value: innerValue, onChange: handleOnChange, inputMode: "decimal", type: innerType, onFocus: handleOnFocus, onBlur: handleOnBlur, onWheel: handleOnWheel, invalid: invalid, valid: valid, inputStart: (jsx("span", { slot: "input-start", className: "d-input-currency-symbol", children: currencyCode || currencyOptions.symbol })) }, props)));
|
|
2190
2740
|
}
|
|
2191
2741
|
const ForwardedDInputCurrency = forwardRef(DInputCurrency);
|
|
2192
2742
|
ForwardedDInputCurrency.displayName = 'DInputCurrency';
|
|
@@ -2273,7 +2823,7 @@ const DEFAULT_VALIDATION_MESSAGES = {
|
|
|
2273
2823
|
notMatch: 'The password confirmation and the new password do not match.',
|
|
2274
2824
|
};
|
|
2275
2825
|
const DEFAULT_ENABLED_CHECKS = ['uppercase', 'lowercase', 'number', 'specialChar'];
|
|
2276
|
-
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, }) {
|
|
2826
|
+
function DPasswordStrengthMeter({ id, label = 'Password', 'aria-label': ariaLabel, placeholder, value = '', name, disabled = false, invalid = false, validationMessages = DEFAULT_VALIDATION_MESSAGES, enabledChecks = DEFAULT_ENABLED_CHECKS, className, style, dataAttributes, onChange, readonly = false, }) {
|
|
2277
2827
|
const [password, setPassword] = useState(value);
|
|
2278
2828
|
useEffect(() => {
|
|
2279
2829
|
setPassword(value);
|
|
@@ -2282,17 +2832,31 @@ function DPasswordStrengthMeter({ id, label = 'Password', placeholder, value = '
|
|
|
2282
2832
|
setPassword(newValue);
|
|
2283
2833
|
onChange === null || onChange === void 0 ? void 0 : onChange(newValue);
|
|
2284
2834
|
};
|
|
2285
|
-
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 })] })));
|
|
2835
|
+
return (jsxs("div", Object.assign({ className: className, style: style }, dataAttributes, { children: [jsx(ForwardedDInputPassword, { id: id, label: label, "aria-label": ariaLabel, placeholder: placeholder, value: password, name: name, disabled: disabled, invalid: invalid, onChange: handleChange, readonly: readonly }), jsx(PasswordChecksList, { password: password, validationMessages: validationMessages, enabledChecks: enabledChecks })] })));
|
|
2286
2836
|
}
|
|
2287
2837
|
|
|
2288
2838
|
function DInputCheck(_a) {
|
|
2289
|
-
var { id: idProp, type, name, label, ariaLabel, checked
|
|
2839
|
+
var { id: idProp, type, name, label, ariaLabel, checked, defaultChecked, disabled = false, invalid = false, valid = false, indeterminate, inputClassName, value, hint, onChange, className, style, dataAttributes } = _a, props = __rest(_a, ["id", "type", "name", "label", "ariaLabel", "checked", "defaultChecked", "disabled", "invalid", "valid", "indeterminate", "inputClassName", "value", "hint", "onChange", "className", "style", "dataAttributes"]);
|
|
2290
2840
|
const innerRef = useRef(null);
|
|
2841
|
+
// See `useControlledState` for why `onChange` takes part in this decision.
|
|
2842
|
+
const isControlled = checked !== undefined && onChange !== undefined;
|
|
2291
2843
|
const innerId = useId();
|
|
2292
2844
|
const id = useMemo(() => idProp || innerId, [idProp, innerId]);
|
|
2293
2845
|
const handleChange = useCallback((event) => {
|
|
2294
2846
|
onChange === null || onChange === void 0 ? void 0 : onChange(event);
|
|
2295
|
-
|
|
2847
|
+
// Controlled only. Activating a checkbox clears the DOM `indeterminate`
|
|
2848
|
+
// flag, and it has no HTML attribute for React to restore the way it
|
|
2849
|
+
// restores `checked` when the parent rejects the change, so the mixed state
|
|
2850
|
+
// would be gone after the first click — the effect below only re-runs when
|
|
2851
|
+
// the prop moves. Uncontrolled keeps the browser's behaviour, where the
|
|
2852
|
+
// click owns the state and `indeterminate` was only the starting look.
|
|
2853
|
+
// Reapplied after `onChange` so a handler reading
|
|
2854
|
+
// `event.target.indeterminate` still sees what the browser left, and a
|
|
2855
|
+
// parent that does move the prop wins through that effect.
|
|
2856
|
+
if (isControlled && innerRef.current) {
|
|
2857
|
+
innerRef.current.indeterminate = type === 'checkbox' && Boolean(indeterminate);
|
|
2858
|
+
}
|
|
2859
|
+
}, [onChange, isControlled, indeterminate, type]);
|
|
2296
2860
|
const ariaDescribedby = useMemo(() => ([
|
|
2297
2861
|
!!hint && `${id}Hint`,
|
|
2298
2862
|
]
|
|
@@ -2306,15 +2870,24 @@ function DInputCheck(_a) {
|
|
|
2306
2870
|
innerRef.current.indeterminate = type === 'checkbox' && Boolean(indeterminate);
|
|
2307
2871
|
}
|
|
2308
2872
|
}, [indeterminate, type]);
|
|
2873
|
+
// Legacy path only: a `checked` with no `onChange` behind it still lands on
|
|
2874
|
+
// the element, but through the DOM, so the input stays uncontrolled and both
|
|
2875
|
+
// clicking it and the native radio-group behaviour keep working.
|
|
2309
2876
|
useEffect(() => {
|
|
2310
|
-
if (innerRef.current) {
|
|
2311
|
-
|
|
2877
|
+
if (isControlled || checked === undefined || !innerRef.current) {
|
|
2878
|
+
return;
|
|
2312
2879
|
}
|
|
2313
|
-
|
|
2314
|
-
|
|
2880
|
+
innerRef.current.checked = checked;
|
|
2881
|
+
}, [isControlled, checked]);
|
|
2882
|
+
const inputComponent = useMemo(() => (jsx("input", Object.assign({ ref: innerRef }, isControlled
|
|
2883
|
+
? { checked }
|
|
2884
|
+
: defaultChecked !== undefined && { defaultChecked }, { onChange: handleChange, className: classNames('form-check-input', {
|
|
2315
2885
|
'is-invalid': invalid,
|
|
2316
2886
|
'is-valid': valid,
|
|
2317
2887
|
}, inputClassName), style: style, id: id, disabled: disabled, type: type, name: name, value: value, "aria-label": ariaLabel }, ariaDescribedby && { 'aria-describedby': ariaDescribedby }, props))), [
|
|
2888
|
+
isControlled,
|
|
2889
|
+
checked,
|
|
2890
|
+
defaultChecked,
|
|
2318
2891
|
handleChange,
|
|
2319
2892
|
invalid,
|
|
2320
2893
|
valid,
|
|
@@ -2329,10 +2902,21 @@ function DInputCheck(_a) {
|
|
|
2329
2902
|
ariaDescribedby,
|
|
2330
2903
|
props,
|
|
2331
2904
|
]);
|
|
2332
|
-
if (
|
|
2905
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
2906
|
+
warnLabelUsage({
|
|
2907
|
+
component: 'DInputCheck',
|
|
2908
|
+
label,
|
|
2909
|
+
// `{...props}` is spread after `aria-label={ariaLabel}`, so a native
|
|
2910
|
+
// `aria-label` wins — including when it is explicitly undefined.
|
|
2911
|
+
hasAccessibleName: !!('aria-label' in props ? props['aria-label'] : ariaLabel)
|
|
2912
|
+
|| !!props['aria-labelledby'],
|
|
2913
|
+
accessibleNameProp: 'ariaLabel',
|
|
2914
|
+
});
|
|
2915
|
+
}
|
|
2916
|
+
if (!hasLabelContent(label)) {
|
|
2333
2917
|
return inputComponent;
|
|
2334
2918
|
}
|
|
2335
|
-
return (jsxs("div", Object.assign({ className: classNames('form-check', className) }, dataAttributes, { children: [inputComponent, jsx(
|
|
2919
|
+
return (jsxs("div", Object.assign({ className: classNames('form-check', className) }, dataAttributes, { children: [inputComponent, jsx(DFormLabel, { className: "form-check-label", htmlFor: id, children: label }), hint && (jsx("div", { className: "form-text", id: `${id}Hint`, children: hint }))] })));
|
|
2336
2920
|
}
|
|
2337
2921
|
|
|
2338
2922
|
function DInputPin({ id: idProp, label = '', placeholder, type = 'text', disabled = false, loading = false, secret = false, characters = 4, innerInputMode = 'text', hint, invalid = false, valid = false, className, style, dataAttributes, onChange, 'aria-label': ariaLabel = 'Pin character number', }) {
|
|
@@ -2408,14 +2992,14 @@ function DInputPin({ id: idProp, label = '', placeholder, type = 'text', disable
|
|
|
2408
2992
|
const wheelInput = useCallback((event) => {
|
|
2409
2993
|
event.currentTarget.blur();
|
|
2410
2994
|
}, []);
|
|
2411
|
-
return (jsxs("div", Object.assign({ className: classNames('d-input-pin', className), style: style }, dataAttributes, { children: [label && (jsx(
|
|
2995
|
+
return (jsxs("div", Object.assign({ className: classNames('d-input-pin', className), style: style }, dataAttributes, { children: [hasLabelContent(label) && (jsx(DFormLabel, { htmlFor: "pinIndex0", children: label })), jsxs("div", { className: "d-input-pin-group", id: id, children: [Array.from({ length: characters }).map((_, index) => (jsx("input", Object.assign({ className: classNames({
|
|
2412
2996
|
'form-control': true,
|
|
2413
2997
|
'is-invalid': invalid,
|
|
2414
2998
|
'is-valid': valid,
|
|
2415
2999
|
}), value: activeInput[index], type: secret ? 'password' : type, "aria-label": `${ariaLabel} ${index + 1} of ${characters}`, inputMode: innerInputMode, id: `pinIndex${index}`, name: `pin-${index}`, maxLength: 1, onInput: (event) => nextInput(event, index), onKeyDown: (event) => prevInput(event, index), onFocus: () => focusInput(index), onWheel: wheelInput, onClick: (event) => event.preventDefault(), onPaste: (event) => handlePaste(event), autoComplete: "off", placeholder: placeholder, disabled: disabled || loading, required: true }, type === 'number' && ({ min: 0, max: 9 })), index))), loading && (jsx("div", { className: "input-group-text", children: jsx("span", { className: "spinner-border spinner-border-sm", role: "status", "aria-hidden": "true", children: jsx("span", { className: "visually-hidden", children: "Loading..." }) }) }))] }), hint && (jsx("div", { className: "form-text", id: `${id}Hint`, children: hint }))] })));
|
|
2416
3000
|
}
|
|
2417
3001
|
|
|
2418
|
-
function DInputSelect({ id: idProp, name, label = '', className, style, options = [], disabled = false, loading = false, iconStart, iconStartFamilyClass, iconStartFamilyPrefix, iconStartAriaLabel, iconEnd, iconEndFamilyClass, iconEndFamilyPrefix, iconEndAriaLabel, hint, value, size, floatingLabel = false, invalid = false, valid = false, dataAttributes, valueExtractor, labelExtractor, onChange, onBlur, onIconStartClick, onIconEndClick, }) {
|
|
3002
|
+
function DInputSelect({ id: idProp, name, label = '', ariaLabel, className, style, options = [], disabled = false, loading = false, iconStart, iconStartFamilyClass, iconStartFamilyPrefix, iconStartAriaLabel, iconEnd, iconEndFamilyClass, iconEndFamilyPrefix, iconEndAriaLabel, hint, value, size, floatingLabel = false, invalid = false, valid = false, dataAttributes, valueExtractor, labelExtractor, onChange, onBlur, onIconStartClick, onIconEndClick, }) {
|
|
2419
3003
|
const innerId = useId();
|
|
2420
3004
|
const id = useMemo(() => idProp || innerId, [idProp, innerId]);
|
|
2421
3005
|
const internalValueExtractor = useCallback((option) => {
|
|
@@ -2465,8 +3049,9 @@ function DInputSelect({ id: idProp, name, label = '', className, style, options
|
|
|
2465
3049
|
'floating-label': floatingLabel,
|
|
2466
3050
|
'is-invalid': invalid,
|
|
2467
3051
|
'is-valid': valid,
|
|
2468
|
-
}), "aria-label": label, disabled: disabled || loading, onChange: changeHandler, onBlur: blurHandler }, ariaDescribedby && { 'aria-describedby': ariaDescribedby }, value && { value }, { children: options.map((option) => (jsx("option", { value: internalValueExtractor(option), children: internalLabelExtractor(option) }, internalValueExtractor(option)))) }))), [
|
|
3052
|
+
}), "aria-label": ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : (isTextLabel(label) ? String(label) : undefined), disabled: disabled || loading, onChange: changeHandler, onBlur: blurHandler }, ariaDescribedby && { 'aria-describedby': ariaDescribedby }, value && { value }, { children: options.map((option) => (jsx("option", { value: internalValueExtractor(option), children: internalLabelExtractor(option) }, internalValueExtractor(option)))) }))), [
|
|
2469
3053
|
ariaDescribedby,
|
|
3054
|
+
ariaLabel,
|
|
2470
3055
|
blurHandler,
|
|
2471
3056
|
changeHandler,
|
|
2472
3057
|
disabled,
|
|
@@ -2483,7 +3068,7 @@ function DInputSelect({ id: idProp, name, label = '', className, style, options
|
|
|
2483
3068
|
valid,
|
|
2484
3069
|
size,
|
|
2485
3070
|
]);
|
|
2486
|
-
const labelComponent = useMemo(() => (jsx(
|
|
3071
|
+
const labelComponent = useMemo(() => (jsx(DFormLabel, { htmlFor: id, children: label })), [
|
|
2487
3072
|
id,
|
|
2488
3073
|
label,
|
|
2489
3074
|
]);
|
|
@@ -2493,15 +3078,26 @@ function DInputSelect({ id: idProp, name, label = '', className, style, options
|
|
|
2493
3078
|
}
|
|
2494
3079
|
return selectComponent;
|
|
2495
3080
|
}, [floatingLabel, labelComponent, selectComponent]);
|
|
2496
|
-
|
|
3081
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
3082
|
+
warnLabelUsage({
|
|
3083
|
+
component: 'DInputSelect',
|
|
3084
|
+
label,
|
|
3085
|
+
hasAccessibleName: !!ariaLabel,
|
|
3086
|
+
accessibleNameProp: 'ariaLabel',
|
|
3087
|
+
floatingLabel,
|
|
3088
|
+
});
|
|
3089
|
+
}
|
|
3090
|
+
return (jsxs("div", Object.assign({ className: className, style: style }, dataAttributes, { children: [hasLabelContent(label) && !floatingLabel && (labelComponent), jsxs("div", { className: classNames({
|
|
2497
3091
|
'input-group': true,
|
|
2498
3092
|
}), children: [iconStart && (jsx("button", { type: "button", className: "input-group-text", id: `${id}Start`, onClick: iconStartClickHandler, disabled: disabled || loading, "aria-label": iconStartAriaLabel, children: iconStart && (jsx(DIcon, { icon: iconStart, familyClass: iconStartFamilyClass, familyPrefix: iconStartFamilyPrefix })) })), dynamicComponent, iconEnd && !loading && (jsx("button", { type: "button", className: "input-group-text", id: `${id}End`, onClick: iconEndClickHandler, disabled: disabled || loading, "aria-label": iconEndAriaLabel, children: iconEnd && (jsx(DIcon, { icon: iconEnd, familyClass: iconEndFamilyClass, familyPrefix: iconEndFamilyPrefix })) })), loading && (jsx("div", { className: "input-group-text form-control-icon loading", children: jsx("span", { className: "spinner-border spinner-border-sm", role: "status", "aria-hidden": "true", children: jsx("span", { className: "visually-hidden", children: "Loading..." }) }) }))] }), hint && (jsx("div", { className: "form-text", id: `${id}Hint`, children: hint }))] })));
|
|
2499
3093
|
}
|
|
2500
3094
|
|
|
2501
|
-
function DInputSwitch({ id: idProp, label, ariaLabel, name, checked, disabled, invalid = false, valid = false, hint, readonly, className, style, dataAttributes, inputClassName, onChange, }) {
|
|
3095
|
+
function DInputSwitch({ id: idProp, label, ariaLabel, name, checked, defaultChecked = false, disabled, invalid = false, valid = false, hint, readonly, className, style, dataAttributes, inputClassName, onChange, }) {
|
|
2502
3096
|
const innerId = useId();
|
|
2503
3097
|
const id = useMemo(() => idProp || innerId, [idProp, innerId]);
|
|
2504
|
-
|
|
3098
|
+
// See `useControlledState` for why `onChange` takes part in this decision.
|
|
3099
|
+
const isControlled = checked !== undefined && onChange !== undefined;
|
|
3100
|
+
const [isChecked, setIsChecked] = useControlledState(checked, isControlled, defaultChecked);
|
|
2505
3101
|
const ariaDescribedby = useMemo(() => ([
|
|
2506
3102
|
!!hint && `${id}Hint`,
|
|
2507
3103
|
]
|
|
@@ -2510,18 +3106,23 @@ function DInputSwitch({ id: idProp, label, ariaLabel, name, checked, disabled, i
|
|
|
2510
3106
|
id,
|
|
2511
3107
|
hint,
|
|
2512
3108
|
]);
|
|
2513
|
-
useEffect(() => {
|
|
2514
|
-
setInternalIsChecked(checked);
|
|
2515
|
-
}, [checked]);
|
|
2516
3109
|
const changeHandler = useCallback((event) => {
|
|
2517
3110
|
const value = event.currentTarget.checked;
|
|
2518
|
-
|
|
3111
|
+
setIsChecked(value);
|
|
2519
3112
|
onChange === null || onChange === void 0 ? void 0 : onChange(value);
|
|
2520
|
-
}, [onChange]);
|
|
3113
|
+
}, [setIsChecked, onChange]);
|
|
3114
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
3115
|
+
warnLabelUsage({
|
|
3116
|
+
component: 'DInputSwitch',
|
|
3117
|
+
label,
|
|
3118
|
+
hasAccessibleName: !!ariaLabel,
|
|
3119
|
+
accessibleNameProp: 'ariaLabel',
|
|
3120
|
+
});
|
|
3121
|
+
}
|
|
2521
3122
|
return (jsxs("div", Object.assign({ className: classNames('form-check form-switch', className) }, dataAttributes, { children: [jsx("input", Object.assign({ id: id, name: name, onChange: readonly ? () => false : changeHandler, className: classNames('form-check-input', {
|
|
2522
3123
|
'is-invalid': invalid,
|
|
2523
3124
|
'is-valid': valid,
|
|
2524
|
-
}, inputClassName), style: style, type: "checkbox", role: "switch", checked:
|
|
3125
|
+
}, inputClassName), style: style, type: "checkbox", role: "switch", checked: isChecked, disabled: disabled, "aria-label": ariaLabel }, ariaDescribedby && { 'aria-describedby': ariaDescribedby })), hasLabelContent(label) && (jsx(DFormLabel, { className: "form-check-label", htmlFor: id, children: label })), hint && (jsx("div", { className: "form-text", id: `${id}Hint`, children: hint }))] })));
|
|
2525
3126
|
}
|
|
2526
3127
|
|
|
2527
3128
|
function DInputRange(_a, ref) {
|
|
@@ -2553,15 +3154,61 @@ function DInputRange(_a, ref) {
|
|
|
2553
3154
|
props,
|
|
2554
3155
|
value,
|
|
2555
3156
|
]);
|
|
2556
|
-
if (
|
|
3157
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
3158
|
+
warnLabelUsage({
|
|
3159
|
+
component: 'DInputRange',
|
|
3160
|
+
label,
|
|
3161
|
+
// `{...props}` is spread after `aria-label={ariaLabel}`, so a native
|
|
3162
|
+
// `aria-label` wins — including when it is explicitly undefined.
|
|
3163
|
+
hasAccessibleName: !!('aria-label' in props ? props['aria-label'] : ariaLabel)
|
|
3164
|
+
|| !!props['aria-labelledby'],
|
|
3165
|
+
accessibleNameProp: 'ariaLabel',
|
|
3166
|
+
});
|
|
3167
|
+
}
|
|
3168
|
+
if (!hasLabelContent(label)) {
|
|
2557
3169
|
return inputComponent;
|
|
2558
3170
|
}
|
|
2559
|
-
return (jsxs(Fragment, { children: [jsx(
|
|
3171
|
+
return (jsxs(Fragment, { children: [jsx(DFormLabel, { className: "form-label", htmlFor: id, children: label }), inputComponent] }));
|
|
2560
3172
|
}
|
|
2561
3173
|
const ForwardedDInputRange = forwardRef(DInputRange);
|
|
2562
3174
|
ForwardedDInputRange.displayName = 'DInputRange';
|
|
2563
3175
|
|
|
2564
|
-
|
|
3176
|
+
/**
|
|
3177
|
+
* Element the enclosing `DListGroup` renders, so each `DListGroupItem` can
|
|
3178
|
+
* check that its own element is valid content for it. `undefined` outside a
|
|
3179
|
+
* `DListGroup`.
|
|
3180
|
+
*/
|
|
3181
|
+
const ListGroupContext = createContext(undefined);
|
|
3182
|
+
|
|
3183
|
+
/**
|
|
3184
|
+
* Combinations already reported, keyed by container and item element, so the
|
|
3185
|
+
* same mismatch only warns once per page load however many items share it.
|
|
3186
|
+
*/
|
|
3187
|
+
const warnedCombinations = new Set();
|
|
3188
|
+
/**
|
|
3189
|
+
* Warns, once per combination and only outside production builds, that a
|
|
3190
|
+
* `DListGroupItem` renders an element its `DListGroup` container can't hold.
|
|
3191
|
+
*
|
|
3192
|
+
* Links and buttons inside a <ul>/<ol> are wrapped in an <li>, so the only
|
|
3193
|
+
* invalid pair left is a plain item (an <li>) inside `as="div"`: an <li>
|
|
3194
|
+
* outside of any list. It doesn't fail visibly, which is why it's reported.
|
|
3195
|
+
*
|
|
3196
|
+
* The `process.env.NODE_ENV` guard at the call site is what bundlers
|
|
3197
|
+
* constant-fold, so the whole call — and this module — drops out of a
|
|
3198
|
+
* consumer's production bundle.
|
|
3199
|
+
*/
|
|
3200
|
+
function warnInvalidListMarkup(container, item) {
|
|
3201
|
+
const key = `${container}>${item}`;
|
|
3202
|
+
if (warnedCombinations.has(key))
|
|
3203
|
+
return;
|
|
3204
|
+
warnedCombinations.add(key);
|
|
3205
|
+
// eslint-disable-next-line no-console
|
|
3206
|
+
console.warn(`[Dynamic UI] DListGroupItem: a <${item}> inside a <${container}> is invalid markup. `
|
|
3207
|
+
+ 'Keep the container as a list (the default `as="ul"`, or `numbered`), or render the item '
|
|
3208
|
+
+ 'as a link or button. It never appears in production builds.');
|
|
3209
|
+
}
|
|
3210
|
+
|
|
3211
|
+
function DListGroupItem({ as = 'li', action: actionProp, active, ariaCurrent = 'true', disabled, href, onClick, color, iconStart, iconStartFamilyClass, iconStartFamilyPrefix, iconStartMaterialStyle, iconEnd, iconEndFamilyClass, iconEndFamilyPrefix, iconEndMaterialStyle, children, className, style, dataAttributes, }) {
|
|
2565
3212
|
const { icon: { familyClass, familyPrefix, materialStyle, }, } = useDContext();
|
|
2566
3213
|
const Tag = useMemo(() => {
|
|
2567
3214
|
if (href) {
|
|
@@ -2572,29 +3219,45 @@ function DListGroupItem({ as = 'li', action: actionProp, active, disabled, href,
|
|
|
2572
3219
|
}
|
|
2573
3220
|
return as;
|
|
2574
3221
|
}, [href, as, actionProp]);
|
|
2575
|
-
const
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
3222
|
+
const container = useContext(ListGroupContext);
|
|
3223
|
+
const isInteractive = Tag === 'a' || Tag === 'button';
|
|
3224
|
+
// Inside a <ul>/<ol>, a link or button is wrapped in the <li> that carries
|
|
3225
|
+
// the item styles, so the group keeps list semantics (`list > listitem >
|
|
3226
|
+
// link|button`) without breaking Bootstrap's sibling selectors.
|
|
3227
|
+
const isWrapped = isInteractive && (container === 'ul' || container === 'ol');
|
|
3228
|
+
if (process.env.NODE_ENV !== 'production' && container === 'div' && Tag === 'li') {
|
|
3229
|
+
warnInvalidListMarkup(container, Tag);
|
|
3230
|
+
}
|
|
2581
3231
|
const generateClasses = useMemo(() => ({
|
|
2582
3232
|
'list-group-item': true,
|
|
2583
|
-
'list-group-item-action':
|
|
3233
|
+
'list-group-item-action': isInteractive || actionProp,
|
|
3234
|
+
'd-list-group-item-interactive': isWrapped,
|
|
2584
3235
|
[`list-group-item-${color}`]: !!color,
|
|
2585
3236
|
active,
|
|
2586
3237
|
disabled,
|
|
2587
|
-
}), [
|
|
2588
|
-
|
|
3238
|
+
}), [isInteractive, actionProp, isWrapped, active, disabled, color]);
|
|
3239
|
+
// A disabled link leaves the tab order and can't be activated: without
|
|
3240
|
+
// `href` and `onClick`, Enter does nothing. Dropping `href` also drops the
|
|
3241
|
+
// implicit link role, so it is set back explicitly. A button uses
|
|
3242
|
+
// `disabled`.
|
|
3243
|
+
const interactiveProps = useMemo(() => {
|
|
2589
3244
|
if (Tag === 'button') {
|
|
2590
|
-
return Object.assign(Object.assign({}, active && { 'aria-current':
|
|
3245
|
+
return Object.assign(Object.assign(Object.assign({ type: 'button' }, onClick && { onClick }), active && { 'aria-current': ariaCurrent }), disabled && { disabled: true });
|
|
2591
3246
|
}
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
3247
|
+
if (Tag === 'a') {
|
|
3248
|
+
return disabled
|
|
3249
|
+
? Object.assign({ role: 'link', 'aria-disabled': true, tabIndex: -1 }, active && { 'aria-current': ariaCurrent }) : Object.assign(Object.assign(Object.assign({}, href && { href }), onClick && { onClick }), active && { 'aria-current': ariaCurrent });
|
|
3250
|
+
}
|
|
3251
|
+
return Object.assign(Object.assign(Object.assign({}, onClick && { onClick }), active && { 'aria-current': ariaCurrent }), disabled && { 'aria-disabled': true });
|
|
3252
|
+
}, [Tag, href, onClick, active, ariaCurrent, disabled]);
|
|
3253
|
+
const content = (jsxs(Fragment, { children: [iconStart && (jsx(DIcon, { icon: iconStart, familyClass: iconStartFamilyClass !== null && iconStartFamilyClass !== void 0 ? iconStartFamilyClass : familyClass, familyPrefix: iconStartFamilyPrefix !== null && iconStartFamilyPrefix !== void 0 ? iconStartFamilyPrefix : familyPrefix, materialStyle: iconStartMaterialStyle !== null && iconStartMaterialStyle !== void 0 ? iconStartMaterialStyle : materialStyle })), children, iconEnd && (jsx(DIcon, { icon: iconEnd, familyClass: iconEndFamilyClass !== null && iconEndFamilyClass !== void 0 ? iconEndFamilyClass : familyClass, familyPrefix: iconEndFamilyPrefix !== null && iconEndFamilyPrefix !== void 0 ? iconEndFamilyPrefix : familyPrefix, materialStyle: iconEndMaterialStyle !== null && iconEndMaterialStyle !== void 0 ? iconEndMaterialStyle : materialStyle, className: "ms-auto" }))] }));
|
|
3254
|
+
if (isWrapped) {
|
|
3255
|
+
return (jsx("li", { className: classNames(generateClasses, className), style: style, children: jsx(Tag, Object.assign({ className: "d-list-group-item-link" }, interactiveProps, dataAttributes, { children: content })) }));
|
|
3256
|
+
}
|
|
3257
|
+
return (jsx(Tag, Object.assign({ className: classNames(generateClasses, className), style: style }, interactiveProps, dataAttributes, { children: content })));
|
|
2595
3258
|
}
|
|
2596
3259
|
|
|
2597
|
-
function DListGroup({ as = 'ul', numbered, flush, horizontal, children, className, style, dataAttributes, }) {
|
|
3260
|
+
function DListGroup({ as = 'ul', numbered, flush, horizontal, ariaLabel, ariaLabelledBy, children, className, style, dataAttributes, }) {
|
|
2598
3261
|
const Tag = useMemo(() => {
|
|
2599
3262
|
if (numbered) {
|
|
2600
3263
|
return 'ol';
|
|
@@ -2612,7 +3275,14 @@ function DListGroup({ as = 'ul', numbered, flush, horizontal, children, classNam
|
|
|
2612
3275
|
[listGroupHorizontalClass]: !!horizontal,
|
|
2613
3276
|
};
|
|
2614
3277
|
}, [flush, horizontal, numbered]);
|
|
2615
|
-
|
|
3278
|
+
const labelProps = useMemo(() => {
|
|
3279
|
+
if (!ariaLabelledBy && !ariaLabel)
|
|
3280
|
+
return {};
|
|
3281
|
+
return Object.assign(Object.assign({}, ariaLabelledBy
|
|
3282
|
+
? { 'aria-labelledby': ariaLabelledBy }
|
|
3283
|
+
: { 'aria-label': ariaLabel }), Tag === 'div' && { role: 'group' });
|
|
3284
|
+
}, [ariaLabel, ariaLabelledBy, Tag]);
|
|
3285
|
+
return (jsx(ListGroupContext.Provider, { value: Tag, children: jsx(Tag, Object.assign({ className: classNames(generateClasses, className), style: style }, labelProps, dataAttributes, { children: children })) }));
|
|
2616
3286
|
}
|
|
2617
3287
|
var DListGroup$1 = Object.assign(DListGroup, {
|
|
2618
3288
|
Item: DListGroupItem,
|
|
@@ -2798,14 +3468,20 @@ function DPopover({ children, renderComponent, open, setOpen, adjustContentToRen
|
|
|
2798
3468
|
}), ref: refs.setFloating, style: floatingStyles, "aria-labelledby": headingId }, getFloatingProps(), { children: children })) }))] })));
|
|
2799
3469
|
}
|
|
2800
3470
|
|
|
2801
|
-
function DProgress({ className, style, currentValue, minValue = 0, maxValue = 100, hideCurrentValue = false, enableStripedAnimation = false, height, dataAttributes, }) {
|
|
2802
|
-
const percentage = useMemo(() =>
|
|
3471
|
+
function DProgress({ className, style, currentValue, minValue = 0, maxValue = 100, hideCurrentValue = false, enableStripedAnimation = false, height, ariaLabel, ariaLabelledBy, dataAttributes, }) {
|
|
3472
|
+
const percentage = useMemo(() => {
|
|
3473
|
+
const range = maxValue - minValue;
|
|
3474
|
+
if (range <= 0) {
|
|
3475
|
+
return 0;
|
|
3476
|
+
}
|
|
3477
|
+
return Math.round(((currentValue - minValue) * 100) / range);
|
|
3478
|
+
}, [currentValue, minValue, maxValue]);
|
|
2803
3479
|
const formatProgress = useMemo(() => `${percentage}%`, [percentage]);
|
|
2804
3480
|
const generateClasses = useMemo(() => ({
|
|
2805
3481
|
'progress-bar': true,
|
|
2806
3482
|
'progress-bar-striped progress-bar-animated': enableStripedAnimation,
|
|
2807
3483
|
}), [enableStripedAnimation]);
|
|
2808
|
-
return (jsx("div", Object.assign({ className: classNames('progress', className), style: Object.assign({ height }, style) }, dataAttributes, { children: jsx("div", { className: classNames(generateClasses), role: "progressbar", "aria-label":
|
|
3484
|
+
return (jsx("div", Object.assign({ className: classNames('progress', className), style: Object.assign({ height }, style) }, dataAttributes, { children: jsx("div", { className: classNames(generateClasses), role: "progressbar", "aria-label": ariaLabelledBy ? undefined : (ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : 'Progress bar'), "aria-labelledby": ariaLabelledBy, style: { width: formatProgress }, "aria-valuenow": currentValue, "aria-valuemin": minValue, "aria-valuemax": maxValue, children: !hideCurrentValue && formatProgress }) })));
|
|
2809
3485
|
}
|
|
2810
3486
|
|
|
2811
3487
|
function DStepper$2({ options, currentStep, iconSuccess: iconSuccessProp, iconSuccessFamilyClass, iconSuccessFamilyPrefix, iconSuccessMaterialStyle, vertical = false, completed, alignStart = false, className, style, }) {
|
|
@@ -2913,10 +3589,11 @@ function DTimeline({ className, style, dataAttributes, items, }) {
|
|
|
2913
3589
|
}
|
|
2914
3590
|
|
|
2915
3591
|
const TabContext = createContext(undefined);
|
|
3592
|
+
const TabsStateContext = createContext(undefined);
|
|
2916
3593
|
function useTabContext() {
|
|
2917
3594
|
const context = useContext(TabContext);
|
|
2918
3595
|
if (context === undefined) {
|
|
2919
|
-
throw new Error('useTabContext was used outside of
|
|
3596
|
+
throw new Error('useTabContext was used outside of DTabs');
|
|
2920
3597
|
}
|
|
2921
3598
|
return context;
|
|
2922
3599
|
}
|
|
@@ -2926,33 +3603,44 @@ function DTabContent({ tab, children, className, style, }) {
|
|
|
2926
3603
|
if (!isSelected(tab)) {
|
|
2927
3604
|
return null;
|
|
2928
3605
|
}
|
|
2929
|
-
return (jsx("div", { className: classNames('tab-pane fade show active', className), id: `${tab}Pane`, role: "tabpanel", tabIndex: 0, "aria-labelledby": `${tab}Tab`, style: style, children: children }));
|
|
3606
|
+
return (jsx("div", { className: classNames('tab-pane fade show active', className), id: `${tab}Pane`, role: "tabpanel", tabIndex: 0, "aria-labelledby": `${tab}Tab`, style: style, children: jsx(TabsStateContext.Provider, { value: undefined, children: children }) }));
|
|
2930
3607
|
}
|
|
2931
3608
|
|
|
2932
|
-
|
|
3609
|
+
/**
|
|
3610
|
+
* Owns the selected tab so the tab bar (`DTabs`) and its panels (`DTabs.Tab`)
|
|
3611
|
+
* can live anywhere inside it, e.g. the bar in a header and the panels in
|
|
3612
|
+
* another column.
|
|
3613
|
+
*/
|
|
3614
|
+
function DTabsProvider({ defaultSelected, children, }) {
|
|
2933
3615
|
const [selected, setSelected] = useState(defaultSelected);
|
|
3616
|
+
useEffect(() => {
|
|
3617
|
+
setSelected(defaultSelected);
|
|
3618
|
+
}, [defaultSelected]);
|
|
3619
|
+
const isSelected = useCallback((tab) => (selected === tab), [selected]);
|
|
3620
|
+
const state = useMemo(() => ({ selected, setSelected }), [selected]);
|
|
3621
|
+
const value = useMemo(() => ({ isSelected }), [isSelected]);
|
|
3622
|
+
return (jsx(TabsStateContext.Provider, { value: state, children: jsx(TabContext.Provider, { value: value, children: children }) }));
|
|
3623
|
+
}
|
|
3624
|
+
|
|
3625
|
+
function DTabs({ children, defaultSelected, onChange, options, className, classNameTab, classNameContent, style, vertical, variant = 'underline', dataAttributes, ariaLabel, ariaLabelledBy, }) {
|
|
3626
|
+
var _a;
|
|
3627
|
+
const shared = useContext(TabsStateContext);
|
|
3628
|
+
const [ownSelected, setOwnSelected] = useState(defaultSelected);
|
|
3629
|
+
const selected = shared
|
|
3630
|
+
? shared.selected
|
|
3631
|
+
: ownSelected !== null && ownSelected !== void 0 ? ownSelected : (_a = options.find((opt) => !opt.disabled)) === null || _a === void 0 ? void 0 : _a.tab;
|
|
3632
|
+
const setSelected = shared ? shared.setSelected : setOwnSelected;
|
|
2934
3633
|
const onSelect = useCallback((option) => {
|
|
2935
3634
|
if (option.tab) {
|
|
2936
3635
|
setSelected(option.tab);
|
|
2937
3636
|
}
|
|
2938
3637
|
onChange === null || onChange === void 0 ? void 0 : onChange(option);
|
|
2939
|
-
}, [onChange]);
|
|
3638
|
+
}, [onChange, setSelected]);
|
|
2940
3639
|
useEffect(() => {
|
|
2941
|
-
|
|
3640
|
+
setOwnSelected(defaultSelected);
|
|
2942
3641
|
}, [defaultSelected]);
|
|
2943
3642
|
const generateClasses = useMemo(() => (Object.assign({ nav: true, 'd-tabs-nav-vertical': vertical && variant !== 'tabs', [`nav-${variant}`]: true }, className && { [className]: true })), [vertical, variant, className]);
|
|
2944
3643
|
const tabRefs = useRef([]);
|
|
2945
|
-
// Always holds the latest `options` without needing to be a dependency:
|
|
2946
|
-
// `options` is commonly passed as an inline array literal (e.g.
|
|
2947
|
-
// `options={[{ label: 'SMS', tab: 'sms' }, ...]}`), so it's a new array
|
|
2948
|
-
// reference on every parent render even when its content hasn't changed.
|
|
2949
|
-
// Reading it from this ref (updated synchronously on every render) lets
|
|
2950
|
-
// the focus effect below react only to `selected` changing.
|
|
2951
|
-
const optionsRef = useRef(options);
|
|
2952
|
-
optionsRef.current = options;
|
|
2953
|
-
useEffect(() => {
|
|
2954
|
-
tabRefs.current = options.map((_, i) => tabRefs.current[i] || createRef());
|
|
2955
|
-
}, [options]);
|
|
2956
3644
|
// Ensure selected is never disabled
|
|
2957
3645
|
useEffect(() => {
|
|
2958
3646
|
if (options.length === 0)
|
|
@@ -2963,29 +3651,16 @@ function DTabs({ children, defaultSelected, onChange, options, className, classN
|
|
|
2963
3651
|
if (firstEnabled)
|
|
2964
3652
|
setSelected(firstEnabled.tab);
|
|
2965
3653
|
}
|
|
2966
|
-
}, [options, selected]);
|
|
2967
|
-
//
|
|
2968
|
-
//
|
|
2969
|
-
//
|
|
2970
|
-
//
|
|
3654
|
+
}, [options, selected, setSelected]);
|
|
3655
|
+
// Focus only moves in response to the user: arrow keys and clicks call
|
|
3656
|
+
// this directly. Changes to `selected` that don't come from an interaction
|
|
3657
|
+
// (mount, a new `defaultSelected`, the disabled-tab fallback above) leave
|
|
3658
|
+
// focus where it is, so the page doesn't scroll to the tablist and no other
|
|
3659
|
+
// element (a search field, an OTP input) loses focus.
|
|
2971
3660
|
const focusTab = useCallback((idx) => {
|
|
2972
3661
|
var _a;
|
|
2973
|
-
|
|
2974
|
-
tabRefs.current[idx].current.focus();
|
|
2975
|
-
}
|
|
3662
|
+
(_a = tabRefs.current[idx]) === null || _a === void 0 ? void 0 : _a.focus();
|
|
2976
3663
|
}, []);
|
|
2977
|
-
// Focus selected tab when selected changes.
|
|
2978
|
-
// Reads `options` from `optionsRef` (see comment above) instead of
|
|
2979
|
-
// depending on `options` directly, so a parent re-render that merely
|
|
2980
|
-
// creates a new `options` reference with identical content doesn't call
|
|
2981
|
-
// `focusTab` again and steal focus away from unrelated elements on the
|
|
2982
|
-
// page (e.g. an OTP input).
|
|
2983
|
-
useEffect(() => {
|
|
2984
|
-
const idx = optionsRef.current.findIndex((opt) => opt.tab === selected && !opt.disabled);
|
|
2985
|
-
if (idx !== -1) {
|
|
2986
|
-
focusTab(idx);
|
|
2987
|
-
}
|
|
2988
|
-
}, [selected, focusTab]);
|
|
2989
3664
|
const handleKeyDown = useCallback((idx, e) => {
|
|
2990
3665
|
const count = options.length;
|
|
2991
3666
|
if (count === 0)
|
|
@@ -3014,7 +3689,7 @@ function DTabs({ children, defaultSelected, onChange, options, className, classN
|
|
|
3014
3689
|
}
|
|
3015
3690
|
}
|
|
3016
3691
|
}
|
|
3017
|
-
}, [options, vertical, focusTab]);
|
|
3692
|
+
}, [options, vertical, focusTab, setSelected]);
|
|
3018
3693
|
let tablistProps = {};
|
|
3019
3694
|
if (ariaLabelledBy) {
|
|
3020
3695
|
tablistProps = { 'aria-labelledby': ariaLabelledBy };
|
|
@@ -3031,11 +3706,17 @@ function DTabs({ children, defaultSelected, onChange, options, className, classN
|
|
|
3031
3706
|
'd-tabs-column': !vertical || variant === 'tabs',
|
|
3032
3707
|
}), style: style }, dataAttributes, { children: [jsx("ul", Object.assign({ className: classNames(generateClasses), role: "tablist", "aria-orientation": vertical ? 'vertical' : undefined }, tablistProps, { children: options.map((option, idx) => {
|
|
3033
3708
|
const isTabSelected = !!option.tab && option.tab === selected;
|
|
3034
|
-
return (jsx("li", { role: "presentation", className: "nav-item", children: jsx("button", { ref:
|
|
3035
|
-
|
|
3709
|
+
return (jsx("li", { role: "presentation", className: "nav-item", children: jsx("button", { ref: (element) => {
|
|
3710
|
+
tabRefs.current[idx] = element;
|
|
3711
|
+
}, id: `${option.tab}Tab`, className: classNames('nav-link', { active: isTabSelected }, classNameTab), type: "button", role: "tab", "aria-controls": `${option.tab}Pane`, "aria-selected": isTabSelected, tabIndex: isTabSelected ? 0 : -1, disabled: option.disabled, onClick: () => {
|
|
3712
|
+
focusTab(idx);
|
|
3713
|
+
onSelect(option);
|
|
3714
|
+
}, onKeyDown: (e) => handleKeyDown(idx, e), children: option.label }) }, option.tab));
|
|
3715
|
+
}) })), Children.toArray(children).length > 0 && (jsx("div", { className: classNames('d-tabs-content tab-content', classNameContent), children: children }))] })) }));
|
|
3036
3716
|
}
|
|
3037
3717
|
var DTabs$1 = Object.assign(DTabs, {
|
|
3038
3718
|
Tab: DTabContent,
|
|
3719
|
+
Provider: DTabsProvider,
|
|
3039
3720
|
});
|
|
3040
3721
|
|
|
3041
3722
|
function DToastHeader({ children, className, style }) {
|
|
@@ -3046,8 +3727,13 @@ function DToastBody({ children, className, style }) {
|
|
|
3046
3727
|
return (jsx("div", { className: classNames('toast-body', className), style: style, children: children }));
|
|
3047
3728
|
}
|
|
3048
3729
|
|
|
3049
|
-
|
|
3050
|
-
|
|
3730
|
+
const LIVE_REGION_ATTRIBUTES = {
|
|
3731
|
+
alert: { role: 'alert', 'aria-live': 'assertive', 'aria-atomic': 'true' },
|
|
3732
|
+
status: { role: 'status', 'aria-live': 'polite', 'aria-atomic': 'true' },
|
|
3733
|
+
none: {},
|
|
3734
|
+
};
|
|
3735
|
+
function DToast({ children, role = 'alert', className, style, dataAttributes, }) {
|
|
3736
|
+
return (jsx("div", Object.assign({ className: classNames('toast', className) }, LIVE_REGION_ATTRIBUTES[role], { style: style }, dataAttributes, { children: children })));
|
|
3051
3737
|
}
|
|
3052
3738
|
var DToast$1 = Object.assign(DToast, {
|
|
3053
3739
|
Header: DToastHeader,
|
|
@@ -3086,19 +3772,19 @@ function useDToast() {
|
|
|
3086
3772
|
if (typeof data === 'function') {
|
|
3087
3773
|
return toast.custom(data, toastProps);
|
|
3088
3774
|
}
|
|
3089
|
-
const { title, description, icon, closeIcon, timestamp, color, } = data;
|
|
3775
|
+
const { title, description, icon, closeIcon, timestamp, color, role, closeAriaLabel = 'Close', } = data;
|
|
3090
3776
|
return toast.custom(({ id, visible }) => {
|
|
3091
3777
|
if (!visible) {
|
|
3092
3778
|
return null;
|
|
3093
3779
|
}
|
|
3094
3780
|
if (!description) {
|
|
3095
|
-
return (jsx(DToast$1, { className: classNames({
|
|
3781
|
+
return (jsx(DToast$1, { role: role, className: classNames({
|
|
3096
3782
|
[`toast-${color}`]: !!color,
|
|
3097
|
-
}, 'show'), children: jsxs(DToast$1.Body, { children: [icon && (jsx(DIcon, { className: "toast-icon", icon: icon })), jsx("p", { className: "toast-title", children: title }), jsx("button", { type: "button", className: "d-close", "aria-label":
|
|
3783
|
+
}, 'show'), children: jsxs(DToast$1.Body, { children: [icon && (jsx(DIcon, { className: "toast-icon", icon: icon })), jsx("p", { className: "toast-title", children: title }), jsx("button", { type: "button", className: "d-close", "aria-label": closeAriaLabel, onClick: () => toast.dismiss(id), children: jsx(DIcon, { icon: closeIcon || xLg }) })] }) }));
|
|
3098
3784
|
}
|
|
3099
|
-
return (jsxs(DToast$1, { className: classNames({
|
|
3785
|
+
return (jsxs(DToast$1, { role: role, className: classNames({
|
|
3100
3786
|
[`toast-${color}`]: !!color,
|
|
3101
|
-
}, 'show'), children: [jsxs(DToast$1.Header, { children: [icon && (jsx(DIcon, { className: "toast-icon", icon: icon })), jsx("p", { className: "toast-title", children: title }), timestamp && (jsx("small", { className: "toast-timestamp", children: timestamp })), jsx("button", { type: "button", className: "d-close", "aria-label":
|
|
3787
|
+
}, 'show'), children: [jsxs(DToast$1.Header, { children: [icon && (jsx(DIcon, { className: "toast-icon", icon: icon })), jsx("p", { className: "toast-title", children: title }), timestamp && (jsx("small", { className: "toast-timestamp", children: timestamp })), jsx("button", { type: "button", className: "d-close", "aria-label": closeAriaLabel, onClick: () => toast.dismiss(id), children: jsx(DIcon, { icon: closeIcon || xLg }) })] }), jsx(DToast$1.Body, { children: jsx("span", { children: description }) })] }));
|
|
3102
3788
|
}, toastProps);
|
|
3103
3789
|
}, [xLg]);
|
|
3104
3790
|
return {
|
|
@@ -3334,7 +4020,7 @@ function DInputPhone(_a, ref) {
|
|
|
3334
4020
|
placeholder,
|
|
3335
4021
|
valid,
|
|
3336
4022
|
]);
|
|
3337
|
-
const labelComponent = useMemo(() => (jsx(
|
|
4023
|
+
const labelComponent = useMemo(() => (jsx(DFormLabel, { htmlFor: id, children: label })), [
|
|
3338
4024
|
id,
|
|
3339
4025
|
label,
|
|
3340
4026
|
]);
|
|
@@ -3348,7 +4034,16 @@ function DInputPhone(_a, ref) {
|
|
|
3348
4034
|
inputComponent,
|
|
3349
4035
|
labelComponent,
|
|
3350
4036
|
]);
|
|
3351
|
-
|
|
4037
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
4038
|
+
warnLabelUsage({
|
|
4039
|
+
component: 'DInputPhone',
|
|
4040
|
+
label,
|
|
4041
|
+
hasAccessibleName: !!inputProps['aria-label'] || !!inputProps['aria-labelledby'],
|
|
4042
|
+
accessibleNameProp: 'aria-label',
|
|
4043
|
+
floatingLabel,
|
|
4044
|
+
});
|
|
4045
|
+
}
|
|
4046
|
+
return (jsxs("div", Object.assign({ className: classNames('d-input-phone', className), style: style }, dataAttributes, { children: [hasLabelContent(label) && !floatingLabel && labelComponent, jsxs("div", { className: classNames({
|
|
3352
4047
|
[`input-group-${size}`]: !!size,
|
|
3353
4048
|
'input-group': true,
|
|
3354
4049
|
'has-validation': invalid || valid,
|
|
@@ -3815,11 +4510,11 @@ function DOtp({ className, action, isLoading, otpSize = 6, texts = TEXT_PROPS, s
|
|
|
3815
4510
|
}, loading: isLoading || isSubmitting }), jsx("p", { className: "d-otp-contact", children: texts.contact })] })] })] }));
|
|
3816
4511
|
}
|
|
3817
4512
|
|
|
3818
|
-
function DefaultErrorBoundary({ resetErrorBoundary }) {
|
|
3819
|
-
return (jsx(DAlert, { color: "danger", showClose: false, children: jsxs("div", { className: "d-error-boundary-content", children: [jsx("span", { children:
|
|
4513
|
+
function DefaultErrorBoundary({ resetErrorBoundary, message = 'An unexpected error occurred.', retryMessage = 'Retry', }) {
|
|
4514
|
+
return (jsx(DAlert, { color: "danger", showClose: false, children: jsxs("div", { className: "d-error-boundary-content", children: [jsx("span", { children: message }), jsx(DButton, { color: "secondary", variant: "outline", size: "sm", onClick: resetErrorBoundary, children: retryMessage })] }) }));
|
|
3820
4515
|
}
|
|
3821
4516
|
|
|
3822
|
-
function DErrorBoundary({ name, fallback, resetKeys, onReset, onError, children, }) {
|
|
4517
|
+
function DErrorBoundary({ name, fallback, resetKeys, onReset, onError, messages, children, }) {
|
|
3823
4518
|
const handleError = useCallback((error, info) => {
|
|
3824
4519
|
// eslint-disable-next-line no-console
|
|
3825
4520
|
console.error(`[DErrorBoundary${name ? `:${name}` : ''}]`, getErrorMessage(error), info);
|
|
@@ -3828,8 +4523,8 @@ function DErrorBoundary({ name, fallback, resetKeys, onReset, onError, children,
|
|
|
3828
4523
|
const FallbackRender = useCallback((props) => {
|
|
3829
4524
|
if (fallback)
|
|
3830
4525
|
return fallback(props);
|
|
3831
|
-
return (jsx(DefaultErrorBoundary, { resetErrorBoundary: props.resetErrorBoundary }));
|
|
3832
|
-
}, [fallback]);
|
|
4526
|
+
return (jsx(DefaultErrorBoundary, { resetErrorBoundary: props.resetErrorBoundary, message: messages === null || messages === void 0 ? void 0 : messages.error, retryMessage: messages === null || messages === void 0 ? void 0 : messages.retry }));
|
|
4527
|
+
}, [fallback, messages]);
|
|
3833
4528
|
return (jsx(ErrorBoundary, { resetKeys: resetKeys, onReset: onReset, onError: handleError, fallbackRender: FallbackRender, children: children }));
|
|
3834
4529
|
}
|
|
3835
4530
|
|
|
@@ -3850,6 +4545,11 @@ function render(renderable) {
|
|
|
3850
4545
|
return null;
|
|
3851
4546
|
return typeof renderable === 'function' ? renderable() : renderable;
|
|
3852
4547
|
}
|
|
4548
|
+
function isEmpty(data) {
|
|
4549
|
+
if (Array.isArray(data))
|
|
4550
|
+
return data.length === 0;
|
|
4551
|
+
return data === null || data === undefined;
|
|
4552
|
+
}
|
|
3853
4553
|
function DDataStateWrapper({ isLoading, isError, data, onRetry, messages, renderLoading, renderEmpty, renderError, children, }) {
|
|
3854
4554
|
// 1. Loading
|
|
3855
4555
|
if (isLoading) {
|
|
@@ -3863,13 +4563,15 @@ function DDataStateWrapper({ isLoading, isError, data, onRetry, messages, render
|
|
|
3863
4563
|
return render(renderError);
|
|
3864
4564
|
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 }));
|
|
3865
4565
|
}
|
|
3866
|
-
// 3. Empty
|
|
3867
|
-
if (
|
|
4566
|
+
// 3. Empty: no items for a collection, null/undefined for a single resource
|
|
4567
|
+
if (isEmpty(data)) {
|
|
3868
4568
|
if (renderEmpty)
|
|
3869
4569
|
return render(renderEmpty);
|
|
3870
4570
|
return (jsx(EmptyState, { message: messages === null || messages === void 0 ? void 0 : messages.empty }));
|
|
3871
4571
|
}
|
|
3872
|
-
// 4. Success
|
|
4572
|
+
// 4. Success: the render prop gets the same shape it was given
|
|
4573
|
+
// Both overloads pair `data` with its own `children` signature, so the
|
|
4574
|
+
// value is handed back exactly as it was received.
|
|
3873
4575
|
return jsx(Fragment, { children: children(data) });
|
|
3874
4576
|
}
|
|
3875
4577
|
|
|
@@ -4007,5 +4709,5 @@ const CORE_LUCIDE_ICONS = [
|
|
|
4007
4709
|
'Trash',
|
|
4008
4710
|
];
|
|
4009
4711
|
|
|
4010
|
-
export { CORE_LUCIDE_ICONS, 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, buildUrl, changeQueryString, checkMediaQuery, configureI8n as configureI18n, formatCurrency, getCssVariable, getQueryString, sanitizeHref, subscribeToMediaQuery, useConfirmModal, useCountdown, useDContext, useDPortalContext, useDToast, useDisableBodyScrollEffect, useDisableInputWheel, useFormatCurrency, useInputCurrency, useItemSelection, useMediaBreakpointUpLg, useMediaBreakpointUpMd, useMediaBreakpointUpSm, useMediaBreakpointUpXl, useMediaBreakpointUpXs, useMediaBreakpointUpXxl, useMediaQuery, useOtp, useProvidedRefOrCreate, useScreenshot, useScreenshotDownload, useScreenshotWebShare, useStackState, useTabContext, validatePhoneNumber };
|
|
4712
|
+
export { CORE_LUCIDE_ICONS, 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, DTabsProvider, DTimeline, DToast$1 as DToast, DToastContainer, DTooltip, DVoucher, EmptyState, ErrorState, LoadingState, buildUrl, changeQueryString, checkMediaQuery, configureI8n as configureI18n, formatCurrency, getCssVariable, getQueryString, sanitizeHref, subscribeToMediaQuery, useConfirmModal, useCountdown, useDContext, useDPortalContext, useDToast, useDisableBodyScrollEffect, useDisableInputWheel, useFormatCurrency, useInputCurrency, useItemSelection, useMediaBreakpointUpLg, useMediaBreakpointUpMd, useMediaBreakpointUpSm, useMediaBreakpointUpXl, useMediaBreakpointUpXs, useMediaBreakpointUpXxl, useMediaQuery, useOtp, useProvidedRefOrCreate, useScreenshot, useScreenshotDownload, useScreenshotWebShare, useStackState, useTabContext, validatePhoneNumber };
|
|
4011
4713
|
//# sourceMappingURL=index.esm.js.map
|