@pageboard/html 0.11.28 → 0.12.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.
Files changed (52) hide show
  1. package/elements/accordion.js +0 -1
  2. package/elements/card.js +5 -5
  3. package/elements/consent.js +1 -1
  4. package/elements/embed.js +1 -1
  5. package/elements/fieldsets.js +2 -2
  6. package/elements/form.js +2 -2
  7. package/elements/grid.js +3 -3
  8. package/elements/heading.js +1 -1
  9. package/elements/image.js +4 -4
  10. package/elements/input-date.js +3 -4
  11. package/elements/inputs.js +12 -12
  12. package/elements/layout.js +8 -9
  13. package/elements/link.js +1 -1
  14. package/elements/menu.js +7 -7
  15. package/elements/navigation.js +3 -16
  16. package/elements/pagination.js +1 -1
  17. package/elements/paragraph.js +2 -2
  18. package/elements/sitemap.js +8 -8
  19. package/elements/sticky.js +2 -2
  20. package/elements/tab.js +1 -1
  21. package/elements/table.js +10 -10
  22. package/elements/template.js +1 -1
  23. package/lib/nouislider.js +28 -0
  24. package/package.json +1 -1
  25. package/ui/accordion.js +3 -21
  26. package/ui/consent.css +3 -3
  27. package/ui/consent.js +9 -13
  28. package/ui/embed.js +11 -17
  29. package/ui/fieldset-list.js +9 -9
  30. package/ui/fieldset.js +4 -8
  31. package/ui/form.js +143 -158
  32. package/ui/heading-helper.js +13 -19
  33. package/ui/image.js +19 -30
  34. package/ui/input-date-slot.js +2 -2
  35. package/ui/input-date.js +2 -7
  36. package/ui/input-file.js +58 -61
  37. package/ui/input-range.js +4 -8
  38. package/ui/layout.js +2 -6
  39. package/ui/media.js +15 -28
  40. package/ui/menu.js +23 -27
  41. package/ui/pagination.js +2 -2
  42. package/ui/query-tags.js +4 -4
  43. package/ui/rating.js +2 -2
  44. package/ui/scroll-link.js +2 -9
  45. package/ui/select.js +21 -23
  46. package/ui/sitemap-helper.js +5 -5
  47. package/ui/sitemap.js +15 -15
  48. package/ui/sticky.js +2 -6
  49. package/ui/tab-helper.js +32 -34
  50. package/ui/tab.js +2 -4
  51. package/ui/time.js +2 -7
  52. package/ui/transition.js +21 -21
package/ui/image.js CHANGED
@@ -1,4 +1,4 @@
1
- class HTMLElementImage extends VirtualHTMLElement {
1
+ const HTMLElementImageConstructor = Superclass => class extends Superclass {
2
2
  static defaults = {
3
3
  src: null,
4
4
  crop: null
@@ -7,6 +7,8 @@ class HTMLElementImage extends VirtualHTMLElement {
7
7
  static defaultWidth = 240;
8
8
  static defaultHeight = 180;
9
9
 
10
+ #defer = new Deferred();
11
+
10
12
  static getZoom({ w, h, rw, rh, fit }) {
11
13
  let z = 100;
12
14
  if (!rw && !rh) return z;
@@ -31,10 +33,6 @@ class HTMLElementImage extends VirtualHTMLElement {
31
33
  }
32
34
  return { w, h };
33
35
  }
34
- init() {
35
- this.promise = Promise.resolve();
36
- this.promise.done = function () { };
37
- }
38
36
  findClass(list) {
39
37
  return list.find(name => this.matches(`.${name}`)) || list[0];
40
38
  }
@@ -95,8 +93,8 @@ class HTMLElementImage extends VirtualHTMLElement {
95
93
  if (this.currentSrc != this.options.src) {
96
94
  this.classList.remove('error');
97
95
  }
98
- this.dataset.width = this.constructor.defaultWidth;
99
- this.dataset.height = this.constructor.defaultHeight;
96
+ this.dataset.width = this.constructor.defaultWidth || "";
97
+ this.dataset.height = this.constructor.defaultHeight || "";
100
98
  if (this.options.src) {
101
99
  const loc = Page.parse(this.options.src);
102
100
  const meta = state.scope.$hrefs?.[loc.pathname];
@@ -106,8 +104,8 @@ class HTMLElementImage extends VirtualHTMLElement {
106
104
  }
107
105
  }
108
106
  const { w, h } = this.dimensions;
109
- this.image.width = w || this.dataset.width;
110
- this.image.height = h || this.dataset.height;
107
+ if (w) this.image.width = w || this.dataset.width;
108
+ if (h) this.image.height = h || this.dataset.height;
111
109
  if (!this.currentSrc) {
112
110
  this.placeholder();
113
111
  }
@@ -156,27 +154,24 @@ class HTMLElementImage extends VirtualHTMLElement {
156
154
  const rh = rect.height;
157
155
  if (rw == 0 && rh == 0) {
158
156
  // don't show
159
- return this.promise;
157
+ return this.#defer;
160
158
  }
161
- loc.query.rs = "z-" + HTMLElementImage.getZoom({ w, h, rw, rh, fit });
159
+ loc.query.rs = "z-" + this.constructor.getZoom({ w, h, rw, rh, fit });
162
160
  }
163
161
  const curSrc = loc.toString();
164
162
  if (curSrc != this.currentSrc) {
165
163
  this.classList.add('loading');
166
- let done;
167
- this.promise = new Promise(resolve => done = resolve);
168
- this.promise.done = done;
169
164
  img.setAttribute('src', curSrc);
170
165
  }
171
- return this.promise;
166
+ return this.#defer;
172
167
  }
173
168
  captureLoad() {
174
- this.promise?.done();
169
+ this.#defer.resolve();
175
170
  this.classList.remove('loading');
176
171
  this.fix(this.image);
177
172
  }
178
173
  captureError() {
179
- this.promise?.done();
174
+ this.#defer.resolve();
180
175
  this.classList.remove('loading');
181
176
  this.classList.add('error');
182
177
  this.placeholder(true);
@@ -184,21 +179,16 @@ class HTMLElementImage extends VirtualHTMLElement {
184
179
  placeholder(error) {
185
180
  this.image.removeAttribute('src');
186
181
  }
187
- }
182
+ };
188
183
 
189
- class HTMLElementInlineImage extends HTMLImageElement {
190
- constructor() {
191
- super();
192
- if (this.init) this.init();
193
- }
184
+ const HTMLElementImage = HTMLElementImageConstructor(Page.Element);
185
+
186
+ class HTMLElementInlineImage extends HTMLElementImageConstructor(Page.create(HTMLImageElement)) {
194
187
  static defaults = {
195
188
  dataSrc: null,
196
189
  dataCrop: null
197
190
  };
198
191
 
199
- static defaultWidth = 40;
200
- static defaultHeight = 30;
201
-
202
192
  get image() {
203
193
  return this;
204
194
  }
@@ -213,12 +203,11 @@ class HTMLElementInlineImage extends HTMLImageElement {
213
203
  }
214
204
 
215
205
  get currentSrc() {
216
- const cur = super.currentSrc;
206
+ const cur = Object.getOwnPropertyDescriptor(HTMLImageElement.prototype, 'currentSrc').get.call(this);
217
207
  if (!cur && this.image.src?.startsWith('data:')) return this.src;
218
208
  else return cur;
219
209
  }
220
210
  }
221
211
 
222
- VirtualHTMLElement.inherits(HTMLElementInlineImage, HTMLElementImage);
223
- VirtualHTMLElement.define('element-image', HTMLElementImage);
224
- VirtualHTMLElement.define(`element-img`, HTMLElementInlineImage, 'img');
212
+ Page.define('element-image', HTMLElementImage);
213
+ Page.define(`element-img`, HTMLElementInlineImage, 'img');
@@ -1,4 +1,4 @@
1
- class HTMLElementInputDateSlot extends VirtualHTMLElement {
1
+ class HTMLElementInputDateSlot extends Page.Element {
2
2
  handleChange(e, state) {
3
3
  this.update(e.target);
4
4
  }
@@ -74,5 +74,5 @@ class HTMLElementInputDateSlot extends VirtualHTMLElement {
74
74
  }
75
75
  }
76
76
 
77
- VirtualHTMLElement.define('element-input-date-slot', HTMLElementInputDateSlot);
77
+ Page.define('element-input-date-slot', HTMLElementInputDateSlot);
78
78
 
package/ui/input-date.js CHANGED
@@ -1,9 +1,4 @@
1
- class HTMLElementInputDate extends HTMLInputElement {
2
- constructor() {
3
- super();
4
- if (this.init) this.init();
5
- }
6
-
1
+ class HTMLElementInputDate extends Page.create(HTMLInputElement) {
7
2
  patch() {
8
3
  if (this.type == "date") this.removeAttribute('step');
9
4
  }
@@ -70,4 +65,4 @@ class HTMLElementInputDate extends HTMLInputElement {
70
65
  }
71
66
  }
72
67
 
73
- VirtualHTMLElement.define('element-input-date', HTMLElementInputDate, 'input');
68
+ Page.define('element-input-date', HTMLElementInputDate, 'input');
package/ui/input-file.js CHANGED
@@ -1,6 +1,6 @@
1
- class HTMLElementInputFile extends HTMLInputElement {
1
+ class HTMLElementInputFile extends Page.create(HTMLInputElement) {
2
2
  #xhr;
3
- #promise;
3
+ #defer;
4
4
  #defaultValue;
5
5
 
6
6
  /* Since input[type=file] does not allow setting "value" property,
@@ -10,11 +10,6 @@ class HTMLElementInputFile extends HTMLInputElement {
10
10
  * and filled value attribute.
11
11
  */
12
12
 
13
- constructor() {
14
- super();
15
- if (this.init) this.init();
16
- this.save();
17
- }
18
13
  get defaultValue() {
19
14
  return this.#defaultValue;
20
15
  }
@@ -32,6 +27,9 @@ class HTMLElementInputFile extends HTMLInputElement {
32
27
  super.value = "";
33
28
  }
34
29
  }
30
+ patch() {
31
+ this.save();
32
+ }
35
33
  captureClick(e, state) {
36
34
  if (this.value) {
37
35
  e.preventDefault();
@@ -55,7 +53,7 @@ class HTMLElementInputFile extends HTMLInputElement {
55
53
  }
56
54
 
57
55
  presubmit() {
58
- if (this.#promise) return this.#promise;
56
+ if (this.#defer) return this.#defer;
59
57
  if (!this.files.length) return Promise.resolve();
60
58
  const field = this.closest('.field');
61
59
  field.classList.remove('success', 'error');
@@ -65,68 +63,67 @@ class HTMLElementInputFile extends HTMLInputElement {
65
63
  }
66
64
  track(0);
67
65
  field.classList.add('loading');
68
- const p = new Promise((resolve, reject) => {
69
- const fail = (err) => {
70
- field.classList.add('error');
71
- field.classList.remove('loading');
72
- this.#xhr = null;
73
- reject(err);
74
- this.#promise = null;
75
- };
76
- const pass = (obj) => {
77
- if (!obj.items || obj.items.length == 0) return fail(new Error("File rejected"));
78
- const val = obj.items[0];
79
- this.value = val;
80
- field.classList.add('success');
81
- field.classList.remove('loading');
82
- this.#xhr = null;
83
- resolve();
84
- this.#promise = null;
85
- };
86
- if (this.files.length == 0) return resolve(); // or reject ?
66
+ this.#defer = new Deferred();
87
67
 
88
- const fd = new FormData();
89
- fd.append("files", this.files[0]);
68
+ const fail = (err) => {
69
+ field.classList.add('error');
70
+ field.classList.remove('loading');
71
+ this.#xhr = null;
72
+ this.#defer.reject(err);
73
+ this.#defer = null;
74
+ };
75
+ const pass = (obj) => {
76
+ if (!obj.items || obj.items.length == 0) return fail(new Error("File rejected"));
77
+ const val = obj.items[0];
78
+ this.value = val;
79
+ field.classList.add('success');
80
+ field.classList.remove('loading');
81
+ this.#xhr = null;
82
+ this.#defer.resolve();
83
+ this.#defer = null;
84
+ };
85
+ if (this.files.length == 0) return this.#defer.resolve(); // or reject ?
90
86
 
91
- const xhr = new XMLHttpRequest();
87
+ const fd = new FormData();
88
+ fd.append("files", this.files[0]);
92
89
 
93
- xhr.upload.addEventListener("progress", (e) => {
94
- if (e.lengthComputable) {
95
- let percent = Math.round((e.loaded * 100) / e.total);
96
- if (percent >= 100) percent = 99; // only load event can reach 100
97
- track(percent);
98
- }
99
- });
90
+ const xhr = new XMLHttpRequest();
100
91
 
101
- xhr.addEventListener('load', () => {
102
- track(100);
103
- try {
104
- pass(JSON.parse(xhr.responseText));
105
- } catch (ex) {
106
- fail(ex);
107
- }
108
- });
92
+ xhr.upload.addEventListener("progress", (e) => {
93
+ if (e.lengthComputable) {
94
+ let percent = Math.round((e.loaded * 100) / e.total);
95
+ if (percent >= 100) percent = 99; // only load event can reach 100
96
+ track(percent);
97
+ }
98
+ });
109
99
 
110
- xhr.addEventListener('error', (e) => {
111
- if (xhr.status == 0) return fail("Connection error");
112
- const msg = xhr.statusText || "Connection error";
113
- const err = new Error(msg);
114
- err.statusCode = xhr.status;
115
- fail(err);
116
- });
100
+ xhr.addEventListener('load', () => {
101
+ track(100);
117
102
  try {
118
- xhr.open("POST", `/.api/upload/${this.id}`, true);
119
- xhr.setRequestHeader('Accept', "application/json; q=1.0");
120
- xhr.send(fd);
121
- this.#xhr = xhr;
122
- } catch (err) {
123
- fail(err);
103
+ pass(JSON.parse(xhr.responseText));
104
+ } catch (ex) {
105
+ fail(ex);
124
106
  }
125
107
  });
126
- this.#promise = p;
127
- return p;
108
+
109
+ xhr.addEventListener('error', (e) => {
110
+ if (xhr.status == 0) return fail("Connection error");
111
+ const msg = xhr.statusText || "Connection error";
112
+ const err = new Error(msg);
113
+ err.statusCode = xhr.status;
114
+ fail(err);
115
+ });
116
+ try {
117
+ xhr.open("POST", `/.api/upload/${this.id}`, true);
118
+ xhr.setRequestHeader('Accept', "application/json; q=1.0");
119
+ xhr.send(fd);
120
+ this.#xhr = xhr;
121
+ } catch (err) {
122
+ fail(err);
123
+ }
124
+ return this.#defer;
128
125
  }
129
126
  }
130
127
 
131
- VirtualHTMLElement.define('element-input-file', HTMLElementInputFile, 'input');
128
+ Page.define('element-input-file', HTMLElementInputFile, 'input');
132
129
 
package/ui/input-range.js CHANGED
@@ -1,8 +1,4 @@
1
- class HTMLElementInputRange extends HTMLInputElement {
2
- constructor() {
3
- super();
4
- if (this.init) this.init();
5
- }
1
+ class HTMLElementInputRange extends Page.create(HTMLInputElement) {
6
2
  static parse(x) {
7
3
  return (x == null ? '' : x)
8
4
  .split('⩽')
@@ -120,7 +116,7 @@ class HTMLElementInputRange extends HTMLInputElement {
120
116
  }
121
117
  }
122
118
 
123
- Page.ready(() => {
124
- VirtualHTMLElement.define('element-input-range', HTMLElementInputRange, 'input');
125
- });
119
+
120
+ Page.define('element-input-range', HTMLElementInputRange, 'input');
121
+
126
122
 
package/ui/layout.js CHANGED
@@ -1,8 +1,4 @@
1
- class HTMLElementLayout extends HTMLDivElement {
2
- constructor() {
3
- super();
4
- if (this.init) this.init();
5
- }
1
+ class HTMLElementLayout extends Page.create(HTMLDivElement) {
6
2
  static defaults = {
7
3
  dataSrc: null,
8
4
  dataCrop: null,
@@ -93,4 +89,4 @@ for (const name of ['crop', 'dimensions']) {
93
89
  }
94
90
  })(window.customElements.get('element-image'));
95
91
 
96
- VirtualHTMLElement.define(`element-layout`, HTMLElementLayout, 'div');
92
+ Page.define(`element-layout`, HTMLElementLayout, 'div');
package/ui/media.js CHANGED
@@ -1,8 +1,6 @@
1
- const MixinMedia = {
2
- init() {
3
- this.promise = Promise.resolve();
4
- this.promise.done = function() {};
5
- },
1
+ const HTMLElementMediaConstructor = Superclass => class extends Superclass {
2
+ #defer = new Deferred();
3
+
6
4
  patch(state) {
7
5
  this.classList.remove('error', 'loading');
8
6
  const loc = Page.parse(this.options.src);
@@ -10,55 +8,44 @@ const MixinMedia = {
10
8
  if (!meta || !meta.width || !meta.height) return;
11
9
  this.width = meta.width;
12
10
  this.height = meta.height;
13
- },
11
+ }
14
12
  reveal(state) {
15
13
  const curSrc = this.options.src;
16
14
  if (curSrc != this.currentSrc) {
17
15
  try {
18
16
  this.currentSrc = curSrc;
19
- } catch(e) {
17
+ } catch (e) {
20
18
  // pass
21
19
  }
22
20
  this.setAttribute('src', curSrc);
23
21
  }
24
22
  if (this.isContentEditable) this.pause();
25
- return this.promise;
26
- },
23
+ return this.#defer;
24
+ }
27
25
  handleClick(e) {
28
26
  if (this.isContentEditable) e.preventDefault();
29
- },
27
+ }
30
28
  captureLoad() {
31
- this.promise.done();
29
+ this.#defer.resolve();
32
30
  this.classList.remove('loading');
33
- },
31
+ }
34
32
  captureError() {
35
- this.promise.done();
33
+ this.#defer.resolve();
36
34
  this.classList.remove('loading');
37
35
  this.classList.add('error');
38
36
  }
39
37
  };
40
38
 
41
- class HTMLElementVideo extends HTMLVideoElement {
42
- constructor() {
43
- super();
44
- if (this.init) this.init();
45
- }
39
+ class HTMLElementVideo extends HTMLElementMediaConstructor(Page.create(HTMLVideoElement)) {
46
40
  static defaults = {
47
41
  dataSrc: null
48
42
  };
49
43
  }
50
- Object.assign(HTMLElementVideo.prototype, MixinMedia);
44
+ Page.define('element-video', HTMLElementVideo, 'video');
51
45
 
52
- class HTMLElementAudio extends HTMLAudioElement {
53
- constructor() {
54
- super();
55
- if (this.init) this.init();
56
- }
46
+ class HTMLElementAudio extends HTMLElementMediaConstructor(Page.create(HTMLAudioElement)) {
57
47
  static defaults = {
58
48
  dataSrc: null
59
49
  };
60
50
  }
61
- Object.assign(HTMLElementAudio.prototype, MixinMedia);
62
-
63
- VirtualHTMLElement.define('element-video', HTMLElementVideo, 'video');
64
- VirtualHTMLElement.define('element-audio', HTMLElementAudio, 'audio');
51
+ Page.define('element-audio', HTMLElementAudio, 'audio');
package/ui/menu.js CHANGED
@@ -1,30 +1,28 @@
1
- Page.patch(state => {
2
- function isSameOrParent(loc, state, isItem) {
3
- if (!state.sameDomain(loc)) {
4
- return false;
5
- } else if (state.samePathname(loc)) {
6
- if (!isItem && loc.sameQuery({query:{}})) return true;
7
- if (state.query.develop !== undefined) {
8
- // kept for backward compatibility
9
- loc.query.develop = state.query.develop;
1
+ class HTMLElementMenu extends Page.Element {
2
+ static patch(state) {
3
+ function isSameOrParent(loc, ref, isItem) {
4
+ if (!ref.sameDomain(loc)) {
5
+ return false;
6
+ } else if (ref.samePathname(loc)) {
7
+ if (!isItem && !loc.search || ref.sameQuery(loc)) return true;
8
+ } else {
9
+ return ref.pathname.startsWith(loc.pathname + '/');
10
10
  }
11
- if (state.sameQuery(loc)) return true;
12
- } else {
13
- return state.pathname.startsWith(loc.pathname + '/');
14
11
  }
15
- }
16
- state.finish(() => {
17
- for (const item of document.querySelectorAll('[block-type="menu"] [href]')) {
18
- const loc = item.getAttribute('href');
19
- if (!loc) continue;
20
- if (isSameOrParent(Page.parse(loc), state, item.matches('.item'))) {
21
- item.classList.add('active');
12
+ state.finish(() => {
13
+ const ref = state.parse(state);
14
+ for (const name of state.ivars) {
15
+ delete ref.query[name];
22
16
  }
23
- }
24
- });
25
- });
26
-
27
- class HTMLElementMenu extends VirtualHTMLElement {
17
+ for (const item of document.querySelectorAll('[block-type="menu"] [href]')) {
18
+ const loc = item.getAttribute('href');
19
+ if (!loc) continue;
20
+ if (isSameOrParent(state.parse(loc), ref, item.matches('.item'))) {
21
+ item.classList.add('active');
22
+ }
23
+ }
24
+ });
25
+ }
28
26
  setup(state) {
29
27
  if (this.isContentEditable || this.matches('.vertical')) return;
30
28
  const menu = this.firstElementChild;
@@ -86,6 +84,4 @@ class HTMLElementMenu extends VirtualHTMLElement {
86
84
  }
87
85
  }
88
86
 
89
- Page.setup(() => {
90
- VirtualHTMLElement.define('element-menu', HTMLElementMenu);
91
- });
87
+ Page.define('element-menu', HTMLElementMenu);
package/ui/pagination.js CHANGED
@@ -1,4 +1,4 @@
1
- class HTMLElementPagination extends HTMLAnchorElement {
1
+ class HTMLElementPagination extends Page.create(HTMLAnchorElement) {
2
2
  patch(state) {
3
3
  if (this.isContentEditable) return;
4
4
  state.finish(() => {
@@ -47,4 +47,4 @@ class HTMLElementPagination extends HTMLAnchorElement {
47
47
  }
48
48
  }
49
49
 
50
- VirtualHTMLElement.define('element-pagination', HTMLElementPagination, "a");
50
+ Page.define('element-pagination', HTMLElementPagination, "a");
package/ui/query-tags.js CHANGED
@@ -1,4 +1,4 @@
1
- class HTMLElementQueryTags extends VirtualHTMLElement {
1
+ class HTMLElementQueryTags extends Page.Element {
2
2
  find(name, value) {
3
3
  let nodes;
4
4
  const formName = this.getAttribute('for');
@@ -102,7 +102,7 @@ class HTMLElementQueryTags extends VirtualHTMLElement {
102
102
  }
103
103
  }
104
104
 
105
- Page.ready(() => {
106
- VirtualHTMLElement.define('element-query-tags', HTMLElementQueryTags);
107
- });
105
+
106
+ Page.define('element-query-tags', HTMLElementQueryTags);
107
+
108
108
 
package/ui/rating.js CHANGED
@@ -1,4 +1,4 @@
1
- class HTMLElementRating extends VirtualHTMLElement {
1
+ class HTMLElementRating extends Page.Element {
2
2
  static defaults = {
3
3
  maximum: 4,
4
4
  value: (x) => parseFloat(x) || 0,
@@ -20,5 +20,5 @@ class HTMLElementRating extends VirtualHTMLElement {
20
20
  }
21
21
  }
22
22
 
23
- VirtualHTMLElement.define('element-rating', HTMLElementRating);
23
+ Page.define('element-rating', HTMLElementRating);
24
24
 
package/ui/scroll-link.js CHANGED
@@ -1,8 +1,4 @@
1
- class HTMLScrollLinkElement extends HTMLAnchorElement {
2
- constructor() {
3
- super();
4
- if (this.init) this.init();
5
- }
1
+ class HTMLScrollLinkElement extends Page.create(HTMLAnchorElement) {
6
2
  static defaults = {
7
3
  dataTo: (x) => ["home", "end"].includes(x) ? x : "end"
8
4
  };
@@ -25,7 +21,4 @@ class HTMLScrollLinkElement extends HTMLAnchorElement {
25
21
  }
26
22
  }
27
23
 
28
- Page.setup(() => {
29
- VirtualHTMLElement.define(`element-scroll-link`, HTMLScrollLinkElement, 'a');
30
- });
31
-
24
+ Page.define(`element-scroll-link`, HTMLScrollLinkElement, 'a');
package/ui/select.js CHANGED
@@ -1,4 +1,4 @@
1
- class HTMLElementSelect extends VirtualHTMLElement {
1
+ class HTMLElementSelect extends Page.Element {
2
2
  #observer;
3
3
 
4
4
  static defaults = {
@@ -24,6 +24,7 @@ class HTMLElementSelect extends VirtualHTMLElement {
24
24
  }
25
25
 
26
26
  handleClick(e, state) {
27
+ if (this.isContentEditable) return;
27
28
  const node = e.target;
28
29
  const item = node.closest('element-select .item');
29
30
  if (item) {
@@ -55,9 +56,9 @@ class HTMLElementSelect extends VirtualHTMLElement {
55
56
  handleChange(e, state) {
56
57
  const opt = e.target;
57
58
  if (opt.selected) {
58
- this.#selectItem(opt.value);
59
+ this.#selectItem(opt);
59
60
  } else {
60
- this.#deselectItem(opt.value);
61
+ this.#deselectItem(opt);
61
62
  }
62
63
  }
63
64
  #toggleMenu(show) {
@@ -65,8 +66,9 @@ class HTMLElementSelect extends VirtualHTMLElement {
65
66
  if (show === undefined) show = !style.display;
66
67
  style.display = show ? "block" : null;
67
68
  }
68
- #selectItem(val) {
69
+ #selectItem(opt) {
69
70
  const select = this.#select;
71
+ const val = opt.getAttribute('value');
70
72
  const item = this.#menuOption(val);
71
73
 
72
74
  if (this.options.multiple) {
@@ -80,11 +82,12 @@ class HTMLElementSelect extends VirtualHTMLElement {
80
82
  this.#setText(item.innerText.trim());
81
83
  }
82
84
 
83
- const defaultOption = select.querySelector(`option[value=""]`);
85
+ const defaultOption = select.querySelector(`option:not([value])`);
84
86
  if (defaultOption && val) defaultOption.selected = false;
85
87
  }
86
- #deselectItem(val) {
87
- if (this.options.multiple) {
88
+ #deselectItem(opt) {
89
+ const val = opt.getAttribute('value');
90
+ if (this.options.multiple && val) {
88
91
  const item = this.#child(`.label[data-value="${val}"]`);
89
92
  if (item) item.remove();
90
93
  }
@@ -112,9 +115,11 @@ class HTMLElementSelect extends VirtualHTMLElement {
112
115
  }
113
116
 
114
117
  #menuOption(val) {
118
+ if (val == null) val = "";
115
119
  return this.querySelector(`element-select-option[data-value="${val}"]`);
116
120
  }
117
121
  #selectOption(val) {
122
+ if (val == null) val = "";
118
123
  return this.querySelector(`select > option[value="${val}"]`);
119
124
  }
120
125
 
@@ -124,20 +129,14 @@ class HTMLElementSelect extends VirtualHTMLElement {
124
129
  this.#observer = null;
125
130
  }
126
131
  }
127
- #fillSelect() {
132
+ #fillSelect(state) {
128
133
  const select = this.#select;
129
134
  if (!select) return;
130
- const menu = this.#menu;
131
- menu.children.forEach(item => {
132
- const val = item.dataset.value;
133
- select.insertAdjacentHTML(
134
- 'beforeEnd',
135
- `<option value="${val == null ? '' : val}">${item.innerHTML}</option>`
136
- );
137
- });
135
+ select.insertAdjacentHTML('afterBegin', '<option value="[item.dataset.value]">[children|at:*|repeat:item|.innerText]</option>');
136
+ select.fuse(this.#menu, state.scope);
138
137
  }
139
138
  setup(state) {
140
- this.#observer = new MutationObserver(mutations => this.#fillSelect());
139
+ this.#observer = new MutationObserver(mutations => this.#fillSelect(state));
141
140
  this.#observer.observe(this.#menu, {
142
141
  childList: true
143
142
  });
@@ -166,24 +165,23 @@ class HTMLElementSelect extends VirtualHTMLElement {
166
165
  menu.insertAdjacentHTML('afterBegin', `<element-select-option data-value="" block-type="input_select_option" class="item">-</element-select-option>`);
167
166
  }
168
167
  }
169
- this.#fillSelect();
168
+ this.#fillSelect(state);
170
169
  }
171
170
 
172
171
  patch(state) {
173
- if (this.isContentEditable) return; // write mode stop there
172
+ if (this.isContentEditable) return;
174
173
  if (this.children.length == 1) this.build(state);
175
174
 
176
175
  state.finish(() => {
177
176
  // synchronize after form has filled select
178
177
  this.#select.children.forEach(opt => {
179
178
  if (opt.value) {
180
- if (opt.selected) this.#selectItem(opt.value);
181
- else this.#deselectItem(opt.value);
179
+ if (opt.selected) this.#selectItem(opt);
180
+ else this.#deselectItem(opt);
182
181
  }
183
182
  });
184
183
  });
185
184
  }
186
185
  }
187
186
 
188
- VirtualHTMLElement.define('element-select', HTMLElementSelect);
189
-
187
+ Page.define('element-select', HTMLElementSelect);