@optionfactory/fml 9.0.0-rc11 → 9.0.0-rc13

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/fml.iife.js CHANGED
@@ -7641,15 +7641,22 @@ var fml = (function (exports) {
7641
7641
 
7642
7642
  const signed = (v, digits) => (v.startsWith('-') ? '-' : '') + digits(v);
7643
7643
 
7644
- const NUMERIC_FILTERS = {
7645
- numeric: (v) => signed(v, (t) => t.replace(/\D/g, '')),
7644
+ const NUMERIC_DIGITS = {
7645
+ numeric: (t) => t.replace(/\D/g, ''),
7646
7646
  //one separator, the first: the rest are a slip while typing, not a value
7647
- decimal: (v) =>
7648
- signed(v, (t) => {
7649
- const kept = t.replace(/[^\d.,]/g, '');
7650
- const at = kept.search(/[.,]/);
7651
- return at === -1 ? kept : kept.slice(0, at + 1) + kept.slice(at + 1).replace(/[.,]/g, '');
7652
- }),
7647
+ decimal: (t) => {
7648
+ const kept = t.replace(/[^\d.,]/g, '');
7649
+ const at = kept.search(/[.,]/);
7650
+ return at === -1 ? kept : kept.slice(0, at + 1) + kept.slice(at + 1).replace(/[.,]/g, '');
7651
+ },
7652
+ };
7653
+
7654
+ const numericFilter = (type, unsigned) => {
7655
+ const digits = NUMERIC_DIGITS[type];
7656
+ if (!digits) {
7657
+ return null;
7658
+ }
7659
+ return unsigned ? digits : (v) => signed(v, digits);
7653
7660
  };
7654
7661
 
7655
7662
  const warnedBoth = new WeakSet();
@@ -7671,7 +7678,7 @@ var fml = (function (exports) {
7671
7678
  const re = compiled('reject', reject);
7672
7679
  return re && ((v) => v.replace(re, ''));
7673
7680
  }
7674
- return NUMERIC_FILTERS[el._type()] ?? null;
7681
+ return numericFilter(el._type(), el.declared('unsigned'));
7675
7682
  };
7676
7683
 
7677
7684
  /** A labelled text input over any native type or textarea; the temporal inputs are its subclasses. */
@@ -7686,6 +7693,7 @@ var fml = (function (exports) {
7686
7693
  'reject',
7687
7694
  'uppercase:presence',
7688
7695
  'trim:presence',
7696
+ 'unsigned:presence',
7689
7697
  'autocomplete',
7690
7698
  ];
7691
7699
  static slots = true;
@@ -8523,7 +8531,18 @@ var fml = (function (exports) {
8523
8531
  if (invoke) {
8524
8532
  //popovertarget needs a target that can be named
8525
8533
  popover.id = popover.id || uid;
8526
- invoker.setAttribute('popovertarget', popover.id);
8534
+ if (invoker.localName === 'button' || invoker.localName === 'input') {
8535
+ invoker.setAttribute('popovertarget', popover.id);
8536
+ } else {
8537
+ invoker.addEventListener('click', () => popover.togglePopover());
8538
+ invoker.addEventListener('keydown', (/** @type any */ evt) => {
8539
+ if (evt.key !== 'Enter' && evt.key !== ' ') {
8540
+ return;
8541
+ }
8542
+ evt.preventDefault();
8543
+ popover.togglePopover();
8544
+ });
8545
+ }
8527
8546
  }
8528
8547
  const anchor = `--${uid}`;
8529
8548
  invoker.style.anchorName = anchor;
@@ -9135,7 +9154,7 @@ var fml = (function (exports) {
9135
9154
  //the select's: a tooltip marker slotted into `info`, a button a page
9136
9155
  //put in an affix. Without this, reading the note beside a select
9137
9156
  //also stole the focus and dropped the dropdown over the note
9138
- const elsewhere = e.target.closest('button, a[href], input, select, textarea');
9157
+ const elsewhere = e.target.closest('button, [role="button"], a[href], input, select, textarea');
9139
9158
  if (elsewhere && elsewhere !== this.#input) {
9140
9159
  return;
9141
9160
  }
@@ -11065,27 +11084,38 @@ var fml = (function (exports) {
11065
11084
  * operator, differing only in what the server converts them to.
11066
11085
  */
11067
11086
  class InFilter extends Select {
11068
- async upgrade() {
11069
- //the snapshot is taken inside super.upgrade(), so an attribute this
11070
- //element does not let an author turn off has to be written before it
11071
- this.toggleAttribute('multiple', true);
11072
- await super.upgrade();
11087
+ static attributes = ['operator'];
11088
+ #keys() {
11089
+ const chosen = super.value;
11090
+ if (this.multiple) {
11091
+ return chosen;
11092
+ }
11093
+ return chosen === null || chosen === undefined ? [] : [chosen];
11073
11094
  }
11074
11095
  get value() {
11075
- const keys = super.value;
11096
+ const keys = this.#keys();
11076
11097
  //an empty set is no filter: the table drops a falsy value, where an
11077
11098
  //empty array would travel and match nothing
11078
- return keys.length === 0 ? null : keys;
11099
+ if (keys.length === 0) {
11100
+ return null;
11101
+ }
11102
+ const operator = this.declared('operator');
11103
+ return operator ? [operator, ...keys] : keys;
11079
11104
  }
11080
11105
  set value(v) {
11081
- super.value = v ?? [];
11106
+ const operator = this.declared('operator');
11107
+ let keys = v === null || v === undefined ? [] : Array.isArray(v) ? v : [v];
11108
+ if (operator && keys[0] === operator) {
11109
+ keys = keys.slice(1);
11110
+ }
11111
+ super.value = this.multiple ? keys : (keys[0] ?? null);
11082
11112
  }
11083
11113
  /** @returns {FilterCriterion|null} */
11084
11114
  get criterion() {
11085
11115
  return asCriterion(
11086
11116
  labelTextOf(this),
11087
- null,
11088
- (super.entry ?? []).map((e) => e.label),
11117
+ this.declared('operator'),
11118
+ [super.entry].flat().filter((e) => e).map((e) => e.label),
11089
11119
  );
11090
11120
  }
11091
11121
  }
@@ -11221,7 +11251,7 @@ var fml = (function (exports) {
11221
11251
  icon: 'info-circle-fill',
11222
11252
  };
11223
11253
  static template = `
11224
- <button type="button" class="ful-tip" data-ref="trigger" data-tpl-aria-label="#l10n:t('info.tooltip')"><ful-icon data-tpl-name="icon ?? config.icon" aria-hidden="true"></ful-icon></button>
11254
+ <span role="button" tabindex="0" class="ful-tip" data-ref="trigger" data-tpl-aria-label="#l10n:t('info.tooltip')"><ful-icon data-tpl-name="icon ?? config.icon" aria-hidden="true"></ful-icon></span>
11225
11255
  <ful-note popover data-ref="content">{{{{ slots.default }}}}</ful-note>
11226
11256
  `;
11227
11257
  render({ slots }) {
@@ -11330,10 +11360,18 @@ var fml = (function (exports) {
11330
11360
  this.dispatchEvent(new CustomEvent('close', { detail: outcome }));
11331
11361
  this.#settle(outcome);
11332
11362
  });
11363
+ let pressedOutside = false;
11364
+ this.#dialog.addEventListener('mousedown', (/** @type any */ e) => {
11365
+ pressedOutside = e.target === this.#dialog;
11366
+ });
11333
11367
  this.#dialog.addEventListener('click', (/** @type any */ e) => {
11334
11368
  const result = e.target.closest('button[data-result]')?.dataset.result;
11335
11369
  if (result !== undefined) {
11336
11370
  this.#dialog.close(result);
11371
+ return;
11372
+ }
11373
+ if (!requiresAnswer && pressedOutside && e.target === this.#dialog) {
11374
+ this.#dialog.close('');
11337
11375
  }
11338
11376
  });
11339
11377
  //dismissal, not an answer: the waiters are settled with a dismissal, as
@@ -11535,6 +11573,15 @@ var fml = (function (exports) {
11535
11573
  this.#dialog.setAttribute('placement', placement);
11536
11574
  }
11537
11575
  fragment.querySelector('[data-ref=close]').addEventListener('click', () => this.close());
11576
+ let pressedOutside = false;
11577
+ this.#dialog.addEventListener('mousedown', (e) => {
11578
+ pressedOutside = e.target === this.#dialog;
11579
+ });
11580
+ this.#dialog.addEventListener('click', (e) => {
11581
+ if (pressedOutside && e.target === this.#dialog) {
11582
+ this.close();
11583
+ }
11584
+ });
11538
11585
  this.#dialog.addEventListener('close', () => {
11539
11586
  this.dispatchEvent(
11540
11587
  new CustomEvent('close', { detail: this.#answer ?? { dismissed: true, response: null } }),