@remix-run/ui 0.0.0 → 0.1.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 (272) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +195 -2
  3. package/dist/animation/animate-layout-mixin.d.ts +12 -0
  4. package/dist/animation/animate-layout-mixin.js +214 -0
  5. package/dist/animation/animate-layout-mixin.js.map +1 -0
  6. package/dist/animation/animate-mixins.d.ts +29 -0
  7. package/dist/animation/animate-mixins.js +235 -0
  8. package/dist/animation/animate-mixins.js.map +1 -0
  9. package/dist/animation/index.d.ts +6 -0
  10. package/dist/animation/index.js +5 -0
  11. package/dist/animation/index.js.map +1 -0
  12. package/dist/animation/spring.d.ts +75 -0
  13. package/dist/animation/spring.js +183 -0
  14. package/dist/animation/spring.js.map +1 -0
  15. package/dist/animation/tween.d.ts +70 -0
  16. package/dist/animation/tween.js +93 -0
  17. package/dist/animation/tween.js.map +1 -0
  18. package/dist/components/accordion/accordion.d.ts +92 -0
  19. package/dist/components/accordion/accordion.js +337 -0
  20. package/dist/components/accordion/accordion.js.map +1 -0
  21. package/dist/components/anchor/anchor.d.ts +13 -0
  22. package/dist/components/anchor/anchor.js +558 -0
  23. package/dist/components/anchor/anchor.js.map +1 -0
  24. package/dist/components/breadcrumbs/breadcrumbs.d.ts +11 -0
  25. package/dist/components/breadcrumbs/breadcrumbs.js +78 -0
  26. package/dist/components/breadcrumbs/breadcrumbs.js.map +1 -0
  27. package/dist/components/button/button.d.ts +23 -0
  28. package/dist/components/button/button.js +147 -0
  29. package/dist/components/button/button.js.map +1 -0
  30. package/dist/components/combobox/combobox.d.ts +101 -0
  31. package/dist/components/combobox/combobox.js +708 -0
  32. package/dist/components/combobox/combobox.js.map +1 -0
  33. package/dist/components/glyph/glyph.d.ts +14 -0
  34. package/dist/components/glyph/glyph.js +65 -0
  35. package/dist/components/glyph/glyph.js.map +1 -0
  36. package/dist/components/listbox/listbox.d.ts +67 -0
  37. package/dist/components/listbox/listbox.js +340 -0
  38. package/dist/components/listbox/listbox.js.map +1 -0
  39. package/dist/components/menu/hover-aim.d.ts +5 -0
  40. package/dist/components/menu/hover-aim.js +308 -0
  41. package/dist/components/menu/hover-aim.js.map +1 -0
  42. package/dist/components/menu/menu.d.ts +164 -0
  43. package/dist/components/menu/menu.js +1106 -0
  44. package/dist/components/menu/menu.js.map +1 -0
  45. package/dist/components/popover/popover.d.ts +35 -0
  46. package/dist/components/popover/popover.js +138 -0
  47. package/dist/components/popover/popover.js.map +1 -0
  48. package/dist/components/select/select.d.ts +67 -0
  49. package/dist/components/select/select.js +397 -0
  50. package/dist/components/select/select.js.map +1 -0
  51. package/dist/components/separator/separator.d.ts +2 -0
  52. package/dist/components/separator/separator.js +15 -0
  53. package/dist/components/separator/separator.js.map +1 -0
  54. package/dist/components/tabs/tabs.d.ts +78 -0
  55. package/dist/components/tabs/tabs.js +341 -0
  56. package/dist/components/tabs/tabs.js.map +1 -0
  57. package/dist/index.d.ts +28 -0
  58. package/dist/index.js +21 -0
  59. package/dist/index.js.map +1 -0
  60. package/dist/interactions/keydown/keydown.d.ts +1 -0
  61. package/dist/interactions/keydown/keydown.js +8 -0
  62. package/dist/interactions/keydown/keydown.js.map +1 -0
  63. package/dist/interactions/outside-click/outside-click-mixin.d.ts +2 -0
  64. package/dist/interactions/outside-click/outside-click-mixin.js +29 -0
  65. package/dist/interactions/outside-click/outside-click-mixin.js.map +1 -0
  66. package/dist/interactions/typeahead/typeahead-mixin.d.ts +8 -0
  67. package/dist/interactions/typeahead/typeahead-mixin.js +59 -0
  68. package/dist/interactions/typeahead/typeahead-mixin.js.map +1 -0
  69. package/dist/jsx-runtime.d.ts +2 -0
  70. package/dist/jsx-runtime.js +3 -0
  71. package/dist/jsx-runtime.js.map +1 -0
  72. package/dist/runtime/client-entries.d.ts +91 -0
  73. package/dist/runtime/client-entries.js +40 -0
  74. package/dist/runtime/client-entries.js.map +1 -0
  75. package/dist/runtime/component.d.ts +223 -0
  76. package/dist/runtime/component.js +152 -0
  77. package/dist/runtime/component.js.map +1 -0
  78. package/dist/runtime/create-element.d.ts +10 -0
  79. package/dist/runtime/create-element.js +29 -0
  80. package/dist/runtime/create-element.js.map +1 -0
  81. package/dist/runtime/diff-dom.d.ts +2 -0
  82. package/dist/runtime/diff-dom.js +364 -0
  83. package/dist/runtime/diff-dom.js.map +1 -0
  84. package/dist/runtime/diff-props.d.ts +9 -0
  85. package/dist/runtime/diff-props.js +195 -0
  86. package/dist/runtime/diff-props.js.map +1 -0
  87. package/dist/runtime/document-state.d.ts +11 -0
  88. package/dist/runtime/document-state.js +106 -0
  89. package/dist/runtime/document-state.js.map +1 -0
  90. package/dist/runtime/dom.d.ts +3189 -0
  91. package/dist/runtime/dom.js +2 -0
  92. package/dist/runtime/dom.js.map +1 -0
  93. package/dist/runtime/error-event.d.ts +20 -0
  94. package/dist/runtime/error-event.js +19 -0
  95. package/dist/runtime/error-event.js.map +1 -0
  96. package/dist/runtime/event-listeners.d.ts +50 -0
  97. package/dist/runtime/event-listeners.js +31 -0
  98. package/dist/runtime/event-listeners.js.map +1 -0
  99. package/dist/runtime/frame.d.ts +119 -0
  100. package/dist/runtime/frame.js +920 -0
  101. package/dist/runtime/frame.js.map +1 -0
  102. package/dist/runtime/invariant.d.ts +23 -0
  103. package/dist/runtime/invariant.js +33 -0
  104. package/dist/runtime/invariant.js.map +1 -0
  105. package/dist/runtime/jsx.d.ts +320 -0
  106. package/dist/runtime/jsx.js +32 -0
  107. package/dist/runtime/jsx.js.map +1 -0
  108. package/dist/runtime/mixins/attrs-mixin.d.ts +9 -0
  109. package/dist/runtime/mixins/attrs-mixin.js +23 -0
  110. package/dist/runtime/mixins/attrs-mixin.js.map +1 -0
  111. package/dist/runtime/mixins/link-mixin.d.ts +17 -0
  112. package/dist/runtime/mixins/link-mixin.js +101 -0
  113. package/dist/runtime/mixins/link-mixin.js.map +1 -0
  114. package/dist/runtime/mixins/mixin.d.ts +145 -0
  115. package/dist/runtime/mixins/mixin.js +570 -0
  116. package/dist/runtime/mixins/mixin.js.map +1 -0
  117. package/dist/runtime/mixins/on-mixin.d.ts +16 -0
  118. package/dist/runtime/mixins/on-mixin.js +49 -0
  119. package/dist/runtime/mixins/on-mixin.js.map +1 -0
  120. package/dist/runtime/mixins/ref-mixin.d.ts +9 -0
  121. package/dist/runtime/mixins/ref-mixin.js +21 -0
  122. package/dist/runtime/mixins/ref-mixin.js.map +1 -0
  123. package/dist/runtime/navigation.d.ts +28 -0
  124. package/dist/runtime/navigation.js +107 -0
  125. package/dist/runtime/navigation.js.map +1 -0
  126. package/dist/runtime/reconcile.d.ts +11 -0
  127. package/dist/runtime/reconcile.js +1262 -0
  128. package/dist/runtime/reconcile.js.map +1 -0
  129. package/dist/runtime/render.d.ts +54 -0
  130. package/dist/runtime/render.js +50 -0
  131. package/dist/runtime/render.js.map +1 -0
  132. package/dist/runtime/run.d.ts +45 -0
  133. package/dist/runtime/run.js +68 -0
  134. package/dist/runtime/run.js.map +1 -0
  135. package/dist/runtime/scheduler.d.ts +30 -0
  136. package/dist/runtime/scheduler.js +195 -0
  137. package/dist/runtime/scheduler.js.map +1 -0
  138. package/dist/runtime/svg-attributes.d.ts +5 -0
  139. package/dist/runtime/svg-attributes.js +124 -0
  140. package/dist/runtime/svg-attributes.js.map +1 -0
  141. package/dist/runtime/to-vnode.d.ts +3 -0
  142. package/dist/runtime/to-vnode.js +46 -0
  143. package/dist/runtime/to-vnode.js.map +1 -0
  144. package/dist/runtime/typed-event-target.d.ts +50 -0
  145. package/dist/runtime/typed-event-target.js +6 -0
  146. package/dist/runtime/typed-event-target.js.map +1 -0
  147. package/dist/runtime/vdom.d.ts +55 -0
  148. package/dist/runtime/vdom.js +198 -0
  149. package/dist/runtime/vdom.js.map +1 -0
  150. package/dist/runtime/vnode.d.ts +79 -0
  151. package/dist/runtime/vnode.js +38 -0
  152. package/dist/runtime/vnode.js.map +1 -0
  153. package/dist/server/stream.d.ts +57 -0
  154. package/dist/server/stream.js +1011 -0
  155. package/dist/server/stream.js.map +1 -0
  156. package/dist/style/css-mixin.d.ts +8 -0
  157. package/dist/style/css-mixin.js +54 -0
  158. package/dist/style/css-mixin.js.map +1 -0
  159. package/dist/style/index.d.ts +5 -0
  160. package/dist/style/index.js +4 -0
  161. package/dist/style/index.js.map +1 -0
  162. package/dist/style/style.d.ts +24 -0
  163. package/dist/style/style.js +269 -0
  164. package/dist/style/style.js.map +1 -0
  165. package/dist/style/stylesheet.d.ts +11 -0
  166. package/dist/style/stylesheet.js +174 -0
  167. package/dist/style/stylesheet.js.map +1 -0
  168. package/dist/test.d.ts +2 -0
  169. package/dist/test.js +2 -0
  170. package/dist/test.js.map +1 -0
  171. package/dist/theme/contract.d.ts +259 -0
  172. package/dist/theme/contract.js +131 -0
  173. package/dist/theme/contract.js.map +1 -0
  174. package/dist/theme/glyph-contract.d.ts +13 -0
  175. package/dist/theme/glyph-contract.js +34 -0
  176. package/dist/theme/glyph-contract.js.map +1 -0
  177. package/dist/theme/presets/rmx-01/glyphs.d.ts +2 -0
  178. package/dist/theme/presets/rmx-01/glyphs.js +252 -0
  179. package/dist/theme/presets/rmx-01/glyphs.js.map +1 -0
  180. package/dist/theme/presets/rmx-01/index.d.ts +2 -0
  181. package/dist/theme/presets/rmx-01/index.js +119 -0
  182. package/dist/theme/presets/rmx-01/index.js.map +1 -0
  183. package/dist/theme/runtime.d.ts +2 -0
  184. package/dist/theme/runtime.js +81 -0
  185. package/dist/theme/runtime.js.map +1 -0
  186. package/dist/theme/theme.d.ts +6 -0
  187. package/dist/theme/theme.js +5 -0
  188. package/dist/theme/theme.js.map +1 -0
  189. package/dist/utils/flash-attribute.d.ts +1 -0
  190. package/dist/utils/flash-attribute.js +11 -0
  191. package/dist/utils/flash-attribute.js.map +1 -0
  192. package/dist/utils/scroll-lock.d.ts +3 -0
  193. package/dist/utils/scroll-lock.js +69 -0
  194. package/dist/utils/scroll-lock.js.map +1 -0
  195. package/dist/utils/wait-for-css-transition.d.ts +1 -0
  196. package/dist/utils/wait-for-css-transition.js +58 -0
  197. package/dist/utils/wait-for-css-transition.js.map +1 -0
  198. package/dist/utils/wait.d.ts +1 -0
  199. package/dist/utils/wait.js +6 -0
  200. package/dist/utils/wait.js.map +1 -0
  201. package/package.json +124 -5
  202. package/src/animation/animate-layout-mixin.ts +266 -0
  203. package/src/animation/animate-mixins.ts +292 -0
  204. package/src/animation/index.ts +6 -0
  205. package/src/animation/spring.ts +299 -0
  206. package/src/animation/tween.ts +134 -0
  207. package/src/components/accordion/accordion.tsx +565 -0
  208. package/src/components/anchor/anchor.ts +904 -0
  209. package/src/components/breadcrumbs/breadcrumbs.tsx +124 -0
  210. package/src/components/button/README.md +44 -0
  211. package/src/components/button/button.tsx +188 -0
  212. package/src/components/combobox/README.md +145 -0
  213. package/src/components/combobox/combobox.tsx +1014 -0
  214. package/src/components/glyph/glyph.tsx +110 -0
  215. package/src/components/listbox/listbox.ts +447 -0
  216. package/src/components/menu/hover-aim.ts +428 -0
  217. package/src/components/menu/menu.tsx +1547 -0
  218. package/src/components/popover/README.md +122 -0
  219. package/src/components/popover/popover.ts +201 -0
  220. package/src/components/select/select.tsx +585 -0
  221. package/src/components/separator/separator.ts +19 -0
  222. package/src/components/tabs/README.md +105 -0
  223. package/src/components/tabs/tabs.tsx +499 -0
  224. package/src/index.ts +72 -0
  225. package/src/interactions/keydown/keydown.ts +14 -0
  226. package/src/interactions/outside-click/outside-click-mixin.ts +55 -0
  227. package/src/interactions/typeahead/typeahead-mixin.ts +89 -0
  228. package/src/jsx-runtime.ts +2 -0
  229. package/src/runtime/client-entries.ts +137 -0
  230. package/src/runtime/component.ts +402 -0
  231. package/src/runtime/create-element.ts +38 -0
  232. package/src/runtime/diff-dom.ts +404 -0
  233. package/src/runtime/diff-props.ts +209 -0
  234. package/src/runtime/document-state.ts +130 -0
  235. package/src/runtime/dom.ts +3971 -0
  236. package/src/runtime/error-event.ts +26 -0
  237. package/src/runtime/event-listeners.ts +171 -0
  238. package/src/runtime/frame.ts +1218 -0
  239. package/src/runtime/invariant.ts +31 -0
  240. package/src/runtime/jsx.ts +397 -0
  241. package/src/runtime/mixins/attrs-mixin.ts +35 -0
  242. package/src/runtime/mixins/link-mixin.ts +131 -0
  243. package/src/runtime/mixins/mixin.ts +908 -0
  244. package/src/runtime/mixins/on-mixin.ts +89 -0
  245. package/src/runtime/mixins/ref-mixin.ts +32 -0
  246. package/src/runtime/navigation.ts +148 -0
  247. package/src/runtime/reconcile.ts +1757 -0
  248. package/src/runtime/render.ts +101 -0
  249. package/src/runtime/run.ts +100 -0
  250. package/src/runtime/scheduler.ts +269 -0
  251. package/src/runtime/svg-attributes.ts +133 -0
  252. package/src/runtime/to-vnode.ts +51 -0
  253. package/src/runtime/typed-event-target.ts +67 -0
  254. package/src/runtime/vdom.ts +295 -0
  255. package/src/runtime/vnode.ts +137 -0
  256. package/src/server/stream.ts +1347 -0
  257. package/src/style/css-mixin.ts +82 -0
  258. package/src/style/index.ts +10 -0
  259. package/src/style/style.ts +312 -0
  260. package/src/style/stylesheet.ts +209 -0
  261. package/src/test/utils.ts +45 -0
  262. package/src/test.ts +2 -0
  263. package/src/theme/contract.ts +183 -0
  264. package/src/theme/glyph-contract.ts +57 -0
  265. package/src/theme/presets/rmx-01/glyphs.tsx +308 -0
  266. package/src/theme/presets/rmx-01/index.ts +121 -0
  267. package/src/theme/runtime.ts +116 -0
  268. package/src/theme/theme.ts +15 -0
  269. package/src/utils/flash-attribute.ts +11 -0
  270. package/src/utils/scroll-lock.ts +97 -0
  271. package/src/utils/wait-for-css-transition.ts +66 -0
  272. package/src/utils/wait.ts +5 -0
@@ -0,0 +1,904 @@
1
+ type ExtendedAnchorPlacement =
2
+ | 'top'
3
+ | 'top-start'
4
+ | 'top-end'
5
+ | 'bottom'
6
+ | 'bottom-start'
7
+ | 'bottom-end'
8
+ | 'left'
9
+ | 'left-start'
10
+ | 'left-end'
11
+ | 'right'
12
+ | 'right-start'
13
+ | 'right-end'
14
+
15
+ export type AnchorPlacement =
16
+ | 'top'
17
+ | 'bottom'
18
+ | 'left'
19
+ | 'right'
20
+ | 'top-start'
21
+ | 'top-end'
22
+ | 'bottom-start'
23
+ | 'bottom-end'
24
+
25
+ type AnchorOffsetValue = number | ((floating: HTMLElement) => number)
26
+
27
+ export type AnchorOptions = {
28
+ placement?: ExtendedAnchorPlacement
29
+ inset?: boolean
30
+ relativeTo?: string
31
+ offset?: AnchorOffsetValue
32
+ offsetX?: AnchorOffsetValue
33
+ offsetY?: AnchorOffsetValue
34
+ }
35
+
36
+ const viewportPaddingPx = 16
37
+
38
+ function isHorizontalPlacement(placement: ExtendedAnchorPlacement) {
39
+ return placement.startsWith('left') || placement.startsWith('right')
40
+ }
41
+
42
+ function isVerticalPlacement(placement: ExtendedAnchorPlacement) {
43
+ return placement.startsWith('top') || placement.startsWith('bottom')
44
+ }
45
+
46
+ function constrainToAxis(
47
+ desiredPosition: number,
48
+ elementSize: number,
49
+ minBound: number,
50
+ maxBound: number,
51
+ ) {
52
+ let elementEnd = desiredPosition + elementSize
53
+
54
+ if (elementEnd > maxBound) {
55
+ desiredPosition -= elementEnd - maxBound
56
+ }
57
+
58
+ if (desiredPosition < minBound) {
59
+ desiredPosition = minBound
60
+ }
61
+
62
+ return desiredPosition
63
+ }
64
+
65
+ function clamp(value: number, min: number, max: number) {
66
+ return Math.min(Math.max(value, min), max)
67
+ }
68
+
69
+ function getConstrainedPositionRange(elementSize: number, minBound: number, maxBound: number) {
70
+ return {
71
+ min: minBound,
72
+ max: Math.max(minBound, maxBound - elementSize),
73
+ }
74
+ }
75
+
76
+ function getOffScreenStatus(
77
+ anchorRect: DOMRect,
78
+ axis: 'horizontal' | 'vertical',
79
+ ): 'before' | 'after' | 'visible' {
80
+ if (axis === 'horizontal') {
81
+ if (anchorRect.right <= 0) return 'before'
82
+ if (anchorRect.left >= window.innerWidth) return 'after'
83
+ return 'visible'
84
+ }
85
+
86
+ if (anchorRect.bottom <= 0) return 'before'
87
+ if (anchorRect.top >= window.innerHeight) return 'after'
88
+ return 'visible'
89
+ }
90
+
91
+ function applyOffset(
92
+ position: { top: number; left: number },
93
+ placement: ExtendedAnchorPlacement,
94
+ offset: number,
95
+ offsetX = 0,
96
+ offsetY = 0,
97
+ ) {
98
+ if (offset === 0 && offsetX === 0 && offsetY === 0) {
99
+ return position
100
+ }
101
+
102
+ let { top, left } = position
103
+
104
+ if (placement.startsWith('top')) {
105
+ top -= offset
106
+ } else if (placement.startsWith('bottom')) {
107
+ top += offset
108
+ } else if (placement.startsWith('left')) {
109
+ left -= offset
110
+ } else if (placement.startsWith('right')) {
111
+ left += offset
112
+ }
113
+
114
+ left += offsetX
115
+ top += offsetY
116
+
117
+ return { top, left }
118
+ }
119
+
120
+ function resolveOffsetValue(offset: AnchorOffsetValue | undefined, floating: HTMLElement) {
121
+ if (typeof offset === 'function') {
122
+ return offset(floating)
123
+ }
124
+
125
+ return offset ?? 0
126
+ }
127
+
128
+ function calculatePosition(
129
+ placement: ExtendedAnchorPlacement,
130
+ anchorRect: DOMRect,
131
+ floatingWidth: number,
132
+ floatingHeight: number,
133
+ inset = false,
134
+ ) {
135
+ let top = anchorRect.bottom
136
+ let left = anchorRect.left + (anchorRect.width - floatingWidth) / 2
137
+
138
+ if (placement === 'bottom-start') {
139
+ left = anchorRect.left
140
+ } else if (placement === 'bottom-end') {
141
+ left = anchorRect.right - floatingWidth
142
+ } else if (placement === 'top') {
143
+ top = anchorRect.top - floatingHeight
144
+ } else if (placement === 'top-start') {
145
+ top = anchorRect.top - floatingHeight
146
+ left = anchorRect.left
147
+ } else if (placement === 'top-end') {
148
+ top = anchorRect.top - floatingHeight
149
+ left = anchorRect.right - floatingWidth
150
+ } else if (placement === 'left') {
151
+ top = anchorRect.top + (anchorRect.height - floatingHeight) / 2
152
+ left = anchorRect.left - floatingWidth
153
+ } else if (placement === 'left-start') {
154
+ top = anchorRect.top
155
+ left = anchorRect.left - floatingWidth
156
+ } else if (placement === 'left-end') {
157
+ top = anchorRect.bottom - floatingHeight
158
+ left = anchorRect.left - floatingWidth
159
+ } else if (placement === 'right') {
160
+ top = anchorRect.top + (anchorRect.height - floatingHeight) / 2
161
+ left = anchorRect.right
162
+ } else if (placement === 'right-start') {
163
+ top = anchorRect.top
164
+ left = anchorRect.right
165
+ } else if (placement === 'right-end') {
166
+ top = anchorRect.bottom - floatingHeight
167
+ left = anchorRect.right
168
+ }
169
+
170
+ if (inset) {
171
+ if (placement.startsWith('bottom')) {
172
+ top = anchorRect.bottom - floatingHeight
173
+ } else if (placement.startsWith('top')) {
174
+ top = anchorRect.top
175
+ } else if (placement.startsWith('left')) {
176
+ left = anchorRect.left
177
+ } else if (placement.startsWith('right')) {
178
+ left = anchorRect.right - floatingWidth
179
+ }
180
+ }
181
+
182
+ return { top, left }
183
+ }
184
+
185
+ function isScrollableOverflow(value: string) {
186
+ return value === 'auto' || value === 'scroll' || value === 'overlay'
187
+ }
188
+
189
+ function getRelativeScrollContainer(floating: HTMLElement, relativeElement: HTMLElement) {
190
+ let current: HTMLElement | null = relativeElement.parentElement
191
+
192
+ while (current && current !== floating) {
193
+ let style = getComputedStyle(current)
194
+ if (isScrollableOverflow(style.overflowX) || isScrollableOverflow(style.overflowY)) {
195
+ return current
196
+ }
197
+ current = current.parentElement
198
+ }
199
+
200
+ return floating
201
+ }
202
+
203
+ function readFloatingDimensions(floating: HTMLElement, relativeTo?: string) {
204
+ let relativeElement = relativeTo ? floating.querySelector(relativeTo) : null
205
+ if (relativeElement && !(relativeElement instanceof HTMLElement)) {
206
+ relativeElement = null
207
+ }
208
+
209
+ let scrollContainer = relativeElement
210
+ ? getRelativeScrollContainer(floating, relativeElement)
211
+ : floating
212
+ let relativeOffsetX: number | null = null
213
+ let relativeOffsetY: number | null = null
214
+ let scrollContainerOffsetX = 0
215
+ let scrollContainerOffsetY = 0
216
+
217
+ if (relativeElement instanceof HTMLElement) {
218
+ let floatingRect = floating.getBoundingClientRect()
219
+ let scrollContainerRect = scrollContainer.getBoundingClientRect()
220
+ let relativeRect = relativeElement.getBoundingClientRect()
221
+ scrollContainerOffsetX = scrollContainerRect.left - floatingRect.left
222
+ scrollContainerOffsetY = scrollContainerRect.top - floatingRect.top
223
+ relativeOffsetX = relativeRect.left - scrollContainerRect.left + scrollContainer.scrollLeft
224
+ relativeOffsetY = relativeRect.top - scrollContainerRect.top + scrollContainer.scrollTop
225
+ }
226
+
227
+ return {
228
+ width: floating.offsetWidth,
229
+ height: floating.offsetHeight,
230
+ scrollContainer,
231
+ scrollContainerOffsetX,
232
+ scrollContainerOffsetY,
233
+ scrollViewportWidth: scrollContainer.offsetWidth,
234
+ scrollViewportHeight: scrollContainer.offsetHeight,
235
+ scrollWidth: Math.max(scrollContainer.scrollWidth, scrollContainer.offsetWidth),
236
+ scrollHeight: Math.max(scrollContainer.scrollHeight, scrollContainer.offsetHeight),
237
+ relativeWidth: relativeElement instanceof HTMLElement ? relativeElement.offsetWidth : null,
238
+ relativeHeight: relativeElement instanceof HTMLElement ? relativeElement.offsetHeight : null,
239
+ relativeElement,
240
+ relativeOffsetX,
241
+ relativeOffsetY,
242
+ }
243
+ }
244
+
245
+ function getFloatingDimensions(
246
+ floating: HTMLElement,
247
+ relativeTo?: string,
248
+ { ignoreInlineMaxSize = false }: { ignoreInlineMaxSize?: boolean } = {},
249
+ ) {
250
+ let originalPosition = floating.style.position
251
+ let originalLeft = floating.style.left
252
+ let originalDisplay = floating.style.display
253
+ let originalMaxWidth = floating.style.maxWidth
254
+ let originalMaxHeight = floating.style.maxHeight
255
+
256
+ if (ignoreInlineMaxSize) {
257
+ floating.style.maxWidth = ''
258
+ floating.style.maxHeight = ''
259
+ }
260
+
261
+ let needsTemporaryLayout = floating.offsetWidth === 0
262
+ if (needsTemporaryLayout) {
263
+ floating.style.position = 'absolute'
264
+ floating.style.left = '-9999px'
265
+ floating.style.display = 'block'
266
+ }
267
+
268
+ let dimensions = readFloatingDimensions(floating, relativeTo)
269
+
270
+ if (needsTemporaryLayout) {
271
+ floating.style.position = originalPosition
272
+ floating.style.left = originalLeft
273
+ floating.style.display = originalDisplay
274
+ }
275
+
276
+ if (ignoreInlineMaxSize) {
277
+ floating.style.maxWidth = originalMaxWidth
278
+ floating.style.maxHeight = originalMaxHeight
279
+ }
280
+
281
+ return dimensions
282
+ }
283
+
284
+ type FloatingDimensions = ReturnType<typeof getFloatingDimensions>
285
+
286
+ function getClientViewportBounds(padding = 0) {
287
+ return {
288
+ left: padding,
289
+ top: padding,
290
+ right: window.innerWidth - padding,
291
+ bottom: window.innerHeight - padding,
292
+ }
293
+ }
294
+
295
+ function getDocumentViewportBounds(isFixed: boolean, padding = 0) {
296
+ if (isFixed) {
297
+ return getClientViewportBounds(padding)
298
+ }
299
+
300
+ return {
301
+ left: window.scrollX + padding,
302
+ top: window.scrollY + padding,
303
+ right: window.scrollX + window.innerWidth - padding,
304
+ bottom: window.scrollY + window.innerHeight - padding,
305
+ }
306
+ }
307
+
308
+ function getOverflowAmount(
309
+ bounds: { left: number; top: number; right: number; bottom: number },
310
+ viewport: { left: number; top: number; right: number; bottom: number },
311
+ ) {
312
+ return (
313
+ Math.max(viewport.left - bounds.left, 0) +
314
+ Math.max(bounds.right - viewport.right, 0) +
315
+ Math.max(viewport.top - bounds.top, 0) +
316
+ Math.max(bounds.bottom - viewport.bottom, 0)
317
+ )
318
+ }
319
+
320
+ function hasRectChanged(currentRect: DOMRect, previousRect: DOMRect) {
321
+ return (
322
+ Math.abs(currentRect.top - previousRect.top) >= 1 ||
323
+ Math.abs(currentRect.left - previousRect.left) >= 1 ||
324
+ Math.abs(currentRect.right - previousRect.right) >= 1 ||
325
+ Math.abs(currentRect.bottom - previousRect.bottom) >= 1
326
+ )
327
+ }
328
+
329
+ function hasNullableNumberChanged(current: number | null, previous: number | null) {
330
+ if (current === null || previous === null) {
331
+ return current !== previous
332
+ }
333
+
334
+ return Math.abs(current - previous) >= 1
335
+ }
336
+
337
+ function hasFloatingDimensionsChanged(
338
+ currentDimensions: FloatingDimensions,
339
+ previousDimensions: FloatingDimensions,
340
+ ) {
341
+ return (
342
+ Math.abs(currentDimensions.width - previousDimensions.width) >= 1 ||
343
+ Math.abs(currentDimensions.height - previousDimensions.height) >= 1 ||
344
+ Math.abs(currentDimensions.scrollViewportWidth - previousDimensions.scrollViewportWidth) >= 1 ||
345
+ Math.abs(currentDimensions.scrollViewportHeight - previousDimensions.scrollViewportHeight) >=
346
+ 1 ||
347
+ Math.abs(currentDimensions.scrollWidth - previousDimensions.scrollWidth) >= 1 ||
348
+ Math.abs(currentDimensions.scrollHeight - previousDimensions.scrollHeight) >= 1 ||
349
+ Math.abs(
350
+ currentDimensions.scrollContainerOffsetX - previousDimensions.scrollContainerOffsetX,
351
+ ) >= 1 ||
352
+ Math.abs(
353
+ currentDimensions.scrollContainerOffsetY - previousDimensions.scrollContainerOffsetY,
354
+ ) >= 1 ||
355
+ hasNullableNumberChanged(currentDimensions.relativeWidth, previousDimensions.relativeWidth) ||
356
+ hasNullableNumberChanged(currentDimensions.relativeHeight, previousDimensions.relativeHeight) ||
357
+ hasNullableNumberChanged(
358
+ currentDimensions.relativeOffsetX,
359
+ previousDimensions.relativeOffsetX,
360
+ ) ||
361
+ hasNullableNumberChanged(currentDimensions.relativeOffsetY, previousDimensions.relativeOffsetY)
362
+ )
363
+ }
364
+
365
+ function getOppositePlacement(placement: ExtendedAnchorPlacement): ExtendedAnchorPlacement {
366
+ let opposites: Record<ExtendedAnchorPlacement, ExtendedAnchorPlacement> = {
367
+ top: 'bottom',
368
+ 'top-start': 'bottom-start',
369
+ 'top-end': 'bottom-end',
370
+ bottom: 'top',
371
+ 'bottom-start': 'top-start',
372
+ 'bottom-end': 'top-end',
373
+ left: 'right',
374
+ 'left-start': 'right-start',
375
+ 'left-end': 'right-end',
376
+ right: 'left',
377
+ 'right-start': 'left-start',
378
+ 'right-end': 'left-end',
379
+ }
380
+
381
+ return opposites[placement]
382
+ }
383
+
384
+ function calculateFloatingBounds(
385
+ placement: ExtendedAnchorPlacement,
386
+ anchorRect: DOMRect,
387
+ placementWidth: number,
388
+ placementHeight: number,
389
+ collisionWidth: number,
390
+ collisionHeight: number,
391
+ inset: boolean,
392
+ offset: number,
393
+ offsetX: number,
394
+ offsetY: number,
395
+ relativeOffsetX?: number | null,
396
+ relativeOffsetY?: number | null,
397
+ ) {
398
+ let position = calculatePosition(placement, anchorRect, placementWidth, placementHeight, inset)
399
+ position = applyOffset(position, placement, offset, offsetX, offsetY)
400
+
401
+ if (relativeOffsetX !== null && relativeOffsetX !== undefined) {
402
+ position.left -= relativeOffsetX
403
+ }
404
+
405
+ if (relativeOffsetY !== null && relativeOffsetY !== undefined) {
406
+ position.top -= relativeOffsetY
407
+ }
408
+
409
+ return {
410
+ top: position.top,
411
+ left: position.left,
412
+ right: position.left + collisionWidth,
413
+ bottom: position.top + collisionHeight,
414
+ }
415
+ }
416
+
417
+ function getPlacementScore(
418
+ placement: ExtendedAnchorPlacement,
419
+ anchorRect: DOMRect,
420
+ placementWidth: number,
421
+ placementHeight: number,
422
+ collisionWidth: number,
423
+ collisionHeight: number,
424
+ inset: boolean,
425
+ offset: number,
426
+ offsetX: number,
427
+ offsetY: number,
428
+ relativeOffsetX?: number | null,
429
+ relativeOffsetY?: number | null,
430
+ ) {
431
+ let viewport = getClientViewportBounds(viewportPaddingPx)
432
+
433
+ let bounds = calculateFloatingBounds(
434
+ placement,
435
+ anchorRect,
436
+ placementWidth,
437
+ placementHeight,
438
+ collisionWidth,
439
+ collisionHeight,
440
+ inset,
441
+ offset,
442
+ offsetX,
443
+ offsetY,
444
+ relativeOffsetX,
445
+ relativeOffsetY,
446
+ )
447
+
448
+ let constraints = {
449
+ leftOk: bounds.left >= viewport.left,
450
+ rightOk: bounds.right <= viewport.right,
451
+ topOk: bounds.top >= viewport.top,
452
+ bottomOk: bounds.bottom <= viewport.bottom,
453
+ }
454
+
455
+ let total = Object.values(constraints).filter(Boolean).length
456
+
457
+ return {
458
+ overflow: getOverflowAmount(bounds, viewport),
459
+ total,
460
+ perfect: total === 4,
461
+ }
462
+ }
463
+
464
+ function getFlippedPlacement(
465
+ placement: ExtendedAnchorPlacement,
466
+ anchorRect: DOMRect,
467
+ placementWidth: number,
468
+ placementHeight: number,
469
+ collisionWidth: number,
470
+ collisionHeight: number,
471
+ anchor: HTMLElement,
472
+ inset: boolean,
473
+ offset: number,
474
+ offsetX: number,
475
+ offsetY: number,
476
+ relativeOffsetX?: number | null,
477
+ relativeOffsetY?: number | null,
478
+ ) {
479
+ let computedStyle = getComputedStyle(anchor)
480
+ let isAbsolutePositioned = computedStyle.position !== 'fixed'
481
+ let hasScroll = window.scrollY > 0
482
+
483
+ if (placement.startsWith('top') && isAbsolutePositioned && hasScroll) {
484
+ if (anchorRect.top - placementHeight < 0) {
485
+ return placement
486
+ }
487
+ }
488
+
489
+ let original = getPlacementScore(
490
+ placement,
491
+ anchorRect,
492
+ placementWidth,
493
+ placementHeight,
494
+ collisionWidth,
495
+ collisionHeight,
496
+ inset,
497
+ offset,
498
+ offsetX,
499
+ offsetY,
500
+ relativeOffsetX,
501
+ relativeOffsetY,
502
+ )
503
+
504
+ if (original.perfect) {
505
+ return placement
506
+ }
507
+
508
+ let flippedPlacement = getOppositePlacement(placement)
509
+ let flipped = getPlacementScore(
510
+ flippedPlacement,
511
+ anchorRect,
512
+ placementWidth,
513
+ placementHeight,
514
+ collisionWidth,
515
+ collisionHeight,
516
+ inset,
517
+ offset,
518
+ offsetX,
519
+ offsetY,
520
+ relativeOffsetX,
521
+ relativeOffsetY,
522
+ )
523
+
524
+ if (
525
+ flipped.perfect ||
526
+ flipped.total > original.total ||
527
+ (flipped.total === original.total && flipped.overflow < original.overflow)
528
+ ) {
529
+ return flippedPlacement
530
+ }
531
+
532
+ return placement
533
+ }
534
+
535
+ function getAvailableWidthForPlacement(
536
+ placement: ExtendedAnchorPlacement,
537
+ left: number,
538
+ width: number,
539
+ viewport: { left: number; right: number },
540
+ ) {
541
+ if (placement.startsWith('left')) {
542
+ return left + width - viewport.left
543
+ }
544
+
545
+ if (placement.startsWith('right')) {
546
+ return viewport.right - left
547
+ }
548
+
549
+ return viewport.right - viewport.left
550
+ }
551
+
552
+ function getAvailableHeightForPlacement(
553
+ placement: ExtendedAnchorPlacement,
554
+ top: number,
555
+ height: number,
556
+ viewport: { top: number; bottom: number },
557
+ ) {
558
+ if (placement.startsWith('top')) {
559
+ return top + height - viewport.top
560
+ }
561
+
562
+ if (placement.startsWith('bottom')) {
563
+ return viewport.bottom - top
564
+ }
565
+
566
+ return viewport.bottom - viewport.top
567
+ }
568
+
569
+ function calculateDocumentPosition(
570
+ placement: ExtendedAnchorPlacement,
571
+ anchorRect: DOMRect,
572
+ placementWidth: number,
573
+ placementHeight: number,
574
+ inset: boolean,
575
+ offset: number,
576
+ offsetX: number,
577
+ offsetY: number,
578
+ isFixed: boolean,
579
+ scrollContainerOffsetX = 0,
580
+ scrollContainerOffsetY = 0,
581
+ relativeOffsetX?: number | null,
582
+ relativeOffsetY?: number | null,
583
+ ) {
584
+ let position = calculatePosition(placement, anchorRect, placementWidth, placementHeight, inset)
585
+
586
+ if (!isFixed) {
587
+ position.top += window.scrollY
588
+ position.left += window.scrollX
589
+ }
590
+
591
+ position = applyOffset(position, placement, offset, offsetX, offsetY)
592
+
593
+ position.left -= scrollContainerOffsetX
594
+ position.top -= scrollContainerOffsetY
595
+
596
+ if (relativeOffsetX !== null && relativeOffsetX !== undefined) {
597
+ position.left -= relativeOffsetX
598
+ }
599
+
600
+ if (relativeOffsetY !== null && relativeOffsetY !== undefined) {
601
+ position.top -= relativeOffsetY
602
+ }
603
+
604
+ return position
605
+ }
606
+
607
+ function solveRelativeAlignmentAxis(options: {
608
+ currentScroll: number
609
+ desiredPosition: number
610
+ naturalSize: number
611
+ visibleSize: number
612
+ minPosition: number
613
+ maxPosition: number
614
+ }) {
615
+ let maxScroll = Math.max(options.naturalSize - options.visibleSize, 0)
616
+ let scroll = clamp(options.currentScroll, 0, maxScroll)
617
+
618
+ if (maxScroll === 0) {
619
+ return {
620
+ position: clamp(options.desiredPosition, options.minPosition, options.maxPosition),
621
+ scroll,
622
+ }
623
+ }
624
+
625
+ let exactScrollMin = Math.max(0, options.minPosition - options.desiredPosition)
626
+ let exactScrollMax = Math.min(maxScroll, options.maxPosition - options.desiredPosition)
627
+
628
+ if (exactScrollMin <= exactScrollMax) {
629
+ scroll = clamp(scroll, exactScrollMin, exactScrollMax)
630
+ } else if (options.desiredPosition > options.maxPosition) {
631
+ scroll = 0
632
+ } else if (options.desiredPosition + maxScroll < options.minPosition) {
633
+ scroll = maxScroll
634
+ }
635
+
636
+ return {
637
+ position: clamp(options.desiredPosition + scroll, options.minPosition, options.maxPosition),
638
+ scroll,
639
+ }
640
+ }
641
+
642
+ export function anchor(
643
+ floating: HTMLElement,
644
+ anchorElement: HTMLElement,
645
+ options: AnchorOptions = {},
646
+ ) {
647
+ let lastAnchorRect: DOMRect
648
+ let lastFloatingDimensions: FloatingDimensions
649
+
650
+ if (!(floating instanceof HTMLElement)) {
651
+ throw new TypeError('anchor() expected a floating HTMLElement')
652
+ }
653
+
654
+ if (!(anchorElement instanceof HTMLElement)) {
655
+ throw new TypeError('anchor() expected an anchor HTMLElement')
656
+ }
657
+
658
+ let {
659
+ placement = 'bottom',
660
+ inset = false,
661
+ relativeTo,
662
+ offset: rawOffset = 0,
663
+ offsetX: rawOffsetX = 0,
664
+ offsetY: rawOffsetY = 0,
665
+ } = options
666
+
667
+ let isFixed =
668
+ floating.hasAttribute('popover') || getComputedStyle(anchorElement).position === 'fixed'
669
+ let animationFrameId = 0
670
+
671
+ function updatePosition(
672
+ anchorRect = anchorElement.getBoundingClientRect(),
673
+ naturalDimensions = getFloatingDimensions(floating, relativeTo, {
674
+ ignoreInlineMaxSize: true,
675
+ }),
676
+ ) {
677
+ let offset = resolveOffsetValue(rawOffset, floating)
678
+ let offsetX = resolveOffsetValue(rawOffsetX, floating)
679
+ let offsetY = resolveOffsetValue(rawOffsetY, floating)
680
+ let viewport = getDocumentViewportBounds(isFixed, viewportPaddingPx)
681
+
682
+ let placementWidth =
683
+ relativeTo && naturalDimensions.relativeWidth
684
+ ? naturalDimensions.relativeWidth
685
+ : naturalDimensions.width
686
+ let placementHeight =
687
+ relativeTo && naturalDimensions.relativeHeight
688
+ ? naturalDimensions.relativeHeight
689
+ : naturalDimensions.height
690
+
691
+ let finalPlacement = getFlippedPlacement(
692
+ placement,
693
+ anchorRect,
694
+ placementWidth,
695
+ placementHeight,
696
+ naturalDimensions.width,
697
+ naturalDimensions.height,
698
+ anchorElement,
699
+ inset,
700
+ offset,
701
+ offsetX,
702
+ offsetY,
703
+ naturalDimensions.relativeOffsetX,
704
+ naturalDimensions.relativeOffsetY,
705
+ )
706
+ floating.setAttribute('data-anchor-placement', finalPlacement)
707
+
708
+ let position = calculateDocumentPosition(
709
+ finalPlacement,
710
+ anchorRect,
711
+ placementWidth,
712
+ placementHeight,
713
+ inset,
714
+ offset,
715
+ offsetX,
716
+ offsetY,
717
+ isFixed,
718
+ naturalDimensions.scrollContainerOffsetX,
719
+ naturalDimensions.scrollContainerOffsetY,
720
+ naturalDimensions.relativeOffsetX,
721
+ naturalDimensions.relativeOffsetY,
722
+ )
723
+
724
+ let availableWidth = Math.max(
725
+ getAvailableWidthForPlacement(
726
+ finalPlacement,
727
+ position.left,
728
+ naturalDimensions.width,
729
+ viewport,
730
+ ),
731
+ 0,
732
+ )
733
+ let availableHeight = Math.max(
734
+ getAvailableHeightForPlacement(
735
+ finalPlacement,
736
+ position.top,
737
+ naturalDimensions.height,
738
+ viewport,
739
+ ),
740
+ 0,
741
+ )
742
+
743
+ floating.style.maxWidth =
744
+ availableWidth < naturalDimensions.width ? `${Math.floor(availableWidth)}px` : ''
745
+ floating.style.maxHeight =
746
+ availableHeight < naturalDimensions.height ? `${Math.floor(availableHeight)}px` : ''
747
+
748
+ let dimensions = getFloatingDimensions(floating, relativeTo)
749
+ placementWidth =
750
+ relativeTo && dimensions.relativeWidth ? dimensions.relativeWidth : dimensions.width
751
+ placementHeight =
752
+ relativeTo && dimensions.relativeHeight ? dimensions.relativeHeight : dimensions.height
753
+
754
+ position = calculateDocumentPosition(
755
+ finalPlacement,
756
+ anchorRect,
757
+ placementWidth,
758
+ placementHeight,
759
+ inset,
760
+ offset,
761
+ offsetX,
762
+ offsetY,
763
+ isFixed,
764
+ dimensions.scrollContainerOffsetX,
765
+ dimensions.scrollContainerOffsetY,
766
+ dimensions.relativeOffsetX,
767
+ dimensions.relativeOffsetY,
768
+ )
769
+
770
+ let minX = viewport.left
771
+ let minY = viewport.top
772
+ let maxX = viewport.right
773
+ let maxY = viewport.bottom
774
+ let xRange = getConstrainedPositionRange(dimensions.width, minX, maxX)
775
+ let yRange = getConstrainedPositionRange(dimensions.height, minY, maxY)
776
+
777
+ if (isHorizontalPlacement(finalPlacement)) {
778
+ if (relativeTo && dimensions.relativeOffsetY !== null) {
779
+ let solvedY = solveRelativeAlignmentAxis({
780
+ currentScroll: dimensions.scrollContainer.scrollTop,
781
+ desiredPosition: position.top,
782
+ naturalSize: dimensions.scrollHeight,
783
+ visibleSize: dimensions.scrollViewportHeight,
784
+ minPosition: yRange.min,
785
+ maxPosition: yRange.max,
786
+ })
787
+ dimensions.scrollContainer.scrollTop = solvedY.scroll
788
+ position.top = solvedY.position
789
+ } else {
790
+ position.top = constrainToAxis(position.top, dimensions.height, minY, maxY)
791
+ }
792
+
793
+ if (relativeTo) {
794
+ if (dimensions.relativeOffsetX !== null) {
795
+ let solvedX = solveRelativeAlignmentAxis({
796
+ currentScroll: dimensions.scrollContainer.scrollLeft,
797
+ desiredPosition: position.left,
798
+ naturalSize: dimensions.scrollWidth,
799
+ visibleSize: dimensions.scrollViewportWidth,
800
+ minPosition: xRange.min,
801
+ maxPosition: xRange.max,
802
+ })
803
+ dimensions.scrollContainer.scrollLeft = solvedX.scroll
804
+ position.left = solvedX.position
805
+ } else {
806
+ position.left = clamp(position.left, xRange.min, xRange.max)
807
+ }
808
+ }
809
+ } else if (isVerticalPlacement(finalPlacement)) {
810
+ if (relativeTo && dimensions.relativeOffsetX !== null) {
811
+ let solvedX = solveRelativeAlignmentAxis({
812
+ currentScroll: dimensions.scrollContainer.scrollLeft,
813
+ desiredPosition: position.left,
814
+ naturalSize: dimensions.scrollWidth,
815
+ visibleSize: dimensions.scrollViewportWidth,
816
+ minPosition: xRange.min,
817
+ maxPosition: xRange.max,
818
+ })
819
+ dimensions.scrollContainer.scrollLeft = solvedX.scroll
820
+ position.left = solvedX.position
821
+ } else {
822
+ position.left = constrainToAxis(position.left, dimensions.width, minX, maxX)
823
+ }
824
+
825
+ if (!isFixed && window.scrollY > 0 && position.top < viewport.top) {
826
+ position.top = viewport.top
827
+ }
828
+
829
+ if (relativeTo) {
830
+ if (dimensions.relativeOffsetY !== null) {
831
+ let solvedY = solveRelativeAlignmentAxis({
832
+ currentScroll: dimensions.scrollContainer.scrollTop,
833
+ desiredPosition: position.top,
834
+ naturalSize: dimensions.scrollHeight,
835
+ visibleSize: dimensions.scrollViewportHeight,
836
+ minPosition: yRange.min,
837
+ maxPosition: yRange.max,
838
+ })
839
+ dimensions.scrollContainer.scrollTop = solvedY.scroll
840
+ position.top = solvedY.position
841
+ } else {
842
+ position.top = clamp(position.top, yRange.min, yRange.max)
843
+ }
844
+ }
845
+ }
846
+
847
+ if (isHorizontalPlacement(finalPlacement)) {
848
+ let status = getOffScreenStatus(anchorRect, 'horizontal')
849
+ if (status === 'before') {
850
+ position.left = minX
851
+ } else if (status === 'after') {
852
+ position.left = maxX - dimensions.width
853
+ }
854
+ } else if (isVerticalPlacement(finalPlacement)) {
855
+ let status = getOffScreenStatus(anchorRect, 'vertical')
856
+ if (status === 'before') {
857
+ position.top = minY
858
+ } else if (status === 'after') {
859
+ position.top = maxY - dimensions.height
860
+ }
861
+ }
862
+
863
+ floating.style.position = isFixed ? 'fixed' : 'absolute'
864
+ floating.style.inset = 'auto'
865
+ floating.style.top = `${position.top}px`
866
+ floating.style.left = `${position.left}px`
867
+
868
+ lastAnchorRect = anchorRect
869
+ lastFloatingDimensions = dimensions
870
+ }
871
+
872
+ updatePosition()
873
+
874
+ function pollForPositionChanges() {
875
+ let currentRect = anchorElement.getBoundingClientRect()
876
+ let currentDimensions = getFloatingDimensions(floating, relativeTo)
877
+ if (
878
+ hasRectChanged(currentRect, lastAnchorRect) ||
879
+ hasFloatingDimensionsChanged(currentDimensions, lastFloatingDimensions)
880
+ ) {
881
+ updatePosition(currentRect, currentDimensions)
882
+ }
883
+
884
+ animationFrameId = requestAnimationFrame(pollForPositionChanges)
885
+ }
886
+
887
+ function handleScroll() {
888
+ updatePosition()
889
+ }
890
+
891
+ function handleResize() {
892
+ updatePosition()
893
+ }
894
+
895
+ animationFrameId = requestAnimationFrame(pollForPositionChanges)
896
+ window.addEventListener('scroll', handleScroll, { passive: true })
897
+ window.addEventListener('resize', handleResize, { passive: true })
898
+
899
+ return () => {
900
+ cancelAnimationFrame(animationFrameId)
901
+ window.removeEventListener('scroll', handleScroll)
902
+ window.removeEventListener('resize', handleResize)
903
+ }
904
+ }