@aspicio/react
Advanced tools
+25
-5
@@ -23,2 +23,8 @@ import { CSSProperties, ReactElement } from "react"; | ||
| onViewer?: (viewer: DxfViewer | null) => void; | ||
| /** | ||
| * When provided, the canvas hit-tests the layer under the cursor on hover, | ||
| * highlights it, and reports its name (or null). DxfEmbed uses this to | ||
| * reverse-highlight the matching layer-panel row. | ||
| */ | ||
| onHoverLayer?: (layer: string | null) => void; | ||
| } | ||
@@ -41,2 +47,3 @@ /** | ||
| readonly panel: "#191c22"; | ||
| readonly panel2: "#1f232b"; | ||
| readonly hover: "rgba(255,255,255,.055)"; | ||
@@ -49,2 +56,6 @@ readonly hairline: "#282c34"; | ||
| readonly crease: "#4c8dff"; | ||
| readonly creasedim: "rgba(76,141,255,.16)"; | ||
| readonly amber: "#e0a82e"; | ||
| readonly amberdim: "rgba(224,168,46,.16)"; | ||
| readonly amberborder: "rgba(224,168,46,.4)"; | ||
| readonly fontSans: '"IBM Plex Sans", system-ui, -apple-system, "Segoe UI", sans-serif'; | ||
@@ -85,2 +96,9 @@ readonly fontMono: '"IBM Plex Mono", ui-monospace, "SF Mono", Menlo, monospace'; | ||
| theme?: DxfTheme; | ||
| /** | ||
| * Layer currently hovered on the canvas (from DxfPreview's onHoverLayer). | ||
| * Its row is reverse-highlighted. DxfEmbed wires this automatically. | ||
| */ | ||
| reverseHighlightLayer?: string | null; | ||
| /** Show the gesture-hints footer (themed mode only). Default: true. */ | ||
| hints?: boolean; | ||
| className?: string; | ||
@@ -90,7 +108,7 @@ style?: CSSProperties; | ||
| /** | ||
| * Ready-made layer list for an embedded viewer: visibility checkboxes, | ||
| * effective-color swatches, entity counts, and hover-to-highlight. Styled | ||
| * like the Aspicio demo app by default (fonts degrade to system faces | ||
| * unless the host loads IBM Plex); pass theme="none" to inherit the host's | ||
| * look instead. | ||
| * Ready-made layer list for an embedded viewer, matching the Aspicio demo | ||
| * app: header with layer count, visibility checkboxes, effective-color | ||
| * swatches, entity counts, hover-to-highlight, double-click-to-solo (with a | ||
| * banner), canvas-hover reverse-highlight, and a gesture-hints footer. Pass | ||
| * theme="none" for a minimal list that inherits the host's styling. | ||
| */ | ||
@@ -100,2 +118,4 @@ declare function DxfLayerPanel({ | ||
| theme, | ||
| reverseHighlightLayer, | ||
| hints, | ||
| className, | ||
@@ -102,0 +122,0 @@ style |
+407
-61
@@ -10,2 +10,3 @@ import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from "react"; | ||
| panel: "#191c22", | ||
| panel2: "#1f232b", | ||
| hover: "rgba(255,255,255,.055)", | ||
@@ -18,2 +19,6 @@ hairline: "#282c34", | ||
| crease: "#4c8dff", | ||
| creasedim: "rgba(76,141,255,.16)", | ||
| amber: "#e0a82e", | ||
| amberdim: "rgba(224,168,46,.16)", | ||
| amberborder: "rgba(224,168,46,.4)", | ||
| fontSans: "\"IBM Plex Sans\", system-ui, -apple-system, \"Segoe UI\", sans-serif", | ||
@@ -35,22 +40,59 @@ fontMono: "\"IBM Plex Mono\", ui-monospace, \"SF Mono\", Menlo, monospace" | ||
| //#region src/DxfLayerPanel.tsx | ||
| const baseRow = { | ||
| display: "flex", | ||
| alignItems: "center", | ||
| gap: "0.6em", | ||
| padding: "0.35em 0.5em", | ||
| borderRadius: 3, | ||
| cursor: "pointer", | ||
| userSelect: "none" | ||
| }; | ||
| const DESKTOP_HINTS = [ | ||
| ["DRAG", "pan"], | ||
| ["SCROLL", "zoom"], | ||
| ["⇧+DRAG", "rotate"], | ||
| ["2×CLICK", "fit to view"], | ||
| ["HOVER", "highlight layer"], | ||
| ["2×CLICK ROW", "solo layer"] | ||
| ]; | ||
| const CheckIcon = () => /* @__PURE__ */ jsxs("svg", { | ||
| width: 16, | ||
| height: 16, | ||
| viewBox: "0 0 16 16", | ||
| "aria-hidden": true, | ||
| children: [/* @__PURE__ */ jsx("rect", { | ||
| x: 1.5, | ||
| y: 1.5, | ||
| width: 13, | ||
| height: 13, | ||
| rx: 2.5, | ||
| fill: aspicioTokens.crease | ||
| }), /* @__PURE__ */ jsx("path", { | ||
| d: "M4.4 8.2 L6.9 10.6 L11.6 5.3", | ||
| fill: "none", | ||
| stroke: "#fff", | ||
| strokeWidth: 1.7, | ||
| strokeLinecap: "round", | ||
| strokeLinejoin: "round" | ||
| })] | ||
| }); | ||
| const UncheckIcon = () => /* @__PURE__ */ jsx("svg", { | ||
| width: 16, | ||
| height: 16, | ||
| viewBox: "0 0 16 16", | ||
| "aria-hidden": true, | ||
| children: /* @__PURE__ */ jsx("rect", { | ||
| x: 1.5, | ||
| y: 1.5, | ||
| width: 13, | ||
| height: 13, | ||
| rx: 2.5, | ||
| fill: "none", | ||
| stroke: aspicioTokens.hairline2, | ||
| strokeWidth: 1.4 | ||
| }) | ||
| }); | ||
| /** | ||
| * Ready-made layer list for an embedded viewer: visibility checkboxes, | ||
| * effective-color swatches, entity counts, and hover-to-highlight. Styled | ||
| * like the Aspicio demo app by default (fonts degrade to system faces | ||
| * unless the host loads IBM Plex); pass theme="none" to inherit the host's | ||
| * look instead. | ||
| * Ready-made layer list for an embedded viewer, matching the Aspicio demo | ||
| * app: header with layer count, visibility checkboxes, effective-color | ||
| * swatches, entity counts, hover-to-highlight, double-click-to-solo (with a | ||
| * banner), canvas-hover reverse-highlight, and a gesture-hints footer. Pass | ||
| * theme="none" for a minimal list that inherits the host's styling. | ||
| */ | ||
| function DxfLayerPanel({ viewer, theme = "aspicio", className, style }) { | ||
| function DxfLayerPanel({ viewer, theme = "aspicio", reverseHighlightLayer = null, hints = true, className, style }) { | ||
| const [layers, setLayers] = useState([]); | ||
| const [soloLayer, setSoloLayer] = useState(null); | ||
| const [hoveredRow, setHoveredRow] = useState(null); | ||
| const [, bump] = useState(0); | ||
| const [, forceRender] = useState(0); | ||
| const themed = theme === "aspicio"; | ||
@@ -62,3 +104,6 @@ useEffect(() => { | ||
| } | ||
| const sync = () => setLayers(viewer.getLayers()); | ||
| const sync = () => { | ||
| setSoloLayer(null); | ||
| setLayers(viewer.getLayers()); | ||
| }; | ||
| sync(); | ||
@@ -68,20 +113,34 @@ viewer.on("loaded", sync); | ||
| }, [viewer]); | ||
| const toggle = (layer) => { | ||
| viewer?.setLayerVisible(layer.name, !layer.visible); | ||
| bump((n) => n + 1); | ||
| const tick = () => forceRender((n) => n + 1); | ||
| const isVisible = (layer) => soloLayer ? layer.name === soloLayer : layer.visible !== false; | ||
| const toggleLayer = (layer) => { | ||
| if (!viewer) return; | ||
| if (soloLayer) { | ||
| for (const l of layers) viewer.setLayerVisible(l.name, l.name === layer.name); | ||
| setSoloLayer(null); | ||
| } else viewer.setLayerVisible(layer.name, layer.visible === false); | ||
| tick(); | ||
| }; | ||
| return /* @__PURE__ */ jsx("ul", { | ||
| const toggleSolo = (layer) => { | ||
| if (!viewer) return; | ||
| const next = soloLayer === layer.name ? null : layer.name; | ||
| for (const l of layers) viewer.setLayerVisible(l.name, next ? l.name === next : true); | ||
| setSoloLayer(next); | ||
| tick(); | ||
| }; | ||
| const exitSolo = () => { | ||
| if (!viewer) return; | ||
| for (const l of layers) viewer.setLayerVisible(l.name, true); | ||
| setSoloLayer(null); | ||
| tick(); | ||
| }; | ||
| const hover = (name) => { | ||
| setHoveredRow(name); | ||
| viewer?.setLayerHighlight(name); | ||
| }; | ||
| if (!themed) return /* @__PURE__ */ jsx("ul", { | ||
| className, | ||
| style: themed ? { | ||
| style: { | ||
| listStyle: "none", | ||
| margin: 0, | ||
| padding: "8px", | ||
| background: aspicioTokens.panel, | ||
| color: aspicioTokens.text, | ||
| fontFamily: aspicioTokens.fontSans, | ||
| fontSize: 13, | ||
| ...style | ||
| } : { | ||
| listStyle: "none", | ||
| margin: 0, | ||
| padding: 0, | ||
@@ -91,24 +150,23 @@ ...style | ||
| children: layers.map((layer) => { | ||
| const visible = isVisible(layer); | ||
| const color = `#${(layer.effectiveColors?.[0] ?? layer.color).toString(16).padStart(6, "0")}`; | ||
| return /* @__PURE__ */ jsxs("li", { | ||
| style: { | ||
| ...baseRow, | ||
| opacity: layer.visible ? 1 : .5, | ||
| background: themed && hoveredRow === layer.name ? aspicioTokens.hover : void 0 | ||
| display: "flex", | ||
| alignItems: "center", | ||
| gap: "0.6em", | ||
| padding: "0.35em 0.5em", | ||
| cursor: "pointer", | ||
| userSelect: "none", | ||
| opacity: visible ? 1 : .5 | ||
| }, | ||
| onMouseEnter: () => { | ||
| setHoveredRow(layer.name); | ||
| viewer?.setLayerHighlight(layer.name); | ||
| }, | ||
| onMouseLeave: () => { | ||
| setHoveredRow(null); | ||
| viewer?.setLayerHighlight(null); | ||
| }, | ||
| onMouseEnter: () => hover(layer.name), | ||
| onMouseLeave: () => hover(null), | ||
| onDoubleClick: () => toggleSolo(layer), | ||
| children: [ | ||
| /* @__PURE__ */ jsx("input", { | ||
| type: "checkbox", | ||
| checked: layer.visible, | ||
| onChange: () => toggle(layer), | ||
| "aria-label": layer.name, | ||
| style: themed ? { accentColor: aspicioTokens.crease } : void 0 | ||
| checked: visible, | ||
| onChange: () => toggleLayer(layer), | ||
| "aria-label": layer.name | ||
| }), | ||
@@ -122,4 +180,4 @@ /* @__PURE__ */ jsx("span", { | ||
| flexShrink: 0, | ||
| background: layer.visible ? color : "transparent", | ||
| border: layer.visible ? "1px solid rgba(255,255,255,.25)" : `1px solid ${color}` | ||
| background: visible ? color : "transparent", | ||
| border: visible ? "1px solid rgba(128,128,128,.5)" : `1px solid ${color}` | ||
| } | ||
@@ -133,4 +191,3 @@ }), | ||
| textOverflow: "ellipsis", | ||
| whiteSpace: "nowrap", | ||
| color: themed && !layer.visible ? aspicioTokens.text3 : void 0 | ||
| whiteSpace: "nowrap" | ||
| }, | ||
@@ -140,7 +197,3 @@ children: layer.name | ||
| /* @__PURE__ */ jsx("span", { | ||
| style: themed ? { | ||
| color: aspicioTokens.text3, | ||
| fontFamily: aspicioTokens.fontMono, | ||
| fontSize: 11 | ||
| } : { | ||
| style: { | ||
| opacity: .55, | ||
@@ -155,2 +208,257 @@ fontSize: "0.85em" | ||
| }); | ||
| const mono = aspicioTokens.fontMono; | ||
| return /* @__PURE__ */ jsxs("div", { | ||
| className, | ||
| style: { | ||
| display: "flex", | ||
| flexDirection: "column", | ||
| minHeight: 0, | ||
| height: "100%", | ||
| background: aspicioTokens.panel, | ||
| color: aspicioTokens.text, | ||
| fontFamily: aspicioTokens.fontSans, | ||
| fontSize: 13, | ||
| ...style | ||
| }, | ||
| children: [ | ||
| /* @__PURE__ */ jsxs("div", { | ||
| style: { | ||
| display: "flex", | ||
| alignItems: "center", | ||
| gap: 8, | ||
| padding: "13px 14px 10px", | ||
| flexShrink: 0, | ||
| fontFamily: mono, | ||
| fontSize: 11, | ||
| letterSpacing: "0.14em", | ||
| color: aspicioTokens.text3 | ||
| }, | ||
| children: ["LAYERS", /* @__PURE__ */ jsx("span", { | ||
| style: { | ||
| fontSize: 10.5, | ||
| background: aspicioTokens.panel2, | ||
| border: `1px solid ${aspicioTokens.hairline}`, | ||
| borderRadius: 3, | ||
| padding: "1px 6px", | ||
| fontFeatureSettings: "\"tnum\" 1" | ||
| }, | ||
| children: layers.length | ||
| })] | ||
| }), | ||
| soloLayer && /* @__PURE__ */ jsxs("div", { | ||
| style: { | ||
| margin: "0 12px 8px", | ||
| display: "flex", | ||
| alignItems: "center", | ||
| gap: 8, | ||
| padding: "7px 9px", | ||
| background: aspicioTokens.amberdim, | ||
| border: `1px solid ${aspicioTokens.amberborder}`, | ||
| borderRadius: 3 | ||
| }, | ||
| children: [ | ||
| /* @__PURE__ */ jsx("span", { | ||
| style: { | ||
| fontFamily: mono, | ||
| fontSize: 10, | ||
| letterSpacing: "0.1em", | ||
| fontWeight: 600, | ||
| color: aspicioTokens.amber | ||
| }, | ||
| children: "SOLO" | ||
| }), | ||
| /* @__PURE__ */ jsx("span", { | ||
| style: { | ||
| flex: 1, | ||
| minWidth: 0, | ||
| overflow: "hidden", | ||
| textOverflow: "ellipsis", | ||
| whiteSpace: "nowrap", | ||
| fontFamily: mono, | ||
| fontSize: 11.5, | ||
| color: aspicioTokens.text | ||
| }, | ||
| children: soloLayer | ||
| }), | ||
| /* @__PURE__ */ jsx("button", { | ||
| type: "button", | ||
| onClick: exitSolo, | ||
| style: { | ||
| border: "none", | ||
| background: "transparent", | ||
| color: aspicioTokens.amber, | ||
| cursor: "pointer", | ||
| fontFamily: mono, | ||
| fontSize: 10, | ||
| letterSpacing: "0.08em", | ||
| padding: "2px 4px" | ||
| }, | ||
| children: "EXIT" | ||
| }) | ||
| ] | ||
| }), | ||
| /* @__PURE__ */ jsxs("ul", { | ||
| style: { | ||
| listStyle: "none", | ||
| margin: 0, | ||
| padding: "0 8px", | ||
| flex: 1, | ||
| minHeight: 0, | ||
| overflowY: "auto" | ||
| }, | ||
| children: [layers.length === 0 && /* @__PURE__ */ jsx("li", { | ||
| style: { | ||
| padding: "24px 10px", | ||
| textAlign: "center", | ||
| color: aspicioTokens.text3, | ||
| fontSize: 12.5, | ||
| lineHeight: 1.6 | ||
| }, | ||
| children: "No layers yet." | ||
| }), layers.map((layer) => { | ||
| const visible = isVisible(layer); | ||
| const isSolo = soloLayer === layer.name; | ||
| const dimmed = !!soloLayer && !isSolo; | ||
| const reverse = reverseHighlightLayer === layer.name; | ||
| const rowHover = hoveredRow === layer.name; | ||
| const color = `#${(layer.effectiveColors?.[0] ?? layer.color).toString(16).padStart(6, "0")}`; | ||
| return /* @__PURE__ */ jsxs("li", { | ||
| title: `${layer.name} · ${layer.entityCount} entities`, | ||
| onMouseEnter: () => hover(layer.name), | ||
| onMouseLeave: () => hover(null), | ||
| onDoubleClick: (e) => { | ||
| e.preventDefault(); | ||
| toggleSolo(layer); | ||
| }, | ||
| style: { | ||
| display: "flex", | ||
| alignItems: "center", | ||
| gap: 10, | ||
| padding: "8px 8px", | ||
| paddingLeft: isSolo ? 12 : 8, | ||
| borderRadius: 3, | ||
| cursor: "pointer", | ||
| userSelect: "none", | ||
| position: "relative", | ||
| minWidth: 0, | ||
| border: `1px solid ${reverse ? aspicioTokens.crease : isSolo ? aspicioTokens.amberborder : "transparent"}`, | ||
| background: reverse ? aspicioTokens.creasedim : isSolo ? aspicioTokens.amberdim : rowHover ? aspicioTokens.hover : "transparent", | ||
| opacity: dimmed ? .34 : visible || isSolo ? 1 : .55, | ||
| transition: "background 140ms, border-color 140ms, opacity 140ms" | ||
| }, | ||
| children: [ | ||
| isSolo && /* @__PURE__ */ jsx("span", { | ||
| "aria-hidden": true, | ||
| style: { | ||
| position: "absolute", | ||
| left: 0, | ||
| top: 3, | ||
| bottom: 3, | ||
| width: 2, | ||
| background: aspicioTokens.amber, | ||
| borderRadius: 2 | ||
| } | ||
| }), | ||
| /* @__PURE__ */ jsx("span", { | ||
| role: "checkbox", | ||
| "aria-checked": visible, | ||
| "aria-label": layer.name, | ||
| tabIndex: 0, | ||
| onClick: (e) => { | ||
| e.stopPropagation(); | ||
| toggleLayer(layer); | ||
| }, | ||
| style: { | ||
| flexShrink: 0, | ||
| display: "inline-flex", | ||
| alignItems: "center", | ||
| justifyContent: "center", | ||
| width: 20, | ||
| height: 20, | ||
| cursor: "pointer" | ||
| }, | ||
| children: visible ? /* @__PURE__ */ jsx(CheckIcon, {}) : /* @__PURE__ */ jsx(UncheckIcon, {}) | ||
| }), | ||
| /* @__PURE__ */ jsx("span", { | ||
| "aria-hidden": true, | ||
| style: { | ||
| width: 13, | ||
| height: 13, | ||
| borderRadius: 2, | ||
| flexShrink: 0, | ||
| background: visible ? color : "transparent", | ||
| border: visible ? "1px solid rgba(255,255,255,.25)" : `1px solid ${color}` | ||
| } | ||
| }), | ||
| /* @__PURE__ */ jsx("span", { | ||
| style: { | ||
| flex: 1, | ||
| minWidth: 0, | ||
| overflow: "hidden", | ||
| textOverflow: "ellipsis", | ||
| whiteSpace: "nowrap", | ||
| fontSize: 13, | ||
| color: visible || isSolo ? aspicioTokens.text : aspicioTokens.text3 | ||
| }, | ||
| children: layer.name | ||
| }), | ||
| isSolo && /* @__PURE__ */ jsx("span", { | ||
| style: { | ||
| fontFamily: mono, | ||
| fontSize: 8.5, | ||
| letterSpacing: "0.1em", | ||
| color: aspicioTokens.amber, | ||
| border: `1px solid ${aspicioTokens.amberborder}`, | ||
| borderRadius: 2, | ||
| padding: "1px 4px", | ||
| flexShrink: 0 | ||
| }, | ||
| children: "SOLO" | ||
| }), | ||
| /* @__PURE__ */ jsx("span", { | ||
| style: { | ||
| fontFamily: mono, | ||
| fontSize: 11, | ||
| color: aspicioTokens.text3, | ||
| flexShrink: 0, | ||
| fontFeatureSettings: "\"tnum\" 1" | ||
| }, | ||
| children: layer.entityCount | ||
| }) | ||
| ] | ||
| }, layer.name); | ||
| })] | ||
| }), | ||
| hints && /* @__PURE__ */ jsx("div", { | ||
| style: { | ||
| flexShrink: 0, | ||
| padding: "11px 14px 13px", | ||
| borderTop: `1px solid ${aspicioTokens.hairline}`, | ||
| display: "grid", | ||
| gridTemplateColumns: "auto 1fr", | ||
| gap: "5px 10px", | ||
| alignItems: "baseline" | ||
| }, | ||
| children: DESKTOP_HINTS.map(([k, v]) => /* @__PURE__ */ jsxs("div", { | ||
| style: { display: "contents" }, | ||
| children: [/* @__PURE__ */ jsx("span", { | ||
| style: { | ||
| fontFamily: mono, | ||
| fontSize: 9.5, | ||
| letterSpacing: "0.06em", | ||
| color: aspicioTokens.text2, | ||
| whiteSpace: "nowrap" | ||
| }, | ||
| children: k | ||
| }), /* @__PURE__ */ jsx("span", { | ||
| style: { | ||
| fontSize: 11, | ||
| color: aspicioTokens.text3 | ||
| }, | ||
| children: v | ||
| })] | ||
| }, k)) | ||
| }) | ||
| ] | ||
| }); | ||
| } | ||
@@ -165,3 +473,3 @@ //#endregion | ||
| */ | ||
| const DxfPreview = forwardRef(function DxfPreview({ src, srcUrl, options, className, style, onLoaded, onError, onViewer }, ref) { | ||
| const DxfPreview = forwardRef(function DxfPreview({ src, srcUrl, options, className, style, onLoaded, onError, onViewer, onHoverLayer }, ref) { | ||
| const containerRef = useRef(null); | ||
@@ -173,3 +481,4 @@ const [viewer, setViewer] = useState(null); | ||
| onError, | ||
| onViewer | ||
| onViewer, | ||
| onHoverLayer | ||
| }); | ||
@@ -179,3 +488,4 @@ callbacks.current = { | ||
| onError, | ||
| onViewer | ||
| onViewer, | ||
| onHoverLayer | ||
| }; | ||
@@ -213,2 +523,33 @@ const optionsKey = JSON.stringify(options ?? {}); | ||
| ]); | ||
| const hoverEnabled = onHoverLayer != null; | ||
| useEffect(() => { | ||
| const container = containerRef.current; | ||
| if (!viewer || !container || !hoverEnabled) return; | ||
| let queued = false; | ||
| let last = null; | ||
| const report = (layer) => { | ||
| if (layer === last) return; | ||
| last = layer; | ||
| viewer.setLayerHighlight(layer); | ||
| callbacks.current.onHoverLayer?.(layer); | ||
| }; | ||
| const onMove = (e) => { | ||
| if (e.pointerType !== "mouse" || e.buttons !== 0 || queued) return; | ||
| queued = true; | ||
| const { clientX, clientY } = e; | ||
| requestAnimationFrame(() => { | ||
| queued = false; | ||
| const rect = container.getBoundingClientRect(); | ||
| report(viewer.pickLayer(clientX - rect.left, clientY - rect.top)); | ||
| }); | ||
| }; | ||
| const onLeave = () => report(null); | ||
| container.addEventListener("pointermove", onMove); | ||
| container.addEventListener("pointerleave", onLeave); | ||
| return () => { | ||
| container.removeEventListener("pointermove", onMove); | ||
| container.removeEventListener("pointerleave", onLeave); | ||
| report(null); | ||
| }; | ||
| }, [viewer, hoverEnabled]); | ||
| useImperativeHandle(ref, () => viewer, [viewer]); | ||
@@ -240,4 +581,5 @@ return /* @__PURE__ */ jsx("div", { | ||
| */ | ||
| const DxfEmbed = forwardRef(function DxfEmbed({ panel = "left", panelClassName, panelStyle, theme = "aspicio", className, style, options, onViewer, ...previewProps }, ref) { | ||
| const DxfEmbed = forwardRef(function DxfEmbed({ panel = "left", panelClassName, panelStyle, theme = "aspicio", className, style, options, onViewer, onHoverLayer, ...previewProps }, ref) { | ||
| const [viewer, setViewer] = useState(null); | ||
| const [hoveredLayer, setHoveredLayer] = useState(null); | ||
| useImperativeHandle(ref, () => viewer, [viewer]); | ||
@@ -248,2 +590,3 @@ const themed = theme === "aspicio"; | ||
| theme, | ||
| reverseHighlightLayer: hoveredLayer, | ||
| className: panelClassName, | ||
@@ -253,4 +596,3 @@ style: { | ||
| flexShrink: 0, | ||
| overflowY: "auto", | ||
| ...themed && { [panel === "left" ? "borderRight" : "borderLeft"]: `1px solid ${aspicioTokens.hairline}` }, | ||
| ...themed ? { [panel === "left" ? "borderRight" : "borderLeft"]: `1px solid ${aspicioTokens.hairline}` } : { overflowY: "auto" }, | ||
| ...panelStyle | ||
@@ -290,2 +632,6 @@ } | ||
| }, | ||
| onHoverLayer: (layer) => { | ||
| if (panel !== "none") setHoveredLayer(layer); | ||
| onHoverLayer?.(layer); | ||
| }, | ||
| onViewer: (instance) => { | ||
@@ -292,0 +638,0 @@ setViewer(instance); |
+4
-3
| { | ||
| "name": "@aspicio/react", | ||
| "version": "0.1.0", | ||
| "version": "0.2.0", | ||
| "description": "React bindings for the Aspicio DXF viewer.", | ||
@@ -33,3 +33,3 @@ "homepage": "https://github.com/frontsail-ai/aspicio/tree/main/packages/react#readme", | ||
| "dependencies": { | ||
| "@aspicio/core": "^0.0.0" | ||
| "@aspicio/core": "^0.2.0" | ||
| }, | ||
@@ -48,4 +48,5 @@ "devDependencies": { | ||
| "peerDependencies": { | ||
| "react": "^18.0.0 || ^19.0.0" | ||
| "react": "^18.0.0 || ^19.0.0", | ||
| "three": ">=0.184.0 <1.0.0" | ||
| } | ||
| } |
+7
-4
@@ -7,3 +7,3 @@ # @aspicio/react | ||
| ```bash | ||
| npm install @aspicio/react # @aspicio/core comes along; react 18/19 is a peer | ||
| npm install @aspicio/react react three # @aspicio/core comes along; react 18/19 and three (>=0.184) are peers | ||
| ``` | ||
@@ -14,5 +14,8 @@ | ||
| - `<DxfPreview>` — the embeddable canvas alone: pan/zoom/rotate (mouse and | ||
| multi-touch), animated fit, batched WebGL rendering. No chrome. | ||
| - `<DxfLayerPanel>` — the ready-made layer list alone: visibility toggles, | ||
| effective-color swatches, entity counts, hover-to-highlight. | ||
| multi-touch), animated fit, batched WebGL rendering. No chrome. Pass | ||
| `onHoverLayer` to hit-test the layer under the cursor. | ||
| - `<DxfLayerPanel>` — the ready-made layer list, identical to the demo app: | ||
| header with layer count, visibility checkboxes, effective-color swatches, | ||
| entity counts, hover-to-highlight, double-click-to-solo (with a banner), | ||
| and a gesture-hints footer. `theme="none"` renders a minimal unstyled list. | ||
@@ -19,0 +22,0 @@ ## One component |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
27104
65.94%629
122.26%82
3.8%3
50%1
Infinity%+ Added
+ Added
+ Added
+ Added
Updated