@octane-xplat/macos-renderer 0.13.0 → 0.15.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.
Files changed (222) hide show
  1. package/README.md +95 -20
  2. package/package.json +15 -2
  3. package/src/appearance.ts +2 -0
  4. package/src/fonts.ts +2 -0
  5. package/src/formatted-text.ts +147 -0
  6. package/src/gestures.ts +196 -0
  7. package/src/host-globals.d.ts +55 -0
  8. package/src/html-text.ts +88 -0
  9. package/src/image.ts +91 -16
  10. package/src/index.d.ts +9 -0
  11. package/src/index.ts +5030 -678
  12. package/src/layer.ts +11 -9
  13. package/src/list-template.ts +266 -0
  14. package/src/list-view.ts +445 -0
  15. package/src/portal-components.macos.tsx +86 -0
  16. package/src/presentation.ts +48 -15
  17. package/src/scroll-view.ts +124 -0
  18. package/src/tab-view.ts +512 -0
  19. package/src/template-props.ts +402 -0
  20. package/src/vendor/listview/LICENSE +19 -0
  21. package/src/vendor/listview/README.md +1 -0
  22. package/src/vendor/listview/binding-options.ts +189 -0
  23. package/src/vendor/listview/expressions.ts +285 -0
  24. package/src/webview.ts +36 -7
  25. package/types/ARKitCore.d.ts +199 -0
  26. package/types/AVFAudio.d.ts +2304 -0
  27. package/types/AVFoundation.d.ts +6974 -0
  28. package/types/AVKit.d.ts +215 -0
  29. package/types/AVRouting.d.ts +7 -0
  30. package/types/Accelerate.d.ts +4776 -0
  31. package/types/Accessibility.d.ts +69 -0
  32. package/types/Accounts.d.ts +134 -0
  33. package/types/AdSupport.d.ts +12 -0
  34. package/types/AddressBook.d.ts +1012 -0
  35. package/types/AppKit.d.ts +34247 -0
  36. package/types/AppTrackingTransparency.d.ts +23 -0
  37. package/types/AppleScriptKit.d.ts +6 -0
  38. package/types/ApplicationServices.d.ts +6346 -0
  39. package/types/AudioToolbox.d.ts +7129 -0
  40. package/types/AudioVideoBridging.d.ts +613 -0
  41. package/types/AuthenticationServices.d.ts +982 -0
  42. package/types/AutomaticAssessmentConfiguration.d.ts +46 -0
  43. package/types/Automator.d.ts +248 -0
  44. package/types/BackgroundAssets.d.ts +9 -0
  45. package/types/BrowserEngineKit.d.ts +166 -0
  46. package/types/BusinessChat.d.ts +29 -0
  47. package/types/CFNetwork.d.ts +931 -0
  48. package/types/CalendarStore.d.ts +354 -0
  49. package/types/CallKit.d.ts +30 -0
  50. package/types/Carbon.d.ts +16207 -0
  51. package/types/ClassKit.d.ts +281 -0
  52. package/types/CloudKit.d.ts +1565 -0
  53. package/types/Collaboration.d.ts +123 -0
  54. package/types/ColorSync.d.ts +562 -0
  55. package/types/CompositorServices.d.ts +60 -0
  56. package/types/ComputeGraph.d.ts +16 -0
  57. package/types/Contacts.d.ts +1558 -0
  58. package/types/ContactsUI.d.ts +39 -0
  59. package/types/CoreAudio.d.ts +1376 -0
  60. package/types/CoreAudioKit.d.ts +122 -0
  61. package/types/CoreAudioTypes.d.ts +1003 -0
  62. package/types/CoreBluetooth.d.ts +657 -0
  63. package/types/CoreData.d.ts +2479 -0
  64. package/types/CoreFoundation.d.ts +5546 -0
  65. package/types/CoreGraphics.d.ts +3676 -0
  66. package/types/CoreHaptics.d.ts +361 -0
  67. package/types/CoreImage.d.ts +4781 -0
  68. package/types/CoreLocation.d.ts +564 -0
  69. package/types/CoreMIDI.d.ts +1538 -0
  70. package/types/CoreML.d.ts +905 -0
  71. package/types/CoreMedia.d.ts +2615 -0
  72. package/types/CoreMediaIO.d.ts +1046 -0
  73. package/types/CoreMotion.d.ts +316 -0
  74. package/types/CoreServices.d.ts +24810 -0
  75. package/types/CoreSpotlight.d.ts +789 -0
  76. package/types/CoreTelephony.d.ts +31 -0
  77. package/types/CoreText.d.ts +3973 -0
  78. package/types/CoreTransferable.d.ts +3 -0
  79. package/types/CoreVideo.d.ts +1162 -0
  80. package/types/CoreWLAN.d.ts +556 -0
  81. package/types/CryptoTokenKit.d.ts +548 -0
  82. package/types/DVDPlayback.d.ts +931 -0
  83. package/types/DataDetection.d.ts +66 -0
  84. package/types/DeviceCheck.d.ts +46 -0
  85. package/types/DeviceDiscoveryExtension.d.ts +142 -0
  86. package/types/DirectoryService.d.ts +966 -0
  87. package/types/DiscRecording.d.ts +2345 -0
  88. package/types/DiscRecordingUI.d.ts +288 -0
  89. package/types/DiskArbitration.d.ts +366 -0
  90. package/types/DriverKit.d.ts +639 -0
  91. package/types/EventKit.d.ts +640 -0
  92. package/types/ExceptionHandling.d.ts +56 -0
  93. package/types/ExecutionPolicy.d.ts +30 -0
  94. package/types/ExternalAccessory.d.ts +98 -0
  95. package/types/FSKit.d.ts +237 -0
  96. package/types/FileProvider.d.ts +495 -0
  97. package/types/FileProviderUI.d.ts +31 -0
  98. package/types/FinderSync.d.ts +165 -0
  99. package/types/FocusEngine.d.ts +133 -0
  100. package/types/ForceFeedback.d.ts +399 -0
  101. package/types/Foundation.d.ts +18216 -0
  102. package/types/GLKit.d.ts +937 -0
  103. package/types/GLUT.d.ts +1018 -0
  104. package/types/GSS.d.ts +855 -0
  105. package/types/GameController.d.ts +1394 -0
  106. package/types/GameKit.d.ts +1958 -0
  107. package/types/GameSave.d.ts +54 -0
  108. package/types/GameplayKit.d.ts +1645 -0
  109. package/types/HealthKit.d.ts +278 -0
  110. package/types/Hypervisor.d.ts +2079 -0
  111. package/types/ICADevices.d.ts +1400 -0
  112. package/types/IOBluetooth.d.ts +6094 -0
  113. package/types/IOBluetoothUI.d.ts +250 -0
  114. package/types/IOKit.d.ts +25804 -0
  115. package/types/IOSurface.d.ts +435 -0
  116. package/types/IOUSBHost.d.ts +809 -0
  117. package/types/ImageCaptureCore.d.ts +1104 -0
  118. package/types/ImageIO.d.ts +1697 -0
  119. package/types/InputMethodKit.d.ts +264 -0
  120. package/types/InstallerPlugins.d.ts +102 -0
  121. package/types/InstantMessage.d.ts +229 -0
  122. package/types/Intents.d.ts +1833 -0
  123. package/types/IntentsUI.d.ts +5 -0
  124. package/types/JavaRuntimeSupport.d.ts +520 -0
  125. package/types/JavaScriptCore.d.ts +934 -0
  126. package/types/Kerberos.d.ts +3754 -0
  127. package/types/Kernel.d.ts +33698 -0
  128. package/types/KernelManagement.d.ts +5 -0
  129. package/types/LatentSemanticMapping.d.ts +152 -0
  130. package/types/LinkPresentation.d.ts +57 -0
  131. package/types/LocalAuthentication.d.ts +94 -0
  132. package/types/MLCompute.d.ts +2343 -0
  133. package/types/MapKit.d.ts +2125 -0
  134. package/types/Matter.d.ts +151 -0
  135. package/types/MediaAccessibility.d.ts +137 -0
  136. package/types/MediaLibrary.d.ts +394 -0
  137. package/types/MediaPlayer.d.ts +475 -0
  138. package/types/MediaToolbox.d.ts +70 -0
  139. package/types/Metal.d.ts +4542 -0
  140. package/types/MetalFX.d.ts +107 -0
  141. package/types/MetalKit.d.ts +524 -0
  142. package/types/MetalPerformanceShaders.d.ts +9306 -0
  143. package/types/MetalPerformanceShadersGraph.d.ts +1394 -0
  144. package/types/MetricKit.d.ts +231 -0
  145. package/types/ModelIO.d.ts +2908 -0
  146. package/types/MultipeerConnectivity.d.ts +348 -0
  147. package/types/NaturalLanguage.d.ts +556 -0
  148. package/types/NearbyInteraction.d.ts +51 -0
  149. package/types/NetFS.d.ts +99 -0
  150. package/types/Network.d.ts +1622 -0
  151. package/types/NetworkExtension.d.ts +1574 -0
  152. package/types/NotificationCenter.d.ts +103 -0
  153. package/types/OSAKit.d.ts +260 -0
  154. package/types/OSLog.d.ts +208 -0
  155. package/types/OpenAL.d.ts +314 -0
  156. package/types/OpenCL.d.ts +2489 -0
  157. package/types/OpenDirectory.d.ts +1919 -0
  158. package/types/OpenGL.d.ts +11322 -0
  159. package/types/PCSC.d.ts +534 -0
  160. package/types/PDFKit.d.ts +1575 -0
  161. package/types/PHASE.d.ts +166 -0
  162. package/types/ParavirtualizedGraphics.d.ts +153 -0
  163. package/types/PassKit.d.ts +1287 -0
  164. package/types/PencilKit.d.ts +201 -0
  165. package/types/Photos.d.ts +1125 -0
  166. package/types/PhotosUI.d.ts +397 -0
  167. package/types/PreferencePanes.d.ts +68 -0
  168. package/types/ProximityReaderStub.d.ts +5 -0
  169. package/types/PushKit.d.ts +45 -0
  170. package/types/Quartz.d.ts +1642 -0
  171. package/types/QuartzCore.d.ts +1398 -0
  172. package/types/QuickLook.d.ts +212 -0
  173. package/types/QuickLookThumbnailing.d.ts +131 -0
  174. package/types/QuickLookUI.d.ts +117 -0
  175. package/types/REVISION.json +5 -0
  176. package/types/ReplayKit.d.ts +294 -0
  177. package/types/Runtime.d.ts +51605 -0
  178. package/types/SafariServices.d.ts +430 -0
  179. package/types/SafetyKit.d.ts +7 -0
  180. package/types/SceneKit.d.ts +4996 -0
  181. package/types/ScreenCaptureKit.d.ts +16 -0
  182. package/types/ScreenSaver.d.ts +41 -0
  183. package/types/ScreenTime.d.ts +41 -0
  184. package/types/ScriptingBridge.d.ts +96 -0
  185. package/types/Security.d.ts +14336 -0
  186. package/types/SecurityFoundation.d.ts +53 -0
  187. package/types/SecurityInterface.d.ts +297 -0
  188. package/types/SensorKit.d.ts +7 -0
  189. package/types/ServiceManagement.d.ts +71 -0
  190. package/types/SharedWithYou.d.ts +9 -0
  191. package/types/SharedWithYouCore.d.ts +5 -0
  192. package/types/ShazamKit.d.ts +12 -0
  193. package/types/Social.d.ts +306 -0
  194. package/types/SoundAnalysis.d.ts +177 -0
  195. package/types/Speech.d.ts +246 -0
  196. package/types/SpriteKit.d.ts +2766 -0
  197. package/types/StoreKit.d.ts +509 -0
  198. package/types/SyncServices.d.ts +551 -0
  199. package/types/SystemConfiguration.d.ts +1415 -0
  200. package/types/SystemExtensions.d.ts +89 -0
  201. package/types/TWAIN.d.ts +380 -0
  202. package/types/Tcl.d.ts +4013 -0
  203. package/types/ThreadNetwork.d.ts +77 -0
  204. package/types/Tk.d.ts +3750 -0
  205. package/types/UIUtilities.d.ts +31 -0
  206. package/types/UniformTypeIdentifiers.d.ts +358 -0
  207. package/types/UserNotifications.d.ts +538 -0
  208. package/types/UserNotificationsUI.d.ts +38 -0
  209. package/types/VideoDecodeAcceleration.d.ts +50 -0
  210. package/types/VideoSubscriberAccount.d.ts +139 -0
  211. package/types/VideoToolbox.d.ts +907 -0
  212. package/types/Virtualization.d.ts +302 -0
  213. package/types/Vision.d.ts +1723 -0
  214. package/types/WebKit.d.ts +5195 -0
  215. package/types/WidgetKit.d.ts +9 -0
  216. package/types/_LocationEssentials.d.ts +159 -0
  217. package/types/_ScreenCaptureKit_SwiftUI.d.ts +5 -0
  218. package/types/iTunesLibrary.d.ts +528 -0
  219. package/types/index.d.ts +9 -0
  220. package/types/vecLib.d.ts +33586 -0
  221. package/types/vmnet.d.ts +178 -0
  222. package/src/appkit.d.ts +0 -472
package/README.md CHANGED
@@ -105,6 +105,47 @@ that view and call `unmount()` before disposing the host. Root creation does
105
105
  not create a window or start the application run loop. The package also
106
106
  re-exports Octane's universal native runtime for compiler and hook imports.
107
107
 
108
+ ## Preserve context through portals
109
+
110
+ `createPortal` can place content into an AppKit `NSView` while keeping it in
111
+ the declaring Octane tree. The host application still owns the target view;
112
+ the renderer inserts and removes only the portal's child views. Use a
113
+ caller-owned view or a view from the same Octane root; targets owned by another
114
+ Octane root are rejected.
115
+
116
+ ```tsx
117
+ /** @jsxImportSource @octane-xplat/macos-renderer */
118
+ import { createContext, createPortal, useContext, useEffect, useRef, useState } from 'octane'
119
+
120
+ const AccountName = createContext('Guest')
121
+ const AccountNameProvider: any = AccountName
122
+
123
+ function AccountLabel() {
124
+ return <label text={'Account: ' + useContext(AccountName)} />
125
+ }
126
+
127
+ export function AccountPage() {
128
+ const targetRef = useRef<any>(null)
129
+ const [target, setTarget] = useState<any>(null)
130
+
131
+ useEffect(() => {
132
+ setTarget(targetRef.current)
133
+ }, [])
134
+
135
+ return (
136
+ <AccountNameProvider value="Avery">
137
+ <rootlayout ref={targetRef} style={{ width: 280, height: 120 }} />
138
+ {target ? createPortal(<AccountLabel />, target) : null}
139
+ </AccountNameProvider>
140
+ )
141
+ }
142
+ ```
143
+
144
+ The label renders `Account: Avery` inside the `rootlayout` and reads the same
145
+ provider value as its declaring tree. Run the maintained portal fixture with
146
+ `pnpm --filter @octane-xplat/macos-renderer test:portals` to check context,
147
+ provider updates, and native child cleanup.
148
+
108
149
  Use [the packaging guide](../../docs/start/toolchain.md#experimental-appkit-target)
109
150
  for `xplat.targets.macos` metadata, signing, and CLI commands. The
110
151
  [independent fixture](test/fixtures/main.ts) demonstrates a complete host
@@ -249,14 +290,20 @@ vertically centered content. The renderer recognizes these inputs on
249
290
  ```tsx
250
291
  /** @jsxImportSource @octane-xplat/macos-renderer */
251
292
  export function Rows() {
252
- return <stack>
253
- <flexboxlayout flexDirection="row" gap={12} alignItems="center" justifyContent="start">
254
- <label text="Name" /><label text="Value" />
255
- </flexboxlayout>
256
- <flexboxlayout style={{ flexDirection: 'row', gap: 12, alignItems: 'center', justifyContent: 'start' }}>
257
- <label text="Name" /><label text="Value" />
258
- </flexboxlayout>
259
- </stack>
293
+ return (
294
+ <stack>
295
+ <flexboxlayout flexDirection="row" gap={12} alignItems="center" justifyContent="start">
296
+ <label text="Name" />
297
+ <label text="Value" />
298
+ </flexboxlayout>
299
+ <flexboxlayout
300
+ style={{ flexDirection: 'row', gap: 12, alignItems: 'center', justifyContent: 'start' }}
301
+ >
302
+ <label text="Name" />
303
+ <label text="Value" />
304
+ </flexboxlayout>
305
+ </stack>
306
+ )
260
307
  }
261
308
  ```
262
309
 
@@ -269,10 +316,16 @@ restores `gap-2` (8 points); removing the class then restores `gap={4}`.
269
316
  ```tsx
270
317
  /** @jsxImportSource @octane-xplat/macos-renderer */
271
318
  export function ConflictingRow({ inlineGap }: { inlineGap?: number }) {
272
- return <flexboxlayout gap={4} className="flex-row gap-2 items-center justify-end"
273
- style={{ gap: inlineGap }}>
274
- <label text="Name" /><label text="Value" />
275
- </flexboxlayout>
319
+ return (
320
+ <flexboxlayout
321
+ gap={4}
322
+ className="flex-row gap-2 items-center justify-end"
323
+ style={{ gap: inlineGap }}
324
+ >
325
+ <label text="Name" />
326
+ <label text="Value" />
327
+ </flexboxlayout>
328
+ )
276
329
  }
277
330
  ```
278
331
 
@@ -287,9 +340,12 @@ The class forms are `flex-row`/`flex-col`, `gap-1/2/3/4/6`,
287
340
  ```tsx
288
341
  /** @jsxImportSource @octane-xplat/macos-renderer */
289
342
  export function AxisGap() {
290
- return <flexboxlayout style={{ flexDirection: 'row', gap: 4, columnGap: 12 }}>
291
- <label text="Name" /><label text="Value" />
292
- </flexboxlayout>
343
+ return (
344
+ <flexboxlayout style={{ flexDirection: 'row', gap: 4, columnGap: 12 }}>
345
+ <label text="Name" />
346
+ <label text="Value" />
347
+ </flexboxlayout>
348
+ )
293
349
  }
294
350
  ```
295
351
 
@@ -303,11 +359,18 @@ placement into styles. Keep those values on the child host.
303
359
  ```tsx
304
360
  /** @jsxImportSource @octane-xplat/macos-renderer */
305
361
  export function PositionedRow() {
306
- return <absolutelayout>
307
- <flexboxlayout left={20} top={12} style={{ width: 240, height: 40, flexDirection: 'row', gap: 12 }}>
308
- <label text="Name" /><label text="Value" />
309
- </flexboxlayout>
310
- </absolutelayout>
362
+ return (
363
+ <absolutelayout>
364
+ <flexboxlayout
365
+ left={20}
366
+ top={12}
367
+ style={{ width: 240, height: 40, flexDirection: 'row', gap: 12 }}
368
+ >
369
+ <label text="Name" />
370
+ <label text="Value" />
371
+ </flexboxlayout>
372
+ </absolutelayout>
373
+ )
311
374
  }
312
375
  ```
313
376
 
@@ -361,6 +424,7 @@ From this repository:
361
424
  ```sh
362
425
  pnpm --filter @octane-xplat/macos-renderer test
363
426
  pnpm --filter @octane-xplat/macos-renderer test:overflow
427
+ pnpm --filter @octane-xplat/macos-renderer test:portals
364
428
  pnpm --filter @octane-xplat/macos-renderer test:packed
365
429
  pnpm --filter @xplat/macos test:fonts
366
430
  pnpm --filter @xplat/macos test:layout-channels
@@ -375,3 +439,14 @@ the checkout. It checks declarations and production compilation on every
375
439
  host; on Apple Silicon macOS it also packages and launches the app and verifies
376
440
  retained-root HMR. Programmatic action dispatch establishes handler behavior,
377
441
  not OS input or hit-testing.
442
+
443
+ ## Press and swipe callbacks
444
+
445
+ See [macOS gesture callbacks](../ui/changes/macos-gestures.md) for shared
446
+ Pressable and View examples, swipe directions, and verification limits.
447
+ The maintained nonvisual fixture checks Objective-C gesture target dispatch;
448
+ physical OS input remains a separate manual check.
449
+
450
+ ```sh
451
+ goddard-agent resource run --json '{"resources":{"native_builds":1},"purpose":"AppKit gesture verification"}' -- pnpm --filter @octane-xplat/macos-renderer test:gestures
452
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@octane-xplat/macos-renderer",
3
- "version": "0.13.0",
3
+ "version": "0.15.0",
4
4
  "description": "Experimental AppKit renderer for Octane on macOS",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -10,6 +10,7 @@
10
10
  },
11
11
  "files": [
12
12
  "src",
13
+ "types",
13
14
  "LICENSE"
14
15
  ],
15
16
  "type": "module",
@@ -26,6 +27,12 @@
26
27
  "./jsx-dev-runtime": {
27
28
  "types": "./src/jsx-runtime.d.ts"
28
29
  },
30
+ "./types": {
31
+ "types": "./types/index.d.ts"
32
+ },
33
+ "./types/*": {
34
+ "types": "./types/*.d.ts"
35
+ },
29
36
  "./compat/native-core": "./src/native-core-shim.ts",
30
37
  "./compat/escape-props": "./src/native-escape-props.ts"
31
38
  },
@@ -33,13 +40,19 @@
33
40
  "access": "public"
34
41
  },
35
42
  "dependencies": {
36
- "@nativescript/macos-node-api": "0.4.4-next.2026-08-09-31292056208"
43
+ "@nativescript/macos-node-api": "0.4.4-next.2026-08-09-31292056208",
44
+ "@nativescript/objc-node-api": "1.0.0-alpha.7",
45
+ "@octane-xplat/platform": "0.15.0",
46
+ "acorn": "8.15.0",
47
+ "saxes": "6.0.0"
37
48
  },
38
49
  "peerDependencies": {
39
50
  "octane": ">=0.6.3 <1"
40
51
  },
41
52
  "scripts": {
42
53
  "test": "node --experimental-strip-types --test test/*.test.ts",
54
+ "test:gestures": "node ../../scripts/probe.mjs run test/fixtures/gestures.macos.tsrx --target macos",
55
+ "test:portals": "node ../../scripts/probe.mjs run test/fixtures/portal-lifecycle.macos.tsrx --target macos",
43
56
  "test:overflow": "node ../../scripts/probe.mjs run test/fixtures/text-overflow.macos.tsrx --target macos",
44
57
  "test:packed": "node --experimental-strip-types test/packed-consumer.ts",
45
58
  "typecheck": "tsc --noEmit -p tsconfig.json"
package/src/appearance.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  /** App-wide effective appearance. AppKit resolves both system mode and an
2
2
  * explicit NSApplication.appearance override; colors remain app-owned. */
3
+
4
+ import { NSApplication, NSObject } from '@octane-xplat/platform/macos'
3
5
  export function installAppearanceBridge(bridge: Record<string, any>): void {
4
6
  // Preserve an installed producer across module reloads and host customization.
5
7
  if (bridge.getColorScheme && bridge.onAppearanceChange) {
package/src/fonts.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { NSFont, NSFontDescriptor, NSFontManager } from '@octane-xplat/platform/macos'
2
+
1
3
  interface FontFace {
2
4
  weight: number
3
5
  descriptor: NSFontDescriptor
@@ -0,0 +1,147 @@
1
+ /** Viewless text composition. No AppKit object is allocated for a logical node. */
2
+ interface TextNode {
3
+ type: string
4
+ props: Record<string, any>
5
+ text: string
6
+ children: TextNode[]
7
+ }
8
+
9
+ const TEXT_STYLE_KEYS = [
10
+ 'fontFamily',
11
+ 'fontSize',
12
+ 'fontWeight',
13
+ 'fontStyle',
14
+ 'textDecoration',
15
+ 'color',
16
+ 'backgroundColor',
17
+ ] as const
18
+
19
+ export function validateTextStyle(props: Record<string, any>, color: (value: any) => any) {
20
+ if (props.style != null && (typeof props.style !== 'object' || Array.isArray(props.style))) {
21
+ throw new Error('AppKit formatted text expects a style object')
22
+ }
23
+
24
+ for (const source of [props, props.style ?? {}]) {
25
+ if (source.fontSize != null && (!Number.isFinite(source.fontSize) || source.fontSize <= 0)) {
26
+ throw new Error('AppKit formatted text fontSize must be positive and finite')
27
+ }
28
+
29
+ if (source.fontFamily != null && typeof source.fontFamily !== 'string') {
30
+ throw new Error('AppKit formatted text fontFamily must be a string')
31
+ }
32
+
33
+ if (source.fontStyle != null && !['normal', 'italic', 'oblique'].includes(source.fontStyle)) {
34
+ throw new Error('AppKit formatted text fontStyle supports normal, italic and oblique')
35
+ }
36
+
37
+ if (
38
+ source.textDecoration != null &&
39
+ ![
40
+ 'none',
41
+ 'underline',
42
+ 'line-through',
43
+ 'underline line-through',
44
+ 'line-through underline',
45
+ ].includes(source.textDecoration)
46
+ ) {
47
+ throw new Error('AppKit formatted text has an unsupported textDecoration')
48
+ }
49
+
50
+ for (const key of ['color', 'backgroundColor']) {
51
+ if (source[key] != null) {
52
+ color(source[key])
53
+ }
54
+ }
55
+ }
56
+ }
57
+
58
+ /** Fold inherited values without mutating authored props. Removed values fall
59
+ * through to the ancestor; style wins over the direct property. */
60
+ export function textRuns(
61
+ node: TextNode,
62
+ inherited: Record<string, any> = {},
63
+ ): Array<{ text: string; style: Record<string, any> }> {
64
+ const style = { ...inherited }
65
+ for (const key of TEXT_STYLE_KEYS) {
66
+ const value = node.props.style?.[key] ?? node.props[key]
67
+ if (value != null) {
68
+ style[key] = value
69
+ }
70
+ }
71
+
72
+ const own =
73
+ node.type === '#text'
74
+ ? node.text
75
+ : node.type === 'span'
76
+ ? String(node.props.text ?? '')
77
+ .replace('\\n', '\n')
78
+ .replace('\\t', '\t')
79
+ : ''
80
+
81
+ return [
82
+ ...(own ? [{ text: own, style }] : []),
83
+ ...node.children.flatMap((child) => textRuns(child, style)),
84
+ ]
85
+ }
86
+
87
+ export function composeAttributedText(
88
+ children: TextNode[],
89
+ view: any,
90
+ props: Record<string, any>,
91
+ font: (size: number, weight: number | string, family?: string) => any,
92
+ color: (value: any) => any,
93
+ defaults: Record<string, any>,
94
+ ) {
95
+ const native = globalThis as any
96
+ const result = native.NSMutableAttributedString.alloc().initWithString('')
97
+ const paragraph = native.NSMutableParagraphStyle.alloc().init()
98
+ paragraph.alignment = view.alignment ?? 0
99
+ paragraph.lineBreakMode = view.cell?.lineBreakMode ?? native.NSLineBreakMode.WordWrapping
100
+ const lineHeight = Number(props.style?.lineHeight)
101
+ if (Number.isFinite(lineHeight) && lineHeight > 0) {
102
+ paragraph.minimumLineHeight = lineHeight
103
+ paragraph.maximumLineHeight = lineHeight
104
+ }
105
+
106
+ for (const run of children.flatMap((child) => textRuns(child, defaults))) {
107
+ const style = run.style
108
+ let face = font(style.fontSize, style.fontWeight, style.fontFamily)
109
+ if (style.fontStyle === 'italic' || style.fontStyle === 'oblique') {
110
+ face = native.NSFontManager.sharedFontManager.convertFontToHaveTrait(
111
+ face,
112
+ native.NSFontTraitMask.Italic,
113
+ )
114
+ }
115
+
116
+ const attributes: Record<string, any> = {
117
+ [native.NSFontAttributeName]: face,
118
+ [native.NSParagraphStyleAttributeName]: paragraph,
119
+ }
120
+
121
+ if (style.color != null) {
122
+ attributes[native.NSForegroundColorAttributeName] =
123
+ typeof style.color === 'object' ? style.color : color(style.color)
124
+ }
125
+
126
+ if (style.backgroundColor != null) {
127
+ attributes[native.NSBackgroundColorAttributeName] =
128
+ typeof style.backgroundColor === 'object'
129
+ ? style.backgroundColor
130
+ : color(style.backgroundColor)
131
+ }
132
+
133
+ if (String(style.textDecoration).includes('underline')) {
134
+ attributes[native.NSUnderlineStyleAttributeName] = 1
135
+ }
136
+
137
+ if (String(style.textDecoration).includes('line-through')) {
138
+ attributes[native.NSStrikethroughStyleAttributeName] = 1
139
+ }
140
+
141
+ result.appendAttributedString(
142
+ native.NSAttributedString.alloc().initWithStringAttributes(run.text, attributes),
143
+ )
144
+ }
145
+
146
+ return result
147
+ }
@@ -0,0 +1,196 @@
1
+ /** Gesture ownership stays on the renderer's original NSView, including when
2
+ * layout inserts a margin wrapper. No window-wide input monitor is needed. */
3
+
4
+ import {
5
+ NSClickGestureRecognizer,
6
+ NSEvent,
7
+ NSGestureRecognizer,
8
+ NSGestureRecognizerDelegate,
9
+ NSObject,
10
+ NSPressGestureRecognizer,
11
+ NSView,
12
+ NativeClass,
13
+ interop,
14
+ } from '@octane-xplat/platform/macos'
15
+
16
+ interface GestureNode {
17
+ view: NSView | null
18
+ props: Record<string, any>
19
+ container: { root: { eventScope(kind: 'discrete', fn: () => void): void } | null }
20
+ [key: string]: any
21
+ }
22
+
23
+ const nodes = new WeakMap<object, GestureNode>()
24
+ const owned = new WeakSet<object>()
25
+
26
+ function dispatch(node: GestureNode | undefined, name: string, event?: any) {
27
+ if (!node || node.props.disabled || typeof node.props[name] !== 'function') {
28
+ return
29
+ }
30
+
31
+ try {
32
+ node.container.root!.eventScope('discrete', () => node.props[name](event))
33
+ } catch (error) {
34
+ console.error('[macos-event] ' + name + ' handler failed', error)
35
+ }
36
+ }
37
+
38
+ /** The JSC host's native property enumeration sees enumerable keys only.
39
+ * Make these renderer-owned methods visible before NativeScript registers them. */
40
+ export function registerGestureClass(ctor: any) {
41
+ for (const name of Object.getOwnPropertyNames(ctor.prototype)) {
42
+ if (name === 'constructor') {
43
+ continue
44
+ }
45
+
46
+ const descriptor = Object.getOwnPropertyDescriptor(ctor.prototype, name)!
47
+ Object.defineProperty(ctor.prototype, name, { ...descriptor, enumerable: true })
48
+ }
49
+
50
+ NativeClass(ctor)
51
+ }
52
+
53
+ class GestureDelegate extends NSObject {
54
+ static ObjCProtocols = [NSGestureRecognizerDelegate]
55
+ static {
56
+ registerGestureClass(this)
57
+ }
58
+ gestureRecognizerShouldRecognizeSimultaneouslyWithGestureRecognizer(
59
+ a: NSGestureRecognizer,
60
+ b: NSGestureRecognizer,
61
+ ) {
62
+ // Do not change arbitration with child controls or hosted engines.
63
+ return a.view === b.view && owned.has(a) && owned.has(b)
64
+ }
65
+ }
66
+
67
+ const delegate = GestureDelegate.new()
68
+
69
+ export function configureGesture(recognizer: NSGestureRecognizer) {
70
+ owned.add(recognizer)
71
+ recognizer.delegate = delegate
72
+ recognizer.delaysPrimaryMouseButtonEvents = false
73
+ return recognizer
74
+ }
75
+
76
+ /** AppKit has no down/up recognizer. This passive mouse observer never claims
77
+ * the sequence from click/pan, and balances down with up or reset/cancel. */
78
+ class TouchGestureRecognizer extends NSGestureRecognizer {
79
+ active = false
80
+ static {
81
+ registerGestureClass(this)
82
+ }
83
+ mouseDown(_event: NSEvent) {
84
+ this.active = true
85
+ dispatch(nodes.get(this.view), 'onTouch', { action: 'down', view: this.view })
86
+ }
87
+ mouseDragged(_event: NSEvent) {
88
+ if (!this.active) {
89
+ return
90
+ }
91
+
92
+ dispatch(nodes.get(this.view), 'onTouch', { action: 'move', view: this.view })
93
+ }
94
+ mouseUp(_event: NSEvent) {
95
+ if (!this.active) {
96
+ return
97
+ }
98
+
99
+ this.active = false
100
+ dispatch(nodes.get(this.view), 'onTouch', { action: 'up', view: this.view })
101
+ }
102
+ reset() {
103
+ if (this.active) {
104
+ this.active = false
105
+ dispatch(nodes.get(this.view), 'onTouch', { action: 'cancel', view: this.view })
106
+ }
107
+
108
+ super.reset()
109
+ }
110
+ }
111
+
112
+ class GestureTarget extends NSObject {
113
+ static ObjCExposedMethods = {
114
+ 'longPressed:': { params: [NSObject], returns: interop.types.void },
115
+ 'doublePressed:': { params: [NSObject], returns: interop.types.void },
116
+ }
117
+ static {
118
+ registerGestureClass(this)
119
+ }
120
+ ['longPressed:'](sender: NSGestureRecognizer) {
121
+ this.longPressed(sender)
122
+ }
123
+ ['doublePressed:'](sender: NSGestureRecognizer) {
124
+ this.doublePressed(sender)
125
+ }
126
+ longPressed(sender: NSGestureRecognizer) {
127
+ if (Number(sender.state) === 3) {
128
+ dispatch(nodes.get(sender.view), 'onLongPress')
129
+ }
130
+ }
131
+ doublePressed(sender: NSGestureRecognizer) {
132
+ if (Number(sender.state) === 3) {
133
+ dispatch(nodes.get(sender.view), 'onDoubleTap')
134
+ }
135
+ }
136
+ }
137
+
138
+ const target = GestureTarget.new()
139
+
140
+ export function setPressGesture(node: GestureNode, name: string) {
141
+ nodes.set(node.view!, node)
142
+ const key = name + 'GestureRecognizer'
143
+ let recognizer = node[key] as NSGestureRecognizer | undefined
144
+ if (!recognizer && typeof node.props[name] === 'function') {
145
+ if (name === 'onTouch') {
146
+ recognizer = TouchGestureRecognizer.new() as NSGestureRecognizer
147
+ } else if (name === 'onLongPress') {
148
+ const press = NSPressGestureRecognizer.alloc().initWithTargetAction(target, 'longPressed:')
149
+ press.minimumPressDuration = 0.5
150
+ recognizer = press
151
+ } else {
152
+ const click = NSClickGestureRecognizer.alloc().initWithTargetAction(target, 'doublePressed:')
153
+ click.numberOfClicksRequired = 2
154
+ recognizer = click
155
+ }
156
+
157
+ node[key] = configureGesture(recognizer)
158
+ node.view!.addGestureRecognizer(recognizer)
159
+ }
160
+
161
+ if (recognizer) {
162
+ recognizer.enabled = !node.props.disabled && typeof node.props[name] === 'function'
163
+ }
164
+ }
165
+
166
+ export function disposePressGestures(node: GestureNode) {
167
+ if (!node.view) {
168
+ return
169
+ }
170
+
171
+ // Forget callbacks before AppKit resets a recognizer during teardown.
172
+ nodes.delete(node.view)
173
+ for (const name of ['onTouch', 'onLongPress', 'onDoubleTap']) {
174
+ const recognizer = node[name + 'GestureRecognizer']
175
+ if (recognizer) {
176
+ recognizer.enabled = false
177
+ node.view.removeGestureRecognizer(recognizer)
178
+ }
179
+ }
180
+ }
181
+
182
+ /** NativeScript SwipeDirection values. Only completed drags of at least 30
183
+ * layout points count; cancelled/failed pans never produce a swipe. */
184
+ export function swipeDirection(event: { state: number; deltaX: number; deltaY: number }) {
185
+ if (event.state !== 3 || Math.max(Math.abs(event.deltaX), Math.abs(event.deltaY)) < 30) {
186
+ return null
187
+ }
188
+
189
+ return Math.abs(event.deltaX) >= Math.abs(event.deltaY)
190
+ ? event.deltaX > 0
191
+ ? 1
192
+ : 2
193
+ : event.deltaY < 0
194
+ ? 4
195
+ : 8
196
+ }
@@ -0,0 +1,55 @@
1
+ // Ambient host globals injected by @nativescript/macos-node-api and the
2
+ // embedding Node process — runtime loader, the renderer's __xplatAppKit bridge
3
+ // channel, and the Node-ish globals the macOS host provides.
4
+ // Runtime framework loader injected by the bridge host.
5
+ // `declare global` member types resolve ambiently — module imports don't bind
6
+ // inside the global block — so AppKit names use inline import() types.
7
+ declare global {
8
+ function dlopen(path: string, flags: number): unknown
9
+
10
+ // Cross-package bridge published by this renderer for AppKit-specific leaves
11
+ // (hover, popups, menus, sheets) and the VirtualList offset channel.
12
+ var __xplatAppKit: {
13
+ observeHover?: (view: import('@octane-xplat/platform/macos').NSView, handlers: any) => void
14
+ showAnchoredPopup?: (options: any) => void
15
+ showLayer?: (options: any) => any
16
+ attachContextMenu?: (
17
+ view: import('@octane-xplat/platform/macos').NSView,
18
+ options: any,
19
+ onSelect: any,
20
+ ) => void
21
+ attachDatePicker?: (
22
+ view: import('@octane-xplat/platform/macos').NSView,
23
+ options: any,
24
+ onChange: any,
25
+ ) => void
26
+ presentSheet?: (view: import('@octane-xplat/platform/macos').NSView, options: any) => any
27
+ presentSurface?: (options: any) => any
28
+ setSurfaceAppearance?: (
29
+ view: import('@octane-xplat/platform/macos').NSView,
30
+ dark: boolean,
31
+ ) => void
32
+ [key: string]: any
33
+ }
34
+
35
+ var __xplatVlistOffsets: { [key: string]: number } | undefined
36
+
37
+ // Node host globals.
38
+ const performance: { now(): number }
39
+ function setTimeout(callback: () => void, ms?: number): unknown
40
+ function clearTimeout(timer: unknown): void
41
+ function queueMicrotask(callback: () => void): void
42
+ const console: {
43
+ log(...args: any[]): void
44
+ warn(...args: any[]): void
45
+ error(...args: any[]): void
46
+ [key: string]: any
47
+ }
48
+
49
+ // The macOS host embeds Node — process.env reads and cwd() are real.
50
+ // Other programs may declare a narrower process ambient; duplicate ambient
51
+ // consts merge loosely under skipLibCheck, so keep both members here.
52
+ const process: { env: { [key: string]: string | undefined }; cwd(): string }
53
+ }
54
+
55
+ export {}