@jungherz-de/glasskit-elements 1.21.1 → 1.22.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.
@@ -28,7 +28,9 @@ class GlkSearch extends GlkFormElement {
28
28
  const name = this.getAttribute('name');
29
29
  if (name) this._input.setAttribute('name', name);
30
30
 
31
- const value = this.getAttribute('value');
31
+ // A value set as a property before render() has waited for it.
32
+ const value = this._pendingValue ?? this.getAttribute('value');
33
+ this._pendingValue = undefined;
32
34
  if (value) this._input.value = value;
33
35
 
34
36
  if (this.getBoolAttr('disabled')) this._input.disabled = true;
@@ -90,9 +92,13 @@ class GlkSearch extends GlkFormElement {
90
92
  this._syncFormValue();
91
93
  }
92
94
 
93
- get value() { return this._input?.value ?? ''; }
95
+ get value() { return this._input ? this._input.value : (this._pendingValue ?? this.getAttribute('value') ?? ''); }
94
96
  set value(v) {
95
- if (this._input) this._input.value = v;
97
+ const value = v == null ? '' : String(v);
98
+ // Frameworks set properties right after creating the element. Before
99
+ // render() the value waits for it — it used to throw.
100
+ if (!this._input) { this._pendingValue = value; return; }
101
+ this._input.value = value;
96
102
  this._syncFormValue();
97
103
  }
98
104
  }
@@ -1,6 +1,16 @@
1
1
  import { GlkFormElement } from './base.js';
2
2
  import '@jungherz-de/glasskit/glasskit-styles.js';
3
3
 
4
+ // The wanted value. A select can show a value only once an option carries it,
5
+ // and the options arrive late: they are copied in a frame after connecting,
6
+ // and a framework may add them later still — while it sets `value` as a
7
+ // property right after creating the element. So the value the attribute or
8
+ // the property asks for is kept as wanted until an option carries it; a
9
+ // choice of the user replaces it. Before 1.22.0 only the attribute was kept:
10
+ // a property set before connecting threw, and one set before the options
11
+ // were copied was lost — the select showed its first option while the app
12
+ // held the other value.
13
+
4
14
  class GlkSelect extends GlkFormElement {
5
15
  static get observedAttributes() {
6
16
  return ['label', 'disabled', 'name', 'value', 'required'];
@@ -16,6 +26,8 @@ class GlkSelect extends GlkFormElement {
16
26
  this._labelEl.textContent = this.getAttribute('label') || '';
17
27
 
18
28
  this._select = this.createElement('select', ['glass-select'], { id: 'field' });
29
+ // A value set as a property before render() is wanted already.
30
+ if (this._wanted === undefined) this._wanted = this.getAttribute('value');
19
31
 
20
32
  const name = this.getAttribute('name');
21
33
  if (name) this._select.setAttribute('name', name);
@@ -47,10 +59,10 @@ class GlkSelect extends GlkFormElement {
47
59
 
48
60
  this._moveOptions();
49
61
 
50
- // Keep the live selection when it survived the rebuild; otherwise fall back
51
- // to the value attribute. Without this the selection jumps back to the first
52
- // entry every time the list is updated.
53
- if (!this._applyValue(previous)) this._applyValue(this.getAttribute('value'));
62
+ // The wanted value first, then the live selection if it survived the
63
+ // rebuild — without that, the selection would jump back to the first entry
64
+ // every time the list is updated.
65
+ if (!this._applyValue(this._wanted)) this._applyValue(previous);
54
66
  this._syncFormValue();
55
67
  }
56
68
 
@@ -76,6 +88,7 @@ class GlkSelect extends GlkFormElement {
76
88
 
77
89
  setupEvents() {
78
90
  this._onChange = () => {
91
+ this._wanted = null; // the user's choice replaces it
79
92
  this._syncFormValue();
80
93
  this.emit('glk-change', { value: this._select.value });
81
94
  this.dispatchEvent(new Event('change', { bubbles: true }));
@@ -100,7 +113,8 @@ class GlkSelect extends GlkFormElement {
100
113
  this._select.setAttribute('name', this.getAttribute('name') || '');
101
114
  break;
102
115
  case 'value':
103
- this._applyValue(this.getAttribute('value'));
116
+ this._wanted = this.getAttribute('value');
117
+ this._applyValue(this._wanted);
104
118
  this._syncFormValue();
105
119
  break;
106
120
  case 'required':
@@ -116,13 +130,22 @@ class GlkSelect extends GlkFormElement {
116
130
  }
117
131
 
118
132
  resetValue() {
119
- this._select.selectedIndex = 0;
133
+ // Back to what the markup asked for, else the first option.
134
+ this._wanted = this.getAttribute('value');
135
+ if (!this._applyValue(this._wanted)) this._select.selectedIndex = 0;
120
136
  this._syncFormValue();
121
137
  }
122
138
 
123
- get value() { return this._select?.value ?? ''; }
139
+ get value() {
140
+ // Until options are there, the wanted value is the answer: a framework
141
+ // reads back what it has just set.
142
+ if (!this._select?.options.length) return this._wanted ?? this.getAttribute('value') ?? '';
143
+ return this._select.value;
144
+ }
124
145
  set value(v) {
125
- if (this._select) this._select.value = v;
146
+ this._wanted = v == null ? '' : String(v);
147
+ if (!this._select) return; // render() takes it from here
148
+ this._applyValue(this._wanted);
126
149
  this._syncFormValue();
127
150
  }
128
151
 
@@ -3,7 +3,7 @@ import '@jungherz-de/glasskit/glasskit-styles.js';
3
3
 
4
4
  class GlkTextarea extends GlkFormElement {
5
5
  static get observedAttributes() {
6
- return ['label', 'placeholder', 'rows', 'disabled', 'name', 'value', 'required'];
6
+ return ['label', 'placeholder', 'rows', 'disabled', 'readonly', 'name', 'value', 'required'];
7
7
  }
8
8
 
9
9
  render() {
@@ -23,10 +23,13 @@ class GlkTextarea extends GlkFormElement {
23
23
  const name = this.getAttribute('name');
24
24
  if (name) this._textarea.setAttribute('name', name);
25
25
 
26
- const value = this.getAttribute('value');
26
+ // A value set as a property before render() has waited for it.
27
+ const value = this._pendingValue ?? this.getAttribute('value');
28
+ this._pendingValue = undefined;
27
29
  if (value) this._textarea.value = value;
28
30
 
29
31
  if (this.getBoolAttr('disabled')) this._textarea.disabled = true;
32
+ if (this.getBoolAttr('readonly')) this._textarea.readOnly = true;
30
33
  if (this.getBoolAttr('required')) this._textarea.required = true;
31
34
 
32
35
  group.appendChild(this._labelEl);
@@ -64,6 +67,9 @@ class GlkTextarea extends GlkFormElement {
64
67
  case 'disabled':
65
68
  this._textarea.disabled = this.getBoolAttr('disabled');
66
69
  break;
70
+ case 'readonly':
71
+ this._textarea.readOnly = this.getBoolAttr('readonly');
72
+ break;
67
73
  case 'name':
68
74
  this._textarea.setAttribute('name', this.getAttribute('name') || '');
69
75
  break;
@@ -88,14 +94,21 @@ class GlkTextarea extends GlkFormElement {
88
94
  this._syncFormValue();
89
95
  }
90
96
 
91
- get value() { return this._textarea?.value ?? ''; }
97
+ get value() { return this._textarea ? this._textarea.value : (this._pendingValue ?? this.getAttribute('value') ?? ''); }
92
98
  set value(v) {
93
- if (this._textarea) this._textarea.value = v;
99
+ const value = v == null ? '' : String(v);
100
+ // Frameworks set properties right after creating the element. Before
101
+ // render() the value waits for it — it used to throw.
102
+ if (!this._textarea) { this._pendingValue = value; return; }
103
+ this._textarea.value = value;
94
104
  this._syncFormValue();
95
105
  }
96
106
 
97
107
  get disabled() { return this.getBoolAttr('disabled'); }
98
108
  set disabled(v) { this.setBoolAttr('disabled', v); }
109
+
110
+ get readOnly() { return this.getBoolAttr('readonly'); }
111
+ set readOnly(v) { this.setBoolAttr('readonly', v); }
99
112
  }
100
113
 
101
114
  customElements.define('glk-textarea', GlkTextarea);
@@ -105,11 +105,12 @@ class GlkToggle extends GlkFormElement {
105
105
  }
106
106
  }
107
107
 
108
- get checked() { return this._input?.checked ?? false; }
108
+ // Before render() the attribute carries the state: render() reads it.
109
+ get checked() { return this._input ? this._input.checked : this.getBoolAttr('checked'); }
109
110
  set checked(v) {
110
111
  if (this._input) this._input.checked = v;
111
112
  this.setBoolAttr('checked', v);
112
- this._syncFormValue();
113
+ if (this._input) this._syncFormValue();
113
114
  }
114
115
 
115
116
  get disabled() { return this.getBoolAttr('disabled'); }