polly-graph 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -3809,6 +3809,138 @@ function createGraphSimulation(config) {
3809
3809
  return { simulation };
3810
3810
  }
3811
3811
 
3812
+ // src/controls/graph-controls.utils.ts
3813
+ function resolveControlsPosition(position) {
3814
+ return position ?? "bottom-left";
3815
+ }
3816
+ function resolveControlsOrientation(orientation) {
3817
+ return orientation ?? "vertical";
3818
+ }
3819
+ function shouldRenderControl(config, key) {
3820
+ const show = config.show;
3821
+ if (!show) {
3822
+ return true;
3823
+ }
3824
+ const value = show[key];
3825
+ if (value === void 0) {
3826
+ return true;
3827
+ }
3828
+ return value;
3829
+ }
3830
+
3831
+ // src/assets/plus.svg?raw
3832
+ var plus_default = '<svg\n xmlns="http://www.w3.org/2000/svg"\n viewBox="0 0 24 24"\n fill="none"\n stroke="currentColor"\n stroke-width="2"\n stroke-linecap="round"\n stroke-linejoin="round"\n>\n <path d="M5 12h14m-7-7v14" />\n</svg>';
3833
+
3834
+ // src/assets/minus.svg?raw
3835
+ var minus_default = '<svg\n xmlns="http://www.w3.org/2000/svg"\n viewBox="0 0 24 24"\n fill="none"\n stroke="currentColor"\n stroke-width="2"\n stroke-linecap="round"\n stroke-linejoin="round"\n>\n <path d="M19 12H5" />\n</svg>';
3836
+
3837
+ // src/assets/fit.svg?raw
3838
+ var fit_default = '<svg\n xmlns="http://www.w3.org/2000/svg"\n viewBox="0 0 24 24"\n fill="none"\n stroke="currentColor"\n stroke-width="2"\n stroke-linecap="round"\n stroke-linejoin="round"\n>\n <path d="M5 9V5H9" />\n <path d="M19 9V5H15" />\n <path d="M5 15V19H9" />\n <path d="M19 15V19H15" />\n</svg>';
3839
+
3840
+ // src/assets/reset.svg?raw
3841
+ var reset_default = '<svg\n xmlns="http://www.w3.org/2000/svg"\n viewBox="0 0 24 24"\n fill="none"\n stroke="currentColor"\n stroke-width="2"\n stroke-linecap="round"\n stroke-linejoin="round"\n>\n <path d="M20 12a8 8 0 1 1-2.3-5.7" />\n <path d="M20 4.5v4h-4" />\n</svg>';
3842
+
3843
+ // src/controls/graph-controls.icons.ts
3844
+ var ICON_MAP = {
3845
+ "zoom-in": plus_default,
3846
+ "zoom-out": minus_default,
3847
+ fit: fit_default,
3848
+ reset: reset_default
3849
+ };
3850
+ function getControlIcon(icon) {
3851
+ const raw = ICON_MAP[icon];
3852
+ if (!raw) {
3853
+ throw new Error(`Icon not found: ${icon}`);
3854
+ }
3855
+ return raw.replace("<svg", '<svg class="pg-icon"');
3856
+ }
3857
+
3858
+ // src/controls/create-graph-controls.ts
3859
+ function createGraphControls(container, graph, config) {
3860
+ let root2 = null;
3861
+ function mount() {
3862
+ if (!config.enabled) {
3863
+ return;
3864
+ }
3865
+ const parent = container.parentElement;
3866
+ if (!parent) {
3867
+ return;
3868
+ }
3869
+ root2 = document.createElement("div");
3870
+ root2.className = "pg-controls";
3871
+ applyPosition(root2, config);
3872
+ applyOrientation(root2, config);
3873
+ appendControls(root2, config, graph);
3874
+ parent.appendChild(root2);
3875
+ }
3876
+ function appendControls(root3, config2, graph2) {
3877
+ if (shouldRenderControl(config2, "zoomIn")) {
3878
+ root3.appendChild(createButton("zoom-in", "Zoom in", graph2.zoomIn.bind(graph2)));
3879
+ }
3880
+ if (shouldRenderControl(config2, "zoomOut")) {
3881
+ root3.appendChild(createButton("zoom-out", "Zoom out", graph2.zoomOut.bind(graph2)));
3882
+ }
3883
+ if (shouldRenderControl(config2, "fit")) {
3884
+ root3.appendChild(createButton("fit", "Fit view", graph2.fitView.bind(graph2)));
3885
+ }
3886
+ if (shouldRenderControl(config2, "reset")) {
3887
+ root3.appendChild(createButton("reset", "Reset view", graph2.resetView.bind(graph2)));
3888
+ }
3889
+ }
3890
+ function createButton(type, label, onClick) {
3891
+ const button = document.createElement("button");
3892
+ button.type = "button";
3893
+ button.setAttribute("aria-label", label);
3894
+ const wrapper = document.createElement("div");
3895
+ wrapper.innerHTML = getControlIcon(type);
3896
+ const svg = wrapper.querySelector("svg");
3897
+ if (!svg) {
3898
+ throw new Error(`Invalid SVG for icon: ${type}`);
3899
+ }
3900
+ svg.classList.add("pg-icon");
3901
+ button.appendChild(svg);
3902
+ button.addEventListener("click", onClick);
3903
+ return button;
3904
+ }
3905
+ function destroy() {
3906
+ if (!root2) {
3907
+ return;
3908
+ }
3909
+ const clone = root2.cloneNode(true);
3910
+ root2.replaceWith(clone);
3911
+ root2 = null;
3912
+ }
3913
+ return { mount, destroy };
3914
+ }
3915
+ function applyPosition(el, config) {
3916
+ const position = resolveControlsPosition(config.position);
3917
+ const offset = config.offset ?? { x: 16, y: 16 };
3918
+ el.style.position = "absolute";
3919
+ switch (position) {
3920
+ case "bottom-left":
3921
+ el.style.left = `${offset.x}px`;
3922
+ el.style.bottom = `${offset.y}px`;
3923
+ break;
3924
+ case "bottom-right":
3925
+ el.style.right = `${offset.x}px`;
3926
+ el.style.bottom = `${offset.y}px`;
3927
+ break;
3928
+ case "top-left":
3929
+ el.style.left = `${offset.x}px`;
3930
+ el.style.top = `${offset.y}px`;
3931
+ break;
3932
+ case "top-right":
3933
+ el.style.right = `${offset.x}px`;
3934
+ el.style.top = `${offset.y}px`;
3935
+ break;
3936
+ }
3937
+ }
3938
+ function applyOrientation(el, config) {
3939
+ const orientation = resolveControlsOrientation(config.orientation);
3940
+ el.style.display = "flex";
3941
+ el.style.flexDirection = orientation === "vertical" ? "column" : "row";
3942
+ }
3943
+
3812
3944
  // src/utils/resolve-link-style.ts
3813
3945
  var DEFAULT_LINK_STYLE = {
3814
3946
  stroke: "#94a3b8",
@@ -4365,6 +4497,7 @@ function createGraph(config) {
4365
4497
  let cleanupResize = null;
4366
4498
  let cleanupZoom = null;
4367
4499
  let tooltipBinding = null;
4500
+ let controls = null;
4368
4501
  let dimensions = { width: 0, height: 0 };
4369
4502
  let rootGroup = null;
4370
4503
  let zoomBehavior = null;
@@ -4464,6 +4597,10 @@ function createGraph(config) {
4464
4597
  }
4465
4598
  if (config.interaction?.selection?.enabled) {
4466
4599
  }
4600
+ if (config.controls?.enabled) {
4601
+ controls = createGraphControls(config.container, { zoomIn, zoomOut, resetView, fitView, destroy, render }, config.controls);
4602
+ controls.mount();
4603
+ }
4467
4604
  }
4468
4605
  function resetView() {
4469
4606
  if (!zoomBehavior) {
@@ -4534,6 +4671,10 @@ function createGraph(config) {
4534
4671
  simulation.stop();
4535
4672
  simulation = null;
4536
4673
  }
4674
+ if (controls) {
4675
+ controls.destroy();
4676
+ controls = null;
4677
+ }
4537
4678
  rootGroup = null;
4538
4679
  zoomBehavior = null;
4539
4680
  while (config.container.firstChild) {
package/dist/index.css CHANGED
@@ -10,6 +10,12 @@
10
10
  color: #f8fafc;
11
11
  border: 1px solid rgba(255, 255, 255, 0.08);
12
12
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.22);
13
+ font-family:
14
+ "Inter",
15
+ -apple-system,
16
+ BlinkMacSystemFont,
17
+ "Segoe UI",
18
+ sans-serif;
13
19
  font-size: 12px;
14
20
  line-height: 1.5;
15
21
  opacity: 0;
@@ -72,5 +78,46 @@
72
78
  margin-top: 4px;
73
79
  font-size: 14px;
74
80
  font-weight: 600;
81
+ line-height: 1.4;
75
82
  color: #ffffff;
76
83
  }
84
+
85
+ /* src/styles/graph-controls.css */
86
+ .pg-controls {
87
+ background: #ffffff;
88
+ border-radius: 12px;
89
+ box-shadow: 0 12px 30px rgba(0, 0, 0, 0.12), 0 6px 14px rgba(0, 0, 0, 0.06);
90
+ display: flex;
91
+ flex-direction: column;
92
+ overflow: hidden;
93
+ position: absolute;
94
+ z-index: 10;
95
+ }
96
+ .pg-controls button {
97
+ all: unset;
98
+ align-items: center;
99
+ background: transparent;
100
+ border-bottom: 1px solid rgba(0, 0, 0, 0.06);
101
+ cursor: pointer;
102
+ display: flex;
103
+ justify-content: center;
104
+ transition: background 120ms ease, transform 80ms ease;
105
+ height: 44px;
106
+ width: 44px;
107
+ }
108
+ .pg-controls button:last-child {
109
+ border-bottom: none;
110
+ }
111
+ .pg-controls button:hover {
112
+ background: #f9fafb;
113
+ }
114
+ .pg-controls button:active {
115
+ background: #f3f4f6;
116
+ }
117
+ .pg-controls button svg.pg-icon {
118
+ display: block;
119
+ fill: none;
120
+ height: 18px;
121
+ stroke: currentColor;
122
+ width: 18px;
123
+ }
package/dist/index.d.cts CHANGED
@@ -1,5 +1,25 @@
1
1
  import { SimulationNodeDatum, SimulationLinkDatum } from 'd3-force';
2
2
 
3
+ interface Coordinates {
4
+ readonly x: number;
5
+ readonly y: number;
6
+ }
7
+
8
+ type GraphControlsPosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
9
+ type GraphControlsOrientation = 'vertical' | 'horizontal';
10
+ interface GraphControlsConfig {
11
+ readonly enabled: boolean;
12
+ readonly position?: GraphControlsPosition;
13
+ readonly orientation?: GraphControlsOrientation;
14
+ readonly offset?: Coordinates;
15
+ readonly show?: {
16
+ readonly zoomIn?: boolean;
17
+ readonly zoomOut?: boolean;
18
+ readonly reset?: boolean;
19
+ readonly fit?: boolean;
20
+ };
21
+ }
22
+
3
23
  interface GraphNode extends SimulationNodeDatum {
4
24
  readonly id: string;
5
25
  readonly type: string;
@@ -81,6 +101,7 @@ interface GraphConfig {
81
101
  readonly nodes: GraphNode[];
82
102
  readonly links: GraphLink[];
83
103
  readonly interaction?: GraphInteractionConfig;
104
+ readonly controls?: GraphControlsConfig;
84
105
  }
85
106
 
86
107
  interface GraphInstance {
package/dist/index.d.ts CHANGED
@@ -1,5 +1,25 @@
1
1
  import { SimulationNodeDatum, SimulationLinkDatum } from 'd3-force';
2
2
 
3
+ interface Coordinates {
4
+ readonly x: number;
5
+ readonly y: number;
6
+ }
7
+
8
+ type GraphControlsPosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
9
+ type GraphControlsOrientation = 'vertical' | 'horizontal';
10
+ interface GraphControlsConfig {
11
+ readonly enabled: boolean;
12
+ readonly position?: GraphControlsPosition;
13
+ readonly orientation?: GraphControlsOrientation;
14
+ readonly offset?: Coordinates;
15
+ readonly show?: {
16
+ readonly zoomIn?: boolean;
17
+ readonly zoomOut?: boolean;
18
+ readonly reset?: boolean;
19
+ readonly fit?: boolean;
20
+ };
21
+ }
22
+
3
23
  interface GraphNode extends SimulationNodeDatum {
4
24
  readonly id: string;
5
25
  readonly type: string;
@@ -81,6 +101,7 @@ interface GraphConfig {
81
101
  readonly nodes: GraphNode[];
82
102
  readonly links: GraphLink[];
83
103
  readonly interaction?: GraphInteractionConfig;
104
+ readonly controls?: GraphControlsConfig;
84
105
  }
85
106
 
86
107
  interface GraphInstance {
package/dist/index.js CHANGED
@@ -3783,6 +3783,138 @@ function createGraphSimulation(config) {
3783
3783
  return { simulation };
3784
3784
  }
3785
3785
 
3786
+ // src/controls/graph-controls.utils.ts
3787
+ function resolveControlsPosition(position) {
3788
+ return position ?? "bottom-left";
3789
+ }
3790
+ function resolveControlsOrientation(orientation) {
3791
+ return orientation ?? "vertical";
3792
+ }
3793
+ function shouldRenderControl(config, key) {
3794
+ const show = config.show;
3795
+ if (!show) {
3796
+ return true;
3797
+ }
3798
+ const value = show[key];
3799
+ if (value === void 0) {
3800
+ return true;
3801
+ }
3802
+ return value;
3803
+ }
3804
+
3805
+ // src/assets/plus.svg?raw
3806
+ var plus_default = '<svg\n xmlns="http://www.w3.org/2000/svg"\n viewBox="0 0 24 24"\n fill="none"\n stroke="currentColor"\n stroke-width="2"\n stroke-linecap="round"\n stroke-linejoin="round"\n>\n <path d="M5 12h14m-7-7v14" />\n</svg>';
3807
+
3808
+ // src/assets/minus.svg?raw
3809
+ var minus_default = '<svg\n xmlns="http://www.w3.org/2000/svg"\n viewBox="0 0 24 24"\n fill="none"\n stroke="currentColor"\n stroke-width="2"\n stroke-linecap="round"\n stroke-linejoin="round"\n>\n <path d="M19 12H5" />\n</svg>';
3810
+
3811
+ // src/assets/fit.svg?raw
3812
+ var fit_default = '<svg\n xmlns="http://www.w3.org/2000/svg"\n viewBox="0 0 24 24"\n fill="none"\n stroke="currentColor"\n stroke-width="2"\n stroke-linecap="round"\n stroke-linejoin="round"\n>\n <path d="M5 9V5H9" />\n <path d="M19 9V5H15" />\n <path d="M5 15V19H9" />\n <path d="M19 15V19H15" />\n</svg>';
3813
+
3814
+ // src/assets/reset.svg?raw
3815
+ var reset_default = '<svg\n xmlns="http://www.w3.org/2000/svg"\n viewBox="0 0 24 24"\n fill="none"\n stroke="currentColor"\n stroke-width="2"\n stroke-linecap="round"\n stroke-linejoin="round"\n>\n <path d="M20 12a8 8 0 1 1-2.3-5.7" />\n <path d="M20 4.5v4h-4" />\n</svg>';
3816
+
3817
+ // src/controls/graph-controls.icons.ts
3818
+ var ICON_MAP = {
3819
+ "zoom-in": plus_default,
3820
+ "zoom-out": minus_default,
3821
+ fit: fit_default,
3822
+ reset: reset_default
3823
+ };
3824
+ function getControlIcon(icon) {
3825
+ const raw = ICON_MAP[icon];
3826
+ if (!raw) {
3827
+ throw new Error(`Icon not found: ${icon}`);
3828
+ }
3829
+ return raw.replace("<svg", '<svg class="pg-icon"');
3830
+ }
3831
+
3832
+ // src/controls/create-graph-controls.ts
3833
+ function createGraphControls(container, graph, config) {
3834
+ let root2 = null;
3835
+ function mount() {
3836
+ if (!config.enabled) {
3837
+ return;
3838
+ }
3839
+ const parent = container.parentElement;
3840
+ if (!parent) {
3841
+ return;
3842
+ }
3843
+ root2 = document.createElement("div");
3844
+ root2.className = "pg-controls";
3845
+ applyPosition(root2, config);
3846
+ applyOrientation(root2, config);
3847
+ appendControls(root2, config, graph);
3848
+ parent.appendChild(root2);
3849
+ }
3850
+ function appendControls(root3, config2, graph2) {
3851
+ if (shouldRenderControl(config2, "zoomIn")) {
3852
+ root3.appendChild(createButton("zoom-in", "Zoom in", graph2.zoomIn.bind(graph2)));
3853
+ }
3854
+ if (shouldRenderControl(config2, "zoomOut")) {
3855
+ root3.appendChild(createButton("zoom-out", "Zoom out", graph2.zoomOut.bind(graph2)));
3856
+ }
3857
+ if (shouldRenderControl(config2, "fit")) {
3858
+ root3.appendChild(createButton("fit", "Fit view", graph2.fitView.bind(graph2)));
3859
+ }
3860
+ if (shouldRenderControl(config2, "reset")) {
3861
+ root3.appendChild(createButton("reset", "Reset view", graph2.resetView.bind(graph2)));
3862
+ }
3863
+ }
3864
+ function createButton(type, label, onClick) {
3865
+ const button = document.createElement("button");
3866
+ button.type = "button";
3867
+ button.setAttribute("aria-label", label);
3868
+ const wrapper = document.createElement("div");
3869
+ wrapper.innerHTML = getControlIcon(type);
3870
+ const svg = wrapper.querySelector("svg");
3871
+ if (!svg) {
3872
+ throw new Error(`Invalid SVG for icon: ${type}`);
3873
+ }
3874
+ svg.classList.add("pg-icon");
3875
+ button.appendChild(svg);
3876
+ button.addEventListener("click", onClick);
3877
+ return button;
3878
+ }
3879
+ function destroy() {
3880
+ if (!root2) {
3881
+ return;
3882
+ }
3883
+ const clone = root2.cloneNode(true);
3884
+ root2.replaceWith(clone);
3885
+ root2 = null;
3886
+ }
3887
+ return { mount, destroy };
3888
+ }
3889
+ function applyPosition(el, config) {
3890
+ const position = resolveControlsPosition(config.position);
3891
+ const offset = config.offset ?? { x: 16, y: 16 };
3892
+ el.style.position = "absolute";
3893
+ switch (position) {
3894
+ case "bottom-left":
3895
+ el.style.left = `${offset.x}px`;
3896
+ el.style.bottom = `${offset.y}px`;
3897
+ break;
3898
+ case "bottom-right":
3899
+ el.style.right = `${offset.x}px`;
3900
+ el.style.bottom = `${offset.y}px`;
3901
+ break;
3902
+ case "top-left":
3903
+ el.style.left = `${offset.x}px`;
3904
+ el.style.top = `${offset.y}px`;
3905
+ break;
3906
+ case "top-right":
3907
+ el.style.right = `${offset.x}px`;
3908
+ el.style.top = `${offset.y}px`;
3909
+ break;
3910
+ }
3911
+ }
3912
+ function applyOrientation(el, config) {
3913
+ const orientation = resolveControlsOrientation(config.orientation);
3914
+ el.style.display = "flex";
3915
+ el.style.flexDirection = orientation === "vertical" ? "column" : "row";
3916
+ }
3917
+
3786
3918
  // src/utils/resolve-link-style.ts
3787
3919
  var DEFAULT_LINK_STYLE = {
3788
3920
  stroke: "#94a3b8",
@@ -4339,6 +4471,7 @@ function createGraph(config) {
4339
4471
  let cleanupResize = null;
4340
4472
  let cleanupZoom = null;
4341
4473
  let tooltipBinding = null;
4474
+ let controls = null;
4342
4475
  let dimensions = { width: 0, height: 0 };
4343
4476
  let rootGroup = null;
4344
4477
  let zoomBehavior = null;
@@ -4438,6 +4571,10 @@ function createGraph(config) {
4438
4571
  }
4439
4572
  if (config.interaction?.selection?.enabled) {
4440
4573
  }
4574
+ if (config.controls?.enabled) {
4575
+ controls = createGraphControls(config.container, { zoomIn, zoomOut, resetView, fitView, destroy, render }, config.controls);
4576
+ controls.mount();
4577
+ }
4441
4578
  }
4442
4579
  function resetView() {
4443
4580
  if (!zoomBehavior) {
@@ -4508,6 +4645,10 @@ function createGraph(config) {
4508
4645
  simulation.stop();
4509
4646
  simulation = null;
4510
4647
  }
4648
+ if (controls) {
4649
+ controls.destroy();
4650
+ controls = null;
4651
+ }
4511
4652
  rootGroup = null;
4512
4653
  zoomBehavior = null;
4513
4654
  while (config.container.firstChild) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polly-graph",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Reusable D3-based graph visualization SDK with configurable nodes, links, labels, interactions, and layout behaviors.",
5
5
  "license": "MIT",
6
6
  "author": "Badal",
@@ -47,8 +47,8 @@
47
47
  },
48
48
  "scripts": {
49
49
  "clean": "rm -rf dist",
50
- "build": "tsup src/index.ts --format esm,cjs --dts --clean",
51
- "dev": "tsup src/index.ts --watch --format esm,cjs --dts",
50
+ "build": "tsup",
51
+ "dev": "tsup",
52
52
  "lint": "eslint \"src/**/*.ts\"",
53
53
  "typecheck": "tsc --noEmit",
54
54
  "test": "vitest run --passWithNoTests",