@optionfactory/ful 0.90.0 → 0.92.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.iife.js +243 -80
- 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 +243 -80
- package/dist/ful.mjs.map +1 -1
- package/package.json +7 -4
package/dist/ful.mjs
CHANGED
|
@@ -70,21 +70,51 @@ class Hex {
|
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
/**
|
|
74
|
+
* @typedef {{ type: string; context: string?; reason: string; details: any?; }} Problem
|
|
75
|
+
*/
|
|
73
76
|
class Failure extends Error {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
+
/**
|
|
78
|
+
*
|
|
79
|
+
* @param {string} message
|
|
80
|
+
* @param {Problem[]} problems
|
|
81
|
+
* @param {*} cause
|
|
82
|
+
*/
|
|
83
|
+
constructor(message, problems, cause) {
|
|
84
|
+
super(message, { cause });
|
|
85
|
+
this.name = 'Failure';
|
|
77
86
|
this.problems = problems;
|
|
78
87
|
}
|
|
79
88
|
}
|
|
80
89
|
|
|
90
|
+
/**
|
|
91
|
+
* @typedef {Int8Array| Uint8Array| Uint8ClampedArray| Int16Array| Uint16Array| Int32Array| Uint32Array| Float32Array| Float64Array| BigInt64Array| BigUint64Array} TypedArray
|
|
92
|
+
*/
|
|
93
|
+
/**
|
|
94
|
+
* @typedef HttpInterceptor
|
|
95
|
+
* @property {function(Request,HttpInterceptorChain):Promise<Response>} intercept
|
|
96
|
+
*/
|
|
97
|
+
|
|
81
98
|
class HttpClientError extends Failure {
|
|
82
|
-
|
|
83
|
-
|
|
99
|
+
/**
|
|
100
|
+
* @param {string} message
|
|
101
|
+
* @param {number} status
|
|
102
|
+
* @param {{ type: string; context: string?; reason: string; details: any?; }[]} problems
|
|
103
|
+
* @param {Error|undefined} [cause]
|
|
104
|
+
*/
|
|
105
|
+
constructor(message, status, problems, cause) {
|
|
106
|
+
super(message, problems, cause);
|
|
107
|
+
this.name = 'HttpClientError';
|
|
84
108
|
this.status = status;
|
|
85
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
*
|
|
112
|
+
* @param {string} type
|
|
113
|
+
* @param {any} cause
|
|
114
|
+
* @returns
|
|
115
|
+
*/
|
|
86
116
|
static of(type, cause) {
|
|
87
|
-
return new HttpClientError(0, [{
|
|
117
|
+
return new HttpClientError(cause.message, 0, [{
|
|
88
118
|
type,
|
|
89
119
|
context: null,
|
|
90
120
|
reason: cause.message,
|
|
@@ -98,47 +128,61 @@ class HttpClientError extends Failure {
|
|
|
98
128
|
*/
|
|
99
129
|
static async fromResponse(response) {
|
|
100
130
|
const text = await response.text();
|
|
101
|
-
const
|
|
131
|
+
const message = `${response.status} ${response.statusText}: ${text}`;
|
|
132
|
+
const fallback = [{
|
|
102
133
|
type: "GENERIC_PROBLEM",
|
|
103
134
|
context: null,
|
|
104
|
-
reason:
|
|
135
|
+
reason: message,
|
|
105
136
|
details: null
|
|
106
137
|
}];
|
|
107
138
|
try {
|
|
108
|
-
return new HttpClientError(response.status, text ? JSON.parse(text) :
|
|
139
|
+
return new HttpClientError(message, response.status, text ? JSON.parse(text) : fallback);
|
|
109
140
|
} catch (e) {
|
|
110
|
-
return new HttpClientError(response.status,
|
|
141
|
+
return new HttpClientError(message, response.status, fallback);
|
|
111
142
|
}
|
|
112
143
|
}
|
|
113
144
|
}
|
|
114
145
|
|
|
146
|
+
/**
|
|
147
|
+
* @implements {HttpInterceptor}
|
|
148
|
+
*/
|
|
115
149
|
class CsrfTokenInterceptor {
|
|
116
150
|
#k; #v;
|
|
117
151
|
constructor() {
|
|
118
|
-
this.#k = document.querySelector("meta[name='_csrf_header']")
|
|
119
|
-
this.#v = document.querySelector("meta[name='_csrf']")
|
|
120
|
-
}
|
|
152
|
+
this.#k = document.querySelector("meta[name='_csrf_header']")?.getAttribute("content");
|
|
153
|
+
this.#v = document.querySelector("meta[name='_csrf']")?.getAttribute("content");
|
|
154
|
+
}
|
|
121
155
|
async intercept(request, chain) {
|
|
122
|
-
|
|
156
|
+
if(this.#k && this.#v) {
|
|
157
|
+
request.headers.set(this.#k, this.#v);
|
|
158
|
+
}
|
|
123
159
|
return await chain.proceed(request);
|
|
124
160
|
}
|
|
125
161
|
}
|
|
126
|
-
|
|
162
|
+
/**
|
|
163
|
+
* @implements {HttpInterceptor}
|
|
164
|
+
*/
|
|
127
165
|
class RedirectOnUnauthorizedInterceptor {
|
|
128
166
|
#redirectUri;
|
|
167
|
+
/**
|
|
168
|
+
* @param {string} redirectUri
|
|
169
|
+
*/
|
|
129
170
|
constructor(redirectUri) {
|
|
130
171
|
this.#redirectUri = redirectUri;
|
|
131
172
|
}
|
|
132
173
|
async intercept(request, chain) {
|
|
133
174
|
const response = await chain.proceed(request);
|
|
134
|
-
if (response.status
|
|
135
|
-
|
|
175
|
+
if (response.status === 401) {
|
|
176
|
+
window.location.href = this.#redirectUri;
|
|
136
177
|
}
|
|
137
|
-
|
|
178
|
+
return response;
|
|
138
179
|
}
|
|
139
180
|
}
|
|
140
181
|
|
|
141
182
|
class HttpClientBuilder {
|
|
183
|
+
/**
|
|
184
|
+
* @type {HttpInterceptor[]}
|
|
185
|
+
*/
|
|
142
186
|
#interceptors;
|
|
143
187
|
constructor() {
|
|
144
188
|
this.#interceptors = [];
|
|
@@ -151,6 +195,9 @@ class HttpClientBuilder {
|
|
|
151
195
|
this.#interceptors.push(new RedirectOnUnauthorizedInterceptor(redirectUri));
|
|
152
196
|
return this;
|
|
153
197
|
}
|
|
198
|
+
/**
|
|
199
|
+
* @param {...HttpInterceptor} interceptors
|
|
200
|
+
*/
|
|
154
201
|
withInterceptors(...interceptors) {
|
|
155
202
|
this.#interceptors.push(...interceptors);
|
|
156
203
|
return this;
|
|
@@ -160,27 +207,35 @@ class HttpClientBuilder {
|
|
|
160
207
|
}
|
|
161
208
|
}
|
|
162
209
|
|
|
210
|
+
/**
|
|
211
|
+
* @implements {HttpInterceptor}
|
|
212
|
+
*/
|
|
163
213
|
class HttpCall {
|
|
164
|
-
/**
|
|
165
|
-
*
|
|
166
|
-
* @async
|
|
167
|
-
* @param {Request} request
|
|
168
|
-
* @param {HttpInterceptorChain} chain
|
|
169
|
-
* @returns {Promise<Response>} the response
|
|
170
|
-
*/
|
|
171
214
|
async intercept(request, chain) {
|
|
172
215
|
return await fetch(request);
|
|
173
216
|
}
|
|
174
217
|
}
|
|
175
218
|
|
|
176
219
|
class HttpInterceptorChain {
|
|
220
|
+
#interceptors;
|
|
221
|
+
#current;
|
|
222
|
+
/**
|
|
223
|
+
*
|
|
224
|
+
* @param {HttpInterceptor[]} interceptors
|
|
225
|
+
* @param {number} current
|
|
226
|
+
*/
|
|
177
227
|
constructor(interceptors, current) {
|
|
178
|
-
this
|
|
179
|
-
this
|
|
228
|
+
this.#interceptors = interceptors;
|
|
229
|
+
this.#current = current;
|
|
180
230
|
}
|
|
231
|
+
/**
|
|
232
|
+
*
|
|
233
|
+
* @param {Request} request
|
|
234
|
+
* @returns {Promise<Response>} the response
|
|
235
|
+
*/
|
|
181
236
|
async proceed(request) {
|
|
182
|
-
const interceptor = this
|
|
183
|
-
return await interceptor.intercept(request, new HttpInterceptorChain(this
|
|
237
|
+
const interceptor = this.#interceptors[this.#current];
|
|
238
|
+
return await interceptor.intercept(request, new HttpInterceptorChain(this.#interceptors, this.#current + 1));
|
|
184
239
|
}
|
|
185
240
|
}
|
|
186
241
|
|
|
@@ -188,14 +243,14 @@ class HttpClient {
|
|
|
188
243
|
#interceptors;
|
|
189
244
|
/**
|
|
190
245
|
* Creates a builder for an HttpClient.
|
|
191
|
-
* @returns {
|
|
246
|
+
* @returns {HttpClientBuilder} the client builder
|
|
192
247
|
*/
|
|
193
248
|
static builder() {
|
|
194
249
|
return new HttpClientBuilder();
|
|
195
250
|
}
|
|
196
251
|
/**
|
|
197
252
|
* Creates an HttpClient.
|
|
198
|
-
* @
|
|
253
|
+
* @param {HttpInterceptor[]|undefined} interceptors - a list of interceptors to be registered for every request performed by the created client.
|
|
199
254
|
*/
|
|
200
255
|
constructor(interceptors) {
|
|
201
256
|
this.#interceptors = interceptors || [];
|
|
@@ -205,7 +260,7 @@ class HttpClient {
|
|
|
205
260
|
* @async
|
|
206
261
|
* @param {string} uri - the (possibly relative) request url
|
|
207
262
|
* @param {RequestInit|undefined} options - fetch options
|
|
208
|
-
* @param {[
|
|
263
|
+
* @param {HttpInterceptor[]|undefined} interceptors - the HttpInterceptors to be registered for this exchange.
|
|
209
264
|
* @returns {Promise<Response>} the response
|
|
210
265
|
*/
|
|
211
266
|
async exchange(uri, options, interceptors) {
|
|
@@ -272,12 +327,17 @@ class HttpClient {
|
|
|
272
327
|
}
|
|
273
328
|
}
|
|
274
329
|
|
|
275
|
-
|
|
330
|
+
/**
|
|
331
|
+
*
|
|
332
|
+
* @param {Response} response
|
|
333
|
+
* @param {'text'|'json'|'blob'|'arrayBuffer'} type
|
|
334
|
+
* @returns
|
|
335
|
+
*/
|
|
276
336
|
const unmarshal = async (response, type) => {
|
|
277
337
|
try {
|
|
278
338
|
return await response[type]();
|
|
279
|
-
} catch (
|
|
280
|
-
throw HttpClientError.of("UNMARSHALING_PROBLEM",
|
|
339
|
+
} catch (ex) {
|
|
340
|
+
throw HttpClientError.of("UNMARSHALING_PROBLEM", ex);
|
|
281
341
|
}
|
|
282
342
|
};
|
|
283
343
|
|
|
@@ -319,7 +379,7 @@ class HttpRequestBuilder {
|
|
|
319
379
|
* @param {Headers} headers
|
|
320
380
|
* @param {any} body
|
|
321
381
|
* @param {Omit<RequestInit,"headers"|"method"|"body">} options
|
|
322
|
-
* @param {[
|
|
382
|
+
* @param {HttpInterceptor[]} interceptors
|
|
323
383
|
*/
|
|
324
384
|
constructor(client, method, uri, params, headers, body, options, interceptors) {
|
|
325
385
|
this.#client = client;
|
|
@@ -333,7 +393,7 @@ class HttpRequestBuilder {
|
|
|
333
393
|
}
|
|
334
394
|
/**
|
|
335
395
|
* Add all passed headers to the request, overriding existing ones if that key already exists.
|
|
336
|
-
* @param {
|
|
396
|
+
* @param {HeadersInit} hs
|
|
337
397
|
* @returns {HttpRequestBuilder} this builder
|
|
338
398
|
*/
|
|
339
399
|
headers(hs) {
|
|
@@ -395,6 +455,18 @@ class HttpRequestBuilder {
|
|
|
395
455
|
this.#body = JSON.stringify(body);
|
|
396
456
|
return this;
|
|
397
457
|
}
|
|
458
|
+
/**
|
|
459
|
+
* Sets the request body as a FormData configured using the callback.
|
|
460
|
+
* `Content-Type: multipart/form-data` header is automatically added by fetch if not explicitly set.
|
|
461
|
+
* @param {function(HttpMultipartRequestCustomizer):void} callback
|
|
462
|
+
*/
|
|
463
|
+
multipart(callback) {
|
|
464
|
+
const formData = new FormData();
|
|
465
|
+
const builder = new HttpMultipartRequestCustomizer(formData);
|
|
466
|
+
callback(builder);
|
|
467
|
+
this.#body = formData;
|
|
468
|
+
return this;
|
|
469
|
+
}
|
|
398
470
|
/**
|
|
399
471
|
* Sets a fetch options for the request.
|
|
400
472
|
* @param {Omit<RequestInit,"headers"|"method"|"body">} kvs
|
|
@@ -402,6 +474,7 @@ class HttpRequestBuilder {
|
|
|
402
474
|
*/
|
|
403
475
|
options(kvs) {
|
|
404
476
|
for (const [k, v] of Object.entries(kvs)) {
|
|
477
|
+
// @ts-ignore
|
|
405
478
|
this.#options[k] = v;
|
|
406
479
|
}
|
|
407
480
|
return this;
|
|
@@ -468,11 +541,11 @@ class HttpRequestBuilder {
|
|
|
468
541
|
throw await HttpClientError.fromResponse(response);
|
|
469
542
|
}
|
|
470
543
|
return response;
|
|
471
|
-
} catch (
|
|
472
|
-
if (
|
|
473
|
-
throw
|
|
544
|
+
} catch (ex) {
|
|
545
|
+
if (ex instanceof Failure) {
|
|
546
|
+
throw ex;
|
|
474
547
|
}
|
|
475
|
-
throw HttpClientError.of("CONNECTION_PROBLEM",
|
|
548
|
+
throw HttpClientError.of("CONNECTION_PROBLEM", ex);
|
|
476
549
|
}
|
|
477
550
|
}
|
|
478
551
|
/**
|
|
@@ -491,14 +564,6 @@ class HttpRequestBuilder {
|
|
|
491
564
|
const response = await this.fetch();
|
|
492
565
|
return await unmarshal(response, 'json');
|
|
493
566
|
}
|
|
494
|
-
/**
|
|
495
|
-
* Performs an HTTP exchange using the configured client request, and interceptos throwing a failure when response status is not in the 200-299 range.
|
|
496
|
-
* @returns {Promise<Uint8Array>} the response body, as an Uint8Array
|
|
497
|
-
*/
|
|
498
|
-
async fetchBytes() {
|
|
499
|
-
const response = await this.fetch();
|
|
500
|
-
return await unmarshal(response, 'bytes');
|
|
501
|
-
}
|
|
502
567
|
/**
|
|
503
568
|
* Performs an HTTP exchange using the configured client request, and interceptos throwing a failure when response status is not in the 200-299 range.
|
|
504
569
|
* @returns {Promise<Blob>} the response body, as a Blob
|
|
@@ -517,6 +582,68 @@ class HttpRequestBuilder {
|
|
|
517
582
|
}
|
|
518
583
|
}
|
|
519
584
|
|
|
585
|
+
|
|
586
|
+
class HttpMultipartRequestCustomizer {
|
|
587
|
+
#formData;
|
|
588
|
+
/**
|
|
589
|
+
*
|
|
590
|
+
* @param {FormData} formData
|
|
591
|
+
*/
|
|
592
|
+
constructor(formData){
|
|
593
|
+
this.#formData = formData;
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Appends a value to the FormData.
|
|
597
|
+
* @param {string} name
|
|
598
|
+
* @param {*} value
|
|
599
|
+
* @returns this builder
|
|
600
|
+
*/
|
|
601
|
+
field(name, value){
|
|
602
|
+
this.#formData.append(name, value);
|
|
603
|
+
return this;
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Appends a Blob to the FormData.
|
|
607
|
+
* If `filename` is omitted, FormData defaults are applied:
|
|
608
|
+
* The default filename for Blob objects is "blob";
|
|
609
|
+
* The default filename for File objects is the file's filename.
|
|
610
|
+
* @param {string} name
|
|
611
|
+
* @param {Blob} value
|
|
612
|
+
* @param {string|undefined} filename
|
|
613
|
+
* @returns this builder
|
|
614
|
+
*/
|
|
615
|
+
blob(name, value, filename){
|
|
616
|
+
this.#formData.append(name, value, filename);
|
|
617
|
+
return this;
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* Appends multiple Blobs to the FormData with the same name.
|
|
621
|
+
* The default filename for Blob objects is "blob";
|
|
622
|
+
* The default filename for File objects is the file's filename.
|
|
623
|
+
* @param {string} name
|
|
624
|
+
* @param {Blob[]} values
|
|
625
|
+
* @returns this builder
|
|
626
|
+
*/
|
|
627
|
+
blobs(name, values){
|
|
628
|
+
for(let v of values){
|
|
629
|
+
this.#formData.append(name, v);
|
|
630
|
+
}
|
|
631
|
+
return this;
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Appends a JSON serialized blob to the FormData.
|
|
635
|
+
* @param {string} name
|
|
636
|
+
* @param {any} value
|
|
637
|
+
* @param {string|undefined} filename
|
|
638
|
+
* @returns this builder
|
|
639
|
+
*/
|
|
640
|
+
json(name, value, filename){
|
|
641
|
+
const blob = new Blob([JSON.stringify(value)], {type: 'application/json'});
|
|
642
|
+
this.#formData.append(name, blob, filename);
|
|
643
|
+
return this;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
520
647
|
class Storage {
|
|
521
648
|
constructor(prefix, storage) {
|
|
522
649
|
this.prefix = prefix;
|
|
@@ -613,7 +740,7 @@ class AuthorizationCodeFlow {
|
|
|
613
740
|
Object.entries(additionalParams || {}).forEach(kv => {
|
|
614
741
|
url.searchParams.set(kv[0], kv[1]);
|
|
615
742
|
});
|
|
616
|
-
window.location = url;
|
|
743
|
+
window.location.href = url.toString();
|
|
617
744
|
}
|
|
618
745
|
async registration(additionalParams){
|
|
619
746
|
await this.action(this.uri.registration, additionalParams);
|
|
@@ -623,7 +750,7 @@ class AuthorizationCodeFlow {
|
|
|
623
750
|
kc_action: kcAction
|
|
624
751
|
});
|
|
625
752
|
}
|
|
626
|
-
async
|
|
753
|
+
async #tokenExchange(code, state) {
|
|
627
754
|
window.history.replaceState('', "", this.uri.redirect);
|
|
628
755
|
const stateAndVerifier = this.storage.pop(AuthorizationCodeFlow.PKCE_AND_STATE_KEY);
|
|
629
756
|
if (stateAndVerifier.state !== state) {
|
|
@@ -656,7 +783,7 @@ class AuthorizationCodeFlow {
|
|
|
656
783
|
if (code && this.storage.load(AuthorizationCodeFlow.PKCE_AND_STATE_KEY)) {
|
|
657
784
|
//if callback from keycloak and we have our state still stored
|
|
658
785
|
const state = url.searchParams.get("state");
|
|
659
|
-
return await this
|
|
786
|
+
return await this.#tokenExchange(code, state);
|
|
660
787
|
}
|
|
661
788
|
//if not authorized
|
|
662
789
|
await this.action(this.uri.auth, {});
|
|
@@ -727,7 +854,7 @@ class AuthorizationCodeFlowSession {
|
|
|
727
854
|
const url = new URL(this.uri.logout);
|
|
728
855
|
url.searchParams.set("post_logout_redirect_uri", this.uri.redirect);
|
|
729
856
|
url.searchParams.set("id_token_hint", this.token.id_token);
|
|
730
|
-
window.location = url;
|
|
857
|
+
window.location.href = url.toString();
|
|
731
858
|
}
|
|
732
859
|
|
|
733
860
|
bearerToken() {
|
|
@@ -786,7 +913,7 @@ const timing = {
|
|
|
786
913
|
};
|
|
787
914
|
|
|
788
915
|
return function () {
|
|
789
|
-
args = arguments;
|
|
916
|
+
args = [...arguments];
|
|
790
917
|
previousTimestamp = new Date().getTime();
|
|
791
918
|
if (tid === null) {
|
|
792
919
|
tid = setTimeout(later, timeoutMs);
|
|
@@ -820,7 +947,7 @@ const timing = {
|
|
|
820
947
|
previousTimestamp = now;
|
|
821
948
|
}
|
|
822
949
|
const remaining = timeoutMs - (now - previousTimestamp);
|
|
823
|
-
args = arguments;
|
|
950
|
+
args = [...arguments];
|
|
824
951
|
if (remaining <= 0 || remaining > timeoutMs) {
|
|
825
952
|
if (tid !== null) {
|
|
826
953
|
clearTimeout(tid);
|
|
@@ -926,16 +1053,18 @@ class Attributes {
|
|
|
926
1053
|
.forEach(a => {
|
|
927
1054
|
const target = a.substring(prefix.length);
|
|
928
1055
|
if (target === 'class') {
|
|
929
|
-
|
|
1056
|
+
const classes = from.getAttribute(prefix + "class")?.split(" ").filter(a => a.length) ?? [];
|
|
1057
|
+
to.classList.add(...classes);
|
|
930
1058
|
return;
|
|
931
1059
|
}
|
|
1060
|
+
// @ts-ignore
|
|
932
1061
|
to.setAttribute(target, from.getAttribute(a));
|
|
933
1062
|
});
|
|
934
1063
|
}
|
|
935
1064
|
/**
|
|
936
1065
|
*
|
|
937
1066
|
* @param {HTMLElement} el
|
|
938
|
-
* @param {
|
|
1067
|
+
* @param {string} attr
|
|
939
1068
|
* @param {boolean} value
|
|
940
1069
|
*/
|
|
941
1070
|
static toggle(el, attr, value) {
|
|
@@ -962,19 +1091,21 @@ class LightSlots {
|
|
|
962
1091
|
* @returns the slots
|
|
963
1092
|
*/
|
|
964
1093
|
static from(el) {
|
|
1094
|
+
/** @type [string, Element][] */
|
|
965
1095
|
const namedSlots = Array.from(el.childNodes)
|
|
966
|
-
.filter(el => el
|
|
1096
|
+
.filter(el => el instanceof Element)
|
|
1097
|
+
.filter(el => el.matches('[slot]'))
|
|
967
1098
|
.map(el => {
|
|
968
1099
|
el.remove();
|
|
969
1100
|
const slot = el.getAttribute("slot");
|
|
970
1101
|
el.removeAttribute("slot");
|
|
971
|
-
return [slot, el];
|
|
1102
|
+
return [slot ?? 'unnamed', el];
|
|
972
1103
|
});
|
|
973
1104
|
const slots = {};
|
|
974
1105
|
slots.default = new DocumentFragment();
|
|
975
1106
|
slots.default.append(...el.childNodes);
|
|
976
|
-
for(const [name,el] of namedSlots){
|
|
977
|
-
if(!(name in slots)){
|
|
1107
|
+
for (const [name, el] of namedSlots) {
|
|
1108
|
+
if (!(name in slots)) {
|
|
978
1109
|
slots[name] = new DocumentFragment();
|
|
979
1110
|
}
|
|
980
1111
|
slots[name].append(el);
|
|
@@ -1000,7 +1131,8 @@ class TemplatesRegistry {
|
|
|
1000
1131
|
#ec;
|
|
1001
1132
|
put(k, fragment) {
|
|
1002
1133
|
if (this.#ec) {
|
|
1003
|
-
|
|
1134
|
+
// @ts-ignore
|
|
1135
|
+
this.#idToTemplate[k] = ftl.Template.fromFragment(fragment, this.#ec);
|
|
1004
1136
|
return;
|
|
1005
1137
|
}
|
|
1006
1138
|
this.#idToFragment[k] = fragment;
|
|
@@ -1019,6 +1151,7 @@ class TemplatesRegistry {
|
|
|
1019
1151
|
this.#ec = ec;
|
|
1020
1152
|
for (const [k, fragment] of Object.entries(this.#idToFragment)) {
|
|
1021
1153
|
delete this.#idToFragment[k];
|
|
1154
|
+
// @ts-ignore
|
|
1022
1155
|
this.#idToTemplate[k] = ftl.Template.fromFragment(fragment, ec, ...data);
|
|
1023
1156
|
}
|
|
1024
1157
|
}
|
|
@@ -1122,8 +1255,8 @@ const ParsedElement = (conf) => {
|
|
|
1122
1255
|
#initialized;
|
|
1123
1256
|
#reflecting;
|
|
1124
1257
|
#internals;
|
|
1125
|
-
constructor(
|
|
1126
|
-
super(
|
|
1258
|
+
constructor() {
|
|
1259
|
+
super();
|
|
1127
1260
|
this.#internals = this.attachInternals();
|
|
1128
1261
|
}
|
|
1129
1262
|
get initialized() {
|
|
@@ -1151,6 +1284,7 @@ const ParsedElement = (conf) => {
|
|
|
1151
1284
|
observer.disconnect();
|
|
1152
1285
|
upgradeQueue.enqueue(this);
|
|
1153
1286
|
});
|
|
1287
|
+
// @ts-ignore
|
|
1154
1288
|
observer.observe(this.parentNode, { childList: true, subtree: true });
|
|
1155
1289
|
}
|
|
1156
1290
|
attributeChangedCallback(attr, oldValue, newValue) {
|
|
@@ -1176,6 +1310,7 @@ const ParsedElement = (conf) => {
|
|
|
1176
1310
|
return;
|
|
1177
1311
|
}
|
|
1178
1312
|
this.#parsed = true;
|
|
1313
|
+
// @ts-ignore
|
|
1179
1314
|
await this.render(elements.template(templateId), slots ? LightSlots.from(this) : undefined);
|
|
1180
1315
|
|
|
1181
1316
|
for (const [attr, mapper] of attrsAndMappers) {
|
|
@@ -1278,6 +1413,7 @@ class Form extends ParsedElement() {
|
|
|
1278
1413
|
static IGNORED_CHILDREN_SELECTOR = '.d-none, [hidden]';
|
|
1279
1414
|
static SCROLL_OFFSET = 50;
|
|
1280
1415
|
static INVALID_CLASS = 'is-invalid';
|
|
1416
|
+
submitter;
|
|
1281
1417
|
render() {
|
|
1282
1418
|
const form = document.createElement('form');
|
|
1283
1419
|
form.replaceChildren(...this.childNodes);
|
|
@@ -1290,8 +1426,14 @@ class Form extends ParsedElement() {
|
|
|
1290
1426
|
this.replaceChildren(form);
|
|
1291
1427
|
}
|
|
1292
1428
|
spinner(spin) {
|
|
1293
|
-
this.querySelectorAll('ful-spinner').forEach(el =>
|
|
1294
|
-
|
|
1429
|
+
this.querySelectorAll('ful-spinner').forEach(el => {
|
|
1430
|
+
const hel = /** @type HTMLElement} */ (el);
|
|
1431
|
+
hel.hidden = !spin;
|
|
1432
|
+
});
|
|
1433
|
+
this.querySelectorAll('[type=submit],[type=reset]').forEach(el => {
|
|
1434
|
+
const hel = /** @type HTMLButtonElement} */ (el);
|
|
1435
|
+
hel.disabled = spin;
|
|
1436
|
+
});
|
|
1295
1437
|
}
|
|
1296
1438
|
async remoting(fn) {
|
|
1297
1439
|
try {
|
|
@@ -1326,8 +1468,8 @@ class Form extends ParsedElement() {
|
|
|
1326
1468
|
}
|
|
1327
1469
|
}
|
|
1328
1470
|
get values() {
|
|
1329
|
-
return Array.from(this.querySelectorAll('[name]'))
|
|
1330
|
-
.filter(
|
|
1471
|
+
return Array.from(/** @type {NodeListOf<HTMLElement>} */ (this.querySelectorAll('[name]')))
|
|
1472
|
+
.filter(el => {
|
|
1331
1473
|
if (el.dataset['fulBindInclude'] === 'never') {
|
|
1332
1474
|
return false;
|
|
1333
1475
|
}
|
|
@@ -1338,22 +1480,26 @@ class Form extends ParsedElement() {
|
|
|
1338
1480
|
}, {});
|
|
1339
1481
|
}
|
|
1340
1482
|
set errors(es) {
|
|
1341
|
-
const fieldErrors = es.filter(
|
|
1342
|
-
const globalErrors = es.filter(
|
|
1483
|
+
const fieldErrors = es.filter(e => e.type === 'FIELD_ERROR' || e.type === 'INVALID_FORMAT');
|
|
1484
|
+
const globalErrors = es.filter(e => e.type !== 'FIELD_ERROR' && e.type !== 'INVALID_FORMAT');
|
|
1343
1485
|
this.querySelectorAll(`.${Form.INVALID_CLASS}`).forEach(el => el.classList.remove(Form.INVALID_CLASS));
|
|
1344
1486
|
this.querySelectorAll("ful-errors").forEach(el => {
|
|
1345
1487
|
el.replaceChildren();
|
|
1346
1488
|
el.setAttribute('hidden', '');
|
|
1347
1489
|
});
|
|
1348
|
-
fieldErrors.forEach(
|
|
1490
|
+
fieldErrors.forEach(e => {
|
|
1349
1491
|
const name = e.context.replace("[", ".").replace("].", ".");
|
|
1350
1492
|
const validationTargetsSelector = `[name='${CSS.escape(name)}'] [ful-validation-target],[name='${CSS.escape(name)}']:not(:has([ful-validation-target]))`;
|
|
1351
1493
|
this.querySelectorAll(validationTargetsSelector).forEach(input => input.classList.add(Form.INVALID_CLASS));
|
|
1352
1494
|
const fieldErrorsSelector = `ful-field-error[field='${CSS.escape(name)}']`;
|
|
1353
|
-
this.querySelectorAll(fieldErrorsSelector).forEach(el =>
|
|
1495
|
+
this.querySelectorAll(fieldErrorsSelector).forEach(el => {
|
|
1496
|
+
const hel = /** @type HTMLElement} */ (el);
|
|
1497
|
+
hel.innerText = e.reason;
|
|
1498
|
+
});
|
|
1354
1499
|
});
|
|
1355
1500
|
this.querySelectorAll("ful-errors").forEach(el => {
|
|
1356
|
-
|
|
1501
|
+
const hel = /** @type HTMLElement} */ (el);
|
|
1502
|
+
hel.innerText = globalErrors.map(e => e.reason).join("\n");
|
|
1357
1503
|
if (globalErrors.length !== 0) {
|
|
1358
1504
|
el.removeAttribute('hidden');
|
|
1359
1505
|
}
|
|
@@ -1416,6 +1562,7 @@ class Input extends ParsedElement({
|
|
|
1416
1562
|
slots: true,
|
|
1417
1563
|
template: INPUT_TEMPLATE
|
|
1418
1564
|
}){
|
|
1565
|
+
input;
|
|
1419
1566
|
render(template, slots) {
|
|
1420
1567
|
const fragment = makeInputFragment(this, template, slots);
|
|
1421
1568
|
this.replaceChildren(fragment);
|
|
@@ -1451,6 +1598,8 @@ class Select extends ParsedElement({
|
|
|
1451
1598
|
</div>
|
|
1452
1599
|
`
|
|
1453
1600
|
}) {
|
|
1601
|
+
shouldLoad;
|
|
1602
|
+
_unwrappedRemoteLoad;
|
|
1454
1603
|
constructor(tsConfig) {
|
|
1455
1604
|
super();
|
|
1456
1605
|
this.tsConfig = tsConfig;
|
|
@@ -1502,6 +1651,7 @@ class Select extends ParsedElement({
|
|
|
1502
1651
|
}
|
|
1503
1652
|
callback(data);
|
|
1504
1653
|
};
|
|
1654
|
+
// @ts-ignore
|
|
1505
1655
|
this.ts = new TomSelect(input, Object.assign(remote ? {
|
|
1506
1656
|
preload: 'focus',
|
|
1507
1657
|
load: this._unwrappedRemoteLoad,
|
|
@@ -1586,27 +1736,40 @@ class RadioGroup extends ParsedElement({
|
|
|
1586
1736
|
input.addEventListener('change', evt => {
|
|
1587
1737
|
evt.stopPropagation();
|
|
1588
1738
|
//change is not cancelable
|
|
1589
|
-
this.dispatchEvent(new CustomEvent('change', {
|
|
1590
|
-
bubbles: true,
|
|
1591
|
-
cancelable: false,
|
|
1739
|
+
this.dispatchEvent(new CustomEvent('change', {
|
|
1740
|
+
bubbles: true,
|
|
1741
|
+
cancelable: false,
|
|
1592
1742
|
detail: {
|
|
1593
1743
|
value: this.value
|
|
1594
1744
|
}
|
|
1595
|
-
}));
|
|
1596
|
-
});
|
|
1745
|
+
}));
|
|
1746
|
+
});
|
|
1597
1747
|
const label = Fragments.fromChildNodes(el);
|
|
1598
1748
|
return [input, label];
|
|
1599
1749
|
});
|
|
1600
1750
|
|
|
1601
1751
|
radioEls.forEach(el => el.remove());
|
|
1602
|
-
template.renderTo(this, {name, slots, inputsAndLabels});
|
|
1752
|
+
template.renderTo(this, { name, slots, inputsAndLabels });
|
|
1603
1753
|
}
|
|
1604
1754
|
get value() {
|
|
1755
|
+
/** @type {HTMLInputElement|null} */
|
|
1605
1756
|
const checked = this.querySelector('input[type=radio]:checked');
|
|
1606
1757
|
return checked ? checked.value : null;
|
|
1607
1758
|
}
|
|
1608
1759
|
set value(value) {
|
|
1609
|
-
|
|
1760
|
+
if (value === null) {
|
|
1761
|
+
/** @type {HTMLInputElement[]} */
|
|
1762
|
+
this.querySelectorAll(`input[type=radio]`).forEach(el => {
|
|
1763
|
+
// @ts-ignore
|
|
1764
|
+
el.checked = false;
|
|
1765
|
+
});
|
|
1766
|
+
return;
|
|
1767
|
+
}
|
|
1768
|
+
/** @type {HTMLInputElement|null} */
|
|
1769
|
+
const el = this.querySelector(`input[type=radio][value=${CSS.escape(value)}]`);
|
|
1770
|
+
if (el) {
|
|
1771
|
+
el.checked = true;
|
|
1772
|
+
}
|
|
1610
1773
|
}
|
|
1611
1774
|
}
|
|
1612
1775
|
|