@memberjunction/ng-filter-builder 0.0.1 → 2.123.1

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 (30) hide show
  1. package/dist/lib/filter-builder/filter-builder.component.d.ts +155 -0
  2. package/dist/lib/filter-builder/filter-builder.component.d.ts.map +1 -0
  3. package/dist/lib/filter-builder/filter-builder.component.js +482 -0
  4. package/dist/lib/filter-builder/filter-builder.component.js.map +1 -0
  5. package/dist/lib/filter-builder.module.d.ts +39 -0
  6. package/dist/lib/filter-builder.module.d.ts.map +1 -0
  7. package/dist/lib/filter-builder.module.js +66 -0
  8. package/dist/lib/filter-builder.module.js.map +1 -0
  9. package/dist/lib/filter-group/filter-group.component.d.ts +102 -0
  10. package/dist/lib/filter-group/filter-group.component.d.ts.map +1 -0
  11. package/dist/lib/filter-group/filter-group.component.js +328 -0
  12. package/dist/lib/filter-group/filter-group.component.js.map +1 -0
  13. package/dist/lib/filter-rule/filter-rule.component.d.ts +165 -0
  14. package/dist/lib/filter-rule/filter-rule.component.d.ts.map +1 -0
  15. package/dist/lib/filter-rule/filter-rule.component.js +682 -0
  16. package/dist/lib/filter-rule/filter-rule.component.js.map +1 -0
  17. package/dist/lib/types/filter.types.d.ts +142 -0
  18. package/dist/lib/types/filter.types.d.ts.map +1 -0
  19. package/dist/lib/types/filter.types.js +82 -0
  20. package/dist/lib/types/filter.types.js.map +1 -0
  21. package/dist/lib/types/operators.d.ts +49 -0
  22. package/dist/lib/types/operators.d.ts.map +1 -0
  23. package/dist/lib/types/operators.js +99 -0
  24. package/dist/lib/types/operators.js.map +1 -0
  25. package/dist/public-api.d.ts +15 -0
  26. package/dist/public-api.d.ts.map +1 -0
  27. package/dist/public-api.js +19 -0
  28. package/dist/public-api.js.map +1 -0
  29. package/package.json +38 -6
  30. package/README.md +0 -45
@@ -0,0 +1,682 @@
1
+ import { Component, Input, Output, EventEmitter, ViewEncapsulation, HostListener } from '@angular/core';
2
+ import { getOperatorsForType, operatorRequiresValue } from '../types/operators';
3
+ import * as i0 from "@angular/core";
4
+ const _forTrack0 = ($index, $item) => $item.name;
5
+ const _forTrack1 = ($index, $item) => $item.value;
6
+ function FilterRuleComponent_Conditional_6_For_2_Template(rf, ctx) { if (rf & 1) {
7
+ const _r1 = i0.ɵɵgetCurrentView();
8
+ i0.ɵɵelementStart(0, "div", 11);
9
+ i0.ɵɵlistener("click", function FilterRuleComponent_Conditional_6_For_2_Template_div_click_0_listener() { const field_r2 = i0.ɵɵrestoreView(_r1).$implicit; const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.selectField(field_r2.name)); });
10
+ i0.ɵɵtext(1);
11
+ i0.ɵɵelementEnd();
12
+ } if (rf & 2) {
13
+ const field_r2 = ctx.$implicit;
14
+ const ɵ$index_15_r4 = ctx.$index;
15
+ const ctx_r2 = i0.ɵɵnextContext(2);
16
+ i0.ɵɵclassProp("selected", field_r2.name === ctx_r2.filter.field)("highlighted", ɵ$index_15_r4 === ctx_r2.fieldHighlightIndex);
17
+ i0.ɵɵadvance();
18
+ i0.ɵɵtextInterpolate1(" ", field_r2.displayName, " ");
19
+ } }
20
+ function FilterRuleComponent_Conditional_6_Conditional_3_Template(rf, ctx) { if (rf & 1) {
21
+ i0.ɵɵelementStart(0, "div", 10);
22
+ i0.ɵɵtext(1, "No fields available");
23
+ i0.ɵɵelementEnd();
24
+ } }
25
+ function FilterRuleComponent_Conditional_6_Template(rf, ctx) { if (rf & 1) {
26
+ i0.ɵɵelementStart(0, "div", 5);
27
+ i0.ɵɵrepeaterCreate(1, FilterRuleComponent_Conditional_6_For_2_Template, 2, 5, "div", 9, _forTrack0);
28
+ i0.ɵɵtemplate(3, FilterRuleComponent_Conditional_6_Conditional_3_Template, 2, 0, "div", 10);
29
+ i0.ɵɵelementEnd();
30
+ } if (rf & 2) {
31
+ const ctx_r2 = i0.ɵɵnextContext();
32
+ i0.ɵɵadvance();
33
+ i0.ɵɵrepeater(ctx_r2.fields);
34
+ i0.ɵɵadvance(2);
35
+ i0.ɵɵconditional(ctx_r2.fields.length === 0 ? 3 : -1);
36
+ } }
37
+ function FilterRuleComponent_Conditional_12_For_2_Template(rf, ctx) { if (rf & 1) {
38
+ const _r5 = i0.ɵɵgetCurrentView();
39
+ i0.ɵɵelementStart(0, "div", 11);
40
+ i0.ɵɵlistener("click", function FilterRuleComponent_Conditional_12_For_2_Template_div_click_0_listener() { const op_r6 = i0.ɵɵrestoreView(_r5).$implicit; const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.selectOperator(op_r6.value)); });
41
+ i0.ɵɵtext(1);
42
+ i0.ɵɵelementEnd();
43
+ } if (rf & 2) {
44
+ const op_r6 = ctx.$implicit;
45
+ const ɵ$index_35_r7 = ctx.$index;
46
+ const ctx_r2 = i0.ɵɵnextContext(2);
47
+ i0.ɵɵclassProp("selected", op_r6.value === ctx_r2.filter.operator)("highlighted", ɵ$index_35_r7 === ctx_r2.operatorHighlightIndex);
48
+ i0.ɵɵadvance();
49
+ i0.ɵɵtextInterpolate1(" ", op_r6.label, " ");
50
+ } }
51
+ function FilterRuleComponent_Conditional_12_Template(rf, ctx) { if (rf & 1) {
52
+ i0.ɵɵelementStart(0, "div", 5);
53
+ i0.ɵɵrepeaterCreate(1, FilterRuleComponent_Conditional_12_For_2_Template, 2, 5, "div", 9, _forTrack1);
54
+ i0.ɵɵelementEnd();
55
+ } if (rf & 2) {
56
+ const ctx_r2 = i0.ɵɵnextContext();
57
+ i0.ɵɵadvance();
58
+ i0.ɵɵrepeater(ctx_r2.availableOperators);
59
+ } }
60
+ function FilterRuleComponent_Conditional_13_Conditional_0_Conditional_5_For_4_Template(rf, ctx) { if (rf & 1) {
61
+ const _r10 = i0.ɵɵgetCurrentView();
62
+ i0.ɵɵelementStart(0, "div", 11);
63
+ i0.ɵɵlistener("click", function FilterRuleComponent_Conditional_13_Conditional_0_Conditional_5_For_4_Template_div_click_0_listener() { const option_r11 = i0.ɵɵrestoreView(_r10).$implicit; const ctx_r2 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r2.selectValue(option_r11.value)); });
64
+ i0.ɵɵtext(1);
65
+ i0.ɵɵelementEnd();
66
+ } if (rf & 2) {
67
+ const option_r11 = ctx.$implicit;
68
+ const ɵ$index_56_r12 = ctx.$index;
69
+ const ctx_r2 = i0.ɵɵnextContext(4);
70
+ i0.ɵɵclassProp("selected", option_r11.value === ctx_r2.filter.value)("highlighted", ɵ$index_56_r12 + 1 === ctx_r2.valueHighlightIndex);
71
+ i0.ɵɵadvance();
72
+ i0.ɵɵtextInterpolate1(" ", option_r11.label, " ");
73
+ } }
74
+ function FilterRuleComponent_Conditional_13_Conditional_0_Conditional_5_Template(rf, ctx) { if (rf & 1) {
75
+ const _r9 = i0.ɵɵgetCurrentView();
76
+ i0.ɵɵelementStart(0, "div", 5)(1, "div", 11);
77
+ i0.ɵɵlistener("click", function FilterRuleComponent_Conditional_13_Conditional_0_Conditional_5_Template_div_click_1_listener() { i0.ɵɵrestoreView(_r9); const ctx_r2 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r2.selectValue("")); });
78
+ i0.ɵɵtext(2, " Select... ");
79
+ i0.ɵɵelementEnd();
80
+ i0.ɵɵrepeaterCreate(3, FilterRuleComponent_Conditional_13_Conditional_0_Conditional_5_For_4_Template, 2, 5, "div", 9, _forTrack1);
81
+ i0.ɵɵelementEnd();
82
+ } if (rf & 2) {
83
+ const ctx_r2 = i0.ɵɵnextContext(3);
84
+ i0.ɵɵadvance();
85
+ i0.ɵɵclassProp("selected", !ctx_r2.filter.value)("highlighted", 0 === ctx_r2.valueHighlightIndex);
86
+ i0.ɵɵadvance(2);
87
+ i0.ɵɵrepeater(ctx_r2.selectedField.valueList);
88
+ } }
89
+ function FilterRuleComponent_Conditional_13_Conditional_0_Template(rf, ctx) { if (rf & 1) {
90
+ const _r8 = i0.ɵɵgetCurrentView();
91
+ i0.ɵɵelementStart(0, "div", 18)(1, "button", 2);
92
+ i0.ɵɵlistener("click", function FilterRuleComponent_Conditional_13_Conditional_0_Template_button_click_1_listener() { i0.ɵɵrestoreView(_r8); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.toggleValueDropdown()); })("keydown", function FilterRuleComponent_Conditional_13_Conditional_0_Template_button_keydown_1_listener($event) { i0.ɵɵrestoreView(_r8); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onValueKeydown($event)); });
93
+ i0.ɵɵelementStart(2, "span", 3);
94
+ i0.ɵɵtext(3);
95
+ i0.ɵɵelementEnd();
96
+ i0.ɵɵelement(4, "i", 4);
97
+ i0.ɵɵelementEnd();
98
+ i0.ɵɵtemplate(5, FilterRuleComponent_Conditional_13_Conditional_0_Conditional_5_Template, 5, 4, "div", 5);
99
+ i0.ɵɵelementEnd();
100
+ } if (rf & 2) {
101
+ const ctx_r2 = i0.ɵɵnextContext(2);
102
+ i0.ɵɵclassProp("open", ctx_r2.valueDropdownOpen)("disabled", ctx_r2.disabled);
103
+ i0.ɵɵadvance();
104
+ i0.ɵɵproperty("disabled", ctx_r2.disabled);
105
+ i0.ɵɵadvance(2);
106
+ i0.ɵɵtextInterpolate(ctx_r2.getSelectedValueLabel());
107
+ i0.ɵɵadvance(2);
108
+ i0.ɵɵconditional(ctx_r2.valueDropdownOpen ? 5 : -1);
109
+ } }
110
+ function FilterRuleComponent_Conditional_13_Conditional_1_Template(rf, ctx) { if (rf & 1) {
111
+ const _r13 = i0.ɵɵgetCurrentView();
112
+ i0.ɵɵelementStart(0, "input", 19);
113
+ i0.ɵɵlistener("input", function FilterRuleComponent_Conditional_13_Conditional_1_Template_input_input_0_listener($event) { i0.ɵɵrestoreView(_r13); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onValueChange($event.target.value)); });
114
+ i0.ɵɵelementEnd();
115
+ } if (rf & 2) {
116
+ const ctx_r2 = i0.ɵɵnextContext(2);
117
+ i0.ɵɵproperty("value", ctx_r2.filter.value || "")("disabled", ctx_r2.disabled);
118
+ } }
119
+ function FilterRuleComponent_Conditional_13_Conditional_2_Template(rf, ctx) { if (rf & 1) {
120
+ const _r14 = i0.ɵɵgetCurrentView();
121
+ i0.ɵɵelementStart(0, "input", 20);
122
+ i0.ɵɵlistener("input", function FilterRuleComponent_Conditional_13_Conditional_2_Template_input_input_0_listener($event) { i0.ɵɵrestoreView(_r14); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onValueChange($event.target.valueAsNumber)); });
123
+ i0.ɵɵelementEnd();
124
+ } if (rf & 2) {
125
+ const ctx_r2 = i0.ɵɵnextContext(2);
126
+ i0.ɵɵproperty("value", ctx_r2.filter.value)("disabled", ctx_r2.disabled);
127
+ } }
128
+ function FilterRuleComponent_Conditional_13_Conditional_3_Template(rf, ctx) { if (rf & 1) {
129
+ const _r15 = i0.ɵɵgetCurrentView();
130
+ i0.ɵɵelementStart(0, "div", 15)(1, "button", 21);
131
+ i0.ɵɵlistener("click", function FilterRuleComponent_Conditional_13_Conditional_3_Template_button_click_1_listener() { i0.ɵɵrestoreView(_r15); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onBooleanChange(true)); });
132
+ i0.ɵɵtext(2, " True ");
133
+ i0.ɵɵelementEnd();
134
+ i0.ɵɵelementStart(3, "button", 22);
135
+ i0.ɵɵlistener("click", function FilterRuleComponent_Conditional_13_Conditional_3_Template_button_click_3_listener() { i0.ɵɵrestoreView(_r15); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onBooleanChange(false)); });
136
+ i0.ɵɵtext(4, " False ");
137
+ i0.ɵɵelementEnd()();
138
+ } if (rf & 2) {
139
+ const ctx_r2 = i0.ɵɵnextContext(2);
140
+ i0.ɵɵadvance();
141
+ i0.ɵɵclassProp("active", ctx_r2.filter.value === true);
142
+ i0.ɵɵproperty("disabled", ctx_r2.disabled);
143
+ i0.ɵɵadvance(2);
144
+ i0.ɵɵclassProp("active", ctx_r2.filter.value === false);
145
+ i0.ɵɵproperty("disabled", ctx_r2.disabled);
146
+ } }
147
+ function FilterRuleComponent_Conditional_13_Conditional_4_Template(rf, ctx) { if (rf & 1) {
148
+ const _r16 = i0.ɵɵgetCurrentView();
149
+ i0.ɵɵelementStart(0, "input", 23);
150
+ i0.ɵɵlistener("change", function FilterRuleComponent_Conditional_13_Conditional_4_Template_input_change_0_listener($event) { i0.ɵɵrestoreView(_r16); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onDateChange($event)); });
151
+ i0.ɵɵelementEnd();
152
+ } if (rf & 2) {
153
+ const ctx_r2 = i0.ɵɵnextContext(2);
154
+ i0.ɵɵproperty("value", ctx_r2.getDateInputValue())("disabled", ctx_r2.disabled);
155
+ } }
156
+ function FilterRuleComponent_Conditional_13_Conditional_5_Template(rf, ctx) { if (rf & 1) {
157
+ const _r17 = i0.ɵɵgetCurrentView();
158
+ i0.ɵɵelementStart(0, "input", 24);
159
+ i0.ɵɵlistener("input", function FilterRuleComponent_Conditional_13_Conditional_5_Template_input_input_0_listener($event) { i0.ɵɵrestoreView(_r17); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.onValueChange($event.target.value)); });
160
+ i0.ɵɵelementEnd();
161
+ } if (rf & 2) {
162
+ const ctx_r2 = i0.ɵɵnextContext(2);
163
+ i0.ɵɵproperty("value", ctx_r2.filter.value || "")("disabled", ctx_r2.disabled);
164
+ } }
165
+ function FilterRuleComponent_Conditional_13_Template(rf, ctx) { if (rf & 1) {
166
+ i0.ɵɵtemplate(0, FilterRuleComponent_Conditional_13_Conditional_0_Template, 6, 7, "div", 12)(1, FilterRuleComponent_Conditional_13_Conditional_1_Template, 1, 2, "input", 13)(2, FilterRuleComponent_Conditional_13_Conditional_2_Template, 1, 2, "input", 14)(3, FilterRuleComponent_Conditional_13_Conditional_3_Template, 5, 6, "div", 15)(4, FilterRuleComponent_Conditional_13_Conditional_4_Template, 1, 2, "input", 16)(5, FilterRuleComponent_Conditional_13_Conditional_5_Template, 1, 2, "input", 17);
167
+ } if (rf & 2) {
168
+ const ctx_r2 = i0.ɵɵnextContext();
169
+ i0.ɵɵconditional(ctx_r2.selectedField.type === "string" && ctx_r2.hasValueList() ? 0 : ctx_r2.selectedField.type === "string" ? 1 : ctx_r2.selectedField.type === "number" ? 2 : ctx_r2.selectedField.type === "boolean" ? 3 : ctx_r2.selectedField.type === "date" ? 4 : ctx_r2.selectedField.type === "lookup" ? 5 : -1);
170
+ } }
171
+ function FilterRuleComponent_Conditional_15_Template(rf, ctx) { if (rf & 1) {
172
+ const _r18 = i0.ɵɵgetCurrentView();
173
+ i0.ɵɵelementStart(0, "button", 25);
174
+ i0.ɵɵlistener("click", function FilterRuleComponent_Conditional_15_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r18); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.onDelete()); });
175
+ i0.ɵɵelement(1, "i", 26);
176
+ i0.ɵɵelementEnd();
177
+ } if (rf & 2) {
178
+ const ctx_r2 = i0.ɵɵnextContext();
179
+ i0.ɵɵproperty("disabled", ctx_r2.disabled);
180
+ } }
181
+ /**
182
+ * FilterRuleComponent - A single filter condition row
183
+ *
184
+ * Displays field selector, operator selector, and value editor
185
+ * based on the field type.
186
+ */
187
+ export class FilterRuleComponent {
188
+ elementRef;
189
+ /**
190
+ * The filter descriptor for this rule
191
+ */
192
+ filter;
193
+ /**
194
+ * Available fields to filter on
195
+ */
196
+ fields = [];
197
+ /**
198
+ * Whether the component is disabled
199
+ */
200
+ disabled = false;
201
+ /**
202
+ * Whether to show the delete button
203
+ */
204
+ showDelete = true;
205
+ /**
206
+ * Emitted when the filter changes
207
+ */
208
+ filterChange = new EventEmitter();
209
+ /**
210
+ * Emitted when the delete button is clicked
211
+ */
212
+ delete = new EventEmitter();
213
+ /**
214
+ * Currently selected field info
215
+ */
216
+ selectedField = null;
217
+ /**
218
+ * Available operators for the selected field type
219
+ */
220
+ availableOperators = [];
221
+ /**
222
+ * Whether the current operator requires a value
223
+ */
224
+ requiresValue = true;
225
+ // Dropdown state
226
+ fieldDropdownOpen = false;
227
+ operatorDropdownOpen = false;
228
+ valueDropdownOpen = false;
229
+ // Keyboard navigation state
230
+ fieldHighlightIndex = -1;
231
+ operatorHighlightIndex = -1;
232
+ valueHighlightIndex = -1;
233
+ constructor(elementRef) {
234
+ this.elementRef = elementRef;
235
+ }
236
+ /**
237
+ * Close dropdowns when clicking outside the component
238
+ */
239
+ onDocumentClick(event) {
240
+ if (!this.elementRef.nativeElement.contains(event.target)) {
241
+ this.closeAllDropdowns();
242
+ }
243
+ }
244
+ ngOnInit() {
245
+ this.updateFieldSelection();
246
+ }
247
+ ngOnChanges(changes) {
248
+ if (changes['filter'] || changes['fields']) {
249
+ this.updateFieldSelection();
250
+ }
251
+ }
252
+ /**
253
+ * Update the selected field and available operators
254
+ */
255
+ updateFieldSelection() {
256
+ if (!this.filter || !this.fields.length)
257
+ return;
258
+ this.selectedField = this.fields.find(f => f.name === this.filter.field) || null;
259
+ if (this.selectedField) {
260
+ this.availableOperators = getOperatorsForType(this.selectedField.type);
261
+ this.requiresValue = operatorRequiresValue(this.filter.operator);
262
+ }
263
+ else {
264
+ this.availableOperators = [];
265
+ this.requiresValue = true;
266
+ }
267
+ }
268
+ // ========================================
269
+ // DROPDOWN TOGGLE METHODS
270
+ // ========================================
271
+ toggleFieldDropdown() {
272
+ if (this.disabled)
273
+ return;
274
+ const wasOpen = this.fieldDropdownOpen;
275
+ this.closeAllDropdowns();
276
+ this.fieldDropdownOpen = !wasOpen;
277
+ }
278
+ toggleOperatorDropdown() {
279
+ if (this.disabled || !this.selectedField)
280
+ return;
281
+ const wasOpen = this.operatorDropdownOpen;
282
+ this.closeAllDropdowns();
283
+ this.operatorDropdownOpen = !wasOpen;
284
+ }
285
+ toggleValueDropdown() {
286
+ if (this.disabled)
287
+ return;
288
+ const wasOpen = this.valueDropdownOpen;
289
+ this.closeAllDropdowns();
290
+ this.valueDropdownOpen = !wasOpen;
291
+ }
292
+ closeFieldDropdown() {
293
+ this.fieldDropdownOpen = false;
294
+ }
295
+ closeOperatorDropdown() {
296
+ this.operatorDropdownOpen = false;
297
+ }
298
+ closeValueDropdown() {
299
+ this.valueDropdownOpen = false;
300
+ }
301
+ closeAllDropdowns() {
302
+ this.fieldDropdownOpen = false;
303
+ this.operatorDropdownOpen = false;
304
+ this.valueDropdownOpen = false;
305
+ this.resetHighlightIndices();
306
+ }
307
+ resetHighlightIndices() {
308
+ this.fieldHighlightIndex = -1;
309
+ this.operatorHighlightIndex = -1;
310
+ this.valueHighlightIndex = -1;
311
+ }
312
+ // ========================================
313
+ // KEYBOARD NAVIGATION
314
+ // ========================================
315
+ /**
316
+ * Handle keyboard events on the field dropdown trigger
317
+ */
318
+ onFieldKeydown(event) {
319
+ if (this.disabled)
320
+ return;
321
+ if (!this.fieldDropdownOpen) {
322
+ // Open dropdown on Enter, Space, or arrow keys
323
+ if (event.key === 'Enter' || event.key === ' ' || event.key === 'ArrowDown' || event.key === 'ArrowUp') {
324
+ event.preventDefault();
325
+ this.toggleFieldDropdown();
326
+ this.fieldHighlightIndex = this.fields.findIndex(f => f.name === this.filter.field);
327
+ if (this.fieldHighlightIndex < 0)
328
+ this.fieldHighlightIndex = 0;
329
+ }
330
+ return;
331
+ }
332
+ this.handleDropdownKeydown(event, this.fields, this.fieldHighlightIndex, (index) => this.fieldHighlightIndex = index, (item) => this.selectField(item.name), (item) => item.displayName, () => this.closeFieldDropdown());
333
+ }
334
+ /**
335
+ * Handle keyboard events on the operator dropdown trigger
336
+ */
337
+ onOperatorKeydown(event) {
338
+ if (this.disabled || !this.selectedField)
339
+ return;
340
+ if (!this.operatorDropdownOpen) {
341
+ if (event.key === 'Enter' || event.key === ' ' || event.key === 'ArrowDown' || event.key === 'ArrowUp') {
342
+ event.preventDefault();
343
+ this.toggleOperatorDropdown();
344
+ this.operatorHighlightIndex = this.availableOperators.findIndex(o => o.value === this.filter.operator);
345
+ if (this.operatorHighlightIndex < 0)
346
+ this.operatorHighlightIndex = 0;
347
+ }
348
+ return;
349
+ }
350
+ this.handleDropdownKeydown(event, this.availableOperators, this.operatorHighlightIndex, (index) => this.operatorHighlightIndex = index, (item) => this.selectOperator(item.value), (item) => item.label, () => this.closeOperatorDropdown());
351
+ }
352
+ /**
353
+ * Handle keyboard events on the value dropdown trigger
354
+ */
355
+ onValueKeydown(event) {
356
+ if (this.disabled || !this.selectedField?.valueList)
357
+ return;
358
+ if (!this.valueDropdownOpen) {
359
+ if (event.key === 'Enter' || event.key === ' ' || event.key === 'ArrowDown' || event.key === 'ArrowUp') {
360
+ event.preventDefault();
361
+ this.toggleValueDropdown();
362
+ const valueList = this.selectedField.valueList;
363
+ this.valueHighlightIndex = valueList.findIndex(v => v.value === this.filter.value);
364
+ if (this.valueHighlightIndex < 0)
365
+ this.valueHighlightIndex = 0;
366
+ }
367
+ return;
368
+ }
369
+ this.handleDropdownKeydown(event, this.selectedField.valueList, this.valueHighlightIndex, (index) => this.valueHighlightIndex = index, (item) => this.selectValueFromOption(item.value), (item) => item.label, () => this.closeValueDropdown());
370
+ }
371
+ /**
372
+ * Generic handler for dropdown keyboard navigation
373
+ */
374
+ handleDropdownKeydown(event, items, currentIndex, setIndex, onSelect, getLabel, onClose) {
375
+ switch (event.key) {
376
+ case 'ArrowDown':
377
+ event.preventDefault();
378
+ setIndex(Math.min(currentIndex + 1, items.length - 1));
379
+ this.scrollHighlightedItemIntoView();
380
+ break;
381
+ case 'ArrowUp':
382
+ event.preventDefault();
383
+ setIndex(Math.max(currentIndex - 1, 0));
384
+ this.scrollHighlightedItemIntoView();
385
+ break;
386
+ case 'Enter':
387
+ case ' ':
388
+ event.preventDefault();
389
+ if (currentIndex >= 0 && currentIndex < items.length) {
390
+ onSelect(items[currentIndex]);
391
+ }
392
+ break;
393
+ case 'Escape':
394
+ case 'Tab':
395
+ event.preventDefault();
396
+ onClose();
397
+ break;
398
+ case 'Home':
399
+ event.preventDefault();
400
+ setIndex(0);
401
+ this.scrollHighlightedItemIntoView();
402
+ break;
403
+ case 'End':
404
+ event.preventDefault();
405
+ setIndex(items.length - 1);
406
+ this.scrollHighlightedItemIntoView();
407
+ break;
408
+ default:
409
+ // Type-ahead: jump to first item starting with typed character
410
+ if (event.key.length === 1 && /[a-zA-Z0-9]/.test(event.key)) {
411
+ const char = event.key.toLowerCase();
412
+ const startIndex = currentIndex + 1;
413
+ // Search from current position to end, then from start
414
+ for (let i = 0; i < items.length; i++) {
415
+ const idx = (startIndex + i) % items.length;
416
+ const label = getLabel(items[idx]).toLowerCase();
417
+ if (label.startsWith(char)) {
418
+ setIndex(idx);
419
+ this.scrollHighlightedItemIntoView();
420
+ break;
421
+ }
422
+ }
423
+ }
424
+ break;
425
+ }
426
+ }
427
+ /**
428
+ * Scroll the highlighted dropdown item into view
429
+ */
430
+ scrollHighlightedItemIntoView() {
431
+ // Use setTimeout to let Angular update the DOM first
432
+ setTimeout(() => {
433
+ const highlightedEl = this.elementRef.nativeElement.querySelector('.dropdown-item.highlighted');
434
+ if (highlightedEl) {
435
+ highlightedEl.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
436
+ }
437
+ }, 0);
438
+ }
439
+ // ========================================
440
+ // DISPLAY HELPERS
441
+ // ========================================
442
+ /**
443
+ * Get the display name for the currently selected field
444
+ */
445
+ getSelectedFieldDisplayName() {
446
+ if (!this.filter.field)
447
+ return 'Select field...';
448
+ const field = this.fields.find(f => f.name === this.filter.field);
449
+ return field?.displayName || this.filter.field;
450
+ }
451
+ /**
452
+ * Get the label for the currently selected operator
453
+ */
454
+ getSelectedOperatorLabel() {
455
+ if (!this.filter.operator)
456
+ return 'Select...';
457
+ const op = this.availableOperators.find(o => o.value === this.filter.operator);
458
+ return op?.label || this.filter.operator;
459
+ }
460
+ /**
461
+ * Get the label for the currently selected value (for value list dropdowns)
462
+ */
463
+ getSelectedValueLabel() {
464
+ if (!this.filter.value || !this.selectedField?.valueList)
465
+ return 'Select...';
466
+ const option = this.selectedField.valueList.find(o => o.value === this.filter.value);
467
+ return option?.label || String(this.filter.value);
468
+ }
469
+ // ========================================
470
+ // SELECTION HANDLERS
471
+ // ========================================
472
+ /**
473
+ * Handle field selection from custom dropdown
474
+ */
475
+ selectField(fieldName) {
476
+ this.closeFieldDropdown();
477
+ this.onFieldChange(fieldName);
478
+ }
479
+ /**
480
+ * Handle operator selection from custom dropdown
481
+ */
482
+ selectOperator(operator) {
483
+ this.closeOperatorDropdown();
484
+ this.onOperatorChange(operator);
485
+ }
486
+ /**
487
+ * Handle value selection from custom dropdown
488
+ */
489
+ selectValue(value) {
490
+ this.closeValueDropdown();
491
+ this.onValueChange(value);
492
+ }
493
+ /**
494
+ * Handle value selection from value list option (supports union type)
495
+ */
496
+ selectValueFromOption(value) {
497
+ this.closeValueDropdown();
498
+ this.onValueChange(value);
499
+ }
500
+ /**
501
+ * Handle field selection change
502
+ */
503
+ onFieldChange(fieldName) {
504
+ const field = this.fields.find(f => f.name === fieldName);
505
+ if (!field)
506
+ return;
507
+ this.selectedField = field;
508
+ this.availableOperators = getOperatorsForType(field.type);
509
+ // Get default operator for the new field type
510
+ const defaultOperator = this.availableOperators[0]?.value || 'eq';
511
+ this.requiresValue = operatorRequiresValue(defaultOperator);
512
+ // Emit updated filter with new field and reset value
513
+ this.emitChange({
514
+ field: fieldName,
515
+ operator: defaultOperator,
516
+ value: this.getDefaultValue(field.type)
517
+ });
518
+ }
519
+ /**
520
+ * Handle operator selection change
521
+ */
522
+ onOperatorChange(operator) {
523
+ this.requiresValue = operatorRequiresValue(operator);
524
+ // If operator doesn't require value, clear it
525
+ const value = this.requiresValue ? this.filter.value : null;
526
+ this.emitChange({
527
+ ...this.filter,
528
+ operator,
529
+ value
530
+ });
531
+ }
532
+ /**
533
+ * Handle value change
534
+ */
535
+ onValueChange(value) {
536
+ this.emitChange({
537
+ ...this.filter,
538
+ value
539
+ });
540
+ }
541
+ /**
542
+ * Handle boolean toggle
543
+ */
544
+ onBooleanChange(value) {
545
+ this.emitChange({
546
+ ...this.filter,
547
+ value
548
+ });
549
+ }
550
+ /**
551
+ * Handle date change
552
+ */
553
+ onDateChange(event) {
554
+ const input = event.target;
555
+ const value = input.value ? new Date(input.value).toISOString() : null;
556
+ this.emitChange({
557
+ ...this.filter,
558
+ value
559
+ });
560
+ }
561
+ /**
562
+ * Handle delete button click
563
+ */
564
+ onDelete() {
565
+ this.delete.emit();
566
+ }
567
+ /**
568
+ * Emit the filter change event
569
+ */
570
+ emitChange(filter) {
571
+ this.filterChange.emit(filter);
572
+ }
573
+ /**
574
+ * Get default value for a field type
575
+ */
576
+ getDefaultValue(type) {
577
+ switch (type) {
578
+ case 'string':
579
+ return '';
580
+ case 'number':
581
+ return null;
582
+ case 'boolean':
583
+ return true;
584
+ case 'date':
585
+ return null;
586
+ case 'lookup':
587
+ return null;
588
+ default:
589
+ return null;
590
+ }
591
+ }
592
+ /**
593
+ * Get the date value formatted for the date input
594
+ */
595
+ getDateInputValue() {
596
+ if (!this.filter.value)
597
+ return '';
598
+ try {
599
+ const date = new Date(this.filter.value);
600
+ return date.toISOString().split('T')[0];
601
+ }
602
+ catch {
603
+ return '';
604
+ }
605
+ }
606
+ /**
607
+ * Check if the current field has a value list (dropdown options)
608
+ */
609
+ hasValueList() {
610
+ return !!(this.selectedField?.valueList && this.selectedField.valueList.length > 0);
611
+ }
612
+ static ɵfac = function FilterRuleComponent_Factory(t) { return new (t || FilterRuleComponent)(i0.ɵɵdirectiveInject(i0.ElementRef)); };
613
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: FilterRuleComponent, selectors: [["mj-filter-rule"]], hostBindings: function FilterRuleComponent_HostBindings(rf, ctx) { if (rf & 1) {
614
+ i0.ɵɵlistener("click", function FilterRuleComponent_click_HostBindingHandler($event) { return ctx.onDocumentClick($event); }, false, i0.ɵɵresolveDocument);
615
+ } }, inputs: { filter: "filter", fields: "fields", disabled: "disabled", showDelete: "showDelete" }, outputs: { filterChange: "filterChange", delete: "delete" }, features: [i0.ɵɵNgOnChangesFeature], decls: 16, vars: 18, consts: [[1, "filter-rule"], [1, "custom-dropdown", "field-dropdown"], ["type", "button", 1, "dropdown-trigger", 3, "click", "keydown", "disabled"], [1, "dropdown-value"], [1, "fa-solid", "fa-chevron-down", "dropdown-arrow"], [1, "dropdown-menu"], [1, "custom-dropdown", "operator-dropdown"], [1, "rule-actions"], ["type", "button", "title", "Remove filter", 1, "action-btn", "delete", 3, "disabled"], [1, "dropdown-item", 3, "selected", "highlighted"], [1, "dropdown-empty"], [1, "dropdown-item", 3, "click"], [1, "custom-dropdown", "value-dropdown", 3, "open", "disabled"], ["type", "text", "placeholder", "Enter value...", 1, "value-input", 3, "value", "disabled"], ["type", "number", "placeholder", "Enter number...", 1, "value-input", 3, "value", "disabled"], [1, "value-toggle"], ["type", "date", 1, "value-input", "value-date", 3, "value", "disabled"], ["type", "text", "placeholder", "Enter ID...", 1, "value-input", 3, "value", "disabled"], [1, "custom-dropdown", "value-dropdown"], ["type", "text", "placeholder", "Enter value...", 1, "value-input", 3, "input", "value", "disabled"], ["type", "number", "placeholder", "Enter number...", 1, "value-input", 3, "input", "value", "disabled"], ["type", "button", 1, "toggle-btn", "true", 3, "click", "disabled"], ["type", "button", 1, "toggle-btn", "false", 3, "click", "disabled"], ["type", "date", 1, "value-input", "value-date", 3, "change", "value", "disabled"], ["type", "text", "placeholder", "Enter ID...", 1, "value-input", 3, "input", "value", "disabled"], ["type", "button", "title", "Remove filter", 1, "action-btn", "delete", 3, "click", "disabled"], [1, "fa-solid", "fa-times"]], template: function FilterRuleComponent_Template(rf, ctx) { if (rf & 1) {
616
+ i0.ɵɵelementStart(0, "div", 0)(1, "div", 1)(2, "button", 2);
617
+ i0.ɵɵlistener("click", function FilterRuleComponent_Template_button_click_2_listener() { return ctx.toggleFieldDropdown(); })("keydown", function FilterRuleComponent_Template_button_keydown_2_listener($event) { return ctx.onFieldKeydown($event); });
618
+ i0.ɵɵelementStart(3, "span", 3);
619
+ i0.ɵɵtext(4);
620
+ i0.ɵɵelementEnd();
621
+ i0.ɵɵelement(5, "i", 4);
622
+ i0.ɵɵelementEnd();
623
+ i0.ɵɵtemplate(6, FilterRuleComponent_Conditional_6_Template, 4, 1, "div", 5);
624
+ i0.ɵɵelementEnd();
625
+ i0.ɵɵelementStart(7, "div", 6)(8, "button", 2);
626
+ i0.ɵɵlistener("click", function FilterRuleComponent_Template_button_click_8_listener() { return ctx.toggleOperatorDropdown(); })("keydown", function FilterRuleComponent_Template_button_keydown_8_listener($event) { return ctx.onOperatorKeydown($event); });
627
+ i0.ɵɵelementStart(9, "span", 3);
628
+ i0.ɵɵtext(10);
629
+ i0.ɵɵelementEnd();
630
+ i0.ɵɵelement(11, "i", 4);
631
+ i0.ɵɵelementEnd();
632
+ i0.ɵɵtemplate(12, FilterRuleComponent_Conditional_12_Template, 3, 0, "div", 5);
633
+ i0.ɵɵelementEnd();
634
+ i0.ɵɵtemplate(13, FilterRuleComponent_Conditional_13_Template, 6, 1);
635
+ i0.ɵɵelementStart(14, "div", 7);
636
+ i0.ɵɵtemplate(15, FilterRuleComponent_Conditional_15_Template, 2, 1, "button", 8);
637
+ i0.ɵɵelementEnd()();
638
+ } if (rf & 2) {
639
+ i0.ɵɵclassProp("disabled", ctx.disabled);
640
+ i0.ɵɵadvance();
641
+ i0.ɵɵclassProp("open", ctx.fieldDropdownOpen)("disabled", ctx.disabled);
642
+ i0.ɵɵadvance();
643
+ i0.ɵɵproperty("disabled", ctx.disabled);
644
+ i0.ɵɵadvance(2);
645
+ i0.ɵɵtextInterpolate(ctx.getSelectedFieldDisplayName());
646
+ i0.ɵɵadvance(2);
647
+ i0.ɵɵconditional(ctx.fieldDropdownOpen ? 6 : -1);
648
+ i0.ɵɵadvance();
649
+ i0.ɵɵclassProp("open", ctx.operatorDropdownOpen)("disabled", ctx.disabled || !ctx.selectedField);
650
+ i0.ɵɵadvance();
651
+ i0.ɵɵproperty("disabled", ctx.disabled || !ctx.selectedField);
652
+ i0.ɵɵadvance(2);
653
+ i0.ɵɵtextInterpolate(ctx.getSelectedOperatorLabel());
654
+ i0.ɵɵadvance(2);
655
+ i0.ɵɵconditional(ctx.operatorDropdownOpen ? 12 : -1);
656
+ i0.ɵɵadvance();
657
+ i0.ɵɵconditional(ctx.requiresValue && ctx.selectedField ? 13 : -1);
658
+ i0.ɵɵadvance(2);
659
+ i0.ɵɵconditional(ctx.showDelete ? 15 : -1);
660
+ } }, styles: ["/* Scoped to mj-filter-rule to prevent style leakage with ViewEncapsulation.None */\nmj-filter-rule .filter-rule {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 12px 16px;\n background: linear-gradient(to bottom, #ffffff, #fafafa);\n border: 1px solid #e5e7eb;\n border-radius: 10px;\n transition: all 0.2s ease;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);\n}\n\nmj-filter-rule .filter-rule:hover {\n border-color: #d1d5db;\n background: linear-gradient(to bottom, #ffffff, #f5f5f5);\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.06);\n}\n\nmj-filter-rule .filter-rule.disabled {\n opacity: 0.6;\n pointer-events: none;\n box-shadow: none;\n}\n\n/* ========================================\n CUSTOM DROPDOWN STYLES\n ======================================== */\n\nmj-filter-rule .custom-dropdown {\n position: relative;\n min-width: 160px;\n}\n\nmj-filter-rule .custom-dropdown.field-dropdown {\n min-width: 180px;\n}\n\nmj-filter-rule .custom-dropdown.operator-dropdown {\n min-width: 160px;\n}\n\nmj-filter-rule .custom-dropdown.value-dropdown {\n min-width: 160px;\n flex: 1;\n}\n\nmj-filter-rule .custom-dropdown.disabled {\n opacity: 0.6;\n pointer-events: none;\n}\n\n/* Dropdown Trigger Button */\nmj-filter-rule .dropdown-trigger {\n display: flex;\n align-items: center;\n justify-content: space-between;\n width: 100%;\n padding: 10px 14px;\n border: 1px solid #d4d4d8;\n border-radius: 8px;\n font-size: 14px;\n font-weight: 500;\n background: white;\n color: #374151;\n cursor: pointer;\n transition: all 0.2s ease;\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);\n text-align: left;\n}\n\nmj-filter-rule .dropdown-trigger:hover:not(:disabled) {\n border-color: #a1a1aa;\n background: #fafafa;\n}\n\nmj-filter-rule .dropdown-trigger:focus {\n outline: none;\n border-color: #3b82f6;\n box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);\n background: white;\n}\n\nmj-filter-rule .dropdown-trigger:disabled {\n background-color: #f4f4f5;\n color: #a1a1aa;\n cursor: not-allowed;\n box-shadow: none;\n}\n\nmj-filter-rule .custom-dropdown.open .dropdown-trigger {\n border-color: #3b82f6;\n box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);\n}\n\nmj-filter-rule .dropdown-value {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\nmj-filter-rule .dropdown-arrow {\n font-size: 10px;\n color: #6b7280;\n margin-left: 8px;\n transition: transform 0.2s ease;\n}\n\nmj-filter-rule .custom-dropdown.open .dropdown-arrow {\n transform: rotate(180deg);\n}\n\n/* Dropdown Menu */\nmj-filter-rule .dropdown-menu {\n position: absolute;\n top: calc(100% + 4px);\n left: 0;\n right: 0;\n min-width: 100%;\n max-height: 280px;\n overflow-y: auto;\n background: white;\n border: 1px solid #e5e7eb;\n border-radius: 10px;\n box-shadow: 0 10px 40px rgba(0, 0, 0, 0.12), 0 2px 6px rgba(0, 0, 0, 0.04);\n z-index: 9999;\n animation: dropdownFadeIn 0.15s ease;\n}\n\n@keyframes dropdownFadeIn {\n from {\n opacity: 0;\n transform: translateY(-8px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n/* Dropdown Items */\nmj-filter-rule .dropdown-item {\n padding: 10px 14px;\n font-size: 14px;\n color: #374151;\n cursor: pointer;\n transition: all 0.15s ease;\n border-bottom: 1px solid #f3f4f6;\n}\n\nmj-filter-rule .dropdown-item:last-child {\n border-bottom: none;\n}\n\nmj-filter-rule .dropdown-item:hover,\nmj-filter-rule .dropdown-item.highlighted {\n background: #f0f9ff;\n color: #1d4ed8;\n}\n\nmj-filter-rule .dropdown-item.highlighted {\n outline: 2px solid #3b82f6;\n outline-offset: -2px;\n}\n\nmj-filter-rule .dropdown-item.selected {\n background: #eff6ff;\n color: #1d4ed8;\n font-weight: 600;\n}\n\nmj-filter-rule .dropdown-item.selected::before {\n content: '\\f00c';\n font-family: 'Font Awesome 6 Free';\n font-weight: 900;\n font-size: 10px;\n margin-right: 8px;\n color: #3b82f6;\n}\n\nmj-filter-rule .dropdown-empty {\n padding: 16px;\n text-align: center;\n color: #9ca3af;\n font-size: 13px;\n font-style: italic;\n}\n\n/* Scrollbar styling for dropdown */\nmj-filter-rule .dropdown-menu::-webkit-scrollbar {\n width: 6px;\n}\n\nmj-filter-rule .dropdown-menu::-webkit-scrollbar-track {\n background: #f1f1f1;\n border-radius: 3px;\n}\n\nmj-filter-rule .dropdown-menu::-webkit-scrollbar-thumb {\n background: #c1c1c1;\n border-radius: 3px;\n}\n\nmj-filter-rule .dropdown-menu::-webkit-scrollbar-thumb:hover {\n background: #a1a1a1;\n}\n\n/* ========================================\n VALUE INPUT STYLES\n ======================================== */\n\nmj-filter-rule .value-input {\n padding: 10px 14px;\n border: 1px solid #d4d4d8;\n border-radius: 8px;\n font-size: 14px;\n font-weight: 500;\n background-color: white;\n color: #374151;\n transition: all 0.2s ease;\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);\n flex: 1;\n min-width: 150px;\n}\n\nmj-filter-rule .value-input:hover:not(:disabled) {\n border-color: #a1a1aa;\n background-color: #fafafa;\n}\n\nmj-filter-rule .value-input:focus {\n outline: none;\n border-color: #3b82f6;\n box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);\n background-color: white;\n}\n\nmj-filter-rule .value-input:disabled {\n background-color: #f4f4f5;\n color: #a1a1aa;\n cursor: not-allowed;\n box-shadow: none;\n}\n\nmj-filter-rule .value-input::placeholder {\n color: #9ca3af;\n font-weight: 400;\n}\n\nmj-filter-rule .value-date {\n min-width: 160px;\n}\n\n/* ========================================\n BOOLEAN TOGGLE STYLES\n ======================================== */\n\nmj-filter-rule .value-toggle {\n display: flex;\n background: #f4f4f5;\n border-radius: 8px;\n padding: 3px;\n border: 1px solid #e5e7eb;\n}\n\nmj-filter-rule .toggle-btn {\n padding: 8px 18px;\n border: none;\n background: transparent;\n border-radius: 6px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.2s ease;\n color: #71717a;\n}\n\nmj-filter-rule .toggle-btn:hover:not(:disabled) {\n color: #3f3f46;\n background: rgba(255, 255, 255, 0.5);\n}\n\nmj-filter-rule .toggle-btn:disabled {\n cursor: not-allowed;\n opacity: 0.5;\n}\n\nmj-filter-rule .toggle-btn.active {\n background: white;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n color: #18181b;\n}\n\nmj-filter-rule .toggle-btn.true.active {\n color: #16a34a;\n}\n\nmj-filter-rule .toggle-btn.false.active {\n color: #dc2626;\n}\n\n/* ========================================\n RULE ACTIONS\n ======================================== */\n\nmj-filter-rule .rule-actions {\n display: flex;\n gap: 4px;\n margin-left: auto;\n flex-shrink: 0;\n}\n\nmj-filter-rule .action-btn {\n width: 32px;\n height: 32px;\n border: none;\n background: transparent;\n border-radius: 8px;\n cursor: pointer;\n color: #a1a1aa;\n transition: all 0.2s ease;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 14px;\n}\n\nmj-filter-rule .action-btn:hover:not(:disabled) {\n background: #f4f4f5;\n color: #71717a;\n}\n\nmj-filter-rule .action-btn:disabled {\n cursor: not-allowed;\n opacity: 0.3;\n}\n\nmj-filter-rule .action-btn.delete:hover:not(:disabled) {\n background: #fef2f2;\n color: #ef4444;\n}\n\n/* ========================================\n RESPONSIVE\n ======================================== */\n\n@media (max-width: 768px) {\n mj-filter-rule .filter-rule {\n flex-wrap: wrap;\n }\n\n mj-filter-rule .custom-dropdown,\n mj-filter-rule .value-input {\n min-width: 100%;\n flex: 1 1 100%;\n }\n\n mj-filter-rule .custom-dropdown.field-dropdown,\n mj-filter-rule .custom-dropdown.operator-dropdown,\n mj-filter-rule .custom-dropdown.value-dropdown {\n min-width: 100%;\n }\n}\n"], encapsulation: 2 });
661
+ }
662
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(FilterRuleComponent, [{
663
+ type: Component,
664
+ args: [{ selector: 'mj-filter-rule', encapsulation: ViewEncapsulation.None, template: "<div class=\"filter-rule\" [class.disabled]=\"disabled\">\n <!-- Field Selector - Custom Dropdown -->\n <div class=\"custom-dropdown field-dropdown\" [class.open]=\"fieldDropdownOpen\" [class.disabled]=\"disabled\">\n <button\n type=\"button\"\n class=\"dropdown-trigger\"\n (click)=\"toggleFieldDropdown()\"\n (keydown)=\"onFieldKeydown($event)\"\n [disabled]=\"disabled\">\n <span class=\"dropdown-value\">{{ getSelectedFieldDisplayName() }}</span>\n <i class=\"fa-solid fa-chevron-down dropdown-arrow\"></i>\n </button>\n @if (fieldDropdownOpen) {\n <div class=\"dropdown-menu\">\n @for (field of fields; track field.name; let i = $index) {\n <div\n class=\"dropdown-item\"\n [class.selected]=\"field.name === filter.field\"\n [class.highlighted]=\"i === fieldHighlightIndex\"\n (click)=\"selectField(field.name)\">\n {{ field.displayName }}\n </div>\n }\n @if (fields.length === 0) {\n <div class=\"dropdown-empty\">No fields available</div>\n }\n </div>\n }\n </div>\n\n <!-- Operator Selector - Custom Dropdown -->\n <div class=\"custom-dropdown operator-dropdown\" [class.open]=\"operatorDropdownOpen\" [class.disabled]=\"disabled || !selectedField\">\n <button\n type=\"button\"\n class=\"dropdown-trigger\"\n (click)=\"toggleOperatorDropdown()\"\n (keydown)=\"onOperatorKeydown($event)\"\n [disabled]=\"disabled || !selectedField\">\n <span class=\"dropdown-value\">{{ getSelectedOperatorLabel() }}</span>\n <i class=\"fa-solid fa-chevron-down dropdown-arrow\"></i>\n </button>\n @if (operatorDropdownOpen) {\n <div class=\"dropdown-menu\">\n @for (op of availableOperators; track op.value; let i = $index) {\n <div\n class=\"dropdown-item\"\n [class.selected]=\"op.value === filter.operator\"\n [class.highlighted]=\"i === operatorHighlightIndex\"\n (click)=\"selectOperator(op.value)\">\n {{ op.label }}\n </div>\n }\n </div>\n }\n </div>\n\n <!-- Value Editor - varies by field type -->\n @if (requiresValue && selectedField) {\n <!-- String with value list (custom dropdown) -->\n @if (selectedField.type === 'string' && hasValueList()) {\n <div class=\"custom-dropdown value-dropdown\" [class.open]=\"valueDropdownOpen\" [class.disabled]=\"disabled\">\n <button\n type=\"button\"\n class=\"dropdown-trigger\"\n (click)=\"toggleValueDropdown()\"\n (keydown)=\"onValueKeydown($event)\"\n [disabled]=\"disabled\">\n <span class=\"dropdown-value\">{{ getSelectedValueLabel() }}</span>\n <i class=\"fa-solid fa-chevron-down dropdown-arrow\"></i>\n </button>\n @if (valueDropdownOpen) {\n <div class=\"dropdown-menu\">\n <div\n class=\"dropdown-item\"\n [class.selected]=\"!filter.value\"\n [class.highlighted]=\"0 === valueHighlightIndex\"\n (click)=\"selectValue('')\">\n Select...\n </div>\n @for (option of selectedField.valueList; track option.value; let i = $index) {\n <div\n class=\"dropdown-item\"\n [class.selected]=\"option.value === filter.value\"\n [class.highlighted]=\"(i + 1) === valueHighlightIndex\"\n (click)=\"selectValue(option.value)\">\n {{ option.label }}\n </div>\n }\n </div>\n }\n </div>\n }\n <!-- String (text input) -->\n @else if (selectedField.type === 'string') {\n <input\n type=\"text\"\n class=\"value-input\"\n [value]=\"filter.value || ''\"\n (input)=\"onValueChange($any($event.target).value)\"\n [disabled]=\"disabled\"\n placeholder=\"Enter value...\">\n }\n <!-- Number -->\n @else if (selectedField.type === 'number') {\n <input\n type=\"number\"\n class=\"value-input\"\n [value]=\"filter.value\"\n (input)=\"onValueChange($any($event.target).valueAsNumber)\"\n [disabled]=\"disabled\"\n placeholder=\"Enter number...\">\n }\n <!-- Boolean -->\n @else if (selectedField.type === 'boolean') {\n <div class=\"value-toggle\">\n <button\n type=\"button\"\n class=\"toggle-btn true\"\n [class.active]=\"filter.value === true\"\n (click)=\"onBooleanChange(true)\"\n [disabled]=\"disabled\">\n True\n </button>\n <button\n type=\"button\"\n class=\"toggle-btn false\"\n [class.active]=\"filter.value === false\"\n (click)=\"onBooleanChange(false)\"\n [disabled]=\"disabled\">\n False\n </button>\n </div>\n }\n <!-- Date -->\n @else if (selectedField.type === 'date') {\n <input\n type=\"date\"\n class=\"value-input value-date\"\n [value]=\"getDateInputValue()\"\n (change)=\"onDateChange($event)\"\n [disabled]=\"disabled\">\n }\n <!-- Lookup (for now, simple text input - can be enhanced with record selector) -->\n @else if (selectedField.type === 'lookup') {\n <input\n type=\"text\"\n class=\"value-input\"\n [value]=\"filter.value || ''\"\n (input)=\"onValueChange($any($event.target).value)\"\n [disabled]=\"disabled\"\n placeholder=\"Enter ID...\">\n }\n }\n\n <!-- Actions -->\n <div class=\"rule-actions\">\n @if (showDelete) {\n <button\n type=\"button\"\n class=\"action-btn delete\"\n (click)=\"onDelete()\"\n [disabled]=\"disabled\"\n title=\"Remove filter\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n }\n </div>\n</div>\n", styles: ["/* Scoped to mj-filter-rule to prevent style leakage with ViewEncapsulation.None */\nmj-filter-rule .filter-rule {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 12px 16px;\n background: linear-gradient(to bottom, #ffffff, #fafafa);\n border: 1px solid #e5e7eb;\n border-radius: 10px;\n transition: all 0.2s ease;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);\n}\n\nmj-filter-rule .filter-rule:hover {\n border-color: #d1d5db;\n background: linear-gradient(to bottom, #ffffff, #f5f5f5);\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.06);\n}\n\nmj-filter-rule .filter-rule.disabled {\n opacity: 0.6;\n pointer-events: none;\n box-shadow: none;\n}\n\n/* ========================================\n CUSTOM DROPDOWN STYLES\n ======================================== */\n\nmj-filter-rule .custom-dropdown {\n position: relative;\n min-width: 160px;\n}\n\nmj-filter-rule .custom-dropdown.field-dropdown {\n min-width: 180px;\n}\n\nmj-filter-rule .custom-dropdown.operator-dropdown {\n min-width: 160px;\n}\n\nmj-filter-rule .custom-dropdown.value-dropdown {\n min-width: 160px;\n flex: 1;\n}\n\nmj-filter-rule .custom-dropdown.disabled {\n opacity: 0.6;\n pointer-events: none;\n}\n\n/* Dropdown Trigger Button */\nmj-filter-rule .dropdown-trigger {\n display: flex;\n align-items: center;\n justify-content: space-between;\n width: 100%;\n padding: 10px 14px;\n border: 1px solid #d4d4d8;\n border-radius: 8px;\n font-size: 14px;\n font-weight: 500;\n background: white;\n color: #374151;\n cursor: pointer;\n transition: all 0.2s ease;\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);\n text-align: left;\n}\n\nmj-filter-rule .dropdown-trigger:hover:not(:disabled) {\n border-color: #a1a1aa;\n background: #fafafa;\n}\n\nmj-filter-rule .dropdown-trigger:focus {\n outline: none;\n border-color: #3b82f6;\n box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);\n background: white;\n}\n\nmj-filter-rule .dropdown-trigger:disabled {\n background-color: #f4f4f5;\n color: #a1a1aa;\n cursor: not-allowed;\n box-shadow: none;\n}\n\nmj-filter-rule .custom-dropdown.open .dropdown-trigger {\n border-color: #3b82f6;\n box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);\n}\n\nmj-filter-rule .dropdown-value {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\nmj-filter-rule .dropdown-arrow {\n font-size: 10px;\n color: #6b7280;\n margin-left: 8px;\n transition: transform 0.2s ease;\n}\n\nmj-filter-rule .custom-dropdown.open .dropdown-arrow {\n transform: rotate(180deg);\n}\n\n/* Dropdown Menu */\nmj-filter-rule .dropdown-menu {\n position: absolute;\n top: calc(100% + 4px);\n left: 0;\n right: 0;\n min-width: 100%;\n max-height: 280px;\n overflow-y: auto;\n background: white;\n border: 1px solid #e5e7eb;\n border-radius: 10px;\n box-shadow: 0 10px 40px rgba(0, 0, 0, 0.12), 0 2px 6px rgba(0, 0, 0, 0.04);\n z-index: 9999;\n animation: dropdownFadeIn 0.15s ease;\n}\n\n@keyframes dropdownFadeIn {\n from {\n opacity: 0;\n transform: translateY(-8px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n/* Dropdown Items */\nmj-filter-rule .dropdown-item {\n padding: 10px 14px;\n font-size: 14px;\n color: #374151;\n cursor: pointer;\n transition: all 0.15s ease;\n border-bottom: 1px solid #f3f4f6;\n}\n\nmj-filter-rule .dropdown-item:last-child {\n border-bottom: none;\n}\n\nmj-filter-rule .dropdown-item:hover,\nmj-filter-rule .dropdown-item.highlighted {\n background: #f0f9ff;\n color: #1d4ed8;\n}\n\nmj-filter-rule .dropdown-item.highlighted {\n outline: 2px solid #3b82f6;\n outline-offset: -2px;\n}\n\nmj-filter-rule .dropdown-item.selected {\n background: #eff6ff;\n color: #1d4ed8;\n font-weight: 600;\n}\n\nmj-filter-rule .dropdown-item.selected::before {\n content: '\\f00c';\n font-family: 'Font Awesome 6 Free';\n font-weight: 900;\n font-size: 10px;\n margin-right: 8px;\n color: #3b82f6;\n}\n\nmj-filter-rule .dropdown-empty {\n padding: 16px;\n text-align: center;\n color: #9ca3af;\n font-size: 13px;\n font-style: italic;\n}\n\n/* Scrollbar styling for dropdown */\nmj-filter-rule .dropdown-menu::-webkit-scrollbar {\n width: 6px;\n}\n\nmj-filter-rule .dropdown-menu::-webkit-scrollbar-track {\n background: #f1f1f1;\n border-radius: 3px;\n}\n\nmj-filter-rule .dropdown-menu::-webkit-scrollbar-thumb {\n background: #c1c1c1;\n border-radius: 3px;\n}\n\nmj-filter-rule .dropdown-menu::-webkit-scrollbar-thumb:hover {\n background: #a1a1a1;\n}\n\n/* ========================================\n VALUE INPUT STYLES\n ======================================== */\n\nmj-filter-rule .value-input {\n padding: 10px 14px;\n border: 1px solid #d4d4d8;\n border-radius: 8px;\n font-size: 14px;\n font-weight: 500;\n background-color: white;\n color: #374151;\n transition: all 0.2s ease;\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);\n flex: 1;\n min-width: 150px;\n}\n\nmj-filter-rule .value-input:hover:not(:disabled) {\n border-color: #a1a1aa;\n background-color: #fafafa;\n}\n\nmj-filter-rule .value-input:focus {\n outline: none;\n border-color: #3b82f6;\n box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);\n background-color: white;\n}\n\nmj-filter-rule .value-input:disabled {\n background-color: #f4f4f5;\n color: #a1a1aa;\n cursor: not-allowed;\n box-shadow: none;\n}\n\nmj-filter-rule .value-input::placeholder {\n color: #9ca3af;\n font-weight: 400;\n}\n\nmj-filter-rule .value-date {\n min-width: 160px;\n}\n\n/* ========================================\n BOOLEAN TOGGLE STYLES\n ======================================== */\n\nmj-filter-rule .value-toggle {\n display: flex;\n background: #f4f4f5;\n border-radius: 8px;\n padding: 3px;\n border: 1px solid #e5e7eb;\n}\n\nmj-filter-rule .toggle-btn {\n padding: 8px 18px;\n border: none;\n background: transparent;\n border-radius: 6px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.2s ease;\n color: #71717a;\n}\n\nmj-filter-rule .toggle-btn:hover:not(:disabled) {\n color: #3f3f46;\n background: rgba(255, 255, 255, 0.5);\n}\n\nmj-filter-rule .toggle-btn:disabled {\n cursor: not-allowed;\n opacity: 0.5;\n}\n\nmj-filter-rule .toggle-btn.active {\n background: white;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n color: #18181b;\n}\n\nmj-filter-rule .toggle-btn.true.active {\n color: #16a34a;\n}\n\nmj-filter-rule .toggle-btn.false.active {\n color: #dc2626;\n}\n\n/* ========================================\n RULE ACTIONS\n ======================================== */\n\nmj-filter-rule .rule-actions {\n display: flex;\n gap: 4px;\n margin-left: auto;\n flex-shrink: 0;\n}\n\nmj-filter-rule .action-btn {\n width: 32px;\n height: 32px;\n border: none;\n background: transparent;\n border-radius: 8px;\n cursor: pointer;\n color: #a1a1aa;\n transition: all 0.2s ease;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 14px;\n}\n\nmj-filter-rule .action-btn:hover:not(:disabled) {\n background: #f4f4f5;\n color: #71717a;\n}\n\nmj-filter-rule .action-btn:disabled {\n cursor: not-allowed;\n opacity: 0.3;\n}\n\nmj-filter-rule .action-btn.delete:hover:not(:disabled) {\n background: #fef2f2;\n color: #ef4444;\n}\n\n/* ========================================\n RESPONSIVE\n ======================================== */\n\n@media (max-width: 768px) {\n mj-filter-rule .filter-rule {\n flex-wrap: wrap;\n }\n\n mj-filter-rule .custom-dropdown,\n mj-filter-rule .value-input {\n min-width: 100%;\n flex: 1 1 100%;\n }\n\n mj-filter-rule .custom-dropdown.field-dropdown,\n mj-filter-rule .custom-dropdown.operator-dropdown,\n mj-filter-rule .custom-dropdown.value-dropdown {\n min-width: 100%;\n }\n}\n"] }]
665
+ }], () => [{ type: i0.ElementRef }], { filter: [{
666
+ type: Input
667
+ }], fields: [{
668
+ type: Input
669
+ }], disabled: [{
670
+ type: Input
671
+ }], showDelete: [{
672
+ type: Input
673
+ }], filterChange: [{
674
+ type: Output
675
+ }], delete: [{
676
+ type: Output
677
+ }], onDocumentClick: [{
678
+ type: HostListener,
679
+ args: ['document:click', ['$event']]
680
+ }] }); })();
681
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(FilterRuleComponent, { className: "FilterRuleComponent", filePath: "src/lib/filter-rule/filter-rule.component.ts", lineNumber: 22 }); })();
682
+ //# sourceMappingURL=filter-rule.component.js.map