Split @tldraw/state into @tldraw/state and @tldraw/state-react (#4170)

The backend code uses `@tldraw/state`, which is fine, but the package
has a peer dependency on `react`, which is not fine to impose on backend
consumers. So let's split this up again.

### Change type

- [ ] `bugfix`
- [ ] `improvement`
- [ ] `feature`
- [x] `api`
- [x] `other`

### Test plan

1. Create a shape...
2.

- [ ] Unit tests
- [ ] End to end tests

### Release notes

- Fixed a bug with…
This commit is contained in:
David Sheldrick 2024-07-15 12:18:59 +01:00 committed by GitHub
parent 719332d272
commit 7ba4040e84
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
68 changed files with 243 additions and 121 deletions

View file

@ -79,15 +79,15 @@ import { TLStoreSnapshot } from '@tldraw/tlschema';
import { TLUnknownBinding } from '@tldraw/tlschema';
import { TLUnknownShape } from '@tldraw/tlschema';
import { TLVideoAsset } from '@tldraw/tlschema';
import { track } from '@tldraw/state';
import { track } from '@tldraw/state-react';
import { transact } from '@tldraw/state';
import { transaction } from '@tldraw/state';
import { UnknownRecord } from '@tldraw/store';
import { useComputed } from '@tldraw/state';
import { useQuickReactor } from '@tldraw/state';
import { useReactor } from '@tldraw/state';
import { useStateTracking } from '@tldraw/state';
import { useValue } from '@tldraw/state';
import { useComputed } from '@tldraw/state-react';
import { useQuickReactor } from '@tldraw/state-react';
import { useReactor } from '@tldraw/state-react';
import { useStateTracking } from '@tldraw/state-react';
import { useValue } from '@tldraw/state-react';
import { VecModel } from '@tldraw/tlschema';
import { whyAmIRunning } from '@tldraw/state';

View file

@ -46,6 +46,7 @@
},
"dependencies": {
"@tldraw/state": "workspace:*",
"@tldraw/state-react": "workspace:*",
"@tldraw/store": "workspace:*",
"@tldraw/tlschema": "workspace:*",
"@tldraw/utils": "workspace:*",

View file

@ -22,18 +22,20 @@ export {
atom,
computed,
react,
track,
transact,
transaction,
whyAmIRunning,
type Atom,
type Signal,
} from '@tldraw/state'
export {
track,
useComputed,
useQuickReactor,
useReactor,
useStateTracking,
useValue,
whyAmIRunning,
type Atom,
type Signal,
} from '@tldraw/state'
} from '@tldraw/state-react'
export {
ErrorScreen,
LoadingScreen,

View file

@ -1,4 +1,4 @@
import { track } from '@tldraw/state'
import { track } from '@tldraw/state-react'
import { modulate } from '@tldraw/utils'
import { useEffect, useState } from 'react'
import { useEditor } from '../hooks/useEditor'

View file

@ -1,4 +1,4 @@
import { track } from '@tldraw/state'
import { track } from '@tldraw/state-react'
import { TLInstancePresence } from '@tldraw/tlschema'
import { useEffect, useRef, useState } from 'react'
import { Editor } from '../editor/Editor'

View file

@ -1,4 +1,4 @@
import { useQuickReactor, useStateTracking } from '@tldraw/state'
import { useQuickReactor, useStateTracking } from '@tldraw/state-react'
import { TLShape, TLShapeId } from '@tldraw/tlschema'
import { memo, useCallback, useRef } from 'react'
import { ShapeUtil } from '../editor/shapes/ShapeUtil'

View file

@ -1,4 +1,5 @@
import { react, useQuickReactor, useValue } from '@tldraw/state'
import { react } from '@tldraw/state'
import { useQuickReactor, useValue } from '@tldraw/state-react'
import { TLHandle, TLShapeId } from '@tldraw/tlschema'
import { dedupe, modulate, objectMapValues } from '@tldraw/utils'
import classNames from 'classnames'

View file

@ -1,4 +1,4 @@
import { useValue } from '@tldraw/state'
import { useValue } from '@tldraw/state-react'
import { noop } from '@tldraw/utils'
import classNames from 'classnames'
import { ComponentType, useEffect, useLayoutEffect, useRef, useState } from 'react'

View file

@ -1,4 +1,4 @@
import { useValue } from '@tldraw/state'
import { useValue } from '@tldraw/state-react'
import classNames from 'classnames'
import { useRef } from 'react'
import { useEditor } from '../../hooks/useEditor'

View file

@ -1,4 +1,4 @@
import { useQuickReactor, useStateTracking, useValue } from '@tldraw/state'
import { useQuickReactor, useStateTracking, useValue } from '@tldraw/state-react'
import { TLShape, TLShapeId } from '@tldraw/tlschema'
import classNames from 'classnames'
import { memo, useLayoutEffect, useRef } from 'react'

View file

@ -1,4 +1,4 @@
import { useValue } from '@tldraw/state'
import { useValue } from '@tldraw/state-react'
import { TLShapeId } from '@tldraw/tlschema'
import { memo, useRef } from 'react'
import { useEditor } from '../../hooks/useEditor'

View file

@ -1,4 +1,4 @@
import { useValue } from '@tldraw/state'
import { useValue } from '@tldraw/state-react'
import { useEditor } from '../../../hooks/useEditor'
import { Box } from '../../../primitives/Box'
import { getPerfectDashProps } from '../shared/getPerfectDashProps'

View file

@ -1,4 +1,4 @@
import { useQuickReactor } from '@tldraw/state'
import { useQuickReactor } from '@tldraw/state-react'
import { TLCursorType } from '@tldraw/tlschema'
import { PI, radiansToDegrees } from '../primitives/utils'
import { useContainer } from './useContainer'

View file

@ -1,4 +1,4 @@
import { useValue } from '@tldraw/state'
import { useValue } from '@tldraw/state-react'
import React from 'react'
import { debugFlags } from '../utils/debug-flags'
import { useContainer } from './useContainer'

View file

@ -1,4 +1,4 @@
import { useValue } from '@tldraw/state'
import { useValue } from '@tldraw/state-react'
import { useEffect } from 'react'
import { TLKeyboardEventInfo } from '../editor/types/event-types'
import { preventDefault } from '../utils/dom'

View file

@ -1,4 +1,4 @@
import { useValue } from '@tldraw/state'
import { useValue } from '@tldraw/state-react'
import { TLShapeId } from '@tldraw/tlschema'
import { useEditor } from './useEditor'

View file

@ -1,4 +1,4 @@
import { useValue } from '@tldraw/state'
import { useValue } from '@tldraw/state-react'
import { useSvgExportContext } from '../editor/types/SvgExportContext'
import { useEditor } from './useEditor'

View file

@ -1,4 +1,4 @@
import { useValue } from '@tldraw/state'
import { useValue } from '@tldraw/state-react'
import { TLShapeId } from '@tldraw/tlschema'
import { useEditor } from './useEditor'

View file

@ -1,4 +1,4 @@
import { useComputed, useValue } from '@tldraw/state'
import { useComputed, useValue } from '@tldraw/state-react'
import { uniq } from '../utils/uniq'
import { useEditor } from './useEditor'

View file

@ -1,4 +1,4 @@
import { useValue } from '@tldraw/state'
import { useValue } from '@tldraw/state-react'
import { TLInstancePresence } from '@tldraw/tlschema'
import { useEditor } from './useEditor'

View file

@ -4,7 +4,6 @@
"compilerOptions": {
"outDir": "./.tsbuild",
"rootDir": "src",
// TODO: enable these two options
"noImplicitOverride": false,
"noImplicitReturns": false,
"types": ["node", "@types/jest"]
@ -13,6 +12,9 @@
{
"path": "../state"
},
{
"path": "../state-react"
},
{
"path": "../store"
},

View file

@ -0,0 +1 @@
This code is licensed under the [tldraw license](https://github.com/tldraw/tldraw/blob/main/LICENSE.md)

View file

@ -0,0 +1,3 @@
# @tldraw/state
...

View file

@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
"extends": "../../config/api-extractor.json"
}

View file

@ -0,0 +1,47 @@
## API Report File for "@tldraw/state-react"
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
import { Atom } from '@tldraw/state';
import { AtomOptions } from '@tldraw/state';
import { Computed } from '@tldraw/state';
import { ComputedOptions } from '@tldraw/state';
import { FunctionComponent } from 'react';
import { default as React_2 } from 'react';
import { Signal } from '@tldraw/state';
// @public
export function track<T extends FunctionComponent<any>>(baseComponent: T): React_2.NamedExoticComponent<React_2.ComponentProps<T>>;
// @public
export function useAtom<Value, Diff = unknown>(
name: string,
valueOrInitialiser: (() => Value) | Value,
options?: AtomOptions<Value, Diff>): Atom<Value, Diff>;
// @public
export function useComputed<Value>(name: string, compute: () => Value, deps: any[]): Computed<Value>;
// @public (undocumented)
export function useComputed<Value, Diff = unknown>(name: string, compute: () => Value, opts: ComputedOptions<Value, Diff>, deps: any[]): Computed<Value>;
// @public (undocumented)
export function useQuickReactor(name: string, reactFn: () => void, deps?: any[]): void;
// @public (undocumented)
export function useReactor(name: string, reactFn: () => void, deps?: any[] | undefined): void;
// @public
export function useStateTracking<T>(name: string, render: () => T): T;
// @public
export function useValue<Value>(value: Signal<Value>): Value;
// @public (undocumented)
export function useValue<Value>(name: string, fn: () => Value, deps: unknown[]): Value;
// (No @packageDocumentation comment for this package)
```

View file

@ -0,0 +1,74 @@
{
"name": "@tldraw/state-react",
"description": "A tiny little drawing app (react bindings for state).",
"version": "2.3.0",
"author": {
"name": "tldraw Inc.",
"email": "hello@tldraw.com"
},
"homepage": "https://tldraw.dev",
"license": "SEE LICENSE IN LICENSE.md",
"repository": {
"type": "git",
"url": "https://github.com/tldraw/tldraw"
},
"bugs": {
"url": "https://github.com/tldraw/tldraw/issues"
},
"keywords": [
"tldraw",
"drawing",
"app",
"development",
"whiteboard",
"canvas",
"infinite"
],
"/* NOTE */": "These `main` and `types` fields are rewritten by the build script. They are not the actual values we publish",
"main": "./src/index.ts",
"types": "./.tsbuild/index.d.ts",
"/* GOTCHA */": "files will include ./dist and index.d.ts by default, add any others you want to include in here",
"files": [],
"scripts": {
"test-ci": "lazy inherit",
"test": "yarn run -T jest",
"test-coverage": "lazy inherit",
"build": "yarn run -T tsx ../../scripts/build-package.ts",
"build-api": "yarn run -T tsx ../../scripts/build-api.ts",
"prepack": "yarn run -T tsx ../../scripts/prepack.ts",
"postpack": "../../scripts/postpack.sh",
"pack-tarball": "yarn pack",
"lint": "yarn run -T tsx ../../scripts/lint.ts"
},
"jest": {
"preset": "config/jest/node",
"setupFiles": [
"raf/polyfill"
],
"moduleNameMapper": {
"^~(.*)": "<rootDir>/src/$1"
},
"transformIgnorePatterns": [
"node_modules/(?!(nanoid)/)"
]
},
"dependencies": {
"@tldraw/state": "workspace:*"
},
"devDependencies": {
"@types/lodash": "^4.14.188",
"@types/react": "^18.2.47",
"@types/react-test-renderer": "^18.0.0",
"lodash": "^4.17.21",
"react-test-renderer": "^18.2.0"
},
"peerDependencies": {
"react": "^18"
},
"typedoc": {
"readmeFile": "none",
"entryPoint": "./src/index.ts",
"displayName": "@tldraw/state",
"tsconfig": "./tsconfig.json"
}
}

View file

@ -0,0 +1,7 @@
export { track } from './lib/track'
export { useAtom } from './lib/useAtom'
export { useComputed } from './lib/useComputed'
export { useQuickReactor } from './lib/useQuickReactor'
export { useReactor } from './lib/useReactor'
export { useStateTracking } from './lib/useStateTracking'
export { useValue } from './lib/useValue'

View file

@ -1,6 +1,6 @@
import { atom } from '@tldraw/state'
import { createRef, forwardRef, memo, useEffect, useImperativeHandle } from 'react'
import { ReactTestRenderer, act, create } from 'react-test-renderer'
import { atom } from '../core/Atom'
import { track } from './track'
test("tracked components are memo'd", async () => {

View file

@ -1,5 +1,5 @@
import { Atom } from '@tldraw/state'
import ReactTestRenderer from 'react-test-renderer'
import { Atom } from '../core/Atom'
import { useAtom } from './useAtom'
import { useValue } from './useValue'

View file

@ -1,5 +1,5 @@
import { Atom, AtomOptions, atom } from '@tldraw/state'
import { useState } from 'react'
import { Atom, AtomOptions, atom } from '../core'
/**
* Creates a new atom and returns it. The atom will be created only once.

View file

@ -1,7 +1,6 @@
import { Atom, Computed } from '@tldraw/state'
import { useState } from 'react'
import ReactTestRenderer from 'react-test-renderer'
import { Atom } from '../core/Atom'
import { Computed } from '../core/Computed'
import { useAtom } from './useAtom'
import { useComputed } from './useComputed'
import { useValue } from './useValue'

View file

@ -1,6 +1,6 @@
/* eslint-disable prefer-rest-params */
import { Computed, ComputedOptions, computed } from '@tldraw/state'
import { useMemo } from 'react'
import { Computed, ComputedOptions, computed } from '../core'
/**
* Creates a new computed signal and returns it. The computed signal will be created only once.

View file

@ -1,5 +1,5 @@
import { EMPTY_ARRAY, EffectScheduler } from '@tldraw/state'
import { useEffect } from 'react'
import { EMPTY_ARRAY, EffectScheduler } from '../core'
/** @public */
export function useQuickReactor(name: string, reactFn: () => void, deps: any[] = EMPTY_ARRAY) {

View file

@ -1,5 +1,5 @@
import { EffectScheduler } from '@tldraw/state'
import { useEffect, useMemo, useRef } from 'react'
import { EffectScheduler } from '../core'
/** @public */
export function useReactor(name: string, reactFn: () => void, deps: undefined | any[] = []) {

View file

@ -1,6 +1,6 @@
import { atom } from '@tldraw/state'
import * as React from 'react'
import { act, create, ReactTestRenderer } from 'react-test-renderer'
import { atom } from '../core/Atom'
import { useStateTracking } from './useStateTracking'
describe('useStateTracking', () => {

View file

@ -1,5 +1,5 @@
import { EffectScheduler } from '@tldraw/state'
import React from 'react'
import { EffectScheduler } from '../core'
/**
* Wraps some synchronous react render logic in a reactive tracking context.

View file

@ -1,7 +1,6 @@
import { Atom, Computed, atom } from '@tldraw/state'
import { useState } from 'react'
import ReactTestRenderer from 'react-test-renderer'
import { Atom, atom } from '../core/Atom'
import { Computed } from '../core/Computed'
import { useAtom } from './useAtom'
import { useComputed } from './useComputed'
import { useValue } from './useValue'

View file

@ -1,6 +1,6 @@
/* eslint-disable prefer-rest-params */
import { Signal, computed, react } from '@tldraw/state'
import { useMemo, useRef, useSyncExternalStore } from 'react'
import { Signal, computed, react } from '../core'
/**
* Extracts the value from a signal and subscribes to it.

View file

@ -0,0 +1,14 @@
{
"extends": "../../config/tsconfig.base.json",
"include": ["src"],
"exclude": ["node_modules", "dist", ".tsbuild*"],
"compilerOptions": {
"outDir": "./.tsbuild",
"rootDir": "src"
},
"references": [
{
"path": "../state"
}
]
}

View file

@ -4,9 +4,6 @@
```ts
import { FunctionComponent } from 'react';
import { default as React_2 } from 'react';
// @internal
export class ArraySet<T> {
add(elem: T): boolean;
@ -162,9 +159,6 @@ export interface Signal<Value, Diff = unknown> {
name: string;
}
// @public
export function track<T extends FunctionComponent<any>>(baseComponent: T): React_2.NamedExoticComponent<React_2.ComponentProps<T>>;
// @public
export function transact<T>(fn: () => T): T;
@ -180,33 +174,6 @@ export type UNINITIALIZED = typeof UNINITIALIZED;
// @public
export function unsafe__withoutCapture<T>(fn: () => T): T;
// @public
export function useAtom<Value, Diff = unknown>(
name: string,
valueOrInitialiser: (() => Value) | Value,
options?: AtomOptions<Value, Diff>): Atom<Value, Diff>;
// @public
export function useComputed<Value>(name: string, compute: () => Value, deps: any[]): Computed<Value>;
// @public (undocumented)
export function useComputed<Value, Diff = unknown>(name: string, compute: () => Value, opts: ComputedOptions<Value, Diff>, deps: any[]): Computed<Value>;
// @public (undocumented)
export function useQuickReactor(name: string, reactFn: () => void, deps?: any[]): void;
// @public (undocumented)
export function useReactor(name: string, reactFn: () => void, deps?: any[] | undefined): void;
// @public
export function useStateTracking<T>(name: string, render: () => T): T;
// @public
export function useValue<Value>(value: Signal<Value>): Value;
// @public (undocumented)
export function useValue<Value>(name: string, fn: () => Value, deps: unknown[]): Value;
// @public
export function whyAmIRunning(): void;

View file

@ -54,13 +54,7 @@
},
"devDependencies": {
"@types/lodash": "^4.14.188",
"@types/react": "^18.2.47",
"@types/react-test-renderer": "^18.0.0",
"lodash": "^4.17.21",
"react-test-renderer": "^18.2.0"
},
"peerDependencies": {
"react": "^18"
"lodash": "^4.17.21"
},
"typedoc": {
"readmeFile": "none",

View file

@ -1,3 +1,33 @@
/* eslint-disable local/no-export-star */
export * from './lib/core'
export * from './lib/react'
import { singleton } from './lib/helpers'
export { ArraySet } from './lib/ArraySet'
export { atom, isAtom } from './lib/Atom'
export type { Atom, AtomOptions } from './lib/Atom'
export {
UNINITIALIZED,
computed,
getComputedInstance,
isUninitialized,
withDiff,
} from './lib/Computed'
export type { Computed, ComputedOptions, WithDiff } from './lib/Computed'
export { EffectScheduler, react, reactor } from './lib/EffectScheduler'
export type { EffectSchedulerOptions, Reactor } from './lib/EffectScheduler'
export { unsafe__withoutCapture, whyAmIRunning } from './lib/capture'
export { EMPTY_ARRAY } from './lib/helpers'
export { isSignal } from './lib/isSignal'
export { transact, transaction } from './lib/transactions'
export { RESET_VALUE } from './lib/types'
export type { Child, ComputeDiff, Signal } from './lib/types'
// This should be incremented any time an API change is made. i.e. for additions or removals.
// Bugfixes need not increment this.
const currentApiVersion = 1
const actualApiVersion = singleton('apiVersion', () => currentApiVersion)
if (actualApiVersion !== currentApiVersion) {
throw new Error(
`You have multiple incompatible versions of @tldraw/state in your app. Please deduplicate the package.`
)
}

View file

@ -1,27 +0,0 @@
import { singleton } from './helpers'
export { ArraySet } from './ArraySet'
export { atom, isAtom } from './Atom'
export type { Atom, AtomOptions } from './Atom'
export { UNINITIALIZED, computed, getComputedInstance, isUninitialized, withDiff } from './Computed'
export type { Computed, ComputedOptions, WithDiff } from './Computed'
export { EffectScheduler, react, reactor } from './EffectScheduler'
export type { EffectSchedulerOptions, Reactor } from './EffectScheduler'
export { unsafe__withoutCapture, whyAmIRunning } from './capture'
export { EMPTY_ARRAY } from './helpers'
export { isSignal } from './isSignal'
export { transact, transaction } from './transactions'
export { RESET_VALUE } from './types'
export type { Child, ComputeDiff, Signal } from './types'
// This should be incremented any time an API change is made. i.e. for additions or removals.
// Bugfixes need not increment this.
const currentApiVersion = 1
const actualApiVersion = singleton('apiVersion', () => currentApiVersion)
if (actualApiVersion !== currentApiVersion) {
throw new Error(
`You have multiple incompatible versions of @tldraw/state in your app. Please deduplicate the package.`
)
}

View file

@ -1,7 +0,0 @@
export { track } from './track'
export { useAtom } from './useAtom'
export { useComputed } from './useComputed'
export { useQuickReactor } from './useQuickReactor'
export { useReactor } from './useReactor'
export { useStateTracking } from './useStateTracking'
export { useValue } from './useValue'

View file

@ -6110,6 +6110,7 @@ __metadata:
"@testing-library/jest-dom": "npm:^5.16.5"
"@testing-library/react": "npm:^14.0.0"
"@tldraw/state": "workspace:*"
"@tldraw/state-react": "workspace:*"
"@tldraw/store": "workspace:*"
"@tldraw/tlschema": "workspace:*"
"@tldraw/utils": "workspace:*"
@ -6224,10 +6225,11 @@ __metadata:
languageName: unknown
linkType: soft
"@tldraw/state@workspace:*, @tldraw/state@workspace:packages/state":
"@tldraw/state-react@workspace:*, @tldraw/state-react@workspace:packages/state-react":
version: 0.0.0-use.local
resolution: "@tldraw/state@workspace:packages/state"
resolution: "@tldraw/state-react@workspace:packages/state-react"
dependencies:
"@tldraw/state": "workspace:*"
"@types/lodash": "npm:^4.14.188"
"@types/react": "npm:^18.2.47"
"@types/react-test-renderer": "npm:^18.0.0"
@ -6238,6 +6240,15 @@ __metadata:
languageName: unknown
linkType: soft
"@tldraw/state@workspace:*, @tldraw/state@workspace:packages/state":
version: 0.0.0-use.local
resolution: "@tldraw/state@workspace:packages/state"
dependencies:
"@types/lodash": "npm:^4.14.188"
lodash: "npm:^4.17.21"
languageName: unknown
linkType: soft
"@tldraw/store@workspace:*, @tldraw/store@workspace:packages/store":
version: 0.0.0-use.local
resolution: "@tldraw/store@workspace:packages/store"