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 +21 -0
- package/README.md +230 -0
- package/dist/PluginControl-B4juDAny.cjs +223 -0
- package/dist/PluginControl-B4juDAny.cjs.map +1 -0
- package/dist/PluginControl-CwImH8Ra.js +224 -0
- package/dist/PluginControl-CwImH8Ra.js.map +1 -0
- package/dist/index.cjs +50 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.mjs +50 -0
- package/dist/index.mjs.map +1 -0
- package/dist/maplibre-gl-plugin-template.css +234 -0
- package/dist/react.cjs +79 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.mjs +79 -0
- package/dist/react.mjs.map +1 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/lib/core/PluginControl.d.ts +113 -0
- package/dist/types/lib/core/PluginControl.d.ts.map +1 -0
- package/dist/types/lib/core/PluginControlReact.d.ts +34 -0
- package/dist/types/lib/core/PluginControlReact.d.ts.map +1 -0
- package/dist/types/lib/core/types.d.ts +72 -0
- package/dist/types/lib/core/types.d.ts.map +1 -0
- package/dist/types/lib/hooks/index.d.ts +2 -0
- package/dist/types/lib/hooks/index.d.ts.map +1 -0
- package/dist/types/lib/hooks/usePluginState.d.ts +40 -0
- package/dist/types/lib/hooks/usePluginState.d.ts.map +1 -0
- package/dist/types/lib/utils/helpers.d.ts +86 -0
- package/dist/types/lib/utils/helpers.d.ts.map +1 -0
- package/dist/types/lib/utils/index.d.ts +2 -0
- package/dist/types/lib/utils/index.d.ts.map +1 -0
- package/dist/types/react.d.ts +4 -0
- package/dist/types/react.d.ts.map +1 -0
- package/package.json +101 -0
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
|
+
[](https://www.npmjs.com/package/maplibre-gl-plugin-template)
|
|
6
|
+
[](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 = "×";
|
|
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 = '×';\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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"}
|