@mlola-ui/behavior 1.1.10 → 1.1.11

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mlola-ui/behavior",
3
- "version": "1.1.10",
3
+ "version": "1.1.11",
4
4
  "description": "Framework-free behavior for Mlola UI markup: standard DOM, no runtime peers",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
package/src/index.js CHANGED
@@ -21,6 +21,7 @@ import {
21
21
  clampToStep,
22
22
  focusTrapIndex,
23
23
  percentOf,
24
+ fitMenu,
24
25
  fitTooltip,
25
26
  isSidewaysDrag,
26
27
  placeFloating,
@@ -156,11 +157,24 @@ const behaviors = {
156
157
  (item) => !item.disabled && item.getAttribute("aria-disabled") !== "true",
157
158
  );
158
159
 
160
+ // CSS hangs the menu under the trigger; this keeps it on screen.
161
+ const fit = () => {
162
+ const rect = root.getBoundingClientRect();
163
+ const { side, shift } = fitMenu(
164
+ { x: rect.left, y: rect.top, width: rect.width, height: rect.height },
165
+ { width: menu.offsetWidth, height: menu.offsetHeight },
166
+ { width: window.innerWidth, height: window.innerHeight },
167
+ menu.dataset.align === "end" ? "end" : "start",
168
+ );
169
+ menu.style.setProperty("--ml-dropdown-shift", `${shift}px`);
170
+ menu.dataset.side = side;
171
+ };
159
172
  const setOpen = (open) => {
160
173
  root.dataset.state = open ? "open" : "closed";
161
174
  menu.dataset.state = open ? "open" : "closed";
162
175
  menu.hidden = !open;
163
176
  trigger.setAttribute("aria-expanded", String(open));
177
+ if (open) fit();
164
178
  if (!open) for (const item of items()) delete item.dataset.highlighted;
165
179
  };
166
180
  const isOpen = () => root.dataset.state === "open";
@@ -207,6 +221,9 @@ const behaviors = {
207
221
  on(document, "pointerdown", (event) => {
208
222
  if (isOpen() && !root.contains(event.target)) setOpen(false);
209
223
  }),
224
+ on(window, "resize", () => {
225
+ if (isOpen()) fit();
226
+ }),
210
227
  ];
211
228
  },
212
229
 
@@ -177,6 +177,18 @@ export function placeFloating(anchor, floating, viewport, { side = "bottom", ali
177
177
  return { x: Math.round(x), y: Math.round(y), side: chosen };
178
178
  }
179
179
 
180
+ /**
181
+ * Where a menu anchored under its trigger by CSS should sit to stay on
182
+ * screen: the side it opens on (above when there is no room below), and how
183
+ * far to slide it sideways from where CSS put it (its start edge on the
184
+ * anchor's start, or its end edge on the anchor's end).
185
+ */
186
+ export function fitMenu(anchor, menu, viewport, align = "start", { offset = 6, padding = 8 } = {}) {
187
+ const placed = placeFloating(anchor, menu, viewport, { side: "bottom", align, offset, padding });
188
+ const cssX = align === "end" ? anchor.x + anchor.width - menu.width : anchor.x;
189
+ return { side: placed.side, shift: Math.round(placed.x - cssX) };
190
+ }
191
+
180
192
  /**
181
193
  * Where a tooltip anchored beside its trigger by CSS should sit to stay on
182
194
  * screen: the side it opens on (flipped when the other has more room, or
package/src/logic.d.ts CHANGED
@@ -136,6 +136,15 @@ export function placeFloating(
136
136
  options?: { side?: Side; align?: Align; offset?: number; padding?: number },
137
137
  ): Placement;
138
138
 
139
+ /** A CSS-anchored menu's side (bottom, or top without room) and its sideways slide to stay on screen. */
140
+ export function fitMenu(
141
+ anchor: Rect,
142
+ menu: { width: number; height: number },
143
+ viewport: { width: number; height: number },
144
+ align?: "start" | "end",
145
+ options?: { offset?: number; padding?: number },
146
+ ): { side: Side; shift: number };
147
+
139
148
  /** A CSS-anchored tooltip's side and its slide along that edge to stay on screen. */
140
149
  export function fitTooltip(
141
150
  anchor: Rect,