@panoramax/web-viewer 3.2.3-develop-6e69906d → 3.2.3-develop-8b82a4e5
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/build/index.css +2 -2
- package/build/index.css.map +1 -1
- package/build/index.js +535 -216
- package/build/index.js.map +1 -1
- package/build/widgets.html +1 -1
- package/docs/reference/components/core/PhotoViewer.md +2 -0
- package/docs/reference/components/core/Viewer.md +2 -0
- package/docs/reference/components/layout/BottomDrawer.md +35 -0
- package/docs/reference/components/layout/Tabs.md +45 -0
- package/docs/reference/components/menus/PictureLegend.md +1 -0
- package/docs/reference/components/ui/Button.md +3 -2
- package/docs/reference/components/ui/CopyButton.md +7 -4
- package/docs/reference/components/ui/LinkButton.md +1 -0
- package/docs/reference/components/ui/ListGroup.md +22 -0
- package/docs/reference/components/ui/widgets/Legend.md +11 -0
- package/docs/reference/components/ui/widgets/OSMEditors.md +15 -0
- package/docs/reference/components/ui/widgets/PictureLegendActions.md +32 -0
- package/docs/reference.md +6 -2
- package/mkdocs.yml +5 -1
- package/package.json +1 -1
- package/public/widgets.html +45 -9
- package/src/components/core/Basic.css +1 -0
- package/src/components/core/PhotoViewer.css +0 -23
- package/src/components/core/PhotoViewer.js +41 -22
- package/src/components/core/Viewer.css +6 -31
- package/src/components/core/Viewer.js +40 -11
- package/src/components/layout/BottomDrawer.js +204 -0
- package/src/components/layout/CorneredGrid.js +3 -0
- package/src/components/layout/Tabs.js +133 -0
- package/src/components/layout/index.js +2 -0
- package/src/components/menus/PictureLegend.js +162 -23
- package/src/components/menus/PictureMetadata.js +220 -110
- package/src/components/menus/Share.js +2 -142
- package/src/components/styles.js +47 -47
- package/src/components/ui/Button.js +4 -2
- package/src/components/ui/CopyButton.js +34 -5
- package/src/components/ui/LinkButton.js +6 -7
- package/src/components/ui/ListGroup.js +66 -0
- package/src/components/ui/Map.js +4 -1
- package/src/components/ui/QualityScore.js +19 -24
- package/src/components/ui/TogglableGroup.js +47 -53
- package/src/components/ui/index.js +1 -0
- package/src/components/ui/widgets/Legend.js +29 -6
- package/src/components/ui/widgets/OSMEditors.js +153 -0
- package/src/components/ui/widgets/PictureLegendActions.js +131 -0
- package/src/components/ui/widgets/index.js +5 -4
- package/src/translations/en.json +14 -8
- package/src/translations/fr.json +14 -8
- package/src/utils/InitParameters.js +2 -1
- package/src/utils/geocoder.js +3 -1
- package/src/utils/picture.js +1 -2
- package/src/utils/widgets.js +5 -43
- package/tests/components/core/__snapshots__/PhotoViewer.test.js.snap +12 -32
- package/tests/components/core/__snapshots__/Viewer.test.js.snap +5 -25
- package/tests/components/ui/__snapshots__/Photo.test.js.snap +6 -2
- package/tests/utils/InitParameters.test.js +7 -9
- package/tests/utils/__snapshots__/picture.test.js.snap +13 -4
- package/tests/utils/picture.test.js +2 -2
- package/tests/utils/widgets.test.js +0 -59
- package/docs/reference/components/ui/widgets/Share.md +0 -15
- package/src/components/ui/widgets/Share.js +0 -30
|
@@ -7,6 +7,7 @@ import URLHandler from "../../utils/URLHandler";
|
|
|
7
7
|
import Basic from "./Basic";
|
|
8
8
|
import Photo, { PSV_DEFAULT_ZOOM, PSV_ANIM_DURATION } from "../ui/Photo";
|
|
9
9
|
import { createWebComp } from "../../utils/widgets";
|
|
10
|
+
import { isNullId } from "../../utils/utils";
|
|
10
11
|
import { default as InitParameters, alterPSVState, alterMapState, alterPhotoViewerState } from "../../utils/InitParameters";
|
|
11
12
|
|
|
12
13
|
|
|
@@ -40,6 +41,7 @@ const PSV_MOVE_DELTA = Math.PI / 6;
|
|
|
40
41
|
* @slot `bottom-left` The bottom-left corner
|
|
41
42
|
* @slot `bottom` The bottom middle corner
|
|
42
43
|
* @slot `bottom-right` The bottom-right corner
|
|
44
|
+
* @slot `editors` External links to map editors, or any tool that may be helpful. Defaults to OSM tools (iD & JOSM).
|
|
43
45
|
* @example
|
|
44
46
|
* ```html
|
|
45
47
|
* <!-- Basic example -->
|
|
@@ -54,6 +56,7 @@ const PSV_MOVE_DELTA = Math.PI / 6;
|
|
|
54
56
|
* style="width: 300px; height: 250px"
|
|
55
57
|
* >
|
|
56
58
|
* <p slot="top-right">My custom text</p>
|
|
59
|
+
* <p slot="editors"><a href="https://my.own.tool/">Edit in my own tool</a></p>
|
|
57
60
|
* </pnx-photo-viewer>
|
|
58
61
|
*
|
|
59
62
|
* <!-- With only your custom widgets -->
|
|
@@ -124,29 +127,41 @@ export default class PhotoViewer extends Basic {
|
|
|
124
127
|
/** @private */
|
|
125
128
|
_initWidgets() {
|
|
126
129
|
if(this._initParams.getParentPostInit().widgets !== "false") {
|
|
130
|
+
this.grid.appendChild(createWebComp("pnx-widget-player", {
|
|
131
|
+
slot: "top",
|
|
132
|
+
_parent: this,
|
|
133
|
+
class: "pnx-only-psv pnx-print-hidden",
|
|
134
|
+
size: this.isHeightSmall() ? "md": "xl",
|
|
135
|
+
}));
|
|
136
|
+
|
|
127
137
|
if(!this.isWidthSmall()) {
|
|
138
|
+
this.legend = createWebComp("pnx-widget-legend", {
|
|
139
|
+
slot: !this.isWidthSmall() ? "top-left" : undefined,
|
|
140
|
+
_parent: this,
|
|
141
|
+
focus: this._initParams.getParentPostInit().focus,
|
|
142
|
+
picture: this._initParams.getParentPostInit().picture,
|
|
143
|
+
});
|
|
128
144
|
this.grid.appendChild(createWebComp("pnx-widget-zoom", {
|
|
129
145
|
slot: "bottom-right",
|
|
130
146
|
class: "pnx-print-hidden",
|
|
131
147
|
_parent: this
|
|
132
148
|
}));
|
|
149
|
+
this.grid.appendChild(this.legend);
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
this.legend = createWebComp("pnx-picture-legend", { _parent: this });
|
|
153
|
+
this.bottomDrawer = createWebComp("pnx-bottom-drawer", {
|
|
154
|
+
slot: "bottom",
|
|
155
|
+
_parent: this,
|
|
156
|
+
class: this._initParams.getParentPostInit().picture ? undefined: "pnx-hidden",
|
|
157
|
+
});
|
|
158
|
+
this.bottomDrawer.appendChild(this.legend);
|
|
159
|
+
this.grid.appendChild(this.bottomDrawer);
|
|
160
|
+
this.addEventListener("select", e => {
|
|
161
|
+
if(isNullId(e.detail.picId)) { this.bottomDrawer.classList.add("pnx-hidden"); }
|
|
162
|
+
else { this.bottomDrawer.classList.remove("pnx-hidden"); }
|
|
163
|
+
});
|
|
133
164
|
}
|
|
134
|
-
|
|
135
|
-
this.grid.appendChild(createWebComp("pnx-widget-share", {slot: "bottom-right", class: "pnx-print-hidden", _parent: this}));
|
|
136
|
-
|
|
137
|
-
this.legend = createWebComp("pnx-widget-legend", {
|
|
138
|
-
slot: this.isWidthSmall() ? "top" : "top-left",
|
|
139
|
-
_parent: this,
|
|
140
|
-
focus: this._initParams.getParentPostInit().focus,
|
|
141
|
-
picture: this._initParams.getParentPostInit().picture,
|
|
142
|
-
});
|
|
143
|
-
this.grid.appendChild(this.legend);
|
|
144
|
-
this.grid.appendChild(createWebComp("pnx-widget-player", {
|
|
145
|
-
slot: "top",
|
|
146
|
-
_parent: this,
|
|
147
|
-
class: "pnx-only-psv pnx-print-hidden",
|
|
148
|
-
size: this.isHeightSmall() ? "md": "xl",
|
|
149
|
-
}));
|
|
150
165
|
}
|
|
151
166
|
}
|
|
152
167
|
|
|
@@ -323,7 +338,14 @@ export default class PhotoViewer extends Basic {
|
|
|
323
338
|
n._parent = this;
|
|
324
339
|
n._t = this._t;
|
|
325
340
|
}
|
|
326
|
-
|
|
341
|
+
// Editors slot -> legend
|
|
342
|
+
if(n.getAttribute("slot") === "editors") {
|
|
343
|
+
this.onceReady().then(() => this.legend.appendChild(n));
|
|
344
|
+
}
|
|
345
|
+
// Add to grid for other cases
|
|
346
|
+
else {
|
|
347
|
+
this.grid.appendChild(n);
|
|
348
|
+
}
|
|
327
349
|
}
|
|
328
350
|
}
|
|
329
351
|
}
|
|
@@ -357,14 +379,11 @@ export default class PhotoViewer extends Basic {
|
|
|
357
379
|
_showReportForm() {
|
|
358
380
|
if(!this.psv.getPictureMetadata()) { throw new Error("No picture currently selected"); }
|
|
359
381
|
this.setPopup(true, [createWebComp("pnx-report-form", {_parent: this})]);
|
|
360
|
-
this.dispatchEvent(new CustomEvent("focus-changed", { detail: { focus: "meta" } }));
|
|
361
382
|
}
|
|
362
383
|
|
|
363
384
|
/** @private */
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
this.setPopup(true, [createWebComp("pnx-picture-metadata", {_parent: this})]);
|
|
367
|
-
this.dispatchEvent(new CustomEvent("focus-changed", { detail: { focus: "meta" } }));
|
|
385
|
+
_showShareOptions() {
|
|
386
|
+
this.setPopup(true, [createWebComp("pnx-share-menu", {_parent: this})]);
|
|
368
387
|
}
|
|
369
388
|
|
|
370
389
|
/**
|
|
@@ -39,17 +39,18 @@ pnx-viewer pnx-cornered-grid::part(corner-bottom-right) {
|
|
|
39
39
|
pnx-viewer pnx-mini {
|
|
40
40
|
box-sizing: border-box;
|
|
41
41
|
aspect-ratio: 1/1;
|
|
42
|
-
width: 250px;
|
|
43
|
-
height: 250px;
|
|
42
|
+
min-width: 250px;
|
|
43
|
+
min-height: 250px;
|
|
44
44
|
position: relative;
|
|
45
45
|
display: block;
|
|
46
46
|
z-index: 120;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
@media screen and (max-width: 576px) {
|
|
49
|
+
@media screen and ((max-height: 400px) or (max-width: 576px)) {
|
|
50
50
|
pnx-viewer pnx-mini {
|
|
51
|
-
width: unset;
|
|
52
|
-
height: unset;
|
|
51
|
+
min-width: unset;
|
|
52
|
+
min-height: unset;
|
|
53
|
+
margin-bottom: 40px;
|
|
53
54
|
}
|
|
54
55
|
}
|
|
55
56
|
|
|
@@ -65,32 +66,6 @@ pnx-viewer:not(pnx-viewer[focus="pic"]) .pnx-only-psv {
|
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
/* Override legend positioning */
|
|
68
|
-
@media screen and (max-width: 576px) {
|
|
69
|
-
pnx-viewer[focus="map"] pnx-widget-legend {
|
|
70
|
-
display: none;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
pnx-viewer[focus="pic"] pnx-widget-legend {
|
|
74
|
-
position: fixed;
|
|
75
|
-
top: 0;
|
|
76
|
-
left: 0;
|
|
77
|
-
right: 0;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
pnx-viewer[focus="pic"] pnx-widget-legend::part(panel) {
|
|
81
|
-
border-radius: 0;
|
|
82
|
-
border-top: none;
|
|
83
|
-
border-left: none;
|
|
84
|
-
border-right: none;
|
|
85
|
-
width: unset;
|
|
86
|
-
max-width: unset;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
pnx-viewer[focus="pic"] pnx-widget-player {
|
|
90
|
-
margin-top: 60px;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
69
|
@media screen and (min-width: 576px) {
|
|
95
70
|
pnx-viewer pnx-widget-legend {
|
|
96
71
|
position: absolute;
|
|
@@ -49,6 +49,7 @@ const MAP_MOVE_DELTA = 100;
|
|
|
49
49
|
* @slot `bottom-left` The bottom-left corner
|
|
50
50
|
* @slot `bottom` The bottom middle corner
|
|
51
51
|
* @slot `bottom-right` The bottom-right corner
|
|
52
|
+
* @slot `editors` External links to map editors, or any tool that may be helpful. Defaults to OSM tools (iD & JOSM).
|
|
52
53
|
* @example
|
|
53
54
|
* ```html
|
|
54
55
|
* <!-- Basic example -->
|
|
@@ -63,6 +64,7 @@ const MAP_MOVE_DELTA = 100;
|
|
|
63
64
|
* style="width: 300px; height: 250px"
|
|
64
65
|
* >
|
|
65
66
|
* <p slot="top-right">My custom text</p>
|
|
67
|
+
* <p slot="editors"><a href="https://my.own.tool/">Edit in my own tool</a></p>
|
|
66
68
|
* </pnx-viewer>
|
|
67
69
|
*
|
|
68
70
|
* <!-- With only your custom widgets -->
|
|
@@ -145,15 +147,31 @@ export default class Viewer extends PhotoViewer {
|
|
|
145
147
|
class: this.isWidthSmall() ? "pnx-only-map pnx-print-hidden" : "pnx-print-hidden",
|
|
146
148
|
_parent: this
|
|
147
149
|
}));
|
|
148
|
-
this.grid.appendChild(createWebComp("pnx-widget-share", {slot: "bottom-right", class: "pnx-print-hidden", _parent: this}));
|
|
149
150
|
|
|
150
|
-
this.
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
151
|
+
if(!this.isWidthSmall()) {
|
|
152
|
+
this.legend = createWebComp("pnx-widget-legend", {
|
|
153
|
+
slot: this.isWidthSmall() ? "top" : "top-left",
|
|
154
|
+
_parent: this,
|
|
155
|
+
focus: this._initParams.getParentPostInit().focus,
|
|
156
|
+
picture: this._initParams.getParentPostInit().picture,
|
|
157
|
+
});
|
|
158
|
+
this.grid.appendChild(this.legend);
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
this.legend = createWebComp("pnx-picture-legend", { _parent: this });
|
|
162
|
+
this.bottomDrawer = createWebComp("pnx-bottom-drawer", {
|
|
163
|
+
slot: "bottom",
|
|
164
|
+
_parent: this,
|
|
165
|
+
class: this._initParams.getParentPostInit().picture ? undefined: "pnx-hidden",
|
|
166
|
+
});
|
|
167
|
+
this.bottomDrawer.appendChild(this.legend);
|
|
168
|
+
this.grid.appendChild(this.bottomDrawer);
|
|
169
|
+
this.addEventListener("select", e => {
|
|
170
|
+
if(isNullId(e.detail.picId)) { this.bottomDrawer.classList.add("pnx-hidden"); }
|
|
171
|
+
else { this.bottomDrawer.classList.remove("pnx-hidden"); }
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
157
175
|
this.grid.appendChild(createWebComp("pnx-widget-player", {
|
|
158
176
|
slot: "top",
|
|
159
177
|
_parent: this,
|
|
@@ -196,7 +214,10 @@ export default class Viewer extends PhotoViewer {
|
|
|
196
214
|
this._handleKeyboardManagement();
|
|
197
215
|
|
|
198
216
|
if(myPostInitParams.picture) {
|
|
199
|
-
this.psv.addEventListener("picture-loaded", () =>
|
|
217
|
+
this.psv.addEventListener("picture-loaded", () => {
|
|
218
|
+
alterViewerState(this, myPostInitParams); // Do it again for forcing focus
|
|
219
|
+
this.loader.dismiss();
|
|
220
|
+
}, {once: true});
|
|
200
221
|
}
|
|
201
222
|
else {
|
|
202
223
|
this.loader.dismiss();
|
|
@@ -236,8 +257,16 @@ export default class Viewer extends PhotoViewer {
|
|
|
236
257
|
if(isNullId(old) && !isNullId(value)) {
|
|
237
258
|
this.mini.removeAttribute("collapsed");
|
|
238
259
|
}
|
|
239
|
-
|
|
240
|
-
|
|
260
|
+
|
|
261
|
+
// Unselect -> show map wide instead
|
|
262
|
+
if(isNullId(value)) {
|
|
263
|
+
if(this.map && this.isMapWide()) { this.mini.classList.add("pnx-hidden"); }
|
|
264
|
+
else if(this.map && !this.isMapWide()) { this._setFocus("map"); }
|
|
265
|
+
}
|
|
266
|
+
// Select after none selected -> show pic wide
|
|
267
|
+
else {
|
|
268
|
+
this.mini.classList.remove("pnx-hidden");
|
|
269
|
+
if(isNullId(old)) { this._setFocus("pic"); }
|
|
241
270
|
}
|
|
242
271
|
}
|
|
243
272
|
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { LitElement, html, css } from "lit";
|
|
2
|
+
import { classMap } from "lit/directives/class-map.js";
|
|
3
|
+
|
|
4
|
+
const OPENNESS_Y_PCT = { "opened": 0, "half-opened": 0.7, "closed": 1 };
|
|
5
|
+
const OPENNESS_Y_PCT_RANGE = { "opened": [0, 0.4], "half-opened": [0.4, 0.8], "closed": [0.8, 1] };
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* BottomDrawer layout offers a mobile-like drawer menu, coming from bottom of the screen.
|
|
9
|
+
* @class Panoramax.components.layout.BottomDrawer
|
|
10
|
+
* @element pnx-bottom-drawer
|
|
11
|
+
* @extends [lit.LitElement](https://lit.dev/docs/api/LitElement/)
|
|
12
|
+
* @slot `default` The drawer content
|
|
13
|
+
* @example
|
|
14
|
+
* ```html
|
|
15
|
+
* <pnx-bottom-drawer openness="opened">
|
|
16
|
+
* <p>My drawer content</p>
|
|
17
|
+
* </pnx-bottom-drawer>
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export default class BottomDrawer extends LitElement {
|
|
21
|
+
/** @private */
|
|
22
|
+
static styles = css`
|
|
23
|
+
:host {
|
|
24
|
+
position: fixed;
|
|
25
|
+
bottom: 0;
|
|
26
|
+
left: 0;
|
|
27
|
+
width: 100%;
|
|
28
|
+
z-index: 130;
|
|
29
|
+
pointer-events: none;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.drawer {
|
|
33
|
+
background: var(--widget-bg);
|
|
34
|
+
border-top-left-radius: 16px;
|
|
35
|
+
border-top-right-radius: 16px;
|
|
36
|
+
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.2);
|
|
37
|
+
display: flex;
|
|
38
|
+
flex-direction: column;
|
|
39
|
+
transition: transform 0.3s ease;
|
|
40
|
+
will-change: transform;
|
|
41
|
+
touch-action: none;
|
|
42
|
+
pointer-events: auto;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.drawer.dragging { transition: none; }
|
|
46
|
+
.drawer.closed { transform: translateY(calc(100% - 30px)); }
|
|
47
|
+
.drawer.half-opened { transform: translateY(70%); }
|
|
48
|
+
.drawer.half-opened .content { overflow-y: hidden; }
|
|
49
|
+
.drawer.opened { transform: translateY(0%); }
|
|
50
|
+
|
|
51
|
+
.handle {
|
|
52
|
+
height: 30px;
|
|
53
|
+
display: flex;
|
|
54
|
+
align-items: center;
|
|
55
|
+
justify-content: center;
|
|
56
|
+
cursor: grab;
|
|
57
|
+
touch-action: none;
|
|
58
|
+
pointer-events: auto;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.handle-bar {
|
|
62
|
+
width: 40px;
|
|
63
|
+
height: 5px;
|
|
64
|
+
background: var(--grey-pale);
|
|
65
|
+
border-radius: 3px;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.content {
|
|
69
|
+
overflow: auto;
|
|
70
|
+
flex: 1;
|
|
71
|
+
}
|
|
72
|
+
`;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Component properties.
|
|
76
|
+
* @memberof Panoramax.components.layout.BottomDrawer#
|
|
77
|
+
* @type {Object}
|
|
78
|
+
* @property {string} [openness=half-opened] How much the drawer should be opened (closed, half-opened, opened)
|
|
79
|
+
*/
|
|
80
|
+
static properties = {
|
|
81
|
+
_fingerY: {state: true},
|
|
82
|
+
_deltaFingerY: {state: true},
|
|
83
|
+
_drawerY: {state: true},
|
|
84
|
+
_isDragging: {state: true},
|
|
85
|
+
_drawerHeight: {state: true},
|
|
86
|
+
openness: {type: String, reflect: true},
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
constructor() {
|
|
90
|
+
super();
|
|
91
|
+
this._isDragging = false;
|
|
92
|
+
this.openness = "half-opened";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** @private */
|
|
96
|
+
firstUpdated() {
|
|
97
|
+
super.firstUpdated();
|
|
98
|
+
this._boundTouchMove = this._onTouchMove.bind(this);
|
|
99
|
+
this._boundTouchEnd = this._onTouchEnd.bind(this);
|
|
100
|
+
|
|
101
|
+
this._drawerHeight = window.innerHeight - 30;
|
|
102
|
+
const drawer = this.shadowRoot?.querySelector(".drawer");
|
|
103
|
+
if (!drawer) { return; }
|
|
104
|
+
drawer.style.height = `${this._drawerHeight}px`;
|
|
105
|
+
drawer.style.maxHeight = `${this._drawerHeight}px`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** @private */
|
|
109
|
+
disconnectedCallback() {
|
|
110
|
+
super.disconnectedCallback();
|
|
111
|
+
this._cleanupTouchListeners();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** @private */
|
|
115
|
+
_onHandleClick() {
|
|
116
|
+
if(this.openness === "opened" || this.openness === "closed") { this.openness = "half-opened"; }
|
|
117
|
+
else if(this.openness === "half-opened") { this.openness = "opened"; }
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** @private */
|
|
121
|
+
_onTouchStart(e) {
|
|
122
|
+
this._isDragging = true;
|
|
123
|
+
this._startFingerY = e.touches[0].clientY;
|
|
124
|
+
this._deltaFingerY = 0;
|
|
125
|
+
this._drawerY = this._drawerHeight * OPENNESS_Y_PCT[this.openness];
|
|
126
|
+
window.addEventListener("touchmove", this._boundTouchMove, { passive: true });
|
|
127
|
+
window.addEventListener("touchend", this._boundTouchEnd);
|
|
128
|
+
window.addEventListener("touchcancel", this._boundTouchEnd);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** @private */
|
|
132
|
+
_onTouchMove(e) {
|
|
133
|
+
if (!this._isDragging) return;
|
|
134
|
+
this._deltaFingerY = e.touches[0].clientY - this._startFingerY;
|
|
135
|
+
this._updateDrawerTransform(this._drawerY + this._deltaFingerY);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** @private */
|
|
139
|
+
_onTouchEnd() {
|
|
140
|
+
if (!this._isDragging) return;
|
|
141
|
+
this._isDragging = false;
|
|
142
|
+
this._updateDrawerTransform(this._drawerY + this._deltaFingerY, true);
|
|
143
|
+
|
|
144
|
+
this._cleanupTouchListeners();
|
|
145
|
+
this._startFingerY = null;
|
|
146
|
+
this._deltaFingerY = null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** @private */
|
|
150
|
+
_cleanupTouchListeners() {
|
|
151
|
+
window.removeEventListener("touchmove", this._boundTouchMove);
|
|
152
|
+
window.removeEventListener("touchend", this._boundTouchEnd);
|
|
153
|
+
window.removeEventListener("touchcancel", this._boundTouchCancel);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** @private */
|
|
157
|
+
_updateDrawerTransform(y, snap = false) {
|
|
158
|
+
const drawer = this.shadowRoot?.querySelector(".drawer");
|
|
159
|
+
if (!drawer) { return; }
|
|
160
|
+
|
|
161
|
+
y = Math.max(0, Math.min(y, this._drawerHeight - 30));
|
|
162
|
+
|
|
163
|
+
// Snap to nearest static position
|
|
164
|
+
if(snap) {
|
|
165
|
+
const pct = y / (this._drawerHeight - 30);
|
|
166
|
+
if(pct > OPENNESS_Y_PCT_RANGE.opened[0] && pct <= OPENNESS_Y_PCT_RANGE.opened[1]) { this.openness = "opened"; }
|
|
167
|
+
if(pct > OPENNESS_Y_PCT_RANGE["half-opened"][0] && pct <= OPENNESS_Y_PCT_RANGE["half-opened"][1]) { this.openness = "half-opened"; }
|
|
168
|
+
if(pct > OPENNESS_Y_PCT_RANGE.closed[0] && pct <= OPENNESS_Y_PCT_RANGE.closed[1]) { this.openness = "closed"; }
|
|
169
|
+
this._drawerY = null;
|
|
170
|
+
drawer.style.transform = null;
|
|
171
|
+
}
|
|
172
|
+
// Live drag
|
|
173
|
+
else {
|
|
174
|
+
drawer.style.transform = `translateY(${y}px)`;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** @private */
|
|
179
|
+
render() {
|
|
180
|
+
const classes = {
|
|
181
|
+
"drawer": true,
|
|
182
|
+
[this.openness]: true,
|
|
183
|
+
"dragging": this._isDragging,
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
return html`
|
|
187
|
+
<div
|
|
188
|
+
class=${classMap(classes)}
|
|
189
|
+
@touchstart="${this._onTouchStart}"
|
|
190
|
+
@touchmove="${this._onTouchMove}"
|
|
191
|
+
@touchend="${this._onTouchEnd}"
|
|
192
|
+
>
|
|
193
|
+
<div class="handle" @click=${this._onHandleClick}>
|
|
194
|
+
<div class="handle-bar"></div>
|
|
195
|
+
</div>
|
|
196
|
+
<div class="content">
|
|
197
|
+
<slot></slot>
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
`;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
customElements.define("pnx-bottom-drawer", BottomDrawer);
|
|
@@ -50,11 +50,14 @@ export default class CorneredGrid extends LitElement {
|
|
|
50
50
|
pointer-events: none;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
.row.bottom { align-items: flex-end; }
|
|
54
|
+
|
|
53
55
|
.corner {
|
|
54
56
|
position: relative;
|
|
55
57
|
flex: 1 1 33%;
|
|
56
58
|
display: flex;
|
|
57
59
|
gap: 10px;
|
|
60
|
+
height: min-content;
|
|
58
61
|
}
|
|
59
62
|
|
|
60
63
|
.corner slot {
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { LitElement, html, css } from "lit";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Tabs offers a nice paged content rendering based on tabs buttons.
|
|
5
|
+
* The list of tab names are passed through `title` slots, and content using `content` slots.
|
|
6
|
+
* Note that tab names and contents should respect a coherent order.
|
|
7
|
+
* @class Panoramax.components.layout.Tabs
|
|
8
|
+
* @element pnx-tabs
|
|
9
|
+
* @extends [lit.LitElement](https://lit.dev/docs/api/LitElement/)
|
|
10
|
+
* @slot `title` A single tab name
|
|
11
|
+
* @slot `content` A single tab content
|
|
12
|
+
* @example
|
|
13
|
+
* ```html
|
|
14
|
+
* <pnx-tabs>
|
|
15
|
+
* <h4 slot="title">Tab 1</h4>
|
|
16
|
+
* <div slot="content">Tab 1 content</div>
|
|
17
|
+
*
|
|
18
|
+
* <h4 slot="title">Tab 2</h4>
|
|
19
|
+
* <div slot="content">Tab 2 content</div>
|
|
20
|
+
*
|
|
21
|
+
* <h4 slot="title">Tab 3</h4>
|
|
22
|
+
* <div slot="content">Tab 3 content</div>
|
|
23
|
+
* </pnx-tabs>
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export default class Tabs extends LitElement {
|
|
27
|
+
/** @private */
|
|
28
|
+
static styles = css`
|
|
29
|
+
/* Tabs */
|
|
30
|
+
nav {
|
|
31
|
+
display: flex;
|
|
32
|
+
gap: 5px;
|
|
33
|
+
align-items: stretch;
|
|
34
|
+
overflow-x: auto;
|
|
35
|
+
touch-action: pan-x;
|
|
36
|
+
}
|
|
37
|
+
nav ::slotted(*) {
|
|
38
|
+
color: var(--grey-dark);
|
|
39
|
+
border-bottom: 2px solid rgba(0,0,0,0);
|
|
40
|
+
cursor: pointer;
|
|
41
|
+
transition: all 0.1s;
|
|
42
|
+
flex: 1;
|
|
43
|
+
}
|
|
44
|
+
nav ::slotted(*:hover:not(.active)) { background-color: var(--blue-pale); }
|
|
45
|
+
nav ::slotted(*.active) {
|
|
46
|
+
color: var(--blue-dark);
|
|
47
|
+
border-bottom: 2px solid var(--blue-dark);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* Content */
|
|
51
|
+
.contents ::slotted(div) { display: none !important; }
|
|
52
|
+
.contents ::slotted(.active) { display: block !important; }
|
|
53
|
+
`;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Component properties.
|
|
57
|
+
* @memberof Panoramax.components.layout.Tabs#
|
|
58
|
+
* @type {Object}
|
|
59
|
+
* @property {string} [activeTabIndex=0] The selected tab index
|
|
60
|
+
*/
|
|
61
|
+
static properties = {
|
|
62
|
+
activeTabIndex: {type: Number},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
constructor() {
|
|
66
|
+
super();
|
|
67
|
+
this.activeTabIndex = 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** @private */
|
|
71
|
+
_getTabs() {
|
|
72
|
+
return this.shadowRoot.querySelector("slot[name='title']").assignedNodes();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** @private */
|
|
76
|
+
_getContents() {
|
|
77
|
+
return this.shadowRoot.querySelector("slot[name='content']").assignedNodes();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** @private */
|
|
81
|
+
_changeTab(tabTarget, tabIndex) {
|
|
82
|
+
const tabs = this._getTabs();
|
|
83
|
+
const contents = this._getContents();
|
|
84
|
+
|
|
85
|
+
// Check if tab change is possible
|
|
86
|
+
if(tabTarget !== undefined || tabIndex !== undefined) {
|
|
87
|
+
// For tab target, check if a nav tab has really been clicked
|
|
88
|
+
if(tabTarget) { tabIndex = tabs.findIndex(tab => (
|
|
89
|
+
tab === tabTarget || tab === tabTarget.parentNode
|
|
90
|
+
)); }
|
|
91
|
+
|
|
92
|
+
if(!isNaN(tabIndex) && tabIndex >= 0 && tabIndex < tabs.length) {
|
|
93
|
+
tabs.forEach((tab, index) => {
|
|
94
|
+
if (index == tabIndex) {
|
|
95
|
+
this.activeTabIndex = index;
|
|
96
|
+
contents[index].classList.add("active");
|
|
97
|
+
tab.classList.add("active");
|
|
98
|
+
} else {
|
|
99
|
+
contents[index].classList.remove("active");
|
|
100
|
+
tab.classList.remove("active");
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** @private */
|
|
109
|
+
_handleTabClick(event) {
|
|
110
|
+
this._changeTab(event.target);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** @private */
|
|
114
|
+
updated(changedProperties) {
|
|
115
|
+
if(changedProperties.has("activeTabIndex")) {
|
|
116
|
+
this._changeTab(undefined, this.activeTabIndex);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** @private */
|
|
121
|
+
render() {
|
|
122
|
+
return html`
|
|
123
|
+
<nav @click="${this._handleTabClick}">
|
|
124
|
+
<slot name="title"></slot>
|
|
125
|
+
</nav>
|
|
126
|
+
<div class="contents">
|
|
127
|
+
<slot name="content"></slot>
|
|
128
|
+
</div>
|
|
129
|
+
`;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
customElements.define("pnx-tabs", Tabs);
|