@navikt/ds-react 7.30.1 → 7.31.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/cjs/form/checkbox/Checkbox.js +1 -1
- package/cjs/form/checkbox/Checkbox.js.map +1 -1
- package/cjs/form/combobox/Combobox.js +15 -13
- package/cjs/form/combobox/Combobox.js.map +1 -1
- package/cjs/form/combobox/FilteredOptions/FilteredOptions.js +53 -3
- package/cjs/form/combobox/FilteredOptions/FilteredOptions.js.map +1 -1
- package/cjs/form/combobox/FilteredOptions/useVirtualFocus.js.map +1 -1
- package/cjs/form/combobox/Input/InputController.js +15 -14
- package/cjs/form/combobox/Input/InputController.js.map +1 -1
- package/cjs/form/radio/Radio.js +1 -1
- package/cjs/form/radio/Radio.js.map +1 -1
- package/cjs/overlays/floating/Floating.d.ts +11 -0
- package/cjs/overlays/floating/Floating.js +32 -8
- package/cjs/overlays/floating/Floating.js.map +1 -1
- package/cjs/overlays/overlay/hooks/useAnimationsFinished.d.ts +27 -0
- package/cjs/overlays/overlay/hooks/useAnimationsFinished.js +138 -0
- package/cjs/overlays/overlay/hooks/useAnimationsFinished.js.map +1 -0
- package/cjs/overlays/overlay/hooks/useEventCallback.d.ts +6 -0
- package/cjs/overlays/overlay/hooks/useEventCallback.js +89 -0
- package/cjs/overlays/overlay/hooks/useEventCallback.js.map +1 -0
- package/cjs/overlays/overlay/hooks/useLatestRef.d.ts +5 -0
- package/cjs/overlays/overlay/hooks/useLatestRef.js +23 -0
- package/cjs/overlays/overlay/hooks/useLatestRef.js.map +1 -0
- package/cjs/overlays/overlay/hooks/useOpenChangeComplete.d.ts +31 -0
- package/cjs/overlays/overlay/hooks/useOpenChangeComplete.js +35 -0
- package/cjs/overlays/overlay/hooks/useOpenChangeComplete.js.map +1 -0
- package/cjs/overlays/overlay/hooks/useRefWithInit.d.ts +7 -0
- package/cjs/overlays/overlay/hooks/useRefWithInit.js +14 -0
- package/cjs/overlays/overlay/hooks/useRefWithInit.js.map +1 -0
- package/cjs/table/ExpandableRow.d.ts +1 -1
- package/cjs/table/ExpandableRow.js +2 -10
- package/cjs/table/ExpandableRow.js.map +1 -1
- package/cjs/table/Row.d.ts +7 -0
- package/cjs/table/Row.js +13 -2
- package/cjs/table/Row.js.map +1 -1
- package/cjs/table/Table.utils.d.ts +9 -0
- package/cjs/table/Table.utils.js +57 -0
- package/cjs/table/Table.utils.js.map +1 -0
- package/esm/form/checkbox/Checkbox.js +1 -1
- package/esm/form/checkbox/Checkbox.js.map +1 -1
- package/esm/form/combobox/Combobox.js +15 -13
- package/esm/form/combobox/Combobox.js.map +1 -1
- package/esm/form/combobox/FilteredOptions/FilteredOptions.js +21 -4
- package/esm/form/combobox/FilteredOptions/FilteredOptions.js.map +1 -1
- package/esm/form/combobox/FilteredOptions/useVirtualFocus.js.map +1 -1
- package/esm/form/combobox/Input/InputController.js +15 -14
- package/esm/form/combobox/Input/InputController.js.map +1 -1
- package/esm/form/radio/Radio.js +1 -1
- package/esm/form/radio/Radio.js.map +1 -1
- package/esm/overlays/floating/Floating.d.ts +11 -0
- package/esm/overlays/floating/Floating.js +32 -8
- package/esm/overlays/floating/Floating.js.map +1 -1
- package/esm/overlays/overlay/hooks/useAnimationsFinished.d.ts +27 -0
- package/esm/overlays/overlay/hooks/useAnimationsFinished.js +99 -0
- package/esm/overlays/overlay/hooks/useAnimationsFinished.js.map +1 -0
- package/esm/overlays/overlay/hooks/useEventCallback.d.ts +6 -0
- package/esm/overlays/overlay/hooks/useEventCallback.js +53 -0
- package/esm/overlays/overlay/hooks/useEventCallback.js.map +1 -0
- package/esm/overlays/overlay/hooks/useLatestRef.d.ts +5 -0
- package/esm/overlays/overlay/hooks/useLatestRef.js +20 -0
- package/esm/overlays/overlay/hooks/useLatestRef.js.map +1 -0
- package/esm/overlays/overlay/hooks/useOpenChangeComplete.d.ts +31 -0
- package/esm/overlays/overlay/hooks/useOpenChangeComplete.js +32 -0
- package/esm/overlays/overlay/hooks/useOpenChangeComplete.js.map +1 -0
- package/esm/overlays/overlay/hooks/useRefWithInit.d.ts +7 -0
- package/esm/overlays/overlay/hooks/useRefWithInit.js +12 -0
- package/esm/overlays/overlay/hooks/useRefWithInit.js.map +1 -0
- package/esm/table/ExpandableRow.d.ts +1 -1
- package/esm/table/ExpandableRow.js +2 -10
- package/esm/table/ExpandableRow.js.map +1 -1
- package/esm/table/Row.d.ts +7 -0
- package/esm/table/Row.js +13 -2
- package/esm/table/Row.js.map +1 -1
- package/esm/table/Table.utils.d.ts +9 -0
- package/esm/table/Table.utils.js +55 -0
- package/esm/table/Table.utils.js.map +1 -0
- package/package.json +3 -3
- package/src/form/checkbox/Checkbox.tsx +5 -3
- package/src/form/combobox/Combobox.tsx +44 -41
- package/src/form/combobox/FilteredOptions/FilteredOptions.tsx +29 -4
- package/src/form/combobox/FilteredOptions/useVirtualFocus.ts +1 -0
- package/src/form/combobox/Input/InputController.tsx +33 -29
- package/src/form/radio/Radio.tsx +5 -3
- package/src/overlays/floating/Floating.tsx +110 -59
- package/src/overlays/overlay/hooks/useAnimationsFinished.ts +117 -0
- package/src/overlays/overlay/hooks/useEventCallback.ts +73 -0
- package/src/overlays/overlay/hooks/useLatestRef.ts +25 -0
- package/src/overlays/overlay/hooks/useOpenChangeComplete.ts +66 -0
- package/src/overlays/overlay/hooks/useRefWithInit.ts +25 -0
- package/src/table/ExpandableRow.tsx +4 -17
- package/src/table/Row.tsx +33 -1
- package/src/table/Table.utils.ts +65 -0
package/esm/table/Row.d.ts
CHANGED
|
@@ -10,6 +10,13 @@ export interface RowProps extends React.HTMLAttributes<HTMLTableRowElement> {
|
|
|
10
10
|
* @default true
|
|
11
11
|
*/
|
|
12
12
|
shadeOnHover?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Click handler for row. This differs from onClick by not being called
|
|
15
|
+
* when clicking on interactive elements within the row (buttons, links, inputs etc).
|
|
16
|
+
*
|
|
17
|
+
* **Warning:** This will not be accessible by keyboard! Provide an alternative way to select the row, e.g. a checkbox or a button.
|
|
18
|
+
*/
|
|
19
|
+
onRowClick?: (event: React.MouseEvent<HTMLTableRowElement>) => void;
|
|
13
20
|
}
|
|
14
21
|
export type RowType = React.ForwardRefExoticComponent<RowProps & React.RefAttributes<HTMLTableRowElement>>;
|
|
15
22
|
export declare const Row: RowType;
|
package/esm/table/Row.js
CHANGED
|
@@ -11,13 +11,24 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
11
11
|
};
|
|
12
12
|
import React, { forwardRef } from "react";
|
|
13
13
|
import { useRenameCSS } from "../theme/Theme.js";
|
|
14
|
+
import { composeEventHandlers } from "../util/composeEventHandlers.js";
|
|
15
|
+
import { isElementInteractiveTarget } from "./Table.utils.js";
|
|
14
16
|
export const Row = forwardRef((_a, ref) => {
|
|
15
|
-
var { className, selected = false, shadeOnHover = true } = _a, rest = __rest(_a, ["className", "selected", "shadeOnHover"]);
|
|
17
|
+
var { className, selected = false, shadeOnHover = true, onClick, onRowClick } = _a, rest = __rest(_a, ["className", "selected", "shadeOnHover", "onClick", "onRowClick"]);
|
|
16
18
|
const { cn } = useRenameCSS();
|
|
19
|
+
const handleRowClick = (event) => {
|
|
20
|
+
if (!onRowClick) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (isElementInteractiveTarget(event.target)) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
onRowClick(event);
|
|
27
|
+
};
|
|
17
28
|
return (React.createElement("tr", Object.assign({}, rest, { ref: ref, className: cn("navds-table__row", className, {
|
|
18
29
|
"navds-table__row--selected": selected,
|
|
19
30
|
"navds-table__row--shade-on-hover": shadeOnHover,
|
|
20
|
-
}) })));
|
|
31
|
+
}), onClick: composeEventHandlers(onClick, handleRowClick), "data-interactive": !!onRowClick })));
|
|
21
32
|
});
|
|
22
33
|
export default Row;
|
|
23
34
|
//# sourceMappingURL=Row.js.map
|
package/esm/table/Row.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Row.js","sourceRoot":"","sources":["../../src/table/Row.tsx"],"names":[],"mappings":";;;;;;;;;;;AAAA,OAAO,KAAK,EAAE,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"Row.js","sourceRoot":"","sources":["../../src/table/Row.tsx"],"names":[],"mappings":";;;;;;;;;;;AAAA,OAAO,KAAK,EAAE,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAE,0BAA0B,EAAE,MAAM,eAAe,CAAC;AA0B3D,MAAM,CAAC,MAAM,GAAG,GAAY,UAAU,CACpC,CACE,EAOC,EACD,GAAG,EACH,EAAE;QATF,EACE,SAAS,EACT,QAAQ,GAAG,KAAK,EAChB,YAAY,GAAG,IAAI,EACnB,OAAO,EACP,UAAU,OAEX,EADI,IAAI,cANT,kEAOC,CADQ;IAIT,MAAM,EAAE,EAAE,EAAE,GAAG,YAAY,EAAE,CAAC;IAE9B,MAAM,cAAc,GAAG,CAAC,KAA4C,EAAE,EAAE;QACtE,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,OAAO;QACT,CAAC;QACD,IAAI,0BAA0B,CAAC,KAAK,CAAC,MAAqB,CAAC,EAAE,CAAC;YAC5D,OAAO;QACT,CAAC;QACD,UAAU,CAAC,KAAK,CAAC,CAAC;IACpB,CAAC,CAAC;IAEF,OAAO,CACL,4CACM,IAAI,IACR,GAAG,EAAE,GAAG,EACR,SAAS,EAAE,EAAE,CAAC,kBAAkB,EAAE,SAAS,EAAE;YAC3C,4BAA4B,EAAE,QAAQ;YACtC,kCAAkC,EAAE,YAAY;SACjD,CAAC,EACF,OAAO,EAAE,oBAAoB,CAAC,OAAO,EAAE,cAAc,CAAC,sBACpC,CAAC,CAAC,UAAU,IAC9B,CACH,CAAC;AACJ,CAAC,CACF,CAAC;AAEF,eAAe,GAAG,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Walks up from the event target until TR/TH (row / header) or root.
|
|
3
|
+
* Returns true if any ancestor is inherently interactive, explicitly focusable,
|
|
4
|
+
* or has an interactive ARIA role.
|
|
5
|
+
* Used to decide whether a row click should be treated as a row selection
|
|
6
|
+
* or ignored because the user interacted with an embedded control.
|
|
7
|
+
*/
|
|
8
|
+
declare function isElementInteractiveTarget(element: HTMLElement | null): boolean;
|
|
9
|
+
export { isElementInteractiveTarget };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const INTERACTIVE_TAGS = new Set([
|
|
2
|
+
"BUTTON",
|
|
3
|
+
"A",
|
|
4
|
+
"INPUT",
|
|
5
|
+
"SELECT",
|
|
6
|
+
"TEXTAREA",
|
|
7
|
+
"DETAILS",
|
|
8
|
+
"SUMMARY",
|
|
9
|
+
"LABEL",
|
|
10
|
+
]);
|
|
11
|
+
const INTERACTIVE_ROLES = new Set([
|
|
12
|
+
"button",
|
|
13
|
+
"link",
|
|
14
|
+
"checkbox",
|
|
15
|
+
"radio",
|
|
16
|
+
"switch",
|
|
17
|
+
"menuitem",
|
|
18
|
+
"option",
|
|
19
|
+
"tab",
|
|
20
|
+
"textbox",
|
|
21
|
+
"combobox",
|
|
22
|
+
"spinbutton",
|
|
23
|
+
"slider",
|
|
24
|
+
]);
|
|
25
|
+
/**
|
|
26
|
+
* Walks up from the event target until TR/TH (row / header) or root.
|
|
27
|
+
* Returns true if any ancestor is inherently interactive, explicitly focusable,
|
|
28
|
+
* or has an interactive ARIA role.
|
|
29
|
+
* Used to decide whether a row click should be treated as a row selection
|
|
30
|
+
* or ignored because the user interacted with an embedded control.
|
|
31
|
+
*/
|
|
32
|
+
function isElementInteractiveTarget(element) {
|
|
33
|
+
for (let node = element; node && node.nodeName !== "TR" && node.nodeName !== "TH"; node = node.parentElement) {
|
|
34
|
+
const tag = node.nodeName;
|
|
35
|
+
/* Native interactive tag */
|
|
36
|
+
if (INTERACTIVE_TAGS.has(tag)) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
/* Explicit interactive role */
|
|
40
|
+
const role = node.getAttribute("role");
|
|
41
|
+
if (role && INTERACTIVE_ROLES.has(role)) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
/* Focusable via tabindex (exclude -1) */
|
|
45
|
+
if (node.hasAttribute("tabindex")) {
|
|
46
|
+
const ti = node.getAttribute("tabindex");
|
|
47
|
+
if (ti !== "-1") {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
export { isElementInteractiveTarget };
|
|
55
|
+
//# sourceMappingURL=Table.utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Table.utils.js","sourceRoot":"","sources":["../../src/table/Table.utils.ts"],"names":[],"mappings":"AAAA,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC;IAC/B,QAAQ;IACR,GAAG;IACH,OAAO;IACP,QAAQ;IACR,UAAU;IACV,SAAS;IACT,SAAS;IACT,OAAO;CACR,CAAC,CAAC;AAEH,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC;IAChC,QAAQ;IACR,MAAM;IACN,UAAU;IACV,OAAO;IACP,QAAQ;IACR,UAAU;IACV,QAAQ;IACR,KAAK;IACL,SAAS;IACT,UAAU;IACV,YAAY;IACZ,QAAQ;CACT,CAAC,CAAC;AAEH;;;;;;GAMG;AACH,SAAS,0BAA0B,CAAC,OAA2B;IAC7D,KACE,IAAI,IAAI,GAAuB,OAAO,EACtC,IAAI,IAAI,IAAI,CAAC,QAAQ,KAAK,IAAI,IAAI,IAAI,CAAC,QAAQ,KAAK,IAAI,EACxD,IAAI,GAAG,IAAI,CAAC,aAAa,EACzB,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC;QAE1B,4BAA4B;QAC5B,IAAI,gBAAgB,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YAC9B,OAAO,IAAI,CAAC;QACd,CAAC;QAED,+BAA+B;QAC/B,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;QACvC,IAAI,IAAI,IAAI,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YACxC,OAAO,IAAI,CAAC;QACd,CAAC;QAED,yCAAyC;QACzC,IAAI,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,EAAE,CAAC;YAClC,MAAM,EAAE,GAAG,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;YACzC,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC;gBAChB,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,OAAO,EAAE,0BAA0B,EAAE,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@navikt/ds-react",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.31.0",
|
|
4
4
|
"description": "React components from the Norwegian Labour and Welfare Administration.",
|
|
5
5
|
"author": "Aksel, a team part of the Norwegian Labour and Welfare Administration.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -650,8 +650,8 @@
|
|
|
650
650
|
"dependencies": {
|
|
651
651
|
"@floating-ui/react": "0.27.8",
|
|
652
652
|
"@floating-ui/react-dom": "^2.0.9",
|
|
653
|
-
"@navikt/aksel-icons": "^7.
|
|
654
|
-
"@navikt/ds-tokens": "^7.
|
|
653
|
+
"@navikt/aksel-icons": "^7.31.0",
|
|
654
|
+
"@navikt/ds-tokens": "^7.31.0",
|
|
655
655
|
"clsx": "^2.1.0",
|
|
656
656
|
"date-fns": "^4.0.0",
|
|
657
657
|
"react-day-picker": "9.7.0"
|
|
@@ -42,9 +42,11 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
|
|
|
42
42
|
"readOnly",
|
|
43
43
|
])}
|
|
44
44
|
{...omit(inputProps, ["aria-invalid", "aria-describedby"])}
|
|
45
|
-
aria-describedby={
|
|
46
|
-
[
|
|
47
|
-
|
|
45
|
+
aria-describedby={
|
|
46
|
+
cl(inputProps["aria-describedby"], {
|
|
47
|
+
[descriptionId]: props.description,
|
|
48
|
+
}) || undefined
|
|
49
|
+
}
|
|
48
50
|
type="checkbox"
|
|
49
51
|
className={cn("navds-checkbox__input")}
|
|
50
52
|
ref={(el) => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { forwardRef } from "react";
|
|
2
|
+
import { Floating } from "../../overlays/floating/Floating";
|
|
2
3
|
import { useRenameCSS } from "../../theme/Theme";
|
|
3
4
|
import { BodyShort, ErrorMessage, Label } from "../../typography";
|
|
4
5
|
import { ReadOnlyIconWithTitle } from "../ReadOnlyIcon";
|
|
@@ -34,52 +35,54 @@ export const Combobox = forwardRef<
|
|
|
34
35
|
} = useInputContext();
|
|
35
36
|
|
|
36
37
|
return (
|
|
37
|
-
<
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
<Label
|
|
45
|
-
htmlFor={inputProps.id}
|
|
46
|
-
size={size}
|
|
47
|
-
className={cn("navds-form-field__label", {
|
|
48
|
-
"navds-sr-only": hideLabel,
|
|
49
|
-
})}
|
|
38
|
+
<Floating>
|
|
39
|
+
<ComboboxWrapper
|
|
40
|
+
className={className}
|
|
41
|
+
hasError={hasError}
|
|
42
|
+
inputProps={inputProps}
|
|
43
|
+
inputSize={size}
|
|
44
|
+
toggleIsListOpen={toggleIsListOpen}
|
|
50
45
|
>
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
<BodyShort
|
|
56
|
-
as="div"
|
|
57
|
-
className={cn("navds-form-field__description", {
|
|
46
|
+
<Label
|
|
47
|
+
htmlFor={inputProps.id}
|
|
48
|
+
size={size}
|
|
49
|
+
className={cn("navds-form-field__label", {
|
|
58
50
|
"navds-sr-only": hideLabel,
|
|
59
51
|
})}
|
|
60
|
-
id={inputDescriptionId}
|
|
61
|
-
size={size}
|
|
62
52
|
>
|
|
63
|
-
{
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
<ErrorMessage size={size} showIcon>
|
|
78
|
-
{error}
|
|
79
|
-
</ErrorMessage>
|
|
53
|
+
{readOnly && <ReadOnlyIconWithTitle />}
|
|
54
|
+
{label}
|
|
55
|
+
</Label>
|
|
56
|
+
{!!description && (
|
|
57
|
+
<BodyShort
|
|
58
|
+
as="div"
|
|
59
|
+
className={cn("navds-form-field__description", {
|
|
60
|
+
"navds-sr-only": hideLabel,
|
|
61
|
+
})}
|
|
62
|
+
id={inputDescriptionId}
|
|
63
|
+
size={size}
|
|
64
|
+
>
|
|
65
|
+
{description}
|
|
66
|
+
</BodyShort>
|
|
80
67
|
)}
|
|
81
|
-
|
|
82
|
-
|
|
68
|
+
<div className={cn("navds-combobox__wrapper")}>
|
|
69
|
+
<InputController ref={ref} {...rest} />
|
|
70
|
+
<FilteredOptions />
|
|
71
|
+
</div>
|
|
72
|
+
<div
|
|
73
|
+
className={cn("navds-form-field__error")}
|
|
74
|
+
id={errorId}
|
|
75
|
+
aria-relevant="additions removals"
|
|
76
|
+
aria-live="polite"
|
|
77
|
+
>
|
|
78
|
+
{showErrorMsg && (
|
|
79
|
+
<ErrorMessage size={size} showIcon>
|
|
80
|
+
{error}
|
|
81
|
+
</ErrorMessage>
|
|
82
|
+
)}
|
|
83
|
+
</div>
|
|
84
|
+
</ComboboxWrapper>
|
|
85
|
+
</Floating>
|
|
83
86
|
);
|
|
84
87
|
});
|
|
85
88
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import {
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { Floating } from "../../../overlays/floating/Floating";
|
|
3
|
+
import { useRenameCSS, useThemeInternal } from "../../../theme/Theme";
|
|
4
|
+
import { useClientLayoutEffect } from "../../../util";
|
|
3
5
|
import { useInputContext } from "../Input/Input.context";
|
|
4
6
|
import { useSelectedOptionsContext } from "../SelectedOptions/selectedOptionsContext";
|
|
5
7
|
import AddNewOption from "./AddNewOption";
|
|
@@ -12,6 +14,7 @@ import { useFilteredOptionsContext } from "./filteredOptionsContext";
|
|
|
12
14
|
|
|
13
15
|
const FilteredOptions = () => {
|
|
14
16
|
const { cn } = useRenameCSS();
|
|
17
|
+
const themeContext = useThemeInternal(false);
|
|
15
18
|
const {
|
|
16
19
|
inputProps: { id },
|
|
17
20
|
} = useInputContext();
|
|
@@ -25,6 +28,16 @@ const FilteredOptions = () => {
|
|
|
25
28
|
isMouseLastUsedInputDevice,
|
|
26
29
|
isValueNew,
|
|
27
30
|
} = useFilteredOptionsContext();
|
|
31
|
+
const [localOpen, setLocalOpen] = useState(isListOpen);
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* This is a dirty hack to make the positioning-logic in Floating base the "flip" on the static 290px max-height,
|
|
35
|
+
* instead of the dynamic one based on available space. Without this, the list won't flip to top when there's
|
|
36
|
+
* not enough space below the input.
|
|
37
|
+
*/
|
|
38
|
+
useClientLayoutEffect(() => {
|
|
39
|
+
queueMicrotask(() => setLocalOpen(isListOpen));
|
|
40
|
+
}, [isListOpen]);
|
|
28
41
|
|
|
29
42
|
const { maxSelected, isMultiSelect } = useSelectedOptionsContext();
|
|
30
43
|
|
|
@@ -37,14 +50,26 @@ const FilteredOptions = () => {
|
|
|
37
50
|
(allowNewValues && isValueNew && !maxSelected.isLimitReached) || // Render add new option
|
|
38
51
|
filteredOptions.length > 0; // Render filtered options
|
|
39
52
|
|
|
53
|
+
const height = themeContext?.isDarkside ? "316px" : "290px";
|
|
54
|
+
|
|
40
55
|
return (
|
|
41
|
-
<
|
|
56
|
+
<Floating.Content
|
|
42
57
|
className={cn("navds-combobox__list", {
|
|
43
58
|
"navds-combobox__list--closed": !isListOpen,
|
|
44
59
|
"navds-combobox__list--with-hover": isMouseLastUsedInputDevice,
|
|
45
60
|
})}
|
|
46
61
|
id={filteredOptionsUtil.getFilteredOptionsId(id)}
|
|
47
62
|
tabIndex={-1}
|
|
63
|
+
sideOffset={8}
|
|
64
|
+
side="bottom"
|
|
65
|
+
fallbackPlacements={["top"]}
|
|
66
|
+
enabled={isListOpen}
|
|
67
|
+
style={{
|
|
68
|
+
maxHeight: localOpen
|
|
69
|
+
? `min(${height}, var(--ac-floating-available-height))`
|
|
70
|
+
: `${height}`,
|
|
71
|
+
}}
|
|
72
|
+
autoUpdateWhileMounted={false}
|
|
48
73
|
>
|
|
49
74
|
{shouldRenderNonSelectables && (
|
|
50
75
|
<div
|
|
@@ -74,7 +99,7 @@ const FilteredOptions = () => {
|
|
|
74
99
|
))}
|
|
75
100
|
</ul>
|
|
76
101
|
)}
|
|
77
|
-
</
|
|
102
|
+
</Floating.Content>
|
|
78
103
|
);
|
|
79
104
|
};
|
|
80
105
|
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
/* eslint-disable jsx-a11y/click-events-have-key-events */
|
|
2
|
+
|
|
1
3
|
/* eslint-disable jsx-a11y/no-static-element-interactions */
|
|
2
4
|
import React, { forwardRef } from "react";
|
|
5
|
+
import { Floating } from "../../../overlays/floating/Floating";
|
|
3
6
|
import { useRenameCSS } from "../../../theme/Theme";
|
|
4
7
|
import { useMergeRefs } from "../../../util/hooks";
|
|
5
8
|
import { useFilteredOptionsContext } from "../FilteredOptions/filteredOptionsContext";
|
|
@@ -54,42 +57,43 @@ export const InputController = forwardRef<
|
|
|
54
57
|
const mergedInputRef = useMergeRefs(inputRef, ref);
|
|
55
58
|
|
|
56
59
|
return (
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
60
|
+
<Floating.Anchor asChild>
|
|
61
|
+
<div
|
|
62
|
+
className={cn("navds-combobox__wrapper-inner navds-text-field__input", {
|
|
63
|
+
"navds-combobox__wrapper-inner--virtually-unfocused":
|
|
64
|
+
activeDecendantId !== undefined,
|
|
65
|
+
})}
|
|
66
|
+
onClick={() => {
|
|
67
|
+
if (inputProps.disabled || readOnly) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
67
70
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
<Input
|
|
74
|
-
id={inputProps.id}
|
|
75
|
-
ref={mergedInputRef}
|
|
76
|
-
inputClassName={inputClassName}
|
|
77
|
-
readOnly={readOnly}
|
|
78
|
-
{...rest}
|
|
79
|
-
/>
|
|
80
|
-
) : (
|
|
81
|
-
<SelectedOptions selectedOptions={selectedOptions} size={size}>
|
|
71
|
+
toggleIsListOpen(true);
|
|
72
|
+
focusInput();
|
|
73
|
+
}}
|
|
74
|
+
>
|
|
75
|
+
{!shouldShowSelectedOptions ? (
|
|
82
76
|
<Input
|
|
83
77
|
id={inputProps.id}
|
|
84
78
|
ref={mergedInputRef}
|
|
85
79
|
inputClassName={inputClassName}
|
|
86
|
-
shouldShowSelectedOptions={shouldShowSelectedOptions}
|
|
87
80
|
readOnly={readOnly}
|
|
88
81
|
{...rest}
|
|
89
82
|
/>
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
83
|
+
) : (
|
|
84
|
+
<SelectedOptions selectedOptions={selectedOptions} size={size}>
|
|
85
|
+
<Input
|
|
86
|
+
id={inputProps.id}
|
|
87
|
+
ref={mergedInputRef}
|
|
88
|
+
inputClassName={inputClassName}
|
|
89
|
+
shouldShowSelectedOptions={shouldShowSelectedOptions}
|
|
90
|
+
readOnly={readOnly}
|
|
91
|
+
{...rest}
|
|
92
|
+
/>
|
|
93
|
+
</SelectedOptions>
|
|
94
|
+
)}
|
|
95
|
+
{toggleListButton && <ToggleListButton ref={toggleOpenButtonRef} />}
|
|
96
|
+
</div>
|
|
97
|
+
</Floating.Anchor>
|
|
94
98
|
);
|
|
95
99
|
});
|
package/src/form/radio/Radio.tsx
CHANGED
|
@@ -25,9 +25,11 @@ export const Radio = forwardRef<HTMLInputElement, RadioProps>((props, ref) => {
|
|
|
25
25
|
<input
|
|
26
26
|
{...omit(props, ["children", "size", "description", "readOnly"])}
|
|
27
27
|
{...omit(inputProps, ["aria-invalid", "aria-describedby"])}
|
|
28
|
-
aria-describedby={
|
|
29
|
-
[
|
|
30
|
-
|
|
28
|
+
aria-describedby={
|
|
29
|
+
cl(inputProps["aria-describedby"], {
|
|
30
|
+
[descriptionId]: props.description,
|
|
31
|
+
}) || undefined
|
|
32
|
+
}
|
|
31
33
|
className={cn("navds-radio__input")}
|
|
32
34
|
ref={ref}
|
|
33
35
|
/>
|
|
@@ -18,6 +18,7 @@ import React, {
|
|
|
18
18
|
useRef,
|
|
19
19
|
useState,
|
|
20
20
|
} from "react";
|
|
21
|
+
import { useModalContext } from "../../modal/Modal.context";
|
|
21
22
|
import { Slot } from "../../slot/Slot";
|
|
22
23
|
import { createContext } from "../../util/create-context";
|
|
23
24
|
import {
|
|
@@ -26,6 +27,7 @@ import {
|
|
|
26
27
|
useMergeRefs,
|
|
27
28
|
} from "../../util/hooks";
|
|
28
29
|
import { AsChildProps } from "../../util/types";
|
|
30
|
+
import { useOpenChangeComplete } from "../overlay/hooks/useOpenChangeComplete";
|
|
29
31
|
import {
|
|
30
32
|
type Align,
|
|
31
33
|
type Measurable,
|
|
@@ -188,7 +190,17 @@ interface FloatingContentProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
188
190
|
collisionPadding?: number | Partial<Record<Side, number>>;
|
|
189
191
|
hideWhenDetached?: boolean;
|
|
190
192
|
updatePositionStrategy?: "optimized" | "always";
|
|
193
|
+
fallbackPlacements?: FlipOptions["fallbackPlacements"];
|
|
191
194
|
onPlaced?: () => void;
|
|
195
|
+
/**
|
|
196
|
+
* @default true
|
|
197
|
+
*/
|
|
198
|
+
enabled?: boolean;
|
|
199
|
+
/**
|
|
200
|
+
* Only use this option if your floating element is conditionally rendered, not hidden with CSS.
|
|
201
|
+
* @default true
|
|
202
|
+
*/
|
|
203
|
+
autoUpdateWhileMounted?: boolean;
|
|
192
204
|
arrow?: {
|
|
193
205
|
className?: string;
|
|
194
206
|
padding?: number;
|
|
@@ -212,11 +224,15 @@ const FloatingContent = forwardRef<HTMLDivElement, FloatingContentProps>(
|
|
|
212
224
|
updatePositionStrategy = "optimized",
|
|
213
225
|
onPlaced,
|
|
214
226
|
arrow: _arrow,
|
|
227
|
+
fallbackPlacements,
|
|
228
|
+
enabled = true,
|
|
229
|
+
autoUpdateWhileMounted = true,
|
|
215
230
|
...contentProps
|
|
216
231
|
}: FloatingContentProps,
|
|
217
232
|
forwardedRef,
|
|
218
233
|
) => {
|
|
219
234
|
const context = useFloatingContext();
|
|
235
|
+
const modalContext = useModalContext(false);
|
|
220
236
|
|
|
221
237
|
const arrowDefaults = {
|
|
222
238
|
padding: 5,
|
|
@@ -256,68 +272,103 @@ const FloatingContent = forwardRef<HTMLDivElement, FloatingContentProps>(
|
|
|
256
272
|
altBoundary: hasExplicitBoundaries,
|
|
257
273
|
/* https://floating-ui.com/docs/flip#fallbackaxissidedirection */
|
|
258
274
|
fallbackAxisSideDirection: "end",
|
|
275
|
+
fallbackPlacements,
|
|
259
276
|
};
|
|
260
277
|
|
|
261
|
-
const {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
278
|
+
const {
|
|
279
|
+
refs,
|
|
280
|
+
floatingStyles,
|
|
281
|
+
placement,
|
|
282
|
+
isPositioned,
|
|
283
|
+
middlewareData,
|
|
284
|
+
elements: floatingElements,
|
|
285
|
+
update,
|
|
286
|
+
} = useFloating({
|
|
287
|
+
open: enabled,
|
|
288
|
+
// default to `fixed` strategy so users don't have to pick and we also avoid focus scroll issues
|
|
289
|
+
strategy: "fixed",
|
|
290
|
+
placement: desiredPlacement,
|
|
291
|
+
whileElementsMounted: autoUpdateWhileMounted
|
|
292
|
+
? (...args) => {
|
|
293
|
+
const cleanup = autoUpdate(...args, {
|
|
294
|
+
animationFrame: updatePositionStrategy === "always",
|
|
295
|
+
});
|
|
296
|
+
return cleanup;
|
|
297
|
+
}
|
|
298
|
+
: undefined,
|
|
299
|
+
elements: {
|
|
300
|
+
reference: context.anchor,
|
|
301
|
+
},
|
|
302
|
+
middleware: [
|
|
303
|
+
offset({
|
|
304
|
+
mainAxis: sideOffset + arrowHeight,
|
|
305
|
+
alignmentAxis: alignOffset,
|
|
306
|
+
}),
|
|
307
|
+
avoidCollisions &&
|
|
308
|
+
shift({
|
|
309
|
+
mainAxis: true,
|
|
310
|
+
crossAxis: false,
|
|
311
|
+
limiter: limitShift(),
|
|
279
312
|
}),
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
313
|
+
avoidCollisions && flip({ ...detectOverflowOptions }),
|
|
314
|
+
size({
|
|
315
|
+
...detectOverflowOptions,
|
|
316
|
+
apply: ({ elements, rects, availableWidth, availableHeight }) => {
|
|
317
|
+
const { width: anchorWidth, height: anchorHeight } =
|
|
318
|
+
rects.reference;
|
|
319
|
+
const contentStyle = elements.floating.style;
|
|
320
|
+
/**
|
|
321
|
+
* Allows styling and animations based on the available space.
|
|
322
|
+
*/
|
|
323
|
+
contentStyle.setProperty(
|
|
324
|
+
"--ac-floating-available-width",
|
|
325
|
+
`${availableWidth}px`,
|
|
326
|
+
);
|
|
327
|
+
contentStyle.setProperty(
|
|
328
|
+
"--ac-floating-available-height",
|
|
329
|
+
`${availableHeight}px`,
|
|
330
|
+
);
|
|
331
|
+
contentStyle.setProperty(
|
|
332
|
+
"--ac-floating-anchor-width",
|
|
333
|
+
`${anchorWidth}px`,
|
|
334
|
+
);
|
|
335
|
+
contentStyle.setProperty(
|
|
336
|
+
"--ac-floating-anchor-height",
|
|
337
|
+
`${anchorHeight}px`,
|
|
338
|
+
);
|
|
339
|
+
},
|
|
340
|
+
}),
|
|
341
|
+
arrow &&
|
|
342
|
+
floatingArrow({ element: arrow, padding: arrowDefaults.padding }),
|
|
343
|
+
transformOrigin({ arrowWidth, arrowHeight }),
|
|
344
|
+
hideWhenDetached &&
|
|
345
|
+
hide({ strategy: "referenceHidden", ...detectOverflowOptions }),
|
|
346
|
+
],
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
useEffect(() => {
|
|
350
|
+
if (autoUpdateWhileMounted || !enabled) {
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
if (floatingElements.reference && floatingElements.floating) {
|
|
354
|
+
const cleanup = autoUpdate(
|
|
355
|
+
floatingElements.reference,
|
|
356
|
+
floatingElements.floating,
|
|
357
|
+
update,
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
return () => {
|
|
361
|
+
cleanup();
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
}, [autoUpdateWhileMounted, enabled, floatingElements, update]);
|
|
365
|
+
|
|
366
|
+
useOpenChangeComplete({
|
|
367
|
+
enabled: !!modalContext?.ref,
|
|
368
|
+
open: enabled,
|
|
369
|
+
ref: modalContext?.ref,
|
|
370
|
+
onComplete: update,
|
|
371
|
+
});
|
|
321
372
|
|
|
322
373
|
const [placedSide, placedAlign] = getSideAndAlignFromPlacement(placement);
|
|
323
374
|
|