nakarte

Source code of https://map.sikmir.ru (fork)
git clone git://git.sikmir.ru/nakarte
Log | Files | Refs | LICENSE

commit 9493a34382e90e8dd3f3432103f7622630f898ab
parent 9388cdd1458be0663c56059efcfbea8125b001ff
Author: Sergej Orlov <wladimirych@gmail.com>
Date:   Fri,  5 Dec 2025 22:38:00 +0100

layers control: improve custom hotkeys look and interaction

- use divs instead of text inputs for better alignement
- add tooltips for hotkey inputs
- clear hotkey on space, delete and backspace keys
- remove focus from input on enter and escape keys
- fix check for occupied hotkeys
- display error messages for invalid keys

Diffstat:
Msrc/lib/leaflet.control.layers.configure/index.js | 111++++++++++++++++++++++++++++++++++++++++---------------------------------------
Msrc/lib/leaflet.control.layers.configure/style.css | 38++++++++++++++++++++++++++++++--------
2 files changed, 86 insertions(+), 63 deletions(-)

diff --git a/src/lib/leaflet.control.layers.configure/index.js b/src/lib/leaflet.control.layers.configure/index.js @@ -26,6 +26,10 @@ class LayersConfigDialog { ]; } + allLayerModels() { + return [].concat(...this.layerGroups().map((group) => group.layers())); + } + initWindow() { const container = this.window = L.DomUtil.create('div', 'leaflet-layers-dialog-wrapper'); @@ -40,11 +44,24 @@ class LayersConfigDialog { <div class="section-header" data-bind="html: group"></div> <!-- ko foreach: layers --> <label class="layer-label"> - <input type="checkbox" data-bind="checked: enabled"/> - <span data-bind="text: title"> - </span> - <input type="text" class="hotkey-input" size="1" maxlength="1" - data-bind="value: hotkey, event: {keypress: $root.validateHotkeyAndDisplayError.bind($root, event, hotkey)}"/> + <input type="checkbox" class="layer-enabled-checkbox" data-bind="checked: enabled"/> + <span data-bind="text: title"></span> + <div class="hotkey-input" + title="Change hotkey" + tabindex="0" + data-bind=" + text: hotkey, + event: { + keyup: $root.onHotkeyInput.bind($root), + keypress: function() {}, + click: function(_, e) {e.target.focus()}, + blur: function() {error(null)}, + }, + clickBubble: false, + keypressBubble: false, + keyupBubble: false"> + ></div> + <div class="error" data-bind="text: error, visible: error"></div> </label> <!-- /ko --> <!-- /ko --> @@ -79,6 +96,7 @@ class LayersConfigDialog { enabled: ko.observable(l.enabled), hotkey: ko.observable(l.layer.hotkey), origLayer: l, + error: ko.observable(null), })) ), }); @@ -92,6 +110,7 @@ class LayersConfigDialog { enabled: ko.observable(l.enabled), hotkey: ko.observable(l.layer.hotkey), origLayer: l, + error: ko.observable(null), })) ), }); @@ -99,21 +118,17 @@ class LayersConfigDialog { } updateLayersFromModel() { - for (const group of this.layerGroups()) { - for (const layer of group.layers()) { - layer.origLayer.enabled = layer.enabled(); - layer.origLayer.layer.hotkey = layer.hotkey(); - } + for (const layer of this.allLayerModels()) { + layer.origLayer.enabled = layer.enabled(); + layer.origLayer.layer.hotkey = layer.hotkey(); } } getLayersEnabledOnlyInModel() { const newLayers = []; - for (const group of this.layerGroups()) { - for (const layer of group.layers()) { - if (layer.enabled() && !layer.origLayer.enabled) { - newLayers.push(layer.origLayer); - } + for (const layer of this.allLayerModels()) { + if (layer.enabled() && !layer.origLayer.enabled) { + newLayers.push(layer.origLayer); } } return newLayers; @@ -131,51 +146,39 @@ class LayersConfigDialog { } onResetClicked() { - for (const group of this.layerGroups()) { - for (const layer of group.layers()) { - layer.enabled(layer.origLayer.isDefault); - } + for (const layer of this.allLayerModels()) { + layer.enabled(layer.origLayer.isDefault); } } - validateHotkeyAndDisplayError(e, hotkey) { - const isValid = this.validateHotkey(e, hotkey); - - if (!isValid) { - setTimeout(() => { - e.target.value = ''; - }, 0); + onHotkeyInput(layerModel, event) { + layerModel.error(null); + if (['Delete', 'Backspace', 'Space'].includes(event.code)) { + layerModel.hotkey(null); + return; } - e.target.setAttribute('data-error', !isValid); - - return isValid; - } - - validateHotkey(e, hotkey) { - const hotkeys = this.allLayers().map((layer) => layer.layer.hotkey).filter((layer) => layer); - - const codeRegexp = /^Key|Digit/u; - - if (!codeRegexp.test(e.code)) { - return false; + if (/Enter|Escape/u.test(event.code)) { + event.target.blur(); + return; } - let value = e.code.replace(codeRegexp, ''); - - setTimeout(() => { - e.target.value = value; - }, 0); - - if (hotkey && hotkeys.includes(value)) { - return value === hotkey; + if (/Alt|Shift|Control|Tab|Lock|Meta|ContextMenu|Lang|Arrow/u.test(event.code)) { + return; } - - if (!hotkey) { - return !hotkeys.includes(value); + const match = /^(Key|Digit)(.)/u.exec(event.code); + if (!match) { + layerModel.error('Only keys A-Z and 0-9 can be used for hotkeys.'); + return; } - - return true; + const newHotkey = match[2]; + for (const layer of this.allLayerModels()) { + if (layer !== layerModel && layer.hotkey() === newHotkey) { + layerModel.error(`Hotkey "${newHotkey}" is already used by layer "${layer.title}"`); + return; + } + } + layerModel.hotkey(newHotkey); } } @@ -638,11 +641,9 @@ function enableConfig(control, {layers, customLayersOrder}) { return; } - const layerPos = this._customLayers.indexOf(layer); - this._customLayers.remove(layer); const newLayer = this.createCustomLayer(newFieldValues); newLayer.layer.hotkey = layer.layer.hotkey; - this._customLayers.splice(layerPos, 0, newLayer); + this._customLayers.splice(this._customLayers.indexOf(layer), 1, newLayer); const newLayerVisible = ( this._map.hasLayer(layer.layer) && // turn off layer if changing from overlay to baselayer @@ -661,7 +662,7 @@ function enableConfig(control, {layers, customLayersOrder}) { onCustomLayerDeleteClicked: function(layer) { this._map.removeLayer(layer.layer); - this._customLayers.remove(layer); + this._customLayers.splice(this._customLayers.indexOf(layer), 1); this.updateLayers(); this.hideCustomLayerForm(); }, diff --git a/src/lib/leaflet.control.layers.configure/style.css b/src/lib/leaflet.control.layers.configure/style.css @@ -68,6 +68,8 @@ .leaflet-layers-config-window .layer-label { display: block; + position: relative; + vertical-align: bottom; } .leaflet-layers-config-window .button, @@ -164,18 +166,38 @@ color: #777; } +.leaflet-layers-config-window .layer-enabled-checkbox { + vertical-align: bottom; +} + .leaflet-layers-config-window .hotkey-input { - color: #999; + display: inline-block; + text-align: center; + vertical-align: bottom; + color: hsl(0, 0%, 50%); border: 1px solid transparent; - font-size: 9px; + font-size: 10px; + width: 15px; + height: 15px; + border-radius: 2px; } -.leaflet-layers-config-window .hotkey-input:focus, -.leaflet-layers-config-window .layer-label:hover .hotkey-input { - border-color: #999; +.leaflet-layers-config-window .error { + color: hsl(0, 60%, 50%); + background-color: hsl(0, 0%, 97%); + border-radius: 4px; + position: absolute; + width: 100%; + top: 100%; + z-index: 10000; + padding: 4px; } -.leaflet-layers-config-window .hotkey-input:focus[data-error=true] { - border-color: red !important; - transition: border-color 2s 0s; +.leaflet-layers-config-window .hotkey-input:focus { + border-color: hsl(0, 0%, 20%) !important; + color: hsl(0, 0%, 10%) !important; +} + +.leaflet-layers-config-window .layer-label:hover .hotkey-input { + border-color: hsl(0, 0%, 80%); }