@yuuvis/client-framework 3.8.0 → 3.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/yuuvis-client-framework-forms.mjs +5 -1
- package/fesm2022/yuuvis-client-framework-forms.mjs.map +1 -1
- package/fesm2022/yuuvis-client-framework-metadata-form-defaults.mjs +2 -2
- package/fesm2022/yuuvis-client-framework-metadata-form-defaults.mjs.map +1 -1
- package/fesm2022/yuuvis-client-framework-object-details.mjs +47 -1
- package/fesm2022/yuuvis-client-framework-object-details.mjs.map +1 -1
- package/fesm2022/yuuvis-client-framework-smart-search.mjs +233 -19
- package/fesm2022/yuuvis-client-framework-smart-search.mjs.map +1 -1
- package/fesm2022/yuuvis-client-framework-tile-list.mjs +4 -0
- package/fesm2022/yuuvis-client-framework-tile-list.mjs.map +1 -1
- package/fesm2022/yuuvis-client-framework.mjs +245 -227
- package/fesm2022/yuuvis-client-framework.mjs.map +1 -1
- package/lib/assets/i18n/de.json +31 -24
- package/lib/assets/i18n/en.json +31 -24
- package/package.json +5 -5
- package/types/yuuvis-client-framework-smart-search.d.ts +114 -5
- package/types/yuuvis-client-framework.d.ts +1 -3
|
@@ -19,7 +19,9 @@ import { SystemService, TranslateService, BaseObjectTypeField, TranslatePipe } f
|
|
|
19
19
|
import { MetadataFormFieldComponent } from '@yuuvis/client-framework/metadata-form';
|
|
20
20
|
import { RendererDirective } from '@yuuvis/client-framework/renderer';
|
|
21
21
|
import { YmtIconButtonDirective } from '@yuuvis/material';
|
|
22
|
+
import { Subscription } from 'rxjs';
|
|
22
23
|
import { debounceTime } from 'rxjs/operators';
|
|
24
|
+
import { _ } from '@ngx-translate/core';
|
|
23
25
|
import { NgTemplateOutlet } from '@angular/common';
|
|
24
26
|
|
|
25
27
|
function isConditionGroup(node) {
|
|
@@ -147,6 +149,34 @@ function isValuelessOperator(operator) {
|
|
|
147
149
|
operator === 'empty' ||
|
|
148
150
|
operator === 'not_empty');
|
|
149
151
|
}
|
|
152
|
+
/**
|
|
153
|
+
* Whether a condition is an *unset placeholder*: it uses a value-requiring operator
|
|
154
|
+
* (eq / like / gt / …) but carries no value yet. Such conditions are kept in the tree
|
|
155
|
+
* — so a saved query can act as a fill-in template — but contribute nothing to the
|
|
156
|
+
* emitted CMIS query. An empty value is unambiguously "unset" here because null-checks
|
|
157
|
+
* have their own `empty` / `not_empty` operators (offered on every queryable type).
|
|
158
|
+
*/
|
|
159
|
+
function isConditionUnset(cond) {
|
|
160
|
+
return !isValuelessOperator(cond.operator) && !isValuePresent(cond.value);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Produce a copy of `blocks` with the values of the conditions in `overrides` replaced.
|
|
164
|
+
* Used by form mode to overlay the user's fill-out values onto the (untouched) template
|
|
165
|
+
* before building the query, so the saved template stays reusable. Conditions are matched
|
|
166
|
+
* by reference — the keys are the dynamic-condition references captured when form mode was
|
|
167
|
+
* entered. Returns the original array unchanged when there is nothing to overlay.
|
|
168
|
+
*/
|
|
169
|
+
function overlayConditionValues(blocks, overrides) {
|
|
170
|
+
if (overrides.size === 0)
|
|
171
|
+
return blocks;
|
|
172
|
+
const mapNode = (node) => {
|
|
173
|
+
if (isConditionGroup(node) || isTableCondition(node)) {
|
|
174
|
+
return { ...node, conditions: node.conditions.map(mapNode) };
|
|
175
|
+
}
|
|
176
|
+
return overrides.has(node) ? { ...node, value: overrides.get(node) } : node;
|
|
177
|
+
};
|
|
178
|
+
return blocks.map((block) => ({ ...block, conditions: block.conditions.map(mapNode) }));
|
|
179
|
+
}
|
|
150
180
|
/* eslint-disable id-length */
|
|
151
181
|
/**
|
|
152
182
|
* Calendar-day arithmetic. Using `new Date(y, m, d ± n)` (rather than
|
|
@@ -195,6 +225,10 @@ function datePresetToRange(preset) {
|
|
|
195
225
|
*/
|
|
196
226
|
function buildConditionClause(cond) {
|
|
197
227
|
const operator = cond.operator;
|
|
228
|
+
// Unset placeholders (value-requiring operator, no value yet) contribute nothing —
|
|
229
|
+
// this also guards against `LIKE '%%'`, `= ''` and `IN ()` for empty input.
|
|
230
|
+
if (isConditionUnset(cond))
|
|
231
|
+
return '';
|
|
198
232
|
// Valueless null checks short-circuit before any value handling.
|
|
199
233
|
if (operator === 'empty')
|
|
200
234
|
return `${cond.fieldId} IS NULL`;
|
|
@@ -462,6 +496,13 @@ function findOwningBlock(blocks, container) {
|
|
|
462
496
|
}
|
|
463
497
|
|
|
464
498
|
const DATE_PREFIX_LEN$1 = 5;
|
|
499
|
+
_('yuv.smart-search.date-preset.this-month');
|
|
500
|
+
_('yuv.smart-search.date-preset.this-week');
|
|
501
|
+
_('yuv.smart-search.date-preset.today');
|
|
502
|
+
_('yuv.smart-search.date-preset.this-year');
|
|
503
|
+
_('yuv.smart-search.operator.like');
|
|
504
|
+
_('yuv.smart-search.operator.empty');
|
|
505
|
+
_('yuv.smart-search.operator.not-empty');
|
|
465
506
|
/* eslint-disable id-length */
|
|
466
507
|
const OPERATOR_LABEL_KEYS = {
|
|
467
508
|
like: 'yuv.smart-search.operator.like',
|
|
@@ -536,6 +577,27 @@ class SmartSearchEditController {
|
|
|
536
577
|
this.inputTerm = signal('', ...(ngDevMode ? [{ debugName: "inputTerm" }] : /* istanbul ignore next */ []));
|
|
537
578
|
/** The whole-object full-text search unit (independent of the condition blocks). */
|
|
538
579
|
this.fulltext = signal({ term: '', scope: 'all', types: [] }, ...(ngDevMode ? [{ debugName: "fulltext" }] : /* istanbul ignore next */ []));
|
|
580
|
+
/** Whether the dynamic-conditions feature is enabled (mirrors the host `supportDynamicConditions` input). */
|
|
581
|
+
this.supportDynamic = signal(false, ...(ngDevMode ? [{ debugName: "supportDynamic" }] : /* istanbul ignore next */ []));
|
|
582
|
+
/**
|
|
583
|
+
* Form mode: replace the builder with a generated form of the user-marked dynamic
|
|
584
|
+
* conditions. View state only — the template ({@link blocks}) is never mutated; the
|
|
585
|
+
* form's values are overlaid onto the dynamic conditions to drive {@link cmisQuery},
|
|
586
|
+
* so the template stays reusable.
|
|
587
|
+
*/
|
|
588
|
+
this.formMode = signal(false, ...(ngDevMode ? [{ debugName: "formMode" }] : /* istanbul ignore next */ []));
|
|
589
|
+
/** The dynamic conditions surfaced as form rows; rebuilt on each {@link enterFormMode}. */
|
|
590
|
+
this.formFields = signal([], ...(ngDevMode ? [{ debugName: "formFields" }] : /* istanbul ignore next */ []));
|
|
591
|
+
/**
|
|
592
|
+
* The dynamic form rows grouped by their owning block (each carrying a muted header of
|
|
593
|
+
* target types); rebuilt on each {@link enterFormMode}. Blocks combine with `OR`
|
|
594
|
+
* ("as well as"), matching the builder.
|
|
595
|
+
*/
|
|
596
|
+
this.formBlocks = signal([], ...(ngDevMode ? [{ debugName: "formBlocks" }] : /* istanbul ignore next */ []));
|
|
597
|
+
/** Overlay values entered in the form, keyed by the dynamic condition's reference (refs are stable while form mode is active). */
|
|
598
|
+
this.#formValues = signal(new Map(), ...(ngDevMode ? [{ debugName: "#formValues" }] : /* istanbul ignore next */ []));
|
|
599
|
+
/** Whether any condition is marked dynamic (gates the form-mode toggle). */
|
|
600
|
+
this.hasDynamicConditions = computed(() => this.blocks().some((block) => this.#anyDynamic(block.conditions)), ...(ngDevMode ? [{ debugName: "hasDynamicConditions" }] : /* istanbul ignore next */ []));
|
|
539
601
|
this.objectTypes = computed(() => this.#system.getObjectTypes(true, 'search').filter((type) => this.allowedTypes().includes(type.id)), ...(ngDevMode ? [{ debugName: "objectTypes" }] : /* istanbul ignore next */ []));
|
|
540
602
|
/** Fields for the active top-level block, used by the field-step suggestions. */
|
|
541
603
|
this.activeBlockFields = computed(() => this.#sharedFields(this.activeBlock()?.types ?? []), ...(ngDevMode ? [{ debugName: "activeBlockFields" }] : /* istanbul ignore next */ []));
|
|
@@ -589,7 +651,12 @@ class SmartSearchEditController {
|
|
|
589
651
|
/** Number of top-level query units: the full-text unit (when it has a term) plus each block. */
|
|
590
652
|
this.unitCount = computed(() => (this.fulltext().term.trim() ? 1 : 0) + this.blocks().length, ...(ngDevMode ? [{ debugName: "unitCount" }] : /* istanbul ignore next */ []));
|
|
591
653
|
this.showCombinator = computed(() => this.unitCount() >= 2, ...(ngDevMode ? [{ debugName: "showCombinator" }] : /* istanbul ignore next */ []));
|
|
592
|
-
this.cmisQuery = computed(() =>
|
|
654
|
+
this.cmisQuery = computed(() => {
|
|
655
|
+
// In form mode, overlay the form's entered values onto the (untouched) template so
|
|
656
|
+
// the emitted query reflects the user's fill-out without mutating the saved blocks.
|
|
657
|
+
const blocks = this.formMode() ? overlayConditionValues(this.blocks(), this.#formValues()) : this.blocks();
|
|
658
|
+
return buildCmisQuery(blocks, this.combinator(), this.fulltext());
|
|
659
|
+
}, ...(ngDevMode ? [{ debugName: "cmisQuery" }] : /* istanbul ignore next */ []));
|
|
593
660
|
/** The active input control for the current step. */
|
|
594
661
|
this.activeCtrl = computed(() => {
|
|
595
662
|
switch (this.step()) {
|
|
@@ -610,6 +677,8 @@ class SmartSearchEditController {
|
|
|
610
677
|
#editingSnapshot;
|
|
611
678
|
/** Monotonic counter for generating stable block ids. */
|
|
612
679
|
#blockSeq;
|
|
680
|
+
/** Overlay values entered in the form, keyed by the dynamic condition's reference (refs are stable while form mode is active). */
|
|
681
|
+
#formValues;
|
|
613
682
|
// ── State save / restore ──────────────────────────────────────────────────
|
|
614
683
|
/** Deep-clone the current blocks, combinator and full-text unit into a serializable {@link SmartSearchState}. */
|
|
615
684
|
getState() {
|
|
@@ -622,6 +691,8 @@ class SmartSearchEditController {
|
|
|
622
691
|
/** Replace the current state with a saved one, normalizing legacy blocks and cancelling any in-progress edit. */
|
|
623
692
|
loadState(state) {
|
|
624
693
|
this.cancelPending();
|
|
694
|
+
// Form-mode overlay keys reference the old tree; drop form mode on reload.
|
|
695
|
+
this.exitFormMode();
|
|
625
696
|
this.blocks.set(state.blocks.map((block) => this.#normalizeBlock(block)));
|
|
626
697
|
this.combinator.set(state.combinator);
|
|
627
698
|
this.fulltext.set(state.fulltext ?? { term: '', scope: 'all', types: [] });
|
|
@@ -632,6 +703,82 @@ class SmartSearchEditController {
|
|
|
632
703
|
this.blocks.set([]);
|
|
633
704
|
this.combinator.set('OR');
|
|
634
705
|
this.fulltext.set({ term: '', scope: 'all', types: [] });
|
|
706
|
+
this.exitFormMode();
|
|
707
|
+
}
|
|
708
|
+
// ── Dynamic conditions + form mode ────────────────────────────────────────
|
|
709
|
+
/** Mark or unmark a condition as dynamic (immutably replaces the node in its container). */
|
|
710
|
+
setConditionDynamic(container, condition, dynamic) {
|
|
711
|
+
this.blocks.update((blocks) => updateContainer(blocks, container, (target) => withConditions(target, target.conditions.map((node) => (node === condition ? { ...condition, dynamic } : node)))));
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Enter form mode: collect every dynamic condition, resolve its value editor and seed a
|
|
715
|
+
* `FormControl` with its current value. The blocks themselves are left untouched — the
|
|
716
|
+
* form's edits are overlaid via {@link setFormValue} / {@link cmisQuery}.
|
|
717
|
+
*/
|
|
718
|
+
enterFormMode() {
|
|
719
|
+
if (!this.supportDynamic())
|
|
720
|
+
return;
|
|
721
|
+
const allFields = [];
|
|
722
|
+
const formBlocks = [];
|
|
723
|
+
for (const block of this.blocks()) {
|
|
724
|
+
const fields = [];
|
|
725
|
+
const walk = (nodes, table) => {
|
|
726
|
+
for (const node of nodes) {
|
|
727
|
+
if (isTableCondition(node))
|
|
728
|
+
walk(node.conditions, node);
|
|
729
|
+
else if (isConditionGroup(node))
|
|
730
|
+
walk(node.conditions, table);
|
|
731
|
+
else if (node.dynamic && !isValuelessOperator(node.operator)) {
|
|
732
|
+
const field = {
|
|
733
|
+
condition: node,
|
|
734
|
+
def: this.#resolveDynamicFieldDef(block, table, node.fieldId),
|
|
735
|
+
control: new FormControl(node.value)
|
|
736
|
+
};
|
|
737
|
+
fields.push(field);
|
|
738
|
+
allFields.push(field);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
};
|
|
742
|
+
walk(block.conditions, null);
|
|
743
|
+
// Only blocks that actually contribute a fillable row get a header in the form.
|
|
744
|
+
if (fields.length)
|
|
745
|
+
formBlocks.push({ block, fields });
|
|
746
|
+
}
|
|
747
|
+
this.formFields.set(allFields);
|
|
748
|
+
this.formBlocks.set(formBlocks);
|
|
749
|
+
this.#formValues.set(new Map());
|
|
750
|
+
this.formMode.set(true);
|
|
751
|
+
}
|
|
752
|
+
/** Leave form mode and discard the transient form state (the template is untouched). */
|
|
753
|
+
exitFormMode() {
|
|
754
|
+
this.formMode.set(false);
|
|
755
|
+
this.formFields.set([]);
|
|
756
|
+
this.formBlocks.set([]);
|
|
757
|
+
this.#formValues.set(new Map());
|
|
758
|
+
}
|
|
759
|
+
/** Toggle form mode (rebuilds the form rows on each entry). */
|
|
760
|
+
toggleFormMode() {
|
|
761
|
+
if (this.formMode())
|
|
762
|
+
this.exitFormMode();
|
|
763
|
+
else
|
|
764
|
+
this.enterFormMode();
|
|
765
|
+
}
|
|
766
|
+
/** Record a value entered in the form for a dynamic condition (drives the overlaid {@link cmisQuery}). */
|
|
767
|
+
setFormValue(condition, raw) {
|
|
768
|
+
this.#formValues.update((current) => new Map(current).set(condition, normalizeConditionValue(raw)));
|
|
769
|
+
}
|
|
770
|
+
/** Whether any condition in `nodes` (recursively) is marked dynamic and has a fillable value. */
|
|
771
|
+
#anyDynamic(nodes) {
|
|
772
|
+
return nodes.some((node) => isConditionGroup(node) || isTableCondition(node)
|
|
773
|
+
? this.#anyDynamic(node.conditions)
|
|
774
|
+
: !!node.dynamic && !isValuelessOperator(node.operator));
|
|
775
|
+
}
|
|
776
|
+
/** Resolve the value-editor field definition for a dynamic condition, honouring its table context. */
|
|
777
|
+
#resolveDynamicFieldDef(block, table, fieldId) {
|
|
778
|
+
if (table) {
|
|
779
|
+
return this.#tableColumns(table, block).find((col) => col.id === fieldId) ?? null;
|
|
780
|
+
}
|
|
781
|
+
return this.resolveFieldDefinition(block.types, fieldId);
|
|
635
782
|
}
|
|
636
783
|
// ── Full-text unit ────────────────────────────────────────────────────────
|
|
637
784
|
/** Set the full-text search term. */
|
|
@@ -891,17 +1038,16 @@ class SmartSearchEditController {
|
|
|
891
1038
|
return withConditions(target, conditions);
|
|
892
1039
|
}));
|
|
893
1040
|
}
|
|
894
|
-
/**
|
|
1041
|
+
/**
|
|
1042
|
+
* Whether the in-progress condition has all parts needed to be committed. A value
|
|
1043
|
+
* is *not* required: a field + operator with a blank value commits as an unset
|
|
1044
|
+
* placeholder (see {@link isConditionUnset}).
|
|
1045
|
+
*/
|
|
895
1046
|
isConditionComplete() {
|
|
896
1047
|
const field = this.pendingField();
|
|
897
1048
|
if (!field)
|
|
898
1049
|
return false;
|
|
899
|
-
|
|
900
|
-
if (!operator)
|
|
901
|
-
return false;
|
|
902
|
-
if (isValuelessOperator(operator))
|
|
903
|
-
return true;
|
|
904
|
-
return isValuePresent(this.valueCtrl.value);
|
|
1050
|
+
return (field.operator ?? '') !== '';
|
|
905
1051
|
}
|
|
906
1052
|
/** The operators available for a field's internal type, as autocomplete items. */
|
|
907
1053
|
operatorsForField(field) {
|
|
@@ -959,7 +1105,11 @@ class SmartSearchEditController {
|
|
|
959
1105
|
operator: operatorId,
|
|
960
1106
|
operatorLabel: operatorLabelText,
|
|
961
1107
|
value,
|
|
962
|
-
|
|
1108
|
+
// Omit the value segment for an unset placeholder so the chip/aria label reads
|
|
1109
|
+
// cleanly ("Name like") instead of carrying a trailing space.
|
|
1110
|
+
conditionLabel: valueLabel
|
|
1111
|
+
? `${field.label} ${operatorLabelText} ${valueLabel}`
|
|
1112
|
+
: `${field.label} ${operatorLabelText}`
|
|
963
1113
|
};
|
|
964
1114
|
}
|
|
965
1115
|
/**
|
|
@@ -1081,9 +1231,11 @@ class SmartSearchEditController {
|
|
|
1081
1231
|
return fields.filter((field) => (seen.has(field.id) ? false : seen.add(field.id) && true));
|
|
1082
1232
|
}
|
|
1083
1233
|
// ── Table columns ──────────────────────────────────────────────────────────
|
|
1084
|
-
/**
|
|
1085
|
-
|
|
1086
|
-
|
|
1234
|
+
/**
|
|
1235
|
+
* Resolve a table field's `columnDefinitions` into full `ObjectTypeField`s. Defaults to
|
|
1236
|
+
* the active block (the inline-edit case); form mode passes the owning block explicitly.
|
|
1237
|
+
*/
|
|
1238
|
+
#tableColumns(table, block = this.activeBlock()) {
|
|
1087
1239
|
if (!block)
|
|
1088
1240
|
return [];
|
|
1089
1241
|
const tableField = this.#resolveTypeField(block.types, table.fieldId);
|
|
@@ -1270,7 +1422,7 @@ class SmartSearchGroupComponent {
|
|
|
1270
1422
|
return node;
|
|
1271
1423
|
}
|
|
1272
1424
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: SmartSearchGroupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1273
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: SmartSearchGroupComponent, isStandalone: true, selector: "yuv-smart-search-group", inputs: { group: { classPropertyName: "group", publicName: "group", isSignal: true, isRequired: true, transformFunction: null }, bare: { classPropertyName: "bare", publicName: "bare", isSignal: true, isRequired: false, transformFunction: null }, chipTpl: { classPropertyName: "chipTpl", publicName: "chipTpl", isSignal: true, isRequired: true, transformFunction: null }, editorTpl: { classPropertyName: "editorTpl", publicName: "editorTpl", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "@if (bare()) {\n <ng-container *ngTemplateOutlet=\"body\" />\n} @else {\n <div class=\"group\" [class.group--active]=\"isActive()\" [class.group--table]=\"isTableContainer()\">\n @if (isTableContainer()) {\n <div class=\"group__header\">\n <span class=\"group__table-label\">{{ tableLabel() }}</span>\n <!-- Pseudo-operator: a table reads like a normal condition \u2014 \"Agent has \u2026\".\n It is the only operator a table offers, so it is applied implicitly. -->\n <span class=\"group__table-op\">{{ 'yuv.smart-search.operator.has' | translate }}</span>\n </div>\n }\n <div class=\"group__body\">\n <ng-container *ngTemplateOutlet=\"body\" />\n </div>\n </div>\n}\n\n<ng-template #body>\n @for (node of group().conditions; track node; let i = $index) {\n @if (isTable(node)) {\n
|
|
1425
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: SmartSearchGroupComponent, isStandalone: true, selector: "yuv-smart-search-group", inputs: { group: { classPropertyName: "group", publicName: "group", isSignal: true, isRequired: true, transformFunction: null }, bare: { classPropertyName: "bare", publicName: "bare", isSignal: true, isRequired: false, transformFunction: null }, chipTpl: { classPropertyName: "chipTpl", publicName: "chipTpl", isSignal: true, isRequired: true, transformFunction: null }, editorTpl: { classPropertyName: "editorTpl", publicName: "editorTpl", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "@if (bare()) {\n <ng-container *ngTemplateOutlet=\"body\" />\n} @else {\n <div class=\"group\" [class.group--active]=\"isActive()\" [class.group--table]=\"isTableContainer()\">\n @if (isTableContainer()) {\n <div class=\"group__header\">\n <span class=\"group__table-label\">{{ tableLabel() }}</span>\n <!-- Pseudo-operator: a table reads like a normal condition \u2014 \"Agent has \u2026\".\n It is the only operator a table offers, so it is applied implicitly. -->\n <span class=\"group__table-op\">{{ 'yuv.smart-search.operator.has' | translate }}</span>\n </div>\n }\n <div class=\"group__body\">\n <ng-container *ngTemplateOutlet=\"body\" />\n </div>\n </div>\n}\n\n<ng-template #body>\n @for (node of group().conditions; track node; let i = $index) {\n <div class=\"condition-row\">\n @if (isTable(node)) {\n <yuv-smart-search-group [group]=\"asTable(node)\" [chipTpl]=\"chipTpl()\" [editorTpl]=\"editorTpl()\" />\n } @else if (isGroup(node)) {\n <yuv-smart-search-group [group]=\"asGroup(node)\" [chipTpl]=\"chipTpl()\" [editorTpl]=\"editorTpl()\" />\n } @else {\n <ng-container\n *ngTemplateOutlet=\"chipTpl(); context: { $implicit: asCondition(node), container: group(), index: i }\"\n />\n }\n @if (!$last) {\n <div class=\"condition-combinator\">\n <!-- Table columns are row-scoped and always combine with OR (\"a row that\n has this as well as that\"), so they show a static label instead of a\n toggle. Groups and blocks keep their AND/OR toggle. -->\n @if (isTableContainer()) {\n <span class=\"combinator__label\">{{ 'yuv.smart-search.combinator.as-well-as' | translate }}</span>\n } @else {\n <button\n class=\"combinator__btn\"\n [class.combinator__btn--active]=\"containerCombinator() === 'AND'\"\n (click)=\"setCombinator('AND')\"\n >\n {{ 'yuv.smart-search.combinator.and' | translate }}\n </button>\n <button\n class=\"combinator__btn\"\n [class.combinator__btn--active]=\"containerCombinator() === 'OR'\"\n (click)=\"setCombinator('OR')\"\n >\n {{ 'yuv.smart-search.combinator.or' | translate }}\n </button>\n }\n </div>\n }\n </div>\n }\n\n @if (isActive()) {\n <ng-container *ngTemplateOutlet=\"editorTpl(); context: { $implicit: group() }\" />\n } @else {\n <div class=\"add-btns\">\n <button\n ymtIconButton\n icon-button-size=\"small\"\n class=\"add-condition-btn\"\n [matTooltip]=\"'yuv.smart-search.add-condition' | translate\"\n (click)=\"startAddCondition()\"\n >\n <mat-icon>add</mat-icon>\n </button>\n <!-- Tables hold column conditions directly \u2014 no grouping inside them. -->\n @if (!isTableContainer()) {\n <button\n ymtIconButton\n icon-button-size=\"small\"\n class=\"add-condition-btn\"\n [matTooltip]=\"'yuv.smart-search.add-group' | translate\"\n (click)=\"addGroup()\"\n >\n <mat-icon>data_array</mat-icon>\n </button>\n }\n </div>\n }\n</ng-template>\n", styles: [":host{display:contents}.group{flex:1;display:flex;flex-direction:column;gap:var(--ymt-spacing-2xs);padding:var(--ymt-spacing-2xs);margin:var(--ymt-spacing-2xs) 0;border-radius:var(--ymt-corner-xs);background:rgb(from var(--ymt-outline) r g b/.06);border:1px dashed var(--ymt-outline)}.group--active{border-color:var(--ymt-primary)}.group--table{border-style:solid}.group__header{display:flex;align-items:center;gap:var(--ymt-spacing-xs)}.group__table-label{font-weight:600;font-size:.85em;opacity:.85}.group__table-op{font:var(--ymt-font-body-subtle);color:var(--ymt-text-color-subtle)}.group__remove{margin-left:auto;opacity:.6}.group__remove:hover{opacity:1}.group__body{display:flex;flex-direction:column;align-items:stretch;gap:var(--ymt-spacing-2xs)}.condition-row{display:flex;align-items:center;gap:var(--ymt-spacing-xs)}.condition-row>:first-child{flex:1;min-width:0}.add-btns{display:flex;align-items:center;gap:var(--ymt-spacing-xs)}.add-condition-btn{color:var(--ymt-text-color-subtle)}.combinator{display:flex;justify-content:center;gap:0}.combinator__label{font:var(--ymt-font-body-subtle);text-transform:lowercase;color:var(--ymt-text-color-subtle);padding:var(--ymt-spacing-2xs) var(--ymt-spacing-xs)}.combinator__btn{--bg: transparent;--fg: var(--ymt-text-color);--mdc-shape-small: var(--ymt-corner-xs);font-size:.82em;background-color:var(--bg);color:var(--fg);padding:var(--ymt-spacing-2xs) var(--ymt-spacing-xs);border:1px solid var(--ymt-inverse-surface)}.combinator__btn:focus-visible{box-shadow:0 0 0 2px var(--focus-visible-border-shadow-color)}.combinator__btn:first-child{border-radius:var(--mdc-shape-small) 0 0 var(--mdc-shape-small)}.combinator__btn:last-child{border-radius:0 var(--mdc-shape-small) var(--mdc-shape-small) 0}.combinator__btn--active{--bg: var(--ymt-inverse-surface);--fg: var(--ymt-on-inverse-surface)}\n"], dependencies: [{ kind: "component", type: SmartSearchGroupComponent, selector: "yuv-smart-search-group", inputs: ["group", "bare", "chipTpl", "editorTpl"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "directive", type: YmtIconButtonDirective, selector: "button[ymtIconButton],button[ymt-icon-button],a[ymtIconButton],a[ymt-icon-button]", inputs: ["disabled", "disableRipple", "aria-disabled", "disabledInteractive", "icon-button-size"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
1274
1426
|
}
|
|
1275
1427
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: SmartSearchGroupComponent, decorators: [{
|
|
1276
1428
|
type: Component,
|
|
@@ -1282,7 +1434,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
1282
1434
|
MatTooltipModule,
|
|
1283
1435
|
YmtIconButtonDirective,
|
|
1284
1436
|
TranslatePipe
|
|
1285
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (bare()) {\n <ng-container *ngTemplateOutlet=\"body\" />\n} @else {\n <div class=\"group\" [class.group--active]=\"isActive()\" [class.group--table]=\"isTableContainer()\">\n @if (isTableContainer()) {\n <div class=\"group__header\">\n <span class=\"group__table-label\">{{ tableLabel() }}</span>\n <!-- Pseudo-operator: a table reads like a normal condition \u2014 \"Agent has \u2026\".\n It is the only operator a table offers, so it is applied implicitly. -->\n <span class=\"group__table-op\">{{ 'yuv.smart-search.operator.has' | translate }}</span>\n </div>\n }\n <div class=\"group__body\">\n <ng-container *ngTemplateOutlet=\"body\" />\n </div>\n </div>\n}\n\n<ng-template #body>\n @for (node of group().conditions; track node; let i = $index) {\n @if (isTable(node)) {\n
|
|
1437
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (bare()) {\n <ng-container *ngTemplateOutlet=\"body\" />\n} @else {\n <div class=\"group\" [class.group--active]=\"isActive()\" [class.group--table]=\"isTableContainer()\">\n @if (isTableContainer()) {\n <div class=\"group__header\">\n <span class=\"group__table-label\">{{ tableLabel() }}</span>\n <!-- Pseudo-operator: a table reads like a normal condition \u2014 \"Agent has \u2026\".\n It is the only operator a table offers, so it is applied implicitly. -->\n <span class=\"group__table-op\">{{ 'yuv.smart-search.operator.has' | translate }}</span>\n </div>\n }\n <div class=\"group__body\">\n <ng-container *ngTemplateOutlet=\"body\" />\n </div>\n </div>\n}\n\n<ng-template #body>\n @for (node of group().conditions; track node; let i = $index) {\n <div class=\"condition-row\">\n @if (isTable(node)) {\n <yuv-smart-search-group [group]=\"asTable(node)\" [chipTpl]=\"chipTpl()\" [editorTpl]=\"editorTpl()\" />\n } @else if (isGroup(node)) {\n <yuv-smart-search-group [group]=\"asGroup(node)\" [chipTpl]=\"chipTpl()\" [editorTpl]=\"editorTpl()\" />\n } @else {\n <ng-container\n *ngTemplateOutlet=\"chipTpl(); context: { $implicit: asCondition(node), container: group(), index: i }\"\n />\n }\n @if (!$last) {\n <div class=\"condition-combinator\">\n <!-- Table columns are row-scoped and always combine with OR (\"a row that\n has this as well as that\"), so they show a static label instead of a\n toggle. Groups and blocks keep their AND/OR toggle. -->\n @if (isTableContainer()) {\n <span class=\"combinator__label\">{{ 'yuv.smart-search.combinator.as-well-as' | translate }}</span>\n } @else {\n <button\n class=\"combinator__btn\"\n [class.combinator__btn--active]=\"containerCombinator() === 'AND'\"\n (click)=\"setCombinator('AND')\"\n >\n {{ 'yuv.smart-search.combinator.and' | translate }}\n </button>\n <button\n class=\"combinator__btn\"\n [class.combinator__btn--active]=\"containerCombinator() === 'OR'\"\n (click)=\"setCombinator('OR')\"\n >\n {{ 'yuv.smart-search.combinator.or' | translate }}\n </button>\n }\n </div>\n }\n </div>\n }\n\n @if (isActive()) {\n <ng-container *ngTemplateOutlet=\"editorTpl(); context: { $implicit: group() }\" />\n } @else {\n <div class=\"add-btns\">\n <button\n ymtIconButton\n icon-button-size=\"small\"\n class=\"add-condition-btn\"\n [matTooltip]=\"'yuv.smart-search.add-condition' | translate\"\n (click)=\"startAddCondition()\"\n >\n <mat-icon>add</mat-icon>\n </button>\n <!-- Tables hold column conditions directly \u2014 no grouping inside them. -->\n @if (!isTableContainer()) {\n <button\n ymtIconButton\n icon-button-size=\"small\"\n class=\"add-condition-btn\"\n [matTooltip]=\"'yuv.smart-search.add-group' | translate\"\n (click)=\"addGroup()\"\n >\n <mat-icon>data_array</mat-icon>\n </button>\n }\n </div>\n }\n</ng-template>\n", styles: [":host{display:contents}.group{flex:1;display:flex;flex-direction:column;gap:var(--ymt-spacing-2xs);padding:var(--ymt-spacing-2xs);margin:var(--ymt-spacing-2xs) 0;border-radius:var(--ymt-corner-xs);background:rgb(from var(--ymt-outline) r g b/.06);border:1px dashed var(--ymt-outline)}.group--active{border-color:var(--ymt-primary)}.group--table{border-style:solid}.group__header{display:flex;align-items:center;gap:var(--ymt-spacing-xs)}.group__table-label{font-weight:600;font-size:.85em;opacity:.85}.group__table-op{font:var(--ymt-font-body-subtle);color:var(--ymt-text-color-subtle)}.group__remove{margin-left:auto;opacity:.6}.group__remove:hover{opacity:1}.group__body{display:flex;flex-direction:column;align-items:stretch;gap:var(--ymt-spacing-2xs)}.condition-row{display:flex;align-items:center;gap:var(--ymt-spacing-xs)}.condition-row>:first-child{flex:1;min-width:0}.add-btns{display:flex;align-items:center;gap:var(--ymt-spacing-xs)}.add-condition-btn{color:var(--ymt-text-color-subtle)}.combinator{display:flex;justify-content:center;gap:0}.combinator__label{font:var(--ymt-font-body-subtle);text-transform:lowercase;color:var(--ymt-text-color-subtle);padding:var(--ymt-spacing-2xs) var(--ymt-spacing-xs)}.combinator__btn{--bg: transparent;--fg: var(--ymt-text-color);--mdc-shape-small: var(--ymt-corner-xs);font-size:.82em;background-color:var(--bg);color:var(--fg);padding:var(--ymt-spacing-2xs) var(--ymt-spacing-xs);border:1px solid var(--ymt-inverse-surface)}.combinator__btn:focus-visible{box-shadow:0 0 0 2px var(--focus-visible-border-shadow-color)}.combinator__btn:first-child{border-radius:var(--mdc-shape-small) 0 0 var(--mdc-shape-small)}.combinator__btn:last-child{border-radius:0 var(--mdc-shape-small) var(--mdc-shape-small) 0}.combinator__btn--active{--bg: var(--ymt-inverse-surface);--fg: var(--ymt-on-inverse-surface)}\n"] }]
|
|
1286
1438
|
}], propDecorators: { group: [{ type: i0.Input, args: [{ isSignal: true, alias: "group", required: true }] }], bare: [{ type: i0.Input, args: [{ isSignal: true, alias: "bare", required: false }] }], chipTpl: [{ type: i0.Input, args: [{ isSignal: true, alias: "chipTpl", required: true }] }], editorTpl: [{ type: i0.Input, args: [{ isSignal: true, alias: "editorTpl", required: true }] }] } });
|
|
1287
1439
|
|
|
1288
1440
|
const DEBOUNCE_MS = 150;
|
|
@@ -1354,6 +1506,13 @@ class SmartSearchComponent {
|
|
|
1354
1506
|
* table columns alike.
|
|
1355
1507
|
*/
|
|
1356
1508
|
this.skipProperties = input([], ...(ngDevMode ? [{ debugName: "skipProperties" }] : /* istanbul ignore next */ []));
|
|
1509
|
+
/**
|
|
1510
|
+
* Enables the **dynamic conditions** feature. When `true`, each committed condition can
|
|
1511
|
+
* be marked dynamic and a form-mode toggle appears that swaps the builder for a generated
|
|
1512
|
+
* fill-out form of those conditions. Off by default — the builder then behaves exactly as
|
|
1513
|
+
* it does without the feature.
|
|
1514
|
+
*/
|
|
1515
|
+
this.supportDynamicConditions = input(false, ...(ngDevMode ? [{ debugName: "supportDynamicConditions" }] : /* istanbul ignore next */ []));
|
|
1357
1516
|
/**
|
|
1358
1517
|
* Emits the current CMIS query string whenever the search changes. An empty
|
|
1359
1518
|
* string is emitted for an empty search (including the initial seed), so
|
|
@@ -1409,6 +1568,18 @@ class SmartSearchComponent {
|
|
|
1409
1568
|
this.fulltext = this.ctrl.fulltext;
|
|
1410
1569
|
this.objectTypes = this.ctrl.objectTypes;
|
|
1411
1570
|
this.activeBlockFields = this.ctrl.activeBlockFields;
|
|
1571
|
+
this.formMode = this.ctrl.formMode;
|
|
1572
|
+
this.formFields = this.ctrl.formFields;
|
|
1573
|
+
this.formBlocks = this.ctrl.formBlocks;
|
|
1574
|
+
this.hasDynamicConditions = this.ctrl.hasDynamicConditions;
|
|
1575
|
+
/** Whether a committed condition is an unset placeholder (drives the chip's "fill me" affordance). */
|
|
1576
|
+
this.isUnset = isConditionUnset;
|
|
1577
|
+
/**
|
|
1578
|
+
* Whether an operator carries its own meaning and has no value input (`is empty`,
|
|
1579
|
+
* `is not empty`, date presets). Such conditions can't be made dynamic — there is
|
|
1580
|
+
* nothing to fill out in the form.
|
|
1581
|
+
*/
|
|
1582
|
+
this.isValueless = isValuelessOperator;
|
|
1412
1583
|
/**
|
|
1413
1584
|
* Selected ids for the type multi-select. Falls back to the `ALL_TYPES`
|
|
1414
1585
|
* sentinel when no concrete type is picked (empty selection = no restriction).
|
|
@@ -1445,6 +1616,27 @@ class SmartSearchComponent {
|
|
|
1445
1616
|
effect(() => {
|
|
1446
1617
|
this.ctrl.skipProperties.set(this.skipProperties());
|
|
1447
1618
|
});
|
|
1619
|
+
// Mirror the dynamic-conditions opt-in into the controller; leaving the feature
|
|
1620
|
+
// disabled also forces form mode off.
|
|
1621
|
+
effect(() => {
|
|
1622
|
+
const enabled = this.supportDynamicConditions();
|
|
1623
|
+
this.ctrl.supportDynamic.set(enabled);
|
|
1624
|
+
if (!enabled)
|
|
1625
|
+
this.ctrl.exitFormMode();
|
|
1626
|
+
});
|
|
1627
|
+
// Bind each generated form row's control to the controller's overlay values. The
|
|
1628
|
+
// form-field set is rebuilt on every enterFormMode, so re-subscribe on change and
|
|
1629
|
+
// tear the previous subscriptions down via the effect's cleanup.
|
|
1630
|
+
effect((onCleanup) => {
|
|
1631
|
+
const fields = this.ctrl.formFields();
|
|
1632
|
+
const sub = new Subscription();
|
|
1633
|
+
for (const field of fields) {
|
|
1634
|
+
sub.add(field.control.valueChanges
|
|
1635
|
+
.pipe(debounceTime(DEBOUNCE_MS))
|
|
1636
|
+
.subscribe((val) => this.ctrl.setFormValue(field.condition, val)));
|
|
1637
|
+
}
|
|
1638
|
+
onCleanup(() => sub.unsubscribe());
|
|
1639
|
+
});
|
|
1448
1640
|
// Drive inputTerm from whichever control is active for the current step
|
|
1449
1641
|
this.ctrl.fieldCtrl.valueChanges.pipe(debounceTime(DEBOUNCE_MS), takeUntilDestroyed()).subscribe((val) => {
|
|
1450
1642
|
const step = this.ctrl.step();
|
|
@@ -1505,6 +1697,9 @@ class SmartSearchComponent {
|
|
|
1505
1697
|
/** Restore a previously {@link getState saved} search, replacing the current one. */
|
|
1506
1698
|
loadState(state) {
|
|
1507
1699
|
this.ctrl.loadState(state);
|
|
1700
|
+
// The full-text term input is a local control, not bound to the signal (scope and
|
|
1701
|
+
// types are). Sync it from the restored state so the term box reflects the load.
|
|
1702
|
+
this.fulltextTermCtrl.setValue(this.ctrl.fulltext().term, { emitEvent: false });
|
|
1508
1703
|
}
|
|
1509
1704
|
/** Clear the whole search: discard all blocks, conditions and the full-text term. */
|
|
1510
1705
|
clear() {
|
|
@@ -1512,6 +1707,26 @@ class SmartSearchComponent {
|
|
|
1512
1707
|
this.trigger()?.closePanel();
|
|
1513
1708
|
this.ctrl.reset();
|
|
1514
1709
|
}
|
|
1710
|
+
/**
|
|
1711
|
+
* Toggle form mode: swap the builder for a generated fill-out form of the user-marked
|
|
1712
|
+
* dynamic conditions. The template is left intact — the form's values are overlaid onto
|
|
1713
|
+
* the query. No-op unless {@link supportDynamicConditions} is set.
|
|
1714
|
+
*/
|
|
1715
|
+
toggleFormMode() {
|
|
1716
|
+
this.ctrl.toggleFormMode();
|
|
1717
|
+
}
|
|
1718
|
+
/** Enter form mode ("Essentials") — generated fill-out form of the dynamic conditions. */
|
|
1719
|
+
enterFormMode() {
|
|
1720
|
+
this.ctrl.enterFormMode();
|
|
1721
|
+
}
|
|
1722
|
+
/** Leave form mode ("Full Form") — back to the full builder search. */
|
|
1723
|
+
exitFormMode() {
|
|
1724
|
+
this.ctrl.exitFormMode();
|
|
1725
|
+
}
|
|
1726
|
+
/** Mark/unmark a committed condition as dynamic (surfaced in the fill-out form). */
|
|
1727
|
+
toggleDynamic(container, condition) {
|
|
1728
|
+
this.ctrl.setConditionDynamic(container, condition, !condition.dynamic);
|
|
1729
|
+
}
|
|
1515
1730
|
/**
|
|
1516
1731
|
* Set the top-level combinator joining the query units (the full-text unit and
|
|
1517
1732
|
* the type blocks). Kept for state round-tripping; the UI currently always
|
|
@@ -1677,9 +1892,8 @@ class SmartSearchComponent {
|
|
|
1677
1892
|
return;
|
|
1678
1893
|
if (this.ctrl.step() !== 'value')
|
|
1679
1894
|
return;
|
|
1895
|
+
// No value guard: a blank value commits an unset placeholder (fill-in template).
|
|
1680
1896
|
const raw = this.ctrl.valueCtrl.value;
|
|
1681
|
-
if (!isValuePresent(raw))
|
|
1682
|
-
return;
|
|
1683
1897
|
const field = this.ctrl.pendingField();
|
|
1684
1898
|
const fieldOp = field?.operator ?? '';
|
|
1685
1899
|
if (!field || !fieldOp)
|
|
@@ -1895,7 +2109,7 @@ class SmartSearchComponent {
|
|
|
1895
2109
|
this.#host.nativeElement.querySelector(`.block[data-block-id="${blockId}"] .add-condition-btn`)?.focus();
|
|
1896
2110
|
}
|
|
1897
2111
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: SmartSearchComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1898
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: SmartSearchComponent, isStandalone: true, selector: "yuv-smart-search", inputs: { types: { classPropertyName: "types", publicName: "types", isSignal: true, isRequired: false, transformFunction: null }, skipProperties: { classPropertyName: "skipProperties", publicName: "skipProperties", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { queryChange: "queryChange" }, providers: [SmartSearchEditController], viewQueries: [{ propertyName: "auto", first: true, predicate: ["auto"], descendants: true, isSignal: true }, { propertyName: "trigger", first: true, predicate: MatAutocompleteTrigger, descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"smart-search\" halo-container halo-container-skip=\"true\">\n <!-- Condition chip template \u2014 passed down into the recursive group component. -->\n <ng-template #chipTpl let-condition let-container=\"container\" let-i=\"index\">\n <div\n class=\"condition-chip\"\n tabindex=\"0\"\n role=\"button\"\n [attr.aria-label]=\"'yuv.smart-search.condition.edit-aria' | translate: { label: condition.conditionLabel }\"\n (click)=\"editCondition(container, condition, i)\"\n (keydown.space)=\"$event.preventDefault(); editCondition(container, condition, i)\"\n (keydown.enter)=\"$event.preventDefault(); editCondition(container, condition, i)\"\n >\n <span class=\"condition-chip__part condition-chip__field\">\n <!-- <mat-icon>tune</mat-icon> -->\n {{ condition.fieldLabel }}\n </span>\n <span class=\"condition-chip__part condition-chip__op\">{{ condition.operatorLabel }}</span>\n <span class=\"condition-chip__part condition-chip__value\">\n @if (valueRendererInput(condition); as rendererInput) {\n <ng-container *yuvRenderer=\"rendererInput\" />\n } @else {\n {{ condition.value }}\n }\n </span>\n <button\n ymtIconButton\n icon-button-size=\"extra-small\"\n class=\"condition-chip__remove\"\n tabindex=\"-1\"\n [attr.aria-label]=\"'yuv.smart-search.condition.remove' | translate\"\n (click)=\"$event.stopPropagation(); removeCondition(container, condition)\"\n >\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </ng-template>\n\n <!-- Inline editor template \u2014 rendered inside whichever container is active. -->\n <ng-template #editorTpl>\n <div class=\"inline-input\" tabindex=\"-1\" (focusout)=\"onInlineBlur($event)\" (keydown.escape)=\"cancelPending()\">\n @if (ctrl.step() !== 'field' && ctrl.pendingField()) {\n <button\n class=\"part-pill\"\n [class.part-pill--active]=\"ctrl.step() === 'field'\"\n (click)=\"editField()\"\n [matTooltip]=\"'yuv.smart-search.field.change' | translate\"\n >\n {{ ctrl.pendingField()?.label }}\n </button>\n }\n\n @if (ctrl.step() === 'value' && ctrl.operatorCtrl.value) {\n <button\n class=\"part-pill\"\n [class.part-pill--active]=\"ctrl.step() === 'operator'\"\n (click)=\"editOperator()\"\n [matTooltip]=\"'yuv.smart-search.operator.change' | translate\"\n >\n {{ ctrl.operatorCtrl.value }}\n </button>\n }\n\n @if (ctrl.step() === 'field') {\n <input\n [formControl]=\"ctrl.fieldCtrl\"\n [matAutocomplete]=\"auto\"\n [placeholder]=\"'yuv.smart-search.field.pick' | translate\"\n (keydown.enter)=\"onEnter()\"\n />\n } @else if (ctrl.step() === 'operator') {\n <input\n [formControl]=\"ctrl.operatorCtrl\"\n [matAutocomplete]=\"auto\"\n [placeholder]=\"'yuv.smart-search.operator.select' | translate\"\n (keydown.enter)=\"onEnter()\"\n />\n } @else if (ctrl.step() === 'value') {\n @let otf = valueFieldDef();\n @if (otf) {\n <yuv-metadata-form-field variant=\"raw\" [field]=\"otf\" situation=\"EDIT\" [formControl]=\"ctrl.valueCtrl\" />\n } @else {\n <input\n [formControl]=\"ctrl.valueCtrl\"\n [matAutocomplete]=\"auto\"\n [placeholder]=\"'yuv.smart-search.value.enter' | translate\"\n (keydown.enter)=\"onEnter()\"\n />\n }\n }\n\n <button\n ymtIconButton\n icon-button-size=\"extra-small\"\n class=\"confirm-btn\"\n [disabled]=\"!ctrl.isConditionComplete()\"\n (click)=\"onEnter()\"\n [matTooltip]=\"'yuv.smart-search.confirm' | translate\"\n >\n <mat-icon>check</mat-icon>\n </button>\n <button\n ymtIconButton\n icon-button-size=\"extra-small\"\n class=\"cancel-btn\"\n (click)=\"cancelPending()\"\n [matTooltip]=\"'yuv.smart-search.cancel' | translate\"\n >\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </ng-template>\n\n <!-- \u2500\u2500 Full-text search bar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"fulltext\" [class.muted]=\"!fulltext().term.trim() && ctrl.blocks().length\">\n <!-- Row 1: full-width term -->\n <div class=\"fulltext__term\">\n <mat-icon class=\"fulltext__icon\">search</mat-icon>\n <input\n class=\"fulltext__input\"\n [formControl]=\"fulltextTermCtrl\"\n [placeholder]=\"'yuv.smart-search.fulltext.placeholder' | translate\"\n />\n </div>\n\n <!-- Row 2: scope (single) + types (multiple) -->\n <div class=\"fulltext__filters\">\n <mat-select\n class=\"fulltext__scope\"\n [panelWidth]=\"null\"\n panelClass=\"smart-search-select-panel\"\n [value]=\"fulltext().scope\"\n (selectionChange)=\"setFulltextScope($event.value)\"\n [aria-label]=\"'yuv.smart-search.fulltext.scope.label' | translate\"\n >\n <mat-option value=\"all\">{{ 'yuv.smart-search.fulltext.scope.all' | translate }}</mat-option>\n <mat-option value=\"metadata\">{{ 'yuv.smart-search.fulltext.scope.metadata' | translate }}</mat-option>\n <mat-option value=\"content\">{{ 'yuv.smart-search.fulltext.scope.content' | translate }}</mat-option>\n </mat-select>\n\n <mat-select\n class=\"fulltext__types\"\n multiple\n [panelWidth]=\"null\"\n panelClass=\"smart-search-select-panel\"\n [value]=\"fulltextTypeSelection()\"\n (selectionChange)=\"onFulltextTypesChange($event.value)\"\n [aria-label]=\"'yuv.smart-search.fulltext.types.label' | translate\"\n >\n <mat-option [value]=\"ALL_TYPES\">{{ 'yuv.smart-search.fulltext.types.all' | translate }}</mat-option>\n @for (t of objectTypes(); track t.id) {\n <mat-option [value]=\"t.id\">{{ t.label ?? t.id }}</mat-option>\n }\n </mat-select>\n </div>\n </div>\n\n <!-- Joiner between the full-text unit and the condition blocks. Top-level units\n always combine with OR (\"looking for this as well as that\"), so this is a\n static label rather than a toggle. -->\n @if (fulltext().term.trim() && ctrl.blocks().length) {\n <div class=\"combinator\">\n <span class=\"combinator__label\">{{ 'yuv.smart-search.combinator.as-well-as' | translate }}</span>\n </div>\n }\n\n <!-- \u2500\u2500 Step 1: Type blocks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @for (block of ctrl.blocks(); track block.id) {\n <div class=\"block\" [attr.data-block-id]=\"block.id\" [class.block--active]=\"ctrl.activeBlock() === block\">\n <div class=\"block__header\">\n <div class=\"block__types\">\n @for (t of block.types; track t.id) {\n @if (!$first) {\n <span class=\"block__type-sep\">{{ 'yuv.smart-search.combinator.or' | translate }}</span>\n }\n <span class=\"block__type\">\n <span class=\"block__type-label\">{{ t.label }}</span>\n </span>\n }\n </div>\n <button\n ymtIconButton\n icon-button-size=\"extra-small\"\n class=\"block__remove\"\n (click)=\"removeBlock(block)\"\n [matTooltip]=\"'yuv.smart-search.type.remove' | translate\"\n >\n <mat-icon>close</mat-icon>\n </button>\n </div>\n\n <div class=\"block__conditions\">\n <yuv-smart-search-group [group]=\"block\" [bare]=\"true\" [chipTpl]=\"chipTpl\" [editorTpl]=\"editorTpl\" />\n </div>\n </div>\n\n <!-- Static joiner between blocks \u2014 blocks are type-scoped, so they always\n combine with OR (\"this type as well as that type\"). -->\n @if (!$last && ctrl.showCombinator()) {\n <div class=\"combinator\">\n <span class=\"combinator__label\">{{ 'yuv.smart-search.combinator.as-well-as' | translate }}</span>\n </div>\n }\n }\n\n <!-- \u2500\u2500 Step 1 input: add a type block (multi-select) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (ctrl.step() === 'type') {\n <div class=\"add-type-row\">\n <button\n type=\"button\"\n ymtIconButton\n icon-button-size=\"extra-small\"\n class=\"add-type-row__icon\"\n (click)=\"typeInput.focus()\"\n [attr.aria-label]=\"'yuv.smart-search.add-type' | translate\"\n >\n <mat-icon>add_circle_outline</mat-icon>\n </button>\n\n <!-- Staged draft types as removable Material chips -->\n <mat-chip-grid\n #typeChipGrid\n class=\"add-type-row__chips\"\n [attr.aria-label]=\"'yuv.smart-search.add-type' | translate\"\n >\n @for (t of ctrl.draftTypes(); track t.id) {\n <mat-chip-row (removed)=\"ctrl.removeDraftType(t.id)\">\n {{ t.label }}\n <button matChipRemove [attr.aria-label]=\"'yuv.smart-search.type.remove-draft' | translate\">\n <mat-icon>close</mat-icon>\n </button>\n </mat-chip-row>\n }\n <input\n #typeInput\n [formControl]=\"ctrl.fieldCtrl\"\n [placeholder]=\"'yuv.smart-search.add-type' | translate\"\n [matAutocomplete]=\"auto\"\n [matChipInputFor]=\"typeChipGrid\"\n (keydown.enter)=\"onTypeEnter()\"\n (keydown.escape)=\"cancelPending()\"\n />\n </mat-chip-grid>\n\n @if (ctrl.draftTypes().length) {\n <button\n ymtIconButton\n icon-button-size=\"extra-small\"\n class=\"add-type-row__confirm\"\n (click)=\"confirmTypes()\"\n [matTooltip]=\"'yuv.smart-search.type.confirm' | translate\"\n >\n <mat-icon>check</mat-icon>\n </button>\n }\n </div>\n }\n\n <!-- \u2500\u2500 Shared autocomplete panel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <mat-autocomplete\n #auto\n panelWidth=\"auto\"\n [displayWith]=\"displayFn\"\n (optionSelected)=\"onSuggestionSelected($event)\"\n (closed)=\"onPickerClosed()\"\n >\n @for (s of ctrl.suggestions(); track s.id) {\n <mat-option [value]=\"s\">\n <div class=\"suggestion\">\n @if (s.kind !== 'type') {\n <mat-icon class=\"suggestion__icon\">\n @switch (s.kind) {\n @case ('field') {\n tune\n }\n @case ('date-preset') {\n calendar_today\n }\n @default {\n manage_search\n }\n }\n </mat-icon>\n }\n <span class=\"suggestion__label\">{{ s.label }}</span>\n </div>\n </mat-option>\n }\n </mat-autocomplete>\n</div>\n", styles: [":host{display:block;--outline: rgb(from var(--ymt-text-color) r g b / .5);--focus-visible-border-color: var(--ymt-primary);--focus-visible-border-shadow-color: rgb(from var(--ymt-primary) r g b / .3)}::ng-deep .smart-search-select-panel.mat-mdc-select-panel{min-width:max-content}::ng-deep .smart-search-select-panel.mat-mdc-select-panel .mat-mdc-option .mdc-list-item__primary-text{white-space:nowrap}.smart-search{display:flex;flex-direction:column;gap:var(--ymt-spacing-xs)}.fulltext{display:flex;flex-direction:column;gap:var(--ymt-spacing-xs);padding:var(--ymt-spacing-xs) var(--ymt-spacing-s);border:1px solid var(--outline);border-radius:var(--ymt-corner-s);transition:opacity .15s}.fulltext.muted{opacity:.7}.fulltext.muted:hover,.fulltext.muted:focus-within{opacity:1}.fulltext__term{display:flex;align-items:center;gap:var(--ymt-spacing-xs)}.fulltext__icon{color:var(--ymt-text-color-subtle)}.fulltext__input{flex:1;min-width:120px;border:none;outline:none;background:transparent;font:inherit;color:var(--ymt-text-color)}.fulltext__input::placeholder{color:var(--ymt-text-color-subtle);opacity:.7}.fulltext__filters{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:var(--ymt-spacing-s)}.fulltext__scope,.fulltext__types{width:auto;font-size:.9em;color:var(--ymt-text-color-subtle);border:1px solid var(--ymt-outline);border-radius:var(--ymt-corner-xs);padding-inline:var(--ymt-spacing-xs)}.fulltext__scope:focus-visible,.fulltext__types:focus-visible{border-color:var(--focus-visible-border-color);box-shadow:0 0 0 2px var(--focus-visible-border-shadow-color)}.block{border:1px solid var(--outline);border-radius:var(--ymt-corner-s);overflow:hidden;transition:border-color .15s;padding:var(--ymt-spacing-2xs)}.block:focus-within{border-color:var(--focus-visible-border-color);box-shadow:0 0 0 3px var(--focus-visible-border-shadow-color)}.block__header{border-radius:var(--ymt-corner-xs);display:flex;flex-wrap:wrap;align-items:center;gap:var(--ymt-spacing-xs);padding:var(--ymt-spacing-xs) var(--ymt-spacing-s);background:var(--ymt-surface-container-high)}.block__types{flex:1;display:flex;align-items:center;flex-wrap:wrap}.block__type{display:inline-flex;align-items:center;gap:var(--ymt-spacing-2xs)}.block__type-sep{font:var(--ymt-font-body-subtle);text-transform:lowercase;color:var(--ymt-text-color-subtle);padding:0 var(--ymt-spacing-xs)}.block__remove{margin-inline-start:auto;opacity:.7}.block__conditions{display:flex;flex-wrap:wrap;align-items:center;row-gap:var(--ymt-spacing-2xs);column-gap:var(--ymt-spacing-xs);padding:var(--ymt-spacing-xs) var(--ymt-spacing-s);min-height:44px}.condition-chip{display:inline-flex;align-items:center;gap:0;border-radius:var(--ymt-corner-xs);border:1px solid var(--outline);cursor:pointer;overflow:hidden;outline:none;padding:var(--ymt-spacing-4xs) var(--ymt-spacing-3xs);gap:var(--ymt-spacing-4xs)}.condition-chip:hover{border-color:var(--ymt-outline)}.condition-chip:focus-visible{border-color:var(--focus-visible-border-color);box-shadow:0 0 0 2px var(--focus-visible-border-shadow-color)}.condition-chip__part{display:inline-flex;align-items:center;gap:var(--ymt-spacing-4xs);padding:0 var(--ymt-spacing-xs);height:100%;white-space:nowrap;color:var(--ymt-text-color)}.condition-chip__part:focus-visible{border-color:var(--focus-visible-border-color);box-shadow:0 0 0 2px var(--focus-visible-border-shadow-color)}.condition-chip__op{font:var(--ymt-font-body-subtle);color:var(--ymt-text-color-subtle);padding:0}.condition-chip__value{max-width:120px;overflow:hidden;text-overflow:ellipsis;--tile-slot-padding: 0;--yuv-renderer-display: inline-flex}.condition-chip__remove{color:var(--ymt-text-color-subtle)}.condition-chip__remove:hover{color:var(--ymt-text-color)}.inline-input{display:flex;align-items:center;gap:var(--ymt-spacing-3xs);flex:1;border:1px dashed var(--outline);border-radius:var(--ymt-corner-xs);padding:var(--ymt-spacing-4xs) var(--ymt-spacing-2xs);background:var(--ymt-surface)}.inline-input .part-pill{display:inline-flex;align-items:center;gap:var(--ymt-spacing-4xs);white-space:nowrap;font:inherit;font-size:var(--ymt-font-body-subtle-size);color:var(--ymt-text-color);background:var(--ymt-surface-container);border:1px solid transparent;border-radius:var(--ymt-corner-xs);padding:var(--ymt-spacing-4xs) var(--ymt-spacing-xs);cursor:pointer;transition:border-color .1s,background .1s}.inline-input .part-pill:hover{background:var(--ymt-surface-container-high)}.inline-input .part-pill:focus-visible{border-color:var(--focus-visible-border-color);box-shadow:0 0 0 2px var(--focus-visible-border-shadow-color)}.inline-input .part-pill--active{border-color:var(--ymt-primary);box-shadow:0 0 0 2px var(--focus-visible-border-shadow-color)}.inline-input .part-pill mat-icon{font-size:var(--ymt-sizing-xs);width:var(--ymt-sizing-xs);height:var(--ymt-sizing-xs)}.inline-input yuv-metadata-form-field{flex:1;min-width:150px}.inline-input input{flex:1;border:none;outline:none;background:transparent}.inline-input .confirm-btn:not(:disabled){opacity:1;color:var(--ymt-color-accent)}.combinator{display:flex;justify-content:center;gap:0}.combinator__label{font:var(--ymt-font-body-subtle);text-transform:lowercase;color:var(--ymt-text-color-subtle);padding:var(--ymt-spacing-2xs) var(--ymt-spacing-xs)}.combinator__btn{--bg: transparent;--fg: var(--ymt-text-color);--mdc-shape-small: var(--ymt-corner-xs);background-color:var(--bg);color:var(--fg);padding:var(--ymt-spacing-2xs) var(--ymt-spacing-xs);border:0;outline:1px solid var(--ymt-inverse-surface);outline-offset:-1px}.combinator__btn:first-child{border-radius:var(--mdc-shape-small) 0 0 var(--mdc-shape-small)}.combinator__btn:last-child{border-radius:0 var(--mdc-shape-small) var(--mdc-shape-small) 0}.combinator__btn--active{--bg: var(--ymt-inverse-surface);--fg: var(--ymt-on-inverse-surface)}.add-type-row{display:flex;flex-wrap:wrap;align-items:center;gap:var(--ymt-spacing-xs);padding:var(--ymt-spacing-xs) var(--ymt-spacing-s);border:1px dashed var(--ymt-outline);border-radius:var(--ymt-corner-s);color:var(--ymt-text-color-subtle)}.add-type-row__icon{display:inline-flex;align-items:center;padding:0;border:0;background:transparent;color:inherit;cursor:pointer}.add-type-row__icon mat-icon{font-size:var(--ymt-sizing-m);width:var(--ymt-sizing-m);height:var(--ymt-sizing-m);opacity:.5}.add-type-row__icon:hover mat-icon{opacity:.8}.add-type-row__confirm{color:var(--ymt-primary)}.add-type-row__chips{flex:1;min-width:120px}.add-type-row input{flex:1;min-width:120px;border:none;outline:none;background:transparent;font:inherit;font-size:.9em;color:inherit}.add-type-row input::placeholder{color:var(--ymt-text-color-subtle);opacity:.7}.condition-combinator{display:flex;align-self:center}.suggestion{display:flex;align-items:center;gap:var(--ymt-spacing-xs)}.suggestion__icon{font-size:var(--ymt-sizing-s);width:var(--ymt-sizing-s);height:var(--ymt-sizing-s);--icon-size: var(--ymt-sizing-s);opacity:.6}.suggestion__label{flex:1}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatAutocompleteModule }, { kind: "component", type: i2$1.MatAutocomplete, selector: "mat-autocomplete", inputs: ["aria-label", "aria-labelledby", "displayWith", "autoActiveFirstOption", "autoSelectActiveOption", "requireSelection", "panelWidth", "disableRipple", "class", "hideSingleSelectionIndicator"], outputs: ["optionSelected", "opened", "closed", "optionActivated"], exportAs: ["matAutocomplete"] }, { kind: "component", type: i2$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "directive", type: i2$1.MatAutocompleteTrigger, selector: "input[matAutocomplete], textarea[matAutocomplete]", inputs: ["matAutocomplete", "matAutocompletePosition", "matAutocompleteConnectedTo", "autocomplete", "matAutocompleteDisabled"], exportAs: ["matAutocompleteTrigger"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "ngmodule", type: MatChipsModule }, { kind: "component", type: i4.MatChipGrid, selector: "mat-chip-grid", inputs: ["disabled", "placeholder", "required", "value", "errorStateMatcher"], outputs: ["change", "valueChange"] }, { kind: "directive", type: i4.MatChipInput, selector: "input[matChipInputFor]", inputs: ["matChipInputFor", "matChipInputAddOnBlur", "matChipInputSeparatorKeyCodes", "placeholder", "id", "disabled", "readonly", "matChipInputDisabledInteractive"], outputs: ["matChipInputTokenEnd"], exportAs: ["matChipInput", "matChipInputFor"] }, { kind: "directive", type: i4.MatChipRemove, selector: "[matChipRemove]" }, { kind: "component", type: i4.MatChipRow, selector: "mat-chip-row, [mat-chip-row], mat-basic-chip-row, [mat-basic-chip-row]", inputs: ["editable"], outputs: ["edited"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i5.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "directive", type: YmtIconButtonDirective, selector: "button[ymtIconButton],button[ymt-icon-button],a[ymtIconButton],a[ymt-icon-button]", inputs: ["disabled", "disableRipple", "aria-disabled", "disabledInteractive", "icon-button-size"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: MetadataFormFieldComponent, selector: "yuv-metadata-form-field", inputs: ["formChangedSubject", "field", "variant", "situation"] }, { kind: "directive", type: RendererDirective, selector: "[yuvRenderer]", inputs: ["yuvRenderer"] }, { kind: "component", type: SmartSearchGroupComponent, selector: "yuv-smart-search-group", inputs: ["group", "bare", "chipTpl", "editorTpl"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
2112
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: SmartSearchComponent, isStandalone: true, selector: "yuv-smart-search", inputs: { types: { classPropertyName: "types", publicName: "types", isSignal: true, isRequired: false, transformFunction: null }, skipProperties: { classPropertyName: "skipProperties", publicName: "skipProperties", isSignal: true, isRequired: false, transformFunction: null }, supportDynamicConditions: { classPropertyName: "supportDynamicConditions", publicName: "supportDynamicConditions", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { queryChange: "queryChange" }, providers: [SmartSearchEditController], viewQueries: [{ propertyName: "auto", first: true, predicate: ["auto"], descendants: true, isSignal: true }, { propertyName: "trigger", first: true, predicate: MatAutocompleteTrigger, descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"smart-search\" halo-container halo-container-skip=\"true\">\n <!-- Condition chip template \u2014 passed down into the recursive group component. -->\n <ng-template #chipTpl let-condition let-container=\"container\" let-i=\"index\">\n <div\n class=\"condition-chip\"\n [class.condition-chip--unset]=\"isUnset(condition)\"\n [class.condition-chip--dynamic]=\"condition.dynamic\"\n tabindex=\"0\"\n role=\"button\"\n [attr.aria-label]=\"'yuv.smart-search.condition.edit-aria' | translate: { label: condition.conditionLabel }\"\n (click)=\"editCondition(container, condition, i)\"\n (keydown.space)=\"$event.preventDefault(); editCondition(container, condition, i)\"\n (keydown.enter)=\"$event.preventDefault(); editCondition(container, condition, i)\"\n >\n @if (supportDynamicConditions() && !isValueless(condition.operator)) {\n <button\n ymtIconButton\n icon-button-size=\"extra-small\"\n class=\"condition-chip__dynamic\"\n tabindex=\"-1\"\n [attr.aria-pressed]=\"!!condition.dynamic\"\n [matTooltip]=\"\n (condition.dynamic\n ? 'yuv.smart-search.condition.unmark-dynamic'\n : 'yuv.smart-search.condition.mark-dynamic'\n ) | translate\n \"\n (click)=\"$event.stopPropagation(); toggleDynamic(container, condition)\"\n >\n <mat-icon>bolt</mat-icon>\n </button>\n }\n\n <span class=\"condition-chip__part condition-chip__field\">\n <!-- <mat-icon>tune</mat-icon> -->\n {{ condition.fieldLabel }}\n </span>\n <span class=\"condition-chip__part condition-chip__op\">{{ condition.operatorLabel }}</span>\n <span class=\"condition-chip__part condition-chip__value\">\n @if (isUnset(condition)) {\n <span class=\"condition-chip__placeholder\">{{ 'yuv.smart-search.condition.unset-value' | translate }}</span>\n } @else if (valueRendererInput(condition); as rendererInput) {\n <ng-container *yuvRenderer=\"rendererInput\" />\n } @else {\n {{ condition.value }}\n }\n </span>\n <!-- @if (supportDynamicConditions() && !isValueless(condition.operator)) {\n <button\n ymtIconButton\n icon-button-size=\"extra-small\"\n class=\"condition-chip__dynamic\"\n tabindex=\"-1\"\n [attr.aria-pressed]=\"!!condition.dynamic\"\n [matTooltip]=\"\n (condition.dynamic\n ? 'yuv.smart-search.condition.unmark-dynamic'\n : 'yuv.smart-search.condition.mark-dynamic'\n ) | translate\n \"\n (click)=\"$event.stopPropagation(); toggleDynamic(container, condition)\"\n >\n <mat-icon>bolt</mat-icon>\n </button>\n } -->\n <button\n ymtIconButton\n icon-button-size=\"extra-small\"\n class=\"condition-chip__remove\"\n tabindex=\"-1\"\n [attr.aria-label]=\"'yuv.smart-search.condition.remove' | translate\"\n (click)=\"$event.stopPropagation(); removeCondition(container, condition)\"\n >\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </ng-template>\n\n <!-- Inline editor template \u2014 rendered inside whichever container is active. -->\n <ng-template #editorTpl>\n <div class=\"inline-input\" tabindex=\"-1\" (focusout)=\"onInlineBlur($event)\" (keydown.escape)=\"cancelPending()\">\n @if (ctrl.step() !== 'field' && ctrl.pendingField()) {\n <button\n class=\"part-pill\"\n [class.part-pill--active]=\"ctrl.step() === 'field'\"\n (click)=\"editField()\"\n [matTooltip]=\"'yuv.smart-search.field.change' | translate\"\n >\n {{ ctrl.pendingField()?.label }}\n </button>\n }\n\n @if (ctrl.step() === 'value' && ctrl.operatorCtrl.value) {\n <button\n class=\"part-pill\"\n [class.part-pill--active]=\"ctrl.step() === 'operator'\"\n (click)=\"editOperator()\"\n [matTooltip]=\"'yuv.smart-search.operator.change' | translate\"\n >\n {{ ctrl.operatorCtrl.value }}\n </button>\n }\n\n @if (ctrl.step() === 'field') {\n <input\n [formControl]=\"ctrl.fieldCtrl\"\n [matAutocomplete]=\"auto\"\n [placeholder]=\"'yuv.smart-search.field.pick' | translate\"\n (keydown.enter)=\"onEnter()\"\n />\n } @else if (ctrl.step() === 'operator') {\n <input\n [formControl]=\"ctrl.operatorCtrl\"\n [matAutocomplete]=\"auto\"\n [placeholder]=\"'yuv.smart-search.operator.select' | translate\"\n (keydown.enter)=\"onEnter()\"\n />\n } @else if (ctrl.step() === 'value') {\n @let otf = valueFieldDef();\n @if (otf) {\n <yuv-metadata-form-field variant=\"raw\" [field]=\"otf\" situation=\"EDIT\" [formControl]=\"ctrl.valueCtrl\" />\n } @else {\n <input\n [formControl]=\"ctrl.valueCtrl\"\n [matAutocomplete]=\"auto\"\n [placeholder]=\"'yuv.smart-search.value.enter' | translate\"\n (keydown.enter)=\"onEnter()\"\n />\n }\n }\n\n <button\n ymtIconButton\n icon-button-size=\"extra-small\"\n class=\"confirm-btn\"\n [disabled]=\"!ctrl.isConditionComplete()\"\n (click)=\"onEnter()\"\n [matTooltip]=\"'yuv.smart-search.confirm' | translate\"\n >\n <mat-icon>check</mat-icon>\n </button>\n <button\n ymtIconButton\n icon-button-size=\"extra-small\"\n class=\"cancel-btn\"\n (click)=\"cancelPending()\"\n [matTooltip]=\"'yuv.smart-search.cancel' | translate\"\n >\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </ng-template>\n\n <!-- \u2500\u2500 Toolbar: Essentials \u21C4 Full Form mode switch (only when the dynamic feature is enabled) \u2500\u2500 -->\n @if (supportDynamicConditions() && (hasDynamicConditions() || formMode())) {\n <div class=\"toolbar\">\n <div class=\"toolbar__mode\" role=\"group\" [attr.aria-label]=\"'yuv.smart-search.mode.label' | translate\">\n <button\n type=\"button\"\n class=\"toolbar__mode-btn\"\n [class.toolbar__mode-btn--active]=\"formMode()\"\n [attr.aria-pressed]=\"formMode()\"\n (click)=\"enterFormMode()\"\n >\n {{ 'yuv.smart-search.mode.essentials' | translate }}\n </button>\n <button\n type=\"button\"\n class=\"toolbar__mode-btn\"\n [class.toolbar__mode-btn--active]=\"!formMode()\"\n [attr.aria-pressed]=\"!formMode()\"\n (click)=\"exitFormMode()\"\n >\n {{ 'yuv.smart-search.mode.full' | translate }}\n </button>\n </div>\n </div>\n }\n\n <!-- \u2500\u2500 Generated fill-out form of the dynamic conditions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (formMode()) {\n <div class=\"dynamic-form\">\n @for (grp of formBlocks(); track grp.block.id; let last = $last) {\n <div class=\"dynamic-form__block\">\n <!-- Muted header listing the block's target types (a block matches any of them). -->\n <div class=\"dynamic-form__block-header\">\n @for (t of grp.block.types; track t.id) {\n @if (!$first) {\n <span class=\"dynamic-form__type-sep\">{{ 'yuv.smart-search.combinator.or' | translate }}</span>\n }\n <span class=\"dynamic-form__type\">{{ t.label }}</span>\n }\n </div>\n\n <div class=\"dynamic-form__rows\">\n @for (f of grp.fields; track f.condition) {\n <div class=\"dynamic-form__row\">\n <span class=\"dynamic-form__label\">{{ f.condition.fieldLabel }}</span>\n <span class=\"dynamic-form__op\">{{ f.condition.operatorLabel }}</span>\n <div class=\"dynamic-form__value\">\n @if (f.def) {\n <yuv-metadata-form-field variant=\"raw\" [field]=\"f.def\" situation=\"EDIT\" [formControl]=\"f.control\" />\n } @else {\n <input [formControl]=\"f.control\" [placeholder]=\"'yuv.smart-search.value.enter' | translate\" />\n }\n </div>\n </div>\n }\n </div>\n </div>\n\n <!-- Blocks are type-scoped and always combine with OR (\"this type as well as that type\"). -->\n @if (!last) {\n <div class=\"combinator\">\n <span class=\"combinator__label\">{{ 'yuv.smart-search.combinator.as-well-as' | translate }}</span>\n </div>\n }\n } @empty {\n <span class=\"dynamic-form__empty\">{{ 'yuv.smart-search.mode.empty' | translate }}</span>\n }\n </div>\n } @else {\n <!-- \u2500\u2500 Full-text search bar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"fulltext\" [class.muted]=\"!fulltext().term.trim() && ctrl.blocks().length\">\n <!-- Row 1: full-width term -->\n <div class=\"fulltext__term\">\n <mat-icon class=\"fulltext__icon\">search</mat-icon>\n <input\n class=\"fulltext__input\"\n [formControl]=\"fulltextTermCtrl\"\n [placeholder]=\"'yuv.smart-search.fulltext.placeholder' | translate\"\n />\n </div>\n\n <!-- Row 2: scope (single) + types (multiple) -->\n <div class=\"fulltext__filters\">\n <mat-select\n class=\"fulltext__scope\"\n [panelWidth]=\"null\"\n panelClass=\"smart-search-select-panel\"\n [value]=\"fulltext().scope\"\n (selectionChange)=\"setFulltextScope($event.value)\"\n [aria-label]=\"'yuv.smart-search.fulltext.scope.label' | translate\"\n >\n <mat-option value=\"all\">{{ 'yuv.smart-search.fulltext.scope.all' | translate }}</mat-option>\n <mat-option value=\"metadata\">{{ 'yuv.smart-search.fulltext.scope.metadata' | translate }}</mat-option>\n <mat-option value=\"content\">{{ 'yuv.smart-search.fulltext.scope.content' | translate }}</mat-option>\n </mat-select>\n\n <mat-select\n class=\"fulltext__types\"\n multiple\n [panelWidth]=\"null\"\n panelClass=\"smart-search-select-panel\"\n [value]=\"fulltextTypeSelection()\"\n (selectionChange)=\"onFulltextTypesChange($event.value)\"\n [aria-label]=\"'yuv.smart-search.fulltext.types.label' | translate\"\n >\n <mat-option [value]=\"ALL_TYPES\">{{ 'yuv.smart-search.fulltext.types.all' | translate }}</mat-option>\n @for (t of objectTypes(); track t.id) {\n <mat-option [value]=\"t.id\">{{ t.label ?? t.id }}</mat-option>\n }\n </mat-select>\n </div>\n </div>\n\n <!-- Joiner between the full-text unit and the condition blocks. Top-level units\n always combine with OR (\"looking for this as well as that\"), so this is a\n static label rather than a toggle. -->\n @if (fulltext().term.trim() && ctrl.blocks().length) {\n <div class=\"combinator\">\n <span class=\"combinator__label\">{{ 'yuv.smart-search.combinator.as-well-as' | translate }}</span>\n </div>\n }\n\n <!-- \u2500\u2500 Step 1: Type blocks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @for (block of ctrl.blocks(); track block.id) {\n <div class=\"block\" [attr.data-block-id]=\"block.id\" [class.block--active]=\"ctrl.activeBlock() === block\">\n <div class=\"block__header\">\n <div class=\"block__types\">\n @for (t of block.types; track t.id) {\n @if (!$first) {\n <span class=\"block__type-sep\">{{ 'yuv.smart-search.combinator.or' | translate }}</span>\n }\n <span class=\"block__type\">\n <span class=\"block__type-label\">{{ t.label }}</span>\n </span>\n }\n </div>\n <button\n ymtIconButton\n icon-button-size=\"extra-small\"\n class=\"block__remove\"\n (click)=\"removeBlock(block)\"\n [matTooltip]=\"'yuv.smart-search.type.remove' | translate\"\n >\n <mat-icon>close</mat-icon>\n </button>\n </div>\n\n <div class=\"block__conditions\">\n <yuv-smart-search-group [group]=\"block\" [bare]=\"true\" [chipTpl]=\"chipTpl\" [editorTpl]=\"editorTpl\" />\n </div>\n </div>\n\n <!-- Static joiner between blocks \u2014 blocks are type-scoped, so they always\n combine with OR (\"this type as well as that type\"). -->\n @if (!$last && ctrl.showCombinator()) {\n <div class=\"combinator\">\n <span class=\"combinator__label\">{{ 'yuv.smart-search.combinator.as-well-as' | translate }}</span>\n </div>\n }\n }\n\n <!-- \u2500\u2500 Step 1 input: add a type block (multi-select) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (ctrl.step() === 'type') {\n <div class=\"add-type-row\">\n <button\n type=\"button\"\n ymtIconButton\n icon-button-size=\"extra-small\"\n class=\"add-type-row__icon\"\n (click)=\"typeInput.focus()\"\n [attr.aria-label]=\"'yuv.smart-search.add-type' | translate\"\n >\n <mat-icon>add_circle_outline</mat-icon>\n </button>\n\n <!-- Staged draft types as removable Material chips -->\n <mat-chip-grid\n #typeChipGrid\n class=\"add-type-row__chips\"\n [attr.aria-label]=\"'yuv.smart-search.add-type' | translate\"\n >\n @for (t of ctrl.draftTypes(); track t.id) {\n <mat-chip-row (removed)=\"ctrl.removeDraftType(t.id)\">\n {{ t.label }}\n <button matChipRemove [attr.aria-label]=\"'yuv.smart-search.type.remove-draft' | translate\">\n <mat-icon>close</mat-icon>\n </button>\n </mat-chip-row>\n }\n <input\n #typeInput\n [formControl]=\"ctrl.fieldCtrl\"\n [placeholder]=\"'yuv.smart-search.add-type' | translate\"\n [matAutocomplete]=\"auto\"\n [matChipInputFor]=\"typeChipGrid\"\n (keydown.enter)=\"onTypeEnter()\"\n (keydown.escape)=\"cancelPending()\"\n />\n </mat-chip-grid>\n\n @if (ctrl.draftTypes().length) {\n <button\n ymtIconButton\n icon-button-size=\"extra-small\"\n class=\"add-type-row__confirm\"\n (click)=\"confirmTypes()\"\n [matTooltip]=\"'yuv.smart-search.type.confirm' | translate\"\n >\n <mat-icon>check</mat-icon>\n </button>\n }\n </div>\n }\n }\n\n <!-- \u2500\u2500 Shared autocomplete panel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <mat-autocomplete\n #auto\n panelWidth=\"auto\"\n [displayWith]=\"displayFn\"\n (optionSelected)=\"onSuggestionSelected($event)\"\n (closed)=\"onPickerClosed()\"\n >\n @for (s of ctrl.suggestions(); track s.id) {\n <mat-option [value]=\"s\">\n <div class=\"suggestion\">\n @if (s.kind !== 'type') {\n <mat-icon class=\"suggestion__icon\">\n @switch (s.kind) {\n @case ('field') {\n tune\n }\n @case ('date-preset') {\n calendar_today\n }\n @default {\n manage_search\n }\n }\n </mat-icon>\n }\n <span class=\"suggestion__label\">{{ s.label }}</span>\n </div>\n </mat-option>\n }\n </mat-autocomplete>\n</div>\n", styles: [":host{display:block;--outline: rgb(from var(--ymt-text-color) r g b / .5);--focus-visible-border-color: var(--ymt-primary);--focus-visible-border-shadow-color: rgb(from var(--ymt-primary) r g b / .3)}::ng-deep .smart-search-select-panel.mat-mdc-select-panel{min-width:max-content}::ng-deep .smart-search-select-panel.mat-mdc-select-panel .mat-mdc-option .mdc-list-item__primary-text{white-space:nowrap}.smart-search{display:flex;flex-direction:column;gap:var(--ymt-spacing-xs)}.fulltext{display:flex;flex-direction:column;gap:var(--ymt-spacing-xs);padding:var(--ymt-spacing-xs) var(--ymt-spacing-s);border:1px solid var(--outline);border-radius:var(--ymt-corner-s);transition:opacity .15s}.fulltext.muted{opacity:.7}.fulltext.muted:hover,.fulltext.muted:focus-within{opacity:1}.fulltext__term{display:flex;align-items:center;gap:var(--ymt-spacing-xs)}.fulltext__icon{color:var(--ymt-text-color-subtle)}.fulltext__input{flex:1;min-width:120px;border:none;outline:none;background:transparent;font:inherit;color:var(--ymt-text-color)}.fulltext__input::placeholder{color:var(--ymt-text-color-subtle);opacity:.7}.fulltext__filters{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:var(--ymt-spacing-s)}.fulltext__scope,.fulltext__types{width:auto;font-size:.9em;color:var(--ymt-text-color-subtle);border:1px solid var(--ymt-outline);border-radius:var(--ymt-corner-xs);padding-inline:var(--ymt-spacing-xs)}.fulltext__scope:focus-visible,.fulltext__types:focus-visible{border-color:var(--focus-visible-border-color);box-shadow:0 0 0 2px var(--focus-visible-border-shadow-color)}.block{border:1px solid var(--outline);border-radius:var(--ymt-corner-s);overflow:hidden;transition:border-color .15s;padding:var(--ymt-spacing-2xs)}.block:focus-within{border-color:var(--focus-visible-border-color);box-shadow:0 0 0 3px var(--focus-visible-border-shadow-color)}.block__header{border-radius:var(--ymt-corner-xs);display:flex;flex-wrap:wrap;align-items:center;gap:var(--ymt-spacing-xs);padding:var(--ymt-spacing-xs) var(--ymt-spacing-s);background:var(--ymt-surface-container-high)}.block__types{flex:1;display:flex;align-items:center;flex-wrap:wrap}.block__type{display:inline-flex;align-items:center;gap:var(--ymt-spacing-2xs)}.block__type-sep{font:var(--ymt-font-body-subtle);text-transform:lowercase;color:var(--ymt-text-color-subtle);padding:0 var(--ymt-spacing-xs)}.block__remove{margin-inline-start:auto;opacity:.7}.block__conditions{display:flex;flex-direction:column;align-items:stretch;gap:var(--ymt-spacing-2xs);padding:var(--ymt-spacing-xs) var(--ymt-spacing-s);min-height:44px}.condition-chip{display:flex;flex:1;align-items:center;gap:0;border-radius:var(--ymt-corner-xs);border:1px solid var(--outline);cursor:pointer;overflow:hidden;outline:none;gap:var(--ymt-spacing-2xs)}.condition-chip:hover{border-color:var(--ymt-outline)}.condition-chip:focus-visible{border-color:var(--focus-visible-border-color);box-shadow:0 0 0 2px var(--focus-visible-border-shadow-color)}.condition-chip__part{display:inline-flex;align-items:center;gap:var(--ymt-spacing-4xs);padding:0 var(--ymt-spacing-xs);height:100%;white-space:nowrap;color:var(--ymt-text-color)}.condition-chip__part:focus-visible{border-color:var(--focus-visible-border-color);box-shadow:0 0 0 2px var(--focus-visible-border-shadow-color)}.condition-chip__op{font:var(--ymt-font-body-subtle);color:var(--ymt-text-color-subtle);padding:0}.condition-chip__value{flex:1;overflow:hidden;text-overflow:ellipsis;min-height:24px;--tile-slot-padding: 0;--yuv-renderer-display: inline-flex}.condition-chip__remove{--mat-icon-button-container-shape: 0;color:var(--ymt-text-color-subtle)}.condition-chip__remove:hover{color:var(--ymt-text-color)}.condition-chip__placeholder{font:var(--ymt-font-body-subtle);font-style:italic;color:var(--ymt-text-color-subtle);opacity:.8}.condition-chip__dynamic{--mat-icon-button-container-shape: 0;color:var(--ymt-text-color-subtle)}.condition-chip__dynamic:hover{color:var(--ymt-text-color)}.condition-chip--dynamic{outline:2px solid var(--ymt-inverse-surface);outline-offset:1px}.condition-chip--dynamic .condition-chip__dynamic{border-radius:0;background-color:var(--ymt-inverse-surface);color:var(--ymt-on-inverse-surface)}.toolbar{display:flex;justify-content:flex-end;align-items:center;background:var(--ymt-surface-container-high);border-radius:var(--ymt-corner-xs);border:1px solid var(--ymt-outline);padding:var(--ymt-spacing-3xs)}.toolbar__mode{display:inline-flex;gap:0}.toolbar__mode-btn{--bg: transparent;--fg: var(--ymt-text-color);--mdc-shape-small: var(--ymt-corner-xs);font-size:.82em;background-color:var(--bg);color:var(--fg);padding:var(--ymt-spacing-2xs) var(--ymt-spacing-xs);border:1px solid var(--ymt-inverse-surface);cursor:pointer}.toolbar__mode-btn:focus-visible{box-shadow:0 0 0 2px var(--focus-visible-border-shadow-color)}.toolbar__mode-btn:first-child{border-radius:var(--mdc-shape-small) 0 0 var(--mdc-shape-small)}.toolbar__mode-btn:last-child{border-radius:0 var(--mdc-shape-small) var(--mdc-shape-small) 0}.toolbar__mode-btn--active{--bg: var(--ymt-inverse-surface);--fg: var(--ymt-on-inverse-surface)}.dynamic-form{display:flex;flex-direction:column;gap:var(--ymt-spacing-xs)}.dynamic-form__block{container-type:inline-size;border:1px solid var(--outline);border-radius:var(--ymt-corner-s);overflow:hidden}.dynamic-form__block-header{display:flex;flex-wrap:wrap;align-items:center;gap:var(--ymt-spacing-xs);color:var(--ymt-text-color-subtle);padding:var(--ymt-spacing-2xs) var(--ymt-spacing-s);font:var(--ymt-font-body-subtle)}.dynamic-form__type-sep{text-transform:lowercase;color:var(--ymt-text-color-subtle);padding:0 var(--ymt-spacing-2xs)}.dynamic-form__rows{display:grid;grid-template-columns:max-content max-content 1fr;row-gap:var(--ymt-spacing-s);column-gap:var(--ymt-spacing-xs);overflow:auto;padding:var(--ymt-spacing-s)}.dynamic-form__row{display:grid;grid-template-columns:subgrid;grid-column:1/-1;align-items:center}.dynamic-form__label{color:var(--ymt-text-color)}.dynamic-form__op{font:var(--ymt-font-body-subtle);color:var(--ymt-text-color-subtle);border:1px solid var(--outline);padding-inline:var(--ymt-spacing-2xs);border-radius:var(--ymt-corner-xs)}.dynamic-form__value{flex:1;padding:var(--ymt-spacing-xs);border:1px solid var(--outline);border-radius:var(--ymt-corner-xs)}.dynamic-form__value input{width:100%;border:none;outline:none;background:transparent;font:inherit;color:var(--ymt-text-color)}.dynamic-form__empty{font:var(--ymt-font-body-subtle);color:var(--ymt-text-color-subtle)}@container (max-width: 499px){.dynamic-form__rows{grid-template-columns:1fr auto}.dynamic-form__row{grid-template-columns:subgrid;grid-column:1/-1;row-gap:var(--ymt-spacing-2xs)}.dynamic-form__value{grid-column:1/-1}}.inline-input{display:flex;align-items:center;gap:var(--ymt-spacing-2xs);flex:1;border:1px dashed var(--outline);border-radius:var(--ymt-corner-xs);padding:var(--ymt-spacing-2xs);background:var(--ymt-surface)}.inline-input yuv-metadata-form-field{border-radius:var(--ymt-corner-xs)}.inline-input yuv-metadata-form-field:focus-within{outline:1px solid var(--focus-visible-border-color)}.inline-input .part-pill{display:inline-flex;align-items:center;gap:var(--ymt-spacing-4xs);white-space:nowrap;font:inherit;font-size:var(--ymt-font-body-subtle-size);color:var(--ymt-text-color);background:var(--ymt-surface-container);border:1px solid var(--ymt-outline);border-radius:var(--ymt-corner-xs);padding:var(--ymt-spacing-4xs) var(--ymt-spacing-xs);cursor:pointer;transition:border-color .1s,background .1s}.inline-input .part-pill:hover{background:var(--ymt-surface-container-high)}.inline-input .part-pill:focus-visible{border-color:var(--focus-visible-border-color);box-shadow:0 0 0 2px var(--focus-visible-border-shadow-color)}.inline-input .part-pill--active{border-color:var(--ymt-primary);box-shadow:0 0 0 2px var(--focus-visible-border-shadow-color)}.inline-input .part-pill mat-icon{font-size:var(--ymt-sizing-xs);width:var(--ymt-sizing-xs);height:var(--ymt-sizing-xs)}.inline-input yuv-metadata-form-field{flex:1;min-width:150px}.inline-input input{flex:1;border:none;outline:none;background:transparent}.inline-input .confirm-btn:not(:disabled){opacity:1;color:var(--ymt-color-accent)}.combinator{display:flex;justify-content:center;gap:0}.combinator__label{font:var(--ymt-font-body-subtle);text-transform:lowercase;color:var(--ymt-text-color-subtle);padding:var(--ymt-spacing-2xs) var(--ymt-spacing-xs)}.add-type-row{display:flex;flex-wrap:wrap;align-items:center;gap:var(--ymt-spacing-xs);padding:var(--ymt-spacing-xs) var(--ymt-spacing-s);border:1px dashed var(--ymt-outline);border-radius:var(--ymt-corner-s);color:var(--ymt-text-color-subtle)}.add-type-row__icon{display:inline-flex;align-items:center;padding:0;border:0;background:transparent;color:inherit;cursor:pointer}.add-type-row__icon mat-icon{font-size:var(--ymt-sizing-m);width:var(--ymt-sizing-m);height:var(--ymt-sizing-m);opacity:.5}.add-type-row__icon:hover mat-icon{opacity:.8}.add-type-row__confirm{color:var(--ymt-primary)}.add-type-row__chips{flex:1;min-width:120px}.add-type-row input{flex:1;min-width:120px;border:none;outline:none;background:transparent;font:inherit;font-size:.9em;color:inherit}.add-type-row input::placeholder{color:var(--ymt-text-color-subtle);opacity:.7}.condition-combinator{display:flex;align-self:center}.suggestion{display:flex;align-items:center;gap:var(--ymt-spacing-xs)}.suggestion__icon{font-size:var(--ymt-sizing-s);width:var(--ymt-sizing-s);height:var(--ymt-sizing-s);--icon-size: var(--ymt-sizing-s);opacity:.6}.suggestion__label{flex:1}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatAutocompleteModule }, { kind: "component", type: i2$1.MatAutocomplete, selector: "mat-autocomplete", inputs: ["aria-label", "aria-labelledby", "displayWith", "autoActiveFirstOption", "autoSelectActiveOption", "requireSelection", "panelWidth", "disableRipple", "class", "hideSingleSelectionIndicator"], outputs: ["optionSelected", "opened", "closed", "optionActivated"], exportAs: ["matAutocomplete"] }, { kind: "component", type: i2$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "directive", type: i2$1.MatAutocompleteTrigger, selector: "input[matAutocomplete], textarea[matAutocomplete]", inputs: ["matAutocomplete", "matAutocompletePosition", "matAutocompleteConnectedTo", "autocomplete", "matAutocompleteDisabled"], exportAs: ["matAutocompleteTrigger"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "ngmodule", type: MatChipsModule }, { kind: "component", type: i4.MatChipGrid, selector: "mat-chip-grid", inputs: ["disabled", "placeholder", "required", "value", "errorStateMatcher"], outputs: ["change", "valueChange"] }, { kind: "directive", type: i4.MatChipInput, selector: "input[matChipInputFor]", inputs: ["matChipInputFor", "matChipInputAddOnBlur", "matChipInputSeparatorKeyCodes", "placeholder", "id", "disabled", "readonly", "matChipInputDisabledInteractive"], outputs: ["matChipInputTokenEnd"], exportAs: ["matChipInput", "matChipInputFor"] }, { kind: "directive", type: i4.MatChipRemove, selector: "[matChipRemove]" }, { kind: "component", type: i4.MatChipRow, selector: "mat-chip-row, [mat-chip-row], mat-basic-chip-row, [mat-basic-chip-row]", inputs: ["editable"], outputs: ["edited"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i5.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "directive", type: YmtIconButtonDirective, selector: "button[ymtIconButton],button[ymt-icon-button],a[ymtIconButton],a[ymt-icon-button]", inputs: ["disabled", "disableRipple", "aria-disabled", "disabledInteractive", "icon-button-size"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: MetadataFormFieldComponent, selector: "yuv-metadata-form-field", inputs: ["formChangedSubject", "field", "variant", "situation"] }, { kind: "directive", type: RendererDirective, selector: "[yuvRenderer]", inputs: ["yuvRenderer"] }, { kind: "component", type: SmartSearchGroupComponent, selector: "yuv-smart-search-group", inputs: ["group", "bare", "chipTpl", "editorTpl"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
1899
2113
|
}
|
|
1900
2114
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: SmartSearchComponent, decorators: [{
|
|
1901
2115
|
type: Component,
|
|
@@ -1913,8 +2127,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
1913
2127
|
RendererDirective,
|
|
1914
2128
|
SmartSearchGroupComponent,
|
|
1915
2129
|
TranslatePipe
|
|
1916
|
-
], providers: [SmartSearchEditController], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"smart-search\" halo-container halo-container-skip=\"true\">\n <!-- Condition chip template \u2014 passed down into the recursive group component. -->\n <ng-template #chipTpl let-condition let-container=\"container\" let-i=\"index\">\n <div\n class=\"condition-chip\"\n tabindex=\"0\"\n role=\"button\"\n [attr.aria-label]=\"'yuv.smart-search.condition.edit-aria' | translate: { label: condition.conditionLabel }\"\n (click)=\"editCondition(container, condition, i)\"\n (keydown.space)=\"$event.preventDefault(); editCondition(container, condition, i)\"\n (keydown.enter)=\"$event.preventDefault(); editCondition(container, condition, i)\"\n >\n <span class=\"condition-chip__part condition-chip__field\">\n <!-- <mat-icon>tune</mat-icon> -->\n {{ condition.fieldLabel }}\n </span>\n <span class=\"condition-chip__part condition-chip__op\">{{ condition.operatorLabel }}</span>\n <span class=\"condition-chip__part condition-chip__value\">\n @if (valueRendererInput(condition); as rendererInput) {\n <ng-container *yuvRenderer=\"rendererInput\" />\n } @else {\n {{ condition.value }}\n }\n </span>\n <button\n ymtIconButton\n icon-button-size=\"extra-small\"\n class=\"condition-chip__remove\"\n tabindex=\"-1\"\n [attr.aria-label]=\"'yuv.smart-search.condition.remove' | translate\"\n (click)=\"$event.stopPropagation(); removeCondition(container, condition)\"\n >\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </ng-template>\n\n <!-- Inline editor template \u2014 rendered inside whichever container is active. -->\n <ng-template #editorTpl>\n <div class=\"inline-input\" tabindex=\"-1\" (focusout)=\"onInlineBlur($event)\" (keydown.escape)=\"cancelPending()\">\n @if (ctrl.step() !== 'field' && ctrl.pendingField()) {\n <button\n class=\"part-pill\"\n [class.part-pill--active]=\"ctrl.step() === 'field'\"\n (click)=\"editField()\"\n [matTooltip]=\"'yuv.smart-search.field.change' | translate\"\n >\n {{ ctrl.pendingField()?.label }}\n </button>\n }\n\n @if (ctrl.step() === 'value' && ctrl.operatorCtrl.value) {\n <button\n class=\"part-pill\"\n [class.part-pill--active]=\"ctrl.step() === 'operator'\"\n (click)=\"editOperator()\"\n [matTooltip]=\"'yuv.smart-search.operator.change' | translate\"\n >\n {{ ctrl.operatorCtrl.value }}\n </button>\n }\n\n @if (ctrl.step() === 'field') {\n <input\n [formControl]=\"ctrl.fieldCtrl\"\n [matAutocomplete]=\"auto\"\n [placeholder]=\"'yuv.smart-search.field.pick' | translate\"\n (keydown.enter)=\"onEnter()\"\n />\n } @else if (ctrl.step() === 'operator') {\n <input\n [formControl]=\"ctrl.operatorCtrl\"\n [matAutocomplete]=\"auto\"\n [placeholder]=\"'yuv.smart-search.operator.select' | translate\"\n (keydown.enter)=\"onEnter()\"\n />\n } @else if (ctrl.step() === 'value') {\n @let otf = valueFieldDef();\n @if (otf) {\n <yuv-metadata-form-field variant=\"raw\" [field]=\"otf\" situation=\"EDIT\" [formControl]=\"ctrl.valueCtrl\" />\n } @else {\n <input\n [formControl]=\"ctrl.valueCtrl\"\n [matAutocomplete]=\"auto\"\n [placeholder]=\"'yuv.smart-search.value.enter' | translate\"\n (keydown.enter)=\"onEnter()\"\n />\n }\n }\n\n <button\n ymtIconButton\n icon-button-size=\"extra-small\"\n class=\"confirm-btn\"\n [disabled]=\"!ctrl.isConditionComplete()\"\n (click)=\"onEnter()\"\n [matTooltip]=\"'yuv.smart-search.confirm' | translate\"\n >\n <mat-icon>check</mat-icon>\n </button>\n <button\n ymtIconButton\n icon-button-size=\"extra-small\"\n class=\"cancel-btn\"\n (click)=\"cancelPending()\"\n [matTooltip]=\"'yuv.smart-search.cancel' | translate\"\n >\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </ng-template>\n\n <!-- \u2500\u2500 Full-text search bar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"fulltext\" [class.muted]=\"!fulltext().term.trim() && ctrl.blocks().length\">\n <!-- Row 1: full-width term -->\n <div class=\"fulltext__term\">\n <mat-icon class=\"fulltext__icon\">search</mat-icon>\n <input\n class=\"fulltext__input\"\n [formControl]=\"fulltextTermCtrl\"\n [placeholder]=\"'yuv.smart-search.fulltext.placeholder' | translate\"\n />\n </div>\n\n <!-- Row 2: scope (single) + types (multiple) -->\n <div class=\"fulltext__filters\">\n <mat-select\n class=\"fulltext__scope\"\n [panelWidth]=\"null\"\n panelClass=\"smart-search-select-panel\"\n [value]=\"fulltext().scope\"\n (selectionChange)=\"setFulltextScope($event.value)\"\n [aria-label]=\"'yuv.smart-search.fulltext.scope.label' | translate\"\n >\n <mat-option value=\"all\">{{ 'yuv.smart-search.fulltext.scope.all' | translate }}</mat-option>\n <mat-option value=\"metadata\">{{ 'yuv.smart-search.fulltext.scope.metadata' | translate }}</mat-option>\n <mat-option value=\"content\">{{ 'yuv.smart-search.fulltext.scope.content' | translate }}</mat-option>\n </mat-select>\n\n <mat-select\n class=\"fulltext__types\"\n multiple\n [panelWidth]=\"null\"\n panelClass=\"smart-search-select-panel\"\n [value]=\"fulltextTypeSelection()\"\n (selectionChange)=\"onFulltextTypesChange($event.value)\"\n [aria-label]=\"'yuv.smart-search.fulltext.types.label' | translate\"\n >\n <mat-option [value]=\"ALL_TYPES\">{{ 'yuv.smart-search.fulltext.types.all' | translate }}</mat-option>\n @for (t of objectTypes(); track t.id) {\n <mat-option [value]=\"t.id\">{{ t.label ?? t.id }}</mat-option>\n }\n </mat-select>\n </div>\n </div>\n\n <!-- Joiner between the full-text unit and the condition blocks. Top-level units\n always combine with OR (\"looking for this as well as that\"), so this is a\n static label rather than a toggle. -->\n @if (fulltext().term.trim() && ctrl.blocks().length) {\n <div class=\"combinator\">\n <span class=\"combinator__label\">{{ 'yuv.smart-search.combinator.as-well-as' | translate }}</span>\n </div>\n }\n\n <!-- \u2500\u2500 Step 1: Type blocks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @for (block of ctrl.blocks(); track block.id) {\n <div class=\"block\" [attr.data-block-id]=\"block.id\" [class.block--active]=\"ctrl.activeBlock() === block\">\n <div class=\"block__header\">\n <div class=\"block__types\">\n @for (t of block.types; track t.id) {\n @if (!$first) {\n <span class=\"block__type-sep\">{{ 'yuv.smart-search.combinator.or' | translate }}</span>\n }\n <span class=\"block__type\">\n <span class=\"block__type-label\">{{ t.label }}</span>\n </span>\n }\n </div>\n <button\n ymtIconButton\n icon-button-size=\"extra-small\"\n class=\"block__remove\"\n (click)=\"removeBlock(block)\"\n [matTooltip]=\"'yuv.smart-search.type.remove' | translate\"\n >\n <mat-icon>close</mat-icon>\n </button>\n </div>\n\n <div class=\"block__conditions\">\n <yuv-smart-search-group [group]=\"block\" [bare]=\"true\" [chipTpl]=\"chipTpl\" [editorTpl]=\"editorTpl\" />\n </div>\n </div>\n\n <!-- Static joiner between blocks \u2014 blocks are type-scoped, so they always\n combine with OR (\"this type as well as that type\"). -->\n @if (!$last && ctrl.showCombinator()) {\n <div class=\"combinator\">\n <span class=\"combinator__label\">{{ 'yuv.smart-search.combinator.as-well-as' | translate }}</span>\n </div>\n }\n }\n\n <!-- \u2500\u2500 Step 1 input: add a type block (multi-select) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (ctrl.step() === 'type') {\n <div class=\"add-type-row\">\n <button\n type=\"button\"\n ymtIconButton\n icon-button-size=\"extra-small\"\n class=\"add-type-row__icon\"\n (click)=\"typeInput.focus()\"\n [attr.aria-label]=\"'yuv.smart-search.add-type' | translate\"\n >\n <mat-icon>add_circle_outline</mat-icon>\n </button>\n\n <!-- Staged draft types as removable Material chips -->\n <mat-chip-grid\n #typeChipGrid\n class=\"add-type-row__chips\"\n [attr.aria-label]=\"'yuv.smart-search.add-type' | translate\"\n >\n @for (t of ctrl.draftTypes(); track t.id) {\n <mat-chip-row (removed)=\"ctrl.removeDraftType(t.id)\">\n {{ t.label }}\n <button matChipRemove [attr.aria-label]=\"'yuv.smart-search.type.remove-draft' | translate\">\n <mat-icon>close</mat-icon>\n </button>\n </mat-chip-row>\n }\n <input\n #typeInput\n [formControl]=\"ctrl.fieldCtrl\"\n [placeholder]=\"'yuv.smart-search.add-type' | translate\"\n [matAutocomplete]=\"auto\"\n [matChipInputFor]=\"typeChipGrid\"\n (keydown.enter)=\"onTypeEnter()\"\n (keydown.escape)=\"cancelPending()\"\n />\n </mat-chip-grid>\n\n @if (ctrl.draftTypes().length) {\n <button\n ymtIconButton\n icon-button-size=\"extra-small\"\n class=\"add-type-row__confirm\"\n (click)=\"confirmTypes()\"\n [matTooltip]=\"'yuv.smart-search.type.confirm' | translate\"\n >\n <mat-icon>check</mat-icon>\n </button>\n }\n </div>\n }\n\n <!-- \u2500\u2500 Shared autocomplete panel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <mat-autocomplete\n #auto\n panelWidth=\"auto\"\n [displayWith]=\"displayFn\"\n (optionSelected)=\"onSuggestionSelected($event)\"\n (closed)=\"onPickerClosed()\"\n >\n @for (s of ctrl.suggestions(); track s.id) {\n <mat-option [value]=\"s\">\n <div class=\"suggestion\">\n @if (s.kind !== 'type') {\n <mat-icon class=\"suggestion__icon\">\n @switch (s.kind) {\n @case ('field') {\n tune\n }\n @case ('date-preset') {\n calendar_today\n }\n @default {\n manage_search\n }\n }\n </mat-icon>\n }\n <span class=\"suggestion__label\">{{ s.label }}</span>\n </div>\n </mat-option>\n }\n </mat-autocomplete>\n</div>\n", styles: [":host{display:block;--outline: rgb(from var(--ymt-text-color) r g b / .5);--focus-visible-border-color: var(--ymt-primary);--focus-visible-border-shadow-color: rgb(from var(--ymt-primary) r g b / .3)}::ng-deep .smart-search-select-panel.mat-mdc-select-panel{min-width:max-content}::ng-deep .smart-search-select-panel.mat-mdc-select-panel .mat-mdc-option .mdc-list-item__primary-text{white-space:nowrap}.smart-search{display:flex;flex-direction:column;gap:var(--ymt-spacing-xs)}.fulltext{display:flex;flex-direction:column;gap:var(--ymt-spacing-xs);padding:var(--ymt-spacing-xs) var(--ymt-spacing-s);border:1px solid var(--outline);border-radius:var(--ymt-corner-s);transition:opacity .15s}.fulltext.muted{opacity:.7}.fulltext.muted:hover,.fulltext.muted:focus-within{opacity:1}.fulltext__term{display:flex;align-items:center;gap:var(--ymt-spacing-xs)}.fulltext__icon{color:var(--ymt-text-color-subtle)}.fulltext__input{flex:1;min-width:120px;border:none;outline:none;background:transparent;font:inherit;color:var(--ymt-text-color)}.fulltext__input::placeholder{color:var(--ymt-text-color-subtle);opacity:.7}.fulltext__filters{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:var(--ymt-spacing-s)}.fulltext__scope,.fulltext__types{width:auto;font-size:.9em;color:var(--ymt-text-color-subtle);border:1px solid var(--ymt-outline);border-radius:var(--ymt-corner-xs);padding-inline:var(--ymt-spacing-xs)}.fulltext__scope:focus-visible,.fulltext__types:focus-visible{border-color:var(--focus-visible-border-color);box-shadow:0 0 0 2px var(--focus-visible-border-shadow-color)}.block{border:1px solid var(--outline);border-radius:var(--ymt-corner-s);overflow:hidden;transition:border-color .15s;padding:var(--ymt-spacing-2xs)}.block:focus-within{border-color:var(--focus-visible-border-color);box-shadow:0 0 0 3px var(--focus-visible-border-shadow-color)}.block__header{border-radius:var(--ymt-corner-xs);display:flex;flex-wrap:wrap;align-items:center;gap:var(--ymt-spacing-xs);padding:var(--ymt-spacing-xs) var(--ymt-spacing-s);background:var(--ymt-surface-container-high)}.block__types{flex:1;display:flex;align-items:center;flex-wrap:wrap}.block__type{display:inline-flex;align-items:center;gap:var(--ymt-spacing-2xs)}.block__type-sep{font:var(--ymt-font-body-subtle);text-transform:lowercase;color:var(--ymt-text-color-subtle);padding:0 var(--ymt-spacing-xs)}.block__remove{margin-inline-start:auto;opacity:.7}.block__conditions{display:flex;flex-wrap:wrap;align-items:center;row-gap:var(--ymt-spacing-2xs);column-gap:var(--ymt-spacing-xs);padding:var(--ymt-spacing-xs) var(--ymt-spacing-s);min-height:44px}.condition-chip{display:inline-flex;align-items:center;gap:0;border-radius:var(--ymt-corner-xs);border:1px solid var(--outline);cursor:pointer;overflow:hidden;outline:none;padding:var(--ymt-spacing-4xs) var(--ymt-spacing-3xs);gap:var(--ymt-spacing-4xs)}.condition-chip:hover{border-color:var(--ymt-outline)}.condition-chip:focus-visible{border-color:var(--focus-visible-border-color);box-shadow:0 0 0 2px var(--focus-visible-border-shadow-color)}.condition-chip__part{display:inline-flex;align-items:center;gap:var(--ymt-spacing-4xs);padding:0 var(--ymt-spacing-xs);height:100%;white-space:nowrap;color:var(--ymt-text-color)}.condition-chip__part:focus-visible{border-color:var(--focus-visible-border-color);box-shadow:0 0 0 2px var(--focus-visible-border-shadow-color)}.condition-chip__op{font:var(--ymt-font-body-subtle);color:var(--ymt-text-color-subtle);padding:0}.condition-chip__value{max-width:120px;overflow:hidden;text-overflow:ellipsis;--tile-slot-padding: 0;--yuv-renderer-display: inline-flex}.condition-chip__remove{color:var(--ymt-text-color-subtle)}.condition-chip__remove:hover{color:var(--ymt-text-color)}.inline-input{display:flex;align-items:center;gap:var(--ymt-spacing-3xs);flex:1;border:1px dashed var(--outline);border-radius:var(--ymt-corner-xs);padding:var(--ymt-spacing-4xs) var(--ymt-spacing-2xs);background:var(--ymt-surface)}.inline-input .part-pill{display:inline-flex;align-items:center;gap:var(--ymt-spacing-4xs);white-space:nowrap;font:inherit;font-size:var(--ymt-font-body-subtle-size);color:var(--ymt-text-color);background:var(--ymt-surface-container);border:1px solid transparent;border-radius:var(--ymt-corner-xs);padding:var(--ymt-spacing-4xs) var(--ymt-spacing-xs);cursor:pointer;transition:border-color .1s,background .1s}.inline-input .part-pill:hover{background:var(--ymt-surface-container-high)}.inline-input .part-pill:focus-visible{border-color:var(--focus-visible-border-color);box-shadow:0 0 0 2px var(--focus-visible-border-shadow-color)}.inline-input .part-pill--active{border-color:var(--ymt-primary);box-shadow:0 0 0 2px var(--focus-visible-border-shadow-color)}.inline-input .part-pill mat-icon{font-size:var(--ymt-sizing-xs);width:var(--ymt-sizing-xs);height:var(--ymt-sizing-xs)}.inline-input yuv-metadata-form-field{flex:1;min-width:150px}.inline-input input{flex:1;border:none;outline:none;background:transparent}.inline-input .confirm-btn:not(:disabled){opacity:1;color:var(--ymt-color-accent)}.combinator{display:flex;justify-content:center;gap:0}.combinator__label{font:var(--ymt-font-body-subtle);text-transform:lowercase;color:var(--ymt-text-color-subtle);padding:var(--ymt-spacing-2xs) var(--ymt-spacing-xs)}.combinator__btn{--bg: transparent;--fg: var(--ymt-text-color);--mdc-shape-small: var(--ymt-corner-xs);background-color:var(--bg);color:var(--fg);padding:var(--ymt-spacing-2xs) var(--ymt-spacing-xs);border:0;outline:1px solid var(--ymt-inverse-surface);outline-offset:-1px}.combinator__btn:first-child{border-radius:var(--mdc-shape-small) 0 0 var(--mdc-shape-small)}.combinator__btn:last-child{border-radius:0 var(--mdc-shape-small) var(--mdc-shape-small) 0}.combinator__btn--active{--bg: var(--ymt-inverse-surface);--fg: var(--ymt-on-inverse-surface)}.add-type-row{display:flex;flex-wrap:wrap;align-items:center;gap:var(--ymt-spacing-xs);padding:var(--ymt-spacing-xs) var(--ymt-spacing-s);border:1px dashed var(--ymt-outline);border-radius:var(--ymt-corner-s);color:var(--ymt-text-color-subtle)}.add-type-row__icon{display:inline-flex;align-items:center;padding:0;border:0;background:transparent;color:inherit;cursor:pointer}.add-type-row__icon mat-icon{font-size:var(--ymt-sizing-m);width:var(--ymt-sizing-m);height:var(--ymt-sizing-m);opacity:.5}.add-type-row__icon:hover mat-icon{opacity:.8}.add-type-row__confirm{color:var(--ymt-primary)}.add-type-row__chips{flex:1;min-width:120px}.add-type-row input{flex:1;min-width:120px;border:none;outline:none;background:transparent;font:inherit;font-size:.9em;color:inherit}.add-type-row input::placeholder{color:var(--ymt-text-color-subtle);opacity:.7}.condition-combinator{display:flex;align-self:center}.suggestion{display:flex;align-items:center;gap:var(--ymt-spacing-xs)}.suggestion__icon{font-size:var(--ymt-sizing-s);width:var(--ymt-sizing-s);height:var(--ymt-sizing-s);--icon-size: var(--ymt-sizing-s);opacity:.6}.suggestion__label{flex:1}\n"] }]
|
|
1917
|
-
}], ctorParameters: () => [], propDecorators: { types: [{ type: i0.Input, args: [{ isSignal: true, alias: "types", required: false }] }], skipProperties: [{ type: i0.Input, args: [{ isSignal: true, alias: "skipProperties", required: false }] }], queryChange: [{ type: i0.Output, args: ["queryChange"] }], auto: [{ type: i0.ViewChild, args: ['auto', { isSignal: true }] }], trigger: [{ type: i0.ViewChild, args: [i0.forwardRef(() => MatAutocompleteTrigger), { isSignal: true }] }] } });
|
|
2130
|
+
], providers: [SmartSearchEditController], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"smart-search\" halo-container halo-container-skip=\"true\">\n <!-- Condition chip template \u2014 passed down into the recursive group component. -->\n <ng-template #chipTpl let-condition let-container=\"container\" let-i=\"index\">\n <div\n class=\"condition-chip\"\n [class.condition-chip--unset]=\"isUnset(condition)\"\n [class.condition-chip--dynamic]=\"condition.dynamic\"\n tabindex=\"0\"\n role=\"button\"\n [attr.aria-label]=\"'yuv.smart-search.condition.edit-aria' | translate: { label: condition.conditionLabel }\"\n (click)=\"editCondition(container, condition, i)\"\n (keydown.space)=\"$event.preventDefault(); editCondition(container, condition, i)\"\n (keydown.enter)=\"$event.preventDefault(); editCondition(container, condition, i)\"\n >\n @if (supportDynamicConditions() && !isValueless(condition.operator)) {\n <button\n ymtIconButton\n icon-button-size=\"extra-small\"\n class=\"condition-chip__dynamic\"\n tabindex=\"-1\"\n [attr.aria-pressed]=\"!!condition.dynamic\"\n [matTooltip]=\"\n (condition.dynamic\n ? 'yuv.smart-search.condition.unmark-dynamic'\n : 'yuv.smart-search.condition.mark-dynamic'\n ) | translate\n \"\n (click)=\"$event.stopPropagation(); toggleDynamic(container, condition)\"\n >\n <mat-icon>bolt</mat-icon>\n </button>\n }\n\n <span class=\"condition-chip__part condition-chip__field\">\n <!-- <mat-icon>tune</mat-icon> -->\n {{ condition.fieldLabel }}\n </span>\n <span class=\"condition-chip__part condition-chip__op\">{{ condition.operatorLabel }}</span>\n <span class=\"condition-chip__part condition-chip__value\">\n @if (isUnset(condition)) {\n <span class=\"condition-chip__placeholder\">{{ 'yuv.smart-search.condition.unset-value' | translate }}</span>\n } @else if (valueRendererInput(condition); as rendererInput) {\n <ng-container *yuvRenderer=\"rendererInput\" />\n } @else {\n {{ condition.value }}\n }\n </span>\n <!-- @if (supportDynamicConditions() && !isValueless(condition.operator)) {\n <button\n ymtIconButton\n icon-button-size=\"extra-small\"\n class=\"condition-chip__dynamic\"\n tabindex=\"-1\"\n [attr.aria-pressed]=\"!!condition.dynamic\"\n [matTooltip]=\"\n (condition.dynamic\n ? 'yuv.smart-search.condition.unmark-dynamic'\n : 'yuv.smart-search.condition.mark-dynamic'\n ) | translate\n \"\n (click)=\"$event.stopPropagation(); toggleDynamic(container, condition)\"\n >\n <mat-icon>bolt</mat-icon>\n </button>\n } -->\n <button\n ymtIconButton\n icon-button-size=\"extra-small\"\n class=\"condition-chip__remove\"\n tabindex=\"-1\"\n [attr.aria-label]=\"'yuv.smart-search.condition.remove' | translate\"\n (click)=\"$event.stopPropagation(); removeCondition(container, condition)\"\n >\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </ng-template>\n\n <!-- Inline editor template \u2014 rendered inside whichever container is active. -->\n <ng-template #editorTpl>\n <div class=\"inline-input\" tabindex=\"-1\" (focusout)=\"onInlineBlur($event)\" (keydown.escape)=\"cancelPending()\">\n @if (ctrl.step() !== 'field' && ctrl.pendingField()) {\n <button\n class=\"part-pill\"\n [class.part-pill--active]=\"ctrl.step() === 'field'\"\n (click)=\"editField()\"\n [matTooltip]=\"'yuv.smart-search.field.change' | translate\"\n >\n {{ ctrl.pendingField()?.label }}\n </button>\n }\n\n @if (ctrl.step() === 'value' && ctrl.operatorCtrl.value) {\n <button\n class=\"part-pill\"\n [class.part-pill--active]=\"ctrl.step() === 'operator'\"\n (click)=\"editOperator()\"\n [matTooltip]=\"'yuv.smart-search.operator.change' | translate\"\n >\n {{ ctrl.operatorCtrl.value }}\n </button>\n }\n\n @if (ctrl.step() === 'field') {\n <input\n [formControl]=\"ctrl.fieldCtrl\"\n [matAutocomplete]=\"auto\"\n [placeholder]=\"'yuv.smart-search.field.pick' | translate\"\n (keydown.enter)=\"onEnter()\"\n />\n } @else if (ctrl.step() === 'operator') {\n <input\n [formControl]=\"ctrl.operatorCtrl\"\n [matAutocomplete]=\"auto\"\n [placeholder]=\"'yuv.smart-search.operator.select' | translate\"\n (keydown.enter)=\"onEnter()\"\n />\n } @else if (ctrl.step() === 'value') {\n @let otf = valueFieldDef();\n @if (otf) {\n <yuv-metadata-form-field variant=\"raw\" [field]=\"otf\" situation=\"EDIT\" [formControl]=\"ctrl.valueCtrl\" />\n } @else {\n <input\n [formControl]=\"ctrl.valueCtrl\"\n [matAutocomplete]=\"auto\"\n [placeholder]=\"'yuv.smart-search.value.enter' | translate\"\n (keydown.enter)=\"onEnter()\"\n />\n }\n }\n\n <button\n ymtIconButton\n icon-button-size=\"extra-small\"\n class=\"confirm-btn\"\n [disabled]=\"!ctrl.isConditionComplete()\"\n (click)=\"onEnter()\"\n [matTooltip]=\"'yuv.smart-search.confirm' | translate\"\n >\n <mat-icon>check</mat-icon>\n </button>\n <button\n ymtIconButton\n icon-button-size=\"extra-small\"\n class=\"cancel-btn\"\n (click)=\"cancelPending()\"\n [matTooltip]=\"'yuv.smart-search.cancel' | translate\"\n >\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </ng-template>\n\n <!-- \u2500\u2500 Toolbar: Essentials \u21C4 Full Form mode switch (only when the dynamic feature is enabled) \u2500\u2500 -->\n @if (supportDynamicConditions() && (hasDynamicConditions() || formMode())) {\n <div class=\"toolbar\">\n <div class=\"toolbar__mode\" role=\"group\" [attr.aria-label]=\"'yuv.smart-search.mode.label' | translate\">\n <button\n type=\"button\"\n class=\"toolbar__mode-btn\"\n [class.toolbar__mode-btn--active]=\"formMode()\"\n [attr.aria-pressed]=\"formMode()\"\n (click)=\"enterFormMode()\"\n >\n {{ 'yuv.smart-search.mode.essentials' | translate }}\n </button>\n <button\n type=\"button\"\n class=\"toolbar__mode-btn\"\n [class.toolbar__mode-btn--active]=\"!formMode()\"\n [attr.aria-pressed]=\"!formMode()\"\n (click)=\"exitFormMode()\"\n >\n {{ 'yuv.smart-search.mode.full' | translate }}\n </button>\n </div>\n </div>\n }\n\n <!-- \u2500\u2500 Generated fill-out form of the dynamic conditions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (formMode()) {\n <div class=\"dynamic-form\">\n @for (grp of formBlocks(); track grp.block.id; let last = $last) {\n <div class=\"dynamic-form__block\">\n <!-- Muted header listing the block's target types (a block matches any of them). -->\n <div class=\"dynamic-form__block-header\">\n @for (t of grp.block.types; track t.id) {\n @if (!$first) {\n <span class=\"dynamic-form__type-sep\">{{ 'yuv.smart-search.combinator.or' | translate }}</span>\n }\n <span class=\"dynamic-form__type\">{{ t.label }}</span>\n }\n </div>\n\n <div class=\"dynamic-form__rows\">\n @for (f of grp.fields; track f.condition) {\n <div class=\"dynamic-form__row\">\n <span class=\"dynamic-form__label\">{{ f.condition.fieldLabel }}</span>\n <span class=\"dynamic-form__op\">{{ f.condition.operatorLabel }}</span>\n <div class=\"dynamic-form__value\">\n @if (f.def) {\n <yuv-metadata-form-field variant=\"raw\" [field]=\"f.def\" situation=\"EDIT\" [formControl]=\"f.control\" />\n } @else {\n <input [formControl]=\"f.control\" [placeholder]=\"'yuv.smart-search.value.enter' | translate\" />\n }\n </div>\n </div>\n }\n </div>\n </div>\n\n <!-- Blocks are type-scoped and always combine with OR (\"this type as well as that type\"). -->\n @if (!last) {\n <div class=\"combinator\">\n <span class=\"combinator__label\">{{ 'yuv.smart-search.combinator.as-well-as' | translate }}</span>\n </div>\n }\n } @empty {\n <span class=\"dynamic-form__empty\">{{ 'yuv.smart-search.mode.empty' | translate }}</span>\n }\n </div>\n } @else {\n <!-- \u2500\u2500 Full-text search bar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"fulltext\" [class.muted]=\"!fulltext().term.trim() && ctrl.blocks().length\">\n <!-- Row 1: full-width term -->\n <div class=\"fulltext__term\">\n <mat-icon class=\"fulltext__icon\">search</mat-icon>\n <input\n class=\"fulltext__input\"\n [formControl]=\"fulltextTermCtrl\"\n [placeholder]=\"'yuv.smart-search.fulltext.placeholder' | translate\"\n />\n </div>\n\n <!-- Row 2: scope (single) + types (multiple) -->\n <div class=\"fulltext__filters\">\n <mat-select\n class=\"fulltext__scope\"\n [panelWidth]=\"null\"\n panelClass=\"smart-search-select-panel\"\n [value]=\"fulltext().scope\"\n (selectionChange)=\"setFulltextScope($event.value)\"\n [aria-label]=\"'yuv.smart-search.fulltext.scope.label' | translate\"\n >\n <mat-option value=\"all\">{{ 'yuv.smart-search.fulltext.scope.all' | translate }}</mat-option>\n <mat-option value=\"metadata\">{{ 'yuv.smart-search.fulltext.scope.metadata' | translate }}</mat-option>\n <mat-option value=\"content\">{{ 'yuv.smart-search.fulltext.scope.content' | translate }}</mat-option>\n </mat-select>\n\n <mat-select\n class=\"fulltext__types\"\n multiple\n [panelWidth]=\"null\"\n panelClass=\"smart-search-select-panel\"\n [value]=\"fulltextTypeSelection()\"\n (selectionChange)=\"onFulltextTypesChange($event.value)\"\n [aria-label]=\"'yuv.smart-search.fulltext.types.label' | translate\"\n >\n <mat-option [value]=\"ALL_TYPES\">{{ 'yuv.smart-search.fulltext.types.all' | translate }}</mat-option>\n @for (t of objectTypes(); track t.id) {\n <mat-option [value]=\"t.id\">{{ t.label ?? t.id }}</mat-option>\n }\n </mat-select>\n </div>\n </div>\n\n <!-- Joiner between the full-text unit and the condition blocks. Top-level units\n always combine with OR (\"looking for this as well as that\"), so this is a\n static label rather than a toggle. -->\n @if (fulltext().term.trim() && ctrl.blocks().length) {\n <div class=\"combinator\">\n <span class=\"combinator__label\">{{ 'yuv.smart-search.combinator.as-well-as' | translate }}</span>\n </div>\n }\n\n <!-- \u2500\u2500 Step 1: Type blocks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @for (block of ctrl.blocks(); track block.id) {\n <div class=\"block\" [attr.data-block-id]=\"block.id\" [class.block--active]=\"ctrl.activeBlock() === block\">\n <div class=\"block__header\">\n <div class=\"block__types\">\n @for (t of block.types; track t.id) {\n @if (!$first) {\n <span class=\"block__type-sep\">{{ 'yuv.smart-search.combinator.or' | translate }}</span>\n }\n <span class=\"block__type\">\n <span class=\"block__type-label\">{{ t.label }}</span>\n </span>\n }\n </div>\n <button\n ymtIconButton\n icon-button-size=\"extra-small\"\n class=\"block__remove\"\n (click)=\"removeBlock(block)\"\n [matTooltip]=\"'yuv.smart-search.type.remove' | translate\"\n >\n <mat-icon>close</mat-icon>\n </button>\n </div>\n\n <div class=\"block__conditions\">\n <yuv-smart-search-group [group]=\"block\" [bare]=\"true\" [chipTpl]=\"chipTpl\" [editorTpl]=\"editorTpl\" />\n </div>\n </div>\n\n <!-- Static joiner between blocks \u2014 blocks are type-scoped, so they always\n combine with OR (\"this type as well as that type\"). -->\n @if (!$last && ctrl.showCombinator()) {\n <div class=\"combinator\">\n <span class=\"combinator__label\">{{ 'yuv.smart-search.combinator.as-well-as' | translate }}</span>\n </div>\n }\n }\n\n <!-- \u2500\u2500 Step 1 input: add a type block (multi-select) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (ctrl.step() === 'type') {\n <div class=\"add-type-row\">\n <button\n type=\"button\"\n ymtIconButton\n icon-button-size=\"extra-small\"\n class=\"add-type-row__icon\"\n (click)=\"typeInput.focus()\"\n [attr.aria-label]=\"'yuv.smart-search.add-type' | translate\"\n >\n <mat-icon>add_circle_outline</mat-icon>\n </button>\n\n <!-- Staged draft types as removable Material chips -->\n <mat-chip-grid\n #typeChipGrid\n class=\"add-type-row__chips\"\n [attr.aria-label]=\"'yuv.smart-search.add-type' | translate\"\n >\n @for (t of ctrl.draftTypes(); track t.id) {\n <mat-chip-row (removed)=\"ctrl.removeDraftType(t.id)\">\n {{ t.label }}\n <button matChipRemove [attr.aria-label]=\"'yuv.smart-search.type.remove-draft' | translate\">\n <mat-icon>close</mat-icon>\n </button>\n </mat-chip-row>\n }\n <input\n #typeInput\n [formControl]=\"ctrl.fieldCtrl\"\n [placeholder]=\"'yuv.smart-search.add-type' | translate\"\n [matAutocomplete]=\"auto\"\n [matChipInputFor]=\"typeChipGrid\"\n (keydown.enter)=\"onTypeEnter()\"\n (keydown.escape)=\"cancelPending()\"\n />\n </mat-chip-grid>\n\n @if (ctrl.draftTypes().length) {\n <button\n ymtIconButton\n icon-button-size=\"extra-small\"\n class=\"add-type-row__confirm\"\n (click)=\"confirmTypes()\"\n [matTooltip]=\"'yuv.smart-search.type.confirm' | translate\"\n >\n <mat-icon>check</mat-icon>\n </button>\n }\n </div>\n }\n }\n\n <!-- \u2500\u2500 Shared autocomplete panel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <mat-autocomplete\n #auto\n panelWidth=\"auto\"\n [displayWith]=\"displayFn\"\n (optionSelected)=\"onSuggestionSelected($event)\"\n (closed)=\"onPickerClosed()\"\n >\n @for (s of ctrl.suggestions(); track s.id) {\n <mat-option [value]=\"s\">\n <div class=\"suggestion\">\n @if (s.kind !== 'type') {\n <mat-icon class=\"suggestion__icon\">\n @switch (s.kind) {\n @case ('field') {\n tune\n }\n @case ('date-preset') {\n calendar_today\n }\n @default {\n manage_search\n }\n }\n </mat-icon>\n }\n <span class=\"suggestion__label\">{{ s.label }}</span>\n </div>\n </mat-option>\n }\n </mat-autocomplete>\n</div>\n", styles: [":host{display:block;--outline: rgb(from var(--ymt-text-color) r g b / .5);--focus-visible-border-color: var(--ymt-primary);--focus-visible-border-shadow-color: rgb(from var(--ymt-primary) r g b / .3)}::ng-deep .smart-search-select-panel.mat-mdc-select-panel{min-width:max-content}::ng-deep .smart-search-select-panel.mat-mdc-select-panel .mat-mdc-option .mdc-list-item__primary-text{white-space:nowrap}.smart-search{display:flex;flex-direction:column;gap:var(--ymt-spacing-xs)}.fulltext{display:flex;flex-direction:column;gap:var(--ymt-spacing-xs);padding:var(--ymt-spacing-xs) var(--ymt-spacing-s);border:1px solid var(--outline);border-radius:var(--ymt-corner-s);transition:opacity .15s}.fulltext.muted{opacity:.7}.fulltext.muted:hover,.fulltext.muted:focus-within{opacity:1}.fulltext__term{display:flex;align-items:center;gap:var(--ymt-spacing-xs)}.fulltext__icon{color:var(--ymt-text-color-subtle)}.fulltext__input{flex:1;min-width:120px;border:none;outline:none;background:transparent;font:inherit;color:var(--ymt-text-color)}.fulltext__input::placeholder{color:var(--ymt-text-color-subtle);opacity:.7}.fulltext__filters{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:var(--ymt-spacing-s)}.fulltext__scope,.fulltext__types{width:auto;font-size:.9em;color:var(--ymt-text-color-subtle);border:1px solid var(--ymt-outline);border-radius:var(--ymt-corner-xs);padding-inline:var(--ymt-spacing-xs)}.fulltext__scope:focus-visible,.fulltext__types:focus-visible{border-color:var(--focus-visible-border-color);box-shadow:0 0 0 2px var(--focus-visible-border-shadow-color)}.block{border:1px solid var(--outline);border-radius:var(--ymt-corner-s);overflow:hidden;transition:border-color .15s;padding:var(--ymt-spacing-2xs)}.block:focus-within{border-color:var(--focus-visible-border-color);box-shadow:0 0 0 3px var(--focus-visible-border-shadow-color)}.block__header{border-radius:var(--ymt-corner-xs);display:flex;flex-wrap:wrap;align-items:center;gap:var(--ymt-spacing-xs);padding:var(--ymt-spacing-xs) var(--ymt-spacing-s);background:var(--ymt-surface-container-high)}.block__types{flex:1;display:flex;align-items:center;flex-wrap:wrap}.block__type{display:inline-flex;align-items:center;gap:var(--ymt-spacing-2xs)}.block__type-sep{font:var(--ymt-font-body-subtle);text-transform:lowercase;color:var(--ymt-text-color-subtle);padding:0 var(--ymt-spacing-xs)}.block__remove{margin-inline-start:auto;opacity:.7}.block__conditions{display:flex;flex-direction:column;align-items:stretch;gap:var(--ymt-spacing-2xs);padding:var(--ymt-spacing-xs) var(--ymt-spacing-s);min-height:44px}.condition-chip{display:flex;flex:1;align-items:center;gap:0;border-radius:var(--ymt-corner-xs);border:1px solid var(--outline);cursor:pointer;overflow:hidden;outline:none;gap:var(--ymt-spacing-2xs)}.condition-chip:hover{border-color:var(--ymt-outline)}.condition-chip:focus-visible{border-color:var(--focus-visible-border-color);box-shadow:0 0 0 2px var(--focus-visible-border-shadow-color)}.condition-chip__part{display:inline-flex;align-items:center;gap:var(--ymt-spacing-4xs);padding:0 var(--ymt-spacing-xs);height:100%;white-space:nowrap;color:var(--ymt-text-color)}.condition-chip__part:focus-visible{border-color:var(--focus-visible-border-color);box-shadow:0 0 0 2px var(--focus-visible-border-shadow-color)}.condition-chip__op{font:var(--ymt-font-body-subtle);color:var(--ymt-text-color-subtle);padding:0}.condition-chip__value{flex:1;overflow:hidden;text-overflow:ellipsis;min-height:24px;--tile-slot-padding: 0;--yuv-renderer-display: inline-flex}.condition-chip__remove{--mat-icon-button-container-shape: 0;color:var(--ymt-text-color-subtle)}.condition-chip__remove:hover{color:var(--ymt-text-color)}.condition-chip__placeholder{font:var(--ymt-font-body-subtle);font-style:italic;color:var(--ymt-text-color-subtle);opacity:.8}.condition-chip__dynamic{--mat-icon-button-container-shape: 0;color:var(--ymt-text-color-subtle)}.condition-chip__dynamic:hover{color:var(--ymt-text-color)}.condition-chip--dynamic{outline:2px solid var(--ymt-inverse-surface);outline-offset:1px}.condition-chip--dynamic .condition-chip__dynamic{border-radius:0;background-color:var(--ymt-inverse-surface);color:var(--ymt-on-inverse-surface)}.toolbar{display:flex;justify-content:flex-end;align-items:center;background:var(--ymt-surface-container-high);border-radius:var(--ymt-corner-xs);border:1px solid var(--ymt-outline);padding:var(--ymt-spacing-3xs)}.toolbar__mode{display:inline-flex;gap:0}.toolbar__mode-btn{--bg: transparent;--fg: var(--ymt-text-color);--mdc-shape-small: var(--ymt-corner-xs);font-size:.82em;background-color:var(--bg);color:var(--fg);padding:var(--ymt-spacing-2xs) var(--ymt-spacing-xs);border:1px solid var(--ymt-inverse-surface);cursor:pointer}.toolbar__mode-btn:focus-visible{box-shadow:0 0 0 2px var(--focus-visible-border-shadow-color)}.toolbar__mode-btn:first-child{border-radius:var(--mdc-shape-small) 0 0 var(--mdc-shape-small)}.toolbar__mode-btn:last-child{border-radius:0 var(--mdc-shape-small) var(--mdc-shape-small) 0}.toolbar__mode-btn--active{--bg: var(--ymt-inverse-surface);--fg: var(--ymt-on-inverse-surface)}.dynamic-form{display:flex;flex-direction:column;gap:var(--ymt-spacing-xs)}.dynamic-form__block{container-type:inline-size;border:1px solid var(--outline);border-radius:var(--ymt-corner-s);overflow:hidden}.dynamic-form__block-header{display:flex;flex-wrap:wrap;align-items:center;gap:var(--ymt-spacing-xs);color:var(--ymt-text-color-subtle);padding:var(--ymt-spacing-2xs) var(--ymt-spacing-s);font:var(--ymt-font-body-subtle)}.dynamic-form__type-sep{text-transform:lowercase;color:var(--ymt-text-color-subtle);padding:0 var(--ymt-spacing-2xs)}.dynamic-form__rows{display:grid;grid-template-columns:max-content max-content 1fr;row-gap:var(--ymt-spacing-s);column-gap:var(--ymt-spacing-xs);overflow:auto;padding:var(--ymt-spacing-s)}.dynamic-form__row{display:grid;grid-template-columns:subgrid;grid-column:1/-1;align-items:center}.dynamic-form__label{color:var(--ymt-text-color)}.dynamic-form__op{font:var(--ymt-font-body-subtle);color:var(--ymt-text-color-subtle);border:1px solid var(--outline);padding-inline:var(--ymt-spacing-2xs);border-radius:var(--ymt-corner-xs)}.dynamic-form__value{flex:1;padding:var(--ymt-spacing-xs);border:1px solid var(--outline);border-radius:var(--ymt-corner-xs)}.dynamic-form__value input{width:100%;border:none;outline:none;background:transparent;font:inherit;color:var(--ymt-text-color)}.dynamic-form__empty{font:var(--ymt-font-body-subtle);color:var(--ymt-text-color-subtle)}@container (max-width: 499px){.dynamic-form__rows{grid-template-columns:1fr auto}.dynamic-form__row{grid-template-columns:subgrid;grid-column:1/-1;row-gap:var(--ymt-spacing-2xs)}.dynamic-form__value{grid-column:1/-1}}.inline-input{display:flex;align-items:center;gap:var(--ymt-spacing-2xs);flex:1;border:1px dashed var(--outline);border-radius:var(--ymt-corner-xs);padding:var(--ymt-spacing-2xs);background:var(--ymt-surface)}.inline-input yuv-metadata-form-field{border-radius:var(--ymt-corner-xs)}.inline-input yuv-metadata-form-field:focus-within{outline:1px solid var(--focus-visible-border-color)}.inline-input .part-pill{display:inline-flex;align-items:center;gap:var(--ymt-spacing-4xs);white-space:nowrap;font:inherit;font-size:var(--ymt-font-body-subtle-size);color:var(--ymt-text-color);background:var(--ymt-surface-container);border:1px solid var(--ymt-outline);border-radius:var(--ymt-corner-xs);padding:var(--ymt-spacing-4xs) var(--ymt-spacing-xs);cursor:pointer;transition:border-color .1s,background .1s}.inline-input .part-pill:hover{background:var(--ymt-surface-container-high)}.inline-input .part-pill:focus-visible{border-color:var(--focus-visible-border-color);box-shadow:0 0 0 2px var(--focus-visible-border-shadow-color)}.inline-input .part-pill--active{border-color:var(--ymt-primary);box-shadow:0 0 0 2px var(--focus-visible-border-shadow-color)}.inline-input .part-pill mat-icon{font-size:var(--ymt-sizing-xs);width:var(--ymt-sizing-xs);height:var(--ymt-sizing-xs)}.inline-input yuv-metadata-form-field{flex:1;min-width:150px}.inline-input input{flex:1;border:none;outline:none;background:transparent}.inline-input .confirm-btn:not(:disabled){opacity:1;color:var(--ymt-color-accent)}.combinator{display:flex;justify-content:center;gap:0}.combinator__label{font:var(--ymt-font-body-subtle);text-transform:lowercase;color:var(--ymt-text-color-subtle);padding:var(--ymt-spacing-2xs) var(--ymt-spacing-xs)}.add-type-row{display:flex;flex-wrap:wrap;align-items:center;gap:var(--ymt-spacing-xs);padding:var(--ymt-spacing-xs) var(--ymt-spacing-s);border:1px dashed var(--ymt-outline);border-radius:var(--ymt-corner-s);color:var(--ymt-text-color-subtle)}.add-type-row__icon{display:inline-flex;align-items:center;padding:0;border:0;background:transparent;color:inherit;cursor:pointer}.add-type-row__icon mat-icon{font-size:var(--ymt-sizing-m);width:var(--ymt-sizing-m);height:var(--ymt-sizing-m);opacity:.5}.add-type-row__icon:hover mat-icon{opacity:.8}.add-type-row__confirm{color:var(--ymt-primary)}.add-type-row__chips{flex:1;min-width:120px}.add-type-row input{flex:1;min-width:120px;border:none;outline:none;background:transparent;font:inherit;font-size:.9em;color:inherit}.add-type-row input::placeholder{color:var(--ymt-text-color-subtle);opacity:.7}.condition-combinator{display:flex;align-self:center}.suggestion{display:flex;align-items:center;gap:var(--ymt-spacing-xs)}.suggestion__icon{font-size:var(--ymt-sizing-s);width:var(--ymt-sizing-s);height:var(--ymt-sizing-s);--icon-size: var(--ymt-sizing-s);opacity:.6}.suggestion__label{flex:1}\n"] }]
|
|
2131
|
+
}], ctorParameters: () => [], propDecorators: { types: [{ type: i0.Input, args: [{ isSignal: true, alias: "types", required: false }] }], skipProperties: [{ type: i0.Input, args: [{ isSignal: true, alias: "skipProperties", required: false }] }], supportDynamicConditions: [{ type: i0.Input, args: [{ isSignal: true, alias: "supportDynamicConditions", required: false }] }], queryChange: [{ type: i0.Output, args: ["queryChange"] }], auto: [{ type: i0.ViewChild, args: ['auto', { isSignal: true }] }], trigger: [{ type: i0.ViewChild, args: [i0.forwardRef(() => MatAutocompleteTrigger), { isSignal: true }] }] } });
|
|
1918
2132
|
|
|
1919
2133
|
/**
|
|
1920
2134
|
* Convenience NgModule that imports and re-exports {@link SmartSearchComponent}.
|