@sd-angular/core 19.0.0-beta.101 → 19.0.0-beta.103

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 (68) hide show
  1. package/components/operator/src/operator.component.d.ts +0 -4
  2. package/components/query-bar/src/actions-bar.component.d.ts +31 -0
  3. package/components/query-bar/src/build-chip.component.d.ts +67 -0
  4. package/components/query-bar/src/chip-popover.component.d.ts +73 -0
  5. package/components/query-bar/src/field-picker.component.d.ts +37 -0
  6. package/components/query-bar/src/inline-chip.component.d.ts +87 -0
  7. package/components/query-bar/src/popover-chip.component.d.ts +35 -0
  8. package/components/query-bar/src/query-bar.component.d.ts +46 -89
  9. package/components/query-bar/src/query-bar.model.d.ts +44 -29
  10. package/components/query-bar/src/saved-filters-menu.component.d.ts +39 -0
  11. package/components/view/src/view.component.d.ts +2 -1
  12. package/fesm2022/sd-angular-core-components-anchor.mjs +73 -47
  13. package/fesm2022/sd-angular-core-components-anchor.mjs.map +1 -1
  14. package/fesm2022/sd-angular-core-components-badge.mjs +2 -2
  15. package/fesm2022/sd-angular-core-components-badge.mjs.map +1 -1
  16. package/fesm2022/sd-angular-core-components-form-generic.mjs +10 -10
  17. package/fesm2022/sd-angular-core-components-form-generic.mjs.map +1 -1
  18. package/fesm2022/sd-angular-core-components-operator.mjs +3 -16
  19. package/fesm2022/sd-angular-core-components-operator.mjs.map +1 -1
  20. package/fesm2022/sd-angular-core-components-query-bar.mjs +871 -432
  21. package/fesm2022/sd-angular-core-components-query-bar.mjs.map +1 -1
  22. package/fesm2022/sd-angular-core-components-table.mjs +6 -6
  23. package/fesm2022/sd-angular-core-components-table.mjs.map +1 -1
  24. package/fesm2022/sd-angular-core-components-upload-file.mjs +14 -13
  25. package/fesm2022/sd-angular-core-components-upload-file.mjs.map +1 -1
  26. package/fesm2022/sd-angular-core-components-view.mjs +5 -2
  27. package/fesm2022/sd-angular-core-components-view.mjs.map +1 -1
  28. package/fesm2022/sd-angular-core-forms-autocomplete.mjs +3 -3
  29. package/fesm2022/sd-angular-core-forms-autocomplete.mjs.map +1 -1
  30. package/fesm2022/sd-angular-core-forms-checkbox.mjs +14 -2
  31. package/fesm2022/sd-angular-core-forms-checkbox.mjs.map +1 -1
  32. package/fesm2022/sd-angular-core-forms-chip-calendar.mjs +3 -3
  33. package/fesm2022/sd-angular-core-forms-chip-calendar.mjs.map +1 -1
  34. package/fesm2022/sd-angular-core-forms-chip.mjs +3 -3
  35. package/fesm2022/sd-angular-core-forms-chip.mjs.map +1 -1
  36. package/fesm2022/sd-angular-core-forms-date-range.mjs +41 -4
  37. package/fesm2022/sd-angular-core-forms-date-range.mjs.map +1 -1
  38. package/fesm2022/sd-angular-core-forms-date.mjs +4 -4
  39. package/fesm2022/sd-angular-core-forms-date.mjs.map +1 -1
  40. package/fesm2022/sd-angular-core-forms-datetime.mjs +4 -4
  41. package/fesm2022/sd-angular-core-forms-datetime.mjs.map +1 -1
  42. package/fesm2022/sd-angular-core-forms-input-number.mjs +3 -3
  43. package/fesm2022/sd-angular-core-forms-input-number.mjs.map +1 -1
  44. package/fesm2022/sd-angular-core-forms-input.mjs +3 -4
  45. package/fesm2022/sd-angular-core-forms-input.mjs.map +1 -1
  46. package/fesm2022/sd-angular-core-forms-radio.mjs +20 -3
  47. package/fesm2022/sd-angular-core-forms-radio.mjs.map +1 -1
  48. package/fesm2022/sd-angular-core-forms-select.mjs +14 -6
  49. package/fesm2022/sd-angular-core-forms-select.mjs.map +1 -1
  50. package/fesm2022/sd-angular-core-forms-switch.mjs +14 -2
  51. package/fesm2022/sd-angular-core-forms-switch.mjs.map +1 -1
  52. package/fesm2022/sd-angular-core-forms-textarea.mjs +3 -3
  53. package/fesm2022/sd-angular-core-forms-textarea.mjs.map +1 -1
  54. package/fesm2022/sd-angular-core-i18n.mjs +35 -0
  55. package/fesm2022/sd-angular-core-i18n.mjs.map +1 -1
  56. package/fesm2022/sd-angular-core-modules-layout.mjs +2 -2
  57. package/fesm2022/sd-angular-core-modules-layout.mjs.map +1 -1
  58. package/fesm2022/sd-angular-core-services-confirm.mjs +1 -1
  59. package/fesm2022/sd-angular-core-services-confirm.mjs.map +1 -1
  60. package/forms/checkbox/src/checkbox.component.d.ts +4 -2
  61. package/forms/date-range/src/date-range.component.d.ts +16 -2
  62. package/forms/input/src/input.component.d.ts +1 -2
  63. package/forms/radio/src/radio.component.d.ts +3 -1
  64. package/forms/switch/src/switch.component.d.ts +2 -1
  65. package/i18n/src/en.d.ts +5 -0
  66. package/package.json +75 -75
  67. package/sd-angular-core-19.0.0-beta.103.tgz +0 -0
  68. package/sd-angular-core-19.0.0-beta.101.tgz +0 -0
@@ -1,30 +1,170 @@
1
- import * as i1$1 from '@angular/common';
2
1
  import { CommonModule } from '@angular/common';
3
2
  import * as i0 from '@angular/core';
4
- import { input, output, viewChild, signal, computed, effect, afterNextRender, ChangeDetectionStrategy, Component, inject, Injector, ElementRef, viewChildren, model, booleanAttribute, isSignal } from '@angular/core';
5
- import * as i1 from '@angular/forms';
3
+ import { input, output, signal, computed, effect, ChangeDetectionStrategy, Component, booleanAttribute, model, viewChild, afterNextRender, isSignal, inject, Injector, ChangeDetectorRef, viewChildren } from '@angular/core';
4
+ import * as i1$1 from '@angular/forms';
6
5
  import { FormsModule } from '@angular/forms';
7
6
  import * as i2 from '@angular/material/icon';
8
7
  import { MatIconModule } from '@angular/material/icon';
9
- import * as i4 from '@angular/material/menu';
10
- import { MatMenuModule } from '@angular/material/menu';
11
- import * as i5 from '@angular/material/tooltip';
8
+ import * as i1 from '@angular/material/menu';
9
+ import { MatMenuModule, MatMenu, MatMenuTrigger } from '@angular/material/menu';
10
+ import * as i3 from '@angular/material/tooltip';
12
11
  import { MatTooltipModule } from '@angular/material/tooltip';
13
- import { isObservable, firstValueFrom } from 'rxjs';
14
- import { SdButton } from '@sd-angular/core/components/button';
12
+ import { I18nService } from '@sd-angular/core/i18n';
15
13
  import { SdOperator } from '@sd-angular/core/components/operator';
16
14
  import { SdDate } from '@sd-angular/core/forms/date';
15
+ import { SdDateRange } from '@sd-angular/core/forms/date-range';
17
16
  import { SdDatetime } from '@sd-angular/core/forms/datetime';
18
17
  import { SdInput } from '@sd-angular/core/forms/input';
19
18
  import { SdInputNumber } from '@sd-angular/core/forms/input-number';
20
19
  import { SdSelect } from '@sd-angular/core/forms/select';
21
- import { I18nService } from '@sd-angular/core/i18n';
20
+ import { isObservable } from 'rxjs';
21
+ import { SdButton } from '@sd-angular/core/components/button';
22
+
23
+ /* eslint-disable @typescript-eslint/no-explicit-any */
24
+ /**
25
+ * Saved-filters dropdown for `sd-query-bar`.
26
+ *
27
+ * Self-contained: persists the list to `localStorage` under
28
+ * `sd-query-bar:savedFilters:<key>` whenever the host passes a `[key]`. Without a
29
+ * key the dropdown still renders (host can still preview the menu) but the
30
+ * trigger stays disabled.
31
+ *
32
+ * The host owns the live `[query]`: each save snapshots it into a new entry,
33
+ * each apply emits the chosen filter back via `(apply)` for the host to install
34
+ * into its own `filters` / `logic` / `search` model.
35
+ *
36
+ * The save action (`promptSave`) is public so the parent toolbar can place its
37
+ * own "Lưu bộ lọc" button anywhere — typically next to the Search trigger.
38
+ */
39
+ class SdQuerySavedFiltersMenu {
40
+ /** Namespace key used to scope localStorage. `undefined` → menu disabled. */
41
+ key = input(undefined);
42
+ /** Current query the host wants to snapshot when the user clicks "Save current". */
43
+ query = input({ filters: [], logic: 'AND' });
44
+ /** Fired when the user picks a saved filter from the list. */
45
+ apply = output();
46
+ savedFilters = signal([]);
47
+ #storageKey = computed(() => {
48
+ const k = this.key();
49
+ return k ? `sd-query-bar:savedFilters:${k}` : undefined;
50
+ });
51
+ // why: reload whenever the namespace key changes (e.g. host switches user/context).
52
+ #loadFilters = effect(() => {
53
+ const storage = this.#storageKey();
54
+ if (!storage) {
55
+ this.savedFilters.set([]);
56
+ return;
57
+ }
58
+ try {
59
+ const raw = localStorage.getItem(storage);
60
+ this.savedFilters.set(raw ? JSON.parse(raw) : []);
61
+ }
62
+ catch {
63
+ this.savedFilters.set([]);
64
+ }
65
+ });
66
+ #persist(filters) {
67
+ const storage = this.#storageKey();
68
+ if (!storage)
69
+ return;
70
+ try {
71
+ localStorage.setItem(storage, JSON.stringify(filters));
72
+ }
73
+ catch {
74
+ /* quota / disabled storage — silent */
75
+ }
76
+ }
77
+ /**
78
+ * Prompt for a name and append a snapshot of the current query as a new
79
+ * saved filter. Public so the parent toolbar can wire its external "save"
80
+ * button to this method without re-implementing persistence.
81
+ */
82
+ promptSave() {
83
+ if (!this.#storageKey())
84
+ return;
85
+ const name = window.prompt('Tên bộ lọc:');
86
+ if (!name?.trim())
87
+ return;
88
+ const filter = {
89
+ id: (typeof crypto !== 'undefined' && crypto.randomUUID) ? crypto.randomUUID() : String(Date.now()),
90
+ name: name.trim(),
91
+ query: this.query(),
92
+ };
93
+ const next = [...this.savedFilters(), filter];
94
+ this.savedFilters.set(next);
95
+ this.#persist(next);
96
+ }
97
+ /** Apply a saved filter — host re-installs filters/logic/search via `(apply)`. */
98
+ pick(filter) {
99
+ this.apply.emit(filter);
100
+ }
101
+ /** Delete a saved filter by id. Stops propagation so the parent button doesn't fire `pick`. */
102
+ remove(id, ev) {
103
+ ev?.stopPropagation();
104
+ const next = this.savedFilters().filter(f => f.id !== id);
105
+ this.savedFilters.set(next);
106
+ this.#persist(next);
107
+ }
108
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdQuerySavedFiltersMenu, deps: [], target: i0.ɵɵFactoryTarget.Component });
109
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: SdQuerySavedFiltersMenu, isStandalone: true, selector: "sd-query-saved-filters-menu", inputs: { key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: false, transformFunction: null }, query: { classPropertyName: "query", publicName: "query", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { apply: "apply" }, ngImport: i0, template: "<button\r\n type=\"button\"\r\n class=\"c-saved-filters\"\r\n [disabled]=\"!key()\"\r\n [matMenuTriggerFor]=\"menu\"\r\n [matTooltip]=\"key() ? 'B\u1ED9 l\u1ECDc \u0111\u00E3 l\u01B0u' : 'C\u1EA7n [savedFiltersKey] \u0111\u1EC3 b\u1EADt'\">\r\n <mat-icon fontSet=\"material-icons-outlined\">bookmark_border</mat-icon>\r\n</button>\r\n\r\n<mat-menu #menu=\"matMenu\" class=\"c-saved-filters-menu\" xPosition=\"before\" yPosition=\"below\">\r\n @for (f of savedFilters(); track f.id) {\r\n <button type=\"button\" mat-menu-item class=\"c-sf-row\" (click)=\"pick(f)\">\r\n <mat-icon class=\"c-sf-lead\" fontSet=\"material-icons-outlined\">filter_alt</mat-icon>\r\n <span class=\"c-sf-name\">{{ f.name }}</span>\r\n <span\r\n role=\"button\"\r\n tabindex=\"0\"\r\n class=\"c-sf-del\"\r\n aria-label=\"Xo\u00E1 b\u1ED9 l\u1ECDc\"\r\n (click)=\"remove(f.id, $event)\"\r\n (keydown.enter)=\"remove(f.id, $event)\">\r\n <mat-icon fontSet=\"material-icons-outlined\">close</mat-icon>\r\n </span>\r\n </button>\r\n } @empty {\r\n <div class=\"c-sf-empty\">Ch\u01B0a c\u00F3 b\u1ED9 l\u1ECDc n\u00E0o.</div>\r\n }\r\n</mat-menu>\r\n", styles: [":host{display:inline-flex}.c-saved-filters{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:1px solid #d6d8db;border-radius:6px;background:#fff;color:#5f6368;cursor:pointer;padding:0}.c-saved-filters mat-icon{font-size:18px;width:18px;height:18px}.c-saved-filters:hover:not([disabled]){border-color:var(--sd-primary);color:var(--sd-primary)}.c-saved-filters[disabled]{opacity:.5;cursor:not-allowed}::ng-deep .mat-mdc-menu-panel.c-saved-filters-menu{min-width:220px;max-width:320px}::ng-deep .mat-mdc-menu-panel.c-saved-filters-menu .mat-mdc-menu-item.c-sf-row{display:flex;align-items:center;min-height:36px}::ng-deep .mat-mdc-menu-panel.c-saved-filters-menu .c-sf-lead{flex:0 0 auto;font-size:18px;width:18px;height:18px;margin-right:8px}::ng-deep .mat-mdc-menu-panel.c-saved-filters-menu .c-sf-name{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}::ng-deep .mat-mdc-menu-panel.c-saved-filters-menu .c-sf-del{flex:0 0 auto;align-self:center;margin-left:auto;display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:50%;color:#7a7a7a;cursor:pointer}::ng-deep .mat-mdc-menu-panel.c-saved-filters-menu .c-sf-del mat-icon{font-size:14px;width:14px;height:14px;line-height:14px}::ng-deep .mat-mdc-menu-panel.c-saved-filters-menu .c-sf-del:hover{background:#0000000f;color:#d23535}::ng-deep .mat-mdc-menu-panel.c-saved-filters-menu .c-sf-empty{padding:12px 16px;font-size:13px;color:#7a7a7a}\n"], dependencies: [{ kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i1.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i1.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i1.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i3.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
110
+ }
111
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdQuerySavedFiltersMenu, decorators: [{
112
+ type: Component,
113
+ args: [{ selector: 'sd-query-saved-filters-menu', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [MatMenuModule, MatIconModule, MatTooltipModule], template: "<button\r\n type=\"button\"\r\n class=\"c-saved-filters\"\r\n [disabled]=\"!key()\"\r\n [matMenuTriggerFor]=\"menu\"\r\n [matTooltip]=\"key() ? 'B\u1ED9 l\u1ECDc \u0111\u00E3 l\u01B0u' : 'C\u1EA7n [savedFiltersKey] \u0111\u1EC3 b\u1EADt'\">\r\n <mat-icon fontSet=\"material-icons-outlined\">bookmark_border</mat-icon>\r\n</button>\r\n\r\n<mat-menu #menu=\"matMenu\" class=\"c-saved-filters-menu\" xPosition=\"before\" yPosition=\"below\">\r\n @for (f of savedFilters(); track f.id) {\r\n <button type=\"button\" mat-menu-item class=\"c-sf-row\" (click)=\"pick(f)\">\r\n <mat-icon class=\"c-sf-lead\" fontSet=\"material-icons-outlined\">filter_alt</mat-icon>\r\n <span class=\"c-sf-name\">{{ f.name }}</span>\r\n <span\r\n role=\"button\"\r\n tabindex=\"0\"\r\n class=\"c-sf-del\"\r\n aria-label=\"Xo\u00E1 b\u1ED9 l\u1ECDc\"\r\n (click)=\"remove(f.id, $event)\"\r\n (keydown.enter)=\"remove(f.id, $event)\">\r\n <mat-icon fontSet=\"material-icons-outlined\">close</mat-icon>\r\n </span>\r\n </button>\r\n } @empty {\r\n <div class=\"c-sf-empty\">Ch\u01B0a c\u00F3 b\u1ED9 l\u1ECDc n\u00E0o.</div>\r\n }\r\n</mat-menu>\r\n", styles: [":host{display:inline-flex}.c-saved-filters{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:1px solid #d6d8db;border-radius:6px;background:#fff;color:#5f6368;cursor:pointer;padding:0}.c-saved-filters mat-icon{font-size:18px;width:18px;height:18px}.c-saved-filters:hover:not([disabled]){border-color:var(--sd-primary);color:var(--sd-primary)}.c-saved-filters[disabled]{opacity:.5;cursor:not-allowed}::ng-deep .mat-mdc-menu-panel.c-saved-filters-menu{min-width:220px;max-width:320px}::ng-deep .mat-mdc-menu-panel.c-saved-filters-menu .mat-mdc-menu-item.c-sf-row{display:flex;align-items:center;min-height:36px}::ng-deep .mat-mdc-menu-panel.c-saved-filters-menu .c-sf-lead{flex:0 0 auto;font-size:18px;width:18px;height:18px;margin-right:8px}::ng-deep .mat-mdc-menu-panel.c-saved-filters-menu .c-sf-name{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}::ng-deep .mat-mdc-menu-panel.c-saved-filters-menu .c-sf-del{flex:0 0 auto;align-self:center;margin-left:auto;display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:50%;color:#7a7a7a;cursor:pointer}::ng-deep .mat-mdc-menu-panel.c-saved-filters-menu .c-sf-del mat-icon{font-size:14px;width:14px;height:14px;line-height:14px}::ng-deep .mat-mdc-menu-panel.c-saved-filters-menu .c-sf-del:hover{background:#0000000f;color:#d23535}::ng-deep .mat-mdc-menu-panel.c-saved-filters-menu .c-sf-empty{padding:12px 16px;font-size:13px;color:#7a7a7a}\n"] }]
114
+ }] });
115
+
116
+ /* eslint-disable @typescript-eslint/no-explicit-any */
117
+ /**
118
+ * Right-pinned toolbar for `sd-query-bar`: AND/OR connector toggle, saved-filters
119
+ * dropdown, clear-all, a save-filter shortcut, and the deferred Search trigger.
120
+ * Owns no business logic — just routes user intent back via outputs
121
+ * (`(logic)` model, `(clear)`, `(search)`, `(applyFilter)`).
122
+ *
123
+ * The "save filter" button forwards to `SdQuerySavedFiltersMenu.promptSave()` so
124
+ * the user can save the current query without first opening the saved-filters
125
+ * dropdown — keeps the save action adjacent to the Search trigger.
126
+ */
127
+ class SdQueryActionsBar {
128
+ // AND/OR toggle —
129
+ showLogicToggle = input(false, { transform: booleanAttribute });
130
+ logic = model('AND');
131
+ filtersCount = input(0);
132
+ // Saved filters —
133
+ showSavedFilters = input(false, { transform: booleanAttribute });
134
+ savedFiltersKey = input(undefined);
135
+ query = input({ filters: [], logic: 'AND' });
136
+ applyFilter = output();
137
+ // Clear-all —
138
+ showClearAll = input(true, { transform: booleanAttribute });
139
+ clear = output();
140
+ // Search trigger —
141
+ canSearch = input(false);
142
+ search = output();
143
+ // why: viewChild để có thể gọi promptSave() từ nút "Lưu bộ lọc" đặt cạnh Search.
144
+ // Nút lưu nằm ngoài <mat-menu> nên không thể dùng (click) wiring nội bộ.
145
+ savedFiltersMenu = viewChild(SdQuerySavedFiltersMenu);
146
+ setLogic(value) {
147
+ if (this.logic() === value)
148
+ return;
149
+ this.logic.set(value);
150
+ }
151
+ /** Trigger save-current-query via the saved-filters child (no-op when menu absent / no key). */
152
+ promptSave() {
153
+ this.savedFiltersMenu()?.promptSave();
154
+ }
155
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdQueryActionsBar, deps: [], target: i0.ɵɵFactoryTarget.Component });
156
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: SdQueryActionsBar, isStandalone: true, selector: "sd-query-actions-bar", inputs: { showLogicToggle: { classPropertyName: "showLogicToggle", publicName: "showLogicToggle", isSignal: true, isRequired: false, transformFunction: null }, logic: { classPropertyName: "logic", publicName: "logic", isSignal: true, isRequired: false, transformFunction: null }, filtersCount: { classPropertyName: "filtersCount", publicName: "filtersCount", isSignal: true, isRequired: false, transformFunction: null }, showSavedFilters: { classPropertyName: "showSavedFilters", publicName: "showSavedFilters", isSignal: true, isRequired: false, transformFunction: null }, savedFiltersKey: { classPropertyName: "savedFiltersKey", publicName: "savedFiltersKey", isSignal: true, isRequired: false, transformFunction: null }, query: { classPropertyName: "query", publicName: "query", isSignal: true, isRequired: false, transformFunction: null }, showClearAll: { classPropertyName: "showClearAll", publicName: "showClearAll", isSignal: true, isRequired: false, transformFunction: null }, canSearch: { classPropertyName: "canSearch", publicName: "canSearch", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { logic: "logicChange", applyFilter: "applyFilter", clear: "clear", search: "search" }, viewQueries: [{ propertyName: "savedFiltersMenu", first: true, predicate: SdQuerySavedFiltersMenu, descendants: true, isSignal: true }], ngImport: i0, template: "<!-- Vertical separator between the wrapping filter area (left) and the pinned toolbar. -->\r\n<span class=\"c-bar-divider\" aria-hidden=\"true\"></span>\r\n\r\n<div class=\"c-query-bar__actions\">\r\n <!-- AND / OR toggle -->\r\n @if (showLogicToggle() && filtersCount() >= 2) {\r\n <div class=\"c-logic-toggle\" role=\"group\" aria-label=\"Logic operator\">\r\n <button\r\n type=\"button\"\r\n class=\"c-logic-segment\"\r\n [class.c-logic-active]=\"logic() === 'AND'\"\r\n (click)=\"setLogic('AND')\">\r\n AND\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"c-logic-segment\"\r\n [class.c-logic-active]=\"logic() === 'OR'\"\r\n (click)=\"setLogic('OR')\">\r\n OR\r\n </button>\r\n </div>\r\n }\r\n\r\n @if (showSavedFilters()) {\r\n <sd-query-saved-filters-menu\r\n [key]=\"savedFiltersKey()\"\r\n [query]=\"query()\"\r\n (apply)=\"applyFilter.emit($event)\" />\r\n }\r\n\r\n @if (showClearAll() && filtersCount() > 0) {\r\n <button\r\n type=\"button\"\r\n class=\"c-clear-all\"\r\n [matTooltip]=\"'X\u00F3a t\u1EA5t c\u1EA3 (' + filtersCount() + ')'\"\r\n (click)=\"clear.emit()\">\r\n <mat-icon fontSet=\"material-icons-outlined\">delete_sweep</mat-icon>\r\n </button>\r\n }\r\n\r\n <!-- why: n\u00FAt \"L\u01B0u b\u1ED9 l\u1ECDc hi\u1EC7n t\u1EA1i\" \u0111\u1EB7t s\u00E1t Search \u2014 user l\u01B0u xong r\u1ED3i t\u00ECm\r\n lu\u00F4n trong c\u00F9ng v\u00F9ng ph\u1EA3i. Ch\u1EC9 hi\u1EC7n khi showSavedFilters=true V\u00C0 c\u00F3\r\n savedFiltersKey (kh\u00F4ng c\u00F3 key th\u00EC promptSave l\u00E0 no-op). -->\r\n @if (showSavedFilters() && savedFiltersKey()) {\r\n <button\r\n type=\"button\"\r\n class=\"c-save-filter\"\r\n matTooltip=\"L\u01B0u b\u1ED9 l\u1ECDc hi\u1EC7n t\u1EA1i\"\r\n (click)=\"promptSave()\">\r\n <mat-icon fontSet=\"material-icons-outlined\">bookmark_add</mat-icon>\r\n </button>\r\n }\r\n\r\n <!-- why: far-right \"submit at the end of the toolbar\" position; sole query trigger. -->\r\n <button\r\n type=\"button\"\r\n class=\"c-search-trigger\"\r\n [disabled]=\"!canSearch()\"\r\n matTooltip=\"T\u00ECm ki\u1EBFm\"\r\n (click)=\"search.emit()\">\r\n <mat-icon fontSet=\"material-icons-outlined\">search</mat-icon>\r\n </button>\r\n</div>\r\n", styles: [":host{display:contents}.c-bar-divider{flex:0 0 auto;align-self:stretch;width:1px;min-height:28px;background:#e2e4e7}.c-query-bar__actions{flex:0 0 auto;display:flex;align-items:center;gap:6px}.c-logic-toggle{display:inline-flex;align-items:center;padding:2px;background:#f5f6f7;border:1px solid #e2e4e7;border-radius:6px}:host-context(.c-density-compact) .c-logic-toggle{height:28px}:host-context(.c-density-comfortable) .c-logic-toggle{height:32px}.c-logic-toggle .c-logic-segment{height:100%;padding:0 10px;border:none;background:transparent;font-family:Roboto Mono,monospace;font-size:11px;font-weight:600;letter-spacing:.4px;color:#5f6368;cursor:pointer;border-radius:4px}.c-logic-toggle .c-logic-segment.c-logic-active{background:#fff;color:var(--sd-primary);box-shadow:0 1px 2px #00000014}.c-clear-all,.c-search-trigger{display:inline-flex;align-items:center;justify-content:center;border:1px solid #d6d8db;border-radius:6px;background:#fff;color:#5f6368;cursor:pointer;padding:0}:host-context(.c-density-compact) .c-clear-all,:host-context(.c-density-compact) .c-search-trigger{width:28px;height:28px}:host-context(.c-density-comfortable) .c-clear-all,:host-context(.c-density-comfortable) .c-search-trigger{width:32px;height:32px}.c-clear-all mat-icon,.c-search-trigger mat-icon{font-size:18px;width:18px;height:18px}.c-clear-all:hover:not([disabled]),.c-search-trigger:hover:not([disabled]){border-color:var(--sd-primary);color:var(--sd-primary)}.c-clear-all[disabled],.c-search-trigger[disabled]{opacity:.5;cursor:not-allowed}.c-clear-all:hover{border-color:#dc2626;color:#dc2626}\n"], dependencies: [{ kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i3.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: SdQuerySavedFiltersMenu, selector: "sd-query-saved-filters-menu", inputs: ["key", "query"], outputs: ["apply"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
157
+ }
158
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdQueryActionsBar, decorators: [{
159
+ type: Component,
160
+ args: [{ selector: 'sd-query-actions-bar', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [MatIconModule, MatTooltipModule, SdQuerySavedFiltersMenu], template: "<!-- Vertical separator between the wrapping filter area (left) and the pinned toolbar. -->\r\n<span class=\"c-bar-divider\" aria-hidden=\"true\"></span>\r\n\r\n<div class=\"c-query-bar__actions\">\r\n <!-- AND / OR toggle -->\r\n @if (showLogicToggle() && filtersCount() >= 2) {\r\n <div class=\"c-logic-toggle\" role=\"group\" aria-label=\"Logic operator\">\r\n <button\r\n type=\"button\"\r\n class=\"c-logic-segment\"\r\n [class.c-logic-active]=\"logic() === 'AND'\"\r\n (click)=\"setLogic('AND')\">\r\n AND\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"c-logic-segment\"\r\n [class.c-logic-active]=\"logic() === 'OR'\"\r\n (click)=\"setLogic('OR')\">\r\n OR\r\n </button>\r\n </div>\r\n }\r\n\r\n @if (showSavedFilters()) {\r\n <sd-query-saved-filters-menu\r\n [key]=\"savedFiltersKey()\"\r\n [query]=\"query()\"\r\n (apply)=\"applyFilter.emit($event)\" />\r\n }\r\n\r\n @if (showClearAll() && filtersCount() > 0) {\r\n <button\r\n type=\"button\"\r\n class=\"c-clear-all\"\r\n [matTooltip]=\"'X\u00F3a t\u1EA5t c\u1EA3 (' + filtersCount() + ')'\"\r\n (click)=\"clear.emit()\">\r\n <mat-icon fontSet=\"material-icons-outlined\">delete_sweep</mat-icon>\r\n </button>\r\n }\r\n\r\n <!-- why: n\u00FAt \"L\u01B0u b\u1ED9 l\u1ECDc hi\u1EC7n t\u1EA1i\" \u0111\u1EB7t s\u00E1t Search \u2014 user l\u01B0u xong r\u1ED3i t\u00ECm\r\n lu\u00F4n trong c\u00F9ng v\u00F9ng ph\u1EA3i. Ch\u1EC9 hi\u1EC7n khi showSavedFilters=true V\u00C0 c\u00F3\r\n savedFiltersKey (kh\u00F4ng c\u00F3 key th\u00EC promptSave l\u00E0 no-op). -->\r\n @if (showSavedFilters() && savedFiltersKey()) {\r\n <button\r\n type=\"button\"\r\n class=\"c-save-filter\"\r\n matTooltip=\"L\u01B0u b\u1ED9 l\u1ECDc hi\u1EC7n t\u1EA1i\"\r\n (click)=\"promptSave()\">\r\n <mat-icon fontSet=\"material-icons-outlined\">bookmark_add</mat-icon>\r\n </button>\r\n }\r\n\r\n <!-- why: far-right \"submit at the end of the toolbar\" position; sole query trigger. -->\r\n <button\r\n type=\"button\"\r\n class=\"c-search-trigger\"\r\n [disabled]=\"!canSearch()\"\r\n matTooltip=\"T\u00ECm ki\u1EBFm\"\r\n (click)=\"search.emit()\">\r\n <mat-icon fontSet=\"material-icons-outlined\">search</mat-icon>\r\n </button>\r\n</div>\r\n", styles: [":host{display:contents}.c-bar-divider{flex:0 0 auto;align-self:stretch;width:1px;min-height:28px;background:#e2e4e7}.c-query-bar__actions{flex:0 0 auto;display:flex;align-items:center;gap:6px}.c-logic-toggle{display:inline-flex;align-items:center;padding:2px;background:#f5f6f7;border:1px solid #e2e4e7;border-radius:6px}:host-context(.c-density-compact) .c-logic-toggle{height:28px}:host-context(.c-density-comfortable) .c-logic-toggle{height:32px}.c-logic-toggle .c-logic-segment{height:100%;padding:0 10px;border:none;background:transparent;font-family:Roboto Mono,monospace;font-size:11px;font-weight:600;letter-spacing:.4px;color:#5f6368;cursor:pointer;border-radius:4px}.c-logic-toggle .c-logic-segment.c-logic-active{background:#fff;color:var(--sd-primary);box-shadow:0 1px 2px #00000014}.c-clear-all,.c-search-trigger{display:inline-flex;align-items:center;justify-content:center;border:1px solid #d6d8db;border-radius:6px;background:#fff;color:#5f6368;cursor:pointer;padding:0}:host-context(.c-density-compact) .c-clear-all,:host-context(.c-density-compact) .c-search-trigger{width:28px;height:28px}:host-context(.c-density-comfortable) .c-clear-all,:host-context(.c-density-comfortable) .c-search-trigger{width:32px;height:32px}.c-clear-all mat-icon,.c-search-trigger mat-icon{font-size:18px;width:18px;height:18px}.c-clear-all:hover:not([disabled]),.c-search-trigger:hover:not([disabled]){border-color:var(--sd-primary);color:var(--sd-primary)}.c-clear-all[disabled],.c-search-trigger[disabled]{opacity:.5;cursor:not-allowed}.c-clear-all:hover{border-color:#dc2626;color:#dc2626}\n"] }]
161
+ }] });
22
162
 
23
163
  // ---------------------------------------------------------------------------
24
164
  // Constants
25
165
  // ---------------------------------------------------------------------------
26
- /** Full operator set per field kind — used when `operators: true`. */
27
- const SD_QUERY_OPERATORS_BY_KIND = {
166
+ /** Full operator set per field type — used when `operators: true`. */
167
+ const SD_QUERY_OPERATORS_BY_TYPE = {
28
168
  string: ['CONTAIN', 'EQUAL', 'NOT_EQUAL', 'START_WITH', 'END_WITH', 'NULL', 'NOT_NULL'],
29
169
  number: ['EQUAL', 'NOT_EQUAL', 'GREATER_THAN', 'GREATER_OR_EQUAL', 'LESS_THAN', 'LESS_OR_EQUAL', 'BETWEEN'],
30
170
  boolean: ['EQUAL'],
@@ -34,7 +174,7 @@ const SD_QUERY_OPERATORS_BY_KIND = {
34
174
  'lazy-values': ['IN', 'NOT_IN', 'NULL', 'NOT_NULL'],
35
175
  };
36
176
  /** Default operator when a chip is created and `field.defaultOperator` is not set. */
37
- const SD_QUERY_DEFAULT_OPERATOR_BY_KIND = {
177
+ const SD_QUERY_DEFAULT_OPERATOR_BY_TYPE = {
38
178
  string: 'CONTAIN',
39
179
  number: 'EQUAL',
40
180
  boolean: 'EQUAL',
@@ -43,28 +183,8 @@ const SD_QUERY_DEFAULT_OPERATOR_BY_KIND = {
43
183
  values: 'IN',
44
184
  'lazy-values': 'IN',
45
185
  };
46
- /** Vietnamese operator labels. Replace via i18n in the consuming template if needed. */
47
- const SD_QUERY_OPERATOR_LABEL = {
48
- EQUAL: 'là',
49
- NOT_EQUAL: 'không là',
50
- CONTAIN: 'chứa',
51
- NOT_CONTAIN: 'không chứa',
52
- START_WITH: 'bắt đầu bằng',
53
- NOT_START_WITH: 'không bắt đầu bằng',
54
- END_WITH: 'kết thúc bằng',
55
- NOT_END_WITH: 'không kết thúc bằng',
56
- IN: 'thuộc',
57
- NOT_IN: 'không thuộc',
58
- GREATER_THAN: '>',
59
- LESS_THAN: '<',
60
- GREATER_OR_EQUAL: '≥',
61
- LESS_OR_EQUAL: '≤',
62
- BETWEEN: 'trong khoảng',
63
- NULL: 'trống',
64
- NOT_NULL: 'có giá trị',
65
- };
66
- /** Icon fallback per field kind when `SdQueryField.icon` is not set. */
67
- const SD_QUERY_KIND_ICON = {
186
+ /** Icon fallback per field type when `SdQueryField.icon` is not set. */
187
+ const SD_QUERY_TYPE_ICON = {
68
188
  string: 'text_fields',
69
189
  number: 'tag',
70
190
  boolean: 'toggle_on',
@@ -75,10 +195,10 @@ const SD_QUERY_KIND_ICON = {
75
195
  };
76
196
  /**
77
197
  * Resolves the icon shown on chips and in the field picker.
78
- * Priority: `field.icon` → `SD_QUERY_KIND_ICON[field.kind]` → `'tune'`.
198
+ * Priority: `field.icon` → `SD_QUERY_TYPE_ICON[field.type]` → `'tune'`.
79
199
  */
80
200
  function sdQueryFieldIcon(field) {
81
- return field.icon ?? SD_QUERY_KIND_ICON[field.kind] ?? 'tune';
201
+ return field.icon ?? SD_QUERY_TYPE_ICON[field.type] ?? 'tune';
82
202
  }
83
203
  /** Operators that carry no data — value section is hidden in chip popover. */
84
204
  const SD_QUERY_NO_DATA_OPERATORS = ['NULL', 'NOT_NULL'];
@@ -88,17 +208,17 @@ const SD_QUERY_MULTI_OPERATORS = ['IN', 'NOT_IN'];
88
208
  function sdQueryDefaultOperator(field) {
89
209
  if (field.defaultOperator)
90
210
  return field.defaultOperator;
91
- return SD_QUERY_DEFAULT_OPERATOR_BY_KIND[field.kind];
211
+ return SD_QUERY_DEFAULT_OPERATOR_BY_TYPE[field.type];
92
212
  }
93
213
  /**
94
214
  * Operators offered in the chip popover for `field`:
95
- * - `operators === true` → full set for the kind
96
- * - `operators` is an array → that array (deduped against the kind set is the caller's job)
215
+ * - `operators === true` → full set for the type
216
+ * - `operators` is an array → that array (deduped against the type set is the caller's job)
97
217
  * - otherwise (simple mode) → just the single default operator (no real choice)
98
218
  */
99
219
  function sdQueryAllowedOperators(field) {
100
220
  if (field.operators === true)
101
- return SD_QUERY_OPERATORS_BY_KIND[field.kind];
221
+ return SD_QUERY_OPERATORS_BY_TYPE[field.type];
102
222
  if (Array.isArray(field.operators) && field.operators.length > 0)
103
223
  return field.operators;
104
224
  return [sdQueryDefaultOperator(field)];
@@ -148,7 +268,7 @@ class SdQueryInlineValueChip {
148
268
  draft = signal('');
149
269
  draftFrom = signal('');
150
270
  draftTo = signal('');
151
- isNumber = computed(() => this.field().kind === 'number');
271
+ isNumber = computed(() => this.field().type === 'number');
152
272
  isBetween = computed(() => this.operator() === 'BETWEEN');
153
273
  isNoData = computed(() => SD_QUERY_NO_DATA_OPERATORS.includes(this.operator()));
154
274
  icon = computed(() => sdQueryFieldIcon(this.field()));
@@ -231,61 +351,642 @@ class SdQueryInlineValueChip {
231
351
  this.hasError.set(true);
232
352
  return;
233
353
  }
234
- this.hasError.set(false);
235
- this.valueChange.emit({ from: from.value, to: to.value });
354
+ this.hasError.set(false);
355
+ this.valueChange.emit({ from: from.value, to: to.value });
356
+ }
357
+ /** Esc — revert drafts to the committed value and blur without emitting. */
358
+ revertAndBlur(input) {
359
+ const v = this.value();
360
+ if (this.isBetween()) {
361
+ const r = (v ?? {});
362
+ this.draftFrom.set(this.#format(r.from));
363
+ this.draftTo.set(this.#format(r.to));
364
+ }
365
+ else {
366
+ this.draft.set(this.#format(v));
367
+ }
368
+ this.hasError.set(false);
369
+ this.focused.set(false);
370
+ input.blur();
371
+ }
372
+ onRemove(ev) {
373
+ ev.stopPropagation();
374
+ this.remove.emit();
375
+ }
376
+ /** Format a stored value for display — number → vi-VN grouped digits. */
377
+ #format(v) {
378
+ if (v == null || v === '')
379
+ return '';
380
+ if (this.isNumber()) {
381
+ const n = typeof v === 'number' ? v : Number(String(v).replace(/\./g, ''));
382
+ return Number.isFinite(n) ? n.toLocaleString('vi-VN') : String(v);
383
+ }
384
+ return String(v);
385
+ }
386
+ /** Parse a draft back to a stored value. Number kind rejects non-numeric input. */
387
+ #parse(s) {
388
+ const t = (s ?? '').trim();
389
+ if (t === '')
390
+ return { ok: true, value: this.isNumber() ? null : '' };
391
+ if (this.isNumber()) {
392
+ // vi-VN groups thousands with '.', so strip dots + spaces, then require a plain
393
+ // (optionally negative) integer — anything else (letters, stray symbols) is an error.
394
+ const cleaned = t.replace(/[.\s]/g, '');
395
+ if (!/^-?\d+$/.test(cleaned))
396
+ return { ok: false, value: t };
397
+ const n = Number(cleaned);
398
+ return Number.isFinite(n) ? { ok: true, value: n } : { ok: false, value: t };
399
+ }
400
+ return { ok: true, value: t };
401
+ }
402
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdQueryInlineValueChip, deps: [], target: i0.ɵɵFactoryTarget.Component });
403
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: SdQueryInlineValueChip, isStandalone: true, selector: "sd-query-inline-value-chip", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, operator: { classPropertyName: "operator", publicName: "operator", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, density: { classPropertyName: "density", publicName: "density", isSignal: true, isRequired: false, transformFunction: null }, showOperator: { classPropertyName: "showOperator", publicName: "showOperator", isSignal: true, isRequired: false, transformFunction: null }, autoId: { classPropertyName: "autoId", publicName: "autoId", isSignal: true, isRequired: false, transformFunction: null }, autofocus: { classPropertyName: "autofocus", publicName: "autofocus", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange", remove: "remove" }, viewQueries: [{ propertyName: "firstInput", first: true, predicate: ["firstInput"], descendants: true, isSignal: true }], ngImport: i0, template: "<!--\r\n Seamless string/number chip \u2014 the pill IS the input. One border, three flush\r\n segments: label \u00B7 input(s) \u00B7 \u00D7. State styling driven by [data-state].\r\n-->\r\n<span\r\n class=\"c-seamless\"\r\n [attr.data-state]=\"state()\"\r\n [class.c-seamless--compact]=\"density() === 'compact'\"\r\n [class.c-seamless--comfortable]=\"density() === 'comfortable'\"\r\n (click)=\"focusFromShell($event)\">\r\n\r\n <!-- Label segment: icon + field name (+ optional operator pill) -->\r\n <span class=\"c-seamless__label\">\r\n <mat-icon class=\"c-seamless__icon\" fontSet=\"material-icons-outlined\">{{ icon() }}</mat-icon>\r\n <span class=\"c-seamless__field\">{{ field().label }}</span>\r\n @if (showOperator() && !isNoData()) {\r\n <sd-operator class=\"c-seamless__op\" [operators]=\"[operator()]\" [model]=\"operator()\" [disabled]=\"true\" />\r\n } @else if (!isNoData()) {\r\n <span class=\"c-seamless__sep\">:</span>\r\n }\r\n </span>\r\n\r\n <!-- Input segment \u2014 transparent, borderless; hidden for NULL / NOT_NULL -->\r\n @if (!isNoData()) {\r\n <span class=\"c-seamless__input\">\r\n @if (isBetween()) {\r\n <input\r\n #firstInput\r\n type=\"text\"\r\n class=\"c-seamless__field-input\"\r\n [attr.data-autoid]=\"autoId() ? autoId() + '-from' : null\"\r\n [ngModel]=\"draftFrom()\"\r\n (ngModelChange)=\"draftFrom.set($event)\"\r\n [size]=\"sizeFor(draftFrom(), phFrom())\"\r\n [placeholder]=\"phFrom()\"\r\n (focus)=\"onFocus()\"\r\n (blur)=\"commitRange()\"\r\n (keydown.enter)=\"$any($event.target).blur()\"\r\n (keydown.escape)=\"revertAndBlur($any($event.target))\" />\r\n <span class=\"c-seamless__dash\" aria-hidden=\"true\">\u2014</span>\r\n <input\r\n type=\"text\"\r\n class=\"c-seamless__field-input\"\r\n [attr.data-autoid]=\"autoId() ? autoId() + '-to' : null\"\r\n [ngModel]=\"draftTo()\"\r\n (ngModelChange)=\"draftTo.set($event)\"\r\n [size]=\"sizeFor(draftTo(), phTo())\"\r\n [placeholder]=\"phTo()\"\r\n (focus)=\"onFocus()\"\r\n (blur)=\"commitRange()\"\r\n (keydown.enter)=\"$any($event.target).blur()\"\r\n (keydown.escape)=\"revertAndBlur($any($event.target))\" />\r\n } @else {\r\n <input\r\n #firstInput\r\n type=\"text\"\r\n class=\"c-seamless__field-input\"\r\n [attr.data-autoid]=\"autoId() ?? null\"\r\n [ngModel]=\"draft()\"\r\n (ngModelChange)=\"draft.set($event)\"\r\n [size]=\"sizeFor(draft(), ph())\"\r\n [placeholder]=\"ph()\"\r\n (focus)=\"onFocus()\"\r\n (blur)=\"commitSingle()\"\r\n (keydown.enter)=\"$any($event.target).blur()\"\r\n (keydown.escape)=\"revertAndBlur($any($event.target))\" />\r\n }\r\n </span>\r\n }\r\n\r\n <!-- \u00D7 segment -->\r\n <button type=\"button\" class=\"c-seamless__x\" aria-label=\"Remove filter\" (click)=\"onRemove($event)\">\r\n <mat-icon fontSet=\"material-icons-outlined\">close</mat-icon>\r\n </button>\r\n</span>\r\n", styles: [":host{display:inline-flex}.c-seamless{display:inline-flex;align-items:stretch;border-radius:999px;overflow:hidden;font-family:inherit;font-size:13px;line-height:1;cursor:text;border:1px solid #d6d8db;background:#fff;color:#5f6368;transition:border-color .12s ease,background-color .12s ease,box-shadow .12s ease,color .12s ease}.c-seamless--compact{height:28px}.c-seamless--comfortable{height:32px}.c-seamless:hover:not([data-state=focus]):not([data-state=error]){border-color:var(--sd-primary)}.c-seamless[data-state=pending]{border-style:dashed;border-color:#d6d8db;background:#fff;color:#5f6368}.c-seamless[data-state=pending] .c-seamless__field-input{color:#212121}.c-seamless[data-state=pending] .c-seamless__field-input::placeholder{color:#7a7a7a;font-style:italic}.c-seamless[data-state=active]{border-color:var(--sd-primary-light);background:var(--sd-primary-light)}.c-seamless[data-state=active] .c-seamless__field-input{color:var(--sd-primary)}.c-seamless[data-state=focus]{border-color:var(--sd-primary);background:#fff;box-shadow:0 0 0 3px var(--sd-primary-light)}.c-seamless[data-state=focus] .c-seamless__field-input{color:#212121}.c-seamless[data-state=error]{border-color:#d23535;background:#fff5f5;color:#d23535;box-shadow:0 0 0 3px #d235351f}.c-seamless[data-state=error] .c-seamless__field-input{color:#d23535}.c-seamless__label{display:inline-flex;align-items:center;gap:4px;font-weight:500;white-space:nowrap;cursor:text;color:inherit}.c-seamless--compact .c-seamless__label{padding:0 4px 0 12px}.c-seamless--comfortable .c-seamless__label{padding:0 6px 0 14px}.c-seamless__icon{font-size:14px;width:14px;height:14px;color:inherit}.c-seamless__field{color:inherit}.c-seamless__op{display:inline-flex;align-items:center;color:inherit}.c-seamless__sep{color:#7a7a7a}.c-seamless__input{display:inline-flex;align-items:center;gap:4px}.c-seamless--compact .c-seamless__input{padding:0 4px}.c-seamless--comfortable .c-seamless__input{padding:0 6px}.c-seamless__dash{color:#7a7a7a}.c-seamless__field-input{appearance:none;border:0;outline:0;background:transparent;font:inherit;font-weight:500;padding:0;min-width:24px;max-width:200px}.c-seamless__x{display:inline-flex;align-items:center;justify-content:center;border:0;border-left:1px solid rgba(0,0,0,.04);background:transparent;cursor:pointer;color:inherit}.c-seamless--compact .c-seamless__x{width:26px}.c-seamless--comfortable .c-seamless__x{width:28px}.c-seamless__x mat-icon{font-size:14px;width:14px;height:14px}.c-seamless__x:hover{background:#0000000d}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
404
+ }
405
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdQueryInlineValueChip, decorators: [{
406
+ type: Component,
407
+ args: [{ selector: 'sd-query-inline-value-chip', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, MatIconModule, SdOperator], template: "<!--\r\n Seamless string/number chip \u2014 the pill IS the input. One border, three flush\r\n segments: label \u00B7 input(s) \u00B7 \u00D7. State styling driven by [data-state].\r\n-->\r\n<span\r\n class=\"c-seamless\"\r\n [attr.data-state]=\"state()\"\r\n [class.c-seamless--compact]=\"density() === 'compact'\"\r\n [class.c-seamless--comfortable]=\"density() === 'comfortable'\"\r\n (click)=\"focusFromShell($event)\">\r\n\r\n <!-- Label segment: icon + field name (+ optional operator pill) -->\r\n <span class=\"c-seamless__label\">\r\n <mat-icon class=\"c-seamless__icon\" fontSet=\"material-icons-outlined\">{{ icon() }}</mat-icon>\r\n <span class=\"c-seamless__field\">{{ field().label }}</span>\r\n @if (showOperator() && !isNoData()) {\r\n <sd-operator class=\"c-seamless__op\" [operators]=\"[operator()]\" [model]=\"operator()\" [disabled]=\"true\" />\r\n } @else if (!isNoData()) {\r\n <span class=\"c-seamless__sep\">:</span>\r\n }\r\n </span>\r\n\r\n <!-- Input segment \u2014 transparent, borderless; hidden for NULL / NOT_NULL -->\r\n @if (!isNoData()) {\r\n <span class=\"c-seamless__input\">\r\n @if (isBetween()) {\r\n <input\r\n #firstInput\r\n type=\"text\"\r\n class=\"c-seamless__field-input\"\r\n [attr.data-autoid]=\"autoId() ? autoId() + '-from' : null\"\r\n [ngModel]=\"draftFrom()\"\r\n (ngModelChange)=\"draftFrom.set($event)\"\r\n [size]=\"sizeFor(draftFrom(), phFrom())\"\r\n [placeholder]=\"phFrom()\"\r\n (focus)=\"onFocus()\"\r\n (blur)=\"commitRange()\"\r\n (keydown.enter)=\"$any($event.target).blur()\"\r\n (keydown.escape)=\"revertAndBlur($any($event.target))\" />\r\n <span class=\"c-seamless__dash\" aria-hidden=\"true\">\u2014</span>\r\n <input\r\n type=\"text\"\r\n class=\"c-seamless__field-input\"\r\n [attr.data-autoid]=\"autoId() ? autoId() + '-to' : null\"\r\n [ngModel]=\"draftTo()\"\r\n (ngModelChange)=\"draftTo.set($event)\"\r\n [size]=\"sizeFor(draftTo(), phTo())\"\r\n [placeholder]=\"phTo()\"\r\n (focus)=\"onFocus()\"\r\n (blur)=\"commitRange()\"\r\n (keydown.enter)=\"$any($event.target).blur()\"\r\n (keydown.escape)=\"revertAndBlur($any($event.target))\" />\r\n } @else {\r\n <input\r\n #firstInput\r\n type=\"text\"\r\n class=\"c-seamless__field-input\"\r\n [attr.data-autoid]=\"autoId() ?? null\"\r\n [ngModel]=\"draft()\"\r\n (ngModelChange)=\"draft.set($event)\"\r\n [size]=\"sizeFor(draft(), ph())\"\r\n [placeholder]=\"ph()\"\r\n (focus)=\"onFocus()\"\r\n (blur)=\"commitSingle()\"\r\n (keydown.enter)=\"$any($event.target).blur()\"\r\n (keydown.escape)=\"revertAndBlur($any($event.target))\" />\r\n }\r\n </span>\r\n }\r\n\r\n <!-- \u00D7 segment -->\r\n <button type=\"button\" class=\"c-seamless__x\" aria-label=\"Remove filter\" (click)=\"onRemove($event)\">\r\n <mat-icon fontSet=\"material-icons-outlined\">close</mat-icon>\r\n </button>\r\n</span>\r\n", styles: [":host{display:inline-flex}.c-seamless{display:inline-flex;align-items:stretch;border-radius:999px;overflow:hidden;font-family:inherit;font-size:13px;line-height:1;cursor:text;border:1px solid #d6d8db;background:#fff;color:#5f6368;transition:border-color .12s ease,background-color .12s ease,box-shadow .12s ease,color .12s ease}.c-seamless--compact{height:28px}.c-seamless--comfortable{height:32px}.c-seamless:hover:not([data-state=focus]):not([data-state=error]){border-color:var(--sd-primary)}.c-seamless[data-state=pending]{border-style:dashed;border-color:#d6d8db;background:#fff;color:#5f6368}.c-seamless[data-state=pending] .c-seamless__field-input{color:#212121}.c-seamless[data-state=pending] .c-seamless__field-input::placeholder{color:#7a7a7a;font-style:italic}.c-seamless[data-state=active]{border-color:var(--sd-primary-light);background:var(--sd-primary-light)}.c-seamless[data-state=active] .c-seamless__field-input{color:var(--sd-primary)}.c-seamless[data-state=focus]{border-color:var(--sd-primary);background:#fff;box-shadow:0 0 0 3px var(--sd-primary-light)}.c-seamless[data-state=focus] .c-seamless__field-input{color:#212121}.c-seamless[data-state=error]{border-color:#d23535;background:#fff5f5;color:#d23535;box-shadow:0 0 0 3px #d235351f}.c-seamless[data-state=error] .c-seamless__field-input{color:#d23535}.c-seamless__label{display:inline-flex;align-items:center;gap:4px;font-weight:500;white-space:nowrap;cursor:text;color:inherit}.c-seamless--compact .c-seamless__label{padding:0 4px 0 12px}.c-seamless--comfortable .c-seamless__label{padding:0 6px 0 14px}.c-seamless__icon{font-size:14px;width:14px;height:14px;color:inherit}.c-seamless__field{color:inherit}.c-seamless__op{display:inline-flex;align-items:center;color:inherit}.c-seamless__sep{color:#7a7a7a}.c-seamless__input{display:inline-flex;align-items:center;gap:4px}.c-seamless--compact .c-seamless__input{padding:0 4px}.c-seamless--comfortable .c-seamless__input{padding:0 6px}.c-seamless__dash{color:#7a7a7a}.c-seamless__field-input{appearance:none;border:0;outline:0;background:transparent;font:inherit;font-weight:500;padding:0;min-width:24px;max-width:200px}.c-seamless__x{display:inline-flex;align-items:center;justify-content:center;border:0;border-left:1px solid rgba(0,0,0,.04);background:transparent;cursor:pointer;color:inherit}.c-seamless--compact .c-seamless__x{width:26px}.c-seamless--comfortable .c-seamless__x{width:28px}.c-seamless__x mat-icon{font-size:14px;width:14px;height:14px}.c-seamless__x:hover{background:#0000000d}\n"] }]
408
+ }], ctorParameters: () => [] });
409
+
410
+ /* eslint-disable @typescript-eslint/no-explicit-any */
411
+ /**
412
+ * Visual block for the in-progress build chip (inline mode token builder).
413
+ *
414
+ * why: tách khỏi `<sd-query-bar>` để parent chỉ giữ state (`building()` signal +
415
+ * step transitions) — tất cả render logic của 2 step (operator picker / value
416
+ * picker) cộng với 7 nhánh field type sống tại đây.
417
+ *
418
+ * The chip has two visually distinct branches:
419
+ * - **Seamless** (`string` / `number`) — the entire pill IS an `<sd-query-inline-value-chip>`
420
+ * (its own border, autofocused input). The host's `.c-token-building` shell is skipped.
421
+ * - **Token** (`values` / `lazy-values` / `date` / `datetime` / `boolean` / other) —
422
+ * `.c-token.c-token-building` dashed pill with field label + operator (menu in
423
+ * operator step, disabled badge in value step) + the per-type picker + × cancel.
424
+ */
425
+ class SdQueryBuildChip {
426
+ /** Current build state — drives every visual branch. */
427
+ building = input.required();
428
+ /** Density preset — forwarded to the seamless chip + sizes the .c-token row. */
429
+ density = input('compact');
430
+ /** Operator set offered in the operator-step menu (parent's `allowedOperatorsFor(field)`). */
431
+ allowedOperators = input([]);
432
+ /** Whether the active operator is a multi-select (`IN`/`NOT_IN`) — drives sd-select [multiple]. */
433
+ multiple = input(false);
434
+ /** Show the operator label on the seamless chip's pill (forwarded to inline-value-chip). */
435
+ showOperator = input(false);
436
+ /**
437
+ * Prefix for `data-autoid` on the inner picker / seamless input.
438
+ * why: parent originally hard-coded `'qb-build-value'` everywhere; default preserves that
439
+ * for backward compatibility with existing e2e selectors.
440
+ */
441
+ autoId = input('qb-build-value');
442
+ // ---------------------------------------------------------------------------
443
+ // Outputs
444
+ // ---------------------------------------------------------------------------
445
+ /** Operator chosen from the operator-step menu — parent advances to value step. */
446
+ pickOperator = output();
447
+ /**
448
+ * Value committed from a non-seamless picker (sd-select / sd-date / sd-date-range /
449
+ * sd-datetime) or a boolean toggle. Parent pushes the completed chip and clears building.
450
+ */
451
+ commitValue = output();
452
+ /** × button — abandon the build (parent clears `building` signal). */
453
+ cancel = output();
454
+ /**
455
+ * Commit from the seamless (string / number) branch. Parent decides empty → cancel
456
+ * vs push complete (see `onBuildSeamlessCommit`); this just forwards the raw value.
457
+ */
458
+ seamlessCommit = output();
459
+ /** Fallback editor (boolean ng-template / other) — staged draft on every change. */
460
+ draftChange = output();
461
+ /** Fallback editor — Enter / click commits the staged draft. */
462
+ draftCommit = output();
463
+ // ---------------------------------------------------------------------------
464
+ // Internal refs — used by parent via public open*() methods
465
+ // ---------------------------------------------------------------------------
466
+ /** Operator-step menu — auto-opened by parent right after `building` enters operator step. */
467
+ opMenu = viewChild('buildOperator');
468
+ /** Value-step picker (the active bare control) — auto-opened by parent at value step. */
469
+ picker = viewChild('bPicker');
470
+ /** Open the operator menu — parent calls after rendering the operator step. */
471
+ openOperator() {
472
+ this.opMenu()?.open();
473
+ }
474
+ /** Open the value-step picker's native panel — parent calls after rendering the value step. */
475
+ openPicker() {
476
+ this.picker()?.open?.();
477
+ }
478
+ // ---------------------------------------------------------------------------
479
+ // Template helpers
480
+ // ---------------------------------------------------------------------------
481
+ /** True when the seamless branch (string/number value step) should be rendered. */
482
+ isSeamless = computed(() => {
483
+ const b = this.building();
484
+ if (b.step !== 'value')
485
+ return false;
486
+ const t = b.field.type;
487
+ return t === 'string' || t === 'number';
488
+ });
489
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdQueryBuildChip, deps: [], target: i0.ɵɵFactoryTarget.Component });
490
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: SdQueryBuildChip, isStandalone: true, selector: "sd-query-build-chip", inputs: { building: { classPropertyName: "building", publicName: "building", isSignal: true, isRequired: true, transformFunction: null }, density: { classPropertyName: "density", publicName: "density", isSignal: true, isRequired: false, transformFunction: null }, allowedOperators: { classPropertyName: "allowedOperators", publicName: "allowedOperators", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, showOperator: { classPropertyName: "showOperator", publicName: "showOperator", isSignal: true, isRequired: false, transformFunction: null }, autoId: { classPropertyName: "autoId", publicName: "autoId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { pickOperator: "pickOperator", commitValue: "commitValue", cancel: "cancel", seamlessCommit: "seamlessCommit", draftChange: "draftChange", draftCommit: "draftCommit" }, viewQueries: [{ propertyName: "opMenu", first: true, predicate: ["buildOperator"], descendants: true, isSignal: true }, { propertyName: "picker", first: true, predicate: ["bPicker"], descendants: true, isSignal: true }], ngImport: i0, template: "<!--\r\n In-progress chip (inline-mode token build flow).\r\n Seamless string/number branch keeps its own pill; everything else renders inside\r\n the dashed .c-token.c-token-building shell.\r\n-->\r\n@let _b = building();\r\n\r\n@if (isSeamless()) {\r\n <sd-query-inline-value-chip\r\n [field]=\"_b.field\"\r\n [operator]=\"$any(_b.operator)\"\r\n [value]=\"_b.value ?? null\"\r\n [density]=\"density()\"\r\n [showOperator]=\"showOperator()\"\r\n [autoId]=\"autoId()\"\r\n [autofocus]=\"true\"\r\n (valueChange)=\"seamlessCommit.emit($event)\"\r\n (remove)=\"cancel.emit()\" />\r\n} @else {\r\n <div class=\"c-token c-token-building\">\r\n <span class=\"c-token-field\">{{ _b.field.label }}</span>\r\n\r\n @if (_b.step === 'operator') {\r\n <sd-operator\r\n #buildOperator\r\n class=\"c-token-op\"\r\n [operators]=\"allowedOperators()\"\r\n [model]=\"_b.operator\"\r\n (modelChange)=\"pickOperator.emit($any($event))\" />\r\n } @else {\r\n <!-- why: badge readonly cho operator \u0111\u00E3 ch\u1ECDn \u2014 v\u1EABn render <sd-operator>\r\n \u0111\u1EC3 gi\u1EEF th\u1ECB gi\u00E1c gi\u1ED1ng chip ho\u00E0n th\u00E0nh (icon + tooltip). -->\r\n <sd-operator\r\n class=\"c-token-op\"\r\n [operators]=\"[$any(_b.operator)]\"\r\n [model]=\"$any(_b.operator)\"\r\n [disabled]=\"true\" />\r\n\r\n @if (_b.field.type === 'values' || _b.field.type === 'lazy-values') {\r\n @let _bopt = $any(_b.field).option;\r\n <span class=\"c-token-value c-token-value-edit\">\r\n <sd-select #bPicker bare size=\"sm\" minWidthPanel=\"300px\"\r\n [autoId]=\"autoId()\"\r\n [items]=\"_b.field.type === 'lazy-values' ? $any(_bopt).search : $any(_bopt.items)\"\r\n [valueField]=\"_bopt.valueField\"\r\n [displayField]=\"_bopt.displayField\"\r\n [multiple]=\"multiple()\"\r\n [model]=\"$any(_b.value)\"\r\n (sdChange)=\"commitValue.emit($event)\">\r\n <!-- why: build chip multi-select c\u0169ng c\u1EA7n \"head +N\" \u0111\u1EC3 g\u1ECDn (gi\u1ED1ng completed chip). -->\r\n <ng-template #sdValue let-selectedItems=\"selectedItems\">\r\n @if ((selectedItems?.length ?? 0) > 1) {\r\n <span class=\"c-token-multi-head\">{{ $any(selectedItems)[0][_bopt.displayField] }}</span> +{{ selectedItems.length - 1 }}\r\n } @else if ((selectedItems?.length ?? 0) === 1) {\r\n <span class=\"c-token-multi-head\">{{ $any(selectedItems)[0][_bopt.displayField] }}</span>\r\n }\r\n </ng-template>\r\n </sd-select>\r\n </span>\r\n } @else if (_b.field.type === 'date') {\r\n <span class=\"c-token-value c-token-value-edit\">\r\n @if (_b.operator === 'BETWEEN') {\r\n <!-- why: 1 sd-date-range thay 2 sd-date \u2014 g\u1ECDn h\u01A1n, h\u00E0nh vi th\u1ED1ng nh\u1EA5t v\u1EDBi completed chip. -->\r\n <sd-date-range #bPicker bare size=\"sm\"\r\n [autoId]=\"autoId()\"\r\n [model]=\"$any(_b.value)\"\r\n (sdChange)=\"commitValue.emit($event)\"></sd-date-range>\r\n } @else {\r\n <sd-date #bPicker bare size=\"sm\"\r\n [autoId]=\"autoId()\"\r\n [model]=\"$any(_b.value)\"\r\n (sdChange)=\"commitValue.emit($event)\"></sd-date>\r\n }\r\n </span>\r\n } @else if (_b.field.type === 'datetime') {\r\n <span class=\"c-token-value c-token-value-edit\">\r\n @if (_b.operator === 'BETWEEN') {\r\n <!-- why: datetime BETWEEN downgrade v\u1EC1 date-range (m\u1EA5t time precision; \u0111\u00E3 th\u1ED1ng nh\u1EA5t). -->\r\n <sd-date-range #bPicker bare size=\"sm\"\r\n [autoId]=\"autoId()\"\r\n [model]=\"$any(_b.value)\"\r\n (sdChange)=\"commitValue.emit($event)\"></sd-date-range>\r\n } @else {\r\n <sd-datetime #bPicker bare size=\"sm\"\r\n [autoId]=\"autoId()\"\r\n [model]=\"$any(_b.value)\"\r\n (sdChange)=\"commitValue.emit($event)\"></sd-datetime>\r\n }\r\n </span>\r\n } @else if (_b.field.type === 'boolean') {\r\n <!-- why: native button thay sd-button (tr\u00E1nh mat-button baseline 36px g\u00E2y tr\u00E0n).\r\n Pill nh\u1ECF v\u1EBD b\u1EB1ng CSS scoped trong chip \u2014 gi\u1EEF semantic colour qua class\r\n .c-bool-true / .c-bool-false. -->\r\n <span class=\"c-token-value c-token-value-edit c-bool-toggle\">\r\n <button\r\n type=\"button\"\r\n class=\"c-bool-btn c-bool-true\"\r\n [class.c-bool-active]=\"_b.value === true\"\r\n [attr.data-autoId]=\"autoId() + '-true'\"\r\n (click)=\"commitValue.emit(true)\">\r\n {{ $any(_b.field).trueLabel || 'C\u00F3' }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"c-bool-btn c-bool-false\"\r\n [class.c-bool-active]=\"_b.value === false\"\r\n [attr.data-autoId]=\"autoId() + '-false'\"\r\n (click)=\"commitValue.emit(false)\">\r\n {{ $any(_b.field).falseLabel || 'Kh\u00F4ng' }}\r\n </button>\r\n </span>\r\n } @else {\r\n <!-- Fallback (e.g. unforeseen type) \u2014 generic sd-input wired via draft outputs. -->\r\n <span class=\"c-token-value c-token-value-edit\">\r\n <sd-input size=\"sm\"\r\n [autoId]=\"autoId()\"\r\n [model]=\"$any(_b.value)\"\r\n (sdChange)=\"draftChange.emit($event)\"\r\n (keyupEnter)=\"draftCommit.emit()\"></sd-input>\r\n </span>\r\n }\r\n }\r\n\r\n <button type=\"button\" class=\"c-token-remove\" aria-label=\"Cancel\" (click)=\"cancel.emit()\">\r\n <mat-icon fontSet=\"material-icons-outlined\">close</mat-icon>\r\n </button>\r\n </div>\r\n}\r\n", styles: [":host{display:contents}.c-token{display:inline-flex;align-items:center;gap:4px;padding:0 0 0 12px;border:1px solid var(--sd-primary-light);border-radius:999px;background:var(--sd-primary-light);font-size:13px;color:#5f6368;white-space:nowrap;overflow:hidden}:host-context(.c-density-compact) .c-token{height:28px}:host-context(.c-density-comfortable) .c-token{height:32px}.c-token-field{font-weight:500;line-height:1}.c-token-op{display:inline-flex;align-items:center;color:inherit}.c-token-op ::ng-deep .c-op-trigger{padding:0;color:inherit}.c-token-op ::ng-deep .c-op-icon svg{width:14px;height:14px;display:block}.c-token-value{border:none;background:transparent;padding:0;font:inherit;font-weight:500;color:var(--sd-primary)}.c-token-value-edit{display:inline-flex;align-items:center;gap:4px;max-width:240px}.c-token-value-edit sd-input,.c-token-value-edit sd-input-number{display:inline-flex;max-width:140px}.c-token-value-edit sd-date-range{display:inline-flex;max-width:220px;min-width:0;flex:0 1 auto}.c-token-value-edit sd-select,.c-token-value-edit sd-date,.c-token-value-edit sd-datetime{display:inline-flex;align-items:center;line-height:1}.c-token ::ng-deep .mat-mdc-form-field{line-height:1}.c-token ::ng-deep .mat-mdc-text-field-wrapper,.c-token ::ng-deep .mat-mdc-form-field-flex{padding:0;min-height:0;background:transparent}.c-token ::ng-deep .mat-mdc-form-field-infix{padding:0;min-height:0;width:auto;border:0;display:inline-flex;align-items:center}.c-token ::ng-deep .mat-mdc-select-value,.c-token ::ng-deep .mat-mdc-input-element,.c-token ::ng-deep input{line-height:1;font-size:13px;padding:0;height:auto;color:inherit}.c-token ::ng-deep .mat-mdc-select-value{display:inline-flex;align-items:center;min-width:0;max-width:200px;overflow:hidden;text-overflow:ellipsis;color:var(--sd-primary);font-weight:500}.c-token ::ng-deep .sd-suffix-icon{display:none}.c-token-multi-head{display:inline-block;max-width:140px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom}.c-token-building{border-style:dashed}.c-bool-toggle{display:inline-flex;align-items:center;gap:4px}.c-bool-btn{display:inline-flex;align-items:center;justify-content:center;height:18px;min-width:0;padding:0 8px;border:1px solid currentColor;border-radius:999px;background:transparent;font:inherit;font-size:11px;font-weight:500;line-height:1;cursor:pointer;white-space:nowrap;-webkit-user-select:none;user-select:none;transition:background .12s ease,color .12s ease}:host-context(.c-density-comfortable) .c-bool-btn{height:20px;padding:0 10px;font-size:12px}.c-bool-true{color:var(--sd-success)}.c-bool-false{color:var(--sd-warning)}.c-bool-active.c-bool-true{background:var(--sd-success);color:#fff;border-color:var(--sd-success)}.c-bool-active.c-bool-false{background:var(--sd-warning);color:#fff;border-color:var(--sd-warning)}.c-bool-btn:hover:not(.c-bool-active).c-bool-true{background:var(--sd-success-light)}.c-bool-btn:hover:not(.c-bool-active).c-bool-false{background:var(--sd-warning-light)}.c-token-remove{align-self:stretch;display:inline-flex;align-items:center;justify-content:center;border:none;border-left:1px solid rgba(0,0,0,.04);border-radius:0;background:transparent;cursor:pointer;color:inherit}:host-context(.c-density-compact) .c-token-remove{width:26px}:host-context(.c-density-comfortable) .c-token-remove{width:28px}.c-token-remove mat-icon{font-size:14px;width:14px;height:14px}.c-token-remove:hover{background:#0000000d}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.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: SdDate, selector: "sd-date", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "required", "disabled", "viewed", "bare", "inlineError", "hyperlink", "appearance", "floatLabel", "min", "minDate", "max", "maxDate", "model"], outputs: ["modelChange", "sdChange", "sdFocus"] }, { kind: "component", type: SdDateRange, selector: "sd-date-range", inputs: ["autoId", "name", "size", "form", "label", "helperText", "hideInlineError", "required", "disabled", "bare", "viewed", "appearance", "floatLabel", "min", "max", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "component", type: SdDatetime, selector: "sd-datetime", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "required", "disabled", "viewed", "showSeconds", "bare", "inlineError", "hyperlink", "appearance", "floatLabel", "min", "minDate", "max", "maxDate", "model"], outputs: ["modelChange", "sdChange", "sdFocus"] }, { 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", "sdFocusForceBlur"] }, { 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", "bare", "validator", "inlineError", "appearance", "floatLabel", "items", "model"], outputs: ["modelChange", "sdChange", "sdSelection"] }, { kind: "component", type: SdQueryInlineValueChip, selector: "sd-query-inline-value-chip", inputs: ["field", "operator", "value", "density", "showOperator", "autoId", "autofocus"], outputs: ["valueChange", "remove"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
491
+ }
492
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdQueryBuildChip, decorators: [{
493
+ type: Component,
494
+ args: [{ selector: 'sd-query-build-chip', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [
495
+ CommonModule,
496
+ MatIconModule,
497
+ SdOperator,
498
+ SdDate,
499
+ SdDateRange,
500
+ SdDatetime,
501
+ SdInput,
502
+ SdInputNumber,
503
+ SdSelect,
504
+ SdQueryInlineValueChip,
505
+ ], template: "<!--\r\n In-progress chip (inline-mode token build flow).\r\n Seamless string/number branch keeps its own pill; everything else renders inside\r\n the dashed .c-token.c-token-building shell.\r\n-->\r\n@let _b = building();\r\n\r\n@if (isSeamless()) {\r\n <sd-query-inline-value-chip\r\n [field]=\"_b.field\"\r\n [operator]=\"$any(_b.operator)\"\r\n [value]=\"_b.value ?? null\"\r\n [density]=\"density()\"\r\n [showOperator]=\"showOperator()\"\r\n [autoId]=\"autoId()\"\r\n [autofocus]=\"true\"\r\n (valueChange)=\"seamlessCommit.emit($event)\"\r\n (remove)=\"cancel.emit()\" />\r\n} @else {\r\n <div class=\"c-token c-token-building\">\r\n <span class=\"c-token-field\">{{ _b.field.label }}</span>\r\n\r\n @if (_b.step === 'operator') {\r\n <sd-operator\r\n #buildOperator\r\n class=\"c-token-op\"\r\n [operators]=\"allowedOperators()\"\r\n [model]=\"_b.operator\"\r\n (modelChange)=\"pickOperator.emit($any($event))\" />\r\n } @else {\r\n <!-- why: badge readonly cho operator \u0111\u00E3 ch\u1ECDn \u2014 v\u1EABn render <sd-operator>\r\n \u0111\u1EC3 gi\u1EEF th\u1ECB gi\u00E1c gi\u1ED1ng chip ho\u00E0n th\u00E0nh (icon + tooltip). -->\r\n <sd-operator\r\n class=\"c-token-op\"\r\n [operators]=\"[$any(_b.operator)]\"\r\n [model]=\"$any(_b.operator)\"\r\n [disabled]=\"true\" />\r\n\r\n @if (_b.field.type === 'values' || _b.field.type === 'lazy-values') {\r\n @let _bopt = $any(_b.field).option;\r\n <span class=\"c-token-value c-token-value-edit\">\r\n <sd-select #bPicker bare size=\"sm\" minWidthPanel=\"300px\"\r\n [autoId]=\"autoId()\"\r\n [items]=\"_b.field.type === 'lazy-values' ? $any(_bopt).search : $any(_bopt.items)\"\r\n [valueField]=\"_bopt.valueField\"\r\n [displayField]=\"_bopt.displayField\"\r\n [multiple]=\"multiple()\"\r\n [model]=\"$any(_b.value)\"\r\n (sdChange)=\"commitValue.emit($event)\">\r\n <!-- why: build chip multi-select c\u0169ng c\u1EA7n \"head +N\" \u0111\u1EC3 g\u1ECDn (gi\u1ED1ng completed chip). -->\r\n <ng-template #sdValue let-selectedItems=\"selectedItems\">\r\n @if ((selectedItems?.length ?? 0) > 1) {\r\n <span class=\"c-token-multi-head\">{{ $any(selectedItems)[0][_bopt.displayField] }}</span> +{{ selectedItems.length - 1 }}\r\n } @else if ((selectedItems?.length ?? 0) === 1) {\r\n <span class=\"c-token-multi-head\">{{ $any(selectedItems)[0][_bopt.displayField] }}</span>\r\n }\r\n </ng-template>\r\n </sd-select>\r\n </span>\r\n } @else if (_b.field.type === 'date') {\r\n <span class=\"c-token-value c-token-value-edit\">\r\n @if (_b.operator === 'BETWEEN') {\r\n <!-- why: 1 sd-date-range thay 2 sd-date \u2014 g\u1ECDn h\u01A1n, h\u00E0nh vi th\u1ED1ng nh\u1EA5t v\u1EDBi completed chip. -->\r\n <sd-date-range #bPicker bare size=\"sm\"\r\n [autoId]=\"autoId()\"\r\n [model]=\"$any(_b.value)\"\r\n (sdChange)=\"commitValue.emit($event)\"></sd-date-range>\r\n } @else {\r\n <sd-date #bPicker bare size=\"sm\"\r\n [autoId]=\"autoId()\"\r\n [model]=\"$any(_b.value)\"\r\n (sdChange)=\"commitValue.emit($event)\"></sd-date>\r\n }\r\n </span>\r\n } @else if (_b.field.type === 'datetime') {\r\n <span class=\"c-token-value c-token-value-edit\">\r\n @if (_b.operator === 'BETWEEN') {\r\n <!-- why: datetime BETWEEN downgrade v\u1EC1 date-range (m\u1EA5t time precision; \u0111\u00E3 th\u1ED1ng nh\u1EA5t). -->\r\n <sd-date-range #bPicker bare size=\"sm\"\r\n [autoId]=\"autoId()\"\r\n [model]=\"$any(_b.value)\"\r\n (sdChange)=\"commitValue.emit($event)\"></sd-date-range>\r\n } @else {\r\n <sd-datetime #bPicker bare size=\"sm\"\r\n [autoId]=\"autoId()\"\r\n [model]=\"$any(_b.value)\"\r\n (sdChange)=\"commitValue.emit($event)\"></sd-datetime>\r\n }\r\n </span>\r\n } @else if (_b.field.type === 'boolean') {\r\n <!-- why: native button thay sd-button (tr\u00E1nh mat-button baseline 36px g\u00E2y tr\u00E0n).\r\n Pill nh\u1ECF v\u1EBD b\u1EB1ng CSS scoped trong chip \u2014 gi\u1EEF semantic colour qua class\r\n .c-bool-true / .c-bool-false. -->\r\n <span class=\"c-token-value c-token-value-edit c-bool-toggle\">\r\n <button\r\n type=\"button\"\r\n class=\"c-bool-btn c-bool-true\"\r\n [class.c-bool-active]=\"_b.value === true\"\r\n [attr.data-autoId]=\"autoId() + '-true'\"\r\n (click)=\"commitValue.emit(true)\">\r\n {{ $any(_b.field).trueLabel || 'C\u00F3' }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"c-bool-btn c-bool-false\"\r\n [class.c-bool-active]=\"_b.value === false\"\r\n [attr.data-autoId]=\"autoId() + '-false'\"\r\n (click)=\"commitValue.emit(false)\">\r\n {{ $any(_b.field).falseLabel || 'Kh\u00F4ng' }}\r\n </button>\r\n </span>\r\n } @else {\r\n <!-- Fallback (e.g. unforeseen type) \u2014 generic sd-input wired via draft outputs. -->\r\n <span class=\"c-token-value c-token-value-edit\">\r\n <sd-input size=\"sm\"\r\n [autoId]=\"autoId()\"\r\n [model]=\"$any(_b.value)\"\r\n (sdChange)=\"draftChange.emit($event)\"\r\n (keyupEnter)=\"draftCommit.emit()\"></sd-input>\r\n </span>\r\n }\r\n }\r\n\r\n <button type=\"button\" class=\"c-token-remove\" aria-label=\"Cancel\" (click)=\"cancel.emit()\">\r\n <mat-icon fontSet=\"material-icons-outlined\">close</mat-icon>\r\n </button>\r\n </div>\r\n}\r\n", styles: [":host{display:contents}.c-token{display:inline-flex;align-items:center;gap:4px;padding:0 0 0 12px;border:1px solid var(--sd-primary-light);border-radius:999px;background:var(--sd-primary-light);font-size:13px;color:#5f6368;white-space:nowrap;overflow:hidden}:host-context(.c-density-compact) .c-token{height:28px}:host-context(.c-density-comfortable) .c-token{height:32px}.c-token-field{font-weight:500;line-height:1}.c-token-op{display:inline-flex;align-items:center;color:inherit}.c-token-op ::ng-deep .c-op-trigger{padding:0;color:inherit}.c-token-op ::ng-deep .c-op-icon svg{width:14px;height:14px;display:block}.c-token-value{border:none;background:transparent;padding:0;font:inherit;font-weight:500;color:var(--sd-primary)}.c-token-value-edit{display:inline-flex;align-items:center;gap:4px;max-width:240px}.c-token-value-edit sd-input,.c-token-value-edit sd-input-number{display:inline-flex;max-width:140px}.c-token-value-edit sd-date-range{display:inline-flex;max-width:220px;min-width:0;flex:0 1 auto}.c-token-value-edit sd-select,.c-token-value-edit sd-date,.c-token-value-edit sd-datetime{display:inline-flex;align-items:center;line-height:1}.c-token ::ng-deep .mat-mdc-form-field{line-height:1}.c-token ::ng-deep .mat-mdc-text-field-wrapper,.c-token ::ng-deep .mat-mdc-form-field-flex{padding:0;min-height:0;background:transparent}.c-token ::ng-deep .mat-mdc-form-field-infix{padding:0;min-height:0;width:auto;border:0;display:inline-flex;align-items:center}.c-token ::ng-deep .mat-mdc-select-value,.c-token ::ng-deep .mat-mdc-input-element,.c-token ::ng-deep input{line-height:1;font-size:13px;padding:0;height:auto;color:inherit}.c-token ::ng-deep .mat-mdc-select-value{display:inline-flex;align-items:center;min-width:0;max-width:200px;overflow:hidden;text-overflow:ellipsis;color:var(--sd-primary);font-weight:500}.c-token ::ng-deep .sd-suffix-icon{display:none}.c-token-multi-head{display:inline-block;max-width:140px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom}.c-token-building{border-style:dashed}.c-bool-toggle{display:inline-flex;align-items:center;gap:4px}.c-bool-btn{display:inline-flex;align-items:center;justify-content:center;height:18px;min-width:0;padding:0 8px;border:1px solid currentColor;border-radius:999px;background:transparent;font:inherit;font-size:11px;font-weight:500;line-height:1;cursor:pointer;white-space:nowrap;-webkit-user-select:none;user-select:none;transition:background .12s ease,color .12s ease}:host-context(.c-density-comfortable) .c-bool-btn{height:20px;padding:0 10px;font-size:12px}.c-bool-true{color:var(--sd-success)}.c-bool-false{color:var(--sd-warning)}.c-bool-active.c-bool-true{background:var(--sd-success);color:#fff;border-color:var(--sd-success)}.c-bool-active.c-bool-false{background:var(--sd-warning);color:#fff;border-color:var(--sd-warning)}.c-bool-btn:hover:not(.c-bool-active).c-bool-true{background:var(--sd-success-light)}.c-bool-btn:hover:not(.c-bool-active).c-bool-false{background:var(--sd-warning-light)}.c-token-remove{align-self:stretch;display:inline-flex;align-items:center;justify-content:center;border:none;border-left:1px solid rgba(0,0,0,.04);border-radius:0;background:transparent;cursor:pointer;color:inherit}:host-context(.c-density-compact) .c-token-remove{width:26px}:host-context(.c-density-comfortable) .c-token-remove{width:28px}.c-token-remove mat-icon{font-size:14px;width:14px;height:14px}.c-token-remove:hover{background:#0000000d}\n"] }]
506
+ }] });
507
+
508
+ /* eslint-disable @typescript-eslint/no-explicit-any */
509
+ /**
510
+ * Popover-mode chip editor — the entire `<mat-menu>` that opens when a popover-mode
511
+ * chip is clicked. Owns ALL operator+value staging signals and async option loading.
512
+ *
513
+ * why: tách khỏi `<sd-query-bar>` để parent chỉ giữ `editingIndex` (chip nào đang
514
+ * mở) + nhận `(commit)` khi popover đóng. Mỗi lần parent gọi `seed(filter, field)`
515
+ * trước khi mở chip popover, child reset staging signals + kích option loading.
516
+ *
517
+ * Public surface (template ref `#cp`):
518
+ * - `cp.menu()` — the `<mat-menu>` to feed `[menu]` of `<sd-query-popover-chip>`
519
+ * - `cp.seed(filter, field)` — parent reseeds staging before opening
520
+ * - `(commit)` output — emitted on mat-menu close with staged Filter
521
+ * - `(swapField)` output — emitted when the nested field switcher picks a new field
522
+ */
523
+ class SdQueryChipPopover {
524
+ // ---------------------------------------------------------------------------
525
+ // Inputs
526
+ // ---------------------------------------------------------------------------
527
+ /** Resolved field of the currently editing chip — parent passes from `fieldByKey()`. */
528
+ field = input(undefined);
529
+ /** The chip's current Filter — used by `seed()` for initial staging values. */
530
+ filter = input(undefined);
531
+ /** Optional chip index — embedded in the composed autoIds. */
532
+ chipIndex = input(null);
533
+ /** Prefix for `data-autoId` on inner controls. */
534
+ autoIdBase = input('qb');
535
+ /** MatMenu for the field switcher — parent passes its `<sd-query-field-picker>` menu. */
536
+ switchPickerMenu = input(undefined);
537
+ // ---------------------------------------------------------------------------
538
+ // Outputs
539
+ // ---------------------------------------------------------------------------
540
+ /** Emitted when mat-menu closes — parent splices `next` into filters[idx]. */
541
+ commit = output();
542
+ /** Emitted when nested field-switcher picks a new field — parent calls changeFilterField. */
543
+ swapField = output();
544
+ // ---------------------------------------------------------------------------
545
+ // Staging signals — uncommitted edits, committed on menu close.
546
+ // ---------------------------------------------------------------------------
547
+ /** Staged operator. */
548
+ editingOperator = signal('EQUAL');
549
+ /** Staged value (shape depends on field type + operator). */
550
+ editingValue = signal(null);
551
+ /** Resolved option list for values / lazy-values. */
552
+ editingOptions = signal([]);
553
+ /** Loading flag for async option fetch. */
554
+ editingOptionsLoading = signal(false);
555
+ // ---------------------------------------------------------------------------
556
+ // Derived state
557
+ // ---------------------------------------------------------------------------
558
+ allowedOperators = computed(() => {
559
+ const f = this.field();
560
+ return f ? sdQueryAllowedOperators(f) : [];
561
+ });
562
+ showOperatorSelector = computed(() => {
563
+ const f = this.field();
564
+ return f ? sdQueryShowOperatorSelector(f) : false;
565
+ });
566
+ /** Whether the staged operator is multi-select (IN/NOT_IN). */
567
+ multiple(op) {
568
+ return SD_QUERY_MULTI_OPERATORS.includes(op ?? this.editingOperator());
569
+ }
570
+ /** Whether the staged operator carries no data (NULL/NOT_NULL). */
571
+ isNoDataOperator(op) {
572
+ return SD_QUERY_NO_DATA_OPERATORS.includes(op);
573
+ }
574
+ /** Compose an autoId for an inner control (`<base>-chip<idx>-<role>`). */
575
+ chipAutoId(role) {
576
+ const base = this.autoIdBase() ?? 'qb';
577
+ const idx = this.chipIndex();
578
+ return `${base}-chip${idx ?? 'x'}-${role}`;
579
+ }
580
+ iconFor = sdQueryFieldIcon;
581
+ // ---------------------------------------------------------------------------
582
+ // mat-menu instance — parent reads via `cp.menu()`
583
+ // ---------------------------------------------------------------------------
584
+ menu = viewChild('menu');
585
+ // ---------------------------------------------------------------------------
586
+ // Seed — parent calls before opening the popover so staging signals are
587
+ // primed from the current filter + the option list is loaded.
588
+ // ---------------------------------------------------------------------------
589
+ /** Reseed staging from a new filter/field pair — call before opening the menu. */
590
+ seed(filter, field) {
591
+ if (!filter || !field) {
592
+ this.editingOperator.set('EQUAL');
593
+ this.editingValue.set(null);
594
+ this.editingOptions.set([]);
595
+ this.editingOptionsLoading.set(false);
596
+ return;
597
+ }
598
+ this.editingOperator.set(filter.operator);
599
+ this.editingValue.set(filter.data ?? null);
600
+ if (field.type === 'values') {
601
+ this.#loadValuesOptions(field);
602
+ }
603
+ else if (field.type === 'lazy-values') {
604
+ this.#loadLazyOptions(field);
605
+ }
606
+ else {
607
+ this.editingOptions.set([]);
608
+ this.editingOptionsLoading.set(false);
609
+ }
610
+ }
611
+ // ---------------------------------------------------------------------------
612
+ // Operator + value mutators
613
+ // ---------------------------------------------------------------------------
614
+ /** Called on operator change in the popover header — reshapes value when family changes. */
615
+ onEditingOperatorChange(next) {
616
+ const prev = this.editingOperator();
617
+ this.editingOperator.set(next);
618
+ if (SD_QUERY_NO_DATA_OPERATORS.includes(next)) {
619
+ this.editingValue.set(null);
620
+ return;
621
+ }
622
+ if (next === 'BETWEEN') {
623
+ const v = this.editingValue();
624
+ if (v && typeof v === 'object' && 'from' in v)
625
+ return;
626
+ this.editingValue.set({ from: null, to: null });
627
+ return;
628
+ }
629
+ const goingToMulti = SD_QUERY_MULTI_OPERATORS.includes(next);
630
+ const wasMulti = SD_QUERY_MULTI_OPERATORS.includes(prev);
631
+ if (goingToMulti && !wasMulti) {
632
+ const v = this.editingValue();
633
+ this.editingValue.set(v == null ? [] : [v]);
634
+ }
635
+ else if (!goingToMulti && wasMulti) {
636
+ const v = this.editingValue();
637
+ this.editingValue.set(Array.isArray(v) && v.length > 0 ? v[0] : null);
638
+ }
639
+ }
640
+ /** Single-value input change. */
641
+ onEditingValueInput(value) {
642
+ this.editingValue.set(value);
643
+ }
644
+ /** BETWEEN range — mutate only `.from`. */
645
+ onEditingRangeFrom(value) {
646
+ const v = this.editingValue() ?? {};
647
+ this.editingValue.set({ ...v, from: value });
648
+ }
649
+ /** BETWEEN range — mutate only `.to`. */
650
+ onEditingRangeTo(value) {
651
+ const v = this.editingValue() ?? {};
652
+ this.editingValue.set({ ...v, to: value });
653
+ }
654
+ /** Toggle a value in the staged multi-select array. */
655
+ toggleEditingMultiValue(value) {
656
+ const arr = Array.isArray(this.editingValue()) ? [...this.editingValue()] : [];
657
+ const idx = arr.indexOf(value);
658
+ if (idx >= 0)
659
+ arr.splice(idx, 1);
660
+ else
661
+ arr.push(value);
662
+ this.editingValue.set(arr);
663
+ }
664
+ isEditingMultiSelected(value) {
665
+ const v = this.editingValue();
666
+ return Array.isArray(v) && v.includes(value);
667
+ }
668
+ // ---------------------------------------------------------------------------
669
+ // Menu lifecycle — close = commit
670
+ // ---------------------------------------------------------------------------
671
+ /**
672
+ * mat-menu's (closed) handler — compose the next Filter from staging signals and
673
+ * emit (commit). Parent splices into its filters[] and nulls editingIndex.
674
+ */
675
+ onMenuClosed() {
676
+ const f = this.filter();
677
+ if (!f)
678
+ return;
679
+ const next = {
680
+ field: f.field,
681
+ operator: this.editingOperator(),
682
+ data: this.editingValue(),
683
+ };
684
+ this.commit.emit(next);
685
+ }
686
+ // ---------------------------------------------------------------------------
687
+ // Async option loaders — same logic as the parent's private methods.
688
+ // ---------------------------------------------------------------------------
689
+ #loadValuesOptions(field) {
690
+ const opt = field.option;
691
+ const items = opt?.items;
692
+ if (Array.isArray(items)) {
693
+ this.editingOptions.set(items);
694
+ this.editingOptionsLoading.set(false);
695
+ return;
696
+ }
697
+ if (isSignal(items)) {
698
+ this.editingOptions.set(items());
699
+ this.editingOptionsLoading.set(false);
700
+ return;
701
+ }
702
+ if (typeof items === 'function') {
703
+ this.editingOptionsLoading.set(true);
704
+ Promise.resolve(items())
705
+ .then((arr) => this.editingOptions.set(arr ?? []))
706
+ .finally(() => this.editingOptionsLoading.set(false));
707
+ }
708
+ }
709
+ #loadLazyOptions(field, search = '') {
710
+ const opt = field.option;
711
+ if (!opt?.search)
712
+ return;
713
+ this.editingOptionsLoading.set(true);
714
+ const result = opt.search({ type: 'SEARCH', searchText: search });
715
+ if (isObservable(result)) {
716
+ result.subscribe({
717
+ next: (arr) => this.editingOptions.set(arr ?? []),
718
+ complete: () => this.editingOptionsLoading.set(false),
719
+ error: () => this.editingOptionsLoading.set(false),
720
+ });
721
+ }
722
+ else {
723
+ Promise.resolve(result)
724
+ .then((arr) => this.editingOptions.set(arr ?? []))
725
+ .finally(() => this.editingOptionsLoading.set(false));
726
+ }
727
+ }
728
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdQueryChipPopover, deps: [], target: i0.ɵɵFactoryTarget.Component });
729
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: SdQueryChipPopover, isStandalone: true, selector: "sd-query-chip-popover", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: false, transformFunction: null }, filter: { classPropertyName: "filter", publicName: "filter", isSignal: true, isRequired: false, transformFunction: null }, chipIndex: { classPropertyName: "chipIndex", publicName: "chipIndex", isSignal: true, isRequired: false, transformFunction: null }, autoIdBase: { classPropertyName: "autoIdBase", publicName: "autoIdBase", isSignal: true, isRequired: false, transformFunction: null }, switchPickerMenu: { classPropertyName: "switchPickerMenu", publicName: "switchPickerMenu", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { commit: "commit", swapField: "swapField" }, viewQueries: [{ propertyName: "menu", first: true, predicate: ["menu"], descendants: true, isSignal: true }], ngImport: i0, template: "<!--\r\n Chip popover \u2014 operator + value editor for the popover-mode chip.\r\n Visual layout mirrors the previous inline `<mat-menu #chipPopover>` in query-bar.\r\n Body branches on operator + field.type \u2014 8 paths preserved exactly.\r\n-->\r\n<mat-menu #menu=\"matMenu\" class=\"c-chip-popover\" xPosition=\"after\" yPosition=\"below\" (closed)=\"onMenuClosed()\">\r\n <ng-template matMenuContent>\r\n @let _field = field();\r\n @let _op = editingOperator();\r\n @let _val = editingValue();\r\n @let _options = editingOptions();\r\n @let _loading = editingOptionsLoading();\r\n @let _switchMenu = switchPickerMenu();\r\n\r\n @if (_field) {\r\n <div class=\"c-pop-header\" (click)=\"$event.stopPropagation()\">\r\n <mat-icon fontSet=\"material-icons-outlined\">{{ iconFor(_field) }}</mat-icon>\r\n <!-- why: nested mat-menu trigger to swap the chip's field \u2014 parent provides\r\n the field-picker MatMenu via [switchPickerMenu]. -->\r\n @if (_switchMenu) {\r\n <button\r\n type=\"button\"\r\n class=\"c-pop-header-field\"\r\n [matMenuTriggerFor]=\"_switchMenu\"\r\n matTooltip=\"\u0110\u1ED5i field\">\r\n <span>{{ _field.label }}</span>\r\n <mat-icon fontSet=\"material-icons-outlined\">unfold_more</mat-icon>\r\n </button>\r\n } @else {\r\n <span class=\"c-pop-header-field\">{{ _field.label }}</span>\r\n }\r\n <!-- Operator selector (right-aligned). Disabled when field has \u22641 operator. -->\r\n <sd-operator\r\n class=\"c-pop-header-operator\"\r\n [autoId]=\"chipAutoId('operator')\"\r\n [operators]=\"allowedOperators()\"\r\n [model]=\"_op\"\r\n [disabled]=\"!showOperatorSelector()\"\r\n (modelChange)=\"onEditingOperatorChange($any($event))\" />\r\n </div>\r\n\r\n <div class=\"c-pop-body\" (click)=\"$event.stopPropagation()\">\r\n @if (!isNoDataOperator(_op)) {\r\n @if (_op === 'BETWEEN') {\r\n @if (_field.type === 'date') {\r\n <div class=\"c-pop-between\">\r\n <sd-date size=\"sm\" hideInlineError\r\n [autoId]=\"chipAutoId('value-from')\"\r\n placeholder=\"T\u1EEB\"\r\n [model]=\"$any(_val)?.from\"\r\n (sdChange)=\"onEditingRangeFrom($event)\">\r\n </sd-date>\r\n <sd-date size=\"sm\" hideInlineError\r\n [autoId]=\"chipAutoId('value-to')\"\r\n placeholder=\"\u0110\u1EBFn\"\r\n [model]=\"$any(_val)?.to\"\r\n (sdChange)=\"onEditingRangeTo($event)\">\r\n </sd-date>\r\n </div>\r\n } @else if (_field.type === 'datetime') {\r\n <div class=\"c-pop-between\">\r\n <sd-datetime size=\"sm\" hideInlineError\r\n [autoId]=\"chipAutoId('value-from')\"\r\n placeholder=\"T\u1EEB\"\r\n [model]=\"$any(_val)?.from\"\r\n (sdChange)=\"onEditingRangeFrom($event)\">\r\n </sd-datetime>\r\n <sd-datetime size=\"sm\" hideInlineError\r\n [autoId]=\"chipAutoId('value-to')\"\r\n placeholder=\"\u0110\u1EBFn\"\r\n [model]=\"$any(_val)?.to\"\r\n (sdChange)=\"onEditingRangeTo($event)\">\r\n </sd-datetime>\r\n </div>\r\n } @else {\r\n <div class=\"c-pop-between\">\r\n <sd-input-number size=\"sm\" hideInlineError\r\n [autoId]=\"chipAutoId('value-from')\"\r\n placeholder=\"T\u1EEB\"\r\n [model]=\"$any(_val)?.from\"\r\n (sdChange)=\"onEditingRangeFrom($event)\">\r\n </sd-input-number>\r\n <sd-input-number size=\"sm\" hideInlineError\r\n [autoId]=\"chipAutoId('value-to')\"\r\n placeholder=\"\u0110\u1EBFn\"\r\n [model]=\"$any(_val)?.to\"\r\n (sdChange)=\"onEditingRangeTo($event)\">\r\n </sd-input-number>\r\n </div>\r\n }\r\n } @else if (_field.type === 'boolean') {\r\n <div class=\"c-pop-boolean c-bool-toggle\">\r\n <sd-button\r\n [autoId]=\"chipAutoId('value-true')\"\r\n [type]=\"_val === true ? 'fill' : 'outline'\"\r\n color=\"success\"\r\n [title]=\"$any(_field).trueLabel || 'C\u00F3'\"\r\n (click)=\"onEditingValueInput(true)\">\r\n </sd-button>\r\n <sd-button\r\n [autoId]=\"chipAutoId('value-false')\"\r\n [type]=\"_val === false ? 'fill' : 'outline'\"\r\n color=\"warning\"\r\n [title]=\"$any(_field).falseLabel || 'Kh\u00F4ng'\"\r\n (click)=\"onEditingValueInput(false)\">\r\n </sd-button>\r\n </div>\r\n } @else if (_field.type === 'values' || _field.type === 'lazy-values') {\r\n @let _opt = $any(_field).option;\r\n <sd-select size=\"sm\" hideInlineError\r\n [autoId]=\"chipAutoId('value')\"\r\n placeholder=\"Ch\u1ECDn gi\u00E1 tr\u1ECB\"\r\n [items]=\"_options\"\r\n [valueField]=\"_opt.valueField\"\r\n [displayField]=\"_opt.displayField\"\r\n [multiple]=\"multiple()\"\r\n [model]=\"$any(_val)\"\r\n (sdChange)=\"onEditingValueInput($event)\">\r\n </sd-select>\r\n @if (_loading) {\r\n <div class=\"c-pop-values-loading\">\u0110ang t\u1EA3i...</div>\r\n }\r\n } @else if (_field.type === 'date') {\r\n <sd-date size=\"sm\" hideInlineError\r\n [autoId]=\"chipAutoId('value')\"\r\n placeholder=\"Ch\u1ECDn gi\u00E1 tr\u1ECB\"\r\n [model]=\"$any(_val)\"\r\n (sdChange)=\"onEditingValueInput($event)\">\r\n </sd-date>\r\n } @else if (_field.type === 'datetime') {\r\n <sd-datetime size=\"sm\" hideInlineError\r\n [autoId]=\"chipAutoId('value')\"\r\n placeholder=\"Ch\u1ECDn gi\u00E1 tr\u1ECB\"\r\n [model]=\"$any(_val)\"\r\n (sdChange)=\"onEditingValueInput($event)\">\r\n </sd-datetime>\r\n } @else if (_field.type === 'number') {\r\n <sd-input-number size=\"sm\" hideInlineError\r\n [autoId]=\"chipAutoId('value')\"\r\n placeholder=\"Nh\u1EADp gi\u00E1 tr\u1ECB\"\r\n [model]=\"$any(_val)\"\r\n (sdChange)=\"onEditingValueInput($event)\">\r\n </sd-input-number>\r\n } @else {\r\n <sd-input size=\"sm\" hideInlineError\r\n [autoId]=\"chipAutoId('value')\"\r\n placeholder=\"Nh\u1EADp gi\u00E1 tr\u1ECB\"\r\n [model]=\"$any(_val)\"\r\n (sdChange)=\"onEditingValueInput($event)\">\r\n </sd-input>\r\n }\r\n }\r\n </div>\r\n }\r\n </ng-template>\r\n</mat-menu>\r\n", styles: [":host{display:contents}::ng-deep .mat-mdc-menu-panel.c-chip-popover{min-width:280px;max-width:320px}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-header{display:flex;align-items:center;gap:8px;padding:10px 12px;border-bottom:1px solid #e2e4e7;font-size:13px;font-weight:500;color:#212121}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-header mat-icon{font-size:16px;width:16px;height:16px;color:var(--sd-primary)}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-header .c-pop-header-operator{margin-left:auto}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-header-field{display:inline-flex;align-items:center;gap:4px;padding:2px 4px 2px 6px;margin-left:-4px;background:transparent;border:1px solid transparent;border-radius:4px;font:inherit;font-weight:500;color:#212121;cursor:pointer;transition:background-color .12s ease,border-color .12s ease}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-header-field mat-icon{font-size:14px!important;width:14px!important;height:14px!important;color:#7a7a7a!important}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-header-field:hover{background:#f5f6f7;border-color:#e2e4e7}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-body{padding:10px 12px;display:flex;flex-direction:column;gap:12px}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-between{display:grid;grid-template-columns:1fr 1fr;gap:8px}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-boolean{display:flex;gap:8px}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-boolean button{flex:1;height:32px;border:1px solid #d6d8db;border-radius:6px;background:#fff;font:inherit;font-size:13px;cursor:pointer}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-values-loading{padding:10px 8px;font-size:12px;color:#7a7a7a}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-bool-toggle{display:inline-flex;align-items:center;gap:4px}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-bool-toggle .mdc-button,::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-bool-toggle button[mat-button],::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-bool-toggle button[mat-stroked-button],::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-bool-toggle button[mat-flat-button]{border-radius:999px;min-width:0;padding:0 12px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i1.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "directive", type: i1.MatMenuContent, selector: "ng-template[matMenuContent]" }, { kind: "directive", type: i1.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i3.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: SdButton, selector: "sd-button", inputs: ["autoId", "type", "color", "size", "fontSet", "title", "width", "tooltip", "prefixIcon", "suffixIcon", "disabled", "loading", "block", "htmlType"], outputs: ["click"] }, { 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", "bare", "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", "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", "sdFocusForceBlur"] }, { kind: "component", type: SdDate, selector: "sd-date", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "required", "disabled", "viewed", "bare", "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", "bare", "inlineError", "hyperlink", "appearance", "floatLabel", "min", "minDate", "max", "maxDate", "model"], outputs: ["modelChange", "sdChange", "sdFocus"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
730
+ }
731
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdQueryChipPopover, decorators: [{
732
+ type: Component,
733
+ args: [{ selector: 'sd-query-chip-popover', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [
734
+ CommonModule,
735
+ FormsModule,
736
+ MatIconModule,
737
+ MatMenuModule,
738
+ MatTooltipModule,
739
+ SdButton,
740
+ SdOperator,
741
+ SdSelect,
742
+ SdInput,
743
+ SdInputNumber,
744
+ SdDate,
745
+ SdDatetime,
746
+ ], template: "<!--\r\n Chip popover \u2014 operator + value editor for the popover-mode chip.\r\n Visual layout mirrors the previous inline `<mat-menu #chipPopover>` in query-bar.\r\n Body branches on operator + field.type \u2014 8 paths preserved exactly.\r\n-->\r\n<mat-menu #menu=\"matMenu\" class=\"c-chip-popover\" xPosition=\"after\" yPosition=\"below\" (closed)=\"onMenuClosed()\">\r\n <ng-template matMenuContent>\r\n @let _field = field();\r\n @let _op = editingOperator();\r\n @let _val = editingValue();\r\n @let _options = editingOptions();\r\n @let _loading = editingOptionsLoading();\r\n @let _switchMenu = switchPickerMenu();\r\n\r\n @if (_field) {\r\n <div class=\"c-pop-header\" (click)=\"$event.stopPropagation()\">\r\n <mat-icon fontSet=\"material-icons-outlined\">{{ iconFor(_field) }}</mat-icon>\r\n <!-- why: nested mat-menu trigger to swap the chip's field \u2014 parent provides\r\n the field-picker MatMenu via [switchPickerMenu]. -->\r\n @if (_switchMenu) {\r\n <button\r\n type=\"button\"\r\n class=\"c-pop-header-field\"\r\n [matMenuTriggerFor]=\"_switchMenu\"\r\n matTooltip=\"\u0110\u1ED5i field\">\r\n <span>{{ _field.label }}</span>\r\n <mat-icon fontSet=\"material-icons-outlined\">unfold_more</mat-icon>\r\n </button>\r\n } @else {\r\n <span class=\"c-pop-header-field\">{{ _field.label }}</span>\r\n }\r\n <!-- Operator selector (right-aligned). Disabled when field has \u22641 operator. -->\r\n <sd-operator\r\n class=\"c-pop-header-operator\"\r\n [autoId]=\"chipAutoId('operator')\"\r\n [operators]=\"allowedOperators()\"\r\n [model]=\"_op\"\r\n [disabled]=\"!showOperatorSelector()\"\r\n (modelChange)=\"onEditingOperatorChange($any($event))\" />\r\n </div>\r\n\r\n <div class=\"c-pop-body\" (click)=\"$event.stopPropagation()\">\r\n @if (!isNoDataOperator(_op)) {\r\n @if (_op === 'BETWEEN') {\r\n @if (_field.type === 'date') {\r\n <div class=\"c-pop-between\">\r\n <sd-date size=\"sm\" hideInlineError\r\n [autoId]=\"chipAutoId('value-from')\"\r\n placeholder=\"T\u1EEB\"\r\n [model]=\"$any(_val)?.from\"\r\n (sdChange)=\"onEditingRangeFrom($event)\">\r\n </sd-date>\r\n <sd-date size=\"sm\" hideInlineError\r\n [autoId]=\"chipAutoId('value-to')\"\r\n placeholder=\"\u0110\u1EBFn\"\r\n [model]=\"$any(_val)?.to\"\r\n (sdChange)=\"onEditingRangeTo($event)\">\r\n </sd-date>\r\n </div>\r\n } @else if (_field.type === 'datetime') {\r\n <div class=\"c-pop-between\">\r\n <sd-datetime size=\"sm\" hideInlineError\r\n [autoId]=\"chipAutoId('value-from')\"\r\n placeholder=\"T\u1EEB\"\r\n [model]=\"$any(_val)?.from\"\r\n (sdChange)=\"onEditingRangeFrom($event)\">\r\n </sd-datetime>\r\n <sd-datetime size=\"sm\" hideInlineError\r\n [autoId]=\"chipAutoId('value-to')\"\r\n placeholder=\"\u0110\u1EBFn\"\r\n [model]=\"$any(_val)?.to\"\r\n (sdChange)=\"onEditingRangeTo($event)\">\r\n </sd-datetime>\r\n </div>\r\n } @else {\r\n <div class=\"c-pop-between\">\r\n <sd-input-number size=\"sm\" hideInlineError\r\n [autoId]=\"chipAutoId('value-from')\"\r\n placeholder=\"T\u1EEB\"\r\n [model]=\"$any(_val)?.from\"\r\n (sdChange)=\"onEditingRangeFrom($event)\">\r\n </sd-input-number>\r\n <sd-input-number size=\"sm\" hideInlineError\r\n [autoId]=\"chipAutoId('value-to')\"\r\n placeholder=\"\u0110\u1EBFn\"\r\n [model]=\"$any(_val)?.to\"\r\n (sdChange)=\"onEditingRangeTo($event)\">\r\n </sd-input-number>\r\n </div>\r\n }\r\n } @else if (_field.type === 'boolean') {\r\n <div class=\"c-pop-boolean c-bool-toggle\">\r\n <sd-button\r\n [autoId]=\"chipAutoId('value-true')\"\r\n [type]=\"_val === true ? 'fill' : 'outline'\"\r\n color=\"success\"\r\n [title]=\"$any(_field).trueLabel || 'C\u00F3'\"\r\n (click)=\"onEditingValueInput(true)\">\r\n </sd-button>\r\n <sd-button\r\n [autoId]=\"chipAutoId('value-false')\"\r\n [type]=\"_val === false ? 'fill' : 'outline'\"\r\n color=\"warning\"\r\n [title]=\"$any(_field).falseLabel || 'Kh\u00F4ng'\"\r\n (click)=\"onEditingValueInput(false)\">\r\n </sd-button>\r\n </div>\r\n } @else if (_field.type === 'values' || _field.type === 'lazy-values') {\r\n @let _opt = $any(_field).option;\r\n <sd-select size=\"sm\" hideInlineError\r\n [autoId]=\"chipAutoId('value')\"\r\n placeholder=\"Ch\u1ECDn gi\u00E1 tr\u1ECB\"\r\n [items]=\"_options\"\r\n [valueField]=\"_opt.valueField\"\r\n [displayField]=\"_opt.displayField\"\r\n [multiple]=\"multiple()\"\r\n [model]=\"$any(_val)\"\r\n (sdChange)=\"onEditingValueInput($event)\">\r\n </sd-select>\r\n @if (_loading) {\r\n <div class=\"c-pop-values-loading\">\u0110ang t\u1EA3i...</div>\r\n }\r\n } @else if (_field.type === 'date') {\r\n <sd-date size=\"sm\" hideInlineError\r\n [autoId]=\"chipAutoId('value')\"\r\n placeholder=\"Ch\u1ECDn gi\u00E1 tr\u1ECB\"\r\n [model]=\"$any(_val)\"\r\n (sdChange)=\"onEditingValueInput($event)\">\r\n </sd-date>\r\n } @else if (_field.type === 'datetime') {\r\n <sd-datetime size=\"sm\" hideInlineError\r\n [autoId]=\"chipAutoId('value')\"\r\n placeholder=\"Ch\u1ECDn gi\u00E1 tr\u1ECB\"\r\n [model]=\"$any(_val)\"\r\n (sdChange)=\"onEditingValueInput($event)\">\r\n </sd-datetime>\r\n } @else if (_field.type === 'number') {\r\n <sd-input-number size=\"sm\" hideInlineError\r\n [autoId]=\"chipAutoId('value')\"\r\n placeholder=\"Nh\u1EADp gi\u00E1 tr\u1ECB\"\r\n [model]=\"$any(_val)\"\r\n (sdChange)=\"onEditingValueInput($event)\">\r\n </sd-input-number>\r\n } @else {\r\n <sd-input size=\"sm\" hideInlineError\r\n [autoId]=\"chipAutoId('value')\"\r\n placeholder=\"Nh\u1EADp gi\u00E1 tr\u1ECB\"\r\n [model]=\"$any(_val)\"\r\n (sdChange)=\"onEditingValueInput($event)\">\r\n </sd-input>\r\n }\r\n }\r\n </div>\r\n }\r\n </ng-template>\r\n</mat-menu>\r\n", styles: [":host{display:contents}::ng-deep .mat-mdc-menu-panel.c-chip-popover{min-width:280px;max-width:320px}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-header{display:flex;align-items:center;gap:8px;padding:10px 12px;border-bottom:1px solid #e2e4e7;font-size:13px;font-weight:500;color:#212121}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-header mat-icon{font-size:16px;width:16px;height:16px;color:var(--sd-primary)}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-header .c-pop-header-operator{margin-left:auto}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-header-field{display:inline-flex;align-items:center;gap:4px;padding:2px 4px 2px 6px;margin-left:-4px;background:transparent;border:1px solid transparent;border-radius:4px;font:inherit;font-weight:500;color:#212121;cursor:pointer;transition:background-color .12s ease,border-color .12s ease}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-header-field mat-icon{font-size:14px!important;width:14px!important;height:14px!important;color:#7a7a7a!important}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-header-field:hover{background:#f5f6f7;border-color:#e2e4e7}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-body{padding:10px 12px;display:flex;flex-direction:column;gap:12px}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-between{display:grid;grid-template-columns:1fr 1fr;gap:8px}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-boolean{display:flex;gap:8px}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-boolean button{flex:1;height:32px;border:1px solid #d6d8db;border-radius:6px;background:#fff;font:inherit;font-size:13px;cursor:pointer}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-values-loading{padding:10px 8px;font-size:12px;color:#7a7a7a}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-bool-toggle{display:inline-flex;align-items:center;gap:4px}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-bool-toggle .mdc-button,::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-bool-toggle button[mat-button],::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-bool-toggle button[mat-stroked-button],::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-bool-toggle button[mat-flat-button]{border-radius:999px;min-width:0;padding:0 12px}\n"] }]
747
+ }] });
748
+
749
+ /* eslint-disable @typescript-eslint/no-explicit-any */
750
+ /**
751
+ * Field picker mat-menu for `sd-query-bar`.
752
+ *
753
+ * Shared between two flows in the bar:
754
+ * - "Add filter" — pass `[usedKeys]` (a Set of field keys already on the bar) so
755
+ * those entries render disabled with a check.
756
+ * - "Đổi field" inside the chip popover — pass `[currentKey]` to disable the
757
+ * currently-bound field.
758
+ *
759
+ * Parent triggers via `[matMenuTriggerFor]="picker.menu()"` and receives the
760
+ * chosen field through `(pick)`.
761
+ */
762
+ class SdQueryFieldPicker {
763
+ /** Field list shown in the menu (caller-supplied; rendered in input order). */
764
+ fields = input([]);
765
+ /** Single key disabled with a trailing check — used in the chip-popover "Đổi field" flow. */
766
+ currentKey = input(undefined);
767
+ /** Set of keys already in use — used in the "Add filter" flow to grey out duplicates. */
768
+ usedKeys = input(new Set());
769
+ /** Emits when the user picks an enabled field. */
770
+ pick = output();
771
+ /** MatMenu instance — parent binds `[matMenuTriggerFor]="picker.menu()"`. */
772
+ menu = viewChild.required(MatMenu);
773
+ iconFor = sdQueryFieldIcon;
774
+ /** Per-field disabled state — current key OR used key OR field's own disabled flag. */
775
+ disabledMap = computed(() => {
776
+ const set = new Set(this.usedKeys());
777
+ const cur = this.currentKey();
778
+ if (cur)
779
+ set.add(cur);
780
+ return set;
781
+ });
782
+ isDisabled(field) {
783
+ return field.disabled === true || this.disabledMap().has(field.key);
784
+ }
785
+ /** "Current" check icon — only when the field matches `currentKey`. */
786
+ isCurrent(field) {
787
+ return field.key === this.currentKey();
788
+ }
789
+ /** "Used" check icon — when the field is in `usedKeys` (not the same as current). */
790
+ isUsed(field) {
791
+ return this.usedKeys().has(field.key);
792
+ }
793
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdQueryFieldPicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
794
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: SdQueryFieldPicker, isStandalone: true, selector: "sd-query-field-picker", inputs: { fields: { classPropertyName: "fields", publicName: "fields", isSignal: true, isRequired: false, transformFunction: null }, currentKey: { classPropertyName: "currentKey", publicName: "currentKey", isSignal: true, isRequired: false, transformFunction: null }, usedKeys: { classPropertyName: "usedKeys", publicName: "usedKeys", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { pick: "pick" }, viewQueries: [{ propertyName: "menu", first: true, predicate: MatMenu, descendants: true, isSignal: true }], ngImport: i0, template: "<mat-menu #m=\"matMenu\" xPosition=\"after\" yPosition=\"below\" class=\"c-field-picker-menu\">\r\n @for (field of fields(); track $index) {\r\n <button\r\n type=\"button\"\r\n mat-menu-item\r\n [disabled]=\"isDisabled(field)\"\r\n (click)=\"pick.emit(field)\">\r\n <mat-icon fontSet=\"material-icons-outlined\" class=\"c-field-item-icon\">\r\n {{ iconFor(field) }}\r\n </mat-icon>\r\n <span class=\"c-field-item-label\">{{ field.label }}</span>\r\n <span class=\"c-field-item-type\">{{ field.type }}</span>\r\n @if (isCurrent(field) || isUsed(field)) {\r\n <mat-icon class=\"c-field-item-check\" fontSet=\"material-icons-outlined\">check</mat-icon>\r\n }\r\n </button>\r\n } @empty {\r\n <div class=\"c-field-empty\">Ch\u01B0a c\u00F3 field n\u00E0o \u0111\u01B0\u1EE3c c\u1EA5u h\u00ECnh.</div>\r\n }\r\n</mat-menu>\r\n", styles: ["::ng-deep .mat-mdc-menu-panel.c-field-picker-menu{min-width:200px;max-width:260px}::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-menu-item{min-height:36px}::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-menu-item .mat-mdc-menu-item-text{display:flex;align-items:center;flex:1;min-width:0}::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .c-field-item-icon{font-size:16px;width:16px;height:16px;margin-right:8px;color:#5f6368;flex-shrink:0}::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .c-field-item-label{flex:1 1 auto;min-width:0;font-size:13px;color:#212121;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .c-field-item-type{flex-shrink:0;margin-left:auto;padding-left:12px;font-family:Roboto Mono,monospace;font-size:11px;color:#7a7a7a}::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .c-field-item-check{flex-shrink:0;font-size:14px;width:14px;height:14px;margin-left:6px;color:var(--sd-primary)}::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .c-field-empty{padding:12px 16px;font-size:13px;color:#7a7a7a}\n"], dependencies: [{ kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i1.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i1.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
795
+ }
796
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdQueryFieldPicker, decorators: [{
797
+ type: Component,
798
+ args: [{ selector: 'sd-query-field-picker', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [MatMenuModule, MatIconModule], template: "<mat-menu #m=\"matMenu\" xPosition=\"after\" yPosition=\"below\" class=\"c-field-picker-menu\">\r\n @for (field of fields(); track $index) {\r\n <button\r\n type=\"button\"\r\n mat-menu-item\r\n [disabled]=\"isDisabled(field)\"\r\n (click)=\"pick.emit(field)\">\r\n <mat-icon fontSet=\"material-icons-outlined\" class=\"c-field-item-icon\">\r\n {{ iconFor(field) }}\r\n </mat-icon>\r\n <span class=\"c-field-item-label\">{{ field.label }}</span>\r\n <span class=\"c-field-item-type\">{{ field.type }}</span>\r\n @if (isCurrent(field) || isUsed(field)) {\r\n <mat-icon class=\"c-field-item-check\" fontSet=\"material-icons-outlined\">check</mat-icon>\r\n }\r\n </button>\r\n } @empty {\r\n <div class=\"c-field-empty\">Ch\u01B0a c\u00F3 field n\u00E0o \u0111\u01B0\u1EE3c c\u1EA5u h\u00ECnh.</div>\r\n }\r\n</mat-menu>\r\n", styles: ["::ng-deep .mat-mdc-menu-panel.c-field-picker-menu{min-width:200px;max-width:260px}::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-menu-item{min-height:36px}::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-menu-item .mat-mdc-menu-item-text{display:flex;align-items:center;flex:1;min-width:0}::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .c-field-item-icon{font-size:16px;width:16px;height:16px;margin-right:8px;color:#5f6368;flex-shrink:0}::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .c-field-item-label{flex:1 1 auto;min-width:0;font-size:13px;color:#212121;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .c-field-item-type{flex-shrink:0;margin-left:auto;padding-left:12px;font-family:Roboto Mono,monospace;font-size:11px;color:#7a7a7a}::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .c-field-item-check{flex-shrink:0;font-size:14px;width:14px;height:14px;margin-left:6px;color:var(--sd-primary)}::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .c-field-empty{padding:12px 16px;font-size:13px;color:#7a7a7a}\n"] }]
799
+ }] });
800
+
801
+ /* eslint-disable @typescript-eslint/no-explicit-any */
802
+ /**
803
+ * Completed inline chip (non-string/number — those use `<sd-query-inline-value-chip>`).
804
+ *
805
+ * why: tách edit lifecycle ra khỏi parent `<sd-query-bar>` — child tự quản lý
806
+ * `isEditing` signal, auto-open picker, focusout exit. Parent chỉ pass field/filter
807
+ * và nhận commit/remove outputs.
808
+ *
809
+ * Covers types: boolean, date, datetime, values, lazy-values, plus a fallback else
810
+ * branch. BETWEEN (date / datetime) uses a single `<sd-date-range>` (datetime
811
+ * downgrades to date precision).
812
+ */
813
+ class SdQueryInlineChip {
814
+ #injector = inject(Injector);
815
+ #cdr = inject(ChangeDetectorRef);
816
+ // ---------------------------------------------------------------------------
817
+ // Inputs
818
+ // ---------------------------------------------------------------------------
819
+ /** Resolved field — parent passes it (no lookup needed inside). */
820
+ field = input.required();
821
+ /** The chip's Filter — used for `data` + `operator`. */
822
+ filter = input.required();
823
+ /** Density preset — sizes the `.c-token` row. */
824
+ density = input('compact');
825
+ /** Whether the active operator is a multi-select (IN/NOT_IN). */
826
+ multiple = input(false);
827
+ /** Show `<sd-operator>` (disabled badge) on the chip; otherwise show `':'` separator. */
828
+ showOperator = input(false);
829
+ /** autoId prefix for inner controls (parent passes `inlineAutoId(i, 'value')`). */
830
+ autoId = input('');
831
+ /** Pre-computed display text — parent owns `chipValueText(filter)` so we don't recompute. */
832
+ valueText = input('');
833
+ /** No-data operator (NULL/NOT_NULL) — hide entire value slot when true. */
834
+ isNoData = input(false);
835
+ // ---------------------------------------------------------------------------
836
+ // Outputs
837
+ // ---------------------------------------------------------------------------
838
+ /** Single-value commit — parent runs `updateFilter(i, { data })`. */
839
+ commit = output();
840
+ /** BETWEEN commit (date/datetime) — parent runs `setFilterRange(i, ev)`. */
841
+ commitRange = output();
842
+ /** Live edit during multi-select — parent runs `editValueFn(i)(v)`. */
843
+ liveChange = output();
844
+ /** × removal. */
845
+ remove = output();
846
+ // ---------------------------------------------------------------------------
847
+ // Internal state
848
+ // ---------------------------------------------------------------------------
849
+ /** Edit toggle — viewed (false) ↔ editable (true). */
850
+ #editing = signal(false);
851
+ editing = this.#editing.asReadonly();
852
+ /** Active picker reference — auto-opened right after entering edit. */
853
+ chipPicker = viewChild('chipPicker');
854
+ // ---------------------------------------------------------------------------
855
+ // Template helpers
856
+ // ---------------------------------------------------------------------------
857
+ iconName = computed(() => sdQueryFieldIcon(this.field()));
858
+ /** Operator code — convenience accessor for template. */
859
+ operator = computed(() => this.filter().operator);
860
+ /** Data payload — convenience accessor for template. */
861
+ data = computed(() => this.filter().data);
862
+ // ---------------------------------------------------------------------------
863
+ // Edit lifecycle
864
+ // ---------------------------------------------------------------------------
865
+ /** Enter edit mode + auto-open the picker on next render. */
866
+ enterEdit() {
867
+ if (this.#editing())
868
+ return;
869
+ this.#editing.set(true);
870
+ afterNextRender(() => this.chipPicker()?.open?.(), { injector: this.#injector });
871
+ }
872
+ /**
873
+ * Exit edit only when focus actually leaves the wrapper subtree.
874
+ * why: focusout fires for every internal blur — only exit when relatedTarget
875
+ * is OUTSIDE the wrapper AND không thuộc cdk-overlay (mat-select panel, mat-calendar,
876
+ * datetime overlay đều render trong document.body). Tick option trong panel multi-
877
+ * select sẽ chuyển focus sang `<mat-option>` (nằm trong cdk-overlay-container) →
878
+ * nếu exit ngay sẽ flip viewed → kill panel sớm. Chỉ exit khi panel đã close
879
+ * (relatedTarget không nằm trong overlay). markForCheck vì overlay focusout
880
+ * không kéo theo CD cho OnPush parent.
881
+ */
882
+ onFocusOut(ev) {
883
+ const wrapper = ev.currentTarget;
884
+ const next = ev.relatedTarget;
885
+ if (!wrapper)
886
+ return;
887
+ if (next && wrapper.contains(next))
888
+ return;
889
+ if (next instanceof Element && next.closest('.cdk-overlay-container'))
890
+ return;
891
+ if (this.#editing()) {
892
+ this.#editing.set(false);
893
+ this.#cdr.markForCheck();
894
+ // why: sd-date-range / sd-datetime đôi khi emit sdChange NGAY SAU focusout —
895
+ // viewed flip xảy ra trước khi model mới về tới [model] input → viewed text
896
+ // format dùng giá trị cũ. Schedule thêm 1 CD pass để re-render với model mới
897
+ // (user-visible bug: "click ra lần đầu chưa update, click vào rồi ra lần 2 mới đúng").
898
+ queueMicrotask(() => this.#cdr.markForCheck());
899
+ }
236
900
  }
237
- /** Escrevert drafts to the committed value and blur without emitting. */
238
- revertAndBlur(input) {
239
- const v = this.value();
240
- if (this.isBetween()) {
241
- const r = (v ?? {});
242
- this.draftFrom.set(this.#format(r.from));
243
- this.draftTo.set(this.#format(r.to));
244
- }
245
- else {
246
- this.draft.set(this.#format(v));
247
- }
248
- this.hasError.set(false);
249
- this.focused.set(false);
250
- input.blur();
901
+ /** Test-only shim direct call avoids dispatching real FocusEvent in JSDOM. */
902
+ onFocusOutForTest(ev) {
903
+ this.onFocusOut(ev);
251
904
  }
252
- onRemove(ev) {
253
- ev.stopPropagation();
254
- this.remove.emit();
905
+ // ---------------------------------------------------------------------------
906
+ // Commit handlers — single emit per category
907
+ // ---------------------------------------------------------------------------
908
+ /** Single-value commit (sd-date, sd-datetime, single sd-select). */
909
+ emitSingleCommit(v) {
910
+ this.commit.emit(v);
911
+ this.#editing.set(false);
255
912
  }
256
- /** Format a stored value for display — number → vi-VN grouped digits. */
257
- #format(v) {
258
- if (v == null || v === '')
259
- return '';
260
- if (this.isNumber()) {
261
- const n = typeof v === 'number' ? v : Number(String(v).replace(/\./g, ''));
262
- return Number.isFinite(n) ? n.toLocaleString('vi-VN') : String(v);
913
+ /**
914
+ * BETWEEN commit — sd-date-range emits {from, to}.
915
+ * why: nếu cả from + to đều có giá trị → coi như range đã chọn xong → exit edit
916
+ * ngay tại commit (đồng bộ với model.set của parent), không chờ focusout. Tránh
917
+ * race "focusout firing trước sdChange" làm viewed text render với model cũ.
918
+ */
919
+ emitRangeCommit(ev) {
920
+ this.commitRange.emit(ev ?? null);
921
+ if (ev && ev.from != null && ev.to != null) {
922
+ this.#editing.set(false);
923
+ this.#cdr.markForCheck();
263
924
  }
264
- return String(v);
265
925
  }
266
- /** Parse a draft back to a stored value. Number kind rejects non-numeric input. */
267
- #parse(s) {
268
- const t = (s ?? '').trim();
269
- if (t === '')
270
- return { ok: true, value: this.isNumber() ? null : '' };
271
- if (this.isNumber()) {
272
- // vi-VN groups thousands with '.', so strip dots + spaces, then require a plain
273
- // (optionally negative) integer — anything else (letters, stray symbols) is an error.
274
- const cleaned = t.replace(/[.\s]/g, '');
275
- if (!/^-?\d+$/.test(cleaned))
276
- return { ok: false, value: t };
277
- const n = Number(cleaned);
278
- return Number.isFinite(n) ? { ok: true, value: n } : { ok: false, value: t };
279
- }
280
- return { ok: true, value: t };
926
+ /** Multi-select live emit does NOT exit edit (focusout handles exit). */
927
+ emitLive(v) {
928
+ this.liveChange.emit(v);
281
929
  }
282
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdQueryInlineValueChip, deps: [], target: i0.ɵɵFactoryTarget.Component });
283
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: SdQueryInlineValueChip, isStandalone: true, selector: "sd-query-inline-value-chip", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, operator: { classPropertyName: "operator", publicName: "operator", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, density: { classPropertyName: "density", publicName: "density", isSignal: true, isRequired: false, transformFunction: null }, showOperator: { classPropertyName: "showOperator", publicName: "showOperator", isSignal: true, isRequired: false, transformFunction: null }, autoId: { classPropertyName: "autoId", publicName: "autoId", isSignal: true, isRequired: false, transformFunction: null }, autofocus: { classPropertyName: "autofocus", publicName: "autofocus", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange", remove: "remove" }, viewQueries: [{ propertyName: "firstInput", first: true, predicate: ["firstInput"], descendants: true, isSignal: true }], ngImport: i0, template: "<!--\r\n Seamless string/number chip \u2014 the pill IS the input. One border, three flush\r\n segments: label \u00B7 input(s) \u00B7 \u00D7. State styling driven by [data-state].\r\n-->\r\n<span\r\n class=\"c-seamless\"\r\n [attr.data-state]=\"state()\"\r\n [class.c-seamless--compact]=\"density() === 'compact'\"\r\n [class.c-seamless--comfortable]=\"density() === 'comfortable'\"\r\n (click)=\"focusFromShell($event)\">\r\n\r\n <!-- Label segment: icon + field name (+ optional operator pill) -->\r\n <span class=\"c-seamless__label\">\r\n <mat-icon class=\"c-seamless__icon\" fontSet=\"material-icons-outlined\">{{ icon() }}</mat-icon>\r\n <span class=\"c-seamless__field\">{{ field().label }}</span>\r\n @if (showOperator() && !isNoData()) {\r\n <sd-operator class=\"c-seamless__op\" [operators]=\"[operator()]\" [model]=\"operator()\" [disabled]=\"true\" />\r\n } @else if (!isNoData()) {\r\n <span class=\"c-seamless__sep\">:</span>\r\n }\r\n </span>\r\n\r\n <!-- Input segment \u2014 transparent, borderless; hidden for NULL / NOT_NULL -->\r\n @if (!isNoData()) {\r\n <span class=\"c-seamless__input\">\r\n @if (isBetween()) {\r\n <input\r\n #firstInput\r\n type=\"text\"\r\n class=\"c-seamless__field-input\"\r\n [attr.data-autoid]=\"autoId() ? autoId() + '-from' : null\"\r\n [ngModel]=\"draftFrom()\"\r\n (ngModelChange)=\"draftFrom.set($event)\"\r\n [size]=\"sizeFor(draftFrom(), phFrom())\"\r\n [placeholder]=\"phFrom()\"\r\n (focus)=\"onFocus()\"\r\n (blur)=\"commitRange()\"\r\n (keydown.enter)=\"$any($event.target).blur()\"\r\n (keydown.escape)=\"revertAndBlur($any($event.target))\" />\r\n <span class=\"c-seamless__dash\" aria-hidden=\"true\">\u2014</span>\r\n <input\r\n type=\"text\"\r\n class=\"c-seamless__field-input\"\r\n [attr.data-autoid]=\"autoId() ? autoId() + '-to' : null\"\r\n [ngModel]=\"draftTo()\"\r\n (ngModelChange)=\"draftTo.set($event)\"\r\n [size]=\"sizeFor(draftTo(), phTo())\"\r\n [placeholder]=\"phTo()\"\r\n (focus)=\"onFocus()\"\r\n (blur)=\"commitRange()\"\r\n (keydown.enter)=\"$any($event.target).blur()\"\r\n (keydown.escape)=\"revertAndBlur($any($event.target))\" />\r\n } @else {\r\n <input\r\n #firstInput\r\n type=\"text\"\r\n class=\"c-seamless__field-input\"\r\n [attr.data-autoid]=\"autoId() ?? null\"\r\n [ngModel]=\"draft()\"\r\n (ngModelChange)=\"draft.set($event)\"\r\n [size]=\"sizeFor(draft(), ph())\"\r\n [placeholder]=\"ph()\"\r\n (focus)=\"onFocus()\"\r\n (blur)=\"commitSingle()\"\r\n (keydown.enter)=\"$any($event.target).blur()\"\r\n (keydown.escape)=\"revertAndBlur($any($event.target))\" />\r\n }\r\n </span>\r\n }\r\n\r\n <!-- \u00D7 segment -->\r\n <button type=\"button\" class=\"c-seamless__x\" aria-label=\"Remove filter\" (click)=\"onRemove($event)\">\r\n <mat-icon fontSet=\"material-icons-outlined\">close</mat-icon>\r\n </button>\r\n</span>\r\n", styles: [":host{display:inline-flex}.c-seamless{display:inline-flex;align-items:stretch;border-radius:999px;overflow:hidden;font-family:inherit;font-size:13px;line-height:1;cursor:text;border:1px solid #d6d8db;background:#fff;color:#5f6368;transition:border-color .12s ease,background-color .12s ease,box-shadow .12s ease,color .12s ease}.c-seamless--compact{height:28px}.c-seamless--comfortable{height:32px}.c-seamless:hover:not([data-state=focus]):not([data-state=error]){border-color:var(--sd-primary)}.c-seamless[data-state=pending]{border-style:dashed;border-color:#d6d8db;background:#fff;color:#5f6368}.c-seamless[data-state=pending] .c-seamless__field-input{color:#212121}.c-seamless[data-state=pending] .c-seamless__field-input::placeholder{color:#7a7a7a;font-style:italic}.c-seamless[data-state=active]{border-color:var(--sd-primary-light);background:var(--sd-primary-light)}.c-seamless[data-state=active] .c-seamless__field-input{color:var(--sd-primary)}.c-seamless[data-state=focus]{border-color:var(--sd-primary);background:#fff;box-shadow:0 0 0 3px var(--sd-primary-light)}.c-seamless[data-state=focus] .c-seamless__field-input{color:#212121}.c-seamless[data-state=error]{border-color:#d23535;background:#fff5f5;color:#d23535;box-shadow:0 0 0 3px #d235351f}.c-seamless[data-state=error] .c-seamless__field-input{color:#d23535}.c-seamless__label{display:inline-flex;align-items:center;gap:4px;font-weight:500;white-space:nowrap;cursor:text;color:inherit}.c-seamless--compact .c-seamless__label{padding:0 4px 0 12px}.c-seamless--comfortable .c-seamless__label{padding:0 6px 0 14px}.c-seamless__icon{font-size:14px;width:14px;height:14px;color:inherit}.c-seamless__field{color:inherit}.c-seamless__op{display:inline-flex;align-items:center;color:inherit}.c-seamless__sep{color:#7a7a7a}.c-seamless__input{display:inline-flex;align-items:center;gap:4px}.c-seamless--compact .c-seamless__input{padding:0 4px}.c-seamless--comfortable .c-seamless__input{padding:0 6px}.c-seamless__dash{color:#7a7a7a}.c-seamless__field-input{appearance:none;border:0;outline:0;background:transparent;font:inherit;font-weight:500;padding:0;min-width:24px;max-width:200px}.c-seamless__x{display:inline-flex;align-items:center;justify-content:center;border:0;border-left:1px solid rgba(0,0,0,.04);background:transparent;cursor:pointer;color:inherit}.c-seamless--compact .c-seamless__x{width:26px}.c-seamless--comfortable .c-seamless__x{width:28px}.c-seamless__x mat-icon{font-size:14px;width:14px;height:14px}.c-seamless__x:hover{background:#0000000d}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
930
+ /** Boolean toggle commit exits edit. */
931
+ emitBoolean(v) {
932
+ this.commit.emit(v);
933
+ this.#editing.set(false);
934
+ }
935
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdQueryInlineChip, deps: [], target: i0.ɵɵFactoryTarget.Component });
936
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: SdQueryInlineChip, isStandalone: true, selector: "sd-query-inline-chip", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, filter: { classPropertyName: "filter", publicName: "filter", isSignal: true, isRequired: true, transformFunction: null }, density: { classPropertyName: "density", publicName: "density", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, showOperator: { classPropertyName: "showOperator", publicName: "showOperator", isSignal: true, isRequired: false, transformFunction: null }, autoId: { classPropertyName: "autoId", publicName: "autoId", isSignal: true, isRequired: false, transformFunction: null }, valueText: { classPropertyName: "valueText", publicName: "valueText", isSignal: true, isRequired: false, transformFunction: null }, isNoData: { classPropertyName: "isNoData", publicName: "isNoData", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { commit: "commit", commitRange: "commitRange", liveChange: "liveChange", remove: "remove" }, viewQueries: [{ propertyName: "chipPicker", first: true, predicate: ["chipPicker"], descendants: true, isSignal: true }], ngImport: i0, template: "<!--\r\n Completed inline chip \u2014 non-string/number/seamless branches.\r\n Edit lifecycle is self-managed: click enters edit + auto-opens picker,\r\n focusout outside the wrapper exits edit, picker emits commit single OR\r\n liveChange (multi) OR commitRange (BETWEEN).\r\n-->\r\n@let _f = field();\r\n@let _op = operator();\r\n@let _data = data();\r\n@let _multi = multiple();\r\n@let _editing = editing();\r\n@let _isNoData = isNoData();\r\n\r\n<div class=\"c-token\" [class.c-token-editing]=\"_editing\">\r\n <mat-icon class=\"c-token-icon\" fontSet=\"material-icons-outlined\">{{ iconName() }}</mat-icon>\r\n <span class=\"c-token-field\">{{ _f.label }}</span>\r\n\r\n @if (!_isNoData && showOperator()) {\r\n <sd-operator class=\"c-token-op\" [operators]=\"[_op]\" [model]=\"_op\" [disabled]=\"true\" />\r\n } @else if (!_isNoData) {\r\n <span class=\"c-token-sep\">:</span>\r\n }\r\n\r\n @if (!_isNoData) {\r\n @if (_f.type === 'boolean') {\r\n @if (_editing) {\r\n <!-- why: native button thay sd-button \u2014 mat-button MDC baseline 36px bu\u1ED9c\r\n ph\u1EA3i hack height/padding overrides; chip 28/32px nh\u1ECF h\u01A1n nhi\u1EC1u. T\u1EF1 v\u1EBD\r\n pill nh\u1ECF v\u1EDBi CSS scoped trong chip, kh\u00F4ng qua MDC, g\u1ECDn + chu\u1EA9n baseline. -->\r\n <span class=\"c-token-value c-token-value-edit c-bool-toggle\">\r\n <button\r\n type=\"button\"\r\n class=\"c-bool-btn c-bool-true\"\r\n [class.c-bool-active]=\"_data === true\"\r\n [attr.data-autoId]=\"autoId() + '-true'\"\r\n (click)=\"emitBoolean(true)\">\r\n {{ $any(_f).trueLabel || 'C\u00F3' }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"c-bool-btn c-bool-false\"\r\n [class.c-bool-active]=\"_data === false\"\r\n [attr.data-autoId]=\"autoId() + '-false'\"\r\n (click)=\"emitBoolean(false)\">\r\n {{ $any(_f).falseLabel || 'Kh\u00F4ng' }}\r\n </button>\r\n </span>\r\n } @else {\r\n <button type=\"button\" class=\"c-token-value\" (click)=\"enterEdit()\">{{ valueText() }}</button>\r\n }\r\n } @else if ((_f.type === 'date' || _f.type === 'datetime') && _op === 'BETWEEN') {\r\n <!-- why: d\u00F9ng sd-date-range th\u1ED1ng nh\u1EA5t cho c\u1EA3 date + datetime BETWEEN (datetime downgrade v\u1EC1 date). -->\r\n <span\r\n class=\"c-token-value c-token-value-edit\"\r\n (click)=\"enterEdit()\"\r\n (focusout)=\"onFocusOut($event)\">\r\n <sd-date-range #chipPicker bare size=\"sm\"\r\n [autoId]=\"autoId()\"\r\n [viewed]=\"!_editing\"\r\n [model]=\"$any(_data)\"\r\n (sdChange)=\"emitRangeCommit($any($event))\"></sd-date-range>\r\n </span>\r\n } @else if (_f.type === 'values' || _f.type === 'lazy-values') {\r\n @let _opt = $any(_f).option;\r\n <span\r\n class=\"c-token-value c-token-value-edit\"\r\n (click)=\"enterEdit()\"\r\n (focusout)=\"onFocusOut($event)\">\r\n <sd-select #chipPicker bare size=\"sm\" minWidthPanel=\"300px\"\r\n [autoId]=\"autoId()\"\r\n [items]=\"_f.type === 'lazy-values' ? $any(_opt).search : $any(_opt.items)\"\r\n [valueField]=\"_opt.valueField\"\r\n [displayField]=\"_opt.displayField\"\r\n [multiple]=\"_multi\"\r\n [viewed]=\"!_editing\"\r\n [model]=\"$any(_data)\"\r\n (sdChange)=\"_multi ? emitLive($event) : emitSingleCommit($event)\">\r\n <!-- why: viewed mode m\u1EB7c \u0111\u1ECBnh d\u00F9ng comma-joined cho multi \u2014 qu\u00E1 d\u00E0i cho chip.\r\n Override b\u1EB1ng template \"head +N\" gi\u1ED1ng popover chipValueText. -->\r\n <ng-template #sdValue let-selectedItems=\"selectedItems\">\r\n @if ((selectedItems?.length ?? 0) > 1) {\r\n <span class=\"c-token-multi-head\">{{ $any(selectedItems)[0][_opt.displayField] }}</span> +{{ selectedItems.length - 1 }}\r\n } @else if ((selectedItems?.length ?? 0) === 1) {\r\n <span class=\"c-token-multi-head\">{{ $any(selectedItems)[0][_opt.displayField] }}</span>\r\n }\r\n </ng-template>\r\n </sd-select>\r\n </span>\r\n } @else if (_f.type === 'date') {\r\n <span\r\n class=\"c-token-value c-token-value-edit\"\r\n (click)=\"enterEdit()\"\r\n (focusout)=\"onFocusOut($event)\">\r\n <sd-date #chipPicker bare size=\"sm\"\r\n [autoId]=\"autoId()\"\r\n [viewed]=\"!_editing\"\r\n [model]=\"$any(_data)\"\r\n (sdChange)=\"emitSingleCommit($any($event))\"></sd-date>\r\n </span>\r\n } @else if (_f.type === 'datetime') {\r\n <span\r\n class=\"c-token-value c-token-value-edit\"\r\n (click)=\"enterEdit()\"\r\n (focusout)=\"onFocusOut($event)\">\r\n <sd-datetime #chipPicker bare size=\"sm\"\r\n [autoId]=\"autoId()\"\r\n [viewed]=\"!_editing\"\r\n [model]=\"$any(_data)\"\r\n (sdChange)=\"emitSingleCommit($any($event))\"></sd-datetime>\r\n </span>\r\n } @else {\r\n <!-- Fallback (unforeseen type) \u2014 read-only display via valueText. -->\r\n <span class=\"c-token-value\">{{ valueText() }}</span>\r\n }\r\n }\r\n\r\n <button type=\"button\" class=\"c-token-remove\" aria-label=\"Remove filter\" (click)=\"remove.emit()\">\r\n <mat-icon fontSet=\"material-icons-outlined\">close</mat-icon>\r\n </button>\r\n</div>\r\n", styles: [":host{display:contents}.c-token{display:inline-flex;align-items:center;gap:4px;padding:0 0 0 12px;border:1px solid var(--sd-primary-light);border-radius:999px;background:var(--sd-primary-light);font-size:13px;color:#5f6368;white-space:nowrap;overflow:hidden}:host-context(.c-density-compact) .c-token{height:28px}:host-context(.c-density-comfortable) .c-token{height:32px}.c-token-icon{font-size:14px;width:14px;height:14px;line-height:14px;color:inherit}.c-token-field{font-weight:500;line-height:1}.c-token-op{display:inline-flex;align-items:center;color:inherit}.c-token-op ::ng-deep .c-op-trigger{padding:0;color:inherit}.c-token-op ::ng-deep .c-op-icon svg{width:14px;height:14px;display:block}.c-token-sep{color:#7a7a7a}.c-token-value{border:none;background:transparent;padding:0;font:inherit;font-weight:500;color:var(--sd-primary);cursor:pointer}.c-token-value:hover{text-decoration:underline}.c-token-value-edit{display:inline-flex;align-items:center;gap:4px;max-width:240px}.c-token-value-edit sd-date-range{display:inline-flex;max-width:220px;min-width:0;flex:0 1 auto}.c-token-value-edit sd-select,.c-token-value-edit sd-date,.c-token-value-edit sd-datetime{display:inline-flex;align-items:center;line-height:1}.c-token ::ng-deep .mat-mdc-form-field{line-height:1}.c-token ::ng-deep .mat-mdc-text-field-wrapper,.c-token ::ng-deep .mat-mdc-form-field-flex{padding:0;min-height:0;background:transparent}.c-token ::ng-deep .mat-mdc-form-field-infix{padding:0;min-height:0;width:auto;border:0;display:inline-flex;align-items:center}.c-token ::ng-deep .mat-mdc-select-value,.c-token ::ng-deep .mat-mdc-input-element,.c-token ::ng-deep input{line-height:1;font-size:13px;padding:0;height:auto;color:inherit}.c-token ::ng-deep .mat-mdc-select-value{display:inline-flex;align-items:center;min-width:0;max-width:200px;overflow:hidden;text-overflow:ellipsis;color:var(--sd-primary);font-weight:500}.c-token ::ng-deep .sd-suffix-icon{display:none}.c-token-multi-head{display:inline-block;max-width:140px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom}.c-bool-toggle{display:inline-flex;align-items:center;gap:4px}.c-bool-btn{display:inline-flex;align-items:center;justify-content:center;height:18px;min-width:0;padding:0 8px;border:1px solid currentColor;border-radius:999px;background:transparent;font:inherit;font-size:11px;font-weight:500;line-height:1;cursor:pointer;white-space:nowrap;-webkit-user-select:none;user-select:none;transition:background .12s ease,color .12s ease}:host-context(.c-density-comfortable) .c-bool-btn{height:20px;padding:0 10px;font-size:12px}.c-bool-true{color:var(--sd-success)}.c-bool-false{color:var(--sd-warning)}.c-bool-active.c-bool-true{background:var(--sd-success);color:#fff;border-color:var(--sd-success)}.c-bool-active.c-bool-false{background:var(--sd-warning);color:#fff;border-color:var(--sd-warning)}.c-bool-btn:hover:not(.c-bool-active).c-bool-true{background:var(--sd-success-light)}.c-bool-btn:hover:not(.c-bool-active).c-bool-false{background:var(--sd-warning-light)}.c-token-remove{align-self:stretch;display:inline-flex;align-items:center;justify-content:center;border:none;border-left:1px solid rgba(0,0,0,.04);border-radius:0;background:transparent;cursor:pointer;color:inherit}:host-context(.c-density-compact) .c-token-remove{width:26px}:host-context(.c-density-comfortable) .c-token-remove{width:28px}.c-token-remove mat-icon{font-size:14px;width:14px;height:14px}.c-token-remove:hover{background:#0000000d}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.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: SdDate, selector: "sd-date", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "required", "disabled", "viewed", "bare", "inlineError", "hyperlink", "appearance", "floatLabel", "min", "minDate", "max", "maxDate", "model"], outputs: ["modelChange", "sdChange", "sdFocus"] }, { kind: "component", type: SdDateRange, selector: "sd-date-range", inputs: ["autoId", "name", "size", "form", "label", "helperText", "hideInlineError", "required", "disabled", "bare", "viewed", "appearance", "floatLabel", "min", "max", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "component", type: SdDatetime, selector: "sd-datetime", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "required", "disabled", "viewed", "showSeconds", "bare", "inlineError", "hyperlink", "appearance", "floatLabel", "min", "minDate", "max", "maxDate", "model"], outputs: ["modelChange", "sdChange", "sdFocus"] }, { 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", "bare", "validator", "inlineError", "appearance", "floatLabel", "items", "model"], outputs: ["modelChange", "sdChange", "sdSelection"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
284
937
  }
285
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdQueryInlineValueChip, decorators: [{
938
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdQueryInlineChip, decorators: [{
286
939
  type: Component,
287
- args: [{ selector: 'sd-query-inline-value-chip', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, MatIconModule, SdOperator], template: "<!--\r\n Seamless string/number chip \u2014 the pill IS the input. One border, three flush\r\n segments: label \u00B7 input(s) \u00B7 \u00D7. State styling driven by [data-state].\r\n-->\r\n<span\r\n class=\"c-seamless\"\r\n [attr.data-state]=\"state()\"\r\n [class.c-seamless--compact]=\"density() === 'compact'\"\r\n [class.c-seamless--comfortable]=\"density() === 'comfortable'\"\r\n (click)=\"focusFromShell($event)\">\r\n\r\n <!-- Label segment: icon + field name (+ optional operator pill) -->\r\n <span class=\"c-seamless__label\">\r\n <mat-icon class=\"c-seamless__icon\" fontSet=\"material-icons-outlined\">{{ icon() }}</mat-icon>\r\n <span class=\"c-seamless__field\">{{ field().label }}</span>\r\n @if (showOperator() && !isNoData()) {\r\n <sd-operator class=\"c-seamless__op\" [operators]=\"[operator()]\" [model]=\"operator()\" [disabled]=\"true\" />\r\n } @else if (!isNoData()) {\r\n <span class=\"c-seamless__sep\">:</span>\r\n }\r\n </span>\r\n\r\n <!-- Input segment \u2014 transparent, borderless; hidden for NULL / NOT_NULL -->\r\n @if (!isNoData()) {\r\n <span class=\"c-seamless__input\">\r\n @if (isBetween()) {\r\n <input\r\n #firstInput\r\n type=\"text\"\r\n class=\"c-seamless__field-input\"\r\n [attr.data-autoid]=\"autoId() ? autoId() + '-from' : null\"\r\n [ngModel]=\"draftFrom()\"\r\n (ngModelChange)=\"draftFrom.set($event)\"\r\n [size]=\"sizeFor(draftFrom(), phFrom())\"\r\n [placeholder]=\"phFrom()\"\r\n (focus)=\"onFocus()\"\r\n (blur)=\"commitRange()\"\r\n (keydown.enter)=\"$any($event.target).blur()\"\r\n (keydown.escape)=\"revertAndBlur($any($event.target))\" />\r\n <span class=\"c-seamless__dash\" aria-hidden=\"true\">\u2014</span>\r\n <input\r\n type=\"text\"\r\n class=\"c-seamless__field-input\"\r\n [attr.data-autoid]=\"autoId() ? autoId() + '-to' : null\"\r\n [ngModel]=\"draftTo()\"\r\n (ngModelChange)=\"draftTo.set($event)\"\r\n [size]=\"sizeFor(draftTo(), phTo())\"\r\n [placeholder]=\"phTo()\"\r\n (focus)=\"onFocus()\"\r\n (blur)=\"commitRange()\"\r\n (keydown.enter)=\"$any($event.target).blur()\"\r\n (keydown.escape)=\"revertAndBlur($any($event.target))\" />\r\n } @else {\r\n <input\r\n #firstInput\r\n type=\"text\"\r\n class=\"c-seamless__field-input\"\r\n [attr.data-autoid]=\"autoId() ?? null\"\r\n [ngModel]=\"draft()\"\r\n (ngModelChange)=\"draft.set($event)\"\r\n [size]=\"sizeFor(draft(), ph())\"\r\n [placeholder]=\"ph()\"\r\n (focus)=\"onFocus()\"\r\n (blur)=\"commitSingle()\"\r\n (keydown.enter)=\"$any($event.target).blur()\"\r\n (keydown.escape)=\"revertAndBlur($any($event.target))\" />\r\n }\r\n </span>\r\n }\r\n\r\n <!-- \u00D7 segment -->\r\n <button type=\"button\" class=\"c-seamless__x\" aria-label=\"Remove filter\" (click)=\"onRemove($event)\">\r\n <mat-icon fontSet=\"material-icons-outlined\">close</mat-icon>\r\n </button>\r\n</span>\r\n", styles: [":host{display:inline-flex}.c-seamless{display:inline-flex;align-items:stretch;border-radius:999px;overflow:hidden;font-family:inherit;font-size:13px;line-height:1;cursor:text;border:1px solid #d6d8db;background:#fff;color:#5f6368;transition:border-color .12s ease,background-color .12s ease,box-shadow .12s ease,color .12s ease}.c-seamless--compact{height:28px}.c-seamless--comfortable{height:32px}.c-seamless:hover:not([data-state=focus]):not([data-state=error]){border-color:var(--sd-primary)}.c-seamless[data-state=pending]{border-style:dashed;border-color:#d6d8db;background:#fff;color:#5f6368}.c-seamless[data-state=pending] .c-seamless__field-input{color:#212121}.c-seamless[data-state=pending] .c-seamless__field-input::placeholder{color:#7a7a7a;font-style:italic}.c-seamless[data-state=active]{border-color:var(--sd-primary-light);background:var(--sd-primary-light)}.c-seamless[data-state=active] .c-seamless__field-input{color:var(--sd-primary)}.c-seamless[data-state=focus]{border-color:var(--sd-primary);background:#fff;box-shadow:0 0 0 3px var(--sd-primary-light)}.c-seamless[data-state=focus] .c-seamless__field-input{color:#212121}.c-seamless[data-state=error]{border-color:#d23535;background:#fff5f5;color:#d23535;box-shadow:0 0 0 3px #d235351f}.c-seamless[data-state=error] .c-seamless__field-input{color:#d23535}.c-seamless__label{display:inline-flex;align-items:center;gap:4px;font-weight:500;white-space:nowrap;cursor:text;color:inherit}.c-seamless--compact .c-seamless__label{padding:0 4px 0 12px}.c-seamless--comfortable .c-seamless__label{padding:0 6px 0 14px}.c-seamless__icon{font-size:14px;width:14px;height:14px;color:inherit}.c-seamless__field{color:inherit}.c-seamless__op{display:inline-flex;align-items:center;color:inherit}.c-seamless__sep{color:#7a7a7a}.c-seamless__input{display:inline-flex;align-items:center;gap:4px}.c-seamless--compact .c-seamless__input{padding:0 4px}.c-seamless--comfortable .c-seamless__input{padding:0 6px}.c-seamless__dash{color:#7a7a7a}.c-seamless__field-input{appearance:none;border:0;outline:0;background:transparent;font:inherit;font-weight:500;padding:0;min-width:24px;max-width:200px}.c-seamless__x{display:inline-flex;align-items:center;justify-content:center;border:0;border-left:1px solid rgba(0,0,0,.04);background:transparent;cursor:pointer;color:inherit}.c-seamless--compact .c-seamless__x{width:26px}.c-seamless--comfortable .c-seamless__x{width:28px}.c-seamless__x mat-icon{font-size:14px;width:14px;height:14px}.c-seamless__x:hover{background:#0000000d}\n"] }]
288
- }], ctorParameters: () => [] });
940
+ args: [{ selector: 'sd-query-inline-chip', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [
941
+ CommonModule,
942
+ MatIconModule,
943
+ SdOperator,
944
+ SdDate,
945
+ SdDateRange,
946
+ SdDatetime,
947
+ SdSelect,
948
+ ], template: "<!--\r\n Completed inline chip \u2014 non-string/number/seamless branches.\r\n Edit lifecycle is self-managed: click enters edit + auto-opens picker,\r\n focusout outside the wrapper exits edit, picker emits commit single OR\r\n liveChange (multi) OR commitRange (BETWEEN).\r\n-->\r\n@let _f = field();\r\n@let _op = operator();\r\n@let _data = data();\r\n@let _multi = multiple();\r\n@let _editing = editing();\r\n@let _isNoData = isNoData();\r\n\r\n<div class=\"c-token\" [class.c-token-editing]=\"_editing\">\r\n <mat-icon class=\"c-token-icon\" fontSet=\"material-icons-outlined\">{{ iconName() }}</mat-icon>\r\n <span class=\"c-token-field\">{{ _f.label }}</span>\r\n\r\n @if (!_isNoData && showOperator()) {\r\n <sd-operator class=\"c-token-op\" [operators]=\"[_op]\" [model]=\"_op\" [disabled]=\"true\" />\r\n } @else if (!_isNoData) {\r\n <span class=\"c-token-sep\">:</span>\r\n }\r\n\r\n @if (!_isNoData) {\r\n @if (_f.type === 'boolean') {\r\n @if (_editing) {\r\n <!-- why: native button thay sd-button \u2014 mat-button MDC baseline 36px bu\u1ED9c\r\n ph\u1EA3i hack height/padding overrides; chip 28/32px nh\u1ECF h\u01A1n nhi\u1EC1u. T\u1EF1 v\u1EBD\r\n pill nh\u1ECF v\u1EDBi CSS scoped trong chip, kh\u00F4ng qua MDC, g\u1ECDn + chu\u1EA9n baseline. -->\r\n <span class=\"c-token-value c-token-value-edit c-bool-toggle\">\r\n <button\r\n type=\"button\"\r\n class=\"c-bool-btn c-bool-true\"\r\n [class.c-bool-active]=\"_data === true\"\r\n [attr.data-autoId]=\"autoId() + '-true'\"\r\n (click)=\"emitBoolean(true)\">\r\n {{ $any(_f).trueLabel || 'C\u00F3' }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"c-bool-btn c-bool-false\"\r\n [class.c-bool-active]=\"_data === false\"\r\n [attr.data-autoId]=\"autoId() + '-false'\"\r\n (click)=\"emitBoolean(false)\">\r\n {{ $any(_f).falseLabel || 'Kh\u00F4ng' }}\r\n </button>\r\n </span>\r\n } @else {\r\n <button type=\"button\" class=\"c-token-value\" (click)=\"enterEdit()\">{{ valueText() }}</button>\r\n }\r\n } @else if ((_f.type === 'date' || _f.type === 'datetime') && _op === 'BETWEEN') {\r\n <!-- why: d\u00F9ng sd-date-range th\u1ED1ng nh\u1EA5t cho c\u1EA3 date + datetime BETWEEN (datetime downgrade v\u1EC1 date). -->\r\n <span\r\n class=\"c-token-value c-token-value-edit\"\r\n (click)=\"enterEdit()\"\r\n (focusout)=\"onFocusOut($event)\">\r\n <sd-date-range #chipPicker bare size=\"sm\"\r\n [autoId]=\"autoId()\"\r\n [viewed]=\"!_editing\"\r\n [model]=\"$any(_data)\"\r\n (sdChange)=\"emitRangeCommit($any($event))\"></sd-date-range>\r\n </span>\r\n } @else if (_f.type === 'values' || _f.type === 'lazy-values') {\r\n @let _opt = $any(_f).option;\r\n <span\r\n class=\"c-token-value c-token-value-edit\"\r\n (click)=\"enterEdit()\"\r\n (focusout)=\"onFocusOut($event)\">\r\n <sd-select #chipPicker bare size=\"sm\" minWidthPanel=\"300px\"\r\n [autoId]=\"autoId()\"\r\n [items]=\"_f.type === 'lazy-values' ? $any(_opt).search : $any(_opt.items)\"\r\n [valueField]=\"_opt.valueField\"\r\n [displayField]=\"_opt.displayField\"\r\n [multiple]=\"_multi\"\r\n [viewed]=\"!_editing\"\r\n [model]=\"$any(_data)\"\r\n (sdChange)=\"_multi ? emitLive($event) : emitSingleCommit($event)\">\r\n <!-- why: viewed mode m\u1EB7c \u0111\u1ECBnh d\u00F9ng comma-joined cho multi \u2014 qu\u00E1 d\u00E0i cho chip.\r\n Override b\u1EB1ng template \"head +N\" gi\u1ED1ng popover chipValueText. -->\r\n <ng-template #sdValue let-selectedItems=\"selectedItems\">\r\n @if ((selectedItems?.length ?? 0) > 1) {\r\n <span class=\"c-token-multi-head\">{{ $any(selectedItems)[0][_opt.displayField] }}</span> +{{ selectedItems.length - 1 }}\r\n } @else if ((selectedItems?.length ?? 0) === 1) {\r\n <span class=\"c-token-multi-head\">{{ $any(selectedItems)[0][_opt.displayField] }}</span>\r\n }\r\n </ng-template>\r\n </sd-select>\r\n </span>\r\n } @else if (_f.type === 'date') {\r\n <span\r\n class=\"c-token-value c-token-value-edit\"\r\n (click)=\"enterEdit()\"\r\n (focusout)=\"onFocusOut($event)\">\r\n <sd-date #chipPicker bare size=\"sm\"\r\n [autoId]=\"autoId()\"\r\n [viewed]=\"!_editing\"\r\n [model]=\"$any(_data)\"\r\n (sdChange)=\"emitSingleCommit($any($event))\"></sd-date>\r\n </span>\r\n } @else if (_f.type === 'datetime') {\r\n <span\r\n class=\"c-token-value c-token-value-edit\"\r\n (click)=\"enterEdit()\"\r\n (focusout)=\"onFocusOut($event)\">\r\n <sd-datetime #chipPicker bare size=\"sm\"\r\n [autoId]=\"autoId()\"\r\n [viewed]=\"!_editing\"\r\n [model]=\"$any(_data)\"\r\n (sdChange)=\"emitSingleCommit($any($event))\"></sd-datetime>\r\n </span>\r\n } @else {\r\n <!-- Fallback (unforeseen type) \u2014 read-only display via valueText. -->\r\n <span class=\"c-token-value\">{{ valueText() }}</span>\r\n }\r\n }\r\n\r\n <button type=\"button\" class=\"c-token-remove\" aria-label=\"Remove filter\" (click)=\"remove.emit()\">\r\n <mat-icon fontSet=\"material-icons-outlined\">close</mat-icon>\r\n </button>\r\n</div>\r\n", styles: [":host{display:contents}.c-token{display:inline-flex;align-items:center;gap:4px;padding:0 0 0 12px;border:1px solid var(--sd-primary-light);border-radius:999px;background:var(--sd-primary-light);font-size:13px;color:#5f6368;white-space:nowrap;overflow:hidden}:host-context(.c-density-compact) .c-token{height:28px}:host-context(.c-density-comfortable) .c-token{height:32px}.c-token-icon{font-size:14px;width:14px;height:14px;line-height:14px;color:inherit}.c-token-field{font-weight:500;line-height:1}.c-token-op{display:inline-flex;align-items:center;color:inherit}.c-token-op ::ng-deep .c-op-trigger{padding:0;color:inherit}.c-token-op ::ng-deep .c-op-icon svg{width:14px;height:14px;display:block}.c-token-sep{color:#7a7a7a}.c-token-value{border:none;background:transparent;padding:0;font:inherit;font-weight:500;color:var(--sd-primary);cursor:pointer}.c-token-value:hover{text-decoration:underline}.c-token-value-edit{display:inline-flex;align-items:center;gap:4px;max-width:240px}.c-token-value-edit sd-date-range{display:inline-flex;max-width:220px;min-width:0;flex:0 1 auto}.c-token-value-edit sd-select,.c-token-value-edit sd-date,.c-token-value-edit sd-datetime{display:inline-flex;align-items:center;line-height:1}.c-token ::ng-deep .mat-mdc-form-field{line-height:1}.c-token ::ng-deep .mat-mdc-text-field-wrapper,.c-token ::ng-deep .mat-mdc-form-field-flex{padding:0;min-height:0;background:transparent}.c-token ::ng-deep .mat-mdc-form-field-infix{padding:0;min-height:0;width:auto;border:0;display:inline-flex;align-items:center}.c-token ::ng-deep .mat-mdc-select-value,.c-token ::ng-deep .mat-mdc-input-element,.c-token ::ng-deep input{line-height:1;font-size:13px;padding:0;height:auto;color:inherit}.c-token ::ng-deep .mat-mdc-select-value{display:inline-flex;align-items:center;min-width:0;max-width:200px;overflow:hidden;text-overflow:ellipsis;color:var(--sd-primary);font-weight:500}.c-token ::ng-deep .sd-suffix-icon{display:none}.c-token-multi-head{display:inline-block;max-width:140px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom}.c-bool-toggle{display:inline-flex;align-items:center;gap:4px}.c-bool-btn{display:inline-flex;align-items:center;justify-content:center;height:18px;min-width:0;padding:0 8px;border:1px solid currentColor;border-radius:999px;background:transparent;font:inherit;font-size:11px;font-weight:500;line-height:1;cursor:pointer;white-space:nowrap;-webkit-user-select:none;user-select:none;transition:background .12s ease,color .12s ease}:host-context(.c-density-comfortable) .c-bool-btn{height:20px;padding:0 10px;font-size:12px}.c-bool-true{color:var(--sd-success)}.c-bool-false{color:var(--sd-warning)}.c-bool-active.c-bool-true{background:var(--sd-success);color:#fff;border-color:var(--sd-success)}.c-bool-active.c-bool-false{background:var(--sd-warning);color:#fff;border-color:var(--sd-warning)}.c-bool-btn:hover:not(.c-bool-active).c-bool-true{background:var(--sd-success-light)}.c-bool-btn:hover:not(.c-bool-active).c-bool-false{background:var(--sd-warning-light)}.c-token-remove{align-self:stretch;display:inline-flex;align-items:center;justify-content:center;border:none;border-left:1px solid rgba(0,0,0,.04);border-radius:0;background:transparent;cursor:pointer;color:inherit}:host-context(.c-density-compact) .c-token-remove{width:26px}:host-context(.c-density-comfortable) .c-token-remove{width:28px}.c-token-remove mat-icon{font-size:14px;width:14px;height:14px}.c-token-remove:hover{background:#0000000d}\n"] }]
949
+ }] });
950
+
951
+ /* eslint-disable @typescript-eslint/no-explicit-any */
952
+ /**
953
+ * Compact popover-mode chip face. Click → opens the parent-supplied [menu] (mat-menu
954
+ * containing the chip editor). Inert div[role=button] so the nested <sd-operator>
955
+ * doesn't end up as a button-in-button (invalid HTML).
956
+ */
957
+ class SdQueryPopoverChip {
958
+ /** Resolved field (from the bar's `fieldByKey()` map). `undefined` → render raw key. */
959
+ field = input(undefined);
960
+ /** The chip's filter — used to read `field` / `operator` / `data` for display text. */
961
+ filter = input.required();
962
+ /** "Active" = chip has a real value or is no-data op. Drives layout + colour. */
963
+ active = input.required();
964
+ /** Operator visible on the chip face (else hidden, ":" separator). */
965
+ showOperator = input(false);
966
+ /** Rendered value text for the value slot. */
967
+ valueText = input('');
968
+ /** Chip popover mat-menu instance supplied by the parent. */
969
+ menu = input.required();
970
+ /** Fired when the chip popover opens (parent seeds its editing state). */
971
+ open = output();
972
+ /** Fired when the user clicks the × removal icon. */
973
+ remove = output();
974
+ iconFor = sdQueryFieldIcon;
975
+ filterField() { return this.filter().field; }
976
+ filterOperator() { return this.filter().operator; }
977
+ // why: parent's add/swap flow auto-opens the chip popover after render, and removeFilter
978
+ // closes the open one — expose open/close so the parent doesn't need a ViewChild on the
979
+ // internal MatMenuTrigger directive.
980
+ trigger = viewChild('chipTrigger', { read: MatMenuTrigger });
981
+ openMenu() { this.trigger()?.openMenu(); }
982
+ closeMenu() { this.trigger()?.closeMenu(); }
983
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdQueryPopoverChip, deps: [], target: i0.ɵɵFactoryTarget.Component });
984
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: SdQueryPopoverChip, isStandalone: true, selector: "sd-query-popover-chip", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: false, transformFunction: null }, filter: { classPropertyName: "filter", publicName: "filter", isSignal: true, isRequired: true, transformFunction: null }, active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: true, transformFunction: null }, showOperator: { classPropertyName: "showOperator", publicName: "showOperator", isSignal: true, isRequired: false, transformFunction: null }, valueText: { classPropertyName: "valueText", publicName: "valueText", isSignal: true, isRequired: false, transformFunction: null }, menu: { classPropertyName: "menu", publicName: "menu", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { open: "open", remove: "remove" }, viewQueries: [{ propertyName: "trigger", first: true, predicate: ["chipTrigger"], descendants: true, read: MatMenuTrigger, isSignal: true }], ngImport: i0, template: "<!-- why: div[role=button] (not <button>) so the nested <sd-operator> isn't button-in-button (invalid HTML). -->\r\n<div\r\n #chipTrigger=\"matMenuTrigger\"\r\n role=\"button\"\r\n tabindex=\"0\"\r\n class=\"c-chip\"\r\n [class.c-chip-active]=\"active()\"\r\n [class.c-chip-inactive]=\"!active()\"\r\n [attr.data-field]=\"filterField()\"\r\n [matTooltip]=\"field()?.label || filterField()\"\r\n [matMenuTriggerFor]=\"menu()\"\r\n (menuOpened)=\"open.emit()\"\r\n (keydown.enter)=\"chipTrigger.openMenu()\"\r\n (keydown.space)=\"$event.preventDefault(); chipTrigger.openMenu()\">\r\n <mat-icon class=\"c-chip-icon\" fontSet=\"material-icons-outlined\">\r\n {{ field() ? iconFor(field()!) : 'tune' }}\r\n </mat-icon>\r\n <span class=\"c-chip-label\">{{ field()?.label || filterField() }}</span>\r\n\r\n @if (showOperator() && active()) {\r\n <sd-operator class=\"c-chip-operator\" [operators]=\"[filterOperator()]\" [model]=\"filterOperator()\" [disabled]=\"true\" />\r\n } @else if (active()) {\r\n <span class=\"c-chip-sep\">:</span>\r\n }\r\n\r\n @if (active()) {\r\n <span class=\"c-chip-value\">{{ valueText() }}</span>\r\n }\r\n\r\n <mat-icon class=\"c-chip-chevron\" fontSet=\"material-icons-outlined\">arrow_drop_down</mat-icon>\r\n\r\n @if (active()) {\r\n <span\r\n role=\"button\"\r\n tabindex=\"0\"\r\n class=\"c-chip-remove\"\r\n aria-label=\"Remove filter\"\r\n (click)=\"$event.stopPropagation(); remove.emit()\"\r\n (keydown.enter)=\"$event.stopPropagation(); remove.emit()\">\r\n <mat-icon fontSet=\"material-icons-outlined\">close</mat-icon>\r\n </span>\r\n }\r\n</div>\r\n", styles: [":host{display:contents}.c-chip{display:inline-flex;align-items:center;gap:4px;padding:0 4px 0 10px;border-radius:999px;border:1px solid #d6d8db;background:#fff;cursor:pointer;font-family:inherit;font-size:13px;font-weight:500;color:#5f6368;transition:background-color .12s ease,border-color .12s ease,color .12s ease}:host-context(.c-density-compact) .c-chip{height:28px}:host-context(.c-density-comfortable) .c-chip{height:32px}.c-chip:hover{border-color:var(--sd-primary)}.c-chip.c-chip-active{border-color:var(--sd-primary-light);background:var(--sd-primary-light)}.c-chip.c-chip-active .c-chip-value{color:var(--sd-primary)}.c-chip.c-chip-inactive{padding-right:10px}.c-chip .c-chip-icon{font-size:16px;width:16px;height:16px}.c-chip .c-chip-label{font-weight:500;white-space:nowrap}.c-chip .c-chip-operator{display:inline-flex;align-items:center;color:inherit}.c-chip .c-chip-sep{color:#7a7a7a}.c-chip .c-chip-value{font-weight:500;max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.c-chip .c-chip-chevron{font-size:18px;width:18px;height:18px;color:currentColor}.c-chip .c-chip-remove{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:50%;cursor:pointer;color:#7a7a7a}.c-chip .c-chip-remove mat-icon{font-size:14px;width:14px;height:14px}.c-chip .c-chip-remove:hover{background:#0000000f}.c-chip-operator ::ng-deep .c-op-trigger{padding:0;color:inherit}.c-chip-operator ::ng-deep .c-op-icon svg{width:14px;height:14px;display:block}\n"], dependencies: [{ kind: "ngmodule", type: MatMenuModule }, { kind: "directive", type: i1.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i3.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: SdOperator, selector: "sd-operator", inputs: ["model", "operators", "disabled", "autoId"], outputs: ["modelChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
985
+ }
986
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdQueryPopoverChip, decorators: [{
987
+ type: Component,
988
+ args: [{ selector: 'sd-query-popover-chip', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [MatMenuModule, MatIconModule, MatTooltipModule, SdOperator], template: "<!-- why: div[role=button] (not <button>) so the nested <sd-operator> isn't button-in-button (invalid HTML). -->\r\n<div\r\n #chipTrigger=\"matMenuTrigger\"\r\n role=\"button\"\r\n tabindex=\"0\"\r\n class=\"c-chip\"\r\n [class.c-chip-active]=\"active()\"\r\n [class.c-chip-inactive]=\"!active()\"\r\n [attr.data-field]=\"filterField()\"\r\n [matTooltip]=\"field()?.label || filterField()\"\r\n [matMenuTriggerFor]=\"menu()\"\r\n (menuOpened)=\"open.emit()\"\r\n (keydown.enter)=\"chipTrigger.openMenu()\"\r\n (keydown.space)=\"$event.preventDefault(); chipTrigger.openMenu()\">\r\n <mat-icon class=\"c-chip-icon\" fontSet=\"material-icons-outlined\">\r\n {{ field() ? iconFor(field()!) : 'tune' }}\r\n </mat-icon>\r\n <span class=\"c-chip-label\">{{ field()?.label || filterField() }}</span>\r\n\r\n @if (showOperator() && active()) {\r\n <sd-operator class=\"c-chip-operator\" [operators]=\"[filterOperator()]\" [model]=\"filterOperator()\" [disabled]=\"true\" />\r\n } @else if (active()) {\r\n <span class=\"c-chip-sep\">:</span>\r\n }\r\n\r\n @if (active()) {\r\n <span class=\"c-chip-value\">{{ valueText() }}</span>\r\n }\r\n\r\n <mat-icon class=\"c-chip-chevron\" fontSet=\"material-icons-outlined\">arrow_drop_down</mat-icon>\r\n\r\n @if (active()) {\r\n <span\r\n role=\"button\"\r\n tabindex=\"0\"\r\n class=\"c-chip-remove\"\r\n aria-label=\"Remove filter\"\r\n (click)=\"$event.stopPropagation(); remove.emit()\"\r\n (keydown.enter)=\"$event.stopPropagation(); remove.emit()\">\r\n <mat-icon fontSet=\"material-icons-outlined\">close</mat-icon>\r\n </span>\r\n }\r\n</div>\r\n", styles: [":host{display:contents}.c-chip{display:inline-flex;align-items:center;gap:4px;padding:0 4px 0 10px;border-radius:999px;border:1px solid #d6d8db;background:#fff;cursor:pointer;font-family:inherit;font-size:13px;font-weight:500;color:#5f6368;transition:background-color .12s ease,border-color .12s ease,color .12s ease}:host-context(.c-density-compact) .c-chip{height:28px}:host-context(.c-density-comfortable) .c-chip{height:32px}.c-chip:hover{border-color:var(--sd-primary)}.c-chip.c-chip-active{border-color:var(--sd-primary-light);background:var(--sd-primary-light)}.c-chip.c-chip-active .c-chip-value{color:var(--sd-primary)}.c-chip.c-chip-inactive{padding-right:10px}.c-chip .c-chip-icon{font-size:16px;width:16px;height:16px}.c-chip .c-chip-label{font-weight:500;white-space:nowrap}.c-chip .c-chip-operator{display:inline-flex;align-items:center;color:inherit}.c-chip .c-chip-sep{color:#7a7a7a}.c-chip .c-chip-value{font-weight:500;max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.c-chip .c-chip-chevron{font-size:18px;width:18px;height:18px;color:currentColor}.c-chip .c-chip-remove{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:50%;cursor:pointer;color:#7a7a7a}.c-chip .c-chip-remove mat-icon{font-size:14px;width:14px;height:14px}.c-chip .c-chip-remove:hover{background:#0000000f}.c-chip-operator ::ng-deep .c-op-trigger{padding:0;color:inherit}.c-chip-operator ::ng-deep .c-op-icon svg{width:14px;height:14px;display:block}\n"] }]
989
+ }] });
289
990
 
290
991
  /* eslint-disable @typescript-eslint/no-explicit-any */
291
992
  /** Shared empty array — stable reference so template bindings don't churn change detection. */
@@ -293,32 +994,24 @@ const EMPTY_ARRAY = [];
293
994
  class SdQueryBar {
294
995
  #i18n = inject(I18nService);
295
996
  #injector = inject(Injector);
296
- #host = inject(ElementRef);
297
- /** Focus the inline text/number value control once the build chip is in the value step. */
298
- #focusBuildValue() {
299
- afterNextRender(() => {
300
- const el = this.#host.nativeElement.querySelector('.c-token-building input, .c-token-building textarea');
301
- el?.focus();
302
- }, { injector: this.#injector });
303
- }
304
- /** All chip menu triggers (one per chip button). Used to auto-open the popover after addFilter. */
305
- chipTriggers = viewChildren('chipTrigger');
306
- /** The build chip's operator dropdown — auto-opened when entering the operator step. */
307
- buildOperator = viewChild('buildOperator');
308
- /** The build chip's value picker (bare sd-select / sd-date / sd-datetime) — auto-opened at the value step. */
309
- buildPicker = viewChild('bPicker');
997
+ /** All popover-mode chips in order — used to auto-open after addFilter / close before removeFilter. */
998
+ popoverChips = viewChildren(SdQueryPopoverChip);
999
+ /** The extracted in-progress chip — owns operator menu + value picker view refs. */
1000
+ buildChip = viewChild(SdQueryBuildChip);
1001
+ /** The extracted chip popover — owns operator+value staging + commit-on-close. */
1002
+ chipPopover = viewChild(SdQueryChipPopover);
310
1003
  /**
311
1004
  * Land the user in the value editor after the build chip reaches the value step:
312
- * popover-kind controls auto-open their native picker; text/number just take focus.
1005
+ * popover-kind controls auto-open their native picker via the child; string/number
1006
+ * branches use the seamless chip whose `[autofocus]` handles focus internally.
313
1007
  */
314
1008
  #enterValueStep(field) {
315
- const k = field.kind;
1009
+ const k = field.type;
316
1010
  if (k === 'values' || k === 'lazy-values' || k === 'date' || k === 'datetime') {
317
- afterNextRender(() => this.buildPicker()?.open(), { injector: this.#injector });
318
- }
319
- else {
320
- this.#focusBuildValue();
1011
+ afterNextRender(() => this.buildChip()?.openPicker(), { injector: this.#injector });
321
1012
  }
1013
+ // why: string/number branch — <sd-query-inline-value-chip [autofocus]> tự focus,
1014
+ // không cần parent gọi thêm.
322
1015
  }
323
1016
  // ---------------------------------------------------------------------------
324
1017
  // Inputs — all accept null|undefined at boundary, transform to canonical shape
@@ -361,8 +1054,26 @@ class SdQueryBar {
361
1054
  });
362
1055
  /** Show free-text search input on the left. */
363
1056
  showSearch = input(false, { transform: booleanAttribute });
364
- /** Show saved-views dropdown on the right. */
365
- showSavedViews = input(false, { transform: booleanAttribute });
1057
+ /** Show saved-filters dropdown on the right. Requires `[savedFiltersKey]` to actually persist. */
1058
+ showSavedFilters = input(false, { transform: booleanAttribute });
1059
+ /**
1060
+ * Namespace key for persisting saved filters to `localStorage`. When set, the saved-filters
1061
+ * dropdown loads/saves under `sd-query-bar:savedFilters:<key>`. Leave undefined to disable.
1062
+ */
1063
+ savedFiltersKey = input(undefined);
1064
+ /**
1065
+ * Snapshot of the current query — fed into `<sd-query-saved-filters-menu [query]>` so
1066
+ * saving a filter captures live filters/logic/search.
1067
+ */
1068
+ currentQuery = computed(() => this.#buildQuery());
1069
+ /** Receive an applied filter from the saved-filters menu — re-install bar state + trigger apply. */
1070
+ onApplyFilter(saved) {
1071
+ this.filters.set(saved.query.filters ?? []);
1072
+ this.logic.set(saved.query.logic ?? 'AND');
1073
+ if (saved.query.search !== undefined)
1074
+ this.search.set(saved.query.search);
1075
+ this.triggerApply();
1076
+ }
366
1077
  /** Show AND/OR segmented toggle (renders only when there are ≥2 filters). */
367
1078
  showLogicToggle = input(false, { transform: booleanAttribute });
368
1079
  /** Show "Clear all" button when at least one filter is active. */
@@ -400,19 +1111,12 @@ class SdQueryBar {
400
1111
  /** Search button is actionable only when there is something to apply. */
401
1112
  canSearch = computed(() => this.filters().length > 0 || this.search().trim().length > 0);
402
1113
  // ---------------------------------------------------------------------------
403
- // Chip popover state — staging area (operator + value) for the open chip,
404
- // committed on "Áp dụng", discarded on close-without-apply.
1114
+ // Chip popover state — only `editingIndex` lives on the parent; the
1115
+ // `<sd-query-chip-popover>` child owns ALL staging (operator + value + options).
405
1116
  // ---------------------------------------------------------------------------
406
- /** Index of the chip whose popover is open. Reset to null after Apply / close. */
1117
+ /** Index of the chip whose popover is open. Reset to null after commit / close. */
407
1118
  editingIndex = signal(null);
408
- /** Staged operator (uncommitted). */
409
- editingOperator = signal('EQUAL');
410
- /** Staged value (uncommitted) — type varies by kind. */
411
- editingValue = signal(null);
412
- /** Resolved option list for the currently editing values / lazy-values field. */
413
- editingOptions = signal([]);
414
- /** Loading state for async option fetch. */
415
- editingOptionsLoading = signal(false);
1119
+ /** Resolved field of the chip whose popover is currently open (or null). */
416
1120
  editingField = computed(() => {
417
1121
  const idx = this.editingIndex();
418
1122
  if (idx === null)
@@ -422,22 +1126,13 @@ class SdQueryBar {
422
1126
  return null;
423
1127
  return this.fieldByKey()[f.field] ?? null;
424
1128
  });
425
- editingAllowedOperators = computed(() => {
426
- const field = this.editingField();
427
- return field ? sdQueryAllowedOperators(field) : [];
428
- });
429
- /** Show the operator dropdown only when the field exposes >1 operator (else simple mode). */
430
- editingShowOperatorSelector = computed(() => {
431
- const field = this.editingField();
432
- return field ? sdQueryShowOperatorSelector(field) : false;
433
- });
434
- /** Multi-select is derived from the operator (IN / NOT_IN), not a field flag. */
435
- editingIsMulti = computed(() => SD_QUERY_MULTI_OPERATORS.includes(this.editingOperator()));
436
- /** Compose a deterministic autoId for an inner control inside a chip's editor. */
437
- chipAutoId(role) {
438
- const idx = this.editingIndex();
439
- const base = this.autoIdInput() ?? 'qb';
440
- return `${base}-chip${idx ?? 'x'}-${role}`;
1129
+ /**
1130
+ * Multi-select is derived from the operator (IN / NOT_IN) — not a field flag.
1131
+ * Only called by inline-mode + build-mode templates with a known operator;
1132
+ * popover-mode chips read `multiple()` from the child popover directly.
1133
+ */
1134
+ multiple(op) {
1135
+ return SD_QUERY_MULTI_OPERATORS.includes(op);
441
1136
  }
442
1137
  // ---------------------------------------------------------------------------
443
1138
  // Template helpers
@@ -475,51 +1170,12 @@ class SdQueryBar {
475
1170
  isNoDataOperator(op) {
476
1171
  return SD_QUERY_NO_DATA_OPERATORS.includes(op);
477
1172
  }
478
- isMultiOperator(op) {
479
- return SD_QUERY_MULTI_OPERATORS.includes(op);
480
- }
481
- /**
482
- * Stable `SdSearch` per lazy-values field key so `sd-select` owns search + chip-display
483
- * lookups. why: memoized via a computed map so `[items]="lazyItemsFor(field)"` returns the
484
- * SAME reference each change-detection pass — a fresh function each time would make
485
- * sd-select wipe its option cache and refetch on every CD. Recomputes only when `fields` changes.
486
- * SEARCH -> field.option.search; VALUE -> field.option.views.
487
- */
488
- #lazySearchByKey = computed(() => {
489
- const map = {};
490
- for (const f of this.fields()) {
491
- if (f.kind !== 'lazy-values')
492
- continue;
493
- const opt = f.option;
494
- map[f.key] = async (req) => {
495
- if (req.type === 'VALUE') {
496
- const values = Array.isArray(req.value) ? req.value : req.value == null ? [] : [req.value];
497
- return opt?.views ? await opt.views(values) : [];
498
- }
499
- const result = opt?.search?.({ search: req.searchText ?? '' });
500
- if (!result)
501
- return [];
502
- return isObservable(result) ? await firstValueFrom(result) : await result;
503
- };
504
- }
505
- return map;
506
- });
507
- lazyItemsFor(field) {
508
- return this.#lazySearchByKey()[field.key];
509
- }
510
1173
  // ---------------------------------------------------------------------------
511
1174
  // Inline mode — per-field helpers + live mutators (no popover / no Apply gate)
512
1175
  // ---------------------------------------------------------------------------
513
1176
  /** The single in-progress inline chip (not yet in `filters`). */
514
1177
  #building = signal(null);
515
1178
  building = this.#building.asReadonly();
516
- /** Index of the completed chip whose value is being inline-edited (inline mode). */
517
- #editingValueIndex = signal(null);
518
- editingValueIndex = this.#editingValueIndex.asReadonly();
519
- /** True when chip `i` is in inline value-edit mode. */
520
- isEditingValue(i) {
521
- return this.#editingValueIndex() === i;
522
- }
523
1179
  allowedOperatorsFor(field) {
524
1180
  return sdQueryAllowedOperators(field);
525
1181
  }
@@ -547,16 +1203,20 @@ class SdQueryBar {
547
1203
  }
548
1204
  /** Entry point from the field picker — start building a chip for `field`. */
549
1205
  beginBuild(field) {
550
- this.#editingValueIndex.set(null);
551
1206
  const allowed = sdQueryAllowedOperators(field);
552
- if (allowed.length > 1) {
1207
+ const def = sdQueryDefaultOperator(field);
1208
+ // why: date/datetime mặc định là BETWEEN — skip operator step để user vào value
1209
+ // step ngay (95% case dùng BETWEEN cho date/datetime; cắt 1 click cho luồng phổ
1210
+ // biến nhất). Field type khác giữ flow chọn operator để user thấy đầy đủ lựa chọn.
1211
+ const skipOperatorStep = (field.type === 'date' || field.type === 'datetime') && allowed.includes(def);
1212
+ if (allowed.length > 1 && !skipOperatorStep) {
553
1213
  this.#building.set({ field, step: 'operator' });
554
1214
  // why: auto-open the operator menu so the user can pick the condition without a
555
1215
  // second click — the viewChild only resolves after the building chip renders.
556
- afterNextRender(() => this.buildOperator()?.open(), { injector: this.#injector });
1216
+ afterNextRender(() => this.buildChip()?.openOperator(), { injector: this.#injector });
557
1217
  return;
558
1218
  }
559
- const operator = allowed[0] ?? sdQueryDefaultOperator(field);
1219
+ const operator = skipOperatorStep ? def : (allowed[0] ?? def);
560
1220
  if (SD_QUERY_NO_DATA_OPERATORS.includes(operator)) {
561
1221
  this.#pushComplete(field, operator, null);
562
1222
  this.#building.set(null);
@@ -606,113 +1266,14 @@ class SdQueryBar {
606
1266
  cancelBuild() {
607
1267
  this.#building.set(null);
608
1268
  }
609
- /** Latest value typed/selected during inline value edit (chip already in `filters`). */
610
- #editDraft = signal(null);
611
- /** Live-commit an inline edit for a completed chip (bare control path). */
612
- editValueFn(i) {
613
- return (v) => this.updateFilter(i, { data: v });
614
- }
615
- /** Single-value commit from a completed chip in edit mode — write data + exit edit. */
616
- onChipSingleCommit(i, v) {
617
- this.updateFilter(i, { data: v });
618
- this.#editingValueIndex.set(null);
619
- }
620
- /**
621
- * Multi-value chips exit edit when focus actually leaves the wrapper subtree.
622
- * why: focusout fires on every internal blur (option click, search input, …) — only
623
- * exit when the new focus target is NOT a descendant of the chip's value wrapper.
624
- */
625
- onChipValueFocusOut(i, ev) {
626
- const wrapper = ev.currentTarget;
627
- const next = ev.relatedTarget;
628
- // why: nếu event không có wrapper (ví dụ event được tạo thủ công, chưa dispatch),
629
- // không thể xác định focus có thoát khỏi subtree hay không → giữ nguyên edit mode.
630
- if (!wrapper)
631
- return;
632
- if (next && wrapper.contains(next))
633
- return;
634
- if (this.#editingValueIndex() === i)
635
- this.#editingValueIndex.set(null);
636
- }
637
- /**
638
- * Cập nhật đầu `from` của BETWEEN cho 1 chip đã commit (inline edit).
639
- * why: dual picker BETWEEN trên chip hoàn tất cần ghi từng đầu vào filter mà không reset đầu kia.
640
- */
641
- setFilterRangeFrom(i, v) {
642
- const cur = this.filters()[i];
643
- if (!cur)
644
- return;
645
- const curData = (cur.data && typeof cur.data === 'object') ? cur.data : {};
646
- this.updateFilter(i, { data: { ...curData, from: v } });
647
- }
648
- /**
649
- * Cập nhật đầu `to` của BETWEEN cho 1 chip đã commit (inline edit).
650
- */
651
- setFilterRangeTo(i, v) {
652
- const cur = this.filters()[i];
653
- if (!cur)
654
- return;
655
- const curData = (cur.data && typeof cur.data === 'object') ? cur.data : {};
656
- this.updateFilter(i, { data: { ...curData, to: v } });
657
- }
658
- noop = () => undefined;
659
- setEditDraftFn() {
660
- return (v) => this.#editDraft.set(v);
661
- }
662
- commitEditDraftFn(i) {
663
- return () => this.commitEditValue(i, this.#editDraft());
664
- }
665
- setBuildDraftFn() {
666
- return (v) => {
667
- const b = this.#building();
668
- if (b)
669
- this.#building.set({ ...b, value: v });
670
- };
671
- }
672
- /** Update the `from` end of the build chip's BETWEEN range without committing. */
673
- setBuildRangeFrom(v) {
674
- const b = this.#building();
675
- if (!b)
676
- return;
677
- const cur = (b.value && typeof b.value === 'object') ? b.value : {};
678
- this.#building.set({ ...b, value: { ...cur, from: v } });
679
- }
680
1269
  /**
681
- * Update the `to` end of the build chip's BETWEEN range. Once both ends are set, commit
682
- * the chip so the user does not need an extra blur / apply press.
1270
+ * Commit both ends of a BETWEEN range at once called from `<sd-query-inline-chip>`'s
1271
+ * `(commitRange)` output when the user picks a date range.
1272
+ * why: sd-date-range emits {from,to} via (sdChange) — single call thay cho cặp
1273
+ * setFilterRangeFrom / setFilterRangeTo cũ (đã bỏ).
683
1274
  */
684
- setBuildRangeTo(v) {
685
- const b = this.#building();
686
- if (!b)
687
- return;
688
- const cur = (b.value && typeof b.value === 'object') ? b.value : {};
689
- const next = { ...cur, to: v };
690
- this.#building.set({ ...b, value: next });
691
- if (next.from != null && next.to != null)
692
- this.commitBuildValue(next);
693
- }
694
- commitBuildDraftFn() {
695
- return () => {
696
- const b = this.#building();
697
- if (b)
698
- this.commitBuildValue(b.value);
699
- };
700
- }
701
- /** Start inline value edit on a completed chip (operator + field stay locked). */
702
- beginEditValue(i) {
703
- if (i < 0 || i >= this.filters().length)
704
- return;
705
- this.#building.set(null);
706
- const filter = this.filters()[i];
707
- // why: seed the draft with the current value so Enter can finalize the existing text
708
- // if the user presses Enter without retyping.
709
- this.#editDraft.set(filter.data ?? null);
710
- this.#editingValueIndex.set(i);
711
- }
712
- /** Commit an inline value edit — change only `data`, no emit. */
713
- commitEditValue(i, value) {
714
- this.updateFilter(i, { data: value });
715
- this.#editingValueIndex.set(i === this.#editingValueIndex() ? null : this.#editingValueIndex());
1275
+ setFilterRange(i, ev) {
1276
+ this.updateFilter(i, { data: ev ?? { from: null, to: null } });
716
1277
  }
717
1278
  // ---------------------------------------------------------------------------
718
1279
  // Mutations
@@ -725,12 +1286,7 @@ class SdQueryBar {
725
1286
  this.filters.set([...this.filters(), next]);
726
1287
  // why: user added a field — open its edit popover immediately so they can pick
727
1288
  // operator + value in one continuous flow (no second click required).
728
- afterNextRender(() => {
729
- const triggers = this.chipTriggers();
730
- const trigger = triggers[newIndex];
731
- if (trigger)
732
- trigger.openMenu();
733
- }, { injector: this.#injector });
1289
+ afterNextRender(() => this.popoverChips()[newIndex]?.openMenu(), { injector: this.#injector });
734
1290
  }
735
1291
  /** Swap the field of an existing chip — reset operator + value to the new field's defaults. */
736
1292
  changeFilterField(index, field) {
@@ -744,11 +1300,7 @@ class SdQueryBar {
744
1300
  // why: clicking an item in the nested fieldSwitchPicker closes both the picker AND
745
1301
  // the parent chipPopover. Re-open the chip's popover on the next render so user can
746
1302
  // continue picking operator + value without a second manual click.
747
- afterNextRender(() => {
748
- const trigger = this.chipTriggers()[index];
749
- if (trigger)
750
- trigger.openMenu();
751
- }, { injector: this.#injector });
1303
+ afterNextRender(() => this.popoverChips()[index]?.openMenu(), { injector: this.#injector });
752
1304
  }
753
1305
  updateFilter(index, patch) {
754
1306
  const list = [...this.filters()];
@@ -763,11 +1315,9 @@ class SdQueryBar {
763
1315
  return;
764
1316
  // close the open popover (if it's this chip's) before the chip + its trigger vanish
765
1317
  if (this.editingIndex() === index) {
766
- this.chipTriggers()[index]?.closeMenu();
1318
+ this.popoverChips()[index]?.closeMenu();
767
1319
  this.editingIndex.set(null);
768
1320
  }
769
- if (this.#editingValueIndex() === index)
770
- this.#editingValueIndex.set(null);
771
1321
  list.splice(index, 1);
772
1322
  this.filters.set(list);
773
1323
  }
@@ -795,97 +1345,25 @@ class SdQueryBar {
795
1345
  this.apply.emit(q);
796
1346
  }
797
1347
  // ---------------------------------------------------------------------------
798
- // Chip popover — stage edits, commit on close
1348
+ // Chip popover — delegate staging to `<sd-query-chip-popover>` child.
799
1349
  // ---------------------------------------------------------------------------
800
- /** Called when a chip is clicked — copy current operator + value into the staging signals. */
1350
+ /**
1351
+ * Called when a popover-mode chip is clicked — set editingIndex and seed the
1352
+ * child popover's staging signals from the chip's Filter.
1353
+ */
801
1354
  openChipPopover(index) {
802
1355
  const filter = this.filters()[index];
803
1356
  if (!filter || !('field' in filter))
804
1357
  return;
805
1358
  this.editingIndex.set(index);
806
- this.editingOperator.set(filter.operator);
807
- this.editingValue.set(filter.data ?? null);
808
1359
  const field = this.fieldByKey()[filter.field];
809
- if (field?.kind === 'values') {
810
- this.#loadValuesOptions(field);
811
- }
812
- else if (field?.kind === 'lazy-values') {
813
- this.#loadLazyOptions(field);
814
- }
815
- else {
816
- this.editingOptions.set([]);
817
- this.editingOptionsLoading.set(false);
818
- }
819
- }
820
- /** Called on operator <select> change in the popover. */
821
- onEditingOperatorChange(next) {
822
- const prev = this.editingOperator();
823
- this.editingOperator.set(next);
824
- // Reset / reshape staged value when operator family changes
825
- if (SD_QUERY_NO_DATA_OPERATORS.includes(next)) {
826
- this.editingValue.set(null);
827
- return;
828
- }
829
- if (next === 'BETWEEN') {
830
- // Convert single → range
831
- const v = this.editingValue();
832
- if (v && typeof v === 'object' && 'from' in v)
833
- return;
834
- this.editingValue.set({ from: null, to: null });
835
- return;
836
- }
837
- const goingToMulti = SD_QUERY_MULTI_OPERATORS.includes(next);
838
- const wasMulti = SD_QUERY_MULTI_OPERATORS.includes(prev);
839
- if (goingToMulti && !wasMulti) {
840
- const v = this.editingValue();
841
- this.editingValue.set(v == null ? [] : [v]);
842
- }
843
- else if (!goingToMulti && wasMulti) {
844
- const v = this.editingValue();
845
- this.editingValue.set(Array.isArray(v) && v.length > 0 ? v[0] : null);
846
- }
847
- }
848
- /** Two-way bind for primitive value control. */
849
- onEditingValueInput(value) {
850
- this.editingValue.set(value);
851
- }
852
- /** BETWEEN range mutators. */
853
- onEditingRangeFrom(value) {
854
- const v = this.editingValue() ?? {};
855
- this.editingValue.set({ ...v, from: value });
856
- }
857
- onEditingRangeTo(value) {
858
- const v = this.editingValue() ?? {};
859
- this.editingValue.set({ ...v, to: value });
1360
+ this.chipPopover()?.seed(filter, field);
860
1361
  }
861
- /** Toggle a value inside the staged multi-select array. */
862
- toggleEditingMultiValue(value) {
863
- const arr = Array.isArray(this.editingValue()) ? [...this.editingValue()] : [];
864
- const idx = arr.indexOf(value);
865
- if (idx >= 0)
866
- arr.splice(idx, 1);
867
- else
868
- arr.push(value);
869
- this.editingValue.set(arr);
870
- }
871
- isEditingMultiSelected(value) {
872
- const v = this.editingValue();
873
- return Array.isArray(v) && v.includes(value);
874
- }
875
- /**
876
- * Commit the staged operator + value into the chip when the popover closes.
877
- * why: nút Áp dụng đã bị bỏ — đóng popover (out-focus) tự động áp dụng phần
878
- * chỉnh sửa đang staging. Không emit: nút Search toàn cục vẫn là trigger query duy nhất.
879
- */
880
- commitChipEditOnClose() {
1362
+ /** Receive `(commit)` from the child popover — splice into filters, null the index. */
1363
+ onChipPopoverCommit(next) {
881
1364
  const idx = this.editingIndex();
882
1365
  if (idx === null)
883
1366
  return;
884
- const next = {
885
- field: this.filters()[idx].field,
886
- operator: this.editingOperator(),
887
- data: this.editingValue(),
888
- };
889
1367
  const list = [...this.filters()];
890
1368
  list[idx] = next;
891
1369
  this.filters.set(list);
@@ -906,18 +1384,19 @@ class SdQueryBar {
906
1384
  const field = this.fieldByKey()[fieldKey];
907
1385
  if (!field)
908
1386
  return String(raw);
909
- if (field.kind === 'boolean') {
1387
+ if (field.type === 'boolean') {
910
1388
  const trueLabel = field.trueLabel ?? 'Có';
911
1389
  const falseLabel = field.falseLabel ?? 'Không';
912
1390
  return raw ? trueLabel : falseLabel;
913
1391
  }
914
- if (field.kind === 'values' || field.kind === 'lazy-values') {
1392
+ if (field.type === 'values' || field.type === 'lazy-values') {
915
1393
  const opt = field.option;
916
- const items = this.#resolveSyncItems(opt?.items) ?? this.editingOptions();
1394
+ const cachedOptions = this.chipPopover()?.editingOptions() ?? [];
1395
+ const items = this.#resolveSyncItems(opt?.items) ?? cachedOptions;
917
1396
  const match = items.find((i) => i?.[opt.valueField] === raw);
918
1397
  if (match)
919
1398
  return String(match[opt.displayField]);
920
- const cached = this.editingOptions().find((i) => i?.[opt.valueField] === raw);
1399
+ const cached = cachedOptions.find((i) => i?.[opt.valueField] === raw);
921
1400
  return cached ? String(cached[opt.displayField]) : String(raw);
922
1401
  }
923
1402
  return String(raw);
@@ -929,47 +1408,8 @@ class SdQueryBar {
929
1408
  return items();
930
1409
  return null;
931
1410
  }
932
- #loadValuesOptions(field) {
933
- const opt = field.option;
934
- const items = opt?.items;
935
- if (Array.isArray(items)) {
936
- this.editingOptions.set(items);
937
- this.editingOptionsLoading.set(false);
938
- return;
939
- }
940
- if (isSignal(items)) {
941
- this.editingOptions.set(items());
942
- this.editingOptionsLoading.set(false);
943
- return;
944
- }
945
- if (typeof items === 'function') {
946
- this.editingOptionsLoading.set(true);
947
- Promise.resolve(items())
948
- .then((arr) => this.editingOptions.set(arr ?? []))
949
- .finally(() => this.editingOptionsLoading.set(false));
950
- }
951
- }
952
- #loadLazyOptions(field, search = '') {
953
- const opt = field.option;
954
- if (!opt?.search)
955
- return;
956
- this.editingOptionsLoading.set(true);
957
- const result = opt.search({ search });
958
- if (isObservable(result)) {
959
- result.subscribe({
960
- next: (arr) => this.editingOptions.set(arr ?? []),
961
- complete: () => this.editingOptionsLoading.set(false),
962
- error: () => this.editingOptionsLoading.set(false),
963
- });
964
- }
965
- else {
966
- Promise.resolve(result)
967
- .then((arr) => this.editingOptions.set(arr ?? []))
968
- .finally(() => this.editingOptionsLoading.set(false));
969
- }
970
- }
971
1411
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdQueryBar, deps: [], target: i0.ɵɵFactoryTarget.Component });
972
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: SdQueryBar, isStandalone: true, selector: "sd-query-bar", inputs: { autoIdInput: { classPropertyName: "autoIdInput", publicName: "autoId", isSignal: true, isRequired: false, transformFunction: null }, fields: { classPropertyName: "fields", publicName: "fields", isSignal: true, isRequired: false, transformFunction: null }, filters: { classPropertyName: "filters", publicName: "filters", isSignal: true, isRequired: false, transformFunction: null }, logic: { classPropertyName: "logic", publicName: "logic", isSignal: true, isRequired: false, transformFunction: null }, search: { classPropertyName: "search", publicName: "search", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, density: { classPropertyName: "density", publicName: "density", isSignal: true, isRequired: false, transformFunction: null }, showSearch: { classPropertyName: "showSearch", publicName: "showSearch", isSignal: true, isRequired: false, transformFunction: null }, showSavedViews: { classPropertyName: "showSavedViews", publicName: "showSavedViews", isSignal: true, isRequired: false, transformFunction: null }, showLogicToggle: { classPropertyName: "showLogicToggle", publicName: "showLogicToggle", isSignal: true, isRequired: false, transformFunction: null }, showClearAll: { classPropertyName: "showClearAll", publicName: "showClearAll", isSignal: true, isRequired: false, transformFunction: null }, showOperatorOnChip: { classPropertyName: "showOperatorOnChip", publicName: "showOperatorOnChip", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { filters: "filtersChange", logic: "logicChange", search: "searchChange", queryChange: "queryChange", apply: "apply" }, viewQueries: [{ propertyName: "chipTriggers", predicate: ["chipTrigger"], descendants: true, isSignal: true }, { propertyName: "buildOperator", first: true, predicate: ["buildOperator"], descendants: true, isSignal: true }, { propertyName: "buildPicker", first: true, predicate: ["bPicker"], descendants: true, isSignal: true }], ngImport: i0, template: "<!--\r\n sd-query-bar \u2014 unified filter chip row (Jira/Linear/Notion style).\r\n Visual spec: refs/design_handoff_sd_query_bar/README.md\r\n TODO(next-iter): replace inline placeholders for field-picker + chip-popover\r\n with extracted sub-components driven by CDK overlay.\r\n-->\r\n@let _filters = filters();\r\n@let _fields = fields();\r\n@let _logic = logic();\r\n@let _density = density();\r\n@let _showOr = showOrConnector();\r\n@let _used = usedFieldKeys();\r\n\r\n<div\r\n class=\"c-query-bar\"\r\n [class.c-density-compact]=\"_density === 'compact'\"\r\n [class.c-density-comfortable]=\"_density === 'comfortable'\">\r\n\r\n <!-- Main region \u2014 search + filters + add-filter. Wraps internally; the right\r\n action toolbar stays anchored and does NOT flow with this. -->\r\n <div class=\"c-query-bar__main\">\r\n\r\n <!-- Free-text search (left, optional) -->\r\n @if (showSearch()) {\r\n <div class=\"c-search\">\r\n <mat-icon class=\"c-search-icon\" fontSet=\"material-icons-outlined\">search</mat-icon>\r\n <input\r\n class=\"c-search-input\"\r\n type=\"text\"\r\n [ngModel]=\"search()\"\r\n (ngModelChange)=\"setSearch($event)\"\r\n (keydown.enter)=\"triggerApply()\"\r\n placeholder=\"T\u00ECm ki\u1EBFm...\" />\r\n </div>\r\n <span class=\"c-divider\" aria-hidden=\"true\"></span>\r\n }\r\n\r\n <!-- Field switcher (used by chipPopover header) \u2014 declared first so chipPopover's\r\n nested ng-template can resolve the template ref. -->\r\n <mat-menu #fieldSwitchPicker=\"matMenu\" xPosition=\"after\" yPosition=\"below\" class=\"c-field-picker-menu\">\r\n <ng-template matMenuContent let-currentIndex=\"currentIndex\">\r\n @let _currentFilter = filters()[currentIndex];\r\n @let _currentKey = $any(_currentFilter)?.field;\r\n @for (field of _fields; track $index) {\r\n @let _isCurrent = field.key === _currentKey;\r\n <button\r\n type=\"button\"\r\n mat-menu-item\r\n [disabled]=\"_isCurrent || field.disabled\"\r\n (click)=\"changeFilterField(currentIndex, field)\">\r\n <mat-icon fontSet=\"material-icons-outlined\" class=\"c-field-item-icon\">\r\n {{ iconFor(field) }}\r\n </mat-icon>\r\n <span class=\"c-field-item-label\">{{ field.label }}</span>\r\n <span class=\"c-field-item-kind\">{{ field.kind }}</span>\r\n @if (_isCurrent) {\r\n <mat-icon class=\"c-field-item-check\" fontSet=\"material-icons-outlined\">check</mat-icon>\r\n }\r\n </button>\r\n }\r\n </ng-template>\r\n </mat-menu>\r\n\r\n <!-- Chip popover \u2014 declared BEFORE chips so @for can resolve the forward template ref -->\r\n <mat-menu #chipPopover=\"matMenu\" class=\"c-chip-popover\" xPosition=\"after\" yPosition=\"below\">\r\n <ng-template matMenuContent let-index=\"index\">\r\n @let _editField = editingField();\r\n @let _editOp = editingOperator();\r\n @let _editVal = editingValue();\r\n @let _options = editingOptions();\r\n @let _loading = editingOptionsLoading();\r\n\r\n @if (_editField) {\r\n <div class=\"c-pop-header\" (click)=\"$event.stopPropagation()\">\r\n <mat-icon fontSet=\"material-icons-outlined\">{{ iconFor(_editField) }}</mat-icon>\r\n <!-- Click field name \u2192 open nested picker to swap field on this chip. -->\r\n <button\r\n type=\"button\"\r\n class=\"c-pop-header-field\"\r\n [matMenuTriggerFor]=\"fieldSwitchPicker\"\r\n [matMenuTriggerData]=\"{ currentIndex: index }\"\r\n matTooltip=\"\u0110\u1ED5i field\">\r\n <span>{{ _editField.label }}</span>\r\n <mat-icon fontSet=\"material-icons-outlined\">unfold_more</mat-icon>\r\n </button>\r\n <!-- why: operator moved into the header (right-aligned) to compact the popover.\r\n Always shown \u2014 when the field has \u22641 operator it renders disabled so the\r\n user still sees which operator the filter uses. -->\r\n <sd-operator\r\n class=\"c-pop-header-operator\"\r\n [autoId]=\"chipAutoId('operator')\"\r\n [operators]=\"editingAllowedOperators()\"\r\n [model]=\"_editOp\"\r\n [disabled]=\"!editingShowOperatorSelector()\"\r\n (modelChange)=\"onEditingOperatorChange($any($event))\" />\r\n </div>\r\n\r\n <div class=\"c-pop-body\" (click)=\"$event.stopPropagation()\">\r\n <!-- Value \u2014 per-kind sd-* control, skipped for NULL / NOT_NULL. Placeholder\r\n makes the field self-explanatory now that section labels are gone. -->\r\n @if (!isNoDataOperator(_editOp)) {\r\n @if (_editOp === 'BETWEEN') {\r\n @if (_editField.kind === 'date') {\r\n <div class=\"c-pop-between\">\r\n <sd-date size=\"sm\"\r\n [autoId]=\"chipAutoId('value-from')\"\r\n placeholder=\"T\u1EEB\"\r\n [model]=\"$any(_editVal)?.from\"\r\n (sdChange)=\"onEditingRangeFrom($event)\">\r\n </sd-date>\r\n <sd-date size=\"sm\"\r\n [autoId]=\"chipAutoId('value-to')\"\r\n placeholder=\"\u0110\u1EBFn\"\r\n [model]=\"$any(_editVal)?.to\"\r\n (sdChange)=\"onEditingRangeTo($event)\">\r\n </sd-date>\r\n </div>\r\n } @else if (_editField.kind === 'datetime') {\r\n <div class=\"c-pop-between\">\r\n <sd-datetime size=\"sm\"\r\n [autoId]=\"chipAutoId('value-from')\"\r\n placeholder=\"T\u1EEB\"\r\n [model]=\"$any(_editVal)?.from\"\r\n (sdChange)=\"onEditingRangeFrom($event)\">\r\n </sd-datetime>\r\n <sd-datetime size=\"sm\"\r\n [autoId]=\"chipAutoId('value-to')\"\r\n placeholder=\"\u0110\u1EBFn\"\r\n [model]=\"$any(_editVal)?.to\"\r\n (sdChange)=\"onEditingRangeTo($event)\">\r\n </sd-datetime>\r\n </div>\r\n } @else {\r\n <div class=\"c-pop-between\">\r\n <sd-input-number size=\"sm\"\r\n [autoId]=\"chipAutoId('value-from')\"\r\n placeholder=\"T\u1EEB\"\r\n [model]=\"$any(_editVal)?.from\"\r\n (sdChange)=\"onEditingRangeFrom($event)\">\r\n </sd-input-number>\r\n <sd-input-number size=\"sm\"\r\n [autoId]=\"chipAutoId('value-to')\"\r\n placeholder=\"\u0110\u1EBFn\"\r\n [model]=\"$any(_editVal)?.to\"\r\n (sdChange)=\"onEditingRangeTo($event)\">\r\n </sd-input-number>\r\n </div>\r\n }\r\n } @else if (_editField.kind === 'boolean') {\r\n <div class=\"c-pop-boolean\">\r\n <sd-button\r\n [autoId]=\"chipAutoId('value-true')\"\r\n [type]=\"_editVal === true ? 'fill' : 'outline'\"\r\n color=\"primary\"\r\n [title]=\"$any(_editField).trueLabel || 'C\u00F3'\"\r\n (click)=\"onEditingValueInput(true)\">\r\n </sd-button>\r\n <sd-button\r\n [autoId]=\"chipAutoId('value-false')\"\r\n [type]=\"_editVal === false ? 'fill' : 'outline'\"\r\n color=\"primary\"\r\n [title]=\"$any(_editField).falseLabel || 'Kh\u00F4ng'\"\r\n (click)=\"onEditingValueInput(false)\">\r\n </sd-button>\r\n </div>\r\n } @else if (_editField.kind === 'values' || _editField.kind === 'lazy-values') {\r\n @let _opt = $any(_editField).option;\r\n <sd-select size=\"sm\"\r\n [autoId]=\"chipAutoId('value')\"\r\n placeholder=\"Ch\u1ECDn gi\u00E1 tr\u1ECB\"\r\n [items]=\"_options\"\r\n [valueField]=\"_opt.valueField\"\r\n [displayField]=\"_opt.displayField\"\r\n [multiple]=\"editingIsMulti()\"\r\n [model]=\"$any(_editVal)\"\r\n (sdChange)=\"onEditingValueInput($event)\">\r\n </sd-select>\r\n @if (_loading) {\r\n <div class=\"c-pop-values-loading\">\u0110ang t\u1EA3i...</div>\r\n }\r\n } @else if (_editField.kind === 'date') {\r\n <sd-date size=\"sm\"\r\n [autoId]=\"chipAutoId('value')\"\r\n placeholder=\"Ch\u1ECDn gi\u00E1 tr\u1ECB\"\r\n [model]=\"$any(_editVal)\"\r\n (sdChange)=\"onEditingValueInput($event)\">\r\n </sd-date>\r\n } @else if (_editField.kind === 'datetime') {\r\n <sd-datetime size=\"sm\"\r\n [autoId]=\"chipAutoId('value')\"\r\n placeholder=\"Ch\u1ECDn gi\u00E1 tr\u1ECB\"\r\n [model]=\"$any(_editVal)\"\r\n (sdChange)=\"onEditingValueInput($event)\">\r\n </sd-datetime>\r\n } @else if (_editField.kind === 'number') {\r\n <sd-input-number size=\"sm\"\r\n [autoId]=\"chipAutoId('value')\"\r\n placeholder=\"Nh\u1EADp gi\u00E1 tr\u1ECB\"\r\n [model]=\"$any(_editVal)\"\r\n (sdChange)=\"onEditingValueInput($event)\">\r\n </sd-input-number>\r\n } @else {\r\n <sd-input size=\"sm\"\r\n [autoId]=\"chipAutoId('value')\"\r\n placeholder=\"Nh\u1EADp gi\u00E1 tr\u1ECB\"\r\n [model]=\"$any(_editVal)\"\r\n (sdChange)=\"onEditingValueInput($event)\">\r\n </sd-input>\r\n }\r\n }\r\n </div>\r\n }\r\n </ng-template>\r\n </mat-menu>\r\n\r\n @if (mode() === 'inline') {\r\n <!-- ===== INLINE MODE (GitLab-style token builder) ===== -->\r\n\r\n <!-- Reusable value editor. ctx: field, data, isMulti, change (stash fn), enter (finalize fn), autoId\r\n why: text/number commit on Enter, so the typed value is stashed via `change` then finalized\r\n by `enter`; selection-style controls (select/date/boolean) stash + finalize in one action. -->\r\n <ng-template #valueEditor let-field=\"field\" let-data=\"data\" let-isMulti=\"isMulti\" let-change=\"change\" let-enter=\"enter\" let-autoId=\"autoId\">\r\n @if (field.kind === 'boolean') {\r\n <sd-button [autoId]=\"autoId + '-true'\" [type]=\"data === true ? 'fill' : 'outline'\" color=\"primary\" [title]=\"$any(field).trueLabel || 'C\u00F3'\" (click)=\"change(true); enter()\"></sd-button>\r\n <sd-button [autoId]=\"autoId + '-false'\" [type]=\"data === false ? 'fill' : 'outline'\" color=\"primary\" [title]=\"$any(field).falseLabel || 'Kh\u00F4ng'\" (click)=\"change(false); enter()\"></sd-button>\r\n } @else if (field.kind === 'values') {\r\n @let _opt = $any(field).option;\r\n <sd-select bare size=\"sm\" minWidthPanel=\"220px\" [autoId]=\"autoId\" [items]=\"$any(_opt.items)\" [valueField]=\"_opt.valueField\" [displayField]=\"_opt.displayField\" [multiple]=\"isMulti\" [model]=\"data\" (sdChange)=\"change($event); enter()\"></sd-select>\r\n } @else if (field.kind === 'lazy-values') {\r\n @let _lopt = $any(field).option;\r\n <sd-select bare size=\"sm\" minWidthPanel=\"220px\" [autoId]=\"autoId\" [items]=\"lazyItemsFor(field)\" [valueField]=\"_lopt.valueField\" [displayField]=\"_lopt.displayField\" [multiple]=\"isMulti\" [model]=\"data\" (sdChange)=\"change($event); enter()\"></sd-select>\r\n } @else if (field.kind === 'date') {\r\n <sd-date bare size=\"sm\" [autoId]=\"autoId\" [model]=\"data\" (sdChange)=\"change($event); enter()\"></sd-date>\r\n } @else if (field.kind === 'datetime') {\r\n <sd-datetime bare size=\"sm\" [autoId]=\"autoId\" [model]=\"data\" (sdChange)=\"change($event); enter()\"></sd-datetime>\r\n } @else if (field.kind === 'number') {\r\n <sd-input-number size=\"sm\" [autoId]=\"autoId\" [model]=\"data\" (sdChange)=\"change($event)\" (keyupEnter)=\"enter()\"></sd-input-number>\r\n } @else {\r\n <sd-input size=\"sm\" [autoId]=\"autoId\" [model]=\"data\" (sdChange)=\"change($event)\" (keyupEnter)=\"enter()\"></sd-input>\r\n }\r\n </ng-template>\r\n\r\n <!-- Completed chips -->\r\n @for (filter of _filters; track $index; let i = $index) {\r\n @if (_showOr && i > 0) {\r\n <span class=\"c-or-connector\" aria-hidden=\"true\">OR</span>\r\n }\r\n @let _field = fieldByKey()[$any(filter).field];\r\n @if (_field) {\r\n @let _op = $any(filter.operator);\r\n @let _data = $any(filter).data;\r\n @if (_field.kind === 'string' || _field.kind === 'number') {\r\n <!-- Seamless chip \u2014 the pill is the input (string / number). -->\r\n <sd-query-inline-value-chip\r\n [field]=\"_field\"\r\n [operator]=\"_op\"\r\n [value]=\"_data\"\r\n [density]=\"_density\"\r\n [showOperator]=\"showOperatorOnChip()\"\r\n [autoId]=\"inlineAutoId(i, 'value')\"\r\n (valueChange)=\"updateFilter(i, { data: $event })\"\r\n (remove)=\"removeFilter(i)\" />\r\n } @else {\r\n <div class=\"c-token\" [class.c-token-editing]=\"isEditingValue(i)\">\r\n <mat-icon class=\"c-token-icon\" fontSet=\"material-icons-outlined\">{{ iconFor(_field) }}</mat-icon>\r\n <span class=\"c-token-field\">{{ _field.label }}</span>\r\n @if (!isNoDataOperator(_op) && showOperatorOnChip()) {\r\n <sd-operator class=\"c-token-op\" [operators]=\"[_op]\" [model]=\"_op\" [disabled]=\"true\" />\r\n } @else if (!isNoDataOperator(_op)) {\r\n <span class=\"c-token-sep\">:</span>\r\n }\r\n @if (!isNoDataOperator(_op)) {\r\n @if (_field.kind === 'boolean') {\r\n @if (isEditingValue(i)) {\r\n <span class=\"c-token-value c-token-value-edit\">\r\n <ng-container *ngTemplateOutlet=\"valueEditor; context: { field: _field, data: _data, isMulti: isMultiOperator(_op), change: setEditDraftFn(), enter: commitEditDraftFn(i), autoId: inlineAutoId(i, 'value') }\"></ng-container>\r\n </span>\r\n } @else {\r\n <button type=\"button\" class=\"c-token-value\" (click)=\"beginEditValue(i)\">{{ chipValueText(filter) }}</button>\r\n }\r\n } @else if ((_field.kind === 'date' || _field.kind === 'datetime') && _op === 'BETWEEN') {\r\n <!-- why: chip BETWEEN date/datetime \u0111\u00E3 commit -> render 2 picker inline cho ph\u00E9p s\u1EEDa t\u1EEBng \u0111\u1EA7u -->\r\n <span class=\"c-token-value c-token-value-edit\">\r\n @if (_field.kind === 'date') {\r\n <sd-date bare size=\"sm\" placeholder=\"T\u1EEB\"\r\n [autoId]=\"inlineAutoId(i, 'value-from')\"\r\n [model]=\"$any(_data)?.from\"\r\n (sdChange)=\"setFilterRangeFrom(i, $any($event))\"></sd-date>\r\n <span class=\"c-token-dash\" aria-hidden=\"true\">\u2014</span>\r\n <sd-date bare size=\"sm\" placeholder=\"\u0110\u1EBFn\"\r\n [autoId]=\"inlineAutoId(i, 'value-to')\"\r\n [model]=\"$any(_data)?.to\"\r\n (sdChange)=\"setFilterRangeTo(i, $any($event))\"></sd-date>\r\n } @else {\r\n <sd-datetime bare size=\"sm\" placeholder=\"T\u1EEB\"\r\n [autoId]=\"inlineAutoId(i, 'value-from')\"\r\n [model]=\"$any(_data)?.from\"\r\n (sdChange)=\"setFilterRangeFrom(i, $any($event))\"></sd-datetime>\r\n <span class=\"c-token-dash\" aria-hidden=\"true\">\u2014</span>\r\n <sd-datetime bare size=\"sm\" placeholder=\"\u0110\u1EBFn\"\r\n [autoId]=\"inlineAutoId(i, 'value-to')\"\r\n [model]=\"$any(_data)?.to\"\r\n (sdChange)=\"setFilterRangeTo(i, $any($event))\"></sd-datetime>\r\n }\r\n </span>\r\n } @else if (_field.kind === 'values' || _field.kind === 'lazy-values') {\r\n @let _opt = $any(_field).option;\r\n <span\r\n class=\"c-token-value c-token-value-edit\"\r\n (click)=\"beginEditValue(i)\"\r\n (focusout)=\"onChipValueFocusOut(i, $event)\">\r\n <sd-select bare size=\"sm\" minWidthPanel=\"220px\"\r\n [autoId]=\"inlineAutoId(i, 'value')\"\r\n [items]=\"_field.kind === 'lazy-values' ? lazyItemsFor(_field) : $any(_opt.items)\"\r\n [valueField]=\"_opt.valueField\"\r\n [displayField]=\"_opt.displayField\"\r\n [multiple]=\"isMultiOperator(_op)\"\r\n [viewed]=\"!isEditingValue(i)\"\r\n [model]=\"_data\"\r\n (sdChange)=\"isMultiOperator(_op) ? editValueFn(i)($event) : onChipSingleCommit(i, $event)\"></sd-select>\r\n </span>\r\n } @else if (_field.kind === 'date') {\r\n <span\r\n class=\"c-token-value c-token-value-edit\"\r\n (click)=\"beginEditValue(i)\"\r\n (focusout)=\"onChipValueFocusOut(i, $event)\">\r\n <sd-date bare size=\"sm\"\r\n [autoId]=\"inlineAutoId(i, 'value')\"\r\n [viewed]=\"!isEditingValue(i)\"\r\n [model]=\"_data\"\r\n (sdChange)=\"onChipSingleCommit(i, $any($event))\"></sd-date>\r\n </span>\r\n } @else if (_field.kind === 'datetime') {\r\n <span\r\n class=\"c-token-value c-token-value-edit\"\r\n (click)=\"beginEditValue(i)\"\r\n (focusout)=\"onChipValueFocusOut(i, $event)\">\r\n <sd-datetime bare size=\"sm\"\r\n [autoId]=\"inlineAutoId(i, 'value')\"\r\n [viewed]=\"!isEditingValue(i)\"\r\n [model]=\"_data\"\r\n (sdChange)=\"onChipSingleCommit(i, $any($event))\"></sd-datetime>\r\n </span>\r\n } @else {\r\n <!-- Fallback (number / other) \u2014 keep the shared editor outlet path. -->\r\n <span class=\"c-token-value c-token-value-edit\">\r\n <ng-container *ngTemplateOutlet=\"valueEditor; context: { field: _field, data: _data, isMulti: isMultiOperator(_op), change: editValueFn(i), enter: noop, autoId: inlineAutoId(i, 'value') }\"></ng-container>\r\n </span>\r\n }\r\n }\r\n <button type=\"button\" class=\"c-token-remove\" aria-label=\"Remove filter\" (click)=\"removeFilter(i)\">\r\n <mat-icon fontSet=\"material-icons-outlined\">close</mat-icon>\r\n </button>\r\n </div>\r\n }\r\n }\r\n }\r\n\r\n <!-- Building chip (in-progress) -->\r\n @if (building(); as _b) {\r\n @if (_b.step === 'value' && (_b.field.kind === 'string' || _b.field.kind === 'number')) {\r\n <!-- Seamless build chip \u2014 type straight in, no rectangle. Enter/blur commits. -->\r\n <sd-query-inline-value-chip\r\n [field]=\"_b.field\"\r\n [operator]=\"$any(_b.operator)\"\r\n [value]=\"_b.value ?? null\"\r\n [density]=\"_density\"\r\n [showOperator]=\"showOperatorOnChip()\"\r\n [autoId]=\"'qb-build-value'\"\r\n [autofocus]=\"true\"\r\n (valueChange)=\"onBuildSeamlessCommit($event)\"\r\n (remove)=\"cancelBuild()\" />\r\n } @else {\r\n <div class=\"c-token c-token-building\">\r\n <span class=\"c-token-field\">{{ _b.field.label }}</span>\r\n @if (_b.step === 'operator') {\r\n <sd-operator #buildOperator class=\"c-token-op\" [operators]=\"allowedOperatorsFor(_b.field)\" [model]=\"_b.operator\" (modelChange)=\"pickBuildOperator($any($event))\" />\r\n } @else {\r\n <sd-operator class=\"c-token-op\" [operators]=\"[$any(_b.operator)]\" [model]=\"$any(_b.operator)\" [disabled]=\"true\" />\r\n <!-- why: build value step renders the bare picker with a #bPicker ref so the\r\n build flow can auto-open its native panel; sd-select needs minWidthPanel so the\r\n narrow bare trigger still opens a readable panel. boolean keeps the editor outlet. -->\r\n @if (_b.field.kind === 'values' || _b.field.kind === 'lazy-values') {\r\n @let _bopt = $any(_b.field).option;\r\n <span class=\"c-token-value c-token-value-edit\">\r\n <sd-select #bPicker bare size=\"sm\" minWidthPanel=\"220px\" autoId=\"qb-build-value\"\r\n [items]=\"_b.field.kind === 'lazy-values' ? lazyItemsFor(_b.field) : $any(_bopt.items)\"\r\n [valueField]=\"_bopt.valueField\" [displayField]=\"_bopt.displayField\"\r\n [multiple]=\"isMultiOperator($any(_b.operator))\" [model]=\"$any(_b.value)\"\r\n (sdChange)=\"commitBuildValue($event)\"></sd-select>\r\n </span>\r\n } @else if (_b.field.kind === 'date') {\r\n <span class=\"c-token-value c-token-value-edit\">\r\n <!-- why: BETWEEN c\u1EA7n 2 picker (T\u1EEB / \u0110\u1EBFn); #bPicker \u0111\u1EB7t tr\u00EAn picker T\u1EEB \u0111\u1EC3 auto-open gi\u1EEF h\u00E0nh vi c\u0169 -->\r\n @if (_b.operator === 'BETWEEN') {\r\n <sd-date #bPicker bare size=\"sm\" autoId=\"qb-build-value-from\" placeholder=\"T\u1EEB\"\r\n [model]=\"$any(_b.value)?.from\" (sdChange)=\"setBuildRangeFrom($event)\"></sd-date>\r\n <span class=\"c-token-dash\" aria-hidden=\"true\">\u2014</span>\r\n <sd-date bare size=\"sm\" autoId=\"qb-build-value-to\" placeholder=\"\u0110\u1EBFn\"\r\n [model]=\"$any(_b.value)?.to\" (sdChange)=\"setBuildRangeTo($event)\"></sd-date>\r\n } @else {\r\n <sd-date #bPicker bare size=\"sm\" autoId=\"qb-build-value\" [model]=\"$any(_b.value)\" (sdChange)=\"commitBuildValue($event)\"></sd-date>\r\n }\r\n </span>\r\n } @else if (_b.field.kind === 'datetime') {\r\n <span class=\"c-token-value c-token-value-edit\">\r\n <!-- why: BETWEEN c\u1EA7n 2 picker (T\u1EEB / \u0110\u1EBFn); #bPicker \u0111\u1EB7t tr\u00EAn picker T\u1EEB \u0111\u1EC3 auto-open gi\u1EEF h\u00E0nh vi c\u0169 -->\r\n @if (_b.operator === 'BETWEEN') {\r\n <sd-datetime #bPicker bare size=\"sm\" autoId=\"qb-build-value-from\" placeholder=\"T\u1EEB\"\r\n [model]=\"$any(_b.value)?.from\" (sdChange)=\"setBuildRangeFrom($event)\"></sd-datetime>\r\n <span class=\"c-token-dash\" aria-hidden=\"true\">\u2014</span>\r\n <sd-datetime bare size=\"sm\" autoId=\"qb-build-value-to\" placeholder=\"\u0110\u1EBFn\"\r\n [model]=\"$any(_b.value)?.to\" (sdChange)=\"setBuildRangeTo($event)\"></sd-datetime>\r\n } @else {\r\n <sd-datetime #bPicker bare size=\"sm\" autoId=\"qb-build-value\" [model]=\"$any(_b.value)\" (sdChange)=\"commitBuildValue($event)\"></sd-datetime>\r\n }\r\n </span>\r\n } @else {\r\n <span class=\"c-token-value c-token-value-edit\">\r\n <ng-container *ngTemplateOutlet=\"valueEditor; context: { field: _b.field, data: _b.value ?? null, isMulti: isMultiOperator($any(_b.operator)), change: setBuildDraftFn(), enter: commitBuildDraftFn(), autoId: 'qb-build-value' }\"></ng-container>\r\n </span>\r\n }\r\n }\r\n <button type=\"button\" class=\"c-token-remove\" aria-label=\"Cancel\" (click)=\"cancelBuild()\">\r\n <mat-icon fontSet=\"material-icons-outlined\">close</mat-icon>\r\n </button>\r\n </div>\r\n }\r\n }\r\n\r\n <!-- Add filter -->\r\n <button\r\n type=\"button\"\r\n class=\"c-add-filter\"\r\n [matMenuTriggerFor]=\"fieldPicker\"\r\n [disabled]=\"_fields.length === 0 || _used.size >= _fields.length\"\r\n [matTooltip]=\"_fields.length === 0 ? 'Ch\u01B0a c\u1EA5u h\u00ECnh fields' : 'Th\u00EAm filter'\">\r\n <mat-icon fontSet=\"material-icons-outlined\">add</mat-icon>\r\n </button>\r\n } @else {\r\n <!-- ===== POPOVER MODE (default) \u2014 compact chips edited via mat-menu ===== -->\r\n @for (filter of _filters; track $index; let i = $index) {\r\n @if (_showOr && i > 0) {\r\n <span class=\"c-or-connector\" aria-hidden=\"true\">OR</span>\r\n }\r\n @let _field = fieldByKey()[$any(filter).field];\r\n @let _active = isFilterActive(filter);\r\n <!-- why: div[role=button] (not <button>) so the operator can render as a nested\r\n <sd-operator> \u2014 matching inline chips. A button cannot legally contain a button. -->\r\n <div\r\n #chipTrigger=\"matMenuTrigger\"\r\n role=\"button\"\r\n tabindex=\"0\"\r\n class=\"c-chip\"\r\n [class.c-chip-active]=\"_active\"\r\n [class.c-chip-inactive]=\"!_active\"\r\n [attr.data-field]=\"$any(filter).field\"\r\n [matTooltip]=\"_field?.label || $any(filter).field\"\r\n [matMenuTriggerFor]=\"chipPopover\"\r\n [matMenuTriggerData]=\"{ index: i }\"\r\n (menuOpened)=\"openChipPopover(i)\"\r\n (menuClosed)=\"commitChipEditOnClose()\"\r\n (keydown.enter)=\"chipTrigger.openMenu()\"\r\n (keydown.space)=\"$event.preventDefault(); chipTrigger.openMenu()\">\r\n <mat-icon class=\"c-chip-icon\" fontSet=\"material-icons-outlined\">\r\n {{ _field ? iconFor(_field) : 'tune' }}\r\n </mat-icon>\r\n <span class=\"c-chip-label\">{{ _field?.label || $any(filter).field }}</span>\r\n\r\n @if (showOperatorOnChip() && _active) {\r\n <sd-operator class=\"c-chip-operator\" [operators]=\"[$any(filter.operator)]\" [model]=\"$any(filter.operator)\" [disabled]=\"true\" />\r\n } @else if (_active) {\r\n <span class=\"c-chip-sep\">:</span>\r\n }\r\n\r\n @if (_active) {\r\n <span class=\"c-chip-value\">{{ chipValueText(filter) }}</span>\r\n }\r\n\r\n <mat-icon class=\"c-chip-chevron\" fontSet=\"material-icons-outlined\">arrow_drop_down</mat-icon>\r\n\r\n @if (_active) {\r\n <span\r\n role=\"button\"\r\n tabindex=\"0\"\r\n class=\"c-chip-remove\"\r\n aria-label=\"Remove filter\"\r\n (click)=\"$event.stopPropagation(); removeFilter(i)\"\r\n (keydown.enter)=\"$event.stopPropagation(); removeFilter(i)\">\r\n <mat-icon fontSet=\"material-icons-outlined\">close</mat-icon>\r\n </span>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Add filter trigger -->\r\n <button\r\n type=\"button\"\r\n class=\"c-add-filter\"\r\n [matMenuTriggerFor]=\"fieldPicker\"\r\n [disabled]=\"_fields.length === 0 || _used.size >= _fields.length\"\r\n [matTooltip]=\"_fields.length === 0 ? 'Ch\u01B0a c\u1EA5u h\u00ECnh fields' : 'Th\u00EAm filter'\">\r\n <mat-icon fontSet=\"material-icons-outlined\">add</mat-icon>\r\n </button>\r\n }\r\n\r\n </div><!-- /.c-query-bar__main -->\r\n\r\n <!-- Field picker \u2014 list available fields, fade ones already in use -->\r\n <mat-menu #fieldPicker=\"matMenu\" xPosition=\"after\" yPosition=\"below\" class=\"c-field-picker-menu\">\r\n @for (field of _fields; track $index) {\r\n @let _isUsed = _used.has($any(field.key));\r\n <button\r\n type=\"button\"\r\n mat-menu-item\r\n [disabled]=\"_isUsed || field.disabled\"\r\n (click)=\"mode() === 'inline' ? beginBuild(field) : addFilter(field)\">\r\n <mat-icon fontSet=\"material-icons-outlined\" class=\"c-field-item-icon\">\r\n {{ iconFor(field) }}\r\n </mat-icon>\r\n <span class=\"c-field-item-label\">{{ field.label }}</span>\r\n <span class=\"c-field-item-kind\">{{ field.kind }}</span>\r\n @if (_isUsed) {\r\n <mat-icon class=\"c-field-item-check\" fontSet=\"material-icons-outlined\">check</mat-icon>\r\n }\r\n </button>\r\n } @empty {\r\n <div class=\"c-field-empty\">Ch\u01B0a c\u00F3 field n\u00E0o \u0111\u01B0\u1EE3c c\u1EA5u h\u00ECnh.</div>\r\n }\r\n </mat-menu>\r\n\r\n <!-- Vertical separator between the wrapping filter area (left) and the pinned toolbar. -->\r\n <span class=\"c-bar-divider\" aria-hidden=\"true\"></span>\r\n\r\n <!-- Action toolbar \u2014 pinned top-right; never drifts with the filter rows. -->\r\n <div class=\"c-query-bar__actions\">\r\n <!-- AND / OR toggle -->\r\n @if (showLogicToggle() && _filters.length >= 2) {\r\n <div class=\"c-logic-toggle\" role=\"group\" aria-label=\"Logic operator\">\r\n <button\r\n type=\"button\"\r\n class=\"c-logic-segment\"\r\n [class.c-logic-active]=\"_logic === 'AND'\"\r\n (click)=\"setLogic('AND')\">\r\n AND\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"c-logic-segment\"\r\n [class.c-logic-active]=\"_logic === 'OR'\"\r\n (click)=\"setLogic('OR')\">\r\n OR\r\n </button>\r\n </div>\r\n }\r\n\r\n <!-- Saved views (placeholder \u2014 wire in next iteration) -->\r\n @if (showSavedViews()) {\r\n <button type=\"button\" class=\"c-saved-views\" disabled matTooltip=\"Saved views\">\r\n <mat-icon fontSet=\"material-icons-outlined\">bookmark_border</mat-icon>\r\n </button>\r\n }\r\n\r\n <!-- Clear all \u2014 compact icon + tooltip -->\r\n @if (showClearAll() && _filters.length > 0) {\r\n <button\r\n type=\"button\"\r\n class=\"c-clear-all\"\r\n [matTooltip]=\"'X\u00F3a t\u1EA5t c\u1EA3 (' + _filters.length + ')'\"\r\n (click)=\"clearAll()\">\r\n <mat-icon fontSet=\"material-icons-outlined\">delete_sweep</mat-icon>\r\n </button>\r\n }\r\n\r\n <!-- Search \u2014 the single deferred trigger for both modes. why: far-right placement\r\n mirrors the conventional \"submit at the end of the toolbar\" reading order. -->\r\n <button\r\n type=\"button\"\r\n class=\"c-search-trigger\"\r\n [disabled]=\"!canSearch()\"\r\n matTooltip=\"T\u00ECm ki\u1EBFm\"\r\n (click)=\"triggerApply()\">\r\n <mat-icon fontSet=\"material-icons-outlined\">search</mat-icon>\r\n </button>\r\n </div>\r\n</div>\r\n", styles: [":host{display:block;font-family:Roboto,system-ui,sans-serif}.c-query-bar{display:flex;align-items:flex-start;flex-wrap:nowrap;gap:8px;background:#fff;border:1px solid #e2e4e7;border-radius:8px;box-shadow:0 1px 2px #0f172a0a}.c-query-bar.c-density-compact{padding:8px}.c-query-bar.c-density-comfortable{padding:10px}.c-query-bar__main{flex:1 1 auto;min-width:0;display:flex;align-items:center;flex-wrap:wrap;gap:6px}.c-bar-divider{flex:0 0 auto;align-self:stretch;width:1px;min-height:28px;background:#e2e4e7}.c-query-bar__actions{flex:0 0 auto;display:flex;align-items:center;gap:6px}.c-search{display:flex;align-items:center;gap:6px;min-width:220px;padding:0 10px;border:1px solid #d6d8db;border-radius:6px;background:#fff}.c-density-compact .c-search{height:28px}.c-density-comfortable .c-search{height:32px}.c-search .c-search-icon{font-size:16px;width:16px;height:16px;color:#7a7a7a}.c-search .c-search-input{flex:1;border:none;outline:none;background:transparent;font-size:13px;color:#212121}.c-search .c-search-input::placeholder{color:#7a7a7a}.c-search .c-search-kbd{font-family:Roboto Mono,monospace;font-size:10px;padding:1px 4px;border:1px solid #e2e4e7;border-radius:3px;color:#7a7a7a}.c-divider{width:1px;height:16px;background:#e2e4e7;margin:0 2px}.c-chip{display:inline-flex;align-items:center;gap:4px;padding:0 4px 0 10px;border-radius:999px;border:1px solid #d6d8db;background:#fff;cursor:pointer;font-family:inherit;font-size:13px;font-weight:500;color:#5f6368;transition:background-color .12s ease,border-color .12s ease,color .12s ease}.c-density-compact .c-chip{height:28px}.c-density-comfortable .c-chip{height:32px}.c-chip:hover{border-color:var(--sd-primary)}.c-chip.c-chip-active{border-color:var(--sd-primary-light);background:var(--sd-primary-light)}.c-chip.c-chip-active .c-chip-value{color:var(--sd-primary)}.c-chip.c-chip-inactive{padding-right:10px}.c-chip .c-chip-icon{font-size:16px;width:16px;height:16px}.c-chip .c-chip-label{font-weight:500;white-space:nowrap}.c-chip .c-chip-operator{display:inline-flex;align-items:center;color:inherit}.c-chip .c-chip-sep{color:#7a7a7a}.c-chip .c-chip-value{font-weight:500;max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.c-chip .c-chip-chevron{font-size:18px;width:18px;height:18px;color:currentColor}.c-chip .c-chip-remove{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:50%;cursor:pointer;color:#7a7a7a}.c-chip .c-chip-remove mat-icon{font-size:14px;width:14px;height:14px}.c-chip .c-chip-remove:hover{background:#0000000f}.c-token{display:inline-flex;align-items:center;gap:4px;padding:0 0 0 12px;border:1px solid var(--sd-primary-light);border-radius:999px;background:var(--sd-primary-light);font-size:13px;color:#5f6368;white-space:nowrap;overflow:hidden}.c-density-compact .c-token{height:28px}.c-density-comfortable .c-token{height:32px}.c-token-icon{font-size:14px;width:14px;height:14px;line-height:14px;color:inherit}.c-token-field{font-weight:500;line-height:1}.c-token-op{display:inline-flex;align-items:center;color:inherit}.c-token-op ::ng-deep .c-op-trigger,.c-chip-operator ::ng-deep .c-op-trigger{padding:0;color:inherit}.c-token-op ::ng-deep .c-op-icon svg,.c-chip-operator ::ng-deep .c-op-icon svg{width:14px;height:14px;display:block}.c-token-value-edit sd-select,.c-token-value-edit sd-date,.c-token-value-edit sd-datetime{display:inline-flex;align-items:center;line-height:1}.c-token ::ng-deep .mat-mdc-form-field{line-height:1}.c-token ::ng-deep .mat-mdc-text-field-wrapper,.c-token ::ng-deep .mat-mdc-form-field-flex{padding:0;min-height:0;background:transparent}.c-token ::ng-deep .mat-mdc-form-field-infix{padding:0;min-height:0;width:auto;border:0;display:inline-flex;align-items:center}.c-token ::ng-deep .mat-mdc-select-value,.c-token ::ng-deep .mat-mdc-input-element,.c-token ::ng-deep input{line-height:1;font-size:13px;padding:0;height:auto;color:inherit}.c-token ::ng-deep .mat-mdc-select-value{display:inline-flex;align-items:center;min-width:0;max-width:200px;overflow:hidden;text-overflow:ellipsis;color:var(--sd-primary);font-weight:500}.c-token ::ng-deep .sd-suffix-icon{display:none}.c-token-sep{color:#7a7a7a}.c-token-dash{color:#7a7a7a;padding:0 4px;line-height:1}.c-token-value{border:none;background:transparent;padding:0;font:inherit;font-weight:500;color:var(--sd-primary);cursor:pointer}.c-token-value:hover{text-decoration:underline}.c-token-value-edit{display:inline-flex;align-items:center;gap:4px;max-width:160px}.c-token-value-edit sd-input,.c-token-value-edit sd-input-number{display:inline-flex;max-width:140px}.c-token-building{border-style:dashed}.c-token-remove{align-self:stretch;display:inline-flex;align-items:center;justify-content:center;border:none;border-left:1px solid rgba(0,0,0,.04);border-radius:0;background:transparent;cursor:pointer;color:inherit}.c-density-compact .c-token-remove{width:26px}.c-density-comfortable .c-token-remove{width:28px}.c-token-remove mat-icon{font-size:14px;width:14px;height:14px}.c-token-remove:hover{background:#0000000d}.c-add-filter{display:inline-flex;align-items:center;justify-content:center;padding:0;border-radius:999px;border:1px dashed #d6d8db;background:transparent;cursor:pointer;color:#5f6368;transition:border-color .12s ease,color .12s ease,background-color .12s ease}.c-density-compact .c-add-filter{width:28px;height:28px}.c-density-comfortable .c-add-filter{width:32px;height:32px}.c-add-filter mat-icon{font-size:18px;width:18px;height:18px}.c-add-filter:hover:not([disabled]){border-color:var(--sd-primary);color:var(--sd-primary)}.c-add-filter[disabled]{opacity:.5;cursor:not-allowed}.c-or-connector{font-family:Roboto Mono,monospace;font-size:10px;font-weight:700;letter-spacing:.6px;color:#7a7a7a;padding:0 2px}.c-logic-toggle{display:inline-flex;align-items:center;padding:2px;background:#f5f6f7;border:1px solid #e2e4e7;border-radius:6px}.c-density-compact .c-logic-toggle{height:28px}.c-density-comfortable .c-logic-toggle{height:32px}.c-logic-toggle .c-logic-segment{height:100%;padding:0 10px;border:none;background:transparent;font-family:Roboto Mono,monospace;font-size:11px;font-weight:600;letter-spacing:.4px;color:#5f6368;cursor:pointer;border-radius:4px}.c-logic-toggle .c-logic-segment.c-logic-active{background:#fff;color:var(--sd-primary);box-shadow:0 1px 2px #00000014}.c-saved-views,.c-clear-all{display:inline-flex;align-items:center;justify-content:center;border:1px solid #d6d8db;border-radius:6px;background:#fff;color:#5f6368;cursor:pointer;padding:0}.c-density-compact .c-saved-views,.c-density-compact .c-clear-all{width:28px;height:28px}.c-density-comfortable .c-saved-views,.c-density-comfortable .c-clear-all{width:32px;height:32px}.c-saved-views mat-icon,.c-clear-all mat-icon{font-size:18px;width:18px;height:18px}.c-saved-views:hover:not([disabled]),.c-clear-all:hover:not([disabled]){border-color:var(--sd-primary);color:var(--sd-primary)}.c-saved-views[disabled],.c-clear-all[disabled]{opacity:.5;cursor:not-allowed}.c-clear-all:hover{border-color:#dc2626;color:#dc2626}.c-search-trigger{display:inline-flex;align-items:center;justify-content:center;border:1px solid #d6d8db;border-radius:6px;background:#fff;color:#5f6368;cursor:pointer;padding:0}.c-density-compact .c-search-trigger{width:28px;height:28px}.c-density-comfortable .c-search-trigger{width:32px;height:32px}.c-search-trigger mat-icon{font-size:18px;width:18px;height:18px}.c-search-trigger:hover:not([disabled]){border-color:var(--sd-primary);color:var(--sd-primary)}.c-search-trigger[disabled]{opacity:.5;cursor:not-allowed}::ng-deep .mat-mdc-menu-panel.c-field-picker-menu{min-width:200px;max-width:260px}::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-menu-item{min-height:36px}::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-menu-item .mat-mdc-menu-item-text{display:flex;align-items:center;flex:1;min-width:0}::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .c-field-item-icon{font-size:16px;width:16px;height:16px;margin-right:8px;color:#5f6368;flex-shrink:0}::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .c-field-item-label{flex:1 1 auto;min-width:0;font-size:13px;color:#212121;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .c-field-item-kind{flex-shrink:0;margin-left:auto;padding-left:12px;font-family:Roboto Mono,monospace;font-size:11px;color:#7a7a7a}::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .c-field-item-check{flex-shrink:0;font-size:14px;width:14px;height:14px;margin-left:6px;color:var(--sd-primary)}::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .c-field-empty{padding:12px 16px;font-size:13px;color:#7a7a7a}::ng-deep .mat-mdc-menu-panel.c-chip-popover{min-width:280px;max-width:320px}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-header{display:flex;align-items:center;gap:8px;padding:10px 12px;border-bottom:1px solid #e2e4e7;font-size:13px;font-weight:500;color:#212121}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-header mat-icon{font-size:16px;width:16px;height:16px;color:var(--sd-primary)}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-header .c-pop-header-operator{margin-left:auto}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-header-field{display:inline-flex;align-items:center;gap:4px;padding:2px 4px 2px 6px;margin-left:-4px;background:transparent;border:1px solid transparent;border-radius:4px;font:inherit;font-weight:500;color:#212121;cursor:pointer;transition:background-color .12s ease,border-color .12s ease}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-header-field mat-icon{font-size:14px!important;width:14px!important;height:14px!important;color:#7a7a7a!important}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-header-field:hover{background:#f5f6f7;border-color:#e2e4e7}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-body{padding:10px 12px;display:flex;flex-direction:column;gap:12px}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-input{width:100%;height:32px;padding:0 10px;border:1px solid #d6d8db;border-radius:6px;background:#fff;font-family:inherit;font-size:13px;color:#212121;outline:none}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-input:focus{border-color:var(--sd-primary)}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-between{display:grid;grid-template-columns:1fr 1fr;gap:8px}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-boolean{display:flex;gap:8px}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-boolean button{flex:1;height:32px;border:1px solid #d6d8db;border-radius:6px;background:#fff;font:inherit;font-size:13px;cursor:pointer}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-boolean button.c-pop-boolean-active{background:var(--sd-primary-light);border-color:var(--sd-primary);color:var(--sd-primary)}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-values{max-height:220px;overflow-y:auto;margin:0;padding:0;list-style:none}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-values li{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:4px;font-size:13px;cursor:pointer;-webkit-user-select:none;user-select:none}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-values li:hover{background:#f5f6f7}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-values li.c-pop-value-selected{background:var(--sd-primary-light);color:var(--sd-primary)}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-values li input[type=checkbox],::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-values li input[type=radio]{accent-color:var(--sd-primary)}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-values-loading,::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-values-empty{padding:10px 8px;font-size:12px;color:#7a7a7a}\n", ".c-query-bar ::ng-deep .mat-mdc-form-field .mdc-notched-outline__leading,.c-query-bar ::ng-deep .mat-mdc-form-field .mdc-notched-outline__notch,.c-query-bar ::ng-deep .mat-mdc-form-field .mdc-notched-outline__trailing{border-color:#d6d8db!important;transition:border-color .12s ease}.c-query-bar ::ng-deep .mat-mdc-form-field .mdc-notched-outline__leading{border-radius:6px 0 0 6px!important}.c-query-bar ::ng-deep .mat-mdc-form-field .mdc-notched-outline__trailing{border-radius:0 6px 6px 0!important}.c-query-bar ::ng-deep .mat-mdc-form-field:hover:not(.mat-focused):not(.mat-form-field-disabled) .mdc-notched-outline__leading,.c-query-bar ::ng-deep .mat-mdc-form-field:hover:not(.mat-focused):not(.mat-form-field-disabled) .mdc-notched-outline__notch,.c-query-bar ::ng-deep .mat-mdc-form-field:hover:not(.mat-focused):not(.mat-form-field-disabled) .mdc-notched-outline__trailing{border-color:#b9bdc3!important}.c-query-bar ::ng-deep .mat-mdc-form-field.mat-focused .mdc-notched-outline__leading,.c-query-bar ::ng-deep .mat-mdc-form-field.mat-focused .mdc-notched-outline__notch,.c-query-bar ::ng-deep .mat-mdc-form-field.mat-focused .mdc-notched-outline__trailing{border-color:var(--sd-primary)!important;border-width:1px!important}.c-query-bar ::ng-deep .mat-mdc-form-field .mat-mdc-select-arrow,.c-query-bar ::ng-deep .mat-mdc-form-field .mat-mdc-form-field-icon-suffix .mat-icon{color:#5f6368}.c-query-bar ::ng-deep .mat-mdc-form-field.mat-focused .mat-mdc-select-arrow{color:var(--sd-primary)}::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field .mdc-notched-outline__leading,::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field .mdc-notched-outline__notch,::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field .mdc-notched-outline__trailing,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field .mdc-notched-outline__leading,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field .mdc-notched-outline__notch,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field .mdc-notched-outline__trailing{border-color:#d6d8db!important;transition:border-color .12s ease}::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field .mdc-notched-outline__leading,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field .mdc-notched-outline__leading{border-radius:6px 0 0 6px!important}::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field .mdc-notched-outline__trailing,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field .mdc-notched-outline__trailing{border-radius:0 6px 6px 0!important}::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field:hover:not(.mat-focused):not(.mat-form-field-disabled) .mdc-notched-outline__leading,::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field:hover:not(.mat-focused):not(.mat-form-field-disabled) .mdc-notched-outline__notch,::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field:hover:not(.mat-focused):not(.mat-form-field-disabled) .mdc-notched-outline__trailing,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field:hover:not(.mat-focused):not(.mat-form-field-disabled) .mdc-notched-outline__leading,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field:hover:not(.mat-focused):not(.mat-form-field-disabled) .mdc-notched-outline__notch,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field:hover:not(.mat-focused):not(.mat-form-field-disabled) .mdc-notched-outline__trailing{border-color:#b9bdc3!important}::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field.mat-focused .mdc-notched-outline__leading,::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field.mat-focused .mdc-notched-outline__notch,::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field.mat-focused .mdc-notched-outline__trailing,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field.mat-focused .mdc-notched-outline__leading,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field.mat-focused .mdc-notched-outline__notch,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field.mat-focused .mdc-notched-outline__trailing{border-color:var(--sd-primary)!important;border-width:1px!important}::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field .mat-mdc-select-arrow,::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field .mat-mdc-form-field-icon-suffix .mat-icon,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field .mat-mdc-select-arrow,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field .mat-mdc-form-field-icon-suffix .mat-icon{color:#5f6368}::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field.mat-focused .mat-mdc-select-arrow,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field.mat-focused .mat-mdc-select-arrow{color:var(--sd-primary)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i4.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i4.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i4.MatMenuContent, selector: "ng-template[matMenuContent]" }, { kind: "directive", type: i4.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: SdButton, selector: "sd-button", inputs: ["autoId", "type", "color", "size", "fontSet", "title", "width", "tooltip", "prefixIcon", "suffixIcon", "disabled", "loading", "block", "htmlType"], outputs: ["click"] }, { 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", "bare", "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", "tooltip", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "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", "sdFocusForceBlur"] }, { kind: "component", type: SdDate, selector: "sd-date", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "required", "disabled", "viewed", "bare", "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", "bare", "inlineError", "hyperlink", "appearance", "floatLabel", "min", "minDate", "max", "maxDate", "model"], outputs: ["modelChange", "sdChange", "sdFocus"] }, { kind: "component", type: SdQueryInlineValueChip, selector: "sd-query-inline-value-chip", inputs: ["field", "operator", "value", "density", "showOperator", "autoId", "autofocus"], outputs: ["valueChange", "remove"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1412
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: SdQueryBar, isStandalone: true, selector: "sd-query-bar", inputs: { autoIdInput: { classPropertyName: "autoIdInput", publicName: "autoId", isSignal: true, isRequired: false, transformFunction: null }, fields: { classPropertyName: "fields", publicName: "fields", isSignal: true, isRequired: false, transformFunction: null }, filters: { classPropertyName: "filters", publicName: "filters", isSignal: true, isRequired: false, transformFunction: null }, logic: { classPropertyName: "logic", publicName: "logic", isSignal: true, isRequired: false, transformFunction: null }, search: { classPropertyName: "search", publicName: "search", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, density: { classPropertyName: "density", publicName: "density", isSignal: true, isRequired: false, transformFunction: null }, showSearch: { classPropertyName: "showSearch", publicName: "showSearch", isSignal: true, isRequired: false, transformFunction: null }, showSavedFilters: { classPropertyName: "showSavedFilters", publicName: "showSavedFilters", isSignal: true, isRequired: false, transformFunction: null }, savedFiltersKey: { classPropertyName: "savedFiltersKey", publicName: "savedFiltersKey", isSignal: true, isRequired: false, transformFunction: null }, showLogicToggle: { classPropertyName: "showLogicToggle", publicName: "showLogicToggle", isSignal: true, isRequired: false, transformFunction: null }, showClearAll: { classPropertyName: "showClearAll", publicName: "showClearAll", isSignal: true, isRequired: false, transformFunction: null }, showOperatorOnChip: { classPropertyName: "showOperatorOnChip", publicName: "showOperatorOnChip", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { filters: "filtersChange", logic: "logicChange", search: "searchChange", queryChange: "queryChange", apply: "apply" }, viewQueries: [{ propertyName: "popoverChips", predicate: SdQueryPopoverChip, descendants: true, isSignal: true }, { propertyName: "buildChip", first: true, predicate: SdQueryBuildChip, descendants: true, isSignal: true }, { propertyName: "chipPopover", first: true, predicate: SdQueryChipPopover, descendants: true, isSignal: true }], ngImport: i0, template: "<!--\r\n sd-query-bar \u2014 unified filter chip row (Jira/Linear/Notion style).\r\n Visual spec: refs/design_handoff_sd_query_bar/README.md\r\n TODO(next-iter): replace inline placeholders for field-picker + chip-popover\r\n with extracted sub-components driven by CDK overlay.\r\n-->\r\n@let _filters = filters();\r\n@let _fields = fields();\r\n@let _density = density();\r\n@let _showOr = showOrConnector();\r\n@let _used = usedFieldKeys();\r\n\r\n<div\r\n class=\"c-query-bar\"\r\n [class.c-density-compact]=\"_density === 'compact'\"\r\n [class.c-density-comfortable]=\"_density === 'comfortable'\">\r\n\r\n <!-- Main region \u2014 search + filters + add-filter. Wraps internally; the right\r\n action toolbar stays anchored and does NOT flow with this. -->\r\n <div class=\"c-query-bar__main\">\r\n\r\n <!-- Free-text search (left, optional) -->\r\n @if (showSearch()) {\r\n <div class=\"c-search\">\r\n <mat-icon class=\"c-search-icon\" fontSet=\"material-icons-outlined\">search</mat-icon>\r\n <input\r\n class=\"c-search-input\"\r\n type=\"text\"\r\n [ngModel]=\"search()\"\r\n (ngModelChange)=\"setSearch($event)\"\r\n (keydown.enter)=\"triggerApply()\"\r\n placeholder=\"T\u00ECm ki\u1EBFm...\" />\r\n </div>\r\n <span class=\"c-divider\" aria-hidden=\"true\"></span>\r\n }\r\n\r\n <!-- Field switcher (used by chipPopover header) \u2014 declared first so chipPopover's\r\n nested ng-template can resolve the template ref. -->\r\n <!-- Field switcher used by chipPopover header \u2014 current field key comes from editingIndex. -->\r\n <sd-query-field-picker\r\n #switchPicker\r\n [fields]=\"_fields\"\r\n [currentKey]=\"editingIndex() !== null ? $any(filters()[editingIndex()!])?.field : undefined\"\r\n (pick)=\"editingIndex() !== null && changeFilterField(editingIndex()!, $event)\" />\r\n\r\n <!-- Chip popover \u2014 extracted into <sd-query-chip-popover>. Declared BEFORE chips\r\n so the popover-mode @for chips can bind their [menu] to `chipPopover.menu()`. -->\r\n <sd-query-chip-popover\r\n #chipPopover\r\n [field]=\"editingField() ?? undefined\"\r\n [filter]=\"editingIndex() !== null ? $any(filters()[editingIndex()!]) : undefined\"\r\n [chipIndex]=\"editingIndex()\"\r\n [autoIdBase]=\"autoIdInput() ?? 'qb'\"\r\n [switchPickerMenu]=\"switchPicker.menu()!\"\r\n (commit)=\"onChipPopoverCommit($event)\"\r\n (swapField)=\"editingIndex() !== null && changeFilterField(editingIndex()!, $event)\" />\r\n\r\n @if (mode() === 'inline') {\r\n <!-- ===== INLINE MODE (GitLab-style token builder) ===== -->\r\n\r\n <!-- Completed chips -->\r\n @for (filter of _filters; track $index; let i = $index) {\r\n @if (_showOr && i > 0) {\r\n <span class=\"c-or-connector\" aria-hidden=\"true\">OR</span>\r\n }\r\n @let _field = fieldByKey()[$any(filter).field];\r\n @if (_field) {\r\n @let _op = $any(filter.operator);\r\n @let _data = $any(filter).data;\r\n @if (_field.type === 'string' || _field.type === 'number') {\r\n <!-- Seamless chip \u2014 the pill is the input (string / number). -->\r\n <sd-query-inline-value-chip\r\n [field]=\"_field\"\r\n [operator]=\"_op\"\r\n [value]=\"_data\"\r\n [density]=\"_density\"\r\n [showOperator]=\"showOperatorOnChip()\"\r\n [autoId]=\"inlineAutoId(i, 'value')\"\r\n (valueChange)=\"updateFilter(i, { data: $event })\"\r\n (remove)=\"removeFilter(i)\" />\r\n } @else {\r\n <sd-query-inline-chip\r\n [field]=\"_field\"\r\n [filter]=\"filter\"\r\n [density]=\"_density\"\r\n [multiple]=\"multiple(_op)\"\r\n [showOperator]=\"showOperatorOnChip()\"\r\n [autoId]=\"inlineAutoId(i, 'value')\"\r\n [valueText]=\"chipValueText(filter)\"\r\n [isNoData]=\"isNoDataOperator(_op)\"\r\n (commit)=\"updateFilter(i, { data: $event })\"\r\n (commitRange)=\"setFilterRange(i, $event)\"\r\n (liveChange)=\"updateFilter(i, { data: $event })\"\r\n (remove)=\"removeFilter(i)\" />\r\n }\r\n }\r\n }\r\n\r\n <!-- Building chip (in-progress) \u2014 extracted into <sd-query-build-chip>. -->\r\n @if (building(); as _b) {\r\n <sd-query-build-chip\r\n [building]=\"_b\"\r\n [density]=\"_density\"\r\n [allowedOperators]=\"allowedOperatorsFor(_b.field)\"\r\n [multiple]=\"multiple($any(_b.operator))\"\r\n [showOperator]=\"showOperatorOnChip()\"\r\n (pickOperator)=\"pickBuildOperator($any($event))\"\r\n (commitValue)=\"commitBuildValue($event)\"\r\n (cancel)=\"cancelBuild()\"\r\n (seamlessCommit)=\"onBuildSeamlessCommit($event)\" />\r\n }\r\n\r\n <!-- Add filter -->\r\n <button\r\n type=\"button\"\r\n class=\"c-add-filter\"\r\n [matMenuTriggerFor]=\"addPicker.menu()\"\r\n [disabled]=\"_fields.length === 0 || _used.size >= _fields.length\"\r\n [matTooltip]=\"_fields.length === 0 ? 'Ch\u01B0a c\u1EA5u h\u00ECnh fields' : 'Th\u00EAm filter'\">\r\n <mat-icon fontSet=\"material-icons-outlined\">add</mat-icon>\r\n </button>\r\n } @else {\r\n <!-- ===== POPOVER MODE (default) \u2014 compact chips edited via mat-menu ===== -->\r\n @for (filter of _filters; track $index; let i = $index) {\r\n @if (_showOr && i > 0) {\r\n <span class=\"c-or-connector\" aria-hidden=\"true\">OR</span>\r\n }\r\n @let _field = fieldByKey()[$any(filter).field];\r\n @let _active = isFilterActive(filter);\r\n <!-- why: menu close commit is handled by chipPopover's (commit) \u2192\r\n onChipPopoverCommit; popover-chip only signals open + remove here. -->\r\n <sd-query-popover-chip\r\n [field]=\"_field\"\r\n [filter]=\"filter\"\r\n [active]=\"_active\"\r\n [showOperator]=\"showOperatorOnChip()\"\r\n [valueText]=\"chipValueText(filter)\"\r\n [menu]=\"chipPopover.menu()!\"\r\n (open)=\"openChipPopover(i)\"\r\n (remove)=\"removeFilter(i)\" />\r\n }\r\n\r\n <!-- Add filter trigger -->\r\n <button\r\n type=\"button\"\r\n class=\"c-add-filter\"\r\n [matMenuTriggerFor]=\"addPicker.menu()\"\r\n [disabled]=\"_fields.length === 0 || _used.size >= _fields.length\"\r\n [matTooltip]=\"_fields.length === 0 ? 'Ch\u01B0a c\u1EA5u h\u00ECnh fields' : 'Th\u00EAm filter'\">\r\n <mat-icon fontSet=\"material-icons-outlined\">add</mat-icon>\r\n </button>\r\n }\r\n\r\n </div><!-- /.c-query-bar__main -->\r\n\r\n <!-- Field picker \u2014 list available fields, fade ones already in use. -->\r\n <sd-query-field-picker\r\n #addPicker\r\n [fields]=\"_fields\"\r\n [usedKeys]=\"_used\"\r\n (pick)=\"mode() === 'inline' ? beginBuild($event) : addFilter($event)\" />\r\n\r\n <sd-query-actions-bar\r\n [showLogicToggle]=\"showLogicToggle()\"\r\n [(logic)]=\"logic\"\r\n [filtersCount]=\"_filters.length\"\r\n [showSavedFilters]=\"showSavedFilters()\"\r\n [savedFiltersKey]=\"savedFiltersKey()\"\r\n [query]=\"currentQuery()\"\r\n (applyFilter)=\"onApplyFilter($event)\"\r\n [showClearAll]=\"showClearAll()\"\r\n [canSearch]=\"canSearch()\"\r\n (clear)=\"clearAll()\"\r\n (search)=\"triggerApply()\" />\r\n</div>\r\n", styles: [":host{display:block;font-family:Roboto,system-ui,sans-serif}.c-query-bar{display:flex;align-items:flex-start;flex-wrap:nowrap;gap:8px;background:#fff;border:1px solid #e2e4e7;border-radius:8px;box-shadow:0 1px 2px #0f172a0a}.c-query-bar.c-density-compact{padding:8px}.c-query-bar.c-density-comfortable{padding:10px}.c-query-bar__main{flex:1 1 auto;min-width:0;display:flex;align-items:center;flex-wrap:wrap;gap:6px}.c-search{display:flex;align-items:center;gap:6px;min-width:220px;padding:0 10px;border:1px solid #d6d8db;border-radius:6px;background:#fff}.c-density-compact .c-search{height:28px}.c-density-comfortable .c-search{height:32px}.c-search .c-search-icon{font-size:16px;width:16px;height:16px;color:#7a7a7a}.c-search .c-search-input{flex:1;border:none;outline:none;background:transparent;font-size:13px;color:#212121}.c-search .c-search-input::placeholder{color:#7a7a7a}.c-search .c-search-kbd{font-family:Roboto Mono,monospace;font-size:10px;padding:1px 4px;border:1px solid #e2e4e7;border-radius:3px;color:#7a7a7a}.c-divider{width:1px;height:16px;background:#e2e4e7;margin:0 2px}.c-token{display:inline-flex;align-items:center;gap:4px;padding:0 0 0 12px;border:1px solid var(--sd-primary-light);border-radius:999px;background:var(--sd-primary-light);font-size:13px;color:#5f6368;white-space:nowrap;overflow:hidden}.c-density-compact .c-token{height:28px}.c-density-comfortable .c-token{height:32px}.c-token-icon{font-size:14px;width:14px;height:14px;line-height:14px;color:inherit}.c-token-field{font-weight:500;line-height:1}.c-token-op{display:inline-flex;align-items:center;color:inherit}.c-token-op ::ng-deep .c-op-trigger{padding:0;color:inherit}.c-token-op ::ng-deep .c-op-icon svg{width:14px;height:14px;display:block}.c-token-value-edit sd-select,.c-token-value-edit sd-date,.c-token-value-edit sd-datetime{display:inline-flex;align-items:center;line-height:1}.c-token ::ng-deep .mat-mdc-form-field{line-height:1}.c-token ::ng-deep .mat-mdc-text-field-wrapper,.c-token ::ng-deep .mat-mdc-form-field-flex{padding:0;min-height:0;background:transparent}.c-token ::ng-deep .mat-mdc-form-field-infix{padding:0;min-height:0;width:auto;border:0;display:inline-flex;align-items:center}.c-token ::ng-deep .mat-mdc-select-value,.c-token ::ng-deep .mat-mdc-input-element,.c-token ::ng-deep input{line-height:1;font-size:13px;padding:0;height:auto;color:inherit}.c-token ::ng-deep .mat-mdc-select-value{display:inline-flex;align-items:center;min-width:0;max-width:200px;overflow:hidden;text-overflow:ellipsis;color:var(--sd-primary);font-weight:500}.c-token ::ng-deep .sd-suffix-icon{display:none}.c-token-sep{color:#7a7a7a}.c-token-dash{color:#7a7a7a;padding:0 4px;line-height:1}.c-token-value{border:none;background:transparent;padding:0;font:inherit;font-weight:500;color:var(--sd-primary);cursor:pointer}.c-token-value:hover{text-decoration:underline}.c-token-value-edit{display:inline-flex;align-items:center;gap:4px;max-width:240px}.c-token-value-edit sd-input,.c-token-value-edit sd-input-number{display:inline-flex;max-width:140px}.c-token-value-edit sd-date-range{display:inline-flex;max-width:220px;min-width:0;flex:0 1 auto}.c-token-multi-head{display:inline-block;max-width:140px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom}.c-token-remove{align-self:stretch;display:inline-flex;align-items:center;justify-content:center;border:none;border-left:1px solid rgba(0,0,0,.04);border-radius:0;background:transparent;cursor:pointer;color:inherit}.c-density-compact .c-token-remove{width:26px}.c-density-comfortable .c-token-remove{width:28px}.c-token-remove mat-icon{font-size:14px;width:14px;height:14px}.c-token-remove:hover{background:#0000000d}.c-add-filter{display:inline-flex;align-items:center;justify-content:center;padding:0;border-radius:999px;border:1px dashed #d6d8db;background:transparent;cursor:pointer;color:#5f6368;transition:border-color .12s ease,color .12s ease,background-color .12s ease}.c-density-compact .c-add-filter{width:28px;height:28px}.c-density-comfortable .c-add-filter{width:32px;height:32px}.c-add-filter mat-icon{font-size:18px;width:18px;height:18px}.c-add-filter:hover:not([disabled]){border-color:var(--sd-primary);color:var(--sd-primary)}.c-add-filter[disabled]{opacity:.5;cursor:not-allowed}.c-or-connector{font-family:Roboto Mono,monospace;font-size:10px;font-weight:700;letter-spacing:.6px;color:#7a7a7a;padding:0 2px}::ng-deep .mat-mdc-menu-panel.c-chip-popover{min-width:280px;max-width:320px}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-header{display:flex;align-items:center;gap:8px;padding:10px 12px;border-bottom:1px solid #e2e4e7;font-size:13px;font-weight:500;color:#212121}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-header mat-icon{font-size:16px;width:16px;height:16px;color:var(--sd-primary)}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-header .c-pop-header-operator{margin-left:auto}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-header-field{display:inline-flex;align-items:center;gap:4px;padding:2px 4px 2px 6px;margin-left:-4px;background:transparent;border:1px solid transparent;border-radius:4px;font:inherit;font-weight:500;color:#212121;cursor:pointer;transition:background-color .12s ease,border-color .12s ease}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-header-field mat-icon{font-size:14px!important;width:14px!important;height:14px!important;color:#7a7a7a!important}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-header-field:hover{background:#f5f6f7;border-color:#e2e4e7}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-body{padding:10px 12px;display:flex;flex-direction:column;gap:12px}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-input{width:100%;height:32px;padding:0 10px;border:1px solid #d6d8db;border-radius:6px;background:#fff;font-family:inherit;font-size:13px;color:#212121;outline:none}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-input:focus{border-color:var(--sd-primary)}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-between{display:grid;grid-template-columns:1fr 1fr;gap:8px}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-boolean{display:flex;gap:8px}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-boolean button{flex:1;height:32px;border:1px solid #d6d8db;border-radius:6px;background:#fff;font:inherit;font-size:13px;cursor:pointer}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-boolean button.c-pop-boolean-active{background:var(--sd-primary-light);border-color:var(--sd-primary);color:var(--sd-primary)}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-values{max-height:220px;overflow-y:auto;margin:0;padding:0;list-style:none}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-values li{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:4px;font-size:13px;cursor:pointer;-webkit-user-select:none;user-select:none}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-values li:hover{background:#f5f6f7}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-values li.c-pop-value-selected{background:var(--sd-primary-light);color:var(--sd-primary)}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-values li input[type=checkbox],::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-values li input[type=radio]{accent-color:var(--sd-primary)}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-values-loading,::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-values-empty{padding:10px 8px;font-size:12px;color:#7a7a7a}.c-bool-toggle{display:inline-flex;align-items:center;gap:4px}.c-bool-toggle ::ng-deep .mdc-button,.c-bool-toggle ::ng-deep button[mat-button],.c-bool-toggle ::ng-deep button[mat-stroked-button],.c-bool-toggle ::ng-deep button[mat-flat-button]{border-radius:999px;min-width:0;padding:0 12px}\n", ".c-query-bar ::ng-deep .mat-mdc-form-field .mdc-notched-outline__leading,.c-query-bar ::ng-deep .mat-mdc-form-field .mdc-notched-outline__notch,.c-query-bar ::ng-deep .mat-mdc-form-field .mdc-notched-outline__trailing{border-color:#d6d8db!important;transition:border-color .12s ease}.c-query-bar ::ng-deep .mat-mdc-form-field .mdc-notched-outline__leading{border-radius:6px 0 0 6px!important}.c-query-bar ::ng-deep .mat-mdc-form-field .mdc-notched-outline__trailing{border-radius:0 6px 6px 0!important}.c-query-bar ::ng-deep .mat-mdc-form-field:hover:not(.mat-focused):not(.mat-form-field-disabled) .mdc-notched-outline__leading,.c-query-bar ::ng-deep .mat-mdc-form-field:hover:not(.mat-focused):not(.mat-form-field-disabled) .mdc-notched-outline__notch,.c-query-bar ::ng-deep .mat-mdc-form-field:hover:not(.mat-focused):not(.mat-form-field-disabled) .mdc-notched-outline__trailing{border-color:#b9bdc3!important}.c-query-bar ::ng-deep .mat-mdc-form-field.mat-focused .mdc-notched-outline__leading,.c-query-bar ::ng-deep .mat-mdc-form-field.mat-focused .mdc-notched-outline__notch,.c-query-bar ::ng-deep .mat-mdc-form-field.mat-focused .mdc-notched-outline__trailing{border-color:var(--sd-primary)!important;border-width:1px!important}.c-query-bar ::ng-deep .mat-mdc-form-field .mat-mdc-select-arrow,.c-query-bar ::ng-deep .mat-mdc-form-field .mat-mdc-form-field-icon-suffix .mat-icon{color:#5f6368}.c-query-bar ::ng-deep .mat-mdc-form-field.mat-focused .mat-mdc-select-arrow{color:var(--sd-primary)}::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field .mdc-notched-outline__leading,::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field .mdc-notched-outline__notch,::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field .mdc-notched-outline__trailing,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field .mdc-notched-outline__leading,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field .mdc-notched-outline__notch,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field .mdc-notched-outline__trailing{border-color:#d6d8db!important;transition:border-color .12s ease}::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field .mdc-notched-outline__leading,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field .mdc-notched-outline__leading{border-radius:6px 0 0 6px!important}::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field .mdc-notched-outline__trailing,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field .mdc-notched-outline__trailing{border-radius:0 6px 6px 0!important}::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field:hover:not(.mat-focused):not(.mat-form-field-disabled) .mdc-notched-outline__leading,::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field:hover:not(.mat-focused):not(.mat-form-field-disabled) .mdc-notched-outline__notch,::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field:hover:not(.mat-focused):not(.mat-form-field-disabled) .mdc-notched-outline__trailing,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field:hover:not(.mat-focused):not(.mat-form-field-disabled) .mdc-notched-outline__leading,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field:hover:not(.mat-focused):not(.mat-form-field-disabled) .mdc-notched-outline__notch,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field:hover:not(.mat-focused):not(.mat-form-field-disabled) .mdc-notched-outline__trailing{border-color:#b9bdc3!important}::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field.mat-focused .mdc-notched-outline__leading,::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field.mat-focused .mdc-notched-outline__notch,::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field.mat-focused .mdc-notched-outline__trailing,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field.mat-focused .mdc-notched-outline__leading,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field.mat-focused .mdc-notched-outline__notch,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field.mat-focused .mdc-notched-outline__trailing{border-color:var(--sd-primary)!important;border-width:1px!important}::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field .mat-mdc-select-arrow,::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field .mat-mdc-form-field-icon-suffix .mat-icon,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field .mat-mdc-select-arrow,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field .mat-mdc-form-field-icon-suffix .mat-icon{color:#5f6368}::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field.mat-focused .mat-mdc-select-arrow,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field.mat-focused .mat-mdc-select-arrow{color:var(--sd-primary)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "directive", type: i1.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i3.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: SdQueryActionsBar, selector: "sd-query-actions-bar", inputs: ["showLogicToggle", "logic", "filtersCount", "showSavedFilters", "savedFiltersKey", "query", "showClearAll", "canSearch"], outputs: ["logicChange", "applyFilter", "clear", "search"] }, { kind: "component", type: SdQueryBuildChip, selector: "sd-query-build-chip", inputs: ["building", "density", "allowedOperators", "multiple", "showOperator", "autoId"], outputs: ["pickOperator", "commitValue", "cancel", "seamlessCommit", "draftChange", "draftCommit"] }, { kind: "component", type: SdQueryChipPopover, selector: "sd-query-chip-popover", inputs: ["field", "filter", "chipIndex", "autoIdBase", "switchPickerMenu"], outputs: ["commit", "swapField"] }, { kind: "component", type: SdQueryFieldPicker, selector: "sd-query-field-picker", inputs: ["fields", "currentKey", "usedKeys"], outputs: ["pick"] }, { kind: "component", type: SdQueryInlineChip, selector: "sd-query-inline-chip", inputs: ["field", "filter", "density", "multiple", "showOperator", "autoId", "valueText", "isNoData"], outputs: ["commit", "commitRange", "liveChange", "remove"] }, { kind: "component", type: SdQueryInlineValueChip, selector: "sd-query-inline-value-chip", inputs: ["field", "operator", "value", "density", "showOperator", "autoId", "autofocus"], outputs: ["valueChange", "remove"] }, { kind: "component", type: SdQueryPopoverChip, selector: "sd-query-popover-chip", inputs: ["field", "filter", "active", "showOperator", "valueText", "menu"], outputs: ["open", "remove"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
973
1413
  }
974
1414
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdQueryBar, decorators: [{
975
1415
  type: Component,
@@ -979,20 +1419,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImpo
979
1419
  MatIconModule,
980
1420
  MatMenuModule,
981
1421
  MatTooltipModule,
982
- SdButton,
983
- SdOperator,
984
- SdSelect,
985
- SdInput,
986
- SdInputNumber,
987
- SdDate,
988
- SdDatetime,
1422
+ SdQueryActionsBar,
1423
+ SdQueryBuildChip,
1424
+ SdQueryChipPopover,
1425
+ SdQueryFieldPicker,
1426
+ SdQueryInlineChip,
989
1427
  SdQueryInlineValueChip,
990
- ], template: "<!--\r\n sd-query-bar \u2014 unified filter chip row (Jira/Linear/Notion style).\r\n Visual spec: refs/design_handoff_sd_query_bar/README.md\r\n TODO(next-iter): replace inline placeholders for field-picker + chip-popover\r\n with extracted sub-components driven by CDK overlay.\r\n-->\r\n@let _filters = filters();\r\n@let _fields = fields();\r\n@let _logic = logic();\r\n@let _density = density();\r\n@let _showOr = showOrConnector();\r\n@let _used = usedFieldKeys();\r\n\r\n<div\r\n class=\"c-query-bar\"\r\n [class.c-density-compact]=\"_density === 'compact'\"\r\n [class.c-density-comfortable]=\"_density === 'comfortable'\">\r\n\r\n <!-- Main region \u2014 search + filters + add-filter. Wraps internally; the right\r\n action toolbar stays anchored and does NOT flow with this. -->\r\n <div class=\"c-query-bar__main\">\r\n\r\n <!-- Free-text search (left, optional) -->\r\n @if (showSearch()) {\r\n <div class=\"c-search\">\r\n <mat-icon class=\"c-search-icon\" fontSet=\"material-icons-outlined\">search</mat-icon>\r\n <input\r\n class=\"c-search-input\"\r\n type=\"text\"\r\n [ngModel]=\"search()\"\r\n (ngModelChange)=\"setSearch($event)\"\r\n (keydown.enter)=\"triggerApply()\"\r\n placeholder=\"T\u00ECm ki\u1EBFm...\" />\r\n </div>\r\n <span class=\"c-divider\" aria-hidden=\"true\"></span>\r\n }\r\n\r\n <!-- Field switcher (used by chipPopover header) \u2014 declared first so chipPopover's\r\n nested ng-template can resolve the template ref. -->\r\n <mat-menu #fieldSwitchPicker=\"matMenu\" xPosition=\"after\" yPosition=\"below\" class=\"c-field-picker-menu\">\r\n <ng-template matMenuContent let-currentIndex=\"currentIndex\">\r\n @let _currentFilter = filters()[currentIndex];\r\n @let _currentKey = $any(_currentFilter)?.field;\r\n @for (field of _fields; track $index) {\r\n @let _isCurrent = field.key === _currentKey;\r\n <button\r\n type=\"button\"\r\n mat-menu-item\r\n [disabled]=\"_isCurrent || field.disabled\"\r\n (click)=\"changeFilterField(currentIndex, field)\">\r\n <mat-icon fontSet=\"material-icons-outlined\" class=\"c-field-item-icon\">\r\n {{ iconFor(field) }}\r\n </mat-icon>\r\n <span class=\"c-field-item-label\">{{ field.label }}</span>\r\n <span class=\"c-field-item-kind\">{{ field.kind }}</span>\r\n @if (_isCurrent) {\r\n <mat-icon class=\"c-field-item-check\" fontSet=\"material-icons-outlined\">check</mat-icon>\r\n }\r\n </button>\r\n }\r\n </ng-template>\r\n </mat-menu>\r\n\r\n <!-- Chip popover \u2014 declared BEFORE chips so @for can resolve the forward template ref -->\r\n <mat-menu #chipPopover=\"matMenu\" class=\"c-chip-popover\" xPosition=\"after\" yPosition=\"below\">\r\n <ng-template matMenuContent let-index=\"index\">\r\n @let _editField = editingField();\r\n @let _editOp = editingOperator();\r\n @let _editVal = editingValue();\r\n @let _options = editingOptions();\r\n @let _loading = editingOptionsLoading();\r\n\r\n @if (_editField) {\r\n <div class=\"c-pop-header\" (click)=\"$event.stopPropagation()\">\r\n <mat-icon fontSet=\"material-icons-outlined\">{{ iconFor(_editField) }}</mat-icon>\r\n <!-- Click field name \u2192 open nested picker to swap field on this chip. -->\r\n <button\r\n type=\"button\"\r\n class=\"c-pop-header-field\"\r\n [matMenuTriggerFor]=\"fieldSwitchPicker\"\r\n [matMenuTriggerData]=\"{ currentIndex: index }\"\r\n matTooltip=\"\u0110\u1ED5i field\">\r\n <span>{{ _editField.label }}</span>\r\n <mat-icon fontSet=\"material-icons-outlined\">unfold_more</mat-icon>\r\n </button>\r\n <!-- why: operator moved into the header (right-aligned) to compact the popover.\r\n Always shown \u2014 when the field has \u22641 operator it renders disabled so the\r\n user still sees which operator the filter uses. -->\r\n <sd-operator\r\n class=\"c-pop-header-operator\"\r\n [autoId]=\"chipAutoId('operator')\"\r\n [operators]=\"editingAllowedOperators()\"\r\n [model]=\"_editOp\"\r\n [disabled]=\"!editingShowOperatorSelector()\"\r\n (modelChange)=\"onEditingOperatorChange($any($event))\" />\r\n </div>\r\n\r\n <div class=\"c-pop-body\" (click)=\"$event.stopPropagation()\">\r\n <!-- Value \u2014 per-kind sd-* control, skipped for NULL / NOT_NULL. Placeholder\r\n makes the field self-explanatory now that section labels are gone. -->\r\n @if (!isNoDataOperator(_editOp)) {\r\n @if (_editOp === 'BETWEEN') {\r\n @if (_editField.kind === 'date') {\r\n <div class=\"c-pop-between\">\r\n <sd-date size=\"sm\"\r\n [autoId]=\"chipAutoId('value-from')\"\r\n placeholder=\"T\u1EEB\"\r\n [model]=\"$any(_editVal)?.from\"\r\n (sdChange)=\"onEditingRangeFrom($event)\">\r\n </sd-date>\r\n <sd-date size=\"sm\"\r\n [autoId]=\"chipAutoId('value-to')\"\r\n placeholder=\"\u0110\u1EBFn\"\r\n [model]=\"$any(_editVal)?.to\"\r\n (sdChange)=\"onEditingRangeTo($event)\">\r\n </sd-date>\r\n </div>\r\n } @else if (_editField.kind === 'datetime') {\r\n <div class=\"c-pop-between\">\r\n <sd-datetime size=\"sm\"\r\n [autoId]=\"chipAutoId('value-from')\"\r\n placeholder=\"T\u1EEB\"\r\n [model]=\"$any(_editVal)?.from\"\r\n (sdChange)=\"onEditingRangeFrom($event)\">\r\n </sd-datetime>\r\n <sd-datetime size=\"sm\"\r\n [autoId]=\"chipAutoId('value-to')\"\r\n placeholder=\"\u0110\u1EBFn\"\r\n [model]=\"$any(_editVal)?.to\"\r\n (sdChange)=\"onEditingRangeTo($event)\">\r\n </sd-datetime>\r\n </div>\r\n } @else {\r\n <div class=\"c-pop-between\">\r\n <sd-input-number size=\"sm\"\r\n [autoId]=\"chipAutoId('value-from')\"\r\n placeholder=\"T\u1EEB\"\r\n [model]=\"$any(_editVal)?.from\"\r\n (sdChange)=\"onEditingRangeFrom($event)\">\r\n </sd-input-number>\r\n <sd-input-number size=\"sm\"\r\n [autoId]=\"chipAutoId('value-to')\"\r\n placeholder=\"\u0110\u1EBFn\"\r\n [model]=\"$any(_editVal)?.to\"\r\n (sdChange)=\"onEditingRangeTo($event)\">\r\n </sd-input-number>\r\n </div>\r\n }\r\n } @else if (_editField.kind === 'boolean') {\r\n <div class=\"c-pop-boolean\">\r\n <sd-button\r\n [autoId]=\"chipAutoId('value-true')\"\r\n [type]=\"_editVal === true ? 'fill' : 'outline'\"\r\n color=\"primary\"\r\n [title]=\"$any(_editField).trueLabel || 'C\u00F3'\"\r\n (click)=\"onEditingValueInput(true)\">\r\n </sd-button>\r\n <sd-button\r\n [autoId]=\"chipAutoId('value-false')\"\r\n [type]=\"_editVal === false ? 'fill' : 'outline'\"\r\n color=\"primary\"\r\n [title]=\"$any(_editField).falseLabel || 'Kh\u00F4ng'\"\r\n (click)=\"onEditingValueInput(false)\">\r\n </sd-button>\r\n </div>\r\n } @else if (_editField.kind === 'values' || _editField.kind === 'lazy-values') {\r\n @let _opt = $any(_editField).option;\r\n <sd-select size=\"sm\"\r\n [autoId]=\"chipAutoId('value')\"\r\n placeholder=\"Ch\u1ECDn gi\u00E1 tr\u1ECB\"\r\n [items]=\"_options\"\r\n [valueField]=\"_opt.valueField\"\r\n [displayField]=\"_opt.displayField\"\r\n [multiple]=\"editingIsMulti()\"\r\n [model]=\"$any(_editVal)\"\r\n (sdChange)=\"onEditingValueInput($event)\">\r\n </sd-select>\r\n @if (_loading) {\r\n <div class=\"c-pop-values-loading\">\u0110ang t\u1EA3i...</div>\r\n }\r\n } @else if (_editField.kind === 'date') {\r\n <sd-date size=\"sm\"\r\n [autoId]=\"chipAutoId('value')\"\r\n placeholder=\"Ch\u1ECDn gi\u00E1 tr\u1ECB\"\r\n [model]=\"$any(_editVal)\"\r\n (sdChange)=\"onEditingValueInput($event)\">\r\n </sd-date>\r\n } @else if (_editField.kind === 'datetime') {\r\n <sd-datetime size=\"sm\"\r\n [autoId]=\"chipAutoId('value')\"\r\n placeholder=\"Ch\u1ECDn gi\u00E1 tr\u1ECB\"\r\n [model]=\"$any(_editVal)\"\r\n (sdChange)=\"onEditingValueInput($event)\">\r\n </sd-datetime>\r\n } @else if (_editField.kind === 'number') {\r\n <sd-input-number size=\"sm\"\r\n [autoId]=\"chipAutoId('value')\"\r\n placeholder=\"Nh\u1EADp gi\u00E1 tr\u1ECB\"\r\n [model]=\"$any(_editVal)\"\r\n (sdChange)=\"onEditingValueInput($event)\">\r\n </sd-input-number>\r\n } @else {\r\n <sd-input size=\"sm\"\r\n [autoId]=\"chipAutoId('value')\"\r\n placeholder=\"Nh\u1EADp gi\u00E1 tr\u1ECB\"\r\n [model]=\"$any(_editVal)\"\r\n (sdChange)=\"onEditingValueInput($event)\">\r\n </sd-input>\r\n }\r\n }\r\n </div>\r\n }\r\n </ng-template>\r\n </mat-menu>\r\n\r\n @if (mode() === 'inline') {\r\n <!-- ===== INLINE MODE (GitLab-style token builder) ===== -->\r\n\r\n <!-- Reusable value editor. ctx: field, data, isMulti, change (stash fn), enter (finalize fn), autoId\r\n why: text/number commit on Enter, so the typed value is stashed via `change` then finalized\r\n by `enter`; selection-style controls (select/date/boolean) stash + finalize in one action. -->\r\n <ng-template #valueEditor let-field=\"field\" let-data=\"data\" let-isMulti=\"isMulti\" let-change=\"change\" let-enter=\"enter\" let-autoId=\"autoId\">\r\n @if (field.kind === 'boolean') {\r\n <sd-button [autoId]=\"autoId + '-true'\" [type]=\"data === true ? 'fill' : 'outline'\" color=\"primary\" [title]=\"$any(field).trueLabel || 'C\u00F3'\" (click)=\"change(true); enter()\"></sd-button>\r\n <sd-button [autoId]=\"autoId + '-false'\" [type]=\"data === false ? 'fill' : 'outline'\" color=\"primary\" [title]=\"$any(field).falseLabel || 'Kh\u00F4ng'\" (click)=\"change(false); enter()\"></sd-button>\r\n } @else if (field.kind === 'values') {\r\n @let _opt = $any(field).option;\r\n <sd-select bare size=\"sm\" minWidthPanel=\"220px\" [autoId]=\"autoId\" [items]=\"$any(_opt.items)\" [valueField]=\"_opt.valueField\" [displayField]=\"_opt.displayField\" [multiple]=\"isMulti\" [model]=\"data\" (sdChange)=\"change($event); enter()\"></sd-select>\r\n } @else if (field.kind === 'lazy-values') {\r\n @let _lopt = $any(field).option;\r\n <sd-select bare size=\"sm\" minWidthPanel=\"220px\" [autoId]=\"autoId\" [items]=\"lazyItemsFor(field)\" [valueField]=\"_lopt.valueField\" [displayField]=\"_lopt.displayField\" [multiple]=\"isMulti\" [model]=\"data\" (sdChange)=\"change($event); enter()\"></sd-select>\r\n } @else if (field.kind === 'date') {\r\n <sd-date bare size=\"sm\" [autoId]=\"autoId\" [model]=\"data\" (sdChange)=\"change($event); enter()\"></sd-date>\r\n } @else if (field.kind === 'datetime') {\r\n <sd-datetime bare size=\"sm\" [autoId]=\"autoId\" [model]=\"data\" (sdChange)=\"change($event); enter()\"></sd-datetime>\r\n } @else if (field.kind === 'number') {\r\n <sd-input-number size=\"sm\" [autoId]=\"autoId\" [model]=\"data\" (sdChange)=\"change($event)\" (keyupEnter)=\"enter()\"></sd-input-number>\r\n } @else {\r\n <sd-input size=\"sm\" [autoId]=\"autoId\" [model]=\"data\" (sdChange)=\"change($event)\" (keyupEnter)=\"enter()\"></sd-input>\r\n }\r\n </ng-template>\r\n\r\n <!-- Completed chips -->\r\n @for (filter of _filters; track $index; let i = $index) {\r\n @if (_showOr && i > 0) {\r\n <span class=\"c-or-connector\" aria-hidden=\"true\">OR</span>\r\n }\r\n @let _field = fieldByKey()[$any(filter).field];\r\n @if (_field) {\r\n @let _op = $any(filter.operator);\r\n @let _data = $any(filter).data;\r\n @if (_field.kind === 'string' || _field.kind === 'number') {\r\n <!-- Seamless chip \u2014 the pill is the input (string / number). -->\r\n <sd-query-inline-value-chip\r\n [field]=\"_field\"\r\n [operator]=\"_op\"\r\n [value]=\"_data\"\r\n [density]=\"_density\"\r\n [showOperator]=\"showOperatorOnChip()\"\r\n [autoId]=\"inlineAutoId(i, 'value')\"\r\n (valueChange)=\"updateFilter(i, { data: $event })\"\r\n (remove)=\"removeFilter(i)\" />\r\n } @else {\r\n <div class=\"c-token\" [class.c-token-editing]=\"isEditingValue(i)\">\r\n <mat-icon class=\"c-token-icon\" fontSet=\"material-icons-outlined\">{{ iconFor(_field) }}</mat-icon>\r\n <span class=\"c-token-field\">{{ _field.label }}</span>\r\n @if (!isNoDataOperator(_op) && showOperatorOnChip()) {\r\n <sd-operator class=\"c-token-op\" [operators]=\"[_op]\" [model]=\"_op\" [disabled]=\"true\" />\r\n } @else if (!isNoDataOperator(_op)) {\r\n <span class=\"c-token-sep\">:</span>\r\n }\r\n @if (!isNoDataOperator(_op)) {\r\n @if (_field.kind === 'boolean') {\r\n @if (isEditingValue(i)) {\r\n <span class=\"c-token-value c-token-value-edit\">\r\n <ng-container *ngTemplateOutlet=\"valueEditor; context: { field: _field, data: _data, isMulti: isMultiOperator(_op), change: setEditDraftFn(), enter: commitEditDraftFn(i), autoId: inlineAutoId(i, 'value') }\"></ng-container>\r\n </span>\r\n } @else {\r\n <button type=\"button\" class=\"c-token-value\" (click)=\"beginEditValue(i)\">{{ chipValueText(filter) }}</button>\r\n }\r\n } @else if ((_field.kind === 'date' || _field.kind === 'datetime') && _op === 'BETWEEN') {\r\n <!-- why: chip BETWEEN date/datetime \u0111\u00E3 commit -> render 2 picker inline cho ph\u00E9p s\u1EEDa t\u1EEBng \u0111\u1EA7u -->\r\n <span class=\"c-token-value c-token-value-edit\">\r\n @if (_field.kind === 'date') {\r\n <sd-date bare size=\"sm\" placeholder=\"T\u1EEB\"\r\n [autoId]=\"inlineAutoId(i, 'value-from')\"\r\n [model]=\"$any(_data)?.from\"\r\n (sdChange)=\"setFilterRangeFrom(i, $any($event))\"></sd-date>\r\n <span class=\"c-token-dash\" aria-hidden=\"true\">\u2014</span>\r\n <sd-date bare size=\"sm\" placeholder=\"\u0110\u1EBFn\"\r\n [autoId]=\"inlineAutoId(i, 'value-to')\"\r\n [model]=\"$any(_data)?.to\"\r\n (sdChange)=\"setFilterRangeTo(i, $any($event))\"></sd-date>\r\n } @else {\r\n <sd-datetime bare size=\"sm\" placeholder=\"T\u1EEB\"\r\n [autoId]=\"inlineAutoId(i, 'value-from')\"\r\n [model]=\"$any(_data)?.from\"\r\n (sdChange)=\"setFilterRangeFrom(i, $any($event))\"></sd-datetime>\r\n <span class=\"c-token-dash\" aria-hidden=\"true\">\u2014</span>\r\n <sd-datetime bare size=\"sm\" placeholder=\"\u0110\u1EBFn\"\r\n [autoId]=\"inlineAutoId(i, 'value-to')\"\r\n [model]=\"$any(_data)?.to\"\r\n (sdChange)=\"setFilterRangeTo(i, $any($event))\"></sd-datetime>\r\n }\r\n </span>\r\n } @else if (_field.kind === 'values' || _field.kind === 'lazy-values') {\r\n @let _opt = $any(_field).option;\r\n <span\r\n class=\"c-token-value c-token-value-edit\"\r\n (click)=\"beginEditValue(i)\"\r\n (focusout)=\"onChipValueFocusOut(i, $event)\">\r\n <sd-select bare size=\"sm\" minWidthPanel=\"220px\"\r\n [autoId]=\"inlineAutoId(i, 'value')\"\r\n [items]=\"_field.kind === 'lazy-values' ? lazyItemsFor(_field) : $any(_opt.items)\"\r\n [valueField]=\"_opt.valueField\"\r\n [displayField]=\"_opt.displayField\"\r\n [multiple]=\"isMultiOperator(_op)\"\r\n [viewed]=\"!isEditingValue(i)\"\r\n [model]=\"_data\"\r\n (sdChange)=\"isMultiOperator(_op) ? editValueFn(i)($event) : onChipSingleCommit(i, $event)\"></sd-select>\r\n </span>\r\n } @else if (_field.kind === 'date') {\r\n <span\r\n class=\"c-token-value c-token-value-edit\"\r\n (click)=\"beginEditValue(i)\"\r\n (focusout)=\"onChipValueFocusOut(i, $event)\">\r\n <sd-date bare size=\"sm\"\r\n [autoId]=\"inlineAutoId(i, 'value')\"\r\n [viewed]=\"!isEditingValue(i)\"\r\n [model]=\"_data\"\r\n (sdChange)=\"onChipSingleCommit(i, $any($event))\"></sd-date>\r\n </span>\r\n } @else if (_field.kind === 'datetime') {\r\n <span\r\n class=\"c-token-value c-token-value-edit\"\r\n (click)=\"beginEditValue(i)\"\r\n (focusout)=\"onChipValueFocusOut(i, $event)\">\r\n <sd-datetime bare size=\"sm\"\r\n [autoId]=\"inlineAutoId(i, 'value')\"\r\n [viewed]=\"!isEditingValue(i)\"\r\n [model]=\"_data\"\r\n (sdChange)=\"onChipSingleCommit(i, $any($event))\"></sd-datetime>\r\n </span>\r\n } @else {\r\n <!-- Fallback (number / other) \u2014 keep the shared editor outlet path. -->\r\n <span class=\"c-token-value c-token-value-edit\">\r\n <ng-container *ngTemplateOutlet=\"valueEditor; context: { field: _field, data: _data, isMulti: isMultiOperator(_op), change: editValueFn(i), enter: noop, autoId: inlineAutoId(i, 'value') }\"></ng-container>\r\n </span>\r\n }\r\n }\r\n <button type=\"button\" class=\"c-token-remove\" aria-label=\"Remove filter\" (click)=\"removeFilter(i)\">\r\n <mat-icon fontSet=\"material-icons-outlined\">close</mat-icon>\r\n </button>\r\n </div>\r\n }\r\n }\r\n }\r\n\r\n <!-- Building chip (in-progress) -->\r\n @if (building(); as _b) {\r\n @if (_b.step === 'value' && (_b.field.kind === 'string' || _b.field.kind === 'number')) {\r\n <!-- Seamless build chip \u2014 type straight in, no rectangle. Enter/blur commits. -->\r\n <sd-query-inline-value-chip\r\n [field]=\"_b.field\"\r\n [operator]=\"$any(_b.operator)\"\r\n [value]=\"_b.value ?? null\"\r\n [density]=\"_density\"\r\n [showOperator]=\"showOperatorOnChip()\"\r\n [autoId]=\"'qb-build-value'\"\r\n [autofocus]=\"true\"\r\n (valueChange)=\"onBuildSeamlessCommit($event)\"\r\n (remove)=\"cancelBuild()\" />\r\n } @else {\r\n <div class=\"c-token c-token-building\">\r\n <span class=\"c-token-field\">{{ _b.field.label }}</span>\r\n @if (_b.step === 'operator') {\r\n <sd-operator #buildOperator class=\"c-token-op\" [operators]=\"allowedOperatorsFor(_b.field)\" [model]=\"_b.operator\" (modelChange)=\"pickBuildOperator($any($event))\" />\r\n } @else {\r\n <sd-operator class=\"c-token-op\" [operators]=\"[$any(_b.operator)]\" [model]=\"$any(_b.operator)\" [disabled]=\"true\" />\r\n <!-- why: build value step renders the bare picker with a #bPicker ref so the\r\n build flow can auto-open its native panel; sd-select needs minWidthPanel so the\r\n narrow bare trigger still opens a readable panel. boolean keeps the editor outlet. -->\r\n @if (_b.field.kind === 'values' || _b.field.kind === 'lazy-values') {\r\n @let _bopt = $any(_b.field).option;\r\n <span class=\"c-token-value c-token-value-edit\">\r\n <sd-select #bPicker bare size=\"sm\" minWidthPanel=\"220px\" autoId=\"qb-build-value\"\r\n [items]=\"_b.field.kind === 'lazy-values' ? lazyItemsFor(_b.field) : $any(_bopt.items)\"\r\n [valueField]=\"_bopt.valueField\" [displayField]=\"_bopt.displayField\"\r\n [multiple]=\"isMultiOperator($any(_b.operator))\" [model]=\"$any(_b.value)\"\r\n (sdChange)=\"commitBuildValue($event)\"></sd-select>\r\n </span>\r\n } @else if (_b.field.kind === 'date') {\r\n <span class=\"c-token-value c-token-value-edit\">\r\n <!-- why: BETWEEN c\u1EA7n 2 picker (T\u1EEB / \u0110\u1EBFn); #bPicker \u0111\u1EB7t tr\u00EAn picker T\u1EEB \u0111\u1EC3 auto-open gi\u1EEF h\u00E0nh vi c\u0169 -->\r\n @if (_b.operator === 'BETWEEN') {\r\n <sd-date #bPicker bare size=\"sm\" autoId=\"qb-build-value-from\" placeholder=\"T\u1EEB\"\r\n [model]=\"$any(_b.value)?.from\" (sdChange)=\"setBuildRangeFrom($event)\"></sd-date>\r\n <span class=\"c-token-dash\" aria-hidden=\"true\">\u2014</span>\r\n <sd-date bare size=\"sm\" autoId=\"qb-build-value-to\" placeholder=\"\u0110\u1EBFn\"\r\n [model]=\"$any(_b.value)?.to\" (sdChange)=\"setBuildRangeTo($event)\"></sd-date>\r\n } @else {\r\n <sd-date #bPicker bare size=\"sm\" autoId=\"qb-build-value\" [model]=\"$any(_b.value)\" (sdChange)=\"commitBuildValue($event)\"></sd-date>\r\n }\r\n </span>\r\n } @else if (_b.field.kind === 'datetime') {\r\n <span class=\"c-token-value c-token-value-edit\">\r\n <!-- why: BETWEEN c\u1EA7n 2 picker (T\u1EEB / \u0110\u1EBFn); #bPicker \u0111\u1EB7t tr\u00EAn picker T\u1EEB \u0111\u1EC3 auto-open gi\u1EEF h\u00E0nh vi c\u0169 -->\r\n @if (_b.operator === 'BETWEEN') {\r\n <sd-datetime #bPicker bare size=\"sm\" autoId=\"qb-build-value-from\" placeholder=\"T\u1EEB\"\r\n [model]=\"$any(_b.value)?.from\" (sdChange)=\"setBuildRangeFrom($event)\"></sd-datetime>\r\n <span class=\"c-token-dash\" aria-hidden=\"true\">\u2014</span>\r\n <sd-datetime bare size=\"sm\" autoId=\"qb-build-value-to\" placeholder=\"\u0110\u1EBFn\"\r\n [model]=\"$any(_b.value)?.to\" (sdChange)=\"setBuildRangeTo($event)\"></sd-datetime>\r\n } @else {\r\n <sd-datetime #bPicker bare size=\"sm\" autoId=\"qb-build-value\" [model]=\"$any(_b.value)\" (sdChange)=\"commitBuildValue($event)\"></sd-datetime>\r\n }\r\n </span>\r\n } @else {\r\n <span class=\"c-token-value c-token-value-edit\">\r\n <ng-container *ngTemplateOutlet=\"valueEditor; context: { field: _b.field, data: _b.value ?? null, isMulti: isMultiOperator($any(_b.operator)), change: setBuildDraftFn(), enter: commitBuildDraftFn(), autoId: 'qb-build-value' }\"></ng-container>\r\n </span>\r\n }\r\n }\r\n <button type=\"button\" class=\"c-token-remove\" aria-label=\"Cancel\" (click)=\"cancelBuild()\">\r\n <mat-icon fontSet=\"material-icons-outlined\">close</mat-icon>\r\n </button>\r\n </div>\r\n }\r\n }\r\n\r\n <!-- Add filter -->\r\n <button\r\n type=\"button\"\r\n class=\"c-add-filter\"\r\n [matMenuTriggerFor]=\"fieldPicker\"\r\n [disabled]=\"_fields.length === 0 || _used.size >= _fields.length\"\r\n [matTooltip]=\"_fields.length === 0 ? 'Ch\u01B0a c\u1EA5u h\u00ECnh fields' : 'Th\u00EAm filter'\">\r\n <mat-icon fontSet=\"material-icons-outlined\">add</mat-icon>\r\n </button>\r\n } @else {\r\n <!-- ===== POPOVER MODE (default) \u2014 compact chips edited via mat-menu ===== -->\r\n @for (filter of _filters; track $index; let i = $index) {\r\n @if (_showOr && i > 0) {\r\n <span class=\"c-or-connector\" aria-hidden=\"true\">OR</span>\r\n }\r\n @let _field = fieldByKey()[$any(filter).field];\r\n @let _active = isFilterActive(filter);\r\n <!-- why: div[role=button] (not <button>) so the operator can render as a nested\r\n <sd-operator> \u2014 matching inline chips. A button cannot legally contain a button. -->\r\n <div\r\n #chipTrigger=\"matMenuTrigger\"\r\n role=\"button\"\r\n tabindex=\"0\"\r\n class=\"c-chip\"\r\n [class.c-chip-active]=\"_active\"\r\n [class.c-chip-inactive]=\"!_active\"\r\n [attr.data-field]=\"$any(filter).field\"\r\n [matTooltip]=\"_field?.label || $any(filter).field\"\r\n [matMenuTriggerFor]=\"chipPopover\"\r\n [matMenuTriggerData]=\"{ index: i }\"\r\n (menuOpened)=\"openChipPopover(i)\"\r\n (menuClosed)=\"commitChipEditOnClose()\"\r\n (keydown.enter)=\"chipTrigger.openMenu()\"\r\n (keydown.space)=\"$event.preventDefault(); chipTrigger.openMenu()\">\r\n <mat-icon class=\"c-chip-icon\" fontSet=\"material-icons-outlined\">\r\n {{ _field ? iconFor(_field) : 'tune' }}\r\n </mat-icon>\r\n <span class=\"c-chip-label\">{{ _field?.label || $any(filter).field }}</span>\r\n\r\n @if (showOperatorOnChip() && _active) {\r\n <sd-operator class=\"c-chip-operator\" [operators]=\"[$any(filter.operator)]\" [model]=\"$any(filter.operator)\" [disabled]=\"true\" />\r\n } @else if (_active) {\r\n <span class=\"c-chip-sep\">:</span>\r\n }\r\n\r\n @if (_active) {\r\n <span class=\"c-chip-value\">{{ chipValueText(filter) }}</span>\r\n }\r\n\r\n <mat-icon class=\"c-chip-chevron\" fontSet=\"material-icons-outlined\">arrow_drop_down</mat-icon>\r\n\r\n @if (_active) {\r\n <span\r\n role=\"button\"\r\n tabindex=\"0\"\r\n class=\"c-chip-remove\"\r\n aria-label=\"Remove filter\"\r\n (click)=\"$event.stopPropagation(); removeFilter(i)\"\r\n (keydown.enter)=\"$event.stopPropagation(); removeFilter(i)\">\r\n <mat-icon fontSet=\"material-icons-outlined\">close</mat-icon>\r\n </span>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Add filter trigger -->\r\n <button\r\n type=\"button\"\r\n class=\"c-add-filter\"\r\n [matMenuTriggerFor]=\"fieldPicker\"\r\n [disabled]=\"_fields.length === 0 || _used.size >= _fields.length\"\r\n [matTooltip]=\"_fields.length === 0 ? 'Ch\u01B0a c\u1EA5u h\u00ECnh fields' : 'Th\u00EAm filter'\">\r\n <mat-icon fontSet=\"material-icons-outlined\">add</mat-icon>\r\n </button>\r\n }\r\n\r\n </div><!-- /.c-query-bar__main -->\r\n\r\n <!-- Field picker \u2014 list available fields, fade ones already in use -->\r\n <mat-menu #fieldPicker=\"matMenu\" xPosition=\"after\" yPosition=\"below\" class=\"c-field-picker-menu\">\r\n @for (field of _fields; track $index) {\r\n @let _isUsed = _used.has($any(field.key));\r\n <button\r\n type=\"button\"\r\n mat-menu-item\r\n [disabled]=\"_isUsed || field.disabled\"\r\n (click)=\"mode() === 'inline' ? beginBuild(field) : addFilter(field)\">\r\n <mat-icon fontSet=\"material-icons-outlined\" class=\"c-field-item-icon\">\r\n {{ iconFor(field) }}\r\n </mat-icon>\r\n <span class=\"c-field-item-label\">{{ field.label }}</span>\r\n <span class=\"c-field-item-kind\">{{ field.kind }}</span>\r\n @if (_isUsed) {\r\n <mat-icon class=\"c-field-item-check\" fontSet=\"material-icons-outlined\">check</mat-icon>\r\n }\r\n </button>\r\n } @empty {\r\n <div class=\"c-field-empty\">Ch\u01B0a c\u00F3 field n\u00E0o \u0111\u01B0\u1EE3c c\u1EA5u h\u00ECnh.</div>\r\n }\r\n </mat-menu>\r\n\r\n <!-- Vertical separator between the wrapping filter area (left) and the pinned toolbar. -->\r\n <span class=\"c-bar-divider\" aria-hidden=\"true\"></span>\r\n\r\n <!-- Action toolbar \u2014 pinned top-right; never drifts with the filter rows. -->\r\n <div class=\"c-query-bar__actions\">\r\n <!-- AND / OR toggle -->\r\n @if (showLogicToggle() && _filters.length >= 2) {\r\n <div class=\"c-logic-toggle\" role=\"group\" aria-label=\"Logic operator\">\r\n <button\r\n type=\"button\"\r\n class=\"c-logic-segment\"\r\n [class.c-logic-active]=\"_logic === 'AND'\"\r\n (click)=\"setLogic('AND')\">\r\n AND\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"c-logic-segment\"\r\n [class.c-logic-active]=\"_logic === 'OR'\"\r\n (click)=\"setLogic('OR')\">\r\n OR\r\n </button>\r\n </div>\r\n }\r\n\r\n <!-- Saved views (placeholder \u2014 wire in next iteration) -->\r\n @if (showSavedViews()) {\r\n <button type=\"button\" class=\"c-saved-views\" disabled matTooltip=\"Saved views\">\r\n <mat-icon fontSet=\"material-icons-outlined\">bookmark_border</mat-icon>\r\n </button>\r\n }\r\n\r\n <!-- Clear all \u2014 compact icon + tooltip -->\r\n @if (showClearAll() && _filters.length > 0) {\r\n <button\r\n type=\"button\"\r\n class=\"c-clear-all\"\r\n [matTooltip]=\"'X\u00F3a t\u1EA5t c\u1EA3 (' + _filters.length + ')'\"\r\n (click)=\"clearAll()\">\r\n <mat-icon fontSet=\"material-icons-outlined\">delete_sweep</mat-icon>\r\n </button>\r\n }\r\n\r\n <!-- Search \u2014 the single deferred trigger for both modes. why: far-right placement\r\n mirrors the conventional \"submit at the end of the toolbar\" reading order. -->\r\n <button\r\n type=\"button\"\r\n class=\"c-search-trigger\"\r\n [disabled]=\"!canSearch()\"\r\n matTooltip=\"T\u00ECm ki\u1EBFm\"\r\n (click)=\"triggerApply()\">\r\n <mat-icon fontSet=\"material-icons-outlined\">search</mat-icon>\r\n </button>\r\n </div>\r\n</div>\r\n", styles: [":host{display:block;font-family:Roboto,system-ui,sans-serif}.c-query-bar{display:flex;align-items:flex-start;flex-wrap:nowrap;gap:8px;background:#fff;border:1px solid #e2e4e7;border-radius:8px;box-shadow:0 1px 2px #0f172a0a}.c-query-bar.c-density-compact{padding:8px}.c-query-bar.c-density-comfortable{padding:10px}.c-query-bar__main{flex:1 1 auto;min-width:0;display:flex;align-items:center;flex-wrap:wrap;gap:6px}.c-bar-divider{flex:0 0 auto;align-self:stretch;width:1px;min-height:28px;background:#e2e4e7}.c-query-bar__actions{flex:0 0 auto;display:flex;align-items:center;gap:6px}.c-search{display:flex;align-items:center;gap:6px;min-width:220px;padding:0 10px;border:1px solid #d6d8db;border-radius:6px;background:#fff}.c-density-compact .c-search{height:28px}.c-density-comfortable .c-search{height:32px}.c-search .c-search-icon{font-size:16px;width:16px;height:16px;color:#7a7a7a}.c-search .c-search-input{flex:1;border:none;outline:none;background:transparent;font-size:13px;color:#212121}.c-search .c-search-input::placeholder{color:#7a7a7a}.c-search .c-search-kbd{font-family:Roboto Mono,monospace;font-size:10px;padding:1px 4px;border:1px solid #e2e4e7;border-radius:3px;color:#7a7a7a}.c-divider{width:1px;height:16px;background:#e2e4e7;margin:0 2px}.c-chip{display:inline-flex;align-items:center;gap:4px;padding:0 4px 0 10px;border-radius:999px;border:1px solid #d6d8db;background:#fff;cursor:pointer;font-family:inherit;font-size:13px;font-weight:500;color:#5f6368;transition:background-color .12s ease,border-color .12s ease,color .12s ease}.c-density-compact .c-chip{height:28px}.c-density-comfortable .c-chip{height:32px}.c-chip:hover{border-color:var(--sd-primary)}.c-chip.c-chip-active{border-color:var(--sd-primary-light);background:var(--sd-primary-light)}.c-chip.c-chip-active .c-chip-value{color:var(--sd-primary)}.c-chip.c-chip-inactive{padding-right:10px}.c-chip .c-chip-icon{font-size:16px;width:16px;height:16px}.c-chip .c-chip-label{font-weight:500;white-space:nowrap}.c-chip .c-chip-operator{display:inline-flex;align-items:center;color:inherit}.c-chip .c-chip-sep{color:#7a7a7a}.c-chip .c-chip-value{font-weight:500;max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.c-chip .c-chip-chevron{font-size:18px;width:18px;height:18px;color:currentColor}.c-chip .c-chip-remove{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:50%;cursor:pointer;color:#7a7a7a}.c-chip .c-chip-remove mat-icon{font-size:14px;width:14px;height:14px}.c-chip .c-chip-remove:hover{background:#0000000f}.c-token{display:inline-flex;align-items:center;gap:4px;padding:0 0 0 12px;border:1px solid var(--sd-primary-light);border-radius:999px;background:var(--sd-primary-light);font-size:13px;color:#5f6368;white-space:nowrap;overflow:hidden}.c-density-compact .c-token{height:28px}.c-density-comfortable .c-token{height:32px}.c-token-icon{font-size:14px;width:14px;height:14px;line-height:14px;color:inherit}.c-token-field{font-weight:500;line-height:1}.c-token-op{display:inline-flex;align-items:center;color:inherit}.c-token-op ::ng-deep .c-op-trigger,.c-chip-operator ::ng-deep .c-op-trigger{padding:0;color:inherit}.c-token-op ::ng-deep .c-op-icon svg,.c-chip-operator ::ng-deep .c-op-icon svg{width:14px;height:14px;display:block}.c-token-value-edit sd-select,.c-token-value-edit sd-date,.c-token-value-edit sd-datetime{display:inline-flex;align-items:center;line-height:1}.c-token ::ng-deep .mat-mdc-form-field{line-height:1}.c-token ::ng-deep .mat-mdc-text-field-wrapper,.c-token ::ng-deep .mat-mdc-form-field-flex{padding:0;min-height:0;background:transparent}.c-token ::ng-deep .mat-mdc-form-field-infix{padding:0;min-height:0;width:auto;border:0;display:inline-flex;align-items:center}.c-token ::ng-deep .mat-mdc-select-value,.c-token ::ng-deep .mat-mdc-input-element,.c-token ::ng-deep input{line-height:1;font-size:13px;padding:0;height:auto;color:inherit}.c-token ::ng-deep .mat-mdc-select-value{display:inline-flex;align-items:center;min-width:0;max-width:200px;overflow:hidden;text-overflow:ellipsis;color:var(--sd-primary);font-weight:500}.c-token ::ng-deep .sd-suffix-icon{display:none}.c-token-sep{color:#7a7a7a}.c-token-dash{color:#7a7a7a;padding:0 4px;line-height:1}.c-token-value{border:none;background:transparent;padding:0;font:inherit;font-weight:500;color:var(--sd-primary);cursor:pointer}.c-token-value:hover{text-decoration:underline}.c-token-value-edit{display:inline-flex;align-items:center;gap:4px;max-width:160px}.c-token-value-edit sd-input,.c-token-value-edit sd-input-number{display:inline-flex;max-width:140px}.c-token-building{border-style:dashed}.c-token-remove{align-self:stretch;display:inline-flex;align-items:center;justify-content:center;border:none;border-left:1px solid rgba(0,0,0,.04);border-radius:0;background:transparent;cursor:pointer;color:inherit}.c-density-compact .c-token-remove{width:26px}.c-density-comfortable .c-token-remove{width:28px}.c-token-remove mat-icon{font-size:14px;width:14px;height:14px}.c-token-remove:hover{background:#0000000d}.c-add-filter{display:inline-flex;align-items:center;justify-content:center;padding:0;border-radius:999px;border:1px dashed #d6d8db;background:transparent;cursor:pointer;color:#5f6368;transition:border-color .12s ease,color .12s ease,background-color .12s ease}.c-density-compact .c-add-filter{width:28px;height:28px}.c-density-comfortable .c-add-filter{width:32px;height:32px}.c-add-filter mat-icon{font-size:18px;width:18px;height:18px}.c-add-filter:hover:not([disabled]){border-color:var(--sd-primary);color:var(--sd-primary)}.c-add-filter[disabled]{opacity:.5;cursor:not-allowed}.c-or-connector{font-family:Roboto Mono,monospace;font-size:10px;font-weight:700;letter-spacing:.6px;color:#7a7a7a;padding:0 2px}.c-logic-toggle{display:inline-flex;align-items:center;padding:2px;background:#f5f6f7;border:1px solid #e2e4e7;border-radius:6px}.c-density-compact .c-logic-toggle{height:28px}.c-density-comfortable .c-logic-toggle{height:32px}.c-logic-toggle .c-logic-segment{height:100%;padding:0 10px;border:none;background:transparent;font-family:Roboto Mono,monospace;font-size:11px;font-weight:600;letter-spacing:.4px;color:#5f6368;cursor:pointer;border-radius:4px}.c-logic-toggle .c-logic-segment.c-logic-active{background:#fff;color:var(--sd-primary);box-shadow:0 1px 2px #00000014}.c-saved-views,.c-clear-all{display:inline-flex;align-items:center;justify-content:center;border:1px solid #d6d8db;border-radius:6px;background:#fff;color:#5f6368;cursor:pointer;padding:0}.c-density-compact .c-saved-views,.c-density-compact .c-clear-all{width:28px;height:28px}.c-density-comfortable .c-saved-views,.c-density-comfortable .c-clear-all{width:32px;height:32px}.c-saved-views mat-icon,.c-clear-all mat-icon{font-size:18px;width:18px;height:18px}.c-saved-views:hover:not([disabled]),.c-clear-all:hover:not([disabled]){border-color:var(--sd-primary);color:var(--sd-primary)}.c-saved-views[disabled],.c-clear-all[disabled]{opacity:.5;cursor:not-allowed}.c-clear-all:hover{border-color:#dc2626;color:#dc2626}.c-search-trigger{display:inline-flex;align-items:center;justify-content:center;border:1px solid #d6d8db;border-radius:6px;background:#fff;color:#5f6368;cursor:pointer;padding:0}.c-density-compact .c-search-trigger{width:28px;height:28px}.c-density-comfortable .c-search-trigger{width:32px;height:32px}.c-search-trigger mat-icon{font-size:18px;width:18px;height:18px}.c-search-trigger:hover:not([disabled]){border-color:var(--sd-primary);color:var(--sd-primary)}.c-search-trigger[disabled]{opacity:.5;cursor:not-allowed}::ng-deep .mat-mdc-menu-panel.c-field-picker-menu{min-width:200px;max-width:260px}::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-menu-item{min-height:36px}::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-menu-item .mat-mdc-menu-item-text{display:flex;align-items:center;flex:1;min-width:0}::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .c-field-item-icon{font-size:16px;width:16px;height:16px;margin-right:8px;color:#5f6368;flex-shrink:0}::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .c-field-item-label{flex:1 1 auto;min-width:0;font-size:13px;color:#212121;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .c-field-item-kind{flex-shrink:0;margin-left:auto;padding-left:12px;font-family:Roboto Mono,monospace;font-size:11px;color:#7a7a7a}::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .c-field-item-check{flex-shrink:0;font-size:14px;width:14px;height:14px;margin-left:6px;color:var(--sd-primary)}::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .c-field-empty{padding:12px 16px;font-size:13px;color:#7a7a7a}::ng-deep .mat-mdc-menu-panel.c-chip-popover{min-width:280px;max-width:320px}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-header{display:flex;align-items:center;gap:8px;padding:10px 12px;border-bottom:1px solid #e2e4e7;font-size:13px;font-weight:500;color:#212121}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-header mat-icon{font-size:16px;width:16px;height:16px;color:var(--sd-primary)}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-header .c-pop-header-operator{margin-left:auto}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-header-field{display:inline-flex;align-items:center;gap:4px;padding:2px 4px 2px 6px;margin-left:-4px;background:transparent;border:1px solid transparent;border-radius:4px;font:inherit;font-weight:500;color:#212121;cursor:pointer;transition:background-color .12s ease,border-color .12s ease}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-header-field mat-icon{font-size:14px!important;width:14px!important;height:14px!important;color:#7a7a7a!important}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-header-field:hover{background:#f5f6f7;border-color:#e2e4e7}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-body{padding:10px 12px;display:flex;flex-direction:column;gap:12px}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-input{width:100%;height:32px;padding:0 10px;border:1px solid #d6d8db;border-radius:6px;background:#fff;font-family:inherit;font-size:13px;color:#212121;outline:none}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-input:focus{border-color:var(--sd-primary)}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-between{display:grid;grid-template-columns:1fr 1fr;gap:8px}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-boolean{display:flex;gap:8px}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-boolean button{flex:1;height:32px;border:1px solid #d6d8db;border-radius:6px;background:#fff;font:inherit;font-size:13px;cursor:pointer}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-boolean button.c-pop-boolean-active{background:var(--sd-primary-light);border-color:var(--sd-primary);color:var(--sd-primary)}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-values{max-height:220px;overflow-y:auto;margin:0;padding:0;list-style:none}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-values li{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:4px;font-size:13px;cursor:pointer;-webkit-user-select:none;user-select:none}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-values li:hover{background:#f5f6f7}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-values li.c-pop-value-selected{background:var(--sd-primary-light);color:var(--sd-primary)}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-values li input[type=checkbox],::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-values li input[type=radio]{accent-color:var(--sd-primary)}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-values-loading,::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-values-empty{padding:10px 8px;font-size:12px;color:#7a7a7a}\n", ".c-query-bar ::ng-deep .mat-mdc-form-field .mdc-notched-outline__leading,.c-query-bar ::ng-deep .mat-mdc-form-field .mdc-notched-outline__notch,.c-query-bar ::ng-deep .mat-mdc-form-field .mdc-notched-outline__trailing{border-color:#d6d8db!important;transition:border-color .12s ease}.c-query-bar ::ng-deep .mat-mdc-form-field .mdc-notched-outline__leading{border-radius:6px 0 0 6px!important}.c-query-bar ::ng-deep .mat-mdc-form-field .mdc-notched-outline__trailing{border-radius:0 6px 6px 0!important}.c-query-bar ::ng-deep .mat-mdc-form-field:hover:not(.mat-focused):not(.mat-form-field-disabled) .mdc-notched-outline__leading,.c-query-bar ::ng-deep .mat-mdc-form-field:hover:not(.mat-focused):not(.mat-form-field-disabled) .mdc-notched-outline__notch,.c-query-bar ::ng-deep .mat-mdc-form-field:hover:not(.mat-focused):not(.mat-form-field-disabled) .mdc-notched-outline__trailing{border-color:#b9bdc3!important}.c-query-bar ::ng-deep .mat-mdc-form-field.mat-focused .mdc-notched-outline__leading,.c-query-bar ::ng-deep .mat-mdc-form-field.mat-focused .mdc-notched-outline__notch,.c-query-bar ::ng-deep .mat-mdc-form-field.mat-focused .mdc-notched-outline__trailing{border-color:var(--sd-primary)!important;border-width:1px!important}.c-query-bar ::ng-deep .mat-mdc-form-field .mat-mdc-select-arrow,.c-query-bar ::ng-deep .mat-mdc-form-field .mat-mdc-form-field-icon-suffix .mat-icon{color:#5f6368}.c-query-bar ::ng-deep .mat-mdc-form-field.mat-focused .mat-mdc-select-arrow{color:var(--sd-primary)}::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field .mdc-notched-outline__leading,::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field .mdc-notched-outline__notch,::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field .mdc-notched-outline__trailing,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field .mdc-notched-outline__leading,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field .mdc-notched-outline__notch,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field .mdc-notched-outline__trailing{border-color:#d6d8db!important;transition:border-color .12s ease}::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field .mdc-notched-outline__leading,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field .mdc-notched-outline__leading{border-radius:6px 0 0 6px!important}::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field .mdc-notched-outline__trailing,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field .mdc-notched-outline__trailing{border-radius:0 6px 6px 0!important}::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field:hover:not(.mat-focused):not(.mat-form-field-disabled) .mdc-notched-outline__leading,::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field:hover:not(.mat-focused):not(.mat-form-field-disabled) .mdc-notched-outline__notch,::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field:hover:not(.mat-focused):not(.mat-form-field-disabled) .mdc-notched-outline__trailing,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field:hover:not(.mat-focused):not(.mat-form-field-disabled) .mdc-notched-outline__leading,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field:hover:not(.mat-focused):not(.mat-form-field-disabled) .mdc-notched-outline__notch,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field:hover:not(.mat-focused):not(.mat-form-field-disabled) .mdc-notched-outline__trailing{border-color:#b9bdc3!important}::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field.mat-focused .mdc-notched-outline__leading,::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field.mat-focused .mdc-notched-outline__notch,::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field.mat-focused .mdc-notched-outline__trailing,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field.mat-focused .mdc-notched-outline__leading,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field.mat-focused .mdc-notched-outline__notch,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field.mat-focused .mdc-notched-outline__trailing{border-color:var(--sd-primary)!important;border-width:1px!important}::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field .mat-mdc-select-arrow,::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field .mat-mdc-form-field-icon-suffix .mat-icon,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field .mat-mdc-select-arrow,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field .mat-mdc-form-field-icon-suffix .mat-icon{color:#5f6368}::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field.mat-focused .mat-mdc-select-arrow,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field.mat-focused .mat-mdc-select-arrow{color:var(--sd-primary)}\n"] }]
1428
+ SdQueryPopoverChip,
1429
+ ], template: "<!--\r\n sd-query-bar \u2014 unified filter chip row (Jira/Linear/Notion style).\r\n Visual spec: refs/design_handoff_sd_query_bar/README.md\r\n TODO(next-iter): replace inline placeholders for field-picker + chip-popover\r\n with extracted sub-components driven by CDK overlay.\r\n-->\r\n@let _filters = filters();\r\n@let _fields = fields();\r\n@let _density = density();\r\n@let _showOr = showOrConnector();\r\n@let _used = usedFieldKeys();\r\n\r\n<div\r\n class=\"c-query-bar\"\r\n [class.c-density-compact]=\"_density === 'compact'\"\r\n [class.c-density-comfortable]=\"_density === 'comfortable'\">\r\n\r\n <!-- Main region \u2014 search + filters + add-filter. Wraps internally; the right\r\n action toolbar stays anchored and does NOT flow with this. -->\r\n <div class=\"c-query-bar__main\">\r\n\r\n <!-- Free-text search (left, optional) -->\r\n @if (showSearch()) {\r\n <div class=\"c-search\">\r\n <mat-icon class=\"c-search-icon\" fontSet=\"material-icons-outlined\">search</mat-icon>\r\n <input\r\n class=\"c-search-input\"\r\n type=\"text\"\r\n [ngModel]=\"search()\"\r\n (ngModelChange)=\"setSearch($event)\"\r\n (keydown.enter)=\"triggerApply()\"\r\n placeholder=\"T\u00ECm ki\u1EBFm...\" />\r\n </div>\r\n <span class=\"c-divider\" aria-hidden=\"true\"></span>\r\n }\r\n\r\n <!-- Field switcher (used by chipPopover header) \u2014 declared first so chipPopover's\r\n nested ng-template can resolve the template ref. -->\r\n <!-- Field switcher used by chipPopover header \u2014 current field key comes from editingIndex. -->\r\n <sd-query-field-picker\r\n #switchPicker\r\n [fields]=\"_fields\"\r\n [currentKey]=\"editingIndex() !== null ? $any(filters()[editingIndex()!])?.field : undefined\"\r\n (pick)=\"editingIndex() !== null && changeFilterField(editingIndex()!, $event)\" />\r\n\r\n <!-- Chip popover \u2014 extracted into <sd-query-chip-popover>. Declared BEFORE chips\r\n so the popover-mode @for chips can bind their [menu] to `chipPopover.menu()`. -->\r\n <sd-query-chip-popover\r\n #chipPopover\r\n [field]=\"editingField() ?? undefined\"\r\n [filter]=\"editingIndex() !== null ? $any(filters()[editingIndex()!]) : undefined\"\r\n [chipIndex]=\"editingIndex()\"\r\n [autoIdBase]=\"autoIdInput() ?? 'qb'\"\r\n [switchPickerMenu]=\"switchPicker.menu()!\"\r\n (commit)=\"onChipPopoverCommit($event)\"\r\n (swapField)=\"editingIndex() !== null && changeFilterField(editingIndex()!, $event)\" />\r\n\r\n @if (mode() === 'inline') {\r\n <!-- ===== INLINE MODE (GitLab-style token builder) ===== -->\r\n\r\n <!-- Completed chips -->\r\n @for (filter of _filters; track $index; let i = $index) {\r\n @if (_showOr && i > 0) {\r\n <span class=\"c-or-connector\" aria-hidden=\"true\">OR</span>\r\n }\r\n @let _field = fieldByKey()[$any(filter).field];\r\n @if (_field) {\r\n @let _op = $any(filter.operator);\r\n @let _data = $any(filter).data;\r\n @if (_field.type === 'string' || _field.type === 'number') {\r\n <!-- Seamless chip \u2014 the pill is the input (string / number). -->\r\n <sd-query-inline-value-chip\r\n [field]=\"_field\"\r\n [operator]=\"_op\"\r\n [value]=\"_data\"\r\n [density]=\"_density\"\r\n [showOperator]=\"showOperatorOnChip()\"\r\n [autoId]=\"inlineAutoId(i, 'value')\"\r\n (valueChange)=\"updateFilter(i, { data: $event })\"\r\n (remove)=\"removeFilter(i)\" />\r\n } @else {\r\n <sd-query-inline-chip\r\n [field]=\"_field\"\r\n [filter]=\"filter\"\r\n [density]=\"_density\"\r\n [multiple]=\"multiple(_op)\"\r\n [showOperator]=\"showOperatorOnChip()\"\r\n [autoId]=\"inlineAutoId(i, 'value')\"\r\n [valueText]=\"chipValueText(filter)\"\r\n [isNoData]=\"isNoDataOperator(_op)\"\r\n (commit)=\"updateFilter(i, { data: $event })\"\r\n (commitRange)=\"setFilterRange(i, $event)\"\r\n (liveChange)=\"updateFilter(i, { data: $event })\"\r\n (remove)=\"removeFilter(i)\" />\r\n }\r\n }\r\n }\r\n\r\n <!-- Building chip (in-progress) \u2014 extracted into <sd-query-build-chip>. -->\r\n @if (building(); as _b) {\r\n <sd-query-build-chip\r\n [building]=\"_b\"\r\n [density]=\"_density\"\r\n [allowedOperators]=\"allowedOperatorsFor(_b.field)\"\r\n [multiple]=\"multiple($any(_b.operator))\"\r\n [showOperator]=\"showOperatorOnChip()\"\r\n (pickOperator)=\"pickBuildOperator($any($event))\"\r\n (commitValue)=\"commitBuildValue($event)\"\r\n (cancel)=\"cancelBuild()\"\r\n (seamlessCommit)=\"onBuildSeamlessCommit($event)\" />\r\n }\r\n\r\n <!-- Add filter -->\r\n <button\r\n type=\"button\"\r\n class=\"c-add-filter\"\r\n [matMenuTriggerFor]=\"addPicker.menu()\"\r\n [disabled]=\"_fields.length === 0 || _used.size >= _fields.length\"\r\n [matTooltip]=\"_fields.length === 0 ? 'Ch\u01B0a c\u1EA5u h\u00ECnh fields' : 'Th\u00EAm filter'\">\r\n <mat-icon fontSet=\"material-icons-outlined\">add</mat-icon>\r\n </button>\r\n } @else {\r\n <!-- ===== POPOVER MODE (default) \u2014 compact chips edited via mat-menu ===== -->\r\n @for (filter of _filters; track $index; let i = $index) {\r\n @if (_showOr && i > 0) {\r\n <span class=\"c-or-connector\" aria-hidden=\"true\">OR</span>\r\n }\r\n @let _field = fieldByKey()[$any(filter).field];\r\n @let _active = isFilterActive(filter);\r\n <!-- why: menu close commit is handled by chipPopover's (commit) \u2192\r\n onChipPopoverCommit; popover-chip only signals open + remove here. -->\r\n <sd-query-popover-chip\r\n [field]=\"_field\"\r\n [filter]=\"filter\"\r\n [active]=\"_active\"\r\n [showOperator]=\"showOperatorOnChip()\"\r\n [valueText]=\"chipValueText(filter)\"\r\n [menu]=\"chipPopover.menu()!\"\r\n (open)=\"openChipPopover(i)\"\r\n (remove)=\"removeFilter(i)\" />\r\n }\r\n\r\n <!-- Add filter trigger -->\r\n <button\r\n type=\"button\"\r\n class=\"c-add-filter\"\r\n [matMenuTriggerFor]=\"addPicker.menu()\"\r\n [disabled]=\"_fields.length === 0 || _used.size >= _fields.length\"\r\n [matTooltip]=\"_fields.length === 0 ? 'Ch\u01B0a c\u1EA5u h\u00ECnh fields' : 'Th\u00EAm filter'\">\r\n <mat-icon fontSet=\"material-icons-outlined\">add</mat-icon>\r\n </button>\r\n }\r\n\r\n </div><!-- /.c-query-bar__main -->\r\n\r\n <!-- Field picker \u2014 list available fields, fade ones already in use. -->\r\n <sd-query-field-picker\r\n #addPicker\r\n [fields]=\"_fields\"\r\n [usedKeys]=\"_used\"\r\n (pick)=\"mode() === 'inline' ? beginBuild($event) : addFilter($event)\" />\r\n\r\n <sd-query-actions-bar\r\n [showLogicToggle]=\"showLogicToggle()\"\r\n [(logic)]=\"logic\"\r\n [filtersCount]=\"_filters.length\"\r\n [showSavedFilters]=\"showSavedFilters()\"\r\n [savedFiltersKey]=\"savedFiltersKey()\"\r\n [query]=\"currentQuery()\"\r\n (applyFilter)=\"onApplyFilter($event)\"\r\n [showClearAll]=\"showClearAll()\"\r\n [canSearch]=\"canSearch()\"\r\n (clear)=\"clearAll()\"\r\n (search)=\"triggerApply()\" />\r\n</div>\r\n", styles: [":host{display:block;font-family:Roboto,system-ui,sans-serif}.c-query-bar{display:flex;align-items:flex-start;flex-wrap:nowrap;gap:8px;background:#fff;border:1px solid #e2e4e7;border-radius:8px;box-shadow:0 1px 2px #0f172a0a}.c-query-bar.c-density-compact{padding:8px}.c-query-bar.c-density-comfortable{padding:10px}.c-query-bar__main{flex:1 1 auto;min-width:0;display:flex;align-items:center;flex-wrap:wrap;gap:6px}.c-search{display:flex;align-items:center;gap:6px;min-width:220px;padding:0 10px;border:1px solid #d6d8db;border-radius:6px;background:#fff}.c-density-compact .c-search{height:28px}.c-density-comfortable .c-search{height:32px}.c-search .c-search-icon{font-size:16px;width:16px;height:16px;color:#7a7a7a}.c-search .c-search-input{flex:1;border:none;outline:none;background:transparent;font-size:13px;color:#212121}.c-search .c-search-input::placeholder{color:#7a7a7a}.c-search .c-search-kbd{font-family:Roboto Mono,monospace;font-size:10px;padding:1px 4px;border:1px solid #e2e4e7;border-radius:3px;color:#7a7a7a}.c-divider{width:1px;height:16px;background:#e2e4e7;margin:0 2px}.c-token{display:inline-flex;align-items:center;gap:4px;padding:0 0 0 12px;border:1px solid var(--sd-primary-light);border-radius:999px;background:var(--sd-primary-light);font-size:13px;color:#5f6368;white-space:nowrap;overflow:hidden}.c-density-compact .c-token{height:28px}.c-density-comfortable .c-token{height:32px}.c-token-icon{font-size:14px;width:14px;height:14px;line-height:14px;color:inherit}.c-token-field{font-weight:500;line-height:1}.c-token-op{display:inline-flex;align-items:center;color:inherit}.c-token-op ::ng-deep .c-op-trigger{padding:0;color:inherit}.c-token-op ::ng-deep .c-op-icon svg{width:14px;height:14px;display:block}.c-token-value-edit sd-select,.c-token-value-edit sd-date,.c-token-value-edit sd-datetime{display:inline-flex;align-items:center;line-height:1}.c-token ::ng-deep .mat-mdc-form-field{line-height:1}.c-token ::ng-deep .mat-mdc-text-field-wrapper,.c-token ::ng-deep .mat-mdc-form-field-flex{padding:0;min-height:0;background:transparent}.c-token ::ng-deep .mat-mdc-form-field-infix{padding:0;min-height:0;width:auto;border:0;display:inline-flex;align-items:center}.c-token ::ng-deep .mat-mdc-select-value,.c-token ::ng-deep .mat-mdc-input-element,.c-token ::ng-deep input{line-height:1;font-size:13px;padding:0;height:auto;color:inherit}.c-token ::ng-deep .mat-mdc-select-value{display:inline-flex;align-items:center;min-width:0;max-width:200px;overflow:hidden;text-overflow:ellipsis;color:var(--sd-primary);font-weight:500}.c-token ::ng-deep .sd-suffix-icon{display:none}.c-token-sep{color:#7a7a7a}.c-token-dash{color:#7a7a7a;padding:0 4px;line-height:1}.c-token-value{border:none;background:transparent;padding:0;font:inherit;font-weight:500;color:var(--sd-primary);cursor:pointer}.c-token-value:hover{text-decoration:underline}.c-token-value-edit{display:inline-flex;align-items:center;gap:4px;max-width:240px}.c-token-value-edit sd-input,.c-token-value-edit sd-input-number{display:inline-flex;max-width:140px}.c-token-value-edit sd-date-range{display:inline-flex;max-width:220px;min-width:0;flex:0 1 auto}.c-token-multi-head{display:inline-block;max-width:140px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom}.c-token-remove{align-self:stretch;display:inline-flex;align-items:center;justify-content:center;border:none;border-left:1px solid rgba(0,0,0,.04);border-radius:0;background:transparent;cursor:pointer;color:inherit}.c-density-compact .c-token-remove{width:26px}.c-density-comfortable .c-token-remove{width:28px}.c-token-remove mat-icon{font-size:14px;width:14px;height:14px}.c-token-remove:hover{background:#0000000d}.c-add-filter{display:inline-flex;align-items:center;justify-content:center;padding:0;border-radius:999px;border:1px dashed #d6d8db;background:transparent;cursor:pointer;color:#5f6368;transition:border-color .12s ease,color .12s ease,background-color .12s ease}.c-density-compact .c-add-filter{width:28px;height:28px}.c-density-comfortable .c-add-filter{width:32px;height:32px}.c-add-filter mat-icon{font-size:18px;width:18px;height:18px}.c-add-filter:hover:not([disabled]){border-color:var(--sd-primary);color:var(--sd-primary)}.c-add-filter[disabled]{opacity:.5;cursor:not-allowed}.c-or-connector{font-family:Roboto Mono,monospace;font-size:10px;font-weight:700;letter-spacing:.6px;color:#7a7a7a;padding:0 2px}::ng-deep .mat-mdc-menu-panel.c-chip-popover{min-width:280px;max-width:320px}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-header{display:flex;align-items:center;gap:8px;padding:10px 12px;border-bottom:1px solid #e2e4e7;font-size:13px;font-weight:500;color:#212121}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-header mat-icon{font-size:16px;width:16px;height:16px;color:var(--sd-primary)}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-header .c-pop-header-operator{margin-left:auto}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-header-field{display:inline-flex;align-items:center;gap:4px;padding:2px 4px 2px 6px;margin-left:-4px;background:transparent;border:1px solid transparent;border-radius:4px;font:inherit;font-weight:500;color:#212121;cursor:pointer;transition:background-color .12s ease,border-color .12s ease}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-header-field mat-icon{font-size:14px!important;width:14px!important;height:14px!important;color:#7a7a7a!important}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-header-field:hover{background:#f5f6f7;border-color:#e2e4e7}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-body{padding:10px 12px;display:flex;flex-direction:column;gap:12px}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-input{width:100%;height:32px;padding:0 10px;border:1px solid #d6d8db;border-radius:6px;background:#fff;font-family:inherit;font-size:13px;color:#212121;outline:none}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-input:focus{border-color:var(--sd-primary)}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-between{display:grid;grid-template-columns:1fr 1fr;gap:8px}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-boolean{display:flex;gap:8px}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-boolean button{flex:1;height:32px;border:1px solid #d6d8db;border-radius:6px;background:#fff;font:inherit;font-size:13px;cursor:pointer}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-boolean button.c-pop-boolean-active{background:var(--sd-primary-light);border-color:var(--sd-primary);color:var(--sd-primary)}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-values{max-height:220px;overflow-y:auto;margin:0;padding:0;list-style:none}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-values li{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:4px;font-size:13px;cursor:pointer;-webkit-user-select:none;user-select:none}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-values li:hover{background:#f5f6f7}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-values li.c-pop-value-selected{background:var(--sd-primary-light);color:var(--sd-primary)}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-values li input[type=checkbox],::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-values li input[type=radio]{accent-color:var(--sd-primary)}::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-values-loading,::ng-deep .mat-mdc-menu-panel.c-chip-popover .c-pop-values-empty{padding:10px 8px;font-size:12px;color:#7a7a7a}.c-bool-toggle{display:inline-flex;align-items:center;gap:4px}.c-bool-toggle ::ng-deep .mdc-button,.c-bool-toggle ::ng-deep button[mat-button],.c-bool-toggle ::ng-deep button[mat-stroked-button],.c-bool-toggle ::ng-deep button[mat-flat-button]{border-radius:999px;min-width:0;padding:0 12px}\n", ".c-query-bar ::ng-deep .mat-mdc-form-field .mdc-notched-outline__leading,.c-query-bar ::ng-deep .mat-mdc-form-field .mdc-notched-outline__notch,.c-query-bar ::ng-deep .mat-mdc-form-field .mdc-notched-outline__trailing{border-color:#d6d8db!important;transition:border-color .12s ease}.c-query-bar ::ng-deep .mat-mdc-form-field .mdc-notched-outline__leading{border-radius:6px 0 0 6px!important}.c-query-bar ::ng-deep .mat-mdc-form-field .mdc-notched-outline__trailing{border-radius:0 6px 6px 0!important}.c-query-bar ::ng-deep .mat-mdc-form-field:hover:not(.mat-focused):not(.mat-form-field-disabled) .mdc-notched-outline__leading,.c-query-bar ::ng-deep .mat-mdc-form-field:hover:not(.mat-focused):not(.mat-form-field-disabled) .mdc-notched-outline__notch,.c-query-bar ::ng-deep .mat-mdc-form-field:hover:not(.mat-focused):not(.mat-form-field-disabled) .mdc-notched-outline__trailing{border-color:#b9bdc3!important}.c-query-bar ::ng-deep .mat-mdc-form-field.mat-focused .mdc-notched-outline__leading,.c-query-bar ::ng-deep .mat-mdc-form-field.mat-focused .mdc-notched-outline__notch,.c-query-bar ::ng-deep .mat-mdc-form-field.mat-focused .mdc-notched-outline__trailing{border-color:var(--sd-primary)!important;border-width:1px!important}.c-query-bar ::ng-deep .mat-mdc-form-field .mat-mdc-select-arrow,.c-query-bar ::ng-deep .mat-mdc-form-field .mat-mdc-form-field-icon-suffix .mat-icon{color:#5f6368}.c-query-bar ::ng-deep .mat-mdc-form-field.mat-focused .mat-mdc-select-arrow{color:var(--sd-primary)}::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field .mdc-notched-outline__leading,::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field .mdc-notched-outline__notch,::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field .mdc-notched-outline__trailing,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field .mdc-notched-outline__leading,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field .mdc-notched-outline__notch,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field .mdc-notched-outline__trailing{border-color:#d6d8db!important;transition:border-color .12s ease}::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field .mdc-notched-outline__leading,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field .mdc-notched-outline__leading{border-radius:6px 0 0 6px!important}::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field .mdc-notched-outline__trailing,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field .mdc-notched-outline__trailing{border-radius:0 6px 6px 0!important}::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field:hover:not(.mat-focused):not(.mat-form-field-disabled) .mdc-notched-outline__leading,::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field:hover:not(.mat-focused):not(.mat-form-field-disabled) .mdc-notched-outline__notch,::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field:hover:not(.mat-focused):not(.mat-form-field-disabled) .mdc-notched-outline__trailing,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field:hover:not(.mat-focused):not(.mat-form-field-disabled) .mdc-notched-outline__leading,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field:hover:not(.mat-focused):not(.mat-form-field-disabled) .mdc-notched-outline__notch,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field:hover:not(.mat-focused):not(.mat-form-field-disabled) .mdc-notched-outline__trailing{border-color:#b9bdc3!important}::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field.mat-focused .mdc-notched-outline__leading,::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field.mat-focused .mdc-notched-outline__notch,::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field.mat-focused .mdc-notched-outline__trailing,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field.mat-focused .mdc-notched-outline__leading,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field.mat-focused .mdc-notched-outline__notch,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field.mat-focused .mdc-notched-outline__trailing{border-color:var(--sd-primary)!important;border-width:1px!important}::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field .mat-mdc-select-arrow,::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field .mat-mdc-form-field-icon-suffix .mat-icon,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field .mat-mdc-select-arrow,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field .mat-mdc-form-field-icon-suffix .mat-icon{color:#5f6368}::ng-deep .mat-mdc-menu-panel.c-chip-popover .mat-mdc-form-field.mat-focused .mat-mdc-select-arrow,::ng-deep .mat-mdc-menu-panel.c-field-picker-menu .mat-mdc-form-field.mat-focused .mat-mdc-select-arrow{color:var(--sd-primary)}\n"] }]
991
1430
  }] });
992
1431
 
993
1432
  /**
994
1433
  * Generated bundle index. Do not edit.
995
1434
  */
996
1435
 
997
- export { SD_QUERY_DEFAULT_OPERATOR_BY_KIND, SD_QUERY_KIND_ICON, SD_QUERY_MULTI_OPERATORS, SD_QUERY_NO_DATA_OPERATORS, SD_QUERY_OPERATORS_BY_KIND, SD_QUERY_OPERATOR_LABEL, SdQueryBar, sdQueryAllowedOperators, sdQueryDefaultOperator, sdQueryFieldIcon, sdQueryShowOperatorSelector };
1436
+ export { SD_QUERY_DEFAULT_OPERATOR_BY_TYPE, SD_QUERY_MULTI_OPERATORS, SD_QUERY_NO_DATA_OPERATORS, SD_QUERY_OPERATORS_BY_TYPE, SD_QUERY_TYPE_ICON, SdQueryBar, sdQueryAllowedOperators, sdQueryDefaultOperator, sdQueryFieldIcon, sdQueryShowOperatorSelector };
998
1437
  //# sourceMappingURL=sd-angular-core-components-query-bar.mjs.map