@statewalker/fsm-charts 0.1.1 → 0.1.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.
@@ -0,0 +1,375 @@
1
+ import { __publicField } from './chunk-XEO3YXBM.js';
2
+
3
+ // src/runtime/RuntimeStatechartApi.ts
4
+ var RuntimeStatechartApi = class {
5
+ constructor({
6
+ element,
7
+ statechart
8
+ }) {
9
+ __publicField(this, "element");
10
+ __publicField(this, "statechart");
11
+ __publicField(this, "_listenersCounter", 0);
12
+ __publicField(this, "_transitionsClickListeners", {});
13
+ __publicField(this, "_statesClickListeners", {});
14
+ __publicField(this, "_registry", {});
15
+ this.element = element;
16
+ this.statechart = statechart;
17
+ this._bindListeners();
18
+ }
19
+ close() {
20
+ Object.values(this._registry).forEach((r) => r && r());
21
+ this._registry = {};
22
+ this._transitionsClickListeners = {};
23
+ this._statesClickListeners = {};
24
+ }
25
+ onStateClick(listener) {
26
+ return this._addListener(this._statesClickListeners, listener);
27
+ }
28
+ onTransitionClick(listener) {
29
+ return this._addListener(this._transitionsClickListeners, listener);
30
+ }
31
+ openStatePanels() {
32
+ this._setStatePanelStatus(`details[data-state-id]`, true);
33
+ }
34
+ closeStatePanels() {
35
+ this._setStatePanelStatus(`details[data-state-id]`, false);
36
+ }
37
+ isStatePanelOpen(stateId) {
38
+ const elm = this.element.querySelector(
39
+ `details[data-state-id="${stateId}"]`
40
+ );
41
+ return elm && !!elm.open;
42
+ }
43
+ toggleState(...stateIds) {
44
+ const lastId = stateIds[stateIds.length - 1];
45
+ const selected = this.isStateSelected(lastId);
46
+ this.deselectStates();
47
+ this.closeStatePanels();
48
+ this.activateStates(...stateIds);
49
+ if (selected) {
50
+ this.deselectState(lastId);
51
+ this.closeStatePanel(lastId);
52
+ }
53
+ return !selected;
54
+ }
55
+ activateStates(...stateIds) {
56
+ for (let i = 0; i < stateIds.length; i++) {
57
+ const id = stateIds[i];
58
+ this.openStatePanel(id);
59
+ this.selectState(id);
60
+ }
61
+ }
62
+ // toggleStatePanel(stateId) {
63
+ // const elm = this.element.querySelector(
64
+ // `details[data-state-id="${stateId}"]`
65
+ // );
66
+ // if (elm) {
67
+ // elm.open = !elm.open;
68
+ // }
69
+ // }
70
+ closeStatePanel(stateId) {
71
+ this._setStatePanelStatus(`details[data-state-id="${stateId}"]`, false);
72
+ }
73
+ openStatePanel(stateId) {
74
+ this._setStatePanelStatus(`details[data-state-id="${stateId}"]`, true);
75
+ }
76
+ deselectTransitions(modifier = "selected") {
77
+ this._removeTransitionsModifier("[data-transition-id]", modifier);
78
+ }
79
+ deselectTransition(id, modifier = "selected") {
80
+ this._removeTransitionsModifier(`[data-transition-id="${id}"]`, modifier);
81
+ }
82
+ selectTransition(id, modifier = "selected") {
83
+ this._addTransitionsModifier(`[data-transition-id="${id}"]`, modifier);
84
+ }
85
+ toggleTransition(id, modifier = "selected") {
86
+ const selected = this.isTransitionSelected(id, modifier);
87
+ this.deselectTransitions();
88
+ if (!selected) {
89
+ this.selectTransition(id, modifier);
90
+ }
91
+ }
92
+ isTransitionSelected(id, modifier = "selected") {
93
+ const elm = this.element.querySelector(`[data-transition-id="${id}"]`);
94
+ const stateModifier = `transition--${modifier}`;
95
+ return !!elm?.classList.contains(stateModifier);
96
+ }
97
+ deselectStates(modifier = "selected") {
98
+ this._removeStateModifier("[data-state-id]", modifier);
99
+ }
100
+ isStateSelected(id, modifier = "selected") {
101
+ const elm = this.element.querySelector(`[data-state-id="${id}"]`);
102
+ const stateModifier = `state--${modifier}`;
103
+ return !!elm?.classList.contains(stateModifier);
104
+ }
105
+ deselectState(id, modifier = "selected") {
106
+ this._removeStateModifier(`[data-state-id="${id}"]`, modifier);
107
+ }
108
+ selectState(id, modifier = "selected") {
109
+ this._addStateModifier(`[data-state-id="${id}"]`, modifier);
110
+ }
111
+ // Internal methods
112
+ _addListener(index, listener) {
113
+ const id = this._listenersCounter++;
114
+ index[id] = listener;
115
+ return () => {
116
+ delete index[id];
117
+ };
118
+ }
119
+ _bindListeners() {
120
+ const bindListeners = (index, dataField, eventType) => {
121
+ function newEventListener(index2, elm) {
122
+ return (ev) => {
123
+ ev.preventDefault();
124
+ ev.stopPropagation();
125
+ let id = elm.getAttribute(dataField);
126
+ if (!id) {
127
+ id = elm.closest(`[${dataField}]`)?.getAttribute(dataField);
128
+ }
129
+ if (id) {
130
+ for (const listener of Object.values(index2)) {
131
+ listener(id);
132
+ }
133
+ }
134
+ };
135
+ }
136
+ [...this.element.querySelectorAll(`[${dataField}]`)].forEach((elm) => {
137
+ const eventListener = newEventListener(index, elm);
138
+ elm.addEventListener(eventType, eventListener);
139
+ this._addListener(
140
+ this._registry,
141
+ () => elm.removeEventListener(eventType, eventListener)
142
+ );
143
+ });
144
+ };
145
+ bindListeners(this._statesClickListeners, "data-state-id", "click");
146
+ bindListeners(
147
+ this._transitionsClickListeners,
148
+ "data-transition-id",
149
+ "click"
150
+ );
151
+ }
152
+ _setStatePanelStatus(selector, open) {
153
+ [...this.element.querySelectorAll(selector)].forEach((elm) => {
154
+ if (open)
155
+ elm.setAttribute("open", "open");
156
+ else
157
+ elm.removeAttribute("open");
158
+ });
159
+ }
160
+ _addStateModifier(cssSelector, modifier = "selected") {
161
+ this._applyStateModifier(
162
+ cssSelector,
163
+ modifier,
164
+ (elm, cls) => elm && elm.classList.add(cls)
165
+ );
166
+ }
167
+ _removeStateModifier(cssSelector, modifier = "selected") {
168
+ this._applyStateModifier(
169
+ cssSelector,
170
+ modifier,
171
+ (elm, cls) => elm && elm.classList.remove(cls)
172
+ );
173
+ }
174
+ _applyStateModifier(cssSelector, modifier = "selected", apply) {
175
+ const detailsModifier = `state-details--${modifier}`;
176
+ const stateLabelsModifier = `state-details__label--${modifier}`;
177
+ const stateModifier = `state--${modifier}`;
178
+ [...this.element.querySelectorAll(cssSelector)].forEach((elm) => {
179
+ if (elm.tagName.toLowerCase() === "details") {
180
+ apply(elm, detailsModifier);
181
+ const labelElm = elm.querySelector(".state-details__label");
182
+ if (labelElm)
183
+ apply(labelElm, stateLabelsModifier);
184
+ } else {
185
+ apply(elm, stateModifier);
186
+ }
187
+ });
188
+ }
189
+ _addTransitionsModifier(cssSelector, modifier = "selected") {
190
+ this._applyTransitionsModifier(
191
+ cssSelector,
192
+ modifier,
193
+ (elm, cls) => elm && elm.classList.add(cls)
194
+ );
195
+ }
196
+ _removeTransitionsModifier(cssSelector, modifier = "selected") {
197
+ this._applyTransitionsModifier(
198
+ cssSelector,
199
+ modifier,
200
+ (elm, cls) => elm && elm.classList.remove(cls)
201
+ );
202
+ }
203
+ _applyTransitionsModifier(cssSelector, modifier = "selected", apply) {
204
+ const transitionModifier = `transition--${modifier}`;
205
+ const transitionLineModifier = `transition__line--${modifier}`;
206
+ const transitionBoxModifier = `transition__box--${modifier}`;
207
+ const transitionLabelModifier = `transition__label--${modifier}`;
208
+ [...this.element.querySelectorAll(cssSelector)].forEach((elm) => {
209
+ apply(elm, transitionModifier);
210
+ apply(elm.querySelector(".transition__line"), transitionLineModifier);
211
+ apply(elm.querySelector(".transition__box"), transitionBoxModifier);
212
+ apply(elm.querySelector(".transition__label"), transitionLabelModifier);
213
+ });
214
+ }
215
+ };
216
+
217
+ // src/runtime/StateChartIndex.ts
218
+ var StateChartIndex = class {
219
+ constructor({ statechart }) {
220
+ __publicField(this, "statechart");
221
+ __publicField(this, "_chartsIndex", {});
222
+ __publicField(this, "_nodesIndex", {});
223
+ __publicField(this, "_nodesParentsIndex", {});
224
+ __publicField(this, "_edgeIndex", {});
225
+ __publicField(this, "_edgesParentsIndex", {});
226
+ this.statechart = statechart;
227
+ this._buildIndexes();
228
+ }
229
+ /**
230
+ * Transforms a stack of state keys to the corresponding identifiers.
231
+ * If a key in the stack was not found then this method returns
232
+ * an empty stack.
233
+ * @param stack a stack of state keys
234
+ * @returns a stack of state identifiers for the given states
235
+ */
236
+ getStatesIds(...stack) {
237
+ let result = [];
238
+ let list = [this.statechart];
239
+ for (let i = 0; list?.length > 0 && i < stack.length; i++) {
240
+ const key = stack[i];
241
+ const node = list.find((node2) => node2.key === key);
242
+ if (!node)
243
+ break;
244
+ result.push(node.id);
245
+ list = [...node.children || [], ...node.nodes || []];
246
+ }
247
+ return result.length === stack.length ? result : [];
248
+ }
249
+ /**
250
+ * Returns graph nodes for the specified identifier.
251
+ * @param id the id of a node
252
+ * @returns a stack of nodes
253
+ */
254
+ getStackByStateId(id) {
255
+ if (!id)
256
+ return [];
257
+ const node = this._chartsIndex[id] || this._nodesIndex[id];
258
+ if (!node)
259
+ return [];
260
+ const parentId = this._nodesParentsIndex[id];
261
+ if (parentId) {
262
+ const parentStack = this.getStackByStateId(parentId);
263
+ if (!parentStack.length)
264
+ return [];
265
+ parentStack.push(node);
266
+ return parentStack;
267
+ } else {
268
+ return [node];
269
+ }
270
+ }
271
+ /**
272
+ * Returns an edge object corresponding to the transition from the state
273
+ * @param stateId identifier of the source state
274
+ * @param event an event triggering the transition
275
+ * @returns graph edge
276
+ */
277
+ getTransition(stateId, event) {
278
+ const stack = this.getStackByStateId(stateId);
279
+ const state = stack.pop();
280
+ if (!state || !stack.length)
281
+ return;
282
+ const parent = stack[stack.length - 1];
283
+ const t = parent.edges.find(
284
+ (t2) => t2.from === state.key && t2.event === event
285
+ );
286
+ return t;
287
+ }
288
+ /**
289
+ * Returns a list of edge objects corresponding to all transitions from the state
290
+ * @param stateId identifier of the source state
291
+ * @returns a list of graph edges
292
+ */
293
+ getTransitions(stateId) {
294
+ const stack = this.getStackByStateId(stateId);
295
+ const state = stack.pop();
296
+ if (!state || !stack.length)
297
+ return [];
298
+ const parent = stack[stack.length - 1];
299
+ return parent.edges.filter((t) => t.from === state.key);
300
+ }
301
+ /**
302
+ * Returns a graph node corresponding to the specified state id.
303
+ * @param stateId identifier of the state
304
+ * @returns graph node
305
+ * @see getStateChart
306
+ */
307
+ getStateNode(stateId) {
308
+ return stateId ? this._nodesIndex[stateId] : void 0;
309
+ }
310
+ /**
311
+ * Returns a chart corresponding to the specified state id.
312
+ * @param stateId identifier of the state
313
+ * @returns graph node
314
+ * @see getStateNode
315
+ */
316
+ getStateChart(stateId) {
317
+ return stateId ? this._chartsIndex[stateId] : void 0;
318
+ }
319
+ /**
320
+ * Returns ids of the previous and next state for the specified transition
321
+ * @param transition a graph edge instance defining transition
322
+ * @returns an array with two identifiers - [sourceStateId, targetStateId] - the source state id and target state id
323
+ */
324
+ getTransitionStateIds(transition) {
325
+ const result = [
326
+ void 0,
327
+ void 0
328
+ ];
329
+ if (!transition)
330
+ return result;
331
+ const parentId = this._edgesParentsIndex[transition.id];
332
+ if (!parentId)
333
+ return result;
334
+ const parent = this._chartsIndex[parentId];
335
+ if (!parent)
336
+ return result;
337
+ parent.nodes.forEach((node) => {
338
+ if (node.key === transition.from) {
339
+ result[0] = node.id;
340
+ }
341
+ if (node.key === transition.to) {
342
+ result[1] = node.id;
343
+ }
344
+ });
345
+ return result;
346
+ }
347
+ // ----------------------------------------------------------
348
+ // Internal methods
349
+ _buildIndexes() {
350
+ const addToIndex = (chart) => {
351
+ const chartId = chart.id;
352
+ this._chartsIndex[chartId] = chart;
353
+ if (!(chartId in this._nodesIndex)) {
354
+ this._nodesIndex[chartId] = chart;
355
+ }
356
+ chart.nodes.forEach((node) => {
357
+ const nodeId = node.id;
358
+ this._nodesIndex[nodeId] = node;
359
+ this._nodesParentsIndex[nodeId] = chartId;
360
+ });
361
+ chart.edges.forEach((edge) => {
362
+ const edgeId = edge.id;
363
+ this._edgeIndex[edgeId] = edge;
364
+ this._edgesParentsIndex[edgeId] = chartId;
365
+ });
366
+ (chart.children || []).forEach((child) => {
367
+ this._nodesParentsIndex[child.id] = chartId;
368
+ addToIndex(child);
369
+ });
370
+ };
371
+ addToIndex(this.statechart);
372
+ }
373
+ };
374
+
375
+ export { RuntimeStatechartApi, StateChartIndex };
@@ -2474,8 +2474,9 @@ function buildFlatCharts({
2474
2474
  try {
2475
2475
  setLodash(lodash);
2476
2476
  const graph = new Graph({
2477
- directed: true
2478
- }).setGraph({ rankdir: vertical ? "tb" : "lr" }).setDefaultEdgeLabel(() => ({}));
2477
+ directed: true,
2478
+ multigraph: true
2479
+ }).setGraph({ rankdir: vertical ? "tb" : "lr", multigraph: true });
2479
2480
  const nodesIndex = {};
2480
2481
  const getStateNodeId = (key, state = key) => {
2481
2482
  let id = nodesIndex[key];
@@ -2492,12 +2493,18 @@ function buildFlatCharts({
2492
2493
  const toStateKey = toState || finalStateKey;
2493
2494
  const to = getStateNodeId(toStateKey, toState);
2494
2495
  const params = getTransitionParams(fromStateKey, event, toStateKey);
2495
- graph.setEdge(from, to, {
2496
- id: newId("t"),
2497
- ...params
2498
- });
2496
+ const edgeId = newId("t");
2497
+ graph.setEdge(
2498
+ from,
2499
+ to,
2500
+ {
2501
+ id: edgeId,
2502
+ ...params
2503
+ },
2504
+ edgeId
2505
+ );
2499
2506
  }
2500
- layout(graph, { rankdir: vertical ? "tb" : "lr" });
2507
+ layout(graph, { rankdir: "lr" });
2501
2508
  const [top, right, bottom, left] = getPadding(padding);
2502
2509
  const nodes = graph.nodes().map((id) => {
2503
2510
  const n = graph.node(id);
@@ -1,3 +1,3 @@
1
- export { buildCharts, buildFlatCharts, getGraphParamsProvider, getLabelDimensions } from './chunk-ZSGS37QH.js';
1
+ export { buildCharts, buildFlatCharts, getGraphParamsProvider, getLabelDimensions } from './chunk-OTGN3OYG.js';
2
2
  import './chunk-NXWOFQMO.js';
3
3
  import './chunk-XEO3YXBM.js';
@@ -1,6 +1,48 @@
1
1
  import { S as StateChart, c as StateGraphNode, b as StateGraphEdge } from './StateChart-PdtJDEPy.js';
2
2
  export { D as Dimensions, F as FsmStateConfig, G as GraphParamsProvider, P as Padding, a as Position, T as Transition, d as TransitionsGraph } from './StateChart-PdtJDEPy.js';
3
3
 
4
+ type RuntimeListener = (id: string) => void;
5
+ declare class RuntimeStatechartApi {
6
+ element: HTMLElement;
7
+ statechart: StateChart;
8
+ _listenersCounter: number;
9
+ _transitionsClickListeners: Record<number, RuntimeListener>;
10
+ _statesClickListeners: Record<number, RuntimeListener>;
11
+ _registry: Record<number, () => void>;
12
+ constructor({ element, statechart, }: {
13
+ element: HTMLElement;
14
+ statechart: StateChart;
15
+ });
16
+ close(): void;
17
+ onStateClick(listener: RuntimeListener): () => void;
18
+ onTransitionClick(listener: RuntimeListener): () => void;
19
+ openStatePanels(): void;
20
+ closeStatePanels(): void;
21
+ isStatePanelOpen(stateId: string): boolean;
22
+ toggleState(...stateIds: string[]): boolean;
23
+ activateStates(...stateIds: string[]): void;
24
+ closeStatePanel(stateId: string): void;
25
+ openStatePanel(stateId: string): void;
26
+ deselectTransitions(modifier?: string): void;
27
+ deselectTransition(id: string, modifier?: string): void;
28
+ selectTransition(id: string, modifier?: string): void;
29
+ toggleTransition(id: string, modifier?: string): void;
30
+ isTransitionSelected(id: string, modifier?: string): boolean;
31
+ deselectStates(modifier?: string): void;
32
+ isStateSelected(id: string, modifier?: string): boolean;
33
+ deselectState(id: string, modifier?: string): void;
34
+ selectState(id: string, modifier?: string): void;
35
+ _addListener<T = Function>(index: Record<string, T>, listener: T): () => void;
36
+ _bindListeners(): void;
37
+ _setStatePanelStatus(selector: string, open: boolean): void;
38
+ _addStateModifier(cssSelector: string, modifier?: string): void;
39
+ _removeStateModifier(cssSelector: string, modifier?: string): void;
40
+ _applyStateModifier(cssSelector: string, modifier: string | undefined, apply: (elm: Element | null, className: string) => void): void;
41
+ _addTransitionsModifier(cssSelector: string, modifier?: string): void;
42
+ _removeTransitionsModifier(cssSelector: string, modifier?: string): void;
43
+ _applyTransitionsModifier(cssSelector: string, modifier: string | undefined, apply: (elm: Element | null, className: string) => void): void;
44
+ }
45
+
4
46
  declare class StateChartIndex {
5
47
  statechart: StateChart;
6
48
  _chartsIndex: Record<string, StateChart>;
@@ -61,4 +103,4 @@ declare class StateChartIndex {
61
103
  _buildIndexes(): void;
62
104
  }
63
105
 
64
- export { StateChart, StateChartIndex, StateGraphEdge, StateGraphNode };
106
+ export { type RuntimeListener, RuntimeStatechartApi, StateChart, StateChartIndex, StateGraphEdge, StateGraphNode };
@@ -1,2 +1,2 @@
1
- export { StateChartIndex } from './chunk-EU64L34M.js';
1
+ export { RuntimeStatechartApi, StateChartIndex } from './chunk-4KTXTN7J.js';
2
2
  import './chunk-XEO3YXBM.js';
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { D as Dimensions, F as FsmStateConfig, G as GraphParamsProvider, P as Padding, a as Position, S as StateChart, b as StateGraphEdge, c as StateGraphNode, T as Transition, d as TransitionsGraph } from './StateChart-PdtJDEPy.js';
2
2
  export { buildCharts, buildFlatCharts, getGraphParamsProvider, getLabelDimensions } from './index-layout.js';
3
3
  export { C as CssTree, b as buildStatechartCss, c as buildStatechartStyles, d as buildStatechartSvg, a as buildStatechartsPanel, s as serializeCss } from './index-html-CbDZzk2n.js';
4
- export { StateChartIndex } from './index-runtime.js';
4
+ export { RuntimeListener, RuntimeStatechartApi, StateChartIndex } from './index-runtime.js';
5
5
 
6
6
  declare function toKebabCase(str: string): string;
7
7
 
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- export { buildCharts, buildFlatCharts, getGraphParamsProvider, getLabelDimensions } from './chunk-ZSGS37QH.js';
1
+ export { buildCharts, buildFlatCharts, getGraphParamsProvider, getLabelDimensions } from './chunk-OTGN3OYG.js';
2
2
  export { buildStatechartCss, buildStatechartStyles, buildStatechartSvg, buildStatechartsPanel, serializeCss, toKebabCase } from './chunk-YYNW6Q2T.js';
3
3
  import './chunk-NXWOFQMO.js';
4
- export { StateChartIndex } from './chunk-EU64L34M.js';
4
+ export { RuntimeStatechartApi, StateChartIndex } from './chunk-4KTXTN7J.js';
5
5
  import './chunk-XEO3YXBM.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@statewalker/fsm-charts",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "HFSM Implementation",
5
5
  "keywords": [],
6
6
  "homepage": "https://github.com/statewalker/statewalker-fsm-charts",
@@ -49,9 +49,8 @@ export function buildFlatCharts({
49
49
  setLodash(lodash);
50
50
  const graph = new Graph({
51
51
  directed: true,
52
- })
53
- .setGraph({ rankdir: vertical ? "tb" : "lr" })
54
- .setDefaultEdgeLabel(() => ({}));
52
+ multigraph: true,
53
+ }).setGraph({ rankdir: vertical ? "tb" : "lr", multigraph: true });
55
54
 
56
55
  const nodesIndex: Record<string, string> = {};
57
56
 
@@ -71,12 +70,18 @@ export function buildFlatCharts({
71
70
  const toStateKey = toState || finalStateKey;
72
71
  const to = getStateNodeId(toStateKey, toState);
73
72
  const params = getTransitionParams(fromStateKey, event, toStateKey);
74
- graph.setEdge(from, to, {
75
- id: newId("t"),
76
- ...params,
77
- });
73
+ const edgeId = newId("t");
74
+ graph.setEdge(
75
+ from,
76
+ to,
77
+ {
78
+ id: edgeId,
79
+ ...params,
80
+ },
81
+ edgeId
82
+ );
78
83
  }
79
- layout(graph, { rankdir: vertical ? "tb" : "lr" });
84
+ layout(graph, { rankdir: "lr" });
80
85
 
81
86
  const [top, right, bottom, left] = getPadding(padding);
82
87
 
@@ -0,0 +1,250 @@
1
+ import type { StateChart } from "../types/index.ts";
2
+
3
+ export type RuntimeListener = (id: string) => void;
4
+ export class RuntimeStatechartApi {
5
+ element: HTMLElement;
6
+ statechart: StateChart;
7
+ _listenersCounter: number = 0;
8
+ _transitionsClickListeners: Record<number, RuntimeListener> = {};
9
+ _statesClickListeners: Record<number, RuntimeListener> = {};
10
+ _registry: Record<number, () => void> = {};
11
+
12
+ constructor({
13
+ element,
14
+ statechart,
15
+ }: {
16
+ element: HTMLElement;
17
+ statechart: StateChart;
18
+ }) {
19
+ this.element = element;
20
+ this.statechart = statechart;
21
+ this._bindListeners();
22
+ }
23
+
24
+ close() {
25
+ Object.values(this._registry).forEach((r) => r && r());
26
+ this._registry = {};
27
+ this._transitionsClickListeners = {};
28
+ this._statesClickListeners = {};
29
+ }
30
+
31
+ onStateClick(listener: RuntimeListener) {
32
+ return this._addListener(this._statesClickListeners, listener);
33
+ }
34
+ onTransitionClick(listener: RuntimeListener) {
35
+ return this._addListener(this._transitionsClickListeners, listener);
36
+ }
37
+
38
+ openStatePanels() {
39
+ this._setStatePanelStatus(`details[data-state-id]`, true);
40
+ }
41
+ closeStatePanels() {
42
+ this._setStatePanelStatus(`details[data-state-id]`, false);
43
+ }
44
+ isStatePanelOpen(stateId: string) {
45
+ const elm = this.element.querySelector(
46
+ `details[data-state-id="${stateId}"]`
47
+ ) as HTMLDetailsElement;
48
+ return elm && !!elm.open;
49
+ }
50
+
51
+ toggleState(...stateIds: string[]) {
52
+ const lastId = stateIds[stateIds.length - 1];
53
+ const selected = this.isStateSelected(lastId);
54
+ this.deselectStates();
55
+ this.closeStatePanels();
56
+ this.activateStates(...stateIds);
57
+ if (selected) {
58
+ this.deselectState(lastId);
59
+ this.closeStatePanel(lastId);
60
+ }
61
+ return !selected;
62
+ }
63
+
64
+ activateStates(...stateIds: string[]) {
65
+ for (let i = 0; i < stateIds.length; i++) {
66
+ const id = stateIds[i];
67
+ this.openStatePanel(id);
68
+ this.selectState(id);
69
+ }
70
+ }
71
+
72
+ // toggleStatePanel(stateId) {
73
+ // const elm = this.element.querySelector(
74
+ // `details[data-state-id="${stateId}"]`
75
+ // );
76
+ // if (elm) {
77
+ // elm.open = !elm.open;
78
+ // }
79
+ // }
80
+
81
+ closeStatePanel(stateId: string) {
82
+ this._setStatePanelStatus(`details[data-state-id="${stateId}"]`, false);
83
+ }
84
+ openStatePanel(stateId: string) {
85
+ this._setStatePanelStatus(`details[data-state-id="${stateId}"]`, true);
86
+ }
87
+
88
+ deselectTransitions(modifier = "selected") {
89
+ this._removeTransitionsModifier("[data-transition-id]", modifier);
90
+ }
91
+ deselectTransition(id: string, modifier: string = "selected") {
92
+ this._removeTransitionsModifier(`[data-transition-id="${id}"]`, modifier);
93
+ }
94
+ selectTransition(id: string, modifier: string = "selected") {
95
+ this._addTransitionsModifier(`[data-transition-id="${id}"]`, modifier);
96
+ }
97
+ toggleTransition(id: string, modifier: string = "selected") {
98
+ const selected = this.isTransitionSelected(id, modifier);
99
+ this.deselectTransitions();
100
+ if (!selected) {
101
+ this.selectTransition(id, modifier);
102
+ }
103
+ // if (!selected) this.deselectTransition(id, modifier);
104
+ // else this.selectTransition(id, modifier);
105
+ }
106
+ isTransitionSelected(id: string, modifier: string = "selected") {
107
+ const elm = this.element.querySelector(`[data-transition-id="${id}"]`);
108
+ const stateModifier = `transition--${modifier}`;
109
+ return !!elm?.classList.contains(stateModifier);
110
+ }
111
+
112
+ deselectStates(modifier: string = "selected") {
113
+ this._removeStateModifier("[data-state-id]", modifier);
114
+ }
115
+ isStateSelected(id: string, modifier: string = "selected") {
116
+ const elm = this.element.querySelector(`[data-state-id="${id}"]`);
117
+ const stateModifier = `state--${modifier}`;
118
+ return !!elm?.classList.contains(stateModifier);
119
+ }
120
+ deselectState(id: string, modifier: string = "selected") {
121
+ this._removeStateModifier(`[data-state-id="${id}"]`, modifier);
122
+ }
123
+ selectState(id: string, modifier: string = "selected") {
124
+ this._addStateModifier(`[data-state-id="${id}"]`, modifier);
125
+ }
126
+
127
+ // Internal methods
128
+
129
+ _addListener<T = Function>(index: Record<string, T>, listener: T) {
130
+ const id = this._listenersCounter++;
131
+ index[id] = listener;
132
+ return () => {
133
+ delete index[id];
134
+ };
135
+ }
136
+
137
+ _bindListeners() {
138
+ const bindListeners = (
139
+ index: Record<string, RuntimeListener>,
140
+ dataField: string,
141
+ eventType: string
142
+ ) => {
143
+ function newEventListener(
144
+ index: Record<string, RuntimeListener>,
145
+ elm: Element
146
+ ) {
147
+ return (ev: Event) => {
148
+ ev.preventDefault();
149
+ ev.stopPropagation();
150
+ let id: string | null | undefined = elm.getAttribute(dataField);
151
+ if (!id) {
152
+ id = elm.closest(`[${dataField}]`)?.getAttribute(dataField);
153
+ }
154
+ if (id) {
155
+ for (const listener of Object.values(index)) {
156
+ listener(id);
157
+ }
158
+ }
159
+ };
160
+ }
161
+ [...this.element.querySelectorAll(`[${dataField}]`)].forEach((elm) => {
162
+ const eventListener = newEventListener(index, elm);
163
+ elm.addEventListener(eventType, eventListener);
164
+ this._addListener(this._registry, () =>
165
+ elm.removeEventListener(eventType, eventListener)
166
+ );
167
+ });
168
+ };
169
+ bindListeners(this._statesClickListeners, "data-state-id", "click");
170
+ bindListeners(
171
+ this._transitionsClickListeners,
172
+ "data-transition-id",
173
+ "click"
174
+ );
175
+ }
176
+
177
+ _setStatePanelStatus(selector: string, open: boolean) {
178
+ [...this.element.querySelectorAll(selector)].forEach((elm) => {
179
+ if (open) elm.setAttribute("open", "open");
180
+ else elm.removeAttribute("open");
181
+ });
182
+ }
183
+
184
+ _addStateModifier(cssSelector: string, modifier: string = "selected") {
185
+ this._applyStateModifier(
186
+ cssSelector,
187
+ modifier,
188
+ (elm, cls) => elm && elm.classList.add(cls)
189
+ );
190
+ }
191
+ _removeStateModifier(cssSelector: string, modifier: string = "selected") {
192
+ this._applyStateModifier(
193
+ cssSelector,
194
+ modifier,
195
+ (elm, cls) => elm && elm.classList.remove(cls)
196
+ );
197
+ }
198
+ _applyStateModifier(
199
+ cssSelector: string,
200
+ modifier: string = "selected",
201
+ apply: (elm: Element | null, className: string) => void
202
+ ) {
203
+ const detailsModifier = `state-details--${modifier}`;
204
+ const stateLabelsModifier = `state-details__label--${modifier}`;
205
+ const stateModifier = `state--${modifier}`;
206
+ [...this.element.querySelectorAll(cssSelector)].forEach((elm) => {
207
+ if (elm.tagName.toLowerCase() === "details") {
208
+ apply(elm, detailsModifier);
209
+ const labelElm = elm.querySelector(".state-details__label");
210
+ if (labelElm) apply(labelElm, stateLabelsModifier);
211
+ } else {
212
+ apply(elm, stateModifier);
213
+ }
214
+ });
215
+ }
216
+
217
+ _addTransitionsModifier(cssSelector: string, modifier: string = "selected") {
218
+ this._applyTransitionsModifier(
219
+ cssSelector,
220
+ modifier,
221
+ (elm, cls) => elm && elm.classList.add(cls)
222
+ );
223
+ }
224
+ _removeTransitionsModifier(
225
+ cssSelector: string,
226
+ modifier: string = "selected"
227
+ ) {
228
+ this._applyTransitionsModifier(
229
+ cssSelector,
230
+ modifier,
231
+ (elm, cls) => elm && elm.classList.remove(cls)
232
+ );
233
+ }
234
+ _applyTransitionsModifier(
235
+ cssSelector: string,
236
+ modifier: string = "selected",
237
+ apply: (elm: Element | null, className: string) => void
238
+ ) {
239
+ const transitionModifier = `transition--${modifier}`;
240
+ const transitionLineModifier = `transition__line--${modifier}`;
241
+ const transitionBoxModifier = `transition__box--${modifier}`;
242
+ const transitionLabelModifier = `transition__label--${modifier}`;
243
+ [...this.element.querySelectorAll(cssSelector)].forEach((elm) => {
244
+ apply(elm, transitionModifier);
245
+ apply(elm.querySelector(".transition__line"), transitionLineModifier);
246
+ apply(elm.querySelector(".transition__box"), transitionBoxModifier);
247
+ apply(elm.querySelector(".transition__label"), transitionLabelModifier);
248
+ });
249
+ }
250
+ }
@@ -32,8 +32,7 @@ export class StateChartIndex {
32
32
  const node = list.find((node) => node.key === key);
33
33
  if (!node) break;
34
34
  result.push(node.id);
35
- list = node.children;
36
- if (!list?.length) list = node.nodes;
35
+ list = [...(node.children || []), ...(node.nodes || [])];
37
36
  }
38
37
  return result.length === stack.length ? result : [];
39
38
  }
@@ -1 +1,2 @@
1
+ export * from "./RuntimeStatechartApi.ts";
1
2
  export * from "./StateChartIndex.ts";
@@ -1,163 +0,0 @@
1
- import { __publicField } from './chunk-XEO3YXBM.js';
2
-
3
- // src/runtime/StateChartIndex.ts
4
- var StateChartIndex = class {
5
- constructor({ statechart }) {
6
- __publicField(this, "statechart");
7
- __publicField(this, "_chartsIndex", {});
8
- __publicField(this, "_nodesIndex", {});
9
- __publicField(this, "_nodesParentsIndex", {});
10
- __publicField(this, "_edgeIndex", {});
11
- __publicField(this, "_edgesParentsIndex", {});
12
- this.statechart = statechart;
13
- this._buildIndexes();
14
- }
15
- /**
16
- * Transforms a stack of state keys to the corresponding identifiers.
17
- * If a key in the stack was not found then this method returns
18
- * an empty stack.
19
- * @param stack a stack of state keys
20
- * @returns a stack of state identifiers for the given states
21
- */
22
- getStatesIds(...stack) {
23
- let result = [];
24
- let list = [this.statechart];
25
- for (let i = 0; list?.length > 0 && i < stack.length; i++) {
26
- const key = stack[i];
27
- const node = list.find((node2) => node2.key === key);
28
- if (!node)
29
- break;
30
- result.push(node.id);
31
- list = node.children;
32
- if (!list?.length)
33
- list = node.nodes;
34
- }
35
- return result.length === stack.length ? result : [];
36
- }
37
- /**
38
- * Returns graph nodes for the specified identifier.
39
- * @param id the id of a node
40
- * @returns a stack of nodes
41
- */
42
- getStackByStateId(id) {
43
- if (!id)
44
- return [];
45
- const node = this._chartsIndex[id] || this._nodesIndex[id];
46
- if (!node)
47
- return [];
48
- const parentId = this._nodesParentsIndex[id];
49
- if (parentId) {
50
- const parentStack = this.getStackByStateId(parentId);
51
- if (!parentStack.length)
52
- return [];
53
- parentStack.push(node);
54
- return parentStack;
55
- } else {
56
- return [node];
57
- }
58
- }
59
- /**
60
- * Returns an edge object corresponding to the transition from the state
61
- * @param stateId identifier of the source state
62
- * @param event an event triggering the transition
63
- * @returns graph edge
64
- */
65
- getTransition(stateId, event) {
66
- const stack = this.getStackByStateId(stateId);
67
- const state = stack.pop();
68
- if (!state || !stack.length)
69
- return;
70
- const parent = stack[stack.length - 1];
71
- const t = parent.edges.find(
72
- (t2) => t2.from === state.key && t2.event === event
73
- );
74
- return t;
75
- }
76
- /**
77
- * Returns a list of edge objects corresponding to all transitions from the state
78
- * @param stateId identifier of the source state
79
- * @returns a list of graph edges
80
- */
81
- getTransitions(stateId) {
82
- const stack = this.getStackByStateId(stateId);
83
- const state = stack.pop();
84
- if (!state || !stack.length)
85
- return [];
86
- const parent = stack[stack.length - 1];
87
- return parent.edges.filter((t) => t.from === state.key);
88
- }
89
- /**
90
- * Returns a graph node corresponding to the specified state id.
91
- * @param stateId identifier of the state
92
- * @returns graph node
93
- * @see getStateChart
94
- */
95
- getStateNode(stateId) {
96
- return stateId ? this._nodesIndex[stateId] : void 0;
97
- }
98
- /**
99
- * Returns a chart corresponding to the specified state id.
100
- * @param stateId identifier of the state
101
- * @returns graph node
102
- * @see getStateNode
103
- */
104
- getStateChart(stateId) {
105
- return stateId ? this._chartsIndex[stateId] : void 0;
106
- }
107
- /**
108
- * Returns ids of the previous and next state for the specified transition
109
- * @param transition a graph edge instance defining transition
110
- * @returns an array with two identifiers - [sourceStateId, targetStateId] - the source state id and target state id
111
- */
112
- getTransitionStateIds(transition) {
113
- const result = [
114
- void 0,
115
- void 0
116
- ];
117
- if (!transition)
118
- return result;
119
- const parentId = this._edgesParentsIndex[transition.id];
120
- if (!parentId)
121
- return result;
122
- const parent = this._chartsIndex[parentId];
123
- if (!parent)
124
- return result;
125
- parent.nodes.forEach((node) => {
126
- if (node.key === transition.from) {
127
- result[0] = node.id;
128
- }
129
- if (node.key === transition.to) {
130
- result[1] = node.id;
131
- }
132
- });
133
- return result;
134
- }
135
- // ----------------------------------------------------------
136
- // Internal methods
137
- _buildIndexes() {
138
- const addToIndex = (chart) => {
139
- const chartId = chart.id;
140
- this._chartsIndex[chartId] = chart;
141
- if (!(chartId in this._nodesIndex)) {
142
- this._nodesIndex[chartId] = chart;
143
- }
144
- chart.nodes.forEach((node) => {
145
- const nodeId = node.id;
146
- this._nodesIndex[nodeId] = node;
147
- this._nodesParentsIndex[nodeId] = chartId;
148
- });
149
- chart.edges.forEach((edge) => {
150
- const edgeId = edge.id;
151
- this._edgeIndex[edgeId] = edge;
152
- this._edgesParentsIndex[edgeId] = chartId;
153
- });
154
- (chart.children || []).forEach((child) => {
155
- this._nodesParentsIndex[child.id] = chartId;
156
- addToIndex(child);
157
- });
158
- };
159
- addToIndex(this.statechart);
160
- }
161
- };
162
-
163
- export { StateChartIndex };