@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/ful.mjs CHANGED
@@ -778,15 +778,17 @@ class Input extends ParsedElement {
778
778
  });
779
779
  }
780
780
  get disabled() {
781
- return this._input.hasAttribute('disabled');
781
+ //the claim only, like a native input: the effective state, claim or disabled
782
+ //ancestry, is what :disabled matches
783
+ return this.hasAttribute('disabled');
782
784
  }
783
785
  set disabled(d) {
784
- Attributes.toggle(this._input, 'disabled', d);
785
- //also on the host: a form associated element only matches :disabled through its
786
- //own attribute, and that is what keeps it out of the submitted values. no reflect
787
- //is needed, disabled is deliberately not observed: the platform delivers it
788
- //through formDisabledCallback, which also covers a disabled ancestor fieldset
786
+ //the claim belongs to the author alone, nothing else ever writes it
789
787
  Attributes.toggle(this, 'disabled', d);
788
+ //the inner control carries the claim as a native input would: a disabled
789
+ //fieldset ancestry is left to the browser, which reaches the inner control
790
+ //as a descendant of the fieldset and re-enables it on its own
791
+ Attributes.toggle(this._input, 'disabled', d);
790
792
  }
791
793
  get required() {
792
794
  return this._input.getAttribute('aria-required') === 'true';
@@ -1706,7 +1708,9 @@ class Select extends ParsedElement {
1706
1708
  if (e.target.matches('input')) {
1707
1709
  return;
1708
1710
  }
1709
- if (this.disabled || this.readonly) {
1711
+ //badges and other chrome are not form controls, the guard must ask the
1712
+ //effective state
1713
+ if (this.matches(':disabled') || this.readonly) {
1710
1714
  return;
1711
1715
  }
1712
1716
  if (this.#ddmenu.shown) {
@@ -1722,7 +1726,7 @@ class Select extends ParsedElement {
1722
1726
  if (!e.target.closest('button')) {
1723
1727
  return;
1724
1728
  }
1725
- if (this.disabled || this.readonly) {
1729
+ if (this.matches(':disabled') || this.readonly) {
1726
1730
  return;
1727
1731
  }
1728
1732
  const idx = [...this.#items.children].indexOf(e.target.closest('ful-item'));
@@ -1735,7 +1739,7 @@ class Select extends ParsedElement {
1735
1739
  });
1736
1740
  this.#badges.addEventListener('click', (e) => {
1737
1741
  e.stopPropagation();
1738
- if (this.disabled || this.readonly) {
1742
+ if (this.matches(':disabled') || this.readonly) {
1739
1743
  return;
1740
1744
  }
1741
1745
  const idx = [...this.#badges.children].indexOf(e.target);
@@ -1760,7 +1764,7 @@ class Select extends ParsedElement {
1760
1764
  this.#input.value = '';
1761
1765
  });
1762
1766
  this.#input.addEventListener('keydown', (e) => {
1763
- if (this.disabled || this.readonly) {
1767
+ if (this.matches(':disabled') || this.readonly) {
1764
1768
  return;
1765
1769
  }
1766
1770
  switch (e.code) {
@@ -1813,7 +1817,7 @@ class Select extends ParsedElement {
1813
1817
  });
1814
1818
  this.#input.addEventListener('input', (e) => {
1815
1819
  e.stopPropagation();
1816
- if (this.disabled || this.readonly) {
1820
+ if (this.matches(':disabled') || this.readonly) {
1817
1821
  return;
1818
1822
  }
1819
1823
  dload();
@@ -1823,7 +1827,7 @@ class Select extends ParsedElement {
1823
1827
  if (!this.#multiple) {
1824
1828
  this.#values.clear();
1825
1829
  }
1826
- this.#values.set(e.detail.data[0], e.detail.data.slice(1));
1830
+ this.#values.set(this.#coerceKey(e.detail.data[0]), e.detail.data.slice(1));
1827
1831
  this.#changed();
1828
1832
  this.#syncBadges();
1829
1833
  this.#input.focus();
@@ -1874,10 +1878,36 @@ class Select extends ParsedElement {
1874
1878
  this.#items.replaceChildren();
1875
1879
  this.template('items').withOverlay({ entries: this.#values.entries() }).renderTo(this.#items);
1876
1880
  }
1881
+ /**
1882
+ * Coerces a key to the type declared by `k-type`. Keys reach the element from
1883
+ * both worlds: the `value` attribute is text, a loader returns whatever its
1884
+ * endpoint carries. One canonical type keeps the internal Map, which compares
1885
+ * keys strictly, consistent. A key that does not decode is left as it is.
1886
+ */
1887
+ #coerceKey(k) {
1888
+ switch (this.getAttribute('k-type')) {
1889
+ case 'number': {
1890
+ const n = k === '' ? Number.NaN : Number(k);
1891
+ return Number.isNaN(n) ? k : n;
1892
+ }
1893
+ case 'boolean': {
1894
+ if (k === true || k === 'true') {
1895
+ return true;
1896
+ }
1897
+ if (k === false || k === 'false') {
1898
+ return false;
1899
+ }
1900
+ return k;
1901
+ }
1902
+ default:
1903
+ return String(k);
1904
+ }
1905
+ }
1906
+
1877
1907
  set value(vs) {
1878
1908
  //the csvm mapper yields [] for a missing multiple value, an empty string is
1879
1909
  //left alone: it is a usable key for an <option value="">
1880
- const keys = vs == null ? [] : Array.isArray(vs) ? vs : [vs];
1910
+ const keys = (vs == null ? [] : Array.isArray(vs) ? vs : [vs]).map((k) => this.#coerceKey(k));
1881
1911
  //the keys are known synchronously and are all `value` reads, so they are applied
1882
1912
  //now: only the labels need the loader, until then a key stands in for its own
1883
1913
  this.#values = new Map(keys.map((k) => [k, [k]]));
@@ -1900,7 +1930,8 @@ class Select extends ParsedElement {
1900
1930
  }
1901
1931
  //label the keys that are still selected: a removal made while the lookup was in
1902
1932
  //flight must not be undone by it, and a key the loader does not know is dropped
1903
- const resolved = new Map(entries.map((e) => [e[0], e.slice(1)]));
1933
+ //the loader keys are coerced too, so they line up with the assigned ones
1934
+ const resolved = new Map(entries.map((e) => [this.#coerceKey(e[0]), e.slice(1)]));
1904
1935
  for (const key of keys) {
1905
1936
  if (!this.#values.has(key)) {
1906
1937
  continue;
@@ -1926,15 +1957,17 @@ class Select extends ParsedElement {
1926
1957
  return [...this.#values.entries()][0] ?? null;
1927
1958
  }
1928
1959
  get disabled() {
1929
- return this.#input.hasAttribute('disabled');
1960
+ //the claim only, like a native input: the effective state, claim or disabled
1961
+ //ancestry, is what :disabled matches
1962
+ return this.hasAttribute('disabled');
1930
1963
  }
1931
1964
  set disabled(d) {
1932
- Attributes.toggle(this.#input, 'disabled', d);
1933
- //also on the host: a form associated element only matches :disabled through its
1934
- //own attribute, and that is what keeps it out of the submitted values. no reflect
1935
- //is needed, disabled is deliberately not observed: the platform delivers it
1936
- //through formDisabledCallback, which also covers a disabled ancestor fieldset
1965
+ //the claim belongs to the author alone, nothing else ever writes it
1937
1966
  Attributes.toggle(this, 'disabled', d);
1967
+ //the inner control carries the claim as a native input would: a disabled
1968
+ //fieldset ancestry is left to the browser, which reaches the inner control
1969
+ //as a descendant of the fieldset and re-enables it on its own
1970
+ Attributes.toggle(this.#input, 'disabled', d);
1938
1971
  }
1939
1972
  get readonly() {
1940
1973
  return this.#input.readOnly;
@@ -2082,15 +2115,17 @@ class RadioGroup extends ParsedElement {
2082
2115
  });
2083
2116
  }
2084
2117
  get disabled() {
2085
- return this.#fieldset.hasAttribute('disabled');
2118
+ //the claim only, like a native input: the effective state, claim or disabled
2119
+ //ancestry, is what :disabled matches
2120
+ return this.hasAttribute('disabled');
2086
2121
  }
2087
2122
  set disabled(d) {
2088
- Attributes.toggle(this.#fieldset, 'disabled', d);
2089
- //also on the host: a form associated element only matches :disabled through its
2090
- //own attribute, and that is what keeps it out of the submitted values. no reflect
2091
- //is needed, disabled is deliberately not observed: the platform delivers it
2092
- //through formDisabledCallback, which also covers a disabled ancestor fieldset
2123
+ //the claim belongs to the author alone, nothing else ever writes it
2093
2124
  Attributes.toggle(this, 'disabled', d);
2125
+ //the group disables through its own fieldset, which carries the claim like
2126
+ //a native input would: a disabled outer ancestry is left to the browser,
2127
+ //which reaches the radios as descendants and re-enables them on its own
2128
+ this.#fieldset.disabled = d;
2094
2129
  }
2095
2130
  get required() {
2096
2131
  return this.#fieldset.getAttribute('aria-required') === 'true';
@@ -2165,7 +2200,8 @@ class Checkbox extends ParsedElement {
2165
2200
  const label = fragment.querySelector('label');
2166
2201
  label.addEventListener('click', () => {
2167
2202
  this.focus();
2168
- if (this.disabled || this.readonly) {
2203
+ //a label is not a form control, the guard must ask the effective state
2204
+ if (this.matches(':disabled') || this.readonly) {
2169
2205
  return;
2170
2206
  }
2171
2207
  this.value = !this.value;
@@ -2200,15 +2236,17 @@ class Checkbox extends ParsedElement {
2200
2236
  });
2201
2237
  }
2202
2238
  get disabled() {
2203
- return this.#input.hasAttribute('disabled');
2239
+ //the claim only, like a native input: the effective state, claim or disabled
2240
+ //ancestry, is what :disabled matches
2241
+ return this.hasAttribute('disabled');
2204
2242
  }
2205
2243
  set disabled(d) {
2206
- Attributes.toggle(this.#input, 'disabled', d);
2207
- //also on the host: a form associated element only matches :disabled through its
2208
- //own attribute, and that is what keeps it out of the submitted values. no reflect
2209
- //is needed, disabled is deliberately not observed: the platform delivers it
2210
- //through formDisabledCallback, which also covers a disabled ancestor fieldset
2244
+ //the claim belongs to the author alone, nothing else ever writes it
2211
2245
  Attributes.toggle(this, 'disabled', d);
2246
+ //the inner control carries the claim as a native input would: a disabled
2247
+ //fieldset ancestry is left to the browser, which reaches the inner control
2248
+ //as a descendant of the fieldset and re-enables it on its own
2249
+ Attributes.toggle(this.#input, 'disabled', d);
2212
2250
  }
2213
2251
  get required() {
2214
2252
  return this.#input.getAttribute('aria-required') === 'true';
@@ -2846,8 +2884,10 @@ class InstantFilter extends Input {
2846
2884
  return super.disabled;
2847
2885
  }
2848
2886
  set disabled(d) {
2849
- Attributes.toggle(this.#value2, 'disabled', d);
2887
+ //the claim and the first operand are the base's, the second operand mirrors
2888
+ //the claim like the first one does
2850
2889
  super.disabled = d;
2890
+ Attributes.toggle(this.#value2, 'disabled', d);
2851
2891
  }
2852
2892
  }
2853
2893
 
@@ -2962,8 +3002,10 @@ class LocalDateFilter extends Input {
2962
3002
  return super.disabled;
2963
3003
  }
2964
3004
  set disabled(d) {
2965
- Attributes.toggle(this.#value2, 'disabled', d);
3005
+ //the claim and the first operand are the base's, the second operand mirrors
3006
+ //the claim like the first one does
2966
3007
  super.disabled = d;
3008
+ Attributes.toggle(this.#value2, 'disabled', d);
2967
3009
  }
2968
3010
  }
2969
3011