@sdcorejs/angular 19.0.4 → 19.0.6
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/assets/{scss/README.md → STYLE-GUIDE.md} +3 -0
- package/components/index.d.ts +2 -0
- package/components/org-chart/index.d.ts +3 -0
- package/components/org-chart/src/org-chart-item-def.directive.d.ts +9 -0
- package/components/org-chart/src/org-chart.component.d.ts +26 -0
- package/components/org-chart/src/org-chart.model.d.ts +32 -0
- package/components/query-bar/src/query-bar.component.d.ts +15 -2
- package/components/query-bar/src/query-bar.model.d.ts +22 -0
- package/components/query-builder/src/query-builder.component.d.ts +23 -6
- package/components/query-builder/src/query-builder.model.d.ts +71 -23
- package/components/section/src/section.component.d.ts +4 -1
- package/components/splitter/src/splitter.component.d.ts +8 -2
- package/components/splitter/src/splitter.models.d.ts +13 -0
- package/components/table/src/services/table-filter/table-filter.model.d.ts +2 -0
- package/components/tree/index.d.ts +3 -0
- package/components/tree/src/tree-item-def.directive.d.ts +9 -0
- package/components/tree/src/tree.component.d.ts +102 -0
- package/components/tree/src/tree.model.d.ts +142 -0
- package/fesm2022/sdcorejs-angular-components-form-generic.mjs +2 -2
- package/fesm2022/sdcorejs-angular-components-form-generic.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-org-chart.mjs +110 -0
- package/fesm2022/sdcorejs-angular-components-org-chart.mjs.map +1 -0
- package/fesm2022/sdcorejs-angular-components-query-bar.mjs +37 -8
- package/fesm2022/sdcorejs-angular-components-query-bar.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-query-builder.mjs +257 -59
- package/fesm2022/sdcorejs-angular-components-query-builder.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-section.mjs +7 -4
- package/fesm2022/sdcorejs-angular-components-section.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-splitter.mjs +23 -12
- package/fesm2022/sdcorejs-angular-components-splitter.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-table.mjs +6 -2
- package/fesm2022/sdcorejs-angular-components-table.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-tree.mjs +526 -0
- package/fesm2022/sdcorejs-angular-components-tree.mjs.map +1 -0
- package/fesm2022/sdcorejs-angular-components.mjs +2 -0
- package/fesm2022/sdcorejs-angular-components.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs +2 -2
- package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-select.mjs +65 -6
- package/fesm2022/sdcorejs-angular-forms-select.mjs.map +1 -1
- package/forms/select/index.d.ts +1 -0
- package/forms/select/src/select-footer-action.directive.d.ts +11 -0
- package/forms/select/src/select.component.d.ts +17 -1
- package/package.json +10 -2
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { NgTemplateOutlet } from '@angular/common';
|
|
2
2
|
import * as i0 from '@angular/core';
|
|
3
3
|
import { input, booleanAttribute, model, signal, computed, effect, untracked, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
4
|
+
import * as i1 from '@angular/material/icon';
|
|
5
|
+
import { MatIconModule } from '@angular/material/icon';
|
|
4
6
|
import { SdDate } from '@sdcorejs/angular/forms/date';
|
|
5
7
|
import { SdDatetime } from '@sdcorejs/angular/forms/datetime';
|
|
6
8
|
import { SdInput } from '@sdcorejs/angular/forms/input';
|
|
9
|
+
import { SdInputNumber } from '@sdcorejs/angular/forms/input-number';
|
|
7
10
|
import { SdSelect } from '@sdcorejs/angular/forms/select';
|
|
11
|
+
import { SdItemDefDefDirective } from '@sdcorejs/angular/forms/directives';
|
|
8
12
|
import { SdOperator } from '@sdcorejs/angular/components/operator';
|
|
13
|
+
import { FilterUtilities } from '@sdcorejs/utils/fns';
|
|
9
14
|
|
|
10
15
|
/** Type guard — narrows a `QbNode` to a `QbGroup`. */
|
|
11
16
|
function isQbGroup(node) {
|
|
@@ -64,6 +69,32 @@ function qbIsNoDataOperator(op) {
|
|
|
64
69
|
function qbIsMultiOperator(op) {
|
|
65
70
|
return !!op && QB_MULTI_OPERATORS.includes(op);
|
|
66
71
|
}
|
|
72
|
+
/** True when the operator accepts one right-hand operand, including a field reference. */
|
|
73
|
+
function qbSupportsFieldCompareOperator(op) {
|
|
74
|
+
return !!op && !qbIsNoDataOperator(op) && !qbIsMultiOperator(op) && op !== 'BETWEEN';
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Icon fallback per field type when `SdQueryBuilderField.icon` is not set.
|
|
78
|
+
* Mirrors query-bar's `SD_QUERY_TYPE_ICON` so the two components read consistently,
|
|
79
|
+
* but kept local so query-builder has no dependency on the query-bar package.
|
|
80
|
+
*/
|
|
81
|
+
const QB_TYPE_ICON = {
|
|
82
|
+
string: 'text_fields',
|
|
83
|
+
number: 'tag',
|
|
84
|
+
boolean: 'toggle_on',
|
|
85
|
+
date: 'event',
|
|
86
|
+
datetime: 'schedule',
|
|
87
|
+
values: 'list',
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Icon shown before a field in the field picker / selected trigger.
|
|
91
|
+
* Priority: `field.icon` → `QB_TYPE_ICON[field.type]` → `'tune'` (default, like query-bar).
|
|
92
|
+
*/
|
|
93
|
+
function qbFieldIcon(field) {
|
|
94
|
+
if (!field)
|
|
95
|
+
return 'tune';
|
|
96
|
+
return field.icon ?? QB_TYPE_ICON[field.type] ?? 'tune';
|
|
97
|
+
}
|
|
67
98
|
// ---------------------------------------------------------------------------
|
|
68
99
|
// Node factories — monotonic counter for stable, render-safe ids (no Math.random
|
|
69
100
|
// / Date.now so SSR + tests stay deterministic).
|
|
@@ -74,26 +105,35 @@ function qbId(prefix = 'qb') {
|
|
|
74
105
|
return `${prefix}-${++qbSeq}`;
|
|
75
106
|
}
|
|
76
107
|
/** Build a fresh rule node (optionally pre-filled). */
|
|
77
|
-
function qbNewRule(field, operator, value) {
|
|
78
|
-
return { id: qbId('r'), kind: 'rule', field, operator, value };
|
|
108
|
+
function qbNewRule(field, operator, value, valueSource, compareField) {
|
|
109
|
+
return { id: qbId('r'), kind: 'rule', field, operator, value, valueSource, compareField };
|
|
79
110
|
}
|
|
80
111
|
/** Build a fresh group node. */
|
|
81
112
|
function qbNewGroup(logic = 'AND', children = []) {
|
|
82
113
|
return { id: qbId('g'), kind: 'group', logic, children };
|
|
83
114
|
}
|
|
84
|
-
/**
|
|
85
|
-
|
|
86
|
-
|
|
115
|
+
/** Sentinel held as a rule value (and emitted as `data`) for the "today" date mode. */
|
|
116
|
+
const QB_TODAY = 'TODAY';
|
|
117
|
+
/** Type guard — narrows a rule value to a utils `DateRelative` (offset spec). */
|
|
118
|
+
const qbIsRelativeDate = FilterUtilities.isDateRelative;
|
|
119
|
+
/** Type guard — narrows a rule value to the `'TODAY'` sentinel ("now"/today mode). */
|
|
120
|
+
function qbIsToday(v) {
|
|
121
|
+
return v === QB_TODAY;
|
|
87
122
|
}
|
|
88
123
|
/** Starting relative value when a rule first switches to "relative" mode. */
|
|
89
124
|
function qbDefaultRelative() {
|
|
90
|
-
return {
|
|
125
|
+
return { amount: 1, direction: 'previous', unit: 'day' };
|
|
91
126
|
}
|
|
92
127
|
/** Stable option list for the date-mode select (module ref — never reallocated). */
|
|
93
128
|
const QB_DATE_MODES = [
|
|
94
|
-
{ value: 'absolute', display: 'Ngày cụ thể' },
|
|
95
|
-
{ value: 'now', display: 'Hôm nay' },
|
|
96
|
-
{ value: 'relative', display: 'Tương đối' },
|
|
129
|
+
{ value: 'absolute', display: 'Ngày cụ thể', icon: 'event' },
|
|
130
|
+
{ value: 'now', display: 'Hôm nay', icon: 'today' },
|
|
131
|
+
{ value: 'relative', display: 'Tương đối', icon: 'history' },
|
|
132
|
+
];
|
|
133
|
+
/** Stable option list for choosing between a literal value and another field. */
|
|
134
|
+
const QB_VALUE_SOURCE_OPTIONS = [
|
|
135
|
+
{ value: 'literal', display: 'Nhập giá trị', icon: 'edit_note' },
|
|
136
|
+
{ value: 'field', display: 'Chọn trường', icon: 'view_column' },
|
|
97
137
|
];
|
|
98
138
|
/** Stable combined direction×unit option list (token `'unit:direction'`). */
|
|
99
139
|
const QB_RELATIVE_UNIT_OPTIONS = [
|
|
@@ -134,6 +174,11 @@ function ruleToFilter(rule) {
|
|
|
134
174
|
if (QB_NO_DATA_OPERATORS.includes(operator)) {
|
|
135
175
|
return { field: rule.field, operator };
|
|
136
176
|
}
|
|
177
|
+
if (rule.valueSource === 'field') {
|
|
178
|
+
if (!qbSupportsFieldCompareOperator(operator) || isEmptyValue(rule.compareField))
|
|
179
|
+
return null;
|
|
180
|
+
return { field: rule.field, operator, dataType: 'field', data: rule.compareField };
|
|
181
|
+
}
|
|
137
182
|
if (operator === 'BETWEEN') {
|
|
138
183
|
const v = rule.value;
|
|
139
184
|
if (!v || typeof v !== 'object')
|
|
@@ -148,20 +193,25 @@ function ruleToFilter(rule) {
|
|
|
148
193
|
return null;
|
|
149
194
|
return { field: rule.field, operator, data: arr };
|
|
150
195
|
}
|
|
196
|
+
if (qbIsToday(rule.value)) {
|
|
197
|
+
return { field: rule.field, operator, dataType: 'date-today', data: QB_TODAY };
|
|
198
|
+
}
|
|
151
199
|
if (qbIsRelativeDate(rule.value)) {
|
|
152
200
|
const v = rule.value;
|
|
153
|
-
|
|
154
|
-
return { field: rule.field, operator, data: { rel: 'now' } };
|
|
155
|
-
// offset — only emit when fully specified, else drop the incomplete rule
|
|
201
|
+
// emit only when fully specified, else drop the incomplete rule
|
|
156
202
|
if (v.unit && v.direction && typeof v.amount === 'number' && v.amount >= 1) {
|
|
157
203
|
return {
|
|
158
204
|
field: rule.field,
|
|
159
205
|
operator,
|
|
160
|
-
|
|
206
|
+
dataType: 'date-relative',
|
|
207
|
+
data: { amount: v.amount, direction: v.direction, unit: v.unit },
|
|
161
208
|
};
|
|
162
209
|
}
|
|
163
210
|
return null;
|
|
164
211
|
}
|
|
212
|
+
// A non-relative plain object is an incomplete/stray shape (e.g. a partial offset) — drop it.
|
|
213
|
+
if (rule.value !== null && typeof rule.value === 'object' && !Array.isArray(rule.value))
|
|
214
|
+
return null;
|
|
165
215
|
if (isEmptyValue(rule.value))
|
|
166
216
|
return null;
|
|
167
217
|
return { field: rule.field, operator, data: rule.value };
|
|
@@ -194,8 +244,35 @@ function ruleFromFilter(filter) {
|
|
|
194
244
|
if (QB_NO_DATA_OPERATORS.includes(operator)) {
|
|
195
245
|
return qbNewRule(filter.field, operator);
|
|
196
246
|
}
|
|
247
|
+
if (filter.dataType === 'field') {
|
|
248
|
+
return qbNewRule(filter.field, operator, undefined, 'field', filter.data);
|
|
249
|
+
}
|
|
250
|
+
// Relative dates: read the utils `dataType` discriminator back into the internal value.
|
|
251
|
+
if (filter.dataType === 'date-today') {
|
|
252
|
+
return qbNewRule(filter.field, operator, QB_TODAY);
|
|
253
|
+
}
|
|
254
|
+
if (filter.dataType === 'date-relative') {
|
|
255
|
+
return qbNewRule(filter.field, operator, filter.data);
|
|
256
|
+
}
|
|
257
|
+
// Back-compat: legacy `{ rel: 'now' | 'offset' }` payloads (pre-dataType migration)
|
|
258
|
+
// still seed the editor correctly.
|
|
259
|
+
const legacy = readLegacyRelative(filter.data);
|
|
260
|
+
if (legacy !== undefined) {
|
|
261
|
+
return qbNewRule(filter.field, operator, legacy);
|
|
262
|
+
}
|
|
197
263
|
return qbNewRule(filter.field, operator, filter.data);
|
|
198
264
|
}
|
|
265
|
+
/** Map a pre-migration `{ rel }` payload to the new internal value, or `undefined` if not legacy. */
|
|
266
|
+
function readLegacyRelative(data) {
|
|
267
|
+
if (!data || typeof data !== 'object')
|
|
268
|
+
return undefined;
|
|
269
|
+
if (data.rel === 'now')
|
|
270
|
+
return QB_TODAY;
|
|
271
|
+
if (data.rel === 'offset' && data.unit && data.direction && typeof data.amount === 'number') {
|
|
272
|
+
return { amount: data.amount, direction: data.direction, unit: data.unit };
|
|
273
|
+
}
|
|
274
|
+
return undefined;
|
|
275
|
+
}
|
|
199
276
|
function nodeFromFilter(filter) {
|
|
200
277
|
return isAndOr$1(filter) ? groupFromFilter(filter) : ruleFromFilter(filter);
|
|
201
278
|
}
|
|
@@ -223,17 +300,17 @@ function filterToTree(filter) {
|
|
|
223
300
|
function escapeStr(s) {
|
|
224
301
|
return s.replace(/'/g, "''");
|
|
225
302
|
}
|
|
226
|
-
const REL_UNIT_VI = { day: 'ngày', week: 'tuần', month: 'tháng' };
|
|
227
|
-
/** Render a relative-date
|
|
303
|
+
const REL_UNIT_VI = { hour: 'giờ', day: 'ngày', week: 'tuần', month: 'tháng' };
|
|
304
|
+
/** Render a relative-date offset as readable Vietnamese for the view string. */
|
|
228
305
|
function formatRelative(v) {
|
|
229
|
-
|
|
230
|
-
return 'hôm nay';
|
|
231
|
-
const unit = REL_UNIT_VI[v.unit ?? 'day'];
|
|
306
|
+
const unit = REL_UNIT_VI[v.unit] ?? REL_UNIT_VI.day;
|
|
232
307
|
const dir = v.direction === 'next' ? 'tới' : 'trước';
|
|
233
|
-
return `${v.amount
|
|
308
|
+
return `${v.amount} ${unit} ${dir}`;
|
|
234
309
|
}
|
|
235
310
|
/** Format one scalar value for the view string — quoted/labelled per field type. */
|
|
236
311
|
function formatScalar(field, raw) {
|
|
312
|
+
if (qbIsToday(raw))
|
|
313
|
+
return 'hôm nay';
|
|
237
314
|
if (qbIsRelativeDate(raw))
|
|
238
315
|
return formatRelative(raw);
|
|
239
316
|
if (raw === null || raw === undefined)
|
|
@@ -259,6 +336,11 @@ function likePattern(operator, raw) {
|
|
|
259
336
|
return `'${esc}%'`;
|
|
260
337
|
return `'%${esc}'`; // END_WITH / NOT_END_WITH
|
|
261
338
|
}
|
|
339
|
+
function operatorToken(operator) {
|
|
340
|
+
if (LIKE_OPERATORS.includes(operator))
|
|
341
|
+
return operator.startsWith('NOT_') ? 'not like' : 'like';
|
|
342
|
+
return SYMBOL[operator] ?? operator;
|
|
343
|
+
}
|
|
262
344
|
function ruleTokens(filter, fields) {
|
|
263
345
|
const field = fields.find(f => f.key === filter.field);
|
|
264
346
|
const out = [{ kind: 'field', text: field?.label ?? String(filter.field) }];
|
|
@@ -286,15 +368,23 @@ function ruleTokens(filter, fields) {
|
|
|
286
368
|
out.push({ kind: 'value', text: `(${arr.map((v) => formatScalar(field, v)).join(', ')})` });
|
|
287
369
|
return out;
|
|
288
370
|
}
|
|
371
|
+
if (filter.dataType === 'field') {
|
|
372
|
+
const compareField = fields.find(f => f.key === filter.data);
|
|
373
|
+
sp();
|
|
374
|
+
out.push({ kind: 'op', text: operatorToken(operator) });
|
|
375
|
+
sp();
|
|
376
|
+
out.push({ kind: 'field', text: compareField?.label ?? String(filter.data) });
|
|
377
|
+
return out;
|
|
378
|
+
}
|
|
289
379
|
if (LIKE_OPERATORS.includes(operator)) {
|
|
290
380
|
sp();
|
|
291
|
-
out.push({ kind: 'op', text: operator
|
|
381
|
+
out.push({ kind: 'op', text: operatorToken(operator) });
|
|
292
382
|
sp();
|
|
293
383
|
out.push({ kind: 'value', text: likePattern(operator, String(filter.data ?? '')) });
|
|
294
384
|
return out;
|
|
295
385
|
}
|
|
296
386
|
sp();
|
|
297
|
-
out.push({ kind: 'op', text:
|
|
387
|
+
out.push({ kind: 'op', text: operatorToken(operator) });
|
|
298
388
|
sp();
|
|
299
389
|
out.push({ kind: 'value', text: formatScalar(field, filter.data) });
|
|
300
390
|
return out;
|
|
@@ -340,6 +430,7 @@ function filterToTokens(filter, fields) {
|
|
|
340
430
|
function isAndOr(filter) {
|
|
341
431
|
return !!filter && (filter.operator === 'AND' || filter.operator === 'OR') && Array.isArray(filter.data);
|
|
342
432
|
}
|
|
433
|
+
const QB_EMPTY_FIELDS = [];
|
|
343
434
|
/**
|
|
344
435
|
* Visual filter / rule builder. Compose nested AND / OR groups of
|
|
345
436
|
* `field operator value` conditions; emits the canonical `Filter` (root is a
|
|
@@ -351,10 +442,14 @@ class SdQueryBuilder {
|
|
|
351
442
|
// -------------------------------------------------------------------------
|
|
352
443
|
// Inputs
|
|
353
444
|
// -------------------------------------------------------------------------
|
|
445
|
+
/** Main option object. Prefer this API for new usage; individual inputs stay as a migration bridge. */
|
|
446
|
+
option = input(undefined);
|
|
354
447
|
/** Filterable fields — each declares a `type` that drives the operator set + value editor. */
|
|
355
448
|
fields = input([]);
|
|
356
449
|
/** `'edit'` (default) = interactive tree · `'view'` = read-only raw-query string. */
|
|
357
450
|
mode = input('edit');
|
|
451
|
+
/** `value-only` = literal operands only · `value-or-field` = each rule may compare against another field. */
|
|
452
|
+
comparisonMode = input('value-only');
|
|
358
453
|
/** Disable all editing controls. */
|
|
359
454
|
disabled = input(false, { transform: booleanAttribute });
|
|
360
455
|
/** Prefix for `data-autoid` on inner controls (E2E selectors). */
|
|
@@ -379,9 +474,14 @@ class SdQueryBuilder {
|
|
|
379
474
|
// Derived state
|
|
380
475
|
// -------------------------------------------------------------------------
|
|
381
476
|
/** `field.key` → field, for fast lookup from a rule. */
|
|
477
|
+
resolvedFields = computed(() => this.option()?.fields ?? this.fields());
|
|
478
|
+
resolvedMode = computed(() => this.option()?.mode ?? this.mode());
|
|
479
|
+
resolvedComparisonMode = computed(() => this.option()?.comparisonMode ?? this.comparisonMode());
|
|
480
|
+
resolvedDisabled = computed(() => this.option()?.disabled ?? this.disabled());
|
|
481
|
+
resolvedAutoId = computed(() => this.option()?.autoId ?? this.autoId());
|
|
382
482
|
fieldByKey = computed(() => {
|
|
383
483
|
const map = {};
|
|
384
|
-
for (const f of this.
|
|
484
|
+
for (const f of this.resolvedFields())
|
|
385
485
|
map[f.key] = f;
|
|
386
486
|
return map;
|
|
387
487
|
});
|
|
@@ -390,7 +490,7 @@ class SdQueryBuilder {
|
|
|
390
490
|
* → `markForCheck()` loops → OOM). Recomputes only when `fields()` changes. */
|
|
391
491
|
#booleanOptionsByKey = computed(() => {
|
|
392
492
|
const map = new Map();
|
|
393
|
-
for (const f of this.
|
|
493
|
+
for (const f of this.resolvedFields()) {
|
|
394
494
|
if (f.type === 'boolean') {
|
|
395
495
|
map.set(f.key, [
|
|
396
496
|
{ value: true, display: f.trueLabel ?? 'Có' },
|
|
@@ -402,31 +502,58 @@ class SdQueryBuilder {
|
|
|
402
502
|
});
|
|
403
503
|
/** Stable option list for the date-mode select. */
|
|
404
504
|
dateModes = QB_DATE_MODES;
|
|
505
|
+
/** Stable option list for the literal-vs-field operand select. */
|
|
506
|
+
valueSourceOptions = QB_VALUE_SOURCE_OPTIONS;
|
|
405
507
|
/** Stable combined unit×direction option list for the relative select. */
|
|
406
508
|
relativeUnitOptions = QB_RELATIVE_UNIT_OPTIONS;
|
|
509
|
+
#compareFieldsByKey = computed(() => {
|
|
510
|
+
const fields = this.resolvedFields();
|
|
511
|
+
const map = new Map();
|
|
512
|
+
for (const left of fields) {
|
|
513
|
+
if (left.allowFieldCompare === false) {
|
|
514
|
+
map.set(left.key, QB_EMPTY_FIELDS);
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
map.set(left.key, fields.filter(right => {
|
|
518
|
+
if (right.key === left.key)
|
|
519
|
+
return false;
|
|
520
|
+
if (right.allowFieldCompare === false)
|
|
521
|
+
return false;
|
|
522
|
+
if (right.type !== left.type)
|
|
523
|
+
return false;
|
|
524
|
+
if ((left.compareGroup ?? right.compareGroup) !== undefined && left.compareGroup !== right.compareGroup)
|
|
525
|
+
return false;
|
|
526
|
+
return true;
|
|
527
|
+
}));
|
|
528
|
+
}
|
|
529
|
+
return map;
|
|
530
|
+
});
|
|
407
531
|
/** View-mode token stream — recomputed from the emitted value. */
|
|
408
|
-
tokens = computed(() => filterToTokens(this.value(), this.
|
|
409
|
-
isView = computed(() => this.
|
|
532
|
+
tokens = computed(() => filterToTokens(this.option()?.value ?? this.value(), this.resolvedFields()));
|
|
533
|
+
isView = computed(() => this.resolvedMode() === 'view');
|
|
410
534
|
/** Expose the type-guard to the template. */
|
|
411
535
|
isGroup = isQbGroup;
|
|
536
|
+
/** Resolve the leading icon for a field (own `icon` → per-type → `'tune'`) — used by the field picker. */
|
|
537
|
+
fieldIcon = qbFieldIcon;
|
|
412
538
|
constructor() {
|
|
413
539
|
// Inbound seeding: an EXTERNAL write to value / filters rebuilds the tree.
|
|
414
540
|
// Our own emits are recognised via #lastEmitted and skipped (no echo loop).
|
|
415
541
|
effect(() => {
|
|
416
|
-
const
|
|
417
|
-
const
|
|
418
|
-
const
|
|
542
|
+
const option = this.option();
|
|
543
|
+
const v = option?.value ?? this.value();
|
|
544
|
+
const fl = option?.filters ?? this.filters();
|
|
545
|
+
const incoming = this.#coalesce(v, fl, option?.rootLogic);
|
|
419
546
|
if (this.#filterEq(incoming, this.#lastEmitted))
|
|
420
547
|
return;
|
|
421
548
|
this.#tree.set(filterToTree(incoming));
|
|
422
549
|
this.#commit();
|
|
423
550
|
});
|
|
424
551
|
}
|
|
425
|
-
#coalesce(v, fl) {
|
|
552
|
+
#coalesce(v, fl, logic) {
|
|
426
553
|
if (v)
|
|
427
554
|
return v;
|
|
428
555
|
if (fl && fl.length)
|
|
429
|
-
return { operator: untracked(() => this.rootLogic()), data: fl };
|
|
556
|
+
return { operator: logic ?? untracked(() => this.rootLogic()), data: fl };
|
|
430
557
|
return null;
|
|
431
558
|
}
|
|
432
559
|
#filterEq(a, b) {
|
|
@@ -444,10 +571,14 @@ class SdQueryBuilder {
|
|
|
444
571
|
const filter = treeToFilter(this.#tree());
|
|
445
572
|
this.#lastEmitted = filter;
|
|
446
573
|
this.value.set(filter);
|
|
447
|
-
|
|
574
|
+
const filters = isAndOr(filter) ? filter.data : filter ? [filter] : [];
|
|
575
|
+
this.filters.set(filters);
|
|
448
576
|
const logic = this.#tree().logic;
|
|
449
577
|
if (this.rootLogic() !== logic)
|
|
450
578
|
this.rootLogic.set(logic);
|
|
579
|
+
this.option()?.onValueChange?.(filter);
|
|
580
|
+
this.option()?.onFiltersChange?.(filters);
|
|
581
|
+
this.option()?.onRootLogicChange?.(logic);
|
|
451
582
|
}
|
|
452
583
|
/** Mutate-in-place + re-render + emit. */
|
|
453
584
|
#apply() {
|
|
@@ -459,14 +590,14 @@ class SdQueryBuilder {
|
|
|
459
590
|
// -------------------------------------------------------------------------
|
|
460
591
|
/** Switch a group's AND/OR connector and re-emit. No-op when disabled or unchanged. */
|
|
461
592
|
setGroupLogic(group, logic) {
|
|
462
|
-
if (this.
|
|
593
|
+
if (this.resolvedDisabled() || group.logic === logic)
|
|
463
594
|
return;
|
|
464
595
|
group.logic = logic;
|
|
465
596
|
this.#apply();
|
|
466
597
|
}
|
|
467
598
|
/** Open this group's "+" menu (closing any other open one). UI-only — does not emit. */
|
|
468
599
|
toggleDropdown(group, event) {
|
|
469
|
-
if (this.
|
|
600
|
+
if (this.resolvedDisabled())
|
|
470
601
|
return;
|
|
471
602
|
event.stopPropagation();
|
|
472
603
|
const willOpen = !group.open;
|
|
@@ -493,7 +624,7 @@ class SdQueryBuilder {
|
|
|
493
624
|
}
|
|
494
625
|
/** Append a blank rule to `group`. */
|
|
495
626
|
addRule(group) {
|
|
496
|
-
if (this.
|
|
627
|
+
if (this.resolvedDisabled())
|
|
497
628
|
return;
|
|
498
629
|
group.children.push(qbNewRule());
|
|
499
630
|
group.open = false;
|
|
@@ -501,7 +632,7 @@ class SdQueryBuilder {
|
|
|
501
632
|
}
|
|
502
633
|
/** Append a nested sub-group (seeded with one blank rule) to `group`. */
|
|
503
634
|
addGroup(group) {
|
|
504
|
-
if (this.
|
|
635
|
+
if (this.resolvedDisabled())
|
|
505
636
|
return;
|
|
506
637
|
group.children.push(qbNewGroup('AND', [qbNewRule()]));
|
|
507
638
|
group.open = false;
|
|
@@ -509,7 +640,7 @@ class SdQueryBuilder {
|
|
|
509
640
|
}
|
|
510
641
|
/** Remove the child at `index` from `parent` (a rule or a whole sub-group). */
|
|
511
642
|
removeNode(parent, index) {
|
|
512
|
-
if (this.
|
|
643
|
+
if (this.resolvedDisabled())
|
|
513
644
|
return;
|
|
514
645
|
parent.children.splice(index, 1);
|
|
515
646
|
this.#apply();
|
|
@@ -526,7 +657,7 @@ class SdQueryBuilder {
|
|
|
526
657
|
* key is a no-op — the field can be changed but never cleared (issue #2).
|
|
527
658
|
*/
|
|
528
659
|
setField(rule, key) {
|
|
529
|
-
if (this.
|
|
660
|
+
if (this.resolvedDisabled())
|
|
530
661
|
return;
|
|
531
662
|
if (key == null)
|
|
532
663
|
return; // swap-only: never clear the field (issue #2)
|
|
@@ -535,37 +666,74 @@ class SdQueryBuilder {
|
|
|
535
666
|
const op = qbDefaultOperator(field);
|
|
536
667
|
rule.operator = op;
|
|
537
668
|
rule.value = this.#defaultValueFor(op);
|
|
669
|
+
this.#useLiteralSource(rule);
|
|
538
670
|
this.#apply();
|
|
539
671
|
}
|
|
540
672
|
/** Change `rule`'s operator and reshape its value to fit the new operator (array / range / none). */
|
|
541
673
|
setOperator(rule, op) {
|
|
542
|
-
if (this.
|
|
674
|
+
if (this.resolvedDisabled())
|
|
543
675
|
return;
|
|
544
676
|
rule.operator = op;
|
|
545
|
-
rule.
|
|
677
|
+
if (rule.valueSource === 'field' && this.canCompareWithField(rule)) {
|
|
678
|
+
rule.value = null;
|
|
679
|
+
if (!this.#isCompareFieldAllowed(rule, rule.compareField))
|
|
680
|
+
rule.compareField = undefined;
|
|
681
|
+
}
|
|
682
|
+
else {
|
|
683
|
+
rule.value = this.#reshapeValue(op, rule.value);
|
|
684
|
+
this.#useLiteralSource(rule);
|
|
685
|
+
}
|
|
546
686
|
this.#apply();
|
|
547
687
|
}
|
|
548
688
|
/** Set a single-value rule's value (coercing numeric fields to `number`). */
|
|
549
689
|
setScalar(rule, raw) {
|
|
550
|
-
if (this.
|
|
690
|
+
if (this.resolvedDisabled())
|
|
551
691
|
return;
|
|
692
|
+
this.#useLiteralSource(rule);
|
|
552
693
|
rule.value = this.#coerce(rule, raw);
|
|
553
694
|
this.#apply();
|
|
554
695
|
}
|
|
555
696
|
/** Set the lower bound of a BETWEEN rule's `{ from, to }` value. */
|
|
556
697
|
setBetweenFrom(rule, raw) {
|
|
557
|
-
if (this.
|
|
698
|
+
if (this.resolvedDisabled())
|
|
558
699
|
return;
|
|
700
|
+
this.#useLiteralSource(rule);
|
|
559
701
|
rule.value = { ...(rule.value ?? {}), from: this.#coerce(rule, raw) };
|
|
560
702
|
this.#apply();
|
|
561
703
|
}
|
|
562
704
|
/** Set the upper bound of a BETWEEN rule's `{ from, to }` value. */
|
|
563
705
|
setBetweenTo(rule, raw) {
|
|
564
|
-
if (this.
|
|
706
|
+
if (this.resolvedDisabled())
|
|
565
707
|
return;
|
|
708
|
+
this.#useLiteralSource(rule);
|
|
566
709
|
rule.value = { ...(rule.value ?? {}), to: this.#coerce(rule, raw) };
|
|
567
710
|
this.#apply();
|
|
568
711
|
}
|
|
712
|
+
setValueSource(rule, source) {
|
|
713
|
+
if (this.resolvedDisabled())
|
|
714
|
+
return;
|
|
715
|
+
if (source === 'field' && this.resolvedComparisonMode() === 'value-or-field' && this.canCompareWithField(rule)) {
|
|
716
|
+
rule.valueSource = 'field';
|
|
717
|
+
rule.value = null;
|
|
718
|
+
if (!this.#isCompareFieldAllowed(rule, rule.compareField))
|
|
719
|
+
rule.compareField = undefined;
|
|
720
|
+
}
|
|
721
|
+
else {
|
|
722
|
+
this.#useLiteralSource(rule);
|
|
723
|
+
rule.value = this.#defaultValueFor(rule.operator);
|
|
724
|
+
}
|
|
725
|
+
this.#apply();
|
|
726
|
+
}
|
|
727
|
+
setCompareField(rule, key) {
|
|
728
|
+
if (this.resolvedDisabled())
|
|
729
|
+
return;
|
|
730
|
+
if (!this.#isCompareFieldAllowed(rule, key))
|
|
731
|
+
return;
|
|
732
|
+
rule.valueSource = 'field';
|
|
733
|
+
rule.compareField = key;
|
|
734
|
+
rule.value = null;
|
|
735
|
+
this.#apply();
|
|
736
|
+
}
|
|
569
737
|
/** Coerce numeric fields to `number` so the emitted Filter carries a real number. */
|
|
570
738
|
#coerce(rule, raw) {
|
|
571
739
|
const field = this.fieldByKey()[rule.field];
|
|
@@ -588,16 +756,23 @@ class SdQueryBuilder {
|
|
|
588
756
|
if (qbIsMultiOperator(op))
|
|
589
757
|
return Array.isArray(current) ? current : current == null ? [] : [current];
|
|
590
758
|
if (op === 'BETWEEN') {
|
|
591
|
-
// a relative spec can't be a BETWEEN endpoint — reset to a fresh range
|
|
759
|
+
// a relative spec / 'TODAY' can't be a BETWEEN endpoint — reset to a fresh range
|
|
592
760
|
return current && typeof current === 'object' && !Array.isArray(current) && !qbIsRelativeDate(current)
|
|
593
761
|
? current
|
|
594
762
|
: { from: null, to: null };
|
|
595
763
|
}
|
|
596
|
-
// single value — keep a relative spec;
|
|
597
|
-
if (qbIsRelativeDate(current))
|
|
764
|
+
// single value — keep a relative spec or the 'TODAY' sentinel; else drop array/range remnants
|
|
765
|
+
if (qbIsToday(current) || qbIsRelativeDate(current))
|
|
598
766
|
return current;
|
|
599
767
|
return Array.isArray(current) || (current && typeof current === 'object') ? null : current;
|
|
600
768
|
}
|
|
769
|
+
#useLiteralSource(rule) {
|
|
770
|
+
rule.valueSource = 'literal';
|
|
771
|
+
rule.compareField = undefined;
|
|
772
|
+
}
|
|
773
|
+
#isCompareFieldAllowed(rule, key) {
|
|
774
|
+
return !!key && this.compareFields(rule).some(f => f.key === key);
|
|
775
|
+
}
|
|
601
776
|
// -------------------------------------------------------------------------
|
|
602
777
|
// Template helpers
|
|
603
778
|
// -------------------------------------------------------------------------
|
|
@@ -613,6 +788,24 @@ class SdQueryBuilder {
|
|
|
613
788
|
showOperatorSelector(rule) {
|
|
614
789
|
return this.allowedOperators(rule).length > 1;
|
|
615
790
|
}
|
|
791
|
+
compareFields(rule) {
|
|
792
|
+
const field = this.fieldOf(rule);
|
|
793
|
+
if (!field)
|
|
794
|
+
return QB_EMPTY_FIELDS;
|
|
795
|
+
return this.#compareFieldsByKey().get(field.key) ?? QB_EMPTY_FIELDS;
|
|
796
|
+
}
|
|
797
|
+
canCompareWithField(rule) {
|
|
798
|
+
return qbSupportsFieldCompareOperator(rule.operator) && this.compareFields(rule).length > 0;
|
|
799
|
+
}
|
|
800
|
+
showValueSourceSelector(rule) {
|
|
801
|
+
return this.canCompareWithField(rule) && (this.resolvedComparisonMode() === 'value-or-field' || rule.valueSource === 'field');
|
|
802
|
+
}
|
|
803
|
+
valueSourceOf(rule) {
|
|
804
|
+
return rule.valueSource === 'field' && this.canCompareWithField(rule) ? 'field' : 'literal';
|
|
805
|
+
}
|
|
806
|
+
isFieldSource(rule) {
|
|
807
|
+
return this.valueSourceOf(rule) === 'field';
|
|
808
|
+
}
|
|
616
809
|
/** True for NULL / NOT_NULL — the template then renders no value editor. */
|
|
617
810
|
isNoData(op) {
|
|
618
811
|
return qbIsNoDataOperator(op);
|
|
@@ -628,16 +821,19 @@ class SdQueryBuilder {
|
|
|
628
821
|
/** Current date-value mode of a rule, derived from its value (no separate state). */
|
|
629
822
|
dateMode(rule) {
|
|
630
823
|
const v = rule.value;
|
|
824
|
+
if (qbIsToday(v))
|
|
825
|
+
return 'now';
|
|
631
826
|
if (qbIsRelativeDate(v))
|
|
632
|
-
return
|
|
827
|
+
return 'relative';
|
|
633
828
|
return 'absolute';
|
|
634
829
|
}
|
|
635
830
|
/** Switch a date/datetime rule's value mode, reseeding the value for the new mode. */
|
|
636
831
|
setDateMode(rule, mode) {
|
|
637
|
-
if (this.
|
|
832
|
+
if (this.resolvedDisabled())
|
|
638
833
|
return;
|
|
834
|
+
this.#useLiteralSource(rule);
|
|
639
835
|
if (mode === 'now')
|
|
640
|
-
rule.value =
|
|
836
|
+
rule.value = QB_TODAY;
|
|
641
837
|
else if (mode === 'relative')
|
|
642
838
|
rule.value = qbDefaultRelative();
|
|
643
839
|
else
|
|
@@ -647,49 +843,51 @@ class SdQueryBuilder {
|
|
|
647
843
|
/** Read the offset amount of a relative rule (default 1). */
|
|
648
844
|
relativeAmount(rule) {
|
|
649
845
|
const v = rule.value;
|
|
650
|
-
return qbIsRelativeDate(v)
|
|
846
|
+
return qbIsRelativeDate(v) ? v.amount ?? 1 : 1;
|
|
651
847
|
}
|
|
652
848
|
/** Set the offset amount (clamped to an integer >= 1). */
|
|
653
849
|
setRelativeAmount(rule, raw) {
|
|
654
|
-
if (this.
|
|
850
|
+
if (this.resolvedDisabled())
|
|
655
851
|
return;
|
|
852
|
+
this.#useLiteralSource(rule);
|
|
656
853
|
const n = Math.floor(Number(raw));
|
|
657
854
|
const amount = Number.isNaN(n) || n < 1 ? 1 : n;
|
|
658
855
|
const cur = qbIsRelativeDate(rule.value) ? rule.value : qbDefaultRelative();
|
|
659
|
-
rule.value = {
|
|
856
|
+
rule.value = { amount, direction: cur.direction, unit: cur.unit };
|
|
660
857
|
this.#apply();
|
|
661
858
|
}
|
|
662
859
|
/** Read the `'unit:direction'` token of a relative rule (default `'day:previous'`). */
|
|
663
860
|
relativeUnitDirValue(rule) {
|
|
664
861
|
const v = rule.value;
|
|
665
|
-
if (qbIsRelativeDate(v)
|
|
666
|
-
return `${v.unit
|
|
862
|
+
if (qbIsRelativeDate(v))
|
|
863
|
+
return `${v.unit}:${v.direction}`;
|
|
667
864
|
return 'day:previous';
|
|
668
865
|
}
|
|
669
866
|
/** Set the offset unit + direction from a `'unit:direction'` token. */
|
|
670
867
|
setRelativeUnitDir(rule, token) {
|
|
671
|
-
if (this.
|
|
868
|
+
if (this.resolvedDisabled())
|
|
672
869
|
return;
|
|
870
|
+
this.#useLiteralSource(rule);
|
|
673
871
|
const [unit, direction] = (token ?? 'day:previous').split(':');
|
|
674
872
|
const cur = qbIsRelativeDate(rule.value) ? rule.value : qbDefaultRelative();
|
|
675
|
-
rule.value = {
|
|
873
|
+
rule.value = { amount: cur.amount, direction, unit };
|
|
676
874
|
this.#apply();
|
|
677
875
|
}
|
|
678
876
|
/** Build a `data-autoid` for an inner control under this builder's `autoId` prefix. */
|
|
679
877
|
autoIdFor(suffix) {
|
|
680
|
-
return `${this.
|
|
878
|
+
return `${this.resolvedAutoId() ?? 'qb'}-${suffix}`;
|
|
681
879
|
}
|
|
682
880
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdQueryBuilder, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
683
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: SdQueryBuilder, isStandalone: true, selector: "sd-query-builder", inputs: { fields: { classPropertyName: "fields", publicName: "fields", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, autoId: { classPropertyName: "autoId", publicName: "autoId", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, filters: { classPropertyName: "filters", publicName: "filters", isSignal: true, isRequired: false, transformFunction: null }, rootLogic: { classPropertyName: "rootLogic", publicName: "rootLogic", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", filters: "filtersChange", rootLogic: "rootLogicChange" }, ngImport: i0, template: "@if (isView()) {\n <div class=\"qb-view\" [attr.data-autoid]=\"autoIdFor('view')\" aria-readonly=\"true\">\n @if (tokens().length) {\n @for (t of tokens(); track $index) {\n <span class=\"qb-tok qb-tok-{{ t.kind }}\">{{ t.text }}</span>\n }\n } @else {\n <span class=\"qb-view-empty\">\u2014</span>\n }\n </div>\n} @else {\n <div class=\"qb-container\" [class.is-disabled]=\"disabled()\" (click)=\"closeAllDropdowns()\">\n <ng-container [ngTemplateOutlet]=\"groupTpl\" [ngTemplateOutletContext]=\"{ $implicit: tree(), isRoot: true }\"></ng-container>\n </div>\n}\n\n<!-- Recursive group node -->\n<ng-template #groupTpl let-group let-isRoot=\"isRoot\" let-parent=\"parent\" let-index=\"index\">\n <div class=\"qb-group\" [class.is-root]=\"isRoot\">\n <div class=\"qb-header\" [class.is-open]=\"group.open\">\n <div class=\"qb-logic-switch\">\n <button type=\"button\" [class.active]=\"group.logic === 'AND'\" [disabled]=\"disabled()\" (click)=\"setGroupLogic(group, 'AND')\">AND</button>\n <button type=\"button\" [class.active]=\"group.logic === 'OR'\" [disabled]=\"disabled()\" (click)=\"setGroupLogic(group, 'OR')\">OR</button>\n </div>\n\n <div class=\"qb-actions\">\n <button type=\"button\" class=\"qb-btn qb-btn-add\" title=\"Th\u00EAm\" aria-label=\"Th\u00EAm \u0111i\u1EC1u ki\u1EC7n ho\u1EB7c nh\u00F3m\" [disabled]=\"disabled()\" (click)=\"toggleDropdown(group, $event)\">+</button>\n @if (group.open) {\n <div class=\"qb-dropdown\" (click)=\"$event.stopPropagation()\">\n <button type=\"button\" (click)=\"addRule(group)\">+ \u0110i\u1EC1u ki\u1EC7n</button>\n <button type=\"button\" (click)=\"addGroup(group)\">+ Nh\u00F3m</button>\n </div>\n }\n @if (!isRoot) {\n <button type=\"button\" class=\"qb-btn qb-btn-remove\" title=\"Xo\u00E1 nh\u00F3m\" aria-label=\"Xo\u00E1 nh\u00F3m\" [disabled]=\"disabled()\" (click)=\"removeNode(parent, index)\">\u2715</button>\n }\n </div>\n </div>\n\n <div class=\"qb-body\">\n @for (node of group.children; track node.id; let i = $index) {\n <div class=\"qb-treenode\" [class.is-group]=\"isGroup(node)\" [class.is-rule]=\"!isGroup(node)\">\n @if (isGroup(node)) {\n <ng-container\n [ngTemplateOutlet]=\"groupTpl\"\n [ngTemplateOutletContext]=\"{ $implicit: node, isRoot: false, parent: group, index: i }\"\n ></ng-container>\n } @else {\n <ng-container [ngTemplateOutlet]=\"ruleTpl\" [ngTemplateOutletContext]=\"{ $implicit: node, parent: group, index: i }\"></ng-container>\n }\n </div>\n } @empty {\n <div class=\"qb-empty\">Ch\u01B0a c\u00F3 \u0111i\u1EC1u ki\u1EC7n</div>\n }\n </div>\n </div>\n</ng-template>\n\n<!-- Single rule node: field \u2192 operator \u2192 value -->\n<ng-template #ruleTpl let-rule let-parent=\"parent\" let-index=\"index\">\n <div class=\"qb-rule\">\n <sd-select\n class=\"qb-field\"\n [items]=\"fields()\"\n valueField=\"key\"\n displayField=\"label\"\n placeholder=\"Ch\u1ECDn tr\u01B0\u1EDDng\"\n size=\"sm\"\n [model]=\"rule.field\"\n (modelChange)=\"setField(rule, $event)\"\n [disabled]=\"disabled()\"\n [clearable]=\"false\"\n hideInlineError\n [autoId]=\"autoIdFor('field-' + index)\"\n ></sd-select>\n\n @if (rule.field) {\n <div class=\"qb-divider\"></div>\n\n @if (showOperatorSelector(rule)) {\n <sd-operator\n class=\"qb-op\"\n [operators]=\"allowedOperators(rule)\"\n [model]=\"rule.operator\"\n (modelChange)=\"setOperator(rule, $event)\"\n [disabled]=\"disabled()\"\n [autoId]=\"autoIdFor('op-' + index)\"\n ></sd-operator>\n }\n\n @if (rule.operator && !isNoData(rule.operator)) {\n <div class=\"qb-divider\"></div>\n\n @if (rule.operator === 'BETWEEN') {\n <div class=\"qb-between\">\n @switch (fieldOf(rule)?.type) {\n @case ('number') {\n <sd-input class=\"qb-val\" type=\"number\" placeholder=\"T\u1EEB\" size=\"sm\" [model]=\"rule.value?.from\" (modelChange)=\"setBetweenFrom(rule, $event)\" [disabled]=\"disabled()\" hideInlineError [autoId]=\"autoIdFor('from-' + index)\"></sd-input>\n <span class=\"qb-dash\">\u2014</span>\n <sd-input class=\"qb-val\" type=\"number\" placeholder=\"\u0110\u1EBFn\" size=\"sm\" [model]=\"rule.value?.to\" (modelChange)=\"setBetweenTo(rule, $event)\" [disabled]=\"disabled()\" hideInlineError [autoId]=\"autoIdFor('to-' + index)\"></sd-input>\n }\n @case ('date') {\n <sd-date class=\"qb-val\" size=\"sm\" [model]=\"rule.value?.from\" (modelChange)=\"setBetweenFrom(rule, $event)\" [disabled]=\"disabled()\" hideInlineError [autoId]=\"autoIdFor('from-' + index)\"></sd-date>\n <span class=\"qb-dash\">\u2014</span>\n <sd-date class=\"qb-val\" size=\"sm\" [model]=\"rule.value?.to\" (modelChange)=\"setBetweenTo(rule, $event)\" [disabled]=\"disabled()\" hideInlineError [autoId]=\"autoIdFor('to-' + index)\"></sd-date>\n }\n @case ('datetime') {\n <sd-datetime class=\"qb-val\" size=\"sm\" [model]=\"rule.value?.from\" (modelChange)=\"setBetweenFrom(rule, $event)\" [disabled]=\"disabled()\" hideInlineError [autoId]=\"autoIdFor('from-' + index)\"></sd-datetime>\n <span class=\"qb-dash\">\u2014</span>\n <sd-datetime class=\"qb-val\" size=\"sm\" [model]=\"rule.value?.to\" (modelChange)=\"setBetweenTo(rule, $event)\" [disabled]=\"disabled()\" hideInlineError [autoId]=\"autoIdFor('to-' + index)\"></sd-datetime>\n }\n }\n </div>\n } @else {\n @switch (fieldOf(rule)?.type) {\n @case ('string') {\n <sd-input class=\"qb-val\" placeholder=\"Gi\u00E1 tr\u1ECB\" size=\"sm\" [model]=\"rule.value\" (modelChange)=\"setScalar(rule, $event)\" [disabled]=\"disabled()\" hideInlineError [autoId]=\"autoIdFor('val-' + index)\"></sd-input>\n }\n @case ('number') {\n <sd-input class=\"qb-val\" type=\"number\" placeholder=\"Gi\u00E1 tr\u1ECB\" size=\"sm\" [model]=\"rule.value\" (modelChange)=\"setScalar(rule, $event)\" [disabled]=\"disabled()\" hideInlineError [autoId]=\"autoIdFor('val-' + index)\"></sd-input>\n }\n @case ('boolean') {\n <sd-select class=\"qb-val\" [items]=\"booleanItems(fieldOf(rule)!)\" valueField=\"value\" displayField=\"display\" placeholder=\"Gi\u00E1 tr\u1ECB\" size=\"sm\" [model]=\"rule.value\" (modelChange)=\"setScalar(rule, $event)\" [disabled]=\"disabled()\" [clearable]=\"false\" hideInlineError [autoId]=\"autoIdFor('val-' + index)\"></sd-select>\n }\n @case ('date') {\n @let _dateMode = dateMode(rule);\n <sd-select class=\"qb-date-mode\" [items]=\"dateModes\" valueField=\"value\" displayField=\"display\" size=\"sm\" [model]=\"_dateMode\" (modelChange)=\"setDateMode(rule, $event)\" [disabled]=\"disabled()\" [clearable]=\"false\" hideInlineError [autoId]=\"autoIdFor('datemode-' + index)\"></sd-select>\n @switch (_dateMode) {\n @case ('absolute') {\n <sd-date class=\"qb-val\" size=\"sm\" [model]=\"rule.value\" (modelChange)=\"setScalar(rule, $event)\" [disabled]=\"disabled()\" hideInlineError [autoId]=\"autoIdFor('val-' + index)\"></sd-date>\n }\n @case ('relative') {\n <sd-input class=\"qb-rel-amount\" type=\"number\" size=\"sm\" [model]=\"relativeAmount(rule)\" (modelChange)=\"setRelativeAmount(rule, $event)\" [disabled]=\"disabled()\" hideInlineError [autoId]=\"autoIdFor('rel-amount-' + index)\"></sd-input>\n <sd-select class=\"qb-rel-unit\" [items]=\"relativeUnitOptions\" valueField=\"value\" displayField=\"display\" size=\"sm\" [model]=\"relativeUnitDirValue(rule)\" (modelChange)=\"setRelativeUnitDir(rule, $event)\" [disabled]=\"disabled()\" [clearable]=\"false\" hideInlineError [autoId]=\"autoIdFor('rel-unit-' + index)\"></sd-select>\n }\n }\n }\n @case ('datetime') {\n @let _dtMode = dateMode(rule);\n <sd-select class=\"qb-date-mode\" [items]=\"dateModes\" valueField=\"value\" displayField=\"display\" size=\"sm\" [model]=\"_dtMode\" (modelChange)=\"setDateMode(rule, $event)\" [disabled]=\"disabled()\" [clearable]=\"false\" hideInlineError [autoId]=\"autoIdFor('datemode-' + index)\"></sd-select>\n @switch (_dtMode) {\n @case ('absolute') {\n <sd-datetime class=\"qb-val\" size=\"sm\" [model]=\"rule.value\" (modelChange)=\"setScalar(rule, $event)\" [disabled]=\"disabled()\" hideInlineError [autoId]=\"autoIdFor('val-' + index)\"></sd-datetime>\n }\n @case ('relative') {\n <sd-input class=\"qb-rel-amount\" type=\"number\" size=\"sm\" [model]=\"relativeAmount(rule)\" (modelChange)=\"setRelativeAmount(rule, $event)\" [disabled]=\"disabled()\" hideInlineError [autoId]=\"autoIdFor('rel-amount-' + index)\"></sd-input>\n <sd-select class=\"qb-rel-unit\" [items]=\"relativeUnitOptions\" valueField=\"value\" displayField=\"display\" size=\"sm\" [model]=\"relativeUnitDirValue(rule)\" (modelChange)=\"setRelativeUnitDir(rule, $event)\" [disabled]=\"disabled()\" [clearable]=\"false\" hideInlineError [autoId]=\"autoIdFor('rel-unit-' + index)\"></sd-select>\n }\n }\n }\n @case ('values') {\n <sd-select class=\"qb-val\" [items]=\"fieldOf(rule)!.values || []\" valueField=\"value\" displayField=\"display\" [multiple]=\"isMulti(rule.operator)\" placeholder=\"Gi\u00E1 tr\u1ECB\" size=\"sm\" [model]=\"rule.value\" (modelChange)=\"setScalar(rule, $event)\" [disabled]=\"disabled()\" [clearable]=\"false\" hideInlineError [autoId]=\"autoIdFor('val-' + index)\"></sd-select>\n }\n }\n }\n }\n }\n\n <button type=\"button\" class=\"qb-btn qb-btn-remove-rule\" title=\"Xo\u00E1 \u0111i\u1EC1u ki\u1EC7n\" aria-label=\"Xo\u00E1 \u0111i\u1EC1u ki\u1EC7n\" [disabled]=\"disabled()\" (click)=\"removeNode(parent, index)\">\u2715</button>\n </div>\n</ng-template>\n", styles: [":host{display:block;font-size:14px;color:var(--sd-text, #333)}.qb-view{display:block;width:100%;min-height:38px;padding:8px 12px;border:1px solid #d0d5dd;border-radius:6px;background:var(--sd-disabled-bg, #f6f7f9);color:var(--sd-text, #333);font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:13px;line-height:1.7;cursor:default;-webkit-user-select:text;user-select:text;white-space:pre-wrap;word-break:break-word}.qb-view-empty{color:#98a2b3}.qb-tok{white-space:pre}.qb-tok.qb-tok-field{color:var(--sd-text, #1d2939);font-weight:600}.qb-tok.qb-tok-op{color:var(--sd-primary, #2a66f4);font-weight:600}.qb-tok.qb-tok-value{color:var(--sd-success, #1a7f37)}.qb-tok.qb-tok-logic{color:#98a2b3;font-weight:700;text-transform:uppercase}.qb-tok.qb-tok-paren{color:#667085}.qb-container{padding:16px;background:#fff;border:1px solid #e0e0e0;border-radius:6px}.qb-container.is-disabled{opacity:.7;pointer-events:none}.qb-header{display:flex;align-items:center;gap:8px;margin-bottom:12px;position:relative;z-index:1}.qb-header.is-open{z-index:1000}.qb-logic-switch{display:inline-flex;border:1px solid #e0e0e0;border-radius:4px;overflow:hidden;height:30px}.qb-logic-switch button{border:none;background:transparent;padding:0 14px;font-weight:600;font-size:12px;cursor:pointer;color:#667085;height:100%}.qb-logic-switch button.active{background:var(--sd-primary, #2a66f4);color:#fff}.qb-logic-switch button:hover:not(.active):not([disabled]){background:var(--sd-primary-light, #eaf1ff);color:var(--sd-primary-dark, #1c4ad9)}.qb-logic-switch button[disabled]{cursor:default}.qb-actions{position:relative;display:flex;align-items:center;gap:4px}.qb-btn{background:transparent;border:none;cursor:pointer;font-size:16px;color:#667085;width:28px;height:28px;display:inline-flex;align-items:center;justify-content:center;border-radius:4px}.qb-btn:hover:not([disabled]){background:var(--sd-primary-light, #eaf1ff);color:var(--sd-primary, #2a66f4)}.qb-btn[disabled]{cursor:default;color:#cbd2dc}.qb-btn-remove:hover:not([disabled]),.qb-btn-remove-rule:hover:not([disabled]){background:#fee4e2;color:#d92d20}.qb-btn-remove-rule{margin-left:auto}.qb-dropdown{position:absolute;top:100%;left:0;margin-top:4px;background:#fff;border:1px solid #e0e0e0;box-shadow:0 4px 12px #1018281f;border-radius:6px;min-width:150px;z-index:100;overflow:hidden}.qb-dropdown button{display:block;width:100%;text-align:left;background:none;border:none;padding:9px 14px;cursor:pointer;font-size:13px;color:#344054}.qb-dropdown button:hover{background:var(--sd-primary-light, #eaf1ff);color:var(--sd-primary, #2a66f4)}.qb-body{padding-left:24px}.qb-treenode{position:relative;padding-left:22px;margin-bottom:12px}.qb-treenode:last-child{margin-bottom:0}.qb-treenode:before{content:\"\";position:absolute;left:0;top:-12px;bottom:50%;border-left:1px dashed #d0d5dd}.qb-treenode:after{content:\"\";position:absolute;left:0;top:50%;width:18px;border-top:1px dashed #d0d5dd}.qb-treenode:last-child:before{bottom:auto;height:calc(50% + 12px)}.qb-empty{color:#98a2b3;font-style:italic;font-size:13px;padding:4px 0}.qb-rule{display:flex;align-items:center;gap:8px;background:#fafbfc;border:1px solid #e0e0e0;border-radius:6px;padding:6px 10px}.qb-rule:hover{border-color:#c2c8d0}.qb-field{min-width:150px}.qb-val{min-width:140px}.qb-between{display:flex;align-items:center;gap:6px}.qb-dash{color:#98a2b3}.qb-divider{width:1px;align-self:stretch;background:#e0e0e0}.qb-treenode.is-group>.qb-group{border:1px solid #e0e0e0;border-left:2px solid var(--sd-primary, #2a66f4);border-radius:6px;padding:12px;background:#fff}.qb-date-mode{min-width:120px}.qb-rel-amount{width:72px}.qb-rel-unit{min-width:110px}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: SdOperator, selector: "sd-operator", inputs: ["model", "operators", "disabled", "autoId"], outputs: ["modelChange"] }, { kind: "component", type: SdSelect, selector: "sd-select", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "cacheChecksum", "limit", "hyperlink", "minWidthPanel", "hideInlineError", "required", "disabled", "viewed", "multiple", "clearable", "validator", "inlineError", "appearance", "floatLabel", "items", "model"], outputs: ["modelChange", "sdChange", "sdSelection"] }, { kind: "component", type: SdInput, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdDate, selector: "sd-date", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "required", "disabled", "viewed", "clearable", "inlineError", "hyperlink", "appearance", "floatLabel", "min", "minDate", "max", "maxDate", "model"], outputs: ["modelChange", "sdChange", "sdFocus"] }, { kind: "component", type: SdDatetime, selector: "sd-datetime", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "required", "disabled", "viewed", "showSeconds", "clearable", "inlineError", "hyperlink", "appearance", "floatLabel", "min", "minDate", "max", "maxDate", "model"], outputs: ["modelChange", "sdChange", "sdFocus"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
881
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: SdQueryBuilder, isStandalone: true, selector: "sd-query-builder", inputs: { option: { classPropertyName: "option", publicName: "option", isSignal: true, isRequired: false, transformFunction: null }, fields: { classPropertyName: "fields", publicName: "fields", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, comparisonMode: { classPropertyName: "comparisonMode", publicName: "comparisonMode", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, autoId: { classPropertyName: "autoId", publicName: "autoId", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, filters: { classPropertyName: "filters", publicName: "filters", isSignal: true, isRequired: false, transformFunction: null }, rootLogic: { classPropertyName: "rootLogic", publicName: "rootLogic", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", filters: "filtersChange", rootLogic: "rootLogicChange" }, ngImport: i0, template: "@if (isView()) {\n <div class=\"qb-view\" [attr.data-autoid]=\"autoIdFor('view')\" aria-readonly=\"true\">\n @if (tokens().length) {\n @for (t of tokens(); track $index) {\n <span class=\"qb-tok qb-tok-{{ t.kind }}\">{{ t.text }}</span>\n }\n } @else {\n <span class=\"qb-view-empty\">\u2014</span>\n }\n </div>\n} @else {\n <div class=\"qb-container\" [class.is-disabled]=\"resolvedDisabled()\" (click)=\"closeAllDropdowns()\">\n <ng-container [ngTemplateOutlet]=\"groupTpl\" [ngTemplateOutletContext]=\"{ $implicit: tree(), isRoot: true }\"></ng-container>\n </div>\n}\n\n<!-- Recursive group node -->\n<ng-template #groupTpl let-group let-isRoot=\"isRoot\" let-parent=\"parent\" let-index=\"index\">\n <div class=\"qb-group\" [class.is-root]=\"isRoot\">\n <div class=\"qb-header\" [class.is-open]=\"group.open\">\n <div class=\"qb-logic-switch\">\n <button type=\"button\" [class.active]=\"group.logic === 'AND'\" [disabled]=\"resolvedDisabled()\" (click)=\"setGroupLogic(group, 'AND')\">AND</button>\n <button type=\"button\" [class.active]=\"group.logic === 'OR'\" [disabled]=\"resolvedDisabled()\" (click)=\"setGroupLogic(group, 'OR')\">OR</button>\n </div>\n\n <div class=\"qb-actions\">\n <button type=\"button\" class=\"qb-btn qb-btn-add\" title=\"Th\u00EAm\" aria-label=\"Th\u00EAm \u0111i\u1EC1u ki\u1EC7n ho\u1EB7c nh\u00F3m\" [disabled]=\"resolvedDisabled()\" (click)=\"toggleDropdown(group, $event)\">+</button>\n @if (group.open) {\n <div class=\"qb-dropdown\" (click)=\"$event.stopPropagation()\">\n <button type=\"button\" (click)=\"addRule(group)\">+ \u0110i\u1EC1u ki\u1EC7n</button>\n <button type=\"button\" (click)=\"addGroup(group)\">+ Nh\u00F3m</button>\n </div>\n }\n @if (!isRoot) {\n <button type=\"button\" class=\"qb-btn qb-btn-remove\" title=\"Xo\u00E1 nh\u00F3m\" aria-label=\"Xo\u00E1 nh\u00F3m\" [disabled]=\"resolvedDisabled()\" (click)=\"removeNode(parent, index)\">\u2715</button>\n }\n </div>\n </div>\n\n <div class=\"qb-body\">\n @for (node of group.children; track node.id; let i = $index) {\n <div class=\"qb-treenode\" [class.is-group]=\"isGroup(node)\" [class.is-rule]=\"!isGroup(node)\">\n @if (isGroup(node)) {\n <ng-container\n [ngTemplateOutlet]=\"groupTpl\"\n [ngTemplateOutletContext]=\"{ $implicit: node, isRoot: false, parent: group, index: i }\"\n ></ng-container>\n } @else {\n <ng-container [ngTemplateOutlet]=\"ruleTpl\" [ngTemplateOutletContext]=\"{ $implicit: node, parent: group, index: i }\"></ng-container>\n }\n </div>\n } @empty {\n <div class=\"qb-empty\">Ch\u01B0a c\u00F3 \u0111i\u1EC1u ki\u1EC7n</div>\n }\n </div>\n </div>\n</ng-template>\n\n<!-- Single rule node: field \u2192 operator \u2192 value -->\n<ng-template #ruleTpl let-rule let-parent=\"parent\" let-index=\"index\">\n <div class=\"qb-rule\">\n <sd-select\n class=\"qb-field\"\n [items]=\"resolvedFields()\"\n valueField=\"key\"\n displayField=\"label\"\n placeholder=\"Ch\u1ECDn tr\u01B0\u1EDDng\"\n size=\"sm\"\n [model]=\"rule.field\"\n (modelChange)=\"setField(rule, $event)\"\n [disabled]=\"resolvedDisabled()\"\n [clearable]=\"false\"\n hideInlineError\n [autoId]=\"autoIdFor('field-' + index)\"\n >\n <!-- Option trong dropdown: icon th\u1EC3 hi\u1EC7n type + label. -->\n <ng-template sdItemDef let-item=\"item\">\n <span class=\"qb-opt\">\n <mat-icon class=\"qb-opt-icon\" fontSet=\"material-icons-outlined\">{{ fieldIcon(item) }}</mat-icon>\n <span class=\"qb-opt-label\">{{ item.label }}</span>\n </span>\n </ng-template>\n <!-- Gi\u00E1 tr\u1ECB \u0111\u00E3 ch\u1ECDn \u1EDF trigger: icon m\u1EB7c \u0111\u1ECBnh theo type khi field ch\u01B0a c\u00F3 icon ri\u00EAng. -->\n <ng-template #sdSelected let-item=\"item\">\n <span class=\"qb-opt\">\n <mat-icon class=\"qb-opt-icon\" fontSet=\"material-icons-outlined\">{{ fieldIcon(item) }}</mat-icon>\n <span class=\"qb-opt-label\">{{ item?.label }}</span>\n </span>\n </ng-template>\n </sd-select>\n\n @if (rule.field) {\n <div class=\"qb-divider\"></div>\n\n @if (showOperatorSelector(rule)) {\n <sd-operator\n class=\"qb-op\"\n [operators]=\"allowedOperators(rule)\"\n [model]=\"rule.operator\"\n (modelChange)=\"setOperator(rule, $event)\"\n [disabled]=\"resolvedDisabled()\"\n [autoId]=\"autoIdFor('op-' + index)\"\n ></sd-operator>\n }\n\n @if (rule.operator && !isNoData(rule.operator)) {\n <div class=\"qb-divider\"></div>\n\n @if (showValueSourceSelector(rule)) {\n <sd-select\n class=\"qb-value-source\"\n [items]=\"valueSourceOptions\"\n valueField=\"value\"\n displayField=\"display\"\n size=\"sm\"\n [model]=\"valueSourceOf(rule)\"\n (modelChange)=\"setValueSource(rule, $event)\"\n [disabled]=\"resolvedDisabled()\"\n [clearable]=\"false\"\n hideInlineError\n [autoId]=\"autoIdFor('valuesource-' + index)\"\n >\n <ng-template sdItemDef let-item=\"item\">\n <span class=\"qb-opt\">\n <mat-icon class=\"qb-opt-icon\" fontSet=\"material-icons-outlined\">{{ item.icon }}</mat-icon>\n <span class=\"qb-opt-label\">{{ item.display }}</span>\n </span>\n </ng-template>\n <ng-template #sdSelected let-item=\"item\">\n <span class=\"qb-opt\">\n <mat-icon class=\"qb-opt-icon\" fontSet=\"material-icons-outlined\">{{ item?.icon }}</mat-icon>\n <span class=\"qb-opt-label\">{{ item?.display }}</span>\n </span>\n </ng-template>\n </sd-select>\n <div class=\"qb-divider\"></div>\n }\n\n @if (isFieldSource(rule)) {\n <sd-select\n class=\"qb-val qb-compare-field\"\n [items]=\"compareFields(rule)\"\n valueField=\"key\"\n displayField=\"label\"\n placeholder=\"Ch\u1ECDn tr\u01B0\u1EDDng\"\n size=\"sm\"\n [model]=\"rule.compareField\"\n (modelChange)=\"setCompareField(rule, $event)\"\n [disabled]=\"resolvedDisabled()\"\n [clearable]=\"false\"\n hideInlineError\n [autoId]=\"autoIdFor('compare-field-' + index)\"\n >\n <ng-template sdItemDef let-item=\"item\">\n <span class=\"qb-opt\">\n <mat-icon class=\"qb-opt-icon\" fontSet=\"material-icons-outlined\">{{ fieldIcon(item) }}</mat-icon>\n <span class=\"qb-opt-label\">{{ item.label }}</span>\n </span>\n </ng-template>\n <ng-template #sdSelected let-item=\"item\">\n <span class=\"qb-opt\">\n <mat-icon class=\"qb-opt-icon\" fontSet=\"material-icons-outlined\">{{ fieldIcon(item) }}</mat-icon>\n <span class=\"qb-opt-label\">{{ item?.label }}</span>\n </span>\n </ng-template>\n </sd-select>\n } @else if (rule.operator === 'BETWEEN') {\n <div class=\"qb-between\">\n @switch (fieldOf(rule)?.type) {\n @case ('number') {\n <sd-input-number class=\"qb-val\" placeholder=\"T\u1EEB\" size=\"sm\" [model]=\"rule.value?.from\" (modelChange)=\"setBetweenFrom(rule, $event)\" [min]=\"fieldOf(rule)?.min\" [max]=\"fieldOf(rule)?.max\" [disabled]=\"resolvedDisabled()\" hideInlineError [autoId]=\"autoIdFor('from-' + index)\"></sd-input-number>\n <span class=\"qb-dash\">\u2014</span>\n <sd-input-number class=\"qb-val\" placeholder=\"\u0110\u1EBFn\" size=\"sm\" [model]=\"rule.value?.to\" (modelChange)=\"setBetweenTo(rule, $event)\" [min]=\"fieldOf(rule)?.min\" [max]=\"fieldOf(rule)?.max\" [disabled]=\"resolvedDisabled()\" hideInlineError [autoId]=\"autoIdFor('to-' + index)\"></sd-input-number>\n }\n @case ('date') {\n <sd-date class=\"qb-val qb-date-val\" size=\"sm\" [model]=\"rule.value?.from\" (modelChange)=\"setBetweenFrom(rule, $event)\" [disabled]=\"resolvedDisabled()\" hideInlineError [autoId]=\"autoIdFor('from-' + index)\"></sd-date>\n <span class=\"qb-dash\">\u2014</span>\n <sd-date class=\"qb-val qb-date-val\" size=\"sm\" [model]=\"rule.value?.to\" (modelChange)=\"setBetweenTo(rule, $event)\" [disabled]=\"resolvedDisabled()\" hideInlineError [autoId]=\"autoIdFor('to-' + index)\"></sd-date>\n }\n @case ('datetime') {\n <sd-datetime class=\"qb-val\" size=\"sm\" [model]=\"rule.value?.from\" (modelChange)=\"setBetweenFrom(rule, $event)\" [disabled]=\"resolvedDisabled()\" hideInlineError [autoId]=\"autoIdFor('from-' + index)\"></sd-datetime>\n <span class=\"qb-dash\">\u2014</span>\n <sd-datetime class=\"qb-val\" size=\"sm\" [model]=\"rule.value?.to\" (modelChange)=\"setBetweenTo(rule, $event)\" [disabled]=\"resolvedDisabled()\" hideInlineError [autoId]=\"autoIdFor('to-' + index)\"></sd-datetime>\n }\n }\n </div>\n } @else {\n @switch (fieldOf(rule)?.type) {\n @case ('string') {\n <sd-input class=\"qb-val\" placeholder=\"Gi\u00E1 tr\u1ECB\" size=\"sm\" [model]=\"rule.value\" (modelChange)=\"setScalar(rule, $event)\" [disabled]=\"resolvedDisabled()\" hideInlineError [autoId]=\"autoIdFor('val-' + index)\"></sd-input>\n }\n @case ('number') {\n <sd-input-number class=\"qb-val\" placeholder=\"Gi\u00E1 tr\u1ECB\" size=\"sm\" [model]=\"rule.value\" (modelChange)=\"setScalar(rule, $event)\" [min]=\"fieldOf(rule)?.min\" [max]=\"fieldOf(rule)?.max\" [disabled]=\"resolvedDisabled()\" hideInlineError [autoId]=\"autoIdFor('val-' + index)\"></sd-input-number>\n }\n @case ('boolean') {\n <sd-select class=\"qb-val\" [items]=\"booleanItems(fieldOf(rule)!)\" valueField=\"value\" displayField=\"display\" placeholder=\"Gi\u00E1 tr\u1ECB\" size=\"sm\" [model]=\"rule.value\" (modelChange)=\"setScalar(rule, $event)\" [disabled]=\"resolvedDisabled()\" [clearable]=\"false\" hideInlineError [autoId]=\"autoIdFor('val-' + index)\"></sd-select>\n }\n @case ('date') {\n @let _dateMode = dateMode(rule);\n <sd-select class=\"qb-date-mode\" [items]=\"dateModes\" valueField=\"value\" displayField=\"display\" size=\"sm\" [model]=\"_dateMode\" (modelChange)=\"setDateMode(rule, $event)\" [disabled]=\"resolvedDisabled()\" [clearable]=\"false\" hideInlineError [autoId]=\"autoIdFor('datemode-' + index)\">\n <ng-template sdItemDef let-item=\"item\">\n <span class=\"qb-opt\">\n <mat-icon class=\"qb-opt-icon\" fontSet=\"material-icons-outlined\">{{ item.icon }}</mat-icon>\n <span class=\"qb-opt-label\">{{ item.display }}</span>\n </span>\n </ng-template>\n <ng-template #sdSelected let-item=\"item\">\n <span class=\"qb-opt\">\n <mat-icon class=\"qb-opt-icon\" fontSet=\"material-icons-outlined\">{{ item?.icon }}</mat-icon>\n <span class=\"qb-opt-label\">{{ item?.display }}</span>\n </span>\n </ng-template>\n </sd-select>\n @switch (_dateMode) {\n @case ('absolute') {\n <sd-date class=\"qb-val qb-date-val\" size=\"sm\" [model]=\"rule.value\" (modelChange)=\"setScalar(rule, $event)\" [disabled]=\"resolvedDisabled()\" hideInlineError [autoId]=\"autoIdFor('val-' + index)\"></sd-date>\n }\n @case ('relative') {\n <sd-input-number class=\"qb-rel-amount\" type=\"positive\" [precision]=\"0\" size=\"sm\" [model]=\"relativeAmount(rule)\" (modelChange)=\"setRelativeAmount(rule, $event)\" [disabled]=\"resolvedDisabled()\" hideInlineError [autoId]=\"autoIdFor('rel-amount-' + index)\"></sd-input-number>\n <sd-select class=\"qb-rel-unit\" [items]=\"relativeUnitOptions\" valueField=\"value\" displayField=\"display\" size=\"sm\" [model]=\"relativeUnitDirValue(rule)\" (modelChange)=\"setRelativeUnitDir(rule, $event)\" [disabled]=\"resolvedDisabled()\" [clearable]=\"false\" hideInlineError [autoId]=\"autoIdFor('rel-unit-' + index)\"></sd-select>\n }\n }\n }\n @case ('datetime') {\n @let _dtMode = dateMode(rule);\n <sd-select class=\"qb-date-mode\" [items]=\"dateModes\" valueField=\"value\" displayField=\"display\" size=\"sm\" [model]=\"_dtMode\" (modelChange)=\"setDateMode(rule, $event)\" [disabled]=\"resolvedDisabled()\" [clearable]=\"false\" hideInlineError [autoId]=\"autoIdFor('datemode-' + index)\">\n <ng-template sdItemDef let-item=\"item\">\n <span class=\"qb-opt\">\n <mat-icon class=\"qb-opt-icon\" fontSet=\"material-icons-outlined\">{{ item.icon }}</mat-icon>\n <span class=\"qb-opt-label\">{{ item.display }}</span>\n </span>\n </ng-template>\n <ng-template #sdSelected let-item=\"item\">\n <span class=\"qb-opt\">\n <mat-icon class=\"qb-opt-icon\" fontSet=\"material-icons-outlined\">{{ item?.icon }}</mat-icon>\n <span class=\"qb-opt-label\">{{ item?.display }}</span>\n </span>\n </ng-template>\n </sd-select>\n @switch (_dtMode) {\n @case ('absolute') {\n <sd-datetime class=\"qb-val\" size=\"sm\" [model]=\"rule.value\" (modelChange)=\"setScalar(rule, $event)\" [disabled]=\"resolvedDisabled()\" hideInlineError [autoId]=\"autoIdFor('val-' + index)\"></sd-datetime>\n }\n @case ('relative') {\n <sd-input-number class=\"qb-rel-amount\" type=\"positive\" [precision]=\"0\" size=\"sm\" [model]=\"relativeAmount(rule)\" (modelChange)=\"setRelativeAmount(rule, $event)\" [disabled]=\"resolvedDisabled()\" hideInlineError [autoId]=\"autoIdFor('rel-amount-' + index)\"></sd-input-number>\n <sd-select class=\"qb-rel-unit\" [items]=\"relativeUnitOptions\" valueField=\"value\" displayField=\"display\" size=\"sm\" [model]=\"relativeUnitDirValue(rule)\" (modelChange)=\"setRelativeUnitDir(rule, $event)\" [disabled]=\"resolvedDisabled()\" [clearable]=\"false\" hideInlineError [autoId]=\"autoIdFor('rel-unit-' + index)\"></sd-select>\n }\n }\n }\n @case ('values') {\n <sd-select class=\"qb-val\" [items]=\"fieldOf(rule)!.values || []\" valueField=\"value\" displayField=\"display\" [multiple]=\"isMulti(rule.operator)\" placeholder=\"Gi\u00E1 tr\u1ECB\" size=\"sm\" [model]=\"rule.value\" (modelChange)=\"setScalar(rule, $event)\" [disabled]=\"resolvedDisabled()\" [clearable]=\"false\" hideInlineError [autoId]=\"autoIdFor('val-' + index)\"></sd-select>\n }\n }\n }\n }\n }\n\n <button type=\"button\" class=\"qb-btn qb-btn-remove-rule\" title=\"Xo\u00E1 \u0111i\u1EC1u ki\u1EC7n\" aria-label=\"Xo\u00E1 \u0111i\u1EC1u ki\u1EC7n\" [disabled]=\"resolvedDisabled()\" (click)=\"removeNode(parent, index)\">\u2715</button>\n </div>\n</ng-template>\n", styles: [":host{display:block;font-size:14px;color:var(--sd-text, #333)}.qb-view{display:block;width:100%;min-height:38px;padding:8px 12px;border:1px solid #d0d5dd;border-radius:6px;background:var(--sd-disabled-bg, #f6f7f9);color:var(--sd-text, #333);font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:13px;line-height:1.7;cursor:default;-webkit-user-select:text;user-select:text;white-space:pre-wrap;word-break:break-word}.qb-view-empty{color:#98a2b3}.qb-tok{white-space:pre}.qb-tok.qb-tok-field{color:var(--sd-text, #1d2939);font-weight:600}.qb-tok.qb-tok-op{color:var(--sd-primary, #2a66f4);font-weight:600}.qb-tok.qb-tok-value{color:var(--sd-success, #1a7f37)}.qb-tok.qb-tok-logic{color:#98a2b3;font-weight:700;text-transform:uppercase}.qb-tok.qb-tok-paren{color:#667085}.qb-container{padding:16px;background:#fff;border:1px solid #e0e0e0;border-radius:6px}.qb-container.is-disabled{opacity:.7;pointer-events:none}.qb-header{display:flex;align-items:center;gap:8px;margin-bottom:12px;position:relative;z-index:1}.qb-header.is-open{z-index:1000}.qb-logic-switch{display:inline-flex;border:1px solid #e0e0e0;border-radius:4px;overflow:hidden;height:30px}.qb-logic-switch button{border:none;background:transparent;padding:0 14px;font-weight:600;font-size:12px;cursor:pointer;color:#667085;height:100%}.qb-logic-switch button.active{background:var(--sd-primary, #2a66f4);color:#fff}.qb-logic-switch button:hover:not(.active):not([disabled]){background:var(--sd-primary-light, #eaf1ff);color:var(--sd-primary-dark, #1c4ad9)}.qb-logic-switch button[disabled]{cursor:default}.qb-actions{position:relative;display:flex;align-items:center;gap:4px}.qb-btn{background:transparent;border:none;cursor:pointer;font-size:16px;color:#667085;width:28px;height:28px;display:inline-flex;align-items:center;justify-content:center;border-radius:4px}.qb-btn:hover:not([disabled]){background:var(--sd-primary-light, #eaf1ff);color:var(--sd-primary, #2a66f4)}.qb-btn[disabled]{cursor:default;color:#cbd2dc}.qb-btn-remove:hover:not([disabled]),.qb-btn-remove-rule:hover:not([disabled]){background:#fee4e2;color:#d92d20}.qb-btn-remove-rule{margin-left:auto}.qb-dropdown{position:absolute;top:100%;left:0;margin-top:4px;background:#fff;border:1px solid #e0e0e0;box-shadow:0 4px 12px #1018281f;border-radius:6px;min-width:150px;z-index:100;overflow:hidden}.qb-dropdown button{display:block;width:100%;text-align:left;background:none;border:none;padding:9px 14px;cursor:pointer;font-size:13px;color:#344054}.qb-dropdown button:hover{background:var(--sd-primary-light, #eaf1ff);color:var(--sd-primary, #2a66f4)}.qb-body{padding-left:24px}.qb-treenode{position:relative;padding-left:22px;margin-bottom:12px}.qb-treenode:last-child{margin-bottom:0}.qb-treenode:before{content:\"\";position:absolute;left:0;top:-12px;bottom:0;border-left:1px dashed #d0d5dd}.qb-treenode:after{content:\"\";position:absolute;left:0;top:50%;width:18px;border-top:1px dashed #d0d5dd}.qb-treenode:last-child:before{bottom:auto;height:calc(50% + 12px)}.qb-empty{color:#98a2b3;font-style:italic;font-size:13px;padding:4px 0}.qb-rule{display:flex;align-items:center;gap:8px;background:#fafbfc;border:1px solid #e0e0e0;border-radius:6px;padding:6px 10px}.qb-rule:hover{border-color:#c2c8d0}.qb-field{min-width:170px}.qb-val{min-width:140px}.qb-value-source{min-width:132px}.qb-compare-field{min-width:170px}.qb-date-val{min-width:0;width:132px;flex:0 0 auto}::ng-deep .qb-opt{display:flex;align-items:center;gap:6px;min-width:0}::ng-deep .qb-opt .qb-opt-icon.mat-icon{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;margin:0;font-size:18px;width:18px;height:18px;line-height:1;color:#667085}::ng-deep .qb-opt .qb-opt-label{min-width:0}.qb-between{display:flex;align-items:center;gap:6px}.qb-dash{color:#98a2b3}.qb-divider{width:1px;align-self:stretch;background:#e0e0e0}.qb-treenode.is-group>.qb-group{border:1px solid #e0e0e0;border-left:2px solid var(--sd-primary, #2a66f4);border-radius:6px;padding:12px;background:#fff}.qb-date-mode{min-width:160px}.qb-rel-amount{width:72px}.qb-rel-unit{min-width:110px}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: SdOperator, selector: "sd-operator", inputs: ["model", "operators", "disabled", "autoId"], outputs: ["modelChange"] }, { kind: "component", type: SdSelect, selector: "sd-select", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "cacheChecksum", "limit", "hyperlink", "minWidthPanel", "hideInlineError", "required", "disabled", "viewed", "multiple", "clearable", "validator", "inlineError", "appearance", "floatLabel", "items", "model"], outputs: ["modelChange", "sdChange", "sdSelection"] }, { kind: "component", type: SdInput, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdInputNumber, selector: "sd-input-number", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "type", "precision", "format", "min", "max", "validator", "inlineError", "hyperlink", "appearance", "floatLabel", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdDate, selector: "sd-date", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "required", "disabled", "viewed", "clearable", "inlineError", "hyperlink", "appearance", "floatLabel", "min", "minDate", "max", "maxDate", "model"], outputs: ["modelChange", "sdChange", "sdFocus"] }, { kind: "component", type: SdDatetime, selector: "sd-datetime", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "required", "disabled", "viewed", "showSeconds", "clearable", "inlineError", "hyperlink", "appearance", "floatLabel", "min", "minDate", "max", "maxDate", "model"], outputs: ["modelChange", "sdChange", "sdFocus"] }, { kind: "directive", type: SdItemDefDefDirective, selector: "[sdItemDef]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
684
882
|
}
|
|
685
883
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdQueryBuilder, decorators: [{
|
|
686
884
|
type: Component,
|
|
687
|
-
args: [{ selector: 'sd-query-builder', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgTemplateOutlet, SdOperator, SdSelect, SdInput, SdDate, SdDatetime], template: "@if (isView()) {\n <div class=\"qb-view\" [attr.data-autoid]=\"autoIdFor('view')\" aria-readonly=\"true\">\n @if (tokens().length) {\n @for (t of tokens(); track $index) {\n <span class=\"qb-tok qb-tok-{{ t.kind }}\">{{ t.text }}</span>\n }\n } @else {\n <span class=\"qb-view-empty\">\u2014</span>\n }\n </div>\n} @else {\n <div class=\"qb-container\" [class.is-disabled]=\"disabled()\" (click)=\"closeAllDropdowns()\">\n <ng-container [ngTemplateOutlet]=\"groupTpl\" [ngTemplateOutletContext]=\"{ $implicit: tree(), isRoot: true }\"></ng-container>\n </div>\n}\n\n<!-- Recursive group node -->\n<ng-template #groupTpl let-group let-isRoot=\"isRoot\" let-parent=\"parent\" let-index=\"index\">\n <div class=\"qb-group\" [class.is-root]=\"isRoot\">\n <div class=\"qb-header\" [class.is-open]=\"group.open\">\n <div class=\"qb-logic-switch\">\n <button type=\"button\" [class.active]=\"group.logic === 'AND'\" [disabled]=\"disabled()\" (click)=\"setGroupLogic(group, 'AND')\">AND</button>\n <button type=\"button\" [class.active]=\"group.logic === 'OR'\" [disabled]=\"disabled()\" (click)=\"setGroupLogic(group, 'OR')\">OR</button>\n </div>\n\n <div class=\"qb-actions\">\n <button type=\"button\" class=\"qb-btn qb-btn-add\" title=\"Th\u00EAm\" aria-label=\"Th\u00EAm \u0111i\u1EC1u ki\u1EC7n ho\u1EB7c nh\u00F3m\" [disabled]=\"disabled()\" (click)=\"toggleDropdown(group, $event)\">+</button>\n @if (group.open) {\n <div class=\"qb-dropdown\" (click)=\"$event.stopPropagation()\">\n <button type=\"button\" (click)=\"addRule(group)\">+ \u0110i\u1EC1u ki\u1EC7n</button>\n <button type=\"button\" (click)=\"addGroup(group)\">+ Nh\u00F3m</button>\n </div>\n }\n @if (!isRoot) {\n <button type=\"button\" class=\"qb-btn qb-btn-remove\" title=\"Xo\u00E1 nh\u00F3m\" aria-label=\"Xo\u00E1 nh\u00F3m\" [disabled]=\"disabled()\" (click)=\"removeNode(parent, index)\">\u2715</button>\n }\n </div>\n </div>\n\n <div class=\"qb-body\">\n @for (node of group.children; track node.id; let i = $index) {\n <div class=\"qb-treenode\" [class.is-group]=\"isGroup(node)\" [class.is-rule]=\"!isGroup(node)\">\n @if (isGroup(node)) {\n <ng-container\n [ngTemplateOutlet]=\"groupTpl\"\n [ngTemplateOutletContext]=\"{ $implicit: node, isRoot: false, parent: group, index: i }\"\n ></ng-container>\n } @else {\n <ng-container [ngTemplateOutlet]=\"ruleTpl\" [ngTemplateOutletContext]=\"{ $implicit: node, parent: group, index: i }\"></ng-container>\n }\n </div>\n } @empty {\n <div class=\"qb-empty\">Ch\u01B0a c\u00F3 \u0111i\u1EC1u ki\u1EC7n</div>\n }\n </div>\n </div>\n</ng-template>\n\n<!-- Single rule node: field \u2192 operator \u2192 value -->\n<ng-template #ruleTpl let-rule let-parent=\"parent\" let-index=\"index\">\n <div class=\"qb-rule\">\n <sd-select\n class=\"qb-field\"\n [items]=\"fields()\"\n valueField=\"key\"\n displayField=\"label\"\n placeholder=\"Ch\u1ECDn tr\u01B0\u1EDDng\"\n size=\"sm\"\n [model]=\"rule.field\"\n (modelChange)=\"setField(rule, $event)\"\n [disabled]=\"disabled()\"\n [clearable]=\"false\"\n hideInlineError\n [autoId]=\"autoIdFor('field-' + index)\"\n ></sd-select>\n\n @if (rule.field) {\n <div class=\"qb-divider\"></div>\n\n @if (showOperatorSelector(rule)) {\n <sd-operator\n class=\"qb-op\"\n [operators]=\"allowedOperators(rule)\"\n [model]=\"rule.operator\"\n (modelChange)=\"setOperator(rule, $event)\"\n [disabled]=\"disabled()\"\n [autoId]=\"autoIdFor('op-' + index)\"\n ></sd-operator>\n }\n\n @if (rule.operator && !isNoData(rule.operator)) {\n <div class=\"qb-divider\"></div>\n\n @if (rule.operator === 'BETWEEN') {\n <div class=\"qb-between\">\n @switch (fieldOf(rule)?.type) {\n @case ('number') {\n <sd-input class=\"qb-val\" type=\"number\" placeholder=\"T\u1EEB\" size=\"sm\" [model]=\"rule.value?.from\" (modelChange)=\"setBetweenFrom(rule, $event)\" [disabled]=\"disabled()\" hideInlineError [autoId]=\"autoIdFor('from-' + index)\"></sd-input>\n <span class=\"qb-dash\">\u2014</span>\n <sd-input class=\"qb-val\" type=\"number\" placeholder=\"\u0110\u1EBFn\" size=\"sm\" [model]=\"rule.value?.to\" (modelChange)=\"setBetweenTo(rule, $event)\" [disabled]=\"disabled()\" hideInlineError [autoId]=\"autoIdFor('to-' + index)\"></sd-input>\n }\n @case ('date') {\n <sd-date class=\"qb-val\" size=\"sm\" [model]=\"rule.value?.from\" (modelChange)=\"setBetweenFrom(rule, $event)\" [disabled]=\"disabled()\" hideInlineError [autoId]=\"autoIdFor('from-' + index)\"></sd-date>\n <span class=\"qb-dash\">\u2014</span>\n <sd-date class=\"qb-val\" size=\"sm\" [model]=\"rule.value?.to\" (modelChange)=\"setBetweenTo(rule, $event)\" [disabled]=\"disabled()\" hideInlineError [autoId]=\"autoIdFor('to-' + index)\"></sd-date>\n }\n @case ('datetime') {\n <sd-datetime class=\"qb-val\" size=\"sm\" [model]=\"rule.value?.from\" (modelChange)=\"setBetweenFrom(rule, $event)\" [disabled]=\"disabled()\" hideInlineError [autoId]=\"autoIdFor('from-' + index)\"></sd-datetime>\n <span class=\"qb-dash\">\u2014</span>\n <sd-datetime class=\"qb-val\" size=\"sm\" [model]=\"rule.value?.to\" (modelChange)=\"setBetweenTo(rule, $event)\" [disabled]=\"disabled()\" hideInlineError [autoId]=\"autoIdFor('to-' + index)\"></sd-datetime>\n }\n }\n </div>\n } @else {\n @switch (fieldOf(rule)?.type) {\n @case ('string') {\n <sd-input class=\"qb-val\" placeholder=\"Gi\u00E1 tr\u1ECB\" size=\"sm\" [model]=\"rule.value\" (modelChange)=\"setScalar(rule, $event)\" [disabled]=\"disabled()\" hideInlineError [autoId]=\"autoIdFor('val-' + index)\"></sd-input>\n }\n @case ('number') {\n <sd-input class=\"qb-val\" type=\"number\" placeholder=\"Gi\u00E1 tr\u1ECB\" size=\"sm\" [model]=\"rule.value\" (modelChange)=\"setScalar(rule, $event)\" [disabled]=\"disabled()\" hideInlineError [autoId]=\"autoIdFor('val-' + index)\"></sd-input>\n }\n @case ('boolean') {\n <sd-select class=\"qb-val\" [items]=\"booleanItems(fieldOf(rule)!)\" valueField=\"value\" displayField=\"display\" placeholder=\"Gi\u00E1 tr\u1ECB\" size=\"sm\" [model]=\"rule.value\" (modelChange)=\"setScalar(rule, $event)\" [disabled]=\"disabled()\" [clearable]=\"false\" hideInlineError [autoId]=\"autoIdFor('val-' + index)\"></sd-select>\n }\n @case ('date') {\n @let _dateMode = dateMode(rule);\n <sd-select class=\"qb-date-mode\" [items]=\"dateModes\" valueField=\"value\" displayField=\"display\" size=\"sm\" [model]=\"_dateMode\" (modelChange)=\"setDateMode(rule, $event)\" [disabled]=\"disabled()\" [clearable]=\"false\" hideInlineError [autoId]=\"autoIdFor('datemode-' + index)\"></sd-select>\n @switch (_dateMode) {\n @case ('absolute') {\n <sd-date class=\"qb-val\" size=\"sm\" [model]=\"rule.value\" (modelChange)=\"setScalar(rule, $event)\" [disabled]=\"disabled()\" hideInlineError [autoId]=\"autoIdFor('val-' + index)\"></sd-date>\n }\n @case ('relative') {\n <sd-input class=\"qb-rel-amount\" type=\"number\" size=\"sm\" [model]=\"relativeAmount(rule)\" (modelChange)=\"setRelativeAmount(rule, $event)\" [disabled]=\"disabled()\" hideInlineError [autoId]=\"autoIdFor('rel-amount-' + index)\"></sd-input>\n <sd-select class=\"qb-rel-unit\" [items]=\"relativeUnitOptions\" valueField=\"value\" displayField=\"display\" size=\"sm\" [model]=\"relativeUnitDirValue(rule)\" (modelChange)=\"setRelativeUnitDir(rule, $event)\" [disabled]=\"disabled()\" [clearable]=\"false\" hideInlineError [autoId]=\"autoIdFor('rel-unit-' + index)\"></sd-select>\n }\n }\n }\n @case ('datetime') {\n @let _dtMode = dateMode(rule);\n <sd-select class=\"qb-date-mode\" [items]=\"dateModes\" valueField=\"value\" displayField=\"display\" size=\"sm\" [model]=\"_dtMode\" (modelChange)=\"setDateMode(rule, $event)\" [disabled]=\"disabled()\" [clearable]=\"false\" hideInlineError [autoId]=\"autoIdFor('datemode-' + index)\"></sd-select>\n @switch (_dtMode) {\n @case ('absolute') {\n <sd-datetime class=\"qb-val\" size=\"sm\" [model]=\"rule.value\" (modelChange)=\"setScalar(rule, $event)\" [disabled]=\"disabled()\" hideInlineError [autoId]=\"autoIdFor('val-' + index)\"></sd-datetime>\n }\n @case ('relative') {\n <sd-input class=\"qb-rel-amount\" type=\"number\" size=\"sm\" [model]=\"relativeAmount(rule)\" (modelChange)=\"setRelativeAmount(rule, $event)\" [disabled]=\"disabled()\" hideInlineError [autoId]=\"autoIdFor('rel-amount-' + index)\"></sd-input>\n <sd-select class=\"qb-rel-unit\" [items]=\"relativeUnitOptions\" valueField=\"value\" displayField=\"display\" size=\"sm\" [model]=\"relativeUnitDirValue(rule)\" (modelChange)=\"setRelativeUnitDir(rule, $event)\" [disabled]=\"disabled()\" [clearable]=\"false\" hideInlineError [autoId]=\"autoIdFor('rel-unit-' + index)\"></sd-select>\n }\n }\n }\n @case ('values') {\n <sd-select class=\"qb-val\" [items]=\"fieldOf(rule)!.values || []\" valueField=\"value\" displayField=\"display\" [multiple]=\"isMulti(rule.operator)\" placeholder=\"Gi\u00E1 tr\u1ECB\" size=\"sm\" [model]=\"rule.value\" (modelChange)=\"setScalar(rule, $event)\" [disabled]=\"disabled()\" [clearable]=\"false\" hideInlineError [autoId]=\"autoIdFor('val-' + index)\"></sd-select>\n }\n }\n }\n }\n }\n\n <button type=\"button\" class=\"qb-btn qb-btn-remove-rule\" title=\"Xo\u00E1 \u0111i\u1EC1u ki\u1EC7n\" aria-label=\"Xo\u00E1 \u0111i\u1EC1u ki\u1EC7n\" [disabled]=\"disabled()\" (click)=\"removeNode(parent, index)\">\u2715</button>\n </div>\n</ng-template>\n", styles: [":host{display:block;font-size:14px;color:var(--sd-text, #333)}.qb-view{display:block;width:100%;min-height:38px;padding:8px 12px;border:1px solid #d0d5dd;border-radius:6px;background:var(--sd-disabled-bg, #f6f7f9);color:var(--sd-text, #333);font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:13px;line-height:1.7;cursor:default;-webkit-user-select:text;user-select:text;white-space:pre-wrap;word-break:break-word}.qb-view-empty{color:#98a2b3}.qb-tok{white-space:pre}.qb-tok.qb-tok-field{color:var(--sd-text, #1d2939);font-weight:600}.qb-tok.qb-tok-op{color:var(--sd-primary, #2a66f4);font-weight:600}.qb-tok.qb-tok-value{color:var(--sd-success, #1a7f37)}.qb-tok.qb-tok-logic{color:#98a2b3;font-weight:700;text-transform:uppercase}.qb-tok.qb-tok-paren{color:#667085}.qb-container{padding:16px;background:#fff;border:1px solid #e0e0e0;border-radius:6px}.qb-container.is-disabled{opacity:.7;pointer-events:none}.qb-header{display:flex;align-items:center;gap:8px;margin-bottom:12px;position:relative;z-index:1}.qb-header.is-open{z-index:1000}.qb-logic-switch{display:inline-flex;border:1px solid #e0e0e0;border-radius:4px;overflow:hidden;height:30px}.qb-logic-switch button{border:none;background:transparent;padding:0 14px;font-weight:600;font-size:12px;cursor:pointer;color:#667085;height:100%}.qb-logic-switch button.active{background:var(--sd-primary, #2a66f4);color:#fff}.qb-logic-switch button:hover:not(.active):not([disabled]){background:var(--sd-primary-light, #eaf1ff);color:var(--sd-primary-dark, #1c4ad9)}.qb-logic-switch button[disabled]{cursor:default}.qb-actions{position:relative;display:flex;align-items:center;gap:4px}.qb-btn{background:transparent;border:none;cursor:pointer;font-size:16px;color:#667085;width:28px;height:28px;display:inline-flex;align-items:center;justify-content:center;border-radius:4px}.qb-btn:hover:not([disabled]){background:var(--sd-primary-light, #eaf1ff);color:var(--sd-primary, #2a66f4)}.qb-btn[disabled]{cursor:default;color:#cbd2dc}.qb-btn-remove:hover:not([disabled]),.qb-btn-remove-rule:hover:not([disabled]){background:#fee4e2;color:#d92d20}.qb-btn-remove-rule{margin-left:auto}.qb-dropdown{position:absolute;top:100%;left:0;margin-top:4px;background:#fff;border:1px solid #e0e0e0;box-shadow:0 4px 12px #1018281f;border-radius:6px;min-width:150px;z-index:100;overflow:hidden}.qb-dropdown button{display:block;width:100%;text-align:left;background:none;border:none;padding:9px 14px;cursor:pointer;font-size:13px;color:#344054}.qb-dropdown button:hover{background:var(--sd-primary-light, #eaf1ff);color:var(--sd-primary, #2a66f4)}.qb-body{padding-left:24px}.qb-treenode{position:relative;padding-left:22px;margin-bottom:12px}.qb-treenode:last-child{margin-bottom:0}.qb-treenode:before{content:\"\";position:absolute;left:0;top:-12px;bottom:50%;border-left:1px dashed #d0d5dd}.qb-treenode:after{content:\"\";position:absolute;left:0;top:50%;width:18px;border-top:1px dashed #d0d5dd}.qb-treenode:last-child:before{bottom:auto;height:calc(50% + 12px)}.qb-empty{color:#98a2b3;font-style:italic;font-size:13px;padding:4px 0}.qb-rule{display:flex;align-items:center;gap:8px;background:#fafbfc;border:1px solid #e0e0e0;border-radius:6px;padding:6px 10px}.qb-rule:hover{border-color:#c2c8d0}.qb-field{min-width:150px}.qb-val{min-width:140px}.qb-between{display:flex;align-items:center;gap:6px}.qb-dash{color:#98a2b3}.qb-divider{width:1px;align-self:stretch;background:#e0e0e0}.qb-treenode.is-group>.qb-group{border:1px solid #e0e0e0;border-left:2px solid var(--sd-primary, #2a66f4);border-radius:6px;padding:12px;background:#fff}.qb-date-mode{min-width:120px}.qb-rel-amount{width:72px}.qb-rel-unit{min-width:110px}\n"] }]
|
|
885
|
+
args: [{ selector: 'sd-query-builder', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgTemplateOutlet, MatIconModule, SdOperator, SdSelect, SdInput, SdInputNumber, SdDate, SdDatetime, SdItemDefDefDirective], template: "@if (isView()) {\n <div class=\"qb-view\" [attr.data-autoid]=\"autoIdFor('view')\" aria-readonly=\"true\">\n @if (tokens().length) {\n @for (t of tokens(); track $index) {\n <span class=\"qb-tok qb-tok-{{ t.kind }}\">{{ t.text }}</span>\n }\n } @else {\n <span class=\"qb-view-empty\">\u2014</span>\n }\n </div>\n} @else {\n <div class=\"qb-container\" [class.is-disabled]=\"resolvedDisabled()\" (click)=\"closeAllDropdowns()\">\n <ng-container [ngTemplateOutlet]=\"groupTpl\" [ngTemplateOutletContext]=\"{ $implicit: tree(), isRoot: true }\"></ng-container>\n </div>\n}\n\n<!-- Recursive group node -->\n<ng-template #groupTpl let-group let-isRoot=\"isRoot\" let-parent=\"parent\" let-index=\"index\">\n <div class=\"qb-group\" [class.is-root]=\"isRoot\">\n <div class=\"qb-header\" [class.is-open]=\"group.open\">\n <div class=\"qb-logic-switch\">\n <button type=\"button\" [class.active]=\"group.logic === 'AND'\" [disabled]=\"resolvedDisabled()\" (click)=\"setGroupLogic(group, 'AND')\">AND</button>\n <button type=\"button\" [class.active]=\"group.logic === 'OR'\" [disabled]=\"resolvedDisabled()\" (click)=\"setGroupLogic(group, 'OR')\">OR</button>\n </div>\n\n <div class=\"qb-actions\">\n <button type=\"button\" class=\"qb-btn qb-btn-add\" title=\"Th\u00EAm\" aria-label=\"Th\u00EAm \u0111i\u1EC1u ki\u1EC7n ho\u1EB7c nh\u00F3m\" [disabled]=\"resolvedDisabled()\" (click)=\"toggleDropdown(group, $event)\">+</button>\n @if (group.open) {\n <div class=\"qb-dropdown\" (click)=\"$event.stopPropagation()\">\n <button type=\"button\" (click)=\"addRule(group)\">+ \u0110i\u1EC1u ki\u1EC7n</button>\n <button type=\"button\" (click)=\"addGroup(group)\">+ Nh\u00F3m</button>\n </div>\n }\n @if (!isRoot) {\n <button type=\"button\" class=\"qb-btn qb-btn-remove\" title=\"Xo\u00E1 nh\u00F3m\" aria-label=\"Xo\u00E1 nh\u00F3m\" [disabled]=\"resolvedDisabled()\" (click)=\"removeNode(parent, index)\">\u2715</button>\n }\n </div>\n </div>\n\n <div class=\"qb-body\">\n @for (node of group.children; track node.id; let i = $index) {\n <div class=\"qb-treenode\" [class.is-group]=\"isGroup(node)\" [class.is-rule]=\"!isGroup(node)\">\n @if (isGroup(node)) {\n <ng-container\n [ngTemplateOutlet]=\"groupTpl\"\n [ngTemplateOutletContext]=\"{ $implicit: node, isRoot: false, parent: group, index: i }\"\n ></ng-container>\n } @else {\n <ng-container [ngTemplateOutlet]=\"ruleTpl\" [ngTemplateOutletContext]=\"{ $implicit: node, parent: group, index: i }\"></ng-container>\n }\n </div>\n } @empty {\n <div class=\"qb-empty\">Ch\u01B0a c\u00F3 \u0111i\u1EC1u ki\u1EC7n</div>\n }\n </div>\n </div>\n</ng-template>\n\n<!-- Single rule node: field \u2192 operator \u2192 value -->\n<ng-template #ruleTpl let-rule let-parent=\"parent\" let-index=\"index\">\n <div class=\"qb-rule\">\n <sd-select\n class=\"qb-field\"\n [items]=\"resolvedFields()\"\n valueField=\"key\"\n displayField=\"label\"\n placeholder=\"Ch\u1ECDn tr\u01B0\u1EDDng\"\n size=\"sm\"\n [model]=\"rule.field\"\n (modelChange)=\"setField(rule, $event)\"\n [disabled]=\"resolvedDisabled()\"\n [clearable]=\"false\"\n hideInlineError\n [autoId]=\"autoIdFor('field-' + index)\"\n >\n <!-- Option trong dropdown: icon th\u1EC3 hi\u1EC7n type + label. -->\n <ng-template sdItemDef let-item=\"item\">\n <span class=\"qb-opt\">\n <mat-icon class=\"qb-opt-icon\" fontSet=\"material-icons-outlined\">{{ fieldIcon(item) }}</mat-icon>\n <span class=\"qb-opt-label\">{{ item.label }}</span>\n </span>\n </ng-template>\n <!-- Gi\u00E1 tr\u1ECB \u0111\u00E3 ch\u1ECDn \u1EDF trigger: icon m\u1EB7c \u0111\u1ECBnh theo type khi field ch\u01B0a c\u00F3 icon ri\u00EAng. -->\n <ng-template #sdSelected let-item=\"item\">\n <span class=\"qb-opt\">\n <mat-icon class=\"qb-opt-icon\" fontSet=\"material-icons-outlined\">{{ fieldIcon(item) }}</mat-icon>\n <span class=\"qb-opt-label\">{{ item?.label }}</span>\n </span>\n </ng-template>\n </sd-select>\n\n @if (rule.field) {\n <div class=\"qb-divider\"></div>\n\n @if (showOperatorSelector(rule)) {\n <sd-operator\n class=\"qb-op\"\n [operators]=\"allowedOperators(rule)\"\n [model]=\"rule.operator\"\n (modelChange)=\"setOperator(rule, $event)\"\n [disabled]=\"resolvedDisabled()\"\n [autoId]=\"autoIdFor('op-' + index)\"\n ></sd-operator>\n }\n\n @if (rule.operator && !isNoData(rule.operator)) {\n <div class=\"qb-divider\"></div>\n\n @if (showValueSourceSelector(rule)) {\n <sd-select\n class=\"qb-value-source\"\n [items]=\"valueSourceOptions\"\n valueField=\"value\"\n displayField=\"display\"\n size=\"sm\"\n [model]=\"valueSourceOf(rule)\"\n (modelChange)=\"setValueSource(rule, $event)\"\n [disabled]=\"resolvedDisabled()\"\n [clearable]=\"false\"\n hideInlineError\n [autoId]=\"autoIdFor('valuesource-' + index)\"\n >\n <ng-template sdItemDef let-item=\"item\">\n <span class=\"qb-opt\">\n <mat-icon class=\"qb-opt-icon\" fontSet=\"material-icons-outlined\">{{ item.icon }}</mat-icon>\n <span class=\"qb-opt-label\">{{ item.display }}</span>\n </span>\n </ng-template>\n <ng-template #sdSelected let-item=\"item\">\n <span class=\"qb-opt\">\n <mat-icon class=\"qb-opt-icon\" fontSet=\"material-icons-outlined\">{{ item?.icon }}</mat-icon>\n <span class=\"qb-opt-label\">{{ item?.display }}</span>\n </span>\n </ng-template>\n </sd-select>\n <div class=\"qb-divider\"></div>\n }\n\n @if (isFieldSource(rule)) {\n <sd-select\n class=\"qb-val qb-compare-field\"\n [items]=\"compareFields(rule)\"\n valueField=\"key\"\n displayField=\"label\"\n placeholder=\"Ch\u1ECDn tr\u01B0\u1EDDng\"\n size=\"sm\"\n [model]=\"rule.compareField\"\n (modelChange)=\"setCompareField(rule, $event)\"\n [disabled]=\"resolvedDisabled()\"\n [clearable]=\"false\"\n hideInlineError\n [autoId]=\"autoIdFor('compare-field-' + index)\"\n >\n <ng-template sdItemDef let-item=\"item\">\n <span class=\"qb-opt\">\n <mat-icon class=\"qb-opt-icon\" fontSet=\"material-icons-outlined\">{{ fieldIcon(item) }}</mat-icon>\n <span class=\"qb-opt-label\">{{ item.label }}</span>\n </span>\n </ng-template>\n <ng-template #sdSelected let-item=\"item\">\n <span class=\"qb-opt\">\n <mat-icon class=\"qb-opt-icon\" fontSet=\"material-icons-outlined\">{{ fieldIcon(item) }}</mat-icon>\n <span class=\"qb-opt-label\">{{ item?.label }}</span>\n </span>\n </ng-template>\n </sd-select>\n } @else if (rule.operator === 'BETWEEN') {\n <div class=\"qb-between\">\n @switch (fieldOf(rule)?.type) {\n @case ('number') {\n <sd-input-number class=\"qb-val\" placeholder=\"T\u1EEB\" size=\"sm\" [model]=\"rule.value?.from\" (modelChange)=\"setBetweenFrom(rule, $event)\" [min]=\"fieldOf(rule)?.min\" [max]=\"fieldOf(rule)?.max\" [disabled]=\"resolvedDisabled()\" hideInlineError [autoId]=\"autoIdFor('from-' + index)\"></sd-input-number>\n <span class=\"qb-dash\">\u2014</span>\n <sd-input-number class=\"qb-val\" placeholder=\"\u0110\u1EBFn\" size=\"sm\" [model]=\"rule.value?.to\" (modelChange)=\"setBetweenTo(rule, $event)\" [min]=\"fieldOf(rule)?.min\" [max]=\"fieldOf(rule)?.max\" [disabled]=\"resolvedDisabled()\" hideInlineError [autoId]=\"autoIdFor('to-' + index)\"></sd-input-number>\n }\n @case ('date') {\n <sd-date class=\"qb-val qb-date-val\" size=\"sm\" [model]=\"rule.value?.from\" (modelChange)=\"setBetweenFrom(rule, $event)\" [disabled]=\"resolvedDisabled()\" hideInlineError [autoId]=\"autoIdFor('from-' + index)\"></sd-date>\n <span class=\"qb-dash\">\u2014</span>\n <sd-date class=\"qb-val qb-date-val\" size=\"sm\" [model]=\"rule.value?.to\" (modelChange)=\"setBetweenTo(rule, $event)\" [disabled]=\"resolvedDisabled()\" hideInlineError [autoId]=\"autoIdFor('to-' + index)\"></sd-date>\n }\n @case ('datetime') {\n <sd-datetime class=\"qb-val\" size=\"sm\" [model]=\"rule.value?.from\" (modelChange)=\"setBetweenFrom(rule, $event)\" [disabled]=\"resolvedDisabled()\" hideInlineError [autoId]=\"autoIdFor('from-' + index)\"></sd-datetime>\n <span class=\"qb-dash\">\u2014</span>\n <sd-datetime class=\"qb-val\" size=\"sm\" [model]=\"rule.value?.to\" (modelChange)=\"setBetweenTo(rule, $event)\" [disabled]=\"resolvedDisabled()\" hideInlineError [autoId]=\"autoIdFor('to-' + index)\"></sd-datetime>\n }\n }\n </div>\n } @else {\n @switch (fieldOf(rule)?.type) {\n @case ('string') {\n <sd-input class=\"qb-val\" placeholder=\"Gi\u00E1 tr\u1ECB\" size=\"sm\" [model]=\"rule.value\" (modelChange)=\"setScalar(rule, $event)\" [disabled]=\"resolvedDisabled()\" hideInlineError [autoId]=\"autoIdFor('val-' + index)\"></sd-input>\n }\n @case ('number') {\n <sd-input-number class=\"qb-val\" placeholder=\"Gi\u00E1 tr\u1ECB\" size=\"sm\" [model]=\"rule.value\" (modelChange)=\"setScalar(rule, $event)\" [min]=\"fieldOf(rule)?.min\" [max]=\"fieldOf(rule)?.max\" [disabled]=\"resolvedDisabled()\" hideInlineError [autoId]=\"autoIdFor('val-' + index)\"></sd-input-number>\n }\n @case ('boolean') {\n <sd-select class=\"qb-val\" [items]=\"booleanItems(fieldOf(rule)!)\" valueField=\"value\" displayField=\"display\" placeholder=\"Gi\u00E1 tr\u1ECB\" size=\"sm\" [model]=\"rule.value\" (modelChange)=\"setScalar(rule, $event)\" [disabled]=\"resolvedDisabled()\" [clearable]=\"false\" hideInlineError [autoId]=\"autoIdFor('val-' + index)\"></sd-select>\n }\n @case ('date') {\n @let _dateMode = dateMode(rule);\n <sd-select class=\"qb-date-mode\" [items]=\"dateModes\" valueField=\"value\" displayField=\"display\" size=\"sm\" [model]=\"_dateMode\" (modelChange)=\"setDateMode(rule, $event)\" [disabled]=\"resolvedDisabled()\" [clearable]=\"false\" hideInlineError [autoId]=\"autoIdFor('datemode-' + index)\">\n <ng-template sdItemDef let-item=\"item\">\n <span class=\"qb-opt\">\n <mat-icon class=\"qb-opt-icon\" fontSet=\"material-icons-outlined\">{{ item.icon }}</mat-icon>\n <span class=\"qb-opt-label\">{{ item.display }}</span>\n </span>\n </ng-template>\n <ng-template #sdSelected let-item=\"item\">\n <span class=\"qb-opt\">\n <mat-icon class=\"qb-opt-icon\" fontSet=\"material-icons-outlined\">{{ item?.icon }}</mat-icon>\n <span class=\"qb-opt-label\">{{ item?.display }}</span>\n </span>\n </ng-template>\n </sd-select>\n @switch (_dateMode) {\n @case ('absolute') {\n <sd-date class=\"qb-val qb-date-val\" size=\"sm\" [model]=\"rule.value\" (modelChange)=\"setScalar(rule, $event)\" [disabled]=\"resolvedDisabled()\" hideInlineError [autoId]=\"autoIdFor('val-' + index)\"></sd-date>\n }\n @case ('relative') {\n <sd-input-number class=\"qb-rel-amount\" type=\"positive\" [precision]=\"0\" size=\"sm\" [model]=\"relativeAmount(rule)\" (modelChange)=\"setRelativeAmount(rule, $event)\" [disabled]=\"resolvedDisabled()\" hideInlineError [autoId]=\"autoIdFor('rel-amount-' + index)\"></sd-input-number>\n <sd-select class=\"qb-rel-unit\" [items]=\"relativeUnitOptions\" valueField=\"value\" displayField=\"display\" size=\"sm\" [model]=\"relativeUnitDirValue(rule)\" (modelChange)=\"setRelativeUnitDir(rule, $event)\" [disabled]=\"resolvedDisabled()\" [clearable]=\"false\" hideInlineError [autoId]=\"autoIdFor('rel-unit-' + index)\"></sd-select>\n }\n }\n }\n @case ('datetime') {\n @let _dtMode = dateMode(rule);\n <sd-select class=\"qb-date-mode\" [items]=\"dateModes\" valueField=\"value\" displayField=\"display\" size=\"sm\" [model]=\"_dtMode\" (modelChange)=\"setDateMode(rule, $event)\" [disabled]=\"resolvedDisabled()\" [clearable]=\"false\" hideInlineError [autoId]=\"autoIdFor('datemode-' + index)\">\n <ng-template sdItemDef let-item=\"item\">\n <span class=\"qb-opt\">\n <mat-icon class=\"qb-opt-icon\" fontSet=\"material-icons-outlined\">{{ item.icon }}</mat-icon>\n <span class=\"qb-opt-label\">{{ item.display }}</span>\n </span>\n </ng-template>\n <ng-template #sdSelected let-item=\"item\">\n <span class=\"qb-opt\">\n <mat-icon class=\"qb-opt-icon\" fontSet=\"material-icons-outlined\">{{ item?.icon }}</mat-icon>\n <span class=\"qb-opt-label\">{{ item?.display }}</span>\n </span>\n </ng-template>\n </sd-select>\n @switch (_dtMode) {\n @case ('absolute') {\n <sd-datetime class=\"qb-val\" size=\"sm\" [model]=\"rule.value\" (modelChange)=\"setScalar(rule, $event)\" [disabled]=\"resolvedDisabled()\" hideInlineError [autoId]=\"autoIdFor('val-' + index)\"></sd-datetime>\n }\n @case ('relative') {\n <sd-input-number class=\"qb-rel-amount\" type=\"positive\" [precision]=\"0\" size=\"sm\" [model]=\"relativeAmount(rule)\" (modelChange)=\"setRelativeAmount(rule, $event)\" [disabled]=\"resolvedDisabled()\" hideInlineError [autoId]=\"autoIdFor('rel-amount-' + index)\"></sd-input-number>\n <sd-select class=\"qb-rel-unit\" [items]=\"relativeUnitOptions\" valueField=\"value\" displayField=\"display\" size=\"sm\" [model]=\"relativeUnitDirValue(rule)\" (modelChange)=\"setRelativeUnitDir(rule, $event)\" [disabled]=\"resolvedDisabled()\" [clearable]=\"false\" hideInlineError [autoId]=\"autoIdFor('rel-unit-' + index)\"></sd-select>\n }\n }\n }\n @case ('values') {\n <sd-select class=\"qb-val\" [items]=\"fieldOf(rule)!.values || []\" valueField=\"value\" displayField=\"display\" [multiple]=\"isMulti(rule.operator)\" placeholder=\"Gi\u00E1 tr\u1ECB\" size=\"sm\" [model]=\"rule.value\" (modelChange)=\"setScalar(rule, $event)\" [disabled]=\"resolvedDisabled()\" [clearable]=\"false\" hideInlineError [autoId]=\"autoIdFor('val-' + index)\"></sd-select>\n }\n }\n }\n }\n }\n\n <button type=\"button\" class=\"qb-btn qb-btn-remove-rule\" title=\"Xo\u00E1 \u0111i\u1EC1u ki\u1EC7n\" aria-label=\"Xo\u00E1 \u0111i\u1EC1u ki\u1EC7n\" [disabled]=\"resolvedDisabled()\" (click)=\"removeNode(parent, index)\">\u2715</button>\n </div>\n</ng-template>\n", styles: [":host{display:block;font-size:14px;color:var(--sd-text, #333)}.qb-view{display:block;width:100%;min-height:38px;padding:8px 12px;border:1px solid #d0d5dd;border-radius:6px;background:var(--sd-disabled-bg, #f6f7f9);color:var(--sd-text, #333);font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:13px;line-height:1.7;cursor:default;-webkit-user-select:text;user-select:text;white-space:pre-wrap;word-break:break-word}.qb-view-empty{color:#98a2b3}.qb-tok{white-space:pre}.qb-tok.qb-tok-field{color:var(--sd-text, #1d2939);font-weight:600}.qb-tok.qb-tok-op{color:var(--sd-primary, #2a66f4);font-weight:600}.qb-tok.qb-tok-value{color:var(--sd-success, #1a7f37)}.qb-tok.qb-tok-logic{color:#98a2b3;font-weight:700;text-transform:uppercase}.qb-tok.qb-tok-paren{color:#667085}.qb-container{padding:16px;background:#fff;border:1px solid #e0e0e0;border-radius:6px}.qb-container.is-disabled{opacity:.7;pointer-events:none}.qb-header{display:flex;align-items:center;gap:8px;margin-bottom:12px;position:relative;z-index:1}.qb-header.is-open{z-index:1000}.qb-logic-switch{display:inline-flex;border:1px solid #e0e0e0;border-radius:4px;overflow:hidden;height:30px}.qb-logic-switch button{border:none;background:transparent;padding:0 14px;font-weight:600;font-size:12px;cursor:pointer;color:#667085;height:100%}.qb-logic-switch button.active{background:var(--sd-primary, #2a66f4);color:#fff}.qb-logic-switch button:hover:not(.active):not([disabled]){background:var(--sd-primary-light, #eaf1ff);color:var(--sd-primary-dark, #1c4ad9)}.qb-logic-switch button[disabled]{cursor:default}.qb-actions{position:relative;display:flex;align-items:center;gap:4px}.qb-btn{background:transparent;border:none;cursor:pointer;font-size:16px;color:#667085;width:28px;height:28px;display:inline-flex;align-items:center;justify-content:center;border-radius:4px}.qb-btn:hover:not([disabled]){background:var(--sd-primary-light, #eaf1ff);color:var(--sd-primary, #2a66f4)}.qb-btn[disabled]{cursor:default;color:#cbd2dc}.qb-btn-remove:hover:not([disabled]),.qb-btn-remove-rule:hover:not([disabled]){background:#fee4e2;color:#d92d20}.qb-btn-remove-rule{margin-left:auto}.qb-dropdown{position:absolute;top:100%;left:0;margin-top:4px;background:#fff;border:1px solid #e0e0e0;box-shadow:0 4px 12px #1018281f;border-radius:6px;min-width:150px;z-index:100;overflow:hidden}.qb-dropdown button{display:block;width:100%;text-align:left;background:none;border:none;padding:9px 14px;cursor:pointer;font-size:13px;color:#344054}.qb-dropdown button:hover{background:var(--sd-primary-light, #eaf1ff);color:var(--sd-primary, #2a66f4)}.qb-body{padding-left:24px}.qb-treenode{position:relative;padding-left:22px;margin-bottom:12px}.qb-treenode:last-child{margin-bottom:0}.qb-treenode:before{content:\"\";position:absolute;left:0;top:-12px;bottom:0;border-left:1px dashed #d0d5dd}.qb-treenode:after{content:\"\";position:absolute;left:0;top:50%;width:18px;border-top:1px dashed #d0d5dd}.qb-treenode:last-child:before{bottom:auto;height:calc(50% + 12px)}.qb-empty{color:#98a2b3;font-style:italic;font-size:13px;padding:4px 0}.qb-rule{display:flex;align-items:center;gap:8px;background:#fafbfc;border:1px solid #e0e0e0;border-radius:6px;padding:6px 10px}.qb-rule:hover{border-color:#c2c8d0}.qb-field{min-width:170px}.qb-val{min-width:140px}.qb-value-source{min-width:132px}.qb-compare-field{min-width:170px}.qb-date-val{min-width:0;width:132px;flex:0 0 auto}::ng-deep .qb-opt{display:flex;align-items:center;gap:6px;min-width:0}::ng-deep .qb-opt .qb-opt-icon.mat-icon{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;margin:0;font-size:18px;width:18px;height:18px;line-height:1;color:#667085}::ng-deep .qb-opt .qb-opt-label{min-width:0}.qb-between{display:flex;align-items:center;gap:6px}.qb-dash{color:#98a2b3}.qb-divider{width:1px;align-self:stretch;background:#e0e0e0}.qb-treenode.is-group>.qb-group{border:1px solid #e0e0e0;border-left:2px solid var(--sd-primary, #2a66f4);border-radius:6px;padding:12px;background:#fff}.qb-date-mode{min-width:160px}.qb-rel-amount{width:72px}.qb-rel-unit{min-width:110px}\n"] }]
|
|
688
886
|
}], ctorParameters: () => [] });
|
|
689
887
|
|
|
690
888
|
/**
|
|
691
889
|
* Generated bundle index. Do not edit.
|
|
692
890
|
*/
|
|
693
891
|
|
|
694
|
-
export { QB_DATE_MODES, QB_DEFAULT_OPERATOR_BY_TYPE, QB_EMPTY_OPTIONS, QB_MULTI_OPERATORS, QB_NO_DATA_OPERATORS, QB_OPERATORS_BY_TYPE, QB_RELATIVE_UNIT_OPTIONS, SdQueryBuilder, filterToTokens, filterToTree, isQbGroup, qbAllowedOperators, qbDefaultOperator, qbDefaultRelative, qbId, qbIsMultiOperator, qbIsNoDataOperator, qbIsRelativeDate, qbNewGroup, qbNewRule, treeToFilter };
|
|
892
|
+
export { QB_DATE_MODES, QB_DEFAULT_OPERATOR_BY_TYPE, QB_EMPTY_OPTIONS, QB_MULTI_OPERATORS, QB_NO_DATA_OPERATORS, QB_OPERATORS_BY_TYPE, QB_RELATIVE_UNIT_OPTIONS, QB_TODAY, QB_TYPE_ICON, QB_VALUE_SOURCE_OPTIONS, SdQueryBuilder, filterToTokens, filterToTree, isQbGroup, qbAllowedOperators, qbDefaultOperator, qbDefaultRelative, qbFieldIcon, qbId, qbIsMultiOperator, qbIsNoDataOperator, qbIsRelativeDate, qbIsToday, qbNewGroup, qbNewRule, qbSupportsFieldCompareOperator, treeToFilter };
|
|
695
893
|
//# sourceMappingURL=sdcorejs-angular-components-query-builder.mjs.map
|