@jungherz-de/glasskit-elements 1.11.0 → 1.13.0
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/README.md +4 -4
- package/dist/components/glk-accordion-item.js +1 -1
- package/dist/components/glk-accordion.js +1 -1
- package/dist/components/glk-avatar.js +1 -1
- package/dist/components/glk-badge.js +67 -7
- package/dist/components/glk-button.js +1 -1
- package/dist/components/glk-card.js +1 -1
- package/dist/components/glk-checkbox.js +1 -1
- package/dist/components/glk-divider.js +1 -1
- package/dist/components/glk-input.js +1 -1
- package/dist/components/glk-list-item.js +1 -1
- package/dist/components/glk-list.js +1 -1
- package/dist/components/glk-modal.js +19 -12
- package/dist/components/glk-nav.js +1 -1
- package/dist/components/glk-pill.js +1 -1
- package/dist/components/glk-popover.js +1 -1
- package/dist/components/glk-progress.js +1 -1
- package/dist/components/glk-radio.js +1 -1
- package/dist/components/glk-range.js +1 -1
- package/dist/components/glk-search.js +1 -1
- package/dist/components/glk-select.js +39 -8
- package/dist/components/glk-status.js +1 -1
- package/dist/components/glk-tab-accessory.js +1 -1
- package/dist/components/glk-tab-bar.js +1 -1
- package/dist/components/glk-tab-dock.js +1 -1
- package/dist/components/glk-tab-item.js +26 -15
- package/dist/components/glk-textarea.js +1 -1
- package/dist/components/glk-title.js +1 -1
- package/dist/components/glk-toast.js +1 -1
- package/dist/components/glk-toggle.js +1 -1
- package/dist/components/shared/{base-C0B1hgOt.js → base-ClsXgLON.js} +38 -5
- package/dist/glasskit-elements.esm.js +185 -43
- package/dist/glasskit-elements.js +185 -43
- package/dist/glasskit-elements.min.js +1 -1
- package/package.json +4 -3
- package/src/base.js +36 -3
- package/src/components/content/glk-badge.js +66 -6
- package/src/components/feedback/glk-modal.js +18 -11
- package/src/components/forms/glk-select.js +38 -7
- package/src/components/navigation/glk-tab-item.js +25 -14
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<a href="#"><img src="https://img.shields.io/badge/vanilla_JS-no_dependencies-44cc11?style=flat-square" alt="Vanilla JS"></a>
|
|
6
6
|
<a href="#"><img src="https://img.shields.io/badge/components-29-7ec8e3?style=flat-square" alt="29 Components"></a>
|
|
7
7
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue?style=flat-square" alt="MIT License"></a>
|
|
8
|
-
<a href="CHANGELOG.md"><img src="https://img.shields.io/badge/changelog-v1.
|
|
8
|
+
<a href="CHANGELOG.md"><img src="https://img.shields.io/badge/changelog-v1.13.0-lightgrey?style=flat-square" alt="Changelog"></a>
|
|
9
9
|
<a href="https://www.npmjs.com/package/@jungherz-de/glasskit-elements"><img src="https://img.shields.io/badge/npm-%40jungherz--de%2Fglasskit--elements-cb3837?style=flat-square&logo=npm" alt="npm"></a>
|
|
10
10
|
</p>
|
|
11
11
|
|
|
@@ -66,7 +66,7 @@ It is the **app layer** of the GlassKit family — three layers, one design lang
|
|
|
66
66
|
|
|
67
67
|
```html
|
|
68
68
|
<!-- 1. GlassKit CSS -->
|
|
69
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit@1.
|
|
69
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit@1.12/glasskit.min.css">
|
|
70
70
|
|
|
71
71
|
<!-- 2. GlassKit Elements -->
|
|
72
72
|
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit-elements/dist/glasskit-elements.min.js"></script>
|
|
@@ -99,7 +99,7 @@ import '@jungherz-de/glasskit-elements/components/glk-toggle.js';
|
|
|
99
99
|
<!DOCTYPE html>
|
|
100
100
|
<html data-theme="dark">
|
|
101
101
|
<head>
|
|
102
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit@1.
|
|
102
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit@1.12/glasskit.min.css">
|
|
103
103
|
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit-elements/dist/glasskit-elements.min.js"></script>
|
|
104
104
|
</head>
|
|
105
105
|
<body>
|
|
@@ -130,7 +130,7 @@ import '@jungherz-de/glasskit-elements/components/glk-toggle.js';
|
|
|
130
130
|
| Component | Description | Key Attributes |
|
|
131
131
|
|---|---|---|
|
|
132
132
|
| `<glk-card>` | Glass-effect content card | `glow` |
|
|
133
|
-
| `<glk-badge>` | Inline status badge | `variant` (primary, success, error) |
|
|
133
|
+
| `<glk-badge>` | Inline status badge, or a filter chip with `interactive` | `variant` (primary, success, error), `interactive`, `selected` |
|
|
134
134
|
| `<glk-avatar>` | Circular avatar | `size` (sm, lg), `src` |
|
|
135
135
|
| `<glk-title>` | Styled heading | — |
|
|
136
136
|
| `<glk-divider>` | Horizontal divider | — |
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { G as GlkElement } from './shared/base-
|
|
1
|
+
import { G as GlkElement } from './shared/base-ClsXgLON.js';
|
|
2
2
|
|
|
3
3
|
const CHEVRON_SVG = `<span class="glass-accordion__trigger-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><polyline points="6 9 12 15 18 9"/></svg></span>`;
|
|
4
4
|
|
|
@@ -1,22 +1,74 @@
|
|
|
1
|
-
import { G as GlkElement } from './shared/base-
|
|
1
|
+
import { G as GlkElement } from './shared/base-ClsXgLON.js';
|
|
2
2
|
|
|
3
3
|
const VARIANTS = ['primary', 'success', 'error'];
|
|
4
4
|
|
|
5
5
|
class GlkBadge extends GlkElement {
|
|
6
6
|
static get displayInline() { return true; }
|
|
7
7
|
static get observedAttributes() {
|
|
8
|
-
return ['variant'];
|
|
8
|
+
return ['variant', 'interactive', 'selected'];
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
render() {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
// One slot for the lifetime of the element — it moves between hosts when
|
|
13
|
+
// the badge turns into a chip and back, so the light-DOM children never
|
|
14
|
+
// have to be re-assigned.
|
|
15
|
+
this._slot = document.createElement('slot');
|
|
16
|
+
this._mount();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// A chip is a real <button>, so the keyboard, the focus ring and the
|
|
20
|
+
// aria-pressed announcement come for free; a plain badge stays a <span>.
|
|
21
|
+
// Toggling `interactive` therefore swaps the element, not just a class.
|
|
22
|
+
_mount() {
|
|
23
|
+
const interactive = this.getBoolAttr('interactive');
|
|
24
|
+
const el = this.createElement(interactive ? 'button' : 'span', this._computeClasses());
|
|
25
|
+
if (interactive) {
|
|
26
|
+
el.type = 'button';
|
|
27
|
+
el.setAttribute('aria-pressed', String(this.getBoolAttr('selected')));
|
|
28
|
+
}
|
|
29
|
+
el.appendChild(this._slot);
|
|
30
|
+
|
|
31
|
+
if (this._badge) {
|
|
32
|
+
this._badge.removeEventListener('click', this._onClick);
|
|
33
|
+
this._badge.replaceWith(el);
|
|
34
|
+
} else {
|
|
35
|
+
this._wrapper.appendChild(el);
|
|
36
|
+
}
|
|
37
|
+
this._badge = el;
|
|
38
|
+
// setupEvents() has not run yet on the very first mount; it attaches then.
|
|
39
|
+
if (this._onClick) el.addEventListener('click', this._onClick);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
setupEvents() {
|
|
43
|
+
// Forward clicks as glk-click — only for chips, like glk-list-item.
|
|
44
|
+
this._onClick = () => {
|
|
45
|
+
if (this.getBoolAttr('interactive')) {
|
|
46
|
+
this.emit('glk-click');
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
this._badge.addEventListener('click', this._onClick);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
teardownEvents() {
|
|
53
|
+
this._badge?.removeEventListener('click', this._onClick);
|
|
54
|
+
// Forget the handler, or a swap while disconnected would re-attach it and
|
|
55
|
+
// the next connect would add a second one — two glk-click per click.
|
|
56
|
+
this._onClick = null;
|
|
15
57
|
}
|
|
16
58
|
|
|
17
59
|
onAttributeChanged(name) {
|
|
18
|
-
if (
|
|
19
|
-
|
|
60
|
+
if (!this._badge) return;
|
|
61
|
+
switch (name) {
|
|
62
|
+
case 'interactive':
|
|
63
|
+
this._mount();
|
|
64
|
+
break;
|
|
65
|
+
case 'variant':
|
|
66
|
+
case 'selected':
|
|
67
|
+
this._badge.className = this._computeClasses().join(' ');
|
|
68
|
+
if (this._badge.tagName === 'BUTTON') {
|
|
69
|
+
this._badge.setAttribute('aria-pressed', String(this.getBoolAttr('selected')));
|
|
70
|
+
}
|
|
71
|
+
break;
|
|
20
72
|
}
|
|
21
73
|
}
|
|
22
74
|
|
|
@@ -26,11 +78,19 @@ class GlkBadge extends GlkElement {
|
|
|
26
78
|
if (variant && VARIANTS.includes(variant)) {
|
|
27
79
|
classes.push(`glass-badge--${variant}`);
|
|
28
80
|
}
|
|
81
|
+
if (this.getBoolAttr('interactive')) classes.push('glass-badge--interactive');
|
|
82
|
+
if (this.getBoolAttr('selected')) classes.push('glass-badge--selected');
|
|
29
83
|
return classes;
|
|
30
84
|
}
|
|
31
85
|
|
|
32
86
|
get variant() { return this.getAttribute('variant'); }
|
|
33
87
|
set variant(v) { this.setAttribute('variant', v); }
|
|
88
|
+
|
|
89
|
+
get interactive() { return this.getBoolAttr('interactive'); }
|
|
90
|
+
set interactive(v) { this.setBoolAttr('interactive', v); }
|
|
91
|
+
|
|
92
|
+
get selected() { return this.getBoolAttr('selected'); }
|
|
93
|
+
set selected(v) { this.setBoolAttr('selected', v); }
|
|
34
94
|
}
|
|
35
95
|
|
|
36
96
|
customElements.define('glk-badge', GlkBadge);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { G as GlkElement } from './shared/base-
|
|
1
|
+
import { G as GlkElement } from './shared/base-ClsXgLON.js';
|
|
2
2
|
|
|
3
3
|
// <glk-card fill> stretches the visible card to the height of its grid or flex
|
|
4
4
|
// cell. The host must become a grid for this: it is stretched by the outer
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as GlkFormElement } from './shared/base-
|
|
1
|
+
import { a as GlkFormElement } from './shared/base-ClsXgLON.js';
|
|
2
2
|
|
|
3
3
|
const CHECKMARK_SVG = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>`;
|
|
4
4
|
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { G as GlkElement } from './shared/base-
|
|
1
|
+
import { G as GlkElement } from './shared/base-ClsXgLON.js';
|
|
2
2
|
|
|
3
3
|
class GlkModal extends GlkElement {
|
|
4
4
|
static get observedAttributes() {
|
|
5
5
|
return ['open', 'title'];
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
static get observesLightDom() { return true; }
|
|
9
|
+
|
|
8
10
|
render() {
|
|
9
11
|
this._overlay = this.createElement('div', ['glass-modal-overlay'], { part: 'overlay' });
|
|
10
12
|
|
|
@@ -28,7 +30,7 @@ class GlkModal extends GlkElement {
|
|
|
28
30
|
modal.appendChild(this._footer);
|
|
29
31
|
|
|
30
32
|
// Defer footer population — children may not be parsed yet
|
|
31
|
-
requestAnimationFrame(() => this.
|
|
33
|
+
requestAnimationFrame(() => this.projectLightDom());
|
|
32
34
|
|
|
33
35
|
this._overlay.appendChild(modal);
|
|
34
36
|
this._wrapper.appendChild(this._overlay);
|
|
@@ -38,18 +40,23 @@ class GlkModal extends GlkElement {
|
|
|
38
40
|
}
|
|
39
41
|
}
|
|
40
42
|
|
|
41
|
-
|
|
43
|
+
projectLightDom() {
|
|
44
|
+
const buttons = [...this.querySelectorAll('[slot="actions"] button')];
|
|
45
|
+
const signature = buttons.map(b => b.outerHTML).join('');
|
|
46
|
+
if (signature === this._footerSignature) return;
|
|
47
|
+
this._footerSignature = signature;
|
|
48
|
+
|
|
42
49
|
this._footer.innerHTML = '';
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
this._footer.appendChild(clone);
|
|
50
|
+
buttons.forEach((btn, i) => {
|
|
51
|
+
const clone = btn.cloneNode(true);
|
|
52
|
+
// Look the original up again at click time instead of closing over it: a
|
|
53
|
+
// framework that re-renders replaces the node, and a captured reference
|
|
54
|
+
// would then forward the click to a button no longer in the document.
|
|
55
|
+
clone.addEventListener('click', () => {
|
|
56
|
+
this.querySelectorAll('[slot="actions"] button')[i]?.click();
|
|
51
57
|
});
|
|
52
|
-
|
|
58
|
+
this._footer.appendChild(clone);
|
|
59
|
+
});
|
|
53
60
|
}
|
|
54
61
|
|
|
55
62
|
setupEvents() {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as GlkFormElement } from './shared/base-
|
|
1
|
+
import { a as GlkFormElement } from './shared/base-ClsXgLON.js';
|
|
2
2
|
|
|
3
3
|
const SEARCH_SVG = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>`;
|
|
4
4
|
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { a as GlkFormElement } from './shared/base-
|
|
1
|
+
import { a as GlkFormElement } from './shared/base-ClsXgLON.js';
|
|
2
2
|
|
|
3
3
|
class GlkSelect extends GlkFormElement {
|
|
4
4
|
static get observedAttributes() {
|
|
5
5
|
return ['label', 'disabled', 'name', 'value', 'required'];
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
static get observesLightDom() { return true; }
|
|
9
|
+
|
|
8
10
|
render() {
|
|
9
11
|
const group = this.createElement('div', ['glass-input-group']);
|
|
10
12
|
|
|
@@ -25,12 +27,29 @@ class GlkSelect extends GlkFormElement {
|
|
|
25
27
|
this._wrapper.appendChild(group);
|
|
26
28
|
|
|
27
29
|
// Defer option copying — children may not be parsed yet in connectedCallback
|
|
28
|
-
requestAnimationFrame(() =>
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
requestAnimationFrame(() => this.projectLightDom());
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
projectLightDom() {
|
|
34
|
+
// The options are pure data — the clones carry no listeners — so skipping an
|
|
35
|
+
// unchanged rebuild is safe, and it keeps an open dropdown from snapping shut
|
|
36
|
+
// on light-DOM churn elsewhere.
|
|
37
|
+
const signature = [...this.querySelectorAll('option')].map(o => o.outerHTML).join('');
|
|
38
|
+
if (signature === this._optionSignature) return;
|
|
39
|
+
this._optionSignature = signature;
|
|
40
|
+
|
|
41
|
+
// innerHTML = '' below drops the selection, so remember it first. A
|
|
42
|
+
// selectedIndex of -1 means "nothing is selected", which is not the same as
|
|
43
|
+
// an option whose value happens to be the empty string.
|
|
44
|
+
const previous = this._select.selectedIndex >= 0 ? this._select.value : null;
|
|
45
|
+
|
|
46
|
+
this._moveOptions();
|
|
47
|
+
|
|
48
|
+
// Keep the live selection when it survived the rebuild; otherwise fall back
|
|
49
|
+
// to the value attribute. Without this the selection jumps back to the first
|
|
50
|
+
// entry every time the list is updated.
|
|
51
|
+
if (!this._applyValue(previous)) this._applyValue(this.getAttribute('value'));
|
|
52
|
+
this._syncFormValue();
|
|
34
53
|
}
|
|
35
54
|
|
|
36
55
|
_moveOptions() {
|
|
@@ -41,6 +60,18 @@ class GlkSelect extends GlkFormElement {
|
|
|
41
60
|
});
|
|
42
61
|
}
|
|
43
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Selects `value` if an option carries it, and reports whether it did. The
|
|
65
|
+
* empty string is a value like any other — "" is a real option in plenty of
|
|
66
|
+
* forms ("detect automatically", "enter your own below").
|
|
67
|
+
*/
|
|
68
|
+
_applyValue(value) {
|
|
69
|
+
if (value === null) return false;
|
|
70
|
+
if (![...this._select.options].some(o => o.value === value)) return false;
|
|
71
|
+
this._select.value = value;
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
|
|
44
75
|
setupEvents() {
|
|
45
76
|
this._onChange = () => {
|
|
46
77
|
this._syncFormValue();
|
|
@@ -67,7 +98,7 @@ class GlkSelect extends GlkFormElement {
|
|
|
67
98
|
this._select.setAttribute('name', this.getAttribute('name') || '');
|
|
68
99
|
break;
|
|
69
100
|
case 'value':
|
|
70
|
-
this.
|
|
101
|
+
this._applyValue(this.getAttribute('value'));
|
|
71
102
|
this._syncFormValue();
|
|
72
103
|
break;
|
|
73
104
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { G as GlkElement } from './shared/base-
|
|
1
|
+
import { G as GlkElement } from './shared/base-ClsXgLON.js';
|
|
2
2
|
|
|
3
3
|
class GlkTabItem extends GlkElement {
|
|
4
4
|
static get observedAttributes() {
|
|
5
5
|
return ['label', 'active', 'badge'];
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
static get observesLightDom() { return true; }
|
|
9
|
+
|
|
8
10
|
render() {
|
|
9
11
|
this._btn = this.createElement('button', ['glass-tab-bar__item']);
|
|
10
12
|
if (this.getBoolAttr('active')) this._btn.classList.add('is-active');
|
|
@@ -30,23 +32,32 @@ class GlkTabItem extends GlkElement {
|
|
|
30
32
|
this._wrapper.appendChild(this._btn);
|
|
31
33
|
|
|
32
34
|
// Defer icon cloning — children may not be parsed yet
|
|
33
|
-
requestAnimationFrame(() => this.
|
|
35
|
+
requestAnimationFrame(() => this.projectLightDom());
|
|
34
36
|
}
|
|
35
37
|
|
|
36
|
-
|
|
38
|
+
projectLightDom() {
|
|
37
39
|
const svg = this.querySelector('svg');
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
40
|
+
const signature = svg ? svg.outerHTML : '';
|
|
41
|
+
if (signature === this._iconSignature) return;
|
|
42
|
+
this._iconSignature = signature;
|
|
43
|
+
|
|
44
|
+
// Drop the previous clone by hand: the icon container also holds the badge,
|
|
45
|
+
// and inserting without removing would stack a second icon on every update.
|
|
46
|
+
this._iconClone?.remove();
|
|
47
|
+
this._iconClone = null;
|
|
48
|
+
if (!svg) return;
|
|
49
|
+
|
|
50
|
+
const clone = svg.cloneNode(true);
|
|
51
|
+
// Remove inline attributes that would override GlassKit styles
|
|
52
|
+
clone.removeAttribute('width');
|
|
53
|
+
clone.removeAttribute('height');
|
|
54
|
+
clone.removeAttribute('stroke');
|
|
55
|
+
clone.removeAttribute('stroke-width');
|
|
56
|
+
clone.removeAttribute('stroke-linecap');
|
|
57
|
+
clone.removeAttribute('stroke-linejoin');
|
|
58
|
+
clone.removeAttribute('fill');
|
|
59
|
+
this._iconEl.insertBefore(clone, this._iconEl.firstChild);
|
|
60
|
+
this._iconClone = clone;
|
|
50
61
|
}
|
|
51
62
|
|
|
52
63
|
setupEvents() {
|