@optionfactory/ful 1.0.12 → 1.0.14

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.mjs CHANGED
@@ -1386,7 +1386,9 @@ class Form extends ParsedElement {
1386
1386
  this.spinner(false);
1387
1387
  }
1388
1388
  }
1389
-
1389
+ reset(){
1390
+ this.form.reset();
1391
+ }
1390
1392
  spinner(spin) {
1391
1393
  this.querySelectorAll('ful-spinner').forEach(el => {
1392
1394
  const hel = /** @type HTMLElement */ (el);
@@ -1412,16 +1414,16 @@ class Input extends ParsedElement {
1412
1414
  static observed = ['value', 'readonly:presence'];
1413
1415
  static slots = true;
1414
1416
  static template = `
1415
- <label data-tpl-for="id" class="form-label">{{{{ slots.default }}}}</label>
1417
+ <label class="form-label">{{{{ slots.default }}}}</label>
1416
1418
  <div class="input-group">
1417
1419
  <span data-tpl-if="slots.ibefore" class="input-group-text">{{{{ slots.ibefore }}}}</span>
1418
1420
  {{{{ slots.before }}}}
1419
- <input data-tpl-if="type != 'textarea'" class="form-control" data-tpl-id="id" data-tpl-type="type" placeholder=" " data-tpl-aria-describedby="fieldErrorId" form="">
1420
- <textarea data-tpl-if="type == 'textarea'" class="form-control" data-tpl-id="id" placeholder=" " data-tpl-aria-describedby="fieldErrorId" form=""></textarea>
1421
+ <input data-tpl-if="type != 'textarea'" class="form-control" data-tpl-type="type" placeholder=" " form="">
1422
+ <textarea data-tpl-if="type == 'textarea'" class="form-control" placeholder=" " form=""></textarea>
1421
1423
  {{{{ slots.after }}}}
1422
1424
  <span data-tpl-if="slots.iafter" class="input-group-text">{{{{ slots.iafter }}}}</span>
1423
1425
  </div>
1424
- <ful-field-error data-tpl-id="fieldErrorId"></ful-field-error>
1426
+ <ful-field-error></ful-field-error>
1425
1427
  `;
1426
1428
  static formAssociated = true;
1427
1429
  #input;
@@ -1429,15 +1431,18 @@ class Input extends ParsedElement {
1429
1431
  constructor() {
1430
1432
  super();
1431
1433
  this.internals = this.attachInternals();
1432
- this.internals.role = 'textbox';
1434
+ this.internals.role = 'presentation';
1433
1435
  }
1434
- render({ slots }) {
1435
- const id = Attributes.uid('ful-input');
1436
- const fieldErrorId = `${id}-error`;
1436
+ render({ slots, observed, disabled }) {
1437
1437
  const type = this.getAttribute("type") ?? 'text';
1438
- const fragment = this.template().withOverlay({ id, type, fieldErrorId, slots }).render();
1438
+ const fragment = this.template().withOverlay({ type, slots }).render();
1439
1439
  this.#input = fragment.querySelector("input,textarea");
1440
+
1440
1441
  Attributes.forward('input-', this, this.#input);
1442
+ this.disabled = disabled;
1443
+ this.readonly = observed.readonly;
1444
+ this.value = observed.value;
1445
+
1441
1446
  this.#input.addEventListener('change', (evt) => {
1442
1447
  evt.stopPropagation();
1443
1448
  this.dispatchEvent(new CustomEvent('change', {
@@ -1448,7 +1453,11 @@ class Input extends ParsedElement {
1448
1453
  }
1449
1454
  }));
1450
1455
  });
1456
+ const label = fragment.querySelector('label');
1457
+ label.addEventListener('click', () => this.focus());
1451
1458
  this.#fieldError = fragment.querySelector('ful-field-error');
1459
+ this.#input.ariaDescribedByElements = [this.#fieldError];
1460
+ this.#input.ariaLabelledByElements = [label];
1452
1461
  this.replaceChildren(fragment);
1453
1462
  }
1454
1463
  get value() {
@@ -1463,6 +1472,12 @@ class Input extends ParsedElement {
1463
1472
  set readonly(v) {
1464
1473
  this.#input.readOnly = v;
1465
1474
  }
1475
+ get disabled(){
1476
+ return this.#input.hasAttribute('disabled');
1477
+ }
1478
+ set disabled(d){
1479
+ Attributes.toggle(this.#input, 'disabled', d);
1480
+ }
1466
1481
  focus(options) {
1467
1482
  this.#input.focus(options);
1468
1483
  }
@@ -1475,6 +1490,9 @@ class Input extends ParsedElement {
1475
1490
  this.internals.setValidity({ customError: true }, " ");
1476
1491
  this.#fieldError.innerText = error;
1477
1492
  }
1493
+ formResetCallback(){
1494
+ this.value = this.getAttribute("value");
1495
+ }
1478
1496
  }
1479
1497
 
1480
1498
  class CompleteSelectLoader {
@@ -1564,11 +1582,9 @@ class OptionsSlotSelectLoader {
1564
1582
  this.#data = data;
1565
1583
  }
1566
1584
  async exact(...keys) {
1567
- await timing.sleep(500);
1568
1585
  return this.#data.filter(([k, v]) => keys.includes(k));
1569
1586
  }
1570
1587
  async load(needle) {
1571
- await timing.sleep(500);
1572
1588
  return this.#data.filter(([k, v]) => v.includes(needle?.toLowerCase()));
1573
1589
  }
1574
1590
  }
@@ -1677,22 +1693,22 @@ class Dropdown extends ParsedElement {
1677
1693
  }
1678
1694
 
1679
1695
  class Select extends ParsedElement {
1680
- static observed = ['value:csvm']
1696
+ static observed = ['value:csvm', 'readonly:presence']
1681
1697
  static slots = true
1682
1698
  static template = `
1683
- <label data-tpl-for="id" class="form-label">{{{{ slots.default }}}}</label>
1699
+ <label class="form-label">{{{{ slots.default }}}}</label>
1684
1700
  <div class="input-group flex-nowrap" tabindex="-1">
1685
1701
  <span data-tpl-if="slots.ibefore" class="input-group-text">{{{{ slots.ibefore }}}}</span>
1686
1702
  {{{{ slots.before }}}}
1687
1703
  <div class="ful-select-input">
1688
1704
  <badges></badges>
1689
- <input data-tpl-id="id" data-tpl-ariadesribed-by="fieldErrorId" type="text" form="">
1705
+ <input type="text" form="">
1690
1706
  </div>
1691
1707
  {{{{ slots.after }}}}
1692
1708
  <span data-tpl-if="slots.iafter" class="input-group-text">{{{{ slots.iafter }}}}</span>
1693
1709
  </div>
1694
1710
  <ful-dropdown hidden></ful-dropdown>
1695
- <ful-field-error data-tpl-id="fieldErrorId"></ful-field-error>
1711
+ <ful-field-error></ful-field-error>
1696
1712
  `;
1697
1713
  static mappers = {
1698
1714
  "csvm": (v, name, el) => {
@@ -1714,20 +1730,27 @@ class Select extends ParsedElement {
1714
1730
  constructor() {
1715
1731
  super();
1716
1732
  this.internals = this.attachInternals();
1717
- this.internals.role = 'combobox';
1733
+ this.internals.role = 'presentation';
1718
1734
  }
1719
- async render({ slots, observed }) {
1735
+ async render({ slots, observed, disabled }) {
1720
1736
  const name = this.getAttribute("name");
1721
- const id = Attributes.uid('ful-select');
1722
- const fieldErrorId = id + "-error";
1723
1737
  this.#loader = Loaders.fromAttributes(this, 'loaders:select', { options: slots.options });
1724
1738
  await this.#loader.prefetch?.();
1725
- const fragment = this.template().withOverlay({ slots, name, id, fieldErrorId }).render();
1739
+ const fragment = this.template().withOverlay({ slots, name }).render();
1726
1740
  this.#input = fragment.querySelector('input');
1727
1741
  this.#badges = fragment.querySelector('badges');
1742
+
1743
+ this.value = observed.value;
1744
+ this.disabled = disabled;
1745
+ this.readonly = observed.readonly;
1746
+
1728
1747
  this.#ddmenu = fragment.querySelector('ful-dropdown');
1729
1748
  this.#multiple = this.hasAttribute("multiple");
1749
+ const label = fragment.querySelector('label');
1750
+ label.addEventListener('click', () => this.focus());
1730
1751
  this.#fieldError = fragment.querySelector('ful-field-error');
1752
+ this.#input.ariaDescribedByElements = [this.#fieldError];
1753
+ this.#input.ariaLabelledByElements = [label];
1731
1754
 
1732
1755
  const self = this;
1733
1756
  const [dload, abortdload] = timing.debounce(400, () => self.#ddmenu.show(() => self.#loader.load(self.#input.value)));
@@ -1821,6 +1844,11 @@ class Select extends ParsedElement {
1821
1844
  this.#badges.append(...badges);
1822
1845
  }
1823
1846
  set value(value) {
1847
+ if(value === null){
1848
+ this.#values = new Map();
1849
+ this.#syncBadges();
1850
+ return;
1851
+ }
1824
1852
  (async () => {
1825
1853
  const entries = await (this.#multiple ? this.#loader.exact(...value) : this.#loader.exact(value));
1826
1854
  this.#values = new Map(entries);
@@ -1848,10 +1876,10 @@ class Select extends ParsedElement {
1848
1876
  }
1849
1877
 
1850
1878
  class RadioGroup extends ParsedElement {
1851
- static observed = ['value'];
1879
+ static observed = ['value', 'readonly:presence'];
1852
1880
  static slots = true;
1853
1881
  static template = `
1854
- <fieldset data-tpl-aria-describedby="fieldErrorId">
1882
+ <fieldset>
1855
1883
  <legend class="form-label">
1856
1884
  {{{{ slots.default }}}}
1857
1885
  </legend>
@@ -1866,13 +1894,14 @@ class RadioGroup extends ParsedElement {
1866
1894
  </label>
1867
1895
  </div>
1868
1896
  </section>
1869
- <ful-field-error data-tpl-id="fieldErrorId"></ful-field-error>
1897
+ <ful-field-error></ful-field-error>
1870
1898
  <footer data-tpl-if="slots.footer">
1871
1899
  {{{{ slots.footer }}}}
1872
1900
  </footer>
1873
1901
  </fieldset>
1874
1902
  `;
1875
1903
  static formAssociated = true;
1904
+ #fieldset;
1876
1905
  #fieldError;
1877
1906
  #firstRadio;
1878
1907
  #booleanType;
@@ -1881,7 +1910,7 @@ class RadioGroup extends ParsedElement {
1881
1910
  this.internals = this.attachInternals();
1882
1911
  this.internals.role = 'radiogroup';
1883
1912
  }
1884
- render({ slots }) {
1913
+ render({ slots, observed, disabled }) {
1885
1914
  const name = this.getAttribute('name') ?? Attributes.uid('ful-radiogroup');
1886
1915
  const radioEls = Array.from(slots.default.querySelectorAll('ful-radio'));
1887
1916
  const inputsAndLabels = radioEls.map(el => {
@@ -1907,9 +1936,13 @@ class RadioGroup extends ParsedElement {
1907
1936
  });
1908
1937
 
1909
1938
  radioEls.forEach(el => el.remove());
1910
- const fieldErrorId = Attributes.uid("ful-error");
1911
- this.template().withOverlay({ name, fieldErrorId, slots, inputsAndLabels }).renderTo(this);
1939
+ this.template().withOverlay({ name, slots, inputsAndLabels }).renderTo(this);
1940
+ this.#fieldset = this.firstElementChild;
1941
+ this.disabled = disabled;
1942
+ this.readonly = observed.readonly;
1943
+ this.value = observed.value;
1912
1944
  this.#fieldError = this.querySelector('ful-field-error');
1945
+ this.ariaDescribedByElements = [this.#fieldError];
1913
1946
  this.#firstRadio = this.querySelector('input[type=radio]');
1914
1947
  this.#booleanType = this.getAttribute('type') === 'boolean';
1915
1948
  }
@@ -1930,7 +1963,19 @@ class RadioGroup extends ParsedElement {
1930
1963
  if (el) {
1931
1964
  el.checked = true;
1932
1965
  }
1966
+ }
1967
+ get readonly(){
1968
+ return this.#fieldset.inert;
1969
+ }
1970
+ set readonly(v) {
1971
+ this.#fieldset.inert = v;
1972
+ }
1973
+ get disabled(){
1974
+ return this.#fieldset.hasAttribute('disabled');
1933
1975
  }
1976
+ set disabled(d){
1977
+ Attributes.toggle(this.#fieldset, 'disabled', d);
1978
+ }
1934
1979
  focus(options) {
1935
1980
  this.#firstRadio.focus(options);
1936
1981
  }
@@ -1946,34 +1991,35 @@ class RadioGroup extends ParsedElement {
1946
1991
  }
1947
1992
 
1948
1993
  class Checkbox extends ParsedElement {
1949
- static observed = ['value:bool'];
1994
+ static observed = ['value:bool', 'readonly:presence'];
1950
1995
  static slots = true;
1951
1996
  static template = `
1952
1997
  <div data-tpl-class="klass">
1953
1998
  <div class="input-container">
1954
- <input data-tpl-id="id" class="form-check-input" type="checkbox" role="switch" form="" placeholder=" " data-tpl-aria-describedby="fieldErrorId">
1999
+ <input class="form-check-input" type="checkbox" role="switch" form="" placeholder=" ">
1955
2000
  </div>
1956
- <label data-tpl-for="id" class="form-check-label">{{{{ slots.default }}}}</label>
2001
+ <label class="form-check-label">{{{{ slots.default }}}}</label>
1957
2002
  </div>
1958
- <ful-field-error data-tpl-if="fieldErrorId"></ful-field-error>
2003
+ <ful-field-error></ful-field-error>
1959
2004
  `;
2005
+ #container;
1960
2006
  #input;
1961
2007
  #fieldError;
1962
2008
  static formAssociated = true;
1963
2009
  constructor() {
1964
2010
  super();
1965
2011
  this.internals = this.attachInternals();
1966
- this.internals.role = 'checkbox';
2012
+ this.internals.role = 'presentation';
1967
2013
  }
1968
- render({ slots }) {
1969
- const id = Attributes.uid("ful-checkbox");
1970
- const fieldErrorId = id + "-error";
2014
+ render({ slots, observed, disabled }) {
1971
2015
  const klass = this.getAttribute('type') == 'switch' ? "form-check form-switch" : "form-check";
1972
- this.internals.role = this.getAttribute('type') == 'switch' ? 'switch' : 'checkbox';
1973
- const fragment = this.template().withOverlay({ slots, klass, id, fieldErrorId }).render();
2016
+ const fragment = this.template().withOverlay({ slots, klass }).render();
2017
+ this.#container = fragment.firstElementChild;
1974
2018
  this.#input = fragment.querySelector("input");
1975
2019
  Attributes.forward('input-', this, this.#input);
1976
- this.#fieldError = fragment.querySelector('ful-field-error');
2020
+ this.disabled = disabled;
2021
+ this.readonly = observed.readonly;
2022
+ this.value = observed.value;
1977
2023
  this.#input.addEventListener('change', (evt) => {
1978
2024
  evt.stopPropagation();
1979
2025
  this.dispatchEvent(new CustomEvent('change', {
@@ -1983,7 +2029,18 @@ class Checkbox extends ParsedElement {
1983
2029
  value: this.value
1984
2030
  }
1985
2031
  }));
1986
- });
2032
+ });
2033
+ const label = fragment.querySelector('label');
2034
+ label.addEventListener('click', () => {
2035
+ this.focus();
2036
+ if (this.disabled || this.readonly) {
2037
+ return;
2038
+ }
2039
+ this.value = !this.value;
2040
+ });
2041
+ this.#fieldError = fragment.querySelector('ful-field-error');
2042
+ this.#input.ariaDescribedByElements = [this.#fieldError];
2043
+ this.#input.ariaLabelledByElements = [label];
1987
2044
  this.replaceChildren(fragment);
1988
2045
  }
1989
2046
  get value() {
@@ -1992,6 +2049,18 @@ class Checkbox extends ParsedElement {
1992
2049
  set value(value) {
1993
2050
  this.#input.checked = value;
1994
2051
  }
2052
+ get readonly(){
2053
+ return this.#container.inert;
2054
+ }
2055
+ set readonly(v) {
2056
+ this.#container.inert = v;
2057
+ }
2058
+ get disabled() {
2059
+ return this.#input.hasAttribute('disabled');
2060
+ }
2061
+ set disabled(d) {
2062
+ Attributes.toggle(this.#input, 'disabled', d);
2063
+ }
1995
2064
  focus(options) {
1996
2065
  this.#input.focus(options);
1997
2066
  }
@@ -2215,48 +2284,50 @@ class Table extends ParsedElement {
2215
2284
  <ful-form data-tpl-if="slots.filters">
2216
2285
  {{{{ slots.filters }}}}
2217
2286
  </ful-form>
2218
- <table class="table">
2219
- <caption data-tpl-if="slots.caption">{{{{ slots.caption }}}}</caption>
2220
- <thead>
2221
- <tr>
2222
- <th data-tpl-each="schema" scope="col" data-tpl-class="title.classes">
2223
- {{{{ title.fragment }}}}
2224
- <ful-sorter data-tpl-if="sorter || order" data-tpl-sorter="sorter" data-tpl-order="order"></ful-sorter>
2225
- </th>
2226
- </tr>
2227
- </thead>
2228
- <tbody></tbody>
2229
- <tbody data-ref="no-autoload">
2230
- <tr>
2231
- <td data-tpl-colspan="schema.length" class="text-center align-middle p-4">
2232
- <i class="bi bi-search" style="font-size: 40px; color: #BDC3CA"></i>
2233
- <p class="mt-3 mb-0" style="color: #BDC3CA">
2234
- Avvia la ricerca per visualizzare i risultati...
2235
- </p>
2236
- </td>
2237
- </tr>
2238
- </tbody>
2239
- <tbody data-ref="loading" hidden>
2240
- <tr>
2241
- <td data-tpl-colspan="schema.length" class="text-center align-middle p-4">
2242
- <ful-spinner class="big"></ful-spinner>
2243
- </td>
2244
- </tr>
2245
- </tbody>
2246
- <tbody data-ref="feedback" hidden>
2247
- <tr>
2248
- <td data-tpl-colspan="schema.length" class="text-center align-middle p-4">
2249
- <div class="alert alert-danger">
2250
- <p>Errore nel caricamento della tabella:</p>
2251
- <p class="mb-0" data-ref="feedback-error"></p>
2252
- </div>
2253
- </td>
2254
- </tr>
2255
- </tbody>
2256
- <tfoot data-tpl-if="slots.footer">
2257
- {{{{ slots.footer }}}}
2258
- </tfoot>
2259
- </table>
2287
+ <div class="table-wrapper">
2288
+ <table class="table">
2289
+ <caption data-tpl-if="slots.caption">{{{{ slots.caption }}}}</caption>
2290
+ <thead>
2291
+ <tr>
2292
+ <th data-tpl-each="schema" scope="col" data-tpl-class="title.classes">
2293
+ {{{{ title.fragment }}}}
2294
+ <ful-sorter data-tpl-if="sorter || order" data-tpl-sorter="sorter" data-tpl-order="order"></ful-sorter>
2295
+ </th>
2296
+ </tr>
2297
+ </thead>
2298
+ <tbody></tbody>
2299
+ <tbody data-ref="no-autoload">
2300
+ <tr>
2301
+ <td data-tpl-colspan="schema.length" class="text-center align-middle p-4">
2302
+ <i class="bi bi-search" style="font-size: 40px; color: #BDC3CA"></i>
2303
+ <p class="mt-3 mb-0" style="color: #BDC3CA">
2304
+ Avvia la ricerca per visualizzare i risultati...
2305
+ </p>
2306
+ </td>
2307
+ </tr>
2308
+ </tbody>
2309
+ <tbody data-ref="loading" hidden>
2310
+ <tr>
2311
+ <td data-tpl-colspan="schema.length" class="text-center align-middle p-4">
2312
+ <ful-spinner class="big"></ful-spinner>
2313
+ </td>
2314
+ </tr>
2315
+ </tbody>
2316
+ <tbody data-ref="feedback" hidden>
2317
+ <tr>
2318
+ <td data-tpl-colspan="schema.length" class="text-center align-middle p-4">
2319
+ <div class="alert alert-danger">
2320
+ <p>Errore nel caricamento della tabella:</p>
2321
+ <p class="mb-0" data-ref="feedback-error"></p>
2322
+ </div>
2323
+ </td>
2324
+ </tr>
2325
+ </tbody>
2326
+ <tfoot data-tpl-if="slots.footer">
2327
+ {{{{ slots.footer }}}}
2328
+ </tfoot>
2329
+ </table>
2330
+ </div>
2260
2331
  <ful-pagination current="0" total="1"></ful-pagination>
2261
2332
  `;
2262
2333
  static templates = {
@@ -2285,7 +2356,8 @@ class Table extends ParsedElement {
2285
2356
  const template = this.template();
2286
2357
  const schema = TableSchemaParser.parse(slots.default, template);
2287
2358
  const fragment = template.withOverlay({ slots, schema }).render();
2288
- const table = /** @type HTMLTableElement */ (Nodes.queryChildren(fragment, 'table'));
2359
+ const tableWrapper = /** @type HTMLTableElement */ (Nodes.queryChildren(fragment, '.table-wrapper'));
2360
+ const table = /** @type HTMLTableElement */ (tableWrapper.querySelector("table"));
2289
2361
  Attributes.forward('table-', this, table);
2290
2362
  this.#schema = schema;
2291
2363
  this.#body = table.querySelector(':scope > tbody');
@@ -2378,7 +2450,7 @@ class InstantFilter extends ParsedElement {
2378
2450
  static observed = ["value:json"];
2379
2451
  static slots = true;
2380
2452
  static template = `
2381
- <label data-tpl-for="id" class="form-label" data-tpl-if="label">{{{{ label }}}}</label>
2453
+ <label class="form-label" data-tpl-if="label">{{{{ label }}}}</label>
2382
2454
  <div class="input-group">
2383
2455
  <button data-ref="operator" class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false" value="LTE" form="">&PrecedesSlantEqual;</button>
2384
2456
  <ul class="dropdown-menu">
@@ -2390,7 +2462,7 @@ class InstantFilter extends ParsedElement {
2390
2462
  <li><a class="dropdown-item" role="button" value="GTE">&SucceedsSlantEqual;</a></li>
2391
2463
  <li><a class="dropdown-item" role="button" value="BETWEEN">&LeftRightArrow;</a></li>
2392
2464
  </ul>
2393
- <input data-tpl-id="id" data-ref="value1" type="datetime-local" class="form-control" form="">
2465
+ <input data-ref="value1" type="datetime-local" class="form-control" form="">
2394
2466
  <input data-ref="value2" type="datetime-local" class="form-control" form="" hidden>
2395
2467
  <span class="input-group-text"><i class="bi bi-search"></i></span>
2396
2468
  </div>
@@ -2406,15 +2478,18 @@ class InstantFilter extends ParsedElement {
2406
2478
  this.internals = this.attachInternals();
2407
2479
  }
2408
2480
  render({ slots }) {
2409
- const id = Attributes.uid('instant-filter');
2410
2481
  const label = Fragments.toHtml(slots.default.cloneNode(true)).trim().length === 0 ? null : slots.default;
2411
2482
  const name = this.getAttribute("name");
2412
- const fragment = this.template().withOverlay({ id, label, name }).render(this);
2483
+ const fragment = this.template().withOverlay({ label, name }).render(this);
2413
2484
  this.#operator = fragment.querySelector('[data-ref=operator]');
2414
2485
  this.#value1 = fragment.querySelector('[data-ref=value1]');
2415
2486
  this.#value2 = fragment.querySelector('[data-ref=value2]');
2487
+ this.#fieldError = fragment.querySelector('ful-field-error');
2488
+ const labelEl = fragment.querySelector('label');
2489
+ labelEl?.addEventListener('click', () => this.focus());
2490
+ this.#value1.ariaDescribedByElements = [this.#fieldError];
2491
+ this.#value1.ariaLabelledByElements = labelEl ? [labelEl] : [];
2416
2492
  this.replaceChildren(fragment);
2417
- this.#fieldError = this.querySelector('ful-field-error');
2418
2493
  this.addEventListener('click', (evt) => {
2419
2494
  const target = /** @type HTMLElement */ (evt.target);
2420
2495
  if (!target.matches('ul > li > a')) {
@@ -2477,7 +2552,7 @@ class LocalDateFilter extends ParsedElement {
2477
2552
  static observed = ["value:json"];
2478
2553
  static slots = true;
2479
2554
  static template = `
2480
- <label data-tpl-for="id" class="form-label" data-tpl-if="label">{{{{ label }}}}</label>
2555
+ <label class="form-label" data-tpl-if="label">{{{{ label }}}}</label>
2481
2556
  <div class="input-group">
2482
2557
  <button data-ref="operator" class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false" value="EQ" form="">=</button>
2483
2558
  <ul class="dropdown-menu">
@@ -2489,10 +2564,9 @@ class LocalDateFilter extends ParsedElement {
2489
2564
  <li><a class="dropdown-item" role="button" value="GTE">&SucceedsSlantEqual;</a></li>
2490
2565
  <li><a class="dropdown-item" role="button" value="BETWEEN">&LeftRightArrow;</a></li>
2491
2566
  </ul>
2492
- <input data-tpl-id="id" data-ref="value1" type="date" class="form-control" form="">
2567
+ <input data-ref="value1" type="date" class="form-control" form="">
2493
2568
  <input data-ref="value2" type="date" class="form-control" form="" hidden>
2494
2569
  <span class="input-group-text"><i class="bi bi-search"></i></span>
2495
-
2496
2570
  </div>
2497
2571
  <ful-field-error></ful-field-error>
2498
2572
  `;
@@ -2506,15 +2580,18 @@ class LocalDateFilter extends ParsedElement {
2506
2580
  this.internals = this.attachInternals();
2507
2581
  }
2508
2582
  render({ slots }) {
2509
- const id = Attributes.uid('instant-filter');
2510
2583
  const label = Fragments.toHtml(slots.default.cloneNode(true)).trim().length === 0 ? null : slots.default;
2511
2584
  const name = this.getAttribute("name");
2512
- const fragment = this.template().withOverlay({ id, label, name }).render(this);
2585
+ const fragment = this.template().withOverlay({ label, name }).render(this);
2513
2586
  this.#operator = fragment.querySelector('[data-ref=operator]');
2514
2587
  this.#value1 = fragment.querySelector('[data-ref=value1]');
2515
2588
  this.#value2 = fragment.querySelector('[data-ref=value2]');
2589
+ this.#fieldError = fragment.querySelector('ful-field-error');
2590
+ const labelEl = fragment.querySelector('label');
2591
+ labelEl?.addEventListener('click', () => this.focus());
2592
+ this.#value1.ariaDescribedByElements = [this.#fieldError];
2593
+ this.#value1.ariaLabelledByElements = labelEl ? [labelEl] : [];
2516
2594
  this.replaceChildren(fragment);
2517
- this.#fieldError = this.querySelector('ful-field-error');
2518
2595
  this.addEventListener('click', (evt) => {
2519
2596
  const target = /** @type HTMLElement */(evt.target);
2520
2597
  if (!target.matches('ul > li > a')) {
@@ -2567,7 +2644,7 @@ class TextFilter extends ParsedElement {
2567
2644
  static observed = ["value:json"];
2568
2645
  static slots = true;
2569
2646
  static template = `
2570
- <label data-tpl-for="id" class="form-label" data-tpl-if="label">{{{{ label }}}}</label>
2647
+ <label class="form-label" data-tpl-if="label">{{{{ label }}}}</label>
2571
2648
  <div class="input-group">
2572
2649
  <button data-ref="operator" class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false" value="CONTAINS" form="">&mldr;a&mldr;</button>
2573
2650
  <ul class="dropdown-menu">
@@ -2576,7 +2653,7 @@ class TextFilter extends ParsedElement {
2576
2653
  <li><a class="dropdown-item" role="button" value="ENDS_WITH">&mldr;a</a></li>
2577
2654
  <li><a class="dropdown-item" role="button" value="EQ">=</a></li>
2578
2655
  </ul>
2579
- <input data-tpl-id="id" data-ref="value" type="text" class="form-control" form="">
2656
+ <input data-ref="value" type="text" class="form-control" form="">
2580
2657
  <span class="input-group-text"><i class="bi bi-search"></i></span>
2581
2658
  </div>
2582
2659
  <ful-field-error></ful-field-error>
@@ -2590,14 +2667,17 @@ class TextFilter extends ParsedElement {
2590
2667
  this.internals = this.attachInternals();
2591
2668
  }
2592
2669
  render({ slots }) {
2593
- const id = Attributes.uid('string-filter');
2594
2670
  const label = Fragments.toHtml(slots.default.cloneNode(true)).trim().length === 0 ? null : slots.default;
2595
2671
  const name = this.getAttribute("name");
2596
- const fragment = this.template().withOverlay({ id, label, name }).render(this);
2672
+ const fragment = this.template().withOverlay({ label, name }).render(this);
2597
2673
  this.#operator = fragment.querySelector('[data-ref=operator]');
2598
2674
  this.#value = fragment.querySelector('[data-ref=value]');
2675
+ this.#fieldError = fragment.querySelector('ful-field-error');
2676
+ const labelEl = fragment.querySelector('label');
2677
+ labelEl?.addEventListener('click', () => this.focus());
2678
+ this.#value.ariaDescribedByElements = [this.#fieldError];
2679
+ this.#value.ariaLabelledByElements = labelEl ? [labelEl] : [];
2599
2680
  this.replaceChildren(fragment);
2600
- this.#fieldError = this.querySelector('ful-field-error');
2601
2681
  this.addEventListener('click', (evt) => {
2602
2682
  const target = /** @type HTMLElement */(evt.target);
2603
2683
  if (!target.matches('ul > li > a')) {