@jungherz-de/glasskit-elements 1.14.0 → 1.15.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 +12 -6
- package/dist/components/base.js +1 -21
- package/dist/components/glk-accordion-item.js +1 -0
- package/dist/components/glk-accordion.js +1 -0
- package/dist/components/glk-avatar.js +1 -0
- package/dist/components/glk-badge.js +1 -0
- package/dist/components/glk-button.js +1 -0
- package/dist/components/glk-card.js +1 -0
- package/dist/components/glk-checkbox.js +1 -0
- package/dist/components/glk-divider.js +1 -0
- package/dist/components/glk-empty.js +53 -0
- package/dist/components/glk-input.js +1 -0
- package/dist/components/glk-list-item.js +1 -0
- package/dist/components/glk-list.js +1 -0
- package/dist/components/glk-modal.js +1 -0
- package/dist/components/glk-nav.js +1 -0
- package/dist/components/glk-pill.js +1 -0
- package/dist/components/glk-popover.js +1 -0
- package/dist/components/glk-progress.js +1 -0
- package/dist/components/glk-radio.js +1 -0
- package/dist/components/glk-range.js +1 -0
- package/dist/components/glk-search.js +1 -0
- package/dist/components/glk-segmented.js +124 -0
- package/dist/components/glk-select.js +1 -0
- package/dist/components/glk-sheet.js +141 -0
- package/dist/components/glk-status.js +1 -0
- package/dist/components/glk-steps.js +75 -0
- package/dist/components/glk-tab-accessory.js +1 -0
- package/dist/components/glk-tab-bar.js +1 -0
- package/dist/components/glk-tab-dock.js +1 -0
- package/dist/components/glk-tab-item.js +1 -0
- package/dist/components/glk-textarea.js +1 -0
- package/dist/components/glk-title.js +1 -0
- package/dist/components/glk-toast.js +1 -0
- package/dist/components/glk-toggle.js +1 -0
- package/dist/glasskit-elements.esm.js +380 -3
- package/dist/glasskit-elements.js +383 -2
- package/dist/glasskit-elements.min.js +1 -1
- package/package.json +3 -3
- package/src/components/content/glk-empty.js +51 -0
- package/src/components/feedback/glk-sheet.js +139 -0
- package/src/components/forms/glk-segmented.js +122 -0
- package/src/components/navigation/glk-steps.js +73 -0
- package/src/index.js +4 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { GlkElement } from './base.js';
|
|
2
|
+
import '@jungherz-de/glasskit/glasskit-styles.js';
|
|
3
|
+
|
|
4
|
+
// Bottom sheet — the mobile sibling of <glk-modal>. Came back from
|
|
5
|
+
// EhrenPfoten in 1.15.0.
|
|
6
|
+
//
|
|
7
|
+
// Attributes: open, inline (no overlay, always visible), title
|
|
8
|
+
// Properties: open, inline · Methods: show(), close()
|
|
9
|
+
// Slots: default (content), actions
|
|
10
|
+
// Event: glk-close — only when the user closes it (scrim click, Escape),
|
|
11
|
+
// like <glk-modal>
|
|
12
|
+
// Parts: overlay, sheet, title, body, actions
|
|
13
|
+
//
|
|
14
|
+
// Opening and closing are animated by the CSS block: the overlay gets
|
|
15
|
+
// .is-active one frame after it is unhidden, and on closing it is hidden
|
|
16
|
+
// only after transitionend (safety net 400 ms), so the blurred overlay
|
|
17
|
+
// leaves the layout instead of idling at opacity 0. Under
|
|
18
|
+
// prefers-reduced-motion it switches at once.
|
|
19
|
+
|
|
20
|
+
const HIDE_FALLBACK_MS = 400;
|
|
21
|
+
|
|
22
|
+
function reducedMotion() {
|
|
23
|
+
return typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
class GlkSheet extends GlkElement {
|
|
27
|
+
static get observedAttributes() { return ['open', 'inline', 'title']; }
|
|
28
|
+
|
|
29
|
+
render() {
|
|
30
|
+
this._overlay = this.createElement('div', [], { part: 'overlay' });
|
|
31
|
+
this._panel = this.createElement('div', ['glass-sheet'], { role: 'dialog', part: 'sheet' });
|
|
32
|
+
this._panel.appendChild(this.createElement('div', ['glass-sheet__grip'], { 'aria-hidden': 'true' }));
|
|
33
|
+
this._titleEl = this.createElement('h2', ['glass-sheet__title'], { part: 'title' });
|
|
34
|
+
const body = this.createElement('div', ['glass-sheet__body'], { part: 'body' });
|
|
35
|
+
body.appendChild(document.createElement('slot'));
|
|
36
|
+
const actions = this.createElement('div', ['glass-sheet__actions'], { part: 'actions' });
|
|
37
|
+
actions.appendChild(this.createElement('slot', [], { name: 'actions' }));
|
|
38
|
+
this._panel.append(this._titleEl, body, actions);
|
|
39
|
+
this._overlay.appendChild(this._panel);
|
|
40
|
+
this._wrapper.appendChild(this._overlay);
|
|
41
|
+
this._applyTitle();
|
|
42
|
+
this._applyMode();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
_applyTitle() {
|
|
46
|
+
const title = this.getAttribute('title') || '';
|
|
47
|
+
this._titleEl.textContent = title;
|
|
48
|
+
this._titleEl.hidden = !title;
|
|
49
|
+
if (title) this._panel.setAttribute('aria-label', title);
|
|
50
|
+
else this._panel.removeAttribute('aria-label');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
_applyMode() {
|
|
54
|
+
const inline = this.inline;
|
|
55
|
+
this._overlay.classList.toggle('glass-sheet-overlay', !inline);
|
|
56
|
+
this._panel.classList.toggle('glass-sheet--inline', inline);
|
|
57
|
+
this._panel.setAttribute('aria-modal', String(!inline));
|
|
58
|
+
clearTimeout(this._hideTimer);
|
|
59
|
+
if (inline) {
|
|
60
|
+
this._overlay.classList.remove('is-active');
|
|
61
|
+
this._overlay.hidden = false;
|
|
62
|
+
} else if (this.open) {
|
|
63
|
+
this._show();
|
|
64
|
+
} else {
|
|
65
|
+
this._overlay.classList.remove('is-active');
|
|
66
|
+
this._overlay.hidden = true;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
_show() {
|
|
71
|
+
clearTimeout(this._hideTimer);
|
|
72
|
+
this._panel.removeEventListener('transitionend', this._onHidden);
|
|
73
|
+
this._overlay.hidden = false;
|
|
74
|
+
void this._overlay.offsetHeight; // reflow, so the transition starts from the hidden state
|
|
75
|
+
requestAnimationFrame(() => {
|
|
76
|
+
if (this.open && !this.inline) this._overlay.classList.add('is-active');
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
_hide() {
|
|
81
|
+
this._overlay.classList.remove('is-active');
|
|
82
|
+
this._onHidden = () => {
|
|
83
|
+
clearTimeout(this._hideTimer);
|
|
84
|
+
this._panel.removeEventListener('transitionend', this._onHidden);
|
|
85
|
+
if (!this.open && !this.inline) this._overlay.hidden = true;
|
|
86
|
+
};
|
|
87
|
+
if (reducedMotion()) {
|
|
88
|
+
this._onHidden();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
this._panel.addEventListener('transitionend', this._onHidden);
|
|
92
|
+
this._hideTimer = setTimeout(this._onHidden, HIDE_FALLBACK_MS);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
_dismiss() {
|
|
96
|
+
this.removeAttribute('open');
|
|
97
|
+
this.emit('glk-close');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
setupEvents() {
|
|
101
|
+
this._onOverlayClick = (event) => {
|
|
102
|
+
if (!this.inline && event.target === this._overlay) this._dismiss();
|
|
103
|
+
};
|
|
104
|
+
this._onKeydown = (event) => {
|
|
105
|
+
if (event.key === 'Escape' && this.open && !this.inline) this._dismiss();
|
|
106
|
+
};
|
|
107
|
+
this._overlay.addEventListener('click', this._onOverlayClick);
|
|
108
|
+
document.addEventListener('keydown', this._onKeydown);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
teardownEvents() {
|
|
112
|
+
this._overlay?.removeEventListener('click', this._onOverlayClick);
|
|
113
|
+
document.removeEventListener('keydown', this._onKeydown);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
onAttributeChanged(name) {
|
|
117
|
+
if (!this._overlay) return;
|
|
118
|
+
switch (name) {
|
|
119
|
+
case 'open':
|
|
120
|
+
if (this.inline) return;
|
|
121
|
+
if (this.open) this._show();
|
|
122
|
+
else this._hide();
|
|
123
|
+
break;
|
|
124
|
+
case 'inline': this._applyMode(); break;
|
|
125
|
+
case 'title': this._applyTitle(); break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
show() { this.setAttribute('open', ''); }
|
|
130
|
+
close() { this.removeAttribute('open'); }
|
|
131
|
+
|
|
132
|
+
get open() { return this.getBoolAttr('open'); }
|
|
133
|
+
set open(v) { this.setBoolAttr('open', v); }
|
|
134
|
+
|
|
135
|
+
get inline() { return this.getBoolAttr('inline'); }
|
|
136
|
+
set inline(v) { this.setBoolAttr('inline', v); }
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
customElements.define('glk-sheet', GlkSheet);
|
|
140
|
+
|
|
141
|
+
export { GlkSheet };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { GlkElement } from './base.js';
|
|
2
|
+
import '@jungherz-de/glasskit/glasskit-styles.js';
|
|
3
|
+
|
|
4
|
+
// Progress through a short flow — purely presentational. Came back from
|
|
5
|
+
// EhrenPfoten in 1.15.0.
|
|
6
|
+
//
|
|
7
|
+
// Attributes: steps (comma-separated labels), current (0-based), label
|
|
8
|
+
// (aria-label of the list, optional)
|
|
9
|
+
// Property: current, steps (array or comma-separated text)
|
|
10
|
+
// Part: steps
|
|
11
|
+
//
|
|
12
|
+
// The narrowing in tight frames is the CSS block's container query; the
|
|
13
|
+
// element knows nothing about it. Done steps show an SVG check, not a text
|
|
14
|
+
// glyph, so the mark does not depend on the font.
|
|
15
|
+
|
|
16
|
+
const CHECK = '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12l5 5 9-10"/></svg>';
|
|
17
|
+
|
|
18
|
+
class GlkSteps extends GlkElement {
|
|
19
|
+
static get observedAttributes() { return ['steps', 'current', 'label']; }
|
|
20
|
+
|
|
21
|
+
render() {
|
|
22
|
+
this._list = this.createElement('ol', ['glass-steps'], { part: 'steps' });
|
|
23
|
+
this._wrapper.appendChild(this._list);
|
|
24
|
+
this._build();
|
|
25
|
+
this._applyLabel();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
_build() {
|
|
29
|
+
const items = this.steps;
|
|
30
|
+
const current = this.current;
|
|
31
|
+
this._list.replaceChildren();
|
|
32
|
+
items.forEach((label, i) => {
|
|
33
|
+
const item = this.createElement('li', ['glass-steps__item']);
|
|
34
|
+
if (i < current) item.classList.add('glass-steps__item--done');
|
|
35
|
+
if (i === current) {
|
|
36
|
+
item.classList.add('glass-steps__item--current');
|
|
37
|
+
item.setAttribute('aria-current', 'step');
|
|
38
|
+
}
|
|
39
|
+
const num = this.createElement('span', ['glass-steps__num']);
|
|
40
|
+
if (i < current) num.innerHTML = CHECK;
|
|
41
|
+
else num.textContent = String(i + 1);
|
|
42
|
+
const text = this.createElement('span', ['glass-steps__label']);
|
|
43
|
+
text.textContent = label;
|
|
44
|
+
item.append(num, text);
|
|
45
|
+
this._list.appendChild(item);
|
|
46
|
+
if (i < items.length - 1) {
|
|
47
|
+
this._list.appendChild(this.createElement('li', ['glass-steps__line'], { 'aria-hidden': 'true' }));
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
_applyLabel() {
|
|
53
|
+
const label = this.getAttribute('label');
|
|
54
|
+
if (label) this._list.setAttribute('aria-label', label);
|
|
55
|
+
else this._list.removeAttribute('aria-label');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
onAttributeChanged(name) {
|
|
59
|
+
if (!this._list) return;
|
|
60
|
+
if (name === 'label') this._applyLabel();
|
|
61
|
+
else this._build();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
get current() { return Number(this.getAttribute('current')) || 0; }
|
|
65
|
+
set current(v) { this.setAttribute('current', String(Number(v) || 0)); }
|
|
66
|
+
|
|
67
|
+
get steps() {
|
|
68
|
+
return (this.getAttribute('steps') || '').split(',').map(s => s.trim()).filter(Boolean);
|
|
69
|
+
}
|
|
70
|
+
set steps(v) { this.setAttribute('steps', Array.isArray(v) ? v.join(',') : String(v ?? '')); }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
customElements.define('glk-steps', GlkSteps);
|
|
74
|
+
|
|
75
|
+
export { GlkSteps };
|