@pageboard/html 0.10.14 → 0.11.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/ui/form.js CHANGED
@@ -33,27 +33,26 @@ class HTMLCustomFormElement extends HTMLFormElement {
33
33
  delete state.query.submit;
34
34
  state.finish(() => {
35
35
  if (state.status != 200) return;
36
- const e = document.createEvent('HTMLEvents');
37
- e.initEvent('submit', true, true);
38
- this.dispatchEvent(e);
36
+ this.dispatchEvent(new Event('submit', {
37
+ bubbles: true,
38
+ cancelable: true
39
+ }));
39
40
  });
40
41
  }
41
- read(withDefaults) {
42
+ read(withDefaults = false) {
42
43
  const fd = new FormData(this);
43
44
  const query = {};
44
45
  fd.forEach((val, key) => {
45
- if (val == null || val == "") {
46
- const cur = this.querySelectorAll(`[name="${key}"]`).slice(-1).pop();
47
- if (cur.required == false) {
48
- val = undefined;
49
- } else {
50
- val = null;
51
- }
46
+ const cur = this.querySelectorAll(`[name="${key}"]`).slice(-1).pop();
47
+ if (cur.type == "file") {
48
+ val = cur.value;
52
49
  }
50
+ if (val == "") val = null;
51
+ // build array-like values
53
52
  const old = query[key];
54
53
  if (old !== undefined) {
55
54
  if (!Array.isArray(old)) {
56
- query[key] = [old];
55
+ query[key] = old == null ? [] : [old];
57
56
  }
58
57
  if (val !== undefined) query[key].push(val);
59
58
  } else {
@@ -61,55 +60,63 @@ class HTMLCustomFormElement extends HTMLFormElement {
61
60
  }
62
61
  });
63
62
 
63
+ // withDefaults: keep value if equals to its default
64
+ // else unset value
64
65
  for (const node of this.elements) {
65
- if (node.name == null || node.name == "" || node.type == "button") continue;
66
+ const { name, type } = node;
67
+ if (name == null || name == "" || type == "button") {
68
+ continue;
69
+ }
66
70
  let val = node.value;
67
71
  if (val == "") val = null;
68
- if (node.type == "radio") {
69
- if (!withDefaults && node.checked == node.defaultChecked && query[node.name] == val) {
70
- query[node.name] = undefined;
71
- }
72
- } else if (node.type == "checkbox") {
73
- if (!(node.name in query)) {
74
- if (!withDefaults) query[node.name] = undefined;
75
- }
76
- } else if (node.type == "hidden") {
77
- // always include them
78
- } else {
79
- let defVal = node.defaultValue;
80
- if (defVal == "") defVal = null;
81
- if (!withDefaults && query[node.name] == defVal) {
82
- query[node.name] = undefined;
83
- } else {
84
- // not yet using form-associated custom input
85
- query[node.name] = node.value;
86
- }
87
- }
88
- if (query[node.name] === undefined && withDefaults) {
89
- query[node.name] = null;
72
+ let defVal = node.defaultValue;
73
+ if (defVal == "") defVal = null;
74
+
75
+ switch (type) {
76
+ case "radio":
77
+ if (!withDefaults && node.checked == node.defaultChecked) {
78
+ if (query[name] == val) {
79
+ query[name] = undefined;
80
+ }
81
+ }
82
+ break;
83
+ case "checkbox":
84
+ if (!withDefaults) {
85
+ if (!(name in query)) {
86
+ query[name] = undefined;
87
+ }
88
+ }
89
+ break;
90
+ case "hidden":
91
+ break;
92
+ default:
93
+ if (withDefaults) {
94
+ if (query[name] === undefined) {
95
+ query[name] = defVal;
96
+ }
97
+ } else if (val === defVal) {
98
+ query[name] = node.required ? null : undefined;
99
+ }
90
100
  }
91
101
  }
102
+ // FIXME use e.submitter polyfill when available
103
+ // https://github.com/Financial-Times/polyfill-library/issues/1111
92
104
  const btn = document.activeElement;
93
- // FIXME https://github.com/whatwg/html/issues/3195 use e.submitter polyfill
94
- // https://github.com/whatwg/xhr/issues/262 it's a mess
95
105
  if (btn && btn.type == "submit" && btn.name && btn.value) {
96
106
  query[btn.name] = btn.value;
97
107
  }
98
108
  return query;
99
109
  }
100
110
  fill(query, scope) {
101
- // workaround for merging arrays
102
- const tagList = "element-fieldset-list";
103
- const FieldSet = VirtualHTMLElement.define(tagList);
104
- for (const node of this.querySelectorAll(tagList)) {
105
- if (!node.fill) Object.setPrototypeOf(node, FieldSet.prototype);
111
+ // fieldset-list are not custom inputs yet
112
+ for (const node of this.querySelectorAll("element-fieldset-list")) {
106
113
  node.fill(query, scope);
107
114
  }
108
115
  const vars = [];
109
116
  for (const elem of this.elements) {
110
117
  const name = elem.name;
111
118
  if (!name) continue;
112
- if (Object.prototype.hasOwnProperty.call(query, name) && !vars.includes(name)) vars.push(name);
119
+ if (name in query && !vars.includes(name)) vars.push(name);
113
120
  const val = query[name];
114
121
  const str = ((v) => {
115
122
  if (v == null) return "";
@@ -218,7 +225,7 @@ class HTMLCustomFormElement extends HTMLFormElement {
218
225
  const loc = Page.parse(redirect);
219
226
  Object.assign(loc.query, this.read(false));
220
227
  if (loc.samePathname(state)) {
221
- loc.query = Object.assign({}, state.query, loc.query);
228
+ loc.query = { ...state.query, ...loc.query };
222
229
  }
223
230
  let status = 200;
224
231
  const p = this.ignoreInputChange
@@ -345,7 +352,8 @@ HTMLInputElement.prototype.fill = function (val) {
345
352
  if (this.type == "radio" || this.type == "checkbox") {
346
353
  this.checked = val;
347
354
  } else if (this.type == "file") {
348
- this.setAttribute('value', val);
355
+ if (val == '' || val == null) this.removeAttribute('value');
356
+ else this.setAttribute('value', val);
349
357
  } else {
350
358
  this.value = val;
351
359
  }
@@ -363,7 +371,7 @@ HTMLInputElement.prototype.save = function () {
363
371
  if (this.type == "radio" || this.type == "checkbox") {
364
372
  this.defaultChecked = this.checked;
365
373
  } else if (this.type == "file") {
366
- this.defaultValue = this.getAttribute('value');
374
+ this.defaultValue = this.getAttribute('value') || '';
367
375
  } else {
368
376
  this.defaultValue = this.value;
369
377
  }
@@ -381,11 +389,13 @@ Object.defineProperty(HTMLInputElement.prototype, 'defaultValue', {
381
389
  configurable: true,
382
390
  enumerable: true,
383
391
  get: function () {
392
+ // FIXME might not be needed anymore
384
393
  if (this.form?.method == "get") return '';
385
394
  else return this.getAttribute('value');
386
395
  },
387
396
  set: function (val) {
388
- this.setAttribute('value', val);
397
+ if (val == '' || val == null) this.removeAttribute('value');
398
+ else this.setAttribute('value', val);
389
399
  }
390
400
  });
391
401
 
@@ -417,7 +427,7 @@ Page.setup((state) => {
417
427
  }
418
428
  });
419
429
 
420
- Page.ready((state) => {
430
+ Page.patch(state => {
421
431
  const filters = state.scope.$filters;
422
432
 
423
433
  function linearizeValues(query, obj = {}, prefix) {
@@ -448,35 +458,32 @@ Page.ready((state) => {
448
458
  if (action == "toggle") {
449
459
  action = val ? "enable" : "disable";
450
460
  }
451
- // NB: call Class methods to deal with uninstantiated custom form
452
- if (action == "enable") {
453
- HTMLCustomFormElement.prototype.enable.call(form);
454
- } else if (action == "disable") {
455
- HTMLCustomFormElement.prototype.disable.call(form);
456
- } else if (action == "fill") {
457
- if (val == null) {
458
- form.reset();
459
- } else if (typeof val == "object") {
460
- let values = val;
461
- if (val.id && val.data) {
462
- // old way
463
- values = Object.assign({}, val.data);
464
- for (const key of Object.keys(val)) {
465
- if (key != "data") values['$' + key] = val[key];
461
+
462
+ state.finish(() => {
463
+ if (action == "enable") {
464
+ form.enable();
465
+ } else if (action == "disable") {
466
+ form.disable();
467
+ } else if (action == "fill") {
468
+ if (val == null) {
469
+ form.reset();
470
+ } else if (typeof val == "object") {
471
+ let values = val;
472
+ if (val.id && val.data) {
473
+ // old way
474
+ values = { ...val.data };
475
+ for (const key of Object.keys(val)) {
476
+ if (key != "data") values['$' + key] = val[key];
477
+ }
478
+ } else {
479
+ // new way
466
480
  }
467
- } else {
468
- // new way
481
+ form.fill(linearizeValues(values), state.scope);
482
+ form.save();
469
483
  }
470
- HTMLCustomFormElement.prototype.fill.call(form, linearizeValues(values), state.scope);
471
- HTMLCustomFormElement.prototype.save.call(form);
472
- }
473
- } else if (action == "read") {
474
- const obj = {};
475
- for (const [key, kval] of Object.entries(val)) {
476
- if (form.querySelector(`[name="${key}"]`)) obj[key] = kval;
477
484
  }
478
- return obj;
479
- }
485
+ });
486
+
480
487
  return val;
481
488
  };
482
489
  });
package/ui/input-file.js CHANGED
@@ -14,7 +14,6 @@ class HTMLElementInputFile extends HTMLInputElement {
14
14
  super();
15
15
  if (this.init) this.init();
16
16
  this.save();
17
-
18
17
  }
19
18
  get defaultValue() {
20
19
  return this.#defaultValue;
@@ -26,7 +25,7 @@ class HTMLElementInputFile extends HTMLInputElement {
26
25
  return this.getAttribute('value');
27
26
  }
28
27
  set value(str) {
29
- if (str != null) {
28
+ if (str) {
30
29
  this.setAttribute('value', str);
31
30
  } else {
32
31
  this.removeAttribute('value');
package/ui/input-range.js CHANGED
@@ -81,9 +81,10 @@ class HTMLElementInputRange extends HTMLInputElement {
81
81
  helper.classList.remove('indeterminate');
82
82
  if (isInt) values = values.map((n) => parseInt(n));
83
83
  this.rangeValue = values;
84
- const e = document.createEvent('HTMLEvents');
85
- e.initEvent('change', true, true);
86
- this.dispatchEvent(e);
84
+ this.dispatchEvent(new Event('change', {
85
+ bubbles: true,
86
+ cancelable: true
87
+ }));
87
88
  });
88
89
  helper.addEventListener('keydown', this, true);
89
90
  helper.addEventListener('dblclick', this, true);
@@ -92,9 +93,10 @@ class HTMLElementInputRange extends HTMLInputElement {
92
93
  handleEvent(e) {
93
94
  if (e.type == "dblclick" || e.keyCode == 8 || e.keyCode == 46) {
94
95
  this.fill();
95
- const ne = document.createEvent('HTMLEvents');
96
- ne.initEvent('change', true, true);
97
- this.dispatchEvent(ne);
96
+ this.dispatchEvent(new Event('change', {
97
+ bubbles: true,
98
+ cancelable: true
99
+ }));
98
100
  }
99
101
  }
100
102
 
package/ui/menu.js CHANGED
@@ -27,15 +27,17 @@ class HTMLElementMenu extends VirtualHTMLElement {
27
27
  const menu = this.firstElementChild;
28
28
  const helper = this.lastElementChild;
29
29
  helper.lastElementChild.lastElementChild.appendChild(this.toHelper(menu));
30
- this.observer = new ResizeObserver((entries, observer) => {
31
- window.requestAnimationFrame(() => {
32
- const styles = window.getComputedStyle(this);
33
- const parentWidth = parseFloat(styles.marginLeft) + parseFloat(styles.marginRight) + this.offsetWidth;
34
- const menuWidth = menu.offsetWidth;
35
- this.classList.toggle('burger', parentWidth <= menuWidth);
30
+ state.finish(() => {
31
+ this.observer = new ResizeObserver((entries, observer) => {
32
+ window.requestAnimationFrame(() => {
33
+ const styles = window.getComputedStyle(this);
34
+ const parentWidth = parseFloat(styles.marginLeft) + parseFloat(styles.marginRight) + this.offsetWidth;
35
+ const menuWidth = menu.offsetWidth;
36
+ this.classList.toggle('burger', parentWidth <= menuWidth);
37
+ });
36
38
  });
39
+ this.observer.observe(this.parentNode);
37
40
  });
38
- this.observer.observe(this.parentNode);
39
41
  }
40
42
  close(state) {
41
43
  if (this.observer) this.observer.disconnect();
package/ui/pagination.js CHANGED
@@ -27,9 +27,10 @@ class HTMLElementPagination extends HTMLAnchorElement {
27
27
  } else {
28
28
  this.setAttribute('href', Page.format({
29
29
  pathname: state.pathname,
30
- query: Object.assign({}, state.query, {
30
+ query: {
31
+ ...state.query,
31
32
  [name]: cur || undefined
32
- })
33
+ }
33
34
  }));
34
35
  }
35
36
  state.finish(() => {
package/ui/query-tags.js CHANGED
@@ -90,9 +90,10 @@ class HTMLElementQueryTags extends VirtualHTMLElement {
90
90
  else if (control.selected) control.selected = false;
91
91
  else if (control.reset) control.reset();
92
92
  else if (control.value) control.value = "";
93
- const e = document.createEvent('HTMLEvents');
94
- e.initEvent('submit', true, true);
95
- control.form.dispatchEvent(e);
93
+ control.form.dispatchEvent(new Event('submit', {
94
+ bubbles: true,
95
+ cancelable: true
96
+ }));
96
97
  }
97
98
  label.remove();
98
99
  }
package/ui/tab.js CHANGED
@@ -14,7 +14,7 @@ class HTMLElementTabs extends VirtualHTMLElement {
14
14
  const id = this.id;
15
15
 
16
16
  this.items.children.forEach((item, i) => {
17
- const query = Object.assign({}, state.query);
17
+ const query = { ...state.query };
18
18
  const key = `${id}.index`;
19
19
  if (i == 0) delete query[key];
20
20
  else query[key] = i;