@optionfactory/fml 9.0.0-rc10 → 9.0.0-rc11
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 +213 -13
- package/dist/fml.css +6 -5
- package/dist/fml.css.map +1 -1
- package/dist/fml.iife.js +183 -35
- 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/ful.css +6 -5
- package/dist/ful.css.map +1 -1
- package/dist/ful.d.mts +27 -1
- package/dist/ful.iife.js +182 -35
- package/dist/ful.iife.js.map +1 -1
- package/dist/ful.iife.min.js +1 -1
- package/dist/ful.iife.min.js.map +1 -1
- package/dist/ful.min.mjs +1 -1
- package/dist/ful.min.mjs.map +1 -1
- package/dist/ful.mjs +182 -36
- package/dist/ful.mjs.map +1 -1
- package/dist/vscode.html-custom-data.json +94 -12
- package/dist/web-types.json +213 -14
- package/package.json +1 -1
package/dist/ful.mjs
CHANGED
|
@@ -1407,6 +1407,22 @@ const compiled = (attr, pattern) =>
|
|
|
1407
1407
|
*/
|
|
1408
1408
|
const inheritedAutocomplete = (el) => el.closest('form')?.getAttribute('autocomplete') ?? null;
|
|
1409
1409
|
|
|
1410
|
+
/** Rendering a text input is what costs these the native min/max/step enforcement. */
|
|
1411
|
+
const INPUT_MODES = { numeric: 'numeric', decimal: 'decimal' };
|
|
1412
|
+
|
|
1413
|
+
const signed = (v, digits) => (v.startsWith('-') ? '-' : '') + digits(v);
|
|
1414
|
+
|
|
1415
|
+
const NUMERIC_FILTERS = {
|
|
1416
|
+
numeric: (v) => signed(v, (t) => t.replace(/\D/g, '')),
|
|
1417
|
+
//one separator, the first: the rest are a slip while typing, not a value
|
|
1418
|
+
decimal: (v) =>
|
|
1419
|
+
signed(v, (t) => {
|
|
1420
|
+
const kept = t.replace(/[^\d.,]/g, '');
|
|
1421
|
+
const at = kept.search(/[.,]/);
|
|
1422
|
+
return at === -1 ? kept : kept.slice(0, at + 1) + kept.slice(at + 1).replace(/[.,]/g, '');
|
|
1423
|
+
}),
|
|
1424
|
+
};
|
|
1425
|
+
|
|
1410
1426
|
const warnedBoth = new WeakSet();
|
|
1411
1427
|
const filterOf = (el) => {
|
|
1412
1428
|
const keep = el.declared('keep');
|
|
@@ -1426,7 +1442,7 @@ const filterOf = (el) => {
|
|
|
1426
1442
|
const re = compiled('reject', reject);
|
|
1427
1443
|
return re && ((v) => v.replace(re, ''));
|
|
1428
1444
|
}
|
|
1429
|
-
return null;
|
|
1445
|
+
return NUMERIC_FILTERS[el._type()] ?? null;
|
|
1430
1446
|
};
|
|
1431
1447
|
|
|
1432
1448
|
/** A labelled text input over any native type or textarea; the temporal inputs are its subclasses. */
|
|
@@ -1462,9 +1478,17 @@ class Input extends Field {
|
|
|
1462
1478
|
return this.declared('type') ?? (this.declared('v-type') === 'number' ? 'number' : 'text');
|
|
1463
1479
|
}
|
|
1464
1480
|
_build({ slots }) {
|
|
1465
|
-
const
|
|
1481
|
+
const declared = this._type();
|
|
1482
|
+
const mode = INPUT_MODES[declared];
|
|
1483
|
+
//a numeric widget is a text input: the type names the keyboard, not the
|
|
1484
|
+
//control, so the rendered markup stays valid html
|
|
1485
|
+
const type = mode ? 'text' : declared;
|
|
1466
1486
|
const fragment = this.template().withOverlay({ type, slots }).render();
|
|
1467
1487
|
this._input = fragment.querySelector('input,textarea');
|
|
1488
|
+
if (mode) {
|
|
1489
|
+
//before the passthrough, which stays the last word
|
|
1490
|
+
Attributes.set(this._input, 'inputmode', mode);
|
|
1491
|
+
}
|
|
1468
1492
|
|
|
1469
1493
|
//the browser reads autocomplete off the control it is classifying, so the
|
|
1470
1494
|
//field's own token, or the form's where it declares none, is put there.
|
|
@@ -1516,13 +1540,15 @@ class Input extends Field {
|
|
|
1516
1540
|
if (trimmed === '') {
|
|
1517
1541
|
return null;
|
|
1518
1542
|
}
|
|
1543
|
+
//the wire carries a dot wherever the comma is what the keyboard shows
|
|
1544
|
+
const normalized = this._type() === 'decimal' ? trimmed.replaceAll(',', '.') : trimmed;
|
|
1519
1545
|
if (this.declared('v-type') === 'number') {
|
|
1520
1546
|
//typed values are an explicit opt in, as the select's k-type: blank
|
|
1521
1547
|
//stays null, and a value that does not decode is kept as it is
|
|
1522
|
-
const n = Number(
|
|
1523
|
-
return Number.isNaN(n) ?
|
|
1548
|
+
const n = Number(normalized);
|
|
1549
|
+
return Number.isNaN(n) ? normalized : n;
|
|
1524
1550
|
}
|
|
1525
|
-
return
|
|
1551
|
+
return normalized;
|
|
1526
1552
|
}
|
|
1527
1553
|
set value(value) {
|
|
1528
1554
|
this._input.value = value === '' || value === undefined ? null : value;
|
|
@@ -2514,10 +2540,27 @@ class SelectLoader {
|
|
|
2514
2540
|
method: el.declared('method') ?? 'POST',
|
|
2515
2541
|
mode: el.declared('mode'),
|
|
2516
2542
|
prefetch: el.declared('preload'),
|
|
2517
|
-
revision: el
|
|
2543
|
+
revision: SelectLoader.#revisionFrom(el),
|
|
2518
2544
|
responseMapper: SelectLoader.#responseMapperFrom(el),
|
|
2519
2545
|
});
|
|
2520
2546
|
}
|
|
2547
|
+
static #revisionFrom(el) {
|
|
2548
|
+
const declared = el.declared('revision');
|
|
2549
|
+
if (declared !== '') {
|
|
2550
|
+
return declared;
|
|
2551
|
+
}
|
|
2552
|
+
const configured = el.component('revision');
|
|
2553
|
+
if (configured === undefined || configured === null) {
|
|
2554
|
+
//not caching while the markup says to cache is the quiet failure
|
|
2555
|
+
//this branch exists to make loud
|
|
2556
|
+
console.warn(
|
|
2557
|
+
"a valueless revision asks the registry for one, and no 'revision' component is defined: the vocabulary will not be cached",
|
|
2558
|
+
el,
|
|
2559
|
+
);
|
|
2560
|
+
return null;
|
|
2561
|
+
}
|
|
2562
|
+
return typeof configured === 'function' ? configured() : configured;
|
|
2563
|
+
}
|
|
2521
2564
|
static #responseMapperFrom(el) {
|
|
2522
2565
|
if (el.declared('k-expr') && el.declared('l-expr')) {
|
|
2523
2566
|
return (response) => {
|
|
@@ -2550,7 +2593,7 @@ class Dropdown extends ParsedElement {
|
|
|
2550
2593
|
static slots = true;
|
|
2551
2594
|
static template = `
|
|
2552
2595
|
<ful-spinner class="centered" role="status" hidden><span class="ful-sr-only">{{ #l10n:t('spinner.loading') }}</span></ful-spinner>
|
|
2553
|
-
<
|
|
2596
|
+
<ful-empty data-ref="empty" aria-live="polite" hidden>{{ #l10n:t('dropdown.empty') }}</ful-empty>
|
|
2554
2597
|
<menu tabindex="-1" role="listbox" hidden></menu>
|
|
2555
2598
|
`;
|
|
2556
2599
|
static templates = {
|
|
@@ -2572,7 +2615,7 @@ class Dropdown extends ParsedElement {
|
|
|
2572
2615
|
? this.template('options')
|
|
2573
2616
|
: Templates.fromFragment(slots.default);
|
|
2574
2617
|
this.#spinner = fragment.querySelector('ful-spinner');
|
|
2575
|
-
this.#empty = fragment.querySelector('
|
|
2618
|
+
this.#empty = fragment.querySelector('[data-ref=empty]');
|
|
2576
2619
|
this.#menu = fragment.querySelector('menu');
|
|
2577
2620
|
//the listbox is named so a combobox can point aria-controls and
|
|
2578
2621
|
//aria-activedescendant at it: a reference to an unnamed element resolves
|
|
@@ -2932,7 +2975,7 @@ class Select extends Field {
|
|
|
2932
2975
|
return;
|
|
2933
2976
|
}
|
|
2934
2977
|
this.#abortdload();
|
|
2935
|
-
this.#close();
|
|
2978
|
+
this.#close(true);
|
|
2936
2979
|
});
|
|
2937
2980
|
this.#input.addEventListener('keydown', (e) => {
|
|
2938
2981
|
if (!this._interactive()) {
|
|
@@ -2965,7 +3008,6 @@ class Select extends Field {
|
|
|
2965
3008
|
this.#editing = false;
|
|
2966
3009
|
this.#values.set(this.#coerceKey(e.detail.entry.key), e.detail.entry);
|
|
2967
3010
|
this.#changed();
|
|
2968
|
-
this.#syncBadges();
|
|
2969
3011
|
this.#input.focus();
|
|
2970
3012
|
this.#ddmenu.hide();
|
|
2971
3013
|
if (!this.#multiple) {
|
|
@@ -3022,7 +3064,6 @@ class Select extends Field {
|
|
|
3022
3064
|
}
|
|
3023
3065
|
this.#values.delete(key);
|
|
3024
3066
|
this.#changed();
|
|
3025
|
-
this.#syncBadges();
|
|
3026
3067
|
}
|
|
3027
3068
|
#chipKeydown(e, badge) {
|
|
3028
3069
|
switch (e.code) {
|
|
@@ -3118,7 +3159,7 @@ class Select extends Field {
|
|
|
3118
3159
|
}
|
|
3119
3160
|
case 'Tab': {
|
|
3120
3161
|
this.#abortdload();
|
|
3121
|
-
this.#close();
|
|
3162
|
+
this.#close(true);
|
|
3122
3163
|
break;
|
|
3123
3164
|
}
|
|
3124
3165
|
}
|
|
@@ -3134,12 +3175,25 @@ class Select extends Field {
|
|
|
3134
3175
|
}
|
|
3135
3176
|
return;
|
|
3136
3177
|
}
|
|
3137
|
-
this.#
|
|
3138
|
-
this.#ddmenu.moveOrShow(forward, () => this.#loader.load(this.#input.value), [...this.#values.keys()]);
|
|
3178
|
+
this.#ddmenu.moveOrShow(forward, () => this.#loader.load(this.#query()), [...this.#values.keys()]);
|
|
3139
3179
|
}
|
|
3140
|
-
|
|
3180
|
+
/**
|
|
3181
|
+
* @param commit whether the user is leaving the field, which only a blur or
|
|
3182
|
+
* a Tab is: Escape cancels, and closing the list or a claim landing leaves
|
|
3183
|
+
* the edit in progress
|
|
3184
|
+
*/
|
|
3185
|
+
#close(commit = false) {
|
|
3141
3186
|
this.#ddmenu.hide();
|
|
3187
|
+
//emptying the box and leaving is a clear: redisplaying the label would
|
|
3188
|
+
//discard the edit without a word. A multiple select keeps its box empty
|
|
3189
|
+
//by design, so only a single one can mean it
|
|
3190
|
+
const cleared =
|
|
3191
|
+
commit && this.#editing && !this.#multiple && this.#input.value === '' && this.#values.size !== 0;
|
|
3142
3192
|
this.#editing = false;
|
|
3193
|
+
if (cleared) {
|
|
3194
|
+
this.#values.clear();
|
|
3195
|
+
this.#changed();
|
|
3196
|
+
}
|
|
3143
3197
|
this.#display();
|
|
3144
3198
|
}
|
|
3145
3199
|
/**
|
|
@@ -3148,14 +3202,15 @@ class Select extends Field {
|
|
|
3148
3202
|
* highlighted.
|
|
3149
3203
|
*/
|
|
3150
3204
|
#open() {
|
|
3151
|
-
this.#
|
|
3152
|
-
return this.#ddmenu.show(() => this.#loader.load(this.#input.value), [...this.#values.keys()]);
|
|
3205
|
+
return this.#ddmenu.show(() => this.#loader.load(this.#query()), [...this.#values.keys()]);
|
|
3153
3206
|
}
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3207
|
+
/**
|
|
3208
|
+
* Browsing is the whole vocabulary, not the label already chosen, and the
|
|
3209
|
+
* label stays on screen while it happens: a select showing nothing reads as
|
|
3210
|
+
* an empty one.
|
|
3211
|
+
*/
|
|
3212
|
+
#query() {
|
|
3213
|
+
return this.#editing ? this.#input.value : '';
|
|
3159
3214
|
}
|
|
3160
3215
|
#display() {
|
|
3161
3216
|
const entry = this.#values.values().next().value;
|
|
@@ -3166,6 +3221,9 @@ class Select extends Field {
|
|
|
3166
3221
|
return [...this.#values.values()];
|
|
3167
3222
|
}
|
|
3168
3223
|
#changed() {
|
|
3224
|
+
//settled before the event: a listener reading the control during it has to
|
|
3225
|
+
//see the selection that caused it
|
|
3226
|
+
this.#syncBadges();
|
|
3169
3227
|
//the detail carries the keys the value property answers with, as every
|
|
3170
3228
|
//other field's does, and the labeled selection beside them
|
|
3171
3229
|
this._notifyChange({ entry: this.entry });
|
|
@@ -3812,6 +3870,7 @@ class Table extends ParsedElement {
|
|
|
3812
3870
|
static slots = true;
|
|
3813
3871
|
static config = {
|
|
3814
3872
|
searchIcon: 'search',
|
|
3873
|
+
emptyIcon: 'inbox',
|
|
3815
3874
|
};
|
|
3816
3875
|
static template = `
|
|
3817
3876
|
<ful-form data-tpl-if="slots.filters">
|
|
@@ -3825,10 +3884,21 @@ class Table extends ParsedElement {
|
|
|
3825
3884
|
<tbody data-ref="initial">
|
|
3826
3885
|
<tr>
|
|
3827
3886
|
<td data-tpl-colspan="schema.length">
|
|
3828
|
-
<
|
|
3829
|
-
<
|
|
3887
|
+
<ful-empty>
|
|
3888
|
+
<ful-icon data-tpl-if="config.searchIcon" data-tpl-name="config.searchIcon" aria-hidden="true"></ful-icon>
|
|
3830
3889
|
{{ #l10n:t('table.initial') }}
|
|
3831
|
-
</
|
|
3890
|
+
</ful-empty>
|
|
3891
|
+
</td>
|
|
3892
|
+
</tr>
|
|
3893
|
+
</tbody>
|
|
3894
|
+
<tbody data-ref="empty" hidden>
|
|
3895
|
+
<tr>
|
|
3896
|
+
<td data-tpl-colspan="schema.length">
|
|
3897
|
+
<ful-empty data-tpl-if="slots.empty">{{{{ slots.empty }}}}</ful-empty>
|
|
3898
|
+
<ful-empty data-tpl-if="!slots.empty">
|
|
3899
|
+
<ful-icon data-tpl-if="config.emptyIcon" data-tpl-name="config.emptyIcon" aria-hidden="true"></ful-icon>
|
|
3900
|
+
{{ #l10n:t('table.no-data') }}
|
|
3901
|
+
</ful-empty>
|
|
3832
3902
|
</td>
|
|
3833
3903
|
</tr>
|
|
3834
3904
|
</tbody>
|
|
@@ -3858,11 +3928,6 @@ class Table extends ParsedElement {
|
|
|
3858
3928
|
`;
|
|
3859
3929
|
static templates = {
|
|
3860
3930
|
row: `
|
|
3861
|
-
<tr data-tpl-if="pageResponse.data.length == 0">
|
|
3862
|
-
<td data-tpl-colspan="schema.length">
|
|
3863
|
-
{{ #l10n:t('table.no-data') }}
|
|
3864
|
-
</td>
|
|
3865
|
-
</tr>
|
|
3866
3931
|
{{{{ schema.rowsTemplate.withOverlay({'rows': pageResponse.data}).render() }}}}
|
|
3867
3932
|
`,
|
|
3868
3933
|
};
|
|
@@ -3871,6 +3936,7 @@ class Table extends ParsedElement {
|
|
|
3871
3936
|
#body;
|
|
3872
3937
|
#loading;
|
|
3873
3938
|
#noAutoload;
|
|
3939
|
+
#empty;
|
|
3874
3940
|
#feedback;
|
|
3875
3941
|
#paginator;
|
|
3876
3942
|
#sorters;
|
|
@@ -3921,6 +3987,7 @@ class Table extends ParsedElement {
|
|
|
3921
3987
|
this.#body = table.querySelector(':scope > tbody');
|
|
3922
3988
|
this.#loading = table.querySelector(':scope > tbody[data-ref=loading]');
|
|
3923
3989
|
this.#noAutoload = table.querySelector(':scope > tbody[data-ref=initial]');
|
|
3990
|
+
this.#empty = table.querySelector(':scope > tbody[data-ref=empty]');
|
|
3924
3991
|
this.#feedback = table.querySelector(':scope > tbody[data-ref=feedback]');
|
|
3925
3992
|
this.#paginator = Nodes.queryChildren(fragment, 'ful-pagination');
|
|
3926
3993
|
this.replaceChildren(fragment);
|
|
@@ -4011,6 +4078,7 @@ class Table extends ParsedElement {
|
|
|
4011
4078
|
this.#loading.toggleAttribute('hidden', this.#body.childElementCount > 0);
|
|
4012
4079
|
this.#feedback.setAttribute('hidden', '');
|
|
4013
4080
|
this.#noAutoload.setAttribute('hidden', '');
|
|
4081
|
+
this.#empty.setAttribute('hidden', '');
|
|
4014
4082
|
this.setAttribute('aria-busy', 'true');
|
|
4015
4083
|
try {
|
|
4016
4084
|
const pageResponse = await this.#loader.load(pageRequest, sortRequest, filterRequest);
|
|
@@ -4019,6 +4087,16 @@ class Table extends ParsedElement {
|
|
|
4019
4087
|
}
|
|
4020
4088
|
this.#latestRequest = { pageRequest, sortRequest, filterRequest };
|
|
4021
4089
|
this.#update(pageRequest, sortRequest, filterRequest, pageResponse);
|
|
4090
|
+
//settled before the event: a listener counting what loaded has to
|
|
4091
|
+
//see the table as it now is
|
|
4092
|
+
this.removeAttribute('aria-busy');
|
|
4093
|
+
this.dispatchEvent(
|
|
4094
|
+
new CustomEvent('load:success', {
|
|
4095
|
+
bubbles: true,
|
|
4096
|
+
cancelable: false,
|
|
4097
|
+
detail: { pageRequest, sortRequest, filterRequest, response: pageResponse },
|
|
4098
|
+
}),
|
|
4099
|
+
);
|
|
4022
4100
|
} catch (/** @type any */ error) {
|
|
4023
4101
|
if (claim.stale) {
|
|
4024
4102
|
//the newer load owns the table and its outcome: a superseded
|
|
@@ -4035,12 +4113,15 @@ class Table extends ParsedElement {
|
|
|
4035
4113
|
error,
|
|
4036
4114
|
`${error}`,
|
|
4037
4115
|
);
|
|
4116
|
+
this.removeAttribute('aria-busy');
|
|
4117
|
+
this.dispatchEvent(
|
|
4118
|
+
new CustomEvent('load:failure', {
|
|
4119
|
+
bubbles: true,
|
|
4120
|
+
cancelable: false,
|
|
4121
|
+
detail: { pageRequest, sortRequest, filterRequest, exception: error },
|
|
4122
|
+
}),
|
|
4123
|
+
);
|
|
4038
4124
|
throw error;
|
|
4039
|
-
} finally {
|
|
4040
|
-
//a superseded load owns nothing, the newer one's busy state included
|
|
4041
|
-
if (!claim.stale) {
|
|
4042
|
-
this.removeAttribute('aria-busy');
|
|
4043
|
-
}
|
|
4044
4125
|
}
|
|
4045
4126
|
}
|
|
4046
4127
|
/** Hands the loader to the callback, for runtime reconfigurations. */
|
|
@@ -4077,6 +4158,7 @@ class Table extends ParsedElement {
|
|
|
4077
4158
|
})
|
|
4078
4159
|
.render(),
|
|
4079
4160
|
);
|
|
4161
|
+
this.#empty.toggleAttribute('hidden', pageResponse.data.length !== 0);
|
|
4080
4162
|
//one move, one repaint: the page and the count are the same state
|
|
4081
4163
|
this.#paginator.update({ current: pageRequest.page, total: pages });
|
|
4082
4164
|
}
|
|
@@ -4330,6 +4412,19 @@ const operatorLabel = (op) => t(`filters.op.${op}`);
|
|
|
4330
4412
|
const sensitivityLabel = (sensitivity) => t(`filters.sensitivity.${sensitivity}`);
|
|
4331
4413
|
const booleanValueLabel = (token) => t(token === '' ? 'filters.boolean.any' : `filters.boolean.${token}`);
|
|
4332
4414
|
|
|
4415
|
+
/**
|
|
4416
|
+
* The operands are the words the reader chose, not the keys the wire carries.
|
|
4417
|
+
* @typedef {{ label: string|null, operator: string|null, operands: string[] }} FilterCriterion
|
|
4418
|
+
*/
|
|
4419
|
+
|
|
4420
|
+
const labelTextOf = (filter) => filter.querySelector(':scope > label')?.textContent.trim() || null;
|
|
4421
|
+
|
|
4422
|
+
/** @returns {FilterCriterion|null} */
|
|
4423
|
+
const asCriterion = (label, operator, operands) => {
|
|
4424
|
+
const shown = operands.filter((o) => o !== null && o !== undefined && `${o}` !== '');
|
|
4425
|
+
return shown.length === 0 ? null : { label, operator, operands: shown.map((o) => `${o}`) };
|
|
4426
|
+
};
|
|
4427
|
+
|
|
4333
4428
|
/**
|
|
4334
4429
|
* The shared shape of every operator-and-operands filter: an operator menu, one
|
|
4335
4430
|
* or two operands of the type the subclass declares, and a tuple that mirrors
|
|
@@ -4469,6 +4564,18 @@ class CompareFilter extends Input {
|
|
|
4469
4564
|
_syncBetween() {
|
|
4470
4565
|
this._value2.toggleAttribute('hidden', this._operator.value !== 'BETWEEN');
|
|
4471
4566
|
}
|
|
4567
|
+
/**
|
|
4568
|
+
* The operands as shown rather than as serialized: a date filter describes
|
|
4569
|
+
* what is in its control, not the iso string it sends.
|
|
4570
|
+
* @returns {FilterCriterion|null}
|
|
4571
|
+
*/
|
|
4572
|
+
get criterion() {
|
|
4573
|
+
const operands =
|
|
4574
|
+
this._operator.value === 'BETWEEN'
|
|
4575
|
+
? [this._value1.value, this._value2.value]
|
|
4576
|
+
: [this._value1.value];
|
|
4577
|
+
return asCriterion(labelTextOf(this), this._operator.value, operands);
|
|
4578
|
+
}
|
|
4472
4579
|
get disabled() {
|
|
4473
4580
|
return super.disabled;
|
|
4474
4581
|
}
|
|
@@ -4704,6 +4811,13 @@ class BooleanFilter extends Field {
|
|
|
4704
4811
|
this._operator.value = this._operator.pinned ? this._operator.allowed[0] : v[0];
|
|
4705
4812
|
this._value.value = v[1] ?? '';
|
|
4706
4813
|
}
|
|
4814
|
+
/** @returns {FilterCriterion|null} */
|
|
4815
|
+
get criterion() {
|
|
4816
|
+
const token = this._value.value;
|
|
4817
|
+
return asCriterion(labelTextOf(this), this._operator.value, [
|
|
4818
|
+
token === '' ? '' : booleanValueLabel(token),
|
|
4819
|
+
]);
|
|
4820
|
+
}
|
|
4707
4821
|
get disabled() {
|
|
4708
4822
|
return super.disabled;
|
|
4709
4823
|
}
|
|
@@ -4716,6 +4830,37 @@ class BooleanFilter extends Field {
|
|
|
4716
4830
|
}
|
|
4717
4831
|
}
|
|
4718
4832
|
|
|
4833
|
+
/**
|
|
4834
|
+
* Set membership over a select's vocabulary, answering for both of data-jpa's
|
|
4835
|
+
* set annotations: InEnum and InList take the same bare array of values and no
|
|
4836
|
+
* operator, differing only in what the server converts them to.
|
|
4837
|
+
*/
|
|
4838
|
+
class InFilter extends Select {
|
|
4839
|
+
async upgrade() {
|
|
4840
|
+
//the snapshot is taken inside super.upgrade(), so an attribute this
|
|
4841
|
+
//element does not let an author turn off has to be written before it
|
|
4842
|
+
this.toggleAttribute('multiple', true);
|
|
4843
|
+
await super.upgrade();
|
|
4844
|
+
}
|
|
4845
|
+
get value() {
|
|
4846
|
+
const keys = super.value;
|
|
4847
|
+
//an empty set is no filter: the table drops a falsy value, where an
|
|
4848
|
+
//empty array would travel and match nothing
|
|
4849
|
+
return keys.length === 0 ? null : keys;
|
|
4850
|
+
}
|
|
4851
|
+
set value(v) {
|
|
4852
|
+
super.value = v ?? [];
|
|
4853
|
+
}
|
|
4854
|
+
/** @returns {FilterCriterion|null} */
|
|
4855
|
+
get criterion() {
|
|
4856
|
+
return asCriterion(
|
|
4857
|
+
labelTextOf(this),
|
|
4858
|
+
null,
|
|
4859
|
+
(super.entry ?? []).map((e) => e.label),
|
|
4860
|
+
);
|
|
4861
|
+
}
|
|
4862
|
+
}
|
|
4863
|
+
|
|
4719
4864
|
/**
|
|
4720
4865
|
* The async section machinery shared by ful-tabs, ful-wizard, ful-dialog and
|
|
4721
4866
|
* ful-drawer: every activation of a section fires the section:requested family
|
|
@@ -5879,6 +6024,7 @@ class Plugin {
|
|
|
5879
6024
|
.defineElement('ful-filter-number', NumberFilter)
|
|
5880
6025
|
.defineElement('ful-filter-boolean', BooleanFilter)
|
|
5881
6026
|
.defineElement('ful-filter-text', TextFilter)
|
|
6027
|
+
.defineElement('ful-filter-in', InFilter)
|
|
5882
6028
|
.defineElement('ful-select', Select)
|
|
5883
6029
|
.defineElement('ful-dropdown', Dropdown)
|
|
5884
6030
|
.defineComponent('loaders:select', SelectLoader)
|
|
@@ -5895,5 +6041,5 @@ class Plugin {
|
|
|
5895
6041
|
}
|
|
5896
6042
|
}
|
|
5897
6043
|
|
|
5898
|
-
export { Accordion, Anchors, AsyncEvents, Bindings, BooleanFilter, Checkbox, Claims, CompareFilter, Dialog, Drawer, Dropdown, Field, Form, FormLoader, Input, InputFile, InputInstant, InputLocalDate, InputLocalTime, Instant, InstantFilter, LocalDate, LocalDateFilter, LocalStorage, NumberFilter, Pagination, Plugin, RadioGroup, Select, SelectLoader, SessionStorage, SortButton, Table, TableLoader, TableSchemaParser, Tabs, TextFilter, Timing, Toasts, Tooltip, VersionedLocalStorage, VersionedSessionStorage, Wizard, describable };
|
|
6044
|
+
export { Accordion, Anchors, AsyncEvents, Bindings, BooleanFilter, Checkbox, Claims, CompareFilter, Dialog, Drawer, Dropdown, Field, Form, FormLoader, InFilter, Input, InputFile, InputInstant, InputLocalDate, InputLocalTime, Instant, InstantFilter, LocalDate, LocalDateFilter, LocalStorage, NumberFilter, Pagination, Plugin, RadioGroup, Select, SelectLoader, SessionStorage, SortButton, Table, TableLoader, TableSchemaParser, Tabs, TextFilter, Timing, Toasts, Tooltip, VersionedLocalStorage, VersionedSessionStorage, Wizard, describable };
|
|
5899
6045
|
//# sourceMappingURL=ful.mjs.map
|