@svelteframe/shapes 0.0.0-stage → 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sumeet
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,35 @@
1
- # Temporary Holding Version
1
+ # @svelteframe/shapes
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ SVG shape primitives and smart connecting arrows for Svelteframe video compositions.
4
+
5
+ ## Features
6
+
7
+ - **Shapes**: `Rect`, `Circle`, `Ellipse`, `Polygon`, `Star`, `Triangle`, and raw `Shape` components.
8
+ - **Canvas**: Coordinate system and viewBox management.
9
+ - **Connectors**: `Arrow` that attaches smoothly to any registered shape by `id`.
10
+
11
+ ## Installation
12
+
13
+ ```sh
14
+ npm install @svelteframe/shapes @svelteframe/core
15
+ # or
16
+ pnpm add @svelteframe/shapes @svelteframe/core
17
+ ```
18
+
19
+ ## Basic Usage
20
+
21
+ ```svelte
22
+ <script lang="ts">
23
+ import { Canvas, Rect, Circle, Arrow } from '@svelteframe/shapes';
24
+ </script>
25
+
26
+ <Canvas width={800} height={600}>
27
+ <Rect id="box-a" x={100} y={200} width={120} height={80} fill="#3b82f6" />
28
+ <Circle id="box-b" cx={500} cy={200} r={50} fill="#10b981" />
29
+ <Arrow from="box-a" to="box-b" stroke="#ffffff" strokeWidth={3} />
30
+ </Canvas>
31
+ ```
32
+
33
+ ## License
34
+
35
+ MIT
@@ -0,0 +1,76 @@
1
+ <script lang="ts">
2
+ import type { End } from './connect.js';
3
+ import { useEnds } from './ends.svelte.js';
4
+ import { arrowGeometry, boundsOf, paintAttributes, pointsAttribute, round, type ArrowHeads, type Point } from './geometry.js';
5
+ import Shape from './Shape.svelte';
6
+ import type { ShapeProps } from './types.js';
7
+
8
+ interface Props extends Omit<ShapeProps, 'x' | 'y' | 'anchor' | 'fill'> {
9
+ /**
10
+ * The ends: a point `[x, y]`, or a shape in the same `<Canvas>` by its id: `"card"` for the
11
+ * edge facing the other end, `"card.bottom"` for a point of its box, or
12
+ * `{ id, anchor, dx, dy }` to move the end.
13
+ */
14
+ from: End;
15
+ to: End;
16
+ /** Space between an end and the shape it is attached to. */
17
+ gap?: number;
18
+ /** Which ends get a head. */
19
+ heads?: ArrowHeads;
20
+ /** Length of a head along the line. Defaults to 4 times the stroke width. */
21
+ headLength?: number;
22
+ /** Width of a head across the line. Defaults to 0.8 times its length. */
23
+ headWidth?: number;
24
+ }
25
+
26
+ let {
27
+ from,
28
+ to,
29
+ gap,
30
+ heads = 'end',
31
+ headLength,
32
+ headWidth,
33
+ stroke = 'currentColor',
34
+ strokeWidth = 4,
35
+ strokeLinecap,
36
+ strokeLinejoin = 'round',
37
+ opacity,
38
+ progress = 1,
39
+ ...placement
40
+ }: Props = $props();
41
+
42
+ const ends = useEnds('Arrow', () => from, () => to, () => gap);
43
+ const paint = $derived(paintAttributes({ stroke, strokeWidth, strokeLinecap, opacity }));
44
+ const headPaint = $derived(paintAttributes({ fill: stroke, stroke, strokeWidth: 0, strokeLinejoin, opacity }));
45
+
46
+ // `progress` grows the arrow from the first end to the second, its head going along with the tip.
47
+ const arrow = $derived.by(() => {
48
+ if (!ends.points) return undefined;
49
+ const [start, finish] = ends.points;
50
+ const box = boundsOf([start, finish]);
51
+ const local = (p: Point): Point => [p[0] - box.x, p[1] - box.y];
52
+ const geometry = arrowGeometry(start, finish, { progress, strokeWidth, headLength, headWidth, heads });
53
+ return {
54
+ box,
55
+ shaft: geometry.shaft?.map(local),
56
+ heads: geometry.heads.map((head) => head.map(local))
57
+ };
58
+ });
59
+ </script>
60
+
61
+ {#if arrow}
62
+ <Shape x={arrow.box.x} y={arrow.box.y} width={arrow.box.width} height={arrow.box.height} {...placement}>
63
+ {#if arrow.shaft}
64
+ <line
65
+ x1={round(arrow.shaft[0][0])}
66
+ y1={round(arrow.shaft[0][1])}
67
+ x2={round(arrow.shaft[1][0])}
68
+ y2={round(arrow.shaft[1][1])}
69
+ {...paint}
70
+ />
71
+ {/if}
72
+ {#each arrow.heads as head}
73
+ <polygon points={pointsAttribute(head)} {...headPaint} />
74
+ {/each}
75
+ </Shape>
76
+ {/if}
@@ -0,0 +1,23 @@
1
+ import type { End } from './connect.js';
2
+ import { type ArrowHeads } from './geometry.js';
3
+ import type { ShapeProps } from './types.js';
4
+ interface Props extends Omit<ShapeProps, 'x' | 'y' | 'anchor' | 'fill'> {
5
+ /**
6
+ * The ends: a point `[x, y]`, or a shape in the same `<Canvas>` by its id: `"card"` for the
7
+ * edge facing the other end, `"card.bottom"` for a point of its box, or
8
+ * `{ id, anchor, dx, dy }` to move the end.
9
+ */
10
+ from: End;
11
+ to: End;
12
+ /** Space between an end and the shape it is attached to. */
13
+ gap?: number;
14
+ /** Which ends get a head. */
15
+ heads?: ArrowHeads;
16
+ /** Length of a head along the line. Defaults to 4 times the stroke width. */
17
+ headLength?: number;
18
+ /** Width of a head across the line. Defaults to 0.8 times its length. */
19
+ headWidth?: number;
20
+ }
21
+ declare const Arrow: import("svelte").Component<Props, {}, "">;
22
+ type Arrow = ReturnType<typeof Arrow>;
23
+ export default Arrow;
@@ -0,0 +1,29 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import { useVideo } from '@svelteframe/core';
4
+ import { setCanvas } from './registry.svelte.js';
5
+
6
+ interface Props {
7
+ class?: string;
8
+ style?: string;
9
+ children?: Snippet;
10
+ }
11
+
12
+ let { class: className, style = '', children }: Props = $props();
13
+
14
+ const video = useVideo();
15
+ setCanvas();
16
+ </script>
17
+
18
+ <!-- One user unit is one pixel of the composition, with the origin at the top-left. Like a
19
+ <Sequence>, it fills its nearest positioned parent. -->
20
+ <svg
21
+ class={className}
22
+ width={video.width}
23
+ height={video.height}
24
+ viewBox="0 0 {video.width} {video.height}"
25
+ style="position:absolute;inset:0;{style}"
26
+ aria-hidden="true"
27
+ >
28
+ {@render children?.()}
29
+ </svg>
@@ -0,0 +1,9 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ class?: string;
4
+ style?: string;
5
+ children?: Snippet;
6
+ }
7
+ declare const Canvas: import("svelte").Component<Props, {}, "">;
8
+ type Canvas = ReturnType<typeof Canvas>;
9
+ export default Canvas;
@@ -0,0 +1,22 @@
1
+ <script lang="ts">
2
+ import { assertSize, needsPathLength, paintAttributes } from './geometry.js';
3
+ import Shape from './Shape.svelte';
4
+ import type { ShapeProps } from './types.js';
5
+
6
+ interface Props extends ShapeProps {
7
+ radius: number;
8
+ }
9
+
10
+ let { radius, fill, stroke, strokeWidth, strokeLinecap, strokeLinejoin, opacity, progress, ...placement }: Props = $props();
11
+
12
+ const paint = $derived(paintAttributes({ fill, stroke, strokeWidth, strokeLinecap, strokeLinejoin, opacity, progress }));
13
+ const size = $derived.by(() => {
14
+ assertSize('radius', radius);
15
+ return radius * 2;
16
+ });
17
+ </script>
18
+
19
+ <!-- The box is the circle's bounding square, so `anchor="center"` is its centre. -->
20
+ <Shape width={size} height={size} outline={{ kind: 'ellipse' }} {...placement}>
21
+ <circle cx={radius} cy={radius} r={radius} pathLength={needsPathLength(progress) ? 1 : undefined} {...paint} />
22
+ </Shape>
@@ -0,0 +1,7 @@
1
+ import type { ShapeProps } from './types.js';
2
+ interface Props extends ShapeProps {
3
+ radius: number;
4
+ }
5
+ declare const Circle: import("svelte").Component<Props, {}, "">;
6
+ type Circle = ReturnType<typeof Circle>;
7
+ export default Circle;
@@ -0,0 +1,25 @@
1
+ <script lang="ts">
2
+ import { assertSize, needsPathLength, paintAttributes } from './geometry.js';
3
+ import Shape from './Shape.svelte';
4
+ import type { ShapeProps } from './types.js';
5
+
6
+ interface Props extends ShapeProps {
7
+ /** Horizontal radius. */
8
+ rx: number;
9
+ /** Vertical radius. */
10
+ ry: number;
11
+ }
12
+
13
+ let { rx, ry, fill, stroke, strokeWidth, strokeLinecap, strokeLinejoin, opacity, progress, ...placement }: Props = $props();
14
+
15
+ const paint = $derived(paintAttributes({ fill, stroke, strokeWidth, strokeLinecap, strokeLinejoin, opacity, progress }));
16
+ const size = $derived.by(() => {
17
+ assertSize('rx', rx);
18
+ assertSize('ry', ry);
19
+ return [rx * 2, ry * 2] as const;
20
+ });
21
+ </script>
22
+
23
+ <Shape width={size[0]} height={size[1]} outline={{ kind: 'ellipse' }} {...placement}>
24
+ <ellipse cx={rx} cy={ry} {rx} {ry} pathLength={needsPathLength(progress) ? 1 : undefined} {...paint} />
25
+ </Shape>
@@ -0,0 +1,10 @@
1
+ import type { ShapeProps } from './types.js';
2
+ interface Props extends ShapeProps {
3
+ /** Horizontal radius. */
4
+ rx: number;
5
+ /** Vertical radius. */
6
+ ry: number;
7
+ }
8
+ declare const Ellipse: import("svelte").Component<Props, {}, "">;
9
+ type Ellipse = ReturnType<typeof Ellipse>;
10
+ export default Ellipse;
@@ -0,0 +1,47 @@
1
+ <script lang="ts">
2
+ import type { End } from './connect.js';
3
+ import { useEnds } from './ends.svelte.js';
4
+ import { boundsOf, clampProgress, lerpPoint, paintAttributes, round } from './geometry.js';
5
+ import Shape from './Shape.svelte';
6
+ import type { ShapeProps } from './types.js';
7
+
8
+ interface Props extends Omit<ShapeProps, 'x' | 'y' | 'anchor' | 'fill'> {
9
+ /**
10
+ * The ends: a point `[x, y]`, or a shape in the same `<Canvas>` by its id: `"card"` for the
11
+ * edge facing the other end, `"card.bottom"` for a point of its box, or
12
+ * `{ id, anchor, dx, dy }` to move the end.
13
+ */
14
+ from: End;
15
+ to: End;
16
+ /** Space between an end and the shape it is attached to. */
17
+ gap?: number;
18
+ }
19
+
20
+ let { from, to, gap, stroke = 'currentColor', strokeWidth = 4, strokeLinecap, strokeLinejoin, opacity, progress = 1, ...placement }: Props = $props();
21
+
22
+ const ends = useEnds('Line', () => from, () => to, () => gap);
23
+ const paint = $derived(paintAttributes({ stroke, strokeWidth, strokeLinecap, strokeLinejoin, opacity }));
24
+
25
+ // `progress` draws the line from the first end towards the second.
26
+ const line = $derived.by(() => {
27
+ if (!ends.points) return undefined;
28
+ const [start, finish] = ends.points;
29
+ const end = lerpPoint(start, finish, clampProgress(progress));
30
+ return { start, end, box: boundsOf([start, finish]), visible: end[0] !== start[0] || end[1] !== start[1] };
31
+ });
32
+ </script>
33
+
34
+ <!-- Drawn relative to the box, which a <Canvas> places where the line is. -->
35
+ {#if line}
36
+ <Shape x={line.box.x} y={line.box.y} width={line.box.width} height={line.box.height} {...placement}>
37
+ {#if line.visible}
38
+ <line
39
+ x1={round(line.start[0] - line.box.x)}
40
+ y1={round(line.start[1] - line.box.y)}
41
+ x2={round(line.end[0] - line.box.x)}
42
+ y2={round(line.end[1] - line.box.y)}
43
+ {...paint}
44
+ />
45
+ {/if}
46
+ </Shape>
47
+ {/if}
@@ -0,0 +1,16 @@
1
+ import type { End } from './connect.js';
2
+ import type { ShapeProps } from './types.js';
3
+ interface Props extends Omit<ShapeProps, 'x' | 'y' | 'anchor' | 'fill'> {
4
+ /**
5
+ * The ends: a point `[x, y]`, or a shape in the same `<Canvas>` by its id: `"card"` for the
6
+ * edge facing the other end, `"card.bottom"` for a point of its box, or
7
+ * `{ id, anchor, dx, dy }` to move the end.
8
+ */
9
+ from: End;
10
+ to: End;
11
+ /** Space between an end and the shape it is attached to. */
12
+ gap?: number;
13
+ }
14
+ declare const Line: import("svelte").Component<Props, {}, "">;
15
+ type Line = ReturnType<typeof Line>;
16
+ export default Line;
@@ -0,0 +1,23 @@
1
+ <script lang="ts">
2
+ import { needsPathLength, paintAttributes, pointsAttribute, regularPolygon } from './geometry.js';
3
+ import Shape from './Shape.svelte';
4
+ import type { ShapeProps } from './types.js';
5
+
6
+ interface Props extends ShapeProps {
7
+ /** 3 for a triangle, 5 for a pentagon, and so on. */
8
+ sides: number;
9
+ /** Distance from the centre to a corner. One corner points up. */
10
+ radius: number;
11
+ }
12
+
13
+ let { sides, radius, fill, stroke, strokeWidth, strokeLinecap, strokeLinejoin = 'round', opacity, progress, ...placement }: Props = $props();
14
+
15
+ const paint = $derived(paintAttributes({ fill, stroke, strokeWidth, strokeLinecap, strokeLinejoin, opacity, progress }));
16
+ const vertices = $derived(regularPolygon(sides, radius));
17
+ const points = $derived(pointsAttribute(vertices));
18
+ </script>
19
+
20
+ <!-- The box is the circle the corners lie on, so `anchor="center"` is the polygon's centre. -->
21
+ <Shape width={radius * 2} height={radius * 2} outline={{ kind: 'polygon', points: vertices }} {...placement}>
22
+ <polygon {points} pathLength={needsPathLength(progress) ? 1 : undefined} {...paint} />
23
+ </Shape>
@@ -0,0 +1,10 @@
1
+ import type { ShapeProps } from './types.js';
2
+ interface Props extends ShapeProps {
3
+ /** 3 for a triangle, 5 for a pentagon, and so on. */
4
+ sides: number;
5
+ /** Distance from the centre to a corner. One corner points up. */
6
+ radius: number;
7
+ }
8
+ declare const Polygon: import("svelte").Component<Props, {}, "">;
9
+ type Polygon = ReturnType<typeof Polygon>;
10
+ export default Polygon;
@@ -0,0 +1,26 @@
1
+ <script lang="ts">
2
+ import { assertSize, needsPathLength, paintAttributes } from './geometry.js';
3
+ import Shape from './Shape.svelte';
4
+ import type { ShapeProps } from './types.js';
5
+
6
+ interface Props extends ShapeProps {
7
+ width: number;
8
+ height: number;
9
+ /** Corner radius; limited to half the shorter side. */
10
+ radius?: number;
11
+ }
12
+
13
+ let { width, height, radius = 0, fill, stroke, strokeWidth, strokeLinecap, strokeLinejoin, opacity, progress, ...placement }: Props = $props();
14
+
15
+ const paint = $derived(paintAttributes({ fill, stroke, strokeWidth, strokeLinecap, strokeLinejoin, opacity, progress }));
16
+ const corner = $derived.by(() => {
17
+ assertSize('width', width);
18
+ assertSize('height', height);
19
+ assertSize('radius', radius);
20
+ return Math.min(radius, width / 2, height / 2);
21
+ });
22
+ </script>
23
+
24
+ <Shape {width} {height} {...placement}>
25
+ <rect {width} {height} rx={corner || undefined} pathLength={needsPathLength(progress) ? 1 : undefined} {...paint} />
26
+ </Shape>
@@ -0,0 +1,10 @@
1
+ import type { ShapeProps } from './types.js';
2
+ interface Props extends ShapeProps {
3
+ width: number;
4
+ height: number;
5
+ /** Corner radius; limited to half the shorter side. */
6
+ radius?: number;
7
+ }
8
+ declare const Rect: import("svelte").Component<Props, {}, "">;
9
+ type Rect = ReturnType<typeof Rect>;
10
+ export default Rect;
@@ -0,0 +1,49 @@
1
+ <script lang="ts">
2
+ import { onDestroy, type Snippet } from 'svelte';
3
+ import { useCanvas } from './registry.svelte.js';
4
+ import type { Outline, ShapeGeometry } from './connect.js';
5
+ import { shapeTransform } from './geometry.js';
6
+ import type { ShapeProps } from './types.js';
7
+
8
+ /** Places a shape: in a `<Canvas>` as a group, otherwise as an element of its own. */
9
+ interface Props extends Pick<ShapeProps, 'id' | 'x' | 'y' | 'anchor' | 'rotate' | 'scale' | 'origin' | 'class' | 'style'> {
10
+ /** The size of the shape's box, before it is scaled. */
11
+ width: number;
12
+ height: number;
13
+ /** Where its edge is, for arrows that attach to it. Defaults to the box. */
14
+ outline?: Outline;
15
+ children: Snippet;
16
+ }
17
+
18
+ let { id, width, height, outline = { kind: 'box' }, x, y, anchor, rotate, scale, origin, class: className, style, children }: Props = $props();
19
+
20
+ const registry = useCanvas();
21
+ const canvas = registry !== undefined;
22
+
23
+ // Findable by id as soon as it exists, and as long as it does.
24
+ // svelte-ignore state_referenced_locally
25
+ if (registry && id !== undefined) {
26
+ const name = id;
27
+ const geometry = (): ShapeGeometry => ({ width, height, outline, placement: { x, y, anchor, rotate, scale, origin } });
28
+ registry.register(name, geometry);
29
+ onDestroy(() => registry.unregister(name, geometry));
30
+ }
31
+
32
+ // Elsewhere CSS positions the element, so only what the shape does to itself applies.
33
+ const transform = $derived(
34
+ shapeTransform({ width, height, anchor, rotate, scale, origin, ...(canvas ? { x, y } : {}) })
35
+ );
36
+ </script>
37
+
38
+ {#if canvas}
39
+ <g class={className} {style} transform={transform || undefined}>
40
+ {@render children()}
41
+ </g>
42
+ {:else}
43
+ <!-- Overflow is visible so strokes, rotation and scaling are not cut off by the box. -->
44
+ <svg class={className} {width} {height} viewBox="0 0 {width} {height}" style="display:block;overflow:visible;{style ?? ''}" aria-hidden="true">
45
+ <g transform={transform || undefined}>
46
+ {@render children()}
47
+ </g>
48
+ </svg>
49
+ {/if}
@@ -0,0 +1,15 @@
1
+ import { type Snippet } from 'svelte';
2
+ import type { Outline } from './connect.js';
3
+ import type { ShapeProps } from './types.js';
4
+ /** Places a shape: in a `<Canvas>` as a group, otherwise as an element of its own. */
5
+ interface Props extends Pick<ShapeProps, 'id' | 'x' | 'y' | 'anchor' | 'rotate' | 'scale' | 'origin' | 'class' | 'style'> {
6
+ /** The size of the shape's box, before it is scaled. */
7
+ width: number;
8
+ height: number;
9
+ /** Where its edge is, for arrows that attach to it. Defaults to the box. */
10
+ outline?: Outline;
11
+ children: Snippet;
12
+ }
13
+ declare const Shape: import("svelte").Component<Props, {}, "">;
14
+ type Shape = ReturnType<typeof Shape>;
15
+ export default Shape;
@@ -0,0 +1,24 @@
1
+ <script lang="ts">
2
+ import { needsPathLength, paintAttributes, pointsAttribute, star } from './geometry.js';
3
+ import Shape from './Shape.svelte';
4
+ import type { ShapeProps } from './types.js';
5
+
6
+ interface Props extends ShapeProps {
7
+ /** Number of points. */
8
+ points?: number;
9
+ /** Distance from the centre to the tips. One tip points up. */
10
+ outerRadius: number;
11
+ /** Distance from the centre to the notches. Defaults to 0.4 times the outer radius. */
12
+ innerRadius?: number;
13
+ }
14
+
15
+ let { points = 5, outerRadius, innerRadius, fill, stroke, strokeWidth, strokeLinecap, strokeLinejoin = 'round', opacity, progress, ...placement }: Props = $props();
16
+
17
+ const paint = $derived(paintAttributes({ fill, stroke, strokeWidth, strokeLinecap, strokeLinejoin, opacity, progress }));
18
+ const corners = $derived(star(points, outerRadius, innerRadius ?? outerRadius * 0.4));
19
+ const vertices = $derived(pointsAttribute(corners));
20
+ </script>
21
+
22
+ <Shape width={outerRadius * 2} height={outerRadius * 2} outline={{ kind: 'polygon', points: corners }} {...placement}>
23
+ <polygon points={vertices} pathLength={needsPathLength(progress) ? 1 : undefined} {...paint} />
24
+ </Shape>
@@ -0,0 +1,12 @@
1
+ import type { ShapeProps } from './types.js';
2
+ interface Props extends ShapeProps {
3
+ /** Number of points. */
4
+ points?: number;
5
+ /** Distance from the centre to the tips. One tip points up. */
6
+ outerRadius: number;
7
+ /** Distance from the centre to the notches. Defaults to 0.4 times the outer radius. */
8
+ innerRadius?: number;
9
+ }
10
+ declare const Star: import("svelte").Component<Props, {}, "">;
11
+ type Star = ReturnType<typeof Star>;
12
+ export default Star;
@@ -0,0 +1,25 @@
1
+ <script lang="ts">
2
+ import { assertSize, needsPathLength, paintAttributes, pointsAttribute, type Point } from './geometry.js';
3
+ import Shape from './Shape.svelte';
4
+ import type { ShapeProps } from './types.js';
5
+
6
+ interface Props extends ShapeProps {
7
+ width: number;
8
+ height: number;
9
+ }
10
+
11
+ let { width, height, fill, stroke, strokeWidth, strokeLinecap, strokeLinejoin = 'round', opacity, progress, ...placement }: Props = $props();
12
+
13
+ const paint = $derived(paintAttributes({ fill, stroke, strokeWidth, strokeLinecap, strokeLinejoin, opacity, progress }));
14
+ // Pointing up, with its base along the bottom of the box.
15
+ const corners = $derived.by((): Point[] => {
16
+ assertSize('width', width);
17
+ assertSize('height', height);
18
+ return [[width / 2, 0], [width, height], [0, height]];
19
+ });
20
+ const points = $derived(pointsAttribute(corners));
21
+ </script>
22
+
23
+ <Shape {width} {height} outline={{ kind: 'polygon', points: corners }} {...placement}>
24
+ <polygon {points} pathLength={needsPathLength(progress) ? 1 : undefined} {...paint} />
25
+ </Shape>
@@ -0,0 +1,8 @@
1
+ import type { ShapeProps } from './types.js';
2
+ interface Props extends ShapeProps {
3
+ width: number;
4
+ height: number;
5
+ }
6
+ declare const Triangle: import("svelte").Component<Props, {}, "">;
7
+ type Triangle = ReturnType<typeof Triangle>;
8
+ export default Triangle;
@@ -0,0 +1,73 @@
1
+ import { type Anchor, type Point, type TransformOptions } from './geometry.js';
2
+ /** The names an anchor can have, as written after the dot in `"card.bottom"`. */
3
+ export declare const ANCHORS: readonly Anchor[];
4
+ /** The shape of a shape's edge, in the shape's own coordinates. */
5
+ export type Outline =
6
+ /** The box. Also what shapes with no better outline fall back to; rounded corners are ignored. */
7
+ {
8
+ kind: 'box';
9
+ }
10
+ /** The ellipse that fills the box. */
11
+ | {
12
+ kind: 'ellipse';
13
+ } | {
14
+ kind: 'polygon';
15
+ points: readonly Point[];
16
+ };
17
+ /** What the `<Canvas>` knows about a shape that has an `id`. */
18
+ export interface ShapeGeometry {
19
+ /** The size of the shape's box, before it is scaled. */
20
+ width: number;
21
+ height: number;
22
+ outline: Outline;
23
+ /** Where the shape is on the canvas. */
24
+ placement: Omit<TransformOptions, 'width' | 'height'>;
25
+ }
26
+ /** What `from` and `to` of a line or arrow accept. */
27
+ export type End =
28
+ /** A point on the canvas. */
29
+ Point
30
+ /** `"id"`: the edge of the shape facing the other end. `"id.anchor"`: that point of its box. */
31
+ | string | {
32
+ id: string;
33
+ anchor?: Anchor; /** Moves the end, in canvas pixels. */
34
+ dx?: number;
35
+ dy?: number;
36
+ };
37
+ interface ShapeEnd {
38
+ kind: 'shape';
39
+ id: string;
40
+ anchor?: Anchor;
41
+ dx: number;
42
+ dy: number;
43
+ }
44
+ type ParsedEnd = {
45
+ kind: 'point';
46
+ point: Point;
47
+ } | ShapeEnd;
48
+ /** Read what `from` or `to` was given as. */
49
+ export declare function parseEnd(end: End): ParsedEnd;
50
+ /** The ids a shape can be found by must not contain a dot, which separates the anchor. */
51
+ export declare function assertShapeId(id: string): void;
52
+ /** A point of the shape's box, on the canvas. */
53
+ export declare function shapeAnchorPoint(shape: ShapeGeometry, anchor: Anchor): Point;
54
+ /**
55
+ * Where the line from the centre of `shape` towards `target` leaves its outline. When the target
56
+ * is inside the shape there is no edge to meet, and the centre is returned.
57
+ */
58
+ export declare function boundaryToward(shape: ShapeGeometry, target: Point): Point;
59
+ export interface ResolveOptions {
60
+ /** Finds a shape by id; `undefined` while there is none (yet). */
61
+ find: (id: string) => ShapeGeometry | undefined;
62
+ /** Space to leave between an end and the shape it is attached to, in canvas pixels. */
63
+ gap?: number;
64
+ }
65
+ export interface ResolvedEnds {
66
+ /** The two ends on the canvas, or `undefined` while a shape is missing. */
67
+ points?: readonly [Point, Point];
68
+ /** The ids that no shape has. */
69
+ missing: string[];
70
+ }
71
+ /** Turn the `from` and `to` of a line into points on the canvas. */
72
+ export declare function resolveEnds(from: End, to: End, { find, gap }: ResolveOptions): ResolvedEnds;
73
+ export {};