@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
- const rect = triggerRef.getBoundingClientRect();
208
- const width = Math.max(rect.width, 192);
209
- // Viewport-aware flip: if there isn't room below the trigger but there
210
- // is above, anchor the panel to the trigger's top and grow upward. This
211
- // keeps the panel on-screen for rows near the bottom of a scroll area
212
- // (e.g. suggested-task rows in the completed-session overlay).
213
- const estPanelHeight = 300;
214
- const spaceBelow = window.innerHeight - rect.bottom;
215
- const spaceAbove = rect.top;
216
- const flipUp = spaceBelow < estPanelHeight && spaceAbove > spaceBelow;
217
- // Clamp left so a right-edge trigger doesn't push the panel off-screen.
218
- const left = Math.max(8, Math.min(rect.left, window.innerWidth - width - 8));
219
- // .sd-panel's class rule always sets `top: 100%`, so each branch must
220
- // explicitly reset the property it isn't using — otherwise the flip-up
221
- // branch ends up with both top (100% = viewport height) and bottom set
222
- // while height is auto, which over-constrains to a negative (clamped
223
- // to 0) height and the whole panel silently renders invisible.
224
- const vertical = flipUp
225
- ? `bottom: ${window.innerHeight - rect.top + 4}px; top: auto;`
226
- : `top: ${rect.bottom + 4}px; bottom: auto;`;
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(); });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@joewinke/jatui",
3
- "version": "0.8.4",
3
+ "version": "0.8.5",
4
4
  "private": false,
5
5
  "description": "Shared Svelte 5 component library for JAT projects",
6
6
  "type": "module",