diff --git a/README.md b/README.md
index 3728a12..3b74c08 100644
--- a/README.md
+++ b/README.md
@@ -169,11 +169,11 @@ The options object is optional, as are each of its properties.
The `start` and `end` options accept an object:
-| Property | Type | Default | Description |
-| -------- | -------- | ------- | ------------------------------------------- |
-| `cap` | boolean | true | Whether to draw a cap. |
-| `taper` | number | 0 | The distance to taper. |
-| `easing` | function | t => t | An easing function for the tapering effect. |
+| Property | Type | Default | Description |
+| -------- | ----------------- | ------- | ---------------------------------------------------------------------------------------- |
+| `cap` | boolean | true | Whether to draw a cap. |
+| `taper` | number or boolean | 0 | The distance to taper. If set to true, the taper will be the total length of the stroke. |
+| `easing` | function | t => t | An easing function for the tapering effect. |
**Note:** The `cap` property has no effect when `taper` is more than zero.
diff --git a/package.json b/package.json
index 2674286..d6c05a8 100644
--- a/package.json
+++ b/package.json
@@ -40,8 +40,6 @@
"@testing-library/react": "^12.0.0",
"@types/jest": "^27.0.1",
"@types/node": "^15.0.1",
- "@types/react": "^17.0.19",
- "@types/react-dom": "^17.0.9",
"@typescript-eslint/eslint-plugin": "^4.19.0",
"@typescript-eslint/parser": "^4.19.0",
"babel-jest": "^27.1.0",
@@ -49,8 +47,6 @@
"fake-indexeddb": "^3.1.3",
"jest": "^27.1.0",
"lerna": "^3.15.0",
- "react": "^17.0.2",
- "react-dom": "^17.0.2",
"ts-jest": "^27.0.5",
"tslib": "^2.3.0",
"typedoc": "^0.21.9",
diff --git a/packages/dev/package.json b/packages/dev/package.json
index 3856870..8c961be 100644
--- a/packages/dev/package.json
+++ b/packages/dev/package.json
@@ -27,9 +27,7 @@
"files": [
"src"
],
- "dependencies": {
- "@types/react-dom": "^17.0.11"
- },
+ "dependencies": {},
"devDependencies": {
"@modulz/radix-icons": "^4.0.0",
"@radix-ui/react-checkbox": "^0.1.0",
@@ -40,8 +38,8 @@
"@tldraw/intersect": "^0.1.3",
"@tldraw/vec": "^0.1.3",
"@types/node": "^14.14.35",
- "@types/react": "^17.0.34",
- "@types/react-dom": "^17.0.9",
+ "@types/react": "^18.0.10",
+ "@types/react-dom": "^18.0.5",
"concurrently": "6.0.1",
"create-serve": "1.0.1",
"css-tree": "^1.1.3",
@@ -50,12 +48,13 @@
"husky": "^7.0.0",
"jest": "^27.0.6",
"perfect-freehand": "^1.0.16",
- "react": "^17.0.2",
- "react-dom": "^17.0.2",
"react-hotkeys-hook": "^3.4.0",
"rimraf": "3.0.2",
"rko": "^0.5.25",
"tslib": "^2.3.1",
- "typescript": "^4.3.5"
+ "typescript": "^4.3.5",
+ "react": "^18.1.0",
+ "react-dom": "^18.1.0",
+ "zustand": "^4.0.0-rc.1"
}
-}
+}
\ No newline at end of file
diff --git a/packages/dev/src/components/controls/controls.module.css b/packages/dev/src/components/controls/controls.module.css
index 0f91335..f185b09 100644
--- a/packages/dev/src/components/controls/controls.module.css
+++ b/packages/dev/src/components/controls/controls.module.css
@@ -19,7 +19,7 @@
.inputs {
display: grid;
- grid-template-columns: auto 1fr auto;
+ grid-template-columns: 96px 1fr auto;
grid-auto-rows: 32px;
align-items: center;
column-gap: 16px;
diff --git a/packages/dev/src/components/controls/controls.tsx b/packages/dev/src/components/controls/controls.tsx
index 61450ee..199dfb3 100644
--- a/packages/dev/src/components/controls/controls.tsx
+++ b/packages/dev/src/components/controls/controls.tsx
@@ -101,7 +101,7 @@ export function Controls() {
}, [])
const handleTaperStartChange = React.useCallback((v: number[]) => {
- app.patchStyleForAllShapes({ taperStart: v[0] })
+ app.patchStyleForAllShapes({ taperStart: v[0] === 100 ? true : v[0] })
}, [])
const handleEasingStartChange = React.useCallback((easing: string) => {
@@ -120,7 +120,7 @@ export function Controls() {
}, [])
const handleTaperEndChange = React.useCallback((v: number[]) => {
- app.patchStyleForAllShapes({ taperEnd: v[0] })
+ app.patchStyleForAllShapes({ taperEnd: v[0] === 100 ? true : v[0] })
}, [])
const handleEasingEndChange = React.useCallback((easing: string) => {
@@ -199,7 +199,7 @@ export function Controls() {
{style.taperEnd <= 0 && (
void
onPointerUp: () => void
onDoubleClick: () => void
+ label?: string
}
export function Slider({
@@ -24,6 +25,7 @@ export function Slider({
min,
max,
step,
+ label,
value = [0],
...props
}: SliderProps) {
@@ -60,15 +62,19 @@ export function Slider({
))}
-
+ {label ? (
+ {label}
+ ) : (
+
+ )}
>
)
}
diff --git a/packages/dev/src/state/shapes/draw.tsx b/packages/dev/src/state/shapes/draw.tsx
index 9ac156b..35bbbcc 100644
--- a/packages/dev/src/state/shapes/draw.tsx
+++ b/packages/dev/src/state/shapes/draw.tsx
@@ -25,7 +25,7 @@ export class DrawUtil extends TLShapeUtil {
rotatedCache = new WeakMap([])
- strokeCache = new WeakMap([])
+ strokeCache = new WeakMap([])
getShape = (props: Partial): T => {
return Utils.deepMerge(
@@ -85,25 +85,26 @@ export class DrawUtil extends TLShapeUtil {
const simulatePressure = shape.points[2]?.[2] === 0.5
- const outlinePoints = Utils.getFromCache(
- this.strokeCache,
- shape.points,
- () =>
- getStroke(shape.points, {
- size,
- thinning,
- streamline,
- easing: EASINGS[easing],
- smoothing,
- start: {
- taper: taperStart,
- cap: capStart,
- easing: EASINGS[easingStart],
- },
- end: { taper: taperEnd, cap: capEnd, easing: EASINGS[easingEnd] },
- simulatePressure,
- last: isDone,
- })
+ const outlinePoints = Utils.getFromCache(this.strokeCache, shape, () =>
+ getStroke(shape.points, {
+ size,
+ thinning,
+ streamline,
+ easing: EASINGS[easing],
+ smoothing,
+ start: {
+ taper: taperStart,
+ cap: capStart,
+ easing: EASINGS[easingStart],
+ },
+ end: {
+ taper: taperEnd,
+ cap: capEnd,
+ easing: EASINGS[easingEnd],
+ },
+ simulatePressure,
+ last: isDone,
+ })
)
const drawPathData = getSvgPathFromStroke(outlinePoints)
diff --git a/packages/dev/src/state/state.ts b/packages/dev/src/state/state.ts
index 26700fb..76c5de8 100644
--- a/packages/dev/src/state/state.ts
+++ b/packages/dev/src/state/state.ts
@@ -481,7 +481,7 @@ export class AppState extends StateManager {
}
if (Utils.pointInBounds(pt, bounds)) {
- const points = draw.strokeCache.get(shape.points)
+ const points = draw.strokeCache.get(shape)
if (
(points &&
diff --git a/packages/dev/src/types.ts b/packages/dev/src/types.ts
index e34e9f3..5af778f 100644
--- a/packages/dev/src/types.ts
+++ b/packages/dev/src/types.ts
@@ -21,8 +21,8 @@ export interface DrawStyles {
thinning: number
streamline: number
smoothing: number
- taperStart: number
- taperEnd: number
+ taperStart: number | boolean
+ taperEnd: number | boolean
capStart: boolean
capEnd: boolean
easingStart: Easing
diff --git a/packages/perfect-freehand/README.md b/packages/perfect-freehand/README.md
index 3728a12..3b74c08 100644
--- a/packages/perfect-freehand/README.md
+++ b/packages/perfect-freehand/README.md
@@ -169,11 +169,11 @@ The options object is optional, as are each of its properties.
The `start` and `end` options accept an object:
-| Property | Type | Default | Description |
-| -------- | -------- | ------- | ------------------------------------------- |
-| `cap` | boolean | true | Whether to draw a cap. |
-| `taper` | number | 0 | The distance to taper. |
-| `easing` | function | t => t | An easing function for the tapering effect. |
+| Property | Type | Default | Description |
+| -------- | ----------------- | ------- | ---------------------------------------------------------------------------------------- |
+| `cap` | boolean | true | Whether to draw a cap. |
+| `taper` | number or boolean | 0 | The distance to taper. If set to true, the taper will be the total length of the stroke. |
+| `easing` | function | t => t | An easing function for the tapering effect. |
**Note:** The `cap` property has no effect when `taper` is more than zero.
diff --git a/packages/perfect-freehand/src/getStrokeOutlinePoints.ts b/packages/perfect-freehand/src/getStrokeOutlinePoints.ts
index 4ca9f82..0cf2706 100644
--- a/packages/perfect-freehand/src/getStrokeOutlinePoints.ts
+++ b/packages/perfect-freehand/src/getStrokeOutlinePoints.ts
@@ -52,17 +52,11 @@ export function getStrokeOutlinePoints(
last: isComplete = false,
} = options
- const {
- cap: capStart = true,
- taper: taperStart = 0,
- easing: taperStartEase = (t) => t * (2 - t),
- } = start
+ let { cap: capStart = true, easing: taperStartEase = (t) => t * (2 - t) } =
+ start
- const {
- cap: capEnd = true,
- taper: taperEnd = 0,
- easing: taperEndEase = (t) => --t * t * t + 1,
- } = end
+ let { cap: capEnd = true, easing: taperEndEase = (t) => --t * t * t + 1 } =
+ end
// We can't do anything with an empty array or a stroke with negative size.
if (points.length === 0 || size <= 0) {
@@ -72,6 +66,20 @@ export function getStrokeOutlinePoints(
// The total length of the line
const totalLength = points[points.length - 1].runningLength
+ const taperStart =
+ start.taper === false
+ ? 0
+ : start.taper === true
+ ? Math.max(size, totalLength)
+ : (start.taper as number)
+
+ const taperEnd =
+ end.taper === false
+ ? 0
+ : end.taper === true
+ ? Math.max(size, totalLength)
+ : (end.taper as number)
+
// The minimum allowed distance between points (squared)
const minDistance = Math.pow(size * smoothing, 2)
diff --git a/packages/perfect-freehand/src/types.ts b/packages/perfect-freehand/src/types.ts
index 3c226f5..d2ec767 100644
--- a/packages/perfect-freehand/src/types.ts
+++ b/packages/perfect-freehand/src/types.ts
@@ -20,12 +20,12 @@ export interface StrokeOptions {
simulatePressure?: boolean
start?: {
cap?: boolean
- taper?: number
+ taper?: number | boolean
easing?: (distance: number) => number
}
end?: {
cap?: boolean
- taper?: number
+ taper?: number | boolean
easing?: (distance: number) => number
}
// Whether to handle the points as a completed stroke.
diff --git a/yarn.lock b/yarn.lock
index 6e1b147..d6f9bb0 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2467,21 +2467,14 @@
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11"
integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==
-"@types/react-dom@^17.0.11":
- version "17.0.11"
- resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.11.tgz#e1eadc3c5e86bdb5f7684e00274ae228e7bcc466"
- integrity sha512-f96K3k+24RaLGVu/Y2Ng3e1EbZ8/cVJvypZWd7cy0ofCBaf2lcM46xNhycMZ2xGwbBjRql7hOlZ+e2WlJ5MH3Q==
+"@types/react-dom@^18.0.5":
+ version "18.0.5"
+ resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.5.tgz#330b2d472c22f796e5531446939eacef8378444a"
+ integrity sha512-OWPWTUrY/NIrjsAPkAk1wW9LZeIjSvkXRhclsFO8CZcZGCOg2G0YZy4ft+rOyYxy8B7ui5iZzi9OkDebZ7/QSA==
dependencies:
"@types/react" "*"
-"@types/react-dom@^17.0.9":
- version "17.0.9"
- resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.9.tgz#441a981da9d7be117042e1a6fd3dac4b30f55add"
- integrity sha512-wIvGxLfgpVDSAMH5utdL9Ngm5Owu0VsGmldro3ORLXV8CShrL8awVj06NuEXFQ5xyaYfdca7Sgbk/50Ri1GdPg==
- dependencies:
- "@types/react" "*"
-
-"@types/react@*", "@types/react@^17.0.19":
+"@types/react@*":
version "17.0.19"
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.19.tgz#8f2a85e8180a43b57966b237d26a29481dacc991"
integrity sha512-sX1HisdB1/ZESixMTGnMxH9TDe8Sk709734fEQZzCV/4lSu9kJCPbo2PbTRoZM+53Pp0P10hYVyReUueGwUi4A==
@@ -2490,10 +2483,10 @@
"@types/scheduler" "*"
csstype "^3.0.2"
-"@types/react@^17.0.34":
- version "17.0.34"
- resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.34.tgz#797b66d359b692e3f19991b6b07e4b0c706c0102"
- integrity sha512-46FEGrMjc2+8XhHXILr+3+/sTe3OfzSPU9YGKILLrUYbQ1CLQC9Daqo1KzENGXAWwrFwiY0l4ZbF20gRvgpWTg==
+"@types/react@^18.0.10":
+ version "18.0.10"
+ resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.10.tgz#5692944d4a45e204fb7a981eb1388afe919cf4d0"
+ integrity sha512-dIugadZuIPrRzvIEevIu7A1smqOAjkSMv8qOfwPt9Ve6i6JT/FQcCHyk2qIAxwsQNKZt5/oGR0T4z9h2dXRAkg==
dependencies:
"@types/prop-types" "*"
"@types/scheduler" "*"
@@ -6968,7 +6961,7 @@ oauth-sign@~0.9.0:
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
-object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
+object-assign@^4.0.1, object-assign@^4.1.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
@@ -7617,14 +7610,13 @@ quick-lru@^4.0.1:
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f"
integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==
-react-dom@^17.0.2:
- version "17.0.2"
- resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23"
- integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==
+react-dom@^18.1.0:
+ version "18.1.0"
+ resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.1.0.tgz#7f6dd84b706408adde05e1df575b3a024d7e8a2f"
+ integrity sha512-fU1Txz7Budmvamp7bshe4Zi32d0ll7ect+ccxNu9FlObT605GOEB8BfO4tmRJ39R5Zj831VCpvQ05QPBW5yb+w==
dependencies:
loose-envify "^1.1.0"
- object-assign "^4.1.1"
- scheduler "^0.20.2"
+ scheduler "^0.22.0"
react-hotkeys-hook@^3.4.0:
version "3.4.0"
@@ -7638,13 +7630,12 @@ react-is@^17.0.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
-react@^17.0.2:
- version "17.0.2"
- resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"
- integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==
+react@^18.1.0:
+ version "18.1.0"
+ resolved "https://registry.yarnpkg.com/react/-/react-18.1.0.tgz#6f8620382decb17fdc5cc223a115e2adbf104890"
+ integrity sha512-4oL8ivCz5ZEPyclFQXaNksK3adutVS8l2xzZU0cqEFrE9Sb7fC0EFK5uEk74wIreL1DERyjvsU915j1pcT2uEQ==
dependencies:
loose-envify "^1.1.0"
- object-assign "^4.1.1"
read-cmd-shim@^1.0.1:
version "1.0.5"
@@ -8064,13 +8055,12 @@ saxes@^5.0.1:
dependencies:
xmlchars "^2.2.0"
-scheduler@^0.20.2:
- version "0.20.2"
- resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91"
- integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==
+scheduler@^0.22.0:
+ version "0.22.0"
+ resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.22.0.tgz#83a5d63594edf074add9a7198b1bae76c3db01b8"
+ integrity sha512-6QAm1BgQI88NPYymgGQLCZgvep4FyePDWFpXVK+zNSUgHwlqpJy8VEh8Et0KxTACS4VWwMousBElAZOH9nkkoQ==
dependencies:
loose-envify "^1.1.0"
- object-assign "^4.1.1"
"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1:
version "5.7.1"
@@ -9120,6 +9110,11 @@ urix@^0.1.0:
resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=
+use-sync-external-store@1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.1.0.tgz#3343c3fe7f7e404db70f8c687adf5c1652d34e82"
+ integrity sha512-SEnieB2FPKEVne66NpXPd1Np4R1lTNKfjuy3XdIoPQKYBAFdzbzSZlSn1KJZUiihQLQC5Znot4SBz1EOTBwQAQ==
+
use@^3.1.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
@@ -9473,3 +9468,10 @@ zustand@^3.5.9:
version "3.5.10"
resolved "https://registry.yarnpkg.com/zustand/-/zustand-3.5.10.tgz#d2622efd64530ffda285ee5b13ff645b68ab0faf"
integrity sha512-upluvSRWrlCiExu2UbkuMIPJ9AigyjRFoO7O9eUossIj7rPPq7pcJ0NKk6t2P7KF80tg/UdPX6/pNKOSbs9DEg==
+
+zustand@^4.0.0-rc.1:
+ version "4.0.0-rc.1"
+ resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.0.0-rc.1.tgz#ec30a3afc03728adec7e1bd7bcc3592176372201"
+ integrity sha512-qgcs7zLqBdHu0PuT3GW4WCIY5SgXdsv30GQMu9Qpp1BA2aS+sNS8l4x0hWuyEhjXkN+701aGWawhKDv6oWJAcw==
+ dependencies:
+ use-sync-external-store "1.1.0"