leaflet-theme-control 0.1.3 → 0.1.5
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/README.md +2 -2
- package/package.json +15 -15
- package/src/leaflet-theme-control.js +39 -39
- package/src/leaflet-theme-editor.js +82 -100
- package/types/index.d.ts +27 -0
package/README.md
CHANGED
|
@@ -8,13 +8,13 @@ A Leaflet control for switching between visual themes using CSS filters. Perfect
|
|
|
8
8
|
|
|
9
9
|
- **Multiple themes**: Light, Dark, Grayscale, Custom
|
|
10
10
|
- **Theme Editor**: Customize filters with live preview sliders (optional)
|
|
11
|
-
-
|
|
11
|
+
- **Accessibility**: Adaptable themes for better visibility
|
|
12
12
|
- **CSS Filters**: No need for multiple tile sources
|
|
13
13
|
- **Persistent**: Saves user preference in localStorage
|
|
14
14
|
- **System Detection**: Automatically detects OS dark mode preference
|
|
15
15
|
- **i18n Ready**: Customizable labels with auto-update on language change
|
|
16
16
|
- **Lightweight**: Zero dependencies (except Leaflet)
|
|
17
|
-
-
|
|
17
|
+
- **Performance**: Instant theme switching without reloading tiles
|
|
18
18
|
|
|
19
19
|
## Installation
|
|
20
20
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "leaflet-theme-control",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "A Leaflet control for switching between visual themes (light, dark, grayscale, custom, etc.) using CSS filters",
|
|
5
5
|
"main": "src/leaflet-theme-control.js",
|
|
6
6
|
"module": "src/leaflet-theme-control.js",
|
|
@@ -38,31 +38,31 @@
|
|
|
38
38
|
"license": "MIT",
|
|
39
39
|
"repository": {
|
|
40
40
|
"type": "git",
|
|
41
|
-
"url": "git+https://github.com/
|
|
41
|
+
"url": "git+https://github.com/KristjanESPERANTO/leaflet-theme-control.git"
|
|
42
42
|
},
|
|
43
43
|
"bugs": {
|
|
44
|
-
"url": "https://github.com/
|
|
44
|
+
"url": "https://github.com/KristjanESPERANTO/leaflet-theme-control/issues"
|
|
45
45
|
},
|
|
46
|
-
"homepage": "https://
|
|
46
|
+
"homepage": "https://KristjanESPERANTO.github.io/leaflet-theme-control/",
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"leaflet": ">=2.0.0-alpha.1"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@eslint/css": "^0.
|
|
52
|
-
"@eslint/js": "^
|
|
51
|
+
"@eslint/css": "^1.0.0",
|
|
52
|
+
"@eslint/js": "^10.0.1",
|
|
53
53
|
"@eslint/markdown": "^7.5.1",
|
|
54
|
-
"@stylistic/eslint-plugin": "^5.
|
|
54
|
+
"@stylistic/eslint-plugin": "^5.10.0",
|
|
55
55
|
"commit-and-tag-version": "^12.6.1",
|
|
56
|
-
"eslint": "^
|
|
57
|
-
"eslint-plugin-import-x": "^4.16.
|
|
58
|
-
"eslint-plugin-jsdoc": "^
|
|
59
|
-
"globals": "^
|
|
60
|
-
"happy-dom": "^20.
|
|
56
|
+
"eslint": "^10.0.3",
|
|
57
|
+
"eslint-plugin-import-x": "^4.16.2",
|
|
58
|
+
"eslint-plugin-jsdoc": "^62.8.0",
|
|
59
|
+
"globals": "^17.4.0",
|
|
60
|
+
"happy-dom": "^20.8.4",
|
|
61
61
|
"leaflet": "^2.0.0-alpha.1",
|
|
62
|
-
"lint-staged": "^16.
|
|
63
|
-
"prettier": "^3.
|
|
62
|
+
"lint-staged": "^16.4.0",
|
|
63
|
+
"prettier": "^3.8.1",
|
|
64
64
|
"simple-git-hooks": "^2.13.1",
|
|
65
|
-
"vitest": "^4.0
|
|
65
|
+
"vitest": "^4.1.0"
|
|
66
66
|
},
|
|
67
67
|
"scripts": {
|
|
68
68
|
"test": "npm run lint && vitest run",
|
|
@@ -33,22 +33,19 @@ export class ThemeControl extends Control {
|
|
|
33
33
|
initialize(options) {
|
|
34
34
|
Util.setOptions(this, options)
|
|
35
35
|
|
|
36
|
-
// Create
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
Object.keys(DEFAULT_THEMES).forEach((key) => {
|
|
50
|
-
this.options.themes[key] = { ...DEFAULT_THEMES[key] }
|
|
51
|
-
})
|
|
36
|
+
// Create shallow copies of themes to avoid mutating the source objects
|
|
37
|
+
this.options.themes = this._shallowCopyThemes(
|
|
38
|
+
this.options.themes || DEFAULT_THEMES,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
// Store original themes for reset functionality in editor
|
|
42
|
+
// This ensures reset uses user-provided values, not DEFAULT_THEMES
|
|
43
|
+
this.originalThemes = {}
|
|
44
|
+
for (const [key, theme] of Object.entries(this.options.themes)) {
|
|
45
|
+
this.originalThemes[key] = {
|
|
46
|
+
filter: theme.filter,
|
|
47
|
+
controlStyle: theme.controlStyle,
|
|
48
|
+
}
|
|
52
49
|
}
|
|
53
50
|
|
|
54
51
|
this.root = document.documentElement
|
|
@@ -250,32 +247,12 @@ export class ThemeControl extends Control {
|
|
|
250
247
|
const controlStyle = theme.controlStyle || 'light'
|
|
251
248
|
this.root.setAttribute('data-control-style', controlStyle)
|
|
252
249
|
|
|
253
|
-
//
|
|
254
|
-
|
|
255
|
-
if (t.className) {
|
|
256
|
-
this.root.classList.remove(t.className)
|
|
257
|
-
}
|
|
258
|
-
})
|
|
259
|
-
|
|
260
|
-
// Add current theme class to root
|
|
261
|
-
if (theme.className) {
|
|
262
|
-
this.root.classList.add(theme.className)
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Also apply theme class to control container if map exists
|
|
250
|
+
// Apply theme classes to root and control container
|
|
251
|
+
this._applyThemeClasses(this.root, theme)
|
|
266
252
|
if (this.map) {
|
|
267
253
|
const controlContainer = this.map.getContainer().querySelector('.leaflet-control-container')
|
|
268
254
|
if (controlContainer) {
|
|
269
|
-
|
|
270
|
-
Object.values(this.options.themes).forEach((t) => {
|
|
271
|
-
if (t.className) {
|
|
272
|
-
controlContainer.classList.remove(t.className)
|
|
273
|
-
}
|
|
274
|
-
})
|
|
275
|
-
// Add current theme class
|
|
276
|
-
if (theme.className) {
|
|
277
|
-
controlContainer.classList.add(theme.className)
|
|
278
|
-
}
|
|
255
|
+
this._applyThemeClasses(controlContainer, theme)
|
|
279
256
|
}
|
|
280
257
|
}
|
|
281
258
|
|
|
@@ -336,6 +313,29 @@ export class ThemeControl extends Control {
|
|
|
336
313
|
}
|
|
337
314
|
}
|
|
338
315
|
|
|
316
|
+
/**
|
|
317
|
+
* Shallow-copy each theme object from the given source.
|
|
318
|
+
* @param {object} source - Theme map to copy
|
|
319
|
+
* @returns {object} New object with copied themes
|
|
320
|
+
*/
|
|
321
|
+
_shallowCopyThemes(source) {
|
|
322
|
+
return Object.fromEntries(
|
|
323
|
+
Object.entries(source).map(([key, theme]) => [key, { ...theme }]),
|
|
324
|
+
)
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Remove all theme classes from an element, then add the active one.
|
|
329
|
+
* @param {HTMLElement} el - Target element
|
|
330
|
+
* @param {object} theme - Currently active theme config
|
|
331
|
+
*/
|
|
332
|
+
_applyThemeClasses(el, theme) {
|
|
333
|
+
for (const t of Object.values(this.options.themes)) {
|
|
334
|
+
if (t.className) el.classList.remove(t.className)
|
|
335
|
+
}
|
|
336
|
+
if (theme.className) el.classList.add(theme.className)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
339
|
getCurrentTheme() {
|
|
340
340
|
return this.currentTheme
|
|
341
341
|
}
|
|
@@ -1,6 +1,48 @@
|
|
|
1
1
|
import { DomEvent, DomUtil } from 'leaflet'
|
|
2
2
|
import { DEFAULT_THEMES } from './leaflet-theme-control-themes.js'
|
|
3
3
|
|
|
4
|
+
/** Default editor UI labels (allocated once, reused on every _getLabel call) */
|
|
5
|
+
const DEFAULT_LABELS = {
|
|
6
|
+
selectTheme: 'Select Theme',
|
|
7
|
+
customize: 'Customize',
|
|
8
|
+
close: 'Close',
|
|
9
|
+
back: 'Back',
|
|
10
|
+
resetToDefault: 'Reset to Default',
|
|
11
|
+
customizeTheme: 'Customize this theme',
|
|
12
|
+
customBadge: 'Custom',
|
|
13
|
+
controlStyle: 'Control Style',
|
|
14
|
+
lightControls: 'Light',
|
|
15
|
+
darkControls: 'Dark',
|
|
16
|
+
invert: 'Invert',
|
|
17
|
+
hueRotate: 'Hue Rotate',
|
|
18
|
+
saturate: 'Saturate',
|
|
19
|
+
brightness: 'Brightness',
|
|
20
|
+
contrast: 'Contrast',
|
|
21
|
+
sepia: 'Sepia',
|
|
22
|
+
grayscaleFilter: 'Grayscale',
|
|
23
|
+
themeButton: 'Theme',
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Filter definitions: key → { cssName, regex, defaultValue }
|
|
28
|
+
* Used by both _parseFilterString and _isThemeModified.
|
|
29
|
+
*/
|
|
30
|
+
const FILTER_DEFS = [
|
|
31
|
+
{ key: 'invert', css: 'invert', default: 0 },
|
|
32
|
+
{ key: 'hueRotate', css: 'hue-rotate', default: 0, unit: 'deg' },
|
|
33
|
+
{ key: 'saturate', css: 'saturate', default: 1 },
|
|
34
|
+
{ key: 'brightness', css: 'brightness', default: 1 },
|
|
35
|
+
{ key: 'contrast', css: 'contrast', default: 1 },
|
|
36
|
+
{ key: 'sepia', css: 'sepia', default: 0 },
|
|
37
|
+
{ key: 'grayscale', css: 'grayscale', default: 0 },
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
// Pre-compiled regexes (one per filter, built from FILTER_DEFS)
|
|
41
|
+
const FILTER_REGEXES = FILTER_DEFS.map(({ css, unit }) => {
|
|
42
|
+
const unitPattern = unit ? unit : ''
|
|
43
|
+
return new RegExp(`${css}\\(([\\d.]+)${unitPattern}\\)`)
|
|
44
|
+
})
|
|
45
|
+
|
|
4
46
|
/**
|
|
5
47
|
* ThemeEditor - UI for selecting and editing themes
|
|
6
48
|
*
|
|
@@ -16,6 +58,9 @@ export class ThemeEditor {
|
|
|
16
58
|
this.currentView = 'selector' // 'selector' or 'editor'
|
|
17
59
|
this.editingTheme = null
|
|
18
60
|
|
|
61
|
+
// AbortController for automatic event cleanup
|
|
62
|
+
this._abortController = new AbortController()
|
|
63
|
+
|
|
19
64
|
// Storage key for custom filters
|
|
20
65
|
this.storageKey = `${themeControl.options.storageKey}-custom-filters`
|
|
21
66
|
|
|
@@ -117,12 +162,12 @@ export class ThemeEditor {
|
|
|
117
162
|
|
|
118
163
|
this.panel = panel
|
|
119
164
|
|
|
120
|
-
// Close on ESC key
|
|
121
|
-
|
|
165
|
+
// Close on ESC key (listener lives for the lifetime of the panel)
|
|
166
|
+
document.addEventListener('keydown', (e) => {
|
|
122
167
|
if (e.key === 'Escape' && this.isOpen) {
|
|
123
168
|
this.close()
|
|
124
169
|
}
|
|
125
|
-
}
|
|
170
|
+
}, { signal: this._abortController.signal })
|
|
126
171
|
|
|
127
172
|
return panel
|
|
128
173
|
}
|
|
@@ -133,29 +178,7 @@ export class ThemeEditor {
|
|
|
133
178
|
return this.themeControl.options.getEditorLabels(key)
|
|
134
179
|
}
|
|
135
180
|
|
|
136
|
-
|
|
137
|
-
const labels = {
|
|
138
|
-
selectTheme: 'Select Theme',
|
|
139
|
-
customize: 'Customize',
|
|
140
|
-
close: 'Close',
|
|
141
|
-
back: 'Back',
|
|
142
|
-
resetToDefault: 'Reset to Default',
|
|
143
|
-
customizeTheme: 'Customize this theme',
|
|
144
|
-
customBadge: 'Custom',
|
|
145
|
-
controlStyle: 'Control Style',
|
|
146
|
-
lightControls: 'Light',
|
|
147
|
-
darkControls: 'Dark',
|
|
148
|
-
invert: 'Invert',
|
|
149
|
-
hueRotate: 'Hue Rotate',
|
|
150
|
-
saturate: 'Saturate',
|
|
151
|
-
brightness: 'Brightness',
|
|
152
|
-
contrast: 'Contrast',
|
|
153
|
-
sepia: 'Sepia',
|
|
154
|
-
grayscaleFilter: 'Grayscale',
|
|
155
|
-
themeButton: 'Theme',
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
return labels[key] || key
|
|
181
|
+
return DEFAULT_LABELS[key] || key
|
|
159
182
|
}
|
|
160
183
|
|
|
161
184
|
openThemeSelector() {
|
|
@@ -166,9 +189,6 @@ export class ThemeEditor {
|
|
|
166
189
|
this.panel.style.display = 'block'
|
|
167
190
|
this._renderThemeSelector()
|
|
168
191
|
|
|
169
|
-
// Add keyboard listener
|
|
170
|
-
document.addEventListener('keydown', this._onKeyDown)
|
|
171
|
-
|
|
172
192
|
// Focus first interactive element
|
|
173
193
|
setTimeout(() => {
|
|
174
194
|
const firstBtn = this.panel.querySelector('.theme-select-btn')
|
|
@@ -202,9 +222,6 @@ export class ThemeEditor {
|
|
|
202
222
|
this.panel.style.display = 'none'
|
|
203
223
|
this.currentView = 'selector'
|
|
204
224
|
this.editingTheme = null
|
|
205
|
-
|
|
206
|
-
// Remove keyboard listener
|
|
207
|
-
document.removeEventListener('keydown', this._onKeyDown)
|
|
208
225
|
}
|
|
209
226
|
|
|
210
227
|
_isThemeModified(themeKey) {
|
|
@@ -244,11 +261,8 @@ export class ThemeEditor {
|
|
|
244
261
|
this.close()
|
|
245
262
|
}
|
|
246
263
|
|
|
247
|
-
//
|
|
248
|
-
|
|
249
|
-
document.removeEventListener('keydown', this._onKeyDown)
|
|
250
|
-
this._onKeyDown = null
|
|
251
|
-
}
|
|
264
|
+
// Abort all event listeners
|
|
265
|
+
this._abortController.abort()
|
|
252
266
|
|
|
253
267
|
// Remove panel from DOM
|
|
254
268
|
if (this.panel && this.panel.parentNode) {
|
|
@@ -454,13 +468,13 @@ export class ThemeEditor {
|
|
|
454
468
|
body.appendChild(this._createControlStyleSelector(controlStyle))
|
|
455
469
|
|
|
456
470
|
// Filter sliders
|
|
457
|
-
body.appendChild(this._createSlider('invert', filterValues.invert
|
|
458
|
-
body.appendChild(this._createSlider('hueRotate', filterValues.hueRotate
|
|
459
|
-
body.appendChild(this._createSlider('saturate', filterValues.saturate
|
|
460
|
-
body.appendChild(this._createSlider('brightness', filterValues.brightness
|
|
461
|
-
body.appendChild(this._createSlider('contrast', filterValues.contrast
|
|
462
|
-
body.appendChild(this._createSlider('sepia', filterValues.sepia
|
|
463
|
-
body.appendChild(this._createSlider('grayscale', filterValues.grayscale
|
|
471
|
+
body.appendChild(this._createSlider('invert', filterValues.invert ?? 0, 0, 1, 0.1))
|
|
472
|
+
body.appendChild(this._createSlider('hueRotate', filterValues.hueRotate ?? 0, 0, 360, 1, '°'))
|
|
473
|
+
body.appendChild(this._createSlider('saturate', filterValues.saturate ?? 1, 0, 2, 0.1))
|
|
474
|
+
body.appendChild(this._createSlider('brightness', filterValues.brightness ?? 1, 0, 2, 0.1))
|
|
475
|
+
body.appendChild(this._createSlider('contrast', filterValues.contrast ?? 1, 0, 2, 0.1))
|
|
476
|
+
body.appendChild(this._createSlider('sepia', filterValues.sepia ?? 0, 0, 1, 0.1))
|
|
477
|
+
body.appendChild(this._createSlider('grayscale', filterValues.grayscale ?? 0, 0, 1, 0.1))
|
|
464
478
|
|
|
465
479
|
return body
|
|
466
480
|
}
|
|
@@ -593,16 +607,16 @@ export class ThemeEditor {
|
|
|
593
607
|
delete this.customFilters[themeKey]
|
|
594
608
|
this._saveCustomFilters()
|
|
595
609
|
|
|
596
|
-
// Restore
|
|
610
|
+
// Restore original filter and controlStyle from user-provided themes
|
|
597
611
|
// but KEEP user-defined properties like applyToSelectors
|
|
598
|
-
const
|
|
612
|
+
const originalTheme = this.themeControl.originalThemes[themeKey]
|
|
599
613
|
const currentTheme = this.themeControl.options.themes[themeKey]
|
|
600
614
|
|
|
601
|
-
if (
|
|
615
|
+
if (originalTheme && currentTheme) {
|
|
602
616
|
// Only reset the editable properties (filter, controlStyle)
|
|
603
617
|
// Keep other properties like applyToSelectors, icon, label, className
|
|
604
|
-
currentTheme.filter =
|
|
605
|
-
currentTheme.controlStyle =
|
|
618
|
+
currentTheme.filter = originalTheme.filter
|
|
619
|
+
currentTheme.controlStyle = originalTheme.controlStyle
|
|
606
620
|
}
|
|
607
621
|
else if (!currentTheme) {
|
|
608
622
|
// Theme doesn't exist - this shouldn't happen
|
|
@@ -610,8 +624,8 @@ export class ThemeEditor {
|
|
|
610
624
|
return
|
|
611
625
|
}
|
|
612
626
|
else {
|
|
613
|
-
// For
|
|
614
|
-
console.warn(`Theme "${themeKey}" has no
|
|
627
|
+
// For themes not in originalThemes, we can't reset
|
|
628
|
+
console.warn(`Theme "${themeKey}" has no original values, cannot reset filter values`)
|
|
615
629
|
}
|
|
616
630
|
|
|
617
631
|
// Reapply theme if it's currently active
|
|
@@ -631,64 +645,32 @@ export class ThemeEditor {
|
|
|
631
645
|
}
|
|
632
646
|
|
|
633
647
|
_buildFilterString(values) {
|
|
648
|
+
const EPS = 0.01
|
|
634
649
|
const parts = []
|
|
635
650
|
|
|
636
|
-
if (values.invert >
|
|
637
|
-
if (values.hueRotate
|
|
638
|
-
if (values.saturate
|
|
639
|
-
if (values.brightness
|
|
640
|
-
if (values.contrast
|
|
641
|
-
if (values.sepia >
|
|
642
|
-
if (values.grayscale >
|
|
651
|
+
if (values.invert > EPS) parts.push(`invert(${values.invert})`)
|
|
652
|
+
if (Math.abs(values.hueRotate) > EPS) parts.push(`hue-rotate(${values.hueRotate}deg)`)
|
|
653
|
+
if (Math.abs(values.saturate - 1) > EPS) parts.push(`saturate(${values.saturate})`)
|
|
654
|
+
if (Math.abs(values.brightness - 1) > EPS) parts.push(`brightness(${values.brightness})`)
|
|
655
|
+
if (Math.abs(values.contrast - 1) > EPS) parts.push(`contrast(${values.contrast})`)
|
|
656
|
+
if (values.sepia > EPS) parts.push(`sepia(${values.sepia})`)
|
|
657
|
+
if (values.grayscale > EPS) parts.push(`grayscale(${values.grayscale})`)
|
|
643
658
|
|
|
644
659
|
return parts.join(' ')
|
|
645
660
|
}
|
|
646
661
|
|
|
647
662
|
_parseFilterString(filterString) {
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
663
|
+
const values = {}
|
|
664
|
+
for (let i = 0; i < FILTER_DEFS.length; i++) {
|
|
665
|
+
const { key, default: def } = FILTER_DEFS[i]
|
|
666
|
+
if (filterString) {
|
|
667
|
+
const match = filterString.match(FILTER_REGEXES[i])
|
|
668
|
+
values[key] = match ? parseFloat(match[1]) : def
|
|
669
|
+
}
|
|
670
|
+
else {
|
|
671
|
+
values[key] = def
|
|
657
672
|
}
|
|
658
673
|
}
|
|
659
|
-
|
|
660
|
-
const values = {
|
|
661
|
-
invert: 0,
|
|
662
|
-
hueRotate: 0,
|
|
663
|
-
saturate: 1,
|
|
664
|
-
brightness: 1,
|
|
665
|
-
contrast: 1,
|
|
666
|
-
sepia: 0,
|
|
667
|
-
grayscale: 0,
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
// Parse each filter function
|
|
671
|
-
const invertMatch = filterString.match(/invert\(([\d.]+)\)/)
|
|
672
|
-
if (invertMatch) values.invert = parseFloat(invertMatch[1])
|
|
673
|
-
|
|
674
|
-
const hueMatch = filterString.match(/hue-rotate\(([\d.]+)deg\)/)
|
|
675
|
-
if (hueMatch) values.hueRotate = parseFloat(hueMatch[1])
|
|
676
|
-
|
|
677
|
-
const saturateMatch = filterString.match(/saturate\(([\d.]+)\)/)
|
|
678
|
-
if (saturateMatch) values.saturate = parseFloat(saturateMatch[1])
|
|
679
|
-
|
|
680
|
-
const brightnessMatch = filterString.match(/brightness\(([\d.]+)\)/)
|
|
681
|
-
if (brightnessMatch) values.brightness = parseFloat(brightnessMatch[1])
|
|
682
|
-
|
|
683
|
-
const contrastMatch = filterString.match(/contrast\(([\d.]+)\)/)
|
|
684
|
-
if (contrastMatch) values.contrast = parseFloat(contrastMatch[1])
|
|
685
|
-
|
|
686
|
-
const sepiaMatch = filterString.match(/sepia\(([\d.]+)\)/)
|
|
687
|
-
if (sepiaMatch) values.sepia = parseFloat(sepiaMatch[1])
|
|
688
|
-
|
|
689
|
-
const grayscaleMatch = filterString.match(/grayscale\(([\d.]+)\)/)
|
|
690
|
-
if (grayscaleMatch) values.grayscale = parseFloat(grayscaleMatch[1])
|
|
691
|
-
|
|
692
674
|
return values
|
|
693
675
|
}
|
|
694
676
|
}
|
package/types/index.d.ts
CHANGED
|
@@ -130,6 +130,28 @@ declare module "leaflet" {
|
|
|
130
130
|
panelZIndex?: number;
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
/**
|
|
134
|
+
* Theme editor for customizing theme filters.
|
|
135
|
+
* Only available when enableEditor is true.
|
|
136
|
+
*/
|
|
137
|
+
class ThemeEditor {
|
|
138
|
+
/**
|
|
139
|
+
* Opens the theme selector panel.
|
|
140
|
+
*/
|
|
141
|
+
openThemeSelector(): void;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Opens the theme editor for a specific theme.
|
|
145
|
+
* @param themeKey - The key of the theme to edit
|
|
146
|
+
*/
|
|
147
|
+
openThemeEditor(themeKey: string): void;
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Closes the editor panel.
|
|
151
|
+
*/
|
|
152
|
+
close(): void;
|
|
153
|
+
}
|
|
154
|
+
|
|
133
155
|
/**
|
|
134
156
|
* Leaflet control for switching between visual themes.
|
|
135
157
|
*
|
|
@@ -156,6 +178,11 @@ declare module "leaflet" {
|
|
|
156
178
|
class ThemeControl extends Control {
|
|
157
179
|
options: ThemeControlOptions;
|
|
158
180
|
|
|
181
|
+
/**
|
|
182
|
+
* Theme editor instance (only available when enableEditor is true).
|
|
183
|
+
*/
|
|
184
|
+
editor?: ThemeEditor;
|
|
185
|
+
|
|
159
186
|
/**
|
|
160
187
|
* Creates a new ThemeControl instance.
|
|
161
188
|
* @param options - Configuration options
|