@optionfactory/ful 6.0.3 → 6.0.5

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
@@ -1,4 +1,4 @@
1
- import { ParsedElement, Attributes, registry, Fragments, Nodes, Rendering } from '@optionfactory/ftl';
1
+ import { ParsedElement, Attributes, registry, Fragments, Templates, Nodes, Rendering } from '@optionfactory/ftl';
2
2
 
3
3
  class Base64 {
4
4
  static encode(arrayBuffer, dialect) {
@@ -1356,7 +1356,7 @@ class Form extends ParsedElement {
1356
1356
  form.addEventListener('submit', async (e) => {
1357
1357
  e.preventDefault();
1358
1358
  e.stopPropagation();
1359
- await this.submit(e.submitter);
1359
+ await this.submit(e.submitter ?? undefined);
1360
1360
  });
1361
1361
  if (this.hasAttribute("clear-invalid-on-change")) {
1362
1362
  this.addEventListener('change', (/** @type any */evt) => {
@@ -1377,19 +1377,20 @@ class Form extends ParsedElement {
1377
1377
  const values = Bindings.extractFrom(this.form, submitter);
1378
1378
  let request = await loader.prepare(values, this);
1379
1379
  try {
1380
- const se = new CustomEvent('submit', { bubbles: true, cancelable: true, detail: { values, request } });
1380
+ const se = new CustomEvent('submit', { bubbles: true, cancelable: true, detail: { submitter, values, request } });
1381
1381
  if (!this.dispatchEvent(se)) {
1382
1382
  return;
1383
1383
  }
1384
- const sre = new CustomEvent('submit:requested', { bubbles: true, cancelable: false, detail: { values: se.detail.values, request: se.detail.request} });
1384
+ this.errors = [];
1385
+ const sre = new CustomEvent('submit:requested', { bubbles: true, cancelable: false, detail: { submitter, values: se.detail.values, request: se.detail.request} });
1385
1386
  let response = await AsyncEvents.fireAsync(this, sre);
1386
1387
  request = sre.detail.request;
1387
1388
 
1388
1389
  response = await loader.submit(request, this, response);
1389
1390
  const mapped = await loader.transform(response, this);
1390
- this.dispatchEvent(new CustomEvent('submit:success', { bubbles: true, cancelable: false, detail: { values, request, response: mapped } }));
1391
+ this.dispatchEvent(new CustomEvent('submit:success', { bubbles: true, cancelable: false, detail: { submitter, values, request, response: mapped } }));
1391
1392
  } catch (e) {
1392
- this.dispatchEvent(new CustomEvent('submit:failure', { bubbles: true, cancelable: false, detail: { values, request, exception: e } }));
1393
+ this.dispatchEvent(new CustomEvent('submit:failure', { bubbles: true, cancelable: false, detail: { submitter, values, request, exception: e } }));
1393
1394
  if (e instanceof Failure) {
1394
1395
  this.errors = e.problems;
1395
1396
  }
@@ -1470,7 +1471,35 @@ class Input extends ParsedElement {
1470
1471
  this.required = observed.required;
1471
1472
  this.value = observed.value;
1472
1473
  }
1473
-
1474
+ this._input.addEventListener('keydown', (evt) => {
1475
+ if (evt.key !== 'Enter' || this._type() === 'textarea') {
1476
+ return;
1477
+ }
1478
+ const form = this.internals.form;
1479
+ if(!form){
1480
+ return;
1481
+ }
1482
+ const candidates = /** @type [HTMLButtonElement|HTMLInputElement] */ (Array.from(form.querySelectorAll(
1483
+ 'button:not(:disabled), input:not(:disabled)'
1484
+ )));
1485
+ const submitter = candidates.find(el => el.type === 'submit');
1486
+ form.requestSubmit(submitter);
1487
+ });
1488
+ this._input.addEventListener('input', (evt) => {
1489
+ const re = this.getAttribute('mask');
1490
+ if (!re) {
1491
+ return;
1492
+ }
1493
+ const before = evt.target.value;
1494
+ const after = before.replace(new RegExp(re, 'g'), '');
1495
+ if (before === after) {
1496
+ return;
1497
+ }
1498
+ const start = evt.target.selectionStart;
1499
+ const offset = before.length - after.length;
1500
+ evt.target.value = after;
1501
+ evt.target.setSelectionRange(start - offset, start - offset);
1502
+ });
1474
1503
  this._input.addEventListener('change', (evt) => {
1475
1504
  evt.stopPropagation();
1476
1505
  this.dispatchEvent(new CustomEvent('change', {
@@ -1518,7 +1547,7 @@ class Input extends ParsedElement {
1518
1547
  this.reflect(() => {
1519
1548
  Attributes.toggle(this, 'required', d);
1520
1549
  });
1521
- }
1550
+ }
1522
1551
  focus(options) {
1523
1552
  this._input.focus(options);
1524
1553
  }
@@ -2108,11 +2137,20 @@ class Dropdown extends ParsedElement {
2108
2137
  <ful-spinner class="centered" hidden></ful-spinner>
2109
2138
  <menu tabindex="-1" hidden></menu>
2110
2139
  `;
2140
+ static templates = {
2141
+ options: `
2142
+ <li data-tpl-each="self" data-tpl-selected="index == 0" data-tpl-value="index">
2143
+ {{ label }}
2144
+ </li>
2145
+ `
2146
+ };
2111
2147
  #spinner;
2112
2148
  #menu;
2149
+ #optionstemplate;
2113
2150
  #options = new Map();
2114
2151
  render({ slots }) {
2115
2152
  const fragment = this.template().render();
2153
+ this.#optionstemplate = Fragments.isBlank(slots.default) ? this.template('options') : Templates.fromFragment(slots.default);
2116
2154
  this.#spinner = fragment.querySelector("ful-spinner");
2117
2155
  this.#menu = fragment.querySelector("menu");
2118
2156
  this.#menu.addEventListener('click', evt => {
@@ -2134,21 +2172,8 @@ class Dropdown extends ParsedElement {
2134
2172
  throw new Error("null data");
2135
2173
  }
2136
2174
  this.#options = new Map(values.map((v, i) => [String(i), v]));
2137
- if (values.length === 0) {
2138
- const el = document.createElement('div');
2139
- el.classList.add('text-center', 'py-2', 'bi', 'bi-database-slash');
2140
- this.#menu.replaceChildren(el);
2141
- return;
2142
- }
2143
- this.#menu.replaceChildren(...values.map(([k, v, m], i) => {
2144
- const el = document.createElement('li');
2145
- if (i === 0) {
2146
- el.setAttribute("selected", '');
2147
- }
2148
- el.setAttribute("value", i);
2149
- el.innerText = v;
2150
- return el;
2151
- }));
2175
+ const data = values.map(([key, label, metadata], index) => ({ index, key, label, metadata}));
2176
+ this.#optionstemplate.withOverlay(data).renderTo(this.#menu);
2152
2177
  }
2153
2178
  #change(target) {
2154
2179
  const index = target.getAttribute('value');
@@ -2209,7 +2234,7 @@ class Select extends ParsedElement {
2209
2234
  <badges></badges>
2210
2235
  <input type="text" form="">
2211
2236
  </div>
2212
- <ful-dropdown hidden popover="manual"></ful-dropdown>
2237
+ <ful-dropdown hidden popover="manual">{{{{ slots.dropdown }}}}</ful-dropdown>
2213
2238
  </div>
2214
2239
  {{{{ slots.after }}}}
2215
2240
  <span data-tpl-if="slots.iafter" class="input-group-text">{{{{ slots.iafter }}}}</span>
@@ -2323,16 +2348,17 @@ class Select extends ParsedElement {
2323
2348
  this.#input.value = '';
2324
2349
  });
2325
2350
  this.#input.addEventListener('keydown', e => {
2326
- e.stopPropagation();
2327
2351
  if (this.disabled || this.readonly) {
2328
2352
  return;
2329
2353
  }
2330
2354
  switch (e.code) {
2331
2355
  case 'ArrowUp': {
2356
+ e.preventDefault();
2332
2357
  this.#ddmenu.moveOrShow(false, () => self.#loader.load(self.#input.value));
2333
2358
  break;
2334
2359
  }
2335
2360
  case 'ArrowDown': {
2361
+ e.preventDefault();
2336
2362
  this.#ddmenu.moveOrShow(true, () => self.#loader.load(self.#input.value));
2337
2363
  break;
2338
2364
  }
@@ -2341,12 +2367,13 @@ class Select extends ParsedElement {
2341
2367
  break;
2342
2368
  }
2343
2369
  case 'Enter': {
2370
+ e.preventDefault();
2344
2371
  this.#ddmenu.acceptSelection();
2345
2372
  this.#input.value = '';
2346
2373
  break;
2347
2374
  }
2348
2375
  case 'Backspace': {
2349
- //remove last if caret a position 0
2376
+ //remove last if caret at position 0
2350
2377
  if (this.#values.size && this.#input.selectionStart === 0 && this.#input.selectionEnd === 0) {
2351
2378
  this.#values.delete(Array.from(this.#values.keys()).pop());
2352
2379
  this.#changed();