@optionfactory/fml 8.0.0 → 8.0.2
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/client-errors.iife.js +11 -8
- package/dist/client-errors.iife.js.map +1 -1
- package/dist/client-errors.iife.min.js +1 -1
- package/dist/client-errors.iife.min.js.map +1 -1
- package/dist/custom-elements.json +1210 -0
- package/dist/fml.css +10 -1
- package/dist/fml.css.map +1 -1
- package/dist/fml.d.mts +73 -13
- package/dist/fml.iife.js +628 -209
- 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 +628 -209
- package/dist/fml.mjs.map +1 -1
- package/dist/ftl.d.mts +2 -1
- package/dist/ftl.iife.js +90 -34
- 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 +90 -34
- package/dist/ftl.mjs.map +1 -1
- package/dist/ful.css +10 -1
- package/dist/ful.css.map +1 -1
- package/dist/ful.d.mts +22 -6
- package/dist/ful.iife.js +496 -166
- 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 +496 -166
- package/dist/ful.mjs.map +1 -1
- package/dist/httpc.d.mts +49 -6
- package/dist/httpc.iife.js +40 -9
- package/dist/httpc.iife.js.map +1 -1
- package/dist/httpc.iife.min.js +1 -1
- package/dist/httpc.iife.min.js.map +1 -1
- package/dist/httpc.min.mjs +1 -1
- package/dist/httpc.min.mjs.map +1 -1
- package/dist/httpc.mjs +40 -9
- package/dist/httpc.mjs.map +1 -1
- package/dist/vscode.html-custom-data.json +357 -0
- package/dist/web-types.json +1006 -0
- package/package.json +11 -9
package/dist/fml.iife.js
CHANGED
|
@@ -3460,7 +3460,7 @@ var fml = (function (exports) {
|
|
|
3460
3460
|
case '<=':
|
|
3461
3461
|
return lhs <= rhs;
|
|
3462
3462
|
default:
|
|
3463
|
-
throw new Error(
|
|
3463
|
+
throw new Error(`unknown cmp op ${node.op}`);
|
|
3464
3464
|
}
|
|
3465
3465
|
}
|
|
3466
3466
|
[nodes.call](node) {
|
|
@@ -3471,7 +3471,7 @@ var fml = (function (exports) {
|
|
|
3471
3471
|
}
|
|
3472
3472
|
const fn = module[fnRef.value];
|
|
3473
3473
|
if (!fn) {
|
|
3474
|
-
throw new Error(`Function "#${fnRef.module === null ? '' : fnRef.module
|
|
3474
|
+
throw new Error(`Function "#${fnRef.module === null ? '' : `${fnRef.module}:`}${fnRef.value}" not found`);
|
|
3475
3475
|
}
|
|
3476
3476
|
const args = node.args.map((arg) => this.visit(arg));
|
|
3477
3477
|
return fn.apply(this.#resolve_proxy(), args);
|
|
@@ -3507,14 +3507,14 @@ var fml = (function (exports) {
|
|
|
3507
3507
|
return cond ? cond : this.visit(node.ifFalse);
|
|
3508
3508
|
}
|
|
3509
3509
|
[nodes.access](node) {
|
|
3510
|
-
let prev
|
|
3510
|
+
let prev ;
|
|
3511
3511
|
let cur = this.visit(node.lhs);
|
|
3512
3512
|
for (let i = 0; i !== node.rhs.length; ++i) {
|
|
3513
3513
|
const rhs = node.rhs[i];
|
|
3514
3514
|
if (rhs.ns && cur == null) {
|
|
3515
3515
|
return undefined;
|
|
3516
3516
|
}
|
|
3517
|
-
let value
|
|
3517
|
+
let value ;
|
|
3518
3518
|
switch (rhs.type) {
|
|
3519
3519
|
case nodes.member: {
|
|
3520
3520
|
value = cur[rhs.rhs];
|
|
@@ -3555,7 +3555,7 @@ var fml = (function (exports) {
|
|
|
3555
3555
|
case nodes.templated.ten:
|
|
3556
3556
|
return { type: nodes.dom.n, value: this.visit(node.value) };
|
|
3557
3557
|
default:
|
|
3558
|
-
throw new Error(
|
|
3558
|
+
throw new Error(`unknown node type ${node.type.toString()}`);
|
|
3559
3559
|
}
|
|
3560
3560
|
});
|
|
3561
3561
|
}
|
|
@@ -3731,7 +3731,7 @@ var fml = (function (exports) {
|
|
|
3731
3731
|
.forEach((a) => {
|
|
3732
3732
|
const target = a.substring(prefix.length);
|
|
3733
3733
|
if (target === 'class') {
|
|
3734
|
-
const classes = from.getAttribute(prefix
|
|
3734
|
+
const classes = from.getAttribute(`${prefix}class`)?.split(/\s+/).filter((a) => a.length) ?? [];
|
|
3735
3735
|
to.classList.add(...classes);
|
|
3736
3736
|
return;
|
|
3737
3737
|
}
|
|
@@ -3823,8 +3823,7 @@ var fml = (function (exports) {
|
|
|
3823
3823
|
* @returns
|
|
3824
3824
|
*/
|
|
3825
3825
|
static isParsed(el) {
|
|
3826
|
-
|
|
3827
|
-
for (let c = el; c; c = c.parentNode) {
|
|
3826
|
+
for (let c = /** @type {Node | null} */ (el); c; c = c.parentNode) {
|
|
3828
3827
|
if (c.nextSibling) {
|
|
3829
3828
|
return true;
|
|
3830
3829
|
}
|
|
@@ -3887,19 +3886,18 @@ var fml = (function (exports) {
|
|
|
3887
3886
|
}
|
|
3888
3887
|
|
|
3889
3888
|
class NodeOperations {
|
|
3890
|
-
#forRemoval;
|
|
3891
|
-
constructor() {
|
|
3892
|
-
this.#forRemoval = [];
|
|
3893
|
-
}
|
|
3889
|
+
#forRemoval = new Set();
|
|
3894
3890
|
removed(node) {
|
|
3895
|
-
return this.#forRemoval.
|
|
3891
|
+
return this.#forRemoval.has(node);
|
|
3896
3892
|
}
|
|
3897
3893
|
remove(node) {
|
|
3898
3894
|
node.replaceChildren?.();
|
|
3899
3895
|
Object.keys(node.dataset || {})
|
|
3900
3896
|
.filter((k) => k.startsWith('tpl'))
|
|
3901
|
-
.forEach((k) =>
|
|
3902
|
-
|
|
3897
|
+
.forEach((k) => {
|
|
3898
|
+
delete node.dataset[k];
|
|
3899
|
+
});
|
|
3900
|
+
this.#forRemoval.add(node);
|
|
3903
3901
|
}
|
|
3904
3902
|
popData(node, key) {
|
|
3905
3903
|
const v = node.dataset[key];
|
|
@@ -3917,9 +3915,10 @@ var fml = (function (exports) {
|
|
|
3917
3915
|
this.remove(ref);
|
|
3918
3916
|
}
|
|
3919
3917
|
cleanup() {
|
|
3920
|
-
|
|
3921
|
-
|
|
3918
|
+
for (const node of this.#forRemoval) {
|
|
3919
|
+
node.remove();
|
|
3922
3920
|
}
|
|
3921
|
+
this.#forRemoval.clear();
|
|
3923
3922
|
}
|
|
3924
3923
|
}
|
|
3925
3924
|
|
|
@@ -3974,7 +3973,7 @@ var fml = (function (exports) {
|
|
|
3974
3973
|
}
|
|
3975
3974
|
static tplRemove(node, value, ops, modules, dataStack) {
|
|
3976
3975
|
switch (value.toLowerCase()) {
|
|
3977
|
-
case 'tag':
|
|
3976
|
+
case 'tag': {
|
|
3978
3977
|
const fragment = new DocumentFragment();
|
|
3979
3978
|
while (node.firstChild) {
|
|
3980
3979
|
fragment.appendChild(node.firstChild);
|
|
@@ -3988,6 +3987,7 @@ var fml = (function (exports) {
|
|
|
3988
3987
|
ops.remove(node);
|
|
3989
3988
|
}
|
|
3990
3989
|
break;
|
|
3990
|
+
}
|
|
3991
3991
|
case 'body':
|
|
3992
3992
|
node.replaceChildren();
|
|
3993
3993
|
break;
|
|
@@ -4053,8 +4053,10 @@ var fml = (function (exports) {
|
|
|
4053
4053
|
}
|
|
4054
4054
|
}
|
|
4055
4055
|
|
|
4056
|
-
// Module-isolated string cache for dataset-to-attribute conversions
|
|
4056
|
+
// Module-isolated string cache for dataset-to-attribute conversions, bounded with
|
|
4057
|
+
// FIFO eviction like the expression ast cache
|
|
4057
4058
|
const attributeCache = new Map();
|
|
4059
|
+
const ATTRIBUTE_CACHE_MAX_SIZE = 1000;
|
|
4058
4060
|
|
|
4059
4061
|
/**
|
|
4060
4062
|
* Converts a tpl camelCase dataset key into a kebab-case attribute name.
|
|
@@ -4065,6 +4067,9 @@ var fml = (function (exports) {
|
|
|
4065
4067
|
function toAttr(dataSetKey) {
|
|
4066
4068
|
let cached = attributeCache.get(dataSetKey);
|
|
4067
4069
|
if (!cached) {
|
|
4070
|
+
if (attributeCache.size >= ATTRIBUTE_CACHE_MAX_SIZE) {
|
|
4071
|
+
attributeCache.delete(attributeCache.keys().next().value);
|
|
4072
|
+
}
|
|
4068
4073
|
cached = dataSetKey
|
|
4069
4074
|
.substring(3)
|
|
4070
4075
|
.split(/(?=[A-Z])/)
|
|
@@ -4199,9 +4204,10 @@ var fml = (function (exports) {
|
|
|
4199
4204
|
* @param {string} expression
|
|
4200
4205
|
* @param {(typeof Expressions.MODE_EXPRESSION | typeof Expressions.MODE_TEMPLATED)?} [mode]
|
|
4201
4206
|
* @param {...*} data
|
|
4207
|
+
* @returns the evaluated expression result
|
|
4202
4208
|
*/
|
|
4203
4209
|
evaluate(expression, mode, ...data) {
|
|
4204
|
-
new ExpressionEvaluator(this.#modules, this.#dataStack).withOverlay(...data).evaluate(expression, mode);
|
|
4210
|
+
return new ExpressionEvaluator(this.#modules, this.#dataStack).withOverlay(...data).evaluate(expression, mode);
|
|
4205
4211
|
}
|
|
4206
4212
|
/**
|
|
4207
4213
|
* Returns an expression evaluator with bound modules and dataStack.
|
|
@@ -4346,8 +4352,8 @@ var fml = (function (exports) {
|
|
|
4346
4352
|
if (node.nodeType === Node.TEXT_NODE) {
|
|
4347
4353
|
node.nodeValue = node.nodeValue.trim();
|
|
4348
4354
|
}
|
|
4349
|
-
for (
|
|
4350
|
-
|
|
4355
|
+
for (let n = 0; n < node.childNodes.length; n++) {
|
|
4356
|
+
const child = node.childNodes[n];
|
|
4351
4357
|
if (child.nodeType === Node.TEXT_NODE) {
|
|
4352
4358
|
child.nodeValue = child.nodeValue.trim();
|
|
4353
4359
|
if (child.nodeValue.length === 0) {
|
|
@@ -4366,8 +4372,7 @@ var fml = (function (exports) {
|
|
|
4366
4372
|
#q = new Map();
|
|
4367
4373
|
constructor() {
|
|
4368
4374
|
document.addEventListener('DOMContentLoaded', async () => {
|
|
4369
|
-
|
|
4370
|
-
await Promise.all(pending);
|
|
4375
|
+
await this.settle();
|
|
4371
4376
|
document.dispatchEvent(
|
|
4372
4377
|
new CustomEvent('ftl:ready', {
|
|
4373
4378
|
bubbles: false,
|
|
@@ -4376,16 +4381,39 @@ var fml = (function (exports) {
|
|
|
4376
4381
|
);
|
|
4377
4382
|
});
|
|
4378
4383
|
}
|
|
4384
|
+
#finished = new Map();
|
|
4379
4385
|
enqueue(el) {
|
|
4380
4386
|
if (this.#q.has(el)) {
|
|
4381
4387
|
//already upgrading, can happen when disconnecting an element
|
|
4382
4388
|
//while it's already queued for upgrade (e.g.: ful-form)
|
|
4383
4389
|
return;
|
|
4384
4390
|
}
|
|
4391
|
+
//settling waits on a signal that only ever resolves, so nothing here attaches a
|
|
4392
|
+
//rejection handler to the upgrade itself: a component that fails is still
|
|
4393
|
+
//reported the way it always was
|
|
4394
|
+
const { promise: finished, resolve: markFinished } = /** @type {PromiseWithResolvers<void>} */ (
|
|
4395
|
+
Promise.withResolvers()
|
|
4396
|
+
);
|
|
4385
4397
|
const promise = Nodes.waitParsed(el)
|
|
4386
4398
|
.then(() => el.upgrade())
|
|
4387
|
-
.finally(() =>
|
|
4399
|
+
.finally(() => {
|
|
4400
|
+
this.#q.delete(el);
|
|
4401
|
+
this.#finished.delete(el);
|
|
4402
|
+
markFinished();
|
|
4403
|
+
});
|
|
4388
4404
|
this.#q.set(el, promise);
|
|
4405
|
+
this.#finished.set(el, finished);
|
|
4406
|
+
}
|
|
4407
|
+
/**
|
|
4408
|
+
* Waits for every queued upgrade to settle, including the ones enqueued while
|
|
4409
|
+
* waiting: a component is only queued once its parent connects it, so a single pass
|
|
4410
|
+
* would miss everything nested. A component that fails to upgrade does not hold the
|
|
4411
|
+
* others back, readiness means the queue drained rather than that everything worked.
|
|
4412
|
+
*/
|
|
4413
|
+
async settle() {
|
|
4414
|
+
while (this.#finished.size !== 0) {
|
|
4415
|
+
await Promise.all(Array.from(this.#finished.values()));
|
|
4416
|
+
}
|
|
4389
4417
|
}
|
|
4390
4418
|
get entries() {
|
|
4391
4419
|
return this.#q.entries();
|
|
@@ -4581,18 +4609,30 @@ var fml = (function (exports) {
|
|
|
4581
4609
|
}
|
|
4582
4610
|
}
|
|
4583
4611
|
|
|
4584
|
-
|
|
4585
|
-
|
|
4612
|
+
/**
|
|
4613
|
+
* Waits for the queued upgrades matching the filter, including the ones enqueued while
|
|
4614
|
+
* waiting: a component is only queued once its parent connects it, so a single pass
|
|
4615
|
+
* would miss everything nested.
|
|
4616
|
+
* @param {(el: Element) => boolean} accept
|
|
4617
|
+
*/
|
|
4618
|
+
const settle = async (accept) => {
|
|
4619
|
+
for (;;) {
|
|
4586
4620
|
const pending = Array.from(registry.upgrades)
|
|
4587
|
-
.filter(([child
|
|
4588
|
-
.map(([
|
|
4621
|
+
.filter(([child]) => accept(child))
|
|
4622
|
+
.map(([, promise]) => promise);
|
|
4623
|
+
if (pending.length === 0) {
|
|
4624
|
+
return;
|
|
4625
|
+
}
|
|
4589
4626
|
await Promise.all(pending);
|
|
4590
4627
|
}
|
|
4628
|
+
};
|
|
4629
|
+
|
|
4630
|
+
class Rendering {
|
|
4631
|
+
static async waitFor(el) {
|
|
4632
|
+
await settle((child) => el.contains(child));
|
|
4633
|
+
}
|
|
4591
4634
|
static async waitForChildren(el) {
|
|
4592
|
-
|
|
4593
|
-
.filter(([child, promise]) => el !== child && el.contains(child))
|
|
4594
|
-
.map(([child, promise]) => promise);
|
|
4595
|
-
await Promise.all(pending);
|
|
4635
|
+
await settle((child) => el !== child && el.contains(child));
|
|
4596
4636
|
}
|
|
4597
4637
|
}
|
|
4598
4638
|
|
|
@@ -4634,7 +4674,7 @@ var fml = (function (exports) {
|
|
|
4634
4674
|
template(name) {
|
|
4635
4675
|
const { modules, data } = registry.context();
|
|
4636
4676
|
let t = this.#bits().TEMPLATES[name ?? 'default'].withData(data).withModules(modules);
|
|
4637
|
-
for (
|
|
4677
|
+
for (const k of ['l10n', 'config']) {
|
|
4638
4678
|
const v = this.constructor[k];
|
|
4639
4679
|
if (v) {
|
|
4640
4680
|
t = t.withOverlay({ [k]: v });
|
|
@@ -4664,6 +4704,19 @@ var fml = (function (exports) {
|
|
|
4664
4704
|
return;
|
|
4665
4705
|
}
|
|
4666
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
|
+
}
|
|
4667
4720
|
}
|
|
4668
4721
|
async upgrade() {
|
|
4669
4722
|
if (this.#parsed) {
|
|
@@ -4679,6 +4732,9 @@ var fml = (function (exports) {
|
|
|
4679
4732
|
);
|
|
4680
4733
|
const disabled = this.#disabledBeforeParsed ?? false;
|
|
4681
4734
|
await this.render({ slots, observed, disabled });
|
|
4735
|
+
if (disabled) {
|
|
4736
|
+
this.#unclaimFormDisabled();
|
|
4737
|
+
}
|
|
4682
4738
|
}
|
|
4683
4739
|
render(c) {}
|
|
4684
4740
|
reflect(fn) {
|
|
@@ -4775,13 +4831,22 @@ var fml = (function (exports) {
|
|
|
4775
4831
|
|
|
4776
4832
|
let vi = 0;
|
|
4777
4833
|
let si = 0;
|
|
4778
|
-
|
|
4834
|
+
//every group yields three bytes, but a padded input stops short of the last
|
|
4835
|
+
//one or two: writing them anyway would rely on typed arrays dropping writes
|
|
4836
|
+
//past their length
|
|
4837
|
+
while (vi < nbytes) {
|
|
4779
4838
|
const v1 = d.indexOf(str.charAt(si++));
|
|
4780
4839
|
const v2 = d.indexOf(str.charAt(si++));
|
|
4781
4840
|
const v3 = d.indexOf(str.charAt(si++));
|
|
4782
4841
|
const v4 = d.indexOf(str.charAt(si++));
|
|
4783
4842
|
view[vi++] = (v1 << 2) | (v2 >> 4);
|
|
4843
|
+
if (vi === nbytes) {
|
|
4844
|
+
break;
|
|
4845
|
+
}
|
|
4784
4846
|
view[vi++] = ((v2 & 15) << 4) | (v3 >> 2);
|
|
4847
|
+
if (vi === nbytes) {
|
|
4848
|
+
break;
|
|
4849
|
+
}
|
|
4785
4850
|
view[vi++] = ((v3 & 3) << 6) | v4;
|
|
4786
4851
|
}
|
|
4787
4852
|
|
|
@@ -4808,7 +4873,7 @@ var fml = (function (exports) {
|
|
|
4808
4873
|
return Array.from(bytes)
|
|
4809
4874
|
.map((b) => b.toString(16))
|
|
4810
4875
|
.map((b) => (upper ? b.toUpperCase() : b))
|
|
4811
|
-
.map((o) => o.padStart(2, 0))
|
|
4876
|
+
.map((o) => o.padStart(2, '0'))
|
|
4812
4877
|
.join('');
|
|
4813
4878
|
}
|
|
4814
4879
|
}
|
|
@@ -5138,6 +5203,26 @@ var fml = (function (exports) {
|
|
|
5138
5203
|
}
|
|
5139
5204
|
};
|
|
5140
5205
|
|
|
5206
|
+
/**
|
|
5207
|
+
* Yields the entries of a headers or params initializer preserving nullish values:
|
|
5208
|
+
* normalizing through `Headers`/`URLSearchParams` first would stringify them to
|
|
5209
|
+
* "null"/"undefined" instead of removing the key.
|
|
5210
|
+
* @param {any} source
|
|
5211
|
+
* @returns {Iterable<[string, any]>}
|
|
5212
|
+
*/
|
|
5213
|
+
const rawEntries = (source) => {
|
|
5214
|
+
if (source == null) {
|
|
5215
|
+
return [];
|
|
5216
|
+
}
|
|
5217
|
+
if (typeof source === 'string') {
|
|
5218
|
+
return new URLSearchParams(source);
|
|
5219
|
+
}
|
|
5220
|
+
if (typeof source[Symbol.iterator] === 'function') {
|
|
5221
|
+
return source;
|
|
5222
|
+
}
|
|
5223
|
+
return Object.entries(source);
|
|
5224
|
+
};
|
|
5225
|
+
|
|
5141
5226
|
class HttpRequestBuilder {
|
|
5142
5227
|
#client;
|
|
5143
5228
|
#method;
|
|
@@ -5190,11 +5275,11 @@ var fml = (function (exports) {
|
|
|
5190
5275
|
}
|
|
5191
5276
|
/**
|
|
5192
5277
|
* Add all passed headers to the request, overriding existing ones if that key already exists. Null and undefined values cause the key to be removed.
|
|
5193
|
-
* @param {HeadersInit} hs
|
|
5278
|
+
* @param {HeadersInit|Record<string,string|null|undefined>} hs
|
|
5194
5279
|
* @returns {HttpRequestBuilder} this builder
|
|
5195
5280
|
*/
|
|
5196
5281
|
headers(hs) {
|
|
5197
|
-
for (const [k, v] of
|
|
5282
|
+
for (const [k, v] of rawEntries(hs)) {
|
|
5198
5283
|
if (v == null) {
|
|
5199
5284
|
this.#headers.delete(k);
|
|
5200
5285
|
} else {
|
|
@@ -5219,11 +5304,11 @@ var fml = (function (exports) {
|
|
|
5219
5304
|
}
|
|
5220
5305
|
/**
|
|
5221
5306
|
* Add all query parameters to the request, overriding existing ones if that key already exists. Null and undefined values cause the key to be removed
|
|
5222
|
-
* @param {URLSearchParams|Record<string,string>|string[][]|string} ps
|
|
5307
|
+
* @param {URLSearchParams|Record<string,string|null|undefined>|string[][]|string} ps
|
|
5223
5308
|
* @returns {HttpRequestBuilder} this builder
|
|
5224
5309
|
*/
|
|
5225
5310
|
params(ps) {
|
|
5226
|
-
for (const [k, v] of
|
|
5311
|
+
for (const [k, v] of rawEntries(ps)) {
|
|
5227
5312
|
if (v == null) {
|
|
5228
5313
|
this.#params.delete(k);
|
|
5229
5314
|
} else {
|
|
@@ -5239,8 +5324,10 @@ var fml = (function (exports) {
|
|
|
5239
5324
|
* @returns {HttpRequestBuilder} this builder
|
|
5240
5325
|
*/
|
|
5241
5326
|
param(k, ...vs) {
|
|
5327
|
+
//overriding, as header, headers and params all do: pass every value in one
|
|
5328
|
+
//call to get a multi valued parameter
|
|
5329
|
+
this.#params.delete(k);
|
|
5242
5330
|
if (vs.length === 0 || vs[0] == null) {
|
|
5243
|
-
this.#params.delete(k);
|
|
5244
5331
|
return this;
|
|
5245
5332
|
}
|
|
5246
5333
|
for (const v of vs) {
|
|
@@ -5273,7 +5360,7 @@ var fml = (function (exports) {
|
|
|
5273
5360
|
/**
|
|
5274
5361
|
* Sets the request body as a FormData configured using the callback.
|
|
5275
5362
|
* `Content-Type: multipart/form-data` header is automatically added by fetch if not explicitly set.
|
|
5276
|
-
* @param {
|
|
5363
|
+
* @param {(c: HttpMultipartRequestCustomizer) => void} callback
|
|
5277
5364
|
*/
|
|
5278
5365
|
multipart(callback) {
|
|
5279
5366
|
const formData = new FormData();
|
|
@@ -5438,7 +5525,7 @@ var fml = (function (exports) {
|
|
|
5438
5525
|
* @returns this builder
|
|
5439
5526
|
*/
|
|
5440
5527
|
blobs(name, values) {
|
|
5441
|
-
for (
|
|
5528
|
+
for (const v of values) {
|
|
5442
5529
|
this.#formData.append(name, v);
|
|
5443
5530
|
}
|
|
5444
5531
|
return this;
|
|
@@ -5473,7 +5560,16 @@ var fml = (function (exports) {
|
|
|
5473
5560
|
}
|
|
5474
5561
|
static load(k) {
|
|
5475
5562
|
const got = localStorage.getItem(k);
|
|
5476
|
-
|
|
5563
|
+
if (got === null) {
|
|
5564
|
+
return undefined;
|
|
5565
|
+
}
|
|
5566
|
+
try {
|
|
5567
|
+
return JSON.parse(got);
|
|
5568
|
+
} catch {
|
|
5569
|
+
//not what save wrote: drop it, otherwise every later read fails the same way
|
|
5570
|
+
localStorage.removeItem(k);
|
|
5571
|
+
return undefined;
|
|
5572
|
+
}
|
|
5477
5573
|
}
|
|
5478
5574
|
static remove(k) {
|
|
5479
5575
|
localStorage.removeItem(k);
|
|
@@ -5491,7 +5587,16 @@ var fml = (function (exports) {
|
|
|
5491
5587
|
}
|
|
5492
5588
|
static load(k) {
|
|
5493
5589
|
const got = sessionStorage.getItem(k);
|
|
5494
|
-
|
|
5590
|
+
if (got === null) {
|
|
5591
|
+
return undefined;
|
|
5592
|
+
}
|
|
5593
|
+
try {
|
|
5594
|
+
return JSON.parse(got);
|
|
5595
|
+
} catch {
|
|
5596
|
+
//not what save wrote: drop it, otherwise every later read fails the same way
|
|
5597
|
+
sessionStorage.removeItem(k);
|
|
5598
|
+
return undefined;
|
|
5599
|
+
}
|
|
5495
5600
|
}
|
|
5496
5601
|
static remove(k) {
|
|
5497
5602
|
sessionStorage.removeItem(k);
|
|
@@ -5513,7 +5618,7 @@ var fml = (function (exports) {
|
|
|
5513
5618
|
return undefined;
|
|
5514
5619
|
}
|
|
5515
5620
|
if (stored.revision !== revision) {
|
|
5516
|
-
|
|
5621
|
+
LocalStorage.remove(key);
|
|
5517
5622
|
return undefined;
|
|
5518
5623
|
}
|
|
5519
5624
|
return stored.data;
|
|
@@ -5530,7 +5635,7 @@ var fml = (function (exports) {
|
|
|
5530
5635
|
return undefined;
|
|
5531
5636
|
}
|
|
5532
5637
|
if (stored.revision !== revision) {
|
|
5533
|
-
|
|
5638
|
+
SessionStorage.remove(key);
|
|
5534
5639
|
return undefined;
|
|
5535
5640
|
}
|
|
5536
5641
|
return stored.data;
|
|
@@ -5665,7 +5770,7 @@ var fml = (function (exports) {
|
|
|
5665
5770
|
*/
|
|
5666
5771
|
static debounce(timeoutMs, func, options) {
|
|
5667
5772
|
const opts = options ?? Timing.DEBOUNCE_DEFAULT;
|
|
5668
|
-
let tid = null;
|
|
5773
|
+
let tid = /** @type {number | null} */ (null);
|
|
5669
5774
|
let args = [];
|
|
5670
5775
|
let previousTimestamp = 0;
|
|
5671
5776
|
|
|
@@ -5685,8 +5790,8 @@ var fml = (function (exports) {
|
|
|
5685
5790
|
}
|
|
5686
5791
|
};
|
|
5687
5792
|
|
|
5688
|
-
const debounced =
|
|
5689
|
-
args =
|
|
5793
|
+
const debounced = (...called) => {
|
|
5794
|
+
args = called;
|
|
5690
5795
|
previousTimestamp = performance.now();
|
|
5691
5796
|
if (tid === null) {
|
|
5692
5797
|
tid = setTimeout(later, timeoutMs);
|
|
@@ -5695,7 +5800,11 @@ var fml = (function (exports) {
|
|
|
5695
5800
|
}
|
|
5696
5801
|
}
|
|
5697
5802
|
};
|
|
5698
|
-
const abort = () =>
|
|
5803
|
+
const abort = () => {
|
|
5804
|
+
clearTimeout(tid ?? undefined);
|
|
5805
|
+
tid = null;
|
|
5806
|
+
args = [];
|
|
5807
|
+
};
|
|
5699
5808
|
return [debounced, abort];
|
|
5700
5809
|
}
|
|
5701
5810
|
static THROTTLE_DEFAULT = 0;
|
|
@@ -5710,7 +5819,7 @@ var fml = (function (exports) {
|
|
|
5710
5819
|
*/
|
|
5711
5820
|
static throttle(timeoutMs, func, options) {
|
|
5712
5821
|
const opts = options ?? Timing.THROTTLE_DEFAULT;
|
|
5713
|
-
let tid = null;
|
|
5822
|
+
let tid = /** @type {number | null} */ (null);
|
|
5714
5823
|
let args = [];
|
|
5715
5824
|
let previousTimestamp = 0;
|
|
5716
5825
|
|
|
@@ -5722,13 +5831,13 @@ var fml = (function (exports) {
|
|
|
5722
5831
|
args = [];
|
|
5723
5832
|
}
|
|
5724
5833
|
};
|
|
5725
|
-
const throttled =
|
|
5834
|
+
const throttled = (...called) => {
|
|
5726
5835
|
const now = performance.now();
|
|
5727
5836
|
if (!previousTimestamp && opts & Timing.THROTTLE_NO_LEADING) {
|
|
5728
5837
|
previousTimestamp = now;
|
|
5729
5838
|
}
|
|
5730
5839
|
const remaining = previousTimestamp === 0 ? 0 : timeoutMs - (now - previousTimestamp);
|
|
5731
|
-
args =
|
|
5840
|
+
args = called;
|
|
5732
5841
|
if (remaining <= 0 || remaining > timeoutMs) {
|
|
5733
5842
|
if (tid !== null) {
|
|
5734
5843
|
clearTimeout(tid);
|
|
@@ -5743,7 +5852,11 @@ var fml = (function (exports) {
|
|
|
5743
5852
|
tid = setTimeout(later, remaining);
|
|
5744
5853
|
}
|
|
5745
5854
|
};
|
|
5746
|
-
const abort = () =>
|
|
5855
|
+
const abort = () => {
|
|
5856
|
+
clearTimeout(tid ?? undefined);
|
|
5857
|
+
tid = null;
|
|
5858
|
+
args = [];
|
|
5859
|
+
};
|
|
5747
5860
|
return [throttled, abort];
|
|
5748
5861
|
}
|
|
5749
5862
|
}
|
|
@@ -5757,7 +5870,7 @@ var fml = (function (exports) {
|
|
|
5757
5870
|
*/
|
|
5758
5871
|
static flatten(obj, prefix, stops) {
|
|
5759
5872
|
return Object.keys(obj).reduce((acc, k) => {
|
|
5760
|
-
const pre = prefix.length ? prefix
|
|
5873
|
+
const pre = prefix.length ? `${prefix}.${k}` : k;
|
|
5761
5874
|
if (!stops.has(pre) && typeof obj[k] === 'object' && obj[k] !== null) {
|
|
5762
5875
|
Object.assign(acc, Bindings.flatten(obj[k], pre, stops));
|
|
5763
5876
|
} else {
|
|
@@ -5775,7 +5888,7 @@ var fml = (function (exports) {
|
|
|
5775
5888
|
static providePath(result, path, value) {
|
|
5776
5889
|
const keys = path.split('.').map((k) => (/^[0-9]+$/.test(k) ? +k : k));
|
|
5777
5890
|
let current = result ?? {};
|
|
5778
|
-
let previous = null;
|
|
5891
|
+
let previous = /** @type {any} */ (null);
|
|
5779
5892
|
for (let i = 0; ; ++i) {
|
|
5780
5893
|
const ckey = keys[i];
|
|
5781
5894
|
const pkey = keys[i - 1];
|
|
@@ -5808,12 +5921,12 @@ var fml = (function (exports) {
|
|
|
5808
5921
|
if (!el.checked) {
|
|
5809
5922
|
return undefined;
|
|
5810
5923
|
}
|
|
5811
|
-
return el.dataset
|
|
5924
|
+
return el.dataset.fulBindType === 'boolean' ? el.value === 'true' : el.value;
|
|
5812
5925
|
}
|
|
5813
5926
|
if (el.getAttribute('type') === 'checkbox') {
|
|
5814
5927
|
return el.checked;
|
|
5815
5928
|
}
|
|
5816
|
-
if (el.dataset
|
|
5929
|
+
if (el.dataset.fulBindType === 'boolean') {
|
|
5817
5930
|
return !el.value ? null : el.value === 'true';
|
|
5818
5931
|
}
|
|
5819
5932
|
if (el.tagName === 'INPUT' || el.tagName === 'SELECT' || el.tagName === 'TEXTAREA') {
|
|
@@ -5846,12 +5959,14 @@ var fml = (function (exports) {
|
|
|
5846
5959
|
|
|
5847
5960
|
/**
|
|
5848
5961
|
*
|
|
5849
|
-
* @param {Element
|
|
5962
|
+
* @param {Element & {dataset?: any} & {checked?: boolean} & {value?: any}} el
|
|
5850
5963
|
* @returns
|
|
5851
5964
|
*/
|
|
5852
5965
|
static mutate(el, raw) {
|
|
5853
5966
|
if (el.getAttribute('type') === 'radio') {
|
|
5854
|
-
|
|
5967
|
+
//values are matched as strings, as ful-radio-group does: extract decodes
|
|
5968
|
+
//boolean radios, and payloads carry numbers where the attribute is text
|
|
5969
|
+
el.checked = raw != null && el.getAttribute('value') === String(raw);
|
|
5855
5970
|
return;
|
|
5856
5971
|
}
|
|
5857
5972
|
if (el.getAttribute('type') === 'checkbox') {
|
|
@@ -5875,7 +5990,9 @@ var fml = (function (exports) {
|
|
|
5875
5990
|
static errors(form, es, scrollOnError) {
|
|
5876
5991
|
const fieldErrors = es.filter((e) => e.type === 'FIELD_ERROR' || e.type === 'INVALID_FORMAT');
|
|
5877
5992
|
const globalErrors = es.filter((e) => e.type !== 'FIELD_ERROR' && e.type !== 'INVALID_FORMAT');
|
|
5878
|
-
form.querySelectorAll(`[name]`).forEach((el) =>
|
|
5993
|
+
form.querySelectorAll(`[name]`).forEach((el) => {
|
|
5994
|
+
el.setCustomValidity?.('');
|
|
5995
|
+
});
|
|
5879
5996
|
form.querySelectorAll('ful-errors').forEach((el) => {
|
|
5880
5997
|
el.replaceChildren();
|
|
5881
5998
|
el.setAttribute('hidden', '');
|
|
@@ -5883,12 +6000,12 @@ var fml = (function (exports) {
|
|
|
5883
6000
|
fieldErrors.forEach((e) => {
|
|
5884
6001
|
const name = e.context.replace(/\[/g, '.').replace(/\]\./g, '.').replace(/\]/g, '');
|
|
5885
6002
|
const parts = name.split('.');
|
|
5886
|
-
for (let i = parts.length; i
|
|
6003
|
+
for (let i = parts.length; i !== 0; --i) {
|
|
5887
6004
|
const prefix = parts.slice(0, i).join('.');
|
|
5888
6005
|
const suffix = parts.slice(i, parts.length).join('.');
|
|
5889
|
-
form.querySelectorAll(`[name='${CSS.escape(prefix)}']`).forEach((input) =>
|
|
5890
|
-
input.setCustomValidity?.(e.reason, suffix)
|
|
5891
|
-
);
|
|
6006
|
+
form.querySelectorAll(`[name='${CSS.escape(prefix)}']`).forEach((input) => {
|
|
6007
|
+
input.setCustomValidity?.(e.reason, suffix);
|
|
6008
|
+
});
|
|
5892
6009
|
}
|
|
5893
6010
|
});
|
|
5894
6011
|
form.querySelectorAll('ful-errors').forEach((el) => {
|
|
@@ -5898,7 +6015,7 @@ var fml = (function (exports) {
|
|
|
5898
6015
|
el.removeAttribute('hidden');
|
|
5899
6016
|
}
|
|
5900
6017
|
});
|
|
5901
|
-
if (es.length
|
|
6018
|
+
if (es.length === 0 || !scrollOnError) {
|
|
5902
6019
|
return;
|
|
5903
6020
|
}
|
|
5904
6021
|
Array.from(form.querySelectorAll(`:invalid`))
|
|
@@ -5970,7 +6087,8 @@ var fml = (function (exports) {
|
|
|
5970
6087
|
class Form extends ParsedElement {
|
|
5971
6088
|
form;
|
|
5972
6089
|
render() {
|
|
5973
|
-
const form =
|
|
6090
|
+
const form = document.createElement('form');
|
|
6091
|
+
this.form = form;
|
|
5974
6092
|
form.setAttribute('novalidate', '');
|
|
5975
6093
|
Attributes.forward('form-', this, form);
|
|
5976
6094
|
form.replaceChildren(...this.childNodes);
|
|
@@ -5993,50 +6111,53 @@ var fml = (function (exports) {
|
|
|
5993
6111
|
*/
|
|
5994
6112
|
async submit(submitter) {
|
|
5995
6113
|
this.spinner(true);
|
|
6114
|
+
//one try: building the loader and preparing the request are as much part of a
|
|
6115
|
+
//submit as sending it, and a mapper that throws is how a caller reports a
|
|
6116
|
+
//problem with the values
|
|
6117
|
+
let values;
|
|
6118
|
+
let request;
|
|
5996
6119
|
try {
|
|
5997
6120
|
const loader = registry.component(this.getAttribute('loader') ?? 'loaders:form').create(this);
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
|
|
6001
|
-
|
|
6121
|
+
values = Bindings.extractFrom(this.form, submitter);
|
|
6122
|
+
request = await loader.prepare(values, this);
|
|
6123
|
+
const se = new CustomEvent('submit', {
|
|
6124
|
+
bubbles: true,
|
|
6125
|
+
cancelable: true,
|
|
6126
|
+
detail: { submitter, values, request },
|
|
6127
|
+
});
|
|
6128
|
+
if (!this.dispatchEvent(se)) {
|
|
6129
|
+
return;
|
|
6130
|
+
}
|
|
6131
|
+
this.errors = [];
|
|
6132
|
+
const sre = new CustomEvent('submit:requested', {
|
|
6133
|
+
bubbles: true,
|
|
6134
|
+
cancelable: false,
|
|
6135
|
+
detail: { submitter, values: se.detail.values, request: se.detail.request },
|
|
6136
|
+
});
|
|
6137
|
+
let response = await AsyncEvents.fireAsync(this, sre, { mode: 'pipeline' });
|
|
6138
|
+
request = sre.detail.request;
|
|
6139
|
+
|
|
6140
|
+
response = await loader.submit(request, this, response);
|
|
6141
|
+
const mapped = await loader.transform(response, this);
|
|
6142
|
+
this.dispatchEvent(
|
|
6143
|
+
new CustomEvent('submit:success', {
|
|
6002
6144
|
bubbles: true,
|
|
6003
|
-
cancelable:
|
|
6004
|
-
detail: { submitter, values, request },
|
|
6005
|
-
})
|
|
6006
|
-
|
|
6007
|
-
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
const sre = new CustomEvent('submit:requested', {
|
|
6145
|
+
cancelable: false,
|
|
6146
|
+
detail: { submitter, values, request, response: mapped },
|
|
6147
|
+
}),
|
|
6148
|
+
);
|
|
6149
|
+
} catch (e) {
|
|
6150
|
+
this.dispatchEvent(
|
|
6151
|
+
new CustomEvent('submit:failure', {
|
|
6011
6152
|
bubbles: true,
|
|
6012
6153
|
cancelable: false,
|
|
6013
|
-
detail: { submitter, values
|
|
6014
|
-
})
|
|
6015
|
-
|
|
6016
|
-
|
|
6017
|
-
|
|
6018
|
-
response = await loader.submit(request, this, response);
|
|
6019
|
-
const mapped = await loader.transform(response, this);
|
|
6020
|
-
this.dispatchEvent(
|
|
6021
|
-
new CustomEvent('submit:success', {
|
|
6022
|
-
bubbles: true,
|
|
6023
|
-
cancelable: false,
|
|
6024
|
-
detail: { submitter, values, request, response: mapped },
|
|
6025
|
-
}),
|
|
6026
|
-
);
|
|
6027
|
-
} catch (e) {
|
|
6028
|
-
this.dispatchEvent(
|
|
6029
|
-
new CustomEvent('submit:failure', {
|
|
6030
|
-
bubbles: true,
|
|
6031
|
-
cancelable: false,
|
|
6032
|
-
detail: { submitter, values, request, exception: e },
|
|
6033
|
-
}),
|
|
6034
|
-
);
|
|
6035
|
-
if (e instanceof Failure) {
|
|
6036
|
-
this.errors = e.problems;
|
|
6037
|
-
}
|
|
6038
|
-
console.warn('failed to submit form', this, 'reason:', e);
|
|
6154
|
+
detail: { submitter, values, request, exception: e },
|
|
6155
|
+
}),
|
|
6156
|
+
);
|
|
6157
|
+
if (e instanceof Failure) {
|
|
6158
|
+
this.errors = e.problems;
|
|
6039
6159
|
}
|
|
6160
|
+
console.warn('failed to submit form', this, 'reason:', e);
|
|
6040
6161
|
} finally {
|
|
6041
6162
|
this.spinner(false);
|
|
6042
6163
|
}
|
|
@@ -6044,7 +6165,20 @@ var fml = (function (exports) {
|
|
|
6044
6165
|
reset() {
|
|
6045
6166
|
this.form.reset();
|
|
6046
6167
|
}
|
|
6168
|
+
#spinning = 0;
|
|
6047
6169
|
spinner(spin) {
|
|
6170
|
+
//submits can overlap: only the outermost one saves and restores the button states
|
|
6171
|
+
if (spin) {
|
|
6172
|
+
++this.#spinning;
|
|
6173
|
+
if (this.#spinning !== 1) {
|
|
6174
|
+
return;
|
|
6175
|
+
}
|
|
6176
|
+
} else {
|
|
6177
|
+
this.#spinning = Math.max(0, this.#spinning - 1);
|
|
6178
|
+
if (this.#spinning !== 0) {
|
|
6179
|
+
return;
|
|
6180
|
+
}
|
|
6181
|
+
}
|
|
6048
6182
|
this.querySelectorAll('ful-spinner').forEach((el) => {
|
|
6049
6183
|
const hel = /** @type HTMLElement */ (el);
|
|
6050
6184
|
hel.hidden = !spin;
|
|
@@ -6075,7 +6209,7 @@ var fml = (function (exports) {
|
|
|
6075
6209
|
}
|
|
6076
6210
|
|
|
6077
6211
|
class Input extends ParsedElement {
|
|
6078
|
-
static observed = ['value', 'readonly:presence', 'required:presence'];
|
|
6212
|
+
static observed = ['value', 'readonly:presence', 'required:presence', 'placeholder'];
|
|
6079
6213
|
static slots = true;
|
|
6080
6214
|
static template = `
|
|
6081
6215
|
<div class="form-label">
|
|
@@ -6112,12 +6246,6 @@ var fml = (function (exports) {
|
|
|
6112
6246
|
this._input = fragment.querySelector('input,textarea');
|
|
6113
6247
|
|
|
6114
6248
|
Attributes.forward('input-', this, this._input);
|
|
6115
|
-
if (!skipObservedSetup) {
|
|
6116
|
-
this.disabled = disabled;
|
|
6117
|
-
this.readonly = observed.readonly;
|
|
6118
|
-
this.required = observed.required;
|
|
6119
|
-
this.value = observed.value;
|
|
6120
|
-
}
|
|
6121
6249
|
this._input.addEventListener('keydown', (evt) => {
|
|
6122
6250
|
if (evt.key !== 'Enter' || this._type() === 'textarea') {
|
|
6123
6251
|
return;
|
|
@@ -6133,19 +6261,26 @@ var fml = (function (exports) {
|
|
|
6133
6261
|
form.requestSubmit(submitter);
|
|
6134
6262
|
});
|
|
6135
6263
|
this._input.addEventListener('input', (evt) => {
|
|
6136
|
-
const
|
|
6137
|
-
if (!
|
|
6264
|
+
const mask = this.getAttribute('mask');
|
|
6265
|
+
if (!mask) {
|
|
6138
6266
|
return;
|
|
6139
6267
|
}
|
|
6268
|
+
const strip = (v) => v.replace(new RegExp(mask, 'g'), '');
|
|
6140
6269
|
const before = evt.target.value;
|
|
6141
|
-
const after = before
|
|
6270
|
+
const after = strip(before);
|
|
6142
6271
|
if (before === after) {
|
|
6143
6272
|
return;
|
|
6144
6273
|
}
|
|
6145
6274
|
const start = evt.target.selectionStart;
|
|
6146
|
-
const offset = before.length - after.length;
|
|
6147
6275
|
evt.target.value = after;
|
|
6148
|
-
|
|
6276
|
+
if (start === null) {
|
|
6277
|
+
//email, number and the date types have no selection to restore
|
|
6278
|
+
return;
|
|
6279
|
+
}
|
|
6280
|
+
//the caret keeps its place among the characters that survived, so only the
|
|
6281
|
+
//ones stripped before it count
|
|
6282
|
+
const caret = strip(before.slice(0, start)).length;
|
|
6283
|
+
evt.target.setSelectionRange(caret, caret);
|
|
6149
6284
|
});
|
|
6150
6285
|
this._input.addEventListener('change', (evt) => {
|
|
6151
6286
|
evt.stopPropagation();
|
|
@@ -6165,6 +6300,15 @@ var fml = (function (exports) {
|
|
|
6165
6300
|
this._input.ariaDescribedByElements = [this._fieldError];
|
|
6166
6301
|
this._input.ariaLabelledByElements = [label];
|
|
6167
6302
|
this.replaceChildren(fragment);
|
|
6303
|
+
if (!skipObservedSetup) {
|
|
6304
|
+
// biome-ignore lint/complexity/noUselessThisAlias: keeps checkJs from seeing these as class fields
|
|
6305
|
+
const el = this;
|
|
6306
|
+
el.disabled = disabled;
|
|
6307
|
+
el.readonly = observed.readonly;
|
|
6308
|
+
el.required = observed.required;
|
|
6309
|
+
el.placeholder = observed.placeholder;
|
|
6310
|
+
el.value = observed.value;
|
|
6311
|
+
}
|
|
6168
6312
|
}
|
|
6169
6313
|
get value() {
|
|
6170
6314
|
const uppercase = this.hasAttribute('uppercase');
|
|
@@ -6191,6 +6335,11 @@ var fml = (function (exports) {
|
|
|
6191
6335
|
}
|
|
6192
6336
|
set disabled(d) {
|
|
6193
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
|
|
6342
|
+
Attributes.toggle(this, 'disabled', d);
|
|
6194
6343
|
}
|
|
6195
6344
|
get required() {
|
|
6196
6345
|
return this._input.getAttribute('aria-required') === 'true';
|
|
@@ -6201,6 +6350,18 @@ var fml = (function (exports) {
|
|
|
6201
6350
|
Attributes.toggle(this, 'required', d);
|
|
6202
6351
|
});
|
|
6203
6352
|
}
|
|
6353
|
+
get placeholder() {
|
|
6354
|
+
const v = this._input.getAttribute('placeholder');
|
|
6355
|
+
return v === ' ' ? null : v;
|
|
6356
|
+
}
|
|
6357
|
+
set placeholder(d) {
|
|
6358
|
+
//without a placeholder :placeholder-shown never matches, and floating labels
|
|
6359
|
+
//rely on it, so a blank one stands in for none
|
|
6360
|
+
Attributes.set(this._input, 'placeholder', d ?? ' ');
|
|
6361
|
+
this.reflect(() => {
|
|
6362
|
+
Attributes.set(this, 'placeholder', d);
|
|
6363
|
+
});
|
|
6364
|
+
}
|
|
6204
6365
|
focus(options) {
|
|
6205
6366
|
this._input.focus(options);
|
|
6206
6367
|
}
|
|
@@ -6220,23 +6381,23 @@ var fml = (function (exports) {
|
|
|
6220
6381
|
|
|
6221
6382
|
class LocalDate extends ParsedElement {
|
|
6222
6383
|
render() {
|
|
6223
|
-
const content = this.
|
|
6384
|
+
const content = this.textContent.trim();
|
|
6224
6385
|
if (content === '') {
|
|
6225
|
-
this.
|
|
6386
|
+
this.replaceChildren(this.getAttribute('default') ?? '');
|
|
6226
6387
|
return;
|
|
6227
6388
|
}
|
|
6228
6389
|
const locale = this.getAttribute('locale') ?? Intl.DateTimeFormat().resolvedOptions().locale;
|
|
6229
6390
|
const formatter = new Intl.DateTimeFormat(locale, { year: 'numeric', month: 'numeric', day: 'numeric' });
|
|
6230
6391
|
const [y, m, d] = content.split('-').map(Number);
|
|
6231
|
-
this.
|
|
6392
|
+
this.replaceChildren(formatter.format(new Date(y, m - 1, d)));
|
|
6232
6393
|
}
|
|
6233
6394
|
}
|
|
6234
6395
|
|
|
6235
6396
|
class Instant extends ParsedElement {
|
|
6236
6397
|
render() {
|
|
6237
|
-
const content = this.
|
|
6398
|
+
const content = this.textContent.trim();
|
|
6238
6399
|
if (content === '') {
|
|
6239
|
-
this.
|
|
6400
|
+
this.replaceChildren(this.getAttribute('default') ?? '');
|
|
6240
6401
|
return;
|
|
6241
6402
|
}
|
|
6242
6403
|
const locale = this.getAttribute('locale') ?? Intl.DateTimeFormat().resolvedOptions().locale;
|
|
@@ -6249,7 +6410,7 @@ var fml = (function (exports) {
|
|
|
6249
6410
|
second: 'numeric',
|
|
6250
6411
|
hour12: false,
|
|
6251
6412
|
});
|
|
6252
|
-
this.
|
|
6413
|
+
this.replaceChildren(format.format(new Date(Instant.isoToLocal(content))));
|
|
6253
6414
|
}
|
|
6254
6415
|
static isoToLocal(iso) {
|
|
6255
6416
|
//this is so sad
|
|
@@ -6262,16 +6423,17 @@ var fml = (function (exports) {
|
|
|
6262
6423
|
}
|
|
6263
6424
|
|
|
6264
6425
|
class InputLocalDate extends Input {
|
|
6265
|
-
static observed = ['value', 'readonly:presence', 'required:presence', 'min', 'max', 'step'];
|
|
6426
|
+
static observed = ['value', 'readonly:presence', 'required:presence', 'placeholder', 'min', 'max', 'step'];
|
|
6266
6427
|
_type() {
|
|
6267
6428
|
return 'date';
|
|
6268
6429
|
}
|
|
6269
6430
|
render(conf) {
|
|
6270
6431
|
const { observed } = conf;
|
|
6271
6432
|
super.render(conf);
|
|
6433
|
+
//step first: on a time input min and max are snapped to its grid
|
|
6434
|
+
this.step = observed.step;
|
|
6272
6435
|
this.min = observed.min;
|
|
6273
6436
|
this.max = observed.max;
|
|
6274
|
-
this.step = observed.step;
|
|
6275
6437
|
}
|
|
6276
6438
|
get min() {
|
|
6277
6439
|
const v = this._input.min;
|
|
@@ -6317,13 +6479,14 @@ var fml = (function (exports) {
|
|
|
6317
6479
|
case 'd':
|
|
6318
6480
|
r.setDate(r.getDate() + offset * sign);
|
|
6319
6481
|
break;
|
|
6320
|
-
case 'm':
|
|
6482
|
+
case 'm': {
|
|
6321
6483
|
const originalDay = r.getDate();
|
|
6322
6484
|
r.setMonth(r.getMonth() + offset * sign);
|
|
6323
6485
|
if (r.getDate() !== originalDay) {
|
|
6324
6486
|
r.setDate(0);
|
|
6325
6487
|
}
|
|
6326
6488
|
break;
|
|
6489
|
+
}
|
|
6327
6490
|
case 'y':
|
|
6328
6491
|
r.setFullYear(r.getFullYear() + offset * sign);
|
|
6329
6492
|
break;
|
|
@@ -6336,10 +6499,62 @@ var fml = (function (exports) {
|
|
|
6336
6499
|
_type() {
|
|
6337
6500
|
return 'time';
|
|
6338
6501
|
}
|
|
6502
|
+
get min() {
|
|
6503
|
+
const v = this._input.min;
|
|
6504
|
+
return v === '' ? null : v;
|
|
6505
|
+
}
|
|
6506
|
+
set min(v) {
|
|
6507
|
+
this._input.min = this.#fromNowOrOffset(v);
|
|
6508
|
+
}
|
|
6509
|
+
get max() {
|
|
6510
|
+
const v = this._input.max;
|
|
6511
|
+
return v === '' ? null : v;
|
|
6512
|
+
}
|
|
6513
|
+
set max(v) {
|
|
6514
|
+
this._input.max = this.#fromNowOrOffset(v);
|
|
6515
|
+
}
|
|
6516
|
+
/**
|
|
6517
|
+
* Resolves `now` and hour or minute offsets against the current time, wrapping
|
|
6518
|
+
* around midnight. `m` is minutes here, unlike the date offsets of the parent where
|
|
6519
|
+
* it is months: months mean nothing on a time. Anything else is passed through.
|
|
6520
|
+
*/
|
|
6521
|
+
#fromNowOrOffset(v) {
|
|
6522
|
+
if (!v) {
|
|
6523
|
+
return '';
|
|
6524
|
+
}
|
|
6525
|
+
const resolved = new Date();
|
|
6526
|
+
if (v !== 'now') {
|
|
6527
|
+
const re = /^([+-])(\d+)([hm])$/;
|
|
6528
|
+
const match = re.exec(v);
|
|
6529
|
+
if (!match) {
|
|
6530
|
+
return v;
|
|
6531
|
+
}
|
|
6532
|
+
const sign = match[1] === '-' ? -1 : 1;
|
|
6533
|
+
const offset = +match[2] * sign;
|
|
6534
|
+
if (match[3] === 'h') {
|
|
6535
|
+
resolved.setHours(resolved.getHours() + offset);
|
|
6536
|
+
} else {
|
|
6537
|
+
resolved.setMinutes(resolved.getMinutes() + offset);
|
|
6538
|
+
}
|
|
6539
|
+
}
|
|
6540
|
+
return InputLocalTime.#snapped(resolved, Number(this._input.step) || 60);
|
|
6541
|
+
}
|
|
6542
|
+
/**
|
|
6543
|
+
* Truncates a time to the step grid: min anchors that grid, so a bound that is not
|
|
6544
|
+
* on it makes every value on it invalid.
|
|
6545
|
+
*/
|
|
6546
|
+
static #snapped(date, stepSeconds) {
|
|
6547
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
6548
|
+
const seconds = date.getHours() * 3600 + date.getMinutes() * 60 + date.getSeconds();
|
|
6549
|
+
const snapped = Math.floor(seconds / stepSeconds) * stepSeconds;
|
|
6550
|
+
const hh = pad(Math.floor(snapped / 3600));
|
|
6551
|
+
const mm = pad(Math.floor((snapped % 3600) / 60));
|
|
6552
|
+
return stepSeconds % 60 === 0 ? `${hh}:${mm}` : `${hh}:${mm}:${pad(snapped % 60)}`;
|
|
6553
|
+
}
|
|
6339
6554
|
}
|
|
6340
6555
|
|
|
6341
6556
|
class InputInstant extends Input {
|
|
6342
|
-
static observed = ['value', 'readonly:presence', 'required:presence', 'min', 'max', 'step'];
|
|
6557
|
+
static observed = ['value', 'readonly:presence', 'required:presence', 'placeholder', 'min', 'max', 'step'];
|
|
6343
6558
|
_type() {
|
|
6344
6559
|
return 'datetime-local';
|
|
6345
6560
|
}
|
|
@@ -6384,28 +6599,28 @@ var fml = (function (exports) {
|
|
|
6384
6599
|
static l10n = {
|
|
6385
6600
|
en: {
|
|
6386
6601
|
dropzonelabel: 'Click or drop your files here',
|
|
6387
|
-
|
|
6602
|
+
unacceptablefiletype: 'Only files of type {0} are supported',
|
|
6388
6603
|
maxfilesizeexceeded: 'Maximum supported file size is {0}',
|
|
6389
6604
|
maxtotalsizeexceeded: 'Maximum supported total file size is {0}',
|
|
6390
6605
|
maxfilesexceeded: 'Maximum files count exceeded',
|
|
6391
6606
|
},
|
|
6392
6607
|
it: {
|
|
6393
6608
|
dropzonelabel: 'Clicca o trascina i file qui',
|
|
6394
|
-
|
|
6609
|
+
unacceptablefiletype: 'Solo i file di tipo {0} sono supportati',
|
|
6395
6610
|
maxfilesizeexceeded: 'La dimensione massima di un file è di {0}',
|
|
6396
6611
|
maxtotalsizeexceeded: 'La dimensione massima complessiva dei file è di {0}',
|
|
6397
6612
|
maxfilesexceeded: 'Numero massimo di file superato',
|
|
6398
6613
|
},
|
|
6399
6614
|
es: {
|
|
6400
6615
|
dropzonelabel: 'Haz clic o arrastra tus archivos aquí',
|
|
6401
|
-
|
|
6616
|
+
unacceptablefiletype: 'Solo se admiten archivos de tipo {0}',
|
|
6402
6617
|
maxfilesizeexceeded: 'El tamaño máximo de archivo admitido es {0}',
|
|
6403
6618
|
maxtotalsizeexceeded: 'El tamaño total máximo admitido es {0}',
|
|
6404
6619
|
maxfilesexceeded: 'Se ha superado el número máximo de archivos',
|
|
6405
6620
|
},
|
|
6406
6621
|
fr: {
|
|
6407
6622
|
dropzonelabel: 'Cliquez ou déposez vos fichiers ici',
|
|
6408
|
-
|
|
6623
|
+
unacceptablefiletype: 'Seuls les fichiers de type {0} sont pris en charge',
|
|
6409
6624
|
maxfilesizeexceeded: 'La taille maximale de fichier prise en charge est {0}',
|
|
6410
6625
|
maxtotalsizeexceeded: 'La taille totale maximale prise en charge est {0}',
|
|
6411
6626
|
maxfilesexceeded: 'Nombre maximal de fichiers dépassé',
|
|
@@ -6415,6 +6630,7 @@ var fml = (function (exports) {
|
|
|
6415
6630
|
'value',
|
|
6416
6631
|
'readonly:presence',
|
|
6417
6632
|
'required:presence',
|
|
6633
|
+
'placeholder',
|
|
6418
6634
|
'accept:csv',
|
|
6419
6635
|
'multiple:presence',
|
|
6420
6636
|
'itemlist:presence',
|
|
@@ -6464,7 +6680,7 @@ var fml = (function (exports) {
|
|
|
6464
6680
|
};
|
|
6465
6681
|
render(conf) {
|
|
6466
6682
|
const { observed } = conf;
|
|
6467
|
-
super.render(conf);
|
|
6683
|
+
super.render({ ...conf, skipObservedSetup: true });
|
|
6468
6684
|
this.#items = this.querySelector('ful-item-list');
|
|
6469
6685
|
this.#dropzone = this.querySelector('[data-ref=dropzone]');
|
|
6470
6686
|
this.#warnings = this.querySelector('ful-field-warnings');
|
|
@@ -6475,6 +6691,12 @@ var fml = (function (exports) {
|
|
|
6475
6691
|
this.maxfiles = observed.maxfiles;
|
|
6476
6692
|
this.maxfilesize = observed.maxfilesize;
|
|
6477
6693
|
this.maxtotalsize = observed.maxtotalsize;
|
|
6694
|
+
|
|
6695
|
+
this.disabled = conf.disabled;
|
|
6696
|
+
this.readonly = observed.readonly;
|
|
6697
|
+
this.required = observed.required;
|
|
6698
|
+
this.placeholder = observed.placeholder;
|
|
6699
|
+
this.value = observed.value;
|
|
6478
6700
|
this.#warnings.addEventListener('animationend', (e) => {
|
|
6479
6701
|
e.target.remove();
|
|
6480
6702
|
});
|
|
@@ -6482,11 +6704,17 @@ var fml = (function (exports) {
|
|
|
6482
6704
|
if (!e.target.closest('button')) {
|
|
6483
6705
|
return;
|
|
6484
6706
|
}
|
|
6485
|
-
const
|
|
6707
|
+
const idx = [...this.#items.children].indexOf(e.target.closest('ful-item'));
|
|
6708
|
+
if (idx === -1) {
|
|
6709
|
+
return;
|
|
6710
|
+
}
|
|
6486
6711
|
const dt = new DataTransfer();
|
|
6487
|
-
[...this.files]
|
|
6712
|
+
[...this.files]
|
|
6713
|
+
.filter((f, i) => i !== idx)
|
|
6714
|
+
.forEach((f) => {
|
|
6715
|
+
dt.items.add(f);
|
|
6716
|
+
});
|
|
6488
6717
|
this.files = dt.files;
|
|
6489
|
-
this.#update();
|
|
6490
6718
|
});
|
|
6491
6719
|
this.#dropzone.addEventListener('click', (e) => {
|
|
6492
6720
|
this.querySelector('input')?.click();
|
|
@@ -6497,10 +6725,15 @@ var fml = (function (exports) {
|
|
|
6497
6725
|
});
|
|
6498
6726
|
this.#dropzone.addEventListener('drop', (e) => {
|
|
6499
6727
|
e.preventDefault();
|
|
6728
|
+
const dropped = [...e.dataTransfer.items].filter((i) => i.kind === 'file');
|
|
6729
|
+
if (dropped.length === 0) {
|
|
6730
|
+
return;
|
|
6731
|
+
}
|
|
6500
6732
|
const dt = new DataTransfer();
|
|
6501
|
-
|
|
6733
|
+
dropped.forEach((i) => {
|
|
6734
|
+
dt.items.add(i.getAsFile());
|
|
6735
|
+
});
|
|
6502
6736
|
this.files = dt.files;
|
|
6503
|
-
this.#update();
|
|
6504
6737
|
});
|
|
6505
6738
|
this._input.addEventListener('change', (e) => {
|
|
6506
6739
|
this.#update();
|
|
@@ -6515,6 +6748,7 @@ var fml = (function (exports) {
|
|
|
6515
6748
|
}
|
|
6516
6749
|
#update() {
|
|
6517
6750
|
this.setCustomValidity();
|
|
6751
|
+
this.#warnings.replaceChildren();
|
|
6518
6752
|
this.#ensureAcceptable();
|
|
6519
6753
|
this.#ensureFileSizes();
|
|
6520
6754
|
this.#ensureTotalSize();
|
|
@@ -6525,7 +6759,7 @@ var fml = (function (exports) {
|
|
|
6525
6759
|
.renderTo(this.#items);
|
|
6526
6760
|
}
|
|
6527
6761
|
warning(key, args) {
|
|
6528
|
-
this.template('warning').withOverlay({ key, args }).
|
|
6762
|
+
this.template('warning').withOverlay({ key, args }).appendTo(this.#warnings);
|
|
6529
6763
|
}
|
|
6530
6764
|
#ensureAcceptable() {
|
|
6531
6765
|
if (!this.#accept.length) {
|
|
@@ -6538,10 +6772,14 @@ var fml = (function (exports) {
|
|
|
6538
6772
|
if (unacceptable.length === 0) {
|
|
6539
6773
|
return;
|
|
6540
6774
|
}
|
|
6541
|
-
this.warning('
|
|
6775
|
+
this.warning('unacceptablefiletype', this.#accept.join(', '));
|
|
6542
6776
|
const dt = new DataTransfer();
|
|
6543
|
-
[...this.files]
|
|
6544
|
-
|
|
6777
|
+
[...this.files]
|
|
6778
|
+
.filter((f) => !unacceptable.includes(f))
|
|
6779
|
+
.forEach((f) => {
|
|
6780
|
+
dt.items.add(f);
|
|
6781
|
+
});
|
|
6782
|
+
this._input.files = dt.files;
|
|
6545
6783
|
}
|
|
6546
6784
|
#ensureFilesCount() {
|
|
6547
6785
|
if (this.#maxfiles === null) {
|
|
@@ -6551,8 +6789,7 @@ var fml = (function (exports) {
|
|
|
6551
6789
|
return;
|
|
6552
6790
|
}
|
|
6553
6791
|
this.warning('maxfilesexceeded');
|
|
6554
|
-
|
|
6555
|
-
this.files = dt.files;
|
|
6792
|
+
this._input.files = new DataTransfer().files;
|
|
6556
6793
|
}
|
|
6557
6794
|
|
|
6558
6795
|
#ensureFileSizes() {
|
|
@@ -6565,8 +6802,12 @@ var fml = (function (exports) {
|
|
|
6565
6802
|
}
|
|
6566
6803
|
this.warning('maxfilesizeexceeded', this.#formatByteSize(this.#maxfilesize));
|
|
6567
6804
|
const dt = new DataTransfer();
|
|
6568
|
-
[...this.files]
|
|
6569
|
-
|
|
6805
|
+
[...this.files]
|
|
6806
|
+
.filter((f) => !oversized.includes(f))
|
|
6807
|
+
.forEach((f) => {
|
|
6808
|
+
dt.items.add(f);
|
|
6809
|
+
});
|
|
6810
|
+
this._input.files = dt.files;
|
|
6570
6811
|
}
|
|
6571
6812
|
#ensureTotalSize() {
|
|
6572
6813
|
if (this.#maxtotalsize === null) {
|
|
@@ -6577,7 +6818,7 @@ var fml = (function (exports) {
|
|
|
6577
6818
|
return;
|
|
6578
6819
|
}
|
|
6579
6820
|
this.warning('maxtotalsizeexceeded', this.#formatByteSize(this.#maxtotalsize));
|
|
6580
|
-
this.files = new DataTransfer().files;
|
|
6821
|
+
this._input.files = new DataTransfer().files;
|
|
6581
6822
|
}
|
|
6582
6823
|
get accept() {
|
|
6583
6824
|
return this.#accept;
|
|
@@ -6603,6 +6844,7 @@ var fml = (function (exports) {
|
|
|
6603
6844
|
}
|
|
6604
6845
|
set files(vs) {
|
|
6605
6846
|
this._input.files = vs;
|
|
6847
|
+
this.#update();
|
|
6606
6848
|
}
|
|
6607
6849
|
get file() {
|
|
6608
6850
|
return this.files[0] ?? null;
|
|
@@ -6623,7 +6865,6 @@ var fml = (function (exports) {
|
|
|
6623
6865
|
return;
|
|
6624
6866
|
}
|
|
6625
6867
|
this.files = new DataTransfer().files;
|
|
6626
|
-
this.#update();
|
|
6627
6868
|
}
|
|
6628
6869
|
get totalsize() {
|
|
6629
6870
|
return Array.from(this.files).reduce((a, f) => a + f.size, 0);
|
|
@@ -6790,7 +7031,7 @@ var fml = (function (exports) {
|
|
|
6790
7031
|
const http = registry.component('http-client');
|
|
6791
7032
|
const responseMapper = SelectLoader.#responseMapperFrom(el);
|
|
6792
7033
|
|
|
6793
|
-
if ('chunked'
|
|
7034
|
+
if ('chunked' === el.getAttribute('mode')) {
|
|
6794
7035
|
return new PartialRemoteLoader({
|
|
6795
7036
|
http,
|
|
6796
7037
|
url: el.getAttribute('src'),
|
|
@@ -6866,8 +7107,14 @@ var fml = (function (exports) {
|
|
|
6866
7107
|
});
|
|
6867
7108
|
this.replaceChildren(fragment);
|
|
6868
7109
|
}
|
|
7110
|
+
#selected() {
|
|
7111
|
+
return this.#menu?.querySelector('[selected]') ?? this.#menu?.firstElementChild ?? null;
|
|
7112
|
+
}
|
|
6869
7113
|
acceptSelection() {
|
|
6870
|
-
const selected = this.#
|
|
7114
|
+
const selected = this.#selected();
|
|
7115
|
+
if (!selected) {
|
|
7116
|
+
return;
|
|
7117
|
+
}
|
|
6871
7118
|
this.#change(selected);
|
|
6872
7119
|
}
|
|
6873
7120
|
update(values) {
|
|
@@ -6903,6 +7150,9 @@ var fml = (function (exports) {
|
|
|
6903
7150
|
try {
|
|
6904
7151
|
const data = await loader();
|
|
6905
7152
|
this.update(data);
|
|
7153
|
+
} catch (e) {
|
|
7154
|
+
this.hide();
|
|
7155
|
+
throw e;
|
|
6906
7156
|
} finally {
|
|
6907
7157
|
this.#spinner.setAttribute('hidden', '');
|
|
6908
7158
|
this.#menu.removeAttribute('hidden');
|
|
@@ -6910,9 +7160,9 @@ var fml = (function (exports) {
|
|
|
6910
7160
|
}
|
|
6911
7161
|
async moveOrShow(forward, loader) {
|
|
6912
7162
|
if (this.shown) {
|
|
6913
|
-
const selected = this.#
|
|
6914
|
-
const candidate = selected[`${forward ? 'next' : 'previous'}ElementSibling`];
|
|
6915
|
-
if (candidate) {
|
|
7163
|
+
const selected = this.#selected();
|
|
7164
|
+
const candidate = selected?.[`${forward ? 'next' : 'previous'}ElementSibling`];
|
|
7165
|
+
if (selected && candidate) {
|
|
6916
7166
|
selected.removeAttribute('selected');
|
|
6917
7167
|
candidate.setAttribute('selected', '');
|
|
6918
7168
|
candidate.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
|
|
@@ -6965,6 +7215,7 @@ var fml = (function (exports) {
|
|
|
6965
7215
|
#multiple;
|
|
6966
7216
|
#fieldError;
|
|
6967
7217
|
#values = new Map();
|
|
7218
|
+
#token = 0;
|
|
6968
7219
|
constructor() {
|
|
6969
7220
|
super();
|
|
6970
7221
|
this.internals = this.attachInternals();
|
|
@@ -6977,7 +7228,11 @@ var fml = (function (exports) {
|
|
|
6977
7228
|
.create(this, { options: slots.options });
|
|
6978
7229
|
|
|
6979
7230
|
this.#multiple = this.hasAttribute('multiple');
|
|
6980
|
-
|
|
7231
|
+
try {
|
|
7232
|
+
await this.#loader.prefetch?.();
|
|
7233
|
+
} catch (/** @type any */ e) {
|
|
7234
|
+
console.warn('failed to prefetch select options', this, 'reason:', e);
|
|
7235
|
+
}
|
|
6981
7236
|
const fragment = this.template().withOverlay({ slots, name }).render();
|
|
6982
7237
|
this.#input = fragment.querySelector('input');
|
|
6983
7238
|
this.#items = fragment.querySelector('ful-item-list');
|
|
@@ -6996,11 +7251,9 @@ var fml = (function (exports) {
|
|
|
6996
7251
|
this.#fieldError = fragment.querySelector('ful-field-error');
|
|
6997
7252
|
this.#input.ariaDescribedByElements = [this.#fieldError];
|
|
6998
7253
|
this.#input.ariaLabelledByElements = [label];
|
|
6999
|
-
|
|
7000
|
-
const self = this;
|
|
7001
7254
|
const [dload, abortdload] = Timing.throttle(400, () => {
|
|
7002
|
-
|
|
7003
|
-
|
|
7255
|
+
this.#input.setAttribute('aria-expanded', 'true');
|
|
7256
|
+
this.#ddmenu.show(() => this.#loader.load(this.#input.value));
|
|
7004
7257
|
});
|
|
7005
7258
|
this.addEventListener('click', (/** @type any */ e) => {
|
|
7006
7259
|
if (e.target.matches('input')) {
|
|
@@ -7067,13 +7320,13 @@ var fml = (function (exports) {
|
|
|
7067
7320
|
case 'ArrowUp': {
|
|
7068
7321
|
e.preventDefault();
|
|
7069
7322
|
this.#input.setAttribute('aria-expanded', 'true');
|
|
7070
|
-
this.#ddmenu.moveOrShow(false, () =>
|
|
7323
|
+
this.#ddmenu.moveOrShow(false, () => this.#loader.load(this.#input.value));
|
|
7071
7324
|
break;
|
|
7072
7325
|
}
|
|
7073
7326
|
case 'ArrowDown': {
|
|
7074
7327
|
e.preventDefault();
|
|
7075
7328
|
this.#input.setAttribute('aria-expanded', 'true');
|
|
7076
|
-
this.#ddmenu.moveOrShow(true, () =>
|
|
7329
|
+
this.#ddmenu.moveOrShow(true, () => this.#loader.load(this.#input.value));
|
|
7077
7330
|
break;
|
|
7078
7331
|
}
|
|
7079
7332
|
case 'Escape': {
|
|
@@ -7082,6 +7335,12 @@ var fml = (function (exports) {
|
|
|
7082
7335
|
break;
|
|
7083
7336
|
}
|
|
7084
7337
|
case 'Enter': {
|
|
7338
|
+
if (!this.#ddmenu.shown) {
|
|
7339
|
+
//nothing to accept: submit the form as ful-input does. the inner
|
|
7340
|
+
//input carries form="" so it never submits one on its own
|
|
7341
|
+
this.#requestSubmit();
|
|
7342
|
+
return;
|
|
7343
|
+
}
|
|
7085
7344
|
e.preventDefault();
|
|
7086
7345
|
this.#input.setAttribute('aria-expanded', 'false');
|
|
7087
7346
|
this.#ddmenu.acceptSelection();
|
|
@@ -7130,6 +7389,16 @@ var fml = (function (exports) {
|
|
|
7130
7389
|
async withLoader(fn) {
|
|
7131
7390
|
return await fn(this.#loader);
|
|
7132
7391
|
}
|
|
7392
|
+
#requestSubmit() {
|
|
7393
|
+
const form = this.internals.form;
|
|
7394
|
+
if (!form) {
|
|
7395
|
+
return;
|
|
7396
|
+
}
|
|
7397
|
+
const candidates = /** @type [HTMLButtonElement|HTMLInputElement] */ (
|
|
7398
|
+
Array.from(form.querySelectorAll('button:not(:disabled), input:not(:disabled)'))
|
|
7399
|
+
);
|
|
7400
|
+
form.requestSubmit(candidates.find((el) => el.type === 'submit'));
|
|
7401
|
+
}
|
|
7133
7402
|
#changed() {
|
|
7134
7403
|
const selection = [...this.#values.entries()].map((e) => ({
|
|
7135
7404
|
key: e[0],
|
|
@@ -7159,16 +7428,43 @@ var fml = (function (exports) {
|
|
|
7159
7428
|
this.template('items').withOverlay({ entries: this.#values.entries() }).renderTo(this.#items);
|
|
7160
7429
|
}
|
|
7161
7430
|
set value(vs) {
|
|
7162
|
-
|
|
7163
|
-
|
|
7164
|
-
|
|
7431
|
+
//the csvm mapper yields [] for a missing multiple value, an empty string is
|
|
7432
|
+
//left alone: it is a usable key for an <option value="">
|
|
7433
|
+
const keys = vs == null ? [] : Array.isArray(vs) ? vs : [vs];
|
|
7434
|
+
//the keys are known synchronously and are all `value` reads, so they are applied
|
|
7435
|
+
//now: only the labels need the loader, until then a key stands in for its own
|
|
7436
|
+
this.#values = new Map(keys.map((k) => [k, [k]]));
|
|
7437
|
+
this.#syncBadges();
|
|
7438
|
+
const token = ++this.#token;
|
|
7439
|
+
if (keys.length === 0) {
|
|
7165
7440
|
return;
|
|
7166
7441
|
}
|
|
7167
|
-
(
|
|
7168
|
-
|
|
7169
|
-
|
|
7170
|
-
|
|
7171
|
-
|
|
7442
|
+
this.#resolve(keys, token);
|
|
7443
|
+
}
|
|
7444
|
+
/**
|
|
7445
|
+
* Resolves the labels of the assigned keys. A failed lookup is left to reject so
|
|
7446
|
+
* that it is reported like any other failure: the keys stay applied either way.
|
|
7447
|
+
*/
|
|
7448
|
+
async #resolve(keys, token) {
|
|
7449
|
+
const entries = await this.#loader.exact(...keys);
|
|
7450
|
+
if (token !== this.#token) {
|
|
7451
|
+
//a newer assignment has been made in the meantime
|
|
7452
|
+
return;
|
|
7453
|
+
}
|
|
7454
|
+
//label the keys that are still selected: a removal made while the lookup was in
|
|
7455
|
+
//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)]));
|
|
7457
|
+
for (const key of keys) {
|
|
7458
|
+
if (!this.#values.has(key)) {
|
|
7459
|
+
continue;
|
|
7460
|
+
}
|
|
7461
|
+
if (resolved.has(key)) {
|
|
7462
|
+
this.#values.set(key, resolved.get(key));
|
|
7463
|
+
} else {
|
|
7464
|
+
this.#values.delete(key);
|
|
7465
|
+
}
|
|
7466
|
+
}
|
|
7467
|
+
this.#syncBadges();
|
|
7172
7468
|
}
|
|
7173
7469
|
get value() {
|
|
7174
7470
|
if (this.#multiple) {
|
|
@@ -7187,6 +7483,11 @@ var fml = (function (exports) {
|
|
|
7187
7483
|
}
|
|
7188
7484
|
set disabled(d) {
|
|
7189
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
|
|
7490
|
+
Attributes.toggle(this, 'disabled', d);
|
|
7190
7491
|
}
|
|
7191
7492
|
get readonly() {
|
|
7192
7493
|
return this.#input.readOnly;
|
|
@@ -7292,7 +7593,9 @@ var fml = (function (exports) {
|
|
|
7292
7593
|
return [input, label];
|
|
7293
7594
|
});
|
|
7294
7595
|
|
|
7295
|
-
radioEls.forEach((el) =>
|
|
7596
|
+
radioEls.forEach((el) => {
|
|
7597
|
+
el.remove();
|
|
7598
|
+
});
|
|
7296
7599
|
this.template().withOverlay({ name, slots, inputsAndLabels }).renderTo(this);
|
|
7297
7600
|
this.#fieldset = this.firstElementChild;
|
|
7298
7601
|
this.disabled = disabled;
|
|
@@ -7336,6 +7639,11 @@ var fml = (function (exports) {
|
|
|
7336
7639
|
}
|
|
7337
7640
|
set disabled(d) {
|
|
7338
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
|
|
7646
|
+
Attributes.toggle(this, 'disabled', d);
|
|
7339
7647
|
}
|
|
7340
7648
|
get required() {
|
|
7341
7649
|
return this.#fieldset.getAttribute('aria-required') === 'true';
|
|
@@ -7385,7 +7693,7 @@ var fml = (function (exports) {
|
|
|
7385
7693
|
this.internals.role = 'presentation';
|
|
7386
7694
|
}
|
|
7387
7695
|
render({ slots, observed, disabled }) {
|
|
7388
|
-
const isSwitch = this.getAttribute('type')
|
|
7696
|
+
const isSwitch = this.getAttribute('type') === 'switch';
|
|
7389
7697
|
const klass = isSwitch ? 'form-check form-switch' : 'form-check';
|
|
7390
7698
|
const fragment = this.template().withOverlay({ slots, klass, isSwitch }).render();
|
|
7391
7699
|
this.#container = fragment.firstElementChild;
|
|
@@ -7449,6 +7757,11 @@ var fml = (function (exports) {
|
|
|
7449
7757
|
}
|
|
7450
7758
|
set disabled(d) {
|
|
7451
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
|
|
7764
|
+
Attributes.toggle(this, 'disabled', d);
|
|
7452
7765
|
}
|
|
7453
7766
|
get required() {
|
|
7454
7767
|
return this.#input.getAttribute('aria-required') === 'true';
|
|
@@ -7489,9 +7802,10 @@ var fml = (function (exports) {
|
|
|
7489
7802
|
class SortButton extends ParsedElement {
|
|
7490
7803
|
static observed = ['order'];
|
|
7491
7804
|
#order;
|
|
7492
|
-
render() {
|
|
7805
|
+
render({ observed }) {
|
|
7493
7806
|
const sorter = this.getAttribute('sorter');
|
|
7494
7807
|
const orders = ['asc', 'desc', null];
|
|
7808
|
+
this.order = observed.order;
|
|
7495
7809
|
this.addEventListener('click', () => {
|
|
7496
7810
|
const nextOrder = orders[(orders.indexOf(this.order) + 1) % 3];
|
|
7497
7811
|
this.dispatchEvent(
|
|
@@ -7581,10 +7895,12 @@ var fml = (function (exports) {
|
|
|
7581
7895
|
#total = 0;
|
|
7582
7896
|
#current = 0;
|
|
7583
7897
|
render({ observed }) {
|
|
7584
|
-
this.
|
|
7898
|
+
this.total = observed.total ?? 0;
|
|
7899
|
+
this.current = observed.current ?? 0;
|
|
7585
7900
|
this.addEventListener('click', (/** @type any */ evt) => {
|
|
7586
7901
|
const el = evt.target.closest('a');
|
|
7587
|
-
if (!el) {
|
|
7902
|
+
if (!el || el.classList.contains('disabled')) {
|
|
7903
|
+
//a disabled link leads nowhere: the page it would ask for does not exist
|
|
7588
7904
|
return;
|
|
7589
7905
|
}
|
|
7590
7906
|
this.dispatchEvent(
|
|
@@ -7600,25 +7916,20 @@ var fml = (function (exports) {
|
|
|
7600
7916
|
}
|
|
7601
7917
|
update(current, total) {
|
|
7602
7918
|
const maxRender = Number(this.getAttribute('pages') ?? '5');
|
|
7603
|
-
const
|
|
7919
|
+
const hasPrev = current > 0;
|
|
7920
|
+
const hasNext = current + 1 < total;
|
|
7921
|
+
//a disabled arrow carries no page: there is nothing valid for it to point at
|
|
7922
|
+
const prev = { index: hasPrev ? current - 1 : null, enabled: hasPrev };
|
|
7604
7923
|
const curr = { index: current, label: current + 1 };
|
|
7605
|
-
const next = { index:
|
|
7606
|
-
|
|
7607
|
-
|
|
7608
|
-
|
|
7609
|
-
|
|
7610
|
-
|
|
7611
|
-
|
|
7612
|
-
|
|
7613
|
-
|
|
7614
|
-
if (p >= 0) {
|
|
7615
|
-
pages.unshift({ index: p, label: p + 1 });
|
|
7616
|
-
}
|
|
7617
|
-
const n = mid + offset;
|
|
7618
|
-
if (n < total) {
|
|
7619
|
-
pages.push({ index: n, label: n + 1 });
|
|
7620
|
-
}
|
|
7621
|
-
}
|
|
7924
|
+
const next = { index: hasNext ? current + 1 : null, enabled: hasNext };
|
|
7925
|
+
//the window holds at most maxRender pages, centered on the current one and slid
|
|
7926
|
+
//back towards the end so it stays full on the last pages
|
|
7927
|
+
const rendered = Math.max(1, Math.min(maxRender, total));
|
|
7928
|
+
const first = Math.max(0, Math.min(current - Math.floor((rendered - 1) / 2), total - rendered));
|
|
7929
|
+
const pages = Array.from({ length: rendered }, (_, offset) => ({
|
|
7930
|
+
index: first + offset,
|
|
7931
|
+
label: first + offset + 1,
|
|
7932
|
+
}));
|
|
7622
7933
|
this.template().withOverlay({ total, prev, curr, next, pages }).renderTo(this);
|
|
7623
7934
|
}
|
|
7624
7935
|
get total() {
|
|
@@ -7645,9 +7956,10 @@ var fml = (function (exports) {
|
|
|
7645
7956
|
|
|
7646
7957
|
class TableSchemaParser {
|
|
7647
7958
|
static parse(nodeOrFragment, template) {
|
|
7648
|
-
|
|
7959
|
+
//nodeOrFragment is undefined when the slot is missing altogether
|
|
7960
|
+
const schema = nodeOrFragment ? Nodes.queryChildren(nodeOrFragment, 'schema') : null;
|
|
7649
7961
|
if (!schema) {
|
|
7650
|
-
throw new Error(
|
|
7962
|
+
throw new Error('missing expected <schema>: ful-table needs a <template slot="schema"> holding one');
|
|
7651
7963
|
}
|
|
7652
7964
|
const headersTr = document.createElement('tr');
|
|
7653
7965
|
const rowsTr = document.createElement('tr');
|
|
@@ -7866,9 +8178,10 @@ var fml = (function (exports) {
|
|
|
7866
8178
|
this.#noAutoload = table.querySelector(':scope > tbody[data-ref=initial]');
|
|
7867
8179
|
this.#feedback = table.querySelector(':scope > tbody[data-ref=feedback]');
|
|
7868
8180
|
this.#paginator = Nodes.queryChildren(fragment, 'ful-pagination');
|
|
7869
|
-
this.#sorters = table.querySelectorAll(':scope > thead ful-sorter') ?? [];
|
|
7870
8181
|
this.replaceChildren(fragment);
|
|
7871
|
-
|
|
8182
|
+
const thead = /** @type HTMLTableSectionElement */ (this.querySelector('thead'));
|
|
8183
|
+
schema.headersTemplate.renderTo(thead);
|
|
8184
|
+
this.#sorters = thead.querySelectorAll('ful-sorter');
|
|
7872
8185
|
await Rendering.waitForChildren(this);
|
|
7873
8186
|
|
|
7874
8187
|
const maybeForm = /** @type any */ (Nodes.queryChildren(this, 'ful-form'));
|
|
@@ -7903,11 +8216,16 @@ var fml = (function (exports) {
|
|
|
7903
8216
|
this.addEventListener('sort-requested', async (/** @type any */ e) => {
|
|
7904
8217
|
const sortRequest = e.detail.value.order ? e.detail.value : null;
|
|
7905
8218
|
await this.load(this.#latestRequest.pageRequest, sortRequest, this.#latestRequest.filterRequest);
|
|
7906
|
-
this.#sorters.forEach((s) =>
|
|
8219
|
+
this.#sorters.forEach((s) => {
|
|
8220
|
+
s.order = null;
|
|
8221
|
+
});
|
|
7907
8222
|
e.target.order = e.detail.value.order;
|
|
7908
8223
|
});
|
|
7909
8224
|
if (this.hasAttribute('autoload')) {
|
|
7910
|
-
|
|
8225
|
+
//not awaited: the first load must not hold up the upgrade, and a loader that
|
|
8226
|
+
//fails or never answers must not keep ftl:ready from firing for the page.
|
|
8227
|
+
//load renders its own error state and lets the failure reject, so it is reported
|
|
8228
|
+
this.reload();
|
|
7911
8229
|
}
|
|
7912
8230
|
}
|
|
7913
8231
|
|
|
@@ -7920,7 +8238,7 @@ var fml = (function (exports) {
|
|
|
7920
8238
|
}
|
|
7921
8239
|
async load(pageRequest, sortRequest, filterRequest) {
|
|
7922
8240
|
this.#body.replaceChildren();
|
|
7923
|
-
this.#loading.removeAttribute('hidden'
|
|
8241
|
+
this.#loading.removeAttribute('hidden');
|
|
7924
8242
|
this.#feedback.setAttribute('hidden', '');
|
|
7925
8243
|
this.#noAutoload.setAttribute('hidden', '');
|
|
7926
8244
|
try {
|
|
@@ -7929,7 +8247,7 @@ var fml = (function (exports) {
|
|
|
7929
8247
|
this.#update(pageRequest, sortRequest, filterRequest, pageResponse);
|
|
7930
8248
|
} catch (/** @type any */ error) {
|
|
7931
8249
|
this.#loading.setAttribute('hidden', '');
|
|
7932
|
-
this.#feedback.removeAttribute('hidden'
|
|
8250
|
+
this.#feedback.removeAttribute('hidden');
|
|
7933
8251
|
if (!error.problems) {
|
|
7934
8252
|
this.#feedback.querySelector('[data-ref=feedback-error]').textContent = error;
|
|
7935
8253
|
} else {
|
|
@@ -7971,7 +8289,7 @@ var fml = (function (exports) {
|
|
|
7971
8289
|
}
|
|
7972
8290
|
|
|
7973
8291
|
class InstantFilter extends Input {
|
|
7974
|
-
static observed = ['value:json', 'readonly:presence', 'required:presence'];
|
|
8292
|
+
static observed = ['value:json', 'readonly:presence', 'required:presence', 'placeholder'];
|
|
7975
8293
|
static template = `
|
|
7976
8294
|
<div class="form-label">
|
|
7977
8295
|
<label>{{{{ slots.default }}}}</label>
|
|
@@ -8005,10 +8323,16 @@ var fml = (function (exports) {
|
|
|
8005
8323
|
this.#operator = this.querySelector('[data-ref=operator]');
|
|
8006
8324
|
this.#value1 = this.querySelector('[data-ref=value1]');
|
|
8007
8325
|
this.#value2 = this.querySelector('[data-ref=value2]');
|
|
8326
|
+
//Input.render only re-dispatches changes coming from the first operand
|
|
8327
|
+
this.#value2.addEventListener('change', (evt) => {
|
|
8328
|
+
evt.stopPropagation();
|
|
8329
|
+
this.#notifyChange();
|
|
8330
|
+
});
|
|
8008
8331
|
|
|
8009
8332
|
this.disabled = conf.disabled;
|
|
8010
8333
|
this.readonly = conf.observed.readonly;
|
|
8011
8334
|
this.required = conf.observed.required;
|
|
8335
|
+
this.placeholder = conf.observed.placeholder;
|
|
8012
8336
|
this.value = conf.observed.value;
|
|
8013
8337
|
|
|
8014
8338
|
this.addEventListener('click', (evt) => {
|
|
@@ -8018,9 +8342,13 @@ var fml = (function (exports) {
|
|
|
8018
8342
|
}
|
|
8019
8343
|
const btn = /** @type HTMLButtonElement */ (target.closest('ul')?.previousElementSibling);
|
|
8020
8344
|
const value = /** @type String */ (target.getAttribute('value'));
|
|
8345
|
+
const previous = btn.getAttribute('value');
|
|
8021
8346
|
Attributes.toggle(this.#value2, 'hidden', value !== 'BETWEEN');
|
|
8022
8347
|
btn.setAttribute('value', value);
|
|
8023
8348
|
btn.innerHTML = target.innerHTML;
|
|
8349
|
+
if (previous !== value) {
|
|
8350
|
+
this.#notifyChange();
|
|
8351
|
+
}
|
|
8024
8352
|
});
|
|
8025
8353
|
}
|
|
8026
8354
|
|
|
@@ -8036,14 +8364,40 @@ var fml = (function (exports) {
|
|
|
8036
8364
|
return;
|
|
8037
8365
|
}
|
|
8038
8366
|
const [operator, ...values] = v;
|
|
8039
|
-
this.#
|
|
8367
|
+
this.#showOperator(operator);
|
|
8040
8368
|
this.#value1.value = values[0] ? Instant.isoToLocal(values[0]) : values[0];
|
|
8041
8369
|
this.#value2.value = values[1] ? Instant.isoToLocal(values[1]) : values[1];
|
|
8042
8370
|
}
|
|
8371
|
+
#showOperator(operator) {
|
|
8372
|
+
this.#operator.setAttribute('value', operator);
|
|
8373
|
+
const items = Array.from(this.#operator.nextElementSibling?.querySelectorAll('li > a[value]') ?? []);
|
|
8374
|
+
const item = items.find((a) => a.getAttribute('value') === operator);
|
|
8375
|
+
if (item) {
|
|
8376
|
+
this.#operator.innerHTML = item.innerHTML;
|
|
8377
|
+
}
|
|
8378
|
+
Attributes.toggle(this.#value2, 'hidden', operator !== 'BETWEEN');
|
|
8379
|
+
}
|
|
8380
|
+
#notifyChange() {
|
|
8381
|
+
this.dispatchEvent(
|
|
8382
|
+
new CustomEvent('change', {
|
|
8383
|
+
bubbles: true,
|
|
8384
|
+
cancelable: false,
|
|
8385
|
+
detail: {
|
|
8386
|
+
value: this.value,
|
|
8387
|
+
},
|
|
8388
|
+
}),
|
|
8389
|
+
);
|
|
8390
|
+
}
|
|
8391
|
+
get readonly() {
|
|
8392
|
+
return super.readonly;
|
|
8393
|
+
}
|
|
8043
8394
|
set readonly(v) {
|
|
8044
8395
|
this.#value2.readOnly = v;
|
|
8045
8396
|
super.readonly = v;
|
|
8046
8397
|
}
|
|
8398
|
+
get disabled() {
|
|
8399
|
+
return super.disabled;
|
|
8400
|
+
}
|
|
8047
8401
|
set disabled(d) {
|
|
8048
8402
|
Attributes.toggle(this.#value2, 'disabled', d);
|
|
8049
8403
|
super.disabled = d;
|
|
@@ -8051,7 +8405,7 @@ var fml = (function (exports) {
|
|
|
8051
8405
|
}
|
|
8052
8406
|
|
|
8053
8407
|
class LocalDateFilter extends Input {
|
|
8054
|
-
static observed = ['value:json', 'readonly:presence', 'required:presence'];
|
|
8408
|
+
static observed = ['value:json', 'readonly:presence', 'required:presence', 'placeholder'];
|
|
8055
8409
|
static template = `
|
|
8056
8410
|
<div class="form-label">
|
|
8057
8411
|
<label>{{{{ slots.default }}}}</label>
|
|
@@ -8086,10 +8440,16 @@ var fml = (function (exports) {
|
|
|
8086
8440
|
this.#operator = this.querySelector('[data-ref=operator]');
|
|
8087
8441
|
this.#value1 = this.querySelector('[data-ref=value1]');
|
|
8088
8442
|
this.#value2 = this.querySelector('[data-ref=value2]');
|
|
8443
|
+
//Input.render only re-dispatches changes coming from the first operand
|
|
8444
|
+
this.#value2.addEventListener('change', (evt) => {
|
|
8445
|
+
evt.stopPropagation();
|
|
8446
|
+
this.#notifyChange();
|
|
8447
|
+
});
|
|
8089
8448
|
|
|
8090
8449
|
this.disabled = conf.disabled;
|
|
8091
8450
|
this.readonly = conf.observed.readonly;
|
|
8092
8451
|
this.required = conf.observed.required;
|
|
8452
|
+
this.placeholder = conf.observed.placeholder;
|
|
8093
8453
|
this.value = conf.observed.value;
|
|
8094
8454
|
|
|
8095
8455
|
this.addEventListener('click', (evt) => {
|
|
@@ -8099,14 +8459,18 @@ var fml = (function (exports) {
|
|
|
8099
8459
|
}
|
|
8100
8460
|
const btn = /** @type HTMLButtonElement */ (target.closest('ul')?.previousElementSibling);
|
|
8101
8461
|
const value = /** @type String */ (target.getAttribute('value'));
|
|
8462
|
+
const previous = btn.getAttribute('value');
|
|
8102
8463
|
Attributes.toggle(this.#value2, 'hidden', value !== 'BETWEEN');
|
|
8103
8464
|
btn.setAttribute('value', value);
|
|
8104
8465
|
btn.innerHTML = target.innerHTML;
|
|
8466
|
+
if (previous !== value) {
|
|
8467
|
+
this.#notifyChange();
|
|
8468
|
+
}
|
|
8105
8469
|
});
|
|
8106
8470
|
}
|
|
8107
8471
|
get value() {
|
|
8108
8472
|
const operator = this.#operator.getAttribute('value');
|
|
8109
|
-
const values = operator
|
|
8473
|
+
const values = operator === 'BETWEEN' ? [this.#value1.value, this.#value2.value] : [this.#value1.value];
|
|
8110
8474
|
return values.some((v) => v === '') ? undefined : [operator, ...values];
|
|
8111
8475
|
}
|
|
8112
8476
|
set value(v) {
|
|
@@ -8116,14 +8480,40 @@ var fml = (function (exports) {
|
|
|
8116
8480
|
return;
|
|
8117
8481
|
}
|
|
8118
8482
|
const [operator, ...values] = v;
|
|
8119
|
-
this.#
|
|
8483
|
+
this.#showOperator(operator);
|
|
8120
8484
|
this.#value1.value = values[0];
|
|
8121
8485
|
this.#value2.value = values[1];
|
|
8122
8486
|
}
|
|
8487
|
+
#showOperator(operator) {
|
|
8488
|
+
this.#operator.setAttribute('value', operator);
|
|
8489
|
+
const items = Array.from(this.#operator.nextElementSibling?.querySelectorAll('li > a[value]') ?? []);
|
|
8490
|
+
const item = items.find((a) => a.getAttribute('value') === operator);
|
|
8491
|
+
if (item) {
|
|
8492
|
+
this.#operator.innerHTML = item.innerHTML;
|
|
8493
|
+
}
|
|
8494
|
+
Attributes.toggle(this.#value2, 'hidden', operator !== 'BETWEEN');
|
|
8495
|
+
}
|
|
8496
|
+
#notifyChange() {
|
|
8497
|
+
this.dispatchEvent(
|
|
8498
|
+
new CustomEvent('change', {
|
|
8499
|
+
bubbles: true,
|
|
8500
|
+
cancelable: false,
|
|
8501
|
+
detail: {
|
|
8502
|
+
value: this.value,
|
|
8503
|
+
},
|
|
8504
|
+
}),
|
|
8505
|
+
);
|
|
8506
|
+
}
|
|
8507
|
+
get readonly() {
|
|
8508
|
+
return super.readonly;
|
|
8509
|
+
}
|
|
8123
8510
|
set readonly(v) {
|
|
8124
8511
|
this.#value2.readOnly = v;
|
|
8125
8512
|
super.readonly = v;
|
|
8126
8513
|
}
|
|
8514
|
+
get disabled() {
|
|
8515
|
+
return super.disabled;
|
|
8516
|
+
}
|
|
8127
8517
|
set disabled(d) {
|
|
8128
8518
|
Attributes.toggle(this.#value2, 'disabled', d);
|
|
8129
8519
|
super.disabled = d;
|
|
@@ -8131,7 +8521,7 @@ var fml = (function (exports) {
|
|
|
8131
8521
|
}
|
|
8132
8522
|
|
|
8133
8523
|
class TextFilter extends Input {
|
|
8134
|
-
static observed = ['value:json', 'readonly:presence', 'required:presence'];
|
|
8524
|
+
static observed = ['value:json', 'readonly:presence', 'required:presence', 'placeholder'];
|
|
8135
8525
|
static template = `
|
|
8136
8526
|
<div class="form-label">
|
|
8137
8527
|
<label>{{{{ slots.default }}}}</label>
|
|
@@ -8155,6 +8545,8 @@ var fml = (function (exports) {
|
|
|
8155
8545
|
`;
|
|
8156
8546
|
#operator;
|
|
8157
8547
|
#value;
|
|
8548
|
+
//the sensitivity has no control of its own: it is carried through from whoever set the value
|
|
8549
|
+
#sensitivity = 'IGNORE_CASE';
|
|
8158
8550
|
render(conf) {
|
|
8159
8551
|
super.render({ ...conf, skipObservedSetup: true });
|
|
8160
8552
|
|
|
@@ -8164,6 +8556,7 @@ var fml = (function (exports) {
|
|
|
8164
8556
|
this.disabled = conf.disabled;
|
|
8165
8557
|
this.readonly = conf.observed.readonly;
|
|
8166
8558
|
this.required = conf.observed.required;
|
|
8559
|
+
this.placeholder = conf.observed.placeholder;
|
|
8167
8560
|
this.value = conf.observed.value;
|
|
8168
8561
|
|
|
8169
8562
|
this.addEventListener('click', (evt) => {
|
|
@@ -8173,13 +8566,17 @@ var fml = (function (exports) {
|
|
|
8173
8566
|
}
|
|
8174
8567
|
const btn = /** @type HTMLButtonElement */ (target.closest('ul')?.previousElementSibling);
|
|
8175
8568
|
const value = /** @type String */ (target.getAttribute('value'));
|
|
8569
|
+
const previous = btn.getAttribute('value');
|
|
8176
8570
|
btn.setAttribute('value', value);
|
|
8177
8571
|
btn.innerHTML = target.innerHTML;
|
|
8572
|
+
if (previous !== value) {
|
|
8573
|
+
this.#notifyChange();
|
|
8574
|
+
}
|
|
8178
8575
|
});
|
|
8179
8576
|
}
|
|
8180
8577
|
get value() {
|
|
8181
8578
|
const operator = this.#operator.getAttribute('value');
|
|
8182
|
-
return this.#value.value === '' ? undefined : [operator,
|
|
8579
|
+
return this.#value.value === '' ? undefined : [operator, this.#sensitivity, this.#value.value];
|
|
8183
8580
|
}
|
|
8184
8581
|
set value(v) {
|
|
8185
8582
|
if (v == null) {
|
|
@@ -8187,15 +8584,35 @@ var fml = (function (exports) {
|
|
|
8187
8584
|
return;
|
|
8188
8585
|
}
|
|
8189
8586
|
const [operator, sensitivity, value] = v;
|
|
8190
|
-
this.#
|
|
8587
|
+
this.#showOperator(operator);
|
|
8588
|
+
this.#sensitivity = sensitivity ?? 'IGNORE_CASE';
|
|
8191
8589
|
this.#value.value = value;
|
|
8192
8590
|
}
|
|
8591
|
+
#showOperator(operator) {
|
|
8592
|
+
this.#operator.setAttribute('value', operator);
|
|
8593
|
+
const items = Array.from(this.#operator.nextElementSibling?.querySelectorAll('li > a[value]') ?? []);
|
|
8594
|
+
const item = items.find((a) => a.getAttribute('value') === operator);
|
|
8595
|
+
if (item) {
|
|
8596
|
+
this.#operator.innerHTML = item.innerHTML;
|
|
8597
|
+
}
|
|
8598
|
+
}
|
|
8599
|
+
#notifyChange() {
|
|
8600
|
+
this.dispatchEvent(
|
|
8601
|
+
new CustomEvent('change', {
|
|
8602
|
+
bubbles: true,
|
|
8603
|
+
cancelable: false,
|
|
8604
|
+
detail: {
|
|
8605
|
+
value: this.value,
|
|
8606
|
+
},
|
|
8607
|
+
}),
|
|
8608
|
+
);
|
|
8609
|
+
}
|
|
8193
8610
|
}
|
|
8194
8611
|
|
|
8195
8612
|
class LocalizationModule {
|
|
8196
8613
|
static t(k, ...args) {
|
|
8197
|
-
//@ts-
|
|
8198
|
-
const format = this.l10n?.[this.language]?.[k] ?? this.l10n?.
|
|
8614
|
+
//@ts-expect-error l10n and language come from the element class and the data stack
|
|
8615
|
+
const format = this.l10n?.[this.language]?.[k] ?? this.l10n?.en?.[k] ?? k;
|
|
8199
8616
|
if (args.length === 0) {
|
|
8200
8617
|
return format;
|
|
8201
8618
|
}
|
|
@@ -8278,9 +8695,11 @@ var fml = (function (exports) {
|
|
|
8278
8695
|
});
|
|
8279
8696
|
|
|
8280
8697
|
if (typeof window !== 'undefined') {
|
|
8698
|
+
// biome-ignore-start lint/complexity/useLiteralKeys: checkJs rejects unknown properties on Window
|
|
8281
8699
|
window['ftl'] = ftl;
|
|
8282
8700
|
window['httpc'] = httpc;
|
|
8283
8701
|
window['ful'] = ful;
|
|
8702
|
+
// biome-ignore-end lint/complexity/useLiteralKeys: checkJs rejects unknown properties on Window
|
|
8284
8703
|
}
|
|
8285
8704
|
|
|
8286
8705
|
exports.AsyncEvents = AsyncEvents;
|