@multiplatform.one/forms 7.65.2 → 7.67.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/FloatingPanel/RegularPanel.cjs +164 -0
- package/dist/cjs/FloatingPanel/RegularPanel.native.js +169 -0
- package/dist/cjs/FloatingPanel/RegularPanel.native.js.map +1 -0
- package/dist/cjs/FloatingPanel/compactFace.cjs +6 -0
- package/dist/cjs/FloatingPanel/compactFace.native.js +6 -0
- package/dist/cjs/FloatingPanel/compactFace.native.js.map +1 -1
- package/dist/cjs/FloatingPanel/containingBlock.cjs +46 -0
- package/dist/cjs/FloatingPanel/containingBlock.native.js +51 -0
- package/dist/cjs/FloatingPanel/containingBlock.native.js.map +1 -0
- package/dist/cjs/FloatingPanel/index.cjs +2 -20
- package/dist/cjs/FloatingPanel/index.native.js +79 -45
- package/dist/cjs/FloatingPanel/index.native.js.map +1 -1
- package/dist/cjs/FloatingPanel/pointAnchor.cjs +25 -0
- package/dist/cjs/FloatingPanel/pointAnchor.native.js +26 -0
- package/dist/cjs/FloatingPanel/pointAnchor.native.js.map +1 -1
- package/dist/cjs/FloatingPanel/regularPlacement.cjs +61 -0
- package/dist/cjs/FloatingPanel/regularPlacement.native.js +65 -0
- package/dist/cjs/FloatingPanel/regularPlacement.native.js.map +1 -0
- package/dist/cjs/fields/Combobox/index.cjs +8 -10
- package/dist/cjs/fields/Combobox/index.native.js +10 -10
- package/dist/cjs/fields/Combobox/index.native.js.map +1 -1
- package/dist/cjs/fields/DatePicker/parts.cjs +1 -0
- package/dist/cjs/fields/DatePicker/parts.native.js +3 -0
- package/dist/cjs/fields/DatePicker/parts.native.js.map +1 -1
- package/dist/cjs/fields/Input/index.cjs +5 -0
- package/dist/cjs/fields/Input/index.native.js +5 -0
- package/dist/cjs/fields/Input/index.native.js.map +1 -1
- package/dist/cjs/fields/Select/InlineChoices.cjs +3 -2
- package/dist/cjs/fields/Select/InlineChoices.native.js +5 -2
- package/dist/cjs/fields/Select/InlineChoices.native.js.map +1 -1
- package/dist/cjs/fields/Select/SheetPickerRow.cjs +154 -0
- package/dist/cjs/fields/Select/SheetPickerRow.native.js +165 -0
- package/dist/cjs/fields/Select/SheetPickerRow.native.js.map +1 -0
- package/dist/cjs/fields/Select/index.cjs +64 -5
- package/dist/cjs/fields/Select/index.native.js +86 -27
- package/dist/cjs/fields/Select/index.native.js.map +1 -1
- package/dist/cjs/fields/Select/shouldInlineChoices.cjs +14 -1
- package/dist/cjs/fields/Select/shouldInlineChoices.native.js +15 -1
- package/dist/cjs/fields/Select/shouldInlineChoices.native.js.map +1 -1
- package/dist/cjs/fields/Slider/index.cjs +96 -4
- package/dist/cjs/fields/Slider/index.native.js +103 -5
- package/dist/cjs/fields/Slider/index.native.js.map +1 -1
- package/dist/cjs/fields/Switch/index.cjs +71 -1
- package/dist/cjs/fields/Switch/index.native.js +72 -1
- package/dist/cjs/fields/Switch/index.native.js.map +1 -1
- package/dist/cjs/fields/TimePicker/parts.cjs +1 -0
- package/dist/cjs/fields/TimePicker/parts.native.js +1 -0
- package/dist/cjs/fields/TimePicker/parts.native.js.map +1 -1
- package/dist/cjs/fields/ToggleGroup/index.cjs +67 -1
- package/dist/cjs/fields/ToggleGroup/index.native.js +75 -1
- package/dist/cjs/fields/ToggleGroup/index.native.js.map +1 -1
- package/dist/cjs/fields/nativeDateTime/OsDateTimeSheet.cjs +34 -19
- package/dist/cjs/fields/nativeDateTime/OsDateTimeSheet.native.js +44 -21
- package/dist/cjs/fields/nativeDateTime/OsDateTimeSheet.native.js.map +1 -1
- package/dist/cjs/fields/nativeDateTime/sheetKeyboard.cjs +54 -0
- package/dist/cjs/fields/nativeDateTime/sheetKeyboard.native.js +57 -0
- package/dist/cjs/fields/nativeDateTime/sheetKeyboard.native.js.map +1 -0
- package/dist/cjs/index.cjs +11 -0
- package/dist/cjs/index.native.js +11 -0
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/cjs/shared/overlaySizeClass.cjs +42 -0
- package/dist/cjs/shared/overlaySizeClass.native.js +45 -0
- package/dist/cjs/shared/overlaySizeClass.native.js.map +1 -0
- package/dist/cjs/shared/sheetNavigation.cjs +76 -0
- package/dist/cjs/shared/sheetNavigation.native.js +129 -0
- package/dist/cjs/shared/sheetNavigation.native.js.map +1 -0
- package/dist/cjs/shared/systemControlModules.cjs +34 -0
- package/dist/cjs/shared/systemControlModules.gnome.cjs +34 -0
- package/dist/cjs/shared/systemControlModules.gnome.native.js +37 -0
- package/dist/cjs/shared/systemControlModules.gnome.native.js.map +1 -0
- package/dist/cjs/shared/systemControlModules.native.js +77 -0
- package/dist/cjs/shared/systemControlModules.native.js.map +1 -0
- package/dist/cjs/shared/systemControls.cjs +47 -0
- package/dist/cjs/shared/systemControls.native.js +51 -0
- package/dist/cjs/shared/systemControls.native.js.map +1 -0
- package/dist/esm/FloatingPanel/RegularPanel.mjs +139 -0
- package/dist/esm/FloatingPanel/RegularPanel.mjs.map +1 -0
- package/dist/esm/FloatingPanel/RegularPanel.native.js +141 -0
- package/dist/esm/FloatingPanel/RegularPanel.native.js.map +1 -0
- package/dist/esm/FloatingPanel/compactFace.mjs +5 -1
- package/dist/esm/FloatingPanel/compactFace.mjs.map +1 -1
- package/dist/esm/FloatingPanel/compactFace.native.js +5 -1
- package/dist/esm/FloatingPanel/compactFace.native.js.map +1 -1
- package/dist/esm/FloatingPanel/containingBlock.mjs +21 -0
- package/dist/esm/FloatingPanel/containingBlock.mjs.map +1 -0
- package/dist/esm/FloatingPanel/containingBlock.native.js +23 -0
- package/dist/esm/FloatingPanel/containingBlock.native.js.map +1 -0
- package/dist/esm/FloatingPanel/index.mjs +1 -19
- package/dist/esm/FloatingPanel/index.mjs.map +1 -1
- package/dist/esm/FloatingPanel/index.native.js +83 -49
- package/dist/esm/FloatingPanel/index.native.js.map +1 -1
- package/dist/esm/FloatingPanel/pointAnchor.mjs +25 -1
- package/dist/esm/FloatingPanel/pointAnchor.mjs.map +1 -1
- package/dist/esm/FloatingPanel/pointAnchor.native.js +26 -1
- package/dist/esm/FloatingPanel/pointAnchor.native.js.map +1 -1
- package/dist/esm/FloatingPanel/regularPlacement.mjs +35 -0
- package/dist/esm/FloatingPanel/regularPlacement.mjs.map +1 -0
- package/dist/esm/FloatingPanel/regularPlacement.native.js +36 -0
- package/dist/esm/FloatingPanel/regularPlacement.native.js.map +1 -0
- package/dist/esm/fields/Combobox/index.mjs +9 -11
- package/dist/esm/fields/Combobox/index.mjs.map +1 -1
- package/dist/esm/fields/Combobox/index.native.js +11 -11
- package/dist/esm/fields/Combobox/index.native.js.map +1 -1
- package/dist/esm/fields/DatePicker/parts.mjs +1 -0
- package/dist/esm/fields/DatePicker/parts.mjs.map +1 -1
- package/dist/esm/fields/DatePicker/parts.native.js +3 -0
- package/dist/esm/fields/DatePicker/parts.native.js.map +1 -1
- package/dist/esm/fields/Input/index.mjs +6 -1
- package/dist/esm/fields/Input/index.mjs.map +1 -1
- package/dist/esm/fields/Input/index.native.js +6 -1
- package/dist/esm/fields/Input/index.native.js.map +1 -1
- package/dist/esm/fields/Select/InlineChoices.mjs +3 -2
- package/dist/esm/fields/Select/InlineChoices.mjs.map +1 -1
- package/dist/esm/fields/Select/InlineChoices.native.js +5 -2
- package/dist/esm/fields/Select/InlineChoices.native.js.map +1 -1
- package/dist/esm/fields/Select/SheetPickerRow.mjs +129 -0
- package/dist/esm/fields/Select/SheetPickerRow.mjs.map +1 -0
- package/dist/esm/fields/Select/SheetPickerRow.native.js +137 -0
- package/dist/esm/fields/Select/SheetPickerRow.native.js.map +1 -0
- package/dist/esm/fields/Select/index.mjs +66 -7
- package/dist/esm/fields/Select/index.mjs.map +1 -1
- package/dist/esm/fields/Select/index.native.js +88 -29
- package/dist/esm/fields/Select/index.native.js.map +1 -1
- package/dist/esm/fields/Select/shouldInlineChoices.mjs +12 -1
- package/dist/esm/fields/Select/shouldInlineChoices.mjs.map +1 -1
- package/dist/esm/fields/Select/shouldInlineChoices.native.js +13 -1
- package/dist/esm/fields/Select/shouldInlineChoices.native.js.map +1 -1
- package/dist/esm/fields/Slider/index.mjs +96 -4
- package/dist/esm/fields/Slider/index.mjs.map +1 -1
- package/dist/esm/fields/Slider/index.native.js +103 -5
- package/dist/esm/fields/Slider/index.native.js.map +1 -1
- package/dist/esm/fields/Switch/index.mjs +71 -1
- package/dist/esm/fields/Switch/index.mjs.map +1 -1
- package/dist/esm/fields/Switch/index.native.js +72 -1
- package/dist/esm/fields/Switch/index.native.js.map +1 -1
- package/dist/esm/fields/TimePicker/parts.mjs +1 -0
- package/dist/esm/fields/TimePicker/parts.mjs.map +1 -1
- package/dist/esm/fields/TimePicker/parts.native.js +1 -0
- package/dist/esm/fields/TimePicker/parts.native.js.map +1 -1
- package/dist/esm/fields/ToggleGroup/index.mjs +67 -1
- package/dist/esm/fields/ToggleGroup/index.mjs.map +1 -1
- package/dist/esm/fields/ToggleGroup/index.native.js +75 -1
- package/dist/esm/fields/ToggleGroup/index.native.js.map +1 -1
- package/dist/esm/fields/nativeDateTime/OsDateTimeSheet.mjs +36 -21
- package/dist/esm/fields/nativeDateTime/OsDateTimeSheet.mjs.map +1 -1
- package/dist/esm/fields/nativeDateTime/OsDateTimeSheet.native.js +46 -23
- package/dist/esm/fields/nativeDateTime/OsDateTimeSheet.native.js.map +1 -1
- package/dist/esm/fields/nativeDateTime/sheetKeyboard.mjs +21 -0
- package/dist/esm/fields/nativeDateTime/sheetKeyboard.mjs.map +1 -0
- package/dist/esm/fields/nativeDateTime/sheetKeyboard.native.js +21 -0
- package/dist/esm/fields/nativeDateTime/sheetKeyboard.native.js.map +1 -0
- package/dist/esm/index.mjs +3 -1
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/index.native.js +3 -1
- package/dist/esm/index.native.js.map +1 -1
- package/dist/esm/shared/overlaySizeClass.mjs +14 -0
- package/dist/esm/shared/overlaySizeClass.mjs.map +1 -0
- package/dist/esm/shared/overlaySizeClass.native.js +14 -0
- package/dist/esm/shared/overlaySizeClass.native.js.map +1 -0
- package/dist/esm/shared/sheetNavigation.mjs +47 -0
- package/dist/esm/shared/sheetNavigation.mjs.map +1 -0
- package/dist/esm/shared/sheetNavigation.native.js +97 -0
- package/dist/esm/shared/sheetNavigation.native.js.map +1 -0
- package/dist/esm/shared/systemControlModules.gnome.mjs +8 -0
- package/dist/esm/shared/systemControlModules.gnome.mjs.map +1 -0
- package/dist/esm/shared/systemControlModules.gnome.native.js +8 -0
- package/dist/esm/shared/systemControlModules.gnome.native.js.map +1 -0
- package/dist/esm/shared/systemControlModules.mjs +8 -0
- package/dist/esm/shared/systemControlModules.mjs.map +1 -0
- package/dist/esm/shared/systemControlModules.native.js +47 -0
- package/dist/esm/shared/systemControlModules.native.js.map +1 -0
- package/dist/esm/shared/systemControls.mjs +19 -0
- package/dist/esm/shared/systemControls.mjs.map +1 -0
- package/dist/esm/shared/systemControls.native.js +20 -0
- package/dist/esm/shared/systemControls.native.js.map +1 -0
- package/dist/jsx/FloatingPanel/RegularPanel.mjs +139 -0
- package/dist/jsx/FloatingPanel/RegularPanel.mjs.map +1 -0
- package/dist/jsx/FloatingPanel/RegularPanel.native.js +169 -0
- package/dist/jsx/FloatingPanel/RegularPanel.native.js.map +1 -0
- package/dist/jsx/FloatingPanel/compactFace.mjs +5 -1
- package/dist/jsx/FloatingPanel/compactFace.mjs.map +1 -1
- package/dist/jsx/FloatingPanel/compactFace.native.js +6 -0
- package/dist/jsx/FloatingPanel/compactFace.native.js.map +1 -1
- package/dist/jsx/FloatingPanel/containingBlock.mjs +21 -0
- package/dist/jsx/FloatingPanel/containingBlock.mjs.map +1 -0
- package/dist/jsx/FloatingPanel/containingBlock.native.js +51 -0
- package/dist/jsx/FloatingPanel/containingBlock.native.js.map +1 -0
- package/dist/jsx/FloatingPanel/index.mjs +1 -19
- package/dist/jsx/FloatingPanel/index.mjs.map +1 -1
- package/dist/jsx/FloatingPanel/index.native.js +79 -45
- package/dist/jsx/FloatingPanel/index.native.js.map +1 -1
- package/dist/jsx/FloatingPanel/pointAnchor.mjs +25 -1
- package/dist/jsx/FloatingPanel/pointAnchor.mjs.map +1 -1
- package/dist/jsx/FloatingPanel/pointAnchor.native.js +26 -0
- package/dist/jsx/FloatingPanel/pointAnchor.native.js.map +1 -1
- package/dist/jsx/FloatingPanel/regularPlacement.mjs +35 -0
- package/dist/jsx/FloatingPanel/regularPlacement.mjs.map +1 -0
- package/dist/jsx/FloatingPanel/regularPlacement.native.js +65 -0
- package/dist/jsx/FloatingPanel/regularPlacement.native.js.map +1 -0
- package/dist/jsx/fields/Combobox/index.mjs +9 -11
- package/dist/jsx/fields/Combobox/index.mjs.map +1 -1
- package/dist/jsx/fields/Combobox/index.native.js +10 -10
- package/dist/jsx/fields/Combobox/index.native.js.map +1 -1
- package/dist/jsx/fields/DatePicker/parts.mjs +1 -0
- package/dist/jsx/fields/DatePicker/parts.mjs.map +1 -1
- package/dist/jsx/fields/DatePicker/parts.native.js +3 -0
- package/dist/jsx/fields/DatePicker/parts.native.js.map +1 -1
- package/dist/jsx/fields/Input/index.mjs +6 -1
- package/dist/jsx/fields/Input/index.mjs.map +1 -1
- package/dist/jsx/fields/Input/index.native.js +5 -0
- package/dist/jsx/fields/Input/index.native.js.map +1 -1
- package/dist/jsx/fields/Select/InlineChoices.mjs +3 -2
- package/dist/jsx/fields/Select/InlineChoices.mjs.map +1 -1
- package/dist/jsx/fields/Select/InlineChoices.native.js +5 -2
- package/dist/jsx/fields/Select/InlineChoices.native.js.map +1 -1
- package/dist/jsx/fields/Select/SheetPickerRow.mjs +129 -0
- package/dist/jsx/fields/Select/SheetPickerRow.mjs.map +1 -0
- package/dist/jsx/fields/Select/SheetPickerRow.native.js +165 -0
- package/dist/jsx/fields/Select/SheetPickerRow.native.js.map +1 -0
- package/dist/jsx/fields/Select/index.mjs +66 -7
- package/dist/jsx/fields/Select/index.mjs.map +1 -1
- package/dist/jsx/fields/Select/index.native.js +86 -27
- package/dist/jsx/fields/Select/index.native.js.map +1 -1
- package/dist/jsx/fields/Select/shouldInlineChoices.mjs +12 -1
- package/dist/jsx/fields/Select/shouldInlineChoices.mjs.map +1 -1
- package/dist/jsx/fields/Select/shouldInlineChoices.native.js +15 -1
- package/dist/jsx/fields/Select/shouldInlineChoices.native.js.map +1 -1
- package/dist/jsx/fields/Slider/index.mjs +96 -4
- package/dist/jsx/fields/Slider/index.mjs.map +1 -1
- package/dist/jsx/fields/Slider/index.native.js +103 -5
- package/dist/jsx/fields/Slider/index.native.js.map +1 -1
- package/dist/jsx/fields/Switch/index.mjs +71 -1
- package/dist/jsx/fields/Switch/index.mjs.map +1 -1
- package/dist/jsx/fields/Switch/index.native.js +72 -1
- package/dist/jsx/fields/Switch/index.native.js.map +1 -1
- package/dist/jsx/fields/TimePicker/parts.mjs +1 -0
- package/dist/jsx/fields/TimePicker/parts.mjs.map +1 -1
- package/dist/jsx/fields/TimePicker/parts.native.js +1 -0
- package/dist/jsx/fields/TimePicker/parts.native.js.map +1 -1
- package/dist/jsx/fields/ToggleGroup/index.mjs +67 -1
- package/dist/jsx/fields/ToggleGroup/index.mjs.map +1 -1
- package/dist/jsx/fields/ToggleGroup/index.native.js +75 -1
- package/dist/jsx/fields/ToggleGroup/index.native.js.map +1 -1
- package/dist/jsx/fields/nativeDateTime/OsDateTimeSheet.mjs +36 -21
- package/dist/jsx/fields/nativeDateTime/OsDateTimeSheet.mjs.map +1 -1
- package/dist/jsx/fields/nativeDateTime/OsDateTimeSheet.native.js +44 -21
- package/dist/jsx/fields/nativeDateTime/OsDateTimeSheet.native.js.map +1 -1
- package/dist/jsx/fields/nativeDateTime/sheetKeyboard.mjs +21 -0
- package/dist/jsx/fields/nativeDateTime/sheetKeyboard.mjs.map +1 -0
- package/dist/jsx/fields/nativeDateTime/sheetKeyboard.native.js +57 -0
- package/dist/jsx/fields/nativeDateTime/sheetKeyboard.native.js.map +1 -0
- package/dist/jsx/index.js +3 -1
- package/dist/jsx/index.js.map +1 -1
- package/dist/jsx/index.mjs +3 -1
- package/dist/jsx/index.mjs.map +1 -1
- package/dist/jsx/index.native.js +11 -0
- package/dist/jsx/index.native.js.map +1 -1
- package/dist/jsx/shared/overlaySizeClass.mjs +14 -0
- package/dist/jsx/shared/overlaySizeClass.mjs.map +1 -0
- package/dist/jsx/shared/overlaySizeClass.native.js +45 -0
- package/dist/jsx/shared/overlaySizeClass.native.js.map +1 -0
- package/dist/jsx/shared/sheetNavigation.mjs +47 -0
- package/dist/jsx/shared/sheetNavigation.mjs.map +1 -0
- package/dist/jsx/shared/sheetNavigation.native.js +129 -0
- package/dist/jsx/shared/sheetNavigation.native.js.map +1 -0
- package/dist/jsx/shared/systemControlModules.gnome.mjs +8 -0
- package/dist/jsx/shared/systemControlModules.gnome.mjs.map +1 -0
- package/dist/jsx/shared/systemControlModules.gnome.native.js +37 -0
- package/dist/jsx/shared/systemControlModules.gnome.native.js.map +1 -0
- package/dist/jsx/shared/systemControlModules.mjs +8 -0
- package/dist/jsx/shared/systemControlModules.mjs.map +1 -0
- package/dist/jsx/shared/systemControlModules.native.js +77 -0
- package/dist/jsx/shared/systemControlModules.native.js.map +1 -0
- package/dist/jsx/shared/systemControls.mjs +19 -0
- package/dist/jsx/shared/systemControls.mjs.map +1 -0
- package/dist/jsx/shared/systemControls.native.js +51 -0
- package/dist/jsx/shared/systemControls.native.js.map +1 -0
- package/package.json +16 -8
- package/src/FloatingPanel/FloatingPanel.menuFace.native.spec.tsx +73 -3
- package/src/FloatingPanel/FloatingPanel.regularWidth.native.spec.tsx +193 -0
- package/src/FloatingPanel/RegularPanel.tsx +159 -0
- package/src/FloatingPanel/compactFace.spec.ts +24 -1
- package/src/FloatingPanel/compactFace.ts +16 -0
- package/src/FloatingPanel/containingBlock.ts +32 -0
- package/src/FloatingPanel/index.native.tsx +80 -35
- package/src/FloatingPanel/index.tsx +1 -33
- package/src/FloatingPanel/pointAnchor.spec.ts +29 -0
- package/src/FloatingPanel/pointAnchor.ts +30 -0
- package/src/FloatingPanel/regularPlacement.spec.ts +65 -0
- package/src/FloatingPanel/regularPlacement.ts +40 -0
- package/src/fields/Combobox/Combobox.regularWidth.native.spec.tsx +94 -0
- package/src/fields/Combobox/index.tsx +11 -34
- package/src/fields/DatePicker/parts.tsx +3 -0
- package/src/fields/DatePicker/touchWeb.spec.tsx +13 -0
- package/src/fields/Input/Input.formName.spec.tsx +53 -0
- package/src/fields/Input/index.tsx +6 -1
- package/src/fields/Select/InlineChoices.tsx +6 -3
- package/src/fields/Select/SheetPickerRow.tsx +160 -0
- package/src/fields/Select/index.tsx +67 -5
- package/src/fields/Select/shouldInlineChoices.spec.ts +21 -1
- package/src/fields/Select/shouldInlineChoices.ts +26 -0
- package/src/fields/Slider/Slider.system.native.spec.tsx +168 -0
- package/src/fields/Slider/index.tsx +166 -30
- package/src/fields/Switch/Switch.system.native.spec.tsx +162 -0
- package/src/fields/Switch/index.tsx +98 -0
- package/src/fields/TimePicker/parts.tsx +1 -0
- package/src/fields/ToggleGroup/ToggleGroup.system.native.spec.tsx +192 -0
- package/src/fields/ToggleGroup/index.tsx +111 -0
- package/src/fields/nativeDateTime/OsDateTimeSheet.presentation.spec.tsx +167 -0
- package/src/fields/nativeDateTime/OsDateTimeSheet.tsx +36 -18
- package/src/fields/nativeDateTime/sheetKeyboard.spec.ts +60 -0
- package/src/fields/nativeDateTime/sheetKeyboard.ts +45 -0
- package/src/index.ts +15 -0
- package/src/shared/overlaySizeClass.spec.ts +27 -0
- package/src/shared/overlaySizeClass.ts +36 -0
- package/src/shared/sheetNavigation.ts +82 -0
- package/src/shared/systemControlModules.gnome.ts +10 -0
- package/src/shared/systemControlModules.native.spec.ts +52 -0
- package/src/shared/systemControlModules.native.ts +66 -0
- package/src/shared/systemControlModules.ts +10 -0
- package/src/shared/systemControls.ts +70 -0
- package/types/FloatingPanel/RegularPanel.d.ts +29 -0
- package/types/FloatingPanel/RegularPanel.d.ts.map +1 -0
- package/types/FloatingPanel/compactFace.d.ts +12 -0
- package/types/FloatingPanel/compactFace.d.ts.map +1 -1
- package/types/FloatingPanel/containingBlock.d.ts +14 -0
- package/types/FloatingPanel/containingBlock.d.ts.map +1 -0
- package/types/FloatingPanel/index.d.ts.map +1 -1
- package/types/FloatingPanel/index.native.d.ts +6 -1
- package/types/FloatingPanel/index.native.d.ts.map +1 -1
- package/types/FloatingPanel/pointAnchor.d.ts +12 -0
- package/types/FloatingPanel/pointAnchor.d.ts.map +1 -1
- package/types/FloatingPanel/regularPlacement.d.ts +20 -0
- package/types/FloatingPanel/regularPlacement.d.ts.map +1 -0
- package/types/fields/Combobox/index.d.ts.map +1 -1
- package/types/fields/DatePicker/parts.d.ts.map +1 -1
- package/types/fields/Input/index.d.ts.map +1 -1
- package/types/fields/Select/InlineChoices.d.ts.map +1 -1
- package/types/fields/Select/SheetPickerRow.d.ts +23 -0
- package/types/fields/Select/SheetPickerRow.d.ts.map +1 -0
- package/types/fields/Select/index.d.ts.map +1 -1
- package/types/fields/Select/shouldInlineChoices.d.ts +15 -0
- package/types/fields/Select/shouldInlineChoices.d.ts.map +1 -1
- package/types/fields/Slider/index.d.ts +7 -1
- package/types/fields/Slider/index.d.ts.map +1 -1
- package/types/fields/Switch/index.d.ts +7 -1
- package/types/fields/Switch/index.d.ts.map +1 -1
- package/types/fields/TimePicker/parts.d.ts.map +1 -1
- package/types/fields/ToggleGroup/index.d.ts +8 -1
- package/types/fields/ToggleGroup/index.d.ts.map +1 -1
- package/types/fields/nativeDateTime/OsDateTimeSheet.d.ts.map +1 -1
- package/types/fields/nativeDateTime/sheetKeyboard.d.ts +20 -0
- package/types/fields/nativeDateTime/sheetKeyboard.d.ts.map +1 -0
- package/types/index.d.ts +4 -0
- package/types/index.d.ts.map +1 -1
- package/types/shared/overlaySizeClass.d.ts +24 -0
- package/types/shared/overlaySizeClass.d.ts.map +1 -0
- package/types/shared/sheetNavigation.d.ts +31 -0
- package/types/shared/sheetNavigation.d.ts.map +1 -0
- package/types/shared/systemControlModules.d.ts +5 -0
- package/types/shared/systemControlModules.d.ts.map +1 -0
- package/types/shared/systemControlModules.gnome.d.ts +5 -0
- package/types/shared/systemControlModules.gnome.d.ts.map +1 -0
- package/types/shared/systemControlModules.native.d.ts +20 -0
- package/types/shared/systemControlModules.native.d.ts.map +1 -0
- package/types/shared/systemControls.d.ts +53 -0
- package/types/shared/systemControls.d.ts.map +1 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { renderWithProviders } from "@multiplatform.one/test-utils";
|
|
2
|
+
import { createDefaultThemeConfig } from "@multiplatform.one/theme";
|
|
3
|
+
import { useForm } from "@tanstack/react-form";
|
|
4
|
+
import { cleanup, fireEvent, render } from "@testing-library/react";
|
|
5
|
+
import type { ReactElement } from "react";
|
|
6
|
+
import { TamaguiProvider } from "tamagui";
|
|
7
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
8
|
+
import { Form } from "../../Form";
|
|
9
|
+
import { ToggleGroup } from "./index";
|
|
10
|
+
|
|
11
|
+
const system = vi.hoisted(() => ({
|
|
12
|
+
os: "ios" as "ios" | "android" | null,
|
|
13
|
+
installed: true,
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
// UISegmentedControl as a probe: it shows the props it got and selects a
|
|
17
|
+
// segment through onChange, the way a tap on a segment does.
|
|
18
|
+
const SegmentedProbe = vi.hoisted(
|
|
19
|
+
() =>
|
|
20
|
+
function SegmentedProbe(props: Record<string, any>) {
|
|
21
|
+
return (
|
|
22
|
+
<div
|
|
23
|
+
data-testid="system-segmented"
|
|
24
|
+
data-selected={props.selectedIndex}
|
|
25
|
+
data-enabled={String(props.enabled)}
|
|
26
|
+
data-tint={props.tintColor}
|
|
27
|
+
data-ink={props.activeFontStyle?.color}
|
|
28
|
+
data-appearance={props.appearance}
|
|
29
|
+
data-label={props.accessibilityLabel ?? ""}
|
|
30
|
+
>
|
|
31
|
+
{props.values.map((value: string, index: number) => (
|
|
32
|
+
<button
|
|
33
|
+
key={value}
|
|
34
|
+
type="button"
|
|
35
|
+
onClick={() => props.onChange?.({ nativeEvent: { selectedSegmentIndex: index } })}
|
|
36
|
+
>
|
|
37
|
+
{value}
|
|
38
|
+
</button>
|
|
39
|
+
))}
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
},
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
vi.mock("../../shared/systemControls", async (importOriginal) => ({
|
|
46
|
+
...(await importOriginal<typeof import("../../shared/systemControls")>()),
|
|
47
|
+
systemControlPlatform: () => system.os,
|
|
48
|
+
}));
|
|
49
|
+
|
|
50
|
+
vi.mock("../../shared/systemControlModules", () => ({
|
|
51
|
+
loadSystemSlider: () => null,
|
|
52
|
+
loadSystemSegmentedControl: () => (system.installed ? SegmentedProbe : null),
|
|
53
|
+
}));
|
|
54
|
+
|
|
55
|
+
const mpoTheme = createDefaultThemeConfig();
|
|
56
|
+
const renderInScheme = (ui: ReactElement, scheme: "light" | "dark") =>
|
|
57
|
+
render(
|
|
58
|
+
<TamaguiProvider config={mpoTheme.tamagui} defaultTheme={scheme} disableInjectCSS>
|
|
59
|
+
{ui}
|
|
60
|
+
</TamaguiProvider>,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
afterEach(cleanup);
|
|
64
|
+
beforeEach(() => {
|
|
65
|
+
system.os = "ios";
|
|
66
|
+
system.installed = true;
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const views = [
|
|
70
|
+
{ label: "Day", value: "day" },
|
|
71
|
+
{ label: "Week", value: "week" },
|
|
72
|
+
{ label: "Month", value: "month" },
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
const literal = (value: string | null) => Boolean(value) && !value!.startsWith("$");
|
|
76
|
+
const segmented = (result: ReturnType<typeof renderWithProviders>) =>
|
|
77
|
+
result.queryByTestId("system-segmented");
|
|
78
|
+
|
|
79
|
+
function FormToggleGroup({ onSubmit }: { onSubmit: (value: { view: string }) => void }) {
|
|
80
|
+
const form = useForm({
|
|
81
|
+
defaultValues: { view: "day" },
|
|
82
|
+
onSubmit: ({ value }) => onSubmit(value),
|
|
83
|
+
});
|
|
84
|
+
return (
|
|
85
|
+
<Form form={form}>
|
|
86
|
+
<ToggleGroup name="view" label="View" options={views} />
|
|
87
|
+
<button type="button" onClick={() => form.handleSubmit()}>
|
|
88
|
+
Save
|
|
89
|
+
</button>
|
|
90
|
+
</Form>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// LC-266 PLATFORM-ERGONOMICS (FZ-8119). On iOS a short single choice is
|
|
95
|
+
// UISegmentedControl when the binary carries the module, painted with the
|
|
96
|
+
// LC-131 selected tint and ink. Without it, the MPO segmented control.
|
|
97
|
+
describe("ToggleGroup on iOS", () => {
|
|
98
|
+
it.each(["light", "dark"] as const)(
|
|
99
|
+
"renders UISegmentedControl with the %s theme's selected tint, ink and scheme",
|
|
100
|
+
(scheme) => {
|
|
101
|
+
const result = renderInScheme(
|
|
102
|
+
<ToggleGroup label="View" options={views} defaultValue="week" />,
|
|
103
|
+
scheme,
|
|
104
|
+
);
|
|
105
|
+
const view = result.getByTestId("system-segmented");
|
|
106
|
+
expect(view.dataset).toMatchObject({ selected: "1", enabled: "true", label: "View" });
|
|
107
|
+
expect(literal(view.getAttribute("data-tint"))).toBe(true);
|
|
108
|
+
expect(literal(view.getAttribute("data-ink"))).toBe(true);
|
|
109
|
+
expect(view.dataset.appearance).toBe(scheme);
|
|
110
|
+
},
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
it("selects uncontrolled and reports the option value", () => {
|
|
114
|
+
const onChange = vi.fn();
|
|
115
|
+
const result = renderWithProviders(
|
|
116
|
+
<ToggleGroup options={views} defaultValue="day" onChange={onChange} />,
|
|
117
|
+
);
|
|
118
|
+
fireEvent.click(result.getByText("Month"));
|
|
119
|
+
expect(onChange).toHaveBeenCalledExactlyOnceWith("month");
|
|
120
|
+
expect(segmented(result)!.dataset.selected).toBe("2");
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("follows a controlled value", () => {
|
|
124
|
+
const result = renderWithProviders(<ToggleGroup options={views} value="day" />);
|
|
125
|
+
fireEvent.click(result.getByText("Week"));
|
|
126
|
+
expect(segmented(result)!.dataset.selected).toBe("0");
|
|
127
|
+
result.rerender(<ToggleGroup options={views} value="month" />);
|
|
128
|
+
expect(segmented(result)!.dataset.selected).toBe("2");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("writes the form field it is bound to", async () => {
|
|
132
|
+
const onSubmit = vi.fn();
|
|
133
|
+
const result = renderWithProviders(<FormToggleGroup onSubmit={onSubmit} />);
|
|
134
|
+
expect(segmented(result)!.dataset.selected).toBe("0");
|
|
135
|
+
fireEvent.click(result.getByText("Week"));
|
|
136
|
+
fireEvent.click(result.getByText("Save"));
|
|
137
|
+
await vi.waitFor(() => expect(onSubmit).toHaveBeenCalledWith({ view: "week" }));
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("disables the control and never changes a disabled or read-only value", () => {
|
|
141
|
+
const onChange = vi.fn();
|
|
142
|
+
const result = renderWithProviders(
|
|
143
|
+
<>
|
|
144
|
+
<ToggleGroup
|
|
145
|
+
options={views}
|
|
146
|
+
value="day"
|
|
147
|
+
onChange={onChange}
|
|
148
|
+
disabled
|
|
149
|
+
disabledReason="Locked"
|
|
150
|
+
/>
|
|
151
|
+
<ToggleGroup options={views} value="day" onChange={onChange} readOnly />
|
|
152
|
+
</>,
|
|
153
|
+
);
|
|
154
|
+
const [disabledView, readOnlyView] = result.getAllByTestId("system-segmented");
|
|
155
|
+
expect(disabledView!.dataset.enabled).toBe("false");
|
|
156
|
+
expect(readOnlyView!.dataset.enabled).toBe("true");
|
|
157
|
+
for (const week of result.getAllByText("Week")) fireEvent.click(week);
|
|
158
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("falls back to the MPO control when the binary lacks the module", () => {
|
|
162
|
+
system.installed = false;
|
|
163
|
+
const result = renderWithProviders(<ToggleGroup options={views} defaultValue="day" />);
|
|
164
|
+
expect(segmented(result)).toBeNull();
|
|
165
|
+
expect(result.getByText("Week")).toBeTruthy();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("keeps the MPO control for multi-select, icons, a disabled segment, six segments and native={false}", () => {
|
|
169
|
+
const six = ["a", "b", "c", "d", "e", "f"].map((value) => ({ label: value, value }));
|
|
170
|
+
const result = renderWithProviders(
|
|
171
|
+
<>
|
|
172
|
+
<ToggleGroup type="multiple" options={views} defaultValue={["day"]} />
|
|
173
|
+
<ToggleGroup options={[{ ...views[0]!, icon: <span>i</span> }, views[1]!]} />
|
|
174
|
+
<ToggleGroup
|
|
175
|
+
options={[views[0]!, { ...views[1]!, disabled: true, disabledReason: "Locked" }]}
|
|
176
|
+
/>
|
|
177
|
+
<ToggleGroup options={six} />
|
|
178
|
+
<ToggleGroup options={views} native={false} />
|
|
179
|
+
</>,
|
|
180
|
+
);
|
|
181
|
+
expect(segmented(result)).toBeNull();
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("keeps the MPO control on Android, web and GNOME", () => {
|
|
185
|
+
for (const os of ["android", null] as const) {
|
|
186
|
+
system.os = os;
|
|
187
|
+
const result = renderWithProviders(<ToggleGroup options={views} defaultValue="day" />);
|
|
188
|
+
expect(segmented(result)).toBeNull();
|
|
189
|
+
result.unmount();
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
});
|
|
@@ -33,6 +33,13 @@ import {
|
|
|
33
33
|
} from "../../shared/colorRamps";
|
|
34
34
|
import { warnBareDisabled } from "../../shared/devWarn";
|
|
35
35
|
import { choiceItemA11y, composeA11yName } from "../../shared/nativeA11y";
|
|
36
|
+
import { loadSystemSegmentedControl } from "../../shared/systemControlModules";
|
|
37
|
+
import {
|
|
38
|
+
type SystemSegmentedControlComponent,
|
|
39
|
+
systemControlPlatform,
|
|
40
|
+
useSystemControlScheme,
|
|
41
|
+
useThemeLiteral,
|
|
42
|
+
} from "../../shared/systemControls";
|
|
36
43
|
import {
|
|
37
44
|
getElevationWrapperProps,
|
|
38
45
|
getFieldError,
|
|
@@ -100,6 +107,69 @@ function ToggleItemWithElevation({
|
|
|
100
107
|
return <ToggleItemFrame {...props} {...groupedRadius} />;
|
|
101
108
|
}
|
|
102
109
|
|
|
110
|
+
/** HIG: a segmented control holds about five segments at most. */
|
|
111
|
+
const SYSTEM_SEGMENT_LIMIT = 5;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* LC-266: UISegmentedControl, so iOS draws its own segmented control (iOS 26
|
|
115
|
+
* glass included) with the LC-131 selected paint: the soft accent tint behind
|
|
116
|
+
* the chosen segment and the accent ink on its label. It follows the app's
|
|
117
|
+
* scheme, not the OS's. It spans the row, as segmented controls in iOS forms do.
|
|
118
|
+
*/
|
|
119
|
+
function SystemSegmentedControl({
|
|
120
|
+
Component,
|
|
121
|
+
id,
|
|
122
|
+
options,
|
|
123
|
+
value,
|
|
124
|
+
controlled,
|
|
125
|
+
disabled,
|
|
126
|
+
readOnly,
|
|
127
|
+
accessibilityLabel,
|
|
128
|
+
accessibilityHint,
|
|
129
|
+
onSelect,
|
|
130
|
+
}: {
|
|
131
|
+
Component: SystemSegmentedControlComponent;
|
|
132
|
+
id: string;
|
|
133
|
+
options: ToggleGroupOption[];
|
|
134
|
+
value: string | undefined;
|
|
135
|
+
controlled: boolean;
|
|
136
|
+
disabled?: boolean;
|
|
137
|
+
readOnly?: boolean;
|
|
138
|
+
accessibilityLabel?: string;
|
|
139
|
+
accessibilityHint?: string;
|
|
140
|
+
onSelect: (value: string) => void;
|
|
141
|
+
}) {
|
|
142
|
+
const literal = useThemeLiteral();
|
|
143
|
+
const appearance = useSystemControlScheme();
|
|
144
|
+
const [uncontrolledValue, setUncontrolledValue] = useState(value);
|
|
145
|
+
const current = controlled ? value : uncontrolledValue;
|
|
146
|
+
return (
|
|
147
|
+
<View
|
|
148
|
+
alignSelf="stretch"
|
|
149
|
+
{...(readOnly ? { pointerEvents: "none" as const } : undefined)}
|
|
150
|
+
data-mp-system-control="segmented"
|
|
151
|
+
>
|
|
152
|
+
<Component
|
|
153
|
+
id={id}
|
|
154
|
+
values={options.map((option) => option.label)}
|
|
155
|
+
selectedIndex={options.findIndex((option) => option.value === current)}
|
|
156
|
+
enabled={!disabled}
|
|
157
|
+
appearance={appearance}
|
|
158
|
+
tintColor={literal("$selectedBackground")}
|
|
159
|
+
activeFontStyle={{ color: literal("$selectedColor") }}
|
|
160
|
+
accessibilityLabel={accessibilityLabel}
|
|
161
|
+
accessibilityHint={accessibilityHint}
|
|
162
|
+
onChange={(event) => {
|
|
163
|
+
const next = options[event.nativeEvent.selectedSegmentIndex]?.value;
|
|
164
|
+
if (next === undefined || disabled || readOnly) return;
|
|
165
|
+
if (!controlled) setUncontrolledValue(next);
|
|
166
|
+
onSelect(next);
|
|
167
|
+
}}
|
|
168
|
+
/>
|
|
169
|
+
</View>
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
103
173
|
export interface ToggleGroupOption {
|
|
104
174
|
label: string;
|
|
105
175
|
value: string;
|
|
@@ -177,6 +247,13 @@ export interface ToggleGroupProps {
|
|
|
177
247
|
"aria-labelledby"?: string;
|
|
178
248
|
/** Extra spoken context for the group (native `accessibilityHint`). */
|
|
179
249
|
accessibilityHint?: string;
|
|
250
|
+
/**
|
|
251
|
+
* LC-266: on iOS a single-choice group of two to five text segments, none
|
|
252
|
+
* disabled, renders UISegmentedControl when the binary carries
|
|
253
|
+
* `@react-native-segmented-control/segmented-control`. `false` keeps the MPO
|
|
254
|
+
* control there. Android keeps the MPO control, which is Material 3's form.
|
|
255
|
+
*/
|
|
256
|
+
native?: boolean;
|
|
180
257
|
}
|
|
181
258
|
|
|
182
259
|
export function ToggleGroup({
|
|
@@ -207,6 +284,7 @@ export function ToggleGroup({
|
|
|
207
284
|
accessibilityHint,
|
|
208
285
|
"aria-label": ariaLabel,
|
|
209
286
|
"aria-labelledby": ariaLabelledBy,
|
|
287
|
+
native = true,
|
|
210
288
|
}: ToggleGroupProps) {
|
|
211
289
|
// Canonical `onChange` and the alias `onValueChange` both fire from the
|
|
212
290
|
// single emit site below (same reconciliation as Switch).
|
|
@@ -289,6 +367,18 @@ export function ToggleGroup({
|
|
|
289
367
|
const index = uniqueItemReasons.indexOf(reason.trim());
|
|
290
368
|
return index >= 0 ? `${id}-item-reason-${index}` : undefined;
|
|
291
369
|
};
|
|
370
|
+
const SystemSegmented =
|
|
371
|
+
native &&
|
|
372
|
+
type === "single" &&
|
|
373
|
+
orientation === "horizontal" &&
|
|
374
|
+
!children &&
|
|
375
|
+
options &&
|
|
376
|
+
options.length >= 2 &&
|
|
377
|
+
options.length <= SYSTEM_SEGMENT_LIMIT &&
|
|
378
|
+
options.every((option) => !option.icon && !option.disabled) &&
|
|
379
|
+
systemControlPlatform() === "ios"
|
|
380
|
+
? loadSystemSegmentedControl()
|
|
381
|
+
: null;
|
|
292
382
|
const optionDisabledByValue = (val: string) =>
|
|
293
383
|
Boolean(options?.find((o) => o.value === val)?.disabled);
|
|
294
384
|
|
|
@@ -527,6 +617,27 @@ export function ToggleGroup({
|
|
|
527
617
|
) => {
|
|
528
618
|
const emptyValue = type === "multiple" ? [] : "";
|
|
529
619
|
|
|
620
|
+
if (SystemSegmented && options) {
|
|
621
|
+
return (
|
|
622
|
+
<SystemSegmentedControl
|
|
623
|
+
Component={SystemSegmented}
|
|
624
|
+
id={id}
|
|
625
|
+
options={options}
|
|
626
|
+
value={typeof resolvedValue === "string" ? resolvedValue : undefined}
|
|
627
|
+
controlled={isControlled}
|
|
628
|
+
disabled={disabled}
|
|
629
|
+
readOnly={readOnly}
|
|
630
|
+
accessibilityLabel={groupA11yName ?? (typeof label === "string" ? label : undefined)}
|
|
631
|
+
accessibilityHint={accessibilityHint}
|
|
632
|
+
onSelect={(val) => {
|
|
633
|
+
handleChange?.(val);
|
|
634
|
+
handleValueChange(val);
|
|
635
|
+
handleBlur?.();
|
|
636
|
+
}}
|
|
637
|
+
/>
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
|
|
530
641
|
const toggleProps: any = {
|
|
531
642
|
// LC-71: the ring rides the focused segment, never the group. A
|
|
532
643
|
// container ring is square over the rounded frame and paints on mouse
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the OS date/time sheet asks the native picker for. Every MPO date,
|
|
3
|
+
* time and datetime field on iOS and Android opens through this sheet, the
|
|
4
|
+
* Frappe `Date` field included (FZ-8000 retired its private copy).
|
|
5
|
+
*
|
|
6
|
+
* - iOS `date` opens the iOS 14+ calendar (`display="inline"`), not the legacy
|
|
7
|
+
* wheel; `time` keeps the wheel.
|
|
8
|
+
* - iOS `datetime` runs calendar → wheel across the sheet's Next button and
|
|
9
|
+
* commits one moment built from both halves.
|
|
10
|
+
* - The iOS sheet ends above the keyboard and the calendar shrinks to fit
|
|
11
|
+
* (MPO-64).
|
|
12
|
+
* - Android keeps `display="default"`, the Material dialog, and runs its own
|
|
13
|
+
* date-then-time sequence.
|
|
14
|
+
*
|
|
15
|
+
* The suite runs on web, so `react-native` resolves to `react-native-web`;
|
|
16
|
+
* the picker is recorded rather than rendered and the platform flags are mocked.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
20
|
+
|
|
21
|
+
const mockState = vi.hoisted(() => ({
|
|
22
|
+
os: "ios" as "ios" | "android",
|
|
23
|
+
/** Props of every mounted picker, newest last. */
|
|
24
|
+
picks: [] as Record<string, unknown>[],
|
|
25
|
+
keyboardInset: 0,
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
vi.mock("../../shared/useKeyboardInset", () => ({
|
|
29
|
+
useKeyboardInset: () => mockState.keyboardInset,
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
// jsdom lays nothing out, so react-native-web reports a 0pt window and the
|
|
33
|
+
// keyboard cap would floor the picker on both sides of the comparison.
|
|
34
|
+
vi.mock("react-native-web", async (importOriginal) => ({
|
|
35
|
+
...(await importOriginal<typeof import("react-native")>()),
|
|
36
|
+
useWindowDimensions: () => ({ width: 390, height: 844, scale: 3, fontScale: 1 }),
|
|
37
|
+
}));
|
|
38
|
+
|
|
39
|
+
vi.mock("./dateTimePickerLoader", () => ({
|
|
40
|
+
loadDateTimePicker: () => (props: Record<string, unknown>) => {
|
|
41
|
+
mockState.picks.push(props);
|
|
42
|
+
return null;
|
|
43
|
+
},
|
|
44
|
+
}));
|
|
45
|
+
|
|
46
|
+
vi.mock("@multiplatform.one/platform", async (importOriginal) => {
|
|
47
|
+
const actual = (await importOriginal()) as Record<string, unknown>;
|
|
48
|
+
const real = actual.platform as Record<string, unknown>;
|
|
49
|
+
return {
|
|
50
|
+
...actual,
|
|
51
|
+
platform: new Proxy(
|
|
52
|
+
{},
|
|
53
|
+
{
|
|
54
|
+
get(_target, prop) {
|
|
55
|
+
if (prop === "isIos") return mockState.os === "ios";
|
|
56
|
+
if (prop === "isAndroid") return mockState.os === "android";
|
|
57
|
+
return real[prop as string];
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
),
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
import { renderWithProviders } from "@multiplatform.one/test-utils";
|
|
65
|
+
import { act, cleanup, fireEvent, waitFor } from "@testing-library/react";
|
|
66
|
+
import { OsDateTimeSheet, type OsDateTimeMode } from "./OsDateTimeSheet";
|
|
67
|
+
|
|
68
|
+
const norm = (value: string) => value.replace(/\s+/gu, " ").trim();
|
|
69
|
+
|
|
70
|
+
async function press(text: string) {
|
|
71
|
+
let target: Element | null = null;
|
|
72
|
+
await waitFor(() => {
|
|
73
|
+
const node = Array.from(document.body.querySelectorAll("*")).find(
|
|
74
|
+
(el) => el.children.length === 0 && norm(el.textContent ?? "") === norm(text),
|
|
75
|
+
);
|
|
76
|
+
if (!node) throw new Error(`no element with text "${text}"`);
|
|
77
|
+
target = node.closest('button, [role="button"]') ?? node;
|
|
78
|
+
});
|
|
79
|
+
fireEvent.click(target!);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const lastPick = () => mockState.picks[mockState.picks.length - 1];
|
|
83
|
+
|
|
84
|
+
function open(mode: OsDateTimeMode, value: Date, onConfirm = vi.fn()) {
|
|
85
|
+
renderWithProviders(
|
|
86
|
+
<OsDateTimeSheet open mode={mode} value={value} onCancel={vi.fn()} onConfirm={onConfirm} />,
|
|
87
|
+
);
|
|
88
|
+
return onConfirm;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
beforeEach(() => {
|
|
92
|
+
cleanup();
|
|
93
|
+
mockState.picks.length = 0;
|
|
94
|
+
mockState.os = "ios";
|
|
95
|
+
mockState.keyboardInset = 0;
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe("OS date/time sheet — iOS presentation", () => {
|
|
99
|
+
it("a date opens the calendar, not the legacy wheel", () => {
|
|
100
|
+
open("date", new Date(2026, 2, 15));
|
|
101
|
+
expect(lastPick().mode).toBe("date");
|
|
102
|
+
expect(lastPick().display).toBe("inline");
|
|
103
|
+
expect(["light", "dark"]).toContain(lastPick().themeVariant);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("shrinks the calendar when the keyboard is up so the sheet can sit above it", () => {
|
|
107
|
+
open("date", new Date(2026, 2, 15));
|
|
108
|
+
const resting = (lastPick().style as { height: number }).height;
|
|
109
|
+
expect(resting).toBe(360);
|
|
110
|
+
cleanup();
|
|
111
|
+
mockState.picks.length = 0;
|
|
112
|
+
mockState.keyboardInset = 420;
|
|
113
|
+
open("date", new Date(2026, 2, 15));
|
|
114
|
+
const raised = (lastPick().style as { height: number }).height;
|
|
115
|
+
expect(raised).toBeLessThan(resting);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("a time keeps the wheel", () => {
|
|
119
|
+
open("time", new Date(2026, 2, 15, 9, 0));
|
|
120
|
+
expect(lastPick().mode).toBe("time");
|
|
121
|
+
expect(lastPick().display).toBe("spinner");
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("a datetime runs calendar then wheel, and commits both halves", async () => {
|
|
125
|
+
const onConfirm = open("datetime", new Date(2026, 2, 15, 14, 30));
|
|
126
|
+
expect(lastPick().mode).toBe("date");
|
|
127
|
+
expect(lastPick().display).toBe("inline");
|
|
128
|
+
|
|
129
|
+
await press("Next");
|
|
130
|
+
await waitFor(() => expect(lastPick().mode).toBe("time"));
|
|
131
|
+
expect(lastPick().display).toBe("spinner");
|
|
132
|
+
|
|
133
|
+
await press("Done");
|
|
134
|
+
expect(onConfirm).toHaveBeenCalledTimes(1);
|
|
135
|
+
expect(onConfirm).toHaveBeenCalledWith(new Date(2026, 2, 15, 14, 30));
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe("OS date/time sheet — Android presentation", () => {
|
|
140
|
+
it("keeps the Material dialog and mounts inline, without the iOS sheet", () => {
|
|
141
|
+
mockState.os = "android";
|
|
142
|
+
open("date", new Date(2026, 2, 15));
|
|
143
|
+
expect(lastPick().mode).toBe("date");
|
|
144
|
+
expect(lastPick().display).toBe("default");
|
|
145
|
+
expect(document.body.textContent).not.toContain("Done");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("a datetime advances from date to time after the first commit", async () => {
|
|
149
|
+
mockState.os = "android";
|
|
150
|
+
const onConfirm = open("datetime", new Date(2026, 2, 15, 14, 30));
|
|
151
|
+
expect(lastPick().mode).toBe("date");
|
|
152
|
+
|
|
153
|
+
const onPickerChange = lastPick().onChange as (event: unknown, date?: Date) => void;
|
|
154
|
+
await act(async () => {
|
|
155
|
+
onPickerChange({ type: "set" }, new Date(2026, 2, 20, 0, 0));
|
|
156
|
+
});
|
|
157
|
+
await waitFor(() => expect(lastPick().mode).toBe("time"));
|
|
158
|
+
expect(onConfirm).not.toHaveBeenCalled();
|
|
159
|
+
|
|
160
|
+
const onTimeChange = lastPick().onChange as (event: unknown, date?: Date) => void;
|
|
161
|
+
await act(async () => {
|
|
162
|
+
onTimeChange({ type: "set" }, new Date(2026, 0, 1, 9, 45));
|
|
163
|
+
});
|
|
164
|
+
expect(onConfirm).toHaveBeenCalledTimes(1);
|
|
165
|
+
expect(onConfirm).toHaveBeenCalledWith(new Date(2026, 2, 20, 9, 45, 0));
|
|
166
|
+
});
|
|
167
|
+
});
|
|
@@ -10,14 +10,25 @@
|
|
|
10
10
|
* does a missing `@react-native-community/datetimepicker` (an optional peer,
|
|
11
11
|
* loaded through `loadDateTimePicker`).
|
|
12
12
|
*/
|
|
13
|
-
import { useCallback, useState } from "react";
|
|
14
|
-
import {
|
|
13
|
+
import { useCallback, useContext, useState } from "react";
|
|
14
|
+
import {
|
|
15
|
+
Modal,
|
|
16
|
+
Pressable,
|
|
17
|
+
StyleSheet,
|
|
18
|
+
useColorScheme,
|
|
19
|
+
useWindowDimensions,
|
|
20
|
+
View as RNView,
|
|
21
|
+
} from "react-native";
|
|
22
|
+
import { platform } from "@multiplatform.one/platform";
|
|
23
|
+
import { SafeAreaInsetsContext, initialWindowMetrics } from "react-native-safe-area-context";
|
|
15
24
|
import { useResolvedKnobs, Text } from "@multiplatform.one/theme";
|
|
16
25
|
import { View, XStack, YStack, useTheme, useThemeName } from "tamagui";
|
|
17
26
|
import { formCommonColors } from "../../shared/colorRamps";
|
|
18
27
|
import { t } from "../../shared/t";
|
|
19
28
|
import { type DateTimePickerEvent, loadDateTimePicker } from "./dateTimePickerLoader";
|
|
20
29
|
import { osDateTimeIosDisplay } from "./osDateTimeIosDisplay";
|
|
30
|
+
import { datePickerHeight, dateSheetBottomPadding, dateSheetMaxHeight } from "./sheetKeyboard";
|
|
31
|
+
import { useKeyboardInset } from "../../shared/useKeyboardInset";
|
|
21
32
|
|
|
22
33
|
export type OsDateTimeMode = "date" | "time" | "datetime";
|
|
23
34
|
|
|
@@ -52,7 +63,12 @@ export function OsDateTimeSheet({
|
|
|
52
63
|
const themeVariant = useNativePickerScheme();
|
|
53
64
|
const theme = useTheme();
|
|
54
65
|
const accentColor = theme.accentColor?.val;
|
|
55
|
-
const isIOS =
|
|
66
|
+
const isIOS = platform.isIos;
|
|
67
|
+
// MPO-64: the sheet ends ABOVE the keyboard and the picker shrinks to fit.
|
|
68
|
+
const keyboardInset = useKeyboardInset();
|
|
69
|
+
const { height: windowHeight } = useWindowDimensions();
|
|
70
|
+
const topInset = useContext(SafeAreaInsetsContext)?.top ?? initialWindowMetrics?.insets.top ?? 0;
|
|
71
|
+
const maxHeight = dateSheetMaxHeight(windowHeight, keyboardInset, topInset);
|
|
56
72
|
const [showTimePicker, setShowTimePicker] = useState(false);
|
|
57
73
|
const [temp, setTemp] = useState(value);
|
|
58
74
|
const pickerMode: "date" | "time" =
|
|
@@ -172,21 +188,25 @@ export function OsDateTimeSheet({
|
|
|
172
188
|
|
|
173
189
|
return (
|
|
174
190
|
<Modal transparent animationType="fade" visible={open} onRequestClose={onCancel}>
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
/>
|
|
191
|
+
{/*
|
|
192
|
+
A plain padded column, not KeyboardAvoidingView: inside a Modal the
|
|
193
|
+
latter pads the container while the sheet keeps its own height, so the
|
|
194
|
+
sheet overflows off the top instead of shrinking (MPO-64).
|
|
195
|
+
*/}
|
|
196
|
+
<RNView style={[styles.modalOverlay, { paddingBottom: keyboardInset }]}>
|
|
197
|
+
<Pressable style={StyleSheet.absoluteFill} onPress={onCancel}>
|
|
198
|
+
<View pointerEvents="none" flex={1} backgroundColor="$shadow6" />
|
|
199
|
+
</Pressable>
|
|
185
200
|
<Pressable onPress={(e) => e.stopPropagation()}>
|
|
186
201
|
<YStack
|
|
202
|
+
testID="os-date-time-sheet"
|
|
187
203
|
{...knobProps.elevatedSurface}
|
|
188
204
|
borderBottomLeftRadius={0}
|
|
189
205
|
borderBottomRightRadius={0}
|
|
206
|
+
maxHeight={maxHeight}
|
|
207
|
+
flexShrink={1}
|
|
208
|
+
minHeight={0}
|
|
209
|
+
paddingBottom={dateSheetBottomPadding(keyboardInset, isIOS)}
|
|
190
210
|
>
|
|
191
211
|
<XStack justifyContent="space-between" {...knobProps.panelPadding}>
|
|
192
212
|
<Pressable onPress={onCancel} accessibilityRole="button">
|
|
@@ -202,7 +222,7 @@ export function OsDateTimeSheet({
|
|
|
202
222
|
</Pressable>
|
|
203
223
|
)}
|
|
204
224
|
</XStack>
|
|
205
|
-
<YStack {...knobProps.panelPadding}>
|
|
225
|
+
<YStack {...knobProps.panelPadding} flexShrink={1} minHeight={0}>
|
|
206
226
|
<DateTimePicker
|
|
207
227
|
value={temp}
|
|
208
228
|
mode={pickerMode}
|
|
@@ -212,12 +232,12 @@ export function OsDateTimeSheet({
|
|
|
212
232
|
onChange={handleIOSChange}
|
|
213
233
|
minimumDate={mode !== "time" ? minDate : undefined}
|
|
214
234
|
maximumDate={mode !== "time" ? maxDate : undefined}
|
|
215
|
-
style={
|
|
235
|
+
style={{ width: "100%", height: datePickerHeight(pickerMode, maxHeight) }}
|
|
216
236
|
/>
|
|
217
237
|
</YStack>
|
|
218
238
|
</YStack>
|
|
219
239
|
</Pressable>
|
|
220
|
-
</
|
|
240
|
+
</RNView>
|
|
221
241
|
</Modal>
|
|
222
242
|
);
|
|
223
243
|
}
|
|
@@ -227,6 +247,4 @@ const styles = StyleSheet.create({
|
|
|
227
247
|
flex: 1,
|
|
228
248
|
justifyContent: "flex-end",
|
|
229
249
|
},
|
|
230
|
-
wheel: { height: 216 },
|
|
231
|
-
calendar: { height: 360 },
|
|
232
250
|
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MPO-64 leftover: the OS date/time iOS sheet must use the same keyboard
|
|
3
|
+
* cap as FloatingPanel / SheetModal. KeyboardAvoidingView cannot shrink a
|
|
4
|
+
* fixed-height calendar, so the cap is the only thing that keeps the sheet
|
|
5
|
+
* above the suggestion strip.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, expect, it } from "vitest";
|
|
8
|
+
import {
|
|
9
|
+
datePickerHeight,
|
|
10
|
+
dateSheetBottomPadding,
|
|
11
|
+
dateSheetMaxHeight,
|
|
12
|
+
DATE_PICKER_CALENDAR_HEIGHT,
|
|
13
|
+
DATE_SHEET_BACKDROP_STRIP,
|
|
14
|
+
DATE_SHEET_MIN_HEIGHT,
|
|
15
|
+
} from "./sheetKeyboard";
|
|
16
|
+
|
|
17
|
+
describe("dateSheetMaxHeight", () => {
|
|
18
|
+
it("uses the 85% detent when the keyboard is down", () => {
|
|
19
|
+
expect(dateSheetMaxHeight(800, 0, 47)).toBe(Math.round(800 * 0.85));
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("caps to the space above the keyboard, less the 48pt backdrop strip", () => {
|
|
23
|
+
expect(dateSheetMaxHeight(800, 400, 0)).toBe(800 - 400 - DATE_SHEET_BACKDROP_STRIP);
|
|
24
|
+
expect(dateSheetMaxHeight(800, 400, 0)).toBeLessThan(dateSheetMaxHeight(800, 0, 0));
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("keeps the backdrop strip below the status bar, not inside it", () => {
|
|
28
|
+
const windowHeight = 874;
|
|
29
|
+
const keyboardInset = 335;
|
|
30
|
+
const topInset = 62;
|
|
31
|
+
const sheetTop =
|
|
32
|
+
windowHeight - keyboardInset - dateSheetMaxHeight(windowHeight, keyboardInset, topInset);
|
|
33
|
+
expect(sheetTop - topInset).toBe(DATE_SHEET_BACKDROP_STRIP);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("never collapses below the minimum sheet height", () => {
|
|
37
|
+
expect(dateSheetMaxHeight(800, 700, 47)).toBe(DATE_SHEET_MIN_HEIGHT);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe("datePickerHeight", () => {
|
|
42
|
+
it("keeps the calendar grid when the sheet is tall enough", () => {
|
|
43
|
+
expect(datePickerHeight("date", 700)).toBe(DATE_PICKER_CALENDAR_HEIGHT);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("shrinks the calendar so the sheet can sit above the keyboard", () => {
|
|
47
|
+
const maxHeight = dateSheetMaxHeight(800, 400, 47);
|
|
48
|
+
const height = datePickerHeight("date", maxHeight);
|
|
49
|
+
expect(height).toBeLessThan(DATE_PICKER_CALENDAR_HEIGHT);
|
|
50
|
+
expect(height).toBeLessThanOrEqual(maxHeight);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe("dateSheetBottomPadding", () => {
|
|
55
|
+
it("drops the home-indicator inset while the keyboard covers it", () => {
|
|
56
|
+
expect(dateSheetBottomPadding(0, true)).toBe(24);
|
|
57
|
+
expect(dateSheetBottomPadding(336, true)).toBe(8);
|
|
58
|
+
expect(dateSheetBottomPadding(0, false)).toBe(16);
|
|
59
|
+
});
|
|
60
|
+
});
|