Skip to content

Commit d0c473f

Browse files
silvester-parispectrachrome
andauthoredOct 12, 2023
Layercontrol/main (#300)
* chore(docs): fix typo * chore: re-introduce rendering hack and update e2e tests * chore: bump version * fix: keying issue in loops and prevent endless loop * Layercontrol refactoring (#271) * chore(WIP): layercontrol refactoring * chore: finalize formatting, linting, tests * chore: update stories * chore: remove base class, remove unneeded package, formatting, documentation * Expand/contract layer configs (#244) * fix: do not wrap numbers in quotation marks * feat: move commits from other branch * style: reduce caret width to 0 * fix: modify layerconfig to remove awkward 9-pixel gap for collapsed entries * wip: start implementing tab layout from figma * chore: define info mode for testing * style: remove drag handle * chore: put drag handle into tabs component * style: fix icons * style: switch to vertical dots * chore: add info and opacity tools for testing * wip: render different layer config in different modes * fix: non-appearing info tab content * feat(wip): implement a generic slider group component for use in tabs object * Merge branch 'layercontrol/chore/refactor-structure' into layercontrol/feature/expandable-layerconfig * feat: evolution of tabs functionality in new structure * fix: merging mistake * chore: move tools into separate component * chore: remove disabled layer --------- Co-authored-by: Silvester <[email protected]> * fix: sorting animation --------- Co-authored-by: Moritz Riede <[email protected]>
1 parent 50c16f7 commit d0c473f

30 files changed

+1906
-1320
lines changed
 

‎.eslintrc

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
{
2-
"extends": ["@eox", "plugin:@typescript-eslint/recommended"],
2+
"extends": [
3+
"@eox",
4+
"plugin:cypress/recommended",
5+
"plugin:@typescript-eslint/recommended"
6+
],
37
"ignorePatterns": ["**/dist/*", "**/public/*"],
48
"parser": "@typescript-eslint/parser",
59
"plugins": ["cypress", "@typescript-eslint"],
6-
"root": true
10+
"root": true,
11+
"rules": {
12+
"@typescript-eslint/ban-ts-comment": "warn"
13+
}
714
}

‎custom-elements-manifest.config.mjs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
export default {
2-
globs: ["**/main.ts"],
2+
globs: ["**/main.js", "**/main.ts"],
33
};

‎cypress/e2e/agri.cy.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ describe("Layer Switcher", () => {
3131

3232
it("adapts to setLayers with changed layer id", () => {
3333
cy.fixture("agri.json").then((layers) => {
34-
layers[0].id = "changedId";
34+
layers[0].properties.id = "changedId";
3535

3636
cy.get("eox-map").then(($el) => {
3737
const eoxMap = $el[0];
@@ -54,7 +54,7 @@ describe("Layer Switcher", () => {
5454
const groupLayerIndex = layers.findIndex(
5555
(layer) => layer.type === "Group"
5656
);
57-
layers[groupLayerIndex].layers[0].id = "changed-nested-Id";
57+
layers[groupLayerIndex].layers[0].properties.id = "changed-nested-Id";
5858

5959
cy.get("eox-map").then(($el) => {
6060
const eoxMap = $el[0];

‎cypress/fixtures/agri.json

+20-10
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,41 @@
11
[
22
{
33
"type": "Vector",
4-
"id": "draw",
5-
"title": "Reference",
4+
"properties": {
5+
"id": "draw",
6+
"title": "Reference"
7+
},
68
"source": { "type": "Vector" }
79
},
810
{
911
"type": "Tile",
10-
"id": "osm",
11-
"title": "Background",
12+
"properties": {
13+
"id": "osm",
14+
"title": "Background"
15+
},
1216
"source": { "type": "OSM" }
1317
},
1418
{
1519
"type": "Group",
16-
"id": "myGroup",
17-
"title": "My Group",
20+
"properties": {
21+
"id": "myGroup",
22+
"title": "My Group"
23+
},
1824
"layers": [
1925
{
2026
"type": "Vector",
21-
"id": "nested-layer-1",
22-
"title": "Nested 1",
27+
"properties": {
28+
"id": "nested-layer-1",
29+
"title": "Nested 1"
30+
},
2331
"source": { "type": "Vector" }
2432
},
2533
{
2634
"type": "Vector",
27-
"id": "nested-layer-2",
28-
"title": "Nested 2",
35+
"properties": {
36+
"id": "nested-layer-2",
37+
"title": "Nested 2"
38+
},
2939
"source": { "type": "Vector" }
3040
}
3141
]

‎elements/layercontrol/README.md

+3-12
Original file line numberDiff line numberDiff line change
@@ -35,22 +35,14 @@ document.querySelector("map-div").map = olMap
3535
<eox-layercontrol for="#map-div"></eox-layercontrol>
3636
```
3737

38-
### `layerIdentifier: string = "id"`
38+
### `idProperty: string = "id"`
3939

4040
The layer identifier property of the layers. Fallback is set automatically based on `ol_uid` if not provided.
4141

42-
### `layerTitle: string = "title"`
42+
### `titleProperty: string = "title"`
4343

4444
The layer title property of the layers. "title" by default, fallback is set automatically based on "layer" + `ol_uid` if not provided.
4545

46-
### `layerConfig: Array<string> = ["opacity"]`
47-
48-
// TODO
49-
50-
### `externalLayerConfig: Boolean = undefined`
51-
52-
// TODO
53-
5446
### `unstyled: Boolean`
5547

5648
Display the unstyled version of the layer control.
@@ -79,7 +71,7 @@ Initially hide a layer from the layer control, but make it available as an optio
7971

8072
Make layers mutually exclusive. If two or more layers (on the same level, i.e. at root or inside a layer group) have this property, then only one of them can be visualized at a time.
8173

82-
### `layerControlExpanded?: Boolean`
74+
### `layerControlExpand?: Boolean`
8375

8476
Pre-expand a layer dropdown so that it is always open when the component initializes.
8577

@@ -101,7 +93,6 @@ npm publish (requires OTP)
10193

10294
// TODO
10395

104-
10596
## Changelog
10697

10798
Created automatically [here](./CHANGELOG.md)

‎elements/layercontrol/layercontrol.stories.js

+225-21
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ const map = html` <eox-map
1111
"properties": {
1212
"id": "group2",
1313
"title": "Data Layers",
14-
"layerControlExpand": true
14+
"layerControlExpand": true,
15+
"description": "# Hello world"
1516
},
1617
"layers": [
1718
{
@@ -132,10 +133,19 @@ export default {
132133
* Basic layercontrol setup.
133134
*/
134135
export const Primary = {
135-
args: {},
136-
render: (args, test) => html`
136+
args: {
137+
idProperty: "id",
138+
titleProperty: "title",
139+
unstyled: false,
140+
},
141+
render: (args) => html`
137142
<div style="display: flex">
138-
<eox-layercontrol for="eox-map"></eox-layercontrol>
143+
<eox-layercontrol
144+
.idProperty=${args.idProperty}
145+
.titleProperty=${args.titleProperty}
146+
.unstyled=${args.unstyled}
147+
for="eox-map"
148+
></eox-layercontrol>
139149
${map}
140150
</div>
141151
`,
@@ -199,16 +209,6 @@ export const OptionalLayers = {
199209
id="optional"
200210
style="width: 400px; height: 300px; margin-left: 7px;"
201211
layers=${JSON.stringify([
202-
{
203-
type: "Tile",
204-
properties: {
205-
title: "Terrain Light",
206-
},
207-
source: {
208-
type: "XYZ",
209-
url: "//s2maps-tiles.eu/wmts/1.0.0/terrain-light_3857/default/g/{z}/{y}/{x}.jpg",
210-
},
211-
},
212212
{
213213
type: "Tile",
214214
properties: {
@@ -245,6 +245,16 @@ export const OptionalLayers = {
245245
},
246246
visible: false,
247247
},
248+
{
249+
type: "Tile",
250+
properties: {
251+
title: "Terrain Light",
252+
},
253+
source: {
254+
type: "XYZ",
255+
url: "//s2maps-tiles.eu/wmts/1.0.0/terrain-light_3857/default/g/{z}/{y}/{x}.jpg",
256+
},
257+
},
248258
])}
249259
>
250260
</eox-map>
@@ -276,16 +286,24 @@ export const ExpandedLayers = {
276286
},
277287
},
278288
{
279-
type: "Tile",
289+
type: "Group",
280290
properties: {
281-
title: "EOxCloudless",
291+
title: "Layer group",
282292
layerControlExpand: true,
283293
},
284-
source: {
285-
type: "XYZ",
286-
url: "//s2maps-tiles.eu/wmts/1.0.0/s2cloudless-2021_3857/default/g/{z}/{y}/{x}.jpg",
287-
},
288-
visible: false,
294+
layers: [
295+
{
296+
type: "Tile",
297+
properties: {
298+
title: "EOxCloudless",
299+
},
300+
source: {
301+
type: "XYZ",
302+
url: "//s2maps-tiles.eu/wmts/1.0.0/s2cloudless-2021_3857/default/g/{z}/{y}/{x}.jpg",
303+
},
304+
visible: false,
305+
},
306+
],
289307
},
290308
])}
291309
>
@@ -294,6 +312,58 @@ export const ExpandedLayers = {
294312
`,
295313
};
296314

315+
/**
316+
* The layer control accepts a "tools" array, which enable
317+
* extra functionalities for layers
318+
*/
319+
export const Tools = {
320+
args: {},
321+
render: (args, test) => html`
322+
<p>Default tools: info, opacity, remove, sort</p>
323+
<eox-layercontrol for="eox-map#tools"></eox-layercontrol>
324+
<hr />
325+
<p>Only one tool: info</p>
326+
<eox-layercontrol .tools=${["info"]} for="eox-map#tools"></eox-layercontrol>
327+
<hr />
328+
<p>Only one tool: sort</p>
329+
<eox-layercontrol .tools=${["sort"]} for="eox-map#tools"></eox-layercontrol>
330+
<hr />
331+
<p>No tools</p>
332+
<eox-layercontrol .tools=${[]} for="eox-map#tools"></eox-layercontrol>
333+
<eox-map
334+
id="tools"
335+
style="width: 400px; height: 300px; margin-left: 7px;"
336+
layers=${JSON.stringify([
337+
{
338+
type: "Vector",
339+
properties: {
340+
title: "Regions",
341+
id: "regions",
342+
description: "Ecological regions of the earth.",
343+
},
344+
source: {
345+
type: "Vector",
346+
url: "https://openlayers.org/data/vector/ecoregions.json",
347+
format: "GeoJSON",
348+
attributions: "Regions: @ openlayers.org",
349+
},
350+
},
351+
{
352+
type: "Tile",
353+
properties: {
354+
title: "Terrain Light",
355+
},
356+
source: {
357+
type: "XYZ",
358+
url: "//s2maps-tiles.eu/wmts/1.0.0/terrain-light_3857/default/g/{z}/{y}/{x}.jpg",
359+
},
360+
},
361+
])}
362+
>
363+
</eox-map>
364+
`,
365+
};
366+
297367
/**
298368
* By adding the `layerControlHide` property to map layers,
299369
* they aren't displayed in the layer control at all (but may
@@ -338,3 +408,137 @@ export const HiddenLayers = {
338408
</div>
339409
`,
340410
};
411+
412+
export const SingleLayer = {
413+
args: { idProperty: "id", titleProperty: "title", unstyled: false },
414+
render: (args, test) => html`
415+
<div style="display: flex">
416+
<eox-layercontrol-layer
417+
.idProperty=${args.idProperty}
418+
.titleProperty=${args.titleProperty}
419+
.unstyled=${args.unstyled}
420+
></eox-layercontrol-layer>
421+
<eox-map
422+
id="single"
423+
style="width: 400px; height: 300px;"
424+
layers='[
425+
{
426+
"type": "Tile",
427+
"properties": {
428+
"id": "osm",
429+
"title": "Open Street Map"
430+
},
431+
"visible": true,
432+
"opacity": 0.5,
433+
"source": {
434+
"type": "OSM"
435+
}
436+
}
437+
]'
438+
></eox-map>
439+
</div>
440+
<script>
441+
const olMap = document.querySelector("eox-map#single").map;
442+
olMap.on("loadend", () => {
443+
const firstLayer = olMap.getLayers().getArray()[0];
444+
document.querySelector("eox-layercontrol-layer").layer = firstLayer;
445+
document.querySelector("eox-layercontrol-layer").olMap = olMap;
446+
});
447+
</script>
448+
`,
449+
};
450+
451+
export const LayerList = {
452+
args: { unstyled: false },
453+
render: (args, test) => html`
454+
<div style="display: flex">
455+
<eox-layercontrol-layer-list
456+
.unstyled=${args.unstyled}
457+
></eox-layercontrol-layer-list>
458+
<eox-map
459+
id="list"
460+
style="width: 400px; height: 300px;"
461+
layers='[
462+
{
463+
"type": "Tile",
464+
"opacity": 0.5,
465+
"visible": false,
466+
"properties": {
467+
"id": "wind",
468+
"title": "WIND"
469+
},
470+
"source": {
471+
"type": "TileWMS",
472+
"url": "https://services.sentinel-hub.com/ogc/wms/0635c213-17a1-48ee-aef7-9d1731695a54",
473+
"params": {
474+
"LAYERS": "AWS_VIS_WIND_V_10M"
475+
}
476+
}
477+
},
478+
{
479+
"type": "Tile",
480+
"properties": {
481+
"id": "osm",
482+
"title": "Open Street Map"
483+
},
484+
"visible": true,
485+
"source": {
486+
"type": "OSM"
487+
}
488+
},
489+
{
490+
"type": "Tile",
491+
"properties": {
492+
"id": "osm2",
493+
"title": "Another OSM"
494+
},
495+
"visible": true,
496+
"source": {
497+
"type": "OSM"
498+
}
499+
}
500+
]'
501+
></eox-map>
502+
</div>
503+
<script>
504+
const olMapList = document.querySelector("eox-map#list").map;
505+
olMapList.once("loadend", () => {
506+
const layerCollection = olMapList.getLayers();
507+
document.querySelector("eox-layercontrol-layer-list").layers =
508+
layerCollection;
509+
document.querySelector("eox-layercontrol-layer-list").olMapList =
510+
olMapList;
511+
});
512+
</script>
513+
`,
514+
};
515+
516+
/**
517+
* Layer control tabs
518+
*/
519+
export const Tabs = {
520+
render: () => html`
521+
<eox-layercontrol-tabs
522+
.actions=${["delete"]}
523+
.tabs=${["info", "opacity", "config"]}
524+
></eox-layercontrol-tabs>
525+
`,
526+
};
527+
528+
/**
529+
* Unstyled version of the Element
530+
*/
531+
export const Unstyled = {
532+
args: {
533+
unstyled: true,
534+
},
535+
render: (args) => html`
536+
<div style="display: flex">
537+
<eox-layercontrol
538+
.unstyled=${args.unstyled}
539+
for="eox-map"
540+
></eox-layercontrol>
541+
${map}
542+
</div>
543+
`,
544+
};

‎elements/layercontrol/package.json

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@eox/layercontrol",
3-
"version": "0.6.8",
3+
"version": "0.6.9",
44
"type": "module",
55
"devDependencies": {
66
"@eox/eslint-config": "^1.0.0",
@@ -23,11 +23,12 @@
2323
],
2424
"main": "./dist/eox-layercontrol.js",
2525
"scripts": {
26-
"build": "tsc && vite build",
27-
"watch": "tsc && vite build --watch",
26+
"build": "vite build",
27+
"watch": "vite build --watch",
2828
"format": "prettier --write .",
2929
"lint": "eslint --ext .js,.ts .",
30-
"lint:fix": "eslint --ext .js,.ts . --fix"
30+
"lint:fix": "eslint --ext .js,.ts . --fix",
31+
"typecheck": "tsc"
3132
},
3233
"dependencies": {
3334
"dayjs": "^1.11.8",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import { LitElement, html } from "lit";
2+
import { when } from "lit/directives/when.js";
3+
import { live } from "lit/directives/live.js";
4+
import "./layerTools";
5+
import { checkbox } from "../../../../utils/styles/checkbox";
6+
7+
/**
8+
* A single layer display
9+
*
10+
* @element eox-layercontrol-layer
11+
*/
12+
export class EOxLayerControlLayer extends LitElement {
13+
static properties = {
14+
layer: { attribute: false },
15+
titleProperty: { attribute: "title-property", type: String },
16+
tools: { attribute: false },
17+
unstyled: { type: Boolean },
18+
};
19+
20+
constructor() {
21+
super();
22+
23+
/**
24+
* The native OL layer
25+
* @type {import("ol/layer").Layer}
26+
* @see {@link https://openlayers.org/en/latest/apidoc/module-ol_layer_Layer-Layer.html}
27+
*/
28+
this.layer = null;
29+
30+
/**
31+
* The layer title property
32+
*/
33+
this.titleProperty = "title";
34+
35+
/**
36+
* @type Array<string>
37+
*/
38+
this.tools = [];
39+
40+
/**
41+
* Render the element without additional styles
42+
*/
43+
this.unstyled = false;
44+
}
45+
46+
createRenderRoot() {
47+
return this;
48+
}
49+
50+
render() {
51+
return html`
52+
<style>
53+
${this.#styleBasic}
54+
${!this.unstyled && this.#styleEOX}
55+
</style>
56+
${when(
57+
this.layer,
58+
() => html`
59+
<div class="layer ${this.layer.getVisible() ? "visible" : ""}">
60+
<label
61+
class="${this.layer.get("layerControlDisable") ? "disabled" : ""}"
62+
>
63+
<input
64+
type=${this.layer.get("layerControlExclusive")
65+
? "radio"
66+
: "checkbox"}
67+
.checked=${live(this.layer.getVisible())}
68+
@click=${(
69+
/** @type {{ target: { checked: boolean; }; }} */ evt
70+
) => {
71+
this.layer.setVisible(evt.target.checked);
72+
if (
73+
evt.target.checked &&
74+
this.layer.get("layerControlExclusive")
75+
) {
76+
/**
77+
* @type NodeListOf<Element & {layer: any, requestUpdate: function}>
78+
*/
79+
const siblings =
80+
this.parentNode.parentNode.querySelectorAll(
81+
"li > eox-layercontrol-layer"
82+
);
83+
siblings.forEach((sibling) => {
84+
if (
85+
sibling.layer !== this.layer &&
86+
sibling.layer?.get("layerControlExclusive")
87+
) {
88+
sibling.layer.setVisible(false);
89+
sibling.requestUpdate();
90+
}
91+
});
92+
}
93+
this.dispatchEvent(
94+
new CustomEvent("changed", { bubbles: true })
95+
);
96+
this.requestUpdate();
97+
}}
98+
/>
99+
<span class="title">${this.layer.get(this.titleProperty)}</span>
100+
${when(
101+
this.tools?.length > 0,
102+
() => html`<span class="tools-placeholder"></span>`
103+
)}
104+
</label>
105+
</div>
106+
<eox-layercontrol-layer-tools
107+
.layer=${this.layer}
108+
.tools=${this.tools}
109+
.unstyled=${this.unstyled}
110+
></eox-layercontrol-layer-tools>
111+
`
112+
)}
113+
`;
114+
}
115+
116+
#styleBasic = ``;
117+
118+
#styleEOX = `
119+
${checkbox}
120+
eox-layercontrol-layer {
121+
width: 100%;
122+
}
123+
.layer {
124+
width: 100%;
125+
align-items: center;
126+
justify-content: space-between;
127+
padding: 4px 0;
128+
}
129+
label, span {
130+
display: flex;
131+
align-items: center;
132+
cursor: pointer;
133+
}
134+
[data-type] .title::before {
135+
width: 20px;
136+
min-width: 20px;
137+
height: 20px;
138+
margin-right: 6px;
139+
}
140+
[data-type=group] .title::before {
141+
content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%230041703a' viewBox='0 0 24 24'%3E%3Ctitle%3Efolder-outline%3C/title%3E%3Cpath d='M20,18H4V8H20M20,6H12L10,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8C22,6.89 21.1,6 20,6Z' /%3E%3C/svg%3E");
142+
}
143+
[data-type=group] > eox-layercontrol-layer-group > details[open] > summary > eox-layercontrol-layer > .layer > label > .title::before {
144+
content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%230041703a' viewBox='0 0 24 24'%3E%3Ctitle%3Efolder-open-outline%3C/title%3E%3Cpath d='M6.1,10L4,18V8H21A2,2 0 0,0 19,6H12L10,4H4A2,2 0 0,0 2,6V18A2,2 0 0,0 4,20H19C19.9,20 20.7,19.4 20.9,18.5L23.2,10H6.1M19,18H6L7.6,12H20.6L19,18Z' /%3E%3C/svg%3E");
145+
}
146+
[data-type=raster] .title::before {
147+
content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%230041703a' viewBox='0 0 24 24'%3E%3Ctitle%3Echeckerboard%3C/title%3E%3Cpath d='M2 2V22H22V2H2M20 12H16V16H20V20H16V16H12V20H8V16H4V12H8V8H4V4H8V8H12V4H16V8H20V12M16 8V12H12V8H16M12 12V16H8V12H12Z' /%3E%3C/svg%3E");
148+
}
149+
[data-type=vector] .title::before {
150+
content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%230041703a' viewBox='0 0 24 24'%3E%3Ctitle%3Eshape-outline%3C/title%3E%3Cpath d='M11,13.5V21.5H3V13.5H11M9,15.5H5V19.5H9V15.5M12,2L17.5,11H6.5L12,2M12,5.86L10.08,9H13.92L12,5.86M17.5,13C20,13 22,15 22,17.5C22,20 20,22 17.5,22C15,22 13,20 13,17.5C13,15 15,13 17.5,13M17.5,15A2.5,2.5 0 0,0 15,17.5A2.5,2.5 0 0,0 17.5,20A2.5,2.5 0 0,0 20,17.5A2.5,2.5 0 0,0 17.5,15Z' /%3E%3C/svg%3E");
151+
}
152+
[data-type=draw] .title::before {
153+
content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%230041703a' viewBox='0 0 24 24'%3E%3Ctitle%3Evector-square-edit%3C/title%3E%3Cpath d='M22.7 14.4L21.7 15.4L19.6 13.3L20.6 12.3C20.8 12.1 21.2 12.1 21.4 12.3L22.7 13.6C22.9 13.8 22.9 14.1 22.7 14.4M13 19.9L19.1 13.8L21.2 15.9L15.1 22H13V19.9M11 19.9V19.1L11.6 18.5L12.1 18H8V16H6V8H8V6H16V8H18V12.1L19.1 11L19.3 10.8C19.5 10.6 19.8 10.4 20.1 10.3V8H22.1V2H16.1V4H8V2H2V8H4V16H2V22H8V20L11 19.9M18 4H20V6H18V4M4 4H6V6H4V4M6 20H4V18H6V20Z' /%3E%3C/svg%3E");
154+
}
155+
`;
156+
}
157+
158+
customElements.define("eox-layercontrol-layer", EOxLayerControlLayer);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { LitElement, html } from "lit";
2+
import { live } from "lit/directives/live.js";
3+
4+
/**
5+
* Layer configuration for an individual layer
6+
*
7+
* @element eox-layercontrol-layerconfig
8+
*/
9+
export class EOxLayerControlLayerConfig extends LitElement {
10+
static properties = {
11+
layer: { attribute: false },
12+
unstyled: { type: Boolean },
13+
};
14+
15+
constructor() {
16+
super();
17+
18+
/**
19+
* The native OL layer
20+
* @type {import("ol/layer").Layer}
21+
* @see {@link https://openlayers.org/en/latest/apidoc/module-ol_layer_Layer-Layer.html}
22+
*/
23+
this.layer = null;
24+
25+
/**
26+
* Render the element without additional styles
27+
*/
28+
this.unstyled = false;
29+
}
30+
31+
render() {
32+
return html`
33+
<style>
34+
${this.#styleBasic}
35+
${!this.unstyled && this.#styleEOX}
36+
</style>
37+
<input
38+
type="range"
39+
min="0"
40+
max="1"
41+
step="0.01"
42+
value=${live(this.layer?.getOpacity())}
43+
@input=${(/** @type {{ target: { value: string; }; }} */ evt) =>
44+
this.layer.setOpacity(parseFloat(evt.target.value))}
45+
/>
46+
<button
47+
class="delete"
48+
@click=${() => {
49+
this.layer?.set("layerControlOptional", true);
50+
this.layer?.setVisible(false);
51+
this.dispatchEvent(
52+
new CustomEvent("changed", { detail: this.layer, bubbles: true })
53+
);
54+
}}
55+
>
56+
x
57+
</button>
58+
`;
59+
}
60+
61+
#styleBasic = ``;
62+
63+
#styleEOX = ``;
64+
}
65+
66+
customElements.define(
67+
"eox-layercontrol-layerconfig",
68+
EOxLayerControlLayerConfig
69+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import { LitElement, html, nothing } from "lit";
2+
import { when } from "lit/directives/when.js";
3+
import "./layer";
4+
import "./layerList";
5+
6+
/**
7+
* Display of a layer group
8+
*
9+
* @element eox-layercontrol-layer-group
10+
*/
11+
export class EOxLayerControlLayerGroup extends LitElement {
12+
static properties = {
13+
group: { attribute: false },
14+
idProperty: { attribute: "id-property" },
15+
map: { attribute: false, state: true },
16+
titleProperty: { attribute: "title-property", type: String },
17+
tools: { attribute: false },
18+
unstyled: { type: Boolean },
19+
};
20+
21+
constructor() {
22+
super();
23+
24+
/**
25+
* The native OL layer group
26+
* @type {import("ol/layer").Group}
27+
* @see {@link https://openlayers.org/en/latest/apidoc/module-ol_layer_Group-LayerGroup.html}
28+
*/
29+
this.group = null;
30+
31+
/**
32+
* The layer id property
33+
*/
34+
this.idProperty = "id";
35+
36+
/**
37+
* The native OL map
38+
* @type {import("ol").Map}
39+
* @see {@link https://openlayers.org/en/latest/apidoc/module-ol_Map-Map.html}
40+
*/
41+
this.map = null;
42+
43+
/**
44+
* The layer title property
45+
*/
46+
this.titleProperty = "title";
47+
48+
/**
49+
* @type Array<string>
50+
*/
51+
this.tools = [];
52+
53+
/**
54+
* Render the element without additional styles
55+
*/
56+
this.unstyled = false;
57+
}
58+
59+
createRenderRoot() {
60+
return this;
61+
}
62+
63+
render() {
64+
return html`
65+
<style>
66+
${this.#styleBasic}
67+
${!this.unstyled && this.#styleEOX}
68+
</style>
69+
${when(
70+
this.group,
71+
() => html`
72+
<details open=${this.group.get("layerControlExpand") || nothing}>
73+
<summary>
74+
<eox-layercontrol-layer
75+
.layer=${this.group}
76+
.titleProperty=${this.titleProperty}
77+
.tools=${this.tools}
78+
.unstyled=${this.unstyled}
79+
@changed=${() => this.requestUpdate()}
80+
></eox-layercontrol-layer>
81+
</summary>
82+
<eox-layercontrol-layer-list
83+
.idProperty=${this.idProperty}
84+
.layers=${this.group.getLayers()}
85+
.map=${this.map}
86+
.titleProperty=${this.titleProperty}
87+
.tools=${this.tools}
88+
.unstyled=${this.unstyled}
89+
@changed=${() => this.requestUpdate()}
90+
></eox-layercontrol-layer-list>
91+
</details>
92+
`
93+
)}
94+
`;
95+
}
96+
97+
#styleBasic = ``;
98+
99+
#styleEOX = `
100+
details summary {
101+
cursor: pointer;
102+
display: flex;
103+
}
104+
details summary { list-style-type: none; } /* Firefox */
105+
details summary::-webkit-details-marker { display: none; } /* Chrome */
106+
details summary::marker { display: none; }
107+
details summary::before {
108+
display: block;
109+
content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23004170' viewBox='0 0 24 24'%3E%3Ctitle%3Echevron-right%3C/title%3E%3Cpath d='M8.59,16.58L13.17,12L8.59,7.41L10,6L16,12L10,18L8.59,16.58Z' /%3E%3C/svg%3E");
110+
font-size: 13px;
111+
width: 24px;
112+
height: 24px;
113+
margin: 4px 0;
114+
transform-origin: center;
115+
transition: transform 0.1s ease-in-out;
116+
}
117+
details[open] > summary:before {
118+
transform: rotate(90deg);
119+
}
120+
`;
121+
}
122+
123+
customElements.define(
124+
"eox-layercontrol-layer-group",
125+
EOxLayerControlLayerGroup
126+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
import { LitElement, html } from "lit";
2+
import { when } from "lit/directives/when.js";
3+
import { keyed } from "lit/directives/keyed.js";
4+
import { createSortable, getLayerType } from "../helpers";
5+
import "./layer";
6+
import "./layerGroup";
7+
8+
/**
9+
* Display of a list of layers
10+
*
11+
* @element eox-layercontrol-layer-list
12+
*/
13+
export class EOxLayerControlLayerList extends LitElement {
14+
static properties = {
15+
idProperty: { attribute: "id-property" },
16+
layers: { attribute: false },
17+
map: { attribute: false, state: true },
18+
titleProperty: { attribute: "title-property", type: String },
19+
tools: { attribute: false },
20+
unstyled: { type: Boolean },
21+
};
22+
23+
constructor() {
24+
super();
25+
26+
/**
27+
* The layer id property
28+
*/
29+
this.idProperty = "id";
30+
31+
/**
32+
* The OL layer collection
33+
* @type {import("ol").Collection<import("ol/layer").Layer | import("ol/layer").Group>}
34+
* @see {@link https://openlayers.org/en/latest/apidoc/module-ol_Collection-Collection.html}
35+
*/
36+
this.layers = null;
37+
38+
/**
39+
* The native OL map
40+
* @type {import("ol").Map}
41+
* @see {@link https://openlayers.org/en/latest/apidoc/module-ol_Map-Map.html}
42+
*/
43+
this.map = null;
44+
45+
/**
46+
* @type Array<string>
47+
*/
48+
this.tools = undefined;
49+
50+
/**
51+
* The layer title property
52+
*/
53+
this.titleProperty = "title";
54+
55+
/**
56+
* Render the element without additional styles
57+
*/
58+
this.unstyled = false;
59+
}
60+
61+
updated() {
62+
if (!this.layers) {
63+
return;
64+
}
65+
this.layers.on("change:length", () => {
66+
this.requestUpdate();
67+
this.dispatchEvent(new CustomEvent("changed", { bubbles: true }));
68+
});
69+
createSortable(this.renderRoot.querySelector("ul"), this.layers, this);
70+
}
71+
72+
createRenderRoot() {
73+
return this;
74+
}
75+
76+
render() {
77+
return html`
78+
<style>
79+
${this.#styleBasic}
80+
${!this.unstyled && this.#styleEOX}
81+
</style>
82+
<ul>
83+
${when(
84+
this.layers,
85+
() => html`
86+
${this.layers
87+
.getArray()
88+
.filter(
89+
(l) =>
90+
!l.get("layerControlHide") && !l.get("layerControlOptional")
91+
)
92+
.reverse()
93+
.map((layer) =>
94+
keyed(
95+
layer.get(this.idProperty),
96+
html`
97+
<li
98+
data-layer="${layer.get(this.idProperty)}"
99+
data-type="${getLayerType(layer, this.map)}"
100+
>
101+
${
102+
/** @type {import("ol/layer").Group} */ (layer)
103+
.getLayers
104+
? html`
105+
<eox-layercontrol-layer-group
106+
.group=${layer}
107+
.idProperty=${this.idProperty}
108+
.map=${this.map}
109+
.titleProperty=${this.titleProperty}
110+
.tools=${this.tools}
111+
.unstyled=${this.unstyled}
112+
@changed=${() => this.requestUpdate()}
113+
>
114+
</eox-layercontrol-layer-group>
115+
`
116+
: html`
117+
<eox-layercontrol-layer
118+
.layer=${layer}
119+
.titleProperty=${this.titleProperty}
120+
.tools=${this.tools}
121+
.unstyled=${this.unstyled}
122+
@changed=${() => {
123+
this.requestUpdate();
124+
}}
125+
></eox-layercontrol-layer>
126+
`
127+
}
128+
</li>
129+
`
130+
)
131+
)}
132+
`
133+
)}
134+
</ul>
135+
`;
136+
}
137+
138+
#styleBasic = ``;
139+
140+
#styleEOX = `
141+
ul {
142+
padding: 0;
143+
}
144+
ul ul {
145+
padding-left: 48px;
146+
}
147+
li {
148+
list-style: none;
149+
}
150+
li {
151+
border-bottom: 1px solid #0041703a;
152+
}
153+
li:first-child {
154+
border-top: 1px solid #0041703a;
155+
}
156+
li:last-child {
157+
border: none;
158+
}
159+
li.sortable-chosen {
160+
background: #eeea;
161+
}
162+
li.sortable-drag {
163+
opacity: 0;
164+
}
165+
li.sortable-ghost {
166+
}
167+
`;
168+
}
169+
170+
customElements.define("eox-layercontrol-layer-list", EOxLayerControlLayerList);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
import { LitElement, html } from "lit";
2+
import { unsafeHTML } from "lit/directives/unsafe-html.js";
3+
import { map } from "lit/directives/map.js";
4+
import { when } from "lit/directives/when.js";
5+
import { live } from "lit/directives/live.js";
6+
import "./layerConfig";
7+
import "./tabs";
8+
import { button } from "../../../../utils/styles/button";
9+
import { radio } from "../../../../utils/styles/radio";
10+
import { checkbox } from "../../../../utils/styles/checkbox";
11+
import { slider } from "../../../../utils/styles/slider";
12+
13+
/**
14+
* Layer tools
15+
*
16+
* @element eox-layercontrol-layer-tools
17+
*/
18+
export class EOxLayerControlLayerTools extends LitElement {
19+
static properties = {
20+
layer: { attribute: false },
21+
tools: { attribute: false },
22+
unstyled: { type: Boolean },
23+
};
24+
25+
constructor() {
26+
super();
27+
28+
/**
29+
* The native OL layer
30+
* @type {import("ol/layer").Layer}
31+
* @see {@link https://openlayers.org/en/latest/apidoc/module-ol_layer_Layer-Layer.html}
32+
*/
33+
this.layer = null;
34+
35+
/**
36+
* @type Array<string>
37+
*/
38+
this.tools = [];
39+
40+
/**
41+
* Render the element without additional styles
42+
*/
43+
this.unstyled = false;
44+
}
45+
46+
/**
47+
*
48+
* @param {Array<string>} tools
49+
*/
50+
_parseActions = (tools) =>
51+
tools?.filter((t) =>
52+
["remove", "sort"]
53+
.filter((k) =>
54+
this.layer?.get("layerControlDisable") ? k !== "sort" : true
55+
)
56+
.includes(t)
57+
);
58+
59+
/**
60+
*
61+
* @param {Array<string>} tools
62+
*/
63+
_parseTools = (tools) =>
64+
tools?.filter((t) => {
65+
let pass = true;
66+
if (["remove", "sort"].includes(t)) {
67+
pass = false;
68+
}
69+
if (t === "info") {
70+
pass = this.layer.get("description");
71+
}
72+
if (t === "config") {
73+
// @ts-ignore
74+
pass = this.layer.style_?.color;
75+
}
76+
return pass;
77+
});
78+
79+
_removeButton = html`
80+
<button
81+
class="remove-icon icon"
82+
@click=${() => {
83+
this.layer?.set("layerControlOptional", true);
84+
this.layer?.setVisible(false);
85+
this.dispatchEvent(
86+
new CustomEvent("changed", {
87+
detail: this.layer,
88+
bubbles: true,
89+
})
90+
);
91+
}}
92+
>
93+
x
94+
</button>
95+
`;
96+
97+
_sortButton = html`
98+
<button class="sort-icon icon drag-handle">sort</button>
99+
`;
100+
101+
createRenderRoot() {
102+
return this;
103+
}
104+
105+
render() {
106+
return html`
107+
<style>
108+
${this.#styleBasic}
109+
${!this.unstyled && this.#styleEOX}
110+
</style>
111+
${when(
112+
this._parseActions(this.tools)?.length +
113+
this._parseTools(this.tools)?.length >
114+
0,
115+
() => html`
116+
${when(
117+
this._parseActions(this.tools)?.length === 1 &&
118+
this._parseTools(this.tools)?.length === 0,
119+
() => html`
120+
<div class="single-action-container">
121+
<div class="single-action">
122+
${
123+
// @ts-ignore
124+
this[`_${this._parseActions(this.tools)[0]}Button`]
125+
}
126+
</div>
127+
</div>
128+
`,
129+
() => html`
130+
<details class="tools">
131+
<summary>
132+
<button
133+
class="icon ${this.tools.length === 1
134+
? `${this.tools[0]}-icon`
135+
: ""}"
136+
>
137+
Tools
138+
</button>
139+
</summary>
140+
<eox-layercontrol-tabs
141+
.actions=${this._parseActions(this.tools)}
142+
.tabs=${this._parseTools(this.tools)}
143+
.unstyled=${this.unstyled}
144+
>
145+
${map(
146+
this._parseTools(this.tools),
147+
(tool) => html`
148+
<button slot="${tool}-icon" class="icon">${tool}</button>
149+
`
150+
)}
151+
152+
<div slot="info-content">
153+
${unsafeHTML(this.layer.get("description"))}
154+
</div>
155+
<div slot="opacity-content">
156+
<input
157+
type="range"
158+
min="0"
159+
max="1"
160+
step="0.01"
161+
value=${live(this.layer?.getOpacity())}
162+
@input=${(
163+
/** @type {{ target: { value: string; }; }} */ evt
164+
) => this.layer.setOpacity(parseFloat(evt.target.value))}
165+
/>
166+
</div>
167+
<div slot="config-content"></div>
168+
<!--<eox-layercontrol-layerconfig
169+
slot="config-content"
170+
.layer=${this.layer}
171+
.unstyled=${this.unstyled}
172+
@changed=${() => this.requestUpdate()}
173+
></eox-layercontrol-layerconfig>-->
174+
<div slot="remove-icon">${this._removeButton}</div>
175+
<div slot="sort-icon">${this._sortButton}</div>
176+
</eox-layercontrol-tabs>
177+
</details>
178+
`
179+
)}
180+
`
181+
)}
182+
`;
183+
}
184+
185+
#styleBasic = ``;
186+
187+
#styleEOX = `
188+
${button}
189+
${radio}
190+
${checkbox}
191+
${slider}
192+
button.icon.drag-handle {
193+
cursor: n-resize;
194+
}
195+
.single-action-container,
196+
details.tools {
197+
position: relative;
198+
}
199+
eox-layercontrol-layer details summary::before {
200+
content: "";
201+
}
202+
details.tools[open] {
203+
/*border-top: 1px solid #0041703a;*/
204+
}
205+
.single-action {
206+
position: relative;
207+
}
208+
details.tools summary .icon {
209+
pointer-events: none;
210+
}
211+
.single-action,
212+
details.tools summary {
213+
position: absolute;
214+
right: 0;
215+
top: -24px;
216+
display: flex;
217+
border-radius: 4px;
218+
cursor: pointer;
219+
}
220+
.single-action .icon::before,
221+
details.tools summary .icon::before {
222+
content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23004170' viewBox='0 0 24 24'%3E%3Ctitle%3Edots-vertical%3C/title%3E%3Cpath d='M12,16A2,2 0 0,1 14,18A2,2 0 0,1 12,20A2,2 0 0,1 10,18A2,2 0 0,1 12,16M12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8A2,2 0 0,1 10,6A2,2 0 0,1 12,4Z' /%3E%3C/svg%3E");
223+
}
224+
.single-action,
225+
details.tools summary,
226+
eox-layercontrol-tabs button.icon {
227+
transition: opacity .2s;
228+
}
229+
.single-action,
230+
details.tools summary {
231+
opacity: .5;
232+
}
233+
eox-layercontrol-tabs button.icon {
234+
opacity: .7;
235+
}
236+
.single-action:hover,
237+
details.tools summary:hover,
238+
eox-layercontrol-tabs button.icon:hover {
239+
opacity: 1;
240+
}
241+
.tools-placeholder,
242+
.single-action .icon,
243+
.single-action .icon::before,
244+
details.tools summary .icon,
245+
details.tools summary .icon::before {
246+
height: 16px;
247+
width: 16px;
248+
}
249+
eox-layercontrol-tabs button.icon {
250+
display: flex;
251+
justify-content: center;
252+
}
253+
eox-layercontrol-tabs .icon::before {
254+
width: 16px;
255+
height: 16px;
256+
}
257+
details.tools summary .info-icon,
258+
button.icon[slot=info-icon]::before {
259+
content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23004170' viewBox='0 0 24 24'%3E%3Ctitle%3Einformation-outline%3C/title%3E%3Cpath d='M11,9H13V7H11M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M11,17H13V11H11V17Z' /%3E%3C/svg%3E");
260+
}
261+
details.tools summary .opacity-icon,
262+
button.icon[slot=opacity-icon]::before {
263+
content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23004170' viewBox='0 0 24 24'%3E%3Ctitle%3Eopacity%3C/title%3E%3Cpath d='M17.66,8L12,2.35L6.34,8C4.78,9.56 4,11.64 4,13.64C4,15.64 4.78,17.75 6.34,19.31C7.9,20.87 9.95,21.66 12,21.66C14.05,21.66 16.1,20.87 17.66,19.31C19.22,17.75 20,15.64 20,13.64C20,11.64 19.22,9.56 17.66,8M6,14C6,12 6.62,10.73 7.76,9.6L12,5.27L16.24,9.65C17.38,10.77 18,12 18,14H6Z' /%3E%3C/svg%3E");
264+
}
265+
details.tools summary .config-icon,
266+
button.icon[slot=config-icon]::before {
267+
content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23004170' viewBox='0 0 24 24'%3E%3Ctitle%3Etune%3C/title%3E%3Cpath d='M3,17V19H9V17H3M3,5V7H13V5H3M13,21V19H21V17H13V15H11V21H13M7,9V11H3V13H7V15H9V9H7M21,13V11H11V13H21M15,9H17V7H21V5H17V3H15V9Z' /%3E%3C/svg%3E");
268+
}
269+
.single-action .remove-icon::before,
270+
[slot=remove-icon] button.icon::before {
271+
content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23ff0000' viewBox='0 0 24 24'%3E%3Ctitle%3Edelete-outline%3C/title%3E%3Cpath d='M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19M8,9H16V19H8V9M15.5,4L14.5,3H9.5L8.5,4H5V6H19V4H15.5Z' /%3E%3C/svg%3E");
272+
}
273+
.single-action .sort-icon::before,
274+
[slot=sort-icon] button.icon::before {
275+
content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23004170' viewBox='0 0 24 24'%3E%3Ctitle%3Edrag-horizontal-variant%3C/title%3E%3Cpath d='M21 11H3V9H21V11M21 13H3V15H21V13Z' /%3E%3C/svg%3E");
276+
}
277+
[slot=info-content],
278+
[slot=opacity-content] {
279+
padding: 12px 6px;
280+
}
281+
`;
282+
}
283+
284+
customElements.define(
285+
"eox-layercontrol-layer-tools",
286+
EOxLayerControlLayerTools
287+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import { LitElement, html } from "lit";
2+
import { filterLayers } from "../helpers";
3+
4+
export class EOxLayerControlOptionalList extends LitElement {
5+
static properties = {
6+
idProperty: { attribute: "id-property" },
7+
layers: { attribute: false },
8+
titleProperty: { attribute: "title-property", type: String },
9+
unstyled: { type: Boolean },
10+
};
11+
12+
constructor() {
13+
super();
14+
15+
/**
16+
* The layer id property
17+
*/
18+
this.idProperty = "id";
19+
20+
/**
21+
* The OL layer collection
22+
* @type {import("ol").Collection<import("ol/layer").Layer>}
23+
* @see {@link https://openlayers.org/en/latest/apidoc/module-ol_Collection-Collection.html}
24+
*/
25+
this.layers = null;
26+
27+
/**
28+
* The layer title property
29+
*/
30+
this.titleProperty = "title";
31+
32+
/**
33+
* Render the element without additional styles
34+
*/
35+
this.unstyled = false;
36+
}
37+
38+
createRenderRoot() {
39+
return this;
40+
}
41+
42+
render() {
43+
return html`
44+
<label for="optional">Optional layers</label>
45+
46+
<select name="optional" data-cy="optionalLayers">
47+
<option disabled selected value>
48+
-- select an optional layer to add --
49+
</option>
50+
${filterLayers(
51+
this.layers.getArray(),
52+
"layerControlOptional",
53+
true
54+
).map(
55+
(layer) => html`
56+
<option
57+
value="${
58+
// @ts-ignore
59+
layer.get(this.idProperty) || layer.ol_uid
60+
}"
61+
>
62+
${layer.get(this.titleProperty) ||
63+
`layer ${layer.get(this.idProperty)}`}
64+
</option>
65+
`
66+
)}
67+
</select>
68+
<button
69+
@click="${() => {
70+
const selectedLayer = filterLayers(
71+
this.layers.getArray(),
72+
"layerControlOptional",
73+
true
74+
).find((l) => {
75+
return (
76+
// @ts-ignore
77+
(l.get(this.idProperty) || l.ol_uid) ===
78+
/** @type HTMLInputElement*/ (
79+
this.querySelector("select[name=optional]")
80+
).value
81+
);
82+
});
83+
// TODO always set the new layer at the first position
84+
selectedLayer?.set("layerControlOptional", false);
85+
selectedLayer?.setVisible(true);
86+
this.dispatchEvent(new CustomEvent("changed", { bubbles: true }));
87+
this.renderRoot.parentNode
88+
.querySelectorAll("eox-layercontrol-layer-list")
89+
.forEach((layerList) =>
90+
/** @type {import("lit").LitElement} */ (
91+
layerList
92+
).requestUpdate()
93+
);
94+
this.requestUpdate();
95+
}}"
96+
>
97+
add
98+
</button>
99+
`;
100+
}
101+
}
102+
103+
customElements.define(
104+
"eox-layercontrol-optional-list",
105+
EOxLayerControlOptionalList
106+
);

‎elements/layercontrol/src/components/tab-sliders.js

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import { LitElement, html } from "lit";
2+
import { when } from "lit/directives/when.js";
3+
import { map } from "lit/directives/map.js";
4+
5+
export class EOxLayerControlTabs extends LitElement {
6+
static properties = {
7+
actions: { attribute: false },
8+
selectedTab: { state: true },
9+
tabs: { attribute: false },
10+
unstyled: { type: Boolean },
11+
};
12+
13+
constructor() {
14+
super();
15+
16+
/**
17+
* List of action ids
18+
* @type Array<string>
19+
*/
20+
this.actions = [];
21+
22+
this.selectedTab = 0;
23+
24+
/**
25+
* List of tab ids
26+
* @type Array<string>
27+
*/
28+
this.tabs = [];
29+
30+
/**
31+
* Render the element without additional styles
32+
*/
33+
this.unstyled = false;
34+
}
35+
36+
render() {
37+
return html`
38+
<style>
39+
${this.#styleBasic}
40+
${!this.unstyled && this.#styleEOX}
41+
</style>
42+
<div class="tabbed">
43+
${when(
44+
this.actions.length + this.tabs.length > 1,
45+
() => html`
46+
<nav>
47+
<div>
48+
${map(
49+
this.tabs,
50+
(tab, index) => html`
51+
<label
52+
class=${this.selectedTab === index && "highlighted"}
53+
@click=${() => (this.selectedTab = index)}
54+
>
55+
<slot name=${tab + "-icon"}>${tab}</slot>
56+
</label>
57+
`
58+
)}
59+
</div>
60+
<div>
61+
${map(
62+
this.actions,
63+
(action) => html`
64+
<span>
65+
<slot name=${action + "-icon"}>${action}</slot>
66+
</span>
67+
`
68+
)}
69+
</div>
70+
</nav>
71+
`
72+
)}
73+
<figure>
74+
${map(
75+
this.tabs,
76+
(tab, index) => html`
77+
<div class="tab ${this.selectedTab === index && "highlighted"}">
78+
<slot name=${tab + "-content"}>${tab}</slot>
79+
</div>
80+
`
81+
)}
82+
</figure>
83+
</div>
84+
`;
85+
}
86+
87+
#styleBasic = `
88+
.tabbed figure {
89+
margin: 0;
90+
}
91+
.tabbed nav {
92+
display: flex;
93+
justify-content: space-between;
94+
}
95+
.tabbed nav div {
96+
display: flex;
97+
}
98+
.tabbed .tab {
99+
display: none;
100+
}
101+
.tabbed .tab.highlighted {
102+
display: block;
103+
}
104+
.tabbed label.highlighted {
105+
background: lightgrey;
106+
}
107+
`;
108+
109+
#styleEOX = `
110+
.tabbed {
111+
font-size: small;
112+
}
113+
.tabbed label.highlighted {
114+
background: #00417011;
115+
}
116+
nav div label,
117+
nav div span {
118+
width: 20px;
119+
height: 20px;
120+
display: flex;
121+
align-items: center;
122+
justify-content: center;
123+
cursor: pointer;
124+
}
125+
figure {
126+
background: #00417011;
127+
border-top: 1px solid #0041701a;
128+
}
129+
`;
130+
}
131+
132+
customElements.define("eox-layercontrol-tabs", EOxLayerControlTabs);

‎elements/layercontrol/src/helpers.js

+123
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import Sortable from "sortablejs";
2+
3+
/**
4+
*
5+
* @param {HTMLElement} element
6+
* @param {import("ol").Collection<import("ol/layer").Layer | import("ol/layer").Group>} layers
7+
* @param {import("lit").LitElement} that
8+
*/
9+
export const createSortable = (element, layers, that) => {
10+
/**
11+
* @type {any[]}
12+
*/
13+
let childNodes = [];
14+
/** @type HTMLElement & {_sortable: import("sortablejs")}*/ (
15+
element
16+
)._sortable = Sortable.create(element, {
17+
handle: ".drag-handle",
18+
filter: ".drag-handle.disabled",
19+
swapThreshold: 0.5,
20+
animation: 150,
21+
easing: "cubic-bezier(1, 0, 0, 1)",
22+
onStart: (e) => {
23+
// https://github.com/SortableJS/Sortable/issues/546
24+
const node = e.item;
25+
// Remember the list of child nodes when drag started.
26+
childNodes = Array.prototype.slice.call(node.parentNode.childNodes);
27+
// Filter out the 'sortable-fallback' element used on mobile/old browsers.
28+
childNodes = childNodes.filter(
29+
(node) =>
30+
node.nodeType != Node.ELEMENT_NODE ||
31+
!node.classList.contains("sortable-fallback")
32+
);
33+
},
34+
onEnd: (e) => {
35+
// Undo DOM changes by re-adding all children in their original order.
36+
const node = e.item;
37+
const parentNode = node.parentNode;
38+
for (const childNode of childNodes) {
39+
parentNode.appendChild(childNode);
40+
}
41+
if (e.oldIndex == e.newIndex) return;
42+
// Then move the element using your own logic.
43+
// automatically dispatches "sort" event
44+
const layer = layers.getArray().find(
45+
(l) =>
46+
// @ts-ignore
47+
l.ol_uid ===
48+
/** @type Element & {layer: import("ol/layer").Layer} */ (
49+
e.item.querySelector("eox-layercontrol-layer")
50+
// @ts-ignore
51+
).layer.ol_uid
52+
);
53+
layers.remove(layer);
54+
layers.insertAt(layers.getLength() - e.newIndex, layer);
55+
that.requestUpdate();
56+
},
57+
});
58+
};
59+
60+
/**
61+
* Filter all map layers by property
62+
*
63+
* @param {Array<import("ol/layer").Layer>} layers
64+
* @param {string} key
65+
* @param {any} value
66+
* @returns {Array<import("ol/layer").Layer>} found layers
67+
*/
68+
export const filterLayers = (layers, key, value) => {
69+
/**
70+
* @type {any[]}
71+
*/
72+
let found = [];
73+
/**
74+
*
75+
* @param {any[]} searchLayers
76+
* @param {string} key
77+
* @param {any} value
78+
*/
79+
const search = (searchLayers, key, value) => {
80+
found = [...found, ...searchLayers.filter((l) => l.get(key) === value)];
81+
const groups = searchLayers.filter((l) => l.getLayers);
82+
if (groups.length > 0) {
83+
groups.forEach((group) =>
84+
search(group.getLayers().getArray(), key, value)
85+
);
86+
}
87+
return found;
88+
};
89+
search(layers, key, value);
90+
return found;
91+
};
92+
93+
/**
94+
* Trying to guess the layer type from certain properties.
95+
* The proper way would be to use instanceOf, but for this
96+
* we'd need OL as a dependency, which we're trying to avoid
97+
* @param {import("ol/layer").Layer | import("ol/layer").Group} layer
98+
* @param {import("ol").Map} map
99+
*/
100+
export const getLayerType = (layer, map) => {
101+
if (!layer || !map) {
102+
return undefined;
103+
}
104+
return /** @type {import("ol/layer").Group} */ (layer).getLayers
105+
? "group"
106+
: map
107+
.getInteractions()
108+
.getArray()
109+
// @ts-ignore
110+
.filter((i) => i.freehand_ !== undefined)
111+
// @ts-ignore
112+
.map((i) => i.source_)
113+
// @ts-ignore
114+
?.ol_uid?.includes(
115+
// @ts-ignore
116+
layer.getSource ? layer.getSource()?.ol_uid : undefined
117+
)
118+
? "draw"
119+
: // @ts-ignore
120+
layer.declutter_ !== undefined
121+
? "vector"
122+
: "raster";
123+
};

‎elements/layercontrol/src/main.js

+139
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import { LitElement, html } from "lit";
2+
import { when } from "lit/directives/when.js";
3+
import "./components/layerList";
4+
import "./components/optionalList";
5+
import { filterLayers } from "./helpers";
6+
7+
/**
8+
* Display layers and groups of a connected OpenLayers map
9+
*
10+
* ## Layer properties
11+
* In order to be displayed correctly, the OpenLayers map layers need some custom properties (using e.g. `layer.set(property, value)`).
12+
*
13+
* #### `id?: string`
14+
* The layer id. Not required but recommended. Can also be any other layer property (defined via the `idProperty` property or `id-property`attribute - see API).
15+
*
16+
* #### `title?: string`
17+
* The title of the layer displayed in the layer control. Not required, but recommended in order to display human-readable layer titles. Can be any other layer property (defined via the `titleProperty` property or `title-property`attribute - see API).
18+
*
19+
* #### `layerControlHide?: Boolean`
20+
* Completely hide a layer from the layer control.
21+
*
22+
* #### `layerControlOptional?: Boolean`
23+
* Initially hide a layer from the layer control, but make it available as an optional layer. If the layer is selected and added, it will be set to visible and pushed to the top of the layer list or (if originally configured within a layer group) to the top of the layer group.
24+
*
25+
* #### `layerControlExclusive?: Boolean`
26+
* Make layers mutually exclusive. If two or more layers (on the same level, i.e. at root or inside a layer group) have this property, then only one of them can be visualized at a time.
27+
*
28+
* #### `layerControlExpand?: Boolean`
29+
* Pre-expand a layer dropdown so that it is always open when the component initializes.
30+
*
31+
* @element eox-layercontrol
32+
*/
33+
export class EOxLayerControl extends LitElement {
34+
static properties = {
35+
for: { type: String },
36+
idProperty: { attribute: "id-property" },
37+
map: { attribute: false, state: true },
38+
titleProperty: { attribute: "title-property", type: String },
39+
tools: { attribute: false },
40+
unstyled: { type: Boolean },
41+
};
42+
43+
constructor() {
44+
super();
45+
46+
/**
47+
* Query selector of an eox-map or another DOM element containing an OL map proeprty
48+
*/
49+
this.for = "eox-map";
50+
51+
/**
52+
* Layer id property
53+
*/
54+
this.idProperty = "id";
55+
56+
/**
57+
* The native OL map instance
58+
* @type {import("ol").Map}
59+
* @see {@link https://openlayers.org/en/latest/apidoc/module-ol_Map-Map.html}
60+
*/
61+
this.map = null;
62+
63+
/**
64+
* Layer title property
65+
*/
66+
this.titleProperty = "title";
67+
68+
/**
69+
* Layer tools
70+
*/
71+
this.tools = ["info", "opacity", "remove", "sort"];
72+
73+
/**
74+
* Render the element without additional styles
75+
*/
76+
this.unstyled = false;
77+
}
78+
79+
updated() {
80+
/**
81+
* @type Element & { map: import("ol").Map }
82+
*/
83+
const foundElement = document.querySelector(this.for);
84+
if (foundElement) {
85+
this.map = foundElement.map;
86+
}
87+
}
88+
89+
render() {
90+
return html`
91+
<style>
92+
${this.#styleBasic}
93+
${!this.unstyled && this.#styleEOX}
94+
</style>
95+
${when(
96+
this.map,
97+
() => html`
98+
<eox-layercontrol-layer-list
99+
class="layers"
100+
.idProperty=${this.idProperty}
101+
.layers=${this.map.getLayers()}
102+
.map=${this.map}
103+
.titleProperty=${this.titleProperty}
104+
.tools=${this.tools}
105+
.unstyled=${this.unstyled}
106+
@changed=${() => this.requestUpdate()}
107+
></eox-layercontrol-layer-list>
108+
`
109+
)}
110+
${when(
111+
this.map &&
112+
filterLayers(
113+
// @ts-ignore
114+
this.map.getLayers().getArray(),
115+
"layerControlOptional",
116+
true
117+
)?.length > 0,
118+
() => html`
119+
<eox-layercontrol-optional-list
120+
.idProperty=${this.idProperty}
121+
.layers=${this.map.getLayers()}
122+
.titleProperty=${this.titleProperty}
123+
@changed=${() => this.requestUpdate()}
124+
></eox-layercontrol-optional-list>
125+
`
126+
)}
127+
`;
128+
}
129+
130+
#styleBasic = ``;
131+
132+
#styleEOX = `
133+
* {
134+
font-family: Roboto, sans-serif;
135+
}
136+
`;
137+
}
138+
139+
customElements.define("eox-layercontrol", EOxLayerControl);

‎elements/layercontrol/src/main.ts

-691
This file was deleted.

‎elements/layercontrol/src/style.eox.ts

-270
This file was deleted.

‎elements/layercontrol/src/style.ts

-14
This file was deleted.
+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
class MockLayer {
2+
constructor(layer) {
3+
Object.keys(layer).forEach((k) => {
4+
if (k === "properties") {
5+
this.properties = {
6+
...this.properties,
7+
...layer.properties,
8+
};
9+
} else {
10+
this[k] = layer[k];
11+
}
12+
if (layer.layers) {
13+
this.layers = new MockCollection(layer.layers);
14+
}
15+
});
16+
}
17+
get(prop) {
18+
return this.properties[prop];
19+
}
20+
getLayers() {
21+
return this.layers || new MockCollection([]);
22+
}
23+
getOpacity() {
24+
return this.opacity;
25+
}
26+
getVisible() {
27+
return this.visible;
28+
}
29+
layers;
30+
opacity = 1;
31+
properties = {
32+
id: "foo",
33+
title: "layer",
34+
};
35+
set(prop, value) {
36+
this.properties[prop] = value;
37+
}
38+
setVisible(visible) {
39+
this.visible = visible;
40+
}
41+
visible = true;
42+
}
43+
44+
class MockCollection {
45+
constructor(layers) {
46+
this.layers = layers.map((l) => new MockLayer(l));
47+
setTimeout(() => {
48+
this.events["change:length"]();
49+
});
50+
}
51+
events = {
52+
["change:length"]: () => undefined,
53+
};
54+
getArray = () => {
55+
return this.layers;
56+
};
57+
layers = [];
58+
on = (event, fun) => (this.events = { [event]: fun });
59+
pop() {
60+
this.layers.pop();
61+
this.events["change:length"]();
62+
}
63+
push(newLayer) {
64+
this.layers.push(new MockLayer(newLayer));
65+
this.events["change:length"]();
66+
}
67+
}
68+
69+
export class MockMap extends HTMLElement {
70+
constructor() {
71+
super();
72+
}
73+
layers;
74+
events = {};
75+
map = {
76+
getInteractions: () => ({
77+
getArray: () => [{}],
78+
}),
79+
getLayers: () => {
80+
if (this.layers) {
81+
return this.layers;
82+
} else {
83+
return new MockCollection([]);
84+
}
85+
},
86+
};
87+
setLayers = (layers) => {
88+
this.layers = new MockCollection(layers);
89+
};
90+
}
91+
customElements.define("mock-map", MockMap);

‎elements/layercontrol/test/_mockMap.ts

-93
This file was deleted.

‎elements/layercontrol/test/general.cy.ts ‎elements/layercontrol/test/general.cy.js

+34-19
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ describe("LayerControl", () => {
1616

1717
it("displays the correct amount of layers", () => {
1818
cy.get("mock-map").and(($el) => {
19-
(<MockMap>$el[0]).setLayers([{ visible: true }, { visible: false }]);
19+
$el /**MockMap*/[0]
20+
.setLayers([{ visible: true }, { visible: false }]);
2021
});
2122
cy.get("eox-layercontrol")
2223
.shadow()
@@ -30,10 +31,11 @@ describe("LayerControl", () => {
3031

3132
it("hides layers correctly", () => {
3233
cy.get("mock-map").and(($el) => {
33-
(<MockMap>$el[0]).setLayers([
34-
{ visible: true },
35-
{ layerControlHide: true },
36-
]);
34+
$el /**MockMap*/[0]
35+
.setLayers([
36+
{ visible: true },
37+
{ properties: { layerControlHide: true } },
38+
]);
3739
});
3840
cy.get("eox-layercontrol")
3941
.shadow()
@@ -44,10 +46,11 @@ describe("LayerControl", () => {
4446

4547
it("renders the optional layer selection", () => {
4648
cy.get("mock-map").and(($el) => {
47-
(<MockMap>$el[0]).setLayers([
48-
{ visible: true },
49-
{ layerControlOptional: true },
50-
]);
49+
$el /**MockMap*/[0]
50+
.setLayers([
51+
{ visible: true },
52+
{ properties: { layerControlOptional: true } },
53+
]);
5154
});
5255
cy.get("eox-layercontrol")
5356
.shadow()
@@ -62,21 +65,29 @@ describe("LayerControl", () => {
6265

6366
it("disables the drag handle of the disabled layer", () => {
6467
cy.get("mock-map").and(($el) => {
65-
(<MockMap>$el[0]).setLayers([
66-
{ visible: true },
67-
{ layerControlDisable: true },
68-
]);
68+
$el /**MockMap*/[0]
69+
.setLayers([
70+
{ visible: true },
71+
{ properties: { layerControlDisable: true } },
72+
]);
6973
});
7074
cy.get("eox-layercontrol")
7175
.shadow()
7276
.within(() => {
73-
cy.get(".drag-handle.disabled").should("have.length", 1);
77+
cy.get(".tools summary")
78+
.click({ multiple: true })
79+
.get(".drag-handle:visible")
80+
.should("have.length", 1);
7481
});
7582
});
7683

7784
it("shows the correct layer titles", () => {
7885
cy.get("mock-map").and(($el) => {
79-
(<MockMap>$el[0]).setLayers([{ title: "foo" }, { title: "bar" }]);
86+
$el /**MockMap*/[0]
87+
.setLayers([
88+
{ properties: { title: "foo" } },
89+
{ properties: { title: "bar" } },
90+
]);
8091
});
8192
cy.get("eox-layercontrol")
8293
.shadow()
@@ -88,10 +99,14 @@ describe("LayerControl", () => {
8899

89100
it("pre-opens a section if layerControlExpand is present", () => {
90101
cy.get("mock-map").and(($el) => {
91-
(<MockMap>$el[0]).setLayers([
92-
{ visible: true },
93-
{ layerControlExpand: true },
94-
]);
102+
$el /**MockMap*/[0]
103+
.setLayers([
104+
{ visible: true },
105+
{
106+
properties: { layerControlExpand: true },
107+
layers: [{ visible: true }],
108+
},
109+
]);
95110
});
96111
cy.get("eox-layercontrol")
97112
.shadow()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
import "../src/main";
2+
import "./_mockMap";
3+
4+
describe("LayerControl", () => {
5+
beforeEach(() => {
6+
cy.mount("<mock-map></mock-map>").as("mock-map");
7+
cy.mount(
8+
`
9+
<eox-layercontrol for="mock-map"></eox-layercontrol>`
10+
).as("eox-layercontrol");
11+
});
12+
13+
it("displays the correct amount of layers after multiple calls of setMap()", () => {
14+
cy.get("mock-map").and(($el) => {
15+
$el /**MockMap*/[0]
16+
.setLayers([
17+
{ properties: { title: "1st round - foo" } },
18+
{ properties: { title: "1st round - bar" } },
19+
]);
20+
});
21+
cy.get("mock-map").and(($el) => {
22+
$el /**MockMap*/[0]
23+
.setLayers([]);
24+
});
25+
cy.get("mock-map").and(($el) => {
26+
$el /**MockMap*/[0]
27+
.setLayers([
28+
{ properties: { title: "2nd round - baz" } },
29+
{ properties: { title: "2nd round - qux" } },
30+
]);
31+
});
32+
let numberOfLayers;
33+
cy.get("mock-map").and(($el) => {
34+
numberOfLayers = $el /**MockMap*/[0].map
35+
.getLayers()
36+
.getArray().length;
37+
});
38+
cy.get("eox-layercontrol")
39+
.shadow()
40+
.within(() => {
41+
cy.get(".layers").find("li").should("have.length", numberOfLayers);
42+
});
43+
});
44+
45+
it("updates if a layer is pushed to the root collection", () => {
46+
cy.get("mock-map").and(($el) => {
47+
$el /**MockMap*/[0]
48+
.setLayers([{ properties: { title: "foo" } }]);
49+
});
50+
51+
cy.get("mock-map").and(($el) => {
52+
$el /**MockMap*/[0].map
53+
.getLayers()
54+
.push({ properties: { title: "bar" } });
55+
});
56+
cy.get("eox-layercontrol")
57+
.shadow()
58+
.within(() => {
59+
cy.get(".layer").find(".title").contains("bar");
60+
});
61+
});
62+
63+
it("updates if a layer is pushed to a group", () => {
64+
cy.get("mock-map").and(($el) => {
65+
$el /**MockMap*/[0]
66+
.setLayers([
67+
{
68+
properties: {
69+
title: "group",
70+
layerControlExpand: true,
71+
},
72+
layers: [{ properties: { title: "foo" } }],
73+
},
74+
{ properties: { title: "bar" } },
75+
]);
76+
});
77+
78+
cy.get("mock-map").and(($el) => {
79+
$el /**MockMap*/[0].map
80+
.getLayers()
81+
.getArray()[0]
82+
.getLayers()
83+
.push({ properties: { title: "baz" } });
84+
});
85+
cy.get("eox-layercontrol")
86+
.shadow()
87+
.within(() => {
88+
cy.get(".layer").find(".title").contains("baz");
89+
});
90+
});
91+
92+
it("updates if a layer is removed from the root collection", () => {
93+
cy.get("mock-map").and(($el) => {
94+
$el /**MockMap*/[0]
95+
.setLayers([
96+
{ properties: { id: "foo" } },
97+
{ properties: { id: "bar" } },
98+
]);
99+
});
100+
101+
const layerToDelete = "foo";
102+
103+
cy.get("eox-layercontrol")
104+
.shadow()
105+
.within(() => {
106+
cy.get(`[data-layer=${layerToDelete}] .tools > summary`)
107+
.click()
108+
.get("button.remove-icon:visible")
109+
.last()
110+
.click();
111+
cy.get(`[data-layer=${layerToDelete}]`).should("not.exist");
112+
});
113+
114+
// new approach is to just hide layers, not delete
115+
// keeping this as reference if we choose to remove layers in future
116+
// cy.get("mock-map").then(($el) => {
117+
// let layer = $el /**MockMap*/[0].layers
118+
// .find((l) => l.id === layerToDelete);
119+
// assert.equal(layer, undefined, "deleted layer should not be found");
120+
// });
121+
});
122+
123+
it("updates if a layer is removed from a group", () => {
124+
cy.get("mock-map").and(($el) => {
125+
$el /**MockMap*/[0]
126+
.setLayers([
127+
{
128+
properties: {
129+
title: "group",
130+
layerControlExpand: true,
131+
},
132+
layers: [{ properties: { title: "baz" } }],
133+
},
134+
{ title: "bar" },
135+
]);
136+
});
137+
138+
cy.get("mock-map").and(($el) => {
139+
$el /**MockMap*/[0].map
140+
.getLayers()
141+
.getArray()[0]
142+
.getLayers()
143+
.pop();
144+
});
145+
146+
cy.get("eox-layercontrol")
147+
.shadow()
148+
.within(() => {
149+
cy.get(".layer").find(".title").contains("baz").should("not.exist");
150+
});
151+
});
152+
153+
it("adds a layer to a correct group when originally optional", () => {
154+
cy.get("mock-map").and(($el) => {
155+
$el /**MockMap*/[0]
156+
.setLayers([
157+
{
158+
properties: {
159+
title: "group1",
160+
layerControlExpand: true,
161+
},
162+
layers: [{ properties: { title: "title1" } }],
163+
},
164+
{
165+
properties: {
166+
title: "group2",
167+
layerControlExpand: true,
168+
},
169+
layers: [
170+
{ properties: { title: "foo" } },
171+
{
172+
properties: {
173+
id: "title2",
174+
title: "title2",
175+
layerControlOptional: true,
176+
},
177+
visible: false,
178+
},
179+
],
180+
},
181+
]);
182+
});
183+
184+
cy.get("eox-layercontrol")
185+
.shadow()
186+
.within(() => {
187+
cy.get("[data-cy='optionalLayers']").select("title2");
188+
cy.get("[data-cy='optionalLayers']").siblings("button").click();
189+
});
190+
191+
cy.get("eox-layercontrol")
192+
.shadow()
193+
.within(() => {
194+
cy.get(".layer")
195+
.find(".title")
196+
.contains("title2")
197+
.should("have.length", 1);
198+
});
199+
});
200+
});

‎elements/layercontrol/test/layersUpdate.cy.ts

-171
This file was deleted.

‎elements/layercontrol/tsconfig.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
"noImplicitReturns": true,
1616
"skipLibCheck": true,
1717
"experimentalDecorators": true,
18+
"allowJs": true,
19+
"checkJs": true,
1820
"types": ["cypress"]
1921
},
20-
"include": ["**/*.ts", "../map/**/*.ts", "../../cypress/cypress.d.ts"]
22+
"include": ["src/*.js"]
2123
}

‎elements/layercontrol/typings.d.ts

-7
This file was deleted.

‎elements/layercontrol/vite.config.ts ‎elements/layercontrol/vite.config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { defineConfig } from "vite";
44
export default defineConfig({
55
build: {
66
lib: {
7-
entry: "./src/main.ts",
7+
entry: "./src/main.js",
88
name: "eox-layercontrol",
99
// the proper extensions will be added
1010
fileName: "eox-layercontrol",

‎package-lock.json

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎utils/styles/button.ts

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ button.icon {
5353
padding: 0;
5454
border-radius: 50%;
5555
width: 24px;
56+
text-indent: -9999px;
5657
}
5758
5859
button.icon-text {

0 commit comments

Comments
 (0)
Please sign in to comment.