@jungherz-de/glasskit-elements 1.25.0 → 1.27.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/CHANGELOG.md +29 -0
- package/README.md +36 -5
- package/SKILL.md +38 -4
- package/dist/components/glk-segmented.js +22 -3
- package/dist/components/glk-tab-bar.js +61 -1
- package/dist/components/glk-tab-item.js +22 -0
- package/dist/components/shared/glasskit-droplet-C3k1mxn4.js +217 -0
- package/dist/glasskit-elements.esm.js +322 -7
- package/dist/glasskit-elements.js +322 -7
- package/dist/glasskit-elements.min.js +1 -1
- package/package.json +3 -3
- package/src/components/forms/glk-segmented.js +22 -3
- package/src/components/navigation/glk-tab-bar.js +61 -1
- package/src/components/navigation/glk-tab-item.js +22 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jungherz-de/glasskit-elements",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.27.0",
|
|
4
4
|
"description": "Vanilla JavaScript Web Components wrapping the GlassKit CSS glassmorphism library",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://glasskit-elements.jungherz.com",
|
|
@@ -49,10 +49,10 @@
|
|
|
49
49
|
"vanilla-js"
|
|
50
50
|
],
|
|
51
51
|
"peerDependencies": {
|
|
52
|
-
"@jungherz-de/glasskit": ">=1.
|
|
52
|
+
"@jungherz-de/glasskit": ">=1.27.0"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"@jungherz-de/glasskit": "^1.
|
|
55
|
+
"@jungherz-de/glasskit": "^1.27.0",
|
|
56
56
|
"@rollup/plugin-node-resolve": "^15.0.0",
|
|
57
57
|
"@rollup/plugin-terser": "^0.4.0",
|
|
58
58
|
"rollup": "^4.0.0"
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { GlkFormElement } from '../../base.js';
|
|
2
2
|
import { revealCentered } from '../../reveal.js';
|
|
3
|
+
import { attachDroplet } from '@jungherz-de/glasskit/glasskit-droplet.js';
|
|
3
4
|
|
|
4
5
|
// One button per option, the chosen one marked aria-pressed="true" — GlassKit
|
|
5
6
|
// styles exactly that attribute. Came back from EhrenPfoten in 1.15.0.
|
|
@@ -9,9 +10,11 @@ import { revealCentered } from '../../reveal.js';
|
|
|
9
10
|
// do not fit — without it the row stays one line and runs past
|
|
10
11
|
// the edge), label (aria-label of the group), name (form field),
|
|
11
12
|
// disabled (the whole row; since 1.22.3, as is a disabled
|
|
12
|
-
// fieldset around it)
|
|
13
|
+
// fieldset around it), droplet (since 1.27.0: the chosen option
|
|
14
|
+
// sits on a drop of glass that glides to the next one and can be
|
|
15
|
+
// dragged — GlassKit's .glass-segmented--droplet)
|
|
13
16
|
// Properties: value, options (array or JSON text), full, overflow, label,
|
|
14
|
-
// disabled
|
|
17
|
+
// disabled, droplet
|
|
15
18
|
// Event: glk-change { value } — only on a change made by the user
|
|
16
19
|
// Part: group
|
|
17
20
|
//
|
|
@@ -40,7 +43,7 @@ function parseList(json) {
|
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
class GlkSegmented extends GlkFormElement {
|
|
43
|
-
static get observedAttributes() { return ['options', 'value', 'full', 'overflow', 'label']; }
|
|
46
|
+
static get observedAttributes() { return ['options', 'value', 'full', 'overflow', 'label', 'droplet']; }
|
|
44
47
|
|
|
45
48
|
// A row of buttons with one tab stop: delegated focus would land on the
|
|
46
49
|
// first button, not the chosen one.
|
|
@@ -91,6 +94,7 @@ class GlkSegmented extends GlkFormElement {
|
|
|
91
94
|
_applyLayout() {
|
|
92
95
|
const overflow = this.overflow;
|
|
93
96
|
this._group.classList.toggle('glass-segmented--full', this.getBoolAttr('full'));
|
|
97
|
+
this._group.classList.toggle('glass-segmented--droplet', this.getBoolAttr('droplet'));
|
|
94
98
|
for (const mode of OVERFLOWS) this._group.classList.toggle(`glass-segmented--${mode}`, overflow === mode);
|
|
95
99
|
this._reveal('instant');
|
|
96
100
|
}
|
|
@@ -120,12 +124,23 @@ class GlkSegmented extends GlkFormElement {
|
|
|
120
124
|
this._group.addEventListener('click', this._onClick);
|
|
121
125
|
this._resizeObserver = new ResizeObserver(this._onResize);
|
|
122
126
|
this._resizeObserver.observe(this._group);
|
|
127
|
+
this._applyDroplet();
|
|
123
128
|
}
|
|
124
129
|
|
|
125
130
|
teardownEvents() {
|
|
126
131
|
this._group?.removeEventListener('click', this._onClick);
|
|
127
132
|
this._resizeObserver?.disconnect();
|
|
128
133
|
this._resizeObserver = null;
|
|
134
|
+
this._droplet?.destroy();
|
|
135
|
+
this._droplet = null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// The drop follows aria-pressed inside this shadow root by itself; a drag
|
|
139
|
+
// that ends on an option clicks it, the same path as a tap.
|
|
140
|
+
_applyDroplet() {
|
|
141
|
+
const on = this.getBoolAttr('droplet') && this.isConnected;
|
|
142
|
+
if (on && !this._droplet) this._droplet = attachDroplet(this._group);
|
|
143
|
+
else if (!on && this._droplet) { this._droplet.destroy(); this._droplet = null; }
|
|
129
144
|
}
|
|
130
145
|
|
|
131
146
|
onAttributeChanged(name) {
|
|
@@ -135,6 +150,7 @@ class GlkSegmented extends GlkFormElement {
|
|
|
135
150
|
case 'options': this._build(); break;
|
|
136
151
|
case 'full':
|
|
137
152
|
case 'overflow': this._applyLayout(); break;
|
|
153
|
+
case 'droplet': this._applyLayout(); this._applyDroplet(); break;
|
|
138
154
|
case 'label': this._applyLabel(); break;
|
|
139
155
|
}
|
|
140
156
|
}
|
|
@@ -154,6 +170,9 @@ class GlkSegmented extends GlkFormElement {
|
|
|
154
170
|
get full() { return this.getBoolAttr('full'); }
|
|
155
171
|
set full(v) { this.setBoolAttr('full', v); }
|
|
156
172
|
|
|
173
|
+
get droplet() { return this.getBoolAttr('droplet'); }
|
|
174
|
+
set droplet(v) { this.setBoolAttr('droplet', v); }
|
|
175
|
+
|
|
157
176
|
get overflow() {
|
|
158
177
|
const v = this.getAttribute('overflow');
|
|
159
178
|
return OVERFLOWS.includes(v) ? v : '';
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import { GlkElement } from '../../base.js';
|
|
2
|
+
import { attachDroplet } from '@jungherz-de/glasskit/glasskit-droplet.js';
|
|
3
|
+
|
|
4
|
+
// droplet (since 1.27.0): the active item sits on a drop of glass that glides
|
|
5
|
+
// to the next one and can be dragged — GlassKit's .glass-tab-bar--droplet,
|
|
6
|
+
// moved by its glasskit-droplet.js. The items live in shadow roots of their
|
|
7
|
+
// own, so the bar hands its state down to them (see glk-tab-item _syncBar)
|
|
8
|
+
// and tells the drop when the active item changed.
|
|
2
9
|
|
|
3
10
|
class GlkTabBar extends GlkElement {
|
|
4
11
|
static get observedAttributes() {
|
|
5
|
-
return ['static', 'floating'];
|
|
12
|
+
return ['static', 'floating', 'droplet'];
|
|
6
13
|
}
|
|
7
14
|
|
|
8
15
|
render() {
|
|
@@ -14,6 +21,7 @@ class GlkTabBar extends GlkElement {
|
|
|
14
21
|
if (this.getBoolAttr('floating')) {
|
|
15
22
|
nav.classList.add('glass-tab-bar--floating');
|
|
16
23
|
}
|
|
24
|
+
nav.classList.toggle('glass-tab-bar--droplet', this.getBoolAttr('droplet'));
|
|
17
25
|
|
|
18
26
|
if (this.getBoolAttr('static')) {
|
|
19
27
|
this._applyStatic();
|
|
@@ -26,6 +34,10 @@ class GlkTabBar extends GlkElement {
|
|
|
26
34
|
this._applyStatic();
|
|
27
35
|
} else if (name === 'floating') {
|
|
28
36
|
this._nav.classList.toggle('glass-tab-bar--floating', this.getBoolAttr('floating'));
|
|
37
|
+
this._syncItems();
|
|
38
|
+
} else if (name === 'droplet') {
|
|
39
|
+
this._nav.classList.toggle('glass-tab-bar--droplet', this.getBoolAttr('droplet'));
|
|
40
|
+
this._applyDroplet();
|
|
29
41
|
}
|
|
30
42
|
}
|
|
31
43
|
|
|
@@ -34,6 +46,42 @@ class GlkTabBar extends GlkElement {
|
|
|
34
46
|
this._nav.style.borderRadius = '16px';
|
|
35
47
|
}
|
|
36
48
|
|
|
49
|
+
_items() {
|
|
50
|
+
return [...this.children].filter(el => el.localName === 'glk-tab-item');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Hands the bar's floating and droplet state to every item's wrapper. */
|
|
54
|
+
_syncItems() {
|
|
55
|
+
const state = {
|
|
56
|
+
floating: this.getBoolAttr('floating'),
|
|
57
|
+
droplet: this.getBoolAttr('droplet'),
|
|
58
|
+
placed: Boolean(this._nav?.hasAttribute('data-droplet')),
|
|
59
|
+
drag: Boolean(this._nav?.hasAttribute('data-droplet-drag')),
|
|
60
|
+
};
|
|
61
|
+
for (const item of this._items()) item._syncBar?.(state);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Called by an item that rendered or changed its active state. */
|
|
65
|
+
_itemChanged() {
|
|
66
|
+
this._syncItems();
|
|
67
|
+
this._droplet?.update();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
_applyDroplet() {
|
|
71
|
+
const on = this.getBoolAttr('droplet') && this.isConnected;
|
|
72
|
+
if (on && !this._droplet) {
|
|
73
|
+
// The drop measures and marks the items' own buttons; picking one clicks
|
|
74
|
+
// it, which is what a tap does.
|
|
75
|
+
this._droplet = attachDroplet(this._nav, {
|
|
76
|
+
items: () => this._items().map(item => item._btn).filter(Boolean),
|
|
77
|
+
});
|
|
78
|
+
} else if (!on && this._droplet) {
|
|
79
|
+
this._droplet.destroy();
|
|
80
|
+
this._droplet = null;
|
|
81
|
+
}
|
|
82
|
+
this._syncItems();
|
|
83
|
+
}
|
|
84
|
+
|
|
37
85
|
setupEvents() {
|
|
38
86
|
// Listen for tab selection events from child glk-tab-item elements
|
|
39
87
|
this._onTabSelect = (e) => {
|
|
@@ -47,10 +95,22 @@ class GlkTabBar extends GlkElement {
|
|
|
47
95
|
this.emit('glk-tab-change', { tab: e.target });
|
|
48
96
|
};
|
|
49
97
|
this.addEventListener('glk-tab-select', this._onTabSelect);
|
|
98
|
+
|
|
99
|
+
// The drop's state lives on the nav; the items carry it on their wrappers.
|
|
100
|
+
this._navObserver = new MutationObserver(() => this._syncItems());
|
|
101
|
+
this._navObserver.observe(this._nav, { attributes: true, attributeFilter: ['data-droplet', 'data-droplet-drag'] });
|
|
102
|
+
// Items added or removed.
|
|
103
|
+
this._itemObserver = new MutationObserver(() => this._itemChanged());
|
|
104
|
+
this._itemObserver.observe(this, { childList: true });
|
|
105
|
+
this._applyDroplet();
|
|
50
106
|
}
|
|
51
107
|
|
|
52
108
|
teardownEvents() {
|
|
53
109
|
this.removeEventListener('glk-tab-select', this._onTabSelect);
|
|
110
|
+
this._navObserver?.disconnect();
|
|
111
|
+
this._itemObserver?.disconnect();
|
|
112
|
+
this._droplet?.destroy();
|
|
113
|
+
this._droplet = null;
|
|
54
114
|
}
|
|
55
115
|
}
|
|
56
116
|
|
|
@@ -66,6 +66,27 @@ class GlkTabItem extends GlkElement {
|
|
|
66
66
|
this.emit('glk-tab-select');
|
|
67
67
|
};
|
|
68
68
|
this._btn.addEventListener('click', this._onClick);
|
|
69
|
+
this._tellBar();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The bar's look reaches the item through its wrapper. GlassKit's rules for
|
|
74
|
+
* floating and droplet items are descendant selectors of the bar's classes,
|
|
75
|
+
* and this button sits in a shadow root of its own, so <glk-tab-bar> hands
|
|
76
|
+
* its state down and the wrapper carries it. Before 1.27.0 the items of a
|
|
77
|
+
* floating bar kept the fixed bar's look: no spotlight, no pill, the dot.
|
|
78
|
+
*/
|
|
79
|
+
_syncBar({ floating, droplet, placed, drag }) {
|
|
80
|
+
if (!this._wrapper) return;
|
|
81
|
+
this._wrapper.classList.toggle('glass-tab-bar--floating', floating);
|
|
82
|
+
this._wrapper.classList.toggle('glass-tab-bar--droplet', droplet);
|
|
83
|
+
this._wrapper.toggleAttribute('data-droplet', placed);
|
|
84
|
+
this._wrapper.toggleAttribute('data-droplet-drag', drag);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Tells the bar that this item rendered or changed its active state. */
|
|
88
|
+
_tellBar() {
|
|
89
|
+
this.closest('glk-tab-bar')?._itemChanged?.();
|
|
69
90
|
}
|
|
70
91
|
|
|
71
92
|
teardownEvents() {
|
|
@@ -77,6 +98,7 @@ class GlkTabItem extends GlkElement {
|
|
|
77
98
|
switch (name) {
|
|
78
99
|
case 'active':
|
|
79
100
|
this._btn.classList.toggle('is-active', this.getBoolAttr('active'));
|
|
101
|
+
this._tellBar();
|
|
80
102
|
break;
|
|
81
103
|
case 'label':
|
|
82
104
|
this._labelEl.textContent = this.getAttribute('label') || '';
|