@multiplatform.one/forms 7.77.1 → 7.78.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 +58 -27
- package/dist/cjs/Button/IconButtonGroup.native.js +64 -29
- package/dist/cjs/Button/IconButtonGroup.native.js.map +1 -1
- package/dist/cjs/Button/circleGlyph.cjs +12 -2
- package/dist/cjs/Button/circleGlyph.native.js +12 -1
- package/dist/cjs/Button/circleGlyph.native.js.map +1 -1
- package/dist/cjs/Button/glass.cjs +2 -1
- package/dist/cjs/Button/glass.native.js +44 -19
- package/dist/cjs/Button/glass.native.js.map +1 -1
- package/dist/cjs/Button/iconButtonGroupContext.cjs +7 -1
- package/dist/cjs/Button/iconButtonGroupContext.native.js +7 -1
- package/dist/cjs/Button/iconButtonGroupContext.native.js.map +1 -1
- package/dist/cjs/Button/index.cjs +15 -23
- package/dist/cjs/Button/index.native.js +15 -24
- package/dist/cjs/Button/index.native.js.map +1 -1
- package/dist/cjs/Button/webGlass.cjs +9 -5
- package/dist/cjs/Button/webGlass.native.js +9 -5
- package/dist/cjs/Button/webGlass.native.js.map +1 -1
- package/dist/cjs/FloatingPanel/coverRect.cjs +50 -0
- package/dist/cjs/FloatingPanel/coverRect.native.js +54 -0
- package/dist/cjs/FloatingPanel/coverRect.native.js.map +1 -0
- package/dist/cjs/FloatingPanel/iconEcho.cjs +3 -1
- package/dist/cjs/FloatingPanel/iconEcho.native.js +3 -1
- package/dist/cjs/FloatingPanel/iconEcho.native.js.map +1 -1
- package/dist/cjs/FloatingPanel/index.cjs +23 -13
- package/dist/cjs/FloatingPanel/index.native.js +41 -15
- package/dist/cjs/FloatingPanel/index.native.js.map +1 -1
- package/dist/cjs/InputParts/index.cjs +5 -0
- package/dist/cjs/InputParts/index.native.js +7 -0
- package/dist/cjs/InputParts/index.native.js.map +1 -1
- package/dist/cjs/{Button/mediaPlate.cjs → InputParts/keyboardAppearance.cjs} +7 -8
- package/dist/{jsx/Button/mediaPlate.native.js → cjs/InputParts/keyboardAppearance.native.js} +8 -9
- package/dist/cjs/InputParts/keyboardAppearance.native.js.map +1 -0
- package/dist/cjs/{Button/mediaPlate.native.js → fields/Duration/DurationWheel.cjs} +11 -11
- package/dist/cjs/fields/Duration/DurationWheel.native.js +132 -0
- package/dist/cjs/fields/Duration/DurationWheel.native.js.map +1 -0
- package/dist/cjs/fields/Duration/durationMath.cjs +19 -1
- package/dist/cjs/fields/Duration/durationMath.native.js +19 -1
- package/dist/cjs/fields/Duration/durationMath.native.js.map +1 -1
- package/dist/cjs/fields/Duration/durationWheelTypes.cjs +18 -0
- package/dist/cjs/fields/Duration/durationWheelTypes.native.js +21 -0
- package/dist/cjs/fields/Duration/durationWheelTypes.native.js.map +1 -0
- package/dist/cjs/fields/Duration/index.cjs +30 -10
- package/dist/cjs/fields/Duration/index.native.js +32 -12
- package/dist/cjs/fields/Duration/index.native.js.map +1 -1
- package/dist/cjs/fields/nativeDateTime/OsDateTimeSheet.cjs +8 -7
- package/dist/cjs/fields/nativeDateTime/OsDateTimeSheet.native.js +8 -7
- package/dist/cjs/fields/nativeDateTime/OsDateTimeSheet.native.js.map +1 -1
- package/dist/cjs/fields/nativeDateTime/osDateTimeIosDisplay.cjs +1 -1
- package/dist/cjs/fields/nativeDateTime/osDateTimeIosDisplay.native.js +1 -1
- package/dist/cjs/fields/nativeDateTime/osDateTimeIosDisplay.native.js.map +1 -1
- package/dist/cjs/fields/nativeDateTime/sheetKeyboard.cjs +1 -1
- package/dist/cjs/fields/nativeDateTime/sheetKeyboard.native.js +1 -1
- package/dist/cjs/fields/nativeDateTime/sheetKeyboard.native.js.map +1 -1
- package/dist/cjs/index.cjs +0 -1
- package/dist/cjs/index.native.js +0 -1
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/esm/Button/IconButtonGroup.mjs +65 -33
- package/dist/esm/Button/IconButtonGroup.mjs.map +1 -1
- package/dist/esm/Button/IconButtonGroup.native.js +71 -35
- package/dist/esm/Button/IconButtonGroup.native.js.map +1 -1
- package/dist/esm/Button/circleGlyph.mjs +9 -1
- package/dist/esm/Button/circleGlyph.mjs.map +1 -1
- package/dist/esm/Button/circleGlyph.native.js +10 -1
- package/dist/esm/Button/circleGlyph.native.js.map +1 -1
- package/dist/esm/Button/glass.mjs +2 -1
- package/dist/esm/Button/glass.mjs.map +1 -1
- package/dist/esm/Button/glass.native.js +43 -19
- package/dist/esm/Button/glass.native.js.map +1 -1
- package/dist/esm/Button/iconButtonGroupContext.mjs +5 -1
- package/dist/esm/Button/iconButtonGroupContext.mjs.map +1 -1
- package/dist/esm/Button/iconButtonGroupContext.native.js +5 -1
- package/dist/esm/Button/iconButtonGroupContext.native.js.map +1 -1
- package/dist/esm/Button/index.mjs +15 -22
- package/dist/esm/Button/index.mjs.map +1 -1
- package/dist/esm/Button/index.native.js +15 -23
- package/dist/esm/Button/index.native.js.map +1 -1
- package/dist/esm/Button/webGlass.mjs +10 -6
- package/dist/esm/Button/webGlass.mjs.map +1 -1
- package/dist/esm/Button/webGlass.native.js +10 -6
- package/dist/esm/Button/webGlass.native.js.map +1 -1
- package/dist/esm/FloatingPanel/coverRect.mjs +23 -0
- package/dist/esm/FloatingPanel/coverRect.mjs.map +1 -0
- package/dist/esm/FloatingPanel/coverRect.native.js +24 -0
- package/dist/esm/FloatingPanel/coverRect.native.js.map +1 -0
- package/dist/esm/FloatingPanel/iconEcho.mjs +4 -2
- package/dist/esm/FloatingPanel/iconEcho.mjs.map +1 -1
- package/dist/esm/FloatingPanel/iconEcho.native.js +4 -2
- package/dist/esm/FloatingPanel/iconEcho.native.js.map +1 -1
- package/dist/esm/FloatingPanel/index.mjs +23 -13
- package/dist/esm/FloatingPanel/index.mjs.map +1 -1
- package/dist/esm/FloatingPanel/index.native.js +41 -15
- package/dist/esm/FloatingPanel/index.native.js.map +1 -1
- package/dist/esm/InputParts/index.mjs +6 -1
- package/dist/esm/InputParts/index.mjs.map +1 -1
- package/dist/esm/InputParts/index.native.js +8 -1
- package/dist/esm/InputParts/index.native.js.map +1 -1
- package/dist/esm/InputParts/keyboardAppearance.mjs +5 -0
- package/dist/esm/InputParts/keyboardAppearance.mjs.map +1 -0
- package/dist/esm/InputParts/keyboardAppearance.native.js +5 -0
- package/dist/esm/InputParts/keyboardAppearance.native.js.map +1 -0
- package/dist/esm/fields/Duration/DurationWheel.mjs +8 -0
- package/dist/esm/fields/Duration/DurationWheel.mjs.map +1 -0
- package/dist/esm/fields/Duration/DurationWheel.native.js +103 -0
- package/dist/esm/fields/Duration/DurationWheel.native.js.map +1 -0
- package/dist/esm/fields/Duration/durationMath.mjs +16 -1
- package/dist/esm/fields/Duration/durationMath.mjs.map +1 -1
- package/dist/esm/fields/Duration/durationMath.native.js +16 -1
- package/dist/esm/fields/Duration/durationMath.native.js.map +1 -1
- package/dist/esm/fields/Duration/durationWheelTypes.mjs +2 -0
- package/dist/esm/fields/Duration/durationWheelTypes.mjs.map +1 -0
- package/dist/esm/fields/Duration/durationWheelTypes.native.js +2 -0
- package/dist/esm/fields/Duration/durationWheelTypes.native.js.map +1 -0
- package/dist/esm/fields/Duration/index.mjs +28 -11
- package/dist/esm/fields/Duration/index.mjs.map +1 -1
- package/dist/esm/fields/Duration/index.native.js +30 -13
- package/dist/esm/fields/Duration/index.native.js.map +1 -1
- package/dist/esm/fields/nativeDateTime/OsDateTimeSheet.mjs +8 -7
- package/dist/esm/fields/nativeDateTime/OsDateTimeSheet.mjs.map +1 -1
- package/dist/esm/fields/nativeDateTime/OsDateTimeSheet.native.js +8 -7
- package/dist/esm/fields/nativeDateTime/OsDateTimeSheet.native.js.map +1 -1
- package/dist/esm/fields/nativeDateTime/osDateTimeIosDisplay.mjs +1 -1
- package/dist/esm/fields/nativeDateTime/osDateTimeIosDisplay.mjs.map +1 -1
- package/dist/esm/fields/nativeDateTime/osDateTimeIosDisplay.native.js +1 -1
- package/dist/esm/fields/nativeDateTime/osDateTimeIosDisplay.native.js.map +1 -1
- package/dist/esm/fields/nativeDateTime/sheetKeyboard.mjs +1 -1
- package/dist/esm/fields/nativeDateTime/sheetKeyboard.mjs.map +1 -1
- package/dist/esm/fields/nativeDateTime/sheetKeyboard.native.js +1 -1
- package/dist/esm/fields/nativeDateTime/sheetKeyboard.native.js.map +1 -1
- package/dist/esm/index.mjs +2 -2
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/index.native.js +2 -2
- package/dist/esm/index.native.js.map +1 -1
- package/dist/jsx/Button/IconButtonGroup.mjs +65 -33
- package/dist/jsx/Button/IconButtonGroup.mjs.map +1 -1
- package/dist/jsx/Button/IconButtonGroup.native.js +64 -29
- package/dist/jsx/Button/IconButtonGroup.native.js.map +1 -1
- package/dist/jsx/Button/circleGlyph.mjs +9 -1
- package/dist/jsx/Button/circleGlyph.mjs.map +1 -1
- package/dist/jsx/Button/circleGlyph.native.js +12 -1
- package/dist/jsx/Button/circleGlyph.native.js.map +1 -1
- package/dist/jsx/Button/glass.mjs +2 -1
- package/dist/jsx/Button/glass.mjs.map +1 -1
- package/dist/jsx/Button/glass.native.js +44 -19
- package/dist/jsx/Button/glass.native.js.map +1 -1
- package/dist/jsx/Button/iconButtonGroupContext.mjs +5 -1
- package/dist/jsx/Button/iconButtonGroupContext.mjs.map +1 -1
- package/dist/jsx/Button/iconButtonGroupContext.native.js +7 -1
- package/dist/jsx/Button/iconButtonGroupContext.native.js.map +1 -1
- package/dist/jsx/Button/index.mjs +15 -22
- package/dist/jsx/Button/index.mjs.map +1 -1
- package/dist/jsx/Button/index.native.js +15 -24
- package/dist/jsx/Button/index.native.js.map +1 -1
- package/dist/jsx/Button/webGlass.mjs +10 -6
- package/dist/jsx/Button/webGlass.mjs.map +1 -1
- package/dist/jsx/Button/webGlass.native.js +9 -5
- package/dist/jsx/Button/webGlass.native.js.map +1 -1
- package/dist/jsx/FloatingPanel/coverRect.mjs +23 -0
- package/dist/jsx/FloatingPanel/coverRect.mjs.map +1 -0
- package/dist/jsx/FloatingPanel/coverRect.native.js +54 -0
- package/dist/jsx/FloatingPanel/coverRect.native.js.map +1 -0
- package/dist/jsx/FloatingPanel/iconEcho.mjs +4 -2
- package/dist/jsx/FloatingPanel/iconEcho.mjs.map +1 -1
- package/dist/jsx/FloatingPanel/iconEcho.native.js +3 -1
- package/dist/jsx/FloatingPanel/iconEcho.native.js.map +1 -1
- package/dist/jsx/FloatingPanel/index.mjs +23 -13
- package/dist/jsx/FloatingPanel/index.mjs.map +1 -1
- package/dist/jsx/FloatingPanel/index.native.js +41 -15
- package/dist/jsx/FloatingPanel/index.native.js.map +1 -1
- package/dist/jsx/InputParts/index.mjs +6 -1
- package/dist/jsx/InputParts/index.mjs.map +1 -1
- package/dist/jsx/InputParts/index.native.js +7 -0
- package/dist/jsx/InputParts/index.native.js.map +1 -1
- package/dist/jsx/InputParts/keyboardAppearance.mjs +5 -0
- package/dist/jsx/InputParts/keyboardAppearance.mjs.map +1 -0
- package/dist/jsx/InputParts/keyboardAppearance.native.js +33 -0
- package/dist/jsx/InputParts/keyboardAppearance.native.js.map +1 -0
- package/dist/jsx/fields/Duration/DurationWheel.mjs +8 -0
- package/dist/jsx/fields/Duration/DurationWheel.mjs.map +1 -0
- package/dist/jsx/fields/Duration/DurationWheel.native.js +132 -0
- package/dist/jsx/fields/Duration/DurationWheel.native.js.map +1 -0
- package/dist/jsx/fields/Duration/durationMath.mjs +16 -1
- package/dist/jsx/fields/Duration/durationMath.mjs.map +1 -1
- package/dist/jsx/fields/Duration/durationMath.native.js +19 -1
- package/dist/jsx/fields/Duration/durationMath.native.js.map +1 -1
- package/dist/jsx/fields/Duration/durationWheelTypes.mjs +2 -0
- package/dist/jsx/fields/Duration/durationWheelTypes.mjs.map +1 -0
- package/dist/jsx/fields/Duration/durationWheelTypes.native.js +21 -0
- package/dist/jsx/fields/Duration/durationWheelTypes.native.js.map +1 -0
- package/dist/jsx/fields/Duration/index.mjs +28 -11
- package/dist/jsx/fields/Duration/index.mjs.map +1 -1
- package/dist/jsx/fields/Duration/index.native.js +32 -12
- package/dist/jsx/fields/Duration/index.native.js.map +1 -1
- package/dist/jsx/fields/nativeDateTime/OsDateTimeSheet.mjs +8 -7
- package/dist/jsx/fields/nativeDateTime/OsDateTimeSheet.mjs.map +1 -1
- package/dist/jsx/fields/nativeDateTime/OsDateTimeSheet.native.js +8 -7
- package/dist/jsx/fields/nativeDateTime/OsDateTimeSheet.native.js.map +1 -1
- package/dist/jsx/fields/nativeDateTime/osDateTimeIosDisplay.mjs +1 -1
- package/dist/jsx/fields/nativeDateTime/osDateTimeIosDisplay.mjs.map +1 -1
- package/dist/jsx/fields/nativeDateTime/osDateTimeIosDisplay.native.js +1 -1
- package/dist/jsx/fields/nativeDateTime/sheetKeyboard.mjs +1 -1
- package/dist/jsx/fields/nativeDateTime/sheetKeyboard.mjs.map +1 -1
- package/dist/jsx/fields/nativeDateTime/sheetKeyboard.native.js +1 -1
- package/dist/jsx/fields/nativeDateTime/sheetKeyboard.native.js.map +1 -1
- package/dist/jsx/index.js +2 -2
- package/dist/jsx/index.js.map +1 -1
- package/dist/jsx/index.mjs +2 -2
- package/dist/jsx/index.mjs.map +1 -1
- package/dist/jsx/index.native.js +0 -1
- package/dist/jsx/index.native.js.map +1 -1
- package/package.json +8 -8
- package/src/Button/Button.iconButton.spec.tsx +47 -7
- package/src/Button/IconButtonGroup.spec.tsx +103 -19
- package/src/Button/IconButtonGroup.tsx +84 -46
- package/src/Button/circleGlyph.ts +18 -0
- package/src/Button/glass.native.spec.ts +62 -0
- package/src/Button/glass.native.ts +58 -37
- package/src/Button/glass.spec.ts +64 -16
- package/src/Button/glass.ts +16 -12
- package/src/Button/iconButtonGroupContext.ts +27 -3
- package/src/Button/index.tsx +21 -33
- package/src/Button/webGlass.ts +25 -18
- package/src/FloatingPanel/FloatingPanel.iconEcho.spec.tsx +61 -1
- package/src/FloatingPanel/FloatingPanel.menuFace.native.spec.tsx +104 -0
- package/src/FloatingPanel/coverRect.ts +42 -0
- package/src/FloatingPanel/iconEcho.tsx +9 -3
- package/src/FloatingPanel/index.native.tsx +63 -18
- package/src/FloatingPanel/index.tsx +35 -15
- package/src/InputParts/index.tsx +6 -0
- package/src/InputParts/keyboardAppearance.spec.ts +12 -0
- package/src/InputParts/keyboardAppearance.ts +4 -0
- package/src/fields/Duration/Duration.spec.tsx +19 -10
- package/src/fields/Duration/Duration.wheel.native.spec.tsx +98 -0
- package/src/fields/Duration/DurationWheel.native.tsx +101 -0
- package/src/fields/Duration/DurationWheel.tsx +11 -0
- package/src/fields/Duration/durationMath.spec.ts +43 -0
- package/src/fields/Duration/durationMath.ts +31 -0
- package/src/fields/Duration/durationWheelTypes.ts +18 -0
- package/src/fields/Duration/index.tsx +58 -27
- package/src/fields/nativeDateTime/OsDateTimeSheet.countdown.native.spec.tsx +121 -0
- package/src/fields/nativeDateTime/OsDateTimeSheet.spec.ts +4 -0
- package/src/fields/nativeDateTime/OsDateTimeSheet.tsx +14 -9
- package/src/fields/nativeDateTime/osDateTimeIosDisplay.ts +3 -3
- package/src/fields/nativeDateTime/sheetKeyboard.spec.ts +6 -0
- package/src/fields/nativeDateTime/sheetKeyboard.ts +2 -2
- package/src/index.ts +0 -1
- package/types/Button/IconButtonGroup.d.ts +7 -12
- package/types/Button/IconButtonGroup.d.ts.map +1 -1
- package/types/Button/circleGlyph.d.ts +12 -0
- package/types/Button/circleGlyph.d.ts.map +1 -1
- package/types/Button/glass.d.ts +7 -11
- package/types/Button/glass.d.ts.map +1 -1
- package/types/Button/glass.native.d.ts +22 -10
- package/types/Button/glass.native.d.ts.map +1 -1
- package/types/Button/iconButtonGroupContext.d.ts +21 -3
- package/types/Button/iconButtonGroupContext.d.ts.map +1 -1
- package/types/Button/index.d.ts +3 -10
- package/types/Button/index.d.ts.map +1 -1
- package/types/Button/webGlass.d.ts +8 -16
- package/types/Button/webGlass.d.ts.map +1 -1
- package/types/FloatingPanel/coverRect.d.ts +25 -0
- package/types/FloatingPanel/coverRect.d.ts.map +1 -0
- package/types/FloatingPanel/index.d.ts.map +1 -1
- package/types/FloatingPanel/index.native.d.ts.map +1 -1
- package/types/InputParts/index.d.ts.map +1 -1
- package/types/InputParts/keyboardAppearance.d.ts +3 -0
- package/types/InputParts/keyboardAppearance.d.ts.map +1 -0
- package/types/fields/Duration/DurationWheel.d.ts +5 -0
- package/types/fields/Duration/DurationWheel.d.ts.map +1 -0
- package/types/fields/Duration/DurationWheel.native.d.ts +5 -0
- package/types/fields/Duration/DurationWheel.native.d.ts.map +1 -0
- package/types/fields/Duration/durationMath.d.ts +13 -0
- package/types/fields/Duration/durationMath.d.ts.map +1 -1
- package/types/fields/Duration/durationWheelTypes.d.ts +18 -0
- package/types/fields/Duration/durationWheelTypes.d.ts.map +1 -0
- package/types/fields/Duration/index.d.ts +8 -3
- package/types/fields/Duration/index.d.ts.map +1 -1
- package/types/fields/nativeDateTime/OsDateTimeSheet.d.ts +1 -1
- package/types/fields/nativeDateTime/OsDateTimeSheet.d.ts.map +1 -1
- package/types/fields/nativeDateTime/osDateTimeIosDisplay.d.ts +2 -2
- package/types/fields/nativeDateTime/osDateTimeIosDisplay.d.ts.map +1 -1
- package/types/fields/nativeDateTime/sheetKeyboard.d.ts +1 -1
- package/types/fields/nativeDateTime/sheetKeyboard.d.ts.map +1 -1
- package/types/index.d.ts +1 -1
- package/types/index.d.ts.map +1 -1
- package/dist/cjs/Button/mediaPlate.native.js.map +0 -1
- package/dist/esm/Button/mediaPlate.mjs +0 -6
- package/dist/esm/Button/mediaPlate.mjs.map +0 -1
- package/dist/esm/Button/mediaPlate.native.js +0 -6
- package/dist/esm/Button/mediaPlate.native.js.map +0 -1
- package/dist/jsx/Button/mediaPlate.mjs +0 -6
- package/dist/jsx/Button/mediaPlate.mjs.map +0 -1
- package/dist/jsx/Button/mediaPlate.native.js.map +0 -1
- package/src/Button/mediaPlate.ts +0 -9
- package/types/Button/mediaPlate.d.ts +0 -10
- package/types/Button/mediaPlate.d.ts.map +0 -1
|
@@ -123,6 +123,12 @@ import { RegularPanel } from "./RegularPanel";
|
|
|
123
123
|
import { panelViewportPadding, resolvePanelWidthMode } from "./useFloatingPanel";
|
|
124
124
|
import { TrailingActionOverlay, TriggerWithTrailingAction } from "./trailingAction";
|
|
125
125
|
import { iconEchoElement, iconEchoOverlays, type IconEcho } from "./iconEcho";
|
|
126
|
+
import { echoInCover, iconCoverRect } from "./coverRect";
|
|
127
|
+
import {
|
|
128
|
+
IconButtonGroupSurfaceContext,
|
|
129
|
+
useIconButtonGroupSurface,
|
|
130
|
+
type IconButtonGroupPlate,
|
|
131
|
+
} from "../Button/iconButtonGroupContext";
|
|
126
132
|
|
|
127
133
|
// Re-export the hook + constants so Select / Combobox / external consumers
|
|
128
134
|
// resolve identically on native (their floating cores are unused on device
|
|
@@ -189,10 +195,13 @@ export function FloatingPanel({
|
|
|
189
195
|
const { knobProps } = useResolvedKnobs({ painted: false });
|
|
190
196
|
const triggerSurface = SurfaceContext.useStyledContext();
|
|
191
197
|
const windowZIndex = useOverlayZIndex("sheet");
|
|
198
|
+
// A grouped trigger's capsule is its own: a panel's content never covers it.
|
|
192
199
|
const children = (
|
|
193
|
-
<
|
|
194
|
-
{
|
|
195
|
-
|
|
200
|
+
<IconButtonGroupSurfaceContext.Provider value={null}>
|
|
201
|
+
<Surface painted={false} elevated>
|
|
202
|
+
{panelChildren}
|
|
203
|
+
</Surface>
|
|
204
|
+
</IconButtonGroupSurfaceContext.Provider>
|
|
196
205
|
);
|
|
197
206
|
const slideTransition = knobProps.motion.slide.transition;
|
|
198
207
|
const enterDuration = motionDurationMs(slideTransition);
|
|
@@ -250,6 +259,10 @@ export function FloatingPanel({
|
|
|
250
259
|
const anchored = asMenu || asRegular;
|
|
251
260
|
const triggerRef = useRef<RNView | null>(null);
|
|
252
261
|
const [anchor, setAnchor] = useState<TriggerRect | null>(null);
|
|
262
|
+
// LC-112 (FZ-8550): a trigger in a bar's glass capsule covers the capsule.
|
|
263
|
+
const groupSurface = useIconButtonGroupSurface();
|
|
264
|
+
// `undefined` while the capsule is still being measured.
|
|
265
|
+
const [group, setGroup] = useState<IconButtonGroupPlate | null | undefined>(null);
|
|
253
266
|
const [triggerRadius, setTriggerRadius] = useState<number | null>(null);
|
|
254
267
|
const reportTriggerRadius = useCallback(
|
|
255
268
|
(radius: number) => setTriggerRadius((prev) => (prev === radius ? prev : radius)),
|
|
@@ -260,6 +273,7 @@ export function FloatingPanel({
|
|
|
260
273
|
useLayoutEffect(() => {
|
|
261
274
|
if (!anchored || !open) {
|
|
262
275
|
setAnchor(null);
|
|
276
|
+
setGroup(null);
|
|
263
277
|
return;
|
|
264
278
|
}
|
|
265
279
|
const node = triggerRef.current as
|
|
@@ -275,10 +289,18 @@ export function FloatingPanel({
|
|
|
275
289
|
} else {
|
|
276
290
|
setAnchor({ x: 0, y: 0, width: 0, height: 0 });
|
|
277
291
|
}
|
|
292
|
+
if (iconEcho && anchorMode !== "attach" && groupSurface) {
|
|
293
|
+
setGroup(undefined);
|
|
294
|
+
groupSurface.measure((plate) => {
|
|
295
|
+
if (live) setGroup(plate);
|
|
296
|
+
});
|
|
297
|
+
} else {
|
|
298
|
+
setGroup(null);
|
|
299
|
+
}
|
|
278
300
|
return () => {
|
|
279
301
|
live = false;
|
|
280
302
|
};
|
|
281
|
-
}, [anchored, open]);
|
|
303
|
+
}, [anchored, open, iconEcho, anchorMode, groupSurface]);
|
|
282
304
|
|
|
283
305
|
const handleBackdropPress = useCallback(() => {
|
|
284
306
|
if (backdropArmed) close();
|
|
@@ -636,7 +658,7 @@ export function FloatingPanel({
|
|
|
636
658
|
<TriggerMenuPanel
|
|
637
659
|
// An attached menu (an icon trigger, no echo row) opens under its
|
|
638
660
|
// trigger instead of over it, the UIMenu pull-down (LC-262).
|
|
639
|
-
anchor={anchor}
|
|
661
|
+
anchor={group === undefined ? null : anchor}
|
|
640
662
|
attach={anchorMode === "attach"}
|
|
641
663
|
bounds={{
|
|
642
664
|
top: topInset + panelViewportPadding,
|
|
@@ -655,6 +677,7 @@ export function FloatingPanel({
|
|
|
655
677
|
: null
|
|
656
678
|
}
|
|
657
679
|
iconEchoKind={iconEcho}
|
|
680
|
+
group={iconEcho && anchorMode !== "attach" ? (group ?? null) : null}
|
|
658
681
|
coverAlign={anchorMode === "attach" ? "start" : coverAlign}
|
|
659
682
|
panelWidth={panelWidth}
|
|
660
683
|
triggerRadius={anchorMode === "attach" ? null : triggerRadius}
|
|
@@ -824,6 +847,7 @@ function TriggerMenuPanel({
|
|
|
824
847
|
echoLabel,
|
|
825
848
|
iconEcho = null,
|
|
826
849
|
iconEchoKind = "menu",
|
|
850
|
+
group = null,
|
|
827
851
|
coverAlign,
|
|
828
852
|
panelWidth,
|
|
829
853
|
triggerRadius,
|
|
@@ -841,6 +865,8 @@ function TriggerMenuPanel({
|
|
|
841
865
|
/** LC-112 (FZ-8480): the icon trigger drawn again in its own spot. */
|
|
842
866
|
iconEcho?: ReactNode;
|
|
843
867
|
iconEchoKind?: IconEcho;
|
|
868
|
+
/** LC-112 (FZ-8550): the glass capsule a grouped trigger sits on. */
|
|
869
|
+
group?: IconButtonGroupPlate | null;
|
|
844
870
|
coverAlign: "start" | "auto";
|
|
845
871
|
panelWidth: PanelWidth;
|
|
846
872
|
triggerRadius: number | null;
|
|
@@ -852,6 +878,11 @@ function TriggerMenuPanel({
|
|
|
852
878
|
}) {
|
|
853
879
|
const { knobProps } = useResolvedKnobs({ painted: false });
|
|
854
880
|
const [size, setSize] = useState<{ width: number; height: number } | null>(null);
|
|
881
|
+
// LC-112 (FZ-8480, FZ-8550): an icon trigger's panel covers the surface the
|
|
882
|
+
// trigger is seen on, its capsule or its press target, and the X keeps the
|
|
883
|
+
// trigger's own box inside it.
|
|
884
|
+
const cover = anchor && iconEcho != null ? iconCoverRect(anchor, group) : anchor;
|
|
885
|
+
const echoCover = anchor && cover && iconEcho != null ? echoInCover(anchor, cover) : null;
|
|
855
886
|
// LC-112 width setting (FZ-8438 r3). `"content"` keeps the iOS pull-down's
|
|
856
887
|
// 250pt floor (FZ-8220), and no panel is narrower than an echoed trigger.
|
|
857
888
|
const room =
|
|
@@ -871,12 +902,12 @@ function TriggerMenuPanel({
|
|
|
871
902
|
trigger: anchor?.width ?? 0,
|
|
872
903
|
available: room.right - room.left,
|
|
873
904
|
echo: echo != null,
|
|
874
|
-
floor: Math.max(MENU_MIN_WIDTH,
|
|
905
|
+
floor: Math.max(MENU_MIN_WIDTH, cover?.width ?? 0),
|
|
875
906
|
});
|
|
876
907
|
const placement =
|
|
877
|
-
anchor && size
|
|
908
|
+
anchor && cover && size
|
|
878
909
|
? (attach ? placeBesideTrigger : placeOverTrigger)({
|
|
879
|
-
trigger:
|
|
910
|
+
trigger: cover,
|
|
880
911
|
width: size.width,
|
|
881
912
|
height: size.height,
|
|
882
913
|
bounds: room,
|
|
@@ -888,13 +919,15 @@ function TriggerMenuPanel({
|
|
|
888
919
|
// LC-112 seamless cover: the menu is its trigger grown open, so it draws the
|
|
889
920
|
// trigger's corner arc at all four corners. A field reports its radius; an
|
|
890
921
|
// icon button with no echo is a circle or capsule, half its short side.
|
|
891
|
-
const half =
|
|
922
|
+
const half = cover ? Math.min(cover.width, cover.height) / 2 : undefined;
|
|
892
923
|
const radius =
|
|
893
|
-
|
|
894
|
-
? Math.min(
|
|
895
|
-
:
|
|
896
|
-
? half
|
|
897
|
-
:
|
|
924
|
+
group != null && iconEcho != null
|
|
925
|
+
? Math.min(group.radius, half ?? group.radius)
|
|
926
|
+
: triggerRadius != null && half != null
|
|
927
|
+
? Math.min(triggerRadius, half)
|
|
928
|
+
: coverAlign === "auto" && half
|
|
929
|
+
? half
|
|
930
|
+
: undefined;
|
|
898
931
|
const borderWidth = Number(knobProps.elevatedSurface.borderWidth) || 0;
|
|
899
932
|
const echoOverlays = iconEchoOverlays(iconEchoKind, placement?.up ?? false);
|
|
900
933
|
// A panel slid along the screen (a `"screen"` or fixed width, or one pushed
|
|
@@ -981,7 +1014,13 @@ function TriggerMenuPanel({
|
|
|
981
1014
|
</Pressable>
|
|
982
1015
|
) : null}
|
|
983
1016
|
{iconEcho != null && anchor && !placement?.up && !echoOverlays ? (
|
|
984
|
-
<IconEchoBand
|
|
1017
|
+
<IconEchoBand
|
|
1018
|
+
anchor={anchor}
|
|
1019
|
+
cover={echoCover}
|
|
1020
|
+
shift={echoShift}
|
|
1021
|
+
borderWidth={borderWidth}
|
|
1022
|
+
edge="top"
|
|
1023
|
+
>
|
|
985
1024
|
{iconEcho}
|
|
986
1025
|
</IconEchoBand>
|
|
987
1026
|
) : null}
|
|
@@ -996,7 +1035,7 @@ function TriggerMenuPanel({
|
|
|
996
1035
|
? echoHeaderBox({
|
|
997
1036
|
offset: echoShift,
|
|
998
1037
|
width: anchor.width,
|
|
999
|
-
height: anchor.height,
|
|
1038
|
+
height: echoCover?.height ?? anchor.height,
|
|
1000
1039
|
panelWidth: placement.width,
|
|
1001
1040
|
rtl: I18nManager.isRTL,
|
|
1002
1041
|
})
|
|
@@ -1010,6 +1049,7 @@ function TriggerMenuPanel({
|
|
|
1010
1049
|
{iconEcho != null && anchor && (placement?.up || echoOverlays) ? (
|
|
1011
1050
|
<IconEchoBand
|
|
1012
1051
|
anchor={anchor}
|
|
1052
|
+
cover={echoCover}
|
|
1013
1053
|
shift={echoShift}
|
|
1014
1054
|
borderWidth={borderWidth}
|
|
1015
1055
|
edge={echoOverlays ? "overlay" : "bottom"}
|
|
@@ -1032,18 +1072,22 @@ function TriggerMenuPanel({
|
|
|
1032
1072
|
*/
|
|
1033
1073
|
function IconEchoBand({
|
|
1034
1074
|
anchor,
|
|
1075
|
+
cover = null,
|
|
1035
1076
|
shift,
|
|
1036
1077
|
borderWidth,
|
|
1037
1078
|
edge,
|
|
1038
1079
|
children,
|
|
1039
1080
|
}: {
|
|
1040
1081
|
anchor: TriggerRect;
|
|
1082
|
+
/** The trigger's offset in, and the height of, a cover larger than it. */
|
|
1083
|
+
cover?: { top: number; height: number } | null;
|
|
1041
1084
|
shift: number;
|
|
1042
1085
|
borderWidth: number;
|
|
1043
1086
|
/** `"overlay"`: an X laid over the content's top corner, taking no height. */
|
|
1044
1087
|
edge: "top" | "bottom" | "overlay";
|
|
1045
1088
|
children: ReactNode;
|
|
1046
1089
|
}) {
|
|
1090
|
+
const bandHeight = cover?.height ?? anchor.height;
|
|
1047
1091
|
return (
|
|
1048
1092
|
<RNView
|
|
1049
1093
|
testID="floating-panel-icon-echo-band"
|
|
@@ -1055,11 +1099,11 @@ function IconEchoBand({
|
|
|
1055
1099
|
top: -borderWidth,
|
|
1056
1100
|
start: -borderWidth,
|
|
1057
1101
|
end: -borderWidth,
|
|
1058
|
-
height:
|
|
1102
|
+
height: bandHeight,
|
|
1059
1103
|
zIndex: 1,
|
|
1060
1104
|
}
|
|
1061
1105
|
: {
|
|
1062
|
-
height:
|
|
1106
|
+
height: bandHeight,
|
|
1063
1107
|
marginHorizontal: -borderWidth,
|
|
1064
1108
|
...(edge === "top" ? { marginTop: -borderWidth } : { marginBottom: -borderWidth }),
|
|
1065
1109
|
}
|
|
@@ -1070,6 +1114,7 @@ function IconEchoBand({
|
|
|
1070
1114
|
width: anchor.width,
|
|
1071
1115
|
height: anchor.height,
|
|
1072
1116
|
marginStart: shift,
|
|
1117
|
+
marginTop: cover?.top ?? 0,
|
|
1073
1118
|
alignItems: "flex-start",
|
|
1074
1119
|
justifyContent: "flex-start",
|
|
1075
1120
|
}}
|
|
@@ -48,6 +48,7 @@ import {
|
|
|
48
48
|
type PanelIconEchoBox,
|
|
49
49
|
} from "./echoContext";
|
|
50
50
|
import { iconEchoElement, iconEchoOverlays, type IconEcho } from "./iconEcho";
|
|
51
|
+
import { echoInCover, iconCoverRect } from "./coverRect";
|
|
51
52
|
import { groupSurfaceOf, paintedTriggerBox } from "./paintedBox";
|
|
52
53
|
import {
|
|
53
54
|
contextMenuPoint,
|
|
@@ -566,8 +567,9 @@ function placeIconEcho(
|
|
|
566
567
|
up: boolean;
|
|
567
568
|
overlay: boolean;
|
|
568
569
|
/**
|
|
569
|
-
* The
|
|
570
|
-
*
|
|
570
|
+
* The surface the panel covers when it is not the trigger's own box (a
|
|
571
|
+
* grouped capsule, or the 44pt target of a smaller avatar): the band
|
|
572
|
+
* covers all of it, and only the pressed trigger shows, at its own spot.
|
|
571
573
|
*/
|
|
572
574
|
group?: { top: number; height: number } | null;
|
|
573
575
|
},
|
|
@@ -1065,22 +1067,43 @@ function FloatingPanelWeb({
|
|
|
1065
1067
|
measured.height + firstActionHeight + bodyPadding + totalBorderWidth,
|
|
1066
1068
|
)
|
|
1067
1069
|
: totalContentHeight;
|
|
1070
|
+
// LC-112 (FZ-8480): an icon trigger's panel covers the surface the
|
|
1071
|
+
// trigger is seen on: its capsule, else its press target.
|
|
1072
|
+
const painted = showIconEcho ? paintedTriggerBox(trig, measured) : null;
|
|
1073
|
+
const coverRect = painted
|
|
1074
|
+
? iconCoverRect(
|
|
1075
|
+
{
|
|
1076
|
+
x: painted.left,
|
|
1077
|
+
y: measured.top,
|
|
1078
|
+
width: painted.right - painted.left,
|
|
1079
|
+
height: measured.height,
|
|
1080
|
+
},
|
|
1081
|
+
groupBox
|
|
1082
|
+
? {
|
|
1083
|
+
x: groupBox.left,
|
|
1084
|
+
y: groupBox.top,
|
|
1085
|
+
width: groupBox.width,
|
|
1086
|
+
height: groupBox.height,
|
|
1087
|
+
}
|
|
1088
|
+
: null,
|
|
1089
|
+
)
|
|
1090
|
+
: null;
|
|
1091
|
+
const echoCover = coverRect
|
|
1092
|
+
? echoInCover(
|
|
1093
|
+
{ x: measured.left, y: measured.top, width: measured.width, height: measured.height },
|
|
1094
|
+
coverRect,
|
|
1095
|
+
)
|
|
1096
|
+
: null;
|
|
1068
1097
|
let placedTop = measured.top;
|
|
1069
1098
|
let placedLeft = left;
|
|
1070
1099
|
let placedMaxHeight: number | null = null;
|
|
1071
1100
|
iconEchoUpRef.current = false;
|
|
1072
|
-
if (
|
|
1101
|
+
if (coverRect && measured.width > 0 && measured.height > 0) {
|
|
1073
1102
|
// LC-112 (FZ-8480): the button is drawn again in its own spot, so the
|
|
1074
1103
|
// panel stays on it: down from its top edge, or up from its bottom.
|
|
1075
1104
|
// It covers the button's plate and press box where they reach past it.
|
|
1076
|
-
const painted = groupBox ?? paintedTriggerBox(trig, measured);
|
|
1077
1105
|
const placed = placeOverTrigger({
|
|
1078
|
-
trigger:
|
|
1079
|
-
x: painted.left,
|
|
1080
|
-
y: groupBox ? groupBox.top : measured.top,
|
|
1081
|
-
width: painted.right - painted.left,
|
|
1082
|
-
height: groupBox ? groupBox.height : measured.height,
|
|
1083
|
-
},
|
|
1106
|
+
trigger: coverRect,
|
|
1084
1107
|
width,
|
|
1085
1108
|
height: totalContentHeight,
|
|
1086
1109
|
bounds: {
|
|
@@ -1152,7 +1175,7 @@ function FloatingPanelWeb({
|
|
|
1152
1175
|
? echoHeaderBox({
|
|
1153
1176
|
offset: echoStart,
|
|
1154
1177
|
width: measured.width,
|
|
1155
|
-
height: measured.height,
|
|
1178
|
+
height: echoCover?.height ?? measured.height,
|
|
1156
1179
|
panelWidth: width,
|
|
1157
1180
|
rtl,
|
|
1158
1181
|
})
|
|
@@ -1164,10 +1187,7 @@ function FloatingPanelWeb({
|
|
|
1164
1187
|
height: measured.height,
|
|
1165
1188
|
up: iconEchoUpRef.current,
|
|
1166
1189
|
overlay: echoOverlays,
|
|
1167
|
-
group:
|
|
1168
|
-
groupSurface && groupBox
|
|
1169
|
-
? { top: measured.top - groupBox.top, height: groupBox.height }
|
|
1170
|
-
: null,
|
|
1190
|
+
group: echoCover,
|
|
1171
1191
|
});
|
|
1172
1192
|
placeEchoOverTrigger(
|
|
1173
1193
|
dropdownElement,
|
package/src/InputParts/index.tsx
CHANGED
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
styled,
|
|
35
35
|
useGetThemedIcon,
|
|
36
36
|
useTheme,
|
|
37
|
+
useThemeName,
|
|
37
38
|
} from "tamagui";
|
|
38
39
|
import {
|
|
39
40
|
ensureCompositeFocusRing,
|
|
@@ -64,6 +65,7 @@ import { pressSlopProps } from "../shared/pressSlopProps";
|
|
|
64
65
|
import { useFieldRow } from "../shared/fieldRowContext";
|
|
65
66
|
import { useIsInTableCell } from "../shared/tableCellContext";
|
|
66
67
|
import { useInPanelEcho, usePanelTriggerRadiusReporter } from "../FloatingPanel/echoContext";
|
|
68
|
+
import { keyboardAppearanceFor } from "./keyboardAppearance";
|
|
67
69
|
import { useFieldA11y, useFieldDescribedBy } from "../fieldLayout";
|
|
68
70
|
|
|
69
71
|
/**
|
|
@@ -857,6 +859,7 @@ const InputAreaImpl = InputFrame.styleable<{
|
|
|
857
859
|
// field given `size` directly (no Surface) must not type at `$true`.
|
|
858
860
|
const { knobProps } = useResolvedKnobs({ size: size as SizeTokens });
|
|
859
861
|
const theme = useTheme();
|
|
862
|
+
const themeName = useThemeName();
|
|
860
863
|
const fieldDescribedBy = useFieldDescribedBy();
|
|
861
864
|
const nativeA11yProps = useNativeFieldA11yProps();
|
|
862
865
|
const inTableCell = useIsInTableCell();
|
|
@@ -929,6 +932,9 @@ const InputAreaImpl = InputFrame.styleable<{
|
|
|
929
932
|
paddingHorizontal={0}
|
|
930
933
|
paddingVertical={0}
|
|
931
934
|
keyboardType={resolvedKeyboardType}
|
|
935
|
+
// The iOS keyboard follows the OS appearance unless told, so a light
|
|
936
|
+
// app on a dark phone typed on a dark keypad (FZ-8111).
|
|
937
|
+
{...(isWeb ? undefined : { keyboardAppearance: keyboardAppearanceFor(themeName) })}
|
|
932
938
|
textContentType={textContentType}
|
|
933
939
|
inputMode={resolvedInputMode}
|
|
934
940
|
autoComplete={
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { keyboardAppearanceFor } from "./keyboardAppearance";
|
|
3
|
+
|
|
4
|
+
describe("keyboardAppearanceFor (FZ-8111)", () => {
|
|
5
|
+
it("follows the app's scheme, not the phone's", () => {
|
|
6
|
+
expect(keyboardAppearanceFor("light")).toBe("light");
|
|
7
|
+
expect(keyboardAppearanceFor("light_accent")).toBe("light");
|
|
8
|
+
expect(keyboardAppearanceFor("dark")).toBe("dark");
|
|
9
|
+
expect(keyboardAppearanceFor("dark_accent")).toBe("dark");
|
|
10
|
+
expect(keyboardAppearanceFor(undefined)).toBe("light");
|
|
11
|
+
});
|
|
12
|
+
});
|
|
@@ -104,14 +104,14 @@ describe("Duration", () => {
|
|
|
104
104
|
|
|
105
105
|
it("supplies a default clock affordance", () => {
|
|
106
106
|
const result = renderWithProviders(<Duration label="Clock" name="c" value={3600} />);
|
|
107
|
-
expect(result.container.querySelector("svg")).toBeTruthy();
|
|
107
|
+
expect(result.container.querySelector("[data-duration-box] svg")).toBeTruthy();
|
|
108
108
|
});
|
|
109
109
|
|
|
110
110
|
it("hides the clock when clockIcon is null", () => {
|
|
111
111
|
const result = renderWithProviders(
|
|
112
112
|
<Duration label="No clock" name="c" value={3600} clockIcon={null} />,
|
|
113
113
|
);
|
|
114
|
-
expect(result.container.querySelector("svg")).toBeNull();
|
|
114
|
+
expect(result.container.querySelector("[data-duration-box] svg")).toBeNull();
|
|
115
115
|
});
|
|
116
116
|
|
|
117
117
|
it("normalises a negative value in the hours segment", () => {
|
|
@@ -193,7 +193,7 @@ describe("Duration", () => {
|
|
|
193
193
|
expect(DURATION_PRESET_SIZE).toBe("$2");
|
|
194
194
|
expect(sizeRecipeForToken(DURATION_PRESET_SIZE).height).toBe(26);
|
|
195
195
|
const result = renderWithProviders(<Duration label="P" value={0} />);
|
|
196
|
-
const chips = result.container.querySelectorAll("[data-duration-preset]");
|
|
196
|
+
const chips = result.container.querySelectorAll("[data-duration-preset] [data-mp-chip]");
|
|
197
197
|
expect(chips).toHaveLength(6);
|
|
198
198
|
for (const chip of chips) {
|
|
199
199
|
const el = chip as HTMLElement;
|
|
@@ -222,8 +222,8 @@ describe("Duration", () => {
|
|
|
222
222
|
it("clock glyph follows the icon recipe, not a hardcoded 16 (LC-75)", () => {
|
|
223
223
|
const small = renderWithProviders(<Duration label="S" size="$2" value={3600} />);
|
|
224
224
|
const large = renderWithProviders(<Duration label="L" size="$6" value={3600} />);
|
|
225
|
-
const sSvg = small.container.querySelector("svg");
|
|
226
|
-
const lSvg = large.container.querySelector("svg");
|
|
225
|
+
const sSvg = small.container.querySelector("[data-duration-box] svg");
|
|
226
|
+
const lSvg = large.container.querySelector("[data-duration-box] svg");
|
|
227
227
|
const sSize = Number(sSvg?.getAttribute("width") ?? sSvg?.getAttribute("height"));
|
|
228
228
|
const lSize = Number(lSvg?.getAttribute("width") ?? lSvg?.getAttribute("height"));
|
|
229
229
|
expect(sSize).toBe(sizeRecipeForToken("$2").iconSize);
|
|
@@ -244,13 +244,22 @@ describe("Duration", () => {
|
|
|
244
244
|
it("fires onChange with seconds (LC-14) from a preset", () => {
|
|
245
245
|
const onChange = vi.fn();
|
|
246
246
|
const result = renderWithProviders(<Duration label="P" value={0} onChange={onChange} />);
|
|
247
|
-
const chip = result.container.querySelector(
|
|
248
|
-
"[data-duration-preset], [data-duration-presets] button, [data-duration-presets] [role='button']",
|
|
249
|
-
);
|
|
247
|
+
const chip = result.container.querySelector("[data-duration-preset='1h'] [role='button']");
|
|
250
248
|
expect(chip).toBeTruthy();
|
|
251
249
|
fireEvent.click(chip as HTMLElement);
|
|
252
|
-
expect(onChange).
|
|
253
|
-
|
|
250
|
+
expect(onChange).toHaveBeenCalledWith(3600);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("presets are MPO chips, the current value's chip selected (FZ-8111)", () => {
|
|
254
|
+
const result = renderWithProviders(<Duration label="P" value={3600} />);
|
|
255
|
+
const selected = result.container.querySelectorAll(
|
|
256
|
+
"[data-duration-preset] [data-mp-chip][data-selected='true']",
|
|
257
|
+
);
|
|
258
|
+
expect(selected).toHaveLength(1);
|
|
259
|
+
expect(
|
|
260
|
+
selected[0]!.closest("[data-duration-preset]")?.getAttribute("data-duration-preset"),
|
|
261
|
+
).toBe("1h");
|
|
262
|
+
expect(result.container.querySelector("[data-duration-presets] button")).toBeNull();
|
|
254
263
|
});
|
|
255
264
|
|
|
256
265
|
it("arrow-up on hours commits via onChange", () => {
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { renderWithProviders } from "@multiplatform.one/test-utils";
|
|
2
|
+
import { act, fireEvent } from "@testing-library/react";
|
|
3
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
|
+
|
|
5
|
+
const picker = vi.hoisted(() => ({ available: true, props: [] as Record<string, unknown>[] }));
|
|
6
|
+
|
|
7
|
+
vi.mock("react-native", async (importOriginal) => {
|
|
8
|
+
const actual = await importOriginal<typeof import("react-native")>();
|
|
9
|
+
return { ...actual, Platform: { ...actual.Platform, OS: "ios" } };
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
vi.mock("@multiplatform.one/platform", async (importOriginal) => {
|
|
13
|
+
const actual = (await importOriginal()) as Record<string, unknown>;
|
|
14
|
+
const real = actual.platform as Record<string, unknown>;
|
|
15
|
+
return {
|
|
16
|
+
...actual,
|
|
17
|
+
platform: new Proxy(
|
|
18
|
+
{},
|
|
19
|
+
{
|
|
20
|
+
get(_target, prop) {
|
|
21
|
+
if (prop === "isIos") return true;
|
|
22
|
+
if (prop === "isAndroid") return false;
|
|
23
|
+
return real[prop as string];
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
),
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
vi.mock("./DurationWheel", async () => await import("./DurationWheel.native"));
|
|
31
|
+
|
|
32
|
+
vi.mock("../nativeDateTime/dateTimePickerLoader", () => ({
|
|
33
|
+
loadDateTimePicker: () =>
|
|
34
|
+
picker.available
|
|
35
|
+
? (props: Record<string, unknown>) => {
|
|
36
|
+
picker.props.push(props);
|
|
37
|
+
return <div data-testid="os-picker" />;
|
|
38
|
+
}
|
|
39
|
+
: null,
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
const { Duration } = await import("./index");
|
|
43
|
+
|
|
44
|
+
const at = (h: number, m: number) => {
|
|
45
|
+
const d = new Date(2026, 9, 29);
|
|
46
|
+
d.setHours(h, m, 0, 0);
|
|
47
|
+
return d;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
describe("Duration on a phone (FZ-8111, LC-266)", () => {
|
|
51
|
+
beforeEach(() => {
|
|
52
|
+
picker.available = true;
|
|
53
|
+
picker.props = [];
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("shows the value on a trigger, not typed segments", () => {
|
|
57
|
+
const result = renderWithProviders(<Duration label="Duration" value={3720} />);
|
|
58
|
+
expect(result.getByTestId("duration-wheel-trigger")).toBeTruthy();
|
|
59
|
+
expect(result.container.querySelector("[data-duration-value]")?.textContent).toBe("1 h 2 m");
|
|
60
|
+
expect(result.container.querySelector('input[aria-label="Hours"]')).toBeNull();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("opens the countdown wheel at the value and writes the pick back as seconds", () => {
|
|
64
|
+
const onChange = vi.fn();
|
|
65
|
+
const result = renderWithProviders(
|
|
66
|
+
<Duration label="Duration" value={3720} hideDays onChange={onChange} />,
|
|
67
|
+
);
|
|
68
|
+
fireEvent.click(result.getByTestId("duration-wheel-trigger"));
|
|
69
|
+
const props = picker.props.at(-1)!;
|
|
70
|
+
expect(props.mode).toBe("countdown");
|
|
71
|
+
expect([(props.value as Date).getHours(), (props.value as Date).getMinutes()]).toEqual([1, 2]);
|
|
72
|
+
act(() => {
|
|
73
|
+
(props.onChange as (e: unknown, d: Date) => void)({ type: "set" }, at(2, 30));
|
|
74
|
+
});
|
|
75
|
+
fireEvent.click(result.getByRole("button", { name: "Done" }));
|
|
76
|
+
expect(onChange).toHaveBeenCalledWith(9000);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("keeps the typed segments when the binary has no picker module", () => {
|
|
80
|
+
picker.available = false;
|
|
81
|
+
const result = renderWithProviders(<Duration label="Duration" value={3720} />);
|
|
82
|
+
expect(result.queryByTestId("duration-wheel-trigger")).toBeNull();
|
|
83
|
+
expect(result.container.querySelector('input[aria-label="Hours"]')).toBeTruthy();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("keeps the typed segments for a field that shows seconds", () => {
|
|
87
|
+
const result = renderWithProviders(<Duration label="Duration" value={3725} showSeconds />);
|
|
88
|
+
expect(result.queryByTestId("duration-wheel-trigger")).toBeNull();
|
|
89
|
+
expect(result.container.querySelector('input[aria-label="Hours"]')).toBeTruthy();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("keeps the MPO chip presets under the wheel trigger", () => {
|
|
93
|
+
const result = renderWithProviders(<Duration label="Duration" value={0} />);
|
|
94
|
+
expect(result.container.querySelectorAll("[data-duration-preset] [data-mp-chip]")).toHaveLength(
|
|
95
|
+
6,
|
|
96
|
+
);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { sizeRecipeForToken } from "@multiplatform.one/theme";
|
|
2
|
+
import { ClockIcon } from "@phosphor-icons/react";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { Pressable as RNPressable } from "react-native";
|
|
5
|
+
import type { SizeTokens } from "tamagui";
|
|
6
|
+
import { Text } from "tamagui";
|
|
7
|
+
import { Input as InputParts } from "../../InputParts";
|
|
8
|
+
import { formCommonColors } from "../../shared/colorRamps";
|
|
9
|
+
import { t } from "../../shared/t";
|
|
10
|
+
import { loadDateTimePicker } from "../nativeDateTime/dateTimePickerLoader";
|
|
11
|
+
import { OsDateTimeSheet } from "../nativeDateTime/OsDateTimeSheet";
|
|
12
|
+
import {
|
|
13
|
+
durationToWheelDate,
|
|
14
|
+
formatDurationParts,
|
|
15
|
+
secondsToParts,
|
|
16
|
+
wheelDateToDuration,
|
|
17
|
+
} from "./durationMath";
|
|
18
|
+
import type { DurationWheelFieldProps } from "./durationWheelTypes";
|
|
19
|
+
|
|
20
|
+
export type { DurationWheelFieldProps };
|
|
21
|
+
|
|
22
|
+
export function durationWheelAvailable(): boolean {
|
|
23
|
+
return loadDateTimePicker() != null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function DurationWheelField({
|
|
27
|
+
seconds,
|
|
28
|
+
onChange,
|
|
29
|
+
disabled,
|
|
30
|
+
readOnly,
|
|
31
|
+
foldDays,
|
|
32
|
+
clockIcon,
|
|
33
|
+
hasError,
|
|
34
|
+
id,
|
|
35
|
+
hasLabel,
|
|
36
|
+
required,
|
|
37
|
+
size,
|
|
38
|
+
knobProps,
|
|
39
|
+
}: DurationWheelFieldProps) {
|
|
40
|
+
const [open, setOpen] = useState(false);
|
|
41
|
+
const sizeToken = String(size || knobProps.sizeToken);
|
|
42
|
+
const recipe = sizeRecipeForToken(sizeToken);
|
|
43
|
+
const resolvedClock = clockIcon === undefined ? <ClockIcon size={recipe.iconSize} /> : clockIcon;
|
|
44
|
+
const shown = formatDurationParts(secondsToParts(seconds, foldDays), foldDays, {
|
|
45
|
+
d: t("d"),
|
|
46
|
+
h: t("h"),
|
|
47
|
+
m: t("m"),
|
|
48
|
+
});
|
|
49
|
+
const interactive = !disabled && !readOnly;
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<InputParts size={size || knobProps.sizeToken} {...(hasError ? { theme: "error" } : undefined)}>
|
|
53
|
+
<RNPressable
|
|
54
|
+
nativeID={id}
|
|
55
|
+
onPress={interactive ? () => setOpen(true) : undefined}
|
|
56
|
+
disabled={!interactive}
|
|
57
|
+
accessibilityRole="button"
|
|
58
|
+
accessibilityLabel={hasLabel ? undefined : t("Duration")}
|
|
59
|
+
accessibilityValue={{ text: shown }}
|
|
60
|
+
accessibilityState={{ disabled: !!disabled }}
|
|
61
|
+
aria-required={required || undefined}
|
|
62
|
+
aria-invalid={hasError || undefined}
|
|
63
|
+
style={{ width: "100%" }}
|
|
64
|
+
testID="duration-wheel-trigger"
|
|
65
|
+
>
|
|
66
|
+
<InputParts.Box
|
|
67
|
+
disabled={disabled}
|
|
68
|
+
data-duration-box=""
|
|
69
|
+
data-duration-wheel=""
|
|
70
|
+
justifyContent="flex-start"
|
|
71
|
+
size={sizeToken as SizeTokens}
|
|
72
|
+
pointerEvents="none"
|
|
73
|
+
>
|
|
74
|
+
{resolvedClock != null ? (
|
|
75
|
+
<InputParts.Icon adornment="leading">{resolvedClock}</InputParts.Icon>
|
|
76
|
+
) : null}
|
|
77
|
+
<Text
|
|
78
|
+
{...knobProps.body}
|
|
79
|
+
{...knobProps.controlType}
|
|
80
|
+
fontSize={recipe.fontSize}
|
|
81
|
+
color={formCommonColors.text}
|
|
82
|
+
numberOfLines={1}
|
|
83
|
+
data-duration-value=""
|
|
84
|
+
>
|
|
85
|
+
{shown}
|
|
86
|
+
</Text>
|
|
87
|
+
</InputParts.Box>
|
|
88
|
+
</RNPressable>
|
|
89
|
+
<OsDateTimeSheet
|
|
90
|
+
open={open}
|
|
91
|
+
value={durationToWheelDate(seconds)}
|
|
92
|
+
mode="countdown"
|
|
93
|
+
onCancel={() => setOpen(false)}
|
|
94
|
+
onConfirm={(picked) => {
|
|
95
|
+
setOpen(false);
|
|
96
|
+
onChange(wheelDateToDuration(picked, seconds));
|
|
97
|
+
}}
|
|
98
|
+
/>
|
|
99
|
+
</InputParts>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { DurationWheelFieldProps } from "./durationWheelTypes";
|
|
2
|
+
|
|
3
|
+
export type { DurationWheelFieldProps };
|
|
4
|
+
|
|
5
|
+
export function durationWheelAvailable(): boolean {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function DurationWheelField(_props: DurationWheelFieldProps) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|