@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/ftl.mjs
CHANGED
|
@@ -3457,7 +3457,7 @@ class EvaluatingVisitor {
|
|
|
3457
3457
|
case '<=':
|
|
3458
3458
|
return lhs <= rhs;
|
|
3459
3459
|
default:
|
|
3460
|
-
throw new Error(
|
|
3460
|
+
throw new Error(`unknown cmp op ${node.op}`);
|
|
3461
3461
|
}
|
|
3462
3462
|
}
|
|
3463
3463
|
[nodes.call](node) {
|
|
@@ -3468,7 +3468,7 @@ class EvaluatingVisitor {
|
|
|
3468
3468
|
}
|
|
3469
3469
|
const fn = module[fnRef.value];
|
|
3470
3470
|
if (!fn) {
|
|
3471
|
-
throw new Error(`Function "#${fnRef.module === null ? '' : fnRef.module
|
|
3471
|
+
throw new Error(`Function "#${fnRef.module === null ? '' : `${fnRef.module}:`}${fnRef.value}" not found`);
|
|
3472
3472
|
}
|
|
3473
3473
|
const args = node.args.map((arg) => this.visit(arg));
|
|
3474
3474
|
return fn.apply(this.#resolve_proxy(), args);
|
|
@@ -3504,14 +3504,14 @@ class EvaluatingVisitor {
|
|
|
3504
3504
|
return cond ? cond : this.visit(node.ifFalse);
|
|
3505
3505
|
}
|
|
3506
3506
|
[nodes.access](node) {
|
|
3507
|
-
let prev
|
|
3507
|
+
let prev ;
|
|
3508
3508
|
let cur = this.visit(node.lhs);
|
|
3509
3509
|
for (let i = 0; i !== node.rhs.length; ++i) {
|
|
3510
3510
|
const rhs = node.rhs[i];
|
|
3511
3511
|
if (rhs.ns && cur == null) {
|
|
3512
3512
|
return undefined;
|
|
3513
3513
|
}
|
|
3514
|
-
let value
|
|
3514
|
+
let value ;
|
|
3515
3515
|
switch (rhs.type) {
|
|
3516
3516
|
case nodes.member: {
|
|
3517
3517
|
value = cur[rhs.rhs];
|
|
@@ -3552,7 +3552,7 @@ class EvaluatingVisitor {
|
|
|
3552
3552
|
case nodes.templated.ten:
|
|
3553
3553
|
return { type: nodes.dom.n, value: this.visit(node.value) };
|
|
3554
3554
|
default:
|
|
3555
|
-
throw new Error(
|
|
3555
|
+
throw new Error(`unknown node type ${node.type.toString()}`);
|
|
3556
3556
|
}
|
|
3557
3557
|
});
|
|
3558
3558
|
}
|
|
@@ -3728,7 +3728,7 @@ class Attributes {
|
|
|
3728
3728
|
.forEach((a) => {
|
|
3729
3729
|
const target = a.substring(prefix.length);
|
|
3730
3730
|
if (target === 'class') {
|
|
3731
|
-
const classes = from.getAttribute(prefix
|
|
3731
|
+
const classes = from.getAttribute(`${prefix}class`)?.split(/\s+/).filter((a) => a.length) ?? [];
|
|
3732
3732
|
to.classList.add(...classes);
|
|
3733
3733
|
return;
|
|
3734
3734
|
}
|
|
@@ -3820,8 +3820,7 @@ class Nodes {
|
|
|
3820
3820
|
* @returns
|
|
3821
3821
|
*/
|
|
3822
3822
|
static isParsed(el) {
|
|
3823
|
-
|
|
3824
|
-
for (let c = el; c; c = c.parentNode) {
|
|
3823
|
+
for (let c = /** @type {Node | null} */ (el); c; c = c.parentNode) {
|
|
3825
3824
|
if (c.nextSibling) {
|
|
3826
3825
|
return true;
|
|
3827
3826
|
}
|
|
@@ -3884,19 +3883,18 @@ class Nodes {
|
|
|
3884
3883
|
}
|
|
3885
3884
|
|
|
3886
3885
|
class NodeOperations {
|
|
3887
|
-
#forRemoval;
|
|
3888
|
-
constructor() {
|
|
3889
|
-
this.#forRemoval = [];
|
|
3890
|
-
}
|
|
3886
|
+
#forRemoval = new Set();
|
|
3891
3887
|
removed(node) {
|
|
3892
|
-
return this.#forRemoval.
|
|
3888
|
+
return this.#forRemoval.has(node);
|
|
3893
3889
|
}
|
|
3894
3890
|
remove(node) {
|
|
3895
3891
|
node.replaceChildren?.();
|
|
3896
3892
|
Object.keys(node.dataset || {})
|
|
3897
3893
|
.filter((k) => k.startsWith('tpl'))
|
|
3898
|
-
.forEach((k) =>
|
|
3899
|
-
|
|
3894
|
+
.forEach((k) => {
|
|
3895
|
+
delete node.dataset[k];
|
|
3896
|
+
});
|
|
3897
|
+
this.#forRemoval.add(node);
|
|
3900
3898
|
}
|
|
3901
3899
|
popData(node, key) {
|
|
3902
3900
|
const v = node.dataset[key];
|
|
@@ -3914,9 +3912,10 @@ class NodeOperations {
|
|
|
3914
3912
|
this.remove(ref);
|
|
3915
3913
|
}
|
|
3916
3914
|
cleanup() {
|
|
3917
|
-
|
|
3918
|
-
|
|
3915
|
+
for (const node of this.#forRemoval) {
|
|
3916
|
+
node.remove();
|
|
3919
3917
|
}
|
|
3918
|
+
this.#forRemoval.clear();
|
|
3920
3919
|
}
|
|
3921
3920
|
}
|
|
3922
3921
|
|
|
@@ -3971,7 +3970,7 @@ class CommandsHandler {
|
|
|
3971
3970
|
}
|
|
3972
3971
|
static tplRemove(node, value, ops, modules, dataStack) {
|
|
3973
3972
|
switch (value.toLowerCase()) {
|
|
3974
|
-
case 'tag':
|
|
3973
|
+
case 'tag': {
|
|
3975
3974
|
const fragment = new DocumentFragment();
|
|
3976
3975
|
while (node.firstChild) {
|
|
3977
3976
|
fragment.appendChild(node.firstChild);
|
|
@@ -3985,6 +3984,7 @@ class CommandsHandler {
|
|
|
3985
3984
|
ops.remove(node);
|
|
3986
3985
|
}
|
|
3987
3986
|
break;
|
|
3987
|
+
}
|
|
3988
3988
|
case 'body':
|
|
3989
3989
|
node.replaceChildren();
|
|
3990
3990
|
break;
|
|
@@ -4050,8 +4050,10 @@ class CommandsHandler {
|
|
|
4050
4050
|
}
|
|
4051
4051
|
}
|
|
4052
4052
|
|
|
4053
|
-
// Module-isolated string cache for dataset-to-attribute conversions
|
|
4053
|
+
// Module-isolated string cache for dataset-to-attribute conversions, bounded with
|
|
4054
|
+
// FIFO eviction like the expression ast cache
|
|
4054
4055
|
const attributeCache = new Map();
|
|
4056
|
+
const ATTRIBUTE_CACHE_MAX_SIZE = 1000;
|
|
4055
4057
|
|
|
4056
4058
|
/**
|
|
4057
4059
|
* Converts a tpl camelCase dataset key into a kebab-case attribute name.
|
|
@@ -4062,6 +4064,9 @@ const attributeCache = new Map();
|
|
|
4062
4064
|
function toAttr(dataSetKey) {
|
|
4063
4065
|
let cached = attributeCache.get(dataSetKey);
|
|
4064
4066
|
if (!cached) {
|
|
4067
|
+
if (attributeCache.size >= ATTRIBUTE_CACHE_MAX_SIZE) {
|
|
4068
|
+
attributeCache.delete(attributeCache.keys().next().value);
|
|
4069
|
+
}
|
|
4065
4070
|
cached = dataSetKey
|
|
4066
4071
|
.substring(3)
|
|
4067
4072
|
.split(/(?=[A-Z])/)
|
|
@@ -4196,9 +4201,10 @@ class Template {
|
|
|
4196
4201
|
* @param {string} expression
|
|
4197
4202
|
* @param {(typeof Expressions.MODE_EXPRESSION | typeof Expressions.MODE_TEMPLATED)?} [mode]
|
|
4198
4203
|
* @param {...*} data
|
|
4204
|
+
* @returns the evaluated expression result
|
|
4199
4205
|
*/
|
|
4200
4206
|
evaluate(expression, mode, ...data) {
|
|
4201
|
-
new ExpressionEvaluator(this.#modules, this.#dataStack).withOverlay(...data).evaluate(expression, mode);
|
|
4207
|
+
return new ExpressionEvaluator(this.#modules, this.#dataStack).withOverlay(...data).evaluate(expression, mode);
|
|
4202
4208
|
}
|
|
4203
4209
|
/**
|
|
4204
4210
|
* Returns an expression evaluator with bound modules and dataStack.
|
|
@@ -4343,8 +4349,8 @@ class RenderError extends Error {
|
|
|
4343
4349
|
if (node.nodeType === Node.TEXT_NODE) {
|
|
4344
4350
|
node.nodeValue = node.nodeValue.trim();
|
|
4345
4351
|
}
|
|
4346
|
-
for (
|
|
4347
|
-
|
|
4352
|
+
for (let n = 0; n < node.childNodes.length; n++) {
|
|
4353
|
+
const child = node.childNodes[n];
|
|
4348
4354
|
if (child.nodeType === Node.TEXT_NODE) {
|
|
4349
4355
|
child.nodeValue = child.nodeValue.trim();
|
|
4350
4356
|
if (child.nodeValue.length === 0) {
|
|
@@ -4363,8 +4369,7 @@ class UpgradeQueue {
|
|
|
4363
4369
|
#q = new Map();
|
|
4364
4370
|
constructor() {
|
|
4365
4371
|
document.addEventListener('DOMContentLoaded', async () => {
|
|
4366
|
-
|
|
4367
|
-
await Promise.all(pending);
|
|
4372
|
+
await this.settle();
|
|
4368
4373
|
document.dispatchEvent(
|
|
4369
4374
|
new CustomEvent('ftl:ready', {
|
|
4370
4375
|
bubbles: false,
|
|
@@ -4373,16 +4378,39 @@ class UpgradeQueue {
|
|
|
4373
4378
|
);
|
|
4374
4379
|
});
|
|
4375
4380
|
}
|
|
4381
|
+
#finished = new Map();
|
|
4376
4382
|
enqueue(el) {
|
|
4377
4383
|
if (this.#q.has(el)) {
|
|
4378
4384
|
//already upgrading, can happen when disconnecting an element
|
|
4379
4385
|
//while it's already queued for upgrade (e.g.: ful-form)
|
|
4380
4386
|
return;
|
|
4381
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
|
+
);
|
|
4382
4394
|
const promise = Nodes.waitParsed(el)
|
|
4383
4395
|
.then(() => el.upgrade())
|
|
4384
|
-
.finally(() =>
|
|
4396
|
+
.finally(() => {
|
|
4397
|
+
this.#q.delete(el);
|
|
4398
|
+
this.#finished.delete(el);
|
|
4399
|
+
markFinished();
|
|
4400
|
+
});
|
|
4385
4401
|
this.#q.set(el, promise);
|
|
4402
|
+
this.#finished.set(el, finished);
|
|
4403
|
+
}
|
|
4404
|
+
/**
|
|
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.
|
|
4409
|
+
*/
|
|
4410
|
+
async settle() {
|
|
4411
|
+
while (this.#finished.size !== 0) {
|
|
4412
|
+
await Promise.all(Array.from(this.#finished.values()));
|
|
4413
|
+
}
|
|
4386
4414
|
}
|
|
4387
4415
|
get entries() {
|
|
4388
4416
|
return this.#q.entries();
|
|
@@ -4578,18 +4606,30 @@ class Templates {
|
|
|
4578
4606
|
}
|
|
4579
4607
|
}
|
|
4580
4608
|
|
|
4581
|
-
|
|
4582
|
-
|
|
4609
|
+
/**
|
|
4610
|
+
* Waits for the queued upgrades matching the filter, including the ones enqueued while
|
|
4611
|
+
* waiting: a component is only queued once its parent connects it, so a single pass
|
|
4612
|
+
* would miss everything nested.
|
|
4613
|
+
* @param {(el: Element) => boolean} accept
|
|
4614
|
+
*/
|
|
4615
|
+
const settle = async (accept) => {
|
|
4616
|
+
for (;;) {
|
|
4583
4617
|
const pending = Array.from(registry.upgrades)
|
|
4584
|
-
.filter(([child
|
|
4585
|
-
.map(([
|
|
4618
|
+
.filter(([child]) => accept(child))
|
|
4619
|
+
.map(([, promise]) => promise);
|
|
4620
|
+
if (pending.length === 0) {
|
|
4621
|
+
return;
|
|
4622
|
+
}
|
|
4586
4623
|
await Promise.all(pending);
|
|
4587
4624
|
}
|
|
4625
|
+
};
|
|
4626
|
+
|
|
4627
|
+
class Rendering {
|
|
4628
|
+
static async waitFor(el) {
|
|
4629
|
+
await settle((child) => el.contains(child));
|
|
4630
|
+
}
|
|
4588
4631
|
static async waitForChildren(el) {
|
|
4589
|
-
|
|
4590
|
-
.filter(([child, promise]) => el !== child && el.contains(child))
|
|
4591
|
-
.map(([child, promise]) => promise);
|
|
4592
|
-
await Promise.all(pending);
|
|
4632
|
+
await settle((child) => el !== child && el.contains(child));
|
|
4593
4633
|
}
|
|
4594
4634
|
}
|
|
4595
4635
|
|
|
@@ -4631,7 +4671,7 @@ class ParsedElement extends HTMLElement {
|
|
|
4631
4671
|
template(name) {
|
|
4632
4672
|
const { modules, data } = registry.context();
|
|
4633
4673
|
let t = this.#bits().TEMPLATES[name ?? 'default'].withData(data).withModules(modules);
|
|
4634
|
-
for (
|
|
4674
|
+
for (const k of ['l10n', 'config']) {
|
|
4635
4675
|
const v = this.constructor[k];
|
|
4636
4676
|
if (v) {
|
|
4637
4677
|
t = t.withOverlay({ [k]: v });
|
|
@@ -4661,6 +4701,19 @@ class ParsedElement extends HTMLElement {
|
|
|
4661
4701
|
return;
|
|
4662
4702
|
}
|
|
4663
4703
|
Reflect.set(this, 'disabled', disabled);
|
|
4704
|
+
if (disabled) {
|
|
4705
|
+
this.#unclaimFormDisabled();
|
|
4706
|
+
}
|
|
4707
|
+
}
|
|
4708
|
+
//a disabled ancestor fieldset already matches the element through :disabled, and
|
|
4709
|
+
//an attribute of its own would keep the element disabled once the fieldset is
|
|
4710
|
+
//re-enabled, as formDisabledCallback(false) is only delivered on an actual state
|
|
4711
|
+
//change. a claim of its own made while already disabled by ancestry is safe: the
|
|
4712
|
+
//platform fires no callback for it, so it survives the fieldset being re-enabled
|
|
4713
|
+
#unclaimFormDisabled() {
|
|
4714
|
+
if (this.closest('fieldset:disabled')) {
|
|
4715
|
+
this.removeAttribute('disabled');
|
|
4716
|
+
}
|
|
4664
4717
|
}
|
|
4665
4718
|
async upgrade() {
|
|
4666
4719
|
if (this.#parsed) {
|
|
@@ -4676,6 +4729,9 @@ class ParsedElement extends HTMLElement {
|
|
|
4676
4729
|
);
|
|
4677
4730
|
const disabled = this.#disabledBeforeParsed ?? false;
|
|
4678
4731
|
await this.render({ slots, observed, disabled });
|
|
4732
|
+
if (disabled) {
|
|
4733
|
+
this.#unclaimFormDisabled();
|
|
4734
|
+
}
|
|
4679
4735
|
}
|
|
4680
4736
|
render(c) {}
|
|
4681
4737
|
reflect(fn) {
|