@draftably/editor 0.3.0 → 0.5.0
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/README.md +5 -3
- package/dist/assets/engine.worker-BDrPGHQt.js +7634 -0
- package/dist/element.js +182 -17
- package/dist/index.js +8827 -7969
- package/dist/types/src/components/canvas/Canvas.d.ts +4 -0
- package/dist/types/src/components/scene/NodeView.d.ts +7 -0
- package/dist/types/src/components/scene/parts.d.ts +7 -11
- package/dist/types/src/components/session-context.d.ts +7 -1
- package/dist/types/src/dsl/cells.d.ts +1 -0
- package/dist/types/src/dsl/compile.d.ts +2 -1
- package/dist/types/src/dsl/legend-notes.d.ts +14 -0
- package/dist/types/src/dsl/syntax.d.ts +3 -1
- package/dist/types/src/dsl/translate.d.ts +1 -1
- package/dist/types/src/editor/css.d.ts +3 -0
- package/dist/types/src/editor/element.d.ts +8 -6
- package/dist/types/src/editor/internal.d.ts +486 -0
- package/dist/types/src/editor/mount.d.ts +77 -5
- package/dist/types/src/graph-api/core.d.ts +125 -0
- package/dist/types/src/model/appearance.d.ts +27 -1
- package/dist/types/src/model/colors.d.ts +10 -2
- package/dist/types/src/model/labels.d.ts +22 -6
- package/dist/types/src/runtime/anchors.d.ts +46 -0
- package/dist/types/src/runtime/camera.d.ts +41 -0
- package/dist/types/src/runtime/color-scheme.d.ts +15 -0
- package/dist/types/src/runtime/element-base.d.ts +40 -0
- package/dist/types/src/runtime/geometry.d.ts +72 -0
- package/dist/types/src/runtime/host.d.ts +78 -0
- package/dist/types/src/runtime/page-shared.d.ts +24 -0
- package/dist/types/src/runtime/policy.d.ts +7 -0
- package/dist/types/src/runtime/render.d.ts +10 -0
- package/dist/types/src/runtime/schemes.d.ts +2 -0
- package/dist/types/src/runtime/svg-renderer.d.ts +12 -0
- package/dist/types/src/runtime/types.d.ts +122 -0
- package/dist/types/src/runtime/version.d.ts +2 -0
- package/dist/types/src/runtime/viewer.d.ts +123 -0
- package/dist/types/src/state/active-session.d.ts +10 -1
- package/dist/types/src/state/dom-root.d.ts +8 -0
- package/dist/types/src/state/session/document.d.ts +3 -1
- package/dist/types/src/state/session/editor.d.ts +3 -2
- package/dist/types/src/state/session/emphasis.d.ts +3 -0
- package/dist/types/src/state/session/index.d.ts +13 -1
- package/package.json +1 -1
- package/dist/assets/engine.worker-BY9GKt7x.js +0 -7505
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { Point, RoutingSettings, Side } from '../model/types';
|
|
2
|
+
export type Shape = 'box' | 'pill' | 'circle' | 'diamond' | 'text';
|
|
3
|
+
/** How a scope arranges its children: a recipe, or a layout engine as `engine:<name>`. */
|
|
4
|
+
export type Arrange = 'flow' | 'row' | 'column' | 'lanes' | 'sequence' | `engine:${string}`;
|
|
5
|
+
/**
|
|
6
|
+
* How hard the router works. `full` repairs crossings and evens out lanes; `quick` routes each
|
|
7
|
+
* line once. `auto` (the default) routes quickly first, and fully when that will stay fast.
|
|
8
|
+
*/
|
|
9
|
+
export type Quality = 'auto' | 'full' | 'quick';
|
|
10
|
+
export interface RoutingOptions extends Partial<RoutingSettings> {
|
|
11
|
+
quality?: Quality;
|
|
12
|
+
}
|
|
13
|
+
export interface NodeIn {
|
|
14
|
+
id: string;
|
|
15
|
+
label?: string;
|
|
16
|
+
parent?: string;
|
|
17
|
+
shape?: Shape;
|
|
18
|
+
/** Fixed size, kept within 40–1200 by 24–1200. Without one, the box fits its label. */
|
|
19
|
+
width?: number;
|
|
20
|
+
height?: number;
|
|
21
|
+
}
|
|
22
|
+
export interface GroupIn {
|
|
23
|
+
id: string;
|
|
24
|
+
label?: string;
|
|
25
|
+
parent?: string;
|
|
26
|
+
/** How the group arranges its own children; `column` by default. */
|
|
27
|
+
arrange?: Arrange;
|
|
28
|
+
direction?: 'right' | 'down';
|
|
29
|
+
gap?: number;
|
|
30
|
+
}
|
|
31
|
+
export interface EdgeIn {
|
|
32
|
+
/** Unique among edges. Without one, the edge is `e1`, `e2`, … by position. */
|
|
33
|
+
id?: string;
|
|
34
|
+
source: string;
|
|
35
|
+
target: string;
|
|
36
|
+
label?: string;
|
|
37
|
+
dashed?: boolean;
|
|
38
|
+
sourceSide?: Side;
|
|
39
|
+
targetSide?: Side;
|
|
40
|
+
}
|
|
41
|
+
export interface GraphIn {
|
|
42
|
+
nodes: NodeIn[];
|
|
43
|
+
groups?: GroupIn[];
|
|
44
|
+
edges?: EdgeIn[];
|
|
45
|
+
/** How the top level is arranged; `flow` by default. */
|
|
46
|
+
arrange?: Arrange;
|
|
47
|
+
direction?: 'right' | 'down';
|
|
48
|
+
gap?: number;
|
|
49
|
+
fit?: 'wide' | 'slide' | 'square' | 'page' | 'tall';
|
|
50
|
+
/**
|
|
51
|
+
* `auto` (the default) folds a long `flow` into rows, like lines of text, when it is much
|
|
52
|
+
* wider than a 1100×720 page; `never` keeps one row.
|
|
53
|
+
*/
|
|
54
|
+
fold?: 'auto' | 'never';
|
|
55
|
+
routing?: RoutingOptions;
|
|
56
|
+
}
|
|
57
|
+
export interface Box {
|
|
58
|
+
x: number;
|
|
59
|
+
y: number;
|
|
60
|
+
width: number;
|
|
61
|
+
height: number;
|
|
62
|
+
}
|
|
63
|
+
export interface PlacedBox extends Box {
|
|
64
|
+
parent?: string;
|
|
65
|
+
}
|
|
66
|
+
export interface EdgeOut {
|
|
67
|
+
source: string;
|
|
68
|
+
target: string;
|
|
69
|
+
sourceSide: Side;
|
|
70
|
+
targetSide: Side;
|
|
71
|
+
/** From the source's outline to the target's. The host draws the arrowhead at the last point. */
|
|
72
|
+
points: Point[];
|
|
73
|
+
/** SVG path data with the rounded corners draftably draws. */
|
|
74
|
+
path: string;
|
|
75
|
+
/** The label's box. Draw a backing behind it: other lines may pass under it. */
|
|
76
|
+
label: Box | null;
|
|
77
|
+
/** True when the router found no clear way and drew a fallback line. */
|
|
78
|
+
blocked: boolean;
|
|
79
|
+
}
|
|
80
|
+
export interface Routed {
|
|
81
|
+
edges: Record<string, EdgeOut>;
|
|
82
|
+
/** The router mode that drew the lines. */
|
|
83
|
+
quality: 'full' | 'quick';
|
|
84
|
+
warnings: string[];
|
|
85
|
+
}
|
|
86
|
+
export interface LayoutResult extends Routed {
|
|
87
|
+
nodes: Record<string, PlacedBox>;
|
|
88
|
+
groups: Record<string, PlacedBox>;
|
|
89
|
+
/** Everything drawn: boxes, lines and labels. */
|
|
90
|
+
bounds: Box;
|
|
91
|
+
/** The `.draft` file this graph is. Open it in the viewer, or pass it to `renderSvg`. */
|
|
92
|
+
draft: string;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* `auto` routes fully when the quick routes predict a full pass within the budget. The full
|
|
96
|
+
* pass reroutes each line with a crossing or crowded lane against all the others, so its cost
|
|
97
|
+
* grows with lines times conflicts; this limit keeps it near the 1 s load budget.
|
|
98
|
+
*/
|
|
99
|
+
export declare const AUTO_FULL_LIMIT = 1200;
|
|
100
|
+
/** Place and route a graph the way a `.draft` file with the same content draws. */
|
|
101
|
+
export declare function layout(graph: GraphIn): Promise<LayoutResult>;
|
|
102
|
+
/**
|
|
103
|
+
* `layout` without a promise, for callers that cannot wait, such as a stand-in for a synchronous
|
|
104
|
+
* library. It cannot run a layout engine (`engine:` arrangements) or `fit`, which need `layout`.
|
|
105
|
+
*/
|
|
106
|
+
export declare function layoutSync(graph: GraphIn): LayoutResult;
|
|
107
|
+
export interface PlacedNodeIn extends Box {
|
|
108
|
+
id: string;
|
|
109
|
+
parent?: string;
|
|
110
|
+
shape?: Shape;
|
|
111
|
+
}
|
|
112
|
+
export interface PlacedGroupIn extends Box {
|
|
113
|
+
id: string;
|
|
114
|
+
parent?: string;
|
|
115
|
+
/** The title, so lines keep out of its band at the top of the group. */
|
|
116
|
+
label?: string;
|
|
117
|
+
}
|
|
118
|
+
export interface RouteIn {
|
|
119
|
+
nodes: PlacedNodeIn[];
|
|
120
|
+
groups?: PlacedGroupIn[];
|
|
121
|
+
edges: EdgeIn[];
|
|
122
|
+
routing?: RoutingOptions;
|
|
123
|
+
}
|
|
124
|
+
/** Route lines between boxes the caller placed. Nothing moves. */
|
|
125
|
+
export declare function route(graph: RouteIn): Routed;
|
|
@@ -9,7 +9,7 @@ import type { Footnote } from './footnote';
|
|
|
9
9
|
import type { Note } from './note';
|
|
10
10
|
import { type FontRole, type TypeProfile } from './fonts';
|
|
11
11
|
import type { FileConfig } from './link';
|
|
12
|
-
export type ThemeId = 'drafting' | 'fieldnotes' | 'signal' | 'ink' | 'blueprint';
|
|
12
|
+
export type ThemeId = 'drafting' | 'fieldnotes' | 'signal' | 'ink' | 'blueprint' | 'drafting-dark';
|
|
13
13
|
export declare const THEME_IDS: ThemeId[];
|
|
14
14
|
export declare const DEFAULT_THEME: ThemeId;
|
|
15
15
|
/** Shade stops. Lower numbers are lighter and higher numbers darker, in every theme. */
|
|
@@ -18,6 +18,12 @@ export type Shade = (typeof SHADES)[number];
|
|
|
18
18
|
export declare const isShade: (n: unknown) => n is Shade;
|
|
19
19
|
/** Shade of a node that names a color without a shade. */
|
|
20
20
|
export declare const NODE_SHADE: Shade;
|
|
21
|
+
/**
|
|
22
|
+
* Shade of a member card: a node drawn in its container's family with no shade of its own. It is
|
|
23
|
+
* the palest stop on the canvas side of the frame (50 in light themes, 950 in dark ones), so the
|
|
24
|
+
* card stands apart from its frame by fill and keeps the family's outline and ink.
|
|
25
|
+
*/
|
|
26
|
+
export declare const memberShadeFor: (theme: ThemeId) => Shade;
|
|
21
27
|
/**
|
|
22
28
|
* Shade of a group that names a color without a shade. Groups are quiet surfaces behind nodes, so
|
|
23
29
|
* this is a pale shade in light themes and a deep one in dark themes; the scale itself never flips.
|
|
@@ -148,6 +154,15 @@ interface Preset {
|
|
|
148
154
|
hues?: Partial<Record<string, [number, number]>>;
|
|
149
155
|
}
|
|
150
156
|
export declare const PRESETS: Record<ThemeId, Preset>;
|
|
157
|
+
/**
|
|
158
|
+
* The appearance to draw on a page of this scheme. The default theme follows the page, with or
|
|
159
|
+
* without a `theme drafting` line and its size and font overrides: dark draws it as
|
|
160
|
+
* `drafting-dark`. Another named theme, a `theme-color` line or a color override on the `theme`
|
|
161
|
+
* line keeps the file's colors on every page.
|
|
162
|
+
*/
|
|
163
|
+
export declare function forScheme(appearance: DiagramAppearance, dark: boolean): DiagramAppearance;
|
|
164
|
+
/** `a` moved toward `b` by `t` (0–1), in sRGB. */
|
|
165
|
+
export declare function mixColors(a: string, b: string, t: number): string;
|
|
151
166
|
export declare function contrast(a: string, b: string): number;
|
|
152
167
|
/** Contrast targets (WCAG ratios) every generated color meets. */
|
|
153
168
|
export declare const CONTRAST: {
|
|
@@ -184,6 +199,8 @@ export interface ResolvedAppearance {
|
|
|
184
199
|
radius: number;
|
|
185
200
|
shadow: boolean;
|
|
186
201
|
title: TypeProfile;
|
|
202
|
+
/** A node title's detail lines (after its first `\n`): a step smaller and lighter. */
|
|
203
|
+
detail: TypeProfile;
|
|
187
204
|
groupTitle: TypeProfile;
|
|
188
205
|
annotation: TypeProfile;
|
|
189
206
|
/** Space above a group's content; the group title sits in it. */
|
|
@@ -204,6 +221,15 @@ export declare function resolveAppearance(appearance?: DiagramAppearance): Resol
|
|
|
204
221
|
* default surface; the file parser reports them before they get here.
|
|
205
222
|
*/
|
|
206
223
|
export declare function swatchFor(r: ResolvedAppearance, family: string | undefined, shade: Shade | undefined, kind?: 'node' | 'group'): Swatch;
|
|
224
|
+
/**
|
|
225
|
+
* The quiet panel behind notes and a sequence diagram: slate 50 in light themes, and the theme's
|
|
226
|
+
* own group surface in dark ones, so the theme's text ink reads on it.
|
|
227
|
+
*/
|
|
228
|
+
export declare const panelSwatch: (r: ResolvedAppearance) => Swatch;
|
|
229
|
+
/** Bands and dividers drawn over that panel. */
|
|
230
|
+
export declare const bandSwatch: (r: ResolvedAppearance) => Swatch;
|
|
231
|
+
/** Ink for a link in a note or footnote, held to 4.5:1 on `against`. */
|
|
232
|
+
export declare const linkInk: (r: ResolvedAppearance, against: string) => string;
|
|
207
233
|
/** Shades a family defines, in numeric order. */
|
|
208
234
|
export declare const shadesOf: (r: ResolvedAppearance, family: string) => Shade[];
|
|
209
235
|
export {};
|
|
@@ -4,8 +4,11 @@
|
|
|
4
4
|
*
|
|
5
5
|
* - A container inside a container of the same family steps one shade toward emphasis per level
|
|
6
6
|
* (lighter frames underneath, smaller and stronger ones on top), instead of repeating the fill.
|
|
7
|
-
* - A shape
|
|
8
|
-
*
|
|
7
|
+
* - A member card: a shape in its container's family with no shade of its own draws as a pale
|
|
8
|
+
* card on the frame's tint (`memberShadeFor`), so a group of members reads as calm cards
|
|
9
|
+
* rather than a column of dark slabs. This holds while the frame is a quiet surface shade.
|
|
10
|
+
* - Any other shape whose shade is within two steps of its container's, in the same family, moves
|
|
11
|
+
* three steps past the container, so a member never melts into its frame.
|
|
9
12
|
*
|
|
10
13
|
* The canvas, the CLI's SVG and exports all draw through `drawnSwatch`.
|
|
11
14
|
*/
|
|
@@ -20,3 +23,8 @@ export declare function drawnSwatch(doc: Diagram, look: ResolvedAppearance, id:
|
|
|
20
23
|
* its top. Undefined for anything else, and for a participant with no color of its own.
|
|
21
24
|
*/
|
|
22
25
|
export declare function participantAccent(doc: Diagram, look: ResolvedAppearance, id: string): string | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* Ink for a title's detail lines on a swatch: the theme's muted ink on the neutral surface, or the
|
|
28
|
+
* swatch's own ink moved toward its fill on a colored one, held to 4.5:1 on the fill.
|
|
29
|
+
*/
|
|
30
|
+
export declare function detailInk(look: ResolvedAppearance, swatch: Swatch): string;
|
|
@@ -12,10 +12,25 @@ export interface TitleArea {
|
|
|
12
12
|
maxHeight: number;
|
|
13
13
|
}
|
|
14
14
|
export declare function titleArea(shape: ShapeKind, w: number, h: number, r: ResolvedAppearance, icon?: boolean): TitleArea;
|
|
15
|
-
/** Height a shape needs for `
|
|
16
|
-
export declare function titleHeight(shape: ShapeKind,
|
|
17
|
-
/**
|
|
18
|
-
export
|
|
15
|
+
/** Height a shape needs for a title block `text` px tall; never less than `base`. */
|
|
16
|
+
export declare function titleHeight(shape: ShapeKind, text: number, base: number, r: ResolvedAppearance): number;
|
|
17
|
+
/** One line of a shape's title: part of its name, or of its detail. */
|
|
18
|
+
export interface TitleLine {
|
|
19
|
+
text: string;
|
|
20
|
+
/** A detail line, from the text after the title's first `\n`, drawn in `r.detail`. */
|
|
21
|
+
detail: boolean;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* A shape's title in balanced lines no wider than `width` (see `wrapLines`). The text before the
|
|
25
|
+
* first `\n` is the name, in the title type; the rest is detail, in the smaller detail type.
|
|
26
|
+
*/
|
|
27
|
+
export declare function wrapTitle(text: string, width: number, r: ResolvedAppearance): TitleLine[];
|
|
28
|
+
/** The type a title line is set in. */
|
|
29
|
+
export declare const lineType: (l: TitleLine, r: ResolvedAppearance) => TypeProfile;
|
|
30
|
+
/** Height of a block of title lines. */
|
|
31
|
+
export declare const textHeight: (lines: TitleLine[], r: ResolvedAppearance) => number;
|
|
32
|
+
/** Each line's centre, measured from the middle of the block. */
|
|
33
|
+
export declare function lineOffsets(lines: TitleLine[], r: ResolvedAppearance): number[];
|
|
19
34
|
/**
|
|
20
35
|
* Line width for a diamond's title, `s` its side. A text block W wide and H tall fits inside a
|
|
21
36
|
* square diamond when W + H <= s, so this takes the widest measure whose wrapped lines still fit:
|
|
@@ -37,9 +52,10 @@ export interface NodeLabel {
|
|
|
37
52
|
y: number;
|
|
38
53
|
width: number;
|
|
39
54
|
height: number;
|
|
40
|
-
lines:
|
|
55
|
+
lines: TitleLine[];
|
|
56
|
+
/** Each line's centre, relative to `y`. */
|
|
57
|
+
offsets: number[];
|
|
41
58
|
mode: LabelMode;
|
|
42
|
-
lineHeight: number;
|
|
43
59
|
leader: boolean;
|
|
44
60
|
}
|
|
45
61
|
/** Where a shape's title is drawn, relative to the shape. */
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The anchor layer both handles share: one stable target element per anchored item, above the
|
|
3
|
+
* drawing. `body:<id>` targets sit in world space in one transformed layer, so they scale and clip
|
|
4
|
+
* with the node; `overlay:<id>` targets sit in screen space at the item's on-screen box. A target
|
|
5
|
+
* keeps its identity while its item exists, across updates and camera moves. The layer itself is
|
|
6
|
+
* never detached while the handle lives.
|
|
7
|
+
*/
|
|
8
|
+
import type { Diagnostic } from './types';
|
|
9
|
+
import { type Geometry } from './geometry';
|
|
10
|
+
/** A slot name. IDs come from source text, so the type cannot check them; diagnostics do. */
|
|
11
|
+
export type DraftSlot = `body:${string}` | `overlay:${string}`;
|
|
12
|
+
export type AnchorKind = 'node' | 'group' | 'edge';
|
|
13
|
+
export interface Anchor {
|
|
14
|
+
slot: DraftSlot;
|
|
15
|
+
id: string;
|
|
16
|
+
kind: AnchorKind;
|
|
17
|
+
/** The stable target. */
|
|
18
|
+
el: HTMLElement;
|
|
19
|
+
}
|
|
20
|
+
/** Below this zoom, bodies hide and titles show again (`detail: 'auto'`). */
|
|
21
|
+
export declare const LOW_DETAIL_ZOOM = 0.4;
|
|
22
|
+
export interface AnchorLayer {
|
|
23
|
+
readonly el: HTMLElement;
|
|
24
|
+
/** The slots the host wants. Returns true when the targets changed. */
|
|
25
|
+
setSlots(slots: readonly string[]): boolean;
|
|
26
|
+
/** Whether the host wants any slot. */
|
|
27
|
+
hasSlots(): boolean;
|
|
28
|
+
/** A new committed drawing. Returns true when the targets changed. */
|
|
29
|
+
setGeometry(geometry: Geometry | null): boolean;
|
|
30
|
+
/** The camera: screen = origin + world × zoom. Writes only; reads no layout. */
|
|
31
|
+
place(originX: number, originY: number, zoom: number): void;
|
|
32
|
+
anchors(): Anchor[];
|
|
33
|
+
/** IDs of nodes whose body is anchored, for hiding their titles. */
|
|
34
|
+
bodyIds(): string[];
|
|
35
|
+
/** Slot warnings: unknown IDs and content that overflows its box. */
|
|
36
|
+
warnings(): Diagnostic[];
|
|
37
|
+
/** A pointer press that began in the layer is still down: focus it moves is not a Tab. */
|
|
38
|
+
pressed(): boolean;
|
|
39
|
+
/** The target holding `node`, if any. */
|
|
40
|
+
idOf(node: Node): string | undefined;
|
|
41
|
+
destroy(): void;
|
|
42
|
+
}
|
|
43
|
+
export declare function createAnchorLayer(doc: Document, options?: {
|
|
44
|
+
detail?: 'auto' | 'full';
|
|
45
|
+
onOverflow?: () => void;
|
|
46
|
+
}): AnchorLayer;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One camera for the viewer and the editor: the world point at the viewport's centre and a zoom.
|
|
3
|
+
* Both handles frame and reveal through these functions, so the same call moves both the same way.
|
|
4
|
+
*/
|
|
5
|
+
import type { Box } from '../graph-api/core';
|
|
6
|
+
/** World units at the centre of the viewport. Independent of the viewport size. */
|
|
7
|
+
export interface Camera {
|
|
8
|
+
centerX: number;
|
|
9
|
+
centerY: number;
|
|
10
|
+
zoom: number;
|
|
11
|
+
}
|
|
12
|
+
export type CameraReason = 'user' | 'api' | 'fit' | 'resize' | 'restore';
|
|
13
|
+
/** `pending`: the viewport has no size yet; the latest call runs once it has one. */
|
|
14
|
+
export type MoveResult = 'moved' | 'unchanged' | 'missing' | 'pending';
|
|
15
|
+
export interface FrameOptions {
|
|
16
|
+
/** Screen pixels kept clear, for example under a host panel. Default 32. */
|
|
17
|
+
padding?: number | {
|
|
18
|
+
top: number;
|
|
19
|
+
right: number;
|
|
20
|
+
bottom: number;
|
|
21
|
+
left: number;
|
|
22
|
+
};
|
|
23
|
+
minZoom?: number;
|
|
24
|
+
/** Default 1 for `fit`, so a small diagram is not blown up. */
|
|
25
|
+
maxZoom?: number;
|
|
26
|
+
}
|
|
27
|
+
export declare const ZOOM_MIN = 0.02;
|
|
28
|
+
export declare const ZOOM_MAX = 8;
|
|
29
|
+
type View = {
|
|
30
|
+
width: number;
|
|
31
|
+
height: number;
|
|
32
|
+
};
|
|
33
|
+
export declare const clampZoom: (zoom: number, min?: number, max?: number) => number;
|
|
34
|
+
/** The union of boxes, or undefined for none. */
|
|
35
|
+
export declare function unionBox(boxes: readonly Box[]): Box | undefined;
|
|
36
|
+
/** The camera that shows `box`, centred in the padded viewport, at most `maxZoom` (default 1). */
|
|
37
|
+
export declare function fitCamera(box: Box, view: View, options?: FrameOptions): Camera;
|
|
38
|
+
/** The least move that brings `box` fully into the padded viewport; zooms out only if needed. */
|
|
39
|
+
export declare function revealCamera(camera: Camera, box: Box, view: View, options?: FrameOptions): Camera;
|
|
40
|
+
export declare const sameCamera: (a: Camera, b: Camera) => boolean;
|
|
41
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Watches one host's scheme. `check` reads it again now; `stop` ends the watch. */
|
|
2
|
+
export interface SchemeWatch {
|
|
3
|
+
check(): void;
|
|
4
|
+
stop(): void;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Calls `onChange(dark)` now and whenever the scheme in `host` changes: an element, or a shadow
|
|
8
|
+
* root, whose children inherit the element's scheme. A hidden probe in `host` has a `light-dark()`
|
|
9
|
+
* color, so a scheme change anywhere above it (a class flip on the page, a style) starts a
|
|
10
|
+
* transition there; the media query covers a change of system setting under `light dark`. A
|
|
11
|
+
* transition cannot run while the host has no box (a hidden tab, a closed `<details>`, a detached
|
|
12
|
+
* mount), so a resize observer on the host reads the scheme again when it gets one. Callers also
|
|
13
|
+
* call `check` after each commit.
|
|
14
|
+
*/
|
|
15
|
+
export declare function watchColorScheme(host: Element | ShadowRoot, onChange: (dark: boolean) => void): SchemeWatch;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** Helpers both custom elements share. No side effects. */
|
|
2
|
+
import { type HostEvents } from './host';
|
|
3
|
+
import type { Anchor } from './anchors';
|
|
4
|
+
import type { UpdateResult } from './viewer';
|
|
5
|
+
/** Structural equality for option values. Functions are read live, so any two are equal. */
|
|
6
|
+
export declare function sameValue(a: unknown, b: unknown): boolean;
|
|
7
|
+
/** Take a property a framework set before the element class was defined. */
|
|
8
|
+
export declare function takeEarly<T>(el: HTMLElement, key: string): T | undefined;
|
|
9
|
+
/** Warn once when another copy of the runtime already defined `name`. */
|
|
10
|
+
export declare function warnOtherCopy(name: string): void;
|
|
11
|
+
/** Re-dispatch a handle event on the element. Returns false when the host prevented it. */
|
|
12
|
+
export declare function redispatch<K extends keyof HostEvents>(el: HTMLElement, type: K, detail: HostEvents[K]): boolean;
|
|
13
|
+
/** The update result for an element whose handle could not start. */
|
|
14
|
+
export declare function mountFailure(revision: number, error: unknown, fallback: string): Extract<UpdateResult, {
|
|
15
|
+
status: 'error';
|
|
16
|
+
}>;
|
|
17
|
+
/**
|
|
18
|
+
* One constructable stylesheet per element type and page, shared by every instance. It needs no
|
|
19
|
+
* nonce under a strict CSP. Undefined where the browser cannot adopt sheets.
|
|
20
|
+
*/
|
|
21
|
+
export declare function sharedSheet(doc: Document, key: string, css: string): CSSStyleSheet | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* Re-dispatch a handle event whose payload may carry `preventDefault()`: the DOM event is
|
|
24
|
+
* cancelable instead, and a host that cancels it cancels Draftably's default.
|
|
25
|
+
*/
|
|
26
|
+
export declare function relay<K extends keyof HostEvents>(el: HTMLElement, type: K, event: HostEvents[K] | (HostEvents[K] & {
|
|
27
|
+
preventDefault(): void;
|
|
28
|
+
})): undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Manual slot assignment for an element's children. A child marked `data-draft-slot="body:api"`
|
|
31
|
+
* goes into the target for that slot; other children go to `fallback`. The element never writes
|
|
32
|
+
* to the host's DOM: it only calls `slot.assign()`.
|
|
33
|
+
*/
|
|
34
|
+
export declare function createSlotting(host: HTMLElement, fallback: HTMLSlotElement | null, onSlots: (names: string[]) => void): {
|
|
35
|
+
start(): void;
|
|
36
|
+
stop(): void;
|
|
37
|
+
/** The handle's targets changed: put each slot's children in its target. */
|
|
38
|
+
setAnchors(next: readonly Anchor[]): void;
|
|
39
|
+
names: () => string[];
|
|
40
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Geometry as hosts see it: the graph API's shapes (`Box`, `PlacedBox`, `EdgeOut`) in world units,
|
|
3
|
+
* built from the boxes and routes a drawing uses. `renderSvg`, the viewer, the editor and the graph
|
|
4
|
+
* API all build it here, so their numbers agree.
|
|
5
|
+
*/
|
|
6
|
+
import type { Box, EdgeOut, PlacedBox } from '../graph-api/core';
|
|
7
|
+
import type { Diagram, Point, Rect, Side } from '../model/types';
|
|
8
|
+
export type { Box, EdgeOut, PlacedBox };
|
|
9
|
+
/** The graph API's shapes, in world units, for the drawing now committed. */
|
|
10
|
+
export interface Geometry {
|
|
11
|
+
revision: number;
|
|
12
|
+
/** Everything drawn: heading, legend, notes, footnotes. */
|
|
13
|
+
frame: Box;
|
|
14
|
+
/** Shapes, lines and labels only. */
|
|
15
|
+
bounds: Box;
|
|
16
|
+
nodes: Record<string, PlacedBox>;
|
|
17
|
+
groups: Record<string, PlacedBox>;
|
|
18
|
+
/** The routes as drawn. */
|
|
19
|
+
edges: Record<string, EdgeOut>;
|
|
20
|
+
}
|
|
21
|
+
/** The route fields geometry reads; `computeRoutes` returns more. */
|
|
22
|
+
export interface DrawnRoute {
|
|
23
|
+
id: string;
|
|
24
|
+
from: string;
|
|
25
|
+
to: string;
|
|
26
|
+
fromSide: Side;
|
|
27
|
+
toSide: Side;
|
|
28
|
+
points: Point[];
|
|
29
|
+
path: string;
|
|
30
|
+
label?: {
|
|
31
|
+
x: number;
|
|
32
|
+
y: number;
|
|
33
|
+
w: number;
|
|
34
|
+
h: number;
|
|
35
|
+
} | null;
|
|
36
|
+
blocked?: boolean;
|
|
37
|
+
}
|
|
38
|
+
export declare function boundsOf(boxes: readonly Box[]): Box;
|
|
39
|
+
/** An edge's box: its route's points and its label. */
|
|
40
|
+
export declare const edgeBox: (e: EdgeOut) => Box;
|
|
41
|
+
/**
|
|
42
|
+
* Every visible node, group and edge of a projected document, under the caller's IDs. `rects`
|
|
43
|
+
* holds the visible objects' boxes; `routes` the lines as drawn.
|
|
44
|
+
*/
|
|
45
|
+
export declare function placedItems(doc: Diagram, rects: ReadonlyMap<string, Rect>, routes: readonly DrawnRoute[], objectId?: (id: string) => string, edgeId?: (id: string) => string): {
|
|
46
|
+
nodes: {
|
|
47
|
+
[k: string]: PlacedBox;
|
|
48
|
+
};
|
|
49
|
+
groups: {
|
|
50
|
+
[k: string]: PlacedBox;
|
|
51
|
+
};
|
|
52
|
+
edges: {
|
|
53
|
+
[k: string]: {
|
|
54
|
+
source: string;
|
|
55
|
+
target: string;
|
|
56
|
+
sourceSide: Side;
|
|
57
|
+
targetSide: Side;
|
|
58
|
+
points: Point[];
|
|
59
|
+
path: string;
|
|
60
|
+
label: {
|
|
61
|
+
x: number;
|
|
62
|
+
y: number;
|
|
63
|
+
width: number;
|
|
64
|
+
height: number;
|
|
65
|
+
} | null;
|
|
66
|
+
blocked: boolean;
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
bounds: Box;
|
|
70
|
+
};
|
|
71
|
+
/** The box of any item in a geometry, or undefined. */
|
|
72
|
+
export declare function itemBox(g: Geometry, id: string): Box | undefined;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The host contract both elements share: the events they dispatch, as plain data. Framework
|
|
3
|
+
* packages loop over `HOST_EVENTS`, so a new event adds no lines to any of them.
|
|
4
|
+
* This module has no side effects and no runtime imports.
|
|
5
|
+
*/
|
|
6
|
+
import type { RenderFailure } from './types';
|
|
7
|
+
import type { UpdateResult, ViewerEvents } from './viewer';
|
|
8
|
+
export type { Camera, CameraReason, FrameOptions, MoveResult } from './camera';
|
|
9
|
+
export type { Box, EdgeOut, Geometry, PlacedBox } from './geometry';
|
|
10
|
+
export type { Anchor, DraftSlot } from './anchors';
|
|
11
|
+
export interface HostEvents {
|
|
12
|
+
/** After each commit, after the marked children change, and with `[]` before destroy. */
|
|
13
|
+
anchors: ViewerEvents['anchors'];
|
|
14
|
+
/**
|
|
15
|
+
* Each update's result, from the first source on: committed, error or a cancellation. The
|
|
16
|
+
* editor's commit has no `value` or `diagnostics` (see `EditorUpdate`).
|
|
17
|
+
*/
|
|
18
|
+
update: UpdateResult | EditorUpdate;
|
|
19
|
+
render: ViewerEvents['render'];
|
|
20
|
+
diagnostics: ViewerEvents['diagnostics'];
|
|
21
|
+
camera: ViewerEvents['camera'];
|
|
22
|
+
itemactivate: ViewerEvents['itemactivate'];
|
|
23
|
+
/** Cancelable: `preventDefault()` on the DOM event stops Draftably opening the link. */
|
|
24
|
+
link: Omit<ViewerEvents['link'], 'preventDefault'>;
|
|
25
|
+
/** Cancelable: `preventDefault()` on the DOM event stops the browser's menu. */
|
|
26
|
+
contextmenu: Omit<ViewerEvents['contextmenu'], 'preventDefault'>;
|
|
27
|
+
/** The editor only: the selection changed, by the user or through `select()`. */
|
|
28
|
+
select: {
|
|
29
|
+
ids: readonly string[];
|
|
30
|
+
reason: 'user' | 'api';
|
|
31
|
+
};
|
|
32
|
+
/** The editor only: the source after a kept edit, undo or redo. */
|
|
33
|
+
change: {
|
|
34
|
+
source: string;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* `<draft-editor>`'s update. The editor draws in its own session, not as one SVG, so a commit
|
|
39
|
+
* has no `value` and no `diagnostics`: the `diagnostics` event has them. A source that does not
|
|
40
|
+
* parse is an `error` with a `DRAFT_PARSE` diagnostic; the editor keeps the text and shows it.
|
|
41
|
+
*/
|
|
42
|
+
export type EditorUpdate = {
|
|
43
|
+
status: 'committed';
|
|
44
|
+
revision: number;
|
|
45
|
+
value?: never;
|
|
46
|
+
diagnostics?: never;
|
|
47
|
+
} | {
|
|
48
|
+
status: 'error';
|
|
49
|
+
revision: number;
|
|
50
|
+
error: RenderFailure;
|
|
51
|
+
} | {
|
|
52
|
+
status: 'destroyed';
|
|
53
|
+
revision: number;
|
|
54
|
+
};
|
|
55
|
+
export type HostEventName = keyof HostEvents;
|
|
56
|
+
/** Every event, in one list. Each element dispatches these as bubbling `draft-<name>` events. */
|
|
57
|
+
export declare const HOST_EVENTS: readonly ["anchors", "update", "render", "diagnostics", "camera", "itemactivate", "link", "contextmenu", "select", "change"];
|
|
58
|
+
/** Events a host can cancel with `preventDefault()`. */
|
|
59
|
+
export type CancelableEventName = 'link' | 'contextmenu';
|
|
60
|
+
export declare const CANCELABLE_EVENTS: readonly HostEventName[];
|
|
61
|
+
/**
|
|
62
|
+
* What a framework event prop receives: the event's `detail`, and on a cancelable event a
|
|
63
|
+
* `preventDefault()` that cancels the DOM event, so `onLink={e => e.preventDefault()}` works.
|
|
64
|
+
*/
|
|
65
|
+
export type HostEventDetail<K extends HostEventName> = K extends CancelableEventName ? HostEvents[K] & {
|
|
66
|
+
preventDefault(): void;
|
|
67
|
+
} : HostEvents[K];
|
|
68
|
+
/** The value a framework event prop receives for a `draft-<name>` event. */
|
|
69
|
+
export declare function hostDetail(event: Event): unknown;
|
|
70
|
+
export type DraftEventMap = {
|
|
71
|
+
[K in HostEventName as `draft-${K}`]: CustomEvent<HostEvents[K]>;
|
|
72
|
+
};
|
|
73
|
+
/** The element's `status` attribute. */
|
|
74
|
+
export type DraftStatus = 'loading' | 'ready' | 'error';
|
|
75
|
+
declare global {
|
|
76
|
+
interface HTMLElementEventMap extends DraftEventMap {
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What every viewer on a page shares: a small pool of render workers and one font registry. A
|
|
3
|
+
* page with 20 diagrams starts at most `POOL_SIZE` workers and loads each font face once.
|
|
4
|
+
*/
|
|
5
|
+
import type { RenderOptions, RenderResult } from './types';
|
|
6
|
+
/** Names the worker and the usual fix, so "worker failed" is not a dead end. */
|
|
7
|
+
export declare const WORKER_HELP = "The worker is the module next to @draftably/runtime's viewer. With Vite 5\u20137, add optimizeDeps: { exclude: ['@draftably/runtime', '@draftably/editor'] } to vite.config.";
|
|
8
|
+
/**
|
|
9
|
+
* Render in the page's worker pool. Jobs queue on the main thread; an aborted job leaves the
|
|
10
|
+
* queue before a worker sees it. A job that runs past `timeoutMs` stops its worker only.
|
|
11
|
+
*/
|
|
12
|
+
export declare function renderInWorker(source: string, options: RenderOptions, { create, timeoutMs, signal, }: {
|
|
13
|
+
create?: () => Worker;
|
|
14
|
+
timeoutMs?: number;
|
|
15
|
+
signal?: AbortSignal;
|
|
16
|
+
}): Promise<RenderResult>;
|
|
17
|
+
/** The page's registered face for `key`, if any. */
|
|
18
|
+
export declare const sharedFace: (doc: Document, key: string) => FontFace | undefined;
|
|
19
|
+
/** Take a reference to `face` under `key`, adding it to the page on first use. */
|
|
20
|
+
export declare function retainFace(doc: Document, key: string, face: FontFace): undefined;
|
|
21
|
+
/** Drop a reference; the last one removes the face from the page. */
|
|
22
|
+
export declare function releaseFace(doc: Document, key: string): void;
|
|
23
|
+
/** Tests only: stop every worker and forget every face, as a new page would. */
|
|
24
|
+
export declare function resetPageShared(): void;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Diagram } from '../model/types';
|
|
2
|
+
import type { Diagnostic, RenderOptions } from './types';
|
|
3
|
+
export declare function assertOptions(options: RenderOptions): void;
|
|
4
|
+
/** A link in note or footnote text: kept only as a safe HTTP(S) URL under a safe policy. */
|
|
5
|
+
export declare const textLink: (options: RenderOptions) => (href: string) => string | null;
|
|
6
|
+
/** The host resolves first; final validation never trusts the callback or authored link-base. */
|
|
7
|
+
export declare function applyLinks(doc: Diagram, options: RenderOptions): Diagnostic[];
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type Scene } from '../dsl/compile';
|
|
2
|
+
import type { RenderOptions, RenderResult, ValidateOptions, ValidationResult } from './types';
|
|
3
|
+
export interface InternalRender {
|
|
4
|
+
result: RenderResult;
|
|
5
|
+
scene?: Scene;
|
|
6
|
+
}
|
|
7
|
+
/** Internal bridge for repository callers that need the editable Scene alongside public output. */
|
|
8
|
+
export declare function renderWithScene(source: string, options?: RenderOptions): Promise<InternalRender>;
|
|
9
|
+
export declare function renderSvg(source: string, options?: RenderOptions): Promise<RenderResult>;
|
|
10
|
+
export declare function validate(source: string, options?: ValidateOptions): Promise<ValidationResult>;
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* same appearance, shapes and title wrapping as the canvas and embeds the fonts it uses.
|
|
4
4
|
*/
|
|
5
5
|
import { type ResolvedAppearance } from '../model/appearance';
|
|
6
|
+
import { computeRoutes } from '../model/routes';
|
|
6
7
|
import type { Diagram, Rect } from '../model/types';
|
|
7
8
|
/** How an object's `href` is written into the file; as written unless the caller says. */
|
|
8
9
|
export interface SvgOptions {
|
|
@@ -20,9 +21,20 @@ export interface SvgOptions {
|
|
|
20
21
|
linkTarget?: '_self' | '_blank';
|
|
21
22
|
metadata?: string;
|
|
22
23
|
maxOutputBytes?: number;
|
|
24
|
+
/** Receives the boxes and routes of a one-panel drawing, as drawn (not for compare or states). */
|
|
25
|
+
onDrawn?: (drawn: {
|
|
26
|
+
rects: Map<string, Rect>;
|
|
27
|
+
routes: Route[];
|
|
28
|
+
}) => void;
|
|
29
|
+
/**
|
|
30
|
+
* The routes `onDrawn` gave for this document, so drawing it again in other colors routes
|
|
31
|
+
* nothing again (not for compare or states).
|
|
32
|
+
*/
|
|
33
|
+
routes?: Route[];
|
|
23
34
|
}
|
|
24
35
|
export declare class SvgLimitError extends Error {
|
|
25
36
|
}
|
|
37
|
+
export type Route = ReturnType<typeof computeRoutes>['routes'][number];
|
|
26
38
|
/**
|
|
27
39
|
* A sequence diagram's panel, a light surface with a border that bounds the exchange, and a row
|
|
28
40
|
* band for each phase with its label in a tab at the left edge. Drawn under everything else.
|