jssm 5.151.2 → 5.153.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.
@@ -888,7 +888,19 @@ function peg$parse(input, options) {
888
888
  node.loc = location();
889
889
  }
890
890
  return node;
891
- }, peg$c1573 = peg$otherExpectation("arrange declaration"), peg$currPos = 0, peg$savedPos = 0, peg$posDetailsCache = [{ line: 1, column: 1 }], peg$maxFailPos = 0, peg$maxFailExpected = [], peg$silentFails = 0, peg$result;
891
+ }, peg$c1573 = peg$otherExpectation("oarrange declaration"), peg$c1574 = "oarrange", peg$c1575 = peg$literalExpectation("oarrange", false), peg$c1576 = function (value) {
892
+ const node = { key: 'oarrange_declaration', value };
893
+ if (options.locations) {
894
+ node.loc = location();
895
+ }
896
+ return node;
897
+ }, peg$c1577 = peg$otherExpectation("farrange declaration"), peg$c1578 = "farrange", peg$c1579 = peg$literalExpectation("farrange", false), peg$c1580 = function (value) {
898
+ const node = { key: 'farrange_declaration', value };
899
+ if (options.locations) {
900
+ node.loc = location();
901
+ }
902
+ return node;
903
+ }, peg$c1581 = peg$otherExpectation("arrange declaration"), peg$currPos = 0, peg$savedPos = 0, peg$posDetailsCache = [{ line: 1, column: 1 }], peg$maxFailPos = 0, peg$maxFailExpected = [], peg$silentFails = 0, peg$result;
892
904
  if ("startRule" in options) {
893
905
  if (!(options.startRule in peg$startRuleFunctions)) {
894
906
  throw new Error("Can't start parsing from rule \"" + options.startRule + "\".");
@@ -20456,6 +20468,176 @@ function peg$parse(input, options) {
20456
20468
  }
20457
20469
  return s0;
20458
20470
  }
20471
+ function peg$parseOArrangeDeclaration() {
20472
+ var s0, s1, s2, s3, s4, s5, s6;
20473
+ peg$silentFails++;
20474
+ s0 = peg$currPos;
20475
+ if (input.substr(peg$currPos, 8) === peg$c1574) {
20476
+ s1 = peg$c1574;
20477
+ peg$currPos += 8;
20478
+ }
20479
+ else {
20480
+ s1 = peg$FAILED;
20481
+ if (peg$silentFails === 0 && peg$currPos >= peg$maxFailPos) {
20482
+ peg$fail(peg$c1575);
20483
+ }
20484
+ }
20485
+ if (s1 !== peg$FAILED) {
20486
+ s2 = peg$parseWS();
20487
+ if (s2 === peg$FAILED) {
20488
+ s2 = null;
20489
+ }
20490
+ if (s2 !== peg$FAILED) {
20491
+ s3 = peg$parseLabelOrLabelList();
20492
+ if (s3 !== peg$FAILED) {
20493
+ s4 = peg$parseWS();
20494
+ if (s4 === peg$FAILED) {
20495
+ s4 = null;
20496
+ }
20497
+ if (s4 !== peg$FAILED) {
20498
+ if (input.charCodeAt(peg$currPos) === 59) {
20499
+ s5 = peg$c1213;
20500
+ peg$currPos++;
20501
+ }
20502
+ else {
20503
+ s5 = peg$FAILED;
20504
+ if (peg$silentFails === 0 && peg$currPos >= peg$maxFailPos) {
20505
+ peg$fail(peg$c1214);
20506
+ }
20507
+ }
20508
+ if (s5 !== peg$FAILED) {
20509
+ s6 = peg$parseWS();
20510
+ if (s6 === peg$FAILED) {
20511
+ s6 = null;
20512
+ }
20513
+ if (s6 !== peg$FAILED) {
20514
+ peg$savedPos = s0;
20515
+ s1 = peg$c1576(s3);
20516
+ s0 = s1;
20517
+ }
20518
+ else {
20519
+ peg$currPos = s0;
20520
+ s0 = peg$FAILED;
20521
+ }
20522
+ }
20523
+ else {
20524
+ peg$currPos = s0;
20525
+ s0 = peg$FAILED;
20526
+ }
20527
+ }
20528
+ else {
20529
+ peg$currPos = s0;
20530
+ s0 = peg$FAILED;
20531
+ }
20532
+ }
20533
+ else {
20534
+ peg$currPos = s0;
20535
+ s0 = peg$FAILED;
20536
+ }
20537
+ }
20538
+ else {
20539
+ peg$currPos = s0;
20540
+ s0 = peg$FAILED;
20541
+ }
20542
+ }
20543
+ else {
20544
+ peg$currPos = s0;
20545
+ s0 = peg$FAILED;
20546
+ }
20547
+ peg$silentFails--;
20548
+ if (s0 === peg$FAILED) {
20549
+ s1 = peg$FAILED;
20550
+ if (peg$silentFails === 0 && peg$currPos >= peg$maxFailPos) {
20551
+ peg$fail(peg$c1573);
20552
+ }
20553
+ }
20554
+ return s0;
20555
+ }
20556
+ function peg$parseFArrangeDeclaration() {
20557
+ var s0, s1, s2, s3, s4, s5, s6;
20558
+ peg$silentFails++;
20559
+ s0 = peg$currPos;
20560
+ if (input.substr(peg$currPos, 8) === peg$c1578) {
20561
+ s1 = peg$c1578;
20562
+ peg$currPos += 8;
20563
+ }
20564
+ else {
20565
+ s1 = peg$FAILED;
20566
+ if (peg$silentFails === 0 && peg$currPos >= peg$maxFailPos) {
20567
+ peg$fail(peg$c1579);
20568
+ }
20569
+ }
20570
+ if (s1 !== peg$FAILED) {
20571
+ s2 = peg$parseWS();
20572
+ if (s2 === peg$FAILED) {
20573
+ s2 = null;
20574
+ }
20575
+ if (s2 !== peg$FAILED) {
20576
+ s3 = peg$parseLabelOrLabelList();
20577
+ if (s3 !== peg$FAILED) {
20578
+ s4 = peg$parseWS();
20579
+ if (s4 === peg$FAILED) {
20580
+ s4 = null;
20581
+ }
20582
+ if (s4 !== peg$FAILED) {
20583
+ if (input.charCodeAt(peg$currPos) === 59) {
20584
+ s5 = peg$c1213;
20585
+ peg$currPos++;
20586
+ }
20587
+ else {
20588
+ s5 = peg$FAILED;
20589
+ if (peg$silentFails === 0 && peg$currPos >= peg$maxFailPos) {
20590
+ peg$fail(peg$c1214);
20591
+ }
20592
+ }
20593
+ if (s5 !== peg$FAILED) {
20594
+ s6 = peg$parseWS();
20595
+ if (s6 === peg$FAILED) {
20596
+ s6 = null;
20597
+ }
20598
+ if (s6 !== peg$FAILED) {
20599
+ peg$savedPos = s0;
20600
+ s1 = peg$c1580(s3);
20601
+ s0 = s1;
20602
+ }
20603
+ else {
20604
+ peg$currPos = s0;
20605
+ s0 = peg$FAILED;
20606
+ }
20607
+ }
20608
+ else {
20609
+ peg$currPos = s0;
20610
+ s0 = peg$FAILED;
20611
+ }
20612
+ }
20613
+ else {
20614
+ peg$currPos = s0;
20615
+ s0 = peg$FAILED;
20616
+ }
20617
+ }
20618
+ else {
20619
+ peg$currPos = s0;
20620
+ s0 = peg$FAILED;
20621
+ }
20622
+ }
20623
+ else {
20624
+ peg$currPos = s0;
20625
+ s0 = peg$FAILED;
20626
+ }
20627
+ }
20628
+ else {
20629
+ peg$currPos = s0;
20630
+ s0 = peg$FAILED;
20631
+ }
20632
+ peg$silentFails--;
20633
+ if (s0 === peg$FAILED) {
20634
+ s1 = peg$FAILED;
20635
+ if (peg$silentFails === 0 && peg$currPos >= peg$maxFailPos) {
20636
+ peg$fail(peg$c1577);
20637
+ }
20638
+ }
20639
+ return s0;
20640
+ }
20459
20641
  function peg$parseArrangeDeclaration() {
20460
20642
  var s0;
20461
20643
  peg$silentFails++;
@@ -20463,13 +20645,19 @@ function peg$parse(input, options) {
20463
20645
  if (s0 === peg$FAILED) {
20464
20646
  s0 = peg$parseArrangeEndDeclaration();
20465
20647
  if (s0 === peg$FAILED) {
20466
- s0 = peg$parseRegularArrangeDeclaration();
20648
+ s0 = peg$parseOArrangeDeclaration();
20649
+ if (s0 === peg$FAILED) {
20650
+ s0 = peg$parseFArrangeDeclaration();
20651
+ if (s0 === peg$FAILED) {
20652
+ s0 = peg$parseRegularArrangeDeclaration();
20653
+ }
20654
+ }
20467
20655
  }
20468
20656
  }
20469
20657
  peg$silentFails--;
20470
20658
  if (s0 === peg$FAILED) {
20471
20659
  if (peg$silentFails === 0 && peg$currPos >= peg$maxFailPos) {
20472
- peg$fail(peg$c1573);
20660
+ peg$fail(peg$c1581);
20473
20661
  }
20474
20662
  }
20475
20663
  return s0;
@@ -22370,7 +22558,8 @@ function compile_rule_handler(rule) {
22370
22558
  return { agg_as: 'state_declaration', val: { state: rule.name, declarations: rule.value } };
22371
22559
  }
22372
22560
  if (['arrange_declaration', 'arrange_start_declaration',
22373
- 'arrange_end_declaration'].includes(rule.key)) {
22561
+ 'arrange_end_declaration', 'oarrange_declaration',
22562
+ 'farrange_declaration'].includes(rule.key)) {
22374
22563
  return { agg_as: rule.key, val: [rule.value] };
22375
22564
  }
22376
22565
  // things that can only exist once and are just a value under their own name
@@ -22576,6 +22765,8 @@ function compile(tree) {
22576
22765
  arrange_declaration: [],
22577
22766
  arrange_start_declaration: [],
22578
22767
  arrange_end_declaration: [],
22768
+ oarrange_declaration: [],
22769
+ farrange_declaration: [],
22579
22770
  machine_version: [],
22580
22771
  default_state_config: [],
22581
22772
  default_active_state_config: [],
@@ -22701,6 +22892,7 @@ function compile(tree) {
22701
22892
  }
22702
22893
  });
22703
22894
  ['arrange_declaration', 'arrange_start_declaration', 'arrange_end_declaration',
22895
+ 'oarrange_declaration', 'farrange_declaration',
22704
22896
  'machine_author', 'machine_contributor', 'machine_reference', 'theme',
22705
22897
  'state_declaration', 'property_definition', 'default_state_config',
22706
22898
  'default_start_state_config', 'default_end_state_config',
@@ -23371,7 +23563,7 @@ var constants = /*#__PURE__*/Object.freeze({
23371
23563
  * Useful for runtime diagnostics and for embedding in serialized machine
23372
23564
  * snapshots so that deserializers can detect version-skew.
23373
23565
  */
23374
- const version = "5.151.2";
23566
+ const version = "5.153.0";
23375
23567
 
23376
23568
  // whargarbl lots of these return arrays could/should be sets
23377
23569
  const { state_name_chars, state_name_first_chars, action_label_chars } = constants;
@@ -23688,7 +23880,7 @@ function find_connected_components(states, edges) {
23688
23880
  }
23689
23881
  class Machine {
23690
23882
  // whargarbl this badly needs to be broken up, monolith master
23691
- constructor({ start_states, end_states = [], failed_outputs = [], initial_state, start_states_no_enforce, complete = [], transitions, machine_author, machine_comment, machine_contributor, machine_definition, machine_language, machine_license, machine_name, machine_version, npm_name, default_size, state_declaration, property_definition, state_property, fsl_version, dot_preamble = undefined, arrange_declaration = [], arrange_start_declaration = [], arrange_end_declaration = [], theme = ['default'], flow = 'down', graph_layout = 'dot', instance_name, history, boundary_depth_limit, data, default_state_config, default_active_state_config, default_hooked_state_config, default_terminal_state_config, default_start_state_config, default_end_state_config, default_transition_config, default_graph_config, group_registry, group_metadata, group_hooks, state_hooks, allows_override, config_allows_override, allow_islands, editor_config, rng_seed, time_source, timeout_source, clear_timeout_source }) {
23883
+ constructor({ start_states, end_states = [], failed_outputs = [], initial_state, start_states_no_enforce, complete = [], transitions, machine_author, machine_comment, machine_contributor, machine_definition, machine_language, machine_license, machine_name, machine_version, npm_name, default_size, state_declaration, property_definition, state_property, fsl_version, dot_preamble = undefined, arrange_declaration = [], arrange_start_declaration = [], arrange_end_declaration = [], oarrange_declaration = [], farrange_declaration = [], theme = ['default'], flow = 'down', graph_layout = 'dot', instance_name, history, boundary_depth_limit, data, default_state_config, default_active_state_config, default_hooked_state_config, default_terminal_state_config, default_start_state_config, default_end_state_config, default_transition_config, default_graph_config, group_registry, group_metadata, group_hooks, state_hooks, allows_override, config_allows_override, allow_islands, editor_config, rng_seed, time_source, timeout_source, clear_timeout_source }) {
23692
23884
  this._time_source = time_source !== null && time_source !== void 0 ? time_source : (() => new Date().getTime());
23693
23885
  this._create_started = this._time_source();
23694
23886
  this._instance_name = instance_name;
@@ -23725,6 +23917,8 @@ class Machine {
23725
23917
  this._arrange_declaration = arrange_declaration;
23726
23918
  this._arrange_start_declaration = arrange_start_declaration;
23727
23919
  this._arrange_end_declaration = arrange_end_declaration;
23920
+ this._oarrange_declaration = oarrange_declaration;
23921
+ this._farrange_declaration = farrange_declaration;
23728
23922
  this._dot_preamble = dot_preamble;
23729
23923
  this._themes = theme;
23730
23924
  this._flow = flow;
@@ -24137,11 +24331,11 @@ class Machine {
24137
24331
  }
24138
24332
  this._created = this._time_source();
24139
24333
  this.auto_set_state_timeout();
24140
- this._arrange_declaration.forEach((arrange_pair) => arrange_pair.forEach((possibleState) => {
24334
+ [this._arrange_declaration, this._oarrange_declaration, this._farrange_declaration].forEach((declaration) => declaration.forEach((arrange_pair) => arrange_pair.forEach((possibleState) => {
24141
24335
  if (!(this._states.has(possibleState))) {
24142
24336
  throw new JssmError(this, `Cannot arrange state that does not exist "${possibleState}"`);
24143
24337
  }
24144
- }));
24338
+ })));
24145
24339
  }
24146
24340
  /********
24147
24341
  *
@@ -24346,13 +24540,13 @@ class Machine {
24346
24540
  * `;
24347
24541
  *
24348
24542
  * traffic_light.state(); // Off
24349
- * traffic_light.props(); // { can_go: true, hesitate: true, stop_first: true; }
24543
+ * traffic_light.props(); // { can_go: true, hesitate: true, stop_first: true }
24350
24544
  *
24351
24545
  * traffic_light.go('Red');
24352
- * traffic_light.props(); // { can_go: false, hesitate: true, stop_first: true; }
24546
+ * traffic_light.props(); // { can_go: false, hesitate: true, stop_first: true }
24353
24547
  *
24354
24548
  * traffic_light.go('Green');
24355
- * traffic_light.props(); // { can_go: true, hesitate: false, stop_first: false; }
24549
+ * traffic_light.props(); // { can_go: true, hesitate: false, stop_first: false }
24356
24550
  * ```
24357
24551
  *
24358
24552
  * @returns An object mapping every known property name to its current value
@@ -29037,6 +29231,242 @@ function build_hook_descriptor(spec, wrapped) {
29037
29231
  return base;
29038
29232
  }
29039
29233
 
29234
+ /**
29235
+ * Compressed, URL-safe permalink wire format for FSL machines, shared by the
29236
+ * toolbar's Export→Permalink and the per-instance URL sync controller.
29237
+ *
29238
+ * A machine is encoded to a `<scheme><payload>` segment: `<payload>` is URL-safe
29239
+ * base64, `<scheme>` is `1` when DEFLATE shrank the source and `0` for the raw
29240
+ * bytes when it did not (so a short machine's link never grows). Segments live in
29241
+ * a URL fragment as `#<key>=<segment>` joined by `&`, so several machines can
29242
+ * share one URL.
29243
+ */
29244
+ /**
29245
+ * Default fragment key for the single-machine case (back-compat with 5.150).
29246
+ *
29247
+ * @example
29248
+ * DEFAULT_PERMALINK_KEY; // 'm'
29249
+ */
29250
+ /**
29251
+ * URL-safe base64 (RFC 4648 §5) of raw bytes: standard base64 with `+`→`-`,
29252
+ * `/`→`_`, and trailing `=` padding stripped.
29253
+ *
29254
+ * @example
29255
+ * bytes_to_base64url(new TextEncoder().encode("a")); // "YQ"
29256
+ */
29257
+ function bytes_to_base64url(bytes) {
29258
+ let binary = '';
29259
+ for (const byte of bytes) {
29260
+ binary += String.fromCharCode(byte);
29261
+ }
29262
+ return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
29263
+ }
29264
+ /**
29265
+ * Inverse of {@link bytes_to_base64url}.
29266
+ *
29267
+ * @example
29268
+ * new TextDecoder().decode(base64url_to_bytes("YQ")); // "a"
29269
+ */
29270
+ function base64url_to_bytes(text) {
29271
+ const standard = text.replace(/-/g, '+').replace(/_/g, '/');
29272
+ const padded = standard + '='.repeat((4 - (standard.length % 4)) % 4); // atob wants 4-aligned input
29273
+ const binary = atob(padded);
29274
+ const bytes = new Uint8Array(binary.length);
29275
+ for (let i = 0; i < binary.length; i++) {
29276
+ bytes[i] = binary.charCodeAt(i);
29277
+ }
29278
+ return bytes;
29279
+ }
29280
+ /**
29281
+ * DEFLATE `bytes` (raw, headerless) via the platform `CompressionStream`.
29282
+ *
29283
+ * @example
29284
+ * await deflate_raw(new TextEncoder().encode("aaaaaaaa")); // shorter Uint8Array of raw DEFLATE bytes
29285
+ */
29286
+ async function deflate_raw(bytes) {
29287
+ const stream = new CompressionStream('deflate-raw');
29288
+ const writer = stream.writable.getWriter();
29289
+ void writer.write(bytes);
29290
+ void writer.close();
29291
+ return new Uint8Array(await new Response(stream.readable).arrayBuffer());
29292
+ }
29293
+ /**
29294
+ * Inverse of {@link deflate_raw}.
29295
+ *
29296
+ * @example
29297
+ * new TextDecoder().decode(await inflate_raw(await deflate_raw(new TextEncoder().encode("hi")))); // "hi"
29298
+ */
29299
+ async function inflate_raw(bytes) {
29300
+ const stream = new DecompressionStream('deflate-raw');
29301
+ const writer = stream.writable.getWriter();
29302
+ void writer.write(bytes);
29303
+ void writer.close();
29304
+ return new Uint8Array(await new Response(stream.readable).arrayBuffer());
29305
+ }
29306
+ /**
29307
+ * Encode FSL to a `<scheme><payload>` segment value (the part after `key=`).
29308
+ * DEFLATE is used (scheme `1`) only when it is strictly shorter than the raw
29309
+ * bytes (scheme `0`).
29310
+ *
29311
+ * @example
29312
+ * await encode_machine("a -> b;"); // "0YSAtPiBiOw"
29313
+ */
29314
+ async function encode_machine(fsl) {
29315
+ const utf8 = new TextEncoder().encode(fsl);
29316
+ const raw = bytes_to_base64url(utf8);
29317
+ const deflated = bytes_to_base64url(await deflate_raw(utf8));
29318
+ return deflated.length < raw.length ? `1${deflated}` : `0${raw}`;
29319
+ }
29320
+ /**
29321
+ * Inverse of {@link encode_machine}: decode a `<scheme><payload>` segment back
29322
+ * to FSL. Async because inflate is async.
29323
+ *
29324
+ * @example
29325
+ * await decode_machine("0YSAtPiBiOw"); // "a -> b;"
29326
+ */
29327
+ async function decode_machine(segment) {
29328
+ const scheme = segment[0];
29329
+ const bytes = base64url_to_bytes(segment.slice(1));
29330
+ const plain = scheme === '1' ? await inflate_raw(bytes) : bytes;
29331
+ return new TextDecoder().decode(plain);
29332
+ }
29333
+ /** Split a fragment (leading `#` optional) into `[key, value]` pairs, dropping empties. */
29334
+ function fragment_pairs(hash) {
29335
+ const body = hash.startsWith('#') ? hash.slice(1) : hash;
29336
+ return body.split('&').filter(Boolean).map(seg => {
29337
+ const eq = seg.indexOf('=');
29338
+ return eq === -1
29339
+ ? [seg, '']
29340
+ : [seg.slice(0, eq), seg.slice(eq + 1)];
29341
+ });
29342
+ }
29343
+ /**
29344
+ * Read one segment's value out of a `#a=…&b=…` fragment.
29345
+ *
29346
+ * @returns The value, or `null` if `key` is absent.
29347
+ *
29348
+ * @example
29349
+ * read_fragment_param('#a=0AAA&b=1BBB', 'b'); // "1BBB"
29350
+ */
29351
+ function read_fragment_param(hash, key) {
29352
+ const found = fragment_pairs(hash).find(([k]) => k === key);
29353
+ return found ? found[1] : null;
29354
+ }
29355
+ /**
29356
+ * Return a new fragment body (no leading `#`) with `key`'s segment set to
29357
+ * `value`, preserving every other segment and its order; appends if absent.
29358
+ *
29359
+ * @example
29360
+ * set_fragment_param('#a=0AAA', 'b', '1BBB'); // "a=0AAA&b=1BBB"
29361
+ */
29362
+ function set_fragment_param(hash, key, value) {
29363
+ const pairs = fragment_pairs(hash);
29364
+ const at = pairs.findIndex(([k]) => k === key);
29365
+ if (at === -1) {
29366
+ pairs.push([key, value]);
29367
+ }
29368
+ else {
29369
+ pairs[at] = [key, value];
29370
+ }
29371
+ return pairs.map(([k, v]) => `${k}=${v}`).join('&');
29372
+ }
29373
+ /**
29374
+ * The fragment key an element owns: its `uhash` attribute if set, else its
29375
+ * `id`, else `null` (does not participate in URL sync). The single source of
29376
+ * this rule, shared by the toolbar export and the sync controller.
29377
+ *
29378
+ * @example
29379
+ * permalink_key_for(el); // "myId" (when <el id="myId">, no uhash)
29380
+ */
29381
+ function permalink_key_for(host) {
29382
+ var _a, _b;
29383
+ return (_b = (_a = host.getAttribute('uhash')) !== null && _a !== void 0 ? _a : host.getAttribute('id')) !== null && _b !== void 0 ? _b : null;
29384
+ }
29385
+
29386
+ /** Debounce before a live edit is written to the URL fragment. */
29387
+ const PERMALINK_WRITE_DEBOUNCE_MS = 300;
29388
+ /**
29389
+ * Binds an `<fsl-instance>` to a segment of the URL fragment: restores from it
29390
+ * on connect and writes back (debounced, via `history.replaceState`) whenever
29391
+ * the machine is rebuilt. Inert when the host has no key ({@link permalink_key_for}
29392
+ * returns `null`), so an `fsl-instance` without an `id`/`uhash` never touches
29393
+ * `location`.
29394
+ *
29395
+ * Echo guard: `_last` holds the segment most recently read or written, so a
29396
+ * restore→rebuild→write cycle and a self-induced `hashchange` are both no-ops.
29397
+ *
29398
+ * @example
29399
+ * // In an element's constructor:
29400
+ * new FslPermalinkSync(this); // reads <el id="k">'s #k=… on connect, writes it on edit
29401
+ */
29402
+ class FslPermalinkSync {
29403
+ constructor(host) {
29404
+ this.key = null;
29405
+ this._last = null;
29406
+ this._onRebuilt = () => { this._scheduleWrite(); };
29407
+ this._onHashChange = () => { void this._restore(); };
29408
+ this.host = host;
29409
+ host.addController(this);
29410
+ }
29411
+ hostConnected() {
29412
+ this.key = permalink_key_for(this.host);
29413
+ if (this.key === null) {
29414
+ return;
29415
+ }
29416
+ void this._restore();
29417
+ this.host.addEventListener('fsl-machine-rebuilt', this._onRebuilt);
29418
+ window.addEventListener('hashchange', this._onHashChange);
29419
+ }
29420
+ hostDisconnected() {
29421
+ if (this.key === null) {
29422
+ return;
29423
+ }
29424
+ this.host.removeEventListener('fsl-machine-rebuilt', this._onRebuilt);
29425
+ window.removeEventListener('hashchange', this._onHashChange);
29426
+ if (this._timer !== undefined) {
29427
+ clearTimeout(this._timer);
29428
+ this._timer = undefined;
29429
+ }
29430
+ }
29431
+ /** Read our segment and, if new, push it into the host (overriding declared source). */
29432
+ async _restore() {
29433
+ const segment = read_fragment_param(location.hash, this.key);
29434
+ if (segment === null || segment === this._last) {
29435
+ return;
29436
+ }
29437
+ try {
29438
+ const fsl = await decode_machine(segment);
29439
+ this._last = segment;
29440
+ this.host.fsl = fsl;
29441
+ }
29442
+ catch (_a) {
29443
+ // Malformed/truncated segment, or no compression support: leave the
29444
+ // declared source intact. A bad URL never bricks the page.
29445
+ }
29446
+ }
29447
+ _scheduleWrite() {
29448
+ if (this._timer !== undefined) {
29449
+ clearTimeout(this._timer);
29450
+ }
29451
+ this._timer = setTimeout(() => { void this._write(); }, PERMALINK_WRITE_DEBOUNCE_MS);
29452
+ }
29453
+ /** Encode the current source and merge it into the fragment, history-silently. */
29454
+ async _write() {
29455
+ try {
29456
+ const segment = await encode_machine(this.host.fsl);
29457
+ if (segment === this._last) {
29458
+ return;
29459
+ }
29460
+ this._last = segment;
29461
+ const fragment = set_fragment_param(location.hash, this.key, segment);
29462
+ history.replaceState(history.state, '', `#${fragment}`);
29463
+ }
29464
+ catch (_a) {
29465
+ // No compression support: skip the write rather than throw.
29466
+ }
29467
+ }
29468
+ }
29469
+
29040
29470
  /**
29041
29471
  * Theme registry for the fsl-* suite. A **theme** is a named pair of light/dark
29042
29472
  * palettes; a **mode** (`system` | `light` | `dark`) picks which variant applies,
@@ -29434,8 +29864,10 @@ function split_ratio(coord, start, size) {
29434
29864
  * @slot footer - Footer slot.
29435
29865
  */
29436
29866
  class FslInstance extends i {
29867
+ /** Bind this instance to a URL-fragment segment keyed by its `uhash`/`id`
29868
+ * (inert if it has neither): restore on connect, write debounced on edit. */
29437
29869
  constructor() {
29438
- super(...arguments);
29870
+ super();
29439
29871
  /**
29440
29872
  * FSL source attribute. When non-empty, this is the sole channel
29441
29873
  * supplying the machine's source. Setting both this and a child
@@ -29587,6 +30019,7 @@ class FslInstance extends i {
29587
30019
  this._split = 50;
29588
30020
  this.requestUpdate();
29589
30021
  };
30022
+ new FslPermalinkSync(this);
29590
30023
  }
29591
30024
  /**
29592
30025
  * Raw machine accessor. Returns the owned {@link Machine} instance.
@@ -29790,30 +30223,43 @@ class FslInstance extends i {
29790
30223
  super.connectedCallback();
29791
30224
  // Step 1: resolve FSL source.
29792
30225
  const resolved = resolve_fsl_source(this, this.fsl);
29793
- if (resolved.error !== undefined) {
30226
+ // A permalink-only instance has no declared source, but its own URL segment
30227
+ // will supply one asynchronously: FslPermalinkSync (attached in the
30228
+ // constructor) restores `fsl` just after connect, which rebuilds the machine
30229
+ // via willUpdate -> _rebuild_machine. Defer to that instead of throwing;
30230
+ // render() shows the placeholder until the restore lands.
30231
+ const deferToPermalink = resolved.provided_count === 0 && this._permalinkSegmentPresent();
30232
+ if (resolved.error !== undefined && !deferToPermalink) {
29794
30233
  throw new Error(`fsl-instance: ${resolved.error}`);
29795
30234
  }
29796
- // Step 2: construct the machine.
29797
- // (The resolver guarantees `fsl` is a non-empty string when error is undefined.)
29798
- const fsl_source = resolved.fsl;
29799
- this._machine = this._build_machine(fsl_source);
29800
- this._applyEditorConfig();
29801
- // Step 3: paint initial host attributes + CSS custom properties.
29802
- this._paint_state_reflection();
29803
- // Step 4: shadow DOM render is automatic via Lit; requesting an update
29804
- // here ensures the first paint sees the freshly painted attributes.
29805
- this.requestUpdate();
29806
- // #639 mechanism 4: subscribe to library events and re-emit them as
29807
- // DOM CustomEvents from this host (#638 supplies the event API).
29808
- this._install_event_reemission();
29809
- // #641: <jssm-hook> declarative discovery.
29810
- this._install_declarative_hooks();
29811
- // #643: <jssm-on> declarative event observation.
29812
- this._install_jssm_on_children();
29813
- // #645: discover <jssm-bind> tags and `data-jssm-bind` descendants,
29814
- // install live machine-to-DOM projections.
29815
- this._unsubs.push(...install_bindings(this, this._machine));
29816
- // #640: <jssm-action> DOM event → machine action wiring.
30235
+ // Steps 2-4 + machine-scoped wiring: only when a source is available now. In
30236
+ // the deferred case these run later, from _rebuild_machine, once the restore
30237
+ // sets `fsl`.
30238
+ if (!deferToPermalink) {
30239
+ // Step 2: construct the machine.
30240
+ // (The resolver guarantees `fsl` is a non-empty string when error is undefined.)
30241
+ const fsl_source = resolved.fsl;
30242
+ this._machine = this._build_machine(fsl_source);
30243
+ this._applyEditorConfig();
30244
+ // Step 3: paint initial host attributes + CSS custom properties.
30245
+ this._paint_state_reflection();
30246
+ // Step 4: shadow DOM render is automatic via Lit; requesting an update
30247
+ // here ensures the first paint sees the freshly painted attributes.
30248
+ this.requestUpdate();
30249
+ // #639 mechanism 4: subscribe to library events and re-emit them as
30250
+ // DOM CustomEvents from this host (#638 supplies the event API).
30251
+ this._install_event_reemission();
30252
+ // #641: <jssm-hook> declarative discovery.
30253
+ this._install_declarative_hooks();
30254
+ // #643: <jssm-on> declarative event observation.
30255
+ this._install_jssm_on_children();
30256
+ // #645: discover <jssm-bind> tags and `data-jssm-bind` descendants,
30257
+ // install live machine-to-DOM projections.
30258
+ this._unsubs.push(...install_bindings(this, this._machine));
30259
+ }
30260
+ // #640: <jssm-action> DOM event -> machine action wiring. The listeners read
30261
+ // `this.machine` live on event, so discovery is correct even before a
30262
+ // deferred build completes.
29817
30263
  this._discover_jssm_actions();
29818
30264
  // Theme: register the palette tokens as animatable colors (once, globally, so
29819
30265
  // switches can ease), follow the OS while in `system` mode, then apply the
@@ -29825,6 +30271,16 @@ class FslInstance extends i {
29825
30271
  }
29826
30272
  this._applyTheme();
29827
30273
  }
30274
+ /**
30275
+ * True when this instance owns a URL permalink segment (keyed by its
30276
+ * `uhash`/`id`) that a pending {@link FslPermalinkSync} restore will turn into
30277
+ * its FSL source — so `connectedCallback` can defer the machine build to that
30278
+ * restore instead of throwing on an otherwise-absent source.
30279
+ */
30280
+ _permalinkSegmentPresent() {
30281
+ const key = permalink_key_for(this);
30282
+ return key !== null && read_fragment_param(location.hash, key) !== null;
30283
+ }
29828
30284
  /**
29829
30285
  * Discover direct-child `<jssm-on>` elements and install their
29830
30286
  * subscriptions on the owned machine. Per #643:
@@ -30151,6 +30607,12 @@ class FslInstance extends i {
30151
30607
  */
30152
30608
  _install_action_listener(config) {
30153
30609
  const handler = (e) => {
30610
+ // A permalink-only instance wires its actions at connect but builds its
30611
+ // machine asynchronously (deferred restore). An event in that window is a
30612
+ // no-op rather than a throw via the `machine` getter.
30613
+ if (this._machine === undefined) {
30614
+ return;
30615
+ }
30154
30616
  if (config.prevent_default) {
30155
30617
  e.preventDefault();
30156
30618
  }