@optionfactory/fml 9.0.0-rc10 → 9.0.0-rc12
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 +283 -13
- package/dist/fml.css +7 -6
- package/dist/fml.css.map +1 -1
- package/dist/fml.iife.js +205 -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 +7 -6
- package/dist/ful.css.map +1 -1
- package/dist/ful.d.mts +28 -1
- package/dist/ful.iife.js +204 -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 +204 -36
- package/dist/ful.mjs.map +1 -1
- package/dist/vscode.html-custom-data.json +134 -12
- package/dist/web-types.json +293 -14
- package/package.json +1 -1
package/dist/ful.mjs
CHANGED
|
@@ -1407,6 +1407,29 @@ 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_DIGITS = {
|
|
1416
|
+
numeric: (t) => t.replace(/\D/g, ''),
|
|
1417
|
+
//one separator, the first: the rest are a slip while typing, not a value
|
|
1418
|
+
decimal: (t) => {
|
|
1419
|
+
const kept = t.replace(/[^\d.,]/g, '');
|
|
1420
|
+
const at = kept.search(/[.,]/);
|
|
1421
|
+
return at === -1 ? kept : kept.slice(0, at + 1) + kept.slice(at + 1).replace(/[.,]/g, '');
|
|
1422
|
+
},
|
|
1423
|
+
};
|
|
1424
|
+
|
|
1425
|
+
const numericFilter = (type, unsigned) => {
|
|
1426
|
+
const digits = NUMERIC_DIGITS[type];
|
|
1427
|
+
if (!digits) {
|
|
1428
|
+
return null;
|
|
1429
|
+
}
|
|
1430
|
+
return unsigned ? digits : (v) => signed(v, digits);
|
|
1431
|
+
};
|
|
1432
|
+
|
|
1410
1433
|
const warnedBoth = new WeakSet();
|
|
1411
1434
|
const filterOf = (el) => {
|
|
1412
1435
|
const keep = el.declared('keep');
|
|
@@ -1426,7 +1449,7 @@ const filterOf = (el) => {
|
|
|
1426
1449
|
const re = compiled('reject', reject);
|
|
1427
1450
|
return re && ((v) => v.replace(re, ''));
|
|
1428
1451
|
}
|
|
1429
|
-
return
|
|
1452
|
+
return numericFilter(el._type(), el.declared('unsigned'));
|
|
1430
1453
|
};
|
|
1431
1454
|
|
|
1432
1455
|
/** A labelled text input over any native type or textarea; the temporal inputs are its subclasses. */
|
|
@@ -1441,6 +1464,7 @@ class Input extends Field {
|
|
|
1441
1464
|
'reject',
|
|
1442
1465
|
'uppercase:presence',
|
|
1443
1466
|
'trim:presence',
|
|
1467
|
+
'unsigned:presence',
|
|
1444
1468
|
'autocomplete',
|
|
1445
1469
|
];
|
|
1446
1470
|
static slots = true;
|
|
@@ -1462,9 +1486,17 @@ class Input extends Field {
|
|
|
1462
1486
|
return this.declared('type') ?? (this.declared('v-type') === 'number' ? 'number' : 'text');
|
|
1463
1487
|
}
|
|
1464
1488
|
_build({ slots }) {
|
|
1465
|
-
const
|
|
1489
|
+
const declared = this._type();
|
|
1490
|
+
const mode = INPUT_MODES[declared];
|
|
1491
|
+
//a numeric widget is a text input: the type names the keyboard, not the
|
|
1492
|
+
//control, so the rendered markup stays valid html
|
|
1493
|
+
const type = mode ? 'text' : declared;
|
|
1466
1494
|
const fragment = this.template().withOverlay({ type, slots }).render();
|
|
1467
1495
|
this._input = fragment.querySelector('input,textarea');
|
|
1496
|
+
if (mode) {
|
|
1497
|
+
//before the passthrough, which stays the last word
|
|
1498
|
+
Attributes.set(this._input, 'inputmode', mode);
|
|
1499
|
+
}
|
|
1468
1500
|
|
|
1469
1501
|
//the browser reads autocomplete off the control it is classifying, so the
|
|
1470
1502
|
//field's own token, or the form's where it declares none, is put there.
|
|
@@ -1516,13 +1548,15 @@ class Input extends Field {
|
|
|
1516
1548
|
if (trimmed === '') {
|
|
1517
1549
|
return null;
|
|
1518
1550
|
}
|
|
1551
|
+
//the wire carries a dot wherever the comma is what the keyboard shows
|
|
1552
|
+
const normalized = this._type() === 'decimal' ? trimmed.replaceAll(',', '.') : trimmed;
|
|
1519
1553
|
if (this.declared('v-type') === 'number') {
|
|
1520
1554
|
//typed values are an explicit opt in, as the select's k-type: blank
|
|
1521
1555
|
//stays null, and a value that does not decode is kept as it is
|
|
1522
|
-
const n = Number(
|
|
1523
|
-
return Number.isNaN(n) ?
|
|
1556
|
+
const n = Number(normalized);
|
|
1557
|
+
return Number.isNaN(n) ? normalized : n;
|
|
1524
1558
|
}
|
|
1525
|
-
return
|
|
1559
|
+
return normalized;
|
|
1526
1560
|
}
|
|
1527
1561
|
set value(value) {
|
|
1528
1562
|
this._input.value = value === '' || value === undefined ? null : value;
|
|
@@ -2514,10 +2548,27 @@ class SelectLoader {
|
|
|
2514
2548
|
method: el.declared('method') ?? 'POST',
|
|
2515
2549
|
mode: el.declared('mode'),
|
|
2516
2550
|
prefetch: el.declared('preload'),
|
|
2517
|
-
revision: el
|
|
2551
|
+
revision: SelectLoader.#revisionFrom(el),
|
|
2518
2552
|
responseMapper: SelectLoader.#responseMapperFrom(el),
|
|
2519
2553
|
});
|
|
2520
2554
|
}
|
|
2555
|
+
static #revisionFrom(el) {
|
|
2556
|
+
const declared = el.declared('revision');
|
|
2557
|
+
if (declared !== '') {
|
|
2558
|
+
return declared;
|
|
2559
|
+
}
|
|
2560
|
+
const configured = el.component('revision');
|
|
2561
|
+
if (configured === undefined || configured === null) {
|
|
2562
|
+
//not caching while the markup says to cache is the quiet failure
|
|
2563
|
+
//this branch exists to make loud
|
|
2564
|
+
console.warn(
|
|
2565
|
+
"a valueless revision asks the registry for one, and no 'revision' component is defined: the vocabulary will not be cached",
|
|
2566
|
+
el,
|
|
2567
|
+
);
|
|
2568
|
+
return null;
|
|
2569
|
+
}
|
|
2570
|
+
return typeof configured === 'function' ? configured() : configured;
|
|
2571
|
+
}
|
|
2521
2572
|
static #responseMapperFrom(el) {
|
|
2522
2573
|
if (el.declared('k-expr') && el.declared('l-expr')) {
|
|
2523
2574
|
return (response) => {
|
|
@@ -2550,7 +2601,7 @@ class Dropdown extends ParsedElement {
|
|
|
2550
2601
|
static slots = true;
|
|
2551
2602
|
static template = `
|
|
2552
2603
|
<ful-spinner class="centered" role="status" hidden><span class="ful-sr-only">{{ #l10n:t('spinner.loading') }}</span></ful-spinner>
|
|
2553
|
-
<
|
|
2604
|
+
<ful-empty data-ref="empty" aria-live="polite" hidden>{{ #l10n:t('dropdown.empty') }}</ful-empty>
|
|
2554
2605
|
<menu tabindex="-1" role="listbox" hidden></menu>
|
|
2555
2606
|
`;
|
|
2556
2607
|
static templates = {
|
|
@@ -2572,7 +2623,7 @@ class Dropdown extends ParsedElement {
|
|
|
2572
2623
|
? this.template('options')
|
|
2573
2624
|
: Templates.fromFragment(slots.default);
|
|
2574
2625
|
this.#spinner = fragment.querySelector('ful-spinner');
|
|
2575
|
-
this.#empty = fragment.querySelector('
|
|
2626
|
+
this.#empty = fragment.querySelector('[data-ref=empty]');
|
|
2576
2627
|
this.#menu = fragment.querySelector('menu');
|
|
2577
2628
|
//the listbox is named so a combobox can point aria-controls and
|
|
2578
2629
|
//aria-activedescendant at it: a reference to an unnamed element resolves
|
|
@@ -2932,7 +2983,7 @@ class Select extends Field {
|
|
|
2932
2983
|
return;
|
|
2933
2984
|
}
|
|
2934
2985
|
this.#abortdload();
|
|
2935
|
-
this.#close();
|
|
2986
|
+
this.#close(true);
|
|
2936
2987
|
});
|
|
2937
2988
|
this.#input.addEventListener('keydown', (e) => {
|
|
2938
2989
|
if (!this._interactive()) {
|
|
@@ -2965,7 +3016,6 @@ class Select extends Field {
|
|
|
2965
3016
|
this.#editing = false;
|
|
2966
3017
|
this.#values.set(this.#coerceKey(e.detail.entry.key), e.detail.entry);
|
|
2967
3018
|
this.#changed();
|
|
2968
|
-
this.#syncBadges();
|
|
2969
3019
|
this.#input.focus();
|
|
2970
3020
|
this.#ddmenu.hide();
|
|
2971
3021
|
if (!this.#multiple) {
|
|
@@ -3022,7 +3072,6 @@ class Select extends Field {
|
|
|
3022
3072
|
}
|
|
3023
3073
|
this.#values.delete(key);
|
|
3024
3074
|
this.#changed();
|
|
3025
|
-
this.#syncBadges();
|
|
3026
3075
|
}
|
|
3027
3076
|
#chipKeydown(e, badge) {
|
|
3028
3077
|
switch (e.code) {
|
|
@@ -3118,7 +3167,7 @@ class Select extends Field {
|
|
|
3118
3167
|
}
|
|
3119
3168
|
case 'Tab': {
|
|
3120
3169
|
this.#abortdload();
|
|
3121
|
-
this.#close();
|
|
3170
|
+
this.#close(true);
|
|
3122
3171
|
break;
|
|
3123
3172
|
}
|
|
3124
3173
|
}
|
|
@@ -3134,12 +3183,25 @@ class Select extends Field {
|
|
|
3134
3183
|
}
|
|
3135
3184
|
return;
|
|
3136
3185
|
}
|
|
3137
|
-
this.#
|
|
3138
|
-
this.#ddmenu.moveOrShow(forward, () => this.#loader.load(this.#input.value), [...this.#values.keys()]);
|
|
3186
|
+
this.#ddmenu.moveOrShow(forward, () => this.#loader.load(this.#query()), [...this.#values.keys()]);
|
|
3139
3187
|
}
|
|
3140
|
-
|
|
3188
|
+
/**
|
|
3189
|
+
* @param commit whether the user is leaving the field, which only a blur or
|
|
3190
|
+
* a Tab is: Escape cancels, and closing the list or a claim landing leaves
|
|
3191
|
+
* the edit in progress
|
|
3192
|
+
*/
|
|
3193
|
+
#close(commit = false) {
|
|
3141
3194
|
this.#ddmenu.hide();
|
|
3195
|
+
//emptying the box and leaving is a clear: redisplaying the label would
|
|
3196
|
+
//discard the edit without a word. A multiple select keeps its box empty
|
|
3197
|
+
//by design, so only a single one can mean it
|
|
3198
|
+
const cleared =
|
|
3199
|
+
commit && this.#editing && !this.#multiple && this.#input.value === '' && this.#values.size !== 0;
|
|
3142
3200
|
this.#editing = false;
|
|
3201
|
+
if (cleared) {
|
|
3202
|
+
this.#values.clear();
|
|
3203
|
+
this.#changed();
|
|
3204
|
+
}
|
|
3143
3205
|
this.#display();
|
|
3144
3206
|
}
|
|
3145
3207
|
/**
|
|
@@ -3148,14 +3210,15 @@ class Select extends Field {
|
|
|
3148
3210
|
* highlighted.
|
|
3149
3211
|
*/
|
|
3150
3212
|
#open() {
|
|
3151
|
-
this.#
|
|
3152
|
-
return this.#ddmenu.show(() => this.#loader.load(this.#input.value), [...this.#values.keys()]);
|
|
3213
|
+
return this.#ddmenu.show(() => this.#loader.load(this.#query()), [...this.#values.keys()]);
|
|
3153
3214
|
}
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3215
|
+
/**
|
|
3216
|
+
* Browsing is the whole vocabulary, not the label already chosen, and the
|
|
3217
|
+
* label stays on screen while it happens: a select showing nothing reads as
|
|
3218
|
+
* an empty one.
|
|
3219
|
+
*/
|
|
3220
|
+
#query() {
|
|
3221
|
+
return this.#editing ? this.#input.value : '';
|
|
3159
3222
|
}
|
|
3160
3223
|
#display() {
|
|
3161
3224
|
const entry = this.#values.values().next().value;
|
|
@@ -3166,6 +3229,9 @@ class Select extends Field {
|
|
|
3166
3229
|
return [...this.#values.values()];
|
|
3167
3230
|
}
|
|
3168
3231
|
#changed() {
|
|
3232
|
+
//settled before the event: a listener reading the control during it has to
|
|
3233
|
+
//see the selection that caused it
|
|
3234
|
+
this.#syncBadges();
|
|
3169
3235
|
//the detail carries the keys the value property answers with, as every
|
|
3170
3236
|
//other field's does, and the labeled selection beside them
|
|
3171
3237
|
this._notifyChange({ entry: this.entry });
|
|
@@ -3812,6 +3878,7 @@ class Table extends ParsedElement {
|
|
|
3812
3878
|
static slots = true;
|
|
3813
3879
|
static config = {
|
|
3814
3880
|
searchIcon: 'search',
|
|
3881
|
+
emptyIcon: 'inbox',
|
|
3815
3882
|
};
|
|
3816
3883
|
static template = `
|
|
3817
3884
|
<ful-form data-tpl-if="slots.filters">
|
|
@@ -3825,10 +3892,21 @@ class Table extends ParsedElement {
|
|
|
3825
3892
|
<tbody data-ref="initial">
|
|
3826
3893
|
<tr>
|
|
3827
3894
|
<td data-tpl-colspan="schema.length">
|
|
3828
|
-
<
|
|
3829
|
-
<
|
|
3895
|
+
<ful-empty>
|
|
3896
|
+
<ful-icon data-tpl-if="config.searchIcon" data-tpl-name="config.searchIcon" aria-hidden="true"></ful-icon>
|
|
3830
3897
|
{{ #l10n:t('table.initial') }}
|
|
3831
|
-
</
|
|
3898
|
+
</ful-empty>
|
|
3899
|
+
</td>
|
|
3900
|
+
</tr>
|
|
3901
|
+
</tbody>
|
|
3902
|
+
<tbody data-ref="empty" hidden>
|
|
3903
|
+
<tr>
|
|
3904
|
+
<td data-tpl-colspan="schema.length">
|
|
3905
|
+
<ful-empty data-tpl-if="slots.empty">{{{{ slots.empty }}}}</ful-empty>
|
|
3906
|
+
<ful-empty data-tpl-if="!slots.empty">
|
|
3907
|
+
<ful-icon data-tpl-if="config.emptyIcon" data-tpl-name="config.emptyIcon" aria-hidden="true"></ful-icon>
|
|
3908
|
+
{{ #l10n:t('table.no-data') }}
|
|
3909
|
+
</ful-empty>
|
|
3832
3910
|
</td>
|
|
3833
3911
|
</tr>
|
|
3834
3912
|
</tbody>
|
|
@@ -3858,11 +3936,6 @@ class Table extends ParsedElement {
|
|
|
3858
3936
|
`;
|
|
3859
3937
|
static templates = {
|
|
3860
3938
|
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
3939
|
{{{{ schema.rowsTemplate.withOverlay({'rows': pageResponse.data}).render() }}}}
|
|
3867
3940
|
`,
|
|
3868
3941
|
};
|
|
@@ -3871,6 +3944,7 @@ class Table extends ParsedElement {
|
|
|
3871
3944
|
#body;
|
|
3872
3945
|
#loading;
|
|
3873
3946
|
#noAutoload;
|
|
3947
|
+
#empty;
|
|
3874
3948
|
#feedback;
|
|
3875
3949
|
#paginator;
|
|
3876
3950
|
#sorters;
|
|
@@ -3921,6 +3995,7 @@ class Table extends ParsedElement {
|
|
|
3921
3995
|
this.#body = table.querySelector(':scope > tbody');
|
|
3922
3996
|
this.#loading = table.querySelector(':scope > tbody[data-ref=loading]');
|
|
3923
3997
|
this.#noAutoload = table.querySelector(':scope > tbody[data-ref=initial]');
|
|
3998
|
+
this.#empty = table.querySelector(':scope > tbody[data-ref=empty]');
|
|
3924
3999
|
this.#feedback = table.querySelector(':scope > tbody[data-ref=feedback]');
|
|
3925
4000
|
this.#paginator = Nodes.queryChildren(fragment, 'ful-pagination');
|
|
3926
4001
|
this.replaceChildren(fragment);
|
|
@@ -4011,6 +4086,7 @@ class Table extends ParsedElement {
|
|
|
4011
4086
|
this.#loading.toggleAttribute('hidden', this.#body.childElementCount > 0);
|
|
4012
4087
|
this.#feedback.setAttribute('hidden', '');
|
|
4013
4088
|
this.#noAutoload.setAttribute('hidden', '');
|
|
4089
|
+
this.#empty.setAttribute('hidden', '');
|
|
4014
4090
|
this.setAttribute('aria-busy', 'true');
|
|
4015
4091
|
try {
|
|
4016
4092
|
const pageResponse = await this.#loader.load(pageRequest, sortRequest, filterRequest);
|
|
@@ -4019,6 +4095,16 @@ class Table extends ParsedElement {
|
|
|
4019
4095
|
}
|
|
4020
4096
|
this.#latestRequest = { pageRequest, sortRequest, filterRequest };
|
|
4021
4097
|
this.#update(pageRequest, sortRequest, filterRequest, pageResponse);
|
|
4098
|
+
//settled before the event: a listener counting what loaded has to
|
|
4099
|
+
//see the table as it now is
|
|
4100
|
+
this.removeAttribute('aria-busy');
|
|
4101
|
+
this.dispatchEvent(
|
|
4102
|
+
new CustomEvent('load:success', {
|
|
4103
|
+
bubbles: true,
|
|
4104
|
+
cancelable: false,
|
|
4105
|
+
detail: { pageRequest, sortRequest, filterRequest, response: pageResponse },
|
|
4106
|
+
}),
|
|
4107
|
+
);
|
|
4022
4108
|
} catch (/** @type any */ error) {
|
|
4023
4109
|
if (claim.stale) {
|
|
4024
4110
|
//the newer load owns the table and its outcome: a superseded
|
|
@@ -4035,12 +4121,15 @@ class Table extends ParsedElement {
|
|
|
4035
4121
|
error,
|
|
4036
4122
|
`${error}`,
|
|
4037
4123
|
);
|
|
4124
|
+
this.removeAttribute('aria-busy');
|
|
4125
|
+
this.dispatchEvent(
|
|
4126
|
+
new CustomEvent('load:failure', {
|
|
4127
|
+
bubbles: true,
|
|
4128
|
+
cancelable: false,
|
|
4129
|
+
detail: { pageRequest, sortRequest, filterRequest, exception: error },
|
|
4130
|
+
}),
|
|
4131
|
+
);
|
|
4038
4132
|
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
4133
|
}
|
|
4045
4134
|
}
|
|
4046
4135
|
/** Hands the loader to the callback, for runtime reconfigurations. */
|
|
@@ -4077,6 +4166,7 @@ class Table extends ParsedElement {
|
|
|
4077
4166
|
})
|
|
4078
4167
|
.render(),
|
|
4079
4168
|
);
|
|
4169
|
+
this.#empty.toggleAttribute('hidden', pageResponse.data.length !== 0);
|
|
4080
4170
|
//one move, one repaint: the page and the count are the same state
|
|
4081
4171
|
this.#paginator.update({ current: pageRequest.page, total: pages });
|
|
4082
4172
|
}
|
|
@@ -4330,6 +4420,19 @@ const operatorLabel = (op) => t(`filters.op.${op}`);
|
|
|
4330
4420
|
const sensitivityLabel = (sensitivity) => t(`filters.sensitivity.${sensitivity}`);
|
|
4331
4421
|
const booleanValueLabel = (token) => t(token === '' ? 'filters.boolean.any' : `filters.boolean.${token}`);
|
|
4332
4422
|
|
|
4423
|
+
/**
|
|
4424
|
+
* The operands are the words the reader chose, not the keys the wire carries.
|
|
4425
|
+
* @typedef {{ label: string|null, operator: string|null, operands: string[] }} FilterCriterion
|
|
4426
|
+
*/
|
|
4427
|
+
|
|
4428
|
+
const labelTextOf = (filter) => filter.querySelector(':scope > label')?.textContent.trim() || null;
|
|
4429
|
+
|
|
4430
|
+
/** @returns {FilterCriterion|null} */
|
|
4431
|
+
const asCriterion = (label, operator, operands) => {
|
|
4432
|
+
const shown = operands.filter((o) => o !== null && o !== undefined && `${o}` !== '');
|
|
4433
|
+
return shown.length === 0 ? null : { label, operator, operands: shown.map((o) => `${o}`) };
|
|
4434
|
+
};
|
|
4435
|
+
|
|
4333
4436
|
/**
|
|
4334
4437
|
* The shared shape of every operator-and-operands filter: an operator menu, one
|
|
4335
4438
|
* or two operands of the type the subclass declares, and a tuple that mirrors
|
|
@@ -4469,6 +4572,18 @@ class CompareFilter extends Input {
|
|
|
4469
4572
|
_syncBetween() {
|
|
4470
4573
|
this._value2.toggleAttribute('hidden', this._operator.value !== 'BETWEEN');
|
|
4471
4574
|
}
|
|
4575
|
+
/**
|
|
4576
|
+
* The operands as shown rather than as serialized: a date filter describes
|
|
4577
|
+
* what is in its control, not the iso string it sends.
|
|
4578
|
+
* @returns {FilterCriterion|null}
|
|
4579
|
+
*/
|
|
4580
|
+
get criterion() {
|
|
4581
|
+
const operands =
|
|
4582
|
+
this._operator.value === 'BETWEEN'
|
|
4583
|
+
? [this._value1.value, this._value2.value]
|
|
4584
|
+
: [this._value1.value];
|
|
4585
|
+
return asCriterion(labelTextOf(this), this._operator.value, operands);
|
|
4586
|
+
}
|
|
4472
4587
|
get disabled() {
|
|
4473
4588
|
return super.disabled;
|
|
4474
4589
|
}
|
|
@@ -4704,6 +4819,13 @@ class BooleanFilter extends Field {
|
|
|
4704
4819
|
this._operator.value = this._operator.pinned ? this._operator.allowed[0] : v[0];
|
|
4705
4820
|
this._value.value = v[1] ?? '';
|
|
4706
4821
|
}
|
|
4822
|
+
/** @returns {FilterCriterion|null} */
|
|
4823
|
+
get criterion() {
|
|
4824
|
+
const token = this._value.value;
|
|
4825
|
+
return asCriterion(labelTextOf(this), this._operator.value, [
|
|
4826
|
+
token === '' ? '' : booleanValueLabel(token),
|
|
4827
|
+
]);
|
|
4828
|
+
}
|
|
4707
4829
|
get disabled() {
|
|
4708
4830
|
return super.disabled;
|
|
4709
4831
|
}
|
|
@@ -4716,6 +4838,51 @@ class BooleanFilter extends Field {
|
|
|
4716
4838
|
}
|
|
4717
4839
|
}
|
|
4718
4840
|
|
|
4841
|
+
/**
|
|
4842
|
+
* Set membership over a select's vocabulary, answering for both of data-jpa's
|
|
4843
|
+
* set annotations: InEnum and InList take the same bare array of values and no
|
|
4844
|
+
* operator, differing only in what the server converts them to.
|
|
4845
|
+
*/
|
|
4846
|
+
class InFilter extends Select {
|
|
4847
|
+
static attributes = ['operator'];
|
|
4848
|
+
/** the selection as the array the wire takes, whether one key was chosen or many */
|
|
4849
|
+
#keys() {
|
|
4850
|
+
const chosen = super.value;
|
|
4851
|
+
if (this.multiple) {
|
|
4852
|
+
return chosen;
|
|
4853
|
+
}
|
|
4854
|
+
return chosen === null || chosen === undefined ? [] : [chosen];
|
|
4855
|
+
}
|
|
4856
|
+
get value() {
|
|
4857
|
+
const keys = this.#keys();
|
|
4858
|
+
//an empty set is no filter: the table drops a falsy value, where an
|
|
4859
|
+
//empty array would travel and match nothing
|
|
4860
|
+
if (keys.length === 0) {
|
|
4861
|
+
return null;
|
|
4862
|
+
}
|
|
4863
|
+
const operator = this.declared('operator');
|
|
4864
|
+
return operator ? [operator, ...keys] : keys;
|
|
4865
|
+
}
|
|
4866
|
+
set value(v) {
|
|
4867
|
+
const operator = this.declared('operator');
|
|
4868
|
+
let keys = v === null || v === undefined ? [] : Array.isArray(v) ? v : [v];
|
|
4869
|
+
//what the getter answers has to be assignable back, and it carries the
|
|
4870
|
+
//operator in front of the keys
|
|
4871
|
+
if (operator && keys[0] === operator) {
|
|
4872
|
+
keys = keys.slice(1);
|
|
4873
|
+
}
|
|
4874
|
+
super.value = this.multiple ? keys : (keys[0] ?? null);
|
|
4875
|
+
}
|
|
4876
|
+
/** @returns {FilterCriterion|null} */
|
|
4877
|
+
get criterion() {
|
|
4878
|
+
return asCriterion(
|
|
4879
|
+
labelTextOf(this),
|
|
4880
|
+
this.declared('operator'),
|
|
4881
|
+
[super.entry].flat().filter((e) => e).map((e) => e.label),
|
|
4882
|
+
);
|
|
4883
|
+
}
|
|
4884
|
+
}
|
|
4885
|
+
|
|
4719
4886
|
/**
|
|
4720
4887
|
* The async section machinery shared by ful-tabs, ful-wizard, ful-dialog and
|
|
4721
4888
|
* ful-drawer: every activation of a section fires the section:requested family
|
|
@@ -5879,6 +6046,7 @@ class Plugin {
|
|
|
5879
6046
|
.defineElement('ful-filter-number', NumberFilter)
|
|
5880
6047
|
.defineElement('ful-filter-boolean', BooleanFilter)
|
|
5881
6048
|
.defineElement('ful-filter-text', TextFilter)
|
|
6049
|
+
.defineElement('ful-filter-in', InFilter)
|
|
5882
6050
|
.defineElement('ful-select', Select)
|
|
5883
6051
|
.defineElement('ful-dropdown', Dropdown)
|
|
5884
6052
|
.defineComponent('loaders:select', SelectLoader)
|
|
@@ -5895,5 +6063,5 @@ class Plugin {
|
|
|
5895
6063
|
}
|
|
5896
6064
|
}
|
|
5897
6065
|
|
|
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 };
|
|
6066
|
+
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
6067
|
//# sourceMappingURL=ful.mjs.map
|