@statewalker/fsm-charts 0.0.1 → 0.0.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@statewalker/fsm-charts",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "HFSM Implementation",
5
5
  "keywords": [],
6
6
  "homepage": "https://github.com/statewalker/statewalker-fsm-charts",
package/src/index.ts CHANGED
@@ -1,2 +1,4 @@
1
1
  export * from "./index-layout.ts";
2
2
  export * from "./index-svg.ts";
3
+ export * from "./utils/toKebabCase.ts";
4
+ export * from "./utils/serializeCss.ts";
@@ -41,7 +41,7 @@ export function buildCharts({
41
41
  } & GraphParamsProvider): StateChart {
42
42
  return buildChart(config);
43
43
 
44
- function buildChart(config: FsmStateConfig, id: string = newId("state")) {
44
+ function buildChart(config: FsmStateConfig, id: string = newId("s")) {
45
45
  const graph = buildFlatCharts({
46
46
  lodash,
47
47
  transitions: config.transitions,
@@ -1,120 +1,7 @@
1
- export function buildStatechartCss({ prefix = "" } = {}): string {
2
- return `
3
- --state--fill: white;
4
- --state--stroke-color: silver;
5
- --state--stroke-width: 1px;
6
-
7
- --state-initial--fill: var(--state--stroke-color, silver);
8
- --state-initial--stroke-color: var(--state--stroke-color, none);
9
- --state-initial--stroke-width: var(--state--stroke-width, 0);
10
-
11
- --state-final--fill: var(--state--fill, white);
12
- --state-final--stroke-color: var(--state--stroke-color, silver);
13
- --state-final--stroke-width: var(--state--stroke-width, 1px);
14
-
15
- --state-label--color: gray;
16
- --state-label--font-size: 14px;
17
- --state-label--font-family: sans-serif;
18
-
19
- --transition--fill: rgba(255,255,255,0.7);
20
- --transition--stroke-color: none;
21
- --transition--stroke-width: 1;
22
-
23
- --transition-line--stroke-color: silver;
24
- --transition-line--stroke-width: 1px;
25
-
26
- --transition-label--color: gray;
27
- --transition-label--font-size: 12px;
28
- --transition-label--font-family: sans-serif;
29
-
30
- --transition-marker--fill: var(--transition-line--stroke-color, gray);
31
-
32
- .state-details {
33
-
34
- }
35
-
36
- .state-details {
37
- background-color: var(--state--fill, white);
38
- border: var(--state--stroke-width, 1px) solid var(--state--stroke-color, silver);
39
- border-radius: 0.5em;
40
- padding: 0.5em;
41
- }
42
-
43
- .state-details .state-label {
44
- cursor: pointer;
45
- color: var(--transition-label--color, gray);
46
- font-family: var(--state-label--font-family, sans-serif);
47
- font-size: var(--state-label--font-size, 14px);
48
- }
49
- .state-details .state-description {
50
- display: flex;
51
- flex-direction: column;
52
- gap: 0.5em;
53
- }
54
- .state-details .state-chart-container {
55
- max-width: 100%;
56
- overflow: auto;
57
- scrollbar-width: thin;
58
- }
1
+ import { serializeCss } from "../utils/serializeCss";
2
+ import { buildStatechartStyles } from "./buildStatechartStyles";
59
3
 
60
- .state-details .statechart {
61
-
62
- }
63
-
64
- .state-details .statechart .transition-marker {
65
- fill : var(--transition-marker--fill, silver);
66
- }
67
-
68
- .state-details .statechart .transitions {
69
- }
70
-
71
- .state-details .statechart .transitions .transition .transition-line {
72
- stroke: var(--transition-line--stroke-color, silver);
73
- stroke-width: var(--transition-line--stroke-width, 1px);
74
- }
75
-
76
- .state-details .statechart .transitions .transition .transition-box {
77
- fill: var(--transition--fill, rgba(255,255,255,0.7));
78
- stroke: var(--transition--stroke-color, none);
79
- stroke-width: var(--transition--stroke-width, 1);
80
- border-radius: 0.5em;
81
- }
82
-
83
- .state-details .statechart .transitions .transition .transition-label {
84
- fill: var(--transition-label--color, gray);
85
- font-size: var(--transition-label--font-size, 12px);
86
- font-family: var(--transition-label--font-family, sans-serif);
87
- }
88
-
89
- /* ------------------------------------------ */
90
- .state-details .statechart .states .state {
91
- }
92
- .state-details .statechart .states .state .state-box {
93
- fill: var(--state--fill, white);
94
- stroke: var(--state--stroke-color, silver);
95
- stroke-width: var(--state--stroke-width, 1px);
96
- border-radius: 0.5em;
97
- }
98
- .state-details .statechart .states .state .state-initial {
99
- fill: var(--state-initial--fill, gray);
100
- stroke: var(--state-initial--stroke-color, none);
101
- stroke-width: var(--state-initial--stroke-width, 0);
102
- }
103
- .state-details .statechart .states .state .state-final {
104
- fill: var(--state-final--fill, white);
105
- stroke: var(--state-final--stroke-color, silver);
106
- stroke-width: var(--state-final--stroke-width, 2);
107
- }
108
- .state-details .statechart .states .state .state-final.inner {
109
- fill: var(--state-final--stroke-color, silver);
110
- stroke: none;
111
- stroke-width: 0;
112
- }
113
- .state-details .statechart .states .state .state-label {
114
- fill: var(--state-label--color, gray);
115
- font-size: var(--state-label--font-size, 14px);
116
- font-family: var(--state-label--font-family, sans-serif);
117
- }
118
-
119
- `;
4
+ export function buildStatechartCss({ prefix = "" } = {}): string {
5
+ const cssObject = buildStatechartStyles({ root: prefix || ":root" });
6
+ return serializeCss(cssObject);
120
7
  }
@@ -0,0 +1,176 @@
1
+ import { CssTree } from "../utils/serializeCss";
2
+
3
+ function buildStateDetailsStyle() {
4
+ return {
5
+ ".state-details": {
6
+ backgroundColor: "var(--state-background-color, white)",
7
+ border:
8
+ "var(--state-border-width, 1px) solid var(--state-border-color, currentColor)",
9
+ borderRadius: "0.5em",
10
+ padding: "0.5em",
11
+ },
12
+ ".state-details__label": {
13
+ cursor: "pointer",
14
+ color: "var(--transition-label-color, currentColor)",
15
+ fontFamily: "var(--state-label-font-family, sans-serif)",
16
+ fontSize: "var(--state-label-font-size, 14px)",
17
+ },
18
+ ".state-details__description": {
19
+ display: "flex",
20
+ flexDirection: "column",
21
+ gap: "0.5em",
22
+ },
23
+ ".state-details__chart": {
24
+ maxWidth: "100%",
25
+ overflow: "auto",
26
+ scrollbarWidth: "thin",
27
+ },
28
+ //
29
+ ".state-details--selected": {
30
+ backgroundColor: "var(--state-selected-background-color, white)",
31
+ border:
32
+ "var(--state-selected-border-width, 1px) solid var(--state-selected-border-color, currentColor)",
33
+ },
34
+ ".state-details__label--selected": {
35
+ color: "var(--state-selected-label-color)",
36
+ },
37
+ };
38
+ }
39
+ export function buildStatechartStyles({
40
+ root = ":root",
41
+ }: { root?: string } = {}): CssTree {
42
+ return {
43
+ [root]: {
44
+ // --------------------------------------
45
+ // Normal states
46
+ "--state-background-color": "white",
47
+ "--state-border-color": "silver",
48
+ "--state-border-width": "1px",
49
+ "--state-label-color": "gray",
50
+ "--state-label-font-size": "14px",
51
+ "--state-label-font-family": "sans-serif",
52
+
53
+ // Selected states
54
+ "--state-selected-background-color": "#eee",
55
+ "--state-selected-border-color": "red",
56
+ "--state-selected-border-width": "2px",
57
+ "--state-selected-label-color": "red",
58
+
59
+ // --------------------------------------
60
+ // Normal transitions
61
+ "--transition-background-color": "rgba(255,255,255,0.7)",
62
+ "--transition-border-color": "none",
63
+ "--transition-border-width": "1px",
64
+ "--transition-line-color": "silver",
65
+ "--transition-line-width": "1px",
66
+ "--transition-label-color": "gray",
67
+ "--transition-label-font-size": "12px",
68
+ "--transition-label-font-family": "sans-serif",
69
+
70
+ // --------------------------------------
71
+ // Selected transitions
72
+ "--transition-selected-background-color": "#eee",
73
+ "--transition-selected-border-color": "none",
74
+ "--transition-selected-border-width": "0",
75
+ "--transition-selected-line-color": "red",
76
+ "--transition-selected-line-width": "1px",
77
+ "--transition-selected-label-color": "red",
78
+
79
+ // --------------------------------------
80
+ // --------------------------------------
81
+ ...buildStateDetailsStyle(),
82
+ ".transition": {},
83
+ ".transition--selected": {
84
+ "--transition-background-color":
85
+ "var(--transition-selected-background-color)",
86
+ "--transition-border-color": "var(--transition-selected-border-color)",
87
+ "--transition-border-width": "var(--transition-selected-border-width)",
88
+ "--transition-line-color": "var(--transition-selected-line-color)",
89
+ "--transition-line-width": "var(--transition-selected-line-width)",
90
+ "--transition-label-color": "var(--transition-selected-label-color)",
91
+ },
92
+ ".transition__marker": {
93
+ fill: "var(--transition-line-color, currentColor)",
94
+ stroke: "none",
95
+ },
96
+ ".transition__line": {
97
+ stroke: "var(--transition-line-color, currentColor)",
98
+ strokeWidth: "var(--transition-line-width, 1px)",
99
+ },
100
+ ".transition__box": {
101
+ fill: "var(--transition-background-color, rgba(255,255,255,0.7))",
102
+ stroke: "var(--transition-border-color, none)",
103
+ strokeWidth: "var(--transition-border-width, 1px)",
104
+ borderRadius: "0.5em",
105
+ },
106
+ ".transition__label": {
107
+ fill: "var(--transition-label-color, currentColor)",
108
+ fontSize: "var(--transition-label-font-size, 12px)",
109
+ fontFamily: "var(--transition-label-font-family, sans-serif)",
110
+ },
111
+
112
+ // ----------------------------------------------
113
+ // States
114
+
115
+ ".state": {},
116
+ ".state--selected": {
117
+ "--state-background-color": "var(--state-selected-background-color)",
118
+ "--state-border-color": "var(--state-selected-border-color)",
119
+ "--state-border-width": "var(--state-selected-border-width)",
120
+ "--state-label-color": "var(--state-selected-label-color)",
121
+ },
122
+
123
+ // --------------------------
124
+ // Panels
125
+ ".state__details": {
126
+ backgroundColor: "var(--state-background-color, white)",
127
+ border:
128
+ "var(--state-border-width, 1px) solid var(--state-border-color, currentColor)",
129
+ borderRadius: "0.5em",
130
+ padding: "0.5em",
131
+ },
132
+ ".state__description": {
133
+ display: "flex",
134
+ flexDirection: "column",
135
+ gap: "0.5em",
136
+ },
137
+ ".state__chart-container": {
138
+ maxWidth: "100%",
139
+ overflow: "auto",
140
+ scrollbarWidth: "thin",
141
+ },
142
+
143
+ // --------------------------
144
+ // Statecharts
145
+ ".state__box": {
146
+ fill: "var(--state-background-color, white)",
147
+ stroke: "var(--state-border-color, currentColor)",
148
+ strokeWidth: "var(--state-border-width, 1px)",
149
+ borderRadius: "0.5em",
150
+ },
151
+ ".state__label": {
152
+ color: "var(--state-label-color, currentColor)",
153
+ fill: "var(--state-label-color, currentColor)",
154
+ fontSize: "var(--state-label-font-size, 14px)",
155
+ fontFamily: "var(--state-label-font-family, sans-serif)",
156
+ cursor: "pointer",
157
+ },
158
+ ".state__initial": {
159
+ fill: "var(--state-border-color, currentColor)",
160
+ stroke: "var(--state-border-color, currentColor)",
161
+ strokeWidth: "var(--state-border-width, 2px)",
162
+ },
163
+ ".state__final": {
164
+ fill: "white",
165
+ stroke: "var(--state-border-color, currentColor)",
166
+ strokeWidth: "var(--state-border-width, 2px)",
167
+ // ---------------------------
168
+ "&.state__final--inner": {
169
+ fill: "var(--state-border-color, currentColor)",
170
+ stroke: "none",
171
+ strokeWidth: "0",
172
+ },
173
+ },
174
+ },
175
+ };
176
+ }
@@ -65,7 +65,7 @@ export function buildStatechartSvg({
65
65
  .transitions .transition .transition-label
66
66
  ---------------------------------------------------
67
67
  */
68
- println(`<g class="transitions">`);
68
+ println(`<g class="statechart__transitions">`);
69
69
 
70
70
  const markerAttrs = serializeAttrs({
71
71
  refX: 19,
@@ -73,7 +73,7 @@ export function buildStatechartSvg({
73
73
  markerWidth: 20,
74
74
  markerHeight: 14,
75
75
  orient: "auto",
76
- class: "transition-marker",
76
+ class: "transition__marker",
77
77
  stroke: "none",
78
78
  fill: "currentColor",
79
79
  });
@@ -119,8 +119,7 @@ export function buildStatechartSvg({
119
119
  base.lineEnd();
120
120
 
121
121
  const serializedAttrs = serializeAttrs({
122
- class: "transition-line",
123
- "transition-id": id,
122
+ class: "transition__line",
124
123
  fill: "none",
125
124
  stroke: "currentColor",
126
125
  strokeWidth: "1px",
@@ -145,12 +144,12 @@ export function buildStatechartSvg({
145
144
  fill: "rgba(255,255,255,0.7)",
146
145
  stroke: "none",
147
146
  strokeWidth: "1",
148
- class: "transition-box",
147
+ class: "transition__box",
149
148
  });
150
149
  println(` <rect ${rectAttrs} />`);
151
150
 
152
151
  const textAttrs = serializeAttrs({
153
- class: "transition-label",
152
+ class: "transition__label",
154
153
  "text-anchor": "middle",
155
154
  fill: "currentColor",
156
155
  x: Math.round(x + width / 2),
@@ -180,7 +179,7 @@ export function buildStatechartSvg({
180
179
  const initialStateRadius = 6;
181
180
  const finalStateRadius = initialStateRadius / 2;
182
181
 
183
- println(`<g class="states">`);
182
+ println(`<g class="statechart__states">`);
184
183
  for (const node of nodes) {
185
184
  const { key, id, state: text } = node;
186
185
  const { x, y, width, height } = getBoxParams({ ...node, padding: shift });
@@ -191,7 +190,7 @@ export function buildStatechartSvg({
191
190
  const cy = Math.round(y + height / 2);
192
191
  if (key === initialStateKey) {
193
192
  const serializedAttrs = serializeAttrs({
194
- class: "state-initial",
193
+ class: "state__initial",
195
194
  cx,
196
195
  cy,
197
196
  r: initialStateRadius,
@@ -202,7 +201,7 @@ export function buildStatechartSvg({
202
201
  println(` <circle ${serializedAttrs} />`);
203
202
  } else {
204
203
  let serializedAttrs = serializeAttrs({
205
- class: "state-final",
204
+ class: "state__final",
206
205
  cx,
207
206
  cy,
208
207
  r: initialStateRadius,
@@ -213,7 +212,7 @@ export function buildStatechartSvg({
213
212
  println(` <circle ${serializedAttrs} />`);
214
213
 
215
214
  serializedAttrs = serializeAttrs({
216
- class: "state-final inner",
215
+ class: "state__final state__final--inner",
217
216
  cx,
218
217
  cy,
219
218
  r: finalStateRadius,
@@ -234,17 +233,16 @@ export function buildStatechartSvg({
234
233
  fill: "none",
235
234
  stroke: "currentColor",
236
235
  strokeWidth: "2",
237
- class: "state-box",
236
+ class: "state__box",
238
237
  });
239
238
  println(` <rect ${rectAttrs} />`);
240
239
 
241
240
  const textAttrs = serializeAttrs({
242
- class: "state-label",
241
+ class: "state__label",
243
242
  "text-anchor": "middle",
244
243
  fill: "currentColor",
245
244
  x: Math.round(x + width / 2),
246
245
  y: Math.round(y + height / 2 + stateFontSize / 4),
247
- "data-state-id": id,
248
246
  });
249
247
  println(` <text ${textAttrs}>${text}</text>`);
250
248
  }
@@ -17,9 +17,9 @@ export function buildStatechartsPanel({
17
17
  render(statechart, println);
18
18
 
19
19
  function render(statechart: StateChart, println: (str: string) => void) {
20
- println(`<details class="state-details" data-state-id="${statechart.id}">`);
21
- println(` <summary class="state-label">${statechart.key}</summary>`);
22
- println(` <div class="state-description"><div class="state-chart-container">`);
20
+ println(` <details class="state-details" data-state-id="${statechart.id}">`);
21
+ println(` <summary class="state-details__label">${statechart.key}</summary>`);
22
+ println(` <div class="state-details__description"><div class="state-details__chart">`);
23
23
  const printChildren = (str: string) => println(` ${str}`);
24
24
  buildStatechartSvg({
25
25
  graph: statechart.graph,
@@ -28,11 +28,11 @@ export function buildStatechartsPanel({
28
28
  initialStateKey,
29
29
  finalStateKey,
30
30
  });
31
- println(` </div>`);
31
+ println(` </div>`);
32
32
  for (const child of statechart.children || []) {
33
33
  render(child, printChildren);
34
34
  }
35
- println(` </div>`);
36
- println(`</details>`);
35
+ println(` </div>`);
36
+ println(` </details>`);
37
37
  }
38
38
  }
package/src/svg/index.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from "./buildStatechartCss.ts";
2
2
  export * from "./buildStatechartsPanel.ts";
3
+ export * from "./buildStatechartStyles.ts";
3
4
  export * from "./buildStatechartSvg.ts";
@@ -0,0 +1,42 @@
1
+ import { toKebabCase } from "./toKebabCase.ts";
2
+
3
+ export interface CssTree extends Record<string, string | number | CssTree> {}
4
+
5
+ export function serializeCss(obj: CssTree) {
6
+ return visitStyle(obj).join("\n");
7
+
8
+ function visitStyle(
9
+ obj: CssTree,
10
+ lines: string[] = [],
11
+ prefixes: string[] = [""]
12
+ ) {
13
+ for (const [key, style] of Object.entries(obj)) {
14
+ const selectors = [];
15
+ for (const sel of key.split(",").map((_) => _.trim())) {
16
+ for (const prefix of prefixes) {
17
+ const selector = sel.replace(/^(.)/, (_, ch) =>
18
+ ch === "&" ? prefix : prefix.length > 0 ? prefix + " " + ch : ch
19
+ );
20
+ selectors.push(selector);
21
+ }
22
+ }
23
+ const selectorRules = [];
24
+ const extensions: string[] = [];
25
+ for (let [key, value] of Object.entries(style)) {
26
+ if (typeof value === "object") {
27
+ visitStyle({ [key]: value }, extensions, selectors);
28
+ } else {
29
+ key = toKebabCase(key);
30
+ selectorRules.push(` ${key}: ${value};`);
31
+ }
32
+ }
33
+ if (selectorRules.length) {
34
+ lines.push(`${selectors.join(",\n")} {`);
35
+ lines.push(...selectorRules);
36
+ lines.push(`}`);
37
+ }
38
+ lines.push(...extensions);
39
+ }
40
+ return lines;
41
+ }
42
+ }