@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 CHANGED
@@ -73,21 +73,51 @@ var ful = (function (exports) {
73
73
  }
74
74
  }
75
75
 
76
+ /**
77
+ * @typedef {{ type: string; context: string?; reason: string; details: any?; }} Problem
78
+ */
76
79
  class Failure extends Error {
77
- constructor(name, problems, cause) {
78
- super(JSON.stringify(problems), { cause });
79
- this.name = name;
80
+ /**
81
+ *
82
+ * @param {string} message
83
+ * @param {Problem[]} problems
84
+ * @param {*} cause
85
+ */
86
+ constructor(message, problems, cause) {
87
+ super(message, { cause });
88
+ this.name = 'Failure';
80
89
  this.problems = problems;
81
90
  }
82
91
  }
83
92
 
93
+ /**
94
+ * @typedef {Int8Array| Uint8Array| Uint8ClampedArray| Int16Array| Uint16Array| Int32Array| Uint32Array| Float32Array| Float64Array| BigInt64Array| BigUint64Array} TypedArray
95
+ */
96
+ /**
97
+ * @typedef HttpInterceptor
98
+ * @property {function(Request,HttpInterceptorChain):Promise<Response>} intercept
99
+ */
100
+
84
101
  class HttpClientError extends Failure {
85
- constructor(status, problems, cause) {
86
- super(`HttpClientError:${status}`, problems, cause);
102
+ /**
103
+ * @param {string} message
104
+ * @param {number} status
105
+ * @param {{ type: string; context: string?; reason: string; details: any?; }[]} problems
106
+ * @param {Error|undefined} [cause]
107
+ */
108
+ constructor(message, status, problems, cause) {
109
+ super(message, problems, cause);
110
+ this.name = 'HttpClientError';
87
111
  this.status = status;
88
112
  }
113
+ /**
114
+ *
115
+ * @param {string} type
116
+ * @param {any} cause
117
+ * @returns
118
+ */
89
119
  static of(type, cause) {
90
- return new HttpClientError(0, [{
120
+ return new HttpClientError(cause.message, 0, [{
91
121
  type,
92
122
  context: null,
93
123
  reason: cause.message,
@@ -101,47 +131,61 @@ var ful = (function (exports) {
101
131
  */
102
132
  static async fromResponse(response) {
103
133
  const text = await response.text();
104
- const def = [{
134
+ const message = `${response.status} ${response.statusText}: ${text}`;
135
+ const fallback = [{
105
136
  type: "GENERIC_PROBLEM",
106
137
  context: null,
107
- reason: `${response.status} ${response.statusText}: ${text}`,
138
+ reason: message,
108
139
  details: null
109
140
  }];
110
141
  try {
111
- return new HttpClientError(response.status, text ? JSON.parse(text) : def);
142
+ return new HttpClientError(message, response.status, text ? JSON.parse(text) : fallback);
112
143
  } catch (e) {
113
- return new HttpClientError(response.status, def);
144
+ return new HttpClientError(message, response.status, fallback);
114
145
  }
115
146
  }
116
147
  }
117
148
 
149
+ /**
150
+ * @implements {HttpInterceptor}
151
+ */
118
152
  class CsrfTokenInterceptor {
119
153
  #k; #v;
120
154
  constructor() {
121
- this.#k = document.querySelector("meta[name='_csrf_header']").getAttribute("content");
122
- this.#v = document.querySelector("meta[name='_csrf']").getAttribute("content");
123
- }
155
+ this.#k = document.querySelector("meta[name='_csrf_header']")?.getAttribute("content");
156
+ this.#v = document.querySelector("meta[name='_csrf']")?.getAttribute("content");
157
+ }
124
158
  async intercept(request, chain) {
125
- request.headers.set(this.#k, this.#v);
159
+ if(this.#k && this.#v) {
160
+ request.headers.set(this.#k, this.#v);
161
+ }
126
162
  return await chain.proceed(request);
127
163
  }
128
164
  }
129
-
165
+ /**
166
+ * @implements {HttpInterceptor}
167
+ */
130
168
  class RedirectOnUnauthorizedInterceptor {
131
169
  #redirectUri;
170
+ /**
171
+ * @param {string} redirectUri
172
+ */
132
173
  constructor(redirectUri) {
133
174
  this.#redirectUri = redirectUri;
134
175
  }
135
176
  async intercept(request, chain) {
136
177
  const response = await chain.proceed(request);
137
- if (response.status !== 401) {
138
- return response;
178
+ if (response.status === 401) {
179
+ window.location.href = this.#redirectUri;
139
180
  }
140
- window.location.href = this.#redirectUri;
181
+ return response;
141
182
  }
142
183
  }
143
184
 
144
185
  class HttpClientBuilder {
186
+ /**
187
+ * @type {HttpInterceptor[]}
188
+ */
145
189
  #interceptors;
146
190
  constructor() {
147
191
  this.#interceptors = [];
@@ -154,6 +198,9 @@ var ful = (function (exports) {
154
198
  this.#interceptors.push(new RedirectOnUnauthorizedInterceptor(redirectUri));
155
199
  return this;
156
200
  }
201
+ /**
202
+ * @param {...HttpInterceptor} interceptors
203
+ */
157
204
  withInterceptors(...interceptors) {
158
205
  this.#interceptors.push(...interceptors);
159
206
  return this;
@@ -163,27 +210,35 @@ var ful = (function (exports) {
163
210
  }
164
211
  }
165
212
 
213
+ /**
214
+ * @implements {HttpInterceptor}
215
+ */
166
216
  class HttpCall {
167
- /**
168
- *
169
- * @async
170
- * @param {Request} request
171
- * @param {HttpInterceptorChain} chain
172
- * @returns {Promise<Response>} the response
173
- */
174
217
  async intercept(request, chain) {
175
218
  return await fetch(request);
176
219
  }
177
220
  }
178
221
 
179
222
  class HttpInterceptorChain {
223
+ #interceptors;
224
+ #current;
225
+ /**
226
+ *
227
+ * @param {HttpInterceptor[]} interceptors
228
+ * @param {number} current
229
+ */
180
230
  constructor(interceptors, current) {
181
- this.interceptors = interceptors;
182
- this.current = current;
231
+ this.#interceptors = interceptors;
232
+ this.#current = current;
183
233
  }
234
+ /**
235
+ *
236
+ * @param {Request} request
237
+ * @returns {Promise<Response>} the response
238
+ */
184
239
  async proceed(request) {
185
- const interceptor = this.interceptors[this.current];
186
- return await interceptor.intercept(request, new HttpInterceptorChain(this.interceptors, this.current + 1));
240
+ const interceptor = this.#interceptors[this.#current];
241
+ return await interceptor.intercept(request, new HttpInterceptorChain(this.#interceptors, this.#current + 1));
187
242
  }
188
243
  }
189
244
 
@@ -191,14 +246,14 @@ var ful = (function (exports) {
191
246
  #interceptors;
192
247
  /**
193
248
  * Creates a builder for an HttpClient.
194
- * @returns {HttpRequestBuilder} the client builder
249
+ * @returns {HttpClientBuilder} the client builder
195
250
  */
196
251
  static builder() {
197
252
  return new HttpClientBuilder();
198
253
  }
199
254
  /**
200
255
  * Creates an HttpClient.
201
- * @returns {[HttpInterceptor]} interceptors - a list of interceptors to be registered for every request performed by the created client.
256
+ * @param {HttpInterceptor[]|undefined} interceptors - a list of interceptors to be registered for every request performed by the created client.
202
257
  */
203
258
  constructor(interceptors) {
204
259
  this.#interceptors = interceptors || [];
@@ -208,7 +263,7 @@ var ful = (function (exports) {
208
263
  * @async
209
264
  * @param {string} uri - the (possibly relative) request url
210
265
  * @param {RequestInit|undefined} options - fetch options
211
- * @param {[any]|undefined} interceptors - the HttpInterceptors to be registered for this request.
266
+ * @param {HttpInterceptor[]|undefined} interceptors - the HttpInterceptors to be registered for this exchange.
212
267
  * @returns {Promise<Response>} the response
213
268
  */
214
269
  async exchange(uri, options, interceptors) {
@@ -275,12 +330,17 @@ var ful = (function (exports) {
275
330
  }
276
331
  }
277
332
 
278
-
333
+ /**
334
+ *
335
+ * @param {Response} response
336
+ * @param {'text'|'json'|'blob'|'arrayBuffer'} type
337
+ * @returns
338
+ */
279
339
  const unmarshal = async (response, type) => {
280
340
  try {
281
341
  return await response[type]();
282
- } catch (e) {
283
- throw HttpClientError.of("UNMARSHALING_PROBLEM", e);
342
+ } catch (ex) {
343
+ throw HttpClientError.of("UNMARSHALING_PROBLEM", ex);
284
344
  }
285
345
  };
286
346
 
@@ -322,7 +382,7 @@ var ful = (function (exports) {
322
382
  * @param {Headers} headers
323
383
  * @param {any} body
324
384
  * @param {Omit<RequestInit,"headers"|"method"|"body">} options
325
- * @param {[HttpInterceptor]} interceptors
385
+ * @param {HttpInterceptor[]} interceptors
326
386
  */
327
387
  constructor(client, method, uri, params, headers, body, options, interceptors) {
328
388
  this.#client = client;
@@ -336,7 +396,7 @@ var ful = (function (exports) {
336
396
  }
337
397
  /**
338
398
  * Add all passed headers to the request, overriding existing ones if that key already exists.
339
- * @param {headersInit} hs
399
+ * @param {HeadersInit} hs
340
400
  * @returns {HttpRequestBuilder} this builder
341
401
  */
342
402
  headers(hs) {
@@ -398,6 +458,18 @@ var ful = (function (exports) {
398
458
  this.#body = JSON.stringify(body);
399
459
  return this;
400
460
  }
461
+ /**
462
+ * Sets the request body as a FormData configured using the callback.
463
+ * `Content-Type: multipart/form-data` header is automatically added by fetch if not explicitly set.
464
+ * @param {function(HttpMultipartRequestCustomizer):void} callback
465
+ */
466
+ multipart(callback) {
467
+ const formData = new FormData();
468
+ const builder = new HttpMultipartRequestCustomizer(formData);
469
+ callback(builder);
470
+ this.#body = formData;
471
+ return this;
472
+ }
401
473
  /**
402
474
  * Sets a fetch options for the request.
403
475
  * @param {Omit<RequestInit,"headers"|"method"|"body">} kvs
@@ -405,6 +477,7 @@ var ful = (function (exports) {
405
477
  */
406
478
  options(kvs) {
407
479
  for (const [k, v] of Object.entries(kvs)) {
480
+ // @ts-ignore
408
481
  this.#options[k] = v;
409
482
  }
410
483
  return this;
@@ -471,11 +544,11 @@ var ful = (function (exports) {
471
544
  throw await HttpClientError.fromResponse(response);
472
545
  }
473
546
  return response;
474
- } catch (e) {
475
- if (e instanceof Failure) {
476
- throw e;
547
+ } catch (ex) {
548
+ if (ex instanceof Failure) {
549
+ throw ex;
477
550
  }
478
- throw HttpClientError.of("CONNECTION_PROBLEM", e);
551
+ throw HttpClientError.of("CONNECTION_PROBLEM", ex);
479
552
  }
480
553
  }
481
554
  /**
@@ -494,14 +567,6 @@ var ful = (function (exports) {
494
567
  const response = await this.fetch();
495
568
  return await unmarshal(response, 'json');
496
569
  }
497
- /**
498
- * Performs an HTTP exchange using the configured client request, and interceptos throwing a failure when response status is not in the 200-299 range.
499
- * @returns {Promise<Uint8Array>} the response body, as an Uint8Array
500
- */
501
- async fetchBytes() {
502
- const response = await this.fetch();
503
- return await unmarshal(response, 'bytes');
504
- }
505
570
  /**
506
571
  * Performs an HTTP exchange using the configured client request, and interceptos throwing a failure when response status is not in the 200-299 range.
507
572
  * @returns {Promise<Blob>} the response body, as a Blob
@@ -520,6 +585,68 @@ var ful = (function (exports) {
520
585
  }
521
586
  }
522
587
 
588
+
589
+ class HttpMultipartRequestCustomizer {
590
+ #formData;
591
+ /**
592
+ *
593
+ * @param {FormData} formData
594
+ */
595
+ constructor(formData){
596
+ this.#formData = formData;
597
+ }
598
+ /**
599
+ * Appends a value to the FormData.
600
+ * @param {string} name
601
+ * @param {*} value
602
+ * @returns this builder
603
+ */
604
+ field(name, value){
605
+ this.#formData.append(name, value);
606
+ return this;
607
+ }
608
+ /**
609
+ * Appends a Blob to the FormData.
610
+ * If `filename` is omitted, FormData defaults are applied:
611
+ * The default filename for Blob objects is "blob";
612
+ * The default filename for File objects is the file's filename.
613
+ * @param {string} name
614
+ * @param {Blob} value
615
+ * @param {string|undefined} filename
616
+ * @returns this builder
617
+ */
618
+ blob(name, value, filename){
619
+ this.#formData.append(name, value, filename);
620
+ return this;
621
+ }
622
+ /**
623
+ * Appends multiple Blobs to the FormData with the same name.
624
+ * The default filename for Blob objects is "blob";
625
+ * The default filename for File objects is the file's filename.
626
+ * @param {string} name
627
+ * @param {Blob[]} values
628
+ * @returns this builder
629
+ */
630
+ blobs(name, values){
631
+ for(let v of values){
632
+ this.#formData.append(name, v);
633
+ }
634
+ return this;
635
+ }
636
+ /**
637
+ * Appends a JSON serialized blob to the FormData.
638
+ * @param {string} name
639
+ * @param {any} value
640
+ * @param {string|undefined} filename
641
+ * @returns this builder
642
+ */
643
+ json(name, value, filename){
644
+ const blob = new Blob([JSON.stringify(value)], {type: 'application/json'});
645
+ this.#formData.append(name, blob, filename);
646
+ return this;
647
+ }
648
+ }
649
+
523
650
  class Storage {
524
651
  constructor(prefix, storage) {
525
652
  this.prefix = prefix;
@@ -616,7 +743,7 @@ var ful = (function (exports) {
616
743
  Object.entries(additionalParams || {}).forEach(kv => {
617
744
  url.searchParams.set(kv[0], kv[1]);
618
745
  });
619
- window.location = url;
746
+ window.location.href = url.toString();
620
747
  }
621
748
  async registration(additionalParams){
622
749
  await this.action(this.uri.registration, additionalParams);
@@ -626,7 +753,7 @@ var ful = (function (exports) {
626
753
  kc_action: kcAction
627
754
  });
628
755
  }
629
- async _tokenExchange(code, state) {
756
+ async #tokenExchange(code, state) {
630
757
  window.history.replaceState('', "", this.uri.redirect);
631
758
  const stateAndVerifier = this.storage.pop(AuthorizationCodeFlow.PKCE_AND_STATE_KEY);
632
759
  if (stateAndVerifier.state !== state) {
@@ -659,7 +786,7 @@ var ful = (function (exports) {
659
786
  if (code && this.storage.load(AuthorizationCodeFlow.PKCE_AND_STATE_KEY)) {
660
787
  //if callback from keycloak and we have our state still stored
661
788
  const state = url.searchParams.get("state");
662
- return await this._tokenExchange(code, state);
789
+ return await this.#tokenExchange(code, state);
663
790
  }
664
791
  //if not authorized
665
792
  await this.action(this.uri.auth, {});
@@ -730,7 +857,7 @@ var ful = (function (exports) {
730
857
  const url = new URL(this.uri.logout);
731
858
  url.searchParams.set("post_logout_redirect_uri", this.uri.redirect);
732
859
  url.searchParams.set("id_token_hint", this.token.id_token);
733
- window.location = url;
860
+ window.location.href = url.toString();
734
861
  }
735
862
 
736
863
  bearerToken() {
@@ -789,7 +916,7 @@ var ful = (function (exports) {
789
916
  };
790
917
 
791
918
  return function () {
792
- args = arguments;
919
+ args = [...arguments];
793
920
  previousTimestamp = new Date().getTime();
794
921
  if (tid === null) {
795
922
  tid = setTimeout(later, timeoutMs);
@@ -823,7 +950,7 @@ var ful = (function (exports) {
823
950
  previousTimestamp = now;
824
951
  }
825
952
  const remaining = timeoutMs - (now - previousTimestamp);
826
- args = arguments;
953
+ args = [...arguments];
827
954
  if (remaining <= 0 || remaining > timeoutMs) {
828
955
  if (tid !== null) {
829
956
  clearTimeout(tid);
@@ -929,16 +1056,18 @@ var ful = (function (exports) {
929
1056
  .forEach(a => {
930
1057
  const target = a.substring(prefix.length);
931
1058
  if (target === 'class') {
932
- to.classList.add(...from.getAttribute(prefix + "class").split(" ").filter(a => a.length));
1059
+ const classes = from.getAttribute(prefix + "class")?.split(" ").filter(a => a.length) ?? [];
1060
+ to.classList.add(...classes);
933
1061
  return;
934
1062
  }
1063
+ // @ts-ignore
935
1064
  to.setAttribute(target, from.getAttribute(a));
936
1065
  });
937
1066
  }
938
1067
  /**
939
1068
  *
940
1069
  * @param {HTMLElement} el
941
- * @param {stirng} attr
1070
+ * @param {string} attr
942
1071
  * @param {boolean} value
943
1072
  */
944
1073
  static toggle(el, attr, value) {
@@ -965,19 +1094,21 @@ var ful = (function (exports) {
965
1094
  * @returns the slots
966
1095
  */
967
1096
  static from(el) {
1097
+ /** @type [string, Element][] */
968
1098
  const namedSlots = Array.from(el.childNodes)
969
- .filter(el => el.matches && el.matches('[slot]'))
1099
+ .filter(el => el instanceof Element)
1100
+ .filter(el => el.matches('[slot]'))
970
1101
  .map(el => {
971
1102
  el.remove();
972
1103
  const slot = el.getAttribute("slot");
973
1104
  el.removeAttribute("slot");
974
- return [slot, el];
1105
+ return [slot ?? 'unnamed', el];
975
1106
  });
976
1107
  const slots = {};
977
1108
  slots.default = new DocumentFragment();
978
1109
  slots.default.append(...el.childNodes);
979
- for(const [name,el] of namedSlots){
980
- if(!(name in slots)){
1110
+ for (const [name, el] of namedSlots) {
1111
+ if (!(name in slots)) {
981
1112
  slots[name] = new DocumentFragment();
982
1113
  }
983
1114
  slots[name].append(el);
@@ -1003,7 +1134,8 @@ var ful = (function (exports) {
1003
1134
  #ec;
1004
1135
  put(k, fragment) {
1005
1136
  if (this.#ec) {
1006
- this.#idToTemplate[k] = Template.fromFragment(fragment, ec);
1137
+ // @ts-ignore
1138
+ this.#idToTemplate[k] = ftl.Template.fromFragment(fragment, this.#ec);
1007
1139
  return;
1008
1140
  }
1009
1141
  this.#idToFragment[k] = fragment;
@@ -1022,6 +1154,7 @@ var ful = (function (exports) {
1022
1154
  this.#ec = ec;
1023
1155
  for (const [k, fragment] of Object.entries(this.#idToFragment)) {
1024
1156
  delete this.#idToFragment[k];
1157
+ // @ts-ignore
1025
1158
  this.#idToTemplate[k] = ftl.Template.fromFragment(fragment, ec, ...data);
1026
1159
  }
1027
1160
  }
@@ -1125,8 +1258,8 @@ var ful = (function (exports) {
1125
1258
  #initialized;
1126
1259
  #reflecting;
1127
1260
  #internals;
1128
- constructor(...args) {
1129
- super(...args);
1261
+ constructor() {
1262
+ super();
1130
1263
  this.#internals = this.attachInternals();
1131
1264
  }
1132
1265
  get initialized() {
@@ -1154,6 +1287,7 @@ var ful = (function (exports) {
1154
1287
  observer.disconnect();
1155
1288
  upgradeQueue.enqueue(this);
1156
1289
  });
1290
+ // @ts-ignore
1157
1291
  observer.observe(this.parentNode, { childList: true, subtree: true });
1158
1292
  }
1159
1293
  attributeChangedCallback(attr, oldValue, newValue) {
@@ -1179,6 +1313,7 @@ var ful = (function (exports) {
1179
1313
  return;
1180
1314
  }
1181
1315
  this.#parsed = true;
1316
+ // @ts-ignore
1182
1317
  await this.render(elements.template(templateId), slots ? LightSlots.from(this) : undefined);
1183
1318
 
1184
1319
  for (const [attr, mapper] of attrsAndMappers) {
@@ -1281,6 +1416,7 @@ var ful = (function (exports) {
1281
1416
  static IGNORED_CHILDREN_SELECTOR = '.d-none, [hidden]';
1282
1417
  static SCROLL_OFFSET = 50;
1283
1418
  static INVALID_CLASS = 'is-invalid';
1419
+ submitter;
1284
1420
  render() {
1285
1421
  const form = document.createElement('form');
1286
1422
  form.replaceChildren(...this.childNodes);
@@ -1293,8 +1429,14 @@ var ful = (function (exports) {
1293
1429
  this.replaceChildren(form);
1294
1430
  }
1295
1431
  spinner(spin) {
1296
- this.querySelectorAll('ful-spinner').forEach(el => el.hidden = !spin);
1297
- this.querySelectorAll('[type=submit],[type=reset]').forEach(el => el.disabled = spin);
1432
+ this.querySelectorAll('ful-spinner').forEach(el => {
1433
+ const hel = /** @type HTMLElement} */ (el);
1434
+ hel.hidden = !spin;
1435
+ });
1436
+ this.querySelectorAll('[type=submit],[type=reset]').forEach(el => {
1437
+ const hel = /** @type HTMLButtonElement} */ (el);
1438
+ hel.disabled = spin;
1439
+ });
1298
1440
  }
1299
1441
  async remoting(fn) {
1300
1442
  try {
@@ -1329,8 +1471,8 @@ var ful = (function (exports) {
1329
1471
  }
1330
1472
  }
1331
1473
  get values() {
1332
- return Array.from(this.querySelectorAll('[name]'))
1333
- .filter((el) => {
1474
+ return Array.from(/** @type {NodeListOf<HTMLElement>} */ (this.querySelectorAll('[name]')))
1475
+ .filter(el => {
1334
1476
  if (el.dataset['fulBindInclude'] === 'never') {
1335
1477
  return false;
1336
1478
  }
@@ -1341,22 +1483,26 @@ var ful = (function (exports) {
1341
1483
  }, {});
1342
1484
  }
1343
1485
  set errors(es) {
1344
- const fieldErrors = es.filter((e) => e.type === 'FIELD_ERROR' || e.type === 'INVALID_FORMAT');
1345
- const globalErrors = es.filter((e) => e.type !== 'FIELD_ERROR' && e.type !== 'INVALID_FORMAT');
1486
+ const fieldErrors = es.filter(e => e.type === 'FIELD_ERROR' || e.type === 'INVALID_FORMAT');
1487
+ const globalErrors = es.filter(e => e.type !== 'FIELD_ERROR' && e.type !== 'INVALID_FORMAT');
1346
1488
  this.querySelectorAll(`.${Form.INVALID_CLASS}`).forEach(el => el.classList.remove(Form.INVALID_CLASS));
1347
1489
  this.querySelectorAll("ful-errors").forEach(el => {
1348
1490
  el.replaceChildren();
1349
1491
  el.setAttribute('hidden', '');
1350
1492
  });
1351
- fieldErrors.forEach((e) => {
1493
+ fieldErrors.forEach(e => {
1352
1494
  const name = e.context.replace("[", ".").replace("].", ".");
1353
1495
  const validationTargetsSelector = `[name='${CSS.escape(name)}'] [ful-validation-target],[name='${CSS.escape(name)}']:not(:has([ful-validation-target]))`;
1354
1496
  this.querySelectorAll(validationTargetsSelector).forEach(input => input.classList.add(Form.INVALID_CLASS));
1355
1497
  const fieldErrorsSelector = `ful-field-error[field='${CSS.escape(name)}']`;
1356
- this.querySelectorAll(fieldErrorsSelector).forEach(el => el.innerText = e.reason);
1498
+ this.querySelectorAll(fieldErrorsSelector).forEach(el => {
1499
+ const hel = /** @type HTMLElement} */ (el);
1500
+ hel.innerText = e.reason;
1501
+ });
1357
1502
  });
1358
1503
  this.querySelectorAll("ful-errors").forEach(el => {
1359
- el.innerText = globalErrors.map(e => e.reason).join("\n");
1504
+ const hel = /** @type HTMLElement} */ (el);
1505
+ hel.innerText = globalErrors.map(e => e.reason).join("\n");
1360
1506
  if (globalErrors.length !== 0) {
1361
1507
  el.removeAttribute('hidden');
1362
1508
  }
@@ -1419,6 +1565,7 @@ var ful = (function (exports) {
1419
1565
  slots: true,
1420
1566
  template: INPUT_TEMPLATE
1421
1567
  }){
1568
+ input;
1422
1569
  render(template, slots) {
1423
1570
  const fragment = makeInputFragment(this, template, slots);
1424
1571
  this.replaceChildren(fragment);
@@ -1454,6 +1601,8 @@ var ful = (function (exports) {
1454
1601
  </div>
1455
1602
  `
1456
1603
  }) {
1604
+ shouldLoad;
1605
+ _unwrappedRemoteLoad;
1457
1606
  constructor(tsConfig) {
1458
1607
  super();
1459
1608
  this.tsConfig = tsConfig;
@@ -1505,6 +1654,7 @@ var ful = (function (exports) {
1505
1654
  }
1506
1655
  callback(data);
1507
1656
  };
1657
+ // @ts-ignore
1508
1658
  this.ts = new TomSelect(input, Object.assign(remote ? {
1509
1659
  preload: 'focus',
1510
1660
  load: this._unwrappedRemoteLoad,
@@ -1589,27 +1739,40 @@ var ful = (function (exports) {
1589
1739
  input.addEventListener('change', evt => {
1590
1740
  evt.stopPropagation();
1591
1741
  //change is not cancelable
1592
- this.dispatchEvent(new CustomEvent('change', {
1593
- bubbles: true,
1594
- cancelable: false,
1742
+ this.dispatchEvent(new CustomEvent('change', {
1743
+ bubbles: true,
1744
+ cancelable: false,
1595
1745
  detail: {
1596
1746
  value: this.value
1597
1747
  }
1598
- }));
1599
- });
1748
+ }));
1749
+ });
1600
1750
  const label = Fragments.fromChildNodes(el);
1601
1751
  return [input, label];
1602
1752
  });
1603
1753
 
1604
1754
  radioEls.forEach(el => el.remove());
1605
- template.renderTo(this, {name, slots, inputsAndLabels});
1755
+ template.renderTo(this, { name, slots, inputsAndLabels });
1606
1756
  }
1607
1757
  get value() {
1758
+ /** @type {HTMLInputElement|null} */
1608
1759
  const checked = this.querySelector('input[type=radio]:checked');
1609
1760
  return checked ? checked.value : null;
1610
1761
  }
1611
1762
  set value(value) {
1612
- this.querySelector(`input[type=radio][value=${CSS.escape(value)}]`).checked = true;
1763
+ if (value === null) {
1764
+ /** @type {HTMLInputElement[]} */
1765
+ this.querySelectorAll(`input[type=radio]`).forEach(el => {
1766
+ // @ts-ignore
1767
+ el.checked = false;
1768
+ });
1769
+ return;
1770
+ }
1771
+ /** @type {HTMLInputElement|null} */
1772
+ const el = this.querySelector(`input[type=radio][value=${CSS.escape(value)}]`);
1773
+ if (el) {
1774
+ el.checked = true;
1775
+ }
1613
1776
  }
1614
1777
  }
1615
1778