@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
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Backs a value that the consumer may or may not drive, so a control works in
|
|
5
|
+
* both modes without a copy of the state drifting away from the props.
|
|
6
|
+
*
|
|
7
|
+
* A control counts as controlled only when the consumer passes the value *and*
|
|
8
|
+
* an `onChange` to push it back. The value on its own has always meant "start
|
|
9
|
+
* here" in this library — `<DInputCheck type="radio" checked />` inside a radio
|
|
10
|
+
* group, for one — so treating it as controlled would silently freeze those
|
|
11
|
+
* controls, and React emits no warning to say why: the components always attach
|
|
12
|
+
* their own `onChange` handler.
|
|
13
|
+
*
|
|
14
|
+
* @param value the value coming from props, or `undefined` when not passed.
|
|
15
|
+
* @param isControlled whether the consumer can drive the value back.
|
|
16
|
+
* @param initialValue what to start from when no value is passed.
|
|
17
|
+
* @returns the value to render, and a setter to call on every user change.
|
|
18
|
+
*/
|
|
19
|
+
function useControlledState(value, isControlled, initialValue) {
|
|
20
|
+
const [internalValue, setInternalValue] = useState(value === undefined ? initialValue : value);
|
|
21
|
+
// The internal copy follows `value` in both modes.
|
|
22
|
+
//
|
|
23
|
+
// Uncontrolled, `value` without an `onChange` still means "start here", and a
|
|
24
|
+
// later flip of it from outside must still land — but between flips the
|
|
25
|
+
// control has to keep moving on its own, which is why this depends on `value`
|
|
26
|
+
// alone and not on `internalValue`.
|
|
27
|
+
//
|
|
28
|
+
// Controlled, the copy is not what gets rendered, but it is what the control
|
|
29
|
+
// falls back to if the consumer later stops passing a value — `value={locked
|
|
30
|
+
// ? undefined : x}`, say. Keeping it current means that hand-off carries on
|
|
31
|
+
// from where the control is instead of snapping back to the mount-time seed.
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (value === undefined) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
setInternalValue(value);
|
|
37
|
+
}, [value]);
|
|
38
|
+
const setValue = useCallback((next) => {
|
|
39
|
+
if (isControlled) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
setInternalValue(next);
|
|
43
|
+
}, [isControlled]);
|
|
44
|
+
return [
|
|
45
|
+
isControlled && value !== undefined ? value : internalValue,
|
|
46
|
+
setValue,
|
|
47
|
+
];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export { useControlledState as default };
|
|
51
|
+
//# sourceMappingURL=useControlledState.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useControlledState.js","sources":["../../../dist-transpiled/hooks/useControlledState.js"],"sourcesContent":["import { useCallback, useEffect, useState, } from 'react';\n/**\n * Backs a value that the consumer may or may not drive, so a control works in\n * both modes without a copy of the state drifting away from the props.\n *\n * A control counts as controlled only when the consumer passes the value *and*\n * an `onChange` to push it back. The value on its own has always meant \"start\n * here\" in this library — `<DInputCheck type=\"radio\" checked />` inside a radio\n * group, for one — so treating it as controlled would silently freeze those\n * controls, and React emits no warning to say why: the components always attach\n * their own `onChange` handler.\n *\n * @param value the value coming from props, or `undefined` when not passed.\n * @param isControlled whether the consumer can drive the value back.\n * @param initialValue what to start from when no value is passed.\n * @returns the value to render, and a setter to call on every user change.\n */\nexport default function useControlledState(value, isControlled, initialValue) {\n const [internalValue, setInternalValue] = useState(value === undefined ? initialValue : value);\n // The internal copy follows `value` in both modes.\n //\n // Uncontrolled, `value` without an `onChange` still means \"start here\", and a\n // later flip of it from outside must still land — but between flips the\n // control has to keep moving on its own, which is why this depends on `value`\n // alone and not on `internalValue`.\n //\n // Controlled, the copy is not what gets rendered, but it is what the control\n // falls back to if the consumer later stops passing a value — `value={locked\n // ? undefined : x}`, say. Keeping it current means that hand-off carries on\n // from where the control is instead of snapping back to the mount-time seed.\n useEffect(() => {\n if (value === undefined) {\n return;\n }\n setInternalValue(value);\n }, [value]);\n const setValue = useCallback((next) => {\n if (isControlled) {\n return;\n }\n setInternalValue(next);\n }, [isControlled]);\n return [\n isControlled && value !== undefined ? value : internalValue,\n setValue,\n ];\n}\n//# sourceMappingURL=useControlledState.js.map"],"names":[],"mappings":";;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACe,SAAS,kBAAkB,CAAC,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE;AAC9E,IAAI,MAAM,CAAC,aAAa,EAAE,gBAAgB,CAAC,GAAG,QAAQ,CAAC,KAAK,KAAK,SAAS,GAAG,YAAY,GAAG,KAAK,CAAC;AAClG;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,SAAS,CAAC,MAAM;AACpB,QAAQ,IAAI,KAAK,KAAK,SAAS,EAAE;AACjC,YAAY;AACZ,QAAQ;AACR,QAAQ,gBAAgB,CAAC,KAAK,CAAC;AAC/B,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;AACf,IAAI,MAAM,QAAQ,GAAG,WAAW,CAAC,CAAC,IAAI,KAAK;AAC3C,QAAQ,IAAI,YAAY,EAAE;AAC1B,YAAY;AACZ,QAAQ;AACR,QAAQ,gBAAgB,CAAC,IAAI,CAAC;AAC9B,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC;AACtB,IAAI,OAAO;AACX,QAAQ,YAAY,IAAI,KAAK,KAAK,SAAS,GAAG,KAAK,GAAG,aAAa;AACnE,QAAQ,QAAQ;AAChB,KAAK;AACL;;;;"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback, useState,
|
|
1
|
+
import { useRef, useCallback, useState, useEffect, useMemo } from 'react';
|
|
2
2
|
import currency from 'currency.js';
|
|
3
3
|
import useProvidedRefOrCreate from './useProvidedRefOrCreate.js';
|
|
4
4
|
|
|
@@ -8,10 +8,40 @@ function formatValue(value, currencyOptions) {
|
|
|
8
8
|
}
|
|
9
9
|
return currency(value, Object.assign(Object.assign({}, currencyOptions), { symbol: '' })).format();
|
|
10
10
|
}
|
|
11
|
-
|
|
11
|
+
/**
|
|
12
|
+
* State and handlers for a currency input: a formatted string while the field
|
|
13
|
+
* is idle and the raw number while it is being edited.
|
|
14
|
+
*
|
|
15
|
+
* `minValue`/`maxValue` bound the value. With `clamp` (default `true`) an
|
|
16
|
+
* out-of-range value is brought into range whenever it is not being typed:
|
|
17
|
+
* on mount, when `value` or the bounds change, and on blur. Every time the
|
|
18
|
+
* clamp changes the value, `onChange` receives the clamped number, so the
|
|
19
|
+
* input and the consumer's state never disagree. While the field is focused
|
|
20
|
+
* the typed number is reported as is, so a partial entry is not rewritten.
|
|
21
|
+
*
|
|
22
|
+
* With `clamp: false` the value is never changed: the bounds only feed
|
|
23
|
+
* `isOverMax` / `isUnderMin`, so the consumer can show its own message (e.g.
|
|
24
|
+
* "You exceeded the limit") and keep the entered amount.
|
|
25
|
+
*
|
|
26
|
+
* Controlled usage follows the same contract as a native controlled input:
|
|
27
|
+
* reflect `onChange` into `value` in the same event (`setState` in the
|
|
28
|
+
* handler). The hook keeps showing `value` whenever it isn't being edited, so
|
|
29
|
+
* a consumer can reject a change by keeping `value` as it was. Deferring the
|
|
30
|
+
* update (a timer, awaiting a request) makes the hook see a stale `value` in
|
|
31
|
+
* between: the field can flash the previous amount and a clamp can be
|
|
32
|
+
* reported again when the deferred value arrives.
|
|
33
|
+
*/
|
|
34
|
+
function useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref, minValue, maxValue, clamp = true) {
|
|
12
35
|
const inputRef = useProvidedRefOrCreate(ref);
|
|
36
|
+
const onChangeRef = useRef(onChange);
|
|
37
|
+
onChangeRef.current = onChange;
|
|
38
|
+
// Last clamp reported through `onChange`, as the value it came from and the
|
|
39
|
+
// value it produced. StrictMode replays effects, and a consumer may reflect
|
|
40
|
+
// `onChange` asynchronously, both before `value` catches up: the same clamp
|
|
41
|
+
// must not be reported twice.
|
|
42
|
+
const lastReportedClampRef = useRef(null);
|
|
13
43
|
const clampValue = useCallback((newValue) => {
|
|
14
|
-
if (newValue === undefined) {
|
|
44
|
+
if (newValue === undefined || !clamp) {
|
|
15
45
|
return newValue;
|
|
16
46
|
}
|
|
17
47
|
let clampedValue = newValue;
|
|
@@ -22,10 +52,9 @@ function useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref
|
|
|
22
52
|
clampedValue = Math.min(clampedValue, maxValue);
|
|
23
53
|
}
|
|
24
54
|
return clampedValue;
|
|
25
|
-
}, [minValue, maxValue]);
|
|
55
|
+
}, [minValue, maxValue, clamp]);
|
|
26
56
|
const [innerType, setInnerType] = useState('text');
|
|
27
|
-
const [innerNumber, setInnerNumber] = useState(clampValue(value));
|
|
28
|
-
const [innerString, setInnerString] = useState(formatValue(clampValue(value), currencyOptions));
|
|
57
|
+
const [innerNumber, setInnerNumber] = useState(() => clampValue(value));
|
|
29
58
|
const handleOnFocus = useCallback((event) => {
|
|
30
59
|
event.stopPropagation();
|
|
31
60
|
setInnerType('number');
|
|
@@ -37,35 +66,60 @@ function useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref
|
|
|
37
66
|
const clampedNumber = clampValue(innerNumber);
|
|
38
67
|
if (clampedNumber !== innerNumber) {
|
|
39
68
|
setInnerNumber(clampedNumber);
|
|
40
|
-
|
|
69
|
+
// Recorded before notifying, so the sync effect that runs when editing
|
|
70
|
+
// ends doesn't report the same clamp again while `value` still holds the
|
|
71
|
+
// typed number.
|
|
72
|
+
lastReportedClampRef.current = { from: value, to: clampedNumber };
|
|
41
73
|
onChange === null || onChange === void 0 ? void 0 : onChange(clampedNumber);
|
|
42
74
|
}
|
|
43
75
|
onBlur === null || onBlur === void 0 ? void 0 : onBlur(event);
|
|
44
|
-
}, [onBlur, innerNumber, clampValue,
|
|
76
|
+
}, [onBlur, innerNumber, clampValue, onChange, value]);
|
|
45
77
|
const handleOnChange = useCallback((newValue) => {
|
|
46
78
|
const newNumber = (newValue === undefined || newValue === '') ? undefined : Number(newValue);
|
|
47
79
|
if (newNumber !== innerNumber) {
|
|
48
80
|
setInnerNumber(newNumber);
|
|
49
|
-
setInnerString(formatValue(newNumber, currencyOptions));
|
|
50
81
|
onChange === null || onChange === void 0 ? void 0 : onChange(newNumber);
|
|
51
82
|
}
|
|
52
|
-
}, [
|
|
53
|
-
const
|
|
83
|
+
}, [onChange, innerNumber]);
|
|
84
|
+
const isEditing = innerType === 'number';
|
|
85
|
+
// Keep the inner value in sync with `value` and the bounds. While the user
|
|
86
|
+
// is typing, the consumer echoes the typed number back through `value`, so
|
|
87
|
+
// it is taken as is; otherwise it is clamped, and a clamp that changes it is
|
|
88
|
+
// reported through `onChange`.
|
|
54
89
|
useEffect(() => {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
90
|
+
var _a;
|
|
91
|
+
const nextNumber = isEditing ? value : clampValue(value);
|
|
92
|
+
if (nextNumber !== innerNumber) {
|
|
93
|
+
setInnerNumber(nextNumber);
|
|
58
94
|
}
|
|
59
|
-
if (
|
|
60
|
-
|
|
61
|
-
|
|
95
|
+
if (nextNumber !== value) {
|
|
96
|
+
const last = lastReportedClampRef.current;
|
|
97
|
+
if (!last || last.from !== value || last.to !== nextNumber) {
|
|
98
|
+
lastReportedClampRef.current = { from: value, to: nextNumber };
|
|
99
|
+
(_a = onChangeRef.current) === null || _a === void 0 ? void 0 : _a.call(onChangeRef, nextNumber);
|
|
100
|
+
}
|
|
62
101
|
}
|
|
63
|
-
|
|
64
|
-
|
|
102
|
+
else {
|
|
103
|
+
lastReportedClampRef.current = null;
|
|
104
|
+
}
|
|
105
|
+
// `innerNumber` is read, not tracked: the effect reacts to the consumer's
|
|
106
|
+
// value and to the bounds, not to its own updates.
|
|
107
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
108
|
+
}, [value, clampValue, isEditing]);
|
|
109
|
+
// Derived, not stored: the formatted text always matches the current number
|
|
110
|
+
// and options, even when both change in the same render. It depends on the
|
|
111
|
+
// options object itself, so a new custom `format` function is honored too;
|
|
112
|
+
// formatting is cheap and doesn't feed the synchronization effect.
|
|
113
|
+
const innerString = useMemo(() => formatValue(innerNumber, currencyOptions), [innerNumber, currencyOptions]);
|
|
114
|
+
const innerValue = useMemo(() => { var _a; return (innerType === 'number' ? (_a = innerNumber === null || innerNumber === void 0 ? void 0 : innerNumber.toString()) !== null && _a !== void 0 ? _a : '' : innerString); }, [innerType, innerNumber, innerString]);
|
|
115
|
+
const isOverMax = maxValue !== undefined && innerNumber !== undefined && innerNumber > maxValue;
|
|
116
|
+
const isUnderMin = minValue !== undefined && innerNumber !== undefined && innerNumber < minValue;
|
|
65
117
|
return {
|
|
66
118
|
inputRef,
|
|
67
119
|
innerValue,
|
|
68
120
|
innerType,
|
|
121
|
+
isOverMax,
|
|
122
|
+
isUnderMin,
|
|
69
123
|
handleOnFocus,
|
|
70
124
|
handleOnChange,
|
|
71
125
|
handleOnBlur,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useInputCurrency.js","sources":["../../../dist-transpiled/hooks/useInputCurrency.js"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState, } from 'react';\nimport currency from 'currency.js';\nimport useProvidedRefOrCreate from './useProvidedRefOrCreate';\nfunction formatValue(value, currencyOptions) {\n if (value === undefined) {\n return '';\n }\n return currency(value, Object.assign(Object.assign({}, currencyOptions), { symbol: '' })).format();\n}\nexport default function useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref, minValue, maxValue) {\n const inputRef = useProvidedRefOrCreate(ref);\n const clampValue = useCallback((newValue) => {\n if (newValue === undefined) {\n return newValue;\n }\n let clampedValue = newValue;\n if (minValue !== undefined) {\n clampedValue = Math.max(clampedValue, minValue);\n }\n if (maxValue !== undefined) {\n clampedValue = Math.min(clampedValue, maxValue);\n }\n return clampedValue;\n }, [minValue, maxValue]);\n const [innerType, setInnerType] = useState('text');\n const [innerNumber, setInnerNumber] = useState(clampValue(value));\n const [innerString, setInnerString] = useState(formatValue(clampValue(value), currencyOptions));\n const handleOnFocus = useCallback((event) => {\n event.stopPropagation();\n setInnerType('number');\n onFocus === null || onFocus === void 0 ? void 0 : onFocus(event);\n }, [onFocus]);\n const handleOnBlur = useCallback((event) => {\n event.stopPropagation();\n setInnerType('text');\n const clampedNumber = clampValue(innerNumber);\n if (clampedNumber !== innerNumber) {\n setInnerNumber(clampedNumber);\n setInnerString(formatValue(clampedNumber, currencyOptions));\n onChange === null || onChange === void 0 ? void 0 : onChange(clampedNumber);\n }\n onBlur === null || onBlur === void 0 ? void 0 : onBlur(event);\n }, [onBlur, innerNumber, clampValue, currencyOptions, onChange]);\n const handleOnChange = useCallback((newValue) => {\n const newNumber = (newValue === undefined || newValue === '') ? undefined : Number(newValue);\n if (newNumber !== innerNumber) {\n setInnerNumber(newNumber);\n setInnerString(formatValue(newNumber, currencyOptions));\n onChange === null || onChange === void 0 ? void 0 : onChange(newNumber);\n }\n }, [currencyOptions, onChange, innerNumber]);\n const isMountedRef = useRef(false);\n useEffect(() => {\n if (!isMountedRef.current) {\n isMountedRef.current = true;\n return;\n }\n if (value !== innerNumber) {\n setInnerNumber(value);\n setInnerString(formatValue(value, currencyOptions));\n }\n }, [value, currencyOptions, innerNumber]);\n const innerValue = useMemo(() => { var _a; return (innerType === 'number' ? (_a = innerNumber === null || innerNumber === void 0 ? void 0 : innerNumber.toString()) !== null && _a !== void 0 ? _a : '' : innerString !== null && innerString !== void 0 ? innerString : ''); }, [innerType, innerNumber, innerString]);\n return {\n inputRef,\n innerValue,\n innerType,\n handleOnFocus,\n handleOnChange,\n handleOnBlur,\n };\n}\n//# sourceMappingURL=useInputCurrency.js.map"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useInputCurrency.js","sources":["../../../dist-transpiled/hooks/useInputCurrency.js"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState, } from 'react';\nimport currency from 'currency.js';\nimport useProvidedRefOrCreate from './useProvidedRefOrCreate';\nfunction formatValue(value, currencyOptions) {\n if (value === undefined) {\n return '';\n }\n return currency(value, Object.assign(Object.assign({}, currencyOptions), { symbol: '' })).format();\n}\n/**\n * State and handlers for a currency input: a formatted string while the field\n * is idle and the raw number while it is being edited.\n *\n * `minValue`/`maxValue` bound the value. With `clamp` (default `true`) an\n * out-of-range value is brought into range whenever it is not being typed:\n * on mount, when `value` or the bounds change, and on blur. Every time the\n * clamp changes the value, `onChange` receives the clamped number, so the\n * input and the consumer's state never disagree. While the field is focused\n * the typed number is reported as is, so a partial entry is not rewritten.\n *\n * With `clamp: false` the value is never changed: the bounds only feed\n * `isOverMax` / `isUnderMin`, so the consumer can show its own message (e.g.\n * \"You exceeded the limit\") and keep the entered amount.\n *\n * Controlled usage follows the same contract as a native controlled input:\n * reflect `onChange` into `value` in the same event (`setState` in the\n * handler). The hook keeps showing `value` whenever it isn't being edited, so\n * a consumer can reject a change by keeping `value` as it was. Deferring the\n * update (a timer, awaiting a request) makes the hook see a stale `value` in\n * between: the field can flash the previous amount and a clamp can be\n * reported again when the deferred value arrives.\n */\nexport default function useInputCurrency(currencyOptions, value, onFocus, onChange, onBlur, ref, minValue, maxValue, clamp = true) {\n const inputRef = useProvidedRefOrCreate(ref);\n const onChangeRef = useRef(onChange);\n onChangeRef.current = onChange;\n // Last clamp reported through `onChange`, as the value it came from and the\n // value it produced. StrictMode replays effects, and a consumer may reflect\n // `onChange` asynchronously, both before `value` catches up: the same clamp\n // must not be reported twice.\n const lastReportedClampRef = useRef(null);\n const clampValue = useCallback((newValue) => {\n if (newValue === undefined || !clamp) {\n return newValue;\n }\n let clampedValue = newValue;\n if (minValue !== undefined) {\n clampedValue = Math.max(clampedValue, minValue);\n }\n if (maxValue !== undefined) {\n clampedValue = Math.min(clampedValue, maxValue);\n }\n return clampedValue;\n }, [minValue, maxValue, clamp]);\n const [innerType, setInnerType] = useState('text');\n const [innerNumber, setInnerNumber] = useState(() => clampValue(value));\n const handleOnFocus = useCallback((event) => {\n event.stopPropagation();\n setInnerType('number');\n onFocus === null || onFocus === void 0 ? void 0 : onFocus(event);\n }, [onFocus]);\n const handleOnBlur = useCallback((event) => {\n event.stopPropagation();\n setInnerType('text');\n const clampedNumber = clampValue(innerNumber);\n if (clampedNumber !== innerNumber) {\n setInnerNumber(clampedNumber);\n // Recorded before notifying, so the sync effect that runs when editing\n // ends doesn't report the same clamp again while `value` still holds the\n // typed number.\n lastReportedClampRef.current = { from: value, to: clampedNumber };\n onChange === null || onChange === void 0 ? void 0 : onChange(clampedNumber);\n }\n onBlur === null || onBlur === void 0 ? void 0 : onBlur(event);\n }, [onBlur, innerNumber, clampValue, onChange, value]);\n const handleOnChange = useCallback((newValue) => {\n const newNumber = (newValue === undefined || newValue === '') ? undefined : Number(newValue);\n if (newNumber !== innerNumber) {\n setInnerNumber(newNumber);\n onChange === null || onChange === void 0 ? void 0 : onChange(newNumber);\n }\n }, [onChange, innerNumber]);\n const isEditing = innerType === 'number';\n // Keep the inner value in sync with `value` and the bounds. While the user\n // is typing, the consumer echoes the typed number back through `value`, so\n // it is taken as is; otherwise it is clamped, and a clamp that changes it is\n // reported through `onChange`.\n useEffect(() => {\n var _a;\n const nextNumber = isEditing ? value : clampValue(value);\n if (nextNumber !== innerNumber) {\n setInnerNumber(nextNumber);\n }\n if (nextNumber !== value) {\n const last = lastReportedClampRef.current;\n if (!last || last.from !== value || last.to !== nextNumber) {\n lastReportedClampRef.current = { from: value, to: nextNumber };\n (_a = onChangeRef.current) === null || _a === void 0 ? void 0 : _a.call(onChangeRef, nextNumber);\n }\n }\n else {\n lastReportedClampRef.current = null;\n }\n // `innerNumber` is read, not tracked: the effect reacts to the consumer's\n // value and to the bounds, not to its own updates.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [value, clampValue, isEditing]);\n // Derived, not stored: the formatted text always matches the current number\n // and options, even when both change in the same render. It depends on the\n // options object itself, so a new custom `format` function is honored too;\n // formatting is cheap and doesn't feed the synchronization effect.\n const innerString = useMemo(() => formatValue(innerNumber, currencyOptions), [innerNumber, currencyOptions]);\n const innerValue = useMemo(() => { var _a; return (innerType === 'number' ? (_a = innerNumber === null || innerNumber === void 0 ? void 0 : innerNumber.toString()) !== null && _a !== void 0 ? _a : '' : innerString); }, [innerType, innerNumber, innerString]);\n const isOverMax = maxValue !== undefined && innerNumber !== undefined && innerNumber > maxValue;\n const isUnderMin = minValue !== undefined && innerNumber !== undefined && innerNumber < minValue;\n return {\n inputRef,\n innerValue,\n innerType,\n isOverMax,\n isUnderMin,\n handleOnFocus,\n handleOnChange,\n handleOnBlur,\n };\n}\n//# sourceMappingURL=useInputCurrency.js.map"],"names":[],"mappings":"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"}
|
|
@@ -1,10 +1,49 @@
|
|
|
1
1
|
import { useMemo } from 'react';
|
|
2
2
|
import { useDContext } from '../contexts/DContext.js';
|
|
3
|
+
import { PREFIX_BS } from '../components/config.js';
|
|
3
4
|
import useMediaQuery from './useMediaQuery.js';
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
const BREAKPOINTS = ['xs', 'sm', 'md', 'lg', 'xl', 'xxl'];
|
|
7
|
+
// Breakpoints read from the CSS on first use and shared by every component
|
|
8
|
+
// that resolves responsive props: one computed-style read for the page,
|
|
9
|
+
// instead of one per breakpoint per render. `dynamic-ui.css` has to be loaded
|
|
10
|
+
// before rendering; if it isn't, the empty result is kept too, so a
|
|
11
|
+
// misconfigured tree doesn't pay the read on every render (responsive props
|
|
12
|
+
// then keep their default value and useResponsiveProp warns in development).
|
|
13
|
+
let cssBreakpoints = null;
|
|
14
|
+
function readCssBreakpoint(breakpoint) {
|
|
15
|
+
if (cssBreakpoints)
|
|
16
|
+
return cssBreakpoints[breakpoint];
|
|
17
|
+
if (typeof document === 'undefined')
|
|
18
|
+
return '';
|
|
19
|
+
const style = getComputedStyle(document.documentElement);
|
|
20
|
+
const read = Object.fromEntries(BREAKPOINTS.map((name) => [
|
|
21
|
+
name,
|
|
22
|
+
style.getPropertyValue(`--${PREFIX_BS}breakpoint-${name}`).trim(),
|
|
23
|
+
]));
|
|
24
|
+
cssBreakpoints = read;
|
|
25
|
+
return read[breakpoint];
|
|
26
|
+
}
|
|
27
|
+
/** Clears the cached CSS breakpoints. Only meant for tests. */
|
|
28
|
+
function resetCssBreakpointsCache() {
|
|
29
|
+
cssBreakpoints = null;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Pixel value of a breakpoint. `DContextProvider` reads them from the CSS and
|
|
33
|
+
* shares them through the context; outside of it (a tree without the
|
|
34
|
+
* provider, or its first render) they are read from the same CSS variables
|
|
35
|
+
* here, so responsive props resolve either way.
|
|
36
|
+
*/
|
|
37
|
+
function useBreakpointValue(breakpoint) {
|
|
6
38
|
const { breakpoints } = useDContext();
|
|
7
|
-
const
|
|
39
|
+
const fromContext = breakpoints[breakpoint];
|
|
40
|
+
return useMemo(() => fromContext || readCssBreakpoint(breakpoint), [fromContext, breakpoint]);
|
|
41
|
+
}
|
|
42
|
+
function useMediaBreakpointUp(breakpoint, useListener = false) {
|
|
43
|
+
const value = useBreakpointValue(breakpoint);
|
|
44
|
+
// Without a breakpoint the query can't match; `not all` keeps it false
|
|
45
|
+
// instead of an invalid `(min-width: )`.
|
|
46
|
+
const mediaQuery = value ? `(min-width: ${value})` : 'not all';
|
|
8
47
|
return useMediaQuery(mediaQuery, useListener);
|
|
9
48
|
}
|
|
10
49
|
function useMediaBreakpointUpXs(useListener = false) {
|
|
@@ -26,5 +65,5 @@ function useMediaBreakpointUpXxl(useListener = false) {
|
|
|
26
65
|
return useMediaBreakpointUp('xxl', useListener);
|
|
27
66
|
}
|
|
28
67
|
|
|
29
|
-
export { useMediaBreakpointUpLg, useMediaBreakpointUpMd, useMediaBreakpointUpSm, useMediaBreakpointUpXl, useMediaBreakpointUpXs, useMediaBreakpointUpXxl };
|
|
68
|
+
export { resetCssBreakpointsCache, useBreakpointValue, useMediaBreakpointUpLg, useMediaBreakpointUpMd, useMediaBreakpointUpSm, useMediaBreakpointUpXl, useMediaBreakpointUpXs, useMediaBreakpointUpXxl };
|
|
30
69
|
//# sourceMappingURL=useMediaBreakpointUp.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useMediaBreakpointUp.js","sources":["../../../dist-transpiled/hooks/useMediaBreakpointUp.js"],"sourcesContent":["import { useMemo } from 'react';\nimport { useDContext } from '../contexts/DContext';\nimport useMediaQuery from './useMediaQuery';\nfunction
|
|
1
|
+
{"version":3,"file":"useMediaBreakpointUp.js","sources":["../../../dist-transpiled/hooks/useMediaBreakpointUp.js"],"sourcesContent":["import { useMemo } from 'react';\nimport { useDContext } from '../contexts/DContext';\nimport { PREFIX_BS } from '../components/config';\nimport useMediaQuery from './useMediaQuery';\nconst BREAKPOINTS = ['xs', 'sm', 'md', 'lg', 'xl', 'xxl'];\n// Breakpoints read from the CSS on first use and shared by every component\n// that resolves responsive props: one computed-style read for the page,\n// instead of one per breakpoint per render. `dynamic-ui.css` has to be loaded\n// before rendering; if it isn't, the empty result is kept too, so a\n// misconfigured tree doesn't pay the read on every render (responsive props\n// then keep their default value and useResponsiveProp warns in development).\nlet cssBreakpoints = null;\nfunction readCssBreakpoint(breakpoint) {\n if (cssBreakpoints)\n return cssBreakpoints[breakpoint];\n if (typeof document === 'undefined')\n return '';\n const style = getComputedStyle(document.documentElement);\n const read = Object.fromEntries(BREAKPOINTS.map((name) => [\n name,\n style.getPropertyValue(`--${PREFIX_BS}breakpoint-${name}`).trim(),\n ]));\n cssBreakpoints = read;\n return read[breakpoint];\n}\n/** Clears the cached CSS breakpoints. Only meant for tests. */\nexport function resetCssBreakpointsCache() {\n cssBreakpoints = null;\n}\n/**\n * Pixel value of a breakpoint. `DContextProvider` reads them from the CSS and\n * shares them through the context; outside of it (a tree without the\n * provider, or its first render) they are read from the same CSS variables\n * here, so responsive props resolve either way.\n */\nexport function useBreakpointValue(breakpoint) {\n const { breakpoints } = useDContext();\n const fromContext = breakpoints[breakpoint];\n return useMemo(() => fromContext || readCssBreakpoint(breakpoint), [fromContext, breakpoint]);\n}\nfunction useMediaBreakpointUp(breakpoint, useListener = false) {\n const value = useBreakpointValue(breakpoint);\n // Without a breakpoint the query can't match; `not all` keeps it false\n // instead of an invalid `(min-width: )`.\n const mediaQuery = value ? `(min-width: ${value})` : 'not all';\n return useMediaQuery(mediaQuery, useListener);\n}\nexport function useMediaBreakpointUpXs(useListener = false) {\n return useMediaBreakpointUp('xs', useListener);\n}\nexport function useMediaBreakpointUpSm(useListener = false) {\n return useMediaBreakpointUp('sm', useListener);\n}\nexport function useMediaBreakpointUpMd(useListener = false) {\n return useMediaBreakpointUp('md', useListener);\n}\nexport function useMediaBreakpointUpLg(useListener = false) {\n return useMediaBreakpointUp('lg', useListener);\n}\nexport function useMediaBreakpointUpXl(useListener = false) {\n return useMediaBreakpointUp('xl', useListener);\n}\nexport function useMediaBreakpointUpXxl(useListener = false) {\n return useMediaBreakpointUp('xxl', useListener);\n}\n//# sourceMappingURL=useMediaBreakpointUp.js.map"],"names":[],"mappings":";;;;;AAIA,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC;AACzD;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,cAAc,GAAG,IAAI;AACzB,SAAS,iBAAiB,CAAC,UAAU,EAAE;AACvC,IAAI,IAAI,cAAc;AACtB,QAAQ,OAAO,cAAc,CAAC,UAAU,CAAC;AACzC,IAAI,IAAI,OAAO,QAAQ,KAAK,WAAW;AACvC,QAAQ,OAAO,EAAE;AACjB,IAAI,MAAM,KAAK,GAAG,gBAAgB,CAAC,QAAQ,CAAC,eAAe,CAAC;AAC5D,IAAI,MAAM,IAAI,GAAG,MAAM,CAAC,WAAW,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK;AAC9D,QAAQ,IAAI;AACZ,QAAQ,KAAK,CAAC,gBAAgB,CAAC,CAAC,EAAE,EAAE,SAAS,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE;AACzE,KAAK,CAAC,CAAC;AACP,IAAI,cAAc,GAAG,IAAI;AACzB,IAAI,OAAO,IAAI,CAAC,UAAU,CAAC;AAC3B;AACA;AACO,SAAS,wBAAwB,GAAG;AAC3C,IAAI,cAAc,GAAG,IAAI;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,kBAAkB,CAAC,UAAU,EAAE;AAC/C,IAAI,MAAM,EAAE,WAAW,EAAE,GAAG,WAAW,EAAE;AACzC,IAAI,MAAM,WAAW,GAAG,WAAW,CAAC,UAAU,CAAC;AAC/C,IAAI,OAAO,OAAO,CAAC,MAAM,WAAW,IAAI,iBAAiB,CAAC,UAAU,CAAC,EAAE,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC;AACjG;AACA,SAAS,oBAAoB,CAAC,UAAU,EAAE,WAAW,GAAG,KAAK,EAAE;AAC/D,IAAI,MAAM,KAAK,GAAG,kBAAkB,CAAC,UAAU,CAAC;AAChD;AACA;AACA,IAAI,MAAM,UAAU,GAAG,KAAK,GAAG,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,SAAS;AAClE,IAAI,OAAO,aAAa,CAAC,UAAU,EAAE,WAAW,CAAC;AACjD;AACO,SAAS,sBAAsB,CAAC,WAAW,GAAG,KAAK,EAAE;AAC5D,IAAI,OAAO,oBAAoB,CAAC,IAAI,EAAE,WAAW,CAAC;AAClD;AACO,SAAS,sBAAsB,CAAC,WAAW,GAAG,KAAK,EAAE;AAC5D,IAAI,OAAO,oBAAoB,CAAC,IAAI,EAAE,WAAW,CAAC;AAClD;AACO,SAAS,sBAAsB,CAAC,WAAW,GAAG,KAAK,EAAE;AAC5D,IAAI,OAAO,oBAAoB,CAAC,IAAI,EAAE,WAAW,CAAC;AAClD;AACO,SAAS,sBAAsB,CAAC,WAAW,GAAG,KAAK,EAAE;AAC5D,IAAI,OAAO,oBAAoB,CAAC,IAAI,EAAE,WAAW,CAAC;AAClD;AACO,SAAS,sBAAsB,CAAC,WAAW,GAAG,KAAK,EAAE;AAC5D,IAAI,OAAO,oBAAoB,CAAC,IAAI,EAAE,WAAW,CAAC;AAClD;AACO,SAAS,uBAAuB,CAAC,WAAW,GAAG,KAAK,EAAE;AAC7D,IAAI,OAAO,oBAAoB,CAAC,KAAK,EAAE,WAAW,CAAC;AACnD;;;;"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { useCallback } from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { PREFIX_BS } from '../components/config.js';
|
|
3
|
+
import { useMediaBreakpointUpXs, useMediaBreakpointUpSm, useMediaBreakpointUpMd, useMediaBreakpointUpLg, useMediaBreakpointUpXl, useMediaBreakpointUpXxl, useBreakpointValue } from './useMediaBreakpointUp.js';
|
|
3
4
|
|
|
5
|
+
let warnedMissingBreakpoints = false;
|
|
4
6
|
/**
|
|
5
7
|
* React hook to resolve a responsive property value based on the current viewport breakpoint.
|
|
6
8
|
*
|
|
@@ -26,7 +28,16 @@ function useResponsiveProp(useListener = false) {
|
|
|
26
28
|
const bpLgUp = useMediaBreakpointUpLg(useListener);
|
|
27
29
|
const bpXlUp = useMediaBreakpointUpXl(useListener);
|
|
28
30
|
const bpXxlUp = useMediaBreakpointUpXxl(useListener);
|
|
31
|
+
// `xs` is `0` and can't tell a missing variable apart, so `sm` is checked.
|
|
32
|
+
const hasBreakpoints = !!useBreakpointValue('sm');
|
|
29
33
|
const responsivePropValue = useCallback((prop) => {
|
|
34
|
+
if (process.env.NODE_ENV !== 'production' && !hasBreakpoints && !warnedMissingBreakpoints) {
|
|
35
|
+
warnedMissingBreakpoints = true;
|
|
36
|
+
// eslint-disable-next-line no-console
|
|
37
|
+
console.warn(`[Dynamic UI] The --${PREFIX_BS}breakpoint-* CSS variables are not available, so a `
|
|
38
|
+
+ 'responsive prop (an object by breakpoint) falls back to its default value. Load '
|
|
39
|
+
+ 'dynamic-ui.css before rendering. It never appears in production builds.');
|
|
40
|
+
}
|
|
30
41
|
// Pick the highest matched breakpoint value that is defined in prop
|
|
31
42
|
if (prop.xxl !== undefined && bpXxlUp)
|
|
32
43
|
return prop.xxl;
|
|
@@ -42,7 +53,7 @@ function useResponsiveProp(useListener = false) {
|
|
|
42
53
|
return prop.xs;
|
|
43
54
|
// Fallback: return undefined if no breakpoint matches
|
|
44
55
|
return undefined;
|
|
45
|
-
}, [bpSmUp, bpMdUp, bpLgUp, bpXlUp, bpXxlUp, bpXsUp]);
|
|
56
|
+
}, [bpSmUp, bpMdUp, bpLgUp, bpXlUp, bpXxlUp, bpXsUp, hasBreakpoints]);
|
|
46
57
|
return { responsivePropValue };
|
|
47
58
|
}
|
|
48
59
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useResponsiveProp.js","sources":["../../../dist-transpiled/hooks/useResponsiveProp.js"],"sourcesContent":["import { useCallback } from 'react';\nimport { useMediaBreakpointUpLg, useMediaBreakpointUpMd, useMediaBreakpointUpSm, useMediaBreakpointUpXl, useMediaBreakpointUpXs, useMediaBreakpointUpXxl, } from './useMediaBreakpointUp';\n/**\n * React hook to resolve a responsive property value based on the current viewport breakpoint.\n *\n * Given a `ResponsiveProp` object, this hook returns the value for the highest matching breakpoint.\n * If multiple breakpoints match, the value for the largest (highest) breakpoint is used.\n * If no breakpoints match, `undefined` is returned.\n *\n * @param useListener - Whether to listen for breakpoint changes (default: false).\n * @returns An object with a `responsivePropValue` function that takes a\n * `ResponsiveProp` and returns the resolved value.\n *\n * Usage example:\n * ```ts\n * const { responsivePropValue } = useResponsiveProp();\n * const value = responsivePropValue({ xs: \"A\", md: \"B\", xl: \"C\" });\n * // value will be \"C\" if xl breakpoint is active, \"B\" if md is active, etc.\n * ```\n */\nexport function useResponsiveProp(useListener = false) {\n const bpXsUp = useMediaBreakpointUpXs(useListener);\n const bpSmUp = useMediaBreakpointUpSm(useListener);\n const bpMdUp = useMediaBreakpointUpMd(useListener);\n const bpLgUp = useMediaBreakpointUpLg(useListener);\n const bpXlUp = useMediaBreakpointUpXl(useListener);\n const bpXxlUp = useMediaBreakpointUpXxl(useListener);\n const responsivePropValue = useCallback((prop) => {\n // Pick the highest matched breakpoint value that is defined in prop\n if (prop.xxl !== undefined && bpXxlUp)\n return prop.xxl;\n if (prop.xl !== undefined && bpXlUp)\n return prop.xl;\n if (prop.lg !== undefined && bpLgUp)\n return prop.lg;\n if (prop.md !== undefined && bpMdUp)\n return prop.md;\n if (prop.sm !== undefined && bpSmUp)\n return prop.sm;\n if (prop.xs !== undefined && bpXsUp)\n return prop.xs;\n // Fallback: return undefined if no breakpoint matches\n return undefined;\n }, [bpSmUp, bpMdUp, bpLgUp, bpXlUp, bpXxlUp, bpXsUp]);\n return { responsivePropValue };\n}\n//# sourceMappingURL=useResponsiveProp.js.map"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useResponsiveProp.js","sources":["../../../dist-transpiled/hooks/useResponsiveProp.js"],"sourcesContent":["import { useCallback } from 'react';\nimport { PREFIX_BS } from '../components/config';\nimport { useBreakpointValue, useMediaBreakpointUpLg, useMediaBreakpointUpMd, useMediaBreakpointUpSm, useMediaBreakpointUpXl, useMediaBreakpointUpXs, useMediaBreakpointUpXxl, } from './useMediaBreakpointUp';\nlet warnedMissingBreakpoints = false;\n/**\n * React hook to resolve a responsive property value based on the current viewport breakpoint.\n *\n * Given a `ResponsiveProp` object, this hook returns the value for the highest matching breakpoint.\n * If multiple breakpoints match, the value for the largest (highest) breakpoint is used.\n * If no breakpoints match, `undefined` is returned.\n *\n * @param useListener - Whether to listen for breakpoint changes (default: false).\n * @returns An object with a `responsivePropValue` function that takes a\n * `ResponsiveProp` and returns the resolved value.\n *\n * Usage example:\n * ```ts\n * const { responsivePropValue } = useResponsiveProp();\n * const value = responsivePropValue({ xs: \"A\", md: \"B\", xl: \"C\" });\n * // value will be \"C\" if xl breakpoint is active, \"B\" if md is active, etc.\n * ```\n */\nexport function useResponsiveProp(useListener = false) {\n const bpXsUp = useMediaBreakpointUpXs(useListener);\n const bpSmUp = useMediaBreakpointUpSm(useListener);\n const bpMdUp = useMediaBreakpointUpMd(useListener);\n const bpLgUp = useMediaBreakpointUpLg(useListener);\n const bpXlUp = useMediaBreakpointUpXl(useListener);\n const bpXxlUp = useMediaBreakpointUpXxl(useListener);\n // `xs` is `0` and can't tell a missing variable apart, so `sm` is checked.\n const hasBreakpoints = !!useBreakpointValue('sm');\n const responsivePropValue = useCallback((prop) => {\n if (process.env.NODE_ENV !== 'production' && !hasBreakpoints && !warnedMissingBreakpoints) {\n warnedMissingBreakpoints = true;\n // eslint-disable-next-line no-console\n console.warn(`[Dynamic UI] The --${PREFIX_BS}breakpoint-* CSS variables are not available, so a `\n + 'responsive prop (an object by breakpoint) falls back to its default value. Load '\n + 'dynamic-ui.css before rendering. It never appears in production builds.');\n }\n // Pick the highest matched breakpoint value that is defined in prop\n if (prop.xxl !== undefined && bpXxlUp)\n return prop.xxl;\n if (prop.xl !== undefined && bpXlUp)\n return prop.xl;\n if (prop.lg !== undefined && bpLgUp)\n return prop.lg;\n if (prop.md !== undefined && bpMdUp)\n return prop.md;\n if (prop.sm !== undefined && bpSmUp)\n return prop.sm;\n if (prop.xs !== undefined && bpXsUp)\n return prop.xs;\n // Fallback: return undefined if no breakpoint matches\n return undefined;\n }, [bpSmUp, bpMdUp, bpLgUp, bpXlUp, bpXxlUp, bpXsUp, hasBreakpoints]);\n return { responsivePropValue };\n}\n//# sourceMappingURL=useResponsiveProp.js.map"],"names":[],"mappings":";;;;AAGA,IAAI,wBAAwB,GAAG,KAAK;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,iBAAiB,CAAC,WAAW,GAAG,KAAK,EAAE;AACvD,IAAI,MAAM,MAAM,GAAG,sBAAsB,CAAC,WAAW,CAAC;AACtD,IAAI,MAAM,MAAM,GAAG,sBAAsB,CAAC,WAAW,CAAC;AACtD,IAAI,MAAM,MAAM,GAAG,sBAAsB,CAAC,WAAW,CAAC;AACtD,IAAI,MAAM,MAAM,GAAG,sBAAsB,CAAC,WAAW,CAAC;AACtD,IAAI,MAAM,MAAM,GAAG,sBAAsB,CAAC,WAAW,CAAC;AACtD,IAAI,MAAM,OAAO,GAAG,uBAAuB,CAAC,WAAW,CAAC;AACxD;AACA,IAAI,MAAM,cAAc,GAAG,CAAC,CAAC,kBAAkB,CAAC,IAAI,CAAC;AACrD,IAAI,MAAM,mBAAmB,GAAG,WAAW,CAAC,CAAC,IAAI,KAAK;AACtD,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,IAAI,CAAC,cAAc,IAAI,CAAC,wBAAwB,EAAE;AACnG,YAAY,wBAAwB,GAAG,IAAI;AAC3C;AACA,YAAY,OAAO,CAAC,IAAI,CAAC,CAAC,mBAAmB,EAAE,SAAS,CAAC,mDAAmD;AAC5G,kBAAkB;AAClB,kBAAkB,yEAAyE,CAAC;AAC5F,QAAQ;AACR;AACA,QAAQ,IAAI,IAAI,CAAC,GAAG,KAAK,SAAS,IAAI,OAAO;AAC7C,YAAY,OAAO,IAAI,CAAC,GAAG;AAC3B,QAAQ,IAAI,IAAI,CAAC,EAAE,KAAK,SAAS,IAAI,MAAM;AAC3C,YAAY,OAAO,IAAI,CAAC,EAAE;AAC1B,QAAQ,IAAI,IAAI,CAAC,EAAE,KAAK,SAAS,IAAI,MAAM;AAC3C,YAAY,OAAO,IAAI,CAAC,EAAE;AAC1B,QAAQ,IAAI,IAAI,CAAC,EAAE,KAAK,SAAS,IAAI,MAAM;AAC3C,YAAY,OAAO,IAAI,CAAC,EAAE;AAC1B,QAAQ,IAAI,IAAI,CAAC,EAAE,KAAK,SAAS,IAAI,MAAM;AAC3C,YAAY,OAAO,IAAI,CAAC,EAAE;AAC1B,QAAQ,IAAI,IAAI,CAAC,EAAE,KAAK,SAAS,IAAI,MAAM;AAC3C,YAAY,OAAO,IAAI,CAAC,EAAE;AAC1B;AACA,QAAQ,OAAO,SAAS;AACxB,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC;AACzE,IAAI,OAAO,EAAE,mBAAmB,EAAE;AAClC;;;;"}
|
package/dist/esm/index.js
CHANGED
|
@@ -60,6 +60,7 @@ export { default as DStepperDesktop } from './components/DStepperDesktop/DSteppe
|
|
|
60
60
|
export { default as DStepperMobile } from './components/DStepperMobile/DStepperMobile.js';
|
|
61
61
|
export { default as DTabContent } from './components/DTabs/components/DTabContent.js';
|
|
62
62
|
export { default as DTabs } from './components/DTabs/DTabs.js';
|
|
63
|
+
export { default as DTabsProvider } from './components/DTabs/components/DTabsProvider.js';
|
|
63
64
|
export { useTabContext } from './components/DTabs/TabContext.js';
|
|
64
65
|
export { default as DTimeline } from './components/DTimeline/DTimeline.js';
|
|
65
66
|
export { default as DToast } from './components/DToast/DToast.js';
|
package/dist/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { isValidElement } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Whether a `label` node has something to render.
|
|
5
|
+
*
|
|
6
|
+
* The render paths cannot test `label` for truthiness: since the prop widened
|
|
7
|
+
* to `ReactNode` it also accepts `0`, which React renders as the text "0" and
|
|
8
|
+
* which `isTextLabel` counts as a text label, yet which a truthiness check
|
|
9
|
+
* would drop — leaving the control visibly unlabelled.
|
|
10
|
+
*
|
|
11
|
+
* Absent (`undefined`, `null`), empty (`''`) and boolean labels render nothing,
|
|
12
|
+
* so they are the ones that stand for "no label". The empty string matters
|
|
13
|
+
* because it is the default several components fall back to.
|
|
14
|
+
*
|
|
15
|
+
* Arrays are inspected rather than taken at face value: `label={items.map(...)}`
|
|
16
|
+
* over an empty list yields `[]`, while a list of skipped entries yields
|
|
17
|
+
* `[false, null]`. Both render nothing, so treating them as content would leave
|
|
18
|
+
* an empty `<label>` on the page — and make the naming warning fire over a
|
|
19
|
+
* label that was never there.
|
|
20
|
+
*
|
|
21
|
+
* Only arrays, though, even though `ReactNode` admits any iterable. Reading a
|
|
22
|
+
* one-shot iterator — a generator — would exhaust it here, and the component
|
|
23
|
+
* goes on to render the same value, so the label would vanish precisely because
|
|
24
|
+
* we checked for it. An array can be walked as often as needed; anything else
|
|
25
|
+
* is taken on faith.
|
|
26
|
+
*
|
|
27
|
+
* Anything else — an element, a portal, a generator — counts as content: what
|
|
28
|
+
* it renders is only knowable by rendering it, and an element that draws
|
|
29
|
+
* nothing visible is the consumer's decision rather than an absent label.
|
|
30
|
+
*/
|
|
31
|
+
function hasLabelContent(label) {
|
|
32
|
+
if (label === undefined || label === null || typeof label === 'boolean') {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
if (typeof label === 'string') {
|
|
36
|
+
return label !== '';
|
|
37
|
+
}
|
|
38
|
+
if (isValidElement(label)) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
if (Array.isArray(label)) {
|
|
42
|
+
return label.some(hasLabelContent);
|
|
43
|
+
}
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export { hasLabelContent as default };
|
|
48
|
+
//# sourceMappingURL=hasLabelContent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hasLabelContent.js","sources":["../../../dist-transpiled/utils/hasLabelContent.js"],"sourcesContent":["import { isValidElement } from 'react';\n/**\n * Whether a `label` node has something to render.\n *\n * The render paths cannot test `label` for truthiness: since the prop widened\n * to `ReactNode` it also accepts `0`, which React renders as the text \"0\" and\n * which `isTextLabel` counts as a text label, yet which a truthiness check\n * would drop — leaving the control visibly unlabelled.\n *\n * Absent (`undefined`, `null`), empty (`''`) and boolean labels render nothing,\n * so they are the ones that stand for \"no label\". The empty string matters\n * because it is the default several components fall back to.\n *\n * Arrays are inspected rather than taken at face value: `label={items.map(...)}`\n * over an empty list yields `[]`, while a list of skipped entries yields\n * `[false, null]`. Both render nothing, so treating them as content would leave\n * an empty `<label>` on the page — and make the naming warning fire over a\n * label that was never there.\n *\n * Only arrays, though, even though `ReactNode` admits any iterable. Reading a\n * one-shot iterator — a generator — would exhaust it here, and the component\n * goes on to render the same value, so the label would vanish precisely because\n * we checked for it. An array can be walked as often as needed; anything else\n * is taken on faith.\n *\n * Anything else — an element, a portal, a generator — counts as content: what\n * it renders is only knowable by rendering it, and an element that draws\n * nothing visible is the consumer's decision rather than an absent label.\n */\nexport default function hasLabelContent(label) {\n if (label === undefined || label === null || typeof label === 'boolean') {\n return false;\n }\n if (typeof label === 'string') {\n return label !== '';\n }\n if (isValidElement(label)) {\n return true;\n }\n if (Array.isArray(label)) {\n return label.some(hasLabelContent);\n }\n return true;\n}\n//# sourceMappingURL=hasLabelContent.js.map"],"names":[],"mappings":";;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACe,SAAS,eAAe,CAAC,KAAK,EAAE;AAC/C,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,SAAS,EAAE;AAC7E,QAAQ,OAAO,KAAK;AACpB,IAAI;AACJ,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;AACnC,QAAQ,OAAO,KAAK,KAAK,EAAE;AAC3B,IAAI;AACJ,IAAI,IAAI,cAAc,CAAC,KAAK,CAAC,EAAE;AAC/B,QAAQ,OAAO,IAAI;AACnB,IAAI;AACJ,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;AAC9B,QAAQ,OAAO,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC;AAC1C,IAAI;AACJ,IAAI,OAAO,IAAI;AACf;;;;"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Narrows a `label` node to the plain-text case.
|
|
3
|
+
*
|
|
4
|
+
* `label` accepts any `ReactNode` so patterns like a terms-and-conditions link
|
|
5
|
+
* or an info trigger can live next to the field name. Anything that is not text
|
|
6
|
+
* cannot be forwarded to a string-only attribute (`aria-label`, `placeholder`)
|
|
7
|
+
* nor laid out by Bootstrap's `form-floating`, so those paths check this first.
|
|
8
|
+
*
|
|
9
|
+
* Numbers and bigints count as text: React renders them as their string form
|
|
10
|
+
* and so does `String()`, which is what the string-only attributes end up
|
|
11
|
+
* receiving. `bigint` is part of `ReactNode` under these React types, so
|
|
12
|
+
* leaving it out made a label React draws as text get treated as a rich node.
|
|
13
|
+
* React 18 cannot render one, which `DFormLabel` handles by rendering its
|
|
14
|
+
* string form — the classification stays true for every supported React.
|
|
15
|
+
*/
|
|
16
|
+
function isTextLabel(label) {
|
|
17
|
+
return typeof label === 'string'
|
|
18
|
+
|| typeof label === 'number'
|
|
19
|
+
|| typeof label === 'bigint';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export { isTextLabel as default };
|
|
23
|
+
//# sourceMappingURL=isTextLabel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"isTextLabel.js","sources":["../../../dist-transpiled/utils/isTextLabel.js"],"sourcesContent":["/**\n * Narrows a `label` node to the plain-text case.\n *\n * `label` accepts any `ReactNode` so patterns like a terms-and-conditions link\n * or an info trigger can live next to the field name. Anything that is not text\n * cannot be forwarded to a string-only attribute (`aria-label`, `placeholder`)\n * nor laid out by Bootstrap's `form-floating`, so those paths check this first.\n *\n * Numbers and bigints count as text: React renders them as their string form\n * and so does `String()`, which is what the string-only attributes end up\n * receiving. `bigint` is part of `ReactNode` under these React types, so\n * leaving it out made a label React draws as text get treated as a rich node.\n * React 18 cannot render one, which `DFormLabel` handles by rendering its\n * string form — the classification stays true for every supported React.\n */\nexport default function isTextLabel(label) {\n return typeof label === 'string'\n || typeof label === 'number'\n || typeof label === 'bigint';\n}\n//# sourceMappingURL=isTextLabel.js.map"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACe,SAAS,WAAW,CAAC,KAAK,EAAE;AAC3C,IAAI,OAAO,OAAO,KAAK,KAAK;AAC5B,WAAW,OAAO,KAAK,KAAK;AAC5B,WAAW,OAAO,KAAK,KAAK,QAAQ;AACpC;;;;"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Descendants the browser already exempts from a label's click forwarding.
|
|
3
|
+
*
|
|
4
|
+
* Per the HTML spec a label does nothing for events targeted at its interactive
|
|
5
|
+
* content descendants, so these need — and must get — no help from us: calling
|
|
6
|
+
* `preventDefault` on a click targeting a link, or on an image map area, would
|
|
7
|
+
* cancel the navigation it was meant to perform.
|
|
8
|
+
*
|
|
9
|
+
* The list mirrors the spec's interactive content, image maps included, since
|
|
10
|
+
* one of those carrying a `tabindex` would otherwise fall through to the
|
|
11
|
+
* pseudo-interactive branch below and be suppressed by mistake.
|
|
12
|
+
*/
|
|
13
|
+
const NATIVE_INTERACTIVE = 'a[href], area[href], button, input, select, textarea, details, summary, img[usemap], object[usemap], audio[controls], video[controls], embed, iframe';
|
|
14
|
+
/**
|
|
15
|
+
* Descendants that act like controls without being interactive content.
|
|
16
|
+
*
|
|
17
|
+
* A tooltip or modal trigger built from a `span` with a role and a tabindex is
|
|
18
|
+
* the common case. Browsers do not exempt these, so a click on one activates
|
|
19
|
+
* the labelled control as well.
|
|
20
|
+
*/
|
|
21
|
+
const PSEUDO_INTERACTIVE = '[role="button"], [role="link"], [role="checkbox"], [role="switch"], [role="menuitem"], [tabindex]';
|
|
22
|
+
/**
|
|
23
|
+
* Stops a click on a non-native trigger inside a `<label>` from also activating
|
|
24
|
+
* the labelled control.
|
|
25
|
+
*
|
|
26
|
+
* Verified against Chromium, which forwards the click for every one of the
|
|
27
|
+
* `PSEUDO_INTERACTIVE` selectors above while exempting the native ones. Note
|
|
28
|
+
* that jsdom does not reproduce this: its `isInteractiveContent` counts any
|
|
29
|
+
* element carrying a `tabindex` as interactive content, so a jsdom test passes
|
|
30
|
+
* whether or not this guard exists. That is why the guard is unit-tested
|
|
31
|
+
* directly rather than through a rendered component.
|
|
32
|
+
*
|
|
33
|
+
* Native interactive content returns early rather than falling through, since
|
|
34
|
+
* `preventDefault` on those clicks would cancel the link navigation or the
|
|
35
|
+
* image map the label is meant to let through.
|
|
36
|
+
*
|
|
37
|
+
* `DFormLabel` wires this in the capture phase so a trigger that stops
|
|
38
|
+
* propagation cannot skip it.
|
|
39
|
+
*/
|
|
40
|
+
function labelClickGuard(event) {
|
|
41
|
+
const label = event.currentTarget;
|
|
42
|
+
const target = event.target;
|
|
43
|
+
if (!(target === null || target === void 0 ? void 0 : target.closest))
|
|
44
|
+
return;
|
|
45
|
+
/**
|
|
46
|
+
* `closest` walks the whole ancestor chain, so a match may sit above the
|
|
47
|
+
* label — a card with a `role` or a `tabindex` wrapping the control and its
|
|
48
|
+
* label, say. Acting on one of those would suppress the plain label text's
|
|
49
|
+
* own click and leave the control unresponsive, so only matches inside the
|
|
50
|
+
* label count.
|
|
51
|
+
*/
|
|
52
|
+
const inside = (match) => (!!match && match !== label && label.contains(match));
|
|
53
|
+
if (inside(target.closest(NATIVE_INTERACTIVE)))
|
|
54
|
+
return;
|
|
55
|
+
if (inside(target.closest(PSEUDO_INTERACTIVE))) {
|
|
56
|
+
event.preventDefault();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export { labelClickGuard as default };
|
|
61
|
+
//# sourceMappingURL=labelClickGuard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"labelClickGuard.js","sources":["../../../dist-transpiled/utils/labelClickGuard.js"],"sourcesContent":["/**\n * Descendants the browser already exempts from a label's click forwarding.\n *\n * Per the HTML spec a label does nothing for events targeted at its interactive\n * content descendants, so these need — and must get — no help from us: calling\n * `preventDefault` on a click targeting a link, or on an image map area, would\n * cancel the navigation it was meant to perform.\n *\n * The list mirrors the spec's interactive content, image maps included, since\n * one of those carrying a `tabindex` would otherwise fall through to the\n * pseudo-interactive branch below and be suppressed by mistake.\n */\nconst NATIVE_INTERACTIVE = 'a[href], area[href], button, input, select, textarea, details, summary, img[usemap], object[usemap], audio[controls], video[controls], embed, iframe';\n/**\n * Descendants that act like controls without being interactive content.\n *\n * A tooltip or modal trigger built from a `span` with a role and a tabindex is\n * the common case. Browsers do not exempt these, so a click on one activates\n * the labelled control as well.\n */\nconst PSEUDO_INTERACTIVE = '[role=\"button\"], [role=\"link\"], [role=\"checkbox\"], [role=\"switch\"], [role=\"menuitem\"], [tabindex]';\n/**\n * Stops a click on a non-native trigger inside a `<label>` from also activating\n * the labelled control.\n *\n * Verified against Chromium, which forwards the click for every one of the\n * `PSEUDO_INTERACTIVE` selectors above while exempting the native ones. Note\n * that jsdom does not reproduce this: its `isInteractiveContent` counts any\n * element carrying a `tabindex` as interactive content, so a jsdom test passes\n * whether or not this guard exists. That is why the guard is unit-tested\n * directly rather than through a rendered component.\n *\n * Native interactive content returns early rather than falling through, since\n * `preventDefault` on those clicks would cancel the link navigation or the\n * image map the label is meant to let through.\n *\n * `DFormLabel` wires this in the capture phase so a trigger that stops\n * propagation cannot skip it.\n */\nexport default function labelClickGuard(event) {\n const label = event.currentTarget;\n const target = event.target;\n if (!(target === null || target === void 0 ? void 0 : target.closest))\n return;\n /**\n * `closest` walks the whole ancestor chain, so a match may sit above the\n * label — a card with a `role` or a `tabindex` wrapping the control and its\n * label, say. Acting on one of those would suppress the plain label text's\n * own click and leave the control unresponsive, so only matches inside the\n * label count.\n */\n const inside = (match) => (!!match && match !== label && label.contains(match));\n if (inside(target.closest(NATIVE_INTERACTIVE)))\n return;\n if (inside(target.closest(PSEUDO_INTERACTIVE))) {\n event.preventDefault();\n }\n}\n//# sourceMappingURL=labelClickGuard.js.map"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM,kBAAkB,GAAG,sJAAsJ;AACjL;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM,kBAAkB,GAAG,mGAAmG;AAC9H;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACe,SAAS,eAAe,CAAC,KAAK,EAAE;AAC/C,IAAI,MAAM,KAAK,GAAG,KAAK,CAAC,aAAa;AACrC,IAAI,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM;AAC/B,IAAI,IAAI,EAAE,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC;AACzE,QAAQ;AACR;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,MAAM,MAAM,GAAG,CAAC,KAAK,MAAM,CAAC,CAAC,KAAK,IAAI,KAAK,KAAK,KAAK,IAAI,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACnF,IAAI,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC;AAClD,QAAQ;AACR,IAAI,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC,EAAE;AACpD,QAAQ,KAAK,CAAC,cAAc,EAAE;AAC9B,IAAI;AACJ;;;;"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import hasLabelContent from './hasLabelContent.js';
|
|
2
|
+
import isTextLabel from './isTextLabel.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Components already reported, keyed by component and reason, so the same
|
|
6
|
+
* mistake only warns once per page load however often it re-renders or however
|
|
7
|
+
* many instances exist.
|
|
8
|
+
*/
|
|
9
|
+
const warnedLabels = new Set();
|
|
10
|
+
function warnOnce(key, message) {
|
|
11
|
+
if (warnedLabels.has(key))
|
|
12
|
+
return;
|
|
13
|
+
warnedLabels.add(key);
|
|
14
|
+
// eslint-disable-next-line no-console
|
|
15
|
+
console.warn(`[Dynamic UI] ${message} It never appears in production builds.`);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Warns, only outside production builds, about the two ways a non-text `label`
|
|
19
|
+
* goes wrong.
|
|
20
|
+
*
|
|
21
|
+
* A text label doubles as the control's accessible name. A `ReactNode` one does
|
|
22
|
+
* not: the name becomes whatever the subtree happens to compute to, which for a
|
|
23
|
+
* label carrying a link or an icon trigger reads as the wrong thing or as
|
|
24
|
+
* nothing at all — so an explicit name is needed. Which prop carries it differs
|
|
25
|
+
* per component, hence `accessibleNameProp`: naming the wrong one would send
|
|
26
|
+
* the developer to a prop their component rejects.
|
|
27
|
+
*
|
|
28
|
+
* `form-floating` is the second case: it animates a single line of text between
|
|
29
|
+
* the placeholder and the label position, so a node with its own height or its
|
|
30
|
+
* own interactive children breaks the layout rather than the semantics.
|
|
31
|
+
*
|
|
32
|
+
* Labels the render paths drop are skipped through the same `hasLabelContent`
|
|
33
|
+
* they use, so `label={condition && <span />}` with a false condition warns
|
|
34
|
+
* about nothing: there is no label on the page to name.
|
|
35
|
+
*
|
|
36
|
+
* The `process.env.NODE_ENV` guard belongs at the call site — that is what
|
|
37
|
+
* bundlers constant-fold, dropping this module from a consumer's production
|
|
38
|
+
* bundle.
|
|
39
|
+
*/
|
|
40
|
+
function warnLabelUsage({ component, label, hasAccessibleName, accessibleNameProp, floatingLabel = false, }) {
|
|
41
|
+
if (!hasLabelContent(label) || isTextLabel(label))
|
|
42
|
+
return;
|
|
43
|
+
if (!hasAccessibleName) {
|
|
44
|
+
warnOnce(`${component}:name`, `${component}: a non-text "label" does not give the control a reliable accessible name. `
|
|
45
|
+
+ `Pass ${accessibleNameProp} with the plain-text name of the field, since the label `
|
|
46
|
+
+ 'subtree may include links, icons or markup that read as the wrong name or as none at all.');
|
|
47
|
+
}
|
|
48
|
+
if (floatingLabel) {
|
|
49
|
+
warnOnce(`${component}:floating`, `${component}: "floatingLabel" expects a text "label". `
|
|
50
|
+
+ 'The floating layout animates a single line of text, so a node with its own height '
|
|
51
|
+
+ 'or its own interactive children overflows the control. '
|
|
52
|
+
+ 'Use the default label layout for rich labels.');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export { warnLabelUsage as default };
|
|
57
|
+
//# sourceMappingURL=warnLabelUsage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"warnLabelUsage.js","sources":["../../../dist-transpiled/utils/warnLabelUsage.js"],"sourcesContent":["import hasLabelContent from './hasLabelContent';\nimport isTextLabel from './isTextLabel';\n/**\n * Components already reported, keyed by component and reason, so the same\n * mistake only warns once per page load however often it re-renders or however\n * many instances exist.\n */\nconst warnedLabels = new Set();\nfunction warnOnce(key, message) {\n if (warnedLabels.has(key))\n return;\n warnedLabels.add(key);\n // eslint-disable-next-line no-console\n console.warn(`[Dynamic UI] ${message} It never appears in production builds.`);\n}\n/**\n * Warns, only outside production builds, about the two ways a non-text `label`\n * goes wrong.\n *\n * A text label doubles as the control's accessible name. A `ReactNode` one does\n * not: the name becomes whatever the subtree happens to compute to, which for a\n * label carrying a link or an icon trigger reads as the wrong thing or as\n * nothing at all — so an explicit name is needed. Which prop carries it differs\n * per component, hence `accessibleNameProp`: naming the wrong one would send\n * the developer to a prop their component rejects.\n *\n * `form-floating` is the second case: it animates a single line of text between\n * the placeholder and the label position, so a node with its own height or its\n * own interactive children breaks the layout rather than the semantics.\n *\n * Labels the render paths drop are skipped through the same `hasLabelContent`\n * they use, so `label={condition && <span />}` with a false condition warns\n * about nothing: there is no label on the page to name.\n *\n * The `process.env.NODE_ENV` guard belongs at the call site — that is what\n * bundlers constant-fold, dropping this module from a consumer's production\n * bundle.\n */\nexport default function warnLabelUsage({ component, label, hasAccessibleName, accessibleNameProp, floatingLabel = false, }) {\n if (!hasLabelContent(label) || isTextLabel(label))\n return;\n if (!hasAccessibleName) {\n warnOnce(`${component}:name`, `${component}: a non-text \"label\" does not give the control a reliable accessible name. `\n + `Pass ${accessibleNameProp} with the plain-text name of the field, since the label `\n + 'subtree may include links, icons or markup that read as the wrong name or as none at all.');\n }\n if (floatingLabel) {\n warnOnce(`${component}:floating`, `${component}: \"floatingLabel\" expects a text \"label\". `\n + 'The floating layout animates a single line of text, so a node with its own height '\n + 'or its own interactive children overflows the control. '\n + 'Use the default label layout for rich labels.');\n }\n}\n//# sourceMappingURL=warnLabelUsage.js.map"],"names":[],"mappings":";;;AAEA;AACA;AACA;AACA;AACA;AACA,MAAM,YAAY,GAAG,IAAI,GAAG,EAAE;AAC9B,SAAS,QAAQ,CAAC,GAAG,EAAE,OAAO,EAAE;AAChC,IAAI,IAAI,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC;AAC7B,QAAQ;AACR,IAAI,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC;AACzB;AACA,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,aAAa,EAAE,OAAO,CAAC,uCAAuC,CAAC,CAAC;AAClF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACe,SAAS,cAAc,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,aAAa,GAAG,KAAK,GAAG,EAAE;AAC5H,IAAI,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,IAAI,WAAW,CAAC,KAAK,CAAC;AACrD,QAAQ;AACR,IAAI,IAAI,CAAC,iBAAiB,EAAE;AAC5B,QAAQ,QAAQ,CAAC,CAAC,EAAE,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,SAAS,CAAC,2EAA2E;AAC9H,cAAc,CAAC,KAAK,EAAE,kBAAkB,CAAC,wDAAwD;AACjG,cAAc,2FAA2F,CAAC;AAC1G,IAAI;AACJ,IAAI,IAAI,aAAa,EAAE;AACvB,QAAQ,QAAQ,CAAC,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,SAAS,CAAC,0CAA0C;AACjG,cAAc;AACd,cAAc;AACd,cAAc,+CAA+C,CAAC;AAC9D,IAAI;AACJ;;;;"}
|