@octane-xplat/macos-renderer 0.0.1 → 0.11.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,62 @@
1
+ export namespace JSX {
2
+ export type Element = unknown
3
+
4
+ export interface IntrinsicAttributes {
5
+ key?: string | number
6
+ }
7
+
8
+ export interface HostProps {
9
+ id?: string
10
+ key?: string | number
11
+ ref?: unknown
12
+ className?: string
13
+ style?: Record<string, unknown>
14
+ children?: unknown
15
+ flexGrow?: number
16
+ textAlignment?: string
17
+ onTap?: (event?: any) => void
18
+ accessibilityLabel?: string
19
+ accessibilityRole?: string
20
+ accessibilityHint?: string
21
+ accessibilityValue?: string
22
+ accessibilityState?: Record<string, unknown>
23
+ accessibilityLiveRegion?: 'none' | 'polite' | 'assertive'
24
+ }
25
+
26
+ export interface IntrinsicElements {
27
+ stack: HostProps & {
28
+ children?: unknown
29
+ spacing?: number
30
+ }
31
+ label: HostProps & {
32
+ text?: string | number
33
+ fontSize?: number
34
+ whiteSpace?: string
35
+ maxLines?: number
36
+ textOverflow?: string
37
+ }
38
+ button: HostProps & {
39
+ title?: string
40
+ enabled?: boolean
41
+ onPress?: () => void
42
+ }
43
+ flexboxlayout: Record<string, unknown>
44
+ gridlayout: Record<string, unknown>
45
+ absolutelayout: Record<string, unknown>
46
+ stacklayout: Record<string, unknown>
47
+ rootlayout: Record<string, unknown>
48
+ scrollview: Record<string, unknown>
49
+ textfield: Record<string, unknown>
50
+ textview: Record<string, unknown>
51
+ image: Record<string, unknown>
52
+ span: Record<string, unknown>
53
+ formattedstring: Record<string, unknown>
54
+ pager: Record<string, unknown>
55
+ webview: Record<string, unknown>
56
+ cameraplus: Record<string, unknown>
57
+ xplatvideo: Record<string, unknown>
58
+ canvasview: Record<string, unknown>
59
+ svgview: Record<string, unknown>
60
+ [key: string]: any
61
+ }
62
+ }
@@ -0,0 +1,7 @@
1
+ export { JSX } from './intrinsics.js'
2
+
3
+ import type { JSX } from './intrinsics.js'
4
+
5
+ export declare function jsx(type: unknown, props: unknown, key?: unknown): JSX.Element
6
+ export declare function jsxs(type: unknown, props: unknown, key?: unknown): JSX.Element
7
+ export declare function Fragment(props: { children?: JSX.Element }): JSX.Element
@@ -0,0 +1,17 @@
1
+ /** Internal bridge for the self-drawn window layer; native objects are supplied
2
+ * by the renderer or test host. Coordinates use top-left window points. */
3
+ export function showWindowLayer(
4
+ options: Record<string, any>,
5
+ driver: {
6
+ createRoot: (view: any, anchor: any) => any
7
+ fittingSize: (view: any) => { width: number; height: number } | null
8
+ View?: any
9
+ Event?: any
10
+ Center?: any
11
+ },
12
+ ): {
13
+ contentView: any
14
+ closed: boolean
15
+ update(props: Record<string, any>, positioning?: Record<string, any>): void
16
+ close(): void
17
+ } | null
package/src/layer.mjs ADDED
@@ -0,0 +1,221 @@
1
+ const notificationOwners = new WeakMap()
2
+
3
+ function ownNotifications(view) {
4
+ let record = notificationOwners.get(view)
5
+ if (!record) {
6
+ record = {
7
+ count: 0,
8
+ frame: view.postsFrameChangedNotifications,
9
+ bounds: view.postsBoundsChangedNotifications,
10
+ }
11
+
12
+ notificationOwners.set(view, record)
13
+ view.postsFrameChangedNotifications = true
14
+ view.postsBoundsChangedNotifications = true
15
+ }
16
+
17
+ record.count++
18
+ return () => {
19
+ if (--record.count) {
20
+ return
21
+ }
22
+
23
+ notificationOwners.delete(view)
24
+ if (view.postsFrameChangedNotifications === true) {
25
+ view.postsFrameChangedNotifications = record.frame
26
+ }
27
+
28
+ if (view.postsBoundsChangedNotifications === true) {
29
+ view.postsBoundsChangedNotifications = record.bounds
30
+ }
31
+ }
32
+ }
33
+
34
+ /** A self-drawn in-window layer. Position callback uses top-left window points.
35
+ * AppKit popovers remain available for platform-authentic consumers. */
36
+ export function showWindowLayer(
37
+ options,
38
+ {
39
+ createRoot,
40
+ fittingSize,
41
+ View = globalThis.NSView,
42
+ Event = globalThis.NSEvent,
43
+ Center = globalThis.NSNotificationCenter,
44
+ },
45
+ ) {
46
+ const anchor = options.anchor
47
+ const container = anchor?.window?.contentView
48
+ if (
49
+ !container ||
50
+ !View ||
51
+ !anchor.convertRectToView ||
52
+ (options.lightDismiss &&
53
+ typeof Event?.addLocalMonitorForEventsMatchingMaskHandler !== 'function')
54
+ ) {
55
+ return null
56
+ }
57
+
58
+ const contentView = View.alloc().initWithFrame({
59
+ origin: { x: 0, y: 0 },
60
+ size: { width: 1, height: 1 },
61
+ })
62
+
63
+ const root = createRoot(contentView, anchor)
64
+ let surface = options.props ?? {}
65
+ let positioning = options
66
+ let monitor = null
67
+ const observers = []
68
+ const releases = []
69
+ const updatePosition = () => {
70
+ if (layer.closed) {
71
+ return
72
+ }
73
+
74
+ const viewport = container.bounds
75
+ const height = viewport.size.height
76
+ const rect = anchor.convertRectToView(anchor.bounds, container)
77
+ const flipped = !!container.isFlipped
78
+ const bounds = {
79
+ left: rect.origin.x - viewport.origin.x,
80
+ top: flipped
81
+ ? rect.origin.y - viewport.origin.y
82
+ : height - (rect.origin.y - viewport.origin.y) - rect.size.height,
83
+ width: rect.size.width,
84
+ height: rect.size.height,
85
+ }
86
+
87
+ const size = fittingSize(contentView) ?? { width: 1, height: 1 }
88
+ const point = positioning.position(bounds, size, {
89
+ left: 0,
90
+ top: 0,
91
+ width: viewport.size.width,
92
+ height,
93
+ })
94
+
95
+ contentView.frame = {
96
+ origin: {
97
+ x: viewport.origin.x + point.left,
98
+ y: viewport.origin.y + (flipped ? point.top : height - point.top - size.height),
99
+ },
100
+ size,
101
+ }
102
+ }
103
+
104
+ const layer = {
105
+ contentView,
106
+ closed: false,
107
+ update(props, nextPositioning) {
108
+ if (layer.closed) {
109
+ return
110
+ }
111
+
112
+ surface = props
113
+ if (
114
+ nextPositioning?.lightDismiss &&
115
+ typeof Event?.addLocalMonitorForEventsMatchingMaskHandler !== 'function'
116
+ ) {
117
+ throw new TypeError('AppKit lightDismiss requires a local mouse monitor')
118
+ }
119
+
120
+ if (nextPositioning) {
121
+ positioning = { ...positioning, ...nextPositioning }
122
+ }
123
+
124
+ root.render(options.component, surface)
125
+ updatePosition()
126
+ },
127
+ close() {
128
+ if (layer.closed) {
129
+ return
130
+ }
131
+
132
+ layer.closed = true
133
+ if (monitor) {
134
+ Event.removeMonitor(monitor)
135
+ }
136
+
137
+ for (const observer of observers) {
138
+ Center.defaultCenter.removeObserver(observer)
139
+ }
140
+
141
+ for (const release of releases) {
142
+ release()
143
+ }
144
+
145
+ try {
146
+ root.unmount()
147
+ } finally {
148
+ contentView.removeFromSuperview()
149
+ }
150
+ },
151
+ }
152
+
153
+ try {
154
+ container.addSubview(contentView)
155
+ layer.update(surface)
156
+ // Reposition for window resize, anchor movement and enclosing scroll views.
157
+ for (let view = anchor; view; view = view.superview) {
158
+ releases.push(ownNotifications(view))
159
+ for (const name of [
160
+ 'NSViewFrameDidChangeNotification',
161
+ 'NSViewBoundsDidChangeNotification',
162
+ ]) {
163
+ observers.push(
164
+ Center.defaultCenter.addObserverForNameObjectQueueUsingBlock(
165
+ name,
166
+ view,
167
+ null,
168
+ updatePosition,
169
+ ),
170
+ )
171
+ }
172
+ }
173
+
174
+ observers.push(
175
+ Center.defaultCenter.addObserverForNameObjectQueueUsingBlock(
176
+ 'NSWindowWillCloseNotification',
177
+ anchor.window,
178
+ null,
179
+ () => layer.close(),
180
+ ),
181
+ )
182
+
183
+ observers.push(
184
+ Center.defaultCenter.addObserverForNameObjectQueueUsingBlock(
185
+ 'NSWindowDidResizeNotification',
186
+ anchor.window,
187
+ null,
188
+ updatePosition,
189
+ ),
190
+ )
191
+
192
+ if (Event?.addLocalMonitorForEventsMatchingMaskHandler) {
193
+ monitor = Event.addLocalMonitorForEventsMatchingMaskHandler(
194
+ (1 << 1) | (1 << 3) | (1 << 25),
195
+ (event) => {
196
+ if (layer.closed || !positioning.lightDismiss || event.window !== anchor.window) {
197
+ return event
198
+ }
199
+
200
+ const point = container.convertPointFromView(event.locationInWindow, null)
201
+ const frame = contentView.frame
202
+ if (
203
+ point.x < frame.origin.x ||
204
+ point.y < frame.origin.y ||
205
+ point.x > frame.origin.x + frame.size.width ||
206
+ point.y > frame.origin.y + frame.size.height
207
+ ) {
208
+ options.onClose?.()
209
+ }
210
+
211
+ return event
212
+ },
213
+ )
214
+ }
215
+
216
+ return layer
217
+ } catch (error) {
218
+ layer.close()
219
+ throw error
220
+ }
221
+ }
@@ -0,0 +1,47 @@
1
+ // The shared pan leaf imports Utils eagerly, but this spike doesn't implement
2
+ // NativeScript gestures. Fail explicitly if pan velocity is ever requested.
3
+ export const Utils = {
4
+ layout: {
5
+ getDisplayDensity() {
6
+ throw new Error('NativeScript display density is unavailable in the AppKit spike')
7
+ },
8
+ },
9
+ }
10
+
11
+ // The AppKit build may resolve a shared native leaf before tree-shaking it.
12
+ // These values keep import-time platform checks safe; components that need
13
+ // NativeScript application services have explicit AppKit leaves or render
14
+ // an unsupported state.
15
+ export const Application = { ios: null, android: null, on() {}, off() {} }
16
+ export const isAndroid = false
17
+ export const isIOS = false
18
+ export const AccessibilityRole = {
19
+ button: 'button',
20
+ link: 'link',
21
+ heading: 'heading',
22
+ image: 'image',
23
+ text: 'text',
24
+ }
25
+
26
+ // Octane's universal entry may evaluate the NativeScript driver module
27
+ // while the AppKit renderer is selected. The AppKit host never instantiates
28
+ // these NativeScript node classes.
29
+ export class ActionBar {}
30
+ export class ContentView {}
31
+ export class FormattedString {}
32
+ export class LayoutBase {}
33
+ export class ListView {}
34
+ export class Span {}
35
+ export class TextBase {}
36
+ export class View {}
37
+ export const unsetValue = Symbol('unsetValue')
38
+
39
+ // The AppKit spike has no display-link driver — motion's clock just needs a
40
+ // frame-shaped timer; the host's pump runs setTimeout on the main run loop.
41
+ export function requestAnimationFrame(callback: (time: number) => void): unknown {
42
+ return setTimeout(() => callback(Date.now()), 16)
43
+ }
44
+
45
+ export function cancelAnimationFrame(id: unknown) {
46
+ clearTimeout(id)
47
+ }
@@ -0,0 +1,30 @@
1
+ // AppKit has no NativeScript Core device globals or iOS/Android escape bags.
2
+ export function applyEscapeProps() {}
3
+
4
+ export function nativeAccessibilityRole(role: string) {
5
+ return role
6
+ }
7
+
8
+ export function nativeAccessibilityState(state?: {
9
+ disabled?: boolean
10
+ selected?: boolean
11
+ checked?: boolean
12
+ }) {
13
+ if (state?.disabled) {
14
+ return 'disabled'
15
+ }
16
+
17
+ if (state?.selected) {
18
+ return 'selected'
19
+ }
20
+
21
+ if (state?.checked === true) {
22
+ return 'checked'
23
+ }
24
+
25
+ if (state?.checked === false) {
26
+ return 'unchecked'
27
+ }
28
+
29
+ return undefined
30
+ }