Skip to content

Commit e579573

Browse files
feat(shapes): add 'none' dash style to hide shape borders (tldraw#8453)
In order to allow users to create shapes with fill but no visible border/stroke (Closes tldraw#7020), this PR adds a `'none'` value to `DefaultDashStyle`. When the dash style is set to `'none'`, shapes render their fill normally but skip all stroke/border rendering. Shape indicators still work correctly for selection purposes. The `'none'` dash style is not exposed in the UI style panel — it can only be set programmatically via the SDK. ### Change type - [x] `feature` ### Test plan 1. Create a geo shape (rectangle, ellipse, etc.) and set dash style to `'none'` programmatically 2. Verify the shape fill renders but no border/stroke is visible 3. Verify the shape is still selectable (indicator appears on hover/select) 4. Test with draw shapes — closed draw shapes with fill should show fill but no stroke 5. Test with arrow shapes — arrow body should be hidden with none dash 6. Test with line shapes — line should be invisible but still selectable 7. Test SVG export with none dash — exported SVG should have no stroke 8. Test x-box geo shape — the internal X lines should not render with none dash - [ ] Unit tests - [ ] End to end tests ### Release notes - Add `'none'` dash style option that hides shape borders while preserving fill ### API changes - Added `'none'` to `DefaultDashStyle` enum values (`TLDefaultDashStyle` type) - Added `NonePathBuilderOpts` interface for the none style in `PathBuilder` - `PathBuilder.toSvg()` now returns `null` for `'none'` style - `PathBuilder.toPath2D()` returns empty `Path2D` for `'none'` style ### Code changes | Section | LOC change | | --------------- | ---------- | | Core code | +41 / -11 | | Automated files | +15 / -9 | --------- Co-authored-by: huppy-bot[bot] <128400622+huppy-bot[bot]@users.noreply.github.com>
1 parent 989d9d5 commit e579573

14 files changed

Lines changed: 56 additions & 21 deletions
31.3 KB
Loading
31.3 KB
Loading
27.2 KB
Loading
27.2 KB
Loading

packages/editor/src/lib/editor/shapes/shared/getPerfectDashProps.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,13 @@ export function getPerfectDashProps(
4343
}
4444
}
4545

46+
if (style === 'none') {
47+
return {
48+
strokeDasharray: 'none',
49+
strokeDashoffset: 'none',
50+
}
51+
}
52+
4653
switch (style) {
4754
case 'dashed': {
4855
ratio = 1

packages/tldraw/api-report.api.md

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1817,7 +1817,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
18171817
// (undocumented)
18181818
getText(shape: TLGeoShape): string;
18191819
// (undocumented)
1820-
indicator(shape: TLGeoShape): JSX.Element;
1820+
indicator(shape: TLGeoShape): JSX.Element | null;
18211821
// (undocumented)
18221822
static migrations: TLPropsMigrations;
18231823
// (undocumented)
@@ -1831,7 +1831,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
18311831
props: {
18321832
align: "end-legacy" | "end" | "middle-legacy" | "middle" | "start-legacy" | "start";
18331833
color: TLDefaultColorStyle;
1834-
dash: "dashed" | "dotted" | "draw" | "solid";
1834+
dash: "dashed" | "dotted" | "draw" | "none" | "solid";
18351835
fill: "fill" | "lined-fill" | "none" | "pattern" | "semi" | "solid";
18361836
font: TLDefaultFontStyle;
18371837
geo: "arrow-down" | "arrow-left" | "arrow-right" | "arrow-up" | "check-box" | "cloud" | "diamond" | "ellipse" | "heart" | "hexagon" | "octagon" | "oval" | "pentagon" | "rectangle" | "rhombus-2" | "rhombus" | "star" | "trapezoid" | "triangle" | "x-box";
@@ -1866,7 +1866,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
18661866
props: {
18671867
align: "end-legacy" | "end" | "middle-legacy" | "middle" | "start-legacy" | "start";
18681868
color: TLDefaultColorStyle;
1869-
dash: "dashed" | "dotted" | "draw" | "solid";
1869+
dash: "dashed" | "dotted" | "draw" | "none" | "solid";
18701870
fill: "fill" | "lined-fill" | "none" | "pattern" | "semi" | "solid";
18711871
font: TLDefaultFontStyle;
18721872
geo: "arrow-down" | "arrow-left" | "arrow-right" | "arrow-up" | "check-box" | "cloud" | "diamond" | "ellipse" | "heart" | "hexagon" | "octagon" | "oval" | "pentagon" | "rectangle" | "rhombus-2" | "rhombus" | "star" | "trapezoid" | "triangle" | "x-box";
@@ -2345,7 +2345,7 @@ export class LineShapeUtil extends ShapeUtil<TLLineShape> {
23452345
// (undocumented)
23462346
hideSelectionBoundsFg(): boolean;
23472347
// (undocumented)
2348-
indicator(shape: TLLineShape): JSX.Element;
2348+
indicator(shape: TLLineShape): JSX.Element | null;
23492349
// (undocumented)
23502350
static migrations: TLPropsMigrations;
23512351
// (undocumented)
@@ -2360,7 +2360,7 @@ export class LineShapeUtil extends ShapeUtil<TLLineShape> {
23602360
parentId: TLParentId;
23612361
props: {
23622362
color: TLDefaultColorStyle;
2363-
dash: "dashed" | "dotted" | "draw" | "solid";
2363+
dash: "dashed" | "dotted" | "draw" | "none" | "solid";
23642364
points: {
23652365
[x: string]: {
23662366
id: string;
@@ -2389,7 +2389,7 @@ export class LineShapeUtil extends ShapeUtil<TLLineShape> {
23892389
parentId: TLParentId;
23902390
props: {
23912391
color: TLDefaultColorStyle;
2392-
dash: "dashed" | "dotted" | "draw" | "solid";
2392+
dash: "dashed" | "dotted" | "draw" | "none" | "solid";
23932393
points: {
23942394
[x: string]: {
23952395
id: IndexKey;
@@ -2472,6 +2472,12 @@ export interface MoveToPathBuilderCommand extends PathBuilderCommandBase {
24722472
type: 'move';
24732473
}
24742474

2475+
// @public (undocumented)
2476+
export interface NonePathBuilderOpts extends BasePathBuilderOpts {
2477+
// (undocumented)
2478+
style: 'none';
2479+
}
2480+
24752481
// @public (undocumented)
24762482
export interface NoteShapeOptions extends ShapeOptionsWithDisplayValues<TLNoteShape, NoteShapeUtilDisplayValues> {
24772483
resizeMode: 'none' | 'scale';
@@ -2736,7 +2742,7 @@ export class PathBuilder {
27362742
// (undocumented)
27372743
toPath2D(opts: PathBuilderOpts): Path2D;
27382744
// (undocumented)
2739-
toSvg(opts: PathBuilderOpts): JSX.Element;
2745+
toSvg(opts: PathBuilderOpts): JSX.Element | null;
27402746
}
27412747

27422748
// @internal (undocumented)
@@ -2799,7 +2805,7 @@ export interface PathBuilderLineOpts extends PathBuilderCommandOpts {
27992805
}
28002806

28012807
// @public (undocumented)
2802-
export type PathBuilderOpts = DashedPathBuilderOpts | DrawPathBuilderOpts | SolidPathBuilderOpts;
2808+
export type PathBuilderOpts = DashedPathBuilderOpts | DrawPathBuilderOpts | NonePathBuilderOpts | SolidPathBuilderOpts;
28032809

28042810
// @public (undocumented)
28052811
export interface PathBuilderToDOpts {

packages/tldraw/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export {
1111
type DashedPathBuilderOpts,
1212
type DrawPathBuilderDOpts,
1313
type DrawPathBuilderOpts,
14+
type NonePathBuilderOpts,
1415
type LineToPathBuilderCommand,
1516
type MoveToPathBuilderCommand,
1617
type PathBuilderCommand,

packages/tldraw/src/lib/shapes/draw/DrawShapeUtil.tsx

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -423,15 +423,17 @@ function DrawShapeSvg({
423423
) : (
424424
<path fill={fillColor} d={solidStrokePath} />
425425
)}
426-
<path
427-
d={solidStrokePath}
428-
strokeLinecap="round"
429-
fill={isDot ? strokeColor : 'none'}
430-
stroke={strokeColor}
431-
strokeWidth={sw}
432-
strokeDasharray={isDot ? 'none' : getDrawShapeStrokeDashArray(shape, sw, dotAdjustment)}
433-
strokeDashoffset="0"
434-
/>
426+
{shape.props.dash !== 'none' && (
427+
<path
428+
d={solidStrokePath}
429+
strokeLinecap="round"
430+
fill={isDot ? strokeColor : 'none'}
431+
stroke={strokeColor}
432+
strokeWidth={sw}
433+
strokeDasharray={isDot ? 'none' : getDrawShapeStrokeDashArray(shape, sw, dotAdjustment)}
434+
strokeDashoffset="0"
435+
/>
436+
)}
435437
</>
436438
)
437439
}

packages/tldraw/src/lib/shapes/draw/getPath.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,5 +162,6 @@ export function getDrawShapeStrokeDashArray(
162162
solid: `none`,
163163
dotted: `${dotAdjustment} ${strokeWidth * 2}`,
164164
dashed: `${strokeWidth * 2} ${strokeWidth * 2}`,
165+
none: `none`,
165166
}[shape.props.dash]
166167
}

packages/tldraw/src/lib/shapes/geo/getGeoShapePath.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,10 @@ function getXBoxPath(
211211
.lineTo(0, h)
212212
.close()
213213

214+
if (dash === 'none') {
215+
return path
216+
}
217+
214218
if (dash === 'dashed' || dash === 'dotted') {
215219
return path
216220
.moveTo(0, 0, {

0 commit comments

Comments
 (0)