kempo-ui 0.0.40 → 0.0.41
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/dist/components/Dropdown.js +123 -0
- package/dist/components/ShadowComponent.js +1 -1
- package/dist/components/Spinner.js +113 -0
- package/docs/components/dropdown.html +282 -0
- package/docs/components/spinner.html +214 -0
- package/docs/index.html +12 -0
- package/docs/nav-1.inc.html +2 -0
- package/docs/src/components/Dropdown.js +123 -0
- package/docs/src/components/ShadowComponent.js +1 -1
- package/docs/src/components/Spinner.js +113 -0
- package/package.json +1 -1
- package/src/components/Dropdown.js +358 -0
- package/src/components/Spinner.js +152 -0
- package/tests/components/Dropdown.browser-test.js +557 -0
- package/tests/components/HybridComponent.browser-test.js +8 -2
- package/tests/components/ShadowComponent.browser-test.js +6 -6
- package/tests/components/Spinner.browser-test.js +335 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import { html, css } from '../lit-all.min.js';
|
|
2
|
+
import ShadowComponent from './ShadowComponent.js';
|
|
3
|
+
import { boolTrueFalse } from '../utils/propConverters.js';
|
|
4
|
+
|
|
5
|
+
// Track all open dropdowns for mutual exclusion
|
|
6
|
+
const openDropdowns = new Set();
|
|
7
|
+
|
|
8
|
+
export default class Dropdown extends ShadowComponent {
|
|
9
|
+
static properties = {
|
|
10
|
+
opened: { type: Boolean, reflect: true },
|
|
11
|
+
openDirection: { type: String, reflect: true, attribute: 'open-direction' },
|
|
12
|
+
closeOnSelect: { type: Boolean, reflect: true, attribute: 'close-on-select', converter: boolTrueFalse },
|
|
13
|
+
closeOnClickOutside: { type: Boolean, reflect: true, attribute: 'close-on-click-outside', converter: boolTrueFalse }
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
anchorId = `dropdown-anchor-${Math.random().toString(36).slice(2, 11)}`;
|
|
17
|
+
|
|
18
|
+
constructor() {
|
|
19
|
+
super();
|
|
20
|
+
this.opened = false;
|
|
21
|
+
this.openDirection = 'down left';
|
|
22
|
+
this.closeOnSelect = true;
|
|
23
|
+
this.closeOnClickOutside = true;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/*
|
|
27
|
+
Lifecycle Callbacks
|
|
28
|
+
*/
|
|
29
|
+
connectedCallback() {
|
|
30
|
+
super.connectedCallback();
|
|
31
|
+
document.addEventListener('click', this.handleDocumentClick);
|
|
32
|
+
document.addEventListener('keydown', this.handleKeydown);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
disconnectedCallback() {
|
|
36
|
+
super.disconnectedCallback();
|
|
37
|
+
document.removeEventListener('click', this.handleDocumentClick);
|
|
38
|
+
document.removeEventListener('keydown', this.handleKeydown);
|
|
39
|
+
openDropdowns.delete(this);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
updated(changedProperties) {
|
|
43
|
+
super.updated(changedProperties);
|
|
44
|
+
if(changedProperties.has('opened')) {
|
|
45
|
+
if(this.opened) {
|
|
46
|
+
openDropdowns.add(this);
|
|
47
|
+
} else {
|
|
48
|
+
openDropdowns.delete(this);
|
|
49
|
+
}
|
|
50
|
+
this.dispatchEvent(new CustomEvent(this.opened ? 'opened' : 'closed', {
|
|
51
|
+
bubbles: true
|
|
52
|
+
}));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/*
|
|
57
|
+
Event Handlers
|
|
58
|
+
*/
|
|
59
|
+
handleDocumentClick = e => {
|
|
60
|
+
const clickedDropdown = e.target.closest('k-dropdown');
|
|
61
|
+
const isTrigger = e.target.closest('[slot="trigger"]');
|
|
62
|
+
|
|
63
|
+
// If clicking another dropdown's trigger, close this one
|
|
64
|
+
if(clickedDropdown && clickedDropdown !== this && isTrigger) {
|
|
65
|
+
if(this.opened) this.close();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if(!this.opened) return;
|
|
70
|
+
|
|
71
|
+
// Click is outside this dropdown entirely
|
|
72
|
+
if(clickedDropdown !== this) {
|
|
73
|
+
if(this.closeOnClickOutside) this.close();
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Click is on this dropdown's trigger - let handleTriggerClick handle it
|
|
78
|
+
if(isTrigger) return;
|
|
79
|
+
|
|
80
|
+
// Click is inside the menu on an interactive element
|
|
81
|
+
const isInteractive = e.target.closest('a, button');
|
|
82
|
+
if(isInteractive && this.closeOnSelect) this.close();
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
handleTriggerClick = e => {
|
|
86
|
+
e.stopPropagation();
|
|
87
|
+
// Close all other dropdowns before opening this one
|
|
88
|
+
if(!this.opened) {
|
|
89
|
+
openDropdowns.forEach(dropdown => {
|
|
90
|
+
if(dropdown !== this) dropdown.close();
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
this.toggle();
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
handleMenuClick = e => {
|
|
97
|
+
const item = e.target.closest('a, button');
|
|
98
|
+
if(item && !item.hasAttribute('disabled')) {
|
|
99
|
+
const value = item.dataset?.value || item.textContent.trim();
|
|
100
|
+
this.dispatchEvent(new CustomEvent('select', {
|
|
101
|
+
detail: { value, item },
|
|
102
|
+
bubbles: true
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
handleKeydown = e => {
|
|
108
|
+
if(!this.opened) return;
|
|
109
|
+
if(e.key === 'Escape') {
|
|
110
|
+
e.preventDefault();
|
|
111
|
+
this.close();
|
|
112
|
+
this.focusTrigger();
|
|
113
|
+
} else if(e.key === 'ArrowDown') {
|
|
114
|
+
e.preventDefault();
|
|
115
|
+
this.focusNextItem();
|
|
116
|
+
} else if(e.key === 'ArrowUp') {
|
|
117
|
+
e.preventDefault();
|
|
118
|
+
this.focusPreviousItem();
|
|
119
|
+
} else if(e.key === 'Enter' || e.key === ' ') {
|
|
120
|
+
const focused = this.querySelector('a:focus, button:focus');
|
|
121
|
+
if(focused) {
|
|
122
|
+
e.preventDefault();
|
|
123
|
+
focused.click();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/*
|
|
129
|
+
Public Methods
|
|
130
|
+
*/
|
|
131
|
+
open() {
|
|
132
|
+
// Close all other dropdowns first
|
|
133
|
+
openDropdowns.forEach(dropdown => {
|
|
134
|
+
if(dropdown !== this) dropdown.close();
|
|
135
|
+
});
|
|
136
|
+
this.opened = true;
|
|
137
|
+
requestAnimationFrame(() => this.focusFirstItem());
|
|
138
|
+
return this;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
close() {
|
|
142
|
+
this.opened = false;
|
|
143
|
+
return this;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
toggle() {
|
|
147
|
+
return this.opened ? this.close() : this.open();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/*
|
|
151
|
+
Private Methods
|
|
152
|
+
*/
|
|
153
|
+
focusTrigger() {
|
|
154
|
+
const trigger = this.querySelector('[slot="trigger"]');
|
|
155
|
+
if(trigger) trigger.focus();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
getMenuItems() {
|
|
159
|
+
return [...this.querySelectorAll('a, button')].filter(
|
|
160
|
+
el => !el.hasAttribute('disabled') && !el.closest('[slot="trigger"]')
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
focusFirstItem() {
|
|
165
|
+
const items = this.getMenuItems();
|
|
166
|
+
if(items.length > 0) items[0].focus();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
focusNextItem() {
|
|
170
|
+
const items = this.getMenuItems();
|
|
171
|
+
const current = document.activeElement;
|
|
172
|
+
const index = items.indexOf(current);
|
|
173
|
+
const next = items[index + 1] || items[0];
|
|
174
|
+
if(next) next.focus();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
focusPreviousItem() {
|
|
178
|
+
const items = this.getMenuItems();
|
|
179
|
+
const current = document.activeElement;
|
|
180
|
+
const index = items.indexOf(current);
|
|
181
|
+
const prev = items[index - 1] || items[items.length - 1];
|
|
182
|
+
if(prev) prev.focus();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
getPositionArea() {
|
|
186
|
+
const dir = this.openDirection.toLowerCase().trim();
|
|
187
|
+
const parts = dir.split(/\s+/);
|
|
188
|
+
// Map friendly names to position-area values
|
|
189
|
+
const mapping = {
|
|
190
|
+
'down': 'bottom',
|
|
191
|
+
'up': 'top',
|
|
192
|
+
'left': 'left',
|
|
193
|
+
'right': 'right',
|
|
194
|
+
'center': 'center'
|
|
195
|
+
};
|
|
196
|
+
const mapped = parts.map(p => mapping[p] || p);
|
|
197
|
+
// position-area expects: row column (e.g., "bottom left")
|
|
198
|
+
// "down left" -> "bottom left", "up right" -> "top right"
|
|
199
|
+
return mapped.join(' ');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
getFallbacks() {
|
|
203
|
+
const primary = this.getPositionArea();
|
|
204
|
+
const parts = primary.split(' ');
|
|
205
|
+
// Generate sensible fallbacks
|
|
206
|
+
const fallbacks = [];
|
|
207
|
+
// flip-block flips vertical, flip-inline flips horizontal
|
|
208
|
+
fallbacks.push('flip-block');
|
|
209
|
+
fallbacks.push('flip-inline');
|
|
210
|
+
fallbacks.push('flip-block flip-inline');
|
|
211
|
+
return fallbacks.join(', ');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/*
|
|
215
|
+
Styles
|
|
216
|
+
*/
|
|
217
|
+
static styles = css`
|
|
218
|
+
:host {
|
|
219
|
+
display: inline-block;
|
|
220
|
+
position: relative;
|
|
221
|
+
}
|
|
222
|
+
#trigger {
|
|
223
|
+
cursor: pointer;
|
|
224
|
+
anchor-name: --dropdown-trigger;
|
|
225
|
+
}
|
|
226
|
+
#menu {
|
|
227
|
+
display: none;
|
|
228
|
+
position: fixed;
|
|
229
|
+
position-anchor: --dropdown-trigger;
|
|
230
|
+
z-index: 1000;
|
|
231
|
+
min-width: anchor-size(width);
|
|
232
|
+
background: var(--c_bg);
|
|
233
|
+
border: 1px solid var(--c_border);
|
|
234
|
+
box-shadow: var(--drop_shadow);
|
|
235
|
+
margin: 0.25rem;
|
|
236
|
+
}
|
|
237
|
+
:host([opened]) #menu {
|
|
238
|
+
display: block;
|
|
239
|
+
}
|
|
240
|
+
/* Default: down left */
|
|
241
|
+
#menu {
|
|
242
|
+
position-area: bottom span-right;
|
|
243
|
+
position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;
|
|
244
|
+
}
|
|
245
|
+
/* down right */
|
|
246
|
+
:host([open-direction="down right"]) #menu {
|
|
247
|
+
position-area: bottom span-left;
|
|
248
|
+
}
|
|
249
|
+
/* down center */
|
|
250
|
+
:host([open-direction="down center"]) #menu,
|
|
251
|
+
:host([open-direction="down"]) #menu {
|
|
252
|
+
position-area: bottom center;
|
|
253
|
+
}
|
|
254
|
+
/* up left */
|
|
255
|
+
:host([open-direction="up left"]) #menu {
|
|
256
|
+
position-area: top span-right;
|
|
257
|
+
}
|
|
258
|
+
/* up right */
|
|
259
|
+
:host([open-direction="up right"]) #menu {
|
|
260
|
+
position-area: top span-left;
|
|
261
|
+
}
|
|
262
|
+
/* up center */
|
|
263
|
+
:host([open-direction="up center"]) #menu,
|
|
264
|
+
:host([open-direction="up"]) #menu {
|
|
265
|
+
position-area: top center;
|
|
266
|
+
}
|
|
267
|
+
/* left up */
|
|
268
|
+
:host([open-direction="left up"]) #menu {
|
|
269
|
+
position-area: left span-top;
|
|
270
|
+
}
|
|
271
|
+
/* left down */
|
|
272
|
+
:host([open-direction="left down"]) #menu {
|
|
273
|
+
position-area: left span-bottom;
|
|
274
|
+
}
|
|
275
|
+
/* left center */
|
|
276
|
+
:host([open-direction="left center"]) #menu,
|
|
277
|
+
:host([open-direction="left"]) #menu {
|
|
278
|
+
position-area: left center;
|
|
279
|
+
}
|
|
280
|
+
/* right up */
|
|
281
|
+
:host([open-direction="right up"]) #menu {
|
|
282
|
+
position-area: right span-top;
|
|
283
|
+
}
|
|
284
|
+
/* right down */
|
|
285
|
+
:host([open-direction="right down"]) #menu {
|
|
286
|
+
position-area: right span-bottom;
|
|
287
|
+
}
|
|
288
|
+
/* right center */
|
|
289
|
+
:host([open-direction="right center"]) #menu,
|
|
290
|
+
:host([open-direction="right"]) #menu {
|
|
291
|
+
position-area: right center;
|
|
292
|
+
}
|
|
293
|
+
/* Slotted menu item styles (not trigger) */
|
|
294
|
+
::slotted(a:not([slot="trigger"])),
|
|
295
|
+
::slotted(button:not([slot="trigger"])) {
|
|
296
|
+
all: unset !important;
|
|
297
|
+
display: block !important;
|
|
298
|
+
box-sizing: border-box !important;
|
|
299
|
+
width: 100% !important;
|
|
300
|
+
padding: var(--spacer_h) var(--spacer) !important;
|
|
301
|
+
color: var(--tc) !important;
|
|
302
|
+
background: transparent !important;
|
|
303
|
+
border: none !important;
|
|
304
|
+
border-top: 1px solid var(--c_border) !important;
|
|
305
|
+
border-radius: 0 !important;
|
|
306
|
+
font: inherit !important;
|
|
307
|
+
text-align: left !important;
|
|
308
|
+
cursor: pointer !important;
|
|
309
|
+
white-space: nowrap !important;
|
|
310
|
+
transition: background var(--animation_ms) !important;
|
|
311
|
+
}
|
|
312
|
+
::slotted(a.k-dropdown-first),
|
|
313
|
+
::slotted(button.k-dropdown-first) {
|
|
314
|
+
border-top: none !important;
|
|
315
|
+
}
|
|
316
|
+
::slotted(a:not([slot="trigger"]):hover),
|
|
317
|
+
::slotted(a:not([slot="trigger"]):focus-visible),
|
|
318
|
+
::slotted(button:not([slot="trigger"]):hover),
|
|
319
|
+
::slotted(button:not([slot="trigger"]):focus-visible) {
|
|
320
|
+
background: var(--c_bg__alt) !important;
|
|
321
|
+
outline: none !important;
|
|
322
|
+
}
|
|
323
|
+
::slotted(a:not([slot="trigger"])[disabled]),
|
|
324
|
+
::slotted(button:not([slot="trigger"])[disabled]) {
|
|
325
|
+
opacity: 0.5 !important;
|
|
326
|
+
cursor: not-allowed !important;
|
|
327
|
+
pointer-events: none !important;
|
|
328
|
+
}
|
|
329
|
+
::slotted(hr) {
|
|
330
|
+
display: none !important;
|
|
331
|
+
}
|
|
332
|
+
`;
|
|
333
|
+
|
|
334
|
+
/*
|
|
335
|
+
Slot Change Handler
|
|
336
|
+
*/
|
|
337
|
+
handleSlotChange = e => {
|
|
338
|
+
const slot = e.target;
|
|
339
|
+
const items = slot.assignedElements().filter(el => el.matches('a, button'));
|
|
340
|
+
items.forEach((item, i) => item.classList.toggle('k-dropdown-first', i === 0));
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
/*
|
|
344
|
+
Rendering
|
|
345
|
+
*/
|
|
346
|
+
render() {
|
|
347
|
+
return html`
|
|
348
|
+
<div id="trigger" @click=${this.handleTriggerClick}>
|
|
349
|
+
<slot name="trigger"></slot>
|
|
350
|
+
</div>
|
|
351
|
+
<div id="menu" role="menu" @click=${this.handleMenuClick}>
|
|
352
|
+
<slot @slotchange=${this.handleSlotChange}></slot>
|
|
353
|
+
</div>
|
|
354
|
+
`;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
customElements.define('k-dropdown', Dropdown);
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { html, css } from '../lit-all.min.js';
|
|
2
|
+
import ShadowComponent from './ShadowComponent.js';
|
|
3
|
+
|
|
4
|
+
export default class Spinner extends ShadowComponent {
|
|
5
|
+
static properties = {
|
|
6
|
+
size: { type: String, reflect: true },
|
|
7
|
+
variant: { type: String, reflect: true }
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
constructor() {
|
|
11
|
+
super();
|
|
12
|
+
this.size = 'md';
|
|
13
|
+
this.variant = 'spinner';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/*
|
|
17
|
+
Styles
|
|
18
|
+
*/
|
|
19
|
+
static styles = css`
|
|
20
|
+
:host {
|
|
21
|
+
--spinner-size: 2rem;
|
|
22
|
+
--spinner-border-width: 3px;
|
|
23
|
+
--spinner-color: var(--c_primary);
|
|
24
|
+
--spinner-track-color: var(--c_border);
|
|
25
|
+
display: inline-flex;
|
|
26
|
+
align-items: center;
|
|
27
|
+
justify-content: center;
|
|
28
|
+
}
|
|
29
|
+
/* Sizes */
|
|
30
|
+
:host([size="xs"]) {
|
|
31
|
+
--spinner-size: 1rem;
|
|
32
|
+
--spinner-border-width: 2px;
|
|
33
|
+
}
|
|
34
|
+
:host([size="sm"]) {
|
|
35
|
+
--spinner-size: 1.5rem;
|
|
36
|
+
--spinner-border-width: 2px;
|
|
37
|
+
}
|
|
38
|
+
:host([size="md"]) {
|
|
39
|
+
--spinner-size: 2rem;
|
|
40
|
+
--spinner-border-width: 3px;
|
|
41
|
+
}
|
|
42
|
+
:host([size="lg"]) {
|
|
43
|
+
--spinner-size: 3rem;
|
|
44
|
+
--spinner-border-width: 4px;
|
|
45
|
+
}
|
|
46
|
+
:host([size="xl"]) {
|
|
47
|
+
--spinner-size: 4rem;
|
|
48
|
+
--spinner-border-width: 5px;
|
|
49
|
+
}
|
|
50
|
+
/* Spinner variant */
|
|
51
|
+
.spinner {
|
|
52
|
+
width: var(--spinner-size);
|
|
53
|
+
height: var(--spinner-size);
|
|
54
|
+
border: var(--spinner-border-width) solid var(--spinner-track-color);
|
|
55
|
+
border-top-color: var(--spinner-color);
|
|
56
|
+
border-radius: 50%;
|
|
57
|
+
animation: spin 0.8s linear infinite;
|
|
58
|
+
}
|
|
59
|
+
@keyframes spin {
|
|
60
|
+
to { transform: rotate(360deg); }
|
|
61
|
+
}
|
|
62
|
+
/* Dots variant */
|
|
63
|
+
.dots {
|
|
64
|
+
display: flex;
|
|
65
|
+
gap: calc(var(--spinner-size) * 0.2);
|
|
66
|
+
}
|
|
67
|
+
.dots span {
|
|
68
|
+
width: calc(var(--spinner-size) * 0.3);
|
|
69
|
+
height: calc(var(--spinner-size) * 0.3);
|
|
70
|
+
background: var(--spinner-color);
|
|
71
|
+
border-radius: 50%;
|
|
72
|
+
animation: bounce 1.4s ease-in-out infinite both;
|
|
73
|
+
}
|
|
74
|
+
.dots span:nth-child(1) { animation-delay: -0.32s; }
|
|
75
|
+
.dots span:nth-child(2) { animation-delay: -0.16s; }
|
|
76
|
+
.dots span:nth-child(3) { animation-delay: 0s; }
|
|
77
|
+
@keyframes bounce {
|
|
78
|
+
0%, 80%, 100% { transform: scale(0.6); opacity: 0.5; }
|
|
79
|
+
40% { transform: scale(1); opacity: 1; }
|
|
80
|
+
}
|
|
81
|
+
/* Bars variant */
|
|
82
|
+
.bars {
|
|
83
|
+
display: flex;
|
|
84
|
+
align-items: center;
|
|
85
|
+
gap: calc(var(--spinner-size) * 0.1);
|
|
86
|
+
height: var(--spinner-size);
|
|
87
|
+
}
|
|
88
|
+
.bars span {
|
|
89
|
+
width: calc(var(--spinner-size) * 0.15);
|
|
90
|
+
height: 60%;
|
|
91
|
+
background: var(--spinner-color);
|
|
92
|
+
border-radius: calc(var(--spinner-size) * 0.05);
|
|
93
|
+
animation: bars 1.2s ease-in-out infinite;
|
|
94
|
+
}
|
|
95
|
+
.bars span:nth-child(1) { animation-delay: -0.24s; }
|
|
96
|
+
.bars span:nth-child(2) { animation-delay: -0.12s; }
|
|
97
|
+
.bars span:nth-child(3) { animation-delay: 0s; }
|
|
98
|
+
.bars span:nth-child(4) { animation-delay: 0.12s; }
|
|
99
|
+
@keyframes bars {
|
|
100
|
+
0%, 40%, 100% { height: 40%; }
|
|
101
|
+
20% { height: 100%; }
|
|
102
|
+
}
|
|
103
|
+
/* Pulse variant */
|
|
104
|
+
.pulse {
|
|
105
|
+
width: var(--spinner-size);
|
|
106
|
+
height: var(--spinner-size);
|
|
107
|
+
background: var(--spinner-color);
|
|
108
|
+
border-radius: 50%;
|
|
109
|
+
animation: pulse 1.5s ease-in-out infinite;
|
|
110
|
+
}
|
|
111
|
+
@keyframes pulse {
|
|
112
|
+
0% { transform: scale(0.8); opacity: 0.5; }
|
|
113
|
+
50% { transform: scale(1); opacity: 1; }
|
|
114
|
+
100% { transform: scale(0.8); opacity: 0.5; }
|
|
115
|
+
}
|
|
116
|
+
/* Ring variant */
|
|
117
|
+
.ring {
|
|
118
|
+
width: var(--spinner-size);
|
|
119
|
+
height: var(--spinner-size);
|
|
120
|
+
border: var(--spinner-border-width) solid var(--spinner-color);
|
|
121
|
+
border-radius: 50%;
|
|
122
|
+
animation: ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
|
|
123
|
+
}
|
|
124
|
+
@keyframes ring {
|
|
125
|
+
0% { transform: rotate(0deg); border-color: var(--spinner-color) transparent transparent transparent; }
|
|
126
|
+
25% { border-color: transparent var(--spinner-color) transparent transparent; }
|
|
127
|
+
50% { border-color: transparent transparent var(--spinner-color) transparent; }
|
|
128
|
+
75% { border-color: transparent transparent transparent var(--spinner-color); }
|
|
129
|
+
100% { transform: rotate(360deg); border-color: var(--spinner-color) transparent transparent transparent; }
|
|
130
|
+
}
|
|
131
|
+
`;
|
|
132
|
+
|
|
133
|
+
/*
|
|
134
|
+
Rendering
|
|
135
|
+
*/
|
|
136
|
+
render() {
|
|
137
|
+
switch(this.variant) {
|
|
138
|
+
case 'dots':
|
|
139
|
+
return html`<div class="dots"><span></span><span></span><span></span></div>`;
|
|
140
|
+
case 'bars':
|
|
141
|
+
return html`<div class="bars"><span></span><span></span><span></span><span></span></div>`;
|
|
142
|
+
case 'pulse':
|
|
143
|
+
return html`<div class="pulse"></div>`;
|
|
144
|
+
case 'ring':
|
|
145
|
+
return html`<div class="ring"></div>`;
|
|
146
|
+
default:
|
|
147
|
+
return html`<div class="spinner"></div>`;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
customElements.define('k-spinner', Spinner);
|