@modernrelay/orbit-react 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.
Files changed (54) hide show
  1. package/LICENSE +21 -0
  2. package/dist/GraphProvider-B8ITDXS0.d.ts +11 -0
  3. package/dist/chunk-7XB47YST.js +69 -0
  4. package/dist/chunk-7XB47YST.js.map +1 -0
  5. package/dist/chunk-DGFLU2SN.js +108 -0
  6. package/dist/chunk-DGFLU2SN.js.map +1 -0
  7. package/dist/chunk-JN7QCMFW.js +93 -0
  8. package/dist/chunk-JN7QCMFW.js.map +1 -0
  9. package/dist/chunk-QJ7JXQVK.js +119 -0
  10. package/dist/chunk-QJ7JXQVK.js.map +1 -0
  11. package/dist/components/ContextMenu.d.ts +73 -0
  12. package/dist/components/ContextMenu.js +367 -0
  13. package/dist/components/ContextMenu.js.map +1 -0
  14. package/dist/components/Histogram.d.ts +33 -0
  15. package/dist/components/Histogram.js +216 -0
  16. package/dist/components/Histogram.js.map +1 -0
  17. package/dist/components/Inspector.d.ts +63 -0
  18. package/dist/components/Inspector.js +246 -0
  19. package/dist/components/Inspector.js.map +1 -0
  20. package/dist/components/Legend.d.ts +45 -0
  21. package/dist/components/Legend.js +185 -0
  22. package/dist/components/Legend.js.map +1 -0
  23. package/dist/components/Minimap.d.ts +63 -0
  24. package/dist/components/Minimap.js +222 -0
  25. package/dist/components/Minimap.js.map +1 -0
  26. package/dist/components/Navigator.d.ts +90 -0
  27. package/dist/components/Navigator.js +381 -0
  28. package/dist/components/Navigator.js.map +1 -0
  29. package/dist/components/Search.d.ts +70 -0
  30. package/dist/components/Search.js +203 -0
  31. package/dist/components/Search.js.map +1 -0
  32. package/dist/components/SelectionActions.d.ts +36 -0
  33. package/dist/components/SelectionActions.js +126 -0
  34. package/dist/components/SelectionActions.js.map +1 -0
  35. package/dist/components/SimControls.d.ts +124 -0
  36. package/dist/components/SimControls.js +221 -0
  37. package/dist/components/SimControls.js.map +1 -0
  38. package/dist/components/Table.d.ts +92 -0
  39. package/dist/components/Table.js +505 -0
  40. package/dist/components/Table.js.map +1 -0
  41. package/dist/components/Timeline.d.ts +34 -0
  42. package/dist/components/Timeline.js +182 -0
  43. package/dist/components/Timeline.js.map +1 -0
  44. package/dist/components/Toolbar.d.ts +60 -0
  45. package/dist/components/Toolbar.js +220 -0
  46. package/dist/components/Toolbar.js.map +1 -0
  47. package/dist/components/Tooltip.d.ts +65 -0
  48. package/dist/components/Tooltip.js +178 -0
  49. package/dist/components/Tooltip.js.map +1 -0
  50. package/dist/index.d.ts +488 -0
  51. package/dist/index.js +827 -0
  52. package/dist/index.js.map +1 -0
  53. package/dist/shared-Bj3Gk219.d.ts +43 -0
  54. package/package.json +99 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/Histogram/index.tsx"],"names":[],"mappings":";;;;;AAoCA,IAAM,UAAA,GAA4B;AAAA,EAChC,OAAA,EAAS,MAAA;AAAA,EACT,aAAA,EAAe,QAAA;AAAA,EACf,GAAA,EAAK,CAAA;AAAA,EACL,OAAA,EAAS,CAAA;AAAA,EACT,YAAA,EAAc,CAAA;AAAA,EACd,UAAA,EAAY,wBAAA;AAAA,EACZ,MAAA,EAAQ,qCAAA;AAAA,EACR,KAAA,EAAO,SAAA;AAAA,EACP,IAAA,EAAM;AACR,CAAA;AAEA,IAAM,WAAA,GAA6B,EAAE,OAAA,EAAS,IAAA,EAAK;AAEnD,IAAM,UAAA,GAA4B;AAAA,EAChC,QAAA,EAAU,UAAA;AAAA,EACV,MAAA,EAAQ,WAAA;AAAA,EACR,WAAA,EAAa,MAAA;AAAA,EACb,UAAA,EAAY;AACd,CAAA;AAEA,IAAM,SAAA,GAA2B,EAAE,OAAA,EAAS,OAAA,EAAS,OAAO,MAAA,EAAO;AAEnE,IAAM,kBAAA,GAAoC;AAAA,EACxC,UAAA,EAAY,MAAA;AAAA,EACZ,OAAA,EAAS,MAAA;AAAA,EACT,cAAA,EAAgB,eAAA;AAAA,EAChB,GAAA,EAAK,CAAA;AAAA,EACL,UAAA,EAAY,aAAA;AAAA,EACZ,MAAA,EAAQ,MAAA;AAAA,EACR,YAAA,EAAc,CAAA;AAAA,EACd,KAAA,EAAO,SAAA;AAAA,EACP,MAAA,EAAQ,SAAA;AAAA,EACR,IAAA,EAAM,SAAA;AAAA,EACN,OAAA,EAAS,SAAA;AAAA,EACT,SAAA,EAAW;AACb,CAAA;AAEA,IAAM,kBAAA,GAAoC;AAAA,EACxC,GAAG,kBAAA;AAAA,EACH,OAAA,EAAS,IAAA;AAAA,EACT,cAAA,EAAgB;AAClB,CAAA;AAEA,IAAM,UAAA,GAAa,2BAAA;AACnB,IAAM,aAAA,GAAgB,SAAA;AACtB,IAAM,UAAA,GAAa,2BAAA;AACnB,IAAM,YAAA,GAAe,2BAAA;AAEd,SAAS,eAAe,KAAA,EAA0C;AACvE,EAAA,MAAM,QAAA,GAAW,mBAAA,CAAoB,KAAA,CAAM,QAAA,EAAU,kBAAkB,CAAA;AACvE,EAAA,MAAM,EAAE,OAAA,EAAS,KAAA,KAAU,uBAAA,CAAwB,QAAA,EAAU,MAAM,SAAS,CAAA;AAC5E,EAAA,MAAM,QAAA,GAAW,kBAAA,CAAmB,QAAA,EAAU,KAAA,CAAM,SAAS,CAAA;AAE7D,EAAA,MAAM,MAAA,GAAS,SAAS,MAAA,IAAU,IAAA;AAClC,EAAA,MAAM,WAAW,aAAA,CAAc;AAAA,IAC7B,MAAA;AAAA,IACA,QAAA,EAAU,OAAA,EAAS,IAAA,CAAK,MAAA,IAAU,CAAA;AAAA,IAClC,KAAA;AAAA,IACA;AAAA,GACD,CAAA;AAED,EAAA,MAAM,MAAA,GAAS,MAAM,SAAA,KAAc,MAAA;AACnC,EAAA,MAAM,MAAA,GAAS,MAAM,MAAA,IAAU,EAAA;AAC/B,EAAA,MAAM,WAAA,GAAc,OAAA,KAAY,IAAA,IAAQ,OAAA,CAAQ,IAAA,KAAS,aAAA;AACzD,EAAA,MAAM,KAAA,GAAQ,aAAA,CAAc,KAAA,EAAO,MAAM,CAAA;AAEzC,EAAA,MAAM,cAAA,GAAiB,CAAC,GAAA,EAAkB,QAAA,KAAmC;AAC3E,IAAA,MAAM,GAAA,GAAM,WAAW,CAAA,GAAI,IAAA,CAAK,MAAO,GAAA,CAAI,QAAA,GAAW,QAAA,GAAY,GAAG,CAAA,GAAI,CAAA;AACzE,IAAA,uBACE,IAAA;AAAA,MAAC,QAAA;AAAA,MAAA;AAAA,QAEC,IAAA,EAAK,QAAA;AAAA,QACL,iCAA+B,GAAA,CAAI,GAAA;AAAA,QACnC,gBAAc,GAAA,CAAI,QAAA;AAAA,QAClB,YAAA,EAAY,CAAA,gBAAA,EAAmB,GAAA,CAAI,GAAG,CAAA,CAAA;AAAA,QACtC,WAAW,KAAA,CAAM,iBAAA;AAAA,QACjB,OACE,KAAA,CAAM,iBAAA,KAAsB,SACxB,MAAA,GACA,GAAA,CAAI,WACF,kBAAA,GACA,kBAAA;AAAA,QAER,SAAS,MAAM;AACb,UAAA,QAAA,CAAS,uBAAA,CAAwB,KAAA,EAAO,GAAA,CAAI,GAAG,CAAC,CAAA;AAAA,QAClD,CAAA;AAAA,QAEA,QAAA,EAAA;AAAA,0BAAA,GAAA,CAAC,MAAA,EAAA,EAAK,qCAAA,EAAoC,EAAA,EAAI,QAAA,EAAA,GAAA,CAAI,GAAA,EAAI,CAAA;AAAA,0BACtD,IAAA;AAAA,YAAC,MAAA;AAAA,YAAA;AAAA,cACC,qCAAA,EAAoC,EAAA;AAAA,cACpC,aAAA,EAAY,MAAA;AAAA,cACZ,KAAA,EACE,KAAA,CAAM,iBAAA,KAAsB,MAAA,GACxB,MAAA,GACA;AAAA,gBACE,YAAY,CAAA,0BAAA,EAA6B,aAAa,CAAA,GAAA,EAAM,GAAG,kBAAkB,GAAG,CAAA,EAAA,CAAA;AAAA,gBACpF,YAAA,EAAc,CAAA;AAAA,gBACd,OAAA,EAAS;AAAA,eACX;AAAA,cAGL,QAAA,EAAA;AAAA,gBAAA,GAAA,CAAI,QAAA;AAAA,gBAAS,GAAA;AAAA,gBAAE,GAAA,CAAI;AAAA;AAAA;AAAA;AACtB;AAAA,OAAA;AAAA,MAhCK,GAAA,CAAI;AAAA,KAiCX;AAAA,EAEJ,CAAA;AAEA,EAAA,IAAI,IAAA;AACJ,EAAA,IAAI,YAAY,IAAA,EAAM;AACpB,IAAA,IAAA,mBACE,GAAA,CAAC,SAAI,4BAAA,EAA2B,EAAA,EAAG,OAAO,MAAA,GAAS,WAAA,GAAc,QAAW,QAAA,EAAA,SAAA,EAE5E,CAAA;AAAA,EAEJ,WAAW,WAAA,EAAa;AACtB,IAAA,MAAM,QAAA,GAAW,OAAA,CAAQ,UAAA,CAAW,MAAA,CAAO,CAAC,CAAA,EAAG,CAAA,KAAO,CAAA,CAAE,KAAA,GAAQ,CAAA,GAAI,CAAA,CAAE,KAAA,GAAQ,GAAI,CAAC,CAAA;AACnF,IAAA,IAAA,mBACE,GAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,iCAAA,EAAgC,EAAA;AAAA,QAChC,OAAO,MAAA,GAAS,EAAE,SAAS,MAAA,EAAQ,aAAA,EAAe,UAAS,GAAI,MAAA;AAAA,QAE9D,QAAA,EAAA,OAAA,CAAQ,WAAW,GAAA,CAAI,CAAC,QAAQ,cAAA,CAAe,GAAA,EAAK,QAAQ,CAAC;AAAA;AAAA,KAChE;AAAA,EAEJ,CAAA,MAAO;AACL,IAAA,MAAM,OAAO,OAAA,CAAQ,IAAA;AACrB,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,MAAA,CAAO,CAAC,CAAA,EAAG,CAAA,KAAO,CAAA,CAAE,KAAA,GAAQ,CAAA,GAAI,CAAA,CAAE,KAAA,GAAQ,CAAA,EAAI,CAAC,CAAA;AACrE,IAAA,MAAM,OAAO,IAAA,CAAK,MAAA,GAAS,CAAA,GAAI,GAAA,GAAM,KAAK,MAAA,GAAS,GAAA;AACnD,IAAA,IAAA,mBACE,GAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,2BAAA,EAA0B,EAAA;AAAA,QAC1B,QAAA,EAAU,CAAA;AAAA,QACV,YAAA,EAAY,CAAA,EAAG,KAAA,CAAM,SAAS,CAAA,uDAAA,CAAA;AAAA,QAC9B,IAAA,EAAK,QAAA;AAAA,QACL,kBAAA,EAAiB,YAAA;AAAA,QACjB,eAAA,EAAe,OAAA,CAAQ,MAAA,EAAQ,GAAA,IAAO,CAAA;AAAA,QACtC,eAAA,EAAe,OAAA,CAAQ,MAAA,EAAQ,GAAA,IAAO,CAAA;AAAA,QACtC,eAAA,EAAe,cAAA,CAAe,KAAA,EAAO,OAAA,CAAQ,MAAM,CAAA;AAAA,QACnD,gBAAA,EAAgB,eAAA,CAAgB,KAAA,EAAO,OAAA,CAAQ,MAAM,CAAA;AAAA,QACrD,KAAA,EAAO,SAAS,UAAA,GAAa,MAAA;AAAA,QAC7B,eAAe,QAAA,CAAS,aAAA;AAAA,QACxB,eAAe,QAAA,CAAS,aAAA;AAAA,QACxB,aAAa,QAAA,CAAS,WAAA;AAAA,QACtB,iBAAiB,QAAA,CAAS,eAAA;AAAA,QAC1B,eAAe,QAAA,CAAS,aAAA;AAAA,QACxB,WAAW,QAAA,CAAS,SAAA;AAAA,QAEpB,QAAA,kBAAA,IAAA;AAAA,UAAC,KAAA;AAAA,UAAA;AAAA,YACC,KAAA,EAAM,MAAA;AAAA,YACN,MAAA;AAAA,YACA,OAAA,EAAQ,aAAA;AAAA,YACR,mBAAA,EAAoB,MAAA;AAAA,YACpB,aAAA,EAAY,MAAA;AAAA,YACZ,SAAA,EAAU,OAAA;AAAA,YACV,KAAA,EAAO,SAAS,SAAA,GAAY,MAAA;AAAA,YAE3B,QAAA,EAAA;AAAA,cAAA,IAAA,CAAK,GAAA,CAAI,CAAC,GAAA,EAAK,CAAA,KAAM;AACpB,gBAAA,MAAM,CAAA,GAAI,CAAA,GAAI,IAAA,GAAO,IAAA,GAAO,IAAA;AAC5B,gBAAA,MAAM,IAAI,IAAA,GAAO,GAAA;AACjB,gBAAA,MAAM,SAAS,QAAA,GAAW,CAAA,GAAK,GAAA,CAAI,KAAA,GAAQ,WAAY,GAAA,GAAM,CAAA;AAC7D,gBAAA,MAAM,YAAY,QAAA,GAAW,CAAA,GAAK,GAAA,CAAI,QAAA,GAAW,WAAY,GAAA,GAAM,CAAA;AACnE,gBAAA,4BACG,GAAA,EAAA,EACC,QAAA,EAAA;AAAA,kCAAA,GAAA;AAAA,oBAAC,MAAA;AAAA,oBAAA;AAAA,sBACC,gCAAA,EAA+B,EAAA;AAAA,sBAC/B,CAAA;AAAA,sBACA,GAAG,GAAA,GAAM,MAAA;AAAA,sBACT,KAAA,EAAO,CAAA;AAAA,sBACP,MAAA,EAAQ,MAAA;AAAA,sBACR,IAAA,EAAM;AAAA;AAAA,mBACR;AAAA,kCACA,GAAA;AAAA,oBAAC,MAAA;AAAA,oBAAA;AAAA,sBACC,mCAAA,EAAkC,EAAA;AAAA,sBAClC,CAAA;AAAA,sBACA,GAAG,GAAA,GAAM,SAAA;AAAA,sBACT,KAAA,EAAO,CAAA;AAAA,sBACP,MAAA,EAAQ,SAAA;AAAA,sBACR,IAAA,EAAM;AAAA;AAAA;AACR,iBAAA,EAAA,EAhBM,CAiBR,CAAA;AAAA,cAEJ,CAAC,CAAA;AAAA,cACA,UAAU,IAAA,mBACT,GAAA;AAAA,gBAAC,MAAA;AAAA,gBAAA;AAAA,kBACC,4BAAA,EAA2B,EAAA;AAAA,kBAC3B,GAAG,KAAA,CAAM,OAAA;AAAA,kBACT,CAAA,EAAG,CAAA;AAAA,kBACH,OAAO,KAAA,CAAM,QAAA;AAAA,kBACb,MAAA,EAAQ,GAAA;AAAA,kBACR,IAAA,EAAM,UAAA;AAAA,kBACN,MAAA,EAAQ,YAAA;AAAA,kBACR,WAAA,EAAa,GAAA;AAAA,kBACb,aAAA,EAAc;AAAA;AAAA,eAChB,GACE;AAAA;AAAA;AAAA;AACN;AAAA,KACF;AAAA,EAEJ;AAEA,EAAA,uBACE,IAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,wBAAsB,KAAA,CAAM,SAAA;AAAA,MAC5B,IAAA,EAAK,OAAA;AAAA,MACL,YAAA,EAAY,CAAA,EAAG,KAAA,CAAM,SAAS,CAAA,UAAA,CAAA;AAAA,MAC9B,WAAW,KAAA,CAAM,SAAA;AAAA,MACjB,KAAA,EAAO,SAAS,EAAE,GAAG,YAAY,GAAG,KAAA,CAAM,KAAA,EAAM,GAAI,KAAA,CAAM,KAAA;AAAA,MAE1D,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,KAAA,EAAA,EAAI,8BAA2B,EAAA,EAAG,KAAA,EAAO,SAAS,WAAA,GAAc,MAAA,EAC9D,gBAAM,SAAA,EACT,CAAA;AAAA,QACC;AAAA;AAAA;AAAA,GACH;AAEJ","file":"Histogram.js","sourcesContent":["/**\n * <GraphHistogram> — §16.6 crossfilter histogram (S9).\n *\n * SVG dual-layer bar chart over one crossfilter dimension: total bars\n * (muted) with the joint-filtered layer (accent) overlaid. Numeric/temporal\n * dimensions brush by pointer-drag over the plot (any drag REPLACES the\n * brush), double-click clears, ArrowLeft/ArrowRight nudge the window by one\n * bin, Escape clears (the plot is focusable). Categorical dimensions render\n * clickable rows toggling category exclusions. All text values (dimension\n * key, category keys) render as TEXT NODES. Headless-styleable: providing\n * `className` drops the built-in dark-theme inline styles.\n */\n\nimport type { CSSProperties, ReactElement } from 'react';\nimport type { CategoryBin } from '@modernrelay/orbit-core';\nimport type { AnyGraphInstance } from '../../GraphProvider';\nimport { useResolvedInstance } from '../shared';\nimport { brushRangePct, sliderValueNow, sliderValueText, toggleCategoryExclusion, useRangeBrush } from '../_shared/brush';\nimport { useCrossfilterDimension, useSessionSetBrush } from '../_shared/crossfilter';\n\nexport interface GraphHistogramProps {\n /** Crossfilter dimension key (must appear in the `crossfilter` prop). */\n dimension: string;\n /** Plot height in CSS px. Default 96. */\n height?: number;\n /** Explicit instance; defaults to the ambient <Graph>/<GraphProvider> one. */\n instance?: AnyGraphInstance;\n /** Class hook for the root; providing it drops the default styles. */\n className?: string;\n style?: CSSProperties;\n /** Class hook for each categorical row; providing it drops its defaults. */\n categoryClassName?: string;\n}\n\n// --- default dark-theme styling (dropped when `className` is provided) ---\n\nconst ROOT_STYLE: CSSProperties = {\n display: 'flex',\n flexDirection: 'column',\n gap: 4,\n padding: 8,\n borderRadius: 8,\n background: 'rgba(23, 25, 32, 0.92)',\n border: '1px solid rgba(255, 255, 255, 0.14)',\n color: '#e8eaf0',\n font: '12px/1.4 system-ui, sans-serif',\n};\n\nconst TITLE_STYLE: CSSProperties = { opacity: 0.75 };\n\nconst PLOT_STYLE: CSSProperties = {\n position: 'relative',\n cursor: 'crosshair',\n touchAction: 'none',\n userSelect: 'none',\n};\n\nconst SVG_STYLE: CSSProperties = { display: 'block', width: '100%' };\n\nconst CATEGORY_ROW_STYLE: CSSProperties = {\n appearance: 'none',\n display: 'flex',\n justifyContent: 'space-between',\n gap: 8,\n background: 'transparent',\n border: 'none',\n borderRadius: 4,\n color: 'inherit',\n cursor: 'pointer',\n font: 'inherit',\n padding: '2px 4px',\n textAlign: 'left',\n};\n\nconst EXCLUDED_ROW_STYLE: CSSProperties = {\n ...CATEGORY_ROW_STYLE,\n opacity: 0.45,\n textDecoration: 'line-through',\n};\n\nconst TOTAL_FILL = 'rgba(148, 163, 184, 0.35)';\nconst FILTERED_FILL = '#7aa2ff';\nconst BRUSH_FILL = 'rgba(122, 162, 255, 0.18)';\nconst BRUSH_STROKE = 'rgba(122, 162, 255, 0.85)';\n\nexport function GraphHistogram(props: GraphHistogramProps): ReactElement {\n const instance = useResolvedInstance(props.instance, '<GraphHistogram>');\n const { summary, brush } = useCrossfilterDimension(instance, props.dimension);\n const setBrush = useSessionSetBrush(instance, props.dimension);\n\n const domain = summary?.domain ?? null;\n const handlers = useRangeBrush({\n domain,\n binCount: summary?.bins.length ?? 0,\n brush,\n setBrush,\n });\n\n const styled = props.className === undefined;\n const height = props.height ?? 96;\n const categorical = summary !== null && summary.kind === 'categorical';\n const range = brushRangePct(brush, domain);\n\n const renderCategory = (cat: CategoryBin, maxTotal: number): ReactElement => {\n const pct = maxTotal > 0 ? Math.round((cat.filtered / maxTotal) * 100) : 0;\n return (\n <button\n key={cat.key}\n type=\"button\"\n data-orbit-histogram-category={cat.key}\n aria-pressed={cat.excluded}\n aria-label={`Toggle category ${cat.key}`}\n className={props.categoryClassName}\n style={\n props.categoryClassName !== undefined\n ? undefined\n : cat.excluded\n ? EXCLUDED_ROW_STYLE\n : CATEGORY_ROW_STYLE\n }\n onClick={() => {\n setBrush(toggleCategoryExclusion(brush, cat.key));\n }}\n >\n <span data-orbit-histogram-category-label=\"\">{cat.key}</span>\n <span\n data-orbit-histogram-category-count=\"\"\n aria-hidden=\"true\"\n style={\n props.categoryClassName !== undefined\n ? undefined\n : {\n background: `linear-gradient(to right, ${FILTERED_FILL}33 ${pct}%, transparent ${pct}%)`,\n borderRadius: 2,\n padding: '0 4px',\n }\n }\n >\n {cat.filtered}/{cat.total}\n </span>\n </button>\n );\n };\n\n let body: ReactElement;\n if (summary === null) {\n body = (\n <div data-orbit-histogram-empty=\"\" style={styled ? TITLE_STYLE : undefined}>\n No data\n </div>\n );\n } else if (categorical) {\n const maxTotal = summary.categories.reduce((m, c) => (c.total > m ? c.total : m), 0);\n body = (\n <div\n data-orbit-histogram-categories=\"\"\n style={styled ? { display: 'flex', flexDirection: 'column' } : undefined}\n >\n {summary.categories.map((cat) => renderCategory(cat, maxTotal))}\n </div>\n );\n } else {\n const bins = summary.bins;\n const maxTotal = bins.reduce((m, b) => (b.total > m ? b.total : m), 0);\n const barW = bins.length > 0 ? 100 / bins.length : 100;\n body = (\n <div\n data-orbit-histogram-plot=\"\"\n tabIndex={0}\n aria-label={`${props.dimension} brush: drag to filter, arrow keys nudge, Escape clears`}\n role=\"slider\"\n aria-orientation=\"horizontal\"\n aria-valuemin={summary.domain?.min ?? 0}\n aria-valuemax={summary.domain?.max ?? 0}\n aria-valuenow={sliderValueNow(brush, summary.domain)}\n aria-valuetext={sliderValueText(brush, summary.domain)}\n style={styled ? PLOT_STYLE : undefined}\n onPointerDown={handlers.onPointerDown}\n onPointerMove={handlers.onPointerMove}\n onPointerUp={handlers.onPointerUp}\n onPointerCancel={handlers.onPointerCancel}\n onDoubleClick={handlers.onDoubleClick}\n onKeyDown={handlers.onKeyDown}\n >\n <svg\n width=\"100%\"\n height={height}\n viewBox=\"0 0 100 100\"\n preserveAspectRatio=\"none\"\n aria-hidden=\"true\"\n focusable=\"false\"\n style={styled ? SVG_STYLE : undefined}\n >\n {bins.map((bin, i) => {\n const x = i * barW + barW * 0.05;\n const w = barW * 0.9;\n const totalH = maxTotal > 0 ? (bin.total / maxTotal) * 100 : 0;\n const filteredH = maxTotal > 0 ? (bin.filtered / maxTotal) * 100 : 0;\n return (\n <g key={i}>\n <rect\n data-orbit-histogram-bar-total=\"\"\n x={x}\n y={100 - totalH}\n width={w}\n height={totalH}\n fill={TOTAL_FILL}\n />\n <rect\n data-orbit-histogram-bar-filtered=\"\"\n x={x}\n y={100 - filteredH}\n width={w}\n height={filteredH}\n fill={FILTERED_FILL}\n />\n </g>\n );\n })}\n {range !== null ? (\n <rect\n data-orbit-histogram-brush=\"\"\n x={range.leftPct}\n y={0}\n width={range.widthPct}\n height={100}\n fill={BRUSH_FILL}\n stroke={BRUSH_STROKE}\n strokeWidth={0.5}\n pointerEvents=\"none\"\n />\n ) : null}\n </svg>\n </div>\n );\n }\n\n return (\n <div\n data-orbit-histogram={props.dimension}\n role=\"group\"\n aria-label={`${props.dimension} histogram`}\n className={props.className}\n style={styled ? { ...ROOT_STYLE, ...props.style } : props.style}\n >\n <div data-orbit-histogram-title=\"\" style={styled ? TITLE_STYLE : undefined}>\n {props.dimension}\n </div>\n {body}\n </div>\n );\n}\n"]}
@@ -0,0 +1,63 @@
1
+ import { ReactNode, CSSProperties, ReactElement } from 'react';
2
+ import { GraphNode } from '@modernrelay/orbit-core';
3
+ import { A as AnyGraphInstance } from '../GraphProvider-B8ITDXS0.js';
4
+
5
+ /**
6
+ * `@modernrelay/orbit-react/components/Inspector` — `<GraphInspector>`, the
7
+ * §16.9 docked detail panel (S11-T13).
8
+ *
9
+ * Subject: the SELECTED-SINGLE node (`store.selection.nodeIds.length === 1`)
10
+ * — the §16.2 node namespace is the focus proxy the store actually publishes.
11
+ * Anything else (empty or multi selection) renders the empty state.
12
+ *
13
+ * Sections:
14
+ * - **Attrs table** — every attr key/value as TEXT NODES (§14 untrusted
15
+ * content; hostile strings render literally); `renderAttrs` replaces it.
16
+ * - **Neighbor list** — click → `instance.focusNode(neighborId)` (camera
17
+ * fly, §16). v0.9 resolution compromise (documented): the public instance
18
+ * surface resolves a 1-hop neighborhood ONLY through `focusNode`'s return
19
+ * value (the same seam the §15.1 navigator uses on Enter) — a passive
20
+ * adjacency read is a post-v0.9 core surface. The section therefore
21
+ * populates from a per-model-revision cache learned by focus operations,
22
+ * with a 'Show neighbors' affordance that focuses the subject to resolve
23
+ * its neighborhood on demand (flying to the inspected node is coherent
24
+ * §15.1 behavior, and it is the user's explicit action). A model change
25
+ * drops the (now stale) cache.
26
+ * - **Quick actions** — wired to the same public mutators as the §16.4
27
+ * context menu: Expand (`expandNode`, busy while pending §9.2), Isolate
28
+ * (1-hop ego hard scope via the `subgraph` path), Pin/Unpin (store pin
29
+ * slice toggle), Hide (`hideNodes`). `quickActions` is a render-prop
30
+ * REPLACEMENT receiving the defaults, so extension is
31
+ * `[...defaultActions, mine]`.
32
+ */
33
+
34
+ interface GraphInspectorAction {
35
+ /** Stable key; default actions use 'expand' | 'isolate' | 'pin' | 'unpin'
36
+ * | 'hide'. */
37
+ id: string;
38
+ /** Rendered as a text node. */
39
+ label: string;
40
+ disabled?: boolean;
41
+ onSelect(): void;
42
+ }
43
+ interface GraphInspectorProps {
44
+ /** Explicit instance (multi-instance pages); ambient context otherwise. */
45
+ instance?: AnyGraphInstance;
46
+ /** Docking side. Default 'right'. */
47
+ dock?: 'left' | 'right';
48
+ /** Replaces the default attrs table. */
49
+ renderAttrs?: (node: GraphNode<any>) => ReactNode;
50
+ /** Render-prop REPLACEMENT for the quick-action row; receives the default
51
+ * actions so extension is `[...defaultActions, mine]`. */
52
+ quickActions?: (ctx: {
53
+ node: GraphNode<any>;
54
+ defaultActions: readonly GraphInspectorAction[];
55
+ }) => readonly GraphInspectorAction[];
56
+ /** Accessible name of the panel. Default 'Graph inspector'. */
57
+ label?: string;
58
+ className?: string;
59
+ style?: CSSProperties;
60
+ }
61
+ declare function GraphInspector(props: GraphInspectorProps): ReactElement;
62
+
63
+ export { GraphInspector, type GraphInspectorAction, type GraphInspectorProps };
@@ -0,0 +1,246 @@
1
+ import { useResolvedInstance } from '../chunk-7XB47YST.js';
2
+ import { useRef, useState, useCallback, useSyncExternalStore } from 'react';
3
+ import { jsx, jsxs } from 'react/jsx-runtime';
4
+
5
+ function useInspectorSlice(instance) {
6
+ const cacheRef = useRef(null);
7
+ const subscribe = useCallback(
8
+ (onChange) => instance.store.subscribe(onChange),
9
+ [instance]
10
+ );
11
+ const getSnapshot = useCallback(() => {
12
+ const s = instance.store.getState();
13
+ const selection = s.selection.nodeIds;
14
+ const id = selection.length === 1 ? selection[0] : null;
15
+ const next = {
16
+ selection,
17
+ pinned: id !== null && s.pins.has(id),
18
+ hidden: id !== null && s.hiddenNodeIds.has(id),
19
+ expanding: id !== null && s.pendingExpansions.has(id),
20
+ model: s.revisions.model
21
+ };
22
+ const prev = cacheRef.current;
23
+ if (prev !== null && prev.selection === next.selection && prev.pinned === next.pinned && prev.hidden === next.hidden && prev.expanding === next.expanding && prev.model === next.model) {
24
+ return prev;
25
+ }
26
+ cacheRef.current = next;
27
+ return next;
28
+ }, [instance]);
29
+ return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
30
+ }
31
+ function nodeLabelText(node) {
32
+ const attrs = node.attrs;
33
+ const label = attrs?.["label"];
34
+ return label === void 0 || label === null ? String(node.id) : String(label);
35
+ }
36
+ function attrValueText(value) {
37
+ if (typeof value === "string") return value;
38
+ if (typeof value === "object" && value !== null) {
39
+ try {
40
+ return JSON.stringify(value);
41
+ } catch {
42
+ return String(value);
43
+ }
44
+ }
45
+ return String(value);
46
+ }
47
+ var PANEL_STYLE = {
48
+ position: "absolute",
49
+ top: 12,
50
+ bottom: 12,
51
+ width: 280,
52
+ overflowY: "auto",
53
+ pointerEvents: "auto",
54
+ display: "flex",
55
+ flexDirection: "column",
56
+ gap: 8,
57
+ padding: 10,
58
+ borderRadius: 8,
59
+ background: "rgba(23, 25, 32, 0.96)",
60
+ border: "1px solid rgba(255, 255, 255, 0.14)",
61
+ color: "#e8eaf0",
62
+ font: "13px/1.4 system-ui, sans-serif"
63
+ };
64
+ var TITLE_STYLE = {
65
+ fontWeight: 600,
66
+ fontSize: 14,
67
+ overflow: "hidden",
68
+ textOverflow: "ellipsis",
69
+ whiteSpace: "nowrap"
70
+ };
71
+ var SECTION_LABEL_STYLE = { fontSize: 11, opacity: 0.65, marginTop: 4 };
72
+ var TABLE_STYLE = { borderCollapse: "collapse", width: "100%" };
73
+ var CELL_KEY_STYLE = {
74
+ textAlign: "left",
75
+ verticalAlign: "top",
76
+ padding: "2px 8px 2px 0",
77
+ opacity: 0.65,
78
+ fontWeight: 400,
79
+ whiteSpace: "nowrap"
80
+ };
81
+ var CELL_VALUE_STYLE = { padding: "2px 0", overflowWrap: "anywhere" };
82
+ var BUTTON_STYLE = {
83
+ appearance: "none",
84
+ display: "block",
85
+ width: "100%",
86
+ textAlign: "left",
87
+ background: "transparent",
88
+ border: "none",
89
+ borderRadius: 6,
90
+ color: "inherit",
91
+ cursor: "pointer",
92
+ font: "inherit",
93
+ padding: "4px 8px"
94
+ };
95
+ var ACTIONS_STYLE = { display: "flex", flexWrap: "wrap", gap: 4 };
96
+ var ACTION_BUTTON_STYLE = {
97
+ ...BUTTON_STYLE,
98
+ display: "inline-block",
99
+ width: "auto",
100
+ border: "1px solid rgba(255, 255, 255, 0.2)"
101
+ };
102
+ var EMPTY_STYLE = { fontStyle: "italic", opacity: 0.8 };
103
+ function GraphInspector(props) {
104
+ const instance = useResolvedInstance(props.instance, "<GraphInspector>");
105
+ const slice = useInspectorSlice(instance);
106
+ const dock = props.dock ?? "right";
107
+ const neighborsRef = useRef({
108
+ model: -1,
109
+ byId: /* @__PURE__ */ new Map()
110
+ });
111
+ const [, setLearnTick] = useState(0);
112
+ if (neighborsRef.current.model !== slice.model) {
113
+ neighborsRef.current = { model: slice.model, byId: /* @__PURE__ */ new Map() };
114
+ }
115
+ const subjectId = slice.selection.length === 1 ? slice.selection[0] : null;
116
+ const subject = subjectId === null ? void 0 : instance.getNode(subjectId);
117
+ const focusAndLearn = (id) => {
118
+ const neighbors2 = instance.focusNode(id);
119
+ neighborsRef.current.byId.set(id, neighbors2);
120
+ setLearnTick((t) => t + 1);
121
+ };
122
+ const panelLabel = props.label ?? "Graph inspector";
123
+ const dockStyle = dock === "left" ? { left: 12 } : { right: 12 };
124
+ if (subjectId === null || subject === void 0) {
125
+ return /* @__PURE__ */ jsx(
126
+ "div",
127
+ {
128
+ "data-orbit-inspector": "",
129
+ role: "complementary",
130
+ "aria-label": panelLabel,
131
+ className: props.className,
132
+ style: { ...PANEL_STYLE, ...dockStyle, ...props.style },
133
+ children: /* @__PURE__ */ jsx("div", { "data-orbit-inspector-empty": "", style: EMPTY_STYLE, children: slice.selection.length > 1 ? `${slice.selection.length} nodes selected \u2014 select a single node to inspect` : "Select a node to inspect" })
134
+ }
135
+ );
136
+ }
137
+ const attrs = Object.entries(subject.attrs ?? {});
138
+ const neighbors = neighborsRef.current.byId.get(subjectId);
139
+ const defaultActions = [
140
+ {
141
+ id: "expand",
142
+ label: slice.expanding ? "Expanding\u2026" : "Expand",
143
+ disabled: slice.expanding,
144
+ onSelect: () => {
145
+ void instance.expandNode(subjectId).catch(() => {
146
+ });
147
+ }
148
+ },
149
+ {
150
+ id: "isolate",
151
+ label: "Isolate",
152
+ onSelect: () => {
153
+ instance.applyHostUpdate({ subgraph: { seedIds: [subjectId], hops: 1 } });
154
+ }
155
+ },
156
+ slice.pinned ? {
157
+ id: "unpin",
158
+ label: "Unpin",
159
+ onSelect: () => {
160
+ instance.unpinNode(subjectId);
161
+ }
162
+ } : {
163
+ id: "pin",
164
+ label: "Pin",
165
+ onSelect: () => {
166
+ instance.pinNode(subjectId);
167
+ }
168
+ },
169
+ {
170
+ id: "hide",
171
+ label: "Hide",
172
+ onSelect: () => {
173
+ instance.hideNodes([subjectId]);
174
+ }
175
+ }
176
+ ];
177
+ const actions = props.quickActions !== void 0 ? props.quickActions({ node: subject, defaultActions }) : defaultActions;
178
+ const stateBadges = [
179
+ ...slice.pinned ? ["pinned"] : [],
180
+ ...slice.hidden ? ["hidden"] : []
181
+ ].join(" \xB7 ");
182
+ return /* @__PURE__ */ jsxs(
183
+ "div",
184
+ {
185
+ "data-orbit-inspector": "",
186
+ role: "complementary",
187
+ "aria-label": panelLabel,
188
+ className: props.className,
189
+ style: { ...PANEL_STYLE, ...dockStyle, ...props.style },
190
+ children: [
191
+ /* @__PURE__ */ jsx("div", { "data-orbit-inspector-title": "", style: TITLE_STYLE, children: nodeLabelText(subject) }),
192
+ stateBadges !== "" ? /* @__PURE__ */ jsx("div", { "data-orbit-inspector-state": "", style: SECTION_LABEL_STYLE, children: stateBadges }) : null,
193
+ /* @__PURE__ */ jsx("div", { "data-orbit-inspector-actions": "", style: ACTIONS_STYLE, children: actions.map((action) => /* @__PURE__ */ jsx(
194
+ "button",
195
+ {
196
+ type: "button",
197
+ "data-orbit-inspector-action": action.id,
198
+ disabled: action.disabled === true,
199
+ style: ACTION_BUTTON_STYLE,
200
+ onClick: () => {
201
+ if (action.disabled !== true) action.onSelect();
202
+ },
203
+ children: action.label
204
+ },
205
+ action.id
206
+ )) }),
207
+ /* @__PURE__ */ jsx("div", { style: SECTION_LABEL_STYLE, children: "Attributes" }),
208
+ props.renderAttrs !== void 0 ? props.renderAttrs(subject) : attrs.length === 0 ? /* @__PURE__ */ jsx("div", { "data-orbit-inspector-no-attrs": "", style: EMPTY_STYLE, children: "No attributes" }) : /* @__PURE__ */ jsx("table", { "data-orbit-inspector-attrs": "", style: TABLE_STYLE, children: /* @__PURE__ */ jsx("tbody", { children: attrs.map(([key, value]) => /* @__PURE__ */ jsxs("tr", { "data-orbit-inspector-attr": key, children: [
209
+ /* @__PURE__ */ jsx("th", { scope: "row", style: CELL_KEY_STYLE, children: key }),
210
+ /* @__PURE__ */ jsx("td", { style: CELL_VALUE_STYLE, children: attrValueText(value) })
211
+ ] }, key)) }) }),
212
+ /* @__PURE__ */ jsx("div", { style: SECTION_LABEL_STYLE, children: "Neighbors" }),
213
+ neighbors === void 0 ? /* @__PURE__ */ jsx(
214
+ "button",
215
+ {
216
+ type: "button",
217
+ "data-orbit-inspector-resolve-neighbors": "",
218
+ style: BUTTON_STYLE,
219
+ onClick: () => {
220
+ focusAndLearn(subjectId);
221
+ },
222
+ children: "Show neighbors (focuses the node)"
223
+ }
224
+ ) : neighbors.length === 0 ? /* @__PURE__ */ jsx("div", { "data-orbit-inspector-no-neighbors": "", style: EMPTY_STYLE, children: "No neighbors" }) : /* @__PURE__ */ jsx("ul", { "data-orbit-inspector-neighbors": "", style: { margin: 0, padding: 0, listStyle: "none" }, children: neighbors.map((id) => {
225
+ const neighbor = instance.getNode(id);
226
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
227
+ "button",
228
+ {
229
+ type: "button",
230
+ "data-orbit-inspector-neighbor": id,
231
+ style: BUTTON_STYLE,
232
+ onClick: () => {
233
+ focusAndLearn(id);
234
+ },
235
+ children: neighbor === void 0 ? id : nodeLabelText(neighbor)
236
+ }
237
+ ) }, id);
238
+ }) })
239
+ ]
240
+ }
241
+ );
242
+ }
243
+
244
+ export { GraphInspector };
245
+ //# sourceMappingURL=Inspector.js.map
246
+ //# sourceMappingURL=Inspector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/Inspector/index.tsx"],"names":["neighbors"],"mappings":"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","file":"Inspector.js","sourcesContent":["/**\n * `@modernrelay/orbit-react/components/Inspector` — `<GraphInspector>`, the\n * §16.9 docked detail panel (S11-T13).\n *\n * Subject: the SELECTED-SINGLE node (`store.selection.nodeIds.length === 1`)\n * — the §16.2 node namespace is the focus proxy the store actually publishes.\n * Anything else (empty or multi selection) renders the empty state.\n *\n * Sections:\n * - **Attrs table** — every attr key/value as TEXT NODES (§14 untrusted\n * content; hostile strings render literally); `renderAttrs` replaces it.\n * - **Neighbor list** — click → `instance.focusNode(neighborId)` (camera\n * fly, §16). v0.9 resolution compromise (documented): the public instance\n * surface resolves a 1-hop neighborhood ONLY through `focusNode`'s return\n * value (the same seam the §15.1 navigator uses on Enter) — a passive\n * adjacency read is a post-v0.9 core surface. The section therefore\n * populates from a per-model-revision cache learned by focus operations,\n * with a 'Show neighbors' affordance that focuses the subject to resolve\n * its neighborhood on demand (flying to the inspected node is coherent\n * §15.1 behavior, and it is the user's explicit action). A model change\n * drops the (now stale) cache.\n * - **Quick actions** — wired to the same public mutators as the §16.4\n * context menu: Expand (`expandNode`, busy while pending §9.2), Isolate\n * (1-hop ego hard scope via the `subgraph` path), Pin/Unpin (store pin\n * slice toggle), Hide (`hideNodes`). `quickActions` is a render-prop\n * REPLACEMENT receiving the defaults, so extension is\n * `[...defaultActions, mine]`.\n */\n\nimport { useCallback, useRef, useState, useSyncExternalStore } from 'react';\nimport type { CSSProperties, ReactElement, ReactNode } from 'react';\nimport type { GraphNode, GraphStoreState, NodeId } from '@modernrelay/orbit-core';\nimport type { AnyGraphInstance } from '../../GraphProvider';\nimport { useResolvedInstance } from '../shared';\n\nexport interface GraphInspectorAction {\n /** Stable key; default actions use 'expand' | 'isolate' | 'pin' | 'unpin'\n * | 'hide'. */\n id: string;\n /** Rendered as a text node. */\n label: string;\n disabled?: boolean;\n onSelect(): void;\n}\n\nexport interface GraphInspectorProps {\n /** Explicit instance (multi-instance pages); ambient context otherwise. */\n instance?: AnyGraphInstance;\n /** Docking side. Default 'right'. */\n dock?: 'left' | 'right';\n /** Replaces the default attrs table. */\n renderAttrs?: (node: GraphNode<any>) => ReactNode;\n /** Render-prop REPLACEMENT for the quick-action row; receives the default\n * actions so extension is `[...defaultActions, mine]`. */\n quickActions?: (ctx: {\n node: GraphNode<any>;\n defaultActions: readonly GraphInspectorAction[];\n }) => readonly GraphInspectorAction[];\n /** Accessible name of the panel. Default 'Graph inspector'. */\n label?: string;\n className?: string;\n style?: CSSProperties;\n}\n\ninterface InspectorSlice {\n selection: readonly NodeId[];\n pinned: boolean;\n hidden: boolean;\n expanding: boolean;\n model: number;\n}\n\n/** Narrow store subscription: subject id + the flags the panel displays. */\nfunction useInspectorSlice(instance: AnyGraphInstance): InspectorSlice {\n const cacheRef = useRef<InspectorSlice | null>(null);\n const subscribe = useCallback(\n (onChange: () => void) => instance.store.subscribe(onChange),\n [instance],\n );\n const getSnapshot = useCallback((): InspectorSlice => {\n const s: GraphStoreState = instance.store.getState();\n const selection = s.selection.nodeIds;\n const id = selection.length === 1 ? selection[0]! : null;\n const next: InspectorSlice = {\n selection,\n pinned: id !== null && s.pins.has(id),\n hidden: id !== null && s.hiddenNodeIds.has(id),\n expanding: id !== null && s.pendingExpansions.has(id),\n model: s.revisions.model,\n };\n const prev = cacheRef.current;\n if (\n prev !== null &&\n prev.selection === next.selection &&\n prev.pinned === next.pinned &&\n prev.hidden === next.hidden &&\n prev.expanding === next.expanding &&\n prev.model === next.model\n ) {\n return prev;\n }\n cacheRef.current = next;\n return next;\n }, [instance]);\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n\n/** Same derivation as the core §14 label default: `attrs.label ?? id`. */\nfunction nodeLabelText(node: GraphNode<any>): string {\n const attrs = node.attrs as Record<string, unknown> | undefined;\n const label = attrs?.['label'];\n return label === undefined || label === null ? String(node.id) : String(label);\n}\n\n/** Attr value → display TEXT (never markup): strings verbatim, structured\n * values JSON (guarded — cycles degrade to the String form). */\nfunction attrValueText(value: unknown): string {\n if (typeof value === 'string') return value;\n if (typeof value === 'object' && value !== null) {\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n }\n return String(value);\n}\n\n// --- styling defaults (headless-styleable: className/style override) ---\nconst PANEL_STYLE: CSSProperties = {\n position: 'absolute',\n top: 12,\n bottom: 12,\n width: 280,\n overflowY: 'auto',\n pointerEvents: 'auto',\n display: 'flex',\n flexDirection: 'column',\n gap: 8,\n padding: 10,\n borderRadius: 8,\n background: 'rgba(23, 25, 32, 0.96)',\n border: '1px solid rgba(255, 255, 255, 0.14)',\n color: '#e8eaf0',\n font: '13px/1.4 system-ui, sans-serif',\n};\nconst TITLE_STYLE: CSSProperties = {\n fontWeight: 600,\n fontSize: 14,\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n};\nconst SECTION_LABEL_STYLE: CSSProperties = { fontSize: 11, opacity: 0.65, marginTop: 4 };\nconst TABLE_STYLE: CSSProperties = { borderCollapse: 'collapse', width: '100%' };\nconst CELL_KEY_STYLE: CSSProperties = {\n textAlign: 'left',\n verticalAlign: 'top',\n padding: '2px 8px 2px 0',\n opacity: 0.65,\n fontWeight: 400,\n whiteSpace: 'nowrap',\n};\nconst CELL_VALUE_STYLE: CSSProperties = { padding: '2px 0', overflowWrap: 'anywhere' };\nconst BUTTON_STYLE: CSSProperties = {\n appearance: 'none',\n display: 'block',\n width: '100%',\n textAlign: 'left',\n background: 'transparent',\n border: 'none',\n borderRadius: 6,\n color: 'inherit',\n cursor: 'pointer',\n font: 'inherit',\n padding: '4px 8px',\n};\nconst ACTIONS_STYLE: CSSProperties = { display: 'flex', flexWrap: 'wrap', gap: 4 };\nconst ACTION_BUTTON_STYLE: CSSProperties = {\n ...BUTTON_STYLE,\n display: 'inline-block',\n width: 'auto',\n border: '1px solid rgba(255, 255, 255, 0.2)',\n};\nconst EMPTY_STYLE: CSSProperties = { fontStyle: 'italic', opacity: 0.8 };\n\nexport function GraphInspector(props: GraphInspectorProps): ReactElement {\n const instance = useResolvedInstance(props.instance, '<GraphInspector>');\n const slice = useInspectorSlice(instance);\n const dock = props.dock ?? 'right';\n\n /** Per-model neighbor cache learned from focus operations (module JSDoc). */\n const neighborsRef = useRef<{ model: number; byId: Map<NodeId, readonly NodeId[]> }>({\n model: -1,\n byId: new Map(),\n });\n /** Bump to re-render after imperative cache learns (focus clicks). */\n const [, setLearnTick] = useState(0);\n\n // Idempotent render-time cache reset (ref only — safe under StrictMode).\n if (neighborsRef.current.model !== slice.model) {\n neighborsRef.current = { model: slice.model, byId: new Map() };\n }\n\n const subjectId = slice.selection.length === 1 ? slice.selection[0]! : null;\n const subject =\n subjectId === null ? undefined : (instance.getNode(subjectId) as GraphNode<any> | undefined);\n\n /** Focus + learn: the single v0.9 neighborhood-resolution seam. */\n const focusAndLearn = (id: NodeId): void => {\n const neighbors = instance.focusNode(id);\n neighborsRef.current.byId.set(id, neighbors);\n setLearnTick((t) => t + 1);\n };\n\n const panelLabel = props.label ?? 'Graph inspector';\n const dockStyle: CSSProperties = dock === 'left' ? { left: 12 } : { right: 12 };\n\n if (subjectId === null || subject === undefined) {\n return (\n <div\n data-orbit-inspector=\"\"\n role=\"complementary\"\n aria-label={panelLabel}\n className={props.className}\n style={{ ...PANEL_STYLE, ...dockStyle, ...props.style }}\n >\n <div data-orbit-inspector-empty=\"\" style={EMPTY_STYLE}>\n {slice.selection.length > 1\n ? `${slice.selection.length} nodes selected — select a single node to inspect`\n : 'Select a node to inspect'}\n </div>\n </div>\n );\n }\n\n const attrs = Object.entries((subject.attrs ?? {}) as Record<string, unknown>);\n const neighbors = neighborsRef.current.byId.get(subjectId);\n\n const defaultActions: GraphInspectorAction[] = [\n {\n id: 'expand',\n label: slice.expanding ? 'Expanding…' : 'Expand',\n disabled: slice.expanding,\n onSelect: () => {\n // A discard/collapse rejects the promise by design (§9.2); the panel\n // fires-and-forgets — diagnostics carry the why.\n void instance.expandNode(subjectId).catch(() => {});\n },\n },\n {\n id: 'isolate',\n label: 'Isolate',\n onSelect: () => {\n // §9.2 1-hop ego hard scope — the same path as the §16.4 menu item.\n instance.applyHostUpdate({ subgraph: { seedIds: [subjectId], hops: 1 } });\n },\n },\n slice.pinned\n ? {\n id: 'unpin',\n label: 'Unpin',\n onSelect: () => {\n instance.unpinNode(subjectId);\n },\n }\n : {\n id: 'pin',\n label: 'Pin',\n onSelect: () => {\n instance.pinNode(subjectId);\n },\n },\n {\n id: 'hide',\n label: 'Hide',\n onSelect: () => {\n instance.hideNodes([subjectId]);\n },\n },\n ];\n const actions =\n props.quickActions !== undefined\n ? props.quickActions({ node: subject, defaultActions })\n : defaultActions;\n\n const stateBadges = [\n ...(slice.pinned ? ['pinned'] : []),\n ...(slice.hidden ? ['hidden'] : []),\n ].join(' · ');\n\n return (\n <div\n data-orbit-inspector=\"\"\n role=\"complementary\"\n aria-label={panelLabel}\n className={props.className}\n style={{ ...PANEL_STYLE, ...dockStyle, ...props.style }}\n >\n {/* Untrusted node text renders as TEXT NODES only (§14). */}\n <div data-orbit-inspector-title=\"\" style={TITLE_STYLE}>\n {nodeLabelText(subject)}\n </div>\n {stateBadges !== '' ? (\n <div data-orbit-inspector-state=\"\" style={SECTION_LABEL_STYLE}>\n {stateBadges}\n </div>\n ) : null}\n\n <div data-orbit-inspector-actions=\"\" style={ACTIONS_STYLE}>\n {actions.map((action) => (\n <button\n key={action.id}\n type=\"button\"\n data-orbit-inspector-action={action.id}\n disabled={action.disabled === true}\n style={ACTION_BUTTON_STYLE}\n onClick={() => {\n if (action.disabled !== true) action.onSelect();\n }}\n >\n {action.label}\n </button>\n ))}\n </div>\n\n <div style={SECTION_LABEL_STYLE}>Attributes</div>\n {props.renderAttrs !== undefined ? (\n props.renderAttrs(subject)\n ) : attrs.length === 0 ? (\n <div data-orbit-inspector-no-attrs=\"\" style={EMPTY_STYLE}>\n No attributes\n </div>\n ) : (\n <table data-orbit-inspector-attrs=\"\" style={TABLE_STYLE}>\n <tbody>\n {attrs.map(([key, value]) => (\n <tr key={key} data-orbit-inspector-attr={key}>\n <th scope=\"row\" style={CELL_KEY_STYLE}>\n {key}\n </th>\n <td style={CELL_VALUE_STYLE}>{attrValueText(value)}</td>\n </tr>\n ))}\n </tbody>\n </table>\n )}\n\n <div style={SECTION_LABEL_STYLE}>Neighbors</div>\n {neighbors === undefined ? (\n <button\n type=\"button\"\n data-orbit-inspector-resolve-neighbors=\"\"\n style={BUTTON_STYLE}\n onClick={() => {\n focusAndLearn(subjectId);\n }}\n >\n Show neighbors (focuses the node)\n </button>\n ) : neighbors.length === 0 ? (\n <div data-orbit-inspector-no-neighbors=\"\" style={EMPTY_STYLE}>\n No neighbors\n </div>\n ) : (\n <ul data-orbit-inspector-neighbors=\"\" style={{ margin: 0, padding: 0, listStyle: 'none' }}>\n {neighbors.map((id) => {\n const neighbor = instance.getNode(id) as GraphNode<any> | undefined;\n return (\n <li key={id}>\n <button\n type=\"button\"\n data-orbit-inspector-neighbor={id}\n style={BUTTON_STYLE}\n onClick={() => {\n focusAndLearn(id);\n }}\n >\n {neighbor === undefined ? id : nodeLabelText(neighbor)}\n </button>\n </li>\n );\n })}\n </ul>\n )}\n </div>\n );\n}\n"]}
@@ -0,0 +1,45 @@
1
+ import { CSSProperties, ReactElement } from 'react';
2
+ import { A as AnyGraphInstance } from '../GraphProvider-B8ITDXS0.js';
3
+ import '@modernrelay/orbit-core';
4
+
5
+ /**
6
+ * <GraphLegend> — §11/§16.7 built-in legend over one scale-valued styling
7
+ * channel (S10).
8
+ *
9
+ * Reads `instance.getScaleInfo(channel)` and re-reads on store model/scope/
10
+ * render revision advances and theme changes. Renders nothing when the
11
+ * channel is not scale-valued. Forms:
12
+ * - sequential color → gradient ramp bar + min/max domain ticks + metric name
13
+ * - diverging color → two-tone bar + min/mid/max ticks
14
+ * - categorical → swatch rows (color chip + value + count); a row click
15
+ * calls `onCategoryClick(value)`, and rows listed in `excludedValues`
16
+ * render dimmed
17
+ * - numeric range (nodeSize scales) → graduated dots (4 steps, value labels)
18
+ *
19
+ * The HOST owns actual filtering: wire `onCategoryClick` to your `filter`
20
+ * prop and feed the same values back through `excludedValues` for the dimmed
21
+ * affordance. Library-owned legend toggling arrives with S12.
22
+ *
23
+ * All data-derived strings (category values) render as TEXT NODES — never
24
+ * markup. Headless-styleable: providing `className` drops the built-in
25
+ * inline styles (theme-token text/border colors included).
26
+ */
27
+
28
+ interface GraphLegendProps {
29
+ /** Styling channel to describe. Default 'nodeColor'. */
30
+ channel?: 'nodeColor' | 'nodeSize';
31
+ /** Categorical values the host currently filters out — their rows render
32
+ * dimmed (the host owns the actual filter wiring; S12 adds library-owned
33
+ * toggling). */
34
+ excludedValues?: readonly string[];
35
+ /** Fired with the clicked categorical row's value. */
36
+ onCategoryClick?: (value: string) => void;
37
+ /** Explicit instance; defaults to the ambient <Graph>/<GraphProvider> one. */
38
+ instance?: AnyGraphInstance;
39
+ /** Class hook for the root; providing it drops the default styles. */
40
+ className?: string;
41
+ style?: CSSProperties;
42
+ }
43
+ declare function GraphLegend(props: GraphLegendProps): ReactElement | null;
44
+
45
+ export { GraphLegend, type GraphLegendProps };
@@ -0,0 +1,185 @@
1
+ import { useScaleInfoSubscription } from '../chunk-QJ7JXQVK.js';
2
+ import '../chunk-JN7QCMFW.js';
3
+ import { useResolvedInstance } from '../chunk-7XB47YST.js';
4
+ import { useMemo } from 'react';
5
+ import { CATEGORICAL_PALETTE } from '@modernrelay/orbit-core';
6
+ import { jsx, jsxs } from 'react/jsx-runtime';
7
+
8
+ var SIZE_LEGEND_STEPS = 4;
9
+ function rootStyle(theme) {
10
+ return {
11
+ display: "flex",
12
+ flexDirection: "column",
13
+ gap: 6,
14
+ padding: 8,
15
+ borderRadius: 8,
16
+ minWidth: 120,
17
+ background: "rgba(23, 25, 32, 0.85)",
18
+ border: "1px solid rgba(148, 163, 184, 0.25)",
19
+ color: theme.labelFg,
20
+ font: "12px/1.4 system-ui, sans-serif"
21
+ };
22
+ }
23
+ var TITLE_STYLE = { opacity: 0.75 };
24
+ var RAMP_STYLE = { height: 10, borderRadius: 5 };
25
+ var TICK_ROW_STYLE = {
26
+ display: "flex",
27
+ justifyContent: "space-between",
28
+ gap: 8,
29
+ opacity: 0.85
30
+ };
31
+ var CATEGORY_ROW_STYLE = {
32
+ appearance: "none",
33
+ display: "flex",
34
+ alignItems: "center",
35
+ gap: 6,
36
+ background: "transparent",
37
+ border: "none",
38
+ borderRadius: 4,
39
+ color: "inherit",
40
+ cursor: "pointer",
41
+ font: "inherit",
42
+ padding: "2px 4px",
43
+ textAlign: "left"
44
+ };
45
+ var EXCLUDED_ROW_STYLE = {
46
+ ...CATEGORY_ROW_STYLE,
47
+ opacity: 0.45,
48
+ textDecoration: "line-through"
49
+ };
50
+ var VALUE_STYLE = { flex: 1, minWidth: 0 };
51
+ var COUNT_STYLE = { opacity: 0.7 };
52
+ var DOT_ROW_STYLE = {
53
+ display: "flex",
54
+ alignItems: "flex-end",
55
+ gap: 12
56
+ };
57
+ var DOT_STEP_STYLE = {
58
+ display: "flex",
59
+ flexDirection: "column",
60
+ alignItems: "center",
61
+ gap: 4
62
+ };
63
+ function formatTick(v) {
64
+ if (!Number.isFinite(v)) return "";
65
+ if (Number.isInteger(v)) return String(v);
66
+ return String(Number(v.toPrecision(3)));
67
+ }
68
+ function dotStyle(size, theme) {
69
+ const px = Math.max(2, Math.abs(size));
70
+ return {
71
+ width: px,
72
+ height: px,
73
+ borderRadius: "50%",
74
+ background: theme.accent
75
+ };
76
+ }
77
+ function GraphLegend(props) {
78
+ const instance = useResolvedInstance(props.instance, "<GraphLegend>");
79
+ const channel = props.channel ?? "nodeColor";
80
+ const { version, theme } = useScaleInfoSubscription(instance);
81
+ const info = useMemo(
82
+ () => instance.getScaleInfo(channel),
83
+ // `version` invalidates: a new store revision can change scale info.
84
+ [instance, channel, version]
85
+ );
86
+ if (info === null) return null;
87
+ const styled = props.className === void 0;
88
+ const scale = info.scale;
89
+ const title = scale.kind === "categorical" ? typeof scale.by === "string" ? scale.by : "category" : scale.metric;
90
+ let body;
91
+ if (scale.kind === "categorical") {
92
+ const palette = scale.palette ?? CATEGORICAL_PALETTE;
93
+ const excluded = props.excludedValues ?? [];
94
+ const renderRow = (row) => {
95
+ const isExcluded = excluded.includes(row.value);
96
+ const entry = row.colorIndex >= 0 ? palette[row.colorIndex] : void 0;
97
+ return /* @__PURE__ */ jsxs(
98
+ "button",
99
+ {
100
+ type: "button",
101
+ "data-orbit-legend-row": row.value,
102
+ "aria-pressed": isExcluded,
103
+ style: styled ? isExcluded ? EXCLUDED_ROW_STYLE : CATEGORY_ROW_STYLE : void 0,
104
+ onClick: () => {
105
+ props.onCategoryClick?.(row.value);
106
+ },
107
+ children: [
108
+ /* @__PURE__ */ jsx(
109
+ "span",
110
+ {
111
+ "data-orbit-legend-swatch": "",
112
+ "aria-hidden": "true",
113
+ style: typeof entry === "number" ? dotStyle(entry, theme) : {
114
+ width: 10,
115
+ height: 10,
116
+ borderRadius: 2,
117
+ background: entry ?? "transparent",
118
+ flex: "none"
119
+ }
120
+ }
121
+ ),
122
+ /* @__PURE__ */ jsx("span", { "data-orbit-legend-value": "", style: styled ? VALUE_STYLE : void 0, children: row.value }),
123
+ /* @__PURE__ */ jsx("span", { "data-orbit-legend-count": "", style: styled ? COUNT_STYLE : void 0, children: row.count })
124
+ ]
125
+ },
126
+ row.value
127
+ );
128
+ };
129
+ body = /* @__PURE__ */ jsx(
130
+ "div",
131
+ {
132
+ "data-orbit-legend-rows": "",
133
+ style: styled ? { display: "flex", flexDirection: "column" } : void 0,
134
+ children: (info.rows ?? []).map(renderRow)
135
+ }
136
+ );
137
+ } else {
138
+ const stops = scale.range;
139
+ const domain = info.domain;
140
+ if (typeof stops[0] === "number") {
141
+ const lo = stops[0];
142
+ const hi = stops[stops.length - 1];
143
+ body = /* @__PURE__ */ jsx("div", { "data-orbit-legend-dots": "", style: styled ? DOT_ROW_STYLE : void 0, children: Array.from({ length: SIZE_LEGEND_STEPS }, (_, i) => {
144
+ const t = i / (SIZE_LEGEND_STEPS - 1);
145
+ const size = lo + (hi - lo) * t;
146
+ const label = domain !== void 0 ? formatTick(domain[0] + (domain[1] - domain[0]) * t) : "";
147
+ return /* @__PURE__ */ jsxs("span", { "data-orbit-legend-dot-step": "", style: styled ? DOT_STEP_STYLE : void 0, children: [
148
+ /* @__PURE__ */ jsx("span", { "data-orbit-legend-dot": "", "aria-hidden": "true", style: dotStyle(size, theme) }),
149
+ /* @__PURE__ */ jsx("span", { "data-orbit-legend-tick": "", children: label })
150
+ ] }, i);
151
+ }) });
152
+ } else {
153
+ const gradient = `linear-gradient(to right, ${stops.join(", ")})`;
154
+ const ticks = domain === void 0 ? [] : scale.kind === "diverging" ? [formatTick(domain[0]), formatTick(scale.mid), formatTick(domain[1])] : [formatTick(domain[0]), formatTick(domain[1])];
155
+ body = /* @__PURE__ */ jsxs("div", { style: styled ? { display: "flex", flexDirection: "column", gap: 4 } : void 0, children: [
156
+ /* @__PURE__ */ jsx(
157
+ "div",
158
+ {
159
+ "data-orbit-legend-ramp": scale.kind,
160
+ style: styled ? { ...RAMP_STYLE, background: gradient } : { background: gradient }
161
+ }
162
+ ),
163
+ ticks.length > 0 ? /* @__PURE__ */ jsx("div", { "data-orbit-legend-ticks": "", style: styled ? TICK_ROW_STYLE : void 0, children: ticks.map((tick, i) => /* @__PURE__ */ jsx("span", { "data-orbit-legend-tick": "", children: tick }, i)) }) : null
164
+ ] });
165
+ }
166
+ }
167
+ return /* @__PURE__ */ jsxs(
168
+ "div",
169
+ {
170
+ "data-orbit-legend": channel,
171
+ role: "group",
172
+ "aria-label": `${title} legend`,
173
+ className: props.className,
174
+ style: styled ? { ...rootStyle(theme), ...props.style } : props.style,
175
+ children: [
176
+ /* @__PURE__ */ jsx("div", { "data-orbit-legend-title": "", style: styled ? TITLE_STYLE : void 0, children: title }),
177
+ body
178
+ ]
179
+ }
180
+ );
181
+ }
182
+
183
+ export { GraphLegend };
184
+ //# sourceMappingURL=Legend.js.map
185
+ //# sourceMappingURL=Legend.js.map