@jungherz-de/glasskit-elements 1.16.0 → 1.16.1

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 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-33-7ec8e3?style=flat-square" alt="33 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.16.0-lightgrey?style=flat-square" alt="Changelog"></a>
8
+ <a href="CHANGELOG.md"><img src="https://img.shields.io/badge/changelog-v1.16.1-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
 
@@ -181,6 +181,24 @@ class GlkElement extends HTMLElement {
181
181
 
182
182
  // ── Utility Methods ──
183
183
 
184
+ /**
185
+ * For elements whose heading arrives as `title`. That is also the global
186
+ * HTML attribute, and a title on the host shows as a browser tooltip — the
187
+ * sheet's heading floating over its form. So the value is read into
188
+ * this._title and the attribute is taken off the host; a later
189
+ * setAttribute('title') comes through attributeChangedCallback, is taken
190
+ * the same way and removed again, and the element's `title` accessor
191
+ * answers from this._title. The removal fires the callback with null,
192
+ * which is ignored here — only an empty string clears the heading.
193
+ * Returns whether a value was taken.
194
+ */
195
+ takeTitle(value = this.getAttribute('title')) {
196
+ if (value === null) return false;
197
+ this._title = value;
198
+ this.removeAttribute('title');
199
+ return true;
200
+ }
201
+
184
202
  getBoolAttr(name) {
185
203
  return this.hasAttribute(name);
186
204
  }
@@ -14,7 +14,8 @@ class GlkAccordionItem extends GlkElement {
14
14
 
15
15
  // Trigger button
16
16
  this._trigger = this.createElement('button', ['glass-accordion__trigger']);
17
- this._triggerText = document.createTextNode(this.getAttribute('title') || '');
17
+ this.takeTitle();
18
+ this._triggerText = document.createTextNode(this._title || '');
18
19
  this._trigger.appendChild(this._triggerText);
19
20
  this._trigger.insertAdjacentHTML('beforeend', CHEVRON_SVG);
20
21
 
@@ -42,18 +43,29 @@ class GlkAccordionItem extends GlkElement {
42
43
  this._trigger?.removeEventListener('click', this._onClick);
43
44
  }
44
45
 
45
- onAttributeChanged(name) {
46
+ onAttributeChanged(name, _old, value) {
46
47
  if (!this._item) return;
47
48
  switch (name) {
48
49
  case 'open':
49
50
  this._item.classList.toggle('is-open', this.getBoolAttr('open'));
50
51
  break;
51
52
  case 'title':
52
- this._triggerText.textContent = this.getAttribute('title') || '';
53
+ if (this.takeTitle(value)) this._applyTitle();
53
54
  break;
54
55
  }
55
56
  }
56
57
 
58
+ _applyTitle() {
59
+ if (this._triggerText) this._triggerText.textContent = this._title || '';
60
+ }
61
+
62
+ // `title` is the heading, never a tooltip — see GlkElement.takeTitle().
63
+ get title() { return this._title ?? ''; }
64
+ set title(v) {
65
+ this._title = v == null ? '' : String(v);
66
+ this._applyTitle();
67
+ }
68
+
57
69
  get open() { return this.getBoolAttr('open'); }
58
70
  set open(v) { this.setBoolAttr('open', v); }
59
71
  }
@@ -25,11 +25,12 @@ class GlkEmpty extends GlkElement {
25
25
  action.appendChild(this.createElement('slot', [], { name: 'action' }));
26
26
  root.append(icon, this._titleEl, this._textEl, action);
27
27
  this._wrapper.appendChild(root);
28
+ this.takeTitle();
28
29
  this._update();
29
30
  }
30
31
 
31
32
  _update() {
32
- const title = this.getAttribute('title') || '';
33
+ const title = this._title || '';
33
34
  const text = this.getAttribute('text') || '';
34
35
  this._titleEl.textContent = title;
35
36
  this._titleEl.hidden = !title;
@@ -37,10 +38,22 @@ class GlkEmpty extends GlkElement {
37
38
  this._textEl.hidden = !text;
38
39
  }
39
40
 
40
- onAttributeChanged() {
41
+ onAttributeChanged(name, _old, value) {
42
+ if (name === 'title' && !this.takeTitle(value)) return;
41
43
  if (this._titleEl) this._update();
42
44
  }
43
45
 
46
+ _applyTitle() {
47
+ if (this._titleEl) this._update();
48
+ }
49
+
50
+ // `title` is the heading, never a tooltip — see GlkElement.takeTitle().
51
+ get title() { return this._title ?? ''; }
52
+ set title(v) {
53
+ this._title = v == null ? '' : String(v);
54
+ this._applyTitle();
55
+ }
56
+
44
57
  get text() { return this.getAttribute('text') || ''; }
45
58
  set text(v) {
46
59
  if (v) this.setAttribute('text', v);
@@ -49,7 +49,8 @@ class GlkListItem extends GlkElement {
49
49
  // Content — title + optional subtitle.
50
50
  this._content = this.createElement('div', ['glass-list__content']);
51
51
  this._titleEl = this.createElement('div', ['glass-list__title']);
52
- this._titleEl.textContent = this.getAttribute('title') || '';
52
+ this.takeTitle();
53
+ this._titleEl.textContent = this._title || '';
53
54
  this._subtitleEl = this.createElement('div', ['glass-list__subtitle']);
54
55
  if (this.getBoolAttr('wrap')) this._subtitleEl.classList.add('glass-list__subtitle--wrap');
55
56
  const initialSubtitle = this.getAttribute('subtitle') || '';
@@ -115,11 +116,11 @@ class GlkListItem extends GlkElement {
115
116
  this._item?.removeEventListener('click', this._onClick);
116
117
  }
117
118
 
118
- onAttributeChanged(name) {
119
+ onAttributeChanged(name, _old, value) {
119
120
  if (!this._item) return;
120
121
  switch (name) {
121
122
  case 'title':
122
- this._titleEl.textContent = this.getAttribute('title') || '';
123
+ if (this.takeTitle(value)) this._applyTitle();
123
124
  break;
124
125
  case 'subtitle': {
125
126
  const value = this.getAttribute('subtitle') || '';
@@ -159,6 +160,17 @@ class GlkListItem extends GlkElement {
159
160
  }
160
161
  }
161
162
 
163
+ _applyTitle() {
164
+ if (this._titleEl) this._titleEl.textContent = this._title || '';
165
+ }
166
+
167
+ // `title` is the heading, never a tooltip — see GlkElement.takeTitle().
168
+ get title() { return this._title ?? ''; }
169
+ set title(v) {
170
+ this._title = v == null ? '' : String(v);
171
+ this._applyTitle();
172
+ }
173
+
162
174
  get interactive() { return this.getBoolAttr('interactive'); }
163
175
  set interactive(v) { this.setBoolAttr('interactive', v); }
164
176
 
@@ -16,7 +16,8 @@ class GlkModal extends GlkElement {
16
16
  // Header
17
17
  const header = this.createElement('div', ['glass-modal__header'], { part: 'header' });
18
18
  this._titleEl = this.createElement('h2', ['glass-modal__title']);
19
- this._titleEl.textContent = this.getAttribute('title') || '';
19
+ this.takeTitle();
20
+ this._applyTitle();
20
21
  header.appendChild(this._titleEl);
21
22
 
22
23
  // Body
@@ -85,14 +86,14 @@ class GlkModal extends GlkElement {
85
86
  document.removeEventListener('keydown', this._onKeydown);
86
87
  }
87
88
 
88
- onAttributeChanged(name) {
89
+ onAttributeChanged(name, _old, value) {
89
90
  if (!this._overlay) return;
90
91
  switch (name) {
91
92
  case 'open':
92
93
  this._overlay.classList.toggle('is-active', this.getBoolAttr('open'));
93
94
  break;
94
95
  case 'title':
95
- this._titleEl.textContent = this.getAttribute('title') || '';
96
+ if (this.takeTitle(value)) this._applyTitle();
96
97
  break;
97
98
  }
98
99
  }
@@ -100,6 +101,17 @@ class GlkModal extends GlkElement {
100
101
  show() { this.setAttribute('open', ''); }
101
102
  close() { this.removeAttribute('open'); }
102
103
 
104
+ _applyTitle() {
105
+ if (this._titleEl) this._titleEl.textContent = this._title || '';
106
+ }
107
+
108
+ // `title` is the heading, never a tooltip — see GlkElement.takeTitle().
109
+ get title() { return this._title ?? ''; }
110
+ set title(v) {
111
+ this._title = v == null ? '' : String(v);
112
+ this._applyTitle();
113
+ }
114
+
103
115
  get open() { return this.getBoolAttr('open'); }
104
116
  set open(v) { this.setBoolAttr('open', v); }
105
117
  }
@@ -38,12 +38,14 @@ class GlkSheet extends GlkElement {
38
38
  this._panel.append(this._titleEl, body, actions);
39
39
  this._overlay.appendChild(this._panel);
40
40
  this._wrapper.appendChild(this._overlay);
41
+ this.takeTitle();
41
42
  this._applyTitle();
42
43
  this._applyMode();
43
44
  }
44
45
 
45
46
  _applyTitle() {
46
- const title = this.getAttribute('title') || '';
47
+ if (!this._titleEl) return;
48
+ const title = this._title || '';
47
49
  this._titleEl.textContent = title;
48
50
  this._titleEl.hidden = !title;
49
51
  if (title) this._panel.setAttribute('aria-label', title);
@@ -113,7 +115,7 @@ class GlkSheet extends GlkElement {
113
115
  document.removeEventListener('keydown', this._onKeydown);
114
116
  }
115
117
 
116
- onAttributeChanged(name) {
118
+ onAttributeChanged(name, _old, value) {
117
119
  if (!this._overlay) return;
118
120
  switch (name) {
119
121
  case 'open':
@@ -122,13 +124,20 @@ class GlkSheet extends GlkElement {
122
124
  else this._hide();
123
125
  break;
124
126
  case 'inline': this._applyMode(); break;
125
- case 'title': this._applyTitle(); break;
127
+ case 'title': if (this.takeTitle(value)) this._applyTitle(); break;
126
128
  }
127
129
  }
128
130
 
129
131
  show() { this.setAttribute('open', ''); }
130
132
  close() { this.removeAttribute('open'); }
131
133
 
134
+ // `title` is the heading, never a tooltip — see GlkElement.takeTitle().
135
+ get title() { return this._title ?? ''; }
136
+ set title(v) {
137
+ this._title = v == null ? '' : String(v);
138
+ this._applyTitle();
139
+ }
140
+
132
141
  get open() { return this.getBoolAttr('open'); }
133
142
  set open(v) { this.setBoolAttr('open', v); }
134
143
 
@@ -201,6 +201,24 @@ class GlkElement extends HTMLElement {
201
201
 
202
202
  // ── Utility Methods ──
203
203
 
204
+ /**
205
+ * For elements whose heading arrives as `title`. That is also the global
206
+ * HTML attribute, and a title on the host shows as a browser tooltip — the
207
+ * sheet's heading floating over its form. So the value is read into
208
+ * this._title and the attribute is taken off the host; a later
209
+ * setAttribute('title') comes through attributeChangedCallback, is taken
210
+ * the same way and removed again, and the element's `title` accessor
211
+ * answers from this._title. The removal fires the callback with null,
212
+ * which is ignored here — only an empty string clears the heading.
213
+ * Returns whether a value was taken.
214
+ */
215
+ takeTitle(value = this.getAttribute('title')) {
216
+ if (value === null) return false;
217
+ this._title = value;
218
+ this.removeAttribute('title');
219
+ return true;
220
+ }
221
+
204
222
  getBoolAttr(name) {
205
223
  return this.hasAttribute(name);
206
224
  }
@@ -1201,11 +1219,12 @@ class GlkEmpty extends GlkElement {
1201
1219
  action.appendChild(this.createElement('slot', [], { name: 'action' }));
1202
1220
  root.append(icon, this._titleEl, this._textEl, action);
1203
1221
  this._wrapper.appendChild(root);
1222
+ this.takeTitle();
1204
1223
  this._update();
1205
1224
  }
1206
1225
 
1207
1226
  _update() {
1208
- const title = this.getAttribute('title') || '';
1227
+ const title = this._title || '';
1209
1228
  const text = this.getAttribute('text') || '';
1210
1229
  this._titleEl.textContent = title;
1211
1230
  this._titleEl.hidden = !title;
@@ -1213,10 +1232,22 @@ class GlkEmpty extends GlkElement {
1213
1232
  this._textEl.hidden = !text;
1214
1233
  }
1215
1234
 
1216
- onAttributeChanged() {
1235
+ onAttributeChanged(name, _old, value) {
1236
+ if (name === 'title' && !this.takeTitle(value)) return;
1237
+ if (this._titleEl) this._update();
1238
+ }
1239
+
1240
+ _applyTitle() {
1217
1241
  if (this._titleEl) this._update();
1218
1242
  }
1219
1243
 
1244
+ // `title` is the heading, never a tooltip — see GlkElement.takeTitle().
1245
+ get title() { return this._title ?? ''; }
1246
+ set title(v) {
1247
+ this._title = v == null ? '' : String(v);
1248
+ this._applyTitle();
1249
+ }
1250
+
1220
1251
  get text() { return this.getAttribute('text') || ''; }
1221
1252
  set text(v) {
1222
1253
  if (v) this.setAttribute('text', v);
@@ -2898,7 +2929,8 @@ class GlkModal extends GlkElement {
2898
2929
  // Header
2899
2930
  const header = this.createElement('div', ['glass-modal__header'], { part: 'header' });
2900
2931
  this._titleEl = this.createElement('h2', ['glass-modal__title']);
2901
- this._titleEl.textContent = this.getAttribute('title') || '';
2932
+ this.takeTitle();
2933
+ this._applyTitle();
2902
2934
  header.appendChild(this._titleEl);
2903
2935
 
2904
2936
  // Body
@@ -2967,14 +2999,14 @@ class GlkModal extends GlkElement {
2967
2999
  document.removeEventListener('keydown', this._onKeydown);
2968
3000
  }
2969
3001
 
2970
- onAttributeChanged(name) {
3002
+ onAttributeChanged(name, _old, value) {
2971
3003
  if (!this._overlay) return;
2972
3004
  switch (name) {
2973
3005
  case 'open':
2974
3006
  this._overlay.classList.toggle('is-active', this.getBoolAttr('open'));
2975
3007
  break;
2976
3008
  case 'title':
2977
- this._titleEl.textContent = this.getAttribute('title') || '';
3009
+ if (this.takeTitle(value)) this._applyTitle();
2978
3010
  break;
2979
3011
  }
2980
3012
  }
@@ -2982,6 +3014,17 @@ class GlkModal extends GlkElement {
2982
3014
  show() { this.setAttribute('open', ''); }
2983
3015
  close() { this.removeAttribute('open'); }
2984
3016
 
3017
+ _applyTitle() {
3018
+ if (this._titleEl) this._titleEl.textContent = this._title || '';
3019
+ }
3020
+
3021
+ // `title` is the heading, never a tooltip — see GlkElement.takeTitle().
3022
+ get title() { return this._title ?? ''; }
3023
+ set title(v) {
3024
+ this._title = v == null ? '' : String(v);
3025
+ this._applyTitle();
3026
+ }
3027
+
2985
3028
  get open() { return this.getBoolAttr('open'); }
2986
3029
  set open(v) { this.setBoolAttr('open', v); }
2987
3030
  }
@@ -3233,12 +3276,14 @@ class GlkSheet extends GlkElement {
3233
3276
  this._panel.append(this._titleEl, body, actions);
3234
3277
  this._overlay.appendChild(this._panel);
3235
3278
  this._wrapper.appendChild(this._overlay);
3279
+ this.takeTitle();
3236
3280
  this._applyTitle();
3237
3281
  this._applyMode();
3238
3282
  }
3239
3283
 
3240
3284
  _applyTitle() {
3241
- const title = this.getAttribute('title') || '';
3285
+ if (!this._titleEl) return;
3286
+ const title = this._title || '';
3242
3287
  this._titleEl.textContent = title;
3243
3288
  this._titleEl.hidden = !title;
3244
3289
  if (title) this._panel.setAttribute('aria-label', title);
@@ -3308,7 +3353,7 @@ class GlkSheet extends GlkElement {
3308
3353
  document.removeEventListener('keydown', this._onKeydown);
3309
3354
  }
3310
3355
 
3311
- onAttributeChanged(name) {
3356
+ onAttributeChanged(name, _old, value) {
3312
3357
  if (!this._overlay) return;
3313
3358
  switch (name) {
3314
3359
  case 'open':
@@ -3317,13 +3362,20 @@ class GlkSheet extends GlkElement {
3317
3362
  else this._hide();
3318
3363
  break;
3319
3364
  case 'inline': this._applyMode(); break;
3320
- case 'title': this._applyTitle(); break;
3365
+ case 'title': if (this.takeTitle(value)) this._applyTitle(); break;
3321
3366
  }
3322
3367
  }
3323
3368
 
3324
3369
  show() { this.setAttribute('open', ''); }
3325
3370
  close() { this.removeAttribute('open'); }
3326
3371
 
3372
+ // `title` is the heading, never a tooltip — see GlkElement.takeTitle().
3373
+ get title() { return this._title ?? ''; }
3374
+ set title(v) {
3375
+ this._title = v == null ? '' : String(v);
3376
+ this._applyTitle();
3377
+ }
3378
+
3327
3379
  get open() { return this.getBoolAttr('open'); }
3328
3380
  set open(v) { this.setBoolAttr('open', v); }
3329
3381
 
@@ -3356,7 +3408,8 @@ class GlkAccordionItem extends GlkElement {
3356
3408
 
3357
3409
  // Trigger button
3358
3410
  this._trigger = this.createElement('button', ['glass-accordion__trigger']);
3359
- this._triggerText = document.createTextNode(this.getAttribute('title') || '');
3411
+ this.takeTitle();
3412
+ this._triggerText = document.createTextNode(this._title || '');
3360
3413
  this._trigger.appendChild(this._triggerText);
3361
3414
  this._trigger.insertAdjacentHTML('beforeend', CHEVRON_SVG);
3362
3415
 
@@ -3384,18 +3437,29 @@ class GlkAccordionItem extends GlkElement {
3384
3437
  this._trigger?.removeEventListener('click', this._onClick);
3385
3438
  }
3386
3439
 
3387
- onAttributeChanged(name) {
3440
+ onAttributeChanged(name, _old, value) {
3388
3441
  if (!this._item) return;
3389
3442
  switch (name) {
3390
3443
  case 'open':
3391
3444
  this._item.classList.toggle('is-open', this.getBoolAttr('open'));
3392
3445
  break;
3393
3446
  case 'title':
3394
- this._triggerText.textContent = this.getAttribute('title') || '';
3447
+ if (this.takeTitle(value)) this._applyTitle();
3395
3448
  break;
3396
3449
  }
3397
3450
  }
3398
3451
 
3452
+ _applyTitle() {
3453
+ if (this._triggerText) this._triggerText.textContent = this._title || '';
3454
+ }
3455
+
3456
+ // `title` is the heading, never a tooltip — see GlkElement.takeTitle().
3457
+ get title() { return this._title ?? ''; }
3458
+ set title(v) {
3459
+ this._title = v == null ? '' : String(v);
3460
+ this._applyTitle();
3461
+ }
3462
+
3399
3463
  get open() { return this.getBoolAttr('open'); }
3400
3464
  set open(v) { this.setBoolAttr('open', v); }
3401
3465
  }
@@ -3522,7 +3586,8 @@ class GlkListItem extends GlkElement {
3522
3586
  // Content — title + optional subtitle.
3523
3587
  this._content = this.createElement('div', ['glass-list__content']);
3524
3588
  this._titleEl = this.createElement('div', ['glass-list__title']);
3525
- this._titleEl.textContent = this.getAttribute('title') || '';
3589
+ this.takeTitle();
3590
+ this._titleEl.textContent = this._title || '';
3526
3591
  this._subtitleEl = this.createElement('div', ['glass-list__subtitle']);
3527
3592
  if (this.getBoolAttr('wrap')) this._subtitleEl.classList.add('glass-list__subtitle--wrap');
3528
3593
  const initialSubtitle = this.getAttribute('subtitle') || '';
@@ -3588,11 +3653,11 @@ class GlkListItem extends GlkElement {
3588
3653
  this._item?.removeEventListener('click', this._onClick);
3589
3654
  }
3590
3655
 
3591
- onAttributeChanged(name) {
3656
+ onAttributeChanged(name, _old, value) {
3592
3657
  if (!this._item) return;
3593
3658
  switch (name) {
3594
3659
  case 'title':
3595
- this._titleEl.textContent = this.getAttribute('title') || '';
3660
+ if (this.takeTitle(value)) this._applyTitle();
3596
3661
  break;
3597
3662
  case 'subtitle': {
3598
3663
  const value = this.getAttribute('subtitle') || '';
@@ -3632,6 +3697,17 @@ class GlkListItem extends GlkElement {
3632
3697
  }
3633
3698
  }
3634
3699
 
3700
+ _applyTitle() {
3701
+ if (this._titleEl) this._titleEl.textContent = this._title || '';
3702
+ }
3703
+
3704
+ // `title` is the heading, never a tooltip — see GlkElement.takeTitle().
3705
+ get title() { return this._title ?? ''; }
3706
+ set title(v) {
3707
+ this._title = v == null ? '' : String(v);
3708
+ this._applyTitle();
3709
+ }
3710
+
3635
3711
  get interactive() { return this.getBoolAttr('interactive'); }
3636
3712
  set interactive(v) { this.setBoolAttr('interactive', v); }
3637
3713