@optionfactory/ful 0.18.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/README.md +1 -1
- 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 +581 -247
- 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 +575 -247
- 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,78 +70,54 @@ 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() {
|
|
238
114
|
const context = document.querySelector("meta[name='context']").getAttribute("content");
|
|
239
115
|
this.context = context.endsWith("/") ? context.substring(0, context.length - 1) : context;
|
|
240
116
|
}
|
|
241
|
-
|
|
117
|
+
async intercept(request, chain){
|
|
242
118
|
const separator = request.resource.startsWith("/") ? "" : "/";
|
|
243
119
|
request.resource = this.context + separator + request.resource;
|
|
120
|
+
return await chain.proceed(request);
|
|
244
121
|
}
|
|
245
122
|
}
|
|
246
123
|
|
|
@@ -249,10 +126,11 @@ class CsrfTokenInterceptor {
|
|
|
249
126
|
this.k = document.querySelector("meta[name='_csrf_header']").getAttribute("content");
|
|
250
127
|
this.v = document.querySelector("meta[name='_csrf']").getAttribute("content");
|
|
251
128
|
}
|
|
252
|
-
|
|
129
|
+
async intercept(request, chain){
|
|
253
130
|
const headers = new Headers(request.options.headers);
|
|
254
131
|
headers.set(this.k, this.v);
|
|
255
132
|
request.options.headers = headers;
|
|
133
|
+
return await chain.proceed(request);
|
|
256
134
|
}
|
|
257
135
|
}
|
|
258
136
|
|
|
@@ -260,9 +138,10 @@ class RedirectOnUnauthorizedInterceptor {
|
|
|
260
138
|
constructor(redirectUri) {
|
|
261
139
|
this.redirectUri = redirectUri;
|
|
262
140
|
}
|
|
263
|
-
|
|
141
|
+
async intercept(request, chain){
|
|
142
|
+
const response = await chain.proceed(request);
|
|
264
143
|
if (response.status !== 401) {
|
|
265
|
-
return;
|
|
144
|
+
return response;
|
|
266
145
|
}
|
|
267
146
|
window.location.href = redirectUri;
|
|
268
147
|
}
|
|
@@ -319,31 +198,35 @@ class HttpClientBuilder {
|
|
|
319
198
|
}
|
|
320
199
|
}
|
|
321
200
|
|
|
201
|
+
class HttpCall {
|
|
202
|
+
async intercept(request, chain){
|
|
203
|
+
return await fetch(request.resource, request.options);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
class HttpInterceptorChain {
|
|
208
|
+
constructor(interceptors, current){
|
|
209
|
+
this.interceptors = interceptors;
|
|
210
|
+
this.current = current;
|
|
211
|
+
}
|
|
212
|
+
async proceed(request){
|
|
213
|
+
const interceptor = this.interceptors[this.current];
|
|
214
|
+
return await interceptor.intercept(request, new HttpInterceptorChain(this.interceptors, this.current + 1));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
|
|
322
219
|
class HttpClient {
|
|
323
220
|
static builder() {
|
|
324
221
|
return new HttpClientBuilder();
|
|
325
222
|
}
|
|
326
|
-
constructor(
|
|
223
|
+
constructor({interceptors}){
|
|
327
224
|
this.interceptors = interceptors || [];
|
|
328
225
|
}
|
|
329
226
|
async fetch(resource, options) {
|
|
330
|
-
const
|
|
331
|
-
const
|
|
332
|
-
await
|
|
333
|
-
if (!i.before) {
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
336
|
-
await i.before(request);
|
|
337
|
-
});
|
|
338
|
-
const response = await fetch(request.resource, request.options);
|
|
339
|
-
await is.forEach(async (i) => {
|
|
340
|
-
if (!i.after) {
|
|
341
|
-
return;
|
|
342
|
-
}
|
|
343
|
-
await i.after(request, response);
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
return response;
|
|
227
|
+
const interceptors = [...this.interceptors, ...options.interceptors || [], new HttpCall()];
|
|
228
|
+
const chain = new HttpInterceptorChain(interceptors, 0);
|
|
229
|
+
return await chain.proceed({resource, options});
|
|
347
230
|
}
|
|
348
231
|
async json(resource, options) {
|
|
349
232
|
try {
|
|
@@ -366,30 +249,456 @@ class HttpClient {
|
|
|
366
249
|
}]);
|
|
367
250
|
}
|
|
368
251
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/* global Infinity, CSS */
|
|
255
|
+
|
|
256
|
+
class CustomElements {
|
|
257
|
+
static id = 0;
|
|
258
|
+
static uid(prefix) {
|
|
259
|
+
return `${prefix}-${++CustomElements.id}`;
|
|
260
|
+
}
|
|
261
|
+
static forwardAttributes(from, to, except) {
|
|
262
|
+
from.getAttributeNames().filter(a => except.indexOf(a) === -1)
|
|
263
|
+
.filter(a => a[0] === '@')
|
|
264
|
+
.forEach(a => {
|
|
265
|
+
if (a === '@class') {
|
|
266
|
+
to.classList.add(...from.getAttribute("@class").split(" ").filter(a => a.length));
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
to.setAttribute(a.substring(1), from.getAttribute(a));
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
static extractSlots(el) {
|
|
273
|
+
const slotted = Object.fromEntries([...el.querySelectorAll("[slot]")].map(el => {
|
|
274
|
+
el.parentElement.removeChild(el);
|
|
275
|
+
const slot = el.getAttribute("slot");
|
|
276
|
+
el.removeAttribute("slot");
|
|
277
|
+
return [slot, el];
|
|
278
|
+
}));
|
|
279
|
+
slotted.default = new DocumentFragment();
|
|
280
|
+
slotted.default.append(...el.childNodes);
|
|
281
|
+
return slotted;
|
|
282
|
+
}
|
|
283
|
+
static labelAndInputGroup(id, name, isFloating, slotted) {
|
|
284
|
+
if (isFloating) {
|
|
285
|
+
/**
|
|
286
|
+
* <div class="input-group has-validation">
|
|
287
|
+
* <span data-tpl-if="slotted.before" class="input-group-text">{{{{ slotted.before }}}}</span>
|
|
288
|
+
* <div class="form-floating">
|
|
289
|
+
* {{{{ slotted.input }}}}
|
|
290
|
+
* <label data-tpl-for="name" class="form-label">{{{{ slotted.default }}}}</label>
|
|
291
|
+
* </div>
|
|
292
|
+
* <span data-tpl-if="slotted.after" class="input-group-text">{{{{ slotted.after }}}}</span>
|
|
293
|
+
* <ful-field-error data-tpl-field="name"></ful-field-error>
|
|
294
|
+
* </div>
|
|
295
|
+
*/
|
|
296
|
+
const label = document.createElement("label");
|
|
297
|
+
label.setAttribute("for", id);
|
|
298
|
+
label.classList.add('form-label');
|
|
299
|
+
label.append(slotted.default);
|
|
300
|
+
|
|
301
|
+
const ff = document.createElement('div');
|
|
302
|
+
ff.classList.add("form-floating");
|
|
303
|
+
ff.append(slotted.input, label);
|
|
304
|
+
|
|
305
|
+
const ffe = document.createElement('ful-field-error');
|
|
306
|
+
ffe.setAttribute("field", name);
|
|
307
|
+
|
|
308
|
+
const ig = document.createElement("div");
|
|
309
|
+
ig.classList.add('input-group', 'has-validtion');
|
|
310
|
+
|
|
311
|
+
if (slotted.before) {
|
|
312
|
+
ig.append(slotted.before);
|
|
313
|
+
} else if (slotted.ibefore) {
|
|
314
|
+
const igt = document.createElement('div');
|
|
315
|
+
igt.classList.add('input-group-text');
|
|
316
|
+
igt.append(slotted.ibefore);
|
|
317
|
+
ig.append(igt);
|
|
318
|
+
}
|
|
319
|
+
ig.append(ff);
|
|
320
|
+
if (slotted.after) {
|
|
321
|
+
ig.append(slotted.after);
|
|
322
|
+
} else if (slotted.iafter) {
|
|
323
|
+
const igt = document.createElement('div');
|
|
324
|
+
igt.classList.add('input-group-text');
|
|
325
|
+
igt.append(slotted.iafter);
|
|
326
|
+
ig.append(igt);
|
|
327
|
+
}
|
|
328
|
+
ig.append(ffe);
|
|
329
|
+
return ig;
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
<label data-tpl-for="name" class="form-label">{{{{ slotted.default }}}}</label>
|
|
333
|
+
<div class="input-group has-validation">
|
|
334
|
+
<span data-tpl-if="slotted.before" class="input-group-text">{{{{ slotted.before }}}}</span>
|
|
335
|
+
{{{{ slotted.input }}}}
|
|
336
|
+
<span data-tpl-if="slotted.after" class="input-group-text">{{{{ slotted.after }}}}</span>
|
|
337
|
+
<ful-field-error data-tpl-field="name"></ful-field-error>
|
|
338
|
+
</div>
|
|
339
|
+
*/
|
|
340
|
+
|
|
341
|
+
const label = document.createElement("label");
|
|
342
|
+
label.setAttribute("for", name);
|
|
343
|
+
label.classList.add('form-label');
|
|
344
|
+
label.append(slotted.default);
|
|
345
|
+
|
|
346
|
+
const ffe = document.createElement('ful-field-error');
|
|
347
|
+
ffe.setAttribute("field", name);
|
|
348
|
+
|
|
349
|
+
const ig = document.createElement("div");
|
|
350
|
+
ig.classList.add('input-group', 'has-validation');
|
|
351
|
+
|
|
352
|
+
if (slotted.before) {
|
|
353
|
+
ig.append(slotted.before);
|
|
354
|
+
} else if (slotted.ibefore) {
|
|
355
|
+
const igt = document.createElement('div');
|
|
356
|
+
igt.classList.add('input-group-text');
|
|
357
|
+
igt.append(slotted.ibefore);
|
|
358
|
+
ig.append(igt);
|
|
359
|
+
}
|
|
360
|
+
ig.append(slotted.input);
|
|
361
|
+
if (slotted.after) {
|
|
362
|
+
ig.append(slotted.after);
|
|
363
|
+
} else if (slotted.iafter) {
|
|
364
|
+
const igt = document.createElement('div');
|
|
365
|
+
igt.classList.add('input-group-text');
|
|
366
|
+
igt.append(slotted.iafter);
|
|
367
|
+
ig.append(igt);
|
|
368
|
+
}
|
|
369
|
+
ig.append(ffe);
|
|
370
|
+
|
|
371
|
+
const fragment = new DocumentFragment();
|
|
372
|
+
fragment.append(label, ig);
|
|
373
|
+
return fragment;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
class FieldError extends HTMLElement {
|
|
380
|
+
constructor() {
|
|
381
|
+
super();
|
|
382
|
+
}
|
|
383
|
+
connectedCallback() {
|
|
384
|
+
this.classList.add('invalid-feedback');
|
|
385
|
+
}
|
|
386
|
+
static configure() {
|
|
387
|
+
customElements.define('ful-field-error', FieldError);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
class Errors extends HTMLElement {
|
|
392
|
+
constructor() {
|
|
393
|
+
super();
|
|
394
|
+
}
|
|
395
|
+
connectedCallback() {
|
|
396
|
+
this.classList.add('alert', 'alert-danger', 'd-none');
|
|
397
|
+
}
|
|
398
|
+
static configure() {
|
|
399
|
+
customElements.define('ful-errors', Errors);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
class Spinner extends HTMLElement {
|
|
405
|
+
constructor() {
|
|
406
|
+
super();
|
|
407
|
+
}
|
|
408
|
+
connectedCallback() {
|
|
409
|
+
this.classList.add('spinner-border', 'spinner-border-sm', 'd-none');
|
|
410
|
+
this.setAttribute("aria-hidden", "true");
|
|
411
|
+
}
|
|
412
|
+
show() {
|
|
413
|
+
this.classList.remove("d-none");
|
|
414
|
+
}
|
|
415
|
+
hide() {
|
|
416
|
+
this.classList.add("d-none");
|
|
417
|
+
}
|
|
418
|
+
static configure() {
|
|
419
|
+
customElements.define('ful-spinner', Spinner);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
class Input extends HTMLElement {
|
|
426
|
+
constructor() {
|
|
427
|
+
super();
|
|
428
|
+
const id = CustomElements.uid('ful-input');
|
|
429
|
+
const name = this.getAttribute('@name');
|
|
430
|
+
const floating = this.hasAttribute('@floating');
|
|
431
|
+
const slotted = CustomElements.extractSlots(this);
|
|
432
|
+
slotted.input = slotted.input || (() => {
|
|
433
|
+
const el = document.createElement("input");
|
|
434
|
+
el.classList.add("form-control");
|
|
435
|
+
return el;
|
|
436
|
+
})();
|
|
437
|
+
CustomElements.forwardAttributes(this, slotted.input, ['@floating']);
|
|
438
|
+
const attrIfMissing = (el, k, v) => !el.hasAttribute(k) && el.setAttribute(k, v);
|
|
439
|
+
attrIfMissing(slotted.input, "name", id);
|
|
440
|
+
attrIfMissing(slotted.input, "id", id);
|
|
441
|
+
attrIfMissing(slotted.input, "type", "text");
|
|
442
|
+
attrIfMissing(slotted.input, "placeholder", " ");
|
|
443
|
+
this.innerHTML = '';
|
|
444
|
+
this.append(CustomElements.labelAndInputGroup(id, name || id, floating, slotted));
|
|
445
|
+
}
|
|
446
|
+
static configure() {
|
|
447
|
+
customElements.define('ful-input', Input);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* <script src="tom-select.complete.js"></script>
|
|
455
|
+
* <link href="tom-select.bootstrap5.css" rel="stylesheet" />
|
|
456
|
+
*/
|
|
457
|
+
class Select extends HTMLElement {
|
|
458
|
+
constructor(tsConfig) {
|
|
459
|
+
super();
|
|
460
|
+
Observable.mixin(this);
|
|
461
|
+
const id = CustomElements.uid('ful-select');
|
|
462
|
+
const name = this.getAttribute('@name');
|
|
463
|
+
const floating = this.hasAttribute('@floating');
|
|
464
|
+
const remote = this.hasAttribute('@remote');
|
|
465
|
+
const slotted = CustomElements.extractSlots(this);
|
|
466
|
+
slotted.input = slotted.input || (() => {
|
|
467
|
+
return document.createElement("select");
|
|
468
|
+
})();
|
|
469
|
+
CustomElements.forwardAttributes(this, slotted.input, ['@floating', '@remote']);
|
|
470
|
+
const attrIfMissing = (el, k, v) => !el.hasAttribute(k) && el.setAttribute(k, v);
|
|
471
|
+
attrIfMissing(slotted.input, "name", id);
|
|
472
|
+
attrIfMissing(slotted.input, "id", id);
|
|
473
|
+
attrIfMissing(slotted.input, "placeholder", " ");
|
|
474
|
+
this.innerHTML = '';
|
|
475
|
+
this.append(CustomElements.labelAndInputGroup(id, name || id, floating, slotted));
|
|
476
|
+
this.loaded = !remote;
|
|
477
|
+
this.ts = new TomSelect(slotted.input, Object.assign(remote ? {
|
|
478
|
+
preload: 'focus',
|
|
479
|
+
load: async (query, callback) => {
|
|
480
|
+
if (this.loaded) {
|
|
481
|
+
callback();
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
const data = await this.fire('load', query, []);
|
|
485
|
+
this.loaded = true;
|
|
486
|
+
callback(data);
|
|
487
|
+
}
|
|
488
|
+
} : {}, tsConfig));
|
|
489
|
+
slotted.input.setValue = this.setValue.bind(this);
|
|
490
|
+
slotted.input.getValue = this.getValue.bind(this);
|
|
491
|
+
}
|
|
492
|
+
async setValue(v){
|
|
493
|
+
if(!this.loaded){
|
|
494
|
+
await this.ts.load();
|
|
495
|
+
}
|
|
496
|
+
this.ts.setValue(v);
|
|
497
|
+
}
|
|
498
|
+
getValue(){
|
|
499
|
+
const v = this.ts.getValue();
|
|
500
|
+
return v === '' ? null : v;
|
|
501
|
+
}
|
|
502
|
+
static custom(tagName, configuration) {
|
|
503
|
+
customElements.define(tagName, class extends Select {
|
|
504
|
+
constructor() {
|
|
505
|
+
super(configuration);
|
|
376
506
|
}
|
|
377
507
|
});
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
508
|
+
}
|
|
509
|
+
static configure() {
|
|
510
|
+
return Select.custom('ful-select');
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
class Form extends HTMLElement {
|
|
516
|
+
constructor({ mutators, extractors, valueHoldersSelector, ignoredChildrenSelector }) {
|
|
517
|
+
super();
|
|
518
|
+
Observable.mixin(this);
|
|
519
|
+
this.mutators = mutators || {};
|
|
520
|
+
this.extractors = extractors || {};
|
|
521
|
+
this.valueHoldersSelector = valueHoldersSelector || '[name]';
|
|
522
|
+
this.ignoredChildrenSelector = ignoredChildrenSelector || '.d-none';
|
|
523
|
+
|
|
524
|
+
const form = document.createElement('form');
|
|
525
|
+
form.append(...this.childNodes);
|
|
526
|
+
this.appendChild(form);
|
|
527
|
+
|
|
528
|
+
form.addEventListener('submit', async (e) => {
|
|
529
|
+
e.preventDefault();
|
|
530
|
+
this.spinner(true);
|
|
531
|
+
try {
|
|
532
|
+
await this.fire('submit', this.getValues(), this);
|
|
533
|
+
} catch (e) {
|
|
534
|
+
if (e instanceof Failure) {
|
|
535
|
+
this.setErrors(e.problems);
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
throw e;
|
|
539
|
+
} finally {
|
|
540
|
+
this.spinner(false);
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
spinner(spin) {
|
|
545
|
+
this.querySelectorAll('ful-spinner').forEach(el => {
|
|
546
|
+
el[spin ? 'show' : 'hide']();
|
|
547
|
+
});
|
|
548
|
+
this.querySelectorAll('[type=submit],[type=reset]').forEach(el => {
|
|
549
|
+
el.disabled = spin;
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
setValues(values) {
|
|
553
|
+
for (let k in values) {
|
|
554
|
+
if (!values.hasOwnProperty(k)) {
|
|
555
|
+
continue;
|
|
556
|
+
}
|
|
557
|
+
Array.from(this.querySelectorAll(`[name='${CSS.escape(k)}']`)).forEach((el) => {
|
|
558
|
+
Form.mutate(this.mutators, el, values[k], k, values);
|
|
390
559
|
});
|
|
391
560
|
}
|
|
392
561
|
}
|
|
562
|
+
getValues() {
|
|
563
|
+
return Array.from(this.querySelectorAll(this.valueHoldersSelector))
|
|
564
|
+
.filter((el) => {
|
|
565
|
+
if (el.dataset['fulBindInclude'] === 'never') {
|
|
566
|
+
return false;
|
|
567
|
+
}
|
|
568
|
+
return el.dataset['fulBindInclude'] === 'always' || el.closest(this.ignoredChildrenSelector) === null;
|
|
569
|
+
})
|
|
570
|
+
.reduce((result, el) => {
|
|
571
|
+
return Form.providePath(result, el.getAttribute('name'), Form.extract(this.extractors, el));
|
|
572
|
+
}, {});
|
|
573
|
+
}
|
|
574
|
+
setErrors(errors, scroll) {
|
|
575
|
+
this.clearErrors();
|
|
576
|
+
errors
|
|
577
|
+
.filter((e) => e.type === 'FIELD_ERROR' || e.type === 'INVALID_FORMAT')
|
|
578
|
+
.forEach((e) => {
|
|
579
|
+
const name = e.context.replace("[", ".").replace("].", ".");
|
|
580
|
+
this.querySelectorAll(`[name='${CSS.escape(name)}']`)
|
|
581
|
+
.forEach(input => {
|
|
582
|
+
input.classList.add('is-invalid');
|
|
583
|
+
if (input.parentElement.classList.contains("form-floating")) {
|
|
584
|
+
input.parentElement.classList.add('is-invalid');
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
this.querySelectorAll(`ful-field-error[field='${CSS.escape(name)}']`)
|
|
588
|
+
.forEach(el => el.innerText = e.reason);
|
|
589
|
+
});
|
|
590
|
+
this.querySelectorAll("ful-errors")
|
|
591
|
+
.forEach(el => {
|
|
592
|
+
const globalErrors = errors.filter((e) => e.type !== 'FIELD_ERROR' && e.type !== 'INVALID_FORMAT');
|
|
593
|
+
el.innerHTML = globalErrors.map(e => e.reason).join("\n");
|
|
594
|
+
if (globalErrors.length !== 0) {
|
|
595
|
+
el.classList.remove('d-none');
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
if (!scroll) {
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
const ys = Array.from(this.querySelectorAll('ful-field-error:not(.d-none)'))
|
|
603
|
+
.map(el => el.getBoundingClientRect().y + window.scrollY);
|
|
604
|
+
const miny = Math.min(...ys);
|
|
605
|
+
if (miny !== Infinity) {
|
|
606
|
+
window.scroll(window.scrollX, miny > 100 ? miny - 100 : 0);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
clearErrors() {
|
|
610
|
+
this.querySelectorAll('[name].is-invalid, .form-floating.is-invalid')
|
|
611
|
+
.forEach(el => el.classList.remove('is-invalid'));
|
|
612
|
+
this.querySelectorAll("ful-errors")
|
|
613
|
+
.forEach(el => {
|
|
614
|
+
el.innerHTML = '';
|
|
615
|
+
el.classList.add('d-none');
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
static extract(extractors, el) {
|
|
619
|
+
const maybeExtractor = extractors[el.dataset['fulBindExtractor']] || extractors[el.dataset['fulBindProvide']];
|
|
620
|
+
if (maybeExtractor) {
|
|
621
|
+
return maybeExtractor(el);
|
|
622
|
+
}
|
|
623
|
+
if (el.getAttribute('type') === 'radio') {
|
|
624
|
+
if (!el.checked) {
|
|
625
|
+
return undefined;
|
|
626
|
+
}
|
|
627
|
+
return el.dataset['fulBindType'] === 'boolean' ? el.value === 'true' : el.value;
|
|
628
|
+
}
|
|
629
|
+
if (el.getAttribute('type') === 'checkbox') {
|
|
630
|
+
return el.checked;
|
|
631
|
+
}
|
|
632
|
+
if (el.dataset['fulBindType'] === 'boolean') {
|
|
633
|
+
return !el.value ? null : el.value === 'true';
|
|
634
|
+
}
|
|
635
|
+
if (el.getValue) {
|
|
636
|
+
return el.getValue();
|
|
637
|
+
}
|
|
638
|
+
return el.value || null;
|
|
639
|
+
}
|
|
640
|
+
static mutate(mutators, el, raw, key, values) {
|
|
641
|
+
const maybeMutator = mutators[el.dataset['fulBindMutator']] || mutators[el.dataset['fulBindProvide']];
|
|
642
|
+
if (maybeMutator) {
|
|
643
|
+
maybeMutator(el, raw, key, values);
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
if (el.getAttribute('type') === 'radio') {
|
|
647
|
+
el.checked = el.getAttribute('value') === raw;
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
if (el.getAttribute('type') === 'checkbox') {
|
|
651
|
+
el.checked = raw;
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
if (el.setValue) {
|
|
655
|
+
el.setValue(raw);
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
el.value = raw;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
static providePath(result, path, value) {
|
|
662
|
+
const keys = path.split(".").map((k) => k.match(/^[0-9]+$/) ? +k : k);
|
|
663
|
+
let current = result;
|
|
664
|
+
let previous = null;
|
|
665
|
+
for (let i = 0; ; ++i) {
|
|
666
|
+
const ckey = keys[i];
|
|
667
|
+
const pkey = keys[i - 1];
|
|
668
|
+
if (Number.isInteger(ckey) && !Array.isArray(current)) {
|
|
669
|
+
if (previous !== null) {
|
|
670
|
+
previous[pkey] = current = [];
|
|
671
|
+
} else {
|
|
672
|
+
result = current = [];
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
if (i === keys.length - 1) {
|
|
676
|
+
//when value is undefined we only want to define the property if it's not defined
|
|
677
|
+
current[ckey] = value !== undefined ? value : (ckey in current ? current[ckey] : null);
|
|
678
|
+
return result;
|
|
679
|
+
}
|
|
680
|
+
if (current[ckey] === undefined) {
|
|
681
|
+
current[ckey] = {};
|
|
682
|
+
}
|
|
683
|
+
previous = current;
|
|
684
|
+
current = current[ckey];
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
static custom(tagName, configuration) {
|
|
688
|
+
customElements.define(tagName, class extends Form {
|
|
689
|
+
constructor() {
|
|
690
|
+
super(configuration);
|
|
691
|
+
}
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
static configure(configuration) {
|
|
695
|
+
FieldError.configure();
|
|
696
|
+
Errors.configure();
|
|
697
|
+
Spinner.configure();
|
|
698
|
+
Input.configure();
|
|
699
|
+
Select.configure();
|
|
700
|
+
Form.custom('ful-form', configuration || {});
|
|
701
|
+
}
|
|
393
702
|
}
|
|
394
703
|
|
|
395
704
|
class Storage {
|
|
@@ -454,22 +763,22 @@ class VersionedStorage {
|
|
|
454
763
|
|
|
455
764
|
class AuthorizationCodeFlow {
|
|
456
765
|
static forKeycloak(clientId, realmBaseUrl, redirectUri){
|
|
457
|
-
const authUri = new URL("protocol/openid-connect/auth", realmBaseUrl);
|
|
458
|
-
const tokenUri = new URL("protocol/openid-connect/token", realmBaseUrl);
|
|
459
|
-
const logoutUri = new URL("protocol/openid-connect/logout", realmBaseUrl);
|
|
460
766
|
const scope = "openid profile";
|
|
461
|
-
return new AuthorizationCodeFlow(clientId, scope,
|
|
462
|
-
|
|
463
|
-
|
|
767
|
+
return new AuthorizationCodeFlow(clientId, scope, {
|
|
768
|
+
auth: new URL("protocol/openid-connect/auth", realmBaseUrl),
|
|
769
|
+
token: new URL("protocol/openid-connect/token", realmBaseUrl),
|
|
770
|
+
logout: new URL("protocol/openid-connect/logout", realmBaseUrl),
|
|
771
|
+
registration: new URL("protocol/openid-connect/registrations", realmBaseUrl),
|
|
772
|
+
redirect: redirectUri
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
constructor(clientId, scope, {auth, token, registration, logout, redirect}) {
|
|
776
|
+
this.storage = new SessionStorage(clientId);
|
|
464
777
|
this.clientId = clientId;
|
|
465
778
|
this.scope = scope;
|
|
466
|
-
this.
|
|
467
|
-
this.tokenUri = tokenUri;
|
|
468
|
-
this.logoutUri = logoutUri;
|
|
469
|
-
this.redirectUri = redirectUri;
|
|
470
|
-
this.storage = new SessionStorage(clientId);
|
|
779
|
+
this.uri = {auth, token, registration, logout, redirect};
|
|
471
780
|
}
|
|
472
|
-
async
|
|
781
|
+
async action(uri, additionalParams){
|
|
473
782
|
const pkceVerifier = Base64.encode(crypto.getRandomValues(new Uint8Array(32)).buffer);
|
|
474
783
|
const pkceChallenge = Base64.encode(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(pkceVerifier)));
|
|
475
784
|
const state = this.clientId + Base64.encode(crypto.getRandomValues(new Uint8Array(16)).buffer);
|
|
@@ -477,23 +786,34 @@ class AuthorizationCodeFlow {
|
|
|
477
786
|
state: state,
|
|
478
787
|
verifier: pkceVerifier
|
|
479
788
|
});
|
|
480
|
-
const url = new URL(
|
|
789
|
+
const url = new URL(uri);
|
|
481
790
|
url.searchParams.set("client_id", this.clientId);
|
|
482
|
-
url.searchParams.set("redirect_uri", this.
|
|
791
|
+
url.searchParams.set("redirect_uri", this.uri.redirect);
|
|
483
792
|
url.searchParams.set("response_type", 'code');
|
|
484
793
|
url.searchParams.set("scope", this.scope);
|
|
485
794
|
url.searchParams.set("state", state);
|
|
486
795
|
url.searchParams.set("code_challenge", pkceChallenge);
|
|
487
796
|
url.searchParams.set("code_challenge_method", 'S256');
|
|
797
|
+
Object.entries(additionalParams || {}).forEach(kv => {
|
|
798
|
+
url.searchParams.set(kv[0], kv[1]);
|
|
799
|
+
});
|
|
488
800
|
window.location = url;
|
|
489
801
|
}
|
|
802
|
+
async registration(additionalParams){
|
|
803
|
+
await this.action(this.uri.registration, additionalParams);
|
|
804
|
+
}
|
|
805
|
+
async applicationInitiatedAction(kcAction){
|
|
806
|
+
await this.action(this.uri.auth, {
|
|
807
|
+
kc_action: kcAction
|
|
808
|
+
});
|
|
809
|
+
}
|
|
490
810
|
async _tokenExchange(code, state) {
|
|
491
|
-
window.history.replaceState('', "", this.
|
|
811
|
+
window.history.replaceState('', "", this.uri.redirect);
|
|
492
812
|
const stateAndVerifier = this.storage.pop(AuthorizationCodeFlow.PKCE_AND_STATE_KEY);
|
|
493
813
|
if (stateAndVerifier.state !== state) {
|
|
494
814
|
throw new Error("State mismatch");
|
|
495
815
|
}
|
|
496
|
-
const response = await fetch(this.
|
|
816
|
+
const response = await fetch(this.uri.token, {
|
|
497
817
|
method: "POST",
|
|
498
818
|
headers: {
|
|
499
819
|
"Content-Type": 'application/x-www-form-urlencoded'
|
|
@@ -504,7 +824,7 @@ class AuthorizationCodeFlow {
|
|
|
504
824
|
["grant_type", "authorization_code"],
|
|
505
825
|
["code_verifier", stateAndVerifier.verifier],
|
|
506
826
|
["state", stateAndVerifier.state],
|
|
507
|
-
["redirect_uri", this.
|
|
827
|
+
["redirect_uri", this.uri.redirect]
|
|
508
828
|
])
|
|
509
829
|
});
|
|
510
830
|
if (!response.ok) {
|
|
@@ -512,7 +832,7 @@ class AuthorizationCodeFlow {
|
|
|
512
832
|
throw new Error("Error:" + response.status + ": " + text);
|
|
513
833
|
}
|
|
514
834
|
const token = await response.json();
|
|
515
|
-
return new AuthorizationCodeFlowSession(this.clientId, token, this.
|
|
835
|
+
return new AuthorizationCodeFlowSession(this.clientId, token, this.uri);
|
|
516
836
|
}
|
|
517
837
|
async ensureLoggedIn() {
|
|
518
838
|
const url = new URL(window.location.href);
|
|
@@ -523,7 +843,7 @@ class AuthorizationCodeFlow {
|
|
|
523
843
|
return await this._tokenExchange(code, state);
|
|
524
844
|
}
|
|
525
845
|
//if not authorized
|
|
526
|
-
await this.
|
|
846
|
+
await this.action(this.uri.auth, {});
|
|
527
847
|
return null;
|
|
528
848
|
}
|
|
529
849
|
}
|
|
@@ -532,27 +852,26 @@ AuthorizationCodeFlow.PKCE_AND_STATE_KEY = "state-and-verifier";
|
|
|
532
852
|
class AuthorizationCodeFlowSession {
|
|
533
853
|
static parseToken(token) {
|
|
534
854
|
const [rawHeader, rawPayload, signature] = token.split(".");
|
|
855
|
+
const ut8decoder = new TextDecoder("utf-8");
|
|
535
856
|
return {
|
|
536
|
-
header: JSON.parse(
|
|
537
|
-
payload: JSON.parse(
|
|
857
|
+
header: JSON.parse(ut8decoder.decode(Base64.decode(rawHeader, Base64.STANDARD))),
|
|
858
|
+
payload: JSON.parse(ut8decoder.decode(Base64.decode(rawPayload, Base64.STANDARD))),
|
|
538
859
|
signature: signature
|
|
539
860
|
};
|
|
540
861
|
}
|
|
541
|
-
constructor(clientId,
|
|
862
|
+
constructor(clientId, t, {token, logout, redirect}) {
|
|
542
863
|
this.clientId = clientId;
|
|
543
|
-
this.token =
|
|
544
|
-
this.
|
|
545
|
-
this.
|
|
546
|
-
this.
|
|
547
|
-
this.accessToken = AuthorizationCodeFlowSession.parseToken(token.access_token);
|
|
548
|
-
this.refreshToken = AuthorizationCodeFlowSession.parseToken(token.refresh_token);
|
|
864
|
+
this.token = t;
|
|
865
|
+
this.accessToken = AuthorizationCodeFlowSession.parseToken(t.access_token);
|
|
866
|
+
this.refreshToken = AuthorizationCodeFlowSession.parseToken(t.refresh_token);
|
|
867
|
+
this.uri = { token, logout, redirect };
|
|
549
868
|
this.refreshCallback = null;
|
|
550
869
|
}
|
|
551
870
|
onRefresh(callback) {
|
|
552
871
|
this.refreshCallback = callback;
|
|
553
872
|
}
|
|
554
873
|
async refresh() {
|
|
555
|
-
const response = await fetch(this.
|
|
874
|
+
const response = await fetch(this.uri.token, {
|
|
556
875
|
method: "POST",
|
|
557
876
|
headers: {
|
|
558
877
|
"Content-Type": 'application/x-www-form-urlencoded'
|
|
@@ -564,7 +883,7 @@ class AuthorizationCodeFlowSession {
|
|
|
564
883
|
])
|
|
565
884
|
});
|
|
566
885
|
if (!response.ok) {
|
|
567
|
-
throw new Error("Error:" + response.
|
|
886
|
+
throw new Error("Error:" + response.status + ": " + response.text());
|
|
568
887
|
}
|
|
569
888
|
const token = await response.json();
|
|
570
889
|
this.token = token;
|
|
@@ -588,8 +907,8 @@ class AuthorizationCodeFlowSession {
|
|
|
588
907
|
await this.refresh();
|
|
589
908
|
}
|
|
590
909
|
logout() {
|
|
591
|
-
const url = new URL(this.
|
|
592
|
-
url.searchParams.set("post_logout_redirect_uri", this.
|
|
910
|
+
const url = new URL(this.uri.logout);
|
|
911
|
+
url.searchParams.set("post_logout_redirect_uri", this.uri.redirect);
|
|
593
912
|
url.searchParams.set("id_token_hint", this.token.id_token);
|
|
594
913
|
window.location = url;
|
|
595
914
|
}
|
|
@@ -609,13 +928,12 @@ class AuthorizationCodeFlowInterceptor {
|
|
|
609
928
|
this.gracePeriodBefore = gracePeriodBefore || 2000;
|
|
610
929
|
this.gracePeriodAfter = gracePeriodAfter || 30000;
|
|
611
930
|
}
|
|
612
|
-
async
|
|
931
|
+
async intercept(request, chain) {
|
|
613
932
|
await this.session.refreshIf(this.gracePeriodBefore);
|
|
614
933
|
const headers = new Headers(request.options.headers);
|
|
615
934
|
headers.set("Authorization", this.session.bearerToken());
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
async after(request, response) {
|
|
935
|
+
request.options.headers = headers;
|
|
936
|
+
const response = await chain.proceed(request);
|
|
619
937
|
await this.session.refreshIf(this.gracePeriodAfter);
|
|
620
938
|
return response;
|
|
621
939
|
}
|
|
@@ -703,10 +1021,10 @@ const timing = {
|
|
|
703
1021
|
}
|
|
704
1022
|
};
|
|
705
1023
|
|
|
706
|
-
class Wizard {
|
|
707
|
-
constructor(
|
|
708
|
-
|
|
709
|
-
this.progress = [...
|
|
1024
|
+
class Wizard extends HTMLElement {
|
|
1025
|
+
constructor() {
|
|
1026
|
+
super();
|
|
1027
|
+
this.progress = [...this.children].filter(e => e.matches("header,ol,ul"));
|
|
710
1028
|
|
|
711
1029
|
this.progress.forEach(p => {
|
|
712
1030
|
const children = [...p.children];
|
|
@@ -715,8 +1033,8 @@ class Wizard {
|
|
|
715
1033
|
children[0].classList.add('active');
|
|
716
1034
|
}
|
|
717
1035
|
});
|
|
718
|
-
if (this.
|
|
719
|
-
const firstSection = this.
|
|
1036
|
+
if (this.querySelector('section.current') === null) {
|
|
1037
|
+
const firstSection = this.querySelector('section:first-of-type');
|
|
720
1038
|
if (firstSection !== null) {
|
|
721
1039
|
firstSection.classList.add('current');
|
|
722
1040
|
}
|
|
@@ -729,11 +1047,11 @@ class Wizard {
|
|
|
729
1047
|
current?.classList.remove('active');
|
|
730
1048
|
current?.nextElementSibling?.classList.add('active');
|
|
731
1049
|
});
|
|
732
|
-
const currentSection = this.
|
|
1050
|
+
const currentSection = this.querySelector('section.current');
|
|
733
1051
|
currentSection.classList.remove("current");
|
|
734
1052
|
currentSection.nextElementSibling.classList.add('current');
|
|
735
1053
|
|
|
736
|
-
this.
|
|
1054
|
+
this.dispatchEvent(new CustomEvent('wizard:activate', {
|
|
737
1055
|
bubbles: true,
|
|
738
1056
|
cancelable: true
|
|
739
1057
|
}));
|
|
@@ -746,10 +1064,10 @@ class Wizard {
|
|
|
746
1064
|
current?.classList.remove('active');
|
|
747
1065
|
current?.previousElementSibling?.classList.add('active');
|
|
748
1066
|
});
|
|
749
|
-
const currentSection = this.
|
|
1067
|
+
const currentSection = this.querySelector('section.current');
|
|
750
1068
|
currentSection.classList.remove("current");
|
|
751
1069
|
currentSection.previousElementSibling.classList.add('current');
|
|
752
|
-
this.
|
|
1070
|
+
this.dispatchEvent(new CustomEvent('wizard:activate', {
|
|
753
1071
|
bubbles: true,
|
|
754
1072
|
cancelable: true
|
|
755
1073
|
}));
|
|
@@ -761,15 +1079,25 @@ class Wizard {
|
|
|
761
1079
|
current?.classList.remove('active');
|
|
762
1080
|
p.children[+n]?.classList.add('active');
|
|
763
1081
|
});
|
|
764
|
-
const currentSection = this.
|
|
1082
|
+
const currentSection = this.querySelector('section.current');
|
|
765
1083
|
currentSection?.classList.remove("current");
|
|
766
|
-
const nthSection = this.
|
|
1084
|
+
const nthSection = this.querySelector(`section:nth-child(${+n})`);
|
|
767
1085
|
nthSection.classList.add('current');
|
|
768
|
-
this.
|
|
1086
|
+
this.dispatchEvent(new CustomEvent('wizard:activate', {
|
|
769
1087
|
bubbles: true,
|
|
770
1088
|
cancelable: true
|
|
771
1089
|
}));
|
|
772
1090
|
}
|
|
1091
|
+
static custom(tagName, configuration) {
|
|
1092
|
+
customElements.define(tagName, class extends Wizard {
|
|
1093
|
+
constructor() {
|
|
1094
|
+
super(configuration);
|
|
1095
|
+
}
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1098
|
+
static configure() {
|
|
1099
|
+
return Wizard.custom('ful-wizard');
|
|
1100
|
+
}
|
|
773
1101
|
}
|
|
774
1102
|
|
|
775
1103
|
class App {
|
|
@@ -798,5 +1126,5 @@ class App {
|
|
|
798
1126
|
}
|
|
799
1127
|
}
|
|
800
1128
|
|
|
801
|
-
export { App, AuthorizationCodeFlow, AuthorizationCodeFlowInterceptor, AuthorizationCodeFlowSession, Base64,
|
|
1129
|
+
export { App, AuthorizationCodeFlow, AuthorizationCodeFlowInterceptor, AuthorizationCodeFlowSession, Base64, CustomElements, Errors, Failure, FieldError, Form, Hex, HttpClient, Input, LocalStorage, Observable, Select, SessionStorage, Spinner, VersionedStorage, Wizard, timing };
|
|
802
1130
|
//# sourceMappingURL=ful.mjs.map
|