overview-components 1.1.180 → 1.1.182
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/components/lit-data-grid-tanstack.d.ts +1 -1
- package/dist/components/lit-data-grid-tanstack.js +6 -2
- package/dist/schemas/lit-data-grid-tanstack-column-array.schema.d.ts +1 -1
- package/dist/schemas/lit-data-grid-tanstack-column.schema.d.ts +1 -1
- package/dist/schemas/lit-data-grid-tanstack-column.schema.js +1 -0
- package/dist/schemas/lit-data-grid-tanstack.schema.d.ts +1 -1
- package/dist/shared/filter-inputs.d.ts +6 -0
- package/dist/shared/filter-inputs.js +42 -2
- package/dist/shared/lit-checkbox.d.ts +1 -0
- package/dist/shared/lit-checkbox.js +19 -1
- package/dist/shared/lit-include-exclude-select.d.ts +59 -0
- package/dist/shared/lit-include-exclude-select.js +293 -0
- package/dist/utils/custom-filters.d.ts +4 -0
- package/dist/utils/custom-filters.js +20 -0
- package/dist/utils/getOperatorByType.d.ts +1 -1
- package/dist/utils/getOperatorByType.js +12 -0
- package/package.json +1 -1
|
@@ -34,7 +34,7 @@ export interface ColumnFilterServer {
|
|
|
34
34
|
}
|
|
35
35
|
export type ColumnCustomFilter = FromSchema<typeof litDataGridTanstackColumnCustomFilterSchema>;
|
|
36
36
|
export type TColumnVisibility = Record<string, boolean>;
|
|
37
|
-
export type ColumnType = 'string' | 'number' | 'date' | 'range' | 'select' | 'multiselect' | 'dateTime' | 'actions' | 'dateTimeRange' | 'dateRange' | 'numberRange' | 'currency' | 'thumbnail';
|
|
37
|
+
export type ColumnType = 'string' | 'number' | 'date' | 'range' | 'select' | 'multiselect' | 'includeExclude' | 'dateTime' | 'actions' | 'dateTimeRange' | 'dateRange' | 'numberRange' | 'currency' | 'thumbnail';
|
|
38
38
|
export type Density = 'compact' | 'standard' | 'comfort';
|
|
39
39
|
export type DateFormats = null | 'dd.MM.yyyy' | 'd.M.yyyy' | 'dd/MM/yyyy' | 'MM/dd/yyyy' | 'yyyy-MM-dd';
|
|
40
40
|
export interface ColumnCustomExtension {
|
|
@@ -34,7 +34,7 @@ import './components-settings/data-grid-settings.js';
|
|
|
34
34
|
// utils
|
|
35
35
|
import { formatDate } from '../utils/date.js';
|
|
36
36
|
import { formatCurrency, formatDecimal } from '../utils/formatNumber.js';
|
|
37
|
-
import { dateFilterFn, multiselectFilterFn, dateRangeFilterFn } from '../utils/custom-filters.js';
|
|
37
|
+
import { dateFilterFn, multiselectFilterFn, dateRangeFilterFn, includeExcludeFilterFn, } from '../utils/custom-filters.js';
|
|
38
38
|
import { setLocale, getLocale, LOCALE_LANGS } from '../utils/localization.js';
|
|
39
39
|
import { getOperatorsByColumnType } from '../utils/getOperatorByType.js';
|
|
40
40
|
import { tooltip, overflowTooltip } from '../shared/simple-tooltip.js';
|
|
@@ -568,6 +568,7 @@ export class LitDataGridTanstack extends LitElement {
|
|
|
568
568
|
numberRange: filterFns.inNumberRange,
|
|
569
569
|
select: filterFns.weakEquals,
|
|
570
570
|
multiselect: multiselectFilterFn,
|
|
571
|
+
includeExclude: includeExcludeFilterFn,
|
|
571
572
|
number: filterFns.weakEquals,
|
|
572
573
|
currency: filterFns.weakEquals,
|
|
573
574
|
dateRange: dateRangeFilterFn,
|
|
@@ -1097,7 +1098,9 @@ export class LitDataGridTanstack extends LitElement {
|
|
|
1097
1098
|
// can actually be used. The faceted model is expensive because
|
|
1098
1099
|
// FilterInputs requests unique values for every filterable column on
|
|
1099
1100
|
// mount; the grouped/expanded models only matter when grouping is on.
|
|
1100
|
-
const hasSelectColumn = (this.columns || []).some((c) => c.type === 'select' ||
|
|
1101
|
+
const hasSelectColumn = (this.columns || []).some((c) => c.type === 'select' ||
|
|
1102
|
+
c.type === 'multiselect' ||
|
|
1103
|
+
c.type === 'includeExclude');
|
|
1101
1104
|
const hasGroupableColumn = (this.initialGroups?.length ?? 0) > 0 ||
|
|
1102
1105
|
((this.enableGrouping ?? true) &&
|
|
1103
1106
|
(this.columns || []).some((c) => c.enableGrouping ?? true));
|
|
@@ -1142,6 +1145,7 @@ export class LitDataGridTanstack extends LitElement {
|
|
|
1142
1145
|
dateRangeFilterFn: dateRangeFilterFn,
|
|
1143
1146
|
dateFilterFn: dateFilterFn,
|
|
1144
1147
|
multiselectFilterFn: multiselectFilterFn,
|
|
1148
|
+
includeExcludeFilterFn: includeExcludeFilterFn,
|
|
1145
1149
|
},
|
|
1146
1150
|
// debugTable: true,
|
|
1147
1151
|
enableRowSelection: this.enableRowSelection,
|
|
@@ -8,7 +8,7 @@ export declare const litDataGridTanstackColumnArraySchema: {
|
|
|
8
8
|
};
|
|
9
9
|
readonly type: {
|
|
10
10
|
readonly type: "string";
|
|
11
|
-
readonly enum: readonly ["string", "number", "date", "range", "select", "multiselect", "dateTime", "actions", "dateTimeRange", "dateRange", "numberRange", "currency", "thumbnail"];
|
|
11
|
+
readonly enum: readonly ["string", "number", "date", "range", "select", "multiselect", "includeExclude", "dateTime", "actions", "dateTimeRange", "dateRange", "numberRange", "currency", "thumbnail"];
|
|
12
12
|
};
|
|
13
13
|
readonly headerName: {
|
|
14
14
|
readonly type: "string";
|
|
@@ -6,7 +6,7 @@ export declare const litDataGridTanstackColumnSchema: {
|
|
|
6
6
|
};
|
|
7
7
|
readonly type: {
|
|
8
8
|
readonly type: "string";
|
|
9
|
-
readonly enum: readonly ["string", "number", "date", "range", "select", "multiselect", "dateTime", "actions", "dateTimeRange", "dateRange", "numberRange", "currency", "thumbnail"];
|
|
9
|
+
readonly enum: readonly ["string", "number", "date", "range", "select", "multiselect", "includeExclude", "dateTime", "actions", "dateTimeRange", "dateRange", "numberRange", "currency", "thumbnail"];
|
|
10
10
|
};
|
|
11
11
|
readonly headerName: {
|
|
12
12
|
readonly type: "string";
|
|
@@ -17,7 +17,7 @@ export declare const litDataGridTanstackSchema: {
|
|
|
17
17
|
};
|
|
18
18
|
readonly type: {
|
|
19
19
|
readonly type: "string";
|
|
20
|
-
readonly enum: readonly ["string", "number", "date", "range", "select", "multiselect", "dateTime", "actions", "dateTimeRange", "dateRange", "numberRange", "currency", "thumbnail"];
|
|
20
|
+
readonly enum: readonly ["string", "number", "date", "range", "select", "multiselect", "includeExclude", "dateTime", "actions", "dateTimeRange", "dateRange", "numberRange", "currency", "thumbnail"];
|
|
21
21
|
};
|
|
22
22
|
readonly headerName: {
|
|
23
23
|
readonly type: "string";
|
|
@@ -2,6 +2,7 @@ import { LitElement } from 'lit';
|
|
|
2
2
|
import './lit-icon.js';
|
|
3
3
|
import './lit-icon-button.js';
|
|
4
4
|
import './lit-select.js';
|
|
5
|
+
import './lit-include-exclude-select.js';
|
|
5
6
|
import './lit-date-picker.js';
|
|
6
7
|
import './lit-data-grid-operators-popover.js';
|
|
7
8
|
import './lit-input.js';
|
|
@@ -28,6 +29,10 @@ export declare class FilterInputs extends LitElement {
|
|
|
28
29
|
checkNumberOfCharacters(value: string, cf: Function): void;
|
|
29
30
|
willUpdate(changedProperties: any): void;
|
|
30
31
|
handleSetFilter(value: any): void;
|
|
32
|
+
handleSetIncludeExcludeFilter(value: {
|
|
33
|
+
in: string[];
|
|
34
|
+
nin: string[];
|
|
35
|
+
}): void;
|
|
31
36
|
private handleSetOperator;
|
|
32
37
|
handleClearFilter(): void;
|
|
33
38
|
private handleDateChange;
|
|
@@ -39,6 +44,7 @@ export declare class FilterInputs extends LitElement {
|
|
|
39
44
|
focus(): void;
|
|
40
45
|
renderNumberRangeInput(): import("lit-html").TemplateResult<1>;
|
|
41
46
|
renderSelectInput(multiselect?: boolean): import("lit-html").TemplateResult<1>;
|
|
47
|
+
renderIncludeExcludeInput(): import("lit-html").TemplateResult<1>;
|
|
42
48
|
renderNumberInput(): import("lit-html").TemplateResult<1>;
|
|
43
49
|
renderDateInput(): import("lit-html").TemplateResult<1>;
|
|
44
50
|
renderDateRangeInput(): import("lit-html").TemplateResult<1>;
|
|
@@ -12,6 +12,7 @@ import { property, state } from 'lit/decorators.js';
|
|
|
12
12
|
import './lit-icon.js';
|
|
13
13
|
import './lit-icon-button.js';
|
|
14
14
|
import './lit-select.js';
|
|
15
|
+
import './lit-include-exclude-select.js';
|
|
15
16
|
import './lit-date-picker.js';
|
|
16
17
|
import './lit-data-grid-operators-popover.js';
|
|
17
18
|
import './lit-input.js';
|
|
@@ -69,7 +70,9 @@ export class FilterInputs extends LitElement {
|
|
|
69
70
|
}
|
|
70
71
|
willUpdate(changedProperties) {
|
|
71
72
|
if (changedProperties.has('column')) {
|
|
72
|
-
if (this.filterVariant === 'select' ||
|
|
73
|
+
if (this.filterVariant === 'select' ||
|
|
74
|
+
this.filterVariant === 'multiselect' ||
|
|
75
|
+
this.filterVariant === 'includeExclude') {
|
|
73
76
|
if (this.column?.columnDef?.meta.valueOptions || this.server) {
|
|
74
77
|
this.valueOptions = this.column?.columnDef?.meta.valueOptions || [];
|
|
75
78
|
}
|
|
@@ -103,6 +106,21 @@ export class FilterInputs extends LitElement {
|
|
|
103
106
|
this.column?.setFilterValue();
|
|
104
107
|
}
|
|
105
108
|
}
|
|
109
|
+
handleSetIncludeExcludeFilter(value) {
|
|
110
|
+
const isEmpty = (value?.in?.length ?? 0) === 0 && (value?.nin?.length ?? 0) === 0;
|
|
111
|
+
this.value = isEmpty ? null : value;
|
|
112
|
+
if (isEmpty) {
|
|
113
|
+
// {in:[],nin:[]} is not lodash-empty, so clear explicitly to drop the
|
|
114
|
+
// active-filter dot (column.getIsFiltered() -> false).
|
|
115
|
+
this.column?.setFilterValue();
|
|
116
|
+
}
|
|
117
|
+
else if (this.server) {
|
|
118
|
+
this.column?.setFilterValue({ value, operator: 'includeExclude' });
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
this.column?.setFilterValue(value);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
106
124
|
handleClearFilter() {
|
|
107
125
|
if (this.server) {
|
|
108
126
|
this.operator = this.filterOperators[0].value;
|
|
@@ -192,6 +210,11 @@ export class FilterInputs extends LitElement {
|
|
|
192
210
|
if (selects) {
|
|
193
211
|
selects.forEach((select) => elements.push(select));
|
|
194
212
|
}
|
|
213
|
+
// Check for lit-include-exclude-select components
|
|
214
|
+
const includeExcludes = this.shadowRoot?.querySelectorAll('lit-include-exclude-select');
|
|
215
|
+
if (includeExcludes) {
|
|
216
|
+
includeExcludes.forEach((el) => elements.push(el));
|
|
217
|
+
}
|
|
195
218
|
// Check for lit-date-picker components
|
|
196
219
|
const datePickers = this.shadowRoot?.querySelectorAll('lit-date-picker');
|
|
197
220
|
if (datePickers) {
|
|
@@ -200,7 +223,7 @@ export class FilterInputs extends LitElement {
|
|
|
200
223
|
return elements;
|
|
201
224
|
}
|
|
202
225
|
focus() {
|
|
203
|
-
const firstFocusable = this.shadowRoot?.querySelector('lit-input, lit-select, lit-date-picker');
|
|
226
|
+
const firstFocusable = this.shadowRoot?.querySelector('lit-input, lit-select, lit-include-exclude-select, lit-date-picker');
|
|
204
227
|
firstFocusable?.focus();
|
|
205
228
|
}
|
|
206
229
|
renderNumberRangeInput() {
|
|
@@ -266,6 +289,20 @@ export class FilterInputs extends LitElement {
|
|
|
266
289
|
></lit-select>
|
|
267
290
|
`;
|
|
268
291
|
}
|
|
292
|
+
renderIncludeExcludeInput() {
|
|
293
|
+
const current = this.value && typeof this.value === 'object' && !Array.isArray(this.value)
|
|
294
|
+
? { in: this.value.in ?? [], nin: this.value.nin ?? [] }
|
|
295
|
+
: { in: [], nin: [] };
|
|
296
|
+
return html `
|
|
297
|
+
<lit-include-exclude-select
|
|
298
|
+
id=${this.column.id}
|
|
299
|
+
.value="${current}"
|
|
300
|
+
.options="${this.valueOptions}"
|
|
301
|
+
.onChange="${(v) => this.handleSetIncludeExcludeFilter(v)}"
|
|
302
|
+
@keydown=${(e) => this.handleKeyDown(e)}
|
|
303
|
+
></lit-include-exclude-select>
|
|
304
|
+
`;
|
|
305
|
+
}
|
|
269
306
|
renderNumberInput() {
|
|
270
307
|
return html `
|
|
271
308
|
<lit-input
|
|
@@ -363,6 +400,9 @@ export class FilterInputs extends LitElement {
|
|
|
363
400
|
else if (this.filterVariant === 'multiselect') {
|
|
364
401
|
return this.renderSelectInput(true);
|
|
365
402
|
}
|
|
403
|
+
else if (this.filterVariant === 'includeExclude') {
|
|
404
|
+
return this.renderIncludeExcludeInput();
|
|
405
|
+
}
|
|
366
406
|
else if (this.filterVariant === 'number') {
|
|
367
407
|
return this.renderNumberInput();
|
|
368
408
|
}
|
|
@@ -15,6 +15,7 @@ export class LitCheckbox extends LitElement {
|
|
|
15
15
|
this.disabled = false;
|
|
16
16
|
this.checked = false;
|
|
17
17
|
this.indeterminate = false;
|
|
18
|
+
this.cross = false;
|
|
18
19
|
this.onChange = () => { };
|
|
19
20
|
this.value = '';
|
|
20
21
|
this.label = '';
|
|
@@ -68,6 +69,7 @@ export class LitCheckbox extends LitElement {
|
|
|
68
69
|
id="${this.id || `checkbox-${Math.random().toString(36).substring(2, 11)}`}"
|
|
69
70
|
class=${classMap({
|
|
70
71
|
indeterminate: this.indeterminate,
|
|
72
|
+
cross: this.cross,
|
|
71
73
|
marginLeft: this.label !== '',
|
|
72
74
|
})}
|
|
73
75
|
type="checkbox"
|
|
@@ -123,11 +125,24 @@ LitCheckbox.styles = css `
|
|
|
123
125
|
font-size: 16px;
|
|
124
126
|
}
|
|
125
127
|
|
|
128
|
+
/* Cross state: same filled-box design as :checked, but a cross glyph
|
|
129
|
+
(used by the tri-state include/exclude filter to mark exclusion). */
|
|
130
|
+
input[type='checkbox'].cross {
|
|
131
|
+
background-color: var(--color-error-main, #ca2d0a);
|
|
132
|
+
border-color: var(--color-error-main, #ca2d0a);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
input[type='checkbox'].cross::after {
|
|
136
|
+
content: '✕';
|
|
137
|
+
color: var(--background-paper, #fff);
|
|
138
|
+
font-size: 13px;
|
|
139
|
+
}
|
|
140
|
+
|
|
126
141
|
/* Custom checkmark */
|
|
127
142
|
input[type='checkbox']::after {
|
|
128
143
|
content: '✓';
|
|
129
144
|
color: var(--background-paper, #111827);
|
|
130
|
-
font-size:
|
|
145
|
+
font-size: 13px;
|
|
131
146
|
visible: none;
|
|
132
147
|
}
|
|
133
148
|
|
|
@@ -166,6 +181,9 @@ __decorate([
|
|
|
166
181
|
__decorate([
|
|
167
182
|
property({ type: Boolean })
|
|
168
183
|
], LitCheckbox.prototype, "indeterminate", void 0);
|
|
184
|
+
__decorate([
|
|
185
|
+
property({ type: Boolean })
|
|
186
|
+
], LitCheckbox.prototype, "cross", void 0);
|
|
169
187
|
__decorate([
|
|
170
188
|
property({ type: Function })
|
|
171
189
|
], LitCheckbox.prototype, "onChange", void 0);
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { LitElement } from 'lit';
|
|
2
|
+
import './simple-popper.js';
|
|
3
|
+
import './lit-icon.js';
|
|
4
|
+
import './lit-icon-button.js';
|
|
5
|
+
import './lit-checkbox.js';
|
|
6
|
+
import './lit-menu-item.js';
|
|
7
|
+
import './lit-menu.js';
|
|
8
|
+
export type IncludeExcludeValue = {
|
|
9
|
+
in: string[];
|
|
10
|
+
nin: string[];
|
|
11
|
+
};
|
|
12
|
+
export type IncludeExcludeState = 'in' | 'nin' | 'default';
|
|
13
|
+
export type IncludeExcludeSelectProps = {
|
|
14
|
+
id: string;
|
|
15
|
+
value?: IncludeExcludeValue;
|
|
16
|
+
options: {
|
|
17
|
+
value: string;
|
|
18
|
+
label: string;
|
|
19
|
+
}[];
|
|
20
|
+
onChange: (value: IncludeExcludeValue) => void;
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
placeholder?: string;
|
|
23
|
+
};
|
|
24
|
+
export declare class LitIncludeExcludeSelect extends LitElement {
|
|
25
|
+
id: string;
|
|
26
|
+
value: IncludeExcludeValue;
|
|
27
|
+
options: {
|
|
28
|
+
value: string;
|
|
29
|
+
label: string;
|
|
30
|
+
}[];
|
|
31
|
+
onChange: (value: IncludeExcludeValue) => void;
|
|
32
|
+
disabled: boolean;
|
|
33
|
+
placeholder: string;
|
|
34
|
+
private isOpen;
|
|
35
|
+
private searchQuery;
|
|
36
|
+
static styles: import("lit").CSSResult;
|
|
37
|
+
connectedCallback(): void;
|
|
38
|
+
disconnectedCallback(): void;
|
|
39
|
+
private handleOutsideClick;
|
|
40
|
+
private getState;
|
|
41
|
+
private cycleOption;
|
|
42
|
+
private clearValue;
|
|
43
|
+
private isEmptyValue;
|
|
44
|
+
private toggleCustomPopover;
|
|
45
|
+
private closePopover;
|
|
46
|
+
private handleInput;
|
|
47
|
+
private getFilteredOptions;
|
|
48
|
+
focus(): void;
|
|
49
|
+
private handleKeyDown;
|
|
50
|
+
private labelOf;
|
|
51
|
+
private displayValue;
|
|
52
|
+
private menuTemplate;
|
|
53
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
54
|
+
}
|
|
55
|
+
declare global {
|
|
56
|
+
interface HTMLElementTagNameMap {
|
|
57
|
+
'lit-include-exclude-select': LitIncludeExcludeSelect;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { LitElement, html, css, nothing } from 'lit';
|
|
8
|
+
import { msg } from '@lit/localize';
|
|
9
|
+
import { classMap } from 'lit/directives/class-map.js';
|
|
10
|
+
import { property, state } from 'lit/decorators.js';
|
|
11
|
+
import './simple-popper.js';
|
|
12
|
+
import './lit-icon.js';
|
|
13
|
+
import './lit-icon-button.js';
|
|
14
|
+
import './lit-checkbox.js';
|
|
15
|
+
import './lit-menu-item.js';
|
|
16
|
+
import './lit-menu.js';
|
|
17
|
+
// Tri-state multiselect ("includeExclude") filter. Each option cycles through
|
|
18
|
+
// three states on click: default -> in (include) -> nin (exclude) -> default.
|
|
19
|
+
// Modeled on lit-select: input box + simple-popper + lit-menu, with the menu
|
|
20
|
+
// rendered only while open to keep the closed steady-state cheap.
|
|
21
|
+
export class LitIncludeExcludeSelect extends LitElement {
|
|
22
|
+
constructor() {
|
|
23
|
+
super(...arguments);
|
|
24
|
+
this.id = '';
|
|
25
|
+
this.value = { in: [], nin: [] };
|
|
26
|
+
this.options = [];
|
|
27
|
+
this.onChange = () => { };
|
|
28
|
+
this.disabled = false;
|
|
29
|
+
this.placeholder = '';
|
|
30
|
+
this.isOpen = false;
|
|
31
|
+
this.searchQuery = null;
|
|
32
|
+
this.handleOutsideClick = (event) => {
|
|
33
|
+
const path = event.composedPath();
|
|
34
|
+
if (!path.includes(this) &&
|
|
35
|
+
!path.some((element) => element instanceof HTMLElement && element.tagName === 'SIMPLE-POPPER')) {
|
|
36
|
+
this.closePopover();
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
connectedCallback() {
|
|
41
|
+
super.connectedCallback();
|
|
42
|
+
document.addEventListener('click', this.handleOutsideClick);
|
|
43
|
+
}
|
|
44
|
+
disconnectedCallback() {
|
|
45
|
+
super.disconnectedCallback();
|
|
46
|
+
document.removeEventListener('click', this.handleOutsideClick);
|
|
47
|
+
this.closePopover();
|
|
48
|
+
}
|
|
49
|
+
getState(optionValue) {
|
|
50
|
+
if (this.value?.in?.includes(optionValue))
|
|
51
|
+
return 'in';
|
|
52
|
+
if (this.value?.nin?.includes(optionValue))
|
|
53
|
+
return 'nin';
|
|
54
|
+
return 'default';
|
|
55
|
+
}
|
|
56
|
+
// default -> in -> nin -> default
|
|
57
|
+
cycleOption(optionValue) {
|
|
58
|
+
const current = this.getState(optionValue);
|
|
59
|
+
const nextIn = (this.value?.in ?? []).filter((v) => v !== optionValue);
|
|
60
|
+
const nextNin = (this.value?.nin ?? []).filter((v) => v !== optionValue);
|
|
61
|
+
if (current === 'default') {
|
|
62
|
+
nextIn.push(optionValue);
|
|
63
|
+
}
|
|
64
|
+
else if (current === 'in') {
|
|
65
|
+
nextNin.push(optionValue);
|
|
66
|
+
}
|
|
67
|
+
// current === 'nin' -> default: leave the value out of both lists.
|
|
68
|
+
const newValue = { in: nextIn, nin: nextNin };
|
|
69
|
+
this.value = newValue;
|
|
70
|
+
this.onChange(newValue);
|
|
71
|
+
}
|
|
72
|
+
clearValue(e) {
|
|
73
|
+
e.stopPropagation();
|
|
74
|
+
const cleared = { in: [], nin: [] };
|
|
75
|
+
this.value = cleared;
|
|
76
|
+
this.onChange(cleared);
|
|
77
|
+
this.searchQuery = null;
|
|
78
|
+
}
|
|
79
|
+
isEmptyValue() {
|
|
80
|
+
return (this.value?.in?.length ?? 0) === 0 && (this.value?.nin?.length ?? 0) === 0;
|
|
81
|
+
}
|
|
82
|
+
toggleCustomPopover(event) {
|
|
83
|
+
event?.preventDefault();
|
|
84
|
+
this.isOpen = !this.isOpen;
|
|
85
|
+
}
|
|
86
|
+
closePopover() {
|
|
87
|
+
this.isOpen = false;
|
|
88
|
+
this.searchQuery = null;
|
|
89
|
+
}
|
|
90
|
+
handleInput(event) {
|
|
91
|
+
this.searchQuery = event.target.value;
|
|
92
|
+
this.isOpen = true;
|
|
93
|
+
}
|
|
94
|
+
getFilteredOptions() {
|
|
95
|
+
if (!this.searchQuery) {
|
|
96
|
+
return this.options;
|
|
97
|
+
}
|
|
98
|
+
const query = this.searchQuery.toLowerCase();
|
|
99
|
+
return this.options.filter((option) => option.label?.toLowerCase().includes(query));
|
|
100
|
+
}
|
|
101
|
+
focus() {
|
|
102
|
+
this.shadowRoot?.querySelector('input')?.focus();
|
|
103
|
+
}
|
|
104
|
+
handleKeyDown(event) {
|
|
105
|
+
if (event.key === 'Tab' || event.key === 'Escape') {
|
|
106
|
+
this.closePopover();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
labelOf(value) {
|
|
110
|
+
return this.options.find((option) => option.value == value)?.label ?? value;
|
|
111
|
+
}
|
|
112
|
+
displayValue() {
|
|
113
|
+
// Single comma-separated list of every selected option; excluded ones get
|
|
114
|
+
// a leading "!" (e.g. "Active, !Pending").
|
|
115
|
+
const include = (this.value?.in ?? []).map((v) => this.labelOf(v));
|
|
116
|
+
const exclude = (this.value?.nin ?? []).map((v) => `!${this.labelOf(v)}`);
|
|
117
|
+
return [...include, ...exclude].join(', ');
|
|
118
|
+
}
|
|
119
|
+
menuTemplate(selectOptions) {
|
|
120
|
+
return html `
|
|
121
|
+
<lit-menu tabindex="0" id="menu">
|
|
122
|
+
${selectOptions?.length === 0
|
|
123
|
+
? html `<lit-menu-item .disabled=${true}>${msg('Nenalezeno')}</lit-menu-item>`
|
|
124
|
+
: selectOptions?.map((option, index) => {
|
|
125
|
+
const state = this.getState(option.value);
|
|
126
|
+
return html `
|
|
127
|
+
<lit-menu-item
|
|
128
|
+
id="${this.id}-${index}"
|
|
129
|
+
.onClick="${() => this.cycleOption(option.value)}"
|
|
130
|
+
>
|
|
131
|
+
<span class="option-row">
|
|
132
|
+
<lit-checkbox
|
|
133
|
+
class="cursor"
|
|
134
|
+
.checked=${state === 'in'}
|
|
135
|
+
.cross=${state === 'nin'}
|
|
136
|
+
></lit-checkbox>
|
|
137
|
+
${option.label}
|
|
138
|
+
</span>
|
|
139
|
+
</lit-menu-item>
|
|
140
|
+
`;
|
|
141
|
+
})}
|
|
142
|
+
</lit-menu>
|
|
143
|
+
`;
|
|
144
|
+
}
|
|
145
|
+
render() {
|
|
146
|
+
const selectOptions = this.getFilteredOptions();
|
|
147
|
+
const dropdownIconClasses = {
|
|
148
|
+
'dropdown-icon': true,
|
|
149
|
+
'dropdown-icon--rotate': this.isOpen,
|
|
150
|
+
};
|
|
151
|
+
const display = this.isEmptyValue() ? '' : this.displayValue();
|
|
152
|
+
return html `
|
|
153
|
+
<div class="custom-icon-wrapper" slot="reference">
|
|
154
|
+
<input
|
|
155
|
+
id="${this.id}"
|
|
156
|
+
type="text"
|
|
157
|
+
placeholder="${this.placeholder}"
|
|
158
|
+
.value="${this.searchQuery ?? display ?? ''}"
|
|
159
|
+
@input=${this.handleInput}
|
|
160
|
+
@click="${(e) => this.toggleCustomPopover(e)}"
|
|
161
|
+
@keydown=${this.handleKeyDown}
|
|
162
|
+
.disabled="${this.disabled}"
|
|
163
|
+
/>
|
|
164
|
+
${!this.isEmptyValue()
|
|
165
|
+
? html `
|
|
166
|
+
<div class="custom-icon" @click="${this.clearValue}">
|
|
167
|
+
<lit-icon-button
|
|
168
|
+
icon="close"
|
|
169
|
+
size="small"
|
|
170
|
+
variant="text"
|
|
171
|
+
color="secondary"
|
|
172
|
+
></lit-icon-button>
|
|
173
|
+
</div>
|
|
174
|
+
`
|
|
175
|
+
: ''}
|
|
176
|
+
<div class="${classMap(dropdownIconClasses)}">
|
|
177
|
+
<lit-icon
|
|
178
|
+
class="${this.disabled ? 'dropdown-icon-disabled' : null}"
|
|
179
|
+
icon="chevrondown"
|
|
180
|
+
size="17px"
|
|
181
|
+
></lit-icon>
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
<simple-popper
|
|
185
|
+
.showing=${this.isOpen}
|
|
186
|
+
placement="bottom-start"
|
|
187
|
+
@close="${this.closePopover}"
|
|
188
|
+
manualOpening=${true}
|
|
189
|
+
maxWidthAsTarget=${true}
|
|
190
|
+
@keydown=${this.handleKeyDown}
|
|
191
|
+
>
|
|
192
|
+
${this.isOpen ? this.menuTemplate(selectOptions) : nothing}
|
|
193
|
+
</simple-popper>
|
|
194
|
+
`;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
LitIncludeExcludeSelect.styles = css `
|
|
198
|
+
.custom-icon-wrapper {
|
|
199
|
+
position: relative;
|
|
200
|
+
width: 100%;
|
|
201
|
+
display: inline-block;
|
|
202
|
+
color: var(--text-primary, #111827);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
input {
|
|
206
|
+
padding: 0.5rem;
|
|
207
|
+
border: 0.0625rem solid var(--color-divider, #d0d3db);
|
|
208
|
+
border-radius: 0.25rem;
|
|
209
|
+
min-width: 70px;
|
|
210
|
+
width: -webkit-fill-available;
|
|
211
|
+
width: -moz-available;
|
|
212
|
+
width: fill-available;
|
|
213
|
+
background-color: var(--background-paper, #fff);
|
|
214
|
+
color: var(--text-primary, #111827);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
input:hover {
|
|
218
|
+
border: 0.0625rem solid var(--color-secondary-main, #111827);
|
|
219
|
+
cursor: pointer;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
input:focus {
|
|
223
|
+
outline: none;
|
|
224
|
+
border: 0.0625rem solid var(--color-secondary-dark, #010204);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
input:disabled {
|
|
228
|
+
cursor: inherit;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.custom-icon-wrapper .custom-icon {
|
|
232
|
+
display: inline-block;
|
|
233
|
+
position: absolute;
|
|
234
|
+
right: 27px;
|
|
235
|
+
top: 50%;
|
|
236
|
+
transform: translateY(-50%);
|
|
237
|
+
cursor: pointer;
|
|
238
|
+
pointer-events: auto;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.custom-icon-wrapper .dropdown-icon {
|
|
242
|
+
position: absolute;
|
|
243
|
+
right: 10px;
|
|
244
|
+
top: 50%;
|
|
245
|
+
transform: translateY(-50%);
|
|
246
|
+
pointer-events: none;
|
|
247
|
+
background-color: var(--background-paper, #fff);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.custom-icon-wrapper .dropdown-icon--rotate {
|
|
251
|
+
transform: translateY(-55%) rotate(180deg);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.dropdown-icon-disabled {
|
|
255
|
+
background: #f8f8f8;
|
|
256
|
+
color: #5d6371;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.option-row {
|
|
260
|
+
display: flex;
|
|
261
|
+
align-items: center;
|
|
262
|
+
gap: 0.25rem;
|
|
263
|
+
width: 100%;
|
|
264
|
+
}
|
|
265
|
+
`;
|
|
266
|
+
__decorate([
|
|
267
|
+
property({ type: String })
|
|
268
|
+
], LitIncludeExcludeSelect.prototype, "id", void 0);
|
|
269
|
+
__decorate([
|
|
270
|
+
property({ type: Object })
|
|
271
|
+
], LitIncludeExcludeSelect.prototype, "value", void 0);
|
|
272
|
+
__decorate([
|
|
273
|
+
property({ type: Array })
|
|
274
|
+
], LitIncludeExcludeSelect.prototype, "options", void 0);
|
|
275
|
+
__decorate([
|
|
276
|
+
property({ type: Function })
|
|
277
|
+
], LitIncludeExcludeSelect.prototype, "onChange", void 0);
|
|
278
|
+
__decorate([
|
|
279
|
+
property({ type: Boolean })
|
|
280
|
+
], LitIncludeExcludeSelect.prototype, "disabled", void 0);
|
|
281
|
+
__decorate([
|
|
282
|
+
property({ type: String })
|
|
283
|
+
], LitIncludeExcludeSelect.prototype, "placeholder", void 0);
|
|
284
|
+
__decorate([
|
|
285
|
+
property({ reflect: true, type: Boolean }),
|
|
286
|
+
state()
|
|
287
|
+
], LitIncludeExcludeSelect.prototype, "isOpen", void 0);
|
|
288
|
+
__decorate([
|
|
289
|
+
state()
|
|
290
|
+
], LitIncludeExcludeSelect.prototype, "searchQuery", void 0);
|
|
291
|
+
if (!window.customElements.get('lit-include-exclude-select')) {
|
|
292
|
+
window.customElements.define('lit-include-exclude-select', LitIncludeExcludeSelect);
|
|
293
|
+
}
|
|
@@ -4,3 +4,7 @@ export declare const dateRangeFilterFn: (row: any, columnId: string, filterValue
|
|
|
4
4
|
}) => boolean;
|
|
5
5
|
export declare function dateFilterFn(row: any, columnId: string, filterValue: string): boolean;
|
|
6
6
|
export declare const multiselectFilterFn: (row: any, columnId: string, filterValue: any[]) => boolean;
|
|
7
|
+
export declare const includeExcludeFilterFn: (row: any, columnId: string, filterValue: {
|
|
8
|
+
in?: string[];
|
|
9
|
+
nin?: string[];
|
|
10
|
+
}) => boolean;
|
|
@@ -39,3 +39,23 @@ export const multiselectFilterFn = (row, columnId, filterValue) => {
|
|
|
39
39
|
const rowValue = row.getValue(columnId);
|
|
40
40
|
return !!rowValue && filterValue.includes(rowValue);
|
|
41
41
|
};
|
|
42
|
+
// Tri-state ("includeExclude") column filter: each option can be included (`in`),
|
|
43
|
+
// excluded (`nin`) or left in the default/unset state. Semantics: exclude wins,
|
|
44
|
+
// then `in` acts as a whitelist when non-empty.
|
|
45
|
+
export const includeExcludeFilterFn = (row, columnId, filterValue) => {
|
|
46
|
+
const include = filterValue?.in ?? [];
|
|
47
|
+
const exclude = filterValue?.nin ?? [];
|
|
48
|
+
// Defensive: TanStack normally won't call us when the filter is cleared, but
|
|
49
|
+
// an empty selection imposes no constraint.
|
|
50
|
+
if (include.length === 0 && exclude.length === 0)
|
|
51
|
+
return true;
|
|
52
|
+
const rowValue = row.getValue(columnId);
|
|
53
|
+
// Option values are stringified for display, so compare as strings to keep
|
|
54
|
+
// numeric/boolean cell values matching their option.
|
|
55
|
+
const rowKey = rowValue == null ? null : String(rowValue);
|
|
56
|
+
if (rowKey !== null && exclude.includes(rowKey))
|
|
57
|
+
return false; // exclude wins
|
|
58
|
+
if (include.length > 0)
|
|
59
|
+
return rowKey !== null && include.includes(rowKey); // whitelist
|
|
60
|
+
return true; // only excludes specified, and this row wasn't excluded
|
|
61
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type Operator = '=' | '!=' | '>' | '>=' | '<' | '<=' | 'contains' | 'doesNotContain' | 'isEmpty' | 'isNotEmpty' | 'in' | 'nin' | 'fromTo' | 'startsWith' | 'endsWith' | 'is' | 'not' | 'isAnyOfValue';
|
|
1
|
+
export type Operator = '=' | '!=' | '>' | '>=' | '<' | '<=' | 'contains' | 'doesNotContain' | 'isEmpty' | 'isNotEmpty' | 'in' | 'nin' | 'fromTo' | 'startsWith' | 'endsWith' | 'is' | 'not' | 'isAnyOfValue' | 'includeExclude';
|
|
2
2
|
export type FieldType = 'string' | 'select' | 'number' | 'date' | 'dateTime' | 'multiselect';
|
|
3
3
|
export declare function getOperatorsByColumnType(type: FieldType): {
|
|
4
4
|
value: Operator;
|
|
@@ -35,8 +35,20 @@ export function getOperatorsByColumnType(type) {
|
|
|
35
35
|
{ label: msg('není prázdné'), value: 'isNotEmpty' },
|
|
36
36
|
];
|
|
37
37
|
case 'date':
|
|
38
|
+
return [
|
|
39
|
+
{ label: msg('rovná se'), value: '=' },
|
|
40
|
+
{ label: msg('nerovná se'), value: '!=' },
|
|
41
|
+
{ label: msg('větší než'), value: '>' },
|
|
42
|
+
{ label: msg('větší nebo rovné'), value: '>=' },
|
|
43
|
+
{ label: msg('menší než'), value: '<' },
|
|
44
|
+
{ label: msg('menší nebo rovné'), value: '<=' },
|
|
45
|
+
{ label: msg('je prázdné'), value: 'isEmpty' },
|
|
46
|
+
{ label: msg('není prázdné'), value: 'isNotEmpty' },
|
|
47
|
+
{ label: msg('od - do'), value: 'fromTo' },
|
|
48
|
+
];
|
|
38
49
|
case 'dateTime':
|
|
39
50
|
return [
|
|
51
|
+
{ label: msg('obsahuje'), value: 'contains' },
|
|
40
52
|
{ label: msg('rovná se'), value: '=' },
|
|
41
53
|
{ label: msg('nerovná se'), value: '!=' },
|
|
42
54
|
{ label: msg('větší než'), value: '>' },
|