@lotics/ui 47.5.1 → 47.5.2

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.
@@ -136,6 +136,19 @@ the subject into rows.
136
136
  | Register / work list / calendar | 1040 |
137
137
  | Wide wallboard / report | 1100 |
138
138
 
139
+ **A row that navigates PAINTS.** A link is not a row: it paints nothing, so a row tall enough to
140
+ hold an identity mark becomes a large silent target — the pointer crosses it and the only thing
141
+ saying it is pressable is the cursor. Reach for the row surface (`PressableRow`), never a
142
+ hand-rolled hover on the anchor: the wash has to span the WHOLE row *including its trailing
143
+ controls*, and a parent's Pressability hover is released to the innermost nested pressable, which
144
+ stops the wash short of the `⋯` and leaves a gap exactly where the eye is going. Keep the anchor
145
+ INSIDE as the accessible door — a row of places to go owes middle-click, cmd-click and copy-link,
146
+ which only a real anchor gives.
147
+
148
+ Without that wash the trailing controls are what look wrong: nothing ties a `⋯` to its row, so the
149
+ right edge reads as a floating column competing with the names, and the instinct is to hide the
150
+ control. Paint the row first — the clutter usually WAS the missing wash.
151
+
139
152
  **ONE LEFT EDGE — the whole page, and then the whole APP.** Headings, detail labels, prose, a
140
153
  table's first column, a footer's totals, a dialog's title. Then check the screen this one OPENS: a
141
154
  list and the record it opens are one reading column seen twice, and both must derive from ONE
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/ui",
3
- "version": "47.5.1",
3
+ "version": "47.5.2",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./accordion": "./src/accordion.tsx",
package/src/popover.tsx CHANGED
@@ -433,6 +433,34 @@ export function PopoverContent(props: PopoverContentProps) {
433
433
  return () => document.removeEventListener("click", onOutsideClick, true);
434
434
  }, [open, small, onOpenChange, triggerRef]);
435
435
 
436
+ // The one press the click listener above CANNOT see: one that lands inside a
437
+ // cross-origin iframe. Those events are dispatched in the frame's own document
438
+ // and never cross into this one, so a popover anchored over an embedded app —
439
+ // the app page's own chrome is exactly this shape — stayed open while the user
440
+ // clicked around underneath it, with no way to dismiss but pressing the trigger
441
+ // again or hitting Escape.
442
+ //
443
+ // Focus is the signal that survives the boundary: clicking into a frame moves
444
+ // this document's `activeElement` to the IFRAME element and blurs the window.
445
+ //
446
+ // Checking `activeElement` is what keeps this from over-firing. A window blur
447
+ // alone also means "switched tab" or "moved to another app", and dismissing
448
+ // then is a menu that quietly disappears while the user was reading something
449
+ // else. Only a blur that HANDED FOCUS TO A FRAME is a press we missed.
450
+ useEffect(() => {
451
+ if (!open || small) return;
452
+ const onWindowBlur = () => {
453
+ // Deferred: at blur time the browser has not always moved `activeElement`
454
+ // to the frame yet, so reading it synchronously can still report the
455
+ // trigger and miss the dismissal entirely.
456
+ setTimeout(() => {
457
+ if (document.activeElement instanceof HTMLIFrameElement) onOpenChange(false);
458
+ }, 0);
459
+ };
460
+ window.addEventListener("blur", onWindowBlur);
461
+ return () => window.removeEventListener("blur", onWindowBlur);
462
+ }, [open, small, onOpenChange]);
463
+
436
464
  const calculatePosition = useCallback(() => {
437
465
  if (!popoverRef.current) return;
438
466
  if (small) return;