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