diff --git a/changelog.md b/changelog.md index 4d22009b..29cbefe0 100644 --- a/changelog.md +++ b/changelog.md @@ -6,6 +6,10 @@ ## Bug fixes +- Fix the graph widget failing to render in PyCharm and VS Code + ## Improvements +- Reduce the widget payload. + ## Other changes diff --git a/js-applet/src/graph-widget.test.tsx b/js-applet/src/graph-widget.test.tsx index d464a64a..be51eb2e 100644 --- a/js-applet/src/graph-widget.test.tsx +++ b/js-applet/src/graph-widget.test.tsx @@ -60,6 +60,7 @@ type WidgetState = { } | null; visible?: boolean; }; + _css?: string; }; // The static HTML render path uses the real `createLocalModel` shim, so tests @@ -137,6 +138,9 @@ async function renderWidgetInShadowRoot( theme: "light", selected: { nodeIds: [], relationshipIds: [] }, legend: { nodes: null, relationships: null, visible: true }, + // The stylesheet is host-provided (`_css`); a stub is enough to assert the + // shadow-root/document.head bridging. + _css: ".nvl-test-ndl { color: red; }", }); let teardown: RenderedWidget["teardown"] = undefined; diff --git a/js-applet/src/graph-widget.tsx b/js-applet/src/graph-widget.tsx index ab5e812a..859df66d 100644 --- a/js-applet/src/graph-widget.tsx +++ b/js-applet/src/graph-widget.tsx @@ -1,5 +1,4 @@ import { createRender, useModel, useModelState } from "@anywidget/react"; -import ndlCssText from "@neo4j-ndl/base/lib/neo4j-ds-styles.css?inline"; import { Gesture, GraphSelection, GraphVisualization } from "@neo4j-ndl/react-graph"; import type NVL from "@neo4j-nvl/base"; import type { Layout, NvlOptions } from "@neo4j-nvl/base"; @@ -78,6 +77,11 @@ export type WidgetData = { selected: GraphSelection; legend: LegendData; last_event: InteractionEvent | null; + /** + * The NDL stylesheet, provided by the host (anywidget syncs the widget's `_css` + * trait; Streamlit passes it in the component state). Never bundled into the JS. + */ + _css?: string; }; const EMPTY_SELECTION: GraphSelection = { nodeIds: [], relationshipIds: [] }; @@ -154,49 +158,50 @@ function useResolvedTheme(theme: Theme | undefined): "light" | "dark" { return normalizedTheme === "auto" ? detectedTheme : normalizedTheme; } -// @font-face rules in shadow DOM adopted stylesheets don't register fonts at the -// document level, so the browser can't find them for rendering. We extract and hoist -// them into document.head eagerly at module load so fonts begin loading immediately. -const fontFaceRules = (ndlCssText.match(/@font-face\s*\{[^}]*\}/g) || []).join("\n"); -if (fontFaceRules) { - const fontStyle = document.createElement("style"); - fontStyle.textContent = fontFaceRules; - document.head.appendChild(fontStyle); -} - -const documentStyleSelector = "[data-neo4j-viz-ndl-main]"; -const overlayStyleSelector = "[data-neo4j-viz-ndl-overlays]"; -const shadowRootStyleSelector = "[data-neo4j-viz-ndl-shadow-root]"; - -function appendStyle(root: Node & ParentNode, attributeName: string, cssText: string) { +const documentStyleAttr = "data-neo4j-viz-ndl-main"; +const overlayStyleAttr = "data-neo4j-viz-ndl-overlays"; +const shadowRootStyleAttr = "data-neo4j-viz-ndl-shadow-root"; +const fontStyleAttr = "data-neo4j-viz-ndl-fonts"; + +// The NDL stylesheet is deliberately NOT imported into the JS bundle: it is provided +// by the host via the model's `_css` trait (anywidget syncs it, Streamlit passes it in +// the component state). Bundling it here duplicated ~2 MB of CSS into every widget +// payload. `ensureStyle` keeps the injected copies in sync so `_css` updates (HMR) are +// reflected without adding duplicate