jssm 5.124.1 → 5.125.0

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/README.md CHANGED
@@ -18,10 +18,10 @@ Please edit the file it's derived from, instead: `./src/md/readme_base.md`
18
18
 
19
19
 
20
20
 
21
- * Generated for version 5.124.1 at 5/19/2026, 1:07:52 PM
21
+ * Generated for version 5.125.0 at 5/19/2026, 6:05:01 PM
22
22
 
23
23
  -->
24
- # jssm 5.124.1
24
+ # jssm 5.125.0
25
25
 
26
26
  [**Try the live editor**](https://stonecypher.github.io/jssm-viz-demo/graph_explorer.html) ·
27
27
  [Documentation](https://stonecypher.github.io/jssm/docs/) ·
@@ -281,7 +281,7 @@ That decision shows up everywhere downstream:
281
281
  or run `npm run benny` against your own machine.
282
282
 
283
283
  - **More thoroughly tested than any other JavaScript state-machine
284
- library.** 6,078 tests at 100.0% line coverage
284
+ library.** 6,095 tests at 100.0% line coverage
285
285
  ([report](https://coveralls.io/github/StoneCypher/jssm)), plus
286
286
  fuzz testing via `fast-check`, with parser test data across ten natural
287
287
  languages and Emoji.
@@ -414,11 +414,11 @@ If your contribution is missing here, please open an issue.
414
414
 
415
415
  <br/>
416
416
 
417
- ***6,078 tests***, run 56,865 times.
417
+ ***6,095 tests***, run 56,882 times.
418
418
 
419
- - 5,565 specs with 100.0% coverage
419
+ - 5,582 specs with 100.0% coverage
420
420
  - 513 fuzz tests with 4.5% coverage
421
- - 4,331 TypeScript lines - 1.4 tests per line, 13.1 generated tests per line
421
+ - 4,355 TypeScript lines - 1.4 tests per line, 13.1 generated tests per line
422
422
 
423
423
  [![Actions Status](https://github.com/StoneCypher/jssm/workflows/Node%20CI/badge.svg)](https://github.com/StoneCypher/jssm/actions)
424
424
  [![NPM version](https://img.shields.io/npm/v/jssm.svg)](https://www.npmjs.com/package/jssm)
package/dist/cdn/viz.js CHANGED
@@ -21041,7 +21041,7 @@ var constants = /*#__PURE__*/Object.freeze({
21041
21041
  * Useful for runtime diagnostics and for embedding in serialized machine
21042
21042
  * snapshots so that deserializers can detect version-skew.
21043
21043
  */
21044
- const version = "5.124.1";
21044
+ const version = "5.125.0";
21045
21045
 
21046
21046
  // whargarbl lots of these return arrays could/should be sets
21047
21047
  const { state_name_chars, state_name_first_chars, action_label_chars } = constants;
@@ -24357,18 +24357,113 @@ function vc(col) {
24357
24357
  return (_a = default_viz_colors[col]) !== null && _a !== void 0 ? _a : '';
24358
24358
  }
24359
24359
  /**
24360
- * Build a graphviz-safe node identifier for a state, by index. Accepts
24361
- * either a `string[]` (used historically; O(n) per call) or a
24362
- * precomputed `Map<state, index>` (used by rendering hot paths; O(1)
24363
- * per call). The map form is used during dot generation; the array
24364
- * form is retained for direct test access via `_test`.
24360
+ * Convert a state name into a URL-friendly slug suitable for use as the
24361
+ * body of a dot/SVG node identifier. The transformation is:
24362
+ *
24363
+ * 1. Lowercase
24364
+ * 2. Any run of characters outside `[a-z0-9]` (after lowercasing) becomes
24365
+ * a single `-`
24366
+ * 3. Leading and trailing `-` are trimmed
24367
+ *
24368
+ * If the result is empty (e.g. for a state named `"!!!"`), the empty
24369
+ * string is returned — callers are expected to fall back to an indexed
24370
+ * placeholder like `node-N`. See {@link slug_states} for the collision-
24371
+ * resolving wrapper that consumes this helper.
24372
+ *
24373
+ * ```typescript
24374
+ * slug_for('Green Light'); // 'green-light'
24375
+ * slug_for('!!!'); // ''
24376
+ * slug_for(' Foo Bar '); // 'foo-bar'
24377
+ * ```
24378
+ *
24379
+ * @param state The state name to slugify.
24380
+ * @returns The lowercase hyphen-separated slug, or empty string if none of
24381
+ * the characters were retainable.
24382
+ *
24383
+ * @internal
24384
+ */
24385
+ function slug_for(state) {
24386
+ return state
24387
+ .toLowerCase()
24388
+ .replace(/[^a-z0-9]+/g, '-')
24389
+ .replace(/^-+|-+$/g, '');
24390
+ }
24391
+ /**
24392
+ * Build a `Map<state, slug>` assigning every state in `states` a unique,
24393
+ * deterministic, URL-safe slug used as its dot/SVG node identifier.
24394
+ *
24395
+ * Algorithm:
24396
+ *
24397
+ * 1. Slug each state via {@link slug_for}. States whose slug comes out
24398
+ * empty fall back to `node-N`, where `N` is the state's declaration
24399
+ * index (1-based, to match user-visible numbering).
24400
+ * 2. Walk the state list in declaration order, tracking how many times
24401
+ * each base slug has already been used. The first occurrence keeps
24402
+ * the base slug; subsequent collisions get `-2`, `-3`, … suffixes.
24403
+ * If the proposed suffixed slug itself collides with a base slug
24404
+ * used later, the counter advances until a free slot is found.
24405
+ *
24406
+ * This yields a deterministic mapping given the state-declaration order,
24407
+ * so output is stable across runs.
24408
+ *
24409
+ * ```typescript
24410
+ * slug_states(['Red Light', 'red-light']);
24411
+ * // Map { 'Red Light' => 'red-light', 'red-light' => 'red-light-2' }
24412
+ *
24413
+ * slug_states(['!!!', '???']);
24414
+ * // Map { '!!!' => 'node-1', '???' => 'node-2' }
24415
+ * ```
24416
+ *
24417
+ * @param states States in declaration order.
24418
+ * @returns A `Map` from each state name to its unique slug.
24419
+ *
24420
+ * @internal
24421
+ */
24422
+ function slug_states(states) {
24423
+ const used = new Set();
24424
+ const out = new Map();
24425
+ states.forEach((s, i) => {
24426
+ const base = slug_for(s) || `node-${i + 1}`;
24427
+ let candidate = base;
24428
+ let n = 2;
24429
+ while (used.has(candidate)) {
24430
+ candidate = `${base}-${n}`;
24431
+ n += 1;
24432
+ }
24433
+ used.add(candidate);
24434
+ out.set(s, candidate);
24435
+ });
24436
+ return out;
24437
+ }
24438
+ /**
24439
+ * Build a graphviz-safe node identifier for a state. Accepts either a
24440
+ * `string[]` (legacy test-only path; returns an index-based `n0`/`n1`
24441
+ * identifier via `indexOf`), or a precomputed `Map<state, slug>` produced
24442
+ * by {@link slug_states} (used by all rendering hot paths).
24443
+ *
24444
+ * When a slug map is supplied, the identifier is the slug wrapped in
24445
+ * double quotes — dot allows quoted identifiers, and the slug alphabet
24446
+ * (lowercase alphanumerics + `-`) requires quoting because bare dot IDs
24447
+ * may not contain `-`. Graphviz round-trips the quoted form through to
24448
+ * the SVG `<title>` element and uses the slug as a stable basis for the
24449
+ * generated SVG element `id` attribute.
24450
+ *
24451
+ * ```typescript
24452
+ * node_of('Red Light', new Map([['Red Light', 'red-light']]));
24453
+ * // '"red-light"'
24454
+ * ```
24365
24455
  *
24366
24456
  * @internal
24367
24457
  */
24368
24458
  function node_of(state, state_index) {
24369
- return Array.isArray(state_index)
24370
- ? `n${state_index.indexOf(state)}`
24371
- : `n${state_index.get(state)}`;
24459
+ if (Array.isArray(state_index)) {
24460
+ return `n${state_index.indexOf(state)}`;
24461
+ }
24462
+ const v = state_index.get(state);
24463
+ if (typeof v === 'string') {
24464
+ return `"${v}"`;
24465
+ }
24466
+ return `n${v}`;
24372
24467
  }
24373
24468
  /**
24374
24469
  * Compose a graphviz `style` string from a pre-computed
@@ -24706,7 +24801,7 @@ function arranges_for(u_jssm, state_index) {
24706
24801
  function machine_to_dot(u_jssm, opts = {}) {
24707
24802
  var _a;
24708
24803
  const l_states = u_jssm.states();
24709
- const state_index = new Map(l_states.map((s, i) => [s, i]));
24804
+ const state_index = slug_states(l_states);
24710
24805
  const state_kinds = classify_states(u_jssm, l_states);
24711
24806
  const nodes = states_to_nodes_string(u_jssm, l_states, state_index, state_kinds, opts.hide_state_labels === true);
24712
24807
  const edges = states_to_edges_string(u_jssm, l_states, state_index, state_kinds);