@redvars/peacock 3.8.6 → 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.
- package/dist/code-highlighter.js +1 -4
- package/dist/code-highlighter.js.map +1 -1
- package/dist/custom-elements-jsdocs.json +93 -213
- package/dist/custom-elements.json +488 -2149
- package/dist/elevation.js +55 -2
- package/dist/elevation.js.map +1 -1
- package/dist/flow.js +762 -0
- package/dist/flow.js.map +1 -0
- package/dist/index.js +3 -3
- package/dist/loader.js +10 -9
- package/dist/loader.js.map +1 -1
- package/dist/menu-item.js +144 -63
- package/dist/menu-item.js.map +1 -1
- package/dist/menu.js +161 -73
- package/dist/menu.js.map +1 -1
- package/dist/src/flow/flow.d.ts +102 -0
- package/dist/src/flow/index.d.ts +2 -0
- package/dist/src/index.d.ts +3 -4
- package/dist/src/menu/menu/menu.d.ts +10 -9
- package/dist/src/menu/menu-item/menu-item.d.ts +1 -3
- package/dist/src/timeline/index.d.ts +2 -0
- package/dist/src/timeline/timeline-item.d.ts +23 -0
- package/dist/src/timeline/timeline.d.ts +26 -0
- package/dist/sub-menu.js +28 -1
- package/dist/sub-menu.js.map +1 -1
- package/dist/timeline-item.js +163 -0
- package/dist/timeline-item.js.map +1 -0
- package/dist/timeline.js +52 -0
- package/dist/timeline.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -2
- package/readme.md +2 -2
- package/src/code-highlighter/code-highlighter.scss +1 -7
- package/src/elevation/demo/index.html +52 -0
- package/src/elevation/elevation.scss +52 -5
- package/src/elevation/elevation.ts +8 -1
- package/src/flow/flow.scss +195 -0
- package/src/flow/flow.ts +683 -0
- package/src/flow/index.ts +2 -0
- package/src/index.ts +3 -16
- package/src/loader.ts +10 -9
- package/src/menu/menu/copy-component-files.mjs +47 -0
- package/src/menu/menu/menu.scss +55 -17
- package/src/menu/menu/menu.ts +155 -84
- package/src/menu/menu-item/menu-item-colors.scss +3 -3
- package/src/menu/menu-item/menu-item.scss +118 -24
- package/src/menu/menu-item/menu-item.ts +61 -55
- package/src/menu/sub-menu/sub-menu.scss +30 -1
- package/src/timeline/index.ts +2 -0
- package/src/timeline/timeline-item.scss +98 -0
- package/src/timeline/timeline-item.ts +63 -0
- package/src/timeline/timeline.scss +11 -0
- package/src/timeline/timeline.ts +41 -0
- package/dist/canvas.js +0 -385
- package/dist/canvas.js.map +0 -1
- package/dist/flow-designer-node-CGSm6cUH.js +0 -553
- package/dist/flow-designer-node-CGSm6cUH.js.map +0 -1
- package/dist/flow-designer-node.js +0 -4
- package/dist/flow-designer-node.js.map +0 -1
- package/dist/flow-designer.js +0 -1417
- package/dist/flow-designer.js.map +0 -1
- package/dist/src/canvas/canvas.d.ts +0 -92
- package/dist/src/canvas/index.d.ts +0 -2
- package/dist/src/flow-designer/commands.d.ts +0 -66
- package/dist/src/flow-designer/flow-designer-node.d.ts +0 -46
- package/dist/src/flow-designer/flow-designer.d.ts +0 -133
- package/dist/src/flow-designer/index.d.ts +0 -7
- package/dist/src/flow-designer/layout.d.ts +0 -30
- package/dist/src/flow-designer/types.d.ts +0 -142
- package/dist/src/flow-designer/validation.d.ts +0 -43
- package/dist/src/flow-designer/workflow-utils.d.ts +0 -40
- package/src/canvas/canvas.scss +0 -72
- package/src/canvas/canvas.ts +0 -413
- package/src/canvas/index.ts +0 -2
- package/src/flow-designer/DEMO.md +0 -239
- package/src/flow-designer/commands.ts +0 -278
- package/src/flow-designer/flow-designer-node.ts +0 -172
- package/src/flow-designer/flow-designer.scss +0 -457
- package/src/flow-designer/flow-designer.ts +0 -611
- package/src/flow-designer/index.ts +0 -41
- package/src/flow-designer/layout.ts +0 -357
- package/src/flow-designer/types.ts +0 -166
- package/src/flow-designer/validation.ts +0 -284
- package/src/flow-designer/workflow-utils.ts +0 -282
- /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;
|
package/src/canvas/canvas.scss
DELETED
|
@@ -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
|
-
}
|
package/src/canvas/canvas.ts
DELETED
|
@@ -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
|
-
}
|