@optionfactory/ful 0.19.0 → 0.21.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 +523 -192
- 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 +516 -192
- 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() {
|
|
@@ -351,9 +227,10 @@ var ful = (function (exports) {
|
|
|
351
227
|
this.interceptors = interceptors || [];
|
|
352
228
|
}
|
|
353
229
|
async fetch(resource, options) {
|
|
354
|
-
const
|
|
230
|
+
const opts = options || {};
|
|
231
|
+
const interceptors = [...this.interceptors, ...opts.interceptors || [], new HttpCall()];
|
|
355
232
|
const chain = new HttpInterceptorChain(interceptors, 0);
|
|
356
|
-
return await chain.proceed({resource,
|
|
233
|
+
return await chain.proceed({resource, opts});
|
|
357
234
|
}
|
|
358
235
|
async json(resource, options) {
|
|
359
236
|
try {
|
|
@@ -376,30 +253,467 @@ var ful = (function (exports) {
|
|
|
376
253
|
}]);
|
|
377
254
|
}
|
|
378
255
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function jsonRequest(method, body, headers){
|
|
259
|
+
return {
|
|
260
|
+
headers: {
|
|
261
|
+
"Content-Type": "application/json",
|
|
262
|
+
...headers
|
|
263
|
+
},
|
|
264
|
+
method: method,
|
|
265
|
+
body: JSON.stringify(body)
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/* global Infinity, CSS */
|
|
270
|
+
|
|
271
|
+
class CustomElements {
|
|
272
|
+
static id = 0;
|
|
273
|
+
static uid(prefix) {
|
|
274
|
+
return `${prefix}-${++CustomElements.id}`;
|
|
275
|
+
}
|
|
276
|
+
static forwardAttributes(from, to, except) {
|
|
277
|
+
from.getAttributeNames().filter(a => except.indexOf(a) === -1)
|
|
278
|
+
.filter(a => a[0] === '@')
|
|
279
|
+
.forEach(a => {
|
|
280
|
+
if (a === '@class') {
|
|
281
|
+
to.classList.add(...from.getAttribute("@class").split(" ").filter(a => a.length));
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
to.setAttribute(a.substring(1), from.getAttribute(a));
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
static extractSlots(el) {
|
|
288
|
+
const slotted = Object.fromEntries([...el.querySelectorAll("[slot]")].map(el => {
|
|
289
|
+
el.parentElement.removeChild(el);
|
|
290
|
+
const slot = el.getAttribute("slot");
|
|
291
|
+
el.removeAttribute("slot");
|
|
292
|
+
return [slot, el];
|
|
293
|
+
}));
|
|
294
|
+
slotted.default = new DocumentFragment();
|
|
295
|
+
slotted.default.append(...el.childNodes);
|
|
296
|
+
return slotted;
|
|
297
|
+
}
|
|
298
|
+
static labelAndInputGroup(id, name, isFloating, slotted) {
|
|
299
|
+
if (isFloating) {
|
|
300
|
+
/**
|
|
301
|
+
* <div class="input-group has-validation">
|
|
302
|
+
* <span data-tpl-if="slotted.before" class="input-group-text">{{{{ slotted.before }}}}</span>
|
|
303
|
+
* <div class="form-floating">
|
|
304
|
+
* {{{{ slotted.input }}}}
|
|
305
|
+
* <label data-tpl-for="name" class="form-label">{{{{ slotted.default }}}}</label>
|
|
306
|
+
* </div>
|
|
307
|
+
* <span data-tpl-if="slotted.after" class="input-group-text">{{{{ slotted.after }}}}</span>
|
|
308
|
+
* <ful-field-error data-tpl-field="name"></ful-field-error>
|
|
309
|
+
* </div>
|
|
310
|
+
*/
|
|
311
|
+
const label = document.createElement("label");
|
|
312
|
+
label.setAttribute("for", id);
|
|
313
|
+
label.classList.add('form-label');
|
|
314
|
+
label.append(slotted.default);
|
|
315
|
+
|
|
316
|
+
const ff = document.createElement('div');
|
|
317
|
+
ff.classList.add("form-floating");
|
|
318
|
+
ff.append(slotted.input, label);
|
|
319
|
+
|
|
320
|
+
const ffe = document.createElement('ful-field-error');
|
|
321
|
+
ffe.setAttribute("field", name);
|
|
322
|
+
|
|
323
|
+
const ig = document.createElement("div");
|
|
324
|
+
ig.classList.add('input-group', 'has-validtion');
|
|
325
|
+
|
|
326
|
+
if (slotted.before) {
|
|
327
|
+
ig.append(slotted.before);
|
|
328
|
+
} else if (slotted.ibefore) {
|
|
329
|
+
const igt = document.createElement('div');
|
|
330
|
+
igt.classList.add('input-group-text');
|
|
331
|
+
igt.append(slotted.ibefore);
|
|
332
|
+
ig.append(igt);
|
|
333
|
+
}
|
|
334
|
+
ig.append(ff);
|
|
335
|
+
if (slotted.after) {
|
|
336
|
+
ig.append(slotted.after);
|
|
337
|
+
} else if (slotted.iafter) {
|
|
338
|
+
const igt = document.createElement('div');
|
|
339
|
+
igt.classList.add('input-group-text');
|
|
340
|
+
igt.append(slotted.iafter);
|
|
341
|
+
ig.append(igt);
|
|
342
|
+
}
|
|
343
|
+
ig.append(ffe);
|
|
344
|
+
return ig;
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
<label data-tpl-for="name" class="form-label">{{{{ slotted.default }}}}</label>
|
|
348
|
+
<div class="input-group has-validation">
|
|
349
|
+
<span data-tpl-if="slotted.before" class="input-group-text">{{{{ slotted.before }}}}</span>
|
|
350
|
+
{{{{ slotted.input }}}}
|
|
351
|
+
<span data-tpl-if="slotted.after" class="input-group-text">{{{{ slotted.after }}}}</span>
|
|
352
|
+
<ful-field-error data-tpl-field="name"></ful-field-error>
|
|
353
|
+
</div>
|
|
354
|
+
*/
|
|
355
|
+
|
|
356
|
+
const label = document.createElement("label");
|
|
357
|
+
label.setAttribute("for", name);
|
|
358
|
+
label.classList.add('form-label');
|
|
359
|
+
label.append(slotted.default);
|
|
360
|
+
|
|
361
|
+
const ffe = document.createElement('ful-field-error');
|
|
362
|
+
ffe.setAttribute("field", name);
|
|
363
|
+
|
|
364
|
+
const ig = document.createElement("div");
|
|
365
|
+
ig.classList.add('input-group', 'has-validation');
|
|
366
|
+
|
|
367
|
+
if (slotted.before) {
|
|
368
|
+
ig.append(slotted.before);
|
|
369
|
+
} else if (slotted.ibefore) {
|
|
370
|
+
const igt = document.createElement('div');
|
|
371
|
+
igt.classList.add('input-group-text');
|
|
372
|
+
igt.append(slotted.ibefore);
|
|
373
|
+
ig.append(igt);
|
|
374
|
+
}
|
|
375
|
+
ig.append(slotted.input);
|
|
376
|
+
if (slotted.after) {
|
|
377
|
+
ig.append(slotted.after);
|
|
378
|
+
} else if (slotted.iafter) {
|
|
379
|
+
const igt = document.createElement('div');
|
|
380
|
+
igt.classList.add('input-group-text');
|
|
381
|
+
igt.append(slotted.iafter);
|
|
382
|
+
ig.append(igt);
|
|
383
|
+
}
|
|
384
|
+
ig.append(ffe);
|
|
385
|
+
|
|
386
|
+
const fragment = new DocumentFragment();
|
|
387
|
+
fragment.append(label, ig);
|
|
388
|
+
return fragment;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
class FieldError extends HTMLElement {
|
|
395
|
+
constructor() {
|
|
396
|
+
super();
|
|
397
|
+
}
|
|
398
|
+
connectedCallback() {
|
|
399
|
+
this.classList.add('invalid-feedback');
|
|
400
|
+
}
|
|
401
|
+
static configure() {
|
|
402
|
+
customElements.define('ful-field-error', FieldError);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
class Errors extends HTMLElement {
|
|
407
|
+
constructor() {
|
|
408
|
+
super();
|
|
409
|
+
}
|
|
410
|
+
connectedCallback() {
|
|
411
|
+
this.classList.add('alert', 'alert-danger', 'd-none');
|
|
412
|
+
}
|
|
413
|
+
static configure() {
|
|
414
|
+
customElements.define('ful-errors', Errors);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
class Spinner extends HTMLElement {
|
|
420
|
+
constructor() {
|
|
421
|
+
super();
|
|
422
|
+
}
|
|
423
|
+
connectedCallback() {
|
|
424
|
+
this.classList.add('spinner-border', 'spinner-border-sm', 'd-none');
|
|
425
|
+
this.setAttribute("aria-hidden", "true");
|
|
426
|
+
}
|
|
427
|
+
show() {
|
|
428
|
+
this.classList.remove("d-none");
|
|
429
|
+
}
|
|
430
|
+
hide() {
|
|
431
|
+
this.classList.add("d-none");
|
|
432
|
+
}
|
|
433
|
+
static configure() {
|
|
434
|
+
customElements.define('ful-spinner', Spinner);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
class Input extends HTMLElement {
|
|
441
|
+
constructor() {
|
|
442
|
+
super();
|
|
443
|
+
const id = CustomElements.uid('ful-input');
|
|
444
|
+
const name = this.getAttribute('@name');
|
|
445
|
+
const floating = this.hasAttribute('@floating');
|
|
446
|
+
const slotted = CustomElements.extractSlots(this);
|
|
447
|
+
slotted.input = slotted.input || (() => {
|
|
448
|
+
const el = document.createElement("input");
|
|
449
|
+
el.classList.add("form-control");
|
|
450
|
+
return el;
|
|
451
|
+
})();
|
|
452
|
+
CustomElements.forwardAttributes(this, slotted.input, ['@floating']);
|
|
453
|
+
const attrIfMissing = (el, k, v) => !el.hasAttribute(k) && el.setAttribute(k, v);
|
|
454
|
+
attrIfMissing(slotted.input, "name", id);
|
|
455
|
+
attrIfMissing(slotted.input, "id", id);
|
|
456
|
+
attrIfMissing(slotted.input, "type", "text");
|
|
457
|
+
attrIfMissing(slotted.input, "placeholder", " ");
|
|
458
|
+
this.innerHTML = '';
|
|
459
|
+
this.append(CustomElements.labelAndInputGroup(id, name || id, floating, slotted));
|
|
460
|
+
}
|
|
461
|
+
static configure() {
|
|
462
|
+
customElements.define('ful-input', Input);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* <script src="tom-select.complete.js"></script>
|
|
470
|
+
* <link href="tom-select.bootstrap5.css" rel="stylesheet" />
|
|
471
|
+
*/
|
|
472
|
+
class Select extends HTMLElement {
|
|
473
|
+
constructor(tsConfig) {
|
|
474
|
+
super();
|
|
475
|
+
Observable.mixin(this);
|
|
476
|
+
const id = CustomElements.uid('ful-select');
|
|
477
|
+
const name = this.getAttribute('@name');
|
|
478
|
+
const floating = this.hasAttribute('@floating');
|
|
479
|
+
const remote = this.hasAttribute('@remote');
|
|
480
|
+
const slotted = CustomElements.extractSlots(this);
|
|
481
|
+
slotted.input = slotted.input || (() => {
|
|
482
|
+
return document.createElement("select");
|
|
483
|
+
})();
|
|
484
|
+
CustomElements.forwardAttributes(this, slotted.input, ['@floating', '@remote']);
|
|
485
|
+
const attrIfMissing = (el, k, v) => !el.hasAttribute(k) && el.setAttribute(k, v);
|
|
486
|
+
attrIfMissing(slotted.input, "name", id);
|
|
487
|
+
attrIfMissing(slotted.input, "id", id);
|
|
488
|
+
attrIfMissing(slotted.input, "placeholder", " ");
|
|
489
|
+
this.innerHTML = '';
|
|
490
|
+
this.append(CustomElements.labelAndInputGroup(id, name || id, floating, slotted));
|
|
491
|
+
this.loaded = !remote;
|
|
492
|
+
this.ts = new TomSelect(slotted.input, Object.assign(remote ? {
|
|
493
|
+
preload: 'focus',
|
|
494
|
+
load: async (query, callback) => {
|
|
495
|
+
if (this.loaded) {
|
|
496
|
+
callback();
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
const data = await this.fire('load', query, []);
|
|
500
|
+
this.loaded = true;
|
|
501
|
+
callback(data);
|
|
502
|
+
}
|
|
503
|
+
} : {}, tsConfig));
|
|
504
|
+
slotted.input.setValue = this.setValue.bind(this);
|
|
505
|
+
slotted.input.getValue = this.getValue.bind(this);
|
|
506
|
+
}
|
|
507
|
+
async setValue(v){
|
|
508
|
+
if(!this.loaded){
|
|
509
|
+
await this.ts.load();
|
|
510
|
+
}
|
|
511
|
+
this.ts.setValue(v);
|
|
512
|
+
}
|
|
513
|
+
getValue(){
|
|
514
|
+
const v = this.ts.getValue();
|
|
515
|
+
return v === '' ? null : v;
|
|
516
|
+
}
|
|
517
|
+
static custom(tagName, configuration) {
|
|
518
|
+
customElements.define(tagName, class extends Select {
|
|
519
|
+
constructor() {
|
|
520
|
+
super(configuration);
|
|
386
521
|
}
|
|
387
522
|
});
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
523
|
+
}
|
|
524
|
+
static configure() {
|
|
525
|
+
return Select.custom('ful-select');
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
class Form extends HTMLElement {
|
|
531
|
+
constructor({ mutators, extractors, valueHoldersSelector, ignoredChildrenSelector }) {
|
|
532
|
+
super();
|
|
533
|
+
Observable.mixin(this);
|
|
534
|
+
this.mutators = mutators || {};
|
|
535
|
+
this.extractors = extractors || {};
|
|
536
|
+
this.valueHoldersSelector = valueHoldersSelector || '[name]';
|
|
537
|
+
this.ignoredChildrenSelector = ignoredChildrenSelector || '.d-none';
|
|
538
|
+
|
|
539
|
+
const form = document.createElement('form');
|
|
540
|
+
form.append(...this.childNodes);
|
|
541
|
+
this.appendChild(form);
|
|
542
|
+
|
|
543
|
+
form.addEventListener('submit', async (e) => {
|
|
544
|
+
e.preventDefault();
|
|
545
|
+
this.spinner(true);
|
|
546
|
+
try {
|
|
547
|
+
await this.fire('submit', this.getValues(), this);
|
|
548
|
+
} catch (e) {
|
|
549
|
+
if (e instanceof Failure) {
|
|
550
|
+
this.setErrors(e.problems);
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
throw e;
|
|
554
|
+
} finally {
|
|
555
|
+
this.spinner(false);
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
spinner(spin) {
|
|
560
|
+
this.querySelectorAll('ful-spinner').forEach(el => {
|
|
561
|
+
el[spin ? 'show' : 'hide']();
|
|
562
|
+
});
|
|
563
|
+
this.querySelectorAll('[type=submit],[type=reset]').forEach(el => {
|
|
564
|
+
el.disabled = spin;
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
setValues(values) {
|
|
568
|
+
for (let k in values) {
|
|
569
|
+
if (!values.hasOwnProperty(k)) {
|
|
570
|
+
continue;
|
|
571
|
+
}
|
|
572
|
+
Array.from(this.querySelectorAll(`[name='${CSS.escape(k)}']`)).forEach((el) => {
|
|
573
|
+
Form.mutate(this.mutators, el, values[k], k, values);
|
|
400
574
|
});
|
|
401
575
|
}
|
|
402
576
|
}
|
|
577
|
+
getValues() {
|
|
578
|
+
return Array.from(this.querySelectorAll(this.valueHoldersSelector))
|
|
579
|
+
.filter((el) => {
|
|
580
|
+
if (el.dataset['fulBindInclude'] === 'never') {
|
|
581
|
+
return false;
|
|
582
|
+
}
|
|
583
|
+
return el.dataset['fulBindInclude'] === 'always' || el.closest(this.ignoredChildrenSelector) === null;
|
|
584
|
+
})
|
|
585
|
+
.reduce((result, el) => {
|
|
586
|
+
return Form.providePath(result, el.getAttribute('name'), Form.extract(this.extractors, el));
|
|
587
|
+
}, {});
|
|
588
|
+
}
|
|
589
|
+
setErrors(errors, scroll) {
|
|
590
|
+
this.clearErrors();
|
|
591
|
+
errors
|
|
592
|
+
.filter((e) => e.type === 'FIELD_ERROR' || e.type === 'INVALID_FORMAT')
|
|
593
|
+
.forEach((e) => {
|
|
594
|
+
const name = e.context.replace("[", ".").replace("].", ".");
|
|
595
|
+
this.querySelectorAll(`[name='${CSS.escape(name)}']`)
|
|
596
|
+
.forEach(input => {
|
|
597
|
+
input.classList.add('is-invalid');
|
|
598
|
+
if (input.parentElement.classList.contains("form-floating")) {
|
|
599
|
+
input.parentElement.classList.add('is-invalid');
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
this.querySelectorAll(`ful-field-error[field='${CSS.escape(name)}']`)
|
|
603
|
+
.forEach(el => el.innerText = e.reason);
|
|
604
|
+
});
|
|
605
|
+
this.querySelectorAll("ful-errors")
|
|
606
|
+
.forEach(el => {
|
|
607
|
+
const globalErrors = errors.filter((e) => e.type !== 'FIELD_ERROR' && e.type !== 'INVALID_FORMAT');
|
|
608
|
+
el.innerHTML = globalErrors.map(e => e.reason).join("\n");
|
|
609
|
+
if (globalErrors.length !== 0) {
|
|
610
|
+
el.classList.remove('d-none');
|
|
611
|
+
}
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
if (!scroll) {
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
const ys = Array.from(this.querySelectorAll('ful-field-error:not(.d-none)'))
|
|
618
|
+
.map(el => el.getBoundingClientRect().y + window.scrollY);
|
|
619
|
+
const miny = Math.min(...ys);
|
|
620
|
+
if (miny !== Infinity) {
|
|
621
|
+
window.scroll(window.scrollX, miny > 100 ? miny - 100 : 0);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
clearErrors() {
|
|
625
|
+
this.querySelectorAll('[name].is-invalid, .form-floating.is-invalid')
|
|
626
|
+
.forEach(el => el.classList.remove('is-invalid'));
|
|
627
|
+
this.querySelectorAll("ful-errors")
|
|
628
|
+
.forEach(el => {
|
|
629
|
+
el.innerHTML = '';
|
|
630
|
+
el.classList.add('d-none');
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
static extract(extractors, el) {
|
|
634
|
+
const maybeExtractor = extractors[el.dataset['fulBindExtractor']] || extractors[el.dataset['fulBindProvide']];
|
|
635
|
+
if (maybeExtractor) {
|
|
636
|
+
return maybeExtractor(el);
|
|
637
|
+
}
|
|
638
|
+
if (el.getAttribute('type') === 'radio') {
|
|
639
|
+
if (!el.checked) {
|
|
640
|
+
return undefined;
|
|
641
|
+
}
|
|
642
|
+
return el.dataset['fulBindType'] === 'boolean' ? el.value === 'true' : el.value;
|
|
643
|
+
}
|
|
644
|
+
if (el.getAttribute('type') === 'checkbox') {
|
|
645
|
+
return el.checked;
|
|
646
|
+
}
|
|
647
|
+
if (el.dataset['fulBindType'] === 'boolean') {
|
|
648
|
+
return !el.value ? null : el.value === 'true';
|
|
649
|
+
}
|
|
650
|
+
if (el.getValue) {
|
|
651
|
+
return el.getValue();
|
|
652
|
+
}
|
|
653
|
+
return el.value || null;
|
|
654
|
+
}
|
|
655
|
+
static mutate(mutators, el, raw, key, values) {
|
|
656
|
+
const maybeMutator = mutators[el.dataset['fulBindMutator']] || mutators[el.dataset['fulBindProvide']];
|
|
657
|
+
if (maybeMutator) {
|
|
658
|
+
maybeMutator(el, raw, key, values);
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
if (el.getAttribute('type') === 'radio') {
|
|
662
|
+
el.checked = el.getAttribute('value') === raw;
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
if (el.getAttribute('type') === 'checkbox') {
|
|
666
|
+
el.checked = raw;
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
if (el.setValue) {
|
|
670
|
+
el.setValue(raw);
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
el.value = raw;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
static providePath(result, path, value) {
|
|
677
|
+
const keys = path.split(".").map((k) => k.match(/^[0-9]+$/) ? +k : k);
|
|
678
|
+
let current = result;
|
|
679
|
+
let previous = null;
|
|
680
|
+
for (let i = 0; ; ++i) {
|
|
681
|
+
const ckey = keys[i];
|
|
682
|
+
const pkey = keys[i - 1];
|
|
683
|
+
if (Number.isInteger(ckey) && !Array.isArray(current)) {
|
|
684
|
+
if (previous !== null) {
|
|
685
|
+
previous[pkey] = current = [];
|
|
686
|
+
} else {
|
|
687
|
+
result = current = [];
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
if (i === keys.length - 1) {
|
|
691
|
+
//when value is undefined we only want to define the property if it's not defined
|
|
692
|
+
current[ckey] = value !== undefined ? value : (ckey in current ? current[ckey] : null);
|
|
693
|
+
return result;
|
|
694
|
+
}
|
|
695
|
+
if (current[ckey] === undefined) {
|
|
696
|
+
current[ckey] = {};
|
|
697
|
+
}
|
|
698
|
+
previous = current;
|
|
699
|
+
current = current[ckey];
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
static custom(tagName, configuration) {
|
|
703
|
+
customElements.define(tagName, class extends Form {
|
|
704
|
+
constructor() {
|
|
705
|
+
super(configuration);
|
|
706
|
+
}
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
static configure(configuration) {
|
|
710
|
+
FieldError.configure();
|
|
711
|
+
Errors.configure();
|
|
712
|
+
Spinner.configure();
|
|
713
|
+
Input.configure();
|
|
714
|
+
Select.configure();
|
|
715
|
+
Form.custom('ful-form', configuration || {});
|
|
716
|
+
}
|
|
403
717
|
}
|
|
404
718
|
|
|
405
719
|
class Storage {
|
|
@@ -584,7 +898,7 @@ var ful = (function (exports) {
|
|
|
584
898
|
])
|
|
585
899
|
});
|
|
586
900
|
if (!response.ok) {
|
|
587
|
-
throw new Error("Error:" + response.
|
|
901
|
+
throw new Error("Error:" + response.status + ": " + response.text());
|
|
588
902
|
}
|
|
589
903
|
const token = await response.json();
|
|
590
904
|
this.token = token;
|
|
@@ -722,10 +1036,10 @@ var ful = (function (exports) {
|
|
|
722
1036
|
}
|
|
723
1037
|
};
|
|
724
1038
|
|
|
725
|
-
class Wizard {
|
|
726
|
-
constructor(
|
|
727
|
-
|
|
728
|
-
this.progress = [...
|
|
1039
|
+
class Wizard extends HTMLElement {
|
|
1040
|
+
constructor() {
|
|
1041
|
+
super();
|
|
1042
|
+
this.progress = [...this.children].filter(e => e.matches("header,ol,ul"));
|
|
729
1043
|
|
|
730
1044
|
this.progress.forEach(p => {
|
|
731
1045
|
const children = [...p.children];
|
|
@@ -734,8 +1048,8 @@ var ful = (function (exports) {
|
|
|
734
1048
|
children[0].classList.add('active');
|
|
735
1049
|
}
|
|
736
1050
|
});
|
|
737
|
-
if (this.
|
|
738
|
-
const firstSection = this.
|
|
1051
|
+
if (this.querySelector('section.current') === null) {
|
|
1052
|
+
const firstSection = this.querySelector('section:first-of-type');
|
|
739
1053
|
if (firstSection !== null) {
|
|
740
1054
|
firstSection.classList.add('current');
|
|
741
1055
|
}
|
|
@@ -748,11 +1062,11 @@ var ful = (function (exports) {
|
|
|
748
1062
|
current?.classList.remove('active');
|
|
749
1063
|
current?.nextElementSibling?.classList.add('active');
|
|
750
1064
|
});
|
|
751
|
-
const currentSection = this.
|
|
1065
|
+
const currentSection = this.querySelector('section.current');
|
|
752
1066
|
currentSection.classList.remove("current");
|
|
753
1067
|
currentSection.nextElementSibling.classList.add('current');
|
|
754
1068
|
|
|
755
|
-
this.
|
|
1069
|
+
this.dispatchEvent(new CustomEvent('wizard:activate', {
|
|
756
1070
|
bubbles: true,
|
|
757
1071
|
cancelable: true
|
|
758
1072
|
}));
|
|
@@ -765,10 +1079,10 @@ var ful = (function (exports) {
|
|
|
765
1079
|
current?.classList.remove('active');
|
|
766
1080
|
current?.previousElementSibling?.classList.add('active');
|
|
767
1081
|
});
|
|
768
|
-
const currentSection = this.
|
|
1082
|
+
const currentSection = this.querySelector('section.current');
|
|
769
1083
|
currentSection.classList.remove("current");
|
|
770
1084
|
currentSection.previousElementSibling.classList.add('current');
|
|
771
|
-
this.
|
|
1085
|
+
this.dispatchEvent(new CustomEvent('wizard:activate', {
|
|
772
1086
|
bubbles: true,
|
|
773
1087
|
cancelable: true
|
|
774
1088
|
}));
|
|
@@ -780,15 +1094,25 @@ var ful = (function (exports) {
|
|
|
780
1094
|
current?.classList.remove('active');
|
|
781
1095
|
p.children[+n]?.classList.add('active');
|
|
782
1096
|
});
|
|
783
|
-
const currentSection = this.
|
|
1097
|
+
const currentSection = this.querySelector('section.current');
|
|
784
1098
|
currentSection?.classList.remove("current");
|
|
785
|
-
const nthSection = this.
|
|
1099
|
+
const nthSection = this.querySelector(`section:nth-child(${+n})`);
|
|
786
1100
|
nthSection.classList.add('current');
|
|
787
|
-
this.
|
|
1101
|
+
this.dispatchEvent(new CustomEvent('wizard:activate', {
|
|
788
1102
|
bubbles: true,
|
|
789
1103
|
cancelable: true
|
|
790
1104
|
}));
|
|
791
1105
|
}
|
|
1106
|
+
static custom(tagName, configuration) {
|
|
1107
|
+
customElements.define(tagName, class extends Wizard {
|
|
1108
|
+
constructor() {
|
|
1109
|
+
super(configuration);
|
|
1110
|
+
}
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
static configure() {
|
|
1114
|
+
return Wizard.custom('ful-wizard');
|
|
1115
|
+
}
|
|
792
1116
|
}
|
|
793
1117
|
|
|
794
1118
|
class App {
|
|
@@ -822,15 +1146,22 @@ var ful = (function (exports) {
|
|
|
822
1146
|
exports.AuthorizationCodeFlowInterceptor = AuthorizationCodeFlowInterceptor;
|
|
823
1147
|
exports.AuthorizationCodeFlowSession = AuthorizationCodeFlowSession;
|
|
824
1148
|
exports.Base64 = Base64;
|
|
825
|
-
exports.
|
|
1149
|
+
exports.CustomElements = CustomElements;
|
|
1150
|
+
exports.Errors = Errors;
|
|
826
1151
|
exports.Failure = Failure;
|
|
1152
|
+
exports.FieldError = FieldError;
|
|
827
1153
|
exports.Form = Form;
|
|
828
1154
|
exports.Hex = Hex;
|
|
829
1155
|
exports.HttpClient = HttpClient;
|
|
1156
|
+
exports.Input = Input;
|
|
830
1157
|
exports.LocalStorage = LocalStorage;
|
|
1158
|
+
exports.Observable = Observable;
|
|
1159
|
+
exports.Select = Select;
|
|
831
1160
|
exports.SessionStorage = SessionStorage;
|
|
1161
|
+
exports.Spinner = Spinner;
|
|
832
1162
|
exports.VersionedStorage = VersionedStorage;
|
|
833
1163
|
exports.Wizard = Wizard;
|
|
1164
|
+
exports.jsonRequest = jsonRequest;
|
|
834
1165
|
exports.timing = timing;
|
|
835
1166
|
|
|
836
1167
|
Object.defineProperty(exports, '__esModule', { value: true });
|