@optionfactory/ful 4.0.15 → 5.0.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
@@ -461,7 +461,7 @@ var ful = (function (exports, ftl) {
461
461
  */
462
462
  headers(hs) {
463
463
  for (const [k, v] of new Headers(hs).entries()) {
464
- if (v === null || v === undefined) {
464
+ if (v == null) {
465
465
  this.#headers.delete(k);
466
466
  } else {
467
467
  this.#headers.set(k, v);
@@ -476,7 +476,7 @@ var ful = (function (exports, ftl) {
476
476
  * @returns {HttpRequestBuilder} this builder
477
477
  */
478
478
  header(k, v) {
479
- if (v === null || v === undefined) {
479
+ if (v == null) {
480
480
  this.#headers.delete(k);
481
481
  } else {
482
482
  this.#headers.set(k, v);
@@ -490,7 +490,7 @@ var ful = (function (exports, ftl) {
490
490
  */
491
491
  params(ps) {
492
492
  for (const [k, v] of new URLSearchParams(ps).entries()) {
493
- if (v === null || v === undefined) {
493
+ if (v == null) {
494
494
  this.#params.delete(k);
495
495
  } else {
496
496
  this.#params.set(k, v);
@@ -505,7 +505,7 @@ var ful = (function (exports, ftl) {
505
505
  * @returns {HttpRequestBuilder} this builder
506
506
  */
507
507
  param(k, ...vs) {
508
- if (vs.length === 0 || vs[0] === null || vs[0] === undefined) {
508
+ if (vs.length === 0 || vs[0] == null) {
509
509
  this.#params.delete(k);
510
510
  return this;
511
511
  }
@@ -1136,22 +1136,6 @@ var ful = (function (exports, ftl) {
1136
1136
  }
1137
1137
  }
1138
1138
 
1139
- class Loaders {
1140
- static fromAttributes(el, defaultLoader, options) {
1141
- const http = ftl.registry.component("http-client");
1142
- const requestMapper = el.hasAttribute("request-mapper") ? ftl.registry.component(el.getAttribute("request-mapper")) : v => v;
1143
- const responseMapper = el.hasAttribute("response-mapper") ? ftl.registry.component(el.getAttribute("response-mapper")) : v => v;
1144
- const loaderClass = ftl.registry.component(el.getAttribute("loader") ?? defaultLoader);
1145
- return loaderClass.create({
1146
- el,
1147
- http,
1148
- requestMapper,
1149
- responseMapper,
1150
- options: options ?? {}
1151
- });
1152
- }
1153
- }
1154
-
1155
1139
  class Bindings {
1156
1140
 
1157
1141
  /**
@@ -1348,7 +1332,10 @@ var ful = (function (exports, ftl) {
1348
1332
  }
1349
1333
 
1350
1334
  class FormLoader {
1351
- static create({ el, http, requestMapper, responseMapper }) {
1335
+ static create(el, conf) {
1336
+ const http = ftl.registry.component("http-client");
1337
+ const requestMapper = el.hasAttribute("request-mapper") ? ftl.registry.component(el.getAttribute("request-mapper")) : v => v;
1338
+ const responseMapper = el.hasAttribute("response-mapper") ? ftl.registry.component(el.getAttribute("response-mapper")) : v => v;
1352
1339
  const url = el.getAttribute("action");
1353
1340
  if (!url) {
1354
1341
  return new LocalFormLoader(requestMapper, responseMapper);
@@ -1380,7 +1367,7 @@ var ful = (function (exports, ftl) {
1380
1367
  async submit() {
1381
1368
  this.spinner(true);
1382
1369
  try {
1383
- const loader = Loaders.fromAttributes(this, 'loaders:form');
1370
+ const loader = ftl.registry.component(this.getAttribute("loader") ?? 'loaders:form').create(this);
1384
1371
  const values = this.values;
1385
1372
  let request = await loader.prepare(values, this);
1386
1373
  try {
@@ -1431,7 +1418,7 @@ var ful = (function (exports, ftl) {
1431
1418
  }
1432
1419
 
1433
1420
  class Input extends ftl.ParsedElement {
1434
- static observed = ['value', 'readonly:presence'];
1421
+ static observed = ['value', 'readonly:presence', 'required:presence'];
1435
1422
  static slots = true;
1436
1423
  static template = `
1437
1424
  <div class="form-label">
@@ -1462,15 +1449,16 @@ var ful = (function (exports, ftl) {
1462
1449
  _fragment(type, slots) {
1463
1450
  return this.template().withOverlay({ type, slots }).render();
1464
1451
  }
1465
- render({ slots, observed, disabled, skipValueSetup }) {
1452
+ render({ slots, observed, disabled, skipObservedSetup }) {
1466
1453
  const type = this._type();
1467
1454
  const fragment = this._fragment(type, slots);
1468
1455
  this._input = fragment.querySelector("input,textarea");
1469
1456
 
1470
1457
  ftl.Attributes.forward('input-', this, this._input);
1471
- this.disabled = disabled;
1472
- this.readonly = observed.readonly;
1473
- if(!skipValueSetup){
1458
+ if (!skipObservedSetup) {
1459
+ this.disabled = disabled;
1460
+ this.readonly = observed.readonly;
1461
+ this.required = observed.required;
1474
1462
  this.value = observed.value;
1475
1463
  }
1476
1464
 
@@ -1513,6 +1501,15 @@ var ful = (function (exports, ftl) {
1513
1501
  set disabled(d) {
1514
1502
  ftl.Attributes.toggle(this._input, 'disabled', d);
1515
1503
  }
1504
+ get required() {
1505
+ return this._input.getAttribute('aria-required') === 'true';
1506
+ }
1507
+ set required(d) {
1508
+ ftl.Attributes.set(this._input, "aria-required", d ? "true" : null);
1509
+ this.reflect(() => {
1510
+ ftl.Attributes.toggle(this, 'required', d);
1511
+ });
1512
+ }
1516
1513
  focus(options) {
1517
1514
  this._input.focus(options);
1518
1515
  }
@@ -1526,7 +1523,7 @@ var ful = (function (exports, ftl) {
1526
1523
  this._fieldError.innerText = error;
1527
1524
  }
1528
1525
  formResetCallback() {
1529
- this.value = this.getAttribute("value");
1526
+ this.value = this.unmarshal('value', this.getAttribute("value"));
1530
1527
  }
1531
1528
  }
1532
1529
 
@@ -1570,22 +1567,22 @@ var ful = (function (exports, ftl) {
1570
1567
  const date = `${d.getFullYear()}-${pad(2, d.getMonth() + 1)}-${pad(2, d.getDate())}`;
1571
1568
  const time = `${pad(2, d.getHours())}:${pad(2, d.getMinutes())}:${pad(2, d.getSeconds())}.${pad(3, d.getMilliseconds())}`;
1572
1569
  return `${date}T${time}`
1573
- }
1570
+ }
1574
1571
  }
1575
1572
 
1576
1573
 
1577
1574
  class InputLocalDate extends Input {
1578
- static observed = ['value', 'readonly:presence', 'min', 'max', 'step'];
1575
+ static observed = ['value', 'readonly:presence', 'required:presence', 'min', 'max', 'step'];
1579
1576
  _type() {
1580
1577
  return 'date';
1581
1578
  }
1582
- render(conf){
1583
- const {observed} = conf;
1579
+ render(conf) {
1580
+ const { observed } = conf;
1584
1581
  super.render(conf);
1585
1582
  this.min = observed.min;
1586
1583
  this.max = observed.max;
1587
1584
  this.step = observed.step;
1588
- }
1585
+ }
1589
1586
  get min() {
1590
1587
  const v = this._input.min;
1591
1588
  return v === '' ? null : v;
@@ -1645,12 +1642,12 @@ var ful = (function (exports, ftl) {
1645
1642
 
1646
1643
 
1647
1644
  class InputInstant extends Input {
1648
- static observed = ['value', 'readonly:presence', 'min', 'max', 'step'];
1645
+ static observed = ['value', 'readonly:presence', 'required:presence', 'min', 'max', 'step'];
1649
1646
  _type() {
1650
1647
  return 'datetime-local';
1651
1648
  }
1652
- render(conf){
1653
- const {observed} = conf;
1649
+ render(conf) {
1650
+ const { observed } = conf;
1654
1651
  super.render(conf);
1655
1652
  this.min = observed.min;
1656
1653
  this.max = observed.min;
@@ -1701,7 +1698,7 @@ var ful = (function (exports, ftl) {
1701
1698
  'maxtotalsizeexceeded': "La dimensione massima complessiva dei file è di {0}"
1702
1699
  }
1703
1700
  }
1704
- static observed = ['value', 'readonly:presence', "accept:csv", 'multiple:presence', "itemlist:presence", "dropzone:presence", "maxfilesize:number", "maxtotalsize:number"];
1701
+ static observed = ['value', 'readonly:presence', 'required:presence', "accept:csv", 'multiple:presence', "itemlist:presence", "dropzone:presence", "maxfilesize:number", "maxtotalsize:number"];
1705
1702
  #accept;
1706
1703
  #items;
1707
1704
  #dropzone;
@@ -1927,17 +1924,7 @@ var ful = (function (exports, ftl) {
1927
1924
  #prefetch;
1928
1925
  #revision;
1929
1926
  #data;
1930
- static create({ el, http, responseMapper }) {
1931
- return new RemoteLoader({
1932
- http,
1933
- url: el.getAttribute("src"),
1934
- method: el.getAttribute("method") ?? 'POST',
1935
- responseMapper,
1936
- prefetch: el.hasAttribute("preload"),
1937
- revision: el.getAttribute("revision")
1938
- });
1939
- }
1940
- constructor({http, url, method, responseMapper, prefetch, revision}) {
1927
+ constructor({ http, url, method, responseMapper, prefetch, revision }) {
1941
1928
  this.#http = http;
1942
1929
  this.#url = url;
1943
1930
  this.#method = method;
@@ -1960,7 +1947,7 @@ var ful = (function (exports, ftl) {
1960
1947
  await this.#ensureFetched();
1961
1948
  return this.#data.filter(([k, v]) => (v ?? '').toLowerCase().includes(needle?.toLowerCase()));
1962
1949
  }
1963
- async reconfigureUrl(url){
1950
+ async reconfigureUrl(url) {
1964
1951
  this.#data = null;
1965
1952
  this.#url = url;
1966
1953
  }
@@ -1969,9 +1956,9 @@ var ful = (function (exports, ftl) {
1969
1956
  return
1970
1957
  }
1971
1958
  const storageKey = `${this.#method}@${this.#url}`;
1972
- if(this.#revision !== null){
1959
+ if (this.#revision !== null) {
1973
1960
  const data = VersionedLocalStorage.load(storageKey, this.#revision);
1974
- if(data !== undefined){
1961
+ if (data !== undefined) {
1975
1962
  this.#data = data;
1976
1963
  return;
1977
1964
  }
@@ -1979,7 +1966,7 @@ var ful = (function (exports, ftl) {
1979
1966
  const data = await this.#http.request(this.#method, this.#url)
1980
1967
  .fetchJson();
1981
1968
  this.#data = this.#responseMapper(data);
1982
- if(this.#revision !== null){
1969
+ if (this.#revision !== null) {
1983
1970
  VersionedLocalStorage.save(storageKey, this.#revision, this.#data);
1984
1971
  }
1985
1972
  }
@@ -1990,15 +1977,7 @@ var ful = (function (exports, ftl) {
1990
1977
  #url;
1991
1978
  #method;
1992
1979
  #responseMapper;
1993
- static create({ el, http, responseMapper }) {
1994
- return new PartialRemoteLoader({
1995
- http,
1996
- url: el.getAttribute("src"),
1997
- method: el.getAttribute("method") ?? 'POST',
1998
- responseMapper
1999
- });
2000
- }
2001
- constructor({http, url, method, responseMapper}) {
1980
+ constructor({ http, url, method, responseMapper }) {
2002
1981
  this.#http = http;
2003
1982
  this.#url = url;
2004
1983
  this.#method = method;
@@ -2036,16 +2015,49 @@ var ful = (function (exports, ftl) {
2036
2015
 
2037
2016
 
2038
2017
  class SelectLoader {
2039
- static create(conf) {
2040
- if (!conf.el.hasAttribute("src")) {
2041
- const els = Array.from(conf.options.options?.querySelectorAll('option') ?? []);
2018
+ static create(el, conf) {
2019
+ if (!el.hasAttribute("src")) {
2020
+ const els = Array.from(conf.options?.querySelectorAll('option') ?? []);
2042
2021
  const data = els.map(e => {
2043
2022
  return [e.getAttribute("value") ?? e.innerText.trim(), e.innerText.trim()];
2044
2023
  });
2045
2024
  return new InMemoryLoader(data);
2046
2025
  }
2047
- const chunked = "chunked" == conf.el.getAttribute("mode");
2048
- return chunked ? PartialRemoteLoader.create(conf) : RemoteLoader.create(conf);
2026
+ const http = ftl.registry.component("http-client");
2027
+ const responseMapper = SelectLoader.#responseMapperFrom(el);
2028
+
2029
+ if ("chunked" == el.getAttribute("mode")) {
2030
+ return new PartialRemoteLoader({
2031
+ http,
2032
+ url: el.getAttribute("src"),
2033
+ method: el.getAttribute("method") ?? 'POST',
2034
+ responseMapper
2035
+ })
2036
+ }
2037
+ return new RemoteLoader({
2038
+ http,
2039
+ url: el.getAttribute("src"),
2040
+ method: el.getAttribute("method") ?? 'POST',
2041
+ responseMapper,
2042
+ prefetch: el.hasAttribute("preload"),
2043
+ revision: el.getAttribute("revision")
2044
+ });
2045
+ }
2046
+ static #responseMapperFrom(el) {
2047
+ if (el.hasAttribute("k-expr") && el.hasAttribute("l-expr")) {
2048
+ return v => {
2049
+ const evaluator = ftl.registry.evaluator().withOverlay(v);
2050
+ return [
2051
+ evaluator.evaluateExpression(el.getAttribute("k-expr")),
2052
+ evaluator.evaluateExpression(el.getAttribute("l-expr")),
2053
+ evaluator.evaluateExpression(el.getAttribute("m-expr") ?? 'self'),
2054
+ ];
2055
+ };
2056
+ }
2057
+ if (el.hasAttribute("response-mapper")) {
2058
+ return ftl.registry.component(el.getAttribute("response-mapper"));
2059
+ }
2060
+ return v => v;
2049
2061
  }
2050
2062
  }
2051
2063
 
@@ -2080,7 +2092,7 @@ var ful = (function (exports, ftl) {
2080
2092
  if (values === undefined) {
2081
2093
  throw new Error("null data");
2082
2094
  }
2083
- this.#options = new Map(values.map((v,i) => [String(i), v]));
2095
+ this.#options = new Map(values.map((v, i) => [String(i), v]));
2084
2096
  if (values.length === 0) {
2085
2097
  const el = document.createElement('div');
2086
2098
  el.classList.add('text-center', 'py-2', 'bi', 'bi-database-slash');
@@ -2132,7 +2144,7 @@ var ful = (function (exports, ftl) {
2132
2144
  if (candidate) {
2133
2145
  selected.removeAttribute('selected');
2134
2146
  candidate.setAttribute("selected", "");
2135
- candidate.scrollIntoView({block: "nearest", behavior: "smooth"});
2147
+ candidate.scrollIntoView({ block: "nearest", behavior: "smooth" });
2136
2148
  }
2137
2149
  return;
2138
2150
  }
@@ -2141,7 +2153,7 @@ var ful = (function (exports, ftl) {
2141
2153
  }
2142
2154
 
2143
2155
  class Select extends ftl.ParsedElement {
2144
- static observed = ['value:csvm', 'readonly:presence', 'itemlist:presence']
2156
+ static observed = ['value:csvm', 'readonly:presence', "required:presence", 'itemlist:presence']
2145
2157
  static slots = true
2146
2158
  static template = `
2147
2159
  <div class="form-label">
@@ -2171,15 +2183,7 @@ var ful = (function (exports, ftl) {
2171
2183
  <button type="button" class="btn btn-sm btn-outline-danger bi bi-x-lg"></button>
2172
2184
  </ful-item>
2173
2185
  `
2174
- }
2175
- static mappers = {
2176
- "csvm": (v, name, el) => {
2177
- if (el.hasAttribute("multiple")) {
2178
- return v === null ? [] : v.split(",").map(e => e.trim()).filter(e => e)
2179
- }
2180
- return v === null || v === '' ? null : v
2181
- }
2182
- };
2186
+ }
2183
2187
  static formAssociated = true
2184
2188
  internals
2185
2189
  #loader
@@ -2197,7 +2201,8 @@ var ful = (function (exports, ftl) {
2197
2201
  }
2198
2202
  async render({ slots, observed, disabled }) {
2199
2203
  const name = this.getAttribute("name");
2200
- this.#loader = Loaders.fromAttributes(this, 'loaders:select', { options: slots.options });
2204
+ this.#loader = ftl.registry.component(this.getAttribute("loader") ?? 'loaders:select').create(this, {options: slots.options});
2205
+
2201
2206
  this.#multiple = this.hasAttribute("multiple");
2202
2207
  await this.#loader.prefetch?.();
2203
2208
  const fragment = this.template().withOverlay({ slots, name }).render();
@@ -2209,6 +2214,7 @@ var ful = (function (exports, ftl) {
2209
2214
  this.value = observed.value;
2210
2215
  this.disabled = disabled;
2211
2216
  this.readonly = observed.readonly;
2217
+ this.required = observed.required;
2212
2218
  this.itemlist = observed.itemlist;
2213
2219
 
2214
2220
  this.#ddmenu = fragment.querySelector('ful-dropdown');
@@ -2224,7 +2230,7 @@ var ful = (function (exports, ftl) {
2224
2230
  if (e.target.matches('input')) {
2225
2231
  return;
2226
2232
  }
2227
- if(this.disabled || this.readonly){
2233
+ if (this.disabled || this.readonly) {
2228
2234
  return;
2229
2235
  }
2230
2236
  if (this.#ddmenu.shown) {
@@ -2239,9 +2245,9 @@ var ful = (function (exports, ftl) {
2239
2245
  if (!e.target.closest("button")) {
2240
2246
  return;
2241
2247
  }
2242
- if(this.disabled || this.readonly){
2248
+ if (this.disabled || this.readonly) {
2243
2249
  return;
2244
- }
2250
+ }
2245
2251
  const idx = [...this.#items.children].indexOf(e.target.closest('ful-item'));
2246
2252
  if (idx === -1) {
2247
2253
  return;
@@ -2252,7 +2258,7 @@ var ful = (function (exports, ftl) {
2252
2258
  });
2253
2259
  this.#badges.addEventListener('click', (e) => {
2254
2260
  e.stopPropagation();
2255
- if(this.disabled || this.readonly){
2261
+ if (this.disabled || this.readonly) {
2256
2262
  return;
2257
2263
  }
2258
2264
  const idx = [...this.#badges.children].indexOf(e.target);
@@ -2277,7 +2283,7 @@ var ful = (function (exports, ftl) {
2277
2283
  });
2278
2284
  this.#input.addEventListener('keydown', e => {
2279
2285
  e.stopPropagation();
2280
- if(this.disabled || this.readonly){
2286
+ if (this.disabled || this.readonly) {
2281
2287
  return;
2282
2288
  }
2283
2289
  switch (e.code) {
@@ -2316,7 +2322,7 @@ var ful = (function (exports, ftl) {
2316
2322
  });
2317
2323
  this.#input.addEventListener('input', e => {
2318
2324
  e.stopPropagation();
2319
- if(this.disabled || this.readonly){
2325
+ if (this.disabled || this.readonly) {
2320
2326
  return;
2321
2327
  }
2322
2328
  dload();
@@ -2339,7 +2345,7 @@ var ful = (function (exports, ftl) {
2339
2345
  await fn(this.#loader);
2340
2346
  }
2341
2347
  #changed() {
2342
- const selection = [...this.#values.entries()].map(e => ({key: e[0], label: e[1][0], metadata: e[1].slice(1)}));
2348
+ const selection = [...this.#values.entries()].map(e => ({ key: e[0], label: e[1][0], metadata: e[1].slice(1) }));
2343
2349
  const value = this.#multiple ? selection : (selection[0] ?? null);
2344
2350
  this.dispatchEvent(new CustomEvent('change', {
2345
2351
  bubbles: true,
@@ -2361,10 +2367,10 @@ var ful = (function (exports, ftl) {
2361
2367
  this.template('items').withOverlay({ entries: this.#values.entries() }).renderTo(this.#items);
2362
2368
  }
2363
2369
  set value(vs) {
2364
- if(vs === null){
2370
+ if (vs === null) {
2365
2371
  this.#values = new Map();
2366
2372
  this.#syncBadges();
2367
- return;
2373
+ return;
2368
2374
  }
2369
2375
  (async () => {
2370
2376
  const entries = await (this.#multiple ? this.#loader.exact(...vs) : this.#loader.exact(vs));
@@ -2385,14 +2391,14 @@ var ful = (function (exports, ftl) {
2385
2391
  return [...this.#values.entries()][0] ?? null;
2386
2392
  }
2387
2393
  //@ts-ignore
2388
- get disabled(){
2394
+ get disabled() {
2389
2395
  return this.#input.hasAttribute('disabled');
2390
2396
  }
2391
- set disabled(d){
2397
+ set disabled(d) {
2392
2398
  ftl.Attributes.toggle(this.#input, 'disabled', d);
2393
- }
2394
- get readonly(){
2395
- return this.#input.readOnly;
2399
+ }
2400
+ get readonly() {
2401
+ return this.#input.readOnly;
2396
2402
  }
2397
2403
  set readonly(v) {
2398
2404
  this.#input.readOnly = v;
@@ -2400,6 +2406,15 @@ var ful = (function (exports, ftl) {
2400
2406
  ftl.Attributes.toggle(this, 'readonly', v);
2401
2407
  });
2402
2408
  }
2409
+ get required() {
2410
+ return this.#input.getAttribute('aria-required') === 'true';
2411
+ }
2412
+ set required(d) {
2413
+ ftl.Attributes.set(this.#input, "aria-required", d ? "true" : null);
2414
+ this.reflect(() => {
2415
+ ftl.Attributes.toggle(this, 'required', d);
2416
+ });
2417
+ }
2403
2418
  #useItemlist;
2404
2419
  get itemlist() {
2405
2420
  return this.#useItemlist;
@@ -2425,7 +2440,7 @@ var ful = (function (exports, ftl) {
2425
2440
  }
2426
2441
 
2427
2442
  class RadioGroup extends ftl.ParsedElement {
2428
- static observed = ['value', 'readonly:presence'];
2443
+ static observed = ['value', 'readonly:presence', "required:presence"];
2429
2444
  static slots = true;
2430
2445
  static template = `
2431
2446
  <fieldset>
@@ -2489,6 +2504,7 @@ var ful = (function (exports, ftl) {
2489
2504
  this.#fieldset = this.firstElementChild;
2490
2505
  this.disabled = disabled;
2491
2506
  this.readonly = observed.readonly;
2507
+ this.required = observed.required;
2492
2508
  this.value = observed.value;
2493
2509
  this.#fieldError = this.querySelector('ful-field-error');
2494
2510
  this.ariaDescribedByElements = [this.#fieldError];
@@ -2529,6 +2545,15 @@ var ful = (function (exports, ftl) {
2529
2545
  set disabled(d){
2530
2546
  ftl.Attributes.toggle(this.#fieldset, 'disabled', d);
2531
2547
  }
2548
+ get required() {
2549
+ return this.#fieldset.getAttribute('aria-required') === 'true';
2550
+ }
2551
+ set required(d) {
2552
+ ftl.Attributes.set(this.#fieldset, "aria-required", d ? "true" : null);
2553
+ this.reflect(() => {
2554
+ ftl.Attributes.toggle(this, 'required', d);
2555
+ });
2556
+ }
2532
2557
  focus(options) {
2533
2558
  this.#firstRadio.focus(options);
2534
2559
  }
@@ -2544,7 +2569,7 @@ var ful = (function (exports, ftl) {
2544
2569
  }
2545
2570
 
2546
2571
  class Checkbox extends ftl.ParsedElement {
2547
- static observed = ['value:bool', 'readonly:presence'];
2572
+ static observed = ['value:bool', 'readonly:presence', "required:presence"];
2548
2573
  static slots = true;
2549
2574
  static template = `
2550
2575
  <div data-tpl-class="klass">
@@ -2575,6 +2600,7 @@ var ful = (function (exports, ftl) {
2575
2600
  ftl.Attributes.forward('input-', this, this.#input);
2576
2601
  this.disabled = disabled;
2577
2602
  this.readonly = observed.readonly;
2603
+ this.required = observed.required;
2578
2604
  this.value = observed.value;
2579
2605
  this.#input.addEventListener('change', (evt) => {
2580
2606
  evt.stopPropagation();
@@ -2621,6 +2647,15 @@ var ful = (function (exports, ftl) {
2621
2647
  set disabled(d) {
2622
2648
  ftl.Attributes.toggle(this.#input, 'disabled', d);
2623
2649
  }
2650
+ get required() {
2651
+ return this.#input.getAttribute('aria-required') === 'true';
2652
+ }
2653
+ set required(d) {
2654
+ ftl.Attributes.set(this.#input, "aria-required", d ? "true" : null);
2655
+ this.reflect(() => {
2656
+ ftl.Attributes.toggle(this, 'required', d);
2657
+ });
2658
+ }
2624
2659
  focus(options) {
2625
2660
  this.#input.focus(options);
2626
2661
  }
@@ -2868,7 +2903,8 @@ var ful = (function (exports, ftl) {
2868
2903
 
2869
2904
 
2870
2905
  class TableLoader {
2871
- static create({ el, http }) {
2906
+ static create(el, conf) {
2907
+ const http = ftl.registry.component("http-client");
2872
2908
  const url = el.getAttribute("src");
2873
2909
  const method = el.getAttribute("method") ?? 'GET';
2874
2910
  return new RemoteTableLoader(http, url, method);
@@ -2993,7 +3029,8 @@ var ful = (function (exports, ftl) {
2993
3029
  }, this.#latestRequest.sortRequest, this.#latestRequest.filterRequest);
2994
3030
  });
2995
3031
  this.addEventListener('sort-requested', async (/** @type any */e) => {
2996
- await this.load(this.#latestRequest.pageRequest, e.detail.value, this.#latestRequest.filterRequest);
3032
+ const sortRequest = e.detail.value.order ? e.detail.value : null;
3033
+ await this.load(this.#latestRequest.pageRequest, sortRequest, this.#latestRequest.filterRequest);
2997
3034
  this.#sorters.forEach(s => s.order = null);
2998
3035
  e.target.order = e.detail.value.order;
2999
3036
  });
@@ -3011,7 +3048,7 @@ var ful = (function (exports, ftl) {
3011
3048
  this.#feedback.setAttribute("hidden", "");
3012
3049
  this.#noAutoload.setAttribute("hidden", "");
3013
3050
  try {
3014
- const loader = Loaders.fromAttributes(this, 'loaders:table');
3051
+ const loader = ftl.registry.component(this.getAttribute("loader") ?? 'loaders:table').create(this);
3015
3052
  const pageResponse = await loader.load(pageRequest, sortRequest, filterRequest);
3016
3053
  this.#latestRequest = { pageRequest, sortRequest, filterRequest };
3017
3054
  this.#update(pageRequest, sortRequest, filterRequest, pageResponse);
@@ -3048,7 +3085,7 @@ var ful = (function (exports, ftl) {
3048
3085
  }
3049
3086
 
3050
3087
  class InstantFilter extends Input {
3051
- static observed = ['value:json', 'readonly:presence'];
3088
+ static observed = ['value:json', 'readonly:presence', 'required:presence'];
3052
3089
  static template = `
3053
3090
  <div class="form-label">
3054
3091
  <label>{{{{ slots.default }}}}</label>
@@ -3078,10 +3115,14 @@ var ful = (function (exports, ftl) {
3078
3115
  #value1;
3079
3116
  #value2;
3080
3117
  render(conf) {
3081
- super.render({...conf, skipValueSetup: true});
3118
+ super.render({ ...conf, skipObservedSetup: true });
3082
3119
  this.#operator = this.querySelector('[data-ref=operator]');
3083
3120
  this.#value1 = this.querySelector('[data-ref=value1]');
3084
3121
  this.#value2 = this.querySelector('[data-ref=value2]');
3122
+
3123
+ this.disabled = conf.disabled;
3124
+ this.readonly = conf.observed.readonly;
3125
+ this.required = conf.observed.required;
3085
3126
  this.value = conf.observed.value;
3086
3127
 
3087
3128
  this.addEventListener('click', (evt) => {
@@ -3103,7 +3144,7 @@ var ful = (function (exports, ftl) {
3103
3144
  return values.some(v => v === '') ? undefined : [operator, ...values.map(v => new Date(v).toISOString())];
3104
3145
  }
3105
3146
  set value(v) {
3106
- if (v === null || v === undefined) {
3147
+ if (v == null) {
3107
3148
  this.#value1.value = '';
3108
3149
  this.#value2.value = '';
3109
3150
  return;
@@ -3113,10 +3154,18 @@ var ful = (function (exports, ftl) {
3113
3154
  this.#value1.value = values[0] ? Instant.isoToLocal(values[0]) : values[0];
3114
3155
  this.#value2.value = values[1] ? Instant.isoToLocal(values[1]) : values[1];
3115
3156
  }
3157
+ set readonly(v) {
3158
+ this.#value2.readOnly = v;
3159
+ super.readonly = v;
3160
+ }
3161
+ set disabled(d) {
3162
+ ftl.Attributes.toggle(this.#value2, 'disabled', d);
3163
+ super.disabled = d;
3164
+ }
3116
3165
  }
3117
3166
 
3118
3167
  class LocalDateFilter extends Input {
3119
- static observed = ["value:json", 'readonly:presence'];
3168
+ static observed = ["value:json", 'readonly:presence', 'required:presence'];
3120
3169
  static template = `
3121
3170
  <div class="form-label">
3122
3171
  <label>{{{{ slots.default }}}}</label>
@@ -3146,13 +3195,17 @@ var ful = (function (exports, ftl) {
3146
3195
  #value1;
3147
3196
  #value2;
3148
3197
  render(conf) {
3149
- super.render({...conf, skipValueSetup: true});
3198
+ super.render({ ...conf, skipObservedSetup: true });
3150
3199
 
3151
3200
  this.#operator = this.querySelector('[data-ref=operator]');
3152
3201
  this.#value1 = this.querySelector('[data-ref=value1]');
3153
3202
  this.#value2 = this.querySelector('[data-ref=value2]');
3203
+
3204
+ this.disabled = conf.disabled;
3205
+ this.readonly = conf.observed.readonly;
3206
+ this.required = conf.observed.required;
3154
3207
  this.value = conf.observed.value;
3155
-
3208
+
3156
3209
  this.addEventListener('click', (evt) => {
3157
3210
  const target = /** @type HTMLElement */(evt.target);
3158
3211
  if (!target.matches('ul > li > a')) {
@@ -3171,7 +3224,7 @@ var ful = (function (exports, ftl) {
3171
3224
  return values.some(v => v === '') ? undefined : [operator, ...values];
3172
3225
  }
3173
3226
  set value(v) {
3174
- if (v === null || v === undefined) {
3227
+ if (v == null) {
3175
3228
  this.#value1.value = '';
3176
3229
  this.#value2.value = '';
3177
3230
  return;
@@ -3181,10 +3234,18 @@ var ful = (function (exports, ftl) {
3181
3234
  this.#value1.value = values[0];
3182
3235
  this.#value2.value = values[1];
3183
3236
  }
3237
+ set readonly(v) {
3238
+ this.#value2.readOnly = v;
3239
+ super.readonly = v;
3240
+ }
3241
+ set disabled(d) {
3242
+ ftl.Attributes.toggle(this.#value2, 'disabled', d);
3243
+ super.disabled = d;
3244
+ }
3184
3245
  }
3185
3246
 
3186
3247
  class TextFilter extends Input {
3187
- static observed = ["value:json", 'readonly:presence'];
3248
+ static observed = ["value:json", 'readonly:presence', 'required:presence'];
3188
3249
  static template = `
3189
3250
  <div class="form-label">
3190
3251
  <label>{{{{ slots.default }}}}</label>
@@ -3209,10 +3270,14 @@ var ful = (function (exports, ftl) {
3209
3270
  #operator;
3210
3271
  #value;
3211
3272
  render(conf) {
3212
- super.render({...conf, skipValueSetup: true});
3273
+ super.render({ ...conf, skipObservedSetup: true });
3213
3274
 
3214
3275
  this.#operator = this.querySelector('[data-ref=operator]');
3215
3276
  this.#value = this.querySelector('[data-ref=value]');
3277
+
3278
+ this.disabled = conf.disabled;
3279
+ this.readonly = conf.observed.readonly;
3280
+ this.required = conf.observed.required;
3216
3281
  this.value = conf.observed.value;
3217
3282
 
3218
3283
  this.addEventListener('click', (evt) => {
@@ -3226,14 +3291,12 @@ var ful = (function (exports, ftl) {
3226
3291
  btn.innerHTML = target.innerHTML;
3227
3292
  });
3228
3293
  }
3229
-
3230
3294
  get value() {
3231
3295
  const operator = this.#operator.getAttribute('value');
3232
3296
  return this.#value.value === '' ? undefined : [operator, 'IGNORE_CASE', this.#value.value];
3233
3297
  }
3234
-
3235
3298
  set value(v) {
3236
- if (v === null || v === undefined) {
3299
+ if (v == null) {
3237
3300
  this.#value.value = '';
3238
3301
  return;
3239
3302
  }
@@ -3318,7 +3381,6 @@ var ful = (function (exports, ftl) {
3318
3381
  exports.InputLocalTime = InputLocalTime;
3319
3382
  exports.Instant = Instant;
3320
3383
  exports.InstantFilter = InstantFilter;
3321
- exports.Loaders = Loaders;
3322
3384
  exports.LocalDate = LocalDate;
3323
3385
  exports.LocalDateFilter = LocalDateFilter;
3324
3386
  exports.LocalStorage = LocalStorage;