maplibre-gl-plugin-template 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Qiusheng Wu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,230 @@
1
+ # MapLibre GL Plugin Template
2
+
3
+ A template for creating MapLibre GL JS plugins with TypeScript and React support.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/maplibre-gl-plugin-template.svg)](https://www.npmjs.com/package/maplibre-gl-plugin-template)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ ## Features
9
+
10
+ - **TypeScript Support** - Full TypeScript support with type definitions
11
+ - **React Integration** - React wrapper component and custom hooks
12
+ - **IControl Implementation** - Implements MapLibre's IControl interface
13
+ - **Modern Build Setup** - Vite-based build with dual ESM/CJS output
14
+ - **Testing** - Vitest setup with React Testing Library
15
+ - **CI/CD Ready** - GitHub Actions for npm publishing and GitHub Pages
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ npm install maplibre-gl-plugin-template
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ### Vanilla JavaScript/TypeScript
26
+
27
+ ```typescript
28
+ import maplibregl from 'maplibre-gl';
29
+ import { PluginControl } from 'maplibre-gl-plugin-template';
30
+ import 'maplibre-gl-plugin-template/style.css';
31
+
32
+ const map = new maplibregl.Map({
33
+ container: 'map',
34
+ style: 'https://demotiles.maplibre.org/style.json',
35
+ center: [0, 0],
36
+ zoom: 2,
37
+ });
38
+
39
+ map.on('load', () => {
40
+ const control = new PluginControl({
41
+ title: 'My Plugin',
42
+ collapsed: false,
43
+ panelWidth: 300,
44
+ });
45
+
46
+ map.addControl(control, 'top-right');
47
+ });
48
+ ```
49
+
50
+ ### React
51
+
52
+ ```tsx
53
+ import { useEffect, useRef, useState } from 'react';
54
+ import maplibregl, { Map } from 'maplibre-gl';
55
+ import { PluginControlReact, usePluginState } from 'maplibre-gl-plugin-template/react';
56
+ import 'maplibre-gl-plugin-template/style.css';
57
+
58
+ function App() {
59
+ const mapContainer = useRef<HTMLDivElement>(null);
60
+ const [map, setMap] = useState<Map | null>(null);
61
+ const { state, toggle } = usePluginState();
62
+
63
+ useEffect(() => {
64
+ if (!mapContainer.current) return;
65
+
66
+ const mapInstance = new maplibregl.Map({
67
+ container: mapContainer.current,
68
+ style: 'https://demotiles.maplibre.org/style.json',
69
+ center: [0, 0],
70
+ zoom: 2,
71
+ });
72
+
73
+ mapInstance.on('load', () => setMap(mapInstance));
74
+
75
+ return () => mapInstance.remove();
76
+ }, []);
77
+
78
+ return (
79
+ <div style={{ width: '100%', height: '100vh' }}>
80
+ <div ref={mapContainer} style={{ width: '100%', height: '100%' }} />
81
+ {map && (
82
+ <PluginControlReact
83
+ map={map}
84
+ title="My Plugin"
85
+ collapsed={state.collapsed}
86
+ onStateChange={(newState) => console.log(newState)}
87
+ />
88
+ )}
89
+ </div>
90
+ );
91
+ }
92
+ ```
93
+
94
+ ## API
95
+
96
+ ### PluginControl
97
+
98
+ The main control class implementing MapLibre's `IControl` interface.
99
+
100
+ #### Constructor Options
101
+
102
+ | Option | Type | Default | Description |
103
+ |--------|------|---------|-------------|
104
+ | `collapsed` | `boolean` | `true` | Whether the panel starts collapsed (showing only the 29x29 toggle button) |
105
+ | `position` | `string` | `'top-right'` | Control position on the map |
106
+ | `title` | `string` | `'Plugin Control'` | Title displayed in the header |
107
+ | `panelWidth` | `number` | `300` | Width of the dropdown panel in pixels |
108
+ | `className` | `string` | `''` | Custom CSS class name |
109
+
110
+ #### Methods
111
+
112
+ - `toggle()` - Toggle the collapsed state
113
+ - `expand()` - Expand the panel
114
+ - `collapse()` - Collapse the panel
115
+ - `getState()` - Get the current state
116
+ - `setState(state)` - Update the state
117
+ - `on(event, handler)` - Register an event handler
118
+ - `off(event, handler)` - Remove an event handler
119
+ - `getMap()` - Get the map instance
120
+ - `getContainer()` - Get the container element
121
+
122
+ #### Events
123
+
124
+ - `collapse` - Fired when the panel is collapsed
125
+ - `expand` - Fired when the panel is expanded
126
+ - `statechange` - Fired when the state changes
127
+
128
+ ### PluginControlReact
129
+
130
+ React wrapper component for `PluginControl`.
131
+
132
+ #### Props
133
+
134
+ All `PluginControl` options plus:
135
+
136
+ | Prop | Type | Description |
137
+ |------|------|-------------|
138
+ | `map` | `Map` | MapLibre GL map instance (required) |
139
+ | `onStateChange` | `function` | Callback fired when state changes |
140
+
141
+ ### usePluginState
142
+
143
+ Custom React hook for managing plugin state.
144
+
145
+ ```typescript
146
+ const {
147
+ state, // Current state
148
+ setState, // Update entire state
149
+ setCollapsed, // Set collapsed state
150
+ setPanelWidth,// Set panel width
151
+ setData, // Set custom data
152
+ reset, // Reset to initial state
153
+ toggle, // Toggle collapsed state
154
+ } = usePluginState(initialState);
155
+ ```
156
+
157
+ ## Utilities
158
+
159
+ The package exports several utility functions:
160
+
161
+ - `clamp(value, min, max)` - Clamp a value between min and max
162
+ - `formatNumericValue(value, step)` - Format a number with appropriate decimals
163
+ - `generateId(prefix?)` - Generate a unique ID
164
+ - `debounce(fn, delay)` - Debounce a function
165
+ - `throttle(fn, limit)` - Throttle a function
166
+ - `classNames(classes)` - Build a class string from an object
167
+
168
+ ## Development
169
+
170
+ ### Setup
171
+
172
+ ```bash
173
+ # Clone the repository
174
+ git clone https://github.com/your-username/maplibre-gl-plugin-template.git
175
+ cd maplibre-gl-plugin-template
176
+
177
+ # Install dependencies
178
+ npm install
179
+
180
+ # Start development server
181
+ npm run dev
182
+ ```
183
+
184
+ ### Scripts
185
+
186
+ | Script | Description |
187
+ |--------|-------------|
188
+ | `npm run dev` | Start development server |
189
+ | `npm run build` | Build the library |
190
+ | `npm run build:examples` | Build examples for deployment |
191
+ | `npm run test` | Run tests |
192
+ | `npm run test:ui` | Run tests with UI |
193
+ | `npm run test:coverage` | Run tests with coverage |
194
+ | `npm run lint` | Lint the code |
195
+ | `npm run format` | Format the code |
196
+
197
+ ### Project Structure
198
+
199
+ ```
200
+ maplibre-gl-plugin-template/
201
+ ├── src/
202
+ │ ├── index.ts # Main entry point
203
+ │ ├── react.ts # React entry point
204
+ │ ├── index.css # Root styles
205
+ │ └── lib/
206
+ │ ├── core/ # Core classes and types
207
+ │ ├── hooks/ # React hooks
208
+ │ ├── utils/ # Utility functions
209
+ │ └── styles/ # Component styles
210
+ ├── tests/ # Test files
211
+ ├── examples/ # Example applications
212
+ │ ├── basic/ # Vanilla JS example
213
+ │ └── react/ # React example
214
+ └── .github/workflows/ # CI/CD workflows
215
+ ```
216
+
217
+ ## Customization
218
+
219
+ To use this template for your own plugin:
220
+
221
+ 1. Clone or fork this repository
222
+ 2. Update `package.json` with your plugin name and details
223
+ 3. Modify `src/lib/core/PluginControl.ts` to implement your plugin logic
224
+ 4. Update the styles in `src/lib/styles/plugin-control.css`
225
+ 5. Add custom utilities, hooks, or components as needed
226
+ 6. Update the README with your plugin's documentation
227
+
228
+ ## License
229
+
230
+ MIT License - see [LICENSE](LICENSE) for details.
@@ -0,0 +1,223 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
4
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
5
+ const DEFAULT_OPTIONS = {
6
+ collapsed: true,
7
+ position: "top-right",
8
+ title: "Plugin Control",
9
+ panelWidth: 300,
10
+ className: ""
11
+ };
12
+ class PluginControl {
13
+ /**
14
+ * Creates a new PluginControl instance.
15
+ *
16
+ * @param options - Configuration options for the control
17
+ */
18
+ constructor(options) {
19
+ __publicField(this, "_map");
20
+ __publicField(this, "_container");
21
+ __publicField(this, "_panel");
22
+ __publicField(this, "_options");
23
+ __publicField(this, "_state");
24
+ __publicField(this, "_eventHandlers", new globalThis.Map());
25
+ this._options = { ...DEFAULT_OPTIONS, ...options };
26
+ this._state = {
27
+ collapsed: this._options.collapsed,
28
+ panelWidth: this._options.panelWidth,
29
+ data: {}
30
+ };
31
+ }
32
+ /**
33
+ * Called when the control is added to the map.
34
+ * Implements the IControl interface.
35
+ *
36
+ * @param map - The MapLibre GL map instance
37
+ * @returns The control's container element
38
+ */
39
+ onAdd(map) {
40
+ this._map = map;
41
+ this._container = this._createContainer();
42
+ this._panel = this._createPanel();
43
+ this._container.appendChild(this._panel);
44
+ if (!this._state.collapsed) {
45
+ this._panel.classList.add("expanded");
46
+ }
47
+ return this._container;
48
+ }
49
+ /**
50
+ * Called when the control is removed from the map.
51
+ * Implements the IControl interface.
52
+ */
53
+ onRemove() {
54
+ var _a, _b;
55
+ (_b = (_a = this._container) == null ? void 0 : _a.parentNode) == null ? void 0 : _b.removeChild(this._container);
56
+ this._map = void 0;
57
+ this._container = void 0;
58
+ this._panel = void 0;
59
+ this._eventHandlers.clear();
60
+ }
61
+ /**
62
+ * Gets the current state of the control.
63
+ *
64
+ * @returns The current plugin state
65
+ */
66
+ getState() {
67
+ return { ...this._state };
68
+ }
69
+ /**
70
+ * Updates the control state.
71
+ *
72
+ * @param newState - Partial state to merge with current state
73
+ */
74
+ setState(newState) {
75
+ this._state = { ...this._state, ...newState };
76
+ this._emit("statechange");
77
+ }
78
+ /**
79
+ * Toggles the collapsed state of the control panel.
80
+ */
81
+ toggle() {
82
+ this._state.collapsed = !this._state.collapsed;
83
+ if (this._panel) {
84
+ if (this._state.collapsed) {
85
+ this._panel.classList.remove("expanded");
86
+ this._emit("collapse");
87
+ } else {
88
+ this._panel.classList.add("expanded");
89
+ this._emit("expand");
90
+ }
91
+ }
92
+ this._emit("statechange");
93
+ }
94
+ /**
95
+ * Expands the control panel.
96
+ */
97
+ expand() {
98
+ if (this._state.collapsed) {
99
+ this.toggle();
100
+ }
101
+ }
102
+ /**
103
+ * Collapses the control panel.
104
+ */
105
+ collapse() {
106
+ if (!this._state.collapsed) {
107
+ this.toggle();
108
+ }
109
+ }
110
+ /**
111
+ * Registers an event handler.
112
+ *
113
+ * @param event - The event type to listen for
114
+ * @param handler - The callback function
115
+ */
116
+ on(event, handler) {
117
+ if (!this._eventHandlers.has(event)) {
118
+ this._eventHandlers.set(event, /* @__PURE__ */ new Set());
119
+ }
120
+ this._eventHandlers.get(event).add(handler);
121
+ }
122
+ /**
123
+ * Removes an event handler.
124
+ *
125
+ * @param event - The event type
126
+ * @param handler - The callback function to remove
127
+ */
128
+ off(event, handler) {
129
+ var _a;
130
+ (_a = this._eventHandlers.get(event)) == null ? void 0 : _a.delete(handler);
131
+ }
132
+ /**
133
+ * Gets the map instance.
134
+ *
135
+ * @returns The MapLibre GL map instance or undefined if not added to a map
136
+ */
137
+ getMap() {
138
+ return this._map;
139
+ }
140
+ /**
141
+ * Gets the control container element.
142
+ *
143
+ * @returns The container element or undefined if not added to a map
144
+ */
145
+ getContainer() {
146
+ return this._container;
147
+ }
148
+ /**
149
+ * Emits an event to all registered handlers.
150
+ *
151
+ * @param event - The event type to emit
152
+ */
153
+ _emit(event) {
154
+ const handlers = this._eventHandlers.get(event);
155
+ if (handlers) {
156
+ const eventData = { type: event, state: this.getState() };
157
+ handlers.forEach((handler) => handler(eventData));
158
+ }
159
+ }
160
+ /**
161
+ * Creates the main container element for the control.
162
+ * Contains a toggle button (29x29) matching navigation control size.
163
+ *
164
+ * @returns The container element
165
+ */
166
+ _createContainer() {
167
+ const container = document.createElement("div");
168
+ container.className = `maplibregl-ctrl maplibregl-ctrl-group plugin-control${this._options.className ? ` ${this._options.className}` : ""}`;
169
+ const toggleBtn = document.createElement("button");
170
+ toggleBtn.className = "plugin-control-toggle";
171
+ toggleBtn.type = "button";
172
+ toggleBtn.setAttribute("aria-label", this._options.title);
173
+ toggleBtn.innerHTML = `
174
+ <span class="plugin-control-icon">
175
+ <svg viewBox="0 0 24 24" width="22" height="22" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
176
+ <rect x="3" y="3" width="7" height="7" rx="1"/>
177
+ <rect x="14" y="3" width="7" height="7" rx="1"/>
178
+ <rect x="3" y="14" width="7" height="7" rx="1"/>
179
+ <rect x="14" y="14" width="7" height="7" rx="1"/>
180
+ </svg>
181
+ </span>
182
+ `;
183
+ toggleBtn.addEventListener("click", () => this.toggle());
184
+ container.appendChild(toggleBtn);
185
+ return container;
186
+ }
187
+ /**
188
+ * Creates the panel element with header and content areas.
189
+ * Panel is positioned as a dropdown below the toggle button.
190
+ *
191
+ * @returns The panel element
192
+ */
193
+ _createPanel() {
194
+ const panel = document.createElement("div");
195
+ panel.className = "plugin-control-panel";
196
+ panel.style.width = `${this._options.panelWidth}px`;
197
+ const header = document.createElement("div");
198
+ header.className = "plugin-control-header";
199
+ const title = document.createElement("span");
200
+ title.className = "plugin-control-title";
201
+ title.textContent = this._options.title;
202
+ const closeBtn = document.createElement("button");
203
+ closeBtn.className = "plugin-control-close";
204
+ closeBtn.type = "button";
205
+ closeBtn.setAttribute("aria-label", "Close panel");
206
+ closeBtn.innerHTML = "&times;";
207
+ closeBtn.addEventListener("click", () => this.collapse());
208
+ header.appendChild(title);
209
+ header.appendChild(closeBtn);
210
+ const content = document.createElement("div");
211
+ content.className = "plugin-control-content";
212
+ content.innerHTML = `
213
+ <p class="plugin-control-placeholder">
214
+ Add your custom plugin content here.
215
+ </p>
216
+ `;
217
+ panel.appendChild(header);
218
+ panel.appendChild(content);
219
+ return panel;
220
+ }
221
+ }
222
+ exports.PluginControl = PluginControl;
223
+ //# sourceMappingURL=PluginControl-B4juDAny.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PluginControl-B4juDAny.cjs","sources":["../src/lib/core/PluginControl.ts"],"sourcesContent":["import type { IControl, Map as MapLibreMap } from 'maplibre-gl';\nimport type {\n PluginControlOptions,\n PluginState,\n PluginControlEvent,\n PluginControlEventHandler,\n} from './types';\n\n/**\n * Default options for the PluginControl\n */\nconst DEFAULT_OPTIONS: Required<PluginControlOptions> = {\n collapsed: true,\n position: 'top-right',\n title: 'Plugin Control',\n panelWidth: 300,\n className: '',\n};\n\n/**\n * Event handlers map type\n */\ntype EventHandlersMap = globalThis.Map<PluginControlEvent, Set<PluginControlEventHandler>>;\n\n/**\n * A template MapLibre GL control that can be customized for various plugin needs.\n *\n * @example\n * ```typescript\n * const control = new PluginControl({\n * title: 'My Custom Control',\n * collapsed: false,\n * panelWidth: 320,\n * });\n * map.addControl(control, 'top-right');\n * ```\n */\nexport class PluginControl implements IControl {\n private _map?: MapLibreMap;\n private _container?: HTMLElement;\n private _panel?: HTMLElement;\n private _options: Required<PluginControlOptions>;\n private _state: PluginState;\n private _eventHandlers: EventHandlersMap = new globalThis.Map();\n\n /**\n * Creates a new PluginControl instance.\n *\n * @param options - Configuration options for the control\n */\n constructor(options?: Partial<PluginControlOptions>) {\n this._options = { ...DEFAULT_OPTIONS, ...options };\n this._state = {\n collapsed: this._options.collapsed,\n panelWidth: this._options.panelWidth,\n data: {},\n };\n }\n\n /**\n * Called when the control is added to the map.\n * Implements the IControl interface.\n *\n * @param map - The MapLibre GL map instance\n * @returns The control's container element\n */\n onAdd(map: MapLibreMap): HTMLElement {\n this._map = map;\n this._container = this._createContainer();\n this._panel = this._createPanel();\n this._container.appendChild(this._panel);\n\n // Set initial panel state\n if (!this._state.collapsed) {\n this._panel.classList.add('expanded');\n }\n\n return this._container;\n }\n\n /**\n * Called when the control is removed from the map.\n * Implements the IControl interface.\n */\n onRemove(): void {\n this._container?.parentNode?.removeChild(this._container);\n this._map = undefined;\n this._container = undefined;\n this._panel = undefined;\n this._eventHandlers.clear();\n }\n\n /**\n * Gets the current state of the control.\n *\n * @returns The current plugin state\n */\n getState(): PluginState {\n return { ...this._state };\n }\n\n /**\n * Updates the control state.\n *\n * @param newState - Partial state to merge with current state\n */\n setState(newState: Partial<PluginState>): void {\n this._state = { ...this._state, ...newState };\n this._emit('statechange');\n }\n\n /**\n * Toggles the collapsed state of the control panel.\n */\n toggle(): void {\n this._state.collapsed = !this._state.collapsed;\n\n if (this._panel) {\n if (this._state.collapsed) {\n this._panel.classList.remove('expanded');\n this._emit('collapse');\n } else {\n this._panel.classList.add('expanded');\n this._emit('expand');\n }\n }\n\n this._emit('statechange');\n }\n\n /**\n * Expands the control panel.\n */\n expand(): void {\n if (this._state.collapsed) {\n this.toggle();\n }\n }\n\n /**\n * Collapses the control panel.\n */\n collapse(): void {\n if (!this._state.collapsed) {\n this.toggle();\n }\n }\n\n /**\n * Registers an event handler.\n *\n * @param event - The event type to listen for\n * @param handler - The callback function\n */\n on(event: PluginControlEvent, handler: PluginControlEventHandler): void {\n if (!this._eventHandlers.has(event)) {\n this._eventHandlers.set(event, new Set());\n }\n this._eventHandlers.get(event)!.add(handler);\n }\n\n /**\n * Removes an event handler.\n *\n * @param event - The event type\n * @param handler - The callback function to remove\n */\n off(event: PluginControlEvent, handler: PluginControlEventHandler): void {\n this._eventHandlers.get(event)?.delete(handler);\n }\n\n /**\n * Gets the map instance.\n *\n * @returns The MapLibre GL map instance or undefined if not added to a map\n */\n getMap(): MapLibreMap | undefined {\n return this._map;\n }\n\n /**\n * Gets the control container element.\n *\n * @returns The container element or undefined if not added to a map\n */\n getContainer(): HTMLElement | undefined {\n return this._container;\n }\n\n /**\n * Emits an event to all registered handlers.\n *\n * @param event - The event type to emit\n */\n private _emit(event: PluginControlEvent): void {\n const handlers = this._eventHandlers.get(event);\n if (handlers) {\n const eventData = { type: event, state: this.getState() };\n handlers.forEach((handler) => handler(eventData));\n }\n }\n\n /**\n * Creates the main container element for the control.\n * Contains a toggle button (29x29) matching navigation control size.\n *\n * @returns The container element\n */\n private _createContainer(): HTMLElement {\n const container = document.createElement('div');\n container.className = `maplibregl-ctrl maplibregl-ctrl-group plugin-control${\n this._options.className ? ` ${this._options.className}` : ''\n }`;\n\n // Create toggle button (29x29 to match navigation control)\n const toggleBtn = document.createElement('button');\n toggleBtn.className = 'plugin-control-toggle';\n toggleBtn.type = 'button';\n toggleBtn.setAttribute('aria-label', this._options.title);\n toggleBtn.innerHTML = `\n <span class=\"plugin-control-icon\">\n <svg viewBox=\"0 0 24 24\" width=\"22\" height=\"22\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"3\" y=\"3\" width=\"7\" height=\"7\" rx=\"1\"/>\n <rect x=\"14\" y=\"3\" width=\"7\" height=\"7\" rx=\"1\"/>\n <rect x=\"3\" y=\"14\" width=\"7\" height=\"7\" rx=\"1\"/>\n <rect x=\"14\" y=\"14\" width=\"7\" height=\"7\" rx=\"1\"/>\n </svg>\n </span>\n `;\n toggleBtn.addEventListener('click', () => this.toggle());\n\n container.appendChild(toggleBtn);\n\n return container;\n }\n\n /**\n * Creates the panel element with header and content areas.\n * Panel is positioned as a dropdown below the toggle button.\n *\n * @returns The panel element\n */\n private _createPanel(): HTMLElement {\n const panel = document.createElement('div');\n panel.className = 'plugin-control-panel';\n panel.style.width = `${this._options.panelWidth}px`;\n\n // Create header with title and close button\n const header = document.createElement('div');\n header.className = 'plugin-control-header';\n\n const title = document.createElement('span');\n title.className = 'plugin-control-title';\n title.textContent = this._options.title;\n\n const closeBtn = document.createElement('button');\n closeBtn.className = 'plugin-control-close';\n closeBtn.type = 'button';\n closeBtn.setAttribute('aria-label', 'Close panel');\n closeBtn.innerHTML = '&times;';\n closeBtn.addEventListener('click', () => this.collapse());\n\n header.appendChild(title);\n header.appendChild(closeBtn);\n\n // Create content area\n const content = document.createElement('div');\n content.className = 'plugin-control-content';\n content.innerHTML = `\n <p class=\"plugin-control-placeholder\">\n Add your custom plugin content here.\n </p>\n `;\n\n panel.appendChild(header);\n panel.appendChild(content);\n\n return panel;\n }\n}\n"],"names":[],"mappings":"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"}