@multiplatform.one/forms 7.72.0 → 7.74.0
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/dist/cjs/Button/IconButtonGroup.cjs +32 -0
- package/dist/cjs/Button/IconButtonGroup.native.js +32 -0
- package/dist/cjs/Button/IconButtonGroup.native.js.map +1 -1
- package/dist/cjs/Button/circleGlyph.cjs +28 -0
- package/dist/cjs/Button/circleGlyph.native.js +31 -0
- package/dist/cjs/Button/circleGlyph.native.js.map +1 -0
- package/dist/cjs/Button/glass.cjs +4 -1
- package/dist/cjs/Button/iconButtonGroupContext.native.js.map +1 -1
- package/dist/cjs/Button/index.cjs +44 -15
- package/dist/cjs/Button/index.native.js +52 -17
- package/dist/cjs/Button/index.native.js.map +1 -1
- package/dist/cjs/Button/webGlass.cjs +62 -0
- package/dist/cjs/Button/webGlass.native.js +68 -0
- package/dist/cjs/Button/webGlass.native.js.map +1 -0
- package/dist/cjs/FloatingPanel/PanelHeader.cjs +49 -0
- package/dist/cjs/FloatingPanel/PanelHeader.native.js +53 -0
- package/dist/cjs/FloatingPanel/PanelHeader.native.js.map +1 -0
- package/dist/cjs/FloatingPanel/echoContext.cjs +28 -0
- package/dist/cjs/FloatingPanel/echoContext.native.js +29 -0
- package/dist/cjs/FloatingPanel/echoContext.native.js.map +1 -1
- package/dist/cjs/FloatingPanel/iconEcho.cjs +104 -0
- package/dist/cjs/FloatingPanel/iconEcho.native.js +114 -0
- package/dist/cjs/FloatingPanel/iconEcho.native.js.map +1 -0
- package/dist/cjs/FloatingPanel/index.cjs +168 -15
- package/dist/cjs/FloatingPanel/index.native.js +77 -8
- package/dist/cjs/FloatingPanel/index.native.js.map +1 -1
- package/dist/cjs/FloatingPanel/paintedBox.cjs +80 -0
- package/dist/cjs/FloatingPanel/paintedBox.native.js +85 -0
- package/dist/cjs/FloatingPanel/paintedBox.native.js.map +1 -0
- package/dist/cjs/FloatingPanel/pointAnchor.cjs +22 -3
- package/dist/cjs/FloatingPanel/pointAnchor.native.js +26 -7
- package/dist/cjs/FloatingPanel/pointAnchor.native.js.map +1 -1
- package/dist/cjs/FloatingPanel/useFloatingPanel.cjs +2 -1
- package/dist/cjs/FloatingPanel/useFloatingPanel.native.js +3 -1
- package/dist/cjs/FloatingPanel/useFloatingPanel.native.js.map +1 -1
- package/dist/cjs/fields/FileUpload/index.cjs +8 -3
- package/dist/cjs/fields/FileUpload/index.native.js +8 -3
- package/dist/cjs/fields/FileUpload/index.native.js.map +1 -1
- package/dist/cjs/index.cjs +2 -0
- package/dist/cjs/index.native.js +2 -0
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/esm/Button/IconButtonGroup.mjs +32 -0
- package/dist/esm/Button/IconButtonGroup.mjs.map +1 -1
- package/dist/esm/Button/IconButtonGroup.native.js +32 -0
- package/dist/esm/Button/IconButtonGroup.native.js.map +1 -1
- package/dist/esm/Button/circleGlyph.mjs +3 -0
- package/dist/esm/Button/circleGlyph.mjs.map +1 -0
- package/dist/esm/Button/circleGlyph.native.js +3 -0
- package/dist/esm/Button/circleGlyph.native.js.map +1 -0
- package/dist/esm/Button/glass.mjs +4 -2
- package/dist/esm/Button/glass.mjs.map +1 -1
- package/dist/esm/Button/iconButtonGroupContext.mjs.map +1 -1
- package/dist/esm/Button/iconButtonGroupContext.native.js.map +1 -1
- package/dist/esm/Button/index.mjs +44 -16
- package/dist/esm/Button/index.mjs.map +1 -1
- package/dist/esm/Button/index.native.js +52 -18
- package/dist/esm/Button/index.native.js.map +1 -1
- package/dist/esm/Button/webGlass.mjs +36 -0
- package/dist/esm/Button/webGlass.mjs.map +1 -0
- package/dist/esm/Button/webGlass.native.js +39 -0
- package/dist/esm/Button/webGlass.native.js.map +1 -0
- package/dist/esm/FloatingPanel/PanelHeader.mjs +24 -0
- package/dist/esm/FloatingPanel/PanelHeader.mjs.map +1 -0
- package/dist/esm/FloatingPanel/PanelHeader.native.js +25 -0
- package/dist/esm/FloatingPanel/PanelHeader.native.js.map +1 -0
- package/dist/esm/FloatingPanel/echoContext.mjs +25 -1
- package/dist/esm/FloatingPanel/echoContext.mjs.map +1 -1
- package/dist/esm/FloatingPanel/echoContext.native.js +26 -1
- package/dist/esm/FloatingPanel/echoContext.native.js.map +1 -1
- package/dist/esm/FloatingPanel/iconEcho.mjs +77 -0
- package/dist/esm/FloatingPanel/iconEcho.mjs.map +1 -0
- package/dist/esm/FloatingPanel/iconEcho.native.js +84 -0
- package/dist/esm/FloatingPanel/iconEcho.native.js.map +1 -0
- package/dist/esm/FloatingPanel/index.mjs +169 -16
- package/dist/esm/FloatingPanel/index.mjs.map +1 -1
- package/dist/esm/FloatingPanel/index.native.js +79 -10
- package/dist/esm/FloatingPanel/index.native.js.map +1 -1
- package/dist/esm/FloatingPanel/paintedBox.mjs +52 -0
- package/dist/esm/FloatingPanel/paintedBox.mjs.map +1 -0
- package/dist/esm/FloatingPanel/paintedBox.native.js +54 -0
- package/dist/esm/FloatingPanel/paintedBox.native.js.map +1 -0
- package/dist/esm/FloatingPanel/pointAnchor.mjs +22 -3
- package/dist/esm/FloatingPanel/pointAnchor.mjs.map +1 -1
- package/dist/esm/FloatingPanel/pointAnchor.native.js +26 -7
- package/dist/esm/FloatingPanel/pointAnchor.native.js.map +1 -1
- package/dist/esm/FloatingPanel/useFloatingPanel.mjs +2 -1
- package/dist/esm/FloatingPanel/useFloatingPanel.mjs.map +1 -1
- package/dist/esm/FloatingPanel/useFloatingPanel.native.js +3 -1
- package/dist/esm/FloatingPanel/useFloatingPanel.native.js.map +1 -1
- package/dist/esm/fields/FileUpload/index.mjs +8 -3
- package/dist/esm/fields/FileUpload/index.mjs.map +1 -1
- package/dist/esm/fields/FileUpload/index.native.js +8 -3
- package/dist/esm/fields/FileUpload/index.native.js.map +1 -1
- package/dist/esm/index.mjs +2 -1
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/index.native.js +2 -1
- package/dist/esm/index.native.js.map +1 -1
- package/dist/jsx/Button/IconButtonGroup.mjs +32 -0
- package/dist/jsx/Button/IconButtonGroup.mjs.map +1 -1
- package/dist/jsx/Button/IconButtonGroup.native.js +32 -0
- package/dist/jsx/Button/IconButtonGroup.native.js.map +1 -1
- package/dist/jsx/Button/circleGlyph.mjs +3 -0
- package/dist/jsx/Button/circleGlyph.mjs.map +1 -0
- package/dist/jsx/Button/circleGlyph.native.js +31 -0
- package/dist/jsx/Button/circleGlyph.native.js.map +1 -0
- package/dist/jsx/Button/glass.mjs +4 -2
- package/dist/jsx/Button/glass.mjs.map +1 -1
- package/dist/jsx/Button/iconButtonGroupContext.mjs.map +1 -1
- package/dist/jsx/Button/iconButtonGroupContext.native.js.map +1 -1
- package/dist/jsx/Button/index.mjs +44 -16
- package/dist/jsx/Button/index.mjs.map +1 -1
- package/dist/jsx/Button/index.native.js +52 -17
- package/dist/jsx/Button/index.native.js.map +1 -1
- package/dist/jsx/Button/webGlass.mjs +36 -0
- package/dist/jsx/Button/webGlass.mjs.map +1 -0
- package/dist/jsx/Button/webGlass.native.js +68 -0
- package/dist/jsx/Button/webGlass.native.js.map +1 -0
- package/dist/jsx/FloatingPanel/PanelHeader.mjs +24 -0
- package/dist/jsx/FloatingPanel/PanelHeader.mjs.map +1 -0
- package/dist/jsx/FloatingPanel/PanelHeader.native.js +53 -0
- package/dist/jsx/FloatingPanel/PanelHeader.native.js.map +1 -0
- package/dist/jsx/FloatingPanel/echoContext.mjs +25 -1
- package/dist/jsx/FloatingPanel/echoContext.mjs.map +1 -1
- package/dist/jsx/FloatingPanel/echoContext.native.js +29 -0
- package/dist/jsx/FloatingPanel/echoContext.native.js.map +1 -1
- package/dist/jsx/FloatingPanel/iconEcho.mjs +77 -0
- package/dist/jsx/FloatingPanel/iconEcho.mjs.map +1 -0
- package/dist/jsx/FloatingPanel/iconEcho.native.js +114 -0
- package/dist/jsx/FloatingPanel/iconEcho.native.js.map +1 -0
- package/dist/jsx/FloatingPanel/index.mjs +169 -16
- package/dist/jsx/FloatingPanel/index.mjs.map +1 -1
- package/dist/jsx/FloatingPanel/index.native.js +77 -8
- package/dist/jsx/FloatingPanel/index.native.js.map +1 -1
- package/dist/jsx/FloatingPanel/paintedBox.mjs +52 -0
- package/dist/jsx/FloatingPanel/paintedBox.mjs.map +1 -0
- package/dist/jsx/FloatingPanel/paintedBox.native.js +85 -0
- package/dist/jsx/FloatingPanel/paintedBox.native.js.map +1 -0
- package/dist/jsx/FloatingPanel/pointAnchor.mjs +22 -3
- package/dist/jsx/FloatingPanel/pointAnchor.mjs.map +1 -1
- package/dist/jsx/FloatingPanel/pointAnchor.native.js +26 -7
- package/dist/jsx/FloatingPanel/pointAnchor.native.js.map +1 -1
- package/dist/jsx/FloatingPanel/useFloatingPanel.mjs +2 -1
- package/dist/jsx/FloatingPanel/useFloatingPanel.mjs.map +1 -1
- package/dist/jsx/FloatingPanel/useFloatingPanel.native.js +3 -1
- package/dist/jsx/FloatingPanel/useFloatingPanel.native.js.map +1 -1
- package/dist/jsx/fields/FileUpload/index.mjs +8 -3
- package/dist/jsx/fields/FileUpload/index.mjs.map +1 -1
- package/dist/jsx/fields/FileUpload/index.native.js +8 -3
- package/dist/jsx/fields/FileUpload/index.native.js.map +1 -1
- package/dist/jsx/index.js +2 -1
- package/dist/jsx/index.js.map +1 -1
- package/dist/jsx/index.mjs +2 -1
- package/dist/jsx/index.mjs.map +1 -1
- package/dist/jsx/index.native.js +2 -0
- package/dist/jsx/index.native.js.map +1 -1
- package/package.json +8 -8
- package/src/Button/Button.accentInk.spec.tsx +39 -8
- package/src/Button/Button.iconButton.spec.tsx +143 -3
- package/src/Button/Button.stories.tsx +55 -38
- package/src/Button/IconButtonGroup.spec.tsx +26 -0
- package/src/Button/IconButtonGroup.tsx +39 -0
- package/src/Button/circleGlyph.ts +6 -0
- package/src/Button/glass.spec.ts +73 -0
- package/src/Button/glass.ts +8 -3
- package/src/Button/iconButtonGroupContext.ts +2 -1
- package/src/Button/index.tsx +68 -17
- package/src/Button/webGlass.ts +42 -0
- package/src/FloatingPanel/FloatingPanel.iconEcho.spec.tsx +297 -0
- package/src/FloatingPanel/FloatingPanel.menuFace.native.spec.tsx +87 -1
- package/src/FloatingPanel/PanelHeader.tsx +26 -0
- package/src/FloatingPanel/echoContext.ts +45 -0
- package/src/FloatingPanel/iconEcho.tsx +118 -0
- package/src/FloatingPanel/index.native.tsx +115 -4
- package/src/FloatingPanel/index.tsx +238 -16
- package/src/FloatingPanel/paintedBox.spec.ts +95 -0
- package/src/FloatingPanel/paintedBox.ts +75 -0
- package/src/FloatingPanel/pointAnchor.spec.ts +47 -0
- package/src/FloatingPanel/pointAnchor.ts +28 -0
- package/src/FloatingPanel/useFloatingPanel.ts +6 -1
- package/src/fields/FileUpload/FileUpload.spec.tsx +8 -0
- package/src/fields/FileUpload/index.tsx +12 -4
- package/src/index.ts +2 -0
- package/types/Button/IconButtonGroup.d.ts.map +1 -1
- package/types/Button/circleGlyph.d.ts +7 -0
- package/types/Button/circleGlyph.d.ts.map +1 -0
- package/types/Button/glass.d.ts +5 -2
- package/types/Button/glass.d.ts.map +1 -1
- package/types/Button/iconButtonGroupContext.d.ts +2 -1
- package/types/Button/iconButtonGroupContext.d.ts.map +1 -1
- package/types/Button/index.d.ts +7 -4
- package/types/Button/index.d.ts.map +1 -1
- package/types/Button/webGlass.d.ts +32 -0
- package/types/Button/webGlass.d.ts.map +1 -0
- package/types/FloatingPanel/PanelHeader.d.ts +9 -0
- package/types/FloatingPanel/PanelHeader.d.ts.map +1 -0
- package/types/FloatingPanel/echoContext.d.ts +24 -0
- package/types/FloatingPanel/echoContext.d.ts.map +1 -1
- package/types/FloatingPanel/iconEcho.d.ts +29 -0
- package/types/FloatingPanel/iconEcho.d.ts.map +1 -0
- package/types/FloatingPanel/index.d.ts +10 -0
- package/types/FloatingPanel/index.d.ts.map +1 -1
- package/types/FloatingPanel/index.native.d.ts +1 -1
- package/types/FloatingPanel/index.native.d.ts.map +1 -1
- package/types/FloatingPanel/paintedBox.d.ts +29 -0
- package/types/FloatingPanel/paintedBox.d.ts.map +1 -0
- package/types/FloatingPanel/pointAnchor.d.ts +10 -1
- package/types/FloatingPanel/pointAnchor.d.ts.map +1 -1
- package/types/FloatingPanel/useFloatingPanel.d.ts.map +1 -1
- package/types/fields/FileUpload/index.d.ts.map +1 -1
- package/types/index.d.ts +2 -0
- package/types/index.d.ts.map +1 -1
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
OVERLAY_ATTACH_GAP,
|
|
16
16
|
OVERLAY_BREAKPOINT,
|
|
17
17
|
Surface,
|
|
18
|
+
SurfaceContext,
|
|
18
19
|
useResolvedKnobs,
|
|
19
20
|
wasKeyboardFocus,
|
|
20
21
|
ensureKeyboardModalityTracking,
|
|
@@ -40,6 +41,14 @@ import { OverlayStackProvider, useInsideOverlay, useOverlayZIndex } from "../sha
|
|
|
40
41
|
import { resolveCompactFace, type CompactFace } from "./compactFace";
|
|
41
42
|
import { isNestedPanelAction, type PanelActionEvent } from "./nestedAction";
|
|
42
43
|
import { PanelPortal } from "./PanelPortal";
|
|
44
|
+
import {
|
|
45
|
+
echoHeaderBox,
|
|
46
|
+
PanelIconEchoBoxContext,
|
|
47
|
+
sameEchoBox,
|
|
48
|
+
type PanelIconEchoBox,
|
|
49
|
+
} from "./echoContext";
|
|
50
|
+
import { iconEchoElement, iconEchoOverlays, type IconEcho } from "./iconEcho";
|
|
51
|
+
import { groupSurfaceOf, paintedTriggerBox } from "./paintedBox";
|
|
43
52
|
import {
|
|
44
53
|
contextMenuPoint,
|
|
45
54
|
placeOverTrigger,
|
|
@@ -92,6 +101,15 @@ export interface FloatingPanelProps {
|
|
|
92
101
|
* instead of handing it to a sheet, the way an iOS menu grows out of it.
|
|
93
102
|
*/
|
|
94
103
|
coverAlign?: "start" | "auto";
|
|
104
|
+
/**
|
|
105
|
+
* An icon-only trigger's covering panel draws the pressed button again in
|
|
106
|
+
* its own spot as an X, same size and plate, the way iOS morphs a + into an
|
|
107
|
+
* X (LC-112, FZ-8480 Round 37). `"close"` lays the X over the content's
|
|
108
|
+
* header row; `"menu"` gives it its own band above the rows. It closes the
|
|
109
|
+
* panel and reads "Close". The panel never slides off the button: it opens
|
|
110
|
+
* down from the button's top edge, or up from its bottom edge.
|
|
111
|
+
*/
|
|
112
|
+
iconEcho?: IconEcho;
|
|
95
113
|
/**
|
|
96
114
|
* Optional control rendered beside the trigger but OUTSIDE the native press
|
|
97
115
|
* capture (e.g. DatePicker/ColorPicker clear). On native the trigger subtree
|
|
@@ -519,6 +537,103 @@ export function placeEchoOverTrigger(
|
|
|
519
537
|
}
|
|
520
538
|
}
|
|
521
539
|
|
|
540
|
+
/**
|
|
541
|
+
* The band an icon trigger's echo rides (LC-112, FZ-8480): as tall as the
|
|
542
|
+
* trigger, sticky on the panel's top edge (or its bottom edge when the panel
|
|
543
|
+
* opened upward), filled with the panel's surface so scrolled rows pass under
|
|
544
|
+
* it. It comes last in the document, so a keyboard open lands on the panel's
|
|
545
|
+
* content and the echo is the last tab stop; CSS order draws it first.
|
|
546
|
+
*/
|
|
547
|
+
function IconEchoRow({ children }: { children: ReactNode }) {
|
|
548
|
+
return (
|
|
549
|
+
<View
|
|
550
|
+
data-fp-icon-echo=""
|
|
551
|
+
flexShrink={0}
|
|
552
|
+
alignSelf="stretch"
|
|
553
|
+
pointerEvents="box-none"
|
|
554
|
+
style={{ order: -1, position: "sticky", zIndex: 1 }}
|
|
555
|
+
>
|
|
556
|
+
<View data-fp-icon-echo-box="" alignItems="flex-start" justifyContent="flex-start">
|
|
557
|
+
{children}
|
|
558
|
+
</View>
|
|
559
|
+
</View>
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Lay the icon echo on the trigger's measured box: the band is the trigger's
|
|
565
|
+
* height on the edge the panel shares with it, and the box sits `offset` in
|
|
566
|
+
* from the panel's start edge, pulled back over the frame's border so the
|
|
567
|
+
* button lands exactly where the trigger is. An overlaid echo (an X) takes no
|
|
568
|
+
* height of its own and lets the content show beside it.
|
|
569
|
+
*/
|
|
570
|
+
function placeIconEcho(
|
|
571
|
+
panelElement: HTMLElement,
|
|
572
|
+
{
|
|
573
|
+
offset,
|
|
574
|
+
width,
|
|
575
|
+
height,
|
|
576
|
+
up,
|
|
577
|
+
overlay,
|
|
578
|
+
group = null,
|
|
579
|
+
}: {
|
|
580
|
+
offset: number;
|
|
581
|
+
width: number;
|
|
582
|
+
height: number;
|
|
583
|
+
up: boolean;
|
|
584
|
+
overlay: boolean;
|
|
585
|
+
/**
|
|
586
|
+
* The shared plate a grouped trigger sits on: the band covers all of it,
|
|
587
|
+
* and only the pressed trigger shows, at its own spot in the plate.
|
|
588
|
+
*/
|
|
589
|
+
group?: { top: number; height: number } | null;
|
|
590
|
+
},
|
|
591
|
+
) {
|
|
592
|
+
const row = panelElement.querySelector<HTMLElement>("[data-fp-icon-echo]");
|
|
593
|
+
const box = row?.querySelector<HTMLElement>("[data-fp-icon-echo-box]");
|
|
594
|
+
if (!row || !box) return;
|
|
595
|
+
const frame = getComputedStyle(panelElement);
|
|
596
|
+
const border = (side: "Top" | "Bottom" | "Left" | "Right") =>
|
|
597
|
+
parseFloat(frame[`border${side}Width`] || "0") || 0;
|
|
598
|
+
const rtl = panelElement.dir === "rtl";
|
|
599
|
+
row.style.order = up ? "1" : "-1";
|
|
600
|
+
row.style.height = `${height}px`;
|
|
601
|
+
row.style.backgroundColor = overlay ? "transparent" : frame.backgroundColor;
|
|
602
|
+
if (up) {
|
|
603
|
+
row.style.top = "";
|
|
604
|
+
row.style.marginTop = "";
|
|
605
|
+
row.style.marginBottom = `${-border("Bottom")}px`;
|
|
606
|
+
row.style.bottom = row.style.marginBottom;
|
|
607
|
+
} else {
|
|
608
|
+
row.style.bottom = "";
|
|
609
|
+
row.style.marginTop = `${-border("Top")}px`;
|
|
610
|
+
row.style.marginBottom = overlay ? `${-(height - border("Top"))}px` : "";
|
|
611
|
+
row.style.top = row.style.marginTop;
|
|
612
|
+
}
|
|
613
|
+
row.setAttribute("data-fp-icon-echo", overlay ? "overlay" : "band");
|
|
614
|
+
if (group) {
|
|
615
|
+
row.style.height = `${group.height}px`;
|
|
616
|
+
if (!up && overlay) row.style.marginBottom = `${-(group.height - border("Top"))}px`;
|
|
617
|
+
}
|
|
618
|
+
row.style.marginLeft = `${-border("Left")}px`;
|
|
619
|
+
row.style.marginRight = `${-border("Right")}px`;
|
|
620
|
+
box.style.width = `${width}px`;
|
|
621
|
+
box.style.height = `${height}px`;
|
|
622
|
+
box.style.marginLeft = rtl ? "" : `${offset}px`;
|
|
623
|
+
box.style.marginRight = rtl ? `${offset}px` : "";
|
|
624
|
+
box.style.alignSelf = "flex-start";
|
|
625
|
+
box.style.marginTop = group ? `${group.top}px` : "";
|
|
626
|
+
box.style.position = "relative";
|
|
627
|
+
box.style.zIndex = "1";
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/** The box that lays an element out, past any `display: contents` wrappers. */
|
|
631
|
+
function layoutParentOf(element: HTMLElement | null): HTMLElement | null {
|
|
632
|
+
let parent = element?.parentElement ?? null;
|
|
633
|
+
while (parent && getComputedStyle(parent).display === "contents") parent = parent.parentElement;
|
|
634
|
+
return parent;
|
|
635
|
+
}
|
|
636
|
+
|
|
522
637
|
// ── FloatingPanel ─────────────────────────────────────────────
|
|
523
638
|
|
|
524
639
|
// Canonical float↔sheet pivot (theme `layoutBreakpoints.medium` = Tamagui
|
|
@@ -767,6 +882,7 @@ function FloatingPanelWeb({
|
|
|
767
882
|
cover = true,
|
|
768
883
|
triggerEcho = true,
|
|
769
884
|
coverAlign = "start",
|
|
885
|
+
iconEcho,
|
|
770
886
|
triggerRow,
|
|
771
887
|
anchorRect,
|
|
772
888
|
clampCollisions = false,
|
|
@@ -776,6 +892,13 @@ function FloatingPanelWeb({
|
|
|
776
892
|
const [collisionSheet, setCollisionSheet] = useState(false);
|
|
777
893
|
const isFill = sizing === "fill";
|
|
778
894
|
const isContextMenu = openOn === "contextmenu";
|
|
895
|
+
// A context menu anchored on its trigger (a bar button's long-press menu)
|
|
896
|
+
// covers the trigger like any other menu; only a free one opens at the point.
|
|
897
|
+
const pointMenu = isContextMenu && sheetProps.anchorMode !== "cover-trigger";
|
|
898
|
+
const showIconEcho = cover && !pointMenu && iconEcho != null;
|
|
899
|
+
const triggerSurface = SurfaceContext.useStyledContext();
|
|
900
|
+
const iconEchoUpRef = useRef(false);
|
|
901
|
+
const [echoBox, setEchoBox] = useState<PanelIconEchoBox | null>(null);
|
|
779
902
|
const overlayStyleRef = useRef<{ top: number; left?: number; maxHeight: number } | null>(null);
|
|
780
903
|
const pinnedTopRef = useRef(0);
|
|
781
904
|
const pinnedHeightRef = useRef(0);
|
|
@@ -810,10 +933,10 @@ function FloatingPanelWeb({
|
|
|
810
933
|
wheelGrowRef: isFill ? undefined : grownRef,
|
|
811
934
|
widthMode: widthMode ?? (isContextMenu && fitContent === undefined ? "fit-content" : undefined),
|
|
812
935
|
fitContent,
|
|
813
|
-
freeOverlay:
|
|
936
|
+
freeOverlay: pointMenu,
|
|
814
937
|
cover,
|
|
815
938
|
panelWidth,
|
|
816
|
-
echo: cover && triggerEcho && !
|
|
939
|
+
echo: cover && triggerEcho && !pointMenu,
|
|
817
940
|
onBeforeOpen: resetPosition,
|
|
818
941
|
});
|
|
819
942
|
|
|
@@ -854,7 +977,7 @@ function FloatingPanelWeb({
|
|
|
854
977
|
dropdownElement.scrollTop = 0;
|
|
855
978
|
}
|
|
856
979
|
|
|
857
|
-
const point =
|
|
980
|
+
const point = pointMenu
|
|
858
981
|
? (anchorPointRef.current ?? referenceCornerPoint(trig, isRtl()))
|
|
859
982
|
: null;
|
|
860
983
|
const measured = anchorRect
|
|
@@ -887,6 +1010,12 @@ function FloatingPanelWeb({
|
|
|
887
1010
|
(viewport?.width ?? (document.documentElement.clientWidth || window.innerWidth));
|
|
888
1011
|
const viewportBottom = viewportTop + (viewport?.height ?? window.innerHeight);
|
|
889
1012
|
dropdownElement.style.left = `${viewportLeft - containingBlock.left}px`;
|
|
1013
|
+
// LC-112 (FZ-8480 Round 37): a grouped icon trigger's surface is the
|
|
1014
|
+
// group's plate, so the panel covers the whole plate; only the pressed
|
|
1015
|
+
// trigger shows, as its X.
|
|
1016
|
+
const groupSurface = showIconEcho ? groupSurfaceOf(trig) : null;
|
|
1017
|
+
const groupBox = groupSurface?.getBoundingClientRect() ?? null;
|
|
1018
|
+
if (showIconEcho) dropdownElement.style.minWidth = groupBox ? `${groupBox.width}px` : "";
|
|
890
1019
|
const layoutStyle = getComputedStyle(dropdownElement);
|
|
891
1020
|
const scale =
|
|
892
1021
|
layoutStyle.transform &&
|
|
@@ -955,7 +1084,38 @@ function FloatingPanelWeb({
|
|
|
955
1084
|
: totalContentHeight;
|
|
956
1085
|
let placedTop = measured.top;
|
|
957
1086
|
let placedLeft = left;
|
|
958
|
-
|
|
1087
|
+
let placedMaxHeight: number | null = null;
|
|
1088
|
+
iconEchoUpRef.current = false;
|
|
1089
|
+
if (showIconEcho && measured.width > 0 && measured.height > 0) {
|
|
1090
|
+
// LC-112 (FZ-8480): the button is drawn again in its own spot, so the
|
|
1091
|
+
// panel stays on it: down from its top edge, or up from its bottom.
|
|
1092
|
+
// It covers the button's plate and press box where they reach past it.
|
|
1093
|
+
const painted = groupBox ?? paintedTriggerBox(trig, measured);
|
|
1094
|
+
const placed = placeOverTrigger({
|
|
1095
|
+
trigger: {
|
|
1096
|
+
x: painted.left,
|
|
1097
|
+
y: groupBox ? groupBox.top : measured.top,
|
|
1098
|
+
width: painted.right - painted.left,
|
|
1099
|
+
height: groupBox ? groupBox.height : measured.height,
|
|
1100
|
+
},
|
|
1101
|
+
width,
|
|
1102
|
+
height: totalContentHeight,
|
|
1103
|
+
bounds: {
|
|
1104
|
+
top: viewportTop + panelViewportPadding,
|
|
1105
|
+
left: viewportLeft + panelViewportPadding,
|
|
1106
|
+
right: viewportRight - panelViewportPadding,
|
|
1107
|
+
bottom: viewportBottom - panelViewportPadding,
|
|
1108
|
+
},
|
|
1109
|
+
rtl,
|
|
1110
|
+
align: coverAlign,
|
|
1111
|
+
pinned: true,
|
|
1112
|
+
});
|
|
1113
|
+
placedTop = placed.top;
|
|
1114
|
+
if (!slides) placedLeft = placed.left;
|
|
1115
|
+
alignEnd = placed.end;
|
|
1116
|
+
placedMaxHeight = placed.maxHeight;
|
|
1117
|
+
iconEchoUpRef.current = placed.up;
|
|
1118
|
+
} else if (
|
|
959
1119
|
measured.width > 0 &&
|
|
960
1120
|
measured.height > 0 &&
|
|
961
1121
|
((!slides &&
|
|
@@ -996,6 +1156,36 @@ function FloatingPanelWeb({
|
|
|
996
1156
|
alignEnd = placed.end;
|
|
997
1157
|
}
|
|
998
1158
|
coverAlignEndRef.current = alignEnd;
|
|
1159
|
+
const echoStart = echoOffset({
|
|
1160
|
+
triggerLeft: measured.left,
|
|
1161
|
+
triggerRight: measured.right,
|
|
1162
|
+
panelLeft: placedLeft,
|
|
1163
|
+
panelWidth: width,
|
|
1164
|
+
rtl,
|
|
1165
|
+
});
|
|
1166
|
+
const echoOverlays = iconEcho != null && iconEchoOverlays(iconEcho, iconEchoUpRef.current);
|
|
1167
|
+
const nextEchoBox =
|
|
1168
|
+
showIconEcho && echoOverlays
|
|
1169
|
+
? echoHeaderBox({
|
|
1170
|
+
offset: echoStart,
|
|
1171
|
+
width: measured.width,
|
|
1172
|
+
height: measured.height,
|
|
1173
|
+
panelWidth: width,
|
|
1174
|
+
rtl,
|
|
1175
|
+
})
|
|
1176
|
+
: null;
|
|
1177
|
+
setEchoBox((previous) => (sameEchoBox(previous, nextEchoBox) ? previous : nextEchoBox));
|
|
1178
|
+
placeIconEcho(dropdownElement, {
|
|
1179
|
+
offset: echoStart,
|
|
1180
|
+
width: measured.width,
|
|
1181
|
+
height: measured.height,
|
|
1182
|
+
up: iconEchoUpRef.current,
|
|
1183
|
+
overlay: echoOverlays,
|
|
1184
|
+
group:
|
|
1185
|
+
groupSurface && groupBox
|
|
1186
|
+
? { top: measured.top - groupBox.top, height: groupBox.height }
|
|
1187
|
+
: null,
|
|
1188
|
+
});
|
|
999
1189
|
placeEchoOverTrigger(
|
|
1000
1190
|
dropdownElement,
|
|
1001
1191
|
{
|
|
@@ -1009,12 +1199,13 @@ function FloatingPanelWeb({
|
|
|
1009
1199
|
);
|
|
1010
1200
|
const top = placedTop - containingBlock.top;
|
|
1011
1201
|
const cssLeft = placedLeft - containingBlock.left;
|
|
1012
|
-
const maxHeight =
|
|
1202
|
+
const maxHeight =
|
|
1203
|
+
placedMaxHeight ?? Math.max(0, viewportBottom - panelViewportPadding - placedTop);
|
|
1013
1204
|
dropdownElement.style.top = `${top}px`;
|
|
1014
1205
|
dropdownElement.style.left = `${cssLeft}px`;
|
|
1015
1206
|
dropdownElement.style.maxHeight = `${maxHeight}px`;
|
|
1016
1207
|
dropdownElement.dir = rtl ? "rtl" : "ltr";
|
|
1017
|
-
dropdownElement.style.transformOrigin = rtl !== alignEnd ? "
|
|
1208
|
+
dropdownElement.style.transformOrigin = `${iconEchoUpRef.current ? "bottom" : "top"} ${rtl !== alignEnd ? "right" : "left"}`;
|
|
1018
1209
|
dropdownElement.style.visibility = "visible";
|
|
1019
1210
|
overlayStyleRef.current = { top, left: cssLeft, maxHeight };
|
|
1020
1211
|
panel.initializingRef.current = false;
|
|
@@ -1174,6 +1365,8 @@ function FloatingPanelWeb({
|
|
|
1174
1365
|
}, [
|
|
1175
1366
|
isFill,
|
|
1176
1367
|
isContextMenu,
|
|
1368
|
+
pointMenu,
|
|
1369
|
+
showIconEcho,
|
|
1177
1370
|
cover,
|
|
1178
1371
|
anchorRect,
|
|
1179
1372
|
clampCollisions,
|
|
@@ -1243,7 +1436,7 @@ function FloatingPanelWeb({
|
|
|
1243
1436
|
(event: UIEvent) => {
|
|
1244
1437
|
const el = event.target as HTMLElement;
|
|
1245
1438
|
panel.updateArrows(el);
|
|
1246
|
-
if (isFill || (cover && !
|
|
1439
|
+
if (isFill || (cover && !pointMenu)) return;
|
|
1247
1440
|
if (panel.initializingRef.current) {
|
|
1248
1441
|
lastScrollTopRef.current = el.scrollTop;
|
|
1249
1442
|
return;
|
|
@@ -1267,7 +1460,7 @@ function FloatingPanelWeb({
|
|
|
1267
1460
|
repositionFnRef.current?.();
|
|
1268
1461
|
}
|
|
1269
1462
|
},
|
|
1270
|
-
[isFill, cover,
|
|
1463
|
+
[isFill, cover, pointMenu, panel.updateArrows],
|
|
1271
1464
|
);
|
|
1272
1465
|
|
|
1273
1466
|
const webTransition = panel.panelTransition;
|
|
@@ -1354,6 +1547,24 @@ function FloatingPanelWeb({
|
|
|
1354
1547
|
onClickCapture: openFromSynthesizedClick,
|
|
1355
1548
|
...(isContextMenu ? { onContextMenu: openFromContextMenu } : {}),
|
|
1356
1549
|
});
|
|
1550
|
+
// A content-sized trigger hugs its button instead of stretching across its
|
|
1551
|
+
// parent's cross axis. In a row that centres its items (a bar's capsule) it
|
|
1552
|
+
// keeps that centring, so an avatar sits on the row's line with its
|
|
1553
|
+
// neighbours (FZ-8480 Round 37).
|
|
1554
|
+
const contentSized = sheetProps.triggerSizing === "content";
|
|
1555
|
+
const [hugSelf, setHugSelf] = useState<"flex-start" | undefined>(
|
|
1556
|
+
contentSized ? "flex-start" : undefined,
|
|
1557
|
+
);
|
|
1558
|
+
useLayoutEffect(() => {
|
|
1559
|
+
if (!contentSized) {
|
|
1560
|
+
setHugSelf(undefined);
|
|
1561
|
+
return;
|
|
1562
|
+
}
|
|
1563
|
+
const parent = layoutParentOf(panel.triggerRef.current);
|
|
1564
|
+
const align = parent ? getComputedStyle(parent).alignItems : "stretch";
|
|
1565
|
+
setHugSelf(align === "stretch" || align === "normal" ? "flex-start" : undefined);
|
|
1566
|
+
}, [contentSized, panel.triggerRef]);
|
|
1567
|
+
|
|
1357
1568
|
// Guard the composed floating-ui openers, after hook handlers are merged.
|
|
1358
1569
|
// Declining leaves each action's own events and focus defaults intact.
|
|
1359
1570
|
for (const name of ["onMouseDown", "onClick", "onKeyDown", "onKeyUp"] as const) {
|
|
@@ -1371,7 +1582,7 @@ function FloatingPanelWeb({
|
|
|
1371
1582
|
<>
|
|
1372
1583
|
<View
|
|
1373
1584
|
{...referenceProps}
|
|
1374
|
-
alignSelf={
|
|
1585
|
+
alignSelf={hugSelf}
|
|
1375
1586
|
cursor={disabled ? "not-allowed" : "pointer"}
|
|
1376
1587
|
opacity={disabled ? 0.5 : 1}
|
|
1377
1588
|
// The cover contract is stated as a relation between the panel's top
|
|
@@ -1417,7 +1628,7 @@ function FloatingPanelWeb({
|
|
|
1417
1628
|
scrollbarWidth: "none" as any,
|
|
1418
1629
|
zIndex: panelZIndex,
|
|
1419
1630
|
overflow: "auto",
|
|
1420
|
-
...(cover && !
|
|
1631
|
+
...(cover && !pointMenu ? { overflowX: "hidden" as const } : {}),
|
|
1421
1632
|
...panel.floatingStyleRef.current,
|
|
1422
1633
|
...(overlayStyleRef.current
|
|
1423
1634
|
? { maxHeight: overlayStyleRef.current.maxHeight }
|
|
@@ -1429,10 +1640,12 @@ function FloatingPanelWeb({
|
|
|
1429
1640
|
transform: panel.visible ? "none" : "scale(0.96)",
|
|
1430
1641
|
transition: webTransition,
|
|
1431
1642
|
transformOrigin:
|
|
1432
|
-
cover && !
|
|
1433
|
-
?
|
|
1434
|
-
|
|
1435
|
-
|
|
1643
|
+
cover && !pointMenu
|
|
1644
|
+
? `${iconEchoUpRef.current ? "bottom" : "top"} ${
|
|
1645
|
+
isRtl(panel.triggerRef.current) !== coverAlignEndRef.current
|
|
1646
|
+
? "right"
|
|
1647
|
+
: "left"
|
|
1648
|
+
}`
|
|
1436
1649
|
: panelTransformOrigin(
|
|
1437
1650
|
pointPlacementRef.current
|
|
1438
1651
|
? `${pointPlacementRef.current.dropup ? "top" : "bottom"}-${pointPlacementRef.current.alignEnd ? "end" : "start"}`
|
|
@@ -1464,7 +1677,7 @@ function FloatingPanelWeb({
|
|
|
1464
1677
|
paddingVertical={0}
|
|
1465
1678
|
paddingBottom={panel.coverCornerInset || 0}
|
|
1466
1679
|
>
|
|
1467
|
-
{
|
|
1680
|
+
{pointMenu || !cover || !triggerEcho || showIconEcho ? null : (
|
|
1468
1681
|
<TriggerEchoRow
|
|
1469
1682
|
sourceRef={panel.triggerRef}
|
|
1470
1683
|
source={trigger}
|
|
@@ -1485,7 +1698,11 @@ function FloatingPanelWeb({
|
|
|
1485
1698
|
{...(contentPadding !== "none" ? panel.knobProps.panelPadding : undefined)}
|
|
1486
1699
|
{...(contentPadding !== "none" ? panel.knobProps.gapLg : undefined)}
|
|
1487
1700
|
>
|
|
1488
|
-
<OverlayStackProvider zIndex={panelZIndex}>
|
|
1701
|
+
<OverlayStackProvider zIndex={panelZIndex}>
|
|
1702
|
+
<PanelIconEchoBoxContext.Provider value={echoBox}>
|
|
1703
|
+
{children}
|
|
1704
|
+
</PanelIconEchoBoxContext.Provider>
|
|
1705
|
+
</OverlayStackProvider>
|
|
1489
1706
|
</View>
|
|
1490
1707
|
<ScrollArrow
|
|
1491
1708
|
direction="down"
|
|
@@ -1493,6 +1710,11 @@ function FloatingPanelWeb({
|
|
|
1493
1710
|
visible={panel.arrowDown}
|
|
1494
1711
|
arrowScrollDirRef={panel.arrowScrollDirRef}
|
|
1495
1712
|
/>
|
|
1713
|
+
{showIconEcho && iconEcho ? (
|
|
1714
|
+
<IconEchoRow>
|
|
1715
|
+
{iconEchoElement(trigger, iconEcho, () => onOpenChange(false), triggerSurface)}
|
|
1716
|
+
</IconEchoRow>
|
|
1717
|
+
) : null}
|
|
1496
1718
|
</YStack>
|
|
1497
1719
|
</Theme>
|
|
1498
1720
|
</PanelPortal>
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LC-112 (FZ-8480): a panel covering an icon trigger covers the button as it
|
|
3
|
+
* is drawn. The 1440 + hung 6px past its layout slot on its press slop, and
|
|
4
|
+
* the 390 + sat in a lone glass capsule 6px wider on each side; both left a
|
|
5
|
+
* sliver of the button showing beside the panel.
|
|
6
|
+
*/
|
|
7
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
8
|
+
import { PAINTED_BOX_REACH, paintedTriggerBox } from "./paintedBox";
|
|
9
|
+
|
|
10
|
+
type Rect = [left: number, top: number, width: number, height: number];
|
|
11
|
+
|
|
12
|
+
function el(rect: Rect | null, children: HTMLElement[] = [], style = "") {
|
|
13
|
+
const node = document.createElement("div");
|
|
14
|
+
if (style) node.setAttribute("style", style);
|
|
15
|
+
for (const child of children) node.appendChild(child);
|
|
16
|
+
const [left, top, width, height] = rect ?? [0, 0, 0, 0];
|
|
17
|
+
node.getBoundingClientRect = () =>
|
|
18
|
+
({
|
|
19
|
+
left,
|
|
20
|
+
top,
|
|
21
|
+
width,
|
|
22
|
+
height,
|
|
23
|
+
x: left,
|
|
24
|
+
y: top,
|
|
25
|
+
right: left + width,
|
|
26
|
+
bottom: top + height,
|
|
27
|
+
toJSON: () => ({}),
|
|
28
|
+
}) as DOMRect;
|
|
29
|
+
return node;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const box = ([left, top, width, height]: Rect) => ({
|
|
33
|
+
left,
|
|
34
|
+
top,
|
|
35
|
+
right: left + width,
|
|
36
|
+
bottom: top + height,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
afterEach(() => {
|
|
40
|
+
document.body.replaceChildren();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe("paintedTriggerBox (FZ-8480)", () => {
|
|
44
|
+
it("takes in a bare glyph's press box that hangs past its layout slot", () => {
|
|
45
|
+
const button = el([1302.5, 61, 40, 40]);
|
|
46
|
+
const reference = el([1302.5, 61, 34, 40], [el(null, [button], "display: contents")]);
|
|
47
|
+
document.body.appendChild(el([1302.5, 61, 34, 40], [reference]));
|
|
48
|
+
expect(paintedTriggerBox(reference, box([1302.5, 61, 34, 40]))).toEqual(
|
|
49
|
+
box([1302.5, 61, 40, 40]),
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("takes in a lone button's plate, ignoring the plate's absolute face", () => {
|
|
54
|
+
const reference = el([324, 61, 44, 44]);
|
|
55
|
+
const face = el([318, 61, 56, 44], [], "position: absolute");
|
|
56
|
+
const capsule = el([318, 61, 56, 44], [face, reference]);
|
|
57
|
+
const row = el([16, 61, 358, 44], [el([16, 61, 200, 44]), capsule]);
|
|
58
|
+
document.body.appendChild(row);
|
|
59
|
+
expect(paintedTriggerBox(reference, box([324, 61, 44, 44]))).toEqual(box([318, 61, 56, 44]));
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("stops at a wrapper shared with other content", () => {
|
|
63
|
+
const reference = el([290, 0, 44, 44]);
|
|
64
|
+
const pill = el([214, 0, 160, 44], [el([220, 0, 44, 44]), reference]);
|
|
65
|
+
document.body.appendChild(pill);
|
|
66
|
+
expect(paintedTriggerBox(reference, box([290, 0, 44, 44]))).toEqual(box([290, 0, 44, 44]));
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("reads a DOMRect layout box, whose sides are prototype getters", () => {
|
|
70
|
+
const reference = el([324, 61, 44, 44]);
|
|
71
|
+
document.body.appendChild(el([16, 61, 358, 44], [el([16, 61, 200, 44]), reference]));
|
|
72
|
+
class Getters {
|
|
73
|
+
get left() {
|
|
74
|
+
return 324;
|
|
75
|
+
}
|
|
76
|
+
get top() {
|
|
77
|
+
return 61;
|
|
78
|
+
}
|
|
79
|
+
get right() {
|
|
80
|
+
return 368;
|
|
81
|
+
}
|
|
82
|
+
get bottom() {
|
|
83
|
+
return 105;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const layout = new Getters();
|
|
87
|
+
expect(paintedTriggerBox(reference, layout)).toEqual(box([324, 61, 44, 44]));
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it(`never reaches more than ${PAINTED_BOX_REACH}px past the trigger`, () => {
|
|
91
|
+
const reference = el([324, 61, 44, 44]);
|
|
92
|
+
document.body.appendChild(el([0, 61, 390, 44], [reference]));
|
|
93
|
+
expect(paintedTriggerBox(reference, box([324, 61, 44, 44]))).toEqual(box([324, 61, 44, 44]));
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
export type Box = { left: number; top: number; right: number; bottom: number };
|
|
2
|
+
|
|
3
|
+
/** How far a trigger's plate or press box may reach past its layout box. */
|
|
4
|
+
export const PAINTED_BOX_REACH = 12;
|
|
5
|
+
|
|
6
|
+
function inFlowChildren(element: Element): Element[] {
|
|
7
|
+
return Array.from(element.children).filter((child) => {
|
|
8
|
+
const style = getComputedStyle(child);
|
|
9
|
+
return style.display !== "none" && style.position !== "absolute" && style.position !== "fixed";
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The box an icon trigger is seen in, which a panel covering it has to cover
|
|
15
|
+
* (LC-112, FZ-8480). A bare glyph's press box can hang past its layout slot
|
|
16
|
+
* (the trailing press slop), and a lone button's plate can be a wrapper that
|
|
17
|
+
* holds nothing else (an iOS glass capsule). Both count, up to
|
|
18
|
+
* {@link PAINTED_BOX_REACH} on any side; a wrapper shared with other content
|
|
19
|
+
* ends the walk.
|
|
20
|
+
*/
|
|
21
|
+
export function paintedTriggerBox(reference: Element, layout: Box): Box {
|
|
22
|
+
// A DOMRect's sides are prototype getters, so spreading it copies nothing.
|
|
23
|
+
const box = { left: layout.left, top: layout.top, right: layout.right, bottom: layout.bottom };
|
|
24
|
+
const take = (rect: DOMRect) => {
|
|
25
|
+
if (rect.width <= 0 || rect.height <= 0) return true;
|
|
26
|
+
const next = {
|
|
27
|
+
left: Math.min(box.left, rect.left),
|
|
28
|
+
top: Math.min(box.top, rect.top),
|
|
29
|
+
right: Math.max(box.right, rect.right),
|
|
30
|
+
bottom: Math.max(box.bottom, rect.bottom),
|
|
31
|
+
};
|
|
32
|
+
if (
|
|
33
|
+
layout.left - next.left > PAINTED_BOX_REACH ||
|
|
34
|
+
layout.top - next.top > PAINTED_BOX_REACH ||
|
|
35
|
+
next.right - layout.right > PAINTED_BOX_REACH ||
|
|
36
|
+
next.bottom - layout.bottom > PAINTED_BOX_REACH
|
|
37
|
+
) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
Object.assign(box, next);
|
|
41
|
+
return true;
|
|
42
|
+
};
|
|
43
|
+
let inner: Element | null = reference;
|
|
44
|
+
while (inner) {
|
|
45
|
+
const children = inFlowChildren(inner);
|
|
46
|
+
if (children.length !== 1) break;
|
|
47
|
+
inner = children[0]!;
|
|
48
|
+
if (!take(inner.getBoundingClientRect())) break;
|
|
49
|
+
}
|
|
50
|
+
let outer: Element | null = reference.parentElement;
|
|
51
|
+
let child: Element = reference;
|
|
52
|
+
while (outer && outer.tagName !== "BODY") {
|
|
53
|
+
const children = inFlowChildren(outer);
|
|
54
|
+
if (children.length !== 1 || children[0] !== child) break;
|
|
55
|
+
if (!take(outer.getBoundingClientRect())) break;
|
|
56
|
+
child = outer;
|
|
57
|
+
outer = outer.parentElement;
|
|
58
|
+
}
|
|
59
|
+
return box;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* A plate several buttons share (an iOS glass capsule around a bar's group).
|
|
64
|
+
* `data-fp-cover-surface` marks one explicitly.
|
|
65
|
+
*/
|
|
66
|
+
export const GROUP_SURFACE_SELECTOR = "[data-fp-cover-surface], [data-mp-glass-capsule]";
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The shared plate an icon trigger sits on, when it sits in a group (LC-112,
|
|
70
|
+
* FZ-8480 r37): the trigger's surface is the plate under it, so a panel
|
|
71
|
+
* opening from a grouped button covers the whole group.
|
|
72
|
+
*/
|
|
73
|
+
export function groupSurfaceOf(reference: Element): HTMLElement | null {
|
|
74
|
+
return reference.parentElement?.closest<HTMLElement>(GROUP_SURFACE_SELECTOR) ?? null;
|
|
75
|
+
}
|
|
@@ -161,6 +161,7 @@ describe("placeOverTrigger (FZ-7830, LC-112 where no sheet may take it)", () =>
|
|
|
161
161
|
maxHeight: 600,
|
|
162
162
|
shifted: false,
|
|
163
163
|
end: false,
|
|
164
|
+
up: false,
|
|
164
165
|
});
|
|
165
166
|
});
|
|
166
167
|
|
|
@@ -276,6 +277,51 @@ describe("placeOverTrigger (FZ-7830, LC-112 where no sheet may take it)", () =>
|
|
|
276
277
|
});
|
|
277
278
|
});
|
|
278
279
|
|
|
280
|
+
describe("placeOverTrigger pinned: an echoed icon trigger stays in its spot (LC-112, FZ-8480)", () => {
|
|
281
|
+
const phone: PanelBounds = { top: 57, left: 10, right: 380, bottom: 800 };
|
|
282
|
+
|
|
283
|
+
it("opens down from the button's top edge where the menu fits", () => {
|
|
284
|
+
const button = { x: 326, y: 60, width: 44, height: 44 };
|
|
285
|
+
const placed = placeOverTrigger({
|
|
286
|
+
trigger: button,
|
|
287
|
+
width: 250,
|
|
288
|
+
height: 300,
|
|
289
|
+
bounds: phone,
|
|
290
|
+
align: "auto",
|
|
291
|
+
pinned: true,
|
|
292
|
+
});
|
|
293
|
+
expect(placed).toMatchObject({ top: 60, left: 120, end: true, up: false, maxHeight: 740 });
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it("opens up from the button's bottom edge near the bottom, never sliding off the button", () => {
|
|
297
|
+
const button = { x: 20, y: 740, width: 44, height: 44 };
|
|
298
|
+
const placed = placeOverTrigger({
|
|
299
|
+
trigger: button,
|
|
300
|
+
width: 250,
|
|
301
|
+
height: 300,
|
|
302
|
+
bounds: phone,
|
|
303
|
+
align: "auto",
|
|
304
|
+
pinned: true,
|
|
305
|
+
});
|
|
306
|
+
expect(placed.up).toBe(true);
|
|
307
|
+
expect(placed.top + 300).toBe(button.y + button.height);
|
|
308
|
+
expect(placed.maxHeight).toBe(button.y + button.height - phone.top);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it("scrolls from the button rather than covering past the margin when neither side fits", () => {
|
|
312
|
+
const button = { x: 20, y: 400, width: 44, height: 44 };
|
|
313
|
+
const placed = placeOverTrigger({
|
|
314
|
+
trigger: button,
|
|
315
|
+
width: 250,
|
|
316
|
+
height: 2000,
|
|
317
|
+
bounds: phone,
|
|
318
|
+
align: "auto",
|
|
319
|
+
pinned: true,
|
|
320
|
+
});
|
|
321
|
+
expect(placed).toMatchObject({ top: 400, up: false, maxHeight: 400 });
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
|
|
279
325
|
describe("placeBesideTrigger (FZ-7830 round 3, LC-262: beside the trigger, never over it)", () => {
|
|
280
326
|
const phone: PanelBounds = { top: 57, left: 10, right: 380, bottom: 800 };
|
|
281
327
|
const field = { x: 16, y: 300, width: 358, height: 44 };
|
|
@@ -289,6 +335,7 @@ describe("placeBesideTrigger (FZ-7830 round 3, LC-262: beside the trigger, never
|
|
|
289
335
|
maxHeight: 180,
|
|
290
336
|
shifted: false,
|
|
291
337
|
end: false,
|
|
338
|
+
up: false,
|
|
292
339
|
});
|
|
293
340
|
});
|
|
294
341
|
|