@johly/bits-ui 2.18.8 → 2.18.9

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.
@@ -139,7 +139,7 @@
139
139
  <MenuSubContent
140
140
  sideOffset={8}
141
141
  align={getSubmenuAlign(meta)}
142
- collisionAvoidance={{ side: "shift", align: "shift" }}
142
+ collisionAvoidance={{ side: "flip", align: "shift" }}
143
143
  >
144
144
  {#if column && meta?.kind === "column" && meta.editor === "text"}
145
145
  <FilterTextEditor column={column as Column<unknown, "text">} {actions} />
@@ -128,6 +128,13 @@
128
128
  .requestAnimationFrame(measureListAlign);
129
129
  }
130
130
 
131
+ function measureListAlignAndQueueRemeasure() {
132
+ if (align !== "list") return;
133
+ clearMeasureFrame();
134
+ measureListAlign();
135
+ queueMeasureListAlign();
136
+ }
137
+
131
138
  $effect(() => {
132
139
  if (align !== "list") {
133
140
  listAlignOffset = 0;
@@ -137,7 +144,7 @@
137
144
  listAlignOffset = 0;
138
145
  return;
139
146
  }
140
- afterTick(queueMeasureListAlign);
147
+ afterTick(measureListAlignAndQueueRemeasure);
141
148
  });
142
149
 
143
150
  $effect(() => {
@@ -205,7 +212,7 @@
205
212
 
206
213
  function handlePlaced() {
207
214
  onPlaced();
208
- queueMeasureListAlign();
215
+ measureListAlignAndQueueRemeasure();
209
216
  }
210
217
 
211
218
  function handleOnFocusOutside(e: FocusEvent) {
@@ -12,6 +12,72 @@ const OPPOSITE_SIDE = {
12
12
  bottom: "top",
13
13
  left: "right",
14
14
  };
15
+ function getPaddingForSide(padding, side) {
16
+ if (typeof padding === "number")
17
+ return padding;
18
+ return padding?.[side] ?? 0;
19
+ }
20
+ function parsePixelValue(value) {
21
+ const parsed = Number.parseFloat(value);
22
+ return Number.isFinite(parsed) ? parsed : 0;
23
+ }
24
+ function replacePlacementSide(placement, side) {
25
+ const [, align] = placement.split("-");
26
+ return (side + (align ? `-${align}` : ""));
27
+ }
28
+ function getFloatingContentNode(floating) {
29
+ return floating.firstElementChild instanceof HTMLElement
30
+ ? floating.firstElementChild
31
+ : floating instanceof HTMLElement
32
+ ? floating
33
+ : null;
34
+ }
35
+ function getIntendedFloatingWidth(floating, measuredWidth) {
36
+ const contentNode = getFloatingContentNode(floating);
37
+ if (!contentNode)
38
+ return measuredWidth;
39
+ const style = contentNode.ownerDocument.defaultView?.getComputedStyle(contentNode);
40
+ const rowWidth = style ? parsePixelValue(style.getPropertyValue("--row-width")) : 0;
41
+ const isSubmenu = contentNode.hasAttribute("data-menu-sub-content") ||
42
+ contentNode.hasAttribute("data-dropdown-menu-sub-content") ||
43
+ contentNode.hasAttribute("data-context-menu-sub-content") ||
44
+ contentNode.hasAttribute("data-menubar-sub-content");
45
+ return Math.max(measuredWidth, rowWidth, isSubmenu ? 250 : 0);
46
+ }
47
+ function preferOppositeSideWithRoom(opts) {
48
+ return {
49
+ name: "preferOppositeSideWithRoom",
50
+ async fn(state) {
51
+ const side = getSideFromPlacement(state.placement);
52
+ if (side !== "right" && side !== "left")
53
+ return {};
54
+ const referenceNode = state.elements.reference;
55
+ if (!("getBoundingClientRect" in referenceNode))
56
+ return {};
57
+ const referenceRect = referenceNode.getBoundingClientRect();
58
+ const floatingNode = state.elements.floating;
59
+ const win = getWindow(floatingNode);
60
+ const padding = opts.collisionPadding();
61
+ const sideOffset = opts.sideOffset();
62
+ const desiredWidth = getIntendedFloatingWidth(floatingNode, state.rects.floating.width);
63
+ const leftRoom = referenceRect.left - getPaddingForSide(padding, "left") - sideOffset;
64
+ const rightRoom = win.innerWidth -
65
+ referenceRect.right -
66
+ getPaddingForSide(padding, "right") -
67
+ sideOffset;
68
+ const currentRoom = side === "right" ? rightRoom : leftRoom;
69
+ const oppositeSide = OPPOSITE_SIDE[side];
70
+ const oppositeRoom = oppositeSide === "right" ? rightRoom : leftRoom;
71
+ if (currentRoom >= desiredWidth || oppositeRoom < desiredWidth)
72
+ return {};
73
+ return {
74
+ reset: {
75
+ placement: replacePlacementSide(state.placement, oppositeSide),
76
+ },
77
+ };
78
+ },
79
+ };
80
+ }
15
81
  const FloatingRootContext = new Context("Floating.Root");
16
82
  const FloatingContentContext = new Context("Floating.Content");
17
83
  const FloatingTooltipRootContext = new Context("Floating.Root");
@@ -96,6 +162,12 @@ export class FloatingContentState {
96
162
  mainAxis: this.opts.sideOffset.current + this.#arrowHeight,
97
163
  alignmentAxis: this.opts.alignOffset.current,
98
164
  }),
165
+ this.opts.avoidCollisions.current &&
166
+ this.#collisionAvoidance.side === "flip" &&
167
+ preferOppositeSideWithRoom({
168
+ sideOffset: () => this.opts.sideOffset.current + this.#arrowHeight,
169
+ collisionPadding: () => this.opts.collisionPadding.current,
170
+ }),
99
171
  this.opts.avoidCollisions.current &&
100
172
  (this.#collisionAvoidance.side === "shift" ||
101
173
  this.#collisionAvoidance.align === "shift") &&
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@johly/bits-ui",
3
- "version": "2.18.8",
3
+ "version": "2.18.9",
4
4
  "license": "MIT",
5
5
  "repository": "github:johanohly/bits-ui",
6
6
  "funding": "https://github.com/sponsors/huntabyte",