UNPKG

jssm

Version:

A Javascript finite state machine (FSM) with a terse DSL and a simple API. Most FSMs are one-liners. Fast, easy, powerful, well tested, typed with TypeScript, and visualizations. MIT License.

136 lines (133 loc) 5.02 kB
import { css, LitElement, html } from 'lit'; import { property, state } from 'lit/decorators.js'; import { unsafeHTML } from 'lit/directives/unsafe-html.js'; import { fsl_to_svg_string } from 'jssm/viz'; var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; /** * Normalize an arbitrary thrown value into a {@link JssmVizErrorDetail}. * Accepts anything (Error instances, JssmErrors with `.location`, plain * strings, etc.) and always produces a string `message`. * * ```typescript * normalize_viz_error(new Error('boom')); * // => { message: 'boom', location: undefined } * * normalize_viz_error({ message: 'parse failed', location: { line: 1 } }); * // => { message: 'parse failed', location: { line: 1 } } * * normalize_viz_error('bare string failure'); * // => { message: 'bare string failure', location: undefined } * ``` * * @param e The thrown value to normalize. * @returns A `{ message, location }` object suitable for use as the * `detail` of a `viz-error` `CustomEvent`. */ function normalize_viz_error(e) { if (typeof e === 'object' && e !== null) { const rec = e; const raw_message = rec.message; const message = (typeof raw_message === 'string' && raw_message.length > 0) ? raw_message : String(e); return { message, location: rec.location }; } return { message: String(e), location: undefined }; } /** * Web component that renders a jssm machine as inline SVG. * * @element jssm-viz * @cssproperty [--jssm-viz-min-height=100px] - Minimum height of the rendered SVG container. * @fires {CustomEvent<{ message: string; location?: unknown }>} viz-error - Fires when the FSL source fails to parse or render. */ class JssmViz extends LitElement { constructor() { super(...arguments); /** FSL source to render. */ this.fsl = ''; /** Optional Graphviz layout engine override (e.g. 'dot', 'neato'). */ this.engine = undefined; this._svg = ''; } /** * Lit lifecycle hook. Triggers an async SVG render whenever `fsl` or * `engine` change. * * @param changed - Map of changed reactive properties supplied by Lit. */ willUpdate(changed) { if (changed.has('fsl') || changed.has('engine')) { this._renderSvg(); } } /** * Render the current `fsl` source to an SVG string via the headless * `fsl_to_svg_string` pipeline. Updates `_svg` on success; emits a * `viz-error` `CustomEvent` on failure. Guards against stale results * when `fsl` changes mid-flight. * * @returns A promise that resolves once the render attempt has finished. */ async _renderSvg() { const source = this.fsl; if (!source) { this._svg = ''; return; } try { const result = await fsl_to_svg_string(source, this.engine ? { engine: this.engine } : undefined); // Guard against stale results: only commit if fsl has not changed since this render started. if (this.fsl === source) { this._svg = result; } } catch (e) { this._svg = ''; this.dispatchEvent(new CustomEvent('viz-error', { detail: normalize_viz_error(e), bubbles: true, composed: true, })); } } /** * Lit render method. Injects the most recent SVG string into the shadow * tree via the `unsafeHTML` directive. * * SVG content originates from `@viz-js/viz` (Graphviz WASM), which emits * sanitized SVG. `unsafeHTML` is required because Lit's template-literal * interpolation otherwise escapes the markup as text. The directive name * makes the trust boundary explicit at the call site. * * @returns A Lit `TemplateResult` wrapping the SVG in a `.container` div. */ render() { return html `<div class="container">${unsafeHTML(this._svg)}</div>`; } } JssmViz.styles = css ` :host { display: block; min-height: var(--jssm-viz-min-height, 100px); } .container { width: 100%; height: 100%; } `; __decorate([ property({ type: String }) ], JssmViz.prototype, "fsl", void 0); __decorate([ property({ type: String }) ], JssmViz.prototype, "engine", void 0); __decorate([ state() ], JssmViz.prototype, "_svg", void 0); export { JssmViz, normalize_viz_error };