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.
@@ -0,0 +1,224 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+ const DEFAULT_OPTIONS = {
5
+ collapsed: true,
6
+ position: "top-right",
7
+ title: "Plugin Control",
8
+ panelWidth: 300,
9
+ className: ""
10
+ };
11
+ class PluginControl {
12
+ /**
13
+ * Creates a new PluginControl instance.
14
+ *
15
+ * @param options - Configuration options for the control
16
+ */
17
+ constructor(options) {
18
+ __publicField(this, "_map");
19
+ __publicField(this, "_container");
20
+ __publicField(this, "_panel");
21
+ __publicField(this, "_options");
22
+ __publicField(this, "_state");
23
+ __publicField(this, "_eventHandlers", new globalThis.Map());
24
+ this._options = { ...DEFAULT_OPTIONS, ...options };
25
+ this._state = {
26
+ collapsed: this._options.collapsed,
27
+ panelWidth: this._options.panelWidth,
28
+ data: {}
29
+ };
30
+ }
31
+ /**
32
+ * Called when the control is added to the map.
33
+ * Implements the IControl interface.
34
+ *
35
+ * @param map - The MapLibre GL map instance
36
+ * @returns The control's container element
37
+ */
38
+ onAdd(map) {
39
+ this._map = map;
40
+ this._container = this._createContainer();
41
+ this._panel = this._createPanel();
42
+ this._container.appendChild(this._panel);
43
+ if (!this._state.collapsed) {
44
+ this._panel.classList.add("expanded");
45
+ }
46
+ return this._container;
47
+ }
48
+ /**
49
+ * Called when the control is removed from the map.
50
+ * Implements the IControl interface.
51
+ */
52
+ onRemove() {
53
+ var _a, _b;
54
+ (_b = (_a = this._container) == null ? void 0 : _a.parentNode) == null ? void 0 : _b.removeChild(this._container);
55
+ this._map = void 0;
56
+ this._container = void 0;
57
+ this._panel = void 0;
58
+ this._eventHandlers.clear();
59
+ }
60
+ /**
61
+ * Gets the current state of the control.
62
+ *
63
+ * @returns The current plugin state
64
+ */
65
+ getState() {
66
+ return { ...this._state };
67
+ }
68
+ /**
69
+ * Updates the control state.
70
+ *
71
+ * @param newState - Partial state to merge with current state
72
+ */
73
+ setState(newState) {
74
+ this._state = { ...this._state, ...newState };
75
+ this._emit("statechange");
76
+ }
77
+ /**
78
+ * Toggles the collapsed state of the control panel.
79
+ */
80
+ toggle() {
81
+ this._state.collapsed = !this._state.collapsed;
82
+ if (this._panel) {
83
+ if (this._state.collapsed) {
84
+ this._panel.classList.remove("expanded");
85
+ this._emit("collapse");
86
+ } else {
87
+ this._panel.classList.add("expanded");
88
+ this._emit("expand");
89
+ }
90
+ }
91
+ this._emit("statechange");
92
+ }
93
+ /**
94
+ * Expands the control panel.
95
+ */
96
+ expand() {
97
+ if (this._state.collapsed) {
98
+ this.toggle();
99
+ }
100
+ }
101
+ /**
102
+ * Collapses the control panel.
103
+ */
104
+ collapse() {
105
+ if (!this._state.collapsed) {
106
+ this.toggle();
107
+ }
108
+ }
109
+ /**
110
+ * Registers an event handler.
111
+ *
112
+ * @param event - The event type to listen for
113
+ * @param handler - The callback function
114
+ */
115
+ on(event, handler) {
116
+ if (!this._eventHandlers.has(event)) {
117
+ this._eventHandlers.set(event, /* @__PURE__ */ new Set());
118
+ }
119
+ this._eventHandlers.get(event).add(handler);
120
+ }
121
+ /**
122
+ * Removes an event handler.
123
+ *
124
+ * @param event - The event type
125
+ * @param handler - The callback function to remove
126
+ */
127
+ off(event, handler) {
128
+ var _a;
129
+ (_a = this._eventHandlers.get(event)) == null ? void 0 : _a.delete(handler);
130
+ }
131
+ /**
132
+ * Gets the map instance.
133
+ *
134
+ * @returns The MapLibre GL map instance or undefined if not added to a map
135
+ */
136
+ getMap() {
137
+ return this._map;
138
+ }
139
+ /**
140
+ * Gets the control container element.
141
+ *
142
+ * @returns The container element or undefined if not added to a map
143
+ */
144
+ getContainer() {
145
+ return this._container;
146
+ }
147
+ /**
148
+ * Emits an event to all registered handlers.
149
+ *
150
+ * @param event - The event type to emit
151
+ */
152
+ _emit(event) {
153
+ const handlers = this._eventHandlers.get(event);
154
+ if (handlers) {
155
+ const eventData = { type: event, state: this.getState() };
156
+ handlers.forEach((handler) => handler(eventData));
157
+ }
158
+ }
159
+ /**
160
+ * Creates the main container element for the control.
161
+ * Contains a toggle button (29x29) matching navigation control size.
162
+ *
163
+ * @returns The container element
164
+ */
165
+ _createContainer() {
166
+ const container = document.createElement("div");
167
+ container.className = `maplibregl-ctrl maplibregl-ctrl-group plugin-control${this._options.className ? ` ${this._options.className}` : ""}`;
168
+ const toggleBtn = document.createElement("button");
169
+ toggleBtn.className = "plugin-control-toggle";
170
+ toggleBtn.type = "button";
171
+ toggleBtn.setAttribute("aria-label", this._options.title);
172
+ toggleBtn.innerHTML = `
173
+ <span class="plugin-control-icon">
174
+ <svg viewBox="0 0 24 24" width="22" height="22" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
175
+ <rect x="3" y="3" width="7" height="7" rx="1"/>
176
+ <rect x="14" y="3" width="7" height="7" rx="1"/>
177
+ <rect x="3" y="14" width="7" height="7" rx="1"/>
178
+ <rect x="14" y="14" width="7" height="7" rx="1"/>
179
+ </svg>
180
+ </span>
181
+ `;
182
+ toggleBtn.addEventListener("click", () => this.toggle());
183
+ container.appendChild(toggleBtn);
184
+ return container;
185
+ }
186
+ /**
187
+ * Creates the panel element with header and content areas.
188
+ * Panel is positioned as a dropdown below the toggle button.
189
+ *
190
+ * @returns The panel element
191
+ */
192
+ _createPanel() {
193
+ const panel = document.createElement("div");
194
+ panel.className = "plugin-control-panel";
195
+ panel.style.width = `${this._options.panelWidth}px`;
196
+ const header = document.createElement("div");
197
+ header.className = "plugin-control-header";
198
+ const title = document.createElement("span");
199
+ title.className = "plugin-control-title";
200
+ title.textContent = this._options.title;
201
+ const closeBtn = document.createElement("button");
202
+ closeBtn.className = "plugin-control-close";
203
+ closeBtn.type = "button";
204
+ closeBtn.setAttribute("aria-label", "Close panel");
205
+ closeBtn.innerHTML = "&times;";
206
+ closeBtn.addEventListener("click", () => this.collapse());
207
+ header.appendChild(title);
208
+ header.appendChild(closeBtn);
209
+ const content = document.createElement("div");
210
+ content.className = "plugin-control-content";
211
+ content.innerHTML = `
212
+ <p class="plugin-control-placeholder">
213
+ Add your custom plugin content here.
214
+ </p>
215
+ `;
216
+ panel.appendChild(header);
217
+ panel.appendChild(content);
218
+ return panel;
219
+ }
220
+ }
221
+ export {
222
+ PluginControl as P
223
+ };
224
+ //# sourceMappingURL=PluginControl-CwImH8Ra.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PluginControl-CwImH8Ra.js","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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"}
package/dist/index.cjs ADDED
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const PluginControl = require("./PluginControl-B4juDAny.cjs");
4
+ function clamp(value, min, max) {
5
+ return Math.min(Math.max(value, min), max);
6
+ }
7
+ function formatNumericValue(value, step) {
8
+ if (step === 0) return value.toString();
9
+ const decimals = Math.max(0, -Math.floor(Math.log10(step)));
10
+ return value.toFixed(decimals);
11
+ }
12
+ function generateId(prefix) {
13
+ const id = Math.random().toString(36).substring(2, 9);
14
+ return prefix ? `${prefix}-${id}` : id;
15
+ }
16
+ function debounce(fn, delay) {
17
+ let timeoutId = null;
18
+ return (...args) => {
19
+ if (timeoutId) {
20
+ clearTimeout(timeoutId);
21
+ }
22
+ timeoutId = setTimeout(() => {
23
+ fn(...args);
24
+ timeoutId = null;
25
+ }, delay);
26
+ };
27
+ }
28
+ function throttle(fn, limit) {
29
+ let inThrottle = false;
30
+ return (...args) => {
31
+ if (!inThrottle) {
32
+ fn(...args);
33
+ inThrottle = true;
34
+ setTimeout(() => {
35
+ inThrottle = false;
36
+ }, limit);
37
+ }
38
+ };
39
+ }
40
+ function classNames(classes) {
41
+ return Object.entries(classes).filter(([, value]) => value).map(([key]) => key).join(" ");
42
+ }
43
+ exports.PluginControl = PluginControl.PluginControl;
44
+ exports.clamp = clamp;
45
+ exports.classNames = classNames;
46
+ exports.debounce = debounce;
47
+ exports.formatNumericValue = formatNumericValue;
48
+ exports.generateId = generateId;
49
+ exports.throttle = throttle;
50
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","sources":["../src/lib/utils/helpers.ts"],"sourcesContent":["/**\n * Clamps a value between a minimum and maximum.\n *\n * @param value - The value to clamp\n * @param min - The minimum allowed value\n * @param max - The maximum allowed value\n * @returns The clamped value\n *\n * @example\n * ```typescript\n * clamp(5, 0, 10); // returns 5\n * clamp(-5, 0, 10); // returns 0\n * clamp(15, 0, 10); // returns 10\n * ```\n */\nexport function clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max);\n}\n\n/**\n * Formats a numeric value with appropriate decimal places based on step size.\n *\n * @param value - The value to format\n * @param step - The step size to determine decimal places\n * @returns The formatted value as a string\n *\n * @example\n * ```typescript\n * formatNumericValue(5, 1); // returns \"5\"\n * formatNumericValue(0.5, 0.1); // returns \"0.5\"\n * formatNumericValue(0.55, 0.01); // returns \"0.55\"\n * ```\n */\nexport function formatNumericValue(value: number, step: number): string {\n if (step === 0) return value.toString();\n const decimals = Math.max(0, -Math.floor(Math.log10(step)));\n return value.toFixed(decimals);\n}\n\n/**\n * Generates a unique ID string.\n *\n * @param prefix - Optional prefix for the ID\n * @returns A unique ID string\n *\n * @example\n * ```typescript\n * generateId('control'); // returns \"control-abc123\"\n * generateId(); // returns \"abc123\"\n * ```\n */\nexport function generateId(prefix?: string): string {\n const id = Math.random().toString(36).substring(2, 9);\n return prefix ? `${prefix}-${id}` : id;\n}\n\n/**\n * Debounces a function call.\n *\n * @param fn - The function to debounce\n * @param delay - The delay in milliseconds\n * @returns A debounced version of the function\n *\n * @example\n * ```typescript\n * const debouncedUpdate = debounce(() => updateMap(), 100);\n * window.addEventListener('resize', debouncedUpdate);\n * ```\n */\nexport function debounce<T extends (...args: unknown[]) => void>(\n fn: T,\n delay: number\n): (...args: Parameters<T>) => void {\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n\n return (...args: Parameters<T>) => {\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n timeoutId = setTimeout(() => {\n fn(...args);\n timeoutId = null;\n }, delay);\n };\n}\n\n/**\n * Throttles a function call.\n *\n * @param fn - The function to throttle\n * @param limit - The minimum time between calls in milliseconds\n * @returns A throttled version of the function\n *\n * @example\n * ```typescript\n * const throttledScroll = throttle(() => handleScroll(), 100);\n * window.addEventListener('scroll', throttledScroll);\n * ```\n */\nexport function throttle<T extends (...args: unknown[]) => void>(\n fn: T,\n limit: number\n): (...args: Parameters<T>) => void {\n let inThrottle = false;\n\n return (...args: Parameters<T>) => {\n if (!inThrottle) {\n fn(...args);\n inThrottle = true;\n setTimeout(() => {\n inThrottle = false;\n }, limit);\n }\n };\n}\n\n/**\n * Creates a CSS class string from an object of class names.\n *\n * @param classes - Object with class names as keys and boolean values\n * @returns A space-separated string of class names\n *\n * @example\n * ```typescript\n * classNames({ active: true, disabled: false, visible: true });\n * // returns \"active visible\"\n * ```\n */\nexport function classNames(classes: Record<string, boolean>): string {\n return Object.entries(classes)\n .filter(([, value]) => value)\n .map(([key]) => key)\n .join(' ');\n}\n"],"names":[],"mappings":";;;AAeO,SAAS,MAAM,OAAe,KAAa,KAAqB;AACrE,SAAO,KAAK,IAAI,KAAK,IAAI,OAAO,GAAG,GAAG,GAAG;AAC3C;AAgBO,SAAS,mBAAmB,OAAe,MAAsB;AACtE,MAAI,SAAS,EAAG,QAAO,MAAM,SAAA;AAC7B,QAAM,WAAW,KAAK,IAAI,GAAG,CAAC,KAAK,MAAM,KAAK,MAAM,IAAI,CAAC,CAAC;AAC1D,SAAO,MAAM,QAAQ,QAAQ;AAC/B;AAcO,SAAS,WAAW,QAAyB;AAClD,QAAM,KAAK,KAAK,SAAS,SAAS,EAAE,EAAE,UAAU,GAAG,CAAC;AACpD,SAAO,SAAS,GAAG,MAAM,IAAI,EAAE,KAAK;AACtC;AAeO,SAAS,SACd,IACA,OACkC;AAClC,MAAI,YAAkD;AAEtD,SAAO,IAAI,SAAwB;AACjC,QAAI,WAAW;AACb,mBAAa,SAAS;AAAA,IACxB;AACA,gBAAY,WAAW,MAAM;AAC3B,SAAG,GAAG,IAAI;AACV,kBAAY;AAAA,IACd,GAAG,KAAK;AAAA,EACV;AACF;AAeO,SAAS,SACd,IACA,OACkC;AAClC,MAAI,aAAa;AAEjB,SAAO,IAAI,SAAwB;AACjC,QAAI,CAAC,YAAY;AACf,SAAG,GAAG,IAAI;AACV,mBAAa;AACb,iBAAW,MAAM;AACf,qBAAa;AAAA,MACf,GAAG,KAAK;AAAA,IACV;AAAA,EACF;AACF;AAcO,SAAS,WAAW,SAA0C;AACnE,SAAO,OAAO,QAAQ,OAAO,EAC1B,OAAO,CAAC,CAAA,EAAG,KAAK,MAAM,KAAK,EAC3B,IAAI,CAAC,CAAC,GAAG,MAAM,GAAG,EAClB,KAAK,GAAG;AACb;;;;;;;;"}
package/dist/index.mjs ADDED
@@ -0,0 +1,50 @@
1
+ import { P } from "./PluginControl-CwImH8Ra.js";
2
+ function clamp(value, min, max) {
3
+ return Math.min(Math.max(value, min), max);
4
+ }
5
+ function formatNumericValue(value, step) {
6
+ if (step === 0) return value.toString();
7
+ const decimals = Math.max(0, -Math.floor(Math.log10(step)));
8
+ return value.toFixed(decimals);
9
+ }
10
+ function generateId(prefix) {
11
+ const id = Math.random().toString(36).substring(2, 9);
12
+ return prefix ? `${prefix}-${id}` : id;
13
+ }
14
+ function debounce(fn, delay) {
15
+ let timeoutId = null;
16
+ return (...args) => {
17
+ if (timeoutId) {
18
+ clearTimeout(timeoutId);
19
+ }
20
+ timeoutId = setTimeout(() => {
21
+ fn(...args);
22
+ timeoutId = null;
23
+ }, delay);
24
+ };
25
+ }
26
+ function throttle(fn, limit) {
27
+ let inThrottle = false;
28
+ return (...args) => {
29
+ if (!inThrottle) {
30
+ fn(...args);
31
+ inThrottle = true;
32
+ setTimeout(() => {
33
+ inThrottle = false;
34
+ }, limit);
35
+ }
36
+ };
37
+ }
38
+ function classNames(classes) {
39
+ return Object.entries(classes).filter(([, value]) => value).map(([key]) => key).join(" ");
40
+ }
41
+ export {
42
+ P as PluginControl,
43
+ clamp,
44
+ classNames,
45
+ debounce,
46
+ formatNumericValue,
47
+ generateId,
48
+ throttle
49
+ };
50
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","sources":["../src/lib/utils/helpers.ts"],"sourcesContent":["/**\n * Clamps a value between a minimum and maximum.\n *\n * @param value - The value to clamp\n * @param min - The minimum allowed value\n * @param max - The maximum allowed value\n * @returns The clamped value\n *\n * @example\n * ```typescript\n * clamp(5, 0, 10); // returns 5\n * clamp(-5, 0, 10); // returns 0\n * clamp(15, 0, 10); // returns 10\n * ```\n */\nexport function clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max);\n}\n\n/**\n * Formats a numeric value with appropriate decimal places based on step size.\n *\n * @param value - The value to format\n * @param step - The step size to determine decimal places\n * @returns The formatted value as a string\n *\n * @example\n * ```typescript\n * formatNumericValue(5, 1); // returns \"5\"\n * formatNumericValue(0.5, 0.1); // returns \"0.5\"\n * formatNumericValue(0.55, 0.01); // returns \"0.55\"\n * ```\n */\nexport function formatNumericValue(value: number, step: number): string {\n if (step === 0) return value.toString();\n const decimals = Math.max(0, -Math.floor(Math.log10(step)));\n return value.toFixed(decimals);\n}\n\n/**\n * Generates a unique ID string.\n *\n * @param prefix - Optional prefix for the ID\n * @returns A unique ID string\n *\n * @example\n * ```typescript\n * generateId('control'); // returns \"control-abc123\"\n * generateId(); // returns \"abc123\"\n * ```\n */\nexport function generateId(prefix?: string): string {\n const id = Math.random().toString(36).substring(2, 9);\n return prefix ? `${prefix}-${id}` : id;\n}\n\n/**\n * Debounces a function call.\n *\n * @param fn - The function to debounce\n * @param delay - The delay in milliseconds\n * @returns A debounced version of the function\n *\n * @example\n * ```typescript\n * const debouncedUpdate = debounce(() => updateMap(), 100);\n * window.addEventListener('resize', debouncedUpdate);\n * ```\n */\nexport function debounce<T extends (...args: unknown[]) => void>(\n fn: T,\n delay: number\n): (...args: Parameters<T>) => void {\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n\n return (...args: Parameters<T>) => {\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n timeoutId = setTimeout(() => {\n fn(...args);\n timeoutId = null;\n }, delay);\n };\n}\n\n/**\n * Throttles a function call.\n *\n * @param fn - The function to throttle\n * @param limit - The minimum time between calls in milliseconds\n * @returns A throttled version of the function\n *\n * @example\n * ```typescript\n * const throttledScroll = throttle(() => handleScroll(), 100);\n * window.addEventListener('scroll', throttledScroll);\n * ```\n */\nexport function throttle<T extends (...args: unknown[]) => void>(\n fn: T,\n limit: number\n): (...args: Parameters<T>) => void {\n let inThrottle = false;\n\n return (...args: Parameters<T>) => {\n if (!inThrottle) {\n fn(...args);\n inThrottle = true;\n setTimeout(() => {\n inThrottle = false;\n }, limit);\n }\n };\n}\n\n/**\n * Creates a CSS class string from an object of class names.\n *\n * @param classes - Object with class names as keys and boolean values\n * @returns A space-separated string of class names\n *\n * @example\n * ```typescript\n * classNames({ active: true, disabled: false, visible: true });\n * // returns \"active visible\"\n * ```\n */\nexport function classNames(classes: Record<string, boolean>): string {\n return Object.entries(classes)\n .filter(([, value]) => value)\n .map(([key]) => key)\n .join(' ');\n}\n"],"names":[],"mappings":";AAeO,SAAS,MAAM,OAAe,KAAa,KAAqB;AACrE,SAAO,KAAK,IAAI,KAAK,IAAI,OAAO,GAAG,GAAG,GAAG;AAC3C;AAgBO,SAAS,mBAAmB,OAAe,MAAsB;AACtE,MAAI,SAAS,EAAG,QAAO,MAAM,SAAA;AAC7B,QAAM,WAAW,KAAK,IAAI,GAAG,CAAC,KAAK,MAAM,KAAK,MAAM,IAAI,CAAC,CAAC;AAC1D,SAAO,MAAM,QAAQ,QAAQ;AAC/B;AAcO,SAAS,WAAW,QAAyB;AAClD,QAAM,KAAK,KAAK,SAAS,SAAS,EAAE,EAAE,UAAU,GAAG,CAAC;AACpD,SAAO,SAAS,GAAG,MAAM,IAAI,EAAE,KAAK;AACtC;AAeO,SAAS,SACd,IACA,OACkC;AAClC,MAAI,YAAkD;AAEtD,SAAO,IAAI,SAAwB;AACjC,QAAI,WAAW;AACb,mBAAa,SAAS;AAAA,IACxB;AACA,gBAAY,WAAW,MAAM;AAC3B,SAAG,GAAG,IAAI;AACV,kBAAY;AAAA,IACd,GAAG,KAAK;AAAA,EACV;AACF;AAeO,SAAS,SACd,IACA,OACkC;AAClC,MAAI,aAAa;AAEjB,SAAO,IAAI,SAAwB;AACjC,QAAI,CAAC,YAAY;AACf,SAAG,GAAG,IAAI;AACV,mBAAa;AACb,iBAAW,MAAM;AACf,qBAAa;AAAA,MACf,GAAG,KAAK;AAAA,IACV;AAAA,EACF;AACF;AAcO,SAAS,WAAW,SAA0C;AACnE,SAAO,OAAO,QAAQ,OAAO,EAC1B,OAAO,CAAC,CAAA,EAAG,KAAK,MAAM,KAAK,EAC3B,IAAI,CAAC,CAAC,GAAG,MAAM,GAAG,EAClB,KAAK,GAAG;AACb;"}
@@ -0,0 +1,234 @@
1
+ /**
2
+ * MapLibre GL Plugin Control Styles
3
+ *
4
+ * This stylesheet provides the base styles for the plugin control.
5
+ * Customize these styles to match your plugin's design.
6
+ */
7
+
8
+ /* Container - matches maplibregl-ctrl styling */
9
+ .plugin-control {
10
+ background: #fff;
11
+ border-radius: 4px;
12
+ box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1);
13
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
14
+ }
15
+
16
+ /* Toggle button - 29x29 to match navigation control */
17
+ .plugin-control-toggle {
18
+ background: none;
19
+ border: none;
20
+ padding: 0;
21
+ width: 29px;
22
+ height: 29px;
23
+ display: flex;
24
+ align-items: center;
25
+ justify-content: center;
26
+ cursor: pointer;
27
+ outline: none;
28
+ color: #1f2a37;
29
+ }
30
+
31
+ .plugin-control-toggle:hover {
32
+ background-color: rgba(0, 0, 0, 0.05);
33
+ }
34
+
35
+ .plugin-control-toggle .plugin-control-icon {
36
+ width: 100%;
37
+ height: 100%;
38
+ position: relative;
39
+ display: flex;
40
+ align-items: center;
41
+ justify-content: center;
42
+ line-height: 0;
43
+ }
44
+
45
+ .plugin-control-toggle .plugin-control-icon svg {
46
+ width: 22px;
47
+ height: 22px;
48
+ display: block;
49
+ stroke: currentColor;
50
+ fill: none;
51
+ }
52
+
53
+ /* Panel - dropdown below button */
54
+ .plugin-control-panel {
55
+ position: absolute;
56
+ top: 100%;
57
+ margin-top: 5px;
58
+ background: #fff;
59
+ border-radius: 4px;
60
+ box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1);
61
+ min-width: 240px;
62
+ max-width: 420px;
63
+ max-height: 500px;
64
+ overflow-y: auto;
65
+ z-index: 1000;
66
+ padding: 8px;
67
+ font-size: 12px;
68
+ line-height: 1.4;
69
+ display: none;
70
+ }
71
+
72
+ /* Panel positioning based on parent control position */
73
+ .maplibregl-ctrl-top-left .plugin-control-panel,
74
+ .maplibregl-ctrl-bottom-left .plugin-control-panel {
75
+ left: 0;
76
+ }
77
+
78
+ .maplibregl-ctrl-top-right .plugin-control-panel,
79
+ .maplibregl-ctrl-bottom-right .plugin-control-panel {
80
+ right: 0;
81
+ }
82
+
83
+ .plugin-control-panel.expanded {
84
+ display: block;
85
+ }
86
+
87
+ /* Panel header */
88
+ .plugin-control-header {
89
+ display: flex;
90
+ align-items: center;
91
+ justify-content: space-between;
92
+ gap: 10px;
93
+ font-weight: 600;
94
+ color: #333;
95
+ padding: 4px 0 8px 0;
96
+ border-bottom: 1px solid #e0e0e0;
97
+ margin-bottom: 8px;
98
+ }
99
+
100
+ .plugin-control-title {
101
+ flex: 1 1 auto;
102
+ font-size: 13px;
103
+ }
104
+
105
+ .plugin-control-close {
106
+ width: 20px;
107
+ height: 20px;
108
+ border: none;
109
+ background: transparent;
110
+ color: #999;
111
+ font-size: 18px;
112
+ line-height: 1;
113
+ cursor: pointer;
114
+ padding: 0;
115
+ transition: color 0.2s ease;
116
+ display: flex;
117
+ align-items: center;
118
+ justify-content: center;
119
+ }
120
+
121
+ .plugin-control-close:hover {
122
+ color: #333;
123
+ }
124
+
125
+ /* Content */
126
+ .plugin-control-content {
127
+ max-height: 400px;
128
+ overflow-y: auto;
129
+ }
130
+
131
+ .plugin-control-placeholder {
132
+ margin: 0;
133
+ color: #888;
134
+ font-size: 12px;
135
+ text-align: center;
136
+ padding: 16px 0;
137
+ }
138
+
139
+ /* Scrollbar styling */
140
+ .plugin-control-content::-webkit-scrollbar {
141
+ width: 6px;
142
+ }
143
+
144
+ .plugin-control-content::-webkit-scrollbar-track {
145
+ background: transparent;
146
+ }
147
+
148
+ .plugin-control-content::-webkit-scrollbar-thumb {
149
+ background: #ccc;
150
+ border-radius: 3px;
151
+ }
152
+
153
+ .plugin-control-content::-webkit-scrollbar-thumb:hover {
154
+ background: #aaa;
155
+ }
156
+
157
+ /* Form elements */
158
+ .plugin-control-group {
159
+ margin-bottom: 12px;
160
+ }
161
+
162
+ .plugin-control-group:last-child {
163
+ margin-bottom: 0;
164
+ }
165
+
166
+ .plugin-control-label {
167
+ display: block;
168
+ margin-bottom: 4px;
169
+ font-size: 12px;
170
+ font-weight: 500;
171
+ color: #555;
172
+ }
173
+
174
+ .plugin-control-input {
175
+ width: 100%;
176
+ padding: 8px 10px;
177
+ font-size: 14px;
178
+ border: 1px solid #ddd;
179
+ border-radius: 4px;
180
+ outline: none;
181
+ transition: border-color 0.15s, box-shadow 0.15s;
182
+ }
183
+
184
+ .plugin-control-input:focus {
185
+ border-color: #4a90d9;
186
+ box-shadow: 0 0 0 3px rgba(74, 144, 217, 0.15);
187
+ }
188
+
189
+ .plugin-control-button {
190
+ display: inline-flex;
191
+ align-items: center;
192
+ justify-content: center;
193
+ padding: 8px 16px;
194
+ font-size: 14px;
195
+ font-weight: 500;
196
+ color: white;
197
+ background: #4a90d9;
198
+ border: none;
199
+ border-radius: 4px;
200
+ cursor: pointer;
201
+ transition: background-color 0.15s;
202
+ }
203
+
204
+ .plugin-control-button:hover {
205
+ background: #3a7bc8;
206
+ }
207
+
208
+ .plugin-control-button:focus {
209
+ outline: 2px solid #4a90d9;
210
+ outline-offset: 2px;
211
+ }
212
+
213
+ .plugin-control-button:disabled {
214
+ background: #ccc;
215
+ cursor: not-allowed;
216
+ }
217
+
218
+ /* Utility classes */
219
+ .plugin-control-flex {
220
+ display: flex;
221
+ gap: 8px;
222
+ }
223
+
224
+ .plugin-control-flex-col {
225
+ display: flex;
226
+ flex-direction: column;
227
+ gap: 8px;
228
+ }
229
+
230
+ .plugin-control-divider {
231
+ height: 1px;
232
+ background: #e0e0e0;
233
+ margin: 12px 0;
234
+ }
package/dist/react.cjs ADDED
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const react = require("react");
4
+ const PluginControl = require("./PluginControl-B4juDAny.cjs");
5
+ function PluginControlReact({
6
+ map,
7
+ onStateChange,
8
+ ...options
9
+ }) {
10
+ const controlRef = react.useRef(null);
11
+ react.useEffect(() => {
12
+ if (!map) return;
13
+ const control = new PluginControl.PluginControl(options);
14
+ controlRef.current = control;
15
+ if (onStateChange) {
16
+ control.on("statechange", (event) => {
17
+ onStateChange(event.state);
18
+ });
19
+ }
20
+ map.addControl(control, options.position || "top-right");
21
+ return () => {
22
+ if (map.hasControl(control)) {
23
+ map.removeControl(control);
24
+ }
25
+ controlRef.current = null;
26
+ };
27
+ }, [map]);
28
+ react.useEffect(() => {
29
+ if (controlRef.current) {
30
+ const currentState = controlRef.current.getState();
31
+ if (options.collapsed !== void 0 && options.collapsed !== currentState.collapsed) {
32
+ if (options.collapsed) {
33
+ controlRef.current.collapse();
34
+ } else {
35
+ controlRef.current.expand();
36
+ }
37
+ }
38
+ }
39
+ }, [options.collapsed]);
40
+ return null;
41
+ }
42
+ const DEFAULT_STATE = {
43
+ collapsed: true,
44
+ panelWidth: 300,
45
+ data: {}
46
+ };
47
+ function usePluginState(initialState) {
48
+ const [state, setState] = react.useState({
49
+ ...DEFAULT_STATE,
50
+ ...initialState
51
+ });
52
+ const setCollapsed = react.useCallback((collapsed) => {
53
+ setState((prev) => ({ ...prev, collapsed }));
54
+ }, []);
55
+ const setPanelWidth = react.useCallback((panelWidth) => {
56
+ setState((prev) => ({ ...prev, panelWidth }));
57
+ }, []);
58
+ const setData = react.useCallback((data) => {
59
+ setState((prev) => ({ ...prev, data: { ...prev.data, ...data } }));
60
+ }, []);
61
+ const reset = react.useCallback(() => {
62
+ setState({ ...DEFAULT_STATE, ...initialState });
63
+ }, [initialState]);
64
+ const toggle = react.useCallback(() => {
65
+ setState((prev) => ({ ...prev, collapsed: !prev.collapsed }));
66
+ }, []);
67
+ return {
68
+ state,
69
+ setState,
70
+ setCollapsed,
71
+ setPanelWidth,
72
+ setData,
73
+ reset,
74
+ toggle
75
+ };
76
+ }
77
+ exports.PluginControlReact = PluginControlReact;
78
+ exports.usePluginState = usePluginState;
79
+ //# sourceMappingURL=react.cjs.map