@joewinke/jatui 0.8.4 → 0.8.5
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.
|
@@ -197,6 +197,35 @@
|
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
+
function positionPanel() {
|
|
201
|
+
if (!triggerRef) return;
|
|
202
|
+
const rect = triggerRef.getBoundingClientRect();
|
|
203
|
+
const width = Math.max(rect.width, 192);
|
|
204
|
+
// Viewport-aware flip: if there isn't room below the trigger but there
|
|
205
|
+
// is above, anchor the panel to the trigger's top and grow upward. This
|
|
206
|
+
// keeps the panel on-screen for rows near the bottom of a scroll area
|
|
207
|
+
// (e.g. suggested-task rows in the completed-session overlay).
|
|
208
|
+
const estPanelHeight = 300;
|
|
209
|
+
const spaceBelow = window.innerHeight - rect.bottom;
|
|
210
|
+
const spaceAbove = rect.top;
|
|
211
|
+
const flipUp = spaceBelow < estPanelHeight && spaceAbove > spaceBelow;
|
|
212
|
+
// Clamp left so a right-edge trigger doesn't push the panel off-screen.
|
|
213
|
+
const left = Math.max(8, Math.min(rect.left, window.innerWidth - width - 8));
|
|
214
|
+
// .sd-panel's class rule always sets `top: 100%`, so each branch must
|
|
215
|
+
// explicitly reset the property it isn't using — otherwise the flip-up
|
|
216
|
+
// branch ends up with both top (100% = viewport height) and bottom set
|
|
217
|
+
// while height is auto, which over-constrains to a negative (clamped
|
|
218
|
+
// to 0) height and the whole panel silently renders invisible.
|
|
219
|
+
const vertical = flipUp
|
|
220
|
+
? `bottom: ${window.innerHeight - rect.top + 4}px; top: auto;`
|
|
221
|
+
: `top: ${rect.bottom + 4}px; bottom: auto;`;
|
|
222
|
+
// min-width override is required: .sd-panel's class rule sets
|
|
223
|
+
// `min-width: max(100%, 14rem)`, and for a position:fixed element
|
|
224
|
+
// that 100% resolves against the viewport, not the trigger — without
|
|
225
|
+
// this override the panel balloons to ~100vw wide.
|
|
226
|
+
panelStyle = `position: fixed; z-index: 9999; ${vertical} left: ${left}px; width: ${width}px; min-width: ${width}px; margin-top: 0;`;
|
|
227
|
+
}
|
|
228
|
+
|
|
200
229
|
$effect(() => {
|
|
201
230
|
if (embedded) {
|
|
202
231
|
tick().then(() => { searchInput?.focus(); scrollHighlightedIntoView(); });
|
|
@@ -204,31 +233,26 @@
|
|
|
204
233
|
}
|
|
205
234
|
if (open) {
|
|
206
235
|
if (appendToBody && triggerRef) {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
//
|
|
210
|
-
//
|
|
211
|
-
//
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
// min-width override is required: .sd-panel's class rule sets
|
|
228
|
-
// `min-width: max(100%, 14rem)`, and for a position:fixed element
|
|
229
|
-
// that 100% resolves against the viewport, not the trigger — without
|
|
230
|
-
// this override the panel balloons to ~100vw wide.
|
|
231
|
-
panelStyle = `position: fixed; z-index: 9999; ${vertical} left: ${left}px; width: ${width}px; min-width: ${width}px; margin-top: 0;`;
|
|
236
|
+
positionPanel();
|
|
237
|
+
// A position:fixed panel is anchored to the VIEWPORT, so without this it
|
|
238
|
+
// stays put while the page scrolls and appears to follow the reader down
|
|
239
|
+
// the page (Joe UAT 2026-09-25). Re-anchor on every scroll (capture: any
|
|
240
|
+
// scroll container) and resize; close once the trigger leaves the view.
|
|
241
|
+
const reanchor = (e?: Event) => {
|
|
242
|
+
if (e && e.target instanceof Node && containerRef?.contains(e.target)) return;
|
|
243
|
+
const r = triggerRef?.getBoundingClientRect();
|
|
244
|
+
if (!r || r.bottom < 0 || r.top > window.innerHeight) { open = false; searchQuery = ''; return; }
|
|
245
|
+
positionPanel();
|
|
246
|
+
};
|
|
247
|
+
window.addEventListener('scroll', reanchor, { capture: true, passive: true });
|
|
248
|
+
window.addEventListener('resize', reanchor, { passive: true });
|
|
249
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
250
|
+
tick().then(() => { searchInput?.focus(); scrollHighlightedIntoView(); });
|
|
251
|
+
return () => {
|
|
252
|
+
window.removeEventListener('scroll', reanchor, { capture: true });
|
|
253
|
+
window.removeEventListener('resize', reanchor);
|
|
254
|
+
document.removeEventListener('mousedown', handleClickOutside);
|
|
255
|
+
};
|
|
232
256
|
}
|
|
233
257
|
document.addEventListener('mousedown', handleClickOutside);
|
|
234
258
|
tick().then(() => { searchInput?.focus(); scrollHighlightedIntoView(); });
|