@octane-xplat/sheet 0.0.1 → 0.10.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.
@@ -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,2 @@
1
+ export { MaterialBottomSheet } from '../MaterialBottomSheet.android.tsrx'
2
+ export type { MaterialBottomSheetProps } from './types'
@@ -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
+ }
@@ -0,0 +1,2 @@
1
+ export { SwiftUIBottomSheet } from '../SwiftUIBottomSheet.ios.tsrx'
2
+ export type { SwiftUIBottomSheetProps, SwiftUISheetDetent } from './types'
@@ -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,2 @@
1
+ export { AppKitSheet } from '../AppKitSheet.macos.tsrx'
2
+ export type { AppKitSheetProps } from './types'
@@ -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
+ }
@@ -0,0 +1,4 @@
1
+ import './styles.css'
2
+
3
+ export { BottomSheet } from '../BottomSheet.web.tsrx'
4
+ export type { BottomSheetProps } from './types'
@@ -0,0 +1,57 @@
1
+ /* xplat-web-only:start */
2
+ .xplat-web-sheet {
3
+ position: fixed;
4
+ inset: 0;
5
+ width: 100%;
6
+ height: 100%;
7
+ max-width: none;
8
+ max-height: none;
9
+ margin: 0;
10
+ padding: 0;
11
+ border: 0;
12
+ background: transparent;
13
+ color: inherit;
14
+ overflow: visible;
15
+ display: flex;
16
+ align-items: flex-end;
17
+ justify-content: center;
18
+ }
19
+ .xplat-web-sheet::backdrop {
20
+ background: var(--xplat-sheet-scrim, rgb(0 0 0 / 0.4));
21
+ }
22
+
23
+ .xplat-web-sheet-panel {
24
+ box-sizing: border-box;
25
+ width: 100%;
26
+ max-height: var(--xplat-sheet-max-height, 60vh);
27
+ overflow-y: auto;
28
+ overscroll-behavior: contain;
29
+ background: var(--xplat-sheet-container, var(--sheet-bg, Canvas));
30
+ color: var(--xplat-sheet-content, var(--sheet-fg, CanvasText));
31
+ border-radius: 16px 16px 0 0;
32
+ box-shadow: 0 -4px 24px rgb(0 0 0 / 0.2);
33
+ transform: translateY(var(--xplat-sheet-drag-offset, 0px));
34
+ transition: transform 180ms ease-out;
35
+ }
36
+
37
+ .xplat-web-sheet-handle {
38
+ display: flex;
39
+ justify-content: center;
40
+ padding: 8px 0;
41
+ cursor: grab;
42
+ touch-action: none;
43
+ }
44
+
45
+ .xplat-web-sheet-handle-mark {
46
+ width: 36px;
47
+ height: 4px;
48
+ border-radius: 2px;
49
+ background: color-mix(in srgb, CanvasText 30%, transparent);
50
+ }
51
+
52
+ @media (prefers-reduced-motion: reduce) {
53
+ .xplat-web-sheet-panel {
54
+ transition: none;
55
+ }
56
+ }
57
+ /* xplat-web-only:end */
@@ -0,0 +1,27 @@
1
+ /** Minimal element shape used by the Web focus-restoration ref. */
2
+ export type WebFocusTarget = {
3
+ focus(options?: { preventScroll?: boolean }): void
4
+ isConnected: boolean
5
+ closest(selector: string): object | null
6
+ }
7
+
8
+ export interface BottomSheetProps {
9
+ id?: string
10
+ /** Accessible name for the modal dialog. */
11
+ label?: string
12
+ /** Element that should regain focus when the sheet closes. */
13
+ finalFocusRef?: { current: WebFocusTarget | null }
14
+ className?: any
15
+ style?: any
16
+ open?: boolean
17
+ onDismissed?: () => void
18
+ skipPartiallyExpanded?: boolean
19
+ showDragHandle?: boolean
20
+ sheetGesturesEnabled?: boolean
21
+ shouldDismissOnBackPress?: boolean
22
+ shouldDismissOnClickOutside?: boolean
23
+ containerColor?: string
24
+ contentColor?: string
25
+ scrimColor?: string
26
+ content?: () => any
27
+ }
@@ -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,10 @@
1
+ export interface AppKitSheetProps {
2
+ id?: string
3
+ className?: any
4
+ style?: any
5
+ open?: boolean
6
+ onDismissed?: () => void
7
+ content?: () => any
8
+ }
9
+
10
+ export declare function AppKitSheet(props: AppKitSheetProps): unknown
@@ -0,0 +1,29 @@
1
+ /** Minimal element shape used by the Web focus-restoration ref. */
2
+ type WebFocusTarget = {
3
+ focus(options?: { preventScroll?: boolean }): void
4
+ isConnected: boolean
5
+ closest(selector: string): object | null
6
+ }
7
+
8
+ export interface BottomSheetProps {
9
+ id?: string
10
+ /** Accessible name for the modal dialog. */
11
+ label?: string
12
+ /** Element that should regain focus when the sheet closes. */
13
+ finalFocusRef?: { current: WebFocusTarget | null }
14
+ className?: any
15
+ style?: any
16
+ open?: boolean
17
+ onDismissed?: () => void
18
+ skipPartiallyExpanded?: boolean
19
+ showDragHandle?: boolean
20
+ sheetGesturesEnabled?: boolean
21
+ shouldDismissOnBackPress?: boolean
22
+ shouldDismissOnClickOutside?: boolean
23
+ containerColor?: string
24
+ contentColor?: string
25
+ scrimColor?: string
26
+ content?: () => any
27
+ }
28
+
29
+ export declare function BottomSheet(props: BottomSheetProps): unknown
package/index.d.ts DELETED
@@ -1 +0,0 @@
1
- export {}
package/index.js DELETED
@@ -1,2 +0,0 @@
1
- // Reservation stub; implementation will arrive in a later release.
2
- module.exports = {}