@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.js
CHANGED
|
@@ -7,9 +7,9 @@ var tslib = require('tslib');
|
|
|
7
7
|
var LucideIcons = require('lucide-react');
|
|
8
8
|
var reactIs = require('react-is');
|
|
9
9
|
var reactDom = require('react-dom');
|
|
10
|
-
var framerMotion = require('framer-motion');
|
|
11
10
|
var fileSelector = require('file-selector');
|
|
12
11
|
var reactSplide = require('@splidejs/react-splide');
|
|
12
|
+
var framerMotion = require('framer-motion');
|
|
13
13
|
var currency = require('currency.js');
|
|
14
14
|
var DatePicker = require('react-datepicker');
|
|
15
15
|
var dateFns = require('date-fns');
|
|
@@ -171,11 +171,30 @@ function getKeyboardFocusableElements(container) {
|
|
|
171
171
|
].filter((element) => !element.hasAttribute('disabled'));
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
+
const loadPortalStack = () => Promise.resolve().then(function () { return require('./DPortalStack-6zJm6JoD.js'); }).then((module) => module.default);
|
|
174
175
|
const DPortalContext = React.createContext(undefined);
|
|
175
176
|
function DPortalContextProvider({ portalName, children, availablePortals, }) {
|
|
176
177
|
const { created } = usePortal(portalName);
|
|
177
178
|
const [stack, { push, pop }] = useStackState([]);
|
|
179
|
+
const [PortalStack, setPortalStack] = React.useState(null);
|
|
180
|
+
const pending = React.useRef([]);
|
|
181
|
+
const ready = React.useRef(false);
|
|
178
182
|
useDisableBodyScrollEffect(Boolean(stack.length));
|
|
183
|
+
// Loads framer-motion off the critical path only when portals are configured,
|
|
184
|
+
// so the first openPortal does not wait for it.
|
|
185
|
+
const hasPortals = Object.keys(availablePortals !== null && availablePortals !== void 0 ? availablePortals : {}).length > 0;
|
|
186
|
+
React.useEffect(() => {
|
|
187
|
+
if (!hasPortals)
|
|
188
|
+
return;
|
|
189
|
+
loadPortalStack()
|
|
190
|
+
.then((Stack) => {
|
|
191
|
+
ready.current = true;
|
|
192
|
+
setPortalStack(() => Stack);
|
|
193
|
+
})
|
|
194
|
+
.catch(() => {
|
|
195
|
+
// openPortal retries the import and reports the failure.
|
|
196
|
+
});
|
|
197
|
+
}, [hasPortals]);
|
|
179
198
|
const openPortal = React.useCallback(
|
|
180
199
|
// eslint-disable-next-line prefer-arrow-callback
|
|
181
200
|
function openPortalImpl(name, payload) {
|
|
@@ -191,10 +210,30 @@ function DPortalContextProvider({ portalName, children, availablePortals, }) {
|
|
|
191
210
|
}
|
|
192
211
|
// K is a specific member of keyof T & string so the object satisfies
|
|
193
212
|
// InternalStackItem<T>, but TS can't verify generic-over-union assignability.
|
|
194
|
-
|
|
213
|
+
const item = { name, Component, payload };
|
|
214
|
+
if (ready.current) {
|
|
215
|
+
push(item);
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
// Not loaded yet: the portal enters the stack together with its renderer.
|
|
219
|
+
pending.current.push(item);
|
|
220
|
+
loadPortalStack().then((Stack) => {
|
|
221
|
+
ready.current = true;
|
|
222
|
+
setPortalStack(() => Stack);
|
|
223
|
+
pending.current.splice(0).forEach(push);
|
|
224
|
+
}).catch((error) => {
|
|
225
|
+
pending.current = [];
|
|
226
|
+
// eslint-disable-next-line no-console
|
|
227
|
+
console.error('[DPortalContext] Could not load the portal stack', error);
|
|
228
|
+
});
|
|
229
|
+
}
|
|
195
230
|
(_a = document.activeElement) === null || _a === void 0 ? void 0 : _a.blur();
|
|
196
231
|
}, [availablePortals, push]);
|
|
197
232
|
const closePortal = React.useCallback(() => {
|
|
233
|
+
if (pending.current.length > 0) {
|
|
234
|
+
pending.current.pop();
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
198
237
|
// pop() is safe on empty stacks, so close remains idempotent.
|
|
199
238
|
pop();
|
|
200
239
|
}, [pop]);
|
|
@@ -251,13 +290,10 @@ function DPortalContextProvider({ portalName, children, availablePortals, }) {
|
|
|
251
290
|
window.removeEventListener('keydown', keyEvent);
|
|
252
291
|
};
|
|
253
292
|
}, [handleClose, portalName, stack.length]);
|
|
254
|
-
return (jsxRuntime.jsxs(DPortalContext.Provider, { value: value, children: [children, created && reactDom.createPortal(
|
|
293
|
+
return (jsxRuntime.jsxs(DPortalContext.Provider, { value: value, children: [children, created && PortalStack && reactDom.createPortal(
|
|
255
294
|
// eslint-disable-next-line max-len
|
|
256
295
|
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
|
257
|
-
jsxRuntime.jsx("div", { onClick: ({ target }) => handleClose(target), onKeyDown: () => { }, children: jsxRuntime.jsx(
|
|
258
|
-
jsxRuntime.jsx(framerMotion.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`),
|
|
259
|
-
jsxRuntime.jsx(Component, { name: name, payload: payload }, name),
|
|
260
|
-
]) }) }), document.getElementById(portalName))] }));
|
|
296
|
+
jsxRuntime.jsx("div", { onClick: ({ target }) => handleClose(target), onKeyDown: () => { }, children: jsxRuntime.jsx(PortalStack, { stack: stack }) }), document.getElementById(portalName))] }));
|
|
261
297
|
}
|
|
262
298
|
/**
|
|
263
299
|
* Hook to open/close registered portals (modals, offcanvas, etc.).
|
|
@@ -532,9 +568,43 @@ function useMediaQuery(mediaQuery, useListener = false) {
|
|
|
532
568
|
return React.useSyncExternalStore(useListener ? (cb) => subscribeToMediaQuery(mediaQuery, cb) : noop, () => (mediaQuery ? checkMediaQuery(mediaQuery) : true), () => false);
|
|
533
569
|
}
|
|
534
570
|
|
|
535
|
-
|
|
571
|
+
const BREAKPOINTS = ['xs', 'sm', 'md', 'lg', 'xl', 'xxl'];
|
|
572
|
+
// Breakpoints read from the CSS on first use and shared by every component
|
|
573
|
+
// that resolves responsive props: one computed-style read for the page,
|
|
574
|
+
// instead of one per breakpoint per render. `dynamic-ui.css` has to be loaded
|
|
575
|
+
// before rendering; if it isn't, the empty result is kept too, so a
|
|
576
|
+
// misconfigured tree doesn't pay the read on every render (responsive props
|
|
577
|
+
// then keep their default value and useResponsiveProp warns in development).
|
|
578
|
+
let cssBreakpoints = null;
|
|
579
|
+
function readCssBreakpoint(breakpoint) {
|
|
580
|
+
if (cssBreakpoints)
|
|
581
|
+
return cssBreakpoints[breakpoint];
|
|
582
|
+
if (typeof document === 'undefined')
|
|
583
|
+
return '';
|
|
584
|
+
const style = getComputedStyle(document.documentElement);
|
|
585
|
+
const read = Object.fromEntries(BREAKPOINTS.map((name) => [
|
|
586
|
+
name,
|
|
587
|
+
style.getPropertyValue(`--${PREFIX_BS}breakpoint-${name}`).trim(),
|
|
588
|
+
]));
|
|
589
|
+
cssBreakpoints = read;
|
|
590
|
+
return read[breakpoint];
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Pixel value of a breakpoint. `DContextProvider` reads them from the CSS and
|
|
594
|
+
* shares them through the context; outside of it (a tree without the
|
|
595
|
+
* provider, or its first render) they are read from the same CSS variables
|
|
596
|
+
* here, so responsive props resolve either way.
|
|
597
|
+
*/
|
|
598
|
+
function useBreakpointValue(breakpoint) {
|
|
536
599
|
const { breakpoints } = useDContext();
|
|
537
|
-
const
|
|
600
|
+
const fromContext = breakpoints[breakpoint];
|
|
601
|
+
return React.useMemo(() => fromContext || readCssBreakpoint(breakpoint), [fromContext, breakpoint]);
|
|
602
|
+
}
|
|
603
|
+
function useMediaBreakpointUp(breakpoint, useListener = false) {
|
|
604
|
+
const value = useBreakpointValue(breakpoint);
|
|
605
|
+
// Without a breakpoint the query can't match; `not all` keeps it false
|
|
606
|
+
// instead of an invalid `(min-width: )`.
|
|
607
|
+
const mediaQuery = value ? `(min-width: ${value})` : 'not all';
|
|
538
608
|
return useMediaQuery(mediaQuery, useListener);
|
|
539
609
|
}
|
|
540
610
|
function useMediaBreakpointUpXs(useListener = false) {
|
|
@@ -556,6 +626,7 @@ function useMediaBreakpointUpXxl(useListener = false) {
|
|
|
556
626
|
return useMediaBreakpointUp('xxl', useListener);
|
|
557
627
|
}
|
|
558
628
|
|
|
629
|
+
let warnedMissingBreakpoints = false;
|
|
559
630
|
/**
|
|
560
631
|
* React hook to resolve a responsive property value based on the current viewport breakpoint.
|
|
561
632
|
*
|
|
@@ -581,7 +652,16 @@ function useResponsiveProp(useListener = false) {
|
|
|
581
652
|
const bpLgUp = useMediaBreakpointUpLg(useListener);
|
|
582
653
|
const bpXlUp = useMediaBreakpointUpXl(useListener);
|
|
583
654
|
const bpXxlUp = useMediaBreakpointUpXxl(useListener);
|
|
655
|
+
// `xs` is `0` and can't tell a missing variable apart, so `sm` is checked.
|
|
656
|
+
const hasBreakpoints = !!useBreakpointValue('sm');
|
|
584
657
|
const responsivePropValue = React.useCallback((prop) => {
|
|
658
|
+
if (process.env.NODE_ENV !== 'production' && !hasBreakpoints && !warnedMissingBreakpoints) {
|
|
659
|
+
warnedMissingBreakpoints = true;
|
|
660
|
+
// eslint-disable-next-line no-console
|
|
661
|
+
console.warn(`[Dynamic UI] The --${PREFIX_BS}breakpoint-* CSS variables are not available, so a `
|
|
662
|
+
+ 'responsive prop (an object by breakpoint) falls back to its default value. Load '
|
|
663
|
+
+ 'dynamic-ui.css before rendering. It never appears in production builds.');
|
|
664
|
+
}
|
|
585
665
|
// Pick the highest matched breakpoint value that is defined in prop
|
|
586
666
|
if (prop.xxl !== undefined && bpXxlUp)
|
|
587
667
|
return prop.xxl;
|
|
@@ -597,7 +677,7 @@ function useResponsiveProp(useListener = false) {
|
|
|
597
677
|
return prop.xs;
|
|
598
678
|
// Fallback: return undefined if no breakpoint matches
|
|
599
679
|
return undefined;
|
|
600
|
-
}, [bpSmUp, bpMdUp, bpLgUp, bpXlUp, bpXxlUp, bpXsUp]);
|
|
680
|
+
}, [bpSmUp, bpMdUp, bpLgUp, bpXlUp, bpXxlUp, bpXsUp, hasBreakpoints]);
|
|
601
681
|
return { responsivePropValue };
|
|
602
682
|
}
|
|
603
683
|
|
|
@@ -748,12 +828,12 @@ function DIcon(_a) {
|
|
|
748
828
|
return (jsxRuntime.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)));
|
|
749
829
|
}
|
|
750
830
|
|
|
751
|
-
function DAlert({ color = 'success', icon: iconProp, iconFamilyClass, iconFamilyPrefix, iconMaterialStyle, iconClose: iconCloseProp, iconCloseFamilyClass, iconCloseFamilyPrefix, iconCloseMaterialStyle, showClose, onClose, children, id, className, style, dataAttributes, }) {
|
|
831
|
+
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, }) {
|
|
752
832
|
const { icon: { materialStyle, familyClass, familyPrefix, }, iconMap: { alert, xLg, }, } = useDContext();
|
|
753
833
|
const icon = React.useMemo(() => iconProp || alert[color], [alert, iconProp, color]);
|
|
754
834
|
const iconClose = React.useMemo(() => (iconCloseProp || xLg), [iconCloseProp, xLg]);
|
|
755
835
|
const generateClasses = React.useMemo(() => (Object.assign({ alert: true, [`alert-${color}`]: true, 'fade show': !!showClose }, className && { [className]: true })), [color, showClose, className]);
|
|
756
|
-
return (jsxRuntime.jsxs("div", Object.assign({ className: classNames(generateClasses), style: style, role
|
|
836
|
+
return (jsxRuntime.jsxs("div", Object.assign({ className: classNames(generateClasses), style: style }, role !== 'none' && { role }, { id: id }, dataAttributes, { children: [showIcon && icon && (jsxRuntime.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 })), jsxRuntime.jsx("div", { className: "alert-text", children: children }), showClose && (jsxRuntime.jsx("button", { type: "button", className: "d-close", "aria-label": closeAriaLabel, onClick: onClose, children: jsxRuntime.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 }) }))] })));
|
|
757
837
|
}
|
|
758
838
|
|
|
759
839
|
function DAvatar({ id, size, image, name: nameProp, useNameAsInitials = false, className, style, dataAttributes, }) {
|
|
@@ -819,6 +899,223 @@ function useProvidedRefOrCreate(providedRef) {
|
|
|
819
899
|
return providedRef !== null && providedRef !== void 0 ? providedRef : createdRef;
|
|
820
900
|
}
|
|
821
901
|
|
|
902
|
+
/**
|
|
903
|
+
* Descendants the browser already exempts from a label's click forwarding.
|
|
904
|
+
*
|
|
905
|
+
* Per the HTML spec a label does nothing for events targeted at its interactive
|
|
906
|
+
* content descendants, so these need — and must get — no help from us: calling
|
|
907
|
+
* `preventDefault` on a click targeting a link, or on an image map area, would
|
|
908
|
+
* cancel the navigation it was meant to perform.
|
|
909
|
+
*
|
|
910
|
+
* The list mirrors the spec's interactive content, image maps included, since
|
|
911
|
+
* one of those carrying a `tabindex` would otherwise fall through to the
|
|
912
|
+
* pseudo-interactive branch below and be suppressed by mistake.
|
|
913
|
+
*/
|
|
914
|
+
const NATIVE_INTERACTIVE = 'a[href], area[href], button, input, select, textarea, details, summary, img[usemap], object[usemap], audio[controls], video[controls], embed, iframe';
|
|
915
|
+
/**
|
|
916
|
+
* Descendants that act like controls without being interactive content.
|
|
917
|
+
*
|
|
918
|
+
* A tooltip or modal trigger built from a `span` with a role and a tabindex is
|
|
919
|
+
* the common case. Browsers do not exempt these, so a click on one activates
|
|
920
|
+
* the labelled control as well.
|
|
921
|
+
*/
|
|
922
|
+
const PSEUDO_INTERACTIVE = '[role="button"], [role="link"], [role="checkbox"], [role="switch"], [role="menuitem"], [tabindex]';
|
|
923
|
+
/**
|
|
924
|
+
* Stops a click on a non-native trigger inside a `<label>` from also activating
|
|
925
|
+
* the labelled control.
|
|
926
|
+
*
|
|
927
|
+
* Verified against Chromium, which forwards the click for every one of the
|
|
928
|
+
* `PSEUDO_INTERACTIVE` selectors above while exempting the native ones. Note
|
|
929
|
+
* that jsdom does not reproduce this: its `isInteractiveContent` counts any
|
|
930
|
+
* element carrying a `tabindex` as interactive content, so a jsdom test passes
|
|
931
|
+
* whether or not this guard exists. That is why the guard is unit-tested
|
|
932
|
+
* directly rather than through a rendered component.
|
|
933
|
+
*
|
|
934
|
+
* Native interactive content returns early rather than falling through, since
|
|
935
|
+
* `preventDefault` on those clicks would cancel the link navigation or the
|
|
936
|
+
* image map the label is meant to let through.
|
|
937
|
+
*
|
|
938
|
+
* `DFormLabel` wires this in the capture phase so a trigger that stops
|
|
939
|
+
* propagation cannot skip it.
|
|
940
|
+
*/
|
|
941
|
+
function labelClickGuard(event) {
|
|
942
|
+
const label = event.currentTarget;
|
|
943
|
+
const target = event.target;
|
|
944
|
+
if (!(target === null || target === void 0 ? void 0 : target.closest))
|
|
945
|
+
return;
|
|
946
|
+
/**
|
|
947
|
+
* `closest` walks the whole ancestor chain, so a match may sit above the
|
|
948
|
+
* label — a card with a `role` or a `tabindex` wrapping the control and its
|
|
949
|
+
* label, say. Acting on one of those would suppress the plain label text's
|
|
950
|
+
* own click and leave the control unresponsive, so only matches inside the
|
|
951
|
+
* label count.
|
|
952
|
+
*/
|
|
953
|
+
const inside = (match) => (!!match && match !== label && label.contains(match));
|
|
954
|
+
if (inside(target.closest(NATIVE_INTERACTIVE)))
|
|
955
|
+
return;
|
|
956
|
+
if (inside(target.closest(PSEUDO_INTERACTIVE))) {
|
|
957
|
+
event.preventDefault();
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
/**
|
|
962
|
+
* The `<label>` every form control renders, with the click guard already wired.
|
|
963
|
+
*
|
|
964
|
+
* Internal: it exists so the guard — and the lint exception it needs — lives in
|
|
965
|
+
* one place instead of being repeated across every input component.
|
|
966
|
+
*
|
|
967
|
+
* The guard runs in the capture phase, not on bubble: a tooltip or modal
|
|
968
|
+
* trigger that calls `stopPropagation()` in its own handler — a common pattern —
|
|
969
|
+
* would otherwise keep the guard from ever running, and the browser would go on
|
|
970
|
+
* to activate the labelled control. Confirmed in Chromium, where the same
|
|
971
|
+
* trigger toggles the control with a bubble-phase guard and does not with this
|
|
972
|
+
* one. Capturing changes nothing else: `preventDefault` applies to the default
|
|
973
|
+
* action, which is evaluated after propagation either way.
|
|
974
|
+
*
|
|
975
|
+
* The `jsx-a11y` rules below assume a click handler is being used to make a
|
|
976
|
+
* non-interactive element interactive, which then owes the keyboard an
|
|
977
|
+
* equivalent. This handler does the opposite: it suppresses a pointer-only
|
|
978
|
+
* behaviour, the click a label forwards to its control, and adds no way to
|
|
979
|
+
* activate anything. Keyboard support for a nested trigger is the consumer's:
|
|
980
|
+
* `<a href>` carries its own, while a `span` with a role and a tabindex is
|
|
981
|
+
* focusable but inert until they handle Enter and Space themselves.
|
|
982
|
+
*/
|
|
983
|
+
function DFormLabel({ htmlFor, className, children }) {
|
|
984
|
+
/**
|
|
985
|
+
* React 19 renders a bigint child; React 18 throws on one, and this package
|
|
986
|
+
* supports both (`react: >=18 <20`). The label helpers count bigint as text
|
|
987
|
+
* because that is what it draws as, so it is turned into its string form here
|
|
988
|
+
* — the one place every control's label passes through — rather than pushing
|
|
989
|
+
* a version check onto consumers.
|
|
990
|
+
*
|
|
991
|
+
* Only a bigint at the top level: one buried inside an array or a fragment is
|
|
992
|
+
* the consumer's own React child, and normalising it would mean walking and
|
|
993
|
+
* rebuilding the whole subtree.
|
|
994
|
+
*/
|
|
995
|
+
const content = typeof children === 'bigint' ? String(children) : children;
|
|
996
|
+
return (
|
|
997
|
+
/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,
|
|
998
|
+
jsx-a11y/no-noninteractive-element-interactions */
|
|
999
|
+
jsxRuntime.jsx("label", { htmlFor: htmlFor, className: className, onClickCapture: labelClickGuard, children: content }));
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
/**
|
|
1003
|
+
* Whether a `label` node has something to render.
|
|
1004
|
+
*
|
|
1005
|
+
* The render paths cannot test `label` for truthiness: since the prop widened
|
|
1006
|
+
* to `ReactNode` it also accepts `0`, which React renders as the text "0" and
|
|
1007
|
+
* which `isTextLabel` counts as a text label, yet which a truthiness check
|
|
1008
|
+
* would drop — leaving the control visibly unlabelled.
|
|
1009
|
+
*
|
|
1010
|
+
* Absent (`undefined`, `null`), empty (`''`) and boolean labels render nothing,
|
|
1011
|
+
* so they are the ones that stand for "no label". The empty string matters
|
|
1012
|
+
* because it is the default several components fall back to.
|
|
1013
|
+
*
|
|
1014
|
+
* Arrays are inspected rather than taken at face value: `label={items.map(...)}`
|
|
1015
|
+
* over an empty list yields `[]`, while a list of skipped entries yields
|
|
1016
|
+
* `[false, null]`. Both render nothing, so treating them as content would leave
|
|
1017
|
+
* an empty `<label>` on the page — and make the naming warning fire over a
|
|
1018
|
+
* label that was never there.
|
|
1019
|
+
*
|
|
1020
|
+
* Only arrays, though, even though `ReactNode` admits any iterable. Reading a
|
|
1021
|
+
* one-shot iterator — a generator — would exhaust it here, and the component
|
|
1022
|
+
* goes on to render the same value, so the label would vanish precisely because
|
|
1023
|
+
* we checked for it. An array can be walked as often as needed; anything else
|
|
1024
|
+
* is taken on faith.
|
|
1025
|
+
*
|
|
1026
|
+
* Anything else — an element, a portal, a generator — counts as content: what
|
|
1027
|
+
* it renders is only knowable by rendering it, and an element that draws
|
|
1028
|
+
* nothing visible is the consumer's decision rather than an absent label.
|
|
1029
|
+
*/
|
|
1030
|
+
function hasLabelContent(label) {
|
|
1031
|
+
if (label === undefined || label === null || typeof label === 'boolean') {
|
|
1032
|
+
return false;
|
|
1033
|
+
}
|
|
1034
|
+
if (typeof label === 'string') {
|
|
1035
|
+
return label !== '';
|
|
1036
|
+
}
|
|
1037
|
+
if (React.isValidElement(label)) {
|
|
1038
|
+
return true;
|
|
1039
|
+
}
|
|
1040
|
+
if (Array.isArray(label)) {
|
|
1041
|
+
return label.some(hasLabelContent);
|
|
1042
|
+
}
|
|
1043
|
+
return true;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
/**
|
|
1047
|
+
* Narrows a `label` node to the plain-text case.
|
|
1048
|
+
*
|
|
1049
|
+
* `label` accepts any `ReactNode` so patterns like a terms-and-conditions link
|
|
1050
|
+
* or an info trigger can live next to the field name. Anything that is not text
|
|
1051
|
+
* cannot be forwarded to a string-only attribute (`aria-label`, `placeholder`)
|
|
1052
|
+
* nor laid out by Bootstrap's `form-floating`, so those paths check this first.
|
|
1053
|
+
*
|
|
1054
|
+
* Numbers and bigints count as text: React renders them as their string form
|
|
1055
|
+
* and so does `String()`, which is what the string-only attributes end up
|
|
1056
|
+
* receiving. `bigint` is part of `ReactNode` under these React types, so
|
|
1057
|
+
* leaving it out made a label React draws as text get treated as a rich node.
|
|
1058
|
+
* React 18 cannot render one, which `DFormLabel` handles by rendering its
|
|
1059
|
+
* string form — the classification stays true for every supported React.
|
|
1060
|
+
*/
|
|
1061
|
+
function isTextLabel(label) {
|
|
1062
|
+
return typeof label === 'string'
|
|
1063
|
+
|| typeof label === 'number'
|
|
1064
|
+
|| typeof label === 'bigint';
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
/**
|
|
1068
|
+
* Components already reported, keyed by component and reason, so the same
|
|
1069
|
+
* mistake only warns once per page load however often it re-renders or however
|
|
1070
|
+
* many instances exist.
|
|
1071
|
+
*/
|
|
1072
|
+
const warnedLabels = new Set();
|
|
1073
|
+
function warnOnce(key, message) {
|
|
1074
|
+
if (warnedLabels.has(key))
|
|
1075
|
+
return;
|
|
1076
|
+
warnedLabels.add(key);
|
|
1077
|
+
// eslint-disable-next-line no-console
|
|
1078
|
+
console.warn(`[Dynamic UI] ${message} It never appears in production builds.`);
|
|
1079
|
+
}
|
|
1080
|
+
/**
|
|
1081
|
+
* Warns, only outside production builds, about the two ways a non-text `label`
|
|
1082
|
+
* goes wrong.
|
|
1083
|
+
*
|
|
1084
|
+
* A text label doubles as the control's accessible name. A `ReactNode` one does
|
|
1085
|
+
* not: the name becomes whatever the subtree happens to compute to, which for a
|
|
1086
|
+
* label carrying a link or an icon trigger reads as the wrong thing or as
|
|
1087
|
+
* nothing at all — so an explicit name is needed. Which prop carries it differs
|
|
1088
|
+
* per component, hence `accessibleNameProp`: naming the wrong one would send
|
|
1089
|
+
* the developer to a prop their component rejects.
|
|
1090
|
+
*
|
|
1091
|
+
* `form-floating` is the second case: it animates a single line of text between
|
|
1092
|
+
* the placeholder and the label position, so a node with its own height or its
|
|
1093
|
+
* own interactive children breaks the layout rather than the semantics.
|
|
1094
|
+
*
|
|
1095
|
+
* Labels the render paths drop are skipped through the same `hasLabelContent`
|
|
1096
|
+
* they use, so `label={condition && <span />}` with a false condition warns
|
|
1097
|
+
* about nothing: there is no label on the page to name.
|
|
1098
|
+
*
|
|
1099
|
+
* The `process.env.NODE_ENV` guard belongs at the call site — that is what
|
|
1100
|
+
* bundlers constant-fold, dropping this module from a consumer's production
|
|
1101
|
+
* bundle.
|
|
1102
|
+
*/
|
|
1103
|
+
function warnLabelUsage({ component, label, hasAccessibleName, accessibleNameProp, floatingLabel = false, }) {
|
|
1104
|
+
if (!hasLabelContent(label) || isTextLabel(label))
|
|
1105
|
+
return;
|
|
1106
|
+
if (!hasAccessibleName) {
|
|
1107
|
+
warnOnce(`${component}:name`, `${component}: a non-text "label" does not give the control a reliable accessible name. `
|
|
1108
|
+
+ `Pass ${accessibleNameProp} with the plain-text name of the field, since the label `
|
|
1109
|
+
+ 'subtree may include links, icons or markup that read as the wrong name or as none at all.');
|
|
1110
|
+
}
|
|
1111
|
+
if (floatingLabel) {
|
|
1112
|
+
warnOnce(`${component}:floating`, `${component}: "floatingLabel" expects a text "label". `
|
|
1113
|
+
+ 'The floating layout animates a single line of text, so a node with its own height '
|
|
1114
|
+
+ 'or its own interactive children overflows the control. '
|
|
1115
|
+
+ 'Use the default label layout for rich labels.');
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
|
|
822
1119
|
function DInput(_a, ref) {
|
|
823
1120
|
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 = tslib.__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"]);
|
|
824
1121
|
const inputRef = useProvidedRefOrCreate(ref);
|
|
@@ -858,7 +1155,7 @@ function DInput(_a, ref) {
|
|
|
858
1155
|
const inputComponent = React.useMemo(() => (jsxRuntime.jsx("input", Object.assign({ ref: inputRef, id: id, className: classNames('form-control', {
|
|
859
1156
|
'is-invalid': invalid,
|
|
860
1157
|
'is-valid': valid,
|
|
861
|
-
}), disabled: disabled || loading, readOnly: readonly, value: value, onChange: handleOnChange }, (floatingLabel || placeholder) && { placeholder: floatingLabel ? '' : placeholder }, ariaDescribedby && { 'aria-describedby': ariaDescribedby }, inputProps))), [
|
|
1158
|
+
}), disabled: disabled || loading, readOnly: readonly, value: value, onChange: handleOnChange }, (floatingLabel || placeholder) && { placeholder: floatingLabel ? '' : placeholder }, ariaDescribedby && { 'aria-describedby': ariaDescribedby }, invalid && { 'aria-invalid': true }, inputProps))), [
|
|
862
1159
|
ariaDescribedby,
|
|
863
1160
|
disabled,
|
|
864
1161
|
handleOnChange,
|
|
@@ -873,7 +1170,7 @@ function DInput(_a, ref) {
|
|
|
873
1170
|
value,
|
|
874
1171
|
readonly,
|
|
875
1172
|
]);
|
|
876
|
-
const labelComponent = React.useMemo(() => (jsxRuntime.jsx(
|
|
1173
|
+
const labelComponent = React.useMemo(() => (jsxRuntime.jsx(DFormLabel, { htmlFor: id, children: label })), [
|
|
877
1174
|
id,
|
|
878
1175
|
label,
|
|
879
1176
|
]);
|
|
@@ -883,7 +1180,16 @@ function DInput(_a, ref) {
|
|
|
883
1180
|
}
|
|
884
1181
|
return inputComponent;
|
|
885
1182
|
}, [floatingLabel, inputComponent, labelComponent]);
|
|
886
|
-
|
|
1183
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
1184
|
+
warnLabelUsage({
|
|
1185
|
+
component: 'DInput',
|
|
1186
|
+
label,
|
|
1187
|
+
hasAccessibleName: !!inputProps['aria-label'] || !!inputProps['aria-labelledby'],
|
|
1188
|
+
accessibleNameProp: 'aria-label',
|
|
1189
|
+
floatingLabel,
|
|
1190
|
+
});
|
|
1191
|
+
}
|
|
1192
|
+
return (jsxRuntime.jsxs("div", Object.assign({ className: className, style: style }, dataAttributes, { children: [hasLabelContent(label) && !floatingLabel && labelComponent, jsxRuntime.jsxs("div", { className: classNames({
|
|
887
1193
|
[`input-group-${size}`]: !!size,
|
|
888
1194
|
'input-group': true,
|
|
889
1195
|
'has-validation': invalid || valid,
|
|
@@ -1409,7 +1715,7 @@ function warnMissingAccessibleName(icon, kind) {
|
|
|
1409
1715
|
}
|
|
1410
1716
|
|
|
1411
1717
|
function DButtonIcon(_a) {
|
|
1412
|
-
var { id, icon, size, className, variant, state, loadingAriaLabel, iconMaterialStyle, disabled = false, color = 'primary', loading = false, href, target, rel, stopPropagationEnabled = true, style, iconFamilyClass, iconFamilyPrefix, dataAttributes, onClick, 'aria-label': ariaLabelProp } = _a, rest = tslib.__rest(_a, ["id", "icon", "size", "className", "variant", "state", "loadingAriaLabel", "iconMaterialStyle", "disabled", "color", "loading", "href", "target", "rel", "stopPropagationEnabled", "style", "iconFamilyClass", "iconFamilyPrefix", "dataAttributes", "onClick", 'aria-label']);
|
|
1718
|
+
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 = tslib.__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']);
|
|
1413
1719
|
const { icon: { familyClass, familyPrefix, materialStyle, }, } = useDContext();
|
|
1414
1720
|
const generateClasses = React.useMemo(() => {
|
|
1415
1721
|
const variantClass = !variant || variant === 'solid'
|
|
@@ -1417,6 +1723,9 @@ function DButtonIcon(_a) {
|
|
|
1417
1723
|
: `btn-${variant}-${color}`;
|
|
1418
1724
|
return Object.assign(Object.assign(Object.assign({ 'btn d-button-icon': true, [variantClass]: true }, size && { [`btn-${size}`]: true }), (state && state !== 'disabled') && { [state]: true }), { loading });
|
|
1419
1725
|
}, [variant, color, size, state, loading]);
|
|
1726
|
+
// A responsive iconSize has to follow viewport changes, so DIcon only
|
|
1727
|
+
// listens to breakpoints when it gets an object; a plain string needs none.
|
|
1728
|
+
const useIconSizeListener = typeof iconSize === 'object';
|
|
1420
1729
|
const isDisabled = React.useMemo(() => (state === 'disabled' || loading || disabled), [state, loading, disabled]);
|
|
1421
1730
|
const clickHandler = React.useCallback((event) => {
|
|
1422
1731
|
if (stopPropagationEnabled) {
|
|
@@ -1443,11 +1752,11 @@ function DButtonIcon(_a) {
|
|
|
1443
1752
|
if (href) {
|
|
1444
1753
|
return (jsxRuntime.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
|
|
1445
1754
|
? (jsxRuntime.jsx("span", { className: "spinner-border spinner-border-sm", role: "status", "aria-hidden": "true", children: jsxRuntime.jsx("span", { className: "visually-hidden", children: "Loading..." }) }))
|
|
1446
|
-
: (jsxRuntime.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 })) })));
|
|
1755
|
+
: (jsxRuntime.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 })) })));
|
|
1447
1756
|
}
|
|
1448
1757
|
return (jsxRuntime.jsx("button", Object.assign({ className: classNames(generateClasses, className), style: style, disabled: state === 'disabled' || loading, onClick: clickHandler, "aria-label": ariaLabel }, dataAttributes, rest, { children: loading
|
|
1449
1758
|
? (jsxRuntime.jsx("span", { className: "spinner-border spinner-border-sm", role: "status", "aria-hidden": "true", children: jsxRuntime.jsx("span", { className: "visually-hidden", children: "Loading..." }) }))
|
|
1450
|
-
: (jsxRuntime.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 })) })));
|
|
1759
|
+
: (jsxRuntime.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 })) })));
|
|
1451
1760
|
}
|
|
1452
1761
|
|
|
1453
1762
|
function DCardHeader({ className, style, children, }) {
|
|
@@ -1643,24 +1952,102 @@ function DSelectPlaceholder(_a) {
|
|
|
1643
1952
|
return (jsxRuntime.jsx(Select.components.Placeholder, Object.assign({ innerProps: innerProps, selectProps: selectProps }, props, { children: children })));
|
|
1644
1953
|
}
|
|
1645
1954
|
|
|
1955
|
+
/**
|
|
1956
|
+
* Name of the elements an `aria-labelledby` points at, joined as the accessible
|
|
1957
|
+
* name computation does. Read when the message is built rather than when the
|
|
1958
|
+
* select renders, since the referenced elements may render after it.
|
|
1959
|
+
*
|
|
1960
|
+
* Each element contributes its own `aria-label` when it has one, and its text
|
|
1961
|
+
* otherwise. That covers the targets a select is labelled by in practice — a
|
|
1962
|
+
* heading, a span, a labelled region — without shipping the full accessible
|
|
1963
|
+
* name algorithm; names derived from descendants' `aria-label`, `alt` or
|
|
1964
|
+
* hidden content are not resolved, so pass `ariaLiveMessages` for those.
|
|
1965
|
+
*/
|
|
1966
|
+
function resolveLabelledBy(labelledBy) {
|
|
1967
|
+
if (typeof document === 'undefined')
|
|
1968
|
+
return '';
|
|
1969
|
+
return labelledBy
|
|
1970
|
+
.split(/\s+/)
|
|
1971
|
+
.map((ref) => {
|
|
1972
|
+
var _a, _b;
|
|
1973
|
+
const element = document.getElementById(ref);
|
|
1974
|
+
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());
|
|
1975
|
+
})
|
|
1976
|
+
.filter(Boolean)
|
|
1977
|
+
.join(' ');
|
|
1978
|
+
}
|
|
1979
|
+
/**
|
|
1980
|
+
* Builds react-select's `guidance` live message around the control's name.
|
|
1981
|
+
*
|
|
1982
|
+
* react-select announces "<aria-label> is focused" on the first focus of the
|
|
1983
|
+
* input, falling back to "Select" when there is no `aria-label`. Neither a text
|
|
1984
|
+
* `label` nor `aria-labelledby` reaches that message, so the announcement would
|
|
1985
|
+
* disagree with the name assistive technology exposes.
|
|
1986
|
+
*
|
|
1987
|
+
* The name follows the accessible name precedence: `aria-labelledby`, then
|
|
1988
|
+
* `aria-label`, then the `<label>`. react-select does not export its default
|
|
1989
|
+
* messages, so this mirrors them for every context and only swaps the name.
|
|
1990
|
+
*/
|
|
1991
|
+
function createAriaGuidance({ labelledBy, label }) {
|
|
1992
|
+
return ({ 'aria-label': ariaLabel, context, isSearchable, isMulti, tabSelectsValue, isInitialFocus, }) => {
|
|
1993
|
+
switch (context) {
|
|
1994
|
+
case 'menu':
|
|
1995
|
+
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' : ''}.`;
|
|
1996
|
+
case 'input': {
|
|
1997
|
+
if (!isInitialFocus)
|
|
1998
|
+
return '';
|
|
1999
|
+
const name = (labelledBy && resolveLabelledBy(labelledBy)) || ariaLabel || label || 'Select';
|
|
2000
|
+
return `${name} is focused ${isSearchable ? ',type to refine list' : ''}, press Down to open the menu, ${isMulti ? ' press left to focus selected values' : ''}`;
|
|
2001
|
+
}
|
|
2002
|
+
case 'value':
|
|
2003
|
+
return 'Use left and right to toggle between focused values, press Backspace to remove the currently focused value';
|
|
2004
|
+
default:
|
|
2005
|
+
return '';
|
|
2006
|
+
}
|
|
2007
|
+
};
|
|
2008
|
+
}
|
|
2009
|
+
|
|
1646
2010
|
function DSelect$1(_a) {
|
|
1647
|
-
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
|
|
2011
|
+
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 = tslib.__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"]);
|
|
1648
2012
|
const innerId = React.useId();
|
|
1649
2013
|
const id = React.useMemo(() => idProp || innerId, [idProp, innerId]);
|
|
2014
|
+
// The `<label>` must point at the element react-select renders the input
|
|
2015
|
+
// with, so an explicit `inputId` wins over `id` for both.
|
|
2016
|
+
const inputId = inputIdProp || id;
|
|
2017
|
+
// A text label or `aria-labelledby` names the control without going through
|
|
2018
|
+
// `aria-label`, which is all react-select's focus announcement reads, so it
|
|
2019
|
+
// is handed those names explicitly. Messages passed by the consumer still
|
|
2020
|
+
// take precedence.
|
|
2021
|
+
const textLabel = hasLabelContent(label) && isTextLabel(label) ? String(label) : undefined;
|
|
2022
|
+
const labelledBy = props['aria-labelledby'];
|
|
2023
|
+
const liveMessages = React.useMemo(() => (textLabel || labelledBy
|
|
2024
|
+
? Object.assign({ guidance: createAriaGuidance({ labelledBy, label: textLabel }) }, ariaLiveMessages) : ariaLiveMessages), [textLabel, labelledBy, ariaLiveMessages]);
|
|
1650
2025
|
const handleOnIconStartClick = React.useCallback(() => {
|
|
1651
2026
|
onIconStartClick === null || onIconStartClick === void 0 ? void 0 : onIconStartClick(defaultValue);
|
|
1652
2027
|
}, [onIconStartClick, defaultValue]);
|
|
1653
2028
|
const handleOnIconEndClick = React.useCallback(() => {
|
|
1654
2029
|
onIconEndClick === null || onIconEndClick === void 0 ? void 0 : onIconEndClick(defaultValue);
|
|
1655
2030
|
}, [onIconEndClick, defaultValue]);
|
|
2031
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
2032
|
+
warnLabelUsage({
|
|
2033
|
+
component: 'DSelect',
|
|
2034
|
+
label,
|
|
2035
|
+
// `{...props}` is spread after `ariaLabel`, so a native `aria-label` in it
|
|
2036
|
+
// is the one that reaches the input, even when it is `undefined`.
|
|
2037
|
+
hasAccessibleName: !!('aria-label' in props ? props['aria-label'] : ariaLabel)
|
|
2038
|
+
|| !!labelledBy,
|
|
2039
|
+
accessibleNameProp: 'ariaLabel',
|
|
2040
|
+
floatingLabel,
|
|
2041
|
+
});
|
|
2042
|
+
}
|
|
1656
2043
|
return (jsxRuntime.jsxs("div", Object.assign({ className: classNames('d-select', className, {
|
|
1657
2044
|
'd-select-floating': floatingLabel,
|
|
1658
2045
|
disabled: disabled || loading,
|
|
1659
|
-
}), style: style }, dataAttributes, { children: [label && (jsxRuntime.jsx(
|
|
2046
|
+
}), style: style }, dataAttributes, { children: [hasLabelContent(label) && (jsxRuntime.jsx(DFormLabel, { htmlFor: inputId, children: label })), jsxRuntime.jsxs("div", { className: classNames({
|
|
1660
2047
|
'input-group': true,
|
|
1661
2048
|
'has-validation': invalid,
|
|
1662
2049
|
disabled: disabled || loading,
|
|
1663
|
-
}), children: [iconStart && (jsxRuntime.jsx("button", { type: "button", className: "input-group-text", id: `${id}Start`, onClick: handleOnIconStartClick, disabled: disabled || loading, "aria-label": iconStartAriaLabel, tabIndex: iconStartTabIndex, children: jsxRuntime.jsx(DIcon, { icon: iconStart, familyClass: iconStartFamilyClass, familyPrefix: iconStartFamilyPrefix }) })), jsxRuntime.jsx(Select, Object.assign({ id: `${id}Container`, inputId:
|
|
2050
|
+
}), children: [iconStart && (jsxRuntime.jsx("button", { type: "button", className: "input-group-text", id: `${id}Start`, onClick: handleOnIconStartClick, disabled: disabled || loading, "aria-label": iconStartAriaLabel, tabIndex: iconStartTabIndex, children: jsxRuntime.jsx(DIcon, { icon: iconStart, familyClass: iconStartFamilyClass, familyPrefix: iconStartFamilyPrefix }) })), jsxRuntime.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: {
|
|
1664
2051
|
control: (base) => (Object.assign(Object.assign({}, base), { minHeight: 'unset' })),
|
|
1665
2052
|
container: (base) => (Object.assign(Object.assign({}, base), { flex: 1 })),
|
|
1666
2053
|
menu: (base) => (Object.assign(Object.assign({}, base), { width: menuWithMaxContent ? 'max-context' : '100%', zIndex: 1000 })),
|
|
@@ -1855,10 +2242,40 @@ function formatValue(value, currencyOptions) {
|
|
|
1855
2242
|
}
|
|
1856
2243
|
return currency(value, Object.assign(Object.assign({}, currencyOptions), { symbol: '' })).format();
|
|
1857
2244
|
}
|
|
1858
|
-
|
|
2245
|
+
/**
|
|
2246
|
+
* State and handlers for a currency input: a formatted string while the field
|
|
2247
|
+
* is idle and the raw number while it is being edited.
|
|
2248
|
+
*
|
|
2249
|
+
* `minValue`/`maxValue` bound the value. With `clamp` (default `true`) an
|
|
2250
|
+
* out-of-range value is brought into range whenever it is not being typed:
|
|
2251
|
+
* on mount, when `value` or the bounds change, and on blur. Every time the
|
|
2252
|
+
* clamp changes the value, `onChange` receives the clamped number, so the
|
|
2253
|
+
* input and the consumer's state never disagree. While the field is focused
|
|
2254
|
+
* the typed number is reported as is, so a partial entry is not rewritten.
|
|
2255
|
+
*
|
|
2256
|
+
* With `clamp: false` the value is never changed: the bounds only feed
|
|
2257
|
+
* `isOverMax` / `isUnderMin`, so the consumer can show its own message (e.g.
|
|
2258
|
+
* "You exceeded the limit") and keep the entered amount.
|
|
2259
|
+
*
|
|
2260
|
+
* Controlled usage follows the same contract as a native controlled input:
|
|
2261
|
+
* reflect `onChange` into `value` in the same event (`setState` in the
|
|
2262
|
+
* handler). The hook keeps showing `value` whenever it isn't being edited, so
|
|
2263
|
+
* a consumer can reject a change by keeping `value` as it was. Deferring the
|
|
2264
|
+
* update (a timer, awaiting a request) makes the hook see a stale `value` in
|
|
2265
|
+
* between: the field can flash the previous amount and a clamp can be
|
|
2266
|
+
* reported again when the deferred value arrives.
|
|
2267
|
+
*/
|
|
2268
|
+
function useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref, minValue, maxValue, clamp = true) {
|
|
1859
2269
|
const inputRef = useProvidedRefOrCreate(ref);
|
|
2270
|
+
const onChangeRef = React.useRef(onChange);
|
|
2271
|
+
onChangeRef.current = onChange;
|
|
2272
|
+
// Last clamp reported through `onChange`, as the value it came from and the
|
|
2273
|
+
// value it produced. StrictMode replays effects, and a consumer may reflect
|
|
2274
|
+
// `onChange` asynchronously, both before `value` catches up: the same clamp
|
|
2275
|
+
// must not be reported twice.
|
|
2276
|
+
const lastReportedClampRef = React.useRef(null);
|
|
1860
2277
|
const clampValue = React.useCallback((newValue) => {
|
|
1861
|
-
if (newValue === undefined) {
|
|
2278
|
+
if (newValue === undefined || !clamp) {
|
|
1862
2279
|
return newValue;
|
|
1863
2280
|
}
|
|
1864
2281
|
let clampedValue = newValue;
|
|
@@ -1869,10 +2286,9 @@ function useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref
|
|
|
1869
2286
|
clampedValue = Math.min(clampedValue, maxValue);
|
|
1870
2287
|
}
|
|
1871
2288
|
return clampedValue;
|
|
1872
|
-
}, [minValue, maxValue]);
|
|
2289
|
+
}, [minValue, maxValue, clamp]);
|
|
1873
2290
|
const [innerType, setInnerType] = React.useState('text');
|
|
1874
|
-
const [innerNumber, setInnerNumber] = React.useState(clampValue(value));
|
|
1875
|
-
const [innerString, setInnerString] = React.useState(formatValue(clampValue(value), currencyOptions));
|
|
2291
|
+
const [innerNumber, setInnerNumber] = React.useState(() => clampValue(value));
|
|
1876
2292
|
const handleOnFocus = React.useCallback((event) => {
|
|
1877
2293
|
event.stopPropagation();
|
|
1878
2294
|
setInnerType('number');
|
|
@@ -1884,35 +2300,60 @@ function useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref
|
|
|
1884
2300
|
const clampedNumber = clampValue(innerNumber);
|
|
1885
2301
|
if (clampedNumber !== innerNumber) {
|
|
1886
2302
|
setInnerNumber(clampedNumber);
|
|
1887
|
-
|
|
2303
|
+
// Recorded before notifying, so the sync effect that runs when editing
|
|
2304
|
+
// ends doesn't report the same clamp again while `value` still holds the
|
|
2305
|
+
// typed number.
|
|
2306
|
+
lastReportedClampRef.current = { from: value, to: clampedNumber };
|
|
1888
2307
|
onChange === null || onChange === void 0 ? void 0 : onChange(clampedNumber);
|
|
1889
2308
|
}
|
|
1890
2309
|
onBlur === null || onBlur === void 0 ? void 0 : onBlur(event);
|
|
1891
|
-
}, [onBlur, innerNumber, clampValue,
|
|
2310
|
+
}, [onBlur, innerNumber, clampValue, onChange, value]);
|
|
1892
2311
|
const handleOnChange = React.useCallback((newValue) => {
|
|
1893
2312
|
const newNumber = (newValue === undefined || newValue === '') ? undefined : Number(newValue);
|
|
1894
2313
|
if (newNumber !== innerNumber) {
|
|
1895
2314
|
setInnerNumber(newNumber);
|
|
1896
|
-
setInnerString(formatValue(newNumber, currencyOptions));
|
|
1897
2315
|
onChange === null || onChange === void 0 ? void 0 : onChange(newNumber);
|
|
1898
2316
|
}
|
|
1899
|
-
}, [
|
|
1900
|
-
const
|
|
2317
|
+
}, [onChange, innerNumber]);
|
|
2318
|
+
const isEditing = innerType === 'number';
|
|
2319
|
+
// Keep the inner value in sync with `value` and the bounds. While the user
|
|
2320
|
+
// is typing, the consumer echoes the typed number back through `value`, so
|
|
2321
|
+
// it is taken as is; otherwise it is clamped, and a clamp that changes it is
|
|
2322
|
+
// reported through `onChange`.
|
|
1901
2323
|
React.useEffect(() => {
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
2324
|
+
var _a;
|
|
2325
|
+
const nextNumber = isEditing ? value : clampValue(value);
|
|
2326
|
+
if (nextNumber !== innerNumber) {
|
|
2327
|
+
setInnerNumber(nextNumber);
|
|
2328
|
+
}
|
|
2329
|
+
if (nextNumber !== value) {
|
|
2330
|
+
const last = lastReportedClampRef.current;
|
|
2331
|
+
if (!last || last.from !== value || last.to !== nextNumber) {
|
|
2332
|
+
lastReportedClampRef.current = { from: value, to: nextNumber };
|
|
2333
|
+
(_a = onChangeRef.current) === null || _a === void 0 ? void 0 : _a.call(onChangeRef, nextNumber);
|
|
2334
|
+
}
|
|
1905
2335
|
}
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
setInnerString(formatValue(value, currencyOptions));
|
|
2336
|
+
else {
|
|
2337
|
+
lastReportedClampRef.current = null;
|
|
1909
2338
|
}
|
|
1910
|
-
|
|
1911
|
-
|
|
2339
|
+
// `innerNumber` is read, not tracked: the effect reacts to the consumer's
|
|
2340
|
+
// value and to the bounds, not to its own updates.
|
|
2341
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2342
|
+
}, [value, clampValue, isEditing]);
|
|
2343
|
+
// Derived, not stored: the formatted text always matches the current number
|
|
2344
|
+
// and options, even when both change in the same render. It depends on the
|
|
2345
|
+
// options object itself, so a new custom `format` function is honored too;
|
|
2346
|
+
// formatting is cheap and doesn't feed the synchronization effect.
|
|
2347
|
+
const innerString = React.useMemo(() => formatValue(innerNumber, currencyOptions), [innerNumber, currencyOptions]);
|
|
2348
|
+
const innerValue = React.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]);
|
|
2349
|
+
const isOverMax = maxValue !== undefined && innerNumber !== undefined && innerNumber > maxValue;
|
|
2350
|
+
const isUnderMin = minValue !== undefined && innerNumber !== undefined && innerNumber < minValue;
|
|
1912
2351
|
return {
|
|
1913
2352
|
inputRef,
|
|
1914
2353
|
innerValue,
|
|
1915
2354
|
innerType,
|
|
2355
|
+
isOverMax,
|
|
2356
|
+
isUnderMin,
|
|
1916
2357
|
handleOnFocus,
|
|
1917
2358
|
handleOnChange,
|
|
1918
2359
|
handleOnBlur,
|
|
@@ -2163,38 +2604,142 @@ function useOtp({ action, otpSize = 6, seconds = 15, }) {
|
|
|
2163
2604
|
};
|
|
2164
2605
|
}
|
|
2165
2606
|
|
|
2607
|
+
/**
|
|
2608
|
+
* Backs a value that the consumer may or may not drive, so a control works in
|
|
2609
|
+
* both modes without a copy of the state drifting away from the props.
|
|
2610
|
+
*
|
|
2611
|
+
* A control counts as controlled only when the consumer passes the value *and*
|
|
2612
|
+
* an `onChange` to push it back. The value on its own has always meant "start
|
|
2613
|
+
* here" in this library — `<DInputCheck type="radio" checked />` inside a radio
|
|
2614
|
+
* group, for one — so treating it as controlled would silently freeze those
|
|
2615
|
+
* controls, and React emits no warning to say why: the components always attach
|
|
2616
|
+
* their own `onChange` handler.
|
|
2617
|
+
*
|
|
2618
|
+
* @param value the value coming from props, or `undefined` when not passed.
|
|
2619
|
+
* @param isControlled whether the consumer can drive the value back.
|
|
2620
|
+
* @param initialValue what to start from when no value is passed.
|
|
2621
|
+
* @returns the value to render, and a setter to call on every user change.
|
|
2622
|
+
*/
|
|
2623
|
+
function useControlledState(value, isControlled, initialValue) {
|
|
2624
|
+
const [internalValue, setInternalValue] = React.useState(value === undefined ? initialValue : value);
|
|
2625
|
+
// The internal copy follows `value` in both modes.
|
|
2626
|
+
//
|
|
2627
|
+
// Uncontrolled, `value` without an `onChange` still means "start here", and a
|
|
2628
|
+
// later flip of it from outside must still land — but between flips the
|
|
2629
|
+
// control has to keep moving on its own, which is why this depends on `value`
|
|
2630
|
+
// alone and not on `internalValue`.
|
|
2631
|
+
//
|
|
2632
|
+
// Controlled, the copy is not what gets rendered, but it is what the control
|
|
2633
|
+
// falls back to if the consumer later stops passing a value — `value={locked
|
|
2634
|
+
// ? undefined : x}`, say. Keeping it current means that hand-off carries on
|
|
2635
|
+
// from where the control is instead of snapping back to the mount-time seed.
|
|
2636
|
+
React.useEffect(() => {
|
|
2637
|
+
if (value === undefined) {
|
|
2638
|
+
return;
|
|
2639
|
+
}
|
|
2640
|
+
setInternalValue(value);
|
|
2641
|
+
}, [value]);
|
|
2642
|
+
const setValue = React.useCallback((next) => {
|
|
2643
|
+
if (isControlled) {
|
|
2644
|
+
return;
|
|
2645
|
+
}
|
|
2646
|
+
setInternalValue(next);
|
|
2647
|
+
}, [isControlled]);
|
|
2648
|
+
return [
|
|
2649
|
+
isControlled && value !== undefined ? value : internalValue,
|
|
2650
|
+
setValue,
|
|
2651
|
+
];
|
|
2652
|
+
}
|
|
2653
|
+
|
|
2166
2654
|
function DInputCounter(_a, ref) {
|
|
2167
|
-
var { minValue, maxValue, value
|
|
2655
|
+
var { minValue, maxValue, value, defaultValue, invalid, iconStart: iconStartProp, iconEnd: iconEndProp, iconStartAriaLabel = 'decrease action', iconEndAriaLabel = 'increase action', style, onChange } = _a, props = tslib.__rest(_a, ["minValue", "maxValue", "value", "defaultValue", "invalid", "iconStart", "iconEnd", "iconStartAriaLabel", "iconEndAriaLabel", "style", "onChange"]);
|
|
2168
2656
|
const { handleOnWheel, } = useDisableInputWheel(ref);
|
|
2169
2657
|
const inputRef = useProvidedRefOrCreate(ref);
|
|
2170
|
-
|
|
2171
|
-
const
|
|
2658
|
+
// See `useControlledState` for why `onChange` takes part in this decision.
|
|
2659
|
+
const isControlled = value !== undefined && onChange !== undefined;
|
|
2660
|
+
const [currentValue, setCurrentValue] = useControlledState(value, isControlled, defaultValue !== null && defaultValue !== void 0 ? defaultValue : minValue);
|
|
2661
|
+
// The step handlers move *from* the current value rather than replacing it,
|
|
2662
|
+
// so they need the latest one even when several clicks land in the same React
|
|
2663
|
+
// batch and no render has happened in between — the functional update this
|
|
2664
|
+
// replaced handled that on its own. Written on every render so it follows the
|
|
2665
|
+
// props, and by `commitValue` so a second step in the same batch starts from
|
|
2666
|
+
// where the first left off instead of collapsing into it.
|
|
2667
|
+
const currentValueRef = React.useRef(currentValue);
|
|
2668
|
+
currentValueRef.current = currentValue;
|
|
2669
|
+
// `onChange` used to be called from an effect watching the internal value,
|
|
2670
|
+
// which made controlling the counter impossible: a controlled counter never
|
|
2671
|
+
// moves that value, so the effect never fired. Reporting from the handlers
|
|
2672
|
+
// instead works in both modes.
|
|
2673
|
+
const commitValue = React.useCallback((newValue) => {
|
|
2674
|
+
// Clicking at a bound, or typing the value that is already there, is not a
|
|
2675
|
+
// change. The effect this replaced watched the value itself and so stayed
|
|
2676
|
+
// quiet too; without this a batch of clicks against `minValue` would report
|
|
2677
|
+
// the same number once per click.
|
|
2678
|
+
if (newValue === currentValueRef.current) {
|
|
2679
|
+
return;
|
|
2680
|
+
}
|
|
2681
|
+
// Only the uncontrolled path moves the ref ahead of a render. There the
|
|
2682
|
+
// component owns the value, so a second step in the same batch has to build
|
|
2683
|
+
// on the first. Controlled, the prop is the truth and every step proposes
|
|
2684
|
+
// from it: a parent that rejects without re-rendering — `onChange={() => {}}`,
|
|
2685
|
+
// or a setter returning the previous state — never re-runs the assignment
|
|
2686
|
+
// above, so a speculative ref would climb away from the value on screen and
|
|
2687
|
+
// each further click would report a number further from it.
|
|
2688
|
+
if (!isControlled) {
|
|
2689
|
+
currentValueRef.current = newValue;
|
|
2690
|
+
}
|
|
2691
|
+
setCurrentValue(newValue);
|
|
2692
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(newValue);
|
|
2693
|
+
}, [isControlled, setCurrentValue, onChange]);
|
|
2694
|
+
// Consumers have always been handed the starting value through `onChange` on
|
|
2695
|
+
// mount, and some seed their state with it, so that one call stays. The ref
|
|
2696
|
+
// keeps it to the first run: the old effect also re-fired on every render
|
|
2697
|
+
// that passed a fresh inline `onChange`. Nothing is marked as reported until
|
|
2698
|
+
// there is a handler to report to, so a counter mounted without `onChange`
|
|
2699
|
+
// still hands over its starting value once one is attached.
|
|
2700
|
+
const hasReportedInitialValue = React.useRef(false);
|
|
2172
2701
|
React.useEffect(() => {
|
|
2173
|
-
|
|
2174
|
-
|
|
2702
|
+
if (hasReportedInitialValue.current || !onChange) {
|
|
2703
|
+
return;
|
|
2704
|
+
}
|
|
2705
|
+
hasReportedInitialValue.current = true;
|
|
2706
|
+
onChange(currentValue);
|
|
2707
|
+
}, [onChange, currentValue]);
|
|
2708
|
+
// `value` used to default to `minValue`, so a counter left to its own devices
|
|
2709
|
+
// re-seeded — and reported — whenever `minValue` moved. Consumers use it as a
|
|
2710
|
+
// live bound, so that stays; an explicit `value` or `defaultValue` opts out,
|
|
2711
|
+
// exactly as passing `value` did before. The ref keeps this to real changes,
|
|
2712
|
+
// so mounting still reports once and a click is never undone.
|
|
2713
|
+
const hasExplicitStartingValue = value !== undefined || defaultValue !== undefined;
|
|
2714
|
+
const previousMinValue = React.useRef(minValue);
|
|
2175
2715
|
React.useEffect(() => {
|
|
2176
|
-
|
|
2177
|
-
|
|
2716
|
+
if (previousMinValue.current === minValue) {
|
|
2717
|
+
return;
|
|
2718
|
+
}
|
|
2719
|
+
previousMinValue.current = minValue;
|
|
2720
|
+
if (hasExplicitStartingValue) {
|
|
2721
|
+
return;
|
|
2722
|
+
}
|
|
2723
|
+
commitValue(minValue);
|
|
2724
|
+
}, [hasExplicitStartingValue, minValue, commitValue]);
|
|
2178
2725
|
const handleOnChange = React.useCallback((newValue) => {
|
|
2179
|
-
|
|
2180
|
-
}, []);
|
|
2726
|
+
commitValue(Number(newValue || '0'));
|
|
2727
|
+
}, [commitValue]);
|
|
2181
2728
|
const handleOnIconStartClick = React.useCallback(() => {
|
|
2182
|
-
|
|
2183
|
-
}, [minValue]);
|
|
2729
|
+
commitValue(Math.max(currentValueRef.current - 1, minValue));
|
|
2730
|
+
}, [commitValue, minValue]);
|
|
2184
2731
|
const handleOnIconEndClick = React.useCallback(() => {
|
|
2185
|
-
|
|
2186
|
-
}, [maxValue]);
|
|
2732
|
+
commitValue(Math.min(currentValueRef.current + 1, maxValue));
|
|
2733
|
+
}, [commitValue, maxValue]);
|
|
2187
2734
|
const generateStyleVariables = React.useMemo(() => (Object.assign(Object.assign({}, style), { [`--${PREFIX_BS}form-control-component-text-align`]: 'center' })), [style]);
|
|
2188
|
-
const valueString = React.useMemo(() => (
|
|
2189
|
-
React.
|
|
2190
|
-
setInternalIsInvalid(!(internalValue >= minValue && internalValue <= maxValue));
|
|
2191
|
-
}, [internalValue, minValue, maxValue]);
|
|
2735
|
+
const valueString = React.useMemo(() => (currentValue.toString()), [currentValue]);
|
|
2736
|
+
const internalIsInvalid = React.useMemo(() => (!(currentValue >= minValue && currentValue <= maxValue)), [currentValue, minValue, maxValue]);
|
|
2192
2737
|
const { iconMap: { input } } = useDContext();
|
|
2193
2738
|
const iconEnd = React.useMemo(() => iconEndProp || input.increase, [iconEndProp, input.increase]);
|
|
2194
2739
|
const iconStart = React.useMemo(() => iconStartProp || input.decrease, [iconStartProp, input.decrease]);
|
|
2195
|
-
return (jsxRuntime.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 },
|
|
2740
|
+
return (jsxRuntime.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 && {
|
|
2196
2741
|
iconStartDisabled: true,
|
|
2197
|
-
},
|
|
2742
|
+
}, currentValue === maxValue && {
|
|
2198
2743
|
iconEndDisabled: true,
|
|
2199
2744
|
}, props)));
|
|
2200
2745
|
}
|
|
@@ -2202,11 +2747,16 @@ const ForwardedDInputCounter = React.forwardRef(DInputCounter);
|
|
|
2202
2747
|
ForwardedDInputCounter.displayName = 'DInputCounter';
|
|
2203
2748
|
|
|
2204
2749
|
function DInputCurrency(_a, ref) {
|
|
2205
|
-
var { value, minValue, maxValue, currencyCode, onFocus, onBlur, onChange } = _a, props = tslib.__rest(_a, ["value", "minValue", "maxValue", "currencyCode", "onFocus", "onBlur", "onChange"]);
|
|
2750
|
+
var { value, minValue, maxValue, clamp = true, currencyCode, onFocus, onBlur, onChange, invalid: invalidProp, valid: validProp } = _a, props = tslib.__rest(_a, ["value", "minValue", "maxValue", "clamp", "currencyCode", "onFocus", "onBlur", "onChange", "invalid", "valid"]);
|
|
2206
2751
|
const { currency: currencyOptions } = useDContext();
|
|
2207
2752
|
const { handleOnWheel, } = useDisableInputWheel(ref);
|
|
2208
|
-
const { inputRef, innerValue, innerType, handleOnFocus, handleOnChange, handleOnBlur, } = useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref, minValue, maxValue);
|
|
2209
|
-
|
|
2753
|
+
const { inputRef, innerValue, innerType, isOverMax, isUnderMin, handleOnFocus, handleOnChange, handleOnBlur, } = useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref, minValue, maxValue, clamp);
|
|
2754
|
+
const outOfRange = !clamp && (isOverMax || isUnderMin);
|
|
2755
|
+
// One validation state: an explicit `invalid` wins; otherwise an
|
|
2756
|
+
// out-of-range value is invalid and can't also be shown as valid.
|
|
2757
|
+
const invalid = invalidProp !== null && invalidProp !== void 0 ? invalidProp : outOfRange;
|
|
2758
|
+
const valid = invalid ? false : validProp;
|
|
2759
|
+
return (jsxRuntime.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: (jsxRuntime.jsx("span", { slot: "input-start", className: "d-input-currency-symbol", children: currencyCode || currencyOptions.symbol })) }, props)));
|
|
2210
2760
|
}
|
|
2211
2761
|
const ForwardedDInputCurrency = React.forwardRef(DInputCurrency);
|
|
2212
2762
|
ForwardedDInputCurrency.displayName = 'DInputCurrency';
|
|
@@ -2293,7 +2843,7 @@ const DEFAULT_VALIDATION_MESSAGES = {
|
|
|
2293
2843
|
notMatch: 'The password confirmation and the new password do not match.',
|
|
2294
2844
|
};
|
|
2295
2845
|
const DEFAULT_ENABLED_CHECKS = ['uppercase', 'lowercase', 'number', 'specialChar'];
|
|
2296
|
-
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, }) {
|
|
2846
|
+
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, }) {
|
|
2297
2847
|
const [password, setPassword] = React.useState(value);
|
|
2298
2848
|
React.useEffect(() => {
|
|
2299
2849
|
setPassword(value);
|
|
@@ -2302,17 +2852,31 @@ function DPasswordStrengthMeter({ id, label = 'Password', placeholder, value = '
|
|
|
2302
2852
|
setPassword(newValue);
|
|
2303
2853
|
onChange === null || onChange === void 0 ? void 0 : onChange(newValue);
|
|
2304
2854
|
};
|
|
2305
|
-
return (jsxRuntime.jsxs("div", Object.assign({ className: className, style: style }, dataAttributes, { children: [jsxRuntime.jsx(ForwardedDInputPassword, { id: id, label: label, placeholder: placeholder, value: password, name: name, disabled: disabled, invalid: invalid, onChange: handleChange, readonly: readonly }), jsxRuntime.jsx(PasswordChecksList, { password: password, validationMessages: validationMessages, enabledChecks: enabledChecks })] })));
|
|
2855
|
+
return (jsxRuntime.jsxs("div", Object.assign({ className: className, style: style }, dataAttributes, { children: [jsxRuntime.jsx(ForwardedDInputPassword, { id: id, label: label, "aria-label": ariaLabel, placeholder: placeholder, value: password, name: name, disabled: disabled, invalid: invalid, onChange: handleChange, readonly: readonly }), jsxRuntime.jsx(PasswordChecksList, { password: password, validationMessages: validationMessages, enabledChecks: enabledChecks })] })));
|
|
2306
2856
|
}
|
|
2307
2857
|
|
|
2308
2858
|
function DInputCheck(_a) {
|
|
2309
|
-
var { id: idProp, type, name, label, ariaLabel, checked
|
|
2859
|
+
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 = tslib.__rest(_a, ["id", "type", "name", "label", "ariaLabel", "checked", "defaultChecked", "disabled", "invalid", "valid", "indeterminate", "inputClassName", "value", "hint", "onChange", "className", "style", "dataAttributes"]);
|
|
2310
2860
|
const innerRef = React.useRef(null);
|
|
2861
|
+
// See `useControlledState` for why `onChange` takes part in this decision.
|
|
2862
|
+
const isControlled = checked !== undefined && onChange !== undefined;
|
|
2311
2863
|
const innerId = React.useId();
|
|
2312
2864
|
const id = React.useMemo(() => idProp || innerId, [idProp, innerId]);
|
|
2313
2865
|
const handleChange = React.useCallback((event) => {
|
|
2314
2866
|
onChange === null || onChange === void 0 ? void 0 : onChange(event);
|
|
2315
|
-
|
|
2867
|
+
// Controlled only. Activating a checkbox clears the DOM `indeterminate`
|
|
2868
|
+
// flag, and it has no HTML attribute for React to restore the way it
|
|
2869
|
+
// restores `checked` when the parent rejects the change, so the mixed state
|
|
2870
|
+
// would be gone after the first click — the effect below only re-runs when
|
|
2871
|
+
// the prop moves. Uncontrolled keeps the browser's behaviour, where the
|
|
2872
|
+
// click owns the state and `indeterminate` was only the starting look.
|
|
2873
|
+
// Reapplied after `onChange` so a handler reading
|
|
2874
|
+
// `event.target.indeterminate` still sees what the browser left, and a
|
|
2875
|
+
// parent that does move the prop wins through that effect.
|
|
2876
|
+
if (isControlled && innerRef.current) {
|
|
2877
|
+
innerRef.current.indeterminate = type === 'checkbox' && Boolean(indeterminate);
|
|
2878
|
+
}
|
|
2879
|
+
}, [onChange, isControlled, indeterminate, type]);
|
|
2316
2880
|
const ariaDescribedby = React.useMemo(() => ([
|
|
2317
2881
|
!!hint && `${id}Hint`,
|
|
2318
2882
|
]
|
|
@@ -2326,15 +2890,24 @@ function DInputCheck(_a) {
|
|
|
2326
2890
|
innerRef.current.indeterminate = type === 'checkbox' && Boolean(indeterminate);
|
|
2327
2891
|
}
|
|
2328
2892
|
}, [indeterminate, type]);
|
|
2893
|
+
// Legacy path only: a `checked` with no `onChange` behind it still lands on
|
|
2894
|
+
// the element, but through the DOM, so the input stays uncontrolled and both
|
|
2895
|
+
// clicking it and the native radio-group behaviour keep working.
|
|
2329
2896
|
React.useEffect(() => {
|
|
2330
|
-
if (innerRef.current) {
|
|
2331
|
-
|
|
2897
|
+
if (isControlled || checked === undefined || !innerRef.current) {
|
|
2898
|
+
return;
|
|
2332
2899
|
}
|
|
2333
|
-
|
|
2334
|
-
|
|
2900
|
+
innerRef.current.checked = checked;
|
|
2901
|
+
}, [isControlled, checked]);
|
|
2902
|
+
const inputComponent = React.useMemo(() => (jsxRuntime.jsx("input", Object.assign({ ref: innerRef }, isControlled
|
|
2903
|
+
? { checked }
|
|
2904
|
+
: defaultChecked !== undefined && { defaultChecked }, { onChange: handleChange, className: classNames('form-check-input', {
|
|
2335
2905
|
'is-invalid': invalid,
|
|
2336
2906
|
'is-valid': valid,
|
|
2337
2907
|
}, inputClassName), style: style, id: id, disabled: disabled, type: type, name: name, value: value, "aria-label": ariaLabel }, ariaDescribedby && { 'aria-describedby': ariaDescribedby }, props))), [
|
|
2908
|
+
isControlled,
|
|
2909
|
+
checked,
|
|
2910
|
+
defaultChecked,
|
|
2338
2911
|
handleChange,
|
|
2339
2912
|
invalid,
|
|
2340
2913
|
valid,
|
|
@@ -2349,10 +2922,21 @@ function DInputCheck(_a) {
|
|
|
2349
2922
|
ariaDescribedby,
|
|
2350
2923
|
props,
|
|
2351
2924
|
]);
|
|
2352
|
-
if (
|
|
2925
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
2926
|
+
warnLabelUsage({
|
|
2927
|
+
component: 'DInputCheck',
|
|
2928
|
+
label,
|
|
2929
|
+
// `{...props}` is spread after `aria-label={ariaLabel}`, so a native
|
|
2930
|
+
// `aria-label` wins — including when it is explicitly undefined.
|
|
2931
|
+
hasAccessibleName: !!('aria-label' in props ? props['aria-label'] : ariaLabel)
|
|
2932
|
+
|| !!props['aria-labelledby'],
|
|
2933
|
+
accessibleNameProp: 'ariaLabel',
|
|
2934
|
+
});
|
|
2935
|
+
}
|
|
2936
|
+
if (!hasLabelContent(label)) {
|
|
2353
2937
|
return inputComponent;
|
|
2354
2938
|
}
|
|
2355
|
-
return (jsxRuntime.jsxs("div", Object.assign({ className: classNames('form-check', className) }, dataAttributes, { children: [inputComponent, jsxRuntime.jsx(
|
|
2939
|
+
return (jsxRuntime.jsxs("div", Object.assign({ className: classNames('form-check', className) }, dataAttributes, { children: [inputComponent, jsxRuntime.jsx(DFormLabel, { className: "form-check-label", htmlFor: id, children: label }), hint && (jsxRuntime.jsx("div", { className: "form-text", id: `${id}Hint`, children: hint }))] })));
|
|
2356
2940
|
}
|
|
2357
2941
|
|
|
2358
2942
|
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', }) {
|
|
@@ -2428,14 +3012,14 @@ function DInputPin({ id: idProp, label = '', placeholder, type = 'text', disable
|
|
|
2428
3012
|
const wheelInput = React.useCallback((event) => {
|
|
2429
3013
|
event.currentTarget.blur();
|
|
2430
3014
|
}, []);
|
|
2431
|
-
return (jsxRuntime.jsxs("div", Object.assign({ className: classNames('d-input-pin', className), style: style }, dataAttributes, { children: [label && (jsxRuntime.jsx(
|
|
3015
|
+
return (jsxRuntime.jsxs("div", Object.assign({ className: classNames('d-input-pin', className), style: style }, dataAttributes, { children: [hasLabelContent(label) && (jsxRuntime.jsx(DFormLabel, { htmlFor: "pinIndex0", children: label })), jsxRuntime.jsxs("div", { className: "d-input-pin-group", id: id, children: [Array.from({ length: characters }).map((_, index) => (jsxRuntime.jsx("input", Object.assign({ className: classNames({
|
|
2432
3016
|
'form-control': true,
|
|
2433
3017
|
'is-invalid': invalid,
|
|
2434
3018
|
'is-valid': valid,
|
|
2435
3019
|
}), 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 && (jsxRuntime.jsx("div", { className: "input-group-text", children: jsxRuntime.jsx("span", { className: "spinner-border spinner-border-sm", role: "status", "aria-hidden": "true", children: jsxRuntime.jsx("span", { className: "visually-hidden", children: "Loading..." }) }) }))] }), hint && (jsxRuntime.jsx("div", { className: "form-text", id: `${id}Hint`, children: hint }))] })));
|
|
2436
3020
|
}
|
|
2437
3021
|
|
|
2438
|
-
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, }) {
|
|
3022
|
+
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, }) {
|
|
2439
3023
|
const innerId = React.useId();
|
|
2440
3024
|
const id = React.useMemo(() => idProp || innerId, [idProp, innerId]);
|
|
2441
3025
|
const internalValueExtractor = React.useCallback((option) => {
|
|
@@ -2485,8 +3069,9 @@ function DInputSelect({ id: idProp, name, label = '', className, style, options
|
|
|
2485
3069
|
'floating-label': floatingLabel,
|
|
2486
3070
|
'is-invalid': invalid,
|
|
2487
3071
|
'is-valid': valid,
|
|
2488
|
-
}), "aria-label": label, disabled: disabled || loading, onChange: changeHandler, onBlur: blurHandler }, ariaDescribedby && { 'aria-describedby': ariaDescribedby }, value && { value }, { children: options.map((option) => (jsxRuntime.jsx("option", { value: internalValueExtractor(option), children: internalLabelExtractor(option) }, internalValueExtractor(option)))) }))), [
|
|
3072
|
+
}), "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) => (jsxRuntime.jsx("option", { value: internalValueExtractor(option), children: internalLabelExtractor(option) }, internalValueExtractor(option)))) }))), [
|
|
2489
3073
|
ariaDescribedby,
|
|
3074
|
+
ariaLabel,
|
|
2490
3075
|
blurHandler,
|
|
2491
3076
|
changeHandler,
|
|
2492
3077
|
disabled,
|
|
@@ -2503,7 +3088,7 @@ function DInputSelect({ id: idProp, name, label = '', className, style, options
|
|
|
2503
3088
|
valid,
|
|
2504
3089
|
size,
|
|
2505
3090
|
]);
|
|
2506
|
-
const labelComponent = React.useMemo(() => (jsxRuntime.jsx(
|
|
3091
|
+
const labelComponent = React.useMemo(() => (jsxRuntime.jsx(DFormLabel, { htmlFor: id, children: label })), [
|
|
2507
3092
|
id,
|
|
2508
3093
|
label,
|
|
2509
3094
|
]);
|
|
@@ -2513,15 +3098,26 @@ function DInputSelect({ id: idProp, name, label = '', className, style, options
|
|
|
2513
3098
|
}
|
|
2514
3099
|
return selectComponent;
|
|
2515
3100
|
}, [floatingLabel, labelComponent, selectComponent]);
|
|
2516
|
-
|
|
3101
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
3102
|
+
warnLabelUsage({
|
|
3103
|
+
component: 'DInputSelect',
|
|
3104
|
+
label,
|
|
3105
|
+
hasAccessibleName: !!ariaLabel,
|
|
3106
|
+
accessibleNameProp: 'ariaLabel',
|
|
3107
|
+
floatingLabel,
|
|
3108
|
+
});
|
|
3109
|
+
}
|
|
3110
|
+
return (jsxRuntime.jsxs("div", Object.assign({ className: className, style: style }, dataAttributes, { children: [hasLabelContent(label) && !floatingLabel && (labelComponent), jsxRuntime.jsxs("div", { className: classNames({
|
|
2517
3111
|
'input-group': true,
|
|
2518
3112
|
}), children: [iconStart && (jsxRuntime.jsx("button", { type: "button", className: "input-group-text", id: `${id}Start`, onClick: iconStartClickHandler, disabled: disabled || loading, "aria-label": iconStartAriaLabel, children: iconStart && (jsxRuntime.jsx(DIcon, { icon: iconStart, familyClass: iconStartFamilyClass, familyPrefix: iconStartFamilyPrefix })) })), dynamicComponent, iconEnd && !loading && (jsxRuntime.jsx("button", { type: "button", className: "input-group-text", id: `${id}End`, onClick: iconEndClickHandler, disabled: disabled || loading, "aria-label": iconEndAriaLabel, children: iconEnd && (jsxRuntime.jsx(DIcon, { icon: iconEnd, familyClass: iconEndFamilyClass, familyPrefix: iconEndFamilyPrefix })) })), loading && (jsxRuntime.jsx("div", { className: "input-group-text form-control-icon loading", children: jsxRuntime.jsx("span", { className: "spinner-border spinner-border-sm", role: "status", "aria-hidden": "true", children: jsxRuntime.jsx("span", { className: "visually-hidden", children: "Loading..." }) }) }))] }), hint && (jsxRuntime.jsx("div", { className: "form-text", id: `${id}Hint`, children: hint }))] })));
|
|
2519
3113
|
}
|
|
2520
3114
|
|
|
2521
|
-
function DInputSwitch({ id: idProp, label, ariaLabel, name, checked, disabled, invalid = false, valid = false, hint, readonly, className, style, dataAttributes, inputClassName, onChange, }) {
|
|
3115
|
+
function DInputSwitch({ id: idProp, label, ariaLabel, name, checked, defaultChecked = false, disabled, invalid = false, valid = false, hint, readonly, className, style, dataAttributes, inputClassName, onChange, }) {
|
|
2522
3116
|
const innerId = React.useId();
|
|
2523
3117
|
const id = React.useMemo(() => idProp || innerId, [idProp, innerId]);
|
|
2524
|
-
|
|
3118
|
+
// See `useControlledState` for why `onChange` takes part in this decision.
|
|
3119
|
+
const isControlled = checked !== undefined && onChange !== undefined;
|
|
3120
|
+
const [isChecked, setIsChecked] = useControlledState(checked, isControlled, defaultChecked);
|
|
2525
3121
|
const ariaDescribedby = React.useMemo(() => ([
|
|
2526
3122
|
!!hint && `${id}Hint`,
|
|
2527
3123
|
]
|
|
@@ -2530,18 +3126,23 @@ function DInputSwitch({ id: idProp, label, ariaLabel, name, checked, disabled, i
|
|
|
2530
3126
|
id,
|
|
2531
3127
|
hint,
|
|
2532
3128
|
]);
|
|
2533
|
-
React.useEffect(() => {
|
|
2534
|
-
setInternalIsChecked(checked);
|
|
2535
|
-
}, [checked]);
|
|
2536
3129
|
const changeHandler = React.useCallback((event) => {
|
|
2537
3130
|
const value = event.currentTarget.checked;
|
|
2538
|
-
|
|
3131
|
+
setIsChecked(value);
|
|
2539
3132
|
onChange === null || onChange === void 0 ? void 0 : onChange(value);
|
|
2540
|
-
}, [onChange]);
|
|
3133
|
+
}, [setIsChecked, onChange]);
|
|
3134
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
3135
|
+
warnLabelUsage({
|
|
3136
|
+
component: 'DInputSwitch',
|
|
3137
|
+
label,
|
|
3138
|
+
hasAccessibleName: !!ariaLabel,
|
|
3139
|
+
accessibleNameProp: 'ariaLabel',
|
|
3140
|
+
});
|
|
3141
|
+
}
|
|
2541
3142
|
return (jsxRuntime.jsxs("div", Object.assign({ className: classNames('form-check form-switch', className) }, dataAttributes, { children: [jsxRuntime.jsx("input", Object.assign({ id: id, name: name, onChange: readonly ? () => false : changeHandler, className: classNames('form-check-input', {
|
|
2542
3143
|
'is-invalid': invalid,
|
|
2543
3144
|
'is-valid': valid,
|
|
2544
|
-
}, inputClassName), style: style, type: "checkbox", role: "switch", checked:
|
|
3145
|
+
}, inputClassName), style: style, type: "checkbox", role: "switch", checked: isChecked, disabled: disabled, "aria-label": ariaLabel }, ariaDescribedby && { 'aria-describedby': ariaDescribedby })), hasLabelContent(label) && (jsxRuntime.jsx(DFormLabel, { className: "form-check-label", htmlFor: id, children: label })), hint && (jsxRuntime.jsx("div", { className: "form-text", id: `${id}Hint`, children: hint }))] })));
|
|
2545
3146
|
}
|
|
2546
3147
|
|
|
2547
3148
|
function DInputRange(_a, ref) {
|
|
@@ -2573,15 +3174,61 @@ function DInputRange(_a, ref) {
|
|
|
2573
3174
|
props,
|
|
2574
3175
|
value,
|
|
2575
3176
|
]);
|
|
2576
|
-
if (
|
|
3177
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
3178
|
+
warnLabelUsage({
|
|
3179
|
+
component: 'DInputRange',
|
|
3180
|
+
label,
|
|
3181
|
+
// `{...props}` is spread after `aria-label={ariaLabel}`, so a native
|
|
3182
|
+
// `aria-label` wins — including when it is explicitly undefined.
|
|
3183
|
+
hasAccessibleName: !!('aria-label' in props ? props['aria-label'] : ariaLabel)
|
|
3184
|
+
|| !!props['aria-labelledby'],
|
|
3185
|
+
accessibleNameProp: 'ariaLabel',
|
|
3186
|
+
});
|
|
3187
|
+
}
|
|
3188
|
+
if (!hasLabelContent(label)) {
|
|
2577
3189
|
return inputComponent;
|
|
2578
3190
|
}
|
|
2579
|
-
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(
|
|
3191
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(DFormLabel, { className: "form-label", htmlFor: id, children: label }), inputComponent] }));
|
|
2580
3192
|
}
|
|
2581
3193
|
const ForwardedDInputRange = React.forwardRef(DInputRange);
|
|
2582
3194
|
ForwardedDInputRange.displayName = 'DInputRange';
|
|
2583
3195
|
|
|
2584
|
-
|
|
3196
|
+
/**
|
|
3197
|
+
* Element the enclosing `DListGroup` renders, so each `DListGroupItem` can
|
|
3198
|
+
* check that its own element is valid content for it. `undefined` outside a
|
|
3199
|
+
* `DListGroup`.
|
|
3200
|
+
*/
|
|
3201
|
+
const ListGroupContext = React.createContext(undefined);
|
|
3202
|
+
|
|
3203
|
+
/**
|
|
3204
|
+
* Combinations already reported, keyed by container and item element, so the
|
|
3205
|
+
* same mismatch only warns once per page load however many items share it.
|
|
3206
|
+
*/
|
|
3207
|
+
const warnedCombinations = new Set();
|
|
3208
|
+
/**
|
|
3209
|
+
* Warns, once per combination and only outside production builds, that a
|
|
3210
|
+
* `DListGroupItem` renders an element its `DListGroup` container can't hold.
|
|
3211
|
+
*
|
|
3212
|
+
* Links and buttons inside a <ul>/<ol> are wrapped in an <li>, so the only
|
|
3213
|
+
* invalid pair left is a plain item (an <li>) inside `as="div"`: an <li>
|
|
3214
|
+
* outside of any list. It doesn't fail visibly, which is why it's reported.
|
|
3215
|
+
*
|
|
3216
|
+
* The `process.env.NODE_ENV` guard at the call site is what bundlers
|
|
3217
|
+
* constant-fold, so the whole call — and this module — drops out of a
|
|
3218
|
+
* consumer's production bundle.
|
|
3219
|
+
*/
|
|
3220
|
+
function warnInvalidListMarkup(container, item) {
|
|
3221
|
+
const key = `${container}>${item}`;
|
|
3222
|
+
if (warnedCombinations.has(key))
|
|
3223
|
+
return;
|
|
3224
|
+
warnedCombinations.add(key);
|
|
3225
|
+
// eslint-disable-next-line no-console
|
|
3226
|
+
console.warn(`[Dynamic UI] DListGroupItem: a <${item}> inside a <${container}> is invalid markup. `
|
|
3227
|
+
+ 'Keep the container as a list (the default `as="ul"`, or `numbered`), or render the item '
|
|
3228
|
+
+ 'as a link or button. It never appears in production builds.');
|
|
3229
|
+
}
|
|
3230
|
+
|
|
3231
|
+
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, }) {
|
|
2585
3232
|
const { icon: { familyClass, familyPrefix, materialStyle, }, } = useDContext();
|
|
2586
3233
|
const Tag = React.useMemo(() => {
|
|
2587
3234
|
if (href) {
|
|
@@ -2592,29 +3239,45 @@ function DListGroupItem({ as = 'li', action: actionProp, active, disabled, href,
|
|
|
2592
3239
|
}
|
|
2593
3240
|
return as;
|
|
2594
3241
|
}, [href, as, actionProp]);
|
|
2595
|
-
const
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
3242
|
+
const container = React.useContext(ListGroupContext);
|
|
3243
|
+
const isInteractive = Tag === 'a' || Tag === 'button';
|
|
3244
|
+
// Inside a <ul>/<ol>, a link or button is wrapped in the <li> that carries
|
|
3245
|
+
// the item styles, so the group keeps list semantics (`list > listitem >
|
|
3246
|
+
// link|button`) without breaking Bootstrap's sibling selectors.
|
|
3247
|
+
const isWrapped = isInteractive && (container === 'ul' || container === 'ol');
|
|
3248
|
+
if (process.env.NODE_ENV !== 'production' && container === 'div' && Tag === 'li') {
|
|
3249
|
+
warnInvalidListMarkup(container, Tag);
|
|
3250
|
+
}
|
|
2601
3251
|
const generateClasses = React.useMemo(() => ({
|
|
2602
3252
|
'list-group-item': true,
|
|
2603
|
-
'list-group-item-action':
|
|
3253
|
+
'list-group-item-action': isInteractive || actionProp,
|
|
3254
|
+
'd-list-group-item-interactive': isWrapped,
|
|
2604
3255
|
[`list-group-item-${color}`]: !!color,
|
|
2605
3256
|
active,
|
|
2606
3257
|
disabled,
|
|
2607
|
-
}), [
|
|
2608
|
-
|
|
3258
|
+
}), [isInteractive, actionProp, isWrapped, active, disabled, color]);
|
|
3259
|
+
// A disabled link leaves the tab order and can't be activated: without
|
|
3260
|
+
// `href` and `onClick`, Enter does nothing. Dropping `href` also drops the
|
|
3261
|
+
// implicit link role, so it is set back explicitly. A button uses
|
|
3262
|
+
// `disabled`.
|
|
3263
|
+
const interactiveProps = React.useMemo(() => {
|
|
2609
3264
|
if (Tag === 'button') {
|
|
2610
|
-
return Object.assign(Object.assign({}, active && { 'aria-current':
|
|
3265
|
+
return Object.assign(Object.assign(Object.assign({ type: 'button' }, onClick && { onClick }), active && { 'aria-current': ariaCurrent }), disabled && { disabled: true });
|
|
2611
3266
|
}
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
3267
|
+
if (Tag === 'a') {
|
|
3268
|
+
return disabled
|
|
3269
|
+
? 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 });
|
|
3270
|
+
}
|
|
3271
|
+
return Object.assign(Object.assign(Object.assign({}, onClick && { onClick }), active && { 'aria-current': ariaCurrent }), disabled && { 'aria-disabled': true });
|
|
3272
|
+
}, [Tag, href, onClick, active, ariaCurrent, disabled]);
|
|
3273
|
+
const content = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [iconStart && (jsxRuntime.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 && (jsxRuntime.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" }))] }));
|
|
3274
|
+
if (isWrapped) {
|
|
3275
|
+
return (jsxRuntime.jsx("li", { className: classNames(generateClasses, className), style: style, children: jsxRuntime.jsx(Tag, Object.assign({ className: "d-list-group-item-link" }, interactiveProps, dataAttributes, { children: content })) }));
|
|
3276
|
+
}
|
|
3277
|
+
return (jsxRuntime.jsx(Tag, Object.assign({ className: classNames(generateClasses, className), style: style }, interactiveProps, dataAttributes, { children: content })));
|
|
2615
3278
|
}
|
|
2616
3279
|
|
|
2617
|
-
function DListGroup$1({ as = 'ul', numbered, flush, horizontal, children, className, style, dataAttributes, }) {
|
|
3280
|
+
function DListGroup$1({ as = 'ul', numbered, flush, horizontal, ariaLabel, ariaLabelledBy, children, className, style, dataAttributes, }) {
|
|
2618
3281
|
const Tag = React.useMemo(() => {
|
|
2619
3282
|
if (numbered) {
|
|
2620
3283
|
return 'ol';
|
|
@@ -2632,7 +3295,14 @@ function DListGroup$1({ as = 'ul', numbered, flush, horizontal, children, classN
|
|
|
2632
3295
|
[listGroupHorizontalClass]: !!horizontal,
|
|
2633
3296
|
};
|
|
2634
3297
|
}, [flush, horizontal, numbered]);
|
|
2635
|
-
|
|
3298
|
+
const labelProps = React.useMemo(() => {
|
|
3299
|
+
if (!ariaLabelledBy && !ariaLabel)
|
|
3300
|
+
return {};
|
|
3301
|
+
return Object.assign(Object.assign({}, ariaLabelledBy
|
|
3302
|
+
? { 'aria-labelledby': ariaLabelledBy }
|
|
3303
|
+
: { 'aria-label': ariaLabel }), Tag === 'div' && { role: 'group' });
|
|
3304
|
+
}, [ariaLabel, ariaLabelledBy, Tag]);
|
|
3305
|
+
return (jsxRuntime.jsx(ListGroupContext.Provider, { value: Tag, children: jsxRuntime.jsx(Tag, Object.assign({ className: classNames(generateClasses, className), style: style }, labelProps, dataAttributes, { children: children })) }));
|
|
2636
3306
|
}
|
|
2637
3307
|
var DListGroup = Object.assign(DListGroup$1, {
|
|
2638
3308
|
Item: DListGroupItem,
|
|
@@ -2818,14 +3488,20 @@ function DPopover({ children, renderComponent, open, setOpen, adjustContentToRen
|
|
|
2818
3488
|
}), ref: refs.setFloating, style: floatingStyles, "aria-labelledby": headingId }, getFloatingProps(), { children: children })) }))] })));
|
|
2819
3489
|
}
|
|
2820
3490
|
|
|
2821
|
-
function DProgress({ className, style, currentValue, minValue = 0, maxValue = 100, hideCurrentValue = false, enableStripedAnimation = false, height, dataAttributes, }) {
|
|
2822
|
-
const percentage = React.useMemo(() =>
|
|
3491
|
+
function DProgress({ className, style, currentValue, minValue = 0, maxValue = 100, hideCurrentValue = false, enableStripedAnimation = false, height, ariaLabel, ariaLabelledBy, dataAttributes, }) {
|
|
3492
|
+
const percentage = React.useMemo(() => {
|
|
3493
|
+
const range = maxValue - minValue;
|
|
3494
|
+
if (range <= 0) {
|
|
3495
|
+
return 0;
|
|
3496
|
+
}
|
|
3497
|
+
return Math.round(((currentValue - minValue) * 100) / range);
|
|
3498
|
+
}, [currentValue, minValue, maxValue]);
|
|
2823
3499
|
const formatProgress = React.useMemo(() => `${percentage}%`, [percentage]);
|
|
2824
3500
|
const generateClasses = React.useMemo(() => ({
|
|
2825
3501
|
'progress-bar': true,
|
|
2826
3502
|
'progress-bar-striped progress-bar-animated': enableStripedAnimation,
|
|
2827
3503
|
}), [enableStripedAnimation]);
|
|
2828
|
-
return (jsxRuntime.jsx("div", Object.assign({ className: classNames('progress', className), style: Object.assign({ height }, style) }, dataAttributes, { children: jsxRuntime.jsx("div", { className: classNames(generateClasses), role: "progressbar", "aria-label":
|
|
3504
|
+
return (jsxRuntime.jsx("div", Object.assign({ className: classNames('progress', className), style: Object.assign({ height }, style) }, dataAttributes, { children: jsxRuntime.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 }) })));
|
|
2829
3505
|
}
|
|
2830
3506
|
|
|
2831
3507
|
function DStepper$2({ options, currentStep, iconSuccess: iconSuccessProp, iconSuccessFamilyClass, iconSuccessFamilyPrefix, iconSuccessMaterialStyle, vertical = false, completed, alignStart = false, className, style, }) {
|
|
@@ -2933,10 +3609,11 @@ function DTimeline({ className, style, dataAttributes, items, }) {
|
|
|
2933
3609
|
}
|
|
2934
3610
|
|
|
2935
3611
|
const TabContext = React.createContext(undefined);
|
|
3612
|
+
const TabsStateContext = React.createContext(undefined);
|
|
2936
3613
|
function useTabContext() {
|
|
2937
3614
|
const context = React.useContext(TabContext);
|
|
2938
3615
|
if (context === undefined) {
|
|
2939
|
-
throw new Error('useTabContext was used outside of
|
|
3616
|
+
throw new Error('useTabContext was used outside of DTabs');
|
|
2940
3617
|
}
|
|
2941
3618
|
return context;
|
|
2942
3619
|
}
|
|
@@ -2946,33 +3623,44 @@ function DTabContent({ tab, children, className, style, }) {
|
|
|
2946
3623
|
if (!isSelected(tab)) {
|
|
2947
3624
|
return null;
|
|
2948
3625
|
}
|
|
2949
|
-
return (jsxRuntime.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 }));
|
|
3626
|
+
return (jsxRuntime.jsx("div", { className: classNames('tab-pane fade show active', className), id: `${tab}Pane`, role: "tabpanel", tabIndex: 0, "aria-labelledby": `${tab}Tab`, style: style, children: jsxRuntime.jsx(TabsStateContext.Provider, { value: undefined, children: children }) }));
|
|
2950
3627
|
}
|
|
2951
3628
|
|
|
2952
|
-
|
|
3629
|
+
/**
|
|
3630
|
+
* Owns the selected tab so the tab bar (`DTabs`) and its panels (`DTabs.Tab`)
|
|
3631
|
+
* can live anywhere inside it, e.g. the bar in a header and the panels in
|
|
3632
|
+
* another column.
|
|
3633
|
+
*/
|
|
3634
|
+
function DTabsProvider({ defaultSelected, children, }) {
|
|
2953
3635
|
const [selected, setSelected] = React.useState(defaultSelected);
|
|
3636
|
+
React.useEffect(() => {
|
|
3637
|
+
setSelected(defaultSelected);
|
|
3638
|
+
}, [defaultSelected]);
|
|
3639
|
+
const isSelected = React.useCallback((tab) => (selected === tab), [selected]);
|
|
3640
|
+
const state = React.useMemo(() => ({ selected, setSelected }), [selected]);
|
|
3641
|
+
const value = React.useMemo(() => ({ isSelected }), [isSelected]);
|
|
3642
|
+
return (jsxRuntime.jsx(TabsStateContext.Provider, { value: state, children: jsxRuntime.jsx(TabContext.Provider, { value: value, children: children }) }));
|
|
3643
|
+
}
|
|
3644
|
+
|
|
3645
|
+
function DTabs$1({ children, defaultSelected, onChange, options, className, classNameTab, classNameContent, style, vertical, variant = 'underline', dataAttributes, ariaLabel, ariaLabelledBy, }) {
|
|
3646
|
+
var _a;
|
|
3647
|
+
const shared = React.useContext(TabsStateContext);
|
|
3648
|
+
const [ownSelected, setOwnSelected] = React.useState(defaultSelected);
|
|
3649
|
+
const selected = shared
|
|
3650
|
+
? shared.selected
|
|
3651
|
+
: ownSelected !== null && ownSelected !== void 0 ? ownSelected : (_a = options.find((opt) => !opt.disabled)) === null || _a === void 0 ? void 0 : _a.tab;
|
|
3652
|
+
const setSelected = shared ? shared.setSelected : setOwnSelected;
|
|
2954
3653
|
const onSelect = React.useCallback((option) => {
|
|
2955
3654
|
if (option.tab) {
|
|
2956
3655
|
setSelected(option.tab);
|
|
2957
3656
|
}
|
|
2958
3657
|
onChange === null || onChange === void 0 ? void 0 : onChange(option);
|
|
2959
|
-
}, [onChange]);
|
|
3658
|
+
}, [onChange, setSelected]);
|
|
2960
3659
|
React.useEffect(() => {
|
|
2961
|
-
|
|
3660
|
+
setOwnSelected(defaultSelected);
|
|
2962
3661
|
}, [defaultSelected]);
|
|
2963
3662
|
const generateClasses = React.useMemo(() => (Object.assign({ nav: true, 'd-tabs-nav-vertical': vertical && variant !== 'tabs', [`nav-${variant}`]: true }, className && { [className]: true })), [vertical, variant, className]);
|
|
2964
3663
|
const tabRefs = React.useRef([]);
|
|
2965
|
-
// Always holds the latest `options` without needing to be a dependency:
|
|
2966
|
-
// `options` is commonly passed as an inline array literal (e.g.
|
|
2967
|
-
// `options={[{ label: 'SMS', tab: 'sms' }, ...]}`), so it's a new array
|
|
2968
|
-
// reference on every parent render even when its content hasn't changed.
|
|
2969
|
-
// Reading it from this ref (updated synchronously on every render) lets
|
|
2970
|
-
// the focus effect below react only to `selected` changing.
|
|
2971
|
-
const optionsRef = React.useRef(options);
|
|
2972
|
-
optionsRef.current = options;
|
|
2973
|
-
React.useEffect(() => {
|
|
2974
|
-
tabRefs.current = options.map((_, i) => tabRefs.current[i] || React.createRef());
|
|
2975
|
-
}, [options]);
|
|
2976
3664
|
// Ensure selected is never disabled
|
|
2977
3665
|
React.useEffect(() => {
|
|
2978
3666
|
if (options.length === 0)
|
|
@@ -2983,29 +3671,16 @@ function DTabs$1({ children, defaultSelected, onChange, options, className, clas
|
|
|
2983
3671
|
if (firstEnabled)
|
|
2984
3672
|
setSelected(firstEnabled.tab);
|
|
2985
3673
|
}
|
|
2986
|
-
}, [options, selected]);
|
|
2987
|
-
//
|
|
2988
|
-
//
|
|
2989
|
-
//
|
|
2990
|
-
//
|
|
3674
|
+
}, [options, selected, setSelected]);
|
|
3675
|
+
// Focus only moves in response to the user: arrow keys and clicks call
|
|
3676
|
+
// this directly. Changes to `selected` that don't come from an interaction
|
|
3677
|
+
// (mount, a new `defaultSelected`, the disabled-tab fallback above) leave
|
|
3678
|
+
// focus where it is, so the page doesn't scroll to the tablist and no other
|
|
3679
|
+
// element (a search field, an OTP input) loses focus.
|
|
2991
3680
|
const focusTab = React.useCallback((idx) => {
|
|
2992
3681
|
var _a;
|
|
2993
|
-
|
|
2994
|
-
tabRefs.current[idx].current.focus();
|
|
2995
|
-
}
|
|
3682
|
+
(_a = tabRefs.current[idx]) === null || _a === void 0 ? void 0 : _a.focus();
|
|
2996
3683
|
}, []);
|
|
2997
|
-
// Focus selected tab when selected changes.
|
|
2998
|
-
// Reads `options` from `optionsRef` (see comment above) instead of
|
|
2999
|
-
// depending on `options` directly, so a parent re-render that merely
|
|
3000
|
-
// creates a new `options` reference with identical content doesn't call
|
|
3001
|
-
// `focusTab` again and steal focus away from unrelated elements on the
|
|
3002
|
-
// page (e.g. an OTP input).
|
|
3003
|
-
React.useEffect(() => {
|
|
3004
|
-
const idx = optionsRef.current.findIndex((opt) => opt.tab === selected && !opt.disabled);
|
|
3005
|
-
if (idx !== -1) {
|
|
3006
|
-
focusTab(idx);
|
|
3007
|
-
}
|
|
3008
|
-
}, [selected, focusTab]);
|
|
3009
3684
|
const handleKeyDown = React.useCallback((idx, e) => {
|
|
3010
3685
|
const count = options.length;
|
|
3011
3686
|
if (count === 0)
|
|
@@ -3034,7 +3709,7 @@ function DTabs$1({ children, defaultSelected, onChange, options, className, clas
|
|
|
3034
3709
|
}
|
|
3035
3710
|
}
|
|
3036
3711
|
}
|
|
3037
|
-
}, [options, vertical, focusTab]);
|
|
3712
|
+
}, [options, vertical, focusTab, setSelected]);
|
|
3038
3713
|
let tablistProps = {};
|
|
3039
3714
|
if (ariaLabelledBy) {
|
|
3040
3715
|
tablistProps = { 'aria-labelledby': ariaLabelledBy };
|
|
@@ -3051,11 +3726,17 @@ function DTabs$1({ children, defaultSelected, onChange, options, className, clas
|
|
|
3051
3726
|
'd-tabs-column': !vertical || variant === 'tabs',
|
|
3052
3727
|
}), style: style }, dataAttributes, { children: [jsxRuntime.jsx("ul", Object.assign({ className: classNames(generateClasses), role: "tablist", "aria-orientation": vertical ? 'vertical' : undefined }, tablistProps, { children: options.map((option, idx) => {
|
|
3053
3728
|
const isTabSelected = !!option.tab && option.tab === selected;
|
|
3054
|
-
return (jsxRuntime.jsx("li", { role: "presentation", className: "nav-item", children: jsxRuntime.jsx("button", { ref:
|
|
3055
|
-
|
|
3729
|
+
return (jsxRuntime.jsx("li", { role: "presentation", className: "nav-item", children: jsxRuntime.jsx("button", { ref: (element) => {
|
|
3730
|
+
tabRefs.current[idx] = element;
|
|
3731
|
+
}, 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: () => {
|
|
3732
|
+
focusTab(idx);
|
|
3733
|
+
onSelect(option);
|
|
3734
|
+
}, onKeyDown: (e) => handleKeyDown(idx, e), children: option.label }) }, option.tab));
|
|
3735
|
+
}) })), React.Children.toArray(children).length > 0 && (jsxRuntime.jsx("div", { className: classNames('d-tabs-content tab-content', classNameContent), children: children }))] })) }));
|
|
3056
3736
|
}
|
|
3057
3737
|
var DTabs = Object.assign(DTabs$1, {
|
|
3058
3738
|
Tab: DTabContent,
|
|
3739
|
+
Provider: DTabsProvider,
|
|
3059
3740
|
});
|
|
3060
3741
|
|
|
3061
3742
|
function DToastHeader({ children, className, style }) {
|
|
@@ -3066,8 +3747,13 @@ function DToastBody({ children, className, style }) {
|
|
|
3066
3747
|
return (jsxRuntime.jsx("div", { className: classNames('toast-body', className), style: style, children: children }));
|
|
3067
3748
|
}
|
|
3068
3749
|
|
|
3069
|
-
|
|
3070
|
-
|
|
3750
|
+
const LIVE_REGION_ATTRIBUTES = {
|
|
3751
|
+
alert: { role: 'alert', 'aria-live': 'assertive', 'aria-atomic': 'true' },
|
|
3752
|
+
status: { role: 'status', 'aria-live': 'polite', 'aria-atomic': 'true' },
|
|
3753
|
+
none: {},
|
|
3754
|
+
};
|
|
3755
|
+
function DToast$1({ children, role = 'alert', className, style, dataAttributes, }) {
|
|
3756
|
+
return (jsxRuntime.jsx("div", Object.assign({ className: classNames('toast', className) }, LIVE_REGION_ATTRIBUTES[role], { style: style }, dataAttributes, { children: children })));
|
|
3071
3757
|
}
|
|
3072
3758
|
var DToast = Object.assign(DToast$1, {
|
|
3073
3759
|
Header: DToastHeader,
|
|
@@ -3106,19 +3792,19 @@ function useDToast() {
|
|
|
3106
3792
|
if (typeof data === 'function') {
|
|
3107
3793
|
return reactHotToast.toast.custom(data, toastProps);
|
|
3108
3794
|
}
|
|
3109
|
-
const { title, description, icon, closeIcon, timestamp, color, } = data;
|
|
3795
|
+
const { title, description, icon, closeIcon, timestamp, color, role, closeAriaLabel = 'Close', } = data;
|
|
3110
3796
|
return reactHotToast.toast.custom(({ id, visible }) => {
|
|
3111
3797
|
if (!visible) {
|
|
3112
3798
|
return null;
|
|
3113
3799
|
}
|
|
3114
3800
|
if (!description) {
|
|
3115
|
-
return (jsxRuntime.jsx(DToast, { className: classNames({
|
|
3801
|
+
return (jsxRuntime.jsx(DToast, { role: role, className: classNames({
|
|
3116
3802
|
[`toast-${color}`]: !!color,
|
|
3117
|
-
}, 'show'), children: jsxRuntime.jsxs(DToast.Body, { children: [icon && (jsxRuntime.jsx(DIcon, { className: "toast-icon", icon: icon })), jsxRuntime.jsx("p", { className: "toast-title", children: title }), jsxRuntime.jsx("button", { type: "button", className: "d-close", "aria-label":
|
|
3803
|
+
}, 'show'), children: jsxRuntime.jsxs(DToast.Body, { children: [icon && (jsxRuntime.jsx(DIcon, { className: "toast-icon", icon: icon })), jsxRuntime.jsx("p", { className: "toast-title", children: title }), jsxRuntime.jsx("button", { type: "button", className: "d-close", "aria-label": closeAriaLabel, onClick: () => reactHotToast.toast.dismiss(id), children: jsxRuntime.jsx(DIcon, { icon: closeIcon || xLg }) })] }) }));
|
|
3118
3804
|
}
|
|
3119
|
-
return (jsxRuntime.jsxs(DToast, { className: classNames({
|
|
3805
|
+
return (jsxRuntime.jsxs(DToast, { role: role, className: classNames({
|
|
3120
3806
|
[`toast-${color}`]: !!color,
|
|
3121
|
-
}, 'show'), children: [jsxRuntime.jsxs(DToast.Header, { children: [icon && (jsxRuntime.jsx(DIcon, { className: "toast-icon", icon: icon })), jsxRuntime.jsx("p", { className: "toast-title", children: title }), timestamp && (jsxRuntime.jsx("small", { className: "toast-timestamp", children: timestamp })), jsxRuntime.jsx("button", { type: "button", className: "d-close", "aria-label":
|
|
3807
|
+
}, 'show'), children: [jsxRuntime.jsxs(DToast.Header, { children: [icon && (jsxRuntime.jsx(DIcon, { className: "toast-icon", icon: icon })), jsxRuntime.jsx("p", { className: "toast-title", children: title }), timestamp && (jsxRuntime.jsx("small", { className: "toast-timestamp", children: timestamp })), jsxRuntime.jsx("button", { type: "button", className: "d-close", "aria-label": closeAriaLabel, onClick: () => reactHotToast.toast.dismiss(id), children: jsxRuntime.jsx(DIcon, { icon: closeIcon || xLg }) })] }), jsxRuntime.jsx(DToast.Body, { children: jsxRuntime.jsx("span", { children: description }) })] }));
|
|
3122
3808
|
}, toastProps);
|
|
3123
3809
|
}, [xLg]);
|
|
3124
3810
|
return {
|
|
@@ -3354,7 +4040,7 @@ function DInputPhone(_a, ref) {
|
|
|
3354
4040
|
placeholder,
|
|
3355
4041
|
valid,
|
|
3356
4042
|
]);
|
|
3357
|
-
const labelComponent = React.useMemo(() => (jsxRuntime.jsx(
|
|
4043
|
+
const labelComponent = React.useMemo(() => (jsxRuntime.jsx(DFormLabel, { htmlFor: id, children: label })), [
|
|
3358
4044
|
id,
|
|
3359
4045
|
label,
|
|
3360
4046
|
]);
|
|
@@ -3368,7 +4054,16 @@ function DInputPhone(_a, ref) {
|
|
|
3368
4054
|
inputComponent,
|
|
3369
4055
|
labelComponent,
|
|
3370
4056
|
]);
|
|
3371
|
-
|
|
4057
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
4058
|
+
warnLabelUsage({
|
|
4059
|
+
component: 'DInputPhone',
|
|
4060
|
+
label,
|
|
4061
|
+
hasAccessibleName: !!inputProps['aria-label'] || !!inputProps['aria-labelledby'],
|
|
4062
|
+
accessibleNameProp: 'aria-label',
|
|
4063
|
+
floatingLabel,
|
|
4064
|
+
});
|
|
4065
|
+
}
|
|
4066
|
+
return (jsxRuntime.jsxs("div", Object.assign({ className: classNames('d-input-phone', className), style: style }, dataAttributes, { children: [hasLabelContent(label) && !floatingLabel && labelComponent, jsxRuntime.jsxs("div", { className: classNames({
|
|
3372
4067
|
[`input-group-${size}`]: !!size,
|
|
3373
4068
|
'input-group': true,
|
|
3374
4069
|
'has-validation': invalid || valid,
|
|
@@ -3835,11 +4530,11 @@ function DOtp({ className, action, isLoading, otpSize = 6, texts = TEXT_PROPS, s
|
|
|
3835
4530
|
}, loading: isLoading || isSubmitting }), jsxRuntime.jsx("p", { className: "d-otp-contact", children: texts.contact })] })] })] }));
|
|
3836
4531
|
}
|
|
3837
4532
|
|
|
3838
|
-
function DefaultErrorBoundary({ resetErrorBoundary }) {
|
|
3839
|
-
return (jsxRuntime.jsx(DAlert, { color: "danger", showClose: false, children: jsxRuntime.jsxs("div", { className: "d-error-boundary-content", children: [jsxRuntime.jsx("span", { children:
|
|
4533
|
+
function DefaultErrorBoundary({ resetErrorBoundary, message = 'An unexpected error occurred.', retryMessage = 'Retry', }) {
|
|
4534
|
+
return (jsxRuntime.jsx(DAlert, { color: "danger", showClose: false, children: jsxRuntime.jsxs("div", { className: "d-error-boundary-content", children: [jsxRuntime.jsx("span", { children: message }), jsxRuntime.jsx(DButton, { color: "secondary", variant: "outline", size: "sm", onClick: resetErrorBoundary, children: retryMessage })] }) }));
|
|
3840
4535
|
}
|
|
3841
4536
|
|
|
3842
|
-
function DErrorBoundary({ name, fallback, resetKeys, onReset, onError, children, }) {
|
|
4537
|
+
function DErrorBoundary({ name, fallback, resetKeys, onReset, onError, messages, children, }) {
|
|
3843
4538
|
const handleError = React.useCallback((error, info) => {
|
|
3844
4539
|
// eslint-disable-next-line no-console
|
|
3845
4540
|
console.error(`[DErrorBoundary${name ? `:${name}` : ''}]`, reactErrorBoundary.getErrorMessage(error), info);
|
|
@@ -3848,8 +4543,8 @@ function DErrorBoundary({ name, fallback, resetKeys, onReset, onError, children,
|
|
|
3848
4543
|
const FallbackRender = React.useCallback((props) => {
|
|
3849
4544
|
if (fallback)
|
|
3850
4545
|
return fallback(props);
|
|
3851
|
-
return (jsxRuntime.jsx(DefaultErrorBoundary, { resetErrorBoundary: props.resetErrorBoundary }));
|
|
3852
|
-
}, [fallback]);
|
|
4546
|
+
return (jsxRuntime.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 }));
|
|
4547
|
+
}, [fallback, messages]);
|
|
3853
4548
|
return (jsxRuntime.jsx(reactErrorBoundary.ErrorBoundary, { resetKeys: resetKeys, onReset: onReset, onError: handleError, fallbackRender: FallbackRender, children: children }));
|
|
3854
4549
|
}
|
|
3855
4550
|
|
|
@@ -3870,6 +4565,11 @@ function render(renderable) {
|
|
|
3870
4565
|
return null;
|
|
3871
4566
|
return typeof renderable === 'function' ? renderable() : renderable;
|
|
3872
4567
|
}
|
|
4568
|
+
function isEmpty(data) {
|
|
4569
|
+
if (Array.isArray(data))
|
|
4570
|
+
return data.length === 0;
|
|
4571
|
+
return data === null || data === undefined;
|
|
4572
|
+
}
|
|
3873
4573
|
function DDataStateWrapper({ isLoading, isError, data, onRetry, messages, renderLoading, renderEmpty, renderError, children, }) {
|
|
3874
4574
|
// 1. Loading
|
|
3875
4575
|
if (isLoading) {
|
|
@@ -3883,13 +4583,15 @@ function DDataStateWrapper({ isLoading, isError, data, onRetry, messages, render
|
|
|
3883
4583
|
return render(renderError);
|
|
3884
4584
|
return (jsxRuntime.jsx(ErrorState, { onRetry: onRetry, message: messages === null || messages === void 0 ? void 0 : messages.error, retryMessage: messages === null || messages === void 0 ? void 0 : messages.retry }));
|
|
3885
4585
|
}
|
|
3886
|
-
// 3. Empty
|
|
3887
|
-
if (
|
|
4586
|
+
// 3. Empty: no items for a collection, null/undefined for a single resource
|
|
4587
|
+
if (isEmpty(data)) {
|
|
3888
4588
|
if (renderEmpty)
|
|
3889
4589
|
return render(renderEmpty);
|
|
3890
4590
|
return (jsxRuntime.jsx(EmptyState, { message: messages === null || messages === void 0 ? void 0 : messages.empty }));
|
|
3891
4591
|
}
|
|
3892
|
-
// 4. Success
|
|
4592
|
+
// 4. Success: the render prop gets the same shape it was given
|
|
4593
|
+
// Both overloads pair `data` with its own `children` signature, so the
|
|
4594
|
+
// value is handed back exactly as it was received.
|
|
3893
4595
|
return jsxRuntime.jsx(jsxRuntime.Fragment, { children: children(data) });
|
|
3894
4596
|
}
|
|
3895
4597
|
|
|
@@ -4097,6 +4799,7 @@ exports.DStepperDesktop = DStepper$2;
|
|
|
4097
4799
|
exports.DStepperMobile = DStepper$1;
|
|
4098
4800
|
exports.DTabContent = DTabContent;
|
|
4099
4801
|
exports.DTabs = DTabs;
|
|
4802
|
+
exports.DTabsProvider = DTabsProvider;
|
|
4100
4803
|
exports.DTimeline = DTimeline;
|
|
4101
4804
|
exports.DToast = DToast;
|
|
4102
4805
|
exports.DToastContainer = DToastContainer;
|