@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.
- package/elements/accordion.js +0 -1
- package/elements/card.js +5 -5
- package/elements/consent.js +1 -1
- package/elements/embed.js +1 -1
- package/elements/fieldsets.js +2 -2
- package/elements/form.js +2 -2
- package/elements/grid.js +3 -3
- package/elements/heading.js +1 -1
- package/elements/image.js +4 -4
- package/elements/input-date.js +3 -4
- package/elements/inputs.js +12 -12
- package/elements/layout.js +8 -9
- package/elements/link.js +1 -1
- package/elements/menu.js +7 -7
- package/elements/navigation.js +3 -16
- package/elements/pagination.js +1 -1
- package/elements/paragraph.js +2 -2
- package/elements/sitemap.js +8 -8
- package/elements/sticky.js +2 -2
- package/elements/tab.js +1 -1
- package/elements/table.js +10 -10
- package/elements/template.js +1 -1
- package/lib/nouislider.js +28 -0
- package/package.json +1 -1
- package/ui/accordion.js +3 -21
- package/ui/consent.css +3 -3
- package/ui/consent.js +9 -13
- package/ui/embed.js +11 -17
- package/ui/fieldset-list.js +9 -9
- package/ui/fieldset.js +4 -8
- package/ui/form.js +143 -158
- package/ui/heading-helper.js +13 -19
- package/ui/image.js +19 -30
- package/ui/input-date-slot.js +2 -2
- package/ui/input-date.js +2 -7
- package/ui/input-file.js +58 -61
- package/ui/input-range.js +4 -8
- package/ui/layout.js +2 -6
- package/ui/media.js +15 -28
- package/ui/menu.js +23 -27
- package/ui/pagination.js +2 -2
- package/ui/query-tags.js +4 -4
- package/ui/rating.js +2 -2
- package/ui/scroll-link.js +2 -9
- package/ui/select.js +21 -23
- package/ui/sitemap-helper.js +5 -5
- package/ui/sitemap.js +15 -15
- package/ui/sticky.js +2 -6
- package/ui/tab-helper.js +32 -34
- package/ui/tab.js +2 -4
- package/ui/time.js +2 -7
- package/ui/transition.js +21 -21
package/ui/consent.js
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
class
|
|
2
|
-
constructor() {
|
|
3
|
-
super();
|
|
4
|
-
if (this.init) this.init();
|
|
5
|
-
}
|
|
1
|
+
class HTMLElementConsent extends Page.create(HTMLFormElement) {
|
|
6
2
|
static defaults = {
|
|
7
3
|
dataTransient: false
|
|
8
4
|
};
|
|
@@ -27,7 +23,7 @@ class HTMLCustomConsentElement extends HTMLFormElement {
|
|
|
27
23
|
state.consent(this);
|
|
28
24
|
}
|
|
29
25
|
chainConsent(state) {
|
|
30
|
-
window.
|
|
26
|
+
window.HTMLElementForm.prototype.fill.call(this, {
|
|
31
27
|
consent: state.scope.$consent
|
|
32
28
|
});
|
|
33
29
|
if (this.options.transient) this.classList.remove('visible');
|
|
@@ -35,7 +31,7 @@ class HTMLCustomConsentElement extends HTMLFormElement {
|
|
|
35
31
|
handleSubmit(e, state) {
|
|
36
32
|
if (e.type == "submit") e.preventDefault();
|
|
37
33
|
if (this.isContentEditable) return;
|
|
38
|
-
const fd = window.
|
|
34
|
+
const fd = window.HTMLElementForm.prototype.read.call(this);
|
|
39
35
|
const consent = fd.consent;
|
|
40
36
|
if (consent == null) {
|
|
41
37
|
return;
|
|
@@ -63,9 +59,9 @@ class HTMLCustomConsentElement extends HTMLFormElement {
|
|
|
63
59
|
}
|
|
64
60
|
}
|
|
65
61
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
62
|
+
|
|
63
|
+
Page.define(`element-consent`, HTMLElementConsent, 'form');
|
|
64
|
+
|
|
69
65
|
|
|
70
66
|
Page.constructor.prototype.consent = function (fn) {
|
|
71
67
|
const initial = this.scope.$consent === undefined;
|
|
@@ -74,7 +70,7 @@ Page.constructor.prototype.consent = function (fn) {
|
|
|
74
70
|
this.scope.$consent = consent;
|
|
75
71
|
this.chain('consent', fn);
|
|
76
72
|
if (consent === undefined) {
|
|
77
|
-
|
|
73
|
+
HTMLElementConsent.waiting = true;
|
|
78
74
|
} else if (consent === null) {
|
|
79
75
|
// setup finished but no consent is done yet, ask consent
|
|
80
76
|
this.reconsent();
|
|
@@ -86,7 +82,7 @@ Page.constructor.prototype.reconsent = function (fn) {
|
|
|
86
82
|
const consent = this.scope.$consent;
|
|
87
83
|
let asking = false;
|
|
88
84
|
if (consent != "yes") {
|
|
89
|
-
asking =
|
|
85
|
+
asking = HTMLElementConsent.ask();
|
|
90
86
|
}
|
|
91
87
|
if (!asking) {
|
|
92
88
|
if (consent == null) this.scope.$consent = "yes";
|
|
@@ -97,7 +93,7 @@ Page.constructor.prototype.reconsent = function (fn) {
|
|
|
97
93
|
Page.paint(state => {
|
|
98
94
|
state.finish(() => {
|
|
99
95
|
let run = true;
|
|
100
|
-
if (
|
|
96
|
+
if (HTMLElementConsent.waiting) {
|
|
101
97
|
if (state.reconsent()) run = false;
|
|
102
98
|
}
|
|
103
99
|
if (run) state.runChain('consent');
|
package/ui/embed.js
CHANGED
|
@@ -1,21 +1,15 @@
|
|
|
1
|
-
class HTMLElementEmbed extends
|
|
1
|
+
class HTMLElementEmbed extends Page.Element {
|
|
2
2
|
static defaults = {
|
|
3
3
|
src: null,
|
|
4
4
|
hash: null
|
|
5
5
|
};
|
|
6
6
|
static revealRatio = 0.2;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
this.promise.done = function() {};
|
|
10
|
-
}
|
|
7
|
+
#defer = new Deferred();
|
|
8
|
+
|
|
11
9
|
reveal(state) {
|
|
12
|
-
let done;
|
|
13
|
-
this.promise = new Promise(resolve => done = resolve);
|
|
14
|
-
this.promise.done = done;
|
|
15
10
|
this.classList.add('waiting');
|
|
16
|
-
|
|
17
11
|
state.consent(this);
|
|
18
|
-
return this
|
|
12
|
+
return this.#defer;
|
|
19
13
|
}
|
|
20
14
|
consent(state) {
|
|
21
15
|
const consent = state.scope.$consent;
|
|
@@ -25,7 +19,7 @@ class HTMLElementEmbed extends VirtualHTMLElement {
|
|
|
25
19
|
this.iframe = this.querySelector('iframe');
|
|
26
20
|
if (consent != "yes") {
|
|
27
21
|
if (this.iframe) this.iframe.remove();
|
|
28
|
-
this.
|
|
22
|
+
this.#defer.resolve();
|
|
29
23
|
return;
|
|
30
24
|
}
|
|
31
25
|
if (!this.iframe) {
|
|
@@ -52,18 +46,18 @@ class HTMLElementEmbed extends VirtualHTMLElement {
|
|
|
52
46
|
this.iframe.setAttribute('src', this.currentSrc);
|
|
53
47
|
}
|
|
54
48
|
}
|
|
55
|
-
this.
|
|
49
|
+
this.#defer.resolve();
|
|
56
50
|
}
|
|
57
51
|
}
|
|
58
52
|
captureClick(e, state) {
|
|
59
53
|
if (this.matches('.denied')) state.reconsent();
|
|
60
54
|
}
|
|
61
55
|
captureLoad() {
|
|
62
|
-
this.
|
|
56
|
+
this.#defer.resolve();
|
|
63
57
|
this.classList.remove('loading');
|
|
64
58
|
}
|
|
65
59
|
captureError() {
|
|
66
|
-
this.
|
|
60
|
+
this.#defer.resolve();
|
|
67
61
|
this.classList.add('error');
|
|
68
62
|
}
|
|
69
63
|
handleAllMessage(e, state) {
|
|
@@ -79,6 +73,6 @@ class HTMLElementEmbed extends VirtualHTMLElement {
|
|
|
79
73
|
}
|
|
80
74
|
}
|
|
81
75
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
76
|
+
|
|
77
|
+
Page.define('element-embed', HTMLElementEmbed);
|
|
78
|
+
|
package/ui/fieldset-list.js
CHANGED
|
@@ -27,7 +27,7 @@ class WalkIndex {
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
class HTMLElementFieldsetList extends
|
|
30
|
+
class HTMLElementFieldsetList extends Page.Element {
|
|
31
31
|
#size;
|
|
32
32
|
#initialSize;
|
|
33
33
|
#prefix;
|
|
@@ -115,29 +115,29 @@ class HTMLElementFieldsetList extends VirtualHTMLElement {
|
|
|
115
115
|
if (this.#size === len) return;
|
|
116
116
|
this.#size = len;
|
|
117
117
|
let tpl = this.ownTpl.content.cloneNode(true);
|
|
118
|
-
const
|
|
118
|
+
const fieldlist = Array.from(Array(len)).map((x, i) => {
|
|
119
119
|
return { index: i };
|
|
120
120
|
});
|
|
121
121
|
const inputs = tpl.querySelectorAll('[name]');
|
|
122
122
|
const prefix = this.#prefix;
|
|
123
123
|
for (const node of inputs) {
|
|
124
124
|
if (node.name.startsWith(prefix)) {
|
|
125
|
-
node.name = `${prefix}[
|
|
125
|
+
node.name = `${prefix}[fielditem.index].${node.name.substring(prefix.length)}`;
|
|
126
126
|
if (node.id?.startsWith('for-' + prefix)) {
|
|
127
|
-
node.id = `for-${prefix}[
|
|
127
|
+
node.id = `for-${prefix}[fielditem.index].${node.id.substring(4 + prefix.length)}`;
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
const conditionalFieldsets = tpl.querySelectorAll('[is="element-fieldset"]');
|
|
132
132
|
for (const node of conditionalFieldsets) {
|
|
133
133
|
if (node.dataset.name?.startsWith(prefix)) {
|
|
134
|
-
node.dataset.name = `${prefix}[
|
|
134
|
+
node.dataset.name = `${prefix}[fielditem.index].${node.dataset.name.substring(prefix.length)}`;
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
137
|
const labels = tpl.querySelectorAll('label[for]');
|
|
138
138
|
for (const node of labels) {
|
|
139
139
|
if (node.htmlFor?.startsWith('for-' + prefix)) {
|
|
140
|
-
node.htmlFor = `for-${prefix}[
|
|
140
|
+
node.htmlFor = `for-${prefix}[fielditem.index].${node.htmlFor.substring(4 + prefix.length)}`;
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
143
|
|
|
@@ -147,7 +147,7 @@ class HTMLElementFieldsetList extends VirtualHTMLElement {
|
|
|
147
147
|
return;
|
|
148
148
|
}
|
|
149
149
|
subtpl.appendChild(
|
|
150
|
-
subtpl.ownerDocument.createTextNode('[
|
|
150
|
+
subtpl.ownerDocument.createTextNode('[fieldlist|at:*|repeat:fielditem|]')
|
|
151
151
|
);
|
|
152
152
|
if (len == 0) {
|
|
153
153
|
let node = tpl.querySelector(this.#selector('add'));
|
|
@@ -163,7 +163,7 @@ class HTMLElementFieldsetList extends VirtualHTMLElement {
|
|
|
163
163
|
tpl.appendChild(hidden);
|
|
164
164
|
}
|
|
165
165
|
}
|
|
166
|
-
tpl = tpl.fuse({
|
|
166
|
+
tpl = tpl.fuse({ fieldlist }, scope);
|
|
167
167
|
|
|
168
168
|
const view = this.ownView;
|
|
169
169
|
view.textContent = '';
|
|
@@ -282,4 +282,4 @@ class HTMLElementFieldsetList extends VirtualHTMLElement {
|
|
|
282
282
|
}
|
|
283
283
|
}
|
|
284
284
|
|
|
285
|
-
|
|
285
|
+
Page.define('element-fieldset-list', HTMLElementFieldsetList);
|
package/ui/fieldset.js
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
class
|
|
1
|
+
class HTMLElementFieldSet extends Page.create(HTMLFieldSetElement) {
|
|
2
2
|
static defaults = {
|
|
3
3
|
dataName: null,
|
|
4
4
|
dataValue: null
|
|
5
5
|
};
|
|
6
|
-
constructor() {
|
|
7
|
-
super();
|
|
8
|
-
this.init?.();
|
|
9
|
-
}
|
|
10
6
|
|
|
11
7
|
fill(query) {
|
|
12
8
|
if (this.isContentEditable || !this.options?.name || !this.form) return;
|
|
@@ -36,6 +32,6 @@ class HTMLCustomFieldSetElement extends HTMLFieldSetElement {
|
|
|
36
32
|
}
|
|
37
33
|
}
|
|
38
34
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
35
|
+
|
|
36
|
+
Page.define(`element-fieldset`, HTMLElementFieldSet, 'fieldset');
|
|
37
|
+
|
package/ui/form.js
CHANGED
|
@@ -1,10 +1,99 @@
|
|
|
1
|
-
class
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
class HTMLElementForm extends Page.create(HTMLFormElement) {
|
|
2
|
+
getMethodLater = Page.debounce((e, state) => this.getMethod(e, state), 300);
|
|
3
|
+
|
|
4
|
+
static linearizeValues(query, obj = {}, prefix) {
|
|
5
|
+
if (Array.isArray(query) && query.every(val => {
|
|
6
|
+
return val == null || typeof val != "object";
|
|
7
|
+
})) {
|
|
8
|
+
// do not linearize array-as-value
|
|
9
|
+
obj[prefix] = query;
|
|
10
|
+
} else for (let key of Object.keys(query)) {
|
|
11
|
+
const val = query[key];
|
|
12
|
+
if (prefix) key = prefix + '.' + key;
|
|
13
|
+
if (val === undefined) continue;
|
|
14
|
+
if (val == null) obj[key] = val;
|
|
15
|
+
else if (typeof val == "object") this.linearizeValues(val, obj, key);
|
|
16
|
+
else obj[key] = val;
|
|
17
|
+
}
|
|
18
|
+
return obj;
|
|
5
19
|
}
|
|
6
|
-
|
|
7
|
-
|
|
20
|
+
|
|
21
|
+
static patch(state) {
|
|
22
|
+
state.scope.$filters.form = (ctx, val, action, name) => {
|
|
23
|
+
const form = name
|
|
24
|
+
? document.querySelector(`form[name="${name}"]`)
|
|
25
|
+
: ctx.dest.node.closest('form');
|
|
26
|
+
if (!form) {
|
|
27
|
+
// eslint-disable-next-line no-console
|
|
28
|
+
console.warn("No parent form found");
|
|
29
|
+
return val;
|
|
30
|
+
}
|
|
31
|
+
if (action == "toggle") {
|
|
32
|
+
action = val ? "enable" : "disable";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
state.finish(() => {
|
|
36
|
+
if (action == "enable") {
|
|
37
|
+
form.enable();
|
|
38
|
+
} else if (action == "disable") {
|
|
39
|
+
form.disable();
|
|
40
|
+
} else if (action == "fill") {
|
|
41
|
+
if (val == null) {
|
|
42
|
+
form.reset();
|
|
43
|
+
} else if (typeof val == "object") {
|
|
44
|
+
let values = val;
|
|
45
|
+
if (val.id && val.data) {
|
|
46
|
+
// old way
|
|
47
|
+
values = { ...val.data };
|
|
48
|
+
for (const key of Object.keys(val)) {
|
|
49
|
+
if (key != "data") values['$' + key] = val[key];
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
// new way
|
|
53
|
+
}
|
|
54
|
+
form.fill(this.linearizeValues(values), state.scope);
|
|
55
|
+
form.save();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return val;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
static setup(state) {
|
|
65
|
+
// https://daverupert.com/2017/11/happier-html5-forms/
|
|
66
|
+
state.connect({
|
|
67
|
+
captureBlur: (e, state) => blurHandler(e, false),
|
|
68
|
+
captureInvalid: (e, state) => blurHandler(e, true),
|
|
69
|
+
captureFocus: (e, state) => {
|
|
70
|
+
const el = e.target;
|
|
71
|
+
if (!el.matches || !el.matches('input,textarea,select')) return;
|
|
72
|
+
if (e.relatedTarget?.type == "submit") return;
|
|
73
|
+
updateClass(el.closest('.field') || el, el.validity, true);
|
|
74
|
+
}
|
|
75
|
+
}, document);
|
|
76
|
+
|
|
77
|
+
function updateClass(field, validity, remove) {
|
|
78
|
+
for (const [key, has] of Object.entries(validity)) {
|
|
79
|
+
if (key == "valid") continue;
|
|
80
|
+
field.classList.toggle(key, !remove && has);
|
|
81
|
+
}
|
|
82
|
+
field.classList.toggle('error', !remove && !validity.valid);
|
|
83
|
+
}
|
|
84
|
+
function blurHandler(e, checked) {
|
|
85
|
+
const el = e.target;
|
|
86
|
+
if (!el.matches || !el.matches('input,textarea,select')) return;
|
|
87
|
+
if (!checked) el.checkValidity();
|
|
88
|
+
updateClass(el.closest('.field') || el, el.validity);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
toggleMessages(status) {
|
|
93
|
+
return window.HTMLElementTemplate.prototype.toggleMessages.call(this, status, this);
|
|
94
|
+
}
|
|
95
|
+
getRedirect(status) {
|
|
96
|
+
return window.HTMLElementTemplate.prototype.getRedirect.call(this, status, this);
|
|
8
97
|
}
|
|
9
98
|
patch(state) {
|
|
10
99
|
if (this.isContentEditable) return;
|
|
@@ -73,6 +162,7 @@ class HTMLCustomFormElement extends HTMLFormElement {
|
|
|
73
162
|
if (val == "") val = null;
|
|
74
163
|
let defVal = node.defaultValue;
|
|
75
164
|
if (defVal == "") defVal = null;
|
|
165
|
+
else if (type == "select-one" && defVal === undefined) defVal = null;
|
|
76
166
|
|
|
77
167
|
switch (type) {
|
|
78
168
|
case "file":
|
|
@@ -220,75 +310,66 @@ class HTMLCustomFormElement extends HTMLFormElement {
|
|
|
220
310
|
this.toggleMessages(status);
|
|
221
311
|
});
|
|
222
312
|
}
|
|
223
|
-
postMethod(e, state) {
|
|
313
|
+
async postMethod(e, state) {
|
|
224
314
|
if (e.type != "submit") return;
|
|
225
315
|
const form = this;
|
|
226
|
-
const $query = this.dataset;
|
|
227
|
-
|
|
228
316
|
form.classList.add('loading');
|
|
229
317
|
|
|
230
|
-
|
|
231
|
-
return Promise.all(Array.from(form.elements).filter(node => {
|
|
318
|
+
await Promise.all(Array.from(form.elements).filter(node => {
|
|
232
319
|
return Boolean(node.presubmit);
|
|
233
|
-
}).map(input =>
|
|
234
|
-
return input.presubmit();
|
|
235
|
-
})).then(() => {
|
|
236
|
-
data.$query = state.query;
|
|
237
|
-
data.$request = form.read(true);
|
|
238
|
-
form.disable();
|
|
239
|
-
return Pageboard.fetch(form.method, Page.format({
|
|
240
|
-
pathname: form.getAttribute('action'),
|
|
241
|
-
query: data.$query
|
|
242
|
-
}), data.$request);
|
|
243
|
-
}).catch(err => err).then(res => {
|
|
244
|
-
if (res?.grants) state.data.$grants = res.grants;
|
|
245
|
-
state.scope.$response = res;
|
|
246
|
-
form.enable();
|
|
320
|
+
}).map(input => input.presubmit()));
|
|
247
321
|
|
|
248
|
-
|
|
322
|
+
const { scope } = state;
|
|
323
|
+
scope.$request = form.read(true);
|
|
324
|
+
form.disable();
|
|
249
325
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
326
|
+
const res = await Pageboard.fetch(form.method, Page.format({
|
|
327
|
+
pathname: form.getAttribute('action'),
|
|
328
|
+
query: state.query
|
|
329
|
+
}), scope.$request).catch(err => err);
|
|
254
330
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
redirect = state.toString();
|
|
260
|
-
}
|
|
261
|
-
}
|
|
331
|
+
if (res?.grants) scope.$grants = res.grants;
|
|
332
|
+
scope.$response = res;
|
|
333
|
+
scope.$status = res.status;
|
|
334
|
+
form.enable();
|
|
262
335
|
|
|
263
|
-
|
|
336
|
+
form.classList.remove('loading');
|
|
264
337
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
338
|
+
// messages shown inside form, no navigation
|
|
339
|
+
const hasMsg = form.toggleMessages(res.status);
|
|
340
|
+
const ok = res.status >= 200 && res.status < 300;
|
|
341
|
+
let redirect = form.getRedirect(res.status);
|
|
342
|
+
|
|
343
|
+
if (ok) {
|
|
344
|
+
form.forget();
|
|
345
|
+
form.save();
|
|
346
|
+
if (!redirect && form.closest('element-template') && !hasMsg) {
|
|
347
|
+
redirect = state.toString();
|
|
273
348
|
}
|
|
349
|
+
}
|
|
350
|
+
if (!redirect) {
|
|
351
|
+
if (res.granted) redirect = state.toString();
|
|
352
|
+
else return;
|
|
353
|
+
}
|
|
354
|
+
if (redirect && !ok) {
|
|
355
|
+
form.backup();
|
|
356
|
+
}
|
|
274
357
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
}
|
|
283
|
-
state.data.$vary = vary;
|
|
358
|
+
const loc = Page.parse(redirect).fuse({}, scope);
|
|
359
|
+
let vary = false;
|
|
360
|
+
if (loc.samePathname(state)) {
|
|
361
|
+
if (res.granted) {
|
|
362
|
+
vary = true;
|
|
363
|
+
} else {
|
|
364
|
+
vary = "patch";
|
|
284
365
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
});
|
|
366
|
+
scope.$vary = vary;
|
|
367
|
+
}
|
|
368
|
+
return state.push(loc, { vary });
|
|
289
369
|
}
|
|
290
370
|
}
|
|
291
|
-
window.
|
|
371
|
+
window.HTMLElementForm = HTMLElementForm;
|
|
372
|
+
Page.define(`element-form`, HTMLElementForm, 'form');
|
|
292
373
|
|
|
293
374
|
/* these methods must be available even on non-upgraded elements */
|
|
294
375
|
HTMLFormElement.prototype.enable = function () {
|
|
@@ -305,17 +386,8 @@ HTMLFormElement.prototype.disable = function () {
|
|
|
305
386
|
}
|
|
306
387
|
};
|
|
307
388
|
|
|
308
|
-
Page.ready(() => {
|
|
309
|
-
const Cla = window.customElements.get('element-template');
|
|
310
|
-
HTMLCustomFormElement.prototype.toggleMessages = function (status) {
|
|
311
|
-
return Cla.prototype.toggleMessages.call(this, status, this);
|
|
312
|
-
};
|
|
313
|
-
HTMLCustomFormElement.prototype.getRedirect = function (status) {
|
|
314
|
-
return Cla.prototype.getRedirect.call(this, status, this);
|
|
315
|
-
};
|
|
316
389
|
|
|
317
|
-
|
|
318
|
-
});
|
|
390
|
+
|
|
319
391
|
|
|
320
392
|
HTMLSelectElement.prototype.fill = function (val) {
|
|
321
393
|
if (!Array.isArray(val)) val = [val];
|
|
@@ -335,6 +407,7 @@ HTMLInputElement.prototype.fill = function (val) {
|
|
|
335
407
|
this.checked = true;
|
|
336
408
|
} else {
|
|
337
409
|
this.checked = (Array.isArray(val) ? val : [val]).some(str => {
|
|
410
|
+
if (str == false && this.value == "") return true;
|
|
338
411
|
return str.toString() == this.value;
|
|
339
412
|
});
|
|
340
413
|
}
|
|
@@ -396,91 +469,3 @@ Object.defineProperty(HTMLInputElement.prototype, 'defaultValue', {
|
|
|
396
469
|
}
|
|
397
470
|
});
|
|
398
471
|
|
|
399
|
-
Page.setup(state => {
|
|
400
|
-
// https://daverupert.com/2017/11/happier-html5-forms/
|
|
401
|
-
Page.connect({
|
|
402
|
-
captureBlur: (e, state) => blurHandler(e, false),
|
|
403
|
-
captureInvalid: (e, state) => blurHandler(e, true),
|
|
404
|
-
captureFocus: (e, state) => {
|
|
405
|
-
const el = e.target;
|
|
406
|
-
if (!el.matches || !el.matches('input,textarea,select')) return;
|
|
407
|
-
if (e.relatedTarget?.type == "submit") return;
|
|
408
|
-
updateClass(el.closest('.field') || el, el.validity, true);
|
|
409
|
-
}
|
|
410
|
-
}, document);
|
|
411
|
-
|
|
412
|
-
function updateClass(field, validity, remove) {
|
|
413
|
-
for (const [key, has] of Object.entries(validity)) {
|
|
414
|
-
if (key == "valid") continue;
|
|
415
|
-
field.classList.toggle(key, !remove && has);
|
|
416
|
-
}
|
|
417
|
-
field.classList.toggle('error', !remove && !validity.valid);
|
|
418
|
-
}
|
|
419
|
-
function blurHandler(e, checked) {
|
|
420
|
-
const el = e.target;
|
|
421
|
-
if (!el.matches || !el.matches('input,textarea,select')) return;
|
|
422
|
-
if (!checked) el.checkValidity();
|
|
423
|
-
updateClass(el.closest('.field') || el, el.validity);
|
|
424
|
-
}
|
|
425
|
-
});
|
|
426
|
-
|
|
427
|
-
Page.patch(state => {
|
|
428
|
-
const filters = state.scope.$filters;
|
|
429
|
-
|
|
430
|
-
function linearizeValues(query, obj = {}, prefix) {
|
|
431
|
-
if (Array.isArray(query) && query.every(val => {
|
|
432
|
-
return val == null || typeof val != "object";
|
|
433
|
-
})) {
|
|
434
|
-
// do not linearize array-as-value
|
|
435
|
-
obj[prefix] = query;
|
|
436
|
-
} else for (let key of Object.keys(query)) {
|
|
437
|
-
const val = query[key];
|
|
438
|
-
if (prefix) key = prefix + '.' + key;
|
|
439
|
-
if (val === undefined) continue;
|
|
440
|
-
if (val == null) obj[key] = val;
|
|
441
|
-
else if (typeof val == "object") linearizeValues(val, obj, key);
|
|
442
|
-
else obj[key] = val;
|
|
443
|
-
}
|
|
444
|
-
return obj;
|
|
445
|
-
}
|
|
446
|
-
filters.form = function (val, what, action, name) {
|
|
447
|
-
const form = name
|
|
448
|
-
? document.querySelector(`form[name="${name}"]`)
|
|
449
|
-
: what.parent.closest('form');
|
|
450
|
-
if (!form) {
|
|
451
|
-
// eslint-disable-next-line no-console
|
|
452
|
-
console.warn("No parent form found");
|
|
453
|
-
return val;
|
|
454
|
-
}
|
|
455
|
-
if (action == "toggle") {
|
|
456
|
-
action = val ? "enable" : "disable";
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
state.finish(() => {
|
|
460
|
-
if (action == "enable") {
|
|
461
|
-
form.enable();
|
|
462
|
-
} else if (action == "disable") {
|
|
463
|
-
form.disable();
|
|
464
|
-
} else if (action == "fill") {
|
|
465
|
-
if (val == null) {
|
|
466
|
-
form.reset();
|
|
467
|
-
} else if (typeof val == "object") {
|
|
468
|
-
let values = val;
|
|
469
|
-
if (val.id && val.data) {
|
|
470
|
-
// old way
|
|
471
|
-
values = { ...val.data };
|
|
472
|
-
for (const key of Object.keys(val)) {
|
|
473
|
-
if (key != "data") values['$' + key] = val[key];
|
|
474
|
-
}
|
|
475
|
-
} else {
|
|
476
|
-
// new way
|
|
477
|
-
}
|
|
478
|
-
form.fill(linearizeValues(values), state.scope);
|
|
479
|
-
form.save();
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
});
|
|
483
|
-
|
|
484
|
-
return val;
|
|
485
|
-
};
|
|
486
|
-
});
|
package/ui/heading-helper.js
CHANGED
|
@@ -1,17 +1,11 @@
|
|
|
1
|
-
class HTMLElementHeadingHelper extends HTMLHeadingElement {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
if (this.init) this.init();
|
|
5
|
-
}
|
|
6
|
-
init() {
|
|
7
|
-
this.willSync = Pageboard.debounce(this.sync, 100);
|
|
1
|
+
class HTMLElementHeadingHelper extends Page.create(HTMLHeadingElement) {
|
|
2
|
+
setup(state) {
|
|
3
|
+
this.willSync = state.debounce(() => this.sync(), 100);
|
|
8
4
|
this.observer = new MutationObserver(records => {
|
|
9
5
|
if (records.some(mut => {
|
|
10
6
|
return mut.type == "characterData" || mut.type == "childList" && mut.addedNodes.length;
|
|
11
|
-
}))
|
|
7
|
+
})) this.willSync();
|
|
12
8
|
});
|
|
13
|
-
}
|
|
14
|
-
setup() {
|
|
15
9
|
this.observer.observe(this, {
|
|
16
10
|
childList: true,
|
|
17
11
|
subtree: true,
|
|
@@ -32,13 +26,13 @@ class HTMLElementHeadingHelper extends HTMLHeadingElement {
|
|
|
32
26
|
}
|
|
33
27
|
}
|
|
34
28
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
29
|
+
|
|
30
|
+
for (let i = 1; i <= 6; i++) {
|
|
31
|
+
Page.define(
|
|
32
|
+
`h${i}-helper`,
|
|
33
|
+
class extends HTMLElementHeadingHelper { },
|
|
34
|
+
`h${i}`
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
44
38
|
|