@mk-kit/ui 0.36.0 → 0.38.0

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.
@@ -0,0 +1,276 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, input, numberAttribute, booleanAttribute, output, computed, ChangeDetectionStrategy, Component, forwardRef, model } from '@angular/core';
3
+ import { MK_I18N, mkIsQueryGroup, mkQueryOperatorsFor, mkQueryOperatorLabel, MK_QUERY_UNARY, mkCreateQueryRule, mkCreateQueryGroup } from '@mk-kit/ui/core';
4
+ import { MkButton } from '@mk-kit/ui/button';
5
+ import { MkIcon } from '@mk-kit/ui/icon';
6
+ import { MkInput, MkMultiSelect, MkNumberInput, MkSelect } from '@mk-kit/ui/forms';
7
+ import { MkDatePicker } from '@mk-kit/ui/datetime';
8
+
9
+ /** Format a Date as an ISO calendar date (`YYYY-MM-DD`, local). */
10
+ function toIsoDate(d) {
11
+ if (!d)
12
+ return null;
13
+ const pad = (n) => String(n).padStart(2, '0');
14
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
15
+ }
16
+ /** Parse an ISO calendar date back into a local Date (`null` when invalid). */
17
+ function fromIsoDate(v) {
18
+ if (v instanceof Date)
19
+ return v;
20
+ if (typeof v !== 'string' || !v)
21
+ return null;
22
+ const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(v);
23
+ if (!m) {
24
+ const d = new Date(v);
25
+ return Number.isNaN(d.getTime()) ? null : d;
26
+ }
27
+ return new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
28
+ }
29
+ /**
30
+ * One group of the builder — its combinator toggle, rules, nested groups and
31
+ * the add / remove actions. Used by `mk-query-builder`; every edit emits a
32
+ * new immutable group upward.
33
+ */
34
+ class MkQueryGroupComponent {
35
+ i18n = inject(MK_I18N);
36
+ group = input.required(/* @ts-ignore */
37
+ ...(ngDevMode ? [{ debugName: "group" }] : /* istanbul ignore next */ []));
38
+ fields = input.required(/* @ts-ignore */
39
+ ...(ngDevMode ? [{ debugName: "fields" }] : /* istanbul ignore next */ []));
40
+ depth = input(0, { ...(ngDevMode ? { debugName: "depth" } : /* istanbul ignore next */ {}), transform: numberAttribute });
41
+ maxDepth = input(3, { ...(ngDevMode ? { debugName: "maxDepth" } : /* istanbul ignore next */ {}), transform: numberAttribute });
42
+ allowNot = input(false, { ...(ngDevMode ? { debugName: "allowNot" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
43
+ disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
44
+ root = input(false, { ...(ngDevMode ? { debugName: "root" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
45
+ size = input('sm', /* @ts-ignore */
46
+ ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
47
+ /** A new version of this group after an edit. */
48
+ groupChange = output();
49
+ /** The user removed this (non-root) group. */
50
+ remove = output();
51
+ fieldOptions = computed(() => this.fields().map((f) => ({ label: f.label, value: f.key })), /* @ts-ignore */
52
+ ...(ngDevMode ? [{ debugName: "fieldOptions" }] : /* istanbul ignore next */ []));
53
+ canNest = computed(() => this.depth() < this.maxDepth(), /* @ts-ignore */
54
+ ...(ngDevMode ? [{ debugName: "canNest" }] : /* istanbul ignore next */ []));
55
+ booleanOptions = computed(() => [
56
+ { label: this.i18n.queryTrue, value: true },
57
+ { label: this.i18n.queryFalse, value: false },
58
+ ], /* @ts-ignore */
59
+ ...(ngDevMode ? [{ debugName: "booleanOptions" }] : /* istanbul ignore next */ []));
60
+ isGroup = mkIsQueryGroup;
61
+ fieldOf(rule) {
62
+ return this.fields().find((f) => f.key === rule.field);
63
+ }
64
+ typeOf(rule) {
65
+ return this.fieldOf(rule)?.type ?? 'string';
66
+ }
67
+ operatorOptions(rule) {
68
+ return mkQueryOperatorsFor(this.fieldOf(rule)).map((op) => ({
69
+ label: mkQueryOperatorLabel(op, this.i18n),
70
+ value: op,
71
+ }));
72
+ }
73
+ needsValue(rule) {
74
+ return !MK_QUERY_UNARY.has(rule.operator);
75
+ }
76
+ selectOptions(rule) {
77
+ return (this.fieldOf(rule)?.options ?? []).map((o) => ({ label: o.label, value: o.value }));
78
+ }
79
+ isList(rule) {
80
+ return rule.operator === 'in' || rule.operator === 'notIn';
81
+ }
82
+ isRange(rule) {
83
+ return rule.operator === 'between';
84
+ }
85
+ asArray(value) {
86
+ return Array.isArray(value) ? value : [];
87
+ }
88
+ rangePart(rule, index) {
89
+ return Array.isArray(rule.value) ? rule.value[index] : undefined;
90
+ }
91
+ dateValue(v) {
92
+ return fromIsoDate(v);
93
+ }
94
+ numberValue(v) {
95
+ return typeof v === 'number' ? v : v == null || v === '' ? null : Number(v);
96
+ }
97
+ textValue(v) {
98
+ return v == null ? '' : String(v);
99
+ }
100
+ // --- Edits (all immutable) --------------------------------------------------
101
+ setCombinator(combinator) {
102
+ if (this.group().combinator === combinator)
103
+ return;
104
+ this.groupChange.emit({ ...this.group(), combinator });
105
+ }
106
+ toggleNot() {
107
+ this.groupChange.emit({ ...this.group(), not: !this.group().not });
108
+ }
109
+ addRule() {
110
+ const field = this.fields()[0];
111
+ if (!field)
112
+ return;
113
+ this.groupChange.emit({ ...this.group(), rules: [...this.group().rules, mkCreateQueryRule(field)] });
114
+ }
115
+ addGroup() {
116
+ this.groupChange.emit({ ...this.group(), rules: [...this.group().rules, mkCreateQueryGroup()] });
117
+ }
118
+ removeAt(index) {
119
+ this.groupChange.emit({ ...this.group(), rules: this.group().rules.filter((_, i) => i !== index) });
120
+ }
121
+ replaceAt(index, node) {
122
+ this.groupChange.emit({ ...this.group(), rules: this.group().rules.map((n, i) => (i === index ? node : n)) });
123
+ }
124
+ setField(index, rule, key) {
125
+ const field = this.fields().find((f) => f.key === key);
126
+ if (!field || field.key === rule.field)
127
+ return;
128
+ const ops = mkQueryOperatorsFor(field);
129
+ const operator = ops.includes(rule.operator) ? rule.operator : ops[0];
130
+ this.replaceAt(index, { ...rule, field: field.key, operator, value: undefined });
131
+ }
132
+ setOperator(index, rule, op) {
133
+ const operator = op;
134
+ if (operator === rule.operator)
135
+ return;
136
+ const shapeChanged = MK_QUERY_UNARY.has(operator) !== MK_QUERY_UNARY.has(rule.operator) ||
137
+ (operator === 'between') !== (rule.operator === 'between') ||
138
+ this.isList({ ...rule, operator }) !== this.isList(rule);
139
+ this.replaceAt(index, { ...rule, operator, value: shapeChanged ? undefined : rule.value });
140
+ }
141
+ setValue(index, rule, value) {
142
+ this.replaceAt(index, { ...rule, value });
143
+ }
144
+ setRangePart(index, rule, part, value) {
145
+ const current = Array.isArray(rule.value) ? [...rule.value] : [undefined, undefined];
146
+ current[part] = value;
147
+ this.setValue(index, rule, current);
148
+ }
149
+ setDate(index, rule, d) {
150
+ this.setValue(index, rule, toIsoDate(d));
151
+ }
152
+ setDateRange(index, rule, part, d) {
153
+ this.setRangePart(index, rule, part, toIsoDate(d));
154
+ }
155
+ onTextInput(index, rule, event) {
156
+ this.setValue(index, rule, event.target.value);
157
+ }
158
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkQueryGroupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
159
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: MkQueryGroupComponent, isStandalone: true, selector: "mk-query-group", inputs: { group: { classPropertyName: "group", publicName: "group", isSignal: true, isRequired: true, transformFunction: null }, fields: { classPropertyName: "fields", publicName: "fields", isSignal: true, isRequired: true, transformFunction: null }, depth: { classPropertyName: "depth", publicName: "depth", isSignal: true, isRequired: false, transformFunction: null }, maxDepth: { classPropertyName: "maxDepth", publicName: "maxDepth", isSignal: true, isRequired: false, transformFunction: null }, allowNot: { classPropertyName: "allowNot", publicName: "allowNot", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, root: { classPropertyName: "root", publicName: "root", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { groupChange: "groupChange", remove: "remove" }, host: { attributes: { "role": "group" }, properties: { "class.mk-query-group--root": "root()", "class.mk-query-group--not": "!!group().not", "attr.aria-label": "root() ? null : i18n.queryAddGroup" }, classAttribute: "mk-query-group" }, ngImport: i0, template: "@let g = group();\n<div class=\"mk-query-group__bar\">\n <div class=\"mk-query-group__combinator\" role=\"group\" [attr.aria-label]=\"i18n.queryOperator\">\n <button\n type=\"button\"\n class=\"mk-query-group__toggle\"\n [class.mk-query-group__toggle--active]=\"g.combinator === 'and'\"\n [attr.aria-pressed]=\"g.combinator === 'and'\"\n [disabled]=\"disabled()\"\n (click)=\"setCombinator('and')\"\n >{{ i18n.queryAnd }}</button>\n <button\n type=\"button\"\n class=\"mk-query-group__toggle\"\n [class.mk-query-group__toggle--active]=\"g.combinator === 'or'\"\n [attr.aria-pressed]=\"g.combinator === 'or'\"\n [disabled]=\"disabled()\"\n (click)=\"setCombinator('or')\"\n >{{ i18n.queryOr }}</button>\n </div>\n @if (allowNot()) {\n <button\n type=\"button\"\n class=\"mk-query-group__toggle mk-query-group__toggle--not\"\n [class.mk-query-group__toggle--active]=\"!!g.not\"\n [attr.aria-pressed]=\"!!g.not\"\n [disabled]=\"disabled()\"\n (click)=\"toggleNot()\"\n >{{ i18n.queryNot }}</button>\n }\n <span class=\"mk-query-group__spacer\"></span>\n <button mkButton type=\"button\" variant=\"ghost\" tone=\"neutral\" [size]=\"size()\" [disabled]=\"disabled() || !fields().length\" (click)=\"addRule()\">\n <mk-icon name=\"plus\" size=\"sm\" /> {{ i18n.queryAddRule }}\n </button>\n @if (canNest()) {\n <button mkButton type=\"button\" variant=\"ghost\" tone=\"neutral\" [size]=\"size()\" [disabled]=\"disabled()\" (click)=\"addGroup()\">\n <mk-icon name=\"layers\" size=\"sm\" /> {{ i18n.queryAddGroup }}\n </button>\n }\n @if (!root()) {\n <button mkButton type=\"button\" variant=\"ghost\" tone=\"neutral\" [size]=\"size()\" class=\"mk-query-group__remove\" [disabled]=\"disabled()\" [attr.aria-label]=\"i18n.queryRemoveGroup\" (click)=\"remove.emit()\">\n <mk-icon name=\"trash\" size=\"sm\" />\n </button>\n }\n</div>\n\n<div class=\"mk-query-group__rules\">\n @if (!g.rules.length) {\n <p class=\"mk-query-group__empty\">{{ i18n.queryEmptyGroup }}</p>\n }\n @for (node of g.rules; track node.id; let i = $index) {\n @if (isGroup(node)) {\n <mk-query-group\n [group]=\"node\"\n [fields]=\"fields()\"\n [depth]=\"depth() + 1\"\n [maxDepth]=\"maxDepth()\"\n [allowNot]=\"allowNot()\"\n [disabled]=\"disabled()\"\n [size]=\"size()\"\n (groupChange)=\"replaceAt(i, $event)\"\n (remove)=\"removeAt(i)\"\n />\n } @else {\n <div class=\"mk-query-group__rule\" role=\"group\">\n <mk-select\n class=\"mk-query-group__field\"\n [options]=\"fieldOptions()\"\n [value]=\"node.field\"\n [size]=\"size()\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"i18n.queryField\"\n (valueChange)=\"setField(i, node, $event)\"\n />\n <mk-select\n class=\"mk-query-group__operator\"\n [options]=\"operatorOptions(node)\"\n [value]=\"node.operator\"\n [size]=\"size()\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"i18n.queryOperator\"\n (valueChange)=\"setOperator(i, node, $event)\"\n />\n @if (needsValue(node)) {\n @switch (typeOf(node)) {\n @case ('boolean') {\n <mk-select class=\"mk-query-group__value\" [options]=\"booleanOptions()\" [value]=\"node.value ?? null\" [size]=\"size()\" [disabled]=\"disabled()\" [attr.aria-label]=\"i18n.queryValue\" (valueChange)=\"setValue(i, node, $event)\" />\n }\n @case ('select') {\n @if (isList(node)) {\n <mk-multi-select class=\"mk-query-group__value\" [options]=\"selectOptions(node)\" [value]=\"asArray(node.value)\" [size]=\"size()\" [disabled]=\"disabled()\" [attr.aria-label]=\"i18n.queryValue\" (valueChange)=\"setValue(i, node, $event)\" />\n } @else {\n <mk-select class=\"mk-query-group__value\" [options]=\"selectOptions(node)\" [value]=\"node.value ?? null\" [size]=\"size()\" [disabled]=\"disabled()\" [attr.aria-label]=\"i18n.queryValue\" (valueChange)=\"setValue(i, node, $event)\" />\n }\n }\n @case ('number') {\n @if (isRange(node)) {\n <mk-number-input class=\"mk-query-group__value mk-query-group__value--half\" [value]=\"numberValue(rangePart(node, 0))\" [size]=\"size()\" [disabled]=\"disabled()\" [placeholder]=\"i18n.queryValueFrom\" [attr.aria-label]=\"i18n.queryValueFrom\" (valueChange)=\"setRangePart(i, node, 0, $event)\" />\n <mk-number-input class=\"mk-query-group__value mk-query-group__value--half\" [value]=\"numberValue(rangePart(node, 1))\" [size]=\"size()\" [disabled]=\"disabled()\" [placeholder]=\"i18n.queryValueTo\" [attr.aria-label]=\"i18n.queryValueTo\" (valueChange)=\"setRangePart(i, node, 1, $event)\" />\n } @else {\n <mk-number-input class=\"mk-query-group__value\" [value]=\"numberValue(node.value)\" [size]=\"size()\" [disabled]=\"disabled()\" [placeholder]=\"fieldOf(node)?.placeholder ?? ''\" [attr.aria-label]=\"i18n.queryValue\" (valueChange)=\"setValue(i, node, $event)\" />\n }\n }\n @case ('date') {\n @if (isRange(node)) {\n <mk-date-picker class=\"mk-query-group__value mk-query-group__value--half\" [value]=\"dateValue(rangePart(node, 0))\" [size]=\"size()\" [disabled]=\"disabled()\" [placeholder]=\"i18n.queryValueFrom\" (valueChange)=\"setDateRange(i, node, 0, $event)\" />\n <mk-date-picker class=\"mk-query-group__value mk-query-group__value--half\" [value]=\"dateValue(rangePart(node, 1))\" [size]=\"size()\" [disabled]=\"disabled()\" [placeholder]=\"i18n.queryValueTo\" (valueChange)=\"setDateRange(i, node, 1, $event)\" />\n } @else {\n <mk-date-picker class=\"mk-query-group__value\" [value]=\"dateValue(node.value)\" [size]=\"size()\" [disabled]=\"disabled()\" (valueChange)=\"setDate(i, node, $event)\" />\n }\n }\n @default {\n <input mkInput class=\"mk-query-group__value\" type=\"text\" [value]=\"textValue(node.value)\" [size]=\"size()\" [disabled]=\"disabled()\" [placeholder]=\"fieldOf(node)?.placeholder ?? ''\" [attr.aria-label]=\"i18n.queryValue\" (input)=\"onTextInput(i, node, $event)\" />\n }\n }\n }\n <button mkButton type=\"button\" variant=\"ghost\" tone=\"neutral\" [size]=\"size()\" class=\"mk-query-group__remove\" [disabled]=\"disabled()\" [attr.aria-label]=\"i18n.queryRemoveRule\" (click)=\"removeAt(i)\">\n <mk-icon name=\"close\" size=\"sm\" />\n </button>\n </div>\n }\n }\n</div>\n", styles: [":host{display:block;border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-lg);background:var(--mk-surface);color:var(--mk-text)}:host(:not(.mk-query-group--root)){background:var(--mk-surface-2);border-inline-start:3px solid var(--mk-primary)}:host(.mk-query-group--not){border-inline-start-color:var(--mk-danger)}.mk-query-group__bar{display:flex;flex-wrap:wrap;align-items:center;gap:var(--mk-space-2);padding:var(--mk-space-2) var(--mk-space-3);border-bottom:var(--mk-border-width) solid var(--mk-border)}.mk-query-group__spacer{flex:1 1 auto}.mk-query-group__combinator{display:inline-flex;border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-md);overflow:hidden}.mk-query-group__toggle{padding:var(--mk-space-1) var(--mk-space-3);border:0;background:transparent;color:var(--mk-text-muted);font:inherit;font-size:var(--mk-font-size-sm);font-weight:var(--mk-font-weight-semibold);cursor:pointer}.mk-query-group__toggle--active{background:var(--mk-primary);color:var(--mk-primary-contrast, #fff)}.mk-query-group__toggle--not{border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-md)}.mk-query-group__toggle--not.mk-query-group__toggle--active{background:var(--mk-danger);border-color:var(--mk-danger)}.mk-query-group__toggle:disabled{cursor:default;opacity:.6}.mk-query-group__toggle:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width))}.mk-query-group__rules{display:flex;flex-direction:column;gap:var(--mk-space-2);padding:var(--mk-space-3)}.mk-query-group__empty{margin:0;color:var(--mk-text-muted);font-size:var(--mk-font-size-sm)}.mk-query-group__rule{display:grid;grid-template-columns:minmax(8rem,1fr) minmax(8rem,1fr) minmax(10rem,2fr) auto;align-items:center;gap:var(--mk-space-2)}.mk-query-group__value{grid-column:3;min-width:0}.mk-query-group__value--half{grid-column:auto}.mk-query-group__rule:has(.mk-query-group__value--half){grid-template-columns:minmax(8rem,1fr) minmax(8rem,1fr) minmax(5rem,1fr) minmax(5rem,1fr) auto}.mk-query-group__rule>.mk-query-group__remove{grid-column:-2}@media(max-width:640px){.mk-query-group__rule,.mk-query-group__rule:has(.mk-query-group__value--half){grid-template-columns:1fr auto}.mk-query-group__value,.mk-query-group__value--half{grid-column:1}.mk-query-group__rule>.mk-query-group__remove{grid-column:2;grid-row:1}}\n"], dependencies: [{ kind: "component", type: i0.forwardRef(() => MkQueryGroupComponent), selector: "mk-query-group", inputs: ["group", "fields", "depth", "maxDepth", "allowNot", "disabled", "root", "size"], outputs: ["groupChange", "remove"] }, { kind: "component", type: i0.forwardRef(() => MkButton), selector: "button[mkButton], a[mkButton]", inputs: ["variant", "tone", "size", "loading", "fullWidth", "iconOnly", "disabled"] }, { kind: "component", type: i0.forwardRef(() => MkDatePicker), selector: "mk-date-picker", inputs: ["value", "min", "max", "disabledDate", "placeholder", "displayFormat", "disabled", "clearable", "invalid", "firstDayOfWeek", "size"], outputs: ["valueChange"] }, { kind: "component", type: i0.forwardRef(() => MkIcon), selector: "mk-icon", inputs: ["name", "size", "label"] }, { kind: "component", type: i0.forwardRef(() => MkInput), selector: "input[mkInput], textarea[mkInput]", inputs: ["size", "invalid"] }, { kind: "component", type: i0.forwardRef(() => MkMultiSelect), selector: "mk-multi-select", inputs: ["options", "placeholder", "size", "invalid", "disabled", "filterMode", "minChars", "loading", "max", "closeOnSelect", "emptyMessage", "value"], outputs: ["valueChange", "search", "optionAdded", "optionRemoved"] }, { kind: "component", type: i0.forwardRef(() => MkNumberInput), selector: "mk-number-input", inputs: ["min", "max", "step", "value", "placeholder", "disabled", "invalid", "size"], outputs: ["valueChange"] }, { kind: "component", type: i0.forwardRef(() => MkSelect), selector: "mk-select", inputs: ["options", "placeholder", "size", "invalid", "disabled", "value"], outputs: ["valueChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
160
+ }
161
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkQueryGroupComponent, decorators: [{
162
+ type: Component,
163
+ args: [{ selector: 'mk-query-group', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
164
+ forwardRef(() => MkQueryGroupComponent),
165
+ MkButton,
166
+ MkDatePicker,
167
+ MkIcon,
168
+ MkInput,
169
+ MkMultiSelect,
170
+ MkNumberInput,
171
+ MkSelect,
172
+ ], host: {
173
+ class: 'mk-query-group',
174
+ role: 'group',
175
+ '[class.mk-query-group--root]': 'root()',
176
+ '[class.mk-query-group--not]': '!!group().not',
177
+ '[attr.aria-label]': 'root() ? null : i18n.queryAddGroup',
178
+ }, template: "@let g = group();\n<div class=\"mk-query-group__bar\">\n <div class=\"mk-query-group__combinator\" role=\"group\" [attr.aria-label]=\"i18n.queryOperator\">\n <button\n type=\"button\"\n class=\"mk-query-group__toggle\"\n [class.mk-query-group__toggle--active]=\"g.combinator === 'and'\"\n [attr.aria-pressed]=\"g.combinator === 'and'\"\n [disabled]=\"disabled()\"\n (click)=\"setCombinator('and')\"\n >{{ i18n.queryAnd }}</button>\n <button\n type=\"button\"\n class=\"mk-query-group__toggle\"\n [class.mk-query-group__toggle--active]=\"g.combinator === 'or'\"\n [attr.aria-pressed]=\"g.combinator === 'or'\"\n [disabled]=\"disabled()\"\n (click)=\"setCombinator('or')\"\n >{{ i18n.queryOr }}</button>\n </div>\n @if (allowNot()) {\n <button\n type=\"button\"\n class=\"mk-query-group__toggle mk-query-group__toggle--not\"\n [class.mk-query-group__toggle--active]=\"!!g.not\"\n [attr.aria-pressed]=\"!!g.not\"\n [disabled]=\"disabled()\"\n (click)=\"toggleNot()\"\n >{{ i18n.queryNot }}</button>\n }\n <span class=\"mk-query-group__spacer\"></span>\n <button mkButton type=\"button\" variant=\"ghost\" tone=\"neutral\" [size]=\"size()\" [disabled]=\"disabled() || !fields().length\" (click)=\"addRule()\">\n <mk-icon name=\"plus\" size=\"sm\" /> {{ i18n.queryAddRule }}\n </button>\n @if (canNest()) {\n <button mkButton type=\"button\" variant=\"ghost\" tone=\"neutral\" [size]=\"size()\" [disabled]=\"disabled()\" (click)=\"addGroup()\">\n <mk-icon name=\"layers\" size=\"sm\" /> {{ i18n.queryAddGroup }}\n </button>\n }\n @if (!root()) {\n <button mkButton type=\"button\" variant=\"ghost\" tone=\"neutral\" [size]=\"size()\" class=\"mk-query-group__remove\" [disabled]=\"disabled()\" [attr.aria-label]=\"i18n.queryRemoveGroup\" (click)=\"remove.emit()\">\n <mk-icon name=\"trash\" size=\"sm\" />\n </button>\n }\n</div>\n\n<div class=\"mk-query-group__rules\">\n @if (!g.rules.length) {\n <p class=\"mk-query-group__empty\">{{ i18n.queryEmptyGroup }}</p>\n }\n @for (node of g.rules; track node.id; let i = $index) {\n @if (isGroup(node)) {\n <mk-query-group\n [group]=\"node\"\n [fields]=\"fields()\"\n [depth]=\"depth() + 1\"\n [maxDepth]=\"maxDepth()\"\n [allowNot]=\"allowNot()\"\n [disabled]=\"disabled()\"\n [size]=\"size()\"\n (groupChange)=\"replaceAt(i, $event)\"\n (remove)=\"removeAt(i)\"\n />\n } @else {\n <div class=\"mk-query-group__rule\" role=\"group\">\n <mk-select\n class=\"mk-query-group__field\"\n [options]=\"fieldOptions()\"\n [value]=\"node.field\"\n [size]=\"size()\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"i18n.queryField\"\n (valueChange)=\"setField(i, node, $event)\"\n />\n <mk-select\n class=\"mk-query-group__operator\"\n [options]=\"operatorOptions(node)\"\n [value]=\"node.operator\"\n [size]=\"size()\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"i18n.queryOperator\"\n (valueChange)=\"setOperator(i, node, $event)\"\n />\n @if (needsValue(node)) {\n @switch (typeOf(node)) {\n @case ('boolean') {\n <mk-select class=\"mk-query-group__value\" [options]=\"booleanOptions()\" [value]=\"node.value ?? null\" [size]=\"size()\" [disabled]=\"disabled()\" [attr.aria-label]=\"i18n.queryValue\" (valueChange)=\"setValue(i, node, $event)\" />\n }\n @case ('select') {\n @if (isList(node)) {\n <mk-multi-select class=\"mk-query-group__value\" [options]=\"selectOptions(node)\" [value]=\"asArray(node.value)\" [size]=\"size()\" [disabled]=\"disabled()\" [attr.aria-label]=\"i18n.queryValue\" (valueChange)=\"setValue(i, node, $event)\" />\n } @else {\n <mk-select class=\"mk-query-group__value\" [options]=\"selectOptions(node)\" [value]=\"node.value ?? null\" [size]=\"size()\" [disabled]=\"disabled()\" [attr.aria-label]=\"i18n.queryValue\" (valueChange)=\"setValue(i, node, $event)\" />\n }\n }\n @case ('number') {\n @if (isRange(node)) {\n <mk-number-input class=\"mk-query-group__value mk-query-group__value--half\" [value]=\"numberValue(rangePart(node, 0))\" [size]=\"size()\" [disabled]=\"disabled()\" [placeholder]=\"i18n.queryValueFrom\" [attr.aria-label]=\"i18n.queryValueFrom\" (valueChange)=\"setRangePart(i, node, 0, $event)\" />\n <mk-number-input class=\"mk-query-group__value mk-query-group__value--half\" [value]=\"numberValue(rangePart(node, 1))\" [size]=\"size()\" [disabled]=\"disabled()\" [placeholder]=\"i18n.queryValueTo\" [attr.aria-label]=\"i18n.queryValueTo\" (valueChange)=\"setRangePart(i, node, 1, $event)\" />\n } @else {\n <mk-number-input class=\"mk-query-group__value\" [value]=\"numberValue(node.value)\" [size]=\"size()\" [disabled]=\"disabled()\" [placeholder]=\"fieldOf(node)?.placeholder ?? ''\" [attr.aria-label]=\"i18n.queryValue\" (valueChange)=\"setValue(i, node, $event)\" />\n }\n }\n @case ('date') {\n @if (isRange(node)) {\n <mk-date-picker class=\"mk-query-group__value mk-query-group__value--half\" [value]=\"dateValue(rangePart(node, 0))\" [size]=\"size()\" [disabled]=\"disabled()\" [placeholder]=\"i18n.queryValueFrom\" (valueChange)=\"setDateRange(i, node, 0, $event)\" />\n <mk-date-picker class=\"mk-query-group__value mk-query-group__value--half\" [value]=\"dateValue(rangePart(node, 1))\" [size]=\"size()\" [disabled]=\"disabled()\" [placeholder]=\"i18n.queryValueTo\" (valueChange)=\"setDateRange(i, node, 1, $event)\" />\n } @else {\n <mk-date-picker class=\"mk-query-group__value\" [value]=\"dateValue(node.value)\" [size]=\"size()\" [disabled]=\"disabled()\" (valueChange)=\"setDate(i, node, $event)\" />\n }\n }\n @default {\n <input mkInput class=\"mk-query-group__value\" type=\"text\" [value]=\"textValue(node.value)\" [size]=\"size()\" [disabled]=\"disabled()\" [placeholder]=\"fieldOf(node)?.placeholder ?? ''\" [attr.aria-label]=\"i18n.queryValue\" (input)=\"onTextInput(i, node, $event)\" />\n }\n }\n }\n <button mkButton type=\"button\" variant=\"ghost\" tone=\"neutral\" [size]=\"size()\" class=\"mk-query-group__remove\" [disabled]=\"disabled()\" [attr.aria-label]=\"i18n.queryRemoveRule\" (click)=\"removeAt(i)\">\n <mk-icon name=\"close\" size=\"sm\" />\n </button>\n </div>\n }\n }\n</div>\n", styles: [":host{display:block;border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-lg);background:var(--mk-surface);color:var(--mk-text)}:host(:not(.mk-query-group--root)){background:var(--mk-surface-2);border-inline-start:3px solid var(--mk-primary)}:host(.mk-query-group--not){border-inline-start-color:var(--mk-danger)}.mk-query-group__bar{display:flex;flex-wrap:wrap;align-items:center;gap:var(--mk-space-2);padding:var(--mk-space-2) var(--mk-space-3);border-bottom:var(--mk-border-width) solid var(--mk-border)}.mk-query-group__spacer{flex:1 1 auto}.mk-query-group__combinator{display:inline-flex;border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-md);overflow:hidden}.mk-query-group__toggle{padding:var(--mk-space-1) var(--mk-space-3);border:0;background:transparent;color:var(--mk-text-muted);font:inherit;font-size:var(--mk-font-size-sm);font-weight:var(--mk-font-weight-semibold);cursor:pointer}.mk-query-group__toggle--active{background:var(--mk-primary);color:var(--mk-primary-contrast, #fff)}.mk-query-group__toggle--not{border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-md)}.mk-query-group__toggle--not.mk-query-group__toggle--active{background:var(--mk-danger);border-color:var(--mk-danger)}.mk-query-group__toggle:disabled{cursor:default;opacity:.6}.mk-query-group__toggle:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width))}.mk-query-group__rules{display:flex;flex-direction:column;gap:var(--mk-space-2);padding:var(--mk-space-3)}.mk-query-group__empty{margin:0;color:var(--mk-text-muted);font-size:var(--mk-font-size-sm)}.mk-query-group__rule{display:grid;grid-template-columns:minmax(8rem,1fr) minmax(8rem,1fr) minmax(10rem,2fr) auto;align-items:center;gap:var(--mk-space-2)}.mk-query-group__value{grid-column:3;min-width:0}.mk-query-group__value--half{grid-column:auto}.mk-query-group__rule:has(.mk-query-group__value--half){grid-template-columns:minmax(8rem,1fr) minmax(8rem,1fr) minmax(5rem,1fr) minmax(5rem,1fr) auto}.mk-query-group__rule>.mk-query-group__remove{grid-column:-2}@media(max-width:640px){.mk-query-group__rule,.mk-query-group__rule:has(.mk-query-group__value--half){grid-template-columns:1fr auto}.mk-query-group__value,.mk-query-group__value--half{grid-column:1}.mk-query-group__rule>.mk-query-group__remove{grid-column:2;grid-row:1}}\n"] }]
179
+ }], propDecorators: { group: [{ type: i0.Input, args: [{ isSignal: true, alias: "group", required: true }] }], fields: [{ type: i0.Input, args: [{ isSignal: true, alias: "fields", required: true }] }], depth: [{ type: i0.Input, args: [{ isSignal: true, alias: "depth", required: false }] }], maxDepth: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxDepth", required: false }] }], allowNot: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowNot", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], root: [{ type: i0.Input, args: [{ isSignal: true, alias: "root", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], groupChange: [{ type: i0.Output, args: ["groupChange"] }], remove: [{ type: i0.Output, args: ["remove"] }] } });
180
+
181
+ /**
182
+ * QueryBuilder — a rule / group tree the user assembles from your `fields`,
183
+ * held as plain JSON (`MkQueryGroup`) in the two-way `query` model. Pair it
184
+ * with `mkQueryToPredicate()` to filter rows in the browser, with
185
+ * `MkTableDataSource.setQuery()` to send it to the server, or with
186
+ * `mkQueryToText()` for a readable summary.
187
+ *
188
+ * ```html
189
+ * <mk-query-builder [fields]="fields" [(query)]="query" allowNot />
190
+ *
191
+ * fields: MkQueryField[] = [
192
+ * { key: 'name', label: 'Name' },
193
+ * { key: 'orders', label: 'Orders', type: 'number' },
194
+ * { key: 'status', label: 'Status', type: 'select', options: [{ label: 'Active', value: 'active' }, …] },
195
+ * { key: 'since', label: 'Customer since', type: 'date' },
196
+ * { key: 'vip', label: 'VIP', type: 'boolean' },
197
+ * ];
198
+ * rows = computed(() => this.all.filter(mkQueryToPredicate(this.query(), this.fields)));
199
+ * ```
200
+ */
201
+ class MkQueryBuilder {
202
+ i18n = inject(MK_I18N);
203
+ /** The filterable fields, in picker order. */
204
+ fields = input.required(/* @ts-ignore */
205
+ ...(ngDevMode ? [{ debugName: "fields" }] : /* istanbul ignore next */ []));
206
+ /** The query tree (two-way). Starts as an empty `and` group. */
207
+ query = model(mkCreateQueryGroup(), /* @ts-ignore */
208
+ ...(ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []));
209
+ /** How deep groups may nest (0 = rules only). */
210
+ maxDepth = input(3, { ...(ngDevMode ? { debugName: "maxDepth" } : /* istanbul ignore next */ {}), transform: numberAttribute });
211
+ /** Offer a *Not* toggle on every group. */
212
+ allowNot = input(false, { ...(ngDevMode ? { debugName: "allowNot" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
213
+ disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
214
+ /** Control size (default `sm`). */
215
+ size = input('sm', /* @ts-ignore */
216
+ ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
217
+ ariaLabel = input(undefined, /* @ts-ignore */
218
+ ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
219
+ /** Reset to an empty group. */
220
+ clear() {
221
+ this.query.set(mkCreateQueryGroup());
222
+ }
223
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkQueryBuilder, deps: [], target: i0.ɵɵFactoryTarget.Component });
224
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.7", type: MkQueryBuilder, isStandalone: true, selector: "mk-query-builder", inputs: { fields: { classPropertyName: "fields", publicName: "fields", isSignal: true, isRequired: true, transformFunction: null }, query: { classPropertyName: "query", publicName: "query", isSignal: true, isRequired: false, transformFunction: null }, maxDepth: { classPropertyName: "maxDepth", publicName: "maxDepth", isSignal: true, isRequired: false, transformFunction: null }, allowNot: { classPropertyName: "allowNot", publicName: "allowNot", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { query: "queryChange" }, host: { attributes: { "role": "group" }, properties: { "attr.aria-label": "ariaLabel() ?? i18n.queryBuilderLabel" }, styleAttribute: "display: block", classAttribute: "mk-query-builder" }, ngImport: i0, template: `
225
+ <mk-query-group
226
+ root
227
+ [group]="query()"
228
+ [fields]="fields()"
229
+ [depth]="0"
230
+ [maxDepth]="maxDepth()"
231
+ [allowNot]="allowNot()"
232
+ [disabled]="disabled()"
233
+ [size]="size()"
234
+ (groupChange)="query.set($event)"
235
+ />
236
+ `, isInline: true, dependencies: [{ kind: "component", type: MkQueryGroupComponent, selector: "mk-query-group", inputs: ["group", "fields", "depth", "maxDepth", "allowNot", "disabled", "root", "size"], outputs: ["groupChange", "remove"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
237
+ }
238
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkQueryBuilder, decorators: [{
239
+ type: Component,
240
+ args: [{
241
+ selector: 'mk-query-builder',
242
+ template: `
243
+ <mk-query-group
244
+ root
245
+ [group]="query()"
246
+ [fields]="fields()"
247
+ [depth]="0"
248
+ [maxDepth]="maxDepth()"
249
+ [allowNot]="allowNot()"
250
+ [disabled]="disabled()"
251
+ [size]="size()"
252
+ (groupChange)="query.set($event)"
253
+ />
254
+ `,
255
+ changeDetection: ChangeDetectionStrategy.OnPush,
256
+ imports: [MkQueryGroupComponent],
257
+ host: {
258
+ class: 'mk-query-builder',
259
+ role: 'group',
260
+ '[attr.aria-label]': 'ariaLabel() ?? i18n.queryBuilderLabel',
261
+ style: 'display: block',
262
+ },
263
+ }]
264
+ }], propDecorators: { fields: [{ type: i0.Input, args: [{ isSignal: true, alias: "fields", required: true }] }], query: [{ type: i0.Input, args: [{ isSignal: true, alias: "query", required: false }] }, { type: i0.Output, args: ["queryChange"] }], maxDepth: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxDepth", required: false }] }], allowNot: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowNot", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }] } });
265
+
266
+ /**
267
+ * @mk-kit/ui/query-builder — `mk-query-builder`, a rule / group tree editor
268
+ * producing an `MkQueryGroup` (types and `mkQueryToPredicate` live in core).
269
+ */
270
+
271
+ /**
272
+ * Generated bundle index. Do not edit.
273
+ */
274
+
275
+ export { MkQueryBuilder, MkQueryGroupComponent };
276
+ //# sourceMappingURL=mk-kit-ui-query-builder.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mk-kit-ui-query-builder.mjs","sources":["../../../projects/mk-kit/query-builder/query-group.ts","../../../projects/mk-kit/query-builder/query-group.html","../../../projects/mk-kit/query-builder/query-builder.ts","../../../projects/mk-kit/query-builder/index.ts","../../../projects/mk-kit/query-builder/mk-kit-ui-query-builder.ts"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n booleanAttribute,\n computed,\n forwardRef,\n inject,\n input,\n numberAttribute,\n output,\n} from '@angular/core';\nimport {\n MK_I18N,\n MK_QUERY_UNARY,\n mkCreateQueryGroup,\n mkCreateQueryRule,\n mkIsQueryGroup,\n mkQueryOperatorLabel,\n mkQueryOperatorsFor,\n type MkQueryCombinator,\n type MkQueryField,\n type MkQueryGroup,\n type MkQueryNode,\n type MkQueryOperator,\n type MkQueryRule,\n} from '@mk-kit/ui/core';\nimport type { MkSize } from '@mk-kit/ui/core';\nimport { MkButton } from '@mk-kit/ui/button';\nimport { MkIcon } from '@mk-kit/ui/icon';\nimport { MkInput, MkMultiSelect, MkNumberInput, MkSelect, type MkSelectOption } from '@mk-kit/ui/forms';\nimport { MkDatePicker } from '@mk-kit/ui/datetime';\n\n/** Format a Date as an ISO calendar date (`YYYY-MM-DD`, local). */\nfunction toIsoDate(d: Date | null): string | null {\n if (!d) return null;\n const pad = (n: number) => String(n).padStart(2, '0');\n return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;\n}\n\n/** Parse an ISO calendar date back into a local Date (`null` when invalid). */\nfunction fromIsoDate(v: unknown): Date | null {\n if (v instanceof Date) return v;\n if (typeof v !== 'string' || !v) return null;\n const m = /^(\\d{4})-(\\d{2})-(\\d{2})/.exec(v);\n if (!m) {\n const d = new Date(v);\n return Number.isNaN(d.getTime()) ? null : d;\n }\n return new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]));\n}\n\n/**\n * One group of the builder — its combinator toggle, rules, nested groups and\n * the add / remove actions. Used by `mk-query-builder`; every edit emits a\n * new immutable group upward.\n */\n@Component({\n selector: 'mk-query-group',\n templateUrl: './query-group.html',\n styleUrl: './query-group.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [\n forwardRef(() => MkQueryGroupComponent),\n MkButton,\n MkDatePicker,\n MkIcon,\n MkInput,\n MkMultiSelect,\n MkNumberInput,\n MkSelect,\n ],\n host: {\n class: 'mk-query-group',\n role: 'group',\n '[class.mk-query-group--root]': 'root()',\n '[class.mk-query-group--not]': '!!group().not',\n '[attr.aria-label]': 'root() ? null : i18n.queryAddGroup',\n },\n})\nexport class MkQueryGroupComponent {\n protected readonly i18n = inject(MK_I18N);\n\n readonly group = input.required<MkQueryGroup>();\n readonly fields = input.required<readonly MkQueryField[]>();\n readonly depth = input(0, { transform: numberAttribute });\n readonly maxDepth = input(3, { transform: numberAttribute });\n readonly allowNot = input(false, { transform: booleanAttribute });\n readonly disabled = input(false, { transform: booleanAttribute });\n readonly root = input(false, { transform: booleanAttribute });\n readonly size = input<MkSize>('sm');\n\n /** A new version of this group after an edit. */\n readonly groupChange = output<MkQueryGroup>();\n /** The user removed this (non-root) group. */\n readonly remove = output<void>();\n\n protected readonly fieldOptions = computed<MkSelectOption[]>(() =>\n this.fields().map((f) => ({ label: f.label, value: f.key })),\n );\n protected readonly canNest = computed(() => this.depth() < this.maxDepth());\n protected readonly booleanOptions = computed<MkSelectOption[]>(() => [\n { label: this.i18n.queryTrue, value: true },\n { label: this.i18n.queryFalse, value: false },\n ]);\n\n protected isGroup = mkIsQueryGroup;\n\n protected fieldOf(rule: MkQueryRule): MkQueryField | undefined {\n return this.fields().find((f) => f.key === rule.field);\n }\n\n protected typeOf(rule: MkQueryRule): string {\n return this.fieldOf(rule)?.type ?? 'string';\n }\n\n protected operatorOptions(rule: MkQueryRule): MkSelectOption[] {\n return mkQueryOperatorsFor(this.fieldOf(rule)).map((op) => ({\n label: mkQueryOperatorLabel(op, this.i18n),\n value: op,\n }));\n }\n\n protected needsValue(rule: MkQueryRule): boolean {\n return !MK_QUERY_UNARY.has(rule.operator);\n }\n\n protected selectOptions(rule: MkQueryRule): MkSelectOption[] {\n return (this.fieldOf(rule)?.options ?? []).map((o) => ({ label: o.label, value: o.value }));\n }\n\n protected isList(rule: MkQueryRule): boolean {\n return rule.operator === 'in' || rule.operator === 'notIn';\n }\n\n protected isRange(rule: MkQueryRule): boolean {\n return rule.operator === 'between';\n }\n\n protected asArray(value: unknown): unknown[] {\n return Array.isArray(value) ? value : [];\n }\n\n protected rangePart(rule: MkQueryRule, index: 0 | 1): unknown {\n return Array.isArray(rule.value) ? rule.value[index] : undefined;\n }\n\n protected dateValue(v: unknown): Date | null {\n return fromIsoDate(v);\n }\n\n protected numberValue(v: unknown): number | null {\n return typeof v === 'number' ? v : v == null || v === '' ? null : Number(v);\n }\n\n protected textValue(v: unknown): string {\n return v == null ? '' : String(v);\n }\n\n // --- Edits (all immutable) --------------------------------------------------\n\n protected setCombinator(combinator: MkQueryCombinator): void {\n if (this.group().combinator === combinator) return;\n this.groupChange.emit({ ...this.group(), combinator });\n }\n\n protected toggleNot(): void {\n this.groupChange.emit({ ...this.group(), not: !this.group().not });\n }\n\n protected addRule(): void {\n const field = this.fields()[0];\n if (!field) return;\n this.groupChange.emit({ ...this.group(), rules: [...this.group().rules, mkCreateQueryRule(field)] });\n }\n\n protected addGroup(): void {\n this.groupChange.emit({ ...this.group(), rules: [...this.group().rules, mkCreateQueryGroup()] });\n }\n\n protected removeAt(index: number): void {\n this.groupChange.emit({ ...this.group(), rules: this.group().rules.filter((_, i) => i !== index) });\n }\n\n protected replaceAt(index: number, node: MkQueryNode): void {\n this.groupChange.emit({ ...this.group(), rules: this.group().rules.map((n, i) => (i === index ? node : n)) });\n }\n\n protected setField(index: number, rule: MkQueryRule, key: unknown): void {\n const field = this.fields().find((f) => f.key === key);\n if (!field || field.key === rule.field) return;\n const ops = mkQueryOperatorsFor(field);\n const operator = ops.includes(rule.operator) ? rule.operator : ops[0];\n this.replaceAt(index, { ...rule, field: field.key, operator, value: undefined });\n }\n\n protected setOperator(index: number, rule: MkQueryRule, op: unknown): void {\n const operator = op as MkQueryOperator;\n if (operator === rule.operator) return;\n const shapeChanged =\n MK_QUERY_UNARY.has(operator) !== MK_QUERY_UNARY.has(rule.operator) ||\n (operator === 'between') !== (rule.operator === 'between') ||\n this.isList({ ...rule, operator }) !== this.isList(rule);\n this.replaceAt(index, { ...rule, operator, value: shapeChanged ? undefined : rule.value });\n }\n\n protected setValue(index: number, rule: MkQueryRule, value: unknown): void {\n this.replaceAt(index, { ...rule, value });\n }\n\n protected setRangePart(index: number, rule: MkQueryRule, part: 0 | 1, value: unknown): void {\n const current = Array.isArray(rule.value) ? [...rule.value] : [undefined, undefined];\n current[part] = value;\n this.setValue(index, rule, current);\n }\n\n protected setDate(index: number, rule: MkQueryRule, d: Date | null): void {\n this.setValue(index, rule, toIsoDate(d));\n }\n\n protected setDateRange(index: number, rule: MkQueryRule, part: 0 | 1, d: Date | null): void {\n this.setRangePart(index, rule, part, toIsoDate(d));\n }\n\n protected onTextInput(index: number, rule: MkQueryRule, event: Event): void {\n this.setValue(index, rule, (event.target as HTMLInputElement).value);\n }\n}\n","@let g = group();\n<div class=\"mk-query-group__bar\">\n <div class=\"mk-query-group__combinator\" role=\"group\" [attr.aria-label]=\"i18n.queryOperator\">\n <button\n type=\"button\"\n class=\"mk-query-group__toggle\"\n [class.mk-query-group__toggle--active]=\"g.combinator === 'and'\"\n [attr.aria-pressed]=\"g.combinator === 'and'\"\n [disabled]=\"disabled()\"\n (click)=\"setCombinator('and')\"\n >{{ i18n.queryAnd }}</button>\n <button\n type=\"button\"\n class=\"mk-query-group__toggle\"\n [class.mk-query-group__toggle--active]=\"g.combinator === 'or'\"\n [attr.aria-pressed]=\"g.combinator === 'or'\"\n [disabled]=\"disabled()\"\n (click)=\"setCombinator('or')\"\n >{{ i18n.queryOr }}</button>\n </div>\n @if (allowNot()) {\n <button\n type=\"button\"\n class=\"mk-query-group__toggle mk-query-group__toggle--not\"\n [class.mk-query-group__toggle--active]=\"!!g.not\"\n [attr.aria-pressed]=\"!!g.not\"\n [disabled]=\"disabled()\"\n (click)=\"toggleNot()\"\n >{{ i18n.queryNot }}</button>\n }\n <span class=\"mk-query-group__spacer\"></span>\n <button mkButton type=\"button\" variant=\"ghost\" tone=\"neutral\" [size]=\"size()\" [disabled]=\"disabled() || !fields().length\" (click)=\"addRule()\">\n <mk-icon name=\"plus\" size=\"sm\" /> {{ i18n.queryAddRule }}\n </button>\n @if (canNest()) {\n <button mkButton type=\"button\" variant=\"ghost\" tone=\"neutral\" [size]=\"size()\" [disabled]=\"disabled()\" (click)=\"addGroup()\">\n <mk-icon name=\"layers\" size=\"sm\" /> {{ i18n.queryAddGroup }}\n </button>\n }\n @if (!root()) {\n <button mkButton type=\"button\" variant=\"ghost\" tone=\"neutral\" [size]=\"size()\" class=\"mk-query-group__remove\" [disabled]=\"disabled()\" [attr.aria-label]=\"i18n.queryRemoveGroup\" (click)=\"remove.emit()\">\n <mk-icon name=\"trash\" size=\"sm\" />\n </button>\n }\n</div>\n\n<div class=\"mk-query-group__rules\">\n @if (!g.rules.length) {\n <p class=\"mk-query-group__empty\">{{ i18n.queryEmptyGroup }}</p>\n }\n @for (node of g.rules; track node.id; let i = $index) {\n @if (isGroup(node)) {\n <mk-query-group\n [group]=\"node\"\n [fields]=\"fields()\"\n [depth]=\"depth() + 1\"\n [maxDepth]=\"maxDepth()\"\n [allowNot]=\"allowNot()\"\n [disabled]=\"disabled()\"\n [size]=\"size()\"\n (groupChange)=\"replaceAt(i, $event)\"\n (remove)=\"removeAt(i)\"\n />\n } @else {\n <div class=\"mk-query-group__rule\" role=\"group\">\n <mk-select\n class=\"mk-query-group__field\"\n [options]=\"fieldOptions()\"\n [value]=\"node.field\"\n [size]=\"size()\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"i18n.queryField\"\n (valueChange)=\"setField(i, node, $event)\"\n />\n <mk-select\n class=\"mk-query-group__operator\"\n [options]=\"operatorOptions(node)\"\n [value]=\"node.operator\"\n [size]=\"size()\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"i18n.queryOperator\"\n (valueChange)=\"setOperator(i, node, $event)\"\n />\n @if (needsValue(node)) {\n @switch (typeOf(node)) {\n @case ('boolean') {\n <mk-select class=\"mk-query-group__value\" [options]=\"booleanOptions()\" [value]=\"node.value ?? null\" [size]=\"size()\" [disabled]=\"disabled()\" [attr.aria-label]=\"i18n.queryValue\" (valueChange)=\"setValue(i, node, $event)\" />\n }\n @case ('select') {\n @if (isList(node)) {\n <mk-multi-select class=\"mk-query-group__value\" [options]=\"selectOptions(node)\" [value]=\"asArray(node.value)\" [size]=\"size()\" [disabled]=\"disabled()\" [attr.aria-label]=\"i18n.queryValue\" (valueChange)=\"setValue(i, node, $event)\" />\n } @else {\n <mk-select class=\"mk-query-group__value\" [options]=\"selectOptions(node)\" [value]=\"node.value ?? null\" [size]=\"size()\" [disabled]=\"disabled()\" [attr.aria-label]=\"i18n.queryValue\" (valueChange)=\"setValue(i, node, $event)\" />\n }\n }\n @case ('number') {\n @if (isRange(node)) {\n <mk-number-input class=\"mk-query-group__value mk-query-group__value--half\" [value]=\"numberValue(rangePart(node, 0))\" [size]=\"size()\" [disabled]=\"disabled()\" [placeholder]=\"i18n.queryValueFrom\" [attr.aria-label]=\"i18n.queryValueFrom\" (valueChange)=\"setRangePart(i, node, 0, $event)\" />\n <mk-number-input class=\"mk-query-group__value mk-query-group__value--half\" [value]=\"numberValue(rangePart(node, 1))\" [size]=\"size()\" [disabled]=\"disabled()\" [placeholder]=\"i18n.queryValueTo\" [attr.aria-label]=\"i18n.queryValueTo\" (valueChange)=\"setRangePart(i, node, 1, $event)\" />\n } @else {\n <mk-number-input class=\"mk-query-group__value\" [value]=\"numberValue(node.value)\" [size]=\"size()\" [disabled]=\"disabled()\" [placeholder]=\"fieldOf(node)?.placeholder ?? ''\" [attr.aria-label]=\"i18n.queryValue\" (valueChange)=\"setValue(i, node, $event)\" />\n }\n }\n @case ('date') {\n @if (isRange(node)) {\n <mk-date-picker class=\"mk-query-group__value mk-query-group__value--half\" [value]=\"dateValue(rangePart(node, 0))\" [size]=\"size()\" [disabled]=\"disabled()\" [placeholder]=\"i18n.queryValueFrom\" (valueChange)=\"setDateRange(i, node, 0, $event)\" />\n <mk-date-picker class=\"mk-query-group__value mk-query-group__value--half\" [value]=\"dateValue(rangePart(node, 1))\" [size]=\"size()\" [disabled]=\"disabled()\" [placeholder]=\"i18n.queryValueTo\" (valueChange)=\"setDateRange(i, node, 1, $event)\" />\n } @else {\n <mk-date-picker class=\"mk-query-group__value\" [value]=\"dateValue(node.value)\" [size]=\"size()\" [disabled]=\"disabled()\" (valueChange)=\"setDate(i, node, $event)\" />\n }\n }\n @default {\n <input mkInput class=\"mk-query-group__value\" type=\"text\" [value]=\"textValue(node.value)\" [size]=\"size()\" [disabled]=\"disabled()\" [placeholder]=\"fieldOf(node)?.placeholder ?? ''\" [attr.aria-label]=\"i18n.queryValue\" (input)=\"onTextInput(i, node, $event)\" />\n }\n }\n }\n <button mkButton type=\"button\" variant=\"ghost\" tone=\"neutral\" [size]=\"size()\" class=\"mk-query-group__remove\" [disabled]=\"disabled()\" [attr.aria-label]=\"i18n.queryRemoveRule\" (click)=\"removeAt(i)\">\n <mk-icon name=\"close\" size=\"sm\" />\n </button>\n </div>\n }\n }\n</div>\n","import { ChangeDetectionStrategy, Component, booleanAttribute, inject, input, model, numberAttribute } from '@angular/core';\nimport { MK_I18N, mkCreateQueryGroup, type MkQueryField, type MkQueryGroup, type MkSize } from '@mk-kit/ui/core';\nimport { MkQueryGroupComponent } from './query-group';\n\n/**\n * QueryBuilder — a rule / group tree the user assembles from your `fields`,\n * held as plain JSON (`MkQueryGroup`) in the two-way `query` model. Pair it\n * with `mkQueryToPredicate()` to filter rows in the browser, with\n * `MkTableDataSource.setQuery()` to send it to the server, or with\n * `mkQueryToText()` for a readable summary.\n *\n * ```html\n * <mk-query-builder [fields]=\"fields\" [(query)]=\"query\" allowNot />\n *\n * fields: MkQueryField[] = [\n * { key: 'name', label: 'Name' },\n * { key: 'orders', label: 'Orders', type: 'number' },\n * { key: 'status', label: 'Status', type: 'select', options: [{ label: 'Active', value: 'active' }, …] },\n * { key: 'since', label: 'Customer since', type: 'date' },\n * { key: 'vip', label: 'VIP', type: 'boolean' },\n * ];\n * rows = computed(() => this.all.filter(mkQueryToPredicate(this.query(), this.fields)));\n * ```\n */\n@Component({\n selector: 'mk-query-builder',\n template: `\n <mk-query-group\n root\n [group]=\"query()\"\n [fields]=\"fields()\"\n [depth]=\"0\"\n [maxDepth]=\"maxDepth()\"\n [allowNot]=\"allowNot()\"\n [disabled]=\"disabled()\"\n [size]=\"size()\"\n (groupChange)=\"query.set($event)\"\n />\n `,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [MkQueryGroupComponent],\n host: {\n class: 'mk-query-builder',\n role: 'group',\n '[attr.aria-label]': 'ariaLabel() ?? i18n.queryBuilderLabel',\n style: 'display: block',\n },\n})\nexport class MkQueryBuilder {\n protected readonly i18n = inject(MK_I18N);\n\n /** The filterable fields, in picker order. */\n readonly fields = input.required<readonly MkQueryField[]>();\n /** The query tree (two-way). Starts as an empty `and` group. */\n readonly query = model<MkQueryGroup>(mkCreateQueryGroup());\n /** How deep groups may nest (0 = rules only). */\n readonly maxDepth = input(3, { transform: numberAttribute });\n /** Offer a *Not* toggle on every group. */\n readonly allowNot = input(false, { transform: booleanAttribute });\n readonly disabled = input(false, { transform: booleanAttribute });\n /** Control size (default `sm`). */\n readonly size = input<MkSize>('sm');\n readonly ariaLabel = input<string | undefined>(undefined);\n\n /** Reset to an empty group. */\n clear(): void {\n this.query.set(mkCreateQueryGroup());\n }\n}\n","/**\n * @mk-kit/ui/query-builder — `mk-query-builder`, a rule / group tree editor\n * producing an `MkQueryGroup` (types and `mkQueryToPredicate` live in core).\n */\nexport * from './query-builder';\nexport * from './query-group';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { inject, TemplateRef, Directive, input, ElementRef, Injector, PLATFORM_ID, DestroyRef, signal, effect, afterNextRender, booleanAttribute, numberAttribute, model, output, computed, viewChild, contentChild, contentChildren, ChangeDetectionStrategy, Component } from '@angular/core';
3
3
  import { DOCUMENT, isPlatformBrowser, NgTemplateOutlet } from '@angular/common';
4
- import { MkLiveAnnouncer, MK_I18N, mkUniqueId } from '@mk-kit/ui/core';
4
+ import { MkLiveAnnouncer, MK_I18N, mkUniqueId, mkQueryCompact } from '@mk-kit/ui/core';
5
5
  import { MkCheckbox } from '@mk-kit/ui/checkbox';
6
6
 
7
7
  /**
@@ -1400,6 +1400,8 @@ class MkTableDataSource {
1400
1400
  ...(ngDevMode ? [{ debugName: "_sort" }] : /* istanbul ignore next */ []));
1401
1401
  _filter = signal('', /* @ts-ignore */
1402
1402
  ...(ngDevMode ? [{ debugName: "_filter" }] : /* istanbul ignore next */ []));
1403
+ _query = signal(null, /* @ts-ignore */
1404
+ ...(ngDevMode ? [{ debugName: "_query" }] : /* istanbul ignore next */ []));
1403
1405
  /** Rows of the current page (`[]` until the first load lands). */
1404
1406
  rows = this._rows.asReadonly();
1405
1407
  /** Total row count across all pages (feed to `mk-pagination`'s `total`). */
@@ -1416,6 +1418,8 @@ class MkTableDataSource {
1416
1418
  sort = this._sort.asReadonly();
1417
1419
  /** Current filter query (updates immediately, even while debouncing). */
1418
1420
  filter = this._filter.asReadonly();
1421
+ /** Current structured query, or `null`. */
1422
+ query = this._query.asReadonly();
1419
1423
  /** True when a settled load reported no rows at all. */
1420
1424
  empty = computed(() => !this._loading() && this._total() === 0, /* @ts-ignore */
1421
1425
  ...(ngDevMode ? [{ debugName: "empty" }] : /* istanbul ignore next */ []));
@@ -1493,6 +1497,20 @@ class MkTableDataSource {
1493
1497
  this.load();
1494
1498
  }, this.debounceMs);
1495
1499
  }
1500
+ /**
1501
+ * Set (or clear with `null`) the structured query from `mk-query-builder`.
1502
+ * Empty groups and unfinished rules are dropped before the request; a query
1503
+ * with nothing left is sent as `null`. Resets to page 1 and loads at once.
1504
+ */
1505
+ setQuery(query) {
1506
+ const compact = query ? mkQueryCompact(query) : null;
1507
+ const next = compact && compact.rules.length ? compact : null;
1508
+ if (JSON.stringify(next) === JSON.stringify(this._query()))
1509
+ return;
1510
+ this._query.set(next);
1511
+ this._page.set(1);
1512
+ this.load();
1513
+ }
1496
1514
  /**
1497
1515
  * Re-run the current request immediately (e.g. after a mutation). Flushes a
1498
1516
  * pending debounced filter, since the request reads the live filter value.
@@ -1539,6 +1557,7 @@ class MkTableDataSource {
1539
1557
  pageSize: this._pageSize(),
1540
1558
  sort: this._sort(),
1541
1559
  filter: this._filter(),
1560
+ query: this._query(),
1542
1561
  };
1543
1562
  let result;
1544
1563
  try {