@octane-xplat/macos-renderer 0.14.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.
- package/README.md +95 -20
- package/package.json +15 -2
- package/src/appearance.ts +2 -0
- package/src/fonts.ts +2 -0
- package/src/formatted-text.ts +147 -0
- package/src/gestures.ts +196 -0
- package/src/host-globals.d.ts +55 -0
- package/src/html-text.ts +88 -0
- package/src/image.ts +91 -16
- package/src/index.d.ts +9 -0
- package/src/index.ts +5030 -678
- package/src/layer.ts +11 -9
- package/src/list-template.ts +266 -0
- package/src/list-view.ts +445 -0
- package/src/portal-components.macos.tsx +86 -0
- package/src/presentation.ts +48 -15
- package/src/scroll-view.ts +124 -0
- package/src/tab-view.ts +512 -0
- package/src/template-props.ts +402 -0
- package/src/vendor/listview/LICENSE +19 -0
- package/src/vendor/listview/README.md +1 -0
- package/src/vendor/listview/binding-options.ts +189 -0
- package/src/vendor/listview/expressions.ts +285 -0
- package/src/webview.ts +36 -7
- package/types/ARKitCore.d.ts +199 -0
- package/types/AVFAudio.d.ts +2304 -0
- package/types/AVFoundation.d.ts +6974 -0
- package/types/AVKit.d.ts +215 -0
- package/types/AVRouting.d.ts +7 -0
- package/types/Accelerate.d.ts +4776 -0
- package/types/Accessibility.d.ts +69 -0
- package/types/Accounts.d.ts +134 -0
- package/types/AdSupport.d.ts +12 -0
- package/types/AddressBook.d.ts +1012 -0
- package/types/AppKit.d.ts +34247 -0
- package/types/AppTrackingTransparency.d.ts +23 -0
- package/types/AppleScriptKit.d.ts +6 -0
- package/types/ApplicationServices.d.ts +6346 -0
- package/types/AudioToolbox.d.ts +7129 -0
- package/types/AudioVideoBridging.d.ts +613 -0
- package/types/AuthenticationServices.d.ts +982 -0
- package/types/AutomaticAssessmentConfiguration.d.ts +46 -0
- package/types/Automator.d.ts +248 -0
- package/types/BackgroundAssets.d.ts +9 -0
- package/types/BrowserEngineKit.d.ts +166 -0
- package/types/BusinessChat.d.ts +29 -0
- package/types/CFNetwork.d.ts +931 -0
- package/types/CalendarStore.d.ts +354 -0
- package/types/CallKit.d.ts +30 -0
- package/types/Carbon.d.ts +16207 -0
- package/types/ClassKit.d.ts +281 -0
- package/types/CloudKit.d.ts +1565 -0
- package/types/Collaboration.d.ts +123 -0
- package/types/ColorSync.d.ts +562 -0
- package/types/CompositorServices.d.ts +60 -0
- package/types/ComputeGraph.d.ts +16 -0
- package/types/Contacts.d.ts +1558 -0
- package/types/ContactsUI.d.ts +39 -0
- package/types/CoreAudio.d.ts +1376 -0
- package/types/CoreAudioKit.d.ts +122 -0
- package/types/CoreAudioTypes.d.ts +1003 -0
- package/types/CoreBluetooth.d.ts +657 -0
- package/types/CoreData.d.ts +2479 -0
- package/types/CoreFoundation.d.ts +5546 -0
- package/types/CoreGraphics.d.ts +3676 -0
- package/types/CoreHaptics.d.ts +361 -0
- package/types/CoreImage.d.ts +4781 -0
- package/types/CoreLocation.d.ts +564 -0
- package/types/CoreMIDI.d.ts +1538 -0
- package/types/CoreML.d.ts +905 -0
- package/types/CoreMedia.d.ts +2615 -0
- package/types/CoreMediaIO.d.ts +1046 -0
- package/types/CoreMotion.d.ts +316 -0
- package/types/CoreServices.d.ts +24810 -0
- package/types/CoreSpotlight.d.ts +789 -0
- package/types/CoreTelephony.d.ts +31 -0
- package/types/CoreText.d.ts +3973 -0
- package/types/CoreTransferable.d.ts +3 -0
- package/types/CoreVideo.d.ts +1162 -0
- package/types/CoreWLAN.d.ts +556 -0
- package/types/CryptoTokenKit.d.ts +548 -0
- package/types/DVDPlayback.d.ts +931 -0
- package/types/DataDetection.d.ts +66 -0
- package/types/DeviceCheck.d.ts +46 -0
- package/types/DeviceDiscoveryExtension.d.ts +142 -0
- package/types/DirectoryService.d.ts +966 -0
- package/types/DiscRecording.d.ts +2345 -0
- package/types/DiscRecordingUI.d.ts +288 -0
- package/types/DiskArbitration.d.ts +366 -0
- package/types/DriverKit.d.ts +639 -0
- package/types/EventKit.d.ts +640 -0
- package/types/ExceptionHandling.d.ts +56 -0
- package/types/ExecutionPolicy.d.ts +30 -0
- package/types/ExternalAccessory.d.ts +98 -0
- package/types/FSKit.d.ts +237 -0
- package/types/FileProvider.d.ts +495 -0
- package/types/FileProviderUI.d.ts +31 -0
- package/types/FinderSync.d.ts +165 -0
- package/types/FocusEngine.d.ts +133 -0
- package/types/ForceFeedback.d.ts +399 -0
- package/types/Foundation.d.ts +18216 -0
- package/types/GLKit.d.ts +937 -0
- package/types/GLUT.d.ts +1018 -0
- package/types/GSS.d.ts +855 -0
- package/types/GameController.d.ts +1394 -0
- package/types/GameKit.d.ts +1958 -0
- package/types/GameSave.d.ts +54 -0
- package/types/GameplayKit.d.ts +1645 -0
- package/types/HealthKit.d.ts +278 -0
- package/types/Hypervisor.d.ts +2079 -0
- package/types/ICADevices.d.ts +1400 -0
- package/types/IOBluetooth.d.ts +6094 -0
- package/types/IOBluetoothUI.d.ts +250 -0
- package/types/IOKit.d.ts +25804 -0
- package/types/IOSurface.d.ts +435 -0
- package/types/IOUSBHost.d.ts +809 -0
- package/types/ImageCaptureCore.d.ts +1104 -0
- package/types/ImageIO.d.ts +1697 -0
- package/types/InputMethodKit.d.ts +264 -0
- package/types/InstallerPlugins.d.ts +102 -0
- package/types/InstantMessage.d.ts +229 -0
- package/types/Intents.d.ts +1833 -0
- package/types/IntentsUI.d.ts +5 -0
- package/types/JavaRuntimeSupport.d.ts +520 -0
- package/types/JavaScriptCore.d.ts +934 -0
- package/types/Kerberos.d.ts +3754 -0
- package/types/Kernel.d.ts +33698 -0
- package/types/KernelManagement.d.ts +5 -0
- package/types/LatentSemanticMapping.d.ts +152 -0
- package/types/LinkPresentation.d.ts +57 -0
- package/types/LocalAuthentication.d.ts +94 -0
- package/types/MLCompute.d.ts +2343 -0
- package/types/MapKit.d.ts +2125 -0
- package/types/Matter.d.ts +151 -0
- package/types/MediaAccessibility.d.ts +137 -0
- package/types/MediaLibrary.d.ts +394 -0
- package/types/MediaPlayer.d.ts +475 -0
- package/types/MediaToolbox.d.ts +70 -0
- package/types/Metal.d.ts +4542 -0
- package/types/MetalFX.d.ts +107 -0
- package/types/MetalKit.d.ts +524 -0
- package/types/MetalPerformanceShaders.d.ts +9306 -0
- package/types/MetalPerformanceShadersGraph.d.ts +1394 -0
- package/types/MetricKit.d.ts +231 -0
- package/types/ModelIO.d.ts +2908 -0
- package/types/MultipeerConnectivity.d.ts +348 -0
- package/types/NaturalLanguage.d.ts +556 -0
- package/types/NearbyInteraction.d.ts +51 -0
- package/types/NetFS.d.ts +99 -0
- package/types/Network.d.ts +1622 -0
- package/types/NetworkExtension.d.ts +1574 -0
- package/types/NotificationCenter.d.ts +103 -0
- package/types/OSAKit.d.ts +260 -0
- package/types/OSLog.d.ts +208 -0
- package/types/OpenAL.d.ts +314 -0
- package/types/OpenCL.d.ts +2489 -0
- package/types/OpenDirectory.d.ts +1919 -0
- package/types/OpenGL.d.ts +11322 -0
- package/types/PCSC.d.ts +534 -0
- package/types/PDFKit.d.ts +1575 -0
- package/types/PHASE.d.ts +166 -0
- package/types/ParavirtualizedGraphics.d.ts +153 -0
- package/types/PassKit.d.ts +1287 -0
- package/types/PencilKit.d.ts +201 -0
- package/types/Photos.d.ts +1125 -0
- package/types/PhotosUI.d.ts +397 -0
- package/types/PreferencePanes.d.ts +68 -0
- package/types/ProximityReaderStub.d.ts +5 -0
- package/types/PushKit.d.ts +45 -0
- package/types/Quartz.d.ts +1642 -0
- package/types/QuartzCore.d.ts +1398 -0
- package/types/QuickLook.d.ts +212 -0
- package/types/QuickLookThumbnailing.d.ts +131 -0
- package/types/QuickLookUI.d.ts +117 -0
- package/types/REVISION.json +5 -0
- package/types/ReplayKit.d.ts +294 -0
- package/types/Runtime.d.ts +51605 -0
- package/types/SafariServices.d.ts +430 -0
- package/types/SafetyKit.d.ts +7 -0
- package/types/SceneKit.d.ts +4996 -0
- package/types/ScreenCaptureKit.d.ts +16 -0
- package/types/ScreenSaver.d.ts +41 -0
- package/types/ScreenTime.d.ts +41 -0
- package/types/ScriptingBridge.d.ts +96 -0
- package/types/Security.d.ts +14336 -0
- package/types/SecurityFoundation.d.ts +53 -0
- package/types/SecurityInterface.d.ts +297 -0
- package/types/SensorKit.d.ts +7 -0
- package/types/ServiceManagement.d.ts +71 -0
- package/types/SharedWithYou.d.ts +9 -0
- package/types/SharedWithYouCore.d.ts +5 -0
- package/types/ShazamKit.d.ts +12 -0
- package/types/Social.d.ts +306 -0
- package/types/SoundAnalysis.d.ts +177 -0
- package/types/Speech.d.ts +246 -0
- package/types/SpriteKit.d.ts +2766 -0
- package/types/StoreKit.d.ts +509 -0
- package/types/SyncServices.d.ts +551 -0
- package/types/SystemConfiguration.d.ts +1415 -0
- package/types/SystemExtensions.d.ts +89 -0
- package/types/TWAIN.d.ts +380 -0
- package/types/Tcl.d.ts +4013 -0
- package/types/ThreadNetwork.d.ts +77 -0
- package/types/Tk.d.ts +3750 -0
- package/types/UIUtilities.d.ts +31 -0
- package/types/UniformTypeIdentifiers.d.ts +358 -0
- package/types/UserNotifications.d.ts +538 -0
- package/types/UserNotificationsUI.d.ts +38 -0
- package/types/VideoDecodeAcceleration.d.ts +50 -0
- package/types/VideoSubscriberAccount.d.ts +139 -0
- package/types/VideoToolbox.d.ts +907 -0
- package/types/Virtualization.d.ts +302 -0
- package/types/Vision.d.ts +1723 -0
- package/types/WebKit.d.ts +5195 -0
- package/types/WidgetKit.d.ts +9 -0
- package/types/_LocationEssentials.d.ts +159 -0
- package/types/_ScreenCaptureKit_SwiftUI.d.ts +5 -0
- package/types/iTunesLibrary.d.ts +528 -0
- package/types/index.d.ts +9 -0
- package/types/vecLib.d.ts +33586 -0
- package/types/vmnet.d.ts +178 -0
- 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
|
|
253
|
-
<
|
|
254
|
-
<
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
|
291
|
-
<
|
|
292
|
-
|
|
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
|
|
307
|
-
<
|
|
308
|
-
<
|
|
309
|
-
|
|
310
|
-
|
|
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.
|
|
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
|
@@ -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
|
+
}
|
package/src/gestures.ts
ADDED
|
@@ -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 {}
|