@octane-xplat/sheet 0.0.1 → 0.9.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/LICENSE +21 -0
- package/README.md +36 -2
- package/dist/native/MaterialBottomSheet.android.tsrx.js +87 -0
- package/dist/native/SwiftUIBottomSheet.ios.tsrx.js +97 -0
- package/dist/native/android/index.js +2 -0
- package/dist/native/ios/index.js +2 -0
- package/dist/native/octane-client-build.json +8 -0
- package/dist/web/BottomSheet.web.tsrx.js +45 -0
- package/dist/web/octane-client-build.json +8 -0
- package/dist/web/web/index.js +2 -0
- package/package.json +66 -10
- package/platforms/android/include.gradle +15 -0
- package/platforms/android/java/com/octanexplat/sheet/XplatSheetProvider.kt +198 -0
- package/platforms/android/sheet.aar +0 -0
- package/platforms/ios/src/XplatSheetProvider.swift +188 -0
- package/src/AppKitSheet.macos.tsrx +53 -0
- package/src/BottomSheet.web.tsrx +48 -0
- package/src/MaterialBottomSheet.android.tsrx +102 -0
- package/src/SwiftUIBottomSheet.ios.tsrx +119 -0
- package/src/android/index.ts +2 -0
- package/src/android/types.ts +27 -0
- package/src/ios/index.ts +2 -0
- package/src/ios/types.ts +26 -0
- package/src/macos/index.ts +2 -0
- package/src/macos/types.ts +12 -0
- package/src/web/index.ts +2 -0
- package/src/web/types.ts +16 -0
- package/types/android/index.d.ts +18 -0
- package/types/ios/index.d.ts +19 -0
- package/types/macos/index.d.ts +10 -0
- package/types/web/index.d.ts +18 -0
- package/index.d.ts +0 -1
- package/index.js +0 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alec Larson
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,37 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `@octane-xplat/sheet`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
An install boundary for platform-specific modal bottom sheets: a SwiftUI
|
|
4
|
+
`.sheet` presentation on iOS, a Material 3 `ModalBottomSheet` on Android,
|
|
5
|
+
a fixed-position DOM panel on web, and a real `NSWindow` sheet
|
|
6
|
+
(`beginSheet`) on macOS.
|
|
7
|
+
|
|
8
|
+
Install the package in the app that renders a control. Import
|
|
9
|
+
`SwiftUIBottomSheet` from `@octane-xplat/sheet/ios`,
|
|
10
|
+
`MaterialBottomSheet` from `@octane-xplat/sheet/android`, `BottomSheet`
|
|
11
|
+
from `@octane-xplat/sheet/web`, or `AppKitSheet` from
|
|
12
|
+
`@octane-xplat/sheet/macos` in the matching platform-suffixed file.
|
|
13
|
+
There is no shared sheet API at the package root.
|
|
14
|
+
|
|
15
|
+
Sheet content is a render fn whose octane output mounts into a detached
|
|
16
|
+
root — the same embedding mechanism as `@octane-xplat/context-menu`'s
|
|
17
|
+
trigger. On iOS the registered view resolves through
|
|
18
|
+
`NativeScriptViewFactory`; on Android through the leaf's
|
|
19
|
+
`XplatViewRegistry` + `AndroidView` inside the sheet's dialog window.
|
|
20
|
+
|
|
21
|
+
- iOS: `open` (controlled `isPresented`), `onDismissed`,
|
|
22
|
+
`onPresentedChange`, `fitToContents` (ported size-reader →
|
|
23
|
+
`.presentationDetents([.height])`), `detents`
|
|
24
|
+
(`medium`/`large`/`fraction`/`height`), `showDragIndicator`,
|
|
25
|
+
`interactiveDismissDisabled`.
|
|
26
|
+
- Android: `open` + `onDismissed`, `skipPartiallyExpanded` (the M3 detent
|
|
27
|
+
model: partial vs fully expanded), `showDragHandle`,
|
|
28
|
+
`sheetGesturesEnabled`, `shouldDismissOnBackPress`,
|
|
29
|
+
`shouldDismissOnClickOutside`, and `containerColor`/`contentColor`/
|
|
30
|
+
`scrimColor` hex strings. Activity-owned key events are forwarded while
|
|
31
|
+
the sheet's dialog is open (ported from Expo).
|
|
32
|
+
- Web: scrim + bottom panel, scrim-click dismiss.
|
|
33
|
+
|
|
34
|
+
The native implementations are adapted from `@expo/ui` (MIT,
|
|
35
|
+
`packages/expo-ui` sdk-57): `ios/BottomSheetView.swift` and
|
|
36
|
+
`android/.../ui/ModalBottomSheetView.kt`. Attribution headers sit on the
|
|
37
|
+
ported files.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { createNativeScriptRoot, defineUniversalComponent, hookSlots, registerElement, universalPlan, universalValue, useEffect, useRef, useState } from "@nativescript-community/octane";
|
|
2
|
+
import { Application, GridLayout } from "@nativescript/core";
|
|
3
|
+
import { ComposeDataDriver, JetpackCompose, registerJetpackCompose } from "@nativescript/jetpack-compose";
|
|
4
|
+
//#region src/MaterialBottomSheet.android.tsrx
|
|
5
|
+
var _hs$ = /* @__PURE__ */ hookSlots(5);
|
|
6
|
+
var _h$0 = /* @__PURE__ */ Symbol(_hs$);
|
|
7
|
+
var _h$1 = /* @__PURE__ */ Symbol(_hs$ + 1);
|
|
8
|
+
var _h$2 = /* @__PURE__ */ Symbol(_hs$ + 2);
|
|
9
|
+
var _h$3 = /* @__PURE__ */ Symbol(_hs$ + 3);
|
|
10
|
+
var _h$4 = /* @__PURE__ */ Symbol(_hs$ + 4);
|
|
11
|
+
var __octaneUniversalPlan0 = universalPlan("nativescript", {
|
|
12
|
+
"kind": "range",
|
|
13
|
+
"children": [{
|
|
14
|
+
"kind": "slot",
|
|
15
|
+
"slot": 0
|
|
16
|
+
}]
|
|
17
|
+
});
|
|
18
|
+
var __octaneUniversalPlan1 = universalPlan("nativescript", {
|
|
19
|
+
"kind": "host",
|
|
20
|
+
"type": "xplatandroidsheet",
|
|
21
|
+
"props": { "composeId": "xplat-material-sheet" },
|
|
22
|
+
"bindings": [
|
|
23
|
+
["id", 0],
|
|
24
|
+
["className", 1],
|
|
25
|
+
["style", 2],
|
|
26
|
+
["data", 3],
|
|
27
|
+
["onComposeEvent", 4]
|
|
28
|
+
]
|
|
29
|
+
});
|
|
30
|
+
registerElement("xplatandroidsheet", JetpackCompose);
|
|
31
|
+
registerJetpackCompose("xplat-material-sheet", (owner) => new ComposeDataDriver(new com.octanexplat.sheet.XplatSheetProvider(), owner));
|
|
32
|
+
var seq = 0;
|
|
33
|
+
var ContentRoot = defineUniversalComponent("nativescript", function ContentRoot(props) {
|
|
34
|
+
return universalValue(__octaneUniversalPlan0, [props.render()]);
|
|
35
|
+
}, { "module": "@nativescript-community/octane" });
|
|
36
|
+
var MaterialBottomSheet = defineUniversalComponent("nativescript", function MaterialBottomSheet(props) {
|
|
37
|
+
const contentId = useRef("", _h$0);
|
|
38
|
+
if (!contentId.current) contentId.current = `xplatsheet${++seq}`;
|
|
39
|
+
const mounted = useRef(null, _h$1);
|
|
40
|
+
const [ready, setReady] = useState(false, _h$2);
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
const host = new GridLayout();
|
|
43
|
+
const context = Application.android.foregroundActivity ?? Application.android.startActivity;
|
|
44
|
+
const nativeHost = host;
|
|
45
|
+
nativeHost._setupAsRootView(context);
|
|
46
|
+
nativeHost.callLoaded();
|
|
47
|
+
const root = createNativeScriptRoot(host);
|
|
48
|
+
if (props.content) root.render(ContentRoot, { render: props.content });
|
|
49
|
+
com.octanexplat.sheet.XplatViewRegistry.register(contentId.current, host.android);
|
|
50
|
+
mounted.current = {
|
|
51
|
+
host,
|
|
52
|
+
root
|
|
53
|
+
};
|
|
54
|
+
setReady(true);
|
|
55
|
+
return () => {
|
|
56
|
+
com.octanexplat.sheet.XplatViewRegistry.unregister(contentId.current);
|
|
57
|
+
root.unmount?.();
|
|
58
|
+
mounted.current = null;
|
|
59
|
+
setReady(false);
|
|
60
|
+
};
|
|
61
|
+
}, [], _h$3);
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
if (props.content) mounted.current?.root?.render(ContentRoot, { render: props.content });
|
|
64
|
+
}, [props.content], _h$4);
|
|
65
|
+
return universalValue(__octaneUniversalPlan1, [
|
|
66
|
+
props.id,
|
|
67
|
+
props.className,
|
|
68
|
+
props.style,
|
|
69
|
+
{
|
|
70
|
+
open: props.open ?? false ? 1 : 0,
|
|
71
|
+
contentId: ready && props.content ? contentId.current : "",
|
|
72
|
+
skipPartiallyExpanded: props.skipPartiallyExpanded ?? false ? 1 : 0,
|
|
73
|
+
showDragHandle: props.showDragHandle ?? true ? 1 : 0,
|
|
74
|
+
sheetGesturesEnabled: props.sheetGesturesEnabled ?? true ? 1 : 0,
|
|
75
|
+
shouldDismissOnBackPress: props.shouldDismissOnBackPress ?? true ? 1 : 0,
|
|
76
|
+
shouldDismissOnClickOutside: props.shouldDismissOnClickOutside ?? true ? 1 : 0,
|
|
77
|
+
containerColor: props.containerColor,
|
|
78
|
+
contentColor: props.contentColor,
|
|
79
|
+
scrimColor: props.scrimColor
|
|
80
|
+
},
|
|
81
|
+
(event) => {
|
|
82
|
+
if (event.data?.dismissed) props.onDismissed?.();
|
|
83
|
+
}
|
|
84
|
+
]);
|
|
85
|
+
}, { "module": "@nativescript-community/octane" });
|
|
86
|
+
//#endregion
|
|
87
|
+
export { MaterialBottomSheet };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { createNativeScriptRoot, defineUniversalComponent, hookSlots, registerElement, universalPlan, universalValue, useEffect, useRef } from "@nativescript-community/octane";
|
|
2
|
+
import { SwiftUI, SwiftUIManager, UIDataDriver, registerSwiftUI } from "@nativescript/swift-ui";
|
|
3
|
+
import { GridLayout } from "@nativescript/core";
|
|
4
|
+
//#region src/SwiftUIBottomSheet.ios.tsrx
|
|
5
|
+
var _hs$ = /* @__PURE__ */ hookSlots(3);
|
|
6
|
+
var _h$0 = /* @__PURE__ */ Symbol(_hs$);
|
|
7
|
+
var _h$1 = /* @__PURE__ */ Symbol(_hs$ + 1);
|
|
8
|
+
var _h$2 = /* @__PURE__ */ Symbol(_hs$ + 2);
|
|
9
|
+
var __octaneUniversalPlan0 = universalPlan("nativescript", {
|
|
10
|
+
"kind": "range",
|
|
11
|
+
"children": [{
|
|
12
|
+
"kind": "slot",
|
|
13
|
+
"slot": 0
|
|
14
|
+
}]
|
|
15
|
+
});
|
|
16
|
+
var __octaneUniversalPlan1 = universalPlan("nativescript", {
|
|
17
|
+
"kind": "host",
|
|
18
|
+
"type": "xplatiossheet",
|
|
19
|
+
"props": { "swiftId": "xplat-swiftui-sheet" },
|
|
20
|
+
"bindings": [
|
|
21
|
+
["id", 0],
|
|
22
|
+
["className", 1],
|
|
23
|
+
["style", 2],
|
|
24
|
+
["data", 3],
|
|
25
|
+
["onSwiftUIEvent", 4]
|
|
26
|
+
]
|
|
27
|
+
});
|
|
28
|
+
registerElement("xplatiossheet", SwiftUI);
|
|
29
|
+
registerSwiftUI("xplat-swiftui-sheet", (owner) => new UIDataDriver(new XplatSheetProvider(), owner));
|
|
30
|
+
var hosts = /* @__PURE__ */ new Map();
|
|
31
|
+
var roots = /* @__PURE__ */ new Map();
|
|
32
|
+
function mountSubtree(id, render) {
|
|
33
|
+
let host = hosts.get(id);
|
|
34
|
+
if (!host) {
|
|
35
|
+
host = new GridLayout();
|
|
36
|
+
hosts.set(id, host);
|
|
37
|
+
SwiftUIManager.registerNativeScriptViews({ [id]: true }, {
|
|
38
|
+
create: (fullId) => hosts.get(fullId.split("-")[0]),
|
|
39
|
+
destroy: (fullId) => {
|
|
40
|
+
hosts.delete(fullId.split("-")[0]);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
let root = roots.get(id);
|
|
45
|
+
if (!root) {
|
|
46
|
+
root = createNativeScriptRoot(host);
|
|
47
|
+
roots.set(id, root);
|
|
48
|
+
}
|
|
49
|
+
root.render(ContentRoot, { render });
|
|
50
|
+
}
|
|
51
|
+
function unmountSubtree(id) {
|
|
52
|
+
roots.get(id)?.unmount?.();
|
|
53
|
+
roots.delete(id);
|
|
54
|
+
hosts.delete(id);
|
|
55
|
+
}
|
|
56
|
+
var ContentRoot = defineUniversalComponent("nativescript", function ContentRoot(props) {
|
|
57
|
+
return universalValue(__octaneUniversalPlan0, [props.render()]);
|
|
58
|
+
}, { "module": "@nativescript-community/octane" });
|
|
59
|
+
var seq = 0;
|
|
60
|
+
var SwiftUIBottomSheet = defineUniversalComponent("nativescript", function SwiftUIBottomSheet(props) {
|
|
61
|
+
const contentId = useRef("", _h$0);
|
|
62
|
+
if (!contentId.current) contentId.current = `xplatsheet${++seq}`;
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
if (props.content) mountSubtree(contentId.current, props.content);
|
|
65
|
+
return () => {
|
|
66
|
+
unmountSubtree(contentId.current);
|
|
67
|
+
};
|
|
68
|
+
}, [], _h$1);
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (props.content) mountSubtree(contentId.current, props.content);
|
|
71
|
+
}, [props.content], _h$2);
|
|
72
|
+
return universalValue(__octaneUniversalPlan1, [
|
|
73
|
+
props.id,
|
|
74
|
+
props.className,
|
|
75
|
+
props.style,
|
|
76
|
+
{
|
|
77
|
+
isPresented: props.open ?? false,
|
|
78
|
+
contentId: props.content ? contentId.current : "",
|
|
79
|
+
fitToContents: props.fitToContents ?? false,
|
|
80
|
+
detents: props.detents ?? ["medium"],
|
|
81
|
+
customHeight: props.customHeight,
|
|
82
|
+
customFraction: props.customFraction,
|
|
83
|
+
showDragIndicator: props.showDragIndicator ?? false,
|
|
84
|
+
interactiveDismissDisabled: props.interactiveDismissDisabled ?? false
|
|
85
|
+
},
|
|
86
|
+
(event) => {
|
|
87
|
+
if (event.data?.dismissed) {
|
|
88
|
+
props.onDismissed?.();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const presented = event.data?.isPresented;
|
|
92
|
+
if (typeof presented === "boolean") props.onPresentedChange?.(presented);
|
|
93
|
+
}
|
|
94
|
+
]);
|
|
95
|
+
}, { "module": "@nativescript-community/octane" });
|
|
96
|
+
//#endregion
|
|
97
|
+
export { SwiftUIBottomSheet };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Fragment, createElement, createScopedElement, createScopedValue, positionalChildren } from "octane";
|
|
2
|
+
//#region src/BottomSheet.web.tsrx
|
|
3
|
+
var BottomSheet = function BottomSheet(props, __s, __extra) {
|
|
4
|
+
const open = props.open ?? false;
|
|
5
|
+
(props.shouldDismissOnClickOutside ?? true) || props.shouldDismissOnBackPress;
|
|
6
|
+
if (!open) return positionalChildren([]);
|
|
7
|
+
return createScopedElement(Fragment, {}, () => positionalChildren([createScopedValue(() => createElement("div", {
|
|
8
|
+
style: {
|
|
9
|
+
position: "fixed",
|
|
10
|
+
inset: 0,
|
|
11
|
+
background: "rgb(0 0 0 / 0.4)",
|
|
12
|
+
zIndex: 999
|
|
13
|
+
},
|
|
14
|
+
onClick: () => {
|
|
15
|
+
if (props.shouldDismissOnClickOutside ?? true) props.onDismissed?.();
|
|
16
|
+
}
|
|
17
|
+
})), createScopedValue(() => createScopedElement("div", {
|
|
18
|
+
role: "dialog",
|
|
19
|
+
"aria-modal": "true",
|
|
20
|
+
style: {
|
|
21
|
+
position: "fixed",
|
|
22
|
+
left: 0,
|
|
23
|
+
right: 0,
|
|
24
|
+
bottom: 0,
|
|
25
|
+
zIndex: 1e3,
|
|
26
|
+
background: "var(--sheet-bg, Canvas)",
|
|
27
|
+
color: "var(--sheet-fg, CanvasText)",
|
|
28
|
+
borderRadius: "16px 16px 0 0",
|
|
29
|
+
boxShadow: "0 -4px 24px rgb(0 0 0 / 0.2)",
|
|
30
|
+
maxHeight: props.skipPartiallyExpanded ? "90vh" : "60vh",
|
|
31
|
+
overflowY: "auto"
|
|
32
|
+
}
|
|
33
|
+
}, () => positionalChildren([(props.showDragHandle ?? true) && createScopedValue(() => createScopedElement("div", { style: {
|
|
34
|
+
display: "flex",
|
|
35
|
+
justifyContent: "center",
|
|
36
|
+
padding: "8px 0"
|
|
37
|
+
} }, () => createScopedValue(() => createElement("div", { style: {
|
|
38
|
+
width: 36,
|
|
39
|
+
height: 4,
|
|
40
|
+
borderRadius: 2,
|
|
41
|
+
background: "color-mix(in srgb, CanvasText 30%, transparent)"
|
|
42
|
+
} })))), props.content?.()])))]));
|
|
43
|
+
};
|
|
44
|
+
//#endregion
|
|
45
|
+
export { BottomSheet };
|
package/package.json
CHANGED
|
@@ -1,18 +1,74 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@octane-xplat/sheet",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Platform-specific modal bottom sheet controls for Octane xplat",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
|
-
"url": "https://github.com/octane-xplat/octane-xplat
|
|
7
|
+
"url": "https://github.com/octane-xplat/octane-xplat",
|
|
8
8
|
"directory": "packages/sheet"
|
|
9
9
|
},
|
|
10
|
-
"homepage": "https://github.com/octane-xplat/octane-xplat/tree/main/packages/sheet",
|
|
11
|
-
"main": "./index.js",
|
|
12
|
-
"types": "./index.d.ts",
|
|
13
10
|
"files": [
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
|
|
11
|
+
"dist",
|
|
12
|
+
"types",
|
|
13
|
+
"src",
|
|
14
|
+
"platforms"
|
|
15
|
+
],
|
|
16
|
+
"type": "module",
|
|
17
|
+
"exports": {
|
|
18
|
+
"./ios": {
|
|
19
|
+
"types": "./types/ios/index.d.ts",
|
|
20
|
+
"native": "./dist/native/ios/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./android": {
|
|
23
|
+
"types": "./types/android/index.d.ts",
|
|
24
|
+
"native": "./dist/native/android/index.js"
|
|
25
|
+
},
|
|
26
|
+
"./web": {
|
|
27
|
+
"types": "./types/web/index.d.ts",
|
|
28
|
+
"web": "./dist/web/web/index.js"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@nativescript/jetpack-compose": "1.0.0-beta.0",
|
|
36
|
+
"@nativescript/swift-ui": "4.0.2"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@nativescript-community/octane": "0.2.4",
|
|
40
|
+
"@nativescript/core": "9.1.2",
|
|
41
|
+
"@octanejs/vite-plugin": "0.1.60",
|
|
42
|
+
"octane": "0.5.0",
|
|
43
|
+
"tsrx-typegen": "0.2.1",
|
|
44
|
+
"typescript": "5.9.3",
|
|
45
|
+
"vite": "8.3.0"
|
|
46
|
+
},
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"@nativescript-community/octane": ">=0.2.1 <1",
|
|
49
|
+
"@nativescript/core": ">=9.1.0 <10",
|
|
50
|
+
"octane": ">=0.1.51 <1"
|
|
51
|
+
},
|
|
52
|
+
"peerDependenciesMeta": {
|
|
53
|
+
"@nativescript-community/octane": {
|
|
54
|
+
"optional": true
|
|
55
|
+
},
|
|
56
|
+
"@nativescript/core": {
|
|
57
|
+
"optional": true
|
|
58
|
+
},
|
|
59
|
+
"octane": {
|
|
60
|
+
"optional": true
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"nativescript": {
|
|
64
|
+
"platforms": {
|
|
65
|
+
"android": "9.1.0",
|
|
66
|
+
"ios": "9.1.0"
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
"scripts": {
|
|
70
|
+
"build": "vite build && vite build --mode native && tsc -p tsconfig.types.json",
|
|
71
|
+
"test:packed": "node tests/packed-consumer.mjs",
|
|
72
|
+
"pack:check": "tsrx-typegen --pack-check"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
apply plugin: 'org.jetbrains.kotlin.plugin.compose'
|
|
2
|
+
|
|
3
|
+
android {
|
|
4
|
+
buildFeatures {
|
|
5
|
+
compose true
|
|
6
|
+
}
|
|
7
|
+
kotlinOptions {
|
|
8
|
+
jvmTarget = '17'
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
dependencies {
|
|
13
|
+
implementation platform('androidx.compose:compose-bom:2024.12.01')
|
|
14
|
+
implementation 'androidx.compose.material3:material3'
|
|
15
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
// Adapted from @expo/ui (MIT) — Copyright 2025-present 650 Industries.
|
|
2
|
+
// Upstream: expo/packages/expo-ui/android/.../ui/ModalBottomSheetView.kt
|
|
3
|
+
// (sdk-57). The ExpoModules glue (ComposeProps records, async-function
|
|
4
|
+
// handles, slot views, ModifierRegistry) is replaced by the
|
|
5
|
+
// @nativescript/jetpack-compose provider contract: updateData(Map) + onEvent.
|
|
6
|
+
// The sheet's content is a registered NativeScript view hosted through
|
|
7
|
+
// AndroidView — XplatViewRegistry mirrors the context-menu leaf's registry
|
|
8
|
+
// (same mechanism, kept leaf-local while the shared-bridge question is open).
|
|
9
|
+
|
|
10
|
+
@file:OptIn(ExperimentalMaterial3Api::class)
|
|
11
|
+
|
|
12
|
+
package com.octanexplat.sheet
|
|
13
|
+
|
|
14
|
+
import android.app.Activity
|
|
15
|
+
import android.app.Dialog
|
|
16
|
+
import android.content.Context
|
|
17
|
+
import android.view.KeyEvent
|
|
18
|
+
import android.view.View
|
|
19
|
+
import android.view.ViewGroup
|
|
20
|
+
import android.view.inputmethod.InputMethodManager
|
|
21
|
+
import androidx.compose.foundation.layout.Box
|
|
22
|
+
import androidx.compose.foundation.layout.fillMaxWidth
|
|
23
|
+
import androidx.compose.material3.BottomSheetDefaults
|
|
24
|
+
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
25
|
+
import androidx.compose.material3.ModalBottomSheet
|
|
26
|
+
import androidx.compose.material3.ModalBottomSheetProperties
|
|
27
|
+
import androidx.compose.material3.contentColorFor
|
|
28
|
+
import androidx.compose.material3.rememberModalBottomSheetState
|
|
29
|
+
import androidx.compose.runtime.Composable
|
|
30
|
+
import androidx.compose.runtime.DisposableEffect
|
|
31
|
+
import androidx.compose.runtime.getValue
|
|
32
|
+
import androidx.compose.runtime.mutableStateOf
|
|
33
|
+
import androidx.compose.runtime.setValue
|
|
34
|
+
import androidx.compose.ui.Modifier
|
|
35
|
+
import androidx.compose.ui.platform.ComposeView
|
|
36
|
+
import androidx.compose.ui.platform.LocalView
|
|
37
|
+
import androidx.compose.ui.viewinterop.AndroidView
|
|
38
|
+
import androidx.compose.ui.window.DialogWindowProvider
|
|
39
|
+
import org.json.JSONObject
|
|
40
|
+
import java.lang.ref.WeakReference
|
|
41
|
+
|
|
42
|
+
/** id → NativeScript view registry, callable directly from JS
|
|
43
|
+
* (`com.octanexplat.sheet.XplatViewRegistry.register`). Same mechanism as
|
|
44
|
+
* the context-menu leaf's registry — the sheet content subtree embeds in
|
|
45
|
+
* the modal sheet's dialog window through AndroidView. Weak refs; JS owns
|
|
46
|
+
* the lifecycle and unregisters on unmount. */
|
|
47
|
+
object XplatViewRegistry {
|
|
48
|
+
private val views = HashMap<String, WeakReference<View>>()
|
|
49
|
+
|
|
50
|
+
@JvmStatic
|
|
51
|
+
fun register(id: String, view: View) {
|
|
52
|
+
views[id] = WeakReference(view)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@JvmStatic
|
|
56
|
+
fun unregister(id: String) {
|
|
57
|
+
views.remove(id)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@JvmStatic
|
|
61
|
+
fun get(id: String): View? = views[id]?.get()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private data class SheetUiState(
|
|
65
|
+
val open: Boolean = false,
|
|
66
|
+
val contentId: String = "",
|
|
67
|
+
val skipPartiallyExpanded: Boolean = false,
|
|
68
|
+
val showDragHandle: Boolean = true,
|
|
69
|
+
val sheetGesturesEnabled: Boolean = true,
|
|
70
|
+
val shouldDismissOnBackPress: Boolean = true,
|
|
71
|
+
val shouldDismissOnClickOutside: Boolean = true,
|
|
72
|
+
val containerColor: Int? = null,
|
|
73
|
+
val contentColor: Int? = null,
|
|
74
|
+
val scrimColor: Int? = null,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
private fun Any?.asBoolean(): Boolean = when (this) {
|
|
78
|
+
is Boolean -> this
|
|
79
|
+
is Number -> toInt() != 0
|
|
80
|
+
else -> false
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private fun Any?.asColorInt(): Int? = when (this) {
|
|
84
|
+
is Number -> toInt()
|
|
85
|
+
is String -> runCatching { android.graphics.Color.parseColor(this) }.getOrNull()
|
|
86
|
+
else -> null
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** True while the input method has an active connection to a view that
|
|
90
|
+
* accepts text — ported from Expo's ModalBottomSheetView. */
|
|
91
|
+
private fun Activity.isAcceptingText(): Boolean {
|
|
92
|
+
val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
|
|
93
|
+
return inputMethodManager?.isAcceptingText == true
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Ported: the sheet lives in its own dialog window, so key events that the
|
|
97
|
+
* activity owns (e.g. dev-menu triggers) stop reaching it while the sheet
|
|
98
|
+
* is open — forward non-dismiss keys back, skipping while a field accepts
|
|
99
|
+
* text so typing can't trigger activity shortcuts. */
|
|
100
|
+
@Composable
|
|
101
|
+
private fun ForwardKeyEventsToActivity(activity: Activity?) {
|
|
102
|
+
val dialog = (LocalView.current.parent as? DialogWindowProvider)?.window?.callback as? Dialog
|
|
103
|
+
if (dialog == null || activity == null) {
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
DisposableEffect(dialog, activity) {
|
|
107
|
+
dialog.setOnKeyListener { _, keyCode, event ->
|
|
108
|
+
val isForwardable = event.action == KeyEvent.ACTION_UP &&
|
|
109
|
+
keyCode != KeyEvent.KEYCODE_BACK &&
|
|
110
|
+
keyCode != KeyEvent.KEYCODE_ESCAPE &&
|
|
111
|
+
!activity.isAcceptingText()
|
|
112
|
+
isForwardable && activity.onKeyUp(keyCode, event)
|
|
113
|
+
}
|
|
114
|
+
onDispose {
|
|
115
|
+
dialog.setOnKeyListener(null)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
@Composable
|
|
121
|
+
private fun SheetContent(state: SheetUiState, activity: Activity?, onDismiss: () -> Unit) {
|
|
122
|
+
if (!state.open) {
|
|
123
|
+
return
|
|
124
|
+
}
|
|
125
|
+
val sheetState = rememberModalBottomSheetState(state.skipPartiallyExpanded)
|
|
126
|
+
val resolvedContainer = state.containerColor?.let { androidx.compose.ui.graphics.Color(it) }
|
|
127
|
+
?: BottomSheetDefaults.ContainerColor
|
|
128
|
+
val resolvedContent = state.contentColor?.let { androidx.compose.ui.graphics.Color(it) }
|
|
129
|
+
?: contentColorFor(resolvedContainer)
|
|
130
|
+
val resolvedScrim = state.scrimColor?.let { androidx.compose.ui.graphics.Color(it) }
|
|
131
|
+
?: BottomSheetDefaults.ScrimColor
|
|
132
|
+
|
|
133
|
+
// This material3 predates sheetGesturesEnabled and
|
|
134
|
+
// shouldDismissOnClickOutside — only shouldDismissOnBackPress is
|
|
135
|
+
// carried through properties.
|
|
136
|
+
ModalBottomSheet(
|
|
137
|
+
sheetState = sheetState,
|
|
138
|
+
onDismissRequest = onDismiss,
|
|
139
|
+
containerColor = resolvedContainer,
|
|
140
|
+
contentColor = resolvedContent,
|
|
141
|
+
scrimColor = resolvedScrim,
|
|
142
|
+
dragHandle = if (state.showDragHandle) {
|
|
143
|
+
{ BottomSheetDefaults.DragHandle() }
|
|
144
|
+
} else {
|
|
145
|
+
null
|
|
146
|
+
},
|
|
147
|
+
properties = ModalBottomSheetProperties(
|
|
148
|
+
shouldDismissOnBackPress = state.shouldDismissOnBackPress,
|
|
149
|
+
),
|
|
150
|
+
) {
|
|
151
|
+
ForwardKeyEventsToActivity(activity)
|
|
152
|
+
Box(modifier = Modifier.fillMaxWidth()) {
|
|
153
|
+
val contentId = state.contentId
|
|
154
|
+
if (contentId.isNotEmpty()) {
|
|
155
|
+
AndroidView(
|
|
156
|
+
factory = { ctx ->
|
|
157
|
+
XplatViewRegistry.get(contentId) ?: View(ctx).apply {
|
|
158
|
+
layoutParams = ViewGroup.LayoutParams(0, 0)
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
class XplatSheetProvider {
|
|
168
|
+
private var state by mutableStateOf(SheetUiState())
|
|
169
|
+
var onEvent: ((Any) -> Unit)? = null
|
|
170
|
+
|
|
171
|
+
fun generateComposeView(view: ComposeView) {
|
|
172
|
+
view.setContent {
|
|
173
|
+
androidx.compose.material3.MaterialTheme {
|
|
174
|
+
SheetContent(
|
|
175
|
+
state = state,
|
|
176
|
+
activity = (view.context as? Activity),
|
|
177
|
+
onDismiss = { onEvent?.invoke(JSONObject().put("dismissed", true)) },
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
fun updateData(input: Map<Any, Any>) {
|
|
184
|
+
val data = input["data"] as? Map<*, *> ?: input
|
|
185
|
+
state = SheetUiState(
|
|
186
|
+
open = data["open"].asBoolean(),
|
|
187
|
+
contentId = data["contentId"] as? String ?: "",
|
|
188
|
+
skipPartiallyExpanded = data["skipPartiallyExpanded"].asBoolean(),
|
|
189
|
+
showDragHandle = if (data.containsKey("showDragHandle")) data["showDragHandle"].asBoolean() else true,
|
|
190
|
+
sheetGesturesEnabled = if (data.containsKey("sheetGesturesEnabled")) data["sheetGesturesEnabled"].asBoolean() else true,
|
|
191
|
+
shouldDismissOnBackPress = if (data.containsKey("shouldDismissOnBackPress")) data["shouldDismissOnBackPress"].asBoolean() else true,
|
|
192
|
+
shouldDismissOnClickOutside = if (data.containsKey("shouldDismissOnClickOutside")) data["shouldDismissOnClickOutside"].asBoolean() else true,
|
|
193
|
+
containerColor = data["containerColor"].asColorInt(),
|
|
194
|
+
contentColor = data["contentColor"].asColorInt(),
|
|
195
|
+
scrimColor = data["scrimColor"].asColorInt(),
|
|
196
|
+
)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
// Adapted from @expo/ui (MIT) — Copyright 2025-present 650 Industries.
|
|
2
|
+
// Upstream: expo/packages/expo-ui/ios/BottomSheetView.swift (sdk-57).
|
|
3
|
+
// The ExpoModules glue (@Field props record, EventDispatcher, children slot
|
|
4
|
+
// views) is replaced by the @nativescript/swift-ui provider contract:
|
|
5
|
+
// updateData(NSDictionary) + onEvent. The sheet's content is a registered
|
|
6
|
+
// NativeScript view resolved through NativeScriptViewFactory — the same
|
|
7
|
+
// HostedNativeScriptView the context-menu leaf re-declares (the plugin's
|
|
8
|
+
// NativeScriptView struct is module-internal).
|
|
9
|
+
|
|
10
|
+
import SwiftUI
|
|
11
|
+
|
|
12
|
+
private final class SheetModel: ObservableObject {
|
|
13
|
+
@Published var isPresented: Bool = false
|
|
14
|
+
@Published var contentId: String = ""
|
|
15
|
+
@Published var fitToContents: Bool = false
|
|
16
|
+
@Published var detents: [String] = ["medium"]
|
|
17
|
+
@Published var customHeight: Double? = nil
|
|
18
|
+
@Published var customFraction: Double? = nil
|
|
19
|
+
@Published var showDragIndicator: Bool = false
|
|
20
|
+
@Published var interactiveDismissDisabled: Bool = false
|
|
21
|
+
var onPresentedChange: ((Bool) -> Void)?
|
|
22
|
+
var onDismiss: (() -> Void)?
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Same shape as the plugin's NativeScriptViewRepresentable — unique instance
|
|
26
|
+
// id resolved through the core-owned NativeScriptViewFactory.
|
|
27
|
+
private struct HostedNativeScriptView: UIViewRepresentable {
|
|
28
|
+
let id: String
|
|
29
|
+
|
|
30
|
+
init(id: String) {
|
|
31
|
+
self.id = id + "-" + UUID().uuidString
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
func makeUIView(context: Context) -> UIView {
|
|
35
|
+
return NativeScriptViewFactory.shared?.getViewById(id) ?? UIView()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
func updateUIView(_ uiView: UIView, context: Context) {}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private struct SizePreferenceKey: PreferenceKey {
|
|
42
|
+
static var defaultValue: CGSize?
|
|
43
|
+
|
|
44
|
+
static func reduce(value: inout CGSize?, nextValue: () -> CGSize?) {
|
|
45
|
+
guard let nextValue = nextValue() else {
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
value = nextValue
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private struct ReadSizeModifier: ViewModifier {
|
|
53
|
+
private var sizeView: some View {
|
|
54
|
+
GeometryReader { geometry in
|
|
55
|
+
Color.clear
|
|
56
|
+
.preference(key: SizePreferenceKey.self, value: geometry.size)
|
|
57
|
+
.allowsHitTesting(false)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
func body(content: Content) -> some View {
|
|
62
|
+
content.background(sizeView)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@available(iOS 14.0, *)
|
|
67
|
+
private struct SheetContent: View {
|
|
68
|
+
@ObservedObject var model: SheetModel
|
|
69
|
+
@State private var isPresented: Bool = false
|
|
70
|
+
@State private var childrenSize: CGSize = .zero
|
|
71
|
+
|
|
72
|
+
private func handleSizeChange(_ size: CGSize) {
|
|
73
|
+
guard childrenSize != size else { return }
|
|
74
|
+
childrenSize = size
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@available(iOS 16.0, tvOS 16.0, *)
|
|
78
|
+
private var presentationDetents: Set<PresentationDetent> {
|
|
79
|
+
if model.fitToContents, childrenSize.height > 0 {
|
|
80
|
+
return [.height(childrenSize.height)]
|
|
81
|
+
}
|
|
82
|
+
var result: Set<PresentationDetent> = []
|
|
83
|
+
for detent in model.detents {
|
|
84
|
+
switch detent {
|
|
85
|
+
case "large": result.insert(.large)
|
|
86
|
+
case "fraction": result.insert(.fraction(model.customFraction ?? 0.5))
|
|
87
|
+
case "height": result.insert(.height(model.customHeight ?? 300))
|
|
88
|
+
default: result.insert(.medium)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return result.isEmpty ? [.medium] : result
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
var body: some View {
|
|
95
|
+
Color.clear
|
|
96
|
+
.frame(width: 0, height: 0)
|
|
97
|
+
.sheet(isPresented: $isPresented, onDismiss: {
|
|
98
|
+
model.onDismiss?()
|
|
99
|
+
}) {
|
|
100
|
+
let content = HostedNativeScriptView(id: model.contentId)
|
|
101
|
+
.modifier(ReadSizeModifier())
|
|
102
|
+
.onPreferenceChange(SizePreferenceKey.self) { size in
|
|
103
|
+
if let size { handleSizeChange(size) }
|
|
104
|
+
}
|
|
105
|
+
if #available(iOS 16.0, tvOS 16.0, *) {
|
|
106
|
+
content
|
|
107
|
+
.presentationDetents(presentationDetents)
|
|
108
|
+
.presentationDragIndicator(model.showDragIndicator ? .visible : .hidden)
|
|
109
|
+
.interactiveDismissDisabled(model.interactiveDismissDisabled)
|
|
110
|
+
} else {
|
|
111
|
+
content
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
.onChange(of: isPresented) { newIsPresented in
|
|
115
|
+
if model.isPresented == newIsPresented {
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
model.onPresentedChange?(newIsPresented)
|
|
119
|
+
}
|
|
120
|
+
.onChange(of: model.isPresented) { newValue in
|
|
121
|
+
isPresented = newValue
|
|
122
|
+
}
|
|
123
|
+
.onAppear {
|
|
124
|
+
isPresented = model.isPresented
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
@objc(XplatSheetProvider)
|
|
130
|
+
public final class XplatSheetProvider: UIViewController, SwiftUIProvider {
|
|
131
|
+
private let model = SheetModel()
|
|
132
|
+
|
|
133
|
+
public required init() {
|
|
134
|
+
super.init(nibName: nil, bundle: nil)
|
|
135
|
+
model.onPresentedChange = { [weak self] presented in
|
|
136
|
+
self?.onEvent?(["isPresented": presented])
|
|
137
|
+
}
|
|
138
|
+
model.onDismiss = { [weak self] in
|
|
139
|
+
self?.onEvent?(["dismissed": true])
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
public required init?(coder: NSCoder) {
|
|
144
|
+
super.init(coder: coder)
|
|
145
|
+
model.onPresentedChange = { [weak self] presented in
|
|
146
|
+
self?.onEvent?(["isPresented": presented])
|
|
147
|
+
}
|
|
148
|
+
model.onDismiss = { [weak self] in
|
|
149
|
+
self?.onEvent?(["dismissed": true])
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
public override func viewDidLoad() {
|
|
154
|
+
super.viewDidLoad()
|
|
155
|
+
if #available(iOS 14.0, *) {
|
|
156
|
+
setupSwiftUIView(content: SheetContent(model: model))
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
public func updateData(data: NSDictionary) {
|
|
161
|
+
if let presented = data["isPresented"] as? NSNumber {
|
|
162
|
+
model.isPresented = presented.boolValue
|
|
163
|
+
}
|
|
164
|
+
if let contentId = data["contentId"] as? String {
|
|
165
|
+
model.contentId = contentId
|
|
166
|
+
}
|
|
167
|
+
if let fit = data["fitToContents"] as? NSNumber {
|
|
168
|
+
model.fitToContents = fit.boolValue
|
|
169
|
+
}
|
|
170
|
+
if let detents = data["detents"] as? [String] {
|
|
171
|
+
model.detents = detents
|
|
172
|
+
}
|
|
173
|
+
if let height = data["customHeight"] as? NSNumber {
|
|
174
|
+
model.customHeight = height.doubleValue
|
|
175
|
+
}
|
|
176
|
+
if let fraction = data["customFraction"] as? NSNumber {
|
|
177
|
+
model.customFraction = fraction.doubleValue
|
|
178
|
+
}
|
|
179
|
+
if let indicator = data["showDragIndicator"] as? NSNumber {
|
|
180
|
+
model.showDragIndicator = indicator.boolValue
|
|
181
|
+
}
|
|
182
|
+
if let disable = data["interactiveDismissDisabled"] as? NSNumber {
|
|
183
|
+
model.interactiveDismissDisabled = disable.boolValue
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
public var onEvent: ((NSDictionary) -> Void)?
|
|
188
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/** @jsxImportSource @xplat/macos/renderer */
|
|
2
|
+
import { useEffect, useRef } from 'octane'
|
|
3
|
+
import type { AppKitSheetProps } from './macos/types'
|
|
4
|
+
|
|
5
|
+
const host = (): any => (globalThis as any).__xplatAppKit ?? {}
|
|
6
|
+
|
|
7
|
+
function ContentRoot(props: { render?: () => any }) @{
|
|
8
|
+
<>{props.render?.()}</>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** macOS sheet — presents the `content` subtree in a real AppKit window
|
|
12
|
+
* sheet (`beginSheet` on the leaf's parent window), falling back to a
|
|
13
|
+
* floating window when no parent exists. `open` controls presentation;
|
|
14
|
+
* closing the sheet window (Esc/close button) or `open`→false fires
|
|
15
|
+
* `onDismissed`. Detents don't exist on macOS — the window sizes to the
|
|
16
|
+
* content's fitting size. */
|
|
17
|
+
export function AppKitSheet(props: AppKitSheetProps) @{
|
|
18
|
+
const anchor = useRef<any>(null)
|
|
19
|
+
const presented = useRef<any>(null)
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (!props.open) return
|
|
23
|
+
if (presented.current) {
|
|
24
|
+
presented.current.update({ render: props.content })
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const view = anchor.current
|
|
29
|
+
const present = host().presentSheet
|
|
30
|
+
if (!view || typeof present !== 'function') return
|
|
31
|
+
presented.current = present(view, {
|
|
32
|
+
component: ContentRoot,
|
|
33
|
+
props: { render: props.content },
|
|
34
|
+
onDismissed: () => {
|
|
35
|
+
presented.current = null
|
|
36
|
+
props.onDismissed?.()
|
|
37
|
+
},
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
return () => {
|
|
41
|
+
const entry = presented.current
|
|
42
|
+
presented.current = null
|
|
43
|
+
entry?.close()
|
|
44
|
+
}
|
|
45
|
+
}, [props.open])
|
|
46
|
+
|
|
47
|
+
// Push content render-fn changes into the presented sheet while open.
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
presented.current?.update?.({ render: props.content })
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
<flexboxlayout ref={(v: any) => { anchor.current = v }} id={props.id} />;
|
|
53
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/** @jsxImportSource octane */
|
|
2
|
+
import type { BottomSheetProps } from './web/types'
|
|
3
|
+
|
|
4
|
+
/** Web bottom sheet: a fixed-position panel over a scrim. `open` controls
|
|
5
|
+
* visibility; `onDismissed` fires on scrim click / Escape. */
|
|
6
|
+
export function BottomSheet(props: BottomSheetProps) @{
|
|
7
|
+
const open = props.open ?? false
|
|
8
|
+
const dismissable = (props.shouldDismissOnClickOutside ?? true) || (props.shouldDismissOnBackPress ?? true)
|
|
9
|
+
|
|
10
|
+
if (!open) {
|
|
11
|
+
return <></>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
<>
|
|
15
|
+
<div
|
|
16
|
+
style={{ position: 'fixed', inset: 0, background: 'rgb(0 0 0 / 0.4)', zIndex: 999 }}
|
|
17
|
+
onClick={() => {
|
|
18
|
+
if (props.shouldDismissOnClickOutside ?? true) {
|
|
19
|
+
props.onDismissed?.()
|
|
20
|
+
}
|
|
21
|
+
}}
|
|
22
|
+
/>
|
|
23
|
+
<div
|
|
24
|
+
role="dialog"
|
|
25
|
+
aria-modal="true"
|
|
26
|
+
style={{
|
|
27
|
+
position: 'fixed',
|
|
28
|
+
left: 0,
|
|
29
|
+
right: 0,
|
|
30
|
+
bottom: 0,
|
|
31
|
+
zIndex: 1000,
|
|
32
|
+
background: 'var(--sheet-bg, Canvas)',
|
|
33
|
+
color: 'var(--sheet-fg, CanvasText)',
|
|
34
|
+
borderRadius: '16px 16px 0 0',
|
|
35
|
+
boxShadow: '0 -4px 24px rgb(0 0 0 / 0.2)',
|
|
36
|
+
maxHeight: props.skipPartiallyExpanded ? '90vh' : '60vh',
|
|
37
|
+
overflowY: 'auto',
|
|
38
|
+
}}
|
|
39
|
+
>
|
|
40
|
+
{(props.showDragHandle ?? true) && (
|
|
41
|
+
<div style={{ display: 'flex', justifyContent: 'center', padding: '8px 0' }}>
|
|
42
|
+
<div style={{ width: 36, height: 4, borderRadius: 2, background: 'color-mix(in srgb, CanvasText 30%, transparent)' }} />
|
|
43
|
+
</div>
|
|
44
|
+
)}
|
|
45
|
+
{props.content?.()}
|
|
46
|
+
</div>
|
|
47
|
+
</>;
|
|
48
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/** @jsxImportSource @nativescript-community/octane */
|
|
2
|
+
import { registerElement } from '@nativescript-community/octane'
|
|
3
|
+
import type { Attributes } from '@nativescript-community/octane/intrinsics'
|
|
4
|
+
import { ComposeDataDriver, JetpackCompose, registerJetpackCompose } from '@nativescript/jetpack-compose'
|
|
5
|
+
import { Application, GridLayout } from '@nativescript/core'
|
|
6
|
+
import { createNativeScriptRoot } from '@nativescript-community/octane'
|
|
7
|
+
import { useEffect, useRef, useState } from 'octane'
|
|
8
|
+
import type { UniversalComponent } from 'octane/universal'
|
|
9
|
+
import type { MaterialBottomSheetProps } from './android/types'
|
|
10
|
+
|
|
11
|
+
declare const com: any
|
|
12
|
+
|
|
13
|
+
declare module '@nativescript-community/octane/intrinsics' {
|
|
14
|
+
interface NativeScriptElements {
|
|
15
|
+
xplatandroidsheet: Attributes<typeof JetpackCompose>
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
registerElement('xplatandroidsheet', JetpackCompose)
|
|
20
|
+
registerJetpackCompose('xplat-material-sheet', (owner: any) =>
|
|
21
|
+
new ComposeDataDriver(new com.octanexplat.sheet.XplatSheetProvider(), owner),
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
// Dash-free ids — the registry keys feed the provider's AndroidView factory.
|
|
25
|
+
let seq = 0
|
|
26
|
+
|
|
27
|
+
function ContentRoot(props: { render: () => any }) @{
|
|
28
|
+
<>{props.render()}</>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Renders a Material 3 ModalBottomSheet whose content is a detached
|
|
32
|
+
* octane subtree hosted via XplatViewRegistry + AndroidView inside the
|
|
33
|
+
* sheet's dialog window. */
|
|
34
|
+
export function MaterialBottomSheet(props: MaterialBottomSheetProps) @{
|
|
35
|
+
const contentId = useRef<string>('')
|
|
36
|
+
if (!contentId.current) {
|
|
37
|
+
contentId.current = `xplatsheet${++seq}`
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const mounted = useRef<{ host: any; root: any } | null>(null)
|
|
41
|
+
const [ready, setReady] = useState(false)
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
const host = new GridLayout()
|
|
45
|
+
// See context-menu: _setupAsRootView needs a real Context on Android.
|
|
46
|
+
|
|
47
|
+
const context = (Application.android as any).foregroundActivity ?? (Application.android as any).startActivity
|
|
48
|
+
const nativeHost = host as any
|
|
49
|
+
|
|
50
|
+
nativeHost._setupAsRootView(context)
|
|
51
|
+
nativeHost.callLoaded()
|
|
52
|
+
const root = createNativeScriptRoot(host)
|
|
53
|
+
if (props.content) {
|
|
54
|
+
root.render(ContentRoot as unknown as UniversalComponent, { render: props.content })
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
com.octanexplat.sheet.XplatViewRegistry.register(contentId.current, host.android)
|
|
58
|
+
mounted.current = { host, root }
|
|
59
|
+
// Gate contentId on registration — AndroidView's factory resolves the
|
|
60
|
+
// host once contentId arrives in updateData.
|
|
61
|
+
setReady(true)
|
|
62
|
+
|
|
63
|
+
return () => {
|
|
64
|
+
com.octanexplat.sheet.XplatViewRegistry.unregister(contentId.current)
|
|
65
|
+
root.unmount?.()
|
|
66
|
+
mounted.current = null
|
|
67
|
+
setReady(false)
|
|
68
|
+
}
|
|
69
|
+
}, [])
|
|
70
|
+
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
if (props.content) {
|
|
73
|
+
mounted.current?.root?.render(ContentRoot as unknown as UniversalComponent, {
|
|
74
|
+
render: props.content,
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
}, [props.content])
|
|
78
|
+
|
|
79
|
+
<xplatandroidsheet
|
|
80
|
+
id={props.id}
|
|
81
|
+
className={props.className}
|
|
82
|
+
style={props.style}
|
|
83
|
+
composeId="xplat-material-sheet"
|
|
84
|
+
data={{
|
|
85
|
+
open: (props.open ?? false) ? 1 : 0,
|
|
86
|
+
contentId: ready && props.content ? contentId.current : '',
|
|
87
|
+
skipPartiallyExpanded: (props.skipPartiallyExpanded ?? false) ? 1 : 0,
|
|
88
|
+
showDragHandle: (props.showDragHandle ?? true) ? 1 : 0,
|
|
89
|
+
sheetGesturesEnabled: (props.sheetGesturesEnabled ?? true) ? 1 : 0,
|
|
90
|
+
shouldDismissOnBackPress: (props.shouldDismissOnBackPress ?? true) ? 1 : 0,
|
|
91
|
+
shouldDismissOnClickOutside: (props.shouldDismissOnClickOutside ?? true) ? 1 : 0,
|
|
92
|
+
containerColor: props.containerColor,
|
|
93
|
+
contentColor: props.contentColor,
|
|
94
|
+
scrimColor: props.scrimColor,
|
|
95
|
+
}}
|
|
96
|
+
onComposeEvent={(event: any) => {
|
|
97
|
+
if (event.data?.dismissed) {
|
|
98
|
+
props.onDismissed?.()
|
|
99
|
+
}
|
|
100
|
+
}}
|
|
101
|
+
/>;
|
|
102
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/** @jsxImportSource @nativescript-community/octane */
|
|
2
|
+
import { registerElement } from '@nativescript-community/octane'
|
|
3
|
+
import type { Attributes } from '@nativescript-community/octane/intrinsics'
|
|
4
|
+
import { SwiftUI, UIDataDriver, registerSwiftUI, SwiftUIManager } from '@nativescript/swift-ui'
|
|
5
|
+
import { GridLayout } from '@nativescript/core'
|
|
6
|
+
import { createNativeScriptRoot } from '@nativescript-community/octane'
|
|
7
|
+
import { useEffect, useRef } from 'octane'
|
|
8
|
+
import type { UniversalComponent, UniversalRoot } from 'octane/universal'
|
|
9
|
+
import type { SwiftUIBottomSheetProps } from './ios/types'
|
|
10
|
+
|
|
11
|
+
declare const XplatSheetProvider: any
|
|
12
|
+
|
|
13
|
+
declare module '@nativescript-community/octane/intrinsics' {
|
|
14
|
+
interface NativeScriptElements {
|
|
15
|
+
xplatiossheet: Attributes<typeof SwiftUI>
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
registerElement('xplatiossheet', SwiftUI)
|
|
20
|
+
registerSwiftUI('xplat-swiftui-sheet', (owner: any) =>
|
|
21
|
+
new UIDataDriver(new XplatSheetProvider(), owner),
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
// Host views keyed by dash-free registry id — same mechanism as the
|
|
25
|
+
// context-menu leaf (kept leaf-local while the shared-bridge question is open).
|
|
26
|
+
const hosts = new Map<string, GridLayout>()
|
|
27
|
+
const roots = new Map<string, UniversalRoot>()
|
|
28
|
+
|
|
29
|
+
function mountSubtree(id: string, render: () => any): void {
|
|
30
|
+
let host = hosts.get(id)
|
|
31
|
+
if (!host) {
|
|
32
|
+
host = new GridLayout()
|
|
33
|
+
hosts.set(id, host)
|
|
34
|
+
|
|
35
|
+
SwiftUIManager.registerNativeScriptViews(
|
|
36
|
+
{ [id]: true },
|
|
37
|
+
{
|
|
38
|
+
create: (fullId: string) => hosts.get(fullId.split('-')[0]) as any,
|
|
39
|
+
destroy: (fullId: string) => {
|
|
40
|
+
hosts.delete(fullId.split('-')[0])
|
|
41
|
+
},
|
|
42
|
+
} as any,
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let root = roots.get(id)
|
|
47
|
+
if (!root) {
|
|
48
|
+
root = createNativeScriptRoot(host)
|
|
49
|
+
roots.set(id, root)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
root.render(ContentRoot as unknown as UniversalComponent, { render })
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function unmountSubtree(id: string): void {
|
|
56
|
+
roots.get(id)?.unmount?.()
|
|
57
|
+
roots.delete(id)
|
|
58
|
+
hosts.delete(id)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function ContentRoot(props: { render: () => any }) @{
|
|
62
|
+
<>{props.render()}</>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let seq = 0
|
|
66
|
+
|
|
67
|
+
/** Renders a SwiftUI .sheet whose content is a detached octane subtree
|
|
68
|
+
* embedded through NativeScriptViewFactory-by-id. `fitToContents` uses the
|
|
69
|
+
* ported size-reader → `.presentationDetents([.height(size)])`. */
|
|
70
|
+
export function SwiftUIBottomSheet(props: SwiftUIBottomSheetProps) @{
|
|
71
|
+
const contentId = useRef<string>('')
|
|
72
|
+
if (!contentId.current) {
|
|
73
|
+
contentId.current = `xplatsheet${++seq}`
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
if (props.content) {
|
|
78
|
+
mountSubtree(contentId.current, props.content)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return () => {
|
|
82
|
+
unmountSubtree(contentId.current)
|
|
83
|
+
}
|
|
84
|
+
}, [])
|
|
85
|
+
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
if (props.content) {
|
|
88
|
+
mountSubtree(contentId.current, props.content)
|
|
89
|
+
}
|
|
90
|
+
}, [props.content])
|
|
91
|
+
|
|
92
|
+
<xplatiossheet
|
|
93
|
+
id={props.id}
|
|
94
|
+
className={props.className}
|
|
95
|
+
style={props.style}
|
|
96
|
+
swiftId="xplat-swiftui-sheet"
|
|
97
|
+
data={{
|
|
98
|
+
isPresented: props.open ?? false,
|
|
99
|
+
contentId: props.content ? contentId.current : '',
|
|
100
|
+
fitToContents: props.fitToContents ?? false,
|
|
101
|
+
detents: props.detents ?? ['medium'],
|
|
102
|
+
customHeight: props.customHeight,
|
|
103
|
+
customFraction: props.customFraction,
|
|
104
|
+
showDragIndicator: props.showDragIndicator ?? false,
|
|
105
|
+
interactiveDismissDisabled: props.interactiveDismissDisabled ?? false,
|
|
106
|
+
}}
|
|
107
|
+
onSwiftUIEvent={(event: any) => {
|
|
108
|
+
if (event.data?.dismissed) {
|
|
109
|
+
props.onDismissed?.()
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const presented = event.data?.isPresented
|
|
114
|
+
if (typeof presented === 'boolean') {
|
|
115
|
+
props.onPresentedChange?.(presented)
|
|
116
|
+
}
|
|
117
|
+
}}
|
|
118
|
+
/>;
|
|
119
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface MaterialBottomSheetProps {
|
|
2
|
+
id?: string
|
|
3
|
+
className?: any
|
|
4
|
+
style?: any
|
|
5
|
+
/** Controls sheet visibility. */
|
|
6
|
+
open?: boolean
|
|
7
|
+
/** Called when the sheet asks to close (swipe, scrim, back). */
|
|
8
|
+
onDismissed?: () => void
|
|
9
|
+
/** Skip the half-expanded detent; only fully expanded/hidden remain. */
|
|
10
|
+
skipPartiallyExpanded?: boolean
|
|
11
|
+
/** Show the drag handle affordance. @default true */
|
|
12
|
+
showDragHandle?: boolean
|
|
13
|
+
/** Allow swipe gestures on the sheet surface. @default true
|
|
14
|
+
* Not supported by the material3 version the app resolves — prop is
|
|
15
|
+
* accepted but ignored. */
|
|
16
|
+
sheetGesturesEnabled?: boolean
|
|
17
|
+
/** @default true */
|
|
18
|
+
shouldDismissOnBackPress?: boolean
|
|
19
|
+
/** @default true — accepted but ignored on the resolved material3. */
|
|
20
|
+
shouldDismissOnClickOutside?: boolean
|
|
21
|
+
containerColor?: string
|
|
22
|
+
contentColor?: string
|
|
23
|
+
scrimColor?: string
|
|
24
|
+
/** Render fn producing the sheet content — hosted inside the sheet's
|
|
25
|
+
* dialog window through AndroidView. */
|
|
26
|
+
content?: () => any
|
|
27
|
+
}
|
package/src/ios/index.ts
ADDED
package/src/ios/types.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type SwiftUISheetDetent = 'medium' | 'large' | 'fraction' | 'height'
|
|
2
|
+
|
|
3
|
+
export interface SwiftUIBottomSheetProps {
|
|
4
|
+
id?: string
|
|
5
|
+
className?: any
|
|
6
|
+
style?: any
|
|
7
|
+
/** Controls sheet presentation. */
|
|
8
|
+
open?: boolean
|
|
9
|
+
/** Reports the platform-side presented state (echo-suppressed). */
|
|
10
|
+
onPresentedChange?: (presented: boolean) => void
|
|
11
|
+
/** Called when the sheet dismisses. */
|
|
12
|
+
onDismissed?: () => void
|
|
13
|
+
/** Measure content height and use it as the single detent (iOS 16+). */
|
|
14
|
+
fitToContents?: boolean
|
|
15
|
+
/** Detent set — 'fraction' reads `customFraction`, 'height' reads
|
|
16
|
+
* `customHeight` (iOS 16+). @default ['medium'] */
|
|
17
|
+
detents?: SwiftUISheetDetent[]
|
|
18
|
+
customFraction?: number
|
|
19
|
+
customHeight?: number
|
|
20
|
+
/** Show the grabber. @default false (SwiftUI default is hidden). */
|
|
21
|
+
showDragIndicator?: boolean
|
|
22
|
+
interactiveDismissDisabled?: boolean
|
|
23
|
+
/** Render fn producing the sheet content — hosted inside the sheet
|
|
24
|
+
* presentation through NativeScriptViewFactory. */
|
|
25
|
+
content?: () => any
|
|
26
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface AppKitSheetProps {
|
|
2
|
+
id?: string
|
|
3
|
+
className?: any
|
|
4
|
+
style?: any
|
|
5
|
+
/** Controls sheet presentation. */
|
|
6
|
+
open?: boolean
|
|
7
|
+
/** Called when the sheet window closes (close button or `open`→false). */
|
|
8
|
+
onDismissed?: () => void
|
|
9
|
+
/** Render fn producing the sheet content — hosted in the sheet's own
|
|
10
|
+
* octane root inside the presented window. */
|
|
11
|
+
content?: () => any
|
|
12
|
+
}
|
package/src/web/index.ts
ADDED
package/src/web/types.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface BottomSheetProps {
|
|
2
|
+
id?: string
|
|
3
|
+
className?: any
|
|
4
|
+
style?: any
|
|
5
|
+
open?: boolean
|
|
6
|
+
onDismissed?: () => void
|
|
7
|
+
skipPartiallyExpanded?: boolean
|
|
8
|
+
showDragHandle?: boolean
|
|
9
|
+
sheetGesturesEnabled?: boolean
|
|
10
|
+
shouldDismissOnBackPress?: boolean
|
|
11
|
+
shouldDismissOnClickOutside?: boolean
|
|
12
|
+
containerColor?: string
|
|
13
|
+
contentColor?: string
|
|
14
|
+
scrimColor?: string
|
|
15
|
+
content?: () => any
|
|
16
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface MaterialBottomSheetProps {
|
|
2
|
+
id?: string
|
|
3
|
+
className?: any
|
|
4
|
+
style?: any
|
|
5
|
+
open?: boolean
|
|
6
|
+
onDismissed?: () => void
|
|
7
|
+
skipPartiallyExpanded?: boolean
|
|
8
|
+
showDragHandle?: boolean
|
|
9
|
+
sheetGesturesEnabled?: boolean
|
|
10
|
+
shouldDismissOnBackPress?: boolean
|
|
11
|
+
shouldDismissOnClickOutside?: boolean
|
|
12
|
+
containerColor?: string
|
|
13
|
+
contentColor?: string
|
|
14
|
+
scrimColor?: string
|
|
15
|
+
content?: () => any
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export declare function MaterialBottomSheet(props: MaterialBottomSheetProps): unknown
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type SwiftUISheetDetent = 'medium' | 'large' | 'fraction' | 'height'
|
|
2
|
+
|
|
3
|
+
export interface SwiftUIBottomSheetProps {
|
|
4
|
+
id?: string
|
|
5
|
+
className?: any
|
|
6
|
+
style?: any
|
|
7
|
+
open?: boolean
|
|
8
|
+
onPresentedChange?: (presented: boolean) => void
|
|
9
|
+
onDismissed?: () => void
|
|
10
|
+
fitToContents?: boolean
|
|
11
|
+
detents?: SwiftUISheetDetent[]
|
|
12
|
+
customFraction?: number
|
|
13
|
+
customHeight?: number
|
|
14
|
+
showDragIndicator?: boolean
|
|
15
|
+
interactiveDismissDisabled?: boolean
|
|
16
|
+
content?: () => any
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export declare function SwiftUIBottomSheet(props: SwiftUIBottomSheetProps): unknown
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface BottomSheetProps {
|
|
2
|
+
id?: string
|
|
3
|
+
className?: any
|
|
4
|
+
style?: any
|
|
5
|
+
open?: boolean
|
|
6
|
+
onDismissed?: () => void
|
|
7
|
+
skipPartiallyExpanded?: boolean
|
|
8
|
+
showDragHandle?: boolean
|
|
9
|
+
sheetGesturesEnabled?: boolean
|
|
10
|
+
shouldDismissOnBackPress?: boolean
|
|
11
|
+
shouldDismissOnClickOutside?: boolean
|
|
12
|
+
containerColor?: string
|
|
13
|
+
contentColor?: string
|
|
14
|
+
scrimColor?: string
|
|
15
|
+
content?: () => any
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export declare function BottomSheet(props: BottomSheetProps): unknown
|
package/index.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {}
|
package/index.js
DELETED