libpetri 1.8.4 → 2.0.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 +47 -0
- package/dist/{chunk-B2D5DMTO.js → chunk-4L6JVKH4.js} +165 -8
- package/dist/chunk-4L6JVKH4.js.map +1 -0
- package/dist/chunk-H62Z76FY.js +1346 -0
- package/dist/chunk-H62Z76FY.js.map +1 -0
- package/dist/chunk-SXK2Z45Z.js +50 -0
- package/dist/chunk-SXK2Z45Z.js.map +1 -0
- package/dist/debug/index.d.ts +50 -3
- package/dist/debug/index.js +64 -6
- package/dist/debug/index.js.map +1 -1
- package/dist/doclet/index.d.ts +152 -31
- package/dist/doclet/index.js +458 -57
- package/dist/doclet/index.js.map +1 -1
- package/dist/doclet/resources/petrinet-diagrams.css +384 -7
- package/dist/doclet/resources/petrinet-diagrams.js +7214 -106
- package/dist/dot-exporter-PMHOQHZ3.js +8 -0
- package/dist/{event-store-BnyHh3TF.d.ts → event-store-2zkXeQkd.d.ts} +1 -1
- package/dist/export/index.d.ts +16 -2
- package/dist/export/index.js +1 -1
- package/dist/index.d.ts +41 -5
- package/dist/index.js +1221 -35
- package/dist/index.js.map +1 -1
- package/dist/petri-net-BDrj4XZE.d.ts +1461 -0
- package/dist/render-P6GROU7J.js +16 -0
- package/dist/render-P6GROU7J.js.map +1 -0
- package/dist/verification/index.d.ts +3 -144
- package/dist/verification/index.js +30 -1214
- package/dist/verification/index.js.map +1 -1
- package/dist/viewer/index.d.ts +196 -0
- package/dist/viewer/index.js +442 -0
- package/dist/viewer/index.js.map +1 -0
- package/dist/viewer/viewer-static.iife.js +3 -0
- package/dist/viewer/viewer.css +503 -0
- package/dist/viewer/viewer.iife.js +7219 -0
- package/package.json +17 -8
- package/dist/chunk-B2D5DMTO.js.map +0 -1
- package/dist/chunk-VQ4XMJTD.js +0 -107
- package/dist/chunk-VQ4XMJTD.js.map +0 -1
- package/dist/dot-exporter-3CVCH6J4.js +0 -8
- package/dist/petri-net-D-GN9g_D.d.ts +0 -570
- /package/dist/{dot-exporter-3CVCH6J4.js.map → dot-exporter-PMHOQHZ3.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/viewer/pan-zoom.ts","../../src/viewer/cluster-overlay.ts","../../src/viewer/styles.ts","../../src/viewer/index.ts"],"sourcesContent":["/**\n * Pan/zoom wrapper for the canonical libpetri viewer.\n *\n * Delegates to the `panzoom` library (no hand-rolled wheel handlers — we\n * intentionally drop the IIFE/javadoc version's bespoke math in favour of\n * a single battle-tested implementation). Defaults match the old debug-ui\n * and dev-preview values so the canonical viewer feels identical to what\n * users had before.\n *\n * @module viewer/pan-zoom\n */\n\nimport panzoom from 'panzoom';\n\nexport type PanzoomInstance = ReturnType<typeof panzoom>;\nexport type PanzoomOptions = Parameters<typeof panzoom>[1];\n\n/**\n * Default panzoom configuration shared across all viewer surfaces.\n *\n * - `maxZoom: 1000` — effectively unlimited; lets users dive into ~150-node\n * diagrams without hitting an artificial ceiling.\n * - `minZoom: 0.02` — allows fitting very large nets in a small viewport.\n * - `smoothScroll: false` — sharper interactive feel.\n * - `zoomDoubleClickSpeed: 1` — single-click double-zoom toggle.\n */\nexport const DEFAULT_PANZOOM_OPTS = {\n smoothScroll: false,\n zoomDoubleClickSpeed: 1,\n maxZoom: 1000,\n minZoom: 0.02,\n} as const satisfies PanzoomOptions;\n\n/**\n * Attach panzoom to a freshly rendered SVG element. The caller is\n * responsible for disposing any previous instance (or pass it as\n * `previous` to dispose it here).\n */\nexport function attachPanzoom(\n svg: SVGSVGElement,\n options?: PanzoomOptions,\n previous?: PanzoomInstance | null,\n): PanzoomInstance {\n if (previous) {\n try {\n previous.dispose();\n } catch {\n // Swallow disposal errors — the previous instance may already be\n // detached or the panzoom internals may have raised on a missing\n // root node. Either way we just want a fresh instance.\n }\n }\n const opts = { ...DEFAULT_PANZOOM_OPTS, ...options };\n return panzoom(svg, opts);\n}\n","/**\n * Cluster overlay for the canonical libpetri viewer.\n *\n * Post-processes a rendered SVG to surface subnet structure visually:\n * 1. Discover `<g class=\"cluster\">` subgraphs, derive each prefix from the\n * contained `<title>` (Graphviz emits `cluster_<sanitizedPrefix>`).\n * 2. Tag every contained `<g class=\"node\">` with `data-instance=\"<prefix>\"`\n * so filtering can target it via attribute selectors.\n * 3. Assign each cluster a deterministic HSL colour (FNV-1a hash + golden\n * ratio hue increment) and paint its border.\n * 4. Provide collapse/expand and \"show only <prefix>\" filtering.\n *\n * Unlike the previous debug-ui cluster-overlay, this module is instance-\n * based: every `mount()` call gets its own `ClusterOverlay` so multiple\n * viewers on the same page don't share collapse/isolate state.\n *\n * @module viewer/cluster-overlay\n */\n\n/** A discovered cluster with its DOM group, node count, and palette colour. */\nexport interface ClusterDescriptor {\n readonly prefix: string;\n readonly group: SVGGElement;\n readonly nodeCount: number;\n readonly color: string;\n}\n\n/**\n * Augmentation slots stashed on the cluster `<g>` element across collapse\n * cycles. We don't subclass SVGElement; we tag arbitrary properties on the\n * DOM node, matching the doclet's `petrinet-diagrams.js` so the cross-surface\n * mental model is identical.\n *\n * `_petriHiddenSiblings` holds the sibling `<g class=\"node\">` and\n * `<g class=\"edge\">` elements (sibling to the cluster, not its DOM children\n * — Graphviz never nests nodes inside cluster groups) tagged as belonging\n * to this prefix at the moment of collapse. They retain their place in the\n * SVG tree; we hide them via the `petri-collapsed-inside` class so the\n * underlying layout is preserved for a cheap re-expand.\n */\ninterface ClusterAugment {\n _petriHiddenSiblings?: Element[];\n _petriCollapsedBadge?: SVGTextElement | null;\n}\ntype AugmentedGroup = SVGGElement & ClusterAugment;\n\n/**\n * Walk the SVG, find every `<g class=\"cluster\">`, derive the prefix from\n * each cluster's `<title>` (`cluster_<sanitized>`), and tag every cluster-\n * interior `<g class=\"node\">` with `data-instance=\"<prefix>\"`. Returns one\n * descriptor per cluster.\n *\n * Graphviz emits cluster nodes as SIBLINGS of the cluster `<g>`, not as\n * children — `<g class=\"cluster\">` only carries title, polygon border, and\n * the cluster's text label. To recover cluster membership we match each\n * node's `<title>` (the original graph id) against libpetri's prefixing\n * convention: interior places are emitted as `p_<prefix>_<name>`,\n * transitions as `t_<prefix>_<name>`, and junctions as `j_<prefix>_<name>`.\n *\n * Does not split nested-cluster prefixes (`outer_inner` could mean either\n * `outer/inner` or a flat `outer_inner` prefix — Graphviz sanitisation is\n * lossy). We treat every cluster as its own top-level entry, matching the\n * doclet's pragmatism.\n */\nexport function discoverClusters(svg: SVGSVGElement): Map<string, ClusterDescriptor> {\n const out = new Map<string, ClusterDescriptor>();\n const clusterGroups = svg.querySelectorAll<SVGGElement>('g.cluster');\n if (!clusterGroups.length) return out;\n\n const allNodes = Array.from(svg.querySelectorAll<SVGGElement>('g.node'));\n\n for (const g of Array.from(clusterGroups)) {\n const titleEl = directChild(g, 'title');\n if (!titleEl) continue;\n const raw = titleEl.textContent ?? '';\n if (!raw.startsWith('cluster_')) continue;\n const prefix = raw.slice('cluster_'.length);\n if (out.has(prefix)) continue;\n\n let nodeCount = 0;\n for (const node of allNodes) {\n // Don't re-tag a node already claimed by an inner (more specific)\n // cluster — outer clusters can still match by string-prefix at filter\n // time, but the data-instance is the leaf.\n if (node.getAttribute('data-instance')) continue;\n const nodeTitle = directChild(node, 'title');\n if (!nodeTitle) continue;\n const id = (nodeTitle.textContent ?? '').trim();\n if (nodeBelongsToPrefix(id, prefix)) {\n node.setAttribute('data-instance', prefix);\n nodeCount++;\n }\n }\n\n out.set(prefix, {\n prefix,\n group: g,\n nodeCount,\n color: colorForPrefix(prefix),\n });\n }\n return out;\n}\n\n/**\n * Does this node id belong to the cluster with the given prefix?\n *\n * libpetri's DOT renderer prefixes interior names: places as `p_<prefix>_…`,\n * transitions as `t_<prefix>_…`, junctions as `j_<prefix>_…`. Generic DOT\n * may also use slash-separated names (`<prefix>/…`). We accept both shapes\n * so non-libpetri DOT graphs and bare-Graphviz fixtures still match.\n */\nfunction nodeBelongsToPrefix(nodeId: string, prefix: string): boolean {\n return (\n nodeId === prefix ||\n nodeId.startsWith(`${prefix}/`) ||\n nodeId.startsWith(`p_${prefix}_`) ||\n nodeId.startsWith(`t_${prefix}_`) ||\n nodeId.startsWith(`j_${prefix}_`)\n );\n}\n\n/**\n * Deterministic HSL palette. FNV-1a-ish hash of the prefix, multiplied by\n * the golden-ratio fraction (0.61803...) to spread adjacent prefixes\n * visually. Saturation/lightness are fixed (60% / 45%) so the palette\n * stays readable on both the doc background and the dark debug UI.\n */\nexport function colorForPrefix(prefix: string): string {\n let h = 2166136261;\n for (let i = 0; i < prefix.length; i++) {\n h ^= prefix.charCodeAt(i);\n h = (h * 16777619) >>> 0;\n }\n let hue = Math.floor(((h / 4294967296) * 360 + (h % 360) * 0.61803) % 360);\n if (hue < 0) hue += 360;\n return `hsl(${hue}, 60%, 45%)`;\n}\n\n/** Paint the cluster border with the deterministic prefix colour. */\nexport function paintClusterBorders(clusters: Map<string, ClusterDescriptor>): void {\n for (const c of clusters.values()) {\n const border = directChild(c.group, 'polygon') ?? directChild(c.group, 'path');\n if (border) {\n border.setAttribute('stroke', c.color);\n // Slightly thicker than the old 2.0 — the user asked for clusters to\n // be visually obvious. Backed off from 3 to keep it readable at the\n // tiniest zoom.\n border.setAttribute('stroke-width', '2.2');\n }\n }\n}\n\n/**\n * Collapse / expand a cluster.\n *\n * Graphviz emits nodes and edges as siblings of the cluster `<g>`, not as\n * its DOM children. So \"collapsing\" cannot work by detaching the cluster's\n * children — that only removes title/border/label. Instead, we identify the\n * cluster-interior nodes (`data-instance=\"<prefix>\"`) and edges with both\n * endpoints inside the cluster, then add the `petri-collapsed-inside` class\n * so the canonical viewer.css hides them. Stash the list on the cluster\n * group so re-expand is a constant-time class removal.\n *\n * Edges that cross the cluster boundary (one endpoint inside, one outside)\n * are intentionally left visible — they show the cluster's external wiring\n * even when the interior is hidden.\n *\n * A small `<text>` badge with the actual interior node count is appended\n * inside the cluster `<g>` (which keeps its layout box).\n */\nexport function setClusterCollapsed(cluster: ClusterDescriptor, collapsed: boolean): void {\n const g = cluster.group as AugmentedGroup;\n const isCollapsed = g.classList.contains('cluster-collapsed');\n const root = g.ownerSVGElement;\n if (!root) return;\n\n if (collapsed && !isCollapsed) {\n g.classList.add('cluster-collapsed');\n const hidden: Element[] = [];\n\n // Hide interior nodes (sibling elements tagged with this prefix).\n root\n .querySelectorAll<SVGGElement>(`g.node[data-instance=\"${cssEscape(cluster.prefix)}\"]`)\n .forEach((node) => {\n node.classList.add('petri-collapsed-inside');\n hidden.push(node);\n });\n\n // Hide interior-interior edges. Edges' titles are `from->to`; we hide\n // an edge only when BOTH endpoints belong to this cluster.\n root.querySelectorAll<SVGGElement>('g.edge').forEach((edge) => {\n const titleEl = directChild(edge, 'title');\n if (!titleEl) return;\n const t = (titleEl.textContent ?? '').trim();\n const arrow = t.indexOf('->');\n if (arrow < 0) return;\n const from = t.slice(0, arrow);\n const to = t.slice(arrow + 2);\n if (nodeBelongsToPrefix(from, cluster.prefix) && nodeBelongsToPrefix(to, cluster.prefix)) {\n edge.classList.add('petri-collapsed-inside');\n hidden.push(edge);\n }\n });\n\n g._petriHiddenSiblings = hidden;\n\n if (!g._petriCollapsedBadge) {\n const label = directChild(g, 'text') as SVGTextElement | null;\n if (label) {\n const badge = document.createElementNS('http://www.w3.org/2000/svg', 'text');\n badge.setAttribute('x', label.getAttribute('x') ?? '0');\n const labelY = parseFloat(label.getAttribute('y') ?? '0');\n badge.setAttribute('y', String(labelY + 16));\n badge.setAttribute('text-anchor', label.getAttribute('text-anchor') ?? 'middle');\n badge.setAttribute('font-size', '11');\n badge.setAttribute('fill', '#9ca3af');\n badge.setAttribute('class', 'cluster-collapsed-badge');\n badge.textContent = `(${cluster.nodeCount} internal node${cluster.nodeCount === 1 ? '' : 's'})`;\n g.appendChild(badge);\n g._petriCollapsedBadge = badge;\n }\n }\n } else if (!collapsed && isCollapsed) {\n g.classList.remove('cluster-collapsed');\n if (g._petriHiddenSiblings) {\n g._petriHiddenSiblings.forEach((el) => el.classList.remove('petri-collapsed-inside'));\n g._petriHiddenSiblings = undefined;\n }\n if (g._petriCollapsedBadge && g._petriCollapsedBadge.parentNode === g) {\n g.removeChild(g._petriCollapsedBadge);\n g._petriCollapsedBadge = null;\n }\n }\n}\n\n/**\n * Toggle the \"show only <prefix>\" filter. Passing `null` (or the same\n * prefix already active) clears the filter. Mirrors the doclet's\n * `applyFilter`, including the edge-dimming heuristic that parses each\n * edge's `<title>` (`from->to`) and keeps an edge visible when either\n * endpoint matches the active prefix.\n */\nexport function applyFilter(svg: SVGSVGElement, prefix: string | null): void {\n if (prefix == null) {\n svg.removeAttribute('data-active-filter');\n svg.classList.remove('has-active-filter');\n svg.querySelectorAll('g.node, g.edge').forEach((el) => el.classList.remove('petri-dimmed'));\n return;\n }\n svg.setAttribute('data-active-filter', prefix);\n svg.classList.add('has-active-filter');\n svg.querySelectorAll('g.node').forEach((node) => {\n const di = node.getAttribute('data-instance');\n const match =\n !!di &&\n (di === prefix || di.indexOf(prefix + '_') === 0 || di.indexOf(prefix + '/') === 0);\n if (match) node.classList.remove('petri-dimmed');\n else node.classList.add('petri-dimmed');\n });\n svg.querySelectorAll('g.edge').forEach((edge) => {\n const titleEl = directChild(edge, 'title');\n if (!titleEl) return;\n const titleText = titleEl.textContent ?? '';\n const arrow = titleText.indexOf('->');\n if (arrow < 0) {\n edge.classList.remove('petri-dimmed');\n return;\n }\n const from = titleText.slice(0, arrow);\n const to = titleText.slice(arrow + 2);\n const matches = (name: string): boolean =>\n name.indexOf(prefix + '/') >= 0 || name.indexOf(prefix + '_') >= 0 || name === prefix;\n if (matches(from) || matches(to)) edge.classList.remove('petri-dimmed');\n else edge.classList.add('petri-dimmed');\n });\n}\n\n/**\n * Returns true when the place/transition node identified by `graphId` is\n * currently inside a collapsed cluster (i.e. detached from the live SVG).\n * Live consumers (e.g. highlight code in debug-ui) use this to skip lookups\n * that would otherwise hit a stale cache entry pointing at a detached node.\n */\nexport function isInsideCollapsedCluster(\n svg: SVGSVGElement | null,\n clusters: Map<string, ClusterDescriptor>,\n collapsedPrefixes: ReadonlySet<string>,\n graphId: string,\n): boolean {\n if (collapsedPrefixes.size === 0) return false;\n if (!svg) return false;\n for (const prefix of collapsedPrefixes) {\n if (!clusters.has(prefix)) continue;\n if (nodeBelongsToPrefix(graphId, prefix)) return true;\n }\n return false;\n}\n\n/** Minimal CSS.escape polyfill — happy-dom may not implement CSS globals. */\nfunction cssEscape(s: string): string {\n if (typeof CSS !== 'undefined' && typeof CSS.escape === 'function') return CSS.escape(s);\n return s.replace(/[^a-zA-Z0-9_-]/g, (c) => `\\\\${c}`);\n}\n\n/**\n * `:scope > <tag>` polyfill — happy-dom doesn't reliably resolve `:scope`\n * against namespaced SVG elements (returns null even when the child exists).\n * Walk `children` directly instead, matching by lower-cased tag name.\n */\nfunction directChild(parent: Element, tagName: string): Element | null {\n const lc = tagName.toLowerCase();\n for (const child of Array.from(parent.children)) {\n if (child.tagName.toLowerCase() === lc) return child;\n }\n return null;\n}\n","/**\n * CSS custom properties exposed by the canonical viewer stylesheet.\n *\n * Each property has a default in `viewer.css`. Consumers can override any of\n * them at any scope (the viewer container, the document root, etc.) to\n * theme the viewer without touching the canonical CSS.\n *\n * Names are listed here as a reference + so the IIFE bundle can expose them\n * on `window.LibpetriViewer.cssVariables`. The list is informational — the\n * stylesheet is still the source of truth.\n *\n * @module viewer/styles\n */\n\n/** CSS custom property names that the canonical stylesheet honours. */\nexport const VIEWER_CSS_VARIABLES = [\n '--lpv-bg',\n '--lpv-header-bg',\n '--lpv-border',\n '--lpv-text',\n '--lpv-muted',\n '--lpv-cluster-bg',\n '--lpv-cluster-bg-collapsed',\n '--lpv-cluster-stroke-width',\n '--lpv-cluster-stroke-dash',\n '--lpv-cluster-fill-tint',\n '--lpv-dim-opacity',\n '--lpv-active-filter-outline',\n '--lpv-legend-bg',\n '--lpv-chip-bg',\n '--lpv-chip-active-bg',\n] as const;\n\nexport type ViewerCssVariable = (typeof VIEWER_CSS_VARIABLES)[number];\n","/**\n * Canonical libpetri Petri-net diagram viewer.\n *\n * Single source of truth for DOT → SVG rendering + cluster overlay across\n * all four surfaces:\n * - debug-ui (live debugger)\n * - dev-preview (Vite app)\n * - Javadoc taglet (Java doclet)\n * - Rustdoc embed (libpetri-docgen)\n * - TypeDoc / typescript doclet\n *\n * Build outputs:\n * - ESM: `dist/viewer/index.js` — consumed by Vite/Webpack/Node bundlers.\n * - IIFE: `dist/viewer/viewer.iife.js` — self-contained, exposes\n * `window.LibpetriViewer = { mount, ... }`. Used by the doc taglets\n * that ship pre-rendered HTML pages without a bundler.\n * - CSS: `dist/viewer/viewer.css` — canonical stylesheet (copied from\n * `src/viewer/resources/viewer.css`).\n *\n * Edits to viewer behaviour MUST happen here. The three resource directories\n * under `java/`, `rust/`, and `typescript/src/doclet/` are **build outputs**\n * populated by `scripts/build-viewer.sh` and must not be hand-edited.\n *\n * @module viewer\n */\n\nimport {\n attachPanzoom,\n DEFAULT_PANZOOM_OPTS,\n type PanzoomInstance,\n type PanzoomOptions,\n} from './pan-zoom.js';\nimport {\n applyFilter as overlayApplyFilter,\n colorForPrefix,\n discoverClusters,\n isInsideCollapsedCluster as overlayIsInsideCollapsedCluster,\n paintClusterBorders,\n setClusterCollapsed,\n type ClusterDescriptor,\n} from './cluster-overlay.js';\nimport { VIEWER_CSS_VARIABLES } from './styles.js';\n\nexport {\n colorForPrefix,\n discoverClusters,\n VIEWER_CSS_VARIABLES,\n DEFAULT_PANZOOM_OPTS,\n};\nexport type { ClusterDescriptor, PanzoomInstance, PanzoomOptions };\n\n/** Handle returned by {@link mount}. */\nexport interface ViewerHandle {\n /** The rendered root `<svg>` element. */\n readonly svg: SVGSVGElement;\n /** The panzoom instance attached to the SVG. */\n readonly panzoom: PanzoomInstance;\n /** Map of cluster prefix → descriptor for the current SVG. */\n readonly clusters: ReadonlyMap<string, ClusterDescriptor>;\n /** Prefixes currently marked collapsed on this handle. */\n readonly collapsedPrefixes: ReadonlySet<string>;\n /** Currently active \"show only <prefix>\" filter, or null. */\n readonly activeFilter: string | null;\n /** Collapse a single cluster by prefix. No-op if unknown. */\n collapse(prefix: string): void;\n /** Expand a single cluster by prefix. No-op if unknown. */\n expand(prefix: string): void;\n /** Collapse every discovered cluster. */\n collapseAll(): void;\n /** Expand every collapsed cluster. */\n expandAll(): void;\n /** Apply the \"show only <prefix>\" filter; pass `null` to clear. */\n filter(prefix: string | null): void;\n /** Reset pan/zoom to the identity transform. */\n resetZoom(): void;\n /** Scale and translate so the full diagram fits the host viewport. */\n fit(): void;\n /** Reports whether `graphId` is inside a currently-collapsed cluster. */\n isInsideCollapsedCluster(graphId: string): boolean;\n /** Tear down: dispose panzoom and detach listeners. */\n dispose(): void;\n}\n\n/** Options accepted by {@link mount}. */\nexport interface MountOptions {\n /**\n * Previous handle to dispose before mounting. Pass the handle returned\n * by a prior `mount()` call so callers don't need to track it separately.\n * Cluster collapse / filter state from `previousHandle` is preserved\n * across the re-render so the user's intent survives a live re-draw.\n */\n readonly previousHandle?: ViewerHandle | null;\n /** Per-call panzoom overrides; merged on top of {@link DEFAULT_PANZOOM_OPTS}. */\n readonly panzoom?: PanzoomOptions;\n /** Fired after a cluster's collapsed state changes via the handle API. */\n readonly onClusterCollapse?: (prefix: string, collapsed: boolean) => void;\n /** Fired after `filter()` runs, with the new filter prefix or null. */\n readonly onClusterFilter?: (prefix: string | null) => void;\n /** When true, append a legend sidebar + filter chip strip inside `container`. */\n readonly chrome?: boolean;\n}\n\n/**\n * Render a DOT source, mount the resulting SVG into `container`, wire pan/zoom,\n * and surface cluster overlay controls.\n *\n * The container's existing children are removed before the new SVG is\n * appended (the same teardown behaviour `renderDotToContainer` used to provide).\n *\n * Pass `dotSource === null` to adopt an SVG already present as a direct child\n * of `container` (pre-rendered SVG path — e.g. the Java javadoc taglet emits\n * `dot -Tsvg` at build time). In that mode the viewer never imports the\n * runtime DOT renderer, which lets the static IIFE bundle drop `@viz-js/viz`\n * entirely.\n *\n * NOTE: when shipping a pre-rendered SVG (`dotSource === null`), do NOT also\n * pass `previousHandle` — the handle's `dispose()` runs before SVG detection\n * and would orphan the host. The Java taglet mounts once per page so this is\n * a non-issue in practice.\n */\nexport async function mount(\n dotSource: string | null,\n container: HTMLElement,\n opts: MountOptions = {},\n): Promise<ViewerHandle> {\n const previousHandle = opts.previousHandle ?? null;\n const preservedCollapsed = previousHandle\n ? new Set(previousHandle.collapsedPrefixes)\n : new Set<string>();\n const preservedFilter = previousHandle?.activeFilter ?? null;\n\n if (previousHandle) {\n previousHandle.dispose();\n }\n\n // Gate on dotSource (not on \"SVG present\") — debug-ui re-renders by\n // passing DOT with `previousHandle` and the leftover SVG must NOT be\n // misread as pre-rendered. Java passes literal `null` for the\n // pre-rendered path.\n let svg: SVGSVGElement;\n const existing = container.querySelector(':scope > svg');\n if (dotSource == null) {\n if (!(existing instanceof SVGSVGElement)) {\n throw new Error('mount: no DOT source and no pre-rendered SVG');\n }\n svg = existing;\n } else {\n // Dynamic import so the static IIFE entry can alias './render.js' to a\n // throwing stub and drop the @viz-js/viz dependency.\n const { renderDotToSvg } = await import('./render.js');\n svg = await renderDotToSvg(dotSource);\n container.innerHTML = '';\n container.appendChild(svg);\n }\n // Tag the host so the canonical CSS (which scopes to .libpetri-viewer)\n // applies even when the consumer didn't wrap us in a .petrinet-diagram.\n container.classList.add('libpetri-viewer');\n\n const panzoomInstance = attachPanzoom(svg, opts.panzoom);\n const clusters = discoverClusters(svg);\n paintClusterBorders(clusters);\n\n const collapsedPrefixes = new Set<string>();\n let activeFilter: string | null = null;\n let disposed = false;\n\n // Chrome elements (created lazily; only when chrome:true is requested).\n // Re-rendering through the same handle does not duplicate them.\n let chromeRoot: HTMLElement | null = null;\n\n function ensureChrome(): void {\n if (!opts.chrome) return;\n if (chromeRoot && chromeRoot.parentNode === container) return;\n chromeRoot = document.createElement('div');\n chromeRoot.className = 'libpetri-viewer-chrome';\n chromeRoot.style.position = 'absolute';\n chromeRoot.style.inset = '0';\n chromeRoot.style.pointerEvents = 'none';\n\n const legend = document.createElement('div');\n legend.className = 'diagram-legend';\n legend.style.pointerEvents = 'auto';\n legend.innerHTML = '<div class=\"legend-title\">Clusters</div>';\n chromeRoot.appendChild(legend);\n\n const strip = document.createElement('div');\n strip.className = 'diagram-filter-strip';\n strip.style.pointerEvents = 'auto';\n strip.innerHTML = '<span class=\"filter-strip-label\">Show only:</span>';\n chromeRoot.appendChild(strip);\n\n const controls = document.createElement('div');\n controls.className = 'diagram-controls';\n controls.style.pointerEvents = 'auto';\n const resetBtn = document.createElement('button');\n resetBtn.type = 'button';\n resetBtn.className = 'diagram-btn btn-reset';\n resetBtn.title = 'Reset view';\n resetBtn.textContent = 'Reset';\n // Reset = return to the meaningful default (fit-to-host), not the\n // mathematical identity transform. `resetZoom()` stays on the handle\n // API for callers that want raw identity.\n resetBtn.addEventListener('click', () => handle.fit());\n const fsBtn = document.createElement('button');\n fsBtn.type = 'button';\n fsBtn.className = 'diagram-btn btn-fullscreen';\n fsBtn.title = 'Toggle fullscreen';\n fsBtn.textContent = 'Fullscreen';\n fsBtn.addEventListener('click', () => toggleFullscreen(container, fsBtn));\n controls.appendChild(resetBtn);\n controls.appendChild(fsBtn);\n chromeRoot.appendChild(controls);\n\n // Position parent must be relative for the absolute chrome to anchor.\n if (getComputedStyle(container).position === 'static') {\n container.style.position = 'relative';\n }\n container.appendChild(chromeRoot);\n\n renderLegend(legend);\n renderFilterStrip(strip);\n }\n\n // In-page lightbox fullscreen: toggle a CSS class on the host and\n // re-fit. We deliberately don't invoke the native Fullscreen API —\n // users want the diagram to expand inside the page, not yank the\n // browser into OS-level fullscreen.\n function toggleFullscreen(host: HTMLElement, btn: HTMLButtonElement): void {\n const isFs = host.classList.toggle('diagram-fullscreen');\n btn.textContent = isFs ? 'Exit fullscreen' : 'Fullscreen';\n // Re-fit on both enter and exit — the available area changed, so the\n // previous transform is no longer the right size. Defer a frame so\n // flex layout settles before getBBox + clientWidth/Height reads.\n requestAnimationFrame(() => handle.fit());\n }\n\n function renderLegend(legend: HTMLElement): void {\n legend.innerHTML = '<div class=\"legend-title\">Clusters</div>';\n for (const cluster of clusters.values()) {\n const row = document.createElement('div');\n row.className = 'legend-row';\n row.innerHTML =\n '<span class=\"legend-ribbon\" style=\"background:' + cluster.color + '\"></span>' +\n '<span class=\"legend-label\"></span>' +\n '<span class=\"legend-count\"></span>';\n (row.querySelector('.legend-label') as HTMLElement).textContent = cluster.prefix;\n (row.querySelector('.legend-count') as HTMLElement).textContent = String(cluster.nodeCount);\n row.addEventListener('click', () => {\n if (collapsedPrefixes.has(cluster.prefix)) {\n handle.expand(cluster.prefix);\n } else {\n handle.collapse(cluster.prefix);\n }\n });\n legend.appendChild(row);\n }\n }\n\n function renderFilterStrip(strip: HTMLElement): void {\n strip.innerHTML = '<span class=\"filter-strip-label\">Show only:</span>';\n const allChip = document.createElement('button');\n allChip.type = 'button';\n allChip.className = 'filter-chip filter-chip-all filter-chip-active';\n allChip.textContent = 'all';\n allChip.addEventListener('click', () => {\n handle.filter(null);\n });\n strip.appendChild(allChip);\n for (const cluster of clusters.values()) {\n const chip = document.createElement('button');\n chip.type = 'button';\n chip.className = 'filter-chip';\n chip.style.borderColor = cluster.color;\n chip.dataset.prefix = cluster.prefix;\n chip.innerHTML =\n '<span class=\"chip-dot\" style=\"background:' + cluster.color + '\"></span>';\n chip.append(document.createTextNode(cluster.prefix));\n chip.addEventListener('click', () => {\n if (activeFilter === cluster.prefix) {\n handle.filter(null);\n } else {\n handle.filter(cluster.prefix);\n }\n });\n strip.appendChild(chip);\n }\n }\n\n function refreshChromeActiveStates(): void {\n if (!chromeRoot) return;\n const strip = chromeRoot.querySelector('.diagram-filter-strip');\n if (!strip) return;\n strip.querySelectorAll<HTMLElement>('.filter-chip').forEach((chip) => {\n chip.classList.remove('filter-chip-active');\n });\n if (activeFilter === null) {\n strip\n .querySelector<HTMLElement>('.filter-chip-all')\n ?.classList.add('filter-chip-active');\n } else {\n strip\n .querySelector<HTMLElement>(`.filter-chip[data-prefix=\"${activeFilter}\"]`)\n ?.classList.add('filter-chip-active');\n }\n }\n\n const handle: ViewerHandle = {\n svg,\n panzoom: panzoomInstance,\n clusters,\n get collapsedPrefixes() {\n return collapsedPrefixes;\n },\n get activeFilter() {\n return activeFilter;\n },\n collapse(prefix: string): void {\n if (disposed) return;\n const cluster = clusters.get(prefix);\n if (!cluster) return;\n if (!collapsedPrefixes.has(prefix)) {\n setClusterCollapsed(cluster, true);\n collapsedPrefixes.add(prefix);\n opts.onClusterCollapse?.(prefix, true);\n }\n },\n expand(prefix: string): void {\n if (disposed) return;\n const cluster = clusters.get(prefix);\n if (!cluster) return;\n if (collapsedPrefixes.has(prefix)) {\n setClusterCollapsed(cluster, false);\n collapsedPrefixes.delete(prefix);\n opts.onClusterCollapse?.(prefix, false);\n }\n },\n collapseAll(): void {\n if (disposed) return;\n for (const cluster of clusters.values()) {\n if (!collapsedPrefixes.has(cluster.prefix)) {\n setClusterCollapsed(cluster, true);\n collapsedPrefixes.add(cluster.prefix);\n opts.onClusterCollapse?.(cluster.prefix, true);\n }\n }\n },\n expandAll(): void {\n if (disposed) return;\n for (const prefix of Array.from(collapsedPrefixes)) {\n const cluster = clusters.get(prefix);\n if (!cluster) continue;\n setClusterCollapsed(cluster, false);\n collapsedPrefixes.delete(prefix);\n opts.onClusterCollapse?.(prefix, false);\n }\n },\n filter(prefix: string | null): void {\n if (disposed) return;\n activeFilter = prefix;\n overlayApplyFilter(svg, prefix);\n opts.onClusterFilter?.(prefix);\n refreshChromeActiveStates();\n },\n resetZoom(): void {\n if (disposed) return;\n try {\n panzoomInstance.moveTo(0, 0);\n panzoomInstance.zoomAbs(0, 0, 1);\n } catch {\n // panzoom internals can throw if the element was detached; ignore.\n }\n },\n fit(): void {\n if (disposed) return;\n try {\n // With the host CSS-sized and the SVG forced to width/height: 100%\n // (see .petrinet-diagram-viewer rules in viewer.css), the browser\n // auto-fits viewBox content via preserveAspectRatio=\"xMidYMid meet\".\n // \"Fit\" is therefore the panzoom identity transform — no math needed.\n panzoomInstance.zoomAbs(0, 0, 1);\n panzoomInstance.moveTo(0, 0);\n } catch {\n // panzoom internals can throw if the element was detached; ignore.\n }\n },\n isInsideCollapsedCluster(graphId: string): boolean {\n return overlayIsInsideCollapsedCluster(svg, clusters, collapsedPrefixes, graphId);\n },\n dispose(): void {\n if (disposed) return;\n disposed = true;\n try {\n panzoomInstance.dispose();\n } catch {\n // ignore\n }\n if (chromeRoot && chromeRoot.parentNode === container) {\n container.removeChild(chromeRoot);\n }\n chromeRoot = null;\n },\n };\n\n // Re-apply preserved collapse/filter state from the previous handle so\n // a live re-render (e.g. on marking-snapshot) keeps the user's selection.\n for (const prefix of preservedCollapsed) {\n handle.collapse(prefix);\n }\n if (preservedFilter !== null) {\n handle.filter(preservedFilter);\n }\n\n ensureChrome();\n\n // Default state: fit-to-host. Huge diagrams shouldn't mount at 1:1\n // (the user only sees the top-left corner). Deferred a frame so the\n // SVG has been laid out before getBBox + clientWidth/Height reads.\n requestAnimationFrame(() => handle.fit());\n\n return handle;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/*! libpetri viewer (IIFE) — generated, do not edit; source: typescript/src/viewer/ */
|
|
2
|
+
"use strict";(()=>{var Er=Object.create;var Te=Object.defineProperty;var Tr=Object.getOwnPropertyDescriptor;var Ar=Object.getOwnPropertyNames;var Lr=Object.getPrototypeOf,Mr=Object.prototype.hasOwnProperty;var Or=(t,e)=>()=>(t&&(e=t(t=0)),e);var N=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports),ot=(t,e)=>{for(var r in e)Te(t,r,{get:e[r],enumerable:!0})},zr=(t,e,r,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let u of Ar(e))!Mr.call(t,u)&&u!==r&&Te(t,u,{get:()=>e[u],enumerable:!(i=Tr(e,u))||i.enumerable});return t};var Br=(t,e,r)=>(r=t!=null?Er(Lr(t)):{},zr(e||!t||!t.__esModule?Te(r,"default",{value:t,enumerable:!0}):r,t));var at=N((hn,ce)=>{ce.exports=it;ce.exports.addWheelListener=it;ce.exports.removeWheelListener=Fr;function it(t,e,r){t.addEventListener("wheel",e,r)}function Fr(t,e,r){t.removeEventListener("wheel",e,r)}});var dt=N((gn,ft)=>{var Vr=4,kr=.001,Dr=1e-7,Pr=10,Q=11,ue=1/(Q-1),Ir=typeof Float32Array=="function";function lt(t,e){return 1-3*e+3*t}function st(t,e){return 3*e-6*t}function ct(t){return 3*t}function fe(t,e,r){return((lt(e,r)*t+st(e,r))*t+ct(e))*t}function ut(t,e,r){return 3*lt(e,r)*t*t+2*st(e,r)*t+ct(e)}function Nr(t,e,r,i,u){var c,o,v=0;do o=e+(r-e)/2,c=fe(o,i,u)-t,c>0?r=o:e=o;while(Math.abs(c)>Dr&&++v<Pr);return o}function _r(t,e,r,i){for(var u=0;u<Vr;++u){var c=ut(e,r,i);if(c===0)return e;var o=fe(e,r,i)-t;e-=o/c}return e}function qr(t){return t}ft.exports=function(e,r,i,u){if(!(0<=e&&e<=1&&0<=i&&i<=1))throw new Error("bezier x values must be in [0, 1] range");if(e===r&&i===u)return qr;for(var c=Ir?new Float32Array(Q):new Array(Q),o=0;o<Q;++o)c[o]=fe(o*ue,e,i);function v(p){for(var f=0,d=1,y=Q-1;d!==y&&c[d]<=p;++d)f+=ue;--d;var x=(p-c[d])/(c[d+1]-c[d]),h=f+x*ue,L=ut(h,e,i);return L>=kr?_r(p,h,e,i):L===0?h:Nr(p,f,f+ue,e,i)}return function(f){return f===0?0:f===1?1:fe(v(f),r,u)}}});var ht=N((yn,de)=>{var X=dt(),mt={ease:X(.25,.1,.25,1),easeIn:X(.42,0,1,1),easeOut:X(0,0,.58,1),easeInOut:X(.42,0,.58,1),linear:X(0,0,1,1)};de.exports=Zr;de.exports.makeAggregateRaf=vt;de.exports.sharedScheduler=vt();function Zr(t,e,r){var i=Object.create(null),u=Object.create(null);r=r||{};var c=typeof r.easing=="function"?r.easing:mt[r.easing];c||(r.easing&&console.warn("Unknown easing function in amator: "+r.easing),c=mt.ease);var o=typeof r.step=="function"?r.step:pt,v=typeof r.done=="function"?r.done:pt,p=Rr(r.scheduler),f=Object.keys(e);f.forEach(function(S){i[S]=t[S],u[S]=e[S]-t[S]});var d=typeof r.duration=="number"?r.duration:400,y=Math.max(1,d*.06),x,h=0;return x=p.next(B),{cancel:L};function L(){p.cancel(x),x=0}function B(){var S=c(h/y);h+=1,F(S),h<=y?(x=p.next(B),o(t)):(x=0,setTimeout(function(){v(t)},0))}function F(S){f.forEach(function(P){t[P]=u[P]*S+i[P]})}}function pt(){}function Rr(t){if(!t){var e=typeof window<"u"&&window.requestAnimationFrame;return e?Gr():Hr()}if(typeof t.next!="function")throw new Error("Scheduler is supposed to have next(cb) function");if(typeof t.cancel!="function")throw new Error("Scheduler is supposed to have cancel(handle) function");return t}function Gr(){return{next:window.requestAnimationFrame.bind(window),cancel:window.cancelAnimationFrame.bind(window)}}function Hr(){return{next:function(t){return setTimeout(t,1e3/60)},cancel:function(t){return clearTimeout(t)}}}function vt(){var t=new Set,e=new Set,r=0;return{next:u,cancel:u,clearAll:i};function i(){t.clear(),e.clear(),cancelAnimationFrame(r),r=0}function u(p){e.add(p),c()}function c(){r||(r=requestAnimationFrame(o))}function o(){r=0;var p=e;e=t,t=p,t.forEach(function(f){f()}),t.clear()}function v(p){e.delete(p)}}});var yt=N((xn,gt)=>{"use strict";function Wr(t){Yr(t);let e=jr(t);return t.on=e.on,t.off=e.off,t.fire=e.fire,t}function jr(t){let e=Object.create(null);return{on:function(r,i,u){if(typeof i!="function")throw new Error("callback is expected to be a function");let c=e[r];return c||(c=e[r]=[]),c.push({callback:i,ctx:u}),t},off:function(r,i){if(typeof r>"u")return e=Object.create(null),t;if(e[r])if(typeof i!="function")delete e[r];else{let u=e[r];for(let c=0;c<u.length;++c)u[c].callback===i&&u.splice(c,1)}return t},fire:function(r){let i=e[r];if(!i)return t;let u;arguments.length>1&&(u=Array.prototype.slice.call(arguments,1));for(let c=0;c<i.length;++c){let o=i[c];o.callback.apply(o.ctx,u)}return t}}}function Yr(t){if(!t)throw new Error("Eventify cannot use falsy object as events subject");let e=["on","fire","off"];for(let r=0;r<e.length;++r)if(t.hasOwnProperty(e[r]))throw new Error("Subject cannot be eventified, since it already has property '"+e[r]+"'")}gt.exports=Wr});var bt=N((bn,xt)=>{xt.exports=$r;function $r(t,e,r){typeof r!="object"&&(r={});var i=typeof r.minVelocity=="number"?r.minVelocity:5,u=typeof r.amplitude=="number"?r.amplitude:.25,c=typeof r.cancelAnimationFrame=="function"?r.cancelAnimationFrame:Ur(),o=typeof r.requestAnimationFrame=="function"?r.requestAnimationFrame:Kr(),v,p,f=342,d,y,x,h,L,B,F,S;return{start:A,stop:g,cancel:P};function P(){c(d),c(S)}function A(){v=t(),h=F=y=L=0,p=new Date,c(d),c(S),d=o(m)}function m(){var C=Date.now(),O=C-p;p=C;var V=t(),k=V.x-v.x,q=V.y-v.y;v=V;var z=1e3/(1+O);y=.8*k*z+.2*y,L=.8*q*z+.2*L,d=o(m)}function g(){c(d),c(S);var C=t();x=C.x,B=C.y,p=Date.now(),(y<-i||y>i)&&(h=u*y,x+=h),(L<-i||L>i)&&(F=u*L,B+=F),S=o(w)}function w(){var C=Date.now()-p,O=!1,V=0,k=0;h&&(V=-h*Math.exp(-C/f),V>.5||V<-.5?O=!0:V=h=0),F&&(k=-F*Math.exp(-C/f),k>.5||k<-.5?O=!0:k=F=0),O&&(e(x+V,B+k),S=o(w))}}function Ur(){return typeof cancelAnimationFrame=="function"?cancelAnimationFrame:clearTimeout}function Kr(){return typeof requestAnimationFrame=="function"?requestAnimationFrame:function(t){return setTimeout(t,16)}}});var Et=N((wn,Ct)=>{Ct.exports=Jr;function Jr(t){if(t)return{capture:St,release:St};var e,r,i,u=!1;return{capture:c,release:o};function c(v){u=!0,r=window.document.onselectstart,i=window.document.ondragstart,window.document.onselectstart=wt,e=v,e.ondragstart=wt}function o(){u&&(u=!1,window.document.onselectstart=r,e&&(e.ondragstart=i))}}function wt(t){return t.stopPropagation(),!1}function St(){}});var At=N((Sn,Tt)=>{Tt.exports=Qr;function Qr(){this.x=0,this.y=0,this.scale=1}});var Mt=N((Cn,Ae)=>{Ae.exports=Xr;Ae.exports.canAttach=Lt;function Xr(t,e){if(!Lt(t))throw new Error("svg element is required for svg.panzoom to work");var r=t.ownerSVGElement;if(!r)throw new Error("Do not apply panzoom to the root <svg> element. Use its child instead (e.g. <g></g>). As of March 2016 only FireFox supported transform on the root element");e.disableKeyboardInteraction||r.setAttribute("tabindex",0);var i={getBBox:c,getScreenCTM:o,getOwner:u,applyTransform:p,initTransform:v};return i;function u(){return r}function c(){var f=t.getBBox();return{left:f.x,top:f.y,width:f.width,height:f.height}}function o(){var f=r.getCTM();return f||r.getScreenCTM()}function v(f){var d=t.getCTM();d===null&&(d=document.createElementNS("http://www.w3.org/2000/svg","svg").createSVGMatrix()),f.x=d.e,f.y=d.f,f.scale=d.a,r.removeAttributeNS(null,"viewBox")}function p(f){t.setAttribute("transform","matrix("+f.scale+" 0 0 "+f.scale+" "+f.x+" "+f.y+")")}}function Lt(t){return t&&t.ownerSVGElement&&t.getCTM}});var zt=N((En,Le)=>{Le.exports=en;Le.exports.canAttach=Ot;function en(t,e){var r=Ot(t);if(!r)throw new Error("panzoom requires DOM element to be attached to the DOM tree");var i=t.parentElement;t.scrollTop=0,e.disableKeyboardInteraction||i.setAttribute("tabindex",0);var u={getBBox:o,getOwner:c,applyTransform:v};return u;function c(){return i}function o(){return{left:0,top:0,width:t.clientWidth,height:t.clientHeight}}function v(p){t.style.transformOrigin="0 0 0",t.style.transform="matrix("+p.scale+", 0, 0, "+p.scale+", "+p.x+", "+p.y+")"}}function Ot(t){return t&&t.parentElement&&t.style}});var Zt=N((Tn,qt)=>{"use strict";var Bt=at(),Me=ht(),tn=yt(),rn=bt(),Nt=Et(),nn=Nt(),on=Nt(!0),an=At(),Ft=Mt(),Vt=zt(),ln=1,sn=1.75,kt=300,Dt=200;qt.exports=_t;function _t(t,e){e=e||{};var r=e.controller;if(r||(Ft.canAttach(t)?r=Ft(t,e):Vt.canAttach(t)&&(r=Vt(t,e))),!r)throw new Error("Cannot create panzoom for the current type of dom element");var i=r.getOwner(),u={x:0,y:0},c=!1,o=new an;r.initTransform&&r.initTransform(o);var v=typeof e.filterKey=="function"?e.filterKey:$,p=typeof e.pinchSpeed=="number"?e.pinchSpeed:1,f=e.bounds,d=typeof e.maxZoom=="number"?e.maxZoom:Number.POSITIVE_INFINITY,y=typeof e.minZoom=="number"?e.minZoom:0,x=typeof e.boundsPadding=="number"?e.boundsPadding:.05,h=typeof e.zoomDoubleClickSpeed=="number"?e.zoomDoubleClickSpeed:sn,L=e.beforeWheel||$,B=e.beforeMouseDown||$,F=typeof e.zoomSpeed=="number"?e.zoomSpeed:ln,S=Pt(e.transformOrigin),P=e.enableTextSelection?on:nn;cn(f),e.autocenter&&er();var A,m=0,g=0,w=0,C=null,O=new Date,V,k=!1,q=!1,z,D,ge,ye,xe,I;"smoothScroll"in e&&!e.smoothScroll?I=un():I=rn(ur,vr,e.smoothScroll);var be,U,te,re=!1;Ne();var ne={dispose:hr,moveBy:H,moveTo:we,smoothMoveTo:pr,centerOn:mr,zoomTo:le,zoomAbs:oe,smoothZoom:ae,smoothZoomAbs:Cr,showRectangle:Xt,pause:Kt,resume:Jt,isPaused:Qt,getTransform:tr,getMinZoom:rr,setMinZoom:nr,getMaxZoom:or,setMaxZoom:ir,getTransformOrigin:ar,setTransformOrigin:lr,getZoomSpeed:sr,setZoomSpeed:cr};tn(ne);var Fe=typeof e.initialX=="number"?e.initialX:o.x,Ve=typeof e.initialY=="number"?e.initialY:o.y,ke=typeof e.initialZoom=="number"?e.initialZoom:o.scale;return(Fe!=o.x||Ve!=o.y||ke!=o.scale)&&oe(Fe,Ve,ke),ne;function Kt(){_e(),re=!0}function Jt(){re&&(Ne(),re=!1)}function Qt(){return re}function Xt(n){var a=i.getBoundingClientRect(),l=Z(a.width,a.height),s=n.right-n.left,b=n.bottom-n.top;if(!Number.isFinite(s)||!Number.isFinite(b))throw new Error("Invalid rectangle");var E=l.x/s,T=l.y/b,M=Math.min(E,T);o.x=-(n.left+s/2)*M+l.x/2,o.y=-(n.top+b/2)*M+l.y/2,o.scale=M}function Z(n,a){if(r.getScreenCTM){var l=r.getScreenCTM(),s=l.a,b=l.d,E=l.e,T=l.f;u.x=n*s-E,u.y=a*b-T}else u.x=n,u.y=a;return u}function er(){var n,a,l=0,s=0,b=Pe();if(b)l=b.left,s=b.top,n=b.right-b.left,a=b.bottom-b.top;else{var E=i.getBoundingClientRect();n=E.width,a=E.height}var T=r.getBBox();if(!(T.width===0||T.height===0)){var M=a/T.height,j=n/T.width,R=Math.min(j,M);o.x=-(T.left+T.width/2)*R+n/2+l,o.y=-(T.top+T.height/2)*R+a/2+s,o.scale=R}}function tr(){return o}function rr(){return y}function nr(n){y=n}function or(){return d}function ir(n){d=n}function ar(){return S}function lr(n){S=Pt(n)}function sr(){return F}function cr(n){if(!Number.isFinite(n))throw new Error("Zoom speed should be a number");F=n}function ur(){return{x:o.x,y:o.y}}function we(n,a){o.x=n,o.y=a,Se(),W("pan"),Ce()}function De(n,a){we(o.x+n,o.y+a)}function Se(){var n=Pe();if(n){var a=!1,l=fr(),s=n.left-l.right;return s>0&&(o.x+=s,a=!0),s=n.right-l.left,s<0&&(o.x+=s,a=!0),s=n.top-l.bottom,s>0&&(o.y+=s,a=!0),s=n.bottom-l.top,s<0&&(o.y+=s,a=!0),a}}function Pe(){if(f){if(typeof f=="boolean"){var n=i.getBoundingClientRect(),a=n.width,l=n.height;return{left:a*x,top:l*x,right:a*(1-x),bottom:l*(1-x)}}return f}}function fr(){var n=r.getBBox(),a=dr(n.left,n.top);return{left:a.x,top:a.y,right:n.width*o.scale+a.x,bottom:n.height*o.scale+a.y}}function dr(n,a){return{x:n*o.scale+o.x,y:a*o.scale+o.y}}function Ce(){c=!0,A=window.requestAnimationFrame(gr)}function Ie(n,a,l){if(Oe(n)||Oe(a)||Oe(l))throw new Error("zoom requires valid numbers");var s=o.scale*l;if(s<y){if(o.scale===y)return;l=y/o.scale}if(s>d){if(o.scale===d)return;l=d/o.scale}var b=Z(n,a);if(o.x=b.x-l*(b.x-o.x),o.y=b.y-l*(b.y-o.y),f&&x===1&&y===1)o.scale*=l,Se();else{var E=Se();E||(o.scale*=l)}W("zoom"),Ce()}function oe(n,a,l){var s=l/o.scale;Ie(n,a,s)}function mr(n){var a=n.ownerSVGElement;if(!a)throw new Error("ui element is required to be within the scene");var l=n.getBoundingClientRect(),s=l.left+l.width/2,b=l.top+l.height/2,E=a.getBoundingClientRect(),T=E.width/2-s,M=E.height/2-b;H(T,M,!0)}function pr(n,a){H(n-o.x,a-o.y,!0)}function H(n,a,l){if(!l)return De(n,a);be&&be.cancel();var s={x:0,y:0},b={x:n,y:a},E=0,T=0;be=Me(s,b,{step:function(M){De(M.x-E,M.y-T),E=M.x,T=M.y}})}function vr(n,a){se(),we(n,a)}function hr(){_e()}function Ne(){i.addEventListener("mousedown",Ye,{passive:!1}),i.addEventListener("dblclick",je,{passive:!1}),i.addEventListener("touchstart",Ze,{passive:!1}),i.addEventListener("keydown",qe,{passive:!1}),Bt.addWheelListener(i,Qe,{passive:!1}),Ce()}function _e(){Bt.removeWheelListener(i,Qe),i.removeEventListener("mousedown",Ye),i.removeEventListener("keydown",qe),i.removeEventListener("dblclick",je),i.removeEventListener("touchstart",Ze),A&&(window.cancelAnimationFrame(A),A=0),I.cancel(),Ke(),Je(),P.release(),Ee()}function gr(){c&&yr()}function yr(){c=!1,r.applyTransform(o),W("transform"),A=0}function qe(n){var a=0,l=0,s=0;if(n.keyCode===38?l=1:n.keyCode===40?l=-1:n.keyCode===37?a=1:n.keyCode===39?a=-1:n.keyCode===189||n.keyCode===109?s=1:(n.keyCode===187||n.keyCode===107)&&(s=-1),!v(n,a,l,s)){if(a||l){n.preventDefault(),n.stopPropagation();var b=i.getBoundingClientRect(),E=Math.min(b.width,b.height),T=.05,M=E*T*a,j=E*T*l;H(M,j)}if(s){var R=Xe(s*100),E=S?J():xr();le(E.x,E.y,R)}}}function xr(){var n=i.getBoundingClientRect();return{x:n.width/2,y:n.height/2}}function Ze(n){if(br(n),K(),n.touches.length===1)return Sr(n,n.touches[0]);n.touches.length===2&&(xe=We(n.touches[0],n.touches[1]),te=!0,Re())}function br(n){e.onTouch&&!e.onTouch(n)||(n.stopPropagation(),n.preventDefault())}function wr(n){K(),!(e.onDoubleClick&&!e.onDoubleClick(n))&&(n.preventDefault(),n.stopPropagation())}function Sr(n){g=new Date;var a=n.touches[0],l=_(a);V=l;var s=Z(l.x,l.y);z=s.x,D=s.y,ge=z,ye=D,I.cancel(),Re()}function Re(){k||(k=!0,document.addEventListener("touchmove",Ge),document.addEventListener("touchend",ie),document.addEventListener("touchcancel",ie))}function Ge(n){if(n.touches.length===1){n.stopPropagation();var a=n.touches[0],l=_(a),s=Z(l.x,l.y),b=s.x-z,E=s.y-D;b!==0&&E!==0&&et(),z=s.x,D=s.y,H(b,E)}else if(n.touches.length===2){te=!0;var T=n.touches[0],M=n.touches[1],j=We(T,M),R=1+(j/xe-1)*p,rt=_(T),nt=_(M);if(z=(rt.x+nt.x)/2,D=(rt.y+nt.y)/2,S){var l=J();z=l.x,D=l.y}le(z,D,R),xe=j,n.stopPropagation(),n.preventDefault()}}function K(){w&&(clearTimeout(w),w=0)}function He(n){if(e.onClick){K();var a=z-ge,l=D-ye,s=Math.sqrt(a*a+l*l);s>5||(w=setTimeout(function(){w=0,e.onClick(n)},kt))}}function ie(n){if(K(),n.touches.length>0){var a=_(n.touches[0]),l=Z(a.x,a.y);z=l.x,D=l.y}else{var s=new Date;if(s-m<kt)if(S){var a=J();ae(a.x,a.y,h)}else ae(V.x,V.y,h);else s-g<Dt&&He(n);m=s,Ee(),Je()}}function We(n,a){var l=n.clientX-a.clientX,s=n.clientY-a.clientY;return Math.sqrt(l*l+s*s)}function je(n){wr(n);var a=_(n);S&&(a=J()),ae(a.x,a.y,h)}function Ye(n){if(K(),!B(n)){if(C=n,O=new Date,k)return n.stopPropagation(),!1;var a=n.button===1&&window.event!==null||n.button===0;if(a){I.cancel();var l=_(n),s=Z(l.x,l.y);return ge=z=s.x,ye=D=s.y,document.addEventListener("mousemove",$e),document.addEventListener("mouseup",Ue),P.capture(n.target||n.srcElement),!1}}}function $e(n){if(!k){et();var a=_(n),l=Z(a.x,a.y),s=l.x-z,b=l.y-D;z=l.x,D=l.y,H(s,b)}}function Ue(){var n=new Date;n-O<Dt&&He(C),P.release(),Ee(),Ke()}function Ke(){document.removeEventListener("mousemove",$e),document.removeEventListener("mouseup",Ue),q=!1}function Je(){document.removeEventListener("touchmove",Ge),document.removeEventListener("touchend",ie),document.removeEventListener("touchcancel",ie),q=!1,te=!1,k=!1}function Qe(n){if(!L(n)){I.cancel();var a=n.deltaY;n.deltaMode>0&&(a*=100);var l=Xe(a);if(l!==1){var s=S?J():_(n);le(s.x,s.y,l),n.preventDefault()}}}function _(n){var a,l,s=i.getBoundingClientRect();return a=n.clientX-s.left,l=n.clientY-s.top,{x:a,y:l}}function ae(n,a,l){var s=o.scale,b={scale:s},E={scale:l*s};I.cancel(),se(),U=Me(b,E,{step:function(T){oe(n,a,T.scale)},done:tt})}function Cr(n,a,l){var s=o.scale,b={scale:s},E={scale:l};I.cancel(),se(),U=Me(b,E,{step:function(T){oe(n,a,T.scale)},done:tt})}function J(){var n=i.getBoundingClientRect();return{x:n.width*S.x,y:n.height*S.y}}function le(n,a,l){return I.cancel(),se(),Ie(n,a,l)}function se(){U&&(U.cancel(),U=null)}function Xe(n){var a=Math.sign(n),l=Math.min(.25,Math.abs(F*n/128));return 1-a*l}function et(){q||(W("panstart"),q=!0,I.start())}function Ee(){q&&(te||I.stop(),W("panend"))}function tt(){W("zoomend")}function W(n){ne.fire(n,ne)}}function Pt(t){if(t){if(typeof t=="object")return(!Y(t.x)||!Y(t.y))&&It(t),t;It()}}function It(t){throw console.error(t),new Error(["Cannot parse transform origin.","Some good examples:",' "center center" can be achieved with {x: 0.5, y: 0.5}',' "top center" can be achieved with {x: 0.5, y: 0}',' "bottom right" can be achieved with {x: 1, y: 1}'].join(`
|
|
3
|
+
`))}function $(){}function cn(t){var e=typeof t;if(!(e==="undefined"||e==="boolean")){var r=Y(t.left)&&Y(t.top)&&Y(t.bottom)&&Y(t.right);if(!r)throw new Error("Bounds object is not valid. It can be: undefined, boolean (true|false) or an object {left, top, right, bottom}")}}function Y(t){return Number.isFinite(t)}function Oe(t){return Number.isNaN?Number.isNaN(t):t!==t}function un(){return{start:$,stop:$,cancel:$}}function fn(){if(typeof document>"u")return;var t=document.getElementsByTagName("script");if(!t)return;for(var e,r=0;r<t.length;++r){var i=t[r];if(i.src&&i.src.match(/\bpanzoom(\.min)?\.js/)){e=i;break}}if(!e)return;var u=e.getAttribute("query");if(!u)return;var c=e.getAttribute("name")||"pz",o=Date.now();v();function v(){var d=document.querySelector(u);if(!d){var y=Date.now(),x=y-o;if(x<2e3){setTimeout(v,100);return}console.error("Cannot find the panzoom element",c);return}var h=p(e);console.log(h),window[c]=_t(d,h)}function p(d){for(var y=d.attributes,x={},h=0;h<y.length;++h){var L=y[h],B=f(L);B&&(x[B.name]=B.value)}return x}function f(d){if(d.name){var y=d.name[0]==="p"&&d.name[1]==="z"&&d.name[2]==="-";if(y){var x=d.name.substr(3),h=JSON.parse(d.value);return{name:x,value:h}}}}}fn()});var Yt={};ot(Yt,{getViz:()=>pn,renderDotToSvg:()=>mn});async function mn(t){throw new Error("libpetri viewer: static bundle has no DOT renderer. Either ship the pre-rendered SVG in the host (mount(null, container, opts)) or load the full viewer.iife.js bundle.")}function pn(){throw new Error("libpetri viewer: static bundle has no Viz instance.")}var $t=Or(()=>{});var Be={};ot(Be,{DEFAULT_PANZOOM_OPTS:()=>me,VIEWER_CSS_VARIABLES:()=>ze,colorForPrefix:()=>he,discoverClusters:()=>ve,mount:()=>Ut});var Rt=Br(Zt(),1),me={smoothScroll:!1,zoomDoubleClickSpeed:1,maxZoom:1e3,minZoom:.02};function Gt(t,e,r){if(r)try{r.dispose()}catch{}let i={...me,...e};return(0,Rt.default)(t,i)}function ve(t){let e=new Map,r=t.querySelectorAll("g.cluster");if(!r.length)return e;let i=Array.from(t.querySelectorAll("g.node"));for(let u of Array.from(r)){let c=G(u,"title");if(!c)continue;let o=c.textContent??"";if(!o.startsWith("cluster_"))continue;let v=o.slice(8);if(e.has(v))continue;let p=0;for(let f of i){if(f.getAttribute("data-instance"))continue;let d=G(f,"title");if(!d)continue;let y=(d.textContent??"").trim();pe(y,v)&&(f.setAttribute("data-instance",v),p++)}e.set(v,{prefix:v,group:u,nodeCount:p,color:he(v)})}return e}function pe(t,e){return t===e||t.startsWith(`${e}/`)||t.startsWith(`p_${e}_`)||t.startsWith(`t_${e}_`)||t.startsWith(`j_${e}_`)}function he(t){let e=2166136261;for(let i=0;i<t.length;i++)e^=t.charCodeAt(i),e=e*16777619>>>0;let r=Math.floor((e/4294967296*360+e%360*.61803)%360);return r<0&&(r+=360),`hsl(${r}, 60%, 45%)`}function Ht(t){for(let e of t.values()){let r=G(e.group,"polygon")??G(e.group,"path");r&&(r.setAttribute("stroke",e.color),r.setAttribute("stroke-width","2.2"))}}function ee(t,e){let r=t.group,i=r.classList.contains("cluster-collapsed"),u=r.ownerSVGElement;if(u)if(e&&!i){r.classList.add("cluster-collapsed");let c=[];if(u.querySelectorAll(`g.node[data-instance="${dn(t.prefix)}"]`).forEach(o=>{o.classList.add("petri-collapsed-inside"),c.push(o)}),u.querySelectorAll("g.edge").forEach(o=>{let v=G(o,"title");if(!v)return;let p=(v.textContent??"").trim(),f=p.indexOf("->");if(f<0)return;let d=p.slice(0,f),y=p.slice(f+2);pe(d,t.prefix)&&pe(y,t.prefix)&&(o.classList.add("petri-collapsed-inside"),c.push(o))}),r._petriHiddenSiblings=c,!r._petriCollapsedBadge){let o=G(r,"text");if(o){let v=document.createElementNS("http://www.w3.org/2000/svg","text");v.setAttribute("x",o.getAttribute("x")??"0");let p=parseFloat(o.getAttribute("y")??"0");v.setAttribute("y",String(p+16)),v.setAttribute("text-anchor",o.getAttribute("text-anchor")??"middle"),v.setAttribute("font-size","11"),v.setAttribute("fill","#9ca3af"),v.setAttribute("class","cluster-collapsed-badge"),v.textContent=`(${t.nodeCount} internal node${t.nodeCount===1?"":"s"})`,r.appendChild(v),r._petriCollapsedBadge=v}}}else!e&&i&&(r.classList.remove("cluster-collapsed"),r._petriHiddenSiblings&&(r._petriHiddenSiblings.forEach(c=>c.classList.remove("petri-collapsed-inside")),r._petriHiddenSiblings=void 0),r._petriCollapsedBadge&&r._petriCollapsedBadge.parentNode===r&&(r.removeChild(r._petriCollapsedBadge),r._petriCollapsedBadge=null))}function Wt(t,e){if(e==null){t.removeAttribute("data-active-filter"),t.classList.remove("has-active-filter"),t.querySelectorAll("g.node, g.edge").forEach(r=>r.classList.remove("petri-dimmed"));return}t.setAttribute("data-active-filter",e),t.classList.add("has-active-filter"),t.querySelectorAll("g.node").forEach(r=>{let i=r.getAttribute("data-instance");!!i&&(i===e||i.indexOf(e+"_")===0||i.indexOf(e+"/")===0)?r.classList.remove("petri-dimmed"):r.classList.add("petri-dimmed")}),t.querySelectorAll("g.edge").forEach(r=>{let i=G(r,"title");if(!i)return;let u=i.textContent??"",c=u.indexOf("->");if(c<0){r.classList.remove("petri-dimmed");return}let o=u.slice(0,c),v=u.slice(c+2),p=f=>f.indexOf(e+"/")>=0||f.indexOf(e+"_")>=0||f===e;p(o)||p(v)?r.classList.remove("petri-dimmed"):r.classList.add("petri-dimmed")})}function jt(t,e,r,i){if(r.size===0||!t)return!1;for(let u of r)if(e.has(u)&&pe(i,u))return!0;return!1}function dn(t){return typeof CSS<"u"&&typeof CSS.escape=="function"?CSS.escape(t):t.replace(/[^a-zA-Z0-9_-]/g,e=>`\\${e}`)}function G(t,e){let r=e.toLowerCase();for(let i of Array.from(t.children))if(i.tagName.toLowerCase()===r)return i;return null}var ze=["--lpv-bg","--lpv-header-bg","--lpv-border","--lpv-text","--lpv-muted","--lpv-cluster-bg","--lpv-cluster-bg-collapsed","--lpv-cluster-stroke-width","--lpv-cluster-stroke-dash","--lpv-cluster-fill-tint","--lpv-dim-opacity","--lpv-active-filter-outline","--lpv-legend-bg","--lpv-chip-bg","--lpv-chip-active-bg"];async function Ut(t,e,r={}){let i=r.previousHandle??null,u=i?new Set(i.collapsedPrefixes):new Set,c=i?.activeFilter??null;i&&i.dispose();let o,v=e.querySelector(":scope > svg");if(t==null){if(!(v instanceof SVGSVGElement))throw new Error("mount: no DOT source and no pre-rendered SVG");o=v}else{let{renderDotToSvg:m}=await Promise.resolve().then(()=>($t(),Yt));o=await m(t),e.innerHTML="",e.appendChild(o)}e.classList.add("libpetri-viewer");let p=Gt(o,r.panzoom),f=ve(o);Ht(f);let d=new Set,y=null,x=!1,h=null;function L(){if(!r.chrome||h&&h.parentNode===e)return;h=document.createElement("div"),h.className="libpetri-viewer-chrome",h.style.position="absolute",h.style.inset="0",h.style.pointerEvents="none";let m=document.createElement("div");m.className="diagram-legend",m.style.pointerEvents="auto",m.innerHTML='<div class="legend-title">Clusters</div>',h.appendChild(m);let g=document.createElement("div");g.className="diagram-filter-strip",g.style.pointerEvents="auto",g.innerHTML='<span class="filter-strip-label">Show only:</span>',h.appendChild(g);let w=document.createElement("div");w.className="diagram-controls",w.style.pointerEvents="auto";let C=document.createElement("button");C.type="button",C.className="diagram-btn btn-reset",C.title="Reset view",C.textContent="Reset",C.addEventListener("click",()=>A.fit());let O=document.createElement("button");O.type="button",O.className="diagram-btn btn-fullscreen",O.title="Toggle fullscreen",O.textContent="Fullscreen",O.addEventListener("click",()=>B(e,O)),w.appendChild(C),w.appendChild(O),h.appendChild(w),getComputedStyle(e).position==="static"&&(e.style.position="relative"),e.appendChild(h),F(m),S(g)}function B(m,g){let w=m.classList.toggle("diagram-fullscreen");g.textContent=w?"Exit fullscreen":"Fullscreen",requestAnimationFrame(()=>A.fit())}function F(m){m.innerHTML='<div class="legend-title">Clusters</div>';for(let g of f.values()){let w=document.createElement("div");w.className="legend-row",w.innerHTML='<span class="legend-ribbon" style="background:'+g.color+'"></span><span class="legend-label"></span><span class="legend-count"></span>',w.querySelector(".legend-label").textContent=g.prefix,w.querySelector(".legend-count").textContent=String(g.nodeCount),w.addEventListener("click",()=>{d.has(g.prefix)?A.expand(g.prefix):A.collapse(g.prefix)}),m.appendChild(w)}}function S(m){m.innerHTML='<span class="filter-strip-label">Show only:</span>';let g=document.createElement("button");g.type="button",g.className="filter-chip filter-chip-all filter-chip-active",g.textContent="all",g.addEventListener("click",()=>{A.filter(null)}),m.appendChild(g);for(let w of f.values()){let C=document.createElement("button");C.type="button",C.className="filter-chip",C.style.borderColor=w.color,C.dataset.prefix=w.prefix,C.innerHTML='<span class="chip-dot" style="background:'+w.color+'"></span>',C.append(document.createTextNode(w.prefix)),C.addEventListener("click",()=>{y===w.prefix?A.filter(null):A.filter(w.prefix)}),m.appendChild(C)}}function P(){if(!h)return;let m=h.querySelector(".diagram-filter-strip");m&&(m.querySelectorAll(".filter-chip").forEach(g=>{g.classList.remove("filter-chip-active")}),y===null?m.querySelector(".filter-chip-all")?.classList.add("filter-chip-active"):m.querySelector(`.filter-chip[data-prefix="${y}"]`)?.classList.add("filter-chip-active"))}let A={svg:o,panzoom:p,clusters:f,get collapsedPrefixes(){return d},get activeFilter(){return y},collapse(m){if(x)return;let g=f.get(m);g&&(d.has(m)||(ee(g,!0),d.add(m),r.onClusterCollapse?.(m,!0)))},expand(m){if(x)return;let g=f.get(m);g&&d.has(m)&&(ee(g,!1),d.delete(m),r.onClusterCollapse?.(m,!1))},collapseAll(){if(!x)for(let m of f.values())d.has(m.prefix)||(ee(m,!0),d.add(m.prefix),r.onClusterCollapse?.(m.prefix,!0))},expandAll(){if(!x)for(let m of Array.from(d)){let g=f.get(m);g&&(ee(g,!1),d.delete(m),r.onClusterCollapse?.(m,!1))}},filter(m){x||(y=m,Wt(o,m),r.onClusterFilter?.(m),P())},resetZoom(){if(!x)try{p.moveTo(0,0),p.zoomAbs(0,0,1)}catch{}},fit(){if(!x)try{p.zoomAbs(0,0,1),p.moveTo(0,0)}catch{}},isInsideCollapsedCluster(m){return jt(o,f,d,m)},dispose(){if(!x){x=!0;try{p.dispose()}catch{}h&&h.parentNode===e&&e.removeChild(h),h=null}}};for(let m of u)A.collapse(m);return c!==null&&A.filter(c),L(),requestAnimationFrame(()=>A.fit()),A}globalThis.LibpetriViewer=Be;})();
|
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
/* ============================================================
|
|
2
|
+
* Canonical libpetri Petri-net diagram viewer stylesheet.
|
|
3
|
+
*
|
|
4
|
+
* Source of truth: typescript/src/viewer/resources/viewer.css.
|
|
5
|
+
* This file is the union of the old petrinet-diagrams.css (Javadoc taglet)
|
|
6
|
+
* and the cluster-overlay rules from debug-ui/src/styles.css. It is
|
|
7
|
+
* shipped as `petrinet-diagrams.css` to all consumers via the build
|
|
8
|
+
* script (scripts/build-viewer.sh). Edit HERE, not in the consumer dirs.
|
|
9
|
+
*
|
|
10
|
+
* All visual constants surface as CSS custom properties (`--lpv-*`) so
|
|
11
|
+
* individual consumers can theme without forking. Defaults match the
|
|
12
|
+
* doclet palette + the user's "clusters must be visually obvious"
|
|
13
|
+
* feedback (deeper tint, thicker stroke, more prominent dash).
|
|
14
|
+
* ============================================================ */
|
|
15
|
+
|
|
16
|
+
:root,
|
|
17
|
+
.petrinet-diagram,
|
|
18
|
+
.libpetri-viewer {
|
|
19
|
+
--lpv-bg: #fafafa;
|
|
20
|
+
--lpv-header-bg: #f0f0f0;
|
|
21
|
+
--lpv-border: #ddd;
|
|
22
|
+
--lpv-text: #111827;
|
|
23
|
+
--lpv-muted: #6b7280;
|
|
24
|
+
|
|
25
|
+
/* Cluster visuals — user feedback: clusters must be visually obvious.
|
|
26
|
+
Bumped the cluster background from a near-white tint to a saturated
|
|
27
|
+
indigo wash, thickened the stroke, and lengthened the dashes. */
|
|
28
|
+
--lpv-cluster-bg: #eef2ff;
|
|
29
|
+
--lpv-cluster-bg-collapsed: #cbd5e1;
|
|
30
|
+
--lpv-cluster-stroke-width: 2.2;
|
|
31
|
+
--lpv-cluster-stroke-dash: 6 3;
|
|
32
|
+
--lpv-cluster-fill-tint: rgba(99, 102, 241, 0.08);
|
|
33
|
+
|
|
34
|
+
--lpv-dim-opacity: 0.2;
|
|
35
|
+
--lpv-active-filter-outline: rgba(245, 158, 11, 0.4);
|
|
36
|
+
|
|
37
|
+
--lpv-legend-bg: rgba(255, 255, 255, 0.95);
|
|
38
|
+
--lpv-chip-bg: #ffffff;
|
|
39
|
+
--lpv-chip-active-bg: #111827;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* ============================================================
|
|
43
|
+
* Top-level diagram container
|
|
44
|
+
* Kept for backward compatibility with the existing Javadoc/Rustdoc
|
|
45
|
+
* taglet markup. Live consumers (debug-ui, dev-preview) don't use the
|
|
46
|
+
* outer .petrinet-diagram chrome; their styling lives in debug-ui CSS.
|
|
47
|
+
* ============================================================ */
|
|
48
|
+
.petrinet-diagram {
|
|
49
|
+
margin: 1.5em 0;
|
|
50
|
+
border: 1px solid var(--lpv-border);
|
|
51
|
+
border-radius: 8px;
|
|
52
|
+
background: var(--lpv-bg);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.petrinet-diagram h4 {
|
|
56
|
+
margin: 0;
|
|
57
|
+
padding: 12px 16px;
|
|
58
|
+
background: var(--lpv-header-bg);
|
|
59
|
+
border-bottom: 1px solid var(--lpv-border);
|
|
60
|
+
border-radius: 8px 8px 0 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.petrinet-diagram .diagram-container {
|
|
64
|
+
position: relative;
|
|
65
|
+
padding: 16px;
|
|
66
|
+
height: 70vh;
|
|
67
|
+
overflow: hidden;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.petrinet-diagram .diagram-viewport {
|
|
71
|
+
width: 100%;
|
|
72
|
+
height: 100%;
|
|
73
|
+
cursor: grab;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.petrinet-diagram .diagram-viewport:active {
|
|
77
|
+
cursor: grabbing;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.petrinet-diagram .diagram-container svg {
|
|
81
|
+
display: block;
|
|
82
|
+
width: 100%;
|
|
83
|
+
height: 100%;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* 2.0 mount target — the host div emitted by the Java/Rust/TS doclet
|
|
87
|
+
taglets. Needs the same sizing constraints as the legacy
|
|
88
|
+
.diagram-container, otherwise the host grows to the Graphviz-emitted
|
|
89
|
+
SVG intrinsic size and produces a 9000+ px tall column. With this
|
|
90
|
+
rule the SVG fills the constrained host and viewBox auto-fits. */
|
|
91
|
+
.petrinet-diagram-viewer {
|
|
92
|
+
display: block;
|
|
93
|
+
position: relative;
|
|
94
|
+
width: 100%;
|
|
95
|
+
height: 70vh;
|
|
96
|
+
min-height: 400px;
|
|
97
|
+
max-height: 700px;
|
|
98
|
+
overflow: hidden;
|
|
99
|
+
background: var(--lpv-bg);
|
|
100
|
+
border: 1px solid var(--lpv-border);
|
|
101
|
+
border-radius: 4px;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* Override Graphviz's explicit pt width/height on the rendered SVG.
|
|
105
|
+
Without this the SVG retains its intrinsic 5021pt × 7203pt size,
|
|
106
|
+
defeating the host height constraint. */
|
|
107
|
+
.petrinet-diagram-viewer > svg {
|
|
108
|
+
display: block;
|
|
109
|
+
width: 100% !important;
|
|
110
|
+
height: 100% !important;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.petrinet-diagram .diagram-controls,
|
|
114
|
+
.libpetri-viewer .diagram-controls {
|
|
115
|
+
position: absolute;
|
|
116
|
+
top: 8px;
|
|
117
|
+
right: 8px;
|
|
118
|
+
display: flex;
|
|
119
|
+
gap: 4px;
|
|
120
|
+
z-index: 10;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.petrinet-diagram .diagram-btn,
|
|
124
|
+
.libpetri-viewer .diagram-btn {
|
|
125
|
+
padding: 6px 10px;
|
|
126
|
+
border: 1px solid #ccc;
|
|
127
|
+
border-radius: 4px;
|
|
128
|
+
background: white;
|
|
129
|
+
cursor: pointer;
|
|
130
|
+
font-size: 14px;
|
|
131
|
+
min-width: 32px;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.petrinet-diagram .diagram-btn:hover,
|
|
135
|
+
.libpetri-viewer .diagram-btn:hover {
|
|
136
|
+
background: #e8e8e8;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.petrinet-diagram details {
|
|
140
|
+
padding: 0 16px 16px;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.petrinet-diagram summary {
|
|
144
|
+
cursor: pointer;
|
|
145
|
+
font-weight: 500;
|
|
146
|
+
padding: 8px 0;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/* Fullscreen mode */
|
|
150
|
+
.diagram-fullscreen {
|
|
151
|
+
position: fixed !important;
|
|
152
|
+
top: 0 !important;
|
|
153
|
+
left: 0 !important;
|
|
154
|
+
width: 100vw !important;
|
|
155
|
+
height: 100vh !important;
|
|
156
|
+
max-height: 100vh !important;
|
|
157
|
+
background: white !important;
|
|
158
|
+
z-index: 9999 !important;
|
|
159
|
+
margin: 0 !important;
|
|
160
|
+
border-radius: 0 !important;
|
|
161
|
+
overflow: hidden !important;
|
|
162
|
+
display: flex !important;
|
|
163
|
+
flex-direction: column !important;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.diagram-fullscreen .diagram-container {
|
|
167
|
+
flex: 1 !important;
|
|
168
|
+
min-height: 0 !important;
|
|
169
|
+
height: auto !important;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.diagram-fullscreen .diagram-controls {
|
|
173
|
+
position: fixed;
|
|
174
|
+
top: 16px;
|
|
175
|
+
right: 16px;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.diagram-fullscreen details {
|
|
179
|
+
display: none;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.diagram-fullscreen svg {
|
|
183
|
+
max-width: none;
|
|
184
|
+
max-height: none;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/* 2.0 host is the mount target itself (no .diagram-container wrapper),
|
|
188
|
+
so size the direct child SVG to fill the viewport in fullscreen. */
|
|
189
|
+
.libpetri-viewer.diagram-fullscreen > svg {
|
|
190
|
+
flex: 1;
|
|
191
|
+
width: 100%;
|
|
192
|
+
height: 100%;
|
|
193
|
+
min-height: 0;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/* Zoom indicator */
|
|
197
|
+
.zoom-indicator {
|
|
198
|
+
position: absolute;
|
|
199
|
+
bottom: 8px;
|
|
200
|
+
left: 8px;
|
|
201
|
+
background: rgba(0,0,0,0.6);
|
|
202
|
+
color: white;
|
|
203
|
+
padding: 4px 8px;
|
|
204
|
+
border-radius: 4px;
|
|
205
|
+
font-size: 12px;
|
|
206
|
+
opacity: 0;
|
|
207
|
+
transition: opacity 0.3s;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.zoom-indicator.visible {
|
|
211
|
+
opacity: 1;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/* ============================================================
|
|
215
|
+
* Subnet header (badges row above the diagram)
|
|
216
|
+
* ============================================================ */
|
|
217
|
+
.petrinet-diagram .subnet-header {
|
|
218
|
+
padding: 12px 16px;
|
|
219
|
+
border-bottom: 1px solid #e5e7eb;
|
|
220
|
+
background: #f8fafc;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.subnet-header-row {
|
|
224
|
+
display: flex;
|
|
225
|
+
align-items: center;
|
|
226
|
+
gap: 8px;
|
|
227
|
+
font-size: 14px;
|
|
228
|
+
flex-wrap: wrap;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.subnet-header-label {
|
|
232
|
+
font-weight: 600;
|
|
233
|
+
color: var(--lpv-muted);
|
|
234
|
+
text-transform: uppercase;
|
|
235
|
+
font-size: 11px;
|
|
236
|
+
letter-spacing: 0.05em;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.subnet-header-name {
|
|
240
|
+
font-weight: 600;
|
|
241
|
+
color: var(--lpv-text);
|
|
242
|
+
font-family: ui-monospace, "SF Mono", Menlo, monospace;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.subnet-header-of {
|
|
246
|
+
color: var(--lpv-muted);
|
|
247
|
+
font-size: 12px;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.subnet-header-param,
|
|
251
|
+
.subnet-header-params {
|
|
252
|
+
color: #6d28d9;
|
|
253
|
+
font-family: ui-monospace, "SF Mono", Menlo, monospace;
|
|
254
|
+
font-size: 12px;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.subnet-header-params-null {
|
|
258
|
+
color: #9ca3af;
|
|
259
|
+
font-style: italic;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.subnet-header-badge {
|
|
263
|
+
padding: 2px 8px;
|
|
264
|
+
border-radius: 4px;
|
|
265
|
+
background: #fef3c7;
|
|
266
|
+
color: #92400e;
|
|
267
|
+
font-size: 11px;
|
|
268
|
+
font-weight: 500;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.subnet-header-badge-interface {
|
|
272
|
+
background: #e0e7ff;
|
|
273
|
+
color: #3730a3;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.subnet-header-badges {
|
|
277
|
+
display: flex;
|
|
278
|
+
gap: 6px;
|
|
279
|
+
margin-top: 8px;
|
|
280
|
+
flex-wrap: wrap;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.interface-port-badge,
|
|
284
|
+
.interface-channel-badge {
|
|
285
|
+
display: inline-flex;
|
|
286
|
+
align-items: center;
|
|
287
|
+
gap: 4px;
|
|
288
|
+
padding: 3px 8px;
|
|
289
|
+
border-radius: 12px;
|
|
290
|
+
font-size: 11px;
|
|
291
|
+
border: 1px solid;
|
|
292
|
+
background: white;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.interface-port-badge .badge-direction,
|
|
296
|
+
.interface-channel-badge .badge-direction {
|
|
297
|
+
font-weight: 600;
|
|
298
|
+
text-transform: uppercase;
|
|
299
|
+
font-size: 10px;
|
|
300
|
+
letter-spacing: 0.04em;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.interface-port-badge .badge-name,
|
|
304
|
+
.interface-channel-badge .badge-name {
|
|
305
|
+
font-family: ui-monospace, "SF Mono", Menlo, monospace;
|
|
306
|
+
font-weight: 500;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.interface-port-badge .badge-type {
|
|
310
|
+
color: var(--lpv-muted);
|
|
311
|
+
font-family: ui-monospace, "SF Mono", Menlo, monospace;
|
|
312
|
+
font-size: 10px;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.interface-port-badge-input { border-color: #1d4ed8; color: #1d4ed8; background: #e7f1ff; }
|
|
316
|
+
.interface-port-badge-output { border-color: #047857; color: #047857; background: #d1fae5; }
|
|
317
|
+
.interface-port-badge-inout { border-color: #b45309; color: #b45309; background: #fef3c7; }
|
|
318
|
+
.interface-channel-badge { border-color: #6d28d9; color: #6d28d9; background: #f3e8ff; }
|
|
319
|
+
|
|
320
|
+
/* ============================================================
|
|
321
|
+
* Cluster legend sidebar
|
|
322
|
+
* ============================================================ */
|
|
323
|
+
.petrinet-diagram .diagram-legend,
|
|
324
|
+
.libpetri-viewer .diagram-legend {
|
|
325
|
+
position: absolute;
|
|
326
|
+
top: 8px;
|
|
327
|
+
left: 8px;
|
|
328
|
+
background: var(--lpv-legend-bg);
|
|
329
|
+
border: 1px solid #e5e7eb;
|
|
330
|
+
border-radius: 6px;
|
|
331
|
+
padding: 8px;
|
|
332
|
+
font-size: 12px;
|
|
333
|
+
z-index: 9;
|
|
334
|
+
max-height: calc(100% - 16px);
|
|
335
|
+
overflow-y: auto;
|
|
336
|
+
min-width: 160px;
|
|
337
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.diagram-legend .legend-title {
|
|
341
|
+
font-weight: 600;
|
|
342
|
+
color: var(--lpv-muted);
|
|
343
|
+
text-transform: uppercase;
|
|
344
|
+
font-size: 10px;
|
|
345
|
+
letter-spacing: 0.05em;
|
|
346
|
+
margin-bottom: 6px;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.diagram-legend .legend-row {
|
|
350
|
+
display: flex;
|
|
351
|
+
align-items: center;
|
|
352
|
+
gap: 6px;
|
|
353
|
+
padding: 4px 6px;
|
|
354
|
+
border-radius: 4px;
|
|
355
|
+
cursor: pointer;
|
|
356
|
+
transition: background 0.15s;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.diagram-legend .legend-row:hover {
|
|
360
|
+
background: #f3f4f6;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
.diagram-legend .legend-ribbon {
|
|
364
|
+
width: 4px;
|
|
365
|
+
height: 16px;
|
|
366
|
+
border-radius: 2px;
|
|
367
|
+
flex: 0 0 auto;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.diagram-legend .legend-label {
|
|
371
|
+
font-family: ui-monospace, "SF Mono", Menlo, monospace;
|
|
372
|
+
flex: 1 1 auto;
|
|
373
|
+
color: var(--lpv-text);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
.diagram-legend .legend-count {
|
|
377
|
+
color: #9ca3af;
|
|
378
|
+
font-size: 10px;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/* ============================================================
|
|
382
|
+
* Filter chips
|
|
383
|
+
* ============================================================ */
|
|
384
|
+
.petrinet-diagram .diagram-filter-strip,
|
|
385
|
+
.libpetri-viewer .diagram-filter-strip {
|
|
386
|
+
position: absolute;
|
|
387
|
+
bottom: 8px;
|
|
388
|
+
left: 8px;
|
|
389
|
+
right: 8px;
|
|
390
|
+
display: flex;
|
|
391
|
+
flex-wrap: wrap;
|
|
392
|
+
align-items: center;
|
|
393
|
+
gap: 6px;
|
|
394
|
+
padding: 8px;
|
|
395
|
+
background: var(--lpv-legend-bg);
|
|
396
|
+
border: 1px solid #e5e7eb;
|
|
397
|
+
border-radius: 6px;
|
|
398
|
+
z-index: 8;
|
|
399
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
.filter-strip-label {
|
|
403
|
+
font-size: 11px;
|
|
404
|
+
font-weight: 600;
|
|
405
|
+
color: var(--lpv-muted);
|
|
406
|
+
text-transform: uppercase;
|
|
407
|
+
letter-spacing: 0.04em;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
.filter-chip {
|
|
411
|
+
display: inline-flex;
|
|
412
|
+
align-items: center;
|
|
413
|
+
gap: 4px;
|
|
414
|
+
padding: 3px 8px;
|
|
415
|
+
border-radius: 14px;
|
|
416
|
+
font-size: 11px;
|
|
417
|
+
font-family: ui-monospace, "SF Mono", Menlo, monospace;
|
|
418
|
+
background: var(--lpv-chip-bg);
|
|
419
|
+
border: 1px solid #d1d5db;
|
|
420
|
+
cursor: pointer;
|
|
421
|
+
transition: background 0.15s, transform 0.15s;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
.filter-chip:hover {
|
|
425
|
+
background: #f3f4f6;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
.filter-chip-active {
|
|
429
|
+
background: var(--lpv-chip-active-bg);
|
|
430
|
+
color: white;
|
|
431
|
+
border-color: var(--lpv-chip-active-bg);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
.filter-chip-active .chip-dot {
|
|
435
|
+
outline: 2px solid rgba(255, 255, 255, 0.4);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
.filter-chip .chip-dot {
|
|
439
|
+
width: 8px;
|
|
440
|
+
height: 8px;
|
|
441
|
+
border-radius: 50%;
|
|
442
|
+
display: inline-block;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/* ============================================================
|
|
446
|
+
* Cluster visual baseline — make clusters visually obvious
|
|
447
|
+
*
|
|
448
|
+
* These rules apply globally to any rendered SVG that lives inside
|
|
449
|
+
* a `.petrinet-diagram` OR a `.libpetri-viewer` host. Tinted fill,
|
|
450
|
+
* thicker stroke, more prominent dash. Stroke colour is injected
|
|
451
|
+
* inline by the cluster-overlay (deterministic HSL per prefix).
|
|
452
|
+
* ============================================================ */
|
|
453
|
+
g.cluster > polygon,
|
|
454
|
+
g.cluster > path {
|
|
455
|
+
fill: var(--lpv-cluster-bg);
|
|
456
|
+
stroke-width: var(--lpv-cluster-stroke-width);
|
|
457
|
+
stroke-dasharray: var(--lpv-cluster-stroke-dash);
|
|
458
|
+
transition: fill 0.2s ease, stroke 0.2s ease;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/* ============================================================
|
|
462
|
+
* Cluster collapse / expand state
|
|
463
|
+
* ============================================================ */
|
|
464
|
+
.cluster-collapsed > polygon,
|
|
465
|
+
.cluster-collapsed > path {
|
|
466
|
+
fill: var(--lpv-cluster-bg-collapsed) !important;
|
|
467
|
+
stroke-dasharray: 3 3;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
.cluster-collapsed-badge {
|
|
471
|
+
font-style: italic;
|
|
472
|
+
pointer-events: none;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/* Sibling nodes/edges hidden while their cluster is collapsed. The cluster
|
|
476
|
+
* <g> retains its layout box (border, label, badge); the interior elements
|
|
477
|
+
* disappear so the box reads as a single tile. */
|
|
478
|
+
g.node.petri-collapsed-inside,
|
|
479
|
+
g.edge.petri-collapsed-inside {
|
|
480
|
+
display: none;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/* ============================================================
|
|
484
|
+
* Filter dim state (CSS-driven, applied per node by JS)
|
|
485
|
+
* ============================================================ */
|
|
486
|
+
g.node.petri-dimmed,
|
|
487
|
+
g.edge.petri-dimmed {
|
|
488
|
+
opacity: var(--lpv-dim-opacity);
|
|
489
|
+
transition: opacity 0.2s ease;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
g.node:not(.petri-dimmed),
|
|
493
|
+
g.edge:not(.petri-dimmed) {
|
|
494
|
+
transition: opacity 0.2s ease;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/* When the filter is active, give the SVG a hairline ring so the user
|
|
498
|
+
has a global signal that they're seeing a filtered view, not a partial
|
|
499
|
+
net. Matches the debug-ui's previous treatment. */
|
|
500
|
+
svg.has-active-filter {
|
|
501
|
+
outline: 1px solid var(--lpv-active-filter-outline);
|
|
502
|
+
outline-offset: -1px;
|
|
503
|
+
}
|