@yuuvis/client-framework 3.8.0 → 3.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -19,6 +19,7 @@ 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';
23
24
  import { NgTemplateOutlet } from '@angular/common';
24
25
 
@@ -147,6 +148,34 @@ function isValuelessOperator(operator) {
147
148
  operator === 'empty' ||
148
149
  operator === 'not_empty');
149
150
  }
151
+ /**
152
+ * Whether a condition is an *unset placeholder*: it uses a value-requiring operator
153
+ * (eq / like / gt / …) but carries no value yet. Such conditions are kept in the tree
154
+ * — so a saved query can act as a fill-in template — but contribute nothing to the
155
+ * emitted CMIS query. An empty value is unambiguously "unset" here because null-checks
156
+ * have their own `empty` / `not_empty` operators (offered on every queryable type).
157
+ */
158
+ function isConditionUnset(cond) {
159
+ return !isValuelessOperator(cond.operator) && !isValuePresent(cond.value);
160
+ }
161
+ /**
162
+ * Produce a copy of `blocks` with the values of the conditions in `overrides` replaced.
163
+ * Used by form mode to overlay the user's fill-out values onto the (untouched) template
164
+ * before building the query, so the saved template stays reusable. Conditions are matched
165
+ * by reference — the keys are the dynamic-condition references captured when form mode was
166
+ * entered. Returns the original array unchanged when there is nothing to overlay.
167
+ */
168
+ function overlayConditionValues(blocks, overrides) {
169
+ if (overrides.size === 0)
170
+ return blocks;
171
+ const mapNode = (node) => {
172
+ if (isConditionGroup(node) || isTableCondition(node)) {
173
+ return { ...node, conditions: node.conditions.map(mapNode) };
174
+ }
175
+ return overrides.has(node) ? { ...node, value: overrides.get(node) } : node;
176
+ };
177
+ return blocks.map((block) => ({ ...block, conditions: block.conditions.map(mapNode) }));
178
+ }
150
179
  /* eslint-disable id-length */
151
180
  /**
152
181
  * Calendar-day arithmetic. Using `new Date(y, m, d ± n)` (rather than
@@ -195,6 +224,10 @@ function datePresetToRange(preset) {
195
224
  */
196
225
  function buildConditionClause(cond) {
197
226
  const operator = cond.operator;
227
+ // Unset placeholders (value-requiring operator, no value yet) contribute nothing —
228
+ // this also guards against `LIKE '%%'`, `= ''` and `IN ()` for empty input.
229
+ if (isConditionUnset(cond))
230
+ return '';
198
231
  // Valueless null checks short-circuit before any value handling.
199
232
  if (operator === 'empty')
200
233
  return `${cond.fieldId} IS NULL`;
@@ -536,6 +569,27 @@ class SmartSearchEditController {
536
569
  this.inputTerm = signal('', ...(ngDevMode ? [{ debugName: "inputTerm" }] : /* istanbul ignore next */ []));
537
570
  /** The whole-object full-text search unit (independent of the condition blocks). */
538
571
  this.fulltext = signal({ term: '', scope: 'all', types: [] }, ...(ngDevMode ? [{ debugName: "fulltext" }] : /* istanbul ignore next */ []));
572
+ /** Whether the dynamic-conditions feature is enabled (mirrors the host `supportDynamicConditions` input). */
573
+ this.supportDynamic = signal(false, ...(ngDevMode ? [{ debugName: "supportDynamic" }] : /* istanbul ignore next */ []));
574
+ /**
575
+ * Form mode: replace the builder with a generated form of the user-marked dynamic
576
+ * conditions. View state only — the template ({@link blocks}) is never mutated; the
577
+ * form's values are overlaid onto the dynamic conditions to drive {@link cmisQuery},
578
+ * so the template stays reusable.
579
+ */
580
+ this.formMode = signal(false, ...(ngDevMode ? [{ debugName: "formMode" }] : /* istanbul ignore next */ []));
581
+ /** The dynamic conditions surfaced as form rows; rebuilt on each {@link enterFormMode}. */
582
+ this.formFields = signal([], ...(ngDevMode ? [{ debugName: "formFields" }] : /* istanbul ignore next */ []));
583
+ /**
584
+ * The dynamic form rows grouped by their owning block (each carrying a muted header of
585
+ * target types); rebuilt on each {@link enterFormMode}. Blocks combine with `OR`
586
+ * ("as well as"), matching the builder.
587
+ */
588
+ this.formBlocks = signal([], ...(ngDevMode ? [{ debugName: "formBlocks" }] : /* istanbul ignore next */ []));
589
+ /** Overlay values entered in the form, keyed by the dynamic condition's reference (refs are stable while form mode is active). */
590
+ this.#formValues = signal(new Map(), ...(ngDevMode ? [{ debugName: "#formValues" }] : /* istanbul ignore next */ []));
591
+ /** Whether any condition is marked dynamic (gates the form-mode toggle). */
592
+ this.hasDynamicConditions = computed(() => this.blocks().some((block) => this.#anyDynamic(block.conditions)), ...(ngDevMode ? [{ debugName: "hasDynamicConditions" }] : /* istanbul ignore next */ []));
539
593
  this.objectTypes = computed(() => this.#system.getObjectTypes(true, 'search').filter((type) => this.allowedTypes().includes(type.id)), ...(ngDevMode ? [{ debugName: "objectTypes" }] : /* istanbul ignore next */ []));
540
594
  /** Fields for the active top-level block, used by the field-step suggestions. */
541
595
  this.activeBlockFields = computed(() => this.#sharedFields(this.activeBlock()?.types ?? []), ...(ngDevMode ? [{ debugName: "activeBlockFields" }] : /* istanbul ignore next */ []));
@@ -589,7 +643,12 @@ class SmartSearchEditController {
589
643
  /** Number of top-level query units: the full-text unit (when it has a term) plus each block. */
590
644
  this.unitCount = computed(() => (this.fulltext().term.trim() ? 1 : 0) + this.blocks().length, ...(ngDevMode ? [{ debugName: "unitCount" }] : /* istanbul ignore next */ []));
591
645
  this.showCombinator = computed(() => this.unitCount() >= 2, ...(ngDevMode ? [{ debugName: "showCombinator" }] : /* istanbul ignore next */ []));
592
- this.cmisQuery = computed(() => buildCmisQuery(this.blocks(), this.combinator(), this.fulltext()), ...(ngDevMode ? [{ debugName: "cmisQuery" }] : /* istanbul ignore next */ []));
646
+ this.cmisQuery = computed(() => {
647
+ // In form mode, overlay the form's entered values onto the (untouched) template so
648
+ // the emitted query reflects the user's fill-out without mutating the saved blocks.
649
+ const blocks = this.formMode() ? overlayConditionValues(this.blocks(), this.#formValues()) : this.blocks();
650
+ return buildCmisQuery(blocks, this.combinator(), this.fulltext());
651
+ }, ...(ngDevMode ? [{ debugName: "cmisQuery" }] : /* istanbul ignore next */ []));
593
652
  /** The active input control for the current step. */
594
653
  this.activeCtrl = computed(() => {
595
654
  switch (this.step()) {
@@ -610,6 +669,8 @@ class SmartSearchEditController {
610
669
  #editingSnapshot;
611
670
  /** Monotonic counter for generating stable block ids. */
612
671
  #blockSeq;
672
+ /** Overlay values entered in the form, keyed by the dynamic condition's reference (refs are stable while form mode is active). */
673
+ #formValues;
613
674
  // ── State save / restore ──────────────────────────────────────────────────
614
675
  /** Deep-clone the current blocks, combinator and full-text unit into a serializable {@link SmartSearchState}. */
615
676
  getState() {
@@ -622,6 +683,8 @@ class SmartSearchEditController {
622
683
  /** Replace the current state with a saved one, normalizing legacy blocks and cancelling any in-progress edit. */
623
684
  loadState(state) {
624
685
  this.cancelPending();
686
+ // Form-mode overlay keys reference the old tree; drop form mode on reload.
687
+ this.exitFormMode();
625
688
  this.blocks.set(state.blocks.map((block) => this.#normalizeBlock(block)));
626
689
  this.combinator.set(state.combinator);
627
690
  this.fulltext.set(state.fulltext ?? { term: '', scope: 'all', types: [] });
@@ -632,6 +695,82 @@ class SmartSearchEditController {
632
695
  this.blocks.set([]);
633
696
  this.combinator.set('OR');
634
697
  this.fulltext.set({ term: '', scope: 'all', types: [] });
698
+ this.exitFormMode();
699
+ }
700
+ // ── Dynamic conditions + form mode ────────────────────────────────────────
701
+ /** Mark or unmark a condition as dynamic (immutably replaces the node in its container). */
702
+ setConditionDynamic(container, condition, dynamic) {
703
+ this.blocks.update((blocks) => updateContainer(blocks, container, (target) => withConditions(target, target.conditions.map((node) => (node === condition ? { ...condition, dynamic } : node)))));
704
+ }
705
+ /**
706
+ * Enter form mode: collect every dynamic condition, resolve its value editor and seed a
707
+ * `FormControl` with its current value. The blocks themselves are left untouched — the
708
+ * form's edits are overlaid via {@link setFormValue} / {@link cmisQuery}.
709
+ */
710
+ enterFormMode() {
711
+ if (!this.supportDynamic())
712
+ return;
713
+ const allFields = [];
714
+ const formBlocks = [];
715
+ for (const block of this.blocks()) {
716
+ const fields = [];
717
+ const walk = (nodes, table) => {
718
+ for (const node of nodes) {
719
+ if (isTableCondition(node))
720
+ walk(node.conditions, node);
721
+ else if (isConditionGroup(node))
722
+ walk(node.conditions, table);
723
+ else if (node.dynamic && !isValuelessOperator(node.operator)) {
724
+ const field = {
725
+ condition: node,
726
+ def: this.#resolveDynamicFieldDef(block, table, node.fieldId),
727
+ control: new FormControl(node.value)
728
+ };
729
+ fields.push(field);
730
+ allFields.push(field);
731
+ }
732
+ }
733
+ };
734
+ walk(block.conditions, null);
735
+ // Only blocks that actually contribute a fillable row get a header in the form.
736
+ if (fields.length)
737
+ formBlocks.push({ block, fields });
738
+ }
739
+ this.formFields.set(allFields);
740
+ this.formBlocks.set(formBlocks);
741
+ this.#formValues.set(new Map());
742
+ this.formMode.set(true);
743
+ }
744
+ /** Leave form mode and discard the transient form state (the template is untouched). */
745
+ exitFormMode() {
746
+ this.formMode.set(false);
747
+ this.formFields.set([]);
748
+ this.formBlocks.set([]);
749
+ this.#formValues.set(new Map());
750
+ }
751
+ /** Toggle form mode (rebuilds the form rows on each entry). */
752
+ toggleFormMode() {
753
+ if (this.formMode())
754
+ this.exitFormMode();
755
+ else
756
+ this.enterFormMode();
757
+ }
758
+ /** Record a value entered in the form for a dynamic condition (drives the overlaid {@link cmisQuery}). */
759
+ setFormValue(condition, raw) {
760
+ this.#formValues.update((current) => new Map(current).set(condition, normalizeConditionValue(raw)));
761
+ }
762
+ /** Whether any condition in `nodes` (recursively) is marked dynamic and has a fillable value. */
763
+ #anyDynamic(nodes) {
764
+ return nodes.some((node) => isConditionGroup(node) || isTableCondition(node)
765
+ ? this.#anyDynamic(node.conditions)
766
+ : !!node.dynamic && !isValuelessOperator(node.operator));
767
+ }
768
+ /** Resolve the value-editor field definition for a dynamic condition, honouring its table context. */
769
+ #resolveDynamicFieldDef(block, table, fieldId) {
770
+ if (table) {
771
+ return this.#tableColumns(table, block).find((col) => col.id === fieldId) ?? null;
772
+ }
773
+ return this.resolveFieldDefinition(block.types, fieldId);
635
774
  }
636
775
  // ── Full-text unit ────────────────────────────────────────────────────────
637
776
  /** Set the full-text search term. */
@@ -891,17 +1030,16 @@ class SmartSearchEditController {
891
1030
  return withConditions(target, conditions);
892
1031
  }));
893
1032
  }
894
- /** Whether the in-progress condition has all parts needed to be committed. */
1033
+ /**
1034
+ * Whether the in-progress condition has all parts needed to be committed. A value
1035
+ * is *not* required: a field + operator with a blank value commits as an unset
1036
+ * placeholder (see {@link isConditionUnset}).
1037
+ */
895
1038
  isConditionComplete() {
896
1039
  const field = this.pendingField();
897
1040
  if (!field)
898
1041
  return false;
899
- const operator = field.operator ?? '';
900
- if (!operator)
901
- return false;
902
- if (isValuelessOperator(operator))
903
- return true;
904
- return isValuePresent(this.valueCtrl.value);
1042
+ return (field.operator ?? '') !== '';
905
1043
  }
906
1044
  /** The operators available for a field's internal type, as autocomplete items. */
907
1045
  operatorsForField(field) {
@@ -959,7 +1097,9 @@ class SmartSearchEditController {
959
1097
  operator: operatorId,
960
1098
  operatorLabel: operatorLabelText,
961
1099
  value,
962
- conditionLabel: `${field.label} ${operatorLabelText} ${valueLabel}`
1100
+ // Omit the value segment for an unset placeholder so the chip/aria label reads
1101
+ // cleanly ("Name like") instead of carrying a trailing space.
1102
+ conditionLabel: valueLabel ? `${field.label} ${operatorLabelText} ${valueLabel}` : `${field.label} ${operatorLabelText}`
963
1103
  };
964
1104
  }
965
1105
  /**
@@ -1081,9 +1221,11 @@ class SmartSearchEditController {
1081
1221
  return fields.filter((field) => (seen.has(field.id) ? false : seen.add(field.id) && true));
1082
1222
  }
1083
1223
  // ── Table columns ──────────────────────────────────────────────────────────
1084
- /** Resolve a table field's `columnDefinitions` into full `ObjectTypeField`s. */
1085
- #tableColumns(table) {
1086
- const block = this.activeBlock();
1224
+ /**
1225
+ * Resolve a table field's `columnDefinitions` into full `ObjectTypeField`s. Defaults to
1226
+ * the active block (the inline-edit case); form mode passes the owning block explicitly.
1227
+ */
1228
+ #tableColumns(table, block = this.activeBlock()) {
1087
1229
  if (!block)
1088
1230
  return [];
1089
1231
  const tableField = this.#resolveTypeField(block.types, table.fieldId);
@@ -1270,7 +1412,7 @@ class SmartSearchGroupComponent {
1270
1412
  return node;
1271
1413
  }
1272
1414
  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 <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 }\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{display:flex;flex-direction:column;gap:var(--ymt-spacing-3xs);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-wrap:wrap;align-items:center;row-gap:var(--ymt-spacing-3xs);column-gap:var(--ymt-spacing-s)}.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: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)}\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 }); }
1415
+ 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
1416
  }
1275
1417
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: SmartSearchGroupComponent, decorators: [{
1276
1418
  type: Component,
@@ -1282,7 +1424,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
1282
1424
  MatTooltipModule,
1283
1425
  YmtIconButtonDirective,
1284
1426
  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 <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 }\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{display:flex;flex-direction:column;gap:var(--ymt-spacing-3xs);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-wrap:wrap;align-items:center;row-gap:var(--ymt-spacing-3xs);column-gap:var(--ymt-spacing-s)}.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: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)}\n"] }]
1427
+ ], 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
1428
  }], 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
1429
 
1288
1430
  const DEBOUNCE_MS = 150;
@@ -1354,6 +1496,13 @@ class SmartSearchComponent {
1354
1496
  * table columns alike.
1355
1497
  */
1356
1498
  this.skipProperties = input([], ...(ngDevMode ? [{ debugName: "skipProperties" }] : /* istanbul ignore next */ []));
1499
+ /**
1500
+ * Enables the **dynamic conditions** feature. When `true`, each committed condition can
1501
+ * be marked dynamic and a form-mode toggle appears that swaps the builder for a generated
1502
+ * fill-out form of those conditions. Off by default — the builder then behaves exactly as
1503
+ * it does without the feature.
1504
+ */
1505
+ this.supportDynamicConditions = input(false, ...(ngDevMode ? [{ debugName: "supportDynamicConditions" }] : /* istanbul ignore next */ []));
1357
1506
  /**
1358
1507
  * Emits the current CMIS query string whenever the search changes. An empty
1359
1508
  * string is emitted for an empty search (including the initial seed), so
@@ -1409,6 +1558,18 @@ class SmartSearchComponent {
1409
1558
  this.fulltext = this.ctrl.fulltext;
1410
1559
  this.objectTypes = this.ctrl.objectTypes;
1411
1560
  this.activeBlockFields = this.ctrl.activeBlockFields;
1561
+ this.formMode = this.ctrl.formMode;
1562
+ this.formFields = this.ctrl.formFields;
1563
+ this.formBlocks = this.ctrl.formBlocks;
1564
+ this.hasDynamicConditions = this.ctrl.hasDynamicConditions;
1565
+ /** Whether a committed condition is an unset placeholder (drives the chip's "fill me" affordance). */
1566
+ this.isUnset = isConditionUnset;
1567
+ /**
1568
+ * Whether an operator carries its own meaning and has no value input (`is empty`,
1569
+ * `is not empty`, date presets). Such conditions can't be made dynamic — there is
1570
+ * nothing to fill out in the form.
1571
+ */
1572
+ this.isValueless = isValuelessOperator;
1412
1573
  /**
1413
1574
  * Selected ids for the type multi-select. Falls back to the `ALL_TYPES`
1414
1575
  * sentinel when no concrete type is picked (empty selection = no restriction).
@@ -1445,6 +1606,27 @@ class SmartSearchComponent {
1445
1606
  effect(() => {
1446
1607
  this.ctrl.skipProperties.set(this.skipProperties());
1447
1608
  });
1609
+ // Mirror the dynamic-conditions opt-in into the controller; leaving the feature
1610
+ // disabled also forces form mode off.
1611
+ effect(() => {
1612
+ const enabled = this.supportDynamicConditions();
1613
+ this.ctrl.supportDynamic.set(enabled);
1614
+ if (!enabled)
1615
+ this.ctrl.exitFormMode();
1616
+ });
1617
+ // Bind each generated form row's control to the controller's overlay values. The
1618
+ // form-field set is rebuilt on every enterFormMode, so re-subscribe on change and
1619
+ // tear the previous subscriptions down via the effect's cleanup.
1620
+ effect((onCleanup) => {
1621
+ const fields = this.ctrl.formFields();
1622
+ const sub = new Subscription();
1623
+ for (const field of fields) {
1624
+ sub.add(field.control.valueChanges
1625
+ .pipe(debounceTime(DEBOUNCE_MS))
1626
+ .subscribe((val) => this.ctrl.setFormValue(field.condition, val)));
1627
+ }
1628
+ onCleanup(() => sub.unsubscribe());
1629
+ });
1448
1630
  // Drive inputTerm from whichever control is active for the current step
1449
1631
  this.ctrl.fieldCtrl.valueChanges.pipe(debounceTime(DEBOUNCE_MS), takeUntilDestroyed()).subscribe((val) => {
1450
1632
  const step = this.ctrl.step();
@@ -1505,6 +1687,9 @@ class SmartSearchComponent {
1505
1687
  /** Restore a previously {@link getState saved} search, replacing the current one. */
1506
1688
  loadState(state) {
1507
1689
  this.ctrl.loadState(state);
1690
+ // The full-text term input is a local control, not bound to the signal (scope and
1691
+ // types are). Sync it from the restored state so the term box reflects the load.
1692
+ this.fulltextTermCtrl.setValue(this.ctrl.fulltext().term, { emitEvent: false });
1508
1693
  }
1509
1694
  /** Clear the whole search: discard all blocks, conditions and the full-text term. */
1510
1695
  clear() {
@@ -1512,6 +1697,26 @@ class SmartSearchComponent {
1512
1697
  this.trigger()?.closePanel();
1513
1698
  this.ctrl.reset();
1514
1699
  }
1700
+ /**
1701
+ * Toggle form mode: swap the builder for a generated fill-out form of the user-marked
1702
+ * dynamic conditions. The template is left intact — the form's values are overlaid onto
1703
+ * the query. No-op unless {@link supportDynamicConditions} is set.
1704
+ */
1705
+ toggleFormMode() {
1706
+ this.ctrl.toggleFormMode();
1707
+ }
1708
+ /** Enter form mode ("Essentials") — generated fill-out form of the dynamic conditions. */
1709
+ enterFormMode() {
1710
+ this.ctrl.enterFormMode();
1711
+ }
1712
+ /** Leave form mode ("Full Form") — back to the full builder search. */
1713
+ exitFormMode() {
1714
+ this.ctrl.exitFormMode();
1715
+ }
1716
+ /** Mark/unmark a committed condition as dynamic (surfaced in the fill-out form). */
1717
+ toggleDynamic(container, condition) {
1718
+ this.ctrl.setConditionDynamic(container, condition, !condition.dynamic);
1719
+ }
1515
1720
  /**
1516
1721
  * Set the top-level combinator joining the query units (the full-text unit and
1517
1722
  * the type blocks). Kept for state round-tripping; the UI currently always
@@ -1677,9 +1882,8 @@ class SmartSearchComponent {
1677
1882
  return;
1678
1883
  if (this.ctrl.step() !== 'value')
1679
1884
  return;
1885
+ // No value guard: a blank value commits an unset placeholder (fill-in template).
1680
1886
  const raw = this.ctrl.valueCtrl.value;
1681
- if (!isValuePresent(raw))
1682
- return;
1683
1887
  const field = this.ctrl.pendingField();
1684
1888
  const fieldOp = field?.operator ?? '';
1685
1889
  if (!field || !fieldOp)
@@ -1895,7 +2099,7 @@ class SmartSearchComponent {
1895
2099
  this.#host.nativeElement.querySelector(`.block[data-block-id="${blockId}"] .add-condition-btn`)?.focus();
1896
2100
  }
1897
2101
  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 }); }
2102
+ 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
2103
  }
1900
2104
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: SmartSearchComponent, decorators: [{
1901
2105
  type: Component,
@@ -1913,8 +2117,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
1913
2117
  RendererDirective,
1914
2118
  SmartSearchGroupComponent,
1915
2119
  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 }] }] } });
2120
+ ], 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"] }]
2121
+ }], 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
2122
 
1919
2123
  /**
1920
2124
  * Convenience NgModule that imports and re-exports {@link SmartSearchComponent}.