@praxisui/page-builder 10.0.0-rc.36 → 10.0.0-rc.37
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/README.md +51 -0
- package/ai/component-registry.json +14 -6
- package/fesm2022/praxisui-page-builder.mjs +501 -165
- package/package.json +4 -4
- package/types/praxisui-page-builder.d.ts +115 -78
|
@@ -10,27 +10,27 @@ import * as i2$2 from '@praxisui/core';
|
|
|
10
10
|
import { PraxisIconDirective, providePraxisI18n, BUILTIN_WIDGET_SHELL_PRESETS, SETTINGS_EDITOR_SECTIONS_HOSTED, PraxisI18nService, getWidgetShellActionIdError, WidgetShellComponent, SettingsSectionTabsComponent, PraxisIconButtonComponent, NestedWidgetConfigAccessor, GLOBAL_ACTION_CATALOG, ComponentMetadataRegistry, CompositionValidatorService, getGlobalActionCatalog, PRAXIS_GLOBAL_ACTION_CATALOG, SurfaceOpenActionEditorComponent, BUILTIN_PAGE_LAYOUT_PRESETS, resolveWidgetPageLayoutPreset, BUILTIN_PAGE_THEME_PRESETS, prepareCanvasArrangement, SETTINGS_PANEL_DATA as SETTINGS_PANEL_DATA$1, ResourceDiscoveryService, WidgetPageDataSourceRuntimeService, WidgetPageStateRuntimeService, applyDeclarativeValuePipe, validateCanvasArrangement, ResourceQuickConnectComponent, PraxisJsonLogicService, canonicalJsonSha256, ComponentRuntimeProfileService, buildApiUrl, deepMerge, generateId, ObservabilityDashboardService, PraxisRuntimeComponentObservationRegistryService, PraxisCustomizationModeService, domainKnowledgeTimelineToRichContentDocument, DomainRuleService, DomainKnowledgeService, SETTINGS_PANEL_BRIDGE, DynamicWidgetPageComponent, DYNAMIC_PAGE_SHELL_EDITOR } from '@praxisui/core';
|
|
11
11
|
import * as i3 from '@angular/material/tooltip';
|
|
12
12
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
|
13
|
-
import
|
|
14
|
-
import { MatFormFieldModule } from '@angular/material/form-field';
|
|
15
|
-
import * as i8 from '@angular/material/input';
|
|
16
|
-
import { MatInputModule } from '@angular/material/input';
|
|
13
|
+
import { A11yModule, CdkTrapFocus } from '@angular/cdk/a11y';
|
|
17
14
|
import * as i1$1 from '@angular/forms';
|
|
18
15
|
import { FormsModule, FormGroup, FormArray, FormControl, Validators, ReactiveFormsModule } from '@angular/forms';
|
|
19
16
|
import * as i1$2 from '@angular/common';
|
|
20
17
|
import { CommonModule, NgTemplateOutlet } from '@angular/common';
|
|
21
|
-
import * as i4 from '@angular/material/
|
|
18
|
+
import * as i4 from '@angular/material/button-toggle';
|
|
19
|
+
import { MatButtonToggleModule } from '@angular/material/button-toggle';
|
|
20
|
+
import * as i5 from '@angular/material/checkbox';
|
|
22
21
|
import { MatCheckboxModule } from '@angular/material/checkbox';
|
|
22
|
+
import * as i7 from '@angular/material/form-field';
|
|
23
|
+
import { MatFormFieldModule } from '@angular/material/form-field';
|
|
24
|
+
import * as i9 from '@angular/material/input';
|
|
25
|
+
import { MatInputModule } from '@angular/material/input';
|
|
23
26
|
import * as i3$1 from '@angular/material/select';
|
|
24
27
|
import { MatSelectModule } from '@angular/material/select';
|
|
25
28
|
import { BehaviorSubject, merge, timeout, map, catchError, throwError, firstValueFrom, from, share, concatMap, of, timer, takeUntil, Subject, tap, filter, take, isObservable } from 'rxjs';
|
|
26
29
|
import { SETTINGS_PANEL_DATA } from '@praxisui/settings-panel';
|
|
27
30
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
28
|
-
import { CdkTrapFocus } from '@angular/cdk/a11y';
|
|
29
31
|
import * as i2$3 from '@angular/material/autocomplete';
|
|
30
32
|
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
|
31
|
-
import * as
|
|
32
|
-
import { MatButtonToggleModule } from '@angular/material/button-toggle';
|
|
33
|
-
import * as i9 from '@angular/material/tabs';
|
|
33
|
+
import * as i9$1 from '@angular/material/tabs';
|
|
34
34
|
import { MatTabsModule } from '@angular/material/tabs';
|
|
35
35
|
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
|
36
36
|
import { AgenticAuthoringTurnClientService, withAuthoringScopePolicyContextHints, toPraxisAssistantConversationMessages, PraxisAssistantTurnOrchestratorService, PraxisAssistantSessionRegistryService, createPraxisAssistantViewportLayout, PraxisAiAssistantShellComponent } from '@praxisui/ai';
|
|
@@ -363,6 +363,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
363
363
|
}] } });
|
|
364
364
|
|
|
365
365
|
const PRAXIS_PAGE_BUILDER_EN_US = {
|
|
366
|
+
'praxis.pageBuilder.shell.interactionHighlight.label': 'Selection highlight',
|
|
367
|
+
'praxis.pageBuilder.shell.interactionHighlight.inherit': 'Inherit',
|
|
368
|
+
'praxis.pageBuilder.shell.interactionHighlight.standard': 'Standard',
|
|
369
|
+
'praxis.pageBuilder.shell.interactionHighlight.gradient': 'Gradient',
|
|
370
|
+
'praxis.pageBuilder.shell.interactionHighlight.help': 'The preview simulates selection while editing. Inherit uses the preset or platform default.',
|
|
366
371
|
"praxis.pageBuilder.studio.gridSize": "Size in columns × rows",
|
|
367
372
|
"praxis.pageBuilder.studio.visualTab": "Visual templates",
|
|
368
373
|
"praxis.pageBuilder.studio.layoutTab": "Layout templates",
|
|
@@ -1526,6 +1531,18 @@ const PRAXIS_PAGE_BUILDER_EN_US = {
|
|
|
1526
1531
|
'praxis.pageBuilder.palette.componentSingular': "component",
|
|
1527
1532
|
'praxis.pageBuilder.palette.componentPlural': "components",
|
|
1528
1533
|
'praxis.pageBuilder.palette.done': "Done",
|
|
1534
|
+
'praxis.pageBuilder.palette.clearSearchAria': "Clear search",
|
|
1535
|
+
'praxis.pageBuilder.palette.availableSingular': "available",
|
|
1536
|
+
'praxis.pageBuilder.palette.kbdInsert': "insert",
|
|
1537
|
+
'praxis.pageBuilder.palette.kbdQueue': "queue",
|
|
1538
|
+
'praxis.pageBuilder.palette.kbdNavigate': "navigate",
|
|
1539
|
+
'praxis.pageBuilder.palette.categoryAria': "Filter catalog by category",
|
|
1540
|
+
'praxis.pageBuilder.palette.category.all': "All",
|
|
1541
|
+
'praxis.pageBuilder.palette.category.widgets': "Page widgets",
|
|
1542
|
+
'praxis.pageBuilder.palette.category.presets': "Presets",
|
|
1543
|
+
'praxis.pageBuilder.palette.category.fields': "Fields",
|
|
1544
|
+
'praxis.pageBuilder.palette.emptySearchHint': "Try another search term or clear active filters to view the full catalog.",
|
|
1545
|
+
'praxis.pageBuilder.palette.clearFilters': "Clear filters",
|
|
1529
1546
|
'praxis.pageBuilder.drawer.contextSwitcherAria': "Switch edited widget without closing the drawer",
|
|
1530
1547
|
'praxis.pageBuilder.drawer.prevWidgetAria': "Previous widget",
|
|
1531
1548
|
'praxis.pageBuilder.drawer.nextWidgetAria': "Next widget",
|
|
@@ -1536,6 +1553,11 @@ const PRAXIS_PAGE_BUILDER_EN_US = {
|
|
|
1536
1553
|
};
|
|
1537
1554
|
|
|
1538
1555
|
const PRAXIS_PAGE_BUILDER_PT_BR = {
|
|
1556
|
+
'praxis.pageBuilder.shell.interactionHighlight.label': 'Destaque de seleção',
|
|
1557
|
+
'praxis.pageBuilder.shell.interactionHighlight.inherit': 'Herdar',
|
|
1558
|
+
'praxis.pageBuilder.shell.interactionHighlight.standard': 'Padrão',
|
|
1559
|
+
'praxis.pageBuilder.shell.interactionHighlight.gradient': 'Gradiente',
|
|
1560
|
+
'praxis.pageBuilder.shell.interactionHighlight.help': 'A prévia simula a seleção durante a edição. Herdar usa o preset ou o padrão da plataforma.',
|
|
1539
1561
|
"praxis.pageBuilder.studio.gridSize": "Tamanho em colunas × linhas",
|
|
1540
1562
|
"praxis.pageBuilder.studio.visualTab": "Templates visuais",
|
|
1541
1563
|
"praxis.pageBuilder.studio.layoutTab": "Templates de layout",
|
|
@@ -2699,6 +2721,18 @@ const PRAXIS_PAGE_BUILDER_PT_BR = {
|
|
|
2699
2721
|
'praxis.pageBuilder.palette.componentSingular': "componente",
|
|
2700
2722
|
'praxis.pageBuilder.palette.componentPlural': "componentes",
|
|
2701
2723
|
'praxis.pageBuilder.palette.done': "Concluir",
|
|
2724
|
+
'praxis.pageBuilder.palette.clearSearchAria': "Limpar busca",
|
|
2725
|
+
'praxis.pageBuilder.palette.availableSingular': "disponível",
|
|
2726
|
+
'praxis.pageBuilder.palette.kbdInsert': "inserir",
|
|
2727
|
+
'praxis.pageBuilder.palette.kbdQueue': "fila",
|
|
2728
|
+
'praxis.pageBuilder.palette.kbdNavigate': "navegar",
|
|
2729
|
+
'praxis.pageBuilder.palette.categoryAria': "Filtrar catálogo por categoria",
|
|
2730
|
+
'praxis.pageBuilder.palette.category.all': "Todos",
|
|
2731
|
+
'praxis.pageBuilder.palette.category.widgets': "Widgets de página",
|
|
2732
|
+
'praxis.pageBuilder.palette.category.presets': "Predefinições",
|
|
2733
|
+
'praxis.pageBuilder.palette.category.fields': "Campos",
|
|
2734
|
+
'praxis.pageBuilder.palette.emptySearchHint': "Tente outro termo ou limpe os filtros ativos para exibir todo o catálogo.",
|
|
2735
|
+
'praxis.pageBuilder.palette.clearFilters': "Limpar filtros",
|
|
2702
2736
|
'praxis.pageBuilder.drawer.contextSwitcherAria': "Alternar widget editado sem fechar a gaveta",
|
|
2703
2737
|
'praxis.pageBuilder.drawer.prevWidgetAria': "Widget anterior",
|
|
2704
2738
|
'praxis.pageBuilder.drawer.nextWidgetAria': "Próximo widget",
|
|
@@ -2740,6 +2774,18 @@ function resolvePraxisPageBuilderText(i18n, key, fallback) {
|
|
|
2740
2774
|
return i18n.t(namespacedKey, undefined, fallback) || fallback;
|
|
2741
2775
|
}
|
|
2742
2776
|
|
|
2777
|
+
/**
|
|
2778
|
+
* Studio Component Palette Dialog for the Praxis Page Builder.
|
|
2779
|
+
*
|
|
2780
|
+
* Provides three enterprise authoring modes:
|
|
2781
|
+
* 1. **Single Insertion**: Immediate selection and dialog dismissal (`Enter` or card click).
|
|
2782
|
+
* 2. **Continuous Multi-Insert (`keepOpen`)**: Inserts directly onto the canvas without closing the dialog.
|
|
2783
|
+
* 3. **Batch Staging (`batchMode`)**: Stages multiple components with live grid coordinate preview (`L·C`)
|
|
2784
|
+
* and commits them atomically via `onInsertBatch`.
|
|
2785
|
+
*
|
|
2786
|
+
* Automatically prioritizes governed page-level widgets and presets ahead of primitive form field controls
|
|
2787
|
+
* and exposes quick category filter pills for large enterprise catalogs.
|
|
2788
|
+
*/
|
|
2743
2789
|
class ComponentPaletteDialogComponent {
|
|
2744
2790
|
dialogRef;
|
|
2745
2791
|
registry;
|
|
@@ -2748,6 +2794,7 @@ class ComponentPaletteDialogComponent {
|
|
|
2748
2794
|
hostElement = inject((ElementRef));
|
|
2749
2795
|
queueSeq = 0;
|
|
2750
2796
|
query = signal('', ...(ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []));
|
|
2797
|
+
selectedCategory = signal('all', ...(ngDevMode ? [{ debugName: "selectedCategory" }] : /* istanbul ignore next */ []));
|
|
2751
2798
|
selectedColSpan = signal(undefined, ...(ngDevMode ? [{ debugName: "selectedColSpan" }] : /* istanbul ignore next */ []));
|
|
2752
2799
|
batchMode = signal(false, ...(ngDevMode ? [{ debugName: "batchMode" }] : /* istanbul ignore next */ []));
|
|
2753
2800
|
keepOpen = signal(false, ...(ngDevMode ? [{ debugName: "keepOpen" }] : /* istanbul ignore next */ []));
|
|
@@ -2763,15 +2810,53 @@ class ComponentPaletteDialogComponent {
|
|
|
2763
2810
|
return 0;
|
|
2764
2811
|
return new Set(items.map((item) => item.rowPreview)).size;
|
|
2765
2812
|
}, ...(ngDevMode ? [{ debugName: "stagedRowCount" }] : /* istanbul ignore next */ []));
|
|
2766
|
-
spanOptions =
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2813
|
+
spanOptions = computed(() => {
|
|
2814
|
+
const cols = this.canvasColumns();
|
|
2815
|
+
if (cols === 12) {
|
|
2816
|
+
return [
|
|
2817
|
+
{ label: 'Auto', value: undefined },
|
|
2818
|
+
{ label: '25% (3 col)', value: 3 },
|
|
2819
|
+
{ label: '33% (4 col)', value: 4 },
|
|
2820
|
+
{ label: '50% (6 col)', value: 6 },
|
|
2821
|
+
{ label: '100% (12 col)', value: 12 },
|
|
2822
|
+
];
|
|
2823
|
+
}
|
|
2824
|
+
const q1 = Math.max(1, Math.round(cols * 0.25));
|
|
2825
|
+
const q3 = Math.max(1, Math.round(cols / 3));
|
|
2826
|
+
const half = Math.max(1, Math.round(cols * 0.5));
|
|
2827
|
+
const seen = new Set();
|
|
2828
|
+
const dynamic = [{ label: 'Auto', value: undefined }];
|
|
2829
|
+
for (const [pct, val] of [[25, q1], [33, q3], [50, half], [100, cols]]) {
|
|
2830
|
+
if (!seen.has(val)) {
|
|
2831
|
+
seen.add(val);
|
|
2832
|
+
dynamic.push({ label: `${pct}% (${val} col)`, value: val });
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2835
|
+
return dynamic;
|
|
2836
|
+
}, ...(ngDevMode ? [{ debugName: "spanOptions" }] : /* istanbul ignore next */ []));
|
|
2773
2837
|
cycleSpans = [3, 4, 6, 8, 12];
|
|
2774
2838
|
all = signal([], ...(ngDevMode ? [{ debugName: "all" }] : /* istanbul ignore next */ []));
|
|
2839
|
+
allowedCatalog = computed(() => this.applyStructuralConstraints(), ...(ngDevMode ? [{ debugName: "allowedCatalog" }] : /* istanbul ignore next */ []));
|
|
2840
|
+
categoryOptions = computed(() => {
|
|
2841
|
+
const entries = this.allowedCatalog();
|
|
2842
|
+
const counts = {
|
|
2843
|
+
all: entries.length,
|
|
2844
|
+
widget: 0,
|
|
2845
|
+
preset: 0,
|
|
2846
|
+
field: 0,
|
|
2847
|
+
};
|
|
2848
|
+
for (const entry of entries) {
|
|
2849
|
+
counts[entry.category] += 1;
|
|
2850
|
+
}
|
|
2851
|
+
const options = [
|
|
2852
|
+
{ id: 'all', label: this.tx('palette.category.all', 'Todos'), count: counts.all },
|
|
2853
|
+
{ id: 'widget', label: this.tx('palette.category.widgets', 'Widgets de página'), count: counts.widget },
|
|
2854
|
+
{ id: 'preset', label: this.tx('palette.category.presets', 'Predefinições'), count: counts.preset },
|
|
2855
|
+
{ id: 'field', label: this.tx('palette.category.fields', 'Campos'), count: counts.field },
|
|
2856
|
+
];
|
|
2857
|
+
return options.filter((opt) => opt.id === 'all' || opt.count > 0);
|
|
2858
|
+
}, ...(ngDevMode ? [{ debugName: "categoryOptions" }] : /* istanbul ignore next */ []));
|
|
2859
|
+
hasMultipleCategories = computed(() => this.categoryOptions().length > 2, ...(ngDevMode ? [{ debugName: "hasMultipleCategories" }] : /* istanbul ignore next */ []));
|
|
2775
2860
|
filtered = computed(() => this.applyFilters(), ...(ngDevMode ? [{ debugName: "filtered" }] : /* istanbul ignore next */ []));
|
|
2776
2861
|
density = computed(() => {
|
|
2777
2862
|
const n = this.filtered().length;
|
|
@@ -2816,8 +2901,8 @@ class ComponentPaletteDialogComponent {
|
|
|
2816
2901
|
}
|
|
2817
2902
|
resolveEffectiveCols(selection) {
|
|
2818
2903
|
const columns = this.canvasColumns();
|
|
2819
|
-
if (typeof selection.colSpan === 'number' && selection.colSpan
|
|
2820
|
-
return Math.trunc(selection.colSpan);
|
|
2904
|
+
if (typeof selection.colSpan === 'number' && Number.isFinite(selection.colSpan) && selection.colSpan >= 1) {
|
|
2905
|
+
return Math.min(columns, Math.trunc(selection.colSpan));
|
|
2821
2906
|
}
|
|
2822
2907
|
const metaHints = this.registry.get(selection.componentId)?.layoutHints;
|
|
2823
2908
|
if (metaHints?.recommendedCols && metaHints.recommendedCols >= 1) {
|
|
@@ -2825,11 +2910,11 @@ class ComponentPaletteDialogComponent {
|
|
|
2825
2910
|
}
|
|
2826
2911
|
switch (selection.componentId) {
|
|
2827
2912
|
case 'praxis-filter':
|
|
2913
|
+
case 'praxis-crud':
|
|
2828
2914
|
return columns;
|
|
2829
2915
|
case 'praxis-table':
|
|
2830
2916
|
case 'praxis-chart':
|
|
2831
2917
|
case 'praxis-list':
|
|
2832
|
-
case 'praxis-crud':
|
|
2833
2918
|
return Math.min(columns, 8);
|
|
2834
2919
|
default:
|
|
2835
2920
|
return Math.min(columns, 4);
|
|
@@ -2842,12 +2927,12 @@ class ComponentPaletteDialogComponent {
|
|
|
2842
2927
|
}
|
|
2843
2928
|
switch (selection.componentId) {
|
|
2844
2929
|
case 'praxis-table':
|
|
2930
|
+
case 'praxis-crud':
|
|
2845
2931
|
return 6;
|
|
2846
2932
|
case 'praxis-filter':
|
|
2847
2933
|
return 3;
|
|
2848
2934
|
case 'praxis-chart':
|
|
2849
2935
|
case 'praxis-list':
|
|
2850
|
-
case 'praxis-crud':
|
|
2851
2936
|
return 5;
|
|
2852
2937
|
default:
|
|
2853
2938
|
return 2;
|
|
@@ -2905,6 +2990,12 @@ class ComponentPaletteDialogComponent {
|
|
|
2905
2990
|
enqueue(entry, event) {
|
|
2906
2991
|
event?.stopPropagation();
|
|
2907
2992
|
event?.preventDefault();
|
|
2993
|
+
if (!this.batchMode()) {
|
|
2994
|
+
this.batchMode.set(true);
|
|
2995
|
+
if (this.keepOpen()) {
|
|
2996
|
+
this.keepOpen.set(false);
|
|
2997
|
+
}
|
|
2998
|
+
}
|
|
2908
2999
|
const selection = this.buildPayload(entry);
|
|
2909
3000
|
const effectiveCols = this.resolveEffectiveCols(selection);
|
|
2910
3001
|
const effectiveRows = this.resolveEffectiveRows(selection);
|
|
@@ -2975,7 +3066,7 @@ class ComponentPaletteDialogComponent {
|
|
|
2975
3066
|
this.dialogRef.close(selections[0]);
|
|
2976
3067
|
}
|
|
2977
3068
|
select(entry) {
|
|
2978
|
-
if (this.batchMode()) {
|
|
3069
|
+
if (this.batchMode() || (!this.keepOpen() && this.queuedItems().length > 0)) {
|
|
2979
3070
|
this.enqueue(entry);
|
|
2980
3071
|
return;
|
|
2981
3072
|
}
|
|
@@ -2988,9 +3079,37 @@ class ComponentPaletteDialogComponent {
|
|
|
2988
3079
|
this.dialogRef.close(payload);
|
|
2989
3080
|
}
|
|
2990
3081
|
handleSearchKeydown(event) {
|
|
2991
|
-
if (event.key
|
|
3082
|
+
if (event.key === 'ArrowDown') {
|
|
3083
|
+
this.focusPaletteCard(0, event);
|
|
3084
|
+
return;
|
|
3085
|
+
}
|
|
3086
|
+
if (event.key === 'Escape' && this.query()) {
|
|
3087
|
+
event.preventDefault();
|
|
3088
|
+
event.stopPropagation();
|
|
3089
|
+
this.clearSearch();
|
|
2992
3090
|
return;
|
|
2993
|
-
|
|
3091
|
+
}
|
|
3092
|
+
if (event.key === 'Enter') {
|
|
3093
|
+
const results = this.filtered();
|
|
3094
|
+
if (results.length === 1 || (this.query().trim().length > 0 && results.length > 0)) {
|
|
3095
|
+
event.preventDefault();
|
|
3096
|
+
if (event.shiftKey) {
|
|
3097
|
+
this.enqueue(results[0], event);
|
|
3098
|
+
}
|
|
3099
|
+
else {
|
|
3100
|
+
this.select(results[0]);
|
|
3101
|
+
}
|
|
3102
|
+
}
|
|
3103
|
+
}
|
|
3104
|
+
}
|
|
3105
|
+
clearSearch() {
|
|
3106
|
+
this.query.set('');
|
|
3107
|
+
this.searchInput()?.focus();
|
|
3108
|
+
}
|
|
3109
|
+
resetFilters() {
|
|
3110
|
+
this.query.set('');
|
|
3111
|
+
this.selectedCategory.set('all');
|
|
3112
|
+
this.searchInput()?.focus();
|
|
2994
3113
|
}
|
|
2995
3114
|
handleCardKeydown(event, index, entry) {
|
|
2996
3115
|
if (entry && event.shiftKey && (event.key === 'Enter' || event.key === ' ')) {
|
|
@@ -3046,20 +3165,27 @@ class ComponentPaletteDialogComponent {
|
|
|
3046
3165
|
}
|
|
3047
3166
|
// Create effect in injection context (field initializer is allowed)
|
|
3048
3167
|
_adjustSizeEffect = effect(() => {
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
if (n <= 4)
|
|
3052
|
-
width = '560px';
|
|
3053
|
-
else if (n <= 8)
|
|
3054
|
-
width = '680px';
|
|
3168
|
+
void this.filtered().length;
|
|
3169
|
+
const width = '780px';
|
|
3055
3170
|
const container = this.dialogRef._containerInstance;
|
|
3056
3171
|
if (container && container._config) {
|
|
3057
3172
|
container._config.width = width;
|
|
3173
|
+
container._config.maxWidth = '94vw';
|
|
3058
3174
|
}
|
|
3175
|
+
const host = this.hostElement?.nativeElement;
|
|
3176
|
+
const dialogContainer = host?.closest('.mat-mdc-dialog-container');
|
|
3177
|
+
if (dialogContainer) {
|
|
3178
|
+
dialogContainer.style.setProperty('--mat-dialog-container-max-width', width);
|
|
3179
|
+
dialogContainer.style.maxWidth = `min(${width}, 94vw)`;
|
|
3180
|
+
}
|
|
3181
|
+
const overlayPane = host?.closest('.cdk-overlay-pane');
|
|
3182
|
+
if (overlayPane) {
|
|
3183
|
+
overlayPane.style.maxWidth = '94vw';
|
|
3184
|
+
}
|
|
3185
|
+
this.dialogRef.updateSize?.(width);
|
|
3059
3186
|
}, ...(ngDevMode ? [{ debugName: "_adjustSizeEffect" }] : /* istanbul ignore next */ []));
|
|
3060
|
-
|
|
3187
|
+
applyStructuralConstraints() {
|
|
3061
3188
|
const list = (this.all() || []);
|
|
3062
|
-
const q = (this.query() || '').toLowerCase();
|
|
3063
3189
|
const allowIds = new Set((this.data?.allowedWidgetIds || []).map((s) => s.toLowerCase()));
|
|
3064
3190
|
const allowTags = new Set((this.data?.allowedWidgetTags || []).map((s) => s.toLowerCase()));
|
|
3065
3191
|
const allowPresets = new Set((this.data?.allowedPresetIds || []).map((s) => s.toLowerCase()));
|
|
@@ -3068,12 +3194,9 @@ class ComponentPaletteDialogComponent {
|
|
|
3068
3194
|
const meta = metadataById.get(entry.componentId);
|
|
3069
3195
|
if (!meta)
|
|
3070
3196
|
return false;
|
|
3071
|
-
if (q && !entry.searchText.includes(q)) {
|
|
3072
|
-
return false;
|
|
3073
|
-
}
|
|
3074
3197
|
if (allowIds.size > 0 && !allowIds.has((meta.id || '').toLowerCase()))
|
|
3075
3198
|
return false;
|
|
3076
|
-
if (allowTags.size > 0 && !meta.tags?.some(t => allowTags.has(t.toLowerCase())))
|
|
3199
|
+
if (allowTags.size > 0 && !meta.tags?.some((t) => allowTags.has(t.toLowerCase())))
|
|
3077
3200
|
return false;
|
|
3078
3201
|
if (entry.presetId && allowPresets.size > 0 && !allowPresets.has(entry.presetId.toLowerCase()))
|
|
3079
3202
|
return false;
|
|
@@ -3082,11 +3205,33 @@ class ComponentPaletteDialogComponent {
|
|
|
3082
3205
|
return true;
|
|
3083
3206
|
});
|
|
3084
3207
|
}
|
|
3208
|
+
applyFilters() {
|
|
3209
|
+
const list = this.allowedCatalog();
|
|
3210
|
+
const q = (this.query() || '').toLowerCase().trim();
|
|
3211
|
+
const cat = this.selectedCategory();
|
|
3212
|
+
return list.filter((entry) => {
|
|
3213
|
+
if (cat !== 'all' && entry.category !== cat) {
|
|
3214
|
+
return false;
|
|
3215
|
+
}
|
|
3216
|
+
if (q && !entry.searchText.includes(q)) {
|
|
3217
|
+
return false;
|
|
3218
|
+
}
|
|
3219
|
+
return true;
|
|
3220
|
+
});
|
|
3221
|
+
}
|
|
3222
|
+
isFieldMetadata(meta) {
|
|
3223
|
+
const tags = new Set((meta.tags ?? []).map((tag) => tag.toLowerCase()));
|
|
3224
|
+
return tags.has('field') || meta.lib === '@praxisui/dynamic-fields';
|
|
3225
|
+
}
|
|
3085
3226
|
buildEntries(metadata) {
|
|
3086
|
-
|
|
3227
|
+
const pageTier = [];
|
|
3228
|
+
const fieldTier = [];
|
|
3229
|
+
for (const meta of metadata) {
|
|
3230
|
+
const isField = this.isFieldMetadata(meta);
|
|
3087
3231
|
const baseEntry = {
|
|
3088
3232
|
key: meta.id,
|
|
3089
3233
|
componentId: meta.id,
|
|
3234
|
+
category: isField ? 'field' : 'widget',
|
|
3090
3235
|
title: meta.friendlyName || meta.id,
|
|
3091
3236
|
description: meta.description || meta.selector,
|
|
3092
3237
|
icon: meta.icon || 'widgets',
|
|
@@ -3097,6 +3242,7 @@ class ComponentPaletteDialogComponent {
|
|
|
3097
3242
|
key: `${meta.id}::${preset.id}`,
|
|
3098
3243
|
componentId: meta.id,
|
|
3099
3244
|
presetId: preset.id,
|
|
3245
|
+
category: 'preset',
|
|
3100
3246
|
title: `${meta.friendlyName || meta.id} · ${preset.label}`,
|
|
3101
3247
|
description: preset.description || meta.description || meta.selector,
|
|
3102
3248
|
icon: preset.icon || meta.icon || 'widgets',
|
|
@@ -3105,8 +3251,10 @@ class ComponentPaletteDialogComponent {
|
|
|
3105
3251
|
shell: preset.shell ? JSON.parse(JSON.stringify(preset.shell)) : undefined,
|
|
3106
3252
|
inputs: preset.inputs ? JSON.parse(JSON.stringify(preset.inputs)) : undefined,
|
|
3107
3253
|
}));
|
|
3108
|
-
|
|
3109
|
-
|
|
3254
|
+
const bucket = isField ? fieldTier : pageTier;
|
|
3255
|
+
bucket.push(baseEntry, ...presets);
|
|
3256
|
+
}
|
|
3257
|
+
return [...pageTier, ...fieldTier];
|
|
3110
3258
|
}
|
|
3111
3259
|
buildBadges(meta, isPreset) {
|
|
3112
3260
|
const badges = [];
|
|
@@ -3179,49 +3327,92 @@ class ComponentPaletteDialogComponent {
|
|
|
3179
3327
|
}
|
|
3180
3328
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ComponentPaletteDialogComponent, deps: [{ token: i1.MatDialogRef }, { token: i2$2.ComponentMetadataRegistry }, { token: i2$2.PraxisI18nService }, { token: MAT_DIALOG_DATA }], target: i0.ɵɵFactoryTarget.Component });
|
|
3181
3329
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: ComponentPaletteDialogComponent, isStandalone: true, selector: "praxis-component-palette-dialog", providers: [providePraxisPageBuilderI18n()], ngImport: i0, template: `
|
|
3182
|
-
<h2 mat-dialog-title>
|
|
3330
|
+
<h2 mat-dialog-title class="pdx-dialog-header">
|
|
3183
3331
|
<div class="dlg-title">
|
|
3184
|
-
<
|
|
3332
|
+
<div class="dlg-icon-wrap" aria-hidden="true">
|
|
3333
|
+
<mat-icon class="dlg-icon" [praxisIcon]="'widgets'"></mat-icon>
|
|
3334
|
+
</div>
|
|
3185
3335
|
<div class="dlg-texts">
|
|
3186
|
-
<div class="dlg-head">
|
|
3187
|
-
|
|
3336
|
+
<div class="dlg-head-row">
|
|
3337
|
+
<span class="dlg-head">{{ data?.title || tx('palette.title', 'Inserir componente') }}</span>
|
|
3338
|
+
<span class="dlg-count-pill" aria-live="polite">{{ filtered().length }} {{ filtered().length === 1 ? tx('palette.availableSingular', 'disponível') : tx('palette.available', 'disponíveis') }}</span>
|
|
3339
|
+
</div>
|
|
3340
|
+
<div class="dlg-sub">{{ tx('palette.subtitle', 'Escolha um componente para adicionar à página') }}</div>
|
|
3188
3341
|
</div>
|
|
3189
3342
|
</div>
|
|
3190
3343
|
</h2>
|
|
3191
3344
|
<div mat-dialog-content class="pdx-palette-content">
|
|
3192
|
-
<
|
|
3193
|
-
<
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3345
|
+
<div class="pdx-search-shell">
|
|
3346
|
+
<label class="visually-hidden" for="pdx-palette-search-input">
|
|
3347
|
+
{{ tx('palette.searchLabel', 'Buscar componente') }}
|
|
3348
|
+
</label>
|
|
3349
|
+
<div class="pdx-palette-search pdx-search-bar">
|
|
3350
|
+
<mat-icon class="pdx-search-icon" [praxisIcon]="'search'" aria-hidden="true"></mat-icon>
|
|
3351
|
+
<input
|
|
3352
|
+
id="pdx-palette-search-input"
|
|
3353
|
+
class="pdx-search-input"
|
|
3354
|
+
type="text"
|
|
3355
|
+
role="searchbox"
|
|
3356
|
+
cdkFocusInitial
|
|
3357
|
+
autocomplete="off"
|
|
3358
|
+
[ngModel]="query()"
|
|
3359
|
+
(ngModelChange)="query.set($event)"
|
|
3360
|
+
(keydown)="handleSearchKeydown($event)"
|
|
3361
|
+
[placeholder]="tx('palette.searchPlaceholder', 'Nome, capability ou readiness')"
|
|
3362
|
+
data-testid="component-palette-search"
|
|
3203
3363
|
/>
|
|
3204
|
-
|
|
3205
|
-
<div class="pdx-palette-toolbar" data-testid="component-palette-toolbar">
|
|
3206
|
-
<div class="pdx-palette-span-group" role="group" [attr.aria-label]="tx('palette.initialWidth', 'Largura inicial')">
|
|
3207
|
-
<span class="pdx-palette-toolbar-label">{{ tx('palette.initialWidth', 'Largura inicial') }}:</span>
|
|
3208
|
-
@for (opt of spanOptions; track opt.value) {
|
|
3364
|
+
@if (query()) {
|
|
3209
3365
|
<button
|
|
3210
|
-
mat-stroked-button
|
|
3211
3366
|
type="button"
|
|
3212
|
-
class="pdx-
|
|
3213
|
-
[
|
|
3214
|
-
|
|
3215
|
-
[attr.data-testid]="'palette-colspan-' + (opt.value ?? 'auto')"
|
|
3216
|
-
(click)="selectedColSpan.set(opt.value)"
|
|
3367
|
+
class="pdx-search-clear"
|
|
3368
|
+
[attr.aria-label]="tx('palette.clearSearchAria', 'Limpar busca')"
|
|
3369
|
+
(click)="clearSearch()"
|
|
3217
3370
|
>
|
|
3218
|
-
|
|
3371
|
+
<mat-icon [praxisIcon]="'close'" aria-hidden="true"></mat-icon>
|
|
3219
3372
|
</button>
|
|
3220
3373
|
}
|
|
3374
|
+
<kbd class="pdx-search-kbd" aria-hidden="true">↓</kbd>
|
|
3375
|
+
</div>
|
|
3376
|
+
</div>
|
|
3377
|
+
|
|
3378
|
+
@if (hasMultipleCategories()) {
|
|
3379
|
+
<div class="pdx-category-tabs" role="group" [attr.aria-label]="tx('palette.categoryAria', 'Filtrar catálogo por categoria')" data-testid="component-palette-categories">
|
|
3380
|
+
@for (cat of categoryOptions(); track cat.id) {
|
|
3381
|
+
<button
|
|
3382
|
+
type="button"
|
|
3383
|
+
class="pdx-category-pill"
|
|
3384
|
+
[class.selected]="selectedCategory() === cat.id"
|
|
3385
|
+
[attr.aria-pressed]="selectedCategory() === cat.id"
|
|
3386
|
+
[attr.data-testid]="'palette-category-' + cat.id"
|
|
3387
|
+
(click)="selectedCategory.set(cat.id)"
|
|
3388
|
+
>
|
|
3389
|
+
<span>{{ cat.label }}</span>
|
|
3390
|
+
<span class="pdx-category-count">{{ cat.count }}</span>
|
|
3391
|
+
</button>
|
|
3392
|
+
}
|
|
3393
|
+
</div>
|
|
3394
|
+
}
|
|
3395
|
+
|
|
3396
|
+
<div class="pdx-palette-toolbar" data-testid="component-palette-toolbar">
|
|
3397
|
+
<div class="pdx-palette-span-group" role="group" [attr.aria-label]="tx('palette.initialWidth', 'Largura inicial')">
|
|
3398
|
+
<span class="pdx-palette-toolbar-label">{{ tx('palette.initialWidth', 'Largura inicial') }}</span>
|
|
3399
|
+
<div class="pdx-span-segmented">
|
|
3400
|
+
@for (opt of spanOptions(); track opt.value) {
|
|
3401
|
+
<button
|
|
3402
|
+
type="button"
|
|
3403
|
+
class="pdx-span-chip"
|
|
3404
|
+
[class.selected]="selectedColSpan() === opt.value"
|
|
3405
|
+
[attr.aria-pressed]="selectedColSpan() === opt.value"
|
|
3406
|
+
[attr.data-testid]="'palette-colspan-' + (opt.value ?? 'auto')"
|
|
3407
|
+
(click)="selectedColSpan.set(opt.value)"
|
|
3408
|
+
>
|
|
3409
|
+
{{ opt.label }}
|
|
3410
|
+
</button>
|
|
3411
|
+
}
|
|
3412
|
+
</div>
|
|
3221
3413
|
</div>
|
|
3222
3414
|
<div class="pdx-palette-mode-actions">
|
|
3223
3415
|
<button
|
|
3224
|
-
mat-stroked-button
|
|
3225
3416
|
type="button"
|
|
3226
3417
|
class="pdx-batch-mode-chip"
|
|
3227
3418
|
[class.selected]="batchMode()"
|
|
@@ -3230,13 +3421,12 @@ class ComponentPaletteDialogComponent {
|
|
|
3230
3421
|
(click)="toggleBatchMode()"
|
|
3231
3422
|
>
|
|
3232
3423
|
<mat-icon [praxisIcon]="batchMode() ? 'playlist_add_check' : 'playlist_add'" aria-hidden="true"></mat-icon>
|
|
3233
|
-
{{ tx('palette.batchMode', 'Montar em lote') }}
|
|
3424
|
+
<span>{{ tx('palette.batchMode', 'Montar em lote') }}</span>
|
|
3234
3425
|
@if (queuedItems().length > 0) {
|
|
3235
3426
|
<span class="pdx-inserted-badge" data-testid="palette-queued-count">{{ queuedItems().length }}</span>
|
|
3236
3427
|
}
|
|
3237
3428
|
</button>
|
|
3238
3429
|
<button
|
|
3239
|
-
mat-stroked-button
|
|
3240
3430
|
type="button"
|
|
3241
3431
|
class="pdx-keep-open-chip"
|
|
3242
3432
|
[class.selected]="keepOpen()"
|
|
@@ -3245,13 +3435,14 @@ class ComponentPaletteDialogComponent {
|
|
|
3245
3435
|
(click)="toggleKeepOpen()"
|
|
3246
3436
|
>
|
|
3247
3437
|
<mat-icon [praxisIcon]="keepOpen() ? 'check_box' : 'check_box_outline_blank'" aria-hidden="true"></mat-icon>
|
|
3248
|
-
{{ tx('palette.keepOpen', 'Manter aberta') }}
|
|
3438
|
+
<span>{{ tx('palette.keepOpen', 'Manter aberta') }}</span>
|
|
3249
3439
|
@if (insertedCount() > 0) {
|
|
3250
3440
|
<span class="pdx-inserted-badge" data-testid="palette-inserted-count">+{{ insertedCount() }}</span>
|
|
3251
3441
|
}
|
|
3252
3442
|
</button>
|
|
3253
3443
|
</div>
|
|
3254
3444
|
</div>
|
|
3445
|
+
|
|
3255
3446
|
@if (queuedItems().length > 0) {
|
|
3256
3447
|
<div class="pdx-staging-tray" data-testid="palette-staging-tray" role="region" [attr.aria-label]="tx('palette.stagingTrayAria', 'Bandeja de composição em lote')">
|
|
3257
3448
|
<div class="pdx-staging-header">
|
|
@@ -3299,19 +3490,20 @@ class ComponentPaletteDialogComponent {
|
|
|
3299
3490
|
</div>
|
|
3300
3491
|
</div>
|
|
3301
3492
|
}
|
|
3493
|
+
|
|
3302
3494
|
@if (filtered().length) {
|
|
3303
|
-
<div [class]="'pdx-grid ' + density()">
|
|
3495
|
+
<div [class]="'pdx-grid ' + density() + (filtered().length === 1 ? ' single-entry' : '')">
|
|
3304
3496
|
@for (entry of filtered(); track trackById($index, entry)) {
|
|
3305
3497
|
<div class="pdx-card-item">
|
|
3306
3498
|
<button
|
|
3307
3499
|
#paletteCard
|
|
3308
|
-
class="pdx-card
|
|
3500
|
+
class="pdx-card"
|
|
3309
3501
|
type="button"
|
|
3310
3502
|
[attr.aria-label]="tx('palette.addAria', 'Adicionar') + ' ' + entry.title"
|
|
3311
3503
|
(click)="select(entry)"
|
|
3312
3504
|
(keydown)="handleCardKeydown($event, $index, entry)"
|
|
3313
|
-
|
|
3314
|
-
<div class="pdx-card-icon">
|
|
3505
|
+
>
|
|
3506
|
+
<div class="pdx-card-icon" aria-hidden="true">
|
|
3315
3507
|
<mat-icon [praxisIcon]="entry.icon"></mat-icon>
|
|
3316
3508
|
</div>
|
|
3317
3509
|
<div class="pdx-card-body">
|
|
@@ -3326,9 +3518,9 @@ class ComponentPaletteDialogComponent {
|
|
|
3326
3518
|
class="pdx-card-badge"
|
|
3327
3519
|
[class]="'pdx-card-badge ' + badge.tone"
|
|
3328
3520
|
[title]="badge.description"
|
|
3329
|
-
|
|
3521
|
+
>
|
|
3330
3522
|
<mat-icon [praxisIcon]="badge.icon" aria-hidden="true"></mat-icon>
|
|
3331
|
-
{{ badge.label }}
|
|
3523
|
+
<span>{{ badge.label }}</span>
|
|
3332
3524
|
</span>
|
|
3333
3525
|
}
|
|
3334
3526
|
</div>
|
|
@@ -3343,83 +3535,150 @@ class ComponentPaletteDialogComponent {
|
|
|
3343
3535
|
[title]="tx('palette.queueAddTooltip', 'Adicionar à fila de composição em lote (Shift+Enter)')"
|
|
3344
3536
|
(click)="enqueue(entry, $event)"
|
|
3345
3537
|
>
|
|
3346
|
-
|
|
3538
|
+
<span aria-hidden="true">+</span>
|
|
3539
|
+
<span>{{ tx('palette.queueAdd', 'Fila') }}</span>
|
|
3347
3540
|
</button>
|
|
3348
3541
|
</div>
|
|
3349
3542
|
}
|
|
3350
3543
|
</div>
|
|
3351
3544
|
} @else {
|
|
3352
|
-
<div class="pdx-empty">
|
|
3353
|
-
<
|
|
3354
|
-
|
|
3545
|
+
<div class="pdx-empty" role="status" aria-live="polite">
|
|
3546
|
+
<div class="pdx-empty-icon-wrap" aria-hidden="true">
|
|
3547
|
+
<mat-icon [praxisIcon]="'search_off'"></mat-icon>
|
|
3548
|
+
</div>
|
|
3549
|
+
<div class="pdx-empty-title">{{ tx('palette.empty', 'Nenhum componente disponível') }}</div>
|
|
3550
|
+
@if (query() || selectedCategory() !== 'all') {
|
|
3551
|
+
<div class="pdx-empty-sub">{{ tx('palette.emptySearchHint', 'Tente outro termo ou limpe os filtros ativos para exibir todo o catálogo.') }}</div>
|
|
3552
|
+
<button
|
|
3553
|
+
type="button"
|
|
3554
|
+
class="pdx-empty-reset-btn"
|
|
3555
|
+
data-testid="palette-empty-reset-filters"
|
|
3556
|
+
(click)="resetFilters()"
|
|
3557
|
+
>
|
|
3558
|
+
<mat-icon [praxisIcon]="'filter_alt_off'" aria-hidden="true"></mat-icon>
|
|
3559
|
+
<span>{{ tx('palette.clearFilters', 'Limpar filtros') }}</span>
|
|
3560
|
+
</button>
|
|
3561
|
+
}
|
|
3355
3562
|
</div>
|
|
3356
3563
|
}
|
|
3357
|
-
|
|
3358
3564
|
</div>
|
|
3359
|
-
<div mat-dialog-actions align="end">
|
|
3360
|
-
<
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
<
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
(
|
|
3370
|
-
>
|
|
3371
|
-
{{ tx('palette.commitBatchPrefix', 'Inserir') }} {{ queuedItems().length }} {{ queuedItems().length === 1 ? tx('palette.componentSingular', 'componente') : tx('palette.componentPlural', 'componentes') }}
|
|
3565
|
+
<div mat-dialog-actions class="pdx-dialog-footer" align="end">
|
|
3566
|
+
<div class="pdx-footer-hints" aria-hidden="true">
|
|
3567
|
+
<span class="pdx-kbd-hint"><kbd>↵</kbd> {{ tx('palette.kbdInsert', 'inserir') }}</span>
|
|
3568
|
+
<span class="pdx-kbd-sep">·</span>
|
|
3569
|
+
<span class="pdx-kbd-hint"><kbd>Shift+↵</kbd> {{ tx('palette.kbdQueue', 'fila') }}</span>
|
|
3570
|
+
<span class="pdx-kbd-sep">·</span>
|
|
3571
|
+
<span class="pdx-kbd-hint"><kbd>↑↓</kbd> {{ tx('palette.kbdNavigate', 'navegar') }}</span>
|
|
3572
|
+
</div>
|
|
3573
|
+
<div class="pdx-footer-actions">
|
|
3574
|
+
<button mat-stroked-button color="primary" class="pdx-footer-cancel-btn" mat-dialog-close data-testid="component-palette-close">
|
|
3575
|
+
{{ queuedItems().length > 0 ? tx('palette.discardQueue', 'Descartar fila') : (insertedCount() > 0 ? tx('palette.done', 'Concluir') : tx('palette.cancel', 'Cancelar')) }}
|
|
3372
3576
|
</button>
|
|
3373
|
-
|
|
3577
|
+
@if (queuedItems().length > 0) {
|
|
3578
|
+
<button
|
|
3579
|
+
mat-flat-button
|
|
3580
|
+
color="primary"
|
|
3581
|
+
type="button"
|
|
3582
|
+
class="pdx-footer-commit-btn"
|
|
3583
|
+
data-testid="component-palette-commit-batch"
|
|
3584
|
+
(click)="commitBatch()"
|
|
3585
|
+
>
|
|
3586
|
+
{{ tx('palette.commitBatchPrefix', 'Inserir') }} {{ queuedItems().length }} {{ queuedItems().length === 1 ? tx('palette.componentSingular', 'componente') : tx('palette.componentPlural', 'componentes') }}
|
|
3587
|
+
</button>
|
|
3588
|
+
}
|
|
3589
|
+
</div>
|
|
3374
3590
|
</div>
|
|
3375
|
-
`, isInline: true, styles: [":host{display:block}h2[mat-dialog-title]{margin:0;padding:12px 24px 10px;border-bottom:1px solid var(--md-sys-color-outline-variant)}.dlg-title{display:flex;align-items:center;gap:10px}.dlg-icon{color:var(--md-sys-color-primary)}.dlg-head{font-size:18px;font-weight:600;line-height:1.2}.dlg-sub{font-size:12px;opacity:.85;color:var(--md-sys-color-on-surface-variant)}.pdx-palette-content{min-width:480px;max-width:920px;padding-top:8px}.pdx-palette-search{width:100%;margin-bottom:8px}.pdx-palette-toolbar{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:8px;margin-bottom:12px}.pdx-palette-span-group,.pdx-palette-mode-actions{display:flex;flex-wrap:wrap;align-items:center;gap:6px}.pdx-palette-toolbar-label{font-size:12px;color:var(--md-sys-color-on-surface-variant);margin-right:4px}.pdx-span-chip,.pdx-keep-open-chip,.pdx-batch-mode-chip{min-height:32px;padding:0 10px;font-size:12px;border-radius:999px}.pdx-span-chip.selected,.pdx-keep-open-chip.selected,.pdx-batch-mode-chip.selected{background:var(--md-sys-color-secondary-container);color:var(--md-sys-color-on-secondary-container);border-color:var(--md-sys-color-primary)}.pdx-inserted-badge{display:inline-flex;align-items:center;justify-content:center;margin-left:6px;padding:1px 6px;border-radius:999px;background:var(--md-sys-color-primary);color:var(--md-sys-color-on-primary);font-size:11px;font-weight:700}.pdx-staging-tray{margin-bottom:12px;padding:10px 12px;border-radius:12px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 38%,var(--md-sys-color-outline-variant));background:color-mix(in srgb,var(--md-sys-color-primary-container) 22%,var(--md-sys-color-surface-container-low))}.pdx-staging-header{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px;font-size:12px}.pdx-staging-summary{display:flex;align-items:center;gap:6px;color:var(--md-sys-color-on-surface)}.pdx-staging-summary mat-icon{width:16px;height:16px;font-size:16px;color:var(--md-sys-color-primary)}.pdx-staging-clear{min-height:26px;padding:0 8px;font-size:11px}.pdx-staging-list{display:flex;flex-wrap:wrap;gap:6px;max-height:112px;overflow-y:auto}.pdx-staging-chip{display:inline-flex;align-items:center;gap:5px;padding:3px 8px;border-radius:999px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface);font-size:11px;line-height:1.2}.pdx-staging-chip mat-icon{width:14px;height:14px;font-size:14px;color:var(--md-sys-color-primary)}.pdx-staging-index{font-weight:700;color:var(--md-sys-color-primary)}.pdx-staging-title{max-width:140px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:600}.pdx-staging-coords{appearance:none;border:1px solid transparent;padding:1px 6px;border-radius:6px;background:var(--md-sys-color-surface-container-highest);color:var(--md-sys-color-on-surface-variant);font-family:monospace;font-size:10px;cursor:pointer;transition:background .15s ease,color .15s ease,border-color .15s ease}.pdx-staging-coords:hover,.pdx-staging-coords:focus-visible{background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container);border-color:var(--md-sys-color-primary)}.pdx-staging-remove{appearance:none;border:0;background:transparent;color:var(--md-sys-color-on-surface-variant);cursor:pointer;font-size:14px;line-height:1;padding:0 2px;border-radius:50%}.pdx-staging-remove:hover{color:var(--md-sys-color-error)}.pdx-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:14px}.pdx-grid.dense{grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:12px}.pdx-grid.roomy{grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:16px}.pdx-card-item{position:relative;display:flex;flex-direction:column}.pdx-card{appearance:none;position:relative;width:100%;flex:1;display:grid;grid-template-columns:44px 1fr;grid-template-rows:auto auto;gap:10px;padding:14px;border:1px solid transparent;border-radius:14px;cursor:pointer;outline:none;color:inherit;font:inherit;min-height:92px;text-align:left;background:var(--md-sys-color-surface);border-color:var(--md-sys-color-outline-variant);transition:box-shadow .2s ease,background .25s ease,transform .06s ease}.pdx-grid.dense .pdx-card{padding:10px;border-radius:12px;min-height:84px}.pdx-grid.roomy .pdx-card{padding:16px;border-radius:16px;min-height:100px}.pdx-card:focus,.pdx-card:hover{box-shadow:var(--mat-elevation-level4);background:var(--md-sys-color-surface-container-low);border-color:var(--md-sys-color-primary)}.pdx-card:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.pdx-card:active{transform:translateY(1px)}.pdx-card-icon{grid-row:span 2;display:flex;align-items:center;justify-content:center;color:var(--md-sys-color-primary)}.pdx-card-head-row{display:flex;align-items:center;justify-content:space-between;gap:6px;padding-right:48px}.pdx-card-title{font-weight:600;line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pdx-card-queue-btn{appearance:none;position:absolute;top:10px;right:10px;z-index:2;flex-shrink:0;padding:2px 7px;border-radius:999px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 40%,var(--md-sys-color-outline-variant));background:color-mix(in srgb,var(--md-sys-color-primary-container) 45%,var(--md-sys-color-surface));color:var(--md-sys-color-primary);font-size:10px;font-weight:700;line-height:1.2;cursor:pointer;transition:background .15s ease,color .15s ease}.pdx-card-queue-btn:hover,.pdx-card-queue-btn:focus-visible{background:var(--md-sys-color-primary);color:var(--md-sys-color-on-primary)}.pdx-card-desc{color:var(--md-sys-color-on-surface-variant);display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;word-break:break-word}.pdx-card-badges{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}.pdx-card-badge{display:inline-flex;align-items:center;gap:4px;min-height:22px;padding:2px 8px;border-radius:999px;border:1px solid var(--md-sys-color-outline-variant);color:var(--md-sys-color-on-surface-variant);background:var(--md-sys-color-surface-container-highest);font-size:11px;line-height:1.2;font-weight:600;letter-spacing:.01em}.pdx-card-badge mat-icon{width:14px;height:14px;font-size:14px}.pdx-card-badge.ready{color:var(--md-sys-color-primary);border-color:color-mix(in srgb,var(--md-sys-color-primary) 45%,transparent);background:color-mix(in srgb,var(--md-sys-color-primary-container) 72%,transparent)}.pdx-card-badge.info{color:var(--md-sys-color-tertiary);border-color:color-mix(in srgb,var(--md-sys-color-tertiary) 38%,transparent);background:color-mix(in srgb,var(--md-sys-color-tertiary-container) 68%,transparent)}.pdx-card-badge.warning{color:var(--md-sys-color-error);border-color:color-mix(in srgb,var(--md-sys-color-error) 35%,transparent);background:color-mix(in srgb,var(--md-sys-color-error-container) 62%,transparent)}.pdx-empty{padding:24px;display:grid;place-items:center;color:var(--md-sys-color-on-surface-variant);gap:8px}@media(max-width:600px){.pdx-palette-content{min-width:320px}.pdx-grid{grid-template-columns:repeat(auto-fill,minmax(200px,1fr))}}\n"], dependencies: [{ 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.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i1.MatDialogClose, selector: "[mat-dialog-close], [matDialogClose]", inputs: ["aria-label", "type", "mat-dialog-close", "matDialogClose"], exportAs: ["matDialogClose"] }, { kind: "directive", type: i1.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i1.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i6.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i6.MatLabel, selector: "mat-label" }, { kind: "directive", type: i6.MatPrefix, selector: "[matPrefix], [matIconPrefix], [matTextPrefix]", inputs: ["matTextPrefix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i8.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: MatIconModule }, { kind: "component", type: i2$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.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: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3591
|
+
`, isInline: true, styles: [":host{display:block;color:var(--md-sys-color-on-surface)}.visually-hidden:where(:not(:focus-within,:active)){position:absolute!important;clip-path:inset(50%)!important;overflow:hidden!important;width:1px!important;height:1px!important;margin:-1px!important;padding:0!important;border:0!important;white-space:nowrap!important}h2[mat-dialog-title].pdx-dialog-header{margin:0;padding:18px 24px 16px;border-bottom:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 70%,transparent);background:color-mix(in srgb,var(--md-sys-color-surface-container-low) 55%,var(--md-sys-color-surface))}h2[mat-dialog-title].pdx-dialog-header:before{display:none;content:none}.dlg-title{display:flex;align-items:center;gap:14px}.dlg-icon-wrap{width:40px;height:40px;flex-shrink:0;border-radius:12px;display:flex;align-items:center;justify-content:center;background:color-mix(in srgb,var(--md-sys-color-primary-container) 62%,var(--md-sys-color-surface));border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 26%,transparent);color:var(--md-sys-color-primary)}.dlg-icon{width:22px;height:22px;font-size:22px;color:var(--md-sys-color-primary)}.dlg-texts{min-width:0;flex:1;display:flex;flex-direction:column;gap:3px}.dlg-head-row{display:flex;align-items:center;flex-wrap:wrap;gap:10px}.dlg-head{font-size:1.125rem;font-weight:700;letter-spacing:-.015em;line-height:1.25;color:var(--md-sys-color-on-surface)}.dlg-count-pill{display:inline-flex;align-items:center;padding:2px 9px;border-radius:999px;font-size:.72rem;font-weight:600;line-height:1.3;color:var(--md-sys-color-on-secondary-container);background:color-mix(in srgb,var(--md-sys-color-secondary-container) 78%,transparent);border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 60%,transparent)}.dlg-sub{font-size:.8125rem;line-height:1.35;color:var(--md-sys-color-on-surface-variant)}.pdx-palette-content{min-width:560px;max-width:920px;padding:16px 24px 18px!important;display:flex;flex-direction:column;gap:12px}.pdx-search-shell{width:100%}.pdx-search-bar{width:100%;min-height:44px;padding:0 12px 0 14px;border-radius:12px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 90%,transparent);background:color-mix(in srgb,var(--md-sys-color-surface-container-low) 75%,var(--md-sys-color-surface));display:flex;align-items:center;gap:10px;box-sizing:border-box;transition:border-color .16s ease,box-shadow .16s ease,background-color .16s ease}.pdx-search-bar:hover{border-color:color-mix(in srgb,var(--md-sys-color-on-surface) 32%,var(--md-sys-color-outline-variant))}.pdx-search-bar:focus-within{background:var(--md-sys-color-surface);border-color:var(--md-sys-color-primary);box-shadow:0 0 0 3px color-mix(in srgb,var(--md-sys-color-primary) 14%,transparent)}.pdx-search-icon{width:20px;height:20px;font-size:20px;flex-shrink:0;color:var(--md-sys-color-on-surface-variant);transition:color .16s ease}.pdx-search-bar:focus-within .pdx-search-icon{color:var(--md-sys-color-primary)}.pdx-search-input{flex:1;min-width:0;border:0;outline:none;background:transparent;color:var(--md-sys-color-on-surface);font:inherit;font-size:.925rem;line-height:1.4;padding:8px 0}.pdx-search-input::placeholder{color:color-mix(in srgb,var(--md-sys-color-on-surface-variant) 78%,transparent)}.pdx-search-input::-webkit-search-cancel-button{-webkit-appearance:none;appearance:none}.pdx-search-clear{appearance:none;width:26px;height:26px;border-radius:7px;border:0;background:transparent;color:var(--md-sys-color-on-surface-variant);display:inline-flex;align-items:center;justify-content:center;cursor:pointer;padding:0;transition:background-color .15s ease,color .15s ease}.pdx-search-clear mat-icon{width:16px;height:16px;font-size:16px}.pdx-search-clear:hover,.pdx-search-clear:focus-visible{background:var(--md-sys-color-surface-container-highest);color:var(--md-sys-color-on-surface);outline:none}.pdx-search-kbd{display:inline-flex;align-items:center;justify-content:center;min-width:22px;height:22px;padding:0 6px;border-radius:6px;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.72rem;font-weight:600;color:var(--md-sys-color-on-surface-variant);background:var(--md-sys-color-surface-container-high);border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 80%,transparent);flex-shrink:0}.pdx-category-tabs{display:flex;flex-wrap:wrap;align-items:center;gap:6px}.pdx-category-pill{appearance:none;min-height:28px;padding:0 10px;border-radius:999px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 75%,transparent);background:color-mix(in srgb,var(--md-sys-color-surface-container-low) 65%,var(--md-sys-color-surface));color:var(--md-sys-color-on-surface-variant);font-family:inherit;font-size:.75rem;font-weight:600;display:inline-flex;align-items:center;gap:6px;cursor:pointer;transition:background-color .15s ease,color .15s ease,border-color .15s ease}.pdx-category-pill:hover{color:var(--md-sys-color-on-surface);border-color:color-mix(in srgb,var(--md-sys-color-primary) 35%,var(--md-sys-color-outline-variant))}.pdx-category-pill:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.pdx-category-pill.selected{background:color-mix(in srgb,var(--md-sys-color-primary-container) 78%,var(--md-sys-color-surface));color:var(--md-sys-color-on-primary-container);border-color:color-mix(in srgb,var(--md-sys-color-primary) 52%,transparent);font-weight:700}.pdx-category-count{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:16px;padding:0 5px;border-radius:999px;font-size:.675rem;font-weight:700;background:color-mix(in srgb,var(--md-sys-color-surface-container-highest) 75%,transparent);color:currentColor}.pdx-category-pill.selected .pdx-category-count{background:color-mix(in srgb,var(--md-sys-color-primary) 22%,transparent)}.pdx-palette-toolbar{display:flex;flex-wrap:wrap;align-items:flex-end;justify-content:space-between;gap:10px 14px;padding:2px 0 10px;border-bottom:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 55%,transparent)}.pdx-palette-span-group{display:flex;flex-direction:column;align-items:flex-start;gap:5px}.pdx-palette-toolbar-label{font-size:.68rem;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--md-sys-color-on-surface-variant);white-space:nowrap;padding-left:2px}.pdx-span-segmented{display:inline-flex;flex-wrap:wrap;align-items:center;gap:2px;padding:3px;border-radius:9px;background:color-mix(in srgb,var(--md-sys-color-surface-container-highest) 60%,transparent);border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 55%,transparent)}.pdx-span-chip{appearance:none;min-height:28px;padding:0 8px;font-family:inherit;font-size:.74rem;font-weight:600;line-height:1;border-radius:6px;border:1px solid transparent;background:transparent;color:var(--md-sys-color-on-surface-variant);cursor:pointer;white-space:nowrap;transition:background-color .15s ease,color .15s ease,border-color .15s ease,box-shadow .15s ease}.pdx-span-chip:hover{color:var(--md-sys-color-on-surface);background:color-mix(in srgb,var(--md-sys-color-surface) 65%,transparent)}.pdx-span-chip:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:1px}.pdx-span-chip.selected{background:var(--md-sys-color-secondary-container);color:var(--md-sys-color-on-secondary-container);border-color:color-mix(in srgb,var(--md-sys-color-primary) 42%,transparent);font-weight:700;box-shadow:0 1px 2px #0000000f}.pdx-palette-mode-actions{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-left:auto}.pdx-keep-open-chip,.pdx-batch-mode-chip{appearance:none;min-height:30px;padding:0 10px;font-family:inherit;font-size:.75rem;font-weight:600;border-radius:8px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 85%,transparent);background:var(--md-sys-color-surface);color:var(--md-sys-color-on-surface-variant);display:inline-flex;align-items:center;gap:5px;cursor:pointer;white-space:nowrap;transition:background-color .15s ease,color .15s ease,border-color .15s ease}.pdx-keep-open-chip mat-icon,.pdx-batch-mode-chip mat-icon{width:16px;height:16px;font-size:16px;color:currentColor}.pdx-keep-open-chip:hover,.pdx-batch-mode-chip:hover{color:var(--md-sys-color-on-surface);border-color:color-mix(in srgb,var(--md-sys-color-primary) 40%,var(--md-sys-color-outline-variant));background:var(--md-sys-color-surface-container-low)}.pdx-keep-open-chip:focus-visible,.pdx-batch-mode-chip:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.pdx-keep-open-chip.selected,.pdx-batch-mode-chip.selected{background:var(--md-sys-color-secondary-container);color:var(--md-sys-color-on-secondary-container);border-color:var(--md-sys-color-primary);font-weight:700}.pdx-inserted-badge{display:inline-flex;align-items:center;justify-content:center;margin-left:2px;padding:1px 6px;border-radius:999px;background:var(--md-sys-color-primary);color:var(--md-sys-color-on-primary);font-size:10px;font-weight:700;line-height:1.3}.pdx-staging-tray{padding:10px 12px;border-radius:12px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 38%,var(--md-sys-color-outline-variant));background:color-mix(in srgb,var(--md-sys-color-primary-container) 22%,var(--md-sys-color-surface-container-low))}.pdx-staging-header{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px;font-size:12px}.pdx-staging-summary{display:flex;align-items:center;gap:6px;color:var(--md-sys-color-on-surface)}.pdx-staging-summary mat-icon{width:16px;height:16px;font-size:16px;color:var(--md-sys-color-primary)}.pdx-staging-clear{min-height:26px;padding:0 8px;font-size:11px}.pdx-staging-list{display:flex;flex-wrap:wrap;gap:6px;max-height:112px;overflow-y:auto}.pdx-staging-chip{display:inline-flex;align-items:center;gap:5px;padding:3px 8px;border-radius:999px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface);font-size:11px;line-height:1.2}.pdx-staging-chip mat-icon{width:14px;height:14px;font-size:14px;color:var(--md-sys-color-primary)}.pdx-staging-index{font-weight:700;color:var(--md-sys-color-primary)}.pdx-staging-title{max-width:140px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:600}.pdx-staging-coords{appearance:none;border:1px solid transparent;padding:2px 6px;border-radius:6px;background:var(--md-sys-color-surface-container-highest);color:var(--md-sys-color-on-surface-variant);font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:10px;cursor:pointer;transition:background .15s ease,color .15s ease,border-color .15s ease}.pdx-staging-coords:hover,.pdx-staging-coords:focus-visible{background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container);border-color:var(--md-sys-color-primary)}.pdx-staging-remove{appearance:none;border:0;background:transparent;color:var(--md-sys-color-on-surface-variant);cursor:pointer;font-size:14px;line-height:1;padding:0 3px;border-radius:50%}.pdx-staging-remove:hover{color:var(--md-sys-color-error)}.pdx-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px}.pdx-grid.dense{grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:10px}.pdx-grid.roomy{grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:14px}.pdx-grid.single-entry{grid-template-columns:minmax(0,1fr)}.pdx-card-item{position:relative;display:flex;flex-direction:column}.pdx-card{appearance:none;position:relative;width:100%;flex:1;display:grid;grid-template-columns:44px minmax(0,1fr);align-items:start;gap:14px;padding:16px;border-radius:14px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 85%,transparent);cursor:pointer;outline:none;color:inherit;font:inherit;min-height:96px;text-align:left;background:var(--md-sys-color-surface);box-shadow:0 1px 2px #0000000a;transition:box-shadow .18s ease,background-color .18s ease,border-color .18s ease,transform .08s ease}.pdx-grid.dense .pdx-card{padding:12px 14px;border-radius:12px;min-height:86px}.pdx-grid.roomy .pdx-card{padding:16px 18px;border-radius:14px;min-height:100px}.pdx-card:focus,.pdx-card:hover{box-shadow:0 6px 18px #00000014;background:color-mix(in srgb,var(--md-sys-color-surface-container-low) 70%,var(--md-sys-color-surface));border-color:var(--md-sys-color-primary)}.pdx-card:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.pdx-card:active{transform:translateY(1px)}.pdx-card-icon{width:44px;height:44px;border-radius:12px;align-self:start;display:flex;align-items:center;justify-content:center;color:var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary-container) 50%,var(--md-sys-color-surface-container-low));border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 22%,transparent);transition:background-color .18s ease,border-color .18s ease,transform .18s ease}.pdx-card:hover .pdx-card-icon,.pdx-card:focus-visible .pdx-card-icon{background:color-mix(in srgb,var(--md-sys-color-primary-container) 82%,var(--md-sys-color-surface));border-color:color-mix(in srgb,var(--md-sys-color-primary) 42%,transparent)}.pdx-card-icon mat-icon{width:22px;height:22px;font-size:22px}.pdx-card-body{min-width:0;display:flex;flex-direction:column;gap:4px}.pdx-card-head-row{display:flex;align-items:flex-start;justify-content:space-between;gap:8px;padding-right:64px}.pdx-card-title{font-size:.9375rem;font-weight:650;letter-spacing:-.01em;line-height:1.3;color:var(--md-sys-color-on-surface);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pdx-card-queue-btn{appearance:none;position:absolute;top:14px;right:14px;z-index:2;flex-shrink:0;min-height:26px;min-width:52px;padding:0 10px;border-radius:999px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 36%,var(--md-sys-color-outline-variant));background:color-mix(in srgb,var(--md-sys-color-primary-container) 42%,var(--md-sys-color-surface));color:var(--md-sys-color-primary);font-family:inherit;font-size:.6875rem;font-weight:700;line-height:1;display:inline-flex;align-items:center;justify-content:center;gap:3px;cursor:pointer;transition:background-color .15s ease,color .15s ease,border-color .15s ease}.pdx-card-queue-btn:hover,.pdx-card-queue-btn:focus-visible{background:var(--md-sys-color-primary);color:var(--md-sys-color-on-primary);border-color:var(--md-sys-color-primary);outline:none}.pdx-card-desc{font-size:.8125rem;line-height:1.48;color:var(--md-sys-color-on-surface-variant);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;word-break:break-word}.pdx-card-badges{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px}.pdx-card-badge{display:inline-flex;align-items:center;gap:4px;min-height:22px;padding:2px 8px;border-radius:6px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 85%,transparent);color:var(--md-sys-color-on-surface-variant);background:var(--md-sys-color-surface-container-high);font-size:.6875rem;line-height:1.2;font-weight:600;letter-spacing:.01em}.pdx-card-badge mat-icon{width:13px;height:13px;font-size:13px;flex-shrink:0}.pdx-card-badge.ready{color:color-mix(in srgb,var(--md-sys-color-on-primary-container) 75%,var(--md-sys-color-primary));border-color:color-mix(in srgb,var(--md-sys-color-primary) 36%,transparent);background:color-mix(in srgb,var(--md-sys-color-primary-container) 68%,transparent)}.pdx-card-badge.info{color:color-mix(in srgb,var(--md-sys-color-on-tertiary-container) 75%,var(--md-sys-color-tertiary));border-color:color-mix(in srgb,var(--md-sys-color-tertiary) 34%,transparent);background:color-mix(in srgb,var(--md-sys-color-tertiary-container) 62%,transparent)}.pdx-card-badge.warning{color:color-mix(in srgb,var(--md-sys-color-on-error-container) 75%,var(--md-sys-color-error));border-color:color-mix(in srgb,var(--md-sys-color-error) 34%,transparent);background:color-mix(in srgb,var(--md-sys-color-error-container) 60%,transparent)}.pdx-empty{padding:36px 24px;border-radius:14px;border:1px dashed color-mix(in srgb,var(--md-sys-color-outline-variant) 80%,transparent);background:color-mix(in srgb,var(--md-sys-color-surface-container-low) 50%,transparent);display:grid;place-items:center;color:var(--md-sys-color-on-surface-variant);gap:10px;text-align:center}.pdx-empty-icon-wrap{width:44px;height:44px;border-radius:12px;display:flex;align-items:center;justify-content:center;background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface-variant)}.pdx-empty-title{font-size:.875rem;font-weight:600;color:var(--md-sys-color-on-surface)}.pdx-empty-sub{font-size:.8rem;max-width:380px;line-height:1.4;color:var(--md-sys-color-on-surface-variant)}.pdx-empty-reset-btn{appearance:none;margin-top:4px;min-height:32px;padding:0 12px;border-radius:8px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 42%,var(--md-sys-color-outline-variant));background:color-mix(in srgb,var(--md-sys-color-primary-container) 45%,var(--md-sys-color-surface));color:var(--md-sys-color-primary);font-family:inherit;font-size:.775rem;font-weight:600;display:inline-flex;align-items:center;gap:6px;cursor:pointer;transition:background-color .15s ease,color .15s ease}.pdx-empty-reset-btn mat-icon{width:16px;height:16px;font-size:16px}.pdx-empty-reset-btn:hover,.pdx-empty-reset-btn:focus-visible{background:var(--md-sys-color-primary);color:var(--md-sys-color-on-primary);outline:none}div[mat-dialog-actions].pdx-dialog-footer{margin:0;min-height:56px;padding:12px 24px 16px;border-top:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 70%,transparent);background:color-mix(in srgb,var(--md-sys-color-surface-container-low) 45%,var(--md-sys-color-surface));display:flex;align-items:center;justify-content:space-between;gap:12px}.pdx-footer-hints{display:flex;align-items:center;flex-wrap:wrap;gap:6px;font-size:.72rem;color:var(--md-sys-color-on-surface-variant)}.pdx-kbd-hint{display:inline-flex;align-items:center;gap:4px}.pdx-kbd-hint kbd{display:inline-flex;align-items:center;padding:1px 5px;border-radius:5px;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.68rem;font-weight:600;color:var(--md-sys-color-on-surface);background:var(--md-sys-color-surface-container-high);border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 80%,transparent)}.pdx-kbd-sep{opacity:.55}.pdx-footer-actions{display:flex;align-items:center;gap:8px;margin-left:auto}.pdx-footer-cancel-btn,.pdx-footer-commit-btn{border-radius:10px;min-height:36px;padding:0 16px;font-weight:600}@media(max-width:680px){.pdx-palette-content{min-width:300px;padding:14px 16px!important}h2[mat-dialog-title].pdx-dialog-header{padding:14px 16px}div[mat-dialog-actions].pdx-dialog-footer{padding:10px 16px 14px}.pdx-footer-hints{display:none}.pdx-palette-mode-actions{margin-left:0;width:100%}.pdx-grid{grid-template-columns:repeat(auto-fill,minmax(220px,1fr))}}\n"], dependencies: [{ kind: "ngmodule", type: A11yModule }, { 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.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i1.MatDialogClose, selector: "[mat-dialog-close], [matDialogClose]", inputs: ["aria-label", "type", "mat-dialog-close", "matDialogClose"], exportAs: ["matDialogClose"] }, { kind: "directive", type: i1.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i1.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.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: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3376
3592
|
}
|
|
3377
3593
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ComponentPaletteDialogComponent, decorators: [{
|
|
3378
3594
|
type: Component,
|
|
3379
|
-
args: [{ selector: 'praxis-component-palette-dialog', standalone: true, imports: [FormsModule, MatDialogModule,
|
|
3380
|
-
<h2 mat-dialog-title>
|
|
3595
|
+
args: [{ selector: 'praxis-component-palette-dialog', standalone: true, imports: [A11yModule, FormsModule, MatDialogModule, MatIconModule, MatButtonModule, PraxisIconDirective], template: `
|
|
3596
|
+
<h2 mat-dialog-title class="pdx-dialog-header">
|
|
3381
3597
|
<div class="dlg-title">
|
|
3382
|
-
<
|
|
3598
|
+
<div class="dlg-icon-wrap" aria-hidden="true">
|
|
3599
|
+
<mat-icon class="dlg-icon" [praxisIcon]="'widgets'"></mat-icon>
|
|
3600
|
+
</div>
|
|
3383
3601
|
<div class="dlg-texts">
|
|
3384
|
-
<div class="dlg-head">
|
|
3385
|
-
|
|
3602
|
+
<div class="dlg-head-row">
|
|
3603
|
+
<span class="dlg-head">{{ data?.title || tx('palette.title', 'Inserir componente') }}</span>
|
|
3604
|
+
<span class="dlg-count-pill" aria-live="polite">{{ filtered().length }} {{ filtered().length === 1 ? tx('palette.availableSingular', 'disponível') : tx('palette.available', 'disponíveis') }}</span>
|
|
3605
|
+
</div>
|
|
3606
|
+
<div class="dlg-sub">{{ tx('palette.subtitle', 'Escolha um componente para adicionar à página') }}</div>
|
|
3386
3607
|
</div>
|
|
3387
3608
|
</div>
|
|
3388
3609
|
</h2>
|
|
3389
3610
|
<div mat-dialog-content class="pdx-palette-content">
|
|
3390
|
-
<
|
|
3391
|
-
<
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3611
|
+
<div class="pdx-search-shell">
|
|
3612
|
+
<label class="visually-hidden" for="pdx-palette-search-input">
|
|
3613
|
+
{{ tx('palette.searchLabel', 'Buscar componente') }}
|
|
3614
|
+
</label>
|
|
3615
|
+
<div class="pdx-palette-search pdx-search-bar">
|
|
3616
|
+
<mat-icon class="pdx-search-icon" [praxisIcon]="'search'" aria-hidden="true"></mat-icon>
|
|
3617
|
+
<input
|
|
3618
|
+
id="pdx-palette-search-input"
|
|
3619
|
+
class="pdx-search-input"
|
|
3620
|
+
type="text"
|
|
3621
|
+
role="searchbox"
|
|
3622
|
+
cdkFocusInitial
|
|
3623
|
+
autocomplete="off"
|
|
3624
|
+
[ngModel]="query()"
|
|
3625
|
+
(ngModelChange)="query.set($event)"
|
|
3626
|
+
(keydown)="handleSearchKeydown($event)"
|
|
3627
|
+
[placeholder]="tx('palette.searchPlaceholder', 'Nome, capability ou readiness')"
|
|
3628
|
+
data-testid="component-palette-search"
|
|
3401
3629
|
/>
|
|
3402
|
-
|
|
3403
|
-
<div class="pdx-palette-toolbar" data-testid="component-palette-toolbar">
|
|
3404
|
-
<div class="pdx-palette-span-group" role="group" [attr.aria-label]="tx('palette.initialWidth', 'Largura inicial')">
|
|
3405
|
-
<span class="pdx-palette-toolbar-label">{{ tx('palette.initialWidth', 'Largura inicial') }}:</span>
|
|
3406
|
-
@for (opt of spanOptions; track opt.value) {
|
|
3630
|
+
@if (query()) {
|
|
3407
3631
|
<button
|
|
3408
|
-
mat-stroked-button
|
|
3409
3632
|
type="button"
|
|
3410
|
-
class="pdx-
|
|
3411
|
-
[
|
|
3412
|
-
|
|
3413
|
-
[attr.data-testid]="'palette-colspan-' + (opt.value ?? 'auto')"
|
|
3414
|
-
(click)="selectedColSpan.set(opt.value)"
|
|
3633
|
+
class="pdx-search-clear"
|
|
3634
|
+
[attr.aria-label]="tx('palette.clearSearchAria', 'Limpar busca')"
|
|
3635
|
+
(click)="clearSearch()"
|
|
3415
3636
|
>
|
|
3416
|
-
|
|
3637
|
+
<mat-icon [praxisIcon]="'close'" aria-hidden="true"></mat-icon>
|
|
3417
3638
|
</button>
|
|
3418
3639
|
}
|
|
3640
|
+
<kbd class="pdx-search-kbd" aria-hidden="true">↓</kbd>
|
|
3641
|
+
</div>
|
|
3642
|
+
</div>
|
|
3643
|
+
|
|
3644
|
+
@if (hasMultipleCategories()) {
|
|
3645
|
+
<div class="pdx-category-tabs" role="group" [attr.aria-label]="tx('palette.categoryAria', 'Filtrar catálogo por categoria')" data-testid="component-palette-categories">
|
|
3646
|
+
@for (cat of categoryOptions(); track cat.id) {
|
|
3647
|
+
<button
|
|
3648
|
+
type="button"
|
|
3649
|
+
class="pdx-category-pill"
|
|
3650
|
+
[class.selected]="selectedCategory() === cat.id"
|
|
3651
|
+
[attr.aria-pressed]="selectedCategory() === cat.id"
|
|
3652
|
+
[attr.data-testid]="'palette-category-' + cat.id"
|
|
3653
|
+
(click)="selectedCategory.set(cat.id)"
|
|
3654
|
+
>
|
|
3655
|
+
<span>{{ cat.label }}</span>
|
|
3656
|
+
<span class="pdx-category-count">{{ cat.count }}</span>
|
|
3657
|
+
</button>
|
|
3658
|
+
}
|
|
3659
|
+
</div>
|
|
3660
|
+
}
|
|
3661
|
+
|
|
3662
|
+
<div class="pdx-palette-toolbar" data-testid="component-palette-toolbar">
|
|
3663
|
+
<div class="pdx-palette-span-group" role="group" [attr.aria-label]="tx('palette.initialWidth', 'Largura inicial')">
|
|
3664
|
+
<span class="pdx-palette-toolbar-label">{{ tx('palette.initialWidth', 'Largura inicial') }}</span>
|
|
3665
|
+
<div class="pdx-span-segmented">
|
|
3666
|
+
@for (opt of spanOptions(); track opt.value) {
|
|
3667
|
+
<button
|
|
3668
|
+
type="button"
|
|
3669
|
+
class="pdx-span-chip"
|
|
3670
|
+
[class.selected]="selectedColSpan() === opt.value"
|
|
3671
|
+
[attr.aria-pressed]="selectedColSpan() === opt.value"
|
|
3672
|
+
[attr.data-testid]="'palette-colspan-' + (opt.value ?? 'auto')"
|
|
3673
|
+
(click)="selectedColSpan.set(opt.value)"
|
|
3674
|
+
>
|
|
3675
|
+
{{ opt.label }}
|
|
3676
|
+
</button>
|
|
3677
|
+
}
|
|
3678
|
+
</div>
|
|
3419
3679
|
</div>
|
|
3420
3680
|
<div class="pdx-palette-mode-actions">
|
|
3421
3681
|
<button
|
|
3422
|
-
mat-stroked-button
|
|
3423
3682
|
type="button"
|
|
3424
3683
|
class="pdx-batch-mode-chip"
|
|
3425
3684
|
[class.selected]="batchMode()"
|
|
@@ -3428,13 +3687,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
3428
3687
|
(click)="toggleBatchMode()"
|
|
3429
3688
|
>
|
|
3430
3689
|
<mat-icon [praxisIcon]="batchMode() ? 'playlist_add_check' : 'playlist_add'" aria-hidden="true"></mat-icon>
|
|
3431
|
-
{{ tx('palette.batchMode', 'Montar em lote') }}
|
|
3690
|
+
<span>{{ tx('palette.batchMode', 'Montar em lote') }}</span>
|
|
3432
3691
|
@if (queuedItems().length > 0) {
|
|
3433
3692
|
<span class="pdx-inserted-badge" data-testid="palette-queued-count">{{ queuedItems().length }}</span>
|
|
3434
3693
|
}
|
|
3435
3694
|
</button>
|
|
3436
3695
|
<button
|
|
3437
|
-
mat-stroked-button
|
|
3438
3696
|
type="button"
|
|
3439
3697
|
class="pdx-keep-open-chip"
|
|
3440
3698
|
[class.selected]="keepOpen()"
|
|
@@ -3443,13 +3701,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
3443
3701
|
(click)="toggleKeepOpen()"
|
|
3444
3702
|
>
|
|
3445
3703
|
<mat-icon [praxisIcon]="keepOpen() ? 'check_box' : 'check_box_outline_blank'" aria-hidden="true"></mat-icon>
|
|
3446
|
-
{{ tx('palette.keepOpen', 'Manter aberta') }}
|
|
3704
|
+
<span>{{ tx('palette.keepOpen', 'Manter aberta') }}</span>
|
|
3447
3705
|
@if (insertedCount() > 0) {
|
|
3448
3706
|
<span class="pdx-inserted-badge" data-testid="palette-inserted-count">+{{ insertedCount() }}</span>
|
|
3449
3707
|
}
|
|
3450
3708
|
</button>
|
|
3451
3709
|
</div>
|
|
3452
3710
|
</div>
|
|
3711
|
+
|
|
3453
3712
|
@if (queuedItems().length > 0) {
|
|
3454
3713
|
<div class="pdx-staging-tray" data-testid="palette-staging-tray" role="region" [attr.aria-label]="tx('palette.stagingTrayAria', 'Bandeja de composição em lote')">
|
|
3455
3714
|
<div class="pdx-staging-header">
|
|
@@ -3497,19 +3756,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
3497
3756
|
</div>
|
|
3498
3757
|
</div>
|
|
3499
3758
|
}
|
|
3759
|
+
|
|
3500
3760
|
@if (filtered().length) {
|
|
3501
|
-
<div [class]="'pdx-grid ' + density()">
|
|
3761
|
+
<div [class]="'pdx-grid ' + density() + (filtered().length === 1 ? ' single-entry' : '')">
|
|
3502
3762
|
@for (entry of filtered(); track trackById($index, entry)) {
|
|
3503
3763
|
<div class="pdx-card-item">
|
|
3504
3764
|
<button
|
|
3505
3765
|
#paletteCard
|
|
3506
|
-
class="pdx-card
|
|
3766
|
+
class="pdx-card"
|
|
3507
3767
|
type="button"
|
|
3508
3768
|
[attr.aria-label]="tx('palette.addAria', 'Adicionar') + ' ' + entry.title"
|
|
3509
3769
|
(click)="select(entry)"
|
|
3510
3770
|
(keydown)="handleCardKeydown($event, $index, entry)"
|
|
3511
|
-
|
|
3512
|
-
<div class="pdx-card-icon">
|
|
3771
|
+
>
|
|
3772
|
+
<div class="pdx-card-icon" aria-hidden="true">
|
|
3513
3773
|
<mat-icon [praxisIcon]="entry.icon"></mat-icon>
|
|
3514
3774
|
</div>
|
|
3515
3775
|
<div class="pdx-card-body">
|
|
@@ -3524,9 +3784,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
3524
3784
|
class="pdx-card-badge"
|
|
3525
3785
|
[class]="'pdx-card-badge ' + badge.tone"
|
|
3526
3786
|
[title]="badge.description"
|
|
3527
|
-
|
|
3787
|
+
>
|
|
3528
3788
|
<mat-icon [praxisIcon]="badge.icon" aria-hidden="true"></mat-icon>
|
|
3529
|
-
{{ badge.label }}
|
|
3789
|
+
<span>{{ badge.label }}</span>
|
|
3530
3790
|
</span>
|
|
3531
3791
|
}
|
|
3532
3792
|
</div>
|
|
@@ -3541,36 +3801,60 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
3541
3801
|
[title]="tx('palette.queueAddTooltip', 'Adicionar à fila de composição em lote (Shift+Enter)')"
|
|
3542
3802
|
(click)="enqueue(entry, $event)"
|
|
3543
3803
|
>
|
|
3544
|
-
|
|
3804
|
+
<span aria-hidden="true">+</span>
|
|
3805
|
+
<span>{{ tx('palette.queueAdd', 'Fila') }}</span>
|
|
3545
3806
|
</button>
|
|
3546
3807
|
</div>
|
|
3547
3808
|
}
|
|
3548
3809
|
</div>
|
|
3549
3810
|
} @else {
|
|
3550
|
-
<div class="pdx-empty">
|
|
3551
|
-
<
|
|
3552
|
-
|
|
3811
|
+
<div class="pdx-empty" role="status" aria-live="polite">
|
|
3812
|
+
<div class="pdx-empty-icon-wrap" aria-hidden="true">
|
|
3813
|
+
<mat-icon [praxisIcon]="'search_off'"></mat-icon>
|
|
3814
|
+
</div>
|
|
3815
|
+
<div class="pdx-empty-title">{{ tx('palette.empty', 'Nenhum componente disponível') }}</div>
|
|
3816
|
+
@if (query() || selectedCategory() !== 'all') {
|
|
3817
|
+
<div class="pdx-empty-sub">{{ tx('palette.emptySearchHint', 'Tente outro termo ou limpe os filtros ativos para exibir todo o catálogo.') }}</div>
|
|
3818
|
+
<button
|
|
3819
|
+
type="button"
|
|
3820
|
+
class="pdx-empty-reset-btn"
|
|
3821
|
+
data-testid="palette-empty-reset-filters"
|
|
3822
|
+
(click)="resetFilters()"
|
|
3823
|
+
>
|
|
3824
|
+
<mat-icon [praxisIcon]="'filter_alt_off'" aria-hidden="true"></mat-icon>
|
|
3825
|
+
<span>{{ tx('palette.clearFilters', 'Limpar filtros') }}</span>
|
|
3826
|
+
</button>
|
|
3827
|
+
}
|
|
3553
3828
|
</div>
|
|
3554
3829
|
}
|
|
3555
|
-
|
|
3556
3830
|
</div>
|
|
3557
|
-
<div mat-dialog-actions align="end">
|
|
3558
|
-
<
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
<
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
(
|
|
3568
|
-
>
|
|
3569
|
-
{{ tx('palette.commitBatchPrefix', 'Inserir') }} {{ queuedItems().length }} {{ queuedItems().length === 1 ? tx('palette.componentSingular', 'componente') : tx('palette.componentPlural', 'componentes') }}
|
|
3831
|
+
<div mat-dialog-actions class="pdx-dialog-footer" align="end">
|
|
3832
|
+
<div class="pdx-footer-hints" aria-hidden="true">
|
|
3833
|
+
<span class="pdx-kbd-hint"><kbd>↵</kbd> {{ tx('palette.kbdInsert', 'inserir') }}</span>
|
|
3834
|
+
<span class="pdx-kbd-sep">·</span>
|
|
3835
|
+
<span class="pdx-kbd-hint"><kbd>Shift+↵</kbd> {{ tx('palette.kbdQueue', 'fila') }}</span>
|
|
3836
|
+
<span class="pdx-kbd-sep">·</span>
|
|
3837
|
+
<span class="pdx-kbd-hint"><kbd>↑↓</kbd> {{ tx('palette.kbdNavigate', 'navegar') }}</span>
|
|
3838
|
+
</div>
|
|
3839
|
+
<div class="pdx-footer-actions">
|
|
3840
|
+
<button mat-stroked-button color="primary" class="pdx-footer-cancel-btn" mat-dialog-close data-testid="component-palette-close">
|
|
3841
|
+
{{ queuedItems().length > 0 ? tx('palette.discardQueue', 'Descartar fila') : (insertedCount() > 0 ? tx('palette.done', 'Concluir') : tx('palette.cancel', 'Cancelar')) }}
|
|
3570
3842
|
</button>
|
|
3571
|
-
|
|
3843
|
+
@if (queuedItems().length > 0) {
|
|
3844
|
+
<button
|
|
3845
|
+
mat-flat-button
|
|
3846
|
+
color="primary"
|
|
3847
|
+
type="button"
|
|
3848
|
+
class="pdx-footer-commit-btn"
|
|
3849
|
+
data-testid="component-palette-commit-batch"
|
|
3850
|
+
(click)="commitBatch()"
|
|
3851
|
+
>
|
|
3852
|
+
{{ tx('palette.commitBatchPrefix', 'Inserir') }} {{ queuedItems().length }} {{ queuedItems().length === 1 ? tx('palette.componentSingular', 'componente') : tx('palette.componentPlural', 'componentes') }}
|
|
3853
|
+
</button>
|
|
3854
|
+
}
|
|
3855
|
+
</div>
|
|
3572
3856
|
</div>
|
|
3573
|
-
`, providers: [providePraxisPageBuilderI18n()], changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block}h2[mat-dialog-title]{margin:0;padding:12px 24px 10px;border-bottom:1px solid var(--md-sys-color-outline-variant)}.dlg-title{display:flex;align-items:center;gap:10px}.dlg-icon{color:var(--md-sys-color-primary)}.dlg-head{font-size:18px;font-weight:600;line-height:1.2}.dlg-sub{font-size:12px;opacity:.85;color:var(--md-sys-color-on-surface-variant)}.pdx-palette-content{min-width:480px;max-width:920px;padding-top:8px}.pdx-palette-search{width:100%;margin-bottom:8px}.pdx-palette-toolbar{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:8px;margin-bottom:12px}.pdx-palette-span-group,.pdx-palette-mode-actions{display:flex;flex-wrap:wrap;align-items:center;gap:6px}.pdx-palette-toolbar-label{font-size:12px;color:var(--md-sys-color-on-surface-variant);margin-right:4px}.pdx-span-chip,.pdx-keep-open-chip,.pdx-batch-mode-chip{min-height:32px;padding:0 10px;font-size:12px;border-radius:999px}.pdx-span-chip.selected,.pdx-keep-open-chip.selected,.pdx-batch-mode-chip.selected{background:var(--md-sys-color-secondary-container);color:var(--md-sys-color-on-secondary-container);border-color:var(--md-sys-color-primary)}.pdx-inserted-badge{display:inline-flex;align-items:center;justify-content:center;margin-left:6px;padding:1px 6px;border-radius:999px;background:var(--md-sys-color-primary);color:var(--md-sys-color-on-primary);font-size:11px;font-weight:700}.pdx-staging-tray{margin-bottom:12px;padding:10px 12px;border-radius:12px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 38%,var(--md-sys-color-outline-variant));background:color-mix(in srgb,var(--md-sys-color-primary-container) 22%,var(--md-sys-color-surface-container-low))}.pdx-staging-header{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px;font-size:12px}.pdx-staging-summary{display:flex;align-items:center;gap:6px;color:var(--md-sys-color-on-surface)}.pdx-staging-summary mat-icon{width:16px;height:16px;font-size:16px;color:var(--md-sys-color-primary)}.pdx-staging-clear{min-height:26px;padding:0 8px;font-size:11px}.pdx-staging-list{display:flex;flex-wrap:wrap;gap:6px;max-height:112px;overflow-y:auto}.pdx-staging-chip{display:inline-flex;align-items:center;gap:5px;padding:3px 8px;border-radius:999px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface);font-size:11px;line-height:1.2}.pdx-staging-chip mat-icon{width:14px;height:14px;font-size:14px;color:var(--md-sys-color-primary)}.pdx-staging-index{font-weight:700;color:var(--md-sys-color-primary)}.pdx-staging-title{max-width:140px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:600}.pdx-staging-coords{appearance:none;border:1px solid transparent;padding:1px 6px;border-radius:6px;background:var(--md-sys-color-surface-container-highest);color:var(--md-sys-color-on-surface-variant);font-family:monospace;font-size:10px;cursor:pointer;transition:background .15s ease,color .15s ease,border-color .15s ease}.pdx-staging-coords:hover,.pdx-staging-coords:focus-visible{background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container);border-color:var(--md-sys-color-primary)}.pdx-staging-remove{appearance:none;border:0;background:transparent;color:var(--md-sys-color-on-surface-variant);cursor:pointer;font-size:14px;line-height:1;padding:0 2px;border-radius:50%}.pdx-staging-remove:hover{color:var(--md-sys-color-error)}.pdx-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:14px}.pdx-grid.dense{grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:12px}.pdx-grid.roomy{grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:16px}.pdx-card-item{position:relative;display:flex;flex-direction:column}.pdx-card{appearance:none;position:relative;width:100%;flex:1;display:grid;grid-template-columns:44px 1fr;grid-template-rows:auto auto;gap:10px;padding:14px;border:1px solid transparent;border-radius:14px;cursor:pointer;outline:none;color:inherit;font:inherit;min-height:92px;text-align:left;background:var(--md-sys-color-surface);border-color:var(--md-sys-color-outline-variant);transition:box-shadow .2s ease,background .25s ease,transform .06s ease}.pdx-grid.dense .pdx-card{padding:10px;border-radius:12px;min-height:84px}.pdx-grid.roomy .pdx-card{padding:16px;border-radius:16px;min-height:100px}.pdx-card:focus,.pdx-card:hover{box-shadow:var(--mat-elevation-level4);background:var(--md-sys-color-surface-container-low);border-color:var(--md-sys-color-primary)}.pdx-card:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.pdx-card:active{transform:translateY(1px)}.pdx-card-icon{grid-row:span 2;display:flex;align-items:center;justify-content:center;color:var(--md-sys-color-primary)}.pdx-card-head-row{display:flex;align-items:center;justify-content:space-between;gap:6px;padding-right:48px}.pdx-card-title{font-weight:600;line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pdx-card-queue-btn{appearance:none;position:absolute;top:10px;right:10px;z-index:2;flex-shrink:0;padding:2px 7px;border-radius:999px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 40%,var(--md-sys-color-outline-variant));background:color-mix(in srgb,var(--md-sys-color-primary-container) 45%,var(--md-sys-color-surface));color:var(--md-sys-color-primary);font-size:10px;font-weight:700;line-height:1.2;cursor:pointer;transition:background .15s ease,color .15s ease}.pdx-card-queue-btn:hover,.pdx-card-queue-btn:focus-visible{background:var(--md-sys-color-primary);color:var(--md-sys-color-on-primary)}.pdx-card-desc{color:var(--md-sys-color-on-surface-variant);display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;word-break:break-word}.pdx-card-badges{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}.pdx-card-badge{display:inline-flex;align-items:center;gap:4px;min-height:22px;padding:2px 8px;border-radius:999px;border:1px solid var(--md-sys-color-outline-variant);color:var(--md-sys-color-on-surface-variant);background:var(--md-sys-color-surface-container-highest);font-size:11px;line-height:1.2;font-weight:600;letter-spacing:.01em}.pdx-card-badge mat-icon{width:14px;height:14px;font-size:14px}.pdx-card-badge.ready{color:var(--md-sys-color-primary);border-color:color-mix(in srgb,var(--md-sys-color-primary) 45%,transparent);background:color-mix(in srgb,var(--md-sys-color-primary-container) 72%,transparent)}.pdx-card-badge.info{color:var(--md-sys-color-tertiary);border-color:color-mix(in srgb,var(--md-sys-color-tertiary) 38%,transparent);background:color-mix(in srgb,var(--md-sys-color-tertiary-container) 68%,transparent)}.pdx-card-badge.warning{color:var(--md-sys-color-error);border-color:color-mix(in srgb,var(--md-sys-color-error) 35%,transparent);background:color-mix(in srgb,var(--md-sys-color-error-container) 62%,transparent)}.pdx-empty{padding:24px;display:grid;place-items:center;color:var(--md-sys-color-on-surface-variant);gap:8px}@media(max-width:600px){.pdx-palette-content{min-width:320px}.pdx-grid{grid-template-columns:repeat(auto-fill,minmax(200px,1fr))}}\n"] }]
|
|
3857
|
+
`, providers: [providePraxisPageBuilderI18n()], changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;color:var(--md-sys-color-on-surface)}.visually-hidden:where(:not(:focus-within,:active)){position:absolute!important;clip-path:inset(50%)!important;overflow:hidden!important;width:1px!important;height:1px!important;margin:-1px!important;padding:0!important;border:0!important;white-space:nowrap!important}h2[mat-dialog-title].pdx-dialog-header{margin:0;padding:18px 24px 16px;border-bottom:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 70%,transparent);background:color-mix(in srgb,var(--md-sys-color-surface-container-low) 55%,var(--md-sys-color-surface))}h2[mat-dialog-title].pdx-dialog-header:before{display:none;content:none}.dlg-title{display:flex;align-items:center;gap:14px}.dlg-icon-wrap{width:40px;height:40px;flex-shrink:0;border-radius:12px;display:flex;align-items:center;justify-content:center;background:color-mix(in srgb,var(--md-sys-color-primary-container) 62%,var(--md-sys-color-surface));border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 26%,transparent);color:var(--md-sys-color-primary)}.dlg-icon{width:22px;height:22px;font-size:22px;color:var(--md-sys-color-primary)}.dlg-texts{min-width:0;flex:1;display:flex;flex-direction:column;gap:3px}.dlg-head-row{display:flex;align-items:center;flex-wrap:wrap;gap:10px}.dlg-head{font-size:1.125rem;font-weight:700;letter-spacing:-.015em;line-height:1.25;color:var(--md-sys-color-on-surface)}.dlg-count-pill{display:inline-flex;align-items:center;padding:2px 9px;border-radius:999px;font-size:.72rem;font-weight:600;line-height:1.3;color:var(--md-sys-color-on-secondary-container);background:color-mix(in srgb,var(--md-sys-color-secondary-container) 78%,transparent);border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 60%,transparent)}.dlg-sub{font-size:.8125rem;line-height:1.35;color:var(--md-sys-color-on-surface-variant)}.pdx-palette-content{min-width:560px;max-width:920px;padding:16px 24px 18px!important;display:flex;flex-direction:column;gap:12px}.pdx-search-shell{width:100%}.pdx-search-bar{width:100%;min-height:44px;padding:0 12px 0 14px;border-radius:12px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 90%,transparent);background:color-mix(in srgb,var(--md-sys-color-surface-container-low) 75%,var(--md-sys-color-surface));display:flex;align-items:center;gap:10px;box-sizing:border-box;transition:border-color .16s ease,box-shadow .16s ease,background-color .16s ease}.pdx-search-bar:hover{border-color:color-mix(in srgb,var(--md-sys-color-on-surface) 32%,var(--md-sys-color-outline-variant))}.pdx-search-bar:focus-within{background:var(--md-sys-color-surface);border-color:var(--md-sys-color-primary);box-shadow:0 0 0 3px color-mix(in srgb,var(--md-sys-color-primary) 14%,transparent)}.pdx-search-icon{width:20px;height:20px;font-size:20px;flex-shrink:0;color:var(--md-sys-color-on-surface-variant);transition:color .16s ease}.pdx-search-bar:focus-within .pdx-search-icon{color:var(--md-sys-color-primary)}.pdx-search-input{flex:1;min-width:0;border:0;outline:none;background:transparent;color:var(--md-sys-color-on-surface);font:inherit;font-size:.925rem;line-height:1.4;padding:8px 0}.pdx-search-input::placeholder{color:color-mix(in srgb,var(--md-sys-color-on-surface-variant) 78%,transparent)}.pdx-search-input::-webkit-search-cancel-button{-webkit-appearance:none;appearance:none}.pdx-search-clear{appearance:none;width:26px;height:26px;border-radius:7px;border:0;background:transparent;color:var(--md-sys-color-on-surface-variant);display:inline-flex;align-items:center;justify-content:center;cursor:pointer;padding:0;transition:background-color .15s ease,color .15s ease}.pdx-search-clear mat-icon{width:16px;height:16px;font-size:16px}.pdx-search-clear:hover,.pdx-search-clear:focus-visible{background:var(--md-sys-color-surface-container-highest);color:var(--md-sys-color-on-surface);outline:none}.pdx-search-kbd{display:inline-flex;align-items:center;justify-content:center;min-width:22px;height:22px;padding:0 6px;border-radius:6px;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.72rem;font-weight:600;color:var(--md-sys-color-on-surface-variant);background:var(--md-sys-color-surface-container-high);border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 80%,transparent);flex-shrink:0}.pdx-category-tabs{display:flex;flex-wrap:wrap;align-items:center;gap:6px}.pdx-category-pill{appearance:none;min-height:28px;padding:0 10px;border-radius:999px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 75%,transparent);background:color-mix(in srgb,var(--md-sys-color-surface-container-low) 65%,var(--md-sys-color-surface));color:var(--md-sys-color-on-surface-variant);font-family:inherit;font-size:.75rem;font-weight:600;display:inline-flex;align-items:center;gap:6px;cursor:pointer;transition:background-color .15s ease,color .15s ease,border-color .15s ease}.pdx-category-pill:hover{color:var(--md-sys-color-on-surface);border-color:color-mix(in srgb,var(--md-sys-color-primary) 35%,var(--md-sys-color-outline-variant))}.pdx-category-pill:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.pdx-category-pill.selected{background:color-mix(in srgb,var(--md-sys-color-primary-container) 78%,var(--md-sys-color-surface));color:var(--md-sys-color-on-primary-container);border-color:color-mix(in srgb,var(--md-sys-color-primary) 52%,transparent);font-weight:700}.pdx-category-count{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:16px;padding:0 5px;border-radius:999px;font-size:.675rem;font-weight:700;background:color-mix(in srgb,var(--md-sys-color-surface-container-highest) 75%,transparent);color:currentColor}.pdx-category-pill.selected .pdx-category-count{background:color-mix(in srgb,var(--md-sys-color-primary) 22%,transparent)}.pdx-palette-toolbar{display:flex;flex-wrap:wrap;align-items:flex-end;justify-content:space-between;gap:10px 14px;padding:2px 0 10px;border-bottom:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 55%,transparent)}.pdx-palette-span-group{display:flex;flex-direction:column;align-items:flex-start;gap:5px}.pdx-palette-toolbar-label{font-size:.68rem;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--md-sys-color-on-surface-variant);white-space:nowrap;padding-left:2px}.pdx-span-segmented{display:inline-flex;flex-wrap:wrap;align-items:center;gap:2px;padding:3px;border-radius:9px;background:color-mix(in srgb,var(--md-sys-color-surface-container-highest) 60%,transparent);border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 55%,transparent)}.pdx-span-chip{appearance:none;min-height:28px;padding:0 8px;font-family:inherit;font-size:.74rem;font-weight:600;line-height:1;border-radius:6px;border:1px solid transparent;background:transparent;color:var(--md-sys-color-on-surface-variant);cursor:pointer;white-space:nowrap;transition:background-color .15s ease,color .15s ease,border-color .15s ease,box-shadow .15s ease}.pdx-span-chip:hover{color:var(--md-sys-color-on-surface);background:color-mix(in srgb,var(--md-sys-color-surface) 65%,transparent)}.pdx-span-chip:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:1px}.pdx-span-chip.selected{background:var(--md-sys-color-secondary-container);color:var(--md-sys-color-on-secondary-container);border-color:color-mix(in srgb,var(--md-sys-color-primary) 42%,transparent);font-weight:700;box-shadow:0 1px 2px #0000000f}.pdx-palette-mode-actions{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-left:auto}.pdx-keep-open-chip,.pdx-batch-mode-chip{appearance:none;min-height:30px;padding:0 10px;font-family:inherit;font-size:.75rem;font-weight:600;border-radius:8px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 85%,transparent);background:var(--md-sys-color-surface);color:var(--md-sys-color-on-surface-variant);display:inline-flex;align-items:center;gap:5px;cursor:pointer;white-space:nowrap;transition:background-color .15s ease,color .15s ease,border-color .15s ease}.pdx-keep-open-chip mat-icon,.pdx-batch-mode-chip mat-icon{width:16px;height:16px;font-size:16px;color:currentColor}.pdx-keep-open-chip:hover,.pdx-batch-mode-chip:hover{color:var(--md-sys-color-on-surface);border-color:color-mix(in srgb,var(--md-sys-color-primary) 40%,var(--md-sys-color-outline-variant));background:var(--md-sys-color-surface-container-low)}.pdx-keep-open-chip:focus-visible,.pdx-batch-mode-chip:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.pdx-keep-open-chip.selected,.pdx-batch-mode-chip.selected{background:var(--md-sys-color-secondary-container);color:var(--md-sys-color-on-secondary-container);border-color:var(--md-sys-color-primary);font-weight:700}.pdx-inserted-badge{display:inline-flex;align-items:center;justify-content:center;margin-left:2px;padding:1px 6px;border-radius:999px;background:var(--md-sys-color-primary);color:var(--md-sys-color-on-primary);font-size:10px;font-weight:700;line-height:1.3}.pdx-staging-tray{padding:10px 12px;border-radius:12px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 38%,var(--md-sys-color-outline-variant));background:color-mix(in srgb,var(--md-sys-color-primary-container) 22%,var(--md-sys-color-surface-container-low))}.pdx-staging-header{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px;font-size:12px}.pdx-staging-summary{display:flex;align-items:center;gap:6px;color:var(--md-sys-color-on-surface)}.pdx-staging-summary mat-icon{width:16px;height:16px;font-size:16px;color:var(--md-sys-color-primary)}.pdx-staging-clear{min-height:26px;padding:0 8px;font-size:11px}.pdx-staging-list{display:flex;flex-wrap:wrap;gap:6px;max-height:112px;overflow-y:auto}.pdx-staging-chip{display:inline-flex;align-items:center;gap:5px;padding:3px 8px;border-radius:999px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface);font-size:11px;line-height:1.2}.pdx-staging-chip mat-icon{width:14px;height:14px;font-size:14px;color:var(--md-sys-color-primary)}.pdx-staging-index{font-weight:700;color:var(--md-sys-color-primary)}.pdx-staging-title{max-width:140px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:600}.pdx-staging-coords{appearance:none;border:1px solid transparent;padding:2px 6px;border-radius:6px;background:var(--md-sys-color-surface-container-highest);color:var(--md-sys-color-on-surface-variant);font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:10px;cursor:pointer;transition:background .15s ease,color .15s ease,border-color .15s ease}.pdx-staging-coords:hover,.pdx-staging-coords:focus-visible{background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container);border-color:var(--md-sys-color-primary)}.pdx-staging-remove{appearance:none;border:0;background:transparent;color:var(--md-sys-color-on-surface-variant);cursor:pointer;font-size:14px;line-height:1;padding:0 3px;border-radius:50%}.pdx-staging-remove:hover{color:var(--md-sys-color-error)}.pdx-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px}.pdx-grid.dense{grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:10px}.pdx-grid.roomy{grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:14px}.pdx-grid.single-entry{grid-template-columns:minmax(0,1fr)}.pdx-card-item{position:relative;display:flex;flex-direction:column}.pdx-card{appearance:none;position:relative;width:100%;flex:1;display:grid;grid-template-columns:44px minmax(0,1fr);align-items:start;gap:14px;padding:16px;border-radius:14px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 85%,transparent);cursor:pointer;outline:none;color:inherit;font:inherit;min-height:96px;text-align:left;background:var(--md-sys-color-surface);box-shadow:0 1px 2px #0000000a;transition:box-shadow .18s ease,background-color .18s ease,border-color .18s ease,transform .08s ease}.pdx-grid.dense .pdx-card{padding:12px 14px;border-radius:12px;min-height:86px}.pdx-grid.roomy .pdx-card{padding:16px 18px;border-radius:14px;min-height:100px}.pdx-card:focus,.pdx-card:hover{box-shadow:0 6px 18px #00000014;background:color-mix(in srgb,var(--md-sys-color-surface-container-low) 70%,var(--md-sys-color-surface));border-color:var(--md-sys-color-primary)}.pdx-card:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.pdx-card:active{transform:translateY(1px)}.pdx-card-icon{width:44px;height:44px;border-radius:12px;align-self:start;display:flex;align-items:center;justify-content:center;color:var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary-container) 50%,var(--md-sys-color-surface-container-low));border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 22%,transparent);transition:background-color .18s ease,border-color .18s ease,transform .18s ease}.pdx-card:hover .pdx-card-icon,.pdx-card:focus-visible .pdx-card-icon{background:color-mix(in srgb,var(--md-sys-color-primary-container) 82%,var(--md-sys-color-surface));border-color:color-mix(in srgb,var(--md-sys-color-primary) 42%,transparent)}.pdx-card-icon mat-icon{width:22px;height:22px;font-size:22px}.pdx-card-body{min-width:0;display:flex;flex-direction:column;gap:4px}.pdx-card-head-row{display:flex;align-items:flex-start;justify-content:space-between;gap:8px;padding-right:64px}.pdx-card-title{font-size:.9375rem;font-weight:650;letter-spacing:-.01em;line-height:1.3;color:var(--md-sys-color-on-surface);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pdx-card-queue-btn{appearance:none;position:absolute;top:14px;right:14px;z-index:2;flex-shrink:0;min-height:26px;min-width:52px;padding:0 10px;border-radius:999px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 36%,var(--md-sys-color-outline-variant));background:color-mix(in srgb,var(--md-sys-color-primary-container) 42%,var(--md-sys-color-surface));color:var(--md-sys-color-primary);font-family:inherit;font-size:.6875rem;font-weight:700;line-height:1;display:inline-flex;align-items:center;justify-content:center;gap:3px;cursor:pointer;transition:background-color .15s ease,color .15s ease,border-color .15s ease}.pdx-card-queue-btn:hover,.pdx-card-queue-btn:focus-visible{background:var(--md-sys-color-primary);color:var(--md-sys-color-on-primary);border-color:var(--md-sys-color-primary);outline:none}.pdx-card-desc{font-size:.8125rem;line-height:1.48;color:var(--md-sys-color-on-surface-variant);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;word-break:break-word}.pdx-card-badges{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px}.pdx-card-badge{display:inline-flex;align-items:center;gap:4px;min-height:22px;padding:2px 8px;border-radius:6px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 85%,transparent);color:var(--md-sys-color-on-surface-variant);background:var(--md-sys-color-surface-container-high);font-size:.6875rem;line-height:1.2;font-weight:600;letter-spacing:.01em}.pdx-card-badge mat-icon{width:13px;height:13px;font-size:13px;flex-shrink:0}.pdx-card-badge.ready{color:color-mix(in srgb,var(--md-sys-color-on-primary-container) 75%,var(--md-sys-color-primary));border-color:color-mix(in srgb,var(--md-sys-color-primary) 36%,transparent);background:color-mix(in srgb,var(--md-sys-color-primary-container) 68%,transparent)}.pdx-card-badge.info{color:color-mix(in srgb,var(--md-sys-color-on-tertiary-container) 75%,var(--md-sys-color-tertiary));border-color:color-mix(in srgb,var(--md-sys-color-tertiary) 34%,transparent);background:color-mix(in srgb,var(--md-sys-color-tertiary-container) 62%,transparent)}.pdx-card-badge.warning{color:color-mix(in srgb,var(--md-sys-color-on-error-container) 75%,var(--md-sys-color-error));border-color:color-mix(in srgb,var(--md-sys-color-error) 34%,transparent);background:color-mix(in srgb,var(--md-sys-color-error-container) 60%,transparent)}.pdx-empty{padding:36px 24px;border-radius:14px;border:1px dashed color-mix(in srgb,var(--md-sys-color-outline-variant) 80%,transparent);background:color-mix(in srgb,var(--md-sys-color-surface-container-low) 50%,transparent);display:grid;place-items:center;color:var(--md-sys-color-on-surface-variant);gap:10px;text-align:center}.pdx-empty-icon-wrap{width:44px;height:44px;border-radius:12px;display:flex;align-items:center;justify-content:center;background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface-variant)}.pdx-empty-title{font-size:.875rem;font-weight:600;color:var(--md-sys-color-on-surface)}.pdx-empty-sub{font-size:.8rem;max-width:380px;line-height:1.4;color:var(--md-sys-color-on-surface-variant)}.pdx-empty-reset-btn{appearance:none;margin-top:4px;min-height:32px;padding:0 12px;border-radius:8px;border:1px solid color-mix(in srgb,var(--md-sys-color-primary) 42%,var(--md-sys-color-outline-variant));background:color-mix(in srgb,var(--md-sys-color-primary-container) 45%,var(--md-sys-color-surface));color:var(--md-sys-color-primary);font-family:inherit;font-size:.775rem;font-weight:600;display:inline-flex;align-items:center;gap:6px;cursor:pointer;transition:background-color .15s ease,color .15s ease}.pdx-empty-reset-btn mat-icon{width:16px;height:16px;font-size:16px}.pdx-empty-reset-btn:hover,.pdx-empty-reset-btn:focus-visible{background:var(--md-sys-color-primary);color:var(--md-sys-color-on-primary);outline:none}div[mat-dialog-actions].pdx-dialog-footer{margin:0;min-height:56px;padding:12px 24px 16px;border-top:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 70%,transparent);background:color-mix(in srgb,var(--md-sys-color-surface-container-low) 45%,var(--md-sys-color-surface));display:flex;align-items:center;justify-content:space-between;gap:12px}.pdx-footer-hints{display:flex;align-items:center;flex-wrap:wrap;gap:6px;font-size:.72rem;color:var(--md-sys-color-on-surface-variant)}.pdx-kbd-hint{display:inline-flex;align-items:center;gap:4px}.pdx-kbd-hint kbd{display:inline-flex;align-items:center;padding:1px 5px;border-radius:5px;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.68rem;font-weight:600;color:var(--md-sys-color-on-surface);background:var(--md-sys-color-surface-container-high);border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 80%,transparent)}.pdx-kbd-sep{opacity:.55}.pdx-footer-actions{display:flex;align-items:center;gap:8px;margin-left:auto}.pdx-footer-cancel-btn,.pdx-footer-commit-btn{border-radius:10px;min-height:36px;padding:0 16px;font-weight:600}@media(max-width:680px){.pdx-palette-content{min-width:300px;padding:14px 16px!important}h2[mat-dialog-title].pdx-dialog-header{padding:14px 16px}div[mat-dialog-actions].pdx-dialog-footer{padding:10px 16px 14px}.pdx-footer-hints{display:none}.pdx-palette-mode-actions{margin-left:0;width:100%}.pdx-grid{grid-template-columns:repeat(auto-fill,minmax(220px,1fr))}}\n"] }]
|
|
3574
3858
|
}], ctorParameters: () => [{ type: i1.MatDialogRef }, { type: i2$2.ComponentMetadataRegistry }, { type: i2$2.PraxisI18nService }, { type: undefined, decorators: [{
|
|
3575
3859
|
type: Inject,
|
|
3576
3860
|
args: [MAT_DIALOG_DATA]
|
|
@@ -3868,7 +4152,7 @@ class WidgetShellEditorComponent {
|
|
|
3868
4152
|
return this.i18n.t('praxis.pageBuilder.shell.stickyHeader');
|
|
3869
4153
|
}
|
|
3870
4154
|
form = new FormGroup({
|
|
3871
|
-
kind: new FormControl('
|
|
4155
|
+
kind: new FormControl('none', {
|
|
3872
4156
|
nonNullable: true,
|
|
3873
4157
|
}),
|
|
3874
4158
|
preset: new FormControl('', { nonNullable: true }),
|
|
@@ -3891,6 +4175,7 @@ class WidgetShellEditorComponent {
|
|
|
3891
4175
|
cardBorderStyle: new FormControl('', { nonNullable: true }),
|
|
3892
4176
|
cardRadius: new FormControl('', { nonNullable: true }),
|
|
3893
4177
|
cardShadow: new FormControl('', { nonNullable: true }),
|
|
4178
|
+
interactionHighlight: new FormControl('', { nonNullable: true }),
|
|
3894
4179
|
cardFilter: new FormControl('', { nonNullable: true }),
|
|
3895
4180
|
cardFallbackBg: new FormControl('', { nonNullable: true }),
|
|
3896
4181
|
headerBg: new FormControl('', { nonNullable: true }),
|
|
@@ -4092,7 +4377,7 @@ class WidgetShellEditorComponent {
|
|
|
4092
4377
|
const typography = appearance.typography || {};
|
|
4093
4378
|
this.form.setControl('actions', new FormArray((shell?.actions || []).map((a) => this.createActionGroup(a)), control => this.validateActionIds(control)));
|
|
4094
4379
|
this.form.setValue({
|
|
4095
|
-
kind: shell
|
|
4380
|
+
kind: !shell || shell.kind === 'none' ? 'none' : 'dashboard-card',
|
|
4096
4381
|
preset: shell?.preset || '',
|
|
4097
4382
|
applyToAll: false,
|
|
4098
4383
|
title: this.editableText(shell?.title),
|
|
@@ -4111,6 +4396,7 @@ class WidgetShellEditorComponent {
|
|
|
4111
4396
|
cardBorderStyle: card.borderStyle || '',
|
|
4112
4397
|
cardRadius: card.borderRadius || '',
|
|
4113
4398
|
cardShadow: card.shadow || '',
|
|
4399
|
+
interactionHighlight: card.interactionHighlight || '',
|
|
4114
4400
|
cardFilter: card.backdropFilter || '',
|
|
4115
4401
|
cardFallbackBg: card.fallbackBackground || '',
|
|
4116
4402
|
headerBg: header.background || '',
|
|
@@ -4194,7 +4480,7 @@ class WidgetShellEditorComponent {
|
|
|
4194
4480
|
buildShell() {
|
|
4195
4481
|
const raw = this.form.getRawValue();
|
|
4196
4482
|
const initial = this.initialShell.value;
|
|
4197
|
-
const shell = initial ? { ...initial } : {};
|
|
4483
|
+
const shell = initial ? { ...initial } : { kind: 'none' };
|
|
4198
4484
|
if (this.canEditShellIdentity()) {
|
|
4199
4485
|
delete shell.kind;
|
|
4200
4486
|
delete shell.preset;
|
|
@@ -4290,6 +4576,8 @@ class WidgetShellEditorComponent {
|
|
|
4290
4576
|
card.borderRadius = raw.cardRadius.trim();
|
|
4291
4577
|
if (raw.cardShadow.trim())
|
|
4292
4578
|
card.shadow = raw.cardShadow.trim();
|
|
4579
|
+
if (raw.interactionHighlight)
|
|
4580
|
+
card.interactionHighlight = raw.interactionHighlight;
|
|
4293
4581
|
if (raw.cardFilter.trim())
|
|
4294
4582
|
card.backdropFilter = raw.cardFilter.trim();
|
|
4295
4583
|
if (raw.cardFallbackBg.trim())
|
|
@@ -4350,7 +4638,7 @@ class WidgetShellEditorComponent {
|
|
|
4350
4638
|
return shellCapabilities[capability] !== false;
|
|
4351
4639
|
}
|
|
4352
4640
|
appearanceControls = [
|
|
4353
|
-
'cardBg', 'cardBorder', 'cardBorderWidth', 'cardBorderStyle', 'cardRadius', 'cardShadow', 'cardFilter', 'cardFallbackBg', 'headerBg', 'headerBorder', 'headerBorderWidth', 'headerBorderStyle',
|
|
4641
|
+
'interactionHighlight', 'cardBg', 'cardBorder', 'cardBorderWidth', 'cardBorderStyle', 'cardRadius', 'cardShadow', 'cardFilter', 'cardFallbackBg', 'headerBg', 'headerBorder', 'headerBorderWidth', 'headerBorderStyle',
|
|
4354
4642
|
'titleColor', 'subtitleColor', 'iconColor', 'bodyBg', 'bodyColor', 'bodyPadding',
|
|
4355
4643
|
'titleSize', 'titleWeight', 'subtitleSize',
|
|
4356
4644
|
];
|
|
@@ -4888,6 +5176,27 @@ class WidgetShellEditorComponent {
|
|
|
4888
5176
|
<button praxisIconButton="help_outline" size="dense" matSuffix type="button" [matTooltip]="shellText('cardFallbackHelp')" [attr.aria-label]="shellText('cardFallbackHelp')"></button>
|
|
4889
5177
|
@if (form.controls.cardFallbackBg.hasError('cssDeclaration')) { <mat-error>{{ appearanceError() }}</mat-error> }
|
|
4890
5178
|
</mat-form-field>
|
|
5179
|
+
<div class="shell-highlight-control">
|
|
5180
|
+
<span class="shell-highlight-label">{{ shellText('interactionHighlight.label') }}</span>
|
|
5181
|
+
<mat-button-toggle-group
|
|
5182
|
+
class="shell-highlight-options"
|
|
5183
|
+
data-testid="shell-interaction-highlight"
|
|
5184
|
+
[formControl]="form.controls.interactionHighlight"
|
|
5185
|
+
[attr.aria-label]="shellText('interactionHighlight.label')"
|
|
5186
|
+
>
|
|
5187
|
+
<mat-button-toggle value="">{{ shellText('interactionHighlight.inherit') }}</mat-button-toggle>
|
|
5188
|
+
<mat-button-toggle value="standard">
|
|
5189
|
+
<mat-icon aria-hidden="true" praxisIcon="crop_square"></mat-icon>
|
|
5190
|
+
{{ shellText('interactionHighlight.standard') }}
|
|
5191
|
+
</mat-button-toggle>
|
|
5192
|
+
<mat-button-toggle value="gradient">
|
|
5193
|
+
<mat-icon aria-hidden="true" praxisIcon="gradient"></mat-icon>
|
|
5194
|
+
{{ shellText('interactionHighlight.gradient') }}
|
|
5195
|
+
</mat-button-toggle>
|
|
5196
|
+
</mat-button-toggle-group>
|
|
5197
|
+
<p class="shell-action-hint">{{ shellText('interactionHighlight.help') }}</p>
|
|
5198
|
+
</div>
|
|
5199
|
+
|
|
4891
5200
|
<mat-form-field floatLabel="always" appearance="outline" subscriptSizing="dynamic">
|
|
4892
5201
|
<mat-label>{{ shellText('cardBg') }}</mat-label>
|
|
4893
5202
|
<input
|
|
@@ -5262,7 +5571,7 @@ class WidgetShellEditorComponent {
|
|
|
5262
5571
|
<div class="shell-preview-content">
|
|
5263
5572
|
<p class="shell-hint">{{ shellText('previewHint') }}</p>
|
|
5264
5573
|
<div class="shell-preview">
|
|
5265
|
-
<praxis-widget-shell [shell]="previewShell()">
|
|
5574
|
+
<praxis-widget-shell [shell]="previewShell()" [style.--pdx-widget-highlight-active]="1">
|
|
5266
5575
|
<div class="shell-sample-content">
|
|
5267
5576
|
<span>{{ shellText('sampleMetricLabel') }}</span>
|
|
5268
5577
|
<strong>842</strong>
|
|
@@ -5281,7 +5590,7 @@ class WidgetShellEditorComponent {
|
|
|
5281
5590
|
</div>
|
|
5282
5591
|
</details>
|
|
5283
5592
|
</div>
|
|
5284
|
-
`, isInline: true, styles: [":host{display:block;container:shell-editor / inline-size}.shell-effective-style{display:grid;gap:4px;margin:0;font-size:12px}.shell-effective-style span{color:var(--md-sys-color-on-surface-variant)}.shell-topic-panel,.shell-topic{display:grid;gap:var(--pfx-spacing-4, 16px)}.shell-topic-panel{padding-block:var(--pfx-spacing-4, 16px);min-width:0}.shell-topic-panel[hidden]{display:none}.shell-topic-panel>.shell-grid{margin-block-end:var(--pfx-spacing-4, 16px)}.shell-link-summary{display:grid;gap:var(--pfx-spacing-1, 4px);padding-block:var(--pfx-spacing-3, 12px);overflow-wrap:anywhere}.shell-button-task{display:grid;gap:var(--pfx-spacing-4, 16px);min-width:0}.shell-button-count{margin:0;font-size:14px}.shell-action-technical,.shell-action-scopes{min-width:0;font-size:12px}.shell-action-technical summary,.shell-action-scopes summary{cursor:pointer;padding-block:8px}.shell-action-technical summary:focus-visible,.shell-action-scopes summary:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.shell-action-contract{display:grid;grid-template-columns:auto minmax(0,1fr);gap:8px}.shell-action-contract dd{margin:0;overflow-wrap:anywhere}.shell-action-technical .shell-action-row{margin-top:12px}.shell-editor{display:grid;grid-template-columns:minmax(0,1fr);min-width:0;gap:20px;padding:12px 4px 20px}.shell-controls{display:grid;gap:var(--pfx-spacing-4, 16px);min-width:0;container:shell-controls / inline-size}.shell-preview-region{min-width:0}@container shell-controls (max-width: 480px){.shell-controls .shell-grid,.shell-controls .shell-action-row,.shell-controls .shell-quick-inputs,.shell-controls .shell-available-item{grid-template-columns:minmax(0,1fr)}.shell-controls .shell-available-item>button{justify-self:start}}.shell-intro{margin:0;max-width:68ch;color:var(--md-sys-color-on-surface-variant);font-size:14px;line-height:1.5}.shell-details{border-top:1px solid var(--md-sys-color-outline-variant);padding-top:var(--pfx-spacing-4, 16px)}.shell-details summary{cursor:pointer;color:var(--md-sys-color-on-surface);font-weight:600;line-height:1.5}.shell-details summary:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:4px}.shell-option-content{display:grid;gap:var(--pfx-spacing-4, 16px);margin-top:var(--pfx-spacing-4, 16px);min-width:0}.shell-details>.shell-grid{margin-top:var(--pfx-spacing-4, 16px)}.shell-details>.shell-section{margin:var(--pfx-spacing-4, 16px) 0 var(--pfx-spacing-2, 8px)}.shell-validation{color:var(--md-sys-color-error);margin:0;line-height:1.5}.shell-section{margin:4px 0 -8px;color:var(--md-sys-color-on-surface);font-size:14px;font-weight:650;line-height:1.4}.shell-grid{display:grid;gap:12px 16px;grid-template-columns:repeat(2,minmax(0,1fr));align-items:start}.shell-field--wide{grid-column:1 / -1}.shell-preset-preview__body{display:grid;align-content:start;height:100%;color:var(--md-sys-color-on-surface-variant)}.shell-preset-preview__body .shell-mini-value,.shell-preset-preview__body .shell-mini-line{background:currentColor}.shell-preset-copy strong{display:flex;align-items:baseline;justify-content:space-between;gap:var(--pfx-spacing-2, 8px)}.shell-preset-check{color:var(--md-sys-color-primary);flex-shrink:0}.shell-preset-field,.shell-preset-copy{display:grid}.shell-preset-field{gap:8px}.shell-preset-label{color:var(--md-sys-color-on-surface);font-size:13px;font-weight:600}.shell-preset-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px}.shell-preset-option{min-width:0;display:grid;grid-template-columns:72px minmax(0,1fr);align-items:center;gap:10px;padding:10px;border:1px solid var(--md-sys-color-outline-variant);border-radius:12px;background:var(--md-sys-color-surface);color:var(--md-sys-color-on-surface);text-align:left;cursor:pointer}.shell-preset-option:hover{background:var(--md-sys-color-surface-container-low)}.shell-preset-option:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.shell-preset-option--selected{border-color:var(--md-sys-color-primary);box-shadow:0 0 0 1px var(--md-sys-color-primary)}.shell-preset-preview{box-sizing:border-box;display:block;height:64px;overflow:hidden;border:1px solid var(--md-sys-color-outline-variant);background:var(--shell-mini-bg, var(--md-sys-color-surface));-webkit-backdrop-filter:var(--shell-mini-filter, none);backdrop-filter:var(--shell-mini-filter, none)}@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))){.shell-preset-preview--backdrop{background:var(--shell-mini-fallback, var(--md-sys-color-surface))}}@media(prefers-reduced-transparency:reduce){.shell-preset-preview--backdrop{background:var(--shell-mini-fallback, var(--md-sys-color-surface));-webkit-backdrop-filter:none;backdrop-filter:none}}.shell-preset-preview__header{display:flex;align-items:center;gap:4px;padding-inline:8px;height:22px;border-bottom:1px solid transparent}.shell-preset-preview--none{border-style:dashed;border-radius:8px;background:transparent;box-shadow:none}.shell-preset-copy{gap:3px}.shell-preset-copy small{color:var(--md-sys-color-on-surface-variant);font-size:11px;line-height:1.35}.shell-flags{display:flex;gap:16px;align-items:center}.shell-actions-head{display:flex;align-items:center;justify-content:space-between;gap:12px}.shell-available{display:grid;gap:12px}.shell-available-toolbar{display:grid;gap:12px;grid-template-columns:minmax(200px,1fr)}.shell-available-filters{display:flex;gap:12px;align-items:center;flex-wrap:wrap}.shell-available-list{display:grid;gap:10px}.shell-available-item{display:grid;gap:8px;grid-template-columns:minmax(0,1fr) auto;border-bottom:1px solid var(--md-sys-color-outline-variant);padding-block:12px;align-items:start;min-width:0}.shell-available-main{min-width:0;overflow-wrap:anywhere}.shell-available-title{display:flex;align-items:center;gap:10px}.shell-available-text{display:grid;gap:4px}.shell-available-label{font-weight:600}.shell-available-meta{display:flex;gap:8px;flex-wrap:wrap;font-size:11px;color:var(--md-sys-color-on-surface-variant)}.shell-available-desc{font-size:12px;color:var(--md-sys-color-on-surface-variant)}.shell-available-payload{font-size:11px;color:var(--md-sys-color-on-surface-variant)}.shell-action-availability{font-size:12px;line-height:1.5;padding:8px 10px;border-inline-start:2px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container);color:var(--md-sys-color-on-surface-variant)}.shell-available-warning{font-size:11px;color:var(--md-sys-color-error)}.shell-available-actions{display:flex;align-items:flex-start;justify-content:flex-end}.shell-badge{padding:2px 6px;border-radius:999px;border:1px solid var(--md-sys-color-outline-variant);font-size:10px}.shell-code{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.shell-actions{display:grid;gap:12px}.shell-action{border-top:1px solid var(--md-sys-color-outline-variant);padding-block:16px;display:grid;gap:8px}.shell-action-row{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}.shell-action-actions{display:flex;justify-content:flex-end}.shell-action-hint,.shell-empty{font-size:12px;color:var(--md-sys-color-on-surface-variant)}.shell-hint{margin:0;line-height:1.5;align-self:center;font-size:12px;color:var(--md-sys-color-on-surface-variant)}.shell-quick-inputs{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));align-items:end}.shell-input-path{font-size:12px;color:var(--md-sys-color-on-surface-variant);align-self:center}.shell-editor .mat-mdc-form-field{width:100%;min-width:0}:host{display:block;min-width:0}.shell-editor>*,.shell-action>*{min-width:0}.shell-available-text,.shell-input-path{min-width:0;overflow-wrap:anywhere}.shell-preview-region{min-width:0;background:var(--md-sys-color-surface)}.shell-preview-content{display:grid;gap:12px;padding-top:12px}.shell-preview-heading{cursor:pointer;padding-block:8px}.shell-preview-heading:before{content:\"\\25b8\"}.shell-preview-region[open]>.shell-preview-heading:before{content:\"\\25be\"}.shell-preview-heading:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.shell-preview-heading,.shell-override-summary{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px}.shell-preview-heading strong{margin-right:auto;font-size:14px}.shell-preview-heading>span,.shell-override-summary{font-size:12px;color:var(--md-sys-color-on-surface-variant)}.shell-preview{padding:16px;background:var(--md-sys-color-surface-container-low);min-width:0}.shell-sample-content{display:grid;gap:8px}.shell-sample-content>span{font-size:12px;color:var(--md-sys-color-on-surface-variant)}.shell-sample-content strong{font-size:32px;line-height:1.15;font-variant-numeric:tabular-nums}.shell-mini-title,.shell-mini-action,.shell-mini-value,.shell-mini-line{display:block;background:var(--md-sys-color-on-surface-variant);opacity:.45;border-radius:2px}.shell-mini-title{width:24px;height:3px}.shell-mini-action{width:4px;height:4px;margin-left:auto}.shell-mini-value{width:20px;height:8px;margin:8px 8px 4px;opacity:.7}.shell-mini-line{width:32px;height:3px;margin-inline:8px}@media(max-width:640px){.shell-editor{padding-inline:0}.shell-grid,.shell-action-row,.shell-quick-inputs,.shell-available-toolbar{grid-template-columns:minmax(0,1fr)}.shell-actions-head,.shell-flags{flex-wrap:wrap}.shell-field--wide{grid-column:auto}}\n"], dependencies: [{ kind: "component", type: WidgetShellComponent, selector: "praxis-widget-shell", inputs: ["shell", "context", "dragSurfaceEnabled", "dragSurfaceLabel"], outputs: ["action", "dragSurfacePointerDown", "dragSurfaceKeydown"] }, { kind: "ngmodule", type: CommonModule }, { 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.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.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { 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: MatButtonModule }, { kind: "component", type: i2.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: MatCheckboxModule }, { kind: "component", type: i4.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: MatTooltipModule }, { kind: "directive", type: i3.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i6.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i6.MatLabel, selector: "mat-label" }, { kind: "directive", type: i6.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i6.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i6.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i8.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: i3$1.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: i3$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: SettingsSectionTabsComponent, selector: "praxis-settings-section-tabs", inputs: ["sections", "selectedId", "label", "invalidLabel", "panelId"], outputs: ["selectedIdChange"] }, { kind: "component", type: PraxisIconButtonComponent, selector: "button[praxisIconButton]", inputs: ["praxisIconButton", "size", "appearance", "presentation", "pressed", "busy"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
5593
|
+
`, isInline: true, styles: [":host{display:block;container:shell-editor / inline-size}.shell-effective-style{display:grid;gap:4px;margin:0;font-size:12px}.shell-effective-style span{color:var(--md-sys-color-on-surface-variant)}.shell-topic-panel,.shell-topic{display:grid;gap:var(--pfx-spacing-4, 16px)}.shell-topic-panel{padding-block:var(--pfx-spacing-4, 16px);min-width:0}.shell-topic-panel[hidden]{display:none}.shell-topic-panel>.shell-grid{margin-block-end:var(--pfx-spacing-4, 16px)}.shell-link-summary{display:grid;gap:var(--pfx-spacing-1, 4px);padding-block:var(--pfx-spacing-3, 12px);overflow-wrap:anywhere}.shell-button-task{display:grid;gap:var(--pfx-spacing-4, 16px);min-width:0}.shell-button-count{margin:0;font-size:14px}.shell-action-technical,.shell-action-scopes{min-width:0;font-size:12px}.shell-action-technical summary,.shell-action-scopes summary{cursor:pointer;padding-block:8px}.shell-action-technical summary:focus-visible,.shell-action-scopes summary:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.shell-action-contract{display:grid;grid-template-columns:auto minmax(0,1fr);gap:8px}.shell-action-contract dd{margin:0;overflow-wrap:anywhere}.shell-action-technical .shell-action-row{margin-top:12px}.shell-editor{display:grid;grid-template-columns:minmax(0,1fr);min-width:0;gap:20px;padding:12px 4px 20px}.shell-controls{display:grid;gap:var(--pfx-spacing-4, 16px);min-width:0;container:shell-controls / inline-size}.shell-preview-region{min-width:0}.shell-highlight-control{display:grid;gap:8px;min-width:0;grid-column:1 / -1}.shell-highlight-label{font-size:13px;font-weight:600}.shell-highlight-options{display:flex;flex-wrap:wrap;width:fit-content;max-width:100%}.shell-highlight-options mat-button-toggle{min-width:0}.shell-highlight-control p{margin:0;overflow-wrap:anywhere}@container shell-controls (max-width: 480px){.shell-controls .shell-grid,.shell-controls .shell-action-row,.shell-controls .shell-quick-inputs,.shell-controls .shell-available-item{grid-template-columns:minmax(0,1fr)}.shell-controls .shell-available-item>button{justify-self:start}}.shell-intro{margin:0;max-width:68ch;color:var(--md-sys-color-on-surface-variant);font-size:14px;line-height:1.5}.shell-details{border-top:1px solid var(--md-sys-color-outline-variant);padding-top:var(--pfx-spacing-4, 16px)}.shell-details summary{cursor:pointer;color:var(--md-sys-color-on-surface);font-weight:600;line-height:1.5}.shell-details summary:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:4px}.shell-option-content{display:grid;gap:var(--pfx-spacing-4, 16px);margin-top:var(--pfx-spacing-4, 16px);min-width:0}.shell-details>.shell-grid{margin-top:var(--pfx-spacing-4, 16px)}.shell-details>.shell-section{margin:var(--pfx-spacing-4, 16px) 0 var(--pfx-spacing-2, 8px)}.shell-validation{color:var(--md-sys-color-error);margin:0;line-height:1.5}.shell-section{margin:4px 0 -8px;color:var(--md-sys-color-on-surface);font-size:14px;font-weight:650;line-height:1.4}.shell-grid{display:grid;gap:12px 16px;grid-template-columns:repeat(2,minmax(0,1fr));align-items:start}.shell-field--wide{grid-column:1 / -1}.shell-preset-preview__body{display:grid;align-content:start;height:100%;color:var(--md-sys-color-on-surface-variant)}.shell-preset-preview__body .shell-mini-value,.shell-preset-preview__body .shell-mini-line{background:currentColor}.shell-preset-copy strong{display:flex;align-items:baseline;justify-content:space-between;gap:var(--pfx-spacing-2, 8px)}.shell-preset-check{color:var(--md-sys-color-primary);flex-shrink:0}.shell-preset-field,.shell-preset-copy{display:grid}.shell-preset-field{gap:8px}.shell-preset-label{color:var(--md-sys-color-on-surface);font-size:13px;font-weight:600}.shell-preset-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px}.shell-preset-option{min-width:0;display:grid;grid-template-columns:72px minmax(0,1fr);align-items:center;gap:10px;padding:10px;border:1px solid var(--md-sys-color-outline-variant);border-radius:12px;background:var(--md-sys-color-surface);color:var(--md-sys-color-on-surface);text-align:left;cursor:pointer}.shell-preset-option:hover{background:var(--md-sys-color-surface-container-low)}.shell-preset-option:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.shell-preset-option--selected{border-color:var(--md-sys-color-primary);box-shadow:0 0 0 1px var(--md-sys-color-primary)}.shell-preset-preview{box-sizing:border-box;display:block;height:64px;overflow:hidden;border:1px solid var(--md-sys-color-outline-variant);background:var(--shell-mini-bg, var(--md-sys-color-surface));-webkit-backdrop-filter:var(--shell-mini-filter, none);backdrop-filter:var(--shell-mini-filter, none)}@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))){.shell-preset-preview--backdrop{background:var(--shell-mini-fallback, var(--md-sys-color-surface))}}@media(prefers-reduced-transparency:reduce){.shell-preset-preview--backdrop{background:var(--shell-mini-fallback, var(--md-sys-color-surface));-webkit-backdrop-filter:none;backdrop-filter:none}}.shell-preset-preview__header{display:flex;align-items:center;gap:4px;padding-inline:8px;height:22px;border-bottom:1px solid transparent}.shell-preset-preview--none{border-style:dashed;border-radius:8px;background:transparent;box-shadow:none}.shell-preset-copy{gap:3px}.shell-preset-copy small{color:var(--md-sys-color-on-surface-variant);font-size:11px;line-height:1.35}.shell-flags{display:flex;gap:16px;align-items:center}.shell-actions-head{display:flex;align-items:center;justify-content:space-between;gap:12px}.shell-available{display:grid;gap:12px}.shell-available-toolbar{display:grid;gap:12px;grid-template-columns:minmax(200px,1fr)}.shell-available-filters{display:flex;gap:12px;align-items:center;flex-wrap:wrap}.shell-available-list{display:grid;gap:10px}.shell-available-item{display:grid;gap:8px;grid-template-columns:minmax(0,1fr) auto;border-bottom:1px solid var(--md-sys-color-outline-variant);padding-block:12px;align-items:start;min-width:0}.shell-available-main{min-width:0;overflow-wrap:anywhere}.shell-available-title{display:flex;align-items:center;gap:10px}.shell-available-text{display:grid;gap:4px}.shell-available-label{font-weight:600}.shell-available-meta{display:flex;gap:8px;flex-wrap:wrap;font-size:11px;color:var(--md-sys-color-on-surface-variant)}.shell-available-desc{font-size:12px;color:var(--md-sys-color-on-surface-variant)}.shell-available-payload{font-size:11px;color:var(--md-sys-color-on-surface-variant)}.shell-action-availability{font-size:12px;line-height:1.5;padding:8px 10px;border-inline-start:2px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container);color:var(--md-sys-color-on-surface-variant)}.shell-available-warning{font-size:11px;color:var(--md-sys-color-error)}.shell-available-actions{display:flex;align-items:flex-start;justify-content:flex-end}.shell-badge{padding:2px 6px;border-radius:999px;border:1px solid var(--md-sys-color-outline-variant);font-size:10px}.shell-code{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.shell-actions{display:grid;gap:12px}.shell-action{border-top:1px solid var(--md-sys-color-outline-variant);padding-block:16px;display:grid;gap:8px}.shell-action-row{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}.shell-action-actions{display:flex;justify-content:flex-end}.shell-action-hint,.shell-empty{font-size:12px;color:var(--md-sys-color-on-surface-variant)}.shell-hint{margin:0;line-height:1.5;align-self:center;font-size:12px;color:var(--md-sys-color-on-surface-variant)}.shell-quick-inputs{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));align-items:end}.shell-input-path{font-size:12px;color:var(--md-sys-color-on-surface-variant);align-self:center}.shell-editor .mat-mdc-form-field{width:100%;min-width:0}:host{display:block;min-width:0}.shell-editor>*,.shell-action>*{min-width:0}.shell-available-text,.shell-input-path{min-width:0;overflow-wrap:anywhere}.shell-preview-region{min-width:0;background:var(--md-sys-color-surface)}.shell-preview-content{display:grid;gap:12px;padding-top:12px}.shell-preview-heading{cursor:pointer;padding-block:8px}.shell-preview-heading:before{content:\"\\25b8\"}.shell-preview-region[open]>.shell-preview-heading:before{content:\"\\25be\"}.shell-preview-heading:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.shell-preview-heading,.shell-override-summary{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px}.shell-preview-heading strong{margin-right:auto;font-size:14px}.shell-preview-heading>span,.shell-override-summary{font-size:12px;color:var(--md-sys-color-on-surface-variant)}.shell-preview{padding:16px;background:var(--md-sys-color-surface-container-low);min-width:0}.shell-sample-content{display:grid;gap:8px}.shell-sample-content>span{font-size:12px;color:var(--md-sys-color-on-surface-variant)}.shell-sample-content strong{font-size:32px;line-height:1.15;font-variant-numeric:tabular-nums}.shell-mini-title,.shell-mini-action,.shell-mini-value,.shell-mini-line{display:block;background:var(--md-sys-color-on-surface-variant);opacity:.45;border-radius:2px}.shell-mini-title{width:24px;height:3px}.shell-mini-action{width:4px;height:4px;margin-left:auto}.shell-mini-value{width:20px;height:8px;margin:8px 8px 4px;opacity:.7}.shell-mini-line{width:32px;height:3px;margin-inline:8px}@media(max-width:640px){.shell-editor{padding-inline:0}.shell-grid,.shell-action-row,.shell-quick-inputs,.shell-available-toolbar{grid-template-columns:minmax(0,1fr)}.shell-actions-head,.shell-flags{flex-wrap:wrap}.shell-field--wide{grid-column:auto}}\n"], dependencies: [{ kind: "component", type: WidgetShellComponent, selector: "praxis-widget-shell", inputs: ["shell", "context", "dragSurfaceEnabled", "dragSurfaceLabel"], outputs: ["action", "dragSurfacePointerDown", "dragSurfaceKeydown"] }, { kind: "ngmodule", type: CommonModule }, { 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.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.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { 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: MatButtonModule }, { kind: "component", type: i2.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: i4.MatButtonToggleGroup, selector: "mat-button-toggle-group", inputs: ["appearance", "name", "vertical", "value", "multiple", "disabled", "disabledInteractive", "hideSingleSelectionIndicator", "hideMultipleSelectionIndicator"], outputs: ["valueChange", "change"], exportAs: ["matButtonToggleGroup"] }, { kind: "component", type: i4.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: MatCheckboxModule }, { kind: "component", type: i5.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: MatTooltipModule }, { kind: "directive", type: i3.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i7.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i7.MatLabel, selector: "mat-label" }, { kind: "directive", type: i7.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i7.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i7.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i9.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: i3$1.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: i3$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: SettingsSectionTabsComponent, selector: "praxis-settings-section-tabs", inputs: ["sections", "selectedId", "label", "invalidLabel", "panelId"], outputs: ["selectedIdChange"] }, { kind: "component", type: PraxisIconButtonComponent, selector: "button[praxisIconButton]", inputs: ["praxisIconButton", "size", "appearance", "presentation", "pressed", "busy"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
5285
5594
|
}
|
|
5286
5595
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: WidgetShellEditorComponent, decorators: [{
|
|
5287
5596
|
type: Component,
|
|
@@ -5291,6 +5600,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
5291
5600
|
FormsModule,
|
|
5292
5601
|
ReactiveFormsModule,
|
|
5293
5602
|
MatButtonModule,
|
|
5603
|
+
MatButtonToggleModule,
|
|
5294
5604
|
MatCheckboxModule,
|
|
5295
5605
|
MatTooltipModule,
|
|
5296
5606
|
MatFormFieldModule,
|
|
@@ -5552,6 +5862,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
5552
5862
|
<button praxisIconButton="help_outline" size="dense" matSuffix type="button" [matTooltip]="shellText('cardFallbackHelp')" [attr.aria-label]="shellText('cardFallbackHelp')"></button>
|
|
5553
5863
|
@if (form.controls.cardFallbackBg.hasError('cssDeclaration')) { <mat-error>{{ appearanceError() }}</mat-error> }
|
|
5554
5864
|
</mat-form-field>
|
|
5865
|
+
<div class="shell-highlight-control">
|
|
5866
|
+
<span class="shell-highlight-label">{{ shellText('interactionHighlight.label') }}</span>
|
|
5867
|
+
<mat-button-toggle-group
|
|
5868
|
+
class="shell-highlight-options"
|
|
5869
|
+
data-testid="shell-interaction-highlight"
|
|
5870
|
+
[formControl]="form.controls.interactionHighlight"
|
|
5871
|
+
[attr.aria-label]="shellText('interactionHighlight.label')"
|
|
5872
|
+
>
|
|
5873
|
+
<mat-button-toggle value="">{{ shellText('interactionHighlight.inherit') }}</mat-button-toggle>
|
|
5874
|
+
<mat-button-toggle value="standard">
|
|
5875
|
+
<mat-icon aria-hidden="true" praxisIcon="crop_square"></mat-icon>
|
|
5876
|
+
{{ shellText('interactionHighlight.standard') }}
|
|
5877
|
+
</mat-button-toggle>
|
|
5878
|
+
<mat-button-toggle value="gradient">
|
|
5879
|
+
<mat-icon aria-hidden="true" praxisIcon="gradient"></mat-icon>
|
|
5880
|
+
{{ shellText('interactionHighlight.gradient') }}
|
|
5881
|
+
</mat-button-toggle>
|
|
5882
|
+
</mat-button-toggle-group>
|
|
5883
|
+
<p class="shell-action-hint">{{ shellText('interactionHighlight.help') }}</p>
|
|
5884
|
+
</div>
|
|
5885
|
+
|
|
5555
5886
|
<mat-form-field floatLabel="always" appearance="outline" subscriptSizing="dynamic">
|
|
5556
5887
|
<mat-label>{{ shellText('cardBg') }}</mat-label>
|
|
5557
5888
|
<input
|
|
@@ -5926,7 +6257,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
5926
6257
|
<div class="shell-preview-content">
|
|
5927
6258
|
<p class="shell-hint">{{ shellText('previewHint') }}</p>
|
|
5928
6259
|
<div class="shell-preview">
|
|
5929
|
-
<praxis-widget-shell [shell]="previewShell()">
|
|
6260
|
+
<praxis-widget-shell [shell]="previewShell()" [style.--pdx-widget-highlight-active]="1">
|
|
5930
6261
|
<div class="shell-sample-content">
|
|
5931
6262
|
<span>{{ shellText('sampleMetricLabel') }}</span>
|
|
5932
6263
|
<strong>842</strong>
|
|
@@ -5945,7 +6276,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
5945
6276
|
</div>
|
|
5946
6277
|
</details>
|
|
5947
6278
|
</div>
|
|
5948
|
-
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;container:shell-editor / inline-size}.shell-effective-style{display:grid;gap:4px;margin:0;font-size:12px}.shell-effective-style span{color:var(--md-sys-color-on-surface-variant)}.shell-topic-panel,.shell-topic{display:grid;gap:var(--pfx-spacing-4, 16px)}.shell-topic-panel{padding-block:var(--pfx-spacing-4, 16px);min-width:0}.shell-topic-panel[hidden]{display:none}.shell-topic-panel>.shell-grid{margin-block-end:var(--pfx-spacing-4, 16px)}.shell-link-summary{display:grid;gap:var(--pfx-spacing-1, 4px);padding-block:var(--pfx-spacing-3, 12px);overflow-wrap:anywhere}.shell-button-task{display:grid;gap:var(--pfx-spacing-4, 16px);min-width:0}.shell-button-count{margin:0;font-size:14px}.shell-action-technical,.shell-action-scopes{min-width:0;font-size:12px}.shell-action-technical summary,.shell-action-scopes summary{cursor:pointer;padding-block:8px}.shell-action-technical summary:focus-visible,.shell-action-scopes summary:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.shell-action-contract{display:grid;grid-template-columns:auto minmax(0,1fr);gap:8px}.shell-action-contract dd{margin:0;overflow-wrap:anywhere}.shell-action-technical .shell-action-row{margin-top:12px}.shell-editor{display:grid;grid-template-columns:minmax(0,1fr);min-width:0;gap:20px;padding:12px 4px 20px}.shell-controls{display:grid;gap:var(--pfx-spacing-4, 16px);min-width:0;container:shell-controls / inline-size}.shell-preview-region{min-width:0}@container shell-controls (max-width: 480px){.shell-controls .shell-grid,.shell-controls .shell-action-row,.shell-controls .shell-quick-inputs,.shell-controls .shell-available-item{grid-template-columns:minmax(0,1fr)}.shell-controls .shell-available-item>button{justify-self:start}}.shell-intro{margin:0;max-width:68ch;color:var(--md-sys-color-on-surface-variant);font-size:14px;line-height:1.5}.shell-details{border-top:1px solid var(--md-sys-color-outline-variant);padding-top:var(--pfx-spacing-4, 16px)}.shell-details summary{cursor:pointer;color:var(--md-sys-color-on-surface);font-weight:600;line-height:1.5}.shell-details summary:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:4px}.shell-option-content{display:grid;gap:var(--pfx-spacing-4, 16px);margin-top:var(--pfx-spacing-4, 16px);min-width:0}.shell-details>.shell-grid{margin-top:var(--pfx-spacing-4, 16px)}.shell-details>.shell-section{margin:var(--pfx-spacing-4, 16px) 0 var(--pfx-spacing-2, 8px)}.shell-validation{color:var(--md-sys-color-error);margin:0;line-height:1.5}.shell-section{margin:4px 0 -8px;color:var(--md-sys-color-on-surface);font-size:14px;font-weight:650;line-height:1.4}.shell-grid{display:grid;gap:12px 16px;grid-template-columns:repeat(2,minmax(0,1fr));align-items:start}.shell-field--wide{grid-column:1 / -1}.shell-preset-preview__body{display:grid;align-content:start;height:100%;color:var(--md-sys-color-on-surface-variant)}.shell-preset-preview__body .shell-mini-value,.shell-preset-preview__body .shell-mini-line{background:currentColor}.shell-preset-copy strong{display:flex;align-items:baseline;justify-content:space-between;gap:var(--pfx-spacing-2, 8px)}.shell-preset-check{color:var(--md-sys-color-primary);flex-shrink:0}.shell-preset-field,.shell-preset-copy{display:grid}.shell-preset-field{gap:8px}.shell-preset-label{color:var(--md-sys-color-on-surface);font-size:13px;font-weight:600}.shell-preset-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px}.shell-preset-option{min-width:0;display:grid;grid-template-columns:72px minmax(0,1fr);align-items:center;gap:10px;padding:10px;border:1px solid var(--md-sys-color-outline-variant);border-radius:12px;background:var(--md-sys-color-surface);color:var(--md-sys-color-on-surface);text-align:left;cursor:pointer}.shell-preset-option:hover{background:var(--md-sys-color-surface-container-low)}.shell-preset-option:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.shell-preset-option--selected{border-color:var(--md-sys-color-primary);box-shadow:0 0 0 1px var(--md-sys-color-primary)}.shell-preset-preview{box-sizing:border-box;display:block;height:64px;overflow:hidden;border:1px solid var(--md-sys-color-outline-variant);background:var(--shell-mini-bg, var(--md-sys-color-surface));-webkit-backdrop-filter:var(--shell-mini-filter, none);backdrop-filter:var(--shell-mini-filter, none)}@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))){.shell-preset-preview--backdrop{background:var(--shell-mini-fallback, var(--md-sys-color-surface))}}@media(prefers-reduced-transparency:reduce){.shell-preset-preview--backdrop{background:var(--shell-mini-fallback, var(--md-sys-color-surface));-webkit-backdrop-filter:none;backdrop-filter:none}}.shell-preset-preview__header{display:flex;align-items:center;gap:4px;padding-inline:8px;height:22px;border-bottom:1px solid transparent}.shell-preset-preview--none{border-style:dashed;border-radius:8px;background:transparent;box-shadow:none}.shell-preset-copy{gap:3px}.shell-preset-copy small{color:var(--md-sys-color-on-surface-variant);font-size:11px;line-height:1.35}.shell-flags{display:flex;gap:16px;align-items:center}.shell-actions-head{display:flex;align-items:center;justify-content:space-between;gap:12px}.shell-available{display:grid;gap:12px}.shell-available-toolbar{display:grid;gap:12px;grid-template-columns:minmax(200px,1fr)}.shell-available-filters{display:flex;gap:12px;align-items:center;flex-wrap:wrap}.shell-available-list{display:grid;gap:10px}.shell-available-item{display:grid;gap:8px;grid-template-columns:minmax(0,1fr) auto;border-bottom:1px solid var(--md-sys-color-outline-variant);padding-block:12px;align-items:start;min-width:0}.shell-available-main{min-width:0;overflow-wrap:anywhere}.shell-available-title{display:flex;align-items:center;gap:10px}.shell-available-text{display:grid;gap:4px}.shell-available-label{font-weight:600}.shell-available-meta{display:flex;gap:8px;flex-wrap:wrap;font-size:11px;color:var(--md-sys-color-on-surface-variant)}.shell-available-desc{font-size:12px;color:var(--md-sys-color-on-surface-variant)}.shell-available-payload{font-size:11px;color:var(--md-sys-color-on-surface-variant)}.shell-action-availability{font-size:12px;line-height:1.5;padding:8px 10px;border-inline-start:2px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container);color:var(--md-sys-color-on-surface-variant)}.shell-available-warning{font-size:11px;color:var(--md-sys-color-error)}.shell-available-actions{display:flex;align-items:flex-start;justify-content:flex-end}.shell-badge{padding:2px 6px;border-radius:999px;border:1px solid var(--md-sys-color-outline-variant);font-size:10px}.shell-code{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.shell-actions{display:grid;gap:12px}.shell-action{border-top:1px solid var(--md-sys-color-outline-variant);padding-block:16px;display:grid;gap:8px}.shell-action-row{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}.shell-action-actions{display:flex;justify-content:flex-end}.shell-action-hint,.shell-empty{font-size:12px;color:var(--md-sys-color-on-surface-variant)}.shell-hint{margin:0;line-height:1.5;align-self:center;font-size:12px;color:var(--md-sys-color-on-surface-variant)}.shell-quick-inputs{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));align-items:end}.shell-input-path{font-size:12px;color:var(--md-sys-color-on-surface-variant);align-self:center}.shell-editor .mat-mdc-form-field{width:100%;min-width:0}:host{display:block;min-width:0}.shell-editor>*,.shell-action>*{min-width:0}.shell-available-text,.shell-input-path{min-width:0;overflow-wrap:anywhere}.shell-preview-region{min-width:0;background:var(--md-sys-color-surface)}.shell-preview-content{display:grid;gap:12px;padding-top:12px}.shell-preview-heading{cursor:pointer;padding-block:8px}.shell-preview-heading:before{content:\"\\25b8\"}.shell-preview-region[open]>.shell-preview-heading:before{content:\"\\25be\"}.shell-preview-heading:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.shell-preview-heading,.shell-override-summary{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px}.shell-preview-heading strong{margin-right:auto;font-size:14px}.shell-preview-heading>span,.shell-override-summary{font-size:12px;color:var(--md-sys-color-on-surface-variant)}.shell-preview{padding:16px;background:var(--md-sys-color-surface-container-low);min-width:0}.shell-sample-content{display:grid;gap:8px}.shell-sample-content>span{font-size:12px;color:var(--md-sys-color-on-surface-variant)}.shell-sample-content strong{font-size:32px;line-height:1.15;font-variant-numeric:tabular-nums}.shell-mini-title,.shell-mini-action,.shell-mini-value,.shell-mini-line{display:block;background:var(--md-sys-color-on-surface-variant);opacity:.45;border-radius:2px}.shell-mini-title{width:24px;height:3px}.shell-mini-action{width:4px;height:4px;margin-left:auto}.shell-mini-value{width:20px;height:8px;margin:8px 8px 4px;opacity:.7}.shell-mini-line{width:32px;height:3px;margin-inline:8px}@media(max-width:640px){.shell-editor{padding-inline:0}.shell-grid,.shell-action-row,.shell-quick-inputs,.shell-available-toolbar{grid-template-columns:minmax(0,1fr)}.shell-actions-head,.shell-flags{flex-wrap:wrap}.shell-field--wide{grid-column:auto}}\n"] }]
|
|
6279
|
+
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;container:shell-editor / inline-size}.shell-effective-style{display:grid;gap:4px;margin:0;font-size:12px}.shell-effective-style span{color:var(--md-sys-color-on-surface-variant)}.shell-topic-panel,.shell-topic{display:grid;gap:var(--pfx-spacing-4, 16px)}.shell-topic-panel{padding-block:var(--pfx-spacing-4, 16px);min-width:0}.shell-topic-panel[hidden]{display:none}.shell-topic-panel>.shell-grid{margin-block-end:var(--pfx-spacing-4, 16px)}.shell-link-summary{display:grid;gap:var(--pfx-spacing-1, 4px);padding-block:var(--pfx-spacing-3, 12px);overflow-wrap:anywhere}.shell-button-task{display:grid;gap:var(--pfx-spacing-4, 16px);min-width:0}.shell-button-count{margin:0;font-size:14px}.shell-action-technical,.shell-action-scopes{min-width:0;font-size:12px}.shell-action-technical summary,.shell-action-scopes summary{cursor:pointer;padding-block:8px}.shell-action-technical summary:focus-visible,.shell-action-scopes summary:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.shell-action-contract{display:grid;grid-template-columns:auto minmax(0,1fr);gap:8px}.shell-action-contract dd{margin:0;overflow-wrap:anywhere}.shell-action-technical .shell-action-row{margin-top:12px}.shell-editor{display:grid;grid-template-columns:minmax(0,1fr);min-width:0;gap:20px;padding:12px 4px 20px}.shell-controls{display:grid;gap:var(--pfx-spacing-4, 16px);min-width:0;container:shell-controls / inline-size}.shell-preview-region{min-width:0}.shell-highlight-control{display:grid;gap:8px;min-width:0;grid-column:1 / -1}.shell-highlight-label{font-size:13px;font-weight:600}.shell-highlight-options{display:flex;flex-wrap:wrap;width:fit-content;max-width:100%}.shell-highlight-options mat-button-toggle{min-width:0}.shell-highlight-control p{margin:0;overflow-wrap:anywhere}@container shell-controls (max-width: 480px){.shell-controls .shell-grid,.shell-controls .shell-action-row,.shell-controls .shell-quick-inputs,.shell-controls .shell-available-item{grid-template-columns:minmax(0,1fr)}.shell-controls .shell-available-item>button{justify-self:start}}.shell-intro{margin:0;max-width:68ch;color:var(--md-sys-color-on-surface-variant);font-size:14px;line-height:1.5}.shell-details{border-top:1px solid var(--md-sys-color-outline-variant);padding-top:var(--pfx-spacing-4, 16px)}.shell-details summary{cursor:pointer;color:var(--md-sys-color-on-surface);font-weight:600;line-height:1.5}.shell-details summary:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:4px}.shell-option-content{display:grid;gap:var(--pfx-spacing-4, 16px);margin-top:var(--pfx-spacing-4, 16px);min-width:0}.shell-details>.shell-grid{margin-top:var(--pfx-spacing-4, 16px)}.shell-details>.shell-section{margin:var(--pfx-spacing-4, 16px) 0 var(--pfx-spacing-2, 8px)}.shell-validation{color:var(--md-sys-color-error);margin:0;line-height:1.5}.shell-section{margin:4px 0 -8px;color:var(--md-sys-color-on-surface);font-size:14px;font-weight:650;line-height:1.4}.shell-grid{display:grid;gap:12px 16px;grid-template-columns:repeat(2,minmax(0,1fr));align-items:start}.shell-field--wide{grid-column:1 / -1}.shell-preset-preview__body{display:grid;align-content:start;height:100%;color:var(--md-sys-color-on-surface-variant)}.shell-preset-preview__body .shell-mini-value,.shell-preset-preview__body .shell-mini-line{background:currentColor}.shell-preset-copy strong{display:flex;align-items:baseline;justify-content:space-between;gap:var(--pfx-spacing-2, 8px)}.shell-preset-check{color:var(--md-sys-color-primary);flex-shrink:0}.shell-preset-field,.shell-preset-copy{display:grid}.shell-preset-field{gap:8px}.shell-preset-label{color:var(--md-sys-color-on-surface);font-size:13px;font-weight:600}.shell-preset-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px}.shell-preset-option{min-width:0;display:grid;grid-template-columns:72px minmax(0,1fr);align-items:center;gap:10px;padding:10px;border:1px solid var(--md-sys-color-outline-variant);border-radius:12px;background:var(--md-sys-color-surface);color:var(--md-sys-color-on-surface);text-align:left;cursor:pointer}.shell-preset-option:hover{background:var(--md-sys-color-surface-container-low)}.shell-preset-option:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.shell-preset-option--selected{border-color:var(--md-sys-color-primary);box-shadow:0 0 0 1px var(--md-sys-color-primary)}.shell-preset-preview{box-sizing:border-box;display:block;height:64px;overflow:hidden;border:1px solid var(--md-sys-color-outline-variant);background:var(--shell-mini-bg, var(--md-sys-color-surface));-webkit-backdrop-filter:var(--shell-mini-filter, none);backdrop-filter:var(--shell-mini-filter, none)}@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))){.shell-preset-preview--backdrop{background:var(--shell-mini-fallback, var(--md-sys-color-surface))}}@media(prefers-reduced-transparency:reduce){.shell-preset-preview--backdrop{background:var(--shell-mini-fallback, var(--md-sys-color-surface));-webkit-backdrop-filter:none;backdrop-filter:none}}.shell-preset-preview__header{display:flex;align-items:center;gap:4px;padding-inline:8px;height:22px;border-bottom:1px solid transparent}.shell-preset-preview--none{border-style:dashed;border-radius:8px;background:transparent;box-shadow:none}.shell-preset-copy{gap:3px}.shell-preset-copy small{color:var(--md-sys-color-on-surface-variant);font-size:11px;line-height:1.35}.shell-flags{display:flex;gap:16px;align-items:center}.shell-actions-head{display:flex;align-items:center;justify-content:space-between;gap:12px}.shell-available{display:grid;gap:12px}.shell-available-toolbar{display:grid;gap:12px;grid-template-columns:minmax(200px,1fr)}.shell-available-filters{display:flex;gap:12px;align-items:center;flex-wrap:wrap}.shell-available-list{display:grid;gap:10px}.shell-available-item{display:grid;gap:8px;grid-template-columns:minmax(0,1fr) auto;border-bottom:1px solid var(--md-sys-color-outline-variant);padding-block:12px;align-items:start;min-width:0}.shell-available-main{min-width:0;overflow-wrap:anywhere}.shell-available-title{display:flex;align-items:center;gap:10px}.shell-available-text{display:grid;gap:4px}.shell-available-label{font-weight:600}.shell-available-meta{display:flex;gap:8px;flex-wrap:wrap;font-size:11px;color:var(--md-sys-color-on-surface-variant)}.shell-available-desc{font-size:12px;color:var(--md-sys-color-on-surface-variant)}.shell-available-payload{font-size:11px;color:var(--md-sys-color-on-surface-variant)}.shell-action-availability{font-size:12px;line-height:1.5;padding:8px 10px;border-inline-start:2px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container);color:var(--md-sys-color-on-surface-variant)}.shell-available-warning{font-size:11px;color:var(--md-sys-color-error)}.shell-available-actions{display:flex;align-items:flex-start;justify-content:flex-end}.shell-badge{padding:2px 6px;border-radius:999px;border:1px solid var(--md-sys-color-outline-variant);font-size:10px}.shell-code{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.shell-actions{display:grid;gap:12px}.shell-action{border-top:1px solid var(--md-sys-color-outline-variant);padding-block:16px;display:grid;gap:8px}.shell-action-row{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}.shell-action-actions{display:flex;justify-content:flex-end}.shell-action-hint,.shell-empty{font-size:12px;color:var(--md-sys-color-on-surface-variant)}.shell-hint{margin:0;line-height:1.5;align-self:center;font-size:12px;color:var(--md-sys-color-on-surface-variant)}.shell-quick-inputs{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));align-items:end}.shell-input-path{font-size:12px;color:var(--md-sys-color-on-surface-variant);align-self:center}.shell-editor .mat-mdc-form-field{width:100%;min-width:0}:host{display:block;min-width:0}.shell-editor>*,.shell-action>*{min-width:0}.shell-available-text,.shell-input-path{min-width:0;overflow-wrap:anywhere}.shell-preview-region{min-width:0;background:var(--md-sys-color-surface)}.shell-preview-content{display:grid;gap:12px;padding-top:12px}.shell-preview-heading{cursor:pointer;padding-block:8px}.shell-preview-heading:before{content:\"\\25b8\"}.shell-preview-region[open]>.shell-preview-heading:before{content:\"\\25be\"}.shell-preview-heading:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.shell-preview-heading,.shell-override-summary{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px}.shell-preview-heading strong{margin-right:auto;font-size:14px}.shell-preview-heading>span,.shell-override-summary{font-size:12px;color:var(--md-sys-color-on-surface-variant)}.shell-preview{padding:16px;background:var(--md-sys-color-surface-container-low);min-width:0}.shell-sample-content{display:grid;gap:8px}.shell-sample-content>span{font-size:12px;color:var(--md-sys-color-on-surface-variant)}.shell-sample-content strong{font-size:32px;line-height:1.15;font-variant-numeric:tabular-nums}.shell-mini-title,.shell-mini-action,.shell-mini-value,.shell-mini-line{display:block;background:var(--md-sys-color-on-surface-variant);opacity:.45;border-radius:2px}.shell-mini-title{width:24px;height:3px}.shell-mini-action{width:4px;height:4px;margin-left:auto}.shell-mini-value{width:20px;height:8px;margin:8px 8px 4px;opacity:.7}.shell-mini-line{width:32px;height:3px;margin-inline:8px}@media(max-width:640px){.shell-editor{padding-inline:0}.shell-grid,.shell-action-row,.shell-quick-inputs,.shell-available-toolbar{grid-template-columns:minmax(0,1fr)}.shell-actions-head,.shell-flags{flex-wrap:wrap}.shell-field--wide{grid-column:auto}}\n"] }]
|
|
5949
6280
|
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
5950
6281
|
type: Optional
|
|
5951
6282
|
}, {
|
|
@@ -18129,7 +18460,7 @@ class DynamicPageConfigEditorComponent {
|
|
|
18129
18460
|
clone(value) { if (value == null || typeof value !== 'object')
|
|
18130
18461
|
return value; return JSON.parse(JSON.stringify(value)); }
|
|
18131
18462
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DynamicPageConfigEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
18132
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: DynamicPageConfigEditorComponent, isStandalone: true, selector: "praxis-dynamic-page-config-editor", inputs: { page: "page", layout: "layout", presets: "presets", pagePreset: "pagePreset" }, outputs: { pageChange: "pageChange" }, host: { listeners: { "window:resize": "refreshRenderedValidation()" } }, providers: [providePraxisPageBuilderI18n()], viewQueries: [{ propertyName: "arrangementFeedback", first: true, predicate: ["arrangementFeedback"], descendants: true }], ngImport: i0, template: "<div class=\"editor\">\n @if (!isHostedInSettingsPanel) { <div class=\"head\">\n <div>{{ tx('editor.title', 'Page settings') }}</div>\n <button mat-stroked-button type=\"button\" (click)=\"reset()\">{{ tx('editor.reload', 'Reload') }}</button>\n </div> }\n\n @if (nonLimitError) { <p class=\"limits-error\" role=\"alert\">{{ nonLimitError }}</p> }\n @if (limitsError() || previousAdjustableHeights().length) {\n <section class=\"card limits-blocking-banner\" role=\"region\" [attr.aria-label]=\"tx('editor.limits.blockedBannerTitle', 'Height geometry requires review in Widget limits')\" data-testid=\"preset-geometry-blocking-banner\">\n @if (limitsError()) {\n <div class=\"head\">\n <strong>{{ tx('editor.limits.blockedBannerTitle', 'Height geometry requires review in Widget limits') }}</strong>\n </div>\n <p class=\"hint\">{{ tx('editor.limits.blockedBannerHelp', 'One or more widgets use an adjustable pixel height that exceeds the current usable area or requires review before applying the page or template:') }}</p>\n <ul class=\"blocked-widgets-list\">\n @for (item of blockedGeometryWidgets; track item.scope + ':' + item.key) {\n <li>\n <strong>{{ item.title }}</strong>\n @if (item.title !== item.key && !item.title.endsWith('(' + item.key + ')')) { <code>({{ item.key }})</code> }\n <span> \u00B7 {{ item.scopeLabel }}</span>\n @if (item.heightPx !== null) {\n <span> \u00B7 {{ tx('editor.height.fixed', 'Adjustable') }}: <strong>{{ item.heightPx }}px</strong></span>\n }\n </li>\n }\n </ul>\n <p class=\"limits-error\" role=\"alert\">{{ limitsError() }}</p>\n <div class=\"head\">\n <button mat-stroked-button type=\"button\" data-testid=\"goto-blocked-widget-limits\" (click)=\"navigateToBlockedLimit()\">\n <mat-icon aria-hidden=\"true\">tune</mat-icon>{{ tx('editor.limits.gotoLimitsAction', 'Go to Adjustments \u2192 Widget limits') }}\n </button>\n <button mat-flat-button type=\"button\" data-testid=\"switch-blocked-widgets-auto-height\" (click)=\"switchBlockedWidgetsToAutoHeight()\">\n <mat-icon aria-hidden=\"true\">height</mat-icon>{{ tx('editor.limits.switchToAutoHeightAction', 'Switch affected widgets to Automatic height') }}\n </button>\n </div>\n }\n @if (previousAdjustableHeights().length) {\n <div class=\"previous-heights-summary\" data-testid=\"previous-adjustable-heights-summary\">\n <span>{{ tx('editor.limits.previousHeightsSummary', 'Previous adjustable heights preserved for reference:') }}</span>\n @for (prev of previousAdjustableHeights(); track prev.scope + ':' + prev.key) {\n <span class=\"previous-height-chip\">{{ prev.title }} ({{ prev.scopeLabel }}): <strong>{{ prev.heightPx }}px</strong></span>\n }\n </div>\n }\n </section>\n }\n <mat-tab-group class=\"editor-tabs studio-tabs\" [mat-stretch-tabs]=\"false\" [selectedIndex]=\"workspaceTab()\" (selectedIndexChange)=\"workspaceTab.set($event)\">\n <mat-tab [label]=\"tx('studio.visualTab', 'Visual templates')\"><ng-template matTabContent>\n <section class=\"studio-panel\" data-testid=\"studio-visual\">\n <header class=\"studio-heading\"><span class=\"eyebrow\">{{ tx('studio.visualEyebrow', 'Appearance') }}</span><h2>{{ tx('studio.visualTitle', 'Give the page a visual language') }}</h2><p>{{ tx('studio.visualHelp', 'Choose surfaces, density and chart styling together. Individual widget overrides still take precedence.') }}</p></header>\n <button mat-stroked-button type=\"button\" [attr.aria-pressed]=\"!themePresetControl.value\" (click)=\"themePresetControl.setValue('')\">{{ tx('editor.layout.layoutDefault', 'Layout default') }}</button>\n <p class=\"selection-line\">{{ tx('presets.effectiveTheme', 'Effective page theme') }}: <strong>{{ selectedThemePresetEntry?.label || themePresetControl.value || recommendedThemePresetEntry?.label || tx('presets.hostTheme', 'Host theme') }}</strong></p>\n <div class=\"studio-gallery\">\n @for (theme of themePresetEntries; track theme.id) {\n <button type=\"button\" class=\"studio-choice\" [class.selected]=\"themePresetControl.value === theme.id\" [attr.aria-pressed]=\"themePresetControl.value === theme.id\" [attr.data-theme-preset]=\"theme.id\" (click)=\"themePresetControl.setValue(theme.id)\">\n <span class=\"theme-sample\" aria-hidden=\"true\" [style.background]=\"themeTokens(theme.id)['--pdx-page-bg']\" [style.--sample-accent]=\"themeTokens(theme.id)['--pdx-page-accent']\" [style.--sample-surface]=\"themeTokens(theme.id)['--pdx-page-surface']\" [style.--sample-radius]=\"themeTokens(theme.id)['--pdx-page-card-radius']\" [style.--sample-shadow]=\"themeTokens(theme.id)['--pdx-page-card-shadow']\" [style.gap]=\"themeTokens(theme.id)['--pdx-page-gap']\">\n <span class=\"sample-metric\"><b>128</b><i></i></span><span class=\"sample-metric\"><b>86%</b><i></i></span><span class=\"sample-chart\"><i></i><i></i><i></i><i></i><i></i></span>\n </span>\n <span class=\"choice-title\">{{ theme.label }}@if (themePresetControl.value === theme.id) {<mat-icon aria-hidden=\"true\">check_circle</mat-icon>}</span>\n <span class=\"choice-description\">{{ tx('studio.theme.' + theme.id, theme.label) }}</span>\n <span class=\"choice-meta\">{{ tx('studio.density.' + theme.density, theme.density) }} \u00B7 {{ shellName(theme.shellPreset) }}</span>\n </button>\n }\n </div>\n <p class=\"hint\">{{ tx('studio.samplesHelp', 'Illustrative samples using the preset tokens. Apply to see the result with your content.') }}</p>\n <details class=\"shell-options\"><summary>{{ tx('studio.shellTitle', 'Customize widget surfaces') }} \u00B7 {{ shellPresetEntries.length }}</summary>\n <p>{{ tx('studio.shellHelp', 'Override the page theme\u2019s default widget surface. Per-widget appearance settings keep their priority.') }}</p>\n <button mat-stroked-button type=\"button\" [attr.aria-pressed]=\"!shellPresetControl.value\" (click)=\"shellPresetControl.setValue('')\">{{ tx('editor.layout.inheritShell', 'Inherit appearance') }}</button>\n <div class=\"studio-gallery shell-gallery\">@for (shell of shellPresetEntries; track shell.id) {\n <button type=\"button\" class=\"studio-choice\" [class.selected]=\"shellPresetControl.value === shell.id\" [attr.aria-pressed]=\"shellPresetControl.value === shell.id\" (click)=\"shellPresetControl.setValue(shell.id)\">\n <span class=\"shell-sample\" aria-hidden=\"true\" [style.background]=\"shell.appearance.card?.background\" [style.border-color]=\"shell.appearance.card?.borderColor\" [style.border-radius]=\"shell.appearance.card?.borderRadius\" [style.box-shadow]=\"shell.appearance.card?.shadow\"><span [style.background]=\"shell.appearance.header?.background\" [style.border-color]=\"shell.appearance.header?.borderColor\"><mat-icon>analytics</mat-icon><i></i></span><span [style.background]=\"shell.appearance.body?.background\"><i></i><i></i></span></span>\n <span class=\"choice-title\">{{ shellPresetLabel(shell) }}@if (shellPresetControl.value === shell.id) {<mat-icon aria-hidden=\"true\">check_circle</mat-icon>}</span><span class=\"choice-description\">{{ shellDescription(shell) }}</span>\n </button>\n }</div>\n </details>\n </section>\n </ng-template></mat-tab>\n <mat-tab [label]=\"tx('studio.layoutTab', 'Layout templates')\"><ng-template matTabContent>\n <section class=\"studio-panel\" data-testid=\"studio-layout\">\n <header class=\"studio-heading\"><span class=\"eyebrow\">{{ tx('studio.layoutEyebrow', 'Composition model') }}</span><h2>{{ tx('studio.layoutTitle', 'Choose the structure for your use case') }}</h2><p>{{ tx('studio.layoutHelp', 'Compare the roles and default theme of each model. To redistribute the widgets already on this canvas, open Arrange.') }}</p></header>\n @if (duplicateFilterOwners().length) {\n <section class=\"card filter-owner-banner\" role=\"region\" [attr.aria-label]=\"tx('studio.filterOwnerBannerTitle', 'Duplicate filter region detected')\" data-testid=\"duplicate-filter-owner-banner\">\n <div class=\"head\">\n <strong>{{ tx('studio.filterOwnerBannerTitle', 'Single filter region recommendation') }}</strong>\n </div>\n <p class=\"hint\">{{ tx('studio.filterOwnerBannerHelp', 'A table has both its integrated filter bar and an external filter link active. Choose a single query owner to avoid competing filter regions:') }}</p>\n @for (item of duplicateFilterOwners(); track item.tableKey) {\n <div class=\"filter-owner-item\" [attr.data-testid]=\"'duplicate-filter-owner-' + item.tableKey\">\n <span><strong>{{ item.tableTitle }}</strong> <code>({{ item.tableKey }})</code> \u2190 {{ item.externalSourceLabels.join(', ') }}</span>\n <div class=\"studio-actions\">\n <button mat-stroked-button type=\"button\" [attr.data-testid]=\"'consolidate-filter-table-toolbar-' + item.tableKey\" (click)=\"consolidateFilterOwnership(item.tableKey, 'keep-table-toolbar')\">\n {{ tx('studio.keepTableToolbarFilter', 'Use integrated table filter') }}\n </button>\n <button mat-stroked-button type=\"button\" [attr.data-testid]=\"'consolidate-filter-external-link-' + item.tableKey\" (click)=\"consolidateFilterOwnership(item.tableKey, 'keep-external-link')\">\n {{ tx('studio.keepExternalFilterLink', 'Use external filter bar') }}\n </button>\n </div>\n </div>\n }\n </section>\n }\n <details class=\"card composer-card\" data-testid=\"collection-screen-composer\">\n <summary>{{ tx('studio.composer.title', 'Guided collection screen composer (5-minute flow)') }}</summary>\n <p class=\"hint\">{{ tx('studio.composer.help', 'Assemble a complete collection screen with header, KPI indicators, single filter region, primary collection, and optional detail surface in the draft.') }}</p>\n <div class=\"grid\">\n <praxis-resource-quick-connect\n [formControl]=\"composerResourcePathControl\"\n intent=\"collection\"\n [label]=\"tx('studio.composer.resourcePath', 'Resource path')\"\n (resourceSelected)=\"onComposerResourceSelected($event)\"\n data-testid=\"composer-resource-path\"\n />\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('studio.composer.screenTitle', 'Screen title') }}</mat-label>\n <input matInput [formControl]=\"composerTitleControl\" data-testid=\"composer-title\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('studio.composer.groupField', 'KPI group-by field') }}</mat-label>\n <input\n matInput\n [formControl]=\"composerGroupFieldControl\"\n [matAutocomplete]=\"composerGroupFieldPicker\"\n data-testid=\"composer-group-field\"\n />\n <mat-autocomplete\n #composerGroupFieldPicker=\"matAutocomplete\"\n (optionSelected)=\"composerGroupFieldControl.markAsPristine()\"\n >\n @for (field of filteredComposerGroupFieldOptions(); track field.name) {\n <mat-option [value]=\"field.name\" [attr.data-testid]=\"'composer-group-field-option-' + field.name\">\n <div class=\"composer-rich-field-option\">\n <div class=\"composer-rich-field-option__header\">\n <span class=\"composer-rich-field-option__label\">{{ field.label || field.name }}</span>\n @if (field.label && field.label !== field.name) {\n <code class=\"composer-rich-field-option__code\">{{ field.name }}</code>\n }\n @if (getComposerFieldTypeBadge(field); as badge) {\n <span class=\"composer-rich-field-option__badge\">{{ badge }}</span>\n }\n </div>\n @if (getComposerFieldDescription(field); as desc) {\n <span class=\"composer-rich-field-option__desc\">{{ desc }}</span>\n }\n </div>\n </mat-option>\n }\n </mat-autocomplete>\n @if (composerSelectedResource()?.capabilities?.stats === false) {\n <mat-hint data-testid=\"composer-stats-unsupported-hint\">{{ tx('studio.composer.statsUnsupportedHint', 'This resource does not publish stats/group-by aggregation; summary indicators were disabled automatically.') }}</mat-hint>\n } @else {\n <mat-hint>{{ tx('studio.composer.groupFieldHint', 'Choose the boolean or categorical field used in summary indicators.') }}</mat-hint>\n }\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('studio.composer.filterMode', 'Query filter owner') }}</mat-label>\n <mat-select [formControl]=\"composerFilterModeControl\" data-testid=\"composer-filter-mode\">\n <mat-option value=\"table-integrated\">{{ tx('studio.composer.filterIntegrated', 'Integrated table toolbar') }}</mat-option>\n <mat-option value=\"external-filter-bar\">{{ tx('studio.composer.filterExternal', 'Dedicated filter bar widget') }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('studio.composer.collectionKind', 'Primary collection') }}</mat-label>\n <mat-select [formControl]=\"composerCollectionKindControl\" data-testid=\"composer-collection-kind\">\n <mat-option value=\"table\">praxis-Table</mat-option>\n <mat-option value=\"list\">praxis-List</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('studio.composer.detailSurface', 'Secondary detail surface') }}</mat-label>\n <mat-select [formControl]=\"composerDetailSurfaceControl\" data-testid=\"composer-detail-surface\">\n <mat-option value=\"drawer\">Drawer</mat-option>\n <mat-option value=\"modal\">Modal</mat-option>\n <mat-option value=\"none\">{{ tx('studio.composer.noSurface', 'None') }}</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n <div class=\"studio-actions\">\n <button mat-flat-button type=\"button\" data-testid=\"composer-generate-btn\" (click)=\"composeCollectionScreen()\">\n <mat-icon aria-hidden=\"true\">auto_awesome</mat-icon>{{ tx('studio.composer.generate', 'Generate collection screen in draft') }}\n </button>\n </div>\n @if (composerMessage()) {\n <p class=\"studio-status\" role=\"status\" data-testid=\"composer-status-message\">{{ composerMessage() }}</p>\n }\n </details>\n <div class=\"studio-actions\"><button mat-stroked-button type=\"button\" (click)=\"layoutPresetControl.setValue('')\">{{ tx('studio.inheritLayout', 'Use inherited model') }}</button><button mat-button type=\"button\" (click)=\"workspaceTab.set(2)\">{{ tx('studio.goArrange', 'Arrange existing widgets') }}<mat-icon iconPositionEnd>arrow_forward</mat-icon></button></div>\n <praxis-page-preset-comparison [expanded]=\"true\" [layoutId]=\"layoutPresetControl.value\" [layoutPresetOptions]=\"layoutPresetOptions\" [themeId]=\"themePresetControl.value\" [canvas]=\"true\" (choose)=\"layoutPresetControl.setValue($event)\" />\n </section>\n </ng-template></mat-tab>\n <mat-tab [label]=\"tx('studio.arrangeTab', 'Arrange')\"><ng-template matTabContent>\n <section class=\"studio-panel\" data-testid=\"canvas-arrangement\">\n <header class=\"studio-heading\"><span class=\"eyebrow\">{{ tx('studio.arrangeEyebrow', 'Existing widgets') }} \u00B7 {{ canvasColumnsControl.value }} {{ tx('editor.layout.cols', 'Columns') }}</span><h2>{{ tx('studio.arrangeTitle', 'Find the right balance of space') }}</h2><p>{{ tx('studio.arrangeHelp', 'Choose a distribution, compare the real positions, then use it in the draft. Widgets keep their reading order and content.') }}</p></header>\n <div #arrangementFeedback tabindex=\"-1\" class=\"arrangement-feedback\">\n @if (arrangementPreview(); as preview) {\n <section class=\"arrangement-review\" aria-live=\"polite\" data-testid=\"arrangement-preview\">\n <div class=\"review-heading\"><h3>{{ tx('studio.reviewTitle', 'Compare before using') }}</h3><span>{{ preview.keys.length }} {{ tx('studio.changedWidgets', 'widgets change') }}</span></div>\n <div class=\"canvas-comparison\">\n @for (side of ['before', 'after']; track side) {\n <figure><figcaption>{{ side === 'before' ? tx('studio.before', 'Before') : tx('studio.after', 'After') }}</figcaption>\n <div class=\"canvas-map\" [style.grid-template-columns]=\"'repeat(' + preview.canvas.columns + ', minmax(0, 1fr))'\" [style.grid-template-rows]=\"'repeat(' + previewRowCount(preview.before, preview.canvas) + ', minmax(0, 1fr))'\" role=\"img\" [attr.aria-label]=\"side === 'before' ? tx('studio.before', 'Before') : tx('studio.after', 'After')\">\n @for (item of previewItems(side === 'before' ? preview.before : preview.canvas); track item.key) {\n <span [style.grid-column]=\"item.col + ' / span ' + item.colSpan\" [style.grid-row]=\"item.row + ' / span ' + item.rowSpan\" [class.changed]=\"preview.keys.includes(item.key)\" [matTooltip]=\"item.label\">{{ item.number }}</span>\n }\n </div>\n </figure>\n }\n </div>\n <p class=\"hint\">{{ tx('studio.mapHelp', 'Numbered map of your widgets. Positions and spans are to scale; content height is evaluated on the page.') }}</p>\n <details><summary>{{ tx('studio.widgetDetails', 'Widget positions and sizes') }}</summary><ol class=\"widget-changes\">@for (item of previewItems(preview.canvas); track item.key) {<li><strong>{{ item.label }}</strong><span>{{ tx('studio.before', 'Before') }}: {{ tx('editor.arrange.position', 'Column / row') }} {{ preview.before.items[item.key].col }} / {{ preview.before.items[item.key].row }} \u00B7 {{ tx('studio.gridSize', 'Size in columns \u00D7 rows') }} {{ preview.before.items[item.key].colSpan }} \u00D7 {{ preview.before.items[item.key].rowSpan }}</span><span>{{ tx('studio.after', 'After') }}: {{ tx('editor.arrange.position', 'Column / row') }} {{ item.col }} / {{ item.row }} \u00B7 {{ tx('studio.gridSize', 'Size in columns \u00D7 rows') }} {{ item.colSpan }} \u00D7 {{ item.rowSpan }}</span></li>}</ol></details>\n <button mat-flat-button type=\"button\" data-testid=\"arrangement-use\" (click)=\"useArrangement()\">{{ tx('editor.arrange.use', 'Use arrangement in draft') }}<mat-icon iconPositionEnd>arrow_forward</mat-icon></button>\n </section>\n }\n @if (arrangementMessage()) {<p class=\"studio-status\" role=\"status\">{{ arrangementMessage() }}</p>}\n @if (arrangementUndoAvailable()) {<button mat-stroked-button type=\"button\" data-testid=\"arrangement-undo\" (click)=\"undoArrangement()\"><mat-icon>undo</mat-icon>{{ tx('editor.arrange.undo', 'Undo arrangement') }}</button>}\n </div>\n <div class=\"studio-gallery arrangement-gallery\">\n @for (choice of arrangementChoices; track choice.id) {\n <button type=\"button\" class=\"studio-choice\" [class.selected]=\"arrangementSelection() === choice.id\" [attr.aria-pressed]=\"arrangementSelection() === choice.id\" [disabled]=\"!hasCanvasWidgets || !arrangementFits(choice.id)\" [attr.data-testid]=\"'arrange-' + choice.id\" (click)=\"prepareArrangement(choice.id)\">\n <span class=\"distribution-sample\" aria-hidden=\"true\" [style.grid-template-columns]=\"(arrangementFits(choice.id) ? arrangementWidths(choice.id) : choice.weights).join('fr ') + 'fr'\">@for (weight of choice.weights; track $index) {<i>{{ arrangementWidths(choice.id)[$index] }}</i>}@for (weight of choice.weights; track $index) {<i></i>}</span>\n <span class=\"choice-title\">{{ tx('editor.arrange.' + choice.id, choice.id) }}@if (arrangementSelection() === choice.id) {<mat-icon aria-hidden=\"true\">check_circle</mat-icon>}</span>\n <span class=\"choice-description\">{{ tx('studio.arrange.' + choice.id, choice.id) }}</span>\n <span class=\"choice-meta\">{{ arrangementFits(choice.id) ? arrangementWidths(choice.id).join(' + ') : tx('studio.moreColumns', 'Needs more grid columns') }}</span>\n </button>\n }\n </div>\n @if (hasCanvasWidgets) {\n <details class=\"card quick-geometry-card\" data-testid=\"quick-widget-geometry-list\">\n <summary>{{ tx('studio.quickGeometryTitle', 'Quick widget spans (columns \u00D7 rows)') }}</summary>\n <div class=\"quick-geometry-rows\">\n @for (w of quickCanvasWidgets(); track w.key) {\n <div class=\"quick-geometry-row\" [attr.data-testid]=\"'quick-geometry-row-' + w.key\">\n <span class=\"quick-geometry-label\"><mat-icon aria-hidden=\"true\">{{ w.icon }}</mat-icon><strong>{{ w.label }}</strong> <small>({{ w.colSpan }}\u00D7{{ w.rowSpan }})</small></span>\n <div class=\"studio-actions\">\n @for (span of [3, 4, 6, 12]; track span) {\n <button mat-stroked-button type=\"button\" [attr.data-testid]=\"'quick-colspan-' + w.key + '-' + span\" (click)=\"setWidgetCanvasSpan(w.key, span)\">{{ span }}c</button>\n }\n @for (rSpan of [1, 2, 4]; track rSpan) {\n <button mat-button type=\"button\" [attr.data-testid]=\"'quick-rowspan-' + w.key + '-' + rSpan\" (click)=\"setWidgetCanvasSpan(w.key, w.colSpan, rSpan)\">{{ rSpan }}r</button>\n }\n </div>\n </div>\n }\n </div>\n </details>\n }\n @if (!hasCanvasWidgets) {<p role=\"status\">{{ tx('editor.limits.empty', 'Add widgets to the canvas to configure their limits.') }}</p>}\n <details class=\"arrangement-rules\"><summary>{{ tx('studio.rules', 'Scope, devices and locked widgets') }}</summary><p>{{ tx('editor.arrange.help', 'Base canvas, in current reading order. Inheriting devices follow the base; explicit device positions are preserved. Locked positions stay in place.') }}</p><button mat-button type=\"button\" (click)=\"onSelectedTabIndexChange(5)\">{{ tx('studio.goLimits', 'Review widget limits') }}</button></details>\n </section>\n </ng-template></mat-tab>\n <mat-tab [label]=\"tx('studio.settingsTab', 'Adjustments')\">\n <div class=\"studio-panel\"><mat-tab-group class=\"editor-tabs\" [mat-stretch-tabs]=\"false\" [selectedIndex]=\"selectedTabIndex\" (selectedIndexChange)=\"selectDetailTab($event)\">\n <mat-tab [label]=\"tx('editor.tabs.layout', 'Layout')\"><div class=\"grid\"><mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.layout.cols', 'Columns') }}</mat-label><input matInput type=\"number\" min=\"1\" [formControl]=\"canvasColumnsControl\" /></mat-form-field><mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.layout.rowUnit', 'Row unit') }}</mat-label><input matInput [formControl]=\"canvasRowUnitControl\" /></mat-form-field><mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.layout.gap', 'Gap') }}</mat-label><input matInput [formControl]=\"canvasGapControl\" /></mat-form-field><mat-form-field class=\"page-inset-field\" appearance=\"outline\"><mat-label>{{ tx('editor.layout.paddingInline', 'Page side inset') }}</mat-label><input matInput [formControl]=\"paddingInlineControl\" placeholder=\"24px\" /><button praxisIconButton=\"help_outline\" size=\"dense\" matSuffix type=\"button\" [matTooltip]=\"tx('editor.hints.paddingInline', 'Shared space beside all page widgets. Use a CSS length or theme token; 0px removes it. Leave empty to inherit the host theme.')\" [attr.aria-label]=\"tx('editor.layout.paddingInline', 'Page side inset')\"></button></mat-form-field><mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.layout.autoRows', 'Auto rows') }}</mat-label><mat-select [formControl]=\"canvasAutoRowsControl\"><mat-option value=\"fixed\">{{ tx('editor.layout.autoRowsFixed', 'Fixed height') }}</mat-option><mat-option value=\"content\">{{ tx('editor.layout.autoRowsContent', 'Fit content') }}</mat-option></mat-select></mat-form-field></div></mat-tab>\n <mat-tab [label]=\"tx('editor.tabs.grouping', 'Grouping')\"><div class=\"panel\">\n <p class=\"hint\" data-testid=\"canvas-grouping-notice\">{{ tx('editor.grouping.canvasNotice', 'Canvas positions control this page. Groups below are preserved for flow layouts and do not arrange widgets in the canvas. Use the widget move and resize controls to place them side by side.') }}</p><div class=\"head\"><span>{{ tx('editor.grouping.title', 'Grouping') }}</span><button mat-stroked-button type=\"button\" (click)=\"addGroupingNode()\"><mat-icon>add</mat-icon>{{ tx('editor.grouping.add', 'Add group') }}</button></div>@for (group of groupingNodes.controls; track $index) {<div class=\"card\" [formGroup]=\"group\"><div class=\"head\"><span>{{ groupLabel($index) }}</span><button praxisIconButton=\"delete\" size=\"compact\" type=\"button\" [attr.aria-label]=\"tx('editor.grouping.removeAria', 'Remove group')\" (click)=\"removeGroupingNode($index)\"></button></div><div class=\"grid\"><mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.grouping.kind', 'Kind') }}</mat-label><mat-select formControlName=\"kind\"><mat-option value=\"section\">section</mat-option><mat-option value=\"tabs\">tabs</mat-option><mat-option value=\"hero\">hero</mat-option><mat-option value=\"rail\">rail</mat-option></mat-select></mat-form-field><mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.grouping.id', 'Id') }}</mat-label><input matInput formControlName=\"id\" /></mat-form-field><mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.grouping.label', 'Label') }}</mat-label><input matInput formControlName=\"label\" /></mat-form-field>@if (group.controls.kind.value === 'section') {<mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.grouping.layout', 'Layout') }}</mat-label><mat-select formControlName=\"layout\"><mat-option value=\"\">{{ tx('editor.grouping.layoutDefault', 'Default') }}</mat-option><mat-option value=\"stack\">stack</mat-option><mat-option value=\"grid\">grid</mat-option><mat-option value=\"row\">row</mat-option></mat-select></mat-form-field>}@if (group.controls.kind.value === 'hero') {<mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.grouping.emphasis', 'Emphasis') }}</mat-label><mat-select formControlName=\"emphasis\"><mat-option value=\"\">{{ tx('editor.grouping.layoutDefault', 'Default') }}</mat-option><mat-option value=\"high\">high</mat-option><mat-option value=\"medium\">medium</mat-option></mat-select></mat-form-field>}@if (group.controls.kind.value === 'rail') {<mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.grouping.side', 'Side') }}</mat-label><mat-select formControlName=\"side\"><mat-option value=\"left\">left</mat-option><mat-option value=\"right\">right</mat-option></mat-select></mat-form-field>}@if (group.controls.kind.value === 'tabs') {<mat-form-field appearance=\"outline\" class=\"wide\"><mat-label>{{ tx('editor.grouping.tabsJson', 'Tabs (JSON)') }}</mat-label><textarea matInput rows=\"4\" formControlName=\"tabsJson\"></textarea></mat-form-field>} @else {@if (bindablePageWidgets().length > 0) {<mat-form-field appearance=\"outline\" class=\"wide\"><mat-label>{{ tx('editor.grouping.widgets', 'Widgets in group') }}</mat-label><mat-select multiple [value]=\"groupingSelectedWidgetKeys($index)\" (selectionChange)=\"onGroupingWidgetSelectionChange($index, $event.value)\" [attr.data-testid]=\"'grouping-widget-select-' + $index\" panelClass=\"praxis-binder-widget-select-panel\"><mat-select-trigger><span class=\"binder-widget-trigger\"><mat-icon aria-hidden=\"true\" class=\"binder-widget-trigger-icon\">{{ groupingSelectedWidgetIcon($index) }}</mat-icon><span class=\"binder-widget-trigger-text\">{{ groupingSelectedWidgetsLabel($index) }}</span></span></mat-select-trigger>@for (w of bindablePageWidgets(); track w.key) {<mat-option [value]=\"w.key\" [attr.data-widget-key]=\"w.key\" class=\"binder-widget-option\"><span class=\"binder-widget-option-row\"><mat-icon class=\"binder-widget-option-icon\" aria-hidden=\"true\">{{ w.icon }}</mat-icon><span class=\"binder-widget-option-copy\"><strong class=\"binder-widget-option-title\">{{ w.title }}</strong>@if (w.description) {<small class=\"binder-widget-option-desc\">{{ w.description }}</small>}</span><span class=\"binder-widget-option-sr-key\" aria-hidden=\"true\">{{ w.key }}</span></span></mat-option>}</mat-select></mat-form-field>}<mat-form-field appearance=\"outline\" class=\"wide\"><mat-label>{{ tx('editor.grouping.widgetKeysCsv', 'Widget keys (comma-separated)') }}</mat-label><input matInput formControlName=\"widgetKeysCsv\" /></mat-form-field>}</div></div>}</div></mat-tab>\n <mat-tab [label]=\"tx('editor.tabs.devices', 'Device Layouts')\">\n <div class=\"panel\">\n <mat-button-toggle-group [value]=\"activeDevice()\" (change)=\"activeDevice.set($event.value)\" [attr.aria-label]=\"tx('editor.devices.target', 'Device to configure')\">\n @for (device of deviceKeys; track device) {\n <mat-button-toggle [value]=\"device\">{{ deviceLabels[device] }}</mat-button-toggle>\n }\n </mat-button-toggle-group>\n @for (device of deviceKeys; track device) {\n @if (activeDevice() === device) {\n <div class=\"card\">\n <div class=\"head\"><strong>{{ deviceLabels[device] }}</strong><button mat-stroked-button type=\"button\" [attr.data-testid]=\"'stack-device-' + device\" [disabled]=\"!hasCanvasWidgets\" (click)=\"stackDevice(device)\"><mat-icon aria-hidden=\"true\">view_agenda</mat-icon>{{ tx('editor.devices.stack', 'Stack widgets') }}</button></div>\n <p class=\"hint\">{{ tx('editor.devices.stackHelp', 'Use full width and natural height, in reading order. This replaces positions and width limits only for this device; other layouts stay unchanged.') }}</p>\n @if (stackedDevices.has(device)) { <p class=\"hint\" role=\"status\">{{ tx('editor.devices.stacked', 'Single-column layout prepared. Save to apply; Reset undoes the changes.') }}</p> }\n <div class=\"grid\">\n <mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.devices.columns', 'Columns') }}</mat-label><input matInput type=\"number\" min=\"1\" [formControl]=\"deviceForms[device].controls.columns\" /></mat-form-field>\n <mat-form-field class=\"page-inset-field\" appearance=\"outline\"><mat-label>{{ tx('editor.layout.paddingInline', 'Page side inset') }}</mat-label><input matInput [formControl]=\"deviceForms[device].controls.paddingInline\" placeholder=\"16px\" /></mat-form-field><mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.devices.gap', 'Gap') }}</mat-label><input matInput [formControl]=\"deviceForms[device].controls.gap\" /></mat-form-field>\n <mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.layout.autoRows', 'Auto rows') }}</mat-label><mat-select [formControl]=\"deviceForms[device].controls.autoRows\"><mat-option value=\"\">{{ tx('editor.devices.orientationDefault', 'Default') }}</mat-option><mat-option value=\"fixed\">{{ tx('editor.layout.autoRowsFixed', 'Fixed height') }}</mat-option><mat-option value=\"content\">{{ tx('editor.layout.autoRowsContent', 'Fit content') }}</mat-option></mat-select></mat-form-field>\n </div>\n <details><summary>{{ tx('editor.devices.advanced', 'Advanced overrides') }}</summary><div class=\"grid\">\n <mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.layout.rowUnit', 'Row unit') }}</mat-label><input matInput [formControl]=\"deviceForms[device].controls.rowUnit\" /></mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"wide\"><mat-label>{{ tx('editor.devices.groupingOverridesJson', 'Grouping overrides (JSON)') }}</mat-label><textarea matInput rows=\"4\" [formControl]=\"deviceForms[device].controls.groupingOverrides\"></textarea></mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"wide\"><mat-label>{{ tx('editor.devices.widgetOverridesJson', 'Widget overrides (JSON)') }}</mat-label><textarea matInput rows=\"4\" [formControl]=\"deviceForms[device].controls.widgetOverrides\"></textarea><mat-hint>{{ tx('editor.devices.widgetOverridesHelp', 'Supported per-widget properties on this device: className, span, order, and hidden. Does not override internal widget definition or inputs.') }}</mat-hint></mat-form-field>\n </div></details>\n @if (!stackedDevices.has(device)) { <div class=\"hint\">{{ tx('editor.preview.deviceCanvasHint', 'Existing canvas item overrides are preserved.') }}</div> }\n </div>\n }\n }\n </div>\n </mat-tab>\n <mat-tab [label]=\"tx('editor.tabs.context', 'Context')\"><mat-form-field appearance=\"outline\" class=\"wide\"><mat-label>{{ tx('editor.context.json', 'Context (JSON)') }}</mat-label><textarea matInput rows=\"12\" [formControl]=\"contextControl\"></textarea></mat-form-field></mat-tab>\n <mat-tab [label]=\"tx('editor.tabs.state', 'Page State')\">\n <div class=\"panel\">\n <mat-form-field appearance=\"outline\" class=\"wide\">\n <mat-label>{{ tx('editor.state.stateDefinitionJson', 'Page state (values, schema, derived \u2014 JSON)') }}</mat-label>\n <textarea matInput rows=\"10\" [formControl]=\"stateControl\" data-testid=\"page-state-json\"></textarea>\n </mat-form-field>\n\n <section class=\"panel\" data-testid=\"page-datasources-guided\">\n <div class=\"head\">\n <strong>{{ tx('editor.state.dataSourcesTitle', 'Data Sources') }}</strong>\n <div class=\"studio-actions\">\n @if (dataSourceNodes.controls.length) {\n <button\n mat-button\n type=\"button\"\n data-testid=\"page-state-sync-from-datasources\"\n (click)=\"syncStateValuesFromDataSources()\"\n >\n <mat-icon>sync</mat-icon>{{ tx('editor.state.syncFromDataSources', 'Sync state paths') }}\n </button>\n }\n <button mat-stroked-button type=\"button\" data-testid=\"page-datasources-add\" (click)=\"addDataSourceNode()\">\n <mat-icon>add</mat-icon>{{ tx('editor.state.addDataSource', 'Add data source') }}\n </button>\n </div>\n </div>\n <p class=\"hint\">{{ tx('editor.state.dataSourcesCopy', 'Bind HTTP or resource endpoints directly into page state paths with reactive parameters.') }}</p>\n @if (!dataSourceNodes.controls.length) {\n <p class=\"hint\" data-testid=\"page-datasources-empty\">{{ tx('editor.state.emptyDataSources', 'No declarative data sources configured.') }}</p>\n }\n @for (ds of dataSourceNodes.controls; track $index) {\n <div class=\"card\" [formGroup]=\"ds\" [attr.data-testid]=\"'page-datasource-card-' + $index\">\n <div class=\"head\">\n <span>{{ dataSourceLabel($index) }}</span>\n <button\n praxisIconButton=\"delete\"\n size=\"compact\"\n type=\"button\"\n [attr.data-testid]=\"'page-datasource-remove-' + $index\"\n [attr.aria-label]=\"tx('editor.state.removeDataSourceAria', 'Remove data source')\"\n (click)=\"removeDataSourceNode($index)\"\n ></button>\n </div>\n <div class=\"grid\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('editor.state.dataSourceId', 'ID') }}</mat-label>\n <input matInput formControlName=\"id\" [attr.data-testid]=\"'page-datasource-id-' + $index\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('editor.state.dataSourceKind', 'Kind') }}</mat-label>\n <mat-select formControlName=\"kind\" [attr.data-testid]=\"'page-datasource-kind-' + $index\">\n <mat-option value=\"\">{{ tx('editor.common.default', 'Default') }}</mat-option>\n <mat-option value=\"remote-resource\">remote-resource</mat-option>\n <mat-option value=\"stats-query\">stats-query</mat-option>\n <mat-option value=\"raw-http\">raw-http</mat-option>\n </mat-select>\n </mat-form-field>\n <praxis-resource-quick-connect\n formControlName=\"resourcePath\"\n [intent]=\"ds.controls.kind.value === 'stats-query' ? 'stats' : (ds.controls.kind.value === 'raw-http' ? 'any' : 'collection')\"\n [endpointKey]=\"resolveEndpointKey(ds.controls.endpointKey.value)\"\n [label]=\"tx('editor.state.resourcePath', 'Resource path')\"\n [attr.data-testid]=\"'page-datasource-resourcePath-' + $index\"\n />\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('editor.state.endpointKey', 'Endpoint key') }}</mat-label>\n <input matInput formControlName=\"endpointKey\" [attr.data-testid]=\"'page-datasource-endpointKey-' + $index\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('editor.state.method', 'HTTP method') }}</mat-label>\n <mat-select formControlName=\"method\" [attr.data-testid]=\"'page-datasource-method-' + $index\">\n <mat-option value=\"\">{{ tx('editor.common.auto', 'Auto') }}</mat-option>\n <mat-option value=\"GET\">GET</mat-option>\n <mat-option value=\"POST\">POST</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('editor.state.targetStatePath', 'Target state path') }}</mat-label>\n <input\n matInput\n formControlName=\"targetStatePath\"\n [attr.data-testid]=\"'page-datasource-targetStatePath-' + $index\"\n (blur)=\"syncStateValuesFromDataSources()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('editor.state.errorPolicy', 'Error policy') }}</mat-label>\n <mat-select formControlName=\"errorPolicy\" [attr.data-testid]=\"'page-datasource-errorPolicy-' + $index\">\n <mat-option value=\"\">{{ tx('editor.common.default', 'Default') }}</mat-option>\n <mat-option value=\"propagate\">propagate</mat-option>\n <mat-option value=\"silent\">silent</mat-option>\n <mat-option value=\"fallback\">fallback</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('editor.state.refreshIntervalMs', 'Refresh interval (ms)') }}</mat-label>\n <input matInput type=\"number\" min=\"0\" formControlName=\"refreshIntervalMs\" [attr.data-testid]=\"'page-datasource-refreshIntervalMs-' + $index\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('editor.state.autoFetch', 'Auto fetch') }}</mat-label>\n <mat-select formControlName=\"autoFetch\" [attr.data-testid]=\"'page-datasource-autoFetch-' + $index\">\n <mat-option value=\"\">{{ tx('editor.common.default', 'Default') }}</mat-option>\n <mat-option value=\"true\">true</mat-option>\n <mat-option value=\"false\">false</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"wide\">\n <mat-label>{{ tx('editor.state.description', 'Description') }}</mat-label>\n <input matInput formControlName=\"description\" [attr.data-testid]=\"'page-datasource-description-' + $index\" />\n </mat-form-field>\n <div class=\"wide guided-presets-bar\" [attr.data-testid]=\"'page-datasource-guided-presets-' + $index\">\n <span class=\"hint\">{{ tx('editor.state.guidedPresets', 'Guided query & transform templates:') }}</span>\n <div class=\"studio-actions\">\n <button mat-stroked-button type=\"button\" [attr.data-testid]=\"'page-datasource-preset-groupby-' + $index\" (click)=\"applyGuidedDataSourceQuery($index, { mode: 'group-by', groupByField: 'ativo', metricField: 'id', metricOp: 'count' }); applyGuidedDataSourceTransform($index, { kind: 'group-by-map', totalField: 'total', mappingsText: 'true:active, false:inactive', ratioTargetField: 'activeRatePct', ratioNumeratorField: 'active', ratioDenominatorField: 'total', ratioScale: 100, ratioDecimals: 1 })\">\n {{ tx('editor.state.presetGroupByKpi', 'Group-by + Total + Ratio (%)') }}\n </button>\n <button mat-stroked-button type=\"button\" [attr.data-testid]=\"'page-datasource-preset-weighted-' + $index\" (click)=\"applyGuidedDataSourceTransform($index, { kind: 'group-by-map', totalField: 'total', weightedAverageField: 'weightedAvg', weightedAverageDecimals: 2, mappingsText: '1:low, 5:high' })\">\n {{ tx('editor.state.presetWeightedAvg', 'Weighted average') }}\n </button>\n <button mat-stroked-button type=\"button\" [attr.data-testid]=\"'page-datasource-preset-timeseries-' + $index\" (click)=\"applyGuidedDataSourceQuery($index, { mode: 'timeseries', groupByField: 'createdAt', period: 'month', metricField: 'id', metricOp: 'count' }); applyGuidedDataSourceTransform($index, { kind: 'timeseries-last', mappingsText: 'count:latestCount, avg:latestAvg' })\">\n {{ tx('editor.state.presetTimeseriesLast', 'Timeseries latest point') }}\n </button>\n <button mat-stroked-button type=\"button\" [attr.data-testid]=\"'page-datasource-preset-path-' + $index\" (click)=\"applyGuidedDataSourceTransform($index, { kind: 'path', path: 'totalElements' })\">\n {{ tx('editor.state.presetPathExtract', 'Extract path (totalElements)') }}\n </button>\n </div>\n </div>\n <mat-form-field appearance=\"outline\" class=\"wide\">\n <mat-label>{{ tx('editor.state.queryJson', 'Query (JSON)') }}</mat-label>\n <textarea matInput rows=\"3\" formControlName=\"queryJson\" [attr.data-testid]=\"'page-datasource-queryJson-' + $index\"></textarea>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"wide\">\n <mat-label>{{ tx('editor.state.transformJson', 'Transform (JSON)') }}</mat-label>\n <textarea matInput rows=\"3\" formControlName=\"transformJson\" [attr.data-testid]=\"'page-datasource-transformJson-' + $index\"></textarea>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"wide\">\n <mat-label>{{ tx('editor.state.fallbackJson', 'Fallback value (JSON)') }}</mat-label>\n <textarea matInput rows=\"2\" formControlName=\"fallbackJson\" [attr.data-testid]=\"'page-datasource-fallbackJson-' + $index\"></textarea>\n </mat-form-field>\n </div>\n @let preview = getDataSourcePreview($index);\n <div class=\"datasource-preview-box\" [attr.data-testid]=\"'page-datasource-preview-' + $index\">\n <div class=\"head\">\n <strong>{{ tx('editor.state.previewTitle', 'Transform & State Preview') }}</strong>\n <span class=\"previous-height-chip\">{{ preview.sourceKind === 'live' ? tx('editor.state.previewLive', 'Live response') : tx('editor.state.previewSample', 'Simulated sample') }}</span>\n </div>\n @if (preview.status === 'error') {\n <p class=\"limits-error\" role=\"alert\" [attr.data-testid]=\"'page-datasource-preview-error-' + $index\">{{ preview.error }}</p>\n } @else {\n <pre class=\"datasource-preview-json\" [attr.data-testid]=\"'page-datasource-preview-json-' + $index\">{{ preview.formattedJson }}</pre>\n @if (preview.tokens.length) {\n <div class=\"studio-actions\" [attr.data-testid]=\"'page-datasource-preview-tokens-' + $index\">\n <span class=\"hint\">{{ tx('editor.state.availableTokens', 'Click a token to prepare widget binding:') }}</span>\n @for (token of preview.tokens; track token) {\n <button mat-button type=\"button\" (click)=\"binderTokenControl.setValue(token)\"><code>{{ token }}</code></button>\n }\n </div>\n }\n }\n </div>\n </div>\n }\n </section>\n\n <section class=\"card\" data-testid=\"page-derived-measures-guided\">\n <div class=\"head\">\n <strong>{{ tx('editor.state.derivedMeasuresTitle', 'Guided Derived Measures') }}</strong>\n </div>\n <p class=\"hint\">{{ tx('editor.state.derivedMeasuresCopy', 'Create percentages, ratios, weighted averages, or formatted measures from page state paths without writing raw JsonLogic.') }}</p>\n <div class=\"grid\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('editor.state.derivedTargetPath', 'Measure key') }}</mat-label>\n <input matInput [formControl]=\"derivedPathControl\" placeholder=\"activeRatePct\" data-testid=\"derived-target-path\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('editor.state.derivedKind', 'Calculation') }}</mat-label>\n <mat-select [formControl]=\"derivedKindControl\" data-testid=\"derived-kind\">\n <mat-option value=\"ratio-percent\">{{ tx('editor.state.derivedRatio', 'Ratio / Percentage') }}</mat-option>\n <mat-option value=\"weighted-average\">{{ tx('editor.state.derivedWeighted', 'Weighted average') }}</mat-option>\n <mat-option value=\"format-value\">{{ tx('editor.state.derivedFormat', 'Formatted value') }}</mat-option>\n <mat-option value=\"template\">{{ tx('editor.state.derivedTemplate', 'State reference') }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('editor.state.derivedNumerator', 'Numerator / Source path') }}</mat-label>\n <input matInput [formControl]=\"derivedNumeratorControl\" placeholder=\"kpis.active\" data-testid=\"derived-numerator\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('editor.state.derivedDenominator', 'Denominator path') }}</mat-label>\n <input matInput [formControl]=\"derivedDenominatorControl\" placeholder=\"kpis.total\" data-testid=\"derived-denominator\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('editor.state.derivedFormatKind', 'Format') }}</mat-label>\n <mat-select [formControl]=\"derivedFormatControl\" data-testid=\"derived-format\">\n <mat-option value=\"percent\">percent (%)</mat-option>\n <mat-option value=\"number\">number</mat-option>\n <mat-option value=\"integer\">integer</mat-option>\n <mat-option value=\"currency\">currency (R$)</mat-option>\n <mat-option value=\"boolean\">boolean</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n <div class=\"studio-actions\">\n <button mat-stroked-button type=\"button\" data-testid=\"derived-add-btn\" (click)=\"addGuidedDerivedMeasure()\">\n <mat-icon aria-hidden=\"true\">functions</mat-icon>{{ tx('editor.state.addDerivedAction', 'Add measure to page state') }}\n </button>\n </div>\n @let derivedPreview = getDerivedStatePreview();\n @if (derivedPreview.entries.length || derivedPreview.diagnostics.length) {\n <div class=\"datasource-preview-box\" data-testid=\"page-derived-preview\">\n @for (diag of derivedPreview.diagnostics; track diag) {\n <p class=\"limits-error\" role=\"alert\">{{ diag }}</p>\n }\n @if (derivedPreview.entries.length) {\n <div class=\"studio-actions\">\n @for (entry of derivedPreview.entries; track entry.key) {\n <button mat-button type=\"button\" (click)=\"binderTokenControl.setValue(entry.token)\">\n <strong>{{ entry.key }}</strong>: {{ entry.formattedValue }} <code>({{ entry.token }})</code>\n </button>\n }\n </div>\n }\n </div>\n }\n </section>\n\n <section class=\"card\" data-testid=\"page-state-component-binder\">\n <div class=\"head\">\n <strong>{{ tx('editor.state.binderTitle', 'Connect State or Measure to Widget') }}</strong>\n </div>\n <p class=\"hint\">{{ tx('editor.state.binderCopy', 'Bind any state path or derived measure directly to a widget input in the draft.') }}</p>\n <div class=\"grid\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('editor.state.binderToken', 'State token') }}</mat-label>\n <input matInput [formControl]=\"binderTokenControl\" placeholder=\"{{ '{{state.kpis.total}}' }}\" data-testid=\"binder-token\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('editor.state.binderWidget', 'Target widget') }}</mat-label>\n <mat-select [formControl]=\"binderWidgetControl\" data-testid=\"binder-widget\" panelClass=\"praxis-binder-widget-select-panel\">\n @if (selectedBinderWidget(); as selectedWidget) {\n <mat-select-trigger>\n <span class=\"binder-widget-trigger\">\n <mat-icon aria-hidden=\"true\" class=\"binder-widget-trigger-icon\">{{ selectedWidget.icon }}</mat-icon>\n <span class=\"binder-widget-trigger-text\">\n <strong>{{ selectedWidget.title }}</strong>\n @if (selectedWidget.description) {\n <small> \u00B7 {{ selectedWidget.description }}</small>\n }\n </span>\n </span>\n </mat-select-trigger>\n }\n @for (w of bindablePageWidgets(); track w.key) {\n <mat-option [value]=\"w.key\" [attr.data-widget-key]=\"w.key\" class=\"binder-widget-option\">\n <span class=\"binder-widget-option-row\">\n <mat-icon aria-hidden=\"true\" class=\"binder-widget-option-icon\">{{ w.icon }}</mat-icon>\n <span class=\"binder-widget-option-copy\">\n <strong class=\"binder-widget-option-title\">{{ w.title }}</strong>\n @if (w.description) {\n <small class=\"binder-widget-option-desc\">{{ w.description }}</small>\n }\n </span>\n <span class=\"binder-widget-option-sr-key\" aria-hidden=\"true\">{{ w.key }}</span>\n </span>\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('editor.state.binderInputPath', 'Widget input path') }}</mat-label>\n <input matInput [formControl]=\"binderInputPathControl\" placeholder=\"value\" data-testid=\"binder-input-path\" />\n </mat-form-field>\n </div>\n @if (availableStateBindingTokens().length) {\n <div class=\"binder-input-suggestions\" data-testid=\"binder-token-suggestions\">\n <span class=\"hint\">{{ tx('editor.state.binderTokenSuggestions', 'Available tokens:') }}</span>\n @for (availableToken of availableStateBindingTokens(); track availableToken) {\n <button\n type=\"button\"\n class=\"binder-input-suggestion-chip\"\n [class.active]=\"binderTokenControl.value === availableToken\"\n [attr.data-testid]=\"'binder-token-suggestion-' + availableToken\"\n (click)=\"selectBinderTokenSuggestion(availableToken)\"\n >\n <code>{{ availableToken }}</code>\n </button>\n }\n </div>\n }\n <div class=\"binder-input-suggestions\" data-testid=\"binder-pipe-suggestions\">\n <span class=\"hint\">{{ tx('editor.state.binderPipeSuggestions', 'Formatadores CSP-safe (pipes):') }}</span>\n @for (pipeSegment of binderDeclarativePipes; track pipeSegment) {\n <button\n type=\"button\"\n class=\"binder-input-suggestion-chip\"\n [attr.data-testid]=\"'binder-pipe-chip-' + pipeSegment\"\n (click)=\"appendBinderTokenPipe(pipeSegment)\"\n >\n <code>{{ pipeSegment }}</code>\n </button>\n }\n </div>\n @if (binderTokenPipeError(); as pipeErr) {\n <p class=\"studio-status binder-unbound-warning\" role=\"alert\" data-testid=\"binder-pipe-error\">{{ pipeErr }}</p>\n } @else if (binderTokenLivePreview(); as livePreviewVal) {\n <p class=\"hint\" data-testid=\"binder-live-preview\">\n {{ tx('editor.state.binderLivePreview', 'Preview simulado:') }} <strong>{{ livePreviewVal }}</strong>\n </p>\n }\n @if (selectedBinderWidgetInputSuggestions().length) {\n <div class=\"binder-input-suggestions\" data-testid=\"binder-input-suggestions\">\n <span class=\"hint\">{{ tx('editor.state.binderInputSuggestions', 'Suggested paths:') }}</span>\n @for (suggestedPath of selectedBinderWidgetInputSuggestions(); track suggestedPath) {\n <button\n type=\"button\"\n class=\"binder-input-suggestion-chip\"\n [class.active]=\"binderInputPathControl.value === suggestedPath\"\n [attr.data-testid]=\"'binder-input-suggestion-' + suggestedPath\"\n (click)=\"selectBinderInputPathSuggestion(suggestedPath)\"\n >\n <code>{{ suggestedPath }}</code>\n </button>\n }\n </div>\n }\n <div class=\"studio-actions\">\n <button mat-stroked-button type=\"button\" data-testid=\"binder-bind-btn\" (click)=\"bindStateTokenToWidget()\">\n <mat-icon aria-hidden=\"true\">link</mat-icon>{{ tx('editor.state.bindAction', 'Bind token to widget in draft') }}\n </button>\n <button mat-stroked-button type=\"button\" data-testid=\"binder-auto-bind-btn\" (click)=\"autoBindWidgetStatePlaceholders()\">\n <mat-icon aria-hidden=\"true\">auto_fix_high</mat-icon>{{ tx('editor.state.autoBindAction', 'Auto-bind ${...} widget placeholders') }}\n </button>\n </div>\n @if (binderMessage()) {\n <p class=\"studio-status\" role=\"status\" data-testid=\"binder-status-message\">{{ binderMessage() }}</p>\n }\n @if (invalidWidgetStateBindingPipes().length) {\n <div class=\"binder-unbound-warning\" role=\"alert\" data-testid=\"binder-invalid-pipes-warning\">\n <mat-icon aria-hidden=\"true\">error_outline</mat-icon>\n <div class=\"binder-unbound-warning__body\">\n <span>{{ tx('editor.state.invalidWidgetPipesWarning', 'Formatadores (pipes) inv\u00E1lidos detectados:') }}</span>\n <div class=\"binder-unbound-warning__chips\">\n @for (invalidBinding of invalidWidgetStateBindingPipes(); track invalidBinding.widgetKey + ':' + invalidBinding.inputPath) {\n <button\n type=\"button\"\n class=\"binder-unbound-chip\"\n [attr.data-testid]=\"'binder-invalid-pipe-chip-' + invalidBinding.widgetKey + '-' + invalidBinding.inputPath\"\n (click)=\"loadStateBindingIntoForm(invalidBinding)\"\n >\n <strong>{{ invalidBinding.widgetTitle }}</strong>.<code>{{ invalidBinding.inputPath }}</code> (<code>{{ invalidBinding.expression }}</code>)\n </button>\n }\n </div>\n </div>\n </div>\n }\n @if (unboundWidgetStatePlaceholders().length; as unboundCount) {\n <div class=\"binder-unbound-warning\" role=\"status\" data-testid=\"binder-unbound-placeholders-warning\">\n <mat-icon aria-hidden=\"true\">warning_amber</mat-icon>\n <div class=\"binder-unbound-warning__body\">\n <span>{{ tx('editor.state.unboundPlaceholdersWarning', 'Placeholders sem v\u00EDnculo detectados:') }}</span>\n <div class=\"binder-unbound-warning__chips\">\n @for (item of unboundWidgetStatePlaceholders(); track item.widgetKey + ':' + item.placeholder) {\n <button\n type=\"button\"\n class=\"binder-unbound-chip\"\n [attr.data-testid]=\"'binder-unbound-chip-' + item.widgetKey + '-' + item.placeholder\"\n [attr.title]=\"tx('editor.state.selectUnboundPlaceholderHint', 'Clique para preencher widget e caminho no formul\u00E1rio')\"\n (click)=\"selectUnboundPlaceholder(item)\"\n >\n <strong>{{ item.widgetTitle }}</strong> (<code>{{ '${' + item.placeholder + '}' }}</code>)\n </button>\n }\n </div>\n </div>\n </div>\n }\n @if (currentWidgetStateBindings().length) {\n <ul class=\"blocked-widgets-list binder-active-list\" data-testid=\"active-widget-state-bindings\">\n @for (binding of currentWidgetStateBindings(); track binding.widgetKey + ':' + binding.inputPath) {\n @let previewVal = resolveStateTokenPreview(binding.token);\n <li class=\"binder-active-item\">\n <mat-icon aria-hidden=\"true\" class=\"binder-active-icon\">{{ widgetIconForKey(binding.widgetKey) }}</mat-icon>\n <button\n type=\"button\"\n class=\"binder-active-copy binder-active-select-btn\"\n [attr.data-testid]=\"'binder-select-' + binding.widgetKey + '-' + binding.inputPath\"\n [attr.title]=\"tx('editor.state.editBindingHint', 'Click to load binding into form')\"\n (click)=\"loadStateBindingIntoForm(binding)\"\n >\n <strong>{{ binding.widgetTitle }}</strong>.<code>{{ binding.inputPath }}</code> \u2190 <code>{{ binding.token }}</code>\n @if (previewVal !== null) {\n <small class=\"binder-preview-badge\" [attr.data-testid]=\"'binder-preview-' + binding.widgetKey + '-' + binding.inputPath\">= {{ previewVal }}</small>\n }\n </button>\n <button mat-icon-button type=\"button\" [attr.data-testid]=\"'binder-unbind-' + binding.widgetKey + '-' + binding.inputPath\"\n [attr.aria-label]=\"tx('editor.state.unbindAction', 'Remove binding')\"\n (click)=\"unbindStateTokenFromWidget(binding.widgetKey, binding.inputPath)\">\n <mat-icon aria-hidden=\"true\">link_off</mat-icon>\n </button>\n </li>\n }\n </ul>\n }\n </section>\n\n <mat-form-field appearance=\"outline\" class=\"wide\">\n <mat-label>{{ tx('editor.state.dataSourcesJson', 'page.dataSources (JSON)') }}</mat-label>\n <textarea matInput rows=\"8\" [formControl]=\"dataSourcesControl\" [placeholder]=\"tx('editor.hints.dataSourcesExample', '')\" data-testid=\"page-datasources-json\"></textarea>\n </mat-form-field>\n </div>\n </mat-tab>\n <mat-tab [label]=\"tx('editor.limits.tab', 'Widget limits')\">\n <section class=\"limits-panel\" data-testid=\"canvas-limits-editor\">\n @if (workspaceTab() === 3 && activeTab() === 'limits' && canvasLimits.targets.length) {\n <div class=\"selection-identity\" tabindex=\"-1\" data-testid=\"canvas-limits-identity\">\n <mat-icon aria-hidden=\"true\">{{ limitsTargetIcon(limitsTargetControl.value) }}</mat-icon>\n <strong>{{ limitsTargetLabel(limitsTargetControl.value) }}</strong>\n </div>\n }\n\n <p class=\"hint\" id=\"canvas-limits-help\">{{ tx('editor.limits.help', 'Limits use grid columns and rows, not pixels. Leave blank for defaults, or to inherit the base limits on a device.') }}</p>\n @if (canvasLimits.targets.length) {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('editor.limits.target', 'Widget and layout') }}</mat-label>\n <mat-select [formControl]=\"limitsTargetControl\" data-testid=\"canvas-limits-target\">\n <mat-select-trigger>\n <span class=\"binder-widget-trigger\">\n <mat-icon aria-hidden=\"true\" class=\"binder-widget-trigger-icon\">{{ limitsTargetIcon(limitsTargetControl.value) }}</mat-icon>\n <span class=\"binder-widget-trigger-text\">{{ limitsTargetLabel(limitsTargetControl.value) }}</span>\n </span>\n </mat-select-trigger>\n @for (target of canvasLimits.targets; track target.scope + ':' + target.key; let index = $index) {\n <mat-option [value]=\"index\">\n <mat-icon aria-hidden=\"true\">{{ limitsTargetIcon(index) }}</mat-icon>\n <span>{{ limitsTargetLabel(index) }}</span>\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <p class=\"hint\">{{ limitsGeometryLabel() }}</p>\n @if (canvasLimits.form.controls[limitsTargetControl.value]; as limitsForm) {\n <div class=\"limits-fields\" aria-describedby=\"canvas-limits-help\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('editor.limits.minColSpan', 'Minimum width (columns)') }}</mat-label>\n <input matInput [errorStateMatcher]=\"limitsErrorMatcher\" type=\"number\" min=\"1\" step=\"1\" [formControl]=\"limitsForm.controls.minColSpan\" data-testid=\"canvas-limit-minColSpan\">\n <mat-error>{{ limitFieldError(limitsForm.controls.minColSpan) }}</mat-error>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('editor.limits.maxColSpan', 'Maximum width (columns)') }}</mat-label>\n <input matInput [errorStateMatcher]=\"limitsErrorMatcher\" type=\"number\" min=\"1\" step=\"1\" [formControl]=\"limitsForm.controls.maxColSpan\" data-testid=\"canvas-limit-maxColSpan\">\n <mat-error>{{ limitFieldError(limitsForm.controls.maxColSpan) }}</mat-error>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('editor.limits.minRowSpan', 'Minimum height (rows)') }}</mat-label>\n <input matInput [errorStateMatcher]=\"limitsErrorMatcher\" type=\"number\" min=\"1\" step=\"1\" [formControl]=\"limitsForm.controls.minRowSpan\" data-testid=\"canvas-limit-minRowSpan\">\n <mat-error>{{ limitFieldError(limitsForm.controls.minRowSpan) }}</mat-error>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('editor.limits.maxRowSpan', 'Maximum height (rows)') }}</mat-label>\n <input matInput [errorStateMatcher]=\"limitsErrorMatcher\" type=\"number\" min=\"1\" step=\"1\" [formControl]=\"limitsForm.controls.maxRowSpan\" data-testid=\"canvas-limit-maxRowSpan\">\n <mat-error>{{ limitFieldError(limitsForm.controls.maxRowSpan) }}</mat-error>\n </mat-form-field>\n </div>\n @if (limitsUseContentHeight()) {\n <p class=\"limits-notice\"><mat-icon aria-hidden=\"true\">height</mat-icon><span>{{ tx('editor.limits.autoHeight', 'Choose natural height or an adjustable height with internal scrolling.') }}</span></p>\n <mat-button-toggle-group class=\"height-modes\" [formControl]=\"limitsForm.controls.heightMode\" [attr.aria-label]=\"tx('editor.height.mode', 'Widget height')\" data-testid=\"canvas-height-mode\">\n <mat-button-toggle value=\"inherit\">{{ tx('editor.height.inherit', 'Default') }}</mat-button-toggle>\n <mat-button-toggle value=\"auto\">{{ tx('editor.height.auto', 'Automatic') }}</mat-button-toggle>\n <mat-button-toggle value=\"fixed\">{{ tx('editor.height.fixed', 'Adjustable') }}</mat-button-toggle>\n </mat-button-toggle-group>\n @if (limitsForm.controls.heightMode.value === 'fixed') {\n <div class=\"limits-fields\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('editor.height.value', 'Height (px)') }}</mat-label>\n <input matInput type=\"number\" min=\"160\" step=\"1\" [errorStateMatcher]=\"limitsErrorMatcher\" [formControl]=\"limitsForm.controls.heightPx\" data-testid=\"canvas-height-px\">\n <mat-error>{{ limitFieldError(limitsForm.controls.heightPx) }}</mat-error>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('editor.height.top', 'Top inset (px)') }}</mat-label>\n <input matInput type=\"number\" min=\"0\" step=\"1\" [errorStateMatcher]=\"limitsErrorMatcher\" [formControl]=\"limitsForm.controls.offsetTopPx\" data-testid=\"canvas-height-top\">\n <mat-error>{{ limitFieldError(limitsForm.controls.offsetTopPx) }}</mat-error>\n </mat-form-field>\n </div>\n }\n <div class=\"limits-fields\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('editor.height.min', 'Minimum height (px)') }}</mat-label>\n <input matInput type=\"number\" min=\"160\" step=\"1\" [errorStateMatcher]=\"limitsErrorMatcher\" [formControl]=\"limitsForm.controls.minHeightPx\" data-testid=\"canvas-limit-minHeightPx\">\n <mat-error>{{ limitFieldError(limitsForm.controls.minHeightPx) }}</mat-error>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('editor.height.max', 'Maximum height (px)') }}</mat-label>\n <input matInput type=\"number\" min=\"160\" step=\"1\" [errorStateMatcher]=\"limitsErrorMatcher\" [formControl]=\"limitsForm.controls.maxHeightPx\" data-testid=\"canvas-limit-maxHeightPx\">\n <mat-error>{{ limitFieldError(limitsForm.controls.maxHeightPx) }}</mat-error>\n </mat-form-field>\n </div>\n }\n }\n @if (limitsSummaryError) { <p class=\"limits-error\" role=\"alert\">{{ limitsSummaryError }}</p> }\n } @else {\n <p>{{ tx('editor.limits.empty', 'Add widgets to the canvas to configure their limits.') }}</p>\n }\n </section>\n </mat-tab>\n </mat-tab-group></div>\n </mat-tab>\n </mat-tab-group>\n <details class=\"summary-details\">\n <summary>{{ tx('editor.summaryAria', 'Configuration summary') }}</summary>\n <div class=\"summary\">\n @if (!isHostedInSettingsPanel) {\n <span>{{ validationStateLabel }}</span>\n <span>{{ dirtyStateLabel }}</span>\n }\n <span>{{ selectedLayoutPresetLabel }}</span>\n <span>{{ selectedThemePresetLabel }}</span>\n <span>{{ canvasSummaryLabel }}</span>\n </div>\n </details>\n </div>\n", styles: [".page-inset-field{grid-column:1/-1;min-width:0}:host{display:block;min-width:0;max-width:100%}.height-modes{width:fit-content;max-width:100%;flex-wrap:wrap;margin-bottom:16px}.editor,.panel{display:grid;gap:16px;min-width:0;max-width:100%;color:var(--md-sys-color-on-surface, currentColor)}mat-tab-group,mat-form-field{min-width:0;max-width:100%}.head,.summary{display:flex;gap:12px;align-items:center;flex-wrap:wrap}.summary{color:var(--md-sys-color-on-surface-variant, currentColor)}.selection-identity{display:flex;align-items:flex-start;gap:8px;padding-block:8px;background:var(--md-sys-color-surface-container-low, var(--md-sys-color-surface));overflow-wrap:anywhere}.selection-identity mat-icon{flex-shrink:0}:host ::ng-deep .editor-tabs>.mat-mdc-tab-header .mdc-tab{min-width:0;padding-inline:12px}:host ::ng-deep .editor-tabs>.mat-mdc-tab-header .mdc-tab__text-label{max-width:128px;white-space:normal;overflow-wrap:anywhere;text-align:center;letter-spacing:normal;line-height:1.25}.grid{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(min(220px,100%),1fr))}@container (max-width: 600px){.grid{grid-template-columns:minmax(0,1fr)}.head,.summary{align-items:stretch}.head>button{width:100%}}@media(max-width:600px){.grid{grid-template-columns:minmax(0,1fr)}.head,.summary{align-items:stretch}.head>button{width:100%}}.card{display:grid;gap:12px;padding:16px;border:1px solid var(--md-sys-color-outline-variant, color-mix(in srgb, currentColor 18%, transparent));border-radius:8px;background:var(--md-sys-color-surface-container-low, var(--md-sys-color-surface-container, transparent));color:var(--md-sys-color-on-surface, currentColor)}.limits-panel{display:grid;gap:8px;min-width:0}.limits-fields{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:4px 16px}.limits-fields mat-form-field{min-width:0}.limits-notice{display:flex;gap:8px;align-items:flex-start;color:var(--md-sys-color-on-surface-variant, currentColor)}.limits-notice mat-icon{flex-shrink:0}.limits-error{color:var(--md-sys-color-error, #b3261e)}.limits-blocking-banner{border-color:var(--md-sys-color-error, #b3261e);gap:8px}.blocked-widgets-list{margin:0;padding-inline-start:20px;display:grid;gap:4px}.previous-heights-summary{display:flex;flex-wrap:wrap;gap:8px;align-items:center;font:var(--mat-sys-body-small, inherit);color:var(--md-sys-color-on-surface-variant, currentColor)}.previous-height-chip{padding:2px 8px;border-radius:999px;background:var(--md-sys-color-surface-container-highest, rgba(128, 128, 128, .14))}.wide{grid-column:1/-1}.hint{color:var(--md-sys-color-on-surface-variant, currentColor);font-size:12px}.eyebrow{color:var(--md-sys-color-primary, currentColor);font-size:11px;font-weight:700;letter-spacing:0;text-transform:uppercase}:host ::ng-deep .mat-mdc-tab-body-content{padding-top:12px;box-sizing:border-box}:host{container-type:inline-size}.studio-panel{display:grid;gap:var(--p-space-4, 16px);padding-block:var(--p-space-5, 20px);min-width:0}.studio-heading h2{font-size:var(--p-font-size-lg, 20px);line-height:1.25;margin:var(--p-space-2, 8px) 0;font-weight:600}.studio-heading p{margin:0;color:var(--md-sys-color-on-surface-variant);line-height:1.5}.studio-panel h3{font-size:var(--p-font-size-md, 16px);margin:0}.studio-panel>button{justify-self:start}.studio-gallery{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--p-space-3, 12px)}.studio-choice{display:flex;flex-direction:column;gap:var(--p-space-2, 8px);min-width:0;padding:var(--p-space-3, 12px);text-align:start;border:1px solid var(--md-sys-color-outline-variant);border-radius:var(--p-radius-lg, 12px);background:var(--md-sys-color-surface);color:var(--md-sys-color-on-surface);font:inherit;cursor:pointer;transition:border-color var(--md-sys-motion-duration-short2, .1s),background-color var(--md-sys-motion-duration-short2, .1s)}.studio-choice:hover:not(:disabled){border-color:var(--md-sys-color-primary);background:var(--md-sys-color-surface-container-low)}.studio-choice.selected{border-color:var(--md-sys-color-primary);box-shadow:inset 0 0 0 1px var(--md-sys-color-primary);background:var(--md-sys-color-surface-container-low)}.studio-choice:focus-visible,.studio-panel summary:focus-visible{outline:3px solid var(--md-sys-color-primary);outline-offset:3px}.studio-choice:disabled{opacity:var(--p-disabled-opacity, .5);cursor:not-allowed}.choice-title{display:flex;align-items:center;justify-content:space-between;gap:var(--p-space-2, 8px);width:100%;font-weight:600;line-height:1.35}.choice-title mat-icon{color:var(--md-sys-color-primary);flex-shrink:0;font-size:20px;height:20px;width:20px}.choice-description{font-size:var(--p-font-size-sm, 13px);line-height:1.5;color:var(--md-sys-color-on-surface-variant)}.choice-meta{font-size:var(--p-font-size-xs, 12px);color:var(--md-sys-color-on-surface-variant);margin-top:auto;padding-top:var(--p-space-1, 4px)}.theme-sample{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));padding:var(--p-space-3, 12px);border-radius:var(--p-radius-md, 8px);min-height:128px;align-self:stretch;margin-bottom:var(--p-space-1, 4px)}.sample-metric,.sample-chart{background:var(--sample-surface);border-radius:var(--sample-radius);box-shadow:var(--sample-shadow);padding:var(--p-space-2, 8px)}.sample-metric b{display:block;font-size:var(--p-font-size-md, 16px)}.sample-metric i{display:block;height:3px;width:55%;margin-top:var(--p-space-1, 4px);background:var(--sample-accent)}.sample-chart{grid-column:1/-1;display:flex;height:40px;align-items:flex-end;gap:var(--p-space-2, 8px)}.sample-chart i{flex:1;background:var(--sample-accent);height:50%;border-radius:var(--p-radius-xs, 2px)}.sample-chart i:nth-child(2){height:85%}.sample-chart i:nth-child(3){height:65%}.sample-chart i:nth-child(4){height:100%}.sample-chart i:nth-child(5){height:75%}.shell-options,.arrangement-rules{border-top:1px solid var(--md-sys-color-outline-variant);padding-top:var(--p-space-3, 12px)}.studio-panel summary{cursor:pointer;font-weight:500;line-height:1.5;padding-block:var(--p-space-2, 8px)}.shell-gallery{margin-top:var(--p-space-4, 16px)}.shell-sample{display:block;align-self:stretch;border:1px solid;overflow:hidden;margin:var(--p-space-2, 8px)}.shell-sample>span{display:flex;gap:var(--p-space-2, 8px);align-items:center;padding:var(--p-space-2, 8px);border-bottom:1px solid transparent}.shell-sample i{height:4px;width:60%;background:var(--md-sys-color-on-surface-variant);opacity:.4}.shell-sample>span:last-child{min-height:30px;flex-direction:column;align-items:flex-start}.shell-sample>span:last-child i:last-child{width:40%}.selection-line{margin:0;font-size:var(--p-font-size-sm, 13px);color:var(--md-sys-color-on-surface-variant)}.studio-actions{display:flex;flex-wrap:wrap;align-items:center;gap:var(--p-space-2, 8px)}.distribution-sample{display:grid;grid-template-rows:repeat(2,1fr);gap:var(--p-space-1, 4px);align-self:stretch;height:76px;padding:var(--p-space-2, 8px);background:var(--md-sys-color-surface-container);border-radius:var(--p-radius-md, 8px)}.distribution-sample i{display:grid;place-items:center;background:var(--md-sys-color-secondary-container);color:var(--md-sys-color-on-secondary-container);border-radius:var(--p-radius-sm, 4px);font-size:var(--p-font-size-xs, 12px);font-style:normal}.distribution-sample i:nth-child(n+4){opacity:.65}.selected .distribution-sample i{background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container)}.arrangement-review{display:grid;gap:var(--p-space-3, 12px);padding-block:var(--p-space-4, 16px);border-top:2px solid var(--md-sys-color-primary)}.review-heading{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:var(--p-space-2, 8px)}.review-heading>span{font-size:var(--p-font-size-sm, 13px);color:var(--md-sys-color-primary)}.canvas-comparison{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--p-space-3, 12px)}.canvas-comparison figure{margin:0;min-width:0}.canvas-comparison figcaption{font-weight:500;margin-bottom:var(--p-space-2, 8px)}.canvas-map{display:grid;gap:2px;height:220px;padding:var(--p-space-2, 8px);border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-low);border-radius:var(--p-radius-md, 8px)}.canvas-map span{display:grid;place-items:center;min-height:0;min-width:0;overflow:hidden;font-size:var(--p-font-size-xs, 12px);color:var(--md-sys-color-on-secondary-container);background:var(--md-sys-color-secondary-container);border-radius:var(--p-radius-xs, 2px)}.canvas-map .changed{background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container)}.widget-changes{padding-inline-start:var(--p-space-5, 20px);margin:0}.widget-changes li{padding-block:var(--p-space-2, 8px);overflow-wrap:anywhere}.widget-changes span{display:block;font-size:var(--p-font-size-sm, 13px);color:var(--md-sys-color-on-surface-variant)}.studio-status{border-inline-start:3px solid var(--md-sys-color-primary);background:var(--md-sys-color-surface-container-low);padding:var(--p-space-3, 12px);margin:0;line-height:1.5}@container (max-width:420px){.studio-gallery{grid-template-columns:minmax(0,1fr)}.canvas-map{height:180px}}@media(prefers-reduced-motion:reduce){.studio-choice{transition:none}}.arrangement-feedback:empty{display:none}.arrangement-feedback:focus{outline:none}.arrangement-feedback:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:3px}@container (min-width:620px){.arrangement-gallery{grid-template-columns:repeat(3,minmax(0,1fr))}}:host ::ng-deep .studio-tabs>.mat-mdc-tab-header{position:sticky;top:0;z-index:var(--praxis-layer-sticky-local, 100);background:var(--md-sys-color-surface-container-low, var(--md-sys-color-surface))}:host ::ng-deep .studio-tabs>.mat-mdc-tab-header .mdc-tab{flex:1 1 0;padding-inline:var(--p-space-1, 4px)}.arrangement-feedback{scroll-margin-block-start:var(--p-space-16, 64px)}.filter-owner-banner{border-color:var(--md-sys-color-tertiary, var(--md-sys-color-primary))}.filter-owner-item{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:8px}.quick-geometry-rows{display:grid;gap:8px}.quick-geometry-row{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:8px;padding-block:4px;border-bottom:1px solid var(--md-sys-color-outline-variant, rgba(128, 128, 128, .2))}.quick-geometry-label{display:inline-flex;align-items:center;gap:6px;min-width:0}.quick-geometry-label mat-icon{font-size:18px;width:18px;height:18px;flex-shrink:0;color:var(--md-sys-color-primary, currentColor)}.binder-widget-trigger{display:inline-flex;align-items:center;gap:8px;max-width:100%;min-width:0}.binder-widget-trigger-icon{font-size:18px;width:18px;height:18px;flex-shrink:0;color:var(--md-sys-color-primary, currentColor)}.binder-widget-trigger-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.binder-widget-trigger-text small{color:var(--md-sys-color-on-surface-variant, currentColor);font-weight:400}.binder-widget-option-row{position:relative;display:flex;align-items:flex-start;gap:10px;padding-block:4px;min-width:0}.binder-widget-option-icon{font-size:20px;width:20px;height:20px;margin-top:2px;flex-shrink:0;color:var(--md-sys-color-primary, currentColor)}.binder-widget-option-copy{display:flex;flex-direction:column;gap:2px;min-width:0;line-height:1.3}.binder-widget-option-title{font-weight:600;color:var(--md-sys-color-on-surface, currentColor);overflow-wrap:anywhere}.binder-widget-option-desc{font-size:12px;color:var(--md-sys-color-on-surface-variant, currentColor);overflow-wrap:anywhere}.binder-widget-option-sr-key{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;opacity:0;pointer-events:none}.binder-input-suggestions{display:flex;flex-wrap:wrap;align-items:center;gap:6px}.binder-input-suggestion-chip{display:inline-flex;align-items:center;padding:2px 8px;border-radius:999px;border:1px solid var(--md-sys-color-outline-variant, rgba(128, 128, 128, .3));background:var(--md-sys-color-surface, transparent);color:var(--md-sys-color-on-surface, currentColor);font-size:12px;cursor:pointer;transition:border-color .1s,background-color .1s}.binder-input-suggestion-chip:hover{border-color:var(--md-sys-color-primary, currentColor);background:var(--md-sys-color-surface-container-low, rgba(128, 128, 128, .08))}.binder-input-suggestion-chip.active{border-color:var(--md-sys-color-primary, currentColor);background:var(--md-sys-color-primary-container, rgba(128, 128, 128, .16));color:var(--md-sys-color-on-primary-container, currentColor)}.binder-active-list{list-style:none;padding-inline-start:0;gap:6px}.binder-active-item{display:flex;align-items:center;gap:8px;padding:4px 8px;border-radius:6px;background:var(--md-sys-color-surface, rgba(255, 255, 255, .03))}.binder-active-icon{font-size:18px;width:18px;height:18px;flex-shrink:0;color:var(--md-sys-color-primary, currentColor)}.binder-active-copy{flex:1 1 auto;min-width:0;overflow-wrap:anywhere}.binder-active-select-btn{appearance:none;border:none;background:transparent;color:inherit;font:inherit;text-align:start;padding:2px 4px;border-radius:4px;cursor:pointer}.binder-active-select-btn:hover{background:var(--md-sys-color-surface-container-low, rgba(128, 128, 128, .08))}.binder-active-select-btn:focus-visible{outline:2px solid var(--md-sys-color-primary, currentColor);outline-offset:2px}.binder-preview-badge{display:inline-flex;align-items:center;margin-inline-start:6px;padding:1px 6px;border-radius:999px;font-size:11px;font-weight:600;color:var(--md-sys-color-on-secondary-container, #1d192b);background:var(--md-sys-color-secondary-container, rgba(103, 80, 164, .14))}.binder-unbound-warning{display:flex;align-items:flex-start;gap:8px;padding:8px 10px;border-radius:6px;font-size:12px;border:1px solid var(--md-sys-color-tertiary, rgba(180, 110, 0, .35));background:color-mix(in srgb,var(--md-sys-color-tertiary-container, #ffdea6) 45%,transparent);color:var(--md-sys-color-on-surface, inherit)}.binder-unbound-warning__body{display:grid;gap:6px}.binder-unbound-warning__chips{display:flex;flex-wrap:wrap;gap:6px}.binder-unbound-chip{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border-radius:999px;border:1px solid var(--md-sys-color-outline-variant, rgba(128, 128, 128, .35));background:var(--md-sys-color-surface, rgba(255, 255, 255, .65));color:inherit;font:inherit;font-size:11px;cursor:pointer}.binder-unbound-chip:hover{border-color:var(--md-sys-color-primary, currentColor)}.binder-unbound-chip:focus-visible{outline:2px solid var(--md-sys-color-primary, currentColor);outline-offset:2px}.guided-presets-bar{display:grid;gap:6px}.datasource-preview-box{display:grid;gap:8px;padding:12px;border-radius:6px;background:var(--md-sys-color-surface-container, rgba(128, 128, 128, .08))}.datasource-preview-json{margin:0;padding:8px;border-radius:4px;font-size:12px;overflow-x:auto;background:var(--md-sys-color-surface, rgba(0, 0, 0, .04))}.composer-rich-field-option{display:flex;flex-direction:column;gap:2px;padding:2px 0;line-height:1.3;min-width:0}.composer-rich-field-option__header{display:flex;align-items:center;flex-wrap:wrap;gap:6px;min-width:0}.composer-rich-field-option__label{font-weight:500;color:var(--md-sys-color-on-surface, currentColor);overflow:hidden;text-overflow:ellipsis}.composer-rich-field-option__code{font-family:var(--mat-sys-mono-font, monospace);font-size:11px;padding:1px 6px;border-radius:999px;background:var(--md-sys-color-surface-container-highest, rgba(128, 128, 128, .14));color:var(--md-sys-color-on-surface-variant, currentColor)}.composer-rich-field-option__badge{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.03em;padding:1px 6px;border-radius:999px;background:color-mix(in srgb,var(--md-sys-color-primary, #4f46e5) 14%,transparent);color:var(--md-sys-color-primary, currentColor)}.composer-rich-field-option__desc{font-size:11px;color:var(--md-sys-color-on-surface-variant, currentColor);white-space:normal;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}\n"], dependencies: [{ kind: "component", type: PagePresetComparisonComponent, selector: "praxis-page-preset-comparison", inputs: ["expanded", "layoutId", "themeId", "layoutPresetOptions", "canvas"], outputs: ["choose"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.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.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { 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: MatAutocompleteModule }, { kind: "component", type: i2$3.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: i3$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "directive", type: i2$3.MatAutocompleteTrigger, selector: "input[matAutocomplete], textarea[matAutocomplete]", inputs: ["matAutocomplete", "matAutocompletePosition", "matAutocompleteConnectedTo", "autocomplete", "matAutocompleteDisabled"], exportAs: ["matAutocompleteTrigger"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.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: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatButtonToggleModule }, { kind: "directive", type: i5.MatButtonToggleGroup, selector: "mat-button-toggle-group", inputs: ["appearance", "name", "vertical", "value", "multiple", "disabled", "disabledInteractive", "hideSingleSelectionIndicator", "hideMultipleSelectionIndicator"], outputs: ["valueChange", "change"], exportAs: ["matButtonToggleGroup"] }, { kind: "component", type: i5.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: MatFormFieldModule }, { kind: "component", type: i6.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i6.MatLabel, selector: "mat-label" }, { kind: "directive", type: i6.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i6.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i6.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i8.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: i3$1.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: i3$1.MatSelectTrigger, selector: "mat-select-trigger" }, { kind: "ngmodule", type: MatTabsModule }, { kind: "directive", type: i9.MatTabContent, selector: "[matTabContent]" }, { kind: "component", type: i9.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i9.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: MatTooltipModule }, { kind: "directive", type: i3.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: PraxisIconButtonComponent, selector: "button[praxisIconButton]", inputs: ["praxisIconButton", "size", "appearance", "presentation", "pressed", "busy"] }, { kind: "component", type: ResourceQuickConnectComponent, selector: "praxis-resource-quick-connect", inputs: ["resourcePath", "endpointKey", "apiUrlEntry", "intent", "resources", "label", "placeholder", "hint", "disabled"], outputs: ["resourcePathChange", "resourceSelected"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
18463
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: DynamicPageConfigEditorComponent, isStandalone: true, selector: "praxis-dynamic-page-config-editor", inputs: { page: "page", layout: "layout", presets: "presets", pagePreset: "pagePreset" }, outputs: { pageChange: "pageChange" }, host: { listeners: { "window:resize": "refreshRenderedValidation()" } }, providers: [providePraxisPageBuilderI18n()], viewQueries: [{ propertyName: "arrangementFeedback", first: true, predicate: ["arrangementFeedback"], descendants: true }], ngImport: i0, template: "<div class=\"editor\">\n @if (!isHostedInSettingsPanel) { <div class=\"head\">\n <div>{{ tx('editor.title', 'Page settings') }}</div>\n <button mat-stroked-button type=\"button\" (click)=\"reset()\">{{ tx('editor.reload', 'Reload') }}</button>\n </div> }\n\n @if (nonLimitError) { <p class=\"limits-error\" role=\"alert\">{{ nonLimitError }}</p> }\n @if (limitsError() || previousAdjustableHeights().length) {\n <section class=\"card limits-blocking-banner\" role=\"region\" [attr.aria-label]=\"tx('editor.limits.blockedBannerTitle', 'Height geometry requires review in Widget limits')\" data-testid=\"preset-geometry-blocking-banner\">\n @if (limitsError()) {\n <div class=\"head\">\n <strong>{{ tx('editor.limits.blockedBannerTitle', 'Height geometry requires review in Widget limits') }}</strong>\n </div>\n <p class=\"hint\">{{ tx('editor.limits.blockedBannerHelp', 'One or more widgets use an adjustable pixel height that exceeds the current usable area or requires review before applying the page or template:') }}</p>\n <ul class=\"blocked-widgets-list\">\n @for (item of blockedGeometryWidgets; track item.scope + ':' + item.key) {\n <li>\n <strong>{{ item.title }}</strong>\n @if (item.title !== item.key && !item.title.endsWith('(' + item.key + ')')) { <code>({{ item.key }})</code> }\n <span> \u00B7 {{ item.scopeLabel }}</span>\n @if (item.heightPx !== null) {\n <span> \u00B7 {{ tx('editor.height.fixed', 'Adjustable') }}: <strong>{{ item.heightPx }}px</strong></span>\n }\n </li>\n }\n </ul>\n <p class=\"limits-error\" role=\"alert\">{{ limitsError() }}</p>\n <div class=\"head\">\n <button mat-stroked-button type=\"button\" data-testid=\"goto-blocked-widget-limits\" (click)=\"navigateToBlockedLimit()\">\n <mat-icon aria-hidden=\"true\">tune</mat-icon>{{ tx('editor.limits.gotoLimitsAction', 'Go to Adjustments \u2192 Widget limits') }}\n </button>\n <button mat-flat-button type=\"button\" data-testid=\"switch-blocked-widgets-auto-height\" (click)=\"switchBlockedWidgetsToAutoHeight()\">\n <mat-icon aria-hidden=\"true\">height</mat-icon>{{ tx('editor.limits.switchToAutoHeightAction', 'Switch affected widgets to Automatic height') }}\n </button>\n </div>\n }\n @if (previousAdjustableHeights().length) {\n <div class=\"previous-heights-summary\" data-testid=\"previous-adjustable-heights-summary\">\n <span>{{ tx('editor.limits.previousHeightsSummary', 'Previous adjustable heights preserved for reference:') }}</span>\n @for (prev of previousAdjustableHeights(); track prev.scope + ':' + prev.key) {\n <span class=\"previous-height-chip\">{{ prev.title }} ({{ prev.scopeLabel }}): <strong>{{ prev.heightPx }}px</strong></span>\n }\n </div>\n }\n </section>\n }\n <mat-tab-group class=\"editor-tabs studio-tabs\" [mat-stretch-tabs]=\"false\" [selectedIndex]=\"workspaceTab()\" (selectedIndexChange)=\"workspaceTab.set($event)\">\n <mat-tab [label]=\"tx('studio.visualTab', 'Visual templates')\"><ng-template matTabContent>\n <section class=\"studio-panel\" data-testid=\"studio-visual\">\n <header class=\"studio-heading\"><span class=\"eyebrow\">{{ tx('studio.visualEyebrow', 'Appearance') }}</span><h2>{{ tx('studio.visualTitle', 'Give the page a visual language') }}</h2><p>{{ tx('studio.visualHelp', 'Choose surfaces, density and chart styling together. Individual widget overrides still take precedence.') }}</p></header>\n <button mat-stroked-button type=\"button\" [attr.aria-pressed]=\"!themePresetControl.value\" (click)=\"themePresetControl.setValue('')\">{{ tx('editor.layout.layoutDefault', 'Layout default') }}</button>\n <p class=\"selection-line\">{{ tx('presets.effectiveTheme', 'Effective page theme') }}: <strong>{{ selectedThemePresetEntry?.label || themePresetControl.value || recommendedThemePresetEntry?.label || tx('presets.hostTheme', 'Host theme') }}</strong></p>\n <div class=\"studio-gallery\">\n @for (theme of themePresetEntries; track theme.id) {\n <button type=\"button\" class=\"studio-choice\" [class.selected]=\"themePresetControl.value === theme.id\" [attr.aria-pressed]=\"themePresetControl.value === theme.id\" [attr.data-theme-preset]=\"theme.id\" (click)=\"themePresetControl.setValue(theme.id)\">\n <span class=\"theme-sample\" aria-hidden=\"true\" [style.background]=\"themeTokens(theme.id)['--pdx-page-bg']\" [style.--sample-accent]=\"themeTokens(theme.id)['--pdx-page-accent']\" [style.--sample-surface]=\"themeTokens(theme.id)['--pdx-page-surface']\" [style.--sample-radius]=\"themeTokens(theme.id)['--pdx-page-card-radius']\" [style.--sample-shadow]=\"themeTokens(theme.id)['--pdx-page-card-shadow']\" [style.gap]=\"themeTokens(theme.id)['--pdx-page-gap']\">\n <span class=\"sample-metric\"><b>128</b><i></i></span><span class=\"sample-metric\"><b>86%</b><i></i></span><span class=\"sample-chart\"><i></i><i></i><i></i><i></i><i></i></span>\n </span>\n <span class=\"choice-title\">{{ theme.label }}@if (themePresetControl.value === theme.id) {<mat-icon aria-hidden=\"true\">check_circle</mat-icon>}</span>\n <span class=\"choice-description\">{{ tx('studio.theme.' + theme.id, theme.label) }}</span>\n <span class=\"choice-meta\">{{ tx('studio.density.' + theme.density, theme.density) }} \u00B7 {{ shellName(theme.shellPreset) }}</span>\n </button>\n }\n </div>\n <p class=\"hint\">{{ tx('studio.samplesHelp', 'Illustrative samples using the preset tokens. Apply to see the result with your content.') }}</p>\n <details class=\"shell-options\"><summary>{{ tx('studio.shellTitle', 'Customize widget surfaces') }} \u00B7 {{ shellPresetEntries.length }}</summary>\n <p>{{ tx('studio.shellHelp', 'Override the page theme\u2019s default widget surface. Per-widget appearance settings keep their priority.') }}</p>\n <button mat-stroked-button type=\"button\" [attr.aria-pressed]=\"!shellPresetControl.value\" (click)=\"shellPresetControl.setValue('')\">{{ tx('editor.layout.inheritShell', 'Inherit appearance') }}</button>\n <div class=\"studio-gallery shell-gallery\">@for (shell of shellPresetEntries; track shell.id) {\n <button type=\"button\" class=\"studio-choice\" [class.selected]=\"shellPresetControl.value === shell.id\" [attr.aria-pressed]=\"shellPresetControl.value === shell.id\" (click)=\"shellPresetControl.setValue(shell.id)\">\n <span class=\"shell-sample\" aria-hidden=\"true\" [style.background]=\"shell.appearance.card?.background\" [style.border-color]=\"shell.appearance.card?.borderColor\" [style.border-radius]=\"shell.appearance.card?.borderRadius\" [style.box-shadow]=\"shell.appearance.card?.shadow\"><span [style.background]=\"shell.appearance.header?.background\" [style.border-color]=\"shell.appearance.header?.borderColor\"><mat-icon>analytics</mat-icon><i></i></span><span [style.background]=\"shell.appearance.body?.background\"><i></i><i></i></span></span>\n <span class=\"choice-title\">{{ shellPresetLabel(shell) }}@if (shellPresetControl.value === shell.id) {<mat-icon aria-hidden=\"true\">check_circle</mat-icon>}</span><span class=\"choice-description\">{{ shellDescription(shell) }}</span>\n </button>\n }</div>\n </details>\n </section>\n </ng-template></mat-tab>\n <mat-tab [label]=\"tx('studio.layoutTab', 'Layout templates')\"><ng-template matTabContent>\n <section class=\"studio-panel\" data-testid=\"studio-layout\">\n <header class=\"studio-heading\"><span class=\"eyebrow\">{{ tx('studio.layoutEyebrow', 'Composition model') }}</span><h2>{{ tx('studio.layoutTitle', 'Choose the structure for your use case') }}</h2><p>{{ tx('studio.layoutHelp', 'Compare the roles and default theme of each model. To redistribute the widgets already on this canvas, open Arrange.') }}</p></header>\n @if (duplicateFilterOwners().length) {\n <section class=\"card filter-owner-banner\" role=\"region\" [attr.aria-label]=\"tx('studio.filterOwnerBannerTitle', 'Duplicate filter region detected')\" data-testid=\"duplicate-filter-owner-banner\">\n <div class=\"head\">\n <strong>{{ tx('studio.filterOwnerBannerTitle', 'Single filter region recommendation') }}</strong>\n </div>\n <p class=\"hint\">{{ tx('studio.filterOwnerBannerHelp', 'A table has both its integrated filter bar and an external filter link active. Choose a single query owner to avoid competing filter regions:') }}</p>\n @for (item of duplicateFilterOwners(); track item.tableKey) {\n <div class=\"filter-owner-item\" [attr.data-testid]=\"'duplicate-filter-owner-' + item.tableKey\">\n <span><strong>{{ item.tableTitle }}</strong> <code>({{ item.tableKey }})</code> \u2190 {{ item.externalSourceLabels.join(', ') }}</span>\n <div class=\"studio-actions\">\n <button mat-stroked-button type=\"button\" [attr.data-testid]=\"'consolidate-filter-table-toolbar-' + item.tableKey\" (click)=\"consolidateFilterOwnership(item.tableKey, 'keep-table-toolbar')\">\n {{ tx('studio.keepTableToolbarFilter', 'Use integrated table filter') }}\n </button>\n <button mat-stroked-button type=\"button\" [attr.data-testid]=\"'consolidate-filter-external-link-' + item.tableKey\" (click)=\"consolidateFilterOwnership(item.tableKey, 'keep-external-link')\">\n {{ tx('studio.keepExternalFilterLink', 'Use external filter bar') }}\n </button>\n </div>\n </div>\n }\n </section>\n }\n <details class=\"card composer-card\" data-testid=\"collection-screen-composer\">\n <summary>{{ tx('studio.composer.title', 'Guided collection screen composer (5-minute flow)') }}</summary>\n <p class=\"hint\">{{ tx('studio.composer.help', 'Assemble a complete collection screen with header, KPI indicators, single filter region, primary collection, and optional detail surface in the draft.') }}</p>\n <div class=\"grid\">\n <praxis-resource-quick-connect\n [formControl]=\"composerResourcePathControl\"\n intent=\"collection\"\n [label]=\"tx('studio.composer.resourcePath', 'Resource path')\"\n (resourceSelected)=\"onComposerResourceSelected($event)\"\n data-testid=\"composer-resource-path\"\n />\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('studio.composer.screenTitle', 'Screen title') }}</mat-label>\n <input matInput [formControl]=\"composerTitleControl\" data-testid=\"composer-title\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('studio.composer.groupField', 'KPI group-by field') }}</mat-label>\n <input\n matInput\n [formControl]=\"composerGroupFieldControl\"\n [matAutocomplete]=\"composerGroupFieldPicker\"\n data-testid=\"composer-group-field\"\n />\n <mat-autocomplete\n #composerGroupFieldPicker=\"matAutocomplete\"\n (optionSelected)=\"composerGroupFieldControl.markAsPristine()\"\n >\n @for (field of filteredComposerGroupFieldOptions(); track field.name) {\n <mat-option [value]=\"field.name\" [attr.data-testid]=\"'composer-group-field-option-' + field.name\">\n <div class=\"composer-rich-field-option\">\n <div class=\"composer-rich-field-option__header\">\n <span class=\"composer-rich-field-option__label\">{{ field.label || field.name }}</span>\n @if (field.label && field.label !== field.name) {\n <code class=\"composer-rich-field-option__code\">{{ field.name }}</code>\n }\n @if (getComposerFieldTypeBadge(field); as badge) {\n <span class=\"composer-rich-field-option__badge\">{{ badge }}</span>\n }\n </div>\n @if (getComposerFieldDescription(field); as desc) {\n <span class=\"composer-rich-field-option__desc\">{{ desc }}</span>\n }\n </div>\n </mat-option>\n }\n </mat-autocomplete>\n @if (composerSelectedResource()?.capabilities?.stats === false) {\n <mat-hint data-testid=\"composer-stats-unsupported-hint\">{{ tx('studio.composer.statsUnsupportedHint', 'This resource does not publish stats/group-by aggregation; summary indicators were disabled automatically.') }}</mat-hint>\n } @else {\n <mat-hint>{{ tx('studio.composer.groupFieldHint', 'Choose the boolean or categorical field used in summary indicators.') }}</mat-hint>\n }\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('studio.composer.filterMode', 'Query filter owner') }}</mat-label>\n <mat-select [formControl]=\"composerFilterModeControl\" data-testid=\"composer-filter-mode\">\n <mat-option value=\"table-integrated\">{{ tx('studio.composer.filterIntegrated', 'Integrated table toolbar') }}</mat-option>\n <mat-option value=\"external-filter-bar\">{{ tx('studio.composer.filterExternal', 'Dedicated filter bar widget') }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('studio.composer.collectionKind', 'Primary collection') }}</mat-label>\n <mat-select [formControl]=\"composerCollectionKindControl\" data-testid=\"composer-collection-kind\">\n <mat-option value=\"table\">praxis-Table</mat-option>\n <mat-option value=\"list\">praxis-List</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('studio.composer.detailSurface', 'Secondary detail surface') }}</mat-label>\n <mat-select [formControl]=\"composerDetailSurfaceControl\" data-testid=\"composer-detail-surface\">\n <mat-option value=\"drawer\">Drawer</mat-option>\n <mat-option value=\"modal\">Modal</mat-option>\n <mat-option value=\"none\">{{ tx('studio.composer.noSurface', 'None') }}</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n <div class=\"studio-actions\">\n <button mat-flat-button type=\"button\" data-testid=\"composer-generate-btn\" (click)=\"composeCollectionScreen()\">\n <mat-icon aria-hidden=\"true\">auto_awesome</mat-icon>{{ tx('studio.composer.generate', 'Generate collection screen in draft') }}\n </button>\n </div>\n @if (composerMessage()) {\n <p class=\"studio-status\" role=\"status\" data-testid=\"composer-status-message\">{{ composerMessage() }}</p>\n }\n </details>\n <div class=\"studio-actions\"><button mat-stroked-button type=\"button\" (click)=\"layoutPresetControl.setValue('')\">{{ tx('studio.inheritLayout', 'Use inherited model') }}</button><button mat-button type=\"button\" (click)=\"workspaceTab.set(2)\">{{ tx('studio.goArrange', 'Arrange existing widgets') }}<mat-icon iconPositionEnd>arrow_forward</mat-icon></button></div>\n <praxis-page-preset-comparison [expanded]=\"true\" [layoutId]=\"layoutPresetControl.value\" [layoutPresetOptions]=\"layoutPresetOptions\" [themeId]=\"themePresetControl.value\" [canvas]=\"true\" (choose)=\"layoutPresetControl.setValue($event)\" />\n </section>\n </ng-template></mat-tab>\n <mat-tab [label]=\"tx('studio.arrangeTab', 'Arrange')\"><ng-template matTabContent>\n <section class=\"studio-panel\" data-testid=\"canvas-arrangement\">\n <header class=\"studio-heading\"><span class=\"eyebrow\">{{ tx('studio.arrangeEyebrow', 'Existing widgets') }} \u00B7 {{ canvasColumnsControl.value }} {{ tx('editor.layout.cols', 'Columns') }}</span><h2>{{ tx('studio.arrangeTitle', 'Find the right balance of space') }}</h2><p>{{ tx('studio.arrangeHelp', 'Choose a distribution, compare the real positions, then use it in the draft. Widgets keep their reading order and content.') }}</p></header>\n <div #arrangementFeedback tabindex=\"-1\" class=\"arrangement-feedback\">\n @if (arrangementPreview(); as preview) {\n <section class=\"arrangement-review\" aria-live=\"polite\" data-testid=\"arrangement-preview\">\n <div class=\"review-heading\"><h3>{{ tx('studio.reviewTitle', 'Compare before using') }}</h3><span>{{ preview.keys.length }} {{ tx('studio.changedWidgets', 'widgets change') }}</span></div>\n <div class=\"canvas-comparison\">\n @for (side of ['before', 'after']; track side) {\n <figure><figcaption>{{ side === 'before' ? tx('studio.before', 'Before') : tx('studio.after', 'After') }}</figcaption>\n <div class=\"canvas-map\" [style.grid-template-columns]=\"'repeat(' + preview.canvas.columns + ', minmax(0, 1fr))'\" [style.grid-template-rows]=\"'repeat(' + previewRowCount(preview.before, preview.canvas) + ', minmax(0, 1fr))'\" role=\"img\" [attr.aria-label]=\"side === 'before' ? tx('studio.before', 'Before') : tx('studio.after', 'After')\">\n @for (item of previewItems(side === 'before' ? preview.before : preview.canvas); track item.key) {\n <span [style.grid-column]=\"item.col + ' / span ' + item.colSpan\" [style.grid-row]=\"item.row + ' / span ' + item.rowSpan\" [class.changed]=\"preview.keys.includes(item.key)\" [matTooltip]=\"item.label\">{{ item.number }}</span>\n }\n </div>\n </figure>\n }\n </div>\n <p class=\"hint\">{{ tx('studio.mapHelp', 'Numbered map of your widgets. Positions and spans are to scale; content height is evaluated on the page.') }}</p>\n <details><summary>{{ tx('studio.widgetDetails', 'Widget positions and sizes') }}</summary><ol class=\"widget-changes\">@for (item of previewItems(preview.canvas); track item.key) {<li><strong>{{ item.label }}</strong><span>{{ tx('studio.before', 'Before') }}: {{ tx('editor.arrange.position', 'Column / row') }} {{ preview.before.items[item.key].col }} / {{ preview.before.items[item.key].row }} \u00B7 {{ tx('studio.gridSize', 'Size in columns \u00D7 rows') }} {{ preview.before.items[item.key].colSpan }} \u00D7 {{ preview.before.items[item.key].rowSpan }}</span><span>{{ tx('studio.after', 'After') }}: {{ tx('editor.arrange.position', 'Column / row') }} {{ item.col }} / {{ item.row }} \u00B7 {{ tx('studio.gridSize', 'Size in columns \u00D7 rows') }} {{ item.colSpan }} \u00D7 {{ item.rowSpan }}</span></li>}</ol></details>\n <button mat-flat-button type=\"button\" data-testid=\"arrangement-use\" (click)=\"useArrangement()\">{{ tx('editor.arrange.use', 'Use arrangement in draft') }}<mat-icon iconPositionEnd>arrow_forward</mat-icon></button>\n </section>\n }\n @if (arrangementMessage()) {<p class=\"studio-status\" role=\"status\">{{ arrangementMessage() }}</p>}\n @if (arrangementUndoAvailable()) {<button mat-stroked-button type=\"button\" data-testid=\"arrangement-undo\" (click)=\"undoArrangement()\"><mat-icon>undo</mat-icon>{{ tx('editor.arrange.undo', 'Undo arrangement') }}</button>}\n </div>\n <div class=\"studio-gallery arrangement-gallery\">\n @for (choice of arrangementChoices; track choice.id) {\n <button type=\"button\" class=\"studio-choice\" [class.selected]=\"arrangementSelection() === choice.id\" [attr.aria-pressed]=\"arrangementSelection() === choice.id\" [disabled]=\"!hasCanvasWidgets || !arrangementFits(choice.id)\" [attr.data-testid]=\"'arrange-' + choice.id\" (click)=\"prepareArrangement(choice.id)\">\n <span class=\"distribution-sample\" aria-hidden=\"true\" [style.grid-template-columns]=\"(arrangementFits(choice.id) ? arrangementWidths(choice.id) : choice.weights).join('fr ') + 'fr'\">@for (weight of choice.weights; track $index) {<i>{{ arrangementWidths(choice.id)[$index] }}</i>}@for (weight of choice.weights; track $index) {<i></i>}</span>\n <span class=\"choice-title\">{{ tx('editor.arrange.' + choice.id, choice.id) }}@if (arrangementSelection() === choice.id) {<mat-icon aria-hidden=\"true\">check_circle</mat-icon>}</span>\n <span class=\"choice-description\">{{ tx('studio.arrange.' + choice.id, choice.id) }}</span>\n <span class=\"choice-meta\">{{ arrangementFits(choice.id) ? arrangementWidths(choice.id).join(' + ') : tx('studio.moreColumns', 'Needs more grid columns') }}</span>\n </button>\n }\n </div>\n @if (hasCanvasWidgets) {\n <details class=\"card quick-geometry-card\" data-testid=\"quick-widget-geometry-list\">\n <summary>{{ tx('studio.quickGeometryTitle', 'Quick widget spans (columns \u00D7 rows)') }}</summary>\n <div class=\"quick-geometry-rows\">\n @for (w of quickCanvasWidgets(); track w.key) {\n <div class=\"quick-geometry-row\" [attr.data-testid]=\"'quick-geometry-row-' + w.key\">\n <span class=\"quick-geometry-label\"><mat-icon aria-hidden=\"true\">{{ w.icon }}</mat-icon><strong>{{ w.label }}</strong> <small>({{ w.colSpan }}\u00D7{{ w.rowSpan }})</small></span>\n <div class=\"studio-actions\">\n @for (span of [3, 4, 6, 12]; track span) {\n <button mat-stroked-button type=\"button\" [attr.data-testid]=\"'quick-colspan-' + w.key + '-' + span\" (click)=\"setWidgetCanvasSpan(w.key, span)\">{{ span }}c</button>\n }\n @for (rSpan of [1, 2, 4]; track rSpan) {\n <button mat-button type=\"button\" [attr.data-testid]=\"'quick-rowspan-' + w.key + '-' + rSpan\" (click)=\"setWidgetCanvasSpan(w.key, w.colSpan, rSpan)\">{{ rSpan }}r</button>\n }\n </div>\n </div>\n }\n </div>\n </details>\n }\n @if (!hasCanvasWidgets) {<p role=\"status\">{{ tx('editor.limits.empty', 'Add widgets to the canvas to configure their limits.') }}</p>}\n <details class=\"arrangement-rules\"><summary>{{ tx('studio.rules', 'Scope, devices and locked widgets') }}</summary><p>{{ tx('editor.arrange.help', 'Base canvas, in current reading order. Inheriting devices follow the base; explicit device positions are preserved. Locked positions stay in place.') }}</p><button mat-button type=\"button\" (click)=\"onSelectedTabIndexChange(5)\">{{ tx('studio.goLimits', 'Review widget limits') }}</button></details>\n </section>\n </ng-template></mat-tab>\n <mat-tab [label]=\"tx('studio.settingsTab', 'Adjustments')\">\n <div class=\"studio-panel\"><mat-tab-group class=\"editor-tabs\" [mat-stretch-tabs]=\"false\" [selectedIndex]=\"selectedTabIndex\" (selectedIndexChange)=\"selectDetailTab($event)\">\n <mat-tab [label]=\"tx('editor.tabs.layout', 'Layout')\"><div class=\"grid\"><mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.layout.cols', 'Columns') }}</mat-label><input matInput type=\"number\" min=\"1\" [formControl]=\"canvasColumnsControl\" /></mat-form-field><mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.layout.rowUnit', 'Row unit') }}</mat-label><input matInput [formControl]=\"canvasRowUnitControl\" /></mat-form-field><mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.layout.gap', 'Gap') }}</mat-label><input matInput [formControl]=\"canvasGapControl\" /></mat-form-field><mat-form-field class=\"page-inset-field\" appearance=\"outline\"><mat-label>{{ tx('editor.layout.paddingInline', 'Page side inset') }}</mat-label><input matInput [formControl]=\"paddingInlineControl\" placeholder=\"24px\" /><button praxisIconButton=\"help_outline\" size=\"dense\" matSuffix type=\"button\" [matTooltip]=\"tx('editor.hints.paddingInline', 'Shared space beside all page widgets. Use a CSS length or theme token; 0px removes it. Leave empty to inherit the host theme.')\" [attr.aria-label]=\"tx('editor.layout.paddingInline', 'Page side inset')\"></button></mat-form-field><mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.layout.autoRows', 'Auto rows') }}</mat-label><mat-select [formControl]=\"canvasAutoRowsControl\"><mat-option value=\"fixed\">{{ tx('editor.layout.autoRowsFixed', 'Fixed height') }}</mat-option><mat-option value=\"content\">{{ tx('editor.layout.autoRowsContent', 'Fit content') }}</mat-option></mat-select></mat-form-field></div></mat-tab>\n <mat-tab [label]=\"tx('editor.tabs.grouping', 'Grouping')\"><div class=\"panel\">\n <p class=\"hint\" data-testid=\"canvas-grouping-notice\">{{ tx('editor.grouping.canvasNotice', 'Canvas positions control this page. Groups below are preserved for flow layouts and do not arrange widgets in the canvas. Use the widget move and resize controls to place them side by side.') }}</p><div class=\"head\"><span>{{ tx('editor.grouping.title', 'Grouping') }}</span><button mat-stroked-button type=\"button\" (click)=\"addGroupingNode()\"><mat-icon>add</mat-icon>{{ tx('editor.grouping.add', 'Add group') }}</button></div>@for (group of groupingNodes.controls; track $index) {<div class=\"card\" [formGroup]=\"group\"><div class=\"head\"><span>{{ groupLabel($index) }}</span><button praxisIconButton=\"delete\" size=\"compact\" type=\"button\" [attr.aria-label]=\"tx('editor.grouping.removeAria', 'Remove group')\" (click)=\"removeGroupingNode($index)\"></button></div><div class=\"grid\"><mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.grouping.kind', 'Kind') }}</mat-label><mat-select formControlName=\"kind\"><mat-option value=\"section\">section</mat-option><mat-option value=\"tabs\">tabs</mat-option><mat-option value=\"hero\">hero</mat-option><mat-option value=\"rail\">rail</mat-option></mat-select></mat-form-field><mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.grouping.id', 'Id') }}</mat-label><input matInput formControlName=\"id\" /></mat-form-field><mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.grouping.label', 'Label') }}</mat-label><input matInput formControlName=\"label\" /></mat-form-field>@if (group.controls.kind.value === 'section') {<mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.grouping.layout', 'Layout') }}</mat-label><mat-select formControlName=\"layout\"><mat-option value=\"\">{{ tx('editor.grouping.layoutDefault', 'Default') }}</mat-option><mat-option value=\"stack\">stack</mat-option><mat-option value=\"grid\">grid</mat-option><mat-option value=\"row\">row</mat-option></mat-select></mat-form-field>}@if (group.controls.kind.value === 'hero') {<mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.grouping.emphasis', 'Emphasis') }}</mat-label><mat-select formControlName=\"emphasis\"><mat-option value=\"\">{{ tx('editor.grouping.layoutDefault', 'Default') }}</mat-option><mat-option value=\"high\">high</mat-option><mat-option value=\"medium\">medium</mat-option></mat-select></mat-form-field>}@if (group.controls.kind.value === 'rail') {<mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.grouping.side', 'Side') }}</mat-label><mat-select formControlName=\"side\"><mat-option value=\"left\">left</mat-option><mat-option value=\"right\">right</mat-option></mat-select></mat-form-field>}@if (group.controls.kind.value === 'tabs') {<mat-form-field appearance=\"outline\" class=\"wide\"><mat-label>{{ tx('editor.grouping.tabsJson', 'Tabs (JSON)') }}</mat-label><textarea matInput rows=\"4\" formControlName=\"tabsJson\"></textarea></mat-form-field>} @else {@if (bindablePageWidgets().length > 0) {<mat-form-field appearance=\"outline\" class=\"wide\"><mat-label>{{ tx('editor.grouping.widgets', 'Widgets in group') }}</mat-label><mat-select multiple [value]=\"groupingSelectedWidgetKeys($index)\" (selectionChange)=\"onGroupingWidgetSelectionChange($index, $event.value)\" [attr.data-testid]=\"'grouping-widget-select-' + $index\" panelClass=\"praxis-binder-widget-select-panel\"><mat-select-trigger><span class=\"binder-widget-trigger\"><mat-icon aria-hidden=\"true\" class=\"binder-widget-trigger-icon\">{{ groupingSelectedWidgetIcon($index) }}</mat-icon><span class=\"binder-widget-trigger-text\">{{ groupingSelectedWidgetsLabel($index) }}</span></span></mat-select-trigger>@for (w of bindablePageWidgets(); track w.key) {<mat-option [value]=\"w.key\" [attr.data-widget-key]=\"w.key\" class=\"binder-widget-option\"><span class=\"binder-widget-option-row\"><mat-icon class=\"binder-widget-option-icon\" aria-hidden=\"true\">{{ w.icon }}</mat-icon><span class=\"binder-widget-option-copy\"><strong class=\"binder-widget-option-title\">{{ w.title }}</strong>@if (w.description) {<small class=\"binder-widget-option-desc\">{{ w.description }}</small>}</span><span class=\"binder-widget-option-sr-key\" aria-hidden=\"true\">{{ w.key }}</span></span></mat-option>}</mat-select></mat-form-field>}<mat-form-field appearance=\"outline\" class=\"wide\"><mat-label>{{ tx('editor.grouping.widgetKeysCsv', 'Widget keys (comma-separated)') }}</mat-label><input matInput formControlName=\"widgetKeysCsv\" /></mat-form-field>}</div></div>}</div></mat-tab>\n <mat-tab [label]=\"tx('editor.tabs.devices', 'Device Layouts')\">\n <div class=\"panel\">\n <mat-button-toggle-group [value]=\"activeDevice()\" (change)=\"activeDevice.set($event.value)\" [attr.aria-label]=\"tx('editor.devices.target', 'Device to configure')\">\n @for (device of deviceKeys; track device) {\n <mat-button-toggle [value]=\"device\">{{ deviceLabels[device] }}</mat-button-toggle>\n }\n </mat-button-toggle-group>\n @for (device of deviceKeys; track device) {\n @if (activeDevice() === device) {\n <div class=\"card\">\n <div class=\"head\"><strong>{{ deviceLabels[device] }}</strong><button mat-stroked-button type=\"button\" [attr.data-testid]=\"'stack-device-' + device\" [disabled]=\"!hasCanvasWidgets\" (click)=\"stackDevice(device)\"><mat-icon aria-hidden=\"true\">view_agenda</mat-icon>{{ tx('editor.devices.stack', 'Stack widgets') }}</button></div>\n <p class=\"hint\">{{ tx('editor.devices.stackHelp', 'Use full width and natural height, in reading order. This replaces positions and width limits only for this device; other layouts stay unchanged.') }}</p>\n @if (stackedDevices.has(device)) { <p class=\"hint\" role=\"status\">{{ tx('editor.devices.stacked', 'Single-column layout prepared. Save to apply; Reset undoes the changes.') }}</p> }\n <div class=\"grid\">\n <mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.devices.columns', 'Columns') }}</mat-label><input matInput type=\"number\" min=\"1\" [formControl]=\"deviceForms[device].controls.columns\" /></mat-form-field>\n <mat-form-field class=\"page-inset-field\" appearance=\"outline\"><mat-label>{{ tx('editor.layout.paddingInline', 'Page side inset') }}</mat-label><input matInput [formControl]=\"deviceForms[device].controls.paddingInline\" placeholder=\"16px\" /></mat-form-field><mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.devices.gap', 'Gap') }}</mat-label><input matInput [formControl]=\"deviceForms[device].controls.gap\" /></mat-form-field>\n <mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.layout.autoRows', 'Auto rows') }}</mat-label><mat-select [formControl]=\"deviceForms[device].controls.autoRows\"><mat-option value=\"\">{{ tx('editor.devices.orientationDefault', 'Default') }}</mat-option><mat-option value=\"fixed\">{{ tx('editor.layout.autoRowsFixed', 'Fixed height') }}</mat-option><mat-option value=\"content\">{{ tx('editor.layout.autoRowsContent', 'Fit content') }}</mat-option></mat-select></mat-form-field>\n </div>\n <details><summary>{{ tx('editor.devices.advanced', 'Advanced overrides') }}</summary><div class=\"grid\">\n <mat-form-field appearance=\"outline\"><mat-label>{{ tx('editor.layout.rowUnit', 'Row unit') }}</mat-label><input matInput [formControl]=\"deviceForms[device].controls.rowUnit\" /></mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"wide\"><mat-label>{{ tx('editor.devices.groupingOverridesJson', 'Grouping overrides (JSON)') }}</mat-label><textarea matInput rows=\"4\" [formControl]=\"deviceForms[device].controls.groupingOverrides\"></textarea></mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"wide\"><mat-label>{{ tx('editor.devices.widgetOverridesJson', 'Widget overrides (JSON)') }}</mat-label><textarea matInput rows=\"4\" [formControl]=\"deviceForms[device].controls.widgetOverrides\"></textarea><mat-hint>{{ tx('editor.devices.widgetOverridesHelp', 'Supported per-widget properties on this device: className, span, order, and hidden. Does not override internal widget definition or inputs.') }}</mat-hint></mat-form-field>\n </div></details>\n @if (!stackedDevices.has(device)) { <div class=\"hint\">{{ tx('editor.preview.deviceCanvasHint', 'Existing canvas item overrides are preserved.') }}</div> }\n </div>\n }\n }\n </div>\n </mat-tab>\n <mat-tab [label]=\"tx('editor.tabs.context', 'Context')\"><mat-form-field appearance=\"outline\" class=\"wide\"><mat-label>{{ tx('editor.context.json', 'Context (JSON)') }}</mat-label><textarea matInput rows=\"12\" [formControl]=\"contextControl\"></textarea></mat-form-field></mat-tab>\n <mat-tab [label]=\"tx('editor.tabs.state', 'Page State')\">\n <div class=\"panel\">\n <mat-form-field appearance=\"outline\" class=\"wide\">\n <mat-label>{{ tx('editor.state.stateDefinitionJson', 'Page state (values, schema, derived \u2014 JSON)') }}</mat-label>\n <textarea matInput rows=\"10\" [formControl]=\"stateControl\" data-testid=\"page-state-json\"></textarea>\n </mat-form-field>\n\n <section class=\"panel\" data-testid=\"page-datasources-guided\">\n <div class=\"head\">\n <strong>{{ tx('editor.state.dataSourcesTitle', 'Data Sources') }}</strong>\n <div class=\"studio-actions\">\n @if (dataSourceNodes.controls.length) {\n <button\n mat-button\n type=\"button\"\n data-testid=\"page-state-sync-from-datasources\"\n (click)=\"syncStateValuesFromDataSources()\"\n >\n <mat-icon>sync</mat-icon>{{ tx('editor.state.syncFromDataSources', 'Sync state paths') }}\n </button>\n }\n <button mat-stroked-button type=\"button\" data-testid=\"page-datasources-add\" (click)=\"addDataSourceNode()\">\n <mat-icon>add</mat-icon>{{ tx('editor.state.addDataSource', 'Add data source') }}\n </button>\n </div>\n </div>\n <p class=\"hint\">{{ tx('editor.state.dataSourcesCopy', 'Bind HTTP or resource endpoints directly into page state paths with reactive parameters.') }}</p>\n @if (!dataSourceNodes.controls.length) {\n <p class=\"hint\" data-testid=\"page-datasources-empty\">{{ tx('editor.state.emptyDataSources', 'No declarative data sources configured.') }}</p>\n }\n @for (ds of dataSourceNodes.controls; track $index) {\n <div class=\"card\" [formGroup]=\"ds\" [attr.data-testid]=\"'page-datasource-card-' + $index\">\n <div class=\"head\">\n <span>{{ dataSourceLabel($index) }}</span>\n <button\n praxisIconButton=\"delete\"\n size=\"compact\"\n type=\"button\"\n [attr.data-testid]=\"'page-datasource-remove-' + $index\"\n [attr.aria-label]=\"tx('editor.state.removeDataSourceAria', 'Remove data source')\"\n (click)=\"removeDataSourceNode($index)\"\n ></button>\n </div>\n <div class=\"grid\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('editor.state.dataSourceId', 'ID') }}</mat-label>\n <input matInput formControlName=\"id\" [attr.data-testid]=\"'page-datasource-id-' + $index\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('editor.state.dataSourceKind', 'Kind') }}</mat-label>\n <mat-select formControlName=\"kind\" [attr.data-testid]=\"'page-datasource-kind-' + $index\">\n <mat-option value=\"\">{{ tx('editor.common.default', 'Default') }}</mat-option>\n <mat-option value=\"remote-resource\">remote-resource</mat-option>\n <mat-option value=\"stats-query\">stats-query</mat-option>\n <mat-option value=\"raw-http\">raw-http</mat-option>\n </mat-select>\n </mat-form-field>\n <praxis-resource-quick-connect\n formControlName=\"resourcePath\"\n [intent]=\"ds.controls.kind.value === 'stats-query' ? 'stats' : (ds.controls.kind.value === 'raw-http' ? 'any' : 'collection')\"\n [endpointKey]=\"resolveEndpointKey(ds.controls.endpointKey.value)\"\n [label]=\"tx('editor.state.resourcePath', 'Resource path')\"\n [attr.data-testid]=\"'page-datasource-resourcePath-' + $index\"\n />\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('editor.state.endpointKey', 'Endpoint key') }}</mat-label>\n <input matInput formControlName=\"endpointKey\" [attr.data-testid]=\"'page-datasource-endpointKey-' + $index\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('editor.state.method', 'HTTP method') }}</mat-label>\n <mat-select formControlName=\"method\" [attr.data-testid]=\"'page-datasource-method-' + $index\">\n <mat-option value=\"\">{{ tx('editor.common.auto', 'Auto') }}</mat-option>\n <mat-option value=\"GET\">GET</mat-option>\n <mat-option value=\"POST\">POST</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('editor.state.targetStatePath', 'Target state path') }}</mat-label>\n <input\n matInput\n formControlName=\"targetStatePath\"\n [attr.data-testid]=\"'page-datasource-targetStatePath-' + $index\"\n (blur)=\"syncStateValuesFromDataSources()\"\n />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('editor.state.errorPolicy', 'Error policy') }}</mat-label>\n <mat-select formControlName=\"errorPolicy\" [attr.data-testid]=\"'page-datasource-errorPolicy-' + $index\">\n <mat-option value=\"\">{{ tx('editor.common.default', 'Default') }}</mat-option>\n <mat-option value=\"propagate\">propagate</mat-option>\n <mat-option value=\"silent\">silent</mat-option>\n <mat-option value=\"fallback\">fallback</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('editor.state.refreshIntervalMs', 'Refresh interval (ms)') }}</mat-label>\n <input matInput type=\"number\" min=\"0\" formControlName=\"refreshIntervalMs\" [attr.data-testid]=\"'page-datasource-refreshIntervalMs-' + $index\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('editor.state.autoFetch', 'Auto fetch') }}</mat-label>\n <mat-select formControlName=\"autoFetch\" [attr.data-testid]=\"'page-datasource-autoFetch-' + $index\">\n <mat-option value=\"\">{{ tx('editor.common.default', 'Default') }}</mat-option>\n <mat-option value=\"true\">true</mat-option>\n <mat-option value=\"false\">false</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"wide\">\n <mat-label>{{ tx('editor.state.description', 'Description') }}</mat-label>\n <input matInput formControlName=\"description\" [attr.data-testid]=\"'page-datasource-description-' + $index\" />\n </mat-form-field>\n <div class=\"wide guided-presets-bar\" [attr.data-testid]=\"'page-datasource-guided-presets-' + $index\">\n <span class=\"hint\">{{ tx('editor.state.guidedPresets', 'Guided query & transform templates:') }}</span>\n <div class=\"studio-actions\">\n <button mat-stroked-button type=\"button\" [attr.data-testid]=\"'page-datasource-preset-groupby-' + $index\" (click)=\"applyGuidedDataSourceQuery($index, { mode: 'group-by', groupByField: 'ativo', metricField: 'id', metricOp: 'count' }); applyGuidedDataSourceTransform($index, { kind: 'group-by-map', totalField: 'total', mappingsText: 'true:active, false:inactive', ratioTargetField: 'activeRatePct', ratioNumeratorField: 'active', ratioDenominatorField: 'total', ratioScale: 100, ratioDecimals: 1 })\">\n {{ tx('editor.state.presetGroupByKpi', 'Group-by + Total + Ratio (%)') }}\n </button>\n <button mat-stroked-button type=\"button\" [attr.data-testid]=\"'page-datasource-preset-weighted-' + $index\" (click)=\"applyGuidedDataSourceTransform($index, { kind: 'group-by-map', totalField: 'total', weightedAverageField: 'weightedAvg', weightedAverageDecimals: 2, mappingsText: '1:low, 5:high' })\">\n {{ tx('editor.state.presetWeightedAvg', 'Weighted average') }}\n </button>\n <button mat-stroked-button type=\"button\" [attr.data-testid]=\"'page-datasource-preset-timeseries-' + $index\" (click)=\"applyGuidedDataSourceQuery($index, { mode: 'timeseries', groupByField: 'createdAt', period: 'month', metricField: 'id', metricOp: 'count' }); applyGuidedDataSourceTransform($index, { kind: 'timeseries-last', mappingsText: 'count:latestCount, avg:latestAvg' })\">\n {{ tx('editor.state.presetTimeseriesLast', 'Timeseries latest point') }}\n </button>\n <button mat-stroked-button type=\"button\" [attr.data-testid]=\"'page-datasource-preset-path-' + $index\" (click)=\"applyGuidedDataSourceTransform($index, { kind: 'path', path: 'totalElements' })\">\n {{ tx('editor.state.presetPathExtract', 'Extract path (totalElements)') }}\n </button>\n </div>\n </div>\n <mat-form-field appearance=\"outline\" class=\"wide\">\n <mat-label>{{ tx('editor.state.queryJson', 'Query (JSON)') }}</mat-label>\n <textarea matInput rows=\"3\" formControlName=\"queryJson\" [attr.data-testid]=\"'page-datasource-queryJson-' + $index\"></textarea>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"wide\">\n <mat-label>{{ tx('editor.state.transformJson', 'Transform (JSON)') }}</mat-label>\n <textarea matInput rows=\"3\" formControlName=\"transformJson\" [attr.data-testid]=\"'page-datasource-transformJson-' + $index\"></textarea>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" class=\"wide\">\n <mat-label>{{ tx('editor.state.fallbackJson', 'Fallback value (JSON)') }}</mat-label>\n <textarea matInput rows=\"2\" formControlName=\"fallbackJson\" [attr.data-testid]=\"'page-datasource-fallbackJson-' + $index\"></textarea>\n </mat-form-field>\n </div>\n @let preview = getDataSourcePreview($index);\n <div class=\"datasource-preview-box\" [attr.data-testid]=\"'page-datasource-preview-' + $index\">\n <div class=\"head\">\n <strong>{{ tx('editor.state.previewTitle', 'Transform & State Preview') }}</strong>\n <span class=\"previous-height-chip\">{{ preview.sourceKind === 'live' ? tx('editor.state.previewLive', 'Live response') : tx('editor.state.previewSample', 'Simulated sample') }}</span>\n </div>\n @if (preview.status === 'error') {\n <p class=\"limits-error\" role=\"alert\" [attr.data-testid]=\"'page-datasource-preview-error-' + $index\">{{ preview.error }}</p>\n } @else {\n <pre class=\"datasource-preview-json\" [attr.data-testid]=\"'page-datasource-preview-json-' + $index\">{{ preview.formattedJson }}</pre>\n @if (preview.tokens.length) {\n <div class=\"studio-actions\" [attr.data-testid]=\"'page-datasource-preview-tokens-' + $index\">\n <span class=\"hint\">{{ tx('editor.state.availableTokens', 'Click a token to prepare widget binding:') }}</span>\n @for (token of preview.tokens; track token) {\n <button mat-button type=\"button\" (click)=\"binderTokenControl.setValue(token)\"><code>{{ token }}</code></button>\n }\n </div>\n }\n }\n </div>\n </div>\n }\n </section>\n\n <section class=\"card\" data-testid=\"page-derived-measures-guided\">\n <div class=\"head\">\n <strong>{{ tx('editor.state.derivedMeasuresTitle', 'Guided Derived Measures') }}</strong>\n </div>\n <p class=\"hint\">{{ tx('editor.state.derivedMeasuresCopy', 'Create percentages, ratios, weighted averages, or formatted measures from page state paths without writing raw JsonLogic.') }}</p>\n <div class=\"grid\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('editor.state.derivedTargetPath', 'Measure key') }}</mat-label>\n <input matInput [formControl]=\"derivedPathControl\" placeholder=\"activeRatePct\" data-testid=\"derived-target-path\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('editor.state.derivedKind', 'Calculation') }}</mat-label>\n <mat-select [formControl]=\"derivedKindControl\" data-testid=\"derived-kind\">\n <mat-option value=\"ratio-percent\">{{ tx('editor.state.derivedRatio', 'Ratio / Percentage') }}</mat-option>\n <mat-option value=\"weighted-average\">{{ tx('editor.state.derivedWeighted', 'Weighted average') }}</mat-option>\n <mat-option value=\"format-value\">{{ tx('editor.state.derivedFormat', 'Formatted value') }}</mat-option>\n <mat-option value=\"template\">{{ tx('editor.state.derivedTemplate', 'State reference') }}</mat-option>\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('editor.state.derivedNumerator', 'Numerator / Source path') }}</mat-label>\n <input matInput [formControl]=\"derivedNumeratorControl\" placeholder=\"kpis.active\" data-testid=\"derived-numerator\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('editor.state.derivedDenominator', 'Denominator path') }}</mat-label>\n <input matInput [formControl]=\"derivedDenominatorControl\" placeholder=\"kpis.total\" data-testid=\"derived-denominator\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('editor.state.derivedFormatKind', 'Format') }}</mat-label>\n <mat-select [formControl]=\"derivedFormatControl\" data-testid=\"derived-format\">\n <mat-option value=\"percent\">percent (%)</mat-option>\n <mat-option value=\"number\">number</mat-option>\n <mat-option value=\"integer\">integer</mat-option>\n <mat-option value=\"currency\">currency (R$)</mat-option>\n <mat-option value=\"boolean\">boolean</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n <div class=\"studio-actions\">\n <button mat-stroked-button type=\"button\" data-testid=\"derived-add-btn\" (click)=\"addGuidedDerivedMeasure()\">\n <mat-icon aria-hidden=\"true\">functions</mat-icon>{{ tx('editor.state.addDerivedAction', 'Add measure to page state') }}\n </button>\n </div>\n @let derivedPreview = getDerivedStatePreview();\n @if (derivedPreview.entries.length || derivedPreview.diagnostics.length) {\n <div class=\"datasource-preview-box\" data-testid=\"page-derived-preview\">\n @for (diag of derivedPreview.diagnostics; track diag) {\n <p class=\"limits-error\" role=\"alert\">{{ diag }}</p>\n }\n @if (derivedPreview.entries.length) {\n <div class=\"studio-actions\">\n @for (entry of derivedPreview.entries; track entry.key) {\n <button mat-button type=\"button\" (click)=\"binderTokenControl.setValue(entry.token)\">\n <strong>{{ entry.key }}</strong>: {{ entry.formattedValue }} <code>({{ entry.token }})</code>\n </button>\n }\n </div>\n }\n </div>\n }\n </section>\n\n <section class=\"card\" data-testid=\"page-state-component-binder\">\n <div class=\"head\">\n <strong>{{ tx('editor.state.binderTitle', 'Connect State or Measure to Widget') }}</strong>\n </div>\n <p class=\"hint\">{{ tx('editor.state.binderCopy', 'Bind any state path or derived measure directly to a widget input in the draft.') }}</p>\n <div class=\"grid\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('editor.state.binderToken', 'State token') }}</mat-label>\n <input matInput [formControl]=\"binderTokenControl\" placeholder=\"{{ '{{state.kpis.total}}' }}\" data-testid=\"binder-token\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('editor.state.binderWidget', 'Target widget') }}</mat-label>\n <mat-select [formControl]=\"binderWidgetControl\" data-testid=\"binder-widget\" panelClass=\"praxis-binder-widget-select-panel\">\n @if (selectedBinderWidget(); as selectedWidget) {\n <mat-select-trigger>\n <span class=\"binder-widget-trigger\">\n <mat-icon aria-hidden=\"true\" class=\"binder-widget-trigger-icon\">{{ selectedWidget.icon }}</mat-icon>\n <span class=\"binder-widget-trigger-text\">\n <strong>{{ selectedWidget.title }}</strong>\n @if (selectedWidget.description) {\n <small> \u00B7 {{ selectedWidget.description }}</small>\n }\n </span>\n </span>\n </mat-select-trigger>\n }\n @for (w of bindablePageWidgets(); track w.key) {\n <mat-option [value]=\"w.key\" [attr.data-widget-key]=\"w.key\" class=\"binder-widget-option\">\n <span class=\"binder-widget-option-row\">\n <mat-icon aria-hidden=\"true\" class=\"binder-widget-option-icon\">{{ w.icon }}</mat-icon>\n <span class=\"binder-widget-option-copy\">\n <strong class=\"binder-widget-option-title\">{{ w.title }}</strong>\n @if (w.description) {\n <small class=\"binder-widget-option-desc\">{{ w.description }}</small>\n }\n </span>\n <span class=\"binder-widget-option-sr-key\" aria-hidden=\"true\">{{ w.key }}</span>\n </span>\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ tx('editor.state.binderInputPath', 'Widget input path') }}</mat-label>\n <input matInput [formControl]=\"binderInputPathControl\" placeholder=\"value\" data-testid=\"binder-input-path\" />\n </mat-form-field>\n </div>\n @if (availableStateBindingTokens().length) {\n <div class=\"binder-input-suggestions\" data-testid=\"binder-token-suggestions\">\n <span class=\"hint\">{{ tx('editor.state.binderTokenSuggestions', 'Available tokens:') }}</span>\n @for (availableToken of availableStateBindingTokens(); track availableToken) {\n <button\n type=\"button\"\n class=\"binder-input-suggestion-chip\"\n [class.active]=\"binderTokenControl.value === availableToken\"\n [attr.data-testid]=\"'binder-token-suggestion-' + availableToken\"\n (click)=\"selectBinderTokenSuggestion(availableToken)\"\n >\n <code>{{ availableToken }}</code>\n </button>\n }\n </div>\n }\n <div class=\"binder-input-suggestions\" data-testid=\"binder-pipe-suggestions\">\n <span class=\"hint\">{{ tx('editor.state.binderPipeSuggestions', 'Formatadores CSP-safe (pipes):') }}</span>\n @for (pipeSegment of binderDeclarativePipes; track pipeSegment) {\n <button\n type=\"button\"\n class=\"binder-input-suggestion-chip\"\n [attr.data-testid]=\"'binder-pipe-chip-' + pipeSegment\"\n (click)=\"appendBinderTokenPipe(pipeSegment)\"\n >\n <code>{{ pipeSegment }}</code>\n </button>\n }\n </div>\n @if (binderTokenPipeError(); as pipeErr) {\n <p class=\"studio-status binder-unbound-warning\" role=\"alert\" data-testid=\"binder-pipe-error\">{{ pipeErr }}</p>\n } @else if (binderTokenLivePreview(); as livePreviewVal) {\n <p class=\"hint\" data-testid=\"binder-live-preview\">\n {{ tx('editor.state.binderLivePreview', 'Preview simulado:') }} <strong>{{ livePreviewVal }}</strong>\n </p>\n }\n @if (selectedBinderWidgetInputSuggestions().length) {\n <div class=\"binder-input-suggestions\" data-testid=\"binder-input-suggestions\">\n <span class=\"hint\">{{ tx('editor.state.binderInputSuggestions', 'Suggested paths:') }}</span>\n @for (suggestedPath of selectedBinderWidgetInputSuggestions(); track suggestedPath) {\n <button\n type=\"button\"\n class=\"binder-input-suggestion-chip\"\n [class.active]=\"binderInputPathControl.value === suggestedPath\"\n [attr.data-testid]=\"'binder-input-suggestion-' + suggestedPath\"\n (click)=\"selectBinderInputPathSuggestion(suggestedPath)\"\n >\n <code>{{ suggestedPath }}</code>\n </button>\n }\n </div>\n }\n <div class=\"studio-actions\">\n <button mat-stroked-button type=\"button\" data-testid=\"binder-bind-btn\" (click)=\"bindStateTokenToWidget()\">\n <mat-icon aria-hidden=\"true\">link</mat-icon>{{ tx('editor.state.bindAction', 'Bind token to widget in draft') }}\n </button>\n <button mat-stroked-button type=\"button\" data-testid=\"binder-auto-bind-btn\" (click)=\"autoBindWidgetStatePlaceholders()\">\n <mat-icon aria-hidden=\"true\">auto_fix_high</mat-icon>{{ tx('editor.state.autoBindAction', 'Auto-bind ${...} widget placeholders') }}\n </button>\n </div>\n @if (binderMessage()) {\n <p class=\"studio-status\" role=\"status\" data-testid=\"binder-status-message\">{{ binderMessage() }}</p>\n }\n @if (invalidWidgetStateBindingPipes().length) {\n <div class=\"binder-unbound-warning\" role=\"alert\" data-testid=\"binder-invalid-pipes-warning\">\n <mat-icon aria-hidden=\"true\">error_outline</mat-icon>\n <div class=\"binder-unbound-warning__body\">\n <span>{{ tx('editor.state.invalidWidgetPipesWarning', 'Formatadores (pipes) inv\u00E1lidos detectados:') }}</span>\n <div class=\"binder-unbound-warning__chips\">\n @for (invalidBinding of invalidWidgetStateBindingPipes(); track invalidBinding.widgetKey + ':' + invalidBinding.inputPath) {\n <button\n type=\"button\"\n class=\"binder-unbound-chip\"\n [attr.data-testid]=\"'binder-invalid-pipe-chip-' + invalidBinding.widgetKey + '-' + invalidBinding.inputPath\"\n (click)=\"loadStateBindingIntoForm(invalidBinding)\"\n >\n <strong>{{ invalidBinding.widgetTitle }}</strong>.<code>{{ invalidBinding.inputPath }}</code> (<code>{{ invalidBinding.expression }}</code>)\n </button>\n }\n </div>\n </div>\n </div>\n }\n @if (unboundWidgetStatePlaceholders().length; as unboundCount) {\n <div class=\"binder-unbound-warning\" role=\"status\" data-testid=\"binder-unbound-placeholders-warning\">\n <mat-icon aria-hidden=\"true\">warning_amber</mat-icon>\n <div class=\"binder-unbound-warning__body\">\n <span>{{ tx('editor.state.unboundPlaceholdersWarning', 'Placeholders sem v\u00EDnculo detectados:') }}</span>\n <div class=\"binder-unbound-warning__chips\">\n @for (item of unboundWidgetStatePlaceholders(); track item.widgetKey + ':' + item.placeholder) {\n <button\n type=\"button\"\n class=\"binder-unbound-chip\"\n [attr.data-testid]=\"'binder-unbound-chip-' + item.widgetKey + '-' + item.placeholder\"\n [attr.title]=\"tx('editor.state.selectUnboundPlaceholderHint', 'Clique para preencher widget e caminho no formul\u00E1rio')\"\n (click)=\"selectUnboundPlaceholder(item)\"\n >\n <strong>{{ item.widgetTitle }}</strong> (<code>{{ '${' + item.placeholder + '}' }}</code>)\n </button>\n }\n </div>\n </div>\n </div>\n }\n @if (currentWidgetStateBindings().length) {\n <ul class=\"blocked-widgets-list binder-active-list\" data-testid=\"active-widget-state-bindings\">\n @for (binding of currentWidgetStateBindings(); track binding.widgetKey + ':' + binding.inputPath) {\n @let previewVal = resolveStateTokenPreview(binding.token);\n <li class=\"binder-active-item\">\n <mat-icon aria-hidden=\"true\" class=\"binder-active-icon\">{{ widgetIconForKey(binding.widgetKey) }}</mat-icon>\n <button\n type=\"button\"\n class=\"binder-active-copy binder-active-select-btn\"\n [attr.data-testid]=\"'binder-select-' + binding.widgetKey + '-' + binding.inputPath\"\n [attr.title]=\"tx('editor.state.editBindingHint', 'Click to load binding into form')\"\n (click)=\"loadStateBindingIntoForm(binding)\"\n >\n <strong>{{ binding.widgetTitle }}</strong>.<code>{{ binding.inputPath }}</code> \u2190 <code>{{ binding.token }}</code>\n @if (previewVal !== null) {\n <small class=\"binder-preview-badge\" [attr.data-testid]=\"'binder-preview-' + binding.widgetKey + '-' + binding.inputPath\">= {{ previewVal }}</small>\n }\n </button>\n <button mat-icon-button type=\"button\" [attr.data-testid]=\"'binder-unbind-' + binding.widgetKey + '-' + binding.inputPath\"\n [attr.aria-label]=\"tx('editor.state.unbindAction', 'Remove binding')\"\n (click)=\"unbindStateTokenFromWidget(binding.widgetKey, binding.inputPath)\">\n <mat-icon aria-hidden=\"true\">link_off</mat-icon>\n </button>\n </li>\n }\n </ul>\n }\n </section>\n\n <mat-form-field appearance=\"outline\" class=\"wide\">\n <mat-label>{{ tx('editor.state.dataSourcesJson', 'page.dataSources (JSON)') }}</mat-label>\n <textarea matInput rows=\"8\" [formControl]=\"dataSourcesControl\" [placeholder]=\"tx('editor.hints.dataSourcesExample', '')\" data-testid=\"page-datasources-json\"></textarea>\n </mat-form-field>\n </div>\n </mat-tab>\n <mat-tab [label]=\"tx('editor.limits.tab', 'Widget limits')\">\n <section class=\"limits-panel\" data-testid=\"canvas-limits-editor\">\n @if (workspaceTab() === 3 && activeTab() === 'limits' && canvasLimits.targets.length) {\n <div class=\"selection-identity\" tabindex=\"-1\" data-testid=\"canvas-limits-identity\">\n <mat-icon aria-hidden=\"true\">{{ limitsTargetIcon(limitsTargetControl.value) }}</mat-icon>\n <strong>{{ limitsTargetLabel(limitsTargetControl.value) }}</strong>\n </div>\n }\n\n <p class=\"hint\" id=\"canvas-limits-help\">{{ tx('editor.limits.help', 'Limits use grid columns and rows, not pixels. Leave blank for defaults, or to inherit the base limits on a device.') }}</p>\n @if (canvasLimits.targets.length) {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('editor.limits.target', 'Widget and layout') }}</mat-label>\n <mat-select [formControl]=\"limitsTargetControl\" data-testid=\"canvas-limits-target\">\n <mat-select-trigger>\n <span class=\"binder-widget-trigger\">\n <mat-icon aria-hidden=\"true\" class=\"binder-widget-trigger-icon\">{{ limitsTargetIcon(limitsTargetControl.value) }}</mat-icon>\n <span class=\"binder-widget-trigger-text\">{{ limitsTargetLabel(limitsTargetControl.value) }}</span>\n </span>\n </mat-select-trigger>\n @for (target of canvasLimits.targets; track target.scope + ':' + target.key; let index = $index) {\n <mat-option [value]=\"index\">\n <mat-icon aria-hidden=\"true\">{{ limitsTargetIcon(index) }}</mat-icon>\n <span>{{ limitsTargetLabel(index) }}</span>\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <p class=\"hint\">{{ limitsGeometryLabel() }}</p>\n @if (canvasLimits.form.controls[limitsTargetControl.value]; as limitsForm) {\n <div class=\"limits-fields\" aria-describedby=\"canvas-limits-help\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('editor.limits.minColSpan', 'Minimum width (columns)') }}</mat-label>\n <input matInput [errorStateMatcher]=\"limitsErrorMatcher\" type=\"number\" min=\"1\" step=\"1\" [formControl]=\"limitsForm.controls.minColSpan\" data-testid=\"canvas-limit-minColSpan\">\n <mat-error>{{ limitFieldError(limitsForm.controls.minColSpan) }}</mat-error>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('editor.limits.maxColSpan', 'Maximum width (columns)') }}</mat-label>\n <input matInput [errorStateMatcher]=\"limitsErrorMatcher\" type=\"number\" min=\"1\" step=\"1\" [formControl]=\"limitsForm.controls.maxColSpan\" data-testid=\"canvas-limit-maxColSpan\">\n <mat-error>{{ limitFieldError(limitsForm.controls.maxColSpan) }}</mat-error>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('editor.limits.minRowSpan', 'Minimum height (rows)') }}</mat-label>\n <input matInput [errorStateMatcher]=\"limitsErrorMatcher\" type=\"number\" min=\"1\" step=\"1\" [formControl]=\"limitsForm.controls.minRowSpan\" data-testid=\"canvas-limit-minRowSpan\">\n <mat-error>{{ limitFieldError(limitsForm.controls.minRowSpan) }}</mat-error>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('editor.limits.maxRowSpan', 'Maximum height (rows)') }}</mat-label>\n <input matInput [errorStateMatcher]=\"limitsErrorMatcher\" type=\"number\" min=\"1\" step=\"1\" [formControl]=\"limitsForm.controls.maxRowSpan\" data-testid=\"canvas-limit-maxRowSpan\">\n <mat-error>{{ limitFieldError(limitsForm.controls.maxRowSpan) }}</mat-error>\n </mat-form-field>\n </div>\n @if (limitsUseContentHeight()) {\n <p class=\"limits-notice\"><mat-icon aria-hidden=\"true\">height</mat-icon><span>{{ tx('editor.limits.autoHeight', 'Choose natural height or an adjustable height with internal scrolling.') }}</span></p>\n <mat-button-toggle-group class=\"height-modes\" [formControl]=\"limitsForm.controls.heightMode\" [attr.aria-label]=\"tx('editor.height.mode', 'Widget height')\" data-testid=\"canvas-height-mode\">\n <mat-button-toggle value=\"inherit\">{{ tx('editor.height.inherit', 'Default') }}</mat-button-toggle>\n <mat-button-toggle value=\"auto\">{{ tx('editor.height.auto', 'Automatic') }}</mat-button-toggle>\n <mat-button-toggle value=\"fixed\">{{ tx('editor.height.fixed', 'Adjustable') }}</mat-button-toggle>\n </mat-button-toggle-group>\n @if (limitsForm.controls.heightMode.value === 'fixed') {\n <div class=\"limits-fields\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('editor.height.value', 'Height (px)') }}</mat-label>\n <input matInput type=\"number\" min=\"160\" step=\"1\" [errorStateMatcher]=\"limitsErrorMatcher\" [formControl]=\"limitsForm.controls.heightPx\" data-testid=\"canvas-height-px\">\n <mat-error>{{ limitFieldError(limitsForm.controls.heightPx) }}</mat-error>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('editor.height.top', 'Top inset (px)') }}</mat-label>\n <input matInput type=\"number\" min=\"0\" step=\"1\" [errorStateMatcher]=\"limitsErrorMatcher\" [formControl]=\"limitsForm.controls.offsetTopPx\" data-testid=\"canvas-height-top\">\n <mat-error>{{ limitFieldError(limitsForm.controls.offsetTopPx) }}</mat-error>\n </mat-form-field>\n </div>\n }\n <div class=\"limits-fields\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('editor.height.min', 'Minimum height (px)') }}</mat-label>\n <input matInput type=\"number\" min=\"160\" step=\"1\" [errorStateMatcher]=\"limitsErrorMatcher\" [formControl]=\"limitsForm.controls.minHeightPx\" data-testid=\"canvas-limit-minHeightPx\">\n <mat-error>{{ limitFieldError(limitsForm.controls.minHeightPx) }}</mat-error>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ tx('editor.height.max', 'Maximum height (px)') }}</mat-label>\n <input matInput type=\"number\" min=\"160\" step=\"1\" [errorStateMatcher]=\"limitsErrorMatcher\" [formControl]=\"limitsForm.controls.maxHeightPx\" data-testid=\"canvas-limit-maxHeightPx\">\n <mat-error>{{ limitFieldError(limitsForm.controls.maxHeightPx) }}</mat-error>\n </mat-form-field>\n </div>\n }\n }\n @if (limitsSummaryError) { <p class=\"limits-error\" role=\"alert\">{{ limitsSummaryError }}</p> }\n } @else {\n <p>{{ tx('editor.limits.empty', 'Add widgets to the canvas to configure their limits.') }}</p>\n }\n </section>\n </mat-tab>\n </mat-tab-group></div>\n </mat-tab>\n </mat-tab-group>\n <details class=\"summary-details\">\n <summary>{{ tx('editor.summaryAria', 'Configuration summary') }}</summary>\n <div class=\"summary\">\n @if (!isHostedInSettingsPanel) {\n <span>{{ validationStateLabel }}</span>\n <span>{{ dirtyStateLabel }}</span>\n }\n <span>{{ selectedLayoutPresetLabel }}</span>\n <span>{{ selectedThemePresetLabel }}</span>\n <span>{{ canvasSummaryLabel }}</span>\n </div>\n </details>\n </div>\n", styles: [".page-inset-field{grid-column:1/-1;min-width:0}:host{display:block;min-width:0;max-width:100%}.height-modes{width:fit-content;max-width:100%;flex-wrap:wrap;margin-bottom:16px}.editor,.panel{display:grid;gap:16px;min-width:0;max-width:100%;color:var(--md-sys-color-on-surface, currentColor)}mat-tab-group,mat-form-field{min-width:0;max-width:100%}.head,.summary{display:flex;gap:12px;align-items:center;flex-wrap:wrap}.summary{color:var(--md-sys-color-on-surface-variant, currentColor)}.selection-identity{display:flex;align-items:flex-start;gap:8px;padding-block:8px;background:var(--md-sys-color-surface-container-low, var(--md-sys-color-surface));overflow-wrap:anywhere}.selection-identity mat-icon{flex-shrink:0}:host ::ng-deep .editor-tabs>.mat-mdc-tab-header .mdc-tab{min-width:0;padding-inline:12px}:host ::ng-deep .editor-tabs>.mat-mdc-tab-header .mdc-tab__text-label{max-width:128px;white-space:normal;overflow-wrap:anywhere;text-align:center;letter-spacing:normal;line-height:1.25}.grid{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(min(220px,100%),1fr))}@container (max-width: 600px){.grid{grid-template-columns:minmax(0,1fr)}.head,.summary{align-items:stretch}.head>button{width:100%}}@media(max-width:600px){.grid{grid-template-columns:minmax(0,1fr)}.head,.summary{align-items:stretch}.head>button{width:100%}}.card{display:grid;gap:12px;padding:16px;border:1px solid var(--md-sys-color-outline-variant, color-mix(in srgb, currentColor 18%, transparent));border-radius:8px;background:var(--md-sys-color-surface-container-low, var(--md-sys-color-surface-container, transparent));color:var(--md-sys-color-on-surface, currentColor)}.limits-panel{display:grid;gap:8px;min-width:0}.limits-fields{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:4px 16px}.limits-fields mat-form-field{min-width:0}.limits-notice{display:flex;gap:8px;align-items:flex-start;color:var(--md-sys-color-on-surface-variant, currentColor)}.limits-notice mat-icon{flex-shrink:0}.limits-error{color:var(--md-sys-color-error, #b3261e)}.limits-blocking-banner{border-color:var(--md-sys-color-error, #b3261e);gap:8px}.blocked-widgets-list{margin:0;padding-inline-start:20px;display:grid;gap:4px}.previous-heights-summary{display:flex;flex-wrap:wrap;gap:8px;align-items:center;font:var(--mat-sys-body-small, inherit);color:var(--md-sys-color-on-surface-variant, currentColor)}.previous-height-chip{padding:2px 8px;border-radius:999px;background:var(--md-sys-color-surface-container-highest, rgba(128, 128, 128, .14))}.wide{grid-column:1/-1}.hint{color:var(--md-sys-color-on-surface-variant, currentColor);font-size:12px}.eyebrow{color:var(--md-sys-color-primary, currentColor);font-size:11px;font-weight:700;letter-spacing:0;text-transform:uppercase}:host ::ng-deep .mat-mdc-tab-body-content{padding-top:12px;box-sizing:border-box}:host{container-type:inline-size}.studio-panel{display:grid;gap:var(--p-space-4, 16px);padding-block:var(--p-space-5, 20px);min-width:0}.studio-heading h2{font-size:var(--p-font-size-lg, 20px);line-height:1.25;margin:var(--p-space-2, 8px) 0;font-weight:600}.studio-heading p{margin:0;color:var(--md-sys-color-on-surface-variant);line-height:1.5}.studio-panel h3{font-size:var(--p-font-size-md, 16px);margin:0}.studio-panel>button{justify-self:start}.studio-gallery{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--p-space-3, 12px)}.studio-choice{display:flex;flex-direction:column;gap:var(--p-space-2, 8px);min-width:0;padding:var(--p-space-3, 12px);text-align:start;border:1px solid var(--md-sys-color-outline-variant);border-radius:var(--p-radius-lg, 12px);background:var(--md-sys-color-surface);color:var(--md-sys-color-on-surface);font:inherit;cursor:pointer;transition:border-color var(--md-sys-motion-duration-short2, .1s),background-color var(--md-sys-motion-duration-short2, .1s)}.studio-choice:hover:not(:disabled){border-color:var(--md-sys-color-primary);background:var(--md-sys-color-surface-container-low)}.studio-choice.selected{border-color:var(--md-sys-color-primary);box-shadow:inset 0 0 0 1px var(--md-sys-color-primary);background:var(--md-sys-color-surface-container-low)}.studio-choice:focus-visible,.studio-panel summary:focus-visible{outline:3px solid var(--md-sys-color-primary);outline-offset:3px}.studio-choice:disabled{opacity:var(--p-disabled-opacity, .5);cursor:not-allowed}.choice-title{display:flex;align-items:center;justify-content:space-between;gap:var(--p-space-2, 8px);width:100%;font-weight:600;line-height:1.35}.choice-title mat-icon{color:var(--md-sys-color-primary);flex-shrink:0;font-size:20px;height:20px;width:20px}.choice-description{font-size:var(--p-font-size-sm, 13px);line-height:1.5;color:var(--md-sys-color-on-surface-variant)}.choice-meta{font-size:var(--p-font-size-xs, 12px);color:var(--md-sys-color-on-surface-variant);margin-top:auto;padding-top:var(--p-space-1, 4px)}.theme-sample{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));padding:var(--p-space-3, 12px);border-radius:var(--p-radius-md, 8px);min-height:128px;align-self:stretch;margin-bottom:var(--p-space-1, 4px)}.sample-metric,.sample-chart{background:var(--sample-surface);border-radius:var(--sample-radius);box-shadow:var(--sample-shadow);padding:var(--p-space-2, 8px)}.sample-metric b{display:block;font-size:var(--p-font-size-md, 16px)}.sample-metric i{display:block;height:3px;width:55%;margin-top:var(--p-space-1, 4px);background:var(--sample-accent)}.sample-chart{grid-column:1/-1;display:flex;height:40px;align-items:flex-end;gap:var(--p-space-2, 8px)}.sample-chart i{flex:1;background:var(--sample-accent);height:50%;border-radius:var(--p-radius-xs, 2px)}.sample-chart i:nth-child(2){height:85%}.sample-chart i:nth-child(3){height:65%}.sample-chart i:nth-child(4){height:100%}.sample-chart i:nth-child(5){height:75%}.shell-options,.arrangement-rules{border-top:1px solid var(--md-sys-color-outline-variant);padding-top:var(--p-space-3, 12px)}.studio-panel summary{cursor:pointer;font-weight:500;line-height:1.5;padding-block:var(--p-space-2, 8px)}.shell-gallery{margin-top:var(--p-space-4, 16px)}.shell-sample{display:block;align-self:stretch;border:1px solid;overflow:hidden;margin:var(--p-space-2, 8px)}.shell-sample>span{display:flex;gap:var(--p-space-2, 8px);align-items:center;padding:var(--p-space-2, 8px);border-bottom:1px solid transparent}.shell-sample i{height:4px;width:60%;background:var(--md-sys-color-on-surface-variant);opacity:.4}.shell-sample>span:last-child{min-height:30px;flex-direction:column;align-items:flex-start}.shell-sample>span:last-child i:last-child{width:40%}.selection-line{margin:0;font-size:var(--p-font-size-sm, 13px);color:var(--md-sys-color-on-surface-variant)}.studio-actions{display:flex;flex-wrap:wrap;align-items:center;gap:var(--p-space-2, 8px)}.distribution-sample{display:grid;grid-template-rows:repeat(2,1fr);gap:var(--p-space-1, 4px);align-self:stretch;height:76px;padding:var(--p-space-2, 8px);background:var(--md-sys-color-surface-container);border-radius:var(--p-radius-md, 8px)}.distribution-sample i{display:grid;place-items:center;background:var(--md-sys-color-secondary-container);color:var(--md-sys-color-on-secondary-container);border-radius:var(--p-radius-sm, 4px);font-size:var(--p-font-size-xs, 12px);font-style:normal}.distribution-sample i:nth-child(n+4){opacity:.65}.selected .distribution-sample i{background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container)}.arrangement-review{display:grid;gap:var(--p-space-3, 12px);padding-block:var(--p-space-4, 16px);border-top:2px solid var(--md-sys-color-primary)}.review-heading{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:var(--p-space-2, 8px)}.review-heading>span{font-size:var(--p-font-size-sm, 13px);color:var(--md-sys-color-primary)}.canvas-comparison{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--p-space-3, 12px)}.canvas-comparison figure{margin:0;min-width:0}.canvas-comparison figcaption{font-weight:500;margin-bottom:var(--p-space-2, 8px)}.canvas-map{display:grid;gap:2px;height:220px;padding:var(--p-space-2, 8px);border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-low);border-radius:var(--p-radius-md, 8px)}.canvas-map span{display:grid;place-items:center;min-height:0;min-width:0;overflow:hidden;font-size:var(--p-font-size-xs, 12px);color:var(--md-sys-color-on-secondary-container);background:var(--md-sys-color-secondary-container);border-radius:var(--p-radius-xs, 2px)}.canvas-map .changed{background:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container)}.widget-changes{padding-inline-start:var(--p-space-5, 20px);margin:0}.widget-changes li{padding-block:var(--p-space-2, 8px);overflow-wrap:anywhere}.widget-changes span{display:block;font-size:var(--p-font-size-sm, 13px);color:var(--md-sys-color-on-surface-variant)}.studio-status{border-inline-start:3px solid var(--md-sys-color-primary);background:var(--md-sys-color-surface-container-low);padding:var(--p-space-3, 12px);margin:0;line-height:1.5}@container (max-width:420px){.studio-gallery{grid-template-columns:minmax(0,1fr)}.canvas-map{height:180px}}@media(prefers-reduced-motion:reduce){.studio-choice{transition:none}}.arrangement-feedback:empty{display:none}.arrangement-feedback:focus{outline:none}.arrangement-feedback:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:3px}@container (min-width:620px){.arrangement-gallery{grid-template-columns:repeat(3,minmax(0,1fr))}}:host ::ng-deep .studio-tabs>.mat-mdc-tab-header{position:sticky;top:0;z-index:var(--praxis-layer-sticky-local, 100);background:var(--md-sys-color-surface-container-low, var(--md-sys-color-surface))}:host ::ng-deep .studio-tabs>.mat-mdc-tab-header .mdc-tab{flex:1 1 0;padding-inline:var(--p-space-1, 4px)}.arrangement-feedback{scroll-margin-block-start:var(--p-space-16, 64px)}.filter-owner-banner{border-color:var(--md-sys-color-tertiary, var(--md-sys-color-primary))}.filter-owner-item{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:8px}.quick-geometry-rows{display:grid;gap:8px}.quick-geometry-row{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:8px;padding-block:4px;border-bottom:1px solid var(--md-sys-color-outline-variant, rgba(128, 128, 128, .2))}.quick-geometry-label{display:inline-flex;align-items:center;gap:6px;min-width:0}.quick-geometry-label mat-icon{font-size:18px;width:18px;height:18px;flex-shrink:0;color:var(--md-sys-color-primary, currentColor)}.binder-widget-trigger{display:inline-flex;align-items:center;gap:8px;max-width:100%;min-width:0}.binder-widget-trigger-icon{font-size:18px;width:18px;height:18px;flex-shrink:0;color:var(--md-sys-color-primary, currentColor)}.binder-widget-trigger-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.binder-widget-trigger-text small{color:var(--md-sys-color-on-surface-variant, currentColor);font-weight:400}.binder-widget-option-row{position:relative;display:flex;align-items:flex-start;gap:10px;padding-block:4px;min-width:0}.binder-widget-option-icon{font-size:20px;width:20px;height:20px;margin-top:2px;flex-shrink:0;color:var(--md-sys-color-primary, currentColor)}.binder-widget-option-copy{display:flex;flex-direction:column;gap:2px;min-width:0;line-height:1.3}.binder-widget-option-title{font-weight:600;color:var(--md-sys-color-on-surface, currentColor);overflow-wrap:anywhere}.binder-widget-option-desc{font-size:12px;color:var(--md-sys-color-on-surface-variant, currentColor);overflow-wrap:anywhere}.binder-widget-option-sr-key{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;opacity:0;pointer-events:none}.binder-input-suggestions{display:flex;flex-wrap:wrap;align-items:center;gap:6px}.binder-input-suggestion-chip{display:inline-flex;align-items:center;padding:2px 8px;border-radius:999px;border:1px solid var(--md-sys-color-outline-variant, rgba(128, 128, 128, .3));background:var(--md-sys-color-surface, transparent);color:var(--md-sys-color-on-surface, currentColor);font-size:12px;cursor:pointer;transition:border-color .1s,background-color .1s}.binder-input-suggestion-chip:hover{border-color:var(--md-sys-color-primary, currentColor);background:var(--md-sys-color-surface-container-low, rgba(128, 128, 128, .08))}.binder-input-suggestion-chip.active{border-color:var(--md-sys-color-primary, currentColor);background:var(--md-sys-color-primary-container, rgba(128, 128, 128, .16));color:var(--md-sys-color-on-primary-container, currentColor)}.binder-active-list{list-style:none;padding-inline-start:0;gap:6px}.binder-active-item{display:flex;align-items:center;gap:8px;padding:4px 8px;border-radius:6px;background:var(--md-sys-color-surface, rgba(255, 255, 255, .03))}.binder-active-icon{font-size:18px;width:18px;height:18px;flex-shrink:0;color:var(--md-sys-color-primary, currentColor)}.binder-active-copy{flex:1 1 auto;min-width:0;overflow-wrap:anywhere}.binder-active-select-btn{appearance:none;border:none;background:transparent;color:inherit;font:inherit;text-align:start;padding:2px 4px;border-radius:4px;cursor:pointer}.binder-active-select-btn:hover{background:var(--md-sys-color-surface-container-low, rgba(128, 128, 128, .08))}.binder-active-select-btn:focus-visible{outline:2px solid var(--md-sys-color-primary, currentColor);outline-offset:2px}.binder-preview-badge{display:inline-flex;align-items:center;margin-inline-start:6px;padding:1px 6px;border-radius:999px;font-size:11px;font-weight:600;color:var(--md-sys-color-on-secondary-container, #1d192b);background:var(--md-sys-color-secondary-container, rgba(103, 80, 164, .14))}.binder-unbound-warning{display:flex;align-items:flex-start;gap:8px;padding:8px 10px;border-radius:6px;font-size:12px;border:1px solid var(--md-sys-color-tertiary, rgba(180, 110, 0, .35));background:color-mix(in srgb,var(--md-sys-color-tertiary-container, #ffdea6) 45%,transparent);color:var(--md-sys-color-on-surface, inherit)}.binder-unbound-warning__body{display:grid;gap:6px}.binder-unbound-warning__chips{display:flex;flex-wrap:wrap;gap:6px}.binder-unbound-chip{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border-radius:999px;border:1px solid var(--md-sys-color-outline-variant, rgba(128, 128, 128, .35));background:var(--md-sys-color-surface, rgba(255, 255, 255, .65));color:inherit;font:inherit;font-size:11px;cursor:pointer}.binder-unbound-chip:hover{border-color:var(--md-sys-color-primary, currentColor)}.binder-unbound-chip:focus-visible{outline:2px solid var(--md-sys-color-primary, currentColor);outline-offset:2px}.guided-presets-bar{display:grid;gap:6px}.datasource-preview-box{display:grid;gap:8px;padding:12px;border-radius:6px;background:var(--md-sys-color-surface-container, rgba(128, 128, 128, .08))}.datasource-preview-json{margin:0;padding:8px;border-radius:4px;font-size:12px;overflow-x:auto;background:var(--md-sys-color-surface, rgba(0, 0, 0, .04))}.composer-rich-field-option{display:flex;flex-direction:column;gap:2px;padding:2px 0;line-height:1.3;min-width:0}.composer-rich-field-option__header{display:flex;align-items:center;flex-wrap:wrap;gap:6px;min-width:0}.composer-rich-field-option__label{font-weight:500;color:var(--md-sys-color-on-surface, currentColor);overflow:hidden;text-overflow:ellipsis}.composer-rich-field-option__code{font-family:var(--mat-sys-mono-font, monospace);font-size:11px;padding:1px 6px;border-radius:999px;background:var(--md-sys-color-surface-container-highest, rgba(128, 128, 128, .14));color:var(--md-sys-color-on-surface-variant, currentColor)}.composer-rich-field-option__badge{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.03em;padding:1px 6px;border-radius:999px;background:color-mix(in srgb,var(--md-sys-color-primary, #4f46e5) 14%,transparent);color:var(--md-sys-color-primary, currentColor)}.composer-rich-field-option__desc{font-size:11px;color:var(--md-sys-color-on-surface-variant, currentColor);white-space:normal;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}\n"], dependencies: [{ kind: "component", type: PagePresetComparisonComponent, selector: "praxis-page-preset-comparison", inputs: ["expanded", "layoutId", "themeId", "layoutPresetOptions", "canvas"], outputs: ["choose"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.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.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { 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: MatAutocompleteModule }, { kind: "component", type: i2$3.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: i3$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "directive", type: i2$3.MatAutocompleteTrigger, selector: "input[matAutocomplete], textarea[matAutocomplete]", inputs: ["matAutocomplete", "matAutocompletePosition", "matAutocompleteConnectedTo", "autocomplete", "matAutocompleteDisabled"], exportAs: ["matAutocompleteTrigger"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.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: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatButtonToggleModule }, { kind: "directive", type: i4.MatButtonToggleGroup, selector: "mat-button-toggle-group", inputs: ["appearance", "name", "vertical", "value", "multiple", "disabled", "disabledInteractive", "hideSingleSelectionIndicator", "hideMultipleSelectionIndicator"], outputs: ["valueChange", "change"], exportAs: ["matButtonToggleGroup"] }, { kind: "component", type: i4.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: MatFormFieldModule }, { kind: "component", type: i7.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i7.MatLabel, selector: "mat-label" }, { kind: "directive", type: i7.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i7.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i7.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i9.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: i3$1.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: i3$1.MatSelectTrigger, selector: "mat-select-trigger" }, { kind: "ngmodule", type: MatTabsModule }, { kind: "directive", type: i9$1.MatTabContent, selector: "[matTabContent]" }, { kind: "component", type: i9$1.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i9$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: MatTooltipModule }, { kind: "directive", type: i3.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: PraxisIconButtonComponent, selector: "button[praxisIconButton]", inputs: ["praxisIconButton", "size", "appearance", "presentation", "pressed", "busy"] }, { kind: "component", type: ResourceQuickConnectComponent, selector: "praxis-resource-quick-connect", inputs: ["resourcePath", "endpointKey", "apiUrlEntry", "intent", "resources", "label", "placeholder", "hint", "disabled"], outputs: ["resourcePathChange", "resourceSelected"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
18133
18464
|
}
|
|
18134
18465
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DynamicPageConfigEditorComponent, decorators: [{
|
|
18135
18466
|
type: Component,
|
|
@@ -20067,7 +20398,7 @@ class PageConfigEditorComponent {
|
|
|
20067
20398
|
</div>
|
|
20068
20399
|
}
|
|
20069
20400
|
</div>
|
|
20070
|
-
`, isInline: true, styles: [".page-inset-field{grid-column:1 / -1;min-width:0}.page-config{display:grid;gap:12px;padding:16px;background:var(--md-sys-color-surface-container-lowest, var(--md-sys-color-surface));border:1px solid var(--md-sys-color-outline-variant);border-radius:12px}.page-head{display:flex;align-items:flex-start;gap:12px}.page-head__copy{display:grid;gap:4px}.page-title{font-weight:600;font-size:18px;line-height:1.2}.page-subtitle{max-width:72ch;color:var(--md-sys-color-on-surface-variant);font-size:13px;line-height:1.45}.spacer{flex:1}.page-summary{display:flex;flex-wrap:wrap;gap:8px}.page-tabs{border-radius:16px;overflow:hidden;background:color-mix(in srgb,var(--md-sys-color-surface-container-low) 78%,transparent);border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 82%,transparent)}.page-tabs ::ng-deep .mdc-tab{min-width:120px}.page-tabs ::ng-deep .mdc-tab__text-label{font-size:13px;font-weight:600}.page-tab-panel{display:grid;gap:12px;padding:16px 4px 4px}.page-chip{display:inline-flex;align-items:center;gap:8px;min-height:36px;padding:0 12px;border-radius:999px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 88%,transparent);background:color-mix(in srgb,var(--md-sys-color-surface-container-low) 82%,transparent);color:var(--md-sys-color-on-surface-variant);font-size:12px;font-weight:500}.page-chip mat-icon{width:18px;height:18px;font-size:18px}.page-chip--accent{border-color:color-mix(in srgb,var(--md-sys-color-primary) 35%,var(--md-sys-color-outline-variant));color:var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary-container) 30%,transparent)}.page-chip--warning{border-color:color-mix(in srgb,var(--md-sys-color-error) 35%,var(--md-sys-color-outline-variant));color:var(--md-sys-color-error);background:color-mix(in srgb,var(--md-sys-color-error-container) 35%,transparent)}.page-identity{display:grid;gap:4px;padding:12px 14px;border-radius:12px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 82%,transparent);background:color-mix(in srgb,var(--md-sys-color-surface-container-low) 70%,transparent);font-size:13px;color:var(--md-sys-color-on-surface-variant)}.page-section{font-weight:600;margin-top:8px}.page-section-help{margin-top:-4px;color:var(--md-sys-color-on-surface-variant);font-size:13px;line-height:1.45}.grid-form{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr))}.state-grid{display:grid;gap:16px}.state-values,.state-group{padding:14px;border:1px solid var(--md-sys-color-outline-variant);border-radius:14px;background:linear-gradient(180deg,color-mix(in srgb,var(--md-sys-color-surface-container-low) 92%,transparent),transparent),var(--md-sys-color-surface-container-lowest, var(--md-sys-color-surface))}.state-group{display:grid;gap:12px}.state-group-head{display:flex;align-items:center;justify-content:space-between;gap:12px}.state-group-title{font-weight:600}.state-group-help{color:var(--md-sys-color-on-surface-variant);font-size:13px}.state-list{display:grid;gap:12px}.state-card{display:grid;gap:12px;padding:12px;border-radius:12px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 80%,transparent);background:color-mix(in srgb,var(--md-sys-color-surface-container) 84%,transparent)}.state-card-head{display:flex;align-items:center;justify-content:space-between;gap:12px}.state-card-title{font-weight:600}.state-card-grid{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr))}.span-2{grid-column:span 2}.span-3{grid-column:1 / -1}.state-empty{color:var(--md-sys-color-on-surface-variant);font-size:13px}.state-operator-hint{display:flex;align-items:center;gap:8px;min-height:48px;padding:0 12px;border-radius:12px;color:var(--md-sys-color-on-surface-variant);background:color-mix(in srgb,var(--md-sys-color-secondary-container) 35%,transparent)}.state-operator-hint mat-icon{color:var(--md-sys-color-secondary)}.page-actions{display:flex;justify-content:flex-end}.page-error{color:var(--md-sys-color-error);font-size:13px}.page-config .mat-mdc-form-field{width:100%;--mdc-outlined-text-field-container-height: 48px;--mdc-outlined-text-field-outline-color: var(--md-sys-color-outline-variant);--mdc-outlined-text-field-hover-outline-color: var(--md-sys-color-outline);--mdc-outlined-text-field-focus-outline-color: var(--md-sys-color-primary);--mdc-outlined-text-field-error-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-error-focus-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-error-hover-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-label-text-color: var(--md-sys-color-on-surface-variant);--mdc-outlined-text-field-input-text-color: var(--md-sys-color-on-surface);--mdc-outlined-text-field-supporting-text-color: var(--md-sys-color-on-surface-variant)}.help-icon-button{margin-right:-4px}.mat-mdc-form-field-icon-suffix{align-self:center}@media(max-width:720px){.span-2,.span-3{grid-column:auto}.page-head{flex-direction:column}.state-group-head{align-items:stretch;flex-direction:column}}\n"], dependencies: [{ kind: "component", type: PagePresetComparisonComponent, selector: "praxis-page-preset-comparison", inputs: ["expanded", "layoutId", "themeId", "layoutPresetOptions", "canvas"], outputs: ["choose"] }, { 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.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { 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: MatButtonModule }, { kind: "component", type: i2.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: MatFormFieldModule }, { kind: "component", type: i6.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i6.MatLabel, selector: "mat-label" }, { kind: "directive", type: i6.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i8.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: MatIconModule }, { kind: "component", type: i2$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i3$1.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: i3$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "component", type: i9.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i9.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: MatTooltipModule }, { kind: "directive", type: i3.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: PraxisIconButtonComponent, selector: "button[praxisIconButton]", inputs: ["praxisIconButton", "size", "appearance", "presentation", "pressed", "busy"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
20401
|
+
`, isInline: true, styles: [".page-inset-field{grid-column:1 / -1;min-width:0}.page-config{display:grid;gap:12px;padding:16px;background:var(--md-sys-color-surface-container-lowest, var(--md-sys-color-surface));border:1px solid var(--md-sys-color-outline-variant);border-radius:12px}.page-head{display:flex;align-items:flex-start;gap:12px}.page-head__copy{display:grid;gap:4px}.page-title{font-weight:600;font-size:18px;line-height:1.2}.page-subtitle{max-width:72ch;color:var(--md-sys-color-on-surface-variant);font-size:13px;line-height:1.45}.spacer{flex:1}.page-summary{display:flex;flex-wrap:wrap;gap:8px}.page-tabs{border-radius:16px;overflow:hidden;background:color-mix(in srgb,var(--md-sys-color-surface-container-low) 78%,transparent);border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 82%,transparent)}.page-tabs ::ng-deep .mdc-tab{min-width:120px}.page-tabs ::ng-deep .mdc-tab__text-label{font-size:13px;font-weight:600}.page-tab-panel{display:grid;gap:12px;padding:16px 4px 4px}.page-chip{display:inline-flex;align-items:center;gap:8px;min-height:36px;padding:0 12px;border-radius:999px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 88%,transparent);background:color-mix(in srgb,var(--md-sys-color-surface-container-low) 82%,transparent);color:var(--md-sys-color-on-surface-variant);font-size:12px;font-weight:500}.page-chip mat-icon{width:18px;height:18px;font-size:18px}.page-chip--accent{border-color:color-mix(in srgb,var(--md-sys-color-primary) 35%,var(--md-sys-color-outline-variant));color:var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary-container) 30%,transparent)}.page-chip--warning{border-color:color-mix(in srgb,var(--md-sys-color-error) 35%,var(--md-sys-color-outline-variant));color:var(--md-sys-color-error);background:color-mix(in srgb,var(--md-sys-color-error-container) 35%,transparent)}.page-identity{display:grid;gap:4px;padding:12px 14px;border-radius:12px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 82%,transparent);background:color-mix(in srgb,var(--md-sys-color-surface-container-low) 70%,transparent);font-size:13px;color:var(--md-sys-color-on-surface-variant)}.page-section{font-weight:600;margin-top:8px}.page-section-help{margin-top:-4px;color:var(--md-sys-color-on-surface-variant);font-size:13px;line-height:1.45}.grid-form{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr))}.state-grid{display:grid;gap:16px}.state-values,.state-group{padding:14px;border:1px solid var(--md-sys-color-outline-variant);border-radius:14px;background:linear-gradient(180deg,color-mix(in srgb,var(--md-sys-color-surface-container-low) 92%,transparent),transparent),var(--md-sys-color-surface-container-lowest, var(--md-sys-color-surface))}.state-group{display:grid;gap:12px}.state-group-head{display:flex;align-items:center;justify-content:space-between;gap:12px}.state-group-title{font-weight:600}.state-group-help{color:var(--md-sys-color-on-surface-variant);font-size:13px}.state-list{display:grid;gap:12px}.state-card{display:grid;gap:12px;padding:12px;border-radius:12px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline-variant) 80%,transparent);background:color-mix(in srgb,var(--md-sys-color-surface-container) 84%,transparent)}.state-card-head{display:flex;align-items:center;justify-content:space-between;gap:12px}.state-card-title{font-weight:600}.state-card-grid{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr))}.span-2{grid-column:span 2}.span-3{grid-column:1 / -1}.state-empty{color:var(--md-sys-color-on-surface-variant);font-size:13px}.state-operator-hint{display:flex;align-items:center;gap:8px;min-height:48px;padding:0 12px;border-radius:12px;color:var(--md-sys-color-on-surface-variant);background:color-mix(in srgb,var(--md-sys-color-secondary-container) 35%,transparent)}.state-operator-hint mat-icon{color:var(--md-sys-color-secondary)}.page-actions{display:flex;justify-content:flex-end}.page-error{color:var(--md-sys-color-error);font-size:13px}.page-config .mat-mdc-form-field{width:100%;--mdc-outlined-text-field-container-height: 48px;--mdc-outlined-text-field-outline-color: var(--md-sys-color-outline-variant);--mdc-outlined-text-field-hover-outline-color: var(--md-sys-color-outline);--mdc-outlined-text-field-focus-outline-color: var(--md-sys-color-primary);--mdc-outlined-text-field-error-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-error-focus-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-error-hover-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-label-text-color: var(--md-sys-color-on-surface-variant);--mdc-outlined-text-field-input-text-color: var(--md-sys-color-on-surface);--mdc-outlined-text-field-supporting-text-color: var(--md-sys-color-on-surface-variant)}.help-icon-button{margin-right:-4px}.mat-mdc-form-field-icon-suffix{align-self:center}@media(max-width:720px){.span-2,.span-3{grid-column:auto}.page-head{flex-direction:column}.state-group-head{align-items:stretch;flex-direction:column}}\n"], dependencies: [{ kind: "component", type: PagePresetComparisonComponent, selector: "praxis-page-preset-comparison", inputs: ["expanded", "layoutId", "themeId", "layoutPresetOptions", "canvas"], outputs: ["choose"] }, { 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.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { 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: MatButtonModule }, { kind: "component", type: i2.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: MatFormFieldModule }, { kind: "component", type: i7.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i7.MatLabel, selector: "mat-label" }, { kind: "directive", type: i7.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i9.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: MatIconModule }, { kind: "component", type: i2$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i3$1.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: i3$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "component", type: i9$1.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i9$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: MatTooltipModule }, { kind: "directive", type: i3.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: PraxisIconButtonComponent, selector: "button[praxisIconButton]", inputs: ["praxisIconButton", "size", "appearance", "presentation", "pressed", "busy"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
20071
20402
|
}
|
|
20072
20403
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PageConfigEditorComponent, decorators: [{
|
|
20073
20404
|
type: Component,
|
|
@@ -21987,6 +22318,10 @@ const PRAXIS_PAGE_BUILDER_AUTHORING_MANIFEST = {
|
|
|
21987
22318
|
background: { type: 'string' }, borderColor: { type: 'string' }, borderWidth: { type: 'string', description: 'CSS border width; accepts per-side values.' }, borderStyle: { type: 'string', description: 'CSS border style: solid, dashed, dotted, double, none; accepts per-side values.' }, borderRadius: { type: 'string' }, shadow: { type: 'string' },
|
|
21988
22319
|
backdropFilter: { type: 'string', description: 'CSS backdrop effect for a translucent card, e.g. blur(16px) saturate(125%). none disables it.' },
|
|
21989
22320
|
fallbackBackground: { type: 'string', description: 'Opaque background for reduced transparency, unsupported blur and fullscreen/expanded modes.' },
|
|
22321
|
+
interactionHighlight: {
|
|
22322
|
+
type: 'string', enum: ['standard', 'gradient'],
|
|
22323
|
+
description: 'Editing selection and keyboard-focus highlight. Absence in the saved shell inherits the preset, then standard. This operation merges partial input: omission preserves an existing override; use the visual editor to remove it. Gradient uses theme colors; never changes child inputs.',
|
|
22324
|
+
},
|
|
21990
22325
|
} },
|
|
21991
22326
|
header: { type: 'object', properties: {
|
|
21992
22327
|
background: { type: 'string' }, borderColor: { type: 'string' },
|
|
@@ -30802,7 +31137,8 @@ class DynamicPageBuilderComponent {
|
|
|
30802
31137
|
return;
|
|
30803
31138
|
const canvas = this.currentPage()?.canvas;
|
|
30804
31139
|
const ref = this.dialog.open(ComponentPaletteDialogComponent, {
|
|
30805
|
-
width: '
|
|
31140
|
+
width: '780px',
|
|
31141
|
+
maxWidth: '94vw',
|
|
30806
31142
|
data: {
|
|
30807
31143
|
title: this.tx('connections.palette.insertComponent', 'Insert component'),
|
|
30808
31144
|
allowedWidgetIds: this.componentPaletteAllowedWidgetIds || undefined,
|
|
@@ -31436,7 +31772,7 @@ class DynamicPageBuilderComponent {
|
|
|
31436
31772
|
emptyText: this.tx('agentic.projectKnowledgeCockpit.timelineEmpty', 'No safe timeline events published for this change-set yet.'),
|
|
31437
31773
|
});
|
|
31438
31774
|
const timelineNode = document.nodes.find((node) => node.type === 'timeline');
|
|
31439
|
-
return timelineNode?.type === 'timeline' ? timelineNode.items : [];
|
|
31775
|
+
return timelineNode?.type === 'timeline' ? timelineNode.items ?? [] : [];
|
|
31440
31776
|
}
|
|
31441
31777
|
trackProjectKnowledgeTimelineItem(index, item) {
|
|
31442
31778
|
return item.id || `${index}:${item.title || item.badge || 'timeline'}`;
|