@dile/ui 3.18.1 → 3.18.2
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.
|
@@ -1,47 +1,41 @@
|
|
|
1
1
|
import { LitElement, html, css } from 'lit';
|
|
2
|
+
import { DileOverlay } from '../../../mixins/overlay/index.js';
|
|
3
|
+
import { DileCloseOnEscPressed } from '../../../mixins/close-on-esc-pressed/index.js';
|
|
2
4
|
import './DileFloatingMenuItem.js';
|
|
3
5
|
|
|
4
|
-
|
|
6
|
+
// Distance between the floating button and the overlay.
|
|
7
|
+
const OVERLAY_GAP = 10;
|
|
8
|
+
|
|
9
|
+
// DileOverlay places the overlay next to the button and keeps it inside the viewport.
|
|
10
|
+
// The opened property is the source of truth: open(), close() and toggle() only change it.
|
|
11
|
+
export class DileFloatingMenu extends DileOverlay(DileCloseOnEscPressed(LitElement)) {
|
|
5
12
|
static styles = [
|
|
6
13
|
css`
|
|
7
14
|
:host {
|
|
8
|
-
|
|
9
|
-
--dile-floating-menu-margin-bottom: var(--dile-floating-menu-margin-bottom);
|
|
10
|
-
--dile-floating-menu-margin-top: var(--dile-floating-menu-margin-top);
|
|
11
|
-
--dile-floating-menu-margin-left: var(--dile-floating-menu-margin-left);
|
|
12
|
-
--dile-floating-menu-margin-right: var(--dile-floating-menu-margin-right);
|
|
13
|
-
--dile-floating-menu-background-color: navy;
|
|
14
|
-
--dile-floating-menu-background-color-hover: mediumblue;
|
|
15
|
-
--dile-floating-menu-icon-background-color: aliceblue;
|
|
16
|
-
|
|
15
|
+
display: block;
|
|
17
16
|
position: fixed;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
flex-direction: column;
|
|
21
|
-
align-items: flex-start;
|
|
22
|
-
gap: 1rem;
|
|
23
|
-
top: var(--dile-floating-menu-margin-top, var(--dile-floating-menu-margin));
|
|
24
|
-
left: var(--dile-floating-menu-margin-left, var(--dile-floating-menu-margin));
|
|
17
|
+
top: var(--dile-floating-menu-margin-top, var(--dile-floating-menu-margin, 1.5rem));
|
|
18
|
+
left: var(--dile-floating-menu-margin-left, var(--dile-floating-menu-margin, 1.5rem));
|
|
25
19
|
z-index: var(--dile-floating-menu-zindex, 2);
|
|
26
20
|
}
|
|
21
|
+
:host([hidden]) {
|
|
22
|
+
display: none;
|
|
23
|
+
}
|
|
27
24
|
:host([bottom]) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
top: inherit;
|
|
25
|
+
top: auto;
|
|
26
|
+
bottom: var(--dile-floating-menu-margin-bottom, var(--dile-floating-menu-margin, 1.5rem));
|
|
31
27
|
}
|
|
32
28
|
:host([right]) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
left: inherit;
|
|
29
|
+
left: auto;
|
|
30
|
+
right: var(--dile-floating-menu-margin-right, var(--dile-floating-menu-margin, 1.5rem));
|
|
36
31
|
}
|
|
37
32
|
:host([left]) {
|
|
38
|
-
left: var(--dile-floating-menu-margin-left, var(--dile-floating-menu-margin));
|
|
39
|
-
right:
|
|
33
|
+
left: var(--dile-floating-menu-margin-left, var(--dile-floating-menu-margin, 1.5rem));
|
|
34
|
+
right: auto;
|
|
40
35
|
}
|
|
41
36
|
:host([top]) {
|
|
42
|
-
top: var(--dile-floating-menu-margin-top, var(--dile-floating-menu-margin));
|
|
43
|
-
|
|
44
|
-
bottom: inherit;
|
|
37
|
+
top: var(--dile-floating-menu-margin-top, var(--dile-floating-menu-margin, 1.5rem));
|
|
38
|
+
bottom: auto;
|
|
45
39
|
}
|
|
46
40
|
|
|
47
41
|
:host([opened]) #topline {
|
|
@@ -53,47 +47,72 @@ export class DileFloatingMenu extends LitElement {
|
|
|
53
47
|
}
|
|
54
48
|
|
|
55
49
|
.line {
|
|
56
|
-
background-color: var(--dile-floating-menu-icon-background-color);
|
|
50
|
+
background-color: var(--dile-floating-menu-icon-background-color, aliceblue);
|
|
57
51
|
width: 24px;
|
|
58
52
|
height: 2px;
|
|
59
53
|
transition: transform 0.2s ease-in-out;
|
|
60
54
|
}
|
|
61
55
|
|
|
62
|
-
|
|
56
|
+
#trigger {
|
|
57
|
+
box-sizing: border-box;
|
|
63
58
|
display: flex;
|
|
64
59
|
flex-direction: column;
|
|
65
60
|
justify-content: center;
|
|
66
61
|
align-items: center;
|
|
67
62
|
gap: 4px;
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
63
|
+
min-width: var(--dile-floating-menu-button-size, 48px);
|
|
64
|
+
min-height: var(--dile-floating-menu-button-size, 48px);
|
|
65
|
+
padding: var(--dile-floating-menu-button-padding, 0);
|
|
66
|
+
background-color: var(--dile-floating-menu-background-color, navy);
|
|
67
|
+
color: var(--dile-floating-menu-icon-background-color, aliceblue);
|
|
68
|
+
--dile-icon-color: var(--dile-floating-menu-icon-background-color, aliceblue);
|
|
69
|
+
border-radius: var(--dile-floating-menu-button-border-radius, 999px);
|
|
72
70
|
border: none;
|
|
71
|
+
font: inherit;
|
|
73
72
|
cursor: pointer;
|
|
74
|
-
transition: background-color
|
|
73
|
+
transition: background-color 0.3s;
|
|
75
74
|
}
|
|
76
75
|
|
|
77
|
-
|
|
78
|
-
background-color: var(--dile-floating-menu-background-color-hover);
|
|
76
|
+
#trigger:hover {
|
|
77
|
+
background-color: var(--dile-floating-menu-background-color-hover, mediumblue);
|
|
79
78
|
}
|
|
80
79
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
80
|
+
#trigger:focus-visible {
|
|
81
|
+
outline: 2px solid var(--dile-floating-menu-background-color, navy);
|
|
82
|
+
outline-offset: 2px;
|
|
84
83
|
}
|
|
85
84
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
85
|
+
#overlay {
|
|
86
|
+
box-sizing: border-box;
|
|
87
|
+
display: none;
|
|
88
|
+
position: fixed;
|
|
89
|
+
width: var(--dile-floating-menu-overlay-width, max-content);
|
|
90
|
+
max-width: var(--dile-floating-menu-overlay-max-width, calc(100vw - 25px));
|
|
91
|
+
max-height: var(--dile-floating-menu-overlay-max-height, calc(100vh - 100px));
|
|
92
|
+
overflow-y: auto;
|
|
93
|
+
background-color: var(--dile-floating-menu-overlay-background-color, var(--dile-floating-menu-background-color, navy));
|
|
94
|
+
color: var(--dile-floating-menu-overlay-color, aliceblue);
|
|
95
|
+
border-radius: var(--dile-floating-menu-overlay-border-radius, 4px);
|
|
96
|
+
box-shadow: var(--dile-floating-menu-overlay-box-shadow, 0 0 20px rgba(0, 0, 0, 0.3));
|
|
97
|
+
padding: var(--dile-floating-menu-overlay-padding, 24px);
|
|
98
|
+
opacity: 0;
|
|
99
|
+
transform: translateY(-10px);
|
|
100
|
+
transition: transform 0.3s ease, opacity 0.3s ease;
|
|
93
101
|
}
|
|
94
102
|
|
|
95
|
-
|
|
96
|
-
|
|
103
|
+
:host([bottom]) #overlay {
|
|
104
|
+
transform: translateY(10px);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
:host #overlay.opened {
|
|
108
|
+
opacity: 1;
|
|
109
|
+
transform: translateY(0);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
@media (prefers-reduced-motion: reduce) {
|
|
113
|
+
.line, #trigger, #overlay {
|
|
114
|
+
transition: none;
|
|
115
|
+
}
|
|
97
116
|
}
|
|
98
117
|
`
|
|
99
118
|
];
|
|
@@ -102,28 +121,43 @@ export class DileFloatingMenu extends LitElement {
|
|
|
102
121
|
|
|
103
122
|
opened: {
|
|
104
123
|
type: Boolean,
|
|
105
|
-
reflect: true,
|
|
124
|
+
reflect: true,
|
|
106
125
|
},
|
|
107
126
|
|
|
108
127
|
top: {
|
|
109
128
|
type: Boolean,
|
|
110
|
-
reflect: true,
|
|
129
|
+
reflect: true,
|
|
111
130
|
},
|
|
112
131
|
|
|
113
132
|
bottom: {
|
|
114
133
|
type: Boolean,
|
|
115
|
-
reflect: true,
|
|
134
|
+
reflect: true,
|
|
116
135
|
},
|
|
117
136
|
|
|
118
137
|
right: {
|
|
119
138
|
type: Boolean,
|
|
120
|
-
reflect: true,
|
|
139
|
+
reflect: true,
|
|
121
140
|
},
|
|
122
141
|
|
|
123
142
|
left: {
|
|
124
143
|
type: Boolean,
|
|
125
|
-
reflect: true,
|
|
126
|
-
},
|
|
144
|
+
reflect: true,
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Accessible name of the floating button.
|
|
149
|
+
*/
|
|
150
|
+
label: {
|
|
151
|
+
type: String,
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* If true, the menu closes when any link or button inside it is clicked.
|
|
156
|
+
*/
|
|
157
|
+
closeOnClick: {
|
|
158
|
+
type: Boolean,
|
|
159
|
+
attribute: 'close-on-click',
|
|
160
|
+
},
|
|
127
161
|
}
|
|
128
162
|
|
|
129
163
|
constructor() {
|
|
@@ -133,24 +167,124 @@ export class DileFloatingMenu extends LitElement {
|
|
|
133
167
|
this.left = false;
|
|
134
168
|
this.right = false;
|
|
135
169
|
this.bottom = false;
|
|
170
|
+
this.label = 'Menu';
|
|
171
|
+
this.closeOnClick = false;
|
|
172
|
+
this.documentClickHandler = this._onDocumentClick.bind(this);
|
|
173
|
+
this.addEventListener('focusout', this._onFocusOut);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
connectedCallback() {
|
|
177
|
+
super.connectedCallback();
|
|
178
|
+
document.addEventListener('click', this.documentClickHandler);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
disconnectedCallback() {
|
|
182
|
+
super.disconnectedCallback();
|
|
183
|
+
document.removeEventListener('click', this.documentClickHandler);
|
|
184
|
+
this.opened = false;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
updated(changedProperties) {
|
|
188
|
+
if (changedProperties.has('opened')) {
|
|
189
|
+
if (this.opened) {
|
|
190
|
+
this._showOverlay();
|
|
191
|
+
} else if (changedProperties.get('opened')) {
|
|
192
|
+
this._hideOverlay();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
136
195
|
}
|
|
137
196
|
|
|
138
197
|
render() {
|
|
139
198
|
return html`
|
|
140
|
-
<button
|
|
141
|
-
|
|
199
|
+
<button
|
|
200
|
+
id="trigger"
|
|
201
|
+
type="button"
|
|
202
|
+
aria-label="${this.label}"
|
|
203
|
+
aria-expanded="${String(this.opened)}"
|
|
204
|
+
aria-controls="overlay"
|
|
205
|
+
@click="${this.toggle}"
|
|
206
|
+
>
|
|
207
|
+
<slot name="icon">${this._menuIconTpl}</slot>
|
|
142
208
|
</button>
|
|
143
209
|
${this._listTpl}
|
|
144
210
|
`;
|
|
145
211
|
}
|
|
146
212
|
|
|
147
|
-
toggle(
|
|
148
|
-
this.opened
|
|
213
|
+
toggle() {
|
|
214
|
+
if (this.opened) {
|
|
215
|
+
this.close();
|
|
216
|
+
} else {
|
|
217
|
+
this.open();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
open() {
|
|
222
|
+
this.opened = true;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async close() {
|
|
226
|
+
if (!this.opened) {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
const hadFocus = this.matches(':focus-within');
|
|
230
|
+
this.opened = false;
|
|
231
|
+
await this.updateComplete;
|
|
232
|
+
if (hadFocus) {
|
|
233
|
+
this.trigger.focus({ preventScroll: true });
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
_showOverlay() {
|
|
238
|
+
this.verticalAlign = this.bottom ? 'top' : 'bottom';
|
|
239
|
+
this.horizontalAlign = this.right ? 'under_right' : 'under_left';
|
|
240
|
+
// DileOverlay only leaves a gap by itself when the overlay is under the button.
|
|
241
|
+
this.moveTop = this.bottom ? -OVERLAY_GAP : 0;
|
|
242
|
+
super.open();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
_hideOverlay() {
|
|
246
|
+
// Closed during the opening delay of DileOverlay, where its close() would be ignored.
|
|
247
|
+
if (this._opening) {
|
|
248
|
+
this.cancelDelay();
|
|
249
|
+
this._opening = false;
|
|
250
|
+
this._overlayClass = 'opened';
|
|
251
|
+
}
|
|
252
|
+
super.close();
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
_onDocumentClick(e) {
|
|
256
|
+
if (this.opened && !e.composedPath().includes(this)) {
|
|
257
|
+
this.opened = false;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
_onFocusOut(e) {
|
|
262
|
+
// relatedTarget is null when the focus does not go to another element, like when the window loses it.
|
|
263
|
+
if (this.opened && e.relatedTarget && !this.contains(e.relatedTarget)) {
|
|
264
|
+
this.opened = false;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
_onOverlayClick(e) {
|
|
269
|
+
const path = e.composedPath();
|
|
270
|
+
const nodes = path.slice(0, path.indexOf(e.currentTarget));
|
|
271
|
+
const closer = nodes.find(node =>
|
|
272
|
+
node instanceof Element &&
|
|
273
|
+
(node.hasAttribute('data-close') || (this.closeOnClick && (node.tagName === 'A' || node.tagName === 'BUTTON')))
|
|
274
|
+
);
|
|
275
|
+
if (closer) {
|
|
276
|
+
this.close();
|
|
277
|
+
}
|
|
149
278
|
}
|
|
150
279
|
|
|
151
280
|
get _listTpl() {
|
|
152
281
|
return html`
|
|
153
|
-
<div
|
|
282
|
+
<div
|
|
283
|
+
id="overlay"
|
|
284
|
+
class="${this._overlayClass}"
|
|
285
|
+
?inert="${!this.opened}"
|
|
286
|
+
@click="${this._onOverlayClick}"
|
|
287
|
+
>
|
|
154
288
|
<slot name="content-top"></slot>
|
|
155
289
|
<slot></slot>
|
|
156
290
|
<slot name="content-bottom"></slot>
|
|
@@ -165,4 +299,4 @@ export class DileFloatingMenu extends LitElement {
|
|
|
165
299
|
`
|
|
166
300
|
}
|
|
167
301
|
|
|
168
|
-
}
|
|
302
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { LitElement, html, css } from 'lit';
|
|
2
|
+
import { ifDefined } from 'lit/directives/if-defined.js';
|
|
2
3
|
|
|
3
4
|
export class DileFloatingMenuItem extends LitElement {
|
|
4
5
|
static styles = [
|
|
@@ -6,8 +7,9 @@ export class DileFloatingMenuItem extends LitElement {
|
|
|
6
7
|
:host {
|
|
7
8
|
display: block;
|
|
8
9
|
}
|
|
9
|
-
|
|
10
|
+
|
|
10
11
|
a {
|
|
12
|
+
display: block;
|
|
11
13
|
text-decoration: none;
|
|
12
14
|
color: var(--dile-floating-menu-item-color, aliceblue);
|
|
13
15
|
margin: var(--dile-floating-menu-item-margin, 0.5rem 0);
|
|
@@ -16,6 +18,11 @@ export class DileFloatingMenuItem extends LitElement {
|
|
|
16
18
|
a:hover {
|
|
17
19
|
color: var(--dile-floating-menu-item-color-hover, lightblue);
|
|
18
20
|
}
|
|
21
|
+
|
|
22
|
+
a:focus-visible {
|
|
23
|
+
outline: 2px solid currentColor;
|
|
24
|
+
outline-offset: 2px;
|
|
25
|
+
}
|
|
19
26
|
`
|
|
20
27
|
];
|
|
21
28
|
|
|
@@ -23,7 +30,7 @@ export class DileFloatingMenuItem extends LitElement {
|
|
|
23
30
|
|
|
24
31
|
href: {
|
|
25
32
|
type: String,
|
|
26
|
-
reflect: true,
|
|
33
|
+
reflect: true,
|
|
27
34
|
},
|
|
28
35
|
|
|
29
36
|
target: {
|
|
@@ -36,7 +43,7 @@ export class DileFloatingMenuItem extends LitElement {
|
|
|
36
43
|
super();
|
|
37
44
|
this.href = '';
|
|
38
45
|
this.target = '';
|
|
39
|
-
|
|
46
|
+
|
|
40
47
|
}
|
|
41
48
|
|
|
42
49
|
connectedCallback() {
|
|
@@ -46,7 +53,7 @@ export class DileFloatingMenuItem extends LitElement {
|
|
|
46
53
|
|
|
47
54
|
render() {
|
|
48
55
|
return html`
|
|
49
|
-
<a href="${this.href}" target="${this.target}"
|
|
56
|
+
<a href="${ifDefined(this.href || undefined)}" target="${ifDefined(this.target || undefined)}"><slot></slot></a>
|
|
50
57
|
`;
|
|
51
58
|
}
|
|
52
|
-
}
|
|
59
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dile/ui",
|
|
3
|
-
"version": "3.18.
|
|
3
|
+
"version": "3.18.2",
|
|
4
4
|
"description": "UI Core components from dile-components.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -28,5 +28,5 @@
|
|
|
28
28
|
"publishConfig": {
|
|
29
29
|
"access": "public"
|
|
30
30
|
},
|
|
31
|
-
"gitHead": "
|
|
31
|
+
"gitHead": "b59afd879d78118d5ea1cdc53e144f1a84628f13"
|
|
32
32
|
}
|