@sdcorejs/angular 19.0.4 → 19.0.5

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.
Files changed (42) hide show
  1. package/assets/{scss/README.md → STYLE-GUIDE.md} +3 -0
  2. package/components/index.d.ts +2 -0
  3. package/components/org-chart/index.d.ts +3 -0
  4. package/components/org-chart/src/org-chart-item-def.directive.d.ts +9 -0
  5. package/components/org-chart/src/org-chart.component.d.ts +26 -0
  6. package/components/org-chart/src/org-chart.model.d.ts +32 -0
  7. package/components/query-bar/src/query-bar.component.d.ts +15 -2
  8. package/components/query-bar/src/query-bar.model.d.ts +22 -0
  9. package/components/query-builder/src/query-builder.component.d.ts +23 -6
  10. package/components/query-builder/src/query-builder.model.d.ts +71 -23
  11. package/components/section/src/section.component.d.ts +4 -1
  12. package/components/splitter/src/splitter.component.d.ts +8 -2
  13. package/components/splitter/src/splitter.models.d.ts +13 -0
  14. package/components/table/src/services/table-filter/table-filter.model.d.ts +2 -0
  15. package/components/tree/index.d.ts +3 -0
  16. package/components/tree/src/tree-item-def.directive.d.ts +9 -0
  17. package/components/tree/src/tree.component.d.ts +102 -0
  18. package/components/tree/src/tree.model.d.ts +142 -0
  19. package/fesm2022/sdcorejs-angular-components-form-generic.mjs +2 -2
  20. package/fesm2022/sdcorejs-angular-components-form-generic.mjs.map +1 -1
  21. package/fesm2022/sdcorejs-angular-components-org-chart.mjs +110 -0
  22. package/fesm2022/sdcorejs-angular-components-org-chart.mjs.map +1 -0
  23. package/fesm2022/sdcorejs-angular-components-query-bar.mjs +37 -8
  24. package/fesm2022/sdcorejs-angular-components-query-bar.mjs.map +1 -1
  25. package/fesm2022/sdcorejs-angular-components-query-builder.mjs +257 -59
  26. package/fesm2022/sdcorejs-angular-components-query-builder.mjs.map +1 -1
  27. package/fesm2022/sdcorejs-angular-components-section.mjs +7 -4
  28. package/fesm2022/sdcorejs-angular-components-section.mjs.map +1 -1
  29. package/fesm2022/sdcorejs-angular-components-splitter.mjs +23 -12
  30. package/fesm2022/sdcorejs-angular-components-splitter.mjs.map +1 -1
  31. package/fesm2022/sdcorejs-angular-components-table.mjs +6 -2
  32. package/fesm2022/sdcorejs-angular-components-table.mjs.map +1 -1
  33. package/fesm2022/sdcorejs-angular-components-tree.mjs +526 -0
  34. package/fesm2022/sdcorejs-angular-components-tree.mjs.map +1 -0
  35. package/fesm2022/sdcorejs-angular-components.mjs +2 -0
  36. package/fesm2022/sdcorejs-angular-components.mjs.map +1 -1
  37. package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs +2 -2
  38. package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs.map +1 -1
  39. package/fesm2022/sdcorejs-angular-forms-select.mjs +33 -3
  40. package/fesm2022/sdcorejs-angular-forms-select.mjs.map +1 -1
  41. package/forms/select/src/select.component.d.ts +9 -1
  42. package/package.json +13 -5
@@ -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
- /** Type guard narrows an arbitrary value to a relative-date spec. */
85
- function qbIsRelativeDate(v) {
86
- return !!v && typeof v === 'object' && (v.rel === 'now' || v.rel === 'offset');
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 { rel: 'offset', unit: 'day', amount: 1, direction: 'previous' };
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
- if (v.rel === 'now')
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
- data: { rel: 'offset', unit: v.unit, amount: v.amount, direction: v.direction },
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 spec as readable Vietnamese for the view string. */
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
- if (v.rel === 'now')
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 ?? 1} ${unit} ${dir}`;
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.startsWith('NOT_') ? 'not like' : 'like' });
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: SYMBOL[operator] ?? operator });
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.fields())
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.fields()) {
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.fields()));
409
- isView = computed(() => this.mode() === 'view');
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 v = this.value();
417
- const fl = this.filters();
418
- const incoming = this.#coalesce(v, fl);
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
- this.filters.set(isAndOr(filter) ? filter.data : filter ? [filter] : []);
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.disabled() || group.logic === logic)
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.disabled())
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.disabled())
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.disabled())
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.disabled())
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.disabled())
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.disabled())
674
+ if (this.resolvedDisabled())
543
675
  return;
544
676
  rule.operator = op;
545
- rule.value = this.#reshapeValue(op, rule.value);
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.disabled())
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.disabled())
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.disabled())
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; otherwise drop array/range remnants
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 v.rel === 'now' ? 'now' : 'relative';
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.disabled())
832
+ if (this.resolvedDisabled())
638
833
  return;
834
+ this.#useLiteralSource(rule);
639
835
  if (mode === 'now')
640
- rule.value = { rel: 'now' };
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) && v.rel === 'offset' ? v.amount ?? 1 : 1;
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.disabled())
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 = { rel: 'offset', unit: cur.unit ?? 'day', amount, direction: cur.direction ?? 'previous' };
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) && v.rel === 'offset')
666
- return `${v.unit ?? 'day'}:${v.direction ?? 'previous'}`;
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.disabled())
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 = { rel: 'offset', unit, amount: cur.amount ?? 1, direction };
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.autoId() ?? 'qb'}-${suffix}`;
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