@praxisui/table 10.0.0-rc.33 → 10.0.0-rc.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ai/component-registry.json +2 -2
- package/fesm2022/{praxisui-table-praxisui-table-J6jBxItd.mjs → praxisui-table-praxisui-table-BG-ZEZsK.mjs} +160 -33
- package/fesm2022/{praxisui-table-table-agentic-authoring-turn-flow-rii72oJn.mjs → praxisui-table-table-agentic-authoring-turn-flow-2KiSlvRK.mjs} +1 -1
- package/fesm2022/{praxisui-table-table-ai.adapter-CBpOO8mr.mjs → praxisui-table-table-ai.adapter-B4AP612N.mjs} +1 -1
- package/fesm2022/praxisui-table.mjs +1 -1
- package/package.json +10 -10
- package/types/praxisui-table.d.ts +7 -0
|
@@ -11738,6 +11738,22 @@ class FilterSettingsComponent {
|
|
|
11738
11738
|
const m = (this.metadata || []).find((x) => x?.name === name);
|
|
11739
11739
|
return m?.label || name;
|
|
11740
11740
|
}
|
|
11741
|
+
getFieldTypeBadge(meta) {
|
|
11742
|
+
if (!meta)
|
|
11743
|
+
return '';
|
|
11744
|
+
return String(meta.type || meta.dataType || meta.controlType || '').trim();
|
|
11745
|
+
}
|
|
11746
|
+
getFieldDescription(meta) {
|
|
11747
|
+
if (!meta)
|
|
11748
|
+
return '';
|
|
11749
|
+
return String(meta.description || meta.helpText || meta.hint || '').trim();
|
|
11750
|
+
}
|
|
11751
|
+
getSelectedAlwaysVisibleTriggerLabel() {
|
|
11752
|
+
const selected = this.form.controls.alwaysVisibleFields.value || [];
|
|
11753
|
+
if (!selected.length)
|
|
11754
|
+
return '';
|
|
11755
|
+
return selected.map((name) => this.getFieldLabel(name)).join(', ');
|
|
11756
|
+
}
|
|
11741
11757
|
onAlwaysVisibleFieldSearchChange(value) {
|
|
11742
11758
|
this.alwaysVisibleFieldSearchText = String(value || '').trim();
|
|
11743
11759
|
}
|
|
@@ -11755,7 +11771,12 @@ class FilterSettingsComponent {
|
|
|
11755
11771
|
: source.filter((meta) => {
|
|
11756
11772
|
const name = this.normalizeText(meta?.name);
|
|
11757
11773
|
const label = this.normalizeText(meta?.label);
|
|
11758
|
-
|
|
11774
|
+
const type = this.normalizeText(this.getFieldTypeBadge(meta));
|
|
11775
|
+
const description = this.normalizeText(this.getFieldDescription(meta));
|
|
11776
|
+
return (name.includes(query) ||
|
|
11777
|
+
label.includes(query) ||
|
|
11778
|
+
type.includes(query) ||
|
|
11779
|
+
description.includes(query));
|
|
11759
11780
|
});
|
|
11760
11781
|
return [...filtered].sort((a, b) => {
|
|
11761
11782
|
const aSelected = selected.has(a?.name || '') ? 0 : 1;
|
|
@@ -13053,7 +13074,7 @@ class FilterSettingsComponent {
|
|
|
13053
13074
|
// In a real implementation, this would trigger schema reload in PraxisFilter
|
|
13054
13075
|
}
|
|
13055
13076
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: FilterSettingsComponent, deps: [{ token: i1$1.FormBuilder }], target: i0.ɵɵFactoryTarget.Component });
|
|
13056
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: FilterSettingsComponent, isStandalone: true, selector: "filter-settings", inputs: { metadata: "metadata", metadataSource: "metadataSource", metadataLoading: "metadataLoading", metadataErrorMsg: "metadataErrorMsg", settings: "settings", configKey: "configKey" }, outputs: { settingsChange: "settingsChange" }, providers: [providePraxisI18nConfig(PRAXIS_TABLE_EDITOR_I18N_CONFIG)], usesOnChanges: true, ngImport: i0, template: "<form [formGroup]=\"form\">\n <mat-tab-group (selectedTabChange)=\"onTabChange($event)\">\n <mat-tab label=\"Sempre Vis\u00EDveis\">\n <div class=\"always-visible-tab-flow\">\n <mat-form-field appearance=\"outline\" floatLabel=\"always\" class=\"full-width\">\n <mat-label>Campos sempre vis\u00EDveis</mat-label>\n <mat-select\n formControlName=\"alwaysVisibleFields\"\n multiple\n >\n @for (meta of filteredAlwaysVisibleMetadata(); track trackFieldMetadataByName($index, meta)) {\n <mat-option\n [value]=\"meta.name\"\n >\n {{ meta.label || meta.name }}\n </mat-option>\n }\n </mat-select>\n @if (metadataLoading && metadataSource === 'filter-dto') {\n <mat-hint>\n Carregando campos do DTO de filtro...\n </mat-hint>\n }\n @if (metadataErrorMsg) {\n <mat-error>{{ metadataErrorMsg }}</mat-error>\n }\n @if (!metadataLoading && !metadataErrorMsg && !metadata.length && metadataSource === 'filter-dto') {\n <mat-hint\n >\n Nenhum campo do DTO de filtro dispon\u00EDvel para este recurso.\n </mat-hint>\n }\n @if (!metadataLoading && !metadataErrorMsg && metadata.length && !hasAlwaysVisibleMetadataResults() && alwaysVisibleFieldSearchText) {\n <mat-hint\n >\n Nenhum campo encontrado para \"{{ alwaysVisibleFieldSearchText }}\".\n </mat-hint>\n }\n <button\n praxisIconButton=\"help_outline\"\n size=\"dense\"\n matSuffix\n type=\"button\"\n class=\"praxis-help-icon-button\"\n matTooltip=\"Escolha os campos que aparecem sempre na barra de filtros.\"\n aria-label=\"Escolha os campos que aparecem sempre na barra de filtros.\"\n ></button>\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Buscar campo</mat-label>\n <input\n matInput\n [ngModel]=\"alwaysVisibleFieldSearchText\"\n (ngModelChange)=\"onAlwaysVisibleFieldSearchChange($event)\"\n [ngModelOptions]=\"{standalone: true}\"\n placeholder=\"Filtrar por nome ou label\"\n />\n @if (alwaysVisibleFieldSearchText) {\n <button\n praxisIconButton=\"close\"\n size=\"dense\"\n matSuffix\n type=\"button\"\n (click)=\"clearAlwaysVisibleFieldSearch()\"\n aria-label=\"Limpar busca de campo\"\n ></button>\n }\n </mat-form-field>\n <p class=\"field-hint\">\n {{ alwaysVisibleSelectionCount() }} campo(s) selecionado(s).\n </p>\n <div class=\"always-visible-context\">\n <div class=\"always-visible-summary\" role=\"status\" aria-live=\"polite\">\n <div class=\"always-visible-summary-item\">\n <span class=\"summary-value\">{{ alwaysVisibleSelectionCount() }}</span>\n <span class=\"summary-label\">campos fixos</span>\n </div>\n <div class=\"always-visible-summary-item\">\n <span class=\"summary-value\">{{ activeAlwaysVisibleOverrideCount() }}</span>\n <span class=\"summary-label\">overrides ativos</span>\n </div>\n </div>\n <nav\n class=\"always-visible-quick-nav\"\n aria-label=\"Navega\u00E7\u00E3o r\u00E1pida da se\u00E7\u00E3o Sempre Vis\u00EDveis\"\n >\n <button\n mat-stroked-button\n type=\"button\"\n [class.quick-nav-active]=\"isAlwaysVisibleSectionActive('always-visible-order')\"\n [attr.aria-current]=\"isAlwaysVisibleSectionActive('always-visible-order') ? 'location' : null\"\n aria-controls=\"always-visible-order\"\n (click)=\"scrollToAlwaysVisibleSection('always-visible-order')\"\n >\n Ordem\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n [class.quick-nav-active]=\"isAlwaysVisibleSectionActive('always-visible-metadata')\"\n [attr.aria-current]=\"isAlwaysVisibleSectionActive('always-visible-metadata') ? 'location' : null\"\n aria-controls=\"always-visible-metadata\"\n (click)=\"scrollToAlwaysVisibleSection('always-visible-metadata')\"\n >\n Metadados\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n [class.quick-nav-active]=\"isAlwaysVisibleSectionActive('always-visible-json')\"\n [attr.aria-current]=\"isAlwaysVisibleSectionActive('always-visible-json') ? 'location' : null\"\n aria-controls=\"always-visible-json\"\n (click)=\"scrollToAlwaysVisibleSection('always-visible-json')\"\n >\n JSON\n </button>\n </nav>\n </div>\n\n <div\n id=\"always-visible-order\"\n class=\"options options-compact-flow always-visible-section-anchor\"\n [class.section-active]=\"isAlwaysVisibleSectionActive('always-visible-order')\"\n [style.--section-enter-delay]=\"sectionEnterDelay('always-visible-order')\"\n tabindex=\"-1\"\n (focusin)=\"onAlwaysVisibleSectionFocus('always-visible-order')\"\n >\n <div class=\"options-header\">\n <h4 class=\"section-title\">Ordem dos campos</h4>\n <p class=\"section-help\">\n Reordene no formato mais pr\u00F3ximo da toolbar real: faixa de a\u00E7\u00F5es, barra inline e grid.\n </p>\n </div>\n <div class=\"order-layout-toolbar\">\n <mat-button-toggle-group\n class=\"order-viewport-toggle-group\"\n [value]=\"orderPreviewViewport\"\n (change)=\"onAlwaysVisibleViewportChange($event.value)\"\n aria-label=\"Largura simulada da toolbar\"\n >\n @for (viewport of alwaysVisibleViewports; track viewport) {\n <mat-button-toggle\n [value]=\"viewport\"\n [attr.aria-label]=\"alwaysVisibleViewportHint(viewport)\"\n >\n {{ alwaysVisibleViewportLabel(viewport) }}\n </mat-button-toggle>\n }\n </mat-button-toggle-group>\n <span class=\"order-viewport-caption\">\n {{ alwaysVisibleViewportWidth() }}px\n </span>\n </div>\n <p class=\"section-help\">\n Ajuste o viewport para simular a quebra real das linhas na toolbar.\n </p>\n <div class=\"order-layout-viewport\">\n <div\n class=\"order-layout-frame\"\n [attr.data-viewport]=\"orderPreviewViewport\"\n [style.maxWidth.px]=\"alwaysVisibleViewportWidth()\"\n >\n <div class=\"order-layout-composer\" cdkDropListGroup>\n @for (lane of alwaysVisibleLayoutLanes; track lane) {\n <section\n class=\"order-lane\"\n [attr.data-lane]=\"lane\"\n >\n @if (alwaysVisibleLaneItems(lane); as laneItems) {\n <header class=\"order-lane-header\">\n <div class=\"order-lane-title-block\">\n <h5 class=\"order-lane-title\">{{ alwaysVisibleLaneLabel(lane) }}</h5>\n <p class=\"order-lane-help\">{{ alwaysVisibleLaneHint(lane) }}</p>\n </div>\n <span class=\"order-lane-count\">{{ laneItems.length }}</span>\n </header>\n <div\n cdkDropList\n class=\"order-lane-canvas\"\n [id]=\"alwaysVisibleLaneDropListId(lane)\"\n [class.is-empty]=\"!laneItems.length\"\n [cdkDropListData]=\"laneItems\"\n [cdkDropListEnterPredicate]=\"allowAlwaysVisibleLaneEnter\"\n cdkDropListOrientation=\"horizontal\"\n [attr.aria-label]=\"'Faixa ' + alwaysVisibleLaneLabel(lane)\"\n (cdkDropListDropped)=\"dropAlwaysVisibleLane(lane, $event)\"\n >\n @for (item of laneItems; track trackAlwaysVisibleLayoutItemByName(i, item); let i = $index) {\n <button\n type=\"button\"\n class=\"order-pill\"\n cdkDrag\n [cdkDragData]=\"item\"\n [attr.aria-label]=\"alwaysVisibleLayoutItemAriaLabel(item, lane, i, laneItems.length)\"\n (keydown)=\"onAlwaysVisibleLaneItemKeydown($event, lane, item.name)\"\n >\n <mat-icon [praxisIcon]=\"'drag_indicator'\" cdkDragHandle></mat-icon>\n <span class=\"order-pill-label\">{{ item.label }}</span>\n <span class=\"order-pill-badges\">\n <span class=\"order-pill-chip\">{{ item.controlTypeLabel }}</span>\n <span class=\"order-pill-chip order-pill-chip-width\">{{ item.widthHint }}</span>\n @if (item.hasOverride) {\n <span class=\"order-pill-chip order-pill-chip-override\">\n Override\n </span>\n }\n </span>\n </button>\n }\n @if (!laneItems.length) {\n <p class=\"order-lane-empty\">\n {{ alwaysVisibleLaneEmptyHint(lane) }}\n </p>\n }\n </div>\n }\n </section>\n }\n </div>\n </div>\n </div>\n <p class=\"field-hint\">\n Acessibilidade: use Alt/Ctrl + setas para reordenar dentro de cada faixa. O drag & drop n\u00E3o mistura faixas.\n </p>\n @if (!form.controls.alwaysVisibleFields.value?.length) {\n <p class=\"section-help\">\n Nenhum campo selecionado. Use o seletor acima para adicionar.\n </p>\n }\n </div>\n\n <mat-divider class=\"section-divider\" role=\"presentation\"></mat-divider>\n\n <div\n id=\"always-visible-metadata\"\n class=\"options options-compact-flow always-visible-section-anchor\"\n [class.section-active]=\"isAlwaysVisibleSectionActive('always-visible-metadata')\"\n [style.--section-enter-delay]=\"sectionEnterDelay('always-visible-metadata')\"\n tabindex=\"-1\"\n (focusin)=\"onAlwaysVisibleSectionFocus('always-visible-metadata')\"\n >\n <div class=\"options-header\">\n <h4 class=\"section-title\">Editor visual de metadados</h4>\n <p class=\"section-help\">\n Edite os principais ajustes dos campos sempre vis\u00EDveis no fluxo visual. Para propriedades avan\u00E7adas, use o JSON abaixo.\n </p>\n </div>\n @if (!form.controls.alwaysVisibleFields.value?.length) {\n <p class=\"section-help\">\n Selecione campos em \u201CCampos sempre vis\u00EDveis\u201D para habilitar a edi\u00E7\u00E3o visual.\n </p>\n }\n @if (form.controls.alwaysVisibleFields.value?.length) {\n <div class=\"metadata-card-grid\">\n @for (name of form.controls.alwaysVisibleFields.value; track name) {\n <article class=\"metadata-card\">\n <header class=\"metadata-card-header\">\n <div class=\"metadata-card-title\">\n <strong>{{ getFieldLabel(name) }}</strong>\n <span>{{ name }}</span>\n </div>\n <div class=\"metadata-card-badges\">\n <span class=\"metadata-chip\">{{ getAlwaysVisibleControlTypeLabel(name) }}</span>\n @if (hasAlwaysVisibleOverride(name)) {\n <span class=\"metadata-chip metadata-chip-override\">\n Override ativo\n </span>\n }\n </div>\n </header>\n <p class=\"metadata-card-summary\">{{ getAlwaysVisibleOverridePreview(name) }}</p>\n <div class=\"metadata-card-actions\">\n <button mat-stroked-button type=\"button\" (click)=\"openAlwaysVisibleMetadataEditor(name)\">\n <mat-icon [praxisIcon]=\"'tune'\"></mat-icon>\n Editar campo\n </button>\n <button\n mat-button\n type=\"button\"\n color=\"warn\"\n (click)=\"clearAlwaysVisibleOverride(name)\"\n [disabled]=\"!hasAlwaysVisibleOverride(name)\"\n >\n Limpar override\n </button>\n </div>\n </article>\n }\n </div>\n }\n </div>\n\n <mat-divider class=\"section-divider\" role=\"presentation\"></mat-divider>\n\n <div\n id=\"always-visible-json\"\n class=\"always-visible-section-anchor\"\n [class.section-active]=\"isAlwaysVisibleSectionActive('always-visible-json')\"\n [style.--section-enter-delay]=\"sectionEnterDelay('always-visible-json')\"\n tabindex=\"-1\"\n (focusin)=\"onAlwaysVisibleSectionFocus('always-visible-json')\"\n >\n <mat-expansion-panel\n class=\"options options-compact-flow options-advanced-json\"\n [expanded]=\"!!alwaysVisibleOverridesErrorMsg\"\n >\n <mat-expansion-panel-header>\n <mat-panel-title>Avan\u00E7ado (JSON opcional)</mat-panel-title>\n <mat-panel-description>Para ajustes n\u00E3o expostos no editor visual</mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"options-header\">\n <h4 class=\"section-title\">Modo avan\u00E7ado (JSON)</h4>\n <p class=\"section-help\">\n Use JSON para ajustes em lote ou propriedades ainda n\u00E3o expostas no editor visual.\n </p>\n </div>\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>alwaysVisibleFieldMetadataOverrides (JSON)</mat-label>\n <textarea\n matInput\n rows=\"8\"\n [(ngModel)]=\"alwaysVisibleOverridesJsonText\"\n [ngModelOptions]=\"{standalone: true}\"\n ></textarea>\n </mat-form-field>\n <div class=\"actions\">\n <button mat-stroked-button type=\"button\" (click)=\"formatAlwaysVisibleOverridesJson()\">Formatar</button>\n <button mat-stroked-button color=\"primary\" type=\"button\" (click)=\"applyAlwaysVisibleOverridesJson()\">\n Validar e carregar no editor\n </button>\n </div>\n <p class=\"json-tip\">\n Esta a\u00E7\u00E3o n\u00E3o persiste a configura\u00E7\u00E3o. Para persistir, use \u201CSalvar & Fechar\u201D no rodap\u00E9.\n </p>\n <p class=\"json-tip\">\n Exemplo:\n {\"status\":{\"controlType\":\"inlineSelect\",\"clearButton\":{\"enabled\":true},\"inlineAutoSize\":{\"minWidth\":140,\"maxWidth\":280}}}\n </p>\n @if (alwaysVisibleOverridesErrorMsg) {\n <p class=\"json-error\" role=\"status\" aria-live=\"polite\">{{ alwaysVisibleOverridesErrorMsg }}</p>\n }\n </mat-expansion-panel>\n </div>\n </div>\n </mat-tab>\n <mat-tab label=\"Layout\">\n <div class=\"options\">\n <div class=\"options-header\">\n <h4 class=\"section-title\">Grid e responsividade</h4>\n <p class=\"section-help\">\n Ajuste largura m\u00EDnima e n\u00FAmero de colunas por breakpoint. Em telas menores que 600px, o layout usa 1 coluna.\n </p>\n </div>\n <div class=\"layout-preview-section\" aria-live=\"polite\">\n <h5 class=\"layout-preview-title\">Pr\u00E9-visualiza\u00E7\u00E3o do grid</h5>\n <p class=\"section-help\">\n Simula\u00E7\u00E3o com {{ layoutPreviewFields.length }} campos e largura m\u00EDnima de {{ layoutMinWidth() }}px por campo.\n </p>\n <div class=\"layout-preview-cards\">\n <article class=\"layout-preview-card\">\n <div class=\"layout-preview-header\">\n <strong>Mobile</strong>\n <span><600px \u00B7 {{ layoutColsFor('sm') }} coluna</span>\n </div>\n <div class=\"layout-mini-grid\" [style.gridTemplateColumns]=\"'repeat(' + layoutColsFor('sm') + ', minmax(0, 1fr))'\">\n @for (field of layoutPreviewFields; track field) {\n <span>{{ field }}</span>\n }\n </div>\n </article>\n <article class=\"layout-preview-card\">\n <div class=\"layout-preview-header\">\n <strong>Tablet</strong>\n <span>≥600px \u00B7 {{ layoutColsFor('md') }} colunas</span>\n </div>\n <div class=\"layout-mini-grid\" [style.gridTemplateColumns]=\"'repeat(' + layoutColsFor('md') + ', minmax(0, 1fr))'\">\n @for (field of layoutPreviewFields; track field) {\n <span>{{ field }}</span>\n }\n </div>\n </article>\n <article class=\"layout-preview-card\">\n <div class=\"layout-preview-header\">\n <strong>Desktop</strong>\n <span>≥960px \u00B7 {{ layoutColsFor('lg') }} colunas</span>\n </div>\n <div class=\"layout-mini-grid\" [style.gridTemplateColumns]=\"'repeat(' + layoutColsFor('lg') + ', minmax(0, 1fr))'\">\n @for (field of layoutPreviewFields; track field) {\n <span>{{ field }}</span>\n }\n </div>\n </article>\n </div>\n </div>\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Largura m\u00EDnima por campo (px)</mat-label>\n <input\n matInput\n type=\"number\"\n formControlName=\"alwaysMinWidth\"\n min=\"180\"\n max=\"480\"\n step=\"1\"\n inputmode=\"numeric\"\n praxisNumberWheelBlur\n (blur)=\"normalizeLayoutControl('alwaysMinWidth')\"\n />\n <button\n praxisIconButton=\"help_outline\"\n size=\"dense\"\n matSuffix\n type=\"button\"\n class=\"praxis-help-icon-button\"\n aria-label=\"Ajuda sobre largura m\u00EDnima por campo\"\n matTooltip=\"Define a largura m\u00EDnima de cada campo no grid. Recomendado entre 200 e 300px.\"\n ></button>\n <mat-hint align=\"start\">Intervalo permitido: 180 a 480px.</mat-hint>\n <mat-hint align=\"end\">Recomendado: 200 a 300px.</mat-hint>\n @if (layoutControlError('alwaysMinWidth'); as error) {\n <mat-error>{{ error }}</mat-error>\n }\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Colunas em tablet (≥600px)</mat-label>\n <input\n matInput\n type=\"number\"\n formControlName=\"alwaysColsMd\"\n min=\"1\"\n max=\"3\"\n step=\"1\"\n inputmode=\"numeric\"\n praxisNumberWheelBlur\n (blur)=\"normalizeLayoutControl('alwaysColsMd')\"\n />\n <button\n praxisIconButton=\"help_outline\"\n size=\"dense\"\n matSuffix\n type=\"button\"\n class=\"praxis-help-icon-button\"\n aria-label=\"Ajuda sobre colunas em tablet\"\n matTooltip=\"Define quantas colunas aparecem em telas m\u00E9dias (a partir de 600px).\"\n ></button>\n <mat-hint align=\"start\">Mobile (<600px) usa 1 coluna.</mat-hint>\n <mat-hint align=\"end\">Intervalo: 1 a 3 colunas.</mat-hint>\n @if (layoutControlError('alwaysColsMd'); as error) {\n <mat-error>{{ error }}</mat-error>\n }\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Colunas em desktop (≥960px)</mat-label>\n <input\n matInput\n type=\"number\"\n formControlName=\"alwaysColsLg\"\n min=\"1\"\n max=\"4\"\n step=\"1\"\n inputmode=\"numeric\"\n praxisNumberWheelBlur\n (blur)=\"normalizeLayoutControl('alwaysColsLg')\"\n />\n <button\n praxisIconButton=\"help_outline\"\n size=\"dense\"\n matSuffix\n type=\"button\"\n class=\"praxis-help-icon-button\"\n aria-label=\"Ajuda sobre colunas em desktop\"\n matTooltip=\"Define quantas colunas aparecem em telas grandes (a partir de 960px).\"\n ></button>\n <mat-hint align=\"start\">Use mais colunas apenas quando houver espa\u00E7o.</mat-hint>\n <mat-hint align=\"end\">Intervalo: 1 a 4 colunas.</mat-hint>\n @if (layoutControlError('alwaysColsLg'); as error) {\n <mat-error>{{ error }}</mat-error>\n }\n </mat-form-field>\n </div>\n </mat-tab>\n <mat-tab label=\"Booleanos\">\n <div class=\"options\">\n @if (!hasBooleanFieldMetadata()) {\n <p class=\"section-help\">\n Nenhum campo booleano foi detectado no schema atual. Estas prefer\u00EAncias ser\u00E3o aplicadas quando houver\n campos `toggle`/boolean no filtro.\n </p>\n }\n @if (hasBooleanFieldMetadata()) {\n <div class=\"toggle-preview-section\">\n <h4>Visualiza\u00E7\u00E3o dos Booleanos</h4>\n <p class=\"section-help\">Veja o comportamento dos booleanos inline (com texto ou compacto) e a posi\u00E7\u00E3o na barra.</p>\n <div class=\"preview-container\">\n <div class=\"preview-item\">\n <strong>Grade Principal</strong>\n <div class=\"mock-grid\" [class.with-booleans]=\"!form.controls.placeBooleansInActions.value\">\n <div class=\"mock-field\">Campo 1</div>\n <div class=\"mock-field\">Campo 2</div>\n @if (!form.controls.placeBooleansInActions.value) {\n <div class=\"mock-pill\"\n [class.compact]=\"!form.controls.showToggleLabels.value\">\n <mat-slide-toggle [checked]=\"true\"></mat-slide-toggle>\n @if (form.controls.showToggleLabels.value) {\n <span>Ativo</span>\n }\n </div>\n }\n </div>\n </div>\n <div class=\"preview-item\">\n <strong>\u00C1rea de A\u00E7\u00F5es</strong>\n <div class=\"mock-actions\" [class.with-booleans]=\"form.controls.placeBooleansInActions.value\">\n <button praxisIconButton=\"search\" size=\"dense\" type=\"button\" tabindex=\"-1\"\n aria-disabled=\"true\" aria-label=\"Exemplo de a\u00E7\u00E3o de busca\"></button>\n <button praxisIconButton=\"tune\" size=\"dense\" type=\"button\" tabindex=\"-1\"\n aria-disabled=\"true\" aria-label=\"Exemplo de a\u00E7\u00E3o de filtros\"></button>\n @if (form.controls.placeBooleansInActions.value) {\n <div class=\"mock-pill\"\n [class.compact]=\"!form.controls.showToggleLabels.value\">\n <mat-slide-toggle [checked]=\"true\"></mat-slide-toggle>\n @if (form.controls.showToggleLabels.value) {\n <span>Ativo</span>\n }\n </div>\n }\n </div>\n </div>\n </div>\n </div>\n }\n <mat-checkbox formControlName=\"placeBooleansInActions\">\n <mat-icon>swap_horiz</mat-icon>\n Posicionar booleanos na \u00E1rea de a\u00E7\u00F5es\n </mat-checkbox>\n <p class=\"field-hint\">Move os booleanos da grade para os bot\u00F5es de a\u00E7\u00E3o, liberando espa\u00E7o horizontal.</p>\n <mat-checkbox formControlName=\"showToggleLabels\">\n <mat-icon>text_fields</mat-icon>\n Mostrar texto nos booleanos inline\n </mat-checkbox>\n <p class=\"field-hint\">Quando desativado, os booleanos ficam em modo compacto (somente controle, sem texto).</p>\n </div>\n </mat-tab>\n <mat-tab label=\"Op\u00E7\u00F5es\">\n <div class=\"options\">\n <div class=\"options-header\">\n <h4 class=\"section-title\">Painel Avan\u00E7ado</h4>\n <p class=\"section-help\">Apar\u00EAncia e comportamento do painel de filtros avan\u00E7ados.</p>\n </div>\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Modo de abertura do Avan\u00E7ado</mat-label>\n <mat-select formControlName=\"advancedOpenMode\">\n <mat-option value=\"modal\">Modal (padr\u00E3o)</mat-option>\n <mat-option value=\"drawer\">Drawer (direita)</mat-option>\n </mat-select>\n <button\n praxisIconButton=\"help_outline\"\n size=\"dense\"\n matSuffix\n type=\"button\"\n class=\"praxis-help-icon-button\"\n matTooltip=\"Escolha como o painel de filtros avan\u00E7ados ser\u00E1 exibido: centralizado ou lateral.\"\n aria-label=\"Escolha como o painel de filtros avan\u00E7ados ser\u00E1 exibido: centralizado ou lateral.\"\n ></button>\n </mat-form-field>\n\n <mat-checkbox formControlName=\"advancedClearButtonsEnabled\">\n Mostrar bot\u00E3o de limpar nos campos\n </mat-checkbox>\n <p class=\"field-hint\">\n Desative para ocultar o bot\u00E3o de limpar nos campos do painel avan\u00E7ado.\n </p>\n\n <mat-checkbox formControlName=\"useInlineSearchableSelectVariant\">\n Compactar selects com busca/async/autocomplete na barra inline\n </mat-checkbox>\n <p class=\"field-hint\">\n Padr\u00E3o do filtro din\u00E2mico. Desative apenas quando o host precisar preservar o renderer dedicado por compatibilidade, performance ou comportamento legado. O lookup de entidade inline \u00E9 configurado por controlType expl\u00EDcito (`inlineEntityLookup`).\n </p>\n\n <mat-checkbox formControlName=\"useInlineRangeVariant\">\n Compactar range slider na barra inline\n </mat-checkbox>\n <p class=\"field-hint\">\n Usa o componente inline em formato chip + popover para rangeSlider (single e faixa).\n </p>\n\n <mat-checkbox formControlName=\"useInlineDateVariant\">\n Compactar campos de data na barra inline\n </mat-checkbox>\n <p class=\"field-hint\">\n Usa o componente inline dedicado para date/dateInput/datepicker, com largura por conte\u00FAdo e placeholder compacto.\n </p>\n\n <mat-checkbox formControlName=\"useInlineDateRangeVariant\">\n Compactar intervalo de datas na barra inline\n </mat-checkbox>\n <p class=\"field-hint\">\n Padr\u00E3o do filtro din\u00E2mico para dateRange/daterange. Desative apenas para preservar o renderer tradicional em hosts legados.\n </p>\n\n <mat-checkbox formControlName=\"useInlineTimeVariant\">\n Compactar campos de horario na barra inline\n </mat-checkbox>\n <p class=\"field-hint\">\n Padr\u00E3o do filtro din\u00E2mico para time/timePicker, com largura por conteudo e clear rapido. Desative apenas para manter a experi\u00EAncia legada.\n </p>\n\n <mat-checkbox formControlName=\"useInlineTimeRangeVariant\">\n Compactar intervalo de horario na barra inline\n </mat-checkbox>\n <p class=\"field-hint\">\n Padr\u00E3o do filtro din\u00E2mico para timeRange/timerange. Desative apenas para preservar o renderer tradicional em hosts legados.\n </p>\n\n <mat-checkbox formControlName=\"useInlineTreeSelectVariant\">\n Compactar tree select na barra inline\n </mat-checkbox>\n <p class=\"field-hint\">\n Padr\u00E3o do filtro din\u00E2mico para treeSelect na faixa compacta. Desative apenas quando o host depender do renderer dedicado. multiSelectTree mant\u00E9m o componente dedicado.\n </p>\n\n <mat-checkbox formControlName=\"confirmTagDelete\">Confirmar exclus\u00E3o de atalho</mat-checkbox>\n <p class=\"field-hint\">Solicita confirma\u00E7\u00E3o antes de apagar atalhos salvos.</p>\n\n <div class=\"options\">\n <h4>Tema dos Atalhos (Tags)</h4>\n\n <!-- Preview Section -->\n <div class=\"preview-section\" [class.mdc-theme-dark]=\"previewDarkMode\" [class.dark-mode]=\"previewDarkMode\">\n <div class=\"preview-controls\">\n <span class=\"preview-label\">Pr\u00E9-visualiza\u00E7\u00E3o</span>\n <button [praxisIconButton]=\"previewDarkMode ? 'light_mode' : 'dark_mode'\" size=\"dense\" type=\"button\"\n (click)=\"previewDarkMode = !previewDarkMode\"\n [matTooltip]=\"previewDarkMode ? 'Mudar para fundo claro' : 'Mudar para fundo escuro'\"\n [attr.aria-label]=\"previewDarkMode ? 'Mudar para fundo claro' : 'Mudar para fundo escuro'\"></button>\n </div>\n\n <div class=\"preview-chips praxis-filter-tags\"\n [class.outlined]=\"form.controls.tagVariant.value === 'outlined'\"\n [class.tag-color-basic]=\"form.controls.tagColor.value === 'basic'\"\n [class.tag-color-primary]=\"form.controls.tagColor.value === 'primary'\"\n [class.tag-color-accent]=\"form.controls.tagColor.value === 'accent'\"\n [class.tag-color-warn]=\"form.controls.tagColor.value === 'warn'\">\n <mat-chip-set>\n <!-- Inactive Chip -->\n <mat-chip\n [color]=\"form.controls.tagVariant.value === 'outlined' || form.controls.tagColor.value === 'basic' ? null : form.controls.tagColor.value\"\n [highlighted]=\"form.controls.tagVariant.value === 'filled' && form.controls.tagColor.value !== 'basic'\">\n Atalho Inativo\n </mat-chip>\n\n <!-- Active Chip -->\n <mat-chip class=\"active\"\n [color]=\"form.controls.tagVariant.value === 'outlined' || form.controls.tagColor.value === 'basic' ? null : form.controls.tagColor.value\"\n [highlighted]=\"form.controls.tagVariant.value === 'filled' && form.controls.tagColor.value !== 'basic'\">\n <mat-icon matChipAvatar>check</mat-icon>\n Atalho Ativo\n <button matChipTrailingIcon praxisIconButton=\"arrow_drop_down\" size=\"dense\" type=\"button\"\n class=\"preview-chip-action\" tabindex=\"-1\" aria-disabled=\"true\"\n aria-label=\"Exemplo de menu do atalho ativo\"\n [class.preview-chip-action--primary]=\"form.controls.tagButtonColor.value === 'primary'\"\n [class.preview-chip-action--accent]=\"form.controls.tagButtonColor.value === 'accent'\"\n [class.preview-chip-action--warn]=\"form.controls.tagButtonColor.value === 'warn'\"></button>\n </mat-chip>\n </mat-chip-set>\n </div>\n\n @if (getContrastWarning(); as warning) {\n <div class=\"contrast-warning\">\n <mat-icon>warning</mat-icon>\n <span>{{ warning }}</span>\n </div>\n }\n </div>\n\n <!-- Reordered Fields -->\n <mat-form-field appearance=\"outline\">\n <mat-label>Variante do atalho</mat-label>\n <mat-select formControlName=\"tagVariant\">\n <mat-option value=\"filled\">Filled (Preenchido)</mat-option>\n <mat-option value=\"outlined\">Outlined (Contorno)</mat-option>\n </mat-select>\n <button\n praxisIconButton=\"help_outline\"\n size=\"dense\"\n matSuffix\n type=\"button\"\n class=\"praxis-help-icon-button\"\n matTooltip=\"Define o estilo base do atalho.\"\n aria-label=\"Define o estilo base do atalho.\"\n ></button>\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\">\n <mat-label>Cor do atalho</mat-label>\n <mat-select formControlName=\"tagColor\">\n <mat-option value=\"primary\">Primary</mat-option>\n <mat-option value=\"accent\">Accent</mat-option>\n <mat-option value=\"warn\">Warn</mat-option>\n <mat-option value=\"basic\">Basic</mat-option>\n </mat-select>\n <button\n praxisIconButton=\"help_outline\"\n size=\"dense\"\n matSuffix\n type=\"button\"\n class=\"praxis-help-icon-button\"\n matTooltip=\"Cor de fundo (Filled) ou de contorno (Outlined).\"\n aria-label=\"Cor de fundo (Filled) ou de contorno (Outlined).\"\n ></button>\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\">\n <mat-label>Cor do bot\u00E3o do menu do atalho</mat-label>\n <mat-select formControlName=\"tagButtonColor\">\n <mat-option value=\"basic\">Basic</mat-option>\n <mat-option value=\"primary\">Primary</mat-option>\n <mat-option value=\"accent\">Accent</mat-option>\n <mat-option value=\"warn\">Warn</mat-option>\n </mat-select>\n <button\n praxisIconButton=\"help_outline\"\n size=\"dense\"\n matSuffix\n type=\"button\"\n class=\"praxis-help-icon-button\"\n matTooltip=\"Cor do bot\u00E3o de dropdown dentro do atalho ativo.\"\n aria-label=\"Cor do bot\u00E3o de dropdown dentro do atalho ativo.\"\n ></button>\n </mat-form-field>\n </div>\n\n <div class=\"options\">\n <h4>Tema dos bot\u00F5es de a\u00E7\u00E3o</h4>\n <mat-form-field appearance=\"outline\">\n <mat-label>Cor dos bot\u00F5es</mat-label>\n <mat-select formControlName=\"actionsButtonColor\">\n <mat-option value=\"basic\">Basic</mat-option>\n <mat-option value=\"primary\">Primary</mat-option>\n <mat-option value=\"accent\">Accent</mat-option>\n <mat-option value=\"warn\">Warn</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Variante dos bot\u00F5es</mat-label>\n <mat-select formControlName=\"actionsVariant\">\n <mat-option value=\"standard\">Standard</mat-option>\n <mat-option value=\"outlined\">Outlined</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n\n <div class=\"options\">\n <h4>Predefini\u00E7\u00F5es de tema</h4>\n <div class=\"preset-grid\">\n @for (p of presets; track p) {\n <button\n type=\"button\"\n class=\"preset-card\"\n (click)=\"applyPreset(p.key)\"\n [class.preset-active]=\"isPresetActive(p.key)\"\n [attr.aria-pressed]=\"isPresetActive(p.key)\"\n [attr.aria-label]=\"'Aplicar predefini\u00E7\u00E3o: ' + p.label\"\n >\n <div class=\"preset-preview\" [ngClass]=\"p.previewClass\"></div>\n <div class=\"preset-info\">\n <span class=\"preset-name\">{{ p.label }}</span>\n <span class=\"preset-desc\">{{ p.description }}</span>\n </div>\n </button>\n }\n </div>\n <button mat-button color=\"warn\" (click)=\"resetPreferences()\" class=\"restore-btn\">\n Restaurar padr\u00E3o\n </button>\n </div>\n </div>\n </mat-tab>\n @if (configKey) {\n <mat-tab label=\"Atalhos\">\n <div class=\"options\">\n <p class=\"section-help\">Os atalhos s\u00E3o salvos por chave de configura\u00E7\u00E3o e compartilhados entre sess\u00F5es.</p>\n <div class=\"tags-header\">\n <span><strong>Atalhos salvos</strong></span>\n <span class=\"spacer\"></span>\n <button mat-stroked-button color=\"warn\" (click)=\"clearTags()\">Apagar tudo</button>\n </div>\n <div class=\"new-tag\">\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>R\u00F3tulo do novo atalho</mat-label>\n <input matInput [(ngModel)]=\"newTagLabel\" [ngModelOptions]=\"{standalone: true}\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Patch (JSON, opcional)</mat-label>\n <textarea matInput rows=\"3\" [(ngModel)]=\"newTagPatchText\" [ngModelOptions]=\"{standalone: true}\"></textarea>\n </mat-form-field>\n <div>\n <button mat-stroked-button color=\"primary\" (click)=\"addTag()\">Adicionar</button>\n @if (hasCurrentDto()) {\n <button mat-stroked-button (click)=\"addTagFromCurrent()\">Adicionar a partir dos filtros atuais</button>\n }\n </div>\n <div>\n <button mat-stroked-button color=\"primary\" (click)=\"importTags()\">Importar</button>\n <button mat-stroked-button (click)=\"loadTags()\">Recarregar</button>\n <button mat-stroked-button (click)=\"saveTags()\">Salvar</button>\n </div>\n </div>\n </div>\n </mat-tab>\n }\n <mat-tab label=\"JSON\">\n <div class=\"options\">\n <p><strong>Configura\u00E7\u00E3o atual (somente leitura)</strong></p>\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Atual</mat-label>\n <textarea matInput rows=\"8\" [value]=\"effectiveJson\" readonly></textarea>\n </mat-form-field>\n\n <p><strong>Editar JSON</strong></p>\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>JSON</mat-label>\n <textarea matInput rows=\"12\" [(ngModel)]=\"settingsJsonText\" [ngModelOptions]=\"{standalone: true}\"></textarea>\n </mat-form-field>\n <div class=\"actions\">\n <button mat-stroked-button (click)=\"onJsonReload()\">Recarregar do formul\u00E1rio</button>\n <button mat-stroked-button (click)=\"onJsonFormat()\">Formatar</button>\n <button mat-stroked-button color=\"primary\" (click)=\"onJsonApply()\">Carregar JSON no formul\u00E1rio</button>\n </div>\n <p class=\"json-tip\">\n Dica: esta a\u00E7\u00E3o apenas atualiza o editor local. Para persistir, use os bot\u00F5es Aplicar ou\n Salvar & Fechar no rodap\u00E9 do painel.\n </p>\n @if (jsonErrorMsg) {\n <p class=\"json-error\" role=\"status\" aria-live=\"polite\">{{ jsonErrorMsg }}</p>\n }\n </div>\n </mat-tab>\n <mat-tab label=\"Comportamento\">\n <div class=\"options\">\n <mat-checkbox\n formControlName=\"showAddButton\"\n data-testid=\"filter-settings-show-add-button\"\n >\n Exibir bot\u00E3o \"Gerenciar campos\" na barra\n </mat-checkbox>\n <mat-checkbox\n formControlName=\"showFilterSettings\"\n data-testid=\"filter-settings-show-filter-settings\"\n >\n Exibir bot\u00E3o \"Configura\u00E7\u00F5es do filtro\" na barra\n </mat-checkbox>\n <mat-checkbox formControlName=\"allowSaveTags\">Permitir salvar atalhos</mat-checkbox>\n </div>\n </mat-tab>\n <mat-tab label=\"Avan\u00E7ado\">\n <div class=\"options\">\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Debounce de altera\u00E7\u00F5es (ms)</mat-label>\n <input matInput type=\"number\" formControlName=\"changeDebounceMs\" min=\"100\" max=\"1000\" />\n @if (form.controls.changeDebounceMs.invalid) { <mat-error>Valor deve estar entre 100 e 1000</mat-error> }\n </mat-form-field>\n <div class=\"actions\">\n <button mat-stroked-button color=\"warn\" (click)=\"resetPreferences()\">\n <mat-icon [praxisIcon]=\"'restart_alt'\"></mat-icon>\n Redefinir prefer\u00EAncias do filtro\n </button>\n <button mat-stroked-button color=\"accent\" (click)=\"clearPersistedFilters()\">\n <mat-icon [praxisIcon]=\"'clear_all'\"></mat-icon>\n Limpar filtros salvos\n </button>\n <button mat-stroked-button (click)=\"forceSchemaReload()\">\n <mat-icon [praxisIcon]=\"'refresh'\"></mat-icon>\n For\u00E7ar recarregamento do schema\n </button>\n </div>\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>N\u00EDvel de log</mat-label>\n <mat-select formControlName=\"logLevel\">\n <mat-option value=\"none\">Nenhum</mat-option>\n <mat-option value=\"error\">Erro</mat-option>\n <mat-option value=\"warn\">Aviso</mat-option>\n <mat-option value=\"info\">Info</mat-option>\n <mat-option value=\"debug\">Debug</mat-option>\n </mat-select>\n <button\n praxisIconButton=\"help_outline\"\n size=\"dense\"\n matSuffix\n type=\"button\"\n class=\"praxis-help-icon-button\"\n matTooltip=\"Persistido no contrato para integra\u00E7\u00E3o host. Sem efeito direto no runtime atual do filtro.\"\n aria-label=\"Persistido no contrato para integra\u00E7\u00E3o host. Sem efeito direto no runtime atual do filtro.\"\n ></button>\n </mat-form-field>\n <mat-checkbox formControlName=\"enablePerformanceMetrics\">Habilitar m\u00E9tricas de performance</mat-checkbox>\n <p class=\"field-hint\">Prefer\u00EAncia persistida para integra\u00E7\u00E3o host. Sem efeito direto no runtime atual do filtro.</p>\n </div>\n </mat-tab>\n <mat-tab [label]=\"presentationTabLabel\">\n <praxis-filter-presentation-editor [value]=\"form.controls.presentation.value\" (valueChange)=\"updatePresentation($event)\" />\n </mat-tab>\n </mat-tab-group>\n</form>\n", styles: [":host{display:block;min-width:0}.full-width{width:100%}.always-visible-tab-flow{display:flex;flex-direction:column;gap:4px;padding-top:12px}.always-visible-context{position:sticky;top:0;z-index:var(--praxis-layer-sticky-local, 100);display:flex;flex-direction:column;gap:6px;padding:8px;border-radius:10px;border:1px solid var(--md-sys-color-outline-variant);background:color-mix(in srgb,var(--md-sys-color-surface-container-high) 88%,transparent);-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.always-visible-summary{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;padding:8px 10px;border:1px solid var(--md-sys-color-outline-variant);border-radius:10px;background:var(--md-sys-color-surface-container-low)}.always-visible-summary-item{display:flex;align-items:baseline;gap:6px;min-width:0}.summary-value{font-size:1rem;font-weight:700;color:var(--md-sys-color-primary)}.summary-label{font-size:.8rem;color:var(--md-sys-color-on-surface-variant);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.always-visible-quick-nav{display:flex;flex-wrap:wrap;gap:6px}.always-visible-quick-nav button{min-height:30px;font-size:.76rem;padding-inline:10px;transition:border-color .18s ease,background-color .18s ease,color .18s ease}.always-visible-quick-nav button.quick-nav-active{border-color:color-mix(in srgb,var(--md-sys-color-primary) 50%,var(--md-sys-color-outline-variant));background:color-mix(in srgb,var(--md-sys-color-primary-container) 34%,var(--md-sys-color-surface-container-low));color:var(--md-sys-color-primary)}.always-visible-section-anchor{scroll-margin-top:140px;opacity:0;transform:translateY(8px);animation:always-visible-section-enter .22s cubic-bezier(.2,0,0,1) forwards;animation-delay:var(--section-enter-delay, 0ms)}.always-visible-section-anchor.section-active,.always-visible-section-anchor.section-active .options-advanced-json{box-shadow:0 0 0 1px color-mix(in srgb,var(--md-sys-color-primary) 40%,transparent)}.always-visible-section-anchor:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}@keyframes always-visible-section-enter{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.options{display:flex;flex-direction:column;gap:.75rem;padding:12px;width:100%;max-width:100%;min-width:0;box-sizing:border-box;border:1px solid var(--md-sys-color-outline-variant);border-radius:12px;background:var(--md-sys-color-surface-container)}.options .options{padding:0;border:0;border-radius:0;background:transparent}.options-compact-flow{gap:8px;padding-top:10px;padding-bottom:10px}.section-divider{margin:2px;border-top-style:dashed;border-top-color:var(--md-sys-color-outline-variant);opacity:.28}.options-advanced-json{display:block;padding:0;overflow:hidden}:host ::ng-deep .options-advanced-json .mat-expansion-panel-header{padding:0 12px;min-height:52px}:host ::ng-deep .options-advanced-json .mat-expansion-panel-body{padding:0 12px 12px}.options-header{display:flex;flex-direction:column;gap:6px}.section-title{margin:0;font-size:.95rem;font-weight:600;color:var(--md-sys-color-on-surface)}.section-help{margin:0;font-size:.85rem;color:var(--md-sys-color-on-surface);opacity:.84}.field-hint{margin:-4px 0 0;font-size:.8rem;color:var(--md-sys-color-on-surface);opacity:.78}.praxis-help-icon-button{vertical-align:middle}.order-layout-toolbar{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px}.order-viewport-toggle-group{max-width:100%}.order-viewport-caption{display:inline-flex;align-items:center;min-height:28px;padding:0 10px;border-radius:999px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-lowest);font-size:.75rem;font-weight:600;color:var(--md-sys-color-on-surface-variant)}.order-layout-viewport{width:100%;display:flex;justify-content:center;padding:10px;border-radius:14px;border:1px solid var(--md-sys-color-outline-variant);background:color-mix(in srgb,var(--md-sys-color-surface-container-high) 74%,transparent)}.order-layout-frame{width:100%;transition:max-width .18s ease}.order-layout-frame[data-viewport=tablet] .order-lane-canvas{gap:7px}.order-layout-frame[data-viewport=mobile] .order-lane-canvas{gap:6px;padding:7px}.order-layout-frame[data-viewport=mobile] .order-pill{width:100%;max-width:100%;border-radius:12px}.order-layout-composer{display:flex;flex-direction:column;gap:9px}.order-lane{display:flex;flex-direction:column;gap:6px}.order-lane-header{display:flex;align-items:flex-start;justify-content:space-between;gap:10px}.order-lane-title-block{display:flex;flex-direction:column;gap:2px;min-width:0}.order-lane-title{margin:0;font-size:.85rem;font-weight:600;color:var(--md-sys-color-on-surface)}.order-lane-help{margin:0;font-size:.76rem;color:var(--md-sys-color-on-surface-variant)}.order-lane-count{display:inline-flex;align-items:center;justify-content:center;min-width:24px;height:24px;border-radius:999px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-low);color:var(--md-sys-color-on-surface-variant);font-size:.76rem;font-weight:600;padding:0 8px}.order-lane-canvas{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-height:56px;padding:8px;border:1px dashed var(--md-sys-color-outline-variant);border-radius:12px;background:var(--md-sys-color-surface-container-low)}.order-lane-canvas.cdk-drop-list-dragging{border-color:color-mix(in srgb,var(--md-sys-color-primary) 45%,var(--md-sys-color-outline-variant))}.order-lane[data-lane=actions] .order-lane-canvas{background:color-mix(in srgb,var(--md-sys-color-secondary-container) 28%,var(--md-sys-color-surface-container-low))}.order-lane[data-lane=inline] .order-lane-canvas{background:color-mix(in srgb,var(--md-sys-color-primary-container) 22%,var(--md-sys-color-surface-container-low))}.order-lane[data-lane=grid] .order-lane-canvas{background:color-mix(in srgb,var(--md-sys-color-tertiary-container) 18%,var(--md-sys-color-surface-container-low))}.order-lane-canvas.is-empty{justify-content:flex-start}.order-pill{display:inline-flex;align-items:center;gap:8px;max-width:min(100%,520px);min-height:38px;border:1px solid var(--md-sys-color-outline-variant);border-radius:999px;background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface);padding:4px 8px;box-sizing:border-box;cursor:grab;font:inherit;text-align:left}.order-pill:hover{border-color:var(--md-sys-color-outline)}.order-pill:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.order-pill mat-icon{opacity:.75}.order-pill-label{min-width:0;max-width:190px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.86rem;font-weight:500}.order-pill-badges{display:inline-flex;flex-wrap:wrap;align-items:center;gap:4px}.order-pill-chip{display:inline-flex;align-items:center;border-radius:999px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-lowest);color:var(--md-sys-color-on-surface-variant);font-size:.7rem;font-weight:600;padding:1px 7px;white-space:nowrap}.order-pill-chip-width{font-weight:500;opacity:.86}.order-pill-chip-override{border-color:color-mix(in srgb,var(--md-sys-color-primary) 42%,var(--md-sys-color-outline-variant));color:var(--md-sys-color-primary)}.order-lane-empty{margin:0;font-size:.78rem;color:var(--md-sys-color-on-surface-variant)}.order-pill.cdk-drag-preview{box-shadow:var(--md-sys-elevation-level2)}.order-pill.cdk-drag-placeholder{opacity:.45}.layout-preview-section{display:flex;flex-direction:column;gap:10px;padding:12px;border:1px solid var(--md-sys-color-outline-variant);border-radius:10px;background:var(--md-sys-color-surface-container-low);min-width:0}.layout-preview-title{margin:0;font-size:.95rem;font-weight:600;color:var(--md-sys-color-on-surface)}.layout-preview-cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px;min-width:0}.layout-preview-card{display:flex;flex-direction:column;gap:8px;padding:10px;border:1px solid var(--md-sys-color-outline-variant);border-radius:8px;background:var(--md-sys-color-surface-container);min-width:0}.layout-preview-header{display:flex;flex-direction:column;gap:2px}.layout-preview-header strong{font-size:.85rem;color:var(--md-sys-color-on-surface)}.layout-preview-header span{font-size:.78rem;color:var(--md-sys-color-on-surface-variant)}.layout-mini-grid{display:grid;gap:6px;min-width:0}.layout-mini-grid span{display:block;min-width:0;padding:6px 8px;border-radius:6px;background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container);font-size:.75rem;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tags-header{display:flex;align-items:center;gap:8px}.spacer{flex:1 1 auto}.new-tag{display:flex;flex-direction:column;gap:8px}.preset-row{display:flex;flex-wrap:wrap;gap:8px}.option-with-preview{display:flex;align-items:center;gap:8px}.preview-card{width:20px;height:20px;background:var(--md-sys-color-surface-container-high);border:1px solid var(--md-sys-color-outline-variant);border-radius:4px}.preview-frosted{width:20px;height:20px;background:var(--md-sys-color-surface-container);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border:1px solid var(--md-sys-color-outline-variant);border-radius:4px}.toggle-preview-section{margin-bottom:1.5rem;padding:1rem;background:var(--md-sys-color-surface-container-low);border-radius:8px;border:1px solid var(--md-sys-color-outline-variant)}.toggle-preview-section h4{margin:0 0 1rem;font-size:1.1rem;color:var(--md-sys-color-on-surface)}.preview-container{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:1rem}.preview-item{text-align:center}.preview-item strong{display:block;margin-bottom:.5rem;font-weight:500}.mock-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));align-items:center;gap:8px;padding:8px;background:var(--md-sys-color-surface);border:1px solid var(--md-sys-color-outline);border-radius:8px;min-height:72px}.mock-grid.with-booleans{grid-template-columns:repeat(3,minmax(0,1fr))}.mock-field{padding:4px 8px;background:var(--md-sys-color-primary-container);border-radius:6px;font-size:.8rem;text-align:center}.mock-actions{display:flex;align-items:center;gap:8px;padding:8px;background:var(--md-sys-color-surface);border:1px solid var(--md-sys-color-outline);border-radius:8px;min-height:48px}.mock-pill{display:flex;align-items:center;gap:8px;min-height:34px;padding:0 10px;border-radius:999px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface-variant);font-size:.82rem;justify-self:start}.mock-pill.compact{padding-inline:8px;gap:0}.mock-pill mat-slide-toggle{--mdc-switch-selected-track-color: var(--md-sys-color-primary);--mdc-switch-selected-handle-color: var(--md-sys-color-on-primary);transform:scale(.8);pointer-events:none}.mock-pill span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:500}.preview-section{display:flex;flex-direction:column;gap:16px;padding:16px;background:var(--md-sys-color-surface-container-low);border:1px solid var(--md-sys-color-outline-variant);border-radius:8px;margin-bottom:16px;box-shadow:var(--md-sys-elevation-level1);transition:background-color .3s,color .3s}.preview-section.dark-mode{background:var(--md-sys-color-surface-container-low);color:var(--md-sys-color-on-surface);border-color:var(--md-sys-color-outline-variant)}.preview-section.dark-mode .preview-label{color:var(--md-sys-color-on-surface-variant)}.preview-controls{display:flex;justify-content:space-between;align-items:center}.preview-label{font-weight:500;font-size:.9rem;color:var(--md-sys-color-on-surface-variant)}.preview-chips{display:flex;justify-content:center;padding:16px 0}.preview-chip-action--primary{color:var(--md-sys-color-primary)}.preview-chip-action--accent{color:var(--md-sys-color-tertiary)}.preview-chip-action--warn{color:var(--md-sys-color-error)}.praxis-filter-tags.outlined .mat-mdc-chip{background:transparent!important;--mat-chip-outline-width: 1px;--mat-chip-outline-color: var(--md-sys-color-outline-variant);box-shadow:inset 0 0 0 var(--mat-chip-outline-width) var(--mat-chip-outline-color)}.praxis-filter-tags.outlined.tag-color-primary .mat-mdc-chip{--mat-chip-outline-color: var(--md-sys-color-primary)}.praxis-filter-tags.outlined.tag-color-accent .mat-mdc-chip{--mat-chip-outline-color: var(--md-sys-color-tertiary)}.praxis-filter-tags.outlined.tag-color-warn .mat-mdc-chip{--mat-chip-outline-color: var(--md-sys-color-error)}.praxis-filter-tags.outlined mat-chip.active .mat-mdc-chip,.praxis-filter-tags.outlined .mat-mdc-chip.mat-mdc-chip-highlighted{--mat-chip-outline-width: 2px;box-shadow:inset 0 0 0 var(--mat-chip-outline-width) var(--mat-chip-outline-color)!important}.praxis-filter-tags.outlined.tag-color-basic mat-chip.active .mat-mdc-chip{--mat-chip-outline-color: var(--md-sys-color-primary)}.praxis-filter-tags .mat-mdc-standard-chip .mdc-evolution-chip__text-label{display:flex;align-items:center;gap:4px}.contrast-warning{display:flex;align-items:center;gap:8px;padding:8px 12px;background:var(--md-sys-color-error-container);color:var(--md-sys-color-on-error-container);border-radius:4px;font-size:.85rem}.contrast-warning mat-icon{font-size:20px;width:20px;height:20px}.preset-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:12px;margin-bottom:12px}.preset-card{display:flex;width:100%;flex-direction:column;gap:8px;padding:12px;text-align:left;font:inherit;color:inherit;background:transparent;border:1px solid var(--md-sys-color-outline-variant);border-radius:8px;cursor:pointer;transition:all .2s}.preset-card:hover{background:var(--md-sys-color-surface-container-high);border-color:var(--md-sys-color-outline)}.preset-card:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.preset-card.preset-active{border-color:var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary-container) 34%,transparent);box-shadow:inset 0 0 0 1px var(--md-sys-color-primary)}.preset-preview{height:24px;border-radius:4px;background:var(--md-sys-color-surface-variant)}.preset-preview.preview-minimal{background:transparent;border:1px solid var(--md-sys-color-outline)}.preset-preview.preview-primary{background:var(--md-sys-color-primary-container)}.preset-preview.preview-accent{background:var(--md-sys-color-tertiary-container)}.preset-preview.preview-warn{background:var(--md-sys-color-error-container)}.preset-info{display:flex;flex-direction:column}.preset-name{font-weight:500;font-size:.9rem}.preset-desc{font-size:.75rem;opacity:.7}.restore-btn{align-self:flex-start}.metadata-card-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:10px}.metadata-card{display:flex;flex-direction:column;gap:10px;padding:12px;border-radius:10px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-low)}.metadata-card-header{display:flex;justify-content:space-between;align-items:flex-start;gap:10px}.metadata-card-title{display:flex;flex-direction:column;min-width:0}.metadata-card-title strong{font-size:.92rem;color:var(--md-sys-color-on-surface)}.metadata-card-title span{font-size:.78rem;color:var(--md-sys-color-on-surface-variant);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.metadata-card-badges{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:6px}.metadata-chip{display:inline-flex;align-items:center;border-radius:999px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container);color:var(--md-sys-color-on-surface-variant);font-size:.78rem;font-weight:500;padding:2px 8px;white-space:nowrap}.metadata-chip-override{border-color:color-mix(in srgb,var(--md-sys-color-primary) 40%,var(--md-sys-color-outline-variant));color:var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary-container) 38%,var(--md-sys-color-surface-container))}.metadata-card-summary{margin:0;font-size:.8rem;color:var(--md-sys-color-on-surface-variant)}.metadata-card-actions{display:flex;flex-wrap:wrap;gap:6px}.metadata-card-actions button{display:inline-flex;align-items:center;gap:4px}.metadata-card-actions button:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:1px}:host ::ng-deep .metadata-card-actions button[disabled]{opacity:.62}.json-tip{margin:6px 0 0;font-size:.85rem;color:var(--md-sys-color-on-surface-variant)}.json-error{margin-top:6px;font-size:.85rem;color:var(--md-sys-color-error)}@media(prefers-reduced-motion:reduce){.always-visible-section-anchor{animation:none;opacity:1;transform:none}.always-visible-quick-nav button{transition:none}}@media(max-width:680px){.order-layout-toolbar{flex-direction:column;align-items:stretch}.order-viewport-caption{align-self:flex-start}.order-layout-viewport{padding:8px}.always-visible-context{position:static;-webkit-backdrop-filter:none;backdrop-filter:none}.always-visible-summary{grid-template-columns:1fr}.always-visible-quick-nav{display:grid;grid-template-columns:repeat(3,minmax(0,1fr))}.order-pill{width:100%;max-width:100%}.order-pill-label{max-width:none}.order-pill-badges{display:none}}\n"], dependencies: [{ kind: "component", type: FilterPresentationEditorComponent, selector: "praxis-filter-presentation-editor", inputs: ["value"], outputs: ["valueChange"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i10$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { 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.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1$1.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "component", type: i4$1.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i4$1.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2$1.MatLabel, selector: "mat-label" }, { kind: "directive", type: i2$1.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i2$1.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i2$1.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i4.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: "component", type: i4.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3$1.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i7$1.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "ngmodule", type: MatListModule }, { kind: "component", type: i8.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatButtonToggleModule }, { kind: "directive", type: i10$2.MatButtonToggleGroup, selector: "mat-button-toggle-group", inputs: ["appearance", "name", "vertical", "value", "multiple", "disabled", "disabledInteractive", "hideSingleSelectionIndicator", "hideMultipleSelectionIndicator"], outputs: ["valueChange", "change"], exportAs: ["matButtonToggleGroup"] }, { kind: "component", type: i10$2.MatButtonToggle, selector: "mat-button-toggle", inputs: ["aria-label", "aria-labelledby", "id", "name", "value", "tabIndex", "disableRipple", "appearance", "checked", "disabled", "disabledInteractive"], outputs: ["change"], exportAs: ["matButtonToggle"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i7.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: PraxisIconButtonComponent, selector: "button[praxisIconButton]", inputs: ["praxisIconButton", "size", "appearance", "presentation", "pressed", "busy"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i13.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i13.CdkDropListGroup, selector: "[cdkDropListGroup]", inputs: ["cdkDropListGroupDisabled"], exportAs: ["cdkDropListGroup"] }, { kind: "directive", type: i13.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i13.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i5$1.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i10.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatChipsModule }, { kind: "component", type: i15.MatChip, selector: "mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]", inputs: ["role", "id", "aria-label", "aria-description", "value", "color", "removable", "highlighted", "disableRipple", "disabled"], outputs: ["removed", "destroyed"], exportAs: ["matChip"] }, { kind: "directive", type: i15.MatChipAvatar, selector: "mat-chip-avatar, [matChipAvatar]" }, { kind: "component", type: i15.MatChipSet, selector: "mat-chip-set", inputs: ["disabled", "role", "tabIndex"] }, { kind: "directive", type: i15.MatChipTrailingIcon, selector: "mat-chip-trailing-icon, [matChipTrailingIcon]" }, { kind: "ngmodule", type: MatDividerModule }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "component", type: i6.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i6.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i6.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "directive", type: i6.MatExpansionPanelDescription, selector: "mat-panel-description" }, { kind: "directive", type: PraxisNumberWheelBlurDirective, selector: "input[type=number][praxisNumberWheelBlur]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
13077
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: FilterSettingsComponent, isStandalone: true, selector: "filter-settings", inputs: { metadata: "metadata", metadataSource: "metadataSource", metadataLoading: "metadataLoading", metadataErrorMsg: "metadataErrorMsg", settings: "settings", configKey: "configKey" }, outputs: { settingsChange: "settingsChange" }, providers: [providePraxisI18nConfig(PRAXIS_TABLE_EDITOR_I18N_CONFIG)], usesOnChanges: true, ngImport: i0, template: "<form [formGroup]=\"form\">\n <mat-tab-group (selectedTabChange)=\"onTabChange($event)\">\n <mat-tab label=\"Sempre Vis\u00EDveis\">\n <div class=\"always-visible-tab-flow\">\n <mat-form-field appearance=\"outline\" floatLabel=\"always\" class=\"full-width\">\n <mat-label>Campos sempre vis\u00EDveis</mat-label>\n <mat-select\n formControlName=\"alwaysVisibleFields\"\n multiple\n >\n @if (alwaysVisibleSelectionCount() > 0) {\n <mat-select-trigger>{{ getSelectedAlwaysVisibleTriggerLabel() }}</mat-select-trigger>\n }\n @for (meta of filteredAlwaysVisibleMetadata(); track trackFieldMetadataByName($index, meta)) {\n <mat-option\n [value]=\"meta.name\"\n >\n <div class=\"filter-rich-field-option\">\n <div class=\"filter-rich-field-option__top\">\n <span class=\"filter-rich-field-option__label\">{{ meta.label || meta.name }}</span>\n @if (meta.label && meta.label !== meta.name) {\n <code class=\"filter-rich-field-option__code\">{{ meta.name }}</code>\n }\n @if (getFieldTypeBadge(meta); as badge) {\n <span class=\"filter-rich-field-option__badge\">{{ badge }}</span>\n }\n </div>\n @if (getFieldDescription(meta); as desc) {\n <div class=\"filter-rich-field-option__desc\">{{ desc }}</div>\n }\n </div>\n </mat-option>\n }\n </mat-select>\n @if (metadataLoading && metadataSource === 'filter-dto') {\n <mat-hint>\n Carregando campos do DTO de filtro...\n </mat-hint>\n }\n @if (metadataErrorMsg) {\n <mat-error>{{ metadataErrorMsg }}</mat-error>\n }\n @if (!metadataLoading && !metadataErrorMsg && !metadata.length && metadataSource === 'filter-dto') {\n <mat-hint\n >\n Nenhum campo do DTO de filtro dispon\u00EDvel para este recurso.\n </mat-hint>\n }\n @if (!metadataLoading && !metadataErrorMsg && metadata.length && !hasAlwaysVisibleMetadataResults() && alwaysVisibleFieldSearchText) {\n <mat-hint\n >\n Nenhum campo encontrado para \"{{ alwaysVisibleFieldSearchText }}\".\n </mat-hint>\n }\n <button\n praxisIconButton=\"help_outline\"\n size=\"dense\"\n matSuffix\n type=\"button\"\n class=\"praxis-help-icon-button\"\n matTooltip=\"Escolha os campos que aparecem sempre na barra de filtros.\"\n aria-label=\"Escolha os campos que aparecem sempre na barra de filtros.\"\n ></button>\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Buscar campo</mat-label>\n <input\n matInput\n [ngModel]=\"alwaysVisibleFieldSearchText\"\n (ngModelChange)=\"onAlwaysVisibleFieldSearchChange($event)\"\n [ngModelOptions]=\"{standalone: true}\"\n placeholder=\"Filtrar por nome, label, tipo ou descri\u00E7\u00E3o\"\n />\n @if (alwaysVisibleFieldSearchText) {\n <button\n praxisIconButton=\"close\"\n size=\"dense\"\n matSuffix\n type=\"button\"\n (click)=\"clearAlwaysVisibleFieldSearch()\"\n aria-label=\"Limpar busca de campo\"\n ></button>\n }\n </mat-form-field>\n <p class=\"field-hint\">\n {{ alwaysVisibleSelectionCount() }} campo(s) selecionado(s).\n </p>\n <div class=\"always-visible-context\">\n <div class=\"always-visible-summary\" role=\"status\" aria-live=\"polite\">\n <div class=\"always-visible-summary-item\">\n <span class=\"summary-value\">{{ alwaysVisibleSelectionCount() }}</span>\n <span class=\"summary-label\">campos fixos</span>\n </div>\n <div class=\"always-visible-summary-item\">\n <span class=\"summary-value\">{{ activeAlwaysVisibleOverrideCount() }}</span>\n <span class=\"summary-label\">overrides ativos</span>\n </div>\n </div>\n <nav\n class=\"always-visible-quick-nav\"\n aria-label=\"Navega\u00E7\u00E3o r\u00E1pida da se\u00E7\u00E3o Sempre Vis\u00EDveis\"\n >\n <button\n mat-stroked-button\n type=\"button\"\n [class.quick-nav-active]=\"isAlwaysVisibleSectionActive('always-visible-order')\"\n [attr.aria-current]=\"isAlwaysVisibleSectionActive('always-visible-order') ? 'location' : null\"\n aria-controls=\"always-visible-order\"\n (click)=\"scrollToAlwaysVisibleSection('always-visible-order')\"\n >\n Ordem\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n [class.quick-nav-active]=\"isAlwaysVisibleSectionActive('always-visible-metadata')\"\n [attr.aria-current]=\"isAlwaysVisibleSectionActive('always-visible-metadata') ? 'location' : null\"\n aria-controls=\"always-visible-metadata\"\n (click)=\"scrollToAlwaysVisibleSection('always-visible-metadata')\"\n >\n Metadados\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n [class.quick-nav-active]=\"isAlwaysVisibleSectionActive('always-visible-json')\"\n [attr.aria-current]=\"isAlwaysVisibleSectionActive('always-visible-json') ? 'location' : null\"\n aria-controls=\"always-visible-json\"\n (click)=\"scrollToAlwaysVisibleSection('always-visible-json')\"\n >\n JSON\n </button>\n </nav>\n </div>\n\n <div\n id=\"always-visible-order\"\n class=\"options options-compact-flow always-visible-section-anchor\"\n [class.section-active]=\"isAlwaysVisibleSectionActive('always-visible-order')\"\n [style.--section-enter-delay]=\"sectionEnterDelay('always-visible-order')\"\n tabindex=\"-1\"\n (focusin)=\"onAlwaysVisibleSectionFocus('always-visible-order')\"\n >\n <div class=\"options-header\">\n <h4 class=\"section-title\">Ordem dos campos</h4>\n <p class=\"section-help\">\n Reordene no formato mais pr\u00F3ximo da toolbar real: faixa de a\u00E7\u00F5es, barra inline e grid.\n </p>\n </div>\n <div class=\"order-layout-toolbar\">\n <mat-button-toggle-group\n class=\"order-viewport-toggle-group\"\n [value]=\"orderPreviewViewport\"\n (change)=\"onAlwaysVisibleViewportChange($event.value)\"\n aria-label=\"Largura simulada da toolbar\"\n >\n @for (viewport of alwaysVisibleViewports; track viewport) {\n <mat-button-toggle\n [value]=\"viewport\"\n [attr.aria-label]=\"alwaysVisibleViewportHint(viewport)\"\n >\n {{ alwaysVisibleViewportLabel(viewport) }}\n </mat-button-toggle>\n }\n </mat-button-toggle-group>\n <span class=\"order-viewport-caption\">\n {{ alwaysVisibleViewportWidth() }}px\n </span>\n </div>\n <p class=\"section-help\">\n Ajuste o viewport para simular a quebra real das linhas na toolbar.\n </p>\n <div class=\"order-layout-viewport\">\n <div\n class=\"order-layout-frame\"\n [attr.data-viewport]=\"orderPreviewViewport\"\n [style.maxWidth.px]=\"alwaysVisibleViewportWidth()\"\n >\n <div class=\"order-layout-composer\" cdkDropListGroup>\n @for (lane of alwaysVisibleLayoutLanes; track lane) {\n <section\n class=\"order-lane\"\n [attr.data-lane]=\"lane\"\n >\n @if (alwaysVisibleLaneItems(lane); as laneItems) {\n <header class=\"order-lane-header\">\n <div class=\"order-lane-title-block\">\n <h5 class=\"order-lane-title\">{{ alwaysVisibleLaneLabel(lane) }}</h5>\n <p class=\"order-lane-help\">{{ alwaysVisibleLaneHint(lane) }}</p>\n </div>\n <span class=\"order-lane-count\">{{ laneItems.length }}</span>\n </header>\n <div\n cdkDropList\n class=\"order-lane-canvas\"\n [id]=\"alwaysVisibleLaneDropListId(lane)\"\n [class.is-empty]=\"!laneItems.length\"\n [cdkDropListData]=\"laneItems\"\n [cdkDropListEnterPredicate]=\"allowAlwaysVisibleLaneEnter\"\n cdkDropListOrientation=\"horizontal\"\n [attr.aria-label]=\"'Faixa ' + alwaysVisibleLaneLabel(lane)\"\n (cdkDropListDropped)=\"dropAlwaysVisibleLane(lane, $event)\"\n >\n @for (item of laneItems; track trackAlwaysVisibleLayoutItemByName(i, item); let i = $index) {\n <button\n type=\"button\"\n class=\"order-pill\"\n cdkDrag\n [cdkDragData]=\"item\"\n [attr.aria-label]=\"alwaysVisibleLayoutItemAriaLabel(item, lane, i, laneItems.length)\"\n (keydown)=\"onAlwaysVisibleLaneItemKeydown($event, lane, item.name)\"\n >\n <mat-icon [praxisIcon]=\"'drag_indicator'\" cdkDragHandle></mat-icon>\n <span class=\"order-pill-label\">{{ item.label }}</span>\n <span class=\"order-pill-badges\">\n <span class=\"order-pill-chip\">{{ item.controlTypeLabel }}</span>\n <span class=\"order-pill-chip order-pill-chip-width\">{{ item.widthHint }}</span>\n @if (item.hasOverride) {\n <span class=\"order-pill-chip order-pill-chip-override\">\n Override\n </span>\n }\n </span>\n </button>\n }\n @if (!laneItems.length) {\n <p class=\"order-lane-empty\">\n {{ alwaysVisibleLaneEmptyHint(lane) }}\n </p>\n }\n </div>\n }\n </section>\n }\n </div>\n </div>\n </div>\n <p class=\"field-hint\">\n Acessibilidade: use Alt/Ctrl + setas para reordenar dentro de cada faixa. O drag & drop n\u00E3o mistura faixas.\n </p>\n @if (!form.controls.alwaysVisibleFields.value?.length) {\n <p class=\"section-help\">\n Nenhum campo selecionado. Use o seletor acima para adicionar.\n </p>\n }\n </div>\n\n <mat-divider class=\"section-divider\" role=\"presentation\"></mat-divider>\n\n <div\n id=\"always-visible-metadata\"\n class=\"options options-compact-flow always-visible-section-anchor\"\n [class.section-active]=\"isAlwaysVisibleSectionActive('always-visible-metadata')\"\n [style.--section-enter-delay]=\"sectionEnterDelay('always-visible-metadata')\"\n tabindex=\"-1\"\n (focusin)=\"onAlwaysVisibleSectionFocus('always-visible-metadata')\"\n >\n <div class=\"options-header\">\n <h4 class=\"section-title\">Editor visual de metadados</h4>\n <p class=\"section-help\">\n Edite os principais ajustes dos campos sempre vis\u00EDveis no fluxo visual. Para propriedades avan\u00E7adas, use o JSON abaixo.\n </p>\n </div>\n @if (!form.controls.alwaysVisibleFields.value?.length) {\n <p class=\"section-help\">\n Selecione campos em \u201CCampos sempre vis\u00EDveis\u201D para habilitar a edi\u00E7\u00E3o visual.\n </p>\n }\n @if (form.controls.alwaysVisibleFields.value?.length) {\n <div class=\"metadata-card-grid\">\n @for (name of form.controls.alwaysVisibleFields.value; track name) {\n <article class=\"metadata-card\">\n <header class=\"metadata-card-header\">\n <div class=\"metadata-card-title\">\n <strong>{{ getFieldLabel(name) }}</strong>\n <span>{{ name }}</span>\n </div>\n <div class=\"metadata-card-badges\">\n <span class=\"metadata-chip\">{{ getAlwaysVisibleControlTypeLabel(name) }}</span>\n @if (hasAlwaysVisibleOverride(name)) {\n <span class=\"metadata-chip metadata-chip-override\">\n Override ativo\n </span>\n }\n </div>\n </header>\n <p class=\"metadata-card-summary\">{{ getAlwaysVisibleOverridePreview(name) }}</p>\n <div class=\"metadata-card-actions\">\n <button mat-stroked-button type=\"button\" (click)=\"openAlwaysVisibleMetadataEditor(name)\">\n <mat-icon [praxisIcon]=\"'tune'\"></mat-icon>\n Editar campo\n </button>\n <button\n mat-button\n type=\"button\"\n color=\"warn\"\n (click)=\"clearAlwaysVisibleOverride(name)\"\n [disabled]=\"!hasAlwaysVisibleOverride(name)\"\n >\n Limpar override\n </button>\n </div>\n </article>\n }\n </div>\n }\n </div>\n\n <mat-divider class=\"section-divider\" role=\"presentation\"></mat-divider>\n\n <div\n id=\"always-visible-json\"\n class=\"always-visible-section-anchor\"\n [class.section-active]=\"isAlwaysVisibleSectionActive('always-visible-json')\"\n [style.--section-enter-delay]=\"sectionEnterDelay('always-visible-json')\"\n tabindex=\"-1\"\n (focusin)=\"onAlwaysVisibleSectionFocus('always-visible-json')\"\n >\n <mat-expansion-panel\n class=\"options options-compact-flow options-advanced-json\"\n [expanded]=\"!!alwaysVisibleOverridesErrorMsg\"\n >\n <mat-expansion-panel-header>\n <mat-panel-title>Avan\u00E7ado (JSON opcional)</mat-panel-title>\n <mat-panel-description>Para ajustes n\u00E3o expostos no editor visual</mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"options-header\">\n <h4 class=\"section-title\">Modo avan\u00E7ado (JSON)</h4>\n <p class=\"section-help\">\n Use JSON para ajustes em lote ou propriedades ainda n\u00E3o expostas no editor visual.\n </p>\n </div>\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>alwaysVisibleFieldMetadataOverrides (JSON)</mat-label>\n <textarea\n matInput\n rows=\"8\"\n [(ngModel)]=\"alwaysVisibleOverridesJsonText\"\n [ngModelOptions]=\"{standalone: true}\"\n ></textarea>\n </mat-form-field>\n <div class=\"actions\">\n <button mat-stroked-button type=\"button\" (click)=\"formatAlwaysVisibleOverridesJson()\">Formatar</button>\n <button mat-stroked-button color=\"primary\" type=\"button\" (click)=\"applyAlwaysVisibleOverridesJson()\">\n Validar e carregar no editor\n </button>\n </div>\n <p class=\"json-tip\">\n Esta a\u00E7\u00E3o n\u00E3o persiste a configura\u00E7\u00E3o. Para persistir, use \u201CSalvar & Fechar\u201D no rodap\u00E9.\n </p>\n <p class=\"json-tip\">\n Exemplo:\n {\"status\":{\"controlType\":\"inlineSelect\",\"clearButton\":{\"enabled\":true},\"inlineAutoSize\":{\"minWidth\":140,\"maxWidth\":280}}}\n </p>\n @if (alwaysVisibleOverridesErrorMsg) {\n <p class=\"json-error\" role=\"status\" aria-live=\"polite\">{{ alwaysVisibleOverridesErrorMsg }}</p>\n }\n </mat-expansion-panel>\n </div>\n </div>\n </mat-tab>\n <mat-tab label=\"Layout\">\n <div class=\"options\">\n <div class=\"options-header\">\n <h4 class=\"section-title\">Grid e responsividade</h4>\n <p class=\"section-help\">\n Ajuste largura m\u00EDnima e n\u00FAmero de colunas por breakpoint. Em telas menores que 600px, o layout usa 1 coluna.\n </p>\n </div>\n <div class=\"layout-preview-section\" aria-live=\"polite\">\n <h5 class=\"layout-preview-title\">Pr\u00E9-visualiza\u00E7\u00E3o do grid</h5>\n <p class=\"section-help\">\n Simula\u00E7\u00E3o com {{ layoutPreviewFields.length }} campos e largura m\u00EDnima de {{ layoutMinWidth() }}px por campo.\n </p>\n <div class=\"layout-preview-cards\">\n <article class=\"layout-preview-card\">\n <div class=\"layout-preview-header\">\n <strong>Mobile</strong>\n <span><600px \u00B7 {{ layoutColsFor('sm') }} coluna</span>\n </div>\n <div class=\"layout-mini-grid\" [style.gridTemplateColumns]=\"'repeat(' + layoutColsFor('sm') + ', minmax(0, 1fr))'\">\n @for (field of layoutPreviewFields; track field) {\n <span>{{ field }}</span>\n }\n </div>\n </article>\n <article class=\"layout-preview-card\">\n <div class=\"layout-preview-header\">\n <strong>Tablet</strong>\n <span>≥600px \u00B7 {{ layoutColsFor('md') }} colunas</span>\n </div>\n <div class=\"layout-mini-grid\" [style.gridTemplateColumns]=\"'repeat(' + layoutColsFor('md') + ', minmax(0, 1fr))'\">\n @for (field of layoutPreviewFields; track field) {\n <span>{{ field }}</span>\n }\n </div>\n </article>\n <article class=\"layout-preview-card\">\n <div class=\"layout-preview-header\">\n <strong>Desktop</strong>\n <span>≥960px \u00B7 {{ layoutColsFor('lg') }} colunas</span>\n </div>\n <div class=\"layout-mini-grid\" [style.gridTemplateColumns]=\"'repeat(' + layoutColsFor('lg') + ', minmax(0, 1fr))'\">\n @for (field of layoutPreviewFields; track field) {\n <span>{{ field }}</span>\n }\n </div>\n </article>\n </div>\n </div>\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Largura m\u00EDnima por campo (px)</mat-label>\n <input\n matInput\n type=\"number\"\n formControlName=\"alwaysMinWidth\"\n min=\"180\"\n max=\"480\"\n step=\"1\"\n inputmode=\"numeric\"\n praxisNumberWheelBlur\n (blur)=\"normalizeLayoutControl('alwaysMinWidth')\"\n />\n <button\n praxisIconButton=\"help_outline\"\n size=\"dense\"\n matSuffix\n type=\"button\"\n class=\"praxis-help-icon-button\"\n aria-label=\"Ajuda sobre largura m\u00EDnima por campo\"\n matTooltip=\"Define a largura m\u00EDnima de cada campo no grid. Recomendado entre 200 e 300px.\"\n ></button>\n <mat-hint align=\"start\">Intervalo permitido: 180 a 480px.</mat-hint>\n <mat-hint align=\"end\">Recomendado: 200 a 300px.</mat-hint>\n @if (layoutControlError('alwaysMinWidth'); as error) {\n <mat-error>{{ error }}</mat-error>\n }\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Colunas em tablet (≥600px)</mat-label>\n <input\n matInput\n type=\"number\"\n formControlName=\"alwaysColsMd\"\n min=\"1\"\n max=\"3\"\n step=\"1\"\n inputmode=\"numeric\"\n praxisNumberWheelBlur\n (blur)=\"normalizeLayoutControl('alwaysColsMd')\"\n />\n <button\n praxisIconButton=\"help_outline\"\n size=\"dense\"\n matSuffix\n type=\"button\"\n class=\"praxis-help-icon-button\"\n aria-label=\"Ajuda sobre colunas em tablet\"\n matTooltip=\"Define quantas colunas aparecem em telas m\u00E9dias (a partir de 600px).\"\n ></button>\n <mat-hint align=\"start\">Mobile (<600px) usa 1 coluna.</mat-hint>\n <mat-hint align=\"end\">Intervalo: 1 a 3 colunas.</mat-hint>\n @if (layoutControlError('alwaysColsMd'); as error) {\n <mat-error>{{ error }}</mat-error>\n }\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Colunas em desktop (≥960px)</mat-label>\n <input\n matInput\n type=\"number\"\n formControlName=\"alwaysColsLg\"\n min=\"1\"\n max=\"4\"\n step=\"1\"\n inputmode=\"numeric\"\n praxisNumberWheelBlur\n (blur)=\"normalizeLayoutControl('alwaysColsLg')\"\n />\n <button\n praxisIconButton=\"help_outline\"\n size=\"dense\"\n matSuffix\n type=\"button\"\n class=\"praxis-help-icon-button\"\n aria-label=\"Ajuda sobre colunas em desktop\"\n matTooltip=\"Define quantas colunas aparecem em telas grandes (a partir de 960px).\"\n ></button>\n <mat-hint align=\"start\">Use mais colunas apenas quando houver espa\u00E7o.</mat-hint>\n <mat-hint align=\"end\">Intervalo: 1 a 4 colunas.</mat-hint>\n @if (layoutControlError('alwaysColsLg'); as error) {\n <mat-error>{{ error }}</mat-error>\n }\n </mat-form-field>\n </div>\n </mat-tab>\n <mat-tab label=\"Booleanos\">\n <div class=\"options\">\n @if (!hasBooleanFieldMetadata()) {\n <p class=\"section-help\">\n Nenhum campo booleano foi detectado no schema atual. Estas prefer\u00EAncias ser\u00E3o aplicadas quando houver\n campos `toggle`/boolean no filtro.\n </p>\n }\n @if (hasBooleanFieldMetadata()) {\n <div class=\"toggle-preview-section\">\n <h4>Visualiza\u00E7\u00E3o dos Booleanos</h4>\n <p class=\"section-help\">Veja o comportamento dos booleanos inline (com texto ou compacto) e a posi\u00E7\u00E3o na barra.</p>\n <div class=\"preview-container\">\n <div class=\"preview-item\">\n <strong>Grade Principal</strong>\n <div class=\"mock-grid\" [class.with-booleans]=\"!form.controls.placeBooleansInActions.value\">\n <div class=\"mock-field\">Campo 1</div>\n <div class=\"mock-field\">Campo 2</div>\n @if (!form.controls.placeBooleansInActions.value) {\n <div class=\"mock-pill\"\n [class.compact]=\"!form.controls.showToggleLabels.value\">\n <mat-slide-toggle [checked]=\"true\"></mat-slide-toggle>\n @if (form.controls.showToggleLabels.value) {\n <span>Ativo</span>\n }\n </div>\n }\n </div>\n </div>\n <div class=\"preview-item\">\n <strong>\u00C1rea de A\u00E7\u00F5es</strong>\n <div class=\"mock-actions\" [class.with-booleans]=\"form.controls.placeBooleansInActions.value\">\n <button praxisIconButton=\"search\" size=\"dense\" type=\"button\" tabindex=\"-1\"\n aria-disabled=\"true\" aria-label=\"Exemplo de a\u00E7\u00E3o de busca\"></button>\n <button praxisIconButton=\"tune\" size=\"dense\" type=\"button\" tabindex=\"-1\"\n aria-disabled=\"true\" aria-label=\"Exemplo de a\u00E7\u00E3o de filtros\"></button>\n @if (form.controls.placeBooleansInActions.value) {\n <div class=\"mock-pill\"\n [class.compact]=\"!form.controls.showToggleLabels.value\">\n <mat-slide-toggle [checked]=\"true\"></mat-slide-toggle>\n @if (form.controls.showToggleLabels.value) {\n <span>Ativo</span>\n }\n </div>\n }\n </div>\n </div>\n </div>\n </div>\n }\n <mat-checkbox formControlName=\"placeBooleansInActions\">\n <mat-icon>swap_horiz</mat-icon>\n Posicionar booleanos na \u00E1rea de a\u00E7\u00F5es\n </mat-checkbox>\n <p class=\"field-hint\">Move os booleanos da grade para os bot\u00F5es de a\u00E7\u00E3o, liberando espa\u00E7o horizontal.</p>\n <mat-checkbox formControlName=\"showToggleLabels\">\n <mat-icon>text_fields</mat-icon>\n Mostrar texto nos booleanos inline\n </mat-checkbox>\n <p class=\"field-hint\">Quando desativado, os booleanos ficam em modo compacto (somente controle, sem texto).</p>\n </div>\n </mat-tab>\n <mat-tab label=\"Op\u00E7\u00F5es\">\n <div class=\"options\">\n <div class=\"options-header\">\n <h4 class=\"section-title\">Painel Avan\u00E7ado</h4>\n <p class=\"section-help\">Apar\u00EAncia e comportamento do painel de filtros avan\u00E7ados.</p>\n </div>\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Modo de abertura do Avan\u00E7ado</mat-label>\n <mat-select formControlName=\"advancedOpenMode\">\n <mat-option value=\"modal\">Modal (padr\u00E3o)</mat-option>\n <mat-option value=\"drawer\">Drawer (direita)</mat-option>\n </mat-select>\n <button\n praxisIconButton=\"help_outline\"\n size=\"dense\"\n matSuffix\n type=\"button\"\n class=\"praxis-help-icon-button\"\n matTooltip=\"Escolha como o painel de filtros avan\u00E7ados ser\u00E1 exibido: centralizado ou lateral.\"\n aria-label=\"Escolha como o painel de filtros avan\u00E7ados ser\u00E1 exibido: centralizado ou lateral.\"\n ></button>\n </mat-form-field>\n\n <mat-checkbox formControlName=\"advancedClearButtonsEnabled\">\n Mostrar bot\u00E3o de limpar nos campos\n </mat-checkbox>\n <p class=\"field-hint\">\n Desative para ocultar o bot\u00E3o de limpar nos campos do painel avan\u00E7ado.\n </p>\n\n <mat-checkbox formControlName=\"useInlineSearchableSelectVariant\">\n Compactar selects com busca/async/autocomplete na barra inline\n </mat-checkbox>\n <p class=\"field-hint\">\n Padr\u00E3o do filtro din\u00E2mico. Desative apenas quando o host precisar preservar o renderer dedicado por compatibilidade, performance ou comportamento legado. O lookup de entidade inline \u00E9 configurado por controlType expl\u00EDcito (`inlineEntityLookup`).\n </p>\n\n <mat-checkbox formControlName=\"useInlineRangeVariant\">\n Compactar range slider na barra inline\n </mat-checkbox>\n <p class=\"field-hint\">\n Usa o componente inline em formato chip + popover para rangeSlider (single e faixa).\n </p>\n\n <mat-checkbox formControlName=\"useInlineDateVariant\">\n Compactar campos de data na barra inline\n </mat-checkbox>\n <p class=\"field-hint\">\n Usa o componente inline dedicado para date/dateInput/datepicker, com largura por conte\u00FAdo e placeholder compacto.\n </p>\n\n <mat-checkbox formControlName=\"useInlineDateRangeVariant\">\n Compactar intervalo de datas na barra inline\n </mat-checkbox>\n <p class=\"field-hint\">\n Padr\u00E3o do filtro din\u00E2mico para dateRange/daterange. Desative apenas para preservar o renderer tradicional em hosts legados.\n </p>\n\n <mat-checkbox formControlName=\"useInlineTimeVariant\">\n Compactar campos de horario na barra inline\n </mat-checkbox>\n <p class=\"field-hint\">\n Padr\u00E3o do filtro din\u00E2mico para time/timePicker, com largura por conteudo e clear rapido. Desative apenas para manter a experi\u00EAncia legada.\n </p>\n\n <mat-checkbox formControlName=\"useInlineTimeRangeVariant\">\n Compactar intervalo de horario na barra inline\n </mat-checkbox>\n <p class=\"field-hint\">\n Padr\u00E3o do filtro din\u00E2mico para timeRange/timerange. Desative apenas para preservar o renderer tradicional em hosts legados.\n </p>\n\n <mat-checkbox formControlName=\"useInlineTreeSelectVariant\">\n Compactar tree select na barra inline\n </mat-checkbox>\n <p class=\"field-hint\">\n Padr\u00E3o do filtro din\u00E2mico para treeSelect na faixa compacta. Desative apenas quando o host depender do renderer dedicado. multiSelectTree mant\u00E9m o componente dedicado.\n </p>\n\n <mat-checkbox formControlName=\"confirmTagDelete\">Confirmar exclus\u00E3o de atalho</mat-checkbox>\n <p class=\"field-hint\">Solicita confirma\u00E7\u00E3o antes de apagar atalhos salvos.</p>\n\n <div class=\"options\">\n <h4>Tema dos Atalhos (Tags)</h4>\n\n <!-- Preview Section -->\n <div class=\"preview-section\" [class.mdc-theme-dark]=\"previewDarkMode\" [class.dark-mode]=\"previewDarkMode\">\n <div class=\"preview-controls\">\n <span class=\"preview-label\">Pr\u00E9-visualiza\u00E7\u00E3o</span>\n <button [praxisIconButton]=\"previewDarkMode ? 'light_mode' : 'dark_mode'\" size=\"dense\" type=\"button\"\n (click)=\"previewDarkMode = !previewDarkMode\"\n [matTooltip]=\"previewDarkMode ? 'Mudar para fundo claro' : 'Mudar para fundo escuro'\"\n [attr.aria-label]=\"previewDarkMode ? 'Mudar para fundo claro' : 'Mudar para fundo escuro'\"></button>\n </div>\n\n <div class=\"preview-chips praxis-filter-tags\"\n [class.outlined]=\"form.controls.tagVariant.value === 'outlined'\"\n [class.tag-color-basic]=\"form.controls.tagColor.value === 'basic'\"\n [class.tag-color-primary]=\"form.controls.tagColor.value === 'primary'\"\n [class.tag-color-accent]=\"form.controls.tagColor.value === 'accent'\"\n [class.tag-color-warn]=\"form.controls.tagColor.value === 'warn'\">\n <mat-chip-set>\n <!-- Inactive Chip -->\n <mat-chip\n [color]=\"form.controls.tagVariant.value === 'outlined' || form.controls.tagColor.value === 'basic' ? null : form.controls.tagColor.value\"\n [highlighted]=\"form.controls.tagVariant.value === 'filled' && form.controls.tagColor.value !== 'basic'\">\n Atalho Inativo\n </mat-chip>\n\n <!-- Active Chip -->\n <mat-chip class=\"active\"\n [color]=\"form.controls.tagVariant.value === 'outlined' || form.controls.tagColor.value === 'basic' ? null : form.controls.tagColor.value\"\n [highlighted]=\"form.controls.tagVariant.value === 'filled' && form.controls.tagColor.value !== 'basic'\">\n <mat-icon matChipAvatar>check</mat-icon>\n Atalho Ativo\n <button matChipTrailingIcon praxisIconButton=\"arrow_drop_down\" size=\"dense\" type=\"button\"\n class=\"preview-chip-action\" tabindex=\"-1\" aria-disabled=\"true\"\n aria-label=\"Exemplo de menu do atalho ativo\"\n [class.preview-chip-action--primary]=\"form.controls.tagButtonColor.value === 'primary'\"\n [class.preview-chip-action--accent]=\"form.controls.tagButtonColor.value === 'accent'\"\n [class.preview-chip-action--warn]=\"form.controls.tagButtonColor.value === 'warn'\"></button>\n </mat-chip>\n </mat-chip-set>\n </div>\n\n @if (getContrastWarning(); as warning) {\n <div class=\"contrast-warning\">\n <mat-icon>warning</mat-icon>\n <span>{{ warning }}</span>\n </div>\n }\n </div>\n\n <!-- Reordered Fields -->\n <mat-form-field appearance=\"outline\">\n <mat-label>Variante do atalho</mat-label>\n <mat-select formControlName=\"tagVariant\">\n <mat-option value=\"filled\">Filled (Preenchido)</mat-option>\n <mat-option value=\"outlined\">Outlined (Contorno)</mat-option>\n </mat-select>\n <button\n praxisIconButton=\"help_outline\"\n size=\"dense\"\n matSuffix\n type=\"button\"\n class=\"praxis-help-icon-button\"\n matTooltip=\"Define o estilo base do atalho.\"\n aria-label=\"Define o estilo base do atalho.\"\n ></button>\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\">\n <mat-label>Cor do atalho</mat-label>\n <mat-select formControlName=\"tagColor\">\n <mat-option value=\"primary\">Primary</mat-option>\n <mat-option value=\"accent\">Accent</mat-option>\n <mat-option value=\"warn\">Warn</mat-option>\n <mat-option value=\"basic\">Basic</mat-option>\n </mat-select>\n <button\n praxisIconButton=\"help_outline\"\n size=\"dense\"\n matSuffix\n type=\"button\"\n class=\"praxis-help-icon-button\"\n matTooltip=\"Cor de fundo (Filled) ou de contorno (Outlined).\"\n aria-label=\"Cor de fundo (Filled) ou de contorno (Outlined).\"\n ></button>\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\">\n <mat-label>Cor do bot\u00E3o do menu do atalho</mat-label>\n <mat-select formControlName=\"tagButtonColor\">\n <mat-option value=\"basic\">Basic</mat-option>\n <mat-option value=\"primary\">Primary</mat-option>\n <mat-option value=\"accent\">Accent</mat-option>\n <mat-option value=\"warn\">Warn</mat-option>\n </mat-select>\n <button\n praxisIconButton=\"help_outline\"\n size=\"dense\"\n matSuffix\n type=\"button\"\n class=\"praxis-help-icon-button\"\n matTooltip=\"Cor do bot\u00E3o de dropdown dentro do atalho ativo.\"\n aria-label=\"Cor do bot\u00E3o de dropdown dentro do atalho ativo.\"\n ></button>\n </mat-form-field>\n </div>\n\n <div class=\"options\">\n <h4>Tema dos bot\u00F5es de a\u00E7\u00E3o</h4>\n <mat-form-field appearance=\"outline\">\n <mat-label>Cor dos bot\u00F5es</mat-label>\n <mat-select formControlName=\"actionsButtonColor\">\n <mat-option value=\"basic\">Basic</mat-option>\n <mat-option value=\"primary\">Primary</mat-option>\n <mat-option value=\"accent\">Accent</mat-option>\n <mat-option value=\"warn\">Warn</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Variante dos bot\u00F5es</mat-label>\n <mat-select formControlName=\"actionsVariant\">\n <mat-option value=\"standard\">Standard</mat-option>\n <mat-option value=\"outlined\">Outlined</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n\n <div class=\"options\">\n <h4>Predefini\u00E7\u00F5es de tema</h4>\n <div class=\"preset-grid\">\n @for (p of presets; track p) {\n <button\n type=\"button\"\n class=\"preset-card\"\n (click)=\"applyPreset(p.key)\"\n [class.preset-active]=\"isPresetActive(p.key)\"\n [attr.aria-pressed]=\"isPresetActive(p.key)\"\n [attr.aria-label]=\"'Aplicar predefini\u00E7\u00E3o: ' + p.label\"\n >\n <div class=\"preset-preview\" [ngClass]=\"p.previewClass\"></div>\n <div class=\"preset-info\">\n <span class=\"preset-name\">{{ p.label }}</span>\n <span class=\"preset-desc\">{{ p.description }}</span>\n </div>\n </button>\n }\n </div>\n <button mat-button color=\"warn\" (click)=\"resetPreferences()\" class=\"restore-btn\">\n Restaurar padr\u00E3o\n </button>\n </div>\n </div>\n </mat-tab>\n @if (configKey) {\n <mat-tab label=\"Atalhos\">\n <div class=\"options\">\n <p class=\"section-help\">Os atalhos s\u00E3o salvos por chave de configura\u00E7\u00E3o e compartilhados entre sess\u00F5es.</p>\n <div class=\"tags-header\">\n <span><strong>Atalhos salvos</strong></span>\n <span class=\"spacer\"></span>\n <button mat-stroked-button color=\"warn\" (click)=\"clearTags()\">Apagar tudo</button>\n </div>\n <div class=\"new-tag\">\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>R\u00F3tulo do novo atalho</mat-label>\n <input matInput [(ngModel)]=\"newTagLabel\" [ngModelOptions]=\"{standalone: true}\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Patch (JSON, opcional)</mat-label>\n <textarea matInput rows=\"3\" [(ngModel)]=\"newTagPatchText\" [ngModelOptions]=\"{standalone: true}\"></textarea>\n </mat-form-field>\n <div>\n <button mat-stroked-button color=\"primary\" (click)=\"addTag()\">Adicionar</button>\n @if (hasCurrentDto()) {\n <button mat-stroked-button (click)=\"addTagFromCurrent()\">Adicionar a partir dos filtros atuais</button>\n }\n </div>\n <div>\n <button mat-stroked-button color=\"primary\" (click)=\"importTags()\">Importar</button>\n <button mat-stroked-button (click)=\"loadTags()\">Recarregar</button>\n <button mat-stroked-button (click)=\"saveTags()\">Salvar</button>\n </div>\n </div>\n </div>\n </mat-tab>\n }\n <mat-tab label=\"JSON\">\n <div class=\"options\">\n <p><strong>Configura\u00E7\u00E3o atual (somente leitura)</strong></p>\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Atual</mat-label>\n <textarea matInput rows=\"8\" [value]=\"effectiveJson\" readonly></textarea>\n </mat-form-field>\n\n <p><strong>Editar JSON</strong></p>\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>JSON</mat-label>\n <textarea matInput rows=\"12\" [(ngModel)]=\"settingsJsonText\" [ngModelOptions]=\"{standalone: true}\"></textarea>\n </mat-form-field>\n <div class=\"actions\">\n <button mat-stroked-button (click)=\"onJsonReload()\">Recarregar do formul\u00E1rio</button>\n <button mat-stroked-button (click)=\"onJsonFormat()\">Formatar</button>\n <button mat-stroked-button color=\"primary\" (click)=\"onJsonApply()\">Carregar JSON no formul\u00E1rio</button>\n </div>\n <p class=\"json-tip\">\n Dica: esta a\u00E7\u00E3o apenas atualiza o editor local. Para persistir, use os bot\u00F5es Aplicar ou\n Salvar & Fechar no rodap\u00E9 do painel.\n </p>\n @if (jsonErrorMsg) {\n <p class=\"json-error\" role=\"status\" aria-live=\"polite\">{{ jsonErrorMsg }}</p>\n }\n </div>\n </mat-tab>\n <mat-tab label=\"Comportamento\">\n <div class=\"options\">\n <mat-checkbox\n formControlName=\"showAddButton\"\n data-testid=\"filter-settings-show-add-button\"\n >\n Exibir bot\u00E3o \"Gerenciar campos\" na barra\n </mat-checkbox>\n <mat-checkbox\n formControlName=\"showFilterSettings\"\n data-testid=\"filter-settings-show-filter-settings\"\n >\n Exibir bot\u00E3o \"Configura\u00E7\u00F5es do filtro\" na barra\n </mat-checkbox>\n <mat-checkbox formControlName=\"allowSaveTags\">Permitir salvar atalhos</mat-checkbox>\n </div>\n </mat-tab>\n <mat-tab label=\"Avan\u00E7ado\">\n <div class=\"options\">\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Debounce de altera\u00E7\u00F5es (ms)</mat-label>\n <input matInput type=\"number\" formControlName=\"changeDebounceMs\" min=\"100\" max=\"1000\" />\n @if (form.controls.changeDebounceMs.invalid) { <mat-error>Valor deve estar entre 100 e 1000</mat-error> }\n </mat-form-field>\n <div class=\"actions\">\n <button mat-stroked-button color=\"warn\" (click)=\"resetPreferences()\">\n <mat-icon [praxisIcon]=\"'restart_alt'\"></mat-icon>\n Redefinir prefer\u00EAncias do filtro\n </button>\n <button mat-stroked-button color=\"accent\" (click)=\"clearPersistedFilters()\">\n <mat-icon [praxisIcon]=\"'clear_all'\"></mat-icon>\n Limpar filtros salvos\n </button>\n <button mat-stroked-button (click)=\"forceSchemaReload()\">\n <mat-icon [praxisIcon]=\"'refresh'\"></mat-icon>\n For\u00E7ar recarregamento do schema\n </button>\n </div>\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>N\u00EDvel de log</mat-label>\n <mat-select formControlName=\"logLevel\">\n <mat-option value=\"none\">Nenhum</mat-option>\n <mat-option value=\"error\">Erro</mat-option>\n <mat-option value=\"warn\">Aviso</mat-option>\n <mat-option value=\"info\">Info</mat-option>\n <mat-option value=\"debug\">Debug</mat-option>\n </mat-select>\n <button\n praxisIconButton=\"help_outline\"\n size=\"dense\"\n matSuffix\n type=\"button\"\n class=\"praxis-help-icon-button\"\n matTooltip=\"Persistido no contrato para integra\u00E7\u00E3o host. Sem efeito direto no runtime atual do filtro.\"\n aria-label=\"Persistido no contrato para integra\u00E7\u00E3o host. Sem efeito direto no runtime atual do filtro.\"\n ></button>\n </mat-form-field>\n <mat-checkbox formControlName=\"enablePerformanceMetrics\">Habilitar m\u00E9tricas de performance</mat-checkbox>\n <p class=\"field-hint\">Prefer\u00EAncia persistida para integra\u00E7\u00E3o host. Sem efeito direto no runtime atual do filtro.</p>\n </div>\n </mat-tab>\n <mat-tab [label]=\"presentationTabLabel\">\n <praxis-filter-presentation-editor [value]=\"form.controls.presentation.value\" (valueChange)=\"updatePresentation($event)\" />\n </mat-tab>\n </mat-tab-group>\n</form>\n", styles: [":host{display:block;min-width:0}.full-width{width:100%}.always-visible-tab-flow{display:flex;flex-direction:column;gap:4px;padding-top:12px}.always-visible-context{position:sticky;top:0;z-index:var(--praxis-layer-sticky-local, 100);display:flex;flex-direction:column;gap:6px;padding:8px;border-radius:10px;border:1px solid var(--md-sys-color-outline-variant);background:color-mix(in srgb,var(--md-sys-color-surface-container-high) 88%,transparent);-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.always-visible-summary{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;padding:8px 10px;border:1px solid var(--md-sys-color-outline-variant);border-radius:10px;background:var(--md-sys-color-surface-container-low)}.always-visible-summary-item{display:flex;align-items:baseline;gap:6px;min-width:0}.summary-value{font-size:1rem;font-weight:700;color:var(--md-sys-color-primary)}.summary-label{font-size:.8rem;color:var(--md-sys-color-on-surface-variant);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.always-visible-quick-nav{display:flex;flex-wrap:wrap;gap:6px}.always-visible-quick-nav button{min-height:30px;font-size:.76rem;padding-inline:10px;transition:border-color .18s ease,background-color .18s ease,color .18s ease}.always-visible-quick-nav button.quick-nav-active{border-color:color-mix(in srgb,var(--md-sys-color-primary) 50%,var(--md-sys-color-outline-variant));background:color-mix(in srgb,var(--md-sys-color-primary-container) 34%,var(--md-sys-color-surface-container-low));color:var(--md-sys-color-primary)}.always-visible-section-anchor{scroll-margin-top:140px;opacity:0;transform:translateY(8px);animation:always-visible-section-enter .22s cubic-bezier(.2,0,0,1) forwards;animation-delay:var(--section-enter-delay, 0ms)}.always-visible-section-anchor.section-active,.always-visible-section-anchor.section-active .options-advanced-json{box-shadow:0 0 0 1px color-mix(in srgb,var(--md-sys-color-primary) 40%,transparent)}.always-visible-section-anchor:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}@keyframes always-visible-section-enter{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.options{display:flex;flex-direction:column;gap:.75rem;padding:12px;width:100%;max-width:100%;min-width:0;box-sizing:border-box;border:1px solid var(--md-sys-color-outline-variant);border-radius:12px;background:var(--md-sys-color-surface-container)}.options .options{padding:0;border:0;border-radius:0;background:transparent}.options-compact-flow{gap:8px;padding-top:10px;padding-bottom:10px}.section-divider{margin:2px;border-top-style:dashed;border-top-color:var(--md-sys-color-outline-variant);opacity:.28}.options-advanced-json{display:block;padding:0;overflow:hidden}:host ::ng-deep .options-advanced-json .mat-expansion-panel-header{padding:0 12px;min-height:52px}:host ::ng-deep .options-advanced-json .mat-expansion-panel-body{padding:0 12px 12px}.options-header{display:flex;flex-direction:column;gap:6px}.section-title{margin:0;font-size:.95rem;font-weight:600;color:var(--md-sys-color-on-surface)}.section-help{margin:0;font-size:.85rem;color:var(--md-sys-color-on-surface);opacity:.84}.field-hint{margin:-4px 0 0;font-size:.8rem;color:var(--md-sys-color-on-surface);opacity:.78}.praxis-help-icon-button{vertical-align:middle}.order-layout-toolbar{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px}.order-viewport-toggle-group{max-width:100%}.order-viewport-caption{display:inline-flex;align-items:center;min-height:28px;padding:0 10px;border-radius:999px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-lowest);font-size:.75rem;font-weight:600;color:var(--md-sys-color-on-surface-variant)}.order-layout-viewport{width:100%;display:flex;justify-content:center;padding:10px;border-radius:14px;border:1px solid var(--md-sys-color-outline-variant);background:color-mix(in srgb,var(--md-sys-color-surface-container-high) 74%,transparent)}.order-layout-frame{width:100%;transition:max-width .18s ease}.order-layout-frame[data-viewport=tablet] .order-lane-canvas{gap:7px}.order-layout-frame[data-viewport=mobile] .order-lane-canvas{gap:6px;padding:7px}.order-layout-frame[data-viewport=mobile] .order-pill{width:100%;max-width:100%;border-radius:12px}.order-layout-composer{display:flex;flex-direction:column;gap:9px}.order-lane{display:flex;flex-direction:column;gap:6px}.order-lane-header{display:flex;align-items:flex-start;justify-content:space-between;gap:10px}.order-lane-title-block{display:flex;flex-direction:column;gap:2px;min-width:0}.order-lane-title{margin:0;font-size:.85rem;font-weight:600;color:var(--md-sys-color-on-surface)}.order-lane-help{margin:0;font-size:.76rem;color:var(--md-sys-color-on-surface-variant)}.order-lane-count{display:inline-flex;align-items:center;justify-content:center;min-width:24px;height:24px;border-radius:999px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-low);color:var(--md-sys-color-on-surface-variant);font-size:.76rem;font-weight:600;padding:0 8px}.order-lane-canvas{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-height:56px;padding:8px;border:1px dashed var(--md-sys-color-outline-variant);border-radius:12px;background:var(--md-sys-color-surface-container-low)}.order-lane-canvas.cdk-drop-list-dragging{border-color:color-mix(in srgb,var(--md-sys-color-primary) 45%,var(--md-sys-color-outline-variant))}.order-lane[data-lane=actions] .order-lane-canvas{background:color-mix(in srgb,var(--md-sys-color-secondary-container) 28%,var(--md-sys-color-surface-container-low))}.order-lane[data-lane=inline] .order-lane-canvas{background:color-mix(in srgb,var(--md-sys-color-primary-container) 22%,var(--md-sys-color-surface-container-low))}.order-lane[data-lane=grid] .order-lane-canvas{background:color-mix(in srgb,var(--md-sys-color-tertiary-container) 18%,var(--md-sys-color-surface-container-low))}.order-lane-canvas.is-empty{justify-content:flex-start}.order-pill{display:inline-flex;align-items:center;gap:8px;max-width:min(100%,520px);min-height:38px;border:1px solid var(--md-sys-color-outline-variant);border-radius:999px;background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface);padding:4px 8px;box-sizing:border-box;cursor:grab;font:inherit;text-align:left}.order-pill:hover{border-color:var(--md-sys-color-outline)}.order-pill:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.order-pill mat-icon{opacity:.75}.order-pill-label{min-width:0;max-width:190px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.86rem;font-weight:500}.order-pill-badges{display:inline-flex;flex-wrap:wrap;align-items:center;gap:4px}.order-pill-chip{display:inline-flex;align-items:center;border-radius:999px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-lowest);color:var(--md-sys-color-on-surface-variant);font-size:.7rem;font-weight:600;padding:1px 7px;white-space:nowrap}.order-pill-chip-width{font-weight:500;opacity:.86}.order-pill-chip-override{border-color:color-mix(in srgb,var(--md-sys-color-primary) 42%,var(--md-sys-color-outline-variant));color:var(--md-sys-color-primary)}.order-lane-empty{margin:0;font-size:.78rem;color:var(--md-sys-color-on-surface-variant)}.order-pill.cdk-drag-preview{box-shadow:var(--md-sys-elevation-level2)}.order-pill.cdk-drag-placeholder{opacity:.45}.layout-preview-section{display:flex;flex-direction:column;gap:10px;padding:12px;border:1px solid var(--md-sys-color-outline-variant);border-radius:10px;background:var(--md-sys-color-surface-container-low);min-width:0}.layout-preview-title{margin:0;font-size:.95rem;font-weight:600;color:var(--md-sys-color-on-surface)}.layout-preview-cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px;min-width:0}.layout-preview-card{display:flex;flex-direction:column;gap:8px;padding:10px;border:1px solid var(--md-sys-color-outline-variant);border-radius:8px;background:var(--md-sys-color-surface-container);min-width:0}.layout-preview-header{display:flex;flex-direction:column;gap:2px}.layout-preview-header strong{font-size:.85rem;color:var(--md-sys-color-on-surface)}.layout-preview-header span{font-size:.78rem;color:var(--md-sys-color-on-surface-variant)}.layout-mini-grid{display:grid;gap:6px;min-width:0}.layout-mini-grid span{display:block;min-width:0;padding:6px 8px;border-radius:6px;background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container);font-size:.75rem;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tags-header{display:flex;align-items:center;gap:8px}.spacer{flex:1 1 auto}.new-tag{display:flex;flex-direction:column;gap:8px}.preset-row{display:flex;flex-wrap:wrap;gap:8px}.option-with-preview{display:flex;align-items:center;gap:8px}.preview-card{width:20px;height:20px;background:var(--md-sys-color-surface-container-high);border:1px solid var(--md-sys-color-outline-variant);border-radius:4px}.preview-frosted{width:20px;height:20px;background:var(--md-sys-color-surface-container);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border:1px solid var(--md-sys-color-outline-variant);border-radius:4px}.toggle-preview-section{margin-bottom:1.5rem;padding:1rem;background:var(--md-sys-color-surface-container-low);border-radius:8px;border:1px solid var(--md-sys-color-outline-variant)}.toggle-preview-section h4{margin:0 0 1rem;font-size:1.1rem;color:var(--md-sys-color-on-surface)}.preview-container{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:1rem}.preview-item{text-align:center}.preview-item strong{display:block;margin-bottom:.5rem;font-weight:500}.mock-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));align-items:center;gap:8px;padding:8px;background:var(--md-sys-color-surface);border:1px solid var(--md-sys-color-outline);border-radius:8px;min-height:72px}.mock-grid.with-booleans{grid-template-columns:repeat(3,minmax(0,1fr))}.mock-field{padding:4px 8px;background:var(--md-sys-color-primary-container);border-radius:6px;font-size:.8rem;text-align:center}.mock-actions{display:flex;align-items:center;gap:8px;padding:8px;background:var(--md-sys-color-surface);border:1px solid var(--md-sys-color-outline);border-radius:8px;min-height:48px}.mock-pill{display:flex;align-items:center;gap:8px;min-height:34px;padding:0 10px;border-radius:999px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface-variant);font-size:.82rem;justify-self:start}.mock-pill.compact{padding-inline:8px;gap:0}.mock-pill mat-slide-toggle{--mdc-switch-selected-track-color: var(--md-sys-color-primary);--mdc-switch-selected-handle-color: var(--md-sys-color-on-primary);transform:scale(.8);pointer-events:none}.mock-pill span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:500}.preview-section{display:flex;flex-direction:column;gap:16px;padding:16px;background:var(--md-sys-color-surface-container-low);border:1px solid var(--md-sys-color-outline-variant);border-radius:8px;margin-bottom:16px;box-shadow:var(--md-sys-elevation-level1);transition:background-color .3s,color .3s}.preview-section.dark-mode{background:var(--md-sys-color-surface-container-low);color:var(--md-sys-color-on-surface);border-color:var(--md-sys-color-outline-variant)}.preview-section.dark-mode .preview-label{color:var(--md-sys-color-on-surface-variant)}.preview-controls{display:flex;justify-content:space-between;align-items:center}.preview-label{font-weight:500;font-size:.9rem;color:var(--md-sys-color-on-surface-variant)}.preview-chips{display:flex;justify-content:center;padding:16px 0}.preview-chip-action--primary{color:var(--md-sys-color-primary)}.preview-chip-action--accent{color:var(--md-sys-color-tertiary)}.preview-chip-action--warn{color:var(--md-sys-color-error)}.praxis-filter-tags.outlined .mat-mdc-chip{background:transparent!important;--mat-chip-outline-width: 1px;--mat-chip-outline-color: var(--md-sys-color-outline-variant);box-shadow:inset 0 0 0 var(--mat-chip-outline-width) var(--mat-chip-outline-color)}.praxis-filter-tags.outlined.tag-color-primary .mat-mdc-chip{--mat-chip-outline-color: var(--md-sys-color-primary)}.praxis-filter-tags.outlined.tag-color-accent .mat-mdc-chip{--mat-chip-outline-color: var(--md-sys-color-tertiary)}.praxis-filter-tags.outlined.tag-color-warn .mat-mdc-chip{--mat-chip-outline-color: var(--md-sys-color-error)}.praxis-filter-tags.outlined mat-chip.active .mat-mdc-chip,.praxis-filter-tags.outlined .mat-mdc-chip.mat-mdc-chip-highlighted{--mat-chip-outline-width: 2px;box-shadow:inset 0 0 0 var(--mat-chip-outline-width) var(--mat-chip-outline-color)!important}.praxis-filter-tags.outlined.tag-color-basic mat-chip.active .mat-mdc-chip{--mat-chip-outline-color: var(--md-sys-color-primary)}.praxis-filter-tags .mat-mdc-standard-chip .mdc-evolution-chip__text-label{display:flex;align-items:center;gap:4px}.contrast-warning{display:flex;align-items:center;gap:8px;padding:8px 12px;background:var(--md-sys-color-error-container);color:var(--md-sys-color-on-error-container);border-radius:4px;font-size:.85rem}.contrast-warning mat-icon{font-size:20px;width:20px;height:20px}.preset-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:12px;margin-bottom:12px}.preset-card{display:flex;width:100%;flex-direction:column;gap:8px;padding:12px;text-align:left;font:inherit;color:inherit;background:transparent;border:1px solid var(--md-sys-color-outline-variant);border-radius:8px;cursor:pointer;transition:all .2s}.preset-card:hover{background:var(--md-sys-color-surface-container-high);border-color:var(--md-sys-color-outline)}.preset-card:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.preset-card.preset-active{border-color:var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary-container) 34%,transparent);box-shadow:inset 0 0 0 1px var(--md-sys-color-primary)}.preset-preview{height:24px;border-radius:4px;background:var(--md-sys-color-surface-variant)}.preset-preview.preview-minimal{background:transparent;border:1px solid var(--md-sys-color-outline)}.preset-preview.preview-primary{background:var(--md-sys-color-primary-container)}.preset-preview.preview-accent{background:var(--md-sys-color-tertiary-container)}.preset-preview.preview-warn{background:var(--md-sys-color-error-container)}.preset-info{display:flex;flex-direction:column}.preset-name{font-weight:500;font-size:.9rem}.preset-desc{font-size:.75rem;opacity:.7}.restore-btn{align-self:flex-start}.metadata-card-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:10px}.metadata-card{display:flex;flex-direction:column;gap:10px;padding:12px;border-radius:10px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-low)}.metadata-card-header{display:flex;justify-content:space-between;align-items:flex-start;gap:10px}.metadata-card-title{display:flex;flex-direction:column;min-width:0}.metadata-card-title strong{font-size:.92rem;color:var(--md-sys-color-on-surface)}.metadata-card-title span{font-size:.78rem;color:var(--md-sys-color-on-surface-variant);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.metadata-card-badges{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:6px}.metadata-chip{display:inline-flex;align-items:center;border-radius:999px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container);color:var(--md-sys-color-on-surface-variant);font-size:.78rem;font-weight:500;padding:2px 8px;white-space:nowrap}.metadata-chip-override{border-color:color-mix(in srgb,var(--md-sys-color-primary) 40%,var(--md-sys-color-outline-variant));color:var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary-container) 38%,var(--md-sys-color-surface-container))}.metadata-card-summary{margin:0;font-size:.8rem;color:var(--md-sys-color-on-surface-variant)}.metadata-card-actions{display:flex;flex-wrap:wrap;gap:6px}.metadata-card-actions button{display:inline-flex;align-items:center;gap:4px}.metadata-card-actions button:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:1px}:host ::ng-deep .metadata-card-actions button[disabled]{opacity:.62}.json-tip{margin:6px 0 0;font-size:.85rem;color:var(--md-sys-color-on-surface-variant)}.json-error{margin-top:6px;font-size:.85rem;color:var(--md-sys-color-error)}@media(prefers-reduced-motion:reduce){.always-visible-section-anchor{animation:none;opacity:1;transform:none}.always-visible-quick-nav button{transition:none}}@media(max-width:680px){.order-layout-toolbar{flex-direction:column;align-items:stretch}.order-viewport-caption{align-self:flex-start}.order-layout-viewport{padding:8px}.always-visible-context{position:static;-webkit-backdrop-filter:none;backdrop-filter:none}.always-visible-summary{grid-template-columns:1fr}.always-visible-quick-nav{display:grid;grid-template-columns:repeat(3,minmax(0,1fr))}.order-pill{width:100%;max-width:100%}.order-pill-label{max-width:none}.order-pill-badges{display:none}}.filter-rich-field-option{display:flex;flex-direction:column;gap:2px;padding:2px 0;line-height:1.25}.filter-rich-field-option__top{display:flex;align-items:center;gap:6px;flex-wrap:wrap}.filter-rich-field-option__label{font-weight:500;font-size:.82rem;color:var(--md-sys-color-on-surface)}.filter-rich-field-option__code{font-family:var(--praxis-font-mono, monospace);font-size:.7rem;padding:1px 5px;border-radius:4px;background:var(--md-sys-color-surface-container-highest);color:var(--md-sys-color-on-surface-variant)}.filter-rich-field-option__badge{margin-left:auto;font-size:.65rem;font-weight:600;text-transform:uppercase;padding:1px 6px;border-radius:999px;background:var(--md-sys-color-secondary-container);color:var(--md-sys-color-on-secondary-container)}.filter-rich-field-option__desc{font-size:.72rem;color:var(--md-sys-color-on-surface-variant);white-space:normal;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}\n"], dependencies: [{ kind: "component", type: FilterPresentationEditorComponent, selector: "praxis-filter-presentation-editor", inputs: ["value"], outputs: ["valueChange"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i10$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { 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.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1$1.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "component", type: i4$1.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i4$1.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2$1.MatLabel, selector: "mat-label" }, { kind: "directive", type: i2$1.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i2$1.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i2$1.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i4.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: i4.MatSelectTrigger, selector: "mat-select-trigger" }, { kind: "component", type: i4.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3$1.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i7$1.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "ngmodule", type: MatListModule }, { kind: "component", type: i8.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatButtonToggleModule }, { kind: "directive", type: i10$2.MatButtonToggleGroup, selector: "mat-button-toggle-group", inputs: ["appearance", "name", "vertical", "value", "multiple", "disabled", "disabledInteractive", "hideSingleSelectionIndicator", "hideMultipleSelectionIndicator"], outputs: ["valueChange", "change"], exportAs: ["matButtonToggleGroup"] }, { kind: "component", type: i10$2.MatButtonToggle, selector: "mat-button-toggle", inputs: ["aria-label", "aria-labelledby", "id", "name", "value", "tabIndex", "disableRipple", "appearance", "checked", "disabled", "disabledInteractive"], outputs: ["change"], exportAs: ["matButtonToggle"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i7.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: PraxisIconButtonComponent, selector: "button[praxisIconButton]", inputs: ["praxisIconButton", "size", "appearance", "presentation", "pressed", "busy"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i13.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i13.CdkDropListGroup, selector: "[cdkDropListGroup]", inputs: ["cdkDropListGroupDisabled"], exportAs: ["cdkDropListGroup"] }, { kind: "directive", type: i13.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i13.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i5$1.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i10.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatChipsModule }, { kind: "component", type: i15.MatChip, selector: "mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]", inputs: ["role", "id", "aria-label", "aria-description", "value", "color", "removable", "highlighted", "disableRipple", "disabled"], outputs: ["removed", "destroyed"], exportAs: ["matChip"] }, { kind: "directive", type: i15.MatChipAvatar, selector: "mat-chip-avatar, [matChipAvatar]" }, { kind: "component", type: i15.MatChipSet, selector: "mat-chip-set", inputs: ["disabled", "role", "tabIndex"] }, { kind: "directive", type: i15.MatChipTrailingIcon, selector: "mat-chip-trailing-icon, [matChipTrailingIcon]" }, { kind: "ngmodule", type: MatDividerModule }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "component", type: i6.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i6.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i6.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "directive", type: i6.MatExpansionPanelDescription, selector: "mat-panel-description" }, { kind: "directive", type: PraxisNumberWheelBlurDirective, selector: "input[type=number][praxisNumberWheelBlur]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
13057
13078
|
}
|
|
13058
13079
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: FilterSettingsComponent, decorators: [{
|
|
13059
13080
|
type: Component,
|
|
@@ -13080,7 +13101,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
13080
13101
|
MatDividerModule,
|
|
13081
13102
|
MatExpansionModule,
|
|
13082
13103
|
PraxisNumberWheelBlurDirective,
|
|
13083
|
-
], template: "<form [formGroup]=\"form\">\n <mat-tab-group (selectedTabChange)=\"onTabChange($event)\">\n <mat-tab label=\"Sempre Vis\u00EDveis\">\n <div class=\"always-visible-tab-flow\">\n <mat-form-field appearance=\"outline\" floatLabel=\"always\" class=\"full-width\">\n <mat-label>Campos sempre vis\u00EDveis</mat-label>\n <mat-select\n formControlName=\"alwaysVisibleFields\"\n multiple\n >\n @for (meta of filteredAlwaysVisibleMetadata(); track trackFieldMetadataByName($index, meta)) {\n <mat-option\n [value]=\"meta.name\"\n >\n {{ meta.label || meta.name }}\n </mat-option>\n }\n </mat-select>\n @if (metadataLoading && metadataSource === 'filter-dto') {\n <mat-hint>\n Carregando campos do DTO de filtro...\n </mat-hint>\n }\n @if (metadataErrorMsg) {\n <mat-error>{{ metadataErrorMsg }}</mat-error>\n }\n @if (!metadataLoading && !metadataErrorMsg && !metadata.length && metadataSource === 'filter-dto') {\n <mat-hint\n >\n Nenhum campo do DTO de filtro dispon\u00EDvel para este recurso.\n </mat-hint>\n }\n @if (!metadataLoading && !metadataErrorMsg && metadata.length && !hasAlwaysVisibleMetadataResults() && alwaysVisibleFieldSearchText) {\n <mat-hint\n >\n Nenhum campo encontrado para \"{{ alwaysVisibleFieldSearchText }}\".\n </mat-hint>\n }\n <button\n praxisIconButton=\"help_outline\"\n size=\"dense\"\n matSuffix\n type=\"button\"\n class=\"praxis-help-icon-button\"\n matTooltip=\"Escolha os campos que aparecem sempre na barra de filtros.\"\n aria-label=\"Escolha os campos que aparecem sempre na barra de filtros.\"\n ></button>\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Buscar campo</mat-label>\n <input\n matInput\n [ngModel]=\"alwaysVisibleFieldSearchText\"\n (ngModelChange)=\"onAlwaysVisibleFieldSearchChange($event)\"\n [ngModelOptions]=\"{standalone: true}\"\n placeholder=\"Filtrar por nome ou label\"\n />\n @if (alwaysVisibleFieldSearchText) {\n <button\n praxisIconButton=\"close\"\n size=\"dense\"\n matSuffix\n type=\"button\"\n (click)=\"clearAlwaysVisibleFieldSearch()\"\n aria-label=\"Limpar busca de campo\"\n ></button>\n }\n </mat-form-field>\n <p class=\"field-hint\">\n {{ alwaysVisibleSelectionCount() }} campo(s) selecionado(s).\n </p>\n <div class=\"always-visible-context\">\n <div class=\"always-visible-summary\" role=\"status\" aria-live=\"polite\">\n <div class=\"always-visible-summary-item\">\n <span class=\"summary-value\">{{ alwaysVisibleSelectionCount() }}</span>\n <span class=\"summary-label\">campos fixos</span>\n </div>\n <div class=\"always-visible-summary-item\">\n <span class=\"summary-value\">{{ activeAlwaysVisibleOverrideCount() }}</span>\n <span class=\"summary-label\">overrides ativos</span>\n </div>\n </div>\n <nav\n class=\"always-visible-quick-nav\"\n aria-label=\"Navega\u00E7\u00E3o r\u00E1pida da se\u00E7\u00E3o Sempre Vis\u00EDveis\"\n >\n <button\n mat-stroked-button\n type=\"button\"\n [class.quick-nav-active]=\"isAlwaysVisibleSectionActive('always-visible-order')\"\n [attr.aria-current]=\"isAlwaysVisibleSectionActive('always-visible-order') ? 'location' : null\"\n aria-controls=\"always-visible-order\"\n (click)=\"scrollToAlwaysVisibleSection('always-visible-order')\"\n >\n Ordem\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n [class.quick-nav-active]=\"isAlwaysVisibleSectionActive('always-visible-metadata')\"\n [attr.aria-current]=\"isAlwaysVisibleSectionActive('always-visible-metadata') ? 'location' : null\"\n aria-controls=\"always-visible-metadata\"\n (click)=\"scrollToAlwaysVisibleSection('always-visible-metadata')\"\n >\n Metadados\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n [class.quick-nav-active]=\"isAlwaysVisibleSectionActive('always-visible-json')\"\n [attr.aria-current]=\"isAlwaysVisibleSectionActive('always-visible-json') ? 'location' : null\"\n aria-controls=\"always-visible-json\"\n (click)=\"scrollToAlwaysVisibleSection('always-visible-json')\"\n >\n JSON\n </button>\n </nav>\n </div>\n\n <div\n id=\"always-visible-order\"\n class=\"options options-compact-flow always-visible-section-anchor\"\n [class.section-active]=\"isAlwaysVisibleSectionActive('always-visible-order')\"\n [style.--section-enter-delay]=\"sectionEnterDelay('always-visible-order')\"\n tabindex=\"-1\"\n (focusin)=\"onAlwaysVisibleSectionFocus('always-visible-order')\"\n >\n <div class=\"options-header\">\n <h4 class=\"section-title\">Ordem dos campos</h4>\n <p class=\"section-help\">\n Reordene no formato mais pr\u00F3ximo da toolbar real: faixa de a\u00E7\u00F5es, barra inline e grid.\n </p>\n </div>\n <div class=\"order-layout-toolbar\">\n <mat-button-toggle-group\n class=\"order-viewport-toggle-group\"\n [value]=\"orderPreviewViewport\"\n (change)=\"onAlwaysVisibleViewportChange($event.value)\"\n aria-label=\"Largura simulada da toolbar\"\n >\n @for (viewport of alwaysVisibleViewports; track viewport) {\n <mat-button-toggle\n [value]=\"viewport\"\n [attr.aria-label]=\"alwaysVisibleViewportHint(viewport)\"\n >\n {{ alwaysVisibleViewportLabel(viewport) }}\n </mat-button-toggle>\n }\n </mat-button-toggle-group>\n <span class=\"order-viewport-caption\">\n {{ alwaysVisibleViewportWidth() }}px\n </span>\n </div>\n <p class=\"section-help\">\n Ajuste o viewport para simular a quebra real das linhas na toolbar.\n </p>\n <div class=\"order-layout-viewport\">\n <div\n class=\"order-layout-frame\"\n [attr.data-viewport]=\"orderPreviewViewport\"\n [style.maxWidth.px]=\"alwaysVisibleViewportWidth()\"\n >\n <div class=\"order-layout-composer\" cdkDropListGroup>\n @for (lane of alwaysVisibleLayoutLanes; track lane) {\n <section\n class=\"order-lane\"\n [attr.data-lane]=\"lane\"\n >\n @if (alwaysVisibleLaneItems(lane); as laneItems) {\n <header class=\"order-lane-header\">\n <div class=\"order-lane-title-block\">\n <h5 class=\"order-lane-title\">{{ alwaysVisibleLaneLabel(lane) }}</h5>\n <p class=\"order-lane-help\">{{ alwaysVisibleLaneHint(lane) }}</p>\n </div>\n <span class=\"order-lane-count\">{{ laneItems.length }}</span>\n </header>\n <div\n cdkDropList\n class=\"order-lane-canvas\"\n [id]=\"alwaysVisibleLaneDropListId(lane)\"\n [class.is-empty]=\"!laneItems.length\"\n [cdkDropListData]=\"laneItems\"\n [cdkDropListEnterPredicate]=\"allowAlwaysVisibleLaneEnter\"\n cdkDropListOrientation=\"horizontal\"\n [attr.aria-label]=\"'Faixa ' + alwaysVisibleLaneLabel(lane)\"\n (cdkDropListDropped)=\"dropAlwaysVisibleLane(lane, $event)\"\n >\n @for (item of laneItems; track trackAlwaysVisibleLayoutItemByName(i, item); let i = $index) {\n <button\n type=\"button\"\n class=\"order-pill\"\n cdkDrag\n [cdkDragData]=\"item\"\n [attr.aria-label]=\"alwaysVisibleLayoutItemAriaLabel(item, lane, i, laneItems.length)\"\n (keydown)=\"onAlwaysVisibleLaneItemKeydown($event, lane, item.name)\"\n >\n <mat-icon [praxisIcon]=\"'drag_indicator'\" cdkDragHandle></mat-icon>\n <span class=\"order-pill-label\">{{ item.label }}</span>\n <span class=\"order-pill-badges\">\n <span class=\"order-pill-chip\">{{ item.controlTypeLabel }}</span>\n <span class=\"order-pill-chip order-pill-chip-width\">{{ item.widthHint }}</span>\n @if (item.hasOverride) {\n <span class=\"order-pill-chip order-pill-chip-override\">\n Override\n </span>\n }\n </span>\n </button>\n }\n @if (!laneItems.length) {\n <p class=\"order-lane-empty\">\n {{ alwaysVisibleLaneEmptyHint(lane) }}\n </p>\n }\n </div>\n }\n </section>\n }\n </div>\n </div>\n </div>\n <p class=\"field-hint\">\n Acessibilidade: use Alt/Ctrl + setas para reordenar dentro de cada faixa. O drag & drop n\u00E3o mistura faixas.\n </p>\n @if (!form.controls.alwaysVisibleFields.value?.length) {\n <p class=\"section-help\">\n Nenhum campo selecionado. Use o seletor acima para adicionar.\n </p>\n }\n </div>\n\n <mat-divider class=\"section-divider\" role=\"presentation\"></mat-divider>\n\n <div\n id=\"always-visible-metadata\"\n class=\"options options-compact-flow always-visible-section-anchor\"\n [class.section-active]=\"isAlwaysVisibleSectionActive('always-visible-metadata')\"\n [style.--section-enter-delay]=\"sectionEnterDelay('always-visible-metadata')\"\n tabindex=\"-1\"\n (focusin)=\"onAlwaysVisibleSectionFocus('always-visible-metadata')\"\n >\n <div class=\"options-header\">\n <h4 class=\"section-title\">Editor visual de metadados</h4>\n <p class=\"section-help\">\n Edite os principais ajustes dos campos sempre vis\u00EDveis no fluxo visual. Para propriedades avan\u00E7adas, use o JSON abaixo.\n </p>\n </div>\n @if (!form.controls.alwaysVisibleFields.value?.length) {\n <p class=\"section-help\">\n Selecione campos em \u201CCampos sempre vis\u00EDveis\u201D para habilitar a edi\u00E7\u00E3o visual.\n </p>\n }\n @if (form.controls.alwaysVisibleFields.value?.length) {\n <div class=\"metadata-card-grid\">\n @for (name of form.controls.alwaysVisibleFields.value; track name) {\n <article class=\"metadata-card\">\n <header class=\"metadata-card-header\">\n <div class=\"metadata-card-title\">\n <strong>{{ getFieldLabel(name) }}</strong>\n <span>{{ name }}</span>\n </div>\n <div class=\"metadata-card-badges\">\n <span class=\"metadata-chip\">{{ getAlwaysVisibleControlTypeLabel(name) }}</span>\n @if (hasAlwaysVisibleOverride(name)) {\n <span class=\"metadata-chip metadata-chip-override\">\n Override ativo\n </span>\n }\n </div>\n </header>\n <p class=\"metadata-card-summary\">{{ getAlwaysVisibleOverridePreview(name) }}</p>\n <div class=\"metadata-card-actions\">\n <button mat-stroked-button type=\"button\" (click)=\"openAlwaysVisibleMetadataEditor(name)\">\n <mat-icon [praxisIcon]=\"'tune'\"></mat-icon>\n Editar campo\n </button>\n <button\n mat-button\n type=\"button\"\n color=\"warn\"\n (click)=\"clearAlwaysVisibleOverride(name)\"\n [disabled]=\"!hasAlwaysVisibleOverride(name)\"\n >\n Limpar override\n </button>\n </div>\n </article>\n }\n </div>\n }\n </div>\n\n <mat-divider class=\"section-divider\" role=\"presentation\"></mat-divider>\n\n <div\n id=\"always-visible-json\"\n class=\"always-visible-section-anchor\"\n [class.section-active]=\"isAlwaysVisibleSectionActive('always-visible-json')\"\n [style.--section-enter-delay]=\"sectionEnterDelay('always-visible-json')\"\n tabindex=\"-1\"\n (focusin)=\"onAlwaysVisibleSectionFocus('always-visible-json')\"\n >\n <mat-expansion-panel\n class=\"options options-compact-flow options-advanced-json\"\n [expanded]=\"!!alwaysVisibleOverridesErrorMsg\"\n >\n <mat-expansion-panel-header>\n <mat-panel-title>Avan\u00E7ado (JSON opcional)</mat-panel-title>\n <mat-panel-description>Para ajustes n\u00E3o expostos no editor visual</mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"options-header\">\n <h4 class=\"section-title\">Modo avan\u00E7ado (JSON)</h4>\n <p class=\"section-help\">\n Use JSON para ajustes em lote ou propriedades ainda n\u00E3o expostas no editor visual.\n </p>\n </div>\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>alwaysVisibleFieldMetadataOverrides (JSON)</mat-label>\n <textarea\n matInput\n rows=\"8\"\n [(ngModel)]=\"alwaysVisibleOverridesJsonText\"\n [ngModelOptions]=\"{standalone: true}\"\n ></textarea>\n </mat-form-field>\n <div class=\"actions\">\n <button mat-stroked-button type=\"button\" (click)=\"formatAlwaysVisibleOverridesJson()\">Formatar</button>\n <button mat-stroked-button color=\"primary\" type=\"button\" (click)=\"applyAlwaysVisibleOverridesJson()\">\n Validar e carregar no editor\n </button>\n </div>\n <p class=\"json-tip\">\n Esta a\u00E7\u00E3o n\u00E3o persiste a configura\u00E7\u00E3o. Para persistir, use \u201CSalvar & Fechar\u201D no rodap\u00E9.\n </p>\n <p class=\"json-tip\">\n Exemplo:\n {\"status\":{\"controlType\":\"inlineSelect\",\"clearButton\":{\"enabled\":true},\"inlineAutoSize\":{\"minWidth\":140,\"maxWidth\":280}}}\n </p>\n @if (alwaysVisibleOverridesErrorMsg) {\n <p class=\"json-error\" role=\"status\" aria-live=\"polite\">{{ alwaysVisibleOverridesErrorMsg }}</p>\n }\n </mat-expansion-panel>\n </div>\n </div>\n </mat-tab>\n <mat-tab label=\"Layout\">\n <div class=\"options\">\n <div class=\"options-header\">\n <h4 class=\"section-title\">Grid e responsividade</h4>\n <p class=\"section-help\">\n Ajuste largura m\u00EDnima e n\u00FAmero de colunas por breakpoint. Em telas menores que 600px, o layout usa 1 coluna.\n </p>\n </div>\n <div class=\"layout-preview-section\" aria-live=\"polite\">\n <h5 class=\"layout-preview-title\">Pr\u00E9-visualiza\u00E7\u00E3o do grid</h5>\n <p class=\"section-help\">\n Simula\u00E7\u00E3o com {{ layoutPreviewFields.length }} campos e largura m\u00EDnima de {{ layoutMinWidth() }}px por campo.\n </p>\n <div class=\"layout-preview-cards\">\n <article class=\"layout-preview-card\">\n <div class=\"layout-preview-header\">\n <strong>Mobile</strong>\n <span><600px \u00B7 {{ layoutColsFor('sm') }} coluna</span>\n </div>\n <div class=\"layout-mini-grid\" [style.gridTemplateColumns]=\"'repeat(' + layoutColsFor('sm') + ', minmax(0, 1fr))'\">\n @for (field of layoutPreviewFields; track field) {\n <span>{{ field }}</span>\n }\n </div>\n </article>\n <article class=\"layout-preview-card\">\n <div class=\"layout-preview-header\">\n <strong>Tablet</strong>\n <span>≥600px \u00B7 {{ layoutColsFor('md') }} colunas</span>\n </div>\n <div class=\"layout-mini-grid\" [style.gridTemplateColumns]=\"'repeat(' + layoutColsFor('md') + ', minmax(0, 1fr))'\">\n @for (field of layoutPreviewFields; track field) {\n <span>{{ field }}</span>\n }\n </div>\n </article>\n <article class=\"layout-preview-card\">\n <div class=\"layout-preview-header\">\n <strong>Desktop</strong>\n <span>≥960px \u00B7 {{ layoutColsFor('lg') }} colunas</span>\n </div>\n <div class=\"layout-mini-grid\" [style.gridTemplateColumns]=\"'repeat(' + layoutColsFor('lg') + ', minmax(0, 1fr))'\">\n @for (field of layoutPreviewFields; track field) {\n <span>{{ field }}</span>\n }\n </div>\n </article>\n </div>\n </div>\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Largura m\u00EDnima por campo (px)</mat-label>\n <input\n matInput\n type=\"number\"\n formControlName=\"alwaysMinWidth\"\n min=\"180\"\n max=\"480\"\n step=\"1\"\n inputmode=\"numeric\"\n praxisNumberWheelBlur\n (blur)=\"normalizeLayoutControl('alwaysMinWidth')\"\n />\n <button\n praxisIconButton=\"help_outline\"\n size=\"dense\"\n matSuffix\n type=\"button\"\n class=\"praxis-help-icon-button\"\n aria-label=\"Ajuda sobre largura m\u00EDnima por campo\"\n matTooltip=\"Define a largura m\u00EDnima de cada campo no grid. Recomendado entre 200 e 300px.\"\n ></button>\n <mat-hint align=\"start\">Intervalo permitido: 180 a 480px.</mat-hint>\n <mat-hint align=\"end\">Recomendado: 200 a 300px.</mat-hint>\n @if (layoutControlError('alwaysMinWidth'); as error) {\n <mat-error>{{ error }}</mat-error>\n }\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Colunas em tablet (≥600px)</mat-label>\n <input\n matInput\n type=\"number\"\n formControlName=\"alwaysColsMd\"\n min=\"1\"\n max=\"3\"\n step=\"1\"\n inputmode=\"numeric\"\n praxisNumberWheelBlur\n (blur)=\"normalizeLayoutControl('alwaysColsMd')\"\n />\n <button\n praxisIconButton=\"help_outline\"\n size=\"dense\"\n matSuffix\n type=\"button\"\n class=\"praxis-help-icon-button\"\n aria-label=\"Ajuda sobre colunas em tablet\"\n matTooltip=\"Define quantas colunas aparecem em telas m\u00E9dias (a partir de 600px).\"\n ></button>\n <mat-hint align=\"start\">Mobile (<600px) usa 1 coluna.</mat-hint>\n <mat-hint align=\"end\">Intervalo: 1 a 3 colunas.</mat-hint>\n @if (layoutControlError('alwaysColsMd'); as error) {\n <mat-error>{{ error }}</mat-error>\n }\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Colunas em desktop (≥960px)</mat-label>\n <input\n matInput\n type=\"number\"\n formControlName=\"alwaysColsLg\"\n min=\"1\"\n max=\"4\"\n step=\"1\"\n inputmode=\"numeric\"\n praxisNumberWheelBlur\n (blur)=\"normalizeLayoutControl('alwaysColsLg')\"\n />\n <button\n praxisIconButton=\"help_outline\"\n size=\"dense\"\n matSuffix\n type=\"button\"\n class=\"praxis-help-icon-button\"\n aria-label=\"Ajuda sobre colunas em desktop\"\n matTooltip=\"Define quantas colunas aparecem em telas grandes (a partir de 960px).\"\n ></button>\n <mat-hint align=\"start\">Use mais colunas apenas quando houver espa\u00E7o.</mat-hint>\n <mat-hint align=\"end\">Intervalo: 1 a 4 colunas.</mat-hint>\n @if (layoutControlError('alwaysColsLg'); as error) {\n <mat-error>{{ error }}</mat-error>\n }\n </mat-form-field>\n </div>\n </mat-tab>\n <mat-tab label=\"Booleanos\">\n <div class=\"options\">\n @if (!hasBooleanFieldMetadata()) {\n <p class=\"section-help\">\n Nenhum campo booleano foi detectado no schema atual. Estas prefer\u00EAncias ser\u00E3o aplicadas quando houver\n campos `toggle`/boolean no filtro.\n </p>\n }\n @if (hasBooleanFieldMetadata()) {\n <div class=\"toggle-preview-section\">\n <h4>Visualiza\u00E7\u00E3o dos Booleanos</h4>\n <p class=\"section-help\">Veja o comportamento dos booleanos inline (com texto ou compacto) e a posi\u00E7\u00E3o na barra.</p>\n <div class=\"preview-container\">\n <div class=\"preview-item\">\n <strong>Grade Principal</strong>\n <div class=\"mock-grid\" [class.with-booleans]=\"!form.controls.placeBooleansInActions.value\">\n <div class=\"mock-field\">Campo 1</div>\n <div class=\"mock-field\">Campo 2</div>\n @if (!form.controls.placeBooleansInActions.value) {\n <div class=\"mock-pill\"\n [class.compact]=\"!form.controls.showToggleLabels.value\">\n <mat-slide-toggle [checked]=\"true\"></mat-slide-toggle>\n @if (form.controls.showToggleLabels.value) {\n <span>Ativo</span>\n }\n </div>\n }\n </div>\n </div>\n <div class=\"preview-item\">\n <strong>\u00C1rea de A\u00E7\u00F5es</strong>\n <div class=\"mock-actions\" [class.with-booleans]=\"form.controls.placeBooleansInActions.value\">\n <button praxisIconButton=\"search\" size=\"dense\" type=\"button\" tabindex=\"-1\"\n aria-disabled=\"true\" aria-label=\"Exemplo de a\u00E7\u00E3o de busca\"></button>\n <button praxisIconButton=\"tune\" size=\"dense\" type=\"button\" tabindex=\"-1\"\n aria-disabled=\"true\" aria-label=\"Exemplo de a\u00E7\u00E3o de filtros\"></button>\n @if (form.controls.placeBooleansInActions.value) {\n <div class=\"mock-pill\"\n [class.compact]=\"!form.controls.showToggleLabels.value\">\n <mat-slide-toggle [checked]=\"true\"></mat-slide-toggle>\n @if (form.controls.showToggleLabels.value) {\n <span>Ativo</span>\n }\n </div>\n }\n </div>\n </div>\n </div>\n </div>\n }\n <mat-checkbox formControlName=\"placeBooleansInActions\">\n <mat-icon>swap_horiz</mat-icon>\n Posicionar booleanos na \u00E1rea de a\u00E7\u00F5es\n </mat-checkbox>\n <p class=\"field-hint\">Move os booleanos da grade para os bot\u00F5es de a\u00E7\u00E3o, liberando espa\u00E7o horizontal.</p>\n <mat-checkbox formControlName=\"showToggleLabels\">\n <mat-icon>text_fields</mat-icon>\n Mostrar texto nos booleanos inline\n </mat-checkbox>\n <p class=\"field-hint\">Quando desativado, os booleanos ficam em modo compacto (somente controle, sem texto).</p>\n </div>\n </mat-tab>\n <mat-tab label=\"Op\u00E7\u00F5es\">\n <div class=\"options\">\n <div class=\"options-header\">\n <h4 class=\"section-title\">Painel Avan\u00E7ado</h4>\n <p class=\"section-help\">Apar\u00EAncia e comportamento do painel de filtros avan\u00E7ados.</p>\n </div>\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Modo de abertura do Avan\u00E7ado</mat-label>\n <mat-select formControlName=\"advancedOpenMode\">\n <mat-option value=\"modal\">Modal (padr\u00E3o)</mat-option>\n <mat-option value=\"drawer\">Drawer (direita)</mat-option>\n </mat-select>\n <button\n praxisIconButton=\"help_outline\"\n size=\"dense\"\n matSuffix\n type=\"button\"\n class=\"praxis-help-icon-button\"\n matTooltip=\"Escolha como o painel de filtros avan\u00E7ados ser\u00E1 exibido: centralizado ou lateral.\"\n aria-label=\"Escolha como o painel de filtros avan\u00E7ados ser\u00E1 exibido: centralizado ou lateral.\"\n ></button>\n </mat-form-field>\n\n <mat-checkbox formControlName=\"advancedClearButtonsEnabled\">\n Mostrar bot\u00E3o de limpar nos campos\n </mat-checkbox>\n <p class=\"field-hint\">\n Desative para ocultar o bot\u00E3o de limpar nos campos do painel avan\u00E7ado.\n </p>\n\n <mat-checkbox formControlName=\"useInlineSearchableSelectVariant\">\n Compactar selects com busca/async/autocomplete na barra inline\n </mat-checkbox>\n <p class=\"field-hint\">\n Padr\u00E3o do filtro din\u00E2mico. Desative apenas quando o host precisar preservar o renderer dedicado por compatibilidade, performance ou comportamento legado. O lookup de entidade inline \u00E9 configurado por controlType expl\u00EDcito (`inlineEntityLookup`).\n </p>\n\n <mat-checkbox formControlName=\"useInlineRangeVariant\">\n Compactar range slider na barra inline\n </mat-checkbox>\n <p class=\"field-hint\">\n Usa o componente inline em formato chip + popover para rangeSlider (single e faixa).\n </p>\n\n <mat-checkbox formControlName=\"useInlineDateVariant\">\n Compactar campos de data na barra inline\n </mat-checkbox>\n <p class=\"field-hint\">\n Usa o componente inline dedicado para date/dateInput/datepicker, com largura por conte\u00FAdo e placeholder compacto.\n </p>\n\n <mat-checkbox formControlName=\"useInlineDateRangeVariant\">\n Compactar intervalo de datas na barra inline\n </mat-checkbox>\n <p class=\"field-hint\">\n Padr\u00E3o do filtro din\u00E2mico para dateRange/daterange. Desative apenas para preservar o renderer tradicional em hosts legados.\n </p>\n\n <mat-checkbox formControlName=\"useInlineTimeVariant\">\n Compactar campos de horario na barra inline\n </mat-checkbox>\n <p class=\"field-hint\">\n Padr\u00E3o do filtro din\u00E2mico para time/timePicker, com largura por conteudo e clear rapido. Desative apenas para manter a experi\u00EAncia legada.\n </p>\n\n <mat-checkbox formControlName=\"useInlineTimeRangeVariant\">\n Compactar intervalo de horario na barra inline\n </mat-checkbox>\n <p class=\"field-hint\">\n Padr\u00E3o do filtro din\u00E2mico para timeRange/timerange. Desative apenas para preservar o renderer tradicional em hosts legados.\n </p>\n\n <mat-checkbox formControlName=\"useInlineTreeSelectVariant\">\n Compactar tree select na barra inline\n </mat-checkbox>\n <p class=\"field-hint\">\n Padr\u00E3o do filtro din\u00E2mico para treeSelect na faixa compacta. Desative apenas quando o host depender do renderer dedicado. multiSelectTree mant\u00E9m o componente dedicado.\n </p>\n\n <mat-checkbox formControlName=\"confirmTagDelete\">Confirmar exclus\u00E3o de atalho</mat-checkbox>\n <p class=\"field-hint\">Solicita confirma\u00E7\u00E3o antes de apagar atalhos salvos.</p>\n\n <div class=\"options\">\n <h4>Tema dos Atalhos (Tags)</h4>\n\n <!-- Preview Section -->\n <div class=\"preview-section\" [class.mdc-theme-dark]=\"previewDarkMode\" [class.dark-mode]=\"previewDarkMode\">\n <div class=\"preview-controls\">\n <span class=\"preview-label\">Pr\u00E9-visualiza\u00E7\u00E3o</span>\n <button [praxisIconButton]=\"previewDarkMode ? 'light_mode' : 'dark_mode'\" size=\"dense\" type=\"button\"\n (click)=\"previewDarkMode = !previewDarkMode\"\n [matTooltip]=\"previewDarkMode ? 'Mudar para fundo claro' : 'Mudar para fundo escuro'\"\n [attr.aria-label]=\"previewDarkMode ? 'Mudar para fundo claro' : 'Mudar para fundo escuro'\"></button>\n </div>\n\n <div class=\"preview-chips praxis-filter-tags\"\n [class.outlined]=\"form.controls.tagVariant.value === 'outlined'\"\n [class.tag-color-basic]=\"form.controls.tagColor.value === 'basic'\"\n [class.tag-color-primary]=\"form.controls.tagColor.value === 'primary'\"\n [class.tag-color-accent]=\"form.controls.tagColor.value === 'accent'\"\n [class.tag-color-warn]=\"form.controls.tagColor.value === 'warn'\">\n <mat-chip-set>\n <!-- Inactive Chip -->\n <mat-chip\n [color]=\"form.controls.tagVariant.value === 'outlined' || form.controls.tagColor.value === 'basic' ? null : form.controls.tagColor.value\"\n [highlighted]=\"form.controls.tagVariant.value === 'filled' && form.controls.tagColor.value !== 'basic'\">\n Atalho Inativo\n </mat-chip>\n\n <!-- Active Chip -->\n <mat-chip class=\"active\"\n [color]=\"form.controls.tagVariant.value === 'outlined' || form.controls.tagColor.value === 'basic' ? null : form.controls.tagColor.value\"\n [highlighted]=\"form.controls.tagVariant.value === 'filled' && form.controls.tagColor.value !== 'basic'\">\n <mat-icon matChipAvatar>check</mat-icon>\n Atalho Ativo\n <button matChipTrailingIcon praxisIconButton=\"arrow_drop_down\" size=\"dense\" type=\"button\"\n class=\"preview-chip-action\" tabindex=\"-1\" aria-disabled=\"true\"\n aria-label=\"Exemplo de menu do atalho ativo\"\n [class.preview-chip-action--primary]=\"form.controls.tagButtonColor.value === 'primary'\"\n [class.preview-chip-action--accent]=\"form.controls.tagButtonColor.value === 'accent'\"\n [class.preview-chip-action--warn]=\"form.controls.tagButtonColor.value === 'warn'\"></button>\n </mat-chip>\n </mat-chip-set>\n </div>\n\n @if (getContrastWarning(); as warning) {\n <div class=\"contrast-warning\">\n <mat-icon>warning</mat-icon>\n <span>{{ warning }}</span>\n </div>\n }\n </div>\n\n <!-- Reordered Fields -->\n <mat-form-field appearance=\"outline\">\n <mat-label>Variante do atalho</mat-label>\n <mat-select formControlName=\"tagVariant\">\n <mat-option value=\"filled\">Filled (Preenchido)</mat-option>\n <mat-option value=\"outlined\">Outlined (Contorno)</mat-option>\n </mat-select>\n <button\n praxisIconButton=\"help_outline\"\n size=\"dense\"\n matSuffix\n type=\"button\"\n class=\"praxis-help-icon-button\"\n matTooltip=\"Define o estilo base do atalho.\"\n aria-label=\"Define o estilo base do atalho.\"\n ></button>\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\">\n <mat-label>Cor do atalho</mat-label>\n <mat-select formControlName=\"tagColor\">\n <mat-option value=\"primary\">Primary</mat-option>\n <mat-option value=\"accent\">Accent</mat-option>\n <mat-option value=\"warn\">Warn</mat-option>\n <mat-option value=\"basic\">Basic</mat-option>\n </mat-select>\n <button\n praxisIconButton=\"help_outline\"\n size=\"dense\"\n matSuffix\n type=\"button\"\n class=\"praxis-help-icon-button\"\n matTooltip=\"Cor de fundo (Filled) ou de contorno (Outlined).\"\n aria-label=\"Cor de fundo (Filled) ou de contorno (Outlined).\"\n ></button>\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\">\n <mat-label>Cor do bot\u00E3o do menu do atalho</mat-label>\n <mat-select formControlName=\"tagButtonColor\">\n <mat-option value=\"basic\">Basic</mat-option>\n <mat-option value=\"primary\">Primary</mat-option>\n <mat-option value=\"accent\">Accent</mat-option>\n <mat-option value=\"warn\">Warn</mat-option>\n </mat-select>\n <button\n praxisIconButton=\"help_outline\"\n size=\"dense\"\n matSuffix\n type=\"button\"\n class=\"praxis-help-icon-button\"\n matTooltip=\"Cor do bot\u00E3o de dropdown dentro do atalho ativo.\"\n aria-label=\"Cor do bot\u00E3o de dropdown dentro do atalho ativo.\"\n ></button>\n </mat-form-field>\n </div>\n\n <div class=\"options\">\n <h4>Tema dos bot\u00F5es de a\u00E7\u00E3o</h4>\n <mat-form-field appearance=\"outline\">\n <mat-label>Cor dos bot\u00F5es</mat-label>\n <mat-select formControlName=\"actionsButtonColor\">\n <mat-option value=\"basic\">Basic</mat-option>\n <mat-option value=\"primary\">Primary</mat-option>\n <mat-option value=\"accent\">Accent</mat-option>\n <mat-option value=\"warn\">Warn</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Variante dos bot\u00F5es</mat-label>\n <mat-select formControlName=\"actionsVariant\">\n <mat-option value=\"standard\">Standard</mat-option>\n <mat-option value=\"outlined\">Outlined</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n\n <div class=\"options\">\n <h4>Predefini\u00E7\u00F5es de tema</h4>\n <div class=\"preset-grid\">\n @for (p of presets; track p) {\n <button\n type=\"button\"\n class=\"preset-card\"\n (click)=\"applyPreset(p.key)\"\n [class.preset-active]=\"isPresetActive(p.key)\"\n [attr.aria-pressed]=\"isPresetActive(p.key)\"\n [attr.aria-label]=\"'Aplicar predefini\u00E7\u00E3o: ' + p.label\"\n >\n <div class=\"preset-preview\" [ngClass]=\"p.previewClass\"></div>\n <div class=\"preset-info\">\n <span class=\"preset-name\">{{ p.label }}</span>\n <span class=\"preset-desc\">{{ p.description }}</span>\n </div>\n </button>\n }\n </div>\n <button mat-button color=\"warn\" (click)=\"resetPreferences()\" class=\"restore-btn\">\n Restaurar padr\u00E3o\n </button>\n </div>\n </div>\n </mat-tab>\n @if (configKey) {\n <mat-tab label=\"Atalhos\">\n <div class=\"options\">\n <p class=\"section-help\">Os atalhos s\u00E3o salvos por chave de configura\u00E7\u00E3o e compartilhados entre sess\u00F5es.</p>\n <div class=\"tags-header\">\n <span><strong>Atalhos salvos</strong></span>\n <span class=\"spacer\"></span>\n <button mat-stroked-button color=\"warn\" (click)=\"clearTags()\">Apagar tudo</button>\n </div>\n <div class=\"new-tag\">\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>R\u00F3tulo do novo atalho</mat-label>\n <input matInput [(ngModel)]=\"newTagLabel\" [ngModelOptions]=\"{standalone: true}\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Patch (JSON, opcional)</mat-label>\n <textarea matInput rows=\"3\" [(ngModel)]=\"newTagPatchText\" [ngModelOptions]=\"{standalone: true}\"></textarea>\n </mat-form-field>\n <div>\n <button mat-stroked-button color=\"primary\" (click)=\"addTag()\">Adicionar</button>\n @if (hasCurrentDto()) {\n <button mat-stroked-button (click)=\"addTagFromCurrent()\">Adicionar a partir dos filtros atuais</button>\n }\n </div>\n <div>\n <button mat-stroked-button color=\"primary\" (click)=\"importTags()\">Importar</button>\n <button mat-stroked-button (click)=\"loadTags()\">Recarregar</button>\n <button mat-stroked-button (click)=\"saveTags()\">Salvar</button>\n </div>\n </div>\n </div>\n </mat-tab>\n }\n <mat-tab label=\"JSON\">\n <div class=\"options\">\n <p><strong>Configura\u00E7\u00E3o atual (somente leitura)</strong></p>\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Atual</mat-label>\n <textarea matInput rows=\"8\" [value]=\"effectiveJson\" readonly></textarea>\n </mat-form-field>\n\n <p><strong>Editar JSON</strong></p>\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>JSON</mat-label>\n <textarea matInput rows=\"12\" [(ngModel)]=\"settingsJsonText\" [ngModelOptions]=\"{standalone: true}\"></textarea>\n </mat-form-field>\n <div class=\"actions\">\n <button mat-stroked-button (click)=\"onJsonReload()\">Recarregar do formul\u00E1rio</button>\n <button mat-stroked-button (click)=\"onJsonFormat()\">Formatar</button>\n <button mat-stroked-button color=\"primary\" (click)=\"onJsonApply()\">Carregar JSON no formul\u00E1rio</button>\n </div>\n <p class=\"json-tip\">\n Dica: esta a\u00E7\u00E3o apenas atualiza o editor local. Para persistir, use os bot\u00F5es Aplicar ou\n Salvar & Fechar no rodap\u00E9 do painel.\n </p>\n @if (jsonErrorMsg) {\n <p class=\"json-error\" role=\"status\" aria-live=\"polite\">{{ jsonErrorMsg }}</p>\n }\n </div>\n </mat-tab>\n <mat-tab label=\"Comportamento\">\n <div class=\"options\">\n <mat-checkbox\n formControlName=\"showAddButton\"\n data-testid=\"filter-settings-show-add-button\"\n >\n Exibir bot\u00E3o \"Gerenciar campos\" na barra\n </mat-checkbox>\n <mat-checkbox\n formControlName=\"showFilterSettings\"\n data-testid=\"filter-settings-show-filter-settings\"\n >\n Exibir bot\u00E3o \"Configura\u00E7\u00F5es do filtro\" na barra\n </mat-checkbox>\n <mat-checkbox formControlName=\"allowSaveTags\">Permitir salvar atalhos</mat-checkbox>\n </div>\n </mat-tab>\n <mat-tab label=\"Avan\u00E7ado\">\n <div class=\"options\">\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Debounce de altera\u00E7\u00F5es (ms)</mat-label>\n <input matInput type=\"number\" formControlName=\"changeDebounceMs\" min=\"100\" max=\"1000\" />\n @if (form.controls.changeDebounceMs.invalid) { <mat-error>Valor deve estar entre 100 e 1000</mat-error> }\n </mat-form-field>\n <div class=\"actions\">\n <button mat-stroked-button color=\"warn\" (click)=\"resetPreferences()\">\n <mat-icon [praxisIcon]=\"'restart_alt'\"></mat-icon>\n Redefinir prefer\u00EAncias do filtro\n </button>\n <button mat-stroked-button color=\"accent\" (click)=\"clearPersistedFilters()\">\n <mat-icon [praxisIcon]=\"'clear_all'\"></mat-icon>\n Limpar filtros salvos\n </button>\n <button mat-stroked-button (click)=\"forceSchemaReload()\">\n <mat-icon [praxisIcon]=\"'refresh'\"></mat-icon>\n For\u00E7ar recarregamento do schema\n </button>\n </div>\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>N\u00EDvel de log</mat-label>\n <mat-select formControlName=\"logLevel\">\n <mat-option value=\"none\">Nenhum</mat-option>\n <mat-option value=\"error\">Erro</mat-option>\n <mat-option value=\"warn\">Aviso</mat-option>\n <mat-option value=\"info\">Info</mat-option>\n <mat-option value=\"debug\">Debug</mat-option>\n </mat-select>\n <button\n praxisIconButton=\"help_outline\"\n size=\"dense\"\n matSuffix\n type=\"button\"\n class=\"praxis-help-icon-button\"\n matTooltip=\"Persistido no contrato para integra\u00E7\u00E3o host. Sem efeito direto no runtime atual do filtro.\"\n aria-label=\"Persistido no contrato para integra\u00E7\u00E3o host. Sem efeito direto no runtime atual do filtro.\"\n ></button>\n </mat-form-field>\n <mat-checkbox formControlName=\"enablePerformanceMetrics\">Habilitar m\u00E9tricas de performance</mat-checkbox>\n <p class=\"field-hint\">Prefer\u00EAncia persistida para integra\u00E7\u00E3o host. Sem efeito direto no runtime atual do filtro.</p>\n </div>\n </mat-tab>\n <mat-tab [label]=\"presentationTabLabel\">\n <praxis-filter-presentation-editor [value]=\"form.controls.presentation.value\" (valueChange)=\"updatePresentation($event)\" />\n </mat-tab>\n </mat-tab-group>\n</form>\n", styles: [":host{display:block;min-width:0}.full-width{width:100%}.always-visible-tab-flow{display:flex;flex-direction:column;gap:4px;padding-top:12px}.always-visible-context{position:sticky;top:0;z-index:var(--praxis-layer-sticky-local, 100);display:flex;flex-direction:column;gap:6px;padding:8px;border-radius:10px;border:1px solid var(--md-sys-color-outline-variant);background:color-mix(in srgb,var(--md-sys-color-surface-container-high) 88%,transparent);-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.always-visible-summary{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;padding:8px 10px;border:1px solid var(--md-sys-color-outline-variant);border-radius:10px;background:var(--md-sys-color-surface-container-low)}.always-visible-summary-item{display:flex;align-items:baseline;gap:6px;min-width:0}.summary-value{font-size:1rem;font-weight:700;color:var(--md-sys-color-primary)}.summary-label{font-size:.8rem;color:var(--md-sys-color-on-surface-variant);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.always-visible-quick-nav{display:flex;flex-wrap:wrap;gap:6px}.always-visible-quick-nav button{min-height:30px;font-size:.76rem;padding-inline:10px;transition:border-color .18s ease,background-color .18s ease,color .18s ease}.always-visible-quick-nav button.quick-nav-active{border-color:color-mix(in srgb,var(--md-sys-color-primary) 50%,var(--md-sys-color-outline-variant));background:color-mix(in srgb,var(--md-sys-color-primary-container) 34%,var(--md-sys-color-surface-container-low));color:var(--md-sys-color-primary)}.always-visible-section-anchor{scroll-margin-top:140px;opacity:0;transform:translateY(8px);animation:always-visible-section-enter .22s cubic-bezier(.2,0,0,1) forwards;animation-delay:var(--section-enter-delay, 0ms)}.always-visible-section-anchor.section-active,.always-visible-section-anchor.section-active .options-advanced-json{box-shadow:0 0 0 1px color-mix(in srgb,var(--md-sys-color-primary) 40%,transparent)}.always-visible-section-anchor:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}@keyframes always-visible-section-enter{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.options{display:flex;flex-direction:column;gap:.75rem;padding:12px;width:100%;max-width:100%;min-width:0;box-sizing:border-box;border:1px solid var(--md-sys-color-outline-variant);border-radius:12px;background:var(--md-sys-color-surface-container)}.options .options{padding:0;border:0;border-radius:0;background:transparent}.options-compact-flow{gap:8px;padding-top:10px;padding-bottom:10px}.section-divider{margin:2px;border-top-style:dashed;border-top-color:var(--md-sys-color-outline-variant);opacity:.28}.options-advanced-json{display:block;padding:0;overflow:hidden}:host ::ng-deep .options-advanced-json .mat-expansion-panel-header{padding:0 12px;min-height:52px}:host ::ng-deep .options-advanced-json .mat-expansion-panel-body{padding:0 12px 12px}.options-header{display:flex;flex-direction:column;gap:6px}.section-title{margin:0;font-size:.95rem;font-weight:600;color:var(--md-sys-color-on-surface)}.section-help{margin:0;font-size:.85rem;color:var(--md-sys-color-on-surface);opacity:.84}.field-hint{margin:-4px 0 0;font-size:.8rem;color:var(--md-sys-color-on-surface);opacity:.78}.praxis-help-icon-button{vertical-align:middle}.order-layout-toolbar{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px}.order-viewport-toggle-group{max-width:100%}.order-viewport-caption{display:inline-flex;align-items:center;min-height:28px;padding:0 10px;border-radius:999px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-lowest);font-size:.75rem;font-weight:600;color:var(--md-sys-color-on-surface-variant)}.order-layout-viewport{width:100%;display:flex;justify-content:center;padding:10px;border-radius:14px;border:1px solid var(--md-sys-color-outline-variant);background:color-mix(in srgb,var(--md-sys-color-surface-container-high) 74%,transparent)}.order-layout-frame{width:100%;transition:max-width .18s ease}.order-layout-frame[data-viewport=tablet] .order-lane-canvas{gap:7px}.order-layout-frame[data-viewport=mobile] .order-lane-canvas{gap:6px;padding:7px}.order-layout-frame[data-viewport=mobile] .order-pill{width:100%;max-width:100%;border-radius:12px}.order-layout-composer{display:flex;flex-direction:column;gap:9px}.order-lane{display:flex;flex-direction:column;gap:6px}.order-lane-header{display:flex;align-items:flex-start;justify-content:space-between;gap:10px}.order-lane-title-block{display:flex;flex-direction:column;gap:2px;min-width:0}.order-lane-title{margin:0;font-size:.85rem;font-weight:600;color:var(--md-sys-color-on-surface)}.order-lane-help{margin:0;font-size:.76rem;color:var(--md-sys-color-on-surface-variant)}.order-lane-count{display:inline-flex;align-items:center;justify-content:center;min-width:24px;height:24px;border-radius:999px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-low);color:var(--md-sys-color-on-surface-variant);font-size:.76rem;font-weight:600;padding:0 8px}.order-lane-canvas{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-height:56px;padding:8px;border:1px dashed var(--md-sys-color-outline-variant);border-radius:12px;background:var(--md-sys-color-surface-container-low)}.order-lane-canvas.cdk-drop-list-dragging{border-color:color-mix(in srgb,var(--md-sys-color-primary) 45%,var(--md-sys-color-outline-variant))}.order-lane[data-lane=actions] .order-lane-canvas{background:color-mix(in srgb,var(--md-sys-color-secondary-container) 28%,var(--md-sys-color-surface-container-low))}.order-lane[data-lane=inline] .order-lane-canvas{background:color-mix(in srgb,var(--md-sys-color-primary-container) 22%,var(--md-sys-color-surface-container-low))}.order-lane[data-lane=grid] .order-lane-canvas{background:color-mix(in srgb,var(--md-sys-color-tertiary-container) 18%,var(--md-sys-color-surface-container-low))}.order-lane-canvas.is-empty{justify-content:flex-start}.order-pill{display:inline-flex;align-items:center;gap:8px;max-width:min(100%,520px);min-height:38px;border:1px solid var(--md-sys-color-outline-variant);border-radius:999px;background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface);padding:4px 8px;box-sizing:border-box;cursor:grab;font:inherit;text-align:left}.order-pill:hover{border-color:var(--md-sys-color-outline)}.order-pill:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.order-pill mat-icon{opacity:.75}.order-pill-label{min-width:0;max-width:190px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.86rem;font-weight:500}.order-pill-badges{display:inline-flex;flex-wrap:wrap;align-items:center;gap:4px}.order-pill-chip{display:inline-flex;align-items:center;border-radius:999px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-lowest);color:var(--md-sys-color-on-surface-variant);font-size:.7rem;font-weight:600;padding:1px 7px;white-space:nowrap}.order-pill-chip-width{font-weight:500;opacity:.86}.order-pill-chip-override{border-color:color-mix(in srgb,var(--md-sys-color-primary) 42%,var(--md-sys-color-outline-variant));color:var(--md-sys-color-primary)}.order-lane-empty{margin:0;font-size:.78rem;color:var(--md-sys-color-on-surface-variant)}.order-pill.cdk-drag-preview{box-shadow:var(--md-sys-elevation-level2)}.order-pill.cdk-drag-placeholder{opacity:.45}.layout-preview-section{display:flex;flex-direction:column;gap:10px;padding:12px;border:1px solid var(--md-sys-color-outline-variant);border-radius:10px;background:var(--md-sys-color-surface-container-low);min-width:0}.layout-preview-title{margin:0;font-size:.95rem;font-weight:600;color:var(--md-sys-color-on-surface)}.layout-preview-cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px;min-width:0}.layout-preview-card{display:flex;flex-direction:column;gap:8px;padding:10px;border:1px solid var(--md-sys-color-outline-variant);border-radius:8px;background:var(--md-sys-color-surface-container);min-width:0}.layout-preview-header{display:flex;flex-direction:column;gap:2px}.layout-preview-header strong{font-size:.85rem;color:var(--md-sys-color-on-surface)}.layout-preview-header span{font-size:.78rem;color:var(--md-sys-color-on-surface-variant)}.layout-mini-grid{display:grid;gap:6px;min-width:0}.layout-mini-grid span{display:block;min-width:0;padding:6px 8px;border-radius:6px;background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container);font-size:.75rem;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tags-header{display:flex;align-items:center;gap:8px}.spacer{flex:1 1 auto}.new-tag{display:flex;flex-direction:column;gap:8px}.preset-row{display:flex;flex-wrap:wrap;gap:8px}.option-with-preview{display:flex;align-items:center;gap:8px}.preview-card{width:20px;height:20px;background:var(--md-sys-color-surface-container-high);border:1px solid var(--md-sys-color-outline-variant);border-radius:4px}.preview-frosted{width:20px;height:20px;background:var(--md-sys-color-surface-container);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border:1px solid var(--md-sys-color-outline-variant);border-radius:4px}.toggle-preview-section{margin-bottom:1.5rem;padding:1rem;background:var(--md-sys-color-surface-container-low);border-radius:8px;border:1px solid var(--md-sys-color-outline-variant)}.toggle-preview-section h4{margin:0 0 1rem;font-size:1.1rem;color:var(--md-sys-color-on-surface)}.preview-container{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:1rem}.preview-item{text-align:center}.preview-item strong{display:block;margin-bottom:.5rem;font-weight:500}.mock-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));align-items:center;gap:8px;padding:8px;background:var(--md-sys-color-surface);border:1px solid var(--md-sys-color-outline);border-radius:8px;min-height:72px}.mock-grid.with-booleans{grid-template-columns:repeat(3,minmax(0,1fr))}.mock-field{padding:4px 8px;background:var(--md-sys-color-primary-container);border-radius:6px;font-size:.8rem;text-align:center}.mock-actions{display:flex;align-items:center;gap:8px;padding:8px;background:var(--md-sys-color-surface);border:1px solid var(--md-sys-color-outline);border-radius:8px;min-height:48px}.mock-pill{display:flex;align-items:center;gap:8px;min-height:34px;padding:0 10px;border-radius:999px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface-variant);font-size:.82rem;justify-self:start}.mock-pill.compact{padding-inline:8px;gap:0}.mock-pill mat-slide-toggle{--mdc-switch-selected-track-color: var(--md-sys-color-primary);--mdc-switch-selected-handle-color: var(--md-sys-color-on-primary);transform:scale(.8);pointer-events:none}.mock-pill span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:500}.preview-section{display:flex;flex-direction:column;gap:16px;padding:16px;background:var(--md-sys-color-surface-container-low);border:1px solid var(--md-sys-color-outline-variant);border-radius:8px;margin-bottom:16px;box-shadow:var(--md-sys-elevation-level1);transition:background-color .3s,color .3s}.preview-section.dark-mode{background:var(--md-sys-color-surface-container-low);color:var(--md-sys-color-on-surface);border-color:var(--md-sys-color-outline-variant)}.preview-section.dark-mode .preview-label{color:var(--md-sys-color-on-surface-variant)}.preview-controls{display:flex;justify-content:space-between;align-items:center}.preview-label{font-weight:500;font-size:.9rem;color:var(--md-sys-color-on-surface-variant)}.preview-chips{display:flex;justify-content:center;padding:16px 0}.preview-chip-action--primary{color:var(--md-sys-color-primary)}.preview-chip-action--accent{color:var(--md-sys-color-tertiary)}.preview-chip-action--warn{color:var(--md-sys-color-error)}.praxis-filter-tags.outlined .mat-mdc-chip{background:transparent!important;--mat-chip-outline-width: 1px;--mat-chip-outline-color: var(--md-sys-color-outline-variant);box-shadow:inset 0 0 0 var(--mat-chip-outline-width) var(--mat-chip-outline-color)}.praxis-filter-tags.outlined.tag-color-primary .mat-mdc-chip{--mat-chip-outline-color: var(--md-sys-color-primary)}.praxis-filter-tags.outlined.tag-color-accent .mat-mdc-chip{--mat-chip-outline-color: var(--md-sys-color-tertiary)}.praxis-filter-tags.outlined.tag-color-warn .mat-mdc-chip{--mat-chip-outline-color: var(--md-sys-color-error)}.praxis-filter-tags.outlined mat-chip.active .mat-mdc-chip,.praxis-filter-tags.outlined .mat-mdc-chip.mat-mdc-chip-highlighted{--mat-chip-outline-width: 2px;box-shadow:inset 0 0 0 var(--mat-chip-outline-width) var(--mat-chip-outline-color)!important}.praxis-filter-tags.outlined.tag-color-basic mat-chip.active .mat-mdc-chip{--mat-chip-outline-color: var(--md-sys-color-primary)}.praxis-filter-tags .mat-mdc-standard-chip .mdc-evolution-chip__text-label{display:flex;align-items:center;gap:4px}.contrast-warning{display:flex;align-items:center;gap:8px;padding:8px 12px;background:var(--md-sys-color-error-container);color:var(--md-sys-color-on-error-container);border-radius:4px;font-size:.85rem}.contrast-warning mat-icon{font-size:20px;width:20px;height:20px}.preset-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:12px;margin-bottom:12px}.preset-card{display:flex;width:100%;flex-direction:column;gap:8px;padding:12px;text-align:left;font:inherit;color:inherit;background:transparent;border:1px solid var(--md-sys-color-outline-variant);border-radius:8px;cursor:pointer;transition:all .2s}.preset-card:hover{background:var(--md-sys-color-surface-container-high);border-color:var(--md-sys-color-outline)}.preset-card:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.preset-card.preset-active{border-color:var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary-container) 34%,transparent);box-shadow:inset 0 0 0 1px var(--md-sys-color-primary)}.preset-preview{height:24px;border-radius:4px;background:var(--md-sys-color-surface-variant)}.preset-preview.preview-minimal{background:transparent;border:1px solid var(--md-sys-color-outline)}.preset-preview.preview-primary{background:var(--md-sys-color-primary-container)}.preset-preview.preview-accent{background:var(--md-sys-color-tertiary-container)}.preset-preview.preview-warn{background:var(--md-sys-color-error-container)}.preset-info{display:flex;flex-direction:column}.preset-name{font-weight:500;font-size:.9rem}.preset-desc{font-size:.75rem;opacity:.7}.restore-btn{align-self:flex-start}.metadata-card-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:10px}.metadata-card{display:flex;flex-direction:column;gap:10px;padding:12px;border-radius:10px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-low)}.metadata-card-header{display:flex;justify-content:space-between;align-items:flex-start;gap:10px}.metadata-card-title{display:flex;flex-direction:column;min-width:0}.metadata-card-title strong{font-size:.92rem;color:var(--md-sys-color-on-surface)}.metadata-card-title span{font-size:.78rem;color:var(--md-sys-color-on-surface-variant);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.metadata-card-badges{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:6px}.metadata-chip{display:inline-flex;align-items:center;border-radius:999px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container);color:var(--md-sys-color-on-surface-variant);font-size:.78rem;font-weight:500;padding:2px 8px;white-space:nowrap}.metadata-chip-override{border-color:color-mix(in srgb,var(--md-sys-color-primary) 40%,var(--md-sys-color-outline-variant));color:var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary-container) 38%,var(--md-sys-color-surface-container))}.metadata-card-summary{margin:0;font-size:.8rem;color:var(--md-sys-color-on-surface-variant)}.metadata-card-actions{display:flex;flex-wrap:wrap;gap:6px}.metadata-card-actions button{display:inline-flex;align-items:center;gap:4px}.metadata-card-actions button:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:1px}:host ::ng-deep .metadata-card-actions button[disabled]{opacity:.62}.json-tip{margin:6px 0 0;font-size:.85rem;color:var(--md-sys-color-on-surface-variant)}.json-error{margin-top:6px;font-size:.85rem;color:var(--md-sys-color-error)}@media(prefers-reduced-motion:reduce){.always-visible-section-anchor{animation:none;opacity:1;transform:none}.always-visible-quick-nav button{transition:none}}@media(max-width:680px){.order-layout-toolbar{flex-direction:column;align-items:stretch}.order-viewport-caption{align-self:flex-start}.order-layout-viewport{padding:8px}.always-visible-context{position:static;-webkit-backdrop-filter:none;backdrop-filter:none}.always-visible-summary{grid-template-columns:1fr}.always-visible-quick-nav{display:grid;grid-template-columns:repeat(3,minmax(0,1fr))}.order-pill{width:100%;max-width:100%}.order-pill-label{max-width:none}.order-pill-badges{display:none}}\n"] }]
|
|
13104
|
+
], template: "<form [formGroup]=\"form\">\n <mat-tab-group (selectedTabChange)=\"onTabChange($event)\">\n <mat-tab label=\"Sempre Vis\u00EDveis\">\n <div class=\"always-visible-tab-flow\">\n <mat-form-field appearance=\"outline\" floatLabel=\"always\" class=\"full-width\">\n <mat-label>Campos sempre vis\u00EDveis</mat-label>\n <mat-select\n formControlName=\"alwaysVisibleFields\"\n multiple\n >\n @if (alwaysVisibleSelectionCount() > 0) {\n <mat-select-trigger>{{ getSelectedAlwaysVisibleTriggerLabel() }}</mat-select-trigger>\n }\n @for (meta of filteredAlwaysVisibleMetadata(); track trackFieldMetadataByName($index, meta)) {\n <mat-option\n [value]=\"meta.name\"\n >\n <div class=\"filter-rich-field-option\">\n <div class=\"filter-rich-field-option__top\">\n <span class=\"filter-rich-field-option__label\">{{ meta.label || meta.name }}</span>\n @if (meta.label && meta.label !== meta.name) {\n <code class=\"filter-rich-field-option__code\">{{ meta.name }}</code>\n }\n @if (getFieldTypeBadge(meta); as badge) {\n <span class=\"filter-rich-field-option__badge\">{{ badge }}</span>\n }\n </div>\n @if (getFieldDescription(meta); as desc) {\n <div class=\"filter-rich-field-option__desc\">{{ desc }}</div>\n }\n </div>\n </mat-option>\n }\n </mat-select>\n @if (metadataLoading && metadataSource === 'filter-dto') {\n <mat-hint>\n Carregando campos do DTO de filtro...\n </mat-hint>\n }\n @if (metadataErrorMsg) {\n <mat-error>{{ metadataErrorMsg }}</mat-error>\n }\n @if (!metadataLoading && !metadataErrorMsg && !metadata.length && metadataSource === 'filter-dto') {\n <mat-hint\n >\n Nenhum campo do DTO de filtro dispon\u00EDvel para este recurso.\n </mat-hint>\n }\n @if (!metadataLoading && !metadataErrorMsg && metadata.length && !hasAlwaysVisibleMetadataResults() && alwaysVisibleFieldSearchText) {\n <mat-hint\n >\n Nenhum campo encontrado para \"{{ alwaysVisibleFieldSearchText }}\".\n </mat-hint>\n }\n <button\n praxisIconButton=\"help_outline\"\n size=\"dense\"\n matSuffix\n type=\"button\"\n class=\"praxis-help-icon-button\"\n matTooltip=\"Escolha os campos que aparecem sempre na barra de filtros.\"\n aria-label=\"Escolha os campos que aparecem sempre na barra de filtros.\"\n ></button>\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Buscar campo</mat-label>\n <input\n matInput\n [ngModel]=\"alwaysVisibleFieldSearchText\"\n (ngModelChange)=\"onAlwaysVisibleFieldSearchChange($event)\"\n [ngModelOptions]=\"{standalone: true}\"\n placeholder=\"Filtrar por nome, label, tipo ou descri\u00E7\u00E3o\"\n />\n @if (alwaysVisibleFieldSearchText) {\n <button\n praxisIconButton=\"close\"\n size=\"dense\"\n matSuffix\n type=\"button\"\n (click)=\"clearAlwaysVisibleFieldSearch()\"\n aria-label=\"Limpar busca de campo\"\n ></button>\n }\n </mat-form-field>\n <p class=\"field-hint\">\n {{ alwaysVisibleSelectionCount() }} campo(s) selecionado(s).\n </p>\n <div class=\"always-visible-context\">\n <div class=\"always-visible-summary\" role=\"status\" aria-live=\"polite\">\n <div class=\"always-visible-summary-item\">\n <span class=\"summary-value\">{{ alwaysVisibleSelectionCount() }}</span>\n <span class=\"summary-label\">campos fixos</span>\n </div>\n <div class=\"always-visible-summary-item\">\n <span class=\"summary-value\">{{ activeAlwaysVisibleOverrideCount() }}</span>\n <span class=\"summary-label\">overrides ativos</span>\n </div>\n </div>\n <nav\n class=\"always-visible-quick-nav\"\n aria-label=\"Navega\u00E7\u00E3o r\u00E1pida da se\u00E7\u00E3o Sempre Vis\u00EDveis\"\n >\n <button\n mat-stroked-button\n type=\"button\"\n [class.quick-nav-active]=\"isAlwaysVisibleSectionActive('always-visible-order')\"\n [attr.aria-current]=\"isAlwaysVisibleSectionActive('always-visible-order') ? 'location' : null\"\n aria-controls=\"always-visible-order\"\n (click)=\"scrollToAlwaysVisibleSection('always-visible-order')\"\n >\n Ordem\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n [class.quick-nav-active]=\"isAlwaysVisibleSectionActive('always-visible-metadata')\"\n [attr.aria-current]=\"isAlwaysVisibleSectionActive('always-visible-metadata') ? 'location' : null\"\n aria-controls=\"always-visible-metadata\"\n (click)=\"scrollToAlwaysVisibleSection('always-visible-metadata')\"\n >\n Metadados\n </button>\n <button\n mat-stroked-button\n type=\"button\"\n [class.quick-nav-active]=\"isAlwaysVisibleSectionActive('always-visible-json')\"\n [attr.aria-current]=\"isAlwaysVisibleSectionActive('always-visible-json') ? 'location' : null\"\n aria-controls=\"always-visible-json\"\n (click)=\"scrollToAlwaysVisibleSection('always-visible-json')\"\n >\n JSON\n </button>\n </nav>\n </div>\n\n <div\n id=\"always-visible-order\"\n class=\"options options-compact-flow always-visible-section-anchor\"\n [class.section-active]=\"isAlwaysVisibleSectionActive('always-visible-order')\"\n [style.--section-enter-delay]=\"sectionEnterDelay('always-visible-order')\"\n tabindex=\"-1\"\n (focusin)=\"onAlwaysVisibleSectionFocus('always-visible-order')\"\n >\n <div class=\"options-header\">\n <h4 class=\"section-title\">Ordem dos campos</h4>\n <p class=\"section-help\">\n Reordene no formato mais pr\u00F3ximo da toolbar real: faixa de a\u00E7\u00F5es, barra inline e grid.\n </p>\n </div>\n <div class=\"order-layout-toolbar\">\n <mat-button-toggle-group\n class=\"order-viewport-toggle-group\"\n [value]=\"orderPreviewViewport\"\n (change)=\"onAlwaysVisibleViewportChange($event.value)\"\n aria-label=\"Largura simulada da toolbar\"\n >\n @for (viewport of alwaysVisibleViewports; track viewport) {\n <mat-button-toggle\n [value]=\"viewport\"\n [attr.aria-label]=\"alwaysVisibleViewportHint(viewport)\"\n >\n {{ alwaysVisibleViewportLabel(viewport) }}\n </mat-button-toggle>\n }\n </mat-button-toggle-group>\n <span class=\"order-viewport-caption\">\n {{ alwaysVisibleViewportWidth() }}px\n </span>\n </div>\n <p class=\"section-help\">\n Ajuste o viewport para simular a quebra real das linhas na toolbar.\n </p>\n <div class=\"order-layout-viewport\">\n <div\n class=\"order-layout-frame\"\n [attr.data-viewport]=\"orderPreviewViewport\"\n [style.maxWidth.px]=\"alwaysVisibleViewportWidth()\"\n >\n <div class=\"order-layout-composer\" cdkDropListGroup>\n @for (lane of alwaysVisibleLayoutLanes; track lane) {\n <section\n class=\"order-lane\"\n [attr.data-lane]=\"lane\"\n >\n @if (alwaysVisibleLaneItems(lane); as laneItems) {\n <header class=\"order-lane-header\">\n <div class=\"order-lane-title-block\">\n <h5 class=\"order-lane-title\">{{ alwaysVisibleLaneLabel(lane) }}</h5>\n <p class=\"order-lane-help\">{{ alwaysVisibleLaneHint(lane) }}</p>\n </div>\n <span class=\"order-lane-count\">{{ laneItems.length }}</span>\n </header>\n <div\n cdkDropList\n class=\"order-lane-canvas\"\n [id]=\"alwaysVisibleLaneDropListId(lane)\"\n [class.is-empty]=\"!laneItems.length\"\n [cdkDropListData]=\"laneItems\"\n [cdkDropListEnterPredicate]=\"allowAlwaysVisibleLaneEnter\"\n cdkDropListOrientation=\"horizontal\"\n [attr.aria-label]=\"'Faixa ' + alwaysVisibleLaneLabel(lane)\"\n (cdkDropListDropped)=\"dropAlwaysVisibleLane(lane, $event)\"\n >\n @for (item of laneItems; track trackAlwaysVisibleLayoutItemByName(i, item); let i = $index) {\n <button\n type=\"button\"\n class=\"order-pill\"\n cdkDrag\n [cdkDragData]=\"item\"\n [attr.aria-label]=\"alwaysVisibleLayoutItemAriaLabel(item, lane, i, laneItems.length)\"\n (keydown)=\"onAlwaysVisibleLaneItemKeydown($event, lane, item.name)\"\n >\n <mat-icon [praxisIcon]=\"'drag_indicator'\" cdkDragHandle></mat-icon>\n <span class=\"order-pill-label\">{{ item.label }}</span>\n <span class=\"order-pill-badges\">\n <span class=\"order-pill-chip\">{{ item.controlTypeLabel }}</span>\n <span class=\"order-pill-chip order-pill-chip-width\">{{ item.widthHint }}</span>\n @if (item.hasOverride) {\n <span class=\"order-pill-chip order-pill-chip-override\">\n Override\n </span>\n }\n </span>\n </button>\n }\n @if (!laneItems.length) {\n <p class=\"order-lane-empty\">\n {{ alwaysVisibleLaneEmptyHint(lane) }}\n </p>\n }\n </div>\n }\n </section>\n }\n </div>\n </div>\n </div>\n <p class=\"field-hint\">\n Acessibilidade: use Alt/Ctrl + setas para reordenar dentro de cada faixa. O drag & drop n\u00E3o mistura faixas.\n </p>\n @if (!form.controls.alwaysVisibleFields.value?.length) {\n <p class=\"section-help\">\n Nenhum campo selecionado. Use o seletor acima para adicionar.\n </p>\n }\n </div>\n\n <mat-divider class=\"section-divider\" role=\"presentation\"></mat-divider>\n\n <div\n id=\"always-visible-metadata\"\n class=\"options options-compact-flow always-visible-section-anchor\"\n [class.section-active]=\"isAlwaysVisibleSectionActive('always-visible-metadata')\"\n [style.--section-enter-delay]=\"sectionEnterDelay('always-visible-metadata')\"\n tabindex=\"-1\"\n (focusin)=\"onAlwaysVisibleSectionFocus('always-visible-metadata')\"\n >\n <div class=\"options-header\">\n <h4 class=\"section-title\">Editor visual de metadados</h4>\n <p class=\"section-help\">\n Edite os principais ajustes dos campos sempre vis\u00EDveis no fluxo visual. Para propriedades avan\u00E7adas, use o JSON abaixo.\n </p>\n </div>\n @if (!form.controls.alwaysVisibleFields.value?.length) {\n <p class=\"section-help\">\n Selecione campos em \u201CCampos sempre vis\u00EDveis\u201D para habilitar a edi\u00E7\u00E3o visual.\n </p>\n }\n @if (form.controls.alwaysVisibleFields.value?.length) {\n <div class=\"metadata-card-grid\">\n @for (name of form.controls.alwaysVisibleFields.value; track name) {\n <article class=\"metadata-card\">\n <header class=\"metadata-card-header\">\n <div class=\"metadata-card-title\">\n <strong>{{ getFieldLabel(name) }}</strong>\n <span>{{ name }}</span>\n </div>\n <div class=\"metadata-card-badges\">\n <span class=\"metadata-chip\">{{ getAlwaysVisibleControlTypeLabel(name) }}</span>\n @if (hasAlwaysVisibleOverride(name)) {\n <span class=\"metadata-chip metadata-chip-override\">\n Override ativo\n </span>\n }\n </div>\n </header>\n <p class=\"metadata-card-summary\">{{ getAlwaysVisibleOverridePreview(name) }}</p>\n <div class=\"metadata-card-actions\">\n <button mat-stroked-button type=\"button\" (click)=\"openAlwaysVisibleMetadataEditor(name)\">\n <mat-icon [praxisIcon]=\"'tune'\"></mat-icon>\n Editar campo\n </button>\n <button\n mat-button\n type=\"button\"\n color=\"warn\"\n (click)=\"clearAlwaysVisibleOverride(name)\"\n [disabled]=\"!hasAlwaysVisibleOverride(name)\"\n >\n Limpar override\n </button>\n </div>\n </article>\n }\n </div>\n }\n </div>\n\n <mat-divider class=\"section-divider\" role=\"presentation\"></mat-divider>\n\n <div\n id=\"always-visible-json\"\n class=\"always-visible-section-anchor\"\n [class.section-active]=\"isAlwaysVisibleSectionActive('always-visible-json')\"\n [style.--section-enter-delay]=\"sectionEnterDelay('always-visible-json')\"\n tabindex=\"-1\"\n (focusin)=\"onAlwaysVisibleSectionFocus('always-visible-json')\"\n >\n <mat-expansion-panel\n class=\"options options-compact-flow options-advanced-json\"\n [expanded]=\"!!alwaysVisibleOverridesErrorMsg\"\n >\n <mat-expansion-panel-header>\n <mat-panel-title>Avan\u00E7ado (JSON opcional)</mat-panel-title>\n <mat-panel-description>Para ajustes n\u00E3o expostos no editor visual</mat-panel-description>\n </mat-expansion-panel-header>\n <div class=\"options-header\">\n <h4 class=\"section-title\">Modo avan\u00E7ado (JSON)</h4>\n <p class=\"section-help\">\n Use JSON para ajustes em lote ou propriedades ainda n\u00E3o expostas no editor visual.\n </p>\n </div>\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>alwaysVisibleFieldMetadataOverrides (JSON)</mat-label>\n <textarea\n matInput\n rows=\"8\"\n [(ngModel)]=\"alwaysVisibleOverridesJsonText\"\n [ngModelOptions]=\"{standalone: true}\"\n ></textarea>\n </mat-form-field>\n <div class=\"actions\">\n <button mat-stroked-button type=\"button\" (click)=\"formatAlwaysVisibleOverridesJson()\">Formatar</button>\n <button mat-stroked-button color=\"primary\" type=\"button\" (click)=\"applyAlwaysVisibleOverridesJson()\">\n Validar e carregar no editor\n </button>\n </div>\n <p class=\"json-tip\">\n Esta a\u00E7\u00E3o n\u00E3o persiste a configura\u00E7\u00E3o. Para persistir, use \u201CSalvar & Fechar\u201D no rodap\u00E9.\n </p>\n <p class=\"json-tip\">\n Exemplo:\n {\"status\":{\"controlType\":\"inlineSelect\",\"clearButton\":{\"enabled\":true},\"inlineAutoSize\":{\"minWidth\":140,\"maxWidth\":280}}}\n </p>\n @if (alwaysVisibleOverridesErrorMsg) {\n <p class=\"json-error\" role=\"status\" aria-live=\"polite\">{{ alwaysVisibleOverridesErrorMsg }}</p>\n }\n </mat-expansion-panel>\n </div>\n </div>\n </mat-tab>\n <mat-tab label=\"Layout\">\n <div class=\"options\">\n <div class=\"options-header\">\n <h4 class=\"section-title\">Grid e responsividade</h4>\n <p class=\"section-help\">\n Ajuste largura m\u00EDnima e n\u00FAmero de colunas por breakpoint. Em telas menores que 600px, o layout usa 1 coluna.\n </p>\n </div>\n <div class=\"layout-preview-section\" aria-live=\"polite\">\n <h5 class=\"layout-preview-title\">Pr\u00E9-visualiza\u00E7\u00E3o do grid</h5>\n <p class=\"section-help\">\n Simula\u00E7\u00E3o com {{ layoutPreviewFields.length }} campos e largura m\u00EDnima de {{ layoutMinWidth() }}px por campo.\n </p>\n <div class=\"layout-preview-cards\">\n <article class=\"layout-preview-card\">\n <div class=\"layout-preview-header\">\n <strong>Mobile</strong>\n <span><600px \u00B7 {{ layoutColsFor('sm') }} coluna</span>\n </div>\n <div class=\"layout-mini-grid\" [style.gridTemplateColumns]=\"'repeat(' + layoutColsFor('sm') + ', minmax(0, 1fr))'\">\n @for (field of layoutPreviewFields; track field) {\n <span>{{ field }}</span>\n }\n </div>\n </article>\n <article class=\"layout-preview-card\">\n <div class=\"layout-preview-header\">\n <strong>Tablet</strong>\n <span>≥600px \u00B7 {{ layoutColsFor('md') }} colunas</span>\n </div>\n <div class=\"layout-mini-grid\" [style.gridTemplateColumns]=\"'repeat(' + layoutColsFor('md') + ', minmax(0, 1fr))'\">\n @for (field of layoutPreviewFields; track field) {\n <span>{{ field }}</span>\n }\n </div>\n </article>\n <article class=\"layout-preview-card\">\n <div class=\"layout-preview-header\">\n <strong>Desktop</strong>\n <span>≥960px \u00B7 {{ layoutColsFor('lg') }} colunas</span>\n </div>\n <div class=\"layout-mini-grid\" [style.gridTemplateColumns]=\"'repeat(' + layoutColsFor('lg') + ', minmax(0, 1fr))'\">\n @for (field of layoutPreviewFields; track field) {\n <span>{{ field }}</span>\n }\n </div>\n </article>\n </div>\n </div>\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Largura m\u00EDnima por campo (px)</mat-label>\n <input\n matInput\n type=\"number\"\n formControlName=\"alwaysMinWidth\"\n min=\"180\"\n max=\"480\"\n step=\"1\"\n inputmode=\"numeric\"\n praxisNumberWheelBlur\n (blur)=\"normalizeLayoutControl('alwaysMinWidth')\"\n />\n <button\n praxisIconButton=\"help_outline\"\n size=\"dense\"\n matSuffix\n type=\"button\"\n class=\"praxis-help-icon-button\"\n aria-label=\"Ajuda sobre largura m\u00EDnima por campo\"\n matTooltip=\"Define a largura m\u00EDnima de cada campo no grid. Recomendado entre 200 e 300px.\"\n ></button>\n <mat-hint align=\"start\">Intervalo permitido: 180 a 480px.</mat-hint>\n <mat-hint align=\"end\">Recomendado: 200 a 300px.</mat-hint>\n @if (layoutControlError('alwaysMinWidth'); as error) {\n <mat-error>{{ error }}</mat-error>\n }\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Colunas em tablet (≥600px)</mat-label>\n <input\n matInput\n type=\"number\"\n formControlName=\"alwaysColsMd\"\n min=\"1\"\n max=\"3\"\n step=\"1\"\n inputmode=\"numeric\"\n praxisNumberWheelBlur\n (blur)=\"normalizeLayoutControl('alwaysColsMd')\"\n />\n <button\n praxisIconButton=\"help_outline\"\n size=\"dense\"\n matSuffix\n type=\"button\"\n class=\"praxis-help-icon-button\"\n aria-label=\"Ajuda sobre colunas em tablet\"\n matTooltip=\"Define quantas colunas aparecem em telas m\u00E9dias (a partir de 600px).\"\n ></button>\n <mat-hint align=\"start\">Mobile (<600px) usa 1 coluna.</mat-hint>\n <mat-hint align=\"end\">Intervalo: 1 a 3 colunas.</mat-hint>\n @if (layoutControlError('alwaysColsMd'); as error) {\n <mat-error>{{ error }}</mat-error>\n }\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Colunas em desktop (≥960px)</mat-label>\n <input\n matInput\n type=\"number\"\n formControlName=\"alwaysColsLg\"\n min=\"1\"\n max=\"4\"\n step=\"1\"\n inputmode=\"numeric\"\n praxisNumberWheelBlur\n (blur)=\"normalizeLayoutControl('alwaysColsLg')\"\n />\n <button\n praxisIconButton=\"help_outline\"\n size=\"dense\"\n matSuffix\n type=\"button\"\n class=\"praxis-help-icon-button\"\n aria-label=\"Ajuda sobre colunas em desktop\"\n matTooltip=\"Define quantas colunas aparecem em telas grandes (a partir de 960px).\"\n ></button>\n <mat-hint align=\"start\">Use mais colunas apenas quando houver espa\u00E7o.</mat-hint>\n <mat-hint align=\"end\">Intervalo: 1 a 4 colunas.</mat-hint>\n @if (layoutControlError('alwaysColsLg'); as error) {\n <mat-error>{{ error }}</mat-error>\n }\n </mat-form-field>\n </div>\n </mat-tab>\n <mat-tab label=\"Booleanos\">\n <div class=\"options\">\n @if (!hasBooleanFieldMetadata()) {\n <p class=\"section-help\">\n Nenhum campo booleano foi detectado no schema atual. Estas prefer\u00EAncias ser\u00E3o aplicadas quando houver\n campos `toggle`/boolean no filtro.\n </p>\n }\n @if (hasBooleanFieldMetadata()) {\n <div class=\"toggle-preview-section\">\n <h4>Visualiza\u00E7\u00E3o dos Booleanos</h4>\n <p class=\"section-help\">Veja o comportamento dos booleanos inline (com texto ou compacto) e a posi\u00E7\u00E3o na barra.</p>\n <div class=\"preview-container\">\n <div class=\"preview-item\">\n <strong>Grade Principal</strong>\n <div class=\"mock-grid\" [class.with-booleans]=\"!form.controls.placeBooleansInActions.value\">\n <div class=\"mock-field\">Campo 1</div>\n <div class=\"mock-field\">Campo 2</div>\n @if (!form.controls.placeBooleansInActions.value) {\n <div class=\"mock-pill\"\n [class.compact]=\"!form.controls.showToggleLabels.value\">\n <mat-slide-toggle [checked]=\"true\"></mat-slide-toggle>\n @if (form.controls.showToggleLabels.value) {\n <span>Ativo</span>\n }\n </div>\n }\n </div>\n </div>\n <div class=\"preview-item\">\n <strong>\u00C1rea de A\u00E7\u00F5es</strong>\n <div class=\"mock-actions\" [class.with-booleans]=\"form.controls.placeBooleansInActions.value\">\n <button praxisIconButton=\"search\" size=\"dense\" type=\"button\" tabindex=\"-1\"\n aria-disabled=\"true\" aria-label=\"Exemplo de a\u00E7\u00E3o de busca\"></button>\n <button praxisIconButton=\"tune\" size=\"dense\" type=\"button\" tabindex=\"-1\"\n aria-disabled=\"true\" aria-label=\"Exemplo de a\u00E7\u00E3o de filtros\"></button>\n @if (form.controls.placeBooleansInActions.value) {\n <div class=\"mock-pill\"\n [class.compact]=\"!form.controls.showToggleLabels.value\">\n <mat-slide-toggle [checked]=\"true\"></mat-slide-toggle>\n @if (form.controls.showToggleLabels.value) {\n <span>Ativo</span>\n }\n </div>\n }\n </div>\n </div>\n </div>\n </div>\n }\n <mat-checkbox formControlName=\"placeBooleansInActions\">\n <mat-icon>swap_horiz</mat-icon>\n Posicionar booleanos na \u00E1rea de a\u00E7\u00F5es\n </mat-checkbox>\n <p class=\"field-hint\">Move os booleanos da grade para os bot\u00F5es de a\u00E7\u00E3o, liberando espa\u00E7o horizontal.</p>\n <mat-checkbox formControlName=\"showToggleLabels\">\n <mat-icon>text_fields</mat-icon>\n Mostrar texto nos booleanos inline\n </mat-checkbox>\n <p class=\"field-hint\">Quando desativado, os booleanos ficam em modo compacto (somente controle, sem texto).</p>\n </div>\n </mat-tab>\n <mat-tab label=\"Op\u00E7\u00F5es\">\n <div class=\"options\">\n <div class=\"options-header\">\n <h4 class=\"section-title\">Painel Avan\u00E7ado</h4>\n <p class=\"section-help\">Apar\u00EAncia e comportamento do painel de filtros avan\u00E7ados.</p>\n </div>\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Modo de abertura do Avan\u00E7ado</mat-label>\n <mat-select formControlName=\"advancedOpenMode\">\n <mat-option value=\"modal\">Modal (padr\u00E3o)</mat-option>\n <mat-option value=\"drawer\">Drawer (direita)</mat-option>\n </mat-select>\n <button\n praxisIconButton=\"help_outline\"\n size=\"dense\"\n matSuffix\n type=\"button\"\n class=\"praxis-help-icon-button\"\n matTooltip=\"Escolha como o painel de filtros avan\u00E7ados ser\u00E1 exibido: centralizado ou lateral.\"\n aria-label=\"Escolha como o painel de filtros avan\u00E7ados ser\u00E1 exibido: centralizado ou lateral.\"\n ></button>\n </mat-form-field>\n\n <mat-checkbox formControlName=\"advancedClearButtonsEnabled\">\n Mostrar bot\u00E3o de limpar nos campos\n </mat-checkbox>\n <p class=\"field-hint\">\n Desative para ocultar o bot\u00E3o de limpar nos campos do painel avan\u00E7ado.\n </p>\n\n <mat-checkbox formControlName=\"useInlineSearchableSelectVariant\">\n Compactar selects com busca/async/autocomplete na barra inline\n </mat-checkbox>\n <p class=\"field-hint\">\n Padr\u00E3o do filtro din\u00E2mico. Desative apenas quando o host precisar preservar o renderer dedicado por compatibilidade, performance ou comportamento legado. O lookup de entidade inline \u00E9 configurado por controlType expl\u00EDcito (`inlineEntityLookup`).\n </p>\n\n <mat-checkbox formControlName=\"useInlineRangeVariant\">\n Compactar range slider na barra inline\n </mat-checkbox>\n <p class=\"field-hint\">\n Usa o componente inline em formato chip + popover para rangeSlider (single e faixa).\n </p>\n\n <mat-checkbox formControlName=\"useInlineDateVariant\">\n Compactar campos de data na barra inline\n </mat-checkbox>\n <p class=\"field-hint\">\n Usa o componente inline dedicado para date/dateInput/datepicker, com largura por conte\u00FAdo e placeholder compacto.\n </p>\n\n <mat-checkbox formControlName=\"useInlineDateRangeVariant\">\n Compactar intervalo de datas na barra inline\n </mat-checkbox>\n <p class=\"field-hint\">\n Padr\u00E3o do filtro din\u00E2mico para dateRange/daterange. Desative apenas para preservar o renderer tradicional em hosts legados.\n </p>\n\n <mat-checkbox formControlName=\"useInlineTimeVariant\">\n Compactar campos de horario na barra inline\n </mat-checkbox>\n <p class=\"field-hint\">\n Padr\u00E3o do filtro din\u00E2mico para time/timePicker, com largura por conteudo e clear rapido. Desative apenas para manter a experi\u00EAncia legada.\n </p>\n\n <mat-checkbox formControlName=\"useInlineTimeRangeVariant\">\n Compactar intervalo de horario na barra inline\n </mat-checkbox>\n <p class=\"field-hint\">\n Padr\u00E3o do filtro din\u00E2mico para timeRange/timerange. Desative apenas para preservar o renderer tradicional em hosts legados.\n </p>\n\n <mat-checkbox formControlName=\"useInlineTreeSelectVariant\">\n Compactar tree select na barra inline\n </mat-checkbox>\n <p class=\"field-hint\">\n Padr\u00E3o do filtro din\u00E2mico para treeSelect na faixa compacta. Desative apenas quando o host depender do renderer dedicado. multiSelectTree mant\u00E9m o componente dedicado.\n </p>\n\n <mat-checkbox formControlName=\"confirmTagDelete\">Confirmar exclus\u00E3o de atalho</mat-checkbox>\n <p class=\"field-hint\">Solicita confirma\u00E7\u00E3o antes de apagar atalhos salvos.</p>\n\n <div class=\"options\">\n <h4>Tema dos Atalhos (Tags)</h4>\n\n <!-- Preview Section -->\n <div class=\"preview-section\" [class.mdc-theme-dark]=\"previewDarkMode\" [class.dark-mode]=\"previewDarkMode\">\n <div class=\"preview-controls\">\n <span class=\"preview-label\">Pr\u00E9-visualiza\u00E7\u00E3o</span>\n <button [praxisIconButton]=\"previewDarkMode ? 'light_mode' : 'dark_mode'\" size=\"dense\" type=\"button\"\n (click)=\"previewDarkMode = !previewDarkMode\"\n [matTooltip]=\"previewDarkMode ? 'Mudar para fundo claro' : 'Mudar para fundo escuro'\"\n [attr.aria-label]=\"previewDarkMode ? 'Mudar para fundo claro' : 'Mudar para fundo escuro'\"></button>\n </div>\n\n <div class=\"preview-chips praxis-filter-tags\"\n [class.outlined]=\"form.controls.tagVariant.value === 'outlined'\"\n [class.tag-color-basic]=\"form.controls.tagColor.value === 'basic'\"\n [class.tag-color-primary]=\"form.controls.tagColor.value === 'primary'\"\n [class.tag-color-accent]=\"form.controls.tagColor.value === 'accent'\"\n [class.tag-color-warn]=\"form.controls.tagColor.value === 'warn'\">\n <mat-chip-set>\n <!-- Inactive Chip -->\n <mat-chip\n [color]=\"form.controls.tagVariant.value === 'outlined' || form.controls.tagColor.value === 'basic' ? null : form.controls.tagColor.value\"\n [highlighted]=\"form.controls.tagVariant.value === 'filled' && form.controls.tagColor.value !== 'basic'\">\n Atalho Inativo\n </mat-chip>\n\n <!-- Active Chip -->\n <mat-chip class=\"active\"\n [color]=\"form.controls.tagVariant.value === 'outlined' || form.controls.tagColor.value === 'basic' ? null : form.controls.tagColor.value\"\n [highlighted]=\"form.controls.tagVariant.value === 'filled' && form.controls.tagColor.value !== 'basic'\">\n <mat-icon matChipAvatar>check</mat-icon>\n Atalho Ativo\n <button matChipTrailingIcon praxisIconButton=\"arrow_drop_down\" size=\"dense\" type=\"button\"\n class=\"preview-chip-action\" tabindex=\"-1\" aria-disabled=\"true\"\n aria-label=\"Exemplo de menu do atalho ativo\"\n [class.preview-chip-action--primary]=\"form.controls.tagButtonColor.value === 'primary'\"\n [class.preview-chip-action--accent]=\"form.controls.tagButtonColor.value === 'accent'\"\n [class.preview-chip-action--warn]=\"form.controls.tagButtonColor.value === 'warn'\"></button>\n </mat-chip>\n </mat-chip-set>\n </div>\n\n @if (getContrastWarning(); as warning) {\n <div class=\"contrast-warning\">\n <mat-icon>warning</mat-icon>\n <span>{{ warning }}</span>\n </div>\n }\n </div>\n\n <!-- Reordered Fields -->\n <mat-form-field appearance=\"outline\">\n <mat-label>Variante do atalho</mat-label>\n <mat-select formControlName=\"tagVariant\">\n <mat-option value=\"filled\">Filled (Preenchido)</mat-option>\n <mat-option value=\"outlined\">Outlined (Contorno)</mat-option>\n </mat-select>\n <button\n praxisIconButton=\"help_outline\"\n size=\"dense\"\n matSuffix\n type=\"button\"\n class=\"praxis-help-icon-button\"\n matTooltip=\"Define o estilo base do atalho.\"\n aria-label=\"Define o estilo base do atalho.\"\n ></button>\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\">\n <mat-label>Cor do atalho</mat-label>\n <mat-select formControlName=\"tagColor\">\n <mat-option value=\"primary\">Primary</mat-option>\n <mat-option value=\"accent\">Accent</mat-option>\n <mat-option value=\"warn\">Warn</mat-option>\n <mat-option value=\"basic\">Basic</mat-option>\n </mat-select>\n <button\n praxisIconButton=\"help_outline\"\n size=\"dense\"\n matSuffix\n type=\"button\"\n class=\"praxis-help-icon-button\"\n matTooltip=\"Cor de fundo (Filled) ou de contorno (Outlined).\"\n aria-label=\"Cor de fundo (Filled) ou de contorno (Outlined).\"\n ></button>\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\">\n <mat-label>Cor do bot\u00E3o do menu do atalho</mat-label>\n <mat-select formControlName=\"tagButtonColor\">\n <mat-option value=\"basic\">Basic</mat-option>\n <mat-option value=\"primary\">Primary</mat-option>\n <mat-option value=\"accent\">Accent</mat-option>\n <mat-option value=\"warn\">Warn</mat-option>\n </mat-select>\n <button\n praxisIconButton=\"help_outline\"\n size=\"dense\"\n matSuffix\n type=\"button\"\n class=\"praxis-help-icon-button\"\n matTooltip=\"Cor do bot\u00E3o de dropdown dentro do atalho ativo.\"\n aria-label=\"Cor do bot\u00E3o de dropdown dentro do atalho ativo.\"\n ></button>\n </mat-form-field>\n </div>\n\n <div class=\"options\">\n <h4>Tema dos bot\u00F5es de a\u00E7\u00E3o</h4>\n <mat-form-field appearance=\"outline\">\n <mat-label>Cor dos bot\u00F5es</mat-label>\n <mat-select formControlName=\"actionsButtonColor\">\n <mat-option value=\"basic\">Basic</mat-option>\n <mat-option value=\"primary\">Primary</mat-option>\n <mat-option value=\"accent\">Accent</mat-option>\n <mat-option value=\"warn\">Warn</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Variante dos bot\u00F5es</mat-label>\n <mat-select formControlName=\"actionsVariant\">\n <mat-option value=\"standard\">Standard</mat-option>\n <mat-option value=\"outlined\">Outlined</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n\n <div class=\"options\">\n <h4>Predefini\u00E7\u00F5es de tema</h4>\n <div class=\"preset-grid\">\n @for (p of presets; track p) {\n <button\n type=\"button\"\n class=\"preset-card\"\n (click)=\"applyPreset(p.key)\"\n [class.preset-active]=\"isPresetActive(p.key)\"\n [attr.aria-pressed]=\"isPresetActive(p.key)\"\n [attr.aria-label]=\"'Aplicar predefini\u00E7\u00E3o: ' + p.label\"\n >\n <div class=\"preset-preview\" [ngClass]=\"p.previewClass\"></div>\n <div class=\"preset-info\">\n <span class=\"preset-name\">{{ p.label }}</span>\n <span class=\"preset-desc\">{{ p.description }}</span>\n </div>\n </button>\n }\n </div>\n <button mat-button color=\"warn\" (click)=\"resetPreferences()\" class=\"restore-btn\">\n Restaurar padr\u00E3o\n </button>\n </div>\n </div>\n </mat-tab>\n @if (configKey) {\n <mat-tab label=\"Atalhos\">\n <div class=\"options\">\n <p class=\"section-help\">Os atalhos s\u00E3o salvos por chave de configura\u00E7\u00E3o e compartilhados entre sess\u00F5es.</p>\n <div class=\"tags-header\">\n <span><strong>Atalhos salvos</strong></span>\n <span class=\"spacer\"></span>\n <button mat-stroked-button color=\"warn\" (click)=\"clearTags()\">Apagar tudo</button>\n </div>\n <div class=\"new-tag\">\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>R\u00F3tulo do novo atalho</mat-label>\n <input matInput [(ngModel)]=\"newTagLabel\" [ngModelOptions]=\"{standalone: true}\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Patch (JSON, opcional)</mat-label>\n <textarea matInput rows=\"3\" [(ngModel)]=\"newTagPatchText\" [ngModelOptions]=\"{standalone: true}\"></textarea>\n </mat-form-field>\n <div>\n <button mat-stroked-button color=\"primary\" (click)=\"addTag()\">Adicionar</button>\n @if (hasCurrentDto()) {\n <button mat-stroked-button (click)=\"addTagFromCurrent()\">Adicionar a partir dos filtros atuais</button>\n }\n </div>\n <div>\n <button mat-stroked-button color=\"primary\" (click)=\"importTags()\">Importar</button>\n <button mat-stroked-button (click)=\"loadTags()\">Recarregar</button>\n <button mat-stroked-button (click)=\"saveTags()\">Salvar</button>\n </div>\n </div>\n </div>\n </mat-tab>\n }\n <mat-tab label=\"JSON\">\n <div class=\"options\">\n <p><strong>Configura\u00E7\u00E3o atual (somente leitura)</strong></p>\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Atual</mat-label>\n <textarea matInput rows=\"8\" [value]=\"effectiveJson\" readonly></textarea>\n </mat-form-field>\n\n <p><strong>Editar JSON</strong></p>\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>JSON</mat-label>\n <textarea matInput rows=\"12\" [(ngModel)]=\"settingsJsonText\" [ngModelOptions]=\"{standalone: true}\"></textarea>\n </mat-form-field>\n <div class=\"actions\">\n <button mat-stroked-button (click)=\"onJsonReload()\">Recarregar do formul\u00E1rio</button>\n <button mat-stroked-button (click)=\"onJsonFormat()\">Formatar</button>\n <button mat-stroked-button color=\"primary\" (click)=\"onJsonApply()\">Carregar JSON no formul\u00E1rio</button>\n </div>\n <p class=\"json-tip\">\n Dica: esta a\u00E7\u00E3o apenas atualiza o editor local. Para persistir, use os bot\u00F5es Aplicar ou\n Salvar & Fechar no rodap\u00E9 do painel.\n </p>\n @if (jsonErrorMsg) {\n <p class=\"json-error\" role=\"status\" aria-live=\"polite\">{{ jsonErrorMsg }}</p>\n }\n </div>\n </mat-tab>\n <mat-tab label=\"Comportamento\">\n <div class=\"options\">\n <mat-checkbox\n formControlName=\"showAddButton\"\n data-testid=\"filter-settings-show-add-button\"\n >\n Exibir bot\u00E3o \"Gerenciar campos\" na barra\n </mat-checkbox>\n <mat-checkbox\n formControlName=\"showFilterSettings\"\n data-testid=\"filter-settings-show-filter-settings\"\n >\n Exibir bot\u00E3o \"Configura\u00E7\u00F5es do filtro\" na barra\n </mat-checkbox>\n <mat-checkbox formControlName=\"allowSaveTags\">Permitir salvar atalhos</mat-checkbox>\n </div>\n </mat-tab>\n <mat-tab label=\"Avan\u00E7ado\">\n <div class=\"options\">\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>Debounce de altera\u00E7\u00F5es (ms)</mat-label>\n <input matInput type=\"number\" formControlName=\"changeDebounceMs\" min=\"100\" max=\"1000\" />\n @if (form.controls.changeDebounceMs.invalid) { <mat-error>Valor deve estar entre 100 e 1000</mat-error> }\n </mat-form-field>\n <div class=\"actions\">\n <button mat-stroked-button color=\"warn\" (click)=\"resetPreferences()\">\n <mat-icon [praxisIcon]=\"'restart_alt'\"></mat-icon>\n Redefinir prefer\u00EAncias do filtro\n </button>\n <button mat-stroked-button color=\"accent\" (click)=\"clearPersistedFilters()\">\n <mat-icon [praxisIcon]=\"'clear_all'\"></mat-icon>\n Limpar filtros salvos\n </button>\n <button mat-stroked-button (click)=\"forceSchemaReload()\">\n <mat-icon [praxisIcon]=\"'refresh'\"></mat-icon>\n For\u00E7ar recarregamento do schema\n </button>\n </div>\n <mat-form-field appearance=\"outline\" class=\"full-width\">\n <mat-label>N\u00EDvel de log</mat-label>\n <mat-select formControlName=\"logLevel\">\n <mat-option value=\"none\">Nenhum</mat-option>\n <mat-option value=\"error\">Erro</mat-option>\n <mat-option value=\"warn\">Aviso</mat-option>\n <mat-option value=\"info\">Info</mat-option>\n <mat-option value=\"debug\">Debug</mat-option>\n </mat-select>\n <button\n praxisIconButton=\"help_outline\"\n size=\"dense\"\n matSuffix\n type=\"button\"\n class=\"praxis-help-icon-button\"\n matTooltip=\"Persistido no contrato para integra\u00E7\u00E3o host. Sem efeito direto no runtime atual do filtro.\"\n aria-label=\"Persistido no contrato para integra\u00E7\u00E3o host. Sem efeito direto no runtime atual do filtro.\"\n ></button>\n </mat-form-field>\n <mat-checkbox formControlName=\"enablePerformanceMetrics\">Habilitar m\u00E9tricas de performance</mat-checkbox>\n <p class=\"field-hint\">Prefer\u00EAncia persistida para integra\u00E7\u00E3o host. Sem efeito direto no runtime atual do filtro.</p>\n </div>\n </mat-tab>\n <mat-tab [label]=\"presentationTabLabel\">\n <praxis-filter-presentation-editor [value]=\"form.controls.presentation.value\" (valueChange)=\"updatePresentation($event)\" />\n </mat-tab>\n </mat-tab-group>\n</form>\n", styles: [":host{display:block;min-width:0}.full-width{width:100%}.always-visible-tab-flow{display:flex;flex-direction:column;gap:4px;padding-top:12px}.always-visible-context{position:sticky;top:0;z-index:var(--praxis-layer-sticky-local, 100);display:flex;flex-direction:column;gap:6px;padding:8px;border-radius:10px;border:1px solid var(--md-sys-color-outline-variant);background:color-mix(in srgb,var(--md-sys-color-surface-container-high) 88%,transparent);-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.always-visible-summary{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;padding:8px 10px;border:1px solid var(--md-sys-color-outline-variant);border-radius:10px;background:var(--md-sys-color-surface-container-low)}.always-visible-summary-item{display:flex;align-items:baseline;gap:6px;min-width:0}.summary-value{font-size:1rem;font-weight:700;color:var(--md-sys-color-primary)}.summary-label{font-size:.8rem;color:var(--md-sys-color-on-surface-variant);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.always-visible-quick-nav{display:flex;flex-wrap:wrap;gap:6px}.always-visible-quick-nav button{min-height:30px;font-size:.76rem;padding-inline:10px;transition:border-color .18s ease,background-color .18s ease,color .18s ease}.always-visible-quick-nav button.quick-nav-active{border-color:color-mix(in srgb,var(--md-sys-color-primary) 50%,var(--md-sys-color-outline-variant));background:color-mix(in srgb,var(--md-sys-color-primary-container) 34%,var(--md-sys-color-surface-container-low));color:var(--md-sys-color-primary)}.always-visible-section-anchor{scroll-margin-top:140px;opacity:0;transform:translateY(8px);animation:always-visible-section-enter .22s cubic-bezier(.2,0,0,1) forwards;animation-delay:var(--section-enter-delay, 0ms)}.always-visible-section-anchor.section-active,.always-visible-section-anchor.section-active .options-advanced-json{box-shadow:0 0 0 1px color-mix(in srgb,var(--md-sys-color-primary) 40%,transparent)}.always-visible-section-anchor:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}@keyframes always-visible-section-enter{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.options{display:flex;flex-direction:column;gap:.75rem;padding:12px;width:100%;max-width:100%;min-width:0;box-sizing:border-box;border:1px solid var(--md-sys-color-outline-variant);border-radius:12px;background:var(--md-sys-color-surface-container)}.options .options{padding:0;border:0;border-radius:0;background:transparent}.options-compact-flow{gap:8px;padding-top:10px;padding-bottom:10px}.section-divider{margin:2px;border-top-style:dashed;border-top-color:var(--md-sys-color-outline-variant);opacity:.28}.options-advanced-json{display:block;padding:0;overflow:hidden}:host ::ng-deep .options-advanced-json .mat-expansion-panel-header{padding:0 12px;min-height:52px}:host ::ng-deep .options-advanced-json .mat-expansion-panel-body{padding:0 12px 12px}.options-header{display:flex;flex-direction:column;gap:6px}.section-title{margin:0;font-size:.95rem;font-weight:600;color:var(--md-sys-color-on-surface)}.section-help{margin:0;font-size:.85rem;color:var(--md-sys-color-on-surface);opacity:.84}.field-hint{margin:-4px 0 0;font-size:.8rem;color:var(--md-sys-color-on-surface);opacity:.78}.praxis-help-icon-button{vertical-align:middle}.order-layout-toolbar{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px}.order-viewport-toggle-group{max-width:100%}.order-viewport-caption{display:inline-flex;align-items:center;min-height:28px;padding:0 10px;border-radius:999px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-lowest);font-size:.75rem;font-weight:600;color:var(--md-sys-color-on-surface-variant)}.order-layout-viewport{width:100%;display:flex;justify-content:center;padding:10px;border-radius:14px;border:1px solid var(--md-sys-color-outline-variant);background:color-mix(in srgb,var(--md-sys-color-surface-container-high) 74%,transparent)}.order-layout-frame{width:100%;transition:max-width .18s ease}.order-layout-frame[data-viewport=tablet] .order-lane-canvas{gap:7px}.order-layout-frame[data-viewport=mobile] .order-lane-canvas{gap:6px;padding:7px}.order-layout-frame[data-viewport=mobile] .order-pill{width:100%;max-width:100%;border-radius:12px}.order-layout-composer{display:flex;flex-direction:column;gap:9px}.order-lane{display:flex;flex-direction:column;gap:6px}.order-lane-header{display:flex;align-items:flex-start;justify-content:space-between;gap:10px}.order-lane-title-block{display:flex;flex-direction:column;gap:2px;min-width:0}.order-lane-title{margin:0;font-size:.85rem;font-weight:600;color:var(--md-sys-color-on-surface)}.order-lane-help{margin:0;font-size:.76rem;color:var(--md-sys-color-on-surface-variant)}.order-lane-count{display:inline-flex;align-items:center;justify-content:center;min-width:24px;height:24px;border-radius:999px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-low);color:var(--md-sys-color-on-surface-variant);font-size:.76rem;font-weight:600;padding:0 8px}.order-lane-canvas{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-height:56px;padding:8px;border:1px dashed var(--md-sys-color-outline-variant);border-radius:12px;background:var(--md-sys-color-surface-container-low)}.order-lane-canvas.cdk-drop-list-dragging{border-color:color-mix(in srgb,var(--md-sys-color-primary) 45%,var(--md-sys-color-outline-variant))}.order-lane[data-lane=actions] .order-lane-canvas{background:color-mix(in srgb,var(--md-sys-color-secondary-container) 28%,var(--md-sys-color-surface-container-low))}.order-lane[data-lane=inline] .order-lane-canvas{background:color-mix(in srgb,var(--md-sys-color-primary-container) 22%,var(--md-sys-color-surface-container-low))}.order-lane[data-lane=grid] .order-lane-canvas{background:color-mix(in srgb,var(--md-sys-color-tertiary-container) 18%,var(--md-sys-color-surface-container-low))}.order-lane-canvas.is-empty{justify-content:flex-start}.order-pill{display:inline-flex;align-items:center;gap:8px;max-width:min(100%,520px);min-height:38px;border:1px solid var(--md-sys-color-outline-variant);border-radius:999px;background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface);padding:4px 8px;box-sizing:border-box;cursor:grab;font:inherit;text-align:left}.order-pill:hover{border-color:var(--md-sys-color-outline)}.order-pill:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.order-pill mat-icon{opacity:.75}.order-pill-label{min-width:0;max-width:190px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.86rem;font-weight:500}.order-pill-badges{display:inline-flex;flex-wrap:wrap;align-items:center;gap:4px}.order-pill-chip{display:inline-flex;align-items:center;border-radius:999px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-lowest);color:var(--md-sys-color-on-surface-variant);font-size:.7rem;font-weight:600;padding:1px 7px;white-space:nowrap}.order-pill-chip-width{font-weight:500;opacity:.86}.order-pill-chip-override{border-color:color-mix(in srgb,var(--md-sys-color-primary) 42%,var(--md-sys-color-outline-variant));color:var(--md-sys-color-primary)}.order-lane-empty{margin:0;font-size:.78rem;color:var(--md-sys-color-on-surface-variant)}.order-pill.cdk-drag-preview{box-shadow:var(--md-sys-elevation-level2)}.order-pill.cdk-drag-placeholder{opacity:.45}.layout-preview-section{display:flex;flex-direction:column;gap:10px;padding:12px;border:1px solid var(--md-sys-color-outline-variant);border-radius:10px;background:var(--md-sys-color-surface-container-low);min-width:0}.layout-preview-title{margin:0;font-size:.95rem;font-weight:600;color:var(--md-sys-color-on-surface)}.layout-preview-cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px;min-width:0}.layout-preview-card{display:flex;flex-direction:column;gap:8px;padding:10px;border:1px solid var(--md-sys-color-outline-variant);border-radius:8px;background:var(--md-sys-color-surface-container);min-width:0}.layout-preview-header{display:flex;flex-direction:column;gap:2px}.layout-preview-header strong{font-size:.85rem;color:var(--md-sys-color-on-surface)}.layout-preview-header span{font-size:.78rem;color:var(--md-sys-color-on-surface-variant)}.layout-mini-grid{display:grid;gap:6px;min-width:0}.layout-mini-grid span{display:block;min-width:0;padding:6px 8px;border-radius:6px;background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container);font-size:.75rem;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tags-header{display:flex;align-items:center;gap:8px}.spacer{flex:1 1 auto}.new-tag{display:flex;flex-direction:column;gap:8px}.preset-row{display:flex;flex-wrap:wrap;gap:8px}.option-with-preview{display:flex;align-items:center;gap:8px}.preview-card{width:20px;height:20px;background:var(--md-sys-color-surface-container-high);border:1px solid var(--md-sys-color-outline-variant);border-radius:4px}.preview-frosted{width:20px;height:20px;background:var(--md-sys-color-surface-container);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border:1px solid var(--md-sys-color-outline-variant);border-radius:4px}.toggle-preview-section{margin-bottom:1.5rem;padding:1rem;background:var(--md-sys-color-surface-container-low);border-radius:8px;border:1px solid var(--md-sys-color-outline-variant)}.toggle-preview-section h4{margin:0 0 1rem;font-size:1.1rem;color:var(--md-sys-color-on-surface)}.preview-container{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:1rem}.preview-item{text-align:center}.preview-item strong{display:block;margin-bottom:.5rem;font-weight:500}.mock-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));align-items:center;gap:8px;padding:8px;background:var(--md-sys-color-surface);border:1px solid var(--md-sys-color-outline);border-radius:8px;min-height:72px}.mock-grid.with-booleans{grid-template-columns:repeat(3,minmax(0,1fr))}.mock-field{padding:4px 8px;background:var(--md-sys-color-primary-container);border-radius:6px;font-size:.8rem;text-align:center}.mock-actions{display:flex;align-items:center;gap:8px;padding:8px;background:var(--md-sys-color-surface);border:1px solid var(--md-sys-color-outline);border-radius:8px;min-height:48px}.mock-pill{display:flex;align-items:center;gap:8px;min-height:34px;padding:0 10px;border-radius:999px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface-variant);font-size:.82rem;justify-self:start}.mock-pill.compact{padding-inline:8px;gap:0}.mock-pill mat-slide-toggle{--mdc-switch-selected-track-color: var(--md-sys-color-primary);--mdc-switch-selected-handle-color: var(--md-sys-color-on-primary);transform:scale(.8);pointer-events:none}.mock-pill span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:500}.preview-section{display:flex;flex-direction:column;gap:16px;padding:16px;background:var(--md-sys-color-surface-container-low);border:1px solid var(--md-sys-color-outline-variant);border-radius:8px;margin-bottom:16px;box-shadow:var(--md-sys-elevation-level1);transition:background-color .3s,color .3s}.preview-section.dark-mode{background:var(--md-sys-color-surface-container-low);color:var(--md-sys-color-on-surface);border-color:var(--md-sys-color-outline-variant)}.preview-section.dark-mode .preview-label{color:var(--md-sys-color-on-surface-variant)}.preview-controls{display:flex;justify-content:space-between;align-items:center}.preview-label{font-weight:500;font-size:.9rem;color:var(--md-sys-color-on-surface-variant)}.preview-chips{display:flex;justify-content:center;padding:16px 0}.preview-chip-action--primary{color:var(--md-sys-color-primary)}.preview-chip-action--accent{color:var(--md-sys-color-tertiary)}.preview-chip-action--warn{color:var(--md-sys-color-error)}.praxis-filter-tags.outlined .mat-mdc-chip{background:transparent!important;--mat-chip-outline-width: 1px;--mat-chip-outline-color: var(--md-sys-color-outline-variant);box-shadow:inset 0 0 0 var(--mat-chip-outline-width) var(--mat-chip-outline-color)}.praxis-filter-tags.outlined.tag-color-primary .mat-mdc-chip{--mat-chip-outline-color: var(--md-sys-color-primary)}.praxis-filter-tags.outlined.tag-color-accent .mat-mdc-chip{--mat-chip-outline-color: var(--md-sys-color-tertiary)}.praxis-filter-tags.outlined.tag-color-warn .mat-mdc-chip{--mat-chip-outline-color: var(--md-sys-color-error)}.praxis-filter-tags.outlined mat-chip.active .mat-mdc-chip,.praxis-filter-tags.outlined .mat-mdc-chip.mat-mdc-chip-highlighted{--mat-chip-outline-width: 2px;box-shadow:inset 0 0 0 var(--mat-chip-outline-width) var(--mat-chip-outline-color)!important}.praxis-filter-tags.outlined.tag-color-basic mat-chip.active .mat-mdc-chip{--mat-chip-outline-color: var(--md-sys-color-primary)}.praxis-filter-tags .mat-mdc-standard-chip .mdc-evolution-chip__text-label{display:flex;align-items:center;gap:4px}.contrast-warning{display:flex;align-items:center;gap:8px;padding:8px 12px;background:var(--md-sys-color-error-container);color:var(--md-sys-color-on-error-container);border-radius:4px;font-size:.85rem}.contrast-warning mat-icon{font-size:20px;width:20px;height:20px}.preset-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:12px;margin-bottom:12px}.preset-card{display:flex;width:100%;flex-direction:column;gap:8px;padding:12px;text-align:left;font:inherit;color:inherit;background:transparent;border:1px solid var(--md-sys-color-outline-variant);border-radius:8px;cursor:pointer;transition:all .2s}.preset-card:hover{background:var(--md-sys-color-surface-container-high);border-color:var(--md-sys-color-outline)}.preset-card:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.preset-card.preset-active{border-color:var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary-container) 34%,transparent);box-shadow:inset 0 0 0 1px var(--md-sys-color-primary)}.preset-preview{height:24px;border-radius:4px;background:var(--md-sys-color-surface-variant)}.preset-preview.preview-minimal{background:transparent;border:1px solid var(--md-sys-color-outline)}.preset-preview.preview-primary{background:var(--md-sys-color-primary-container)}.preset-preview.preview-accent{background:var(--md-sys-color-tertiary-container)}.preset-preview.preview-warn{background:var(--md-sys-color-error-container)}.preset-info{display:flex;flex-direction:column}.preset-name{font-weight:500;font-size:.9rem}.preset-desc{font-size:.75rem;opacity:.7}.restore-btn{align-self:flex-start}.metadata-card-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:10px}.metadata-card{display:flex;flex-direction:column;gap:10px;padding:12px;border-radius:10px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-low)}.metadata-card-header{display:flex;justify-content:space-between;align-items:flex-start;gap:10px}.metadata-card-title{display:flex;flex-direction:column;min-width:0}.metadata-card-title strong{font-size:.92rem;color:var(--md-sys-color-on-surface)}.metadata-card-title span{font-size:.78rem;color:var(--md-sys-color-on-surface-variant);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.metadata-card-badges{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:6px}.metadata-chip{display:inline-flex;align-items:center;border-radius:999px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container);color:var(--md-sys-color-on-surface-variant);font-size:.78rem;font-weight:500;padding:2px 8px;white-space:nowrap}.metadata-chip-override{border-color:color-mix(in srgb,var(--md-sys-color-primary) 40%,var(--md-sys-color-outline-variant));color:var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary-container) 38%,var(--md-sys-color-surface-container))}.metadata-card-summary{margin:0;font-size:.8rem;color:var(--md-sys-color-on-surface-variant)}.metadata-card-actions{display:flex;flex-wrap:wrap;gap:6px}.metadata-card-actions button{display:inline-flex;align-items:center;gap:4px}.metadata-card-actions button:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:1px}:host ::ng-deep .metadata-card-actions button[disabled]{opacity:.62}.json-tip{margin:6px 0 0;font-size:.85rem;color:var(--md-sys-color-on-surface-variant)}.json-error{margin-top:6px;font-size:.85rem;color:var(--md-sys-color-error)}@media(prefers-reduced-motion:reduce){.always-visible-section-anchor{animation:none;opacity:1;transform:none}.always-visible-quick-nav button{transition:none}}@media(max-width:680px){.order-layout-toolbar{flex-direction:column;align-items:stretch}.order-viewport-caption{align-self:flex-start}.order-layout-viewport{padding:8px}.always-visible-context{position:static;-webkit-backdrop-filter:none;backdrop-filter:none}.always-visible-summary{grid-template-columns:1fr}.always-visible-quick-nav{display:grid;grid-template-columns:repeat(3,minmax(0,1fr))}.order-pill{width:100%;max-width:100%}.order-pill-label{max-width:none}.order-pill-badges{display:none}}.filter-rich-field-option{display:flex;flex-direction:column;gap:2px;padding:2px 0;line-height:1.25}.filter-rich-field-option__top{display:flex;align-items:center;gap:6px;flex-wrap:wrap}.filter-rich-field-option__label{font-weight:500;font-size:.82rem;color:var(--md-sys-color-on-surface)}.filter-rich-field-option__code{font-family:var(--praxis-font-mono, monospace);font-size:.7rem;padding:1px 5px;border-radius:4px;background:var(--md-sys-color-surface-container-highest);color:var(--md-sys-color-on-surface-variant)}.filter-rich-field-option__badge{margin-left:auto;font-size:.65rem;font-weight:600;text-transform:uppercase;padding:1px 6px;border-radius:999px;background:var(--md-sys-color-secondary-container);color:var(--md-sys-color-on-secondary-container)}.filter-rich-field-option__desc{font-size:.72rem;color:var(--md-sys-color-on-surface-variant);white-space:normal;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}\n"] }]
|
|
13084
13105
|
}], ctorParameters: () => [{ type: i1$1.FormBuilder }], propDecorators: { metadata: [{
|
|
13085
13106
|
type: Input
|
|
13086
13107
|
}], metadataSource: [{
|
|
@@ -18733,7 +18754,7 @@ class PraxisFilterWidgetConfigEditor {
|
|
|
18733
18754
|
[configKey]="configKey"
|
|
18734
18755
|
/>
|
|
18735
18756
|
</section>
|
|
18736
|
-
`, isInline: true, dependencies: [{ kind: "component", type: FilterSettingsComponent, selector: "filter-settings", inputs: ["metadata", "metadataSource", "metadataLoading", "metadataErrorMsg", "settings", "configKey"], outputs: ["settingsChange"] }, { kind: "component", type: ResourceQuickConnectComponent, selector: "praxis-resource-quick-connect", inputs: ["resourcePath", "endpointKey", "apiUrlEntry", "intent", "resources", "label", "placeholder", "hint", "disabled"], outputs: ["resourcePathChange"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
18757
|
+
`, isInline: true, dependencies: [{ kind: "component", type: FilterSettingsComponent, selector: "filter-settings", inputs: ["metadata", "metadataSource", "metadataLoading", "metadataErrorMsg", "settings", "configKey"], outputs: ["settingsChange"] }, { kind: "component", type: ResourceQuickConnectComponent, selector: "praxis-resource-quick-connect", inputs: ["resourcePath", "endpointKey", "apiUrlEntry", "intent", "resources", "label", "placeholder", "hint", "disabled"], outputs: ["resourcePathChange", "resourceSelected"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
18737
18758
|
}
|
|
18738
18759
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisFilterWidgetConfigEditor, decorators: [{
|
|
18739
18760
|
type: Component,
|
|
@@ -24310,7 +24331,7 @@ class PraxisTable {
|
|
|
24310
24331
|
if (this.aiAdapter || this.aiAdapterLoadStarted)
|
|
24311
24332
|
return;
|
|
24312
24333
|
this.aiAdapterLoadStarted = true;
|
|
24313
|
-
import('./praxisui-table-table-ai.adapter-
|
|
24334
|
+
import('./praxisui-table-table-ai.adapter-B4AP612N.mjs')
|
|
24314
24335
|
.then(({ TableAiAdapter }) => {
|
|
24315
24336
|
if (!this.isAiAssistantEnabled()) {
|
|
24316
24337
|
this.aiAssistantOpenAfterAdapterLoad = false;
|
|
@@ -25082,7 +25103,7 @@ class PraxisTable {
|
|
|
25082
25103
|
initializeAiAssistantController() {
|
|
25083
25104
|
if (!this.aiAdapter || this.aiAssistantController)
|
|
25084
25105
|
return;
|
|
25085
|
-
import('./praxisui-table-table-agentic-authoring-turn-flow-
|
|
25106
|
+
import('./praxisui-table-table-agentic-authoring-turn-flow-2KiSlvRK.mjs')
|
|
25086
25107
|
.then(({ TableAgenticAuthoringTurnFlow }) => {
|
|
25087
25108
|
if (this.aiAssistantController || !this.aiAdapter)
|
|
25088
25109
|
return;
|
|
@@ -26276,6 +26297,10 @@ class PraxisTable {
|
|
|
26276
26297
|
resetPageSizeOverride: true,
|
|
26277
26298
|
});
|
|
26278
26299
|
this.applyDataSourceSettings();
|
|
26300
|
+
if (Array.isArray(this.dataSource?.data) && this.dataSource.data.length > 0) {
|
|
26301
|
+
this.resetRowContextRuntimeState();
|
|
26302
|
+
this.prefetchRowDiscovery(this.dataSource.data);
|
|
26303
|
+
}
|
|
26279
26304
|
this.applyAppearanceVariables();
|
|
26280
26305
|
if (this.isAiAssistantEnabled()) {
|
|
26281
26306
|
this.ensureAiAdapterLoaded();
|
|
@@ -26368,6 +26393,12 @@ class PraxisTable {
|
|
|
26368
26393
|
const prevConfig = changes['config'].previousValue;
|
|
26369
26394
|
const currConfig = changes['config'].currentValue;
|
|
26370
26395
|
const shouldRefetch = this.shouldRefetchOnConfigChange(prevConfig, currConfig);
|
|
26396
|
+
this.resetRowContextRuntimeState();
|
|
26397
|
+
if (!shouldRefetch &&
|
|
26398
|
+
Array.isArray(this.dataSource?.data) &&
|
|
26399
|
+
this.dataSource.data.length > 0) {
|
|
26400
|
+
this.prefetchRowDiscovery(this.dataSource.data);
|
|
26401
|
+
}
|
|
26371
26402
|
// Avoid fetching twice in same cycle when resourcePath also changed or when config update is non-query-affecting
|
|
26372
26403
|
if (this.isRemoteMode() && !configuredThisChange) {
|
|
26373
26404
|
if (shouldRefetch) {
|
|
@@ -73162,6 +73193,7 @@ class PraxisTableConfigEditor {
|
|
|
73162
73193
|
isValid$ = new BehaviorSubject(true);
|
|
73163
73194
|
isBusy$ = new BehaviorSubject(false);
|
|
73164
73195
|
columnMetas = [];
|
|
73196
|
+
filterSchemaFields = [];
|
|
73165
73197
|
filterDtoMetas = [];
|
|
73166
73198
|
filterDtoMetasLoading = false;
|
|
73167
73199
|
filterDtoMetasErrorMsg = '';
|
|
@@ -73474,63 +73506,146 @@ class PraxisTableConfigEditor {
|
|
|
73474
73506
|
return false;
|
|
73475
73507
|
});
|
|
73476
73508
|
}
|
|
73509
|
+
enrichFieldMetadataList(metas, preferFilterSchema = false) {
|
|
73510
|
+
const rawDefsByName = new Map();
|
|
73511
|
+
const primaryDefs = preferFilterSchema
|
|
73512
|
+
? this.filterSchemaFields
|
|
73513
|
+
: this.resourceSchemaFields;
|
|
73514
|
+
const secondaryDefs = preferFilterSchema
|
|
73515
|
+
? this.resourceSchemaFields
|
|
73516
|
+
: this.filterSchemaFields;
|
|
73517
|
+
for (const def of secondaryDefs || []) {
|
|
73518
|
+
if (def?.name)
|
|
73519
|
+
rawDefsByName.set(def.name, def);
|
|
73520
|
+
}
|
|
73521
|
+
for (const def of primaryDefs || []) {
|
|
73522
|
+
if (def?.name)
|
|
73523
|
+
rawDefsByName.set(def.name, def);
|
|
73524
|
+
}
|
|
73525
|
+
return (metas || []).map((meta) => {
|
|
73526
|
+
const def = rawDefsByName.get(meta.name);
|
|
73527
|
+
if (!def)
|
|
73528
|
+
return meta;
|
|
73529
|
+
const rawType = String(def.type || meta.type || meta.dataType || '').trim();
|
|
73530
|
+
const description = String(meta.description || def.description || def.helpText || def.hint || '').trim();
|
|
73531
|
+
return {
|
|
73532
|
+
...meta,
|
|
73533
|
+
...(rawType ? { type: rawType } : {}),
|
|
73534
|
+
...(description ? { description } : {}),
|
|
73535
|
+
};
|
|
73536
|
+
});
|
|
73537
|
+
}
|
|
73477
73538
|
getFilterSettingsMetas() {
|
|
73478
|
-
if (!this.resourcePath?.trim())
|
|
73479
|
-
return this.columnMetas;
|
|
73480
|
-
|
|
73539
|
+
if (!this.resourcePath?.trim()) {
|
|
73540
|
+
return this.enrichFieldMetadataList(this.columnMetas, true);
|
|
73541
|
+
}
|
|
73542
|
+
return this.enrichFieldMetadataList(this.filterDtoMetas, true);
|
|
73481
73543
|
}
|
|
73482
73544
|
getColumnAuthoringMetas() {
|
|
73483
|
-
if (!this.resourcePath?.trim()
|
|
73545
|
+
if (!this.resourcePath?.trim() &&
|
|
73546
|
+
!this.resourceSchemaFields?.length &&
|
|
73547
|
+
!this.resourceSchemaMetas?.length &&
|
|
73548
|
+
!this.filterDtoMetas?.length) {
|
|
73484
73549
|
return this.columnMetas;
|
|
73485
|
-
const merged = new Map();
|
|
73486
|
-
for (const meta of this.resourceSchemaMetas || []) {
|
|
73487
|
-
if (meta?.name)
|
|
73488
|
-
merged.set(meta.name, meta);
|
|
73489
73550
|
}
|
|
73490
|
-
|
|
73491
|
-
|
|
73492
|
-
|
|
73551
|
+
const hasResponseSchema = (this.resourceSchemaMetas?.length ?? 0) > 0 ||
|
|
73552
|
+
(this.resourceSchemaFields?.length ?? 0) > 0;
|
|
73553
|
+
const rawMetas = [];
|
|
73554
|
+
const seen = new Set();
|
|
73555
|
+
const appendUnique = (items) => {
|
|
73556
|
+
for (const meta of items || []) {
|
|
73557
|
+
if (meta?.name && !seen.has(meta.name)) {
|
|
73558
|
+
seen.add(meta.name);
|
|
73559
|
+
rawMetas.push(meta);
|
|
73560
|
+
}
|
|
73561
|
+
}
|
|
73562
|
+
};
|
|
73563
|
+
appendUnique(this.resourceSchemaMetas);
|
|
73564
|
+
if (!this.resourceSchemaMetas?.length && this.resourceSchemaFields?.length) {
|
|
73565
|
+
appendUnique(mapFieldDefinitionsToMetadata(this.resourceSchemaFields));
|
|
73493
73566
|
}
|
|
73494
|
-
|
|
73495
|
-
|
|
73496
|
-
merged.set(meta.name, meta);
|
|
73567
|
+
if (!hasResponseSchema) {
|
|
73568
|
+
appendUnique(this.filterDtoMetas);
|
|
73497
73569
|
}
|
|
73498
|
-
|
|
73570
|
+
appendUnique(this.columnMetas);
|
|
73571
|
+
return this.enrichFieldMetadataList(rawMetas, false);
|
|
73572
|
+
}
|
|
73573
|
+
getFieldTypeBadge(option) {
|
|
73574
|
+
if (!option)
|
|
73575
|
+
return '';
|
|
73576
|
+
return String(option.type || option.dataType || option.controlType || '').trim();
|
|
73577
|
+
}
|
|
73578
|
+
getFieldDescription(option) {
|
|
73579
|
+
if (!option)
|
|
73580
|
+
return '';
|
|
73581
|
+
return String(option.description || option.helpText || option.hint || '').trim();
|
|
73499
73582
|
}
|
|
73500
73583
|
get filteredIdFieldOptions() {
|
|
73501
73584
|
const query = this.normalizeSearchText(this.idField);
|
|
73585
|
+
const authoringMetas = this.getColumnAuthoringMetas();
|
|
73586
|
+
const authoringByName = new Map(authoringMetas.map((m) => [m.name, m]));
|
|
73502
73587
|
const merged = new Map();
|
|
73503
|
-
const addOption = (
|
|
73504
|
-
const normalizedName = (name || '').trim();
|
|
73588
|
+
const addOption = (meta) => {
|
|
73589
|
+
const normalizedName = (meta?.name || '').trim();
|
|
73505
73590
|
if (!normalizedName || merged.has(normalizedName))
|
|
73506
73591
|
return;
|
|
73592
|
+
const existing = authoringByName.get(normalizedName);
|
|
73507
73593
|
merged.set(normalizedName, {
|
|
73594
|
+
...(existing || {}),
|
|
73595
|
+
...meta,
|
|
73508
73596
|
name: normalizedName,
|
|
73509
|
-
label: (label || normalizedName).trim(),
|
|
73510
|
-
controlType: FieldControlType.INPUT,
|
|
73597
|
+
label: (meta?.label || existing?.label || normalizedName).trim(),
|
|
73598
|
+
controlType: meta?.controlType ?? existing?.controlType ?? FieldControlType.INPUT,
|
|
73511
73599
|
});
|
|
73512
73600
|
};
|
|
73513
|
-
|
|
73514
|
-
|
|
73515
|
-
|
|
73601
|
+
if (this.serverIdField) {
|
|
73602
|
+
addOption({
|
|
73603
|
+
name: this.serverIdField,
|
|
73604
|
+
label: authoringByName.get(this.serverIdField)?.label ||
|
|
73605
|
+
this.tx('connect.primaryKey.serverField', 'Server primary key'),
|
|
73606
|
+
});
|
|
73607
|
+
}
|
|
73608
|
+
for (const meta of authoringMetas) {
|
|
73609
|
+
addOption(meta);
|
|
73516
73610
|
}
|
|
73517
73611
|
const options = Array.from(merged.values());
|
|
73518
73612
|
if (!query)
|
|
73519
73613
|
return options;
|
|
73520
|
-
|
|
73614
|
+
if (options.some((option) => this.normalizeSearchText(option.name) === query)) {
|
|
73615
|
+
return [...options].sort((a, b) => this.normalizeSearchText(a.name) === query
|
|
73616
|
+
? -1
|
|
73617
|
+
: this.normalizeSearchText(b.name) === query
|
|
73618
|
+
? 1
|
|
73619
|
+
: 0);
|
|
73620
|
+
}
|
|
73621
|
+
return options.filter((option) => this.normalizeSearchText(`${option.name} ${option.label || ''} ${this.getFieldTypeBadge(option)} ${this.getFieldDescription(option)}`).includes(query));
|
|
73521
73622
|
}
|
|
73522
73623
|
getFilteredQuickFilterFieldOptions(currentValue) {
|
|
73523
73624
|
const query = this.normalizeSearchText(currentValue);
|
|
73625
|
+
const rawDefsByName = new Map();
|
|
73626
|
+
for (const def of this.filterSchemaFields || []) {
|
|
73627
|
+
if (def?.name)
|
|
73628
|
+
rawDefsByName.set(def.name, def);
|
|
73629
|
+
}
|
|
73630
|
+
for (const def of this.resourceSchemaFields || []) {
|
|
73631
|
+
if (def?.name)
|
|
73632
|
+
rawDefsByName.set(def.name, def);
|
|
73633
|
+
}
|
|
73524
73634
|
const merged = new Map();
|
|
73525
73635
|
const addOption = (meta) => {
|
|
73526
73636
|
const normalizedName = (meta?.name || '').trim();
|
|
73527
73637
|
if (!normalizedName || merged.has(normalizedName))
|
|
73528
73638
|
return;
|
|
73639
|
+
const def = rawDefsByName.get(normalizedName);
|
|
73640
|
+
const rawType = String(def?.type || meta?.type || meta?.dataType || '').trim();
|
|
73641
|
+
const description = String(meta?.description || def?.description || def?.helpText || def?.hint || '').trim();
|
|
73529
73642
|
merged.set(normalizedName, {
|
|
73530
73643
|
...meta,
|
|
73531
73644
|
name: normalizedName,
|
|
73532
|
-
label: (meta?.label || normalizedName).trim(),
|
|
73645
|
+
label: (meta?.label || def?.label || normalizedName).trim(),
|
|
73533
73646
|
controlType: meta?.controlType ?? FieldControlType.INPUT,
|
|
73647
|
+
...(rawType ? { type: rawType } : {}),
|
|
73648
|
+
...(description ? { description } : {}),
|
|
73534
73649
|
});
|
|
73535
73650
|
};
|
|
73536
73651
|
for (const meta of this.filterDtoMetas || []) {
|
|
@@ -73545,7 +73660,14 @@ class PraxisTableConfigEditor {
|
|
|
73545
73660
|
const options = Array.from(merged.values());
|
|
73546
73661
|
if (!query)
|
|
73547
73662
|
return options;
|
|
73548
|
-
|
|
73663
|
+
if (options.some((option) => this.normalizeSearchText(option.name) === query)) {
|
|
73664
|
+
return [...options].sort((a, b) => this.normalizeSearchText(a.name) === query
|
|
73665
|
+
? -1
|
|
73666
|
+
: this.normalizeSearchText(b.name) === query
|
|
73667
|
+
? 1
|
|
73668
|
+
: 0);
|
|
73669
|
+
}
|
|
73670
|
+
return options.filter((option) => this.normalizeSearchText(`${option.name} ${option.label || ''} ${this.getFieldTypeBadge(option)} ${this.getFieldDescription(option)}`).includes(query));
|
|
73549
73671
|
}
|
|
73550
73672
|
isUsingFilterDtoMetas() {
|
|
73551
73673
|
return !!this.resourcePath?.trim();
|
|
@@ -74166,6 +74288,7 @@ class PraxisTableConfigEditor {
|
|
|
74166
74288
|
onResourcePathChange(val) {
|
|
74167
74289
|
this.resourcePath = val ?? '';
|
|
74168
74290
|
this.enforceEffectiveModeStrategyConstraints();
|
|
74291
|
+
this.filterSchemaFields = [];
|
|
74169
74292
|
this.filterDtoMetas = [];
|
|
74170
74293
|
this.filterDtoMetasErrorMsg = '';
|
|
74171
74294
|
this.resourceSchemaFields = [];
|
|
@@ -74225,6 +74348,7 @@ class PraxisTableConfigEditor {
|
|
|
74225
74348
|
const resource = (this.resourcePath || '').trim();
|
|
74226
74349
|
const requestSeq = (this.filterDtoLoadSeq = (this.filterDtoLoadSeq || 0) + 1);
|
|
74227
74350
|
if (!resource) {
|
|
74351
|
+
this.filterSchemaFields = [];
|
|
74228
74352
|
this.filterDtoMetas = [];
|
|
74229
74353
|
this.filterDtoMetasLoading = false;
|
|
74230
74354
|
this.filterDtoMetasErrorMsg = '';
|
|
@@ -74233,6 +74357,7 @@ class PraxisTableConfigEditor {
|
|
|
74233
74357
|
}
|
|
74234
74358
|
const canDiscoverFilter = this.canUseResourceDiscovery('filter');
|
|
74235
74359
|
if (!this.crudService && !canDiscoverFilter) {
|
|
74360
|
+
this.filterSchemaFields = [];
|
|
74236
74361
|
this.filterDtoMetas = [];
|
|
74237
74362
|
this.filterDtoMetasLoading = false;
|
|
74238
74363
|
this.filterDtoMetasErrorMsg = 'Serviço de schema indisponível para carregar o DTO de filtro.';
|
|
@@ -74304,6 +74429,7 @@ class PraxisTableConfigEditor {
|
|
|
74304
74429
|
if (requestSeq !== this.filterDtoLoadSeq)
|
|
74305
74430
|
return;
|
|
74306
74431
|
if (hadError && (!defs || defs.length === 0)) {
|
|
74432
|
+
this.filterSchemaFields = [];
|
|
74307
74433
|
this.filterDtoMetas = [];
|
|
74308
74434
|
this.filterDtoMetasErrorMsg = 'Não foi possível carregar os campos do DTO de filtro para este recurso.';
|
|
74309
74435
|
console.warn('[PraxisTableConfigEditor] loadFilterDtoMetas failed', {
|
|
@@ -74312,6 +74438,7 @@ class PraxisTableConfigEditor {
|
|
|
74312
74438
|
});
|
|
74313
74439
|
}
|
|
74314
74440
|
else {
|
|
74441
|
+
this.filterSchemaFields = (defs || []).filter((def) => !!def?.name);
|
|
74315
74442
|
this.filterDtoMetas = mapFieldDefinitionsToMetadata(defs || []).filter((meta) => !!meta?.name);
|
|
74316
74443
|
}
|
|
74317
74444
|
this.filterDtoMetasLoading = false;
|
|
@@ -74812,7 +74939,7 @@ class PraxisTableConfigEditor {
|
|
|
74812
74939
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: PraxisTableConfigEditor, isStandalone: true, selector: "praxis-table-config-editor", providers: [
|
|
74813
74940
|
TableConfigService,
|
|
74814
74941
|
providePraxisI18nConfig(PRAXIS_TABLE_EDITOR_I18N_CONFIG),
|
|
74815
|
-
], viewQueries: [{ propertyName: "filterSettingsEditor", first: true, predicate: ["tableFilterSettingsRef"], descendants: true }, { propertyName: "behaviorEditor", first: true, predicate: BehaviorConfigEditorComponent, descendants: true }, { propertyName: "detailEditor", first: true, predicate: TableDetailEditorComponent, descendants: true }, { propertyName: "rulesEditor", first: true, predicate: TableRulesEditorComponent, descendants: true }, { propertyName: "crudEditorSetter", first: true, predicate: ["crudEditorRef"], descendants: true }], ngImport: i0, template: "<div class=\"editor-section-selector\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('sections.selector', 'Editor section') }}</mat-label>\n <mat-select [(ngModel)]=\"activeSectionIndex\" data-testid=\"table-config-editor-sections\">\n @for (section of sections; track section.id; let index = $index) {\n <mat-option [value]=\"index\">{{ section.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n</div>\n<mat-tab-group [preserveContent]=\"true\" class=\"config-tabs\" [(selectedIndex)]=\"activeSectionIndex\">\n @for (section of sections; track section.id) {\n <mat-tab>\n <ng-template mat-tab-label>\n @if (section.icon) { <mat-icon [praxisIcon]=\"section.icon\"></mat-icon> }\n <span [attr.data-testid]=\"'config-tab-' + section.id\">{{ section.label }}</span>\n </ng-template>\n <div class=\"tab-content\">\n @switch (section.id) {\n @case ('connect') {\n <div class=\"connect-grid\">\n <praxis-resource-quick-connect\n [resourcePath]=\"resourcePath\"\n [endpointKey]=\"resourceEndpointKey\"\n [apiUrlEntry]=\"resourceApiUrlEntry\"\n (resourcePathChange)=\"onResourcePathChange($event)\"\n intent=\"collection\"\n [label]=\"tx('connect.resource', 'Resource')\"\n [placeholder]=\"tx('connect.resource.placeholder', 'e.g. employees')\"\n />\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('connect.primaryKey', 'Primary key') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"idField\"\n (ngModelChange)=\"onIdFieldChange($event)\"\n [matAutocomplete]=\"idFieldPicker\"\n [placeholder]=\"tx('connect.primaryKey.placeholder', 'e.g. id, uuid, code')\"\n />\n <mat-icon matSuffix>fingerprint</mat-icon>\n <mat-autocomplete\n #idFieldPicker=\"matAutocomplete\"\n (optionSelected)=\"onIdFieldOptionSelected($event.option.value)\"\n >\n @for (option of filteredIdFieldOptions; track option.name) {\n <mat-option [value]=\"option.name\">\n <span>{{ option.name }}</span>\n @if (option.label && option.label !== option.name) {\n <small> - {{ option.label }}</small>\n }\n </mat-option>\n }\n @if (filteredIdFieldOptions.length === 0) {\n <mat-option disabled>\n {{ tx('connect.primaryKey.empty', 'No matching field found.') }}\n </mat-option>\n }\n </mat-autocomplete>\n </mat-form-field>\n <p class=\"connect-note\">{{ tx('connect.note', 'Define the API resource and, when needed, the primary key.') }}</p>\n <!-- Diverg\u00EAncia: idField -->\n @if (idFieldDiverges) { <div class=\"alert alert--warning connect-alert\">\n <mat-icon class=\"alert__icon\">warning</mat-icon>\n <div class=\"alert__text connect-alert__text\">\n {{ tx('connect.serverPrimaryKeyDiverges', 'The primary key on the server differs from the current configuration.') }}\n <span class=\"connect-alert__meta\">(config: {{ idField || 'id' }} | servidor: {{ serverIdField || 'id' }})</span>\n </div>\n <button mat-stroked-button color=\"primary\" type=\"button\" (click)=\"onReconcileIdField()\">{{ tx('connect.reconcile', 'Reconcile') }}</button>\n </div> }\n <!-- Diverg\u00EAncia: serverHash -->\n @if (schemaHashDiverges) { <div class=\"alert alert--info connect-alert\">\n <mat-icon class=\"alert__icon\">info</mat-icon>\n <div class=\"alert__text connect-alert__text\">\n {{ tx('connect.schemaSnapshotDiverges', 'The saved schema snapshot differs from the current server schema.') }}\n </div>\n <button mat-stroked-button color=\"primary\" type=\"button\" (click)=\"onAcceptServerHash()\">{{ tx('connect.updateMetadata', 'Update metadata') }}</button>\n </div> }\n @if (!schemaHashDiverges && schemaSnapshotMissing) { <div class=\"alert alert--info connect-alert\">\n <mat-icon class=\"alert__icon\">info</mat-icon>\n <div class=\"alert__text connect-alert__text\">\n {{ tx('connect.schemaSnapshotMissing', 'A current schema exists on the server, but there is still no persisted snapshot for reconciliation.') }}\n </div>\n <button mat-stroked-button color=\"primary\" type=\"button\" (click)=\"onAcceptServerHash()\">{{ tx('connect.saveSnapshot', 'Save snapshot') }}</button>\n </div> }\n </div>\n }\n @case ('overview') {\n <div class=\"overview-grid\">\n <section class=\"responsive-settings\" aria-labelledby=\"table-responsive-title\">\n <div class=\"responsive-settings__heading\">\n <mat-icon aria-hidden=\"true\">devices</mat-icon>\n <h3 id=\"table-responsive-title\">{{ tx('overview.responsive.title', 'Narrow screens') }}</h3>\n </div>\n <p>{{ tx('overview.responsive.description', 'Choose how records appear when the table has less space.') }}</p>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('overview.responsive.mode', 'Record layout') }}</mat-label>\n <mat-select [ngModel]=\"mobileCardMode\" (ngModelChange)=\"onMobileCardModeChange($event)\">\n <mat-option [value]=\"false\">{{ tx('overview.responsive.table', 'Table') }}</mat-option>\n <mat-option [value]=\"true\">{{ tx('overview.responsive.cards', 'Cards') }}</mat-option>\n </mat-select>\n </mat-form-field>\n @if (mobileCardMode) {\n <p class=\"responsive-settings__hint\">{{ tx('overview.responsive.cardsHint', 'Each record becomes a card with its visible columns. Selection and row actions remain available. Wider areas keep the table layout.') }}</p>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('overview.responsive.breakpoint', 'Show cards at widths up to') }}</mat-label>\n <input matInput type=\"number\" min=\"1\" step=\"1\" required\n [ngModel]=\"mobileBreakpoint\" (ngModelChange)=\"onMobileBreakpointChange($event)\"\n [attr.aria-invalid]=\"mobileBreakpointInvalid\"\n aria-describedby=\"table-responsive-width-help\" />\n <span matTextSuffix>px</span>\n </mat-form-field>\n @if (mobileBreakpointInvalid) {\n <p class=\"responsive-settings__error\" role=\"alert\">{{ tx('overview.responsive.invalidWidth', 'Enter a positive whole number.') }}</p>\n }\n <p id=\"table-responsive-width-help\" class=\"responsive-settings__hint\">{{ tx('overview.responsive.widthHint', 'Uses the smaller width of the screen and the table area. Default: 768 px.') }}</p>\n <p class=\"responsive-settings__hint\">{{ tx('overview.responsive.virtualizationHint', 'Cards require standard rows. With virtual scrolling enabled, records remain in a table.') }}</p>\n }\n </section>\n <div class=\"overview-row\">\n <mat-form-field appearance=\"outline\" class=\"hs-field\">\n <mat-label>{{ tx('overview.horizontalScroll', 'Horizontal scroll') }}</mat-label>\n <mat-select [(ngModel)]=\"horizontalScroll\" (ngModelChange)=\"onHorizontalScrollChange($event)\">\n <mat-option value=\"auto\">{{ tx('overview.horizontalScroll.auto', 'Auto (default)') }}</mat-option>\n <mat-option value=\"wrap\">{{ tx('overview.horizontalScroll.wrap', 'Wrap') }}</mat-option>\n <mat-option value=\"none\">{{ tx('overview.horizontalScroll.none', 'Host controls') }}</mat-option>\n </mat-select>\n </mat-form-field>\n <div class=\"help small\">{{ tx('overview.horizontalScroll.help', 'Controls how the table handles horizontal width and scrollbar behavior.') }}</div>\n </div>\n @if (isEffectiveLocalMode()) { <div class=\"alert alert--info overview-mode-alert\">\n <mat-icon class=\"alert__icon\">info</mat-icon>\n <div class=\"alert__text\">\n {{ tx('overview.localModeActive', 'Effective local mode is active: server pagination and sorting strategies are blocked and persisted as client mode.') }}\n </div>\n </div> }\n <behavior-config-editor\n [config]=\"editedConfig\"\n [resourcePath]=\"resourcePath\"\n [forceClientStrategies]=\"isEffectiveLocalMode()\"\n (configChange)=\"onBehaviorConfigChange($event)\"\n (behaviorChange)=\"onBehaviorChange($event)\"\n ></behavior-config-editor>\n </div>\n }\n @case ('columns') {\n <columns-config-editor\n [config]=\"editedConfig\"\n [fieldMetadata]=\"getColumnAuthoringMetas()\"\n (configChange)=\"onColumnsConfigChange($event)\"\n (columnChange)=\"onColumnChange($event)\"\n ></columns-config-editor>\n }\n @case ('detail') {\n <praxis-table-detail-editor [config]=\"editedConfig\"\n (configChange)=\"onDetailConfigChange($event)\"\n (draftStateChange)=\"onDetailDraftStateChange($event)\"\n ></praxis-table-detail-editor>\n }\n @case ('rules') {\n <table-rules-editor\n [config]=\"editedConfig\"\n [resourcePath]=\"resourcePath\"\n [fields]=\"resourceSchemaFields\"\n (configChange)=\"onRulesConfigChange($event)\"\n ></table-rules-editor>\n }\n @case ('header') {\n <header-appearance-editor\n [config]=\"editedConfig\"\n (configChange)=\"onColumnsConfigChange($event)\"\n ></header-appearance-editor>\n }\n @case ('toolbar') {\n <toolbar-actions-editor\n [config]=\"editedConfig\"\n (configChange)=\"onToolbarActionsConfigChange($event)\"\n (toolbarActionsChange)=\"onToolbarActionsChange($event)\"\n (validationChange)=\"onSurfaceDraftValidationChange($event)\"\n ></toolbar-actions-editor>\n }\n <!-- Aba extra para integra\u00E7\u00F5es CRUD (vis\u00EDvel quando em contexto CRUD) -->\n @case ('crud') {\n <crud-integration-editor\n #crudEditorRef\n [tableId]=\"crudContext?.tableId || 'default'\"\n [crudContext]=\"crudContext\"\n [componentKeyId]=\"crudContext?.componentKeyId\"\n ></crud-integration-editor>\n }\n @case ('filters') {\n <div class=\"filters-authoring-stack\">\n <section\n class=\"responsive-settings toolbar-filters-region\"\n data-testid=\"table-unified-filter-region-section\"\n aria-labelledby=\"table-unified-filter-region-title\"\n >\n <div class=\"responsive-settings__heading\">\n <mat-icon aria-hidden=\"true\">filter_list</mat-icon>\n <h3 id=\"table-unified-filter-region-title\">\n {{ tx('filters.region.title', 'Regi\u00E3o \u00FAnica de filtros e escopos da toolbar') }}\n </h3>\n </div>\n <p class=\"responsive-settings__hint\">\n {{\n tx(\n 'filters.region.description',\n 'Centralize escopos r\u00E1pidos (Todos / Ativos / Inativos), busca e gaveta avan\u00E7ada na toolbar da tabela sem controles duplicados.'\n )\n }}\n </p>\n\n <div class=\"toolbar-filters-toggles\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('filters.region.enabled', 'Regi\u00E3o de filtros na toolbar') }}</mat-label>\n <mat-select\n [ngModel]=\"toolbarFiltersEnabled\"\n (ngModelChange)=\"onToolbarFiltersEnabledChange($event)\"\n data-testid=\"table-toolbar-filters-enabled-select\"\n >\n <mat-option [value]=\"true\">{{ tx('common.enabled', 'Ativada') }}</mat-option>\n <mat-option [value]=\"false\">{{ tx('common.disabled', 'Desativada') }}</mat-option>\n </mat-select>\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('filters.region.showQuickFilters', 'Exibir escopos r\u00E1pidos') }}</mat-label>\n <mat-select\n [ngModel]=\"toolbarShowQuickFilters\"\n (ngModelChange)=\"onToolbarShowQuickFiltersChange($event)\"\n data-testid=\"table-toolbar-show-quick-filters-select\"\n >\n <mat-option [value]=\"true\">{{ tx('common.yes', 'Sim') }}</mat-option>\n <mat-option [value]=\"false\">{{ tx('common.no', 'N\u00E3o') }}</mat-option>\n </mat-select>\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('filters.region.showAdvancedButton', 'Bot\u00E3o de gaveta avan\u00E7ada') }}</mat-label>\n <mat-select\n [ngModel]=\"toolbarShowAdvancedButton\"\n (ngModelChange)=\"onToolbarShowAdvancedButtonChange($event)\"\n data-testid=\"table-toolbar-show-advanced-button-select\"\n >\n <mat-option [value]=\"true\">{{ tx('common.yes', 'Sim') }}</mat-option>\n <mat-option [value]=\"false\">{{ tx('common.no', 'N\u00E3o') }}</mat-option>\n </mat-select>\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('filters.region.showAddButton', 'Bot\u00E3o \"Gerenciar campos\"') }}</mat-label>\n <mat-select\n [ngModel]=\"advancedFilterShowAddButton\"\n (ngModelChange)=\"onAdvancedFilterShowAddButtonChange($event)\"\n data-testid=\"table-toolbar-show-add-button-select\"\n >\n <mat-option [value]=\"true\">{{ tx('common.yes', 'Sim') }}</mat-option>\n <mat-option [value]=\"false\">{{ tx('common.no', 'N\u00E3o') }}</mat-option>\n </mat-select>\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('filters.region.showFilterSettings', 'Bot\u00E3o \"Configura\u00E7\u00F5es do filtro\"') }}</mat-label>\n <mat-select\n [ngModel]=\"advancedFilterShowFilterSettings\"\n (ngModelChange)=\"onAdvancedFilterShowFilterSettingsChange($event)\"\n data-testid=\"table-toolbar-show-filter-settings-select\"\n >\n <mat-option [value]=\"true\">{{ tx('common.yes', 'Sim') }}</mat-option>\n <mat-option [value]=\"false\">{{ tx('common.no', 'N\u00E3o') }}</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n\n @if (duplicatedQuickFilterInlineFields.length > 0) {\n <div\n class=\"alert alert--warning connect-alert\"\n data-testid=\"table-filter-duplicate-scope-warning\"\n >\n <mat-icon class=\"alert__icon\">warning</mat-icon>\n <div class=\"alert__text connect-alert__text\">\n {{\n tx(\n 'filters.region.duplicateWarning',\n 'Os campos abaixo j\u00E1 possuem escopos r\u00E1pidos na toolbar e tamb\u00E9m est\u00E3o nos filtros inline:'\n )\n }}\n <strong>{{ duplicatedQuickFilterInlineFields.join(', ') }}</strong>\n </div>\n <button\n mat-stroked-button\n color=\"primary\"\n type=\"button\"\n data-testid=\"table-filter-consolidate-duplicates-btn\"\n (click)=\"consolidateDuplicateInlineFilterFields()\"\n >\n {{ tx('filters.region.consolidateBtn', 'Consolidar nos escopos r\u00E1pidos') }}\n </button>\n </div>\n }\n\n <div class=\"quick-filters-header\">\n <strong>{{ tx('filters.quickFilters.heading', 'Escopos r\u00E1pidos (quickFilters)') }}</strong>\n <div class=\"quick-filters-actions\">\n <button\n mat-stroked-button\n type=\"button\"\n data-testid=\"table-quick-filters-preset-boolean-btn\"\n (click)=\"applyBooleanQuickFilterPreset()\"\n >\n {{ tx('filters.quickFilters.presetBoolean', 'Preset Todos / Ativos / Inativos') }}\n </button>\n <button\n mat-stroked-button\n color=\"primary\"\n type=\"button\"\n data-testid=\"table-quick-filters-add-btn\"\n (click)=\"addQuickFilterScope()\"\n >\n {{ tx('filters.quickFilters.add', 'Adicionar escopo') }}\n </button>\n </div>\n </div>\n\n @if (toolbarQuickFilters.length > 0) {\n <div class=\"quick-filters-list\" data-testid=\"table-quick-filters-list\">\n @for (qf of toolbarQuickFilters; track $index; let idx = $index) {\n <div class=\"quick-filter-row\" [attr.data-testid]=\"'table-quick-filter-row-' + idx\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('filters.quickFilters.id', 'ID') }}</mat-label>\n <input\n matInput\n [ngModel]=\"qf.id\"\n (ngModelChange)=\"updateQuickFilterMeta(idx, { id: $event })\"\n [attr.data-testid]=\"'table-quick-filter-id-' + idx\"\n />\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('filters.quickFilters.label', 'R\u00F3tulo') }}</mat-label>\n <input\n matInput\n [ngModel]=\"qf.label\"\n (ngModelChange)=\"updateQuickFilterMeta(idx, { label: $event })\"\n [attr.data-testid]=\"'table-quick-filter-label-' + idx\"\n />\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('filters.quickFilters.mode', 'Tipo de escopo') }}</mat-label>\n <mat-select\n [ngModel]=\"getQuickFilterMode(qf)\"\n (ngModelChange)=\"onQuickFilterModeChange(idx, $event)\"\n [attr.data-testid]=\"'table-quick-filter-mode-' + idx\"\n >\n <mat-option value=\"all\">{{ tx('filters.quickFilters.mode.all', 'Todos (limpar filtro)') }}</mat-option>\n <mat-option value=\"boolean\">{{ tx('filters.quickFilters.mode.boolean', 'Status booleano (Sim/N\u00E3o)') }}</mat-option>\n <mat-option value=\"equals\">{{ tx('filters.quickFilters.mode.equals', 'Campo igual a valor') }}</mat-option>\n </mat-select>\n </mat-form-field>\n\n @if (getQuickFilterMode(qf) !== 'all') {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('filters.quickFilters.field', 'Campo') }}</mat-label>\n <input\n matInput\n [ngModel]=\"getQuickFilterField(qf)\"\n (ngModelChange)=\"onQuickFilterFieldChange(idx, $event)\"\n [matAutocomplete]=\"quickFilterFieldPicker\"\n [attr.data-testid]=\"'table-quick-filter-field-' + idx\"\n />\n <mat-autocomplete\n #quickFilterFieldPicker=\"matAutocomplete\"\n (optionSelected)=\"onQuickFilterFieldChange(idx, $event.option.value)\"\n >\n @for (option of getFilteredQuickFilterFieldOptions(getQuickFilterField(qf)); track option.name) {\n <mat-option [value]=\"option.name\">\n <span>{{ option.name }}</span>\n @if (option.label && option.label !== option.name) {\n <small> - {{ option.label }}</small>\n }\n </mat-option>\n }\n </mat-autocomplete>\n </mat-form-field>\n\n @if (getQuickFilterMode(qf) === 'boolean') {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('filters.quickFilters.value', 'Valor') }}</mat-label>\n <mat-select\n [ngModel]=\"getQuickFilterValue(qf)\"\n (ngModelChange)=\"onQuickFilterValueChange(idx, $event)\"\n [attr.data-testid]=\"'table-quick-filter-bool-value-' + idx\"\n >\n <mat-option [value]=\"true\">{{ tx('common.boolean.true', 'Verdadeiro (true)') }}</mat-option>\n <mat-option [value]=\"false\">{{ tx('common.boolean.false', 'Falso (false)') }}</mat-option>\n </mat-select>\n </mat-form-field>\n } @else {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('filters.quickFilters.value', 'Valor') }}</mat-label>\n <input\n matInput\n [ngModel]=\"getQuickFilterValue(qf)\"\n (ngModelChange)=\"onQuickFilterValueChange(idx, $event)\"\n [attr.data-testid]=\"'table-quick-filter-value-' + idx\"\n />\n </mat-form-field>\n }\n }\n\n <button\n mat-stroked-button\n color=\"warn\"\n type=\"button\"\n [attr.data-testid]=\"'table-quick-filter-remove-' + idx\"\n (click)=\"removeQuickFilterScope(idx)\"\n >\n {{ tx('common.remove', 'Remover') }}\n </button>\n </div>\n }\n </div>\n }\n </section>\n\n <filter-settings\n #tableFilterSettingsRef\n [metadata]=\"getFilterSettingsMetas()\"\n [metadataSource]=\"isUsingFilterDtoMetas() ? 'filter-dto' : 'columns'\"\n [metadataLoading]=\"filterDtoMetasLoading\"\n [metadataErrorMsg]=\"filterDtoMetasErrorMsg\"\n [settings]=\"\n editedConfig.behavior?.filtering?.advancedFilters?.settings\n \"\n (settingsChange)=\"onFilterSettingsChange($event)\"\n ></filter-settings>\n </div>\n }\n @case ('messages') {\n <messages-localization-editor\n [config]=\"editedConfig\"\n (configChange)=\"onMessagesLocalizationConfigChange($event)\"\n (messagesLocalizationChange)=\"\n onMessagesLocalizationChange($event)\n \"\n ></messages-localization-editor>\n }\n @case ('dialogs') {\n <confirm-dialog-appearance-editor\n [config]=\"editedConfig\"\n (configChange)=\"onJsonConfigChange($event)\"\n ></confirm-dialog-appearance-editor>\n }\n @case ('json') {\n <json-config-editor\n [document]=\"buildJsonAuthoringDocument()\"\n (documentChange)=\"onJsonConfigChange($event)\"\n (validationChange)=\"onJsonValidationChange($event)\"\n (editorEvent)=\"onJsonEditorEvent($event)\"\n ></json-config-editor>\n }\n }\n </div>\n </mat-tab>\n }\n</mat-tab-group>\n@if (statusMessage) { <div class=\"config-editor-status\">\n <span\n class=\"status-text\"\n [class.error]=\"hasErrors\"\n [class.success]=\"hasSuccess\"\n >{{ statusMessage }}</span\n >\n</div> }\n", styles: ["@charset \"UTF-8\";:host{display:block;min-width:0;container:praxis-authoring-editor/inline-size}.editor-section-selector{display:block;padding:var(--pfx-spacing-2, 8px)}.editor-section-selector mat-form-field{width:100%}@container praxis-authoring-editor (max-width: 768px){.editor-section-selector{display:block}::ng-deep .config-tabs>.mat-mdc-tab-header{display:none}}.config-tabs{flex:1 1 auto;display:flex;flex-direction:column}.config-tabs .mat-mdc-tab{min-width:120px}.config-tabs .mat-mdc-tab-body-wrapper,.config-tabs .mat-mdc-tab-group-container{flex:1 1 auto;min-height:0}.config-tabs .mat-mdc-tab-body-content{height:100%;min-height:0;overflow:visible}.tab-content{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;padding:8px 8px 56px;box-sizing:border-box;overflow:visible}.overview-grid{display:grid;min-width:0;grid-template-columns:minmax(0,1fr);gap:12px}.overview-grid .overview-row{display:grid;grid-template-columns:280px 1fr;align-items:end;gap:12px}.overview-grid .overview-row .help.small{opacity:.75;font-size:12px}.overview-mode-alert{align-items:center}.connect-grid{display:grid;gap:12px;padding:8px;grid-template-columns:minmax(0,1fr);align-items:start}.connect-note{grid-column:1/-1;margin:0;font-size:12px;color:var(--md-sys-color-on-surface-variant)}.connect-alert{grid-column:1/-1;align-items:center}.connect-alert__text{flex:1}.connect-alert__meta{opacity:.85}.educational-card{margin-bottom:24px;background:var(--md-sys-color-surface-container-low);border-left:4px solid var(--md-sys-color-primary);flex-shrink:0}.config-editor-status{padding:12px 16px;margin-top:auto;border-top:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container);flex-shrink:0;position:relative}.status-text{font-size:.875rem;line-height:1.2}.status-text.error{color:var(--md-sys-color-error)}.status-text.success{color:var(--md-sys-color-primary)}@media(max-width:768px){.tab-content{padding:8px 8px 72px}.config-editor-status{padding:12px 16px}.connect-grid{grid-template-columns:1fr}.overview-grid .overview-row{grid-template-columns:1fr;align-items:start}:host{display:flex;flex-direction:column;flex:1 1 auto;min-height:0}}.responsive-settings{display:grid;grid-template-columns:minmax(0,1fr);gap:12px;min-width:0;padding:16px;border:1px solid var(--md-sys-color-outline-variant);border-radius:var(--md-sys-shape-corner-medium, 12px);background:var(--md-sys-color-surface-container-low)}.responsive-settings__heading{display:flex;align-items:center;gap:8px}.responsive-settings h3,.responsive-settings p{margin:0}.responsive-settings h3{font-size:1rem;font-weight:600}.responsive-settings p{font-size:.875rem;line-height:1.5}.responsive-settings__hint{color:var(--md-sys-color-on-surface-variant)}.responsive-settings__error{color:var(--md-sys-color-error)}.responsive-settings mat-form-field{width:100%;min-width:0}.filters-authoring-stack{display:grid;grid-template-columns:minmax(0,1fr);gap:16px}.toolbar-filters-toggles{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px}.quick-filters-header{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:8px}.quick-filters-actions{display:flex;flex-wrap:wrap;gap:8px}.quick-filters-list{display:grid;gap:10px}.quick-filter-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));align-items:center;gap:8px;padding:10px;border:1px solid var(--md-sys-color-outline-variant);border-radius:8px;background:var(--md-sys-color-surface)}\n"], dependencies: [{ kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i7.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "directive", type: i4$1.MatTabLabel, selector: "[mat-tab-label], [matTabLabel]" }, { kind: "component", type: i4$1.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i4$1.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatAutocompleteModule }, { kind: "component", type: i17$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: i4.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "directive", type: i17$1.MatAutocompleteTrigger, selector: "input[matAutocomplete], textarea[matAutocomplete]", inputs: ["matAutocomplete", "matAutocompletePosition", "matAutocompleteConnectedTo", "autocomplete", "matAutocompleteDisabled"], exportAs: ["matAutocompleteTrigger"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2$1.MatLabel, selector: "mat-label" }, { kind: "directive", type: i2$1.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3$1.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i4.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: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: ResourceQuickConnectComponent, selector: "praxis-resource-quick-connect", inputs: ["resourcePath", "endpointKey", "apiUrlEntry", "intent", "resources", "label", "placeholder", "hint", "disabled"], outputs: ["resourcePathChange"] }, { kind: "component", type: JsonConfigEditorComponent, selector: "json-config-editor", inputs: ["document"], outputs: ["documentChange", "validationChange", "editorEvent"] }, { kind: "component", type: ColumnsConfigEditorComponent, selector: "columns-config-editor", inputs: ["config", "readonly", "fieldMetadata"], outputs: ["configChange", "columnChange"] }, { kind: "component", type: BehaviorConfigEditorComponent, selector: "behavior-config-editor", inputs: ["config", "resourcePath", "forceClientStrategies"], outputs: ["configChange", "behaviorChange"] }, { kind: "component", type: HeaderAppearanceEditorComponent, selector: "header-appearance-editor", inputs: ["config"], outputs: ["configChange"] }, { kind: "component", type: ToolbarActionsEditorComponent, selector: "toolbar-actions-editor", inputs: ["config"], outputs: ["configChange", "validationChange", "toolbarActionsChange"] }, { kind: "component", type: MessagesLocalizationEditorComponent, selector: "messages-localization-editor", inputs: ["config"], outputs: ["configChange", "messagesLocalizationChange"] }, { kind: "component", type: FilterSettingsComponent, selector: "filter-settings", inputs: ["metadata", "metadataSource", "metadataLoading", "metadataErrorMsg", "settings", "configKey"], outputs: ["settingsChange"] }, { kind: "component", type: CrudIntegrationEditorComponent, selector: "crud-integration-editor", inputs: ["tableId", "crudContext", "componentKeyId"] }, { kind: "component", type: ConfirmDialogAppearanceEditorComponent, selector: "confirm-dialog-appearance-editor", inputs: ["config"], outputs: ["configChange"] }, { kind: "component", type: TableRulesEditorComponent, selector: "table-rules-editor", inputs: ["config", "resourcePath", "fields", "i18nRules", "debugLogs", "debugLevel"], outputs: ["configChange"] }, { kind: "component", type: TableDetailEditorComponent, selector: "praxis-table-detail-editor", inputs: ["config"], outputs: ["configChange", "draftStateChange"] }] });
|
|
74942
|
+
], viewQueries: [{ propertyName: "filterSettingsEditor", first: true, predicate: ["tableFilterSettingsRef"], descendants: true }, { propertyName: "behaviorEditor", first: true, predicate: BehaviorConfigEditorComponent, descendants: true }, { propertyName: "detailEditor", first: true, predicate: TableDetailEditorComponent, descendants: true }, { propertyName: "rulesEditor", first: true, predicate: TableRulesEditorComponent, descendants: true }, { propertyName: "crudEditorSetter", first: true, predicate: ["crudEditorRef"], descendants: true }], ngImport: i0, template: "<div class=\"editor-section-selector\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('sections.selector', 'Editor section') }}</mat-label>\n <mat-select [(ngModel)]=\"activeSectionIndex\" data-testid=\"table-config-editor-sections\">\n @for (section of sections; track section.id; let index = $index) {\n <mat-option [value]=\"index\">{{ section.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n</div>\n<mat-tab-group [preserveContent]=\"true\" class=\"config-tabs\" [(selectedIndex)]=\"activeSectionIndex\">\n @for (section of sections; track section.id) {\n <mat-tab>\n <ng-template mat-tab-label>\n @if (section.icon) { <mat-icon [praxisIcon]=\"section.icon\"></mat-icon> }\n <span [attr.data-testid]=\"'config-tab-' + section.id\">{{ section.label }}</span>\n </ng-template>\n <div class=\"tab-content\">\n @switch (section.id) {\n @case ('connect') {\n <div class=\"connect-grid\">\n <praxis-resource-quick-connect\n [resourcePath]=\"resourcePath\"\n [endpointKey]=\"resourceEndpointKey\"\n [apiUrlEntry]=\"resourceApiUrlEntry\"\n (resourcePathChange)=\"onResourcePathChange($event)\"\n intent=\"collection\"\n [label]=\"tx('connect.resource', 'Resource')\"\n [placeholder]=\"tx('connect.resource.placeholder', 'e.g. employees')\"\n />\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('connect.primaryKey', 'Primary key') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"idField\"\n (ngModelChange)=\"onIdFieldChange($event)\"\n [matAutocomplete]=\"idFieldPicker\"\n [placeholder]=\"tx('connect.primaryKey.placeholder', 'e.g. id, uuid, code')\"\n />\n <mat-icon matSuffix>fingerprint</mat-icon>\n <mat-autocomplete\n #idFieldPicker=\"matAutocomplete\"\n (optionSelected)=\"onIdFieldOptionSelected($event.option.value)\"\n >\n @for (option of filteredIdFieldOptions; track option.name) {\n <mat-option [value]=\"option.name\">\n <div class=\"table-rich-field-option\">\n <div class=\"table-rich-field-option__top\">\n <span class=\"table-rich-field-option__label\">{{ option.label && option.label !== option.name ? option.label : option.name }}</span>\n @if (option.label && option.label !== option.name) {\n <code class=\"table-rich-field-option__code\">{{ option.name }}</code>\n }\n @if (getFieldTypeBadge(option); as badge) {\n <span class=\"table-rich-field-option__badge\">{{ badge }}</span>\n }\n </div>\n @if (getFieldDescription(option); as desc) {\n <div class=\"table-rich-field-option__desc\">{{ desc }}</div>\n }\n </div>\n </mat-option>\n }\n @if (filteredIdFieldOptions.length === 0) {\n <mat-option disabled>\n {{ tx('connect.primaryKey.empty', 'No matching field found.') }}\n </mat-option>\n }\n </mat-autocomplete>\n </mat-form-field>\n <p class=\"connect-note\">{{ tx('connect.note', 'Define the API resource and, when needed, the primary key.') }}</p>\n <!-- Diverg\u00EAncia: idField -->\n @if (idFieldDiverges) { <div class=\"alert alert--warning connect-alert\">\n <mat-icon class=\"alert__icon\">warning</mat-icon>\n <div class=\"alert__text connect-alert__text\">\n {{ tx('connect.serverPrimaryKeyDiverges', 'The primary key on the server differs from the current configuration.') }}\n <span class=\"connect-alert__meta\">(config: {{ idField || 'id' }} | servidor: {{ serverIdField || 'id' }})</span>\n </div>\n <button mat-stroked-button color=\"primary\" type=\"button\" (click)=\"onReconcileIdField()\">{{ tx('connect.reconcile', 'Reconcile') }}</button>\n </div> }\n <!-- Diverg\u00EAncia: serverHash -->\n @if (schemaHashDiverges) { <div class=\"alert alert--info connect-alert\">\n <mat-icon class=\"alert__icon\">info</mat-icon>\n <div class=\"alert__text connect-alert__text\">\n {{ tx('connect.schemaSnapshotDiverges', 'The saved schema snapshot differs from the current server schema.') }}\n </div>\n <button mat-stroked-button color=\"primary\" type=\"button\" (click)=\"onAcceptServerHash()\">{{ tx('connect.updateMetadata', 'Update metadata') }}</button>\n </div> }\n @if (!schemaHashDiverges && schemaSnapshotMissing) { <div class=\"alert alert--info connect-alert\">\n <mat-icon class=\"alert__icon\">info</mat-icon>\n <div class=\"alert__text connect-alert__text\">\n {{ tx('connect.schemaSnapshotMissing', 'A current schema exists on the server, but there is still no persisted snapshot for reconciliation.') }}\n </div>\n <button mat-stroked-button color=\"primary\" type=\"button\" (click)=\"onAcceptServerHash()\">{{ tx('connect.saveSnapshot', 'Save snapshot') }}</button>\n </div> }\n </div>\n }\n @case ('overview') {\n <div class=\"overview-grid\">\n <section class=\"responsive-settings\" aria-labelledby=\"table-responsive-title\">\n <div class=\"responsive-settings__heading\">\n <mat-icon aria-hidden=\"true\">devices</mat-icon>\n <h3 id=\"table-responsive-title\">{{ tx('overview.responsive.title', 'Narrow screens') }}</h3>\n </div>\n <p>{{ tx('overview.responsive.description', 'Choose how records appear when the table has less space.') }}</p>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('overview.responsive.mode', 'Record layout') }}</mat-label>\n <mat-select [ngModel]=\"mobileCardMode\" (ngModelChange)=\"onMobileCardModeChange($event)\">\n <mat-option [value]=\"false\">{{ tx('overview.responsive.table', 'Table') }}</mat-option>\n <mat-option [value]=\"true\">{{ tx('overview.responsive.cards', 'Cards') }}</mat-option>\n </mat-select>\n </mat-form-field>\n @if (mobileCardMode) {\n <p class=\"responsive-settings__hint\">{{ tx('overview.responsive.cardsHint', 'Each record becomes a card with its visible columns. Selection and row actions remain available. Wider areas keep the table layout.') }}</p>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('overview.responsive.breakpoint', 'Show cards at widths up to') }}</mat-label>\n <input matInput type=\"number\" min=\"1\" step=\"1\" required\n [ngModel]=\"mobileBreakpoint\" (ngModelChange)=\"onMobileBreakpointChange($event)\"\n [attr.aria-invalid]=\"mobileBreakpointInvalid\"\n aria-describedby=\"table-responsive-width-help\" />\n <span matTextSuffix>px</span>\n </mat-form-field>\n @if (mobileBreakpointInvalid) {\n <p class=\"responsive-settings__error\" role=\"alert\">{{ tx('overview.responsive.invalidWidth', 'Enter a positive whole number.') }}</p>\n }\n <p id=\"table-responsive-width-help\" class=\"responsive-settings__hint\">{{ tx('overview.responsive.widthHint', 'Uses the smaller width of the screen and the table area. Default: 768 px.') }}</p>\n <p class=\"responsive-settings__hint\">{{ tx('overview.responsive.virtualizationHint', 'Cards require standard rows. With virtual scrolling enabled, records remain in a table.') }}</p>\n }\n </section>\n <div class=\"overview-row\">\n <mat-form-field appearance=\"outline\" class=\"hs-field\">\n <mat-label>{{ tx('overview.horizontalScroll', 'Horizontal scroll') }}</mat-label>\n <mat-select [(ngModel)]=\"horizontalScroll\" (ngModelChange)=\"onHorizontalScrollChange($event)\">\n <mat-option value=\"auto\">{{ tx('overview.horizontalScroll.auto', 'Auto (default)') }}</mat-option>\n <mat-option value=\"wrap\">{{ tx('overview.horizontalScroll.wrap', 'Wrap') }}</mat-option>\n <mat-option value=\"none\">{{ tx('overview.horizontalScroll.none', 'Host controls') }}</mat-option>\n </mat-select>\n </mat-form-field>\n <div class=\"help small\">{{ tx('overview.horizontalScroll.help', 'Controls how the table handles horizontal width and scrollbar behavior.') }}</div>\n </div>\n @if (isEffectiveLocalMode()) { <div class=\"alert alert--info overview-mode-alert\">\n <mat-icon class=\"alert__icon\">info</mat-icon>\n <div class=\"alert__text\">\n {{ tx('overview.localModeActive', 'Effective local mode is active: server pagination and sorting strategies are blocked and persisted as client mode.') }}\n </div>\n </div> }\n <behavior-config-editor\n [config]=\"editedConfig\"\n [resourcePath]=\"resourcePath\"\n [forceClientStrategies]=\"isEffectiveLocalMode()\"\n (configChange)=\"onBehaviorConfigChange($event)\"\n (behaviorChange)=\"onBehaviorChange($event)\"\n ></behavior-config-editor>\n </div>\n }\n @case ('columns') {\n <columns-config-editor\n [config]=\"editedConfig\"\n [fieldMetadata]=\"getColumnAuthoringMetas()\"\n (configChange)=\"onColumnsConfigChange($event)\"\n (columnChange)=\"onColumnChange($event)\"\n ></columns-config-editor>\n }\n @case ('detail') {\n <praxis-table-detail-editor [config]=\"editedConfig\"\n (configChange)=\"onDetailConfigChange($event)\"\n (draftStateChange)=\"onDetailDraftStateChange($event)\"\n ></praxis-table-detail-editor>\n }\n @case ('rules') {\n <table-rules-editor\n [config]=\"editedConfig\"\n [resourcePath]=\"resourcePath\"\n [fields]=\"resourceSchemaFields\"\n (configChange)=\"onRulesConfigChange($event)\"\n ></table-rules-editor>\n }\n @case ('header') {\n <header-appearance-editor\n [config]=\"editedConfig\"\n (configChange)=\"onColumnsConfigChange($event)\"\n ></header-appearance-editor>\n }\n @case ('toolbar') {\n <toolbar-actions-editor\n [config]=\"editedConfig\"\n (configChange)=\"onToolbarActionsConfigChange($event)\"\n (toolbarActionsChange)=\"onToolbarActionsChange($event)\"\n (validationChange)=\"onSurfaceDraftValidationChange($event)\"\n ></toolbar-actions-editor>\n }\n <!-- Aba extra para integra\u00E7\u00F5es CRUD (vis\u00EDvel quando em contexto CRUD) -->\n @case ('crud') {\n <crud-integration-editor\n #crudEditorRef\n [tableId]=\"crudContext?.tableId || 'default'\"\n [crudContext]=\"crudContext\"\n [componentKeyId]=\"crudContext?.componentKeyId\"\n ></crud-integration-editor>\n }\n @case ('filters') {\n <div class=\"filters-authoring-stack\">\n <section\n class=\"responsive-settings toolbar-filters-region\"\n data-testid=\"table-unified-filter-region-section\"\n aria-labelledby=\"table-unified-filter-region-title\"\n >\n <div class=\"responsive-settings__heading\">\n <mat-icon aria-hidden=\"true\">filter_list</mat-icon>\n <h3 id=\"table-unified-filter-region-title\">\n {{ tx('filters.region.title', 'Regi\u00E3o \u00FAnica de filtros e escopos da toolbar') }}\n </h3>\n </div>\n <p class=\"responsive-settings__hint\">\n {{\n tx(\n 'filters.region.description',\n 'Centralize escopos r\u00E1pidos (Todos / Ativos / Inativos), busca e gaveta avan\u00E7ada na toolbar da tabela sem controles duplicados.'\n )\n }}\n </p>\n\n <div class=\"toolbar-filters-toggles\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('filters.region.enabled', 'Regi\u00E3o de filtros na toolbar') }}</mat-label>\n <mat-select\n [ngModel]=\"toolbarFiltersEnabled\"\n (ngModelChange)=\"onToolbarFiltersEnabledChange($event)\"\n data-testid=\"table-toolbar-filters-enabled-select\"\n >\n <mat-option [value]=\"true\">{{ tx('common.enabled', 'Ativada') }}</mat-option>\n <mat-option [value]=\"false\">{{ tx('common.disabled', 'Desativada') }}</mat-option>\n </mat-select>\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('filters.region.showQuickFilters', 'Exibir escopos r\u00E1pidos') }}</mat-label>\n <mat-select\n [ngModel]=\"toolbarShowQuickFilters\"\n (ngModelChange)=\"onToolbarShowQuickFiltersChange($event)\"\n data-testid=\"table-toolbar-show-quick-filters-select\"\n >\n <mat-option [value]=\"true\">{{ tx('common.yes', 'Sim') }}</mat-option>\n <mat-option [value]=\"false\">{{ tx('common.no', 'N\u00E3o') }}</mat-option>\n </mat-select>\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('filters.region.showAdvancedButton', 'Bot\u00E3o de gaveta avan\u00E7ada') }}</mat-label>\n <mat-select\n [ngModel]=\"toolbarShowAdvancedButton\"\n (ngModelChange)=\"onToolbarShowAdvancedButtonChange($event)\"\n data-testid=\"table-toolbar-show-advanced-button-select\"\n >\n <mat-option [value]=\"true\">{{ tx('common.yes', 'Sim') }}</mat-option>\n <mat-option [value]=\"false\">{{ tx('common.no', 'N\u00E3o') }}</mat-option>\n </mat-select>\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('filters.region.showAddButton', 'Bot\u00E3o \"Gerenciar campos\"') }}</mat-label>\n <mat-select\n [ngModel]=\"advancedFilterShowAddButton\"\n (ngModelChange)=\"onAdvancedFilterShowAddButtonChange($event)\"\n data-testid=\"table-toolbar-show-add-button-select\"\n >\n <mat-option [value]=\"true\">{{ tx('common.yes', 'Sim') }}</mat-option>\n <mat-option [value]=\"false\">{{ tx('common.no', 'N\u00E3o') }}</mat-option>\n </mat-select>\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('filters.region.showFilterSettings', 'Bot\u00E3o \"Configura\u00E7\u00F5es do filtro\"') }}</mat-label>\n <mat-select\n [ngModel]=\"advancedFilterShowFilterSettings\"\n (ngModelChange)=\"onAdvancedFilterShowFilterSettingsChange($event)\"\n data-testid=\"table-toolbar-show-filter-settings-select\"\n >\n <mat-option [value]=\"true\">{{ tx('common.yes', 'Sim') }}</mat-option>\n <mat-option [value]=\"false\">{{ tx('common.no', 'N\u00E3o') }}</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n\n @if (duplicatedQuickFilterInlineFields.length > 0) {\n <div\n class=\"alert alert--warning connect-alert\"\n data-testid=\"table-filter-duplicate-scope-warning\"\n >\n <mat-icon class=\"alert__icon\">warning</mat-icon>\n <div class=\"alert__text connect-alert__text\">\n {{\n tx(\n 'filters.region.duplicateWarning',\n 'Os campos abaixo j\u00E1 possuem escopos r\u00E1pidos na toolbar e tamb\u00E9m est\u00E3o nos filtros inline:'\n )\n }}\n <strong>{{ duplicatedQuickFilterInlineFields.join(', ') }}</strong>\n </div>\n <button\n mat-stroked-button\n color=\"primary\"\n type=\"button\"\n data-testid=\"table-filter-consolidate-duplicates-btn\"\n (click)=\"consolidateDuplicateInlineFilterFields()\"\n >\n {{ tx('filters.region.consolidateBtn', 'Consolidar nos escopos r\u00E1pidos') }}\n </button>\n </div>\n }\n\n <div class=\"quick-filters-header\">\n <strong>{{ tx('filters.quickFilters.heading', 'Escopos r\u00E1pidos (quickFilters)') }}</strong>\n <div class=\"quick-filters-actions\">\n <button\n mat-stroked-button\n type=\"button\"\n data-testid=\"table-quick-filters-preset-boolean-btn\"\n (click)=\"applyBooleanQuickFilterPreset()\"\n >\n {{ tx('filters.quickFilters.presetBoolean', 'Preset Todos / Ativos / Inativos') }}\n </button>\n <button\n mat-stroked-button\n color=\"primary\"\n type=\"button\"\n data-testid=\"table-quick-filters-add-btn\"\n (click)=\"addQuickFilterScope()\"\n >\n {{ tx('filters.quickFilters.add', 'Adicionar escopo') }}\n </button>\n </div>\n </div>\n\n @if (toolbarQuickFilters.length > 0) {\n <div class=\"quick-filters-list\" data-testid=\"table-quick-filters-list\">\n @for (qf of toolbarQuickFilters; track $index; let idx = $index) {\n <div class=\"quick-filter-row\" [attr.data-testid]=\"'table-quick-filter-row-' + idx\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('filters.quickFilters.id', 'ID') }}</mat-label>\n <input\n matInput\n [ngModel]=\"qf.id\"\n (ngModelChange)=\"updateQuickFilterMeta(idx, { id: $event })\"\n [attr.data-testid]=\"'table-quick-filter-id-' + idx\"\n />\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('filters.quickFilters.label', 'R\u00F3tulo') }}</mat-label>\n <input\n matInput\n [ngModel]=\"qf.label\"\n (ngModelChange)=\"updateQuickFilterMeta(idx, { label: $event })\"\n [attr.data-testid]=\"'table-quick-filter-label-' + idx\"\n />\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('filters.quickFilters.mode', 'Tipo de escopo') }}</mat-label>\n <mat-select\n [ngModel]=\"getQuickFilterMode(qf)\"\n (ngModelChange)=\"onQuickFilterModeChange(idx, $event)\"\n [attr.data-testid]=\"'table-quick-filter-mode-' + idx\"\n >\n <mat-option value=\"all\">{{ tx('filters.quickFilters.mode.all', 'Todos (limpar filtro)') }}</mat-option>\n <mat-option value=\"boolean\">{{ tx('filters.quickFilters.mode.boolean', 'Status booleano (Sim/N\u00E3o)') }}</mat-option>\n <mat-option value=\"equals\">{{ tx('filters.quickFilters.mode.equals', 'Campo igual a valor') }}</mat-option>\n </mat-select>\n </mat-form-field>\n\n @if (getQuickFilterMode(qf) !== 'all') {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('filters.quickFilters.field', 'Campo') }}</mat-label>\n <input\n matInput\n [ngModel]=\"getQuickFilterField(qf)\"\n (ngModelChange)=\"onQuickFilterFieldChange(idx, $event)\"\n [matAutocomplete]=\"quickFilterFieldPicker\"\n [attr.data-testid]=\"'table-quick-filter-field-' + idx\"\n />\n <mat-autocomplete\n #quickFilterFieldPicker=\"matAutocomplete\"\n (optionSelected)=\"onQuickFilterFieldChange(idx, $event.option.value)\"\n >\n @for (option of getFilteredQuickFilterFieldOptions(getQuickFilterField(qf)); track option.name) {\n <mat-option [value]=\"option.name\">\n <div class=\"table-rich-field-option\">\n <div class=\"table-rich-field-option__top\">\n <span class=\"table-rich-field-option__label\">{{ option.label && option.label !== option.name ? option.label : option.name }}</span>\n @if (option.label && option.label !== option.name) {\n <code class=\"table-rich-field-option__code\">{{ option.name }}</code>\n }\n @if (getFieldTypeBadge(option); as badge) {\n <span class=\"table-rich-field-option__badge\">{{ badge }}</span>\n }\n </div>\n @if (getFieldDescription(option); as desc) {\n <div class=\"table-rich-field-option__desc\">{{ desc }}</div>\n }\n </div>\n </mat-option>\n }\n </mat-autocomplete>\n </mat-form-field>\n\n @if (getQuickFilterMode(qf) === 'boolean') {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('filters.quickFilters.value', 'Valor') }}</mat-label>\n <mat-select\n [ngModel]=\"getQuickFilterValue(qf)\"\n (ngModelChange)=\"onQuickFilterValueChange(idx, $event)\"\n [attr.data-testid]=\"'table-quick-filter-bool-value-' + idx\"\n >\n <mat-option [value]=\"true\">{{ tx('common.boolean.true', 'Verdadeiro (true)') }}</mat-option>\n <mat-option [value]=\"false\">{{ tx('common.boolean.false', 'Falso (false)') }}</mat-option>\n </mat-select>\n </mat-form-field>\n } @else {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('filters.quickFilters.value', 'Valor') }}</mat-label>\n <input\n matInput\n [ngModel]=\"getQuickFilterValue(qf)\"\n (ngModelChange)=\"onQuickFilterValueChange(idx, $event)\"\n [attr.data-testid]=\"'table-quick-filter-value-' + idx\"\n />\n </mat-form-field>\n }\n }\n\n <button\n mat-stroked-button\n color=\"warn\"\n type=\"button\"\n [attr.data-testid]=\"'table-quick-filter-remove-' + idx\"\n (click)=\"removeQuickFilterScope(idx)\"\n >\n {{ tx('common.remove', 'Remover') }}\n </button>\n </div>\n }\n </div>\n }\n </section>\n\n <filter-settings\n #tableFilterSettingsRef\n [metadata]=\"getFilterSettingsMetas()\"\n [metadataSource]=\"isUsingFilterDtoMetas() ? 'filter-dto' : 'columns'\"\n [metadataLoading]=\"filterDtoMetasLoading\"\n [metadataErrorMsg]=\"filterDtoMetasErrorMsg\"\n [settings]=\"\n editedConfig.behavior?.filtering?.advancedFilters?.settings\n \"\n (settingsChange)=\"onFilterSettingsChange($event)\"\n ></filter-settings>\n </div>\n }\n @case ('messages') {\n <messages-localization-editor\n [config]=\"editedConfig\"\n (configChange)=\"onMessagesLocalizationConfigChange($event)\"\n (messagesLocalizationChange)=\"\n onMessagesLocalizationChange($event)\n \"\n ></messages-localization-editor>\n }\n @case ('dialogs') {\n <confirm-dialog-appearance-editor\n [config]=\"editedConfig\"\n (configChange)=\"onJsonConfigChange($event)\"\n ></confirm-dialog-appearance-editor>\n }\n @case ('json') {\n <json-config-editor\n [document]=\"buildJsonAuthoringDocument()\"\n (documentChange)=\"onJsonConfigChange($event)\"\n (validationChange)=\"onJsonValidationChange($event)\"\n (editorEvent)=\"onJsonEditorEvent($event)\"\n ></json-config-editor>\n }\n }\n </div>\n </mat-tab>\n }\n</mat-tab-group>\n@if (statusMessage) { <div class=\"config-editor-status\">\n <span\n class=\"status-text\"\n [class.error]=\"hasErrors\"\n [class.success]=\"hasSuccess\"\n >{{ statusMessage }}</span\n >\n</div> }\n", styles: ["@charset \"UTF-8\";:host{display:block;min-width:0;container:praxis-authoring-editor/inline-size}.editor-section-selector{display:block;padding:var(--pfx-spacing-2, 8px)}.editor-section-selector mat-form-field{width:100%}@container praxis-authoring-editor (max-width: 768px){.editor-section-selector{display:block}::ng-deep .config-tabs>.mat-mdc-tab-header{display:none}}.config-tabs{flex:1 1 auto;display:flex;flex-direction:column}.config-tabs .mat-mdc-tab{min-width:120px}.config-tabs .mat-mdc-tab-body-wrapper,.config-tabs .mat-mdc-tab-group-container{flex:1 1 auto;min-height:0}.config-tabs .mat-mdc-tab-body-content{height:100%;min-height:0;overflow:visible}.tab-content{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;padding:8px 8px 56px;box-sizing:border-box;overflow:visible}.overview-grid{display:grid;min-width:0;grid-template-columns:minmax(0,1fr);gap:12px}.overview-grid .overview-row{display:grid;grid-template-columns:280px 1fr;align-items:end;gap:12px}.overview-grid .overview-row .help.small{opacity:.75;font-size:12px}.overview-mode-alert{align-items:center}.connect-grid{display:grid;gap:12px;padding:8px;grid-template-columns:minmax(0,1fr);align-items:start}.connect-note{grid-column:1/-1;margin:0;font-size:12px;color:var(--md-sys-color-on-surface-variant)}.connect-alert{grid-column:1/-1;align-items:center}.connect-alert__text{flex:1}.connect-alert__meta{opacity:.85}.educational-card{margin-bottom:24px;background:var(--md-sys-color-surface-container-low);border-left:4px solid var(--md-sys-color-primary);flex-shrink:0}.config-editor-status{padding:12px 16px;margin-top:auto;border-top:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container);flex-shrink:0;position:relative}.status-text{font-size:.875rem;line-height:1.2}.status-text.error{color:var(--md-sys-color-error)}.status-text.success{color:var(--md-sys-color-primary)}@media(max-width:768px){.tab-content{padding:8px 8px 72px}.config-editor-status{padding:12px 16px}.connect-grid{grid-template-columns:1fr}.overview-grid .overview-row{grid-template-columns:1fr;align-items:start}:host{display:flex;flex-direction:column;flex:1 1 auto;min-height:0}}.responsive-settings{display:grid;grid-template-columns:minmax(0,1fr);gap:12px;min-width:0;padding:16px;border:1px solid var(--md-sys-color-outline-variant);border-radius:var(--md-sys-shape-corner-medium, 12px);background:var(--md-sys-color-surface-container-low)}.responsive-settings__heading{display:flex;align-items:center;gap:8px}.responsive-settings h3,.responsive-settings p{margin:0}.responsive-settings h3{font-size:1rem;font-weight:600}.responsive-settings p{font-size:.875rem;line-height:1.5}.responsive-settings__hint{color:var(--md-sys-color-on-surface-variant)}.responsive-settings__error{color:var(--md-sys-color-error)}.responsive-settings mat-form-field{width:100%;min-width:0}.filters-authoring-stack{display:grid;grid-template-columns:minmax(0,1fr);gap:16px}.toolbar-filters-toggles{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px}.quick-filters-header{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:8px}.quick-filters-actions{display:flex;flex-wrap:wrap;gap:8px}.quick-filters-list{display:grid;gap:10px}.quick-filter-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));align-items:center;gap:8px;padding:10px;border:1px solid var(--md-sys-color-outline-variant);border-radius:8px;background:var(--md-sys-color-surface)}.table-rich-field-option{display:flex;flex-direction:column;gap:2px;padding:2px 0;line-height:1.25}.table-rich-field-option__top{display:flex;align-items:center;gap:6px;flex-wrap:wrap}.table-rich-field-option__label{font-weight:500;font-size:13px;color:var(--md-sys-color-on-surface)}.table-rich-field-option__code{font-family:var(--praxis-font-mono, monospace);font-size:11px;padding:1px 5px;border-radius:4px;background:var(--md-sys-color-surface-container-highest);color:var(--md-sys-color-on-surface-variant)}.table-rich-field-option__badge{margin-left:auto;font-size:10px;font-weight:600;text-transform:uppercase;padding:1px 6px;border-radius:999px;background:var(--md-sys-color-secondary-container);color:var(--md-sys-color-on-secondary-container)}.table-rich-field-option__desc{font-size:11px;color:var(--md-sys-color-on-surface-variant);white-space:normal;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}\n"], dependencies: [{ kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i7.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "directive", type: i4$1.MatTabLabel, selector: "[mat-tab-label], [matTabLabel]" }, { kind: "component", type: i4$1.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i4$1.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatAutocompleteModule }, { kind: "component", type: i17$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: i4.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "directive", type: i17$1.MatAutocompleteTrigger, selector: "input[matAutocomplete], textarea[matAutocomplete]", inputs: ["matAutocomplete", "matAutocompletePosition", "matAutocompleteConnectedTo", "autocomplete", "matAutocompleteDisabled"], exportAs: ["matAutocompleteTrigger"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2$1.MatLabel, selector: "mat-label" }, { kind: "directive", type: i2$1.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3$1.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i4.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: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: ResourceQuickConnectComponent, selector: "praxis-resource-quick-connect", inputs: ["resourcePath", "endpointKey", "apiUrlEntry", "intent", "resources", "label", "placeholder", "hint", "disabled"], outputs: ["resourcePathChange", "resourceSelected"] }, { kind: "component", type: JsonConfigEditorComponent, selector: "json-config-editor", inputs: ["document"], outputs: ["documentChange", "validationChange", "editorEvent"] }, { kind: "component", type: ColumnsConfigEditorComponent, selector: "columns-config-editor", inputs: ["config", "readonly", "fieldMetadata"], outputs: ["configChange", "columnChange"] }, { kind: "component", type: BehaviorConfigEditorComponent, selector: "behavior-config-editor", inputs: ["config", "resourcePath", "forceClientStrategies"], outputs: ["configChange", "behaviorChange"] }, { kind: "component", type: HeaderAppearanceEditorComponent, selector: "header-appearance-editor", inputs: ["config"], outputs: ["configChange"] }, { kind: "component", type: ToolbarActionsEditorComponent, selector: "toolbar-actions-editor", inputs: ["config"], outputs: ["configChange", "validationChange", "toolbarActionsChange"] }, { kind: "component", type: MessagesLocalizationEditorComponent, selector: "messages-localization-editor", inputs: ["config"], outputs: ["configChange", "messagesLocalizationChange"] }, { kind: "component", type: FilterSettingsComponent, selector: "filter-settings", inputs: ["metadata", "metadataSource", "metadataLoading", "metadataErrorMsg", "settings", "configKey"], outputs: ["settingsChange"] }, { kind: "component", type: CrudIntegrationEditorComponent, selector: "crud-integration-editor", inputs: ["tableId", "crudContext", "componentKeyId"] }, { kind: "component", type: ConfirmDialogAppearanceEditorComponent, selector: "confirm-dialog-appearance-editor", inputs: ["config"], outputs: ["configChange"] }, { kind: "component", type: TableRulesEditorComponent, selector: "table-rules-editor", inputs: ["config", "resourcePath", "fields", "i18nRules", "debugLogs", "debugLevel"], outputs: ["configChange"] }, { kind: "component", type: TableDetailEditorComponent, selector: "praxis-table-detail-editor", inputs: ["config"], outputs: ["configChange", "draftStateChange"] }] });
|
|
74816
74943
|
}
|
|
74817
74944
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisTableConfigEditor, decorators: [{
|
|
74818
74945
|
type: Component,
|
|
@@ -74841,7 +74968,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
74841
74968
|
], providers: [
|
|
74842
74969
|
TableConfigService,
|
|
74843
74970
|
providePraxisI18nConfig(PRAXIS_TABLE_EDITOR_I18N_CONFIG),
|
|
74844
|
-
], template: "<div class=\"editor-section-selector\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('sections.selector', 'Editor section') }}</mat-label>\n <mat-select [(ngModel)]=\"activeSectionIndex\" data-testid=\"table-config-editor-sections\">\n @for (section of sections; track section.id; let index = $index) {\n <mat-option [value]=\"index\">{{ section.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n</div>\n<mat-tab-group [preserveContent]=\"true\" class=\"config-tabs\" [(selectedIndex)]=\"activeSectionIndex\">\n @for (section of sections; track section.id) {\n <mat-tab>\n <ng-template mat-tab-label>\n @if (section.icon) { <mat-icon [praxisIcon]=\"section.icon\"></mat-icon> }\n <span [attr.data-testid]=\"'config-tab-' + section.id\">{{ section.label }}</span>\n </ng-template>\n <div class=\"tab-content\">\n @switch (section.id) {\n @case ('connect') {\n <div class=\"connect-grid\">\n <praxis-resource-quick-connect\n [resourcePath]=\"resourcePath\"\n [endpointKey]=\"resourceEndpointKey\"\n [apiUrlEntry]=\"resourceApiUrlEntry\"\n (resourcePathChange)=\"onResourcePathChange($event)\"\n intent=\"collection\"\n [label]=\"tx('connect.resource', 'Resource')\"\n [placeholder]=\"tx('connect.resource.placeholder', 'e.g. employees')\"\n />\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('connect.primaryKey', 'Primary key') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"idField\"\n (ngModelChange)=\"onIdFieldChange($event)\"\n [matAutocomplete]=\"idFieldPicker\"\n [placeholder]=\"tx('connect.primaryKey.placeholder', 'e.g. id, uuid, code')\"\n />\n <mat-icon matSuffix>fingerprint</mat-icon>\n <mat-autocomplete\n #idFieldPicker=\"matAutocomplete\"\n (optionSelected)=\"onIdFieldOptionSelected($event.option.value)\"\n >\n @for (option of filteredIdFieldOptions; track option.name) {\n <mat-option [value]=\"option.name\">\n <span>{{ option.name }}</span>\n @if (option.label && option.label !== option.name) {\n <small> - {{ option.label }}</small>\n }\n </mat-option>\n }\n @if (filteredIdFieldOptions.length === 0) {\n <mat-option disabled>\n {{ tx('connect.primaryKey.empty', 'No matching field found.') }}\n </mat-option>\n }\n </mat-autocomplete>\n </mat-form-field>\n <p class=\"connect-note\">{{ tx('connect.note', 'Define the API resource and, when needed, the primary key.') }}</p>\n <!-- Diverg\u00EAncia: idField -->\n @if (idFieldDiverges) { <div class=\"alert alert--warning connect-alert\">\n <mat-icon class=\"alert__icon\">warning</mat-icon>\n <div class=\"alert__text connect-alert__text\">\n {{ tx('connect.serverPrimaryKeyDiverges', 'The primary key on the server differs from the current configuration.') }}\n <span class=\"connect-alert__meta\">(config: {{ idField || 'id' }} | servidor: {{ serverIdField || 'id' }})</span>\n </div>\n <button mat-stroked-button color=\"primary\" type=\"button\" (click)=\"onReconcileIdField()\">{{ tx('connect.reconcile', 'Reconcile') }}</button>\n </div> }\n <!-- Diverg\u00EAncia: serverHash -->\n @if (schemaHashDiverges) { <div class=\"alert alert--info connect-alert\">\n <mat-icon class=\"alert__icon\">info</mat-icon>\n <div class=\"alert__text connect-alert__text\">\n {{ tx('connect.schemaSnapshotDiverges', 'The saved schema snapshot differs from the current server schema.') }}\n </div>\n <button mat-stroked-button color=\"primary\" type=\"button\" (click)=\"onAcceptServerHash()\">{{ tx('connect.updateMetadata', 'Update metadata') }}</button>\n </div> }\n @if (!schemaHashDiverges && schemaSnapshotMissing) { <div class=\"alert alert--info connect-alert\">\n <mat-icon class=\"alert__icon\">info</mat-icon>\n <div class=\"alert__text connect-alert__text\">\n {{ tx('connect.schemaSnapshotMissing', 'A current schema exists on the server, but there is still no persisted snapshot for reconciliation.') }}\n </div>\n <button mat-stroked-button color=\"primary\" type=\"button\" (click)=\"onAcceptServerHash()\">{{ tx('connect.saveSnapshot', 'Save snapshot') }}</button>\n </div> }\n </div>\n }\n @case ('overview') {\n <div class=\"overview-grid\">\n <section class=\"responsive-settings\" aria-labelledby=\"table-responsive-title\">\n <div class=\"responsive-settings__heading\">\n <mat-icon aria-hidden=\"true\">devices</mat-icon>\n <h3 id=\"table-responsive-title\">{{ tx('overview.responsive.title', 'Narrow screens') }}</h3>\n </div>\n <p>{{ tx('overview.responsive.description', 'Choose how records appear when the table has less space.') }}</p>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('overview.responsive.mode', 'Record layout') }}</mat-label>\n <mat-select [ngModel]=\"mobileCardMode\" (ngModelChange)=\"onMobileCardModeChange($event)\">\n <mat-option [value]=\"false\">{{ tx('overview.responsive.table', 'Table') }}</mat-option>\n <mat-option [value]=\"true\">{{ tx('overview.responsive.cards', 'Cards') }}</mat-option>\n </mat-select>\n </mat-form-field>\n @if (mobileCardMode) {\n <p class=\"responsive-settings__hint\">{{ tx('overview.responsive.cardsHint', 'Each record becomes a card with its visible columns. Selection and row actions remain available. Wider areas keep the table layout.') }}</p>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('overview.responsive.breakpoint', 'Show cards at widths up to') }}</mat-label>\n <input matInput type=\"number\" min=\"1\" step=\"1\" required\n [ngModel]=\"mobileBreakpoint\" (ngModelChange)=\"onMobileBreakpointChange($event)\"\n [attr.aria-invalid]=\"mobileBreakpointInvalid\"\n aria-describedby=\"table-responsive-width-help\" />\n <span matTextSuffix>px</span>\n </mat-form-field>\n @if (mobileBreakpointInvalid) {\n <p class=\"responsive-settings__error\" role=\"alert\">{{ tx('overview.responsive.invalidWidth', 'Enter a positive whole number.') }}</p>\n }\n <p id=\"table-responsive-width-help\" class=\"responsive-settings__hint\">{{ tx('overview.responsive.widthHint', 'Uses the smaller width of the screen and the table area. Default: 768 px.') }}</p>\n <p class=\"responsive-settings__hint\">{{ tx('overview.responsive.virtualizationHint', 'Cards require standard rows. With virtual scrolling enabled, records remain in a table.') }}</p>\n }\n </section>\n <div class=\"overview-row\">\n <mat-form-field appearance=\"outline\" class=\"hs-field\">\n <mat-label>{{ tx('overview.horizontalScroll', 'Horizontal scroll') }}</mat-label>\n <mat-select [(ngModel)]=\"horizontalScroll\" (ngModelChange)=\"onHorizontalScrollChange($event)\">\n <mat-option value=\"auto\">{{ tx('overview.horizontalScroll.auto', 'Auto (default)') }}</mat-option>\n <mat-option value=\"wrap\">{{ tx('overview.horizontalScroll.wrap', 'Wrap') }}</mat-option>\n <mat-option value=\"none\">{{ tx('overview.horizontalScroll.none', 'Host controls') }}</mat-option>\n </mat-select>\n </mat-form-field>\n <div class=\"help small\">{{ tx('overview.horizontalScroll.help', 'Controls how the table handles horizontal width and scrollbar behavior.') }}</div>\n </div>\n @if (isEffectiveLocalMode()) { <div class=\"alert alert--info overview-mode-alert\">\n <mat-icon class=\"alert__icon\">info</mat-icon>\n <div class=\"alert__text\">\n {{ tx('overview.localModeActive', 'Effective local mode is active: server pagination and sorting strategies are blocked and persisted as client mode.') }}\n </div>\n </div> }\n <behavior-config-editor\n [config]=\"editedConfig\"\n [resourcePath]=\"resourcePath\"\n [forceClientStrategies]=\"isEffectiveLocalMode()\"\n (configChange)=\"onBehaviorConfigChange($event)\"\n (behaviorChange)=\"onBehaviorChange($event)\"\n ></behavior-config-editor>\n </div>\n }\n @case ('columns') {\n <columns-config-editor\n [config]=\"editedConfig\"\n [fieldMetadata]=\"getColumnAuthoringMetas()\"\n (configChange)=\"onColumnsConfigChange($event)\"\n (columnChange)=\"onColumnChange($event)\"\n ></columns-config-editor>\n }\n @case ('detail') {\n <praxis-table-detail-editor [config]=\"editedConfig\"\n (configChange)=\"onDetailConfigChange($event)\"\n (draftStateChange)=\"onDetailDraftStateChange($event)\"\n ></praxis-table-detail-editor>\n }\n @case ('rules') {\n <table-rules-editor\n [config]=\"editedConfig\"\n [resourcePath]=\"resourcePath\"\n [fields]=\"resourceSchemaFields\"\n (configChange)=\"onRulesConfigChange($event)\"\n ></table-rules-editor>\n }\n @case ('header') {\n <header-appearance-editor\n [config]=\"editedConfig\"\n (configChange)=\"onColumnsConfigChange($event)\"\n ></header-appearance-editor>\n }\n @case ('toolbar') {\n <toolbar-actions-editor\n [config]=\"editedConfig\"\n (configChange)=\"onToolbarActionsConfigChange($event)\"\n (toolbarActionsChange)=\"onToolbarActionsChange($event)\"\n (validationChange)=\"onSurfaceDraftValidationChange($event)\"\n ></toolbar-actions-editor>\n }\n <!-- Aba extra para integra\u00E7\u00F5es CRUD (vis\u00EDvel quando em contexto CRUD) -->\n @case ('crud') {\n <crud-integration-editor\n #crudEditorRef\n [tableId]=\"crudContext?.tableId || 'default'\"\n [crudContext]=\"crudContext\"\n [componentKeyId]=\"crudContext?.componentKeyId\"\n ></crud-integration-editor>\n }\n @case ('filters') {\n <div class=\"filters-authoring-stack\">\n <section\n class=\"responsive-settings toolbar-filters-region\"\n data-testid=\"table-unified-filter-region-section\"\n aria-labelledby=\"table-unified-filter-region-title\"\n >\n <div class=\"responsive-settings__heading\">\n <mat-icon aria-hidden=\"true\">filter_list</mat-icon>\n <h3 id=\"table-unified-filter-region-title\">\n {{ tx('filters.region.title', 'Regi\u00E3o \u00FAnica de filtros e escopos da toolbar') }}\n </h3>\n </div>\n <p class=\"responsive-settings__hint\">\n {{\n tx(\n 'filters.region.description',\n 'Centralize escopos r\u00E1pidos (Todos / Ativos / Inativos), busca e gaveta avan\u00E7ada na toolbar da tabela sem controles duplicados.'\n )\n }}\n </p>\n\n <div class=\"toolbar-filters-toggles\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('filters.region.enabled', 'Regi\u00E3o de filtros na toolbar') }}</mat-label>\n <mat-select\n [ngModel]=\"toolbarFiltersEnabled\"\n (ngModelChange)=\"onToolbarFiltersEnabledChange($event)\"\n data-testid=\"table-toolbar-filters-enabled-select\"\n >\n <mat-option [value]=\"true\">{{ tx('common.enabled', 'Ativada') }}</mat-option>\n <mat-option [value]=\"false\">{{ tx('common.disabled', 'Desativada') }}</mat-option>\n </mat-select>\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('filters.region.showQuickFilters', 'Exibir escopos r\u00E1pidos') }}</mat-label>\n <mat-select\n [ngModel]=\"toolbarShowQuickFilters\"\n (ngModelChange)=\"onToolbarShowQuickFiltersChange($event)\"\n data-testid=\"table-toolbar-show-quick-filters-select\"\n >\n <mat-option [value]=\"true\">{{ tx('common.yes', 'Sim') }}</mat-option>\n <mat-option [value]=\"false\">{{ tx('common.no', 'N\u00E3o') }}</mat-option>\n </mat-select>\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('filters.region.showAdvancedButton', 'Bot\u00E3o de gaveta avan\u00E7ada') }}</mat-label>\n <mat-select\n [ngModel]=\"toolbarShowAdvancedButton\"\n (ngModelChange)=\"onToolbarShowAdvancedButtonChange($event)\"\n data-testid=\"table-toolbar-show-advanced-button-select\"\n >\n <mat-option [value]=\"true\">{{ tx('common.yes', 'Sim') }}</mat-option>\n <mat-option [value]=\"false\">{{ tx('common.no', 'N\u00E3o') }}</mat-option>\n </mat-select>\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('filters.region.showAddButton', 'Bot\u00E3o \"Gerenciar campos\"') }}</mat-label>\n <mat-select\n [ngModel]=\"advancedFilterShowAddButton\"\n (ngModelChange)=\"onAdvancedFilterShowAddButtonChange($event)\"\n data-testid=\"table-toolbar-show-add-button-select\"\n >\n <mat-option [value]=\"true\">{{ tx('common.yes', 'Sim') }}</mat-option>\n <mat-option [value]=\"false\">{{ tx('common.no', 'N\u00E3o') }}</mat-option>\n </mat-select>\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('filters.region.showFilterSettings', 'Bot\u00E3o \"Configura\u00E7\u00F5es do filtro\"') }}</mat-label>\n <mat-select\n [ngModel]=\"advancedFilterShowFilterSettings\"\n (ngModelChange)=\"onAdvancedFilterShowFilterSettingsChange($event)\"\n data-testid=\"table-toolbar-show-filter-settings-select\"\n >\n <mat-option [value]=\"true\">{{ tx('common.yes', 'Sim') }}</mat-option>\n <mat-option [value]=\"false\">{{ tx('common.no', 'N\u00E3o') }}</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n\n @if (duplicatedQuickFilterInlineFields.length > 0) {\n <div\n class=\"alert alert--warning connect-alert\"\n data-testid=\"table-filter-duplicate-scope-warning\"\n >\n <mat-icon class=\"alert__icon\">warning</mat-icon>\n <div class=\"alert__text connect-alert__text\">\n {{\n tx(\n 'filters.region.duplicateWarning',\n 'Os campos abaixo j\u00E1 possuem escopos r\u00E1pidos na toolbar e tamb\u00E9m est\u00E3o nos filtros inline:'\n )\n }}\n <strong>{{ duplicatedQuickFilterInlineFields.join(', ') }}</strong>\n </div>\n <button\n mat-stroked-button\n color=\"primary\"\n type=\"button\"\n data-testid=\"table-filter-consolidate-duplicates-btn\"\n (click)=\"consolidateDuplicateInlineFilterFields()\"\n >\n {{ tx('filters.region.consolidateBtn', 'Consolidar nos escopos r\u00E1pidos') }}\n </button>\n </div>\n }\n\n <div class=\"quick-filters-header\">\n <strong>{{ tx('filters.quickFilters.heading', 'Escopos r\u00E1pidos (quickFilters)') }}</strong>\n <div class=\"quick-filters-actions\">\n <button\n mat-stroked-button\n type=\"button\"\n data-testid=\"table-quick-filters-preset-boolean-btn\"\n (click)=\"applyBooleanQuickFilterPreset()\"\n >\n {{ tx('filters.quickFilters.presetBoolean', 'Preset Todos / Ativos / Inativos') }}\n </button>\n <button\n mat-stroked-button\n color=\"primary\"\n type=\"button\"\n data-testid=\"table-quick-filters-add-btn\"\n (click)=\"addQuickFilterScope()\"\n >\n {{ tx('filters.quickFilters.add', 'Adicionar escopo') }}\n </button>\n </div>\n </div>\n\n @if (toolbarQuickFilters.length > 0) {\n <div class=\"quick-filters-list\" data-testid=\"table-quick-filters-list\">\n @for (qf of toolbarQuickFilters; track $index; let idx = $index) {\n <div class=\"quick-filter-row\" [attr.data-testid]=\"'table-quick-filter-row-' + idx\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('filters.quickFilters.id', 'ID') }}</mat-label>\n <input\n matInput\n [ngModel]=\"qf.id\"\n (ngModelChange)=\"updateQuickFilterMeta(idx, { id: $event })\"\n [attr.data-testid]=\"'table-quick-filter-id-' + idx\"\n />\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('filters.quickFilters.label', 'R\u00F3tulo') }}</mat-label>\n <input\n matInput\n [ngModel]=\"qf.label\"\n (ngModelChange)=\"updateQuickFilterMeta(idx, { label: $event })\"\n [attr.data-testid]=\"'table-quick-filter-label-' + idx\"\n />\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('filters.quickFilters.mode', 'Tipo de escopo') }}</mat-label>\n <mat-select\n [ngModel]=\"getQuickFilterMode(qf)\"\n (ngModelChange)=\"onQuickFilterModeChange(idx, $event)\"\n [attr.data-testid]=\"'table-quick-filter-mode-' + idx\"\n >\n <mat-option value=\"all\">{{ tx('filters.quickFilters.mode.all', 'Todos (limpar filtro)') }}</mat-option>\n <mat-option value=\"boolean\">{{ tx('filters.quickFilters.mode.boolean', 'Status booleano (Sim/N\u00E3o)') }}</mat-option>\n <mat-option value=\"equals\">{{ tx('filters.quickFilters.mode.equals', 'Campo igual a valor') }}</mat-option>\n </mat-select>\n </mat-form-field>\n\n @if (getQuickFilterMode(qf) !== 'all') {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('filters.quickFilters.field', 'Campo') }}</mat-label>\n <input\n matInput\n [ngModel]=\"getQuickFilterField(qf)\"\n (ngModelChange)=\"onQuickFilterFieldChange(idx, $event)\"\n [matAutocomplete]=\"quickFilterFieldPicker\"\n [attr.data-testid]=\"'table-quick-filter-field-' + idx\"\n />\n <mat-autocomplete\n #quickFilterFieldPicker=\"matAutocomplete\"\n (optionSelected)=\"onQuickFilterFieldChange(idx, $event.option.value)\"\n >\n @for (option of getFilteredQuickFilterFieldOptions(getQuickFilterField(qf)); track option.name) {\n <mat-option [value]=\"option.name\">\n <span>{{ option.name }}</span>\n @if (option.label && option.label !== option.name) {\n <small> - {{ option.label }}</small>\n }\n </mat-option>\n }\n </mat-autocomplete>\n </mat-form-field>\n\n @if (getQuickFilterMode(qf) === 'boolean') {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('filters.quickFilters.value', 'Valor') }}</mat-label>\n <mat-select\n [ngModel]=\"getQuickFilterValue(qf)\"\n (ngModelChange)=\"onQuickFilterValueChange(idx, $event)\"\n [attr.data-testid]=\"'table-quick-filter-bool-value-' + idx\"\n >\n <mat-option [value]=\"true\">{{ tx('common.boolean.true', 'Verdadeiro (true)') }}</mat-option>\n <mat-option [value]=\"false\">{{ tx('common.boolean.false', 'Falso (false)') }}</mat-option>\n </mat-select>\n </mat-form-field>\n } @else {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('filters.quickFilters.value', 'Valor') }}</mat-label>\n <input\n matInput\n [ngModel]=\"getQuickFilterValue(qf)\"\n (ngModelChange)=\"onQuickFilterValueChange(idx, $event)\"\n [attr.data-testid]=\"'table-quick-filter-value-' + idx\"\n />\n </mat-form-field>\n }\n }\n\n <button\n mat-stroked-button\n color=\"warn\"\n type=\"button\"\n [attr.data-testid]=\"'table-quick-filter-remove-' + idx\"\n (click)=\"removeQuickFilterScope(idx)\"\n >\n {{ tx('common.remove', 'Remover') }}\n </button>\n </div>\n }\n </div>\n }\n </section>\n\n <filter-settings\n #tableFilterSettingsRef\n [metadata]=\"getFilterSettingsMetas()\"\n [metadataSource]=\"isUsingFilterDtoMetas() ? 'filter-dto' : 'columns'\"\n [metadataLoading]=\"filterDtoMetasLoading\"\n [metadataErrorMsg]=\"filterDtoMetasErrorMsg\"\n [settings]=\"\n editedConfig.behavior?.filtering?.advancedFilters?.settings\n \"\n (settingsChange)=\"onFilterSettingsChange($event)\"\n ></filter-settings>\n </div>\n }\n @case ('messages') {\n <messages-localization-editor\n [config]=\"editedConfig\"\n (configChange)=\"onMessagesLocalizationConfigChange($event)\"\n (messagesLocalizationChange)=\"\n onMessagesLocalizationChange($event)\n \"\n ></messages-localization-editor>\n }\n @case ('dialogs') {\n <confirm-dialog-appearance-editor\n [config]=\"editedConfig\"\n (configChange)=\"onJsonConfigChange($event)\"\n ></confirm-dialog-appearance-editor>\n }\n @case ('json') {\n <json-config-editor\n [document]=\"buildJsonAuthoringDocument()\"\n (documentChange)=\"onJsonConfigChange($event)\"\n (validationChange)=\"onJsonValidationChange($event)\"\n (editorEvent)=\"onJsonEditorEvent($event)\"\n ></json-config-editor>\n }\n }\n </div>\n </mat-tab>\n }\n</mat-tab-group>\n@if (statusMessage) { <div class=\"config-editor-status\">\n <span\n class=\"status-text\"\n [class.error]=\"hasErrors\"\n [class.success]=\"hasSuccess\"\n >{{ statusMessage }}</span\n >\n</div> }\n", styles: ["@charset \"UTF-8\";:host{display:block;min-width:0;container:praxis-authoring-editor/inline-size}.editor-section-selector{display:block;padding:var(--pfx-spacing-2, 8px)}.editor-section-selector mat-form-field{width:100%}@container praxis-authoring-editor (max-width: 768px){.editor-section-selector{display:block}::ng-deep .config-tabs>.mat-mdc-tab-header{display:none}}.config-tabs{flex:1 1 auto;display:flex;flex-direction:column}.config-tabs .mat-mdc-tab{min-width:120px}.config-tabs .mat-mdc-tab-body-wrapper,.config-tabs .mat-mdc-tab-group-container{flex:1 1 auto;min-height:0}.config-tabs .mat-mdc-tab-body-content{height:100%;min-height:0;overflow:visible}.tab-content{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;padding:8px 8px 56px;box-sizing:border-box;overflow:visible}.overview-grid{display:grid;min-width:0;grid-template-columns:minmax(0,1fr);gap:12px}.overview-grid .overview-row{display:grid;grid-template-columns:280px 1fr;align-items:end;gap:12px}.overview-grid .overview-row .help.small{opacity:.75;font-size:12px}.overview-mode-alert{align-items:center}.connect-grid{display:grid;gap:12px;padding:8px;grid-template-columns:minmax(0,1fr);align-items:start}.connect-note{grid-column:1/-1;margin:0;font-size:12px;color:var(--md-sys-color-on-surface-variant)}.connect-alert{grid-column:1/-1;align-items:center}.connect-alert__text{flex:1}.connect-alert__meta{opacity:.85}.educational-card{margin-bottom:24px;background:var(--md-sys-color-surface-container-low);border-left:4px solid var(--md-sys-color-primary);flex-shrink:0}.config-editor-status{padding:12px 16px;margin-top:auto;border-top:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container);flex-shrink:0;position:relative}.status-text{font-size:.875rem;line-height:1.2}.status-text.error{color:var(--md-sys-color-error)}.status-text.success{color:var(--md-sys-color-primary)}@media(max-width:768px){.tab-content{padding:8px 8px 72px}.config-editor-status{padding:12px 16px}.connect-grid{grid-template-columns:1fr}.overview-grid .overview-row{grid-template-columns:1fr;align-items:start}:host{display:flex;flex-direction:column;flex:1 1 auto;min-height:0}}.responsive-settings{display:grid;grid-template-columns:minmax(0,1fr);gap:12px;min-width:0;padding:16px;border:1px solid var(--md-sys-color-outline-variant);border-radius:var(--md-sys-shape-corner-medium, 12px);background:var(--md-sys-color-surface-container-low)}.responsive-settings__heading{display:flex;align-items:center;gap:8px}.responsive-settings h3,.responsive-settings p{margin:0}.responsive-settings h3{font-size:1rem;font-weight:600}.responsive-settings p{font-size:.875rem;line-height:1.5}.responsive-settings__hint{color:var(--md-sys-color-on-surface-variant)}.responsive-settings__error{color:var(--md-sys-color-error)}.responsive-settings mat-form-field{width:100%;min-width:0}.filters-authoring-stack{display:grid;grid-template-columns:minmax(0,1fr);gap:16px}.toolbar-filters-toggles{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px}.quick-filters-header{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:8px}.quick-filters-actions{display:flex;flex-wrap:wrap;gap:8px}.quick-filters-list{display:grid;gap:10px}.quick-filter-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));align-items:center;gap:8px;padding:10px;border:1px solid var(--md-sys-color-outline-variant);border-radius:8px;background:var(--md-sys-color-surface)}\n"] }]
|
|
74971
|
+
], template: "<div class=\"editor-section-selector\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('sections.selector', 'Editor section') }}</mat-label>\n <mat-select [(ngModel)]=\"activeSectionIndex\" data-testid=\"table-config-editor-sections\">\n @for (section of sections; track section.id; let index = $index) {\n <mat-option [value]=\"index\">{{ section.label }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n</div>\n<mat-tab-group [preserveContent]=\"true\" class=\"config-tabs\" [(selectedIndex)]=\"activeSectionIndex\">\n @for (section of sections; track section.id) {\n <mat-tab>\n <ng-template mat-tab-label>\n @if (section.icon) { <mat-icon [praxisIcon]=\"section.icon\"></mat-icon> }\n <span [attr.data-testid]=\"'config-tab-' + section.id\">{{ section.label }}</span>\n </ng-template>\n <div class=\"tab-content\">\n @switch (section.id) {\n @case ('connect') {\n <div class=\"connect-grid\">\n <praxis-resource-quick-connect\n [resourcePath]=\"resourcePath\"\n [endpointKey]=\"resourceEndpointKey\"\n [apiUrlEntry]=\"resourceApiUrlEntry\"\n (resourcePathChange)=\"onResourcePathChange($event)\"\n intent=\"collection\"\n [label]=\"tx('connect.resource', 'Resource')\"\n [placeholder]=\"tx('connect.resource.placeholder', 'e.g. employees')\"\n />\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('connect.primaryKey', 'Primary key') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"idField\"\n (ngModelChange)=\"onIdFieldChange($event)\"\n [matAutocomplete]=\"idFieldPicker\"\n [placeholder]=\"tx('connect.primaryKey.placeholder', 'e.g. id, uuid, code')\"\n />\n <mat-icon matSuffix>fingerprint</mat-icon>\n <mat-autocomplete\n #idFieldPicker=\"matAutocomplete\"\n (optionSelected)=\"onIdFieldOptionSelected($event.option.value)\"\n >\n @for (option of filteredIdFieldOptions; track option.name) {\n <mat-option [value]=\"option.name\">\n <div class=\"table-rich-field-option\">\n <div class=\"table-rich-field-option__top\">\n <span class=\"table-rich-field-option__label\">{{ option.label && option.label !== option.name ? option.label : option.name }}</span>\n @if (option.label && option.label !== option.name) {\n <code class=\"table-rich-field-option__code\">{{ option.name }}</code>\n }\n @if (getFieldTypeBadge(option); as badge) {\n <span class=\"table-rich-field-option__badge\">{{ badge }}</span>\n }\n </div>\n @if (getFieldDescription(option); as desc) {\n <div class=\"table-rich-field-option__desc\">{{ desc }}</div>\n }\n </div>\n </mat-option>\n }\n @if (filteredIdFieldOptions.length === 0) {\n <mat-option disabled>\n {{ tx('connect.primaryKey.empty', 'No matching field found.') }}\n </mat-option>\n }\n </mat-autocomplete>\n </mat-form-field>\n <p class=\"connect-note\">{{ tx('connect.note', 'Define the API resource and, when needed, the primary key.') }}</p>\n <!-- Diverg\u00EAncia: idField -->\n @if (idFieldDiverges) { <div class=\"alert alert--warning connect-alert\">\n <mat-icon class=\"alert__icon\">warning</mat-icon>\n <div class=\"alert__text connect-alert__text\">\n {{ tx('connect.serverPrimaryKeyDiverges', 'The primary key on the server differs from the current configuration.') }}\n <span class=\"connect-alert__meta\">(config: {{ idField || 'id' }} | servidor: {{ serverIdField || 'id' }})</span>\n </div>\n <button mat-stroked-button color=\"primary\" type=\"button\" (click)=\"onReconcileIdField()\">{{ tx('connect.reconcile', 'Reconcile') }}</button>\n </div> }\n <!-- Diverg\u00EAncia: serverHash -->\n @if (schemaHashDiverges) { <div class=\"alert alert--info connect-alert\">\n <mat-icon class=\"alert__icon\">info</mat-icon>\n <div class=\"alert__text connect-alert__text\">\n {{ tx('connect.schemaSnapshotDiverges', 'The saved schema snapshot differs from the current server schema.') }}\n </div>\n <button mat-stroked-button color=\"primary\" type=\"button\" (click)=\"onAcceptServerHash()\">{{ tx('connect.updateMetadata', 'Update metadata') }}</button>\n </div> }\n @if (!schemaHashDiverges && schemaSnapshotMissing) { <div class=\"alert alert--info connect-alert\">\n <mat-icon class=\"alert__icon\">info</mat-icon>\n <div class=\"alert__text connect-alert__text\">\n {{ tx('connect.schemaSnapshotMissing', 'A current schema exists on the server, but there is still no persisted snapshot for reconciliation.') }}\n </div>\n <button mat-stroked-button color=\"primary\" type=\"button\" (click)=\"onAcceptServerHash()\">{{ tx('connect.saveSnapshot', 'Save snapshot') }}</button>\n </div> }\n </div>\n }\n @case ('overview') {\n <div class=\"overview-grid\">\n <section class=\"responsive-settings\" aria-labelledby=\"table-responsive-title\">\n <div class=\"responsive-settings__heading\">\n <mat-icon aria-hidden=\"true\">devices</mat-icon>\n <h3 id=\"table-responsive-title\">{{ tx('overview.responsive.title', 'Narrow screens') }}</h3>\n </div>\n <p>{{ tx('overview.responsive.description', 'Choose how records appear when the table has less space.') }}</p>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('overview.responsive.mode', 'Record layout') }}</mat-label>\n <mat-select [ngModel]=\"mobileCardMode\" (ngModelChange)=\"onMobileCardModeChange($event)\">\n <mat-option [value]=\"false\">{{ tx('overview.responsive.table', 'Table') }}</mat-option>\n <mat-option [value]=\"true\">{{ tx('overview.responsive.cards', 'Cards') }}</mat-option>\n </mat-select>\n </mat-form-field>\n @if (mobileCardMode) {\n <p class=\"responsive-settings__hint\">{{ tx('overview.responsive.cardsHint', 'Each record becomes a card with its visible columns. Selection and row actions remain available. Wider areas keep the table layout.') }}</p>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('overview.responsive.breakpoint', 'Show cards at widths up to') }}</mat-label>\n <input matInput type=\"number\" min=\"1\" step=\"1\" required\n [ngModel]=\"mobileBreakpoint\" (ngModelChange)=\"onMobileBreakpointChange($event)\"\n [attr.aria-invalid]=\"mobileBreakpointInvalid\"\n aria-describedby=\"table-responsive-width-help\" />\n <span matTextSuffix>px</span>\n </mat-form-field>\n @if (mobileBreakpointInvalid) {\n <p class=\"responsive-settings__error\" role=\"alert\">{{ tx('overview.responsive.invalidWidth', 'Enter a positive whole number.') }}</p>\n }\n <p id=\"table-responsive-width-help\" class=\"responsive-settings__hint\">{{ tx('overview.responsive.widthHint', 'Uses the smaller width of the screen and the table area. Default: 768 px.') }}</p>\n <p class=\"responsive-settings__hint\">{{ tx('overview.responsive.virtualizationHint', 'Cards require standard rows. With virtual scrolling enabled, records remain in a table.') }}</p>\n }\n </section>\n <div class=\"overview-row\">\n <mat-form-field appearance=\"outline\" class=\"hs-field\">\n <mat-label>{{ tx('overview.horizontalScroll', 'Horizontal scroll') }}</mat-label>\n <mat-select [(ngModel)]=\"horizontalScroll\" (ngModelChange)=\"onHorizontalScrollChange($event)\">\n <mat-option value=\"auto\">{{ tx('overview.horizontalScroll.auto', 'Auto (default)') }}</mat-option>\n <mat-option value=\"wrap\">{{ tx('overview.horizontalScroll.wrap', 'Wrap') }}</mat-option>\n <mat-option value=\"none\">{{ tx('overview.horizontalScroll.none', 'Host controls') }}</mat-option>\n </mat-select>\n </mat-form-field>\n <div class=\"help small\">{{ tx('overview.horizontalScroll.help', 'Controls how the table handles horizontal width and scrollbar behavior.') }}</div>\n </div>\n @if (isEffectiveLocalMode()) { <div class=\"alert alert--info overview-mode-alert\">\n <mat-icon class=\"alert__icon\">info</mat-icon>\n <div class=\"alert__text\">\n {{ tx('overview.localModeActive', 'Effective local mode is active: server pagination and sorting strategies are blocked and persisted as client mode.') }}\n </div>\n </div> }\n <behavior-config-editor\n [config]=\"editedConfig\"\n [resourcePath]=\"resourcePath\"\n [forceClientStrategies]=\"isEffectiveLocalMode()\"\n (configChange)=\"onBehaviorConfigChange($event)\"\n (behaviorChange)=\"onBehaviorChange($event)\"\n ></behavior-config-editor>\n </div>\n }\n @case ('columns') {\n <columns-config-editor\n [config]=\"editedConfig\"\n [fieldMetadata]=\"getColumnAuthoringMetas()\"\n (configChange)=\"onColumnsConfigChange($event)\"\n (columnChange)=\"onColumnChange($event)\"\n ></columns-config-editor>\n }\n @case ('detail') {\n <praxis-table-detail-editor [config]=\"editedConfig\"\n (configChange)=\"onDetailConfigChange($event)\"\n (draftStateChange)=\"onDetailDraftStateChange($event)\"\n ></praxis-table-detail-editor>\n }\n @case ('rules') {\n <table-rules-editor\n [config]=\"editedConfig\"\n [resourcePath]=\"resourcePath\"\n [fields]=\"resourceSchemaFields\"\n (configChange)=\"onRulesConfigChange($event)\"\n ></table-rules-editor>\n }\n @case ('header') {\n <header-appearance-editor\n [config]=\"editedConfig\"\n (configChange)=\"onColumnsConfigChange($event)\"\n ></header-appearance-editor>\n }\n @case ('toolbar') {\n <toolbar-actions-editor\n [config]=\"editedConfig\"\n (configChange)=\"onToolbarActionsConfigChange($event)\"\n (toolbarActionsChange)=\"onToolbarActionsChange($event)\"\n (validationChange)=\"onSurfaceDraftValidationChange($event)\"\n ></toolbar-actions-editor>\n }\n <!-- Aba extra para integra\u00E7\u00F5es CRUD (vis\u00EDvel quando em contexto CRUD) -->\n @case ('crud') {\n <crud-integration-editor\n #crudEditorRef\n [tableId]=\"crudContext?.tableId || 'default'\"\n [crudContext]=\"crudContext\"\n [componentKeyId]=\"crudContext?.componentKeyId\"\n ></crud-integration-editor>\n }\n @case ('filters') {\n <div class=\"filters-authoring-stack\">\n <section\n class=\"responsive-settings toolbar-filters-region\"\n data-testid=\"table-unified-filter-region-section\"\n aria-labelledby=\"table-unified-filter-region-title\"\n >\n <div class=\"responsive-settings__heading\">\n <mat-icon aria-hidden=\"true\">filter_list</mat-icon>\n <h3 id=\"table-unified-filter-region-title\">\n {{ tx('filters.region.title', 'Regi\u00E3o \u00FAnica de filtros e escopos da toolbar') }}\n </h3>\n </div>\n <p class=\"responsive-settings__hint\">\n {{\n tx(\n 'filters.region.description',\n 'Centralize escopos r\u00E1pidos (Todos / Ativos / Inativos), busca e gaveta avan\u00E7ada na toolbar da tabela sem controles duplicados.'\n )\n }}\n </p>\n\n <div class=\"toolbar-filters-toggles\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('filters.region.enabled', 'Regi\u00E3o de filtros na toolbar') }}</mat-label>\n <mat-select\n [ngModel]=\"toolbarFiltersEnabled\"\n (ngModelChange)=\"onToolbarFiltersEnabledChange($event)\"\n data-testid=\"table-toolbar-filters-enabled-select\"\n >\n <mat-option [value]=\"true\">{{ tx('common.enabled', 'Ativada') }}</mat-option>\n <mat-option [value]=\"false\">{{ tx('common.disabled', 'Desativada') }}</mat-option>\n </mat-select>\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('filters.region.showQuickFilters', 'Exibir escopos r\u00E1pidos') }}</mat-label>\n <mat-select\n [ngModel]=\"toolbarShowQuickFilters\"\n (ngModelChange)=\"onToolbarShowQuickFiltersChange($event)\"\n data-testid=\"table-toolbar-show-quick-filters-select\"\n >\n <mat-option [value]=\"true\">{{ tx('common.yes', 'Sim') }}</mat-option>\n <mat-option [value]=\"false\">{{ tx('common.no', 'N\u00E3o') }}</mat-option>\n </mat-select>\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('filters.region.showAdvancedButton', 'Bot\u00E3o de gaveta avan\u00E7ada') }}</mat-label>\n <mat-select\n [ngModel]=\"toolbarShowAdvancedButton\"\n (ngModelChange)=\"onToolbarShowAdvancedButtonChange($event)\"\n data-testid=\"table-toolbar-show-advanced-button-select\"\n >\n <mat-option [value]=\"true\">{{ tx('common.yes', 'Sim') }}</mat-option>\n <mat-option [value]=\"false\">{{ tx('common.no', 'N\u00E3o') }}</mat-option>\n </mat-select>\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('filters.region.showAddButton', 'Bot\u00E3o \"Gerenciar campos\"') }}</mat-label>\n <mat-select\n [ngModel]=\"advancedFilterShowAddButton\"\n (ngModelChange)=\"onAdvancedFilterShowAddButtonChange($event)\"\n data-testid=\"table-toolbar-show-add-button-select\"\n >\n <mat-option [value]=\"true\">{{ tx('common.yes', 'Sim') }}</mat-option>\n <mat-option [value]=\"false\">{{ tx('common.no', 'N\u00E3o') }}</mat-option>\n </mat-select>\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('filters.region.showFilterSettings', 'Bot\u00E3o \"Configura\u00E7\u00F5es do filtro\"') }}</mat-label>\n <mat-select\n [ngModel]=\"advancedFilterShowFilterSettings\"\n (ngModelChange)=\"onAdvancedFilterShowFilterSettingsChange($event)\"\n data-testid=\"table-toolbar-show-filter-settings-select\"\n >\n <mat-option [value]=\"true\">{{ tx('common.yes', 'Sim') }}</mat-option>\n <mat-option [value]=\"false\">{{ tx('common.no', 'N\u00E3o') }}</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n\n @if (duplicatedQuickFilterInlineFields.length > 0) {\n <div\n class=\"alert alert--warning connect-alert\"\n data-testid=\"table-filter-duplicate-scope-warning\"\n >\n <mat-icon class=\"alert__icon\">warning</mat-icon>\n <div class=\"alert__text connect-alert__text\">\n {{\n tx(\n 'filters.region.duplicateWarning',\n 'Os campos abaixo j\u00E1 possuem escopos r\u00E1pidos na toolbar e tamb\u00E9m est\u00E3o nos filtros inline:'\n )\n }}\n <strong>{{ duplicatedQuickFilterInlineFields.join(', ') }}</strong>\n </div>\n <button\n mat-stroked-button\n color=\"primary\"\n type=\"button\"\n data-testid=\"table-filter-consolidate-duplicates-btn\"\n (click)=\"consolidateDuplicateInlineFilterFields()\"\n >\n {{ tx('filters.region.consolidateBtn', 'Consolidar nos escopos r\u00E1pidos') }}\n </button>\n </div>\n }\n\n <div class=\"quick-filters-header\">\n <strong>{{ tx('filters.quickFilters.heading', 'Escopos r\u00E1pidos (quickFilters)') }}</strong>\n <div class=\"quick-filters-actions\">\n <button\n mat-stroked-button\n type=\"button\"\n data-testid=\"table-quick-filters-preset-boolean-btn\"\n (click)=\"applyBooleanQuickFilterPreset()\"\n >\n {{ tx('filters.quickFilters.presetBoolean', 'Preset Todos / Ativos / Inativos') }}\n </button>\n <button\n mat-stroked-button\n color=\"primary\"\n type=\"button\"\n data-testid=\"table-quick-filters-add-btn\"\n (click)=\"addQuickFilterScope()\"\n >\n {{ tx('filters.quickFilters.add', 'Adicionar escopo') }}\n </button>\n </div>\n </div>\n\n @if (toolbarQuickFilters.length > 0) {\n <div class=\"quick-filters-list\" data-testid=\"table-quick-filters-list\">\n @for (qf of toolbarQuickFilters; track $index; let idx = $index) {\n <div class=\"quick-filter-row\" [attr.data-testid]=\"'table-quick-filter-row-' + idx\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('filters.quickFilters.id', 'ID') }}</mat-label>\n <input\n matInput\n [ngModel]=\"qf.id\"\n (ngModelChange)=\"updateQuickFilterMeta(idx, { id: $event })\"\n [attr.data-testid]=\"'table-quick-filter-id-' + idx\"\n />\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('filters.quickFilters.label', 'R\u00F3tulo') }}</mat-label>\n <input\n matInput\n [ngModel]=\"qf.label\"\n (ngModelChange)=\"updateQuickFilterMeta(idx, { label: $event })\"\n [attr.data-testid]=\"'table-quick-filter-label-' + idx\"\n />\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('filters.quickFilters.mode', 'Tipo de escopo') }}</mat-label>\n <mat-select\n [ngModel]=\"getQuickFilterMode(qf)\"\n (ngModelChange)=\"onQuickFilterModeChange(idx, $event)\"\n [attr.data-testid]=\"'table-quick-filter-mode-' + idx\"\n >\n <mat-option value=\"all\">{{ tx('filters.quickFilters.mode.all', 'Todos (limpar filtro)') }}</mat-option>\n <mat-option value=\"boolean\">{{ tx('filters.quickFilters.mode.boolean', 'Status booleano (Sim/N\u00E3o)') }}</mat-option>\n <mat-option value=\"equals\">{{ tx('filters.quickFilters.mode.equals', 'Campo igual a valor') }}</mat-option>\n </mat-select>\n </mat-form-field>\n\n @if (getQuickFilterMode(qf) !== 'all') {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('filters.quickFilters.field', 'Campo') }}</mat-label>\n <input\n matInput\n [ngModel]=\"getQuickFilterField(qf)\"\n (ngModelChange)=\"onQuickFilterFieldChange(idx, $event)\"\n [matAutocomplete]=\"quickFilterFieldPicker\"\n [attr.data-testid]=\"'table-quick-filter-field-' + idx\"\n />\n <mat-autocomplete\n #quickFilterFieldPicker=\"matAutocomplete\"\n (optionSelected)=\"onQuickFilterFieldChange(idx, $event.option.value)\"\n >\n @for (option of getFilteredQuickFilterFieldOptions(getQuickFilterField(qf)); track option.name) {\n <mat-option [value]=\"option.name\">\n <div class=\"table-rich-field-option\">\n <div class=\"table-rich-field-option__top\">\n <span class=\"table-rich-field-option__label\">{{ option.label && option.label !== option.name ? option.label : option.name }}</span>\n @if (option.label && option.label !== option.name) {\n <code class=\"table-rich-field-option__code\">{{ option.name }}</code>\n }\n @if (getFieldTypeBadge(option); as badge) {\n <span class=\"table-rich-field-option__badge\">{{ badge }}</span>\n }\n </div>\n @if (getFieldDescription(option); as desc) {\n <div class=\"table-rich-field-option__desc\">{{ desc }}</div>\n }\n </div>\n </mat-option>\n }\n </mat-autocomplete>\n </mat-form-field>\n\n @if (getQuickFilterMode(qf) === 'boolean') {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('filters.quickFilters.value', 'Valor') }}</mat-label>\n <mat-select\n [ngModel]=\"getQuickFilterValue(qf)\"\n (ngModelChange)=\"onQuickFilterValueChange(idx, $event)\"\n [attr.data-testid]=\"'table-quick-filter-bool-value-' + idx\"\n >\n <mat-option [value]=\"true\">{{ tx('common.boolean.true', 'Verdadeiro (true)') }}</mat-option>\n <mat-option [value]=\"false\">{{ tx('common.boolean.false', 'Falso (false)') }}</mat-option>\n </mat-select>\n </mat-form-field>\n } @else {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('filters.quickFilters.value', 'Valor') }}</mat-label>\n <input\n matInput\n [ngModel]=\"getQuickFilterValue(qf)\"\n (ngModelChange)=\"onQuickFilterValueChange(idx, $event)\"\n [attr.data-testid]=\"'table-quick-filter-value-' + idx\"\n />\n </mat-form-field>\n }\n }\n\n <button\n mat-stroked-button\n color=\"warn\"\n type=\"button\"\n [attr.data-testid]=\"'table-quick-filter-remove-' + idx\"\n (click)=\"removeQuickFilterScope(idx)\"\n >\n {{ tx('common.remove', 'Remover') }}\n </button>\n </div>\n }\n </div>\n }\n </section>\n\n <filter-settings\n #tableFilterSettingsRef\n [metadata]=\"getFilterSettingsMetas()\"\n [metadataSource]=\"isUsingFilterDtoMetas() ? 'filter-dto' : 'columns'\"\n [metadataLoading]=\"filterDtoMetasLoading\"\n [metadataErrorMsg]=\"filterDtoMetasErrorMsg\"\n [settings]=\"\n editedConfig.behavior?.filtering?.advancedFilters?.settings\n \"\n (settingsChange)=\"onFilterSettingsChange($event)\"\n ></filter-settings>\n </div>\n }\n @case ('messages') {\n <messages-localization-editor\n [config]=\"editedConfig\"\n (configChange)=\"onMessagesLocalizationConfigChange($event)\"\n (messagesLocalizationChange)=\"\n onMessagesLocalizationChange($event)\n \"\n ></messages-localization-editor>\n }\n @case ('dialogs') {\n <confirm-dialog-appearance-editor\n [config]=\"editedConfig\"\n (configChange)=\"onJsonConfigChange($event)\"\n ></confirm-dialog-appearance-editor>\n }\n @case ('json') {\n <json-config-editor\n [document]=\"buildJsonAuthoringDocument()\"\n (documentChange)=\"onJsonConfigChange($event)\"\n (validationChange)=\"onJsonValidationChange($event)\"\n (editorEvent)=\"onJsonEditorEvent($event)\"\n ></json-config-editor>\n }\n }\n </div>\n </mat-tab>\n }\n</mat-tab-group>\n@if (statusMessage) { <div class=\"config-editor-status\">\n <span\n class=\"status-text\"\n [class.error]=\"hasErrors\"\n [class.success]=\"hasSuccess\"\n >{{ statusMessage }}</span\n >\n</div> }\n", styles: ["@charset \"UTF-8\";:host{display:block;min-width:0;container:praxis-authoring-editor/inline-size}.editor-section-selector{display:block;padding:var(--pfx-spacing-2, 8px)}.editor-section-selector mat-form-field{width:100%}@container praxis-authoring-editor (max-width: 768px){.editor-section-selector{display:block}::ng-deep .config-tabs>.mat-mdc-tab-header{display:none}}.config-tabs{flex:1 1 auto;display:flex;flex-direction:column}.config-tabs .mat-mdc-tab{min-width:120px}.config-tabs .mat-mdc-tab-body-wrapper,.config-tabs .mat-mdc-tab-group-container{flex:1 1 auto;min-height:0}.config-tabs .mat-mdc-tab-body-content{height:100%;min-height:0;overflow:visible}.tab-content{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;padding:8px 8px 56px;box-sizing:border-box;overflow:visible}.overview-grid{display:grid;min-width:0;grid-template-columns:minmax(0,1fr);gap:12px}.overview-grid .overview-row{display:grid;grid-template-columns:280px 1fr;align-items:end;gap:12px}.overview-grid .overview-row .help.small{opacity:.75;font-size:12px}.overview-mode-alert{align-items:center}.connect-grid{display:grid;gap:12px;padding:8px;grid-template-columns:minmax(0,1fr);align-items:start}.connect-note{grid-column:1/-1;margin:0;font-size:12px;color:var(--md-sys-color-on-surface-variant)}.connect-alert{grid-column:1/-1;align-items:center}.connect-alert__text{flex:1}.connect-alert__meta{opacity:.85}.educational-card{margin-bottom:24px;background:var(--md-sys-color-surface-container-low);border-left:4px solid var(--md-sys-color-primary);flex-shrink:0}.config-editor-status{padding:12px 16px;margin-top:auto;border-top:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container);flex-shrink:0;position:relative}.status-text{font-size:.875rem;line-height:1.2}.status-text.error{color:var(--md-sys-color-error)}.status-text.success{color:var(--md-sys-color-primary)}@media(max-width:768px){.tab-content{padding:8px 8px 72px}.config-editor-status{padding:12px 16px}.connect-grid{grid-template-columns:1fr}.overview-grid .overview-row{grid-template-columns:1fr;align-items:start}:host{display:flex;flex-direction:column;flex:1 1 auto;min-height:0}}.responsive-settings{display:grid;grid-template-columns:minmax(0,1fr);gap:12px;min-width:0;padding:16px;border:1px solid var(--md-sys-color-outline-variant);border-radius:var(--md-sys-shape-corner-medium, 12px);background:var(--md-sys-color-surface-container-low)}.responsive-settings__heading{display:flex;align-items:center;gap:8px}.responsive-settings h3,.responsive-settings p{margin:0}.responsive-settings h3{font-size:1rem;font-weight:600}.responsive-settings p{font-size:.875rem;line-height:1.5}.responsive-settings__hint{color:var(--md-sys-color-on-surface-variant)}.responsive-settings__error{color:var(--md-sys-color-error)}.responsive-settings mat-form-field{width:100%;min-width:0}.filters-authoring-stack{display:grid;grid-template-columns:minmax(0,1fr);gap:16px}.toolbar-filters-toggles{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px}.quick-filters-header{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:8px}.quick-filters-actions{display:flex;flex-wrap:wrap;gap:8px}.quick-filters-list{display:grid;gap:10px}.quick-filter-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));align-items:center;gap:8px;padding:10px;border:1px solid var(--md-sys-color-outline-variant);border-radius:8px;background:var(--md-sys-color-surface)}.table-rich-field-option{display:flex;flex-direction:column;gap:2px;padding:2px 0;line-height:1.25}.table-rich-field-option__top{display:flex;align-items:center;gap:6px;flex-wrap:wrap}.table-rich-field-option__label{font-weight:500;font-size:13px;color:var(--md-sys-color-on-surface)}.table-rich-field-option__code{font-family:var(--praxis-font-mono, monospace);font-size:11px;padding:1px 5px;border-radius:4px;background:var(--md-sys-color-surface-container-highest);color:var(--md-sys-color-on-surface-variant)}.table-rich-field-option__badge{margin-left:auto;font-size:10px;font-weight:600;text-transform:uppercase;padding:1px 6px;border-radius:999px;background:var(--md-sys-color-secondary-container);color:var(--md-sys-color-on-secondary-container)}.table-rich-field-option__desc{font-size:11px;color:var(--md-sys-color-on-surface-variant);white-space:normal;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}\n"] }]
|
|
74845
74972
|
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: i1.TableConfigService }], propDecorators: { filterSettingsEditor: [{
|
|
74846
74973
|
type: ViewChild,
|
|
74847
74974
|
args: ['tableFilterSettingsRef']
|