diff --git a/.size-snapshot.json b/.size-snapshot.json
index aee764c..72beee1 100644
--- a/.size-snapshot.json
+++ b/.size-snapshot.json
@@ -14,8 +14,8 @@
}
},
"dist/index.cjs.js": {
- "bundled": 6218,
- "minified": 3799,
- "gzipped": 1371
+ "bundled": 7065,
+ "minified": 4289,
+ "gzipped": 1465
}
}
diff --git a/index.js b/index.js
deleted file mode 100644
index 11ce3a4..0000000
--- a/index.js
+++ /dev/null
@@ -1,164 +0,0 @@
-import Zdog from 'zdog'
-import React, { useContext, useRef, useEffect, useLayoutEffect, useState, useImperativeHandle } from 'react'
-import ResizeObserver from 'resize-observer-polyfill'
-
-const stateContext = React.createContext()
-const parentContext = React.createContext()
-
-let globalEffects = []
-export function addEffect(callback) {
- globalEffects.push(callback)
-}
-
-export function invalidate() {
- // TODO: render loop has to be able to render frames on demand
-}
-
-export function applyProps(instance, newProps) {
- Zdog.extend(instance, newProps)
- invalidate()
-}
-
-function useMeasure() {
- const ref = useRef()
- const [bounds, set] = useState({ left: 0, top: 0, width: 0, height: 0 })
- const [ro] = useState(() => new ResizeObserver(([entry]) => set(entry.contentRect)))
- useEffect(() => {
- if (ref.current) ro.observe(ref.current)
- return () => ro.disconnect()
- }, [ref.current])
- return [{ ref }, bounds]
-}
-
-function useRender(fn, deps = []) {
- const state = useContext(stateContext)
- useEffect(() => {
- // Subscribe to the render-loop
- const unsubscribe = state.current.subscribe(fn)
- // Call subscription off on unmount
- return () => unsubscribe()
- }, deps)
-}
-
-function useZdog() {
- const state = useContext(stateContext)
- return state.current
-}
-
-function useZdogPrimitive(primitive, children, props, ref) {
- const state = useContext(stateContext)
- const parent = useContext(parentContext)
- const [node] = useState(() => new primitive(props))
-
- useImperativeHandle(ref, () => node)
- useLayoutEffect(() => void applyProps(node, props), [props])
- useLayoutEffect(() => {
- if (parent) {
- parent.addChild(node)
- state.current.illu.updateGraph()
- return () => {
- parent.removeChild(node)
- parent.updateFlatGraph()
- state.current.illu.updateGraph()
- }
- }
- }, [parent])
- return [, node]
-}
-
-const Illustration = React.memo(({ children, style, resize, element: Element = 'svg', dragRotate, ...rest }) => {
- const canvas = useRef()
- const [bind, size] = useMeasure()
- const [result, scene] = useZdogPrimitive(Zdog.Anchor, children)
-
- const state = useRef({
- scene,
- illu: undefined,
- size: {},
- subscribers: [],
- subscribe: fn => {
- state.current.subscribers.push(fn)
- return () => (state.current.subscribers = state.current.subscribers.filter(s => s !== fn))
- },
- })
-
- useEffect(() => {
- state.current.size = size
- if (state.current.illu) state.current.illu.setSize(size.width, size.height)
- }, [size])
-
- useEffect(() => {
- state.current.illu = new Zdog.Illustration({ element: canvas.current, dragRotate, ...rest })
- state.current.illu.addChild(scene)
- state.current.illu.updateGraph()
-
- let frame
- let active = true
- function render(t) {
- const { size, subscribers } = state.current
- if (size.width && size.height) {
- // Run global effects
- globalEffects.forEach(fn => fn(t))
- // Run local effects
- subscribers.forEach(fn => fn(t))
- // Render scene
- state.current.illu.updateRenderGraph()
- }
- if (active) frame = requestAnimationFrame(render)
- }
-
- // Start render loop
- render()
-
- return () => {
- // Take no chances, the loop has got to stop if the component unmounts
- active = false
- cancelAnimationFrame(frame)
- }
- }, [])
-
- // Takes care of updating the main illustration
- useLayoutEffect(() => void (state.current.illu && applyProps(state.current.illu, rest)), [rest])
-
- return (
-
-
- {state.current.illu && }
-
- )
-})
-
-const createZdog = primitive =>
- React.forwardRef(({ children, ...rest }, ref) => useZdogPrimitive(primitive, children, rest, ref)[0])
-
-const Anchor = createZdog(Zdog.Anchor)
-const Shape = createZdog(Zdog.Shape)
-const Group = createZdog(Zdog.Group)
-const Rect = createZdog(Zdog.Rect)
-const RoundedRect = createZdog(Zdog.RoundedRect)
-const Ellipse = createZdog(Zdog.Ellipse)
-const Polygon = createZdog(Zdog.Polygon)
-const Hemisphere = createZdog(Zdog.Hemisphere)
-const Cylinder = createZdog(Zdog.Cylinder)
-const Cone = createZdog(Zdog.Cone)
-const Box = createZdog(Zdog.Box)
-
-export {
- Illustration,
- useRender,
- useZdog,
- Anchor,
- Shape,
- Group,
- Rect,
- RoundedRect,
- Ellipse,
- Polygon,
- Hemisphere,
- Cylinder,
- Cone,
- Box,
-}
diff --git a/index.tsx b/index.tsx
new file mode 100644
index 0000000..10f4cbd
--- /dev/null
+++ b/index.tsx
@@ -0,0 +1,216 @@
+import React, {
+ CSSProperties,
+ ForwardRefExoticComponent,
+ MutableRefObject,
+ PropsWithChildren,
+ ReactNode,
+ Ref,
+ RefAttributes,
+ useContext,
+ useEffect,
+ useImperativeHandle,
+ useLayoutEffect,
+ useRef,
+ useState,
+} from 'react'
+import ResizeObserver from 'resize-observer-polyfill'
+import * as Zdog from 'zdog'
+
+interface Bounds {
+ left: number
+ top: number
+ width: number
+ height: number
+}
+
+type UnsubscribeFn = () => void
+interface ZdogState {
+ scene: Zdog.Anchor
+ illu: Zdog.Illustration
+ size: Bounds
+ subscribers: FrameRequestCallback[]
+ subscribe: (fn: FrameRequestCallback) => UnsubscribeFn
+}
+
+type PrimitiveProps = PropsWithChildren[0]>
+
+const stateContext = React.createContext>(null)
+const parentContext = React.createContext(null)
+
+let globalEffects: FrameRequestCallback[] = []
+export function addEffect(callback: FrameRequestCallback): void {
+ globalEffects.push(callback)
+}
+
+export function invalidate(): void {
+ // TODO: render loop has to be able to render frames on demand
+}
+
+export function applyProps(instance: Zdog.AnchorOptions, newProps: Zdog.AnchorOptions): void {
+ Zdog.extend(instance, newProps)
+ invalidate()
+}
+
+function useMeasure(): [{ ref: MutableRefObject }, Bounds] {
+ const ref = useRef()
+ const [bounds, set] = useState({ left: 0, top: 0, width: 0, height: 0 })
+ const [ro] = useState(() => new ResizeObserver(([entry]) => set(entry.contentRect)))
+ useEffect(() => {
+ if (ref.current) ro.observe(ref.current)
+ return () => ro.disconnect()
+ }, [ref.current])
+
+ return [{ ref }, bounds]
+}
+
+function useRender(fn: FrameRequestCallback, deps: any[] = []) {
+ const state = useContext(stateContext)
+ useEffect(() => {
+ // Subscribe to the render-loop
+ const unsubscribe = state.current.subscribe(fn)
+ // Call subscription off on unmount
+ return () => unsubscribe()
+ }, deps)
+}
+
+function useZdog() {
+ const state = useContext(stateContext)
+ return state.current
+}
+
+function useZdogPrimitive(
+ primitive: Primitive,
+ children: ReactNode,
+ props?: PrimitiveProps,
+ ref?: Ref>
+): [JSX.Element, InstanceType] {
+ const state = useContext(stateContext)
+ const parent = useContext(parentContext)
+ const [node] = useState(() => new primitive(props) as InstanceType)
+
+ useImperativeHandle(ref, () => node)
+ useLayoutEffect(() => void applyProps(node, props), [props])
+ useLayoutEffect(() => {
+ if (parent) {
+ parent.addChild(node)
+ state.current.illu.updateGraph()
+
+ return () => {
+ parent.removeChild(node)
+ //@ts-ignore updateFlatGraph missing in zdog types
+ parent.updateFlatGraph()
+ state.current.illu.updateGraph()
+ }
+ }
+ }, [parent])
+ return [, node]
+}
+
+export type IllustrationProps = Omit &
+ PropsWithChildren<{
+ style?: CSSProperties
+ element?: 'svg' | 'canvas'
+ }>
+
+const Illustration = React.memo(
+ ({ children, style, resize, element: Element = 'svg', dragRotate, ...rest }) => {
+ const canvas = useRef()
+ const [bind, size] = useMeasure()
+ const [result, scene] = useZdogPrimitive(Zdog.Anchor, children)
+
+ const state: MutableRefObject = useRef({
+ scene,
+ illu: undefined,
+ size: null,
+ subscribers: [],
+ subscribe: fn => {
+ state.current.subscribers.push(fn)
+ return () => (state.current.subscribers = state.current.subscribers.filter(s => s !== fn))
+ },
+ })
+
+ useEffect(() => {
+ state.current.size = size
+ if (state.current.illu) state.current.illu.setSize(size.width, size.height)
+ }, [size])
+
+ useEffect(() => {
+ state.current.illu = new Zdog.Illustration({ element: canvas.current, dragRotate, ...rest })
+ state.current.illu.addChild(scene)
+ state.current.illu.updateGraph()
+
+ let frame: number
+ let active = true
+ const render: FrameRequestCallback = t => {
+ const { size, subscribers } = state.current
+ if (size && size.width && size.height) {
+ // Run global effects
+ globalEffects.forEach(fn => fn(t))
+ // Run local effects
+ subscribers.forEach(fn => fn(t))
+ // Render scene
+ state.current.illu.updateRenderGraph()
+ }
+ if (active) frame = requestAnimationFrame(render)
+ }
+
+ // Start render loop
+ render(0)
+
+ return () => {
+ // Take no chances, the loop has got to stop if the component unmounts
+ active = false
+ cancelAnimationFrame(frame)
+ }
+ }, [])
+
+ // Takes care of updating the main illustration
+ useLayoutEffect(() => void (state.current.illu && applyProps(state.current.illu, rest)), [rest])
+
+ return (
+
+
+ {state.current.illu && }
+
+ )
+ }
+)
+type ZdogComponent = ForwardRefExoticComponent<
+ Omit, 'addTo'> & RefAttributes>
+>
+const createZdog = (primitive: Primitive): ZdogComponent =>
+ React.forwardRef, PrimitiveProps>(
+ ({ children, ...rest }, ref) => useZdogPrimitive(primitive, children, rest, ref)[0]
+ )
+
+const Anchor = createZdog(Zdog.Anchor)
+const Shape = createZdog(Zdog.Shape)
+const Group = createZdog(Zdog.Group)
+const Rect = createZdog(Zdog.Rect)
+const RoundedRect = createZdog(Zdog.RoundedRect)
+const Ellipse = createZdog(Zdog.Ellipse)
+const Polygon = createZdog(Zdog.Polygon)
+const Hemisphere = createZdog(Zdog.Hemisphere)
+const Cylinder = createZdog(Zdog.Cylinder)
+const Cone = createZdog(Zdog.Cone)
+const Box = createZdog(Zdog.Box)
+
+export {
+ Illustration,
+ useRender,
+ useZdog,
+ Anchor,
+ Shape,
+ Group,
+ Rect,
+ RoundedRect,
+ Ellipse,
+ Polygon,
+ Hemisphere,
+ Cylinder,
+ Cone,
+ Box,
+}
diff --git a/package.json b/package.json
index ed0155c..4004022 100644
--- a/package.json
+++ b/package.json
@@ -4,10 +4,12 @@
"description": "React-fiber renderer for zdog",
"main": "dist/index.cjs.js",
"module": "dist/index.js",
+ "types": "dist/index.d.ts",
+ "typings": "dist/index.d.ts",
"sideEffects": false,
"scripts": {
"prebuild": "rimraf dist",
- "build": "rollup -c",
+ "build": "rollup -c rollup.config.ts && tsc",
"prepare": "npm run build",
"test": "echo \"Error: no test specified\" && exit 1"
},
@@ -54,7 +56,6 @@
"zdog": ">=1.1"
},
"devDependencies": {
- "zdog": "^1.1.0",
"@babel/core": "7.4.4",
"@babel/plugin-proposal-class-properties": "7.4.4",
"@babel/plugin-proposal-do-expressions": "7.2.0",
@@ -68,6 +69,7 @@
"@babel/preset-typescript": "^7.3.3",
"@types/lodash-es": "^4.17.3",
"@types/react": "^16.8.15",
+ "@types/zdog": "^1.1.1",
"babel-eslint": "^10.0.1",
"babel-plugin-transform-react-remove-prop-types": "^0.4.24",
"husky": "^2.1.0",
@@ -79,6 +81,8 @@
"rollup-plugin-babel": "^4.3.2",
"rollup-plugin-commonjs": "^9.3.4",
"rollup-plugin-node-resolve": "^4.2.3",
- "rollup-plugin-size-snapshot": "^0.8.0"
+ "rollup-plugin-size-snapshot": "^0.8.0",
+ "typescript": "^3.9.5",
+ "zdog": "^1.1.0"
}
}
diff --git a/rollup.config.js b/rollup.config.ts
similarity index 91%
rename from rollup.config.js
rename to rollup.config.ts
index 5f50164..de2704d 100644
--- a/rollup.config.js
+++ b/rollup.config.ts
@@ -1,14 +1,14 @@
import path from 'path'
import babel from 'rollup-plugin-babel'
-import resolve from 'rollup-plugin-node-resolve'
import commonjs from 'rollup-plugin-commonjs'
+import resolve from 'rollup-plugin-node-resolve'
import { sizeSnapshot } from 'rollup-plugin-size-snapshot'
const root = process.platform === 'win32' ? path.resolve('/') : '/'
const external = id => !id.startsWith('.') && !id.startsWith(root)
const extensions = ['.js', '.jsx', '.ts', '.tsx']
-const getBabelOptions = ({ useESModules }, targets) => ({
+const getBabelOptions = ({ useESModules }, targets = '') => ({
babelrc: false,
extensions,
//exclude: '**/node_modules/**',
@@ -50,9 +50,10 @@ function createConfig(entry, out) {
commonjs({
include: 'node_modules/**',
}),
+ sizeSnapshot(),
],
},
]
}
-export default [...createConfig('index', 'index')]
+export default [...createConfig('index.tsx', 'index')]
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..58dec09
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,14 @@
+{
+ "compilerOptions": {
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "jsx": "preserve",
+ "declaration": true,
+ "emitDeclarationOnly": true,
+ "outDir": "dist"
+ },
+ "exclude": ["node_modules"],
+ "include": ["index.tsx"]
+}
diff --git a/yarn.lock b/yarn.lock
index 56d61c3..b7ca954 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -798,6 +798,11 @@
dependencies:
"@types/node" "*"
+"@types/zdog@^1.1.1":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@types/zdog/-/zdog-1.1.1.tgz#62daf2340fa417caef3b40adb0c028c777206a0e"
+ integrity sha512-pEtwfB3RqQQVFf5Vk92mnVW9XRXUEx2HMItcfg+dSHgMPG4W7jmzDvPbPJYNK8qyZNZNnNWLAqbQpluvOy2Mng==
+
"@webassemblyjs/ast@1.8.5":
version "1.8.5"
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.8.5.tgz#51b1c5fe6576a34953bf4b253df9f0d490d9e359"
@@ -4069,6 +4074,11 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
+typescript@^3.9.5:
+ version "3.9.5"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.5.tgz#586f0dba300cde8be52dd1ac4f7e1009c1b13f36"
+ integrity sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ==
+
unicode-canonical-property-names-ecmascript@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818"