@navikt/ds-react 6.7.0 → 6.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/cjs/chat/Chat.d.ts +2 -1
  2. package/cjs/chat/Chat.js +2 -1
  3. package/cjs/chat/Chat.js.map +1 -1
  4. package/cjs/date/datepicker/parts/DropdownCaption.js +1 -1
  5. package/cjs/date/datepicker/parts/DropdownCaption.js.map +1 -1
  6. package/cjs/date/monthpicker/MonthCaption.js +1 -1
  7. package/cjs/date/utils/labels.d.ts +2 -2
  8. package/cjs/form/ReadOnlyIcon.d.ts +2 -2
  9. package/cjs/form/combobox/Combobox.js +7 -22
  10. package/cjs/form/combobox/Combobox.js.map +1 -1
  11. package/cjs/form/combobox/ComboboxProvider.js +2 -2
  12. package/cjs/form/combobox/ComboboxProvider.js.map +1 -1
  13. package/cjs/form/combobox/ComboboxWrapper.d.ts +1 -2
  14. package/cjs/form/combobox/ComboboxWrapper.js +4 -2
  15. package/cjs/form/combobox/ComboboxWrapper.js.map +1 -1
  16. package/cjs/form/combobox/FilteredOptions/FilteredOptions.js +4 -4
  17. package/cjs/form/combobox/FilteredOptions/FilteredOptions.js.map +1 -1
  18. package/cjs/form/combobox/FilteredOptions/filteredOptionsContext.d.ts +4 -4
  19. package/cjs/form/combobox/FilteredOptions/filteredOptionsContext.js +13 -15
  20. package/cjs/form/combobox/FilteredOptions/filteredOptionsContext.js.map +1 -1
  21. package/cjs/form/combobox/Input/{inputContext.d.ts → Input.context.d.ts} +7 -5
  22. package/cjs/form/combobox/Input/{inputContext.js → Input.context.js} +22 -22
  23. package/cjs/form/combobox/Input/Input.context.js.map +1 -0
  24. package/cjs/form/combobox/Input/Input.js +2 -2
  25. package/cjs/form/combobox/Input/Input.js.map +1 -1
  26. package/cjs/form/combobox/Input/InputController.d.ts +3 -0
  27. package/cjs/form/combobox/Input/InputController.js +70 -0
  28. package/cjs/form/combobox/Input/InputController.js.map +1 -0
  29. package/cjs/form/combobox/{ToggleListButton.js → Input/ToggleListButton.js} +1 -1
  30. package/cjs/form/combobox/Input/ToggleListButton.js.map +1 -0
  31. package/cjs/form/combobox/SelectedOptions/SelectedOptions.js +2 -2
  32. package/cjs/form/combobox/SelectedOptions/SelectedOptions.js.map +1 -1
  33. package/cjs/form/combobox/SelectedOptions/selectedOptionsContext.d.ts +4 -4
  34. package/cjs/form/combobox/SelectedOptions/selectedOptionsContext.js +7 -13
  35. package/cjs/form/combobox/SelectedOptions/selectedOptionsContext.js.map +1 -1
  36. package/cjs/form/combobox/customOptionsContext.d.ts +4 -4
  37. package/cjs/form/combobox/customOptionsContext.js +10 -13
  38. package/cjs/form/combobox/customOptionsContext.js.map +1 -1
  39. package/cjs/form/combobox/types.d.ts +1 -1
  40. package/cjs/help-text/HelpTextIcon.d.ts +1 -1
  41. package/cjs/overlay/dismiss/DismissableLayer.d.ts +70 -0
  42. package/cjs/overlay/dismiss/DismissableLayer.js +253 -0
  43. package/cjs/overlay/dismiss/DismissableLayer.js.map +1 -0
  44. package/cjs/overlay/dismiss/util/dispatchCustomEvent.d.ts +50 -0
  45. package/cjs/overlay/dismiss/util/dispatchCustomEvent.js +65 -0
  46. package/cjs/overlay/dismiss/util/dispatchCustomEvent.js.map +1 -0
  47. package/cjs/overlay/dismiss/util/useEscapeKeydown.d.ts +1 -0
  48. package/cjs/overlay/dismiss/util/useEscapeKeydown.js +19 -0
  49. package/cjs/overlay/dismiss/util/useEscapeKeydown.js.map +1 -0
  50. package/cjs/overlay/dismiss/util/useFocusOutside.d.ts +8 -0
  51. package/cjs/overlay/dismiss/util/useFocusOutside.js +42 -0
  52. package/cjs/overlay/dismiss/util/useFocusOutside.js.map +1 -0
  53. package/cjs/overlay/dismiss/util/usePointerDownOutside.d.ts +10 -0
  54. package/cjs/overlay/dismiss/util/usePointerDownOutside.js +84 -0
  55. package/cjs/overlay/dismiss/util/usePointerDownOutside.js.map +1 -0
  56. package/cjs/overlays/floating/Floating.d.ts +53 -0
  57. package/cjs/overlays/floating/Floating.js +215 -0
  58. package/cjs/overlays/floating/Floating.js.map +1 -0
  59. package/cjs/overlays/floating/Floating.utils.d.ts +18 -0
  60. package/cjs/overlays/floating/Floating.utils.js +52 -0
  61. package/cjs/overlays/floating/Floating.utils.js.map +1 -0
  62. package/cjs/popover/Popover.js +13 -28
  63. package/cjs/popover/Popover.js.map +1 -1
  64. package/cjs/progress-bar/ProgressBar.d.ts +20 -8
  65. package/cjs/progress-bar/ProgressBar.js +19 -9
  66. package/cjs/progress-bar/ProgressBar.js.map +1 -1
  67. package/cjs/tabs/Tabs.context.d.ts +7 -3
  68. package/cjs/tabs/Tabs.context.js +1 -0
  69. package/cjs/tabs/Tabs.context.js.map +1 -1
  70. package/cjs/timeline/AxisLabels.d.ts +1 -1
  71. package/cjs/toggle-group/ToggleGroup.context.d.ts +7 -3
  72. package/cjs/toggle-group/ToggleGroup.context.js +1 -0
  73. package/cjs/toggle-group/ToggleGroup.context.js.map +1 -1
  74. package/cjs/util/hooks/descendants/useDescendant.d.ts +2 -2
  75. package/cjs/util/hooks/descendants/useDescendant.js +49 -52
  76. package/cjs/util/hooks/descendants/useDescendant.js.map +1 -1
  77. package/cjs/util/types/AsChild.d.ts +14 -0
  78. package/cjs/util/types/AsChild.js +3 -0
  79. package/cjs/util/types/AsChild.js.map +1 -0
  80. package/esm/chat/Chat.d.ts +2 -1
  81. package/esm/chat/Chat.js +1 -0
  82. package/esm/chat/Chat.js.map +1 -1
  83. package/esm/date/datepicker/parts/DropdownCaption.js +1 -1
  84. package/esm/date/datepicker/parts/DropdownCaption.js.map +1 -1
  85. package/esm/date/monthpicker/MonthCaption.js +1 -1
  86. package/esm/date/utils/labels.d.ts +2 -2
  87. package/esm/form/ReadOnlyIcon.d.ts +2 -2
  88. package/esm/form/combobox/Combobox.js +8 -23
  89. package/esm/form/combobox/Combobox.js.map +1 -1
  90. package/esm/form/combobox/ComboboxProvider.js +1 -1
  91. package/esm/form/combobox/ComboboxProvider.js.map +1 -1
  92. package/esm/form/combobox/ComboboxWrapper.d.ts +1 -2
  93. package/esm/form/combobox/ComboboxWrapper.js +4 -2
  94. package/esm/form/combobox/ComboboxWrapper.js.map +1 -1
  95. package/esm/form/combobox/FilteredOptions/FilteredOptions.js +3 -3
  96. package/esm/form/combobox/FilteredOptions/FilteredOptions.js.map +1 -1
  97. package/esm/form/combobox/FilteredOptions/filteredOptionsContext.d.ts +4 -4
  98. package/esm/form/combobox/FilteredOptions/filteredOptionsContext.js +15 -16
  99. package/esm/form/combobox/FilteredOptions/filteredOptionsContext.js.map +1 -1
  100. package/esm/form/combobox/Input/{inputContext.d.ts → Input.context.d.ts} +7 -5
  101. package/esm/form/combobox/Input/{inputContext.js → Input.context.js} +22 -21
  102. package/esm/form/combobox/Input/Input.context.js.map +1 -0
  103. package/esm/form/combobox/Input/Input.js +1 -1
  104. package/esm/form/combobox/Input/Input.js.map +1 -1
  105. package/esm/form/combobox/Input/InputController.d.ts +3 -0
  106. package/esm/form/combobox/Input/InputController.js +41 -0
  107. package/esm/form/combobox/Input/InputController.js.map +1 -0
  108. package/esm/form/combobox/{ToggleListButton.js → Input/ToggleListButton.js} +1 -1
  109. package/esm/form/combobox/Input/ToggleListButton.js.map +1 -0
  110. package/esm/form/combobox/SelectedOptions/SelectedOptions.js +1 -1
  111. package/esm/form/combobox/SelectedOptions/SelectedOptions.js.map +1 -1
  112. package/esm/form/combobox/SelectedOptions/selectedOptionsContext.d.ts +4 -4
  113. package/esm/form/combobox/SelectedOptions/selectedOptionsContext.js +9 -14
  114. package/esm/form/combobox/SelectedOptions/selectedOptionsContext.js.map +1 -1
  115. package/esm/form/combobox/customOptionsContext.d.ts +4 -4
  116. package/esm/form/combobox/customOptionsContext.js +10 -12
  117. package/esm/form/combobox/customOptionsContext.js.map +1 -1
  118. package/esm/form/combobox/types.d.ts +1 -1
  119. package/esm/help-text/HelpTextIcon.d.ts +1 -1
  120. package/esm/overlay/dismiss/DismissableLayer.d.ts +70 -0
  121. package/esm/overlay/dismiss/DismissableLayer.js +226 -0
  122. package/esm/overlay/dismiss/DismissableLayer.js.map +1 -0
  123. package/esm/overlay/dismiss/util/dispatchCustomEvent.d.ts +50 -0
  124. package/esm/overlay/dismiss/util/dispatchCustomEvent.js +58 -0
  125. package/esm/overlay/dismiss/util/dispatchCustomEvent.js.map +1 -0
  126. package/esm/overlay/dismiss/util/useEscapeKeydown.d.ts +1 -0
  127. package/esm/overlay/dismiss/util/useEscapeKeydown.js +15 -0
  128. package/esm/overlay/dismiss/util/useEscapeKeydown.js.map +1 -0
  129. package/esm/overlay/dismiss/util/useFocusOutside.d.ts +8 -0
  130. package/esm/overlay/dismiss/util/useFocusOutside.js +38 -0
  131. package/esm/overlay/dismiss/util/useFocusOutside.js.map +1 -0
  132. package/esm/overlay/dismiss/util/usePointerDownOutside.d.ts +10 -0
  133. package/esm/overlay/dismiss/util/usePointerDownOutside.js +80 -0
  134. package/esm/overlay/dismiss/util/usePointerDownOutside.js.map +1 -0
  135. package/esm/overlays/floating/Floating.d.ts +53 -0
  136. package/esm/overlays/floating/Floating.js +188 -0
  137. package/esm/overlays/floating/Floating.js.map +1 -0
  138. package/esm/overlays/floating/Floating.utils.d.ts +18 -0
  139. package/esm/overlays/floating/Floating.utils.js +48 -0
  140. package/esm/overlays/floating/Floating.utils.js.map +1 -0
  141. package/esm/popover/Popover.js +16 -31
  142. package/esm/popover/Popover.js.map +1 -1
  143. package/esm/progress-bar/ProgressBar.d.ts +20 -8
  144. package/esm/progress-bar/ProgressBar.js +20 -10
  145. package/esm/progress-bar/ProgressBar.js.map +1 -1
  146. package/esm/tabs/Tabs.context.d.ts +7 -3
  147. package/esm/tabs/Tabs.context.js +1 -0
  148. package/esm/tabs/Tabs.context.js.map +1 -1
  149. package/esm/timeline/AxisLabels.d.ts +1 -1
  150. package/esm/toggle-group/ToggleGroup.context.d.ts +7 -3
  151. package/esm/toggle-group/ToggleGroup.context.js +1 -0
  152. package/esm/toggle-group/ToggleGroup.context.js.map +1 -1
  153. package/esm/util/hooks/descendants/useDescendant.d.ts +2 -2
  154. package/esm/util/hooks/descendants/useDescendant.js +49 -52
  155. package/esm/util/hooks/descendants/useDescendant.js.map +1 -1
  156. package/esm/util/types/AsChild.d.ts +14 -0
  157. package/esm/util/types/AsChild.js +2 -0
  158. package/esm/util/types/AsChild.js.map +1 -0
  159. package/package.json +6 -5
  160. package/src/chat/Chat.tsx +2 -1
  161. package/src/date/datepicker/parts/DropdownCaption.tsx +5 -1
  162. package/src/date/monthpicker/MonthCaption.tsx +1 -1
  163. package/src/form/combobox/Combobox.tsx +6 -76
  164. package/src/form/combobox/ComboboxProvider.tsx +1 -1
  165. package/src/form/combobox/ComboboxWrapper.tsx +4 -3
  166. package/src/form/combobox/FilteredOptions/FilteredOptions.tsx +3 -3
  167. package/src/form/combobox/FilteredOptions/filteredOptionsContext.tsx +19 -29
  168. package/src/form/combobox/Input/{inputContext.tsx → Input.context.tsx} +30 -33
  169. package/src/form/combobox/Input/Input.tsx +1 -1
  170. package/src/form/combobox/Input/InputController.tsx +102 -0
  171. package/src/form/combobox/{ToggleListButton.tsx → Input/ToggleListButton.tsx} +1 -1
  172. package/src/form/combobox/SelectedOptions/SelectedOptions.tsx +1 -1
  173. package/src/form/combobox/SelectedOptions/selectedOptionsContext.tsx +12 -26
  174. package/src/form/combobox/{combobox-utils.test.ts → __tests__/combobox-utils.test.ts} +1 -1
  175. package/src/form/combobox/{combobox.test.tsx → __tests__/combobox.test.tsx} +2 -3
  176. package/src/form/combobox/customOptionsContext.tsx +14 -18
  177. package/src/form/combobox/types.ts +3 -1
  178. package/src/overlay/README.md +5 -0
  179. package/src/overlay/dismiss/DismissableLayer.tsx +368 -0
  180. package/src/overlay/dismiss/util/dispatchCustomEvent.ts +77 -0
  181. package/src/overlay/dismiss/util/useEscapeKeydown.ts +21 -0
  182. package/src/overlay/dismiss/util/useFocusOutside.ts +52 -0
  183. package/src/overlay/dismiss/util/usePointerDownOutside.ts +95 -0
  184. package/src/overlays/floating/Floating.tsx +399 -0
  185. package/src/overlays/floating/Floating.utils.ts +63 -0
  186. package/src/popover/Popover.tsx +38 -70
  187. package/src/progress-bar/ProgressBar.tsx +45 -20
  188. package/src/tabs/Tabs.context.ts +2 -0
  189. package/src/toggle-group/ToggleGroup.context.ts +1 -0
  190. package/src/util/hooks/descendants/useDescendant.tsx +55 -68
  191. package/src/util/types/AsChild.ts +15 -0
  192. package/cjs/form/combobox/ClearButton.d.ts +0 -7
  193. package/cjs/form/combobox/ClearButton.js +0 -28
  194. package/cjs/form/combobox/ClearButton.js.map +0 -1
  195. package/cjs/form/combobox/FilteredOptions/CheckIcon.d.ts +0 -3
  196. package/cjs/form/combobox/FilteredOptions/CheckIcon.js +0 -12
  197. package/cjs/form/combobox/FilteredOptions/CheckIcon.js.map +0 -1
  198. package/cjs/form/combobox/Input/inputContext.js.map +0 -1
  199. package/cjs/form/combobox/ToggleListButton.js.map +0 -1
  200. package/esm/form/combobox/ClearButton.d.ts +0 -7
  201. package/esm/form/combobox/ClearButton.js +0 -21
  202. package/esm/form/combobox/ClearButton.js.map +0 -1
  203. package/esm/form/combobox/FilteredOptions/CheckIcon.d.ts +0 -3
  204. package/esm/form/combobox/FilteredOptions/CheckIcon.js +0 -7
  205. package/esm/form/combobox/FilteredOptions/CheckIcon.js.map +0 -1
  206. package/esm/form/combobox/Input/inputContext.js.map +0 -1
  207. package/esm/form/combobox/ToggleListButton.js.map +0 -1
  208. package/src/form/combobox/ClearButton.tsx +0 -29
  209. package/src/form/combobox/FilteredOptions/CheckIcon.tsx +0 -23
  210. /package/cjs/form/combobox/{ToggleListButton.d.ts → Input/ToggleListButton.d.ts} +0 -0
  211. /package/esm/form/combobox/{ToggleListButton.d.ts → Input/ToggleListButton.d.ts} +0 -0
@@ -0,0 +1,399 @@
1
+ import {
2
+ Placement,
3
+ autoUpdate,
4
+ flip,
5
+ arrow as floatingArrow,
6
+ hide,
7
+ limitShift,
8
+ offset,
9
+ shift,
10
+ size,
11
+ useFloating,
12
+ } from "@floating-ui/react-dom";
13
+ import React, {
14
+ HTMLAttributes,
15
+ forwardRef,
16
+ useEffect,
17
+ useRef,
18
+ useState,
19
+ } from "react";
20
+ import { Slot } from "../../util/Slot";
21
+ import { createContext } from "../../util/create-context";
22
+ import {
23
+ useCallbackRef,
24
+ useClientLayoutEffect,
25
+ useMergeRefs,
26
+ } from "../../util/hooks";
27
+ import { AsChildProps } from "../../util/types";
28
+ import {
29
+ type Align,
30
+ type Measurable,
31
+ type Side,
32
+ getSideAndAlignFromPlacement,
33
+ transformOrigin,
34
+ } from "./Floating.utils";
35
+
36
+ /**
37
+ * Floating Root
38
+ */
39
+ type FloatingContextValue = {
40
+ anchor: Measurable | null;
41
+ onAnchorChange: (anchor: Measurable | null) => void;
42
+ };
43
+
44
+ export const [FloatingProvider, useFloatingContext] =
45
+ createContext<FloatingContextValue>({
46
+ name: "FloatingContext",
47
+ hookName: "useFloating",
48
+ providerName: "FloatingProvider",
49
+ });
50
+
51
+ interface FloatingProps {
52
+ children: React.ReactNode;
53
+ }
54
+
55
+ interface FloatingComponent extends React.FC<FloatingProps> {
56
+ Anchor: typeof FloatingAnchor;
57
+ Content: typeof FloatingContent;
58
+ }
59
+
60
+ const Floating: FloatingComponent = ({ children }: FloatingProps) => {
61
+ const [anchor, setAnchor] = useState<Measurable | null>(null);
62
+
63
+ return (
64
+ <FloatingProvider anchor={anchor} onAnchorChange={setAnchor}>
65
+ {children}
66
+ </FloatingProvider>
67
+ );
68
+ };
69
+
70
+ /**
71
+ * Floating Anchor
72
+ */
73
+ type FloatingAnchorProps = HTMLAttributes<HTMLDivElement> &
74
+ AsChildProps & {
75
+ virtualRef?: React.RefObject<Measurable>;
76
+ };
77
+
78
+ /**
79
+ * `FloatingAnchor` provides an anchor for a Floating instance.
80
+ * Allows anchoring to non-DOM nodes like a cursor position when used with `virtualRef`.
81
+ */
82
+ const FloatingAnchor = forwardRef<HTMLDivElement, FloatingAnchorProps>(
83
+ ({ virtualRef, asChild, ...rest }: FloatingAnchorProps, forwardedRef) => {
84
+ const context = useFloatingContext();
85
+ const ref = useRef<HTMLDivElement>(null);
86
+
87
+ const mergedRef = useMergeRefs(forwardedRef, ref);
88
+
89
+ useEffect(() => {
90
+ // Allows anchoring the floating to non-DOM nodes like a cursor position.
91
+ // We replace `anchorRef` with a virtual ref in such cases.
92
+ context.onAnchorChange(virtualRef?.current || ref.current);
93
+ });
94
+
95
+ const Comp = asChild ? Slot : "div";
96
+
97
+ return virtualRef ? null : <Comp ref={mergedRef} {...rest} />;
98
+ },
99
+ );
100
+
101
+ /**
102
+ * Floating Arrow
103
+ */
104
+ const OPPOSITE_SIDE: Record<Side, Side> = {
105
+ top: "bottom",
106
+ right: "left",
107
+ bottom: "top",
108
+ left: "right",
109
+ };
110
+
111
+ interface FloatingArrowProps {
112
+ className?: string;
113
+ width?: number;
114
+ height?: number;
115
+ }
116
+
117
+ const FloatingArrow = ({ width, height, className }: FloatingArrowProps) => {
118
+ const context = useFloatingContentContext();
119
+
120
+ const side = OPPOSITE_SIDE[context.placedSide];
121
+
122
+ return (
123
+ <span
124
+ ref={context.onArrowChange}
125
+ style={{
126
+ position: "absolute",
127
+ left: context.arrowX,
128
+ top: context.arrowY,
129
+ [side]: 0,
130
+ transformOrigin: {
131
+ top: "",
132
+ right: "0 0",
133
+ bottom: "center 0",
134
+ left: "100% 0",
135
+ }[context.placedSide],
136
+ transform: {
137
+ top: "translateY(100%)",
138
+ right: "translateY(50%) rotate(90deg) translateX(-50%)",
139
+ bottom: `rotate(180deg)`,
140
+ left: "translateY(50%) rotate(-90deg) translateX(50%)",
141
+ }[context.placedSide],
142
+ visibility: context.hideArrow ? "hidden" : undefined,
143
+ }}
144
+ aria-hidden
145
+ >
146
+ <svg
147
+ className={className}
148
+ width={width}
149
+ height={height}
150
+ viewBox="0 0 30 10"
151
+ preserveAspectRatio="none"
152
+ style={{ display: "block" }}
153
+ >
154
+ <polygon points="0,0 30,0 15,10" />
155
+ </svg>
156
+ </span>
157
+ );
158
+ };
159
+
160
+ /**
161
+ * Floating Content
162
+ */
163
+ type FloatingContentContextValue = {
164
+ placedSide: Side;
165
+ onArrowChange: (arrow: HTMLSpanElement | null) => void;
166
+ arrowX?: number;
167
+ arrowY?: number;
168
+ hideArrow: boolean;
169
+ };
170
+
171
+ const [FloatingContentProvider, useFloatingContentContext] =
172
+ createContext<FloatingContentContextValue>({
173
+ name: "FloatingContentContext",
174
+ hookName: "useFloatingContentContext",
175
+ providerName: "FloatingContentProvider",
176
+ });
177
+
178
+ type Boundary = Element | null;
179
+
180
+ interface FloatingContentProps extends HTMLAttributes<HTMLDivElement> {
181
+ side?: Side;
182
+ sideOffset?: number;
183
+ align?: Align;
184
+ alignOffset?: number;
185
+ avoidCollisions?: boolean;
186
+ collisionBoundary?: Boundary | Boundary[];
187
+ collisionPadding?: number | Partial<Record<Side, number>>;
188
+ hideWhenDetached?: boolean;
189
+ updatePositionStrategy?: "optimized" | "always";
190
+ onPlaced?: () => void;
191
+ arrow?: {
192
+ className?: string;
193
+ padding?: number;
194
+ width: number;
195
+ height: number;
196
+ };
197
+ }
198
+
199
+ const FloatingContent = forwardRef<HTMLDivElement, FloatingContentProps>(
200
+ (
201
+ {
202
+ children,
203
+ side = "bottom",
204
+ sideOffset = 0,
205
+ align = "center",
206
+ alignOffset = 0,
207
+ avoidCollisions = true,
208
+ collisionBoundary = [],
209
+ collisionPadding: collisionPaddingProp = 0,
210
+ hideWhenDetached = false,
211
+ updatePositionStrategy = "optimized",
212
+ onPlaced,
213
+ arrow: _arrow,
214
+ ...contentProps
215
+ }: FloatingContentProps,
216
+ forwardedRef,
217
+ ) => {
218
+ const context = useFloatingContext();
219
+
220
+ const [content, setContent] = useState<HTMLDivElement | null>(null);
221
+ const mergeRefs = useMergeRefs(forwardedRef, setContent);
222
+
223
+ const arrowDefaults = {
224
+ padding: 5,
225
+ width: 0,
226
+ height: 0,
227
+ ..._arrow,
228
+ };
229
+ const [arrow, setArrow] = useState<HTMLSpanElement | null>(null);
230
+ const arrowWidth = arrowDefaults.width;
231
+ const arrowHeight = arrowDefaults.height;
232
+
233
+ const desiredPlacement = (side +
234
+ (align !== "center" ? "-" + align : "")) as Placement;
235
+
236
+ const collisionPadding =
237
+ typeof collisionPaddingProp === "number"
238
+ ? collisionPaddingProp
239
+ : { top: 0, right: 0, bottom: 0, left: 0, ...collisionPaddingProp };
240
+
241
+ const boundary = Array.isArray(collisionBoundary)
242
+ ? collisionBoundary
243
+ : [collisionBoundary];
244
+
245
+ const hasExplicitBoundaries = boundary.length > 0;
246
+
247
+ /**
248
+ * .filter(x => x !== null) does not narrow the type of the array enough.
249
+ */
250
+ function isNotNull<T>(value: T | null): value is T {
251
+ return value !== null;
252
+ }
253
+
254
+ const detectOverflowOptions = {
255
+ padding: collisionPadding,
256
+ boundary: boundary.filter(isNotNull),
257
+ // with `strategy: 'fixed'`, this is the only way to get it to respect boundaries
258
+ altBoundary: hasExplicitBoundaries,
259
+ };
260
+
261
+ const { refs, floatingStyles, placement, isPositioned, middlewareData } =
262
+ useFloating({
263
+ // default to `fixed` strategy so users don't have to pick and we also avoid focus scroll issues
264
+ strategy: "fixed",
265
+ placement: desiredPlacement,
266
+ whileElementsMounted: (...args) => {
267
+ const cleanup = autoUpdate(...args, {
268
+ animationFrame: updatePositionStrategy === "always",
269
+ });
270
+ return cleanup;
271
+ },
272
+ elements: {
273
+ reference: context.anchor,
274
+ },
275
+ middleware: [
276
+ offset({
277
+ mainAxis: sideOffset + arrowHeight,
278
+ alignmentAxis: alignOffset,
279
+ }),
280
+ avoidCollisions &&
281
+ shift({
282
+ mainAxis: true,
283
+ crossAxis: false,
284
+ limiter: limitShift(),
285
+ ...detectOverflowOptions,
286
+ }),
287
+ avoidCollisions && flip({ ...detectOverflowOptions }),
288
+ size({
289
+ ...detectOverflowOptions,
290
+ apply: ({ elements, rects, availableWidth, availableHeight }) => {
291
+ const { width: anchorWidth, height: anchorHeight } =
292
+ rects.reference;
293
+ const contentStyle = elements.floating.style;
294
+ /**
295
+ * Allows styling and animations based on the available space.
296
+ */
297
+ contentStyle.setProperty(
298
+ "--ac-floating-available-width",
299
+ `${availableWidth}px`,
300
+ );
301
+ contentStyle.setProperty(
302
+ "--ac-floating-available-height",
303
+ `${availableHeight}px`,
304
+ );
305
+ contentStyle.setProperty(
306
+ "--ac-floating-anchor-width",
307
+ `${anchorWidth}px`,
308
+ );
309
+ contentStyle.setProperty(
310
+ "--ac-floating-anchor-height",
311
+ `${anchorHeight}px`,
312
+ );
313
+ },
314
+ }),
315
+ arrow &&
316
+ floatingArrow({ element: arrow, padding: arrowDefaults.padding }),
317
+ transformOrigin({ arrowWidth, arrowHeight }),
318
+ hideWhenDetached &&
319
+ hide({ strategy: "referenceHidden", ...detectOverflowOptions }),
320
+ ],
321
+ });
322
+
323
+ const [placedSide, placedAlign] = getSideAndAlignFromPlacement(placement);
324
+
325
+ const handlePlaced = useCallbackRef(onPlaced);
326
+
327
+ useClientLayoutEffect(() => {
328
+ isPositioned && handlePlaced?.();
329
+ }, [isPositioned, handlePlaced]);
330
+
331
+ const arrowX = middlewareData.arrow?.x;
332
+ const arrowY = middlewareData.arrow?.y;
333
+ const cannotCenterArrow = middlewareData.arrow?.centerOffset !== 0;
334
+
335
+ const [contentZIndex, setContentZIndex] = useState<string>();
336
+ useClientLayoutEffect(() => {
337
+ if (content) setContentZIndex(window.getComputedStyle(content).zIndex);
338
+ }, [content]);
339
+
340
+ return (
341
+ <div
342
+ ref={refs.setFloating}
343
+ data-aksel-floating-content-wrapper=""
344
+ style={{
345
+ ...floatingStyles,
346
+ transform: isPositioned
347
+ ? floatingStyles.transform
348
+ : "translate(0, -200%)", // keep off the page when measuring
349
+ minWidth: "max-content",
350
+ zIndex: contentZIndex,
351
+ ["--ac-floating-transform-origin" as any]: [
352
+ middlewareData.transformOrigin?.x,
353
+ middlewareData.transformOrigin?.y,
354
+ ].join(" "),
355
+ }}
356
+ // Floating UI uses the `dir` attribute on the reference/floating node for logical alignment.
357
+ // This attribute is necessary for both portalled and inline calculations.
358
+ dir="ltr"
359
+ >
360
+ <FloatingContentProvider
361
+ placedSide={placedSide}
362
+ onArrowChange={setArrow}
363
+ arrowX={arrowX}
364
+ arrowY={arrowY}
365
+ hideArrow={cannotCenterArrow}
366
+ >
367
+ <div
368
+ ref={mergeRefs}
369
+ data-side={placedSide}
370
+ data-align={placedAlign}
371
+ {...contentProps}
372
+ style={{
373
+ ...contentProps.style,
374
+ // if the FloatingContent hasn't been placed yet (not all measurements done)
375
+ // we prevent animations so that users's animation don't kick in too early referring wrong sides
376
+ animation: !isPositioned ? "none" : undefined,
377
+ // hide the content if using the hide middleware and should be hidden
378
+ opacity: middlewareData.hide?.referenceHidden ? 0 : undefined,
379
+ }}
380
+ >
381
+ {children}
382
+ {_arrow?.height && _arrow?.width && (
383
+ <FloatingArrow
384
+ width={_arrow.width}
385
+ height={_arrow.height}
386
+ className={_arrow.className}
387
+ />
388
+ )}
389
+ </div>
390
+ </FloatingContentProvider>
391
+ </div>
392
+ );
393
+ },
394
+ );
395
+
396
+ Floating.Anchor = FloatingAnchor;
397
+ Floating.Content = FloatingContent;
398
+
399
+ export { Floating };
@@ -0,0 +1,63 @@
1
+ import type { Middleware, Placement } from "@floating-ui/react-dom";
2
+
3
+ const SIDE_OPTIONS = ["top", "right", "bottom", "left"] as const;
4
+ const ALIGN_OPTIONS = ["start", "center", "end"] as const;
5
+
6
+ type Side = (typeof SIDE_OPTIONS)[number];
7
+ type Align = (typeof ALIGN_OPTIONS)[number];
8
+ type Measurable = { getBoundingClientRect(): DOMRect };
9
+
10
+ /**
11
+ * `transformOrigin` is a custom middleware for floating-ui that calculates the transform origin of the floating-element.
12
+ */
13
+ function transformOrigin(options: {
14
+ arrowWidth: number;
15
+ arrowHeight: number;
16
+ }): Middleware {
17
+ return {
18
+ name: "transformOrigin",
19
+ options,
20
+ fn(data) {
21
+ const { placement, rects, middlewareData } = data;
22
+
23
+ const cannotCenterArrow = middlewareData.arrow?.centerOffset !== 0;
24
+ const isArrowHidden = cannotCenterArrow;
25
+ const arrowWidth = isArrowHidden ? 0 : options.arrowWidth;
26
+ const arrowHeight = isArrowHidden ? 0 : options.arrowHeight;
27
+
28
+ const [placedSide, placedAlign] = getSideAndAlignFromPlacement(placement);
29
+ const noArrowAlign = { start: "0%", center: "50%", end: "100%" }[
30
+ placedAlign
31
+ ];
32
+
33
+ const arrowXCenter = (middlewareData.arrow?.x ?? 0) + arrowWidth / 2;
34
+ const arrowYCenter = (middlewareData.arrow?.y ?? 0) + arrowHeight / 2;
35
+
36
+ let x = "";
37
+ let y = "";
38
+
39
+ if (placedSide === "bottom") {
40
+ x = isArrowHidden ? noArrowAlign : `${arrowXCenter}px`;
41
+ y = `${-arrowHeight}px`;
42
+ } else if (placedSide === "top") {
43
+ x = isArrowHidden ? noArrowAlign : `${arrowXCenter}px`;
44
+ y = `${rects.floating.height + arrowHeight}px`;
45
+ } else if (placedSide === "right") {
46
+ x = `${-arrowHeight}px`;
47
+ y = isArrowHidden ? noArrowAlign : `${arrowYCenter}px`;
48
+ } else if (placedSide === "left") {
49
+ x = `${rects.floating.width + arrowHeight}px`;
50
+ y = isArrowHidden ? noArrowAlign : `${arrowYCenter}px`;
51
+ }
52
+ return { data: { x, y } };
53
+ },
54
+ };
55
+ }
56
+
57
+ function getSideAndAlignFromPlacement(placement: Placement) {
58
+ const [side, align = "center"] = placement.split("-");
59
+ return [side as Side, align as Align] as const;
60
+ }
61
+
62
+ export { getSideAndAlignFromPlacement, transformOrigin };
63
+ export type { Side, Align, Measurable };
@@ -4,22 +4,14 @@ import {
4
4
  offset as flOffset,
5
5
  flip,
6
6
  shift,
7
- useClick,
8
- useDismiss,
9
7
  useFloating,
10
- useInteractions,
11
8
  } from "@floating-ui/react";
12
9
  import cl from "clsx";
13
- import React, {
14
- HTMLAttributes,
15
- forwardRef,
16
- useCallback,
17
- useContext,
18
- useRef,
19
- } from "react";
10
+ import React, { HTMLAttributes, forwardRef, useContext, useRef } from "react";
20
11
  import { DateContext } from "../date/context";
21
12
  import { useModalContext } from "../modal/Modal.context";
22
- import { useClientLayoutEffect, useEventListener } from "../util/hooks";
13
+ import { DismissableLayer } from "../overlay/dismiss/DismissableLayer";
14
+ import { useClientLayoutEffect } from "../util/hooks";
23
15
  import { useMergeRefs } from "../util/hooks/useMergeRefs";
24
16
  import PopoverContent, { PopoverContentType } from "./PopoverContent";
25
17
 
@@ -133,19 +125,15 @@ export const Popover = forwardRef<HTMLDivElement, PopoverProps>(
133
125
  const chosenFlip = isInDatepicker ? false : _flip;
134
126
 
135
127
  const {
136
- x,
137
- y,
138
- strategy,
139
- context,
140
128
  update,
141
129
  refs,
142
130
  placement: flPlacement,
143
131
  middlewareData: { arrow: { x: arrowX, y: arrowY } = {} },
132
+ floatingStyles,
144
133
  } = useFloating({
145
134
  strategy: chosenStrategy,
146
135
  placement,
147
136
  open,
148
- onOpenChange: () => onClose(),
149
137
  middleware: [
150
138
  flOffset(offset ?? (arrow ? 16 : 4)),
151
139
  chosenFlip &&
@@ -155,11 +143,6 @@ export const Popover = forwardRef<HTMLDivElement, PopoverProps>(
155
143
  ],
156
144
  });
157
145
 
158
- const { getFloatingProps } = useInteractions([
159
- useClick(context),
160
- useDismiss(context),
161
- ]);
162
-
163
146
  useClientLayoutEffect(() => {
164
147
  refs.setReference(anchorEl);
165
148
  }, [anchorEl]);
@@ -176,24 +159,6 @@ export const Popover = forwardRef<HTMLDivElement, PopoverProps>(
176
159
  return () => cleanup();
177
160
  }, [refs.floating, refs.reference, update, open, anchorEl]);
178
161
 
179
- useEventListener(
180
- "focusin",
181
- useCallback(
182
- (e: FocusEvent) => {
183
- if (
184
- e.target instanceof HTMLElement &&
185
- ![anchorEl, refs.floating.current].some(
186
- (element) => element?.contains(e.target as Node),
187
- ) &&
188
- !e.target.contains(refs.floating.current)
189
- ) {
190
- open && onClose();
191
- }
192
- },
193
- [anchorEl, refs, open, onClose],
194
- ),
195
- );
196
-
197
162
  const staticSide = {
198
163
  top: "bottom",
199
164
  right: "left",
@@ -202,38 +167,41 @@ export const Popover = forwardRef<HTMLDivElement, PopoverProps>(
202
167
  }[flPlacement.split("-")[0]];
203
168
 
204
169
  return (
205
- <div
206
- className={cl("navds-popover", className, {
207
- "navds-popover--hidden": !open || !anchorEl,
208
- })}
209
- data-placement={flPlacement}
210
- aria-hidden={!open || !anchorEl}
211
- {...getFloatingProps({
212
- ref: floatingRef,
213
- style: {
214
- position: strategy,
215
- top: y ?? 0,
216
- left: x ?? 0,
217
- },
218
- tabIndex: undefined,
219
- })}
220
- {...rest}
170
+ <DismissableLayer
171
+ asChild
172
+ safeZone={{
173
+ anchor: anchorEl,
174
+ dismissable: refs.floating.current,
175
+ }}
176
+ onDismiss={() => open && onClose?.()}
177
+ enabled={open}
221
178
  >
222
- {children}
223
- {arrow && (
224
- <div
225
- ref={(node) => {
226
- arrowRef.current = node;
227
- }}
228
- style={{
229
- ...(arrowX != null ? { left: arrowX } : {}),
230
- ...(arrowY != null ? { top: arrowY } : {}),
231
- ...(staticSide ? { [staticSide]: "-0.5rem" } : {}),
232
- }}
233
- className="navds-popover__arrow"
234
- />
235
- )}
236
- </div>
179
+ <div
180
+ ref={floatingRef}
181
+ {...rest}
182
+ className={cl("navds-popover", className, {
183
+ "navds-popover--hidden": !open || !anchorEl,
184
+ })}
185
+ style={{ ...rest.style, ...floatingStyles }}
186
+ data-placement={flPlacement}
187
+ aria-hidden={!open || !anchorEl}
188
+ >
189
+ {children}
190
+ {arrow && (
191
+ <div
192
+ ref={(node) => {
193
+ arrowRef.current = node;
194
+ }}
195
+ style={{
196
+ ...(arrowX != null ? { left: arrowX } : {}),
197
+ ...(arrowY != null ? { top: arrowY } : {}),
198
+ ...(staticSide ? { [staticSide]: "-0.5rem" } : {}),
199
+ }}
200
+ className="navds-popover__arrow"
201
+ />
202
+ )}
203
+ </div>
204
+ </DismissableLayer>
237
205
  );
238
206
  },
239
207
  ) as PopoverComponent;