@qfo/qfchart 0.5.7 → 0.6.1

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.
@@ -1,234 +1,229 @@
1
- import { ChartContext, Plugin } from "../types";
1
+ import { ChartContext, Plugin } from '../types';
2
2
  // We need to import AbstractPlugin if we check instanceof, or just treat all as Plugin interface
3
3
 
4
4
  export class PluginManager {
5
- private plugins: Map<string, Plugin> = new Map();
6
- private activePluginId: string | null = null;
7
- private context: ChartContext;
8
- private toolbarContainer: HTMLElement;
9
- private tooltipElement: HTMLElement | null = null;
10
- private hideTimeout: any = null;
11
-
12
- constructor(context: ChartContext, toolbarContainer: HTMLElement) {
13
- this.context = context;
14
- this.toolbarContainer = toolbarContainer;
15
- this.createTooltip();
16
- this.renderToolbar();
17
- }
18
-
19
- private createTooltip() {
20
- this.tooltipElement = document.createElement("div");
21
- Object.assign(this.tooltipElement.style, {
22
- position: "fixed",
23
- display: "none",
24
- backgroundColor: "#1e293b",
25
- color: "#e2e8f0",
26
- padding: "6px 10px",
27
- borderRadius: "6px",
28
- fontSize: "13px",
29
- lineHeight: "1.4",
30
- fontWeight: "500",
31
- border: "1px solid #334155",
32
- zIndex: "9999",
33
- pointerEvents: "none",
34
- whiteSpace: "nowrap",
35
- boxShadow:
36
- "0 4px 6px -1px rgba(0, 0, 0, 0.3), 0 2px 4px -1px rgba(0, 0, 0, 0.15)",
37
- fontFamily: this.context.getOptions().fontFamily || "sans-serif",
38
- transition: "opacity 0.15s ease-in-out, transform 0.15s ease-in-out",
39
- opacity: "0",
40
- transform: "translateX(-5px)",
41
- });
42
- document.body.appendChild(this.tooltipElement);
43
- }
44
-
45
- public destroy() {
46
- if (this.tooltipElement && this.tooltipElement.parentNode) {
47
- this.tooltipElement.parentNode.removeChild(this.tooltipElement);
5
+ private plugins: Map<string, Plugin> = new Map();
6
+ private activePluginId: string | null = null;
7
+ private context: ChartContext;
8
+ private toolbarContainer: HTMLElement;
9
+ private tooltipElement: HTMLElement | null = null;
10
+ private hideTimeout: any = null;
11
+
12
+ constructor(context: ChartContext, toolbarContainer: HTMLElement) {
13
+ this.context = context;
14
+ this.toolbarContainer = toolbarContainer;
15
+ this.createTooltip();
16
+ this.renderToolbar();
48
17
  }
49
- this.tooltipElement = null;
50
- }
51
18
 
52
- private showTooltip(target: HTMLElement, text: string) {
53
- if (!this.tooltipElement) return;
19
+ private createTooltip() {
20
+ this.tooltipElement = document.createElement('div');
21
+ Object.assign(this.tooltipElement.style, {
22
+ position: 'fixed',
23
+ display: 'none',
24
+ backgroundColor: '#1e293b',
25
+ color: '#e2e8f0',
26
+ padding: '6px 10px',
27
+ borderRadius: '6px',
28
+ fontSize: '13px',
29
+ lineHeight: '1.4',
30
+ fontWeight: '500',
31
+ border: '1px solid #334155',
32
+ zIndex: '9999',
33
+ pointerEvents: 'none',
34
+ whiteSpace: 'nowrap',
35
+ boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.3), 0 2px 4px -1px rgba(0, 0, 0, 0.15)',
36
+ fontFamily: this.context.getOptions().fontFamily || 'sans-serif',
37
+ transition: 'opacity 0.15s ease-in-out, transform 0.15s ease-in-out',
38
+ opacity: '0',
39
+ transform: 'translateX(-5px)',
40
+ });
41
+ document.body.appendChild(this.tooltipElement);
42
+ }
54
43
 
55
- // Clear any pending hide to prevent race conditions
56
- if (this.hideTimeout) {
57
- clearTimeout(this.hideTimeout);
58
- this.hideTimeout = null;
44
+ public destroy() {
45
+ if (this.tooltipElement && this.tooltipElement.parentNode) {
46
+ this.tooltipElement.parentNode.removeChild(this.tooltipElement);
47
+ }
48
+ this.tooltipElement = null;
59
49
  }
60
50
 
61
- const rect = target.getBoundingClientRect();
62
- this.tooltipElement.textContent = text;
63
- this.tooltipElement.style.display = "block";
64
-
65
- // Position to the right of the button, centered vertically
66
- const tooltipRect = this.tooltipElement.getBoundingClientRect();
67
- const top = rect.top + (rect.height - tooltipRect.height) / 2;
68
- const left = rect.right + 10; // 10px gap
69
-
70
- this.tooltipElement.style.top = `${top}px`;
71
- this.tooltipElement.style.left = `${left}px`;
72
-
73
- // Trigger animation
74
- requestAnimationFrame(() => {
75
- if (this.tooltipElement) {
76
- this.tooltipElement.style.opacity = "1";
77
- this.tooltipElement.style.transform = "translateX(0)";
78
- }
79
- });
80
- }
81
-
82
- private hideTooltip() {
83
- if (!this.tooltipElement) return;
84
- this.tooltipElement.style.opacity = "0";
85
- this.tooltipElement.style.transform = "translateX(-5px)";
86
-
87
- if (this.hideTimeout) {
88
- clearTimeout(this.hideTimeout);
51
+ private showTooltip(target: HTMLElement, text: string) {
52
+ if (!this.tooltipElement) return;
53
+
54
+ // Clear any pending hide to prevent race conditions
55
+ if (this.hideTimeout) {
56
+ clearTimeout(this.hideTimeout);
57
+ this.hideTimeout = null;
58
+ }
59
+
60
+ const rect = target.getBoundingClientRect();
61
+ this.tooltipElement.textContent = text;
62
+ this.tooltipElement.style.display = 'block';
63
+
64
+ // Position to the right of the button, centered vertically
65
+ const tooltipRect = this.tooltipElement.getBoundingClientRect();
66
+ const top = rect.top + (rect.height - tooltipRect.height) / 2;
67
+ const left = rect.right + 10; // 10px gap
68
+
69
+ this.tooltipElement.style.top = `${top}px`;
70
+ this.tooltipElement.style.left = `${left}px`;
71
+
72
+ // Trigger animation
73
+ requestAnimationFrame(() => {
74
+ if (this.tooltipElement) {
75
+ this.tooltipElement.style.opacity = '1';
76
+ this.tooltipElement.style.transform = 'translateX(0)';
77
+ }
78
+ });
89
79
  }
90
80
 
91
- // Wait for transition to finish before hiding
92
- this.hideTimeout = setTimeout(() => {
93
- if (this.tooltipElement) {
94
- this.tooltipElement.style.display = "none";
95
- }
96
- this.hideTimeout = null;
97
- }, 150);
98
- }
99
-
100
- public register(plugin: Plugin): void {
101
- if (this.plugins.has(plugin.id)) {
102
- console.warn(`Plugin with id ${plugin.id} is already registered.`);
103
- return;
81
+ private hideTooltip() {
82
+ if (!this.tooltipElement) return;
83
+ this.tooltipElement.style.opacity = '0';
84
+ this.tooltipElement.style.transform = 'translateX(-5px)';
85
+
86
+ if (this.hideTimeout) {
87
+ clearTimeout(this.hideTimeout);
88
+ }
89
+
90
+ // Wait for transition to finish before hiding
91
+ this.hideTimeout = setTimeout(() => {
92
+ if (this.tooltipElement) {
93
+ this.tooltipElement.style.display = 'none';
94
+ }
95
+ this.hideTimeout = null;
96
+ }, 150);
104
97
  }
105
- this.plugins.set(plugin.id, plugin);
106
- plugin.init(this.context);
107
- this.addButton(plugin);
108
- }
109
-
110
- public unregister(pluginId: string): void {
111
- const plugin = this.plugins.get(pluginId);
112
- if (plugin) {
113
- if (this.activePluginId === pluginId) {
114
- this.deactivatePlugin();
115
- }
116
- plugin.destroy?.();
117
- this.plugins.delete(pluginId);
118
- this.removeButton(pluginId);
98
+
99
+ public register(plugin: Plugin): void {
100
+ if (this.plugins.has(plugin.id)) {
101
+ console.warn(`Plugin with id ${plugin.id} is already registered.`);
102
+ return;
103
+ }
104
+ this.plugins.set(plugin.id, plugin);
105
+ plugin.init(this.context);
106
+ this.addButton(plugin);
119
107
  }
120
- }
121
108
 
122
- public activatePlugin(pluginId: string): void {
123
- // If same plugin is clicked, deactivate it (toggle)
124
- if (this.activePluginId === pluginId) {
125
- this.deactivatePlugin();
126
- return;
109
+ public unregister(pluginId: string): void {
110
+ const plugin = this.plugins.get(pluginId);
111
+ if (plugin) {
112
+ if (this.activePluginId === pluginId) {
113
+ this.deactivatePlugin();
114
+ }
115
+ plugin.destroy?.();
116
+ this.plugins.delete(pluginId);
117
+ this.removeButton(pluginId);
118
+ }
127
119
  }
128
120
 
129
- // Deactivate current active plugin
130
- if (this.activePluginId) {
131
- this.deactivatePlugin();
121
+ public activatePlugin(pluginId: string): void {
122
+ // If same plugin is clicked, deactivate it (toggle)
123
+ if (this.activePluginId === pluginId) {
124
+ this.deactivatePlugin();
125
+ return;
126
+ }
127
+
128
+ // Deactivate current active plugin
129
+ if (this.activePluginId) {
130
+ this.deactivatePlugin();
131
+ }
132
+
133
+ const plugin = this.plugins.get(pluginId);
134
+ if (plugin) {
135
+ this.activePluginId = pluginId;
136
+ this.setButtonActive(pluginId, true);
137
+ plugin.activate?.();
138
+ }
132
139
  }
133
140
 
134
- const plugin = this.plugins.get(pluginId);
135
- if (plugin) {
136
- this.activePluginId = pluginId;
137
- this.setButtonActive(pluginId, true);
138
- plugin.activate?.();
141
+ public deactivatePlugin(): void {
142
+ if (this.activePluginId) {
143
+ const plugin = this.plugins.get(this.activePluginId);
144
+ plugin?.deactivate?.();
145
+ this.setButtonActive(this.activePluginId, false);
146
+ this.activePluginId = null;
147
+ }
139
148
  }
140
- }
141
-
142
- public deactivatePlugin(): void {
143
- if (this.activePluginId) {
144
- const plugin = this.plugins.get(this.activePluginId);
145
- plugin?.deactivate?.();
146
- this.setButtonActive(this.activePluginId, false);
147
- this.activePluginId = null;
149
+
150
+ // --- UI Handling ---
151
+
152
+ private renderToolbar(): void {
153
+ this.toolbarContainer.innerHTML = '';
154
+ this.toolbarContainer.classList.add('qfchart-toolbar');
155
+ this.toolbarContainer.style.display = 'flex';
156
+ this.toolbarContainer.style.flexDirection = 'column';
157
+ this.toolbarContainer.style.width = '40px';
158
+ this.toolbarContainer.style.backgroundColor = this.context.getOptions().backgroundColor || '#1e293b';
159
+ this.toolbarContainer.style.borderRight = '1px solid #334155';
160
+ this.toolbarContainer.style.padding = '5px';
161
+ this.toolbarContainer.style.boxSizing = 'border-box';
162
+ this.toolbarContainer.style.gap = '5px';
163
+ this.toolbarContainer.style.flexShrink = '0';
148
164
  }
149
- }
150
-
151
- // --- UI Handling ---
152
-
153
- private renderToolbar(): void {
154
- this.toolbarContainer.innerHTML = "";
155
- this.toolbarContainer.style.display = "flex";
156
- this.toolbarContainer.style.flexDirection = "column";
157
- this.toolbarContainer.style.width = "40px";
158
- this.toolbarContainer.style.backgroundColor =
159
- this.context.getOptions().backgroundColor || "#1e293b";
160
- this.toolbarContainer.style.borderRight = "1px solid #334155";
161
- this.toolbarContainer.style.padding = "5px";
162
- this.toolbarContainer.style.boxSizing = "border-box";
163
- this.toolbarContainer.style.gap = "5px";
164
- this.toolbarContainer.style.flexShrink = "0";
165
- }
166
-
167
- private addButton(plugin: Plugin): void {
168
- const btn = document.createElement("button");
169
- btn.id = `qfchart-plugin-btn-${plugin.id}`;
170
- // Removed native title to use custom tooltip
171
- // btn.title = plugin.name || plugin.id;
172
- btn.style.width = "30px";
173
- btn.style.height = "30px";
174
- btn.style.padding = "4px";
175
- btn.style.border = "1px solid transparent";
176
- btn.style.borderRadius = "4px";
177
- btn.style.backgroundColor = "transparent";
178
- btn.style.cursor = "pointer";
179
- btn.style.color = this.context.getOptions().fontColor || "#cbd5e1";
180
- btn.style.display = "flex";
181
- btn.style.alignItems = "center";
182
- btn.style.justifyContent = "center";
183
-
184
- // Icon
185
- if (plugin.icon) {
186
- btn.innerHTML = plugin.icon;
187
- } else {
188
- btn.innerText = (plugin.name || plugin.id).substring(0, 2).toUpperCase();
165
+
166
+ private addButton(plugin: Plugin): void {
167
+ const btn = document.createElement('button');
168
+ btn.id = `qfchart-plugin-btn-${plugin.id}`;
169
+ // Removed native title to use custom tooltip
170
+ // btn.title = plugin.name || plugin.id;
171
+ btn.style.width = '30px';
172
+ btn.style.height = '30px';
173
+ btn.style.padding = '4px';
174
+ btn.style.border = '1px solid transparent';
175
+ btn.style.borderRadius = '4px';
176
+ btn.style.backgroundColor = 'transparent';
177
+ btn.style.cursor = 'pointer';
178
+ btn.style.color = this.context.getOptions().fontColor || '#cbd5e1';
179
+ btn.style.display = 'flex';
180
+ btn.style.alignItems = 'center';
181
+ btn.style.justifyContent = 'center';
182
+
183
+ // Icon
184
+ if (plugin.icon) {
185
+ btn.innerHTML = plugin.icon;
186
+ } else {
187
+ btn.innerText = (plugin.name || plugin.id).substring(0, 2).toUpperCase();
188
+ }
189
+
190
+ // Hover effects and Tooltip
191
+ btn.addEventListener('mouseenter', () => {
192
+ if (this.activePluginId !== plugin.id) {
193
+ btn.style.backgroundColor = 'rgba(255, 255, 255, 0.1)';
194
+ }
195
+ this.showTooltip(btn, plugin.name || plugin.id);
196
+ });
197
+
198
+ btn.addEventListener('mouseleave', () => {
199
+ if (this.activePluginId !== plugin.id) {
200
+ btn.style.backgroundColor = 'transparent';
201
+ }
202
+ this.hideTooltip();
203
+ });
204
+
205
+ btn.onclick = () => this.activatePlugin(plugin.id);
206
+
207
+ this.toolbarContainer.appendChild(btn);
189
208
  }
190
209
 
191
- // Hover effects and Tooltip
192
- btn.addEventListener("mouseenter", () => {
193
- if (this.activePluginId !== plugin.id) {
194
- btn.style.backgroundColor = "rgba(255, 255, 255, 0.1)";
195
- }
196
- this.showTooltip(btn, plugin.name || plugin.id);
197
- });
198
-
199
- btn.addEventListener("mouseleave", () => {
200
- if (this.activePluginId !== plugin.id) {
201
- btn.style.backgroundColor = "transparent";
202
- }
203
- this.hideTooltip();
204
- });
205
-
206
- btn.onclick = () => this.activatePlugin(plugin.id);
207
-
208
- this.toolbarContainer.appendChild(btn);
209
- }
210
-
211
- private removeButton(pluginId: string): void {
212
- const btn = this.toolbarContainer.querySelector(
213
- `#qfchart-plugin-btn-${pluginId}`
214
- );
215
- if (btn) {
216
- btn.remove();
210
+ private removeButton(pluginId: string): void {
211
+ const btn = this.toolbarContainer.querySelector(`#qfchart-plugin-btn-${pluginId}`);
212
+ if (btn) {
213
+ btn.remove();
214
+ }
217
215
  }
218
- }
219
-
220
- private setButtonActive(pluginId: string, active: boolean): void {
221
- const btn = this.toolbarContainer.querySelector(
222
- `#qfchart-plugin-btn-${pluginId}`
223
- ) as HTMLElement;
224
- if (btn) {
225
- if (active) {
226
- btn.style.backgroundColor = "#2563eb"; // Blue highlight
227
- btn.style.color = "#ffffff";
228
- } else {
229
- btn.style.backgroundColor = "transparent";
230
- btn.style.color = this.context.getOptions().fontColor || "#cbd5e1";
231
- }
216
+
217
+ private setButtonActive(pluginId: string, active: boolean): void {
218
+ const btn = this.toolbarContainer.querySelector(`#qfchart-plugin-btn-${pluginId}`) as HTMLElement;
219
+ if (btn) {
220
+ if (active) {
221
+ btn.style.backgroundColor = '#2563eb'; // Blue highlight
222
+ btn.style.color = '#ffffff';
223
+ } else {
224
+ btn.style.backgroundColor = 'transparent';
225
+ btn.style.color = this.context.getOptions().fontColor || '#cbd5e1';
226
+ }
227
+ }
232
228
  }
233
- }
234
229
  }