@optionfactory/ful 0.19.0 → 0.20.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/dist/ful-client-errors.iife.min.js.map +1 -1
- package/dist/ful.css +2 -0
- package/dist/ful.css.map +1 -0
- package/dist/ful.iife.js +508 -190
- package/dist/ful.iife.js.map +1 -1
- package/dist/ful.iife.min.js +1 -1
- package/dist/ful.iife.min.js.map +1 -1
- package/dist/ful.min.mjs +1 -1
- package/dist/ful.min.mjs.map +1 -1
- package/dist/ful.mjs +502 -190
- package/dist/ful.mjs.map +1 -1
- package/package.json +9 -5
package/dist/ful.iife.js
CHANGED
|
@@ -1,105 +1,6 @@
|
|
|
1
1
|
var ful = (function (exports) {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
/* global CSS */
|
|
5
|
-
|
|
6
|
-
function extract(extractors, el) {
|
|
7
|
-
const maybeExtractor = extractors[el.dataset['bindExtractor']] || extractors[el.dataset['bindProvide']];
|
|
8
|
-
if (maybeExtractor) {
|
|
9
|
-
return maybeExtractor(el);
|
|
10
|
-
}
|
|
11
|
-
if (el.getAttribute('type') === 'radio') {
|
|
12
|
-
if (!el.checked) {
|
|
13
|
-
return undefined;
|
|
14
|
-
}
|
|
15
|
-
return el.dataset['bindType'] === 'boolean' ? el.value === 'true' : el.value;
|
|
16
|
-
}
|
|
17
|
-
if (el.getAttribute('type') === 'checkbox') {
|
|
18
|
-
return el.checked;
|
|
19
|
-
}
|
|
20
|
-
if (el.dataset['bindType'] === 'boolean') {
|
|
21
|
-
return !el.value ? null : el.value === 'true';
|
|
22
|
-
}
|
|
23
|
-
return el.value || null;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function mutate(mutators, el, raw, key, values) {
|
|
27
|
-
const maybeMutator = mutators[el.dataset['bindMutator']] || mutators[el.dataset['bindProvide']];
|
|
28
|
-
if (maybeMutator) {
|
|
29
|
-
maybeMutator(el, raw, key, values);
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
if (el.getAttribute('type') === 'radio') {
|
|
33
|
-
el.checked = el.getAttribute('value') === raw;
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
if (el.getAttribute('type') === 'checkbox') {
|
|
37
|
-
el.checked = raw;
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
el.value = raw;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
function providePath(result, path, value) {
|
|
45
|
-
const keys = path.split(".").map((k) => k.match(/^[0-9]+$/) ? +k : k);
|
|
46
|
-
let current = result;
|
|
47
|
-
let previous = null;
|
|
48
|
-
for (let i = 0; ; ++i) {
|
|
49
|
-
const ckey = keys[i];
|
|
50
|
-
const pkey = keys[i - 1];
|
|
51
|
-
if (Number.isInteger(ckey) && !Array.isArray(current)) {
|
|
52
|
-
if (previous !== null) {
|
|
53
|
-
previous[pkey] = current = [];
|
|
54
|
-
} else {
|
|
55
|
-
result = current = [];
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
if (i === keys.length - 1) {
|
|
59
|
-
//when value is undefined we only want to define the property if it's not defined
|
|
60
|
-
current[ckey] = value !== undefined ? value : (ckey in current ? current[ckey] : null);
|
|
61
|
-
return result;
|
|
62
|
-
}
|
|
63
|
-
if (current[ckey] === undefined) {
|
|
64
|
-
current[ckey] = {};
|
|
65
|
-
}
|
|
66
|
-
previous = current;
|
|
67
|
-
current = current[ckey];
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
class Bindings {
|
|
72
|
-
|
|
73
|
-
constructor( {extractors, mutators, ignoredChildrenSelector, valueHoldersSelector}) {
|
|
74
|
-
this.extractors = extractors || {};
|
|
75
|
-
this.mutators = mutators || {};
|
|
76
|
-
this.valueHoldersSelector = valueHoldersSelector || 'input[name], select[name], textarea[name]';
|
|
77
|
-
this.ignoredChildrenSelector = ignoredChildrenSelector || '.d-none';
|
|
78
|
-
}
|
|
79
|
-
setValues(el, values) {
|
|
80
|
-
for (let k in values) {
|
|
81
|
-
if (!values.hasOwnProperty(k)) {
|
|
82
|
-
continue;
|
|
83
|
-
}
|
|
84
|
-
Array.from(el.querySelectorAll(`[name='${CSS.escape(k)}']`)).forEach((el) => {
|
|
85
|
-
mutate(this.mutators, el, values[k], k, values);
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
getValues(el) {
|
|
90
|
-
return Array.from(el.querySelectorAll(this.valueHoldersSelector))
|
|
91
|
-
.filter((el) => {
|
|
92
|
-
if (el.dataset['bindInclude'] === 'never') {
|
|
93
|
-
return false;
|
|
94
|
-
}
|
|
95
|
-
return el.dataset['bindInclude'] === 'always' || el.closest(this.ignoredChildrenSelector) === null;
|
|
96
|
-
})
|
|
97
|
-
.reduce((result, el) => {
|
|
98
|
-
return providePath(result, el.getAttribute('name'), extract(this.extractors, el));
|
|
99
|
-
}, {});
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
4
|
class Base64 {
|
|
104
5
|
static encode(arrayBuffer, dialect) {
|
|
105
6
|
const d = dialect || Base64.URL_SAFE;
|
|
@@ -172,69 +73,44 @@ var ful = (function (exports) {
|
|
|
172
73
|
}
|
|
173
74
|
}
|
|
174
75
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
class Form {
|
|
179
|
-
constructor(el, bindings, {globalErrorsEl, fieldContainerSelector, errorClass, hideClass}) {
|
|
180
|
-
this.el = el;
|
|
181
|
-
this.bindings = bindings;
|
|
182
|
-
this.globalErrorsEl = globalErrorsEl;
|
|
183
|
-
this.fieldContainerSelector = fieldContainerSelector !== undefined ? fieldContainerSelector : Form.DEFAULT_FIELD_CONTAINER_SELECTOR;
|
|
184
|
-
this.errorClass = errorClass || Form.DEFAULT_ERROR_CLASS;
|
|
185
|
-
this.hideClass = hideClass || Form.DEFAULT_HIDE_CLASS;
|
|
186
|
-
}
|
|
187
|
-
setValues(values) {
|
|
188
|
-
return this.bindings.setValues(this.el, values);
|
|
189
|
-
}
|
|
190
|
-
getValues() {
|
|
191
|
-
return this.bindings.getValues(this.el);
|
|
76
|
+
class Observable {
|
|
77
|
+
constructor() {
|
|
78
|
+
this.listeners = {};
|
|
192
79
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
.filter((e) => e.type === 'FIELD_ERROR' || e.type === 'INVALID_FORMAT')
|
|
199
|
-
.forEach((e) => {
|
|
200
|
-
const name = e.context.replace("[", ".").replace("].", ".");
|
|
201
|
-
Array.from(this.el.querySelectorAll(`[name='${CSS.escape(name)}']`))
|
|
202
|
-
.map(el => this.fieldContainerSelector ? el.closest(this.fieldContainerSelector) : el)
|
|
203
|
-
.filter(el => el !== null)
|
|
204
|
-
.forEach(label => {
|
|
205
|
-
label.classList.add(this.errorClass);
|
|
206
|
-
label.dataset['error'] = e.reason;
|
|
207
|
-
});
|
|
208
|
-
});
|
|
209
|
-
if (this.globalErrorsEl) {
|
|
210
|
-
const globalErrors = errors.filter((e) => e.type !== 'FIELD_ERROR' && e.type !== 'INVALID_FORMAT');
|
|
211
|
-
this.globalErrorsEl.innerHTML = globalErrors.map(e => e.reason).join("\n");
|
|
212
|
-
if (globalErrors.length !== 0) {
|
|
213
|
-
this.globalErrorsEl.classList.remove(this.hideClass);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
if (!scrollFirstErrorIntoView) {
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
const yOffsets = Array.from(this.el.querySelectorAll('.${CSS.escape(this.errorClass)}'))
|
|
220
|
-
.map((label) => label.getBoundingClientRect().y + window.scrollY);
|
|
221
|
-
const firstErrorScrollY = Math.min(...yOffsets);
|
|
222
|
-
if (firstErrorScrollY !== Infinity) {
|
|
223
|
-
window.scroll(window.scrollX, firstErrorScrollY > 100 ? firstErrorScrollY - 100 : 0);
|
|
80
|
+
fireSync(event, data, initialAcc) {
|
|
81
|
+
const listeners = this.listeners[event] || [];
|
|
82
|
+
let acc = initialAcc;
|
|
83
|
+
for (const l of listeners) {
|
|
84
|
+
acc = l(data, this, acc);
|
|
224
85
|
}
|
|
86
|
+
return acc;
|
|
225
87
|
}
|
|
226
|
-
|
|
227
|
-
this.
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
88
|
+
async fire(event, data, initialAcc) {
|
|
89
|
+
const listeners = this.listeners[event] || [];
|
|
90
|
+
let acc = initialAcc;
|
|
91
|
+
for (const l of listeners) {
|
|
92
|
+
acc = await l(data, this, acc);
|
|
231
93
|
}
|
|
94
|
+
return acc;
|
|
95
|
+
}
|
|
96
|
+
on(event, listener) {
|
|
97
|
+
this.listeners[event] = this.listeners[event] || [];
|
|
98
|
+
this.listeners[event].push(listener);
|
|
99
|
+
}
|
|
100
|
+
un(event, listener) {
|
|
101
|
+
const listeners = this.listeners[event] || [];
|
|
102
|
+
const idx = listeners.indexOf(listener);
|
|
103
|
+
return idx === -1 ? [] : listeners.splice(idx, 1);
|
|
104
|
+
}
|
|
105
|
+
static mixin(self) {
|
|
106
|
+
self.listeners = {};
|
|
107
|
+
self.fireSync = Observable.prototype.fireSync;
|
|
108
|
+
self.fire = Observable.prototype.fire;
|
|
109
|
+
self.on = Observable.prototype.on;
|
|
110
|
+
self.un = Observable.prototype.un;
|
|
232
111
|
}
|
|
233
|
-
}
|
|
234
112
|
|
|
235
|
-
|
|
236
|
-
Form.DEFAULT_ERROR_CLASS = 'has-error';
|
|
237
|
-
Form.DEFAULT_HIDE_CLASS = 'd-none';
|
|
113
|
+
}
|
|
238
114
|
|
|
239
115
|
class ContextInterceptor {
|
|
240
116
|
constructor() {
|
|
@@ -376,30 +252,456 @@ var ful = (function (exports) {
|
|
|
376
252
|
}]);
|
|
377
253
|
}
|
|
378
254
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/* global Infinity, CSS */
|
|
258
|
+
|
|
259
|
+
class CustomElements {
|
|
260
|
+
static id = 0;
|
|
261
|
+
static uid(prefix) {
|
|
262
|
+
return `${prefix}-${++CustomElements.id}`;
|
|
263
|
+
}
|
|
264
|
+
static forwardAttributes(from, to, except) {
|
|
265
|
+
from.getAttributeNames().filter(a => except.indexOf(a) === -1)
|
|
266
|
+
.filter(a => a[0] === '@')
|
|
267
|
+
.forEach(a => {
|
|
268
|
+
if (a === '@class') {
|
|
269
|
+
to.classList.add(...from.getAttribute("@class").split(" ").filter(a => a.length));
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
to.setAttribute(a.substring(1), from.getAttribute(a));
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
static extractSlots(el) {
|
|
276
|
+
const slotted = Object.fromEntries([...el.querySelectorAll("[slot]")].map(el => {
|
|
277
|
+
el.parentElement.removeChild(el);
|
|
278
|
+
const slot = el.getAttribute("slot");
|
|
279
|
+
el.removeAttribute("slot");
|
|
280
|
+
return [slot, el];
|
|
281
|
+
}));
|
|
282
|
+
slotted.default = new DocumentFragment();
|
|
283
|
+
slotted.default.append(...el.childNodes);
|
|
284
|
+
return slotted;
|
|
285
|
+
}
|
|
286
|
+
static labelAndInputGroup(id, name, isFloating, slotted) {
|
|
287
|
+
if (isFloating) {
|
|
288
|
+
/**
|
|
289
|
+
* <div class="input-group has-validation">
|
|
290
|
+
* <span data-tpl-if="slotted.before" class="input-group-text">{{{{ slotted.before }}}}</span>
|
|
291
|
+
* <div class="form-floating">
|
|
292
|
+
* {{{{ slotted.input }}}}
|
|
293
|
+
* <label data-tpl-for="name" class="form-label">{{{{ slotted.default }}}}</label>
|
|
294
|
+
* </div>
|
|
295
|
+
* <span data-tpl-if="slotted.after" class="input-group-text">{{{{ slotted.after }}}}</span>
|
|
296
|
+
* <ful-field-error data-tpl-field="name"></ful-field-error>
|
|
297
|
+
* </div>
|
|
298
|
+
*/
|
|
299
|
+
const label = document.createElement("label");
|
|
300
|
+
label.setAttribute("for", id);
|
|
301
|
+
label.classList.add('form-label');
|
|
302
|
+
label.append(slotted.default);
|
|
303
|
+
|
|
304
|
+
const ff = document.createElement('div');
|
|
305
|
+
ff.classList.add("form-floating");
|
|
306
|
+
ff.append(slotted.input, label);
|
|
307
|
+
|
|
308
|
+
const ffe = document.createElement('ful-field-error');
|
|
309
|
+
ffe.setAttribute("field", name);
|
|
310
|
+
|
|
311
|
+
const ig = document.createElement("div");
|
|
312
|
+
ig.classList.add('input-group', 'has-validtion');
|
|
313
|
+
|
|
314
|
+
if (slotted.before) {
|
|
315
|
+
ig.append(slotted.before);
|
|
316
|
+
} else if (slotted.ibefore) {
|
|
317
|
+
const igt = document.createElement('div');
|
|
318
|
+
igt.classList.add('input-group-text');
|
|
319
|
+
igt.append(slotted.ibefore);
|
|
320
|
+
ig.append(igt);
|
|
321
|
+
}
|
|
322
|
+
ig.append(ff);
|
|
323
|
+
if (slotted.after) {
|
|
324
|
+
ig.append(slotted.after);
|
|
325
|
+
} else if (slotted.iafter) {
|
|
326
|
+
const igt = document.createElement('div');
|
|
327
|
+
igt.classList.add('input-group-text');
|
|
328
|
+
igt.append(slotted.iafter);
|
|
329
|
+
ig.append(igt);
|
|
330
|
+
}
|
|
331
|
+
ig.append(ffe);
|
|
332
|
+
return ig;
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
<label data-tpl-for="name" class="form-label">{{{{ slotted.default }}}}</label>
|
|
336
|
+
<div class="input-group has-validation">
|
|
337
|
+
<span data-tpl-if="slotted.before" class="input-group-text">{{{{ slotted.before }}}}</span>
|
|
338
|
+
{{{{ slotted.input }}}}
|
|
339
|
+
<span data-tpl-if="slotted.after" class="input-group-text">{{{{ slotted.after }}}}</span>
|
|
340
|
+
<ful-field-error data-tpl-field="name"></ful-field-error>
|
|
341
|
+
</div>
|
|
342
|
+
*/
|
|
343
|
+
|
|
344
|
+
const label = document.createElement("label");
|
|
345
|
+
label.setAttribute("for", name);
|
|
346
|
+
label.classList.add('form-label');
|
|
347
|
+
label.append(slotted.default);
|
|
348
|
+
|
|
349
|
+
const ffe = document.createElement('ful-field-error');
|
|
350
|
+
ffe.setAttribute("field", name);
|
|
351
|
+
|
|
352
|
+
const ig = document.createElement("div");
|
|
353
|
+
ig.classList.add('input-group', 'has-validation');
|
|
354
|
+
|
|
355
|
+
if (slotted.before) {
|
|
356
|
+
ig.append(slotted.before);
|
|
357
|
+
} else if (slotted.ibefore) {
|
|
358
|
+
const igt = document.createElement('div');
|
|
359
|
+
igt.classList.add('input-group-text');
|
|
360
|
+
igt.append(slotted.ibefore);
|
|
361
|
+
ig.append(igt);
|
|
362
|
+
}
|
|
363
|
+
ig.append(slotted.input);
|
|
364
|
+
if (slotted.after) {
|
|
365
|
+
ig.append(slotted.after);
|
|
366
|
+
} else if (slotted.iafter) {
|
|
367
|
+
const igt = document.createElement('div');
|
|
368
|
+
igt.classList.add('input-group-text');
|
|
369
|
+
igt.append(slotted.iafter);
|
|
370
|
+
ig.append(igt);
|
|
371
|
+
}
|
|
372
|
+
ig.append(ffe);
|
|
373
|
+
|
|
374
|
+
const fragment = new DocumentFragment();
|
|
375
|
+
fragment.append(label, ig);
|
|
376
|
+
return fragment;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
class FieldError extends HTMLElement {
|
|
383
|
+
constructor() {
|
|
384
|
+
super();
|
|
385
|
+
}
|
|
386
|
+
connectedCallback() {
|
|
387
|
+
this.classList.add('invalid-feedback');
|
|
388
|
+
}
|
|
389
|
+
static configure() {
|
|
390
|
+
customElements.define('ful-field-error', FieldError);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
class Errors extends HTMLElement {
|
|
395
|
+
constructor() {
|
|
396
|
+
super();
|
|
397
|
+
}
|
|
398
|
+
connectedCallback() {
|
|
399
|
+
this.classList.add('alert', 'alert-danger', 'd-none');
|
|
400
|
+
}
|
|
401
|
+
static configure() {
|
|
402
|
+
customElements.define('ful-errors', Errors);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
class Spinner extends HTMLElement {
|
|
408
|
+
constructor() {
|
|
409
|
+
super();
|
|
410
|
+
}
|
|
411
|
+
connectedCallback() {
|
|
412
|
+
this.classList.add('spinner-border', 'spinner-border-sm', 'd-none');
|
|
413
|
+
this.setAttribute("aria-hidden", "true");
|
|
414
|
+
}
|
|
415
|
+
show() {
|
|
416
|
+
this.classList.remove("d-none");
|
|
417
|
+
}
|
|
418
|
+
hide() {
|
|
419
|
+
this.classList.add("d-none");
|
|
420
|
+
}
|
|
421
|
+
static configure() {
|
|
422
|
+
customElements.define('ful-spinner', Spinner);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
class Input extends HTMLElement {
|
|
429
|
+
constructor() {
|
|
430
|
+
super();
|
|
431
|
+
const id = CustomElements.uid('ful-input');
|
|
432
|
+
const name = this.getAttribute('@name');
|
|
433
|
+
const floating = this.hasAttribute('@floating');
|
|
434
|
+
const slotted = CustomElements.extractSlots(this);
|
|
435
|
+
slotted.input = slotted.input || (() => {
|
|
436
|
+
const el = document.createElement("input");
|
|
437
|
+
el.classList.add("form-control");
|
|
438
|
+
return el;
|
|
439
|
+
})();
|
|
440
|
+
CustomElements.forwardAttributes(this, slotted.input, ['@floating']);
|
|
441
|
+
const attrIfMissing = (el, k, v) => !el.hasAttribute(k) && el.setAttribute(k, v);
|
|
442
|
+
attrIfMissing(slotted.input, "name", id);
|
|
443
|
+
attrIfMissing(slotted.input, "id", id);
|
|
444
|
+
attrIfMissing(slotted.input, "type", "text");
|
|
445
|
+
attrIfMissing(slotted.input, "placeholder", " ");
|
|
446
|
+
this.innerHTML = '';
|
|
447
|
+
this.append(CustomElements.labelAndInputGroup(id, name || id, floating, slotted));
|
|
448
|
+
}
|
|
449
|
+
static configure() {
|
|
450
|
+
customElements.define('ful-input', Input);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* <script src="tom-select.complete.js"></script>
|
|
458
|
+
* <link href="tom-select.bootstrap5.css" rel="stylesheet" />
|
|
459
|
+
*/
|
|
460
|
+
class Select extends HTMLElement {
|
|
461
|
+
constructor(tsConfig) {
|
|
462
|
+
super();
|
|
463
|
+
Observable.mixin(this);
|
|
464
|
+
const id = CustomElements.uid('ful-select');
|
|
465
|
+
const name = this.getAttribute('@name');
|
|
466
|
+
const floating = this.hasAttribute('@floating');
|
|
467
|
+
const remote = this.hasAttribute('@remote');
|
|
468
|
+
const slotted = CustomElements.extractSlots(this);
|
|
469
|
+
slotted.input = slotted.input || (() => {
|
|
470
|
+
return document.createElement("select");
|
|
471
|
+
})();
|
|
472
|
+
CustomElements.forwardAttributes(this, slotted.input, ['@floating', '@remote']);
|
|
473
|
+
const attrIfMissing = (el, k, v) => !el.hasAttribute(k) && el.setAttribute(k, v);
|
|
474
|
+
attrIfMissing(slotted.input, "name", id);
|
|
475
|
+
attrIfMissing(slotted.input, "id", id);
|
|
476
|
+
attrIfMissing(slotted.input, "placeholder", " ");
|
|
477
|
+
this.innerHTML = '';
|
|
478
|
+
this.append(CustomElements.labelAndInputGroup(id, name || id, floating, slotted));
|
|
479
|
+
this.loaded = !remote;
|
|
480
|
+
this.ts = new TomSelect(slotted.input, Object.assign(remote ? {
|
|
481
|
+
preload: 'focus',
|
|
482
|
+
load: async (query, callback) => {
|
|
483
|
+
if (this.loaded) {
|
|
484
|
+
callback();
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
const data = await this.fire('load', query, []);
|
|
488
|
+
this.loaded = true;
|
|
489
|
+
callback(data);
|
|
490
|
+
}
|
|
491
|
+
} : {}, tsConfig));
|
|
492
|
+
slotted.input.setValue = this.setValue.bind(this);
|
|
493
|
+
slotted.input.getValue = this.getValue.bind(this);
|
|
494
|
+
}
|
|
495
|
+
async setValue(v){
|
|
496
|
+
if(!this.loaded){
|
|
497
|
+
await this.ts.load();
|
|
498
|
+
}
|
|
499
|
+
this.ts.setValue(v);
|
|
500
|
+
}
|
|
501
|
+
getValue(){
|
|
502
|
+
const v = this.ts.getValue();
|
|
503
|
+
return v === '' ? null : v;
|
|
504
|
+
}
|
|
505
|
+
static custom(tagName, configuration) {
|
|
506
|
+
customElements.define(tagName, class extends Select {
|
|
507
|
+
constructor() {
|
|
508
|
+
super(configuration);
|
|
386
509
|
}
|
|
387
510
|
});
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
511
|
+
}
|
|
512
|
+
static configure() {
|
|
513
|
+
return Select.custom('ful-select');
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
class Form extends HTMLElement {
|
|
519
|
+
constructor({ mutators, extractors, valueHoldersSelector, ignoredChildrenSelector }) {
|
|
520
|
+
super();
|
|
521
|
+
Observable.mixin(this);
|
|
522
|
+
this.mutators = mutators || {};
|
|
523
|
+
this.extractors = extractors || {};
|
|
524
|
+
this.valueHoldersSelector = valueHoldersSelector || '[name]';
|
|
525
|
+
this.ignoredChildrenSelector = ignoredChildrenSelector || '.d-none';
|
|
526
|
+
|
|
527
|
+
const form = document.createElement('form');
|
|
528
|
+
form.append(...this.childNodes);
|
|
529
|
+
this.appendChild(form);
|
|
530
|
+
|
|
531
|
+
form.addEventListener('submit', async (e) => {
|
|
532
|
+
e.preventDefault();
|
|
533
|
+
this.spinner(true);
|
|
534
|
+
try {
|
|
535
|
+
await this.fire('submit', this.getValues(), this);
|
|
536
|
+
} catch (e) {
|
|
537
|
+
if (e instanceof Failure) {
|
|
538
|
+
this.setErrors(e.problems);
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
throw e;
|
|
542
|
+
} finally {
|
|
543
|
+
this.spinner(false);
|
|
544
|
+
}
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
spinner(spin) {
|
|
548
|
+
this.querySelectorAll('ful-spinner').forEach(el => {
|
|
549
|
+
el[spin ? 'show' : 'hide']();
|
|
550
|
+
});
|
|
551
|
+
this.querySelectorAll('[type=submit],[type=reset]').forEach(el => {
|
|
552
|
+
el.disabled = spin;
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
setValues(values) {
|
|
556
|
+
for (let k in values) {
|
|
557
|
+
if (!values.hasOwnProperty(k)) {
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
560
|
+
Array.from(this.querySelectorAll(`[name='${CSS.escape(k)}']`)).forEach((el) => {
|
|
561
|
+
Form.mutate(this.mutators, el, values[k], k, values);
|
|
400
562
|
});
|
|
401
563
|
}
|
|
402
564
|
}
|
|
565
|
+
getValues() {
|
|
566
|
+
return Array.from(this.querySelectorAll(this.valueHoldersSelector))
|
|
567
|
+
.filter((el) => {
|
|
568
|
+
if (el.dataset['fulBindInclude'] === 'never') {
|
|
569
|
+
return false;
|
|
570
|
+
}
|
|
571
|
+
return el.dataset['fulBindInclude'] === 'always' || el.closest(this.ignoredChildrenSelector) === null;
|
|
572
|
+
})
|
|
573
|
+
.reduce((result, el) => {
|
|
574
|
+
return Form.providePath(result, el.getAttribute('name'), Form.extract(this.extractors, el));
|
|
575
|
+
}, {});
|
|
576
|
+
}
|
|
577
|
+
setErrors(errors, scroll) {
|
|
578
|
+
this.clearErrors();
|
|
579
|
+
errors
|
|
580
|
+
.filter((e) => e.type === 'FIELD_ERROR' || e.type === 'INVALID_FORMAT')
|
|
581
|
+
.forEach((e) => {
|
|
582
|
+
const name = e.context.replace("[", ".").replace("].", ".");
|
|
583
|
+
this.querySelectorAll(`[name='${CSS.escape(name)}']`)
|
|
584
|
+
.forEach(input => {
|
|
585
|
+
input.classList.add('is-invalid');
|
|
586
|
+
if (input.parentElement.classList.contains("form-floating")) {
|
|
587
|
+
input.parentElement.classList.add('is-invalid');
|
|
588
|
+
}
|
|
589
|
+
});
|
|
590
|
+
this.querySelectorAll(`ful-field-error[field='${CSS.escape(name)}']`)
|
|
591
|
+
.forEach(el => el.innerText = e.reason);
|
|
592
|
+
});
|
|
593
|
+
this.querySelectorAll("ful-errors")
|
|
594
|
+
.forEach(el => {
|
|
595
|
+
const globalErrors = errors.filter((e) => e.type !== 'FIELD_ERROR' && e.type !== 'INVALID_FORMAT');
|
|
596
|
+
el.innerHTML = globalErrors.map(e => e.reason).join("\n");
|
|
597
|
+
if (globalErrors.length !== 0) {
|
|
598
|
+
el.classList.remove('d-none');
|
|
599
|
+
}
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
if (!scroll) {
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
const ys = Array.from(this.querySelectorAll('ful-field-error:not(.d-none)'))
|
|
606
|
+
.map(el => el.getBoundingClientRect().y + window.scrollY);
|
|
607
|
+
const miny = Math.min(...ys);
|
|
608
|
+
if (miny !== Infinity) {
|
|
609
|
+
window.scroll(window.scrollX, miny > 100 ? miny - 100 : 0);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
clearErrors() {
|
|
613
|
+
this.querySelectorAll('[name].is-invalid, .form-floating.is-invalid')
|
|
614
|
+
.forEach(el => el.classList.remove('is-invalid'));
|
|
615
|
+
this.querySelectorAll("ful-errors")
|
|
616
|
+
.forEach(el => {
|
|
617
|
+
el.innerHTML = '';
|
|
618
|
+
el.classList.add('d-none');
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
static extract(extractors, el) {
|
|
622
|
+
const maybeExtractor = extractors[el.dataset['fulBindExtractor']] || extractors[el.dataset['fulBindProvide']];
|
|
623
|
+
if (maybeExtractor) {
|
|
624
|
+
return maybeExtractor(el);
|
|
625
|
+
}
|
|
626
|
+
if (el.getAttribute('type') === 'radio') {
|
|
627
|
+
if (!el.checked) {
|
|
628
|
+
return undefined;
|
|
629
|
+
}
|
|
630
|
+
return el.dataset['fulBindType'] === 'boolean' ? el.value === 'true' : el.value;
|
|
631
|
+
}
|
|
632
|
+
if (el.getAttribute('type') === 'checkbox') {
|
|
633
|
+
return el.checked;
|
|
634
|
+
}
|
|
635
|
+
if (el.dataset['fulBindType'] === 'boolean') {
|
|
636
|
+
return !el.value ? null : el.value === 'true';
|
|
637
|
+
}
|
|
638
|
+
if (el.getValue) {
|
|
639
|
+
return el.getValue();
|
|
640
|
+
}
|
|
641
|
+
return el.value || null;
|
|
642
|
+
}
|
|
643
|
+
static mutate(mutators, el, raw, key, values) {
|
|
644
|
+
const maybeMutator = mutators[el.dataset['fulBindMutator']] || mutators[el.dataset['fulBindProvide']];
|
|
645
|
+
if (maybeMutator) {
|
|
646
|
+
maybeMutator(el, raw, key, values);
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
if (el.getAttribute('type') === 'radio') {
|
|
650
|
+
el.checked = el.getAttribute('value') === raw;
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
if (el.getAttribute('type') === 'checkbox') {
|
|
654
|
+
el.checked = raw;
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
if (el.setValue) {
|
|
658
|
+
el.setValue(raw);
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
el.value = raw;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
static providePath(result, path, value) {
|
|
665
|
+
const keys = path.split(".").map((k) => k.match(/^[0-9]+$/) ? +k : k);
|
|
666
|
+
let current = result;
|
|
667
|
+
let previous = null;
|
|
668
|
+
for (let i = 0; ; ++i) {
|
|
669
|
+
const ckey = keys[i];
|
|
670
|
+
const pkey = keys[i - 1];
|
|
671
|
+
if (Number.isInteger(ckey) && !Array.isArray(current)) {
|
|
672
|
+
if (previous !== null) {
|
|
673
|
+
previous[pkey] = current = [];
|
|
674
|
+
} else {
|
|
675
|
+
result = current = [];
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
if (i === keys.length - 1) {
|
|
679
|
+
//when value is undefined we only want to define the property if it's not defined
|
|
680
|
+
current[ckey] = value !== undefined ? value : (ckey in current ? current[ckey] : null);
|
|
681
|
+
return result;
|
|
682
|
+
}
|
|
683
|
+
if (current[ckey] === undefined) {
|
|
684
|
+
current[ckey] = {};
|
|
685
|
+
}
|
|
686
|
+
previous = current;
|
|
687
|
+
current = current[ckey];
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
static custom(tagName, configuration) {
|
|
691
|
+
customElements.define(tagName, class extends Form {
|
|
692
|
+
constructor() {
|
|
693
|
+
super(configuration);
|
|
694
|
+
}
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
static configure(configuration) {
|
|
698
|
+
FieldError.configure();
|
|
699
|
+
Errors.configure();
|
|
700
|
+
Spinner.configure();
|
|
701
|
+
Input.configure();
|
|
702
|
+
Select.configure();
|
|
703
|
+
Form.custom('ful-form', configuration || {});
|
|
704
|
+
}
|
|
403
705
|
}
|
|
404
706
|
|
|
405
707
|
class Storage {
|
|
@@ -584,7 +886,7 @@ var ful = (function (exports) {
|
|
|
584
886
|
])
|
|
585
887
|
});
|
|
586
888
|
if (!response.ok) {
|
|
587
|
-
throw new Error("Error:" + response.
|
|
889
|
+
throw new Error("Error:" + response.status + ": " + response.text());
|
|
588
890
|
}
|
|
589
891
|
const token = await response.json();
|
|
590
892
|
this.token = token;
|
|
@@ -722,10 +1024,10 @@ var ful = (function (exports) {
|
|
|
722
1024
|
}
|
|
723
1025
|
};
|
|
724
1026
|
|
|
725
|
-
class Wizard {
|
|
726
|
-
constructor(
|
|
727
|
-
|
|
728
|
-
this.progress = [...
|
|
1027
|
+
class Wizard extends HTMLElement {
|
|
1028
|
+
constructor() {
|
|
1029
|
+
super();
|
|
1030
|
+
this.progress = [...this.children].filter(e => e.matches("header,ol,ul"));
|
|
729
1031
|
|
|
730
1032
|
this.progress.forEach(p => {
|
|
731
1033
|
const children = [...p.children];
|
|
@@ -734,8 +1036,8 @@ var ful = (function (exports) {
|
|
|
734
1036
|
children[0].classList.add('active');
|
|
735
1037
|
}
|
|
736
1038
|
});
|
|
737
|
-
if (this.
|
|
738
|
-
const firstSection = this.
|
|
1039
|
+
if (this.querySelector('section.current') === null) {
|
|
1040
|
+
const firstSection = this.querySelector('section:first-of-type');
|
|
739
1041
|
if (firstSection !== null) {
|
|
740
1042
|
firstSection.classList.add('current');
|
|
741
1043
|
}
|
|
@@ -748,11 +1050,11 @@ var ful = (function (exports) {
|
|
|
748
1050
|
current?.classList.remove('active');
|
|
749
1051
|
current?.nextElementSibling?.classList.add('active');
|
|
750
1052
|
});
|
|
751
|
-
const currentSection = this.
|
|
1053
|
+
const currentSection = this.querySelector('section.current');
|
|
752
1054
|
currentSection.classList.remove("current");
|
|
753
1055
|
currentSection.nextElementSibling.classList.add('current');
|
|
754
1056
|
|
|
755
|
-
this.
|
|
1057
|
+
this.dispatchEvent(new CustomEvent('wizard:activate', {
|
|
756
1058
|
bubbles: true,
|
|
757
1059
|
cancelable: true
|
|
758
1060
|
}));
|
|
@@ -765,10 +1067,10 @@ var ful = (function (exports) {
|
|
|
765
1067
|
current?.classList.remove('active');
|
|
766
1068
|
current?.previousElementSibling?.classList.add('active');
|
|
767
1069
|
});
|
|
768
|
-
const currentSection = this.
|
|
1070
|
+
const currentSection = this.querySelector('section.current');
|
|
769
1071
|
currentSection.classList.remove("current");
|
|
770
1072
|
currentSection.previousElementSibling.classList.add('current');
|
|
771
|
-
this.
|
|
1073
|
+
this.dispatchEvent(new CustomEvent('wizard:activate', {
|
|
772
1074
|
bubbles: true,
|
|
773
1075
|
cancelable: true
|
|
774
1076
|
}));
|
|
@@ -780,15 +1082,25 @@ var ful = (function (exports) {
|
|
|
780
1082
|
current?.classList.remove('active');
|
|
781
1083
|
p.children[+n]?.classList.add('active');
|
|
782
1084
|
});
|
|
783
|
-
const currentSection = this.
|
|
1085
|
+
const currentSection = this.querySelector('section.current');
|
|
784
1086
|
currentSection?.classList.remove("current");
|
|
785
|
-
const nthSection = this.
|
|
1087
|
+
const nthSection = this.querySelector(`section:nth-child(${+n})`);
|
|
786
1088
|
nthSection.classList.add('current');
|
|
787
|
-
this.
|
|
1089
|
+
this.dispatchEvent(new CustomEvent('wizard:activate', {
|
|
788
1090
|
bubbles: true,
|
|
789
1091
|
cancelable: true
|
|
790
1092
|
}));
|
|
791
1093
|
}
|
|
1094
|
+
static custom(tagName, configuration) {
|
|
1095
|
+
customElements.define(tagName, class extends Wizard {
|
|
1096
|
+
constructor() {
|
|
1097
|
+
super(configuration);
|
|
1098
|
+
}
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1101
|
+
static configure() {
|
|
1102
|
+
return Wizard.custom('ful-wizard');
|
|
1103
|
+
}
|
|
792
1104
|
}
|
|
793
1105
|
|
|
794
1106
|
class App {
|
|
@@ -822,13 +1134,19 @@ var ful = (function (exports) {
|
|
|
822
1134
|
exports.AuthorizationCodeFlowInterceptor = AuthorizationCodeFlowInterceptor;
|
|
823
1135
|
exports.AuthorizationCodeFlowSession = AuthorizationCodeFlowSession;
|
|
824
1136
|
exports.Base64 = Base64;
|
|
825
|
-
exports.
|
|
1137
|
+
exports.CustomElements = CustomElements;
|
|
1138
|
+
exports.Errors = Errors;
|
|
826
1139
|
exports.Failure = Failure;
|
|
1140
|
+
exports.FieldError = FieldError;
|
|
827
1141
|
exports.Form = Form;
|
|
828
1142
|
exports.Hex = Hex;
|
|
829
1143
|
exports.HttpClient = HttpClient;
|
|
1144
|
+
exports.Input = Input;
|
|
830
1145
|
exports.LocalStorage = LocalStorage;
|
|
1146
|
+
exports.Observable = Observable;
|
|
1147
|
+
exports.Select = Select;
|
|
831
1148
|
exports.SessionStorage = SessionStorage;
|
|
1149
|
+
exports.Spinner = Spinner;
|
|
832
1150
|
exports.VersionedStorage = VersionedStorage;
|
|
833
1151
|
exports.Wizard = Wizard;
|
|
834
1152
|
exports.timing = timing;
|