@jungherz-de/glasskit-elements 1.16.1 → 1.18.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 +6 -6
- package/dist/components/glk-badge.js +3 -1
- package/dist/components/glk-date-strip.js +8 -16
- package/dist/components/glk-input.js +16 -1
- package/dist/components/glk-segmented.js +43 -6
- package/dist/components/shared/reveal-CjjzoxTa.js +21 -0
- package/dist/glasskit-elements.esm.js +91 -27
- package/dist/glasskit-elements.js +91 -27
- package/dist/glasskit-elements.min.js +1 -1
- package/package.json +3 -3
- package/src/components/content/glk-badge.js +3 -1
- package/src/components/forms/glk-input.js +16 -1
- package/src/components/forms/glk-segmented.js +43 -6
- package/src/components/navigation/glk-date-strip.js +8 -16
- package/src/reveal.js +19 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jungherz-de/glasskit-elements",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.18.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",
|
|
@@ -45,10 +45,10 @@
|
|
|
45
45
|
"vanilla-js"
|
|
46
46
|
],
|
|
47
47
|
"peerDependencies": {
|
|
48
|
-
"@jungherz-de/glasskit": ">=1.
|
|
48
|
+
"@jungherz-de/glasskit": ">=1.18.0"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@jungherz-de/glasskit": "^1.
|
|
51
|
+
"@jungherz-de/glasskit": "^1.18.0",
|
|
52
52
|
"@rollup/plugin-node-resolve": "^15.0.0",
|
|
53
53
|
"@rollup/plugin-terser": "^0.4.0",
|
|
54
54
|
"rollup": "^4.0.0"
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { GlkElement } from '../../base.js';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
// warning since 1.18.0 (needs GlassKit 1.18.0). Any other value falls back to
|
|
4
|
+
// the neutral badge, as an unknown value of an enumerated attribute does.
|
|
5
|
+
const VARIANTS = ['primary', 'success', 'warning', 'error'];
|
|
4
6
|
|
|
5
7
|
class GlkBadge extends GlkElement {
|
|
6
8
|
static get displayInline() { return true; }
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { GlkFormElement } from '../../base.js';
|
|
2
2
|
|
|
3
|
+
// Attributes that belong to the inner <input> and are handed down unchanged:
|
|
4
|
+
// the picker's range and step, the typing limits, the pattern, autofill and
|
|
5
|
+
// the on-screen keyboard. Absent on the host means absent inside (since
|
|
6
|
+
// 1.17.0 — before, a date field with min still offered every past day).
|
|
7
|
+
const FORWARDED = ['min', 'max', 'step', 'minlength', 'maxlength', 'pattern', 'autocomplete', 'inputmode'];
|
|
8
|
+
|
|
3
9
|
class GlkInput extends GlkFormElement {
|
|
4
10
|
static get observedAttributes() {
|
|
5
|
-
return ['label', 'type', 'placeholder', 'error', 'hint', 'disabled', 'name', 'value', 'required'];
|
|
11
|
+
return ['label', 'type', 'placeholder', 'error', 'hint', 'disabled', 'name', 'value', 'required', ...FORWARDED];
|
|
6
12
|
}
|
|
7
13
|
|
|
8
14
|
render() {
|
|
@@ -28,6 +34,7 @@ class GlkInput extends GlkFormElement {
|
|
|
28
34
|
|
|
29
35
|
if (this.getBoolAttr('disabled')) this._input.disabled = true;
|
|
30
36
|
if (this.getBoolAttr('required')) this._input.required = true;
|
|
37
|
+
for (const attr of FORWARDED) this._forward(attr);
|
|
31
38
|
|
|
32
39
|
// Hint
|
|
33
40
|
this._hintEl = this.createElement('span', this._computeHintClasses());
|
|
@@ -97,9 +104,17 @@ class GlkInput extends GlkFormElement {
|
|
|
97
104
|
case 'required':
|
|
98
105
|
this._input.required = this.getBoolAttr('required');
|
|
99
106
|
break;
|
|
107
|
+
default:
|
|
108
|
+
if (FORWARDED.includes(name)) this._forward(name);
|
|
100
109
|
}
|
|
101
110
|
}
|
|
102
111
|
|
|
112
|
+
_forward(attr) {
|
|
113
|
+
const value = this.getAttribute(attr);
|
|
114
|
+
if (value === null) this._input.removeAttribute(attr);
|
|
115
|
+
else this._input.setAttribute(attr, value);
|
|
116
|
+
}
|
|
117
|
+
|
|
103
118
|
_computeInputClasses() {
|
|
104
119
|
const classes = ['glass-input'];
|
|
105
120
|
if (this.getBoolAttr('error')) classes.push('glass-input--error');
|
|
@@ -1,19 +1,31 @@
|
|
|
1
1
|
import { GlkFormElement } from '../../base.js';
|
|
2
|
+
import { revealCentered } from '../../reveal.js';
|
|
2
3
|
|
|
3
4
|
// One button per option, the chosen one marked aria-pressed="true" — GlassKit
|
|
4
5
|
// styles exactly that attribute. Came back from EhrenPfoten in 1.15.0.
|
|
5
6
|
//
|
|
6
7
|
// Attributes: options (JSON [{value,label,tone?,disabled?}]), value, full,
|
|
7
|
-
//
|
|
8
|
-
//
|
|
8
|
+
// overflow ("scroll" | "wrap": what happens when the options
|
|
9
|
+
// do not fit — without it the row stays one line and runs past
|
|
10
|
+
// the edge), label (aria-label of the group), name (form field)
|
|
11
|
+
// Properties: value, options (array or JSON text), full, overflow, label
|
|
9
12
|
// Event: glk-change { value } — only on a change made by the user
|
|
10
13
|
// Part: group
|
|
11
14
|
//
|
|
12
15
|
// Form-associated: a surrounding <form> receives name=value, reset restores
|
|
13
16
|
// the initial value. A value change only re-sets aria-pressed, never rebuilds,
|
|
14
17
|
// so focus stays on the button that was pressed.
|
|
18
|
+
//
|
|
19
|
+
// overflow="scroll" keeps the chosen option in view: it is centred in the
|
|
20
|
+
// row after the first layout, on every value change and when the row's width
|
|
21
|
+
// changes, through the row's own scrolling, so the page never moves (since
|
|
22
|
+
// 1.17.0). The attribute is not called "scroll": that is a method of every
|
|
23
|
+
// element, and a framework that sets a property whenever the element has one
|
|
24
|
+
// (hybrids, Vue, React 19) would overwrite the method instead of setting the
|
|
25
|
+
// attribute.
|
|
15
26
|
|
|
16
27
|
const TONES = ['success', 'warning', 'error'];
|
|
28
|
+
const OVERFLOWS = ['scroll', 'wrap'];
|
|
17
29
|
|
|
18
30
|
function parseList(json) {
|
|
19
31
|
try {
|
|
@@ -25,14 +37,14 @@ function parseList(json) {
|
|
|
25
37
|
}
|
|
26
38
|
|
|
27
39
|
class GlkSegmented extends GlkFormElement {
|
|
28
|
-
static get observedAttributes() { return ['options', 'value', 'full', 'label']; }
|
|
40
|
+
static get observedAttributes() { return ['options', 'value', 'full', 'overflow', 'label']; }
|
|
29
41
|
|
|
30
42
|
render() {
|
|
31
43
|
this._group = this.createElement('div', ['glass-segmented'], { role: 'group', part: 'group' });
|
|
32
44
|
this._wrapper.appendChild(this._group);
|
|
33
45
|
this._initialValue = this.value;
|
|
34
46
|
this._build();
|
|
35
|
-
this.
|
|
47
|
+
this._applyLayout();
|
|
36
48
|
this._applyLabel();
|
|
37
49
|
}
|
|
38
50
|
|
|
@@ -58,10 +70,20 @@ class GlkSegmented extends GlkFormElement {
|
|
|
58
70
|
button.setAttribute('aria-pressed', String(button.dataset.value === value));
|
|
59
71
|
}
|
|
60
72
|
this.setFormValue(value);
|
|
73
|
+
this._reveal(this._revealedAt ? 'smooth' : 'instant');
|
|
61
74
|
}
|
|
62
75
|
|
|
63
|
-
|
|
76
|
+
_applyLayout() {
|
|
77
|
+
const overflow = this.overflow;
|
|
64
78
|
this._group.classList.toggle('glass-segmented--full', this.getBoolAttr('full'));
|
|
79
|
+
for (const mode of OVERFLOWS) this._group.classList.toggle(`glass-segmented--${mode}`, overflow === mode);
|
|
80
|
+
this._reveal('instant');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
_reveal(behavior) {
|
|
84
|
+
if (this.overflow !== 'scroll') return;
|
|
85
|
+
const pressed = this._group.querySelector('[aria-pressed="true"]');
|
|
86
|
+
if (revealCentered(this._group, pressed, behavior === 'smooth')) this._revealedAt = this._group.clientWidth;
|
|
65
87
|
}
|
|
66
88
|
|
|
67
89
|
_applyLabel() {
|
|
@@ -79,11 +101,16 @@ class GlkSegmented extends GlkFormElement {
|
|
|
79
101
|
this.value = value;
|
|
80
102
|
this.emit('glk-change', { value });
|
|
81
103
|
};
|
|
104
|
+
this._onResize = () => { if (this._group.clientWidth !== this._revealedAt) this._reveal('instant'); };
|
|
82
105
|
this._group.addEventListener('click', this._onClick);
|
|
106
|
+
this._resizeObserver = new ResizeObserver(this._onResize);
|
|
107
|
+
this._resizeObserver.observe(this._group);
|
|
83
108
|
}
|
|
84
109
|
|
|
85
110
|
teardownEvents() {
|
|
86
111
|
this._group?.removeEventListener('click', this._onClick);
|
|
112
|
+
this._resizeObserver?.disconnect();
|
|
113
|
+
this._resizeObserver = null;
|
|
87
114
|
}
|
|
88
115
|
|
|
89
116
|
onAttributeChanged(name) {
|
|
@@ -91,7 +118,8 @@ class GlkSegmented extends GlkFormElement {
|
|
|
91
118
|
switch (name) {
|
|
92
119
|
case 'value': this._syncValue(); break;
|
|
93
120
|
case 'options': this._build(); break;
|
|
94
|
-
case 'full':
|
|
121
|
+
case 'full':
|
|
122
|
+
case 'overflow': this._applyLayout(); break;
|
|
95
123
|
case 'label': this._applyLabel(); break;
|
|
96
124
|
}
|
|
97
125
|
}
|
|
@@ -111,6 +139,15 @@ class GlkSegmented extends GlkFormElement {
|
|
|
111
139
|
get full() { return this.getBoolAttr('full'); }
|
|
112
140
|
set full(v) { this.setBoolAttr('full', v); }
|
|
113
141
|
|
|
142
|
+
get overflow() {
|
|
143
|
+
const v = this.getAttribute('overflow');
|
|
144
|
+
return OVERFLOWS.includes(v) ? v : '';
|
|
145
|
+
}
|
|
146
|
+
set overflow(v) {
|
|
147
|
+
if (v) this.setAttribute('overflow', String(v));
|
|
148
|
+
else this.removeAttribute('overflow');
|
|
149
|
+
}
|
|
150
|
+
|
|
114
151
|
get label() { return this.getAttribute('label') || ''; }
|
|
115
152
|
set label(v) {
|
|
116
153
|
if (v) this.setAttribute('label', v);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { GlkElement } from '../../base.js';
|
|
2
2
|
import { isoDate, parseIso, addDays, parseJsonObject, resolveLocale, formatters, weekdayShort, isTone } from '../../dates.js';
|
|
3
|
+
import { revealCentered } from '../../reveal.js';
|
|
3
4
|
|
|
4
5
|
// A row of day chips that scrolls sideways — a booking horizon, the days
|
|
5
6
|
// around today. Came back from EhrenPfoten in 1.16.0.
|
|
@@ -84,24 +85,15 @@ class GlkDateStrip extends GlkElement {
|
|
|
84
85
|
for (const chip of this._chips()) chip.tabIndex = chip === target ? 0 : -1;
|
|
85
86
|
}
|
|
86
87
|
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
// value change glides unless the user asked for reduced motion; a size
|
|
94
|
-
// change snaps, so a settling layout never animates.
|
|
88
|
+
// Centre the chosen chip in the strip (see reveal.js). Before the first
|
|
89
|
+
// layout the strip has no width; the ResizeObserver in setupEvents centres
|
|
90
|
+
// then, and again whenever the strip's width changes — a parent that
|
|
91
|
+
// upgrades later and adds its padding, a window resize, a strip that
|
|
92
|
+
// starts out hidden. A value change glides; a size change snaps, so a
|
|
93
|
+
// settling layout never animates.
|
|
95
94
|
_reveal(behavior = 'instant') {
|
|
96
95
|
const chip = this._strip.querySelector('[aria-pressed="true"]');
|
|
97
|
-
|
|
98
|
-
if (!chip || !width) return;
|
|
99
|
-
const strip = this._strip.getBoundingClientRect();
|
|
100
|
-
const box = chip.getBoundingClientRect();
|
|
101
|
-
const left = this._strip.scrollLeft + (box.left - strip.left) - (strip.width - box.width) / 2;
|
|
102
|
-
const smooth = behavior === 'smooth' && !matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
103
|
-
this._strip.scrollTo({ left: Math.max(0, left), behavior: smooth ? 'smooth' : 'instant' });
|
|
104
|
-
this._revealedAt = width;
|
|
96
|
+
if (revealCentered(this._strip, chip, behavior === 'smooth')) this._revealedAt = this._strip.clientWidth;
|
|
105
97
|
}
|
|
106
98
|
|
|
107
99
|
_applyLabel() {
|
package/src/reveal.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Scroll an item to the middle of its scrolling row — shared by
|
|
2
|
+
// <glk-date-strip> and <glk-segmented overflow="scroll">. Computed from
|
|
3
|
+
// client rects and applied to the row alone, where scrollIntoView would move
|
|
4
|
+
// every scrolling ancestor, the page included. A relative scrollBy works in
|
|
5
|
+
// both writing directions: scrollLeft runs negative in RTL, the distance on
|
|
6
|
+
// screen does not. Returns false while the row has no width — before the
|
|
7
|
+
// first layout, or hidden — so the caller can try again from a
|
|
8
|
+
// ResizeObserver. Pass smooth for a reveal that follows a value change, not
|
|
9
|
+
// for one caused by layout; reduced motion wins over it.
|
|
10
|
+
|
|
11
|
+
export function revealCentered(scroller, item, smooth) {
|
|
12
|
+
if (!item || !scroller.clientWidth) return false;
|
|
13
|
+
const port = scroller.getBoundingClientRect();
|
|
14
|
+
const box = item.getBoundingClientRect();
|
|
15
|
+
const delta = box.left + box.width / 2 - (port.left + scroller.clientLeft + scroller.clientWidth / 2);
|
|
16
|
+
const glide = smooth && !matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
17
|
+
scroller.scrollBy({ left: delta, behavior: glide ? 'smooth' : 'instant' });
|
|
18
|
+
return true;
|
|
19
|
+
}
|