@octane-xplat/canvas 0.0.0-nightly.g2e22171

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Alec Larson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,100 @@
1
+ # `@octane-xplat/canvas`
2
+
3
+ ```sh
4
+ pnpm add @octane-xplat/canvas
5
+ ```
6
+
7
+ A GPU/canvas surface for Octane xplat apps: the DOM `<canvas>` on web and
8
+ `@nativescript/canvas` on iOS/Android — 2D, WebGL/WebGL2, and
9
+ WebGPU/WGSL context kinds behind one `getContext` contract.
10
+
11
+ ```tsx
12
+ import { Canvas } from '@octane-xplat/canvas'
13
+
14
+ export function Drawing() {
15
+ return (
16
+ <Canvas
17
+ context="2d"
18
+ width={640}
19
+ height={480}
20
+ onReady={({ context }) => {
21
+ if (!context) return
22
+ context.fillStyle = '#2563eb'
23
+ context.fillRect(0, 0, 80, 80)
24
+ }}
25
+ />
26
+ )
27
+ }
28
+ ```
29
+
30
+ Pass `context` to have the leaf resolve it before `onReady`, or omit it and
31
+ call `getContext` yourself — one kind per canvas, decided on first call.
32
+ `onReady` fires after mount on web and on the plugin's `ready` event on
33
+ iOS/Android (earlier `getContext` calls can return null). For WebGPU,
34
+ `getGPU()` normalizes adapter/device acquisition across targets.
35
+
36
+ ```tsx
37
+ import { Canvas, getGPU } from '@octane-xplat/canvas'
38
+
39
+ export function DeferredCanvas() {
40
+ return (
41
+ <Canvas
42
+ onReady={({ canvas }) => {
43
+ const context = canvas.getContext('2d')
44
+ if (context) context.fillRect(0, 0, 20, 20)
45
+ }}
46
+ />
47
+ )
48
+ }
49
+ const gpu = getGPU()
50
+ const adapter = await gpu?.requestAdapter()
51
+ const device = await adapter?.requestDevice()
52
+ ```
53
+
54
+ ## Keep a fallback when drawing is unavailable
55
+
56
+ Native macOS/AppKit imports and mounts safely, but drawing is unsupported.
57
+ `onReady` reports `context: null` after a neutral frame mounts; it does not
58
+ announce a drawing surface. Its unavailable `canvas` handle returns `null`
59
+ from `getContext()` for every kind, including `2d`, `webgl`, `webgl2`,
60
+ `webgpu`, and `bitmaprenderer`. `getGPU()` also returns `null`, so no adapter
61
+ or device can be acquired. Check these results before drawing and keep your
62
+ own static fallback. For example, put this component in your app:
63
+
64
+ ```tsx
65
+ import { Canvas, getGPU } from '@octane-xplat/canvas'
66
+ import { Text, View } from '@octane-xplat/ui'
67
+ import { useState } from 'octane'
68
+
69
+ export function DrawingWithFallback() {
70
+ const [drawn, setDrawn] = useState(false)
71
+ return (
72
+ <View>
73
+ {!drawn && <Text>Blue square illustration</Text>}
74
+ <Canvas
75
+ context="2d"
76
+ width={80}
77
+ height={80}
78
+ onReady={({ canvas, context }) => {
79
+ const drawing = context ?? canvas.getContext('2d')
80
+ if (!drawing) return
81
+ drawing.fillStyle = '#2563eb'
82
+ drawing.fillRect(0, 0, 80, 80)
83
+ setDrawn(true)
84
+ }}
85
+ />
86
+ </View>
87
+ )
88
+ }
89
+
90
+ // AppKit returns null here, before adapter/device acquisition.
91
+ const gpu = getGPU()
92
+ ```
93
+
94
+ The AppKit backend for 2D, WebGL, and WebGPU remains deferred until a concrete
95
+ consumer needs one and the implementation is approved. Native desktop shader
96
+ effects remain unsupported. A DOM webview uses the separate web implementation.
97
+
98
+ Per-target limits are recorded in
99
+ [known limits](../../docs/verify/known-limits.md). Exercised by
100
+ [`CanvasDemo`](../demos/src/CanvasDemo.tsrx) (including a WGSL path).
package/package.json ADDED
@@ -0,0 +1,80 @@
1
+ {
2
+ "name": "@octane-xplat/canvas",
3
+ "version": "0.0.0-nightly.g2e22171",
4
+ "description": "Canvas/GPU surface for Octane xplat — web <canvas> + @nativescript/canvas (2D, WebGL/WebGL2, WebGPU/WGSL)",
5
+ "keywords": [
6
+ "canvas",
7
+ "cross-platform",
8
+ "gpu",
9
+ "graphics",
10
+ "nativescript",
11
+ "octane",
12
+ "webgl",
13
+ "webgl2",
14
+ "webgpu",
15
+ "wgsl",
16
+ "xplat"
17
+ ],
18
+ "homepage": "https://github.com/octane-xplat/octane-xplat/tree/main/packages/canvas",
19
+ "license": "MIT",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://github.com/octane-xplat/octane-xplat",
23
+ "directory": "packages/canvas"
24
+ },
25
+ "files": [
26
+ "types",
27
+ "src",
28
+ "!src/**/*.test.*",
29
+ "!src/**/*.spec.*"
30
+ ],
31
+ "type": "module",
32
+ "exports": {
33
+ ".": {
34
+ "types": "./types/index.d.ts",
35
+ "web": "./src/index.web.ts",
36
+ "macos": "./src/index.macos.ts",
37
+ "native": "./src/index.ts",
38
+ "default": "./src/index.ts"
39
+ }
40
+ },
41
+ "publishConfig": {
42
+ "access": "public"
43
+ },
44
+ "dependencies": {
45
+ "@nativescript/canvas": "3.0.0-alpha.15"
46
+ },
47
+ "devDependencies": {
48
+ "@nativescript-community/octane": "0.2.5",
49
+ "@nativescript/core": "9.1.3",
50
+ "@octane-xplat/macos-renderer": "0.0.0-nightly.g2e22171",
51
+ "@octanejs/vite-plugin": "0.1.61",
52
+ "octane": "0.11.0",
53
+ "tsrx-typegen": "0.3.0",
54
+ "typescript": "5.9.3",
55
+ "vite": "8.3.0"
56
+ },
57
+ "peerDependencies": {
58
+ "@nativescript-community/octane": ">=0.2.1 <1",
59
+ "@nativescript/core": ">=9.1.0 <10",
60
+ "@octane-xplat/macos-renderer": "0.0.0-nightly.g2e22171",
61
+ "octane": ">=0.6.3 <1"
62
+ },
63
+ "peerDependenciesMeta": {
64
+ "@nativescript-community/octane": {
65
+ "optional": true
66
+ },
67
+ "@nativescript/core": {
68
+ "optional": true
69
+ },
70
+ "@octane-xplat/macos-renderer": {
71
+ "optional": true
72
+ }
73
+ },
74
+ "scripts": {
75
+ "typegen": "tsc -p tsconfig.types.json",
76
+ "test:packed": "node tests/packed-consumer.mjs",
77
+ "test:macos-runtime": "node ../../scripts/probe.mjs run tests/unsupported.macos.tsrx --target macos --deps @octane-xplat/canvas",
78
+ "pack:check": "tsrx-typegen --pack-check"
79
+ }
80
+ }
@@ -0,0 +1,23 @@
1
+ /** @jsxImportSource @octane-xplat/macos-renderer */
2
+ import { useEffect } from 'octane';
3
+ import type { CanvasContextKind, CanvasProps } from './props';
4
+
5
+ /** AppKit mounts a neutral frame. No drawing surface or GPU is created. */
6
+ export function Canvas(props: CanvasProps) @{
7
+ useEffect(() => {
8
+ // Preserve deferred acquisition without pretending this is a native view.
9
+ const canvas = Object.freeze({
10
+ getContext: (_kind: CanvasContextKind, _options?: any): null => null,
11
+ });
12
+
13
+ props.onReady?.({ canvas, context: null });
14
+ }, []);
15
+
16
+ <gridlayout
17
+ id={props.id}
18
+ className={props.className}
19
+ style={props.style}
20
+ width={props.width}
21
+ height={props.height}
22
+ />;
23
+ }
@@ -0,0 +1,39 @@
1
+ /** @jsxImportSource @nativescript-community/octane */
2
+ import { registerElement } from '@nativescript-community/octane';
3
+ import type { Attributes } from '@nativescript-community/octane/intrinsics';
4
+ import { Canvas as NSCanvas } from '@nativescript/canvas';
5
+ import type { CanvasProps } from './props';
6
+
7
+ declare module '@nativescript-community/octane/intrinsics' {
8
+ interface NativeScriptElements {
9
+ nscanvas: Attributes<typeof NSCanvas>;
10
+ }
11
+ }
12
+
13
+ // @nativescript/canvas — Skia + wgpu core exposing the DOM Canvas API
14
+ // (2d, webgl, webgl2, webgpu) on iOS + Android. Element name is `nscanvas`
15
+ // rather than `canvas` because `canvas` is DOM vocabulary in the
16
+ // element-vocabulary lint.
17
+ registerElement('nscanvas', NSCanvas);
18
+
19
+ /** Canvas — DOM Canvas API surface on native. `onReady` wraps the plugin's
20
+ * `ready` event: it fires once the GPU surface exists, which is the
21
+ * earliest `getContext` may be called. */
22
+ export function Canvas(props: CanvasProps) @{
23
+ const ready = (event: any) => {
24
+ const canvas = event.object;
25
+ props.onReady?.({
26
+ canvas,
27
+ context: props.context ? canvas.getContext(props.context) : null,
28
+ });
29
+ };
30
+
31
+ <nscanvas
32
+ id={props.id}
33
+ className={props.className}
34
+ style={props.style}
35
+ width={props.width as any}
36
+ height={props.height as any}
37
+ onReady={ready}
38
+ />;
39
+ }
@@ -0,0 +1,39 @@
1
+ import { useMemo, useRef } from 'octane';
2
+ import type { CanvasProps } from './props';
3
+
4
+ /** Canvas — a plain <canvas>. `onReady` fires at mount with the element
5
+ * and the resolved context. */
6
+ export function Canvas(props: CanvasProps) @{
7
+ // Keep the element callback referentially stable — a fresh closure each
8
+ // render makes the web renderer detach/reattach it, refiring onReady.
9
+ const propsRef = useRef(props);
10
+ propsRef.current = props;
11
+ const canvasRef = useMemo(
12
+ () => (el: HTMLCanvasElement | null) => {
13
+ if (!el) {
14
+ return;
15
+ }
16
+
17
+ propsRef.current.onReady?.({
18
+ canvas: el,
19
+ context: propsRef.current.context
20
+ ? el.getContext(propsRef.current.context)
21
+ : null,
22
+ });
23
+ },
24
+ [],
25
+ );
26
+
27
+ <canvas
28
+ id={props.id}
29
+ className={props.className}
30
+ style={{
31
+ width: typeof props.width === 'string' ? props.width : undefined,
32
+ height: typeof props.height === 'string' ? props.height : undefined,
33
+ ...props.style,
34
+ }}
35
+ width={typeof props.width === 'number' ? props.width : undefined}
36
+ height={typeof props.height === 'number' ? props.height : undefined}
37
+ ref={canvasRef}
38
+ />
39
+ }
@@ -0,0 +1,4 @@
1
+ /** AppKit has no Canvas WebGPU backend. Returns null before device acquisition. */
2
+ export function getGPU(): any {
3
+ return null
4
+ }
package/src/gpu.ts ADDED
@@ -0,0 +1,8 @@
1
+ import { GPU } from '@nativescript/canvas'
2
+
3
+ /** getGPU — returns the plugin's GPU shim (wgpu over Metal/Vulkan). Same
4
+ * spec-shaped surface as web's `navigator.gpu`: `requestAdapter()` +
5
+ * `getPreferredCanvasFormat()`. */
6
+ export function getGPU(): any {
7
+ return new GPU()
8
+ }
package/src/gpu.web.ts ADDED
@@ -0,0 +1,7 @@
1
+ /** getGPU — returns the WebGPU entry point (`navigator.gpu`) or null when
2
+ * WebGPU is unavailable (insecure context, unsupported browser). The
3
+ * returned object is spec-shaped: `requestAdapter()` +
4
+ * `getPreferredCanvasFormat()`. */
5
+ export function getGPU(): any {
6
+ return (globalThis.navigator as any)?.gpu ?? null
7
+ }
@@ -0,0 +1,3 @@
1
+ export { Canvas } from './Canvas.macos.tsrx'
2
+ export { getGPU } from './gpu.macos'
3
+ export type { CanvasProps, CanvasContextKind, CanvasReady } from './props'
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ // Platform barrels — explicit .tsrx leaf imports (moduleSuffixes don't
2
+ // reach .tsrx; same convention as @octane-xplat/ui index.*.ts).
3
+ export { Canvas } from './Canvas.tsrx'
4
+ export { getGPU } from './gpu'
5
+ export type { CanvasProps, CanvasContextKind, CanvasReady } from './props'
@@ -0,0 +1,5 @@
1
+ // Platform barrels — explicit .tsrx leaf imports (moduleSuffixes don't
2
+ // reach .tsrx; same convention as @octane-xplat/ui index.*.ts).
3
+ export { Canvas } from './Canvas.web.tsrx'
4
+ export { getGPU } from './gpu.web'
5
+ export type { CanvasProps, CanvasContextKind, CanvasReady } from './props'
package/src/props.ts ADDED
@@ -0,0 +1,31 @@
1
+ export type CanvasContextKind = '2d' | 'webgl' | 'webgl2' | 'webgpu' | 'bitmaprenderer'
2
+
3
+ export interface CanvasReady {
4
+ /** HTMLCanvasElement on web, the plugin's Canvas view on iOS/Android.
5
+ * AppKit supplies an unavailable handle whose `getContext()` always
6
+ * returns null; it is not a native drawing surface. */
7
+ canvas: any
8
+ /** The resolved context for the `context` prop, or null when that
9
+ * kind is unsupported/unavailable. WebGPU still needs a device —
10
+ * `getGPU()` normalizes adapter/device acquisition. */
11
+ context: any
12
+ }
13
+
14
+ export interface CanvasProps {
15
+ className?: any
16
+ style?: any
17
+ id?: string
18
+ /** Context kind resolved before `onReady` fires. Omit to defer
19
+ * `getContext` to user code (only one kind per canvas — call once). */
20
+ context?: CanvasContextKind
21
+ /** Web: after mount. iOS/Android: the plugin's `ready` event (earlier
22
+ * `getContext` calls can return null). AppKit: after the neutral frame
23
+ * mounts, with context null. This callback does not guarantee drawing
24
+ * or GPU readiness; check context or call canvas.getContext first. */
25
+ onReady?: (ready: CanvasReady) => void
26
+ /** Numeric values map to the element's width/height — on web that is
27
+ * the canvas *drawing buffer* size (pair with style for display
28
+ * size), on native dip size. */
29
+ width?: string | number
30
+ height?: string | number
31
+ }
@@ -0,0 +1,14 @@
1
+ // Boundary types for @octane-xplat/canvas — the mechanical wrapper over
2
+ // props.ts (tsrx-tsc can't emit .tsrx declarations; see ui/types/index.d.ts
3
+ // for the same pattern).
4
+ import type { UniversalComponent } from 'octane/universal'
5
+ import type { CanvasProps } from './props.js'
6
+
7
+ export declare const Canvas: UniversalComponent<CanvasProps>
8
+
9
+ /** Returns the WebGPU entry point — `navigator.gpu` on web, the plugin's
10
+ * GPU shim on iOS/Android — or null where WebGPU is unavailable.
11
+ * Native AppKit always returns null. */
12
+ export declare function getGPU(): any
13
+
14
+ export type { CanvasProps, CanvasContextKind, CanvasReady } from './props.js'
@@ -0,0 +1,29 @@
1
+ export type CanvasContextKind = '2d' | 'webgl' | 'webgl2' | 'webgpu' | 'bitmaprenderer';
2
+ export interface CanvasReady {
3
+ /** HTMLCanvasElement on web, the plugin's Canvas view on iOS/Android.
4
+ * AppKit supplies an unavailable handle whose `getContext()` always
5
+ * returns null; it is not a native drawing surface. */
6
+ canvas: any;
7
+ /** The resolved context for the `context` prop, or null when that
8
+ * kind is unsupported/unavailable. WebGPU still needs a device —
9
+ * `getGPU()` normalizes adapter/device acquisition. */
10
+ context: any;
11
+ }
12
+ export interface CanvasProps {
13
+ className?: any;
14
+ style?: any;
15
+ id?: string;
16
+ /** Context kind resolved before `onReady` fires. Omit to defer
17
+ * `getContext` to user code (only one kind per canvas — call once). */
18
+ context?: CanvasContextKind;
19
+ /** Web: after mount. iOS/Android: the plugin's `ready` event (earlier
20
+ * `getContext` calls can return null). AppKit: after the neutral frame
21
+ * mounts, with context null. This callback does not guarantee drawing
22
+ * or GPU readiness; check context or call canvas.getContext first. */
23
+ onReady?: (ready: CanvasReady) => void;
24
+ /** Numeric values map to the element's width/height — on web that is
25
+ * the canvas *drawing buffer* size (pair with style for display
26
+ * size), on native dip size. */
27
+ width?: string | number;
28
+ height?: string | number;
29
+ }