ontologyviewer 0.1.2 → 0.2.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.
@@ -46094,6 +46094,72 @@ function isKnownVocabularyIri(iri) {
46094
46094
  return KNOWN_VOCABULARY_NAMESPACES.some((ns) => iri.startsWith(ns));
46095
46095
  }
46096
46096
 
46097
+ // src/rdf/compactTriples.ts
46098
+ function compactTriples(graph) {
46099
+ const hasOutgoing = /* @__PURE__ */ new Set();
46100
+ const hasIncoming = /* @__PURE__ */ new Set();
46101
+ for (const edge of graph.edges) {
46102
+ hasOutgoing.add(edge.source);
46103
+ hasIncoming.add(edge.target);
46104
+ }
46105
+ const byId = new Map(graph.nodes.map((node) => [node.id, node]));
46106
+ const foldedIds = /* @__PURE__ */ new Set();
46107
+ for (const node of graph.nodes) {
46108
+ if (hasOutgoing.has(node.id) || !hasIncoming.has(node.id)) {
46109
+ continue;
46110
+ }
46111
+ const isLeafAnnotation = node.kind === "literal" || !node.isBlankNode && isKnownVocabularyIri(node.id);
46112
+ if (isLeafAnnotation) {
46113
+ foldedIds.add(node.id);
46114
+ }
46115
+ }
46116
+ if (foldedIds.size === 0) {
46117
+ return {
46118
+ nodes: graph.nodes,
46119
+ edges: graph.edges,
46120
+ foldedFacts: /* @__PURE__ */ new Map(),
46121
+ removedNodeCount: 0,
46122
+ removedEdgeCount: 0
46123
+ };
46124
+ }
46125
+ const foldedFacts = /* @__PURE__ */ new Map();
46126
+ const edges3 = [];
46127
+ for (const edge of graph.edges) {
46128
+ if (!foldedIds.has(edge.target)) {
46129
+ edges3.push(edge);
46130
+ continue;
46131
+ }
46132
+ if (edge.predicate === RDFS_LABEL || edge.predicate === RDFS_COMMENT) {
46133
+ continue;
46134
+ }
46135
+ const fact = `${factPredicateLabel(edge.predicateLabel)}: ${factObjectLabel(byId.get(edge.target))}`;
46136
+ const existing = foldedFacts.get(edge.source);
46137
+ if (!existing) {
46138
+ foldedFacts.set(edge.source, [fact]);
46139
+ } else if (!existing.includes(fact)) {
46140
+ existing.push(fact);
46141
+ }
46142
+ }
46143
+ const nodes3 = graph.nodes.filter((node) => !foldedIds.has(node.id));
46144
+ return {
46145
+ nodes: nodes3,
46146
+ edges: edges3,
46147
+ foldedFacts,
46148
+ removedNodeCount: graph.nodes.length - nodes3.length,
46149
+ removedEdgeCount: graph.edges.length - edges3.length
46150
+ };
46151
+ }
46152
+ function factPredicateLabel(predicateLabel) {
46153
+ const colon = predicateLabel.indexOf(":");
46154
+ return colon >= 0 ? predicateLabel.slice(colon + 1) : predicateLabel;
46155
+ }
46156
+ function factObjectLabel(node) {
46157
+ if (!node) {
46158
+ return "";
46159
+ }
46160
+ return node.kind === "literal" ? node.literalValue ?? node.label : node.label;
46161
+ }
46162
+
46097
46163
  // src/rdf/graphModel.ts
46098
46164
  function buildGraphModel(quads) {
46099
46165
  const nodes3 = /* @__PURE__ */ new Map();
@@ -46669,6 +46735,9 @@ function relativeLuminance(color) {
46669
46735
  var EN = {
46670
46736
  schemaView: "Schema",
46671
46737
  triplesView: "Triples",
46738
+ compactTriples: "Compact",
46739
+ compactTriplesHint: "Fold labels, comments, and standard vocabulary terms into the nodes that reference them.",
46740
+ compactTriplesActiveHint: "Compact view is on. Click to show labels, comments, and vocabulary terms as separate nodes.",
46672
46741
  searchPlaceholder: "Search classes, relations, properties\u2026",
46673
46742
  noResults: "No results",
46674
46743
  properties: "properties",
@@ -46699,6 +46768,9 @@ var EN = {
46699
46768
  var JA = {
46700
46769
  schemaView: "\u30B9\u30AD\u30FC\u30DE",
46701
46770
  triplesView: "\u30C8\u30EA\u30D7\u30EB",
46771
+ compactTriples: "\u7C21\u7D04",
46772
+ compactTriplesHint: "\u30E9\u30D9\u30EB\u30FB\u30B3\u30E1\u30F3\u30C8\u30FB\u6A19\u6E96\u8A9E\u5F59\u306E\u7528\u8A9E\u3092\u3001\u305D\u308C\u3092\u53C2\u7167\u3057\u3066\u3044\u308B\u30CE\u30FC\u30C9\u306B\u7573\u307F\u8FBC\u307F\u307E\u3059\u3002",
46773
+ compactTriplesActiveHint: "\u7C21\u7D04\u8868\u793A\u304C\u6709\u52B9\u3067\u3059\u3002\u30AF\u30EA\u30C3\u30AF\u3059\u308B\u3068\u3001\u30E9\u30D9\u30EB\u30FB\u30B3\u30E1\u30F3\u30C8\u30FB\u8A9E\u5F59\u306E\u7528\u8A9E\u3092\u500B\u5225\u306E\u30CE\u30FC\u30C9\u3068\u3057\u3066\u8868\u793A\u3057\u307E\u3059\u3002",
46702
46774
  searchPlaceholder: "\u30AF\u30E9\u30B9\u30FB\u95A2\u4FC2\u30FB\u30D7\u30ED\u30D1\u30C6\u30A3\u3092\u691C\u7D22\u2026",
46703
46775
  noResults: "\u7D50\u679C\u306A\u3057",
46704
46776
  properties: "\u30D7\u30ED\u30D1\u30C6\u30A3",
@@ -46894,6 +46966,7 @@ function createViewer(sourceElement, initialTurtle, options2, onDestroy) {
46894
46966
  let graph = { nodes: [], edges: [] };
46895
46967
  let currentView = options2.defaultView ?? "schema";
46896
46968
  let currentLayout = options2.layout ?? "fcose";
46969
+ let compactTriplesEnabled = options2.compactTriples ?? true;
46897
46970
  let dark = isDarkTheme(options2.theme ?? "auto");
46898
46971
  let cy;
46899
46972
  let destroyed2 = false;
@@ -46999,7 +47072,7 @@ function createViewer(sourceElement, initialTurtle, options2, onDestroy) {
46999
47072
  destroyCy();
47000
47073
  focusedId = void 0;
47001
47074
  const view = effectiveView();
47002
- const elements = view === "schema" ? schemaElements(schema) : graphElements(graph);
47075
+ const elements = view === "schema" ? schemaElements(schema) : graphElements(graph, compactTriplesEnabled);
47003
47076
  cy = cytoscape2({
47004
47077
  container: dom.graph,
47005
47078
  elements,
@@ -47143,6 +47216,9 @@ function createViewer(sourceElement, initialTurtle, options2, onDestroy) {
47143
47216
  dom.triplesButton.classList.toggle("active", currentView === "triples");
47144
47217
  dom.schemaButton.setAttribute("aria-pressed", String(currentView === "schema"));
47145
47218
  dom.triplesButton.setAttribute("aria-pressed", String(currentView === "triples"));
47219
+ dom.compactToggle.hidden = effectiveView() !== "triples";
47220
+ dom.compactCheckbox.checked = compactTriplesEnabled;
47221
+ dom.compactToggle.title = compactTriplesEnabled ? messages.compactTriplesActiveHint : messages.compactTriplesHint;
47146
47222
  }
47147
47223
  function updateStats() {
47148
47224
  dom.stats.textContent = `${messages.entities}: ${schema.entities.length} \xB7 ${messages.relations}: ${schema.relations.length} \xB7 ${messages.triples}: ${graph.edges.length}`;
@@ -47261,6 +47337,12 @@ function createViewer(sourceElement, initialTurtle, options2, onDestroy) {
47261
47337
  clearInspector();
47262
47338
  renderGraph();
47263
47339
  }, { signal });
47340
+ dom.compactCheckbox.addEventListener("change", () => {
47341
+ compactTriplesEnabled = dom.compactCheckbox.checked;
47342
+ updateViewButtons();
47343
+ clearInspector();
47344
+ renderGraph();
47345
+ }, { signal });
47264
47346
  dom.search.addEventListener("input", performSearch, { signal });
47265
47347
  dom.search.addEventListener("keydown", (event3) => {
47266
47348
  if (event3.key !== "Escape") return;
@@ -47311,6 +47393,13 @@ function createDom(doc, root2, messages, options2) {
47311
47393
  const schemaButton = button(doc, messages.schemaView, "ov-btn-schema");
47312
47394
  const triplesButton = button(doc, messages.triplesView, "ov-btn-triples");
47313
47395
  viewToggle.append(schemaButton, triplesButton);
47396
+ const compactToggle = element3(doc, "label", "ov-compact-toggle");
47397
+ const compactCheckbox = doc.createElement("input");
47398
+ compactCheckbox.type = "checkbox";
47399
+ compactCheckbox.className = "ov-compact-checkbox";
47400
+ const compactText = element3(doc, "span", "ov-compact-text");
47401
+ compactText.textContent = messages.compactTriples;
47402
+ compactToggle.append(compactCheckbox, compactText);
47314
47403
  const search = doc.createElement("input");
47315
47404
  search.type = "search";
47316
47405
  search.className = "ov-search";
@@ -47336,7 +47425,7 @@ function createDom(doc, root2, messages, options2) {
47336
47425
  button(doc, messages.layout, "ov-btn-layout", "\u21BB"),
47337
47426
  button(doc, messages.exportPng, "ov-btn-export", "\u21E9")
47338
47427
  );
47339
- toolbar.append(viewToggle, search, layout4, controls);
47428
+ toolbar.append(viewToggle, compactToggle, search, layout4, controls);
47340
47429
  const searchResults = element3(doc, "div", "ov-search-dropdown");
47341
47430
  searchResults.setAttribute("role", "listbox");
47342
47431
  searchResults.hidden = true;
@@ -47368,7 +47457,7 @@ function createDom(doc, root2, messages, options2) {
47368
47457
  footer.append(stats, legend);
47369
47458
  root2.append(toolbar, searchResults, body, footer);
47370
47459
  const zoomLabelElement = controls.querySelector(".ov-btn-reset-zoom") ?? controls;
47371
- return { toolbar, schemaButton, triplesButton, search, searchResults, layout: layout4, zoomLabel: zoomLabelElement, stage, graph, message, inspector, stats, legend };
47460
+ return { toolbar, schemaButton, triplesButton, compactToggle, compactCheckbox, search, searchResults, layout: layout4, zoomLabel: zoomLabelElement, stage, graph, message, inspector, stats, legend };
47372
47461
  }
47373
47462
  function schemaElements(schema) {
47374
47463
  const nodes3 = schema.entities.map((entity) => {
@@ -47406,12 +47495,28 @@ function schemaElements(schema) {
47406
47495
  }
47407
47496
  return [...nodes3, ...edges3];
47408
47497
  }
47409
- function graphElements(graph) {
47498
+ function graphElements(graph, compact) {
47499
+ const folded = compact ? compactTriples(graph) : void 0;
47500
+ const nodes3 = folded?.nodes ?? graph.nodes;
47501
+ const edges3 = folded?.edges ?? graph.edges;
47410
47502
  return [
47411
- ...graph.nodes.map((node) => ({ data: { id: node.id, label: node.label, kind: node.kind } })),
47412
- ...graph.edges.map((edge) => ({ data: { id: edge.id, source: edge.source, target: edge.target, edgeLabel: edge.predicateLabel } }))
47503
+ ...nodes3.map((node) => ({
47504
+ data: {
47505
+ id: node.id,
47506
+ label: node.label,
47507
+ // `label` stays the bare name so search keeps matching it; the folded
47508
+ // form is a separate field used only by the stylesheet.
47509
+ tripleLabel: tripleLabelFor(node.label, folded?.foldedFacts.get(node.id)),
47510
+ kind: node.kind
47511
+ }
47512
+ })),
47513
+ ...edges3.map((edge) => ({ data: { id: edge.id, source: edge.source, target: edge.target, edgeLabel: edge.predicateLabel } }))
47413
47514
  ];
47414
47515
  }
47516
+ function tripleLabelFor(label, facts) {
47517
+ return facts && facts.length > 0 ? `${label}
47518
+ ${facts.join("\n")}` : label;
47519
+ }
47415
47520
  function schemaStyles(dark) {
47416
47521
  const canvas = dark ? "#1e1e1e" : "#ffffff";
47417
47522
  return [
@@ -47443,13 +47548,18 @@ function schemaStyles(dark) {
47443
47548
  function triplesStyles(dark) {
47444
47549
  return [
47445
47550
  { selector: "node", style: {
47446
- label: "data(label)",
47551
+ // `tripleLabel` carries the folded statements as extra lines, so the box
47552
+ // must wrap and size to its content — with the previous `ellipsis` wrap
47553
+ // and fixed 154x54 box they were silently clipped away.
47554
+ label: "data(tripleLabel)",
47447
47555
  "text-valign": "center",
47448
47556
  "text-halign": "center",
47449
- "text-wrap": "ellipsis",
47450
- "text-max-width": "130px",
47451
- width: 154,
47452
- height: 54,
47557
+ "text-wrap": "wrap",
47558
+ "text-max-width": "150px",
47559
+ width: "label",
47560
+ height: "label",
47561
+ padding: "10px",
47562
+ "min-width": "120px",
47453
47563
  shape: "round-rectangle",
47454
47564
  "font-size": 11,
47455
47565
  "border-width": 1.5,
@@ -47460,8 +47570,6 @@ function triplesStyles(dark) {
47460
47570
  { selector: "node[kind = 'class']", style: { "background-color": dark ? "#18324b" : "#dbeafe", "border-color": "#3b82f6" } },
47461
47571
  { selector: "node[kind = 'property']", style: { "background-color": dark ? "#3b1f3b" : "#fce7f3", "border-color": "#a855f7" } },
47462
47572
  { selector: "node[kind = 'literal']", style: {
47463
- width: 138,
47464
- height: 44,
47465
47573
  "font-size": 10,
47466
47574
  "border-style": "dashed",
47467
47575
  "background-color": dark ? "#38331f" : "#fef9c3",
@@ -47676,6 +47784,13 @@ function matchingElements(root2, selector) {
47676
47784
  matches33.push(...root2.querySelectorAll(selector));
47677
47785
  return matches33;
47678
47786
  }
47787
+ function parseBoolean(value) {
47788
+ if (value === void 0) return void 0;
47789
+ const normalized = value.trim().toLowerCase();
47790
+ if (normalized === "false" || normalized === "0" || normalized === "off") return false;
47791
+ if (normalized === "" || normalized === "true" || normalized === "1" || normalized === "on") return true;
47792
+ return void 0;
47793
+ }
47679
47794
  function resolveOptions(element4, options2) {
47680
47795
  const data4 = element4.dataset;
47681
47796
  const optionBaseIri = options2.baseIri ?? options2.baseIRI;
@@ -47685,6 +47800,7 @@ function resolveOptions(element4, options2) {
47685
47800
  locale: validLocale(options2.locale) ?? validLocale(data4.locale) ?? "auto",
47686
47801
  layout: validLayout(options2.layout) ?? validLayout(data4.layout) ?? "fcose",
47687
47802
  defaultView: validView(options2.defaultView) ?? validView(data4.defaultView) ?? "schema",
47803
+ compactTriples: options2.compactTriples ?? parseBoolean(data4.compactTriples) ?? true,
47688
47804
  baseIri: safeBaseIri(optionBaseIri ?? data4.baseIri, element4.ownerDocument.baseURI),
47689
47805
  storageKey: safeStorageKey(options2.storageKey ?? data4.storageKey),
47690
47806
  injectStyles: options2.injectStyles,
@@ -47698,7 +47814,7 @@ function injectDefaultStyles(doc) {
47698
47814
  styledDocuments.add(doc);
47699
47815
  return;
47700
47816
  }
47701
- const css = true ? '/* ontologyviewer.css \u2014 all selectors are scoped to the generated root. */\n.ontologyviewer-root {\n --ov-bg: #fff;\n --ov-fg: #1f2937;\n --ov-muted: #64748b;\n --ov-border: #d7dde5;\n --ov-toolbar-bg: #f8fafc;\n --ov-input-bg: #fff;\n --ov-hover: #eef2f7;\n position: relative;\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 600px;\n min-height: 320px;\n overflow: hidden;\n border: 1px solid var(--ov-border);\n border-radius: 10px;\n color: var(--ov-fg);\n background: var(--ov-bg);\n font: 14px/1.45 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;\n container-type: inline-size;\n box-sizing: border-box;\n}\n.ontologyviewer-root *, .ontologyviewer-root *::before, .ontologyviewer-root *::after { box-sizing: border-box; }\n.ontologyviewer-root [hidden] { display: none !important; }\n.ontologyviewer-root.ov-dark {\n --ov-bg: #1e1e1e;\n --ov-fg: #d4d4d4;\n --ov-muted: #a8a8a8;\n --ov-border: #454545;\n --ov-toolbar-bg: #252526;\n --ov-input-bg: #313131;\n --ov-hover: #2a2d2e;\n}\n.ontologyviewer-root button,\n.ontologyviewer-root input,\n.ontologyviewer-root select { font: inherit; }\n.ontologyviewer-root button:focus-visible,\n.ontologyviewer-root input:focus-visible,\n.ontologyviewer-root select:focus-visible,\n.ontologyviewer-root summary:focus-visible { outline: 2px solid #1685d1; outline-offset: 2px; }\n.ontologyviewer-root .ov-toolbar {\n z-index: 5;\n display: flex;\n flex: 0 0 auto;\n align-items: center;\n gap: 8px;\n padding: 8px 10px;\n border-bottom: 1px solid var(--ov-border);\n background: var(--ov-toolbar-bg);\n}\n.ontologyviewer-root .ov-view-toggle,\n.ontologyviewer-root .ov-controls { display: flex; align-items: center; gap: 4px; }\n.ontologyviewer-root .ov-view-toggle button,\n.ontologyviewer-root .ov-controls button,\n.ontologyviewer-root .ov-layout-select,\n.ontologyviewer-root .ov-inspector-close {\n min-height: 32px;\n padding: 4px 9px;\n border: 1px solid var(--ov-border);\n border-radius: 6px;\n color: var(--ov-fg);\n background: var(--ov-bg);\n cursor: pointer;\n}\n.ontologyviewer-root button:hover,\n.ontologyviewer-root .ov-layout-select:hover { background: var(--ov-hover); }\n.ontologyviewer-root .ov-view-toggle button.active {\n border-color: #0878bd;\n color: #fff;\n background: #0878bd;\n}\n.ontologyviewer-root .ov-search {\n flex: 1 1 250px;\n min-width: 130px;\n height: 34px;\n padding: 5px 9px;\n border: 1px solid var(--ov-border);\n border-radius: 7px;\n color: var(--ov-fg);\n background: var(--ov-input-bg);\n}\n.ontologyviewer-root .ov-layout-select { height: 34px; }\n.ontologyviewer-root .ov-body { display: flex; flex: 1 1 auto; min-height: 0; }\n.ontologyviewer-root .ov-stage { position: relative; flex: 1 1 auto; min-width: 0; min-height: 260px; }\n/* Cytoscape forces its container to position: relative; explicit dimensions prevent a zero-height graph. */\n.ontologyviewer-root .ov-graph { position: absolute; inset: 0; width: 100%; height: 100%; }\n.ontologyviewer-root .ov-message {\n position: absolute;\n inset: 0;\n display: grid;\n place-items: center;\n padding: 24px;\n text-align: center;\n}\n.ontologyviewer-root .ov-error { color: #b42318; background: #fff5f4; }\n.ontologyviewer-root.ov-dark .ov-error { color: #ff8a80; background: #2e1b1a; }\n.ontologyviewer-root .ov-inspector {\n position: relative;\n flex: 0 0 300px;\n width: 300px;\n overflow: auto;\n padding: 14px;\n border-left: 1px solid var(--ov-border);\n background: var(--ov-bg);\n}\n.ontologyviewer-root .ov-inspector-close {\n position: absolute;\n top: 6px;\n right: 6px;\n display: none;\n min-width: 30px;\n padding: 2px 7px;\n font-size: 20px;\n}\n.ontologyviewer-root .ov-inspector h3 { margin: 0 30px 8px 0; font-size: 16px; }\n.ontologyviewer-root .ov-inspector h4 { margin: 14px 0 5px; font-size: 13px; }\n.ontologyviewer-root .ov-inspector p { overflow-wrap: anywhere; }\n.ontologyviewer-root .ov-inspector dl {\n display: grid;\n grid-template-columns: max-content minmax(0, 1fr);\n gap: 4px 10px;\n margin: 10px 0;\n}\n.ontologyviewer-root .ov-inspector dt { font-weight: 600; }\n.ontologyviewer-root .ov-inspector dd { margin: 0; overflow-wrap: anywhere; }\n.ontologyviewer-root .ov-inspector ul { margin: 5px 0; padding-left: 20px; }\n.ontologyviewer-root .ov-inspector-empty { color: var(--ov-muted); text-align: center; }\n.ontologyviewer-root .ov-inspector-desc { color: var(--ov-muted); }\n.ontologyviewer-root .ov-prop-list { list-style: none; padding: 0 !important; }\n.ontologyviewer-root .ov-prop-list li { padding: 3px 0; border-bottom: 1px solid var(--ov-border); }\n.ontologyviewer-root .ov-footer {\n display: flex;\n flex: 0 0 auto;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n padding: 5px 10px;\n border-top: 1px solid var(--ov-border);\n color: var(--ov-muted);\n font-size: 12px;\n}\n.ontologyviewer-root .ov-legend summary { cursor: pointer; }\n.ontologyviewer-root .ov-legend-content { display: flex; gap: 12px; padding-top: 5px; }\n.ontologyviewer-root .ov-legend-item::before {\n display: inline-block;\n width: 18px;\n margin-right: 5px;\n border-top: 3px solid #64748b;\n content: "";\n vertical-align: middle;\n}\n.ontologyviewer-root .ov-subclass::before { border-color: #b180d7; }\n.ontologyviewer-root .ov-broader::before { border-color: #16875c; }\n.ontologyviewer-root .ov-inferred::before { border-top-style: dashed; }\n.ontologyviewer-root .ov-search-dropdown {\n position: absolute;\n z-index: 20;\n top: 51px;\n left: 120px;\n width: min(520px, calc(100% - 240px));\n max-height: 260px;\n overflow: auto;\n padding: 5px;\n border: 1px solid var(--ov-border);\n border-radius: 8px;\n background: var(--ov-bg);\n box-shadow: 0 10px 30px rgb(0 0 0 / 20%);\n}\n.ontologyviewer-root .ov-search-item {\n display: flex;\n justify-content: space-between;\n gap: 12px;\n width: 100%;\n padding: 8px;\n border: 0;\n border-radius: 5px;\n color: var(--ov-fg);\n background: transparent;\n text-align: left;\n cursor: pointer;\n}\n.ontologyviewer-root .ov-search-meta { color: var(--ov-muted); font-size: 11px; }\n.ontologyviewer-root .ov-search-empty { margin: 0; padding: 10px; color: var(--ov-muted); }\n\n@container (max-width: 759px) {\n .ontologyviewer-root .ov-toolbar { flex-wrap: wrap; }\n .ontologyviewer-root .ov-search { order: 3; flex-basis: 100%; }\n .ontologyviewer-root .ov-body { display: block; position: relative; }\n .ontologyviewer-root .ov-stage { height: 100%; }\n .ontologyviewer-root .ov-inspector {\n position: absolute;\n z-index: 10;\n right: 0;\n bottom: 0;\n left: 0;\n width: auto;\n max-height: 52%;\n border-top: 1px solid var(--ov-border);\n border-left: 0;\n box-shadow: 0 -8px 24px rgb(0 0 0 / 22%);\n }\n .ontologyviewer-root .ov-inspector-close { display: block; }\n .ontologyviewer-root .ov-search-dropdown { top: 92px; left: 10px; width: calc(100% - 20px); }\n .ontologyviewer-root .ov-footer { align-items: flex-start; }\n}\n\n/* Cytoscape normally injects this fixed rule as an inline style. Keeping it in\n the external bundle lets strict-CSP users avoid that injection. */\n.ontologyviewer-root .__________cytoscape_container { position: relative; }\n' : "";
47817
+ const css = true ? '/* ontologyviewer.css \u2014 all selectors are scoped to the generated root. */\n.ontologyviewer-root {\n --ov-bg: #fff;\n --ov-fg: #1f2937;\n --ov-muted: #64748b;\n --ov-border: #d7dde5;\n --ov-toolbar-bg: #f8fafc;\n --ov-input-bg: #fff;\n --ov-hover: #eef2f7;\n position: relative;\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 600px;\n min-height: 320px;\n overflow: hidden;\n border: 1px solid var(--ov-border);\n border-radius: 10px;\n color: var(--ov-fg);\n background: var(--ov-bg);\n font: 14px/1.45 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;\n container-type: inline-size;\n box-sizing: border-box;\n}\n.ontologyviewer-root *, .ontologyviewer-root *::before, .ontologyviewer-root *::after { box-sizing: border-box; }\n.ontologyviewer-root [hidden] { display: none !important; }\n.ontologyviewer-root.ov-dark {\n --ov-bg: #1e1e1e;\n --ov-fg: #d4d4d4;\n --ov-muted: #a8a8a8;\n --ov-border: #454545;\n --ov-toolbar-bg: #252526;\n --ov-input-bg: #313131;\n --ov-hover: #2a2d2e;\n}\n.ontologyviewer-root button,\n.ontologyviewer-root input,\n.ontologyviewer-root select { font: inherit; }\n.ontologyviewer-root button:focus-visible,\n.ontologyviewer-root input:focus-visible,\n.ontologyviewer-root select:focus-visible,\n.ontologyviewer-root summary:focus-visible { outline: 2px solid #1685d1; outline-offset: 2px; }\n.ontologyviewer-root .ov-toolbar {\n z-index: 5;\n display: flex;\n flex: 0 0 auto;\n align-items: center;\n gap: 8px;\n padding: 8px 10px;\n border-bottom: 1px solid var(--ov-border);\n background: var(--ov-toolbar-bg);\n}\n.ontologyviewer-root .ov-view-toggle,\n.ontologyviewer-root .ov-controls { display: flex; align-items: center; gap: 4px; }\n.ontologyviewer-root .ov-view-toggle button,\n.ontologyviewer-root .ov-controls button,\n.ontologyviewer-root .ov-layout-select,\n.ontologyviewer-root .ov-inspector-close {\n min-height: 32px;\n padding: 4px 9px;\n border: 1px solid var(--ov-border);\n border-radius: 6px;\n color: var(--ov-fg);\n background: var(--ov-bg);\n cursor: pointer;\n}\n.ontologyviewer-root button:hover,\n.ontologyviewer-root .ov-layout-select:hover { background: var(--ov-hover); }\n.ontologyviewer-root .ov-view-toggle button.active {\n border-color: #0878bd;\n color: #fff;\n background: #0878bd;\n}\n/* Compaction toggle. A checkbox rather than a pressed button, because\n schema/triples is an exclusive selection while this is an independent on/off\n \u2014 and a checkbox shows its state without depending on colour. Styled as a\n toolbar chip so it sits at the same height as the buttons beside it. */\n.ontologyviewer-root .ov-compact-toggle {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n min-height: 32px;\n padding: 4px 10px 4px 8px;\n border: 1px solid var(--ov-border);\n border-radius: 6px;\n color: var(--ov-fg);\n background: var(--ov-bg);\n cursor: pointer;\n user-select: none;\n white-space: nowrap;\n}\n.ontologyviewer-root .ov-compact-toggle:hover { background: var(--ov-hover); }\n.ontologyviewer-root .ov-compact-toggle[hidden] { display: none; }\n.ontologyviewer-root .ov-compact-checkbox {\n width: 14px;\n height: 14px;\n margin: 0;\n accent-color: #0878bd;\n cursor: pointer;\n}\n/* Keyboard focus lands on the input, so raise the ring to the whole chip.\n Scoped to :focus-visible so a mouse click does not leave a blue ring behind\n that reads as "still enabled" on an unchecked control. */\n.ontologyviewer-root .ov-compact-toggle:has(.ov-compact-checkbox:focus-visible) {\n outline: 2px solid #0878bd;\n outline-offset: 1px;\n}\n/* Checked state also shifts the chip itself, so the setting reads at a glance\n from across the toolbar rather than only from the 14px box. */\n.ontologyviewer-root .ov-compact-toggle:has(.ov-compact-checkbox:checked) {\n border-color: #0878bd;\n color: #0878bd;\n font-weight: 600;\n}\n.ontologyviewer-root.ov-dark .ov-compact-toggle:has(.ov-compact-checkbox:checked) {\n border-color: #4aa3dd;\n color: #7dc4f0;\n}\n.ontologyviewer-root .ov-search {\n flex: 1 1 250px;\n min-width: 130px;\n height: 34px;\n padding: 5px 9px;\n border: 1px solid var(--ov-border);\n border-radius: 7px;\n color: var(--ov-fg);\n background: var(--ov-input-bg);\n}\n.ontologyviewer-root .ov-layout-select { height: 34px; }\n.ontologyviewer-root .ov-body { display: flex; flex: 1 1 auto; min-height: 0; }\n.ontologyviewer-root .ov-stage { position: relative; flex: 1 1 auto; min-width: 0; min-height: 260px; }\n/* Cytoscape forces its container to position: relative; explicit dimensions prevent a zero-height graph. */\n.ontologyviewer-root .ov-graph { position: absolute; inset: 0; width: 100%; height: 100%; }\n.ontologyviewer-root .ov-message {\n position: absolute;\n inset: 0;\n display: grid;\n place-items: center;\n padding: 24px;\n text-align: center;\n}\n.ontologyviewer-root .ov-error { color: #b42318; background: #fff5f4; }\n.ontologyviewer-root.ov-dark .ov-error { color: #ff8a80; background: #2e1b1a; }\n.ontologyviewer-root .ov-inspector {\n position: relative;\n flex: 0 0 300px;\n width: 300px;\n overflow: auto;\n padding: 14px;\n border-left: 1px solid var(--ov-border);\n background: var(--ov-bg);\n}\n.ontologyviewer-root .ov-inspector-close {\n position: absolute;\n top: 6px;\n right: 6px;\n display: none;\n min-width: 30px;\n padding: 2px 7px;\n font-size: 20px;\n}\n.ontologyviewer-root .ov-inspector h3 { margin: 0 30px 8px 0; font-size: 16px; }\n.ontologyviewer-root .ov-inspector h4 { margin: 14px 0 5px; font-size: 13px; }\n.ontologyviewer-root .ov-inspector p { overflow-wrap: anywhere; }\n.ontologyviewer-root .ov-inspector dl {\n display: grid;\n grid-template-columns: max-content minmax(0, 1fr);\n gap: 4px 10px;\n margin: 10px 0;\n}\n.ontologyviewer-root .ov-inspector dt { font-weight: 600; }\n.ontologyviewer-root .ov-inspector dd { margin: 0; overflow-wrap: anywhere; }\n.ontologyviewer-root .ov-inspector ul { margin: 5px 0; padding-left: 20px; }\n.ontologyviewer-root .ov-inspector-empty { color: var(--ov-muted); text-align: center; }\n.ontologyviewer-root .ov-inspector-desc { color: var(--ov-muted); }\n.ontologyviewer-root .ov-prop-list { list-style: none; padding: 0 !important; }\n.ontologyviewer-root .ov-prop-list li { padding: 3px 0; border-bottom: 1px solid var(--ov-border); }\n.ontologyviewer-root .ov-footer {\n display: flex;\n flex: 0 0 auto;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n padding: 5px 10px;\n border-top: 1px solid var(--ov-border);\n color: var(--ov-muted);\n font-size: 12px;\n}\n.ontologyviewer-root .ov-legend summary { cursor: pointer; }\n.ontologyviewer-root .ov-legend-content { display: flex; gap: 12px; padding-top: 5px; }\n.ontologyviewer-root .ov-legend-item::before {\n display: inline-block;\n width: 18px;\n margin-right: 5px;\n border-top: 3px solid #64748b;\n content: "";\n vertical-align: middle;\n}\n.ontologyviewer-root .ov-subclass::before { border-color: #b180d7; }\n.ontologyviewer-root .ov-broader::before { border-color: #16875c; }\n.ontologyviewer-root .ov-inferred::before { border-top-style: dashed; }\n.ontologyviewer-root .ov-search-dropdown {\n position: absolute;\n z-index: 20;\n top: 51px;\n left: 120px;\n width: min(520px, calc(100% - 240px));\n max-height: 260px;\n overflow: auto;\n padding: 5px;\n border: 1px solid var(--ov-border);\n border-radius: 8px;\n background: var(--ov-bg);\n box-shadow: 0 10px 30px rgb(0 0 0 / 20%);\n}\n.ontologyviewer-root .ov-search-item {\n display: flex;\n justify-content: space-between;\n gap: 12px;\n width: 100%;\n padding: 8px;\n border: 0;\n border-radius: 5px;\n color: var(--ov-fg);\n background: transparent;\n text-align: left;\n cursor: pointer;\n}\n.ontologyviewer-root .ov-search-meta { color: var(--ov-muted); font-size: 11px; }\n.ontologyviewer-root .ov-search-empty { margin: 0; padding: 10px; color: var(--ov-muted); }\n\n@container (max-width: 759px) {\n .ontologyviewer-root .ov-toolbar { flex-wrap: wrap; }\n .ontologyviewer-root .ov-search { order: 3; flex-basis: 100%; }\n .ontologyviewer-root .ov-body { display: block; position: relative; }\n .ontologyviewer-root .ov-stage { height: 100%; }\n .ontologyviewer-root .ov-inspector {\n position: absolute;\n z-index: 10;\n right: 0;\n bottom: 0;\n left: 0;\n width: auto;\n max-height: 52%;\n border-top: 1px solid var(--ov-border);\n border-left: 0;\n box-shadow: 0 -8px 24px rgb(0 0 0 / 22%);\n }\n .ontologyviewer-root .ov-inspector-close { display: block; }\n .ontologyviewer-root .ov-search-dropdown { top: 92px; left: 10px; width: calc(100% - 20px); }\n .ontologyviewer-root .ov-footer { align-items: flex-start; }\n}\n\n/* Cytoscape normally injects this fixed rule as an inline style. Keeping it in\n the external bundle lets strict-CSP users avoid that injection. */\n.ontologyviewer-root .__________cytoscape_container { position: relative; }\n' : "";
47702
47818
  if (!css) return;
47703
47819
  const style3 = doc.createElement("style");
47704
47820
  style3.setAttribute("data-ontologyviewer-styles", "");