@statewalker/fsm-charts 0.2.1 → 0.2.3
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/{chunk-G25OZI4I.js → chunk-36ZLXJVD.js} +14 -17
- package/dist/{chunk-LRMTXZ55.js → chunk-VILLJJJD.js} +65 -49
- package/dist/index-dom.d.ts +37 -9
- package/dist/index-dom.js +1 -1
- package/dist/index-html.js +1 -1
- package/dist/index-runtime.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/package.json +2 -1
- package/src/dom/buildSections.ts +27 -0
- package/src/dom/description-panels.ts +100 -0
- package/src/dom/getStatesDescriptions.ts +80 -0
- package/src/dom/index.ts +4 -168
- package/src/html/buildStatechartSvg.ts +1 -0
- package/src/html/buildStatechartsPanel.ts +15 -19
- package/src/runtime/RuntimeStatechartApi.ts +1 -1
|
@@ -399,6 +399,7 @@ function buildStatechartSvg({
|
|
|
399
399
|
initialStateKey = "<initial>",
|
|
400
400
|
finalStateKey = "<final>"
|
|
401
401
|
}) {
|
|
402
|
+
if (!nodes.length && !edges.length) return;
|
|
402
403
|
width = Math.ceil(width);
|
|
403
404
|
height = Math.ceil(height);
|
|
404
405
|
println(
|
|
@@ -579,24 +580,20 @@ function buildStatechartsPanel({
|
|
|
579
580
|
println2(
|
|
580
581
|
` <summary class="state-details__label">${statechart2.text ?? statechart2.state}</summary>`
|
|
581
582
|
);
|
|
582
|
-
println2(
|
|
583
|
-
` <div class="state-details__content">`
|
|
584
|
-
);
|
|
585
|
-
println2(
|
|
586
|
-
` <div class="state-details__chart">`
|
|
587
|
-
);
|
|
583
|
+
println2(` <div class="state-details__content">`);
|
|
588
584
|
const printChildren = (str) => println2(` ${str}`);
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
`
|
|
599
|
-
|
|
585
|
+
if (statechart2.nodes.length) {
|
|
586
|
+
println2(` <div class="state-details__chart">`);
|
|
587
|
+
buildStatechartSvg({
|
|
588
|
+
graph: statechart2,
|
|
589
|
+
newId,
|
|
590
|
+
println: printChildren,
|
|
591
|
+
initialStateKey,
|
|
592
|
+
finalStateKey
|
|
593
|
+
});
|
|
594
|
+
println2(` </div>`);
|
|
595
|
+
}
|
|
596
|
+
println2(` <div class="state-details__description">`);
|
|
600
597
|
println2(` </div>`);
|
|
601
598
|
for (const child of statechart2.children || []) {
|
|
602
599
|
render(child, printChildren);
|
|
@@ -53,6 +53,24 @@ function buildSections(root) {
|
|
|
53
53
|
}
|
|
54
54
|
return top.content.length === 0 && top.children.length === 1 && !top.header ? top.children[0] : top;
|
|
55
55
|
}
|
|
56
|
+
function* visitSectionNodes(section) {
|
|
57
|
+
if (section.header) yield section.header;
|
|
58
|
+
yield* section.content;
|
|
59
|
+
for (const s of vistSections(section, true)) {
|
|
60
|
+
yield* visitSectionNodes(s);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function* vistSections(root, ignoreRoot = false) {
|
|
64
|
+
!ignoreRoot && (yield root);
|
|
65
|
+
for (const child of root.children) {
|
|
66
|
+
yield* vistSections(child, false);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function findSection(section, accept) {
|
|
70
|
+
for (const s of vistSections(section)) {
|
|
71
|
+
if (accept(s) === true) return s;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
56
74
|
function isDomElement(node) {
|
|
57
75
|
return node.nodeType === Node.ELEMENT_NODE;
|
|
58
76
|
}
|
|
@@ -76,10 +94,11 @@ function* visitDom(elm) {
|
|
|
76
94
|
}
|
|
77
95
|
}
|
|
78
96
|
|
|
79
|
-
// src/dom/
|
|
80
|
-
function
|
|
97
|
+
// src/dom/getStatesDescriptions.ts
|
|
98
|
+
function* visitStateDescriptions({
|
|
81
99
|
element,
|
|
82
100
|
rootStateKey,
|
|
101
|
+
removeFromDom = true,
|
|
83
102
|
splitHeader = (header) => {
|
|
84
103
|
if (!header) return [void 0, ""];
|
|
85
104
|
const text = header.innerText.trim();
|
|
@@ -94,29 +113,52 @@ function getStateDescriptionRenderer({
|
|
|
94
113
|
}
|
|
95
114
|
}) {
|
|
96
115
|
const docSections = buildSections(element);
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return
|
|
101
|
-
|
|
102
|
-
|
|
116
|
+
const rootDescription = findSection(docSections, (section) => {
|
|
117
|
+
if (!section.header) return false;
|
|
118
|
+
const [key] = splitHeader(section.header);
|
|
119
|
+
return key === rootStateKey;
|
|
120
|
+
});
|
|
121
|
+
if (!rootDescription) return;
|
|
122
|
+
if (removeFromDom) {
|
|
123
|
+
for (const node of visitSectionNodes(rootDescription)) {
|
|
124
|
+
if (node.nodeName[0] === "#") continue;
|
|
125
|
+
node.parentElement?.removeChild(node);
|
|
126
|
+
}
|
|
103
127
|
}
|
|
104
|
-
for (const
|
|
105
|
-
if (
|
|
106
|
-
|
|
128
|
+
for (const section of vistSections(rootDescription, false)) {
|
|
129
|
+
if (!section.header) continue;
|
|
130
|
+
const [state, title] = splitHeader(section.header);
|
|
131
|
+
if (state)
|
|
132
|
+
yield {
|
|
133
|
+
state,
|
|
134
|
+
title,
|
|
135
|
+
...section
|
|
136
|
+
};
|
|
107
137
|
}
|
|
138
|
+
}
|
|
139
|
+
function getStatesDescriptions(options) {
|
|
140
|
+
const index = {};
|
|
141
|
+
for (const description of visitStateDescriptions(options)) {
|
|
142
|
+
index[description.state] = description;
|
|
143
|
+
}
|
|
144
|
+
return index;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// src/dom/description-panels.ts
|
|
148
|
+
function getStateDescriptionRenderer(options) {
|
|
149
|
+
const descriptionsIndex = getStatesDescriptions(options);
|
|
108
150
|
return (statesStack) => {
|
|
109
151
|
const stateKey = [...statesStack].pop();
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
if (!
|
|
152
|
+
if (!stateKey) return;
|
|
153
|
+
const section = descriptionsIndex[stateKey];
|
|
154
|
+
if (!section) return;
|
|
113
155
|
const elm = renderSections({
|
|
114
|
-
section
|
|
115
|
-
deep: (
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if (
|
|
119
|
-
return
|
|
156
|
+
section,
|
|
157
|
+
deep: (section2) => false,
|
|
158
|
+
// section.state !== options.rootStateKey, // ???
|
|
159
|
+
header: (section2) => {
|
|
160
|
+
if (section2.state === stateKey) {
|
|
161
|
+
return section2.title || section2.state;
|
|
120
162
|
}
|
|
121
163
|
return;
|
|
122
164
|
}
|
|
@@ -125,37 +167,11 @@ function getStateDescriptionRenderer({
|
|
|
125
167
|
details && (details.open = true);
|
|
126
168
|
return elm;
|
|
127
169
|
};
|
|
128
|
-
function getStateDescriptionFilter(splitHeader2, stateKey) {
|
|
129
|
-
if (!stateKey) return () => false;
|
|
130
|
-
return (section) => {
|
|
131
|
-
if (!section.header) return false;
|
|
132
|
-
const [key] = splitHeader2(section.header);
|
|
133
|
-
return key === stateKey;
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
function* visitSectionNodes(section) {
|
|
138
|
-
if (section.header) yield section.header;
|
|
139
|
-
yield* section.content;
|
|
140
|
-
for (const s of vistSections(section, true)) {
|
|
141
|
-
yield* visitSectionNodes(s);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
function* vistSections(root, ignoreRoot = false) {
|
|
145
|
-
!ignoreRoot && (yield root);
|
|
146
|
-
for (const child of root.children) {
|
|
147
|
-
yield* vistSections(child, false);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
function findSection(section, accept) {
|
|
151
|
-
for (const s of vistSections(section)) {
|
|
152
|
-
if (accept(s) === true) return s;
|
|
153
|
-
}
|
|
154
170
|
}
|
|
155
171
|
function renderSections({
|
|
156
172
|
section,
|
|
157
173
|
deep = (s) => true,
|
|
158
|
-
header = (
|
|
174
|
+
header = (section2) => section2.header?.cloneNode(true)
|
|
159
175
|
}) {
|
|
160
176
|
const container = document.createElement("div");
|
|
161
177
|
const id = `id-${String(Math.random()).split(".")[1]}-${Date.now()}`;
|
|
@@ -197,7 +213,7 @@ function renderSections({
|
|
|
197
213
|
details.appendChild(summary);
|
|
198
214
|
const content = document.createElement("div");
|
|
199
215
|
details.appendChild(content);
|
|
200
|
-
const headerElm = header(section2
|
|
216
|
+
const headerElm = header(section2);
|
|
201
217
|
headerElm && summary.append(headerElm);
|
|
202
218
|
for (const node of section2.content) {
|
|
203
219
|
if (node.nodeName[0] === "#") continue;
|
|
@@ -212,4 +228,4 @@ function renderSections({
|
|
|
212
228
|
}
|
|
213
229
|
}
|
|
214
230
|
|
|
215
|
-
export { findSection, getStateDescriptionRenderer, renderSections, visitSectionNodes, vistSections };
|
|
231
|
+
export { buildSections, findSection, getStateDescriptionRenderer, getStatesDescriptions, isDomElement, newTreeBuilder, renderSections, toDomNodesIterator, visitDom, visitSectionNodes, visitStateDescriptions, vistSections };
|
package/dist/index-dom.d.ts
CHANGED
|
@@ -3,19 +3,47 @@ type DomSection = {
|
|
|
3
3
|
content: Node[];
|
|
4
4
|
children: DomSection[];
|
|
5
5
|
};
|
|
6
|
+
declare function buildSections(root: HTMLElement | Iterable<Node>): DomSection;
|
|
7
|
+
declare function visitSectionNodes(section: DomSection): Iterable<Node>;
|
|
8
|
+
declare function vistSections(root: DomSection, ignoreRoot?: boolean): Generator<DomSection>;
|
|
9
|
+
declare function findSection(section: DomSection, accept: (section: DomSection) => boolean): DomSection | undefined;
|
|
10
|
+
declare function isDomElement(node: Node): node is HTMLElement;
|
|
11
|
+
declare function toDomNodesIterator(root: Node | Iterable<Node>): Iterable<Node>;
|
|
12
|
+
declare function visitDom(elm: Node): Iterable<Node>;
|
|
6
13
|
|
|
7
|
-
declare function getStateDescriptionRenderer(
|
|
14
|
+
declare function getStateDescriptionRenderer(options: {
|
|
8
15
|
element: HTMLElement;
|
|
9
16
|
rootStateKey: string;
|
|
10
17
|
splitHeader?: (header?: HTMLElement) => [stateKey: undefined | string, label: string];
|
|
11
18
|
}): (statesStack: string[]) => HTMLDivElement | undefined;
|
|
12
|
-
declare function
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
section: DomSection;
|
|
17
|
-
deep?: (s: DomSection) => boolean;
|
|
18
|
-
header?: (header: HTMLElement) => undefined | string | HTMLElement;
|
|
19
|
+
declare function renderSections<T extends DomSection>({ section, deep, header, }: {
|
|
20
|
+
section: T;
|
|
21
|
+
deep?: (s: T) => boolean;
|
|
22
|
+
header?: (section: T) => undefined | string | Node;
|
|
19
23
|
}): HTMLDivElement;
|
|
20
24
|
|
|
21
|
-
|
|
25
|
+
type StateDescriptionSection = DomSection & {
|
|
26
|
+
state: string;
|
|
27
|
+
title?: string;
|
|
28
|
+
};
|
|
29
|
+
declare function visitStateDescriptions({ element, rootStateKey, removeFromDom, splitHeader, }: {
|
|
30
|
+
element: HTMLElement;
|
|
31
|
+
rootStateKey: string;
|
|
32
|
+
removeFromDom?: boolean;
|
|
33
|
+
splitHeader?: (header?: HTMLElement) => [stateKey: undefined | string, label: string];
|
|
34
|
+
}): Generator<StateDescriptionSection>;
|
|
35
|
+
declare function getStatesDescriptions(options: {
|
|
36
|
+
element: HTMLElement;
|
|
37
|
+
rootStateKey: string;
|
|
38
|
+
removeFromDom?: boolean;
|
|
39
|
+
splitHeader?: (header?: HTMLElement) => [stateKey: undefined | string, label: string];
|
|
40
|
+
}): {
|
|
41
|
+
[state: string]: StateDescriptionSection;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
declare function newTreeBuilder<T extends unknown[] = []>(context: {
|
|
45
|
+
enter: (level: number, ...params: T) => void;
|
|
46
|
+
exit: (level: number, ...params: T) => void;
|
|
47
|
+
}): (newLevel: number, ...params: T) => void;
|
|
48
|
+
|
|
49
|
+
export { type DomSection, type StateDescriptionSection, buildSections, findSection, getStateDescriptionRenderer, getStatesDescriptions, isDomElement, newTreeBuilder, renderSections, toDomNodesIterator, visitDom, visitSectionNodes, visitStateDescriptions, vistSections };
|
package/dist/index-dom.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { findSection, getStateDescriptionRenderer, renderSections, visitSectionNodes, vistSections } from './chunk-
|
|
1
|
+
export { buildSections, findSection, getStateDescriptionRenderer, getStatesDescriptions, isDomElement, newTreeBuilder, renderSections, toDomNodesIterator, visitDom, visitSectionNodes, visitStateDescriptions, vistSections } from './chunk-VILLJJJD.js';
|
package/dist/index-html.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { activeStatesStyle, activeTransitionsStyle, buildStateDetailsStyle, buildStateDetailsStyleWithModifier, buildStatechartCss, buildStatechartStyles, buildStatechartStylesWithModifier, buildStatechartSvg, buildStatechartsPanel, selectedStatesStyle, selectedTransitionsStyle } from './chunk-
|
|
1
|
+
export { activeStatesStyle, activeTransitionsStyle, buildStateDetailsStyle, buildStateDetailsStyleWithModifier, buildStatechartCss, buildStatechartStyles, buildStatechartStylesWithModifier, buildStatechartSvg, buildStatechartsPanel, selectedStatesStyle, selectedTransitionsStyle } from './chunk-36ZLXJVD.js';
|
|
2
2
|
import './chunk-FIQDLBWD.js';
|
package/dist/index-runtime.d.ts
CHANGED
|
@@ -22,7 +22,7 @@ declare class RuntimeStatechartApi {
|
|
|
22
22
|
toggleState(...stateIds: string[]): boolean;
|
|
23
23
|
activateStates(...stateIds: string[]): void;
|
|
24
24
|
focusStates(...stateIds: string[]): void;
|
|
25
|
-
setStateDescription(stateId: string, description:
|
|
25
|
+
setStateDescription(stateId: string, description: Node | string | undefined): void;
|
|
26
26
|
closeStatePanel(stateId: string): void;
|
|
27
27
|
openStatePanel(stateId: string): void;
|
|
28
28
|
deselectTransitions(modifier?: string): void;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { D as Dimensions, F as FsmStateConfig, G as GraphParamsProvider, L as Label, P as Padding, a as Position, S as StateChart, b as StateGraphEdge, c as StateGraphNode, T as Transition, d as TransitionsGraph } from './StateChart-BYBjTmC2.js';
|
|
2
|
-
export { findSection, getStateDescriptionRenderer, renderSections, visitSectionNodes, vistSections } from './index-dom.js';
|
|
2
|
+
export { DomSection, StateDescriptionSection, buildSections, findSection, getStateDescriptionRenderer, getStatesDescriptions, isDomElement, newTreeBuilder, renderSections, toDomNodesIterator, visitDom, visitSectionNodes, visitStateDescriptions, vistSections } from './index-dom.js';
|
|
3
3
|
export { concatTransitionsToString, serializeProcessConfig, splitTransitionString, toFsmStateConfig } from './index-config.js';
|
|
4
4
|
export { buildCharts, buildFlatCharts, getGraphParamsProvider, getLabelDimensions } from './index-layout.js';
|
|
5
5
|
export { C as CssTree, c as activeStatesStyle, d as activeTransitionsStyle, f as buildStateDetailsStyle, g as buildStateDetailsStyleWithModifier, b as buildStatechartCss, i as buildStatechartStyles, h as buildStatechartStylesWithModifier, j as buildStatechartSvg, a as buildStatechartsPanel, s as selectedStatesStyle, e as selectedTransitionsStyle, k as serializeCss } from './index-html-DgSCNHkR.js';
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { concatTransitionsToString, serializeProcessConfig, splitTransitionString, toFsmStateConfig } from './chunk-SVKVQY7B.js';
|
|
2
|
-
export { findSection, getStateDescriptionRenderer, renderSections, visitSectionNodes, vistSections } from './chunk-
|
|
3
|
-
export { activeStatesStyle, activeTransitionsStyle, buildStateDetailsStyle, buildStateDetailsStyleWithModifier, buildStatechartCss, buildStatechartStyles, buildStatechartStylesWithModifier, buildStatechartSvg, buildStatechartsPanel, selectedStatesStyle, selectedTransitionsStyle, serializeCss, toKebabCase } from './chunk-
|
|
2
|
+
export { buildSections, findSection, getStateDescriptionRenderer, getStatesDescriptions, isDomElement, newTreeBuilder, renderSections, toDomNodesIterator, visitDom, visitSectionNodes, visitStateDescriptions, vistSections } from './chunk-VILLJJJD.js';
|
|
3
|
+
export { activeStatesStyle, activeTransitionsStyle, buildStateDetailsStyle, buildStateDetailsStyleWithModifier, buildStatechartCss, buildStatechartStyles, buildStatechartStylesWithModifier, buildStatechartSvg, buildStatechartsPanel, selectedStatesStyle, selectedTransitionsStyle, serializeCss, toKebabCase } from './chunk-36ZLXJVD.js';
|
|
4
4
|
export { buildCharts, buildFlatCharts, getGraphParamsProvider, getLabelDimensions } from './chunk-AZBRQNTU.js';
|
|
5
5
|
import './chunk-FIQDLBWD.js';
|
|
6
6
|
export { RuntimeStatechartApi, StateChartIndex } from './chunk-TZHCBZVP.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@statewalker/fsm-charts",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "HFSM Implementation",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"homepage": "https://github.com/statewalker/statewalker-fsm-charts",
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
"build": "rm -fr ./dist && yarn test && tsup",
|
|
54
54
|
"prepare": "yarn build",
|
|
55
55
|
"watch": "tsup --watch",
|
|
56
|
+
"watch-for-viewer": "tsup --watch --outDir=../statewalker-fsm-viewer/src/.observablehq/cache/_node/@statewalker/fsm-charts@0.2.1",
|
|
56
57
|
"clean": "rm -rf dist",
|
|
57
58
|
"lint": "eslint",
|
|
58
59
|
"lint:fix": "yarn lint -- --fix",
|
package/src/dom/buildSections.ts
CHANGED
|
@@ -55,6 +55,33 @@ export function buildSections(root: HTMLElement | Iterable<Node>): DomSection {
|
|
|
55
55
|
: top;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
export function* visitSectionNodes(section: DomSection): Iterable<Node> {
|
|
59
|
+
if (section.header) yield section.header;
|
|
60
|
+
yield* section.content;
|
|
61
|
+
for (const s of vistSections(section, true)) {
|
|
62
|
+
yield* visitSectionNodes(s);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function* vistSections(
|
|
67
|
+
root: DomSection,
|
|
68
|
+
ignoreRoot: boolean = false,
|
|
69
|
+
): Generator<DomSection> {
|
|
70
|
+
!ignoreRoot && (yield root);
|
|
71
|
+
for (const child of root.children) {
|
|
72
|
+
yield* vistSections(child, false);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function findSection(
|
|
77
|
+
section: DomSection,
|
|
78
|
+
accept: (section: DomSection) => boolean,
|
|
79
|
+
) {
|
|
80
|
+
for (const s of vistSections(section)) {
|
|
81
|
+
if (accept(s) === true) return s;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
58
85
|
// --------------------------------------------
|
|
59
86
|
// DOM Iterator utilities
|
|
60
87
|
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { type DomSection } from "./buildSections.js";
|
|
2
|
+
import { getStatesDescriptions } from "./getStatesDescriptions.js";
|
|
3
|
+
|
|
4
|
+
export function getStateDescriptionRenderer(options: {
|
|
5
|
+
element: HTMLElement;
|
|
6
|
+
rootStateKey: string;
|
|
7
|
+
splitHeader?: (
|
|
8
|
+
header?: HTMLElement,
|
|
9
|
+
) => [stateKey: undefined | string, label: string];
|
|
10
|
+
}) {
|
|
11
|
+
const descriptionsIndex = getStatesDescriptions(options);
|
|
12
|
+
|
|
13
|
+
return (statesStack: string[]) => {
|
|
14
|
+
const stateKey: string | undefined = [...statesStack].pop();
|
|
15
|
+
if (!stateKey) return;
|
|
16
|
+
|
|
17
|
+
const section = descriptionsIndex[stateKey];
|
|
18
|
+
if (!section) return;
|
|
19
|
+
|
|
20
|
+
const elm = renderSections({
|
|
21
|
+
section,
|
|
22
|
+
deep: (section) => false, // section.state !== options.rootStateKey, // ???
|
|
23
|
+
header: (section) => {
|
|
24
|
+
if (section.state === stateKey) {
|
|
25
|
+
return section.title || section.state;
|
|
26
|
+
}
|
|
27
|
+
return;
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
const details = elm?.querySelector("details");
|
|
31
|
+
details && (details.open = true);
|
|
32
|
+
return elm;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function renderSections<T extends DomSection>({
|
|
37
|
+
section,
|
|
38
|
+
deep = (s) => true,
|
|
39
|
+
header = (section) => section.header?.cloneNode(true) as Node,
|
|
40
|
+
}: {
|
|
41
|
+
section: T;
|
|
42
|
+
deep?: (s: T) => boolean;
|
|
43
|
+
header?: (section: T) => undefined | string | Node;
|
|
44
|
+
}) {
|
|
45
|
+
const container = document.createElement("div");
|
|
46
|
+
const id = `id-${String(Math.random()).split(".")[1]}-${Date.now()}`;
|
|
47
|
+
container.setAttribute("id", id);
|
|
48
|
+
if (!section) return container;
|
|
49
|
+
|
|
50
|
+
const sectionView = renderSection(section);
|
|
51
|
+
container.append(sectionView);
|
|
52
|
+
const style = renderSectionStyles(id);
|
|
53
|
+
container.append(style);
|
|
54
|
+
return container;
|
|
55
|
+
|
|
56
|
+
function renderSectionStyles(id?: string) {
|
|
57
|
+
const prefix = id ? `#${id}` : "";
|
|
58
|
+
const style = document.createElement("style");
|
|
59
|
+
container.appendChild(style);
|
|
60
|
+
style.innerHTML = `
|
|
61
|
+
${prefix} details {
|
|
62
|
+
border: 1px solid #aaa;
|
|
63
|
+
border-radius: 4px;
|
|
64
|
+
padding: 0.5em 0.5em 0;
|
|
65
|
+
margin-bottom: 0.5em;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
${prefix} details > summary {
|
|
69
|
+
font-weight: bold;
|
|
70
|
+
margin: -0.5em -0.5em 0;
|
|
71
|
+
padding: 0.5em;
|
|
72
|
+
cursor: pointer;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
${prefix} details[open] {
|
|
76
|
+
padding: 0.5em;
|
|
77
|
+
}
|
|
78
|
+
`;
|
|
79
|
+
return style;
|
|
80
|
+
}
|
|
81
|
+
function renderSection(section: T) {
|
|
82
|
+
const details = document.createElement("details");
|
|
83
|
+
const summary = document.createElement("summary");
|
|
84
|
+
details.appendChild(summary);
|
|
85
|
+
const content = document.createElement("div");
|
|
86
|
+
details.appendChild(content);
|
|
87
|
+
const headerElm = header(section);
|
|
88
|
+
headerElm && summary.append(headerElm);
|
|
89
|
+
for (const node of section.content) {
|
|
90
|
+
if (node.nodeName[0] === "#") continue;
|
|
91
|
+
content.append(node.cloneNode(true));
|
|
92
|
+
}
|
|
93
|
+
if (deep(section)) {
|
|
94
|
+
for (const child of section.children) {
|
|
95
|
+
content.append(renderSection(child as T));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return details;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildSections,
|
|
3
|
+
findSection,
|
|
4
|
+
visitSectionNodes,
|
|
5
|
+
vistSections,
|
|
6
|
+
type DomSection,
|
|
7
|
+
} from "./buildSections.js";
|
|
8
|
+
|
|
9
|
+
export type StateDescriptionSection = DomSection & {
|
|
10
|
+
state: string;
|
|
11
|
+
title?: string;
|
|
12
|
+
};
|
|
13
|
+
export function* visitStateDescriptions({
|
|
14
|
+
element,
|
|
15
|
+
rootStateKey,
|
|
16
|
+
removeFromDom = true,
|
|
17
|
+
splitHeader = (
|
|
18
|
+
header?: HTMLElement,
|
|
19
|
+
): [stateKey: undefined | string, label: string] => {
|
|
20
|
+
if (!header) return [undefined, ""];
|
|
21
|
+
const text = header.innerText.trim();
|
|
22
|
+
let key: string | undefined;
|
|
23
|
+
let label: string = text;
|
|
24
|
+
text.replace(/^\s*\[(.*)\]\s*(.*)$/, (_, k, l) => {
|
|
25
|
+
key = k.trim();
|
|
26
|
+
label = l.trim() || key;
|
|
27
|
+
return "";
|
|
28
|
+
});
|
|
29
|
+
return [key, label];
|
|
30
|
+
},
|
|
31
|
+
}: {
|
|
32
|
+
element: HTMLElement;
|
|
33
|
+
rootStateKey: string;
|
|
34
|
+
removeFromDom?: boolean; // If true - removes state description nodes from the DOM
|
|
35
|
+
splitHeader?: (
|
|
36
|
+
header?: HTMLElement,
|
|
37
|
+
) => [stateKey: undefined | string, label: string];
|
|
38
|
+
}): Generator<StateDescriptionSection> {
|
|
39
|
+
const docSections = buildSections(element);
|
|
40
|
+
const rootDescription = findSection(docSections, (section: DomSection) => {
|
|
41
|
+
if (!section.header) return false;
|
|
42
|
+
const [key] = splitHeader(section.header);
|
|
43
|
+
return key === rootStateKey;
|
|
44
|
+
});
|
|
45
|
+
if (!rootDescription) return;
|
|
46
|
+
|
|
47
|
+
// Remove description nodes from DOM
|
|
48
|
+
if (removeFromDom) {
|
|
49
|
+
for (const node of visitSectionNodes(rootDescription)) {
|
|
50
|
+
if (node.nodeName[0] === "#") continue;
|
|
51
|
+
node.parentElement?.removeChild(node);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
for (const section of vistSections(rootDescription, false)) {
|
|
56
|
+
if (!section.header) continue;
|
|
57
|
+
const [state, title] = splitHeader(section.header);
|
|
58
|
+
if (state)
|
|
59
|
+
yield {
|
|
60
|
+
state,
|
|
61
|
+
title,
|
|
62
|
+
...section,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function getStatesDescriptions(options: {
|
|
68
|
+
element: HTMLElement;
|
|
69
|
+
rootStateKey: string;
|
|
70
|
+
removeFromDom?: boolean; // If true - removes state description nodes from the DOM
|
|
71
|
+
splitHeader?: (
|
|
72
|
+
header?: HTMLElement,
|
|
73
|
+
) => [stateKey: undefined | string, label: string];
|
|
74
|
+
}): { [state: string]: StateDescriptionSection } {
|
|
75
|
+
const index: { [state: string]: StateDescriptionSection } = {};
|
|
76
|
+
for (const description of visitStateDescriptions(options)) {
|
|
77
|
+
index[description.state] = description;
|
|
78
|
+
}
|
|
79
|
+
return index;
|
|
80
|
+
}
|
package/src/dom/index.ts
CHANGED
|
@@ -1,168 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
rootStateKey,
|
|
6
|
-
splitHeader = (
|
|
7
|
-
header?: HTMLElement,
|
|
8
|
-
): [stateKey: undefined | string, label: string] => {
|
|
9
|
-
if (!header) return [undefined, ""];
|
|
10
|
-
const text = header.innerText.trim();
|
|
11
|
-
let key: string | undefined;
|
|
12
|
-
let label: string = text;
|
|
13
|
-
text.replace(/^\s*\[(.*)\]\s*(.*)$/, (_, k, l) => {
|
|
14
|
-
key = k.trim();
|
|
15
|
-
label = l.trim() || key;
|
|
16
|
-
return "";
|
|
17
|
-
});
|
|
18
|
-
return [key, label];
|
|
19
|
-
},
|
|
20
|
-
}: {
|
|
21
|
-
element: HTMLElement;
|
|
22
|
-
rootStateKey: string;
|
|
23
|
-
splitHeader?: (
|
|
24
|
-
header?: HTMLElement,
|
|
25
|
-
) => [stateKey: undefined | string, label: string];
|
|
26
|
-
}) {
|
|
27
|
-
const docSections = buildSections(element);
|
|
28
|
-
const rootStateFilter = getStateDescriptionFilter(splitHeader, rootStateKey);
|
|
29
|
-
const rootDescription = findSection(docSections, rootStateFilter);
|
|
30
|
-
|
|
31
|
-
if (!rootDescription) {
|
|
32
|
-
return (statesStack: string[]) => {
|
|
33
|
-
return undefined;
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Remove description nodes from DOM
|
|
38
|
-
for (const node of visitSectionNodes(rootDescription)) {
|
|
39
|
-
if (node.nodeName[0] === "#") continue;
|
|
40
|
-
node.parentElement?.removeChild(node);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return (statesStack: string[]) => {
|
|
44
|
-
const stateKey = [...statesStack].pop();
|
|
45
|
-
const filter = getStateDescriptionFilter(splitHeader, stateKey);
|
|
46
|
-
const description = findSection(rootDescription, filter);
|
|
47
|
-
if (!description) return;
|
|
48
|
-
const elm = renderSections({
|
|
49
|
-
section: description,
|
|
50
|
-
deep: (s) => !rootStateFilter(s),
|
|
51
|
-
header: (header) => {
|
|
52
|
-
const [key, label] = splitHeader(header);
|
|
53
|
-
if (key === stateKey) {
|
|
54
|
-
return label || key;
|
|
55
|
-
}
|
|
56
|
-
return;
|
|
57
|
-
},
|
|
58
|
-
});
|
|
59
|
-
const details = elm?.querySelector("details");
|
|
60
|
-
details && (details.open = true);
|
|
61
|
-
return elm;
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
function getStateDescriptionFilter(
|
|
65
|
-
splitHeader: (header: HTMLElement) => [stateKey?: string, label?: string],
|
|
66
|
-
stateKey?: string,
|
|
67
|
-
) {
|
|
68
|
-
if (!stateKey) return () => false;
|
|
69
|
-
return (section: DomSection) => {
|
|
70
|
-
if (!section.header) return false;
|
|
71
|
-
const [key] = splitHeader(section.header);
|
|
72
|
-
return key === stateKey;
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export function* visitSectionNodes(section: DomSection): Iterable<Node> {
|
|
78
|
-
if (section.header) yield section.header;
|
|
79
|
-
yield* section.content;
|
|
80
|
-
for (const s of vistSections(section, true)) {
|
|
81
|
-
yield* visitSectionNodes(s);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export function* vistSections(
|
|
86
|
-
root: DomSection,
|
|
87
|
-
ignoreRoot: boolean = false,
|
|
88
|
-
): Generator<DomSection> {
|
|
89
|
-
!ignoreRoot && (yield root);
|
|
90
|
-
for (const child of root.children) {
|
|
91
|
-
yield* vistSections(child, false);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export function findSection(
|
|
96
|
-
section: DomSection,
|
|
97
|
-
accept: (section: DomSection) => boolean,
|
|
98
|
-
) {
|
|
99
|
-
for (const s of vistSections(section)) {
|
|
100
|
-
if (accept(s) === true) return s;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export function renderSections({
|
|
105
|
-
section,
|
|
106
|
-
deep = (s) => true,
|
|
107
|
-
header = (headerElm) => headerElm,
|
|
108
|
-
}: {
|
|
109
|
-
section: DomSection;
|
|
110
|
-
deep?: (s: DomSection) => boolean;
|
|
111
|
-
header?: (header: HTMLElement) => undefined | string | HTMLElement;
|
|
112
|
-
}) {
|
|
113
|
-
const container = document.createElement("div");
|
|
114
|
-
const id = `id-${String(Math.random()).split(".")[1]}-${Date.now()}`;
|
|
115
|
-
container.setAttribute("id", id);
|
|
116
|
-
if (!section) return container;
|
|
117
|
-
|
|
118
|
-
const sectionView = renderSection(section);
|
|
119
|
-
container.append(sectionView);
|
|
120
|
-
const style = renderSectionStyles(id);
|
|
121
|
-
container.append(style);
|
|
122
|
-
return container;
|
|
123
|
-
|
|
124
|
-
function renderSectionStyles(id?: string) {
|
|
125
|
-
const prefix = id ? `#${id}` : "";
|
|
126
|
-
const style = document.createElement("style");
|
|
127
|
-
container.appendChild(style);
|
|
128
|
-
style.innerHTML = `
|
|
129
|
-
${prefix} details {
|
|
130
|
-
border: 1px solid #aaa;
|
|
131
|
-
border-radius: 4px;
|
|
132
|
-
padding: 0.5em 0.5em 0;
|
|
133
|
-
margin-bottom: 0.5em;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
${prefix} details > summary {
|
|
137
|
-
font-weight: bold;
|
|
138
|
-
margin: -0.5em -0.5em 0;
|
|
139
|
-
padding: 0.5em;
|
|
140
|
-
cursor: pointer;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
${prefix} details[open] {
|
|
144
|
-
padding: 0.5em;
|
|
145
|
-
}
|
|
146
|
-
`;
|
|
147
|
-
return style;
|
|
148
|
-
}
|
|
149
|
-
function renderSection(section: DomSection) {
|
|
150
|
-
const details = document.createElement("details");
|
|
151
|
-
const summary = document.createElement("summary");
|
|
152
|
-
details.appendChild(summary);
|
|
153
|
-
const content = document.createElement("div");
|
|
154
|
-
details.appendChild(content);
|
|
155
|
-
const headerElm = header(section.header!);
|
|
156
|
-
headerElm && summary.append(headerElm);
|
|
157
|
-
for (const node of section.content) {
|
|
158
|
-
if (node.nodeName[0] === "#") continue;
|
|
159
|
-
content.append(node.cloneNode(true));
|
|
160
|
-
}
|
|
161
|
-
if (deep(section)) {
|
|
162
|
-
for (const child of section.children) {
|
|
163
|
-
content.append(renderSection(child));
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
return details;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
1
|
+
export * from "./buildSections.js";
|
|
2
|
+
export * from "./description-panels.js";
|
|
3
|
+
export * from "./getStatesDescriptions.js";
|
|
4
|
+
export * from "./newTreeBuilder.js";
|
|
@@ -18,29 +18,25 @@ export function buildStatechartsPanel({
|
|
|
18
18
|
|
|
19
19
|
function render(statechart: StateChart, println: (str: string) => void) {
|
|
20
20
|
println(
|
|
21
|
-
` <details class="state-details" data-state-id="${statechart.id}"
|
|
21
|
+
` <details class="state-details" data-state-id="${statechart.id}">`,
|
|
22
22
|
);
|
|
23
23
|
println(
|
|
24
|
-
` <summary class="state-details__label">${statechart.text ?? statechart.state}</summary
|
|
25
|
-
);
|
|
26
|
-
println(
|
|
27
|
-
` <div class="state-details__content">`
|
|
28
|
-
);
|
|
29
|
-
println(
|
|
30
|
-
` <div class="state-details__chart">`
|
|
24
|
+
` <summary class="state-details__label">${statechart.text ?? statechart.state}</summary>`,
|
|
31
25
|
);
|
|
26
|
+
println(` <div class="state-details__content">`);
|
|
32
27
|
const printChildren = (str: string) => println(` ${str}`);
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
`
|
|
43
|
-
|
|
28
|
+
if (statechart.nodes.length) {
|
|
29
|
+
println(` <div class="state-details__chart">`);
|
|
30
|
+
buildStatechartSvg({
|
|
31
|
+
graph: statechart,
|
|
32
|
+
newId,
|
|
33
|
+
println: printChildren,
|
|
34
|
+
initialStateKey,
|
|
35
|
+
finalStateKey,
|
|
36
|
+
});
|
|
37
|
+
println(` </div>`);
|
|
38
|
+
}
|
|
39
|
+
println(` <div class="state-details__description">`);
|
|
44
40
|
println(` </div>`);
|
|
45
41
|
for (const child of statechart.children || []) {
|
|
46
42
|
render(child, printChildren);
|
|
@@ -83,7 +83,7 @@ export class RuntimeStatechartApi {
|
|
|
83
83
|
|
|
84
84
|
setStateDescription(
|
|
85
85
|
stateId: string,
|
|
86
|
-
description:
|
|
86
|
+
description: Node | string | undefined,
|
|
87
87
|
) {
|
|
88
88
|
const descriptionContainer = this.element.querySelector(
|
|
89
89
|
`details[data-state-id="${stateId}"] .state-details__description`,
|