Replace Atom.value with Atom.get() (#2189)

This PR replaces the `.value` getter for the atom with `.get()`

### Change Type

- [x] `major` — Breaking change

---------

Co-authored-by: David Sheldrick <d.j.sheldrick@gmail.com>
This commit is contained in:
Steve Ruiz 2023-11-13 11:51:22 +00:00 committed by GitHub
parent 260a31db81
commit 5db3c1553e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
131 changed files with 1388 additions and 929 deletions

View file

@ -10,4 +10,5 @@ fi
npx lazy run build-api
git add packages/*/api-report.md
git add packages/*/api/api.json
npx lint-staged

View file

@ -1,247 +1,249 @@
import test, { Page, expect } from '@playwright/test'
import { Editor, TLShapeId, TLShapePartial } from '@tldraw/tldraw'
import assert from 'assert'
import { rename, writeFile } from 'fs/promises'
import { setupPage } from '../shared-e2e'
export {}
declare const editor: Editor
// import test, { Page, expect } from '@playwright/test'
// import { Editor, TLShapeId, TLShapePartial } from '@tldraw/tldraw'
// import assert from 'assert'
// import { rename, writeFile } from 'fs/promises'
// import { setupPage } from '../shared-e2e'
test.describe('Export snapshots', () => {
const snapshots = {
'Exports geo text with leading line breaks': [
{
id: 'shape:testShape' as TLShapeId,
type: 'geo',
props: {
w: 100,
h: 30,
text: '\n\n\n\n\n\ntext',
},
},
],
'Exports geo text with trailing line breaks': [
{
id: 'shape:testShape' as TLShapeId,
type: 'geo',
props: {
w: 100,
h: 30,
text: 'text\n\n\n\n\n\n',
},
},
],
} as Record<string, TLShapePartial[]>
// declare const editor: Editor
for (const fill of ['none', 'semi', 'solid', 'pattern']) {
snapshots[`geo fill=${fill}`] = [
{
id: 'shape:testShape' as TLShapeId,
type: 'geo',
props: {
fill,
color: 'green',
w: 100,
h: 100,
},
},
]
// test.describe('Export snapshots', () => {
// const snapshots = {
// 'Exports geo text with leading line breaks': [
// {
// id: 'shape:testShape' as TLShapeId,
// type: 'geo',
// props: {
// w: 100,
// h: 30,
// text: '\n\n\n\n\n\ntext',
// },
// },
// ],
// 'Exports geo text with trailing line breaks': [
// {
// id: 'shape:testShape' as TLShapeId,
// type: 'geo',
// props: {
// w: 100,
// h: 30,
// text: 'text\n\n\n\n\n\n',
// },
// },
// ],
// } as Record<string, TLShapePartial[]>
snapshots[`arrow fill=${fill}`] = [
{
id: 'shape:testShape' as TLShapeId,
type: 'arrow',
props: {
color: 'light-green',
fill: fill,
arrowheadStart: 'square',
arrowheadEnd: 'dot',
start: { type: 'point', x: 0, y: 0 },
end: { type: 'point', x: 100, y: 100 },
bend: 20,
},
},
]
// for (const fill of ['none', 'semi', 'solid', 'pattern']) {
// snapshots[`geo fill=${fill}`] = [
// {
// id: 'shape:testShape' as TLShapeId,
// type: 'geo',
// props: {
// fill,
// color: 'green',
// w: 100,
// h: 100,
// },
// },
// ]
snapshots[`draw fill=${fill}`] = [
{
id: 'shape:testShape' as TLShapeId,
type: 'draw',
props: {
color: 'light-violet',
fill: fill,
segments: [
{
type: 'straight',
points: [{ x: 0, y: 0 }],
},
{
type: 'straight',
points: [
{ x: 0, y: 0 },
{ x: 100, y: 0 },
],
},
{
type: 'straight',
points: [
{ x: 100, y: 0 },
{ x: 0, y: 100 },
],
},
{
type: 'straight',
points: [
{ x: 0, y: 100 },
{ x: 100, y: 100 },
],
},
{
type: 'straight',
points: [
{ x: 100, y: 100 },
{ x: 0, y: 0 },
],
},
],
isClosed: true,
isComplete: true,
},
},
]
}
// snapshots[`arrow fill=${fill}`] = [
// {
// id: 'shape:testShape' as TLShapeId,
// type: 'arrow',
// props: {
// color: 'light-green',
// fill: fill,
// arrowheadStart: 'square',
// arrowheadEnd: 'dot',
// start: { type: 'point', x: 0, y: 0 },
// end: { type: 'point', x: 100, y: 100 },
// bend: 20,
// },
// },
// ]
for (const font of ['draw', 'sans', 'serif', 'mono']) {
snapshots[`geo font=${font}`] = [
{
id: 'shape:testShape' as TLShapeId,
type: 'geo',
props: {
text: 'test',
color: 'blue',
font,
w: 100,
h: 100,
},
},
]
// snapshots[`draw fill=${fill}`] = [
// {
// id: 'shape:testShape' as TLShapeId,
// type: 'draw',
// props: {
// color: 'light-violet',
// fill: fill,
// segments: [
// {
// type: 'straight',
// points: [{ x: 0, y: 0 }],
// },
// {
// type: 'straight',
// points: [
// { x: 0, y: 0 },
// { x: 100, y: 0 },
// ],
// },
// {
// type: 'straight',
// points: [
// { x: 100, y: 0 },
// { x: 0, y: 100 },
// ],
// },
// {
// type: 'straight',
// points: [
// { x: 0, y: 100 },
// { x: 100, y: 100 },
// ],
// },
// {
// type: 'straight',
// points: [
// { x: 100, y: 100 },
// { x: 0, y: 0 },
// ],
// },
// ],
// isClosed: true,
// isComplete: true,
// },
// },
// ]
// }
snapshots[`arrow font=${font}`] = [
{
id: 'shape:testShape' as TLShapeId,
type: 'arrow',
props: {
color: 'blue',
fill: 'solid',
arrowheadStart: 'square',
arrowheadEnd: 'arrow',
font,
start: { type: 'point', x: 0, y: 0 },
end: { type: 'point', x: 100, y: 100 },
bend: 20,
text: 'test',
},
},
]
// for (const font of ['draw', 'sans', 'serif', 'mono']) {
// snapshots[`geo font=${font}`] = [
// {
// id: 'shape:testShape' as TLShapeId,
// type: 'geo',
// props: {
// text: 'test',
// color: 'blue',
// font,
// w: 100,
// h: 100,
// },
// },
// ]
snapshots[`arrow font=${font}`] = [
{
id: 'shape:testShape' as TLShapeId,
type: 'arrow',
props: {
color: 'blue',
fill: 'solid',
arrowheadStart: 'square',
arrowheadEnd: 'arrow',
font,
start: { type: 'point', x: 0, y: 0 },
end: { type: 'point', x: 100, y: 100 },
bend: 20,
text: 'test',
},
},
]
// snapshots[`arrow font=${font}`] = [
// {
// id: 'shape:testShape' as TLShapeId,
// type: 'arrow',
// props: {
// color: 'blue',
// fill: 'solid',
// arrowheadStart: 'square',
// arrowheadEnd: 'arrow',
// font,
// start: { type: 'point', x: 0, y: 0 },
// end: { type: 'point', x: 100, y: 100 },
// bend: 20,
// text: 'test',
// },
// },
// ]
snapshots[`note font=${font}`] = [
{
id: 'shape:testShape' as TLShapeId,
type: 'note',
props: {
color: 'violet',
font,
text: 'test',
},
},
]
// snapshots[`arrow font=${font}`] = [
// {
// id: 'shape:testShape' as TLShapeId,
// type: 'arrow',
// props: {
// color: 'blue',
// fill: 'solid',
// arrowheadStart: 'square',
// arrowheadEnd: 'arrow',
// font,
// start: { type: 'point', x: 0, y: 0 },
// end: { type: 'point', x: 100, y: 100 },
// bend: 20,
// text: 'test',
// },
// },
// ]
snapshots[`text font=${font}`] = [
{
id: 'shape:testShape' as TLShapeId,
type: 'text',
props: {
color: 'red',
font,
text: 'test',
},
},
]
}
// snapshots[`note font=${font}`] = [
// {
// id: 'shape:testShape' as TLShapeId,
// type: 'note',
// props: {
// color: 'violet',
// font,
// text: 'test',
// },
// },
// ]
for (const [name, shapes] of Object.entries(snapshots)) {
test(`Exports with ${name}`, async ({ browser }) => {
const page = await browser.newPage()
await setupPage(page)
await page.evaluate((shapes) => {
editor
.updateInstanceState({ exportBackground: false })
.selectAll()
.deleteShapes(editor.selectedShapeIds)
.createShapes(shapes)
}, shapes as any)
// snapshots[`text font=${font}`] = [
// {
// id: 'shape:testShape' as TLShapeId,
// type: 'text',
// props: {
// color: 'red',
// font,
// text: 'test',
// },
// },
// ]
// }
await snapshotTest(page)
})
}
// for (const [name, shapes] of Object.entries(snapshots)) {
// test(`Exports with ${name}`, async ({ browser }) => {
// const page = await browser.newPage()
// await setupPage(page)
// await page.evaluate((shapes) => {
// editor
// .updateInstanceState({ exportBackground: false })
// .selectAll()
// .deleteShapes(editor.selectedShapeIds)
// .createShapes(shapes)
// }, shapes as any)
for (const [name, shapes] of Object.entries(snapshots)) {
test(`Exports with ${name} in dark mode`, async ({ browser }) => {
const page = await browser.newPage()
await setupPage(page)
await page.evaluate((shapes) => {
editor.user.updateUserPreferences({ isDarkMode: true })
editor
.updateInstanceState({ exportBackground: false })
.selectAll()
.deleteShapes(editor.selectedShapeIds)
.createShapes(shapes)
}, shapes as any)
// await snapshotTest(page)
// })
// }
await snapshotTest(page)
})
}
})
// for (const [name, shapes] of Object.entries(snapshots)) {
// test(`Exports with ${name} in dark mode`, async ({ browser }) => {
// const page = await browser.newPage()
// await setupPage(page)
// await page.evaluate((shapes) => {
// editor.user.updateUserPreferences({ isDarkMode: true })
// editor
// .updateInstanceState({ exportBackground: false })
// .selectAll()
// .deleteShapes(editor.selectedShapeIds)
// .createShapes(shapes)
// }, shapes as any)
async function snapshotTest(page: Page) {
page.waitForEvent('download').then(async (download) => {
const path = (await download.path()) as string
assert(path)
await rename(path, path + '.svg')
await writeFile(
path + '.html',
`
<!DOCTYPE html>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<img src="${path}.svg" />
`,
'utf-8'
)
// await snapshotTest(page)
// })
// }
// })
await page.goto(`file://${path}.html`)
const clip = await page.$eval('img', (img) => img.getBoundingClientRect())
await expect(page).toHaveScreenshot({
omitBackground: true,
clip,
})
})
await page.evaluate(() => (window as any)['tldraw-export']())
}
// async function snapshotTest(page: Page) {
// page.waitForEvent('download').then(async (download) => {
// const path = (await download.path()) as string
// assert(path)
// await rename(path, path + '.svg')
// await writeFile(
// path + '.html',
// `
// <!DOCTYPE html>
// <meta charset="utf-8" />
// <meta name="viewport" content="width=device-width, initial-scale=1" />
// <img src="${path}.svg" />
// `,
// 'utf-8'
// )
// await page.goto(`file://${path}.html`)
// const clip = await page.$eval('img', (img) => img.getBoundingClientRect())
// await expect(page).toHaveScreenshot({
// omitBackground: true,
// clip,
// })
// })
// await page.evaluate(() => (window as any)['tldraw-export']())
// }

View file

@ -43,9 +43,9 @@ test.describe('Focus', () => {
({ id }) => {
if (
!(
EDITOR_A.instanceState.isFocused === (id === 'A') &&
EDITOR_B.instanceState.isFocused === (id === 'B') &&
EDITOR_C.instanceState.isFocused === (id === 'C')
EDITOR_A.getInstanceState().isFocused === (id === 'A') &&
EDITOR_B.getInstanceState().isFocused === (id === 'B') &&
EDITOR_C.getInstanceState().isFocused === (id === 'C')
)
) {
throw Error('isFocused is not correct')

View file

@ -54,21 +54,21 @@ const CustomUi = track(() => {
<div className="custom-toolbar">
<button
className="custom-button"
data-isactive={editor.currentToolId === 'select'}
data-isactive={editor.getCurrentToolId() === 'select'}
onClick={() => editor.setCurrentTool('select')}
>
Select
</button>
<button
className="custom-button"
data-isactive={editor.currentToolId === 'draw'}
data-isactive={editor.getCurrentToolId() === 'draw'}
onClick={() => editor.setCurrentTool('draw')}
>
Pencil
</button>
<button
className="custom-button"
data-isactive={editor.currentToolId === 'eraser'}
data-isactive={editor.getCurrentToolId() === 'eraser'}
onClick={() => editor.setCurrentTool('eraser')}
>
Eraser

View file

@ -579,7 +579,9 @@ export class Editor extends EventEmitter<TLEventMap> {
get cameraState(): "idle" | "moving";
cancel(): this;
cancelDoubleClick(): void;
// @deprecated (undocumented)
get canRedo(): boolean;
// @deprecated (undocumented)
get canUndo(): boolean;
// @internal (undocumented)
capturedPointerId: null | number;
@ -617,7 +619,9 @@ export class Editor extends EventEmitter<TLEventMap> {
get currentPageShapes(): TLShape[];
get currentPageShapesSorted(): TLShape[];
get currentPageState(): TLInstancePageState;
// @deprecated (undocumented)
get currentTool(): StateNode | undefined;
// @deprecated (undocumented)
get currentToolId(): string;
deleteAssets(assets: TLAsset[] | TLAssetId[]): this;
deleteOpenMenu(id: string): this;
@ -633,6 +637,7 @@ export class Editor extends EventEmitter<TLEventMap> {
readonly disposables: Set<() => void>;
dispose(): void;
distributeShapes(shapes: TLShape[] | TLShapeId[], operation: 'horizontal' | 'vertical'): this;
// @deprecated (undocumented)
get documentSettings(): TLDocument;
duplicatePage(page: TLPage | TLPageId, createId?: TLPageId): this;
duplicateShapes(shapes: TLShape[] | TLShapeId[], offset?: VecLike): this;
@ -670,11 +675,18 @@ export class Editor extends EventEmitter<TLEventMap> {
}[];
getAsset(asset: TLAsset | TLAssetId): TLAsset | undefined;
getAssetForExternalContent(info: TLExternalAssetContent): Promise<TLAsset | undefined>;
getCanRedo(): boolean;
getCanUndo(): boolean;
getContainer: () => HTMLElement;
getContentFromCurrentPage(shapes: TLShape[] | TLShapeId[]): TLContent | undefined;
getCurrentTool(): StateNode | undefined;
getCurrentToolId(): string;
getDocumentSettings(): TLDocument;
getDroppingOverShape(point: VecLike, droppingShapes?: TLShape[]): TLUnknownShape | undefined;
getHighestIndexForParent(parent: TLPage | TLParentId | TLShape): string;
getInitialMetaForShape(_shape: TLShape): JsonObject;
getInstanceState(): TLInstance;
getOpenMenus(): string[];
getOutermostSelectableShape(shape: TLShape | TLShapeId, filter?: (shape: TLShape) => boolean): TLShape;
getPage(page: TLPage | TLPageId): TLPage | undefined;
getPageShapeIds(page: TLPage | TLPageId): Set<TLShapeId>;
@ -752,6 +764,7 @@ export class Editor extends EventEmitter<TLEventMap> {
isPanning: boolean;
pointerVelocity: Vec2d;
};
// @deprecated (undocumented)
get instanceState(): TLInstance;
interrupt(): this;
isAncestorSelected(shape: TLShape | TLShapeId): boolean;
@ -773,6 +786,7 @@ export class Editor extends EventEmitter<TLEventMap> {
moveShapesToPage(shapes: TLShape[] | TLShapeId[], pageId: TLPageId): this;
nudgeShapes(shapes: TLShape[] | TLShapeId[], offset: VecLike, historyOptions?: TLCommandHistoryOptions): this;
get onlySelectedShape(): null | TLShape;
// @deprecated (undocumented)
get openMenus(): string[];
packShapes(shapes: TLShape[] | TLShapeId[], gap: number): this;
get pages(): TLPage[];

View file

@ -7528,7 +7528,7 @@
{
"kind": "Property",
"canonicalReference": "@tldraw/editor!Editor#canRedo:member",
"docComment": "/**\n * Whether the app can redo.\n *\n * @public\n */\n",
"docComment": "/**\n * @deprecated\n *\n * Use `getCanRedo` instead.\n */\n",
"excerptTokens": [
{
"kind": "Content",
@ -7558,7 +7558,7 @@
{
"kind": "Property",
"canonicalReference": "@tldraw/editor!Editor#canUndo:member",
"docComment": "/**\n * Whether the app can undo.\n *\n * @public\n */\n",
"docComment": "/**\n * @deprecated\n *\n * Use `getCanUndo` instead.\n */\n",
"excerptTokens": [
{
"kind": "Content",
@ -8280,7 +8280,7 @@
{
"kind": "Property",
"canonicalReference": "@tldraw/editor!Editor#currentTool:member",
"docComment": "/**\n * The current selected tool.\n *\n * @public\n */\n",
"docComment": "/**\n * @deprecated\n *\n * Use `getCurrentTool` instead.\n *\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
@ -8315,7 +8315,7 @@
{
"kind": "Property",
"canonicalReference": "@tldraw/editor!Editor#currentToolId:member",
"docComment": "/**\n * The id of the current selected tool.\n *\n * @public\n */\n",
"docComment": "/**\n * @deprecated\n *\n * Use `getCurrentToolId` instead.\n */\n",
"excerptTokens": [
{
"kind": "Content",
@ -8962,7 +8962,7 @@
{
"kind": "Property",
"canonicalReference": "@tldraw/editor!Editor#documentSettings:member",
"docComment": "/**\n * The global document settings that apply to all users.\n *\n * @public\n */\n",
"docComment": "/**\n * @deprecated\n *\n * Use `getDocumentSettings` instead.\n */\n",
"excerptTokens": [
{
"kind": "Content",
@ -9967,6 +9967,68 @@
"isAbstract": false,
"name": "getAssetForExternalContent"
},
{
"kind": "Method",
"canonicalReference": "@tldraw/editor!Editor#getCanRedo:member(1)",
"docComment": "/**\n * Whether the app can redo.\n *\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "getCanRedo(): "
},
{
"kind": "Content",
"text": "boolean"
},
{
"kind": "Content",
"text": ";"
}
],
"isStatic": false,
"returnTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
},
"releaseTag": "Public",
"isProtected": false,
"overloadIndex": 1,
"parameters": [],
"isOptional": false,
"isAbstract": false,
"name": "getCanRedo"
},
{
"kind": "Method",
"canonicalReference": "@tldraw/editor!Editor#getCanUndo:member(1)",
"docComment": "/**\n * Whether the app can undo.\n *\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "getCanUndo(): "
},
{
"kind": "Content",
"text": "boolean"
},
{
"kind": "Content",
"text": ";"
}
],
"isStatic": false,
"returnTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
},
"releaseTag": "Public",
"isProtected": false,
"overloadIndex": 1,
"parameters": [],
"isOptional": false,
"isAbstract": false,
"name": "getCanUndo"
},
{
"kind": "Property",
"canonicalReference": "@tldraw/editor!Editor#getContainer:member",
@ -10069,6 +10131,105 @@
"isAbstract": false,
"name": "getContentFromCurrentPage"
},
{
"kind": "Method",
"canonicalReference": "@tldraw/editor!Editor#getCurrentTool:member(1)",
"docComment": "/**\n * The current selected tool.\n *\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "getCurrentTool(): "
},
{
"kind": "Reference",
"text": "StateNode",
"canonicalReference": "@tldraw/editor!StateNode:class"
},
{
"kind": "Content",
"text": " | undefined"
},
{
"kind": "Content",
"text": ";"
}
],
"isStatic": false,
"returnTypeTokenRange": {
"startIndex": 1,
"endIndex": 3
},
"releaseTag": "Public",
"isProtected": false,
"overloadIndex": 1,
"parameters": [],
"isOptional": false,
"isAbstract": false,
"name": "getCurrentTool"
},
{
"kind": "Method",
"canonicalReference": "@tldraw/editor!Editor#getCurrentToolId:member(1)",
"docComment": "/**\n * The id of the current selected tool.\n *\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "getCurrentToolId(): "
},
{
"kind": "Content",
"text": "string"
},
{
"kind": "Content",
"text": ";"
}
],
"isStatic": false,
"returnTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
},
"releaseTag": "Public",
"isProtected": false,
"overloadIndex": 1,
"parameters": [],
"isOptional": false,
"isAbstract": false,
"name": "getCurrentToolId"
},
{
"kind": "Method",
"canonicalReference": "@tldraw/editor!Editor#getDocumentSettings:member(1)",
"docComment": "/**\n * The global document settings that apply to all users.\n *\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "getDocumentSettings(): "
},
{
"kind": "Reference",
"text": "TLDocument",
"canonicalReference": "@tldraw/tlschema!TLDocument:interface"
},
{
"kind": "Content",
"text": ";"
}
],
"isStatic": false,
"returnTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
},
"releaseTag": "Public",
"isProtected": false,
"overloadIndex": 1,
"parameters": [],
"isOptional": false,
"isAbstract": false,
"name": "getDocumentSettings"
},
{
"kind": "Method",
"canonicalReference": "@tldraw/editor!Editor#getDroppingOverShape:member(1)",
@ -10261,6 +10422,69 @@
"isAbstract": false,
"name": "getInitialMetaForShape"
},
{
"kind": "Method",
"canonicalReference": "@tldraw/editor!Editor#getInstanceState:member(1)",
"docComment": "/**\n * The current instance's state.\n *\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "getInstanceState(): "
},
{
"kind": "Reference",
"text": "TLInstance",
"canonicalReference": "@tldraw/tlschema!TLInstance:interface"
},
{
"kind": "Content",
"text": ";"
}
],
"isStatic": false,
"returnTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
},
"releaseTag": "Public",
"isProtected": false,
"overloadIndex": 1,
"parameters": [],
"isOptional": false,
"isAbstract": false,
"name": "getInstanceState"
},
{
"kind": "Method",
"canonicalReference": "@tldraw/editor!Editor#getOpenMenus:member(1)",
"docComment": "/**\n * A set of strings representing any open menus. When menus are open, certain interactions will behave differently; for example, when a draw tool is selected and a menu is open, a pointer-down will not create a dot (because the user is probably trying to close the menu) however a pointer-down event followed by a drag will begin drawing a line (because the user is BOTH trying to close the menu AND start drawing a line).\n *\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "getOpenMenus(): "
},
{
"kind": "Content",
"text": "string[]"
},
{
"kind": "Content",
"text": ";"
}
],
"isStatic": false,
"returnTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
},
"releaseTag": "Public",
"isProtected": false,
"overloadIndex": 1,
"parameters": [],
"isOptional": false,
"isAbstract": false,
"name": "getOpenMenus"
},
{
"kind": "Method",
"canonicalReference": "@tldraw/editor!Editor#getOutermostSelectableShape:member(1)",
@ -12813,7 +13037,7 @@
{
"kind": "Property",
"canonicalReference": "@tldraw/editor!Editor#instanceState:member",
"docComment": "/**\n * The current instance's state.\n *\n * @public\n */\n",
"docComment": "/**\n * @deprecated\n *\n * Use `getInstanceState` instead.\n */\n",
"excerptTokens": [
{
"kind": "Content",
@ -13801,7 +14025,7 @@
{
"kind": "Property",
"canonicalReference": "@tldraw/editor!Editor#openMenus:member",
"docComment": "/**\n * A set of strings representing any open menus. When menus are open, certain interactions will behave differently; for example, when a draw tool is selected and a menu is open, a pointer-down will not create a dot (because the user is probably trying to close the menu) however a pointer-down event followed by a drag will begin drawing a line (because the user is BOTH trying to close the menu AND start drawing a line).\n *\n * @public\n */\n",
"docComment": "/**\n * @deprecated\n *\n * Use `getOpenMenus` instead.\n */\n",
"excerptTokens": [
{
"kind": "Content",
@ -33176,7 +33400,7 @@
{
"kind": "Property",
"canonicalReference": "@tldraw/editor!StateNode#_currentToolIdMask:member",
"docComment": "/**\n * This is a hack / escape hatch that will tell the editor to report a different state as active (in `currentToolId`) when this state is active. This is usually used when a tool transitions to a child of a different state for a certain interaction and then returns to the original tool when that interaction completes; and where we would want to show the original tool as active in the UI.\n *\n * @public\n */\n",
"docComment": "/**\n * This is a hack / escape hatch that will tell the editor to report a different state as active (in `getCurrentToolId()`) when this state is active. This is usually used when a tool transitions to a child of a different state for a certain interaction and then returns to the original tool when that interaction completes; and where we would want to show the original tool as active in the UI.\n *\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",

View file

@ -221,11 +221,11 @@ const TldrawEditorWithLoadingStore = memo(function TldrawEditorBeforeLoading({
const container = useContainer()
useLayoutEffect(() => {
if (user.userPreferences.value.isDarkMode) {
if (user.userPreferences.get().isDarkMode) {
container.classList.remove('tl-theme__light')
container.classList.add('tl-theme__dark')
}
}, [container, user.userPreferences.value.isDarkMode])
}, [container, user])
switch (store.status) {
case 'error': {

View file

@ -81,9 +81,9 @@ export function Canvas({ className }: { className?: string }) {
[editor]
)
const hideShapes = useValue('debug_shapes', () => debugFlags.hideShapes.value, [debugFlags])
const debugSvg = useValue('debug_svg', () => debugFlags.debugSvg.value, [debugFlags])
const debugGeometry = useValue('debug_geometry', () => debugFlags.debugGeometry.value, [
const hideShapes = useValue('debug_shapes', () => debugFlags.hideShapes.get(), [debugFlags])
const debugSvg = useValue('debug_svg', () => debugFlags.debugSvg.get(), [debugFlags])
const debugGeometry = useValue('debug_geometry', () => debugFlags.debugGeometry.get(), [
debugFlags,
])
@ -135,9 +135,9 @@ export function Canvas({ className }: { className?: string }) {
function GridWrapper() {
const editor = useEditor()
const gridSize = useValue('gridSize', () => editor.documentSettings.gridSize, [editor])
const gridSize = useValue('gridSize', () => editor.getDocumentSettings().gridSize, [editor])
const { x, y, z } = useValue('camera', () => editor.camera, [editor])
const isGridMode = useValue('isGridMode', () => editor.instanceState.isGridMode, [editor])
const isGridMode = useValue('isGridMode', () => editor.getInstanceState().isGridMode, [editor])
const { Grid } = useEditorComponents()
if (!(Grid && isGridMode)) return null
@ -147,7 +147,7 @@ function GridWrapper() {
function ScribbleWrapper() {
const editor = useEditor()
const scribbles = useValue('scribbles', () => editor.instanceState.scribbles, [editor])
const scribbles = useValue('scribbles', () => editor.getInstanceState().scribbles, [editor])
const zoomLevel = useValue('zoomLevel', () => editor.zoomLevel, [editor])
const { Scribble } = useEditorComponents()
@ -169,7 +169,7 @@ function ScribbleWrapper() {
function BrushWrapper() {
const editor = useEditor()
const brush = useValue('brush', () => editor.instanceState.brush, [editor])
const brush = useValue('brush', () => editor.getInstanceState().brush, [editor])
const { Brush } = useEditorComponents()
if (!(Brush && brush)) return null
@ -179,7 +179,7 @@ function BrushWrapper() {
function ZoomBrushWrapper() {
const editor = useEditor()
const zoomBrush = useValue('zoomBrush', () => editor.instanceState.zoomBrush, [editor])
const zoomBrush = useValue('zoomBrush', () => editor.getInstanceState().zoomBrush, [editor])
const { ZoomBrush } = useEditorComponents()
if (!(ZoomBrush && zoomBrush)) return null
@ -211,12 +211,18 @@ function HandlesWrapper() {
const { Handles } = useEditorComponents()
const zoomLevel = useValue('zoomLevel', () => editor.zoomLevel, [editor])
const isCoarse = useValue('coarse pointer', () => editor.instanceState.isCoarsePointer, [editor])
const onlySelectedShape = useValue('onlySelectedShape', () => editor.onlySelectedShape, [editor])
const isChangingStyle = useValue('isChangingStyle', () => editor.instanceState.isChangingStyle, [
const isCoarse = useValue('coarse pointer', () => editor.getInstanceState().isCoarsePointer, [
editor,
])
const onlySelectedShape = useValue('onlySelectedShape', () => editor.onlySelectedShape, [editor])
const isChangingStyle = useValue(
'isChangingStyle',
() => editor.getInstanceState().isChangingStyle,
[editor]
)
const isReadonly = useValue('isChangingStyle', () => editor.getInstanceState().isReadonly, [
editor,
])
const isReadonly = useValue('isChangingStyle', () => editor.instanceState.isReadonly, [editor])
const handles = useValue(
'handles',
() => (editor.onlySelectedShape ? editor.getShapeHandles(editor.onlySelectedShape) : undefined),
@ -345,7 +351,7 @@ function SelectedIdIndicators() {
'select.pointing_shape',
'select.pointing_selection',
'select.pointing_handle'
) && !editor.instanceState.isChangingStyle
) && !editor.getInstanceState().isChangingStyle
)
},
[editor]
@ -365,12 +371,14 @@ function SelectedIdIndicators() {
const HoveredShapeIndicator = function HoveredShapeIndicator() {
const editor = useEditor()
const { HoveredShapeIndicator } = useEditorComponents()
const isCoarsePointer = useValue('coarse pointer', () => editor.instanceState.isCoarsePointer, [
editor,
])
const isCoarsePointer = useValue(
'coarse pointer',
() => editor.getInstanceState().isCoarsePointer,
[editor]
)
const isHoveringCanvas = useValue(
'hovering canvas',
() => editor.instanceState.isHoveringCanvas,
() => editor.getInstanceState().isHoveringCanvas,
[editor]
)
const hoveredShapeId = useValue('hovered id', () => editor.currentPageState.hoveredShapeId, [
@ -480,7 +488,7 @@ const DebugSvgCopy = track(function DupSvg({ id }: { id: TLShapeId }) {
})
function UiLogger() {
const uiLog = useValue('debugging ui log', () => debugFlags.logMessages.value, [debugFlags])
const uiLog = useValue('debugging ui log', () => debugFlags.logMessages.get(), [debugFlags])
if (!uiLog.length) return null

View file

@ -38,7 +38,7 @@ const CollaboratorGuard = track(function CollaboratorGuard({
switch (collaboratorState) {
case 'inactive': {
const { followingUserId, highlightedUserIds } = editor.instanceState
const { followingUserId, highlightedUserIds } = editor.getInstanceState()
// If they're inactive and unless we're following them or they're highlighted, hide them
if (!(followingUserId === presence.userId || highlightedUserIds.includes(presence.userId))) {
return null
@ -46,7 +46,7 @@ const CollaboratorGuard = track(function CollaboratorGuard({
break
}
case 'idle': {
const { highlightedUserIds } = editor.instanceState
const { highlightedUserIds } = editor.getInstanceState()
// If they're idle and following us and unless they have a chat message or are highlighted, hide them
if (
presence.followingUserId === editor.user.id &&

View file

@ -81,7 +81,7 @@ export const Shape = track(function Shape({
if (!shape) return null
const bounds = editor.getShapeGeometry(shape).bounds
const dpr = Math.floor(editor.instanceState.devicePixelRatio * 100) / 100
const dpr = Math.floor(editor.getInstanceState().devicePixelRatio * 100) / 100
// dprMultiple is the smallest number we can multiply dpr by to get an integer
// it's usually 1, 2, or 4 (for e.g. dpr of 2, 2.5 and 2.25 respectively)
const dprMultiple = nearestMultiple(dpr)

View file

@ -170,7 +170,7 @@ export function createSessionStateSnapshotSignal(
const instanceState = store.get(TLINSTANCE_ID)
if (!instanceState) return null
const allPageIds = [...$allPageIds.value]
const allPageIds = [...$allPageIds.get()]
return {
version: CURRENT_SESSION_STATE_SNAPSHOT_VERSION,
currentPageId: instanceState.currentPageId,

View file

@ -189,7 +189,7 @@ function storeUserPreferences() {
USER_DATA_KEY,
JSON.stringify({
version: userMigrations.currentVersion,
user: globalUserPreferences.value,
user: globalUserPreferences.get(),
})
)
}
@ -225,7 +225,7 @@ function broadcastUserPreferencesChange() {
type: broadcastEventKey,
origin: broadcastOrigin,
data: {
user: globalUserPreferences.value,
user: globalUserPreferences.get(),
version: userMigrations.currentVersion,
},
} satisfies UserChangeBroadcastMessage)
@ -233,5 +233,5 @@ function broadcastUserPreferencesChange() {
/** @public */
export function getUserPreferences() {
return globalUserPreferences.value
return globalUserPreferences.get()
}

View file

@ -454,7 +454,7 @@ export class Editor extends EventEmitter<TLEventMap> {
invalidParents.add(record.parentId)
}
// clean up any arrows bound to this shape
const bindings = this._arrowBindingsIndex.value[record.id]
const bindings = this._arrowBindingsIndex.get()[record.id]
if (bindings?.length) {
for (const { arrowId, handleId } of bindings) {
const arrow = this.getShape<TLArrowShape>(arrowId)
@ -476,11 +476,11 @@ export class Editor extends EventEmitter<TLEventMap> {
this.sideEffects.registerBeforeDeleteHandler('page', (record) => {
// page was deleted, need to check whether it's the current page and select another one if so
if (this.instanceState.currentPageId !== record.id) return
if (this.getInstanceState().currentPageId !== record.id) return
const backupPageId = this.pages.find((p) => p.id !== record.id)?.id
if (!backupPageId) return
this.store.put([{ ...this.instanceState, currentPageId: backupPageId }])
this.store.put([{ ...this.getInstanceState(), currentPageId: backupPageId }])
// delete the camera and state for the page if necessary
const cameraId = CameraRecordType.createId(record.id)
@ -496,7 +496,7 @@ export class Editor extends EventEmitter<TLEventMap> {
// if the shape's parent changed and it is bound to an arrow, update the arrow's parent
if (prev.parentId !== next.parentId) {
const reparentBoundArrows = (id: TLShapeId) => {
const boundArrows = this._arrowBindingsIndex.value[id]
const boundArrows = this._arrowBindingsIndex.get()[id]
if (boundArrows?.length) {
for (const arrow of boundArrows) {
reparentArrow(arrow.arrowId)
@ -621,7 +621,7 @@ export class Editor extends EventEmitter<TLEventMap> {
this.root.enter(undefined, 'initial')
if (this.instanceState.followingUserId) {
if (this.getInstanceState().followingUserId) {
this.stopFollowingUser()
}
@ -792,10 +792,17 @@ export class Editor extends EventEmitter<TLEventMap> {
*
* @public
*/
@computed get canUndo(): boolean {
@computed getCanUndo(): boolean {
return this.history.numUndos > 0
}
/**
* @deprecated Use `getCanUndo` instead.
*/
get canUndo(): boolean {
return this.getCanUndo()
}
/**
* Redo to the next mark.
*
@ -816,10 +823,17 @@ export class Editor extends EventEmitter<TLEventMap> {
*
* @public
*/
@computed get canRedo(): boolean {
@computed getCanRedo(): boolean {
return this.history.numRedos > 0
}
/**
* @deprecated Use `getCanRedo` instead.
*/
get canRedo(): boolean {
return this.getCanRedo()
}
/**
* Create a new "mark", or stopping point, in the undo redo history. Creating a mark will clear
* any redos.
@ -910,7 +924,7 @@ export class Editor extends EventEmitter<TLEventMap> {
* @public
*/
getArrowsBoundTo(shapeId: TLShapeId) {
return this._arrowBindingsIndex.value[shapeId] || EMPTY_ARRAY
return this._arrowBindingsIndex.get()[shapeId] || EMPTY_ARRAY
}
@computed
@ -987,7 +1001,7 @@ export class Editor extends EventEmitter<TLEventMap> {
willCrashApp,
},
extras: {
activeStateNode: this.root.path.value,
activeStateNode: this.root.path.get(),
selectedShapes: this.selectedShapes,
editingShape: this.editingShapeId ? this.getShape(this.editingShapeId) : undefined,
inputs: this.inputs,
@ -1049,7 +1063,7 @@ export class Editor extends EventEmitter<TLEventMap> {
while (ids.length > 0) {
const id = ids.pop()
if (!id) return true
const current = state.current.value
const current = state.current.get()
if (current?.id === id) {
if (ids.length === 0) return true
state = current
@ -1097,8 +1111,16 @@ export class Editor extends EventEmitter<TLEventMap> {
*
* @public
*/
@computed get currentTool(): StateNode | undefined {
return this.root.current.value
@computed getCurrentTool(): StateNode | undefined {
return this.root.current.get()
}
/**
* @deprecated Use `getCurrentTool` instead.
* @public
*/
get currentTool() {
return this.getCurrentTool()
}
/**
@ -1106,12 +1128,19 @@ export class Editor extends EventEmitter<TLEventMap> {
*
* @public
*/
@computed get currentToolId(): string {
const { currentTool } = this
@computed getCurrentToolId(): string {
const currentTool = this.getCurrentTool()
if (!currentTool) return ''
return currentTool.currentToolIdMask ?? currentTool.id
}
/**
* @deprecated Use `getCurrentToolId` instead.
*/
get currentToolId() {
return this.getCurrentToolId()
}
/**
* Get a descendant by its path.
*
@ -1145,17 +1174,24 @@ export class Editor extends EventEmitter<TLEventMap> {
*
* @public
**/
@computed get documentSettings() {
@computed getDocumentSettings() {
return this.store.get(TLDOCUMENT_ID)!
}
/**
* @deprecated Use `getDocumentSettings` instead.
*/
get documentSettings() {
return this.getDocumentSettings()
}
/**
* Update the global document settings that apply to all users.
*
* @public
**/
updateDocumentSettings(settings: Partial<TLDocument>): this {
this.store.put([{ ...this.documentSettings, ...settings }])
this.store.put([{ ...this.getDocumentSettings(), ...settings }])
return this
}
@ -1166,10 +1202,17 @@ export class Editor extends EventEmitter<TLEventMap> {
*
* @public
*/
@computed get instanceState(): TLInstance {
@computed getInstanceState(): TLInstance {
return this.store.get(TLINSTANCE_ID)!
}
/**
* @deprecated Use `getInstanceState` instead.
*/
get instanceState() {
return this.getInstanceState()
}
/**
* Update the instance's state.
*
@ -1204,7 +1247,7 @@ export class Editor extends EventEmitter<TLEventMap> {
partial: Partial<Omit<TLInstance, 'currentPageId'>>,
historyOptions?: TLCommandHistoryOptions
) => {
const prev = this.store.get(this.instanceState.id)!
const prev = this.store.get(this.getInstanceState().id)!
const next = { ...prev, ...partial }
return {
@ -1243,8 +1286,15 @@ export class Editor extends EventEmitter<TLEventMap> {
*
* @public
*/
@computed get openMenus(): string[] {
return this.instanceState.openMenus
@computed getOpenMenus(): string[] {
return this.getInstanceState().openMenus
}
/**
* @deprecated Use `getOpenMenus` instead.
*/
get openMenus() {
return this.getOpenMenus()
}
/**
@ -1258,7 +1308,7 @@ export class Editor extends EventEmitter<TLEventMap> {
* @public
*/
addOpenMenu(id: string): this {
const menus = new Set(this.openMenus)
const menus = new Set(this.getOpenMenus())
if (!menus.has(id)) {
menus.add(id)
this.updateInstanceState({ openMenus: [...menus] })
@ -1277,7 +1327,7 @@ export class Editor extends EventEmitter<TLEventMap> {
* @public
*/
deleteOpenMenu(id: string): this {
const menus = new Set(this.openMenus)
const menus = new Set(this.getOpenMenus())
if (menus.has(id)) {
menus.delete(id)
this.updateInstanceState({ openMenus: [...menus] })
@ -1296,7 +1346,7 @@ export class Editor extends EventEmitter<TLEventMap> {
* @public
*/
@computed get isMenuOpen(): boolean {
return this.openMenus.length > 0
return this.getOpenMenus().length > 0
}
/* --------------------- Cursor --------------------- */
@ -1311,7 +1361,7 @@ export class Editor extends EventEmitter<TLEventMap> {
*/
setCursor = (cursor: Partial<TLCursor>): this => {
this.updateInstanceState(
{ cursor: { ...this.instanceState.cursor, ...cursor } },
{ cursor: { ...this.getInstanceState().cursor, ...cursor } },
{ ephemeral: true }
)
return this
@ -1325,7 +1375,7 @@ export class Editor extends EventEmitter<TLEventMap> {
* @public
*/
@computed get pageStates(): TLInstancePageState[] {
return this._pageStates.value
return this._pageStates.get()
}
/** @internal */
@computed private get _pageStates() {
@ -2058,7 +2108,7 @@ export class Editor extends EventEmitter<TLEventMap> {
altKey: this.inputs.altKey,
shiftKey: this.inputs.shiftKey,
button: 0,
isPen: this.instanceState.isPenMode ?? false,
isPen: this.getInstanceState().isPenMode ?? false,
})
this._tickCameraState()
@ -2091,7 +2141,7 @@ export class Editor extends EventEmitter<TLEventMap> {
this.stopCameraAnimation()
// Stop following any user
if (this.instanceState.followingUserId) {
if (this.getInstanceState().followingUserId) {
this.stopFollowingUser()
}
@ -2120,7 +2170,7 @@ export class Editor extends EventEmitter<TLEventMap> {
* @public
*/
centerOnPoint(point: VecLike, animation?: TLAnimationOptions): this {
if (!this.instanceState.canMoveCamera) return this
if (!this.getInstanceState().canMoveCamera) return this
const {
viewportPageBounds: { width: pw, height: ph },
@ -2168,7 +2218,7 @@ export class Editor extends EventEmitter<TLEventMap> {
* @public
*/
zoomToFit(animation?: TLAnimationOptions): this {
if (!this.instanceState.canMoveCamera) return this
if (!this.getInstanceState().canMoveCamera) return this
const ids = [...this.currentPageShapeIds]
if (ids.length <= 0) return this
@ -2194,7 +2244,7 @@ export class Editor extends EventEmitter<TLEventMap> {
* @public
*/
resetZoom(point = this.viewportScreenCenter, animation?: TLAnimationOptions): this {
if (!this.instanceState.canMoveCamera) return this
if (!this.getInstanceState().canMoveCamera) return this
const { x: cx, y: cy, z: cz } = this.camera
const { x, y } = point
@ -2221,7 +2271,7 @@ export class Editor extends EventEmitter<TLEventMap> {
* @public
*/
zoomIn(point = this.viewportScreenCenter, animation?: TLAnimationOptions): this {
if (!this.instanceState.canMoveCamera) return this
if (!this.getInstanceState().canMoveCamera) return this
const { x: cx, y: cy, z: cz } = this.camera
@ -2259,7 +2309,7 @@ export class Editor extends EventEmitter<TLEventMap> {
* @public
*/
zoomOut(point = this.viewportScreenCenter, animation?: TLAnimationOptions): this {
if (!this.instanceState.canMoveCamera) return this
if (!this.getInstanceState().canMoveCamera) return this
const { x: cx, y: cy, z: cz } = this.camera
@ -2300,7 +2350,7 @@ export class Editor extends EventEmitter<TLEventMap> {
* @public
*/
zoomToSelection(animation?: TLAnimationOptions): this {
if (!this.instanceState.canMoveCamera) return this
if (!this.getInstanceState().canMoveCamera) return this
const { selectionPageBounds } = this
if (!selectionPageBounds) return this
@ -2319,7 +2369,7 @@ export class Editor extends EventEmitter<TLEventMap> {
* @public
*/
panZoomIntoView(ids: TLShapeId[], animation?: TLAnimationOptions): this {
if (!this.instanceState.canMoveCamera) return this
if (!this.getInstanceState().canMoveCamera) return this
if (ids.length <= 0) return this
const selectionBounds = Box2d.Common(compact(ids.map((id) => this.getShapePageBounds(id))))
@ -2379,7 +2429,7 @@ export class Editor extends EventEmitter<TLEventMap> {
* @public
*/
zoomToBounds(bounds: Box2d, targetZoom?: number, animation?: TLAnimationOptions): this {
if (!this.instanceState.canMoveCamera) return this
if (!this.getInstanceState().canMoveCamera) return this
const { viewportScreenBounds } = this
@ -2423,7 +2473,7 @@ export class Editor extends EventEmitter<TLEventMap> {
* @param animation - The animation options.
*/
pan(offset: VecLike, animation?: TLAnimationOptions): this {
if (!this.instanceState.canMoveCamera) return this
if (!this.getInstanceState().canMoveCamera) return this
const { x: cx, y: cy, z: cz } = this.camera
this.setCamera({ x: cx + offset.x / cz, y: cy + offset.y / cz, z: cz }, animation)
return this
@ -2490,7 +2540,7 @@ export class Editor extends EventEmitter<TLEventMap> {
// If we have an existing animation, then stop it; also stop following any user
this.stopCameraAnimation()
if (this.instanceState.followingUserId) {
if (this.getInstanceState().followingUserId) {
this.stopFollowingUser()
}
@ -2532,7 +2582,7 @@ export class Editor extends EventEmitter<TLEventMap> {
speedThreshold?: number
}
): this {
if (!this.instanceState.canMoveCamera) return this
if (!this.getInstanceState().canMoveCamera) return this
this.stopCameraAnimation()
@ -2580,7 +2630,7 @@ export class Editor extends EventEmitter<TLEventMap> {
userId: { eq: userId },
}))
const presence = [...presences.value]
const presence = [...presences.get()]
.sort((a, b) => {
return a.lastActivityTimestamp - b.lastActivityTimestamp
})
@ -2590,7 +2640,7 @@ export class Editor extends EventEmitter<TLEventMap> {
this.batch(() => {
// If we're following someone, stop following them
if (this.instanceState.followingUserId !== null) {
if (this.getInstanceState().followingUserId !== null) {
this.stopFollowingUser()
}
@ -2606,12 +2656,12 @@ export class Editor extends EventEmitter<TLEventMap> {
this.centerOnPoint(presence.cursor, options)
// Highlight the user's cursor
const { highlightedUserIds } = this.instanceState
const { highlightedUserIds } = this.getInstanceState()
this.updateInstanceState({ highlightedUserIds: [...highlightedUserIds, userId] })
// Unhighlight the user's cursor after a few seconds
setTimeout(() => {
const highlightedUserIds = [...this.instanceState.highlightedUserIds]
const highlightedUserIds = [...this.getInstanceState().highlightedUserIds]
const index = highlightedUserIds.indexOf(userId)
if (index < 0) return
highlightedUserIds.splice(index, 1)
@ -2628,7 +2678,7 @@ export class Editor extends EventEmitter<TLEventMap> {
* @public
*/
animateToShape(shapeId: TLShapeId, opts: TLAnimationOptions = DEFAULT_ANIMATION_OPTIONS): this {
if (!this.instanceState.canMoveCamera) return this
if (!this.getInstanceState().canMoveCamera) return this
const activeArea = this.viewportScreenBounds.clone().expandBy(-32)
const viewportAspectRatio = activeArea.width / activeArea.height
@ -2703,7 +2753,7 @@ export class Editor extends EventEmitter<TLEventMap> {
{ squashing: true, ephemeral: true }
)
} else {
if (center && !this.instanceState.followingUserId) {
if (center && !this.getInstanceState().followingUserId) {
// Get the page center before the change, make the change, and restore it
const before = this.viewportPageCenter
this.updateInstanceState(
@ -2733,7 +2783,7 @@ export class Editor extends EventEmitter<TLEventMap> {
* @public
*/
@computed get viewportScreenBounds() {
const { x, y, w, h } = this.instanceState.screenBounds
const { x, y, w, h } = this.getInstanceState().screenBounds
return new Box2d(x, y, w, h)
}
@ -2832,7 +2882,7 @@ export class Editor extends EventEmitter<TLEventMap> {
}
// If the leader is following us, then we can't follow them
if (leaderPresences.value.some((p) => p.followingUserId === thisUserId)) {
if (leaderPresences.get().some((p) => p.followingUserId === thisUserId)) {
return this
}
@ -2851,7 +2901,7 @@ export class Editor extends EventEmitter<TLEventMap> {
const moveTowardsUser = () => {
// Stop following if we can't find the user
const leaderPresence = [...leaderPresences.value]
const leaderPresence = [...leaderPresences.get()]
.sort((a, b) => {
return a.lastActivityTimestamp - b.lastActivityTimestamp
})
@ -2959,7 +3009,7 @@ export class Editor extends EventEmitter<TLEventMap> {
* @public
*/
@computed get cameraState() {
return this._cameraState.value
return this._cameraState.get()
}
// Camera state does two things: first, it allows us to subscribe to whether
@ -3136,7 +3186,7 @@ export class Editor extends EventEmitter<TLEventMap> {
* @public
*/
@computed get renderingBounds() {
return this._renderingBounds.value
return this._renderingBounds.get()
}
/** @internal */
@ -3149,7 +3199,7 @@ export class Editor extends EventEmitter<TLEventMap> {
* @public
*/
@computed get renderingBoundsExpanded() {
return this._renderingBoundsExpanded.value
return this._renderingBoundsExpanded.get()
}
/** @internal */
@ -3203,7 +3253,7 @@ export class Editor extends EventEmitter<TLEventMap> {
* @public
*/
@computed get pages(): TLPage[] {
return this._pages.value.sort(sortByIndex)
return this._pages.get().sort(sortByIndex)
}
/**
@ -3222,7 +3272,7 @@ export class Editor extends EventEmitter<TLEventMap> {
* @public
*/
get currentPageId(): TLPageId {
return this.instanceState.currentPageId
return this.getInstanceState().currentPageId
}
/**
@ -3251,7 +3301,7 @@ export class Editor extends EventEmitter<TLEventMap> {
* @public
*/
get currentPageShapeIds() {
return this._currentPageShapeIds.value
return this._currentPageShapeIds.get()
}
/**
@ -3329,7 +3379,7 @@ export class Editor extends EventEmitter<TLEventMap> {
])
}
this.store.put([{ ...this.instanceState, currentPageId: toId }])
this.store.put([{ ...this.getInstanceState(), currentPageId: toId }])
this.updateRenderingBounds()
},
@ -3338,7 +3388,7 @@ export class Editor extends EventEmitter<TLEventMap> {
// in multiplayer contexts this page might have been deleted
return
}
this.store.put([{ ...this.instanceState, currentPageId: fromId }])
this.store.put([{ ...this.getInstanceState(), currentPageId: fromId }])
this.updateRenderingBounds()
},
@ -3370,7 +3420,7 @@ export class Editor extends EventEmitter<TLEventMap> {
private _updatePage = this.history.createCommand(
'updatePage',
(partial: RequiredKeys<TLPage, 'id'>, historyOptions?: TLCommandHistoryOptions) => {
if (this.instanceState.isReadonly) return null
if (this.getInstanceState().isReadonly) return null
const prev = this.getPage(partial.id)
@ -3415,7 +3465,7 @@ export class Editor extends EventEmitter<TLEventMap> {
private _createPage = this.history.createCommand(
'createPage',
(page: Partial<TLPage>) => {
if (this.instanceState.isReadonly) return null
if (this.getInstanceState().isReadonly) return null
if (this.pages.length >= MAX_PAGES) return null
const { pages } = this
@ -3486,7 +3536,7 @@ export class Editor extends EventEmitter<TLEventMap> {
private _deletePage = this.history.createCommand(
'delete_page',
(id: TLPageId) => {
if (this.instanceState.isReadonly) return null
if (this.getInstanceState().isReadonly) return null
const { pages } = this
if (pages.length === 1) return null
@ -3578,7 +3628,7 @@ export class Editor extends EventEmitter<TLEventMap> {
*/
renamePage(page: TLPageId | TLPage, name: string, historyOptions?: TLCommandHistoryOptions) {
const id = typeof page === 'string' ? page : page.id
if (this.instanceState.isReadonly) return this
if (this.getInstanceState().isReadonly) return this
this.updatePage({ id, name }, historyOptions)
return this
}
@ -3596,7 +3646,7 @@ export class Editor extends EventEmitter<TLEventMap> {
* @public
*/
get assets() {
return this._assets.value
return this._assets.get()
}
/**
@ -3619,7 +3669,7 @@ export class Editor extends EventEmitter<TLEventMap> {
private _createAssets = this.history.createCommand(
'createAssets',
(assets: TLAsset[]) => {
if (this.instanceState.isReadonly) return null
if (this.getInstanceState().isReadonly) return null
if (assets.length <= 0) return null
return { data: { assets } }
@ -3655,7 +3705,7 @@ export class Editor extends EventEmitter<TLEventMap> {
private _updateAssets = this.history.createCommand(
'updateAssets',
(assets: TLAssetPartial[]) => {
if (this.instanceState.isReadonly) return
if (this.getInstanceState().isReadonly) return
if (assets.length <= 0) return
const snapshots: Record<string, TLAsset> = {}
@ -3706,7 +3756,7 @@ export class Editor extends EventEmitter<TLEventMap> {
private _deleteAssets = this.history.createCommand(
'deleteAssets',
(ids: TLAssetId[]) => {
if (this.instanceState.isReadonly) return
if (this.getInstanceState().isReadonly) return
if (ids.length <= 0) return
const prev = compact(ids.map((id) => this.store.get(id)))
@ -4858,7 +4908,7 @@ export class Editor extends EventEmitter<TLEventMap> {
*/
getHighestIndexForParent(parent: TLParentId | TLPage | TLShape): string {
const parentId = typeof parent === 'string' ? parent : parent.id
const children = this._parentIdsToChildIds.value[parentId]
const children = this._parentIdsToChildIds.get()[parentId]
if (!children || children.length === 0) {
return 'a1'
@ -4888,7 +4938,7 @@ export class Editor extends EventEmitter<TLEventMap> {
*/
getSortedChildIdsForParent(parent: TLParentId | TLPage | TLShape): TLShapeId[] {
const parentId = typeof parent === 'string' ? parent : parent.id
const ids = this._parentIdsToChildIds.value[parentId]
const ids = this._parentIdsToChildIds.get()[parentId]
if (!ids) return EMPTY_ARRAY
return this._childIdsCache.get(ids, () => ids)
}
@ -5329,7 +5379,7 @@ export class Editor extends EventEmitter<TLEventMap> {
: (shapes as TLShape[]).map((s) => s.id)
if (ids.length === 0) return this
if (this.instanceState.isReadonly) return this
if (this.getInstanceState().isReadonly) return this
const { currentPageId } = this
@ -5392,7 +5442,7 @@ export class Editor extends EventEmitter<TLEventMap> {
? (shapes as TLShapeId[])
: (shapes as TLShape[]).map((s) => s.id)
if (this.instanceState.isReadonly || ids.length === 0) return this
if (this.getInstanceState().isReadonly || ids.length === 0) return this
let allLocked = true,
allUnlocked = true
@ -5540,7 +5590,7 @@ export class Editor extends EventEmitter<TLEventMap> {
? (shapes as TLShapeId[])
: (shapes as TLShape[]).map((s) => s.id)
if (this.instanceState.isReadonly) return this
if (this.getInstanceState().isReadonly) return this
let shapesToFlip = compact(ids.map((id) => this.getShape(id)))
@ -5609,7 +5659,7 @@ export class Editor extends EventEmitter<TLEventMap> {
typeof shapes[0] === 'string'
? (shapes as TLShapeId[])
: (shapes as TLShape[]).map((s) => s.id)
if (this.instanceState.isReadonly) return this
if (this.getInstanceState().isReadonly) return this
const shapesToStack = compact(
ids
@ -5754,7 +5804,7 @@ export class Editor extends EventEmitter<TLEventMap> {
? (shapes as TLShapeId[])
: (shapes as TLShape[]).map((s) => s.id)
if (this.instanceState.isReadonly) return this
if (this.getInstanceState().isReadonly) return this
if (ids.length < 2) return this
const shapesToPack = compact(
@ -5918,7 +5968,7 @@ export class Editor extends EventEmitter<TLEventMap> {
? (shapes as TLShapeId[])
: (shapes as TLShape[]).map((s) => s.id)
if (this.instanceState.isReadonly) return this
if (this.getInstanceState().isReadonly) return this
if (ids.length < 2) return this
const shapesToAlign = compact(ids.map((id) => this.getShape(id))) // always fresh shapes
@ -6009,7 +6059,7 @@ export class Editor extends EventEmitter<TLEventMap> {
? (shapes as TLShapeId[])
: (shapes as TLShape[]).map((s) => s.id)
if (this.instanceState.isReadonly) return this
if (this.getInstanceState().isReadonly) return this
if (ids.length < 3) return this
const len = ids.length
@ -6100,7 +6150,7 @@ export class Editor extends EventEmitter<TLEventMap> {
? (shapes as TLShapeId[])
: (shapes as TLShape[]).map((s) => s.id)
if (this.instanceState.isReadonly) return this
if (this.getInstanceState().isReadonly) return this
if (ids.length < 2) return this
const shapesToStretch = compact(ids.map((id) => this.getShape(id))) // always fresh shapes
@ -6176,7 +6226,7 @@ export class Editor extends EventEmitter<TLEventMap> {
options: TLResizeShapeOptions = {}
): this {
const id = typeof shape === 'string' ? shape : shape.id
if (this.instanceState.isReadonly) return this
if (this.getInstanceState().isReadonly) return this
if (!Number.isFinite(scale.x)) scale = new Vec2d(1, scale.y)
if (!Number.isFinite(scale.y)) scale = new Vec2d(scale.x, 1)
@ -6474,7 +6524,7 @@ export class Editor extends EventEmitter<TLEventMap> {
private _createShapes = this.history.createCommand(
'createShapes',
(partials: OptionalKeys<TLShapePartial, 'id'>[]) => {
if (this.instanceState.isReadonly) return null
if (this.getInstanceState().isReadonly) return null
if (partials.length <= 0) return null
const { currentPageShapeIds } = this
@ -6627,7 +6677,7 @@ export class Editor extends EventEmitter<TLEventMap> {
).create({
...partial,
index,
opacity: partial.opacity ?? this.instanceState.opacityForNextShape,
opacity: partial.opacity ?? this.getInstanceState().opacityForNextShape,
parentId: partial.parentId ?? focusedGroupId,
props: 'props' in partial ? { ...initialProps, ...partial.props } : initialProps,
})
@ -6800,7 +6850,7 @@ export class Editor extends EventEmitter<TLEventMap> {
if (!Array.isArray(shapes)) {
throw Error('Editor.groupShapes: must provide an array of shapes or shape ids')
}
if (this.instanceState.isReadonly) return this
if (this.getInstanceState().isReadonly) return this
const ids =
typeof shapes[0] === 'string'
@ -6818,7 +6868,7 @@ export class Editor extends EventEmitter<TLEventMap> {
const parentId = this.findCommonAncestor(shapesToGroup) ?? this.currentPageId
// Only group when the select tool is active
if (this.currentToolId !== 'select') return this
if (this.getCurrentToolId() !== 'select') return this
// If not already in idle, cancel the current interaction (get back to idle)
if (!this.isIn('select.idle')) {
@ -6864,11 +6914,11 @@ export class Editor extends EventEmitter<TLEventMap> {
ungroupShapes(_ids: TLShapeId[] | TLShape[]) {
const ids =
typeof _ids[0] === 'string' ? (_ids as TLShapeId[]) : (_ids as TLShape[]).map((s) => s.id)
if (this.instanceState.isReadonly) return this
if (this.getInstanceState().isReadonly) return this
if (ids.length === 0) return this
// Only ungroup when the select tool is active
if (this.currentToolId !== 'select') return this
if (this.getCurrentToolId() !== 'select') return this
// If not already in idle, cancel the current interaction (get back to idle)
if (!this.isIn('select.idle')) {
@ -6979,7 +7029,7 @@ export class Editor extends EventEmitter<TLEventMap> {
_partials: (TLShapePartial | null | undefined)[],
historyOptions?: TLCommandHistoryOptions
) => {
if (this.instanceState.isReadonly) return null
if (this.getInstanceState().isReadonly) return null
const partials = compact(_partials)
@ -7129,7 +7179,7 @@ export class Editor extends EventEmitter<TLEventMap> {
private _deleteShapes = this.history.createCommand(
'delete_shapes',
(ids: TLShapeId[]) => {
if (this.instanceState.isReadonly) return null
if (this.getInstanceState().isReadonly) return null
if (ids.length === 0) return null
const prevSelectedShapeIds = [...this.currentPageState.selectedShapeIds]
@ -7142,7 +7192,7 @@ export class Editor extends EventEmitter<TLEventMap> {
}
const deletedIds = [...allIds]
const arrowBindings = this._arrowBindingsIndex.value
const arrowBindings = this._arrowBindingsIndex.get()
const snapshots = compact(
deletedIds.flatMap((id) => {
const shape = this.getShape(id)
@ -7190,7 +7240,7 @@ export class Editor extends EventEmitter<TLEventMap> {
// For groups, ignore the styles of the group shape and instead include the styles of the
// group's children. These are the shapes that would have their styles changed if the
// user called `setStyle` on the current selection.
const childIds = this._parentIdsToChildIds.value[shape.id]
const childIds = this._parentIdsToChildIds.get()[shape.id]
if (!childIds) return
for (let i = 0, n = childIds.length; i < n; i++) {
@ -7224,7 +7274,7 @@ export class Editor extends EventEmitter<TLEventMap> {
/** @internal */
getStyleForNextShape<T>(style: StyleProp<T>): T {
const value = this.instanceState.stylesForNextShape[style.id]
const value = this.getInstanceState().stylesForNextShape[style.id]
return value === undefined ? style.defaultValue : (value as T)
}
@ -7253,12 +7303,12 @@ export class Editor extends EventEmitter<TLEventMap> {
// If we're in selecting and if we have a selection, return the shared styles from the
// current selection
if (this.isIn('select') && this.selectedShapeIds.length > 0) {
return this._selectionSharedStyles.value
return this._selectionSharedStyles.get()
}
// If the current tool is associated with a shape, return the styles for that shape.
// Otherwise, just return an empty map.
const currentTool = this.root.current.value!
const currentTool = this.root.current.get()!
const styles = new SharedStyleMap()
if (currentTool.shapeType) {
for (const style of this.styleProps[currentTool.shapeType].keys()) {
@ -7308,7 +7358,7 @@ export class Editor extends EventEmitter<TLEventMap> {
if (opacity !== null) return { type: 'shared', value: opacity }
}
return { type: 'shared', value: this.instanceState.opacityForNextShape }
return { type: 'shared', value: this.getInstanceState().opacityForNextShape }
}
/**
@ -7398,9 +7448,7 @@ export class Editor extends EventEmitter<TLEventMap> {
value: T,
historyOptions?: TLCommandHistoryOptions
): this {
const {
instanceState: { stylesForNextShape },
} = this
const stylesForNextShape = this.getInstanceState().stylesForNextShape
this.updateInstanceState(
{ stylesForNextShape: { ...stylesForNextShape, [style.id]: value } },
@ -7748,7 +7796,7 @@ export class Editor extends EventEmitter<TLEventMap> {
preserveIds?: boolean
} = {}
): this {
if (this.instanceState.isReadonly) return this
if (this.getInstanceState().isReadonly) return this
// todo: make this able to support putting content onto any page, not just the current page
@ -8591,7 +8639,7 @@ export class Editor extends EventEmitter<TLEventMap> {
switch (type) {
case 'pinch': {
if (!this.instanceState.canMoveCamera) return
if (!this.getInstanceState().canMoveCamera) return
this._updateInputsFromEvent(info)
switch (info.name) {
@ -8657,7 +8705,7 @@ export class Editor extends EventEmitter<TLEventMap> {
}
}
case 'wheel': {
if (!this.instanceState.canMoveCamera) return
if (!this.getInstanceState().canMoveCamera) return
this._updateInputsFromEvent(info)
@ -8693,7 +8741,7 @@ export class Editor extends EventEmitter<TLEventMap> {
!inputs.isDragging &&
inputs.isPointing &&
originPagePoint.dist(currentPagePoint) >
(this.instanceState.isCoarsePointer ? COARSE_DRAG_DISTANCE : DRAG_DISTANCE) /
(this.getInstanceState().isCoarsePointer ? COARSE_DRAG_DISTANCE : DRAG_DISTANCE) /
this.zoomLevel
) {
inputs.isDragging = true
@ -8725,7 +8773,7 @@ export class Editor extends EventEmitter<TLEventMap> {
inputs.isPointing = true
inputs.isDragging = false
if (this.instanceState.isPenMode) {
if (this.getInstanceState().isPenMode) {
if (!isPen) {
return
}
@ -8737,13 +8785,13 @@ export class Editor extends EventEmitter<TLEventMap> {
if (info.button === 5) {
// Eraser button activates eraser
this._restoreToolId = this.currentToolId
this._restoreToolId = this.getCurrentToolId()
this.complete()
this.setCurrentTool('eraser')
} else if (info.button === 1) {
// Middle mouse pan activates panning
if (!this.inputs.isPanning) {
this._prevCursor = this.instanceState.cursor.type
this._prevCursor = this.getInstanceState().cursor.type
}
this.inputs.isPanning = true
@ -8766,7 +8814,7 @@ export class Editor extends EventEmitter<TLEventMap> {
}
case 'pointer_move': {
// If the user is in pen mode, but the pointer is not a pen, stop here.
if (!isPen && this.instanceState.isPenMode) {
if (!isPen && this.getInstanceState().isPenMode) {
return
}
@ -8781,7 +8829,7 @@ export class Editor extends EventEmitter<TLEventMap> {
!inputs.isDragging &&
inputs.isPointing &&
originPagePoint.dist(currentPagePoint) >
(this.instanceState.isCoarsePointer ? COARSE_DRAG_DISTANCE : DRAG_DISTANCE) /
(this.getInstanceState().isCoarsePointer ? COARSE_DRAG_DISTANCE : DRAG_DISTANCE) /
this.zoomLevel
) {
inputs.isDragging = true
@ -8800,7 +8848,7 @@ export class Editor extends EventEmitter<TLEventMap> {
return
}
if (!isPen && this.instanceState.isPenMode) {
if (!isPen && this.getInstanceState().isPenMode) {
return
}
@ -8879,7 +8927,7 @@ export class Editor extends EventEmitter<TLEventMap> {
// If the space key is pressed (but meta / control isn't!) activate panning
if (!info.ctrlKey && info.code === 'Space') {
if (!this.inputs.isPanning) {
this._prevCursor = this.instanceState.cursor.type
this._prevCursor = this.getInstanceState().cursor.type
}
this.inputs.isPanning = true
@ -8921,7 +8969,7 @@ export class Editor extends EventEmitter<TLEventMap> {
}
// If a pointer event, send the event to the click manager.
if (info.isPen === this.instanceState.isPenMode) {
if (info.isPen === this.getInstanceState().isPenMode) {
switch (info.name) {
case 'pointer_down': {
const otherEvent = this._clickManager.transformPointerDownEvent(info)

View file

@ -12,7 +12,7 @@ export const arrowBindingsIndex = (editor: Editor): Computed<TLArrowBindingsInde
const shapeHistory = store.query.filterHistory('shape')
const arrowQuery = store.query.records('shape', () => ({ type: { eq: 'arrow' as const } }))
function fromScratch() {
const allArrows = arrowQuery.value as TLArrowShape[]
const allArrows = arrowQuery.get() as TLArrowShape[]
const bindings2Arrows: TLArrowBindingsIndex = {}

View file

@ -11,7 +11,7 @@ export const parentsToChildren = (store: TLStore) => {
function fromScratch() {
const result: Parents2Children = {}
const shapeIds = shapeIdsQuery.value
const shapeIds = shapeIdsQuery.get()
const shapes = Array(shapeIds.size) as TLShape[]
shapeIds.forEach((id) => shapes.push(store.get(id)!))

View file

@ -40,7 +40,7 @@ export const deriveShapeIdsInCurrentPage = (store: TLStore, getCurrentPageId: ()
const currentPageId = getCurrentPageId()
lastPageId = currentPageId
return new Set(
[...shapesIndex.value].filter((id) => isShapeInPage(store, currentPageId, store.get(id)!))
[...shapesIndex.get()].filter((id) => isShapeInPage(store, currentPageId, store.get(id)!))
)
}
return computed<Set<TLShapeId>>('_shapeIdsInCurrentPage', (prevValue, lastComputedEpoch) => {

View file

@ -227,7 +227,7 @@ export class ClickManager {
this._clickState !== 'idle' &&
this._clickScreenPoint &&
this._clickScreenPoint.dist(this.editor.inputs.currentScreenPoint) >
(this.editor.instanceState.isCoarsePointer ? COARSE_DRAG_DISTANCE : DRAG_DISTANCE)
(this.editor.getInstanceState().isCoarsePointer ? COARSE_DRAG_DISTANCE : DRAG_DISTANCE)
) {
this.cancelDoubleClickTimeout()
}

View file

@ -116,7 +116,7 @@ describe(HistoryManager, () => {
editor.decrement()
expect(editor.getCount()).toBe(3)
const undos = [...editor.history._undos.value]
const undos = [...editor.history._undos.get()]
const parsedUndos = JSON.parse(JSON.stringify(undos))
editor.history._undos.set(stack(parsedUndos))

View file

@ -34,11 +34,11 @@ export class HistoryManager<
private _commands: Record<string, TLCommandHandler<any>> = {}
get numUndos() {
return this._undos.value.length
return this._undos.get().length
}
get numRedos() {
return this._redos.value.length
return this._redos.get().length
}
createCommand = <Name extends string, Constructor extends CommandFn<any>>(
@ -72,7 +72,7 @@ export class HistoryManager<
})
if (!ephemeral) {
const prev = this._undos.value.head
const prev = this._undos.get().head
if (
squashing &&
prev &&
@ -119,9 +119,9 @@ export class HistoryManager<
this._batchDepth++
if (this._batchDepth === 1) {
transact(() => {
const mostRecentActionId = this._undos.value.head?.id
const mostRecentActionId = this._undos.get().head?.id
fn()
if (mostRecentActionId !== this._undos.value.head?.id) {
if (mostRecentActionId !== this._undos.get().head?.id) {
this.onBatchComplete()
}
})
@ -144,8 +144,8 @@ export class HistoryManager<
redos: Stack<TLHistoryEntry>
) => { undos: Stack<TLHistoryEntry>; redos: Stack<TLHistoryEntry> }
) => {
let undos = this._undos.value
let redos = this._redos.value
let undos = this._undos.get()
let redos = this._redos.get()
this._undos.set(stack())
this._redos.set(stack())
@ -286,7 +286,7 @@ export class HistoryManager<
}
mark = (id = uniqueId(), onUndo = true, onRedo = true) => {
const mostRecent = this._undos.value.head
const mostRecent = this._undos.get().head
// dedupe marks, why not
if (mostRecent && mostRecent.type === 'STOP') {
if (mostRecent.id === id && mostRecent.onUndo === onUndo && mostRecent.onRedo === onRedo) {

View file

@ -213,7 +213,7 @@ export class SnapManager {
private _snapLines = atom<SnapLine[] | undefined>('snapLines', undefined)
get lines() {
return this._snapLines.value ?? (EMPTY_ARRAY as SnapLine[])
return this._snapLines.get() ?? (EMPTY_ARRAY as SnapLine[])
}
clear() {

View file

@ -11,7 +11,7 @@ export class UserPreferencesManager {
updateUserPreferences = (userPreferences: Partial<TLUserPreferences>) => {
this.user.setUserPreferences({
...this.user.userPreferences.value,
...this.user.userPreferences.get(),
...userPreferences,
})
}
@ -29,32 +29,32 @@ export class UserPreferencesManager {
@computed get isDarkMode() {
return (
this.user.userPreferences.value.isDarkMode ??
this.user.userPreferences.get().isDarkMode ??
(this.inferDarkMode ? userPrefersDarkUI() : false)
)
}
@computed get animationSpeed() {
return this.user.userPreferences.value.animationSpeed ?? defaultUserPreferences.animationSpeed
return this.user.userPreferences.get().animationSpeed ?? defaultUserPreferences.animationSpeed
}
@computed get id() {
return this.user.userPreferences.value.id
return this.user.userPreferences.get().id
}
@computed get name() {
return this.user.userPreferences.value.name ?? defaultUserPreferences.name
return this.user.userPreferences.get().name ?? defaultUserPreferences.name
}
@computed get locale() {
return this.user.userPreferences.value.locale ?? defaultUserPreferences.locale
return this.user.userPreferences.get().locale ?? defaultUserPreferences.locale
}
@computed get color() {
return this.user.userPreferences.value.color ?? defaultUserPreferences.color
return this.user.userPreferences.get().color ?? defaultUserPreferences.color
}
@computed get isSnapMode() {
return this.user.userPreferences.value.isSnapMode ?? defaultUserPreferences.isSnapMode
return this.user.userPreferences.get().isSnapMode ?? defaultUserPreferences.isSnapMode
}
}

View file

@ -18,7 +18,7 @@ describe('atom manager', () => {
expect(A.lastChangedEpoch).toBe(2)
manager.b
expect(A.value).toMatchObject({ a: 2, b: 4, c: 3 })
expect(A.get()).toMatchObject({ a: 2, b: 4, c: 3 })
expect(cb).toHaveBeenCalledTimes(2)
})

View file

@ -5,18 +5,18 @@ export function getAtomManager<T extends { [key: string]: any }>(
transform?: (prev: T, next: T) => T
): T {
const update = (value: Partial<T>) => {
const curr = atom.value
const curr = atom.get()
const next = { ...curr, ...value }
const final = transform?.(atom.value, atom.value) ?? next
const final = transform?.(atom.get(), atom.get()) ?? next
atom.set(final)
}
return Object.defineProperties(
{} as T,
Object.keys(atom.value).reduce((acc, key) => {
Object.keys(atom.get()).reduce((acc, key) => {
acc[key as keyof T] = computed(atom, key, {
get() {
return atom.value[key as keyof T]
return atom.get()[key as keyof T]
},
set(value: T[keyof T]) {
update({ [key]: value } as any)

View file

@ -112,7 +112,7 @@ export class Pointing extends StateNode {
this.editor.setSelectedShapes([id])
if (this.editor.instanceState.isToolLocked) {
if (this.editor.getInstanceState().isToolLocked) {
this.parent.transition('idle', {})
} else {
this.editor.setCurrentTool('select.idle')

View file

@ -10,8 +10,8 @@ export class RootState extends StateNode {
switch (info.code) {
case 'KeyZ': {
if (!(info.shiftKey || info.ctrlKey)) {
const currentTool = this.current.value
if (currentTool && currentTool.current.value?.id === 'idle') {
const currentTool = this.current.get()
if (currentTool && currentTool.current.get()?.id === 'idle') {
if (this.children!['zoom']) {
this.editor.setCurrentTool('zoom', { ...info, onInteractionEnd: currentTool.id })
}

View file

@ -28,8 +28,8 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
this.current = atom<StateNode | undefined>('toolState' + this.id, undefined)
this.path = computed('toolPath' + this.id, () => {
const current = this.current.value
return this.id + (current ? `.${current.path.value}` : '')
const current = this.current.get()
return this.id + (current ? `.${current.path.get()}` : '')
})
this.parent = parent ?? ({} as any)
@ -81,7 +81,7 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
for (let i = 0; i < path.length; i++) {
const id = path[i]
const prevChildState = currState.current.value
const prevChildState = currState.current.get()
const nextChildState = currState.children?.[id]
if (!nextChildState) {
@ -103,9 +103,9 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
handleEvent = (info: Exclude<TLEventInfo, TLPinchEventInfo>) => {
const cbName = EVENT_NAME_MAP[info.name]
const x = this.current.value
const x = this.current.get()
this[cbName]?.(info as any)
if (this.current.value === x && this.isActive) {
if (this.current.get() === x && this.isActive) {
x?.handleEvent(info)
}
}
@ -124,13 +124,13 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
this.isActive = false
this.onExit?.(info, from)
if (!this.isActive) {
this.current.value?.exit(info, from)
this.current.get()?.exit(info, from)
}
}
/**
* This is a hack / escape hatch that will tell the editor to
* report a different state as active (in `currentToolId`) when
* report a different state as active (in `getCurrentToolId()`) when
* this state is active. This is usually used when a tool transitions
* to a child of a different state for a certain interaction and then
* returns to the original tool when that interaction completes; and
@ -141,7 +141,7 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
_currentToolIdMask = atom('curent tool id mask', undefined as string | undefined)
@computed get currentToolIdMask() {
return this._currentToolIdMask.value
return this._currentToolIdMask.get()
}
set currentToolIdMask(id: string | undefined) {

View file

@ -40,7 +40,7 @@ export function useCanvasEvents() {
...getPointerInfo(e),
})
if (editor.openMenus.length > 0) {
if (editor.getOpenMenus().length > 0) {
editor.updateInstanceState({
openMenus: [],
})
@ -83,14 +83,14 @@ export function useCanvasEvents() {
function onPointerEnter(e: React.PointerEvent) {
if ((e as any).isKilled) return
if (editor.instanceState.isPenMode && e.pointerType !== 'pen') return
if (editor.getInstanceState().isPenMode && e.pointerType !== 'pen') return
const canHover = e.pointerType === 'mouse' || e.pointerType === 'pen'
editor.updateInstanceState({ isHoveringCanvas: canHover ? true : null })
}
function onPointerLeave(e: React.PointerEvent) {
if ((e as any).isKilled) return
if (editor.instanceState.isPenMode && e.pointerType !== 'pen') return
if (editor.getInstanceState().isPenMode && e.pointerType !== 'pen') return
const canHover = e.pointerType === 'mouse' || e.pointerType === 'pen'
editor.updateInstanceState({ isHoveringCanvas: canHover ? false : null })
}

View file

@ -78,7 +78,7 @@ export function useCursor() {
useQuickReactor(
'useCursor',
() => {
const { type, rotation } = editor.instanceState.cursor
const { type, rotation } = editor.getInstanceState().cursor
if (STATIC_CURSORS.includes(type)) {
container.style.setProperty('--tl-cursor', `var(--tl-cursor-${type})`)

View file

@ -23,7 +23,7 @@ export function useDPRMultiple() {
React.useEffect(() => {
return react('useDPRMultiple', () => {
const dpr = editor.instanceState.devicePixelRatio
const dpr = editor.getInstanceState().devicePixelRatio
container.style.setProperty('--tl-dpr-multiple', nearestMultiple(dpr).toString())
})
}, [editor, container])

View file

@ -9,7 +9,7 @@ export function useDocumentEvents() {
const editor = useEditor()
const container = useContainer()
const isAppFocused = useValue('isFocused', () => editor.instanceState.isFocused, [editor])
const isAppFocused = useValue('isFocused', () => editor.getInstanceState().isFocused, [editor])
useEffect(() => {
if (typeof matchMedia === undefined) return
@ -55,7 +55,7 @@ export function useDocumentEvents() {
if (
e.altKey &&
// todo: When should we allow the alt key to be used? Perhaps states should declare which keys matter to them?
(editor.isIn('zoom') || !editor.root.path.value.endsWith('.idle')) &&
(editor.isIn('zoom') || !editor.root.path.get().endsWith('.idle')) &&
!isFocusingInput()
) {
// On windows the alt key opens the menu bar.
@ -107,7 +107,7 @@ export function useDocumentEvents() {
ctrlKey: e.metaKey || e.ctrlKey,
pointerId: 0,
button: 0,
isPen: editor.instanceState.isPenMode,
isPen: editor.getInstanceState().isPenMode,
target: 'canvas',
}
@ -131,7 +131,7 @@ export function useDocumentEvents() {
}
// Don't do anything if we open menus open
if (editor.openMenus.length > 0) return
if (editor.getOpenMenus().length > 0) return
if (!editor.inputs.keys.has('Escape')) {
editor.inputs.keys.add('Escape')
@ -191,7 +191,7 @@ export function useDocumentEvents() {
ctrlKey: e.metaKey || e.ctrlKey,
pointerId: 0,
button: 0,
isPen: editor.instanceState.isPenMode,
isPen: editor.getInstanceState().isPenMode,
target: 'canvas',
}
editor.dispatch(info)

View file

@ -10,7 +10,7 @@ export function useFocusEvents(autoFocus: boolean) {
if (autoFocus) {
// When autoFocus is true, update the editor state to be focused
// unless it's already focused
if (!editor.instanceState.isFocused) {
if (!editor.getInstanceState().isFocused) {
editor.updateInstanceState({ isFocused: true })
}
@ -24,7 +24,7 @@ export function useFocusEvents(autoFocus: boolean) {
} else {
// When autoFocus is false, update the editor state to be not focused
// unless it's already not focused
if (editor.instanceState.isFocused) {
if (editor.getInstanceState().isFocused) {
editor.updateInstanceState({ isFocused: false })
}
}

View file

@ -81,7 +81,7 @@ export function useGestureEvents(ref: React.RefObject<HTMLDivElement>) {
let pinchState = 'not sure' as 'not sure' | 'zooming' | 'panning'
const onWheel: Handler<'wheel', WheelEvent> = ({ event }) => {
if (!editor.instanceState.isFocused) {
if (!editor.getInstanceState().isFocused) {
return
}

View file

@ -18,7 +18,7 @@ export function usePeerIds() {
const $userIds = useComputed(
'userIds',
() => uniq($presences.value.map((p) => p.userId)).sort(),
() => uniq($presences.get().map((p) => p.userId)).sort(),
{ isEqual: (a, b) => a.join(',') === b.join?.(',') },
[$presences]
)

View file

@ -20,7 +20,8 @@ export function usePresence(userId: string): TLInstancePresence | null {
const latestPresence = useValue(
`latestPresence:${userId}`,
() => {
return $presences.value
return $presences
.get()
.slice()
.sort((a, b) => b.lastActivityTimestamp - a.lastActivityTimestamp)[0]
},

View file

@ -32,16 +32,16 @@ beforeEach(() => {
describe('current tool id mask', () => {
it('starts with the correct tool id', () => {
expect(editor.currentToolId).toBe('A')
expect(editor.getCurrentToolId()).toBe('A')
})
it('updates the current tool id', () => {
editor.setCurrentTool('B')
expect(editor.currentToolId).toBe('B')
expect(editor.getCurrentToolId()).toBe('B')
})
it('masks the current tool id', () => {
editor.setCurrentTool('C')
expect(editor.currentToolId).toBe('A')
expect(editor.getCurrentToolId()).toBe('A')
})
})

View file

@ -62,7 +62,7 @@ declare global {
if (typeof window !== 'undefined') {
window.tldrawLog = (message: any) => {
debugFlags.logMessages.set(debugFlags.logMessages.value.concat(message))
debugFlags.logMessages.set(debugFlags.logMessages.get().concat(message))
}
}
@ -82,7 +82,7 @@ if (typeof window !== 'undefined') {
if (typeof Element !== 'undefined') {
const nativeElementRemoveChild = Element.prototype.removeChild
react('element removal logging', () => {
if (debugFlags.elementRemovalLogging.value) {
if (debugFlags.elementRemovalLogging.get()) {
Element.prototype.removeChild = function <T extends Node>(this: any, child: Node): T {
console.warn('[tldraw] removing child:', child)
return nativeElementRemoveChild.call(this, child) as T
@ -130,7 +130,7 @@ function createDebugValueBase<T>(def: DebugFlagDef<T>): DebugFlag<T> {
if (typeof window !== 'undefined') {
if (def.shouldStoreForSession) {
react(`debug:${def.name}`, () => {
const currentValue = valueAtom.value
const currentValue = valueAtom.get()
try {
if (currentValue === defaultValue) {
window.sessionStorage.removeItem(`tldraw_debug:${def.name}`)
@ -145,7 +145,7 @@ function createDebugValueBase<T>(def: DebugFlagDef<T>): DebugFlag<T> {
Object.defineProperty(window, `tldraw${def.name.replace(/^[a-z]/, (l) => l.toUpperCase())}`, {
get() {
return valueAtom.value
return valueAtom.get()
},
set(newValue) {
valueAtom.set(newValue)

View file

@ -37,7 +37,7 @@ export function loopToHtmlElement(elm: Element): HTMLElement {
*/
export function preventDefault(event: React.BaseSyntheticEvent | Event) {
event.preventDefault()
if (debugFlags.preventDefaultLogging.value) {
if (debugFlags.preventDefaultLogging.get()) {
console.warn('preventDefault called on event:', event)
}
}
@ -48,11 +48,11 @@ export function setPointerCapture(
event: React.PointerEvent<Element> | PointerEvent
) {
element.setPointerCapture(event.pointerId)
if (debugFlags.pointerCaptureTracking.value) {
const trackingObj = debugFlags.pointerCaptureTrackingObject.value
if (debugFlags.pointerCaptureTracking.get()) {
const trackingObj = debugFlags.pointerCaptureTrackingObject.get()
trackingObj.set(element, (trackingObj.get(element) ?? 0) + 1)
}
if (debugFlags.pointerCaptureLogging.value) {
if (debugFlags.pointerCaptureLogging.get()) {
console.warn('setPointerCapture called on element:', element, event)
}
}
@ -67,8 +67,8 @@ export function releasePointerCapture(
}
element.releasePointerCapture(event.pointerId)
if (debugFlags.pointerCaptureTracking.value) {
const trackingObj = debugFlags.pointerCaptureTrackingObject.value
if (debugFlags.pointerCaptureTracking.get()) {
const trackingObj = debugFlags.pointerCaptureTrackingObject.get()
if (trackingObj.get(element) === 1) {
trackingObj.delete(element)
} else if (trackingObj.has(element)) {
@ -77,7 +77,7 @@ export function releasePointerCapture(
console.warn('Release without capture')
}
}
if (debugFlags.pointerCaptureLogging.value) {
if (debugFlags.pointerCaptureLogging.get()) {
console.warn('releasePointerCapture called on element:', element, event)
}
}

View file

@ -359,7 +359,7 @@ export class TLLocalSyncClient {
snapshot: this.store.serialize(),
didCancel: () => this.didDispose,
sessionId: this.sessionId,
sessionStateSnapshot: this.$sessionStateSnapshot.value,
sessionStateSnapshot: this.$sessionStateSnapshot.get(),
})
} else {
const diffs = squashRecordDiffs(
@ -371,7 +371,7 @@ export class TLLocalSyncClient {
schema: this.store.schema,
didCancel: () => this.didDispose,
sessionId: this.sessionId,
sessionStateSnapshot: this.$sessionStateSnapshot.value,
sessionStateSnapshot: this.$sessionStateSnapshot.get(),
})
}
this.didLastWriteError = false

View file

@ -114,10 +114,12 @@ export interface Signal<Value, Diff = unknown> {
__unsafe__getWithoutCapture(): Value;
// @internal (undocumented)
children: ArraySet<Child>;
get(): Value;
getDiffSince(epoch: number): Diff[] | RESET_VALUE;
lastChangedEpoch: number;
name: string;
readonly value: Value;
// @deprecated (undocumented)
value: Value;
}
// @public

View file

@ -2139,6 +2139,34 @@
"parameters": [],
"name": "__unsafe__getWithoutCapture"
},
{
"kind": "MethodSignature",
"canonicalReference": "@tldraw/state!Signal#get:member(1)",
"docComment": "/**\n * The current value of the signal. This is a reactive value, and will update when the signal changes. Any computed signal that depends on this signal will be lazily recomputed if this signal changes. Any effect that depends on this signal will be rescheduled if this signal changes.\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "get(): "
},
{
"kind": "Content",
"text": "Value"
},
{
"kind": "Content",
"text": ";"
}
],
"isOptional": false,
"returnTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
},
"releaseTag": "Public",
"overloadIndex": 1,
"parameters": [],
"name": "get"
},
{
"kind": "MethodSignature",
"canonicalReference": "@tldraw/state!Signal#getDiffSince:member(1)",
@ -2246,11 +2274,11 @@
{
"kind": "PropertySignature",
"canonicalReference": "@tldraw/state!Signal#value:member",
"docComment": "/**\n * The current value of the signal. This is a reactive value, and will update when the signal changes. Any computed signal that depends on this signal will be lazily recomputed if this signal changes. Any effect that depends on this signal will be rescheduled if this signal changes.\n */\n",
"docComment": "/**\n * @deprecated\n *\n * Use [[Signal.get]] instead.\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "readonly value: "
"text": "value: "
},
{
"kind": "Content",
@ -2261,7 +2289,7 @@
"text": ";"
}
],
"isReadonly": true,
"isReadonly": false,
"isOptional": false,
"releaseTag": "Public",
"name": "value",

View file

@ -1,9 +1,10 @@
import { ArraySet } from './ArraySet'
import { HistoryBuffer } from './HistoryBuffer'
import { maybeCaptureParent } from './capture'
import { EMPTY_ARRAY, equals } from './helpers'
import { HistoryBuffer } from './HistoryBuffer'
import { advanceGlobalEpoch, atomDidChange, globalEpoch } from './transactions'
import { Child, ComputeDiff, RESET_VALUE, Signal } from './types'
import { logDotValueWarning } from './warnings'
/**
* The options to configure an atom, passed into the [[atom]] function.
@ -99,11 +100,19 @@ export class _Atom<Value, Diff = unknown> implements Atom<Value, Diff> {
return this.current
}
get value() {
get() {
maybeCaptureParent(this)
return this.current
}
/**
* @deprecated Use [[Atom.get]] instead.
*/
get value() {
logDotValueWarning()
return this.get()
}
set(value: Value, diff?: Diff): Value {
// If the value has not changed, do nothing.
if (this.isEqual?.(this.current, value) ?? equals(this.current, value)) {

View file

@ -6,6 +6,7 @@ import { GLOBAL_START_EPOCH } from './constants'
import { EMPTY_ARRAY, equals, haveParentsChanged } from './helpers'
import { globalEpoch } from './transactions'
import { Child, ComputeDiff, RESET_VALUE, Signal } from './types'
import { logComputedGetterWarning, logDotValueWarning } from './warnings'
const UNINITIALIZED = Symbol('UNINITIALIZED')
/**
@ -202,16 +203,24 @@ export class _Computed<Value, Diff = unknown> implements Computed<Value, Diff> {
}
}
get value(): Value {
get(): Value {
const value = this.__unsafe__getWithoutCapture()
maybeCaptureParent(this)
return value
}
/**
* @deprecated Use [[get]] instead.
*/
get value() {
logDotValueWarning()
return this.get()
}
getDiffSince(epoch: number): RESET_VALUE | Diff[] {
// need to call .value to ensure both that this derivation is up to date
// and that tracking happens correctly
this.value
this.get()
if (epoch >= this.lastChangedEpoch) {
return EMPTY_ARRAY
@ -221,11 +230,53 @@ export class _Computed<Value, Diff = unknown> implements Computed<Value, Diff> {
}
}
function computedMethodAnnotation(
options: ComputedOptions<any, any> = {},
_target: any,
key: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value
const derivationKey = Symbol.for('__@tldraw/state__computed__' + key)
descriptor.value = function (this: any) {
let d = this[derivationKey] as _Computed<any> | undefined
if (!d) {
d = new _Computed(key, originalMethod!.bind(this) as any, options)
Object.defineProperty(this, derivationKey, {
enumerable: false,
configurable: false,
writable: false,
value: d,
})
}
return d.get()
}
descriptor.value[isComputedMethodKey] = true
return descriptor
}
function computedAnnotation(
options: ComputedOptions<any, any> = {},
_target: any,
key: string,
descriptor: PropertyDescriptor
) {
if (descriptor.get) {
logComputedGetterWarning()
return computedGetterAnnotation(options, _target, key, descriptor)
} else {
return computedMethodAnnotation(options, _target, key, descriptor)
}
}
function computedGetterAnnotation(
options: ComputedOptions<any, any> = {},
_target: any,
key: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.get
const derivationKey = Symbol.for('__@tldraw/state__computed__' + key)
@ -242,12 +293,14 @@ function computedAnnotation(
value: d,
})
}
return d.value
return d.get()
}
return descriptor
}
const isComputedMethodKey = '@@__isComputedMethod__@@'
/**
* Retrieves the underlying computed instance for a given property created with the [[computed]]
* decorator.
@ -278,12 +331,15 @@ export function getComputedInstance<Obj extends object, Prop extends keyof Obj>(
obj: Obj,
propertyName: Prop
): Computed<Obj[Prop]> {
// deref to make sure it exists first
const key = Symbol.for('__@tldraw/state__computed__' + propertyName.toString())
let inst = obj[key as keyof typeof obj] as _Computed<Obj[Prop]> | undefined
if (!inst) {
// deref to make sure it exists first
obj[propertyName]
const val = obj[propertyName]
if (typeof val === 'function' && (val as any)[isComputedMethodKey]) {
val.call(obj)
}
inst = obj[key as keyof typeof obj] as _Computed<Obj[Prop]> | undefined
}
return inst as any

View file

@ -6,7 +6,7 @@ describe(EffectScheduler, () => {
const a = atom('a', 1)
let numReactions = 0
const scheduler = new EffectScheduler('test', () => {
a.value
a.get()
numReactions++
})
scheduler.attach()
@ -26,7 +26,7 @@ describe(EffectScheduler, () => {
const a = atom('a', 1)
let numReactions = 0
const scheduler = new EffectScheduler('test', () => {
a.value
a.get()
numReactions++
})
scheduler.attach()
@ -51,7 +51,7 @@ describe(EffectScheduler, () => {
const scheduler = new EffectScheduler(
'test',
() => {
a.value
a.get()
numReactions++
},
{

View file

@ -7,14 +7,14 @@ describe('atoms', () => {
it('contain data', () => {
const a = atom('', 1)
expect(a.value).toBe(1)
expect(a.get()).toBe(1)
})
it('can be updated', () => {
const a = atom('', 1)
a.set(2)
expect(a.value).toBe(2)
expect(a.get()).toBe(2)
})
it('will not advance the global epoch on creation', () => {
const startEpoch = globalEpoch
@ -115,7 +115,7 @@ describe('atoms', () => {
a.update((value) => value + 1)
expect(a.value).toBe(2)
expect(a.get()).toBe(2)
expect(globalEpoch).toBe(startEpoch + 1)
})
it('supports passing diffs in .set', () => {
@ -159,7 +159,7 @@ describe('reacting to atoms', () => {
let val = 0
const r = reactor('', () => {
val = a.value
val = a.get()
})
expect(val).toBe(0)
@ -177,7 +177,7 @@ describe('reacting to atoms', () => {
a.set(2342)
expect(val).toBe(939)
expect(a.value).toBe(2342)
expect(a.get()).toBe(2342)
})
})
@ -189,11 +189,11 @@ test('isEqual can provide custom equality checks', () => {
a.set(bar)
expect(a.value).toBe(bar)
expect(a.get()).toBe(bar)
const b = atom('b', foo, { isEqual: (a, b) => a.hello === b.hello })
b.set(bar)
expect(b.value).toBe(foo)
expect(b.get()).toBe(foo)
})

View file

@ -172,7 +172,7 @@ describe(unsafe__withoutCapture, () => {
const atomC = atom('c', 1)
const child = computed('', () => {
return atomA.value + atomB.value + unsafe__withoutCapture(() => atomC.value)
return atomA.get() + atomB.get() + unsafe__withoutCapture(() => atomC.get())
})
let lastValue: number | undefined
@ -180,7 +180,7 @@ describe(unsafe__withoutCapture, () => {
react('', () => {
numReactions++
lastValue = child.value
lastValue = child.get()
})
expect(lastValue).toBe(3)
@ -213,7 +213,7 @@ describe(unsafe__withoutCapture, () => {
react('', () => {
numReactions++
lastValue = atomA.value + atomB.value + unsafe__withoutCapture(() => atomC.value)
lastValue = atomA.get() + atomB.get() + unsafe__withoutCapture(() => atomC.get())
})
expect(lastValue).toBe(3)

View file

@ -17,9 +17,9 @@ describe('derivations', () => {
expect(derive).toHaveBeenCalledTimes(0)
expect(derivation.value).toBe(1)
expect(derivation.value).toBe(1)
expect(derivation.value).toBe(1)
expect(derivation.get()).toBe(1)
expect(derivation.get()).toBe(1)
expect(derivation.get()).toBe(1)
expect(derive).toHaveBeenCalledTimes(1)
@ -28,13 +28,13 @@ describe('derivations', () => {
advanceGlobalEpoch()
advanceGlobalEpoch()
expect(derivation.value).toBe(1)
expect(derivation.value).toBe(1)
expect(derivation.value).toBe(1)
expect(derivation.get()).toBe(1)
expect(derivation.get()).toBe(1)
expect(derivation.get()).toBe(1)
advanceGlobalEpoch()
advanceGlobalEpoch()
expect(derivation.value).toBe(1)
expect(derivation.value).toBe(1)
expect(derivation.get()).toBe(1)
expect(derivation.get()).toBe(1)
expect(derive).toHaveBeenCalledTimes(1)
@ -45,18 +45,18 @@ describe('derivations', () => {
it('will update when parent atoms update', () => {
const a = atom('', 1)
const double = jest.fn(() => a.value * 2)
const double = jest.fn(() => a.get() * 2)
const derivation = computed('', double)
const startEpoch = globalEpoch
expect(double).toHaveBeenCalledTimes(0)
expect(derivation.value).toBe(2)
expect(derivation.get()).toBe(2)
expect(double).toHaveBeenCalledTimes(1)
expect(derivation.lastChangedEpoch).toBe(startEpoch)
expect(derivation.value).toBe(2)
expect(derivation.value).toBe(2)
expect(derivation.get()).toBe(2)
expect(derivation.get()).toBe(2)
expect(double).toHaveBeenCalledTimes(1)
expect(derivation.lastChangedEpoch).toBe(startEpoch)
@ -66,12 +66,12 @@ describe('derivations', () => {
expect(double).toHaveBeenCalledTimes(1)
expect(derivation.lastChangedEpoch).toBe(startEpoch)
expect(derivation.value).toBe(4)
expect(derivation.get()).toBe(4)
expect(double).toHaveBeenCalledTimes(2)
expect(derivation.lastChangedEpoch).toBe(nextEpoch)
expect(derivation.value).toBe(4)
expect(derivation.get()).toBe(4)
expect(double).toHaveBeenCalledTimes(2)
expect(derivation.lastChangedEpoch).toBe(nextEpoch)
@ -81,7 +81,7 @@ describe('derivations', () => {
unrelatedAtom.set(3)
unrelatedAtom.set(5)
expect(derivation.value).toBe(4)
expect(derivation.get()).toBe(4)
expect(double).toHaveBeenCalledTimes(2)
expect(derivation.lastChangedEpoch).toBe(nextEpoch)
})
@ -90,14 +90,14 @@ describe('derivations', () => {
const startEpoch = globalEpoch
const a = atom('', 1)
const derivation = computed('', () => a.value * 2, {
const derivation = computed('', () => a.get() * 2, {
historyLength: 3,
computeDiff: (a, b) => {
return b - a
},
})
derivation.value
derivation.get()
expect(derivation.getDiffSince(startEpoch)).toHaveLength(0)
@ -123,37 +123,37 @@ describe('derivations', () => {
const a = atom('', 1)
const floor = jest.fn((n: number) => Math.floor(n))
const derivation = computed('', () => floor(a.value), {
const derivation = computed('', () => floor(a.get()), {
historyLength: 3,
computeDiff: (a, b) => {
return b - a
},
})
expect(derivation.value).toBe(1)
expect(derivation.get()).toBe(1)
expect(derivation.getDiffSince(startEpoch)).toHaveLength(0)
a.set(1.2)
expect(derivation.value).toBe(1)
expect(derivation.get()).toBe(1)
expect(derivation.getDiffSince(startEpoch)).toHaveLength(0)
expect(floor).toHaveBeenCalledTimes(2)
a.set(1.5)
expect(derivation.value).toBe(1)
expect(derivation.get()).toBe(1)
expect(derivation.getDiffSince(startEpoch)).toHaveLength(0)
expect(floor).toHaveBeenCalledTimes(3)
a.set(1.9)
expect(derivation.value).toBe(1)
expect(derivation.get()).toBe(1)
expect(derivation.getDiffSince(startEpoch)).toHaveLength(0)
expect(floor).toHaveBeenCalledTimes(4)
a.set(2.3)
expect(derivation.value).toBe(2)
expect(derivation.get()).toBe(2)
expect(derivation.getDiffSince(startEpoch)).toEqual([+1])
expect(floor).toHaveBeenCalledTimes(5)
})
@ -162,15 +162,15 @@ describe('derivations', () => {
const startEpoch = globalEpoch
const a = atom('', 1)
const double = jest.fn(() => a.value * 2)
const double = jest.fn(() => a.get() * 2)
const derivation = computed('', double)
derivation.value
derivation.get()
expect(getLastCheckedEpoch(derivation)).toEqual(startEpoch)
advanceGlobalEpoch()
derivation.value
derivation.get()
expect(getLastCheckedEpoch(derivation)).toBeGreaterThan(startEpoch)
@ -179,16 +179,16 @@ describe('derivations', () => {
it('receives UNINTIALIZED as the previousValue the first time it computes', () => {
const a = atom('', 1)
const double = jest.fn((_prevValue) => a.value * 2)
const double = jest.fn((_prevValue) => a.get() * 2)
const derivation = computed('', double)
expect(derivation.value).toBe(2)
expect(derivation.get()).toBe(2)
expect(isUninitialized(double.mock.calls[0][0])).toBe(true)
a.set(2)
expect(derivation.value).toBe(4)
expect(derivation.get()).toBe(4)
expect(isUninitialized(double.mock.calls[1][0])).toBe(false)
expect(double.mock.calls[1][0]).toBe(2)
})
@ -197,17 +197,17 @@ describe('derivations', () => {
const a = atom('', 1)
const double = jest.fn((_prevValue, lastChangedEpoch) => {
expect(lastChangedEpoch).toBe(derivation.lastChangedEpoch)
return a.value * 2
return a.get() * 2
})
const derivation = computed('', double)
expect(derivation.value).toBe(2)
expect(derivation.get()).toBe(2)
const startEpoch = globalEpoch
a.set(2)
expect(derivation.value).toBe(4)
expect(derivation.get()).toBe(4)
expect(derivation.lastChangedEpoch).toBeGreaterThan(startEpoch)
expect(double).toHaveBeenCalledTimes(2)
@ -221,13 +221,13 @@ describe('derivations', () => {
let numTimesComputed = 0
const fullName = computed('', () => {
numTimesComputed++
return `${firstName.value} ${lastName.value}`
return `${firstName.get()} ${lastName.get()}`
})
let numTimesReacted = 0
let name = ''
const r = reactor('', () => {
name = fullName.value
name = fullName.get()
numTimesReacted++
})
@ -263,7 +263,7 @@ describe('derivations', () => {
expect(numTimesComputed).toBe(2)
expect(numTimesReacted).toBe(2)
expect(name).toBe('Jane Doe')
expect(fullName.value).toBe('Wilbur Jones')
expect(fullName.get()).toBe('Wilbur Jones')
expect(numTimesComputed).toBe(3)
expect(numTimesReacted).toBe(2)
@ -279,23 +279,23 @@ describe('derivations', () => {
const firstName = atom('', 'John')
const lastName = atom('', 'Doe')
const fullName = computed('', () => `${firstName.value} ${lastName.value}`)
const fullName = computed('', () => `${firstName.get()} ${lastName.get()}`)
transaction((rollback) => {
firstName.set('Jane')
lastName.set('Jones')
expect(fullName.value).toBe('Jane Jones')
expect(fullName.get()).toBe('Jane Jones')
rollback()
})
expect(fullName.value).toBe('John Doe')
expect(fullName.get()).toBe('John Doe')
})
it('will add history items if a transaction is aborted', () => {
const a = atom('', 1)
const b = atom('', 1)
const c = computed('', () => a.value + b.value, {
const c = computed('', () => a.get() + b.get(), {
historyLength: 3,
computeDiff: (a, b) => b - a,
})
@ -317,7 +317,7 @@ describe('derivations', () => {
const a = atom('', 1)
const b = atom('', 1)
const c = computed('', () => a.value + b.value, {
const c = computed('', () => a.get() + b.get(), {
historyLength: 3,
computeDiff: (a, b) => b - a,
})
@ -341,7 +341,7 @@ function getIncrementalRecordMapper<In, Out>(
mapper: (t: In, k: string) => Out
): Computed<Record<string, Out>> {
function computeFromScratch() {
const input = obj.value
const input = obj.get()
return Object.fromEntries(Object.entries(input).map(([k, v]) => [k, mapper(v, k)]))
}
return computed('', (previousValue, lastComputedEpoch) => {
@ -356,7 +356,7 @@ function getIncrementalRecordMapper<In, Out>(
return previousValue
}
const newUpstream = obj.value
const newUpstream = obj.get()
const result = { ...previousValue } as Record<string, Out>
@ -442,7 +442,7 @@ describe('incremental derivations', () => {
const doubledNodes = getIncrementalRecordMapper(nodes, mapper)
expect(doubledNodes.value).toEqual({
expect(doubledNodes.get()).toEqual({
a: 2,
b: 4,
c: 6,
@ -453,7 +453,7 @@ describe('incremental derivations', () => {
nodes.update((ns) => ({ ...ns, a: 10 }))
expect(doubledNodes.value).toEqual({
expect(doubledNodes.get()).toEqual({
a: 20,
b: 4,
c: 6,
@ -466,7 +466,7 @@ describe('incremental derivations', () => {
// remove d
nodes.update(({ d: _d, ...others }) => others)
expect(doubledNodes.value).toEqual({
expect(doubledNodes.get()).toEqual({
a: 20,
b: 4,
c: 6,
@ -476,7 +476,7 @@ describe('incremental derivations', () => {
nodes.update((ns) => ({ ...ns, f: 50, g: 60 }))
expect(doubledNodes.value).toEqual({
expect(doubledNodes.get()).toEqual({
a: 20,
b: 4,
c: 6,
@ -486,9 +486,9 @@ describe('incremental derivations', () => {
})
expect(mapper).toHaveBeenCalledTimes(8)
nodes.set({ ...nodes.value })
nodes.set({ ...nodes.get() })
// no changes so no new calls to mapper
expect(doubledNodes.value).toEqual({
expect(doubledNodes.get()).toEqual({
a: 20,
b: 4,
c: 6,
@ -510,7 +510,7 @@ describe('incremental derivations', () => {
// nothing was called because we didn't deref yet
expect(mapper).toHaveBeenCalledTimes(8)
expect(doubledNodes.value).toEqual({
expect(doubledNodes.get()).toEqual({
a: 8,
b: 18,
c: 34,
@ -527,18 +527,18 @@ describe('computed as a decorator', () => {
class Foo {
a = atom('a', 1)
@computed
get b() {
return this.a.value * 2
getB() {
return this.a.get() * 2
}
}
const foo = new Foo()
expect(foo.b).toBe(2)
expect(foo.getB()).toBe(2)
foo.a.set(2)
expect(foo.b).toBe(4)
expect(foo.getB()).toBe(4)
})
it('can be used to decorate a class with custom properties', () => {
@ -547,20 +547,20 @@ describe('computed as a decorator', () => {
a = atom('a', 1)
@computed({ isEqual: (a, b) => a.b === b.b })
get b() {
getB() {
numComputations++
return { b: this.a.value * this.a.value }
return { b: this.a.get() * this.a.get() }
}
}
const foo = new Foo()
const firstVal = foo.b
const firstVal = foo.getB()
expect(firstVal).toEqual({ b: 1 })
foo.a.set(-1)
const secondVal = foo.b
const secondVal = foo.getB()
expect(secondVal).toEqual({ b: 1 })
expect(firstVal).toBe(secondVal)
@ -574,14 +574,14 @@ describe(getComputedInstance, () => {
a = atom('a', 1)
@computed({ isEqual: (a, b) => a.b === b.b })
get b() {
return { b: this.a.value * this.a.value }
getB() {
return { b: this.a.get() * this.a.get() }
}
}
const foo = new Foo()
const bInst = getComputedInstance(foo, 'b')
const bInst = getComputedInstance(foo, 'getB')
expect(bInst).toBeDefined()
expect(bInst).toBeInstanceOf(_Computed)
@ -593,18 +593,18 @@ describe('computed isEqual', () => {
const isEqual = jest.fn((a, b) => a === b)
const a = atom('a', 1)
const b = computed('b', () => a.value * 2, { isEqual })
const b = computed('b', () => a.get() * 2, { isEqual })
expect(b.value).toBe(2)
expect(b.get()).toBe(2)
expect(isEqual).not.toHaveBeenCalled()
expect(b.value).toBe(2)
expect(b.get()).toBe(2)
expect(isEqual).not.toHaveBeenCalled()
a.set(2)
expect(b.value).toBe(4)
expect(b.get()).toBe(4)
expect(isEqual).toHaveBeenCalledTimes(1)
expect(b.value).toBe(4)
expect(b.get()).toBe(4)
expect(isEqual).toHaveBeenCalledTimes(1)
})
})

View file

@ -67,7 +67,7 @@ type Letter = (typeof LETTERS)[number]
const unpack = (value: unknown): Letter => {
if (isComputed(value) || isAtom(value)) {
return unpack(value.value) as Letter
return unpack(value.get()) as Letter
}
return value as Letter
}
@ -307,15 +307,15 @@ class Test {
break
}
case 'deref_atom_in_derivation': {
this.systemState.atomsInDerivations[op.id].value
this.systemState.atomsInDerivations[op.id].get()
break
}
case 'deref_derivation': {
this.systemState.derivations[op.id].derivation.value
this.systemState.derivations[op.id].derivation.get()
break
}
case 'deref_derivation_in_derivation': {
this.systemState.derivationsInDerivations[op.id].value
this.systemState.derivationsInDerivations[op.id].get()
break
}
case 'update_atom_in_atom': {

View file

@ -6,7 +6,7 @@ describe('reactors', () => {
it('can be started and stopped ', () => {
const a = atom('', 1)
const r = reactor('', () => {
a.value
a.get()
})
expect(r.scheduler.isActivelyListening).toBe(false)
r.start()
@ -20,7 +20,7 @@ describe('reactors', () => {
it('can not set atom values directly yet', () => {
const a = atom('', 1)
const r = reactor('', () => {
if (a.value < +Infinity) {
if (a.get() < +Infinity) {
a.update((a) => a + 1)
}
})
@ -34,8 +34,8 @@ describe('reactors', () => {
const atomB = atom('', 1)
const react = jest.fn(() => {
atomA.value
atomB.value
atomA.get()
atomB.get()
})
const r = reactor('', react)
@ -53,7 +53,7 @@ describe('reactors', () => {
it('will not react if stopped', () => {
const a = atom('', 1)
const react = jest.fn(() => {
a.value
a.get()
})
const r = reactor('', react)
@ -66,7 +66,7 @@ describe('reactors', () => {
const a = atom('', 1)
const react = jest
.fn(() => {
a.value
a.get()
})
.mockName('react')
const r = reactor('', react)
@ -85,7 +85,7 @@ describe('stopping', () => {
const a = atom('', 1)
const rfn = jest.fn(() => {
a.value
a.get()
})
const r = reactor('', rfn)
@ -120,7 +120,7 @@ test('.start() by default does not trigger a reaction if nothing has changed', (
const a = atom('', 1)
const rfn = jest.fn(() => {
a.value
a.get()
})
const r = reactor('', rfn)
@ -139,7 +139,7 @@ test('.start({force: true}) will trigger a reaction even if nothing has changed'
const a = atom('', 1)
const rfn = jest.fn(() => {
a.value
a.get()
})
const r = reactor('', rfn)

View file

@ -11,14 +11,14 @@ describe('transactions', () => {
let numTimesComputed = 0
const fullName = computed('', () => {
numTimesComputed++
return `${firstName.value} ${lastName.value}`
return `${firstName.get()} ${lastName.get()}`
})
let numTimesReacted = 0
let name = ''
react('', () => {
name = fullName.value
name = fullName.get()
numTimesReacted++
})
@ -35,7 +35,7 @@ describe('transactions', () => {
expect(numTimesComputed).toBe(1)
expect(numTimesReacted).toBe(1)
expect(name).toBe('John Doe')
expect(fullName.value).toBe('Wilbur Jones')
expect(fullName.get()).toBe('Wilbur Jones')
expect(numTimesComputed).toBe(2)
expect(numTimesReacted).toBe(1)
@ -48,7 +48,7 @@ describe('transactions', () => {
expect(numTimesComputed).toBe(3)
expect(numTimesReacted).toBe(2)
expect(fullName.value).toBe('John Doe')
expect(fullName.get()).toBe('John Doe')
expect(name).toBe('John Doe')
})
@ -72,8 +72,8 @@ describe('transactions', () => {
rollback()
})
expect(atomA.value).toBe(0)
expect(atomB.value).toBe(0)
expect(atomA.get()).toBe(0)
expect(atomB.get()).toBe(0)
transaction((rollback) => {
atomA.set(1)
@ -90,8 +90,8 @@ describe('transactions', () => {
rollback()
})
expect(atomA.value).toBe(0)
expect(atomB.value).toBe(0)
expect(atomA.get()).toBe(0)
expect(atomB.get()).toBe(0)
transaction((rollback) => {
atomA.set(1)
@ -107,8 +107,8 @@ describe('transactions', () => {
rollback()
})
expect(atomA.value).toBe(0)
expect(atomB.value).toBe(0)
expect(atomA.get()).toBe(0)
expect(atomB.get()).toBe(0)
transaction(() => {
atomA.set(1)
@ -125,8 +125,8 @@ describe('transactions', () => {
})
})
expect(atomA.value).toBe(1)
expect(atomB.value).toBe(-1)
expect(atomA.get()).toBe(1)
expect(atomB.get()).toBe(-1)
transaction(() => {
atomA.set(1)
@ -142,8 +142,8 @@ describe('transactions', () => {
})
})
expect(atomA.value).toBe(2)
expect(atomB.value).toBe(-2)
expect(atomA.get()).toBe(2)
expect(atomB.get()).toBe(-2)
})
it('should restore the original even if an inner commits', () => {
@ -156,7 +156,7 @@ describe('transactions', () => {
rollback()
})
expect(a.value).toBe('a')
expect(a.get()).toBe('a')
})
})
@ -173,7 +173,7 @@ describe('transact', () => {
expect(e.message).toBe('blah')
}
expect(a.value).toBe('a')
expect(a.get()).toBe('a')
expect.assertions(2)
})
@ -192,10 +192,10 @@ describe('transact', () => {
} catch (e: any) {
expect(e.message).toBe('blah')
}
expect(a.value).toBe('c')
expect(a.get()).toBe('c')
})
expect(a.value).toBe('c')
expect(a.get()).toBe('c')
expect.assertions(3)
})

View file

@ -28,7 +28,11 @@ export interface Signal<Value, Diff = unknown> {
* Any computed signal that depends on this signal will be lazily recomputed if this signal changes.
* Any effect that depends on this signal will be rescheduled if this signal changes.
*/
readonly value: Value
get(): Value
/**
* @deprecated Use [[Signal.get]] instead.
*/
value: Value
/**
* The epoch when this signal's value last changed. Note tha this is not the same as when the value was last computed.
* A signal may recopmute it's value without changing it.

View file

@ -0,0 +1,41 @@
let didWarnDotValue = false
const isDev = typeof process !== 'undefined' && process.env?.NODE_ENV === 'development'
const isProd = !isDev
// remove this once we've removed all getters from our app
const ROLLOUT_OVERRIDE_REMOVE_ME = true
export function logDotValueWarning() {
if (ROLLOUT_OVERRIDE_REMOVE_ME) return
if (isProd) return
if (didWarnDotValue) return
didWarnDotValue = true
console.warn(
'Using Signal.value is deprecated and will be removed in the near future. Please use Signal.get() instead.'
)
}
let didWarnComputedGetter = false
export function logComputedGetterWarning() {
if (ROLLOUT_OVERRIDE_REMOVE_ME) return
if (isProd) return
if (didWarnComputedGetter) return
didWarnComputedGetter = true
console.warn(
`Using \`@computed\` as a decorator for getters is deprecated and will be removed in the near future. Please refactor to use \`@computed\` as a decorator for methods.
// Before
@computed
get foo() {
return 'foo'
}
// After
@computed
getFoo() {
return 'foo'
}
`
)
}

View file

@ -135,7 +135,7 @@ test('tracked components update when the state they refernce updates', async ()
const a = atom('a', 1)
const C = track(function Component() {
return <>{a.value}</>
return <>{a.get()}</>
})
let view: ReactTestRenderer
@ -160,7 +160,7 @@ test('things referenced in effects do not trigger updates', async () => {
const Component = track(function Component() {
numRenders++
useEffect(() => {
a.value
a.get()
}, [])
return <>hi</>
})
@ -185,7 +185,7 @@ test('things referenced in effects do not trigger updates', async () => {
test("tracked zombie-children don't throw", async () => {
const theAtom = atom<Record<string, number>>('map', { a: 1, b: 2, c: 3 })
const Parent = track(function Parent() {
const ids = Object.keys(theAtom.value)
const ids = Object.keys(theAtom.get())
return (
<>
{ids.map((id) => (
@ -195,8 +195,8 @@ test("tracked zombie-children don't throw", async () => {
)
})
const Child = track(function Child({ id }: { id: string }) {
if (!(id in theAtom.value)) throw new Error('id not found!')
const value = theAtom.value[id]
if (!(id in theAtom.get())) throw new Error('id not found!')
const value = theAtom.get()[id]
return <>{value}</>
})

View file

@ -17,7 +17,7 @@ test('useAtom returns an atom', async () => {
})
expect(theAtom).not.toBeNull()
expect(theAtom?.value).toBe('a')
expect(theAtom?.get()).toBe('a')
expect(theAtom?.name).toBe('useAtom(myAtom)')
expect(view!.toJSON()).toMatchInlineSnapshot(`"a"`)
@ -44,7 +44,7 @@ test('useAtom supports taking an initializer', async () => {
})
expect(theAtom).not.toBeNull()
expect(theAtom?.value).toBe('a')
expect(theAtom?.get()).toBe('a')
expect(theAtom?.name).toBe('useAtom(myAtom)')
expect(view!.toJSON()).toMatchInlineSnapshot(`"a"`)

View file

@ -12,7 +12,7 @@ test('useComputed returns a computed value', async () => {
function Component() {
const a = useAtom('a', 1)
theAtom = a
const b = useComputed('a+1', () => a.value + 1, [])
const b = useComputed('a+1', () => a.get() + 1, [])
theComputed = b
return <>{useValue(b)}</>
}
@ -23,7 +23,7 @@ test('useComputed returns a computed value', async () => {
})
expect(theComputed).not.toBeNull()
expect(theComputed?.value).toBe(2)
expect(theComputed?.get()).toBe(2)
expect(theComputed?.name).toBe('useComputed(a+1)')
expect(view!.toJSON()).toMatchInlineSnapshot(`"2"`)
@ -42,7 +42,7 @@ test('useComputed has a dependencies array that allows creating a new computed',
setCount = _setCount
const a = useAtom('a', 1)
theAtom = a
const b = useComputed('a+1', () => a.value + 1, [count])
const b = useComputed('a+1', () => a.get() + 1, [count])
theComputed = b
return <>{useValue(b)}</>
}
@ -55,7 +55,7 @@ test('useComputed has a dependencies array that allows creating a new computed',
const initialComputed = theComputed
expect(theComputed).not.toBeNull()
expect(theComputed?.value).toBe(2)
expect(theComputed?.get()).toBe(2)
expect(theComputed?.name).toBe('useComputed(a+1)')
expect(view!.toJSON()).toMatchInlineSnapshot(`"2"`)
@ -83,7 +83,7 @@ test('useComputed allows optionally passing options', async () => {
setCount = _setCount
const a = useAtom('a', 1)
theAtom = a
const b = useComputed('a+1', () => a.value + 1, { isEqual }, [count])
const b = useComputed('a+1', () => a.get() + 1, { isEqual }, [count])
theComputed = b
return <>{useValue(b)}</>
}
@ -96,7 +96,7 @@ test('useComputed allows optionally passing options', async () => {
const initialComputed = theComputed
expect(theComputed).not.toBeNull()
expect(theComputed?.value).toBe(2)
expect(theComputed?.get()).toBe(2)
expect(theComputed?.name).toBe('useComputed(a+1)')
expect(view!.toJSON()).toMatchInlineSnapshot(`"2"`)

View file

@ -9,7 +9,7 @@ describe('useStateTracking', () => {
const Component = () => {
const val = useStateTracking('', () => {
return a.value
return a.get()
})
return <>You are {val} years old</>
}
@ -48,7 +48,7 @@ describe('useStateTracking', () => {
const age = useStateTracking('', () => {
// eslint-disable-next-line react-hooks/rules-of-hooks
;[height, setHeight] = React.useState(20)
return _age.value
return _age.get()
})
return (
<>
@ -110,12 +110,12 @@ describe('useStateTracking', () => {
const Component = () => {
const val = useStateTracking('', () => {
if (a.value === null) {
if (a.get() === null) {
throw new Promise<string>((r) => {
resolve = r
})
}
return a.value
return a.get()
})
return <>You are {val} years old</>
}
@ -156,7 +156,7 @@ describe('useStateTracking', () => {
const Component = () => {
const val = useStateTracking('', () => {
numRenders++
return a.value
return a.get()
})
return <>You are {val} years old</>
}

View file

@ -12,7 +12,7 @@ test('useValue returns a value from a computed', async () => {
function Component() {
const a = useAtom('a', 1)
theAtom = a
const b = useComputed('a+1', () => a.value + 1, [])
const b = useComputed('a+1', () => a.get() + 1, [])
theComputed = b
return <>{useValue(b)}</>
}
@ -23,7 +23,7 @@ test('useValue returns a value from a computed', async () => {
})
expect(theComputed).not.toBeNull()
expect(theComputed?.value).toBe(2)
expect(theComputed?.get()).toBe(2)
expect(theComputed?.name).toBe('useComputed(a+1)')
expect(view!.toJSON()).toMatchInlineSnapshot(`"2"`)
@ -62,7 +62,7 @@ test('useValue returns a value from a compute function', async () => {
const [b, _setB] = useState(1)
setB = _setB
theAtom = a
const c = useValue('a+b', () => a.value + b, [b])
const c = useValue('a+b', () => a.get() + b, [b])
return <>{c}</>
}
@ -87,7 +87,7 @@ test('useValue returns a value from a compute function', async () => {
test("useValue doesn't throw when used in a zombie-child component", async () => {
const theAtom = atom<Record<string, number>>('map', { a: 1, b: 2, c: 3 })
function Parent() {
const ids = useValue('ids', () => Object.keys(theAtom.value), [])
const ids = useValue('ids', () => Object.keys(theAtom.get()), [])
return (
<>
{ids.map((id) => (
@ -100,8 +100,8 @@ test("useValue doesn't throw when used in a zombie-child component", async () =>
const value = useValue(
'value',
() => {
if (!(id in theAtom.value)) throw new Error('id not found!')
return theAtom.value[id]
if (!(id in theAtom.get())) throw new Error('id not found!')
return theAtom.get()[id]
},
[id]
)

View file

@ -82,11 +82,11 @@ export function useValue() {
return {
subscribe: (listen: () => void) => {
return react(`useValue(${name})`, () => {
$val.value
$val.get()
listen()
})
},
getSnapshot: () => $val.value,
getSnapshot: () => $val.get(),
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [$val])

View file

@ -197,7 +197,7 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
'Store.historyReactor',
() => {
// deref to make sure we're subscribed regardless of whether we need to propagate
this.history.value
this.history.get()
// If we have accumulated history, flush it and update listeners
this._flushHistory()
},
@ -290,7 +290,7 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
if (this.listeners.size === 0) {
this.historyAccumulator.clear()
}
this.history.set(this.history.value + 1, changes)
this.history.set(this.history.get() + 1, changes)
}
validate(phase: 'initialize' | 'createRecord' | 'updateRecord' | 'tests') {
@ -379,7 +379,7 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
const recordAtom = (map ?? currentMap)[record.id as IdOf<R>]
if (recordAtom) {
if (beforeUpdate) record = beforeUpdate(recordAtom.value, record, source)
if (beforeUpdate) record = beforeUpdate(recordAtom.get(), record, source)
// If we already have an atom for this record, update its value.
@ -478,7 +478,7 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
const atom = this.atoms.__unsafe__getWithoutCapture()[id]
if (!atom) continue
if (this.onBeforeDelete(atom.value, source) === false) {
if (this.onBeforeDelete(atom.get(), source) === false) {
cancelled.push(id)
}
}
@ -496,7 +496,7 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
if (!result) result = { ...atoms }
if (!removed) removed = {} as Record<IdOf<R>, R>
delete result[id]
removed[id] = atoms[id].value
removed[id] = atoms[id].get()
}
return result ?? atoms
@ -526,7 +526,7 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
* @public
*/
get = <K extends IdOf<R>>(id: K): RecFromId<K> | undefined => {
return this.atoms.value[id]?.value as any
return this.atoms.get()[id]?.get() as any
}
/**
@ -536,7 +536,7 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
* @public
*/
unsafeGetWithoutCapture = <K extends IdOf<R>>(id: K): RecFromId<K> | undefined => {
return this.atoms.value[id]?.__unsafe__getWithoutCapture() as any
return this.atoms.get()[id]?.__unsafe__getWithoutCapture() as any
}
/**
@ -547,8 +547,8 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
*/
serialize = (scope: RecordScope | 'all' = 'document'): SerializedStore<R> => {
const result = {} as SerializedStore<R>
for (const [id, atom] of objectMapEntries(this.atoms.value)) {
const record = atom.value
for (const [id, atom] of objectMapEntries(this.atoms.get())) {
const record = atom.get()
if (scope === 'all' || this.scopedTypes[scope].has(record.typeName)) {
result[id as IdOf<R>] = record
}
@ -631,7 +631,7 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
* @public
*/
allRecords = (): R[] => {
return objectMapValues(this.atoms.value).map((atom) => atom.value)
return objectMapValues(this.atoms.get()).map((atom) => atom.get())
}
/**
@ -640,7 +640,7 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
* @public
*/
clear = (): void => {
this.remove(objectMapKeys(this.atoms.value))
this.remove(objectMapKeys(this.atoms.get()))
}
/**
@ -651,7 +651,7 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
* @param updater - A function that updates the record.
*/
update = <K extends IdOf<R>>(id: K, updater: (record: RecFromId<K>) => RecFromId<K>) => {
const atom = this.atoms.value[id]
const atom = this.atoms.get()[id]
if (!atom) {
console.error(`Record ${id} not found. This is probably an error`)
return
@ -667,7 +667,7 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
* @public
*/
has = <K extends IdOf<R>>(id: K): boolean => {
return !!this.atoms.value[id]
return !!this.atoms.get()[id]
}
/**
@ -772,18 +772,20 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
const cache = new Cache<Atom<any>, Computed<T | undefined>>()
return {
get: (id: IdOf<V>) => {
const atom = this.atoms.value[id]
const atom = this.atoms.get()[id]
if (!atom) {
return undefined
}
return cache.get(atom, () => {
const recordSignal = isEqual
? computed(atom.name + ':equals', () => atom.value, { isEqual })
: atom
return computed<T | undefined>(name + ':' + id, () => {
return derive(recordSignal.value as V)
return cache
.get(atom, () => {
const recordSignal = isEqual
? computed(atom.name + ':equals', () => atom.get(), { isEqual })
: atom
return computed<T | undefined>(name + ':' + id, () => {
return derive(recordSignal.get() as V)
})
})
}).value
.get()
},
}
}
@ -804,17 +806,17 @@ export class Store<R extends UnknownRecord = UnknownRecord, Props = unknown> {
const cache = new Cache<Atom<any>, Computed<J | undefined>>()
return {
get: (id: IdOf<V>) => {
const atom = this.atoms.value[id]
const atom = this.atoms.get()[id]
if (!atom) {
return undefined
}
const d = computed<T | undefined>(name + ':' + id + ':selector', () =>
selector(atom.value as V)
selector(atom.get() as V)
)
return cache.get(atom, () =>
computed<J | undefined>(name + ':' + id, () => derive(d.value as T))
).value
return cache
.get(atom, () => computed<J | undefined>(name + ':' + id, () => derive(d.get() as T)))
.get()
},
}
}

View file

@ -74,11 +74,11 @@ export class StoreQueries<R extends UnknownRecord> {
'filterHistory:' + typeName,
(lastValue, lastComputedEpoch) => {
if (isUninitialized(lastValue)) {
return this.history.value
return this.history.get()
}
const diff = this.history.getDiffSince(lastComputedEpoch)
if (diff === RESET_VALUE) return this.history.value
if (diff === RESET_VALUE) return this.history.get()
const res = { added: {}, removed: {}, updated: {} } as RecordsDiff<S>
let numAdded = 0
@ -137,7 +137,7 @@ export class StoreQueries<R extends UnknownRecord> {
}
if (numAdded || numRemoved || numUpdated) {
return withDiff(this.history.value, res)
return withDiff(this.history.get(), res)
} else {
return lastValue
}
@ -192,10 +192,10 @@ export class StoreQueries<R extends UnknownRecord> {
const fromScratch = () => {
// deref typeHistory early so that the first time the incremental version runs
// it gets a diff to work with instead of having to bail to this from-scratch version
typeHistory.value
typeHistory.get()
const res = new Map<S[Property], Set<IdOf<S>>>()
for (const atom of objectMapValues(this.atoms.value)) {
const record = atom.value
for (const atom of objectMapValues(this.atoms.get())) {
const record = atom.get()
if (record.typeName === typeName) {
const value = (record as S)[property]
if (!res.has(value)) {
@ -306,8 +306,8 @@ export class StoreQueries<R extends UnknownRecord> {
const ids = this.ids(typeName, queryCreator, name)
return computed<S | undefined>(name, () => {
for (const id of ids.value) {
return this.atoms.value[id]?.value as S
for (const id of ids.get()) {
return this.atoms.get()[id]?.get() as S
}
return undefined
})
@ -329,12 +329,12 @@ export class StoreQueries<R extends UnknownRecord> {
const ids = this.ids(typeName, queryCreator, 'ids:' + name)
return computed<S[]>(name, () => {
return [...ids.value].map((id) => {
const atom = this.atoms.value[id]
return [...ids.get()].map((id) => {
const atom = this.atoms.get()[id]
if (!atom) {
throw new Error('no atom found for record id: ' + id)
}
return atom.value as S
return atom.get() as S
})
})
}
@ -360,12 +360,12 @@ export class StoreQueries<R extends UnknownRecord> {
const fromScratch = () => {
// deref type history early to allow first incremental update to use diffs
typeHistory.value
typeHistory.get()
const query: QueryExpression<S> = queryCreator()
if (Object.keys(query).length === 0) {
return new Set<IdOf<S>>(
objectMapValues(this.atoms.value).flatMap((v) => {
const r = v.value
objectMapValues(this.atoms.get()).flatMap((v) => {
const r = v.get()
if (r.typeName === typeName) {
return r.id
} else {
@ -394,7 +394,7 @@ export class StoreQueries<R extends UnknownRecord> {
return computed(
'query:' + name,
(prevValue, lastComputedEpoch) => {
const query = cachedQuery.value
const query = cachedQuery.get()
if (isUninitialized(prevValue)) {
return fromScratch()
}
@ -455,7 +455,7 @@ export class StoreQueries<R extends UnknownRecord> {
if (ids.size === 0) {
return EMPTY_ARRAY
}
const atoms = this.atoms.value
return [...ids].map((id) => atoms[id].value as Extract<R, { typeName: TypeName }>)
const atoms = this.atoms.get()
return [...ids].map((id) => atoms[id].get() as Extract<R, { typeName: TypeName }>)
}
}

View file

@ -34,7 +34,7 @@ export function executeQuery<R extends UnknownRecord, TypeName extends R['typeNa
for (const [k, matcher] of Object.entries(query)) {
if ('eq' in matcher) {
const index = store.index(typeName, k as any)
const ids = index.value.get(matcher.eq)
const ids = index.get().get(matcher.eq)
if (ids) {
for (const id of ids) {
matchIds[k].add(id)
@ -42,7 +42,7 @@ export function executeQuery<R extends UnknownRecord, TypeName extends R['typeNa
}
} else if ('neq' in matcher) {
const index = store.index(typeName, k as any)
for (const [value, ids] of index.value) {
for (const [value, ids] of index.get()) {
if (value !== matcher.neq) {
for (const id of ids) {
matchIds[k].add(id)
@ -51,7 +51,7 @@ export function executeQuery<R extends UnknownRecord, TypeName extends R['typeNa
}
} else if ('gt' in matcher) {
const index = store.index(typeName, k as any)
for (const [value, ids] of index.value) {
for (const [value, ids] of index.get()) {
if (value > matcher.gt) {
for (const id of ids) {
matchIds[k].add(id)

View file

@ -66,7 +66,7 @@ describe('Store', () => {
it('allows records to be added', () => {
store.put([Author.create({ name: 'J.R.R Tolkein', id: Author.createId('tolkein') })])
expect(store.query.records('author').value).toEqual([
expect(store.query.records('author').get()).toEqual([
{ id: 'author:tolkein', typeName: 'author', name: 'J.R.R Tolkein', isPseudonym: false },
])
@ -80,7 +80,7 @@ describe('Store', () => {
},
])
expect(store.query.records('book').value).toEqual([
expect(store.query.records('book').get()).toEqual([
{
id: 'book:the-hobbit',
typeName: 'book',
@ -217,7 +217,7 @@ describe('Store', () => {
expect(current).toEqual(
Author.create({ name: 'J.R.R Tolkein', id: Author.createId('tolkein') })
)
expect([...store.query.ids('author').value]).toEqual([Author.createId('tolkein')])
expect([...store.query.ids('author').get()]).toEqual([Author.createId('tolkein')])
})
store.put([Author.create({ name: 'J.R.R Tolkein', id: Author.createId('tolkein') })])
@ -257,10 +257,16 @@ describe('Store', () => {
Author.create({ name: 'Cynan Jones', id: Author.createId('cj') }),
Author.create({ name: 'David Foster Wallace', id: Author.createId('dfw') }),
])
const Js = store.query.records('author').value.filter((r) => r.name.startsWith('J'))
const Js = store.query
.records('author')
.get()
.filter((r) => r.name.startsWith('J'))
expect(Js.map((j) => j.name).sort()).toEqual(['J.R.R Tolkein', 'James McAvoy'])
const david = store.query.records('author').value.find((r) => r.name.startsWith('David'))
const david = store.query
.records('author')
.get()
.find((r) => r.name.startsWith('David'))
expect(david?.name).toBe('David Foster Wallace')
})

View file

@ -344,7 +344,7 @@ function runTest(seed: number) {
})
store.onBeforeDelete = (record) => {
if (record.typeName === 'author') {
const books = store.query.index('book', 'authorId').value.get(record.id)
const books = store.query.index('book', 'authorId').get().get(record.id)
if (books) store.remove([...books])
}
}
@ -359,16 +359,16 @@ function runTest(seed: number) {
const bookTitleQueryParam = atom('bookTitle', getRandomBookName(getRandomNumber))
const booksByAuthorQuery = store.query.records('book', () => ({
authorId: { eq: authorIdQueryParam.value },
authorId: { eq: authorIdQueryParam.get() },
}))
const booksByTitleQuery = store.query.records('book', () => ({
title: { eq: bookTitleQueryParam.value },
title: { eq: bookTitleQueryParam.get() },
}))
const authorNameQueryParam = atom('authorName', getRandomAuthorName(getRandomNumber))
const authorIdsByNameQuery = store.query.ids('author', () => ({
name: { neq: authorNameQueryParam.value },
name: { neq: authorNameQueryParam.get() },
}))
const ops = []
@ -384,9 +384,9 @@ function runTest(seed: number) {
const authorNameIndexDiff = authorNameIndex.getDiffSince(lastReactedEpoch)
const authorIdIndexDiff = authorIdIndex.getDiffSince(lastReactedEpoch)
const authorIdsByNameDiff = authorIdsByNameQuery.getDiffSince(lastReactedEpoch)
latestBooksByAuthorQueryResult = booksByAuthorQuery.value
latestBooksByTitleQueryResult = booksByTitleQuery.value
latestAuthorIdsByNameQueryResult = authorIdsByNameQuery.value
latestBooksByAuthorQueryResult = booksByAuthorQuery.get()
latestBooksByTitleQueryResult = booksByTitleQuery.get()
latestAuthorIdsByNameQueryResult = authorIdsByNameQuery.get()
if (
authorNameIndexDiff === RESET_VALUE ||
authorIdIndexDiff === RESET_VALUE ||
@ -419,7 +419,7 @@ function runTest(seed: number) {
store.remove([op.id])
if (op.id === 'book:0.5525377229080933') {
store.query.index('book', 'title').value
store.query.index('book', 'title').get()
}
break
}
@ -440,16 +440,14 @@ function runTest(seed: number) {
effect.execute()
// these tests create a version of the index from scratch and check it against
// the incrementally-updated version to make sure the logic matches.
const authorNameIndexFromScratch = store.query.__uncached_createIndex(
'author',
'name'
).value
const authorIdIndexFromScratch = store.query.__uncached_createIndex(
'book',
'authorId'
).value
expect(authorNameIndex.value).toEqual(authorNameIndexFromScratch)
expect(authorIdIndex.value).toEqual(authorIdIndexFromScratch)
const authorNameIndexFromScratch = store.query
.__uncached_createIndex('author', 'name')
.get()
const authorIdIndexFromScratch = store.query
.__uncached_createIndex('book', 'authorId')
.get()
expect(authorNameIndex.get()).toEqual(authorNameIndexFromScratch)
expect(authorIdIndex.get()).toEqual(authorIdIndexFromScratch)
// these tests recreate the index from scratch based on the diffs so far and
// check it against the gold standard version to make sure the diff logic matches.
expect(recreateIndexFromDiffs(authorNameIndexDiffs)).toEqual(authorNameIndexFromScratch)
@ -459,36 +457,36 @@ function runTest(seed: number) {
store
.allRecords()
.filter(
(r): r is Book => r.typeName === 'book' && r.authorId === authorIdQueryParam.value
(r): r is Book => r.typeName === 'book' && r.authorId === authorIdQueryParam.get()
)
.sort(bookComparator)
)
expect(new Set(latestBooksByAuthorQueryResult.map((b) => b.id))).toEqual(
executeQuery(store.query, 'book', { authorId: { eq: authorIdQueryParam.value } })
executeQuery(store.query, 'book', { authorId: { eq: authorIdQueryParam.get() } })
)
expect(latestBooksByTitleQueryResult.sort(bookComparator)).toEqual(
store
.allRecords()
.filter(
(r): r is Book => r.typeName === 'book' && r.title === bookTitleQueryParam.value
(r): r is Book => r.typeName === 'book' && r.title === bookTitleQueryParam.get()
)
.sort(bookComparator)
)
expect(new Set(latestBooksByTitleQueryResult.map((b) => b.id))).toEqual(
executeQuery(store.query, 'book', { title: { eq: bookTitleQueryParam.value } })
executeQuery(store.query, 'book', { title: { eq: bookTitleQueryParam.get() } })
)
expect(latestAuthorIdsByNameQueryResult).toEqual(
new Set(
store
.allRecords()
.filter(
(r): r is Author => r.typeName === 'author' && r.name !== authorNameQueryParam.value
(r): r is Author => r.typeName === 'author' && r.name !== authorNameQueryParam.get()
)
.map((r) => r.id)
)
)
expect(latestAuthorIdsByNameQueryResult).toEqual(
executeQuery(store.query, 'author', { name: { neq: authorNameQueryParam.value } })
executeQuery(store.query, 'author', { name: { neq: authorNameQueryParam.get() } })
)
// this test checks that the authorIdsByName set matches what you get when you reassemble it from the diffs
expect(reacreateSetFromDiffs(authorIdsByNameDiffs)).toEqual(

View file

@ -96,27 +96,27 @@ describe('indexes', () => {
it('can be made on any property', () => {
const authorNameIndex = store.query.index('author', 'name')
expect(authorNameIndex.value.get('J.R.R. Tolkein')).toEqual(new Set([authors.tolkein.id]))
expect(authorNameIndex.value.get('David Mitchell')).toEqual(
expect(authorNameIndex.get().get('J.R.R. Tolkein')).toEqual(new Set([authors.tolkein.id]))
expect(authorNameIndex.get().get('David Mitchell')).toEqual(
new Set([authors.davidMitchellFunny.id, authors.davidMitchellSerious.id])
)
const bookTitleIndex = store.query.index('book', 'title')
expect(bookTitleIndex.value.get('Cloud Atlas')).toEqual(new Set([books.cloudAtlas.id]))
expect(bookTitleIndex.value.get('Lord of the Rings')).toEqual(new Set([books.lotr.id]))
expect(bookTitleIndex.get().get('Cloud Atlas')).toEqual(new Set([books.cloudAtlas.id]))
expect(bookTitleIndex.get().get('Lord of the Rings')).toEqual(new Set([books.lotr.id]))
})
it('can have things added when records are added', () => {
const authorNameIndex = store.query.index('author', 'name')
// deref to make it compute once
expect(authorNameIndex.value.get('J.R.R. Tolkein')).toEqual(new Set([authors.tolkein.id]))
expect(authorNameIndex.get().get('J.R.R. Tolkein')).toEqual(new Set([authors.tolkein.id]))
let lastChangedEpoch = authorNameIndex.lastChangedEpoch
const newAuthor = Author.create({ name: 'New Author' })
store.put([newAuthor])
expect(authorNameIndex.value.get('New Author')).toEqual(new Set([newAuthor.id]))
expect(authorNameIndex.get().get('New Author')).toEqual(new Set([newAuthor.id]))
const diff = authorNameIndex.getDiffSince(lastChangedEpoch)
if (diff === RESET_VALUE) throw new Error('should not be reset')
@ -135,7 +135,7 @@ describe('indexes', () => {
lastChangedEpoch = authorNameIndex.lastChangedEpoch
store.put(moreNewAuthors)
expect(authorNameIndex.value.get('New Author')).toEqual(
expect(authorNameIndex.get().get('New Author')).toEqual(
new Set([newAuthor.id, ...moreNewAuthors.map((a) => a.id)])
)
@ -150,13 +150,13 @@ describe('indexes', () => {
it('can have things added when records are updated', () => {
const authorNameIndex = store.query.index('author', 'name')
expect(authorNameIndex.value.get('J.R.R. Tolkein')).toEqual(new Set([authors.tolkein.id]))
expect(authorNameIndex.get().get('J.R.R. Tolkein')).toEqual(new Set([authors.tolkein.id]))
let lastChangedEpoch = authorNameIndex.lastChangedEpoch
store.put([{ ...authors.bradbury, name: 'J.R.R. Tolkein' }])
expect(authorNameIndex.value.get('J.R.R. Tolkein')).toEqual(
expect(authorNameIndex.get().get('J.R.R. Tolkein')).toEqual(
new Set([authors.tolkein.id, authors.bradbury.id])
)
@ -178,7 +178,7 @@ describe('indexes', () => {
{ ...authors.davidMitchellSerious, name: 'J.R.R. Tolkein' },
])
expect(authorNameIndex.value.get('J.R.R. Tolkein')).toEqual(
expect(authorNameIndex.get().get('J.R.R. Tolkein')).toEqual(
new Set([
authors.tolkein.id,
authors.bradbury.id,
@ -206,13 +206,13 @@ describe('indexes', () => {
it('can have things removed when records are removed', () => {
const authorNameIndex = store.query.index('author', 'name')
expect(authorNameIndex.value.get('J.R.R. Tolkein')).toEqual(new Set([authors.tolkein.id]))
expect(authorNameIndex.get().get('J.R.R. Tolkein')).toEqual(new Set([authors.tolkein.id]))
let lastChangedEpoch = authorNameIndex.lastChangedEpoch
store.remove([authors.tolkein.id])
expect(authorNameIndex.value.get('J.R.R. Tolkein')).toEqual(undefined)
expect(authorNameIndex.get().get('J.R.R. Tolkein')).toEqual(undefined)
const diff = authorNameIndex.getDiffSince(lastChangedEpoch)
if (diff === RESET_VALUE) throw new Error('should not be reset')
@ -229,8 +229,8 @@ describe('indexes', () => {
authors.davidMitchellSerious.id,
])
expect(authorNameIndex.value.get('Ray Bradbury')).toEqual(undefined)
expect(authorNameIndex.value.get('David Mitchell')).toEqual(undefined)
expect(authorNameIndex.get().get('Ray Bradbury')).toEqual(undefined)
expect(authorNameIndex.get().get('David Mitchell')).toEqual(undefined)
const diff2 = authorNameIndex.getDiffSince(lastChangedEpoch)
@ -250,16 +250,16 @@ describe('indexes', () => {
it('can have things removed when records are updated', () => {
const authorNameIndex = store.query.index('author', 'name')
expect(authorNameIndex.value.get('J.R.R. Tolkein')).toEqual(new Set([authors.tolkein.id]))
expect(authorNameIndex.get().get('J.R.R. Tolkein')).toEqual(new Set([authors.tolkein.id]))
let lastChangedEpoch = authorNameIndex.lastChangedEpoch
store.put([{ ...authors.tolkein, name: 'Ray Bradbury' }])
expect(authorNameIndex.value.get('Ray Bradbury')).toEqual(
expect(authorNameIndex.get().get('Ray Bradbury')).toEqual(
new Set([authors.tolkein.id, authors.bradbury.id])
)
expect(authorNameIndex.value.get('J.R.R. Tolkein')).toEqual(undefined)
expect(authorNameIndex.get().get('J.R.R. Tolkein')).toEqual(undefined)
const diff = authorNameIndex.getDiffSince(lastChangedEpoch)
if (diff === RESET_VALUE) throw new Error('should not be reset')
@ -279,7 +279,7 @@ describe('indexes', () => {
{ ...authors.davidMitchellSerious, name: 'Ray Bradbury' },
])
expect(authorNameIndex.value.get('Ray Bradbury')).toEqual(
expect(authorNameIndex.get().get('Ray Bradbury')).toEqual(
new Set([
authors.tolkein.id,
authors.bradbury.id,
@ -287,7 +287,7 @@ describe('indexes', () => {
authors.davidMitchellSerious.id,
])
)
expect(authorNameIndex.value.get('David Mitchell')).toEqual(undefined)
expect(authorNameIndex.get().get('David Mitchell')).toEqual(undefined)
const diff2 = authorNameIndex.getDiffSince(lastChangedEpoch)
@ -307,7 +307,7 @@ describe('indexes', () => {
it('handles things being removed and added for the same value at the same time', () => {
const authorNameIndex = store.query.index('author', 'name')
expect(authorNameIndex.value.get('J.R.R. Tolkein')).toEqual(new Set([authors.tolkein.id]))
expect(authorNameIndex.get().get('J.R.R. Tolkein')).toEqual(new Set([authors.tolkein.id]))
let lastChangedEpoch = authorNameIndex.lastChangedEpoch
@ -316,7 +316,7 @@ describe('indexes', () => {
const newAuthor = Author.create({ name: 'J.R.R. Tolkein' })
store.put([newAuthor])
expect(authorNameIndex.value.get('J.R.R. Tolkein')).toEqual(new Set([newAuthor.id]))
expect(authorNameIndex.get().get('J.R.R. Tolkein')).toEqual(new Set([newAuthor.id]))
const diff = authorNameIndex.getDiffSince(lastChangedEpoch)
@ -335,10 +335,10 @@ describe('indexes', () => {
store.put([{ ...authors.davidMitchellFunny, name: 'Ray Bradbury' }])
store.put([{ ...authors.bradbury, name: 'David Mitchell' }])
expect(authorNameIndex.value.get('Ray Bradbury')).toEqual(
expect(authorNameIndex.get().get('Ray Bradbury')).toEqual(
new Set([authors.davidMitchellFunny.id])
)
expect(authorNameIndex.value.get('David Mitchell')).toEqual(
expect(authorNameIndex.get().get('David Mitchell')).toEqual(
new Set([authors.bradbury.id, authors.davidMitchellSerious.id])
)
@ -363,13 +363,13 @@ describe('indexes', () => {
it('has the same value if nothing changed', () => {
const authorNameIndex = store.query.index('author', 'name')
expect(authorNameIndex.value.get('J.R.R. Tolkein')).toEqual(new Set([authors.tolkein.id]))
expect(authorNameIndex.get().get('J.R.R. Tolkein')).toEqual(new Set([authors.tolkein.id]))
const lastChangedEpoch = authorNameIndex.lastChangedEpoch
store.put([{ ...authors.tolkein, age: 23 }])
expect(authorNameIndex.value.get('J.R.R. Tolkein')).toEqual(new Set([authors.tolkein.id]))
expect(authorNameIndex.get().get('J.R.R. Tolkein')).toEqual(new Set([authors.tolkein.id]))
expect(lastChangedEpoch).toBe(authorNameIndex.lastChangedEpoch)
})
@ -379,13 +379,13 @@ describe('queries for ids', () => {
it('can query for all values of a given type', () => {
const bookQuery = store.query.ids('book')
expect(bookQuery.value).toEqual(
expect(bookQuery.get()).toEqual(
new Set([books.cloudAtlas.id, books.farenheit.id, books.lotr.id, books.myLifeInComedy.id])
)
const authorQuery = store.query.ids('author')
expect(authorQuery.value).toEqual(
expect(authorQuery.get()).toEqual(
new Set([
authors.bradbury.id,
authors.davidMitchellFunny.id,
@ -398,7 +398,7 @@ describe('queries for ids', () => {
const newBook = Book.create({ title: 'The Hobbit', authorId: newAuthor.id })
store.put([newAuthor, newBook])
expect(bookQuery.value).toEqual(
expect(bookQuery.get()).toEqual(
new Set([
books.cloudAtlas.id,
books.farenheit.id,
@ -408,7 +408,7 @@ describe('queries for ids', () => {
])
)
expect(authorQuery.value).toEqual(
expect(authorQuery.get()).toEqual(
new Set([
authors.bradbury.id,
authors.davidMitchellFunny.id,
@ -424,13 +424,13 @@ describe('queries for ids', () => {
name: { eq: 'J.R.R. Tolkein' },
}))
expect(jrr.value).toEqual(new Set([authors.tolkein.id]))
expect(jrr.get()).toEqual(new Set([authors.tolkein.id]))
const mitchell = store.query.ids('author', () => ({
name: { eq: 'David Mitchell' },
}))
expect(mitchell.value).toEqual(
expect(mitchell.get()).toEqual(
new Set([authors.davidMitchellFunny.id, authors.davidMitchellSerious.id])
)
})
@ -443,7 +443,7 @@ describe('queries for ids', () => {
age: { eq: 30 },
}))
expect(mitchell30.value).toEqual(new Set([authors.davidMitchellFunny.id]))
expect(mitchell30.get()).toEqual(new Set([authors.davidMitchellFunny.id]))
})
it('can use a reactive query', () => {
@ -453,16 +453,16 @@ describe('queries for ids', () => {
const currentAge = atom('currentAge', 30)
const mitchell30 = store.query.ids('author', () => ({
name: { eq: currentAuthor.value },
age: { eq: currentAge.value },
name: { eq: currentAuthor.get() },
age: { eq: currentAge.get() },
}))
expect(mitchell30.value).toEqual(new Set([authors.davidMitchellFunny.id]))
expect(mitchell30.get()).toEqual(new Set([authors.davidMitchellFunny.id]))
let lastChangedEpoch = mitchell30.lastChangedEpoch
currentAge.set(23)
expect(mitchell30.value).toEqual(new Set([authors.davidMitchellSerious.id]))
expect(mitchell30.get()).toEqual(new Set([authors.davidMitchellSerious.id]))
const diff = mitchell30.getDiffSince(lastChangedEpoch)
@ -478,7 +478,7 @@ describe('queries for ids', () => {
lastChangedEpoch = mitchell30.lastChangedEpoch
expect(mitchell30.value).toEqual(new Set([authors.tolkein.id]))
expect(mitchell30.get()).toEqual(new Set([authors.tolkein.id]))
const diff2 = mitchell30.getDiffSince(lastChangedEpoch)
@ -498,13 +498,13 @@ describe('queries for ids', () => {
name: { neq: 'David Mitchell' },
}))
expect(mitchell.value).toEqual(new Set([authors.tolkein.id, authors.bradbury.id]))
expect(mitchell.get()).toEqual(new Set([authors.tolkein.id, authors.bradbury.id]))
const ageNot23 = store.query.ids('author', () => ({
age: { neq: 23 },
}))
expect(ageNot23.value).toEqual(new Set([authors.davidMitchellFunny.id]))
expect(ageNot23.get()).toEqual(new Set([authors.davidMitchellFunny.id]))
})
it('supports records being added', () => {
@ -512,14 +512,14 @@ describe('queries for ids', () => {
name: { eq: 'David Mitchell' },
}))
expect(mitchell.value).toEqual(
expect(mitchell.get()).toEqual(
new Set([authors.davidMitchellFunny.id, authors.davidMitchellSerious.id])
)
const newAuthor = Author.create({ name: 'David Mitchell' })
store.put([newAuthor])
expect(mitchell.value).toEqual(
expect(mitchell.get()).toEqual(
new Set([authors.davidMitchellFunny.id, authors.davidMitchellSerious.id, newAuthor.id])
)
})
@ -529,13 +529,13 @@ describe('queries for ids', () => {
name: { eq: 'David Mitchell' },
}))
expect(mitchell.value).toEqual(
expect(mitchell.get()).toEqual(
new Set([authors.davidMitchellFunny.id, authors.davidMitchellSerious.id])
)
store.remove([authors.davidMitchellFunny.id])
expect(mitchell.value).toEqual(new Set([authors.davidMitchellSerious.id]))
expect(mitchell.get()).toEqual(new Set([authors.davidMitchellSerious.id]))
})
it('supports records being updated', () => {
@ -544,17 +544,17 @@ describe('queries for ids', () => {
age: { neq: 30 },
}))
expect(mitchell.value).toEqual(
expect(mitchell.get()).toEqual(
new Set([authors.davidMitchellFunny.id, authors.davidMitchellSerious.id])
)
store.put([{ ...authors.davidMitchellFunny, age: 30 }])
expect(mitchell.value).toEqual(new Set([authors.davidMitchellSerious.id]))
expect(mitchell.get()).toEqual(new Set([authors.davidMitchellSerious.id]))
store.put([{ ...authors.davidMitchellFunny, age: 23 }])
expect(mitchell.value).toEqual(
expect(mitchell.get()).toEqual(
new Set([authors.davidMitchellSerious.id, authors.davidMitchellFunny.id])
)
})
@ -564,7 +564,7 @@ describe('queries for ids', () => {
name: { eq: 'David Mitchell' },
}))
expect(mitchell.value).toEqual(
expect(mitchell.get()).toEqual(
new Set([authors.davidMitchellFunny.id, authors.davidMitchellSerious.id])
)
@ -573,13 +573,13 @@ describe('queries for ids', () => {
const newAuthor = Author.create({ name: 'William Shakespeare' })
store.put([newAuthor])
mitchell.value
mitchell.get()
expect(mitchell.lastChangedEpoch).toEqual(lastChangedEpoch)
store.remove([authors.tolkein.id])
mitchell.value
mitchell.get()
expect(mitchell.lastChangedEpoch).toEqual(lastChangedEpoch)
})
@ -589,7 +589,7 @@ describe('queries for ids', () => {
name: { eq: 'David Mitchell' },
}))
expect(mitchell.value).toEqual(
expect(mitchell.get()).toEqual(
new Set([authors.davidMitchellFunny.id, authors.davidMitchellSerious.id])
)
@ -597,14 +597,14 @@ describe('queries for ids', () => {
store.put([{ ...authors.davidMitchellFunny, age: 30 }])
mitchell.value
mitchell.get()
expect(mitchell.lastChangedEpoch).toEqual(lastChangedEpoch)
// make a change that does affect the query just to check
store.put([{ ...authors.davidMitchellFunny, name: 'steve' }])
mitchell.value
mitchell.get()
expect(mitchell.lastChangedEpoch).toBeGreaterThan(lastChangedEpoch)
})
@ -613,7 +613,7 @@ describe('queries for ids', () => {
name: { eq: 'David Mitchell' },
}))
expect(mitchell.value).toEqual(
expect(mitchell.get()).toEqual(
new Set([authors.davidMitchellFunny.id, authors.davidMitchellSerious.id])
)
@ -622,7 +622,7 @@ describe('queries for ids', () => {
store.remove([authors.davidMitchellFunny.id])
store.put([authors.davidMitchellFunny])
mitchell.value
mitchell.get()
expect(mitchell.lastChangedEpoch).toEqual(lastChangedEpoch)
})
@ -632,7 +632,7 @@ describe('queries for ids', () => {
name: { eq: 'David Mitchell' },
}))
expect(mitchell.value).toEqual(
expect(mitchell.get()).toEqual(
new Set([authors.davidMitchellFunny.id, authors.davidMitchellSerious.id])
)
@ -643,7 +643,7 @@ describe('queries for ids', () => {
store.put([newMitchell])
store.remove([newMitchell.id])
mitchell.value
mitchell.get()
expect(mitchell.lastChangedEpoch).toEqual(lastChangedEpoch)
})
@ -655,7 +655,7 @@ describe('queries for records', () => {
it('can query for all values of a given type', () => {
const allBooks = store.query.records('book')
expect(allBooks.value.sort(bookComparator)).toEqual(
expect(allBooks.get().sort(bookComparator)).toEqual(
[books.cloudAtlas, books.farenheit, books.lotr, books.myLifeInComedy].sort(bookComparator)
)
@ -663,7 +663,7 @@ describe('queries for records', () => {
store.put([newBook])
expect(allBooks.value.sort(bookComparator)).toEqual(
expect(allBooks.get().sort(bookComparator)).toEqual(
[books.cloudAtlas, books.farenheit, books.lotr, books.myLifeInComedy, newBook].sort(
bookComparator
)
@ -675,7 +675,7 @@ describe('queries for records', () => {
title: { eq: 'Farenheit 451' },
}))
expect(farenheit.value).toEqual([books.farenheit])
expect(farenheit.get()).toEqual([books.farenheit])
})
it('can query for multiple values', () => {
@ -688,7 +688,7 @@ describe('queries for records', () => {
authorId: { eq: authors.davidMitchellFunny.id },
}))
expect(mitchell.value.sort(bookComparator)).toEqual(
expect(mitchell.get().sort(bookComparator)).toEqual(
[books.myLifeInComedy, funnyGuide].sort(bookComparator)
)
})
@ -696,14 +696,14 @@ describe('queries for records', () => {
it('supports reactive queries', () => {
const currentAuthor = atom('currentAuthor', authors.davidMitchellFunny.id)
const booksQuery = store.query.records('book', () => ({
authorId: { eq: currentAuthor.value },
authorId: { eq: currentAuthor.get() },
}))
expect(booksQuery.value).toEqual([books.myLifeInComedy])
expect(booksQuery.get()).toEqual([books.myLifeInComedy])
currentAuthor.set(authors.tolkein.id)
expect(booksQuery.value).toEqual([books.lotr])
expect(booksQuery.get()).toEqual([books.lotr])
})
})
@ -717,7 +717,7 @@ describe('filtering history', () => {
it('allows filtering history', () => {
const authorHistory = store.query.filterHistory('author')
authorHistory.value
authorHistory.get()
let lastChangedEpoch = authorHistory.lastChangedEpoch
@ -726,7 +726,7 @@ describe('filtering history', () => {
// updating an author should change the history
store.put([{ ...authors.davidMitchellFunny, age: 30 }])
authorHistory.value
authorHistory.get()
expect(lastChangedEpoch).toBeLessThan(authorHistory.lastChangedEpoch)
@ -742,7 +742,7 @@ describe('filtering history', () => {
{ ...books.lotr, title: 'The Lord of the Rings Part I: The Fellowship of the Ring' },
])
authorHistory.value
authorHistory.get()
expect(authorHistory.lastChangedEpoch).toEqual(lastChangedEpoch)
expect(authorHistory.getDiffSince(lastChangedEpoch)).toMatchObject([])
@ -751,25 +751,25 @@ describe('filtering history', () => {
it('should not update if changes in a window of time cancel each other out', () => {
const authorHistory = store.query.filterHistory('author')
const epoch = authorHistory.value
const epoch = authorHistory.get()
const newAuthor = Author.create({ name: 'Stanley Briggs' })
store.put([newAuthor])
store.put([{ ...newAuthor, age: 38 }])
store.remove([newAuthor.id])
expect(authorHistory.value).toEqual(epoch)
expect(authorHistory.get()).toEqual(epoch)
store.remove([authors.tolkein.id])
store.put([authors.tolkein])
expect(authorHistory.value).toEqual(epoch)
expect(authorHistory.get()).toEqual(epoch)
})
it('removes update entries if a thing was deleted', () => {
const authorHistory = store.query.filterHistory('author')
authorHistory.value
authorHistory.get()
const lastChangedEpoch = authorHistory.lastChangedEpoch
@ -788,7 +788,7 @@ describe('filtering history', () => {
it('collapses multiple updated entries into one', () => {
const authorHistory = store.query.filterHistory('author')
authorHistory.value
authorHistory.get()
const lastChangedEpoch = authorHistory.lastChangedEpoch
@ -805,7 +805,7 @@ describe('filtering history', () => {
it('collapeses an add + update entry into just an add entry', () => {
const authorHistory = store.query.filterHistory('author')
authorHistory.value
authorHistory.get()
const lastChangedEpoch = authorHistory.lastChangedEpoch

View file

@ -69,7 +69,7 @@ describe('Store with validation', () => {
it('Accepts valid records and rejects invalid records', () => {
store.put([Author.create({ name: 'J.R.R Tolkein', id: Author.createId('tolkein') })])
expect(store.query.records('author').value).toEqual([
expect(store.query.records('author').get()).toEqual([
{ id: 'author:tolkein', typeName: 'author', name: 'J.R.R Tolkein', isPseudonym: false },
])
@ -85,7 +85,7 @@ describe('Store with validation', () => {
])
}).toThrow()
expect(store.query.records('book').value).toEqual([])
expect(store.query.records('book').get()).toEqual([])
})
})

View file

@ -33,8 +33,9 @@ export const TldrawSelectionForeground: TLSelectionForegroundComponent = track(
const bottomRightEvents = useSelectionEvents('bottom_right')
const bottomLeftEvents = useSelectionEvents('bottom_left')
const isDefaultCursor = !editor.isMenuOpen && editor.instanceState.cursor.type === 'default'
const isCoarsePointer = editor.instanceState.isCoarsePointer
const isDefaultCursor =
!editor.isMenuOpen && editor.getInstanceState().cursor.type === 'default'
const isCoarsePointer = editor.getInstanceState().isCoarsePointer
const shapes = editor.selectedShapes
const onlyShape = editor.onlySelectedShape
@ -54,7 +55,7 @@ export const TldrawSelectionForeground: TLSelectionForegroundComponent = track(
bounds = bounds.clone().expandBy(expandOutlineBy).zeroFix()
const zoom = editor.zoomLevel
const isChangingStyle = editor.instanceState.isChangingStyle
const isChangingStyle = editor.getInstanceState().isChangingStyle
const width = bounds.width
const height = bounds.height

View file

@ -33,7 +33,7 @@ beforeEach(() => {
it('enters the arrow state', () => {
editor.setCurrentTool('arrow')
expect(editor.currentToolId).toBe('arrow')
expect(editor.getCurrentToolId()).toBe('arrow')
editor.expectPathToBe('root.arrow.idle')
})

View file

@ -494,7 +494,7 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
'select.pointing_handle',
'select.dragging_handle',
'arrow.dragging'
) && !this.editor.instanceState.isReadonly
) && !this.editor.getInstanceState().isReadonly
const info = this.editor.getArrowInfo(shape)
const bounds = Box2d.ZeroFix(this.editor.getShapeGeometry(shape).bounds)

View file

@ -17,7 +17,7 @@ export class Idle extends StateNode {
override onKeyUp: TLEventHandlers['onKeyUp'] = (info) => {
if (info.key === 'Enter') {
if (this.editor.instanceState.isReadonly) return null
if (this.editor.getInstanceState().isReadonly) return null
const { onlySelectedShape } = this.editor
// If the only selected shape is editable, start editing it
if (
@ -26,7 +26,7 @@ export class Idle extends StateNode {
) {
this.editor.setCurrentTool('select')
this.editor.setEditingShape(onlySelectedShape.id)
this.editor.root.current.value!.transition('editing_shape', {
this.editor.root.current.get()!.transition('editing_shape', {
...info,
target: 'shape',
shape: onlySelectedShape,

View file

@ -13,7 +13,7 @@ export class Idle extends StateNode {
override onKeyUp: TLEventHandlers['onKeyUp'] = (info) => {
if (info.key === 'Enter') {
if (this.editor.instanceState.isReadonly) return null
if (this.editor.getInstanceState().isReadonly) return null
const { onlySelectedShape } = this.editor
// If the only selected shape is editable, start editing it
@ -23,7 +23,7 @@ export class Idle extends StateNode {
) {
this.editor.setCurrentTool('select')
this.editor.setEditingShape(onlySelectedShape.id)
this.editor.root.current.value!.transition('editing_shape', {
this.editor.root.current.get()!.transition('editing_shape', {
...info,
target: 'shape',
shape: onlySelectedShape,

View file

@ -117,7 +117,7 @@ export class Pointing extends StateNode {
},
])
if (this.editor.instanceState.isToolLocked) {
if (this.editor.getInstanceState().isToolLocked) {
this.parent.transition('idle', {})
} else {
this.editor.setCurrentTool('select', {})

View file

@ -9,7 +9,7 @@ beforeEach(() => {
it('enters the line state', () => {
editor.setCurrentTool('line')
expect(editor.currentToolId).toBe('line')
expect(editor.getCurrentToolId()).toBe('line')
editor.expectPathToBe('root.line.idle')
})

View file

@ -62,7 +62,7 @@ export class Pointing extends StateNode {
private complete() {
if (this.wasFocusedOnEnter) {
if (this.editor.instanceState.isToolLocked) {
if (this.editor.getInstanceState().isToolLocked) {
this.parent.transition('idle', {})
} else {
this.editor.setEditingShape(this.shape.id)

View file

@ -137,7 +137,7 @@ const generateImage = (dpr: number, currentZoom: number, darkMode: boolean) => {
ctx.stroke()
canvasEl.toBlob((blob) => {
if (!blob || debugFlags.throwToBlob.value) {
if (!blob || debugFlags.throwToBlob.get()) {
reject()
} else {
resolve(blob)
@ -184,7 +184,7 @@ const getDefaultPatterns = () => {
function usePattern() {
const editor = useEditor()
const dpr = editor.instanceState.devicePixelRatio
const dpr = editor.getInstanceState().devicePixelRatio
const [isReady, setIsReady] = useState(false)
const defaultPatterns = useMemo(() => getDefaultPatterns(), [])
const [backgroundUrls, setBackgroundUrls] = useState<PatternDef[]>(defaultPatterns)

View file

@ -23,7 +23,7 @@ export class Idle extends StateNode {
override onKeyDown: TLEventHandlers['onKeyDown'] = (info) => {
if (info.key === 'Enter') {
if (this.editor.instanceState.isReadonly) return null
if (this.editor.getInstanceState().isReadonly) return null
const { onlySelectedShape } = this.editor
// If the only selected shape is editable, start editing it
if (
@ -32,7 +32,7 @@ export class Idle extends StateNode {
) {
this.editor.setCurrentTool('select')
this.editor.setEditingShape(onlySelectedShape.id)
this.editor.root.current.value!.transition('editing_shape', {
this.editor.root.current.get()!.transition('editing_shape', {
...info,
target: 'shape',
shape: onlySelectedShape,

View file

@ -90,7 +90,7 @@ export class Pointing extends StateNode {
this.editor.setEditingShape(id)
this.editor.setCurrentTool('select')
this.editor.root.current.value?.transition('editing_shape', {})
this.editor.root.current.get()?.transition('editing_shape', {})
}
private cancel() {

View file

@ -179,7 +179,7 @@ export class DraggingHandle extends StateNode {
this.editor.snaps.clear()
const { onInteractionEnd } = this.info
if (this.editor.instanceState.isToolLocked && onInteractionEnd) {
if (this.editor.getInstanceState().isToolLocked && onInteractionEnd) {
// Return to the tool that was active before this one,
// but only if tool lock is turned on!
this.editor.setCurrentTool(onInteractionEnd, { shapeId: this.shapeId })

View file

@ -98,7 +98,7 @@ export class Idle extends StateNode {
break
}
case 'handle': {
if (this.editor.instanceState.isReadonly) break
if (this.editor.getInstanceState().isReadonly) break
if (this.editor.inputs.altKey) {
this.parent.transition('pointing_shape', info)
} else {
@ -222,7 +222,7 @@ export class Idle extends StateNode {
break
}
case 'selection': {
if (this.editor.instanceState.isReadonly) break
if (this.editor.getInstanceState().isReadonly) break
const { onlySelectedShape } = this.editor
@ -271,7 +271,7 @@ export class Idle extends StateNode {
if (
shape.type !== 'video' &&
shape.type !== 'embed' &&
this.editor.instanceState.isReadonly
this.editor.getInstanceState().isReadonly
)
break
@ -302,7 +302,7 @@ export class Idle extends StateNode {
break
}
case 'handle': {
if (this.editor.instanceState.isReadonly) break
if (this.editor.getInstanceState().isReadonly) break
const { shape, handle } = info
const util = this.editor.getShapeUtil(shape)
@ -475,7 +475,7 @@ export class Idle extends StateNode {
handleDoubleClickOnCanvas(info: TLClickEventInfo) {
// Create text shape and transition to editing_shape
if (this.editor.instanceState.isReadonly) return
if (this.editor.getInstanceState().isReadonly) return
this.editor.mark('creating text shape')
@ -500,7 +500,7 @@ export class Idle extends StateNode {
if (!shape) return
const util = this.editor.getShapeUtil(shape)
if (this.editor.instanceState.isReadonly) {
if (this.editor.getInstanceState().isReadonly) {
if (!util.canEditInReadOnly(shape)) {
return
}
@ -534,9 +534,9 @@ export class Idle extends StateNode {
if (!ephemeral) this.editor.mark('nudge shapes')
const { gridSize } = this.editor.documentSettings
const { gridSize } = this.editor.getDocumentSettings()
const step = this.editor.instanceState.isGridMode
const step = this.editor.getInstanceState().isGridMode
? shiftKey
? gridSize * GRID_INCREMENT
: gridSize
@ -548,7 +548,7 @@ export class Idle extends StateNode {
}
private canInteractWithShapeInReadOnly(shape: TLShape) {
if (!this.editor.instanceState.isReadonly) return true
if (!this.editor.getInstanceState().isReadonly) return true
const util = this.editor.getShapeUtil(shape)
if (util.canEditInReadOnly(shape)) return true
return false

View file

@ -25,7 +25,7 @@ export class PointingSelection extends StateNode {
override onPointerMove: TLEventHandlers['onPointerMove'] = (info) => {
if (this.editor.inputs.isDragging) {
if (this.editor.instanceState.isReadonly) return
if (this.editor.getInstanceState().isReadonly) return
this.parent.transition('translating', info)
}
}

View file

@ -151,7 +151,7 @@ export class PointingShape extends StateNode {
this.editor.select(selectingShape.id)
const util = this.editor.getShapeUtil(selectingShape)
if (this.editor.instanceState.isReadonly) {
if (this.editor.getInstanceState().isReadonly) {
if (!util.canEditInReadOnly(selectingShape)) {
return
}
@ -195,7 +195,7 @@ export class PointingShape extends StateNode {
override onPointerMove: TLEventHandlers['onPointerMove'] = (info) => {
if (this.editor.inputs.isDragging) {
if (this.editor.instanceState.isReadonly) return
if (this.editor.getInstanceState().isReadonly) return
this.parent.transition('translating', info)
}
}

View file

@ -113,7 +113,7 @@ export class Resizing extends StateNode {
return
}
if (this.editor.instanceState.isToolLocked && this.info.onInteractionEnd) {
if (this.editor.getInstanceState().isToolLocked && this.info.onInteractionEnd) {
this.editor.setCurrentTool(this.info.onInteractionEnd, {})
return
}
@ -212,8 +212,8 @@ export class Resizing extends StateNode {
.sub(this.creationCursorOffset)
const originPagePoint = this.editor.inputs.originPagePoint.clone().sub(cursorHandleOffset)
if (this.editor.instanceState.isGridMode && !ctrlKey) {
const { gridSize } = this.editor.documentSettings
if (this.editor.getInstanceState().isGridMode && !ctrlKey) {
const { gridSize } = this.editor.getDocumentSettings()
currentPagePoint.snapToGrid(gridSize)
}
@ -328,7 +328,7 @@ export class Resizing extends StateNode {
isFlippedY: boolean
rotation: number
}) {
const nextCursor = { ...this.editor.instanceState.cursor }
const nextCursor = { ...this.editor.getInstanceState().cursor }
switch (dragHandle) {
case 'top_left':

View file

@ -161,7 +161,7 @@ export class Rotating extends StateNode {
} else if (snapToNearestDegree) {
newSelectionRotation = Math.round(newSelectionRotation / EPSILON) * EPSILON
if (this.editor.instanceState.isCoarsePointer) {
if (this.editor.getInstanceState().isCoarsePointer) {
const snappedToRightAngle = snapAngle(newSelectionRotation, 4)
const angleToRightAngle = angleDelta(newSelectionRotation, snappedToRightAngle)
if (Math.abs(angleToRightAngle) < degreesToRadians(5)) {

View file

@ -162,7 +162,7 @@ export class Translating extends StateNode {
this.dragAndDropManager.dropShapes(this.snapshot.movingShapes)
this.handleEnd()
if (this.editor.instanceState.isToolLocked && this.info.onInteractionEnd) {
if (this.editor.getInstanceState().isToolLocked && this.info.onInteractionEnd) {
this.editor.setCurrentTool(this.info.onInteractionEnd)
} else {
if (this.editAfterComplete) {
@ -345,11 +345,11 @@ export function moveShapesToPoint({
initialSelectionPageBounds: Box2d
initialSelectionSnapPoints: SnapPoint[]
}) {
const {
inputs,
instanceState: { isGridMode },
documentSettings: { gridSize },
} = editor
const { inputs } = editor
const isGridMode = editor.getInstanceState().isGridMode
const gridSize = editor.getDocumentSettings().gridSize
const delta = Vec2d.Sub(inputs.currentPagePoint, inputs.originPagePoint)

View file

@ -120,9 +120,11 @@ const TldrawUiContent = React.memo(function TldrawUI({
const editor = useEditor()
const msg = useTranslation()
const breakpoint = useBreakpoint()
const isReadonlyMode = useValue('isReadonlyMode', () => editor.instanceState.isReadonly, [editor])
const isFocusMode = useValue('focus', () => editor.instanceState.isFocusMode, [editor])
const isDebugMode = useValue('debug', () => editor.instanceState.isDebugMode, [editor])
const isReadonlyMode = useValue('isReadonlyMode', () => editor.getInstanceState().isReadonly, [
editor,
])
const isFocusMode = useValue('focus', () => editor.getInstanceState().isFocusMode, [editor])
const isDebugMode = useValue('debug', () => editor.getInstanceState().isDebugMode, [editor])
useKeyboardShortcuts()
useNativeClipboardEvents()

View file

@ -34,7 +34,7 @@ export const ContextMenu = function ContextMenu({ children }: { children: any })
}
} else {
// Weird route: selecting locked shapes on long press
if (editor.instanceState.isCoarsePointer) {
if (editor.getInstanceState().isCoarsePointer) {
const {
selectedShapes,
inputs: { currentPagePoint },
@ -74,9 +74,11 @@ export const ContextMenu = function ContextMenu({ children }: { children: any })
contextTLUiMenuSchema.length === 0 ||
(isReadonly && contextTLUiMenuSchema.every((item) => !item.readonlyOk))
const selectToolActive = useValue('isSelectToolActive', () => editor.currentToolId === 'select', [
editor,
])
const selectToolActive = useValue(
'isSelectToolActive',
() => editor.getCurrentToolId() === 'select',
[editor]
)
const disabled = !selectToolActive || noItemsToShow

View file

@ -65,7 +65,7 @@ export const DebugPanel = React.memo(function DebugPanel({
const CurrentState = track(function CurrentState() {
const editor = useEditor()
return <div className="tlui-debug-panel__current-state">{editor.root.path.value}</div>
return <div className="tlui-debug-panel__current-state">{editor.root.path.get()}</div>
})
const ShapeCount = function ShapeCount() {
@ -261,7 +261,7 @@ const DebugFlagToggle = track(function DebugFlagToggle({
label={flag.name
.replace(/([a-z0-9])([A-Z])/g, (m) => `${m[0]} ${m[1].toLowerCase()}`)
.replace(/^[a-z]/, (m) => m.toUpperCase())}
value={flag.value}
value={flag.get()}
onChange={(newValue) => {
flag.set(newValue)
onChange?.(newValue)

View file

@ -2,7 +2,9 @@ import { useEditor, usePresence, useValue } from '@tldraw/editor'
export function FollowingIndicator() {
const editor = useEditor()
const followingUserId = useValue('follow', () => editor.instanceState.followingUserId, [editor])
const followingUserId = useValue('follow', () => editor.getInstanceState().followingUserId, [
editor,
])
if (!followingUserId) return null
return <FollowingIndicatorInner userId={followingUserId} />
}

View file

@ -30,7 +30,9 @@ export function Minimap({ shapeFill, selectFill, viewportFill }: MinimapProps) {
const rPointing = React.useRef(false)
const isDarkMode = useIsDarkMode()
const devicePixelRatio = useComputed('dpr', () => editor.instanceState.devicePixelRatio, [editor])
const devicePixelRatio = useComputed('dpr', () => editor.getInstanceState().devicePixelRatio, [
editor,
])
const presences = React.useMemo(() => editor.store.query.records('instance_presence'), [editor])
const minimap = React.useMemo(() => new MinimapManager(editor), [editor])
@ -135,7 +137,7 @@ export function Minimap({ shapeFill, selectFill, viewportFill }: MinimapProps) {
name: 'pointer_move',
...getPointerInfo(e),
point: screenPoint,
isPen: editor.instanceState.isPenMode,
isPen: editor.getInstanceState().isPenMode,
}
editor.dispatch(info)
@ -164,7 +166,7 @@ export function Minimap({ shapeFill, selectFill, viewportFill }: MinimapProps) {
useQuickReactor(
'update when dpr changes',
() => {
const dpr = devicePixelRatio.value
const dpr = devicePixelRatio.get()
minimap.setDpr(dpr)
const canvas = rCanvas.current as HTMLCanvasElement
@ -191,7 +193,7 @@ export function Minimap({ shapeFill, selectFill, viewportFill }: MinimapProps) {
currentPageBounds: commonBoundsOfAllShapesOnCurrentPage,
} = editor
const _dpr = devicePixelRatio.value // dereference
const _dpr = devicePixelRatio.get() // dereference
minimap.contentPageBounds = commonBoundsOfAllShapesOnCurrentPage
? Box2d.Expand(commonBoundsOfAllShapesOnCurrentPage, viewportPageBounds)
@ -224,7 +226,7 @@ export function Minimap({ shapeFill, selectFill, viewportFill }: MinimapProps) {
})
minimap.pageBounds = allShapeBounds
minimap.collaborators = presences.value
minimap.collaborators = presences.get()
minimap.render()
},
[editor, minimap]

View file

@ -255,7 +255,7 @@ export class MinimapManager {
// Brush
{
const { brush } = editor.instanceState
const { brush } = editor.getInstanceState()
if (brush) {
const { x, y, w, h } = brush
ctx.beginPath()

View file

@ -46,9 +46,11 @@ export const PageMenu = function PageMenu() {
[editor]
)
const isCoarsePointer = useValue('isCoarsePointer', () => editor.instanceState.isCoarsePointer, [
editor,
])
const isCoarsePointer = useValue(
'isCoarsePointer',
() => editor.getInstanceState().isCoarsePointer,
[editor]
)
// The component has an "editing state" that may be toggled to expose additional controls
const [isEditing, setIsEditing] = useState(false)

View file

@ -5,7 +5,7 @@ import { Button } from './primitives/Button'
export const ExitPenMode = track(function ExitPenMode() {
const editor = useEditor()
const isPenMode = editor.instanceState.isPenMode
const isPenMode = editor.getInstanceState().isPenMode
const actions = useActions()

View file

@ -6,7 +6,7 @@ export const StopFollowing = track(function ExitPenMode() {
const editor = useEditor()
const actions = useActions()
if (!editor.instanceState.followingUserId) {
if (!editor.getInstanceState().followingUserId) {
return null
}

View file

@ -24,7 +24,9 @@ export function ToggleToolLockedButton({ activeToolId }: ToggleToolLockedButtonP
const breakpoint = useBreakpoint()
const msg = useTranslation()
const isToolLocked = useValue('is tool locked', () => editor.instanceState.isToolLocked, [editor])
const isToolLocked = useValue('is tool locked', () => editor.getInstanceState().isToolLocked, [
editor,
])
if (!activeToolId || NOT_LOCKABLE_TOOLS.includes(activeToolId)) return null

View file

@ -29,7 +29,7 @@ export const Toolbar = memo(function Toolbar() {
const toolbarItems = useToolbarSchema()
const laserTool = toolbarItems.find((item) => item.toolItem.id === 'laser')
const activeToolId = useValue('current tool id', () => editor.currentToolId, [editor])
const activeToolId = useValue('current tool id', () => editor.getCurrentToolId(), [editor])
const geoState = useValue('geo', () => editor.sharedStyles.getAsKnownValue(GeoShapeGeoStyle), [
editor,

View file

@ -278,12 +278,12 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
readonlyOk: true,
kbd: 'z',
onSelect(source) {
if (editor.root.current.value?.id === 'zoom') return
if (editor.root.current.get()?.id === 'zoom') return
trackEvent('zoom-tool', { source })
if (!(editor.inputs.shiftKey || editor.inputs.ctrlKey)) {
const currentTool = editor.root.current.value
if (currentTool && currentTool.current.value?.id === 'idle') {
const currentTool = editor.root.current.get()
if (currentTool && currentTool.current.get()?.id === 'idle') {
editor.setCurrentTool('zoom', { onInteractionEnd: currentTool.id, maskAs: 'zoom' })
}
}
@ -402,7 +402,7 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
trackEvent('duplicate-shapes', { source })
const ids = editor.selectedShapeIds
const commonBounds = Box2d.Common(compact(ids.map((id) => editor.getShapePageBounds(id))))
const offset = editor.instanceState.canMoveCamera
const offset = editor.getInstanceState().canMoveCamera
? {
x: commonBounds.width + 10,
y: 0,
@ -955,7 +955,7 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
trackEvent('toggle-transparent', { source })
editor.updateInstanceState(
{
exportBackground: !editor.instanceState.exportBackground,
exportBackground: !editor.getInstanceState().exportBackground,
},
{ ephemeral: true }
)
@ -970,7 +970,7 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
kbd: 'q',
onSelect(source) {
trackEvent('toggle-tool-lock', { source })
editor.updateInstanceState({ isToolLocked: !editor.instanceState.isToolLocked })
editor.updateInstanceState({ isToolLocked: !editor.getInstanceState().isToolLocked })
},
checkbox: true,
},
@ -1006,7 +1006,7 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
trackEvent('toggle-focus-mode', { source })
clearDialogs()
clearToasts()
editor.updateInstanceState({ isFocusMode: !editor.instanceState.isFocusMode })
editor.updateInstanceState({ isFocusMode: !editor.getInstanceState().isFocusMode })
})
})
},
@ -1019,7 +1019,7 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
kbd: "$'",
onSelect(source) {
trackEvent('toggle-grid-mode', { source })
editor.updateInstanceState({ isGridMode: !editor.instanceState.isGridMode })
editor.updateInstanceState({ isGridMode: !editor.getInstanceState().isGridMode })
},
checkbox: true,
},
@ -1031,7 +1031,7 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
onSelect(source) {
trackEvent('toggle-debug-mode', { source })
editor.updateInstanceState({
isDebugMode: !editor.instanceState.isDebugMode,
isDebugMode: !editor.getInstanceState().isDebugMode,
})
},
checkbox: true,

View file

@ -3,5 +3,5 @@ import { useEditor, useValue } from '@tldraw/editor'
/** @public */
export function useCanRedo() {
const editor = useEditor()
return useValue('useCanRedo', () => editor.canRedo, [editor])
return useValue('useCanRedo', () => editor.getCanRedo(), [editor])
}

View file

@ -3,5 +3,5 @@ import { useEditor, useValue } from '@tldraw/editor'
/** @public */
export function useCanUndo() {
const editor = useEditor()
return useValue('useCanUndo', () => editor.canUndo, [editor])
return useValue('useCanUndo', () => editor.getCanUndo(), [editor])
}

View file

@ -625,7 +625,9 @@ export function useNativeClipboardEvents() {
const editor = useEditor()
const trackEvent = useUiEvents()
const appIsFocused = useValue('editor.isFocused', () => editor.instanceState.isFocused, [editor])
const appIsFocused = useValue('editor.isFocused', () => editor.getInstanceState().isFocused, [
editor,
])
useEffect(() => {
if (!appIsFocused) return

Some files were not shown because too many files have changed in this diff Show more