react-native-platform-components 1.5.0 → 1.7.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/README.md +19 -18
- package/android/src/main/java/com/platformcomponents/PCActionSheetView.kt +309 -0
- package/android/src/main/java/com/platformcomponents/PCActionSheetViewManager.kt +131 -0
- package/android/src/main/java/com/platformcomponents/PCButtonGroupView.kt +5 -1
- package/android/src/main/java/com/platformcomponents/PCButtonView.kt +5 -1
- package/android/src/main/java/com/platformcomponents/PCContextMenuView.kt +82 -27
- package/android/src/main/java/com/platformcomponents/PCContextMenuViewManager.kt +12 -13
- package/android/src/main/java/com/platformcomponents/PCMenuSupport.kt +66 -2
- package/android/src/main/java/com/platformcomponents/PlatformComponentsPackage.kt +1 -0
- package/android/src/main/res/values/ids.xml +2 -0
- package/ios/PCActionSheet.h +12 -0
- package/ios/PCActionSheet.mm +160 -0
- package/ios/PCActionSheetView.swift +280 -0
- package/ios/PCContextMenu.mm +22 -1
- package/ios/PCContextMenu.swift +218 -15
- package/ios/PCMenuItems.h +2 -1
- package/ios/PCMenuSupport.swift +22 -3
- package/lib/commonjs/ActionSheet.js +85 -0
- package/lib/commonjs/ActionSheet.js.map +1 -0
- package/lib/commonjs/ActionSheetNativeComponent.ts +94 -0
- package/lib/commonjs/ButtonGroupNativeComponent.ts +2 -0
- package/lib/commonjs/ButtonNativeComponent.ts +2 -0
- package/lib/commonjs/ContextMenu.js +50 -4
- package/lib/commonjs/ContextMenu.js.map +1 -1
- package/lib/commonjs/ContextMenuNativeComponent.ts +29 -8
- package/lib/commonjs/index.js +11 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +7 -0
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/jest.js +92 -5
- package/lib/commonjs/jest.js.map +1 -1
- package/lib/commonjs/menuItems.js +2 -1
- package/lib/commonjs/menuItems.js.map +1 -1
- package/lib/commonjs/selectionIdentifiers.js.map +1 -1
- package/lib/commonjs/web/ActionSheet.js +131 -0
- package/lib/commonjs/web/ActionSheet.js.map +1 -0
- package/lib/commonjs/web/components.js +3 -1
- package/lib/commonjs/web/components.js.map +1 -1
- package/lib/commonjs/web/containers.js +1 -0
- package/lib/commonjs/web/containers.js.map +1 -1
- package/lib/module/ActionSheet.js +78 -0
- package/lib/module/ActionSheet.js.map +1 -0
- package/lib/module/ActionSheetNativeComponent.ts +94 -0
- package/lib/module/ButtonGroupNativeComponent.ts +2 -0
- package/lib/module/ButtonNativeComponent.ts +2 -0
- package/lib/module/ContextMenu.js +51 -4
- package/lib/module/ContextMenu.js.map +1 -1
- package/lib/module/ContextMenuNativeComponent.ts +29 -8
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +1 -1
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/jest.js +90 -5
- package/lib/module/jest.js.map +1 -1
- package/lib/module/menuItems.js +2 -1
- package/lib/module/menuItems.js.map +1 -1
- package/lib/module/selectionIdentifiers.js.map +1 -1
- package/lib/module/web/ActionSheet.js +126 -0
- package/lib/module/web/ActionSheet.js.map +1 -0
- package/lib/module/web/components.js +2 -0
- package/lib/module/web/components.js.map +1 -1
- package/lib/module/web/containers.js +1 -0
- package/lib/module/web/containers.js.map +1 -1
- package/lib/typescript/commonjs/docs/sidebars.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/ActionSheet.d.ts +98 -0
- package/lib/typescript/commonjs/src/ActionSheet.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/ActionSheetNativeComponent.d.ts +78 -0
- package/lib/typescript/commonjs/src/ActionSheetNativeComponent.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/ButtonGroupNativeComponent.d.ts +2 -0
- package/lib/typescript/commonjs/src/ButtonGroupNativeComponent.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/ButtonNativeComponent.d.ts +2 -0
- package/lib/typescript/commonjs/src/ButtonNativeComponent.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/ContextMenu.d.ts +22 -6
- package/lib/typescript/commonjs/src/ContextMenu.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/ContextMenuNativeComponent.d.ts +20 -7
- package/lib/typescript/commonjs/src/ContextMenuNativeComponent.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/index.d.ts +1 -0
- package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/index.web.d.ts +2 -1
- package/lib/typescript/commonjs/src/index.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/jest.d.ts +17 -3
- package/lib/typescript/commonjs/src/jest.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/menuItems.d.ts +12 -0
- package/lib/typescript/commonjs/src/menuItems.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/selectionIdentifiers.d.ts +1 -1
- package/lib/typescript/commonjs/src/selectionIdentifiers.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/web/ActionSheet.d.ts +11 -0
- package/lib/typescript/commonjs/src/web/ActionSheet.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/web/components.d.ts +1 -0
- package/lib/typescript/commonjs/src/web/components.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/web/containers.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/webComponents.d.ts +2 -0
- package/lib/typescript/commonjs/src/webComponents.d.ts.map +1 -1
- package/lib/typescript/module/docs/sidebars.d.ts.map +1 -1
- package/lib/typescript/module/src/ActionSheet.d.ts +98 -0
- package/lib/typescript/module/src/ActionSheet.d.ts.map +1 -0
- package/lib/typescript/module/src/ActionSheetNativeComponent.d.ts +78 -0
- package/lib/typescript/module/src/ActionSheetNativeComponent.d.ts.map +1 -0
- package/lib/typescript/module/src/ButtonGroupNativeComponent.d.ts +2 -0
- package/lib/typescript/module/src/ButtonGroupNativeComponent.d.ts.map +1 -1
- package/lib/typescript/module/src/ButtonNativeComponent.d.ts +2 -0
- package/lib/typescript/module/src/ButtonNativeComponent.d.ts.map +1 -1
- package/lib/typescript/module/src/ContextMenu.d.ts +22 -6
- package/lib/typescript/module/src/ContextMenu.d.ts.map +1 -1
- package/lib/typescript/module/src/ContextMenuNativeComponent.d.ts +20 -7
- package/lib/typescript/module/src/ContextMenuNativeComponent.d.ts.map +1 -1
- package/lib/typescript/module/src/index.d.ts +1 -0
- package/lib/typescript/module/src/index.d.ts.map +1 -1
- package/lib/typescript/module/src/index.web.d.ts +2 -1
- package/lib/typescript/module/src/index.web.d.ts.map +1 -1
- package/lib/typescript/module/src/jest.d.ts +17 -3
- package/lib/typescript/module/src/jest.d.ts.map +1 -1
- package/lib/typescript/module/src/menuItems.d.ts +12 -0
- package/lib/typescript/module/src/menuItems.d.ts.map +1 -1
- package/lib/typescript/module/src/selectionIdentifiers.d.ts +1 -1
- package/lib/typescript/module/src/selectionIdentifiers.d.ts.map +1 -1
- package/lib/typescript/module/src/web/ActionSheet.d.ts +11 -0
- package/lib/typescript/module/src/web/ActionSheet.d.ts.map +1 -0
- package/lib/typescript/module/src/web/components.d.ts +1 -0
- package/lib/typescript/module/src/web/components.d.ts.map +1 -1
- package/lib/typescript/module/src/web/containers.d.ts.map +1 -1
- package/lib/typescript/module/src/webComponents.d.ts +2 -0
- package/lib/typescript/module/src/webComponents.d.ts.map +1 -1
- package/package.json +9 -2
- package/plugin/build/index.cjs +79 -6
- package/plugin/build/index.d.ts +24 -0
- package/plugin/build/index.d.ts.map +1 -1
- package/plugin/tsconfig.json +2 -1
- package/src/ActionSheet.tsx +193 -0
- package/src/ActionSheetNativeComponent.ts +94 -0
- package/src/ButtonGroupNativeComponent.ts +2 -0
- package/src/ButtonNativeComponent.ts +2 -0
- package/src/ContextMenu.tsx +71 -12
- package/src/ContextMenuNativeComponent.ts +29 -8
- package/src/index.tsx +1 -0
- package/src/index.web.tsx +2 -0
- package/src/jest.tsx +108 -4
- package/src/menuItems.ts +13 -0
- package/src/selectionIdentifiers.ts +1 -1
- package/src/web/ActionSheet.tsx +130 -0
- package/src/web/components.tsx +5 -0
- package/src/web/containers.tsx +1 -0
- package/src/webComponents.ts +2 -0
package/README.md
CHANGED
|
@@ -5,20 +5,20 @@
|
|
|
5
5
|
[](https://github.com/JarX-Concepts/react-native-platform-components/actions/workflows/ci.yml)
|
|
6
6
|
[](./LICENSE)
|
|
7
7
|
|
|
8
|
-
Native **TextField**, **DatePicker**, **ContextMenu**, **SelectionMenu**, **SegmentedControl**, **TabBar**, **NavigationRail**, **Button**, **ButtonGroup**, **FloatingActionButton**, **FloatingToolbar** and **LiquidGlass** for React Native. Every component is the real platform widget on both iOS and Android (UIKit on iOS, Material 3 Expressive on Android), behind one typed, declarative API. No JavaScript re-implementations.
|
|
8
|
+
Native **TextField**, **DatePicker**, **ContextMenu**, **ActionSheet**, **SelectionMenu**, **SegmentedControl**, **TabBar**, **NavigationRail**, **Button**, **ButtonGroup**, **FloatingActionButton**, **FloatingToolbar** and **LiquidGlass** for React Native. Every component is the real platform widget on both iOS and Android (UIKit on iOS, Material 3 Expressive on Android), behind one typed, declarative API. No JavaScript re-implementations.
|
|
9
9
|
|
|
10
10
|
<p align="center">
|
|
11
11
|
<a href="https://jarx-concepts.github.io/react-native-platform-components">
|
|
12
12
|
<picture>
|
|
13
13
|
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/JarX-Concepts/react-native-platform-components/main/assets/hero-dark.webp" />
|
|
14
|
-
<img src="https://raw.githubusercontent.com/JarX-Concepts/react-native-platform-components/main/assets/hero-light.webp" alt="DatePicker, TextField, SegmentedControl, Button, TabBar, NavigationRail, FloatingActionButton, ContextMenu,
|
|
14
|
+
<img src="https://raw.githubusercontent.com/JarX-Concepts/react-native-platform-components/main/assets/hero-light.webp" alt="DatePicker, TextField, SegmentedControl, Button, TabBar, NavigationRail, FloatingActionButton, ContextMenu, ActionSheet, FloatingToolbar, LiquidGlass and SelectionMenu on iOS and Android, and the dark mode from useNativeTheme" />
|
|
15
15
|
</picture>
|
|
16
16
|
</a>
|
|
17
17
|
</p>
|
|
18
18
|
<p align="center"><sub>Every tile is the platform's own widget, captured from the example app. The <a href="https://jarx-concepts.github.io/react-native-platform-components">docs site</a> has full recordings of each component on both platforms; <a href="https://jarx-concepts.github.io/react-native-platform-components/components/liquidglass">LiquidGlass</a> is iOS 26 only.</sub></p>
|
|
19
19
|
|
|
20
20
|
<p align="center">
|
|
21
|
-
<a href="https://jarx-concepts.github.io/react-native-platform-components"><img src="https://raw.githubusercontent.com/JarX-Concepts/react-native-platform-components/main/assets/showreel.gif" width="640" alt="Showreel: the native theme changing brand color and switching to dark mode, then DatePicker, TextField, SegmentedControl, TabBar, NavigationRail, Button, FloatingActionButton, ContextMenu, SelectionMenu, FloatingToolbar and LiquidGlass in use, iOS on the left and Android on the right" /></a>
|
|
21
|
+
<a href="https://jarx-concepts.github.io/react-native-platform-components"><img src="https://raw.githubusercontent.com/JarX-Concepts/react-native-platform-components/main/assets/showreel.gif" width="640" alt="Showreel: the native theme changing brand color and switching to dark mode, then DatePicker, TextField, SegmentedControl, TabBar, NavigationRail, Button, FloatingActionButton, ContextMenu, ActionSheet, SelectionMenu, FloatingToolbar and LiquidGlass in use, iOS on the left and Android on the right" /></a>
|
|
22
22
|
</p>
|
|
23
23
|
<p align="center"><sub>iOS on the left, Android on the right. Recorded by the Detox suite in <code>example/</code>.</sub></p>
|
|
24
24
|
|
|
@@ -43,20 +43,21 @@ That renders `UISegmentedControl` on iOS and Material 3 segmented buttons (`Mate
|
|
|
43
43
|
|
|
44
44
|
## Components
|
|
45
45
|
|
|
46
|
-
| Component | iOS | Android | Instead of
|
|
47
|
-
| ---------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
|
|
48
|
-
| [**TextField**](https://jarx-concepts.github.io/react-native-platform-components/components/textfield) | `UITextField` / `UITextView`, with Writing Tools and the iOS 17–18 text traits | Material 3 `TextInputLayout`: outlined or filled, floating label, supporting text, counter | `TextInput` (a bare `EditText` on Android); `react-native-paper` (drawn in JavaScript)
|
|
49
|
-
| [**DatePicker**](https://jarx-concepts.github.io/react-native-platform-components/components/datepicker) | `UIDatePicker`, modal or embedded | `MaterialDatePicker` / `MaterialTimePicker`, or the AppCompat dialogs | `@react-native-community/datetimepicker` (no Material 3 pickers)
|
|
50
|
-
| [**SelectionMenu**](https://jarx-concepts.github.io/react-native-platform-components/components/selectionmenu) | System menus | Material exposed dropdown or `Spinner` | `@react-native-picker/picker` (wheel or dialog, no menu)
|
|
51
|
-
| [**ContextMenu**](https://jarx-concepts.github.io/react-native-platform-components/components/contextmenu) | `UIContextMenuInteraction` | `PopupMenu` | `zeego` (through two more native packages)
|
|
52
|
-
| [**
|
|
53
|
-
| [**
|
|
54
|
-
| [**
|
|
55
|
-
| [**
|
|
56
|
-
| [**
|
|
57
|
-
| [**
|
|
58
|
-
| [**
|
|
59
|
-
| [**
|
|
46
|
+
| Component | iOS | Android | Instead of |
|
|
47
|
+
| ---------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
|
|
48
|
+
| [**TextField**](https://jarx-concepts.github.io/react-native-platform-components/components/textfield) | `UITextField` / `UITextView`, with Writing Tools and the iOS 17–18 text traits | Material 3 `TextInputLayout`: outlined or filled, floating label, supporting text, counter | `TextInput` (a bare `EditText` on Android); `react-native-paper` (drawn in JavaScript) |
|
|
49
|
+
| [**DatePicker**](https://jarx-concepts.github.io/react-native-platform-components/components/datepicker) | `UIDatePicker`, modal or embedded | `MaterialDatePicker` / `MaterialTimePicker`, or the AppCompat dialogs | `@react-native-community/datetimepicker` (no Material 3 pickers) |
|
|
50
|
+
| [**SelectionMenu**](https://jarx-concepts.github.io/react-native-platform-components/components/selectionmenu) | System menus | Material exposed dropdown or `Spinner` | `@react-native-picker/picker` (wheel or dialog, no menu) |
|
|
51
|
+
| [**ContextMenu**](https://jarx-concepts.github.io/react-native-platform-components/components/contextmenu) | `UIContextMenuInteraction` | `PopupMenu` | `zeego` (through two more native packages) |
|
|
52
|
+
| [**ActionSheet**](https://jarx-concepts.github.io/react-native-platform-components/components/actionsheet) | `UIAlertController` (`.actionSheet`), a popover pointing at its trigger on iPad and iOS 26 | Material 3 modal bottom sheet (`BottomSheetDialog`) with icons | `react-native-actions-sheet` (drawn in JavaScript); `ActionSheetIOS` (iOS only, no testIDs or anchor) |
|
|
53
|
+
| [**SegmentedControl**](https://jarx-concepts.github.io/react-native-platform-components/components/segmentedcontrol) | `UISegmentedControl` | Material 3 Expressive connected buttons, or classic segmented buttons | `@react-native-segmented-control/segmented-control` (JavaScript on Android) |
|
|
54
|
+
| [**TabBar**](https://jarx-concepts.github.io/react-native-platform-components/components/tabbar) | `UITabBar`, the floating Liquid Glass bar on iOS 26 | Material 3 navigation bar | React Navigation's bottom tabs (JavaScript); `react-native-bottom-tabs` (tied to its navigator) |
|
|
55
|
+
| [**NavigationRail**](https://jarx-concepts.github.io/react-native-platform-components/components/navigationrail) | A column of tab-style `UIButton`s (iOS has no rail; use TabBar on iPhone) | Material 3 Expressive `NavigationRailView`: header, badges, menu gravity, expanded rail | React Navigation's drawer or a custom view (JavaScript) |
|
|
56
|
+
| [**Button**](https://jarx-concepts.github.io/react-native-platform-components/components/button) | `UIButton` configurations, Liquid Glass variants on iOS 26 | Material 3 Expressive `MaterialButton`: five sizes, shape morph | `react-native-paper` (Material 3, not Expressive, in JavaScript) |
|
|
57
|
+
| [**ButtonGroup**](https://jarx-concepts.github.io/react-native-platform-components/components/buttongroup) | A row of `UIButton`s with a "…" overflow menu; SplitButton: a button and a chevron `UIMenu` button | Material 3 Expressive button groups, single or multiple selection, overflow menu; `MaterialSplitButton` | `react-native-paper` segmented buttons (JavaScript) |
|
|
58
|
+
| [**FloatingActionButton**](https://jarx-concepts.github.io/react-native-platform-components/components/floatingactionbutton) | A round prominent `UIButton`, Liquid Glass on iOS 26 (iOS has no FAB) | Material `FloatingActionButton` / `ExtendedFloatingActionButton`: four sizes, shrink on scroll | `react-native-paper` FAB (drawn in JavaScript) |
|
|
59
|
+
| [**FloatingToolbar**](https://jarx-concepts.github.io/react-native-platform-components/components/floatingtoolbar) | Liquid Glass capsule on iOS 26, blur before | Material 3 Expressive `FloatingToolbarLayout` | A custom view |
|
|
60
|
+
| [**LiquidGlass**](https://jarx-concepts.github.io/react-native-platform-components/components/liquidglass) | `UIGlassEffect` on iOS 26+; `LiquidGlassContainer` merges and morphs it (`UIGlassContainerEffect`) | A fallback `View` | `@callstack/liquid-glass`, `expo-glass-effect` |
|
|
60
61
|
|
|
61
62
|
One package and one API shape: platform-only props go under `ios={{ }}` and `android={{ }}`, with Fabric + Codegen typed bindings, an Expo config plugin and TypeScript, instead of four or five packages with their own conventions, install steps and upgrade cadences.
|
|
62
63
|
|
|
@@ -136,7 +137,7 @@ Full details: [Installation](https://jarx-concepts.github.io/react-native-platfo
|
|
|
136
137
|
Everything else lives on the documentation site:
|
|
137
138
|
|
|
138
139
|
- [Quick Start](https://jarx-concepts.github.io/react-native-platform-components/quick-start): copy-paste examples for every component
|
|
139
|
-
- [DatePicker](https://jarx-concepts.github.io/react-native-platform-components/components/datepicker), [ContextMenu](https://jarx-concepts.github.io/react-native-platform-components/components/contextmenu), [SelectionMenu](https://jarx-concepts.github.io/react-native-platform-components/components/selectionmenu), [SegmentedControl](https://jarx-concepts.github.io/react-native-platform-components/components/segmentedcontrol), [Button](https://jarx-concepts.github.io/react-native-platform-components/components/button), [ButtonGroup](https://jarx-concepts.github.io/react-native-platform-components/components/buttongroup), [FloatingToolbar](https://jarx-concepts.github.io/react-native-platform-components/components/floatingtoolbar), [LiquidGlass](https://jarx-concepts.github.io/react-native-platform-components/components/liquidglass): props, platform options and behavior
|
|
140
|
+
- [DatePicker](https://jarx-concepts.github.io/react-native-platform-components/components/datepicker), [ContextMenu](https://jarx-concepts.github.io/react-native-platform-components/components/contextmenu), [ActionSheet](https://jarx-concepts.github.io/react-native-platform-components/components/actionsheet), [SelectionMenu](https://jarx-concepts.github.io/react-native-platform-components/components/selectionmenu), [SegmentedControl](https://jarx-concepts.github.io/react-native-platform-components/components/segmentedcontrol), [Button](https://jarx-concepts.github.io/react-native-platform-components/components/button), [ButtonGroup](https://jarx-concepts.github.io/react-native-platform-components/components/buttongroup), [FloatingToolbar](https://jarx-concepts.github.io/react-native-platform-components/components/floatingtoolbar), [LiquidGlass](https://jarx-concepts.github.io/react-native-platform-components/components/liquidglass): props, platform options and behavior
|
|
140
141
|
- [Android Theme Configuration](https://jarx-concepts.github.io/react-native-platform-components/guides/android-theme): Material 3 setup and what happens without it
|
|
141
142
|
- [Icons](https://jarx-concepts.github.io/react-native-platform-components/guides/icons): SF Symbols, drawables and images
|
|
142
143
|
- [Theming and Colors](https://jarx-concepts.github.io/react-native-platform-components/guides/theming): light/dark mode and your brand color from JavaScript (`useNativeTheme`), accepted color formats
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
package com.platformcomponents
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.content.res.ColorStateList
|
|
5
|
+
import android.graphics.drawable.Drawable
|
|
6
|
+
import android.util.Log
|
|
7
|
+
import android.util.TypedValue
|
|
8
|
+
import android.view.Gravity
|
|
9
|
+
import android.view.View
|
|
10
|
+
import android.view.ViewGroup
|
|
11
|
+
import android.widget.ImageView
|
|
12
|
+
import android.widget.LinearLayout
|
|
13
|
+
import android.widget.TextView
|
|
14
|
+
import androidx.core.view.ViewCompat
|
|
15
|
+
import androidx.core.widget.ImageViewCompat
|
|
16
|
+
import androidx.core.widget.NestedScrollView
|
|
17
|
+
import androidx.core.widget.TextViewCompat
|
|
18
|
+
import com.facebook.react.views.view.ReactViewGroup
|
|
19
|
+
import com.google.android.material.R as MaterialR
|
|
20
|
+
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
|
21
|
+
import com.google.android.material.bottomsheet.BottomSheetDialog
|
|
22
|
+
import com.google.android.material.bottomsheet.BottomSheetDragHandleView
|
|
23
|
+
import kotlin.math.roundToInt
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The ActionSheet: a Material 3 modal bottom sheet (`BottomSheetDialog`)
|
|
27
|
+
* with an optional title and message and a list of actions, shown while
|
|
28
|
+
* `visible` is "open". Following Material, there is no cancel row: a swipe
|
|
29
|
+
* down, a tap on the scrim and the back button dismiss it and report a
|
|
30
|
+
* cancel. The view itself only hosts the React children.
|
|
31
|
+
*/
|
|
32
|
+
class PCActionSheetView(context: Context) : ReactViewGroup(context) {
|
|
33
|
+
|
|
34
|
+
companion object {
|
|
35
|
+
private const val TAG = "PCActionSheet"
|
|
36
|
+
|
|
37
|
+
// Material 3 list item metrics
|
|
38
|
+
private const val ROW_MIN_HEIGHT_DP = 56
|
|
39
|
+
private const val ROW_START_PADDING_DP = 16
|
|
40
|
+
private const val ROW_END_PADDING_DP = 24
|
|
41
|
+
private const val ICON_SIZE_DP = 24
|
|
42
|
+
private const val ICON_GAP_DP = 16
|
|
43
|
+
private const val HEADER_BOTTOM_PADDING_DP = 8
|
|
44
|
+
private const val LIST_BOTTOM_PADDING_DP = 8
|
|
45
|
+
|
|
46
|
+
// Material's disabled content alpha
|
|
47
|
+
private const val DISABLED_ALPHA = 0.38f
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** One action from the `actions` prop. */
|
|
51
|
+
data class Action(
|
|
52
|
+
val id: String,
|
|
53
|
+
val title: String,
|
|
54
|
+
val icon: PCButtonSupport.Icon,
|
|
55
|
+
val imageColor: String,
|
|
56
|
+
val destructive: Boolean,
|
|
57
|
+
val disabled: Boolean,
|
|
58
|
+
/** The action's own `haptics`; "" = the component's */
|
|
59
|
+
val haptics: String,
|
|
60
|
+
/** The row's view tag, which Detox's `by.id` matches */
|
|
61
|
+
val testID: String
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
// --- Props ---
|
|
65
|
+
var sheetTitle: String = ""
|
|
66
|
+
var message: String = ""
|
|
67
|
+
var actions: List<Action> = emptyList()
|
|
68
|
+
var visible: String = "closed" // "open" | "closed"
|
|
69
|
+
var maxFontSizeMultiplier: Float = 0f // < 1 = no cap
|
|
70
|
+
|
|
71
|
+
/** The `haptics` prop, played when an action is picked (an action's own `haptics` wins). */
|
|
72
|
+
var haptics: String = ""
|
|
73
|
+
|
|
74
|
+
// --- Events ---
|
|
75
|
+
var onSelect: ((id: String) -> Unit)? = null
|
|
76
|
+
var onCancel: (() -> Unit)? = null
|
|
77
|
+
|
|
78
|
+
// --- Internal ---
|
|
79
|
+
/** The sheet on screen, until a pick, a cancel or `visible` ends it */
|
|
80
|
+
private var dialog: BottomSheetDialog? = null
|
|
81
|
+
private var openToken = 0
|
|
82
|
+
|
|
83
|
+
// ---- Public apply* (called by manager) ----
|
|
84
|
+
|
|
85
|
+
fun applyVisible(value: String?) {
|
|
86
|
+
val next = if (value == "open") "open" else "closed"
|
|
87
|
+
if (next == visible) return
|
|
88
|
+
visible = next
|
|
89
|
+
openToken += 1
|
|
90
|
+
if (visible == "open") {
|
|
91
|
+
presentIfNeeded(openToken)
|
|
92
|
+
} else {
|
|
93
|
+
dismissIfNeeded()
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ---- Presentation ----
|
|
98
|
+
|
|
99
|
+
/** Presents after the current layout pass, so the props of this update are all in. */
|
|
100
|
+
private fun presentIfNeeded(token: Int) {
|
|
101
|
+
post {
|
|
102
|
+
if (token != openToken || visible != "open") return@post
|
|
103
|
+
if (dialog != null) return@post
|
|
104
|
+
if (!isAttachedToWindow) {
|
|
105
|
+
Log.d(TAG, "presentIfNeeded: not attached -> skip")
|
|
106
|
+
return@post
|
|
107
|
+
}
|
|
108
|
+
present()
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Dismisses without a callback: neither onSelect nor onCancel fires. */
|
|
113
|
+
private fun dismissIfNeeded() {
|
|
114
|
+
val sheet = dialog ?: return
|
|
115
|
+
dialog = null
|
|
116
|
+
sheet.dismiss()
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private fun present() {
|
|
120
|
+
val activity = PCNativeTheme.findActivity(context)
|
|
121
|
+
if (activity == null || activity.isFinishing) {
|
|
122
|
+
Log.w(TAG, "present: no activity")
|
|
123
|
+
return
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// The app's Material theme, or Material 3 defaults when it has none;
|
|
127
|
+
// BottomSheetDialog adds the theme's bottomSheetDialogTheme overlay.
|
|
128
|
+
val sheet = BottomSheetDialog(PCThemeSupport.materialContext(activity, "ActionSheet"))
|
|
129
|
+
// Current before the rows are built: drawable icons resolve right away
|
|
130
|
+
// and check it.
|
|
131
|
+
dialog = sheet
|
|
132
|
+
sheet.setContentView(buildContent(sheet, sheet.context))
|
|
133
|
+
|
|
134
|
+
// Open fully, and a swipe down closes it instead of stopping half way.
|
|
135
|
+
sheet.behavior.skipCollapsed = true
|
|
136
|
+
sheet.behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
|
137
|
+
|
|
138
|
+
// Swipe down, scrim tap and back: BottomSheetDialog cancels.
|
|
139
|
+
sheet.setOnCancelListener {
|
|
140
|
+
if (dialog !== sheet) return@setOnCancelListener
|
|
141
|
+
dialog = null
|
|
142
|
+
onCancel?.invoke()
|
|
143
|
+
}
|
|
144
|
+
sheet.setOnDismissListener {
|
|
145
|
+
if (dialog === sheet) dialog = null
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
sheet.show()
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private fun buildContent(sheet: BottomSheetDialog, ctx: Context): View {
|
|
152
|
+
val density = ctx.resources.displayMetrics.density
|
|
153
|
+
fun dp(value: Int) = (value * density).roundToInt()
|
|
154
|
+
|
|
155
|
+
val root = LinearLayout(ctx).apply {
|
|
156
|
+
orientation = LinearLayout.VERTICAL
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Material 3 modal sheets carry a drag handle; it also gives TalkBack a
|
|
160
|
+
// way to dismiss the sheet.
|
|
161
|
+
root.addView(
|
|
162
|
+
BottomSheetDragHandleView(ctx),
|
|
163
|
+
LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
if (sheetTitle.isNotEmpty() || message.isNotEmpty()) {
|
|
167
|
+
val header = LinearLayout(ctx).apply {
|
|
168
|
+
orientation = LinearLayout.VERTICAL
|
|
169
|
+
setPadding(dp(ROW_START_PADDING_DP), 0, dp(ROW_END_PADDING_DP), dp(HEADER_BOTTOM_PADDING_DP))
|
|
170
|
+
}
|
|
171
|
+
if (sheetTitle.isNotEmpty()) {
|
|
172
|
+
header.addView(
|
|
173
|
+
TextView(ctx).apply {
|
|
174
|
+
applyTextAppearance(this, MaterialR.attr.textAppearanceTitleMedium)
|
|
175
|
+
PCMenuSupport.themeColor(ctx, MaterialR.attr.colorOnSurface)?.let { setTextColor(it) }
|
|
176
|
+
text = sheetTitle
|
|
177
|
+
ViewCompat.setAccessibilityHeading(this, true)
|
|
178
|
+
}
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
if (message.isNotEmpty()) {
|
|
182
|
+
header.addView(
|
|
183
|
+
TextView(ctx).apply {
|
|
184
|
+
applyTextAppearance(this, MaterialR.attr.textAppearanceBodyMedium)
|
|
185
|
+
PCMenuSupport.themeColor(ctx, MaterialR.attr.colorOnSurfaceVariant)?.let { setTextColor(it) }
|
|
186
|
+
text = message
|
|
187
|
+
if (sheetTitle.isNotEmpty()) setPadding(0, dp(4), 0, 0)
|
|
188
|
+
}
|
|
189
|
+
)
|
|
190
|
+
}
|
|
191
|
+
root.addView(header)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
val list = LinearLayout(ctx).apply {
|
|
195
|
+
orientation = LinearLayout.VERTICAL
|
|
196
|
+
setPadding(0, 0, 0, dp(LIST_BOTTOM_PADDING_DP))
|
|
197
|
+
}
|
|
198
|
+
// Rows line up: with any icon, every row keeps the icon column.
|
|
199
|
+
val iconColumn = actions.any { it.icon.isPresent }
|
|
200
|
+
for (action in actions) {
|
|
201
|
+
list.addView(buildRow(sheet, ctx, action, iconColumn))
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Long lists scroll inside the sheet; the header and handle stay.
|
|
205
|
+
val scroll = NestedScrollView(ctx).apply {
|
|
206
|
+
isFillViewport = false
|
|
207
|
+
addView(list)
|
|
208
|
+
}
|
|
209
|
+
root.addView(
|
|
210
|
+
scroll,
|
|
211
|
+
LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
PCThemeSupport.capTextSizes(root, maxFontSizeMultiplier)
|
|
215
|
+
return root
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private fun buildRow(sheet: BottomSheetDialog, ctx: Context, action: Action, iconColumn: Boolean): View {
|
|
219
|
+
val density = ctx.resources.displayMetrics.density
|
|
220
|
+
fun dp(value: Int) = (value * density).roundToInt()
|
|
221
|
+
|
|
222
|
+
val contentColor = if (action.destructive) {
|
|
223
|
+
PCMenuSupport.errorColor(ctx)
|
|
224
|
+
} else {
|
|
225
|
+
PCMenuSupport.themeColor(ctx, MaterialR.attr.colorOnSurface)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
val row = LinearLayout(ctx).apply {
|
|
229
|
+
orientation = LinearLayout.HORIZONTAL
|
|
230
|
+
gravity = Gravity.CENTER_VERTICAL
|
|
231
|
+
minimumHeight = dp(ROW_MIN_HEIGHT_DP)
|
|
232
|
+
setPadding(dp(ROW_START_PADDING_DP), dp(8), dp(ROW_END_PADDING_DP), dp(8))
|
|
233
|
+
setBackgroundResource(resolveStyle(ctx, android.R.attr.selectableItemBackground))
|
|
234
|
+
isClickable = true
|
|
235
|
+
isFocusable = true
|
|
236
|
+
isEnabled = !action.disabled
|
|
237
|
+
tag = action.testID.ifEmpty { null }
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (iconColumn) {
|
|
241
|
+
val icon = ImageView(ctx).apply {
|
|
242
|
+
scaleType = ImageView.ScaleType.CENTER_INSIDE
|
|
243
|
+
importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
|
|
244
|
+
}
|
|
245
|
+
row.addView(
|
|
246
|
+
icon,
|
|
247
|
+
LinearLayout.LayoutParams(dp(ICON_SIZE_DP), dp(ICON_SIZE_DP)).apply {
|
|
248
|
+
marginEnd = dp(ICON_GAP_DP)
|
|
249
|
+
}
|
|
250
|
+
)
|
|
251
|
+
if (action.icon.isPresent) {
|
|
252
|
+
PCMenuSupport.loadIcon(ctx, action.icon) { drawable: Drawable? ->
|
|
253
|
+
if (drawable == null || dialog !== sheet) return@loadIcon
|
|
254
|
+
icon.setImageDrawable(drawable)
|
|
255
|
+
// An explicit imageColor wins; destructive icons take the error color.
|
|
256
|
+
val tint = when {
|
|
257
|
+
action.imageColor.isNotEmpty() -> ColorParser.parse(action.imageColor)
|
|
258
|
+
action.destructive -> PCMenuSupport.errorColor(ctx)
|
|
259
|
+
else -> null
|
|
260
|
+
}
|
|
261
|
+
if (tint != null) ImageViewCompat.setImageTintList(icon, ColorStateList.valueOf(tint))
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
if (action.disabled) icon.alpha = DISABLED_ALPHA
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
val label = TextView(ctx).apply {
|
|
268
|
+
applyTextAppearance(this, MaterialR.attr.textAppearanceBodyLarge)
|
|
269
|
+
contentColor?.let { setTextColor(it) }
|
|
270
|
+
text = action.title
|
|
271
|
+
if (action.disabled) alpha = DISABLED_ALPHA
|
|
272
|
+
}
|
|
273
|
+
row.addView(label, LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f))
|
|
274
|
+
|
|
275
|
+
row.setOnClickListener {
|
|
276
|
+
if (dialog !== sheet || action.disabled) return@setOnClickListener
|
|
277
|
+
dialog = null
|
|
278
|
+
PCHaptics.perform(this, action.haptics.ifEmpty { haptics })
|
|
279
|
+
sheet.dismiss()
|
|
280
|
+
onSelect?.invoke(action.id)
|
|
281
|
+
}
|
|
282
|
+
return row
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** Applies the theme's text appearance [attr], when the theme has one. */
|
|
286
|
+
private fun applyTextAppearance(view: TextView, attr: Int) {
|
|
287
|
+
val style = resolveStyle(view.context, attr)
|
|
288
|
+
if (style != 0) TextViewCompat.setTextAppearance(view, style)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** The resource id an attribute points at (a style or a drawable), or 0. */
|
|
292
|
+
private fun resolveStyle(ctx: Context, attr: Int): Int {
|
|
293
|
+
val value = TypedValue()
|
|
294
|
+
return if (ctx.theme.resolveAttribute(attr, value, true)) value.resourceId else 0
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
override fun onAttachedToWindow() {
|
|
298
|
+
super.onAttachedToWindow()
|
|
299
|
+
// A sheet asked for before the view was on screen opens now.
|
|
300
|
+
if (visible == "open" && dialog == null) presentIfNeeded(++openToken)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
override fun onDetachedFromWindow() {
|
|
304
|
+
// Unmounted, or its screen went away: the sheet goes too, without a callback.
|
|
305
|
+
openToken += 1
|
|
306
|
+
dismissIfNeeded()
|
|
307
|
+
super.onDetachedFromWindow()
|
|
308
|
+
}
|
|
309
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
package com.platformcomponents
|
|
2
|
+
|
|
3
|
+
import android.view.View
|
|
4
|
+
import com.facebook.react.bridge.Arguments
|
|
5
|
+
import com.facebook.react.bridge.ReadableArray
|
|
6
|
+
import com.facebook.react.bridge.WritableMap
|
|
7
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
8
|
+
import com.facebook.react.uimanager.UIManagerHelper
|
|
9
|
+
import com.facebook.react.uimanager.ViewGroupManager
|
|
10
|
+
import com.facebook.react.uimanager.ViewManagerDelegate
|
|
11
|
+
import com.facebook.react.uimanager.events.Event
|
|
12
|
+
import com.facebook.react.viewmanagers.PCActionSheetManagerDelegate
|
|
13
|
+
import com.facebook.react.viewmanagers.PCActionSheetManagerInterface
|
|
14
|
+
import com.platformcomponents.PCButtonSupport.stringOr
|
|
15
|
+
|
|
16
|
+
class PCActionSheetViewManager :
|
|
17
|
+
ViewGroupManager<PCActionSheetView>(),
|
|
18
|
+
PCActionSheetManagerInterface<PCActionSheetView> {
|
|
19
|
+
|
|
20
|
+
private val delegate: ViewManagerDelegate<PCActionSheetView> =
|
|
21
|
+
PCActionSheetManagerDelegate(this)
|
|
22
|
+
|
|
23
|
+
override fun getName(): String = "PCActionSheet"
|
|
24
|
+
|
|
25
|
+
override fun getDelegate(): ViewManagerDelegate<PCActionSheetView> = delegate
|
|
26
|
+
|
|
27
|
+
override fun createViewInstance(reactContext: ThemedReactContext): PCActionSheetView =
|
|
28
|
+
PCActionSheetView(reactContext)
|
|
29
|
+
|
|
30
|
+
override fun addEventEmitters(reactContext: ThemedReactContext, view: PCActionSheetView) {
|
|
31
|
+
view.onSelect = { id ->
|
|
32
|
+
UIManagerHelper.getEventDispatcherForReactTag(reactContext, view.id)?.dispatchEvent(
|
|
33
|
+
SelectEvent(UIManagerHelper.getSurfaceId(view), view.id, id)
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
view.onCancel = {
|
|
37
|
+
UIManagerHelper.getEventDispatcherForReactTag(reactContext, view.id)?.dispatchEvent(
|
|
38
|
+
CancelEvent(UIManagerHelper.getSurfaceId(view), view.id)
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// "open" | "closed"
|
|
44
|
+
override fun setVisible(view: PCActionSheetView, value: String?) {
|
|
45
|
+
view.applyVisible(value)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
override fun setTitle(view: PCActionSheetView, value: String?) {
|
|
49
|
+
view.sheetTitle = value ?: ""
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
override fun setMessage(view: PCActionSheetView, value: String?) {
|
|
53
|
+
view.message = value ?: ""
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// actions: [{id, title, icon fields, imageColor, destructive, disabled, haptics, testID}]
|
|
57
|
+
override fun setActions(view: PCActionSheetView, value: ReadableArray?) {
|
|
58
|
+
val out = ArrayList<PCActionSheetView.Action>()
|
|
59
|
+
if (value != null) {
|
|
60
|
+
for (i in 0 until value.size()) {
|
|
61
|
+
val m = value.getMap(i) ?: continue
|
|
62
|
+
out.add(
|
|
63
|
+
PCActionSheetView.Action(
|
|
64
|
+
id = m.stringOr("id", ""),
|
|
65
|
+
title = m.stringOr("title", ""),
|
|
66
|
+
icon = PCButtonSupport.parseIcon(m),
|
|
67
|
+
imageColor = m.stringOr("imageColor", ""),
|
|
68
|
+
destructive = m.stringOr("destructive", "false") == "true",
|
|
69
|
+
disabled = m.stringOr("disabled", "false") == "true",
|
|
70
|
+
haptics = m.stringOr("haptics", ""),
|
|
71
|
+
testID = m.stringOr("testID", "")
|
|
72
|
+
)
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
view.actions = out
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// iOS only: Material bottom sheets have no cancel row
|
|
80
|
+
override fun setCancelTitle(view: PCActionSheetView, value: String?) {}
|
|
81
|
+
|
|
82
|
+
override fun setCancelTestID(view: PCActionSheetView, value: String?) {}
|
|
83
|
+
|
|
84
|
+
// "" (none added) | "none" | "selection" | "light" | "medium" | "heavy" | "success" | "warning" | "error"
|
|
85
|
+
override fun setHaptics(view: PCActionSheetView, value: String?) {
|
|
86
|
+
view.haptics = PCHaptics.configure(view, value)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
override fun setMaxFontSizeMultiplier(view: PCActionSheetView, value: Double) {
|
|
90
|
+
view.maxFontSizeMultiplier = value.toFloat()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// --- ViewGroupManager child management ---
|
|
94
|
+
override fun addView(parent: PCActionSheetView, child: View, index: Int) {
|
|
95
|
+
parent.addView(child, index)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
override fun removeViewAt(parent: PCActionSheetView, index: Int) {
|
|
99
|
+
parent.removeViewAt(index)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
override fun getChildCount(parent: PCActionSheetView): Int = parent.childCount
|
|
103
|
+
|
|
104
|
+
override fun getChildAt(parent: PCActionSheetView, index: Int): View? = parent.getChildAt(index)
|
|
105
|
+
|
|
106
|
+
// --- Events ---
|
|
107
|
+
|
|
108
|
+
private class SelectEvent(
|
|
109
|
+
surfaceId: Int,
|
|
110
|
+
viewTag: Int,
|
|
111
|
+
private val actionId: String
|
|
112
|
+
) : Event<SelectEvent>(surfaceId, viewTag) {
|
|
113
|
+
// Every pick counts
|
|
114
|
+
override fun canCoalesce(): Boolean = false
|
|
115
|
+
|
|
116
|
+
override fun getEventName(): String = "topSelect"
|
|
117
|
+
|
|
118
|
+
// Fabric reads the payload from here
|
|
119
|
+
override fun getEventData(): WritableMap = Arguments.createMap().apply {
|
|
120
|
+
putString("actionId", actionId)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private class CancelEvent(surfaceId: Int, viewTag: Int) : Event<CancelEvent>(surfaceId, viewTag) {
|
|
125
|
+
override fun canCoalesce(): Boolean = false
|
|
126
|
+
|
|
127
|
+
override fun getEventName(): String = "topCancel"
|
|
128
|
+
|
|
129
|
+
override fun getEventData(): WritableMap = Arguments.createMap()
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -465,7 +465,10 @@ class PCButtonGroupView(context: Context) :
|
|
|
465
465
|
popup.setOnMenuItemClickListener { menuItem ->
|
|
466
466
|
// Submenu headers open their submenu; they aren't picks
|
|
467
467
|
val item = PCMenuSupport.itemFor(menuItem, items)
|
|
468
|
-
if (item == null || !item.isAction)
|
|
468
|
+
if (item == null || !item.isAction) {
|
|
469
|
+
post { PCMenuSupport.tagPopupRows(popup.menu, items) }
|
|
470
|
+
return@setOnMenuItemClickListener false
|
|
471
|
+
}
|
|
469
472
|
PCHaptics.perform(this, item.haptics.ifEmpty { haptics })
|
|
470
473
|
onMenuSelect?.invoke(item.id, item.title)
|
|
471
474
|
true
|
|
@@ -479,6 +482,7 @@ class PCButtonGroupView(context: Context) :
|
|
|
479
482
|
|
|
480
483
|
onMenuOpen?.invoke()
|
|
481
484
|
popup.show()
|
|
485
|
+
PCMenuSupport.tagPopupRows(popup.menu, items)
|
|
482
486
|
}
|
|
483
487
|
|
|
484
488
|
private fun scheduleSelectionEmit() {
|
|
@@ -402,7 +402,10 @@ class PCButtonView(context: Context) :
|
|
|
402
402
|
popup.setOnMenuItemClickListener { menuItem ->
|
|
403
403
|
// Submenu headers open their submenu; they aren't picks
|
|
404
404
|
val item = PCMenuSupport.itemFor(menuItem, items)
|
|
405
|
-
if (item == null || !item.isAction)
|
|
405
|
+
if (item == null || !item.isAction) {
|
|
406
|
+
post { PCMenuSupport.tagPopupRows(popup.menu, items) }
|
|
407
|
+
return@setOnMenuItemClickListener false
|
|
408
|
+
}
|
|
406
409
|
PCHaptics.perform(this, item.haptics.ifEmpty { haptics })
|
|
407
410
|
onMenuSelect?.invoke(item.id, item.title)
|
|
408
411
|
true
|
|
@@ -414,6 +417,7 @@ class PCButtonView(context: Context) :
|
|
|
414
417
|
|
|
415
418
|
onMenuOpen?.invoke()
|
|
416
419
|
popup.show()
|
|
420
|
+
PCMenuSupport.tagPopupRows(popup.menu, items)
|
|
417
421
|
}
|
|
418
422
|
|
|
419
423
|
// ---- Layout ----
|