@multiplatform.one/forms 7.77.0 → 7.77.2
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 +45 -18
- package/dist/cjs/Button/IconButtonGroup.native.js +51 -20
- 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/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 +3 -11
- package/dist/cjs/Button/index.native.js +3 -12
- package/dist/cjs/Button/index.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 +27 -29
- 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/InputParts/keyboardAppearance.cjs +30 -0
- package/dist/cjs/InputParts/keyboardAppearance.native.js +33 -0
- package/dist/cjs/InputParts/keyboardAppearance.native.js.map +1 -0
- package/dist/cjs/fields/Duration/DurationWheel.cjs +34 -0
- 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/Select/index.cjs +98 -30
- package/dist/cjs/fields/Select/index.native.js +122 -54
- package/dist/cjs/fields/Select/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/shared/floatingList.cjs +2 -3
- package/dist/cjs/shared/floatingList.native.js +2 -3
- package/dist/cjs/shared/floatingList.native.js.map +1 -1
- package/dist/esm/AdaptivePopup/index.native.js.map +1 -1
- package/dist/esm/Button/IconButtonGroup.mjs +47 -20
- package/dist/esm/Button/IconButtonGroup.mjs.map +1 -1
- package/dist/esm/Button/IconButtonGroup.native.js +53 -22
- 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/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 +1 -9
- package/dist/esm/Button/index.mjs.map +1 -1
- package/dist/esm/Button/index.native.js +1 -10
- package/dist/esm/Button/index.native.js.map +1 -1
- package/dist/esm/Button/mediaPlate.native.js.map +1 -1
- package/dist/esm/Button/webGlass.native.js.map +1 -1
- package/dist/esm/Chip/index.native.js.map +1 -1
- package/dist/esm/ContextualSaveBar/UnsavedChangesDialog.native.js.map +1 -1
- package/dist/esm/ContextualSaveBar/clientKnownIdentity.native.js.map +1 -1
- package/dist/esm/ContextualSaveBar/commitImmediateField.native.js.map +1 -1
- package/dist/esm/ContextualSaveBar/index.native.js.map +1 -1
- package/dist/esm/ContextualSaveBar/useUnsavedChangesGuard.native.js.map +1 -1
- package/dist/esm/ControlGroup/index.native.js.map +1 -1
- package/dist/esm/DescriptionList/index.native.js.map +1 -1
- package/dist/esm/ErrorSummary.native.js.map +1 -1
- package/dist/esm/FieldDisplay/editMode.native.js.map +1 -1
- package/dist/esm/FieldDisplay/formatters.native.js.map +1 -1
- package/dist/esm/FieldDisplay/index.native.js.map +1 -1
- package/dist/esm/FieldGroup.native.js.map +1 -1
- package/dist/esm/FloatingPanel/PanelHeader.native.js.map +1 -1
- package/dist/esm/FloatingPanel/RegularPanel.native.js.map +1 -1
- package/dist/esm/FloatingPanel/compactFace.native.js.map +1 -1
- package/dist/esm/FloatingPanel/containingBlock.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/echoContext.native.js.map +1 -1
- 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 +27 -29
- 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/FloatingPanel/nestedAction.native.js.map +1 -1
- package/dist/esm/FloatingPanel/paintedBox.native.js.map +1 -1
- package/dist/esm/FloatingPanel/panelWidth.native.js.map +1 -1
- package/dist/esm/FloatingPanel/pointAnchor.native.js.map +1 -1
- package/dist/esm/FloatingPanel/regularPlacement.native.js.map +1 -1
- package/dist/esm/FloatingPanel/trailingAction.native.js.map +1 -1
- package/dist/esm/FloatingPanel/useFloatingPanel.native.js.map +1 -1
- package/dist/esm/Form/index.native.js.map +1 -1
- package/dist/esm/Form/submissionController.native.js.map +1 -1
- package/dist/esm/Form/validateOn.native.js.map +1 -1
- package/dist/esm/FormGrid.native.js.map +1 -1
- package/dist/esm/FormSection.native.js.map +1 -1
- package/dist/esm/InputParts/PasswordReveal.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/Label/index.native.js.map +1 -1
- package/dist/esm/MediaPicker/base64.native.js.map +1 -1
- package/dist/esm/MediaPicker/payload.native.js.map +1 -1
- package/dist/esm/MediaPicker/types.native.js.map +1 -1
- package/dist/esm/MediaPicker/useMediaPicker.native.js.map +1 -1
- package/dist/esm/Skeleton/index.native.js.map +1 -1
- package/dist/esm/Spinner/SpinnerGlyph.native.js.map +1 -1
- package/dist/esm/Spinner/spinnerSize.native.js.map +1 -1
- package/dist/esm/Table/index.native.js.map +1 -1
- package/dist/esm/Wheel/haptic.native.js.map +1 -1
- package/dist/esm/Wheel/index.native.js.map +1 -1
- package/dist/esm/Wheel/wheelShared.native.js.map +1 -1
- package/dist/esm/actionLabel.native.js.map +1 -1
- package/dist/esm/fieldDefaults.native.js.map +1 -1
- package/dist/esm/fieldLayout.native.js.map +1 -1
- package/dist/esm/fields/Barcode/BarcodePreview.native.js.map +1 -1
- package/dist/esm/fields/Barcode/code128.native.js.map +1 -1
- package/dist/esm/fields/Barcode/index.native.js.map +1 -1
- package/dist/esm/fields/Barcode/qr.native.js.map +1 -1
- package/dist/esm/fields/Barcode/scannerTypes.native.js.map +1 -1
- package/dist/esm/fields/Checkbox/Checkbox.native.js.map +1 -1
- package/dist/esm/fields/Checkbox/CheckboxBox.native.js.map +1 -1
- package/dist/esm/fields/Checkbox/CheckboxCard.native.js.map +1 -1
- package/dist/esm/fields/Checkbox/CheckboxFieldFrame.native.js.map +1 -1
- package/dist/esm/fields/Checkbox/CheckboxGroup.native.js.map +1 -1
- package/dist/esm/fields/Checkbox/checkboxGroupContext.native.js.map +1 -1
- package/dist/esm/fields/Checkbox/index.native.js.map +1 -1
- package/dist/esm/fields/ColorPicker/SystemColorField.native.js.map +1 -1
- package/dist/esm/fields/ColorPicker/index.native.js.map +1 -1
- package/dist/esm/fields/ColorPicker/parts.native.js.map +1 -1
- package/dist/esm/fields/ColorPicker/systemColorWell.gnome.native.js.map +1 -1
- package/dist/esm/fields/Combobox/authorOptions.native.js.map +1 -1
- package/dist/esm/fields/Combobox/index.native.js.map +1 -1
- package/dist/esm/fields/DatePicker/Calendar.native.js.map +1 -1
- package/dist/esm/fields/DatePicker/DateRangePicker.native.js.map +1 -1
- package/dist/esm/fields/DatePicker/DatetimePicker.catalog.native.js.map +1 -1
- package/dist/esm/fields/DatePicker/MonthPicker.native.js.map +1 -1
- package/dist/esm/fields/DatePicker/MultiDatePicker.native.js.map +1 -1
- package/dist/esm/fields/DatePicker/catalog.native.js.map +1 -1
- package/dist/esm/fields/DatePicker/fieldValueSync.native.js.map +1 -1
- package/dist/esm/fields/DatePicker/parts.native.js.map +1 -1
- package/dist/esm/fields/DatePicker/utils.native.js.map +1 -1
- 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/FileUpload/filePickerTypes.native.js.map +1 -1
- package/dist/esm/fields/FileUpload/index.native.js.map +1 -1
- package/dist/esm/fields/FileUpload/nativePickerLoader.native.js.map +1 -1
- package/dist/esm/fields/Geolocation/expoMapsLoader.native.js.map +1 -1
- package/dist/esm/fields/Geolocation/geoValue.native.js.map +1 -1
- package/dist/esm/fields/Geolocation/geocoder.native.js.map +1 -1
- package/dist/esm/fields/Geolocation/geocoderContext.native.js.map +1 -1
- package/dist/esm/fields/Geolocation/index.native.js.map +1 -1
- package/dist/esm/fields/Geolocation/mapMath.native.js.map +1 -1
- package/dist/esm/fields/Geolocation/mapTypes.native.js.map +1 -1
- package/dist/esm/fields/Geolocation/nativeSearchFocus.native.js.map +1 -1
- package/dist/esm/fields/Geolocation/photonGeocoder.native.js.map +1 -1
- package/dist/esm/fields/Icon/drawnSet.native.js.map +1 -1
- package/dist/esm/fields/Icon/index.native.js.map +1 -1
- package/dist/esm/fields/ImageField/index.native.js.map +1 -1
- package/dist/esm/fields/Input/index.native.js.map +1 -1
- package/dist/esm/fields/MentionInput/index.native.js.map +1 -1
- package/dist/esm/fields/OTPInput/index.native.js.map +1 -1
- package/dist/esm/fields/OTPInput/otpCellCss.native.js.map +1 -1
- package/dist/esm/fields/PhoneInput/dialCodes.native.js.map +1 -1
- package/dist/esm/fields/PhoneInput/index.native.js.map +1 -1
- package/dist/esm/fields/PhoneInput/phone.native.js.map +1 -1
- package/dist/esm/fields/PhoneInput/regionNames.native.js.map +1 -1
- package/dist/esm/fields/Progress/index.native.js.map +1 -1
- package/dist/esm/fields/RadioGroup/index.native.js.map +1 -1
- package/dist/esm/fields/Rating/index.native.js.map +1 -1
- package/dist/esm/fields/Rating/ratingMath.native.js.map +1 -1
- package/dist/esm/fields/Rating/types.native.js.map +1 -1
- package/dist/esm/fields/SearchInput/index.native.js.map +1 -1
- package/dist/esm/fields/Select/InlineChoices.native.js.map +1 -1
- package/dist/esm/fields/Select/SheetPickerRow.native.js.map +1 -1
- package/dist/esm/fields/Select/index.mjs +98 -30
- package/dist/esm/fields/Select/index.mjs.map +1 -1
- package/dist/esm/fields/Select/index.native.js +122 -54
- package/dist/esm/fields/Select/index.native.js.map +1 -1
- package/dist/esm/fields/Select/shouldInlineChoices.native.js.map +1 -1
- package/dist/esm/fields/Signature/index.native.js.map +1 -1
- package/dist/esm/fields/Slider/index.native.js.map +1 -1
- package/dist/esm/fields/Slider/jumpMotion.native.js.map +1 -1
- package/dist/esm/fields/Stepper/index.native.js.map +1 -1
- package/dist/esm/fields/Stepper/resolveStepperPlacement.native.js.map +1 -1
- package/dist/esm/fields/Switch/footprint.native.js.map +1 -1
- package/dist/esm/fields/Switch/index.native.js.map +1 -1
- package/dist/esm/fields/TextArea/index.native.js.map +1 -1
- package/dist/esm/fields/TimePicker/catalog.native.js.map +1 -1
- package/dist/esm/fields/TimePicker/parts.native.js.map +1 -1
- package/dist/esm/fields/TimeRangeScrubber/geometry.native.js.map +1 -1
- package/dist/esm/fields/TimeRangeScrubber/index.native.js.map +1 -1
- package/dist/esm/fields/ToggleGroup/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/dateTimePickerLoader.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/fields/nativeSelect.native.js.map +1 -1
- package/dist/esm/fields/selectedOrder.native.js.map +1 -1
- package/dist/esm/formSpacing.native.js.map +1 -1
- package/dist/esm/hooks/useFocusManagement.native.js.map +1 -1
- package/dist/esm/index.native.js.map +1 -1
- package/dist/esm/keyboard/chain.native.js.map +1 -1
- package/dist/esm/keyboard/index.native.js.map +1 -1
- package/dist/esm/keyboard/types.native.js.map +1 -1
- package/dist/esm/requiredMarking.native.js.map +1 -1
- package/dist/esm/shared/TimeColumn.native.js.map +1 -1
- package/dist/esm/shared/capabilityPreflight.native.js.map +1 -1
- package/dist/esm/shared/choiceRowPress.native.js.map +1 -1
- package/dist/esm/shared/colorRamps.native.js.map +1 -1
- package/dist/esm/shared/devWarn.native.js.map +1 -1
- package/dist/esm/shared/fieldRowContext.native.js.map +1 -1
- package/dist/esm/shared/floatingList.mjs +2 -3
- package/dist/esm/shared/floatingList.mjs.map +1 -1
- package/dist/esm/shared/floatingList.native.js +2 -3
- package/dist/esm/shared/floatingList.native.js.map +1 -1
- package/dist/esm/shared/focusManagement.native.js.map +1 -1
- package/dist/esm/shared/groupContext.native.js.map +1 -1
- package/dist/esm/shared/i18n.native.js.map +1 -1
- package/dist/esm/shared/instantMotion.native.js.map +1 -1
- package/dist/esm/shared/nativeA11y.native.js.map +1 -1
- package/dist/esm/shared/nativeAutoFocus.native.js.map +1 -1
- package/dist/esm/shared/overlaySizeClass.native.js.map +1 -1
- package/dist/esm/shared/overlayStack.native.js.map +1 -1
- package/dist/esm/shared/panelPortal.native.js.map +1 -1
- package/dist/esm/shared/pickerTriggerContract.native.js.map +1 -1
- package/dist/esm/shared/pressSlopProps.native.js.map +1 -1
- package/dist/esm/shared/restoreRetries.native.js.map +1 -1
- package/dist/esm/shared/sheetNavigation.native.js.map +1 -1
- package/dist/esm/shared/systemControlModules.gnome.native.js.map +1 -1
- package/dist/esm/shared/systemControls.native.js.map +1 -1
- package/dist/esm/shared/t.native.js.map +1 -1
- package/dist/esm/shared/tableCellContext.native.js.map +1 -1
- package/dist/esm/shared/useDragScroll.native.js.map +1 -1
- package/dist/esm/shared/useKeyboardInset.native.js.map +1 -1
- package/dist/esm/shared/useSheetEscapeDismiss.native.js.map +1 -1
- package/dist/esm/shared/useWheelIncrement.native.js.map +1 -1
- package/dist/esm/shared/utils.native.js.map +1 -1
- package/dist/esm/shared/zIndex.native.js.map +1 -1
- package/dist/esm/standardActions.native.js.map +1 -1
- package/dist/esm/themedPrimitives.native.js.map +1 -1
- package/dist/esm/types.native.js.map +1 -1
- package/dist/esm/utils/LinkCell.native.js.map +1 -1
- package/dist/jsx/Button/IconButtonGroup.mjs +47 -20
- package/dist/jsx/Button/IconButtonGroup.mjs.map +1 -1
- package/dist/jsx/Button/IconButtonGroup.native.js +51 -20
- 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/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 +1 -9
- package/dist/jsx/Button/index.mjs.map +1 -1
- package/dist/jsx/Button/index.native.js +3 -12
- package/dist/jsx/Button/index.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 +27 -29
- 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/Select/index.mjs +98 -30
- package/dist/jsx/fields/Select/index.mjs.map +1 -1
- package/dist/jsx/fields/Select/index.native.js +122 -54
- package/dist/jsx/fields/Select/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.native.js.map +1 -1
- package/dist/jsx/shared/floatingList.mjs +2 -3
- package/dist/jsx/shared/floatingList.mjs.map +1 -1
- package/dist/jsx/shared/floatingList.native.js +2 -3
- package/dist/jsx/shared/floatingList.native.js.map +1 -1
- package/package.json +8 -8
- package/src/Button/IconButtonGroup.spec.tsx +20 -0
- package/src/Button/IconButtonGroup.tsx +60 -25
- package/src/Button/circleGlyph.ts +18 -0
- package/src/Button/iconButtonGroupContext.ts +25 -0
- package/src/Button/index.tsx +2 -20
- 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 +38 -35
- 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/Select/Select.containingBlock.spec.tsx +88 -12
- package/src/fields/Select/Select.stories.tsx +74 -0
- package/src/fields/Select/index.tsx +113 -44
- 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/shared/floatingList.tsx +1 -4
- 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/iconButtonGroupContext.d.ts +19 -0
- package/types/Button/iconButtonGroupContext.d.ts.map +1 -1
- package/types/Button/index.d.ts +2 -9
- package/types/Button/index.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 +1 -6
- 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/Select/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/shared/floatingList.d.ts +1 -3
- package/types/shared/floatingList.d.ts.map +1 -1
|
@@ -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
|
+
}
|
|
@@ -2,10 +2,13 @@ import { describe, expect, it } from "vitest";
|
|
|
2
2
|
import { sizeRecipeForToken } from "@multiplatform.one/theme";
|
|
3
3
|
import {
|
|
4
4
|
durationSegmentWidth,
|
|
5
|
+
durationToWheelDate,
|
|
6
|
+
formatDurationParts,
|
|
5
7
|
normalizeSeconds,
|
|
6
8
|
parseDurationText,
|
|
7
9
|
partsToSeconds,
|
|
8
10
|
secondsToParts,
|
|
11
|
+
wheelDateToDuration,
|
|
9
12
|
} from "./durationMath";
|
|
10
13
|
|
|
11
14
|
describe("durationMath", () => {
|
|
@@ -64,4 +67,44 @@ describe("durationMath", () => {
|
|
|
64
67
|
expect(durationSegmentWidth("$2", 2)).not.toBe(durationSegmentWidth("$6", 2));
|
|
65
68
|
expect(durationSegmentWidth("$2", 2)).toBeLessThan(durationSegmentWidth("$4", 2));
|
|
66
69
|
});
|
|
70
|
+
|
|
71
|
+
describe("countdown wheel (FZ-8111)", () => {
|
|
72
|
+
const at = (h: number, m: number) => {
|
|
73
|
+
const d = new Date(2026, 9, 29);
|
|
74
|
+
d.setHours(h, m, 0, 0);
|
|
75
|
+
return d;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
it("reads hours and minutes within the day onto the wheel", () => {
|
|
79
|
+
const date = durationToWheelDate(3720);
|
|
80
|
+
expect([date.getHours(), date.getMinutes(), date.getSeconds()]).toEqual([1, 2, 0]);
|
|
81
|
+
const late = durationToWheelDate(23 * 3600 + 59 * 60);
|
|
82
|
+
expect([late.getHours(), late.getMinutes()]).toEqual([23, 59]);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("round-trips every wheel position back to the same seconds", () => {
|
|
86
|
+
for (const seconds of [0, 60, 900, 3720, 9000, 86340]) {
|
|
87
|
+
expect(wheelDateToDuration(durationToWheelDate(seconds), 0)).toBe(seconds);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("sets seconds from a wheel pick", () => {
|
|
92
|
+
expect(wheelDateToDuration(at(2, 30), 0)).toBe(9000);
|
|
93
|
+
expect(wheelDateToDuration(at(0, 15), 3720)).toBe(900);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("carries whole days and stray seconds the wheel cannot show", () => {
|
|
97
|
+
expect(wheelDateToDuration(at(2, 30), 86400 + 3600 + 7)).toBe(86400 + 9000 + 7);
|
|
98
|
+
const wheel = durationToWheelDate(90000);
|
|
99
|
+
expect([wheel.getHours(), wheel.getMinutes()]).toEqual([1, 0]);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("formats the trigger text with or without days", () => {
|
|
103
|
+
const units = { d: "d", h: "h", m: "m" };
|
|
104
|
+
expect(formatDurationParts(secondsToParts(3720, true), true, units)).toBe("1 h 2 m");
|
|
105
|
+
expect(formatDurationParts(secondsToParts(90000, true), true, units)).toBe("25 h 0 m");
|
|
106
|
+
expect(formatDurationParts(secondsToParts(93784, false), false, units)).toBe("1 d 2 h 3 m");
|
|
107
|
+
expect(formatDurationParts(secondsToParts(3720, false), false, units)).toBe("1 h 2 m");
|
|
108
|
+
});
|
|
109
|
+
});
|
|
67
110
|
});
|
|
@@ -76,3 +76,34 @@ export function durationSegmentWidth(sizeToken: string, digits: number): number
|
|
|
76
76
|
const recipe = sizeRecipeForToken(sizeToken);
|
|
77
77
|
return recipe.fontSize * digits;
|
|
78
78
|
}
|
|
79
|
+
|
|
80
|
+
const DAY = 86400;
|
|
81
|
+
|
|
82
|
+
/** The countdown wheel's reading of a duration: hours and minutes within one day, on today's date. */
|
|
83
|
+
export function durationToWheelDate(total: number): Date {
|
|
84
|
+
const s = normalizeSeconds(total) % DAY;
|
|
85
|
+
const date = new Date();
|
|
86
|
+
date.setHours(Math.floor(s / 3600), Math.floor((s % 3600) / 60), 0, 0);
|
|
87
|
+
return date;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* A wheel pick back to seconds. The wheel spans 0 h 0 m to 23 h 59 m, so the
|
|
92
|
+
* whole days and the stray seconds of the previous value carry over untouched.
|
|
93
|
+
*/
|
|
94
|
+
export function wheelDateToDuration(picked: Date, previous: number): number {
|
|
95
|
+
const prev = normalizeSeconds(previous);
|
|
96
|
+
return (
|
|
97
|
+
Math.floor(prev / DAY) * DAY + picked.getHours() * 3600 + picked.getMinutes() * 60 + (prev % 60)
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** "1 h 2 m", "1 d 2 h 3 m": the wheel trigger's text, units from the caller. */
|
|
102
|
+
export function formatDurationParts(
|
|
103
|
+
parts: DurationParts,
|
|
104
|
+
hideDays: boolean,
|
|
105
|
+
units: { d: string; h: string; m: string },
|
|
106
|
+
): string {
|
|
107
|
+
const head = hideDays || parts.days === 0 ? [] : [`${parts.days} ${units.d}`];
|
|
108
|
+
return [...head, `${parts.hours} ${units.h}`, `${parts.minutes} ${units.m}`].join(" ");
|
|
109
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { KnobProps } from "@multiplatform.one/theme";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
import type { SizeTokens } from "tamagui";
|
|
4
|
+
|
|
5
|
+
export interface DurationWheelFieldProps {
|
|
6
|
+
seconds: number;
|
|
7
|
+
onChange: (seconds: number) => void;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
readOnly?: boolean;
|
|
10
|
+
foldDays: boolean;
|
|
11
|
+
clockIcon?: ReactNode | null;
|
|
12
|
+
hasError?: boolean;
|
|
13
|
+
id?: string;
|
|
14
|
+
hasLabel?: boolean;
|
|
15
|
+
required?: boolean;
|
|
16
|
+
size?: SizeTokens;
|
|
17
|
+
knobProps: KnobProps;
|
|
18
|
+
}
|
|
@@ -5,7 +5,7 @@ import type { ReactNode } from "react";
|
|
|
5
5
|
import { useCallback, useState } from "react";
|
|
6
6
|
import type { LabelProps, SizeTokens } from "tamagui";
|
|
7
7
|
import { Text, XStack, YStack, isWeb } from "tamagui";
|
|
8
|
-
import {
|
|
8
|
+
import { Chip } from "../../Chip";
|
|
9
9
|
import { Skeleton } from "../../Skeleton";
|
|
10
10
|
import { Field, FieldLayout } from "../../fieldLayout";
|
|
11
11
|
import { Input as InputParts } from "../../InputParts";
|
|
@@ -20,22 +20,29 @@ import {
|
|
|
20
20
|
useResolvedValidators,
|
|
21
21
|
} from "../../shared/utils";
|
|
22
22
|
import type { AnyFormApi } from "../../types";
|
|
23
|
+
import { DurationWheelField, durationWheelAvailable } from "./DurationWheel";
|
|
23
24
|
import {
|
|
24
25
|
durationSegmentWidth,
|
|
26
|
+
durationToWheelDate,
|
|
27
|
+
formatDurationParts,
|
|
25
28
|
normalizeSeconds,
|
|
26
29
|
parseDurationText,
|
|
27
30
|
partsToSeconds,
|
|
28
31
|
secondsToParts,
|
|
32
|
+
wheelDateToDuration,
|
|
29
33
|
type DurationParts,
|
|
30
34
|
} from "./durationMath";
|
|
31
35
|
|
|
32
36
|
export type { DurationParts };
|
|
33
37
|
export {
|
|
34
38
|
durationSegmentWidth,
|
|
39
|
+
durationToWheelDate,
|
|
40
|
+
formatDurationParts,
|
|
35
41
|
normalizeSeconds,
|
|
36
42
|
parseDurationText,
|
|
37
43
|
partsToSeconds,
|
|
38
44
|
secondsToParts,
|
|
45
|
+
wheelDateToDuration,
|
|
39
46
|
};
|
|
40
47
|
|
|
41
48
|
export interface DurationPreset {
|
|
@@ -374,16 +381,15 @@ function DurationPresets({
|
|
|
374
381
|
{presets.map((preset) => {
|
|
375
382
|
const selected = seconds === preset.seconds;
|
|
376
383
|
return (
|
|
377
|
-
<
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
</Button>
|
|
384
|
+
<XStack key={preset.seconds} data-duration-preset={preset.label}>
|
|
385
|
+
<Chip
|
|
386
|
+
size={DURATION_PRESET_SIZE}
|
|
387
|
+
selected={selected}
|
|
388
|
+
onPress={() => onChange(preset.seconds)}
|
|
389
|
+
>
|
|
390
|
+
{preset.label}
|
|
391
|
+
</Chip>
|
|
392
|
+
</XStack>
|
|
387
393
|
);
|
|
388
394
|
})}
|
|
389
395
|
</XStack>
|
|
@@ -425,6 +431,11 @@ export interface DurationProps {
|
|
|
425
431
|
clockIcon?: ReactNode | null;
|
|
426
432
|
/** When true, renders a skeleton placeholder instead of the duration picker */
|
|
427
433
|
skeleton?: boolean;
|
|
434
|
+
/**
|
|
435
|
+
* On iOS and Android a tap opens the system hours/minutes wheel (LC-266).
|
|
436
|
+
* `false` keeps the typed segments there too. Web always types.
|
|
437
|
+
*/
|
|
438
|
+
native?: boolean;
|
|
428
439
|
}
|
|
429
440
|
|
|
430
441
|
export function Duration({
|
|
@@ -450,6 +461,7 @@ export function Duration({
|
|
|
450
461
|
placeholder = t("Select duration"),
|
|
451
462
|
clockIcon,
|
|
452
463
|
skeleton,
|
|
464
|
+
native,
|
|
453
465
|
}: DurationProps) {
|
|
454
466
|
const hydrationTouch = useTouchSurface();
|
|
455
467
|
const { resolvedForm, knobProps, id } = useFormField({
|
|
@@ -489,24 +501,43 @@ export function Duration({
|
|
|
489
501
|
);
|
|
490
502
|
}
|
|
491
503
|
|
|
504
|
+
const wheel = native !== false && !showSeconds && durationWheelAvailable();
|
|
505
|
+
|
|
492
506
|
const fieldBody = (current: number, onValueChange: (s: number) => void, hasError?: boolean) => (
|
|
493
507
|
<YStack {...knobProps.gap}>
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
508
|
+
{wheel ? (
|
|
509
|
+
<DurationWheelField
|
|
510
|
+
seconds={current}
|
|
511
|
+
onChange={onValueChange}
|
|
512
|
+
disabled={disabled}
|
|
513
|
+
readOnly={readOnly}
|
|
514
|
+
foldDays={hideDays}
|
|
515
|
+
clockIcon={clockIcon}
|
|
516
|
+
hasError={hasError}
|
|
517
|
+
id={id}
|
|
518
|
+
hasLabel={Boolean(label)}
|
|
519
|
+
required={required}
|
|
520
|
+
size={size}
|
|
521
|
+
knobProps={knobProps}
|
|
522
|
+
/>
|
|
523
|
+
) : (
|
|
524
|
+
<DurationControl
|
|
525
|
+
seconds={current}
|
|
526
|
+
onChange={onValueChange}
|
|
527
|
+
disabled={disabled}
|
|
528
|
+
readOnly={readOnly}
|
|
529
|
+
hideDays={hideDays}
|
|
530
|
+
showSeconds={showSeconds}
|
|
531
|
+
clockIcon={clockIcon}
|
|
532
|
+
hasError={hasError}
|
|
533
|
+
id={id}
|
|
534
|
+
hasLabel={Boolean(label)}
|
|
535
|
+
placeholder={placeholder}
|
|
536
|
+
required={required}
|
|
537
|
+
size={size}
|
|
538
|
+
knobProps={knobProps}
|
|
539
|
+
/>
|
|
540
|
+
)}
|
|
510
541
|
{readOnly || disabled || inTableCell ? null : (
|
|
511
542
|
<DurationPresets
|
|
512
543
|
seconds={current}
|
|
@@ -98,7 +98,7 @@ describe("Select seamless cover (LC-112, FZ-8438)", () => {
|
|
|
98
98
|
});
|
|
99
99
|
});
|
|
100
100
|
|
|
101
|
-
describe("Select
|
|
101
|
+
describe("Select cover and scroll (LC-112, FZ-8463)", () => {
|
|
102
102
|
const field = (top: number) => ({
|
|
103
103
|
x: 16,
|
|
104
104
|
y: top,
|
|
@@ -111,6 +111,7 @@ describe("Select drop-up (LC-112, FZ-8438 r3)", () => {
|
|
|
111
111
|
toJSON: () => ({}),
|
|
112
112
|
});
|
|
113
113
|
let fieldTop = 700;
|
|
114
|
+
let contentHeight = 300;
|
|
114
115
|
beforeEach(() => {
|
|
115
116
|
Object.defineProperty(window, "innerWidth", {
|
|
116
117
|
configurable: true,
|
|
@@ -136,7 +137,7 @@ describe("Select drop-up (LC-112, FZ-8438 r3)", () => {
|
|
|
136
137
|
);
|
|
137
138
|
vi.spyOn(HTMLElement.prototype, "scrollHeight", "get").mockImplementation(
|
|
138
139
|
function (this: HTMLElement) {
|
|
139
|
-
return this.dataset.testid === "select-dropdown" ?
|
|
140
|
+
return this.dataset.testid === "select-dropdown" ? contentHeight : 0;
|
|
140
141
|
},
|
|
141
142
|
);
|
|
142
143
|
});
|
|
@@ -144,28 +145,103 @@ describe("Select drop-up (LC-112, FZ-8438 r3)", () => {
|
|
|
144
145
|
vi.restoreAllMocks();
|
|
145
146
|
});
|
|
146
147
|
|
|
147
|
-
it("a field
|
|
148
|
+
it("a low field's list covers the field with no echo and grows up from its bottom edge", async () => {
|
|
148
149
|
fieldTop = 700;
|
|
150
|
+
contentHeight = 300;
|
|
149
151
|
const result = renderWithProviders(<Select options={options} value="hel" />);
|
|
150
152
|
const { dropdown } = await openTop(result);
|
|
151
|
-
|
|
152
|
-
//
|
|
153
|
-
await waitFor(() =>
|
|
154
|
-
|
|
153
|
+
await waitFor(() => expect(dropdown.querySelector("[data-fp-trigger-row]")).toBeNull());
|
|
154
|
+
// The panel's bottom edge is the field's (740), the whole list above it.
|
|
155
|
+
await waitFor(() =>
|
|
156
|
+
expect(parseFloat(dropdown.style.top) + parseFloat(dropdown.style.maxHeight)).toBe(740),
|
|
157
|
+
);
|
|
158
|
+
expect(parseFloat(dropdown.style.top)).toBeLessThan(700);
|
|
155
159
|
expect(parseFloat(dropdown.style.maxHeight)).toBeGreaterThanOrEqual(300);
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
+
expect(dropdown.textContent?.match(/Helsinki/g)?.length ?? 0).toBe(1);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("a low field's long list stops at the safe top edge and scrolls", async () => {
|
|
164
|
+
fieldTop = 700;
|
|
165
|
+
contentHeight = 2000;
|
|
166
|
+
const result = renderWithProviders(<Select options={options} value="hel" />);
|
|
167
|
+
const { dropdown } = await openTop(result);
|
|
168
|
+
await waitFor(() => expect(dropdown.querySelector("[data-fp-trigger-row]")).toBeNull());
|
|
169
|
+
await waitFor(() => expect(parseFloat(dropdown.style.top)).toBe(10));
|
|
170
|
+
expect(parseFloat(dropdown.style.maxHeight)).toBe(730);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("a high field's long list covers the field and grows down to the safe bottom edge", async () => {
|
|
174
|
+
fieldTop = 120;
|
|
175
|
+
contentHeight = 2000;
|
|
176
|
+
const result = renderWithProviders(<Select options={options} value="hel" />);
|
|
177
|
+
const { dropdown } = await openTop(result);
|
|
178
|
+
await waitFor(() => expect(dropdown.querySelector("[data-fp-trigger-row]")).toBeNull());
|
|
179
|
+
expect(parseFloat(dropdown.style.top)).toBe(120);
|
|
180
|
+
await waitFor(() => expect(parseFloat(dropdown.style.maxHeight)).toBe(768 - 10 - 120));
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe("with 36px rows", () => {
|
|
184
|
+
const rowTop = 4;
|
|
185
|
+
const rowHeight = 36;
|
|
186
|
+
beforeEach(() => {
|
|
187
|
+
const rowIndex = (el: HTMLElement) =>
|
|
188
|
+
Array.from(
|
|
189
|
+
el.closest('[data-testid="select-dropdown"]')?.querySelectorAll('[role="option"]') ?? [],
|
|
190
|
+
).indexOf(el);
|
|
191
|
+
vi.spyOn(HTMLElement.prototype, "offsetHeight", "get").mockImplementation(
|
|
192
|
+
function (this: HTMLElement) {
|
|
193
|
+
return this.getAttribute("role") === "option" ? rowHeight : 0;
|
|
194
|
+
},
|
|
195
|
+
);
|
|
196
|
+
vi.spyOn(HTMLElement.prototype, "offsetTop", "get").mockImplementation(
|
|
197
|
+
function (this: HTMLElement) {
|
|
198
|
+
return this.getAttribute("role") === "option" ? rowTop + rowIndex(this) * rowHeight : 0;
|
|
199
|
+
},
|
|
200
|
+
);
|
|
201
|
+
vi.spyOn(HTMLElement.prototype, "clientHeight", "get").mockImplementation(
|
|
202
|
+
function (this: HTMLElement) {
|
|
203
|
+
return this.dataset.testid === "select-dropdown"
|
|
204
|
+
? parseFloat(this.style.maxHeight) || 0
|
|
205
|
+
: 0;
|
|
206
|
+
},
|
|
207
|
+
);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("growing up, the row on the field is whole: the panel's bottom stays on the field", async () => {
|
|
211
|
+
fieldTop = 700;
|
|
212
|
+
contentHeight = 2000;
|
|
213
|
+
const result = renderWithProviders(<Select options={options} value="hel" />);
|
|
214
|
+
const { dropdown } = await openTop(result);
|
|
215
|
+
// 730 of room above holds 20 whole rows and 6px of a 21st: that sliver goes.
|
|
216
|
+
await waitFor(() => expect(parseFloat(dropdown.style.maxHeight)).toBe(724));
|
|
217
|
+
expect(parseFloat(dropdown.style.top)).toBe(16);
|
|
218
|
+
const visibleBottom = dropdown.scrollTop + parseFloat(dropdown.style.maxHeight);
|
|
219
|
+
expect((visibleBottom - rowTop) % rowHeight).toBe(0);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it("growing down, no sliver of a row sits at the screen edge", async () => {
|
|
223
|
+
fieldTop = 120;
|
|
224
|
+
contentHeight = 2000;
|
|
225
|
+
const result = renderWithProviders(<Select options={options} value="hel" />);
|
|
226
|
+
const { dropdown } = await openTop(result);
|
|
227
|
+
expect(parseFloat(dropdown.style.top)).toBe(120);
|
|
228
|
+
await waitFor(() =>
|
|
229
|
+
expect(parseFloat(dropdown.style.maxHeight)).toBeLessThan(768 - 10 - 120),
|
|
230
|
+
);
|
|
231
|
+
const visibleBottom = dropdown.scrollTop + parseFloat(dropdown.style.maxHeight);
|
|
232
|
+
expect((visibleBottom - rowTop) % rowHeight).toBe(0);
|
|
233
|
+
});
|
|
160
234
|
});
|
|
161
235
|
|
|
162
236
|
it("a field with room below keeps the echo on top, opening down", async () => {
|
|
163
237
|
fieldTop = 120;
|
|
238
|
+
contentHeight = 300;
|
|
164
239
|
const result = renderWithProviders(<Select options={options} value="hel" />);
|
|
165
240
|
const { dropdown, top } = await openTop(result);
|
|
166
241
|
expect(top).toBe(120);
|
|
167
242
|
const echo = dropdown.querySelector<HTMLElement>("[data-fp-trigger-row]");
|
|
168
|
-
expect(echo
|
|
243
|
+
expect(echo).not.toBeNull();
|
|
244
|
+
expect(dropdown.firstElementChild).toBe(echo);
|
|
169
245
|
});
|
|
170
246
|
});
|
|
171
247
|
|