jssm 5.151.2 → 5.152.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 +7 -7
- package/custom-elements.json +510 -40
- package/dist/cdn/instance.js +292 -24
- package/dist/cdn/viz.js +1 -1
- package/dist/cli/fsl-export-system-prompt.cjs +1 -1
- package/dist/cli/fsl-render.cjs +1 -1
- package/dist/cli/fsl.cjs +1 -1
- package/dist/cli/lib.cjs +1 -1
- package/dist/cli/lib.mjs +1 -1
- package/dist/deno/README.md +7 -7
- package/dist/deno/jssm.js +1 -1
- package/dist/jssm.es5.cjs +1 -1
- package/dist/jssm.es5.iife.js +1 -1
- package/dist/jssm.es6.mjs +1 -1
- package/dist/jssm_viz.cjs +1 -1
- package/dist/jssm_viz.iife.cjs +1 -1
- package/dist/jssm_viz.mjs +1 -1
- package/dist/wc/instance.js +291 -23
- package/dist/wc/widgets.js +111 -43
- package/package.json +1 -1
package/dist/cdn/instance.js
CHANGED
|
@@ -23371,7 +23371,7 @@ var constants = /*#__PURE__*/Object.freeze({
|
|
|
23371
23371
|
* Useful for runtime diagnostics and for embedding in serialized machine
|
|
23372
23372
|
* snapshots so that deserializers can detect version-skew.
|
|
23373
23373
|
*/
|
|
23374
|
-
const version = "5.
|
|
23374
|
+
const version = "5.152.0";
|
|
23375
23375
|
|
|
23376
23376
|
// whargarbl lots of these return arrays could/should be sets
|
|
23377
23377
|
const { state_name_chars, state_name_first_chars, action_label_chars } = constants;
|
|
@@ -29037,6 +29037,242 @@ function build_hook_descriptor(spec, wrapped) {
|
|
|
29037
29037
|
return base;
|
|
29038
29038
|
}
|
|
29039
29039
|
|
|
29040
|
+
/**
|
|
29041
|
+
* Compressed, URL-safe permalink wire format for FSL machines, shared by the
|
|
29042
|
+
* toolbar's Export→Permalink and the per-instance URL sync controller.
|
|
29043
|
+
*
|
|
29044
|
+
* A machine is encoded to a `<scheme><payload>` segment: `<payload>` is URL-safe
|
|
29045
|
+
* base64, `<scheme>` is `1` when DEFLATE shrank the source and `0` for the raw
|
|
29046
|
+
* bytes when it did not (so a short machine's link never grows). Segments live in
|
|
29047
|
+
* a URL fragment as `#<key>=<segment>` joined by `&`, so several machines can
|
|
29048
|
+
* share one URL.
|
|
29049
|
+
*/
|
|
29050
|
+
/**
|
|
29051
|
+
* Default fragment key for the single-machine case (back-compat with 5.150).
|
|
29052
|
+
*
|
|
29053
|
+
* @example
|
|
29054
|
+
* DEFAULT_PERMALINK_KEY; // 'm'
|
|
29055
|
+
*/
|
|
29056
|
+
/**
|
|
29057
|
+
* URL-safe base64 (RFC 4648 §5) of raw bytes: standard base64 with `+`→`-`,
|
|
29058
|
+
* `/`→`_`, and trailing `=` padding stripped.
|
|
29059
|
+
*
|
|
29060
|
+
* @example
|
|
29061
|
+
* bytes_to_base64url(new TextEncoder().encode("a")); // "YQ"
|
|
29062
|
+
*/
|
|
29063
|
+
function bytes_to_base64url(bytes) {
|
|
29064
|
+
let binary = '';
|
|
29065
|
+
for (const byte of bytes) {
|
|
29066
|
+
binary += String.fromCharCode(byte);
|
|
29067
|
+
}
|
|
29068
|
+
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
29069
|
+
}
|
|
29070
|
+
/**
|
|
29071
|
+
* Inverse of {@link bytes_to_base64url}.
|
|
29072
|
+
*
|
|
29073
|
+
* @example
|
|
29074
|
+
* new TextDecoder().decode(base64url_to_bytes("YQ")); // "a"
|
|
29075
|
+
*/
|
|
29076
|
+
function base64url_to_bytes(text) {
|
|
29077
|
+
const standard = text.replace(/-/g, '+').replace(/_/g, '/');
|
|
29078
|
+
const padded = standard + '='.repeat((4 - (standard.length % 4)) % 4); // atob wants 4-aligned input
|
|
29079
|
+
const binary = atob(padded);
|
|
29080
|
+
const bytes = new Uint8Array(binary.length);
|
|
29081
|
+
for (let i = 0; i < binary.length; i++) {
|
|
29082
|
+
bytes[i] = binary.charCodeAt(i);
|
|
29083
|
+
}
|
|
29084
|
+
return bytes;
|
|
29085
|
+
}
|
|
29086
|
+
/**
|
|
29087
|
+
* DEFLATE `bytes` (raw, headerless) via the platform `CompressionStream`.
|
|
29088
|
+
*
|
|
29089
|
+
* @example
|
|
29090
|
+
* await deflate_raw(new TextEncoder().encode("aaaaaaaa")); // shorter Uint8Array of raw DEFLATE bytes
|
|
29091
|
+
*/
|
|
29092
|
+
async function deflate_raw(bytes) {
|
|
29093
|
+
const stream = new CompressionStream('deflate-raw');
|
|
29094
|
+
const writer = stream.writable.getWriter();
|
|
29095
|
+
void writer.write(bytes);
|
|
29096
|
+
void writer.close();
|
|
29097
|
+
return new Uint8Array(await new Response(stream.readable).arrayBuffer());
|
|
29098
|
+
}
|
|
29099
|
+
/**
|
|
29100
|
+
* Inverse of {@link deflate_raw}.
|
|
29101
|
+
*
|
|
29102
|
+
* @example
|
|
29103
|
+
* new TextDecoder().decode(await inflate_raw(await deflate_raw(new TextEncoder().encode("hi")))); // "hi"
|
|
29104
|
+
*/
|
|
29105
|
+
async function inflate_raw(bytes) {
|
|
29106
|
+
const stream = new DecompressionStream('deflate-raw');
|
|
29107
|
+
const writer = stream.writable.getWriter();
|
|
29108
|
+
void writer.write(bytes);
|
|
29109
|
+
void writer.close();
|
|
29110
|
+
return new Uint8Array(await new Response(stream.readable).arrayBuffer());
|
|
29111
|
+
}
|
|
29112
|
+
/**
|
|
29113
|
+
* Encode FSL to a `<scheme><payload>` segment value (the part after `key=`).
|
|
29114
|
+
* DEFLATE is used (scheme `1`) only when it is strictly shorter than the raw
|
|
29115
|
+
* bytes (scheme `0`).
|
|
29116
|
+
*
|
|
29117
|
+
* @example
|
|
29118
|
+
* await encode_machine("a -> b;"); // "0YSAtPiBiOw"
|
|
29119
|
+
*/
|
|
29120
|
+
async function encode_machine(fsl) {
|
|
29121
|
+
const utf8 = new TextEncoder().encode(fsl);
|
|
29122
|
+
const raw = bytes_to_base64url(utf8);
|
|
29123
|
+
const deflated = bytes_to_base64url(await deflate_raw(utf8));
|
|
29124
|
+
return deflated.length < raw.length ? `1${deflated}` : `0${raw}`;
|
|
29125
|
+
}
|
|
29126
|
+
/**
|
|
29127
|
+
* Inverse of {@link encode_machine}: decode a `<scheme><payload>` segment back
|
|
29128
|
+
* to FSL. Async because inflate is async.
|
|
29129
|
+
*
|
|
29130
|
+
* @example
|
|
29131
|
+
* await decode_machine("0YSAtPiBiOw"); // "a -> b;"
|
|
29132
|
+
*/
|
|
29133
|
+
async function decode_machine(segment) {
|
|
29134
|
+
const scheme = segment[0];
|
|
29135
|
+
const bytes = base64url_to_bytes(segment.slice(1));
|
|
29136
|
+
const plain = scheme === '1' ? await inflate_raw(bytes) : bytes;
|
|
29137
|
+
return new TextDecoder().decode(plain);
|
|
29138
|
+
}
|
|
29139
|
+
/** Split a fragment (leading `#` optional) into `[key, value]` pairs, dropping empties. */
|
|
29140
|
+
function fragment_pairs(hash) {
|
|
29141
|
+
const body = hash.startsWith('#') ? hash.slice(1) : hash;
|
|
29142
|
+
return body.split('&').filter(Boolean).map(seg => {
|
|
29143
|
+
const eq = seg.indexOf('=');
|
|
29144
|
+
return eq === -1
|
|
29145
|
+
? [seg, '']
|
|
29146
|
+
: [seg.slice(0, eq), seg.slice(eq + 1)];
|
|
29147
|
+
});
|
|
29148
|
+
}
|
|
29149
|
+
/**
|
|
29150
|
+
* Read one segment's value out of a `#a=…&b=…` fragment.
|
|
29151
|
+
*
|
|
29152
|
+
* @returns The value, or `null` if `key` is absent.
|
|
29153
|
+
*
|
|
29154
|
+
* @example
|
|
29155
|
+
* read_fragment_param('#a=0AAA&b=1BBB', 'b'); // "1BBB"
|
|
29156
|
+
*/
|
|
29157
|
+
function read_fragment_param(hash, key) {
|
|
29158
|
+
const found = fragment_pairs(hash).find(([k]) => k === key);
|
|
29159
|
+
return found ? found[1] : null;
|
|
29160
|
+
}
|
|
29161
|
+
/**
|
|
29162
|
+
* Return a new fragment body (no leading `#`) with `key`'s segment set to
|
|
29163
|
+
* `value`, preserving every other segment and its order; appends if absent.
|
|
29164
|
+
*
|
|
29165
|
+
* @example
|
|
29166
|
+
* set_fragment_param('#a=0AAA', 'b', '1BBB'); // "a=0AAA&b=1BBB"
|
|
29167
|
+
*/
|
|
29168
|
+
function set_fragment_param(hash, key, value) {
|
|
29169
|
+
const pairs = fragment_pairs(hash);
|
|
29170
|
+
const at = pairs.findIndex(([k]) => k === key);
|
|
29171
|
+
if (at === -1) {
|
|
29172
|
+
pairs.push([key, value]);
|
|
29173
|
+
}
|
|
29174
|
+
else {
|
|
29175
|
+
pairs[at] = [key, value];
|
|
29176
|
+
}
|
|
29177
|
+
return pairs.map(([k, v]) => `${k}=${v}`).join('&');
|
|
29178
|
+
}
|
|
29179
|
+
/**
|
|
29180
|
+
* The fragment key an element owns: its `uhash` attribute if set, else its
|
|
29181
|
+
* `id`, else `null` (does not participate in URL sync). The single source of
|
|
29182
|
+
* this rule, shared by the toolbar export and the sync controller.
|
|
29183
|
+
*
|
|
29184
|
+
* @example
|
|
29185
|
+
* permalink_key_for(el); // "myId" (when <el id="myId">, no uhash)
|
|
29186
|
+
*/
|
|
29187
|
+
function permalink_key_for(host) {
|
|
29188
|
+
var _a, _b;
|
|
29189
|
+
return (_b = (_a = host.getAttribute('uhash')) !== null && _a !== void 0 ? _a : host.getAttribute('id')) !== null && _b !== void 0 ? _b : null;
|
|
29190
|
+
}
|
|
29191
|
+
|
|
29192
|
+
/** Debounce before a live edit is written to the URL fragment. */
|
|
29193
|
+
const PERMALINK_WRITE_DEBOUNCE_MS = 300;
|
|
29194
|
+
/**
|
|
29195
|
+
* Binds an `<fsl-instance>` to a segment of the URL fragment: restores from it
|
|
29196
|
+
* on connect and writes back (debounced, via `history.replaceState`) whenever
|
|
29197
|
+
* the machine is rebuilt. Inert when the host has no key ({@link permalink_key_for}
|
|
29198
|
+
* returns `null`), so an `fsl-instance` without an `id`/`uhash` never touches
|
|
29199
|
+
* `location`.
|
|
29200
|
+
*
|
|
29201
|
+
* Echo guard: `_last` holds the segment most recently read or written, so a
|
|
29202
|
+
* restore→rebuild→write cycle and a self-induced `hashchange` are both no-ops.
|
|
29203
|
+
*
|
|
29204
|
+
* @example
|
|
29205
|
+
* // In an element's constructor:
|
|
29206
|
+
* new FslPermalinkSync(this); // reads <el id="k">'s #k=… on connect, writes it on edit
|
|
29207
|
+
*/
|
|
29208
|
+
class FslPermalinkSync {
|
|
29209
|
+
constructor(host) {
|
|
29210
|
+
this.key = null;
|
|
29211
|
+
this._last = null;
|
|
29212
|
+
this._onRebuilt = () => { this._scheduleWrite(); };
|
|
29213
|
+
this._onHashChange = () => { void this._restore(); };
|
|
29214
|
+
this.host = host;
|
|
29215
|
+
host.addController(this);
|
|
29216
|
+
}
|
|
29217
|
+
hostConnected() {
|
|
29218
|
+
this.key = permalink_key_for(this.host);
|
|
29219
|
+
if (this.key === null) {
|
|
29220
|
+
return;
|
|
29221
|
+
}
|
|
29222
|
+
void this._restore();
|
|
29223
|
+
this.host.addEventListener('fsl-machine-rebuilt', this._onRebuilt);
|
|
29224
|
+
window.addEventListener('hashchange', this._onHashChange);
|
|
29225
|
+
}
|
|
29226
|
+
hostDisconnected() {
|
|
29227
|
+
if (this.key === null) {
|
|
29228
|
+
return;
|
|
29229
|
+
}
|
|
29230
|
+
this.host.removeEventListener('fsl-machine-rebuilt', this._onRebuilt);
|
|
29231
|
+
window.removeEventListener('hashchange', this._onHashChange);
|
|
29232
|
+
if (this._timer !== undefined) {
|
|
29233
|
+
clearTimeout(this._timer);
|
|
29234
|
+
this._timer = undefined;
|
|
29235
|
+
}
|
|
29236
|
+
}
|
|
29237
|
+
/** Read our segment and, if new, push it into the host (overriding declared source). */
|
|
29238
|
+
async _restore() {
|
|
29239
|
+
const segment = read_fragment_param(location.hash, this.key);
|
|
29240
|
+
if (segment === null || segment === this._last) {
|
|
29241
|
+
return;
|
|
29242
|
+
}
|
|
29243
|
+
try {
|
|
29244
|
+
const fsl = await decode_machine(segment);
|
|
29245
|
+
this._last = segment;
|
|
29246
|
+
this.host.fsl = fsl;
|
|
29247
|
+
}
|
|
29248
|
+
catch (_a) {
|
|
29249
|
+
// Malformed/truncated segment, or no compression support: leave the
|
|
29250
|
+
// declared source intact. A bad URL never bricks the page.
|
|
29251
|
+
}
|
|
29252
|
+
}
|
|
29253
|
+
_scheduleWrite() {
|
|
29254
|
+
if (this._timer !== undefined) {
|
|
29255
|
+
clearTimeout(this._timer);
|
|
29256
|
+
}
|
|
29257
|
+
this._timer = setTimeout(() => { void this._write(); }, PERMALINK_WRITE_DEBOUNCE_MS);
|
|
29258
|
+
}
|
|
29259
|
+
/** Encode the current source and merge it into the fragment, history-silently. */
|
|
29260
|
+
async _write() {
|
|
29261
|
+
try {
|
|
29262
|
+
const segment = await encode_machine(this.host.fsl);
|
|
29263
|
+
if (segment === this._last) {
|
|
29264
|
+
return;
|
|
29265
|
+
}
|
|
29266
|
+
this._last = segment;
|
|
29267
|
+
const fragment = set_fragment_param(location.hash, this.key, segment);
|
|
29268
|
+
history.replaceState(history.state, '', `#${fragment}`);
|
|
29269
|
+
}
|
|
29270
|
+
catch (_a) {
|
|
29271
|
+
// No compression support: skip the write rather than throw.
|
|
29272
|
+
}
|
|
29273
|
+
}
|
|
29274
|
+
}
|
|
29275
|
+
|
|
29040
29276
|
/**
|
|
29041
29277
|
* Theme registry for the fsl-* suite. A **theme** is a named pair of light/dark
|
|
29042
29278
|
* palettes; a **mode** (`system` | `light` | `dark`) picks which variant applies,
|
|
@@ -29434,8 +29670,10 @@ function split_ratio(coord, start, size) {
|
|
|
29434
29670
|
* @slot footer - Footer slot.
|
|
29435
29671
|
*/
|
|
29436
29672
|
class FslInstance extends i {
|
|
29673
|
+
/** Bind this instance to a URL-fragment segment keyed by its `uhash`/`id`
|
|
29674
|
+
* (inert if it has neither): restore on connect, write debounced on edit. */
|
|
29437
29675
|
constructor() {
|
|
29438
|
-
super(
|
|
29676
|
+
super();
|
|
29439
29677
|
/**
|
|
29440
29678
|
* FSL source attribute. When non-empty, this is the sole channel
|
|
29441
29679
|
* supplying the machine's source. Setting both this and a child
|
|
@@ -29587,6 +29825,7 @@ class FslInstance extends i {
|
|
|
29587
29825
|
this._split = 50;
|
|
29588
29826
|
this.requestUpdate();
|
|
29589
29827
|
};
|
|
29828
|
+
new FslPermalinkSync(this);
|
|
29590
29829
|
}
|
|
29591
29830
|
/**
|
|
29592
29831
|
* Raw machine accessor. Returns the owned {@link Machine} instance.
|
|
@@ -29790,30 +30029,43 @@ class FslInstance extends i {
|
|
|
29790
30029
|
super.connectedCallback();
|
|
29791
30030
|
// Step 1: resolve FSL source.
|
|
29792
30031
|
const resolved = resolve_fsl_source(this, this.fsl);
|
|
29793
|
-
|
|
30032
|
+
// A permalink-only instance has no declared source, but its own URL segment
|
|
30033
|
+
// will supply one asynchronously: FslPermalinkSync (attached in the
|
|
30034
|
+
// constructor) restores `fsl` just after connect, which rebuilds the machine
|
|
30035
|
+
// via willUpdate -> _rebuild_machine. Defer to that instead of throwing;
|
|
30036
|
+
// render() shows the placeholder until the restore lands.
|
|
30037
|
+
const deferToPermalink = resolved.provided_count === 0 && this._permalinkSegmentPresent();
|
|
30038
|
+
if (resolved.error !== undefined && !deferToPermalink) {
|
|
29794
30039
|
throw new Error(`fsl-instance: ${resolved.error}`);
|
|
29795
30040
|
}
|
|
29796
|
-
//
|
|
29797
|
-
//
|
|
29798
|
-
|
|
29799
|
-
|
|
29800
|
-
|
|
29801
|
-
|
|
29802
|
-
|
|
29803
|
-
|
|
29804
|
-
|
|
29805
|
-
|
|
29806
|
-
|
|
29807
|
-
|
|
29808
|
-
|
|
29809
|
-
|
|
29810
|
-
|
|
29811
|
-
|
|
29812
|
-
|
|
29813
|
-
|
|
29814
|
-
|
|
29815
|
-
|
|
29816
|
-
|
|
30041
|
+
// Steps 2-4 + machine-scoped wiring: only when a source is available now. In
|
|
30042
|
+
// the deferred case these run later, from _rebuild_machine, once the restore
|
|
30043
|
+
// sets `fsl`.
|
|
30044
|
+
if (!deferToPermalink) {
|
|
30045
|
+
// Step 2: construct the machine.
|
|
30046
|
+
// (The resolver guarantees `fsl` is a non-empty string when error is undefined.)
|
|
30047
|
+
const fsl_source = resolved.fsl;
|
|
30048
|
+
this._machine = this._build_machine(fsl_source);
|
|
30049
|
+
this._applyEditorConfig();
|
|
30050
|
+
// Step 3: paint initial host attributes + CSS custom properties.
|
|
30051
|
+
this._paint_state_reflection();
|
|
30052
|
+
// Step 4: shadow DOM render is automatic via Lit; requesting an update
|
|
30053
|
+
// here ensures the first paint sees the freshly painted attributes.
|
|
30054
|
+
this.requestUpdate();
|
|
30055
|
+
// #639 mechanism 4: subscribe to library events and re-emit them as
|
|
30056
|
+
// DOM CustomEvents from this host (#638 supplies the event API).
|
|
30057
|
+
this._install_event_reemission();
|
|
30058
|
+
// #641: <jssm-hook> declarative discovery.
|
|
30059
|
+
this._install_declarative_hooks();
|
|
30060
|
+
// #643: <jssm-on> declarative event observation.
|
|
30061
|
+
this._install_jssm_on_children();
|
|
30062
|
+
// #645: discover <jssm-bind> tags and `data-jssm-bind` descendants,
|
|
30063
|
+
// install live machine-to-DOM projections.
|
|
30064
|
+
this._unsubs.push(...install_bindings(this, this._machine));
|
|
30065
|
+
}
|
|
30066
|
+
// #640: <jssm-action> DOM event -> machine action wiring. The listeners read
|
|
30067
|
+
// `this.machine` live on event, so discovery is correct even before a
|
|
30068
|
+
// deferred build completes.
|
|
29817
30069
|
this._discover_jssm_actions();
|
|
29818
30070
|
// Theme: register the palette tokens as animatable colors (once, globally, so
|
|
29819
30071
|
// switches can ease), follow the OS while in `system` mode, then apply the
|
|
@@ -29825,6 +30077,16 @@ class FslInstance extends i {
|
|
|
29825
30077
|
}
|
|
29826
30078
|
this._applyTheme();
|
|
29827
30079
|
}
|
|
30080
|
+
/**
|
|
30081
|
+
* True when this instance owns a URL permalink segment (keyed by its
|
|
30082
|
+
* `uhash`/`id`) that a pending {@link FslPermalinkSync} restore will turn into
|
|
30083
|
+
* its FSL source — so `connectedCallback` can defer the machine build to that
|
|
30084
|
+
* restore instead of throwing on an otherwise-absent source.
|
|
30085
|
+
*/
|
|
30086
|
+
_permalinkSegmentPresent() {
|
|
30087
|
+
const key = permalink_key_for(this);
|
|
30088
|
+
return key !== null && read_fragment_param(location.hash, key) !== null;
|
|
30089
|
+
}
|
|
29828
30090
|
/**
|
|
29829
30091
|
* Discover direct-child `<jssm-on>` elements and install their
|
|
29830
30092
|
* subscriptions on the owned machine. Per #643:
|
|
@@ -30151,6 +30413,12 @@ class FslInstance extends i {
|
|
|
30151
30413
|
*/
|
|
30152
30414
|
_install_action_listener(config) {
|
|
30153
30415
|
const handler = (e) => {
|
|
30416
|
+
// A permalink-only instance wires its actions at connect but builds its
|
|
30417
|
+
// machine asynchronously (deferred restore). An event in that window is a
|
|
30418
|
+
// no-op rather than a throw via the `machine` getter.
|
|
30419
|
+
if (this._machine === undefined) {
|
|
30420
|
+
return;
|
|
30421
|
+
}
|
|
30154
30422
|
if (config.prevent_default) {
|
|
30155
30423
|
e.preventDefault();
|
|
30156
30424
|
}
|
package/dist/cdn/viz.js
CHANGED
|
@@ -23396,7 +23396,7 @@ var constants = /*#__PURE__*/Object.freeze({
|
|
|
23396
23396
|
* Useful for runtime diagnostics and for embedding in serialized machine
|
|
23397
23397
|
* snapshots so that deserializers can detect version-skew.
|
|
23398
23398
|
*/
|
|
23399
|
-
const version = "5.
|
|
23399
|
+
const version = "5.152.0";
|
|
23400
23400
|
|
|
23401
23401
|
// whargarbl lots of these return arrays could/should be sets
|
|
23402
23402
|
const { state_name_chars, state_name_first_chars, action_label_chars } = constants;
|