@optionfactory/ful 1.0.12 → 1.0.13

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
@@ -1412,16 +1412,16 @@ class Input extends ParsedElement {
1412
1412
  static observed = ['value', 'readonly:presence'];
1413
1413
  static slots = true;
1414
1414
  static template = `
1415
- <label data-tpl-for="id" class="form-label">{{{{ slots.default }}}}</label>
1415
+ <label class="form-label">{{{{ slots.default }}}}</label>
1416
1416
  <div class="input-group">
1417
1417
  <span data-tpl-if="slots.ibefore" class="input-group-text">{{{{ slots.ibefore }}}}</span>
1418
1418
  {{{{ 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>
1419
+ <input data-tpl-if="type != 'textarea'" class="form-control" data-tpl-type="type" placeholder=" " form="">
1420
+ <textarea data-tpl-if="type == 'textarea'" class="form-control" placeholder=" " form=""></textarea>
1421
1421
  {{{{ slots.after }}}}
1422
1422
  <span data-tpl-if="slots.iafter" class="input-group-text">{{{{ slots.iafter }}}}</span>
1423
1423
  </div>
1424
- <ful-field-error data-tpl-id="fieldErrorId"></ful-field-error>
1424
+ <ful-field-error></ful-field-error>
1425
1425
  `;
1426
1426
  static formAssociated = true;
1427
1427
  #input;
@@ -1429,13 +1429,11 @@ class Input extends ParsedElement {
1429
1429
  constructor() {
1430
1430
  super();
1431
1431
  this.internals = this.attachInternals();
1432
- this.internals.role = 'textbox';
1432
+ this.internals.role = 'presentation';
1433
1433
  }
1434
1434
  render({ slots }) {
1435
- const id = Attributes.uid('ful-input');
1436
- const fieldErrorId = `${id}-error`;
1437
1435
  const type = this.getAttribute("type") ?? 'text';
1438
- const fragment = this.template().withOverlay({ id, type, fieldErrorId, slots }).render();
1436
+ const fragment = this.template().withOverlay({ type, slots }).render();
1439
1437
  this.#input = fragment.querySelector("input,textarea");
1440
1438
  Attributes.forward('input-', this, this.#input);
1441
1439
  this.#input.addEventListener('change', (evt) => {
@@ -1448,7 +1446,11 @@ class Input extends ParsedElement {
1448
1446
  }
1449
1447
  }));
1450
1448
  });
1449
+ const label = fragment.querySelector('label');
1450
+ label.addEventListener('click', () => this.focus());
1451
1451
  this.#fieldError = fragment.querySelector('ful-field-error');
1452
+ this.#input.ariaDescribedByElements = [this.#fieldError];
1453
+ this.#input.ariaLabelledByElements = [label];
1452
1454
  this.replaceChildren(fragment);
1453
1455
  }
1454
1456
  get value() {
@@ -1564,11 +1566,9 @@ class OptionsSlotSelectLoader {
1564
1566
  this.#data = data;
1565
1567
  }
1566
1568
  async exact(...keys) {
1567
- await timing.sleep(500);
1568
1569
  return this.#data.filter(([k, v]) => keys.includes(k));
1569
1570
  }
1570
1571
  async load(needle) {
1571
- await timing.sleep(500);
1572
1572
  return this.#data.filter(([k, v]) => v.includes(needle?.toLowerCase()));
1573
1573
  }
1574
1574
  }
@@ -1680,19 +1680,19 @@ class Select extends ParsedElement {
1680
1680
  static observed = ['value:csvm']
1681
1681
  static slots = true
1682
1682
  static template = `
1683
- <label data-tpl-for="id" class="form-label">{{{{ slots.default }}}}</label>
1683
+ <label class="form-label">{{{{ slots.default }}}}</label>
1684
1684
  <div class="input-group flex-nowrap" tabindex="-1">
1685
1685
  <span data-tpl-if="slots.ibefore" class="input-group-text">{{{{ slots.ibefore }}}}</span>
1686
1686
  {{{{ slots.before }}}}
1687
1687
  <div class="ful-select-input">
1688
1688
  <badges></badges>
1689
- <input data-tpl-id="id" data-tpl-ariadesribed-by="fieldErrorId" type="text" form="">
1689
+ <input type="text" form="">
1690
1690
  </div>
1691
1691
  {{{{ slots.after }}}}
1692
1692
  <span data-tpl-if="slots.iafter" class="input-group-text">{{{{ slots.iafter }}}}</span>
1693
1693
  </div>
1694
1694
  <ful-dropdown hidden></ful-dropdown>
1695
- <ful-field-error data-tpl-id="fieldErrorId"></ful-field-error>
1695
+ <ful-field-error></ful-field-error>
1696
1696
  `;
1697
1697
  static mappers = {
1698
1698
  "csvm": (v, name, el) => {
@@ -1714,20 +1714,23 @@ class Select extends ParsedElement {
1714
1714
  constructor() {
1715
1715
  super();
1716
1716
  this.internals = this.attachInternals();
1717
- this.internals.role = 'combobox';
1717
+ this.internals.role = 'presentation';
1718
1718
  }
1719
1719
  async render({ slots, observed }) {
1720
1720
  const name = this.getAttribute("name");
1721
- const id = Attributes.uid('ful-select');
1722
- const fieldErrorId = id + "-error";
1723
1721
  this.#loader = Loaders.fromAttributes(this, 'loaders:select', { options: slots.options });
1724
1722
  await this.#loader.prefetch?.();
1725
- const fragment = this.template().withOverlay({ slots, name, id, fieldErrorId }).render();
1723
+ const fragment = this.template().withOverlay({ slots, name }).render();
1726
1724
  this.#input = fragment.querySelector('input');
1727
1725
  this.#badges = fragment.querySelector('badges');
1728
1726
  this.#ddmenu = fragment.querySelector('ful-dropdown');
1729
1727
  this.#multiple = this.hasAttribute("multiple");
1728
+ const label = fragment.querySelector('label');
1729
+ label.addEventListener('click', () => this.focus());
1730
1730
  this.#fieldError = fragment.querySelector('ful-field-error');
1731
+ this.#input.ariaDescribedByElements = [this.#fieldError];
1732
+ this.#input.ariaLabelledByElements = [label];
1733
+
1731
1734
 
1732
1735
  const self = this;
1733
1736
  const [dload, abortdload] = timing.debounce(400, () => self.#ddmenu.show(() => self.#loader.load(self.#input.value)));
@@ -1851,7 +1854,7 @@ class RadioGroup extends ParsedElement {
1851
1854
  static observed = ['value'];
1852
1855
  static slots = true;
1853
1856
  static template = `
1854
- <fieldset data-tpl-aria-describedby="fieldErrorId">
1857
+ <fieldset>
1855
1858
  <legend class="form-label">
1856
1859
  {{{{ slots.default }}}}
1857
1860
  </legend>
@@ -1866,7 +1869,7 @@ class RadioGroup extends ParsedElement {
1866
1869
  </label>
1867
1870
  </div>
1868
1871
  </section>
1869
- <ful-field-error data-tpl-id="fieldErrorId"></ful-field-error>
1872
+ <ful-field-error></ful-field-error>
1870
1873
  <footer data-tpl-if="slots.footer">
1871
1874
  {{{{ slots.footer }}}}
1872
1875
  </footer>
@@ -1907,9 +1910,9 @@ class RadioGroup extends ParsedElement {
1907
1910
  });
1908
1911
 
1909
1912
  radioEls.forEach(el => el.remove());
1910
- const fieldErrorId = Attributes.uid("ful-error");
1911
- this.template().withOverlay({ name, fieldErrorId, slots, inputsAndLabels }).renderTo(this);
1913
+ this.template().withOverlay({ name, slots, inputsAndLabels }).renderTo(this);
1912
1914
  this.#fieldError = this.querySelector('ful-field-error');
1915
+ this.ariaDescribedByElements = [this.#fieldError];
1913
1916
  this.#firstRadio = this.querySelector('input[type=radio]');
1914
1917
  this.#booleanType = this.getAttribute('type') === 'boolean';
1915
1918
  }
@@ -1951,11 +1954,11 @@ class Checkbox extends ParsedElement {
1951
1954
  static template = `
1952
1955
  <div data-tpl-class="klass">
1953
1956
  <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">
1957
+ <input class="form-check-input" type="checkbox" role="switch" form="" placeholder=" ">
1955
1958
  </div>
1956
- <label data-tpl-for="id" class="form-check-label">{{{{ slots.default }}}}</label>
1959
+ <label class="form-check-label">{{{{ slots.default }}}}</label>
1957
1960
  </div>
1958
- <ful-field-error data-tpl-if="fieldErrorId"></ful-field-error>
1961
+ <ful-field-error></ful-field-error>
1959
1962
  `;
1960
1963
  #input;
1961
1964
  #fieldError;
@@ -1963,17 +1966,13 @@ class Checkbox extends ParsedElement {
1963
1966
  constructor() {
1964
1967
  super();
1965
1968
  this.internals = this.attachInternals();
1966
- this.internals.role = 'checkbox';
1969
+ this.internals.role = 'presentation';
1967
1970
  }
1968
1971
  render({ slots }) {
1969
- const id = Attributes.uid("ful-checkbox");
1970
- const fieldErrorId = id + "-error";
1971
1972
  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();
1973
+ const fragment = this.template().withOverlay({ slots, klass }).render();
1974
1974
  this.#input = fragment.querySelector("input");
1975
1975
  Attributes.forward('input-', this, this.#input);
1976
- this.#fieldError = fragment.querySelector('ful-field-error');
1977
1976
  this.#input.addEventListener('change', (evt) => {
1978
1977
  evt.stopPropagation();
1979
1978
  this.dispatchEvent(new CustomEvent('change', {
@@ -1983,7 +1982,12 @@ class Checkbox extends ParsedElement {
1983
1982
  value: this.value
1984
1983
  }
1985
1984
  }));
1986
- });
1985
+ });
1986
+ const label = fragment.querySelector('label');
1987
+ label.addEventListener('click', () => { this.focus(); this.value = !this.value; });
1988
+ this.#fieldError = fragment.querySelector('ful-field-error');
1989
+ this.#input.ariaDescribedByElements = [this.#fieldError];
1990
+ this.#input.ariaLabelledByElements = [label];
1987
1991
  this.replaceChildren(fragment);
1988
1992
  }
1989
1993
  get value() {
@@ -2215,48 +2219,50 @@ class Table extends ParsedElement {
2215
2219
  <ful-form data-tpl-if="slots.filters">
2216
2220
  {{{{ slots.filters }}}}
2217
2221
  </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>
2222
+ <div class="table-wrapper">
2223
+ <table class="table">
2224
+ <caption data-tpl-if="slots.caption">{{{{ slots.caption }}}}</caption>
2225
+ <thead>
2226
+ <tr>
2227
+ <th data-tpl-each="schema" scope="col" data-tpl-class="title.classes">
2228
+ {{{{ title.fragment }}}}
2229
+ <ful-sorter data-tpl-if="sorter || order" data-tpl-sorter="sorter" data-tpl-order="order"></ful-sorter>
2230
+ </th>
2231
+ </tr>
2232
+ </thead>
2233
+ <tbody></tbody>
2234
+ <tbody data-ref="no-autoload">
2235
+ <tr>
2236
+ <td data-tpl-colspan="schema.length" class="text-center align-middle p-4">
2237
+ <i class="bi bi-search" style="font-size: 40px; color: #BDC3CA"></i>
2238
+ <p class="mt-3 mb-0" style="color: #BDC3CA">
2239
+ Avvia la ricerca per visualizzare i risultati...
2240
+ </p>
2241
+ </td>
2242
+ </tr>
2243
+ </tbody>
2244
+ <tbody data-ref="loading" hidden>
2245
+ <tr>
2246
+ <td data-tpl-colspan="schema.length" class="text-center align-middle p-4">
2247
+ <ful-spinner class="big"></ful-spinner>
2248
+ </td>
2249
+ </tr>
2250
+ </tbody>
2251
+ <tbody data-ref="feedback" hidden>
2252
+ <tr>
2253
+ <td data-tpl-colspan="schema.length" class="text-center align-middle p-4">
2254
+ <div class="alert alert-danger">
2255
+ <p>Errore nel caricamento della tabella:</p>
2256
+ <p class="mb-0" data-ref="feedback-error"></p>
2257
+ </div>
2258
+ </td>
2259
+ </tr>
2260
+ </tbody>
2261
+ <tfoot data-tpl-if="slots.footer">
2262
+ {{{{ slots.footer }}}}
2263
+ </tfoot>
2264
+ </table>
2265
+ </div>
2260
2266
  <ful-pagination current="0" total="1"></ful-pagination>
2261
2267
  `;
2262
2268
  static templates = {
@@ -2285,7 +2291,8 @@ class Table extends ParsedElement {
2285
2291
  const template = this.template();
2286
2292
  const schema = TableSchemaParser.parse(slots.default, template);
2287
2293
  const fragment = template.withOverlay({ slots, schema }).render();
2288
- const table = /** @type HTMLTableElement */ (Nodes.queryChildren(fragment, 'table'));
2294
+ const tableWrapper = /** @type HTMLTableElement */ (Nodes.queryChildren(fragment, '.table-wrapper'));
2295
+ const table = /** @type HTMLTableElement */ (tableWrapper.querySelector("table"));
2289
2296
  Attributes.forward('table-', this, table);
2290
2297
  this.#schema = schema;
2291
2298
  this.#body = table.querySelector(':scope > tbody');
@@ -2378,7 +2385,7 @@ class InstantFilter extends ParsedElement {
2378
2385
  static observed = ["value:json"];
2379
2386
  static slots = true;
2380
2387
  static template = `
2381
- <label data-tpl-for="id" class="form-label" data-tpl-if="label">{{{{ label }}}}</label>
2388
+ <label class="form-label" data-tpl-if="label">{{{{ label }}}}</label>
2382
2389
  <div class="input-group">
2383
2390
  <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
2391
  <ul class="dropdown-menu">
@@ -2390,7 +2397,7 @@ class InstantFilter extends ParsedElement {
2390
2397
  <li><a class="dropdown-item" role="button" value="GTE">&SucceedsSlantEqual;</a></li>
2391
2398
  <li><a class="dropdown-item" role="button" value="BETWEEN">&LeftRightArrow;</a></li>
2392
2399
  </ul>
2393
- <input data-tpl-id="id" data-ref="value1" type="datetime-local" class="form-control" form="">
2400
+ <input data-ref="value1" type="datetime-local" class="form-control" form="">
2394
2401
  <input data-ref="value2" type="datetime-local" class="form-control" form="" hidden>
2395
2402
  <span class="input-group-text"><i class="bi bi-search"></i></span>
2396
2403
  </div>
@@ -2406,15 +2413,18 @@ class InstantFilter extends ParsedElement {
2406
2413
  this.internals = this.attachInternals();
2407
2414
  }
2408
2415
  render({ slots }) {
2409
- const id = Attributes.uid('instant-filter');
2410
2416
  const label = Fragments.toHtml(slots.default.cloneNode(true)).trim().length === 0 ? null : slots.default;
2411
2417
  const name = this.getAttribute("name");
2412
- const fragment = this.template().withOverlay({ id, label, name }).render(this);
2418
+ const fragment = this.template().withOverlay({ label, name }).render(this);
2413
2419
  this.#operator = fragment.querySelector('[data-ref=operator]');
2414
2420
  this.#value1 = fragment.querySelector('[data-ref=value1]');
2415
2421
  this.#value2 = fragment.querySelector('[data-ref=value2]');
2422
+ this.#fieldError = fragment.querySelector('ful-field-error');
2423
+ const labelEl = fragment.querySelector('label');
2424
+ labelEl?.addEventListener('click', () => this.focus());
2425
+ this.#value1.ariaDescribedByElements = [this.#fieldError];
2426
+ this.#value1.ariaLabelledByElements = labelEl ? [labelEl] : [];
2416
2427
  this.replaceChildren(fragment);
2417
- this.#fieldError = this.querySelector('ful-field-error');
2418
2428
  this.addEventListener('click', (evt) => {
2419
2429
  const target = /** @type HTMLElement */ (evt.target);
2420
2430
  if (!target.matches('ul > li > a')) {
@@ -2477,7 +2487,7 @@ class LocalDateFilter extends ParsedElement {
2477
2487
  static observed = ["value:json"];
2478
2488
  static slots = true;
2479
2489
  static template = `
2480
- <label data-tpl-for="id" class="form-label" data-tpl-if="label">{{{{ label }}}}</label>
2490
+ <label class="form-label" data-tpl-if="label">{{{{ label }}}}</label>
2481
2491
  <div class="input-group">
2482
2492
  <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
2493
  <ul class="dropdown-menu">
@@ -2489,10 +2499,9 @@ class LocalDateFilter extends ParsedElement {
2489
2499
  <li><a class="dropdown-item" role="button" value="GTE">&SucceedsSlantEqual;</a></li>
2490
2500
  <li><a class="dropdown-item" role="button" value="BETWEEN">&LeftRightArrow;</a></li>
2491
2501
  </ul>
2492
- <input data-tpl-id="id" data-ref="value1" type="date" class="form-control" form="">
2502
+ <input data-ref="value1" type="date" class="form-control" form="">
2493
2503
  <input data-ref="value2" type="date" class="form-control" form="" hidden>
2494
2504
  <span class="input-group-text"><i class="bi bi-search"></i></span>
2495
-
2496
2505
  </div>
2497
2506
  <ful-field-error></ful-field-error>
2498
2507
  `;
@@ -2506,15 +2515,18 @@ class LocalDateFilter extends ParsedElement {
2506
2515
  this.internals = this.attachInternals();
2507
2516
  }
2508
2517
  render({ slots }) {
2509
- const id = Attributes.uid('instant-filter');
2510
2518
  const label = Fragments.toHtml(slots.default.cloneNode(true)).trim().length === 0 ? null : slots.default;
2511
2519
  const name = this.getAttribute("name");
2512
- const fragment = this.template().withOverlay({ id, label, name }).render(this);
2520
+ const fragment = this.template().withOverlay({ label, name }).render(this);
2513
2521
  this.#operator = fragment.querySelector('[data-ref=operator]');
2514
2522
  this.#value1 = fragment.querySelector('[data-ref=value1]');
2515
2523
  this.#value2 = fragment.querySelector('[data-ref=value2]');
2524
+ this.#fieldError = fragment.querySelector('ful-field-error');
2525
+ const labelEl = fragment.querySelector('label');
2526
+ labelEl?.addEventListener('click', () => this.focus());
2527
+ this.#value1.ariaDescribedByElements = [this.#fieldError];
2528
+ this.#value1.ariaLabelledByElements = labelEl ? [labelEl] : [];
2516
2529
  this.replaceChildren(fragment);
2517
- this.#fieldError = this.querySelector('ful-field-error');
2518
2530
  this.addEventListener('click', (evt) => {
2519
2531
  const target = /** @type HTMLElement */(evt.target);
2520
2532
  if (!target.matches('ul > li > a')) {
@@ -2567,7 +2579,7 @@ class TextFilter extends ParsedElement {
2567
2579
  static observed = ["value:json"];
2568
2580
  static slots = true;
2569
2581
  static template = `
2570
- <label data-tpl-for="id" class="form-label" data-tpl-if="label">{{{{ label }}}}</label>
2582
+ <label class="form-label" data-tpl-if="label">{{{{ label }}}}</label>
2571
2583
  <div class="input-group">
2572
2584
  <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
2585
  <ul class="dropdown-menu">
@@ -2576,7 +2588,7 @@ class TextFilter extends ParsedElement {
2576
2588
  <li><a class="dropdown-item" role="button" value="ENDS_WITH">&mldr;a</a></li>
2577
2589
  <li><a class="dropdown-item" role="button" value="EQ">=</a></li>
2578
2590
  </ul>
2579
- <input data-tpl-id="id" data-ref="value" type="text" class="form-control" form="">
2591
+ <input data-ref="value" type="text" class="form-control" form="">
2580
2592
  <span class="input-group-text"><i class="bi bi-search"></i></span>
2581
2593
  </div>
2582
2594
  <ful-field-error></ful-field-error>
@@ -2590,14 +2602,17 @@ class TextFilter extends ParsedElement {
2590
2602
  this.internals = this.attachInternals();
2591
2603
  }
2592
2604
  render({ slots }) {
2593
- const id = Attributes.uid('string-filter');
2594
2605
  const label = Fragments.toHtml(slots.default.cloneNode(true)).trim().length === 0 ? null : slots.default;
2595
2606
  const name = this.getAttribute("name");
2596
- const fragment = this.template().withOverlay({ id, label, name }).render(this);
2607
+ const fragment = this.template().withOverlay({ label, name }).render(this);
2597
2608
  this.#operator = fragment.querySelector('[data-ref=operator]');
2598
2609
  this.#value = fragment.querySelector('[data-ref=value]');
2610
+ this.#fieldError = fragment.querySelector('ful-field-error');
2611
+ const labelEl = fragment.querySelector('label');
2612
+ labelEl?.addEventListener('click', () => this.focus());
2613
+ this.#value.ariaDescribedByElements = [this.#fieldError];
2614
+ this.#value.ariaLabelledByElements = labelEl ? [labelEl] : [];
2599
2615
  this.replaceChildren(fragment);
2600
- this.#fieldError = this.querySelector('ful-field-error');
2601
2616
  this.addEventListener('click', (evt) => {
2602
2617
  const target = /** @type HTMLElement */(evt.target);
2603
2618
  if (!target.matches('ul > li > a')) {