@optionfactory/fml 8.0.1 → 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/custom-elements.json +1210 -0
- package/dist/fml.css +2 -2
- package/dist/fml.css.map +1 -1
- package/dist/fml.d.mts +36 -13
- package/dist/fml.iife.js +123 -51
- package/dist/fml.iife.js.map +1 -1
- package/dist/fml.iife.min.js +1 -1
- package/dist/fml.iife.min.js.map +1 -1
- package/dist/fml.min.mjs +1 -1
- package/dist/fml.min.mjs.map +1 -1
- package/dist/fml.mjs +123 -51
- package/dist/fml.mjs.map +1 -1
- package/dist/ftl.d.mts +24 -1
- package/dist/ftl.iife.js +46 -16
- package/dist/ftl.iife.js.map +1 -1
- package/dist/ftl.iife.min.js +1 -1
- package/dist/ftl.iife.min.js.map +1 -1
- package/dist/ftl.min.mjs +1 -1
- package/dist/ftl.min.mjs.map +1 -1
- package/dist/ftl.mjs +46 -16
- package/dist/ftl.mjs.map +1 -1
- package/dist/ful.css +1 -1
- package/dist/ful.css.map +1 -1
- package/dist/ful.d.mts +12 -12
- package/dist/ful.iife.js +77 -35
- package/dist/ful.iife.js.map +1 -1
- package/dist/ful.iife.min.js +1 -1
- package/dist/ful.iife.min.js.map +1 -1
- package/dist/ful.min.mjs +1 -1
- package/dist/ful.min.mjs.map +1 -1
- package/dist/ful.mjs +77 -35
- package/dist/ful.mjs.map +1 -1
- package/dist/vscode.html-custom-data.json +357 -0
- package/dist/web-types.json +1006 -0
- package/package.json +6 -3
package/dist/fml.mjs
CHANGED
|
@@ -4378,25 +4378,38 @@ class UpgradeQueue {
|
|
|
4378
4378
|
);
|
|
4379
4379
|
});
|
|
4380
4380
|
}
|
|
4381
|
+
#finished = new Map();
|
|
4381
4382
|
enqueue(el) {
|
|
4382
4383
|
if (this.#q.has(el)) {
|
|
4383
4384
|
//already upgrading, can happen when disconnecting an element
|
|
4384
4385
|
//while it's already queued for upgrade (e.g.: ful-form)
|
|
4385
4386
|
return;
|
|
4386
4387
|
}
|
|
4388
|
+
//settling waits on a signal that only ever resolves, so nothing here attaches a
|
|
4389
|
+
//rejection handler to the upgrade itself: a component that fails is still
|
|
4390
|
+
//reported the way it always was
|
|
4391
|
+
const { promise: finished, resolve: markFinished } = /** @type {PromiseWithResolvers<void>} */ (
|
|
4392
|
+
Promise.withResolvers()
|
|
4393
|
+
);
|
|
4387
4394
|
const promise = Nodes.waitParsed(el)
|
|
4388
4395
|
.then(() => el.upgrade())
|
|
4389
|
-
.finally(() =>
|
|
4396
|
+
.finally(() => {
|
|
4397
|
+
this.#q.delete(el);
|
|
4398
|
+
this.#finished.delete(el);
|
|
4399
|
+
markFinished();
|
|
4400
|
+
});
|
|
4390
4401
|
this.#q.set(el, promise);
|
|
4402
|
+
this.#finished.set(el, finished);
|
|
4391
4403
|
}
|
|
4392
4404
|
/**
|
|
4393
|
-
* Waits for every queued upgrade, including the ones enqueued while
|
|
4394
|
-
* a component is only queued once its parent connects it, so a single pass
|
|
4395
|
-
* miss everything nested.
|
|
4405
|
+
* Waits for every queued upgrade to settle, including the ones enqueued while
|
|
4406
|
+
* waiting: a component is only queued once its parent connects it, so a single pass
|
|
4407
|
+
* would miss everything nested. A component that fails to upgrade does not hold the
|
|
4408
|
+
* others back, readiness means the queue drained rather than that everything worked.
|
|
4396
4409
|
*/
|
|
4397
4410
|
async settle() {
|
|
4398
|
-
while (this.#
|
|
4399
|
-
await Promise.all(Array.from(this.#
|
|
4411
|
+
while (this.#finished.size !== 0) {
|
|
4412
|
+
await Promise.all(Array.from(this.#finished.values()));
|
|
4400
4413
|
}
|
|
4401
4414
|
}
|
|
4402
4415
|
get entries() {
|
|
@@ -4681,14 +4694,30 @@ class ParsedElement extends HTMLElement {
|
|
|
4681
4694
|
}
|
|
4682
4695
|
this[attr] = this.unmarshal(attr, newValue);
|
|
4683
4696
|
}
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
|
|
4697
|
+
/**
|
|
4698
|
+
* The disabled protocol follows the semantics of a native form control:
|
|
4699
|
+
*
|
|
4700
|
+
* - the `disabled` attribute on the host is the field's own claim, and nothing
|
|
4701
|
+
* but its author ever writes or removes it, in markup or through the property.
|
|
4702
|
+
* The framework never claims on the form's behalf, so there is nothing to
|
|
4703
|
+
* unclaim and nothing to lose: a field declared disabled inside a disabled
|
|
4704
|
+
* `<fieldset>` stays disabled when the fieldset comes back, exactly like a
|
|
4705
|
+
* native input keeps its attribute.
|
|
4706
|
+
* - the effective state is the claim OR a disabled fieldset ancestry, which the
|
|
4707
|
+
* platform maintains on its own: `:disabled` matches both, a disabled field is
|
|
4708
|
+
* left out of the submitted values, and the inner native controls are reached
|
|
4709
|
+
* by the ancestry as descendants of the fieldset.
|
|
4710
|
+
* - the `disabled` property reflects the claim only, like a native input's: a
|
|
4711
|
+
* field disabled by its ancestry reads `false` while `matches(':disabled')`
|
|
4712
|
+
* tells the effective state. Un-claiming inside a disabled fieldset cannot
|
|
4713
|
+
* enable the field.
|
|
4714
|
+
* - the inner controls mirror the claim and nothing else: the ancestry state is
|
|
4715
|
+
* never written anywhere, so it can never go stale, and the browser composes
|
|
4716
|
+
* the two on its own when it disables and re-enables a fieldset's descendants.
|
|
4717
|
+
*
|
|
4718
|
+
* Because of this, formDisabledCallback carries nothing the framework needs to
|
|
4719
|
+
* apply, and the protocol does not define it.
|
|
4720
|
+
*/
|
|
4692
4721
|
async upgrade() {
|
|
4693
4722
|
if (this.#parsed) {
|
|
4694
4723
|
return;
|
|
@@ -4701,8 +4730,9 @@ class ParsedElement extends HTMLElement {
|
|
|
4701
4730
|
this.unmarshal(attribute, this.getAttribute(attribute)),
|
|
4702
4731
|
]),
|
|
4703
4732
|
);
|
|
4704
|
-
|
|
4705
|
-
|
|
4733
|
+
//the declared claim is what render receives: the ancestry state is not
|
|
4734
|
+
//passed around, it is already where it needs to be
|
|
4735
|
+
await this.render({ slots, observed, disabled: this.hasAttribute('disabled') });
|
|
4706
4736
|
}
|
|
4707
4737
|
render(c) {}
|
|
4708
4738
|
reflect(fn) {
|
|
@@ -6299,15 +6329,17 @@ class Input extends ParsedElement {
|
|
|
6299
6329
|
});
|
|
6300
6330
|
}
|
|
6301
6331
|
get disabled() {
|
|
6302
|
-
|
|
6332
|
+
//the claim only, like a native input: the effective state, claim or disabled
|
|
6333
|
+
//ancestry, is what :disabled matches
|
|
6334
|
+
return this.hasAttribute('disabled');
|
|
6303
6335
|
}
|
|
6304
6336
|
set disabled(d) {
|
|
6305
|
-
|
|
6306
|
-
//also on the host: a form associated element only matches :disabled through its
|
|
6307
|
-
//own attribute, and that is what keeps it out of the submitted values. no reflect
|
|
6308
|
-
//is needed, disabled is deliberately not observed: the platform delivers it
|
|
6309
|
-
//through formDisabledCallback, which also covers a disabled ancestor fieldset
|
|
6337
|
+
//the claim belongs to the author alone, nothing else ever writes it
|
|
6310
6338
|
Attributes.toggle(this, 'disabled', d);
|
|
6339
|
+
//the inner control carries the claim as a native input would: a disabled
|
|
6340
|
+
//fieldset ancestry is left to the browser, which reaches the inner control
|
|
6341
|
+
//as a descendant of the fieldset and re-enables it on its own
|
|
6342
|
+
Attributes.toggle(this._input, 'disabled', d);
|
|
6311
6343
|
}
|
|
6312
6344
|
get required() {
|
|
6313
6345
|
return this._input.getAttribute('aria-required') === 'true';
|
|
@@ -7227,7 +7259,9 @@ class Select extends ParsedElement {
|
|
|
7227
7259
|
if (e.target.matches('input')) {
|
|
7228
7260
|
return;
|
|
7229
7261
|
}
|
|
7230
|
-
|
|
7262
|
+
//badges and other chrome are not form controls, the guard must ask the
|
|
7263
|
+
//effective state
|
|
7264
|
+
if (this.matches(':disabled') || this.readonly) {
|
|
7231
7265
|
return;
|
|
7232
7266
|
}
|
|
7233
7267
|
if (this.#ddmenu.shown) {
|
|
@@ -7243,7 +7277,7 @@ class Select extends ParsedElement {
|
|
|
7243
7277
|
if (!e.target.closest('button')) {
|
|
7244
7278
|
return;
|
|
7245
7279
|
}
|
|
7246
|
-
if (this.disabled || this.readonly) {
|
|
7280
|
+
if (this.matches(':disabled') || this.readonly) {
|
|
7247
7281
|
return;
|
|
7248
7282
|
}
|
|
7249
7283
|
const idx = [...this.#items.children].indexOf(e.target.closest('ful-item'));
|
|
@@ -7256,7 +7290,7 @@ class Select extends ParsedElement {
|
|
|
7256
7290
|
});
|
|
7257
7291
|
this.#badges.addEventListener('click', (e) => {
|
|
7258
7292
|
e.stopPropagation();
|
|
7259
|
-
if (this.disabled || this.readonly) {
|
|
7293
|
+
if (this.matches(':disabled') || this.readonly) {
|
|
7260
7294
|
return;
|
|
7261
7295
|
}
|
|
7262
7296
|
const idx = [...this.#badges.children].indexOf(e.target);
|
|
@@ -7281,7 +7315,7 @@ class Select extends ParsedElement {
|
|
|
7281
7315
|
this.#input.value = '';
|
|
7282
7316
|
});
|
|
7283
7317
|
this.#input.addEventListener('keydown', (e) => {
|
|
7284
|
-
if (this.disabled || this.readonly) {
|
|
7318
|
+
if (this.matches(':disabled') || this.readonly) {
|
|
7285
7319
|
return;
|
|
7286
7320
|
}
|
|
7287
7321
|
switch (e.code) {
|
|
@@ -7334,7 +7368,7 @@ class Select extends ParsedElement {
|
|
|
7334
7368
|
});
|
|
7335
7369
|
this.#input.addEventListener('input', (e) => {
|
|
7336
7370
|
e.stopPropagation();
|
|
7337
|
-
if (this.disabled || this.readonly) {
|
|
7371
|
+
if (this.matches(':disabled') || this.readonly) {
|
|
7338
7372
|
return;
|
|
7339
7373
|
}
|
|
7340
7374
|
dload();
|
|
@@ -7344,7 +7378,7 @@ class Select extends ParsedElement {
|
|
|
7344
7378
|
if (!this.#multiple) {
|
|
7345
7379
|
this.#values.clear();
|
|
7346
7380
|
}
|
|
7347
|
-
this.#values.set(e.detail.data[0], e.detail.data.slice(1));
|
|
7381
|
+
this.#values.set(this.#coerceKey(e.detail.data[0]), e.detail.data.slice(1));
|
|
7348
7382
|
this.#changed();
|
|
7349
7383
|
this.#syncBadges();
|
|
7350
7384
|
this.#input.focus();
|
|
@@ -7395,10 +7429,36 @@ class Select extends ParsedElement {
|
|
|
7395
7429
|
this.#items.replaceChildren();
|
|
7396
7430
|
this.template('items').withOverlay({ entries: this.#values.entries() }).renderTo(this.#items);
|
|
7397
7431
|
}
|
|
7432
|
+
/**
|
|
7433
|
+
* Coerces a key to the type declared by `k-type`. Keys reach the element from
|
|
7434
|
+
* both worlds: the `value` attribute is text, a loader returns whatever its
|
|
7435
|
+
* endpoint carries. One canonical type keeps the internal Map, which compares
|
|
7436
|
+
* keys strictly, consistent. A key that does not decode is left as it is.
|
|
7437
|
+
*/
|
|
7438
|
+
#coerceKey(k) {
|
|
7439
|
+
switch (this.getAttribute('k-type')) {
|
|
7440
|
+
case 'number': {
|
|
7441
|
+
const n = k === '' ? Number.NaN : Number(k);
|
|
7442
|
+
return Number.isNaN(n) ? k : n;
|
|
7443
|
+
}
|
|
7444
|
+
case 'boolean': {
|
|
7445
|
+
if (k === true || k === 'true') {
|
|
7446
|
+
return true;
|
|
7447
|
+
}
|
|
7448
|
+
if (k === false || k === 'false') {
|
|
7449
|
+
return false;
|
|
7450
|
+
}
|
|
7451
|
+
return k;
|
|
7452
|
+
}
|
|
7453
|
+
default:
|
|
7454
|
+
return String(k);
|
|
7455
|
+
}
|
|
7456
|
+
}
|
|
7457
|
+
|
|
7398
7458
|
set value(vs) {
|
|
7399
7459
|
//the csvm mapper yields [] for a missing multiple value, an empty string is
|
|
7400
7460
|
//left alone: it is a usable key for an <option value="">
|
|
7401
|
-
const keys = vs == null ? [] : Array.isArray(vs) ? vs : [vs];
|
|
7461
|
+
const keys = (vs == null ? [] : Array.isArray(vs) ? vs : [vs]).map((k) => this.#coerceKey(k));
|
|
7402
7462
|
//the keys are known synchronously and are all `value` reads, so they are applied
|
|
7403
7463
|
//now: only the labels need the loader, until then a key stands in for its own
|
|
7404
7464
|
this.#values = new Map(keys.map((k) => [k, [k]]));
|
|
@@ -7421,7 +7481,8 @@ class Select extends ParsedElement {
|
|
|
7421
7481
|
}
|
|
7422
7482
|
//label the keys that are still selected: a removal made while the lookup was in
|
|
7423
7483
|
//flight must not be undone by it, and a key the loader does not know is dropped
|
|
7424
|
-
|
|
7484
|
+
//the loader keys are coerced too, so they line up with the assigned ones
|
|
7485
|
+
const resolved = new Map(entries.map((e) => [this.#coerceKey(e[0]), e.slice(1)]));
|
|
7425
7486
|
for (const key of keys) {
|
|
7426
7487
|
if (!this.#values.has(key)) {
|
|
7427
7488
|
continue;
|
|
@@ -7447,15 +7508,17 @@ class Select extends ParsedElement {
|
|
|
7447
7508
|
return [...this.#values.entries()][0] ?? null;
|
|
7448
7509
|
}
|
|
7449
7510
|
get disabled() {
|
|
7450
|
-
|
|
7511
|
+
//the claim only, like a native input: the effective state, claim or disabled
|
|
7512
|
+
//ancestry, is what :disabled matches
|
|
7513
|
+
return this.hasAttribute('disabled');
|
|
7451
7514
|
}
|
|
7452
7515
|
set disabled(d) {
|
|
7453
|
-
|
|
7454
|
-
//also on the host: a form associated element only matches :disabled through its
|
|
7455
|
-
//own attribute, and that is what keeps it out of the submitted values. no reflect
|
|
7456
|
-
//is needed, disabled is deliberately not observed: the platform delivers it
|
|
7457
|
-
//through formDisabledCallback, which also covers a disabled ancestor fieldset
|
|
7516
|
+
//the claim belongs to the author alone, nothing else ever writes it
|
|
7458
7517
|
Attributes.toggle(this, 'disabled', d);
|
|
7518
|
+
//the inner control carries the claim as a native input would: a disabled
|
|
7519
|
+
//fieldset ancestry is left to the browser, which reaches the inner control
|
|
7520
|
+
//as a descendant of the fieldset and re-enables it on its own
|
|
7521
|
+
Attributes.toggle(this.#input, 'disabled', d);
|
|
7459
7522
|
}
|
|
7460
7523
|
get readonly() {
|
|
7461
7524
|
return this.#input.readOnly;
|
|
@@ -7603,15 +7666,17 @@ class RadioGroup extends ParsedElement {
|
|
|
7603
7666
|
});
|
|
7604
7667
|
}
|
|
7605
7668
|
get disabled() {
|
|
7606
|
-
|
|
7669
|
+
//the claim only, like a native input: the effective state, claim or disabled
|
|
7670
|
+
//ancestry, is what :disabled matches
|
|
7671
|
+
return this.hasAttribute('disabled');
|
|
7607
7672
|
}
|
|
7608
7673
|
set disabled(d) {
|
|
7609
|
-
|
|
7610
|
-
//also on the host: a form associated element only matches :disabled through its
|
|
7611
|
-
//own attribute, and that is what keeps it out of the submitted values. no reflect
|
|
7612
|
-
//is needed, disabled is deliberately not observed: the platform delivers it
|
|
7613
|
-
//through formDisabledCallback, which also covers a disabled ancestor fieldset
|
|
7674
|
+
//the claim belongs to the author alone, nothing else ever writes it
|
|
7614
7675
|
Attributes.toggle(this, 'disabled', d);
|
|
7676
|
+
//the group disables through its own fieldset, which carries the claim like
|
|
7677
|
+
//a native input would: a disabled outer ancestry is left to the browser,
|
|
7678
|
+
//which reaches the radios as descendants and re-enables them on its own
|
|
7679
|
+
this.#fieldset.disabled = d;
|
|
7615
7680
|
}
|
|
7616
7681
|
get required() {
|
|
7617
7682
|
return this.#fieldset.getAttribute('aria-required') === 'true';
|
|
@@ -7686,7 +7751,8 @@ class Checkbox extends ParsedElement {
|
|
|
7686
7751
|
const label = fragment.querySelector('label');
|
|
7687
7752
|
label.addEventListener('click', () => {
|
|
7688
7753
|
this.focus();
|
|
7689
|
-
|
|
7754
|
+
//a label is not a form control, the guard must ask the effective state
|
|
7755
|
+
if (this.matches(':disabled') || this.readonly) {
|
|
7690
7756
|
return;
|
|
7691
7757
|
}
|
|
7692
7758
|
this.value = !this.value;
|
|
@@ -7721,15 +7787,17 @@ class Checkbox extends ParsedElement {
|
|
|
7721
7787
|
});
|
|
7722
7788
|
}
|
|
7723
7789
|
get disabled() {
|
|
7724
|
-
|
|
7790
|
+
//the claim only, like a native input: the effective state, claim or disabled
|
|
7791
|
+
//ancestry, is what :disabled matches
|
|
7792
|
+
return this.hasAttribute('disabled');
|
|
7725
7793
|
}
|
|
7726
7794
|
set disabled(d) {
|
|
7727
|
-
|
|
7728
|
-
//also on the host: a form associated element only matches :disabled through its
|
|
7729
|
-
//own attribute, and that is what keeps it out of the submitted values. no reflect
|
|
7730
|
-
//is needed, disabled is deliberately not observed: the platform delivers it
|
|
7731
|
-
//through formDisabledCallback, which also covers a disabled ancestor fieldset
|
|
7795
|
+
//the claim belongs to the author alone, nothing else ever writes it
|
|
7732
7796
|
Attributes.toggle(this, 'disabled', d);
|
|
7797
|
+
//the inner control carries the claim as a native input would: a disabled
|
|
7798
|
+
//fieldset ancestry is left to the browser, which reaches the inner control
|
|
7799
|
+
//as a descendant of the fieldset and re-enables it on its own
|
|
7800
|
+
Attributes.toggle(this.#input, 'disabled', d);
|
|
7733
7801
|
}
|
|
7734
7802
|
get required() {
|
|
7735
7803
|
return this.#input.getAttribute('aria-required') === 'true';
|
|
@@ -8367,8 +8435,10 @@ class InstantFilter extends Input {
|
|
|
8367
8435
|
return super.disabled;
|
|
8368
8436
|
}
|
|
8369
8437
|
set disabled(d) {
|
|
8370
|
-
|
|
8438
|
+
//the claim and the first operand are the base's, the second operand mirrors
|
|
8439
|
+
//the claim like the first one does
|
|
8371
8440
|
super.disabled = d;
|
|
8441
|
+
Attributes.toggle(this.#value2, 'disabled', d);
|
|
8372
8442
|
}
|
|
8373
8443
|
}
|
|
8374
8444
|
|
|
@@ -8483,8 +8553,10 @@ class LocalDateFilter extends Input {
|
|
|
8483
8553
|
return super.disabled;
|
|
8484
8554
|
}
|
|
8485
8555
|
set disabled(d) {
|
|
8486
|
-
|
|
8556
|
+
//the claim and the first operand are the base's, the second operand mirrors
|
|
8557
|
+
//the claim like the first one does
|
|
8487
8558
|
super.disabled = d;
|
|
8559
|
+
Attributes.toggle(this.#value2, 'disabled', d);
|
|
8488
8560
|
}
|
|
8489
8561
|
}
|
|
8490
8562
|
|