@optionfactory/fml 8.0.2 → 8.0.3

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
@@ -4697,27 +4697,30 @@ var fml = (function (exports) {
4697
4697
  }
4698
4698
  this[attr] = this.unmarshal(attr, newValue);
4699
4699
  }
4700
- #disabledBeforeParsed = null;
4701
- formDisabledCallback(disabled) {
4702
- if (!this.#parsed) {
4703
- this.#disabledBeforeParsed = disabled;
4704
- return;
4705
- }
4706
- Reflect.set(this, 'disabled', disabled);
4707
- if (disabled) {
4708
- this.#unclaimFormDisabled();
4709
- }
4710
- }
4711
- //a disabled ancestor fieldset already matches the element through :disabled, and
4712
- //an attribute of its own would keep the element disabled once the fieldset is
4713
- //re-enabled, as formDisabledCallback(false) is only delivered on an actual state
4714
- //change. a claim of its own made while already disabled by ancestry is safe: the
4715
- //platform fires no callback for it, so it survives the fieldset being re-enabled
4716
- #unclaimFormDisabled() {
4717
- if (this.closest('fieldset:disabled')) {
4718
- this.removeAttribute('disabled');
4719
- }
4720
- }
4700
+ /**
4701
+ * The disabled protocol follows the semantics of a native form control:
4702
+ *
4703
+ * - the `disabled` attribute on the host is the field's own claim, and nothing
4704
+ * but its author ever writes or removes it, in markup or through the property.
4705
+ * The framework never claims on the form's behalf, so there is nothing to
4706
+ * unclaim and nothing to lose: a field declared disabled inside a disabled
4707
+ * `<fieldset>` stays disabled when the fieldset comes back, exactly like a
4708
+ * native input keeps its attribute.
4709
+ * - the effective state is the claim OR a disabled fieldset ancestry, which the
4710
+ * platform maintains on its own: `:disabled` matches both, a disabled field is
4711
+ * left out of the submitted values, and the inner native controls are reached
4712
+ * by the ancestry as descendants of the fieldset.
4713
+ * - the `disabled` property reflects the claim only, like a native input's: a
4714
+ * field disabled by its ancestry reads `false` while `matches(':disabled')`
4715
+ * tells the effective state. Un-claiming inside a disabled fieldset cannot
4716
+ * enable the field.
4717
+ * - the inner controls mirror the claim and nothing else: the ancestry state is
4718
+ * never written anywhere, so it can never go stale, and the browser composes
4719
+ * the two on its own when it disables and re-enables a fieldset's descendants.
4720
+ *
4721
+ * Because of this, formDisabledCallback carries nothing the framework needs to
4722
+ * apply, and the protocol does not define it.
4723
+ */
4721
4724
  async upgrade() {
4722
4725
  if (this.#parsed) {
4723
4726
  return;
@@ -4730,11 +4733,9 @@ var fml = (function (exports) {
4730
4733
  this.unmarshal(attribute, this.getAttribute(attribute)),
4731
4734
  ]),
4732
4735
  );
4733
- const disabled = this.#disabledBeforeParsed ?? false;
4734
- await this.render({ slots, observed, disabled });
4735
- if (disabled) {
4736
- this.#unclaimFormDisabled();
4737
- }
4736
+ //the declared claim is what render receives: the ancestry state is not
4737
+ //passed around, it is already where it needs to be
4738
+ await this.render({ slots, observed, disabled: this.hasAttribute('disabled') });
4738
4739
  }
4739
4740
  render(c) {}
4740
4741
  reflect(fn) {
@@ -6331,15 +6332,17 @@ var fml = (function (exports) {
6331
6332
  });
6332
6333
  }
6333
6334
  get disabled() {
6334
- return this._input.hasAttribute('disabled');
6335
+ //the claim only, like a native input: the effective state, claim or disabled
6336
+ //ancestry, is what :disabled matches
6337
+ return this.hasAttribute('disabled');
6335
6338
  }
6336
6339
  set disabled(d) {
6337
- Attributes.toggle(this._input, 'disabled', d);
6338
- //also on the host: a form associated element only matches :disabled through its
6339
- //own attribute, and that is what keeps it out of the submitted values. no reflect
6340
- //is needed, disabled is deliberately not observed: the platform delivers it
6341
- //through formDisabledCallback, which also covers a disabled ancestor fieldset
6340
+ //the claim belongs to the author alone, nothing else ever writes it
6342
6341
  Attributes.toggle(this, 'disabled', d);
6342
+ //the inner control carries the claim as a native input would: a disabled
6343
+ //fieldset ancestry is left to the browser, which reaches the inner control
6344
+ //as a descendant of the fieldset and re-enables it on its own
6345
+ Attributes.toggle(this._input, 'disabled', d);
6343
6346
  }
6344
6347
  get required() {
6345
6348
  return this._input.getAttribute('aria-required') === 'true';
@@ -7259,7 +7262,9 @@ var fml = (function (exports) {
7259
7262
  if (e.target.matches('input')) {
7260
7263
  return;
7261
7264
  }
7262
- if (this.disabled || this.readonly) {
7265
+ //badges and other chrome are not form controls, the guard must ask the
7266
+ //effective state
7267
+ if (this.matches(':disabled') || this.readonly) {
7263
7268
  return;
7264
7269
  }
7265
7270
  if (this.#ddmenu.shown) {
@@ -7275,7 +7280,7 @@ var fml = (function (exports) {
7275
7280
  if (!e.target.closest('button')) {
7276
7281
  return;
7277
7282
  }
7278
- if (this.disabled || this.readonly) {
7283
+ if (this.matches(':disabled') || this.readonly) {
7279
7284
  return;
7280
7285
  }
7281
7286
  const idx = [...this.#items.children].indexOf(e.target.closest('ful-item'));
@@ -7288,7 +7293,7 @@ var fml = (function (exports) {
7288
7293
  });
7289
7294
  this.#badges.addEventListener('click', (e) => {
7290
7295
  e.stopPropagation();
7291
- if (this.disabled || this.readonly) {
7296
+ if (this.matches(':disabled') || this.readonly) {
7292
7297
  return;
7293
7298
  }
7294
7299
  const idx = [...this.#badges.children].indexOf(e.target);
@@ -7313,7 +7318,7 @@ var fml = (function (exports) {
7313
7318
  this.#input.value = '';
7314
7319
  });
7315
7320
  this.#input.addEventListener('keydown', (e) => {
7316
- if (this.disabled || this.readonly) {
7321
+ if (this.matches(':disabled') || this.readonly) {
7317
7322
  return;
7318
7323
  }
7319
7324
  switch (e.code) {
@@ -7366,7 +7371,7 @@ var fml = (function (exports) {
7366
7371
  });
7367
7372
  this.#input.addEventListener('input', (e) => {
7368
7373
  e.stopPropagation();
7369
- if (this.disabled || this.readonly) {
7374
+ if (this.matches(':disabled') || this.readonly) {
7370
7375
  return;
7371
7376
  }
7372
7377
  dload();
@@ -7376,7 +7381,7 @@ var fml = (function (exports) {
7376
7381
  if (!this.#multiple) {
7377
7382
  this.#values.clear();
7378
7383
  }
7379
- this.#values.set(e.detail.data[0], e.detail.data.slice(1));
7384
+ this.#values.set(this.#coerceKey(e.detail.data[0]), e.detail.data.slice(1));
7380
7385
  this.#changed();
7381
7386
  this.#syncBadges();
7382
7387
  this.#input.focus();
@@ -7427,10 +7432,36 @@ var fml = (function (exports) {
7427
7432
  this.#items.replaceChildren();
7428
7433
  this.template('items').withOverlay({ entries: this.#values.entries() }).renderTo(this.#items);
7429
7434
  }
7435
+ /**
7436
+ * Coerces a key to the type declared by `k-type`. Keys reach the element from
7437
+ * both worlds: the `value` attribute is text, a loader returns whatever its
7438
+ * endpoint carries. One canonical type keeps the internal Map, which compares
7439
+ * keys strictly, consistent. A key that does not decode is left as it is.
7440
+ */
7441
+ #coerceKey(k) {
7442
+ switch (this.getAttribute('k-type')) {
7443
+ case 'number': {
7444
+ const n = k === '' ? Number.NaN : Number(k);
7445
+ return Number.isNaN(n) ? k : n;
7446
+ }
7447
+ case 'boolean': {
7448
+ if (k === true || k === 'true') {
7449
+ return true;
7450
+ }
7451
+ if (k === false || k === 'false') {
7452
+ return false;
7453
+ }
7454
+ return k;
7455
+ }
7456
+ default:
7457
+ return String(k);
7458
+ }
7459
+ }
7460
+
7430
7461
  set value(vs) {
7431
7462
  //the csvm mapper yields [] for a missing multiple value, an empty string is
7432
7463
  //left alone: it is a usable key for an <option value="">
7433
- const keys = vs == null ? [] : Array.isArray(vs) ? vs : [vs];
7464
+ const keys = (vs == null ? [] : Array.isArray(vs) ? vs : [vs]).map((k) => this.#coerceKey(k));
7434
7465
  //the keys are known synchronously and are all `value` reads, so they are applied
7435
7466
  //now: only the labels need the loader, until then a key stands in for its own
7436
7467
  this.#values = new Map(keys.map((k) => [k, [k]]));
@@ -7453,7 +7484,8 @@ var fml = (function (exports) {
7453
7484
  }
7454
7485
  //label the keys that are still selected: a removal made while the lookup was in
7455
7486
  //flight must not be undone by it, and a key the loader does not know is dropped
7456
- const resolved = new Map(entries.map((e) => [e[0], e.slice(1)]));
7487
+ //the loader keys are coerced too, so they line up with the assigned ones
7488
+ const resolved = new Map(entries.map((e) => [this.#coerceKey(e[0]), e.slice(1)]));
7457
7489
  for (const key of keys) {
7458
7490
  if (!this.#values.has(key)) {
7459
7491
  continue;
@@ -7479,15 +7511,17 @@ var fml = (function (exports) {
7479
7511
  return [...this.#values.entries()][0] ?? null;
7480
7512
  }
7481
7513
  get disabled() {
7482
- return this.#input.hasAttribute('disabled');
7514
+ //the claim only, like a native input: the effective state, claim or disabled
7515
+ //ancestry, is what :disabled matches
7516
+ return this.hasAttribute('disabled');
7483
7517
  }
7484
7518
  set disabled(d) {
7485
- Attributes.toggle(this.#input, 'disabled', d);
7486
- //also on the host: a form associated element only matches :disabled through its
7487
- //own attribute, and that is what keeps it out of the submitted values. no reflect
7488
- //is needed, disabled is deliberately not observed: the platform delivers it
7489
- //through formDisabledCallback, which also covers a disabled ancestor fieldset
7519
+ //the claim belongs to the author alone, nothing else ever writes it
7490
7520
  Attributes.toggle(this, 'disabled', d);
7521
+ //the inner control carries the claim as a native input would: a disabled
7522
+ //fieldset ancestry is left to the browser, which reaches the inner control
7523
+ //as a descendant of the fieldset and re-enables it on its own
7524
+ Attributes.toggle(this.#input, 'disabled', d);
7491
7525
  }
7492
7526
  get readonly() {
7493
7527
  return this.#input.readOnly;
@@ -7635,15 +7669,17 @@ var fml = (function (exports) {
7635
7669
  });
7636
7670
  }
7637
7671
  get disabled() {
7638
- return this.#fieldset.hasAttribute('disabled');
7672
+ //the claim only, like a native input: the effective state, claim or disabled
7673
+ //ancestry, is what :disabled matches
7674
+ return this.hasAttribute('disabled');
7639
7675
  }
7640
7676
  set disabled(d) {
7641
- Attributes.toggle(this.#fieldset, 'disabled', d);
7642
- //also on the host: a form associated element only matches :disabled through its
7643
- //own attribute, and that is what keeps it out of the submitted values. no reflect
7644
- //is needed, disabled is deliberately not observed: the platform delivers it
7645
- //through formDisabledCallback, which also covers a disabled ancestor fieldset
7677
+ //the claim belongs to the author alone, nothing else ever writes it
7646
7678
  Attributes.toggle(this, 'disabled', d);
7679
+ //the group disables through its own fieldset, which carries the claim like
7680
+ //a native input would: a disabled outer ancestry is left to the browser,
7681
+ //which reaches the radios as descendants and re-enables them on its own
7682
+ this.#fieldset.disabled = d;
7647
7683
  }
7648
7684
  get required() {
7649
7685
  return this.#fieldset.getAttribute('aria-required') === 'true';
@@ -7718,7 +7754,8 @@ var fml = (function (exports) {
7718
7754
  const label = fragment.querySelector('label');
7719
7755
  label.addEventListener('click', () => {
7720
7756
  this.focus();
7721
- if (this.disabled || this.readonly) {
7757
+ //a label is not a form control, the guard must ask the effective state
7758
+ if (this.matches(':disabled') || this.readonly) {
7722
7759
  return;
7723
7760
  }
7724
7761
  this.value = !this.value;
@@ -7753,15 +7790,17 @@ var fml = (function (exports) {
7753
7790
  });
7754
7791
  }
7755
7792
  get disabled() {
7756
- return this.#input.hasAttribute('disabled');
7793
+ //the claim only, like a native input: the effective state, claim or disabled
7794
+ //ancestry, is what :disabled matches
7795
+ return this.hasAttribute('disabled');
7757
7796
  }
7758
7797
  set disabled(d) {
7759
- Attributes.toggle(this.#input, 'disabled', d);
7760
- //also on the host: a form associated element only matches :disabled through its
7761
- //own attribute, and that is what keeps it out of the submitted values. no reflect
7762
- //is needed, disabled is deliberately not observed: the platform delivers it
7763
- //through formDisabledCallback, which also covers a disabled ancestor fieldset
7798
+ //the claim belongs to the author alone, nothing else ever writes it
7764
7799
  Attributes.toggle(this, 'disabled', d);
7800
+ //the inner control carries the claim as a native input would: a disabled
7801
+ //fieldset ancestry is left to the browser, which reaches the inner control
7802
+ //as a descendant of the fieldset and re-enables it on its own
7803
+ Attributes.toggle(this.#input, 'disabled', d);
7765
7804
  }
7766
7805
  get required() {
7767
7806
  return this.#input.getAttribute('aria-required') === 'true';
@@ -8399,8 +8438,10 @@ var fml = (function (exports) {
8399
8438
  return super.disabled;
8400
8439
  }
8401
8440
  set disabled(d) {
8402
- Attributes.toggle(this.#value2, 'disabled', d);
8441
+ //the claim and the first operand are the base's, the second operand mirrors
8442
+ //the claim like the first one does
8403
8443
  super.disabled = d;
8444
+ Attributes.toggle(this.#value2, 'disabled', d);
8404
8445
  }
8405
8446
  }
8406
8447
 
@@ -8515,8 +8556,10 @@ var fml = (function (exports) {
8515
8556
  return super.disabled;
8516
8557
  }
8517
8558
  set disabled(d) {
8518
- Attributes.toggle(this.#value2, 'disabled', d);
8559
+ //the claim and the first operand are the base's, the second operand mirrors
8560
+ //the claim like the first one does
8519
8561
  super.disabled = d;
8562
+ Attributes.toggle(this.#value2, 'disabled', d);
8520
8563
  }
8521
8564
  }
8522
8565