@multiplatform.one/forms 7.56.0 → 7.58.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/AdaptivePopup/index.cjs +5 -1
- package/dist/cjs/AdaptivePopup/index.native.js +6 -2
- package/dist/cjs/AdaptivePopup/index.native.js.map +1 -1
- package/dist/cjs/Button/index.cjs +2 -1
- package/dist/cjs/Button/index.native.js +4 -3
- package/dist/cjs/Button/index.native.js.map +1 -1
- package/dist/cjs/Chip/index.native.js.map +1 -1
- package/dist/cjs/ContextualSaveBar/UnsavedChangesDialog.native.js +1 -1
- package/dist/cjs/ContextualSaveBar/UnsavedChangesDialog.native.js.map +1 -1
- package/dist/cjs/ContextualSaveBar/clientKnownIdentity.native.js.map +1 -1
- package/dist/cjs/ContextualSaveBar/commitImmediateField.native.js.map +1 -1
- package/dist/cjs/ContextualSaveBar/index.native.js +3 -3
- package/dist/cjs/ContextualSaveBar/index.native.js.map +1 -1
- package/dist/cjs/ContextualSaveBar/useUnsavedChangesGuard.native.js.map +1 -1
- package/dist/cjs/ControlGroup/index.native.js +1 -1
- package/dist/cjs/ControlGroup/index.native.js.map +1 -1
- package/dist/cjs/DescriptionList/index.native.js +2 -2
- package/dist/cjs/DescriptionList/index.native.js.map +1 -1
- package/dist/cjs/ErrorSummary.native.js.map +1 -1
- package/dist/cjs/FieldDisplay/editMode.native.js.map +1 -1
- package/dist/cjs/FieldDisplay/formatters.native.js.map +1 -1
- package/dist/cjs/FieldDisplay/index.native.js +1 -1
- package/dist/cjs/FieldDisplay/index.native.js.map +1 -1
- package/dist/cjs/FieldGroup.native.js.map +1 -1
- package/dist/cjs/FloatingPanel/compactFace.cjs +38 -0
- package/dist/cjs/FloatingPanel/compactFace.native.js +42 -0
- package/dist/cjs/FloatingPanel/compactFace.native.js.map +1 -0
- package/dist/cjs/FloatingPanel/index.cjs +38 -9
- package/dist/cjs/FloatingPanel/index.native.js +154 -6
- package/dist/cjs/FloatingPanel/index.native.js.map +1 -1
- package/dist/cjs/FloatingPanel/nestedAction.native.js.map +1 -1
- package/dist/cjs/FloatingPanel/pointAnchor.cjs +22 -0
- package/dist/cjs/FloatingPanel/pointAnchor.native.js +23 -0
- package/dist/cjs/FloatingPanel/pointAnchor.native.js.map +1 -1
- package/dist/cjs/FloatingPanel/trailingAction.native.js +1 -1
- package/dist/cjs/FloatingPanel/trailingAction.native.js.map +1 -1
- package/dist/cjs/FloatingPanel/useFloatingPanel.native.js.map +1 -1
- package/dist/cjs/Form/index.native.js +1 -1
- package/dist/cjs/Form/index.native.js.map +1 -1
- package/dist/cjs/Form/submissionController.native.js.map +1 -1
- package/dist/cjs/Form/validateOn.native.js.map +1 -1
- package/dist/cjs/FormGrid.native.js.map +1 -1
- package/dist/cjs/FormSection.native.js.map +1 -1
- package/dist/cjs/InputParts/PasswordReveal.native.js.map +1 -1
- package/dist/cjs/InputParts/index.cjs +13 -7
- package/dist/cjs/InputParts/index.native.js +13 -7
- package/dist/cjs/InputParts/index.native.js.map +1 -1
- package/dist/cjs/Label/index.native.js.map +1 -1
- package/dist/cjs/MediaPicker/base64.native.js.map +1 -1
- package/dist/cjs/MediaPicker/payload.native.js.map +1 -1
- package/dist/cjs/MediaPicker/types.native.js.map +1 -1
- package/dist/cjs/MediaPicker/useMediaPicker.native.js.map +1 -1
- package/dist/cjs/Skeleton/index.native.js.map +1 -1
- package/dist/cjs/Spinner/SpinnerGlyph.native.js.map +1 -1
- package/dist/cjs/Spinner/spinnerSize.native.js.map +1 -1
- package/dist/cjs/Table/index.native.js.map +1 -1
- package/dist/cjs/Wheel/haptic.native.js.map +1 -1
- package/dist/cjs/Wheel/index.native.js.map +1 -1
- package/dist/cjs/Wheel/wheelShared.native.js.map +1 -1
- package/dist/cjs/actionLabel.native.js.map +1 -1
- package/dist/cjs/fieldDefaults.native.js.map +1 -1
- package/dist/cjs/fieldLayout.native.js +1 -1
- package/dist/cjs/fieldLayout.native.js.map +1 -1
- package/dist/cjs/fields/Barcode/BarcodePreview.native.js.map +1 -1
- package/dist/cjs/fields/Barcode/BarcodeScanner.native.js +1 -1
- package/dist/cjs/fields/Barcode/code128.native.js.map +1 -1
- package/dist/cjs/fields/Barcode/index.native.js +2 -2
- package/dist/cjs/fields/Barcode/index.native.js.map +1 -1
- package/dist/cjs/fields/Barcode/qr.native.js.map +1 -1
- package/dist/cjs/fields/Barcode/scannerTypes.native.js.map +1 -1
- package/dist/cjs/fields/Checkbox/Checkbox.cjs +24 -14
- package/dist/cjs/fields/Checkbox/Checkbox.native.js +39 -23
- package/dist/cjs/fields/Checkbox/Checkbox.native.js.map +1 -1
- package/dist/cjs/fields/Checkbox/CheckboxBox.native.js.map +1 -1
- package/dist/cjs/fields/Checkbox/CheckboxCard.native.js.map +1 -1
- package/dist/cjs/fields/Checkbox/CheckboxFieldFrame.native.js.map +1 -1
- package/dist/cjs/fields/Checkbox/CheckboxGroup.native.js +1 -1
- package/dist/cjs/fields/Checkbox/CheckboxGroup.native.js.map +1 -1
- package/dist/cjs/fields/Checkbox/checkboxGroupContext.native.js.map +1 -1
- package/dist/cjs/fields/Checkbox/index.native.js.map +1 -1
- package/dist/cjs/fields/ColorPicker/index.native.js +2 -2
- package/dist/cjs/fields/ColorPicker/index.native.js.map +1 -1
- package/dist/cjs/fields/ColorPicker/parts.native.js +2 -2
- package/dist/cjs/fields/ColorPicker/parts.native.js.map +1 -1
- package/dist/cjs/fields/Combobox/authorOptions.native.js.map +1 -1
- package/dist/cjs/fields/Combobox/index.native.js +5 -5
- package/dist/cjs/fields/Combobox/index.native.js.map +1 -1
- package/dist/cjs/fields/DatePicker/Calendar.native.js.map +1 -1
- package/dist/cjs/fields/DatePicker/DateRangePicker.native.js +2 -2
- package/dist/cjs/fields/DatePicker/DateRangePicker.native.js.map +1 -1
- package/dist/cjs/fields/DatePicker/DatetimePicker.catalog.native.js +3 -3
- package/dist/cjs/fields/DatePicker/DatetimePicker.catalog.native.js.map +1 -1
- package/dist/cjs/fields/DatePicker/DatetimePicker.native.js +2 -2
- package/dist/cjs/fields/DatePicker/MonthPicker.native.js +2 -2
- package/dist/cjs/fields/DatePicker/MonthPicker.native.js.map +1 -1
- package/dist/cjs/fields/DatePicker/MultiDatePicker.native.js +2 -2
- package/dist/cjs/fields/DatePicker/MultiDatePicker.native.js.map +1 -1
- package/dist/cjs/fields/DatePicker/catalog.native.js +2 -2
- package/dist/cjs/fields/DatePicker/catalog.native.js.map +1 -1
- package/dist/cjs/fields/DatePicker/fieldValueSync.native.js.map +1 -1
- package/dist/cjs/fields/DatePicker/index.native.js +2 -2
- package/dist/cjs/fields/DatePicker/parts.native.js +2 -2
- package/dist/cjs/fields/DatePicker/parts.native.js.map +1 -1
- package/dist/cjs/fields/DatePicker/utils.native.js.map +1 -1
- package/dist/cjs/fields/Duration/durationMath.native.js.map +1 -1
- package/dist/cjs/fields/Duration/index.native.js +3 -3
- package/dist/cjs/fields/Duration/index.native.js.map +1 -1
- package/dist/cjs/fields/FileUpload/filePickerTypes.native.js.map +1 -1
- package/dist/cjs/fields/FileUpload/index.native.js +2 -2
- package/dist/cjs/fields/FileUpload/index.native.js.map +1 -1
- package/dist/cjs/fields/FileUpload/nativePickerLoader.native.js.map +1 -1
- package/dist/cjs/fields/Geolocation/expoMapsLoader.native.js.map +1 -1
- package/dist/cjs/fields/Geolocation/geoValue.native.js.map +1 -1
- package/dist/cjs/fields/Geolocation/geocoder.native.js.map +1 -1
- package/dist/cjs/fields/Geolocation/geocoderContext.native.js.map +1 -1
- package/dist/cjs/fields/Geolocation/index.native.js +5 -5
- package/dist/cjs/fields/Geolocation/index.native.js.map +1 -1
- package/dist/cjs/fields/Geolocation/mapMath.native.js.map +1 -1
- package/dist/cjs/fields/Geolocation/mapTypes.native.js.map +1 -1
- package/dist/cjs/fields/Geolocation/nativeSearchFocus.native.js.map +1 -1
- package/dist/cjs/fields/Geolocation/photonGeocoder.native.js.map +1 -1
- package/dist/cjs/fields/Icon/drawnSet.native.js.map +1 -1
- package/dist/cjs/fields/Icon/index.native.js +3 -3
- package/dist/cjs/fields/Icon/index.native.js.map +1 -1
- package/dist/cjs/fields/ImageField/index.native.js +2 -2
- package/dist/cjs/fields/ImageField/index.native.js.map +1 -1
- package/dist/cjs/fields/Input/index.native.js +3 -3
- package/dist/cjs/fields/Input/index.native.js.map +1 -1
- package/dist/cjs/fields/MentionInput/index.native.js +3 -3
- package/dist/cjs/fields/MentionInput/index.native.js.map +1 -1
- package/dist/cjs/fields/OTPInput/index.native.js +3 -3
- package/dist/cjs/fields/OTPInput/index.native.js.map +1 -1
- package/dist/cjs/fields/OTPInput/otpCellCss.native.js.map +1 -1
- package/dist/cjs/fields/PhoneInput/dialCodes.native.js.map +1 -1
- package/dist/cjs/fields/PhoneInput/index.native.js +3 -3
- package/dist/cjs/fields/PhoneInput/index.native.js.map +1 -1
- package/dist/cjs/fields/PhoneInput/phone.native.js.map +1 -1
- package/dist/cjs/fields/PhoneInput/regionNames.native.js.map +1 -1
- package/dist/cjs/fields/Progress/index.native.js +1 -1
- package/dist/cjs/fields/Progress/index.native.js.map +1 -1
- package/dist/cjs/fields/RadioGroup/index.cjs +6 -1
- package/dist/cjs/fields/RadioGroup/index.native.js +7 -2
- package/dist/cjs/fields/RadioGroup/index.native.js.map +1 -1
- package/dist/cjs/fields/Rating/index.native.js +1 -1
- package/dist/cjs/fields/Rating/index.native.js.map +1 -1
- package/dist/cjs/fields/Rating/ratingMath.native.js.map +1 -1
- package/dist/cjs/fields/Rating/types.native.js.map +1 -1
- package/dist/cjs/fields/SearchInput/index.native.js +2 -2
- package/dist/cjs/fields/SearchInput/index.native.js.map +1 -1
- package/dist/cjs/fields/Select/index.cjs +13 -2
- package/dist/cjs/fields/Select/index.native.js +26 -7
- package/dist/cjs/fields/Select/index.native.js.map +1 -1
- package/dist/cjs/fields/Signature/index.native.js +2 -2
- package/dist/cjs/fields/Signature/index.native.js.map +1 -1
- package/dist/cjs/fields/Slider/index.native.js +1 -1
- package/dist/cjs/fields/Slider/index.native.js.map +1 -1
- package/dist/cjs/fields/Slider/jumpMotion.native.js.map +1 -1
- package/dist/cjs/fields/Stepper/index.native.js +2 -2
- package/dist/cjs/fields/Stepper/index.native.js.map +1 -1
- package/dist/cjs/fields/Stepper/resolveStepperPlacement.native.js.map +1 -1
- package/dist/cjs/fields/Switch/footprint.native.js.map +1 -1
- package/dist/cjs/fields/Switch/index.cjs +22 -10
- package/dist/cjs/fields/Switch/index.native.js +30 -16
- package/dist/cjs/fields/Switch/index.native.js.map +1 -1
- package/dist/cjs/fields/TextArea/index.native.js +2 -2
- package/dist/cjs/fields/TextArea/index.native.js.map +1 -1
- package/dist/cjs/fields/TimePicker/catalog.native.js +3 -3
- package/dist/cjs/fields/TimePicker/catalog.native.js.map +1 -1
- package/dist/cjs/fields/TimePicker/index.native.js +2 -2
- package/dist/cjs/fields/TimePicker/parts.native.js +1 -1
- package/dist/cjs/fields/TimePicker/parts.native.js.map +1 -1
- package/dist/cjs/fields/TimeRangeScrubber/geometry.native.js.map +1 -1
- package/dist/cjs/fields/TimeRangeScrubber/index.native.js +2 -2
- package/dist/cjs/fields/TimeRangeScrubber/index.native.js.map +1 -1
- package/dist/cjs/fields/ToggleGroup/index.native.js +1 -1
- package/dist/cjs/fields/ToggleGroup/index.native.js.map +1 -1
- package/dist/cjs/fields/nativeDateTime/OsDateTimeSheet.native.js.map +1 -1
- package/dist/cjs/fields/nativeDateTime/dateTimePickerLoader.native.js.map +1 -1
- package/dist/cjs/fields/nativeDateTime/osDateTimeIosDisplay.native.js.map +1 -1
- package/dist/cjs/fields/nativeSelect.native.js.map +1 -1
- package/dist/cjs/fields/selectedOrder.native.js.map +1 -1
- package/dist/cjs/formSpacing.native.js.map +1 -1
- package/dist/cjs/hooks/useFocusManagement.native.js.map +1 -1
- package/dist/cjs/index.native.js +45 -45
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/cjs/keyboard/KeyboardDoneBar.native.js +1 -1
- package/dist/cjs/keyboard/chain.native.js.map +1 -1
- package/dist/cjs/keyboard/index.native.js.map +1 -1
- package/dist/cjs/keyboard/types.native.js.map +1 -1
- package/dist/cjs/requiredMarking.native.js.map +1 -1
- package/dist/cjs/shared/TimeColumn.native.js +1 -1
- package/dist/cjs/shared/TimeColumn.native.js.map +1 -1
- package/dist/cjs/shared/capabilityPreflight.native.js.map +1 -1
- package/dist/cjs/shared/choiceRowPress.cjs +46 -0
- package/dist/cjs/shared/choiceRowPress.native.js +50 -0
- package/dist/cjs/shared/choiceRowPress.native.js.map +1 -0
- package/dist/cjs/shared/colorRamps.native.js.map +1 -1
- package/dist/cjs/shared/devWarn.native.js.map +1 -1
- package/dist/cjs/shared/floatingList.native.js +1 -1
- package/dist/cjs/shared/floatingList.native.js.map +1 -1
- package/dist/cjs/shared/focusManagement.native.js.map +1 -1
- package/dist/cjs/shared/groupContext.native.js.map +1 -1
- package/dist/cjs/shared/i18n.native.js.map +1 -1
- package/dist/cjs/shared/instantMotion.native.js.map +1 -1
- package/dist/cjs/shared/nativeA11y.native.js.map +1 -1
- package/dist/cjs/shared/nativeAutoFocus.native.js.map +1 -1
- package/dist/cjs/shared/overlayStack.cjs +4 -0
- package/dist/cjs/shared/overlayStack.native.js +4 -0
- package/dist/cjs/shared/overlayStack.native.js.map +1 -1
- package/dist/cjs/shared/panelPortal.native.js.map +1 -1
- package/dist/cjs/shared/pickerTriggerContract.native.js.map +1 -1
- package/dist/cjs/shared/pressSlopProps.native.js.map +1 -1
- package/dist/cjs/shared/restoreRetries.native.js.map +1 -1
- package/dist/cjs/shared/t.native.js.map +1 -1
- package/dist/cjs/shared/tableCellContext.native.js.map +1 -1
- package/dist/cjs/shared/useDragScroll.native.js.map +1 -1
- package/dist/cjs/shared/useKeyboardInset.native.js.map +1 -1
- package/dist/cjs/shared/useSheetEscapeDismiss.native.js.map +1 -1
- package/dist/cjs/shared/useWheelIncrement.native.js.map +1 -1
- package/dist/cjs/shared/utils.native.js +1 -1
- package/dist/cjs/shared/utils.native.js.map +1 -1
- package/dist/cjs/shared/zIndex.native.js.map +1 -1
- package/dist/cjs/themedPrimitives.native.js.map +1 -1
- package/dist/cjs/types.native.js.map +1 -1
- package/dist/cjs/utils/LinkCell.native.js.map +1 -1
- package/dist/esm/AdaptivePopup/index.mjs +6 -2
- package/dist/esm/AdaptivePopup/index.mjs.map +1 -1
- package/dist/esm/AdaptivePopup/index.native.js +6 -2
- package/dist/esm/AdaptivePopup/index.native.js.map +1 -1
- package/dist/esm/Button/index.mjs +3 -2
- package/dist/esm/Button/index.mjs.map +1 -1
- package/dist/esm/Button/index.native.js +3 -2
- package/dist/esm/Button/index.native.js.map +1 -1
- package/dist/esm/FloatingPanel/compactFace.mjs +11 -0
- package/dist/esm/FloatingPanel/compactFace.mjs.map +1 -0
- package/dist/esm/FloatingPanel/compactFace.native.js +12 -0
- package/dist/esm/FloatingPanel/compactFace.native.js.map +1 -0
- package/dist/esm/FloatingPanel/index.mjs +40 -11
- package/dist/esm/FloatingPanel/index.mjs.map +1 -1
- package/dist/esm/FloatingPanel/index.native.js +156 -8
- package/dist/esm/FloatingPanel/index.native.js.map +1 -1
- package/dist/esm/FloatingPanel/pointAnchor.mjs +22 -1
- package/dist/esm/FloatingPanel/pointAnchor.mjs.map +1 -1
- package/dist/esm/FloatingPanel/pointAnchor.native.js +23 -1
- package/dist/esm/FloatingPanel/pointAnchor.native.js.map +1 -1
- package/dist/esm/InputParts/index.mjs +13 -7
- package/dist/esm/InputParts/index.mjs.map +1 -1
- package/dist/esm/InputParts/index.native.js +13 -7
- package/dist/esm/InputParts/index.native.js.map +1 -1
- package/dist/esm/fields/Checkbox/Checkbox.mjs +24 -14
- package/dist/esm/fields/Checkbox/Checkbox.mjs.map +1 -1
- package/dist/esm/fields/Checkbox/Checkbox.native.js +38 -22
- package/dist/esm/fields/Checkbox/Checkbox.native.js.map +1 -1
- package/dist/esm/fields/RadioGroup/index.mjs +6 -1
- package/dist/esm/fields/RadioGroup/index.mjs.map +1 -1
- package/dist/esm/fields/RadioGroup/index.native.js +6 -1
- package/dist/esm/fields/RadioGroup/index.native.js.map +1 -1
- package/dist/esm/fields/Select/index.mjs +15 -4
- package/dist/esm/fields/Select/index.mjs.map +1 -1
- package/dist/esm/fields/Select/index.native.js +24 -5
- package/dist/esm/fields/Select/index.native.js.map +1 -1
- package/dist/esm/fields/Switch/index.mjs +22 -10
- package/dist/esm/fields/Switch/index.mjs.map +1 -1
- package/dist/esm/fields/Switch/index.native.js +28 -14
- package/dist/esm/fields/Switch/index.native.js.map +1 -1
- package/dist/esm/shared/choiceRowPress.mjs +20 -0
- package/dist/esm/shared/choiceRowPress.mjs.map +1 -0
- package/dist/esm/shared/choiceRowPress.native.js +21 -0
- package/dist/esm/shared/choiceRowPress.native.js.map +1 -0
- package/dist/esm/shared/overlayStack.mjs +4 -1
- package/dist/esm/shared/overlayStack.mjs.map +1 -1
- package/dist/esm/shared/overlayStack.native.js +4 -1
- package/dist/esm/shared/overlayStack.native.js.map +1 -1
- package/dist/jsx/AdaptivePopup/index.mjs +6 -2
- package/dist/jsx/AdaptivePopup/index.mjs.map +1 -1
- package/dist/jsx/AdaptivePopup/index.native.js +5 -1
- package/dist/jsx/AdaptivePopup/index.native.js.map +1 -1
- package/dist/jsx/Button/index.mjs +3 -2
- package/dist/jsx/Button/index.mjs.map +1 -1
- package/dist/jsx/Button/index.native.js +2 -1
- package/dist/jsx/Button/index.native.js.map +1 -1
- package/dist/jsx/FloatingPanel/compactFace.mjs +11 -0
- package/dist/jsx/FloatingPanel/compactFace.mjs.map +1 -0
- package/dist/jsx/FloatingPanel/compactFace.native.js +42 -0
- package/dist/jsx/FloatingPanel/compactFace.native.js.map +1 -0
- package/dist/jsx/FloatingPanel/index.mjs +40 -11
- package/dist/jsx/FloatingPanel/index.mjs.map +1 -1
- package/dist/jsx/FloatingPanel/index.native.js +154 -6
- package/dist/jsx/FloatingPanel/index.native.js.map +1 -1
- package/dist/jsx/FloatingPanel/pointAnchor.mjs +22 -1
- package/dist/jsx/FloatingPanel/pointAnchor.mjs.map +1 -1
- package/dist/jsx/FloatingPanel/pointAnchor.native.js +23 -0
- package/dist/jsx/FloatingPanel/pointAnchor.native.js.map +1 -1
- package/dist/jsx/InputParts/index.mjs +13 -7
- package/dist/jsx/InputParts/index.mjs.map +1 -1
- package/dist/jsx/InputParts/index.native.js +13 -7
- package/dist/jsx/InputParts/index.native.js.map +1 -1
- package/dist/jsx/fields/Checkbox/Checkbox.mjs +24 -14
- package/dist/jsx/fields/Checkbox/Checkbox.mjs.map +1 -1
- package/dist/jsx/fields/Checkbox/Checkbox.native.js +38 -22
- package/dist/jsx/fields/Checkbox/Checkbox.native.js.map +1 -1
- package/dist/jsx/fields/RadioGroup/index.mjs +6 -1
- package/dist/jsx/fields/RadioGroup/index.mjs.map +1 -1
- package/dist/jsx/fields/RadioGroup/index.native.js +6 -1
- package/dist/jsx/fields/RadioGroup/index.native.js.map +1 -1
- package/dist/jsx/fields/Select/index.mjs +15 -4
- package/dist/jsx/fields/Select/index.mjs.map +1 -1
- package/dist/jsx/fields/Select/index.native.js +22 -3
- package/dist/jsx/fields/Select/index.native.js.map +1 -1
- package/dist/jsx/fields/Switch/index.mjs +22 -10
- package/dist/jsx/fields/Switch/index.mjs.map +1 -1
- package/dist/jsx/fields/Switch/index.native.js +28 -14
- package/dist/jsx/fields/Switch/index.native.js.map +1 -1
- package/dist/jsx/index.native.js.map +1 -1
- package/dist/jsx/shared/choiceRowPress.mjs +20 -0
- package/dist/jsx/shared/choiceRowPress.mjs.map +1 -0
- package/dist/jsx/shared/choiceRowPress.native.js +50 -0
- package/dist/jsx/shared/choiceRowPress.native.js.map +1 -0
- package/dist/jsx/shared/overlayStack.mjs +4 -1
- package/dist/jsx/shared/overlayStack.mjs.map +1 -1
- package/dist/jsx/shared/overlayStack.native.js +4 -0
- package/dist/jsx/shared/overlayStack.native.js.map +1 -1
- package/package.json +8 -8
- package/src/AdaptivePopup/index.tsx +6 -1
- package/src/Button/Button.controlTiers.spec.tsx +29 -0
- package/src/Button/index.tsx +3 -1
- package/src/FloatingPanel/FloatingPanel.compactFace.spec.tsx +187 -0
- package/src/FloatingPanel/FloatingPanel.menuFace.native.spec.tsx +183 -0
- package/src/FloatingPanel/compactFace.spec.ts +40 -0
- package/src/FloatingPanel/compactFace.ts +32 -0
- package/src/FloatingPanel/index.native.tsx +171 -4
- package/src/FloatingPanel/index.tsx +55 -11
- package/src/FloatingPanel/pointAnchor.spec.ts +80 -0
- package/src/FloatingPanel/pointAnchor.ts +52 -0
- package/src/InputParts/index.tsx +18 -7
- package/src/fields/Checkbox/Checkbox.native.spec.tsx +44 -0
- package/src/fields/Checkbox/Checkbox.tsx +31 -18
- package/src/fields/DatePicker/columnFit.spec.tsx +85 -0
- package/src/fields/RadioGroup/RadioGroup.native.spec.tsx +65 -0
- package/src/fields/RadioGroup/RadioGroup.stories.tsx +49 -0
- package/src/fields/RadioGroup/index.tsx +14 -2
- package/src/fields/Select/index.tsx +36 -6
- package/src/fields/Switch/Switch.rowPress.native.spec.tsx +69 -0
- package/src/fields/Switch/index.tsx +29 -11
- package/src/fields/pickerRows.spec.tsx +2 -0
- package/src/overlayGround.spec.ts +181 -0
- package/src/shared/choiceRowPress.ts +40 -0
- package/src/shared/overlayStack.tsx +5 -0
- package/types/AdaptivePopup/index.d.ts.map +1 -1
- package/types/Button/index.d.ts.map +1 -1
- package/types/FloatingPanel/compactFace.d.ts +21 -0
- package/types/FloatingPanel/compactFace.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 +8 -1
- package/types/FloatingPanel/index.native.d.ts.map +1 -1
- package/types/FloatingPanel/pointAnchor.d.ts +29 -0
- package/types/FloatingPanel/pointAnchor.d.ts.map +1 -1
- package/types/InputParts/index.d.ts.map +1 -1
- package/types/fields/Checkbox/Checkbox.d.ts.map +1 -1
- package/types/fields/RadioGroup/index.d.ts.map +1 -1
- package/types/fields/Select/index.d.ts.map +1 -1
- package/types/fields/Switch/index.d.ts.map +1 -1
- package/types/shared/choiceRowPress.d.ts +40 -0
- package/types/shared/choiceRowPress.d.ts.map +1 -0
- package/types/shared/overlayStack.d.ts +2 -0
- package/types/shared/overlayStack.d.ts.map +1 -1
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FZ-7830: on a phone-width web a menu opened inside another overlay opens
|
|
3
|
+
* over its trigger, never as a second bottom sheet; at page level it keeps the
|
|
4
|
+
* sheet. Inside an overlay a collision slides the menu back on screen instead
|
|
5
|
+
* of taking LC-139's collision sheet.
|
|
6
|
+
*/
|
|
7
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
8
|
+
import { fireEvent, waitFor } from "@testing-library/react";
|
|
9
|
+
import { renderWithProviders } from "@multiplatform.one/test-utils";
|
|
10
|
+
import { useState } from "react";
|
|
11
|
+
import { Button } from "../Button";
|
|
12
|
+
import { OverlayStackProvider } from "../shared/overlayStack";
|
|
13
|
+
import type { CompactFace } from "./compactFace";
|
|
14
|
+
import { FloatingPanel } from "./index";
|
|
15
|
+
import { panelViewportPadding } from "./useFloatingPanel";
|
|
16
|
+
|
|
17
|
+
const phone = { width: 390, height: 844 };
|
|
18
|
+
const triggerBounds = {
|
|
19
|
+
x: 20,
|
|
20
|
+
y: 240,
|
|
21
|
+
top: 240,
|
|
22
|
+
bottom: 284,
|
|
23
|
+
left: 20,
|
|
24
|
+
right: 140,
|
|
25
|
+
width: 120,
|
|
26
|
+
height: 44,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
let originalWidth = 0;
|
|
30
|
+
let originalHeight = 0;
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
originalWidth = window.innerWidth;
|
|
33
|
+
originalHeight = window.innerHeight;
|
|
34
|
+
Object.defineProperty(window, "innerWidth", {
|
|
35
|
+
configurable: true,
|
|
36
|
+
writable: true,
|
|
37
|
+
value: phone.width,
|
|
38
|
+
});
|
|
39
|
+
Object.defineProperty(window, "innerHeight", {
|
|
40
|
+
configurable: true,
|
|
41
|
+
writable: true,
|
|
42
|
+
value: phone.height,
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
afterEach(() => {
|
|
46
|
+
vi.restoreAllMocks();
|
|
47
|
+
Object.defineProperty(window, "innerWidth", {
|
|
48
|
+
configurable: true,
|
|
49
|
+
writable: true,
|
|
50
|
+
value: originalWidth,
|
|
51
|
+
});
|
|
52
|
+
Object.defineProperty(window, "innerHeight", {
|
|
53
|
+
configurable: true,
|
|
54
|
+
writable: true,
|
|
55
|
+
value: originalHeight,
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
function measure(bounds = triggerBounds, panelWidth = 200, panelHeight = 180) {
|
|
60
|
+
const original = HTMLElement.prototype.getBoundingClientRect;
|
|
61
|
+
vi.spyOn(HTMLElement.prototype, "getBoundingClientRect").mockImplementation(
|
|
62
|
+
function (this: HTMLElement) {
|
|
63
|
+
if (this.hasAttribute("data-fp-reference")) return { ...bounds, toJSON: () => ({}) };
|
|
64
|
+
return original.call(this);
|
|
65
|
+
},
|
|
66
|
+
);
|
|
67
|
+
vi.spyOn(HTMLElement.prototype, "offsetWidth", "get").mockImplementation(
|
|
68
|
+
function (this: HTMLElement) {
|
|
69
|
+
return this.hasAttribute("data-fp-panel") ? panelWidth : 0;
|
|
70
|
+
},
|
|
71
|
+
);
|
|
72
|
+
vi.spyOn(HTMLElement.prototype, "scrollHeight", "get").mockImplementation(
|
|
73
|
+
function (this: HTMLElement) {
|
|
74
|
+
return this.hasAttribute("data-fp-panel") ? panelHeight : 0;
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function Menu({ face }: { face?: CompactFace }) {
|
|
80
|
+
const [open, setOpen] = useState(false);
|
|
81
|
+
return (
|
|
82
|
+
<FloatingPanel
|
|
83
|
+
open={open}
|
|
84
|
+
onOpenChange={setOpen}
|
|
85
|
+
compactFace={face}
|
|
86
|
+
triggerSizing="content"
|
|
87
|
+
trigger={<Button aria-label="Stack actions">⋯</Button>}
|
|
88
|
+
>
|
|
89
|
+
<Button>Stop</Button>
|
|
90
|
+
<Button>Restart</Button>
|
|
91
|
+
</FloatingPanel>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** What a SheetModal, Dialog or AdaptivePopup puts around its content. */
|
|
96
|
+
function InSheet({ children }: { children: React.ReactNode }) {
|
|
97
|
+
return <OverlayStackProvider zIndex={100_000}>{children}</OverlayStackProvider>;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function open(result: ReturnType<typeof renderWithProviders>) {
|
|
101
|
+
fireEvent.click(result.getByRole("button", { name: "Stack actions" }), { detail: 0 });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
describe("FloatingPanel compactFace on a 390pt web viewport (FZ-7830)", () => {
|
|
105
|
+
it("keeps the bottom sheet at page level for the auto face", async () => {
|
|
106
|
+
measure();
|
|
107
|
+
const result = renderWithProviders(<Menu face="auto" />);
|
|
108
|
+
open(result);
|
|
109
|
+
await result.findByTestId("floating-panel-sheet");
|
|
110
|
+
expect(result.queryByTestId("floating-panel-viewport")).toBeNull();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("opens over the trigger inside an overlay for the auto face, never a second sheet", async () => {
|
|
114
|
+
measure();
|
|
115
|
+
const result = renderWithProviders(
|
|
116
|
+
<InSheet>
|
|
117
|
+
<Menu face="auto" />
|
|
118
|
+
</InSheet>,
|
|
119
|
+
);
|
|
120
|
+
open(result);
|
|
121
|
+
const panel = await result.findByTestId("floating-panel-viewport");
|
|
122
|
+
expect(result.queryByTestId("floating-panel-sheet")).toBeNull();
|
|
123
|
+
await waitFor(() => expect(panel.style.top).toBe("240px"));
|
|
124
|
+
expect(panel.style.left).toBe("20px");
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("opens over the trigger at page level for the menu face", async () => {
|
|
128
|
+
measure();
|
|
129
|
+
const result = renderWithProviders(<Menu face="menu" />);
|
|
130
|
+
open(result);
|
|
131
|
+
const panel = await result.findByTestId("floating-panel-viewport");
|
|
132
|
+
expect(result.queryByTestId("floating-panel-sheet")).toBeNull();
|
|
133
|
+
await waitFor(() => expect(panel.style.top).toBe("240px"));
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("leaves a panel without a face as a sheet, even inside an overlay", async () => {
|
|
137
|
+
measure();
|
|
138
|
+
const result = renderWithProviders(
|
|
139
|
+
<InSheet>
|
|
140
|
+
<Menu />
|
|
141
|
+
</InSheet>,
|
|
142
|
+
);
|
|
143
|
+
open(result);
|
|
144
|
+
await result.findByTestId("floating-panel-sheet");
|
|
145
|
+
expect(result.queryByTestId("floating-panel-viewport")).toBeNull();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("slides a trailing ⋯ menu back inside the end edge instead of taking a collision sheet", async () => {
|
|
149
|
+
measure({ ...triggerBounds, x: 330, left: 330, right: 374, width: 44 });
|
|
150
|
+
const result = renderWithProviders(
|
|
151
|
+
<InSheet>
|
|
152
|
+
<Menu face="auto" />
|
|
153
|
+
</InSheet>,
|
|
154
|
+
);
|
|
155
|
+
open(result);
|
|
156
|
+
const panel = await result.findByTestId("floating-panel-viewport");
|
|
157
|
+
expect(result.queryByTestId("floating-panel-sheet")).toBeNull();
|
|
158
|
+
await waitFor(() => expect(parseFloat(panel.style.left)).toBeLessThan(330));
|
|
159
|
+
expect(parseFloat(panel.style.left) + 200).toBeLessThanOrEqual(
|
|
160
|
+
phone.width - panelViewportPadding,
|
|
161
|
+
);
|
|
162
|
+
expect(panel.style.top).toBe("240px");
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("slides up from the bottom edge instead of taking a collision sheet", async () => {
|
|
166
|
+
measure({ ...triggerBounds, y: 800, top: 800, bottom: 844 });
|
|
167
|
+
const result = renderWithProviders(
|
|
168
|
+
<InSheet>
|
|
169
|
+
<Menu face="auto" />
|
|
170
|
+
</InSheet>,
|
|
171
|
+
);
|
|
172
|
+
open(result);
|
|
173
|
+
const panel = await result.findByTestId("floating-panel-viewport");
|
|
174
|
+
expect(result.queryByTestId("floating-panel-sheet")).toBeNull();
|
|
175
|
+
await waitFor(() => expect(parseFloat(panel.style.top)).toBeLessThan(800));
|
|
176
|
+
expect(parseFloat(panel.style.top) + 180).toBeLessThanOrEqual(
|
|
177
|
+
phone.height - panelViewportPadding,
|
|
178
|
+
);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("still takes LC-139's collision sheet at page level", async () => {
|
|
182
|
+
measure({ ...triggerBounds, y: 800, top: 800, bottom: 844 });
|
|
183
|
+
const result = renderWithProviders(<Menu face="menu" />);
|
|
184
|
+
open(result);
|
|
185
|
+
await result.findByTestId("floating-panel-sheet");
|
|
186
|
+
});
|
|
187
|
+
});
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FZ-7830: on native a menu-faced FloatingPanel opens over its trigger (LC-112,
|
|
3
|
+
* the UIMenu pull-down) instead of as a bottom sheet, and an `auto` panel does
|
|
4
|
+
* so only inside another overlay, so a sheet never stacks on a sheet.
|
|
5
|
+
*/
|
|
6
|
+
import { renderWithProviders } from "@multiplatform.one/test-utils";
|
|
7
|
+
import { act } from "@testing-library/react";
|
|
8
|
+
import { useState, type ReactNode } from "react";
|
|
9
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
10
|
+
import { Text } from "tamagui";
|
|
11
|
+
import { OverlayStackProvider } from "../shared/overlayStack";
|
|
12
|
+
import type { CompactFace } from "./compactFace";
|
|
13
|
+
import { FloatingPanel } from "./index.native";
|
|
14
|
+
import { panelViewportPadding } from "./useFloatingPanel";
|
|
15
|
+
|
|
16
|
+
vi.mock("react-native-web", async (importOriginal) => {
|
|
17
|
+
const actual = await importOriginal<typeof import("react-native")>();
|
|
18
|
+
return {
|
|
19
|
+
...actual,
|
|
20
|
+
ActionSheetIOS: undefined,
|
|
21
|
+
Modal: ({ children, visible }: { children: ReactNode; visible: boolean }) =>
|
|
22
|
+
visible ? children : null,
|
|
23
|
+
useWindowDimensions: () => ({ width: 390, height: 844, scale: 3, fontScale: 1 }),
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
let trigger = { x: 20, y: 240, width: 120, height: 44 };
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
vi.useFakeTimers();
|
|
30
|
+
trigger = { x: 20, y: 240, width: 120, height: 44 };
|
|
31
|
+
// RN-web's measureInWindow reads the node's client rect on a timer.
|
|
32
|
+
vi.spyOn(HTMLElement.prototype, "getBoundingClientRect").mockImplementation(() => ({
|
|
33
|
+
x: trigger.x,
|
|
34
|
+
y: trigger.y,
|
|
35
|
+
left: trigger.x,
|
|
36
|
+
top: trigger.y,
|
|
37
|
+
width: trigger.width,
|
|
38
|
+
height: trigger.height,
|
|
39
|
+
right: trigger.x + trigger.width,
|
|
40
|
+
bottom: trigger.y + trigger.height,
|
|
41
|
+
toJSON: () => ({}),
|
|
42
|
+
}));
|
|
43
|
+
});
|
|
44
|
+
afterEach(() => {
|
|
45
|
+
vi.restoreAllMocks();
|
|
46
|
+
vi.useRealTimers();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
function Menu({ face }: { face?: CompactFace }) {
|
|
50
|
+
const [open, setOpen] = useState(false);
|
|
51
|
+
return (
|
|
52
|
+
<FloatingPanel
|
|
53
|
+
open={open}
|
|
54
|
+
onOpenChange={setOpen}
|
|
55
|
+
compactFace={face}
|
|
56
|
+
triggerSizing="content"
|
|
57
|
+
contentPadding="none"
|
|
58
|
+
trigger={<Text>Stack actions</Text>}
|
|
59
|
+
triggerA11y={{ label: "Stack actions" }}
|
|
60
|
+
>
|
|
61
|
+
<Text>Stop</Text>
|
|
62
|
+
<Text>Restart</Text>
|
|
63
|
+
</FloatingPanel>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** What a native SheetModal's OverlayWindow puts around its content. */
|
|
68
|
+
function InSheet({ children }: { children: ReactNode }) {
|
|
69
|
+
return <OverlayStackProvider zIndex={100_000}>{children}</OverlayStackProvider>;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function tap(target: Element) {
|
|
73
|
+
act(() => {
|
|
74
|
+
target.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, button: 0 }));
|
|
75
|
+
target.dispatchEvent(new MouseEvent("mouseup", { bubbles: true, button: 0 }));
|
|
76
|
+
target.dispatchEvent(new MouseEvent("click", { bubbles: true, button: 0 }));
|
|
77
|
+
});
|
|
78
|
+
// measureInWindow answers on the next tick.
|
|
79
|
+
act(() => {
|
|
80
|
+
vi.advanceTimersByTime(1);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** RN-web parks onLayout on the node for a ResizeObserver happy-dom never fires. */
|
|
85
|
+
function layout(node: Element, width: number, height: number) {
|
|
86
|
+
const onLayout = (node as unknown as { __reactLayoutHandler?: (event: unknown) => void })
|
|
87
|
+
.__reactLayoutHandler;
|
|
88
|
+
expect(onLayout).toBeTypeOf("function");
|
|
89
|
+
act(() => {
|
|
90
|
+
onLayout?.({ nativeEvent: { layout: { x: 0, y: 0, width, height } } });
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const menu = () => document.querySelector<HTMLElement>('[data-testid="floating-panel-menu"]');
|
|
95
|
+
const sheet = () => document.querySelector('[data-testid="floating-panel-sheet"]');
|
|
96
|
+
const echo = () => document.querySelector('[data-testid="floating-panel-menu-echo"]');
|
|
97
|
+
|
|
98
|
+
describe("FloatingPanel native menu face (FZ-7830)", () => {
|
|
99
|
+
it("opens the menu face over the trigger at page level, never a sheet", () => {
|
|
100
|
+
const result = renderWithProviders(<Menu face="menu" />);
|
|
101
|
+
tap(result.getByRole("button", { name: "Stack actions" }));
|
|
102
|
+
const panel = menu();
|
|
103
|
+
expect(panel).not.toBeNull();
|
|
104
|
+
expect(sheet()).toBeNull();
|
|
105
|
+
expect(panel?.style.opacity).toBe("0");
|
|
106
|
+
layout(panel as HTMLElement, 200, 180);
|
|
107
|
+
expect(panel?.style.left).toBe("20px");
|
|
108
|
+
expect(panel?.style.top).toBe("240px");
|
|
109
|
+
expect(panel?.style.opacity).toBe("1");
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("repeats the trigger as the first row, in the trigger's own box", () => {
|
|
113
|
+
const result = renderWithProviders(<Menu face="menu" />);
|
|
114
|
+
tap(result.getByRole("button", { name: "Stack actions" }));
|
|
115
|
+
const row = echo() as HTMLElement;
|
|
116
|
+
expect(row).not.toBeNull();
|
|
117
|
+
expect(row.textContent).toBe("Stack actions");
|
|
118
|
+
expect(row.style.height).toBe("44px");
|
|
119
|
+
expect(row.getAttribute("aria-expanded")).toBe("true");
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("closes when the repeated trigger row is pressed", () => {
|
|
123
|
+
const result = renderWithProviders(<Menu face="menu" />);
|
|
124
|
+
tap(result.getByRole("button", { name: "Stack actions" }));
|
|
125
|
+
act(() => {
|
|
126
|
+
vi.advanceTimersByTime(1000);
|
|
127
|
+
});
|
|
128
|
+
tap(echo() as HTMLElement);
|
|
129
|
+
expect(menu()).toBeNull();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("slides a trailing ⋯ menu back inside the padded window", () => {
|
|
133
|
+
trigger = { x: 330, y: 240, width: 44, height: 44 };
|
|
134
|
+
const result = renderWithProviders(<Menu face="menu" />);
|
|
135
|
+
tap(result.getByRole("button", { name: "Stack actions" }));
|
|
136
|
+
const panel = menu() as HTMLElement;
|
|
137
|
+
layout(panel, 200, 180);
|
|
138
|
+
expect(panel.style.left).toBe(`${390 - panelViewportPadding - 200}px`);
|
|
139
|
+
expect(panel.style.top).toBe("240px");
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("slides up from the bottom edge instead of running under it", () => {
|
|
143
|
+
trigger = { x: 20, y: 800, width: 120, height: 44 };
|
|
144
|
+
const result = renderWithProviders(<Menu face="menu" />);
|
|
145
|
+
tap(result.getByRole("button", { name: "Stack actions" }));
|
|
146
|
+
const panel = menu() as HTMLElement;
|
|
147
|
+
layout(panel, 200, 180);
|
|
148
|
+
const top = parseFloat(panel.style.top);
|
|
149
|
+
expect(top + 180).toBeLessThanOrEqual(844 - panelViewportPadding);
|
|
150
|
+
expect(top).toBeLessThan(800);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("keeps the sheet at page level for the auto face", () => {
|
|
154
|
+
const result = renderWithProviders(<Menu face="auto" />);
|
|
155
|
+
tap(result.getByRole("button", { name: "Stack actions" }));
|
|
156
|
+
expect(sheet()).not.toBeNull();
|
|
157
|
+
expect(menu()).toBeNull();
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("opens the auto face over the trigger inside another overlay, never a second sheet", () => {
|
|
161
|
+
const result = renderWithProviders(
|
|
162
|
+
<InSheet>
|
|
163
|
+
<Menu face="auto" />
|
|
164
|
+
</InSheet>,
|
|
165
|
+
);
|
|
166
|
+
tap(result.getByRole("button", { name: "Stack actions" }));
|
|
167
|
+
expect(sheet()).toBeNull();
|
|
168
|
+
const panel = menu() as HTMLElement;
|
|
169
|
+
layout(panel, 200, 180);
|
|
170
|
+
expect(panel.style.top).toBe("240px");
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("leaves a panel without a face as a sheet, even inside an overlay", () => {
|
|
174
|
+
const result = renderWithProviders(
|
|
175
|
+
<InSheet>
|
|
176
|
+
<Menu />
|
|
177
|
+
</InSheet>,
|
|
178
|
+
);
|
|
179
|
+
tap(result.getByRole("button", { name: "Stack actions" }));
|
|
180
|
+
expect(sheet()).not.toBeNull();
|
|
181
|
+
expect(menu()).toBeNull();
|
|
182
|
+
});
|
|
183
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { pickerCompactFace, resolveCompactFace, SHORT_LIST_MAX } from "./compactFace";
|
|
3
|
+
|
|
4
|
+
describe("resolveCompactFace (FZ-7830)", () => {
|
|
5
|
+
it("keeps the sheet by default, at page level and inside an overlay", () => {
|
|
6
|
+
expect(resolveCompactFace(undefined, false)).toBe("sheet");
|
|
7
|
+
expect(resolveCompactFace(undefined, true)).toBe("sheet");
|
|
8
|
+
expect(resolveCompactFace("sheet", true)).toBe("sheet");
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("opens a menu everywhere for the menu face", () => {
|
|
12
|
+
expect(resolveCompactFace("menu", false)).toBe("menu");
|
|
13
|
+
expect(resolveCompactFace("menu", true)).toBe("menu");
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("opens a menu only inside another overlay for the auto face", () => {
|
|
17
|
+
expect(resolveCompactFace("auto", false)).toBe("sheet");
|
|
18
|
+
expect(resolveCompactFace("auto", true)).toBe("menu");
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe("pickerCompactFace (FZ-7830)", () => {
|
|
23
|
+
it("opens a short list as a menu at every level", () => {
|
|
24
|
+
expect(SHORT_LIST_MAX).toBe(7);
|
|
25
|
+
expect(pickerCompactFace(1)).toBe("menu");
|
|
26
|
+
expect(pickerCompactFace(7)).toBe("menu");
|
|
27
|
+
expect(resolveCompactFace(pickerCompactFace(7), false)).toBe("menu");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("keeps a long list's sheet at page level and opens it as a menu inside an overlay", () => {
|
|
31
|
+
expect(pickerCompactFace(8)).toBe("auto");
|
|
32
|
+
expect(resolveCompactFace(pickerCompactFace(8), false)).toBe("sheet");
|
|
33
|
+
expect(resolveCompactFace(pickerCompactFace(8), true)).toBe("menu");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("keeps a searchable list's sheet at page level, however short", () => {
|
|
37
|
+
expect(pickerCompactFace(3, true)).toBe("auto");
|
|
38
|
+
expect(resolveCompactFace(pickerCompactFace(3, true), false)).toBe("sheet");
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a FloatingPanel becomes where it would otherwise be a bottom sheet:
|
|
3
|
+
* on native, and on the web at or below OVERLAY_BREAKPOINT.
|
|
4
|
+
*
|
|
5
|
+
* - `"sheet"`: the bottom sheet, wherever it opens.
|
|
6
|
+
* - `"menu"`: a menu over its trigger (LC-112) wherever it opens.
|
|
7
|
+
* - `"auto"`: the sheet at page level, the menu inside another overlay. A
|
|
8
|
+
* sheet over a sheet is the one thing a phone must never show (FZ-7830).
|
|
9
|
+
*/
|
|
10
|
+
export type CompactFace = "sheet" | "menu" | "auto";
|
|
11
|
+
|
|
12
|
+
export function resolveCompactFace(
|
|
13
|
+
face: CompactFace | undefined,
|
|
14
|
+
insideOverlay: boolean,
|
|
15
|
+
): "sheet" | "menu" {
|
|
16
|
+
if (face === "menu") return "menu";
|
|
17
|
+
if (face === "auto") return insideOverlay ? "menu" : "sheet";
|
|
18
|
+
return "sheet";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** The longest list a picker opens as a menu over its trigger (FZ-7830). */
|
|
22
|
+
export const SHORT_LIST_MAX = 7;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A picker's face (FZ-7830, LC-112): a short list with no search opens as a
|
|
26
|
+
* menu over its trigger at every level, the iOS 14+ pull-down picker. A long or
|
|
27
|
+
* searchable list keeps the sheet at page level and opens as a scrolling menu
|
|
28
|
+
* inside another overlay.
|
|
29
|
+
*/
|
|
30
|
+
export function pickerCompactFace(optionCount: number, searchable = false): CompactFace {
|
|
31
|
+
return !searchable && optionCount <= SHORT_LIST_MAX ? "menu" : "auto";
|
|
32
|
+
}
|
|
@@ -44,6 +44,13 @@
|
|
|
44
44
|
* - Opening a Modal under the same finger can synthesize a backdrop press;
|
|
45
45
|
* backdrop dismiss is armed a short delay after the present begins.
|
|
46
46
|
*
|
|
47
|
+
* Menu face (`compactFace`, FZ-7830): instead of the sheet, a menu opens
|
|
48
|
+
* over the trigger (LC-112), its top start corner on the trigger's, the
|
|
49
|
+
* trigger echoed as its first row, sliding back inside the safe area where it
|
|
50
|
+
* would run off screen. DropdownMenu always opens this way (a UIMenu
|
|
51
|
+
* pull-down); Select opens this way for a short list, and for any list inside
|
|
52
|
+
* another overlay, so a sheet never stacks on a sheet.
|
|
53
|
+
*
|
|
47
54
|
* ContextMenu (`openOn="contextmenu"`): long-press opens it. On a window wider
|
|
48
55
|
* than OVERLAY_BREAKPOINT (a tablet) it opens at the long-press point, the
|
|
49
56
|
* same free overlay the web draws at the pointer; a phone keeps the sheet,
|
|
@@ -83,12 +90,19 @@ import {
|
|
|
83
90
|
motionDurationMs,
|
|
84
91
|
} from "@multiplatform.one/theme";
|
|
85
92
|
import { View, YStack } from "tamagui";
|
|
86
|
-
import { useOverlayZIndex } from "../shared/overlayStack";
|
|
93
|
+
import { useInsideOverlay, useOverlayZIndex } from "../shared/overlayStack";
|
|
87
94
|
import { OverlayWindow } from "../shared/OverlayWindow";
|
|
88
95
|
import { t } from "../shared/t";
|
|
89
96
|
import { useKeyboardInset } from "../shared/useKeyboardInset";
|
|
90
97
|
import type { FloatingPanelProps } from "./index";
|
|
91
|
-
import {
|
|
98
|
+
import { resolveCompactFace } from "./compactFace";
|
|
99
|
+
import {
|
|
100
|
+
placeAtPoint,
|
|
101
|
+
placeOverTrigger,
|
|
102
|
+
type PanelBounds,
|
|
103
|
+
type PanelPoint,
|
|
104
|
+
type TriggerRect,
|
|
105
|
+
} from "./pointAnchor";
|
|
92
106
|
import { panelViewportPadding } from "./useFloatingPanel";
|
|
93
107
|
import { TrailingActionOverlay, TriggerWithTrailingAction } from "./trailingAction";
|
|
94
108
|
|
|
@@ -142,11 +156,18 @@ export function FloatingPanel({
|
|
|
142
156
|
triggerA11y,
|
|
143
157
|
sheetFill,
|
|
144
158
|
openOn = "press",
|
|
159
|
+
compactFace,
|
|
160
|
+
triggerRow,
|
|
161
|
+
triggerEcho = true,
|
|
145
162
|
}: FloatingPanelProps) {
|
|
146
163
|
// The panel floats over the page: it never inherits a painted ancestor.
|
|
147
164
|
const { knobProps } = useResolvedKnobs({ painted: false });
|
|
148
165
|
const windowZIndex = useOverlayZIndex("sheet");
|
|
149
|
-
const children =
|
|
166
|
+
const children = (
|
|
167
|
+
<Surface painted={false} elevated>
|
|
168
|
+
{panelChildren}
|
|
169
|
+
</Surface>
|
|
170
|
+
);
|
|
150
171
|
const slideTransition = knobProps.motion.slide.transition;
|
|
151
172
|
const enterDuration = motionDurationMs(slideTransition);
|
|
152
173
|
const scrimDuration = motionDurationMs(knobProps.motion.scrim.transition);
|
|
@@ -181,6 +202,34 @@ export function FloatingPanel({
|
|
|
181
202
|
);
|
|
182
203
|
const isContextMenu = openOn === "contextmenu";
|
|
183
204
|
const atPoint = isContextMenu && pressPoint !== null && windowWidth > OVERLAY_BREAKPOINT;
|
|
205
|
+
const insideOverlay = useInsideOverlay();
|
|
206
|
+
const asMenu = !isContextMenu && resolveCompactFace(compactFace, insideOverlay) === "menu";
|
|
207
|
+
const triggerRef = useRef<RNView | null>(null);
|
|
208
|
+
const [anchor, setAnchor] = useState<TriggerRect | null>(null);
|
|
209
|
+
// The trigger's window rect, read each time the menu opens: it may have
|
|
210
|
+
// moved (a scrolled sheet) since the last open.
|
|
211
|
+
useLayoutEffect(() => {
|
|
212
|
+
if (!asMenu || !open) {
|
|
213
|
+
setAnchor(null);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
const node = triggerRef.current as
|
|
217
|
+
| (RNView & {
|
|
218
|
+
measureInWindow?: (cb: (x: number, y: number, w: number, h: number) => void) => void;
|
|
219
|
+
})
|
|
220
|
+
| null;
|
|
221
|
+
let live = true;
|
|
222
|
+
if (typeof node?.measureInWindow === "function") {
|
|
223
|
+
node.measureInWindow((x, y, width, height) => {
|
|
224
|
+
if (live) setAnchor({ x, y, width, height });
|
|
225
|
+
});
|
|
226
|
+
} else {
|
|
227
|
+
setAnchor({ x: 0, y: 0, width: 0, height: 0 });
|
|
228
|
+
}
|
|
229
|
+
return () => {
|
|
230
|
+
live = false;
|
|
231
|
+
};
|
|
232
|
+
}, [asMenu, open]);
|
|
184
233
|
|
|
185
234
|
const handleBackdropPress = useCallback(() => {
|
|
186
235
|
if (backdropArmed) close();
|
|
@@ -473,6 +522,8 @@ export function FloatingPanel({
|
|
|
473
522
|
return (
|
|
474
523
|
<>
|
|
475
524
|
<RNView
|
|
525
|
+
ref={triggerRef}
|
|
526
|
+
collapsable={false}
|
|
476
527
|
style={{
|
|
477
528
|
flexDirection: "row",
|
|
478
529
|
alignItems: "center",
|
|
@@ -526,7 +577,29 @@ export function FloatingPanel({
|
|
|
526
577
|
}}
|
|
527
578
|
>
|
|
528
579
|
<OverlayWindow zIndex={windowZIndex}>
|
|
529
|
-
{
|
|
580
|
+
{asMenu ? (
|
|
581
|
+
<TriggerMenuPanel
|
|
582
|
+
anchor={anchor}
|
|
583
|
+
bounds={{
|
|
584
|
+
top: topInset + panelViewportPadding,
|
|
585
|
+
left: (safeArea?.left ?? 0) + panelViewportPadding,
|
|
586
|
+
right: windowWidth - (safeArea?.right ?? 0) - panelViewportPadding,
|
|
587
|
+
bottom:
|
|
588
|
+
windowHeight -
|
|
589
|
+
Math.max(safeArea?.bottom ?? 0, keyboardInset) -
|
|
590
|
+
panelViewportPadding,
|
|
591
|
+
}}
|
|
592
|
+
echo={triggerEcho ? (triggerRow ?? trigger) : null}
|
|
593
|
+
echoLabel={triggerA11y?.label}
|
|
594
|
+
framePadding={framePadding}
|
|
595
|
+
frameGap={frameGap}
|
|
596
|
+
onBackdropPress={handleBackdropPress}
|
|
597
|
+
onClose={close}
|
|
598
|
+
>
|
|
599
|
+
{header}
|
|
600
|
+
{children}
|
|
601
|
+
</TriggerMenuPanel>
|
|
602
|
+
) : atPoint && pressPoint ? (
|
|
530
603
|
<PointPanel
|
|
531
604
|
key={`${pressPoint.x},${pressPoint.y}`}
|
|
532
605
|
point={pressPoint}
|
|
@@ -644,6 +717,100 @@ export function FloatingPanel({
|
|
|
644
717
|
);
|
|
645
718
|
}
|
|
646
719
|
|
|
720
|
+
/**
|
|
721
|
+
* The menu over its trigger (LC-112): measured once per open at its natural
|
|
722
|
+
* size, then placed on the trigger's window rect. The first row repeats the
|
|
723
|
+
* trigger in the trigger's own box, and pressing it closes the menu, as the
|
|
724
|
+
* web echo row does.
|
|
725
|
+
*/
|
|
726
|
+
function TriggerMenuPanel({
|
|
727
|
+
anchor,
|
|
728
|
+
bounds,
|
|
729
|
+
echo,
|
|
730
|
+
echoLabel,
|
|
731
|
+
framePadding,
|
|
732
|
+
frameGap,
|
|
733
|
+
onBackdropPress,
|
|
734
|
+
onClose,
|
|
735
|
+
children,
|
|
736
|
+
}: {
|
|
737
|
+
anchor: TriggerRect | null;
|
|
738
|
+
bounds: PanelBounds;
|
|
739
|
+
echo: ReactNode;
|
|
740
|
+
echoLabel?: string;
|
|
741
|
+
framePadding: string;
|
|
742
|
+
frameGap: string | undefined;
|
|
743
|
+
onBackdropPress: () => void;
|
|
744
|
+
onClose: () => void;
|
|
745
|
+
children: ReactNode;
|
|
746
|
+
}) {
|
|
747
|
+
const { knobProps } = useResolvedKnobs({ painted: false });
|
|
748
|
+
const [size, setSize] = useState<{ width: number; height: number } | null>(null);
|
|
749
|
+
const placement =
|
|
750
|
+
anchor && size
|
|
751
|
+
? placeOverTrigger({
|
|
752
|
+
trigger: anchor,
|
|
753
|
+
width: size.width,
|
|
754
|
+
height: size.height,
|
|
755
|
+
bounds,
|
|
756
|
+
rtl: I18nManager.isRTL,
|
|
757
|
+
})
|
|
758
|
+
: null;
|
|
759
|
+
return (
|
|
760
|
+
<RNView style={StyleSheet.absoluteFill}>
|
|
761
|
+
<Pressable
|
|
762
|
+
style={StyleSheet.absoluteFill}
|
|
763
|
+
onPress={onBackdropPress}
|
|
764
|
+
accessibilityRole="button"
|
|
765
|
+
accessibilityLabel={t("Close")}
|
|
766
|
+
/>
|
|
767
|
+
<RNView
|
|
768
|
+
testID="floating-panel-menu"
|
|
769
|
+
onLayout={(e) => {
|
|
770
|
+
if (size) return;
|
|
771
|
+
const { width, height } = e.nativeEvent.layout;
|
|
772
|
+
if (width > 0 && height > 0) setSize({ width, height });
|
|
773
|
+
}}
|
|
774
|
+
style={{
|
|
775
|
+
position: "absolute",
|
|
776
|
+
left: placement?.left ?? bounds.left,
|
|
777
|
+
top: placement?.top ?? bounds.top,
|
|
778
|
+
minWidth: anchor?.width ?? 0,
|
|
779
|
+
maxWidth: bounds.right - bounds.left,
|
|
780
|
+
maxHeight: placement?.maxHeight ?? bounds.bottom - bounds.top,
|
|
781
|
+
opacity: placement ? 1 : 0,
|
|
782
|
+
}}
|
|
783
|
+
>
|
|
784
|
+
<YStack
|
|
785
|
+
{...knobProps.elevatedSurface}
|
|
786
|
+
flexShrink={1}
|
|
787
|
+
minHeight={0}
|
|
788
|
+
overflow="hidden"
|
|
789
|
+
onAccessibilityEscape={onClose}
|
|
790
|
+
>
|
|
791
|
+
{echo != null && anchor && anchor.height > 0 ? (
|
|
792
|
+
<Pressable
|
|
793
|
+
testID="floating-panel-menu-echo"
|
|
794
|
+
onPress={onClose}
|
|
795
|
+
accessibilityRole="button"
|
|
796
|
+
{...(echoLabel ? { accessibilityLabel: echoLabel } : undefined)}
|
|
797
|
+
aria-expanded
|
|
798
|
+
style={{ height: anchor.height, minWidth: anchor.width, justifyContent: "center" }}
|
|
799
|
+
>
|
|
800
|
+
<RNView pointerEvents="none">{echo}</RNView>
|
|
801
|
+
</Pressable>
|
|
802
|
+
) : null}
|
|
803
|
+
<RNScrollView style={{ flexGrow: 0, flexShrink: 1 }} keyboardShouldPersistTaps="handled">
|
|
804
|
+
<YStack padding={framePadding} gap={frameGap}>
|
|
805
|
+
{children}
|
|
806
|
+
</YStack>
|
|
807
|
+
</RNScrollView>
|
|
808
|
+
</YStack>
|
|
809
|
+
</RNView>
|
|
810
|
+
</RNView>
|
|
811
|
+
);
|
|
812
|
+
}
|
|
813
|
+
|
|
647
814
|
/**
|
|
648
815
|
* The menu at the long-press point: measured once per open at its natural
|
|
649
816
|
* size, then placed by the same rule as the web pointer menu. Later layouts
|