@streamoid/ui 0.6.32 → 0.6.33

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.
@@ -196,6 +196,23 @@ Two things will bite you:
196
196
  `arrow` is `null` until the panel has been measured; `ScPopoverArrow` renders
197
197
  nothing for it, so there is no flash of a caret in the wrong place.
198
198
 
199
+ ### Two popovers on one rail
200
+
201
+ Pass `alignRef` — the rail — to both, and they open on the same vertical line
202
+ while each caret still points at its own trigger:
203
+
204
+ ```tsx
205
+ const { style, arrow } = useStreamoidAnchoredPopover(triggerRef, panelRef, {
206
+ width: 320,
207
+ alignRef: railRef,
208
+ });
209
+ ```
210
+
211
+ Without it each panel hangs off its own button, and those sit at different
212
+ insets (each inside a control with its own padding) — so the pair opens about
213
+ twenty pixels apart, one flush to the rail and the other overlapping it, which
214
+ reads as sloppy rather than as one pattern.
215
+
199
216
  Use the same hook for **every** popover a rail owns. Mixing it with
200
217
  `useStreamoidSidebarPopoverPosition` (which pins to the sidebar CARD's edge)
201
218
  puts two menus from the same rail in visibly different places — one tethered to
package/dist/index.d.mts CHANGED
@@ -835,6 +835,17 @@ interface StreamoidAnchorRect {
835
835
  }
836
836
  interface StreamoidAnchoredPopoverGeometry {
837
837
  anchor: StreamoidAnchorRect;
838
+ /** Sets the horizontal placement when the trigger should not.
839
+ *
840
+ * A rail with two popovers — a workspace menu at the top, an app switcher at
841
+ * the foot — has two triggers at different insets, because each sits inside
842
+ * a button with its own padding. Anchored to those, the two panels open a
843
+ * dozen pixels apart and the pair reads as sloppy rather than as one
844
+ * pattern. Align both to the RAIL and they open on the same line, while the
845
+ * caret still points at whichever trigger was used.
846
+ *
847
+ * Defaults to `anchor`, which is the single-popover case. */
848
+ align?: StreamoidAnchorRect;
838
849
  viewportWidth: number;
839
850
  viewportHeight: number;
840
851
  /** Panel width in pixels. */
@@ -849,6 +860,10 @@ interface StreamoidAnchoredPopoverGeometry {
849
860
  interface StreamoidAnchoredPopoverPositionOptions {
850
861
  /** Panel width in pixels. */
851
862
  width: number;
863
+ /** Element whose edges set the horizontal placement, when the trigger should
864
+ * not. See `align` on the geometry — pass the rail to line two of its
865
+ * popovers up with each other. */
866
+ alignRef?: RefObject<HTMLElement | null>;
852
867
  /** Gap between the trigger and the panel. Defaults to 8px. */
853
868
  gap?: number;
854
869
  /** Keep-out margin from the viewport edges. Defaults to 16px. */
@@ -900,7 +915,7 @@ declare function resolveAnchoredPopoverArrow({ anchor, panelLeft, panelTop, pane
900
915
  * below. Nothing else re-places it, so the panel is always visibly attached to
901
916
  * the button that opened it rather than parked against a container edge.
902
917
  */
903
- declare function resolveAnchoredPopoverPosition({ anchor, viewportWidth, viewportHeight, width, height, gap, inset, }: StreamoidAnchoredPopoverGeometry): {
918
+ declare function resolveAnchoredPopoverPosition({ anchor, align, viewportWidth, viewportHeight, width, height, gap, inset, }: StreamoidAnchoredPopoverGeometry): {
904
919
  left: number;
905
920
  top: number;
906
921
  width: number;
@@ -920,7 +935,7 @@ declare function resolveAnchoredPopoverPosition({ anchor, viewportWidth, viewpor
920
935
  */
921
936
  /** The position and the caret, measured together — a caret measured on a later
922
937
  * render can point somewhere the panel no longer is. */
923
- declare function useStreamoidAnchoredPopover(anchorRef: RefObject<HTMLElement | null>, panelRef: RefObject<HTMLElement | null>, { width, gap, inset }: StreamoidAnchoredPopoverPositionOptions): StreamoidAnchoredPopover;
938
+ declare function useStreamoidAnchoredPopover(anchorRef: RefObject<HTMLElement | null>, panelRef: RefObject<HTMLElement | null>, { width, gap, inset, alignRef, }: StreamoidAnchoredPopoverPositionOptions): StreamoidAnchoredPopover;
924
939
  declare function useStreamoidAnchoredPopoverPosition(anchorRef: RefObject<HTMLElement | null>, panelRef: RefObject<HTMLElement | null>, { width, gap, inset }: StreamoidAnchoredPopoverPositionOptions): StreamoidAnchoredPopoverPosition;
925
940
 
926
941
  interface IScPopoverArrowProps {
package/dist/index.d.ts CHANGED
@@ -835,6 +835,17 @@ interface StreamoidAnchorRect {
835
835
  }
836
836
  interface StreamoidAnchoredPopoverGeometry {
837
837
  anchor: StreamoidAnchorRect;
838
+ /** Sets the horizontal placement when the trigger should not.
839
+ *
840
+ * A rail with two popovers — a workspace menu at the top, an app switcher at
841
+ * the foot — has two triggers at different insets, because each sits inside
842
+ * a button with its own padding. Anchored to those, the two panels open a
843
+ * dozen pixels apart and the pair reads as sloppy rather than as one
844
+ * pattern. Align both to the RAIL and they open on the same line, while the
845
+ * caret still points at whichever trigger was used.
846
+ *
847
+ * Defaults to `anchor`, which is the single-popover case. */
848
+ align?: StreamoidAnchorRect;
838
849
  viewportWidth: number;
839
850
  viewportHeight: number;
840
851
  /** Panel width in pixels. */
@@ -849,6 +860,10 @@ interface StreamoidAnchoredPopoverGeometry {
849
860
  interface StreamoidAnchoredPopoverPositionOptions {
850
861
  /** Panel width in pixels. */
851
862
  width: number;
863
+ /** Element whose edges set the horizontal placement, when the trigger should
864
+ * not. See `align` on the geometry — pass the rail to line two of its
865
+ * popovers up with each other. */
866
+ alignRef?: RefObject<HTMLElement | null>;
852
867
  /** Gap between the trigger and the panel. Defaults to 8px. */
853
868
  gap?: number;
854
869
  /** Keep-out margin from the viewport edges. Defaults to 16px. */
@@ -900,7 +915,7 @@ declare function resolveAnchoredPopoverArrow({ anchor, panelLeft, panelTop, pane
900
915
  * below. Nothing else re-places it, so the panel is always visibly attached to
901
916
  * the button that opened it rather than parked against a container edge.
902
917
  */
903
- declare function resolveAnchoredPopoverPosition({ anchor, viewportWidth, viewportHeight, width, height, gap, inset, }: StreamoidAnchoredPopoverGeometry): {
918
+ declare function resolveAnchoredPopoverPosition({ anchor, align, viewportWidth, viewportHeight, width, height, gap, inset, }: StreamoidAnchoredPopoverGeometry): {
904
919
  left: number;
905
920
  top: number;
906
921
  width: number;
@@ -920,7 +935,7 @@ declare function resolveAnchoredPopoverPosition({ anchor, viewportWidth, viewpor
920
935
  */
921
936
  /** The position and the caret, measured together — a caret measured on a later
922
937
  * render can point somewhere the panel no longer is. */
923
- declare function useStreamoidAnchoredPopover(anchorRef: RefObject<HTMLElement | null>, panelRef: RefObject<HTMLElement | null>, { width, gap, inset }: StreamoidAnchoredPopoverPositionOptions): StreamoidAnchoredPopover;
938
+ declare function useStreamoidAnchoredPopover(anchorRef: RefObject<HTMLElement | null>, panelRef: RefObject<HTMLElement | null>, { width, gap, inset, alignRef, }: StreamoidAnchoredPopoverPositionOptions): StreamoidAnchoredPopover;
924
939
  declare function useStreamoidAnchoredPopoverPosition(anchorRef: RefObject<HTMLElement | null>, panelRef: RefObject<HTMLElement | null>, { width, gap, inset }: StreamoidAnchoredPopoverPositionOptions): StreamoidAnchoredPopoverPosition;
925
940
 
926
941
  interface IScPopoverArrowProps {
package/dist/index.js CHANGED
@@ -5913,6 +5913,7 @@ var DEFAULT_GAP = 8;
5913
5913
  var DEFAULT_INSET = 16;
5914
5914
  function resolveAnchoredPopoverPosition({
5915
5915
  anchor,
5916
+ align,
5916
5917
  viewportWidth,
5917
5918
  viewportHeight,
5918
5919
  width,
@@ -5922,8 +5923,9 @@ function resolveAnchoredPopoverPosition({
5922
5923
  }) {
5923
5924
  const available = Math.max(0, viewportWidth - inset * 2);
5924
5925
  const panelWidth = Math.min(width, available);
5925
- const rightOfAnchor = anchor.right + gap;
5926
- const leftOfAnchor = anchor.left - gap - panelWidth;
5926
+ const beside = align ?? anchor;
5927
+ const rightOfAnchor = beside.right + gap;
5928
+ const leftOfAnchor = beside.left - gap - panelWidth;
5927
5929
  const fitsRight = rightOfAnchor + panelWidth <= viewportWidth - inset;
5928
5930
  const preferredLeft = fitsRight ? rightOfAnchor : leftOfAnchor >= inset ? leftOfAnchor : rightOfAnchor;
5929
5931
  const left = Math.max(
@@ -5941,7 +5943,12 @@ function resolveAnchoredPopoverPosition({
5941
5943
  );
5942
5944
  return { left, top, width: panelWidth };
5943
5945
  }
5944
- function useStreamoidAnchoredPopover(anchorRef, panelRef, { width, gap = DEFAULT_GAP, inset = DEFAULT_INSET }) {
5946
+ function useStreamoidAnchoredPopover(anchorRef, panelRef, {
5947
+ width,
5948
+ gap = DEFAULT_GAP,
5949
+ inset = DEFAULT_INSET,
5950
+ alignRef
5951
+ }) {
5945
5952
  const [state, setState] = (0, import_react16.useState)({
5946
5953
  style: {
5947
5954
  position: "fixed",
@@ -5962,6 +5969,7 @@ function useStreamoidAnchoredPopover(anchorRef, panelRef, { width, gap = DEFAULT
5962
5969
  const panelHeight = panelRef.current?.offsetHeight;
5963
5970
  const { left, top, width: panelWidth } = resolveAnchoredPopoverPosition({
5964
5971
  anchor: bounds,
5972
+ align: alignRef?.current?.getBoundingClientRect(),
5965
5973
  viewportWidth: window.innerWidth,
5966
5974
  viewportHeight: window.innerHeight,
5967
5975
  width,
@@ -5994,12 +6002,13 @@ function useStreamoidAnchoredPopover(anchorRef, panelRef, { width, gap = DEFAULT
5994
6002
  const resizeObserver = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(measure);
5995
6003
  resizeObserver?.observe(anchor);
5996
6004
  if (panelRef.current) resizeObserver?.observe(panelRef.current);
6005
+ if (alignRef?.current) resizeObserver?.observe(alignRef.current);
5997
6006
  return () => {
5998
6007
  window.removeEventListener("resize", measure);
5999
6008
  window.removeEventListener("scroll", measure, true);
6000
6009
  resizeObserver?.disconnect();
6001
6010
  };
6002
- }, [anchorRef, panelRef, width, gap, inset]);
6011
+ }, [anchorRef, panelRef, alignRef, width, gap, inset]);
6003
6012
  return state;
6004
6013
  }
6005
6014
  function useStreamoidAnchoredPopoverPosition(anchorRef, panelRef, { width, gap = DEFAULT_GAP, inset = DEFAULT_INSET }) {
package/dist/index.mjs CHANGED
@@ -5740,6 +5740,7 @@ var DEFAULT_GAP = 8;
5740
5740
  var DEFAULT_INSET = 16;
5741
5741
  function resolveAnchoredPopoverPosition({
5742
5742
  anchor,
5743
+ align,
5743
5744
  viewportWidth,
5744
5745
  viewportHeight,
5745
5746
  width,
@@ -5749,8 +5750,9 @@ function resolveAnchoredPopoverPosition({
5749
5750
  }) {
5750
5751
  const available = Math.max(0, viewportWidth - inset * 2);
5751
5752
  const panelWidth = Math.min(width, available);
5752
- const rightOfAnchor = anchor.right + gap;
5753
- const leftOfAnchor = anchor.left - gap - panelWidth;
5753
+ const beside = align ?? anchor;
5754
+ const rightOfAnchor = beside.right + gap;
5755
+ const leftOfAnchor = beside.left - gap - panelWidth;
5754
5756
  const fitsRight = rightOfAnchor + panelWidth <= viewportWidth - inset;
5755
5757
  const preferredLeft = fitsRight ? rightOfAnchor : leftOfAnchor >= inset ? leftOfAnchor : rightOfAnchor;
5756
5758
  const left = Math.max(
@@ -5768,7 +5770,12 @@ function resolveAnchoredPopoverPosition({
5768
5770
  );
5769
5771
  return { left, top, width: panelWidth };
5770
5772
  }
5771
- function useStreamoidAnchoredPopover(anchorRef, panelRef, { width, gap = DEFAULT_GAP, inset = DEFAULT_INSET }) {
5773
+ function useStreamoidAnchoredPopover(anchorRef, panelRef, {
5774
+ width,
5775
+ gap = DEFAULT_GAP,
5776
+ inset = DEFAULT_INSET,
5777
+ alignRef
5778
+ }) {
5772
5779
  const [state, setState] = useState13({
5773
5780
  style: {
5774
5781
  position: "fixed",
@@ -5789,6 +5796,7 @@ function useStreamoidAnchoredPopover(anchorRef, panelRef, { width, gap = DEFAULT
5789
5796
  const panelHeight = panelRef.current?.offsetHeight;
5790
5797
  const { left, top, width: panelWidth } = resolveAnchoredPopoverPosition({
5791
5798
  anchor: bounds,
5799
+ align: alignRef?.current?.getBoundingClientRect(),
5792
5800
  viewportWidth: window.innerWidth,
5793
5801
  viewportHeight: window.innerHeight,
5794
5802
  width,
@@ -5821,12 +5829,13 @@ function useStreamoidAnchoredPopover(anchorRef, panelRef, { width, gap = DEFAULT
5821
5829
  const resizeObserver = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(measure);
5822
5830
  resizeObserver?.observe(anchor);
5823
5831
  if (panelRef.current) resizeObserver?.observe(panelRef.current);
5832
+ if (alignRef?.current) resizeObserver?.observe(alignRef.current);
5824
5833
  return () => {
5825
5834
  window.removeEventListener("resize", measure);
5826
5835
  window.removeEventListener("scroll", measure, true);
5827
5836
  resizeObserver?.disconnect();
5828
5837
  };
5829
- }, [anchorRef, panelRef, width, gap, inset]);
5838
+ }, [anchorRef, panelRef, alignRef, width, gap, inset]);
5830
5839
  return state;
5831
5840
  }
5832
5841
  function useStreamoidAnchoredPopoverPosition(anchorRef, panelRef, { width, gap = DEFAULT_GAP, inset = DEFAULT_INSET }) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streamoid/ui",
3
- "version": "0.6.32",
3
+ "version": "0.6.33",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.ts",