@optionfactory/fml 9.0.0-rc30 → 9.0.0-rc31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/custom-elements.json +8 -1
- package/dist/fml.css +6 -6
- package/dist/fml.css.map +1 -1
- package/dist/fml.iife.js +234 -77
- 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/ftl.d.mts +32 -9
- package/dist/ftl.iife.js +89 -25
- 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 +89 -25
- package/dist/ftl.mjs.map +1 -1
- package/dist/ful.css +6 -6
- package/dist/ful.css.map +1 -1
- package/dist/ful.d.mts +57 -62
- package/dist/ful.iife.js +145 -51
- 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 +147 -52
- package/dist/ful.mjs.map +1 -1
- package/dist/vscode.html-custom-data.json +5 -1
- package/dist/web-types.json +10 -2
- package/package.json +2 -2
package/dist/fml.iife.js
CHANGED
|
@@ -2850,6 +2850,8 @@ var fml = (function (exports) {
|
|
|
2850
2850
|
return undefined;
|
|
2851
2851
|
};
|
|
2852
2852
|
|
|
2853
|
+
const BOUND = Symbol('BOUND');
|
|
2854
|
+
|
|
2853
2855
|
/**
|
|
2854
2856
|
* Evaluates a parsed expression against a scope. One instance per evaluation:
|
|
2855
2857
|
* it holds the modules and the data stack that names resolve against, and keeps
|
|
@@ -2858,9 +2860,11 @@ var fml = (function (exports) {
|
|
|
2858
2860
|
class EvaluatingVisitor {
|
|
2859
2861
|
#modules;
|
|
2860
2862
|
#dataStack;
|
|
2861
|
-
|
|
2863
|
+
#bound;
|
|
2864
|
+
constructor(modules, dataStack, bound) {
|
|
2862
2865
|
this.#modules = modules;
|
|
2863
2866
|
this.#dataStack = dataStack;
|
|
2867
|
+
this.#bound = bound ?? dataStack.length;
|
|
2864
2868
|
}
|
|
2865
2869
|
#resolve(prop) {
|
|
2866
2870
|
return resolveInStack(this.#dataStack, prop);
|
|
@@ -2884,8 +2888,9 @@ var fml = (function (exports) {
|
|
|
2884
2888
|
#cached_resolve_proxy;
|
|
2885
2889
|
#resolve_proxy() {
|
|
2886
2890
|
if (!this.#cached_resolve_proxy) {
|
|
2891
|
+
const bound = (name) => resolveInStack(this.#dataStack.slice(0, this.#bound), name);
|
|
2887
2892
|
this.#cached_resolve_proxy = new Proxy(this.#dataStack, {
|
|
2888
|
-
get: (target, prop) => this.#resolve(prop),
|
|
2893
|
+
get: (target, prop) => (prop === BOUND ? bound : this.#resolve(prop)),
|
|
2889
2894
|
});
|
|
2890
2895
|
}
|
|
2891
2896
|
return this.#cached_resolve_proxy;
|
|
@@ -3038,6 +3043,16 @@ var fml = (function (exports) {
|
|
|
3038
3043
|
* `{{{ }}}` (html) or `{{{{ }}}}` (node) interpolation, in order.
|
|
3039
3044
|
*/
|
|
3040
3045
|
static MODE_TEMPLATED = Symbol('MODE_TEMPLATED');
|
|
3046
|
+
/**
|
|
3047
|
+
* The key under which the receiver of a module function answers a lookup
|
|
3048
|
+
* against the bound data alone: `this[Expressions.BOUND](name)` resolves
|
|
3049
|
+
* the name in the layers the scope was created with (a registry's data,
|
|
3050
|
+
* the data handed to a Template factory) and never in an overlay pushed
|
|
3051
|
+
* afterwards, by `data-tpl-each`, `data-tpl-with` or `withOverlay`. It is
|
|
3052
|
+
* how a module reads what the page configured without the rendered data
|
|
3053
|
+
* being able to supply or replace it. No name on the data stack reaches it.
|
|
3054
|
+
*/
|
|
3055
|
+
static BOUND = BOUND;
|
|
3041
3056
|
|
|
3042
3057
|
static #astCache = new BoundedCache(1000);
|
|
3043
3058
|
|
|
@@ -3062,10 +3077,11 @@ var fml = (function (exports) {
|
|
|
3062
3077
|
* @param {any[]} dataStack innermost overlay last
|
|
3063
3078
|
* @param {any} ast what `parse` answered
|
|
3064
3079
|
* @param {(typeof Expressions.MODE_EXPRESSION | typeof Expressions.MODE_TEMPLATED)?} [mode] the mode it was parsed in
|
|
3080
|
+
* @param {number} [bound] how many layers, from the outermost, are the bound data; all of them when omitted
|
|
3065
3081
|
* @returns {any} the value, or the list of parts in MODE_TEMPLATED
|
|
3066
3082
|
*/
|
|
3067
|
-
static evaluate(modules, dataStack, ast, mode) {
|
|
3068
|
-
return new EvaluatingVisitor(modules, dataStack).visitRoot(ast, mode === Expressions.MODE_TEMPLATED);
|
|
3083
|
+
static evaluate(modules, dataStack, ast, mode, bound) {
|
|
3084
|
+
return new EvaluatingVisitor(modules, dataStack, bound).visitRoot(ast, mode === Expressions.MODE_TEMPLATED);
|
|
3069
3085
|
}
|
|
3070
3086
|
/**
|
|
3071
3087
|
* Parses and evaluates an expression.
|
|
@@ -3073,10 +3089,11 @@ var fml = (function (exports) {
|
|
|
3073
3089
|
* @param {any[]} dataStack innermost overlay last
|
|
3074
3090
|
* @param {string} expression
|
|
3075
3091
|
* @param {(typeof Expressions.MODE_EXPRESSION | typeof Expressions.MODE_TEMPLATED)?} [mode] MODE_EXPRESSION when omitted
|
|
3092
|
+
* @param {number} [bound] how many layers, from the outermost, are the bound data; all of them when omitted
|
|
3076
3093
|
* @returns {any} the value, or the list of parts in MODE_TEMPLATED
|
|
3077
3094
|
*/
|
|
3078
|
-
static interpret(modules, dataStack, expression, mode) {
|
|
3079
|
-
return Expressions.evaluate(modules, dataStack, Expressions.parse(expression, mode), mode);
|
|
3095
|
+
static interpret(modules, dataStack, expression, mode, bound) {
|
|
3096
|
+
return Expressions.evaluate(modules, dataStack, Expressions.parse(expression, mode), mode, bound);
|
|
3080
3097
|
}
|
|
3081
3098
|
}
|
|
3082
3099
|
|
|
@@ -3085,17 +3102,24 @@ var fml = (function (exports) {
|
|
|
3085
3102
|
* that a bare identifier resolves against, as a single value. Adding an overlay
|
|
3086
3103
|
* returns a new evaluator instead of modifying this one, so a template can add
|
|
3087
3104
|
* data for one subtree without affecting the rest of the render.
|
|
3105
|
+
*
|
|
3106
|
+
* The layers an evaluator is created with are its bound data; the overlays
|
|
3107
|
+
* pushed afterwards are not. A bare identifier resolves against both, while a
|
|
3108
|
+
* module function can ask for the bound data alone through `Expressions.BOUND`.
|
|
3088
3109
|
*/
|
|
3089
3110
|
class ExpressionEvaluator {
|
|
3090
3111
|
#modules;
|
|
3091
3112
|
#dataStack;
|
|
3113
|
+
#bound;
|
|
3092
3114
|
/**
|
|
3093
3115
|
* @param {{ [k: string]: any } | null | undefined} modules
|
|
3094
3116
|
* @param {any[]} dataStack innermost overlay last
|
|
3117
|
+
* @param {number} [bound] how many layers, from the outermost, are the bound data; all of them when omitted
|
|
3095
3118
|
*/
|
|
3096
|
-
constructor(modules, dataStack) {
|
|
3119
|
+
constructor(modules, dataStack, bound) {
|
|
3097
3120
|
this.#modules = modules;
|
|
3098
3121
|
this.#dataStack = dataStack;
|
|
3122
|
+
this.#bound = bound ?? dataStack.length;
|
|
3099
3123
|
}
|
|
3100
3124
|
/**
|
|
3101
3125
|
* A new evaluator with one more module: `#name:fn()` resolves into it, and an
|
|
@@ -3106,11 +3130,11 @@ var fml = (function (exports) {
|
|
|
3106
3130
|
*/
|
|
3107
3131
|
withModule(name, value) {
|
|
3108
3132
|
const module = name ? { [name]: value } : value;
|
|
3109
|
-
return new ExpressionEvaluator({ ...this.#modules, ...module }, this.#dataStack);
|
|
3133
|
+
return new ExpressionEvaluator({ ...this.#modules, ...module }, this.#dataStack, this.#bound);
|
|
3110
3134
|
}
|
|
3111
3135
|
/**
|
|
3112
3136
|
* A new evaluator with the data pushed as the innermost overlays, the last
|
|
3113
|
-
* one winning a shared name.
|
|
3137
|
+
* one winning a shared name. The bound data stays what it was.
|
|
3114
3138
|
* @param {...any} data
|
|
3115
3139
|
* @returns {ExpressionEvaluator}
|
|
3116
3140
|
*/
|
|
@@ -3118,6 +3142,7 @@ var fml = (function (exports) {
|
|
|
3118
3142
|
return new ExpressionEvaluator(
|
|
3119
3143
|
this.#modules,
|
|
3120
3144
|
data.length === 0 ? this.#dataStack : [...this.#dataStack, ...data],
|
|
3145
|
+
this.#bound,
|
|
3121
3146
|
);
|
|
3122
3147
|
}
|
|
3123
3148
|
/**
|
|
@@ -3135,7 +3160,13 @@ var fml = (function (exports) {
|
|
|
3135
3160
|
* @returns {any}
|
|
3136
3161
|
*/
|
|
3137
3162
|
evaluateExpression(expression) {
|
|
3138
|
-
return Expressions.interpret(
|
|
3163
|
+
return Expressions.interpret(
|
|
3164
|
+
this.#modules,
|
|
3165
|
+
this.#dataStack,
|
|
3166
|
+
expression,
|
|
3167
|
+
Expressions.MODE_EXPRESSION,
|
|
3168
|
+
this.#bound,
|
|
3169
|
+
);
|
|
3139
3170
|
}
|
|
3140
3171
|
/**
|
|
3141
3172
|
* Evaluates a templated text, the `{{ }}` form, against this scope.
|
|
@@ -3143,7 +3174,7 @@ var fml = (function (exports) {
|
|
|
3143
3174
|
* @returns {{ type: symbol, value: any }[]} its parts, as MODE_TEMPLATED describes
|
|
3144
3175
|
*/
|
|
3145
3176
|
evaluateTemplated(text) {
|
|
3146
|
-
return Expressions.interpret(this.#modules, this.#dataStack, text, Expressions.MODE_TEMPLATED);
|
|
3177
|
+
return Expressions.interpret(this.#modules, this.#dataStack, text, Expressions.MODE_TEMPLATED, this.#bound);
|
|
3147
3178
|
}
|
|
3148
3179
|
}
|
|
3149
3180
|
|
|
@@ -4039,14 +4070,22 @@ var fml = (function (exports) {
|
|
|
4039
4070
|
#ready = new Promise((resolve) => {
|
|
4040
4071
|
this.#readyResolve = resolve;
|
|
4041
4072
|
});
|
|
4073
|
+
#configured = /** @type {PromiseWithResolvers<void>} */ (Promise.withResolvers());
|
|
4042
4074
|
constructor() {
|
|
4043
|
-
Nodes.waitDomContentLoaded(document).then(() => {
|
|
4075
|
+
Promise.all([Nodes.waitDomContentLoaded(document), this.#configured.promise]).then(() => {
|
|
4044
4076
|
this.#start();
|
|
4045
4077
|
});
|
|
4046
4078
|
}
|
|
4047
4079
|
ready() {
|
|
4048
4080
|
return this.#ready;
|
|
4049
4081
|
}
|
|
4082
|
+
/**
|
|
4083
|
+
* Marks the registry as configured: until then nothing is defined, so an
|
|
4084
|
+
* empty queue says nothing about the page and readiness is held back.
|
|
4085
|
+
*/
|
|
4086
|
+
configured() {
|
|
4087
|
+
this.#configured.resolve();
|
|
4088
|
+
}
|
|
4050
4089
|
async #start() {
|
|
4051
4090
|
await this.settled();
|
|
4052
4091
|
document.dispatchEvent(
|
|
@@ -4089,7 +4128,10 @@ var fml = (function (exports) {
|
|
|
4089
4128
|
* @returns {Promise<void>}
|
|
4090
4129
|
*/
|
|
4091
4130
|
settled() {
|
|
4092
|
-
return this.#drain(
|
|
4131
|
+
return this.#drain(
|
|
4132
|
+
() => true,
|
|
4133
|
+
(entry) => entry.finished,
|
|
4134
|
+
);
|
|
4093
4135
|
}
|
|
4094
4136
|
/**
|
|
4095
4137
|
* Waits for the accepted upgrades, the ones enqueued meanwhile included,
|
|
@@ -4343,7 +4385,9 @@ var fml = (function (exports) {
|
|
|
4343
4385
|
}
|
|
4344
4386
|
/**
|
|
4345
4387
|
* Defines every element deferred so far; from then on, defineElement takes
|
|
4346
|
-
* effect immediately.
|
|
4388
|
+
* effect immediately. Readiness waits for the first call: a page that
|
|
4389
|
+
* configures late (translations fetched first) gets its ftl:ready once the
|
|
4390
|
+
* elements this defines have upgraded, not at DOMContentLoaded.
|
|
4347
4391
|
* @returns {Registry} this registry
|
|
4348
4392
|
*/
|
|
4349
4393
|
configure() {
|
|
@@ -4352,6 +4396,7 @@ var fml = (function (exports) {
|
|
|
4352
4396
|
delete this.#tagToClass[tag];
|
|
4353
4397
|
}
|
|
4354
4398
|
this.#configured = true;
|
|
4399
|
+
this.#upgradeQueue.configured();
|
|
4355
4400
|
return this;
|
|
4356
4401
|
}
|
|
4357
4402
|
/**
|
|
@@ -4383,9 +4428,11 @@ var fml = (function (exports) {
|
|
|
4383
4428
|
/**
|
|
4384
4429
|
* Waits for this registry's readiness: resolves right after its ftl:ready
|
|
4385
4430
|
* event is dispatched on the document, and immediately when that already
|
|
4386
|
-
* happened.
|
|
4387
|
-
*
|
|
4388
|
-
*
|
|
4431
|
+
* happened. The event is dispatched once DOMContentLoaded has passed,
|
|
4432
|
+
* configure() has been called and the queue has drained, so a registry
|
|
4433
|
+
* that is never configured is never ready. Never rejects: a failed
|
|
4434
|
+
* upgrade counts as settled, and its rejection is left unhandled so it
|
|
4435
|
+
* still reaches the console and the page's error reporting.
|
|
4389
4436
|
* @returns {Promise<void>}
|
|
4390
4437
|
*/
|
|
4391
4438
|
ready() {
|
|
@@ -4729,12 +4776,27 @@ var fml = (function (exports) {
|
|
|
4729
4776
|
*/
|
|
4730
4777
|
|
|
4731
4778
|
/**
|
|
4732
|
-
* The receiver contract of the module functions:
|
|
4733
|
-
*
|
|
4779
|
+
* The receiver contract of the module functions: a plain object carrying the
|
|
4780
|
+
* translations and the locale, as Localization.of() binds them, or the proxy
|
|
4781
|
+
* over the data stack a template calls them with.
|
|
4734
4782
|
*
|
|
4735
4783
|
* @typedef {{ l10n?: Messages, locale?: string }} Receiver
|
|
4736
4784
|
*/
|
|
4737
4785
|
|
|
4786
|
+
/**
|
|
4787
|
+
* The translations or the locale of a receiver. From a template they are read
|
|
4788
|
+
* from the bound data alone (the registry's overlays, or the data handed to
|
|
4789
|
+
* the Template factory): the rows and responses a render pushes on top are
|
|
4790
|
+
* what arrives later, and must not supply or replace the page's own messages.
|
|
4791
|
+
* @param {any} receiver
|
|
4792
|
+
* @param {'l10n' | 'locale'} name
|
|
4793
|
+
* @returns {any}
|
|
4794
|
+
*/
|
|
4795
|
+
const configured = (receiver, name) => {
|
|
4796
|
+
const bound = receiver[Expressions.BOUND];
|
|
4797
|
+
return typeof bound === 'function' ? bound(name) : receiver[name];
|
|
4798
|
+
};
|
|
4799
|
+
|
|
4738
4800
|
const PLACEHOLDER = /\{(\w+)\}/g;
|
|
4739
4801
|
const POSITIONAL = /\{(\d+)\}/g;
|
|
4740
4802
|
/** @param {any} v @returns {v is Record<string, string>} */
|
|
@@ -4769,7 +4831,9 @@ var fml = (function (exports) {
|
|
|
4769
4831
|
* The translations as a template module: `#l10n:t()` for messages, plus date,
|
|
4770
4832
|
* number and bytes formatting in the page's locale. The template form and the
|
|
4771
4833
|
* imperative `of()` facade both resolve through the registry's scope, so they
|
|
4772
|
-
* always produce the same result for the same key.
|
|
4834
|
+
* always produce the same result for the same key. A template reads `l10n`
|
|
4835
|
+
* and `locale` from its bound data only, the most recent layer winning: an
|
|
4836
|
+
* overlay pushed while rendering (a row, a response) never reaches them.
|
|
4773
4837
|
*/
|
|
4774
4838
|
class Localization {
|
|
4775
4839
|
/**
|
|
@@ -4788,7 +4852,7 @@ var fml = (function (exports) {
|
|
|
4788
4852
|
* @returns {string} the message, or the key itself when the translations do not carry it
|
|
4789
4853
|
*/
|
|
4790
4854
|
static t(key, ...args) {
|
|
4791
|
-
const messages = this
|
|
4855
|
+
const messages = configured(this, 'l10n') ?? {};
|
|
4792
4856
|
let message = Object.hasOwn(messages, key) ? messages[key] : undefined;
|
|
4793
4857
|
if (message === undefined) {
|
|
4794
4858
|
warnOnce(`missing message "${key}"`);
|
|
@@ -4800,7 +4864,7 @@ var fml = (function (exports) {
|
|
|
4800
4864
|
warnOnce(`plural message "${key}" needs a numeric {count}`);
|
|
4801
4865
|
message = message.other;
|
|
4802
4866
|
} else {
|
|
4803
|
-
const locale = this
|
|
4867
|
+
const locale = configured(this, 'locale') ?? navigator?.language ?? 'en';
|
|
4804
4868
|
const category = formatter(Intl.PluralRules, locale).select(named.count);
|
|
4805
4869
|
message = message[category] ?? message.other;
|
|
4806
4870
|
}
|
|
@@ -4840,7 +4904,7 @@ var fml = (function (exports) {
|
|
|
4840
4904
|
* @returns {string}
|
|
4841
4905
|
*/
|
|
4842
4906
|
static date(value, options) {
|
|
4843
|
-
return formatter(Intl.DateTimeFormat, this
|
|
4907
|
+
return formatter(Intl.DateTimeFormat, configured(this, 'locale'), options).format(value);
|
|
4844
4908
|
}
|
|
4845
4909
|
|
|
4846
4910
|
/**
|
|
@@ -4852,7 +4916,7 @@ var fml = (function (exports) {
|
|
|
4852
4916
|
* @returns {string}
|
|
4853
4917
|
*/
|
|
4854
4918
|
static number(value, options) {
|
|
4855
|
-
return formatter(Intl.NumberFormat, this
|
|
4919
|
+
return formatter(Intl.NumberFormat, configured(this, 'locale'), options).format(value);
|
|
4856
4920
|
}
|
|
4857
4921
|
|
|
4858
4922
|
/**
|
|
@@ -4865,7 +4929,7 @@ var fml = (function (exports) {
|
|
|
4865
4929
|
* @returns {string}
|
|
4866
4930
|
*/
|
|
4867
4931
|
static bytes(value) {
|
|
4868
|
-
const format = formatter(Intl.NumberFormat, this
|
|
4932
|
+
const format = formatter(Intl.NumberFormat, configured(this, 'locale'), { maximumFractionDigits: 2 }).format;
|
|
4869
4933
|
if (value >= 1024 * 1024 * 1024) {
|
|
4870
4934
|
return `${format(value / 1024 / 1024 / 1024)}GiB`;
|
|
4871
4935
|
}
|
|
@@ -6681,12 +6745,13 @@ var fml = (function (exports) {
|
|
|
6681
6745
|
*
|
|
6682
6746
|
* The claim setters, the validity protocol and the aria wiring all act on the
|
|
6683
6747
|
* pieces, so a field with no native control returns a focusable piece of its
|
|
6684
|
-
* own chrome as the control. The claims, `value
|
|
6685
|
-
* members that tolerate a not yet rendered
|
|
6686
|
-
* `value` getter answers what the rendered
|
|
6687
|
-
* declared state,
|
|
6688
|
-
* (see `_holdValue`)
|
|
6689
|
-
* the
|
|
6748
|
+
* own chrome as the control. The claims, `value`, `focus()` and
|
|
6749
|
+
* `setCustomValidity` are the members that tolerate a not yet rendered
|
|
6750
|
+
* element: before the render the `value` getter answers what the rendered
|
|
6751
|
+
* field would answer from its current declared state, a value written then is
|
|
6752
|
+
* held and applied by the render (see `_holdValue`), and a problem reported
|
|
6753
|
+
* then is shown by it. The other members need the pieces and so work only
|
|
6754
|
+
* after the render.
|
|
6690
6755
|
*
|
|
6691
6756
|
* The inner controls carry `form=""`, so the host is the only element the form
|
|
6692
6757
|
* sees. Enter pressed in an inner `<input>` of any type but `file`, `button`,
|
|
@@ -6724,6 +6789,8 @@ var fml = (function (exports) {
|
|
|
6724
6789
|
#also = [];
|
|
6725
6790
|
/** @type {{ value: any } | null} the value written before the render, which the render applies */
|
|
6726
6791
|
#held = null;
|
|
6792
|
+
/** @type {{ error: string, context?: string } | null} the problem reported before the render, which the render shows */
|
|
6793
|
+
#reported = null;
|
|
6727
6794
|
/** Sets the internals' role from the subclass's `static ROLE`. */
|
|
6728
6795
|
constructor() {
|
|
6729
6796
|
super();
|
|
@@ -6788,6 +6855,11 @@ var fml = (function (exports) {
|
|
|
6788
6855
|
this.#pendingFocus = null;
|
|
6789
6856
|
control.focus(options);
|
|
6790
6857
|
}
|
|
6858
|
+
if (this.#reported !== null) {
|
|
6859
|
+
const { error, context } = this.#reported;
|
|
6860
|
+
this.#reported = null;
|
|
6861
|
+
this.setCustomValidity(error, context);
|
|
6862
|
+
}
|
|
6791
6863
|
}
|
|
6792
6864
|
/**
|
|
6793
6865
|
* @param {EventTarget} el
|
|
@@ -6867,11 +6939,17 @@ var fml = (function (exports) {
|
|
|
6867
6939
|
* field owning a whole subtree overrides this to route the message to the
|
|
6868
6940
|
* inner control the path names.
|
|
6869
6941
|
*
|
|
6870
|
-
*
|
|
6942
|
+
* Before the render there is no error region to write to: a message is
|
|
6943
|
+
* kept (the last one wins) and shown once the field has rendered, and a
|
|
6944
|
+
* clear drops the one kept. A field whose render failed shows nothing.
|
|
6871
6945
|
* @param {string} [error] the message, empty or missing to clear
|
|
6872
6946
|
* @param {string} [context] the path below this field's name, '' on an exact match
|
|
6873
6947
|
*/
|
|
6874
6948
|
setCustomValidity(error, context) {
|
|
6949
|
+
if (!this.#fieldError) {
|
|
6950
|
+
this.#reported = error ? { error, context } : null;
|
|
6951
|
+
return;
|
|
6952
|
+
}
|
|
6875
6953
|
Attributes.set(this.#announces ?? this.#control, 'aria-invalid', error ? 'true' : null);
|
|
6876
6954
|
if (!error) {
|
|
6877
6955
|
this.internals.setValidity({});
|
|
@@ -7082,11 +7160,16 @@ var fml = (function (exports) {
|
|
|
7082
7160
|
/**
|
|
7083
7161
|
* Calls `_build(conf)` and wires the pieces it answers. When `_build`
|
|
7084
7162
|
* returns a promise the pieces are wired once it settles; a field that
|
|
7085
|
-
* builds synchronously renders synchronously.
|
|
7163
|
+
* builds synchronously renders synchronously. A default slot holding
|
|
7164
|
+
* nothing but whitespace is emptied first, so the label it becomes is
|
|
7165
|
+
* `:empty` and takes no row.
|
|
7086
7166
|
* @param {{ slots: Record<string, DocumentFragment> | undefined }} conf
|
|
7087
7167
|
* @returns {void | Promise<void>}
|
|
7088
7168
|
*/
|
|
7089
7169
|
render(conf) {
|
|
7170
|
+
if (conf.slots?.default && Fragments.isBlank(conf.slots.default)) {
|
|
7171
|
+
conf.slots.default.replaceChildren();
|
|
7172
|
+
}
|
|
7090
7173
|
const built = /** @type {any} */ (this._build(conf));
|
|
7091
7174
|
if (built instanceof Promise) {
|
|
7092
7175
|
return built.then((pieces) => this.#wire(pieces));
|
|
@@ -8426,7 +8509,8 @@ var fml = (function (exports) {
|
|
|
8426
8509
|
* size and count limits it declares.
|
|
8427
8510
|
*
|
|
8428
8511
|
* The `dropzone` and `item-list` attributes show the dropzone and the list of
|
|
8429
|
-
* chosen files
|
|
8512
|
+
* chosen files, and `no-input` takes the picker control out of sight, leaving
|
|
8513
|
+
* it to the keyboard and to assistive technology. A `dropzone` slot replaces the default dropzone's content, and
|
|
8430
8514
|
* an `items` slot holding a template replaces the stock item, rendered with the
|
|
8431
8515
|
* same `files` overlay so that it reads the File objects it lists. Clicking the
|
|
8432
8516
|
* dropzone opens the picker, and the host carries a `dragover` attribute while
|
|
@@ -8469,6 +8553,7 @@ var fml = (function (exports) {
|
|
|
8469
8553
|
'multiple:presence',
|
|
8470
8554
|
'item-list:presence',
|
|
8471
8555
|
'dropzone:presence',
|
|
8556
|
+
'no-input:presence',
|
|
8472
8557
|
'max-files:number',
|
|
8473
8558
|
'max-file-size:number',
|
|
8474
8559
|
'max-total-size:number',
|
|
@@ -8720,7 +8805,7 @@ var fml = (function (exports) {
|
|
|
8720
8805
|
* @returns {string[]|string|null}
|
|
8721
8806
|
*/
|
|
8722
8807
|
get value() {
|
|
8723
|
-
const names = Array.from(this.
|
|
8808
|
+
const names = Array.from(this.files).map((f) => f.name);
|
|
8724
8809
|
return this.multiple ? names : (names[0] ?? null);
|
|
8725
8810
|
}
|
|
8726
8811
|
/**
|
|
@@ -8743,7 +8828,7 @@ var fml = (function (exports) {
|
|
|
8743
8828
|
* The size of the selected files together, in bytes.
|
|
8744
8829
|
* @returns {number}
|
|
8745
8830
|
*/
|
|
8746
|
-
get
|
|
8831
|
+
get totalSize() {
|
|
8747
8832
|
return Array.from(this.files).reduce((a, f) => a + f.size, 0);
|
|
8748
8833
|
}
|
|
8749
8834
|
#maxFiles;
|
|
@@ -8818,6 +8903,23 @@ var fml = (function (exports) {
|
|
|
8818
8903
|
this.#useDropzone = v;
|
|
8819
8904
|
this.reflectTo('dropzone', v);
|
|
8820
8905
|
}
|
|
8906
|
+
#noInput;
|
|
8907
|
+
/**
|
|
8908
|
+
* Whether the picker control is taken out of sight, the dropzone and the
|
|
8909
|
+
* item list carrying the field alone; the stylesheet hides it from the
|
|
8910
|
+
* reflected `no-input` attribute. The control stays in the tab order and
|
|
8911
|
+
* keeps the label, the description and the error, and the dropzone shows
|
|
8912
|
+
* the focus ring while it holds the focus.
|
|
8913
|
+
* @returns {boolean}
|
|
8914
|
+
*/
|
|
8915
|
+
get noInput() {
|
|
8916
|
+
return this.#noInput;
|
|
8917
|
+
}
|
|
8918
|
+
/** @param {boolean} v */
|
|
8919
|
+
set noInput(v) {
|
|
8920
|
+
this.#noInput = v;
|
|
8921
|
+
this.reflectTo('no-input', v);
|
|
8922
|
+
}
|
|
8821
8923
|
}
|
|
8822
8924
|
|
|
8823
8925
|
/** the viewport's breathing room when clamping, in pixels */
|
|
@@ -10581,9 +10683,7 @@ var fml = (function (exports) {
|
|
|
10581
10683
|
static ROLE = 'radiogroup';
|
|
10582
10684
|
static template = `
|
|
10583
10685
|
<fieldset>
|
|
10584
|
-
<legend>
|
|
10585
|
-
{{{{ slots.default }}}}
|
|
10586
|
-
</legend>
|
|
10686
|
+
<legend>{{{{ slots.default }}}}</legend>
|
|
10587
10687
|
<header data-tpl-if="slots.header">
|
|
10588
10688
|
{{{{ slots.header }}}}
|
|
10589
10689
|
</header>
|
|
@@ -10631,6 +10731,9 @@ var fml = (function (exports) {
|
|
|
10631
10731
|
radioEls.forEach((el) => {
|
|
10632
10732
|
el.remove();
|
|
10633
10733
|
});
|
|
10734
|
+
if (Fragments.isBlank(slots.default)) {
|
|
10735
|
+
slots.default.replaceChildren();
|
|
10736
|
+
}
|
|
10634
10737
|
const fragment = this.template().withOverlay({ name, slots, inputsAndLabels }).render();
|
|
10635
10738
|
this.#fieldset = /** @type HTMLElement */ (fragment.firstElementChild);
|
|
10636
10739
|
this.#firstRadio = fragment.querySelector('input[type=radio]');
|
|
@@ -10786,6 +10889,10 @@ var fml = (function (exports) {
|
|
|
10786
10889
|
* current `order` in the cycle `asc`, `desc`, `null`. The button does not
|
|
10787
10890
|
* change its own `order`: whoever handles the event sets it, which `ful-table`
|
|
10788
10891
|
* does once the sorted page has loaded.
|
|
10892
|
+
*
|
|
10893
|
+
* The stylesheet draws an up arrow for `asc` and a down arrow for `desc`. An
|
|
10894
|
+
* unsorted button shows the up arrow dimmed, `asc` being the order its first
|
|
10895
|
+
* activation asks for.
|
|
10789
10896
|
*/
|
|
10790
10897
|
class SortButton extends ParsedElement {
|
|
10791
10898
|
static attributes = ['sorter'];
|
|
@@ -11362,18 +11469,19 @@ var fml = (function (exports) {
|
|
|
11362
11469
|
* writing the size is not a request to start loading, which is what
|
|
11363
11470
|
* `autoload` and `reload()` are for. A write that does not change the size
|
|
11364
11471
|
* does nothing. A write made before the render is replaced by the declared
|
|
11365
|
-
* `page-size` when the table renders
|
|
11472
|
+
* `page-size` when the table renders, or by ten when that is not a number
|
|
11473
|
+
* above zero.
|
|
11366
11474
|
*
|
|
11367
11475
|
* The reload is not awaited: a failure shows the error panel and its
|
|
11368
11476
|
* rejection is unhandled.
|
|
11369
11477
|
* @param {number|string|null|undefined} value a number or a string of
|
|
11370
11478
|
* one, such as a select's value; absent or null is the default of ten,
|
|
11371
11479
|
* so removing the attribute restores it, and a value that is not a
|
|
11372
|
-
* number is ignored
|
|
11480
|
+
* finite number above zero is ignored
|
|
11373
11481
|
*/
|
|
11374
11482
|
set pageSize(value) {
|
|
11375
11483
|
const size = Number(value ?? 10);
|
|
11376
|
-
if (Number.
|
|
11484
|
+
if (!Number.isFinite(size) || size <= 0 || size === this.#latestRequest.pageRequest.size) {
|
|
11377
11485
|
return;
|
|
11378
11486
|
}
|
|
11379
11487
|
this.#latestRequest = { ...this.#latestRequest, pageRequest: { page: 0, size } };
|
|
@@ -11492,10 +11600,11 @@ var fml = (function (exports) {
|
|
|
11492
11600
|
await Rendering.waitForChildren(this);
|
|
11493
11601
|
|
|
11494
11602
|
const maybeForm = /** @type any */ (Nodes.queryChildren(this, 'ful-form'));
|
|
11603
|
+
const declaredSize = this.declared('page-size');
|
|
11495
11604
|
this.#latestRequest = {
|
|
11496
11605
|
pageRequest: {
|
|
11497
11606
|
page: 0,
|
|
11498
|
-
size:
|
|
11607
|
+
size: Number.isFinite(declaredSize) && declaredSize > 0 ? declaredSize : 10,
|
|
11499
11608
|
},
|
|
11500
11609
|
sortRequest: schema.sort,
|
|
11501
11610
|
filterRequest: maybeForm?.values ?? {},
|
|
@@ -11579,7 +11688,10 @@ var fml = (function (exports) {
|
|
|
11579
11688
|
* `aria-busy` is removed, `load:failure` is dispatched (bubbling, not
|
|
11580
11689
|
* cancelable) with `detail` set to
|
|
11581
11690
|
* `{ pageRequest, sortRequest, filterRequest, exception }`, and the
|
|
11582
|
-
* returned promise rejects with the same error.
|
|
11691
|
+
* returned promise rejects with the same error. An answer the table cannot
|
|
11692
|
+
* page fails the same way, with an error saying so: one whose `size` is
|
|
11693
|
+
* not a number, as a response of another shape read without a
|
|
11694
|
+
* `response-mapper` is, or a page size of zero.
|
|
11583
11695
|
*
|
|
11584
11696
|
* A load started after this one supersedes it: when this one's answer or
|
|
11585
11697
|
* failure arrives it changes nothing, dispatches nothing and resolves;
|
|
@@ -11607,6 +11719,11 @@ var fml = (function (exports) {
|
|
|
11607
11719
|
return;
|
|
11608
11720
|
}
|
|
11609
11721
|
lastPage = Math.max(0, Math.ceil(pageResponse.size / pageRequest.size) - 1);
|
|
11722
|
+
if (!Number.isFinite(lastPage)) {
|
|
11723
|
+
throw new Error(
|
|
11724
|
+
`ful-table cannot page the loader's answer: it needs { data, size }, size being the total number of rows, and got a size of ${pageResponse.size} for pages of ${pageRequest.size}. A response of another shape needs a response-mapper`,
|
|
11725
|
+
);
|
|
11726
|
+
}
|
|
11610
11727
|
if (pageRequest.page <= lastPage) {
|
|
11611
11728
|
this.#latestRequest = { pageRequest, sortRequest, filterRequest };
|
|
11612
11729
|
this.#loaded = true;
|
|
@@ -12872,8 +12989,10 @@ var fml = (function (exports) {
|
|
|
12872
12989
|
* failure's problems, one per line, or its message when it carries none.
|
|
12873
12990
|
*/
|
|
12874
12991
|
class SectionRequests {
|
|
12875
|
-
/** @type {WeakSet<Element>} */
|
|
12992
|
+
/** @type {WeakSet<Element>} the sections a first delivery succeeded for */
|
|
12876
12993
|
#entered = new WeakSet();
|
|
12994
|
+
/** @type {WeakSet<Element>} the sections whose first delivery is in flight */
|
|
12995
|
+
#entering = new WeakSet();
|
|
12877
12996
|
/** @type {WeakMap<Element, Claims>} */
|
|
12878
12997
|
#claims = new WeakMap();
|
|
12879
12998
|
|
|
@@ -12882,9 +13001,12 @@ var fml = (function (exports) {
|
|
|
12882
13001
|
* is given, then `section:requested:<name>` when a name is given, each
|
|
12883
13002
|
* bubbling from `host` with the same detail `{ name, section, index, first }`,
|
|
12884
13003
|
* and awaits every answer attached to any of them. `first` is true until a
|
|
12885
|
-
*
|
|
12886
|
-
*
|
|
12887
|
-
*
|
|
13004
|
+
* delivery of the section succeeds: a request nobody listened to does not
|
|
13005
|
+
* spend it, and neither does one whose answers failed, so the next request
|
|
13006
|
+
* is a first one again. While a first delivery is in flight a newer request
|
|
13007
|
+
* for the same section carries `first` false, so the content is not
|
|
13008
|
+
* delivered twice. A request with answers removes the error a previous
|
|
13009
|
+
* failure painted before it waits.
|
|
12888
13010
|
* @param {Element} host the component the events are dispatched on
|
|
12889
13011
|
* @param {Element} section the element the answers deliver into
|
|
12890
13012
|
* @param {string|null} name the section's `data-step`, when it has one
|
|
@@ -12895,7 +13017,7 @@ var fml = (function (exports) {
|
|
|
12895
13017
|
* problems in the section unless a newer request superseded this one
|
|
12896
13018
|
*/
|
|
12897
13019
|
async request(host, section, name, index) {
|
|
12898
|
-
const first = !this.#entered.has(section);
|
|
13020
|
+
const first = !this.#entered.has(section) && !this.#entering.has(section);
|
|
12899
13021
|
const detail = { name, section, index, first };
|
|
12900
13022
|
const types = [
|
|
12901
13023
|
'section:requested',
|
|
@@ -12913,7 +13035,9 @@ var fml = (function (exports) {
|
|
|
12913
13035
|
if (promises.length === 0) {
|
|
12914
13036
|
return undefined;
|
|
12915
13037
|
}
|
|
12916
|
-
|
|
13038
|
+
if (first) {
|
|
13039
|
+
this.#entering.add(section);
|
|
13040
|
+
}
|
|
12917
13041
|
let claims = this.#claims.get(section);
|
|
12918
13042
|
if (!claims) {
|
|
12919
13043
|
claims = new Claims();
|
|
@@ -12929,13 +13053,20 @@ var fml = (function (exports) {
|
|
|
12929
13053
|
}
|
|
12930
13054
|
});
|
|
12931
13055
|
try {
|
|
12932
|
-
|
|
13056
|
+
const answers = await Promise.all(promises);
|
|
13057
|
+
if (first) {
|
|
13058
|
+
this.#entered.add(section);
|
|
13059
|
+
}
|
|
13060
|
+
return answers;
|
|
12933
13061
|
} catch (cause) {
|
|
12934
13062
|
if (owned()) {
|
|
12935
13063
|
this.#paintError(section, cause);
|
|
12936
13064
|
}
|
|
12937
13065
|
throw cause;
|
|
12938
13066
|
} finally {
|
|
13067
|
+
if (first) {
|
|
13068
|
+
this.#entering.delete(section);
|
|
13069
|
+
}
|
|
12939
13070
|
cancelAnimationFrame(frame);
|
|
12940
13071
|
if (owned()) {
|
|
12941
13072
|
section.toggleAttribute('loading', false);
|
|
@@ -13052,15 +13183,27 @@ var fml = (function (exports) {
|
|
|
13052
13183
|
/**
|
|
13053
13184
|
* Calls `dismiss` when a press and its release both land on the backdrop,
|
|
13054
13185
|
* so a selection dragged out of the panel and released outside does not.
|
|
13186
|
+
* The backdrop is where the pointer is outside the dialog's rectangle: a
|
|
13187
|
+
* part of the panel no child covers, the room around the error section
|
|
13188
|
+
* for one, is the panel.
|
|
13055
13189
|
* @param {() => void} dismiss
|
|
13056
13190
|
*/
|
|
13057
13191
|
onBackdrop(dismiss) {
|
|
13192
|
+
const onBackdrop = (/** @type MouseEvent */ e) => {
|
|
13193
|
+
if (e.target !== this.#dialog) {
|
|
13194
|
+
return false;
|
|
13195
|
+
}
|
|
13196
|
+
const panel = this.#dialog.getBoundingClientRect();
|
|
13197
|
+
return (
|
|
13198
|
+
e.clientX < panel.left || e.clientX > panel.right || e.clientY < panel.top || e.clientY > panel.bottom
|
|
13199
|
+
);
|
|
13200
|
+
};
|
|
13058
13201
|
let pressedOutside = false;
|
|
13059
13202
|
this.#dialog.addEventListener('mousedown', (e) => {
|
|
13060
|
-
pressedOutside = e
|
|
13203
|
+
pressedOutside = onBackdrop(e);
|
|
13061
13204
|
});
|
|
13062
13205
|
this.#dialog.addEventListener('click', (e) => {
|
|
13063
|
-
if (pressedOutside && e
|
|
13206
|
+
if (pressedOutside && onBackdrop(e)) {
|
|
13064
13207
|
dismiss();
|
|
13065
13208
|
}
|
|
13066
13209
|
});
|
|
@@ -13337,7 +13480,7 @@ var fml = (function (exports) {
|
|
|
13337
13480
|
* opening, hides the error and loading sections, and dispatches
|
|
13338
13481
|
* `section:requested` on the element (bubbling, with
|
|
13339
13482
|
* `detail: { section, first, name: null, index: null }`, `section` being
|
|
13340
|
-
* the body and `first` true
|
|
13483
|
+
* the body and `first` true until a delivery succeeds), so a
|
|
13341
13484
|
* listener can fill the body. A failed request is painted into the body
|
|
13342
13485
|
* and does not reject. On a dialog already open nothing is shown or
|
|
13343
13486
|
* requested, and the call waits for the same ending as the calls before it;
|
|
@@ -13589,11 +13732,7 @@ var fml = (function (exports) {
|
|
|
13589
13732
|
#answer = null;
|
|
13590
13733
|
#resolvers = [];
|
|
13591
13734
|
#closing = false;
|
|
13592
|
-
#
|
|
13593
|
-
if (e.target === this.#dialog) {
|
|
13594
|
-
this.#closeNow();
|
|
13595
|
-
}
|
|
13596
|
-
};
|
|
13735
|
+
#slides = new Claims();
|
|
13597
13736
|
/**
|
|
13598
13737
|
* @param {{ slots: Record<string, DocumentFragment> }} c
|
|
13599
13738
|
*/
|
|
@@ -13720,7 +13859,7 @@ var fml = (function (exports) {
|
|
|
13720
13859
|
* opening, hides the error and loading sections, and dispatches
|
|
13721
13860
|
* `section:requested` on the element (bubbling, with
|
|
13722
13861
|
* `detail: { section, first, name: null, index: null }`, `section` being
|
|
13723
|
-
* the content section and `first` true
|
|
13862
|
+
* the content section and `first` true until a delivery succeeds),
|
|
13724
13863
|
* so a listener can fill the content. A failed request is painted into the
|
|
13725
13864
|
* content section and does not reject. On a drawer already open nothing is
|
|
13726
13865
|
* shown or requested, except that one sliding out stops and stays open,
|
|
@@ -13771,11 +13910,13 @@ var fml = (function (exports) {
|
|
|
13771
13910
|
/**
|
|
13772
13911
|
* Slides the drawer out and closes it when the animation ends, setting the
|
|
13773
13912
|
* `closing` attribute on the native dialog meanwhile; the `close` event is
|
|
13774
|
-
* dispatched then, after this call has returned.
|
|
13775
|
-
*
|
|
13776
|
-
*
|
|
13777
|
-
*
|
|
13778
|
-
*
|
|
13913
|
+
* dispatched then, after this call has returned. An animation the page
|
|
13914
|
+
* cancels, by hiding the drawer or an ancestor of it, closes it as one
|
|
13915
|
+
* that ended does. Where the user prefers reduced motion, or no animation
|
|
13916
|
+
* runs, it closes at once. A non-empty result answers as the button
|
|
13917
|
+
* carrying that `data-result` would; no result, or an empty one, is a
|
|
13918
|
+
* dismissal. Does nothing on a drawer that is closed or already sliding
|
|
13919
|
+
* out.
|
|
13779
13920
|
* @param {string} [result]
|
|
13780
13921
|
*/
|
|
13781
13922
|
close(result) {
|
|
@@ -13790,12 +13931,18 @@ var fml = (function (exports) {
|
|
|
13790
13931
|
return;
|
|
13791
13932
|
}
|
|
13792
13933
|
this.#dialog.setAttribute('closing', '');
|
|
13793
|
-
|
|
13934
|
+
const animations = this.#dialog.getAnimations();
|
|
13935
|
+
if (animations.length === 0) {
|
|
13794
13936
|
this.#closeNow();
|
|
13795
13937
|
return;
|
|
13796
13938
|
}
|
|
13797
13939
|
this.#closing = true;
|
|
13798
|
-
this.#
|
|
13940
|
+
const slide = this.#slides.take();
|
|
13941
|
+
Promise.allSettled(animations.map((animation) => animation.finished)).then(() => {
|
|
13942
|
+
if (!slide.stale) {
|
|
13943
|
+
this.#closeNow();
|
|
13944
|
+
}
|
|
13945
|
+
});
|
|
13799
13946
|
}
|
|
13800
13947
|
#closeNow() {
|
|
13801
13948
|
this.#stopSlidingOut();
|
|
@@ -13803,7 +13950,7 @@ var fml = (function (exports) {
|
|
|
13803
13950
|
}
|
|
13804
13951
|
#stopSlidingOut() {
|
|
13805
13952
|
this.#closing = false;
|
|
13806
|
-
this.#
|
|
13953
|
+
this.#slides.invalidate();
|
|
13807
13954
|
this.#dialog.removeAttribute('closing');
|
|
13808
13955
|
}
|
|
13809
13956
|
/** @returns {boolean} whether this call is the one that opened the modal */
|
|
@@ -14348,7 +14495,7 @@ var fml = (function (exports) {
|
|
|
14348
14495
|
}
|
|
14349
14496
|
/**
|
|
14350
14497
|
* Fires the section requests again for a panel, whether it is visible or
|
|
14351
|
-
* not, with `first` false once an earlier
|
|
14498
|
+
* not, with `first` false once an earlier delivery succeeded.
|
|
14352
14499
|
* @param {number | Element} ref the panel's index, or the panel itself
|
|
14353
14500
|
* @returns {Promise<any[] | undefined> | undefined} a promise resolving to
|
|
14354
14501
|
* the answers, or to undefined when nobody listened or the delivery
|
|
@@ -14571,7 +14718,7 @@ var fml = (function (exports) {
|
|
|
14571
14718
|
}
|
|
14572
14719
|
/**
|
|
14573
14720
|
* Fires the section requests again for a section, whether it is current or
|
|
14574
|
-
* not, with `first` false once an earlier
|
|
14721
|
+
* not, with `first` false once an earlier delivery succeeded.
|
|
14575
14722
|
* @param {string | Element} ref the section's `data-step`, or the section
|
|
14576
14723
|
* itself
|
|
14577
14724
|
* @returns {Promise<any[] | undefined> | undefined} a promise resolving to
|
|
@@ -14850,8 +14997,11 @@ var fml = (function (exports) {
|
|
|
14850
14997
|
/**
|
|
14851
14998
|
* @param {{ language?: string, translations?: Record<string, any>, httpClient?: HttpClient }} [options]
|
|
14852
14999
|
* `language` is fixed for the page: a full BCP-47 tag or a primary subtag,
|
|
14853
|
-
* defaulting to `navigator.language`, then to 'en'
|
|
14854
|
-
*
|
|
15000
|
+
* defaulting to `navigator.language`, then to 'en', an empty one included.
|
|
15001
|
+
* It is canonicalized (`IT-it` and `it_IT` both read `it-IT`), and one that
|
|
15002
|
+
* is no language tag at all throws here, rather than at the first message
|
|
15003
|
+
* or number formatted in it. Its primary subtag picks the built-in
|
|
15004
|
+
* translations (en, it, es, fr); the messages
|
|
14855
15005
|
* are the english ones, overridden by that language's, overridden by
|
|
14856
15006
|
* `translations`, so a language without built-in translations reads in
|
|
14857
15007
|
* english. `translations` is a flat map of keys to messages: reword built-in
|
|
@@ -14859,9 +15009,18 @@ var fml = (function (exports) {
|
|
|
14859
15009
|
* `httpClient` is the client every ful component fetches through, registered
|
|
14860
15010
|
* as the `http-client` component; without one, `configure` builds a client
|
|
14861
15011
|
* that sends the csrf token and navigates to '/' on a 401 response.
|
|
15012
|
+
* @throws {RangeError} when `language` is not a language tag
|
|
14862
15013
|
*/
|
|
14863
15014
|
constructor(options = {}) {
|
|
14864
|
-
|
|
15015
|
+
const language = options.language || navigator?.language || 'en';
|
|
15016
|
+
try {
|
|
15017
|
+
// unicode locale identifiers separate subtags with an underscore too, Intl takes the dash alone
|
|
15018
|
+
this.#language = Intl.getCanonicalLocales(String(language).replaceAll('_', '-'))[0];
|
|
15019
|
+
} catch {
|
|
15020
|
+
throw new RangeError(
|
|
15021
|
+
`Plugin: language '${language}' is not a language tag: pass a BCP-47 tag such as 'it' or 'it-IT', or leave it out for the browser's language`,
|
|
15022
|
+
);
|
|
15023
|
+
}
|
|
14865
15024
|
this.#translations = options.translations ?? {};
|
|
14866
15025
|
this.#httpClient = options.httpClient ?? null;
|
|
14867
15026
|
}
|
|
@@ -14870,7 +15029,7 @@ var fml = (function (exports) {
|
|
|
14870
15029
|
* Defines on `registry` the `l10n` module, the `http-client` component,
|
|
14871
15030
|
* every `ful-*` element, the `loaders:select`, `loaders:form` and
|
|
14872
15031
|
* `loaders:table` components, and an overlay publishing `l10n` (the merged
|
|
14873
|
-
* messages) and `locale` (the `language
|
|
15032
|
+
* messages) and `locale` (the `language`, canonicalized) to every template.
|
|
14874
15033
|
* @param {{ defineModule(name: string, module: any): any, defineComponent(name: string, component: any): any, defineElement(tag: string, klass: CustomElementConstructor): any, defineOverlay(...data: any[]): any }} registry
|
|
14875
15034
|
* the ftl `Registry`, each of those methods answering the registry itself
|
|
14876
15035
|
* @returns {void}
|
|
@@ -14878,7 +15037,7 @@ var fml = (function (exports) {
|
|
|
14878
15037
|
configure(registry) {
|
|
14879
15038
|
const httpClient =
|
|
14880
15039
|
this.#httpClient ?? HttpClient.builder().withCsrfToken().withRedirectOnUnauthorized('/').build();
|
|
14881
|
-
const language = this.#language.split('-')[0]
|
|
15040
|
+
const language = this.#language.split('-')[0];
|
|
14882
15041
|
const l10n = { ...BUILTIN.en, ...BUILTIN[language], ...this.#translations };
|
|
14883
15042
|
registry
|
|
14884
15043
|
.defineModule('l10n', Localization)
|
|
@@ -14961,7 +15120,6 @@ var fml = (function (exports) {
|
|
|
14961
15120
|
SortButton: SortButton,
|
|
14962
15121
|
Table: Table,
|
|
14963
15122
|
TableLoader: TableLoader,
|
|
14964
|
-
TableSchemaParser: TableSchemaParser,
|
|
14965
15123
|
Tabs: Tabs,
|
|
14966
15124
|
TextFilter: TextFilter,
|
|
14967
15125
|
Timing: Timing,
|
|
@@ -15040,7 +15198,6 @@ var fml = (function (exports) {
|
|
|
15040
15198
|
exports.SortButton = SortButton;
|
|
15041
15199
|
exports.Table = Table;
|
|
15042
15200
|
exports.TableLoader = TableLoader;
|
|
15043
|
-
exports.TableSchemaParser = TableSchemaParser;
|
|
15044
15201
|
exports.Tabs = Tabs;
|
|
15045
15202
|
exports.Template = Template;
|
|
15046
15203
|
exports.Templates = Templates;
|