Camera options followups (#3701)

This PR adds a slideshow example (similar to @TodePond's slides but more
on rails) as a way to put some pressure on camera controls.

Along the way, it fixes some issues I found with animations and the new
camera controls.

- forced changes will continue to force through animations
- animations no longer set unnecessary additional listeners
- animations end correctly
- updating camera options does not immediately update the camera (to
allow for animations, etc.)

It also changes the location of the "in front of the canvas" element so
that it is not hidden by the hit test blocking element.

### Change Type

- [x] `sdk` — Changes the tldraw SDK
- [x] `improvement` — Improving existing features
pull/3713/head
Steve Ruiz 2024-05-07 11:06:35 +01:00 zatwierdzone przez GitHub
rodzic fabba66c0f
commit ebc892a1a6
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
22 zmienionych plików z 636 dodań i 277 usunięć

Wyświetl plik

@ -44,7 +44,7 @@
"@types/qrcode": "^1.5.0",
"@types/react": "^18.2.47",
"@typescript-eslint/utils": "^5.59.0",
"@vitejs/plugin-react-swc": "^3.5.0",
"@vitejs/plugin-react-swc": "^3.6.0",
"dotenv": "^16.3.1",
"eslint": "^8.37.0",
"fast-glob": "^3.3.1",

Wyświetl plik

@ -50,7 +50,7 @@
},
"devDependencies": {
"@types/lodash": "^4.14.188",
"@vitejs/plugin-react": "^4.2.0",
"@vitejs/plugin-react-swc": "^3.6.0",
"dotenv": "^16.3.1",
"remark": "^15.0.1",
"remark-frontmatter": "^5.0.0",

Wyświetl plik

@ -145,7 +145,7 @@ const CameraOptionsControlPanel = track(() => {
useEffect(() => {
if (!editor) return
editor.batch(() => {
editor.setCameraOptions(cameraOptions, { immediate: true })
editor.setCameraOptions(cameraOptions)
editor.setCamera(editor.getCamera(), {
immediate: true,
})

Wyświetl plik

@ -13,16 +13,26 @@ export default function EditorFocusExample() {
}, [focused])
return (
<div style={{ padding: 32 }}>
<div style={{ display: 'flex', gap: 4 }}>
<input
id={'focus'}
type={'checkbox'}
onChange={(e) => {
setFocused(e.target.checked)
}}
/>
<label htmlFor={'focus'}>Focus</label>
<div
style={{ padding: 32 }}
onPointerDown={() => {
const editor = rEditorRef.current
if (editor && editor.getInstanceState().isFocused) {
editor.updateInstanceState({ isFocused: false })
}
}}
>
<div>
<div style={{ display: 'flex', gap: 4 }}>
<input
id="focus"
type="checkbox"
onChange={(e) => {
setFocused(e.target.checked)
}}
/>
<label htmlFor="focus">Focus</label>
</div>
</div>
<p>
The checkbox controls the editor's <code>instanceState.isFocused</code> property.
@ -39,6 +49,7 @@ export default function EditorFocusExample() {
}}
/>
</div>
<input type="text" placeholder="Test me" />
</div>
)
}

Wyświetl plik

@ -133,23 +133,21 @@ export function ImageAnnotationEditor({
* component hooks into camera updates to keep the camera constrained - try uploading a very long,
* thin image and seeing how the camera behaves.
*/
editor.setCameraOptions(
{
constraints: {
initialZoom: 'fit-max',
baseZoom: 'default',
bounds: { w: image.width, h: image.height, x: 0, y: 0 },
padding: { x: 32, y: 64 },
origin: { x: 0.5, y: 0.5 },
behavior: 'contain',
},
zoomSteps: [1, 2, 4, 8],
zoomSpeed: 1,
panSpeed: 1,
isLocked: false,
editor.setCameraOptions({
constraints: {
initialZoom: 'fit-max',
baseZoom: 'default',
bounds: { w: image.width, h: image.height, x: 0, y: 0 },
padding: { x: 32, y: 64 },
origin: { x: 0.5, y: 0.5 },
behavior: 'contain',
},
{ reset: true }
)
zoomSteps: [1, 2, 4, 8],
zoomSpeed: 1,
panSpeed: 1,
isLocked: false,
})
editor.setCamera(editor.getCamera(), { reset: true })
}, [editor, imageShapeId, image])
return (

Wyświetl plik

@ -117,20 +117,18 @@ export function PdfEditor({ pdf }: { pdf: Pdf }) {
)
function updateCameraBounds(isMobile: boolean) {
editor.setCameraOptions(
{
...DEFAULT_CAMERA_OPTIONS,
constraints: {
bounds: targetBounds,
padding: { x: isMobile ? 16 : 164, y: 64 },
origin: { x: 0.5, y: 0 },
initialZoom: 'fit-x-100',
baseZoom: 'default',
behavior: 'contain',
},
editor.setCameraOptions({
...DEFAULT_CAMERA_OPTIONS,
constraints: {
bounds: targetBounds,
padding: { x: isMobile ? 16 : 164, y: 64 },
origin: { x: 0.5, y: 0 },
initialZoom: 'fit-x-100',
baseZoom: 'default',
behavior: 'contain',
},
{ reset: true }
)
})
editor.setCamera(editor.getCamera(), { reset: true })
}
let isMobile = editor.getViewportScreenBounds().width < 840

Wyświetl plik

@ -0,0 +1,11 @@
---
title: Slideshow with Camera
component: ./SlideShowExample.tsx
category: use-cases
---
---
The `Tldraw` component provides the tldraw editor as a regular React component. You can put this component anywhere in your React project. In this example, we make the component take up the height and width of the container.
By default, the component does not persist between refreshes or sync locally between tabs. To keep your work after a refresh, check the [`persistenceKey`](/peristence-key) example.

Wyświetl plik

@ -0,0 +1,264 @@
import { useEffect, useState } from 'react'
import {
DEFAULT_CAMERA_OPTIONS,
Editor,
TLFrameShape,
Tldraw,
createShapeId,
stopEventPropagation,
transact,
useValue,
} from 'tldraw'
import 'tldraw/tldraw.css'
import { SLIDE_MARGIN, SLIDE_SIZE, SlidesProvider, useSlides } from './SlidesManager'
export default function SlideShowExample() {
return (
<div className="tldraw__editor">
<SlidesProvider>
<InsideSlidesContext />
</SlidesProvider>
</div>
)
}
function InsideSlidesContext() {
const [editor, setEditor] = useState<Editor | null>(null)
const slides = useSlides()
const currentSlide = useValue('currentSlide', () => slides.getCurrentSlide(), [slides])
useEffect(() => {
if (!editor) return
const nextBounds = {
x: currentSlide.index * (SLIDE_SIZE.w + SLIDE_MARGIN),
y: 0,
w: SLIDE_SIZE.w,
h: SLIDE_SIZE.h,
}
editor.setCameraOptions({
...DEFAULT_CAMERA_OPTIONS,
constraints: {
bounds: nextBounds,
behavior: 'contain',
initialZoom: 'fit-max',
baseZoom: 'fit-max',
origin: { x: 0.5, y: 0.5 },
padding: { x: 50, y: 50 },
},
})
editor.zoomToBounds(nextBounds, { force: true, animation: { duration: 500 } })
}, [editor, currentSlide])
const currentSlides = useValue('slides', () => slides.getCurrentSlides(), [slides])
useEffect(() => {
if (!editor) return
const ids = currentSlides.map((slide) => createShapeId(slide.id))
transact(() => {
for (let i = 0; i < currentSlides.length; i++) {
const shapeId = ids[i]
const slide = currentSlides[i]
const shape = editor.getShape(shapeId)
if (shape) {
if (shape.x === slide.index * (SLIDE_SIZE.w + SLIDE_MARGIN)) continue
// if name is still Slide and number, e.g Slide 1, update it. Use regex to test
const regex = /Slide \d+/
let name = (shape as TLFrameShape).props.name
if (regex.test((shape as TLFrameShape).props.name)) {
name = `Slide ${slide.index + 1}`
}
editor.updateShape<TLFrameShape>({
id: shapeId,
type: 'frame',
x: slide.index * (SLIDE_SIZE.w + SLIDE_MARGIN),
props: {
name,
},
})
} else {
editor.createShape<TLFrameShape>({
id: shapeId,
parentId: editor.getCurrentPageId(),
type: 'frame',
x: slide.index * (SLIDE_SIZE.w + SLIDE_MARGIN),
y: 0,
props: {
name: `Slide ${slide.index + 1}`,
w: SLIDE_SIZE.w,
h: SLIDE_SIZE.h,
},
})
}
}
})
const unsubs = [] as (() => void)[]
unsubs.push(
editor.sideEffects.registerBeforeChangeHandler('shape', (prev, next) => {
if (
ids.includes(next.id) &&
(next as TLFrameShape).props.name === (prev as TLFrameShape).props.name
)
return prev
return next
})
)
unsubs.push(
editor.sideEffects.registerBeforeChangeHandler('instance_page_state', (prev, next) => {
next.selectedShapeIds = next.selectedShapeIds.filter((id) => !ids.includes(id))
if (next.hoveredShapeId && ids.includes(next.hoveredShapeId)) next.hoveredShapeId = null
return next
})
)
return () => {
unsubs.forEach((fn) => fn())
}
}, [currentSlides, editor])
const handleMount = (editor: Editor) => {
setEditor(editor)
}
return <Tldraw onMount={handleMount} components={components} />
}
function Slides() {
const slides = useSlides()
const currentSlides = useValue('slides', () => slides.getCurrentSlides(), [slides])
const lowestIndex = currentSlides[0].index
const highestIndex = currentSlides[currentSlides.length - 1].index
return (
<>
{/* {currentSlides.map((slide) => (
<div
key={slide.id}
style={{
position: 'absolute',
top: 0,
left: (SLIDE_SIZE.w + SLIDE_MARGIN) * slide.index,
width: SLIDE_SIZE.w,
height: SLIDE_SIZE.h,
backgroundColor: 'white',
border: '1px solid black',
pointerEvents: 'all',
}}
onPointerDown={(e) => {
if (slide.id !== slides.getCurrentSlideId()) {
stopEventPropagation(e)
slides.setCurrentSlide(slide.id)
}
}}
/>
))} */}
{currentSlides.slice(0, -1).map((slide) => (
<button
key={slide.id + 'between'}
style={{
position: 'absolute',
top: SLIDE_SIZE.h / 2,
left: (slide.index + 1) * (SLIDE_SIZE.w + SLIDE_MARGIN) - (SLIDE_MARGIN + 40) / 2,
width: 40,
height: 40,
pointerEvents: 'all',
}}
onPointerDown={stopEventPropagation}
onClick={() => {
const newSlide = slides.newSlide(slide.index + 1)
slides.setCurrentSlide(newSlide.id)
}}
>
|
</button>
))}
<button
style={{
position: 'absolute',
top: SLIDE_SIZE.h / 2,
left: lowestIndex * (SLIDE_SIZE.w + SLIDE_MARGIN) - (40 + SLIDE_MARGIN * 0.1),
width: 40,
height: 40,
pointerEvents: 'all',
}}
onPointerDown={stopEventPropagation}
onClick={() => {
const slide = slides.newSlide(lowestIndex - 1)
slides.setCurrentSlide(slide.id)
}}
>
{`+`}
</button>
<button
style={{
position: 'absolute',
top: SLIDE_SIZE.h / 2,
left: highestIndex * (SLIDE_SIZE.w + SLIDE_MARGIN) + (SLIDE_SIZE.w + SLIDE_MARGIN * 0.1),
width: 40,
height: 40,
pointerEvents: 'all',
}}
onPointerDown={stopEventPropagation}
onClick={() => {
const slide = slides.newSlide(highestIndex + 1)
slides.setCurrentSlide(slide.id)
}}
>
{`+`}
</button>
</>
)
}
function SlideControls() {
const slides = useSlides()
return (
<>
<button
style={{
pointerEvents: 'all',
position: 'absolute',
top: '50%',
left: 0,
width: 50,
height: 50,
}}
onPointerDown={stopEventPropagation}
onClick={() => slides.prevSlide()}
>
{`<`}
</button>
<button
style={{
pointerEvents: 'all',
position: 'absolute',
top: '50%',
right: 0,
width: 50,
height: 50,
}}
onPointerDown={stopEventPropagation}
onClick={() => slides.nextSlide()}
>
{`>`}
</button>
</>
)
}
const components = {
OnTheCanvas: Slides,
InFrontOfTheCanvas: SlideControls,
}

Wyświetl plik

@ -0,0 +1,100 @@
import { createContext, ReactNode, useContext, useState } from 'react'
import { atom, computed, structuredClone, uniqueId } from 'tldraw'
export const SLIDE_SIZE = { x: 0, y: 0, w: 1600, h: 900 }
export const SLIDE_MARGIN = 100
type Slide = {
id: string
index: number
name: string
}
class SlidesManager {
private _slides = atom<Slide[]>('slide', [
{
id: '1',
index: 0,
name: 'Slide 1',
},
{
id: '2',
index: 1,
name: 'Slide 2',
},
{
id: '3',
index: 2,
name: 'Slide 3',
},
])
@computed getCurrentSlides() {
return this._slides.get().sort((a, b) => (a.index < b.index ? -1 : 1))
}
private _currentSlideId = atom('currentSlide', '1')
@computed getCurrentSlideId() {
return this._currentSlideId.get()
}
@computed getCurrentSlide() {
return this._slides.get().find((slide) => slide.id === this.getCurrentSlideId())!
}
setCurrentSlide(id: string) {
this._currentSlideId.set(id)
}
moveBy(delta: number) {
const slides = this.getCurrentSlides()
const currentIndex = slides.findIndex((slide) => slide.id === this.getCurrentSlideId())
const next = slides[currentIndex + delta]
if (!next) return
this._currentSlideId.set(next.id)
}
nextSlide() {
this.moveBy(1)
}
prevSlide() {
this.moveBy(-1)
}
newSlide(index: number) {
const slides = structuredClone(this.getCurrentSlides())
let bumping = false
for (const slide of slides) {
if (slide.index === index) {
bumping = true
}
if (bumping) {
slide.index++
}
}
const newSlide = {
id: uniqueId(),
index,
name: `Slide ${slides.length + 1}`,
}
this._slides.set([...slides, newSlide])
return newSlide
}
}
const slidesContext = createContext({} as SlidesManager)
export const SlidesProvider = ({ children }: { children: ReactNode }) => {
const [slideManager] = useState(() => new SlidesManager())
return <slidesContext.Provider value={slideManager}>{children}</slidesContext.Provider>
}
export function useSlides() {
return useContext(slidesContext)
}

Wyświetl plik

@ -3,7 +3,8 @@
"include": ["src", "e2e", "./vite.config.ts", "**/*.json"],
"exclude": ["node_modules", "dist", "**/*.css", ".tsbuild*", "./scripts/legacy-translations"],
"compilerOptions": {
"outDir": "./.tsbuild"
"outDir": "./.tsbuild",
"experimentalDecorators": true
},
"references": [
{

Wyświetl plik

@ -1,9 +1,9 @@
import react from '@vitejs/plugin-react'
import react from '@vitejs/plugin-react-swc'
import path from 'path'
import { PluginOption, defineConfig } from 'vite'
export default defineConfig({
plugins: [react(), exampleReadmePlugin()],
plugins: [react({ tsDecorators: true }), exampleReadmePlugin()],
root: path.join(__dirname, 'src'),
publicDir: path.join(__dirname, 'public'),
build: {

Wyświetl plik

@ -899,7 +899,7 @@ export class Editor extends EventEmitter<TLEventMap> {
sendBackward(shapes: TLShape[] | TLShapeId[]): this;
sendToBack(shapes: TLShape[] | TLShapeId[]): this;
setCamera(point: VecLike, opts?: TLCameraMoveOptions): this;
setCameraOptions(options: Partial<TLCameraOptions>, opts?: TLCameraMoveOptions): this;
setCameraOptions(options: Partial<TLCameraOptions>): this;
setCroppingShape(shape: null | TLShape | TLShapeId): this;
setCurrentPage(page: TLPage | TLPageId): this;
setCurrentTool(id: string, info?: {}): this;

Wyświetl plik

@ -28,6 +28,7 @@
--layer-canvas: 200;
--layer-shapes: 300;
--layer-overlays: 400;
--layer-in-front: 500;
--layer-following-indicator: 1000;
--layer-blocker: 10000;
@ -265,7 +266,8 @@ input,
.tl-overlays {
position: absolute;
inset: 0px;
top: 0px;
left: 0px;
height: 100%;
width: 100%;
contain: strict;
@ -291,6 +293,17 @@ input,
pointer-events: none;
}
.tl-front {
z-index: 600;
position: absolute;
inset: 0px;
height: 100%;
width: 100%;
overflow: clip;
content-visibility: auto;
touch-action: none;
pointer-events: none;
}
/* ------------------- Background ------------------- */
.tl-background__wrapper {

Wyświetl plik

@ -367,7 +367,22 @@ function Layout({
useFocusEvents(autoFocus)
useOnMount(onMount)
return <>{children}</>
return (
<>
{children}
<InFrontOfTheCanvasWrapper />
</>
)
}
function InFrontOfTheCanvasWrapper() {
const { InFrontOfTheCanvas } = useEditorComponents()
if (!InFrontOfTheCanvas) return null
return (
<div className="tl-front">
<InFrontOfTheCanvas />
</div>
)
}
function Crash({ crashingError }: { crashingError: unknown }): null {

Wyświetl plik

@ -169,7 +169,6 @@ export function DefaultCanvas({ className }: TLCanvasComponentProps) {
<SelectionForegroundWrapper />
<LiveCollaborators />
</div>
<InFrontOfTheCanvasWrapper />
</div>
<MovingCameraHitTestBlocker />
</div>
@ -647,12 +646,6 @@ function OnTheCanvasWrapper() {
return <OnTheCanvas />
}
function InFrontOfTheCanvasWrapper() {
const { InFrontOfTheCanvas } = useEditorComponents()
if (!InFrontOfTheCanvas) return null
return <InFrontOfTheCanvas />
}
function MovingCameraHitTestBlocker() {
const editor = useEditor()
const cameraState = useValue('camera state', () => editor.getCameraState(), [editor])

Wyświetl plik

@ -2148,26 +2148,24 @@ export class Editor extends EventEmitter<TLEventMap> {
}
/**
* Set the camera options.
* Set the camera options. Changing the options won't immediately change the camera itself, so you may want to call `setCamera` after changing the options.
*
* @example
* ```ts
* editor.setCameraOptions(myCameraOptions)
* editor.setCameraOptions(myCameraOptions, { immediate: true, force: true, initial: false })
* editor.setCamera(editor.getCamera())
* ```
*
* @param options - The camera options to set.
* @param opts - The options for the change.
*
* @public */
setCameraOptions(options: Partial<TLCameraOptions>, opts?: TLCameraMoveOptions) {
setCameraOptions(options: Partial<TLCameraOptions>) {
const next = structuredClone({
...this.getCameraOptions(),
...this._cameraOptions.__unsafe__getWithoutCapture(),
...options,
})
if (next.zoomSteps?.length < 1) next.zoomSteps = [1]
this._cameraOptions.set(next)
this.setCamera(this.getCamera(), opts)
return this
}
@ -2637,16 +2635,16 @@ export class Editor extends EventEmitter<TLEventMap> {
bounds: BoxLike,
opts?: { targetZoom?: number; inset?: number } & TLCameraMoveOptions
): this {
if (this.getCameraOptions().isLocked) return this
const cameraOptions = this._cameraOptions.__unsafe__getWithoutCapture()
if (cameraOptions.isLocked) return this
const viewportScreenBounds = this.getViewportScreenBounds()
const inset = opts?.inset ?? Math.min(256, viewportScreenBounds.width * 0.28)
const baseZoom = this.getBaseZoom()
const { zoomSteps } = this.getCameraOptions()
const zoomMin = zoomSteps[0]
const zoomMax = last(zoomSteps)!
const zoomMin = cameraOptions.zoomSteps[0]
const zoomMax = last(cameraOptions.zoomSteps)!
let zoom = clamp(
Math.min(
@ -2701,19 +2699,13 @@ export class Editor extends EventEmitter<TLEventMap> {
private _animateViewport(ms: number): void {
if (!this._viewportAnimation) return
const cancelAnimation = () => {
this.off('tick', this._animateViewport)
this.off('stop-camera-animation', cancelAnimation)
this._viewportAnimation = null
}
this.once('stop-camera-animation', cancelAnimation)
this._viewportAnimation.elapsed += ms
const { elapsed, easing, duration, start, end } = this._viewportAnimation
if (elapsed > duration) {
this.off('tick', this._animateViewport)
this._viewportAnimation = null
this._setCamera(new Vec(-end.x, -end.y, this.getViewportScreenBounds().width / end.width))
return
}
@ -2725,7 +2717,9 @@ export class Editor extends EventEmitter<TLEventMap> {
const top = start.minY + (end.minY - start.minY) * t
const right = start.maxX + (end.maxX - start.maxX) * t
this._setCamera(new Vec(-left, -top, this.getViewportScreenBounds().width / (right - left)))
this._setCamera(new Vec(-left, -top, this.getViewportScreenBounds().width / (right - left)), {
force: true,
})
}
/** @internal */
@ -2733,8 +2727,9 @@ export class Editor extends EventEmitter<TLEventMap> {
targetViewportPage: Box,
opts = { animation: DEFAULT_ANIMATION_OPTIONS } as TLCameraMoveOptions
) {
if (!opts.animation) return
const { duration = 0, easing = EASINGS.easeInOutCubic } = opts.animation
const { animation, ...rest } = opts
if (!animation) return
const { duration = 0, easing = EASINGS.easeInOutCubic } = animation
const animationSpeed = this.user.getAnimationSpeed()
const viewportPageBounds = this.getViewportPageBounds()
@ -2753,7 +2748,8 @@ export class Editor extends EventEmitter<TLEventMap> {
-targetViewportPage.x,
-targetViewportPage.y,
this.getViewportScreenBounds().width / targetViewportPage.width
)
),
{ ...rest }
)
}
@ -2766,6 +2762,12 @@ export class Editor extends EventEmitter<TLEventMap> {
end: targetViewportPage.clone(),
}
// If we ever get a "stop-camera-animation" event, we stop
this.once('stop-camera-animation', () => {
this.off('tick', this._animateViewport)
this._viewportAnimation = null
})
// On each tick, animate the viewport
this.on('tick', this._animateViewport)

Wyświetl plik

@ -56,6 +56,11 @@ export class Translating extends StateNode {
) => {
const { isCreating = false, onCreate = () => void null } = info
if (!this.editor.getSelectedShapeIds()?.length) {
this.parent.transition('idle')
return
}
this.info = info
this.parent.setCurrentToolIdMask(info.onInteractionEnd)
this.isCreating = isCreating
@ -251,8 +256,17 @@ export class Translating extends StateNode {
}
}
protected handleChange() {
const { movingShapes } = this.snapshot
protected updateShapes() {
const { snapshot } = this
this.dragAndDropManager.updateDroppingNode(snapshot.movingShapes, this.updateParentTransforms)
moveShapesToPoint({
editor: this.editor,
snapshot,
})
const { movingShapes } = snapshot
const changes: TLShapePartial[] = []
@ -270,18 +284,6 @@ export class Translating extends StateNode {
}
}
protected updateShapes() {
const { snapshot } = this
this.dragAndDropManager.updateDroppingNode(snapshot.movingShapes, this.updateParentTransforms)
moveShapesToPoint({
editor: this.editor,
snapshot,
})
this.handleChange()
}
protected updateParentTransforms = () => {
const {
editor,

Wyświetl plik

@ -361,8 +361,8 @@ describe('When constraints are contain', () => {
describe('Zoom reset positions based on origin', () => {
it('Default .5, .5 origin', () => {
editor.setCameraOptions(
{
editor
.setCameraOptions({
...DEFAULT_CAMERA_OPTIONS,
constraints: {
...DEFAULT_CONSTRAINTS,
@ -370,17 +370,17 @@ describe('Zoom reset positions based on origin', () => {
origin: { x: 0.5, y: 0.5 },
initialZoom: 'default',
},
},
{ reset: true }
)
})
.setCamera(editor.getCamera(), { reset: true })
expect(editor.getCamera()).toMatchObject({ x: 200, y: 50, z: 1 })
editor.zoomIn().resetZoom().forceTick()
expect(editor.getCamera()).toMatchObject({ x: 200, y: 50, z: 1 })
})
it('0 0 origin', () => {
editor.setCameraOptions(
{
editor
.setCameraOptions({
...DEFAULT_CAMERA_OPTIONS,
constraints: {
...DEFAULT_CONSTRAINTS,
@ -388,17 +388,17 @@ describe('Zoom reset positions based on origin', () => {
origin: { x: 0, y: 0 },
initialZoom: 'default',
},
},
{ reset: true }
)
})
.setCamera(editor.getCamera(), { reset: true })
expect(editor.getCamera()).toMatchObject({ x: 0, y: 0, z: 1 })
editor.zoomIn().resetZoom().forceTick()
expect(editor.getCamera()).toMatchObject({ x: 0, y: 0, z: 1 })
})
it('1 1 origin', () => {
editor.setCameraOptions(
{
editor
.setCameraOptions({
...DEFAULT_CAMERA_OPTIONS,
constraints: {
...DEFAULT_CONSTRAINTS,
@ -406,9 +406,9 @@ describe('Zoom reset positions based on origin', () => {
origin: { x: 1, y: 1 },
initialZoom: 'default',
},
},
{ reset: true }
)
})
.setCamera(editor.getCamera(), { reset: true })
expect(editor.getCamera()).toMatchObject({ x: 400, y: 100, z: 1 })
editor.zoomIn().resetZoom().forceTick()
expect(editor.getCamera()).toMatchObject({ x: 400, y: 100, z: 1 })
@ -417,8 +417,8 @@ describe('Zoom reset positions based on origin', () => {
describe('CameraOptions.constraints.initialZoom + behavior', () => {
it('When fit is default', () => {
editor.setCameraOptions(
{
editor
.setCameraOptions({
...DEFAULT_CAMERA_OPTIONS,
constraints: {
...DEFAULT_CONSTRAINTS,
@ -426,17 +426,17 @@ describe('CameraOptions.constraints.initialZoom + behavior', () => {
origin: { x: 0.5, y: 0.5 },
initialZoom: 'default',
},
},
{ reset: true }
)
})
.setCamera(editor.getCamera(), { reset: true })
expect(editor.getCamera()).toMatchObject({ x: 200, y: 50, z: 1 })
editor.zoomIn().resetZoom().forceTick()
expect(editor.getCamera()).toMatchObject({ x: 200, y: 50, z: 1 })
})
it('When fit is fit-max', () => {
editor.setCameraOptions(
{
editor
.setCameraOptions({
...DEFAULT_CAMERA_OPTIONS,
constraints: {
...DEFAULT_CONSTRAINTS,
@ -444,17 +444,16 @@ describe('CameraOptions.constraints.initialZoom + behavior', () => {
origin: { x: 0.5, y: 0.5 },
initialZoom: 'fit-max',
},
},
{ reset: true }
)
})
.setCamera(editor.getCamera(), { reset: true })
// y should be 0 because the viewport width is bigger than the height
expect(editor.getCamera()).toCloselyMatchObject({ x: 111.11, y: 0, z: 1.125 }, 2)
editor.zoomIn().resetZoom().forceTick()
expect(editor.getCamera()).toCloselyMatchObject({ x: 111.11, y: 0, z: 1.125 }, 2)
editor.setCameraOptions(
{
editor
.setCameraOptions({
...DEFAULT_CAMERA_OPTIONS,
constraints: {
...DEFAULT_CONSTRAINTS,
@ -463,9 +462,8 @@ describe('CameraOptions.constraints.initialZoom + behavior', () => {
origin: { x: 0.5, y: 0.5 },
initialZoom: 'fit-max',
},
},
{ reset: true }
)
})
.setCamera(editor.getCamera(), { reset: true })
// y should be 0 because the viewport width is bigger than the height
expect(editor.getCamera()).toCloselyMatchObject({ x: 666.66, y: 0, z: 0.75 }, 2)
@ -474,8 +472,8 @@ describe('CameraOptions.constraints.initialZoom + behavior', () => {
// The proportions of the bounds don't matter, it's the proportion of the viewport that decides which axis gets fit
editor.updateViewportScreenBounds(new Box(0, 0, 900, 1600))
editor.setCameraOptions(
{
editor
.setCameraOptions({
...DEFAULT_CAMERA_OPTIONS,
constraints: {
...DEFAULT_CONSTRAINTS,
@ -483,9 +481,8 @@ describe('CameraOptions.constraints.initialZoom + behavior', () => {
origin: { x: 0.5, y: 0.5 },
initialZoom: 'fit-max',
},
},
{ reset: true }
)
})
.setCamera(editor.getCamera(), { reset: true })
expect(editor.getCamera()).toCloselyMatchObject({ x: 0, y: 666.66, z: 0.75 }, 2)
editor.zoomIn().resetZoom().forceTick()
@ -493,8 +490,8 @@ describe('CameraOptions.constraints.initialZoom + behavior', () => {
})
it('When fit is fit-min', () => {
editor.setCameraOptions(
{
editor
.setCameraOptions({
...DEFAULT_CAMERA_OPTIONS,
constraints: {
...DEFAULT_CONSTRAINTS,
@ -502,17 +499,16 @@ describe('CameraOptions.constraints.initialZoom + behavior', () => {
origin: { x: 0.5, y: 0.5 },
initialZoom: 'fit-min',
},
},
{ reset: true }
)
})
.setCamera(editor.getCamera(), { reset: true })
// x should be 0 because the viewport width is bigger than the height
expect(editor.getCamera()).toCloselyMatchObject({ x: 0, y: -62.5, z: 1.333 }, 2)
editor.zoomIn().resetZoom().forceTick()
expect(editor.getCamera()).toCloselyMatchObject({ x: 0, y: -62.5, z: 1.333 }, 2)
editor.setCameraOptions(
{
editor
.setCameraOptions({
...DEFAULT_CAMERA_OPTIONS,
constraints: {
...DEFAULT_CONSTRAINTS,
@ -521,9 +517,8 @@ describe('CameraOptions.constraints.initialZoom + behavior', () => {
origin: { x: 0.5, y: 0.5 },
initialZoom: 'fit-min',
},
},
{ reset: true }
)
})
.setCamera(editor.getCamera(), { reset: true })
// x should be 0 because the viewport width is bigger than the height
expect(editor.getCamera()).toCloselyMatchObject({ x: 0, y: -375, z: 2 }, 2)
@ -532,8 +527,8 @@ describe('CameraOptions.constraints.initialZoom + behavior', () => {
// The proportions of the bounds don't matter, it's the proportion of the viewport that decides which axis gets fit
editor.updateViewportScreenBounds(new Box(0, 0, 900, 1600))
editor.setCameraOptions(
{
editor
.setCameraOptions({
...DEFAULT_CAMERA_OPTIONS,
constraints: {
...DEFAULT_CONSTRAINTS,
@ -541,9 +536,8 @@ describe('CameraOptions.constraints.initialZoom + behavior', () => {
origin: { x: 0.5, y: 0.5 },
initialZoom: 'fit-min',
},
},
{ reset: true }
)
})
.setCamera(editor.getCamera(), { reset: true })
expect(editor.getCamera()).toCloselyMatchObject({ x: -375, y: 0, z: 2 }, 2)
editor.zoomIn().resetZoom().forceTick()
@ -553,8 +547,8 @@ describe('CameraOptions.constraints.initialZoom + behavior', () => {
it('When fit is fit-min-100', () => {
editor.updateViewportScreenBounds(new Box(0, 0, 1600, 900))
editor.setCameraOptions(
{
editor
.setCameraOptions({
...DEFAULT_CAMERA_OPTIONS,
constraints: {
...DEFAULT_CONSTRAINTS,
@ -562,17 +556,16 @@ describe('CameraOptions.constraints.initialZoom + behavior', () => {
origin: { x: 0.5, y: 0.5 },
initialZoom: 'fit-min-100',
},
},
{ reset: true }
)
})
.setCamera(editor.getCamera(), { reset: true })
// Max 1 on initial / reset
expect(editor.getCamera()).toCloselyMatchObject({ x: 200, y: 50, z: 1 }, 2)
// Min is regular
editor.updateViewportScreenBounds(new Box(0, 0, 800, 450))
editor.setCameraOptions(
{
editor
.setCameraOptions({
...DEFAULT_CAMERA_OPTIONS,
constraints: {
...DEFAULT_CONSTRAINTS,
@ -580,9 +573,8 @@ describe('CameraOptions.constraints.initialZoom + behavior', () => {
origin: { x: 0.5, y: 0.5 },
initialZoom: 'fit-min-100',
},
},
{ reset: true }
)
})
.setCamera(editor.getCamera(), { reset: true })
expect(editor.getCamera()).toCloselyMatchObject({ x: 0, y: -62.5, z: 0.66 }, 2)
})
@ -590,8 +582,8 @@ describe('CameraOptions.constraints.initialZoom + behavior', () => {
describe('Padding', () => {
it('sets when padding is zero', () => {
editor.setCameraOptions(
{
editor
.setCameraOptions({
...DEFAULT_CAMERA_OPTIONS,
constraints: {
...DEFAULT_CONSTRAINTS,
@ -600,9 +592,8 @@ describe('Padding', () => {
padding: { x: 0, y: 0 },
initialZoom: 'fit-max',
},
},
{ reset: true }
)
})
.setCamera(editor.getCamera(), { reset: true })
// y should be 0 because the viewport width is bigger than the height
expect(editor.getCamera()).toCloselyMatchObject({ x: 111.11, y: 0, z: 1.125 }, 2)
@ -611,8 +602,8 @@ describe('Padding', () => {
})
it('sets when padding is 100, 0', () => {
editor.setCameraOptions(
{
editor
.setCameraOptions({
...DEFAULT_CAMERA_OPTIONS,
constraints: {
...DEFAULT_CONSTRAINTS,
@ -621,17 +612,16 @@ describe('Padding', () => {
padding: { x: 100, y: 0 },
initialZoom: 'fit-max',
},
},
{ reset: true }
)
})
.setCamera(editor.getCamera(), { reset: true })
// no change because the horizontal axis has extra space available
expect(editor.getCamera()).toCloselyMatchObject({ x: 111.11, y: 0, z: 1.125 }, 2)
editor.zoomIn().resetZoom().forceTick()
expect(editor.getCamera()).toCloselyMatchObject({ x: 111.11, y: 0, z: 1.125 }, 2)
editor.setCameraOptions(
{
editor
.setCameraOptions({
...DEFAULT_CAMERA_OPTIONS,
constraints: {
...DEFAULT_CONSTRAINTS,
@ -640,9 +630,8 @@ describe('Padding', () => {
padding: { x: 200, y: 0 },
initialZoom: 'fit-max',
},
},
{ reset: true }
)
})
.setCamera(editor.getCamera(), { reset: true })
// now we're pinching
expect(editor.getCamera()).toCloselyMatchObject({ x: 200, y: 50, z: 1 }, 2)
@ -651,8 +640,8 @@ describe('Padding', () => {
})
it('sets when padding is 0 x 100', () => {
editor.setCameraOptions(
{
editor
.setCameraOptions({
...DEFAULT_CAMERA_OPTIONS,
constraints: {
...DEFAULT_CONSTRAINTS,
@ -661,9 +650,8 @@ describe('Padding', () => {
padding: { x: 0, y: 100 },
initialZoom: 'fit-max',
},
},
{ reset: true }
)
})
.setCamera(editor.getCamera(), { reset: true, immediate: true })
// y should be 0 because the viewport width is bigger than the height
expect(editor.getCamera()).toCloselyMatchObject({ x: 314.28, y: 114.28, z: 0.875 }, 2)

Wyświetl plik

@ -97,22 +97,22 @@ export function getHashForObject(obj: any): string;
export function getHashForString(string: string): string;
// @public
export function getIndexAbove(below: IndexKey): IndexKey;
export function getIndexAbove(below?: IndexKey | undefined): IndexKey;
// @public
export function getIndexBelow(above: IndexKey): IndexKey;
export function getIndexBelow(above?: IndexKey | undefined): IndexKey;
// @public
export function getIndexBetween(below: IndexKey, above?: IndexKey): IndexKey;
export function getIndexBetween(below: IndexKey | undefined, above: IndexKey | undefined): IndexKey;
// @public
export function getIndices(n: number, start?: IndexKey): IndexKey[];
// @public
export function getIndicesAbove(below: IndexKey, n: number): IndexKey[];
export function getIndicesAbove(below: IndexKey | undefined, n: number): IndexKey[];
// @public
export function getIndicesBelow(above: IndexKey, n: number): IndexKey[];
export function getIndicesBelow(above: IndexKey | undefined, n: number): IndexKey[];
// @public
export function getIndicesBetween(below: IndexKey | undefined, above: IndexKey | undefined, n: number): IndexKey[];

Wyświetl plik

@ -33,7 +33,7 @@ export function getIndicesBetween(
* @param n - The number of indices to get.
* @public
*/
export function getIndicesAbove(below: IndexKey, n: number) {
export function getIndicesAbove(below: IndexKey | undefined, n: number) {
return generateNKeysBetween(below, undefined, n)
}
@ -43,7 +43,7 @@ export function getIndicesAbove(below: IndexKey, n: number) {
* @param n - The number of indices to get.
* @public
*/
export function getIndicesBelow(above: IndexKey, n: number) {
export function getIndicesBelow(above: IndexKey | undefined, n: number) {
return generateNKeysBetween(undefined, above, n)
}
@ -53,7 +53,7 @@ export function getIndicesBelow(above: IndexKey, n: number) {
* @param above - The index above.
* @public
*/
export function getIndexBetween(below: IndexKey, above?: IndexKey) {
export function getIndexBetween(below: IndexKey | undefined, above: IndexKey | undefined) {
return generateNKeysBetween(below, above, 1)[0]
}
@ -62,7 +62,7 @@ export function getIndexBetween(below: IndexKey, above?: IndexKey) {
* @param below - The index below.
* @public
*/
export function getIndexAbove(below: IndexKey) {
export function getIndexAbove(below?: IndexKey | undefined) {
return generateNKeysBetween(below, undefined, 1)[0]
}
@ -71,7 +71,7 @@ export function getIndexAbove(below: IndexKey) {
* @param above - The index above.
* @public
*/
export function getIndexBelow(above: IndexKey) {
export function getIndexBelow(above?: IndexKey | undefined) {
return generateNKeysBetween(undefined, above, 1)[0]
}

Wyświetl plik

@ -25,7 +25,7 @@
"@types/react-dom": "^18.0.11",
"@typescript-eslint/eslint-plugin": "^5.59.0",
"@typescript-eslint/parser": "^5.59.0",
"@vitejs/plugin-react-swc": "^3.0.0",
"@vitejs/plugin-react-swc": "^3.6.0",
"eslint": "^8.38.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.3.4",

153
yarn.lock
Wyświetl plik

@ -898,7 +898,7 @@ __metadata:
languageName: node
linkType: hard
"@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.18.6, @babel/core@npm:^7.20.7, @babel/core@npm:^7.23.5":
"@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.18.6, @babel/core@npm:^7.20.7":
version: 7.23.9
resolution: "@babel/core@npm:7.23.9"
dependencies:
@ -1933,28 +1933,6 @@ __metadata:
languageName: node
linkType: hard
"@babel/plugin-transform-react-jsx-self@npm:^7.23.3":
version: 7.23.3
resolution: "@babel/plugin-transform-react-jsx-self@npm:7.23.3"
dependencies:
"@babel/helper-plugin-utils": "npm:^7.22.5"
peerDependencies:
"@babel/core": ^7.0.0-0
checksum: 882bf56bc932d015c2d83214133939ddcf342e5bcafa21f1a93b19f2e052145115e1e0351730897fd66e5f67cad7875b8a8d81ceb12b6e2a886ad0102cb4eb1f
languageName: node
linkType: hard
"@babel/plugin-transform-react-jsx-source@npm:^7.23.3":
version: 7.23.3
resolution: "@babel/plugin-transform-react-jsx-source@npm:7.23.3"
dependencies:
"@babel/helper-plugin-utils": "npm:^7.22.5"
peerDependencies:
"@babel/core": ^7.0.0-0
checksum: 92287fb797e522d99bdc77eaa573ce79ff0ad9f1cf4e7df374645e28e51dce0adad129f6f075430b129b5bac8dad843f65021970e12e992d6d6671f0d65bb1e0
languageName: node
linkType: hard
"@babel/plugin-transform-regenerator@npm:^7.23.3":
version: 7.23.3
resolution: "@babel/plugin-transform-regenerator@npm:7.23.3"
@ -7164,91 +7142,91 @@ __metadata:
languageName: node
linkType: hard
"@swc/core-darwin-arm64@npm:1.3.103":
version: 1.3.103
resolution: "@swc/core-darwin-arm64@npm:1.3.103"
"@swc/core-darwin-arm64@npm:1.4.17":
version: 1.4.17
resolution: "@swc/core-darwin-arm64@npm:1.4.17"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
"@swc/core-darwin-x64@npm:1.3.103":
version: 1.3.103
resolution: "@swc/core-darwin-x64@npm:1.3.103"
"@swc/core-darwin-x64@npm:1.4.17":
version: 1.4.17
resolution: "@swc/core-darwin-x64@npm:1.4.17"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
"@swc/core-linux-arm-gnueabihf@npm:1.3.103":
version: 1.3.103
resolution: "@swc/core-linux-arm-gnueabihf@npm:1.3.103"
"@swc/core-linux-arm-gnueabihf@npm:1.4.17":
version: 1.4.17
resolution: "@swc/core-linux-arm-gnueabihf@npm:1.4.17"
conditions: os=linux & cpu=arm
languageName: node
linkType: hard
"@swc/core-linux-arm64-gnu@npm:1.3.103":
version: 1.3.103
resolution: "@swc/core-linux-arm64-gnu@npm:1.3.103"
"@swc/core-linux-arm64-gnu@npm:1.4.17":
version: 1.4.17
resolution: "@swc/core-linux-arm64-gnu@npm:1.4.17"
conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node
linkType: hard
"@swc/core-linux-arm64-musl@npm:1.3.103":
version: 1.3.103
resolution: "@swc/core-linux-arm64-musl@npm:1.3.103"
"@swc/core-linux-arm64-musl@npm:1.4.17":
version: 1.4.17
resolution: "@swc/core-linux-arm64-musl@npm:1.4.17"
conditions: os=linux & cpu=arm64 & libc=musl
languageName: node
linkType: hard
"@swc/core-linux-x64-gnu@npm:1.3.103":
version: 1.3.103
resolution: "@swc/core-linux-x64-gnu@npm:1.3.103"
"@swc/core-linux-x64-gnu@npm:1.4.17":
version: 1.4.17
resolution: "@swc/core-linux-x64-gnu@npm:1.4.17"
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard
"@swc/core-linux-x64-musl@npm:1.3.103":
version: 1.3.103
resolution: "@swc/core-linux-x64-musl@npm:1.3.103"
"@swc/core-linux-x64-musl@npm:1.4.17":
version: 1.4.17
resolution: "@swc/core-linux-x64-musl@npm:1.4.17"
conditions: os=linux & cpu=x64 & libc=musl
languageName: node
linkType: hard
"@swc/core-win32-arm64-msvc@npm:1.3.103":
version: 1.3.103
resolution: "@swc/core-win32-arm64-msvc@npm:1.3.103"
"@swc/core-win32-arm64-msvc@npm:1.4.17":
version: 1.4.17
resolution: "@swc/core-win32-arm64-msvc@npm:1.4.17"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
"@swc/core-win32-ia32-msvc@npm:1.3.103":
version: 1.3.103
resolution: "@swc/core-win32-ia32-msvc@npm:1.3.103"
"@swc/core-win32-ia32-msvc@npm:1.4.17":
version: 1.4.17
resolution: "@swc/core-win32-ia32-msvc@npm:1.4.17"
conditions: os=win32 & cpu=ia32
languageName: node
linkType: hard
"@swc/core-win32-x64-msvc@npm:1.3.103":
version: 1.3.103
resolution: "@swc/core-win32-x64-msvc@npm:1.3.103"
"@swc/core-win32-x64-msvc@npm:1.4.17":
version: 1.4.17
resolution: "@swc/core-win32-x64-msvc@npm:1.4.17"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
"@swc/core@npm:^1.3.55, @swc/core@npm:^1.3.96":
version: 1.3.103
resolution: "@swc/core@npm:1.3.103"
"@swc/core@npm:^1.3.107, @swc/core@npm:^1.3.55":
version: 1.4.17
resolution: "@swc/core@npm:1.4.17"
dependencies:
"@swc/core-darwin-arm64": "npm:1.3.103"
"@swc/core-darwin-x64": "npm:1.3.103"
"@swc/core-linux-arm-gnueabihf": "npm:1.3.103"
"@swc/core-linux-arm64-gnu": "npm:1.3.103"
"@swc/core-linux-arm64-musl": "npm:1.3.103"
"@swc/core-linux-x64-gnu": "npm:1.3.103"
"@swc/core-linux-x64-musl": "npm:1.3.103"
"@swc/core-win32-arm64-msvc": "npm:1.3.103"
"@swc/core-win32-ia32-msvc": "npm:1.3.103"
"@swc/core-win32-x64-msvc": "npm:1.3.103"
"@swc/counter": "npm:^0.1.1"
"@swc/core-darwin-arm64": "npm:1.4.17"
"@swc/core-darwin-x64": "npm:1.4.17"
"@swc/core-linux-arm-gnueabihf": "npm:1.4.17"
"@swc/core-linux-arm64-gnu": "npm:1.4.17"
"@swc/core-linux-arm64-musl": "npm:1.4.17"
"@swc/core-linux-x64-gnu": "npm:1.4.17"
"@swc/core-linux-x64-musl": "npm:1.4.17"
"@swc/core-win32-arm64-msvc": "npm:1.4.17"
"@swc/core-win32-ia32-msvc": "npm:1.4.17"
"@swc/core-win32-x64-msvc": "npm:1.4.17"
"@swc/counter": "npm:^0.1.2"
"@swc/types": "npm:^0.1.5"
peerDependencies:
"@swc/helpers": ^0.5.0
@ -7276,14 +7254,14 @@ __metadata:
peerDependenciesMeta:
"@swc/helpers":
optional: true
checksum: 65eff8264dfd73088b226091fc53d5242a8c9576caa76b27a91eeb30714a245ee4c92093ede50c3621dbd99315ca213e3d76ea73208eeacd3e4d0c1f32815309
checksum: 743da3648335b10901f9c2d6c7b332f90913f9ce0e09c040eb9b5cce71dde4e1c9dd6c78c05700433ffc173194f7857c5e0a6146c39ec4bf392f875397ed96d3
languageName: node
linkType: hard
"@swc/counter@npm:^0.1.1":
version: 0.1.2
resolution: "@swc/counter@npm:0.1.2"
checksum: 8427c594f1f0cf44b83885e9c8fe1e370c9db44ae96e07a37c117a6260ee97797d0709483efbcc244e77bac578690215f45b23254c4cd8a70fb25ddbb50bf33e
"@swc/counter@npm:^0.1.2":
version: 0.1.3
resolution: "@swc/counter@npm:0.1.3"
checksum: df8f9cfba9904d3d60f511664c70d23bb323b3a0803ec9890f60133954173047ba9bdeabce28cd70ba89ccd3fd6c71c7b0bd58be85f611e1ffbe5d5c18616598
languageName: node
linkType: hard
@ -7830,7 +7808,7 @@ __metadata:
languageName: node
linkType: hard
"@types/babel__core@npm:^7.1.14, @types/babel__core@npm:^7.20.5":
"@types/babel__core@npm:^7.1.14":
version: 7.20.5
resolution: "@types/babel__core@npm:7.20.5"
dependencies:
@ -8996,29 +8974,14 @@ __metadata:
languageName: node
linkType: hard
"@vitejs/plugin-react-swc@npm:^3.5.0":
version: 3.5.0
resolution: "@vitejs/plugin-react-swc@npm:3.5.0"
"@vitejs/plugin-react-swc@npm:^3.6.0":
version: 3.6.0
resolution: "@vitejs/plugin-react-swc@npm:3.6.0"
dependencies:
"@swc/core": "npm:^1.3.96"
"@swc/core": "npm:^1.3.107"
peerDependencies:
vite: ^4 || ^5
checksum: ca3315e2000303aa6da35b6bedc3a5c57550c5576dfa12e12d097a2f69f8c7bc68e6ce7a068685ae13fcbe121d43c133b47a0d4637ac58e366471dd6645bf8ac
languageName: node
linkType: hard
"@vitejs/plugin-react@npm:^4.2.0":
version: 4.2.1
resolution: "@vitejs/plugin-react@npm:4.2.1"
dependencies:
"@babel/core": "npm:^7.23.5"
"@babel/plugin-transform-react-jsx-self": "npm:^7.23.3"
"@babel/plugin-transform-react-jsx-source": "npm:^7.23.3"
"@types/babel__core": "npm:^7.20.5"
react-refresh: "npm:^0.14.0"
peerDependencies:
vite: ^4.2.0 || ^5.0.0
checksum: d7fa6dacd3c246bcee482ff4b7037b2978b6ca002b79780ad4921e91ae4bc85ab234cfb94f8d4d825fed8488a0acdda2ff02b47c27b3055187c0727b18fc725e
checksum: 8bff5065e9689d0b0405932b5f2483bd0c388812dc13219a1511023f7eaca7a53c43f75f3eae785e27f7ce5a60e99d5d32bac4845a63ab095d5562180f7efa7c
languageName: node
linkType: hard
@ -11934,7 +11897,7 @@ __metadata:
"@types/react": "npm:^18.2.47"
"@typescript-eslint/utils": "npm:^5.59.0"
"@vercel/analytics": "npm:^1.1.1"
"@vitejs/plugin-react-swc": "npm:^3.5.0"
"@vitejs/plugin-react-swc": "npm:^3.6.0"
browser-fs-access: "npm:^0.35.0"
dotenv: "npm:^16.3.1"
eslint: "npm:^8.37.0"
@ -13628,7 +13591,7 @@ __metadata:
"@tldraw/assets": "workspace:*"
"@types/lodash": "npm:^4.14.188"
"@vercel/analytics": "npm:^1.1.1"
"@vitejs/plugin-react": "npm:^4.2.0"
"@vitejs/plugin-react-swc": "npm:^3.6.0"
classnames: "npm:^2.3.2"
dotenv: "npm:^16.3.1"
lazyrepo: "npm:0.0.0-alpha.27"