@redvars/peacock 3.9.0 → 3.9.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.
Files changed (83) hide show
  1. package/dist/code-highlighter.js +1 -4
  2. package/dist/code-highlighter.js.map +1 -1
  3. package/dist/custom-elements-jsdocs.json +39 -223
  4. package/dist/custom-elements.json +391 -2205
  5. package/dist/elevation.js +55 -2
  6. package/dist/elevation.js.map +1 -1
  7. package/dist/flow.js +762 -0
  8. package/dist/flow.js.map +1 -0
  9. package/dist/index.js +1 -3
  10. package/dist/index.js.map +1 -1
  11. package/dist/loader.js +2 -9
  12. package/dist/loader.js.map +1 -1
  13. package/dist/menu-item.js +144 -63
  14. package/dist/menu-item.js.map +1 -1
  15. package/dist/menu.js +161 -73
  16. package/dist/menu.js.map +1 -1
  17. package/dist/src/flow/flow.d.ts +102 -0
  18. package/dist/src/flow/index.d.ts +2 -0
  19. package/dist/src/index.d.ts +2 -4
  20. package/dist/src/menu/menu/menu.d.ts +10 -9
  21. package/dist/src/menu/menu-item/menu-item.d.ts +1 -3
  22. package/dist/src/timeline/timeline.d.ts +0 -1
  23. package/dist/sub-menu.js +28 -1
  24. package/dist/sub-menu.js.map +1 -1
  25. package/dist/timeline-item.js +22 -34
  26. package/dist/timeline-item.js.map +1 -1
  27. package/dist/timeline.js +1 -18
  28. package/dist/timeline.js.map +1 -1
  29. package/dist/tsconfig.tsbuildinfo +1 -1
  30. package/package.json +3 -2
  31. package/readme.md +2 -2
  32. package/src/code-highlighter/code-highlighter.scss +1 -7
  33. package/src/elevation/demo/index.html +52 -0
  34. package/src/elevation/elevation.scss +52 -5
  35. package/src/elevation/elevation.ts +8 -1
  36. package/src/flow/flow.scss +195 -0
  37. package/src/flow/flow.ts +683 -0
  38. package/src/flow/index.ts +2 -0
  39. package/src/index.ts +2 -16
  40. package/src/loader.ts +2 -9
  41. package/src/menu/menu/copy-component-files.mjs +47 -0
  42. package/src/menu/menu/menu.scss +55 -17
  43. package/src/menu/menu/menu.ts +155 -84
  44. package/src/menu/menu-item/menu-item-colors.scss +3 -3
  45. package/src/menu/menu-item/menu-item.scss +118 -24
  46. package/src/menu/menu-item/menu-item.ts +61 -55
  47. package/src/menu/sub-menu/sub-menu.scss +30 -1
  48. package/src/timeline/timeline-item.scss +21 -31
  49. package/src/timeline/timeline-item.ts +2 -1
  50. package/src/timeline/timeline.scss +0 -10
  51. package/src/timeline/timeline.ts +0 -2
  52. package/dist/canvas.js +0 -385
  53. package/dist/canvas.js.map +0 -1
  54. package/dist/flow-designer-node-CGSm6cUH.js +0 -553
  55. package/dist/flow-designer-node-CGSm6cUH.js.map +0 -1
  56. package/dist/flow-designer-node.js +0 -4
  57. package/dist/flow-designer-node.js.map +0 -1
  58. package/dist/flow-designer.js +0 -1417
  59. package/dist/flow-designer.js.map +0 -1
  60. package/dist/src/canvas/canvas.d.ts +0 -92
  61. package/dist/src/canvas/index.d.ts +0 -2
  62. package/dist/src/flow-designer/commands.d.ts +0 -66
  63. package/dist/src/flow-designer/flow-designer-node.d.ts +0 -46
  64. package/dist/src/flow-designer/flow-designer.d.ts +0 -133
  65. package/dist/src/flow-designer/index.d.ts +0 -7
  66. package/dist/src/flow-designer/layout.d.ts +0 -30
  67. package/dist/src/flow-designer/types.d.ts +0 -142
  68. package/dist/src/flow-designer/validation.d.ts +0 -43
  69. package/dist/src/flow-designer/workflow-utils.d.ts +0 -40
  70. package/src/canvas/canvas.scss +0 -72
  71. package/src/canvas/canvas.ts +0 -413
  72. package/src/canvas/index.ts +0 -2
  73. package/src/flow-designer/DEMO.md +0 -239
  74. package/src/flow-designer/commands.ts +0 -278
  75. package/src/flow-designer/flow-designer-node.ts +0 -172
  76. package/src/flow-designer/flow-designer.scss +0 -457
  77. package/src/flow-designer/flow-designer.ts +0 -611
  78. package/src/flow-designer/index.ts +0 -41
  79. package/src/flow-designer/layout.ts +0 -357
  80. package/src/flow-designer/types.ts +0 -166
  81. package/src/flow-designer/validation.ts +0 -284
  82. package/src/flow-designer/workflow-utils.ts +0 -282
  83. /package/dist/test/{flow-designer.test.d.ts → elevation.test.d.ts} +0 -0
@@ -1,142 +0,0 @@
1
- /**
2
- * Workflow Data Types for Flow Designer
3
- * Defines the structure for low-code business process workflows
4
- */
5
- /**
6
- * Supported node types in a workflow
7
- */
8
- export type NodeType = 'trigger' | 'action' | 'decision' | 'loop_start' | 'loop_end' | 'fork' | 'join';
9
- /**
10
- * Payload for workflow-changed event
11
- */
12
- export interface WorkflowChangeEvent {
13
- type: 'node-added' | 'node-deleted' | 'node-edited' | 'node-moved' | 'undo' | 'redo';
14
- nodeId?: string;
15
- workflow: Workflow;
16
- }
17
- /**
18
- * Core workflow node structure
19
- */
20
- export interface WorkflowNode {
21
- id: string;
22
- type: NodeType;
23
- label: string;
24
- description?: string;
25
- icon?: string;
26
- /**
27
- * For sequential nodes in parallel execution (fork tasks)
28
- */
29
- tasks?: WorkflowNode[];
30
- /**
31
- * For conditional branching (decision nodes)
32
- * Keys are branch conditions like "yes", "no"
33
- */
34
- branches?: Record<string, WorkflowNode[]>;
35
- /**
36
- * Child nodes for sequential flow
37
- */
38
- children?: WorkflowNode[];
39
- /**
40
- * For loop_end: points to the loop_start node id to repeat
41
- */
42
- target_id?: string;
43
- /**
44
- * For fork: join node that collects parallel paths
45
- */
46
- join?: WorkflowNode;
47
- /**
48
- * Custom metadata - can store domain-specific data
49
- */
50
- metadata?: Record<string, any>;
51
- }
52
- /**
53
- * Complete workflow definition
54
- */
55
- export interface Workflow {
56
- workflow_id: string;
57
- nodes: WorkflowNode;
58
- metadata?: Record<string, any>;
59
- }
60
- /**
61
- * Layout engine output - node with calculated position
62
- */
63
- export interface PositionedNode {
64
- node: WorkflowNode;
65
- x: number;
66
- y: number;
67
- width: number;
68
- height: number;
69
- lane: string;
70
- depth: number;
71
- branchPath?: string;
72
- parentId?: string;
73
- connectorPoints?: {
74
- from: {
75
- x: number;
76
- y: number;
77
- };
78
- to: {
79
- x: number;
80
- y: number;
81
- };
82
- type: 'straight' | 'curved' | 'branch' | 'fork' | 'join';
83
- }[];
84
- }
85
- /**
86
- * Validation error result
87
- */
88
- export interface ValidationError {
89
- nodeId: string;
90
- type: 'circular_loop' | 'orphaned_node' | 'invalid_branch' | 'missing_target' | 'invalid_fork_join';
91
- message: string;
92
- severity: 'error' | 'warning';
93
- }
94
- /**
95
- * State for a single undo/redo operation
96
- */
97
- export interface HistoryEntry {
98
- command: WorkflowCommand;
99
- workflow: Workflow;
100
- timestamp: number;
101
- }
102
- /**
103
- * Command interface for undo/redo pattern
104
- */
105
- export interface WorkflowCommand {
106
- execute(workflow: Workflow): Workflow;
107
- undo(workflow: Workflow): Workflow;
108
- description: string;
109
- }
110
- /**
111
- * Swimlane configuration
112
- */
113
- export interface SwimlaneConfig {
114
- id: string;
115
- name: string;
116
- nodes: PositionedNode[];
117
- isParallel: boolean;
118
- }
119
- /**
120
- * Editor state
121
- */
122
- export interface EditorState {
123
- selectedNodeId: string | null;
124
- isEditing: boolean;
125
- editingNode: Partial<WorkflowNode> | null;
126
- hoveredNodeId: string | null;
127
- isDragging: boolean;
128
- draggedNodeId: string | null;
129
- zoom: number;
130
- panX: number;
131
- panY: number;
132
- }
133
- /**
134
- * Node template configuration for slots
135
- */
136
- export interface NodeTemplate {
137
- type: NodeType;
138
- defaultIcon?: string;
139
- defaultHeight?: number;
140
- allowedChildren?: NodeType[];
141
- allowedBranches?: string[];
142
- }
@@ -1,43 +0,0 @@
1
- import type { Workflow, ValidationError } from './types.js';
2
- /**
3
- * Workflow validation - checks for common errors and inconsistencies
4
- */
5
- export declare class WorkflowValidator {
6
- /**
7
- * Validate entire workflow
8
- */
9
- static validate(workflow: Workflow): ValidationError[];
10
- /**
11
- * Detect circular loop references
12
- * A loop_end cannot point to a node that is its own descendant (after the loop_start)
13
- */
14
- private static _checkCircularLoops;
15
- /**
16
- * Check for orphaned nodes (not reachable from root)
17
- */
18
- private static _checkOrphanedNodes;
19
- /**
20
- * Check that decision nodes have valid branches
21
- */
22
- private static _checkValidBranches;
23
- /**
24
- * Check that loop_end nodes reference valid loop_start nodes
25
- */
26
- private static _checkMissingTargets;
27
- /**
28
- * Check that fork nodes have corresponding join nodes
29
- */
30
- private static _checkForkJoinPairs;
31
- /**
32
- * Check if workflow would create a valid execution path
33
- */
34
- static isExecutable(workflow: Workflow): boolean;
35
- /**
36
- * Get validation warnings only
37
- */
38
- static getWarnings(workflow: Workflow): ValidationError[];
39
- /**
40
- * Get validation errors only
41
- */
42
- static getErrors(workflow: Workflow): ValidationError[];
43
- }
@@ -1,40 +0,0 @@
1
- import type { Workflow, WorkflowNode } from './types.js';
2
- /**
3
- * Workflow utility functions for tree traversal and manipulation
4
- */
5
- /**
6
- * Deep clone a workflow to ensure immutability
7
- */
8
- export declare function cloneWorkflow(workflow: Workflow): Workflow;
9
- /**
10
- * Deep clone a workflow node
11
- */
12
- export declare function cloneNode(node: WorkflowNode): WorkflowNode;
13
- /**
14
- * Find a node by ID anywhere in the workflow tree
15
- */
16
- export declare function findNodeById(node: WorkflowNode, id: string): WorkflowNode | null;
17
- /**
18
- * Remove a node by ID from the workflow tree
19
- */
20
- export declare function removeNodeById(node: WorkflowNode, id: string): WorkflowNode;
21
- /**
22
- * Insert a node into the workflow tree at a specific location
23
- */
24
- export declare function insertNodeIntoWorkflow(parent: WorkflowNode, nodeToInsert: WorkflowNode, connectionType?: 'child' | 'branch' | 'task', branchKey?: string): void;
25
- /**
26
- * Collect all nodes in the workflow (depth-first)
27
- */
28
- export declare function getAllNodes(node: WorkflowNode): WorkflowNode[];
29
- /**
30
- * Get all parent node IDs for a given node (path from root to node)
31
- */
32
- export declare function getNodePath(rootNode: WorkflowNode, targetId: string): string[];
33
- /**
34
- * Check if a node is a descendant of another node
35
- */
36
- export declare function isDescendant(rootNode: WorkflowNode, potentialParentId: string, nodeId: string): boolean;
37
- /**
38
- * Replace a node in the tree
39
- */
40
- export declare function replaceNode(node: WorkflowNode, targetId: string, replacement: WorkflowNode): WorkflowNode;
@@ -1,72 +0,0 @@
1
- @use '../../scss/mixin';
2
-
3
- @include mixin.base-styles;
4
-
5
- :host {
6
- display: block;
7
- --canvas-background: var(--color-surface-container-low);
8
- --canvas-dot-color: var(--color-outline-variant);
9
- --canvas-line-color: var(--color-on-surface);
10
- --canvas-hover-color: var(--color-primary);
11
- --canvas-arrow-color: var(--color-on-surface);
12
- }
13
-
14
- .canvas-wrapper {
15
- position: relative;
16
- background: var(--canvas-background);
17
- }
18
-
19
- .canvas {
20
- display: block;
21
- }
22
-
23
- .canvas-background,
24
- .canvas-shapes {
25
- position: absolute;
26
- inset: 0;
27
- }
28
-
29
- .canvas-background {
30
- pointer-events: none;
31
- }
32
-
33
- #canvas-background circle {
34
- fill: var(--canvas-dot-color);
35
- }
36
-
37
- #endarrow polyline {
38
- fill: none;
39
- stroke: context-stroke;
40
- vector-effect: non-scaling-stroke;
41
- stroke-width: 2;
42
- stroke-linejoin: round;
43
- stroke-linecap: round;
44
- }
45
-
46
- .line {
47
- &.variant-dashed {
48
- stroke-dasharray: 6 6;
49
- }
50
-
51
- &.variant-animated-dashed {
52
- animation: canvas-dash-flow 600ms linear infinite;
53
- }
54
- }
55
-
56
- .clickable {
57
- cursor: pointer;
58
-
59
- &:hover .line {
60
- stroke: var(--canvas-hover-color);
61
- }
62
- }
63
-
64
- .line.clickable:hover {
65
- stroke: var(--canvas-hover-color);
66
- }
67
-
68
- @keyframes canvas-dash-flow {
69
- to {
70
- stroke-dashoffset: -12;
71
- }
72
- }
@@ -1,413 +0,0 @@
1
- import { html, LitElement, svg, nothing } from 'lit';
2
- import { property } from 'lit/decorators.js';
3
- import { classMap } from 'lit/directives/class-map.js';
4
- import IndividualComponent from '@/IndividualComponent.js';
5
- import styles from './canvas.scss';
6
-
7
- export type CanvasDirection = 'up' | 'down' | 'left' | 'right';
8
-
9
- export type CanvasStrokeVariant = 'solid' | 'dashed' | 'animated-dashed';
10
-
11
- export interface CanvasPoint {
12
- x: number;
13
- y: number;
14
- }
15
-
16
- export interface CanvasPathSegment {
17
- direction: CanvasDirection;
18
- length: number;
19
- }
20
-
21
- interface BaseCanvasShape {
22
- color?: string;
23
- }
24
-
25
- interface BaseCanvasStrokeShape extends BaseCanvasShape {
26
- variant?: CanvasStrokeVariant;
27
- showArrow?: boolean;
28
- clickable?: boolean;
29
- }
30
-
31
- export interface CanvasCircleShape extends BaseCanvasShape {
32
- type: 'circle';
33
- x?: number;
34
- y?: number;
35
- radius?: number;
36
- }
37
-
38
- export interface CanvasRectShape extends BaseCanvasShape {
39
- type: 'rect';
40
- x?: number;
41
- y?: number;
42
- width?: number;
43
- height?: number;
44
- }
45
-
46
- export interface CanvasLineShape extends BaseCanvasStrokeShape {
47
- type: 'line';
48
- start?: CanvasPoint;
49
- end?: CanvasPoint;
50
- }
51
-
52
- export interface CanvasConnectorShape extends BaseCanvasStrokeShape {
53
- type: 'connector';
54
- start?: CanvasPoint;
55
- path?: CanvasPathSegment[];
56
- }
57
-
58
- export type CanvasShape =
59
- | CanvasCircleShape
60
- | CanvasRectShape
61
- | CanvasLineShape
62
- | CanvasConnectorShape;
63
-
64
- const GRID_GAP = 10;
65
- const GRID_DOT_RADIUS = 1;
66
-
67
- interface CanvasBounds {
68
- x: number;
69
- y: number;
70
- width: number;
71
- height: number;
72
- }
73
-
74
- interface CanvasExtents {
75
- minX: number;
76
- minY: number;
77
- maxX: number;
78
- maxY: number;
79
- }
80
-
81
- interface CanvasViewBox {
82
- x: number;
83
- y: number;
84
- width: number;
85
- height: number;
86
- }
87
-
88
- /**
89
- * @label Canvas
90
- * @tag wc-canvas
91
- * @rawTag canvas
92
- * @summary A SVG canvas for drawing shapes, lines, and connectors on a dotted grid.
93
- *
94
- * @cssprop --canvas-background - Background color for the canvas wrapper. Defaults to surface-container-low.
95
- * @cssprop --canvas-dot-color - Color of the background grid dots. Defaults to outline-variant.
96
- * @cssprop --canvas-line-color - Default stroke color for lines and connectors. Defaults to on-surface.
97
- * @cssprop --canvas-hover-color - Stroke color on hover for clickable shapes. Defaults to primary.
98
- * @cssprop --canvas-arrow-color - Stroke color for arrow markers. Defaults to on-surface.
99
- *
100
- * @example
101
- * ```html
102
- * <wc-canvas id="my-canvas"></wc-canvas>
103
- * <script>
104
- * document.querySelector('#my-canvas').shapes = [
105
- * { type: 'circle', x: 0, y: 0, radius: 0.25, color: 'red' },
106
- * ];
107
- * </script>
108
- * ```
109
- */
110
- @IndividualComponent
111
- export class Canvas extends LitElement {
112
- static styles = [styles];
113
-
114
- /**
115
- * Array of shape objects to render on the canvas.
116
- */
117
- @property({ type: Array })
118
- shapes: CanvasShape[] = [];
119
-
120
- /**
121
- * Padding around the computed viewbox (in grid units).
122
- */
123
- @property({ type: Number, reflect: true })
124
- padding: number = 1;
125
-
126
- /**
127
- * Zoom multiplier for the canvas dimensions.
128
- */
129
- @property({ type: Number, reflect: true })
130
- zoom: number = 1;
131
-
132
- /**
133
- * Optional viewbox override string (e.g. "0 0 100 100").
134
- */
135
- @property({ type: String })
136
- viewbox?: string;
137
-
138
- private static getNextPoint(
139
- point: CanvasPoint,
140
- direction: CanvasDirection,
141
- length: number,
142
- ): CanvasPoint {
143
- if (direction === 'down') return { x: point.x, y: point.y + length };
144
- if (direction === 'up') return { x: point.x, y: point.y - length };
145
- if (direction === 'left') return { x: point.x - length, y: point.y };
146
- if (direction === 'right') return { x: point.x + length, y: point.y };
147
- return { x: point.x, y: point.y };
148
- }
149
-
150
- private static updateExtents(
151
- extents: CanvasExtents,
152
- x: number,
153
- y: number,
154
- ) {
155
- if (x < extents.minX) extents.minX = x;
156
- if (x > extents.maxX) extents.maxX = x;
157
- if (y < extents.minY) extents.minY = y;
158
- if (y > extents.maxY) extents.maxY = y;
159
- }
160
-
161
- private static getStrokeVariantClasses(variant?: CanvasStrokeVariant) {
162
- return {
163
- line: true,
164
- 'variant-dashed': variant === 'dashed' || variant === 'animated-dashed',
165
- 'variant-animated-dashed': variant === 'animated-dashed',
166
- };
167
- }
168
-
169
- private computeShapes(initialBounds: CanvasBounds) {
170
- // Track world-space bounds (grid units) as shapes are processed.
171
- const extents: CanvasExtents = {
172
- minX: initialBounds.x,
173
- minY: initialBounds.y,
174
- maxX: initialBounds.x + initialBounds.width,
175
- maxY: initialBounds.y + initialBounds.height,
176
- };
177
-
178
- const shapes = this.shapes.map(shape => {
179
- switch (shape.type) {
180
- case 'circle': {
181
- const r = shape.radius || 1;
182
- const cx = shape.x || 0;
183
- const cy = shape.y || 0;
184
- Canvas.updateExtents(extents, cx - r, cy - r);
185
- Canvas.updateExtents(extents, cx + r, cy + r);
186
-
187
- // Convert from grid units to SVG pixels using the fixed gap.
188
- return svg`<circle
189
- cx=${cx * GRID_GAP + GRID_DOT_RADIUS}
190
- cy=${cy * GRID_GAP + GRID_DOT_RADIUS}
191
- r=${r * GRID_GAP}
192
- fill=${shape.color || 'var(--canvas-line-color, var(--color-on-surface))'}
193
- />`;
194
- }
195
- case 'rect': {
196
- const w = shape.width || 1;
197
- const h = shape.height || 1;
198
- const rx = shape.x || 0;
199
- const ry = shape.y || 0;
200
- Canvas.updateExtents(extents, rx, ry);
201
- Canvas.updateExtents(extents, rx + w, ry + h);
202
-
203
- return svg`<rect
204
- x=${rx * GRID_GAP + GRID_DOT_RADIUS}
205
- y=${ry * GRID_GAP}
206
- width=${w * GRID_GAP + GRID_DOT_RADIUS}
207
- height=${h * GRID_GAP + GRID_DOT_RADIUS}
208
- fill=${shape.color || 'var(--canvas-line-color, var(--color-on-surface))'}
209
- />`;
210
- }
211
- case 'line': {
212
- const start = shape.start || { x: 0, y: 0 };
213
- const end = shape.end || { x: 0, y: 0 };
214
- Canvas.updateExtents(extents, start.x, start.y);
215
- Canvas.updateExtents(extents, end.x, end.y);
216
- const pathString = `M${start.x * GRID_GAP + GRID_DOT_RADIUS} ${start.y * GRID_GAP + GRID_DOT_RADIUS} L${end.x * GRID_GAP + GRID_DOT_RADIUS} ${end.y * GRID_GAP + GRID_DOT_RADIUS}`;
217
- const strokeColor =
218
- shape.color ||
219
- 'var(--canvas-line-color, var(--color-on-surface))';
220
-
221
- return svg`<path
222
- class=${classMap({
223
- ...Canvas.getStrokeVariantClasses(shape.variant),
224
- clickable: !!shape.clickable,
225
- })}
226
- stroke-width="2"
227
- stroke-linecap="round"
228
- stroke-linejoin="round"
229
- stroke=${strokeColor}
230
- marker-end=${shape.showArrow ? 'url(#endarrow)' : ''}
231
- d=${pathString}
232
- stroke-dasharray=${shape.variant === 'dashed' || shape.variant === 'animated-dashed' ? '6,6' : nothing}
233
- fill="none"
234
- />`;
235
- }
236
- case 'connector': {
237
- const start = shape.start || { x: 0, y: 0 };
238
- let pathString = `M${start.x * GRID_GAP + GRID_DOT_RADIUS} ${start.y * GRID_GAP + GRID_DOT_RADIUS}`;
239
- let current = { ...start };
240
- Canvas.updateExtents(extents, current.x, current.y);
241
-
242
- const pathSegments = shape.path || [];
243
- for (let i = 0; i < pathSegments.length; i += 1) {
244
- const path = pathSegments[i];
245
-
246
- if (i === 0) {
247
- // Move one unit first so curved corner joins don't overlap start.
248
- const point = Canvas.getNextPoint(current, path.direction, 1);
249
- pathString += ` L${point.x * GRID_GAP + GRID_DOT_RADIUS} ${point.y * GRID_GAP + GRID_DOT_RADIUS}`;
250
- current = { ...point };
251
- Canvas.updateExtents(extents, current.x, current.y);
252
- }
253
-
254
- const point = Canvas.getNextPoint(
255
- current,
256
- path.direction,
257
- path.length - 2,
258
- );
259
- pathString += ` L${point.x * GRID_GAP + GRID_DOT_RADIUS} ${point.y * GRID_GAP + GRID_DOT_RADIUS}`;
260
- current = { ...point };
261
- Canvas.updateExtents(extents, current.x, current.y);
262
-
263
- if (i === pathSegments.length - 1) {
264
- const endPoint = Canvas.getNextPoint(current, path.direction, 1);
265
- pathString += ` L${endPoint.x * GRID_GAP + GRID_DOT_RADIUS} ${endPoint.y * GRID_GAP + GRID_DOT_RADIUS}`;
266
- current = { ...endPoint };
267
- Canvas.updateExtents(extents, current.x, current.y);
268
- } else {
269
- const nextPath = pathSegments[i + 1];
270
- const midPoint = Canvas.getNextPoint(current, path.direction, 1);
271
- Canvas.updateExtents(extents, midPoint.x, midPoint.y);
272
- const nextPoint = Canvas.getNextPoint(
273
- midPoint,
274
- nextPath.direction,
275
- 1,
276
- );
277
- // Use a quadratic segment to round corners between directions.
278
- pathString += ` Q ${midPoint.x * GRID_GAP + GRID_DOT_RADIUS} ${midPoint.y * GRID_GAP + GRID_DOT_RADIUS} ${nextPoint.x * GRID_GAP + GRID_DOT_RADIUS} ${nextPoint.y * GRID_GAP + GRID_DOT_RADIUS}`;
279
- current = { ...nextPoint };
280
- Canvas.updateExtents(extents, current.x, current.y);
281
- }
282
- }
283
-
284
- const strokeColor =
285
- shape.color ||
286
- 'var(--canvas-line-color, var(--color-on-surface))';
287
-
288
- return svg`<g class=${classMap({ clickable: !!shape.clickable })}>
289
- <path
290
- class=${classMap({
291
- ...Canvas.getStrokeVariantClasses(shape.variant),
292
- })}
293
- stroke-width="2"
294
- stroke-linecap="round"
295
- stroke-linejoin="round"
296
- stroke=${strokeColor}
297
- marker-end=${shape.showArrow ? 'url(#endarrow)' : ''}
298
- d=${pathString}
299
- stroke-dasharray=${shape.variant === 'dashed' || shape.variant === 'animated-dashed' ? '6,6' : nothing}
300
- fill="none"
301
- />
302
- <path
303
- stroke-width="10"
304
- stroke-linecap="round"
305
- stroke-linejoin="round"
306
- stroke="transparent"
307
- d=${pathString}
308
- fill="none"
309
- />
310
- </g>`;
311
- }
312
- default:
313
- return nothing;
314
- }
315
- });
316
-
317
- // Expand bounds with padding so shapes are not flush to the edge.
318
- const computedViewbox = {
319
- x: extents.minX - this.padding,
320
- y: extents.minY - this.padding,
321
- width: Math.max(extents.maxX - extents.minX + this.padding * 2, 0),
322
- height: Math.max(extents.maxY - extents.minY + this.padding * 2, 0),
323
- };
324
-
325
- return { shapes, computedViewbox };
326
- }
327
-
328
- private renderBackgroundSvg(computedViewBox: CanvasViewBox, svgViewBox: string) {
329
- return html`
330
- <svg
331
- class="canvas canvas-background"
332
- height="100%"
333
- width="100%"
334
- viewBox=${svgViewBox}
335
- aria-hidden="true"
336
- >
337
- <defs>
338
- <pattern
339
- id="canvas-background"
340
- patternUnits="userSpaceOnUse"
341
- width=${GRID_GAP}
342
- height=${GRID_GAP}
343
- >
344
- <circle cx="1" cy="1" r=${GRID_DOT_RADIUS} />
345
- </pattern>
346
- </defs>
347
-
348
- <rect
349
- x=${computedViewBox.x * GRID_GAP}
350
- y=${computedViewBox.y * GRID_GAP}
351
- width="100%"
352
- height="100%"
353
- fill="url(#canvas-background)"
354
- />
355
- </svg>
356
- `;
357
- }
358
-
359
- private renderShapesSvg(shapes: unknown[], svgViewBox: string) {
360
- return html`
361
- <svg
362
- class="canvas canvas-shapes"
363
- height="100%"
364
- width="100%"
365
- viewBox=${svgViewBox}
366
- >
367
- <defs>
368
- <marker id="endarrow" markerWidth="10" markerHeight="10" refX="5" refY="5" markerUnits="strokeWidth" orient="auto">
369
- <polyline points="0 2, 5 5, 0 8"></polyline>
370
- </marker>
371
- </defs>
372
-
373
- ${shapes}
374
- </svg>
375
- `;
376
- }
377
-
378
- protected render() {
379
- let computedViewBox = { width: 0, height: 0, x: 0, y: 0 };
380
-
381
- const { shapes, computedViewbox } = this.computeShapes(computedViewBox);
382
- computedViewBox = computedViewbox;
383
-
384
- if (this.viewbox) {
385
- const viewBox = this.viewbox.split(' ');
386
- computedViewBox = {
387
- x: parseInt(viewBox[0], 10),
388
- y: parseInt(viewBox[1], 10),
389
- width: parseInt(viewBox[2], 10),
390
- height: parseInt(viewBox[3], 10),
391
- };
392
- }
393
-
394
- // Zoom scales the outer viewport size while the SVG viewBox stays in world units.
395
- const wrapperWidth =
396
- (computedViewBox.width * GRID_GAP + 2) * GRID_DOT_RADIUS * this.zoom;
397
- const wrapperHeight =
398
- (computedViewBox.height * GRID_GAP + 2) * GRID_DOT_RADIUS * this.zoom;
399
-
400
- // viewBox maps world-space extents into the internal SVG coordinate system.
401
- const svgViewBox = `${computedViewBox.x * GRID_GAP} ${computedViewBox.y * GRID_GAP} ${computedViewBox.width * GRID_GAP + 2 * GRID_DOT_RADIUS} ${computedViewBox.height * GRID_GAP + 2 * GRID_DOT_RADIUS}`;
402
-
403
- return html`
404
- <div
405
- class="canvas-wrapper"
406
- style="width: ${wrapperWidth}px; height: ${wrapperHeight}px;"
407
- >
408
- ${this.renderBackgroundSvg(computedViewBox, svgViewBox)}
409
- ${this.renderShapesSvg(shapes, svgViewBox)}
410
- </div>
411
- `;
412
- }
413
- }