@multiplatform.one/forms 7.80.0 → 7.81.1
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/AdaptivePopup/index.cjs +2 -1
- package/dist/cjs/AdaptivePopup/index.native.js +2 -1
- package/dist/cjs/AdaptivePopup/index.native.js.map +1 -1
- package/dist/cjs/Button/index.cjs +12 -5
- package/dist/cjs/Button/index.native.js +14 -6
- package/dist/cjs/Button/index.native.js.map +1 -1
- package/dist/cjs/FloatingPanel/PanelHeader.cjs +3 -8
- package/dist/cjs/FloatingPanel/PanelHeader.native.js +3 -8
- package/dist/cjs/FloatingPanel/PanelHeader.native.js.map +1 -1
- package/dist/cjs/FloatingPanel/coverRect.cjs +0 -8
- package/dist/cjs/FloatingPanel/coverRect.native.js +0 -8
- package/dist/cjs/FloatingPanel/coverRect.native.js.map +1 -1
- package/dist/cjs/FloatingPanel/echoContext.cjs +5 -27
- package/dist/cjs/FloatingPanel/echoContext.native.js +5 -28
- package/dist/cjs/FloatingPanel/echoContext.native.js.map +1 -1
- package/dist/cjs/FloatingPanel/index.cjs +49 -131
- package/dist/cjs/FloatingPanel/index.native.js +216 -135
- package/dist/cjs/FloatingPanel/index.native.js.map +1 -1
- package/dist/cjs/fields/Combobox/index.cjs +3 -10
- package/dist/cjs/fields/Combobox/index.native.js +3 -10
- package/dist/cjs/fields/Combobox/index.native.js.map +1 -1
- package/dist/cjs/fields/Select/InlineChoices.cjs +4 -8
- package/dist/cjs/fields/Select/InlineChoices.native.js +4 -8
- package/dist/cjs/fields/Select/InlineChoices.native.js.map +1 -1
- package/dist/cjs/fields/Select/index.cjs +65 -201
- package/dist/cjs/fields/Select/index.native.js +74 -209
- package/dist/cjs/fields/Select/index.native.js.map +1 -1
- package/dist/cjs/fields/nativeDateTime/OsDateTimeSheet.cjs +2 -2
- package/dist/cjs/fields/nativeDateTime/OsDateTimeSheet.native.js +2 -2
- package/dist/cjs/fields/nativeDateTime/OsDateTimeSheet.native.js.map +1 -1
- package/dist/cjs/index.cjs +4 -0
- package/dist/cjs/index.native.js +4 -0
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/cjs/shared/CheckGlyph.cjs +40 -0
- package/dist/cjs/shared/CheckGlyph.native.js +44 -0
- package/dist/cjs/shared/CheckGlyph.native.js.map +1 -0
- package/dist/cjs/shared/checkedOnField.cjs +104 -0
- package/dist/cjs/shared/checkedOnField.native.js +117 -0
- package/dist/cjs/shared/checkedOnField.native.js.map +1 -0
- package/dist/cjs/shared/floatingList.cjs +12 -5
- package/dist/cjs/shared/floatingList.native.js +15 -7
- package/dist/cjs/shared/floatingList.native.js.map +1 -1
- package/dist/esm/AdaptivePopup/index.mjs +3 -2
- package/dist/esm/AdaptivePopup/index.mjs.map +1 -1
- package/dist/esm/AdaptivePopup/index.native.js +3 -2
- package/dist/esm/AdaptivePopup/index.native.js.map +1 -1
- package/dist/esm/Button/index.mjs +12 -5
- package/dist/esm/Button/index.mjs.map +1 -1
- package/dist/esm/Button/index.native.js +14 -6
- package/dist/esm/Button/index.native.js.map +1 -1
- package/dist/esm/FloatingPanel/PanelHeader.mjs +3 -8
- package/dist/esm/FloatingPanel/PanelHeader.mjs.map +1 -1
- package/dist/esm/FloatingPanel/PanelHeader.native.js +3 -8
- package/dist/esm/FloatingPanel/PanelHeader.native.js.map +1 -1
- package/dist/esm/FloatingPanel/coverRect.mjs +1 -8
- package/dist/esm/FloatingPanel/coverRect.mjs.map +1 -1
- package/dist/esm/FloatingPanel/coverRect.native.js +1 -8
- package/dist/esm/FloatingPanel/coverRect.native.js.map +1 -1
- package/dist/esm/FloatingPanel/echoContext.mjs +4 -24
- package/dist/esm/FloatingPanel/echoContext.mjs.map +1 -1
- package/dist/esm/FloatingPanel/echoContext.native.js +4 -25
- package/dist/esm/FloatingPanel/echoContext.native.js.map +1 -1
- package/dist/esm/FloatingPanel/index.mjs +53 -135
- package/dist/esm/FloatingPanel/index.mjs.map +1 -1
- package/dist/esm/FloatingPanel/index.native.js +180 -99
- package/dist/esm/FloatingPanel/index.native.js.map +1 -1
- package/dist/esm/fields/Combobox/index.mjs +4 -11
- package/dist/esm/fields/Combobox/index.mjs.map +1 -1
- package/dist/esm/fields/Combobox/index.native.js +4 -11
- package/dist/esm/fields/Combobox/index.native.js.map +1 -1
- package/dist/esm/fields/Select/InlineChoices.mjs +2 -6
- package/dist/esm/fields/Select/InlineChoices.mjs.map +1 -1
- package/dist/esm/fields/Select/InlineChoices.native.js +2 -6
- package/dist/esm/fields/Select/InlineChoices.native.js.map +1 -1
- package/dist/esm/fields/Select/index.mjs +68 -204
- package/dist/esm/fields/Select/index.mjs.map +1 -1
- package/dist/esm/fields/Select/index.native.js +77 -212
- package/dist/esm/fields/Select/index.native.js.map +1 -1
- package/dist/esm/fields/nativeDateTime/OsDateTimeSheet.mjs +3 -3
- package/dist/esm/fields/nativeDateTime/OsDateTimeSheet.mjs.map +1 -1
- package/dist/esm/fields/nativeDateTime/OsDateTimeSheet.native.js +3 -3
- package/dist/esm/fields/nativeDateTime/OsDateTimeSheet.native.js.map +1 -1
- package/dist/esm/index.mjs +3 -1
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/index.native.js +3 -1
- package/dist/esm/index.native.js.map +1 -1
- package/dist/esm/shared/CheckGlyph.mjs +15 -0
- package/dist/esm/shared/CheckGlyph.mjs.map +1 -0
- package/dist/esm/shared/CheckGlyph.native.js +16 -0
- package/dist/esm/shared/CheckGlyph.native.js.map +1 -0
- package/dist/esm/shared/checkedOnField.mjs +76 -0
- package/dist/esm/shared/checkedOnField.mjs.map +1 -0
- package/dist/esm/shared/checkedOnField.native.js +86 -0
- package/dist/esm/shared/checkedOnField.native.js.map +1 -0
- package/dist/esm/shared/floatingList.mjs +9 -5
- package/dist/esm/shared/floatingList.mjs.map +1 -1
- package/dist/esm/shared/floatingList.native.js +12 -7
- package/dist/esm/shared/floatingList.native.js.map +1 -1
- package/dist/jsx/AdaptivePopup/index.mjs +3 -2
- package/dist/jsx/AdaptivePopup/index.mjs.map +1 -1
- package/dist/jsx/AdaptivePopup/index.native.js +2 -1
- package/dist/jsx/AdaptivePopup/index.native.js.map +1 -1
- package/dist/jsx/Button/index.mjs +12 -5
- package/dist/jsx/Button/index.mjs.map +1 -1
- package/dist/jsx/Button/index.native.js +14 -6
- package/dist/jsx/Button/index.native.js.map +1 -1
- package/dist/jsx/FloatingPanel/PanelHeader.mjs +3 -8
- package/dist/jsx/FloatingPanel/PanelHeader.mjs.map +1 -1
- package/dist/jsx/FloatingPanel/PanelHeader.native.js +3 -8
- package/dist/jsx/FloatingPanel/PanelHeader.native.js.map +1 -1
- package/dist/jsx/FloatingPanel/coverRect.mjs +1 -8
- package/dist/jsx/FloatingPanel/coverRect.mjs.map +1 -1
- package/dist/jsx/FloatingPanel/coverRect.native.js +0 -8
- package/dist/jsx/FloatingPanel/coverRect.native.js.map +1 -1
- package/dist/jsx/FloatingPanel/echoContext.mjs +4 -24
- package/dist/jsx/FloatingPanel/echoContext.mjs.map +1 -1
- package/dist/jsx/FloatingPanel/echoContext.native.js +5 -28
- package/dist/jsx/FloatingPanel/echoContext.native.js.map +1 -1
- package/dist/jsx/FloatingPanel/index.mjs +53 -135
- package/dist/jsx/FloatingPanel/index.mjs.map +1 -1
- package/dist/jsx/FloatingPanel/index.native.js +216 -135
- package/dist/jsx/FloatingPanel/index.native.js.map +1 -1
- package/dist/jsx/fields/Combobox/index.mjs +4 -11
- package/dist/jsx/fields/Combobox/index.mjs.map +1 -1
- package/dist/jsx/fields/Combobox/index.native.js +3 -10
- package/dist/jsx/fields/Combobox/index.native.js.map +1 -1
- package/dist/jsx/fields/Select/InlineChoices.mjs +2 -6
- package/dist/jsx/fields/Select/InlineChoices.mjs.map +1 -1
- package/dist/jsx/fields/Select/InlineChoices.native.js +4 -8
- package/dist/jsx/fields/Select/InlineChoices.native.js.map +1 -1
- package/dist/jsx/fields/Select/index.mjs +68 -204
- package/dist/jsx/fields/Select/index.mjs.map +1 -1
- package/dist/jsx/fields/Select/index.native.js +74 -209
- package/dist/jsx/fields/Select/index.native.js.map +1 -1
- package/dist/jsx/fields/nativeDateTime/OsDateTimeSheet.mjs +3 -3
- package/dist/jsx/fields/nativeDateTime/OsDateTimeSheet.mjs.map +1 -1
- package/dist/jsx/fields/nativeDateTime/OsDateTimeSheet.native.js +2 -2
- package/dist/jsx/fields/nativeDateTime/OsDateTimeSheet.native.js.map +1 -1
- package/dist/jsx/index.js +3 -1
- package/dist/jsx/index.js.map +1 -1
- package/dist/jsx/index.mjs +3 -1
- package/dist/jsx/index.mjs.map +1 -1
- package/dist/jsx/index.native.js +4 -0
- package/dist/jsx/index.native.js.map +1 -1
- package/dist/jsx/shared/CheckGlyph.mjs +15 -0
- package/dist/jsx/shared/CheckGlyph.mjs.map +1 -0
- package/dist/jsx/shared/CheckGlyph.native.js +44 -0
- package/dist/jsx/shared/CheckGlyph.native.js.map +1 -0
- package/dist/jsx/shared/checkedOnField.mjs +76 -0
- package/dist/jsx/shared/checkedOnField.mjs.map +1 -0
- package/dist/jsx/shared/checkedOnField.native.js +117 -0
- package/dist/jsx/shared/checkedOnField.native.js.map +1 -0
- package/dist/jsx/shared/floatingList.mjs +9 -5
- package/dist/jsx/shared/floatingList.mjs.map +1 -1
- package/dist/jsx/shared/floatingList.native.js +15 -7
- package/dist/jsx/shared/floatingList.native.js.map +1 -1
- package/package.json +8 -8
- package/src/AdaptivePopup/index.tsx +6 -3
- package/src/Button/Button.iconButton.spec.tsx +39 -0
- package/src/Button/index.tsx +20 -8
- package/src/FloatingPanel/FloatingPanel.iconCover.spec.tsx +279 -0
- package/src/FloatingPanel/FloatingPanel.menuFace.native.spec.tsx +78 -69
- package/src/FloatingPanel/FloatingPanel.seamlessCover.spec.tsx +12 -0
- package/src/FloatingPanel/PanelHeader.tsx +4 -12
- package/src/FloatingPanel/coverRect.ts +2 -15
- package/src/FloatingPanel/echoContext.ts +6 -40
- package/src/FloatingPanel/index.native.tsx +209 -144
- package/src/FloatingPanel/index.tsx +71 -187
- package/src/FloatingPanel/pointAnchor.spec.ts +1 -1
- package/src/FloatingPanel/pointAnchor.ts +4 -4
- package/src/FloatingPanel/sheetGrabber.spec.tsx +53 -0
- package/src/fields/Combobox/index.tsx +4 -9
- package/src/fields/Select/InlineChoices.tsx +2 -2
- package/src/fields/Select/Select.containingBlock.spec.tsx +131 -130
- package/src/fields/Select/Select.popUp.native.spec.tsx +276 -0
- package/src/fields/Select/Select.stories.tsx +26 -0
- package/src/fields/Select/index.tsx +84 -255
- package/src/fields/nativeDateTime/OsDateTimeSheet.tsx +3 -3
- package/src/index.ts +2 -1
- package/src/shared/CheckGlyph.tsx +12 -0
- package/src/shared/checkedOnField.ts +116 -0
- package/src/shared/floatingList.spec.tsx +154 -1
- package/src/shared/floatingList.tsx +18 -9
- package/types/AdaptivePopup/index.d.ts.map +1 -1
- package/types/Button/index.d.ts.map +1 -1
- package/types/FloatingPanel/PanelHeader.d.ts +1 -6
- package/types/FloatingPanel/PanelHeader.d.ts.map +1 -1
- package/types/FloatingPanel/coverRect.d.ts +2 -11
- package/types/FloatingPanel/coverRect.d.ts.map +1 -1
- package/types/FloatingPanel/echoContext.d.ts +5 -22
- package/types/FloatingPanel/echoContext.d.ts.map +1 -1
- package/types/FloatingPanel/index.d.ts +22 -9
- package/types/FloatingPanel/index.d.ts.map +1 -1
- package/types/FloatingPanel/index.native.d.ts +8 -1
- package/types/FloatingPanel/index.native.d.ts.map +1 -1
- package/types/FloatingPanel/pointAnchor.d.ts +4 -4
- package/types/fields/Combobox/index.d.ts.map +1 -1
- package/types/fields/Select/index.d.ts.map +1 -1
- package/types/index.d.ts +2 -1
- package/types/index.d.ts.map +1 -1
- package/types/shared/CheckGlyph.d.ts +10 -0
- package/types/shared/CheckGlyph.d.ts.map +1 -0
- package/types/shared/checkedOnField.d.ts +54 -0
- package/types/shared/checkedOnField.d.ts.map +1 -0
- package/types/shared/floatingList.d.ts +5 -2
- package/types/shared/floatingList.d.ts.map +1 -1
- package/dist/cjs/FloatingPanel/iconEcho.cjs +0 -106
- package/dist/cjs/FloatingPanel/iconEcho.native.js +0 -116
- package/dist/cjs/FloatingPanel/iconEcho.native.js.map +0 -1
- package/dist/esm/FloatingPanel/iconEcho.mjs +0 -79
- package/dist/esm/FloatingPanel/iconEcho.mjs.map +0 -1
- package/dist/esm/FloatingPanel/iconEcho.native.js +0 -86
- package/dist/esm/FloatingPanel/iconEcho.native.js.map +0 -1
- package/dist/jsx/FloatingPanel/iconEcho.mjs +0 -79
- package/dist/jsx/FloatingPanel/iconEcho.mjs.map +0 -1
- package/dist/jsx/FloatingPanel/iconEcho.native.js +0 -116
- package/dist/jsx/FloatingPanel/iconEcho.native.js.map +0 -1
- package/src/FloatingPanel/FloatingPanel.iconEcho.spec.tsx +0 -357
- package/src/FloatingPanel/iconEcho.tsx +0 -124
- package/types/FloatingPanel/iconEcho.d.ts +0 -29
- package/types/FloatingPanel/iconEcho.d.ts.map +0 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { IconSlot, useTouchSurface } from "@multiplatform.one/theme";
|
|
2
|
+
import { CheckGlyph } from "../../shared/CheckGlyph";
|
|
2
3
|
import type { KeyboardEvent, MouseEvent, ReactNode, RefObject, UIEvent } from "react";
|
|
3
4
|
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, memo } from "react";
|
|
4
|
-
import { CaretDownIcon, CaretRightIcon
|
|
5
|
+
import { CaretDownIcon, CaretRightIcon } from "@phosphor-icons/react";
|
|
5
6
|
import {
|
|
6
7
|
useClick,
|
|
7
8
|
useDismiss,
|
|
@@ -50,11 +51,14 @@ import {
|
|
|
50
51
|
useFormField,
|
|
51
52
|
useResolvedValidators,
|
|
52
53
|
} from "../../shared/utils";
|
|
53
|
-
import {
|
|
54
|
+
import {
|
|
55
|
+
growOnScroll,
|
|
56
|
+
placeCheckedOnField,
|
|
57
|
+
ScrollArrow,
|
|
58
|
+
scrollArrowBand,
|
|
59
|
+
} from "../../shared/floatingList";
|
|
54
60
|
import {
|
|
55
61
|
FloatingPanel,
|
|
56
|
-
TriggerEchoRow,
|
|
57
|
-
placeEchoOverTrigger,
|
|
58
62
|
useFloatingPanel,
|
|
59
63
|
panelViewportPadding,
|
|
60
64
|
useViewportGtSm,
|
|
@@ -349,8 +353,8 @@ function selectItemRingProps(isActive: boolean, kbNav: boolean) {
|
|
|
349
353
|
* row. On native the shim can only resolve a token the theme in scope actually
|
|
350
354
|
* carries, and the component sub-themes carry no accent solid at all.
|
|
351
355
|
* Resolve it here, where the value becomes a paint — the same shape as
|
|
352
|
-
* `mutedColor` in Combobox. Keyed off `useMenuRowCheckInk` (
|
|
353
|
-
*
|
|
356
|
+
* `mutedColor` in Combobox. Keyed off `useMenuRowCheckInk` (the accent, ink when hue-less), so the tick
|
|
357
|
+
* holds 4.5:1 on the chosen row (FZ-8355 r6, FZ-8557), degrading
|
|
354
358
|
* to `$color` ink rather than black when a narrow sub-theme lacks the key.
|
|
355
359
|
*/
|
|
356
360
|
function useSelectedMarkColor(): string {
|
|
@@ -506,7 +510,7 @@ const SelectItem = memo(function SelectItem({
|
|
|
506
510
|
>
|
|
507
511
|
{option.label}
|
|
508
512
|
</Text>
|
|
509
|
-
{isSelected && <
|
|
513
|
+
{isSelected && <CheckGlyph />}
|
|
510
514
|
</SelectRow>
|
|
511
515
|
);
|
|
512
516
|
});
|
|
@@ -624,20 +628,13 @@ function FloatingSelectCore({
|
|
|
624
628
|
}, [options, value, multiple, selectedOrder]);
|
|
625
629
|
|
|
626
630
|
const programmaticScrollRef = useRef(0);
|
|
627
|
-
const pinnedTopRef = useRef(0);
|
|
628
|
-
const pinnedHeightRef = useRef(0);
|
|
629
|
-
const grownRef = useRef(false);
|
|
630
|
-
const growUpOnlyRef = useRef(false);
|
|
631
|
-
const growDownOnlyRef = useRef(false);
|
|
632
631
|
const lastScrollTopRef = useRef(0);
|
|
633
|
-
//
|
|
634
|
-
//
|
|
635
|
-
|
|
636
|
-
const
|
|
637
|
-
|
|
638
|
-
const
|
|
639
|
-
const coverTrimRef = useRef(0);
|
|
640
|
-
const echoed = !beside && !coverScroll;
|
|
632
|
+
// The panel's box with the checked row on the field (FZ-8557), in the
|
|
633
|
+
// padding box's viewport coordinates.
|
|
634
|
+
const onFieldRef = useRef<{ top: number; height: number; scrollTop: number } | null>(null);
|
|
635
|
+
const opensUpRef = useRef(false);
|
|
636
|
+
// One band for the layout and the arrows that draw it (FZ-8557).
|
|
637
|
+
const arrowBand = scrollArrowBand(knobProps.menuRow.minHeight);
|
|
641
638
|
|
|
642
639
|
const repositionRef = useRef<(() => void) | null>(null);
|
|
643
640
|
|
|
@@ -646,21 +643,13 @@ function FloatingSelectCore({
|
|
|
646
643
|
onOpenChange: setOpen,
|
|
647
644
|
disabled,
|
|
648
645
|
repositionRef,
|
|
649
|
-
wheelGrowRef: selectedIndex >= 0 ? grownRef : undefined,
|
|
650
646
|
onBeforeOpen: () => {
|
|
651
647
|
computeSortedOptions();
|
|
652
648
|
overlayStyle.current = null;
|
|
653
|
-
pinnedTopRef.current = 0;
|
|
654
|
-
pinnedHeightRef.current = 0;
|
|
655
|
-
grownRef.current = false;
|
|
656
|
-
growUpOnlyRef.current = false;
|
|
657
|
-
growDownOnlyRef.current = false;
|
|
658
649
|
lastScrollTopRef.current = 0;
|
|
659
650
|
programmaticScrollRef.current = 0;
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
coverTrimRef.current = 0;
|
|
663
|
-
setCoverScroll(false);
|
|
651
|
+
onFieldRef.current = null;
|
|
652
|
+
opensUpRef.current = false;
|
|
664
653
|
},
|
|
665
654
|
panelWidth,
|
|
666
655
|
});
|
|
@@ -728,11 +717,6 @@ function FloatingSelectCore({
|
|
|
728
717
|
const isInitial = panel.initializingRef.current;
|
|
729
718
|
if (isInitial) dropdownElement.style.transition = "none";
|
|
730
719
|
|
|
731
|
-
if (pinnedTopRef.current === 0 && pinnedHeightRef.current === 0) {
|
|
732
|
-
programmaticScrollRef.current++;
|
|
733
|
-
dropdownElement.scrollTop = 0;
|
|
734
|
-
}
|
|
735
|
-
|
|
736
720
|
const triggerBounds = trigger.getBoundingClientRect();
|
|
737
721
|
const viewportHeight = window.innerHeight;
|
|
738
722
|
// The math runs in viewport coordinates; a panel hosted in a transformed
|
|
@@ -744,153 +728,46 @@ function FloatingSelectCore({
|
|
|
744
728
|
const borderBottomWidth = parseFloat(computedStyle.borderBottomWidth) || 0;
|
|
745
729
|
const totalBorderWidth = borderTopWidth + borderBottomWidth;
|
|
746
730
|
const scrollContentHeight = dropdownElement.scrollHeight;
|
|
747
|
-
const totalContentHeight = scrollContentHeight + totalBorderWidth;
|
|
748
|
-
|
|
749
|
-
// LC-112 seamless cover (FZ-8438): the panel's top edge is the trigger's,
|
|
750
|
-
// the trigger echoed as its first row, the list below it. A list that
|
|
751
|
-
// doesn't fit below its field covers the field with no echo row and
|
|
752
|
-
// grows toward the side with more room: down from the field's top edge,
|
|
753
|
-
// or up from its bottom edge. Past the safe screen edge it scrolls, and
|
|
754
|
-
// scrolling grows it to fill the screen (FZ-8463).
|
|
755
|
-
const firstPlacement = pinnedTopRef.current === 0 && pinnedHeightRef.current === 0;
|
|
756
|
-
const boundsTop = panelViewportPadding;
|
|
757
|
-
const boundsBottom = viewportHeight - panelViewportPadding;
|
|
758
|
-
const echoRow = dropdownElement.querySelector<HTMLElement>("[data-fp-trigger-row]");
|
|
759
|
-
const echoHeight = echoRow ? triggerBounds.height : 0;
|
|
760
|
-
const listHeight = totalContentHeight - echoHeight;
|
|
761
|
-
const roomBelow = boundsBottom - triggerBounds.top;
|
|
762
|
-
const roomAbove = triggerBounds.bottom - boundsTop;
|
|
763
|
-
if (firstPlacement && totalContentHeight > roomBelow) {
|
|
764
|
-
coverScrollRef.current = true;
|
|
765
|
-
growsUpRef.current = roomAbove > roomBelow;
|
|
766
|
-
setCoverScroll(true);
|
|
767
|
-
}
|
|
768
731
|
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
// far. Opening down, short of that it stops a whole row short, so the
|
|
800
|
-
// top row isn't cut in half. Two pixels of rounding are not a row.
|
|
801
|
-
const maxScrollTop = dropdownElement.scrollHeight - dropdownElement.clientHeight;
|
|
802
|
-
let scrollTop: number;
|
|
803
|
-
if (growsUpRef.current) {
|
|
804
|
-
scrollTop = selectedItem.offsetTop + rowHeight - (triggerBounds.bottom - top);
|
|
805
|
-
} else {
|
|
806
|
-
const short = Math.max(0, selectedItem.offsetTop - maxScrollTop - 2);
|
|
807
|
-
scrollTop = selectedItem.offsetTop - Math.ceil(short / rowHeight) * rowHeight;
|
|
808
|
-
}
|
|
809
|
-
programmaticScrollRef.current++;
|
|
810
|
-
dropdownElement.scrollTop = Math.min(maxScrollTop, Math.max(0, scrollTop));
|
|
811
|
-
}
|
|
812
|
-
if (isInitial && !grown) {
|
|
813
|
-
// The far edge ends on a whole row: growing up, the row on the field
|
|
814
|
-
// isn't cut in half; growing down, no sliver sits at the screen edge.
|
|
815
|
-
const firstRow = listItemsRef.current[0];
|
|
816
|
-
const visibleBottom = dropdownElement.scrollTop + dropdownElement.clientHeight;
|
|
817
|
-
if (firstRow && visibleBottom < dropdownElement.scrollHeight - 2) {
|
|
818
|
-
const cut = (visibleBottom - firstRow.offsetTop) % rowHeight;
|
|
819
|
-
if (cut > 3 && cut < rowHeight - 3) {
|
|
820
|
-
coverTrimRef.current = cut;
|
|
821
|
-
const trimmedHeight = height - cut;
|
|
822
|
-
const trimmedTop = growsUpRef.current ? top + cut : top;
|
|
823
|
-
dropdownElement.style.top = `${trimmedTop - originTop}px`;
|
|
824
|
-
dropdownElement.style.maxHeight = `${trimmedHeight}px`;
|
|
825
|
-
overlayStyle.current = { top: trimmedTop - originTop, maxHeight: trimmedHeight };
|
|
826
|
-
pinnedTopRef.current = trimmedTop;
|
|
827
|
-
pinnedHeightRef.current = trimmedHeight;
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
panel.initializingRef.current = false;
|
|
832
|
-
void dropdownElement.offsetHeight;
|
|
833
|
-
if (isInitial) dropdownElement.style.transition = panel.panelTransition;
|
|
834
|
-
requestAnimationFrame(() => {
|
|
835
|
-
if (dropdownElement) panel.updateArrows(dropdownElement);
|
|
732
|
+
// LC-112 (FZ-8557 r3): a covering Select opens the way a macOS pop-up
|
|
733
|
+
// does. Its checked row sits on the field (the first row when nothing is
|
|
734
|
+
// checked), so the value never shows twice. A list that fits the safe
|
|
735
|
+
// viewport shows whole, slid the least distance to stay on screen; a
|
|
736
|
+
// taller one clips at the safe edges and scrolls under its arrow bands.
|
|
737
|
+
// Scrolling grows a panel that stops short of a safe edge toward it,
|
|
738
|
+
// the rows staying under the pointer.
|
|
739
|
+
const boundsTop = panelViewportPadding + borderTopWidth;
|
|
740
|
+
const boundsBottom = viewportHeight - panelViewportPadding - borderBottomWidth;
|
|
741
|
+
if (!onFieldRef.current) {
|
|
742
|
+
const fit = placeCheckedOnField({
|
|
743
|
+
fieldTop: triggerBounds.top,
|
|
744
|
+
fieldBottom: triggerBounds.bottom,
|
|
745
|
+
rowTop: selectedItem.offsetTop,
|
|
746
|
+
rowHeight: selectedItem.offsetHeight || 1,
|
|
747
|
+
contentHeight: scrollContentHeight,
|
|
748
|
+
boundsTop,
|
|
749
|
+
boundsBottom,
|
|
750
|
+
band: arrowBand,
|
|
751
|
+
});
|
|
752
|
+
onFieldRef.current = fit;
|
|
753
|
+
opensUpRef.current =
|
|
754
|
+
fit.top + fit.height / 2 < (triggerBounds.top + triggerBounds.bottom) / 2;
|
|
755
|
+
} else if (!isInitial) {
|
|
756
|
+
onFieldRef.current = growOnScroll({
|
|
757
|
+
...onFieldRef.current,
|
|
758
|
+
scrollTop: dropdownElement.scrollTop,
|
|
759
|
+
contentHeight: scrollContentHeight,
|
|
760
|
+
boundsTop,
|
|
761
|
+
boundsBottom,
|
|
836
762
|
});
|
|
837
|
-
return;
|
|
838
|
-
}
|
|
839
|
-
const idealTop = triggerBounds.top;
|
|
840
|
-
const idealBottom = idealTop + totalContentHeight;
|
|
841
|
-
const pinnedTop = Math.max(panelViewportPadding, idealTop);
|
|
842
|
-
const pinnedBottom = Math.min(viewportHeight - panelViewportPadding, idealBottom);
|
|
843
|
-
const pinnedHeight = Math.max(120, pinnedBottom - pinnedTop);
|
|
844
|
-
|
|
845
|
-
if (firstPlacement) {
|
|
846
|
-
pinnedTopRef.current = pinnedTop;
|
|
847
|
-
pinnedHeightRef.current = pinnedHeight;
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
let top = pinnedTopRef.current;
|
|
851
|
-
let height = pinnedHeightRef.current;
|
|
852
|
-
|
|
853
|
-
if (grownRef.current) {
|
|
854
|
-
const canGrowDown = Math.max(0, viewportHeight - panelViewportPadding - (top + height));
|
|
855
|
-
height += canGrowDown;
|
|
856
|
-
const canGrowUp = Math.max(0, top - panelViewportPadding);
|
|
857
|
-
top -= canGrowUp;
|
|
858
|
-
height += canGrowUp;
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
if (growUpOnlyRef.current) {
|
|
862
|
-
const canGrowUp = Math.max(0, top - panelViewportPadding);
|
|
863
|
-
top -= canGrowUp;
|
|
864
|
-
height += canGrowUp;
|
|
865
|
-
}
|
|
866
|
-
if (growDownOnlyRef.current) {
|
|
867
|
-
const canGrowDown = Math.max(0, viewportHeight - panelViewportPadding - (top + height));
|
|
868
|
-
height += canGrowDown;
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
height = Math.min(height, viewportHeight - top - panelViewportPadding);
|
|
872
|
-
height = Math.min(height, totalContentHeight);
|
|
873
|
-
height = Math.max(120, height);
|
|
874
|
-
|
|
875
|
-
// When grown but content is shorter than viewport: anchor dropdown to bottom (gap above), dropup to top (gap below)
|
|
876
|
-
const availableViewport = viewportHeight - 2 * panelViewportPadding;
|
|
877
|
-
if (
|
|
878
|
-
(grownRef.current || growUpOnlyRef.current || growDownOnlyRef.current) &&
|
|
879
|
-
height < availableViewport
|
|
880
|
-
) {
|
|
881
|
-
if (panel.placement.startsWith("top")) {
|
|
882
|
-
top = panelViewportPadding;
|
|
883
|
-
} else {
|
|
884
|
-
top = viewportHeight - panelViewportPadding - height;
|
|
885
|
-
}
|
|
886
763
|
}
|
|
887
|
-
|
|
888
|
-
const
|
|
889
|
-
|
|
764
|
+
const placed = onFieldRef.current;
|
|
765
|
+
const top = placed.top - borderTopWidth;
|
|
766
|
+
const height = placed.height + totalBorderWidth;
|
|
890
767
|
if (
|
|
891
|
-
!
|
|
892
|
-
|
|
893
|
-
|
|
768
|
+
!overlayStyle.current ||
|
|
769
|
+
overlayStyle.current.top !== top - originTop ||
|
|
770
|
+
overlayStyle.current.maxHeight !== height
|
|
894
771
|
) {
|
|
895
772
|
dropdownElement.style.top = `${top - originTop}px`;
|
|
896
773
|
dropdownElement.style.maxHeight = `${height}px`;
|
|
@@ -898,63 +775,24 @@ function FloatingSelectCore({
|
|
|
898
775
|
overlayStyle.current = { top: top - originTop, maxHeight: height };
|
|
899
776
|
void dropdownElement.offsetHeight;
|
|
900
777
|
}
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
selectedIndex >= 0 &&
|
|
905
|
-
!grownRef.current &&
|
|
906
|
-
!growUpOnlyRef.current &&
|
|
907
|
-
!growDownOnlyRef.current
|
|
908
|
-
) {
|
|
909
|
-
// The selected row starts in view, just under the sticky echo row.
|
|
910
|
-
const rowBottom = selectedItem.offsetTop + selectedItem.offsetHeight;
|
|
911
|
-
if (rowBottom > dropdownElement.clientHeight) {
|
|
912
|
-
const maxScrollTop = dropdownElement.scrollHeight - dropdownElement.clientHeight;
|
|
913
|
-
programmaticScrollRef.current++;
|
|
914
|
-
dropdownElement.scrollTop = Math.max(
|
|
915
|
-
0,
|
|
916
|
-
Math.min(selectedItem.offsetTop - echoHeight, maxScrollTop),
|
|
917
|
-
);
|
|
918
|
-
}
|
|
778
|
+
if (Math.abs(dropdownElement.scrollTop - placed.scrollTop) >= 0.5) {
|
|
779
|
+
programmaticScrollRef.current++;
|
|
780
|
+
dropdownElement.scrollTop = placed.scrollTop;
|
|
919
781
|
}
|
|
920
|
-
|
|
921
|
-
// A screen-wide or fixed-width list slides inside the margins; its echo
|
|
922
|
-
// stays on the field (LC-112 r3). The scale origin is the left edge, so
|
|
923
|
-
// the client rect's left is the layout left.
|
|
924
|
-
placeEchoOverTrigger(
|
|
925
|
-
dropdownElement,
|
|
926
|
-
{
|
|
927
|
-
triggerLeft: triggerBounds.left,
|
|
928
|
-
triggerRight: triggerBounds.right,
|
|
929
|
-
panelLeft: dropdownElement.getBoundingClientRect().left,
|
|
930
|
-
panelWidth: dropdownElement.offsetWidth,
|
|
931
|
-
rtl: getComputedStyle(trigger).direction === "rtl",
|
|
932
|
-
},
|
|
933
|
-
{ stretch: panelWidth !== undefined },
|
|
934
|
-
);
|
|
935
|
-
|
|
936
782
|
panel.initializingRef.current = false;
|
|
937
|
-
|
|
938
783
|
void dropdownElement.offsetHeight;
|
|
939
784
|
if (isInitial) dropdownElement.style.transition = panel.panelTransition;
|
|
940
785
|
requestAnimationFrame(() => {
|
|
941
|
-
if (dropdownElement)
|
|
942
|
-
panel.updateArrows(dropdownElement);
|
|
943
|
-
requestAnimationFrame(() => {
|
|
944
|
-
if (dropdownElement) panel.updateArrows(dropdownElement);
|
|
945
|
-
});
|
|
946
|
-
}
|
|
786
|
+
if (dropdownElement) panel.updateArrows(dropdownElement);
|
|
947
787
|
});
|
|
948
788
|
}, [
|
|
949
789
|
selectedIndex,
|
|
950
790
|
panel.refs,
|
|
951
791
|
panel.updateArrows,
|
|
952
|
-
panel.placement,
|
|
953
792
|
panel.panelTransition,
|
|
954
793
|
options.length,
|
|
955
794
|
beside,
|
|
956
|
-
|
|
957
|
-
panelWidth,
|
|
795
|
+
arrowBand,
|
|
958
796
|
]);
|
|
959
797
|
|
|
960
798
|
repositionRef.current = repositionOverlay;
|
|
@@ -1156,23 +994,10 @@ function FloatingSelectCore({
|
|
|
1156
994
|
return;
|
|
1157
995
|
}
|
|
1158
996
|
const arrowDir = panel.arrowScrollDirRef.current;
|
|
1159
|
-
if (arrowDir)
|
|
1160
|
-
panel.arrowScrollDirRef.current = null;
|
|
1161
|
-
lastScrollTopRef.current = el.scrollTop;
|
|
1162
|
-
if (arrowDir === "down") {
|
|
1163
|
-
growUpOnlyRef.current = true;
|
|
1164
|
-
} else {
|
|
1165
|
-
growDownOnlyRef.current = true;
|
|
1166
|
-
}
|
|
1167
|
-
repositionOverlay();
|
|
1168
|
-
return;
|
|
1169
|
-
}
|
|
997
|
+
if (arrowDir) panel.arrowScrollDirRef.current = null;
|
|
1170
998
|
const delta = el.scrollTop - lastScrollTopRef.current;
|
|
1171
999
|
lastScrollTopRef.current = el.scrollTop;
|
|
1172
|
-
if (delta !== 0)
|
|
1173
|
-
grownRef.current = true;
|
|
1174
|
-
repositionOverlay();
|
|
1175
|
-
}
|
|
1000
|
+
if (delta !== 0) repositionOverlay();
|
|
1176
1001
|
},
|
|
1177
1002
|
[panel.updateArrows, repositionOverlay],
|
|
1178
1003
|
);
|
|
@@ -1290,10 +1115,7 @@ function FloatingSelectCore({
|
|
|
1290
1115
|
opacity: panel.visible ? 1 : 0,
|
|
1291
1116
|
transform: panel.visible ? "scale(1)" : "scale(0.96)",
|
|
1292
1117
|
transition: panel.panelTransition,
|
|
1293
|
-
transformOrigin:
|
|
1294
|
-
(coverScroll && growsUpRef.current) || panel.placement.startsWith("top")
|
|
1295
|
-
? "bottom left"
|
|
1296
|
-
: "top left",
|
|
1118
|
+
transformOrigin: opensUpRef.current ? "bottom left" : "top left",
|
|
1297
1119
|
...dragProps.style,
|
|
1298
1120
|
},
|
|
1299
1121
|
onPointerDown: dragProps.onPointerDown,
|
|
@@ -1336,22 +1158,12 @@ function FloatingSelectCore({
|
|
|
1336
1158
|
paddingVertical={0}
|
|
1337
1159
|
paddingBottom={panel.coverCornerInset || 0}
|
|
1338
1160
|
>
|
|
1339
|
-
{echoed ? (
|
|
1340
|
-
<TriggerEchoRow
|
|
1341
|
-
sourceRef={panel.triggerRef}
|
|
1342
|
-
source={selectedOption?.label ?? placeholder}
|
|
1343
|
-
onPress={() => setOpen(false)}
|
|
1344
|
-
presentational
|
|
1345
|
-
/>
|
|
1346
|
-
) : null}
|
|
1347
1161
|
<ScrollArrow
|
|
1348
1162
|
direction="up"
|
|
1349
1163
|
scrollRef={panel.refs.floating}
|
|
1350
1164
|
visible={panel.arrowUp}
|
|
1351
1165
|
arrowScrollDirRef={panel.arrowScrollDirRef}
|
|
1352
|
-
|
|
1353
|
-
echoed ? (panel.triggerRef.current?.getBoundingClientRect().height ?? 0) : 0
|
|
1354
|
-
}
|
|
1166
|
+
band={arrowBand}
|
|
1355
1167
|
/>
|
|
1356
1168
|
{groupLabel && (
|
|
1357
1169
|
<GroupHeader color={textColor} paddingHorizontal={knobProps.panelPadding.padding}>
|
|
@@ -1402,6 +1214,7 @@ function FloatingSelectCore({
|
|
|
1402
1214
|
scrollRef={panel.refs.floating}
|
|
1403
1215
|
visible={panel.arrowDown}
|
|
1404
1216
|
arrowScrollDirRef={panel.arrowScrollDirRef}
|
|
1217
|
+
band={arrowBand}
|
|
1405
1218
|
/>
|
|
1406
1219
|
</YStack>
|
|
1407
1220
|
</FloatingFocusManager>
|
|
@@ -1434,6 +1247,7 @@ function NativeSelectCore({
|
|
|
1434
1247
|
const insideOverlay = useInsideOverlay();
|
|
1435
1248
|
const compactFace = pickerCompactFace(options.length);
|
|
1436
1249
|
const asMenu = resolveCompactFace(compactFace, insideOverlay) === "menu";
|
|
1250
|
+
const beside = asMenu && insideOverlay && opensBesideTrigger(options.length);
|
|
1437
1251
|
const hydrationTouch = useTouchSurface();
|
|
1438
1252
|
const describedBy = useFieldDescribedBy();
|
|
1439
1253
|
const fieldA11y = useFieldA11y();
|
|
@@ -1497,6 +1311,13 @@ function NativeSelectCore({
|
|
|
1497
1311
|
}
|
|
1498
1312
|
}, [open, selectedIndex]);
|
|
1499
1313
|
|
|
1314
|
+
// LC-112 (FZ-8655): the native pop-up puts this row on the field, measured
|
|
1315
|
+
// fresh each open.
|
|
1316
|
+
const [onFieldRow, setOnFieldRow] = useState<{ top: number; height: number } | null>(null);
|
|
1317
|
+
useEffect(() => {
|
|
1318
|
+
if (!open) setOnFieldRow(null);
|
|
1319
|
+
}, [open]);
|
|
1320
|
+
|
|
1500
1321
|
const handleSelect = useCallback(
|
|
1501
1322
|
(optionValue: string) => {
|
|
1502
1323
|
if (multiple) {
|
|
@@ -1629,7 +1450,7 @@ function NativeSelectCore({
|
|
|
1629
1450
|
>
|
|
1630
1451
|
{option.label}
|
|
1631
1452
|
</Text>
|
|
1632
|
-
{selected && <
|
|
1453
|
+
{selected && <CheckGlyph />}
|
|
1633
1454
|
</View>
|
|
1634
1455
|
);
|
|
1635
1456
|
if (isWeb) return row;
|
|
@@ -1641,6 +1462,12 @@ function NativeSelectCore({
|
|
|
1641
1462
|
accessibilityRole="button"
|
|
1642
1463
|
accessibilityLabel={option.label}
|
|
1643
1464
|
accessibilityState={{ selected }}
|
|
1465
|
+
onLayout={
|
|
1466
|
+
i === Math.max(selectedIndex, 0)
|
|
1467
|
+
? ({ nativeEvent: { layout } }) =>
|
|
1468
|
+
setOnFieldRow({ top: layout.y, height: layout.height })
|
|
1469
|
+
: undefined
|
|
1470
|
+
}
|
|
1644
1471
|
>
|
|
1645
1472
|
{row}
|
|
1646
1473
|
</RNPressable>
|
|
@@ -1742,9 +1569,11 @@ function NativeSelectCore({
|
|
|
1742
1569
|
// sheet at page level and opens as a menu inside another overlay (FZ-7830).
|
|
1743
1570
|
compactFace={compactFace}
|
|
1744
1571
|
panelWidth={panelWidth}
|
|
1745
|
-
{...(
|
|
1572
|
+
{...(beside
|
|
1746
1573
|
? { anchorMode: "attach" as const, triggerEcho: false }
|
|
1747
|
-
:
|
|
1574
|
+
: isWeb || displayOptions.length === 0
|
|
1575
|
+
? null
|
|
1576
|
+
: { popUp: { row: onFieldRow } })}
|
|
1748
1577
|
triggerA11y={{
|
|
1749
1578
|
label: ariaLabel ?? fieldA11y?.label,
|
|
1750
1579
|
value: triggerValueText,
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
} from "react-native";
|
|
22
22
|
import { platform } from "@multiplatform.one/platform";
|
|
23
23
|
import { SafeAreaInsetsContext, initialWindowMetrics } from "react-native-safe-area-context";
|
|
24
|
-
import { useResolvedKnobs, Text } from "@multiplatform.one/theme";
|
|
24
|
+
import { useResolvedKnobs, Text, overlayScrim } from "@multiplatform.one/theme";
|
|
25
25
|
import { View, XStack, YStack, useTheme, useThemeName } from "tamagui";
|
|
26
26
|
import { ArrowRightIcon } from "@phosphor-icons/react";
|
|
27
27
|
import { IconButton } from "../../Button";
|
|
@@ -159,7 +159,7 @@ export function OsDateTimeSheet({
|
|
|
159
159
|
right={0}
|
|
160
160
|
bottom={0}
|
|
161
161
|
left={0}
|
|
162
|
-
backgroundColor=
|
|
162
|
+
backgroundColor={overlayScrim}
|
|
163
163
|
/>
|
|
164
164
|
<YStack
|
|
165
165
|
testID="os-date-time-sheet-unavailable"
|
|
@@ -205,7 +205,7 @@ export function OsDateTimeSheet({
|
|
|
205
205
|
*/}
|
|
206
206
|
<RNView style={[styles.modalOverlay, { paddingBottom: keyboardInset }]}>
|
|
207
207
|
<Pressable style={StyleSheet.absoluteFill} onPress={onCancel}>
|
|
208
|
-
<View pointerEvents="none" flex={1} backgroundColor=
|
|
208
|
+
<View pointerEvents="none" flex={1} backgroundColor={overlayScrim} />
|
|
209
209
|
</Pressable>
|
|
210
210
|
<Pressable onPress={(e) => e.stopPropagation()}>
|
|
211
211
|
<YStack
|
package/src/index.ts
CHANGED
|
@@ -310,6 +310,8 @@ export { PanelHeader } from "./FloatingPanel/PanelHeader";
|
|
|
310
310
|
export { PanelPortal } from "./FloatingPanel/PanelPortal";
|
|
311
311
|
export type { PanelPortalProps } from "./FloatingPanel/PanelPortal";
|
|
312
312
|
export { containsAcrossPanels } from "./shared/panelPortal";
|
|
313
|
+
export { CheckGlyph } from "./shared/CheckGlyph";
|
|
314
|
+
export { useInPanelSheet } from "./FloatingPanel/echoContext";
|
|
313
315
|
export type {
|
|
314
316
|
FloatingPanelProps,
|
|
315
317
|
PanelWidthMode,
|
|
@@ -317,7 +319,6 @@ export type {
|
|
|
317
319
|
UseFloatingPanelReturn,
|
|
318
320
|
} from "./FloatingPanel";
|
|
319
321
|
export type { PanelWidth } from "./FloatingPanel/panelWidth";
|
|
320
|
-
export type { IconEcho } from "./FloatingPanel/iconEcho";
|
|
321
322
|
|
|
322
323
|
// ── Adaptive Popup (Dialog / Drawer on desktop, Sheet on mobile) ─
|
|
323
324
|
export { AdaptivePopup, pickAdaptivePopupFace } from "./AdaptivePopup";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { CheckIcon } from "@phosphor-icons/react";
|
|
2
|
+
import { menuRowCheckGlyph, type MenuRowGlyph, useMenuRowCheckInk } from "@multiplatform.one/theme";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The check on a checked menu or picker row (LC-262, FZ-8557): the theme's
|
|
6
|
+
* `menuRowCheckGlyph` recipe in the check ink. A row with an icon passes
|
|
7
|
+
* `menuRowIconGlyph`, so the check takes the icons' stroke (FZ-8557 r3). Components draws the same glyph through its own twin, and a spec keeps the two equal.
|
|
8
|
+
*/
|
|
9
|
+
export function CheckGlyph({ glyph = menuRowCheckGlyph }: { glyph?: MenuRowGlyph }) {
|
|
10
|
+
const ink = useMenuRowCheckInk();
|
|
11
|
+
return <CheckIcon size={glyph.size} weight={glyph.weight} color={ink} />;
|
|
12
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
export const scrollArrowThreshold = 8;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A scroll arrow's band: half a menu row, opaque in the panel's own fill.
|
|
5
|
+
* Rows scroll under it, and a row cut at a clipped edge hides under it
|
|
6
|
+
* (FZ-8557, LC-112).
|
|
7
|
+
*/
|
|
8
|
+
export function scrollArrowBand(rowHeight: number): number {
|
|
9
|
+
return Math.round(rowHeight / 2);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ArrowBandFit {
|
|
13
|
+
/** The padding box's top edge, in viewport pixels. */
|
|
14
|
+
top: number;
|
|
15
|
+
/** The padding box's visible height. */
|
|
16
|
+
height: number;
|
|
17
|
+
scrollTop: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface CheckedOnFieldInput {
|
|
21
|
+
/** The field's edges, in viewport pixels: the checked row centres on them. */
|
|
22
|
+
fieldTop: number;
|
|
23
|
+
fieldBottom: number;
|
|
24
|
+
/** The checked row's offset in the scroll content, and its height. */
|
|
25
|
+
rowTop: number;
|
|
26
|
+
rowHeight: number;
|
|
27
|
+
/** The scroll content's full height. */
|
|
28
|
+
contentHeight: number;
|
|
29
|
+
/** The safe edges the panel's padding box may reach. */
|
|
30
|
+
boundsTop: number;
|
|
31
|
+
boundsBottom: number;
|
|
32
|
+
band: number;
|
|
33
|
+
threshold?: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const clamp = (value: number, low: number, high: number) => Math.max(low, Math.min(high, value));
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* A Select covering its field opens the way a macOS pop-up does: its checked
|
|
40
|
+
* row sits on the field, centred on it, so the value stays where the closed
|
|
41
|
+
* field drew it (FZ-8557, LC-112).
|
|
42
|
+
*
|
|
43
|
+
* A list that fits between the safe edges shows every row with no scrolling,
|
|
44
|
+
* slid the least distance that keeps it on screen. A taller list keeps the
|
|
45
|
+
* checked row on the field and clips at the safe edges: a clipped edge sits on
|
|
46
|
+
* the safe edge, and the rows past it scroll under the opaque arrow band there.
|
|
47
|
+
* The checked row is never under a band; where one would cover it, the panel
|
|
48
|
+
* or the list shifts by that much. Returns the padding box.
|
|
49
|
+
*/
|
|
50
|
+
export function placeCheckedOnField(input: CheckedOnFieldInput): ArrowBandFit {
|
|
51
|
+
const { fieldTop, fieldBottom, rowTop, rowHeight, contentHeight, boundsTop, boundsBottom, band } =
|
|
52
|
+
input;
|
|
53
|
+
const threshold = input.threshold ?? scrollArrowThreshold;
|
|
54
|
+
const target = clamp(
|
|
55
|
+
(fieldTop + fieldBottom) / 2 - rowHeight / 2,
|
|
56
|
+
boundsTop,
|
|
57
|
+
boundsBottom - rowHeight,
|
|
58
|
+
);
|
|
59
|
+
if (contentHeight <= boundsBottom - boundsTop) {
|
|
60
|
+
const top = clamp(target - rowTop, boundsTop, boundsBottom - contentHeight);
|
|
61
|
+
return { top, height: contentHeight, scrollTop: 0 };
|
|
62
|
+
}
|
|
63
|
+
let top = target - rowTop;
|
|
64
|
+
let scrollTop = 0;
|
|
65
|
+
if (top < boundsTop) {
|
|
66
|
+
scrollTop = boundsTop - top;
|
|
67
|
+
top = boundsTop;
|
|
68
|
+
}
|
|
69
|
+
const end = () => top - scrollTop + contentHeight;
|
|
70
|
+
const bottom = () => Math.min(boundsBottom, end());
|
|
71
|
+
const checkedTop = () => top - scrollTop + rowTop;
|
|
72
|
+
// Clear of the down band: the panel's top lifts first, then the list scrolls.
|
|
73
|
+
if (end() - bottom() > threshold) {
|
|
74
|
+
const short = checkedTop() + rowHeight - (bottom() - band);
|
|
75
|
+
if (short > 0) {
|
|
76
|
+
const lift = scrollTop > 0 ? 0 : Math.min(short, top - boundsTop);
|
|
77
|
+
top -= lift;
|
|
78
|
+
scrollTop += short - lift;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Clear of the up band: the list drops, never past its start.
|
|
82
|
+
if (scrollTop > threshold) {
|
|
83
|
+
const short = top + band - checkedTop();
|
|
84
|
+
if (short > 0) scrollTop = Math.max(0, scrollTop - short);
|
|
85
|
+
}
|
|
86
|
+
// A scroll too small to show an arrow is the panel's own offset instead.
|
|
87
|
+
if (scrollTop > 0 && scrollTop <= threshold) {
|
|
88
|
+
top += scrollTop;
|
|
89
|
+
scrollTop = 0;
|
|
90
|
+
}
|
|
91
|
+
return { top, height: bottom() - top, scrollTop };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* A scrolled list whose panel stops short of a safe edge grows toward it, the
|
|
96
|
+
* way a macOS menu does: the edge follows the rows until it meets the safe
|
|
97
|
+
* edge, and the rows stay where the scroll put them (FZ-8463, FZ-8557). The
|
|
98
|
+
* panel never shrinks. Takes and returns the padding box.
|
|
99
|
+
*/
|
|
100
|
+
export function growOnScroll({
|
|
101
|
+
top,
|
|
102
|
+
height,
|
|
103
|
+
scrollTop,
|
|
104
|
+
contentHeight,
|
|
105
|
+
boundsTop,
|
|
106
|
+
boundsBottom,
|
|
107
|
+
}: ArrowBandFit & {
|
|
108
|
+
contentHeight: number;
|
|
109
|
+
boundsTop: number;
|
|
110
|
+
boundsBottom: number;
|
|
111
|
+
}): ArrowBandFit {
|
|
112
|
+
const origin = top - scrollTop;
|
|
113
|
+
const nextTop = Math.min(top, Math.max(boundsTop, origin));
|
|
114
|
+
const nextBottom = Math.max(top + height, Math.min(boundsBottom, origin + contentHeight));
|
|
115
|
+
return { top: nextTop, height: nextBottom - nextTop, scrollTop: nextTop - origin };
|
|
116
|
+
}
|