Styles API (#1580)
Removes `propsForNextShape` and replaces it with the new styles API. Changes in here: - New custom style example - `setProp` is now `setStyle` and takes a `StyleProp` instead of a string - `Editor.props` and `Editor.opacity` are now `Editor.sharedStyles` and `Editor.sharedOpacity` - They return an object that flags mixed vs shared types instead of using null to signal mixed types - `Editor.styles` returns a `SharedStyleMap` - keyed on `StyleProp` instead of `string` - `StateNode.shapeType` is now the shape util rather than just a string. This lets us pull the styles from the shape type directly. - `color` is no longer a core part of the editor set on the shape parent. Individual child shapes have to use color directly. - `propsForNextShape` is now `stylesForNextShape` - `InstanceRecordType` is created at runtime in the same way `ShapeRecordType` is. This is so it can pull style validators out of shape defs for `stylesForNextShape` - Shape type are now defined by their props rather than having separate validators & type defs ### Change Type - [x] `major` — Breaking change ### Test Plan 1. Big time regression testing around styles! 2. Check UI works as intended for all shape/style/tool combos - [x] Unit Tests - [ ] End to end tests ### Release Notes - --------- Co-authored-by: Steve Ruiz <steveruizok@gmail.com>
This commit is contained in:
parent
f864d0cfbd
commit
b88a2370b3
144 changed files with 2829 additions and 3311 deletions
|
@ -1,12 +1,13 @@
|
|||
diff --git a/lib/api/ExtractorConfig.js b/lib/api/ExtractorConfig.js
|
||||
index a37db0d564a5662df78055ded63069a4b2706bb1..158d4b121fa0c7cf15c3a52570218a2fe67fc567 100644
|
||||
index 31b46f8b51a2a93c2e538d67cd340e3c6897e242..d2cab369fc53cbd35bcb0c465783f851f1fd5f42 100644
|
||||
--- a/lib/api/ExtractorConfig.js
|
||||
+++ b/lib/api/ExtractorConfig.js
|
||||
@@ -669,5 +669,5 @@ ExtractorConfig.FILENAME = 'api-extractor.json';
|
||||
@@ -668,6 +668,6 @@ ExtractorConfig.FILENAME = 'api-extractor.json';
|
||||
*/
|
||||
ExtractorConfig._tsdocBaseFilePath = path.resolve(__dirname, '../../extends/tsdoc-base.json');
|
||||
ExtractorConfig._defaultConfig = node_core_library_1.JsonFile.load(path.join(__dirname, '../schemas/api-extractor-defaults.json'));
|
||||
-ExtractorConfig._declarationFileExtensionRegExp = /\.d\.ts$/i;
|
||||
+ExtractorConfig._declarationFileExtensionRegExp = /\.d\.(m|c)?ts$/i;
|
||||
exports.ExtractorConfig = ExtractorConfig;
|
||||
//# sourceMappingURL=ExtractorConfig.js.map
|
||||
\ No newline at end of file
|
|
@ -39,6 +39,7 @@
|
|||
"@tldraw/assets": "workspace:*",
|
||||
"@tldraw/tldraw": "workspace:*",
|
||||
"@tldraw/utils": "workspace:*",
|
||||
"@tldraw/validate": "workspace:*",
|
||||
"@vercel/analytics": "^1.0.1",
|
||||
"lazyrepo": "0.0.0-alpha.27",
|
||||
"react": "^18.2.0",
|
||||
|
|
107
apps/examples/src/16-custom-styles/CardShape.tsx
Normal file
107
apps/examples/src/16-custom-styles/CardShape.tsx
Normal file
|
@ -0,0 +1,107 @@
|
|||
import {
|
||||
BaseBoxShapeTool,
|
||||
BaseBoxShapeUtil,
|
||||
DefaultColorStyle,
|
||||
HTMLContainer,
|
||||
StyleProp,
|
||||
TLBaseShape,
|
||||
TLDefaultColorStyle,
|
||||
defineShape,
|
||||
} from '@tldraw/tldraw'
|
||||
import { T } from '@tldraw/validate'
|
||||
|
||||
// Define a style that can be used across multiple shapes. The ID (myApp:filter) must be globally
|
||||
// unique, so we recommend prefixing it with a namespace.
|
||||
export const MyFilterStyle = StyleProp.defineEnum('myApp:filter', {
|
||||
defaultValue: 'none',
|
||||
values: ['none', 'invert', 'grayscale', 'blur'],
|
||||
})
|
||||
|
||||
export type MyFilterStyle = T.TypeOf<typeof MyFilterStyle>
|
||||
|
||||
export type CardShape = TLBaseShape<
|
||||
'card',
|
||||
{
|
||||
w: number
|
||||
h: number
|
||||
color: TLDefaultColorStyle
|
||||
filter: MyFilterStyle
|
||||
}
|
||||
>
|
||||
|
||||
export class CardShapeUtil extends BaseBoxShapeUtil<CardShape> {
|
||||
// Id — the shape util's id
|
||||
static override type = 'card' as const
|
||||
|
||||
// Flags — there are a LOT of other flags!
|
||||
override isAspectRatioLocked = (_shape: CardShape) => false
|
||||
override canResize = (_shape: CardShape) => true
|
||||
override canBind = (_shape: CardShape) => true
|
||||
|
||||
// Default props — used for shapes created with the tool
|
||||
override defaultProps(): CardShape['props'] {
|
||||
return {
|
||||
w: 300,
|
||||
h: 300,
|
||||
color: 'black',
|
||||
filter: 'none',
|
||||
}
|
||||
}
|
||||
|
||||
// Render method — the React component that will be rendered for the shape
|
||||
render(shape: CardShape) {
|
||||
const bounds = this.bounds(shape)
|
||||
|
||||
return (
|
||||
<HTMLContainer
|
||||
id={shape.id}
|
||||
style={{
|
||||
border: `4px solid var(--palette-${shape.props.color})`,
|
||||
borderRadius: 4,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
pointerEvents: 'all',
|
||||
filter: this.filterStyleToCss(shape.props.filter),
|
||||
backgroundColor: `var(--palette-${shape.props.color}-semi)`,
|
||||
}}
|
||||
>
|
||||
🍇🫐🍏🍋🍊🍒 {bounds.w.toFixed()}x{bounds.h.toFixed()} 🍒🍊🍋🍏🫐🍇
|
||||
</HTMLContainer>
|
||||
)
|
||||
}
|
||||
|
||||
// Indicator — used when hovering over a shape or when it's selected; must return only SVG elements here
|
||||
indicator(shape: CardShape) {
|
||||
return <rect width={shape.props.w} height={shape.props.h} />
|
||||
}
|
||||
|
||||
filterStyleToCss(filter: MyFilterStyle) {
|
||||
if (filter === 'invert') return 'invert(100%)'
|
||||
if (filter === 'grayscale') return 'grayscale(100%)'
|
||||
if (filter === 'blur') return 'blur(10px)'
|
||||
return 'none'
|
||||
}
|
||||
}
|
||||
|
||||
// Extending the base box shape tool gives us a lot of functionality for free.
|
||||
export class CardShapeTool extends BaseBoxShapeTool {
|
||||
static override id = 'card'
|
||||
static override initial = 'idle'
|
||||
|
||||
override shapeType = CardShapeUtil
|
||||
}
|
||||
|
||||
export const CardShape = defineShape('card', {
|
||||
util: CardShapeUtil,
|
||||
tool: CardShapeTool,
|
||||
// to use a style prop, you need to describe all the props in your shape.
|
||||
props: {
|
||||
w: T.number,
|
||||
h: T.number,
|
||||
// You can re-use tldraw built-in styles...
|
||||
color: DefaultColorStyle,
|
||||
// ...or your own custom styles.
|
||||
filter: MyFilterStyle,
|
||||
},
|
||||
})
|
85
apps/examples/src/16-custom-styles/CustomStylesExample.tsx
Normal file
85
apps/examples/src/16-custom-styles/CustomStylesExample.tsx
Normal file
|
@ -0,0 +1,85 @@
|
|||
import { TLUiMenuGroup, Tldraw, menuItem, toolbarItem, useEditor } from '@tldraw/tldraw'
|
||||
import '@tldraw/tldraw/editor.css'
|
||||
import '@tldraw/tldraw/ui.css'
|
||||
import { TLUiOverrides } from '@tldraw/ui/src/lib/overrides'
|
||||
import { track } from 'signia-react'
|
||||
import { CardShape, MyFilterStyle } from './CardShape'
|
||||
|
||||
const shapes = [CardShape]
|
||||
|
||||
export default function CustomStylesExample() {
|
||||
return (
|
||||
<div className="tldraw__editor">
|
||||
<Tldraw
|
||||
autoFocus
|
||||
persistenceKey="custom-styles-example"
|
||||
shapes={shapes}
|
||||
overrides={cardToolMenuItems}
|
||||
>
|
||||
<FilterStyleUi />
|
||||
</Tldraw>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const FilterStyleUi = track(function FilterStyleUi() {
|
||||
const editor = useEditor()
|
||||
const filterStyle = editor.sharedStyles.get(MyFilterStyle)
|
||||
|
||||
// if the filter style isn't in sharedStyles, it means it's not relevant to the current tool/selection
|
||||
if (!filterStyle) return null
|
||||
|
||||
return (
|
||||
<div style={{ position: 'absolute', zIndex: 300, top: 64, left: 12 }}>
|
||||
filter:{' '}
|
||||
<select
|
||||
value={filterStyle.type === 'mixed' ? 'mixed' : filterStyle.value}
|
||||
onChange={(e) => editor.setStyle(MyFilterStyle, e.target.value)}
|
||||
>
|
||||
<option value="mixed" disabled>
|
||||
Mixed
|
||||
</option>
|
||||
<option value="none">None</option>
|
||||
<option value="invert">Invert</option>
|
||||
<option value="grayscale">Grayscale</option>
|
||||
<option value="blur">Blur</option>
|
||||
</select>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
const cardToolMenuItems: TLUiOverrides = {
|
||||
// In order for our custom tool to show up in the UI...
|
||||
// We need to add it to the tools list. This "toolItem"
|
||||
// has information about its icon, label, keyboard shortcut,
|
||||
// and what to do when it's selected.
|
||||
tools(editor, tools) {
|
||||
tools.card = {
|
||||
id: 'card',
|
||||
icon: 'color',
|
||||
label: 'Card' as any,
|
||||
kbd: 'c',
|
||||
readonlyOk: false,
|
||||
onSelect: () => {
|
||||
editor.setSelectedTool('card')
|
||||
},
|
||||
}
|
||||
return tools
|
||||
},
|
||||
toolbar(_app, toolbar, { tools }) {
|
||||
// The toolbar is an array of items. We can add it to the
|
||||
// end of the array or splice it in, then return the array.
|
||||
toolbar.splice(4, 0, toolbarItem(tools.card))
|
||||
return toolbar
|
||||
},
|
||||
keyboardShortcutsMenu(_app, keyboardShortcutsMenu, { tools }) {
|
||||
// Same for the keyboard shortcuts menu, but this menu contains
|
||||
// both items and groups. We want to find the "Tools" group and
|
||||
// add it to that before returning the array.
|
||||
const toolsGroup = keyboardShortcutsMenu.find(
|
||||
(group) => group.id === 'shortcuts-dialog.tools'
|
||||
) as TLUiMenuGroup
|
||||
toolsGroup.children.push(menuItem(tools.card))
|
||||
return keyboardShortcutsMenu
|
||||
},
|
||||
}
|
|
@ -1,9 +1,10 @@
|
|||
import {
|
||||
createShapeId,
|
||||
DefaultColorStyle,
|
||||
Editor,
|
||||
Tldraw,
|
||||
TLGeoShape,
|
||||
TLShapePartial,
|
||||
Tldraw,
|
||||
createShapeId,
|
||||
useEditor,
|
||||
} from '@tldraw/tldraw'
|
||||
import '@tldraw/tldraw/editor.css'
|
||||
|
@ -92,7 +93,7 @@ const InsideOfEditorContext = () => {
|
|||
const interval = setInterval(() => {
|
||||
const selection = [...editor.selectedIds]
|
||||
editor.selectAll()
|
||||
editor.setProp('color', i % 2 ? 'blue' : 'light-blue')
|
||||
editor.setStyle(DefaultColorStyle, i % 2 ? 'blue' : 'light-blue')
|
||||
editor.setSelectedIds(selection)
|
||||
i++
|
||||
}, 1000)
|
||||
|
|
|
@ -62,7 +62,7 @@ export class CardShapeTool extends BaseBoxShapeTool {
|
|||
static override id = 'card'
|
||||
static override initial = 'idle'
|
||||
|
||||
override shapeType = 'card'
|
||||
override shapeType = CardShapeUtil
|
||||
}
|
||||
|
||||
export const CardShape = defineShape('card', {
|
||||
|
|
|
@ -15,6 +15,7 @@ import UiEventsExample from './12-ui-events/UiEventsExample'
|
|||
import StoreEventsExample from './13-store-events/StoreEventsExample'
|
||||
import PersistenceExample from './14-persistence/PersistenceExample'
|
||||
import ZonesExample from './15-custom-zones/ZonesExample'
|
||||
import CustomStylesExample from './16-custom-styles/CustomStylesExample'
|
||||
import ExampleApi from './2-api/APIExample'
|
||||
import CustomConfigExample from './3-custom-config/CustomConfigExample'
|
||||
import CustomUiExample from './4-custom-ui/CustomUiExample'
|
||||
|
@ -108,6 +109,10 @@ export const allExamples: Example[] = [
|
|||
path: '/yjs',
|
||||
element: <YjsExample />,
|
||||
},
|
||||
{
|
||||
path: '/custom-styles',
|
||||
element: <CustomStylesExample />,
|
||||
},
|
||||
]
|
||||
|
||||
const router = createBrowserRouter(allExamples)
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
"references": [
|
||||
{ "path": "../../packages/tldraw" },
|
||||
{ "path": "../../packages/utils" },
|
||||
{ "path": "../../packages/assets" }
|
||||
{ "path": "../../packages/assets" },
|
||||
{ "path": "../../packages/validate" }
|
||||
]
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
"concurrently": "7.0.0",
|
||||
"create-serve": "1.0.1",
|
||||
"dotenv": "^16.0.3",
|
||||
"esbuild": "^0.16.7",
|
||||
"esbuild": "^0.18.3",
|
||||
"fs-extra": "^11.1.0",
|
||||
"lazyrepo": "0.0.0-alpha.27",
|
||||
"nanoid": "4.0.2",
|
||||
|
|
|
@ -32,12 +32,12 @@ export async function run() {
|
|||
const entryPoints = [`${rootDir}src/index.tsx`]
|
||||
|
||||
log({ cmd: 'esbuild', args: { entryPoints } })
|
||||
esbuild.build({
|
||||
|
||||
const builder = await esbuild.context({
|
||||
entryPoints,
|
||||
outfile: `${rootDir}/dist/index.js`,
|
||||
minify: false,
|
||||
bundle: true,
|
||||
incremental: true,
|
||||
target: 'es6',
|
||||
jsxFactory: 'React.createElement',
|
||||
jsxFragment: 'React.Fragment',
|
||||
|
@ -51,17 +51,23 @@ export async function run() {
|
|||
define: {
|
||||
'process.env.NODE_ENV': '"development"',
|
||||
},
|
||||
watch: {
|
||||
onRebuild(err) {
|
||||
if (err) {
|
||||
log({ cmd: 'esbuild:error', args: { err } })
|
||||
plugins: [
|
||||
{
|
||||
name: 'log-builds',
|
||||
setup(build) {
|
||||
build.onEnd((result) => {
|
||||
if (result.errors.length) {
|
||||
log({ cmd: 'esbuild:error', args: { err: result.errors } })
|
||||
} else {
|
||||
copyEditor({ log })
|
||||
log({ cmd: 'esbuild:success', args: {} })
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
await builder.watch()
|
||||
} catch (error) {
|
||||
log({ cmd: 'esbuild:error', args: { error } })
|
||||
throw error
|
||||
|
|
|
@ -139,7 +139,7 @@
|
|||
"@typescript-eslint/eslint-plugin": "^5.10.2",
|
||||
"@typescript-eslint/parser": "^5.10.2",
|
||||
"assert": "^2.0.0",
|
||||
"esbuild": "^0.16.7",
|
||||
"esbuild": "^0.18.3",
|
||||
"fs-extra": "^11.1.0",
|
||||
"lazyrepo": "0.0.0-alpha.27",
|
||||
"lodash": "^4.17.21",
|
||||
|
|
|
@ -14,7 +14,7 @@ async function dev() {
|
|||
|
||||
log({ cmd: 'esbuild', args: { entryPoints } })
|
||||
try {
|
||||
esbuild.build({
|
||||
const builder = await esbuild.context({
|
||||
entryPoints,
|
||||
outdir: join(rootDir, 'dist', 'web'),
|
||||
minify: false,
|
||||
|
@ -28,16 +28,21 @@ async function dev() {
|
|||
},
|
||||
tsconfig: './tsconfig.json',
|
||||
external: ['vscode'],
|
||||
incremental: true,
|
||||
watch: {
|
||||
onRebuild(err) {
|
||||
if (err) {
|
||||
log({ cmd: 'esbuild:error', args: { error: err } })
|
||||
plugins: [
|
||||
{
|
||||
name: 'log-builds',
|
||||
setup(build) {
|
||||
build.onEnd((result) => {
|
||||
if (result.errors.length) {
|
||||
log({ cmd: 'esbuild:error', args: { err: result.errors } })
|
||||
} else {
|
||||
copyEditor({ log })
|
||||
log({ cmd: 'esbuild:success', args: {} })
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
],
|
||||
loader: {
|
||||
'.woff2': 'dataurl',
|
||||
'.woff': 'dataurl',
|
||||
|
@ -46,6 +51,7 @@ async function dev() {
|
|||
'.json': 'file',
|
||||
},
|
||||
})
|
||||
await builder.watch()
|
||||
} catch (error) {
|
||||
log({ cmd: 'esbuild:error', args: { error } })
|
||||
throw error
|
||||
|
|
|
@ -85,7 +85,7 @@
|
|||
"typescript": "^5.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/api-extractor": "^7.34.1",
|
||||
"@microsoft/api-extractor": "^7.35.4",
|
||||
"@swc/core": "^1.3.55",
|
||||
"@swc/jest": "^0.2.26",
|
||||
"@types/glob": "^8.1.0",
|
||||
|
@ -97,6 +97,6 @@
|
|||
"vercel": "^28.16.15"
|
||||
},
|
||||
"resolutions": {
|
||||
"@microsoft/api-extractor@^7.34.1": "patch:@microsoft/api-extractor@npm%3A7.34.1#./.yarn/patches/@microsoft-api-extractor-npm-7.34.1-af268a32f8.patch"
|
||||
"@microsoft/api-extractor@^7.35.4": "patch:@microsoft/api-extractor@npm%3A7.35.4#./.yarn/patches/@microsoft-api-extractor-npm-7.35.4-5f4f0357b4.patch"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,9 +35,9 @@ import { Signal } from 'signia';
|
|||
import { StoreSchema } from '@tldraw/store';
|
||||
import { StoreSnapshot } from '@tldraw/store';
|
||||
import { StrokePoint } from '@tldraw/primitives';
|
||||
import { TLAlignType } from '@tldraw/tlschema';
|
||||
import { TLArrowheadType } from '@tldraw/tlschema';
|
||||
import { StyleProp } from '@tldraw/tlschema';
|
||||
import { TLArrowShape } from '@tldraw/tlschema';
|
||||
import { TLArrowShapeArrowheadStyle } from '@tldraw/tlschema';
|
||||
import { TLAsset } from '@tldraw/tlschema';
|
||||
import { TLAssetId } from '@tldraw/tlschema';
|
||||
import { TLAssetPartial } from '@tldraw/tlschema';
|
||||
|
@ -45,13 +45,12 @@ import { TLBaseShape } from '@tldraw/tlschema';
|
|||
import { TLBookmarkAsset } from '@tldraw/tlschema';
|
||||
import { TLBookmarkShape } from '@tldraw/tlschema';
|
||||
import { TLCamera } from '@tldraw/tlschema';
|
||||
import { TLColorStyle } from '@tldraw/tlschema';
|
||||
import { TLColorType } from '@tldraw/tlschema';
|
||||
import { TLCursor } from '@tldraw/tlschema';
|
||||
import { TLDefaultColorStyle } from '@tldraw/tlschema';
|
||||
import { TLDefaultHorizontalAlignStyle } from '@tldraw/tlschema';
|
||||
import { TLDocument } from '@tldraw/tlschema';
|
||||
import { TLDrawShape } from '@tldraw/tlschema';
|
||||
import { TLEmbedShape } from '@tldraw/tlschema';
|
||||
import { TLFontType } from '@tldraw/tlschema';
|
||||
import { TLFrameShape } from '@tldraw/tlschema';
|
||||
import { TLGeoShape } from '@tldraw/tlschema';
|
||||
import { TLGroupShape } from '@tldraw/tlschema';
|
||||
|
@ -62,10 +61,8 @@ import { TLImageShape } from '@tldraw/tlschema';
|
|||
import { TLInstance } from '@tldraw/tlschema';
|
||||
import { TLInstancePageState } from '@tldraw/tlschema';
|
||||
import { TLInstancePresence } from '@tldraw/tlschema';
|
||||
import { TLInstancePropsForNextShape } from '@tldraw/tlschema';
|
||||
import { TLLineShape } from '@tldraw/tlschema';
|
||||
import { TLNoteShape } from '@tldraw/tlschema';
|
||||
import { TLNullableShapeProps } from '@tldraw/tlschema';
|
||||
import { TLPage } from '@tldraw/tlschema';
|
||||
import { TLPageId } from '@tldraw/tlschema';
|
||||
import { TLParentId } from '@tldraw/tlschema';
|
||||
|
@ -74,16 +71,9 @@ import { TLScribble } from '@tldraw/tlschema';
|
|||
import { TLShape } from '@tldraw/tlschema';
|
||||
import { TLShapeId } from '@tldraw/tlschema';
|
||||
import { TLShapePartial } from '@tldraw/tlschema';
|
||||
import { TLShapeProp } from '@tldraw/tlschema';
|
||||
import { TLShapeProps } from '@tldraw/tlschema';
|
||||
import { TLSizeStyle } from '@tldraw/tlschema';
|
||||
import { TLSizeType } from '@tldraw/tlschema';
|
||||
import { TLStore } from '@tldraw/tlschema';
|
||||
import { TLStoreProps } from '@tldraw/tlschema';
|
||||
import { TLStyleCollections } from '@tldraw/tlschema';
|
||||
import { TLStyleType } from '@tldraw/tlschema';
|
||||
import { TLTextShape } from '@tldraw/tlschema';
|
||||
import { TLTextShapeProps } from '@tldraw/tlschema';
|
||||
import { TLUnknownShape } from '@tldraw/tlschema';
|
||||
import { TLVideoAsset } from '@tldraw/tlschema';
|
||||
import { TLVideoShape } from '@tldraw/tlschema';
|
||||
|
@ -107,9 +97,6 @@ export const ANIMATION_MEDIUM_MS = 320;
|
|||
// @internal (undocumented)
|
||||
export const ANIMATION_SHORT_MS = 80;
|
||||
|
||||
// @public (undocumented)
|
||||
export const ARROW_LABEL_FONT_SIZES: Record<TLSizeType, number>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const ArrowShape: TLShapeInfo<TLArrowShape>;
|
||||
|
||||
|
@ -186,7 +173,7 @@ export abstract class BaseBoxShapeTool extends StateNode {
|
|||
// (undocumented)
|
||||
static initial: string;
|
||||
// (undocumented)
|
||||
abstract shapeType: string;
|
||||
abstract shapeType: TLShapeUtilConstructor<any>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
|
@ -233,9 +220,6 @@ export class BookmarkShapeUtil extends BaseBoxShapeUtil<TLBookmarkShape> {
|
|||
static type: "bookmark";
|
||||
}
|
||||
|
||||
// @internal (undocumented)
|
||||
export const BOUND_ARROW_OFFSET = 10;
|
||||
|
||||
// @public (undocumented)
|
||||
export const Canvas: React_3.MemoExoticComponent<({ onDropOverride, }: {
|
||||
onDropOverride?: ((defaultOnDrop: (e: React_3.DragEvent<Element>) => Promise<void>) => (e: React_3.DragEvent<Element>) => Promise<void>) | undefined;
|
||||
|
@ -289,12 +273,6 @@ export const DEFAULT_ANIMATION_OPTIONS: {
|
|||
easing: (t: number) => number;
|
||||
};
|
||||
|
||||
// @internal (undocumented)
|
||||
export const DEFAULT_BOOKMARK_HEIGHT = 320;
|
||||
|
||||
// @internal (undocumented)
|
||||
export const DEFAULT_BOOKMARK_WIDTH = 300;
|
||||
|
||||
// @public (undocumented)
|
||||
export let defaultEditorAssetUrls: TLEditorAssetUrls;
|
||||
|
||||
|
@ -475,7 +453,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
getClipPathById(id: TLShapeId): string | undefined;
|
||||
getContainer: () => HTMLElement;
|
||||
getContent(ids?: TLShapeId[]): TLContent | undefined;
|
||||
getCssColor(id: TLColorStyle['id']): string;
|
||||
getDeltaInParentSpace(shape: TLShape, delta: VecLike): Vec2d;
|
||||
getDeltaInShapeSpace(shape: TLShape, delta: VecLike): Vec2d;
|
||||
getDroppingShape(point: VecLike, droppingShapes?: TLShape[]): TLUnknownShape | undefined;
|
||||
|
@ -517,7 +494,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
getShapeUtil<S extends TLUnknownShape>(shape: S | TLShapePartial<S>): ShapeUtil<S>;
|
||||
getSortedChildIds(parentId: TLParentId): TLShapeId[];
|
||||
getStateDescendant(path: string): StateNode | undefined;
|
||||
getStrokeWidth(id: TLSizeStyle['id']): number;
|
||||
// @internal (undocumented)
|
||||
getStyleForNextShape<T>(style: StyleProp<T>): T;
|
||||
getSvg(ids?: TLShapeId[], opts?: Partial<{
|
||||
scale: number;
|
||||
background: boolean;
|
||||
|
@ -587,7 +565,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
moveShapesToPage(ids: TLShapeId[], pageId: TLPageId): this;
|
||||
nudgeShapes(ids: TLShapeId[], direction: Vec2dModel, major?: boolean, ephemeral?: boolean): this;
|
||||
get onlySelectedShape(): null | TLShape;
|
||||
get opacity(): null | number;
|
||||
get openMenus(): string[];
|
||||
packShapes(ids?: TLShapeId[], padding?: number): this;
|
||||
get pages(): TLPage[];
|
||||
|
@ -602,8 +579,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
popFocusLayer(): this;
|
||||
// @internal (undocumented)
|
||||
get projectName(): string;
|
||||
// @internal
|
||||
get props(): null | TLNullableShapeProps;
|
||||
putContent(content: TLContent, options?: {
|
||||
point?: VecLike;
|
||||
select?: boolean;
|
||||
|
@ -675,12 +650,12 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
setPenMode(isPenMode: boolean): this;
|
||||
// @internal (undocumented)
|
||||
setProjectName(name: string): void;
|
||||
setProp(key: TLShapeProp, value: any, ephemeral?: boolean, squashing?: boolean): this;
|
||||
setReadOnly(isReadOnly: boolean): this;
|
||||
setScribble(scribble?: null | TLScribble): this;
|
||||
setSelectedIds(ids: TLShapeId[], squashing?: boolean): this;
|
||||
setSelectedTool(id: string, info?: {}): this;
|
||||
setSnapMode(isSnapMode: boolean): this;
|
||||
setStyle<T>(style: StyleProp<T>, value: T, ephemeral?: boolean, squashing?: boolean): this;
|
||||
setToolLocked(isToolLocked: boolean): this;
|
||||
setZoomBrush(zoomBrush?: Box2dModel | null): this;
|
||||
get shapeIds(): Set<TLShapeId>;
|
||||
|
@ -688,6 +663,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
shapeUtils: {
|
||||
readonly [K in string]?: ShapeUtil<TLUnknownShape>;
|
||||
};
|
||||
get sharedOpacity(): SharedStyle<number>;
|
||||
get sharedStyles(): ReadonlySharedStyleMap;
|
||||
slideCamera(opts?: {
|
||||
speed: number;
|
||||
direction: Vec2d;
|
||||
|
@ -702,7 +679,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
stopFollowingUser(): this;
|
||||
readonly store: TLStore;
|
||||
stretchShapes(operation: 'horizontal' | 'vertical', ids?: TLShapeId[]): this;
|
||||
static styles: TLStyleCollections;
|
||||
textMeasure: TextManager;
|
||||
toggleLock(ids?: TLShapeId[]): this;
|
||||
undo(): HistoryManager<this>;
|
||||
|
@ -794,15 +770,6 @@ export const featureFlags: {
|
|||
// @public
|
||||
export function fileToBase64(file: Blob): Promise<string>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const FONT_ALIGNMENT: Record<TLAlignType, string>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const FONT_FAMILIES: Record<TLFontType, string>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const FONT_SIZES: Record<TLSizeType, number>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const FrameShape: TLShapeInfo<TLFrameShape>;
|
||||
|
||||
|
@ -870,7 +837,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
|
|||
dash: "dashed" | "dotted" | "draw" | "solid";
|
||||
size: "l" | "m" | "s" | "xl";
|
||||
font: "draw" | "mono" | "sans" | "serif";
|
||||
align: "end" | "middle" | "start";
|
||||
align: "end-legacy" | "end" | "middle-legacy" | "middle" | "start-legacy" | "start";
|
||||
verticalAlign: "end" | "middle" | "start";
|
||||
url: string;
|
||||
w: number;
|
||||
|
@ -899,7 +866,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
|
|||
dash: "dashed" | "dotted" | "draw" | "solid";
|
||||
size: "l" | "m" | "s" | "xl";
|
||||
font: "draw" | "mono" | "sans" | "serif";
|
||||
align: "end" | "middle" | "start";
|
||||
align: "end-legacy" | "end" | "middle-legacy" | "middle" | "start-legacy" | "start";
|
||||
verticalAlign: "end" | "middle" | "start";
|
||||
url: string;
|
||||
w: number;
|
||||
|
@ -1152,9 +1119,6 @@ export function HTMLContainer({ children, className, ...rest }: HTMLContainerPro
|
|||
// @public (undocumented)
|
||||
export type HTMLContainerProps = React_3.HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const ICON_SIZES: Record<TLSizeType, number>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const ImageShape: TLShapeInfo<TLImageShape>;
|
||||
|
||||
|
@ -1204,9 +1168,6 @@ export const isValidHttpURL: (url: string) => boolean;
|
|||
// @public (undocumented)
|
||||
export function isValidUrl(url: string): boolean;
|
||||
|
||||
// @public (undocumented)
|
||||
export const LABEL_FONT_SIZES: Record<TLSizeType, number>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const LineShape: TLShapeInfo<TLLineShape>;
|
||||
|
||||
|
@ -1269,18 +1230,6 @@ export const MAJOR_NUDGE_FACTOR = 10;
|
|||
// @public (undocumented)
|
||||
export function matchEmbedUrl(url: string): {
|
||||
definition: {
|
||||
readonly type: "tldraw";
|
||||
readonly title: "tldraw";
|
||||
readonly hostnames: readonly ["beta.tldraw.com", "lite.tldraw.com", "www.tldraw.com"];
|
||||
readonly minWidth: 300;
|
||||
readonly minHeight: 300;
|
||||
readonly width: 720;
|
||||
readonly height: 500;
|
||||
readonly doesResize: true;
|
||||
readonly canUnmount: true;
|
||||
readonly toEmbedUrl: (url: string) => string | undefined;
|
||||
readonly fromEmbedUrl: (url: string) => string | undefined;
|
||||
} | {
|
||||
readonly type: "codepen";
|
||||
readonly title: "Codepen";
|
||||
readonly hostnames: readonly ["codepen.io"];
|
||||
|
@ -1424,6 +1373,18 @@ export function matchEmbedUrl(url: string): {
|
|||
readonly canUnmount: false;
|
||||
readonly toEmbedUrl: (url: string) => string | undefined;
|
||||
readonly fromEmbedUrl: (url: string) => string | undefined;
|
||||
} | {
|
||||
readonly type: "tldraw";
|
||||
readonly title: "tldraw";
|
||||
readonly hostnames: readonly ["beta.tldraw.com", "lite.tldraw.com", "www.tldraw.com"];
|
||||
readonly minWidth: 300;
|
||||
readonly minHeight: 300;
|
||||
readonly width: 720;
|
||||
readonly height: 500;
|
||||
readonly doesResize: true;
|
||||
readonly canUnmount: true;
|
||||
readonly toEmbedUrl: (url: string) => string | undefined;
|
||||
readonly fromEmbedUrl: (url: string) => string | undefined;
|
||||
} | {
|
||||
readonly type: "vimeo";
|
||||
readonly title: "Vimeo";
|
||||
|
@ -1457,18 +1418,6 @@ export function matchEmbedUrl(url: string): {
|
|||
// @public (undocumented)
|
||||
export function matchUrl(url: string): {
|
||||
definition: {
|
||||
readonly type: "tldraw";
|
||||
readonly title: "tldraw";
|
||||
readonly hostnames: readonly ["beta.tldraw.com", "lite.tldraw.com", "www.tldraw.com"];
|
||||
readonly minWidth: 300;
|
||||
readonly minHeight: 300;
|
||||
readonly width: 720;
|
||||
readonly height: 500;
|
||||
readonly doesResize: true;
|
||||
readonly canUnmount: true;
|
||||
readonly toEmbedUrl: (url: string) => string | undefined;
|
||||
readonly fromEmbedUrl: (url: string) => string | undefined;
|
||||
} | {
|
||||
readonly type: "codepen";
|
||||
readonly title: "Codepen";
|
||||
readonly hostnames: readonly ["codepen.io"];
|
||||
|
@ -1612,6 +1561,18 @@ export function matchUrl(url: string): {
|
|||
readonly canUnmount: false;
|
||||
readonly toEmbedUrl: (url: string) => string | undefined;
|
||||
readonly fromEmbedUrl: (url: string) => string | undefined;
|
||||
} | {
|
||||
readonly type: "tldraw";
|
||||
readonly title: "tldraw";
|
||||
readonly hostnames: readonly ["beta.tldraw.com", "lite.tldraw.com", "www.tldraw.com"];
|
||||
readonly minWidth: 300;
|
||||
readonly minHeight: 300;
|
||||
readonly width: 720;
|
||||
readonly height: 500;
|
||||
readonly doesResize: true;
|
||||
readonly canUnmount: true;
|
||||
readonly toEmbedUrl: (url: string) => string | undefined;
|
||||
readonly fromEmbedUrl: (url: string) => string | undefined;
|
||||
} | {
|
||||
readonly type: "vimeo";
|
||||
readonly title: "Vimeo";
|
||||
|
@ -1657,9 +1618,6 @@ export const MAX_SHAPES_PER_PAGE = 2000;
|
|||
// @internal (undocumented)
|
||||
export const MAX_ZOOM = 8;
|
||||
|
||||
// @internal (undocumented)
|
||||
export const MIN_ARROW_LENGTH = 48;
|
||||
|
||||
// @internal (undocumented)
|
||||
export const MIN_ZOOM = 0.1;
|
||||
|
||||
|
@ -1708,7 +1666,7 @@ export class NoteShapeUtil extends ShapeUtil<TLNoteShape> {
|
|||
color: "black" | "blue" | "green" | "grey" | "light-blue" | "light-green" | "light-red" | "light-violet" | "orange" | "red" | "violet" | "yellow";
|
||||
size: "l" | "m" | "s" | "xl";
|
||||
font: "draw" | "mono" | "sans" | "serif";
|
||||
align: "end" | "middle" | "start";
|
||||
align: "end-legacy" | "end" | "middle-legacy" | "middle" | "start-legacy" | "start";
|
||||
verticalAlign: "end" | "middle" | "start";
|
||||
url: string;
|
||||
text: string;
|
||||
|
@ -1731,7 +1689,7 @@ export class NoteShapeUtil extends ShapeUtil<TLNoteShape> {
|
|||
color: "black" | "blue" | "green" | "grey" | "light-blue" | "light-green" | "light-red" | "light-violet" | "orange" | "red" | "violet" | "yellow";
|
||||
size: "l" | "m" | "s" | "xl";
|
||||
font: "draw" | "mono" | "sans" | "serif";
|
||||
align: "end" | "middle" | "start";
|
||||
align: "end-legacy" | "end" | "middle-legacy" | "middle" | "start-legacy" | "start";
|
||||
verticalAlign: "end" | "middle" | "start";
|
||||
url: string;
|
||||
text: string;
|
||||
|
@ -1796,6 +1754,29 @@ export class PlopManager {
|
|||
// @public
|
||||
export function preventDefault(event: Event | React_2.BaseSyntheticEvent): void;
|
||||
|
||||
// @public (undocumented)
|
||||
export class ReadonlySharedStyleMap {
|
||||
// (undocumented)
|
||||
[Symbol.iterator](): IterableIterator<[StyleProp<unknown>, SharedStyle<unknown>]>;
|
||||
constructor(entries?: Iterable<[StyleProp<unknown>, SharedStyle<unknown>]>);
|
||||
// (undocumented)
|
||||
entries(): IterableIterator<[StyleProp<unknown>, SharedStyle<unknown>]>;
|
||||
// (undocumented)
|
||||
equals(other: ReadonlySharedStyleMap): boolean;
|
||||
// (undocumented)
|
||||
get<T>(prop: StyleProp<T>): SharedStyle<T> | undefined;
|
||||
// (undocumented)
|
||||
getAsKnownValue<T>(prop: StyleProp<T>): T | undefined;
|
||||
// (undocumented)
|
||||
keys(): IterableIterator<StyleProp<unknown>>;
|
||||
// (undocumented)
|
||||
protected map: Map<StyleProp<unknown>, SharedStyle<unknown>>;
|
||||
// (undocumented)
|
||||
get size(): number;
|
||||
// (undocumented)
|
||||
values(): IterableIterator<SharedStyle<unknown>>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export function refreshPage(): void;
|
||||
|
||||
|
@ -1824,9 +1805,6 @@ export function setDefaultEditorAssetUrls(assetUrls: TLEditorAssetUrls): void;
|
|||
// @public (undocumented)
|
||||
export function setPointerCapture(element: Element, event: PointerEvent | React_2.PointerEvent<Element>): void;
|
||||
|
||||
// @public (undocumented)
|
||||
export function setPropsForNextShape(previousProps: TLInstancePropsForNextShape, newProps: Partial<TLShapeProps>): TLInstancePropsForNextShape;
|
||||
|
||||
// @public (undocumented)
|
||||
export function setRuntimeOverrides(input: Partial<typeof runtime>): void;
|
||||
|
||||
|
@ -1834,80 +1812,106 @@ export function setRuntimeOverrides(input: Partial<typeof runtime>): void;
|
|||
export function setUserPreferences(user: TLUserPreferences): void;
|
||||
|
||||
// @public (undocumented)
|
||||
export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
||||
constructor(editor: Editor, type: T['type']);
|
||||
bounds(shape: T): Box2d;
|
||||
canBind: <K>(_shape: T, _otherShape?: K | undefined) => boolean;
|
||||
canCrop: TLShapeUtilFlag<T>;
|
||||
canDropShapes(shape: T, shapes: TLShape[]): boolean;
|
||||
canEdit: TLShapeUtilFlag<T>;
|
||||
canReceiveNewChildrenOfType(shape: T, type: TLShape['type']): boolean;
|
||||
canResize: TLShapeUtilFlag<T>;
|
||||
canScroll: TLShapeUtilFlag<T>;
|
||||
canSnap: TLShapeUtilFlag<T>;
|
||||
canUnmount: TLShapeUtilFlag<T>;
|
||||
center(shape: T): Vec2d;
|
||||
abstract defaultProps(): T['props'];
|
||||
export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
|
||||
constructor(editor: Editor, type: Shape['type'], styleProps: ReadonlyMap<StyleProp<unknown>, string>);
|
||||
bounds(shape: Shape): Box2d;
|
||||
canBind: <K>(_shape: Shape, _otherShape?: K | undefined) => boolean;
|
||||
canCrop: TLShapeUtilFlag<Shape>;
|
||||
canDropShapes(shape: Shape, shapes: TLShape[]): boolean;
|
||||
canEdit: TLShapeUtilFlag<Shape>;
|
||||
canReceiveNewChildrenOfType(shape: Shape, type: TLShape['type']): boolean;
|
||||
canResize: TLShapeUtilFlag<Shape>;
|
||||
canScroll: TLShapeUtilFlag<Shape>;
|
||||
canSnap: TLShapeUtilFlag<Shape>;
|
||||
canUnmount: TLShapeUtilFlag<Shape>;
|
||||
center(shape: Shape): Vec2d;
|
||||
abstract defaultProps(): Shape['props'];
|
||||
// (undocumented)
|
||||
editor: Editor;
|
||||
// @internal (undocumented)
|
||||
expandSelectionOutlinePx(shape: T): number;
|
||||
protected abstract getBounds(shape: T): Box2d;
|
||||
abstract getCenter(shape: T): Vec2d;
|
||||
getEditingBounds: (shape: T) => Box2d;
|
||||
protected getHandles?(shape: T): TLHandle[];
|
||||
protected abstract getOutline(shape: T): Vec2d[];
|
||||
protected getOutlineSegments(shape: T): Vec2d[][];
|
||||
handles(shape: T): TLHandle[];
|
||||
hideResizeHandles: TLShapeUtilFlag<T>;
|
||||
hideRotateHandle: TLShapeUtilFlag<T>;
|
||||
hideSelectionBoundsBg: TLShapeUtilFlag<T>;
|
||||
hideSelectionBoundsFg: TLShapeUtilFlag<T>;
|
||||
hitTestLineSegment(shape: T, A: VecLike, B: VecLike): boolean;
|
||||
hitTestPoint(shape: T, point: VecLike): boolean;
|
||||
abstract indicator(shape: T): any;
|
||||
isAspectRatioLocked: TLShapeUtilFlag<T>;
|
||||
isClosed: TLShapeUtilFlag<T>;
|
||||
onBeforeCreate?: TLOnBeforeCreateHandler<T>;
|
||||
onBeforeUpdate?: TLOnBeforeUpdateHandler<T>;
|
||||
expandSelectionOutlinePx(shape: Shape): number;
|
||||
protected abstract getBounds(shape: Shape): Box2d;
|
||||
abstract getCenter(shape: Shape): Vec2d;
|
||||
getEditingBounds: (shape: Shape) => Box2d;
|
||||
protected getHandles?(shape: Shape): TLHandle[];
|
||||
protected abstract getOutline(shape: Shape): Vec2d[];
|
||||
protected getOutlineSegments(shape: Shape): Vec2d[][];
|
||||
// (undocumented)
|
||||
getStyleIfExists<T>(style: StyleProp<T>, shape: Shape | TLShapePartial<Shape>): T | undefined;
|
||||
handles(shape: Shape): TLHandle[];
|
||||
// (undocumented)
|
||||
hasStyle(style: StyleProp<unknown>): boolean;
|
||||
hideResizeHandles: TLShapeUtilFlag<Shape>;
|
||||
hideRotateHandle: TLShapeUtilFlag<Shape>;
|
||||
hideSelectionBoundsBg: TLShapeUtilFlag<Shape>;
|
||||
hideSelectionBoundsFg: TLShapeUtilFlag<Shape>;
|
||||
hitTestLineSegment(shape: Shape, A: VecLike, B: VecLike): boolean;
|
||||
hitTestPoint(shape: Shape, point: VecLike): boolean;
|
||||
abstract indicator(shape: Shape): any;
|
||||
isAspectRatioLocked: TLShapeUtilFlag<Shape>;
|
||||
isClosed: TLShapeUtilFlag<Shape>;
|
||||
// (undocumented)
|
||||
iterateStyles(shape: Shape | TLShapePartial<Shape>): Generator<[StyleProp<unknown>, unknown], void, unknown>;
|
||||
onBeforeCreate?: TLOnBeforeCreateHandler<Shape>;
|
||||
onBeforeUpdate?: TLOnBeforeUpdateHandler<Shape>;
|
||||
// @internal
|
||||
onBindingChange?: TLOnBindingChangeHandler<T>;
|
||||
onChildrenChange?: TLOnChildrenChangeHandler<T>;
|
||||
onClick?: TLOnClickHandler<T>;
|
||||
onDoubleClick?: TLOnDoubleClickHandler<T>;
|
||||
onDoubleClickEdge?: TLOnDoubleClickHandler<T>;
|
||||
onDoubleClickHandle?: TLOnDoubleClickHandleHandler<T>;
|
||||
onDragShapesOut?: TLOnDragHandler<T>;
|
||||
onDragShapesOver?: TLOnDragHandler<T, {
|
||||
onBindingChange?: TLOnBindingChangeHandler<Shape>;
|
||||
onChildrenChange?: TLOnChildrenChangeHandler<Shape>;
|
||||
onClick?: TLOnClickHandler<Shape>;
|
||||
onDoubleClick?: TLOnDoubleClickHandler<Shape>;
|
||||
onDoubleClickEdge?: TLOnDoubleClickHandler<Shape>;
|
||||
onDoubleClickHandle?: TLOnDoubleClickHandleHandler<Shape>;
|
||||
onDragShapesOut?: TLOnDragHandler<Shape>;
|
||||
onDragShapesOver?: TLOnDragHandler<Shape, {
|
||||
shouldHint: boolean;
|
||||
}>;
|
||||
onDropShapesOver?: TLOnDragHandler<T>;
|
||||
onEditEnd?: TLOnEditEndHandler<T>;
|
||||
onHandleChange?: TLOnHandleChangeHandler<T>;
|
||||
onResize?: TLOnResizeHandler<T>;
|
||||
onResizeEnd?: TLOnResizeEndHandler<T>;
|
||||
onResizeStart?: TLOnResizeStartHandler<T>;
|
||||
onRotate?: TLOnRotateHandler<T>;
|
||||
onRotateEnd?: TLOnRotateEndHandler<T>;
|
||||
onRotateStart?: TLOnRotateStartHandler<T>;
|
||||
onTranslate?: TLOnTranslateHandler<T>;
|
||||
onTranslateEnd?: TLOnTranslateEndHandler<T>;
|
||||
onTranslateStart?: TLOnTranslateStartHandler<T>;
|
||||
outline(shape: T): Vec2d[];
|
||||
outlineSegments(shape: T): Vec2d[][];
|
||||
onDropShapesOver?: TLOnDragHandler<Shape>;
|
||||
onEditEnd?: TLOnEditEndHandler<Shape>;
|
||||
onHandleChange?: TLOnHandleChangeHandler<Shape>;
|
||||
onResize?: TLOnResizeHandler<Shape>;
|
||||
onResizeEnd?: TLOnResizeEndHandler<Shape>;
|
||||
onResizeStart?: TLOnResizeStartHandler<Shape>;
|
||||
onRotate?: TLOnRotateHandler<Shape>;
|
||||
onRotateEnd?: TLOnRotateEndHandler<Shape>;
|
||||
onRotateStart?: TLOnRotateStartHandler<Shape>;
|
||||
onTranslate?: TLOnTranslateHandler<Shape>;
|
||||
onTranslateEnd?: TLOnTranslateEndHandler<Shape>;
|
||||
onTranslateStart?: TLOnTranslateStartHandler<Shape>;
|
||||
outline(shape: Shape): Vec2d[];
|
||||
outlineSegments(shape: Shape): Vec2d[][];
|
||||
// @internal
|
||||
providesBackgroundForChildren(shape: T): boolean;
|
||||
abstract render(shape: T): any;
|
||||
providesBackgroundForChildren(shape: Shape): boolean;
|
||||
abstract render(shape: Shape): any;
|
||||
// @internal
|
||||
renderBackground?(shape: T): any;
|
||||
snapPoints(shape: T): Vec2d[];
|
||||
toBackgroundSvg?(shape: T, font: string | undefined, colors: TLExportColors): null | Promise<SVGElement> | SVGElement;
|
||||
toSvg?(shape: T, font: string | undefined, colors: TLExportColors): Promise<SVGElement> | SVGElement;
|
||||
renderBackground?(shape: Shape): any;
|
||||
// (undocumented)
|
||||
readonly type: T['type'];
|
||||
setStyleInPartial<T>(style: StyleProp<T>, shape: TLShapePartial<Shape>, value: T): TLShapePartial<Shape>;
|
||||
snapPoints(shape: Shape): Vec2d[];
|
||||
// (undocumented)
|
||||
readonly styleProps: ReadonlyMap<StyleProp<unknown>, string>;
|
||||
toBackgroundSvg?(shape: Shape, font: string | undefined, colors: TLExportColors): null | Promise<SVGElement> | SVGElement;
|
||||
toSvg?(shape: Shape, font: string | undefined, colors: TLExportColors): Promise<SVGElement> | SVGElement;
|
||||
// (undocumented)
|
||||
readonly type: Shape['type'];
|
||||
static type: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type SharedStyle<T> = {
|
||||
readonly type: 'mixed';
|
||||
} | {
|
||||
readonly type: 'shared';
|
||||
readonly value: T;
|
||||
};
|
||||
|
||||
// @internal (undocumented)
|
||||
export class SharedStyleMap extends ReadonlySharedStyleMap {
|
||||
// (undocumented)
|
||||
applyValue<T>(prop: StyleProp<T>, value: T): void;
|
||||
// (undocumented)
|
||||
set<T>(prop: StyleProp<T>, value: SharedStyle<T>): void;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export function snapToGrid(n: number, gridSize: number): number;
|
||||
|
||||
|
@ -1981,18 +1985,13 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
|
|||
// (undocumented)
|
||||
path: Computed<string>;
|
||||
// (undocumented)
|
||||
shapeType?: string;
|
||||
// (undocumented)
|
||||
readonly styles: TLStyleType[];
|
||||
shapeType?: TLShapeUtilConstructor<TLBaseShape<any, any>>;
|
||||
// (undocumented)
|
||||
transition(id: string, info: any): this;
|
||||
// (undocumented)
|
||||
type: TLStateNodeType;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const STYLES: TLStyleCollections;
|
||||
|
||||
// @internal (undocumented)
|
||||
export const SVG_PADDING = 32;
|
||||
|
||||
|
@ -2005,16 +2004,6 @@ export type SVGContainerProps = React_3.HTMLAttributes<SVGElement>;
|
|||
// @public
|
||||
export const TAB_ID: string;
|
||||
|
||||
// @public (undocumented)
|
||||
export const TEXT_PROPS: {
|
||||
lineHeight: number;
|
||||
fontWeight: string;
|
||||
fontVariant: string;
|
||||
fontStyle: string;
|
||||
padding: string;
|
||||
maxWidth: string;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const TextShape: TLShapeInfo<TLTextShape>;
|
||||
|
||||
|
@ -2049,7 +2038,16 @@ export class TextShapeUtil extends ShapeUtil<TLTextShape> {
|
|||
parentId: TLParentId;
|
||||
isLocked: boolean;
|
||||
opacity: number;
|
||||
props: TLTextShapeProps;
|
||||
props: {
|
||||
color: "black" | "blue" | "green" | "grey" | "light-blue" | "light-green" | "light-red" | "light-violet" | "orange" | "red" | "violet" | "yellow";
|
||||
size: "l" | "m" | "s" | "xl";
|
||||
font: "draw" | "mono" | "sans" | "serif";
|
||||
align: "end-legacy" | "end" | "middle-legacy" | "middle" | "start-legacy" | "start";
|
||||
w: number;
|
||||
text: string;
|
||||
scale: number;
|
||||
autoSize: boolean;
|
||||
};
|
||||
id: TLShapeId;
|
||||
typeName: "shape";
|
||||
} | undefined;
|
||||
|
@ -2062,7 +2060,7 @@ export class TextShapeUtil extends ShapeUtil<TLTextShape> {
|
|||
color: "black" | "blue" | "green" | "grey" | "light-blue" | "light-green" | "light-red" | "light-violet" | "orange" | "red" | "violet" | "yellow";
|
||||
size: "l" | "m" | "s" | "xl";
|
||||
font: "draw" | "mono" | "sans" | "serif";
|
||||
align: "end" | "middle" | "start";
|
||||
align: "end-legacy" | "end" | "middle-legacy" | "middle" | "start-legacy" | "start";
|
||||
text: string;
|
||||
scale: number;
|
||||
autoSize: boolean;
|
||||
|
@ -2616,7 +2614,7 @@ export type TLShapeInfo<T extends TLUnknownShape = TLUnknownShape> = {
|
|||
// @public (undocumented)
|
||||
export interface TLShapeUtilConstructor<T extends TLUnknownShape, U extends ShapeUtil<T> = ShapeUtil<T>> {
|
||||
// (undocumented)
|
||||
new (editor: Editor, type: T['type']): U;
|
||||
new (editor: Editor, type: T['type'], styleProps: ReadonlyMap<StyleProp<unknown>, string>): U;
|
||||
// (undocumented)
|
||||
type: T['type'];
|
||||
}
|
||||
|
@ -2634,8 +2632,6 @@ export interface TLStateNodeConstructor {
|
|||
id: string;
|
||||
// (undocumented)
|
||||
initial?: string;
|
||||
// (undocumented)
|
||||
styles?: TLStyleType[];
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
|
@ -2772,9 +2768,6 @@ export class VideoShapeUtil extends BaseBoxShapeUtil<TLVideoShape> {
|
|||
static type: "video";
|
||||
}
|
||||
|
||||
// @internal (undocumented)
|
||||
export const WAY_TOO_BIG_ARROW_BEND_FACTOR = 10;
|
||||
|
||||
// @public (undocumented)
|
||||
export class WeakMapCache<T extends object, K> {
|
||||
// (undocumented)
|
||||
|
|
|
@ -51,22 +51,13 @@ export { defineShape, type TLShapeInfo } from './lib/config/defineShape'
|
|||
export {
|
||||
ANIMATION_MEDIUM_MS,
|
||||
ANIMATION_SHORT_MS,
|
||||
ARROW_LABEL_FONT_SIZES,
|
||||
BOUND_ARROW_OFFSET,
|
||||
DEFAULT_ANIMATION_OPTIONS,
|
||||
DEFAULT_BOOKMARK_HEIGHT,
|
||||
DEFAULT_BOOKMARK_WIDTH,
|
||||
DOUBLE_CLICK_DURATION,
|
||||
DRAG_DISTANCE,
|
||||
FONT_ALIGNMENT,
|
||||
FONT_FAMILIES,
|
||||
FONT_SIZES,
|
||||
GRID_INCREMENT,
|
||||
GRID_STEPS,
|
||||
HAND_TOOL_FRICTION,
|
||||
HASH_PATERN_ZOOM_NAMES,
|
||||
ICON_SIZES,
|
||||
LABEL_FONT_SIZES,
|
||||
MAJOR_NUDGE_FACTOR,
|
||||
MAX_ASSET_HEIGHT,
|
||||
MAX_ASSET_WIDTH,
|
||||
|
@ -74,15 +65,11 @@ export {
|
|||
MAX_SHAPES_PER_PAGE,
|
||||
MAX_ZOOM,
|
||||
MINOR_NUDGE_FACTOR,
|
||||
MIN_ARROW_LENGTH,
|
||||
MIN_ZOOM,
|
||||
MULTI_CLICK_DURATION,
|
||||
REMOVE_SYMBOL,
|
||||
RICH_TYPES,
|
||||
STYLES,
|
||||
SVG_PADDING,
|
||||
TEXT_PROPS,
|
||||
WAY_TOO_BIG_ARROW_BEND_FACTOR,
|
||||
ZOOMS,
|
||||
} from './lib/constants'
|
||||
export { Editor, type TLAnimationOptions, type TLEditorOptions } from './lib/editor/Editor'
|
||||
|
@ -198,6 +185,11 @@ export { usePresence } from './lib/hooks/usePresence'
|
|||
export { useQuickReactor } from './lib/hooks/useQuickReactor'
|
||||
export { useReactor } from './lib/hooks/useReactor'
|
||||
export { useTLStore } from './lib/hooks/useTLStore'
|
||||
export {
|
||||
ReadonlySharedStyleMap,
|
||||
SharedStyleMap,
|
||||
type SharedStyle,
|
||||
} from './lib/utils/SharedStylesMap'
|
||||
export { WeakMapCache } from './lib/utils/WeakMapCache'
|
||||
export {
|
||||
ACCEPTED_ASSET_TYPE,
|
||||
|
@ -253,7 +245,6 @@ export {
|
|||
} from './lib/utils/export'
|
||||
export { hardResetEditor } from './lib/utils/hard-reset'
|
||||
export { isAnimated, isGIF } from './lib/utils/is-gif-animated'
|
||||
export { setPropsForNextShape } from './lib/utils/props-for-next-shape'
|
||||
export { refreshPage } from './lib/utils/refresh-page'
|
||||
export { runtime, setRuntimeOverrides } from './lib/utils/runtime'
|
||||
export {
|
||||
|
|
|
@ -66,16 +66,13 @@ export const Shape = track(function Shape({
|
|||
)
|
||||
|
||||
useQuickReactor(
|
||||
'set shape container clip path / color',
|
||||
'set shape container clip path',
|
||||
() => {
|
||||
const shape = editor.getShapeById(id)
|
||||
if (!shape) return null
|
||||
|
||||
const clipPath = editor.getClipPathById(id)
|
||||
setProperty('clip-path', clipPath ?? 'none')
|
||||
if ('color' in shape.props) {
|
||||
setProperty('color', editor.getCssColor(shape.props.color))
|
||||
}
|
||||
},
|
||||
[editor, setProperty]
|
||||
)
|
||||
|
|
|
@ -8,7 +8,6 @@ import {
|
|||
import {
|
||||
CameraRecordType,
|
||||
InstancePageStateRecordType,
|
||||
InstanceRecordType,
|
||||
TLINSTANCE_ID,
|
||||
TLPageId,
|
||||
TLRecord,
|
||||
|
@ -233,7 +232,7 @@ export function loadSessionStateSnapshotIntoStore(
|
|||
removed: {},
|
||||
updated: {},
|
||||
added: {
|
||||
[TLINSTANCE_ID]: InstanceRecordType.create({
|
||||
[TLINSTANCE_ID]: store.schema.types.instance.create({
|
||||
id: TLINSTANCE_ID,
|
||||
currentPageId: res.currentPageId,
|
||||
isDebugMode: res.isDebugMode,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { EASINGS } from '@tldraw/primitives'
|
||||
import { TLAlignType, TLFontType, TLSizeType, TLStyleCollections } from '@tldraw/tlschema'
|
||||
|
||||
/** @internal */
|
||||
export const MAX_SHAPES_PER_PAGE = 2000
|
||||
|
@ -87,19 +86,6 @@ export const DEFAULT_ANIMATION_OPTIONS = {
|
|||
/** @internal */
|
||||
export const HAND_TOOL_FRICTION = 0.09
|
||||
|
||||
/** @internal */
|
||||
export const MIN_ARROW_LENGTH = 48
|
||||
/** @internal */
|
||||
export const BOUND_ARROW_OFFSET = 10
|
||||
/** @internal */
|
||||
export const WAY_TOO_BIG_ARROW_BEND_FACTOR = 10
|
||||
|
||||
/** @internal */
|
||||
export const DEFAULT_BOOKMARK_WIDTH = 300
|
||||
|
||||
/** @internal */
|
||||
export const DEFAULT_BOOKMARK_HEIGHT = 320
|
||||
|
||||
/** @public */
|
||||
export const GRID_STEPS = [
|
||||
{ min: -1, mid: 0.15, step: 100 },
|
||||
|
@ -108,172 +94,6 @@ export const GRID_STEPS = [
|
|||
{ min: 0.7, mid: 2.5, step: 1 },
|
||||
]
|
||||
|
||||
/** @public */
|
||||
export const TEXT_PROPS = {
|
||||
lineHeight: 1.35,
|
||||
fontWeight: 'normal',
|
||||
fontVariant: 'normal',
|
||||
fontStyle: 'normal',
|
||||
padding: '0px',
|
||||
maxWidth: 'auto',
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export const FONT_SIZES: Record<TLSizeType, number> = {
|
||||
s: 18,
|
||||
m: 24,
|
||||
l: 36,
|
||||
xl: 44,
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export const LABEL_FONT_SIZES: Record<TLSizeType, number> = {
|
||||
s: 18,
|
||||
m: 22,
|
||||
l: 26,
|
||||
xl: 32,
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export const ARROW_LABEL_FONT_SIZES: Record<TLSizeType, number> = {
|
||||
s: 18,
|
||||
m: 20,
|
||||
l: 24,
|
||||
xl: 28,
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export const ICON_SIZES: Record<TLSizeType, number> = {
|
||||
s: 16,
|
||||
m: 32,
|
||||
l: 48,
|
||||
xl: 64,
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export const FONT_FAMILIES: Record<TLFontType, string> = {
|
||||
draw: 'var(--tl-font-draw)',
|
||||
sans: 'var(--tl-font-sans)',
|
||||
serif: 'var(--tl-font-serif)',
|
||||
mono: 'var(--tl-font-mono)',
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export const FONT_ALIGNMENT: Record<TLAlignType, string> = {
|
||||
middle: 'center',
|
||||
start: 'left',
|
||||
end: 'right',
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export const STYLES: TLStyleCollections = {
|
||||
color: [
|
||||
{ id: 'black', type: 'color', icon: 'color' },
|
||||
{ id: 'grey', type: 'color', icon: 'color' },
|
||||
{ id: 'light-violet', type: 'color', icon: 'color' },
|
||||
{ id: 'violet', type: 'color', icon: 'color' },
|
||||
{ id: 'blue', type: 'color', icon: 'color' },
|
||||
{ id: 'light-blue', type: 'color', icon: 'color' },
|
||||
{ id: 'yellow', type: 'color', icon: 'color' },
|
||||
{ id: 'orange', type: 'color', icon: 'color' },
|
||||
{ id: 'green', type: 'color', icon: 'color' },
|
||||
{ id: 'light-green', type: 'color', icon: 'color' },
|
||||
{ id: 'light-red', type: 'color', icon: 'color' },
|
||||
{ id: 'red', type: 'color', icon: 'color' },
|
||||
],
|
||||
fill: [
|
||||
{ id: 'none', type: 'fill', icon: 'fill-none' },
|
||||
{ id: 'semi', type: 'fill', icon: 'fill-semi' },
|
||||
{ id: 'solid', type: 'fill', icon: 'fill-solid' },
|
||||
{ id: 'pattern', type: 'fill', icon: 'fill-pattern' },
|
||||
],
|
||||
dash: [
|
||||
{ id: 'draw', type: 'dash', icon: 'dash-draw' },
|
||||
{ id: 'dashed', type: 'dash', icon: 'dash-dashed' },
|
||||
{ id: 'dotted', type: 'dash', icon: 'dash-dotted' },
|
||||
{ id: 'solid', type: 'dash', icon: 'dash-solid' },
|
||||
],
|
||||
size: [
|
||||
{ id: 's', type: 'size', icon: 'size-small' },
|
||||
{ id: 'm', type: 'size', icon: 'size-medium' },
|
||||
{ id: 'l', type: 'size', icon: 'size-large' },
|
||||
{ id: 'xl', type: 'size', icon: 'size-extra-large' },
|
||||
],
|
||||
font: [
|
||||
{ id: 'draw', type: 'font', icon: 'font-draw' },
|
||||
{ id: 'sans', type: 'font', icon: 'font-sans' },
|
||||
{ id: 'serif', type: 'font', icon: 'font-serif' },
|
||||
{ id: 'mono', type: 'font', icon: 'font-mono' },
|
||||
],
|
||||
align: [
|
||||
{ id: 'start', type: 'align', icon: 'text-align-left' },
|
||||
{ id: 'middle', type: 'align', icon: 'text-align-center' },
|
||||
{ id: 'end', type: 'align', icon: 'text-align-right' },
|
||||
],
|
||||
verticalAlign: [
|
||||
{ id: 'start', type: 'verticalAlign', icon: 'vertical-align-start' },
|
||||
{ id: 'middle', type: 'verticalAlign', icon: 'vertical-align-center' },
|
||||
{ id: 'end', type: 'verticalAlign', icon: 'vertical-align-end' },
|
||||
],
|
||||
geo: [
|
||||
{ id: 'rectangle', type: 'geo', icon: 'geo-rectangle' },
|
||||
{ id: 'ellipse', type: 'geo', icon: 'geo-ellipse' },
|
||||
{ id: 'triangle', type: 'geo', icon: 'geo-triangle' },
|
||||
{ id: 'diamond', type: 'geo', icon: 'geo-diamond' },
|
||||
{ id: 'pentagon', type: 'geo', icon: 'geo-pentagon' },
|
||||
{ id: 'hexagon', type: 'geo', icon: 'geo-hexagon' },
|
||||
{ id: 'octagon', type: 'geo', icon: 'geo-octagon' },
|
||||
{ id: 'star', type: 'geo', icon: 'geo-star' },
|
||||
{ id: 'rhombus', type: 'geo', icon: 'geo-rhombus' },
|
||||
{ id: 'rhombus-2', type: 'geo', icon: 'geo-rhombus-2' },
|
||||
{ id: 'oval', type: 'geo', icon: 'geo-oval' },
|
||||
{ id: 'trapezoid', type: 'geo', icon: 'geo-trapezoid' },
|
||||
{ id: 'arrow-right', type: 'geo', icon: 'geo-arrow-right' },
|
||||
{ id: 'arrow-left', type: 'geo', icon: 'geo-arrow-left' },
|
||||
{ id: 'arrow-up', type: 'geo', icon: 'geo-arrow-up' },
|
||||
{ id: 'arrow-down', type: 'geo', icon: 'geo-arrow-down' },
|
||||
{ id: 'x-box', type: 'geo', icon: 'geo-x-box' },
|
||||
{ id: 'check-box', type: 'geo', icon: 'geo-check-box' },
|
||||
],
|
||||
arrowheadStart: [
|
||||
{ id: 'none', type: 'arrowheadStart', icon: 'arrowhead-none' },
|
||||
{ id: 'arrow', type: 'arrowheadStart', icon: 'arrowhead-arrow' },
|
||||
{ id: 'triangle', type: 'arrowheadStart', icon: 'arrowhead-triangle' },
|
||||
{ id: 'square', type: 'arrowheadStart', icon: 'arrowhead-square' },
|
||||
{ id: 'dot', type: 'arrowheadStart', icon: 'arrowhead-dot' },
|
||||
{ id: 'diamond', type: 'arrowheadStart', icon: 'arrowhead-diamond' },
|
||||
{ id: 'inverted', type: 'arrowheadStart', icon: 'arrowhead-triangle-inverted' },
|
||||
{ id: 'bar', type: 'arrowheadStart', icon: 'arrowhead-bar' },
|
||||
],
|
||||
arrowheadEnd: [
|
||||
{ id: 'none', type: 'arrowheadEnd', icon: 'arrowhead-none' },
|
||||
{ id: 'arrow', type: 'arrowheadEnd', icon: 'arrowhead-arrow' },
|
||||
{ id: 'triangle', type: 'arrowheadEnd', icon: 'arrowhead-triangle' },
|
||||
{ id: 'square', type: 'arrowheadEnd', icon: 'arrowhead-square' },
|
||||
{ id: 'dot', type: 'arrowheadEnd', icon: 'arrowhead-dot' },
|
||||
{ id: 'diamond', type: 'arrowheadEnd', icon: 'arrowhead-diamond' },
|
||||
{ id: 'inverted', type: 'arrowheadEnd', icon: 'arrowhead-triangle-inverted' },
|
||||
{ id: 'bar', type: 'arrowheadEnd', icon: 'arrowhead-bar' },
|
||||
],
|
||||
spline: [
|
||||
{ id: 'line', type: 'spline', icon: 'spline-line' },
|
||||
{ id: 'cubic', type: 'spline', icon: 'spline-cubic' },
|
||||
],
|
||||
}
|
||||
|
||||
// These props should not cause Editor.props to update
|
||||
export const BLACKLISTED_PROPS = new Set([
|
||||
'bend',
|
||||
'w',
|
||||
'h',
|
||||
'start',
|
||||
'end',
|
||||
'text',
|
||||
'name',
|
||||
'url',
|
||||
'growY',
|
||||
])
|
||||
|
||||
/** @internal */
|
||||
export const COLLABORATOR_TIMEOUT = 3000
|
||||
|
||||
|
|
|
@ -25,14 +25,15 @@ import { ComputedCache, RecordType } from '@tldraw/store'
|
|||
import {
|
||||
Box2dModel,
|
||||
CameraRecordType,
|
||||
DefaultColorStyle,
|
||||
DefaultFontStyle,
|
||||
InstancePageStateRecordType,
|
||||
PageRecordType,
|
||||
StyleProp,
|
||||
TLArrowShape,
|
||||
TLAsset,
|
||||
TLAssetId,
|
||||
TLAssetPartial,
|
||||
TLColorStyle,
|
||||
TLColorType,
|
||||
TLCursor,
|
||||
TLCursorType,
|
||||
TLDOCUMENT_ID,
|
||||
|
@ -43,7 +44,6 @@ import {
|
|||
TLImageAsset,
|
||||
TLInstance,
|
||||
TLInstancePageState,
|
||||
TLNullableShapeProps,
|
||||
TLPOINTER_ID,
|
||||
TLPage,
|
||||
TLPageId,
|
||||
|
@ -53,13 +53,12 @@ import {
|
|||
TLShape,
|
||||
TLShapeId,
|
||||
TLShapePartial,
|
||||
TLShapeProp,
|
||||
TLSizeStyle,
|
||||
TLStore,
|
||||
TLUnknownShape,
|
||||
TLVideoAsset,
|
||||
Vec2dModel,
|
||||
createShapeId,
|
||||
getShapePropKeysByStyle,
|
||||
isPageId,
|
||||
isShape,
|
||||
isShapeId,
|
||||
|
@ -72,6 +71,7 @@ import {
|
|||
deepCopy,
|
||||
getOwnProperty,
|
||||
hasOwnProperty,
|
||||
objectMapFromEntries,
|
||||
partition,
|
||||
sortById,
|
||||
structuredClone,
|
||||
|
@ -84,7 +84,6 @@ import { checkShapesAndAddCore } from '../config/defaultShapes'
|
|||
import { AnyTLShapeInfo } from '../config/defineShape'
|
||||
import {
|
||||
ANIMATION_MEDIUM_MS,
|
||||
BLACKLISTED_PROPS,
|
||||
COARSE_DRAG_DISTANCE,
|
||||
COLLABORATOR_TIMEOUT,
|
||||
DEFAULT_ANIMATION_OPTIONS,
|
||||
|
@ -103,15 +102,14 @@ import {
|
|||
MAX_ZOOM,
|
||||
MINOR_NUDGE_FACTOR,
|
||||
MIN_ZOOM,
|
||||
STYLES,
|
||||
SVG_PADDING,
|
||||
ZOOMS,
|
||||
} from '../constants'
|
||||
import { exportPatternSvgDefs } from '../hooks/usePattern'
|
||||
import { ReadonlySharedStyleMap, SharedStyle, SharedStyleMap } from '../utils/SharedStylesMap'
|
||||
import { WeakMapCache } from '../utils/WeakMapCache'
|
||||
import { dataUrlToFile } from '../utils/assets'
|
||||
import { getIncrementedName, uniqueId } from '../utils/data'
|
||||
import { setPropsForNextShape } from '../utils/props-for-next-shape'
|
||||
import { applyRotationToSnapshotShapes, getRotationSnapshot } from '../utils/rotation'
|
||||
import { arrowBindingsIndex } from './derivations/arrowBindingsIndex'
|
||||
import { parentsToChildrenWithIndexes } from './derivations/parentsToChildrenWithIndexes'
|
||||
|
@ -216,9 +214,25 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
}" is present in the store schema but not provided to the editor`
|
||||
)
|
||||
}
|
||||
this.shapeUtils = Object.fromEntries(
|
||||
allShapes.map(({ util: Util }) => [Util.type, new Util(this, Util.type)])
|
||||
const shapeUtils = {} as Record<string, ShapeUtil>
|
||||
const allStylesById = new Map<string, StyleProp<unknown>>()
|
||||
|
||||
for (const { util: Util, props } of allShapes) {
|
||||
const propKeysByStyle = getShapePropKeysByStyle(props ?? {})
|
||||
shapeUtils[Util.type] = new Util(this, Util.type, propKeysByStyle)
|
||||
|
||||
for (const style of propKeysByStyle.keys()) {
|
||||
if (!allStylesById.has(style.id)) {
|
||||
allStylesById.set(style.id, style)
|
||||
} else if (allStylesById.get(style.id) !== style) {
|
||||
throw Error(
|
||||
`Multiple style props with id "${style.id}" in use. Style prop IDs must be unique.`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.shapeUtils = shapeUtils
|
||||
|
||||
// Tools.
|
||||
// Accept tools from constructor parameters which may not conflict with the root note's default or
|
||||
|
@ -248,9 +262,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
this.isChromeForIos = false
|
||||
}
|
||||
|
||||
// Set styles
|
||||
this.colors = new Map(Editor.styles.color.map((c) => [c.id, `var(--palette-${c.id})`]))
|
||||
|
||||
this.store.onBeforeDelete = (record) => {
|
||||
if (record.typeName === 'shape') {
|
||||
this._shapeWillBeDeleted(record)
|
||||
|
@ -1086,126 +1097,94 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get all the current props among the users selected shapes
|
||||
* Get all the current styles among the users selected shapes
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
private _extractSharedProps(shape: TLShape, sharedProps: TLNullableShapeProps) {
|
||||
private _extractSharedStyles(shape: TLShape, sharedStyleMap: SharedStyleMap) {
|
||||
if (this.isShapeOfType(shape, GroupShapeUtil)) {
|
||||
// For groups, ignore the props of the group shape and instead include
|
||||
// the props of the group's children. These are the shapes that would have
|
||||
// their props changed if the user called `setProp` on the current selection.
|
||||
// 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]
|
||||
if (!childIds) return
|
||||
|
||||
for (let i = 0, n = childIds.length; i < n; i++) {
|
||||
this._extractSharedProps(this.getShapeById(childIds[i][0])!, sharedProps)
|
||||
this._extractSharedStyles(this.getShapeById(childIds[i][0])!, sharedStyleMap)
|
||||
}
|
||||
} else {
|
||||
const props = Object.entries(shape.props)
|
||||
let prop: [TLShapeProp, any]
|
||||
for (let i = 0, n = props.length; i < n; i++) {
|
||||
prop = props[i] as [TLShapeProp, any]
|
||||
|
||||
// We should probably white list rather than black list here
|
||||
if (BLACKLISTED_PROPS.has(prop[0])) continue
|
||||
|
||||
// Check the value of this prop on the shared props object.
|
||||
switch (sharedProps[prop[0]]) {
|
||||
case undefined: {
|
||||
// If this key hasn't been defined yet in the shared props object,
|
||||
// we can set it to the value from the shape's props object.
|
||||
sharedProps[prop[0]] = prop[1]
|
||||
break
|
||||
}
|
||||
case null:
|
||||
case prop[1]: {
|
||||
// If the value in the shared props object matches the value from
|
||||
// the shape's props object exactly—or if there is already a mixed
|
||||
// value (null) in the shared props object—then this is a noop. We
|
||||
// want to leave the value as it is in the shared props object.
|
||||
continue
|
||||
}
|
||||
default: {
|
||||
// If there's a value in the shared props object that isn't null AND
|
||||
// that isn't undefined AND that doesn't match the shape's props object,
|
||||
// then we've got a conflict, mixed props, so set the value to null.
|
||||
sharedProps[prop[0]] = null
|
||||
}
|
||||
}
|
||||
const util = this.getShapeUtil(shape)
|
||||
for (const [style, value] of util.iterateStyles(shape)) {
|
||||
sharedStyleMap.applyValue(style, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A derived object containing all current props among the user's selected shapes.
|
||||
* A derived map containing all current styles among the user's selected shapes.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
private _selectionSharedProps = computed<TLNullableShapeProps>('_selectionSharedProps', () => {
|
||||
private _selectionSharedStyles = computed<ReadonlySharedStyleMap>(
|
||||
'_selectionSharedStyles',
|
||||
() => {
|
||||
const { selectedShapes } = this
|
||||
|
||||
const sharedProps = {} as TLNullableShapeProps
|
||||
|
||||
for (let i = 0, n = selectedShapes.length; i < n; i++) {
|
||||
this._extractSharedProps(selectedShapes[i], sharedProps)
|
||||
const sharedStyles = new SharedStyleMap()
|
||||
for (const selectedShape of selectedShapes) {
|
||||
this._extractSharedStyles(selectedShape, sharedStyles)
|
||||
}
|
||||
|
||||
return sharedProps as TLNullableShapeProps
|
||||
})
|
||||
return sharedStyles
|
||||
}
|
||||
)
|
||||
|
||||
@computed private get _stylesForNextShape() {
|
||||
return this.instanceState.stylesForNextShape
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
private _prevProps: any = {}
|
||||
getStyleForNextShape<T>(style: StyleProp<T>): T {
|
||||
const value = this._stylesForNextShape[style.id]
|
||||
return value === undefined ? style.defaultValue : (value as T)
|
||||
}
|
||||
|
||||
/**
|
||||
* A derived object containing either all current props among the user's selected shapes, or else
|
||||
* the user's most recent prop choices that correspond to the current active state (i.e. the
|
||||
* selected tool).
|
||||
* A derived object containing either all current styles among the user's selected shapes, or
|
||||
* else the user's most recent style choices that correspond to the current active state (i.e.
|
||||
* the selected tool).
|
||||
*
|
||||
* @internal
|
||||
* @public
|
||||
*/
|
||||
@computed get props(): TLNullableShapeProps | null {
|
||||
let next: TLNullableShapeProps | null
|
||||
|
||||
// If we're in selecting and if we have a selection,
|
||||
// return the shared props from the current selection
|
||||
@computed<ReadonlySharedStyleMap>({ isEqual: (a, b) => a.equals(b) })
|
||||
get sharedStyles(): ReadonlySharedStyleMap {
|
||||
// If we're in selecting and if we have a selection, return the shared styles from the
|
||||
// current selection
|
||||
if (this.isIn('select') && this.selectedIds.length > 0) {
|
||||
next = this._selectionSharedProps.value
|
||||
} else {
|
||||
// Otherwise, pull the style props from the app state
|
||||
// (the most recent choices made by the user) that are
|
||||
// exposed by the current state (i.e. the active tool).
|
||||
const currentState = this.root.current.value!
|
||||
if (currentState.styles.length === 0) {
|
||||
next = null
|
||||
} else {
|
||||
const { propsForNextShape } = this.instanceState
|
||||
next = Object.fromEntries(
|
||||
currentState.styles.map((k) => {
|
||||
return [k, propsForNextShape[k]]
|
||||
})
|
||||
)
|
||||
return this._selectionSharedStyles.value
|
||||
}
|
||||
|
||||
// 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 styles = new SharedStyleMap()
|
||||
if (currentTool.shapeType) {
|
||||
for (const style of this.getShapeUtil(currentTool.shapeType).styleProps.keys()) {
|
||||
styles.applyValue(style, this.getStyleForNextShape(style))
|
||||
}
|
||||
}
|
||||
|
||||
// todo: any way to improve this? still faster than rendering the style panel every frame
|
||||
if (JSON.stringify(this._prevProps) === JSON.stringify(next)) {
|
||||
return this._prevProps
|
||||
}
|
||||
|
||||
this._prevProps = next
|
||||
|
||||
return next
|
||||
return styles
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currently selected opacity.
|
||||
* If any shapes are selected, this returns the opacity of the selected shapes.
|
||||
* Get the currently selected shared opacity.
|
||||
* If any shapes are selected, this returns the shared opacity of the selected shapes.
|
||||
* Otherwise, this returns the chosen opacity for the next shape.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
@computed get opacity(): number | null {
|
||||
@computed get sharedOpacity(): SharedStyle<number> {
|
||||
if (this.isIn('select') && this.selectedIds.length > 0) {
|
||||
const shapesToCheck: TLShape[] = []
|
||||
const addShape = (shapeId: TLShapeId) => {
|
||||
|
@ -1231,14 +1210,13 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
if (opacity === null) {
|
||||
opacity = shape.opacity
|
||||
} else if (opacity !== shape.opacity) {
|
||||
return null
|
||||
return { type: 'mixed' }
|
||||
}
|
||||
}
|
||||
|
||||
return opacity
|
||||
} else {
|
||||
return this.instanceState.opacityForNextShape
|
||||
if (opacity !== null) return { type: 'shared', value: opacity }
|
||||
}
|
||||
return { type: 'shared', value: this.instanceState.opacityForNextShape }
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -3260,14 +3238,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
/* --------------------- Shapes --------------------- */
|
||||
|
||||
/**
|
||||
* The app's set of styles.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
static styles = STYLES
|
||||
|
||||
/**
|
||||
* The current page bounds of all the selected shapes (Not the same thing as the page bounds of the selection bounding box when the selection has been rotated)
|
||||
* The current page bounds of all the selected shapes (Not the same thing as the page bounds of
|
||||
* the selection bounding box when the selection has been rotated)
|
||||
*
|
||||
* @readonly
|
||||
*
|
||||
|
@ -3576,59 +3548,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
return shapeIsInPage
|
||||
}
|
||||
|
||||
/* --------------------- Styles --------------------- */
|
||||
|
||||
/**
|
||||
* A mapping of color ids to CSS color values.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
private colors: Map<TLColorStyle['id'], string>
|
||||
|
||||
/**
|
||||
* A mapping of size ids to size values.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
private sizes = {
|
||||
s: 2,
|
||||
m: 3.5,
|
||||
l: 5,
|
||||
xl: 10,
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the CSS color value for a given color id.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* editor.getCssColor('red')
|
||||
* ```
|
||||
*
|
||||
* @param id - The id of the color to get.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
getCssColor(id: TLColorStyle['id']): string {
|
||||
return this.colors.get(id)!
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stroke width value for a given size id.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* editor.getStrokeWidth('m')
|
||||
* ```
|
||||
*
|
||||
* @param id - The id of the size to get.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
getStrokeWidth(id: TLSizeStyle['id']): number {
|
||||
return this.sizes[id]
|
||||
}
|
||||
|
||||
/* ------------------- Statechart ------------------- */
|
||||
|
||||
/**
|
||||
|
@ -5022,15 +4941,10 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
// The initial props starts as the shape utility's default props
|
||||
const initialProps = util.defaultProps()
|
||||
|
||||
// We then look up each key in the tab state's props; and if it's there,
|
||||
// we use the value from the tab state's props instead of the default.
|
||||
// Note that props will never include opacity.
|
||||
const { propsForNextShape, opacityForNextShape } = this.instanceState
|
||||
for (const key in initialProps) {
|
||||
if (key in propsForNextShape) {
|
||||
if (key === 'url') continue
|
||||
;(initialProps as any)[key] = (propsForNextShape as any)[key]
|
||||
}
|
||||
// We then look up each key in the tab state's styles; and if it's there,
|
||||
// we use the value from the tab state's styles instead of the default.
|
||||
for (const [style, propKey] of util.styleProps) {
|
||||
;(initialProps as any)[propKey] = this.getStyleForNextShape(style)
|
||||
}
|
||||
|
||||
// When we create the shape, take in the partial (the props coming into the
|
||||
|
@ -5043,7 +4957,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
).create({
|
||||
...partial,
|
||||
index,
|
||||
opacity: partial.opacity ?? opacityForNextShape,
|
||||
opacity: partial.opacity ?? this.instanceState.opacityForNextShape,
|
||||
parentId: partial.parentId ?? focusLayerId,
|
||||
props: 'props' in partial ? { ...initialProps, ...partial.props } : initialProps,
|
||||
})
|
||||
|
@ -5974,30 +5888,30 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
const fontsUsedInExport = new Map<string, string>()
|
||||
|
||||
const colors: TLExportColors = {
|
||||
fill: Object.fromEntries(
|
||||
STYLES.color.map((color) => [
|
||||
color.id,
|
||||
containerStyle.getPropertyValue(`--palette-${color.id}`),
|
||||
fill: objectMapFromEntries(
|
||||
DefaultColorStyle.values.map((color) => [
|
||||
color,
|
||||
containerStyle.getPropertyValue(`--palette-${color}`),
|
||||
])
|
||||
) as Record<TLColorType, string>,
|
||||
pattern: Object.fromEntries(
|
||||
STYLES.color.map((color) => [
|
||||
color.id,
|
||||
containerStyle.getPropertyValue(`--palette-${color.id}-pattern`),
|
||||
),
|
||||
pattern: objectMapFromEntries(
|
||||
DefaultColorStyle.values.map((color) => [
|
||||
color,
|
||||
containerStyle.getPropertyValue(`--palette-${color}-pattern`),
|
||||
])
|
||||
) as Record<TLColorType, string>,
|
||||
semi: Object.fromEntries(
|
||||
STYLES.color.map((color) => [
|
||||
color.id,
|
||||
containerStyle.getPropertyValue(`--palette-${color.id}-semi`),
|
||||
),
|
||||
semi: objectMapFromEntries(
|
||||
DefaultColorStyle.values.map((color) => [
|
||||
color,
|
||||
containerStyle.getPropertyValue(`--palette-${color}-semi`),
|
||||
])
|
||||
) as Record<TLColorType, string>,
|
||||
highlight: Object.fromEntries(
|
||||
STYLES.color.map((color) => [
|
||||
color.id,
|
||||
containerStyle.getPropertyValue(`--palette-${color.id}-highlight`),
|
||||
),
|
||||
highlight: objectMapFromEntries(
|
||||
DefaultColorStyle.values.map((color) => [
|
||||
color,
|
||||
containerStyle.getPropertyValue(`--palette-${color}-highlight`),
|
||||
])
|
||||
) as Record<TLColorType, string>,
|
||||
),
|
||||
text: containerStyle.getPropertyValue(`--color-text`),
|
||||
background: containerStyle.getPropertyValue(`--color-background`),
|
||||
solid: containerStyle.getPropertyValue(`--palette-solid`),
|
||||
|
@ -6094,16 +6008,17 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
const util = this.getShapeUtil(shape)
|
||||
|
||||
let font: string | undefined
|
||||
if ('font' in shape.props) {
|
||||
if (shape.props.font) {
|
||||
if (fontsUsedInExport.has(shape.props.font)) {
|
||||
font = fontsUsedInExport.get(shape.props.font)!
|
||||
// TODO: `Editor` shouldn't know about `DefaultFontStyle`. We need another way
|
||||
// for shapes to register fonts for export.
|
||||
const fontFromShape = util.getStyleIfExists(DefaultFontStyle, shape)
|
||||
if (fontFromShape) {
|
||||
if (fontsUsedInExport.has(fontFromShape)) {
|
||||
font = fontsUsedInExport.get(fontFromShape)!
|
||||
} else {
|
||||
// For some reason these styles aren't present in the fake element
|
||||
// so we need to get them from the real element
|
||||
font = realContainerStyle.getPropertyValue(`--tl-font-${shape.props.font}`)
|
||||
fontsUsedInExport.set(shape.props.font, font)
|
||||
}
|
||||
font = realContainerStyle.getPropertyValue(`--tl-font-${fontFromShape}`)
|
||||
fontsUsedInExport.set(fontFromShape, font)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8255,22 +8170,22 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
}
|
||||
|
||||
/**
|
||||
* Set the current props (generally styles).
|
||||
* Set the current styles
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* editor.setProp('color', 'red')
|
||||
* editor.setProp('color', 'red', true)
|
||||
* editor.setProp(DefaultColorStyle, 'red')
|
||||
* editor.setProp(DefaultColorStyle, 'red', true)
|
||||
* ```
|
||||
*
|
||||
* @param key - The key to set.
|
||||
* @param style - The style to set.
|
||||
* @param value - The value to set.
|
||||
* @param ephemeral - Whether the style change is ephemeral. Ephemeral changes don't get added to the undo/redo stack. Defaults to false.
|
||||
* @param squashing - Whether the style change will be squashed into the existing history entry rather than creating a new one. Defaults to false.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
setProp(key: TLShapeProp, value: any, ephemeral = false, squashing = false): this {
|
||||
setStyle<T>(style: StyleProp<T>, value: T, ephemeral = false, squashing = false): this {
|
||||
this.history.batch(() => {
|
||||
if (this.isIn('select')) {
|
||||
const {
|
||||
|
@ -8278,7 +8193,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
} = this
|
||||
|
||||
if (selectedIds.length > 0) {
|
||||
const shapesToUpdate: TLShape[] = []
|
||||
const updates: { originalShape: TLShape; updatePartial: TLShapePartial }[] = []
|
||||
|
||||
// We can have many deep levels of grouped shape
|
||||
// Making a recursive function to look through all the levels
|
||||
|
@ -8290,8 +8205,19 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
for (const childId of childIds) {
|
||||
addShapeById(childId)
|
||||
}
|
||||
} else if (shape!.props[key as keyof TLShape['props']] !== undefined) {
|
||||
shapesToUpdate.push(shape)
|
||||
} else {
|
||||
const util = this.getShapeUtil(shape)
|
||||
if (util.hasStyle(style)) {
|
||||
const shapePartial: TLShapePartial = {
|
||||
id: shape.id,
|
||||
type: shape.type,
|
||||
props: {},
|
||||
}
|
||||
updates.push({
|
||||
originalShape: shape,
|
||||
updatePartial: util.setStyleInPartial(style, shapePartial, value),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8300,41 +8226,29 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
}
|
||||
|
||||
this.updateShapes(
|
||||
shapesToUpdate.map((shape) => {
|
||||
const props = { ...shape.props, [key]: value }
|
||||
if (key === 'color' && 'labelColor' in props) {
|
||||
props.labelColor = 'black'
|
||||
}
|
||||
|
||||
return {
|
||||
id: shape.id,
|
||||
type: shape.type,
|
||||
props,
|
||||
}
|
||||
}),
|
||||
updates.map(({ updatePartial }) => updatePartial),
|
||||
ephemeral
|
||||
)
|
||||
|
||||
if (key !== 'color') {
|
||||
// TODO: find a way to sink this stuff into shape utils directly?
|
||||
const changes: TLShapePartial[] = []
|
||||
|
||||
for (const shape of shapesToUpdate) {
|
||||
const currentShape = this.getShapeById(shape.id)
|
||||
for (const { originalShape: originalShape } of updates) {
|
||||
const currentShape = this.getShapeById(originalShape.id)
|
||||
if (!currentShape) continue
|
||||
const util = this.getShapeUtil(currentShape)
|
||||
|
||||
const boundsA = util.bounds(shape)
|
||||
const boundsA = util.bounds(originalShape)
|
||||
const boundsB = util.bounds(currentShape)
|
||||
|
||||
const change: TLShapePartial = { id: shape.id, type: shape.type }
|
||||
const change: TLShapePartial = { id: originalShape.id, type: originalShape.type }
|
||||
|
||||
let didChange = false
|
||||
|
||||
if (boundsA.width !== boundsB.width) {
|
||||
didChange = true
|
||||
|
||||
if (this.isShapeOfType(shape, TextShapeUtil)) {
|
||||
switch (shape.props.align) {
|
||||
if (this.isShapeOfType(originalShape, TextShapeUtil)) {
|
||||
switch (originalShape.props.align) {
|
||||
case 'middle': {
|
||||
change.x = currentShape.x + (boundsA.width - boundsB.width) / 2
|
||||
break
|
||||
|
@ -8364,13 +8278,10 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.updateInstanceState(
|
||||
{
|
||||
propsForNextShape: setPropsForNextShape(this.instanceState.propsForNextShape, {
|
||||
[key]: value,
|
||||
}),
|
||||
stylesForNextShape: { ...this._stylesForNextShape, [style.id]: value },
|
||||
},
|
||||
ephemeral,
|
||||
squashing
|
||||
|
|
|
@ -11,13 +11,7 @@ import {
|
|||
createShapeId,
|
||||
} from '@tldraw/tlschema'
|
||||
import { compact, getHashForString } from '@tldraw/utils'
|
||||
import {
|
||||
FONT_FAMILIES,
|
||||
FONT_SIZES,
|
||||
MAX_ASSET_HEIGHT,
|
||||
MAX_ASSET_WIDTH,
|
||||
TEXT_PROPS,
|
||||
} from '../../constants'
|
||||
import { MAX_ASSET_HEIGHT, MAX_ASSET_WIDTH } from '../../constants'
|
||||
import {
|
||||
ACCEPTED_IMG_TYPE,
|
||||
ACCEPTED_VID_TYPE,
|
||||
|
@ -31,6 +25,7 @@ import {
|
|||
import { truncateStringWithEllipsis } from '../../utils/dom'
|
||||
import { getEmbedInfo } from '../../utils/embeds'
|
||||
import { Editor } from '../Editor'
|
||||
import { FONT_FAMILIES, FONT_SIZES, TEXT_PROPS } from '../shapes/shared/default-shape-constants'
|
||||
import { INDENT } from '../shapes/text/TextHelpers'
|
||||
import { TextShapeUtil } from '../shapes/text/TextShapeUtil'
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Box2dModel, TLAlignType } from '@tldraw/tlschema'
|
||||
import { Box2dModel, TLDefaultHorizontalAlignStyle } from '@tldraw/tlschema'
|
||||
import { uniqueId } from '../../utils/data'
|
||||
import { Editor } from '../Editor'
|
||||
import { TextHelpers } from '../shapes/text/TextHelpers'
|
||||
|
@ -23,7 +23,7 @@ type TLMeasureTextSpanOpts = {
|
|||
fontFamily: string
|
||||
fontStyle: string
|
||||
lineHeight: number
|
||||
textAlign: TLAlignType
|
||||
textAlign: TLDefaultHorizontalAlignStyle
|
||||
}
|
||||
|
||||
const spaceCharacterRegex = /\s/
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { Box2d, linesIntersect, Vec2d, VecLike } from '@tldraw/primitives'
|
||||
import { ComputedCache } from '@tldraw/store'
|
||||
import { TLHandle, TLShape, TLShapePartial, TLUnknownShape } from '@tldraw/tlschema'
|
||||
import { StyleProp, TLHandle, TLShape, TLShapePartial, TLUnknownShape } from '@tldraw/tlschema'
|
||||
import { computed, EMPTY_ARRAY } from 'signia'
|
||||
import type { Editor } from '../Editor'
|
||||
import { TLResizeHandle } from '../types/selection-types'
|
||||
|
@ -12,7 +12,7 @@ export interface TLShapeUtilConstructor<
|
|||
T extends TLUnknownShape,
|
||||
U extends ShapeUtil<T> = ShapeUtil<T>
|
||||
> {
|
||||
new (editor: Editor, type: T['type']): U
|
||||
new (editor: Editor, type: T['type'], styleProps: ReadonlyMap<StyleProp<unknown>, string>): U
|
||||
type: T['type']
|
||||
}
|
||||
|
||||
|
@ -20,8 +20,45 @@ export interface TLShapeUtilConstructor<
|
|||
export type TLShapeUtilFlag<T> = (shape: T) => boolean
|
||||
|
||||
/** @public */
|
||||
export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
||||
constructor(public editor: Editor, public readonly type: T['type']) {}
|
||||
export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
|
||||
constructor(
|
||||
public editor: Editor,
|
||||
public readonly type: Shape['type'],
|
||||
public readonly styleProps: ReadonlyMap<StyleProp<unknown>, string>
|
||||
) {}
|
||||
|
||||
hasStyle(style: StyleProp<unknown>) {
|
||||
return this.styleProps.has(style)
|
||||
}
|
||||
|
||||
getStyleIfExists<T>(style: StyleProp<T>, shape: Shape | TLShapePartial<Shape>): T | undefined {
|
||||
const styleKey = this.styleProps.get(style)
|
||||
if (!styleKey) return undefined
|
||||
return (shape.props as any)[styleKey]
|
||||
}
|
||||
|
||||
*iterateStyles(shape: Shape | TLShapePartial<Shape>) {
|
||||
for (const [style, styleKey] of this.styleProps) {
|
||||
const value = (shape.props as any)[styleKey]
|
||||
yield [style, value] as [StyleProp<unknown>, unknown]
|
||||
}
|
||||
}
|
||||
|
||||
setStyleInPartial<T>(
|
||||
style: StyleProp<T>,
|
||||
shape: TLShapePartial<Shape>,
|
||||
value: T
|
||||
): TLShapePartial<Shape> {
|
||||
const styleKey = this.styleProps.get(style)
|
||||
if (!styleKey) return shape
|
||||
return {
|
||||
...shape,
|
||||
props: {
|
||||
...shape.props,
|
||||
[styleKey]: value,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of the shape util, which should match the shape's type.
|
||||
|
@ -35,21 +72,21 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
*
|
||||
* @public
|
||||
*/
|
||||
canSnap: TLShapeUtilFlag<T> = () => true
|
||||
canSnap: TLShapeUtilFlag<Shape> = () => true
|
||||
|
||||
/**
|
||||
* Whether the shape can be scrolled while editing.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
canScroll: TLShapeUtilFlag<T> = () => false
|
||||
canScroll: TLShapeUtilFlag<Shape> = () => false
|
||||
|
||||
/**
|
||||
* Whether the shape should unmount when not visible in the editor. Consider keeping this to false if the shape's `component` has local state.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
canUnmount: TLShapeUtilFlag<T> = () => true
|
||||
canUnmount: TLShapeUtilFlag<Shape> = () => true
|
||||
|
||||
/**
|
||||
* Whether the shape can be bound to by an arrow.
|
||||
|
@ -57,28 +94,28 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
* @param _otherShape - The other shape attempting to bind to this shape.
|
||||
* @public
|
||||
*/
|
||||
canBind = <K>(_shape: T, _otherShape?: K) => true
|
||||
canBind = <K>(_shape: Shape, _otherShape?: K) => true
|
||||
|
||||
/**
|
||||
* Whether the shape can be double clicked to edit.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
canEdit: TLShapeUtilFlag<T> = () => false
|
||||
canEdit: TLShapeUtilFlag<Shape> = () => false
|
||||
|
||||
/**
|
||||
* Whether the shape can be resized.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
canResize: TLShapeUtilFlag<T> = () => true
|
||||
canResize: TLShapeUtilFlag<Shape> = () => true
|
||||
|
||||
/**
|
||||
* Whether the shape can be cropped.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
canCrop: TLShapeUtilFlag<T> = () => false
|
||||
canCrop: TLShapeUtilFlag<Shape> = () => false
|
||||
|
||||
/**
|
||||
* Bounds of the shape to edit.
|
||||
|
@ -87,7 +124,7 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
*
|
||||
* @public
|
||||
*/
|
||||
getEditingBounds = (shape: T) => {
|
||||
getEditingBounds = (shape: Shape) => {
|
||||
return this.bounds(shape)
|
||||
}
|
||||
|
||||
|
@ -96,49 +133,49 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
*
|
||||
* @public
|
||||
*/
|
||||
isClosed: TLShapeUtilFlag<T> = () => true
|
||||
isClosed: TLShapeUtilFlag<Shape> = () => true
|
||||
|
||||
/**
|
||||
* Whether the shape should hide its resize handles when selected.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
hideResizeHandles: TLShapeUtilFlag<T> = () => false
|
||||
hideResizeHandles: TLShapeUtilFlag<Shape> = () => false
|
||||
|
||||
/**
|
||||
* Whether the shape should hide its resize handles when selected.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
hideRotateHandle: TLShapeUtilFlag<T> = () => false
|
||||
hideRotateHandle: TLShapeUtilFlag<Shape> = () => false
|
||||
|
||||
/**
|
||||
* Whether the shape should hide its selection bounds background when selected.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
hideSelectionBoundsBg: TLShapeUtilFlag<T> = () => false
|
||||
hideSelectionBoundsBg: TLShapeUtilFlag<Shape> = () => false
|
||||
|
||||
/**
|
||||
* Whether the shape should hide its selection bounds foreground when selected.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
hideSelectionBoundsFg: TLShapeUtilFlag<T> = () => false
|
||||
hideSelectionBoundsFg: TLShapeUtilFlag<Shape> = () => false
|
||||
|
||||
/**
|
||||
* Whether the shape's aspect ratio is locked.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
isAspectRatioLocked: TLShapeUtilFlag<T> = () => false
|
||||
isAspectRatioLocked: TLShapeUtilFlag<Shape> = () => false
|
||||
|
||||
/**
|
||||
* Get the default props for a shape.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
abstract defaultProps(): T['props']
|
||||
abstract defaultProps(): Shape['props']
|
||||
|
||||
/**
|
||||
* Get a JSX element for the shape (as an HTML element).
|
||||
|
@ -146,7 +183,7 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
* @param shape - The shape.
|
||||
* @public
|
||||
*/
|
||||
abstract render(shape: T): any
|
||||
abstract render(shape: Shape): any
|
||||
|
||||
/**
|
||||
* Get JSX describing the shape's indicator (as an SVG element).
|
||||
|
@ -154,7 +191,7 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
* @param shape - The shape.
|
||||
* @public
|
||||
*/
|
||||
abstract indicator(shape: T): any
|
||||
abstract indicator(shape: Shape): any
|
||||
|
||||
/**
|
||||
* Get a JSX element for the shape (as an HTML element) to be rendered as part of the canvas background - behind any other shape content.
|
||||
|
@ -162,7 +199,7 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
* @param shape - The shape.
|
||||
* @internal
|
||||
*/
|
||||
renderBackground?(shape: T): any
|
||||
renderBackground?(shape: Shape): any
|
||||
|
||||
/**
|
||||
* Get an array of handle models for the shape. This is an optional method.
|
||||
|
@ -176,7 +213,7 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
* @param shape - The shape.
|
||||
* @public
|
||||
*/
|
||||
protected getHandles?(shape: T): TLHandle[]
|
||||
protected getHandles?(shape: Shape): TLHandle[]
|
||||
|
||||
@computed
|
||||
private get handlesCache(): ComputedCache<TLHandle[], TLShape> {
|
||||
|
@ -191,7 +228,7 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
* @param shape - The shape.
|
||||
* @public
|
||||
*/
|
||||
handles(shape: T): TLHandle[] {
|
||||
handles(shape: Shape): TLHandle[] {
|
||||
if (!this.getHandles) return EMPTY_ARRAY
|
||||
return this.handlesCache.get(shape.id) ?? EMPTY_ARRAY
|
||||
}
|
||||
|
@ -211,7 +248,7 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
* @param shape - The shape.
|
||||
* @public
|
||||
*/
|
||||
protected getOutlineSegments(shape: T): Vec2d[][] {
|
||||
protected getOutlineSegments(shape: Shape): Vec2d[][] {
|
||||
return [this.outline(shape)]
|
||||
}
|
||||
|
||||
|
@ -228,7 +265,7 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
* @param shape - The shape.
|
||||
* @public
|
||||
*/
|
||||
outlineSegments(shape: T): Vec2d[][] {
|
||||
outlineSegments(shape: Shape): Vec2d[][] {
|
||||
if (!this.getOutlineSegments) return EMPTY_ARRAY
|
||||
return this.outlineSegmentsCache.get(shape.id) ?? EMPTY_ARRAY
|
||||
}
|
||||
|
@ -239,7 +276,7 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
* @param shape - The shape.
|
||||
* @public
|
||||
*/
|
||||
protected abstract getBounds(shape: T): Box2d
|
||||
protected abstract getBounds(shape: Shape): Box2d
|
||||
|
||||
@computed
|
||||
private get boundsCache(): ComputedCache<Box2d, TLShape> {
|
||||
|
@ -254,7 +291,7 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
* @param shape - The shape.
|
||||
* @public
|
||||
*/
|
||||
bounds(shape: T): Box2d {
|
||||
bounds(shape: Shape): Box2d {
|
||||
const result = this.boundsCache.get(shape.id) ?? new Box2d()
|
||||
if (result.width === 0 || result.height === 0) {
|
||||
return new Box2d(result.x, result.y, Math.max(result.width, 1), Math.max(result.height, 1))
|
||||
|
@ -268,7 +305,7 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
* @param shape - The shape.
|
||||
* @public
|
||||
*/
|
||||
protected abstract getOutline(shape: T): Vec2d[]
|
||||
protected abstract getOutline(shape: Shape): Vec2d[]
|
||||
|
||||
@computed
|
||||
private get outlineCache(): ComputedCache<Vec2d[], TLShape> {
|
||||
|
@ -283,7 +320,7 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
* @param shape - The shape.
|
||||
* @public
|
||||
*/
|
||||
outline(shape: T): Vec2d[] {
|
||||
outline(shape: Shape): Vec2d[] {
|
||||
return this.outlineCache.get(shape.id) ?? EMPTY_ARRAY
|
||||
}
|
||||
|
||||
|
@ -293,7 +330,7 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
* @param shape - The shape.
|
||||
* @public
|
||||
*/
|
||||
snapPoints(shape: T) {
|
||||
snapPoints(shape: Shape) {
|
||||
return this.bounds(shape).snapPoints
|
||||
}
|
||||
|
||||
|
@ -303,7 +340,7 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
* @param shape - The shape.
|
||||
* @public
|
||||
*/
|
||||
center(shape: T): Vec2d {
|
||||
center(shape: Shape): Vec2d {
|
||||
return this.getCenter(shape)
|
||||
}
|
||||
|
||||
|
@ -313,7 +350,7 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
* @param shape - The shape.
|
||||
* @public
|
||||
*/
|
||||
abstract getCenter(shape: T): Vec2d
|
||||
abstract getCenter(shape: Shape): Vec2d
|
||||
|
||||
/**
|
||||
* Get whether the shape can receive children of a given type.
|
||||
|
@ -321,7 +358,7 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
* @param type - The shape type.
|
||||
* @public
|
||||
*/
|
||||
canReceiveNewChildrenOfType(shape: T, type: TLShape['type']) {
|
||||
canReceiveNewChildrenOfType(shape: Shape, type: TLShape['type']) {
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -332,7 +369,7 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
* @param shapes - The shapes that are being dropped.
|
||||
* @public
|
||||
*/
|
||||
canDropShapes(shape: T, shapes: TLShape[]) {
|
||||
canDropShapes(shape: Shape, shapes: TLShape[]) {
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -346,7 +383,7 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
* @public
|
||||
*/
|
||||
toSvg?(
|
||||
shape: T,
|
||||
shape: Shape,
|
||||
font: string | undefined,
|
||||
colors: TLExportColors
|
||||
): SVGElement | Promise<SVGElement>
|
||||
|
@ -361,7 +398,7 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
* @public
|
||||
*/
|
||||
toBackgroundSvg?(
|
||||
shape: T,
|
||||
shape: Shape,
|
||||
font: string | undefined,
|
||||
colors: TLExportColors
|
||||
): SVGElement | Promise<SVGElement> | null
|
||||
|
@ -374,7 +411,7 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
* @returns Whether the point intersects the shape.
|
||||
* @public
|
||||
*/
|
||||
hitTestPoint(shape: T, point: VecLike): boolean {
|
||||
hitTestPoint(shape: Shape, point: VecLike): boolean {
|
||||
return this.bounds(shape).containsPoint(point)
|
||||
}
|
||||
|
||||
|
@ -387,7 +424,7 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
* @returns Whether the line segment intersects the shape.
|
||||
* @public
|
||||
*/
|
||||
hitTestLineSegment(shape: T, A: VecLike, B: VecLike): boolean {
|
||||
hitTestLineSegment(shape: Shape, A: VecLike, B: VecLike): boolean {
|
||||
const outline = this.outline(shape)
|
||||
|
||||
for (let i = 0; i < outline.length; i++) {
|
||||
|
@ -400,7 +437,7 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
}
|
||||
|
||||
/** @internal */
|
||||
expandSelectionOutlinePx(shape: T): number {
|
||||
expandSelectionOutlinePx(shape: Shape): number {
|
||||
return 0
|
||||
}
|
||||
|
||||
|
@ -413,7 +450,7 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
*
|
||||
* @internal
|
||||
*/
|
||||
providesBackgroundForChildren(shape: T): boolean {
|
||||
providesBackgroundForChildren(shape: Shape): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -435,7 +472,7 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
* @returns The next shape or void.
|
||||
* @public
|
||||
*/
|
||||
onBeforeCreate?: TLOnBeforeCreateHandler<T>
|
||||
onBeforeCreate?: TLOnBeforeCreateHandler<Shape>
|
||||
|
||||
/**
|
||||
* A callback called just before a shape is updated. This method provides a last chance to modify
|
||||
|
@ -456,7 +493,7 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
* @returns The next shape or void.
|
||||
* @public
|
||||
*/
|
||||
onBeforeUpdate?: TLOnBeforeUpdateHandler<T>
|
||||
onBeforeUpdate?: TLOnBeforeUpdateHandler<Shape>
|
||||
|
||||
/**
|
||||
* A callback called when some other shapes are dragged over this one.
|
||||
|
@ -474,7 +511,7 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
* @returns An object specifying whether the shape should hint that it can receive the dragged shapes.
|
||||
* @public
|
||||
*/
|
||||
onDragShapesOver?: TLOnDragHandler<T, { shouldHint: boolean }>
|
||||
onDragShapesOver?: TLOnDragHandler<Shape, { shouldHint: boolean }>
|
||||
|
||||
/**
|
||||
* A callback called when some other shapes are dragged out of this one.
|
||||
|
@ -483,7 +520,7 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
* @param shapes - The shapes that are being dragged out.
|
||||
* @public
|
||||
*/
|
||||
onDragShapesOut?: TLOnDragHandler<T>
|
||||
onDragShapesOut?: TLOnDragHandler<Shape>
|
||||
|
||||
/**
|
||||
* A callback called when some other shapes are dropped over this one.
|
||||
|
@ -492,7 +529,7 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
* @param shapes - The shapes that are being dropped over this one.
|
||||
* @public
|
||||
*/
|
||||
onDropShapesOver?: TLOnDragHandler<T>
|
||||
onDropShapesOver?: TLOnDragHandler<Shape>
|
||||
|
||||
/**
|
||||
* A callback called when a shape starts being resized.
|
||||
|
@ -501,7 +538,7 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
* @returns A change to apply to the shape, or void.
|
||||
* @public
|
||||
*/
|
||||
onResizeStart?: TLOnResizeStartHandler<T>
|
||||
onResizeStart?: TLOnResizeStartHandler<Shape>
|
||||
|
||||
/**
|
||||
* A callback called when a shape changes from a resize.
|
||||
|
@ -511,7 +548,7 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
* @returns A change to apply to the shape, or void.
|
||||
* @public
|
||||
*/
|
||||
onResize?: TLOnResizeHandler<T>
|
||||
onResize?: TLOnResizeHandler<Shape>
|
||||
|
||||
/**
|
||||
* A callback called when a shape finishes resizing.
|
||||
|
@ -521,7 +558,7 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
* @returns A change to apply to the shape, or void.
|
||||
* @public
|
||||
*/
|
||||
onResizeEnd?: TLOnResizeEndHandler<T>
|
||||
onResizeEnd?: TLOnResizeEndHandler<Shape>
|
||||
|
||||
/**
|
||||
* A callback called when a shape starts being translated.
|
||||
|
@ -530,7 +567,7 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
* @returns A change to apply to the shape, or void.
|
||||
* @public
|
||||
*/
|
||||
onTranslateStart?: TLOnTranslateStartHandler<T>
|
||||
onTranslateStart?: TLOnTranslateStartHandler<Shape>
|
||||
|
||||
/**
|
||||
* A callback called when a shape changes from a translation.
|
||||
|
@ -540,7 +577,7 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
* @returns A change to apply to the shape, or void.
|
||||
* @public
|
||||
*/
|
||||
onTranslate?: TLOnTranslateHandler<T>
|
||||
onTranslate?: TLOnTranslateHandler<Shape>
|
||||
|
||||
/**
|
||||
* A callback called when a shape finishes translating.
|
||||
|
@ -550,7 +587,7 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
* @returns A change to apply to the shape, or void.
|
||||
* @public
|
||||
*/
|
||||
onTranslateEnd?: TLOnTranslateEndHandler<T>
|
||||
onTranslateEnd?: TLOnTranslateEndHandler<Shape>
|
||||
|
||||
/**
|
||||
* A callback called when a shape starts being rotated.
|
||||
|
@ -559,7 +596,7 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
* @returns A change to apply to the shape, or void.
|
||||
* @public
|
||||
*/
|
||||
onRotateStart?: TLOnRotateStartHandler<T>
|
||||
onRotateStart?: TLOnRotateStartHandler<Shape>
|
||||
|
||||
/**
|
||||
* A callback called when a shape changes from a rotation.
|
||||
|
@ -569,7 +606,7 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
* @returns A change to apply to the shape, or void.
|
||||
* @public
|
||||
*/
|
||||
onRotate?: TLOnRotateHandler<T>
|
||||
onRotate?: TLOnRotateHandler<Shape>
|
||||
|
||||
/**
|
||||
* A callback called when a shape finishes rotating.
|
||||
|
@ -579,7 +616,7 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
* @returns A change to apply to the shape, or void.
|
||||
* @public
|
||||
*/
|
||||
onRotateEnd?: TLOnRotateEndHandler<T>
|
||||
onRotateEnd?: TLOnRotateEndHandler<Shape>
|
||||
|
||||
/**
|
||||
* A callback called when a shape's handle changes.
|
||||
|
@ -589,14 +626,14 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
* @returns A change to apply to the shape, or void.
|
||||
* @public
|
||||
*/
|
||||
onHandleChange?: TLOnHandleChangeHandler<T>
|
||||
onHandleChange?: TLOnHandleChangeHandler<Shape>
|
||||
|
||||
/**
|
||||
* Not currently used.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
onBindingChange?: TLOnBindingChangeHandler<T>
|
||||
onBindingChange?: TLOnBindingChangeHandler<Shape>
|
||||
|
||||
/**
|
||||
* A callback called when a shape's children change.
|
||||
|
@ -605,7 +642,7 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
* @returns An array of shape updates, or void.
|
||||
* @public
|
||||
*/
|
||||
onChildrenChange?: TLOnChildrenChangeHandler<T>
|
||||
onChildrenChange?: TLOnChildrenChangeHandler<Shape>
|
||||
|
||||
/**
|
||||
* A callback called when a shape's handle is double clicked.
|
||||
|
@ -615,7 +652,7 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
* @returns A change to apply to the shape, or void.
|
||||
* @public
|
||||
*/
|
||||
onDoubleClickHandle?: TLOnDoubleClickHandleHandler<T>
|
||||
onDoubleClickHandle?: TLOnDoubleClickHandleHandler<Shape>
|
||||
|
||||
/**
|
||||
* A callback called when a shape's edge is double clicked.
|
||||
|
@ -624,7 +661,7 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
* @returns A change to apply to the shape, or void.
|
||||
* @public
|
||||
*/
|
||||
onDoubleClickEdge?: TLOnDoubleClickHandler<T>
|
||||
onDoubleClickEdge?: TLOnDoubleClickHandler<Shape>
|
||||
|
||||
/**
|
||||
* A callback called when a shape is double clicked.
|
||||
|
@ -633,7 +670,7 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
* @returns A change to apply to the shape, or void.
|
||||
* @public
|
||||
*/
|
||||
onDoubleClick?: TLOnDoubleClickHandler<T>
|
||||
onDoubleClick?: TLOnDoubleClickHandler<Shape>
|
||||
|
||||
/**
|
||||
* A callback called when a shape is clicked.
|
||||
|
@ -642,7 +679,7 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
* @returns A change to apply to the shape, or void.
|
||||
* @public
|
||||
*/
|
||||
onClick?: TLOnClickHandler<T>
|
||||
onClick?: TLOnClickHandler<Shape>
|
||||
|
||||
/**
|
||||
* A callback called when a shape finishes being editing.
|
||||
|
@ -650,7 +687,7 @@ export abstract class ShapeUtil<T extends TLUnknownShape = TLUnknownShape> {
|
|||
* @param shape - The shape.
|
||||
* @public
|
||||
*/
|
||||
onEditEnd?: TLOnEditEndHandler<T>
|
||||
onEditEnd?: TLOnEditEndHandler<Shape>
|
||||
}
|
||||
|
||||
/** @public */
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { TLStyleType } from '@tldraw/tlschema'
|
||||
import { StateNode } from '../../tools/StateNode'
|
||||
import { ArrowShapeUtil } from './ArrowShapeUtil'
|
||||
import { Idle } from './toolStates/Idle'
|
||||
import { Pointing } from './toolStates/Pointing'
|
||||
|
||||
|
@ -8,15 +8,5 @@ export class ArrowShapeTool extends StateNode {
|
|||
static initial = 'idle'
|
||||
static children = () => [Idle, Pointing]
|
||||
|
||||
shapeType = 'arrow'
|
||||
|
||||
styles = [
|
||||
'color',
|
||||
'dash',
|
||||
'size',
|
||||
'arrowheadStart',
|
||||
'arrowheadEnd',
|
||||
'font',
|
||||
'fill',
|
||||
] as TLStyleType[]
|
||||
shapeType = ArrowShapeUtil
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { TAU } from '@tldraw/primitives'
|
||||
import { TLArrowShape, TLArrowTerminal, TLShapeId, createShapeId } from '@tldraw/tlschema'
|
||||
import { TLArrowShape, TLArrowShapeTerminal, TLShapeId, createShapeId } from '@tldraw/tlschema'
|
||||
import { assert } from '@tldraw/utils'
|
||||
import { TestEditor } from '../../../test/TestEditor'
|
||||
import { ArrowShapeUtil } from './ArrowShapeUtil'
|
||||
|
@ -343,7 +343,7 @@ describe('When a shape it rotated', () => {
|
|||
})
|
||||
|
||||
const anchor = (
|
||||
editor.getShapeById<TLArrowShape>(arrow.id)!.props.end as TLArrowTerminal & {
|
||||
editor.getShapeById<TLArrowShape>(arrow.id)!.props.end as TLArrowShapeTerminal & {
|
||||
type: 'binding'
|
||||
}
|
||||
).normalizedAnchor
|
||||
|
|
|
@ -12,10 +12,10 @@ import {
|
|||
} from '@tldraw/primitives'
|
||||
import { ComputedCache } from '@tldraw/store'
|
||||
import {
|
||||
TLArrowheadType,
|
||||
TLArrowShape,
|
||||
TLColorType,
|
||||
TLFillType,
|
||||
TLArrowShapeArrowheadStyle,
|
||||
TLDefaultColorStyle,
|
||||
TLDefaultFillStyle,
|
||||
TLHandle,
|
||||
TLShapeId,
|
||||
TLShapePartial,
|
||||
|
@ -25,7 +25,6 @@ import { deepCopy, last, minBy } from '@tldraw/utils'
|
|||
import * as React from 'react'
|
||||
import { computed, EMPTY_ARRAY } from 'signia'
|
||||
import { SVGContainer } from '../../../components/SVGContainer'
|
||||
import { ARROW_LABEL_FONT_SIZES, FONT_FAMILIES, TEXT_PROPS } from '../../../constants'
|
||||
import {
|
||||
ShapeUtil,
|
||||
TLOnEditEndHandler,
|
||||
|
@ -35,6 +34,12 @@ import {
|
|||
TLShapeUtilFlag,
|
||||
} from '../ShapeUtil'
|
||||
import { createTextSvgElementFromSpans } from '../shared/createTextSvgElementFromSpans'
|
||||
import {
|
||||
ARROW_LABEL_FONT_SIZES,
|
||||
FONT_FAMILIES,
|
||||
STROKE_SIZES,
|
||||
TEXT_PROPS,
|
||||
} from '../shared/default-shape-constants'
|
||||
import { getPerfectDashProps } from '../shared/getPerfectDashProps'
|
||||
import { getShapeFillSvg, ShapeFill } from '../shared/ShapeFill'
|
||||
import { TLExportColors } from '../shared/TLExportColors'
|
||||
|
@ -530,7 +535,7 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
|
|||
hitTestPoint(shape: TLArrowShape, point: VecLike): boolean {
|
||||
const outline = this.outline(shape)
|
||||
const zoomLevel = this.editor.zoomLevel
|
||||
const offsetDist = this.editor.getStrokeWidth(shape.props.size) / zoomLevel
|
||||
const offsetDist = STROKE_SIZES[shape.props.size] / zoomLevel
|
||||
|
||||
for (let i = 0; i < outline.length - 1; i++) {
|
||||
const C = outline[i]
|
||||
|
@ -577,7 +582,7 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
|
|||
|
||||
if (!info?.isValid) return null
|
||||
|
||||
const strokeWidth = this.editor.getStrokeWidth(shape.props.size)
|
||||
const strokeWidth = STROKE_SIZES[shape.props.size]
|
||||
|
||||
const as = info.start.arrowhead && getArrowheadPathForType(info, 'start', strokeWidth)
|
||||
const ae = info.end.arrowhead && getArrowheadPathForType(info, 'end', strokeWidth)
|
||||
|
@ -692,7 +697,7 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
|
|||
)}
|
||||
<g
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke={`var(--palette-${shape.props.color})`}
|
||||
strokeWidth={strokeWidth}
|
||||
strokeLinejoin="round"
|
||||
strokeLinecap="round"
|
||||
|
@ -735,7 +740,7 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
|
|||
size={shape.props.size}
|
||||
position={info.middle}
|
||||
width={labelSize?.w ?? 0}
|
||||
labelColor={this.editor.getCssColor(shape.props.labelColor)}
|
||||
labelColor={shape.props.labelColor}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
@ -751,7 +756,7 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
|
|||
if (!info) return null
|
||||
if (Vec2d.Equals(start, end)) return null
|
||||
|
||||
const strokeWidth = this.editor.getStrokeWidth(shape.props.size)
|
||||
const strokeWidth = STROKE_SIZES[shape.props.size]
|
||||
|
||||
const as = info.start.arrowhead && getArrowheadPathForType(info, 'start', strokeWidth)
|
||||
const ae = info.end.arrowhead && getArrowheadPathForType(info, 'end', strokeWidth)
|
||||
|
@ -925,7 +930,7 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
|
|||
|
||||
const info = this.getArrowInfo(shape)
|
||||
|
||||
const strokeWidth = this.editor.getStrokeWidth(shape.props.size)
|
||||
const strokeWidth = STROKE_SIZES[shape.props.size]
|
||||
|
||||
// Group for arrow
|
||||
const g = document.createElementNS('http://www.w3.org/2000/svg', 'g')
|
||||
|
@ -1088,7 +1093,7 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
|
|||
}
|
||||
}
|
||||
|
||||
function getArrowheadSvgMask(d: string, arrowhead: TLArrowheadType) {
|
||||
function getArrowheadSvgMask(d: string, arrowhead: TLArrowShapeArrowheadStyle) {
|
||||
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path')
|
||||
path.setAttribute('d', d)
|
||||
path.setAttribute('fill', arrowhead === 'arrow' ? 'none' : 'black')
|
||||
|
@ -1107,9 +1112,9 @@ function getArrowSvgPath(d: string, color: string, strokeWidth: number) {
|
|||
|
||||
function getArrowheadSvgPath(
|
||||
d: string,
|
||||
color: TLColorType,
|
||||
color: TLDefaultColorStyle,
|
||||
strokeWidth: number,
|
||||
fill: TLFillType,
|
||||
fill: TLDefaultFillStyle,
|
||||
colors: TLExportColors
|
||||
) {
|
||||
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path')
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { VecLike } from '@tldraw/primitives'
|
||||
import { TLArrowheadType } from '@tldraw/tlschema'
|
||||
import { TLArrowShapeArrowheadStyle } from '@tldraw/tlschema'
|
||||
|
||||
export type ArrowPoint = {
|
||||
handle: VecLike
|
||||
point: VecLike
|
||||
arrowhead: TLArrowheadType
|
||||
arrowhead: TLArrowShapeArrowheadStyle
|
||||
}
|
||||
|
||||
export interface ArcInfo {
|
||||
|
|
|
@ -14,14 +14,16 @@ import {
|
|||
VecLike,
|
||||
} from '@tldraw/primitives'
|
||||
import { TLArrowShape } from '@tldraw/tlschema'
|
||||
import type { Editor } from '../../../Editor'
|
||||
import { STROKE_SIZES } from '../../shared/default-shape-constants'
|
||||
import { ArcInfo, ArrowInfo } from './arrow-types'
|
||||
import {
|
||||
BOUND_ARROW_OFFSET,
|
||||
getArrowTerminalsInArrowSpace,
|
||||
getBoundShapeInfoForTerminal,
|
||||
MIN_ARROW_LENGTH,
|
||||
WAY_TOO_BIG_ARROW_BEND_FACTOR,
|
||||
} from '../../../../constants'
|
||||
import type { Editor } from '../../../Editor'
|
||||
import { ArcInfo, ArrowInfo } from './arrow-types'
|
||||
import { getArrowTerminalsInArrowSpace, getBoundShapeInfoForTerminal } from './shared'
|
||||
} from './shared'
|
||||
import { getStraightArrowInfo } from './straight-arrow'
|
||||
|
||||
export function getCurvedArrowInfo(editor: Editor, shape: TLArrowShape, extraBend = 0): ArrowInfo {
|
||||
|
@ -115,9 +117,9 @@ export function getCurvedArrowInfo(editor: Editor, shape: TLArrowShape, extraBen
|
|||
if (arrowheadStart !== 'none') {
|
||||
const offset =
|
||||
BOUND_ARROW_OFFSET +
|
||||
editor.getStrokeWidth(shape.props.size) / 2 +
|
||||
STROKE_SIZES[shape.props.size] / 2 +
|
||||
('size' in startShapeInfo.shape.props
|
||||
? editor.getStrokeWidth(startShapeInfo.shape.props.size) / 2
|
||||
? STROKE_SIZES[startShapeInfo.shape.props.size] / 2
|
||||
: 0)
|
||||
|
||||
a.setTo(
|
||||
|
@ -192,10 +194,8 @@ export function getCurvedArrowInfo(editor: Editor, shape: TLArrowShape, extraBen
|
|||
if (arrowheadEnd !== 'none') {
|
||||
let offset =
|
||||
BOUND_ARROW_OFFSET +
|
||||
editor.getStrokeWidth(shape.props.size) / 2 +
|
||||
('size' in endShapeInfo.shape.props
|
||||
? editor.getStrokeWidth(endShapeInfo.shape.props.size) / 2
|
||||
: 0)
|
||||
STROKE_SIZES[shape.props.size] / 2 +
|
||||
('size' in endShapeInfo.shape.props ? STROKE_SIZES[endShapeInfo.shape.props.size] / 2 : 0)
|
||||
|
||||
if (Vec2d.Dist(a, b) < MIN_ARROW_LENGTH) {
|
||||
offset *= -2
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Matrix2d, Vec2d } from '@tldraw/primitives'
|
||||
import { TLArrowShape, TLArrowTerminal, TLShape } from '@tldraw/tlschema'
|
||||
import { TLArrowShape, TLArrowShapeTerminal, TLShape } from '@tldraw/tlschema'
|
||||
import { Editor } from '../../../Editor'
|
||||
import { ShapeUtil } from '../../ShapeUtil'
|
||||
|
||||
|
@ -13,13 +13,11 @@ export type BoundShapeInfo<T extends TLShape = TLShape> = {
|
|||
didIntersect: boolean
|
||||
isExact: boolean
|
||||
transform: Matrix2d
|
||||
// toLocalPoint: (v: VecLike) => Vec2d
|
||||
// toPagePoint: (v: VecLike) => Vec2d
|
||||
}
|
||||
|
||||
export function getBoundShapeInfoForTerminal(
|
||||
editor: Editor,
|
||||
terminal: TLArrowTerminal
|
||||
terminal: TLArrowShapeTerminal
|
||||
): BoundShapeInfo | undefined {
|
||||
if (terminal.type === 'point') {
|
||||
return
|
||||
|
@ -41,7 +39,7 @@ export function getBoundShapeInfoForTerminal(
|
|||
export function getArrowTerminalInArrowSpace(
|
||||
editor: Editor,
|
||||
arrowPageTransform: Matrix2d,
|
||||
terminal: TLArrowTerminal
|
||||
terminal: TLArrowShapeTerminal
|
||||
) {
|
||||
if (terminal.type === 'point') {
|
||||
return Vec2d.From(terminal)
|
||||
|
@ -72,3 +70,10 @@ export function getArrowTerminalsInArrowSpace(editor: Editor, shape: TLArrowShap
|
|||
|
||||
return { start, end }
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export const MIN_ARROW_LENGTH = 48
|
||||
/** @internal */
|
||||
export const BOUND_ARROW_OFFSET = 10
|
||||
/** @internal */
|
||||
export const WAY_TOO_BIG_ARROW_BEND_FACTOR = 10
|
||||
|
|
|
@ -8,13 +8,15 @@ import {
|
|||
VecLike,
|
||||
} from '@tldraw/primitives'
|
||||
import { TLArrowShape } from '@tldraw/tlschema'
|
||||
import { BOUND_ARROW_OFFSET, MIN_ARROW_LENGTH } from '../../../../constants'
|
||||
import { Editor } from '../../../Editor'
|
||||
import { STROKE_SIZES } from '../../shared/default-shape-constants'
|
||||
import { ArrowInfo } from './arrow-types'
|
||||
import {
|
||||
BOUND_ARROW_OFFSET,
|
||||
BoundShapeInfo,
|
||||
getArrowTerminalsInArrowSpace,
|
||||
getBoundShapeInfoForTerminal,
|
||||
MIN_ARROW_LENGTH,
|
||||
} from './shared'
|
||||
|
||||
export function getStraightArrowInfo(editor: Editor, shape: TLArrowShape): ArrowInfo {
|
||||
|
@ -87,9 +89,9 @@ export function getStraightArrowInfo(editor: Editor, shape: TLArrowShape): Arrow
|
|||
if (startShapeInfo && arrowheadStart !== 'none' && !startShapeInfo.isExact) {
|
||||
const offset =
|
||||
BOUND_ARROW_OFFSET +
|
||||
editor.getStrokeWidth(shape.props.size) / 2 +
|
||||
STROKE_SIZES[shape.props.size] / 2 +
|
||||
('size' in startShapeInfo.shape.props
|
||||
? editor.getStrokeWidth(startShapeInfo.shape.props.size) / 2
|
||||
? STROKE_SIZES[startShapeInfo.shape.props.size] / 2
|
||||
: 0)
|
||||
|
||||
minDist -= offset
|
||||
|
@ -101,10 +103,8 @@ export function getStraightArrowInfo(editor: Editor, shape: TLArrowShape): Arrow
|
|||
if (endShapeInfo && arrowheadEnd !== 'none' && !endShapeInfo.isExact) {
|
||||
const offset =
|
||||
BOUND_ARROW_OFFSET +
|
||||
editor.getStrokeWidth(shape.props.size) / 2 +
|
||||
('size' in endShapeInfo.shape.props
|
||||
? editor.getStrokeWidth(endShapeInfo.shape.props.size) / 2
|
||||
: 0)
|
||||
STROKE_SIZES[shape.props.size] / 2 +
|
||||
('size' in endShapeInfo.shape.props ? STROKE_SIZES[endShapeInfo.shape.props.size] / 2 : 0)
|
||||
|
||||
minDist -= offset
|
||||
b.nudge(a, offset * (didFlip ? -1 : 1))
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { VecLike } from '@tldraw/primitives'
|
||||
import { TLArrowShape, TLShapeId } from '@tldraw/tlschema'
|
||||
import * as React from 'react'
|
||||
import { ARROW_LABEL_FONT_SIZES, TEXT_PROPS } from '../../../../constants'
|
||||
import { stopEventPropagation } from '../../../../utils/dom'
|
||||
import { ARROW_LABEL_FONT_SIZES, TEXT_PROPS } from '../../shared/default-shape-constants'
|
||||
import { useEditableText } from '../../shared/useEditableText'
|
||||
import { TextHelpers } from '../../text/TextHelpers'
|
||||
|
||||
|
|
|
@ -1,108 +0,0 @@
|
|||
import { CubicSpline2d, Polyline2d } from '@tldraw/primitives'
|
||||
import { TLArrowheadType, TLDashType } from '@tldraw/tlschema'
|
||||
import { Segment, SegmentSvg } from './Segment'
|
||||
/**
|
||||
* A base interface for a shape's arrowheads.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface TLArrowHeadModel {
|
||||
id: string
|
||||
type: TLArrowheadType
|
||||
}
|
||||
|
||||
export function DashedArrowComponent({
|
||||
strokeWidth,
|
||||
dash,
|
||||
spline,
|
||||
start,
|
||||
end,
|
||||
}: {
|
||||
dash: TLDashType
|
||||
strokeWidth: number
|
||||
spline: CubicSpline2d | Polyline2d
|
||||
start: TLArrowHeadModel
|
||||
end: TLArrowHeadModel
|
||||
}) {
|
||||
const { segments } = spline
|
||||
|
||||
return (
|
||||
<g stroke="currentColor" strokeWidth={strokeWidth}>
|
||||
{segments.map((segment, i) => (
|
||||
<Segment
|
||||
key={i}
|
||||
strokeWidth={strokeWidth}
|
||||
segment={segment}
|
||||
dash={dash}
|
||||
location={
|
||||
segments.length === 1
|
||||
? start && end
|
||||
? 'middle'
|
||||
: start
|
||||
? 'middle'
|
||||
: 'start'
|
||||
: i === 0
|
||||
? start
|
||||
? 'end'
|
||||
: 'start'
|
||||
: i === segments.length - 1
|
||||
? end
|
||||
? 'middle'
|
||||
: 'end'
|
||||
: 'middle'
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</g>
|
||||
)
|
||||
}
|
||||
|
||||
export function DashedArrowComponentSvg({
|
||||
strokeWidth,
|
||||
dash,
|
||||
spline,
|
||||
start,
|
||||
end,
|
||||
color,
|
||||
}: {
|
||||
dash: TLDashType
|
||||
strokeWidth: number
|
||||
spline: CubicSpline2d | Polyline2d
|
||||
start: TLArrowHeadModel
|
||||
end: TLArrowHeadModel
|
||||
color: string
|
||||
}) {
|
||||
const { segments } = spline
|
||||
|
||||
const g = document.createElementNS('http://www.w3.org/2000/svg', 'g')
|
||||
g.setAttribute('stroke', color)
|
||||
g.setAttribute('stroke-width', strokeWidth.toString())
|
||||
|
||||
segments.forEach((segment, i) => {
|
||||
const segmentG = SegmentSvg({
|
||||
strokeWidth,
|
||||
segment,
|
||||
dash,
|
||||
location:
|
||||
segments.length === 1
|
||||
? start && end
|
||||
? 'middle'
|
||||
: start
|
||||
? 'middle'
|
||||
: 'start'
|
||||
: i === 0
|
||||
? start
|
||||
? 'end'
|
||||
: 'start'
|
||||
: i === segments.length - 1
|
||||
? end
|
||||
? 'middle'
|
||||
: 'end'
|
||||
: 'middle',
|
||||
})
|
||||
|
||||
g.appendChild(segmentG)
|
||||
})
|
||||
|
||||
return g
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
import { CubicSpline2d, getStroke, Polyline2d } from '@tldraw/primitives'
|
||||
import { getSvgPathFromStroke } from '../../../../utils/svg'
|
||||
import { getDrawStrokeInfo } from '../../shared/getDrawStrokeInfo'
|
||||
|
||||
export function DrawArrowComponent({
|
||||
strokeWidth,
|
||||
spline,
|
||||
}: {
|
||||
strokeWidth: number
|
||||
spline: CubicSpline2d | Polyline2d
|
||||
}) {
|
||||
const { segments } = spline
|
||||
const allPoints = segments.flatMap((segment) => segment.lut)
|
||||
const pf = getStroke(allPoints, getDrawStrokeInfo(strokeWidth))
|
||||
const pfPath = getSvgPathFromStroke(pf)
|
||||
|
||||
return <path strokeWidth="0" stroke="none" fill="currentColor" d={pfPath} />
|
||||
}
|
||||
|
||||
export function DrawArrowComponentSvg({
|
||||
strokeWidth,
|
||||
spline,
|
||||
color,
|
||||
}: {
|
||||
strokeWidth: number
|
||||
spline: CubicSpline2d | Polyline2d
|
||||
color: string
|
||||
}) {
|
||||
const { segments } = spline
|
||||
const allPoints = segments.flatMap((segment) => segment.lut)
|
||||
const pf = getStroke(allPoints, getDrawStrokeInfo(strokeWidth))
|
||||
const pfPath = getSvgPathFromStroke(pf)
|
||||
|
||||
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path')
|
||||
path.setAttribute('stroke-width', '0')
|
||||
path.setAttribute('stroke', 'none')
|
||||
path.setAttribute('fill', color)
|
||||
path.setAttribute('d', pfPath)
|
||||
|
||||
return path
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
import { CubicSegment2d, LineSegment2d } from '@tldraw/primitives'
|
||||
import { TLDashType } from '@tldraw/tlschema'
|
||||
import { getPerfectDashProps } from '../../shared/getPerfectDashProps'
|
||||
|
||||
export interface SegmentProps {
|
||||
strokeWidth: number
|
||||
dash: TLDashType
|
||||
segment: LineSegment2d | CubicSegment2d
|
||||
location: 'start' | 'middle' | 'end' | 'solo'
|
||||
}
|
||||
|
||||
export function Segment({ segment, dash, strokeWidth, location }: SegmentProps) {
|
||||
const { strokeDasharray, strokeDashoffset } = getPerfectDashProps(segment.length, strokeWidth, {
|
||||
style: dash,
|
||||
start: location === 'end' || location === 'middle' ? 'outset' : 'none',
|
||||
end: location === 'start' || location === 'middle' ? 'outset' : 'none',
|
||||
})
|
||||
|
||||
return (
|
||||
<path strokeDasharray={strokeDasharray} strokeDashoffset={strokeDashoffset} d={segment.path} />
|
||||
)
|
||||
}
|
||||
|
||||
export function SegmentSvg({ segment, dash, strokeWidth, location }: SegmentProps) {
|
||||
const { strokeDasharray, strokeDashoffset } = getPerfectDashProps(segment.length, strokeWidth, {
|
||||
style: dash,
|
||||
start: location === 'end' || location === 'middle' ? 'outset' : 'none',
|
||||
end: location === 'start' || location === 'middle' ? 'outset' : 'none',
|
||||
})
|
||||
|
||||
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path')
|
||||
path.setAttribute('stroke-dasharray', strokeDasharray.toString())
|
||||
path.setAttribute('stroke-dashoffset', strokeDashoffset.toString())
|
||||
path.setAttribute('d', segment.path)
|
||||
|
||||
return path
|
||||
}
|
|
@ -3,9 +3,6 @@ import { AssetRecordType, TLAssetId, TLBookmarkAsset, TLBookmarkShape } from '@t
|
|||
import { debounce, getHashForString } from '@tldraw/utils'
|
||||
import { HTMLContainer } from '../../../components/HTMLContainer'
|
||||
|
||||
const DEFAULT_BOOKMARK_WIDTH = 300
|
||||
const DEFAULT_BOOKMARK_HEIGHT = 320
|
||||
|
||||
import { isValidUrl } from '../../../utils/data'
|
||||
import {
|
||||
getRotatedBoxShadow,
|
||||
|
@ -29,8 +26,8 @@ export class BookmarkShapeUtil extends BaseBoxShapeUtil<TLBookmarkShape> {
|
|||
override defaultProps(): TLBookmarkShape['props'] {
|
||||
return {
|
||||
url: '',
|
||||
w: DEFAULT_BOOKMARK_WIDTH,
|
||||
h: DEFAULT_BOOKMARK_HEIGHT,
|
||||
w: 300,
|
||||
h: 320,
|
||||
assetId: null,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { TLStyleType } from '@tldraw/tlschema'
|
||||
|
||||
import { StateNode } from '../../tools/StateNode'
|
||||
import { DrawShapeUtil } from './DrawShapeUtil'
|
||||
import { Drawing } from './toolStates/Drawing'
|
||||
import { Idle } from './toolStates/Idle'
|
||||
|
||||
|
@ -9,8 +8,7 @@ export class DrawShapeTool extends StateNode {
|
|||
static initial = 'idle'
|
||||
static children = () => [Idle, Drawing]
|
||||
|
||||
styles = ['color', 'dash', 'fill', 'size'] as TLStyleType[]
|
||||
shapeType = 'draw'
|
||||
shapeType = DrawShapeUtil
|
||||
|
||||
onExit = () => {
|
||||
const drawingState = this.children!['drawing'] as Drawing
|
||||
|
|
|
@ -15,6 +15,7 @@ import { last, rng } from '@tldraw/utils'
|
|||
import { SVGContainer } from '../../../components/SVGContainer'
|
||||
import { getSvgPathFromStroke, getSvgPathFromStrokePoints } from '../../../utils/svg'
|
||||
import { ShapeUtil, TLOnResizeHandler } from '../ShapeUtil'
|
||||
import { STROKE_SIZES } from '../shared/default-shape-constants'
|
||||
import { getShapeFillSvg, ShapeFill } from '../shared/ShapeFill'
|
||||
import { TLExportColors } from '../shared/TLExportColors'
|
||||
import { useForceSolid } from '../shared/useForceSolid'
|
||||
|
@ -59,7 +60,7 @@ export class DrawShapeUtil extends ShapeUtil<TLDrawShape> {
|
|||
hitTestPoint(shape: TLDrawShape, point: VecLike): boolean {
|
||||
const outline = this.outline(shape)
|
||||
const zoomLevel = this.editor.zoomLevel
|
||||
const offsetDist = this.editor.getStrokeWidth(shape.props.size) / zoomLevel
|
||||
const offsetDist = STROKE_SIZES[shape.props.size] / zoomLevel
|
||||
|
||||
if (shape.props.segments.length === 1 && shape.props.segments[0].points.length < 4) {
|
||||
if (shape.props.segments[0].points.some((pt) => Vec2d.Dist(point, pt) < offsetDist * 1.5)) {
|
||||
|
@ -88,7 +89,7 @@ export class DrawShapeUtil extends ShapeUtil<TLDrawShape> {
|
|||
|
||||
if (shape.props.segments.length === 1 && shape.props.segments[0].points.length < 4) {
|
||||
const zoomLevel = this.editor.zoomLevel
|
||||
const offsetDist = this.editor.getStrokeWidth(shape.props.size) / zoomLevel
|
||||
const offsetDist = STROKE_SIZES[shape.props.size] / zoomLevel
|
||||
|
||||
if (
|
||||
shape.props.segments[0].points.some(
|
||||
|
@ -118,7 +119,7 @@ export class DrawShapeUtil extends ShapeUtil<TLDrawShape> {
|
|||
|
||||
render(shape: TLDrawShape) {
|
||||
const forceSolid = useForceSolid()
|
||||
const strokeWidth = this.editor.getStrokeWidth(shape.props.size)
|
||||
const strokeWidth = STROKE_SIZES[shape.props.size]
|
||||
const allPointsFromSegments = getPointsFromSegments(shape.props.segments)
|
||||
|
||||
const showAsComplete = shape.props.isComplete || last(shape.props.segments)?.type === 'straight'
|
||||
|
@ -155,7 +156,7 @@ export class DrawShapeUtil extends ShapeUtil<TLDrawShape> {
|
|||
<path
|
||||
d={getSvgPathFromStroke(strokeOutlinePoints, true)}
|
||||
strokeLinecap="round"
|
||||
fill="currentColor"
|
||||
fill={`var(--palette-${shape.props.color})`}
|
||||
/>
|
||||
</SVGContainer>
|
||||
)
|
||||
|
@ -172,7 +173,7 @@ export class DrawShapeUtil extends ShapeUtil<TLDrawShape> {
|
|||
d={solidStrokePath}
|
||||
strokeLinecap="round"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke={`var(--palette-${shape.props.color})`}
|
||||
strokeWidth={strokeWidth}
|
||||
strokeDasharray={getDrawShapeStrokeDashArray(shape, strokeWidth)}
|
||||
strokeDashoffset="0"
|
||||
|
@ -183,7 +184,7 @@ export class DrawShapeUtil extends ShapeUtil<TLDrawShape> {
|
|||
|
||||
indicator(shape: TLDrawShape) {
|
||||
const forceSolid = useForceSolid()
|
||||
const strokeWidth = this.editor.getStrokeWidth(shape.props.size)
|
||||
const strokeWidth = STROKE_SIZES[shape.props.size]
|
||||
const allPointsFromSegments = getPointsFromSegments(shape.props.segments)
|
||||
|
||||
let sw = strokeWidth
|
||||
|
@ -210,7 +211,7 @@ export class DrawShapeUtil extends ShapeUtil<TLDrawShape> {
|
|||
toSvg(shape: TLDrawShape, _font: string | undefined, colors: TLExportColors) {
|
||||
const { color } = shape.props
|
||||
|
||||
const strokeWidth = this.editor.getStrokeWidth(shape.props.size)
|
||||
const strokeWidth = STROKE_SIZES[shape.props.size]
|
||||
const allPointsFromSegments = getPointsFromSegments(shape.props.segments)
|
||||
|
||||
const showAsComplete = shape.props.isComplete || last(shape.props.segments)?.type === 'straight'
|
||||
|
@ -296,7 +297,7 @@ export class DrawShapeUtil extends ShapeUtil<TLDrawShape> {
|
|||
|
||||
expandSelectionOutlinePx(shape: TLDrawShape): number {
|
||||
const multiplier = shape.props.dash === 'draw' ? 1.6 : 1
|
||||
return (this.editor.getStrokeWidth(shape.props.size) * multiplier) / 2
|
||||
return (STROKE_SIZES[shape.props.size] * multiplier) / 2
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { EASINGS, PI, SIN, StrokeOptions, Vec2d } from '@tldraw/primitives'
|
||||
import { TLDashType, TLDrawShape, TLDrawShapeSegment } from '@tldraw/tlschema'
|
||||
import { TLDefaultDashStyle, TLDrawShape, TLDrawShapeSegment } from '@tldraw/tlschema'
|
||||
|
||||
const PEN_EASING = (t: number) => t * 0.65 + SIN((t * PI) / 2) * 0.35
|
||||
|
||||
|
@ -57,7 +57,7 @@ export function getHighlightFreehandSettings({
|
|||
}
|
||||
|
||||
export function getFreehandOptions(
|
||||
shapeProps: { dash: TLDashType; isPen: boolean; isComplete: boolean },
|
||||
shapeProps: { dash: TLDefaultDashStyle; isPen: boolean; isComplete: boolean },
|
||||
strokeWidth: number,
|
||||
forceComplete: boolean,
|
||||
forceSolid: boolean
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
import { Matrix2d, snapAngle, toFixed, Vec2d } from '@tldraw/primitives'
|
||||
import {
|
||||
createShapeId,
|
||||
TLDefaultSizeStyle,
|
||||
TLDrawShape,
|
||||
TLDrawShapeSegment,
|
||||
TLHighlightShape,
|
||||
TLShapePartial,
|
||||
TLSizeType,
|
||||
Vec2dModel,
|
||||
} from '@tldraw/tlschema'
|
||||
import { last, structuredClone } from '@tldraw/utils'
|
||||
import { DRAG_DISTANCE } from '../../../../constants'
|
||||
import { uniqueId } from '../../../../utils/data'
|
||||
import { TLEventHandlers, TLPointerEventInfo } from '../../../types/event-types'
|
||||
|
||||
import { StateNode } from '../../../tools/StateNode'
|
||||
import { TLEventHandlers, TLPointerEventInfo } from '../../../types/event-types'
|
||||
import { HighlightShapeUtil } from '../../highlight/HighlightShapeUtil'
|
||||
import { STROKE_SIZES } from '../../shared/default-shape-constants'
|
||||
import { DrawShapeUtil } from '../DrawShapeUtil'
|
||||
|
||||
type DrawableShape = TLDrawShape | TLHighlightShape
|
||||
|
||||
|
@ -24,7 +26,9 @@ export class Drawing extends StateNode {
|
|||
|
||||
initialShape?: DrawableShape
|
||||
|
||||
shapeType: DrawableShape['type'] = this.parent.id === 'highlight' ? 'highlight' : 'draw'
|
||||
shapeType = this.parent.id === 'highlight' ? HighlightShapeUtil : DrawShapeUtil
|
||||
|
||||
util = this.editor.getShapeUtil(this.shapeType)
|
||||
|
||||
isPen = false
|
||||
|
||||
|
@ -134,13 +138,13 @@ export class Drawing extends StateNode {
|
|||
}
|
||||
|
||||
canClose() {
|
||||
return this.shapeType !== 'highlight'
|
||||
return this.shapeType.type !== 'highlight'
|
||||
}
|
||||
|
||||
getIsClosed(segments: TLDrawShapeSegment[], size: TLSizeType) {
|
||||
getIsClosed(segments: TLDrawShapeSegment[], size: TLDefaultSizeStyle) {
|
||||
if (!this.canClose()) return false
|
||||
|
||||
const strokeWidth = this.editor.getStrokeWidth(size)
|
||||
const strokeWidth = STROKE_SIZES[size]
|
||||
const firstPoint = segments[0].points[0]
|
||||
const lastSegment = segments[segments.length - 1]
|
||||
const lastPoint = lastSegment.points[lastSegment.points.length - 1]
|
||||
|
@ -215,7 +219,7 @@ export class Drawing extends StateNode {
|
|||
|
||||
const shapePartial: TLShapePartial<DrawableShape> = {
|
||||
id: shape.id,
|
||||
type: this.shapeType,
|
||||
type: this.shapeType.type,
|
||||
props: {
|
||||
segments,
|
||||
},
|
||||
|
@ -242,7 +246,7 @@ export class Drawing extends StateNode {
|
|||
this.editor.createShapes<DrawableShape>([
|
||||
{
|
||||
id,
|
||||
type: this.shapeType,
|
||||
type: this.shapeType.type,
|
||||
x: originPagePoint.x,
|
||||
y: originPagePoint.y,
|
||||
props: {
|
||||
|
@ -345,7 +349,7 @@ export class Drawing extends StateNode {
|
|||
|
||||
const shapePartial: TLShapePartial<DrawableShape> = {
|
||||
id,
|
||||
type: this.shapeType,
|
||||
type: this.shapeType.type,
|
||||
props: {
|
||||
segments: [...segments, newSegment],
|
||||
},
|
||||
|
@ -405,7 +409,7 @@ export class Drawing extends StateNode {
|
|||
|
||||
const shapePartial: TLShapePartial<DrawableShape> = {
|
||||
id,
|
||||
type: this.shapeType,
|
||||
type: this.shapeType.type,
|
||||
props: {
|
||||
segments: finalSegments,
|
||||
},
|
||||
|
@ -547,7 +551,7 @@ export class Drawing extends StateNode {
|
|||
|
||||
const shapePartial: TLShapePartial<DrawableShape> = {
|
||||
id,
|
||||
type: this.shapeType,
|
||||
type: this.shapeType.type,
|
||||
props: {
|
||||
segments: newSegments,
|
||||
},
|
||||
|
@ -592,7 +596,7 @@ export class Drawing extends StateNode {
|
|||
|
||||
const shapePartial: TLShapePartial<DrawableShape> = {
|
||||
id,
|
||||
type: this.shapeType,
|
||||
type: this.shapeType.type,
|
||||
props: {
|
||||
segments: newSegments,
|
||||
},
|
||||
|
@ -609,7 +613,7 @@ export class Drawing extends StateNode {
|
|||
|
||||
// Set a maximum length for the lines array; after 200 points, complete the line.
|
||||
if (newPoints.length > 500) {
|
||||
this.editor.updateShapes([{ id, type: this.shapeType, props: { isComplete: true } }])
|
||||
this.editor.updateShapes([{ id, type: this.shapeType.type, props: { isComplete: true } }])
|
||||
|
||||
const { currentPagePoint } = this.editor.inputs
|
||||
|
||||
|
@ -618,7 +622,7 @@ export class Drawing extends StateNode {
|
|||
this.editor.createShapes<DrawableShape>([
|
||||
{
|
||||
id: newShapeId,
|
||||
type: this.shapeType,
|
||||
type: this.shapeType.type,
|
||||
x: toFixed(currentPagePoint.x),
|
||||
y: toFixed(currentPagePoint.y),
|
||||
props: {
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { BaseBoxShapeTool } from '../../tools/BaseBoxShapeTool/BaseBoxShapeTool'
|
||||
import { FrameShapeUtil } from './FrameShapeUtil'
|
||||
|
||||
export class FrameShapeTool extends BaseBoxShapeTool {
|
||||
static override id = 'frame'
|
||||
static initial = 'idle'
|
||||
|
||||
shapeType = 'frame'
|
||||
shapeType = FrameShapeUtil
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { TLStyleType } from '@tldraw/tlschema'
|
||||
import { StateNode } from '../../tools/StateNode'
|
||||
import { GeoShapeUtil } from './GeoShapeUtil'
|
||||
import { Idle } from './toolStates/Idle'
|
||||
import { Pointing } from './toolStates/Pointing'
|
||||
|
||||
|
@ -8,15 +8,5 @@ export class GeoShapeTool extends StateNode {
|
|||
static initial = 'idle'
|
||||
static children = () => [Idle, Pointing]
|
||||
|
||||
styles = [
|
||||
'color',
|
||||
'dash',
|
||||
'fill',
|
||||
'size',
|
||||
'geo',
|
||||
'font',
|
||||
'align',
|
||||
'verticalAlign',
|
||||
] as TLStyleType[]
|
||||
shapeType = 'geo'
|
||||
shapeType = GeoShapeUtil
|
||||
}
|
||||
|
|
|
@ -12,12 +12,17 @@ import {
|
|||
Vec2d,
|
||||
VecLike,
|
||||
} from '@tldraw/primitives'
|
||||
import { TLDashType, TLGeoShape } from '@tldraw/tlschema'
|
||||
import { TLDefaultDashStyle, TLGeoShape } from '@tldraw/tlschema'
|
||||
import { SVGContainer } from '../../../components/SVGContainer'
|
||||
import { FONT_FAMILIES, LABEL_FONT_SIZES, TEXT_PROPS } from '../../../constants'
|
||||
import { Editor } from '../../Editor'
|
||||
import { BaseBoxShapeUtil } from '../BaseBoxShapeUtil'
|
||||
import { TLOnEditEndHandler, TLOnResizeHandler } from '../ShapeUtil'
|
||||
import {
|
||||
FONT_FAMILIES,
|
||||
LABEL_FONT_SIZES,
|
||||
STROKE_SIZES,
|
||||
TEXT_PROPS,
|
||||
} from '../shared/default-shape-constants'
|
||||
import { getTextLabelSvgElement } from '../shared/getTextLabelSvgElement'
|
||||
import { HyperlinkButton } from '../shared/HyperlinkButton'
|
||||
import { TextLabel } from '../shared/TextLabel'
|
||||
|
@ -90,7 +95,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
|
|||
|
||||
if (shape.props.fill === 'none') {
|
||||
const zoomLevel = this.editor.zoomLevel
|
||||
const offsetDist = this.editor.getStrokeWidth(shape.props.size) / zoomLevel
|
||||
const offsetDist = STROKE_SIZES[shape.props.size] / zoomLevel
|
||||
// Check the outline
|
||||
for (let i = 0; i < outline.length; i++) {
|
||||
const C = outline[i]
|
||||
|
@ -334,7 +339,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
|
|||
const { id, type, props } = shape
|
||||
|
||||
const forceSolid = useForceSolid()
|
||||
const strokeWidth = this.editor.getStrokeWidth(props.size)
|
||||
const strokeWidth = STROKE_SIZES[props.size]
|
||||
|
||||
const { w, color, labelColor, fill, dash, growY, font, align, verticalAlign, size, text } =
|
||||
props
|
||||
|
@ -444,7 +449,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
|
|||
align={align}
|
||||
verticalAlign={verticalAlign}
|
||||
text={text}
|
||||
labelColor={this.editor.getCssColor(labelColor)}
|
||||
labelColor={labelColor}
|
||||
wrap
|
||||
/>
|
||||
{shape.props.url && (
|
||||
|
@ -459,7 +464,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
|
|||
const { w, h, growY, size } = props
|
||||
|
||||
const forceSolid = useForceSolid()
|
||||
const strokeWidth = this.editor.getStrokeWidth(size)
|
||||
const strokeWidth = STROKE_SIZES[size]
|
||||
|
||||
switch (props.geo) {
|
||||
case 'ellipse': {
|
||||
|
@ -499,7 +504,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
|
|||
|
||||
toSvg(shape: TLGeoShape, font: string, colors: TLExportColors) {
|
||||
const { id, props } = shape
|
||||
const strokeWidth = this.editor.getStrokeWidth(props.size)
|
||||
const strokeWidth = STROKE_SIZES[props.size]
|
||||
|
||||
let svgElm: SVGElement
|
||||
|
||||
|
@ -954,7 +959,7 @@ function getLines(props: TLGeoShape['props'], sw: number) {
|
|||
}
|
||||
}
|
||||
|
||||
function getXBoxLines(w: number, h: number, sw: number, dash: TLDashType) {
|
||||
function getXBoxLines(w: number, h: number, sw: number, dash: TLDefaultDashStyle) {
|
||||
const inset = dash === 'draw' ? 0.62 : 0
|
||||
|
||||
if (dash === 'dashed') {
|
||||
|
|
|
@ -44,7 +44,7 @@ export const DashStyleEllipse = React.memo(function DashStyleEllipse({
|
|||
width={toDomPrecision(w)}
|
||||
height={toDomPrecision(h)}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke={`var(--palette-${color})`}
|
||||
strokeDasharray={strokeDasharray}
|
||||
strokeDashoffset={strokeDashoffset}
|
||||
pointerEvents="all"
|
||||
|
|
|
@ -41,7 +41,7 @@ export const DashStyleOval = React.memo(function DashStyleOval({
|
|||
width={toDomPrecision(w)}
|
||||
height={toDomPrecision(h)}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke={`var(--palette-${color})`}
|
||||
strokeDasharray={strokeDasharray}
|
||||
strokeDashoffset={strokeDashoffset}
|
||||
pointerEvents="all"
|
||||
|
|
|
@ -31,7 +31,12 @@ export const DashStylePolygon = React.memo(function DashStylePolygon({
|
|||
d={`M${l[0].x},${l[0].y}L${l[1].x},${l[1].y}`}
|
||||
/>
|
||||
))}
|
||||
<g strokeWidth={strokeWidth} stroke="currentColor" fill="none" pointerEvents="all">
|
||||
<g
|
||||
strokeWidth={strokeWidth}
|
||||
stroke={`var(--palette-${color})`}
|
||||
fill="none"
|
||||
pointerEvents="all"
|
||||
>
|
||||
{Array.from(Array(outline.length)).map((_, i) => {
|
||||
const A = outline[i]
|
||||
const B = outline[(i + 1) % outline.length]
|
||||
|
@ -71,7 +76,7 @@ export const DashStylePolygon = React.memo(function DashStylePolygon({
|
|||
<path
|
||||
key={`line_fg_${i}`}
|
||||
d={`M${A.x},${A.y}L${B.x},${B.y}`}
|
||||
stroke="currentColor"
|
||||
stroke={`var(--palette-${color})`}
|
||||
strokeWidth={strokeWidth}
|
||||
fill="none"
|
||||
strokeDasharray={strokeDasharray}
|
||||
|
|
|
@ -32,7 +32,7 @@ export const DrawStyleEllipse = React.memo(function DrawStyleEllipse({
|
|||
return (
|
||||
<>
|
||||
<ShapeFill d={innerPath} color={color} fill={fill} />
|
||||
<path d={outerPath} fill="currentColor" strokeWidth={0} pointerEvents="all" />
|
||||
<path d={outerPath} fill={`var(--palette-${color})`} strokeWidth={0} pointerEvents="all" />
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
|
|
@ -32,7 +32,12 @@ export const DrawStylePolygon = React.memo(function DrawStylePolygon({
|
|||
return (
|
||||
<>
|
||||
<ShapeFill d={innerPathData} fill={fill} color={color} />
|
||||
<path d={strokePathData} stroke="currentColor" strokeWidth={strokeWidth} fill="none" />
|
||||
<path
|
||||
d={strokePathData}
|
||||
stroke={`var(--palette-${color})`}
|
||||
strokeWidth={strokeWidth}
|
||||
fill="none"
|
||||
/>
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Box2d, getStarBounds } from '@tldraw/primitives'
|
||||
import { TLGeoShape, createShapeId } from '@tldraw/tlschema'
|
||||
import { GeoShapeGeoStyle, TLGeoShape, createShapeId } from '@tldraw/tlschema'
|
||||
import { StateNode } from '../../../tools/StateNode'
|
||||
import { TLEventHandlers } from '../../../types/event-types'
|
||||
|
||||
|
@ -23,7 +23,7 @@ export class Pointing extends StateNode {
|
|||
props: {
|
||||
w: 1,
|
||||
h: 1,
|
||||
geo: this.editor.instanceState.propsForNextShape.geo,
|
||||
geo: this.editor.getStyleForNextShape(GeoShapeGeoStyle),
|
||||
},
|
||||
},
|
||||
])
|
||||
|
@ -70,7 +70,7 @@ export class Pointing extends StateNode {
|
|||
x: originPagePoint.x,
|
||||
y: originPagePoint.y,
|
||||
props: {
|
||||
geo: this.editor.instanceState.propsForNextShape.geo,
|
||||
geo: this.editor.getStyleForNextShape(GeoShapeGeoStyle),
|
||||
w: 1,
|
||||
h: 1,
|
||||
},
|
||||
|
@ -92,7 +92,7 @@ export class Pointing extends StateNode {
|
|||
x: shape.x - delta.x,
|
||||
y: shape.y - delta.y,
|
||||
props: {
|
||||
geo: this.editor.instanceState.propsForNextShape.geo,
|
||||
geo: this.editor.getStyleForNextShape(GeoShapeGeoStyle),
|
||||
w: bounds.width,
|
||||
h: bounds.height,
|
||||
},
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
import { TLStyleType } from '@tldraw/tlschema'
|
||||
|
||||
// shared custody
|
||||
import { StateNode } from '../../tools/StateNode'
|
||||
// shared custody
|
||||
import { Drawing } from '../draw/toolStates/Drawing'
|
||||
import { Idle } from '../draw/toolStates/Idle'
|
||||
import { HighlightShapeUtil } from './HighlightShapeUtil'
|
||||
|
||||
export class HighlightShapeTool extends StateNode {
|
||||
static override id = 'highlight'
|
||||
static initial = 'idle'
|
||||
static children = () => [Idle, Drawing]
|
||||
|
||||
styles = ['color', 'size'] as TLStyleType[]
|
||||
shapeType = 'highlight'
|
||||
shapeType = HighlightShapeUtil
|
||||
|
||||
onExit = () => {
|
||||
const drawingState = this.children!['drawing'] as Drawing
|
||||
|
|
|
@ -3,10 +3,10 @@ import { Box2d, getStrokePoints, linesIntersect, Vec2d, VecLike } from '@tldraw/
|
|||
import { TLDrawShapeSegment, TLHighlightShape } from '@tldraw/tlschema'
|
||||
import { last, rng } from '@tldraw/utils'
|
||||
import { SVGContainer } from '../../../components/SVGContainer'
|
||||
import { FONT_SIZES } from '../../../constants'
|
||||
import { getSvgPathFromStrokePoints } from '../../../utils/svg'
|
||||
import { getHighlightFreehandSettings, getPointsFromSegments } from '../draw/getPath'
|
||||
import { ShapeUtil, TLOnResizeHandler } from '../ShapeUtil'
|
||||
import { FONT_SIZES } from '../shared/default-shape-constants'
|
||||
import { TLExportColors } from '../shared/TLExportColors'
|
||||
import { useForceSolid } from '../shared/useForceSolid'
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { TLStyleType } from '@tldraw/tlschema'
|
||||
import { StateNode } from '../../tools/StateNode'
|
||||
import { LineShapeUtil } from './LineShapeUtil'
|
||||
import { Idle } from './toolStates/Idle'
|
||||
import { Pointing } from './toolStates/Pointing'
|
||||
|
||||
|
@ -8,7 +8,5 @@ export class LineShapeTool extends StateNode {
|
|||
static initial = 'idle'
|
||||
static children = () => [Idle, Pointing]
|
||||
|
||||
shapeType = 'line'
|
||||
|
||||
styles = ['color', 'dash', 'size', 'spline'] as TLStyleType[]
|
||||
shapeType = LineShapeUtil
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import { WeakMapCache } from '../../../utils/WeakMapCache'
|
|||
import { ShapeUtil, TLOnHandleChangeHandler, TLOnResizeHandler } from '../ShapeUtil'
|
||||
import { ShapeFill } from '../shared/ShapeFill'
|
||||
import { TLExportColors } from '../shared/TLExportColors'
|
||||
import { STROKE_SIZES } from '../shared/default-shape-constants'
|
||||
import { getPerfectDashProps } from '../shared/getPerfectDashProps'
|
||||
import { useForceSolid } from '../shared/useForceSolid'
|
||||
import { getLineDrawPath, getLineIndicatorPath, getLinePoints } from './components/getLinePath'
|
||||
|
@ -172,7 +173,7 @@ export class LineShapeUtil extends ShapeUtil<TLLineShape> {
|
|||
|
||||
hitTestPoint(shape: TLLineShape, point: Vec2d): boolean {
|
||||
const zoomLevel = this.editor.zoomLevel
|
||||
const offsetDist = this.editor.getStrokeWidth(shape.props.size) / zoomLevel
|
||||
const offsetDist = STROKE_SIZES[shape.props.size] / zoomLevel
|
||||
return pointNearToPolyline(point, this.outline(shape), offsetDist)
|
||||
}
|
||||
|
||||
|
@ -183,7 +184,7 @@ export class LineShapeUtil extends ShapeUtil<TLLineShape> {
|
|||
render(shape: TLLineShape) {
|
||||
const forceSolid = useForceSolid()
|
||||
const spline = getSplineForLineShape(shape)
|
||||
const strokeWidth = this.editor.getStrokeWidth(shape.props.size)
|
||||
const strokeWidth = STROKE_SIZES[shape.props.size]
|
||||
|
||||
const { dash, color } = shape.props
|
||||
|
||||
|
@ -196,7 +197,12 @@ export class LineShapeUtil extends ShapeUtil<TLLineShape> {
|
|||
return (
|
||||
<SVGContainer id={shape.id}>
|
||||
<ShapeFill d={pathData} fill={'none'} color={color} />
|
||||
<path d={pathData} stroke="currentColor" strokeWidth={strokeWidth} fill="none" />
|
||||
<path
|
||||
d={pathData}
|
||||
stroke={`var(--palette-${color})`}
|
||||
strokeWidth={strokeWidth}
|
||||
fill="none"
|
||||
/>
|
||||
</SVGContainer>
|
||||
)
|
||||
}
|
||||
|
@ -208,7 +214,7 @@ export class LineShapeUtil extends ShapeUtil<TLLineShape> {
|
|||
return (
|
||||
<SVGContainer id={shape.id}>
|
||||
<ShapeFill d={pathData} fill={'none'} color={color} />
|
||||
<g stroke="currentColor" strokeWidth={strokeWidth}>
|
||||
<g stroke={`var(--palette-${color})`} strokeWidth={strokeWidth}>
|
||||
{spline.segments.map((segment, i) => {
|
||||
const { strokeDasharray, strokeDashoffset } = getPerfectDashProps(
|
||||
segment.length,
|
||||
|
@ -242,7 +248,12 @@ export class LineShapeUtil extends ShapeUtil<TLLineShape> {
|
|||
return (
|
||||
<SVGContainer id={shape.id}>
|
||||
<ShapeFill d={innerPathData} fill={'none'} color={color} />
|
||||
<path d={outerPathData} stroke="currentColor" strokeWidth={strokeWidth} fill="none" />
|
||||
<path
|
||||
d={outerPathData}
|
||||
stroke={`var(--palette-${color})`}
|
||||
strokeWidth={strokeWidth}
|
||||
fill="none"
|
||||
/>
|
||||
</SVGContainer>
|
||||
)
|
||||
}
|
||||
|
@ -256,7 +267,12 @@ export class LineShapeUtil extends ShapeUtil<TLLineShape> {
|
|||
return (
|
||||
<SVGContainer id={shape.id}>
|
||||
<ShapeFill d={splinePath} fill={'none'} color={color} />
|
||||
<path strokeWidth={strokeWidth} stroke="currentColor" fill="none" d={splinePath} />
|
||||
<path
|
||||
strokeWidth={strokeWidth}
|
||||
stroke={`var(--palette-${color})`}
|
||||
fill="none"
|
||||
d={splinePath}
|
||||
/>
|
||||
</SVGContainer>
|
||||
)
|
||||
}
|
||||
|
@ -265,7 +281,7 @@ export class LineShapeUtil extends ShapeUtil<TLLineShape> {
|
|||
return (
|
||||
<SVGContainer id={shape.id}>
|
||||
<ShapeFill d={splinePath} fill={'none'} color={color} />
|
||||
<g stroke="currentColor" strokeWidth={strokeWidth}>
|
||||
<g stroke={`var(--palette-${color})`} strokeWidth={strokeWidth}>
|
||||
{spline.segments.map((segment, i) => {
|
||||
const { strokeDasharray, strokeDashoffset } = getPerfectDashProps(
|
||||
segment.length,
|
||||
|
@ -299,8 +315,8 @@ export class LineShapeUtil extends ShapeUtil<TLLineShape> {
|
|||
<path
|
||||
d={getLineDrawPath(shape, spline, strokeWidth)}
|
||||
strokeWidth={1}
|
||||
stroke="currentColor"
|
||||
fill="currentColor"
|
||||
stroke={`var(--palette-${color})`}
|
||||
fill={`var(--palette-${color})`}
|
||||
/>
|
||||
</SVGContainer>
|
||||
)
|
||||
|
@ -309,7 +325,7 @@ export class LineShapeUtil extends ShapeUtil<TLLineShape> {
|
|||
}
|
||||
|
||||
indicator(shape: TLLineShape) {
|
||||
const strokeWidth = this.editor.getStrokeWidth(shape.props.size)
|
||||
const strokeWidth = STROKE_SIZES[shape.props.size]
|
||||
const spline = getSplineForLineShape(shape)
|
||||
const { dash } = shape.props
|
||||
|
||||
|
@ -334,7 +350,7 @@ export class LineShapeUtil extends ShapeUtil<TLLineShape> {
|
|||
const { color: _color, size } = shape.props
|
||||
const color = colors.fill[_color]
|
||||
const spline = getSplineForLineShape(shape)
|
||||
return getLineSvg(shape, spline, color, this.editor.getStrokeWidth(size))
|
||||
return getLineSvg(shape, spline, color, STROKE_SIZES[size])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { CubicSpline2d, Polyline2d } from '@tldraw/primitives'
|
||||
import { TLDashType, TLLineShape } from '@tldraw/tlschema'
|
||||
import { TLDefaultDashStyle, TLLineShape } from '@tldraw/tlschema'
|
||||
import { getPerfectDashProps } from '../../shared/getPerfectDashProps'
|
||||
import { getLineDrawPath } from './getLinePath'
|
||||
|
||||
|
@ -31,7 +31,7 @@ export function getDashedLineShapeSvg({
|
|||
spline,
|
||||
color,
|
||||
}: {
|
||||
dash: TLDashType
|
||||
dash: TLDefaultDashStyle
|
||||
strokeWidth: number
|
||||
spline: CubicSpline2d | Polyline2d
|
||||
color: string
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { TLStyleType } from '@tldraw/tlschema'
|
||||
import { StateNode } from '../../tools/StateNode'
|
||||
import { NoteShapeUtil } from './NoteShapeUtil'
|
||||
import { Idle } from './toolStates/Idle'
|
||||
import { Pointing } from './toolStates/Pointing'
|
||||
|
||||
|
@ -8,6 +8,5 @@ export class NoteShapeTool extends StateNode {
|
|||
static initial = 'idle'
|
||||
static children = () => [Idle, Pointing]
|
||||
|
||||
styles = ['color', 'size', 'align', 'verticalAlign', 'font'] as TLStyleType[]
|
||||
shapeType = 'note'
|
||||
shapeType = NoteShapeUtil
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { Box2d, toDomPrecision, Vec2d } from '@tldraw/primitives'
|
||||
import { TLNoteShape } from '@tldraw/tlschema'
|
||||
import { FONT_FAMILIES, LABEL_FONT_SIZES, TEXT_PROPS } from '../../../constants'
|
||||
import { Editor } from '../../Editor'
|
||||
import { ShapeUtil, TLOnEditEndHandler } from '../ShapeUtil'
|
||||
import { FONT_FAMILIES, LABEL_FONT_SIZES, TEXT_PROPS } from '../shared/default-shape-constants'
|
||||
import { getTextLabelSvgElement } from '../shared/getTextLabelSvgElement'
|
||||
import { HyperlinkButton } from '../shared/HyperlinkButton'
|
||||
import { TextLabel } from '../shared/TextLabel'
|
||||
|
@ -83,7 +83,7 @@ export class NoteShapeUtil extends ShapeUtil<TLNoteShape> {
|
|||
align={align}
|
||||
verticalAlign={verticalAlign}
|
||||
text={text}
|
||||
labelColor="inherit"
|
||||
labelColor={adjustedColor}
|
||||
wrap
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { TLColorType, TLFillType } from '@tldraw/tlschema'
|
||||
import { TLDefaultColorStyle, TLDefaultFillStyle } from '@tldraw/tlschema'
|
||||
import * as React from 'react'
|
||||
import { useValue } from 'signia-react'
|
||||
import { HASH_PATERN_ZOOM_NAMES } from '../../../constants'
|
||||
|
@ -7,8 +7,8 @@ import { TLExportColors } from './TLExportColors'
|
|||
|
||||
export interface ShapeFillProps {
|
||||
d: string
|
||||
fill: TLFillType
|
||||
color: TLColorType
|
||||
fill: TLDefaultFillStyle
|
||||
color: TLDefaultColorStyle
|
||||
}
|
||||
|
||||
export const ShapeFill = React.memo(function ShapeFill({ d, color, fill }: ShapeFillProps) {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { TLColorType } from '@tldraw/tlschema'
|
||||
import { TLDefaultColorStyle } from '@tldraw/tlschema'
|
||||
|
||||
export type TLExportColors = {
|
||||
fill: Record<TLColorType, string>
|
||||
pattern: Record<TLColorType, string>
|
||||
semi: Record<TLColorType, string>
|
||||
highlight: Record<TLColorType, string>
|
||||
fill: Record<TLDefaultColorStyle, string>
|
||||
pattern: Record<TLDefaultColorStyle, string>
|
||||
semi: Record<TLDefaultColorStyle, string>
|
||||
highlight: Record<TLDefaultColorStyle, string>
|
||||
solid: string
|
||||
text: string
|
||||
background: string
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
import {
|
||||
TLAlignType,
|
||||
TLFillType,
|
||||
TLFontType,
|
||||
TLDefaultColorStyle,
|
||||
TLDefaultFillStyle,
|
||||
TLDefaultFontStyle,
|
||||
TLDefaultHorizontalAlignStyle,
|
||||
TLDefaultSizeStyle,
|
||||
TLDefaultVerticalAlignStyle,
|
||||
TLShape,
|
||||
TLSizeType,
|
||||
TLVerticalAlignType,
|
||||
} from '@tldraw/tlschema'
|
||||
import React from 'react'
|
||||
import { LABEL_FONT_SIZES, TEXT_PROPS } from '../../../constants'
|
||||
import { stopEventPropagation } from '../../../utils/dom'
|
||||
import { isLegacyAlign } from '../../../utils/legacy'
|
||||
import { TextHelpers } from '../text/TextHelpers'
|
||||
import { LABEL_FONT_SIZES, TEXT_PROPS } from './default-shape-constants'
|
||||
import { useEditableText } from './useEditableText'
|
||||
|
||||
export const TextLabel = React.memo(function TextLabel<
|
||||
|
@ -28,14 +29,14 @@ export const TextLabel = React.memo(function TextLabel<
|
|||
}: {
|
||||
id: T['id']
|
||||
type: T['type']
|
||||
size: TLSizeType
|
||||
font: TLFontType
|
||||
fill?: TLFillType
|
||||
align: TLAlignType
|
||||
verticalAlign: TLVerticalAlignType
|
||||
size: TLDefaultSizeStyle
|
||||
font: TLDefaultFontStyle
|
||||
fill?: TLDefaultFillStyle
|
||||
align: TLDefaultHorizontalAlignStyle
|
||||
verticalAlign: TLDefaultVerticalAlignStyle
|
||||
wrap?: boolean
|
||||
text: string
|
||||
labelColor: string
|
||||
labelColor: TLDefaultColorStyle
|
||||
}) {
|
||||
const {
|
||||
rInput,
|
||||
|
@ -77,7 +78,7 @@ export const TextLabel = React.memo(function TextLabel<
|
|||
lineHeight: LABEL_FONT_SIZES[size] * TEXT_PROPS.lineHeight + 'px',
|
||||
minHeight: isEmpty ? LABEL_FONT_SIZES[size] * TEXT_PROPS.lineHeight + 32 : 0,
|
||||
minWidth: isEmpty ? 33 : 0,
|
||||
color: labelColor,
|
||||
color: `var(--palette-${labelColor})`,
|
||||
}}
|
||||
>
|
||||
<div className="tl-text tl-text-content" dir="ltr">
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
import { Box2d } from '@tldraw/primitives'
|
||||
import { Box2dModel, TLAlignType, TLVerticalAlignType } from '@tldraw/tlschema'
|
||||
import {
|
||||
Box2dModel,
|
||||
TLDefaultHorizontalAlignStyle,
|
||||
TLDefaultVerticalAlignStyle,
|
||||
} from '@tldraw/tlschema'
|
||||
import { correctSpacesToNbsp } from '../../../utils/string'
|
||||
import { Editor } from '../../Editor'
|
||||
|
||||
|
@ -10,8 +14,8 @@ export function createTextSvgElementFromSpans(
|
|||
opts: {
|
||||
fontSize: number
|
||||
fontFamily: string
|
||||
textAlign: TLAlignType
|
||||
verticalTextAlign: TLVerticalAlignType
|
||||
textAlign: TLDefaultHorizontalAlignStyle
|
||||
verticalTextAlign: TLDefaultVerticalAlignStyle
|
||||
fontWeight: string
|
||||
fontStyle: string
|
||||
lineHeight: number
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
import { TLDefaultFontStyle, TLDefaultSizeStyle } from '@tldraw/tlschema'
|
||||
|
||||
/** @public */
|
||||
export const TEXT_PROPS = {
|
||||
lineHeight: 1.35,
|
||||
fontWeight: 'normal',
|
||||
fontVariant: 'normal',
|
||||
fontStyle: 'normal',
|
||||
padding: '0px',
|
||||
maxWidth: 'auto',
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export const STROKE_SIZES: Record<TLDefaultSizeStyle, number> = {
|
||||
s: 2,
|
||||
m: 3.5,
|
||||
l: 5,
|
||||
xl: 10,
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export const FONT_SIZES: Record<TLDefaultSizeStyle, number> = {
|
||||
s: 18,
|
||||
m: 24,
|
||||
l: 36,
|
||||
xl: 44,
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export const LABEL_FONT_SIZES: Record<TLDefaultSizeStyle, number> = {
|
||||
s: 18,
|
||||
m: 22,
|
||||
l: 26,
|
||||
xl: 32,
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export const ARROW_LABEL_FONT_SIZES: Record<TLDefaultSizeStyle, number> = {
|
||||
s: 18,
|
||||
m: 20,
|
||||
l: 24,
|
||||
xl: 28,
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export const FONT_FAMILIES: Record<TLDefaultFontStyle, string> = {
|
||||
draw: 'var(--tl-font-draw)',
|
||||
sans: 'var(--tl-font-sans)',
|
||||
serif: 'var(--tl-font-serif)',
|
||||
mono: 'var(--tl-font-mono)',
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export const MIN_ARROW_LENGTH = 48
|
||||
/** @internal */
|
||||
export const BOUND_ARROW_OFFSET = 10
|
||||
/** @internal */
|
||||
export const WAY_TOO_BIG_ARROW_BEND_FACTOR = 10
|
|
@ -1,13 +0,0 @@
|
|||
import { StrokeOptions } from '@tldraw/primitives'
|
||||
|
||||
export function getDrawStrokeInfo(strokeWidth: number) {
|
||||
const options: StrokeOptions = {
|
||||
size: strokeWidth * 1.3,
|
||||
thinning: 0.36,
|
||||
streamline: 0,
|
||||
smoothing: 0.25,
|
||||
simulatePressure: true,
|
||||
last: true,
|
||||
}
|
||||
return options
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
import { TLDashType } from '@tldraw/tlschema'
|
||||
import { TLDefaultDashStyle } from '@tldraw/tlschema'
|
||||
|
||||
export function getPerfectDashProps(
|
||||
totalLength: number,
|
||||
strokeWidth: number,
|
||||
opts = {} as Partial<{
|
||||
style: TLDashType
|
||||
style: TLDefaultDashStyle
|
||||
snap: number
|
||||
end: 'skip' | 'outset' | 'none'
|
||||
start: 'skip' | 'outset' | 'none'
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
import { TLDashType } from '@tldraw/tlschema'
|
||||
|
||||
export function getStrokeDashArray(dash: TLDashType, strokeWidth: number) {
|
||||
switch (dash) {
|
||||
case 'dashed':
|
||||
return `${strokeWidth * 2} ${strokeWidth * 2}`
|
||||
case 'dotted':
|
||||
return `0 ${strokeWidth * 2}`
|
||||
case 'solid':
|
||||
return ``
|
||||
}
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
import { Box2d } from '@tldraw/primitives'
|
||||
import { TLGeoShape, TLNoteShape } from '@tldraw/tlschema'
|
||||
import { LABEL_FONT_SIZES, TEXT_PROPS } from '../../../constants'
|
||||
import { getLegacyOffsetX } from '../../../utils/legacy'
|
||||
import { Editor } from '../../Editor'
|
||||
import { createTextSvgElementFromSpans } from './createTextSvgElementFromSpans'
|
||||
import { LABEL_FONT_SIZES, TEXT_PROPS } from './default-shape-constants'
|
||||
|
||||
export function getTextLabelSvgElement({
|
||||
bounds,
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
// Is the color black? Used in a few places to swap for white instead.
|
||||
export function isBlackColor(color: string) {
|
||||
return color === 'black' || color === ' #1d1d1d'
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import { TLStyleType } from '@tldraw/tlschema'
|
||||
import { StateNode } from '../../tools/StateNode'
|
||||
import { TextShapeUtil } from './TextShapeUtil'
|
||||
import { Idle } from './toolStates/Idle'
|
||||
import { Pointing } from './toolStates/Pointing'
|
||||
|
||||
|
@ -9,6 +9,5 @@ export class TextShapeTool extends StateNode {
|
|||
|
||||
static children = () => [Idle, Pointing]
|
||||
|
||||
styles = ['color', 'font', 'align', 'size'] as TLStyleType[]
|
||||
shapeType = 'text'
|
||||
shapeType = TextShapeUtil
|
||||
}
|
||||
|
|
|
@ -2,12 +2,12 @@
|
|||
import { Box2d, toDomPrecision, Vec2d } from '@tldraw/primitives'
|
||||
import { TLTextShape } from '@tldraw/tlschema'
|
||||
import { HTMLContainer } from '../../../components/HTMLContainer'
|
||||
import { FONT_FAMILIES, FONT_SIZES, TEXT_PROPS } from '../../../constants'
|
||||
import { stopEventPropagation } from '../../../utils/dom'
|
||||
import { WeakMapCache } from '../../../utils/WeakMapCache'
|
||||
import { Editor } from '../../Editor'
|
||||
import { ShapeUtil, TLOnEditEndHandler, TLOnResizeHandler, TLShapeUtilFlag } from '../ShapeUtil'
|
||||
import { createTextSvgElementFromSpans } from '../shared/createTextSvgElementFromSpans'
|
||||
import { FONT_FAMILIES, FONT_SIZES, TEXT_PROPS } from '../shared/default-shape-constants'
|
||||
import { resizeScaled } from '../shared/resizeScaled'
|
||||
import { TLExportColors } from '../shared/TLExportColors'
|
||||
import { useEditableText } from '../shared/useEditableText'
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { TLShapeUtilConstructor } from '../../shapes/ShapeUtil'
|
||||
import { StateNode } from '../StateNode'
|
||||
import { Idle } from './children/Idle'
|
||||
import { Pointing } from './children/Pointing'
|
||||
|
@ -8,5 +9,5 @@ export abstract class BaseBoxShapeTool extends StateNode {
|
|||
static initial = 'idle'
|
||||
static children = () => [Idle, Pointing]
|
||||
|
||||
abstract shapeType: string
|
||||
abstract shapeType: TLShapeUtilConstructor<any>
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ export class Pointing extends StateNode {
|
|||
if (this.editor.inputs.isDragging) {
|
||||
const { originPagePoint } = this.editor.inputs
|
||||
|
||||
const shapeType = (this.parent as BaseBoxShapeTool)!.shapeType as TLBaseBoxShape['type']
|
||||
const shapeType = (this.parent as BaseBoxShapeTool)!.shapeType.type as TLBaseBoxShape['type']
|
||||
|
||||
const id = createShapeId()
|
||||
|
||||
|
@ -78,7 +78,7 @@ export class Pointing extends StateNode {
|
|||
|
||||
this.editor.mark(this.markId)
|
||||
|
||||
const shapeType = (this.parent as BaseBoxShapeTool)!.shapeType as TLBaseBoxShape['type']
|
||||
const shapeType = (this.parent as BaseBoxShapeTool)!.shapeType.type as TLBaseBoxShape['type']
|
||||
|
||||
const id = createShapeId()
|
||||
|
||||
|
|
|
@ -11,8 +11,6 @@ export class HandTool extends StateNode {
|
|||
static initial = 'idle'
|
||||
static children = () => [Idle, Pointing, Dragging]
|
||||
|
||||
styles = []
|
||||
|
||||
onDoubleClick: TLClickEvent = (info) => {
|
||||
if (info.phase === 'settle') {
|
||||
const { currentScreenPoint } = this.editor.inputs
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
import { StateNode } from '../StateNode'
|
||||
|
||||
import { TLStyleType } from '@tldraw/tlschema'
|
||||
import { Brushing } from './children/Brushing'
|
||||
import { Crop } from './children/Crop/Crop'
|
||||
import { Cropping } from './children/Cropping'
|
||||
|
@ -42,8 +40,6 @@ export class SelectTool extends StateNode {
|
|||
DraggingHandle,
|
||||
]
|
||||
|
||||
styles = ['color', 'dash', 'fill', 'size'] as TLStyleType[]
|
||||
|
||||
onExit = () => {
|
||||
if (this.editor.pageState.editingId) {
|
||||
this.editor.setEditingId(null)
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import { Vec2d } from '@tldraw/primitives'
|
||||
import { TLBaseShape, TLImageCrop, TLShapePartial } from '@tldraw/tlschema'
|
||||
import { TLBaseShape, TLImageShapeCrop, TLShapePartial } from '@tldraw/tlschema'
|
||||
import { deepCopy } from '@tldraw/utils'
|
||||
import { Editor } from '../../../../../Editor'
|
||||
|
||||
export type ShapeWithCrop = TLBaseShape<string, { w: number; h: number; crop: TLImageCrop }>
|
||||
export type ShapeWithCrop = TLBaseShape<string, { w: number; h: number; crop: TLImageShapeCrop }>
|
||||
|
||||
export function getTranslateCroppedImageChange(
|
||||
editor: Editor,
|
||||
shape: TLBaseShape<string, { w: number; h: number; crop: TLImageCrop }>,
|
||||
shape: TLBaseShape<string, { w: number; h: number; crop: TLImageShapeCrop }>,
|
||||
delta: Vec2d
|
||||
) {
|
||||
if (!shape) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { SelectionHandle, Vec2d } from '@tldraw/primitives'
|
||||
import {
|
||||
TLBaseShape,
|
||||
TLImageCrop,
|
||||
TLImageShapeCrop,
|
||||
TLImageShapeProps,
|
||||
TLShape,
|
||||
TLShapePartial,
|
||||
|
@ -72,7 +72,7 @@ export class Cropping extends StateNode {
|
|||
})
|
||||
}
|
||||
|
||||
private getDefaultCrop = (): TLImageCrop => ({
|
||||
private getDefaultCrop = (): TLImageShapeCrop => ({
|
||||
topLeft: { x: 0, y: 0 },
|
||||
bottomRight: { x: 1, y: 1 },
|
||||
})
|
||||
|
@ -187,7 +187,7 @@ export class Cropping extends StateNode {
|
|||
newPoint.add(pointDelta.rot(shape.rotation))
|
||||
|
||||
const partial: TLShapePartial<
|
||||
TLBaseShape<string, { w: number; h: number; crop: TLImageCrop }>
|
||||
TLBaseShape<string, { w: number; h: number; crop: TLImageShapeCrop }>
|
||||
> = {
|
||||
id: shape.id,
|
||||
type: shape.type,
|
||||
|
|
|
@ -2,7 +2,7 @@ import { sortByIndex } from '@tldraw/indices'
|
|||
import { Matrix2d, snapAngle, Vec2d } from '@tldraw/primitives'
|
||||
import {
|
||||
TLArrowShape,
|
||||
TLArrowTerminal,
|
||||
TLArrowShapeTerminal,
|
||||
TLHandle,
|
||||
TLShapeId,
|
||||
TLShapePartial,
|
||||
|
@ -264,7 +264,7 @@ export class DraggingHandle extends StateNode {
|
|||
|
||||
// Arrows
|
||||
if (initialHandle.canBind) {
|
||||
const bindingAfter = (next.props as any)[initialHandle.id] as TLArrowTerminal | undefined
|
||||
const bindingAfter = (next.props as any)[initialHandle.id] as TLArrowShapeTerminal | undefined
|
||||
|
||||
if (bindingAfter?.type === 'binding') {
|
||||
if (hintingIds[0] !== bindingAfter.boundShapeId) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { TLStyleType } from '@tldraw/tlschema'
|
||||
import { atom, Atom, computed, Computed } from 'signia'
|
||||
import { TLBaseShape } from '@tldraw/tlschema'
|
||||
import { Atom, Computed, atom, computed } from 'signia'
|
||||
import type { Editor } from '../Editor'
|
||||
import { TLShapeUtilConstructor } from '../shapes/ShapeUtil'
|
||||
import {
|
||||
EVENT_NAME_MAP,
|
||||
TLEnterEventHandler,
|
||||
|
@ -18,7 +19,6 @@ export interface TLStateNodeConstructor {
|
|||
id: string
|
||||
initial?: string
|
||||
children?: () => TLStateNodeConstructor[]
|
||||
styles?: TLStyleType[]
|
||||
}
|
||||
|
||||
/** @public */
|
||||
|
@ -69,8 +69,7 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
|
|||
id: string
|
||||
current: Atom<StateNode | undefined>
|
||||
type: TLStateNodeType
|
||||
readonly styles: TLStyleType[] = []
|
||||
shapeType?: string
|
||||
shapeType?: TLShapeUtilConstructor<TLBaseShape<any, any>>
|
||||
initial?: string
|
||||
children?: Record<string, StateNode>
|
||||
parent: StateNode
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { PageRecordType, TLShape, createShapeId } from '@tldraw/tlschema'
|
||||
import { structuredClone } from '@tldraw/utils'
|
||||
import { BaseBoxShapeUtil } from '../editor/shapes/BaseBoxShapeUtil'
|
||||
import { GeoShapeUtil } from '../editor/shapes/geo/GeoShapeUtil'
|
||||
import { TestEditor } from './TestEditor'
|
||||
|
@ -150,26 +149,17 @@ it('Does not create an undo stack item when first clicking on an empty canvas',
|
|||
expect(editor.canUndo).toBe(false)
|
||||
})
|
||||
|
||||
describe('Editor.setProp', () => {
|
||||
it('Does not set non-style props on propsForNextShape', () => {
|
||||
const initialPropsForNextShape = structuredClone(editor.instanceState.propsForNextShape)
|
||||
editor.setProp('w', 100)
|
||||
editor.setProp('url', 'https://example.com')
|
||||
expect(editor.instanceState.propsForNextShape).toStrictEqual(initialPropsForNextShape)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Editor.opacity', () => {
|
||||
describe('Editor.sharedOpacity', () => {
|
||||
it('should return the current opacity', () => {
|
||||
expect(editor.opacity).toBe(1)
|
||||
expect(editor.sharedOpacity).toStrictEqual({ type: 'shared', value: 1 })
|
||||
editor.setOpacity(0.5)
|
||||
expect(editor.opacity).toBe(0.5)
|
||||
expect(editor.sharedOpacity).toStrictEqual({ type: 'shared', value: 0.5 })
|
||||
})
|
||||
|
||||
it('should return opacity for a single selected shape', () => {
|
||||
const { A } = editor.createShapesFromJsx(<TL.geo ref="A" opacity={0.3} x={0} y={0} />)
|
||||
editor.setSelectedIds([A])
|
||||
expect(editor.opacity).toBe(0.3)
|
||||
expect(editor.sharedOpacity).toStrictEqual({ type: 'shared', value: 0.3 })
|
||||
})
|
||||
|
||||
it('should return opacity for multiple selected shapes', () => {
|
||||
|
@ -178,16 +168,16 @@ describe('Editor.opacity', () => {
|
|||
<TL.geo ref="B" opacity={0.3} x={0} y={0} />,
|
||||
])
|
||||
editor.setSelectedIds([A, B])
|
||||
expect(editor.opacity).toBe(0.3)
|
||||
expect(editor.sharedOpacity).toStrictEqual({ type: 'shared', value: 0.3 })
|
||||
})
|
||||
|
||||
it('should return null when multiple selected shapes have different opacity', () => {
|
||||
it('should return mixed when multiple selected shapes have different opacity', () => {
|
||||
const { A, B } = editor.createShapesFromJsx([
|
||||
<TL.geo ref="A" opacity={0.3} x={0} y={0} />,
|
||||
<TL.geo ref="B" opacity={0.5} x={0} y={0} />,
|
||||
])
|
||||
editor.setSelectedIds([A, B])
|
||||
expect(editor.opacity).toBe(null)
|
||||
expect(editor.sharedOpacity).toStrictEqual({ type: 'mixed' })
|
||||
})
|
||||
|
||||
it('ignores the opacity of groups and returns the opacity of their children', () => {
|
||||
|
@ -197,7 +187,7 @@ describe('Editor.opacity', () => {
|
|||
</TL.group>,
|
||||
])
|
||||
editor.setSelectedIds([ids.group])
|
||||
expect(editor.opacity).toBe(0.3)
|
||||
expect(editor.sharedOpacity).toStrictEqual({ type: 'shared', value: 0.3 })
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@ import {
|
|||
} from '@tldraw/primitives'
|
||||
import {
|
||||
Box2dModel,
|
||||
InstanceRecordType,
|
||||
PageRecordType,
|
||||
TLShapeId,
|
||||
TLShapePartial,
|
||||
|
@ -52,7 +51,6 @@ declare global {
|
|||
}
|
||||
}
|
||||
}
|
||||
export const TEST_INSTANCE_ID = InstanceRecordType.createId('testInstance1')
|
||||
|
||||
export class TestEditor extends Editor {
|
||||
constructor(options: Partial<Omit<TLEditorOptions, 'store'>> = {}) {
|
||||
|
|
|
@ -307,10 +307,12 @@ describe('Custom shapes', () => {
|
|||
}
|
||||
}
|
||||
|
||||
const CardShape = defineShape('card', { util: CardUtil })
|
||||
|
||||
class CardTool extends BaseBoxShapeTool {
|
||||
static override id = 'card'
|
||||
static override initial = 'idle'
|
||||
override shapeType = 'card'
|
||||
override shapeType = CardUtil
|
||||
}
|
||||
|
||||
const tools = [CardTool]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { TLArrowShape, TLGeoShape, createShapeId } from '@tldraw/tlschema'
|
||||
import { DefaultColorStyle, TLArrowShape, TLGeoShape, createShapeId } from '@tldraw/tlschema'
|
||||
import { TestEditor } from '../TestEditor'
|
||||
|
||||
let editor: TestEditor
|
||||
|
@ -88,12 +88,12 @@ it('Parents shapes to the current page if the parent is not found', () => {
|
|||
})
|
||||
|
||||
it('Creates shapes with the current style', () => {
|
||||
expect(editor.instanceState.propsForNextShape!.color).toBe('black')
|
||||
expect(editor.instanceState.stylesForNextShape[DefaultColorStyle.id]).toBe(undefined)
|
||||
editor.createShapes([{ id: ids.box1, type: 'geo' }])
|
||||
expect(editor.getShapeById<TLGeoShape>(ids.box1)!.props.color).toEqual('black')
|
||||
|
||||
editor.setProp('color', 'red')
|
||||
expect(editor.instanceState.propsForNextShape!.color).toBe('red')
|
||||
editor.setStyle(DefaultColorStyle, 'red')
|
||||
expect(editor.instanceState.stylesForNextShape[DefaultColorStyle.id]).toBe('red')
|
||||
editor.createShapes([{ id: ids.box2, type: 'geo' }])
|
||||
expect(editor.getShapeById<TLGeoShape>(ids.box2)!.props.color).toEqual('red')
|
||||
})
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { createShapeId } from '@tldraw/tlschema'
|
||||
import { DefaultDashStyle, createShapeId } from '@tldraw/tlschema'
|
||||
import { SVG_PADDING } from '../../constants'
|
||||
import { TestEditor } from '../TestEditor'
|
||||
|
||||
|
@ -12,7 +12,7 @@ const ids = {
|
|||
|
||||
beforeEach(() => {
|
||||
editor = new TestEditor()
|
||||
editor.setProp('dash', 'solid')
|
||||
editor.setStyle(DefaultDashStyle, 'solid')
|
||||
editor.createShapes([
|
||||
{
|
||||
id: ids.boxA,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { PageRecordType, TLShape, createShapeId } from '@tldraw/tlschema'
|
||||
import { DefaultFillStyle, PageRecordType, TLShape, createShapeId } from '@tldraw/tlschema'
|
||||
import { TestEditor } from '../TestEditor'
|
||||
|
||||
let editor: TestEditor
|
||||
|
@ -154,7 +154,7 @@ describe('arrows', () => {
|
|||
.pointerDown(200, 200)
|
||||
.pointerMove(300, 300)
|
||||
.pointerUp(300, 300)
|
||||
.setProp('fill', 'solid')
|
||||
.setStyle(DefaultFillStyle, 'solid')
|
||||
firstBox = editor.onlySelectedShape!
|
||||
|
||||
// draw a second box
|
||||
|
@ -163,7 +163,7 @@ describe('arrows', () => {
|
|||
.pointerDown(400, 400)
|
||||
.pointerMove(500, 500)
|
||||
.pointerUp(500, 500)
|
||||
.setProp('fill', 'solid')
|
||||
.setStyle(DefaultFillStyle, 'solid')
|
||||
secondBox = editor.onlySelectedShape!
|
||||
|
||||
// draw an arrow from the first box to the second box
|
||||
|
|
|
@ -1,114 +0,0 @@
|
|||
import { createDefaultShapes, defaultShapesIds, TestEditor } from './TestEditor'
|
||||
|
||||
let editor: TestEditor
|
||||
|
||||
beforeEach(() => {
|
||||
editor = new TestEditor()
|
||||
editor.createShapes(createDefaultShapes())
|
||||
editor.reparentShapesById([defaultShapesIds.ellipse1], editor.currentPageId)
|
||||
})
|
||||
|
||||
describe('Editor.props', () => {
|
||||
it('should return props', () => {
|
||||
editor.selectNone()
|
||||
expect(editor.props).toEqual({
|
||||
color: 'black',
|
||||
dash: 'draw',
|
||||
fill: 'none',
|
||||
size: 'm',
|
||||
})
|
||||
})
|
||||
|
||||
it('should return props for a single shape', () => {
|
||||
editor.select(defaultShapesIds.box1)
|
||||
expect(editor.props).toEqual({
|
||||
align: 'middle',
|
||||
labelColor: 'black',
|
||||
color: 'black',
|
||||
dash: 'draw',
|
||||
fill: 'none',
|
||||
size: 'm',
|
||||
font: 'draw',
|
||||
geo: 'rectangle',
|
||||
verticalAlign: 'middle',
|
||||
})
|
||||
})
|
||||
|
||||
it('should return props for two matching shapes', () => {
|
||||
editor.select(defaultShapesIds.box1, defaultShapesIds.box2)
|
||||
expect(editor.props).toEqual({
|
||||
align: 'middle',
|
||||
color: 'black',
|
||||
labelColor: 'black',
|
||||
dash: 'draw',
|
||||
fill: 'none',
|
||||
size: 'm',
|
||||
font: 'draw',
|
||||
geo: 'rectangle',
|
||||
verticalAlign: 'middle',
|
||||
})
|
||||
})
|
||||
|
||||
it('should return mixed props for shapes that have mixed values', () => {
|
||||
editor.updateShapes([
|
||||
{
|
||||
id: defaultShapesIds.box1,
|
||||
type: 'geo',
|
||||
props: { h: 200, w: 200, color: 'red', dash: 'solid' },
|
||||
},
|
||||
])
|
||||
|
||||
editor.select(defaultShapesIds.box1, defaultShapesIds.box2)
|
||||
|
||||
expect(editor.props).toEqual({
|
||||
align: 'middle',
|
||||
labelColor: 'black',
|
||||
color: null, // mixed!
|
||||
dash: null, // mixed!
|
||||
fill: 'none',
|
||||
size: 'm',
|
||||
font: 'draw',
|
||||
geo: 'rectangle',
|
||||
verticalAlign: 'middle',
|
||||
})
|
||||
})
|
||||
|
||||
it('should return null for all mixed props', () => {
|
||||
editor.updateShapes([
|
||||
{
|
||||
id: defaultShapesIds.box1,
|
||||
type: 'geo',
|
||||
props: { h: 200, w: 200, color: 'red', dash: 'solid' },
|
||||
},
|
||||
{
|
||||
id: defaultShapesIds.box2,
|
||||
type: 'geo',
|
||||
props: { size: 'l', fill: 'pattern', font: 'mono' },
|
||||
},
|
||||
{
|
||||
id: defaultShapesIds.ellipse1,
|
||||
type: 'geo',
|
||||
props: {
|
||||
align: 'start',
|
||||
text: 'hello world this is a long sentence that should wrap',
|
||||
w: 100,
|
||||
url: 'https://aol.com',
|
||||
verticalAlign: 'start',
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
editor.selectAll()
|
||||
expect(editor.props).toEqual({
|
||||
align: null,
|
||||
labelColor: 'black',
|
||||
color: null,
|
||||
dash: null,
|
||||
fill: null,
|
||||
geo: null,
|
||||
size: null,
|
||||
font: null,
|
||||
verticalAlign: null,
|
||||
})
|
||||
})
|
||||
})
|
189
packages/editor/src/lib/test/styles.test.tsx
Normal file
189
packages/editor/src/lib/test/styles.test.tsx
Normal file
|
@ -0,0 +1,189 @@
|
|||
import { DefaultColorStyle, TLGeoShape, TLGroupShape } from '@tldraw/tlschema'
|
||||
import { ReadonlySharedStyleMap, SharedStyle } from '../utils/SharedStylesMap'
|
||||
import { TestEditor, createDefaultShapes, defaultShapesIds } from './TestEditor'
|
||||
import { TL } from './jsx'
|
||||
|
||||
let editor: TestEditor
|
||||
|
||||
function asPlainObject(styles: ReadonlySharedStyleMap | null) {
|
||||
if (!styles) return null
|
||||
const object: Record<string, SharedStyle<unknown>> = {}
|
||||
for (const [key, value] of styles) {
|
||||
object[key.id] = value
|
||||
}
|
||||
return object
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
editor = new TestEditor()
|
||||
editor.createShapes(createDefaultShapes())
|
||||
editor.reparentShapesById([defaultShapesIds.ellipse1], editor.currentPageId)
|
||||
})
|
||||
|
||||
describe('Editor.styles', () => {
|
||||
it('should return empty if nothing is selected', () => {
|
||||
editor.selectNone()
|
||||
expect(asPlainObject(editor.sharedStyles)).toStrictEqual({})
|
||||
})
|
||||
|
||||
it('should return styles for a single shape', () => {
|
||||
editor.select(defaultShapesIds.box1)
|
||||
expect(asPlainObject(editor.sharedStyles)).toStrictEqual({
|
||||
'tldraw:horizontalAlign': { type: 'shared', value: 'middle' },
|
||||
'tldraw:labelColor': { type: 'shared', value: 'black' },
|
||||
'tldraw:color': { type: 'shared', value: 'black' },
|
||||
'tldraw:dash': { type: 'shared', value: 'draw' },
|
||||
'tldraw:fill': { type: 'shared', value: 'none' },
|
||||
'tldraw:size': { type: 'shared', value: 'm' },
|
||||
'tldraw:font': { type: 'shared', value: 'draw' },
|
||||
'tldraw:geo': { type: 'shared', value: 'rectangle' },
|
||||
'tldraw:verticalAlign': { type: 'shared', value: 'middle' },
|
||||
})
|
||||
})
|
||||
|
||||
it('should return styles for two matching shapes', () => {
|
||||
editor.select(defaultShapesIds.box1, defaultShapesIds.box2)
|
||||
expect(asPlainObject(editor.sharedStyles)).toStrictEqual({
|
||||
'tldraw:horizontalAlign': { type: 'shared', value: 'middle' },
|
||||
'tldraw:labelColor': { type: 'shared', value: 'black' },
|
||||
'tldraw:color': { type: 'shared', value: 'black' },
|
||||
'tldraw:dash': { type: 'shared', value: 'draw' },
|
||||
'tldraw:fill': { type: 'shared', value: 'none' },
|
||||
'tldraw:size': { type: 'shared', value: 'm' },
|
||||
'tldraw:font': { type: 'shared', value: 'draw' },
|
||||
'tldraw:geo': { type: 'shared', value: 'rectangle' },
|
||||
'tldraw:verticalAlign': { type: 'shared', value: 'middle' },
|
||||
})
|
||||
})
|
||||
|
||||
it('should return mixed styles for shapes that have mixed values', () => {
|
||||
editor.updateShapes([
|
||||
{
|
||||
id: defaultShapesIds.box1,
|
||||
type: 'geo',
|
||||
props: { h: 200, w: 200, color: 'red', dash: 'solid' },
|
||||
},
|
||||
])
|
||||
|
||||
editor.select(defaultShapesIds.box1, defaultShapesIds.box2)
|
||||
|
||||
expect(asPlainObject(editor.sharedStyles)).toStrictEqual({
|
||||
'tldraw:horizontalAlign': { type: 'shared', value: 'middle' },
|
||||
'tldraw:labelColor': { type: 'shared', value: 'black' },
|
||||
'tldraw:color': { type: 'mixed' },
|
||||
'tldraw:dash': { type: 'mixed' },
|
||||
'tldraw:fill': { type: 'shared', value: 'none' },
|
||||
'tldraw:size': { type: 'shared', value: 'm' },
|
||||
'tldraw:font': { type: 'shared', value: 'draw' },
|
||||
'tldraw:geo': { type: 'shared', value: 'rectangle' },
|
||||
'tldraw:verticalAlign': { type: 'shared', value: 'middle' },
|
||||
})
|
||||
})
|
||||
|
||||
it('should return mixed for all mixed styles', () => {
|
||||
editor.updateShapes([
|
||||
{
|
||||
id: defaultShapesIds.box1,
|
||||
type: 'geo',
|
||||
props: { h: 200, w: 200, color: 'red', dash: 'solid' },
|
||||
},
|
||||
{
|
||||
id: defaultShapesIds.box2,
|
||||
type: 'geo',
|
||||
props: { size: 'l', fill: 'pattern', font: 'mono' },
|
||||
},
|
||||
{
|
||||
id: defaultShapesIds.ellipse1,
|
||||
type: 'geo',
|
||||
props: {
|
||||
align: 'start',
|
||||
text: 'hello world this is a long sentence that should wrap',
|
||||
w: 100,
|
||||
url: 'https://aol.com',
|
||||
verticalAlign: 'start',
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
editor.selectAll()
|
||||
|
||||
expect(asPlainObject(editor.sharedStyles)).toStrictEqual({
|
||||
'tldraw:color': { type: 'mixed' },
|
||||
'tldraw:dash': { type: 'mixed' },
|
||||
'tldraw:fill': { type: 'mixed' },
|
||||
'tldraw:font': { type: 'mixed' },
|
||||
'tldraw:geo': { type: 'mixed' },
|
||||
'tldraw:horizontalAlign': { type: 'mixed' },
|
||||
'tldraw:labelColor': { type: 'shared', value: 'black' },
|
||||
'tldraw:size': { type: 'mixed' },
|
||||
'tldraw:verticalAlign': { type: 'mixed' },
|
||||
})
|
||||
})
|
||||
|
||||
it('should return the same styles object if nothing relevant changes', () => {
|
||||
editor.select(defaultShapesIds.box1, defaultShapesIds.box2)
|
||||
const initialStyles = editor.sharedStyles
|
||||
|
||||
// update position of one of the shapes - not a style prop, so maps to same styles
|
||||
editor.updateShapes([
|
||||
{
|
||||
id: defaultShapesIds.box1,
|
||||
type: 'geo',
|
||||
x: 1000,
|
||||
y: 1000,
|
||||
},
|
||||
])
|
||||
|
||||
expect(editor.sharedStyles).toBe(initialStyles)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Editor.setStyle', () => {
|
||||
it('should set style for selected shapes', () => {
|
||||
const ids = editor.createShapesFromJsx([
|
||||
<TL.geo ref="A" x={0} y={0} color="blue" />,
|
||||
<TL.geo ref="B" x={0} y={0} color="green" />,
|
||||
])
|
||||
|
||||
editor.setSelectedIds([ids.A, ids.B])
|
||||
editor.setStyle(DefaultColorStyle, 'red')
|
||||
|
||||
expect(editor.getShapeById<TLGeoShape>(ids.A)!.props.color).toBe('red')
|
||||
expect(editor.getShapeById<TLGeoShape>(ids.B)!.props.color).toBe('red')
|
||||
})
|
||||
|
||||
it('should traverse into groups and set styles in their children', () => {
|
||||
const ids = editor.createShapesFromJsx([
|
||||
<TL.geo ref="boxA" x={0} y={0} />,
|
||||
<TL.group ref="groupA" x={0} y={0}>
|
||||
<TL.geo ref="boxB" x={0} y={0} />
|
||||
<TL.group ref="groupB" x={0} y={0}>
|
||||
<TL.geo ref="boxC" x={0} y={0} />
|
||||
<TL.geo ref="boxD" x={0} y={0} />
|
||||
</TL.group>
|
||||
</TL.group>,
|
||||
])
|
||||
|
||||
editor.setSelectedIds([ids.groupA])
|
||||
editor.setStyle(DefaultColorStyle, 'red')
|
||||
|
||||
// a wasn't selected...
|
||||
expect(editor.getShapeById<TLGeoShape>(ids.boxA)!.props.color).toBe('black')
|
||||
|
||||
// b, c, & d were within a selected group...
|
||||
expect(editor.getShapeById<TLGeoShape>(ids.boxB)!.props.color).toBe('red')
|
||||
expect(editor.getShapeById<TLGeoShape>(ids.boxC)!.props.color).toBe('red')
|
||||
expect(editor.getShapeById<TLGeoShape>(ids.boxD)!.props.color).toBe('red')
|
||||
|
||||
// groups get skipped
|
||||
expect(editor.getShapeById<TLGroupShape>(ids.groupA)!.props).not.toHaveProperty('color')
|
||||
expect(editor.getShapeById<TLGroupShape>(ids.groupB)!.props).not.toHaveProperty('color')
|
||||
})
|
||||
|
||||
it('stores styles on stylesForNextShape', () => {
|
||||
editor.setStyle(DefaultColorStyle, 'red')
|
||||
expect(editor.instanceState.stylesForNextShape[DefaultColorStyle.id]).toBe('red')
|
||||
editor.setStyle(DefaultColorStyle, 'green')
|
||||
expect(editor.instanceState.stylesForNextShape[DefaultColorStyle.id]).toBe('green')
|
||||
})
|
||||
})
|
|
@ -1,4 +1,4 @@
|
|||
import { TLArrowShape, createShapeId } from '@tldraw/tlschema'
|
||||
import { DefaultFillStyle, TLArrowShape, createShapeId } from '@tldraw/tlschema'
|
||||
import { FrameShapeUtil } from '../../editor/shapes/frame/FrameShapeUtil'
|
||||
import { TestEditor } from '../TestEditor'
|
||||
|
||||
|
@ -495,7 +495,11 @@ describe('frame shapes', () => {
|
|||
const frameId = editor.onlySelectedShape!.id
|
||||
|
||||
editor.setSelectedTool('geo')
|
||||
editor.pointerDown(125, 125).pointerMove(175, 175).pointerUp(175, 175).setProp('fill', 'solid')
|
||||
editor
|
||||
.pointerDown(125, 125)
|
||||
.pointerMove(175, 175)
|
||||
.pointerUp(175, 175)
|
||||
.setStyle(DefaultFillStyle, 'solid')
|
||||
const boxId = editor.onlySelectedShape!.id
|
||||
|
||||
editor.setSelectedTool('arrow')
|
||||
|
@ -602,7 +606,11 @@ describe('frame shapes', () => {
|
|||
|
||||
// make a shape inside the frame that extends out of the frame
|
||||
editor.setSelectedTool('geo')
|
||||
editor.pointerDown(150, 150).pointerMove(400, 400).pointerUp(400, 400).setProp('fill', 'solid')
|
||||
editor
|
||||
.pointerDown(150, 150)
|
||||
.pointerMove(400, 400)
|
||||
.pointerUp(400, 400)
|
||||
.setStyle(DefaultFillStyle, 'solid')
|
||||
const innerBoxId = editor.onlySelectedShape!.id
|
||||
|
||||
// Make an arrow that binds to the inner box's bottom right corner
|
||||
|
@ -651,15 +659,27 @@ test('arrows bound to a shape within a group within a frame are reparented if th
|
|||
const frameId = editor.onlySelectedShape!.id
|
||||
|
||||
editor.setSelectedTool('geo')
|
||||
editor.pointerDown(110, 110).pointerMove(120, 120).pointerUp(120, 120).setProp('fill', 'solid')
|
||||
editor
|
||||
.pointerDown(110, 110)
|
||||
.pointerMove(120, 120)
|
||||
.pointerUp(120, 120)
|
||||
.setStyle(DefaultFillStyle, 'solid')
|
||||
const boxAId = editor.onlySelectedShape!.id
|
||||
|
||||
editor.setSelectedTool('geo')
|
||||
editor.pointerDown(180, 110).pointerMove(190, 120).pointerUp(190, 120).setProp('fill', 'solid')
|
||||
editor
|
||||
.pointerDown(180, 110)
|
||||
.pointerMove(190, 120)
|
||||
.pointerUp(190, 120)
|
||||
.setStyle(DefaultFillStyle, 'solid')
|
||||
const boxBId = editor.onlySelectedShape!.id
|
||||
|
||||
editor.setSelectedTool('geo')
|
||||
editor.pointerDown(160, 160).pointerMove(170, 170).pointerUp(170, 170).setProp('fill', 'solid')
|
||||
editor
|
||||
.pointerDown(160, 160)
|
||||
.pointerMove(170, 170)
|
||||
.pointerUp(170, 170)
|
||||
.setStyle(DefaultFillStyle, 'solid')
|
||||
const boxCId = editor.onlySelectedShape!.id
|
||||
|
||||
editor.setSelectedTool('select')
|
||||
|
|
105
packages/editor/src/lib/utils/SharedStylesMap.ts
Normal file
105
packages/editor/src/lib/utils/SharedStylesMap.ts
Normal file
|
@ -0,0 +1,105 @@
|
|||
import { StyleProp } from '@tldraw/tlschema'
|
||||
import { exhaustiveSwitchError } from '@tldraw/utils'
|
||||
|
||||
/** @public */
|
||||
export type SharedStyle<T> =
|
||||
| { readonly type: 'mixed' }
|
||||
| { readonly type: 'shared'; readonly value: T }
|
||||
|
||||
function sharedStyleEquals<T>(a: SharedStyle<T>, b: SharedStyle<T> | undefined) {
|
||||
if (!b) return false
|
||||
switch (a.type) {
|
||||
case 'mixed':
|
||||
return b.type === 'mixed'
|
||||
case 'shared':
|
||||
return b.type === 'shared' && a.value === b.value
|
||||
default:
|
||||
throw exhaustiveSwitchError(a)
|
||||
}
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export class ReadonlySharedStyleMap {
|
||||
protected map: Map<StyleProp<unknown>, SharedStyle<unknown>>
|
||||
constructor(entries?: Iterable<[StyleProp<unknown>, SharedStyle<unknown>]>) {
|
||||
this.map = new Map(entries)
|
||||
}
|
||||
|
||||
get<T>(prop: StyleProp<T>): SharedStyle<T> | undefined {
|
||||
return this.map.get(prop) as SharedStyle<T> | undefined
|
||||
}
|
||||
|
||||
getAsKnownValue<T>(prop: StyleProp<T>): T | undefined {
|
||||
const value = this.get(prop)
|
||||
if (!value) return undefined
|
||||
if (value.type === 'mixed') return undefined
|
||||
return value.value
|
||||
}
|
||||
|
||||
get size() {
|
||||
return this.map.size
|
||||
}
|
||||
|
||||
equals(other: ReadonlySharedStyleMap) {
|
||||
if (this.size !== other.size) return false
|
||||
|
||||
const checkedKeys = new Set()
|
||||
for (const [styleProp, value] of this) {
|
||||
if (!sharedStyleEquals(value, other.get(styleProp))) return false
|
||||
checkedKeys.add(styleProp)
|
||||
}
|
||||
for (const [styleProp, value] of other) {
|
||||
if (checkedKeys.has(styleProp)) continue
|
||||
if (!sharedStyleEquals(value, this.get(styleProp))) return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
keys() {
|
||||
return this.map.keys()
|
||||
}
|
||||
|
||||
values() {
|
||||
return this.map.values()
|
||||
}
|
||||
|
||||
entries() {
|
||||
return this.map.entries()
|
||||
}
|
||||
|
||||
[Symbol.iterator]() {
|
||||
return this.map[Symbol.iterator]()
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export class SharedStyleMap extends ReadonlySharedStyleMap {
|
||||
set<T>(prop: StyleProp<T>, value: SharedStyle<T>) {
|
||||
this.map.set(prop, value)
|
||||
}
|
||||
|
||||
applyValue<T>(prop: StyleProp<T>, value: T) {
|
||||
const existingValue = this.get(prop)
|
||||
|
||||
// if we don't have a value yet, set it
|
||||
if (!existingValue) {
|
||||
this.set(prop, { type: 'shared', value })
|
||||
return
|
||||
}
|
||||
|
||||
switch (existingValue.type) {
|
||||
case 'mixed':
|
||||
// we're already mixed, adding new values won't help
|
||||
return
|
||||
case 'shared':
|
||||
if (existingValue.value !== value) {
|
||||
// if the value is different, we're now mixed:
|
||||
this.set(prop, { type: 'mixed' })
|
||||
}
|
||||
return
|
||||
default:
|
||||
exhaustiveSwitchError(existingValue, 'type')
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
import { Box2d } from '@tldraw/primitives'
|
||||
import { Box2dModel, TLAlignType } from '@tldraw/tlschema'
|
||||
import { Box2dModel, TLDefaultHorizontalAlignStyle } from '@tldraw/tlschema'
|
||||
|
||||
export function getLegacyOffsetX(
|
||||
align: TLAlignType | string,
|
||||
align: TLDefaultHorizontalAlignStyle | string,
|
||||
padding: number,
|
||||
spans: { text: string; box: Box2dModel }[],
|
||||
totalWidth: number
|
||||
|
@ -20,7 +20,7 @@ export function getLegacyOffsetX(
|
|||
}
|
||||
}
|
||||
|
||||
// sneaky TLAlignType for legacies
|
||||
export function isLegacyAlign(align: TLAlignType | string): boolean {
|
||||
// sneaky TLDefaultHorizontalAlignStyle for legacies
|
||||
export function isLegacyAlign(align: TLDefaultHorizontalAlignStyle | string): boolean {
|
||||
return align === 'start-legacy' || align === 'middle-legacy' || align === 'end-legacy'
|
||||
}
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
import {
|
||||
TLInstancePropsForNextShape,
|
||||
TLShapeProps,
|
||||
TLStyleType,
|
||||
TL_STYLE_TYPES,
|
||||
} from '@tldraw/tlschema'
|
||||
|
||||
/** @public */
|
||||
export function setPropsForNextShape(
|
||||
previousProps: TLInstancePropsForNextShape,
|
||||
newProps: Partial<TLShapeProps>
|
||||
): TLInstancePropsForNextShape {
|
||||
let nextProps: null | TLInstancePropsForNextShape = null
|
||||
for (const [prop, value] of Object.entries(newProps)) {
|
||||
if (!TL_STYLE_TYPES.has(prop as TLStyleType)) continue
|
||||
if (!nextProps) nextProps = { ...previousProps }
|
||||
// @ts-expect-error typescript can't track `value` correctly
|
||||
nextProps[prop] = value
|
||||
}
|
||||
return nextProps ?? previousProps
|
||||
}
|
|
@ -4,22 +4,22 @@ import {
|
|||
Editor,
|
||||
MAX_SHAPES_PER_PAGE,
|
||||
PageRecordType,
|
||||
TLAlignType,
|
||||
TLArrowShape,
|
||||
TLArrowTerminal,
|
||||
TLArrowheadType,
|
||||
TLArrowShapeArrowheadStyle,
|
||||
TLArrowShapeTerminal,
|
||||
TLAsset,
|
||||
TLAssetId,
|
||||
TLColorType,
|
||||
TLDashType,
|
||||
TLDefaultColorStyle,
|
||||
TLDefaultDashStyle,
|
||||
TLDefaultFontStyle,
|
||||
TLDefaultHorizontalAlignStyle,
|
||||
TLDefaultSizeStyle,
|
||||
TLDrawShape,
|
||||
TLFontType,
|
||||
TLGeoShape,
|
||||
TLImageShape,
|
||||
TLNoteShape,
|
||||
TLPageId,
|
||||
TLShapeId,
|
||||
TLSizeType,
|
||||
TLTextShape,
|
||||
TLVideoShape,
|
||||
Vec2dModel,
|
||||
|
@ -563,7 +563,7 @@ export function buildFromV1Document(editor: Editor, document: LegacyTldrawDocume
|
|||
|
||||
if (change) {
|
||||
if (change.props?.[handleId]) {
|
||||
const terminal = change.props?.[handleId] as TLArrowTerminal
|
||||
const terminal = change.props?.[handleId] as TLArrowShapeTerminal
|
||||
if (terminal.type === 'binding') {
|
||||
terminal.isExact = binding.distance === 0
|
||||
|
||||
|
@ -1076,7 +1076,7 @@ export interface LegacyTldrawDocument {
|
|||
|
||||
/* ------------------ Translations ------------------ */
|
||||
|
||||
const v1ColorsToV2Colors: Record<ColorStyle, TLColorType> = {
|
||||
const v1ColorsToV2Colors: Record<ColorStyle, TLDefaultColorStyle> = {
|
||||
[ColorStyle.White]: 'black',
|
||||
[ColorStyle.Black]: 'black',
|
||||
[ColorStyle.LightGray]: 'grey',
|
||||
|
@ -1091,60 +1091,60 @@ const v1ColorsToV2Colors: Record<ColorStyle, TLColorType> = {
|
|||
[ColorStyle.Violet]: 'light-violet',
|
||||
}
|
||||
|
||||
const v1FontsToV2Fonts: Record<FontStyle, TLFontType> = {
|
||||
const v1FontsToV2Fonts: Record<FontStyle, TLDefaultFontStyle> = {
|
||||
[FontStyle.Mono]: 'mono',
|
||||
[FontStyle.Sans]: 'sans',
|
||||
[FontStyle.Script]: 'draw',
|
||||
[FontStyle.Serif]: 'serif',
|
||||
}
|
||||
|
||||
const v1AlignsToV2Aligns: Record<AlignStyle, TLAlignType> = {
|
||||
const v1AlignsToV2Aligns: Record<AlignStyle, TLDefaultHorizontalAlignStyle> = {
|
||||
[AlignStyle.Start]: 'start',
|
||||
[AlignStyle.Middle]: 'middle',
|
||||
[AlignStyle.End]: 'end',
|
||||
[AlignStyle.Justify]: 'start',
|
||||
}
|
||||
|
||||
const v1TextSizesToV2TextSizes: Record<SizeStyle, TLSizeType> = {
|
||||
const v1TextSizesToV2TextSizes: Record<SizeStyle, TLDefaultSizeStyle> = {
|
||||
[SizeStyle.Small]: 's',
|
||||
[SizeStyle.Medium]: 'l',
|
||||
[SizeStyle.Large]: 'xl',
|
||||
}
|
||||
|
||||
const v1SizesToV2Sizes: Record<SizeStyle, TLSizeType> = {
|
||||
const v1SizesToV2Sizes: Record<SizeStyle, TLDefaultSizeStyle> = {
|
||||
[SizeStyle.Small]: 'm',
|
||||
[SizeStyle.Medium]: 'l',
|
||||
[SizeStyle.Large]: 'xl',
|
||||
}
|
||||
|
||||
const v1DashesToV2Dashes: Record<DashStyle, TLDashType> = {
|
||||
const v1DashesToV2Dashes: Record<DashStyle, TLDefaultDashStyle> = {
|
||||
[DashStyle.Solid]: 'solid',
|
||||
[DashStyle.Dashed]: 'dashed',
|
||||
[DashStyle.Dotted]: 'dotted',
|
||||
[DashStyle.Draw]: 'draw',
|
||||
}
|
||||
|
||||
function getV2Color(color: ColorStyle | undefined): TLColorType {
|
||||
function getV2Color(color: ColorStyle | undefined): TLDefaultColorStyle {
|
||||
return color ? v1ColorsToV2Colors[color] ?? 'black' : 'black'
|
||||
}
|
||||
|
||||
function getV2Font(font: FontStyle | undefined): TLFontType {
|
||||
function getV2Font(font: FontStyle | undefined): TLDefaultFontStyle {
|
||||
return font ? v1FontsToV2Fonts[font] ?? 'draw' : 'draw'
|
||||
}
|
||||
|
||||
function getV2Align(align: AlignStyle | undefined): TLAlignType {
|
||||
function getV2Align(align: AlignStyle | undefined): TLDefaultHorizontalAlignStyle {
|
||||
return align ? v1AlignsToV2Aligns[align] ?? 'middle' : 'middle'
|
||||
}
|
||||
|
||||
function getV2TextSize(size: SizeStyle | undefined): TLSizeType {
|
||||
function getV2TextSize(size: SizeStyle | undefined): TLDefaultSizeStyle {
|
||||
return size ? v1TextSizesToV2TextSizes[size] ?? 'm' : 'm'
|
||||
}
|
||||
|
||||
function getV2Size(size: SizeStyle | undefined): TLSizeType {
|
||||
function getV2Size(size: SizeStyle | undefined): TLDefaultSizeStyle {
|
||||
return size ? v1SizesToV2Sizes[size] ?? 'l' : 'l'
|
||||
}
|
||||
|
||||
function getV2Dash(dash: DashStyle | undefined): TLDashType {
|
||||
function getV2Dash(dash: DashStyle | undefined): TLDefaultDashStyle {
|
||||
return dash ? v1DashesToV2Dashes[dash] ?? 'draw' : 'draw'
|
||||
}
|
||||
|
||||
|
@ -1156,7 +1156,7 @@ function getV2Point(point: number[]): Vec2dModel {
|
|||
}
|
||||
}
|
||||
|
||||
function getV2Arrowhead(decoration: Decoration | undefined): TLArrowheadType {
|
||||
function getV2Arrowhead(decoration: Decoration | undefined): TLArrowShapeArrowheadStyle {
|
||||
return decoration === Decoration.Arrow ? 'arrow' : 'none'
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
```ts
|
||||
|
||||
import { BaseRecord } from '@tldraw/store';
|
||||
import { Expand } from '@tldraw/utils';
|
||||
import { Migrations } from '@tldraw/store';
|
||||
import { RecordId } from '@tldraw/store';
|
||||
import { RecordType } from '@tldraw/store';
|
||||
|
@ -15,14 +16,54 @@ import { StoreSnapshot } from '@tldraw/store';
|
|||
import { T } from '@tldraw/validate';
|
||||
import { UnknownRecord } from '@tldraw/store';
|
||||
|
||||
// @internal (undocumented)
|
||||
export const alignValidator: T.Validator<"end" | "middle" | "start">;
|
||||
// @public (undocumented)
|
||||
export const ArrowShapeArrowheadEndStyle: EnumStyleProp<"arrow" | "bar" | "diamond" | "dot" | "inverted" | "none" | "pipe" | "square" | "triangle">;
|
||||
|
||||
// @public (undocumented)
|
||||
export const ArrowShapeArrowheadStartStyle: EnumStyleProp<"arrow" | "bar" | "diamond" | "dot" | "inverted" | "none" | "pipe" | "square" | "triangle">;
|
||||
|
||||
// @internal (undocumented)
|
||||
export const arrowShapeMigrations: Migrations;
|
||||
|
||||
// @internal (undocumented)
|
||||
export const arrowShapeProps: ShapeProps<TLArrowShape>;
|
||||
// @public (undocumented)
|
||||
export const arrowShapeProps: {
|
||||
labelColor: EnumStyleProp<"black" | "blue" | "green" | "grey" | "light-blue" | "light-green" | "light-red" | "light-violet" | "orange" | "red" | "violet" | "yellow">;
|
||||
color: EnumStyleProp<"black" | "blue" | "green" | "grey" | "light-blue" | "light-green" | "light-red" | "light-violet" | "orange" | "red" | "violet" | "yellow">;
|
||||
fill: EnumStyleProp<"none" | "pattern" | "semi" | "solid">;
|
||||
dash: EnumStyleProp<"dashed" | "dotted" | "draw" | "solid">;
|
||||
size: EnumStyleProp<"l" | "m" | "s" | "xl">;
|
||||
arrowheadStart: EnumStyleProp<"arrow" | "bar" | "diamond" | "dot" | "inverted" | "none" | "pipe" | "square" | "triangle">;
|
||||
arrowheadEnd: EnumStyleProp<"arrow" | "bar" | "diamond" | "dot" | "inverted" | "none" | "pipe" | "square" | "triangle">;
|
||||
font: EnumStyleProp<"draw" | "mono" | "sans" | "serif">;
|
||||
start: T.UnionValidator<"type", {
|
||||
binding: T.ObjectValidator<{
|
||||
type: "binding";
|
||||
boundShapeId: TLShapeId;
|
||||
normalizedAnchor: Vec2dModel;
|
||||
isExact: boolean;
|
||||
}>;
|
||||
point: T.ObjectValidator<{
|
||||
type: "point";
|
||||
x: number;
|
||||
y: number;
|
||||
}>;
|
||||
}, never>;
|
||||
end: T.UnionValidator<"type", {
|
||||
binding: T.ObjectValidator<{
|
||||
type: "binding";
|
||||
boundShapeId: TLShapeId;
|
||||
normalizedAnchor: Vec2dModel;
|
||||
isExact: boolean;
|
||||
}>;
|
||||
point: T.ObjectValidator<{
|
||||
type: "point";
|
||||
x: number;
|
||||
y: number;
|
||||
}>;
|
||||
}, never>;
|
||||
bend: T.Validator<number>;
|
||||
text: T.Validator<string>;
|
||||
};
|
||||
|
||||
// @public
|
||||
export const assetIdValidator: T.Validator<TLAssetId>;
|
||||
|
@ -39,8 +80,13 @@ export const assetValidator: T.Validator<TLAsset>;
|
|||
// @internal (undocumented)
|
||||
export const bookmarkShapeMigrations: Migrations;
|
||||
|
||||
// @internal (undocumented)
|
||||
export const bookmarkShapeProps: ShapeProps<TLBookmarkShape>;
|
||||
// @public (undocumented)
|
||||
export const bookmarkShapeProps: {
|
||||
w: T.Validator<number>;
|
||||
h: T.Validator<number>;
|
||||
assetId: T.Validator<TLAssetId | null>;
|
||||
url: T.Validator<string>;
|
||||
};
|
||||
|
||||
// @public
|
||||
export interface Box2dModel {
|
||||
|
@ -54,6 +100,9 @@ export interface Box2dModel {
|
|||
y: number;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const box2dModelValidator: T.Validator<Box2dModel>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const CameraRecordType: RecordType<TLCamera, never>;
|
||||
|
||||
|
@ -63,9 +112,6 @@ export const canvasUiColorTypeValidator: T.Validator<"accent" | "black" | "laser
|
|||
// @internal (undocumented)
|
||||
export function CLIENT_FIXUP_SCRIPT(persistedStore: StoreSnapshot<TLRecord>): StoreSnapshot<TLRecord>;
|
||||
|
||||
// @internal (undocumented)
|
||||
export const colorValidator: T.Validator<"black" | "blue" | "green" | "grey" | "light-blue" | "light-green" | "light-red" | "light-violet" | "orange" | "red" | "violet" | "yellow">;
|
||||
|
||||
// @public
|
||||
export function createAssetValidator<Type extends string, Props extends object>(type: Type, props: T.Validator<Props>): T.ObjectValidator<{
|
||||
id: TLAssetId;
|
||||
|
@ -106,8 +152,26 @@ export function createTLSchema({ shapes }: {
|
|||
shapes: Record<string, SchemaShapeInfo>;
|
||||
}): TLSchema;
|
||||
|
||||
// @internal (undocumented)
|
||||
export const dashValidator: T.Validator<"dashed" | "dotted" | "draw" | "solid">;
|
||||
// @public (undocumented)
|
||||
export const DefaultColorStyle: EnumStyleProp<"black" | "blue" | "green" | "grey" | "light-blue" | "light-green" | "light-red" | "light-violet" | "orange" | "red" | "violet" | "yellow">;
|
||||
|
||||
// @public (undocumented)
|
||||
export const DefaultDashStyle: EnumStyleProp<"dashed" | "dotted" | "draw" | "solid">;
|
||||
|
||||
// @public (undocumented)
|
||||
export const DefaultFillStyle: EnumStyleProp<"none" | "pattern" | "semi" | "solid">;
|
||||
|
||||
// @public (undocumented)
|
||||
export const DefaultFontStyle: EnumStyleProp<"draw" | "mono" | "sans" | "serif">;
|
||||
|
||||
// @public (undocumented)
|
||||
export const DefaultHorizontalAlignStyle: EnumStyleProp<"end-legacy" | "end" | "middle-legacy" | "middle" | "start-legacy" | "start">;
|
||||
|
||||
// @public (undocumented)
|
||||
export const DefaultSizeStyle: EnumStyleProp<"l" | "m" | "s" | "xl">;
|
||||
|
||||
// @public (undocumented)
|
||||
export const DefaultVerticalAlignStyle: EnumStyleProp<"end" | "middle" | "start">;
|
||||
|
||||
// @public (undocumented)
|
||||
export const DocumentRecordType: RecordType<TLDocument, never>;
|
||||
|
@ -115,8 +179,20 @@ export const DocumentRecordType: RecordType<TLDocument, never>;
|
|||
// @internal (undocumented)
|
||||
export const drawShapeMigrations: Migrations;
|
||||
|
||||
// @internal (undocumented)
|
||||
export const drawShapeProps: ShapeProps<TLDrawShape>;
|
||||
// @public (undocumented)
|
||||
export const drawShapeProps: {
|
||||
color: EnumStyleProp<"black" | "blue" | "green" | "grey" | "light-blue" | "light-green" | "light-red" | "light-violet" | "orange" | "red" | "violet" | "yellow">;
|
||||
fill: EnumStyleProp<"none" | "pattern" | "semi" | "solid">;
|
||||
dash: EnumStyleProp<"dashed" | "dotted" | "draw" | "solid">;
|
||||
size: EnumStyleProp<"l" | "m" | "s" | "xl">;
|
||||
segments: T.ArrayOfValidator<{
|
||||
type: "free" | "straight";
|
||||
points: Vec2dModel[];
|
||||
}>;
|
||||
isComplete: T.Validator<boolean>;
|
||||
isClosed: T.Validator<boolean>;
|
||||
isPen: T.Validator<boolean>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const EMBED_DEFINITIONS: readonly [{
|
||||
|
@ -343,11 +419,20 @@ export const embedShapePermissionDefaults: {
|
|||
readonly 'allow-forms': true;
|
||||
};
|
||||
|
||||
// @internal (undocumented)
|
||||
export const embedShapeProps: ShapeProps<TLEmbedShape>;
|
||||
// @public (undocumented)
|
||||
export const embedShapeProps: {
|
||||
w: T.Validator<number>;
|
||||
h: T.Validator<number>;
|
||||
url: T.Validator<string>;
|
||||
};
|
||||
|
||||
// @internal (undocumented)
|
||||
export const fillValidator: T.Validator<"none" | "pattern" | "semi" | "solid">;
|
||||
// @public (undocumented)
|
||||
export class EnumStyleProp<T> extends StyleProp<T> {
|
||||
// @internal
|
||||
constructor(id: string, defaultValue: T, values: readonly T[]);
|
||||
// (undocumented)
|
||||
readonly values: readonly T[];
|
||||
}
|
||||
|
||||
// @internal (undocumented)
|
||||
export function fixupRecord(oldRecord: TLRecord): {
|
||||
|
@ -355,27 +440,46 @@ export function fixupRecord(oldRecord: TLRecord): {
|
|||
issues: string[];
|
||||
};
|
||||
|
||||
// @internal (undocumented)
|
||||
export const fontValidator: T.Validator<"draw" | "mono" | "sans" | "serif">;
|
||||
|
||||
// @internal (undocumented)
|
||||
export const frameShapeMigrations: Migrations;
|
||||
|
||||
// @internal (undocumented)
|
||||
export const frameShapeProps: ShapeProps<TLFrameShape>;
|
||||
// @public (undocumented)
|
||||
export const frameShapeProps: {
|
||||
w: T.Validator<number>;
|
||||
h: T.Validator<number>;
|
||||
name: T.Validator<string>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const GeoShapeGeoStyle: EnumStyleProp<"arrow-down" | "arrow-left" | "arrow-right" | "arrow-up" | "check-box" | "diamond" | "ellipse" | "hexagon" | "octagon" | "oval" | "pentagon" | "rectangle" | "rhombus-2" | "rhombus" | "star" | "trapezoid" | "triangle" | "x-box">;
|
||||
|
||||
// @internal (undocumented)
|
||||
export const geoShapeMigrations: Migrations;
|
||||
|
||||
// @internal (undocumented)
|
||||
export const geoShapeProps: ShapeProps<TLGeoShape>;
|
||||
|
||||
// @internal (undocumented)
|
||||
export const geoValidator: T.Validator<"arrow-down" | "arrow-left" | "arrow-right" | "arrow-up" | "check-box" | "diamond" | "ellipse" | "hexagon" | "octagon" | "oval" | "pentagon" | "rectangle" | "rhombus-2" | "rhombus" | "star" | "trapezoid" | "triangle" | "x-box">;
|
||||
// @public (undocumented)
|
||||
export const geoShapeProps: {
|
||||
geo: EnumStyleProp<"arrow-down" | "arrow-left" | "arrow-right" | "arrow-up" | "check-box" | "diamond" | "ellipse" | "hexagon" | "octagon" | "oval" | "pentagon" | "rectangle" | "rhombus-2" | "rhombus" | "star" | "trapezoid" | "triangle" | "x-box">;
|
||||
labelColor: EnumStyleProp<"black" | "blue" | "green" | "grey" | "light-blue" | "light-green" | "light-red" | "light-violet" | "orange" | "red" | "violet" | "yellow">;
|
||||
color: EnumStyleProp<"black" | "blue" | "green" | "grey" | "light-blue" | "light-green" | "light-red" | "light-violet" | "orange" | "red" | "violet" | "yellow">;
|
||||
fill: EnumStyleProp<"none" | "pattern" | "semi" | "solid">;
|
||||
dash: EnumStyleProp<"dashed" | "dotted" | "draw" | "solid">;
|
||||
size: EnumStyleProp<"l" | "m" | "s" | "xl">;
|
||||
font: EnumStyleProp<"draw" | "mono" | "sans" | "serif">;
|
||||
align: EnumStyleProp<"end-legacy" | "end" | "middle-legacy" | "middle" | "start-legacy" | "start">;
|
||||
verticalAlign: EnumStyleProp<"end" | "middle" | "start">;
|
||||
url: T.Validator<string>;
|
||||
w: T.Validator<number>;
|
||||
h: T.Validator<number>;
|
||||
growY: T.Validator<number>;
|
||||
text: T.Validator<string>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export function getDefaultTranslationLocale(): TLLanguage['locale'];
|
||||
|
||||
// @internal (undocumented)
|
||||
export function getShapePropKeysByStyle(props: Record<string, T.Validatable<any>>): Map<StyleProp<unknown>, string>;
|
||||
|
||||
// @internal (undocumented)
|
||||
export const groupShapeMigrations: Migrations;
|
||||
|
||||
|
@ -385,17 +489,17 @@ export const groupShapeProps: ShapeProps<TLGroupShape>;
|
|||
// @internal (undocumented)
|
||||
export const highlightShapeMigrations: Migrations;
|
||||
|
||||
// @internal (undocumented)
|
||||
export const highlightShapeProps: ShapeProps<TLHighlightShape>;
|
||||
|
||||
// @internal (undocumented)
|
||||
export const iconShapeMigrations: Migrations;
|
||||
|
||||
// @internal (undocumented)
|
||||
export const iconShapeProps: ShapeProps<TLIconShape>;
|
||||
|
||||
// @internal (undocumented)
|
||||
export const iconValidator: T.Validator<"activity" | "airplay" | "alert-circle" | "alert-octagon" | "alert-triangle" | "align-center" | "align-justify" | "align-left" | "align-right" | "anchor" | "aperture" | "archive" | "arrow-down-circle" | "arrow-down-left" | "arrow-down-right" | "arrow-down" | "arrow-left-circle" | "arrow-left" | "arrow-right-circle" | "arrow-right" | "arrow-up-circle" | "arrow-up-left" | "arrow-up-right" | "arrow-up" | "at-sign" | "award" | "bar-chart-2" | "bar-chart" | "battery-charging" | "battery" | "bell-off" | "bell" | "bluetooth" | "bold" | "book-open" | "book" | "bookmark" | "briefcase" | "calendar" | "camera-off" | "camera" | "cast" | "check-circle" | "check-square" | "check" | "chevron-down" | "chevron-left" | "chevron-right" | "chevron-up" | "chevrons-down" | "chevrons-left" | "chevrons-right" | "chevrons-up" | "chrome" | "circle" | "clipboard" | "clock" | "cloud-drizzle" | "cloud-lightning" | "cloud-off" | "cloud-rain" | "cloud-snow" | "cloud" | "codepen" | "codesandbox" | "coffee" | "columns" | "command" | "compass" | "copy" | "corner-down-left" | "corner-down-right" | "corner-left-down" | "corner-left-up" | "corner-right-down" | "corner-right-up" | "corner-up-left" | "corner-up-right" | "cpu" | "credit-card" | "crop" | "crosshair" | "database" | "delete" | "disc" | "divide-circle" | "divide-square" | "divide" | "dollar-sign" | "download-cloud" | "download" | "dribbble" | "droplet" | "edit-2" | "edit-3" | "edit" | "external-link" | "eye-off" | "eye" | "facebook" | "fast-forward" | "feather" | "figma" | "file-minus" | "file-plus" | "file-text" | "file" | "film" | "filter" | "flag" | "folder-minus" | "folder-plus" | "folder" | "framer" | "frown" | "geo" | "gift" | "git-branch" | "git-commit" | "git-merge" | "git-pull-request" | "github" | "gitlab" | "globe" | "grid" | "hard-drive" | "hash" | "headphones" | "heart" | "help-circle" | "hexagon" | "home" | "image" | "inbox" | "info" | "instagram" | "italic" | "key" | "layers" | "layout" | "life-buoy" | "link-2" | "link" | "linkedin" | "list" | "loader" | "lock" | "log-in" | "log-out" | "mail" | "map-pin" | "map" | "maximize-2" | "maximize" | "meh" | "menu" | "message-circle" | "message-square" | "mic-off" | "mic" | "minimize-2" | "minimize" | "minus-circle" | "minus-square" | "minus" | "monitor" | "moon" | "more-horizontal" | "more-vertical" | "mouse-pointer" | "move" | "music" | "navigation-2" | "navigation" | "octagon" | "package" | "paperclip" | "pause-circle" | "pause" | "pen-tool" | "percent" | "phone-call" | "phone-forwarded" | "phone-incoming" | "phone-missed" | "phone-off" | "phone-outgoing" | "phone" | "pie-chart" | "play-circle" | "play" | "plus-circle" | "plus-square" | "plus" | "pocket" | "power" | "printer" | "radio" | "refresh-ccw" | "refresh-cw" | "repeat" | "rewind" | "rotate-ccw" | "rotate-cw" | "rss" | "save" | "scissors" | "search" | "send" | "server" | "settings" | "share-2" | "share" | "shield-off" | "shield" | "shopping-bag" | "shopping-cart" | "shuffle" | "sidebar" | "skip-back" | "skip-forward" | "slack" | "slash" | "sliders" | "smartphone" | "smile" | "speaker" | "square" | "star" | "stop-circle" | "sun" | "sunrise" | "sunset" | "table" | "tablet" | "tag" | "target" | "terminal" | "thermometer" | "thumbs-down" | "thumbs-up" | "toggle-left" | "toggle-right" | "tool" | "trash-2" | "trash" | "trello" | "trending-down" | "trending-up" | "triangle" | "truck" | "tv" | "twitch" | "twitter" | "type" | "umbrella" | "underline" | "unlock" | "upload-cloud" | "upload" | "user-check" | "user-minus" | "user-plus" | "user-x" | "user" | "users" | "video-off" | "video" | "voicemail" | "volume-1" | "volume-2" | "volume-x" | "volume" | "watch" | "wifi-off" | "wifi" | "wind" | "x-circle" | "x-octagon" | "x-square" | "x" | "youtube" | "zap-off" | "zap" | "zoom-in" | "zoom-out">;
|
||||
// @public (undocumented)
|
||||
export const highlightShapeProps: {
|
||||
color: EnumStyleProp<"black" | "blue" | "green" | "grey" | "light-blue" | "light-green" | "light-red" | "light-violet" | "orange" | "red" | "violet" | "yellow">;
|
||||
size: EnumStyleProp<"l" | "m" | "s" | "xl">;
|
||||
segments: T.ArrayOfValidator<{
|
||||
type: "free" | "straight";
|
||||
points: Vec2dModel[];
|
||||
}>;
|
||||
isComplete: T.Validator<boolean>;
|
||||
isPen: T.Validator<boolean>;
|
||||
};
|
||||
|
||||
// @internal (undocumented)
|
||||
export function idValidator<Id extends RecordId<UnknownRecord>>(prefix: Id['__type__']['typeName']): T.Validator<Id>;
|
||||
|
@ -403,8 +507,18 @@ export function idValidator<Id extends RecordId<UnknownRecord>>(prefix: Id['__ty
|
|||
// @internal (undocumented)
|
||||
export const imageShapeMigrations: Migrations;
|
||||
|
||||
// @internal (undocumented)
|
||||
export const imageShapeProps: ShapeProps<TLImageShape>;
|
||||
// @public (undocumented)
|
||||
export const imageShapeProps: {
|
||||
w: T.Validator<number>;
|
||||
h: T.Validator<number>;
|
||||
playing: T.Validator<boolean>;
|
||||
url: T.Validator<string>;
|
||||
assetId: T.Validator<TLAssetId | null>;
|
||||
crop: T.Validator<{
|
||||
topLeft: Vec2dModel;
|
||||
bottomRight: Vec2dModel;
|
||||
} | null>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const InstancePageStateRecordType: RecordType<TLInstancePageState, "pageId">;
|
||||
|
@ -412,12 +526,6 @@ export const InstancePageStateRecordType: RecordType<TLInstancePageState, "pageI
|
|||
// @public (undocumented)
|
||||
export const InstancePresenceRecordType: RecordType<TLInstancePresence, "currentPageId" | "userId" | "userName">;
|
||||
|
||||
// @public (undocumented)
|
||||
export const InstanceRecordType: RecordType<TLInstance, "currentPageId">;
|
||||
|
||||
// @internal (undocumented)
|
||||
export const instanceTypeValidator: T.Validator<TLInstance>;
|
||||
|
||||
// @public (undocumented)
|
||||
export function isPageId(id: string): id is TLPageId;
|
||||
|
||||
|
@ -532,14 +640,32 @@ export const LANGUAGES: readonly [{
|
|||
// @internal (undocumented)
|
||||
export const lineShapeMigrations: Migrations;
|
||||
|
||||
// @internal (undocumented)
|
||||
export const lineShapeProps: ShapeProps<TLLineShape>;
|
||||
// @public (undocumented)
|
||||
export const lineShapeProps: {
|
||||
color: EnumStyleProp<"black" | "blue" | "green" | "grey" | "light-blue" | "light-green" | "light-red" | "light-violet" | "orange" | "red" | "violet" | "yellow">;
|
||||
dash: EnumStyleProp<"dashed" | "dotted" | "draw" | "solid">;
|
||||
size: EnumStyleProp<"l" | "m" | "s" | "xl">;
|
||||
spline: EnumStyleProp<"cubic" | "line">;
|
||||
handles: T.DictValidator<string, TLHandle>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const LineShapeSplineStyle: EnumStyleProp<"cubic" | "line">;
|
||||
|
||||
// @internal (undocumented)
|
||||
export const noteShapeMigrations: Migrations;
|
||||
|
||||
// @internal (undocumented)
|
||||
export const noteShapeProps: ShapeProps<TLNoteShape>;
|
||||
// @public (undocumented)
|
||||
export const noteShapeProps: {
|
||||
color: EnumStyleProp<"black" | "blue" | "green" | "grey" | "light-blue" | "light-green" | "light-red" | "light-violet" | "orange" | "red" | "violet" | "yellow">;
|
||||
size: EnumStyleProp<"l" | "m" | "s" | "xl">;
|
||||
font: EnumStyleProp<"draw" | "mono" | "sans" | "serif">;
|
||||
align: EnumStyleProp<"end-legacy" | "end" | "middle-legacy" | "middle" | "start-legacy" | "start">;
|
||||
verticalAlign: EnumStyleProp<"end" | "middle" | "start">;
|
||||
growY: T.Validator<number>;
|
||||
url: T.Validator<string>;
|
||||
text: T.Validator<string>;
|
||||
};
|
||||
|
||||
// @internal (undocumented)
|
||||
export const opacityValidator: T.Validator<number>;
|
||||
|
@ -575,117 +701,61 @@ export const shapeIdValidator: T.Validator<TLShapeId>;
|
|||
|
||||
// @public (undocumented)
|
||||
export type ShapeProps<Shape extends TLBaseShape<any, any>> = {
|
||||
[K in keyof Shape['props']]: T.Validator<Shape['props'][K]>;
|
||||
[K in keyof Shape['props']]: T.Validatable<Shape['props'][K]>;
|
||||
};
|
||||
|
||||
// @internal (undocumented)
|
||||
export const sizeValidator: T.Validator<"l" | "m" | "s" | "xl">;
|
||||
|
||||
// @internal (undocumented)
|
||||
export const splineValidator: T.Validator<"cubic" | "line">;
|
||||
// @public (undocumented)
|
||||
export class StyleProp<Type> implements T.Validatable<Type> {
|
||||
protected constructor(id: string, defaultValue: Type, type: T.Validatable<Type>);
|
||||
// (undocumented)
|
||||
readonly defaultValue: Type;
|
||||
// (undocumented)
|
||||
static define<Type>(uniqueId: string, { defaultValue, type }: {
|
||||
defaultValue: Type;
|
||||
type?: T.Validatable<Type>;
|
||||
}): StyleProp<Type>;
|
||||
// (undocumented)
|
||||
static defineEnum<const Values extends readonly unknown[]>(uniqueId: string, { defaultValue, values }: {
|
||||
defaultValue: Values[number];
|
||||
values: Values;
|
||||
}): EnumStyleProp<Values[number]>;
|
||||
// (undocumented)
|
||||
readonly id: string;
|
||||
// (undocumented)
|
||||
readonly type: T.Validatable<Type>;
|
||||
// (undocumented)
|
||||
validate(value: unknown): Type;
|
||||
}
|
||||
|
||||
// @internal (undocumented)
|
||||
export const textShapeMigrations: Migrations;
|
||||
|
||||
// @internal (undocumented)
|
||||
export const textShapeProps: ShapeProps<TLTextShape>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const TL_ALIGN_TYPES: Set<"end" | "middle" | "start">;
|
||||
|
||||
// @public (undocumented)
|
||||
export const TL_ARROWHEAD_TYPES: Set<"arrow" | "bar" | "diamond" | "dot" | "inverted" | "none" | "pipe" | "square" | "triangle">;
|
||||
export const textShapeProps: {
|
||||
color: EnumStyleProp<"black" | "blue" | "green" | "grey" | "light-blue" | "light-green" | "light-red" | "light-violet" | "orange" | "red" | "violet" | "yellow">;
|
||||
size: EnumStyleProp<"l" | "m" | "s" | "xl">;
|
||||
font: EnumStyleProp<"draw" | "mono" | "sans" | "serif">;
|
||||
align: EnumStyleProp<"end-legacy" | "end" | "middle-legacy" | "middle" | "start-legacy" | "start">;
|
||||
w: T.Validator<number>;
|
||||
text: T.Validator<string>;
|
||||
scale: T.Validator<number>;
|
||||
autoSize: T.Validator<boolean>;
|
||||
};
|
||||
|
||||
// @public
|
||||
export const TL_CANVAS_UI_COLOR_TYPES: Set<"accent" | "black" | "laser" | "muted-1" | "selection-fill" | "selection-stroke" | "white">;
|
||||
|
||||
// @public (undocumented)
|
||||
export const TL_COLOR_TYPES: Set<"black" | "blue" | "green" | "grey" | "light-blue" | "light-green" | "light-red" | "light-violet" | "orange" | "red" | "violet" | "yellow">;
|
||||
|
||||
// @public (undocumented)
|
||||
export const TL_DASH_TYPES: Set<"dashed" | "dotted" | "draw" | "solid">;
|
||||
|
||||
// @public (undocumented)
|
||||
export const TL_FILL_TYPES: Set<"none" | "pattern" | "semi" | "solid">;
|
||||
|
||||
// @public (undocumented)
|
||||
export const TL_FONT_TYPES: Set<"draw" | "mono" | "sans" | "serif">;
|
||||
|
||||
// @public (undocumented)
|
||||
export const TL_GEO_TYPES: Set<"arrow-down" | "arrow-left" | "arrow-right" | "arrow-up" | "check-box" | "diamond" | "ellipse" | "hexagon" | "octagon" | "oval" | "pentagon" | "rectangle" | "rhombus-2" | "rhombus" | "star" | "trapezoid" | "triangle" | "x-box">;
|
||||
|
||||
// @public (undocumented)
|
||||
export const TL_SIZE_TYPES: Set<"l" | "m" | "s" | "xl">;
|
||||
|
||||
// @public (undocumented)
|
||||
export const TL_SPLINE_TYPES: Set<"cubic" | "line">;
|
||||
|
||||
// @public (undocumented)
|
||||
export const TL_STYLE_TYPES: Set<"align" | "arrowheadEnd" | "arrowheadStart" | "color" | "dash" | "fill" | "font" | "geo" | "icon" | "labelColor" | "size" | "spline" | "verticalAlign">;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface TLAlignStyle extends TLBaseStyle {
|
||||
// (undocumented)
|
||||
id: TLAlignType;
|
||||
// (undocumented)
|
||||
type: 'align';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLAlignType = SetValue<typeof TL_ALIGN_TYPES>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface TLArrowheadEndStyle extends TLBaseStyle {
|
||||
// (undocumented)
|
||||
id: TLArrowheadType;
|
||||
// (undocumented)
|
||||
type: 'arrowheadEnd';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface TLArrowheadStartStyle extends TLBaseStyle {
|
||||
// (undocumented)
|
||||
id: TLArrowheadType;
|
||||
// (undocumented)
|
||||
type: 'arrowheadStart';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLArrowheadType = SetValue<typeof TL_ARROWHEAD_TYPES>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLArrowShape = TLBaseShape<'arrow', TLArrowShapeProps>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLArrowShapeProps = {
|
||||
labelColor: TLColorType;
|
||||
color: TLColorType;
|
||||
fill: TLFillType;
|
||||
dash: TLDashType;
|
||||
size: TLSizeType;
|
||||
arrowheadStart: TLArrowheadType;
|
||||
arrowheadEnd: TLArrowheadType;
|
||||
font: TLFontType;
|
||||
start: TLArrowTerminal;
|
||||
end: TLArrowTerminal;
|
||||
bend: number;
|
||||
text: string;
|
||||
};
|
||||
export type TLArrowShapeArrowheadStyle = T.TypeOf<typeof ArrowShapeArrowheadStartStyle>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLArrowTerminal = {
|
||||
type: 'binding';
|
||||
boundShapeId: TLShapeId;
|
||||
normalizedAnchor: Vec2dModel;
|
||||
isExact: boolean;
|
||||
} | {
|
||||
type: 'point';
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
export type TLArrowShapeProps = ShapePropsType<typeof arrowShapeProps>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLArrowTerminalType = SetValue<typeof TL_ARROW_TERMINAL_TYPE>;
|
||||
export type TLArrowShapeTerminal = T.TypeOf<typeof ArrowShapeTerminal>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLAsset = TLBookmarkAsset | TLImageAsset | TLVideoAsset;
|
||||
|
@ -764,17 +834,6 @@ export type TLCameraId = RecordId<TLCamera>;
|
|||
// @public
|
||||
export type TLCanvasUiColor = SetValue<typeof TL_CANVAS_UI_COLOR_TYPES>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface TLColorStyle extends TLBaseStyle {
|
||||
// (undocumented)
|
||||
id: TLColorType;
|
||||
// (undocumented)
|
||||
type: 'color';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLColorType = SetValue<typeof TL_COLOR_TYPES>;
|
||||
|
||||
// @public
|
||||
export interface TLCursor {
|
||||
// (undocumented)
|
||||
|
@ -789,18 +848,28 @@ export interface TLCursor {
|
|||
export type TLCursorType = SetValue<typeof TL_CURSOR_TYPES>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface TLDashStyle extends TLBaseStyle {
|
||||
// (undocumented)
|
||||
id: TLDashType;
|
||||
// (undocumented)
|
||||
type: 'dash';
|
||||
}
|
||||
export type TLDefaultColorStyle = T.TypeOf<typeof DefaultColorStyle>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLDashType = SetValue<typeof TL_DASH_TYPES>;
|
||||
export type TLDefaultDashStyle = T.TypeOf<typeof DefaultDashStyle>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLDefaultFillStyle = T.TypeOf<typeof DefaultFillStyle>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLDefaultFontStyle = T.TypeOf<typeof DefaultFontStyle>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLDefaultHorizontalAlignStyle = T.TypeOf<typeof DefaultHorizontalAlignStyle>;
|
||||
|
||||
// @public
|
||||
export type TLDefaultShape = TLArrowShape | TLBookmarkShape | TLDrawShape | TLEmbedShape | TLFrameShape | TLGeoShape | TLGroupShape | TLHighlightShape | TLIconShape | TLImageShape | TLLineShape | TLNoteShape | TLTextShape | TLVideoShape;
|
||||
export type TLDefaultShape = TLArrowShape | TLBookmarkShape | TLDrawShape | TLEmbedShape | TLFrameShape | TLGeoShape | TLGroupShape | TLHighlightShape | TLImageShape | TLLineShape | TLNoteShape | TLTextShape | TLVideoShape;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLDefaultSizeStyle = T.TypeOf<typeof DefaultSizeStyle>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLDefaultVerticalAlignStyle = T.TypeOf<typeof DefaultVerticalAlignStyle>;
|
||||
|
||||
// @public
|
||||
export interface TLDocument extends BaseRecord<'document', RecordId<TLDocument>> {
|
||||
|
@ -817,10 +886,7 @@ export const TLDOCUMENT_ID: RecordId<TLDocument>;
|
|||
export type TLDrawShape = TLBaseShape<'draw', TLDrawShapeProps>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLDrawShapeSegment = {
|
||||
type: SetValue<typeof TL_DRAW_SHAPE_SEGMENT_TYPE>;
|
||||
points: Vec2dModel[];
|
||||
};
|
||||
export type TLDrawShapeSegment = T.TypeOf<typeof DrawShapeSegment>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLEmbedShape = TLBaseShape<'embed', TLEmbedShapeProps>;
|
||||
|
@ -830,45 +896,12 @@ export type TLEmbedShapePermissions = {
|
|||
[K in keyof typeof embedShapePermissionDefaults]?: boolean;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export interface TLFillStyle extends TLBaseStyle {
|
||||
// (undocumented)
|
||||
id: TLFillType;
|
||||
// (undocumented)
|
||||
type: 'fill';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLFillType = SetValue<typeof TL_FILL_TYPES>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface TLFontStyle extends TLBaseStyle {
|
||||
// (undocumented)
|
||||
id: TLFontType;
|
||||
// (undocumented)
|
||||
type: 'font';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLFontType = SetValue<typeof TL_FONT_TYPES>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLFrameShape = TLBaseShape<'frame', TLFrameShapeProps>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLGeoShape = TLBaseShape<'geo', TLGeoShapeProps>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface TLGeoStyle extends TLBaseStyle {
|
||||
// (undocumented)
|
||||
id: TLGeoType;
|
||||
// (undocumented)
|
||||
type: 'geo';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLGeoType = SetValue<typeof TL_GEO_TYPES>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLGroupShape = TLBaseShape<'group', TLGroupShapeProps>;
|
||||
|
||||
|
@ -893,20 +926,6 @@ export type TLHandleType = SetValue<typeof TL_HANDLE_TYPES>;
|
|||
// @public (undocumented)
|
||||
export type TLHighlightShape = TLBaseShape<'highlight', TLHighlightShapeProps>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLIconShape = TLBaseShape<'icon', TLIconShapeProps>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface TLIconStyle extends TLBaseStyle {
|
||||
// (undocumented)
|
||||
id: TLIconType;
|
||||
// (undocumented)
|
||||
type: 'icon';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLIconType = SetValue<typeof TL_ICON_TYPES>;
|
||||
|
||||
// @public
|
||||
export type TLImageAsset = TLBaseAsset<'image', {
|
||||
w: number;
|
||||
|
@ -917,24 +936,14 @@ export type TLImageAsset = TLBaseAsset<'image', {
|
|||
src: null | string;
|
||||
}>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLImageCrop = {
|
||||
topLeft: Vec2dModel;
|
||||
bottomRight: Vec2dModel;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLImageShape = TLBaseShape<'image', TLImageShapeProps>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLImageShapeProps = {
|
||||
url: string;
|
||||
playing: boolean;
|
||||
w: number;
|
||||
h: number;
|
||||
assetId: null | TLAssetId;
|
||||
crop: null | TLImageCrop;
|
||||
};
|
||||
export type TLImageShapeCrop = T.TypeOf<typeof ImageShapeCrop>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLImageShapeProps = ShapePropsType<typeof imageShapeProps>;
|
||||
|
||||
// @public
|
||||
export interface TLInstance extends BaseRecord<'instance', TLInstanceId> {
|
||||
|
@ -967,12 +976,12 @@ export interface TLInstance extends BaseRecord<'instance', TLInstanceId> {
|
|||
// (undocumented)
|
||||
opacityForNextShape: TLOpacityType;
|
||||
// (undocumented)
|
||||
propsForNextShape: TLInstancePropsForNextShape;
|
||||
// (undocumented)
|
||||
screenBounds: Box2dModel;
|
||||
// (undocumented)
|
||||
scribble: null | TLScribble;
|
||||
// (undocumented)
|
||||
stylesForNextShape: Record<string, unknown>;
|
||||
// (undocumented)
|
||||
zoomBrush: Box2dModel | null;
|
||||
}
|
||||
|
||||
|
@ -1041,9 +1050,6 @@ export interface TLInstancePresence extends BaseRecord<'instance_presence', TLIn
|
|||
userName: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLInstancePropsForNextShape = Pick<TLShapeProps, TLStyleType>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLLanguage = (typeof LANGUAGES)[number];
|
||||
|
||||
|
@ -1053,11 +1059,6 @@ export type TLLineShape = TLBaseShape<'line', TLLineShapeProps>;
|
|||
// @public (undocumented)
|
||||
export type TLNoteShape = TLBaseShape<'note', TLNoteShapeProps>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLNullableShapeProps = {
|
||||
[K in TLShapeProp]?: null | TLShapeProps[K];
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLOpacityType = number;
|
||||
|
||||
|
@ -1113,28 +1114,6 @@ export type TLShapeProp = keyof TLShapeProps;
|
|||
// @public (undocumented)
|
||||
export type TLShapeProps = Identity<UnionToIntersection<TLDefaultShape['props']>>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface TLSizeStyle extends TLBaseStyle {
|
||||
// (undocumented)
|
||||
id: TLSizeType;
|
||||
// (undocumented)
|
||||
type: 'size';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLSizeType = SetValue<typeof TL_SIZE_TYPES>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface TLSplineStyle extends TLBaseStyle {
|
||||
// (undocumented)
|
||||
id: TLSplineType;
|
||||
// (undocumented)
|
||||
type: 'spline';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLSplineType = SetValue<typeof TL_SPLINE_TYPES>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLStore = Store<TLRecord, TLStoreProps>;
|
||||
|
||||
|
@ -1149,62 +1128,15 @@ export type TLStoreSchema = StoreSchema<TLRecord, TLStoreProps>;
|
|||
// @public (undocumented)
|
||||
export type TLStoreSnapshot = StoreSnapshot<TLRecord>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface TLStyleCollections {
|
||||
// (undocumented)
|
||||
align: TLAlignStyle[];
|
||||
// (undocumented)
|
||||
arrowheadEnd: TLArrowheadEndStyle[];
|
||||
// (undocumented)
|
||||
arrowheadStart: TLArrowheadStartStyle[];
|
||||
// (undocumented)
|
||||
color: TLColorStyle[];
|
||||
// (undocumented)
|
||||
dash: TLDashStyle[];
|
||||
// (undocumented)
|
||||
fill: TLFillStyle[];
|
||||
// (undocumented)
|
||||
font: TLFontStyle[];
|
||||
// (undocumented)
|
||||
geo: TLGeoStyle[];
|
||||
// (undocumented)
|
||||
size: TLSizeStyle[];
|
||||
// (undocumented)
|
||||
spline: TLSplineStyle[];
|
||||
// (undocumented)
|
||||
verticalAlign: TLVerticalAlignStyle[];
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLStyleItem = TLAlignStyle | TLArrowheadEndStyle | TLArrowheadStartStyle | TLColorStyle | TLDashStyle | TLFillStyle | TLFontStyle | TLGeoStyle | TLSizeStyle | TLSplineStyle | TLVerticalAlignStyle;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLStyleProps = Pick<TLShapeProps, TLStyleType>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLStyleType = SetValue<typeof TL_STYLE_TYPES>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLTextShape = TLBaseShape<'text', TLTextShapeProps>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLTextShapeProps = {
|
||||
color: TLColorType;
|
||||
size: TLSizeType;
|
||||
font: TLFontType;
|
||||
align: TLAlignType;
|
||||
w: number;
|
||||
text: string;
|
||||
scale: number;
|
||||
autoSize: boolean;
|
||||
};
|
||||
export type TLTextShapeProps = ShapePropsType<typeof textShapeProps>;
|
||||
|
||||
// @public
|
||||
export type TLUnknownShape = TLBaseShape<string, object>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type TLVerticalAlignType = SetValue<typeof TL_VERTICAL_ALIGN_TYPES>;
|
||||
|
||||
// @public
|
||||
export type TLVideoAsset = TLBaseAsset<'video', {
|
||||
w: number;
|
||||
|
@ -1228,14 +1160,21 @@ export interface Vec2dModel {
|
|||
z?: number;
|
||||
}
|
||||
|
||||
// @internal (undocumented)
|
||||
export const verticalAlignValidator: T.Validator<"end" | "middle" | "start">;
|
||||
// @public (undocumented)
|
||||
export const vec2dModelValidator: T.Validator<Vec2dModel>;
|
||||
|
||||
// @internal (undocumented)
|
||||
export const videoShapeMigrations: Migrations;
|
||||
|
||||
// @internal (undocumented)
|
||||
export const videoShapeProps: ShapeProps<TLVideoShape>;
|
||||
// @public (undocumented)
|
||||
export const videoShapeProps: {
|
||||
w: T.Validator<number>;
|
||||
h: T.Validator<number>;
|
||||
time: T.Validator<number>;
|
||||
playing: T.Validator<boolean>;
|
||||
url: T.Validator<string>;
|
||||
assetId: T.Validator<TLAssetId | null>;
|
||||
};
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Store, StoreSchema, StoreSchemaOptions, StoreSnapshot } from '@tldraw/s
|
|||
import { annotateError, structuredClone } from '@tldraw/utils'
|
||||
import { CameraRecordType, TLCameraId } from './records/TLCamera'
|
||||
import { DocumentRecordType, TLDOCUMENT_ID } from './records/TLDocument'
|
||||
import { InstanceRecordType, TLINSTANCE_ID } from './records/TLInstance'
|
||||
import { TLINSTANCE_ID } from './records/TLInstance'
|
||||
import { PageRecordType, TLPageId } from './records/TLPage'
|
||||
import { InstancePageStateRecordType, TLInstancePageStateId } from './records/TLPageState'
|
||||
import { PointerRecordType, TLPOINTER_ID } from './records/TLPointer'
|
||||
|
@ -103,7 +103,7 @@ export function createIntegrityChecker(store: TLStore): () => void {
|
|||
const instanceState = store.get(TLINSTANCE_ID)
|
||||
if (!instanceState) {
|
||||
store.put([
|
||||
InstanceRecordType.create({
|
||||
store.schema.types.instance.create({
|
||||
id: TLINSTANCE_ID,
|
||||
currentPageId: getFirstPageId(),
|
||||
exportBackground: true,
|
||||
|
|
|
@ -1,19 +1,18 @@
|
|||
import { Migrations, StoreSchema, createRecordType, defineMigrations } from '@tldraw/store'
|
||||
import { mapObjectMapValues } from '@tldraw/utils'
|
||||
import { T } from '@tldraw/validate'
|
||||
import { Migrations, StoreSchema } from '@tldraw/store'
|
||||
import { objectMapValues } from '@tldraw/utils'
|
||||
import { TLStoreProps, createIntegrityChecker, onValidationFailure } from './TLStore'
|
||||
import { AssetRecordType } from './records/TLAsset'
|
||||
import { CameraRecordType } from './records/TLCamera'
|
||||
import { DocumentRecordType } from './records/TLDocument'
|
||||
import { InstanceRecordType } from './records/TLInstance'
|
||||
import { createInstanceRecordType } from './records/TLInstance'
|
||||
import { PageRecordType } from './records/TLPage'
|
||||
import { InstancePageStateRecordType } from './records/TLPageState'
|
||||
import { PointerRecordType } from './records/TLPointer'
|
||||
import { InstancePresenceRecordType } from './records/TLPresence'
|
||||
import { TLRecord } from './records/TLRecord'
|
||||
import { TLShape, rootShapeMigrations } from './records/TLShape'
|
||||
import { createShapeValidator } from './shapes/TLBaseShape'
|
||||
import { createShapeRecordType, getShapePropKeysByStyle } from './records/TLShape'
|
||||
import { storeMigrations } from './store-migrations'
|
||||
import { StyleProp } from './styles/StyleProp'
|
||||
|
||||
/** @public */
|
||||
export type SchemaShapeInfo = {
|
||||
|
@ -31,23 +30,18 @@ export type TLSchema = StoreSchema<TLRecord, TLStoreProps>
|
|||
*
|
||||
* @public */
|
||||
export function createTLSchema({ shapes }: { shapes: Record<string, SchemaShapeInfo> }): TLSchema {
|
||||
const ShapeRecordType = createRecordType<TLShape>('shape', {
|
||||
migrations: defineMigrations({
|
||||
currentVersion: rootShapeMigrations.currentVersion,
|
||||
firstVersion: rootShapeMigrations.firstVersion,
|
||||
migrators: rootShapeMigrations.migrators,
|
||||
subTypeKey: 'type',
|
||||
subTypeMigrations: mapObjectMapValues(shapes, (k, v) => v.migrations ?? defineMigrations({})),
|
||||
}),
|
||||
scope: 'document',
|
||||
validator: T.model(
|
||||
'shape',
|
||||
T.union(
|
||||
'type',
|
||||
mapObjectMapValues(shapes, (type, { props }) => createShapeValidator(type, props))
|
||||
)
|
||||
),
|
||||
}).withDefaultProperties(() => ({ x: 0, y: 0, rotation: 0, isLocked: false, opacity: 1 }))
|
||||
const stylesById = new Map<string, StyleProp<unknown>>()
|
||||
for (const shape of objectMapValues(shapes)) {
|
||||
for (const style of getShapePropKeysByStyle(shape.props ?? {}).keys()) {
|
||||
if (stylesById.has(style.id) && stylesById.get(style.id) !== style) {
|
||||
throw new Error(`Multiple StyleProp instances with the same id: ${style.id}`)
|
||||
}
|
||||
stylesById.set(style.id, style)
|
||||
}
|
||||
}
|
||||
|
||||
const ShapeRecordType = createShapeRecordType(shapes)
|
||||
const InstanceRecordType = createInstanceRecordType(stylesById)
|
||||
|
||||
return StoreSchema.create(
|
||||
{
|
||||
|
|
|
@ -18,8 +18,14 @@ export {
|
|||
} from './misc/TLColor'
|
||||
export { type TLCursor, type TLCursorType } from './misc/TLCursor'
|
||||
export { type TLHandle, type TLHandleType } from './misc/TLHandle'
|
||||
export { opacityValidator, type TLOpacityType } from './misc/TLOpacity'
|
||||
export { scribbleValidator, type TLScribble } from './misc/TLScribble'
|
||||
export { type Box2dModel, type Vec2dModel } from './misc/geometry-types'
|
||||
export {
|
||||
box2dModelValidator,
|
||||
vec2dModelValidator,
|
||||
type Box2dModel,
|
||||
type Vec2dModel,
|
||||
} from './misc/geometry-types'
|
||||
export { idValidator } from './misc/id-validator'
|
||||
export {
|
||||
AssetRecordType,
|
||||
|
@ -32,14 +38,7 @@ export {
|
|||
} from './records/TLAsset'
|
||||
export { CameraRecordType, type TLCamera, type TLCameraId } from './records/TLCamera'
|
||||
export { DocumentRecordType, TLDOCUMENT_ID, type TLDocument } from './records/TLDocument'
|
||||
export {
|
||||
InstanceRecordType,
|
||||
TLINSTANCE_ID,
|
||||
instanceTypeValidator,
|
||||
type TLInstance,
|
||||
type TLInstanceId,
|
||||
type TLInstancePropsForNextShape,
|
||||
} from './records/TLInstance'
|
||||
export { TLINSTANCE_ID, type TLInstance, type TLInstanceId } from './records/TLInstance'
|
||||
export {
|
||||
PageRecordType,
|
||||
isPageId,
|
||||
|
@ -53,11 +52,11 @@ export { InstancePresenceRecordType, type TLInstancePresence } from './records/T
|
|||
export { type TLRecord } from './records/TLRecord'
|
||||
export {
|
||||
createShapeId,
|
||||
getShapePropKeysByStyle,
|
||||
isShape,
|
||||
isShapeId,
|
||||
rootShapeMigrations,
|
||||
type TLDefaultShape,
|
||||
type TLNullableShapeProps,
|
||||
type TLParentId,
|
||||
type TLShape,
|
||||
type TLShapeId,
|
||||
|
@ -67,12 +66,14 @@ export {
|
|||
type TLUnknownShape,
|
||||
} from './records/TLShape'
|
||||
export {
|
||||
ArrowShapeArrowheadEndStyle,
|
||||
ArrowShapeArrowheadStartStyle,
|
||||
arrowShapeMigrations,
|
||||
arrowShapeProps,
|
||||
type TLArrowShape,
|
||||
type TLArrowShapeArrowheadStyle,
|
||||
type TLArrowShapeProps,
|
||||
type TLArrowTerminal,
|
||||
type TLArrowTerminalType,
|
||||
type TLArrowShapeTerminal,
|
||||
} from './shapes/TLArrowShape'
|
||||
export {
|
||||
createShapeValidator,
|
||||
|
@ -102,22 +103,31 @@ export {
|
|||
type TLEmbedShapePermissions,
|
||||
} from './shapes/TLEmbedShape'
|
||||
export { frameShapeMigrations, frameShapeProps, type TLFrameShape } from './shapes/TLFrameShape'
|
||||
export { geoShapeMigrations, geoShapeProps, type TLGeoShape } from './shapes/TLGeoShape'
|
||||
export {
|
||||
GeoShapeGeoStyle,
|
||||
geoShapeMigrations,
|
||||
geoShapeProps,
|
||||
type TLGeoShape,
|
||||
} from './shapes/TLGeoShape'
|
||||
export { groupShapeMigrations, groupShapeProps, type TLGroupShape } from './shapes/TLGroupShape'
|
||||
export {
|
||||
highlightShapeMigrations,
|
||||
highlightShapeProps,
|
||||
type TLHighlightShape,
|
||||
} from './shapes/TLHighlightShape'
|
||||
export { iconShapeMigrations, iconShapeProps, type TLIconShape } from './shapes/TLIconShape'
|
||||
export {
|
||||
imageShapeMigrations,
|
||||
imageShapeProps,
|
||||
type TLImageCrop,
|
||||
type TLImageShape,
|
||||
type TLImageShapeCrop,
|
||||
type TLImageShapeProps,
|
||||
} from './shapes/TLImageShape'
|
||||
export { lineShapeMigrations, lineShapeProps, type TLLineShape } from './shapes/TLLineShape'
|
||||
export {
|
||||
LineShapeSplineStyle,
|
||||
lineShapeMigrations,
|
||||
lineShapeProps,
|
||||
type TLLineShape,
|
||||
} from './shapes/TLLineShape'
|
||||
export { noteShapeMigrations, noteShapeProps, type TLNoteShape } from './shapes/TLNoteShape'
|
||||
export {
|
||||
textShapeMigrations,
|
||||
|
@ -126,60 +136,20 @@ export {
|
|||
type TLTextShapeProps,
|
||||
} from './shapes/TLTextShape'
|
||||
export { videoShapeMigrations, videoShapeProps, type TLVideoShape } from './shapes/TLVideoShape'
|
||||
export { EnumStyleProp, StyleProp } from './styles/StyleProp'
|
||||
export { DefaultColorStyle, type TLDefaultColorStyle } from './styles/TLColorStyle'
|
||||
export { DefaultDashStyle, type TLDefaultDashStyle } from './styles/TLDashStyle'
|
||||
export { DefaultFillStyle, type TLDefaultFillStyle } from './styles/TLFillStyle'
|
||||
export { DefaultFontStyle, type TLDefaultFontStyle } from './styles/TLFontStyle'
|
||||
export {
|
||||
TL_ALIGN_TYPES,
|
||||
alignValidator,
|
||||
type TLAlignStyle,
|
||||
type TLAlignType,
|
||||
} from './styles/TLAlignStyle'
|
||||
DefaultHorizontalAlignStyle,
|
||||
type TLDefaultHorizontalAlignStyle,
|
||||
} from './styles/TLHorizontalAlignStyle'
|
||||
export { DefaultSizeStyle, type TLDefaultSizeStyle } from './styles/TLSizeStyle'
|
||||
export {
|
||||
TL_ARROWHEAD_TYPES,
|
||||
type TLArrowheadEndStyle,
|
||||
type TLArrowheadStartStyle,
|
||||
type TLArrowheadType,
|
||||
} from './styles/TLArrowheadStyle'
|
||||
export { TL_STYLE_TYPES, type TLStyleType } from './styles/TLBaseStyle'
|
||||
export {
|
||||
TL_COLOR_TYPES,
|
||||
colorValidator,
|
||||
type TLColorStyle,
|
||||
type TLColorType,
|
||||
} from './styles/TLColorStyle'
|
||||
export {
|
||||
TL_DASH_TYPES,
|
||||
dashValidator,
|
||||
type TLDashStyle,
|
||||
type TLDashType,
|
||||
} from './styles/TLDashStyle'
|
||||
export {
|
||||
TL_FILL_TYPES,
|
||||
fillValidator,
|
||||
type TLFillStyle,
|
||||
type TLFillType,
|
||||
} from './styles/TLFillStyle'
|
||||
export {
|
||||
TL_FONT_TYPES,
|
||||
fontValidator,
|
||||
type TLFontStyle,
|
||||
type TLFontType,
|
||||
} from './styles/TLFontStyle'
|
||||
export { TL_GEO_TYPES, geoValidator, type TLGeoStyle, type TLGeoType } from './styles/TLGeoStyle'
|
||||
export { iconValidator, type TLIconStyle, type TLIconType } from './styles/TLIconStyle'
|
||||
export { opacityValidator, type TLOpacityType } from './styles/TLOpacityStyle'
|
||||
export {
|
||||
TL_SIZE_TYPES,
|
||||
sizeValidator,
|
||||
type TLSizeStyle,
|
||||
type TLSizeType,
|
||||
} from './styles/TLSizeStyle'
|
||||
export {
|
||||
TL_SPLINE_TYPES,
|
||||
splineValidator,
|
||||
type TLSplineStyle,
|
||||
type TLSplineType,
|
||||
} from './styles/TLSplineStyle'
|
||||
export { verticalAlignValidator, type TLVerticalAlignType } from './styles/TLVerticalAlignStyle'
|
||||
export { type TLStyleCollections, type TLStyleItem, type TLStyleProps } from './styles/style-types'
|
||||
DefaultVerticalAlignStyle,
|
||||
type TLDefaultVerticalAlignStyle,
|
||||
} from './styles/TLVerticalAlignStyle'
|
||||
export {
|
||||
LANGUAGES,
|
||||
getDefaultTranslationLocale,
|
||||
|
|
|
@ -1191,6 +1191,47 @@ describe('Removes overridePermissions from embed', () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe('propsForNextShape -> stylesForNextShape', () => {
|
||||
test('deletes propsForNextShape and adds stylesForNextShape without trying to bring across contents', () => {
|
||||
const { up, down } =
|
||||
instanceMigrations.migrators[
|
||||
instanceTypeVersions.ReplacePropsForNextShapeWithStylesForNextShape
|
||||
]
|
||||
const beforeUp = {
|
||||
isToolLocked: true,
|
||||
propsForNextShape: {
|
||||
color: 'red',
|
||||
size: 'm',
|
||||
},
|
||||
}
|
||||
const afterUp = {
|
||||
isToolLocked: true,
|
||||
stylesForNextShape: {},
|
||||
}
|
||||
const afterDown = {
|
||||
isToolLocked: true,
|
||||
propsForNextShape: {
|
||||
color: 'black',
|
||||
labelColor: 'black',
|
||||
dash: 'draw',
|
||||
fill: 'none',
|
||||
size: 'm',
|
||||
icon: 'file',
|
||||
font: 'draw',
|
||||
align: 'middle',
|
||||
verticalAlign: 'middle',
|
||||
geo: 'rectangle',
|
||||
arrowheadStart: 'none',
|
||||
arrowheadEnd: 'arrow',
|
||||
spline: 'line',
|
||||
},
|
||||
}
|
||||
|
||||
expect(up(beforeUp)).toEqual(afterUp)
|
||||
expect(down(afterUp)).toEqual(afterDown)
|
||||
})
|
||||
})
|
||||
|
||||
/* --- PUT YOUR MIGRATIONS TESTS ABOVE HERE --- */
|
||||
|
||||
for (const migrator of allMigrators) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { T } from '@tldraw/validate'
|
||||
import { SetValue } from '../util-types'
|
||||
import { TLCanvasUiColor, canvasUiColorTypeValidator } from './TLColor'
|
||||
import { Vec2dModel } from './geometry-types'
|
||||
import { Vec2dModel, vec2dModelValidator } from './geometry-types'
|
||||
|
||||
/**
|
||||
* The scribble states used by tldraw.
|
||||
|
@ -24,7 +24,7 @@ export type TLScribble = {
|
|||
|
||||
/** @internal */
|
||||
export const scribbleValidator: T.Validator<TLScribble> = T.object({
|
||||
points: T.arrayOf(T.point),
|
||||
points: T.arrayOf(vec2dModelValidator),
|
||||
size: T.positiveNumber,
|
||||
color: canvasUiColorTypeValidator,
|
||||
opacity: T.number,
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { T } from '@tldraw/validate'
|
||||
|
||||
/**
|
||||
* A serializable model for 2D boxes.
|
||||
*
|
||||
|
@ -18,3 +20,18 @@ export interface Vec2dModel {
|
|||
y: number
|
||||
z?: number
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export const vec2dModelValidator: T.Validator<Vec2dModel> = T.object({
|
||||
x: T.number,
|
||||
y: T.number,
|
||||
z: T.number.optional(),
|
||||
})
|
||||
|
||||
/** @public */
|
||||
export const box2dModelValidator: T.Validator<Box2dModel> = T.object({
|
||||
x: T.number,
|
||||
y: T.number,
|
||||
w: T.number,
|
||||
h: T.number,
|
||||
})
|
||||
|
|
|
@ -1,27 +1,12 @@
|
|||
import { BaseRecord, createRecordType, defineMigrations, RecordId } from '@tldraw/store'
|
||||
import { T } from '@tldraw/validate'
|
||||
import { Box2dModel } from '../misc/geometry-types'
|
||||
import { Box2dModel, box2dModelValidator } from '../misc/geometry-types'
|
||||
import { idValidator } from '../misc/id-validator'
|
||||
import { cursorValidator, TLCursor } from '../misc/TLCursor'
|
||||
import { opacityValidator, TLOpacityType } from '../misc/TLOpacity'
|
||||
import { scribbleValidator, TLScribble } from '../misc/TLScribble'
|
||||
import { alignValidator } from '../styles/TLAlignStyle'
|
||||
import { arrowheadValidator } from '../styles/TLArrowheadStyle'
|
||||
import { TL_STYLE_TYPES, TLStyleType } from '../styles/TLBaseStyle'
|
||||
import { colorValidator } from '../styles/TLColorStyle'
|
||||
import { dashValidator } from '../styles/TLDashStyle'
|
||||
import { fillValidator } from '../styles/TLFillStyle'
|
||||
import { fontValidator } from '../styles/TLFontStyle'
|
||||
import { geoValidator } from '../styles/TLGeoStyle'
|
||||
import { iconValidator } from '../styles/TLIconStyle'
|
||||
import { opacityValidator, TLOpacityType } from '../styles/TLOpacityStyle'
|
||||
import { sizeValidator } from '../styles/TLSizeStyle'
|
||||
import { splineValidator } from '../styles/TLSplineStyle'
|
||||
import { verticalAlignValidator } from '../styles/TLVerticalAlignStyle'
|
||||
import { StyleProp } from '../styles/StyleProp'
|
||||
import { pageIdValidator, TLPageId } from './TLPage'
|
||||
import { TLShapeProps } from './TLShape'
|
||||
|
||||
/** @public */
|
||||
export type TLInstancePropsForNextShape = Pick<TLShapeProps, TLStyleType>
|
||||
|
||||
/**
|
||||
* TLInstance
|
||||
|
@ -36,7 +21,7 @@ export interface TLInstance extends BaseRecord<'instance', TLInstanceId> {
|
|||
highlightedUserIds: string[]
|
||||
brush: Box2dModel | null
|
||||
opacityForNextShape: TLOpacityType
|
||||
propsForNextShape: TLInstancePropsForNextShape
|
||||
stylesForNextShape: Record<string, unknown>
|
||||
cursor: TLCursor
|
||||
scribble: TLScribble | null
|
||||
isFocusMode: boolean
|
||||
|
@ -45,10 +30,8 @@ export interface TLInstance extends BaseRecord<'instance', TLInstanceId> {
|
|||
exportBackground: boolean
|
||||
screenBounds: Box2dModel
|
||||
zoomBrush: Box2dModel | null
|
||||
|
||||
chatMessage: string
|
||||
isChatting: boolean
|
||||
|
||||
isPenMode: boolean
|
||||
isGridMode: boolean
|
||||
}
|
||||
|
@ -59,46 +42,68 @@ export type TLInstanceId = RecordId<TLInstance>
|
|||
/** @internal */
|
||||
export const instanceIdValidator = idValidator<TLInstanceId>('instance')
|
||||
|
||||
/** @internal */
|
||||
export const instanceTypeValidator: T.Validator<TLInstance> = T.model(
|
||||
export function createInstanceRecordType(stylesById: Map<string, StyleProp<unknown>>) {
|
||||
const stylesForNextShapeValidators = {} as Record<string, T.Validator<unknown>>
|
||||
for (const [id, style] of stylesById) {
|
||||
stylesForNextShapeValidators[id] = T.optional(style)
|
||||
}
|
||||
|
||||
const instanceTypeValidator: T.Validator<TLInstance> = T.model(
|
||||
'instance',
|
||||
T.object({
|
||||
typeName: T.literal('instance'),
|
||||
id: idValidator<TLInstanceId>('instance'),
|
||||
currentPageId: pageIdValidator,
|
||||
followingUserId: T.string.nullable(),
|
||||
highlightedUserIds: T.arrayOf(T.string),
|
||||
brush: T.boxModel.nullable(),
|
||||
brush: box2dModelValidator.nullable(),
|
||||
opacityForNextShape: opacityValidator,
|
||||
propsForNextShape: T.object({
|
||||
color: colorValidator,
|
||||
labelColor: colorValidator,
|
||||
dash: dashValidator,
|
||||
fill: fillValidator,
|
||||
size: sizeValidator,
|
||||
font: fontValidator,
|
||||
align: alignValidator,
|
||||
verticalAlign: verticalAlignValidator,
|
||||
icon: iconValidator,
|
||||
geo: geoValidator,
|
||||
arrowheadStart: arrowheadValidator,
|
||||
arrowheadEnd: arrowheadValidator,
|
||||
spline: splineValidator,
|
||||
}),
|
||||
stylesForNextShape: T.object(stylesForNextShapeValidators),
|
||||
cursor: cursorValidator,
|
||||
scribble: scribbleValidator.nullable(),
|
||||
isFocusMode: T.boolean,
|
||||
isDebugMode: T.boolean,
|
||||
isToolLocked: T.boolean,
|
||||
exportBackground: T.boolean,
|
||||
screenBounds: T.boxModel,
|
||||
zoomBrush: T.boxModel.nullable(),
|
||||
chatMessage: T.string,
|
||||
isChatting: T.boolean,
|
||||
screenBounds: box2dModelValidator,
|
||||
zoomBrush: box2dModelValidator.nullable(),
|
||||
isPenMode: T.boolean,
|
||||
isGridMode: T.boolean,
|
||||
chatMessage: T.string,
|
||||
isChatting: T.boolean,
|
||||
highlightedUserIds: T.arrayOf(T.string),
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
return createRecordType<TLInstance>('instance', {
|
||||
migrations: instanceMigrations,
|
||||
validator: instanceTypeValidator,
|
||||
scope: 'session',
|
||||
}).withDefaultProperties(
|
||||
(): Omit<TLInstance, 'typeName' | 'id' | 'currentPageId'> => ({
|
||||
followingUserId: null,
|
||||
opacityForNextShape: 1,
|
||||
stylesForNextShape: {},
|
||||
brush: null,
|
||||
scribble: null,
|
||||
cursor: {
|
||||
type: 'default',
|
||||
color: 'black',
|
||||
rotation: 0,
|
||||
},
|
||||
isFocusMode: false,
|
||||
exportBackground: false,
|
||||
isDebugMode: process.env.NODE_ENV === 'development',
|
||||
isToolLocked: false,
|
||||
screenBounds: { x: 0, y: 0, w: 1080, h: 720 },
|
||||
zoomBrush: null,
|
||||
isGridMode: false,
|
||||
isPenMode: false,
|
||||
chatMessage: '',
|
||||
isChatting: false,
|
||||
highlightedUserIds: [],
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const Versions = {
|
||||
AddTransparentExportBgs: 1,
|
||||
|
@ -116,13 +121,14 @@ const Versions = {
|
|||
HoistOpacity: 13,
|
||||
AddChat: 14,
|
||||
AddHighlightedUserIds: 15,
|
||||
ReplacePropsForNextShapeWithStylesForNextShape: 16,
|
||||
} as const
|
||||
|
||||
export { Versions as instanceTypeVersions }
|
||||
|
||||
/** @public */
|
||||
export const instanceMigrations = defineMigrations({
|
||||
currentVersion: Versions.AddHighlightedUserIds,
|
||||
currentVersion: Versions.ReplacePropsForNextShapeWithStylesForNextShape,
|
||||
migrators: {
|
||||
[Versions.AddTransparentExportBgs]: {
|
||||
up: (instance: TLInstance) => {
|
||||
|
@ -154,7 +160,21 @@ export const instanceMigrations = defineMigrations({
|
|||
...instance,
|
||||
propsForNextShape: Object.fromEntries(
|
||||
Object.entries(propsForNextShape).filter(([key]) =>
|
||||
TL_STYLE_TYPES.has(key as TLStyleType)
|
||||
[
|
||||
'color',
|
||||
'labelColor',
|
||||
'dash',
|
||||
'fill',
|
||||
'size',
|
||||
'font',
|
||||
'align',
|
||||
'verticalAlign',
|
||||
'icon',
|
||||
'geo',
|
||||
'arrowheadStart',
|
||||
'arrowheadEnd',
|
||||
'spline',
|
||||
].includes(key)
|
||||
)
|
||||
),
|
||||
}
|
||||
|
@ -174,7 +194,7 @@ export const instanceMigrations = defineMigrations({
|
|||
},
|
||||
}
|
||||
},
|
||||
down: (instance: TLInstance) => {
|
||||
down: (instance) => {
|
||||
const { labelColor: _, ...rest } = instance.propsForNextShape
|
||||
return {
|
||||
...instance,
|
||||
|
@ -220,7 +240,7 @@ export const instanceMigrations = defineMigrations({
|
|||
},
|
||||
},
|
||||
[Versions.AddVerticalAlign]: {
|
||||
up: (instance: TLInstance) => {
|
||||
up: (instance) => {
|
||||
return {
|
||||
...instance,
|
||||
propsForNextShape: {
|
||||
|
@ -229,7 +249,7 @@ export const instanceMigrations = defineMigrations({
|
|||
},
|
||||
}
|
||||
},
|
||||
down: (instance: TLInstance) => {
|
||||
down: (instance) => {
|
||||
const { verticalAlign: _, ...propsForNextShape } = instance.propsForNextShape
|
||||
return {
|
||||
...instance,
|
||||
|
@ -307,19 +327,13 @@ export const instanceMigrations = defineMigrations({
|
|||
return instance
|
||||
},
|
||||
},
|
||||
[Versions.ReplacePropsForNextShapeWithStylesForNextShape]: {
|
||||
up: ({ propsForNextShape: _, ...instance }) => {
|
||||
return { ...instance, stylesForNextShape: {} }
|
||||
},
|
||||
})
|
||||
|
||||
/** @public */
|
||||
export const InstanceRecordType = createRecordType<TLInstance>('instance', {
|
||||
migrations: instanceMigrations,
|
||||
validator: instanceTypeValidator,
|
||||
scope: 'session',
|
||||
}).withDefaultProperties(
|
||||
(): Omit<TLInstance, 'typeName' | 'id' | 'currentPageId'> => ({
|
||||
followingUserId: null,
|
||||
highlightedUserIds: [],
|
||||
opacityForNextShape: 1,
|
||||
down: ({ stylesForNextShape: _, ...instance }: TLInstance) => {
|
||||
return {
|
||||
...instance,
|
||||
propsForNextShape: {
|
||||
color: 'black',
|
||||
labelColor: 'black',
|
||||
|
@ -335,25 +349,11 @@ export const InstanceRecordType = createRecordType<TLInstance>('instance', {
|
|||
arrowheadEnd: 'arrow',
|
||||
spline: 'line',
|
||||
},
|
||||
brush: null,
|
||||
scribble: null,
|
||||
cursor: {
|
||||
type: 'default',
|
||||
color: 'black',
|
||||
rotation: 0,
|
||||
}
|
||||
},
|
||||
isFocusMode: false,
|
||||
exportBackground: false,
|
||||
isDebugMode: process.env.NODE_ENV === 'development',
|
||||
isToolLocked: false,
|
||||
screenBounds: { x: 0, y: 0, w: 1080, h: 720 },
|
||||
zoomBrush: null,
|
||||
chatMessage: '',
|
||||
isChatting: false,
|
||||
isGridMode: false,
|
||||
isPenMode: false,
|
||||
})
|
||||
)
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
/** @public */
|
||||
export const TLINSTANCE_ID = InstanceRecordType.createId('instance')
|
||||
export const TLINSTANCE_ID = 'instance:instance' as TLInstanceId
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { BaseRecord, createRecordType, defineMigrations, RecordId } from '@tldraw/store'
|
||||
import { T } from '@tldraw/validate'
|
||||
import { Box2dModel } from '../misc/geometry-types'
|
||||
import { Box2dModel, box2dModelValidator } from '../misc/geometry-types'
|
||||
import { idValidator } from '../misc/id-validator'
|
||||
import { cursorTypeValidator, TLCursor } from '../misc/TLCursor'
|
||||
import { scribbleValidator, TLScribble } from '../misc/TLScribble'
|
||||
|
@ -55,10 +55,10 @@ export const instancePresenceValidator: T.Validator<TLInstancePresence> = T.mode
|
|||
y: T.number,
|
||||
z: T.number,
|
||||
}),
|
||||
screenBounds: T.boxModel,
|
||||
screenBounds: box2dModelValidator,
|
||||
selectedIds: T.arrayOf(idValidator<TLShapeId>('shape')),
|
||||
currentPageId: idValidator<TLPageId>('page'),
|
||||
brush: T.boxModel.nullable(),
|
||||
brush: box2dModelValidator.nullable(),
|
||||
scribble: scribbleValidator.nullable(),
|
||||
chatMessage: T.string,
|
||||
})
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import { defineMigrations, RecordId, UnknownRecord } from '@tldraw/store'
|
||||
import { createRecordType, defineMigrations, RecordId, UnknownRecord } from '@tldraw/store'
|
||||
import { mapObjectMapValues } from '@tldraw/utils'
|
||||
import { T } from '@tldraw/validate'
|
||||
import { nanoid } from 'nanoid'
|
||||
import { SchemaShapeInfo } from '../createTLSchema'
|
||||
import { TLArrowShape } from '../shapes/TLArrowShape'
|
||||
import { TLBaseShape } from '../shapes/TLBaseShape'
|
||||
import { createShapeValidator, TLBaseShape } from '../shapes/TLBaseShape'
|
||||
import { TLBookmarkShape } from '../shapes/TLBookmarkShape'
|
||||
import { TLDrawShape } from '../shapes/TLDrawShape'
|
||||
import { TLEmbedShape } from '../shapes/TLEmbedShape'
|
||||
|
@ -9,12 +12,12 @@ import { TLFrameShape } from '../shapes/TLFrameShape'
|
|||
import { TLGeoShape } from '../shapes/TLGeoShape'
|
||||
import { TLGroupShape } from '../shapes/TLGroupShape'
|
||||
import { TLHighlightShape } from '../shapes/TLHighlightShape'
|
||||
import { TLIconShape } from '../shapes/TLIconShape'
|
||||
import { TLImageShape } from '../shapes/TLImageShape'
|
||||
import { TLLineShape } from '../shapes/TLLineShape'
|
||||
import { TLNoteShape } from '../shapes/TLNoteShape'
|
||||
import { TLTextShape } from '../shapes/TLTextShape'
|
||||
import { TLVideoShape } from '../shapes/TLVideoShape'
|
||||
import { StyleProp } from '../styles/StyleProp'
|
||||
import { TLPageId } from './TLPage'
|
||||
|
||||
/**
|
||||
|
@ -34,7 +37,6 @@ export type TLDefaultShape =
|
|||
| TLNoteShape
|
||||
| TLTextShape
|
||||
| TLVideoShape
|
||||
| TLIconShape
|
||||
| TLHighlightShape
|
||||
|
||||
/**
|
||||
|
@ -79,9 +81,6 @@ export type TLShapeProp = keyof TLShapeProps
|
|||
/** @public */
|
||||
export type TLParentId = TLPageId | TLShapeId
|
||||
|
||||
/** @public */
|
||||
export type TLNullableShapeProps = { [K in TLShapeProp]?: TLShapeProps[K] | null }
|
||||
|
||||
export const Versions = {
|
||||
AddIsLocked: 1,
|
||||
HoistOpacity: 2,
|
||||
|
@ -151,3 +150,46 @@ export function isShapeId(id?: string): id is TLShapeId {
|
|||
export function createShapeId(id?: string): TLShapeId {
|
||||
return `shape:${id ?? nanoid()}` as TLShapeId
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function getShapePropKeysByStyle(props: Record<string, T.Validatable<any>>) {
|
||||
const propKeysByStyle = new Map<StyleProp<unknown>, string>()
|
||||
for (const [key, prop] of Object.entries(props)) {
|
||||
if (prop instanceof StyleProp) {
|
||||
if (propKeysByStyle.has(prop)) {
|
||||
throw new Error(
|
||||
`Duplicate style prop ${prop.id}. Each style prop can only be used once within a shape.`
|
||||
)
|
||||
}
|
||||
propKeysByStyle.set(prop, key)
|
||||
}
|
||||
}
|
||||
return propKeysByStyle
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function createShapeRecordType(shapes: Record<string, SchemaShapeInfo>) {
|
||||
return createRecordType<TLShape>('shape', {
|
||||
migrations: defineMigrations({
|
||||
currentVersion: rootShapeMigrations.currentVersion,
|
||||
firstVersion: rootShapeMigrations.firstVersion,
|
||||
migrators: rootShapeMigrations.migrators,
|
||||
subTypeKey: 'type',
|
||||
subTypeMigrations: mapObjectMapValues(shapes, (k, v) => v.migrations ?? defineMigrations({})),
|
||||
}),
|
||||
scope: 'document',
|
||||
validator: T.model(
|
||||
'shape',
|
||||
T.union(
|
||||
'type',
|
||||
mapObjectMapValues(shapes, (type, { props }) => createShapeValidator(type, props))
|
||||
)
|
||||
),
|
||||
}).withDefaultProperties(() => ({
|
||||
x: 0,
|
||||
y: 0,
|
||||
rotation: 0,
|
||||
isLocked: false,
|
||||
opacity: 1,
|
||||
}))
|
||||
}
|
||||
|
|
|
@ -1,57 +1,47 @@
|
|||
import { defineMigrations } from '@tldraw/store'
|
||||
import { T } from '@tldraw/validate'
|
||||
import { Vec2dModel } from '../misc/geometry-types'
|
||||
import { TLShapeId } from '../records/TLShape'
|
||||
import { TLArrowheadType, arrowheadValidator } from '../styles/TLArrowheadStyle'
|
||||
import { TLColorType, colorValidator } from '../styles/TLColorStyle'
|
||||
import { TLDashType, dashValidator } from '../styles/TLDashStyle'
|
||||
import { TLFillType, fillValidator } from '../styles/TLFillStyle'
|
||||
import { TLFontType, fontValidator } from '../styles/TLFontStyle'
|
||||
import { TLSizeType, sizeValidator } from '../styles/TLSizeStyle'
|
||||
import { SetValue } from '../util-types'
|
||||
import { ShapeProps, TLBaseShape, shapeIdValidator } from './TLBaseShape'
|
||||
import { vec2dModelValidator } from '../misc/geometry-types'
|
||||
import { StyleProp } from '../styles/StyleProp'
|
||||
import { DefaultColorStyle, DefaultLabelColorStyle } from '../styles/TLColorStyle'
|
||||
import { DefaultDashStyle } from '../styles/TLDashStyle'
|
||||
import { DefaultFillStyle } from '../styles/TLFillStyle'
|
||||
import { DefaultFontStyle } from '../styles/TLFontStyle'
|
||||
import { DefaultSizeStyle } from '../styles/TLSizeStyle'
|
||||
import { ShapePropsType, TLBaseShape, shapeIdValidator } from './TLBaseShape'
|
||||
|
||||
const arrowheadTypes = [
|
||||
'arrow',
|
||||
'triangle',
|
||||
'square',
|
||||
'dot',
|
||||
'pipe',
|
||||
'diamond',
|
||||
'inverted',
|
||||
'bar',
|
||||
'none',
|
||||
] as const
|
||||
|
||||
/** @public */
|
||||
export const TL_ARROW_TERMINAL_TYPE = new Set(['binding', 'point'] as const)
|
||||
export const ArrowShapeArrowheadStartStyle = StyleProp.defineEnum('tldraw:arrowheadStart', {
|
||||
defaultValue: 'none',
|
||||
values: arrowheadTypes,
|
||||
})
|
||||
|
||||
/** @public */
|
||||
export type TLArrowTerminalType = SetValue<typeof TL_ARROW_TERMINAL_TYPE>
|
||||
export const ArrowShapeArrowheadEndStyle = StyleProp.defineEnum('tldraw:arrowheadEnd', {
|
||||
defaultValue: 'arrow',
|
||||
values: arrowheadTypes,
|
||||
})
|
||||
|
||||
/** @public */
|
||||
export type TLArrowTerminal =
|
||||
| {
|
||||
type: 'binding'
|
||||
boundShapeId: TLShapeId
|
||||
normalizedAnchor: Vec2dModel
|
||||
isExact: boolean
|
||||
}
|
||||
| { type: 'point'; x: number; y: number }
|
||||
export type TLArrowShapeArrowheadStyle = T.TypeOf<typeof ArrowShapeArrowheadStartStyle>
|
||||
|
||||
/** @public */
|
||||
export type TLArrowShapeProps = {
|
||||
labelColor: TLColorType
|
||||
color: TLColorType
|
||||
fill: TLFillType
|
||||
dash: TLDashType
|
||||
size: TLSizeType
|
||||
arrowheadStart: TLArrowheadType
|
||||
arrowheadEnd: TLArrowheadType
|
||||
font: TLFontType
|
||||
start: TLArrowTerminal
|
||||
end: TLArrowTerminal
|
||||
bend: number
|
||||
text: string
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export type TLArrowShape = TLBaseShape<'arrow', TLArrowShapeProps>
|
||||
|
||||
/** @internal */
|
||||
export const arrowTerminalValidator: T.Validator<TLArrowTerminal> = T.union('type', {
|
||||
const ArrowShapeTerminal = T.union('type', {
|
||||
binding: T.object({
|
||||
type: T.literal('binding'),
|
||||
boundShapeId: shapeIdValidator,
|
||||
normalizedAnchor: T.point,
|
||||
normalizedAnchor: vec2dModelValidator,
|
||||
isExact: T.boolean,
|
||||
}),
|
||||
point: T.object({
|
||||
|
@ -61,22 +51,31 @@ export const arrowTerminalValidator: T.Validator<TLArrowTerminal> = T.union('typ
|
|||
}),
|
||||
})
|
||||
|
||||
/** @internal */
|
||||
export const arrowShapeProps: ShapeProps<TLArrowShape> = {
|
||||
labelColor: colorValidator,
|
||||
color: colorValidator,
|
||||
fill: fillValidator,
|
||||
dash: dashValidator,
|
||||
size: sizeValidator,
|
||||
arrowheadStart: arrowheadValidator,
|
||||
arrowheadEnd: arrowheadValidator,
|
||||
font: fontValidator,
|
||||
start: arrowTerminalValidator,
|
||||
end: arrowTerminalValidator,
|
||||
/** @public */
|
||||
export type TLArrowShapeTerminal = T.TypeOf<typeof ArrowShapeTerminal>
|
||||
|
||||
/** @public */
|
||||
export const arrowShapeProps = {
|
||||
labelColor: DefaultLabelColorStyle,
|
||||
color: DefaultColorStyle,
|
||||
fill: DefaultFillStyle,
|
||||
dash: DefaultDashStyle,
|
||||
size: DefaultSizeStyle,
|
||||
arrowheadStart: ArrowShapeArrowheadStartStyle,
|
||||
arrowheadEnd: ArrowShapeArrowheadEndStyle,
|
||||
font: DefaultFontStyle,
|
||||
start: ArrowShapeTerminal,
|
||||
end: ArrowShapeTerminal,
|
||||
bend: T.number,
|
||||
text: T.string,
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export type TLArrowShapeProps = ShapePropsType<typeof arrowShapeProps>
|
||||
|
||||
/** @public */
|
||||
export type TLArrowShape = TLBaseShape<'arrow', TLArrowShapeProps>
|
||||
|
||||
const Versions = {
|
||||
AddLabelColor: 1,
|
||||
} as const
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { BaseRecord } from '@tldraw/store'
|
||||
import { Expand } from '@tldraw/utils'
|
||||
import { T } from '@tldraw/validate'
|
||||
import { TLOpacityType, opacityValidator } from '../misc/TLOpacity'
|
||||
import { idValidator } from '../misc/id-validator'
|
||||
import { TLParentId, TLShapeId } from '../records/TLShape'
|
||||
import { TLOpacityType, opacityValidator } from '../styles/TLOpacityStyle'
|
||||
|
||||
/** @public */
|
||||
export interface TLBaseShape<Type extends string, Props extends object>
|
||||
|
@ -51,5 +52,9 @@ export function createShapeValidator<Type extends string, Props extends object>(
|
|||
|
||||
/** @public */
|
||||
export type ShapeProps<Shape extends TLBaseShape<any, any>> = {
|
||||
[K in keyof Shape['props']]: T.Validator<Shape['props'][K]>
|
||||
[K in keyof Shape['props']]: T.Validatable<Shape['props'][K]>
|
||||
}
|
||||
|
||||
export type ShapePropsType<Config extends Record<string, T.Validatable<any>>> = Expand<{
|
||||
[K in keyof Config]: T.TypeOf<Config[K]>
|
||||
}>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue