@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.
Files changed (48) hide show
  1. package/dist/client-errors.iife.js +11 -8
  2. package/dist/client-errors.iife.js.map +1 -1
  3. package/dist/client-errors.iife.min.js +1 -1
  4. package/dist/client-errors.iife.min.js.map +1 -1
  5. package/dist/custom-elements.json +1210 -0
  6. package/dist/fml.css +10 -1
  7. package/dist/fml.css.map +1 -1
  8. package/dist/fml.d.mts +73 -13
  9. package/dist/fml.iife.js +628 -209
  10. package/dist/fml.iife.js.map +1 -1
  11. package/dist/fml.iife.min.js +1 -1
  12. package/dist/fml.iife.min.js.map +1 -1
  13. package/dist/fml.min.mjs +1 -1
  14. package/dist/fml.min.mjs.map +1 -1
  15. package/dist/fml.mjs +628 -209
  16. package/dist/fml.mjs.map +1 -1
  17. package/dist/ftl.d.mts +2 -1
  18. package/dist/ftl.iife.js +90 -34
  19. package/dist/ftl.iife.js.map +1 -1
  20. package/dist/ftl.iife.min.js +1 -1
  21. package/dist/ftl.iife.min.js.map +1 -1
  22. package/dist/ftl.min.mjs +1 -1
  23. package/dist/ftl.min.mjs.map +1 -1
  24. package/dist/ftl.mjs +90 -34
  25. package/dist/ftl.mjs.map +1 -1
  26. package/dist/ful.css +10 -1
  27. package/dist/ful.css.map +1 -1
  28. package/dist/ful.d.mts +22 -6
  29. package/dist/ful.iife.js +496 -166
  30. package/dist/ful.iife.js.map +1 -1
  31. package/dist/ful.iife.min.js +1 -1
  32. package/dist/ful.iife.min.js.map +1 -1
  33. package/dist/ful.min.mjs +1 -1
  34. package/dist/ful.min.mjs.map +1 -1
  35. package/dist/ful.mjs +496 -166
  36. package/dist/ful.mjs.map +1 -1
  37. package/dist/httpc.d.mts +49 -6
  38. package/dist/httpc.iife.js +40 -9
  39. package/dist/httpc.iife.js.map +1 -1
  40. package/dist/httpc.iife.min.js +1 -1
  41. package/dist/httpc.iife.min.js.map +1 -1
  42. package/dist/httpc.min.mjs +1 -1
  43. package/dist/httpc.min.mjs.map +1 -1
  44. package/dist/httpc.mjs +40 -9
  45. package/dist/httpc.mjs.map +1 -1
  46. package/dist/vscode.html-custom-data.json +357 -0
  47. package/dist/web-types.json +1006 -0
  48. 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('unknown cmp op ' + node.op);
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 + ':'}${fnRef.value}" not found`);
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 = undefined;
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 = undefined;
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('unknown node type ' + node.type.toString());
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 + 'class')?.split(/\s+/).filter((a) => a.length) ?? [];
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
- //@ts-ignore
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.includes(node);
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) => delete node.dataset[k]);
3899
- this.#forRemoval.push(node);
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
- while (this.#forRemoval.length) {
3918
- this.#forRemoval.pop().remove();
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 (var n = 0; n < node.childNodes.length; n++) {
4347
- var child = node.childNodes[n];
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
- const pending = Array.from(this.entries).map(([child, promise]) => promise);
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(() => this.#q.delete(el));
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
- class Rendering {
4582
- static async waitFor(el) {
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, promise]) => el.contains(child))
4585
- .map(([child, promise]) => promise);
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
- const pending = Array.from(registry.upgrades)
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 (let k of ['l10n', 'config']) {
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) {